slovo/scripts/render-stdlib-api-doc.js
2026-05-22 12:46:41 +02:00

129 lines
4.0 KiB
JavaScript
Executable File

#!/usr/bin/env node
"use strict";
const fs = require("fs");
const path = require("path");
const repoRoot = path.resolve(__dirname, "..");
const stdDir = path.join(repoRoot, "lib", "std");
const outputPath = path.join(repoRoot, "docs", "language", "STDLIB_API.md");
const cargoTomlPath = path.join(repoRoot, "compiler", "Cargo.toml");
function tokenize(source) {
const tokens = [];
const pattern = /\(|\)|[^\s()]+/g;
let match;
while ((match = pattern.exec(source)) !== null) {
tokens.push(match[0]);
}
return tokens;
}
function parseList(tokens, cursor) {
if (tokens[cursor.index] !== "(") {
throw new Error(`expected list at token ${cursor.index}`);
}
cursor.index += 1;
const list = [];
while (cursor.index < tokens.length && tokens[cursor.index] !== ")") {
if (tokens[cursor.index] === "(") {
list.push(parseList(tokens, cursor));
} else {
list.push(tokens[cursor.index]);
cursor.index += 1;
}
}
if (tokens[cursor.index] !== ")") {
throw new Error("unterminated list");
}
cursor.index += 1;
return list;
}
function moduleForm(source, file) {
const tokens = tokenize(source);
if (tokens[0] !== "(") {
throw new Error(`${file}: expected module form`);
}
const form = parseList(tokens, { index: 0 });
if (form[0] !== "module" || typeof form[1] !== "string") {
throw new Error(`${file}: expected (module name ...) form`);
}
const exportForm = form.find((item) => Array.isArray(item) && item[0] === "export");
if (!exportForm) {
throw new Error(`${file}: module must have an explicit export list`);
}
return {
name: form[1],
exports: exportForm.slice(1),
};
}
function stdModules() {
return fs
.readdirSync(stdDir)
.filter((name) => name.endsWith(".slo"))
.sort()
.map((fileName) => {
const relativePath = path.join("lib", "std", fileName);
const source = fs.readFileSync(path.join(stdDir, fileName), "utf8");
const module = moduleForm(source, relativePath);
return {
fileName,
relativePath,
name: module.name,
exports: module.exports,
};
});
}
function packageVersion() {
const cargoToml = fs.readFileSync(cargoTomlPath, "utf8");
const versionMatch = cargoToml.match(/^version\s*=\s*"([^"]+)"$/m);
if (!versionMatch) {
throw new Error("compiler/Cargo.toml must declare a package version");
}
return versionMatch[1];
}
function render(modules, version) {
const totalExports = modules.reduce((count, module) => count + module.exports.length, 0);
const out = [];
out.push("# Slovo Standard Library API Catalog");
out.push("");
out.push("Generated from `lib/std/*.slo` by `scripts/render-stdlib-api-doc.sh`.");
out.push("Do not edit this file by hand.");
out.push("");
out.push("## Stability Tiers");
out.push("");
out.push("- `beta-supported`: exported from `lib/std` and covered by source-search, promotion, or facade gates in the current beta line.");
out.push(`- \`experimental\`: not used for exported \`lib/std\` helpers in \`${version}\`; future releases may mark new helpers this way before they graduate.`);
out.push("- `internal`: helper names that are not exported from their module; they are intentionally omitted from this catalog.");
out.push("");
out.push("The catalog is a beta compatibility aid, not a stable `1.0.0` API freeze.");
out.push("");
out.push("## Summary");
out.push("");
out.push(`- Modules: ${modules.length}`);
out.push(`- Exported helpers: ${totalExports}`);
out.push("- Default tier: `beta-supported`");
out.push("");
out.push("## Modules");
out.push("");
for (const module of modules) {
out.push(`### std.${module.name}`);
out.push("");
out.push(`- Path: \`${module.relativePath}\``);
out.push("- Tier: `beta-supported`");
out.push(`- Exported helpers: ${module.exports.length}`);
out.push("");
for (const exported of module.exports) {
out.push(`- \`${exported}\``);
}
out.push("");
}
return `${out.join("\n")}\n`;
}
fs.writeFileSync(outputPath, render(stdModules(), packageVersion()));