mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-10-25 15:05:25 +00:00
327 lines
12 KiB
JavaScript
327 lines
12 KiB
JavaScript
"use strict";
|
||
// doctools - generate the toolchain/platform reference doc from the tool
|
||
// registry (src/common/toolmeta.ts), the worker platform params
|
||
// (src/worker/platforms.ts), and the same filename->tool selector the IDE and
|
||
// CLI use (src/tools/testlib.ts). FOR TESTING/DEV ONLY.
|
||
//
|
||
// npm run doctools
|
||
//
|
||
// Writes src/docs/toolchains.md. Everything is derived except:
|
||
// - the extra ".a" extension (the 6502 selector's implicit fallback)
|
||
// - the platform list in src/platform/_index.ts (parsed for ids + aliases)
|
||
// - two platforms with no PLATFORM_PARAMS entry (basic, zmachine)
|
||
// If this doc is wrong, one of those is probably out of date.
|
||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||
if (k2 === undefined) k2 = k;
|
||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||
}
|
||
Object.defineProperty(o, k2, desc);
|
||
}) : (function(o, m, k, k2) {
|
||
if (k2 === undefined) k2 = k;
|
||
o[k2] = m[k];
|
||
}));
|
||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||
}) : function(o, v) {
|
||
o["default"] = v;
|
||
});
|
||
var __importStar = (this && this.__importStar) || (function () {
|
||
var ownKeys = function(o) {
|
||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||
var ar = [];
|
||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||
return ar;
|
||
};
|
||
return ownKeys(o);
|
||
};
|
||
return function (mod) {
|
||
if (mod && mod.__esModule) return mod;
|
||
var result = {};
|
||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||
__setModuleDefault(result, mod);
|
||
return result;
|
||
};
|
||
})();
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
const fs = __importStar(require("fs"));
|
||
const toolmeta_1 = require("../common/toolmeta");
|
||
const platforms_1 = require("../worker/platforms");
|
||
const testlib_1 = require("./testlib");
|
||
const util_1 = require("../common/util");
|
||
const OUT_FILE = 'src/docs/toolchains.md';
|
||
const INDEX_SRC = 'src/platform/_index.ts';
|
||
const BANNER = '<!-- generated by `npm run doctools`; do not edit by hand -->';
|
||
// extensions the selector accepts but that no ToolMeta lists: the 6502
|
||
// fallback is dasm's historical ".a".
|
||
const EXTRA_EXTENSIONS = ['.a'];
|
||
// models/aliases that shouldn't get a row of their own
|
||
const SKIP_PLATFORMS = new Set(['gameboy', 'base_z80']);
|
||
function hex(n) {
|
||
return '$' + n.toString(16).toUpperCase().padStart(4, '0');
|
||
}
|
||
/** Parse src/platform/_index.ts for canonical ids and aliases. */
|
||
function readPlatformIndex() {
|
||
var text = fs.readFileSync(INDEX_SRC, 'utf-8');
|
||
var out = [];
|
||
var re = /case\s+"([^"]+)"\s*:\s*return\s+import\("([^"]+)"\)/g;
|
||
var m;
|
||
while ((m = re.exec(text)))
|
||
out.push({ id: m[1], mod: m[2] });
|
||
return out;
|
||
}
|
||
function collectPlatforms() {
|
||
// canonical ids first, keyed by module so aliases collapse
|
||
var byMod = {};
|
||
var aliases = {};
|
||
for (var { id, mod } of readPlatformIndex()) {
|
||
if (!(mod in byMod))
|
||
byMod[mod] = id;
|
||
else
|
||
aliases[id] = byMod[mod];
|
||
}
|
||
var infos = [];
|
||
for (var id of Object.values(byMod)) {
|
||
infos.push({ id, root: (0, util_1.getRootBasePlatform)(id), alias: false });
|
||
}
|
||
// plus every platform/params key the build system can address
|
||
for (var id of Object.keys(platforms_1.PLATFORM_PARAMS)) {
|
||
if (SKIP_PLATFORMS.has(id))
|
||
continue;
|
||
if (infos.some(p => p.id === id))
|
||
continue;
|
||
infos.push({ id, root: (0, util_1.getRootBasePlatform)(id), alias: false });
|
||
}
|
||
// record alias -> canonical for the appendix
|
||
for (var a of Object.keys(aliases)) {
|
||
if (SKIP_PLATFORMS.has(a))
|
||
continue;
|
||
infos.push({ id: a, root: (0, util_1.getRootBasePlatform)(aliases[a]), alias: true, aliasOf: aliases[a] });
|
||
}
|
||
infos.sort((a, b) => a.id.localeCompare(b.id));
|
||
return infos;
|
||
}
|
||
function paramsFor(id) {
|
||
return platforms_1.PLATFORM_PARAMS[id]
|
||
|| platforms_1.PLATFORM_PARAMS[(0, util_1.getBasePlatform)(id)]
|
||
|| platforms_1.PLATFORM_PARAMS[(0, util_1.getRootBasePlatform)(id)];
|
||
}
|
||
function archOf(id) {
|
||
var p = paramsFor(id);
|
||
if (p && p.arch)
|
||
return p.arch;
|
||
if (id === 'basic')
|
||
return 'basic';
|
||
if (id === 'zmachine')
|
||
return 'zmachine';
|
||
return 'unknown';
|
||
}
|
||
//// tool selection
|
||
/**
|
||
* Filename->tool, preferring the platform selector and patching the two
|
||
* platforms that have no PLATFORM_PARAMS entry (so the arch dispatch can't
|
||
* see them). This mirrors what each platform class does.
|
||
*/
|
||
function toolFor(ext, id) {
|
||
if (id === 'basic')
|
||
return ext === '.bas' ? 'basic' : null;
|
||
if (id === 'zmachine')
|
||
return ext === '.dg' ? 'dialog' : ext === '.inf' ? 'inform6' : null;
|
||
return (0, testlib_1.getToolForFilename)('x' + ext, id);
|
||
}
|
||
function extUniverse() {
|
||
var set = new Set();
|
||
for (var id in toolmeta_1.TOOL_META)
|
||
for (var e of toolmeta_1.TOOL_META[id].extensions || [])
|
||
if (e)
|
||
set.add(e);
|
||
for (var e of EXTRA_EXTENSIONS)
|
||
set.add(e);
|
||
return [...set].sort((a, b) => a.localeCompare(b));
|
||
}
|
||
/** Map of extension -> tool for a platform, keeping only real matches. */
|
||
function cellsFor(id, exts) {
|
||
var cells = new Map();
|
||
var arch = archOf(id);
|
||
for (var ext of exts) {
|
||
var tool = toolFor(ext, id);
|
||
if (!tool)
|
||
continue;
|
||
var meta = (0, toolmeta_1.getToolMeta)(tool);
|
||
var is6502 = arch === '6502' || arch === 'huc6280';
|
||
var claims = !!meta && (meta.extensions || []).includes(ext);
|
||
var implicitDasm = ext === '.a' && tool === 'dasm' && is6502;
|
||
if (claims || implicitDasm)
|
||
cells.set(ext, tool);
|
||
}
|
||
return cells;
|
||
}
|
||
//// rendering
|
||
function mdTool(id) {
|
||
return '`' + id + '`';
|
||
}
|
||
function renderTools() {
|
||
var lines = [];
|
||
lines.push('## Tools', '');
|
||
lines.push('| ID | Name | Kind | Arch | Version | Extensions |');
|
||
lines.push('| --- | --- | --- | --- | --- | --- |');
|
||
var ids = Object.keys(toolmeta_1.TOOL_META).sort();
|
||
for (var id of ids) {
|
||
var m = toolmeta_1.TOOL_META[id];
|
||
var exts = (m.extensions || []).length ? m.extensions.map(e => '`' + e + '`').join(' ') : '—';
|
||
var version = (0, toolmeta_1.getToolVersionLabel)(m);
|
||
var toollink = m.helpURL ? `[${m.name}](${m.helpURL})` : m.name;
|
||
lines.push(`| ${mdTool(m.id)} | ${toollink} | ${m.kind} | ${m.arch || '—'} | ${version} | ${exts} |`);
|
||
}
|
||
lines.push('');
|
||
return lines.join('\n');
|
||
}
|
||
const ARCH_ORDER = ['6502', 'huc6280', 'z80', 'gbz80', '6809', 'arm32', 'x86', 'verilog', 'zmachine', 'basic'];
|
||
const ARCH_LABEL = {
|
||
'6502': '6502',
|
||
'huc6280': '6502 (HuC6280)',
|
||
'z80': 'Z80',
|
||
'gbz80': 'Game Boy (SM83)',
|
||
'6809': '6809',
|
||
'arm32': 'ARM32',
|
||
'x86': 'x86',
|
||
'verilog': 'Verilog / HDL',
|
||
'zmachine': 'Z-machine',
|
||
'basic': 'BASIC',
|
||
};
|
||
/** A platform with no overrides, used to describe the arch defaults. */
|
||
const ARCH_REFERENCE = {
|
||
'6502': 'kim1',
|
||
'huc6280': 'pce',
|
||
'z80': 'mw8080bw',
|
||
'gbz80': 'gb',
|
||
'6809': 'vectrex',
|
||
'arm32': 'arm32',
|
||
'x86': 'x86',
|
||
'verilog': 'verilog',
|
||
'zmachine': 'zmachine',
|
||
'basic': 'basic',
|
||
};
|
||
function fmtCells(cells) {
|
||
var parts = [];
|
||
for (var [ext, tool] of cells)
|
||
parts.push('`' + ext + '`→' + tool);
|
||
return parts.join(' · ');
|
||
}
|
||
function overridesFor(id, cells, def) {
|
||
var parts = [];
|
||
for (var [ext, tool] of cells) {
|
||
if (def.get(ext) !== tool)
|
||
parts.push('`' + ext + '`→' + tool);
|
||
}
|
||
for (var [ext] of def) {
|
||
if (!cells.has(ext))
|
||
parts.push('`' + ext + '`→—');
|
||
}
|
||
return parts.length ? parts.join(' · ') : '—';
|
||
}
|
||
function renderPlatforms(infos) {
|
||
var exts = extUniverse();
|
||
var lines = [];
|
||
lines.push('## Platforms', '');
|
||
lines.push('Grouped by CPU family. The **Default** line lists the arch\'s');
|
||
lines.push('extension→tool mapping; the table only shows what each platform');
|
||
lines.push('changes. `Memory` is the layout from `PLATFORM_PARAMS` (start+size).');
|
||
lines.push('');
|
||
for (var arch of ARCH_ORDER) {
|
||
var group = infos.filter(p => !p.alias && archOf(p.id) === arch);
|
||
if (!group.length)
|
||
continue;
|
||
lines.push('### ' + (ARCH_LABEL[arch] || arch), '');
|
||
var ref = ARCH_REFERENCE[arch];
|
||
var def = ref ? cellsFor(ref, exts) : new Map();
|
||
if (def.size)
|
||
lines.push('**Default** (' + mdTool(ref) + '): ' + fmtCells(def), '');
|
||
lines.push('| Platform | Root | Overrides | cfgfile | defines |');
|
||
lines.push('| --- | --- | --- | --- | --- |');
|
||
for (var p of group) {
|
||
var cells = cellsFor(p.id, exts);
|
||
var params = paramsFor(p.id) || {};
|
||
var root = p.root === p.id ? '—' : mdTool(p.root);
|
||
var cfg = params.cfgfile ? '`' + params.cfgfile + '`' : '—';
|
||
var defs = (params.define || []).join(' ') || '—';
|
||
lines.push(`| ${mdTool(p.id)} | ${root} | ${overridesFor(p.id, cells, def)} | ${cfg} | ${defs} |`);
|
||
}
|
||
lines.push('');
|
||
}
|
||
return lines.join('\n');
|
||
}
|
||
function renderMatrix(infos) {
|
||
var exts = extUniverse();
|
||
var lines = [];
|
||
lines.push('## Platform × extension → tool', '');
|
||
lines.push('The canonical extension mapping. Empty cells mean the extension is');
|
||
lines.push('not accepted on that platform.', '');
|
||
for (var arch of ARCH_ORDER) {
|
||
var group = infos.filter(p => !p.alias && archOf(p.id) === arch);
|
||
if (!group.length)
|
||
continue;
|
||
// only columns that someone in the group uses
|
||
var cols = exts.filter(e => group.some(p => cellsFor(p.id, exts).has(e)));
|
||
if (!cols.length)
|
||
continue;
|
||
lines.push('### ' + (ARCH_LABEL[arch] || arch), '');
|
||
lines.push('| Platform | ' + cols.map(c => '`' + c + '`').join(' | ') + ' |');
|
||
lines.push('| --- | ' + cols.map(() => '---').join(' | ') + ' |');
|
||
for (var p of group) {
|
||
var cells = cellsFor(p.id, exts);
|
||
var row = cols.map(e => cells.get(e) || '·');
|
||
lines.push(`| ${mdTool(p.id)} | ${row.join(' | ')} |`);
|
||
}
|
||
lines.push('');
|
||
}
|
||
return lines.join('\n');
|
||
}
|
||
function renderAliases(infos) {
|
||
var aliases = infos.filter(p => p.alias);
|
||
if (!aliases.length)
|
||
return '';
|
||
var lines = [];
|
||
lines.push('## Aliases', '');
|
||
lines.push('| Id | Loads |');
|
||
lines.push('| --- | --- |');
|
||
for (var a of aliases)
|
||
lines.push(`| ${mdTool(a.id)} | ${mdTool(a.aliasOf)} |`);
|
||
lines.push('');
|
||
return lines.join('\n');
|
||
}
|
||
//// main
|
||
function main() {
|
||
var all = collectPlatforms();
|
||
// An arch we can't determine (no PLATFORM_PARAMS entry and no special case)
|
||
// means we also can't say how it would select a tool, so it's left out.
|
||
// Today that's the ambiguous roots: atari8, channelf, devel, exidy, sms,
|
||
// sound_konami, vector. Their variants (atari8-800xl, sms-sms-libcv, ...)
|
||
// have params and do appear.
|
||
var infos = all.filter(p => ARCH_ORDER.includes(archOf(p.id)));
|
||
var skipped = all.filter(p => !infos.includes(p));
|
||
if (skipped.length)
|
||
console.log('skipping (unknown arch): ' + skipped.map(p => p.id).join(', '));
|
||
var out = [];
|
||
out.push(BANNER);
|
||
out.push('');
|
||
out.push('# Toolchains & Platforms');
|
||
out.push('');
|
||
//out.push('Auto-generated from `src/common/toolmeta.ts`, `src/worker/platforms.ts`,');
|
||
//out.push('and the filename→tool selector in `src/tools/testlib.ts`. Run');
|
||
//out.push('`npm run doctools` after changing any of them.');
|
||
//out.push('');
|
||
out.push('A source file\'s **extension** picks a tool, and the **platform**');
|
||
out.push('disambiguates extensions several tools share (`.c`, `.s`, `.bas`).');
|
||
out.push('');
|
||
out.push(renderTools());
|
||
out.push(renderPlatforms(infos));
|
||
out.push(renderMatrix(infos));
|
||
out.push(renderAliases(infos));
|
||
var text = out.join('\n').replace(/\n{3,}/g, '\n\n') + '\n';
|
||
fs.writeFileSync(OUT_FILE, text);
|
||
console.log(`wrote ${OUT_FILE} (${text.split('\n').length} lines, ${infos.filter(p => !p.alias).length} platforms)`);
|
||
}
|
||
main();
|
||
//# sourceMappingURL=doctools.js.map
|