diff --git a/.vscode/cozy-and-windy/cozy-and-windy-0.1.0.vsix b/.vscode/cozy-and-windy/cozy-and-windy-0.1.0.vsix index 37769ea..5851d74 100644 Binary files a/.vscode/cozy-and-windy/cozy-and-windy-0.1.0.vsix and b/.vscode/cozy-and-windy/cozy-and-windy-0.1.0.vsix differ diff --git a/.vscode/cozy-and-windy/themes/cozy-and-windy-color-theme.json b/.vscode/cozy-and-windy/themes/cozy-and-windy-color-theme.json index 27a76e2..af67d93 100644 --- a/.vscode/cozy-and-windy/themes/cozy-and-windy-color-theme.json +++ b/.vscode/cozy-and-windy/themes/cozy-and-windy-color-theme.json @@ -3,8 +3,12 @@ "type": "dark", "semanticHighlighting": true, "colors": { - "editor.background": "#121212", - "editor.foreground": "#c6b2a3", + // 121212 + // 111212 + // 211f1e + // 191817 + "editor.background": "#191817", + "editor.foreground": "#dfc6ba", "editor.lineHighlightBackground": "#1c1c1c", "editor.selectionBackground": "#164371", "editor.selectionForeground": "#c8c8c8", @@ -13,15 +17,15 @@ "editorIndentGuide.background1": "#181818", "editorIndentGuide.activeBackground1": "#202020", "editorRuler.foreground": "#505050", - "editorGutter.background": "#121212", + "editorGutter.background": "#211f1e", "editorBracketMatch.background": "#3b514d", - "editor.foldBackground": "#0c0c0c81", - "editor.wordHighlightBackground": "#1212124d", + "editor.foldBackground": "#0c0c0c6a", + "editor.wordHighlightBackground": "#211f1e4d", "editor.wordHighlightStrongBackground": "#303030", "editorCursor.foreground": "#00fff4", "editorWhitespace.foreground": "#181818", // "editorLineHighlightBorder": "#1c1c1c", - "editorWidget.background": "#121212", + "editorWidget.background": "#211f1e", "editorSuggestWidget.background": "#2c334b", "editorHoverWidget.background": "#2c334b" }, @@ -30,16 +34,17 @@ "keyword": { "foreground": "#d8bd5b" }, "string": { "foreground": "#d46a54" }, "number": { "foreground": "#b5cea8" }, - "operator": { "foreground": "#54cfcf" }, + "operator": { "foreground": "#be8e78" }, "class": { "foreground": "#54a4d6" }, "struct": { "foreground": "#54a4d6" }, "enum": { "foreground": "#54a4d6" }, "type": { "foreground": "#54a4d6" }, "interface": { "foreground": "#7984ab" }, "function": { "foreground": "#cccab5" }, + // "function": { "foreground": "#6090a9" }, "method": { "foreground": "#6090a9" }, "variable": { "foreground": "#bc966c" }, - "parameter": { "foreground": "#867660" }, + "parameter": { "foreground": "#ce8365" }, "property": { "foreground": "#acb8c8" }, "*.static": { "foreground": "#9e95c6" }, "macro": { "foreground": "#5ea852" }, @@ -48,25 +53,29 @@ "enumMember": { "foreground": "#a373b0" }, "label": { "foreground": "#c8c8c8", "fontStyle": "bold" }, "tapeAtomKeyword": { "foreground": "#d8bd5b", "fontStyle": "bold" }, - "tapeAtomName": { "foreground": "#cccab5", "fontStyle": "bold" }, + "tapeAtomName": { "foreground": "#b1b7d6", "fontStyle": "bold" }, "tapeComponentKeyword": { "foreground": "#d68a36", "fontStyle": "bold" }, - "tapeComponentName": { "foreground": "#6090a9", "fontStyle": "bold" }, + "tapeComponentName": { "foreground": "#b1b7d6", "fontStyle": "bold" }, "tapeAnnotation": { "foreground": "#d8bd5b" }, "tapeBindType": { "foreground": "#54a4d6" }, "tapePhase": { "foreground": "#b8d7a3", "fontStyle": "italic" }, "tapeLabel": { "foreground": "#959595", "fontStyle": "bold" }, - "tapeCpuInstruction": { "foreground": "#6090a9" }, - "tapeGteInstruction": { "foreground": "#9e95c6" }, + // "tapeCpuInstruction": { "foreground": "#6d9aa0" }, + // "tapeCpuInstruction": { "foreground": "#cf7539" }, + // "tapeCpuInstruction": { "foreground": "#d16b3a" }, + "tapeCpuInstruction": { "foreground": "#d5895a" }, + "tapeGteInstruction": { "foreground": "#988bcb" }, "tapeGpuInstruction": { "foreground": "#bf7dac" }, "tapeComponentInstruction": { "foreground": "#8baa5d" }, - "tapeGprRegister": { "foreground": "#bc966c" }, - "tapeCop2Register": { "foreground": "#9e95c6" }, + // "tapeGprRegister": { "foreground": "#92d4d9" }, + "tapeGprRegister": { "foreground": "#a2bfa8" }, + "tapeCop2Register": { "foreground": "#945cd9" }, "tapeDuffleType": { "foreground": "#54a4d6" }, "tapeAttribute": { "foreground": "#73a07c" }, - "tapeGprRegister.tapeRead": { "foreground": "#8baa5d", "fontStyle": "italic" }, - "tapeGprRegister.tapeWrite": { "foreground": "#d68a36", "fontStyle": "bold" }, - "tapeCop2Register.tapeRead": { "foreground": "#a373b0", "fontStyle": "italic" }, - "tapeCop2Register.tapeWrite": { "foreground": "#d46a54", "fontStyle": "bold" }, + "tapeGprRegister.tapeRead": { "foreground": "#5bb8b0", "fontStyle": "italic" }, + "tapeGprRegister.tapeWrite": { "foreground": "#2d8f8c", "fontStyle": "bold" }, + "tapeCop2Register.tapeRead": { "foreground": "#b08ae0", "fontStyle": "italic" }, + "tapeCop2Register.tapeWrite": { "foreground": "#7b3ec4", "fontStyle": "bold" }, // "*.tapeAuto": { }, "tapeControlFlow": { "foreground": "#63d169", "fontStyle": "bold" }, "tapeDelaySlot": { "foreground": "#ff5647" } @@ -77,20 +86,21 @@ { "scope": ["string", "string.quoted"], "settings": { "foreground": "#d46a54" } }, { "scope": ["string.quoted.other"], "settings": { "foreground": "#d69d85" } }, { "scope": ["constant.numeric"], "settings": { "foreground": "#b5cea8" } }, - { "scope": ["punctuation", "keyword.operator"], "settings": { "foreground": "#00ffff" } }, - { "scope": ["keyword.operator.overload"], "settings": { "foreground": "#00ff64" } }, + { "scope": ["punctuation", "keyword.operator"], "settings": { "foreground": "#be8e78" } }, + { "scope": ["keyword.operator.overload"], "settings": { "foreground": "#b87e76" } }, { "scope": ["entity.name.type", "entity.name.type.class", "entity.name.type.struct", "entity.name.type.enum"], "settings": { "foreground": "#54a4d6" } }, { "scope": ["entity.name.type.interface"], "settings": { "foreground": "#7984ab" } }, { "scope": ["entity.name.function"], "settings": { "foreground": "#cccab5" } }, { "scope": ["entity.name.function.member"], "settings": { "foreground": "#6090a9" } }, { "scope": ["variable.other.local"], "settings": { "foreground": "#bc966c" } }, - { "scope": ["variable.parameter"], "settings": { "foreground": "#867660", "fontStyle": "underline" } }, + { "scope": ["variable.parameter"], "settings": { "foreground": "#ce8365" } }, { "scope": ["variable.other.property"], "settings": { "foreground": "#acb8c8" } }, { "scope": ["variable.other.constant"], "settings": { "foreground": "#9e95c6" } }, { "scope": ["variable.other.global", "variable.other.defaultLibrary"], "settings": { "foreground": "#bf7dac" } }, { "scope": ["support.type", "support.function"], "settings": { "foreground": "#8baa5d" } }, { "scope": ["meta.preprocessor"], "settings": { "foreground": "#5ea852" } }, - { "scope": ["variable.parameter.preprocessor"], "settings": { "foreground": "#636363" } }, + // { "scope": ["variable.parameter.preprocessor"], "settings": { "foreground": "#636363" } }, + { "scope": ["variable.parameter.preprocessor"], "settings": { "foreground": "#bc966c" } }, { "scope": ["keyword.control.directive"], "settings": { "foreground": "#d68a36" } }, { "scope": ["entity.name.namespace"], "settings": { "foreground": "#8e8e8e" } }, { "scope": ["entity.name.type.parameter"], "settings": { "foreground": "#b8d7a3" } }, @@ -108,14 +118,14 @@ { "scope": ["entity.name.type.duffle.bind"], "settings": { "foreground": "#54a4d6" } }, { "scope": ["entity.name.tag.duffle.phase"], "settings": { "foreground": "#b8d7a3", "fontStyle": "italic" } }, { "scope": ["entity.name.label.duffle.atom"], "settings": { "foreground": "#c8c8c8", "fontStyle": "bold" } }, - { "scope": ["support.function.duffle.cpu"], "settings": { "foreground": "#6090a9" } }, - { "scope": ["support.function.duffle.gte"], "settings": { "foreground": "#9e95c6" } }, + { "scope": ["support.function.duffle.cpu"], "settings": { "foreground": "#6d9aa0" } }, + { "scope": ["support.function.duffle.gte"], "settings": { "foreground": "#988bcb" } }, { "scope": ["support.function.duffle.gpu"], "settings": { "foreground": "#bf7dac" } }, { "scope": ["support.function.duffle.component"], "settings": { "foreground": "#8baa5d" } }, { "scope": ["keyword.control.duffle.branch"], "settings": { "foreground": "#76ff7d", "fontStyle": "bold" } }, { "scope": ["keyword.operator.duffle.delayslot"], "settings": { "foreground": "#ff5647" } }, - { "scope": ["variable.other.constant.duffle.gpr"], "settings": { "foreground": "#bc966c" } }, - { "scope": ["variable.other.constant.duffle.cop2"], "settings": { "foreground": "#9e95c6" } }, + { "scope": ["variable.other.constant.duffle.gpr"], "settings": { "foreground": "#3fa8a6" } }, + { "scope": ["variable.other.constant.duffle.cop2"], "settings": { "foreground": "#945cd9" } }, { "scope": ["storage.type.duffle.type"], "settings": { "foreground": "#54a4d6" } }, { "scope": ["storage.modifier.duffle.attr"], "settings": { "foreground": "#73a07c" } } ] diff --git a/.vscode/tape-atom-syntax/atomasm-psx-0.3.0.vsix b/.vscode/tape-atom-syntax/atomasm-psx-0.3.0.vsix index a2562d9..e42f4f3 100644 Binary files a/.vscode/tape-atom-syntax/atomasm-psx-0.3.0.vsix and b/.vscode/tape-atom-syntax/atomasm-psx-0.3.0.vsix differ diff --git a/.vscode/tape-atom-syntax/classifier.js b/.vscode/tape-atom-syntax/classifier.js index 244eb0e..d4ad0a3 100644 --- a/.vscode/tape-atom-syntax/classifier.js +++ b/.vscode/tape-atom-syntax/classifier.js @@ -23,6 +23,7 @@ const TOKEN_TYPES = [ "tapeDuffleType", "tapeAttribute", "keyword", + "macro", ]; const TOKEN_MODIFIERS = ["declaration", "tapeRead", "tapeWrite", "tapeAuto"]; @@ -38,11 +39,14 @@ const ANNOTATIONS = new Set([ ]); const DSL_KEYWORDS = new Set([ - "FI_", "I_", "NI_", "Relative_", "Struct_", "Enum_", "Union_", - "TypeR_", "TypeV_", "align_", "internal", "local_persist", "global", + "FI_", "I_", "NI_", "Relative_", "Struct_", "Enum_", "Union_", "Array_", + "Slice_", "TypeR_", "TypeV_", "align_", "internal", "local_persist", "global", + "RO_", "LP_", "gknown", "expect_", "cexpr_", + "asm", "asm_words", "asm_rpins", "asm_clobber", "O_", "S_", "C_", "T_", "tmpl", "glue", "r_", "v_", "tr_", "tv_", - "rgcc", "rlit", "r_use", "r_set", "r_mod", "r_imm", "r_mem", - "enc_op", "enc_rs", "enc_rt", "enc_rd", "enc_imm", "enc_i", "enc_r", + "rgcc", "r_use", "r_set", "r_mod", "r_imm", "r_mem", + "u1_", "u2_", "u4_", "u8_", "s1_", "s2_", "s4_", "s8_", + "u1_r", "u2_r", "u4_r", "u8_r", "u1_v", "u2_v", "u4_v", "u8_v", ]); const DELAY_SLOT_KEYWORDS = new Set(["LdSlot_", "BdSlot_"]); @@ -67,15 +71,22 @@ function registerType(name, index) { function instructionType(name, index) { const domain = index.macros.get(name); + if (domain === "control") return "tapeControlFlow"; if (domain === "cpu") return "tapeCpuInstruction"; if (domain === "gte") return "tapeGteInstruction"; if (domain === "gpu") return "tapeGpuInstruction"; - if (domain === "component") return "tapeComponentInstruction"; + if (domain === "component") { + if (/^mac_gte_/.test(name)) return "tapeGteInstruction"; + if (/^mac_gp/.test(name)) return "tapeGpuInstruction"; + if (/^mac_/.test(name)) return "tapeComponentInstruction"; + return "macro"; + } + if (domain === "utility") return "macro"; if (/^gte_(?!cr_)/.test(name)) return "tapeGteInstruction"; if (/^gp[01]_/.test(name)) return "tapeGpuInstruction"; if (/^mac_gte_/.test(name)) return "tapeGteInstruction"; if (/^mac_gp/.test(name)) return "tapeGpuInstruction"; - if (/^mac_/.test(name)) return "tapeCpuInstruction"; + if (/^mac_/.test(name)) return "tapeComponentInstruction"; return null; } @@ -123,7 +134,7 @@ function classifyDocument(source, filePath, workspaceIndex, shouldCancel = () => } else if (ATOM_KEYWORDS.has(token.text)) { type = "tapeAtomKeyword"; } else if (COMPONENT_KEYWORDS.has(token.text)) { - type = "tapeComponentKeyword"; + type = "keyword"; } else if (ANNOTATIONS.has(token.text)) { type = "tapeAnnotation"; } else if (context && context.callee === "atom_bind" && context.argIndex === 0) { @@ -159,19 +170,21 @@ function classifyDocument(source, filePath, workspaceIndex, shouldCancel = () => } if (!type && index.bindTypes.has(token.text)) type = "tapeBindType"; - if (!type && index.types.has(token.text)) type = "tapeDuffleType"; if (!type && DSL_KEYWORDS.has(token.text)) type = "keyword"; + if (!type && index.types.has(token.text)) type = "tapeDuffleType"; if (!type && index.attributes.has(token.text)) type = "tapeAttribute"; if (!type) type = registerType(token.text, index); if (!type && DELAY_SLOT_KEYWORDS.has(token.text)) type = "tapeDelaySlot"; if (!type) { const domain = index.macros.get(token.text); - if (domain && CONTROL_FLOW_PREFIXES.test(token.text)) { + if (domain === "control" || (domain && CONTROL_FLOW_PREFIXES.test(token.text))) { type = "tapeControlFlow"; } } if (!type && isRegUseAccess(scanned.tokens, tokenIndex)) type = "tapeGprRegister"; if (!type) type = instructionType(token.text, index); + if (!type && /^(?:Slice_|A[0-9]+_)/.test(token.text)) type = "tapeDuffleType"; + if (!type && /_[RV]$/.test(token.text)) type = "tapeDuffleType"; if (!type && index.atoms.has(token.text)) type = "tapeAtomName"; if (!type && index.components.has(token.text)) type = "tapeComponentName"; if (!type && index.phases.has(token.text)) type = "tapePhase"; diff --git a/.vscode/tape-atom-syntax/package.json b/.vscode/tape-atom-syntax/package.json index 394d8ed..3bbed10 100644 --- a/.vscode/tape-atom-syntax/package.json +++ b/.vscode/tape-atom-syntax/package.json @@ -39,7 +39,8 @@ { "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" }, - { "id": "keyword", "description": "Standard keyword (DSL built-in macros)" } + { "id": "keyword", "description": "Standard keyword (DSL built-in macros)" }, + { "id": "macro", "description": "Standard macro (utility #define with no instruction domain)" } ], "semanticTokenModifiers": [ { "id": "tapeRead", "description": "Register declared in atom_reads" }, @@ -68,7 +69,8 @@ "tapeCop2Register": ["variable.other.constant.duffle.cop2"], "tapeDuffleType": ["storage.type.duffle.type"], "tapeAttribute": ["storage.modifier.duffle.attr"], - "keyword": ["keyword"] + "keyword": ["keyword"], + "macro": ["entity.name.function.preprocessor"] } } ], diff --git a/.vscode/tape-atom-syntax/source-index.js b/.vscode/tape-atom-syntax/source-index.js index e8581c0..0e5cb07 100644 --- a/.vscode/tape-atom-syntax/source-index.js +++ b/.vscode/tape-atom-syntax/source-index.js @@ -8,12 +8,21 @@ const BASE_TYPES = [ "U1", "U2", "U4", "U8", "MipsAtom", "MipsCode", "Reg", ]; +const C_BUILTINS = new Set([ + "void", "type", "char", "short", "int", "long", "float", "double", + "unsigned", "signed", "bool", "size_t", "uint8_t", "uint16_t", "uint32_t", + "int8_t", "int16_t", "int32_t", +]); + const BASE_ATTRIBUTES = [ - "FI_", "I_", "NI_", "Relative_", "Struct_", "Enum_", "Union_", - "TypeR_", "TypeV_", "align_", "internal", "local_persist", "global", + "FI_", "I_", "NI_", "Relative_", "Struct_", "Enum_", "Union_", "Array_", + "Slice_", "TypeR_", "TypeV_", "align_", "internal", "local_persist", "global", + "RO_", "LP_", "gknown", "expect_", "cexpr_", + "asm", "asm_words", "asm_rpins", "asm_clobber", "O_", "S_", "C_", "T_", "tmpl", "glue", "r_", "v_", "tr_", "tv_", - "rgcc", "rlit", "r_use", "r_set", "r_mod", "r_imm", "r_mem", - "enc_op", "enc_rs", "enc_rt", "enc_rd", "enc_imm", "enc_i", "enc_r", + "rgcc", "r_use", "r_set", "r_mod", "r_imm", "r_mem", + "u1_", "u2_", "u4_", "u8_", "s1_", "s2_", "s4_", "s8_", + "u1_r", "u2_r", "u4_r", "u8_r", "u1_v", "u2_v", "u4_v", "u8_v", ]; function createIndex() { @@ -28,6 +37,7 @@ function createIndex() { phases: new Set(), labels: new Set(), attributes: new Set(BASE_ATTRIBUTES), + componentCallees: new Map(), }; } @@ -38,6 +48,7 @@ function cloneIndex(source) { } for (const [name, domain] of source.macros) result.macros.set(name, domain); for (const [name, domain] of source.registers) result.registers.set(name, domain); + for (const [name, callees] of source.componentCallees) result.componentCallees.set(name, callees.slice()); return result; } @@ -48,18 +59,78 @@ function mergeIndexes(...sources) { for (const key of ["atoms", "components", "componentAliases", "bindTypes", "types", "phases", "labels", "attributes"]) { for (const value of source[key]) result[key].add(value); } - for (const [name, domain] of source.macros) result.macros.set(name, domain); + for (const [name, domain] of source.macros) { + const existing = result.macros.get(name); + if (!existing || domainRank(domain) >= domainRank(existing)) result.macros.set(name, domain); + } for (const [name, domain] of source.registers) result.registers.set(name, domain); + for (const [name, callees] of source.componentCallees) { + const existing = result.componentCallees.get(name) || []; + result.componentCallees.set(name, existing.concat(callees)); + } } - return result; + return resolveComponentDomains(result); } function domainFromPath(filePath) { const base = path.basename(filePath.replaceAll("\\", "/")).toLowerCase(); - if (base === "mips.h" || base === "mips.atom.c") return "cpu"; - if (base === "gte.h" || base === "gte.atom.c") return "gte"; - if (base === "gp.h" || base === "gp.atom.c") return "gpu"; - return "component"; + if (base === "mips.h") return "cpu"; + if (base === "gte.h") return "gte"; + if (base === "gp.h") return "gpu"; + return null; +} + +function prefixDomain(name) { + if (/^(?:branch_|jump_|call_)/.test(name)) return "control"; + if (/^gte_(?!cr_)/.test(name) || name.startsWith("mac_gte_") || name.startsWith("ac_gte_")) return "gte"; + if (/^gp[01]_/.test(name) || name.startsWith("mac_gp_") || name.startsWith("ac_gp_")) return "gpu"; + return null; +} + +function collectBraceIdentifiers(tokens, openBraceIndex) { + const names = []; + let depth = 0; + for (let tokenIndex = openBraceIndex; tokenIndex < tokens.length; tokenIndex += 1) { + if (tokens[tokenIndex].text === "{") depth += 1; + if (tokens[tokenIndex].text === "}") { + depth -= 1; + if (depth === 0) break; + } + if (tokens[tokenIndex].kind === "identifier") names.push(tokens[tokenIndex].text); + } + return names; +} + +function resolveComponentDomains(index) { + const hardwareRank = { cpu: 1, gpu: 2, gte: 3, control: 4 }; + let changed = true; + while (changed) { + changed = false; + for (const [alias, callees] of index.componentCallees) { + let best = index.macros.get(alias) || "component"; + let bestRank = hardwareRank[best] || 0; + for (const callee of callees) { + const domain = prefixDomain(callee) || index.macros.get(callee); + const rank = hardwareRank[domain] || 0; + if (rank > bestRank) { + best = domain; + bestRank = rank; + } + } + if (bestRank > 0 && index.macros.get(alias) !== best) { + index.macros.set(alias, best); + changed = true; + } + } + } + return index; +} + +function domainRank(domain) { + if (domain === "control") return 4; + if (domain === "cpu" || domain === "gte" || domain === "gpu") return 3; + if (domain === "component") return 2; + return 1; } function registerKind(name) { @@ -103,22 +174,23 @@ 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, componentDomain(token.text)); + index.macros.set(alias, prefixDomain(alias) || prefixDomain(token.text) || "component"); } } + function bindComponentCallees(alias, callees) { + if (!alias) return; + index.componentAliases.add(alias); + index.componentCallees.set(alias, callees); + if (!index.macros.has(alias)) index.macros.set(alias, "component"); + } + for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex += 1) { const token = tokens[tokenIndex]; if (token.kind !== "identifier") continue; @@ -173,8 +245,19 @@ function scanSource(source, filePath) { 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 if (/^(?:ac_|mac_)/.test(name.text)) { + const alias = name.text.startsWith("ac_") ? componentAlias(name.text) : name.text; + const rest = []; + for (let restIndex = tokenIndex + 2; restIndex < tokens.length && tokens[restIndex].line === name.line; restIndex += 1) { + if (tokens[restIndex].kind === "identifier") rest.push(tokens[restIndex].text); + } + if (alias) { + index.componentAliases.add(alias); + index.macros.set(alias, prefixDomain(alias) || "component"); + if (rest.length) index.componentCallees.set(alias, rest); + } } else { - index.macros.set(name.text, domain); + index.macros.set(name.text, domain || "utility"); } } } @@ -185,10 +268,10 @@ function scanSource(source, filePath) { let lastIdentifier = null; while (endIndex < tokens.length && tokens[endIndex].text !== ";") { if (tokens[endIndex].text === "{") hasBrace = true; - if (tokens[endIndex].kind === "identifier") lastIdentifier = tokens[endIndex]; + if (tokens[endIndex].kind === "identifier" && !C_BUILTINS.has(tokens[endIndex].text)) lastIdentifier = tokens[endIndex]; endIndex += 1; } - if (!hasBrace && lastIdentifier) { + if (!hasBrace && lastIdentifier && !C_BUILTINS.has(lastIdentifier.text)) { index.types.add(lastIdentifier.text); mark(lastIdentifier, "duffleType"); } @@ -213,7 +296,27 @@ function scanSource(source, filePath) { } for (const call of balanced.calls) { - if (domain === "component") continue; + if (call.callee === "MipsAtomComp_") { + const name = tokens[call.openTokenIndex + 1]; + const brace = tokens[call.closeTokenIndex + 1]; + if (name && name.kind === "identifier" && brace && brace.text === "{") { + bindComponentCallees(componentAlias(name.text), collectBraceIdentifiers(tokens, call.closeTokenIndex + 1)); + } + } + if (call.callee === "MipsAtomComp_Proc_") { + const functionName = findFunctionNameBefore(tokens, call.calleeTokenIndex); + let braceIndex = -1; + for (let tokenIndex = call.openTokenIndex + 1; tokenIndex < call.closeTokenIndex; tokenIndex += 1) { + if (tokens[tokenIndex].text === "{") { + braceIndex = tokenIndex; + break; + } + } + if (functionName && braceIndex >= 0) { + bindComponentCallees(componentAlias(functionName.text), collectBraceIdentifiers(tokens, braceIndex)); + } + } + if (!domain) continue; const name = tokens[call.calleeTokenIndex]; const after = tokens[call.closeTokenIndex + 1]; if (!name || !after || after.text !== "{") continue; @@ -221,7 +324,7 @@ function scanSource(source, filePath) { } return { - index: cloneIndex(index), + index: resolveComponentDomains(cloneIndex(index)), declarations, tokens, contexts, @@ -233,5 +336,6 @@ module.exports = { createIndex, domainFromPath, mergeIndexes, + resolveComponentDomains, scanSource, }; diff --git a/.vscode/tape-atom-syntax/syntaxes/tape_atom.tmLanguage.json b/.vscode/tape-atom-syntax/syntaxes/tape_atom.tmLanguage.json index c95c834..fa768a2 100644 --- a/.vscode/tape-atom-syntax/syntaxes/tape_atom.tmLanguage.json +++ b/.vscode/tape-atom-syntax/syntaxes/tape_atom.tmLanguage.json @@ -29,11 +29,11 @@ { "match": "\\b(MipsAtomComp_)\\s*\\(\\s*(ac_[A-Za-z0-9_]*)", "captures": { - "1": { "name": "keyword.control.duffle.component" }, + "1": { "name": "keyword" }, "2": { "name": "entity.name.function.duffle.component" } } }, - { "match": "\\bMipsAtomComp_Proc_\\b", "name": "keyword.control.duffle.component" } + { "match": "\\bMipsAtomComp_Proc_\\b", "name": "keyword" } ] }, "annotation-arguments": { @@ -64,7 +64,7 @@ "name": "storage.type.duffle.type" }, "attributes": { - "match": "\\b(?:FI_|I_|NI_|Relative_|align_|internal|local_persist|global|O_|S_|C_|T_|tmpl|glue|r_|v_|tr_|tv_|rgcc|rlit|r_use|r_set|r_mod|r_imm|r_mem|enc_op|enc_rs|enc_rt|enc_rd|enc_imm|enc_i|enc_r)\\b", + "match": "\\b(?:FI_|I_|NI_|Relative_|Struct_|Enum_|Union_|Array_|Slice_|TypeR_|TypeV_|align_|internal|local_persist|global|RO_|LP_|gknown|expect_|cexpr_|asm|asm_words|asm_rpins|asm_clobber|O_|S_|C_|T_|tmpl|glue|r_|v_|tr_|tv_|rgcc|r_use|r_set|r_mod|r_imm|r_mem|u[1248]_|u[1248]_r|u[1248]_v|s[1248]_)\\b", "name": "keyword" } } diff --git a/.vscode/tape-atom-syntax/test/classifier.test.js b/.vscode/tape-atom-syntax/test/classifier.test.js index 12628c0..9a67fb3 100644 --- a/.vscode/tape-atom-syntax/test/classifier.test.js +++ b/.vscode/tape-atom-syntax/test/classifier.test.js @@ -45,7 +45,7 @@ test("classifyDocument separates CPU, GTE, GPU, and component domains", () => { workspace.macros.set("load_word", "cpu"); workspace.macros.set("gte_cmdw_rtpt", "gte"); workspace.macros.set("gp1_word_DisplayOn", "gpu"); - workspace.macros.set("mac_yield", "component"); + workspace.macros.set("mac_yield", "control"); workspace.componentAliases.add("mac_yield"); const source = "load_word(R_T0, R_T1, 0), gte_cmdw_rtpt, gp1_word_DisplayOn(), mac_yield(), C2_MAC0, gte_cr_OFX_Code"; @@ -54,20 +54,55 @@ test("classifyDocument separates CPU, GTE, GPU, and component domains", () => { assert.equal(byText(result, "load_word")[0].type, "tapeCpuInstruction"); assert.equal(byText(result, "gte_cmdw_rtpt")[0].type, "tapeGteInstruction"); assert.equal(byText(result, "gp1_word_DisplayOn")[0].type, "tapeGpuInstruction"); - assert.equal(byText(result, "mac_yield")[0].type, "tapeComponentInstruction"); + assert.equal(byText(result, "mac_yield")[0].type, "tapeControlFlow"); assert.equal(byText(result, "C2_MAC0")[0].type, "tapeCop2Register"); assert.equal(byText(result, "gte_cr_OFX_Code")[0].type, "tapeCop2Register"); }); +test("component invocations keep the domain resolved from their emitted instructions", () => { + const workspace = createIndex(); + workspace.macros.set("mac_load_word_imm", "cpu"); + workspace.macros.set("mac_gcmd_push", "gpu"); + workspace.macros.set("mac_gte_store_f3", "gte"); + workspace.macros.set("mac_load_v3s4", "cpu"); + + const source = "mac_load_word_imm(dst, imm), mac_gcmd_push(cmd), mac_gte_store_f3(cursor), mac_load_v3s4()"; + const result = classifyDocument(source, "C:/x/code/hello_camera/hello_camera.atom.c", workspace); + + assert.equal(byText(result, "mac_load_word_imm")[0].type, "tapeCpuInstruction"); + assert.equal(byText(result, "mac_gcmd_push")[0].type, "tapeGpuInstruction"); + assert.equal(byText(result, "mac_gte_store_f3")[0].type, "tapeGteInstruction"); + assert.equal(byText(result, "mac_load_v3s4")[0].type, "tapeCpuInstruction"); +}); + +test("utility macros without a hardware domain use the standard macro token", () => { + const workspace = createIndex(); + workspace.macros.set("load_word", "cpu"); + workspace.macros.set("assert", "utility"); + workspace.macros.set("stringify", "utility"); + workspace.macros.set("u4_hi", "utility"); + + const source = "load_word(R_T0, R_T1, 0), assert(ok), stringify(name), u4_hi(imm)"; + const result = classifyDocument(source, "C:/x/code/hello_camera/hello_camera.c", workspace); + + assert.equal(byText(result, "load_word")[0].type, "tapeCpuInstruction"); + assert.equal(byText(result, "assert")[0].type, "macro"); + assert.equal(byText(result, "stringify")[0].type, "macro"); + assert.equal(byText(result, "u4_hi")[0].type, "macro"); +}); + test("document-local declarations override an empty workspace index", () => { const source = [ "MipsAtomComp_(ac_new_component) { nop };", + "MipsAtomComp_Proc_(ab, { nop })", "mac_new_component(),", ].join("\n"); const result = classifyDocument(source, "C:/x/code/duffle/math.atom.c", createIndex()); + assert.equal(byText(result, "MipsAtomComp_")[0].type, "keyword"); + assert.equal(byText(result, "MipsAtomComp_Proc_")[0].type, "keyword"); assert.equal(byText(result, "ac_new_component")[0].type, "tapeComponentName"); - assert.equal(byText(result, "mac_new_component")[0].type, "tapeCpuInstruction"); + assert.equal(byText(result, "mac_new_component")[0].type, "tapeComponentInstruction"); }); test("classifier returns ordered non-overlapping spans and partial malformed output", () => { diff --git a/.vscode/tape-atom-syntax/test/contract.test.js b/.vscode/tape-atom-syntax/test/contract.test.js index 06c2c19..4aae797 100644 --- a/.vscode/tape-atom-syntax/test/contract.test.js +++ b/.vscode/tape-atom-syntax/test/contract.test.js @@ -57,7 +57,7 @@ test("every semantic token has a scope mapping; DSL-specific tokens also have gr const grammarScopes = collectScopeNames(grammar); const grammarRequired = new Set([ - "tapeAtomKeyword", "tapeAtomName", "tapeComponentKeyword", "tapeComponentName", + "tapeAtomKeyword", "tapeAtomName", "tapeComponentName", "tapeAnnotation", "tapeBindType", "tapePhase", "tapeLabel", "tapeDelaySlot", "tapeDuffleType", "keyword", ]); diff --git a/.vscode/tape-atom-syntax/test/source-index.test.js b/.vscode/tape-atom-syntax/test/source-index.test.js index c662737..7f281fd 100644 --- a/.vscode/tape-atom-syntax/test/source-index.test.js +++ b/.vscode/tape-atom-syntax/test/source-index.test.js @@ -26,7 +26,8 @@ test("scanSource discovers current atom and component forms", () => { assert.equal(result.index.components.has("ac_store_pair"), true); assert.equal(result.index.componentAliases.has("mac_load_pair"), true); assert.equal(result.index.componentAliases.has("mac_store_pair"), true); - assert.equal(result.index.macros.get("mac_store_pair"), "cpu"); + assert.equal(result.index.macros.get("mac_store_pair"), "component"); + assert.equal(result.index.componentCallees.get("mac_store_pair").includes("store_word"), true); assert.equal(result.index.phases.has("cube_g4"), true); assert.equal(result.index.registers.get("R_PrimCursor"), "gpr"); assert.deepEqual(result.errors, []); @@ -58,12 +59,68 @@ test("scanSource discovers binds, labels, registers, typedefs, and macro domains }); test("domainFromPath uses the declaration file rather than parent directory names", () => { - assert.equal(domainFromPath("C:/x/code/hello_gte/hello_gte.atom.c"), "component"); + assert.equal(domainFromPath("C:/x/code/hello_gte/hello_gte.atom.c"), null); assert.equal(domainFromPath("C:/x/code/duffle/mips.h"), "cpu"); - assert.equal(domainFromPath("C:/x/code/duffle/gte.atom.c"), "gte"); + assert.equal(domainFromPath("C:/x/code/duffle/gte.h"), "gte"); assert.equal(domainFromPath("C:/x/code/duffle/gp.h"), "gpu"); }); +test("component aliases inherit the domain of the instructions they emit", () => { + const headers = mergeIndexes( + scanSource("#define load_word(a,b,c) 1\n#define store_word(a,b,c) 1\n#define shift_aright_var(a,b,c) 1\n#define jump_reg(rd) 1\n", "C:/x/code/duffle/mips.h").index, + scanSource("#define gte_sw(rt, base, off) 1\n", "C:/x/code/duffle/gte.h").index + ); + const math = scanSource( + [ + "MipsAtomComp_(ac_load_v3s4) { load_word(R_T0, R_T1, 0) };", + "#define mac_load_p3s4 mac_load_v3s4", + ].join("\n"), + "C:/x/code/duffle/math.atom.c" + ); + const shift = scanSource( + "MipsAtomComp_(ac_shift_aright_var_v3_self) { shift_aright_var(R_T0, R_T0, R_T1) };", + "C:/x/code/duffle/gte.atom.c" + ); + const gte = scanSource( + "MipsAtomComp_(ac_gte_store_f3) { gte_sw(C2_SXY0, R_T0, 0) };", + "C:/x/code/duffle/gte.atom.c" + ); + const yieldAtom = scanSource( + "MipsAtomComp_(ac_yield) { load_word(R_AtomJmp, R_TapePtr, 0), jump_reg(R_AtomJmp), nop };", + "C:/x/code/duffle/lottes_tape.h" + ); + + const merged = mergeIndexes(headers, math.index, shift.index, gte.index, yieldAtom.index); + assert.equal(merged.macros.get("mac_load_v3s4"), "cpu"); + assert.equal(merged.macros.get("mac_load_p3s4"), "cpu"); + assert.equal(merged.macros.get("mac_shift_aright_var_v3_self"), "cpu"); + assert.equal(merged.macros.get("mac_gte_store_f3"), "gte"); + assert.equal(merged.macros.get("mac_yield"), "control"); +}); + +test("scanSource tags utility header defines as utility, not a hardware domain", () => { + const source = [ + "#define assert(cond) ((void)(cond))", + "#define stringify(name) #name", + "#define u4_hi(imm) ((imm) >> 16)", + ].join("\n"); + const result = scanSource(source, "C:/projects/Pikuma/ps1/code/duffle/dsl.h"); + + assert.equal(result.index.macros.get("assert"), "utility"); + assert.equal(result.index.macros.get("stringify"), "utility"); + assert.equal(result.index.macros.get("u4_hi"), "utility"); +}); + +test("mergeIndexes prefers a hardware domain over a later utility define", () => { + const left = createIndex(); + left.macros.set("sub_s", "utility"); + const right = createIndex(); + right.macros.set("sub_s", "cpu"); + + assert.equal(mergeIndexes(left, right).macros.get("sub_s"), "cpu"); + assert.equal(mergeIndexes(right, left).macros.get("sub_s"), "cpu"); +}); + test("mergeIndexes preserves domain-specific aliases", () => { const left = createIndex(); left.macros.set("load_word", "cpu");