mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-08-15 03:58:15 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ec778a68e | ||
|
|
9ca865d5db | ||
|
|
764ded4557 |
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
# Cozy and Windy
|
||||
|
||||
Editor theme ported from the Rider scheme of the same name.
|
||||
|
||||
It colors the editor surface, C/C++ syntax, and tape-atom DSL keywords
|
||||
emitted by `local.tape-atom-syntax`. It does not change workbench chrome.
|
||||
|
||||
## Install
|
||||
|
||||
```powershell
|
||||
cd C:\projects\Pikuma\ps1\.vscode\cozy-and-windy
|
||||
npm run package
|
||||
code --install-extension .\cozy-and-windy-0.1.0.vsix --force
|
||||
```
|
||||
|
||||
Reload the window. Select **Cozy and Windy** as the color theme, or set
|
||||
`workbench.colorTheme` to `Cozy and Windy` in the PS1 workspace settings.
|
||||
|
||||
Keep `local.tape-atom-syntax` installed. This theme colors those token
|
||||
types; it does not classify them.
|
||||
|
||||
## Inspect
|
||||
|
||||
Open `hello_camera.atom.c` and run **Developer: Inspect Editor Tokens and Scopes**
|
||||
on `MipsAtom_`, an atom name, `atom_info`, `R_PrimCursor`, a `gte_*` call,
|
||||
and a `mac_*` call.
|
||||
Binary file not shown.
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "cozy-and-windy",
|
||||
"displayName": "Cozy and Windy",
|
||||
"description": "Editor theme ported from the Rider Cozy and Windy scheme. Colors C/C++ and tape-atom DSL keywords.",
|
||||
"publisher": "local",
|
||||
"version": "0.1.0",
|
||||
"engines": {
|
||||
"vscode": "^1.80.0"
|
||||
},
|
||||
"categories": [
|
||||
"Themes"
|
||||
],
|
||||
"scripts": {
|
||||
"package": "npx --yes @vscode/vsce@3.6.1 package --allow-missing-repository --skip-license --out cozy-and-windy-0.1.0.vsix"
|
||||
},
|
||||
"contributes": {
|
||||
"themes": [
|
||||
{
|
||||
"label": "Cozy and Windy",
|
||||
"uiTheme": "vs-dark",
|
||||
"path": "./themes/cozy-and-windy-color-theme.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"name": "Cozy and Windy",
|
||||
"type": "dark",
|
||||
"semanticHighlighting": true,
|
||||
"colors": {
|
||||
"editor.background": "#121212",
|
||||
"editor.foreground": "#D25A6EFF",
|
||||
"editor.lineHighlightBackground": "#1c1c1c",
|
||||
"editor.selectionBackground": "#164371",
|
||||
"editor.selectionForeground": "#c8c8c8",
|
||||
"editorLineNumber.foreground": "#43c3c3",
|
||||
"editorLineNumber.activeForeground": "#00fff4",
|
||||
"editorIndentGuide.background1": "#181818",
|
||||
"editorIndentGuide.activeBackground1": "#202020",
|
||||
"editorRuler.foreground": "#505050",
|
||||
"editorGutter.background": "#121212",
|
||||
"editorBracketMatch.background": "#3b514d",
|
||||
"editor.foldBackground": "#0c0c0c",
|
||||
"editor.wordHighlightBackground": "#121212",
|
||||
"editor.wordHighlightStrongBackground": "#303030",
|
||||
"editorCursor.foreground": "#00fff4",
|
||||
"editorWhitespace.foreground": "#181818",
|
||||
"editorLineHighlightBorder": "#1c1c1c",
|
||||
"editorWidget.background": "#121212",
|
||||
"editorSuggestWidget.background": "#2c334b",
|
||||
"editorHoverWidget.background": "#2c334b"
|
||||
},
|
||||
"semanticTokenColors": {
|
||||
"comment": { "foreground": "#868686", "fontStyle": "italic" },
|
||||
"keyword": { "foreground": "#d8bd5b" },
|
||||
"string": { "foreground": "#d46a54" },
|
||||
"number": { "foreground": "#b5cea8" },
|
||||
"operator": { "foreground": "#00ffff" },
|
||||
"class": { "foreground": "#54a4d6" },
|
||||
"struct": { "foreground": "#54a4d6" },
|
||||
"enum": { "foreground": "#54a4d6" },
|
||||
"type": { "foreground": "#54a4d6" },
|
||||
"interface": { "foreground": "#7984ab" },
|
||||
"function": { "foreground": "#cccab5" },
|
||||
"method": { "foreground": "#6090a9" },
|
||||
"variable": { "foreground": "#bc966c" },
|
||||
"parameter": { "foreground": "#867660", "fontStyle": "underline" },
|
||||
"property": { "foreground": "#acb8c8" },
|
||||
"*.static": { "foreground": "#9e95c6" },
|
||||
"macro": { "foreground": "#5ea852" },
|
||||
"namespace": { "foreground": "#8e8e8e" },
|
||||
"typeParameter": { "foreground": "#b8d7a3" },
|
||||
"enumMember": { "foreground": "#a373b0" },
|
||||
"label": { "foreground": "#c8c8c8", "fontStyle": "bold" },
|
||||
"tapeAtomKeyword": { "foreground": "#d8bd5b", "fontStyle": "bold" },
|
||||
"tapeAtomName": { "foreground": "#cccab5", "fontStyle": "bold" },
|
||||
"tapeComponentKeyword": { "foreground": "#d68a36", "fontStyle": "bold" },
|
||||
"tapeComponentName": { "foreground": "#6090a9", "fontStyle": "bold" },
|
||||
"tapeAnnotation": { "foreground": "#d8bd5b" },
|
||||
"tapeBindType": { "foreground": "#54a4d6" },
|
||||
"tapePhase": { "foreground": "#b8d7a3", "fontStyle": "italic" },
|
||||
"tapeLabel": { "foreground": "#c8c8c8", "fontStyle": "bold" },
|
||||
"tapeCpuInstruction": { "foreground": "#6090a9" },
|
||||
"tapeGteInstruction": { "foreground": "#9e95c6" },
|
||||
"tapeGpuInstruction": { "foreground": "#bf7dac" },
|
||||
"tapeComponentInstruction": { "foreground": "#8baa5d" },
|
||||
"tapeGprRegister": { "foreground": "#bc966c" },
|
||||
"tapeCop2Register": { "foreground": "#9e95c6" },
|
||||
"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" },
|
||||
"*.tapeAuto": { "fontStyle": "underline" },
|
||||
"tapeControlFlow": { "foreground": "#76ff7d", "fontStyle": "bold" },
|
||||
"tapeDelaySlot": { "foreground": "#ff5647" }
|
||||
},
|
||||
"tokenColors": [
|
||||
{ "scope": ["comment", "comment.block", "comment.line", "comment.block.documentation"], "settings": { "foreground": "#868686", "fontStyle": "italic" } },
|
||||
{ "scope": ["keyword", "keyword.control", "keyword.other"], "settings": { "foreground": "#d8bd5b" } },
|
||||
{ "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": ["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.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": ["keyword.control.directive"], "settings": { "foreground": "#d68a36" } },
|
||||
{ "scope": ["entity.name.namespace"], "settings": { "foreground": "#8e8e8e" } },
|
||||
{ "scope": ["entity.name.type.parameter"], "settings": { "foreground": "#b8d7a3" } },
|
||||
{ "scope": ["variable.other.enummember"], "settings": { "foreground": "#a373b0" } },
|
||||
{ "scope": ["entity.name.type.concept"], "settings": { "foreground": "#76ff7d" } },
|
||||
{ "scope": ["entity.name.type.dependent"], "settings": { "foreground": "#448b5a", "fontStyle": "bold" } },
|
||||
{ "scope": ["entity.name.label"], "settings": { "foreground": "#c8c8c8", "fontStyle": "bold" } },
|
||||
{ "scope": ["invalid"], "settings": { "foreground": "#ff5647" } },
|
||||
{ "scope": ["keyword.codetag.todo"], "settings": { "foreground": "#c10000", "fontStyle": "bold italic" } },
|
||||
{ "scope": ["keyword.control.duffle.atom"], "settings": { "foreground": "#d8bd5b", "fontStyle": "bold" } },
|
||||
{ "scope": ["entity.name.function.duffle.atom"], "settings": { "foreground": "#cccab5", "fontStyle": "bold" } },
|
||||
{ "scope": ["keyword.control.duffle.component"], "settings": { "foreground": "#d68a36", "fontStyle": "bold" } },
|
||||
{ "scope": ["entity.name.function.duffle.component"], "settings": { "foreground": "#6090a9", "fontStyle": "bold" } },
|
||||
{ "scope": ["support.function.duffle.annotation"], "settings": { "foreground": "#d8bd5b" } },
|
||||
{ "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.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": ["storage.type.duffle.type"], "settings": { "foreground": "#54a4d6" } },
|
||||
{ "scope": ["storage.modifier.duffle.attr"], "settings": { "foreground": "#73a07c" } }
|
||||
]
|
||||
}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
"use strict";
|
||||
|
||||
const { nearestCall } = require("./lexer");
|
||||
const { mergeIndexes, scanSource } = require("./source-index");
|
||||
|
||||
const TOKEN_TYPES = [
|
||||
"tapeAtomKeyword",
|
||||
"tapeAtomName",
|
||||
"tapeComponentKeyword",
|
||||
"tapeComponentName",
|
||||
"tapeAnnotation",
|
||||
"tapeBindType",
|
||||
"tapePhase",
|
||||
"tapeLabel",
|
||||
"tapeCpuInstruction",
|
||||
"tapeControlFlow",
|
||||
"tapeGteInstruction",
|
||||
"tapeGpuInstruction",
|
||||
"tapeComponentInstruction",
|
||||
"tapeDelaySlot",
|
||||
"tapeGprRegister",
|
||||
"tapeCop2Register",
|
||||
"tapeDuffleType",
|
||||
"tapeAttribute",
|
||||
];
|
||||
|
||||
const TOKEN_MODIFIERS = ["declaration", "tapeRead", "tapeWrite", "tapeAuto"];
|
||||
const TOKEN_TYPE_INDEX = new Map(TOKEN_TYPES.map((name, index) => [name, index]));
|
||||
const TOKEN_MODIFIER_INDEX = new Map(TOKEN_MODIFIERS.map((name, index) => [name, index]));
|
||||
|
||||
const ATOM_KEYWORDS = new Set(["MipsAtom_", "MipsAtom_Proc_"]);
|
||||
const COMPONENT_KEYWORDS = new Set(["MipsAtomComp_", "MipsAtomComp_Proc_"]);
|
||||
const ANNOTATIONS = new Set([
|
||||
"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",
|
||||
]);
|
||||
|
||||
const DELAY_SLOT_KEYWORDS = new Set(["LdSlot_", "BdSlot_"]);
|
||||
|
||||
const CONTROL_FLOW_PREFIXES = /^(?:branch_|jump_|call_)/;
|
||||
|
||||
const ROLE_TO_TYPE = {
|
||||
atomName: "tapeAtomName",
|
||||
componentName: "tapeComponentName",
|
||||
bindType: "tapeBindType",
|
||||
duffleType: "tapeDuffleType",
|
||||
gprRegister: "tapeGprRegister",
|
||||
cop2Register: "tapeCop2Register",
|
||||
};
|
||||
|
||||
function registerType(name, index) {
|
||||
const kind = index.registers.get(name);
|
||||
if (kind === "gpr" || /^R_[A-Za-z0-9_]+$/.test(name)) return "tapeGprRegister";
|
||||
if (kind === "cop2" || /^(?:C2_|gte_cr_)[A-Za-z0-9_]+$/.test(name)) return "tapeCop2Register";
|
||||
return null;
|
||||
}
|
||||
|
||||
function instructionType(name, index) {
|
||||
const domain = index.macros.get(name);
|
||||
if (domain === "cpu") return "tapeCpuInstruction";
|
||||
if (domain === "gte") return "tapeGteInstruction";
|
||||
if (domain === "gpu") return "tapeGpuInstruction";
|
||||
if (domain === "component") return "tapeComponentInstruction";
|
||||
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";
|
||||
return null;
|
||||
}
|
||||
|
||||
function modifierMask(modifiers) {
|
||||
let mask = 0;
|
||||
for (const modifier of modifiers) {
|
||||
const index = TOKEN_MODIFIER_INDEX.get(modifier);
|
||||
if (index !== undefined) mask |= (1 << index);
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
function isRegUseAccess(tokens, tokenIndex) {
|
||||
const prev = tokens[tokenIndex - 1];
|
||||
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];
|
||||
if (prev3 && prev3.text === "." && prev4 && prev4.kind === "identifier" && prev4.text === "r") return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function classifyDocument(source, filePath, workspaceIndex, shouldCancel = () => false) {
|
||||
const scanned = scanSource(source, filePath);
|
||||
const index = mergeIndexes(workspaceIndex, scanned.index);
|
||||
const spans = [];
|
||||
|
||||
for (let tokenIndex = 0; tokenIndex < scanned.tokens.length; tokenIndex += 1) {
|
||||
if (shouldCancel()) break;
|
||||
const token = scanned.tokens[tokenIndex];
|
||||
if (token.kind !== "identifier") continue;
|
||||
|
||||
let type = null;
|
||||
let modifiers = [];
|
||||
const declaration = scanned.declarations.get(token.start);
|
||||
const context = nearestCall(scanned.contexts, tokenIndex);
|
||||
|
||||
if (declaration) {
|
||||
type = ROLE_TO_TYPE[declaration.role] || null;
|
||||
modifiers = declaration.modifiers.slice();
|
||||
} else if (ATOM_KEYWORDS.has(token.text)) {
|
||||
type = "tapeAtomKeyword";
|
||||
} else if (COMPONENT_KEYWORDS.has(token.text)) {
|
||||
type = "tapeComponentKeyword";
|
||||
} else if (ANNOTATIONS.has(token.text)) {
|
||||
type = "tapeAnnotation";
|
||||
} else if (context && context.callee === "atom_bind" && context.argIndex === 0) {
|
||||
type = "tapeBindType";
|
||||
} else if (context && context.callee === "atom_phase" && context.argIndex === 0) {
|
||||
type = "tapePhase";
|
||||
modifiers = ["declaration"];
|
||||
} else if (context && context.callee === "atom_ctx" && context.argIndex === 0) {
|
||||
type = "tapeAtomName";
|
||||
} else if (context && context.callee === "atom_label" && context.argIndex === 0) {
|
||||
type = "tapeLabel";
|
||||
modifiers = ["declaration"];
|
||||
} else if (context && context.callee === "atom_offset" && context.argIndex <= 1) {
|
||||
type = "tapeLabel";
|
||||
} else if (context && context.callee === "atom_reads") {
|
||||
type = registerType(token.text, index);
|
||||
if (type) modifiers = ["tapeRead"];
|
||||
} else if (context && context.callee === "atom_writes") {
|
||||
type = registerType(token.text, index);
|
||||
if (type) modifiers = ["tapeWrite"];
|
||||
} else if (context && context.callee === "atom_auto_reg") {
|
||||
if (context.argIndex === 0) type = "tapeAtomName";
|
||||
if (context.argIndex === 1) {
|
||||
type = "tapeGprRegister";
|
||||
modifiers = ["declaration", "tapeAuto"];
|
||||
}
|
||||
} else if (context && context.callee === "phase_auto_reg") {
|
||||
if (context.argIndex === 0) type = "tapePhase";
|
||||
if (context.argIndex === 1) {
|
||||
type = "tapeGprRegister";
|
||||
modifiers = ["declaration", "tapeAuto"];
|
||||
}
|
||||
}
|
||||
|
||||
if (!type && index.bindTypes.has(token.text)) type = "tapeBindType";
|
||||
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)) {
|
||||
type = "tapeControlFlow";
|
||||
}
|
||||
}
|
||||
if (!type && isRegUseAccess(scanned.tokens, tokenIndex)) type = "tapeGprRegister";
|
||||
if (!type) type = instructionType(token.text, index);
|
||||
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";
|
||||
if (!type && index.labels.has(token.text)) type = "tapeLabel";
|
||||
if (!type) continue;
|
||||
|
||||
spans.push({
|
||||
text: token.text,
|
||||
type,
|
||||
typeIndex: TOKEN_TYPE_INDEX.get(type),
|
||||
modifiers,
|
||||
modifierMask: modifierMask(modifiers),
|
||||
start: token.start,
|
||||
length: token.end - token.start,
|
||||
line: token.line,
|
||||
character: token.character,
|
||||
});
|
||||
}
|
||||
|
||||
spans.sort((left, right) => left.start - right.start || left.length - right.length);
|
||||
const nonOverlapping = [];
|
||||
for (const span of spans) {
|
||||
const previous = nonOverlapping[nonOverlapping.length - 1];
|
||||
if (!previous || previous.start + previous.length <= span.start) nonOverlapping.push(span);
|
||||
}
|
||||
|
||||
return { spans: nonOverlapping, errors: scanned.errors };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
TOKEN_MODIFIERS,
|
||||
TOKEN_TYPES,
|
||||
classifyDocument,
|
||||
modifierMask,
|
||||
};
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
"use strict";
|
||||
|
||||
const vscode = require("vscode");
|
||||
const { TOKEN_MODIFIERS, TOKEN_TYPES, classifyDocument } = require("./classifier");
|
||||
const { createIndex, mergeIndexes, scanSource } = require("./source-index");
|
||||
|
||||
const SOURCE_GLOB = "**/*.{c,h,cc,cpp,cxx,hh,hpp,hxx}";
|
||||
const EXCLUDE_GLOB = "**/{gen,build,.slop_cache,toolchain,node_modules}/**";
|
||||
const EXCLUDED_SEGMENTS = new Set(["gen", "build", ".slop_cache", "toolchain", "node_modules"]);
|
||||
|
||||
function isExcluded(uri) {
|
||||
const segments = uri.fsPath.replaceAll("\\", "/").split("/");
|
||||
return segments.some((segment) => EXCLUDED_SEGMENTS.has(segment));
|
||||
}
|
||||
|
||||
function formatError(filePath, error) {
|
||||
return `${filePath}:${error.offset}: ${error.kind}`;
|
||||
}
|
||||
|
||||
async function activate(context) {
|
||||
const output = vscode.window.createOutputChannel("Tape Atom DSL");
|
||||
const emitter = new vscode.EventEmitter();
|
||||
const legend = new vscode.SemanticTokensLegend(TOKEN_TYPES, TOKEN_MODIFIERS);
|
||||
let workspaceIndex = createIndex();
|
||||
let rebuildGeneration = 0;
|
||||
let debounceHandle = null;
|
||||
|
||||
async function rebuildIndex() {
|
||||
const generation = ++rebuildGeneration;
|
||||
const files = await vscode.workspace.findFiles(SOURCE_GLOB, EXCLUDE_GLOB);
|
||||
let nextIndex = createIndex();
|
||||
|
||||
for (const uri of files) {
|
||||
if (generation !== rebuildGeneration) return;
|
||||
if (isExcluded(uri)) continue;
|
||||
try {
|
||||
const bytes = await vscode.workspace.fs.readFile(uri);
|
||||
const source = Buffer.from(bytes).toString("utf8");
|
||||
const result = scanSource(source, uri.fsPath);
|
||||
nextIndex = mergeIndexes(nextIndex, result.index);
|
||||
for (const error of result.errors) output.appendLine(formatError(uri.fsPath, error));
|
||||
} catch (error) {
|
||||
output.appendLine(`${uri.fsPath}: ${error.stack || error.message || error}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (generation !== rebuildGeneration) return;
|
||||
workspaceIndex = nextIndex;
|
||||
emitter.fire();
|
||||
}
|
||||
|
||||
function scheduleRebuild(uri) {
|
||||
if (uri && isExcluded(uri)) return;
|
||||
if (debounceHandle !== null) clearTimeout(debounceHandle);
|
||||
debounceHandle = setTimeout(() => {
|
||||
debounceHandle = null;
|
||||
rebuildIndex().catch((error) => output.appendLine(error.stack || String(error)));
|
||||
}, 100);
|
||||
}
|
||||
|
||||
const provider = {
|
||||
onDidChangeSemanticTokens: emitter.event,
|
||||
provideDocumentSemanticTokens(document, cancellationToken) {
|
||||
try {
|
||||
const result = classifyDocument(
|
||||
document.getText(),
|
||||
document.uri.fsPath,
|
||||
workspaceIndex,
|
||||
() => cancellationToken.isCancellationRequested
|
||||
);
|
||||
const builder = new vscode.SemanticTokensBuilder(legend);
|
||||
for (const span of result.spans) {
|
||||
if (cancellationToken.isCancellationRequested) break;
|
||||
builder.push(span.line, span.character, span.length, span.typeIndex, span.modifierMask);
|
||||
}
|
||||
for (const error of result.errors) {
|
||||
output.appendLine(formatError(document.uri.fsPath || document.uri.toString(), error));
|
||||
}
|
||||
return builder.build();
|
||||
} catch (error) {
|
||||
output.appendLine(`${document.uri}: ${error.stack || error.message || error}`);
|
||||
return new vscode.SemanticTokensBuilder(legend).build();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const selector = [
|
||||
{ language: "c", scheme: "file" },
|
||||
{ language: "c", scheme: "untitled" },
|
||||
{ language: "cpp", scheme: "file" },
|
||||
{ language: "cpp", scheme: "untitled" },
|
||||
];
|
||||
const watcher = vscode.workspace.createFileSystemWatcher(SOURCE_GLOB);
|
||||
|
||||
context.subscriptions.push(
|
||||
output,
|
||||
emitter,
|
||||
watcher,
|
||||
watcher.onDidCreate(scheduleRebuild),
|
||||
watcher.onDidChange(scheduleRebuild),
|
||||
watcher.onDidDelete(scheduleRebuild),
|
||||
vscode.languages.registerDocumentSemanticTokensProvider(selector, provider, legend),
|
||||
{ dispose() { if (debounceHandle !== null) clearTimeout(debounceHandle); } }
|
||||
);
|
||||
|
||||
await rebuildIndex();
|
||||
}
|
||||
|
||||
function deactivate() {}
|
||||
|
||||
module.exports = { activate, deactivate };
|
||||
Vendored
+186
@@ -0,0 +1,186 @@
|
||||
"use strict";
|
||||
|
||||
function isIdentifierStart(code) {
|
||||
return code === 95 ||
|
||||
(code >= 65 && code <= 90) ||
|
||||
(code >= 97 && code <= 122);
|
||||
}
|
||||
|
||||
function isIdentifierContinue(code) {
|
||||
return isIdentifierStart(code) || (code >= 48 && code <= 57);
|
||||
}
|
||||
|
||||
function lex(source) {
|
||||
if (typeof source !== "string") throw new TypeError("source must be a string");
|
||||
|
||||
const tokens = [];
|
||||
const errors = [];
|
||||
let offset = 0;
|
||||
let line = 0;
|
||||
let character = 0;
|
||||
|
||||
function advance() {
|
||||
if (source[offset] === "\r" && source[offset + 1] === "\n") {
|
||||
offset += 2;
|
||||
line += 1;
|
||||
character = 0;
|
||||
return;
|
||||
}
|
||||
if (source[offset] === "\n") {
|
||||
offset += 1;
|
||||
line += 1;
|
||||
character = 0;
|
||||
return;
|
||||
}
|
||||
offset += 1;
|
||||
character += 1;
|
||||
}
|
||||
|
||||
function pushToken(kind, start, startLine, startCharacter) {
|
||||
tokens.push({
|
||||
kind,
|
||||
text: source.slice(start, offset),
|
||||
start,
|
||||
end: offset,
|
||||
line: startLine,
|
||||
character: startCharacter,
|
||||
});
|
||||
}
|
||||
|
||||
while (offset < source.length) {
|
||||
const ch = source[offset];
|
||||
|
||||
if (/\s/.test(ch)) {
|
||||
advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === "/" && source[offset + 1] === "/") {
|
||||
while (offset < source.length && source[offset] !== "\r" && source[offset] !== "\n") advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === "/" && source[offset + 1] === "*") {
|
||||
const start = offset;
|
||||
advance();
|
||||
advance();
|
||||
let closed = false;
|
||||
while (offset < source.length) {
|
||||
if (source[offset] === "*" && source[offset + 1] === "/") {
|
||||
advance();
|
||||
advance();
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
advance();
|
||||
}
|
||||
if (!closed) errors.push({ kind: "unterminated-block-comment", offset: start });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === "\"" || ch === "'") {
|
||||
const quote = ch;
|
||||
const start = offset;
|
||||
advance();
|
||||
let closed = false;
|
||||
while (offset < source.length) {
|
||||
if (source[offset] === "\\") {
|
||||
advance();
|
||||
if (offset < source.length) advance();
|
||||
continue;
|
||||
}
|
||||
if (source[offset] === quote) {
|
||||
advance();
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
if (source[offset] === "\n" || source[offset] === "\r") break;
|
||||
advance();
|
||||
}
|
||||
if (!closed) errors.push({ kind: "unterminated-literal", offset: start });
|
||||
continue;
|
||||
}
|
||||
|
||||
const code = source.charCodeAt(offset);
|
||||
if (isIdentifierStart(code)) {
|
||||
const start = offset;
|
||||
const startLine = line;
|
||||
const startCharacter = character;
|
||||
advance();
|
||||
while (offset < source.length && isIdentifierContinue(source.charCodeAt(offset))) advance();
|
||||
pushToken("identifier", start, startLine, startCharacter);
|
||||
continue;
|
||||
}
|
||||
|
||||
const start = offset;
|
||||
const startLine = line;
|
||||
const startCharacter = character;
|
||||
advance();
|
||||
pushToken("punctuation", start, startLine, startCharacter);
|
||||
}
|
||||
|
||||
return { tokens, errors };
|
||||
}
|
||||
|
||||
function buildCallContexts(tokens) {
|
||||
const contexts = Array.from({ length: tokens.length }, () => []);
|
||||
const calls = [];
|
||||
const errors = [];
|
||||
const stack = [];
|
||||
|
||||
for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex += 1) {
|
||||
const token = tokens[tokenIndex];
|
||||
|
||||
if (token.text === ")") {
|
||||
if (stack.length === 0) {
|
||||
errors.push({ kind: "unmatched-close-paren", offset: token.start });
|
||||
} else {
|
||||
const frame = stack.pop();
|
||||
if (frame.callee !== null) calls.push({ ...frame, closeTokenIndex: tokenIndex });
|
||||
}
|
||||
}
|
||||
|
||||
contexts[tokenIndex] = stack
|
||||
.filter((frame) => frame.callee !== null)
|
||||
.map((frame) => ({
|
||||
callee: frame.callee,
|
||||
calleeTokenIndex: frame.calleeTokenIndex,
|
||||
openTokenIndex: frame.openTokenIndex,
|
||||
argIndex: frame.argIndex,
|
||||
}));
|
||||
|
||||
if (token.text === "(") {
|
||||
const previous = tokens[tokenIndex - 1];
|
||||
const hasCallee = previous && previous.kind === "identifier";
|
||||
stack.push({
|
||||
callee: hasCallee ? previous.text : null,
|
||||
calleeTokenIndex: hasCallee ? tokenIndex - 1 : -1,
|
||||
openTokenIndex: tokenIndex,
|
||||
argIndex: 0,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token.text === "," && stack.length > 0) {
|
||||
const frame = stack[stack.length - 1];
|
||||
if (frame.callee !== null) frame.argIndex += 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (const frame of stack) {
|
||||
errors.push({ kind: "unmatched-open-paren", offset: tokens[frame.openTokenIndex].start });
|
||||
}
|
||||
|
||||
return { contexts, calls, errors };
|
||||
}
|
||||
|
||||
function nearestCall(contexts, tokenIndex, callee) {
|
||||
const entries = contexts[tokenIndex] || [];
|
||||
for (let contextIndex = entries.length - 1; contextIndex >= 0; contextIndex -= 1) {
|
||||
const entry = entries[contextIndex];
|
||||
if (callee === undefined || entry.callee === callee) return entry;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = { buildCallContexts, lex, nearestCall };
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"name": "tape-atom-syntax",
|
||||
"displayName": "Tape Atom DSL",
|
||||
"description": "Source-derived semantic highlighting for the Tape/Atom MIPS macro DSL",
|
||||
"publisher": "local",
|
||||
"version": "0.2.0",
|
||||
"engines": { "vscode": "^1.80.0" },
|
||||
"categories": ["Programming Languages"],
|
||||
"activationEvents": ["onLanguage:c", "onLanguage:cpp"],
|
||||
"main": "./extension.js",
|
||||
"files": [
|
||||
"classifier.js",
|
||||
"extension.js",
|
||||
"lexer.js",
|
||||
"source-index.js",
|
||||
"syntaxes/tape_atom.tmLanguage.json"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "node --test test/*.test.js",
|
||||
"package": "npx --yes @vscode/vsce@3.6.1 package --allow-missing-repository --skip-license --out tape-atom-syntax-0.2.0.vsix"
|
||||
},
|
||||
"contributes": {
|
||||
"semanticTokenTypes": [
|
||||
{ "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" },
|
||||
{ "id": "tapeWrite", "description": "Register declared in atom_writes" },
|
||||
{ "id": "tapeAuto", "description": "Auto-allocated register" }
|
||||
],
|
||||
"semanticTokenScopes": [
|
||||
{
|
||||
"language": "c",
|
||||
"scopes": {
|
||||
"tapeAtomKeyword": ["keyword.control.duffle.atom"],
|
||||
"tapeAtomName": ["entity.name.function.duffle.atom"],
|
||||
"tapeComponentKeyword": ["keyword.control.duffle.component"],
|
||||
"tapeComponentName": ["entity.name.function.duffle.component"],
|
||||
"tapeAnnotation": ["support.function.duffle.annotation"],
|
||||
"tapeBindType": ["entity.name.type.duffle.bind"],
|
||||
"tapePhase": ["entity.name.tag.duffle.phase"],
|
||||
"tapeLabel": ["entity.name.label.duffle.atom"],
|
||||
"tapeCpuInstruction": ["support.function.duffle.cpu"],
|
||||
"tapeControlFlow": ["keyword.control.duffle.branch"],
|
||||
"tapeGteInstruction": ["support.function.duffle.gte"],
|
||||
"tapeGpuInstruction": ["support.function.duffle.gpu"],
|
||||
"tapeComponentInstruction": ["support.function.duffle.component"],
|
||||
"tapeDelaySlot": ["keyword.operator.duffle.delayslot"],
|
||||
"tapeGprRegister": ["variable.other.constant.duffle.gpr"],
|
||||
"tapeCop2Register": ["variable.other.constant.duffle.cop2"],
|
||||
"tapeDuffleType": ["storage.type.duffle.type"],
|
||||
"tapeAttribute": ["storage.modifier.duffle.attr"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
{
|
||||
"scopeName": "tape_atom.injection",
|
||||
"path": "./syntaxes/tape_atom.tmLanguage.json",
|
||||
"injectTo": ["source.c", "source.cpp"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
"use strict";
|
||||
|
||||
const path = require("node:path");
|
||||
const { buildCallContexts, lex, nearestCall } = require("./lexer");
|
||||
|
||||
const BASE_TYPES = [
|
||||
"B1", "B2", "B4", "B8", "F4", "F8", "S1", "S2", "S4", "S8",
|
||||
"U1", "U2", "U4", "U8", "MipsAtom", "MipsCode", "Reg",
|
||||
];
|
||||
|
||||
const BASE_ATTRIBUTES = [
|
||||
"FI_", "I_", "NI_", "Relative_", "Struct_", "Enum_", "Union_",
|
||||
"TypeR_", "TypeV_", "align_", "internal", "local_persist", "global",
|
||||
];
|
||||
|
||||
function createIndex() {
|
||||
return {
|
||||
atoms: new Set(),
|
||||
components: new Set(),
|
||||
componentAliases: new Set(),
|
||||
macros: new Map(),
|
||||
registers: new Map(),
|
||||
bindTypes: new Set(),
|
||||
types: new Set(BASE_TYPES),
|
||||
phases: new Set(),
|
||||
labels: new Set(),
|
||||
attributes: new Set(BASE_ATTRIBUTES),
|
||||
};
|
||||
}
|
||||
|
||||
function cloneIndex(source) {
|
||||
const result = createIndex();
|
||||
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.registers) result.registers.set(name, domain);
|
||||
return result;
|
||||
}
|
||||
|
||||
function mergeIndexes(...sources) {
|
||||
const result = createIndex();
|
||||
for (const source of sources) {
|
||||
if (!source) continue;
|
||||
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.registers) result.registers.set(name, domain);
|
||||
}
|
||||
return 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";
|
||||
}
|
||||
|
||||
function registerKind(name) {
|
||||
if (/^R_[A-Za-z0-9_]+$/.test(name)) return "gpr";
|
||||
if (/^(?:C2_|gte_cr_)[A-Za-z0-9_]+$/.test(name)) return "cop2";
|
||||
return null;
|
||||
}
|
||||
|
||||
function componentAlias(name) {
|
||||
return name.startsWith("ac_") ? `mac_${name.slice(3)}` : null;
|
||||
}
|
||||
|
||||
function findFunctionNameBefore(tokens, calleeTokenIndex) {
|
||||
let closeIndex = calleeTokenIndex - 1;
|
||||
while (closeIndex >= 0 && tokens[closeIndex].kind === "identifier" && tokens[closeIndex].text === "atom_dbg_skip") {
|
||||
closeIndex -= 1;
|
||||
}
|
||||
if (!tokens[closeIndex] || tokens[closeIndex].text !== ")") return null;
|
||||
|
||||
let depth = 1;
|
||||
for (let tokenIndex = closeIndex - 1; tokenIndex >= 0; tokenIndex -= 1) {
|
||||
if (tokens[tokenIndex].text === ")") depth += 1;
|
||||
if (tokens[tokenIndex].text === "(") depth -= 1;
|
||||
if (depth !== 0) continue;
|
||||
const name = tokens[tokenIndex - 1];
|
||||
return name && name.kind === "identifier" ? name : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function scanSource(source, filePath) {
|
||||
const lexical = lex(source);
|
||||
const balanced = buildCallContexts(lexical.tokens);
|
||||
const tokens = lexical.tokens;
|
||||
const contexts = balanced.contexts;
|
||||
const index = createIndex();
|
||||
const declarations = new Map();
|
||||
const domain = domainFromPath(filePath);
|
||||
|
||||
function mark(token, role, modifiers = ["declaration"]) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex += 1) {
|
||||
const token = tokens[tokenIndex];
|
||||
if (token.kind !== "identifier") continue;
|
||||
|
||||
const kind = registerKind(token.text);
|
||||
if (kind) {
|
||||
index.registers.set(token.text, kind);
|
||||
if (tokens[tokenIndex + 1] && tokens[tokenIndex + 1].text === "=") {
|
||||
mark(token, kind === "gpr" ? "gprRegister" : "cop2Register");
|
||||
}
|
||||
}
|
||||
|
||||
const context = nearestCall(contexts, tokenIndex);
|
||||
if (context && context.argIndex === 0) {
|
||||
if (context.callee === "MipsAtom_") {
|
||||
index.atoms.add(token.text);
|
||||
mark(token, "atomName");
|
||||
}
|
||||
if (context.callee === "MipsAtomComp_") addComponent(token);
|
||||
if (context.callee === "atom_bind") index.bindTypes.add(token.text);
|
||||
if (context.callee === "atom_phase" || context.callee === "phase_auto_reg") index.phases.add(token.text);
|
||||
if (context.callee === "atom_label" || context.callee === "atom_offset") index.labels.add(token.text);
|
||||
}
|
||||
|
||||
const isWrappedType = context && (
|
||||
((context.callee === "Struct_" || context.callee === "Union_") && context.argIndex === 0) ||
|
||||
(context.callee === "Enum_" && context.argIndex === 1)
|
||||
);
|
||||
if (isWrappedType) {
|
||||
index.types.add(token.text);
|
||||
mark(token, token.text.startsWith("Binds_") ? "bindType" : "duffleType");
|
||||
if (token.text.startsWith("Binds_")) index.bindTypes.add(token.text);
|
||||
}
|
||||
|
||||
if (context && context.callee === "atom_offset" && context.argIndex === 1) index.labels.add(token.text);
|
||||
|
||||
if (context && context.callee === "atom_auto_reg") {
|
||||
if (context.argIndex === 0) index.atoms.add(token.text);
|
||||
if (context.argIndex === 1) {
|
||||
index.registers.set(token.text, "gpr");
|
||||
mark(token, "gprRegister", ["declaration", "tapeAuto"]);
|
||||
}
|
||||
}
|
||||
|
||||
if (context && context.callee === "phase_auto_reg" && context.argIndex === 1) {
|
||||
index.registers.set(token.text, "gpr");
|
||||
mark(token, "gprRegister", ["declaration", "tapeAuto"]);
|
||||
}
|
||||
|
||||
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) {
|
||||
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") {
|
||||
let endIndex = tokenIndex + 1;
|
||||
let hasBrace = false;
|
||||
let lastIdentifier = null;
|
||||
while (endIndex < tokens.length && tokens[endIndex].text !== ";") {
|
||||
if (tokens[endIndex].text === "{") hasBrace = true;
|
||||
if (tokens[endIndex].kind === "identifier") lastIdentifier = tokens[endIndex];
|
||||
endIndex += 1;
|
||||
}
|
||||
if (!hasBrace && lastIdentifier) {
|
||||
index.types.add(lastIdentifier.text);
|
||||
mark(lastIdentifier, "duffleType");
|
||||
}
|
||||
}
|
||||
|
||||
if (token.text === "MipsAtom_Proc_") {
|
||||
const functionName = findFunctionNameBefore(tokens, tokenIndex);
|
||||
if (functionName) {
|
||||
const atomName = functionName.text.endsWith("_proc")
|
||||
? functionName.text.slice(0, -5)
|
||||
: functionName.text;
|
||||
index.atoms.add(atomName);
|
||||
index.atoms.add(functionName.text);
|
||||
mark(functionName, "atomName");
|
||||
}
|
||||
}
|
||||
|
||||
if (token.text === "MipsAtomComp_Proc_") {
|
||||
const functionName = findFunctionNameBefore(tokens, tokenIndex);
|
||||
if (functionName) addComponent(functionName);
|
||||
}
|
||||
}
|
||||
|
||||
for (const call of balanced.calls) {
|
||||
if (domain === "component") continue;
|
||||
const name = tokens[call.calleeTokenIndex];
|
||||
const after = tokens[call.closeTokenIndex + 1];
|
||||
if (!name || !after || after.text !== "{") continue;
|
||||
if (/^(?:gp0_|gp1_|gte_|mac_)/.test(name.text)) index.macros.set(name.text, domain);
|
||||
}
|
||||
|
||||
return {
|
||||
index: cloneIndex(index),
|
||||
declarations,
|
||||
tokens,
|
||||
contexts,
|
||||
errors: [...lexical.errors, ...balanced.errors],
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createIndex,
|
||||
domainFromPath,
|
||||
mergeIndexes,
|
||||
scanSource,
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"scopeName": "tape_atom.injection",
|
||||
"injectionSelector": "L:source.c -comment -string, L:source.cpp -comment -string",
|
||||
"patterns": [
|
||||
{ "include": "#atom-declarations" },
|
||||
{ "include": "#component-declarations" },
|
||||
{ "include": "#annotation-arguments" },
|
||||
{ "include": "#annotations" },
|
||||
{ "include": "#delay-slots" },
|
||||
{ "include": "#types" },
|
||||
{ "include": "#attributes" }
|
||||
],
|
||||
"repository": {
|
||||
"atom-declarations": {
|
||||
"patterns": [
|
||||
{
|
||||
"match": "\\b(MipsAtom_)\\s*\\(\\s*([A-Za-z_][A-Za-z0-9_]*)",
|
||||
"captures": {
|
||||
"1": { "name": "keyword.control.duffle.atom" },
|
||||
"2": { "name": "entity.name.function.duffle.atom" }
|
||||
}
|
||||
},
|
||||
{ "match": "\\bMipsAtom_Proc_\\b", "name": "keyword.control.duffle.atom" },
|
||||
{ "match": "\\b[A-Za-z_][A-Za-z0-9_]*_proc\\b", "name": "entity.name.function.duffle.atom" }
|
||||
]
|
||||
},
|
||||
"component-declarations": {
|
||||
"patterns": [
|
||||
{
|
||||
"match": "\\b(MipsAtomComp_)\\s*\\(\\s*(ac_[A-Za-z0-9_]*)",
|
||||
"captures": {
|
||||
"1": { "name": "keyword.control.duffle.component" },
|
||||
"2": { "name": "entity.name.function.duffle.component" }
|
||||
}
|
||||
},
|
||||
{ "match": "\\bMipsAtomComp_Proc_\\b", "name": "keyword.control.duffle.component" }
|
||||
]
|
||||
},
|
||||
"annotation-arguments": {
|
||||
"patterns": [
|
||||
{
|
||||
"match": "\\b(atom_offset)\\s*\\(\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*,\\s*([A-Za-z_][A-Za-z0-9_]*)",
|
||||
"captures": {
|
||||
"1": { "name": "support.function.duffle.annotation" },
|
||||
"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"
|
||||
},
|
||||
"delay-slots": {
|
||||
"match": "\\b(LdSlot_|BdSlot_)\\b",
|
||||
"name": "keyword.operator.duffle.delayslot"
|
||||
},
|
||||
"types": {
|
||||
"match": "\\b(?:Binds_[A-Za-z0-9_]+|RegUse_[A-Za-z0-9_]+)\\b",
|
||||
"name": "storage.type.duffle.type"
|
||||
},
|
||||
"attributes": {
|
||||
"match": "\\b(?:FI_|I_|NI_|Relative_|align_|internal|local_persist|global)\\b",
|
||||
"name": "storage.modifier.duffle.attr"
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,83 @@
|
||||
"use strict";
|
||||
|
||||
const assert = require("node:assert/strict");
|
||||
const test = require("node:test");
|
||||
|
||||
const { classifyDocument } = require("../classifier");
|
||||
const { createIndex } = require("../source-index");
|
||||
|
||||
function byText(result, text) {
|
||||
return result.spans.filter((span) => span.text === text);
|
||||
}
|
||||
|
||||
test("classifyDocument distinguishes declaration, annotation, phase, bind, and label roles", () => {
|
||||
const source = [
|
||||
"typedef Struct_(Binds_CubeTri) { U4 PrimCursor; };",
|
||||
"MipsAtom_(cube_g4_face) atom_info(atom_bind(Binds_CubeTri), atom_phase(cube_g4),",
|
||||
"\tatom_reads(R_PrimCursor), atom_writes(R_FaceCursor)) {",
|
||||
"\tbranch_le_zero(R_T0, atom_offset(cull, exit)),",
|
||||
"\tatom_label(exit)",
|
||||
"};",
|
||||
].join("\n");
|
||||
|
||||
const result = classifyDocument(source, "C:/x/code/hello_camera/hello_camera.atom.c", createIndex());
|
||||
|
||||
assert.equal(byText(result, "MipsAtom_")[0].type, "tapeAtomKeyword");
|
||||
assert.deepEqual(byText(result, "cube_g4_face")[0].modifiers, ["declaration"]);
|
||||
assert.equal(byText(result, "atom_bind")[0].type, "tapeAnnotation");
|
||||
assert.equal(byText(result, "Binds_CubeTri").at(-1).type, "tapeBindType");
|
||||
assert.equal(byText(result, "cube_g4")[0].type, "tapePhase");
|
||||
assert.deepEqual(byText(result, "cube_g4")[0].modifiers, ["declaration"]);
|
||||
assert.equal(byText(result, "cull")[0].type, "tapeLabel");
|
||||
assert.equal(byText(result, "exit").every((span) => span.type === "tapeLabel"), true);
|
||||
});
|
||||
|
||||
test("classifyDocument applies read and write modifiers to GPRs", () => {
|
||||
const source = "atom_info(atom_reads(R_PrimCursor), atom_writes(R_FaceCursor))";
|
||||
const result = classifyDocument(source, "C:/x/code/test.atom.c", createIndex());
|
||||
|
||||
assert.deepEqual(byText(result, "R_PrimCursor")[0].modifiers, ["tapeRead"]);
|
||||
assert.deepEqual(byText(result, "R_FaceCursor")[0].modifiers, ["tapeWrite"]);
|
||||
});
|
||||
|
||||
test("classifyDocument separates CPU, GTE, GPU, and component domains", () => {
|
||||
const workspace = createIndex();
|
||||
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.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";
|
||||
const result = classifyDocument(source, "C:/x/code/test.c", workspace);
|
||||
|
||||
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, "C2_MAC0")[0].type, "tapeCop2Register");
|
||||
assert.equal(byText(result, "gte_cr_OFX_Code")[0].type, "tapeCop2Register");
|
||||
});
|
||||
|
||||
test("document-local declarations override an empty workspace index", () => {
|
||||
const source = [
|
||||
"MipsAtomComp_(ac_new_component) { nop };",
|
||||
"mac_new_component(),",
|
||||
].join("\n");
|
||||
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, "tapeCpuInstruction");
|
||||
});
|
||||
|
||||
test("classifier returns ordered non-overlapping spans and partial malformed output", () => {
|
||||
const source = "atom_reads(R_A /* broken";
|
||||
const result = classifyDocument(source, "C:/x/code/test.atom.c", createIndex());
|
||||
|
||||
assert.equal(result.errors.some((error) => error.kind === "unterminated-block-comment"), true);
|
||||
for (let spanIndex = 1; spanIndex < result.spans.length; spanIndex += 1) {
|
||||
const previous = result.spans[spanIndex - 1];
|
||||
const current = result.spans[spanIndex];
|
||||
assert.equal(previous.start + previous.length <= current.start, true);
|
||||
}
|
||||
});
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
"use strict";
|
||||
|
||||
const assert = require("node:assert/strict");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const test = require("node:test");
|
||||
|
||||
const { TOKEN_MODIFIERS, TOKEN_TYPES } = require("../classifier");
|
||||
|
||||
const ROOT = path.resolve(__dirname, "..");
|
||||
|
||||
function readJson(filePath) {
|
||||
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()) {
|
||||
if (Array.isArray(value)) {
|
||||
for (const entry of value) collectScopeNames(entry, output);
|
||||
return output;
|
||||
}
|
||||
if (!value || typeof value !== "object") return output;
|
||||
if (typeof value.name === "string") output.add(value.name);
|
||||
for (const child of Object.values(value)) collectScopeNames(child, output);
|
||||
return output;
|
||||
}
|
||||
|
||||
test("package semantic legend matches classifier exports", () => {
|
||||
const packageJson = readJson(path.join(ROOT, "package.json"));
|
||||
const contributedTypes = packageJson.contributes.semanticTokenTypes.map((entry) => entry.id);
|
||||
const contributedModifiers = packageJson.contributes.semanticTokenModifiers.map((entry) => entry.id);
|
||||
|
||||
assert.equal(packageJson.version, "0.2.0");
|
||||
assert.deepEqual(contributedTypes, TOKEN_TYPES);
|
||||
assert.deepEqual(contributedModifiers, TOKEN_MODIFIERS.filter((name) => name !== "declaration"));
|
||||
});
|
||||
|
||||
test("package includes runtime files only and acknowledges local-only metadata", () => {
|
||||
const packageJson = readJson(path.join(ROOT, "package.json"));
|
||||
|
||||
assert.deepEqual(packageJson.files, [
|
||||
"classifier.js",
|
||||
"extension.js",
|
||||
"lexer.js",
|
||||
"source-index.js",
|
||||
"syntaxes/tape_atom.tmLanguage.json",
|
||||
]);
|
||||
assert.equal(packageJson.scripts.package.includes("--allow-missing-repository"), true);
|
||||
assert.equal(packageJson.scripts.package.includes("--skip-license"), true);
|
||||
});
|
||||
|
||||
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}`);
|
||||
if (grammarRequired.has(tokenType)) {
|
||||
assert.equal(mappings[tokenType].some((scope) => grammarScopes.has(scope)), true, `grammar does not emit: ${tokenType}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("TextMate offset labels stay scoped to atom_offset calls", () => {
|
||||
const grammar = readJson(path.join(ROOT, "syntaxes", "tape_atom.tmLanguage.json"));
|
||||
const serialized = JSON.stringify(grammar);
|
||||
const offsetRule = grammar.repository["annotation-arguments"].patterns
|
||||
.find((rule) => rule.match.includes("atom_offset"));
|
||||
|
||||
assert.equal(serialized.includes("(?<=,)"), false);
|
||||
assert.equal(offsetRule.captures[1].name, "support.function.duffle.annotation");
|
||||
assert.equal(offsetRule.captures[2].name, "entity.name.label.duffle.atom");
|
||||
assert.equal(offsetRule.captures[3].name, "entity.name.label.duffle.atom");
|
||||
});
|
||||
|
||||
test("workspace enables semantic highlighting", () => {
|
||||
const settings = readJson(path.resolve(ROOT, "..", "settings.json"));
|
||||
|
||||
assert.equal(settings["editor.semanticHighlighting.enabled"], true);
|
||||
const semantic = settings["editor.semanticTokenColorCustomizations"];
|
||||
assert.equal(semantic.enabled, true);
|
||||
});
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
"use strict";
|
||||
|
||||
const assert = require("node:assert/strict");
|
||||
const test = require("node:test");
|
||||
|
||||
const { buildCallContexts, lex, nearestCall } = require("../lexer");
|
||||
|
||||
test("lex skips comments, strings, and character literals", () => {
|
||||
const source = [
|
||||
"MipsAtom_(visible)",
|
||||
"// MipsAtom_(line_comment)",
|
||||
"const char *s = \"atom_reads(R_Hidden)\";",
|
||||
"char c = '\\''; /* gte_cmdw_hidden */",
|
||||
"atom_reads(R_Visible)",
|
||||
].join("\n");
|
||||
|
||||
const result = lex(source);
|
||||
const identifiers = result.tokens
|
||||
.filter((token) => token.kind === "identifier")
|
||||
.map((token) => token.text);
|
||||
|
||||
assert.deepEqual(result.errors, []);
|
||||
assert.equal(identifiers.includes("visible"), true);
|
||||
assert.equal(identifiers.includes("R_Visible"), true);
|
||||
assert.equal(identifiers.includes("line_comment"), false);
|
||||
assert.equal(identifiers.includes("R_Hidden"), false);
|
||||
assert.equal(identifiers.includes("gte_cmdw_hidden"), false);
|
||||
});
|
||||
|
||||
test("lex reports unterminated block comments without returning comment tokens", () => {
|
||||
const result = lex("R_Visible /* atom_reads(R_Hidden)");
|
||||
|
||||
assert.equal(result.tokens.some((token) => token.text === "R_Visible"), true);
|
||||
assert.equal(result.tokens.some((token) => token.text === "R_Hidden"), false);
|
||||
assert.deepEqual(result.errors.map((error) => error.kind), ["unterminated-block-comment"]);
|
||||
});
|
||||
|
||||
test("line comments stop at CRLF boundaries", () => {
|
||||
const result = lex("// atom_reads(R_Hidden)\r\natom_reads(R_Visible)\r\n");
|
||||
const identifiers = result.tokens
|
||||
.filter((token) => token.kind === "identifier")
|
||||
.map((token) => token.text);
|
||||
|
||||
assert.equal(identifiers.includes("R_Hidden"), false);
|
||||
assert.equal(identifiers.includes("R_Visible"), true);
|
||||
});
|
||||
|
||||
test("balanced contexts retain multiline nesting and argument indexes", () => {
|
||||
const source = [
|
||||
"atom_info(",
|
||||
"\tatom_phase(cube_g4),",
|
||||
"\tatom_reads(R_A, nested(R_B, R_C)),",
|
||||
"\tatom_writes(R_D)",
|
||||
")",
|
||||
].join("\n");
|
||||
const lexical = lex(source);
|
||||
const balanced = buildCallContexts(lexical.tokens);
|
||||
|
||||
const byText = new Map();
|
||||
lexical.tokens.forEach((token, index) => {
|
||||
if (token.kind === "identifier") byText.set(token.text, index);
|
||||
});
|
||||
|
||||
assert.equal(nearestCall(balanced.contexts, byText.get("cube_g4")).callee, "atom_phase");
|
||||
assert.equal(nearestCall(balanced.contexts, byText.get("R_A")).callee, "atom_reads");
|
||||
assert.equal(nearestCall(balanced.contexts, byText.get("R_A")).argIndex, 0);
|
||||
assert.equal(nearestCall(balanced.contexts, byText.get("R_C")).callee, "nested");
|
||||
assert.equal(nearestCall(balanced.contexts, byText.get("R_D")).callee, "atom_writes");
|
||||
assert.deepEqual(balanced.errors, []);
|
||||
});
|
||||
|
||||
test("balanced contexts report unmatched parentheses", () => {
|
||||
const lexical = lex("atom_reads(R_A");
|
||||
const balanced = buildCallContexts(lexical.tokens);
|
||||
|
||||
assert.deepEqual(balanced.errors.map((error) => error.kind), ["unmatched-open-paren"]);
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
"use strict";
|
||||
|
||||
const assert = require("node:assert/strict");
|
||||
const test = require("node:test");
|
||||
|
||||
const {
|
||||
createIndex,
|
||||
domainFromPath,
|
||||
mergeIndexes,
|
||||
scanSource,
|
||||
} = require("../source-index");
|
||||
|
||||
test("scanSource discovers current atom and component forms", () => {
|
||||
const source = [
|
||||
"MipsAtom_(cube_g4_face) atom_info(atom_phase(cube_g4), atom_reads(R_PrimCursor)) { mac_yield() };",
|
||||
"MipsAtomComp_(ac_load_pair) { load_word(R_T0, R_T1, 0) };",
|
||||
"internal MipsAtom* normalize_proc(AtomArena_R aa) MipsAtom_Proc_(aa, { mac_yield() })",
|
||||
"FI_ void ac_store_pair(MipsAtomBuilder_R ab) atom_dbg_skip MipsAtomComp_Proc_(ab, { store_word(R_T0, R_T1, 0) })",
|
||||
].join("\n");
|
||||
|
||||
const result = scanSource(source, "C:/projects/Pikuma/ps1/code/duffle/mips.atom.c");
|
||||
|
||||
assert.equal(result.index.atoms.has("cube_g4_face"), true);
|
||||
assert.equal(result.index.atoms.has("normalize"), true);
|
||||
assert.equal(result.index.components.has("ac_load_pair"), true);
|
||||
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.phases.has("cube_g4"), true);
|
||||
assert.equal(result.index.registers.get("R_PrimCursor"), "gpr");
|
||||
assert.deepEqual(result.errors, []);
|
||||
});
|
||||
|
||||
test("scanSource discovers binds, labels, registers, typedefs, and macro domains", () => {
|
||||
const source = [
|
||||
"typedef Struct_(Binds_CubeTri) { U4 PrimCursor; };",
|
||||
"typedef Enum_(U4, PadStatus) { PadStatus_Ok };",
|
||||
"typedef U4 const MipsCode;",
|
||||
"enum { R_PrimCursor = R_T7 atom_reg, C2_Custom = 12, gte_cr_Custom = 13 };",
|
||||
"#define load_word(rt, base, off) enc_i(rt, base, off)",
|
||||
"atom_bind(Binds_CubeTri)",
|
||||
"atom_label(exit)",
|
||||
"atom_offset(entry, exit)",
|
||||
].join("\n");
|
||||
|
||||
const result = scanSource(source, "C:/projects/Pikuma/ps1/code/duffle/mips.h");
|
||||
|
||||
assert.equal(result.index.bindTypes.has("Binds_CubeTri"), true);
|
||||
assert.equal(result.index.types.has("PadStatus"), true);
|
||||
assert.equal(result.index.types.has("MipsCode"), true);
|
||||
assert.equal(result.index.registers.get("R_PrimCursor"), "gpr");
|
||||
assert.equal(result.index.registers.get("C2_Custom"), "cop2");
|
||||
assert.equal(result.index.registers.get("gte_cr_Custom"), "cop2");
|
||||
assert.equal(result.index.macros.get("load_word"), "cpu");
|
||||
assert.equal(result.index.labels.has("entry"), true);
|
||||
assert.equal(result.index.labels.has("exit"), true);
|
||||
});
|
||||
|
||||
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/duffle/mips.h"), "cpu");
|
||||
assert.equal(domainFromPath("C:/x/code/duffle/gte.atom.c"), "gte");
|
||||
assert.equal(domainFromPath("C:/x/code/duffle/gp.h"), "gpu");
|
||||
});
|
||||
|
||||
test("mergeIndexes preserves domain-specific aliases", () => {
|
||||
const left = createIndex();
|
||||
left.macros.set("load_word", "cpu");
|
||||
const right = createIndex();
|
||||
right.componentAliases.add("mac_gte_store");
|
||||
right.macros.set("mac_gte_store", "gte");
|
||||
|
||||
const merged = mergeIndexes(left, right);
|
||||
assert.equal(merged.macros.get("load_word"), "cpu");
|
||||
assert.equal(merged.macros.get("mac_gte_store"), "gte");
|
||||
});
|
||||
@@ -1,24 +1,17 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
Copyright (C) 2026 Edward R. Gonzalez
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <https://unlicense.org>
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
Reference in New Issue
Block a user