more theme stuff

This commit is contained in:
ed
2026-08-14 21:48:52 -04:00
parent 9ca865d5db
commit 7ec778a68e
11 changed files with 236 additions and 63 deletions
+5 -1
View File
@@ -64,7 +64,9 @@ function instructionType(name, index) {
if (domain === "component") return "tapeComponentInstruction";
if (/^gte_(?!cr_)/.test(name)) return "tapeGteInstruction";
if (/^gp[01]_/.test(name)) return "tapeGpuInstruction";
if (/^mac_/.test(name)) return "tapeComponentInstruction";
if (/^mac_gte_/.test(name)) return "tapeGteInstruction";
if (/^mac_gp/.test(name)) return "tapeGpuInstruction";
if (/^mac_/.test(name)) return "tapeCpuInstruction";
return null;
}
@@ -82,6 +84,8 @@ function isRegUseAccess(tokens, tokenIndex) {
if (!prev || prev.text !== ".") return false;
const prevPrev = tokens[tokenIndex - 2];
if (!prevPrev || prevPrev.kind !== "identifier") return false;
const next = tokens[tokenIndex + 1];
if (next && next.text === ".") return false;
if (prevPrev.text === "r") return true;
const prev3 = tokens[tokenIndex - 3];
const prev4 = tokens[tokenIndex - 4];
+18 -18
View File
@@ -21,24 +21,24 @@
},
"contributes": {
"semanticTokenTypes": [
{ "id": "tapeAtomKeyword", "description": "Tape atom declaration keyword" },
{ "id": "tapeAtomName", "description": "Tape atom name" },
{ "id": "tapeComponentKeyword", "description": "Tape atom component declaration keyword" },
{ "id": "tapeComponentName", "description": "Tape atom component name" },
{ "id": "tapeAnnotation", "description": "Tape atom annotation" },
{ "id": "tapeBindType", "description": "Tape bind structure type" },
{ "id": "tapePhase", "description": "Tape atom phase" },
{ "id": "tapeLabel", "description": "Tape atom branch label" },
{ "id": "tapeCpuInstruction", "description": "MIPS CPU instruction emitter" },
{ "id": "tapeControlFlow", "description": "MIPS branch or jump instruction" },
{ "id": "tapeGteInstruction", "description": "GTE instruction emitter" },
{ "id": "tapeGpuInstruction", "description": "GPU command emitter" },
{ "id": "tapeComponentInstruction", "description": "Tape atom component invocation" },
{ "id": "tapeDelaySlot", "description": "Load or branch delay slot annotation" },
{ "id": "tapeGprRegister", "description": "MIPS GPR alias" },
{ "id": "tapeCop2Register", "description": "COP2 data or control register alias" },
{ "id": "tapeDuffleType", "description": "Duffle type or type constructor" },
{ "id": "tapeAttribute", "description": "Duffle linkage or storage attribute" }
{ "id": "tapeAtomKeyword", "superType": "keyword", "description": "Tape atom declaration keyword" },
{ "id": "tapeAtomName", "superType": "function", "description": "Tape atom name" },
{ "id": "tapeComponentKeyword", "superType": "keyword", "description": "Tape atom component declaration keyword" },
{ "id": "tapeComponentName", "superType": "function", "description": "Tape atom component name" },
{ "id": "tapeAnnotation", "superType": "macro", "description": "Tape atom annotation" },
{ "id": "tapeBindType", "superType": "type", "description": "Tape bind structure type" },
{ "id": "tapePhase", "superType": "label", "description": "Tape atom phase" },
{ "id": "tapeLabel", "superType": "label", "description": "Tape atom branch label" },
{ "id": "tapeCpuInstruction", "superType": "macro", "description": "MIPS CPU instruction emitter" },
{ "id": "tapeControlFlow", "superType": "keyword", "description": "MIPS branch or jump instruction" },
{ "id": "tapeGteInstruction", "superType": "macro", "description": "GTE instruction emitter" },
{ "id": "tapeGpuInstruction", "superType": "macro", "description": "GPU command emitter" },
{ "id": "tapeComponentInstruction", "superType": "macro", "description": "Tape atom component invocation" },
{ "id": "tapeDelaySlot", "superType": "keyword", "description": "Load or branch delay slot annotation" },
{ "id": "tapeGprRegister", "superType": "variable", "description": "MIPS GPR alias" },
{ "id": "tapeCop2Register", "superType": "variable", "description": "COP2 data or control register alias" },
{ "id": "tapeDuffleType", "superType": "type", "description": "Duffle type or type constructor" },
{ "id": "tapeAttribute", "superType": "keyword", "description": "Duffle linkage or storage attribute" }
],
"semanticTokenModifiers": [
{ "id": "tapeRead", "description": "Register declared in atom_reads" },
+14 -2
View File
@@ -100,13 +100,19 @@ function scanSource(source, filePath) {
declarations.set(token.start, { role, modifiers });
}
function componentDomain(name) {
if (name.startsWith("ac_gte_") || name.startsWith("mac_gte_")) return "gte";
if (name.startsWith("ac_gp_") || name.startsWith("mac_gp_")) return "gpu";
return "cpu";
}
function addComponent(token) {
index.components.add(token.text);
mark(token, "componentName");
const alias = componentAlias(token.text);
if (alias) {
index.componentAliases.add(alias);
index.macros.set(alias, domain);
index.macros.set(alias, componentDomain(token.text));
}
}
@@ -161,7 +167,13 @@ function scanSource(source, filePath) {
if (token.text === "define" && tokens[tokenIndex - 1] && tokens[tokenIndex - 1].text === "#") {
const name = tokens[tokenIndex + 1];
if (name && name.kind === "identifier" && name.line === token.line) index.macros.set(name.text, domain);
if (name && name.kind === "identifier" && name.line === token.line) {
if (/^(?:RegUse_|Struct_|Enum_|Union_|TypeR_|TypeV_|Relative_|Binds_)/.test(name.text)) {
index.types.add(name.text);
} else {
index.macros.set(name.text, domain);
}
}
}
if (token.text === "typedef") {
+10 -24
View File
@@ -6,8 +6,7 @@
{ "include": "#component-declarations" },
{ "include": "#annotation-arguments" },
{ "include": "#annotations" },
{ "include": "#instructions" },
{ "include": "#registers" },
{ "include": "#delay-slots" },
{ "include": "#types" },
{ "include": "#attributes" }
],
@@ -34,15 +33,11 @@
"2": { "name": "entity.name.function.duffle.component" }
}
},
{ "match": "\\bMipsAtomComp_Proc_\\b", "name": "keyword.control.duffle.component" },
{ "match": "\\bac_[A-Za-z0-9_]+\\b", "name": "entity.name.function.duffle.component" }
{ "match": "\\bMipsAtomComp_Proc_\\b", "name": "keyword.control.duffle.component" }
]
},
"annotation-arguments": {
"patterns": [
{ "match": "(?<=\\batom_bind\\()\\s*Binds_[A-Za-z0-9_]+", "name": "entity.name.type.duffle.bind" },
{ "match": "(?<=\\batom_phase\\()\\s*[A-Za-z_][A-Za-z0-9_]*", "name": "entity.name.tag.duffle.phase" },
{ "match": "(?<=\\batom_label\\()\\s*[A-Za-z_][A-Za-z0-9_]*", "name": "entity.name.label.duffle.atom" },
{
"match": "\\b(atom_offset)\\s*\\(\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*,\\s*([A-Za-z_][A-Za-z0-9_]*)",
"captures": {
@@ -50,31 +45,22 @@
"2": { "name": "entity.name.label.duffle.atom" },
"3": { "name": "entity.name.label.duffle.atom" }
}
}
},
{ "match": "(?<=\\batom_bind\\()\\s*Binds_[A-Za-z0-9_]+", "name": "entity.name.type.duffle.bind" },
{ "match": "(?<=\\batom_phase\\()\\s*[A-Za-z_][A-Za-z0-9_]*", "name": "entity.name.tag.duffle.phase" },
{ "match": "(?<=\\batom_label\\()\\s*[A-Za-z_][A-Za-z0-9_]*", "name": "entity.name.label.duffle.atom" }
]
},
"annotations": {
"match": "\\b(atom_info|atom_bind|atom_reads|atom_writes|atom_label|atom_offset|atom_reg|atom_type|atom_ctx|atom_phase|atom_auto_reg|phase_auto_reg|atom_dbg_skip)\\b",
"name": "support.function.duffle.annotation"
},
"instructions": {
"patterns": [
{ "match": "\\b(branch_|jump_|jump_rel|call_)[A-Za-z0-9_]*\\b", "name": "keyword.control.duffle.branch" },
{ "match": "\\b(LdSlot_|BdSlot_)\\b", "name": "keyword.operator.duffle.delayslot" },
{ "match": "\\b(load_|store_|add_|sub_|shift_|set_lt_|and_i|or_i|xor_i|nop[0-9]*)[A-Za-z0-9_]*\\b", "name": "support.function.duffle.cpu" },
{ "match": "\\bgte_(?!cr_)[A-Za-z0-9_]+\\b", "name": "support.function.duffle.gte" },
{ "match": "\\bgp[01]_[A-Za-z0-9_]+\\b", "name": "support.function.duffle.gpu" },
{ "match": "\\bmac_[A-Za-z0-9_]+\\b", "name": "support.function.duffle.component" }
]
},
"registers": {
"patterns": [
{ "match": "\\bR_[A-Za-z0-9_]+\\b", "name": "variable.other.constant.duffle.gpr" },
{ "match": "\\b(?:C2_|gte_cr_)[A-Za-z0-9_]+\\b", "name": "variable.other.constant.duffle.cop2" }
]
"delay-slots": {
"match": "\\b(LdSlot_|BdSlot_)\\b",
"name": "keyword.operator.duffle.delayslot"
},
"types": {
"match": "\\b(?:[USFB][1248]|MipsAtom|MipsCode|Reg|Binds_[A-Za-z0-9_]+|Struct_|Enum_|Union_|TypeR_|TypeV_)\\b",
"match": "\\b(?:Binds_[A-Za-z0-9_]+|RegUse_[A-Za-z0-9_]+)\\b",
"name": "storage.type.duffle.type"
},
"attributes": {
Binary file not shown.
+1 -1
View File
@@ -67,7 +67,7 @@ test("document-local declarations override an empty workspace index", () => {
const result = classifyDocument(source, "C:/x/code/duffle/math.atom.c", createIndex());
assert.equal(byText(result, "ac_new_component")[0].type, "tapeComponentName");
assert.equal(byText(result, "mac_new_component")[0].type, "tapeComponentInstruction");
assert.equal(byText(result, "mac_new_component")[0].type, "tapeCpuInstruction");
});
test("classifier returns ordered non-overlapping spans and partial malformed output", () => {
+15 -17
View File
@@ -10,7 +10,9 @@ const { TOKEN_MODIFIERS, TOKEN_TYPES } = require("../classifier");
const ROOT = path.resolve(__dirname, "..");
function readJson(filePath) {
return JSON.parse(fs.readFileSync(filePath, "utf8"));
const raw = fs.readFileSync(filePath, "utf8");
const stripped = raw.replace(/\/\/.*$/gm, "").replace(/,\s*([}\]])/g, "$1");
return JSON.parse(stripped);
}
function collectScopeNames(value, output = new Set()) {
@@ -48,15 +50,23 @@ test("package includes runtime files only and acknowledges local-only metadata",
assert.equal(packageJson.scripts.package.includes("--skip-license"), true);
});
test("every semantic token has a TextMate fallback scope and grammar scope", () => {
test("every semantic token has a scope mapping; DSL-specific tokens also have grammar scopes", () => {
const packageJson = readJson(path.join(ROOT, "package.json"));
const grammar = readJson(path.join(ROOT, "syntaxes", "tape_atom.tmLanguage.json"));
const mappings = packageJson.contributes.semanticTokenScopes[0].scopes;
const grammarScopes = collectScopeNames(grammar);
const grammarRequired = new Set([
"tapeAtomKeyword", "tapeAtomName", "tapeComponentKeyword", "tapeComponentName",
"tapeAnnotation", "tapeBindType", "tapePhase", "tapeLabel",
"tapeDelaySlot", "tapeDuffleType", "tapeAttribute",
]);
for (const tokenType of TOKEN_TYPES) {
assert.equal(Array.isArray(mappings[tokenType]), true, `missing scope mapping: ${tokenType}`);
assert.equal(mappings[tokenType].some((scope) => grammarScopes.has(scope)), true, `grammar does not emit: ${tokenType}`);
if (grammarRequired.has(tokenType)) {
assert.equal(mappings[tokenType].some((scope) => grammarScopes.has(scope)), true, `grammar does not emit: ${tokenType}`);
}
}
});
@@ -72,22 +82,10 @@ test("TextMate offset labels stay scoped to atom_offset calls", () => {
assert.equal(offsetRule.captures[3].name, "entity.name.label.duffle.atom");
});
test("workspace enables semantic highlighting and colors every custom token", () => {
test("workspace enables semantic highlighting", () => {
const settings = readJson(path.resolve(ROOT, "..", "settings.json"));
const semantic = settings["editor.semanticTokenColorCustomizations"];
assert.equal(settings["editor.semanticHighlighting.enabled"], true);
const semantic = settings["editor.semanticTokenColorCustomizations"];
assert.equal(semantic.enabled, true);
for (const tokenType of TOKEN_TYPES) {
assert.equal(Object.hasOwn(semantic.rules, tokenType), true, `missing color: ${tokenType}`);
}
for (const rule of [
"tapeGprRegister.tapeRead",
"tapeGprRegister.tapeWrite",
"tapeCop2Register.tapeRead",
"tapeCop2Register.tapeWrite",
"*.tapeAuto",
]) {
assert.equal(Object.hasOwn(semantic.rules, rule), true, `missing modifier color: ${rule}`);
}
});