Files
8bitworkshop/gen/tools/doctools.js
T

327 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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