@razdolbai/merls 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.serena/memories/conventions.md +6 -0
- package/.serena/memories/core.md +8 -0
- package/.serena/memories/memory_maintenance.md +33 -0
- package/.serena/memories/suggested_commands.md +4 -0
- package/.serena/memories/task_completion.md +7 -0
- package/.serena/memories/tech_stack.md +6 -0
- package/.serena/project.yml +132 -0
- package/AGENTS.md +63 -0
- package/README.md +137 -0
- package/dist/src/asm/diagnostics.js +202 -0
- package/dist/src/asm/document.js +26 -0
- package/dist/src/asm/expression.js +163 -0
- package/dist/src/asm/lexer.js +122 -0
- package/dist/src/asm/local-labels.js +140 -0
- package/dist/src/asm/metadata.js +101 -0
- package/dist/src/asm/parser.js +118 -0
- package/dist/src/asm/symbols.js +40 -0
- package/dist/src/asm/syntax.js +44 -0
- package/dist/src/asm/workspace.js +73 -0
- package/dist/src/cli.js +21 -0
- package/dist/src/index.js +4 -0
- package/dist/src/lsp/completion.js +32 -0
- package/dist/src/lsp/diagnostics.js +63 -0
- package/dist/src/lsp/document-symbols.js +80 -0
- package/dist/src/lsp/hover.js +75 -0
- package/dist/src/lsp/symbol-navigation.js +181 -0
- package/dist/src/lsp/workspace-symbols.js +17 -0
- package/dist/src/server.js +77 -0
- package/dist/test/bootstrap.test.js +11 -0
- package/dist/test/cli-contract.test.js +74 -0
- package/dist/test/coc-config.test.js +21 -0
- package/dist/test/completion.test.js +126 -0
- package/dist/test/definition-references.test.js +126 -0
- package/dist/test/diagnostics.test.js +66 -0
- package/dist/test/document-model.test.js +30 -0
- package/dist/test/document-symbol.test.js +107 -0
- package/dist/test/expression.test.js +100 -0
- package/dist/test/fixture-corpus.test.js +33 -0
- package/dist/test/hover.test.js +142 -0
- package/dist/test/lexer.test.js +53 -0
- package/dist/test/line-parser.test.js +67 -0
- package/dist/test/local-labels.test.js +43 -0
- package/dist/test/metadata.test.js +27 -0
- package/dist/test/publish-diagnostics.test.js +137 -0
- package/dist/test/run-tests.js +132 -0
- package/dist/test/server-entrypoint.test.js +14 -0
- package/dist/test/server-initialize.test.js +77 -0
- package/dist/test/symbols.test.js +37 -0
- package/dist/test/syntax-shape.test.js +18 -0
- package/dist/test/workspace-symbol.test.js +113 -0
- package/dist/test/workspace.test.js +24 -0
- package/examples/coc-settings.json +18 -0
- package/package.json +26 -0
- package/publish.ps1 +9 -0
- package/src/asm/diagnostics.ts +294 -0
- package/src/asm/document.ts +43 -0
- package/src/asm/expression.ts +242 -0
- package/src/asm/lexer.ts +197 -0
- package/src/asm/local-labels.ts +204 -0
- package/src/asm/metadata.ts +150 -0
- package/src/asm/parser.ts +197 -0
- package/src/asm/symbols.ts +55 -0
- package/src/asm/syntax.ts +76 -0
- package/src/asm/workspace.ts +105 -0
- package/src/cli.ts +24 -0
- package/src/index.ts +1 -0
- package/src/lsp/completion.ts +42 -0
- package/src/lsp/diagnostics.ts +82 -0
- package/src/lsp/document-symbols.ts +111 -0
- package/src/lsp/hover.ts +90 -0
- package/src/lsp/symbol-navigation.ts +244 -0
- package/src/lsp/workspace-symbols.ts +24 -0
- package/src/server.ts +121 -0
- package/test/bootstrap.test.ts +7 -0
- package/test/cli-contract.test.ts +94 -0
- package/test/coc-config.test.ts +28 -0
- package/test/completion.test.ts +151 -0
- package/test/definition-references.test.ts +152 -0
- package/test/diagnostics.test.ts +129 -0
- package/test/document-model.test.ts +29 -0
- package/test/document-symbol.test.ts +131 -0
- package/test/expression.test.ts +111 -0
- package/test/fixture-corpus.test.ts +33 -0
- package/test/fixtures/invalid/65816-bank-ops.asm +17 -0
- package/test/fixtures/invalid/65816-long-addressing.asm +26 -0
- package/test/fixtures/valid/merlin32-linkscript.asm +16 -0
- package/test/fixtures/valid/merlin32-main-6502.asm +103 -0
- package/test/fixtures/valid/smoke-test.asm +7 -0
- package/test/hover.test.ts +175 -0
- package/test/lexer.test.ts +87 -0
- package/test/line-parser.test.ts +69 -0
- package/test/local-labels.test.ts +47 -0
- package/test/metadata.test.ts +27 -0
- package/test/publish-diagnostics.test.ts +206 -0
- package/test/run-tests.ts +139 -0
- package/test/server-entrypoint.test.ts +11 -0
- package/test/server-initialize.test.ts +101 -0
- package/test/smoke/run-smoke.ps1 +177 -0
- package/test/smoke/vimrc +17 -0
- package/test/symbols.test.ts +41 -0
- package/test/syntax-shape.test.ts +18 -0
- package/test/workspace-symbol.test.ts +139 -0
- package/test/workspace.test.ts +29 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.lineShapeTable = exports.lineShapeDefinitions = exports.tokenKindTable = exports.tokenKindDefinitions = void 0;
|
|
4
|
+
function defineTokenKind(name, description, captureExamples) {
|
|
5
|
+
return {
|
|
6
|
+
name,
|
|
7
|
+
description,
|
|
8
|
+
captureExamples
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function defineLineShape(name, description, allowsLabel, allowsExpression, requiresOperand, terminal) {
|
|
12
|
+
return {
|
|
13
|
+
name,
|
|
14
|
+
description,
|
|
15
|
+
allowsLabel,
|
|
16
|
+
allowsExpression,
|
|
17
|
+
requiresOperand,
|
|
18
|
+
terminal
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
exports.tokenKindDefinitions = [
|
|
22
|
+
defineTokenKind("comment", "Line or trailing comment text.", ["; trailing note", "* monitor addresses"]),
|
|
23
|
+
defineTokenKind("label", "Global symbol at the start of a line.", ["TEST_START", "GetKey"]),
|
|
24
|
+
defineTokenKind("localLabel", "Merlin local label form.", ["]loop", ":good"]),
|
|
25
|
+
defineTokenKind("directive", "Assembler directive or pseudo-op.", ["org", "dum", "hex"]),
|
|
26
|
+
defineTokenKind("mnemonic", "6502 instruction mnemonic.", ["lda", "adc", "jmp"]),
|
|
27
|
+
defineTokenKind("string", "Quoted string literal.", ["\"THE END\"", "'A'"]),
|
|
28
|
+
defineTokenKind("numericLiteral", "Numeric literal in Merlin syntax.", ["$800", "#0", "%00"]),
|
|
29
|
+
defineTokenKind("modifier", "Unary byte or bank selector modifier.", ["<value", ">value", "^value"]),
|
|
30
|
+
defineTokenKind("expressionOperator", "Operator inside an expression.", ["+", "-", "*"]),
|
|
31
|
+
defineTokenKind("identifier", "Non-label symbol reference.", ["_tmp", "dumSize", "DOSWARM"])
|
|
32
|
+
];
|
|
33
|
+
exports.tokenKindTable = new Map(exports.tokenKindDefinitions.map((definition) => [definition.name, definition]));
|
|
34
|
+
exports.lineShapeDefinitions = [
|
|
35
|
+
defineLineShape("empty", "Whitespace-only line.", false, false, false, true),
|
|
36
|
+
defineLineShape("commentOnly", "Full-line comment.", false, false, false, true),
|
|
37
|
+
defineLineShape("labelOnly", "Standalone label with no operation.", true, false, false, true),
|
|
38
|
+
defineLineShape("instruction", "Instruction mnemonic with optional operand.", true, true, false, true),
|
|
39
|
+
defineLineShape("directive", "Directive with optional operand depending on directive kind.", true, true, false, true),
|
|
40
|
+
defineLineShape("equate", "Symbol definition using an expression.", true, true, true, true),
|
|
41
|
+
defineLineShape("data", "Data-emitting directive such as ASC, DB, or HEX.", true, true, true, true),
|
|
42
|
+
defineLineShape("malformed", "Token sequence that does not match a known line shape.", true, true, false, true)
|
|
43
|
+
];
|
|
44
|
+
exports.lineShapeTable = new Map(exports.lineShapeDefinitions.map((definition) => [definition.name, definition]));
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.indexWorkspace = indexWorkspace;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const document_1 = require("./document");
|
|
10
|
+
const symbols_1 = require("./symbols");
|
|
11
|
+
const includeDirectives = new Set(["asm", "put", "use"]);
|
|
12
|
+
function indexWorkspace(entryPath) {
|
|
13
|
+
const documents = new Map();
|
|
14
|
+
const dependencies = new Map();
|
|
15
|
+
const loadOrder = [];
|
|
16
|
+
const symbols = new Map();
|
|
17
|
+
visitFile(node_path_1.default.resolve(entryPath), documents, dependencies, loadOrder);
|
|
18
|
+
for (const filePath of loadOrder) {
|
|
19
|
+
const document = documents.get(filePath);
|
|
20
|
+
if (document === undefined) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
for (const symbol of (0, symbols_1.collectSymbols)(document).values()) {
|
|
24
|
+
symbols.set(symbol.name, {
|
|
25
|
+
...symbol,
|
|
26
|
+
filePath
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
documents,
|
|
32
|
+
dependencies,
|
|
33
|
+
loadOrder,
|
|
34
|
+
symbols
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function visitFile(filePath, documents, dependencies, loadOrder) {
|
|
38
|
+
if (documents.has(filePath)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const source = node_fs_1.default.readFileSync(filePath, "utf8");
|
|
42
|
+
const document = (0, document_1.parseDocument)(source);
|
|
43
|
+
documents.set(filePath, document);
|
|
44
|
+
loadOrder.push(filePath);
|
|
45
|
+
const resolvedDependencies = document.lines
|
|
46
|
+
.flatMap((line) => {
|
|
47
|
+
const node = line.node;
|
|
48
|
+
if (node.shape !== "directive" || !includeDirectives.has(node.directive)) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
const includePath = readIncludePath(node.operand);
|
|
52
|
+
if (includePath === null) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
return [node_path_1.default.resolve(node_path_1.default.dirname(filePath), includePath)];
|
|
56
|
+
});
|
|
57
|
+
dependencies.set(filePath, resolvedDependencies);
|
|
58
|
+
for (const dependencyPath of resolvedDependencies) {
|
|
59
|
+
visitFile(dependencyPath, documents, dependencies, loadOrder);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function readIncludePath(expression) {
|
|
63
|
+
if (expression === null) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (expression.kind === "identifier") {
|
|
67
|
+
return expression.value;
|
|
68
|
+
}
|
|
69
|
+
if (expression.kind === "string") {
|
|
70
|
+
return expression.value;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.runCli = runCli;
|
|
5
|
+
const server_1 = require("./server");
|
|
6
|
+
const usage = "Usage: merls --stdio";
|
|
7
|
+
function runCli(argv) {
|
|
8
|
+
if (argv.length === 1 && argv[0] === "--stdio") {
|
|
9
|
+
(0, server_1.startServer)();
|
|
10
|
+
return 0;
|
|
11
|
+
}
|
|
12
|
+
if (argv.length === 1 && (argv[0] === "--help" || argv[0] === "-h")) {
|
|
13
|
+
process.stdout.write(`${usage}\n`);
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
process.stderr.write(`${usage}\n`);
|
|
17
|
+
return 1;
|
|
18
|
+
}
|
|
19
|
+
if (require.main === module) {
|
|
20
|
+
process.exitCode = runCli(process.argv.slice(2));
|
|
21
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildCompletionItems = buildCompletionItems;
|
|
4
|
+
const node_1 = require("vscode-languageserver/node");
|
|
5
|
+
const metadata_1 = require("../asm/metadata");
|
|
6
|
+
const document_1 = require("../asm/document");
|
|
7
|
+
const symbols_1 = require("../asm/symbols");
|
|
8
|
+
function buildCompletionItems(openDocuments, uri, line) {
|
|
9
|
+
const source = openDocuments.get(uri);
|
|
10
|
+
if (source === undefined) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
const document = (0, document_1.parseDocument)(source);
|
|
14
|
+
const text = document.lines[line]?.node.text.trimStart().toLowerCase() ?? "";
|
|
15
|
+
if (text === "ld" || text === "ld\n" || text.startsWith("ld")) {
|
|
16
|
+
return metadata_1.opcodeDefinitions.map((opcode) => ({
|
|
17
|
+
label: opcode.mnemonic,
|
|
18
|
+
kind: node_1.CompletionItemKind.Keyword
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
if (text === "du" || text.startsWith("du")) {
|
|
22
|
+
return metadata_1.directiveDefinitions.map((directive) => ({
|
|
23
|
+
label: directive.name,
|
|
24
|
+
kind: node_1.CompletionItemKind.Function
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
const symbols = (0, symbols_1.collectSymbols)(document);
|
|
28
|
+
return [...symbols.values()].map((symbol) => ({
|
|
29
|
+
label: symbol.name,
|
|
30
|
+
kind: node_1.CompletionItemKind.Variable
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.collectDiagnosticsByUri = collectDiagnosticsByUri;
|
|
4
|
+
const node_url_1 = require("node:url");
|
|
5
|
+
const node_1 = require("vscode-languageserver/node");
|
|
6
|
+
const document_1 = require("../asm/document");
|
|
7
|
+
const diagnostics_1 = require("../asm/diagnostics");
|
|
8
|
+
function collectDiagnosticsByUri(openDocuments) {
|
|
9
|
+
const sourcesByFilePath = new Map();
|
|
10
|
+
const uriByFilePath = new Map();
|
|
11
|
+
const entries = [];
|
|
12
|
+
for (const [uri, source] of openDocuments.entries()) {
|
|
13
|
+
const filePath = uriToFilePath(uri);
|
|
14
|
+
sourcesByFilePath.set(filePath, source);
|
|
15
|
+
uriByFilePath.set(filePath, uri);
|
|
16
|
+
entries.push({
|
|
17
|
+
filePath,
|
|
18
|
+
document: (0, document_1.parseDocument)(source)
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
const diagnosticsByFilePath = new Map();
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
diagnosticsByFilePath.set(entry.filePath, []);
|
|
24
|
+
}
|
|
25
|
+
for (const diagnostic of (0, diagnostics_1.collectWorkspaceDiagnostics)(entries)) {
|
|
26
|
+
const source = sourcesByFilePath.get(diagnostic.filePath);
|
|
27
|
+
const diagnostics = diagnosticsByFilePath.get(diagnostic.filePath);
|
|
28
|
+
if (source === undefined || diagnostics === undefined) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
diagnostics.push(toLspDiagnostic(source, diagnostic));
|
|
32
|
+
}
|
|
33
|
+
const diagnosticsByUri = new Map();
|
|
34
|
+
for (const [filePath, uri] of uriByFilePath.entries()) {
|
|
35
|
+
diagnosticsByUri.set(uri, diagnosticsByFilePath.get(filePath) ?? []);
|
|
36
|
+
}
|
|
37
|
+
return diagnosticsByUri;
|
|
38
|
+
}
|
|
39
|
+
function toLspDiagnostic(source, diagnostic) {
|
|
40
|
+
const lineText = source.split(/\r?\n/)[diagnostic.line] ?? "";
|
|
41
|
+
return {
|
|
42
|
+
severity: node_1.DiagnosticSeverity.Error,
|
|
43
|
+
message: diagnostic.message,
|
|
44
|
+
source: "merls",
|
|
45
|
+
code: diagnostic.code,
|
|
46
|
+
range: {
|
|
47
|
+
start: {
|
|
48
|
+
line: diagnostic.line,
|
|
49
|
+
character: 0
|
|
50
|
+
},
|
|
51
|
+
end: {
|
|
52
|
+
line: diagnostic.line,
|
|
53
|
+
character: lineText.length
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function uriToFilePath(uri) {
|
|
59
|
+
if (uri.startsWith("file://")) {
|
|
60
|
+
return (0, node_url_1.fileURLToPath)(uri);
|
|
61
|
+
}
|
|
62
|
+
return uri;
|
|
63
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildDocumentSymbols = buildDocumentSymbols;
|
|
4
|
+
const node_1 = require("vscode-languageserver/node");
|
|
5
|
+
const document_1 = require("../asm/document");
|
|
6
|
+
function buildDocumentSymbols(uri, source) {
|
|
7
|
+
const document = (0, document_1.parseDocument)(source);
|
|
8
|
+
const symbols = [];
|
|
9
|
+
for (const line of document.lines) {
|
|
10
|
+
const symbol = toSymbolInformation(uri, line.node, line.line);
|
|
11
|
+
if (symbol !== null) {
|
|
12
|
+
symbols.push(symbol);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return symbols;
|
|
16
|
+
}
|
|
17
|
+
function toSymbolInformation(uri, node, line) {
|
|
18
|
+
const name = getNodeName(node);
|
|
19
|
+
const kind = getNodeKind(node);
|
|
20
|
+
if (name === null || kind === null) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
name,
|
|
25
|
+
kind,
|
|
26
|
+
location: createLocation(uri, line, name)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function getNodeName(node) {
|
|
30
|
+
if (node.shape === "equate") {
|
|
31
|
+
return node.label;
|
|
32
|
+
}
|
|
33
|
+
if (node.shape === "labelOnly") {
|
|
34
|
+
return node.label;
|
|
35
|
+
}
|
|
36
|
+
if (node.shape === "instruction" && node.label !== null) {
|
|
37
|
+
return node.label;
|
|
38
|
+
}
|
|
39
|
+
if (node.shape === "directive" && node.label !== null) {
|
|
40
|
+
return node.label;
|
|
41
|
+
}
|
|
42
|
+
if (node.shape === "data" && node.label !== null) {
|
|
43
|
+
return node.label;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
function getNodeKind(node) {
|
|
48
|
+
if (node.shape === "equate") {
|
|
49
|
+
return node_1.SymbolKind.Variable;
|
|
50
|
+
}
|
|
51
|
+
if (node.shape === "labelOnly") {
|
|
52
|
+
return node_1.SymbolKind.Method;
|
|
53
|
+
}
|
|
54
|
+
if (node.shape === "instruction" && node.label !== null) {
|
|
55
|
+
return node_1.SymbolKind.Method;
|
|
56
|
+
}
|
|
57
|
+
if (node.shape === "directive" && node.label !== null) {
|
|
58
|
+
return node_1.SymbolKind.Field;
|
|
59
|
+
}
|
|
60
|
+
if (node.shape === "data" && node.label !== null) {
|
|
61
|
+
return node_1.SymbolKind.Field;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
function createLocation(uri, line, name) {
|
|
66
|
+
const range = {
|
|
67
|
+
start: {
|
|
68
|
+
line,
|
|
69
|
+
character: 0
|
|
70
|
+
},
|
|
71
|
+
end: {
|
|
72
|
+
line,
|
|
73
|
+
character: name.length
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
uri,
|
|
78
|
+
range
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildHover = buildHover;
|
|
4
|
+
const metadata_1 = require("../asm/metadata");
|
|
5
|
+
const document_1 = require("../asm/document");
|
|
6
|
+
const lexer_1 = require("../asm/lexer");
|
|
7
|
+
const symbol_navigation_1 = require("./symbol-navigation");
|
|
8
|
+
function buildHover(openDocuments, uri, line, character) {
|
|
9
|
+
const source = openDocuments.get(uri);
|
|
10
|
+
if (source === undefined) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const documentLine = (0, document_1.parseDocument)(source).lines[line];
|
|
14
|
+
const lexedLine = (0, lexer_1.lexSource)(source).lines[line];
|
|
15
|
+
if (documentLine === undefined || lexedLine === undefined) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const token = tokenAtCharacter(lexedLine.tokens, character);
|
|
19
|
+
if (token?.kind === "mnemonic") {
|
|
20
|
+
const definition = metadata_1.opcodeTable.get(token.lexeme.toLowerCase());
|
|
21
|
+
if (definition !== undefined) {
|
|
22
|
+
return {
|
|
23
|
+
contents: `Opcode ${definition.mnemonic}: ${definition.modes.join(", ")}`
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (token?.kind === "directive") {
|
|
28
|
+
const definition = metadata_1.directiveTable.get(token.lexeme.toLowerCase());
|
|
29
|
+
if (definition !== undefined) {
|
|
30
|
+
return {
|
|
31
|
+
contents: `Directive ${definition.name}: ${definition.summary}`
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (token?.kind === "identifier" || token?.kind === "label" || token?.kind === "localLabel") {
|
|
36
|
+
const lexemeLower = token.lexeme.toLowerCase();
|
|
37
|
+
if (lexemeLower === "a" || lexemeLower === "x" || lexemeLower === "y") {
|
|
38
|
+
return {
|
|
39
|
+
contents: `Register ${token.lexeme.toUpperCase()}`
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const definition = (0, symbol_navigation_1.findDefinition)(openDocuments, uri, line);
|
|
43
|
+
if (definition !== null) {
|
|
44
|
+
return {
|
|
45
|
+
contents: `Symbol ${token.lexeme} defined at line ${definition.range.start.line}`
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const node = documentLine.node;
|
|
50
|
+
if (node.shape === "instruction") {
|
|
51
|
+
const definition = metadata_1.opcodeTable.get(node.mnemonic);
|
|
52
|
+
if (definition !== undefined) {
|
|
53
|
+
return {
|
|
54
|
+
contents: `Opcode ${definition.mnemonic}: ${definition.modes.join(", ")}`
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (node.shape === "directive") {
|
|
59
|
+
const definition = metadata_1.directiveTable.get(node.directive);
|
|
60
|
+
if (definition !== undefined) {
|
|
61
|
+
return {
|
|
62
|
+
contents: `Directive ${definition.name}: ${definition.summary}`
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
function tokenAtCharacter(tokens, character) {
|
|
69
|
+
for (const token of tokens) {
|
|
70
|
+
if (character >= token.start && character < token.end) {
|
|
71
|
+
return token;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findDefinition = findDefinition;
|
|
4
|
+
exports.findReferences = findReferences;
|
|
5
|
+
const document_1 = require("../asm/document");
|
|
6
|
+
function findDefinition(openDocuments, uri, line) {
|
|
7
|
+
const targetName = getSymbolAtLine(openDocuments.get(uri), line);
|
|
8
|
+
if (targetName === null) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
for (const [documentUri, source] of openDocuments.entries()) {
|
|
12
|
+
for (const definition of collectDefinitions(documentUri, source)) {
|
|
13
|
+
if (definition.name === targetName) {
|
|
14
|
+
return definition.location;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function findReferences(openDocuments, uri, line, includeDeclaration) {
|
|
21
|
+
const targetName = getSymbolAtLine(openDocuments.get(uri), line);
|
|
22
|
+
if (targetName === null) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
const locations = [];
|
|
26
|
+
for (const [documentUri, source] of openDocuments.entries()) {
|
|
27
|
+
if (includeDeclaration) {
|
|
28
|
+
for (const definition of collectDefinitions(documentUri, source)) {
|
|
29
|
+
if (definition.name === targetName) {
|
|
30
|
+
locations.push(definition.location);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
for (const reference of collectReferences(documentUri, source)) {
|
|
35
|
+
if (reference.name === targetName) {
|
|
36
|
+
locations.push(reference.location);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return locations;
|
|
41
|
+
}
|
|
42
|
+
function getSymbolAtLine(source, line) {
|
|
43
|
+
if (source === undefined) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const node = (0, document_1.parseDocument)(source).lines[line]?.node;
|
|
47
|
+
if (node === undefined) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
if (node.shape === "labelOnly") {
|
|
51
|
+
return node.label;
|
|
52
|
+
}
|
|
53
|
+
if (node.shape === "instruction") {
|
|
54
|
+
if (node.label !== null) {
|
|
55
|
+
return node.label;
|
|
56
|
+
}
|
|
57
|
+
return readOperandIdentifier(node.operand);
|
|
58
|
+
}
|
|
59
|
+
if (node.shape === "directive") {
|
|
60
|
+
if (node.label !== null) {
|
|
61
|
+
return node.label;
|
|
62
|
+
}
|
|
63
|
+
return readExpressionIdentifier(node.operand);
|
|
64
|
+
}
|
|
65
|
+
if (node.shape === "equate") {
|
|
66
|
+
return node.label;
|
|
67
|
+
}
|
|
68
|
+
if (node.shape === "data" && node.label !== null) {
|
|
69
|
+
return node.label;
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
function collectDefinitions(uri, source) {
|
|
74
|
+
const document = (0, document_1.parseDocument)(source);
|
|
75
|
+
const definitions = [];
|
|
76
|
+
for (const line of document.lines) {
|
|
77
|
+
const name = getDefinedName(line.node);
|
|
78
|
+
if (name === null) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
definitions.push({
|
|
82
|
+
name,
|
|
83
|
+
location: createLocation(uri, line.line, name)
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return definitions;
|
|
87
|
+
}
|
|
88
|
+
function collectReferences(uri, source) {
|
|
89
|
+
const document = (0, document_1.parseDocument)(source);
|
|
90
|
+
const references = [];
|
|
91
|
+
for (const line of document.lines) {
|
|
92
|
+
for (const name of getReferencedNames(line.node)) {
|
|
93
|
+
references.push({
|
|
94
|
+
name,
|
|
95
|
+
location: createLocation(uri, line.line, name)
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return references;
|
|
100
|
+
}
|
|
101
|
+
function getDefinedName(node) {
|
|
102
|
+
if (node.shape === "equate") {
|
|
103
|
+
return node.label;
|
|
104
|
+
}
|
|
105
|
+
if (node.shape === "labelOnly") {
|
|
106
|
+
return node.label;
|
|
107
|
+
}
|
|
108
|
+
if (node.shape === "instruction" && node.label !== null) {
|
|
109
|
+
return node.label;
|
|
110
|
+
}
|
|
111
|
+
if (node.shape === "directive" && node.label !== null) {
|
|
112
|
+
return node.label;
|
|
113
|
+
}
|
|
114
|
+
if (node.shape === "data" && node.label !== null) {
|
|
115
|
+
return node.label;
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
function getReferencedNames(node) {
|
|
120
|
+
if (node.shape === "instruction" && node.operand !== null) {
|
|
121
|
+
return collectExpressionIdentifiers(node.operand.expression);
|
|
122
|
+
}
|
|
123
|
+
if (node.shape === "directive" && node.operand !== null) {
|
|
124
|
+
return collectExpressionIdentifiers(node.operand);
|
|
125
|
+
}
|
|
126
|
+
if (node.shape === "equate") {
|
|
127
|
+
return collectExpressionIdentifiers(node.expression);
|
|
128
|
+
}
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
function readOperandIdentifier(operand) {
|
|
132
|
+
if (operand === null) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return readExpressionIdentifier(operand.expression);
|
|
136
|
+
}
|
|
137
|
+
function readExpressionIdentifier(expression) {
|
|
138
|
+
if (expression === null) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
if (expression.kind === "identifier") {
|
|
142
|
+
return expression.value;
|
|
143
|
+
}
|
|
144
|
+
if (expression.kind === "modifier") {
|
|
145
|
+
return readExpressionIdentifier(expression.expression);
|
|
146
|
+
}
|
|
147
|
+
if (expression.kind === "binary") {
|
|
148
|
+
return readExpressionIdentifier(expression.left);
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
function collectExpressionIdentifiers(expression) {
|
|
153
|
+
switch (expression.kind) {
|
|
154
|
+
case "identifier":
|
|
155
|
+
return [expression.value];
|
|
156
|
+
case "modifier":
|
|
157
|
+
return collectExpressionIdentifiers(expression.expression);
|
|
158
|
+
case "binary":
|
|
159
|
+
return [
|
|
160
|
+
...collectExpressionIdentifiers(expression.left),
|
|
161
|
+
...collectExpressionIdentifiers(expression.right)
|
|
162
|
+
];
|
|
163
|
+
default:
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function createLocation(uri, line, name) {
|
|
168
|
+
return {
|
|
169
|
+
uri,
|
|
170
|
+
range: {
|
|
171
|
+
start: {
|
|
172
|
+
line,
|
|
173
|
+
character: 0
|
|
174
|
+
},
|
|
175
|
+
end: {
|
|
176
|
+
line,
|
|
177
|
+
character: name.length
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildWorkspaceSymbols = buildWorkspaceSymbols;
|
|
4
|
+
const document_symbols_1 = require("./document-symbols");
|
|
5
|
+
function buildWorkspaceSymbols(openDocuments, query) {
|
|
6
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
7
|
+
const symbols = [];
|
|
8
|
+
for (const [uri, source] of openDocuments.entries()) {
|
|
9
|
+
for (const symbol of (0, document_symbols_1.buildDocumentSymbols)(uri, source)) {
|
|
10
|
+
if (normalizedQuery.length === 0 ||
|
|
11
|
+
symbol.name.toLowerCase().includes(normalizedQuery)) {
|
|
12
|
+
symbols.push(symbol);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return symbols;
|
|
17
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createServerConnection = createServerConnection;
|
|
4
|
+
exports.startServer = startServer;
|
|
5
|
+
const node_1 = require("vscode-languageserver/node");
|
|
6
|
+
const document_symbols_1 = require("./lsp/document-symbols");
|
|
7
|
+
const completion_1 = require("./lsp/completion");
|
|
8
|
+
const diagnostics_1 = require("./lsp/diagnostics");
|
|
9
|
+
const hover_1 = require("./lsp/hover");
|
|
10
|
+
const symbol_navigation_1 = require("./lsp/symbol-navigation");
|
|
11
|
+
const workspace_symbols_1 = require("./lsp/workspace-symbols");
|
|
12
|
+
function createServerConnection(inputStream = process.stdin, outputStream = process.stdout) {
|
|
13
|
+
return (0, node_1.createConnection)(node_1.ProposedFeatures.all, inputStream, outputStream);
|
|
14
|
+
}
|
|
15
|
+
function startServer(inputStream = process.stdin, outputStream = process.stdout) {
|
|
16
|
+
const connection = createServerConnection(inputStream, outputStream);
|
|
17
|
+
const openDocuments = new Map();
|
|
18
|
+
connection.onInitialize(() => ({
|
|
19
|
+
capabilities: {
|
|
20
|
+
completionProvider: {},
|
|
21
|
+
definitionProvider: true,
|
|
22
|
+
documentSymbolProvider: true,
|
|
23
|
+
hoverProvider: true,
|
|
24
|
+
referencesProvider: true,
|
|
25
|
+
workspaceSymbolProvider: true,
|
|
26
|
+
textDocumentSync: {
|
|
27
|
+
openClose: true,
|
|
28
|
+
change: node_1.TextDocumentSyncKind.Full
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}));
|
|
32
|
+
connection.onDidOpenTextDocument((params) => {
|
|
33
|
+
openDocuments.set(params.textDocument.uri, params.textDocument.text);
|
|
34
|
+
publishDiagnostics();
|
|
35
|
+
});
|
|
36
|
+
connection.onDidChangeTextDocument((params) => {
|
|
37
|
+
const nextText = params.contentChanges.at(-1)?.text;
|
|
38
|
+
if (nextText === undefined) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
openDocuments.set(params.textDocument.uri, nextText);
|
|
42
|
+
publishDiagnostics();
|
|
43
|
+
});
|
|
44
|
+
connection.onDidCloseTextDocument((params) => {
|
|
45
|
+
openDocuments.delete(params.textDocument.uri);
|
|
46
|
+
connection.sendDiagnostics({
|
|
47
|
+
uri: params.textDocument.uri,
|
|
48
|
+
diagnostics: []
|
|
49
|
+
});
|
|
50
|
+
publishDiagnostics();
|
|
51
|
+
});
|
|
52
|
+
connection.onDocumentSymbol((params) => {
|
|
53
|
+
const source = openDocuments.get(params.textDocument.uri);
|
|
54
|
+
if (source === undefined) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
return (0, document_symbols_1.buildDocumentSymbols)(params.textDocument.uri, source);
|
|
58
|
+
});
|
|
59
|
+
connection.onWorkspaceSymbol((params) => (0, workspace_symbols_1.buildWorkspaceSymbols)(openDocuments, params.query));
|
|
60
|
+
connection.onDefinition((params) => (0, symbol_navigation_1.findDefinition)(openDocuments, params.textDocument.uri, params.position.line));
|
|
61
|
+
connection.onReferences((params) => (0, symbol_navigation_1.findReferences)(openDocuments, params.textDocument.uri, params.position.line, params.context.includeDeclaration));
|
|
62
|
+
connection.onHover((params) => (0, hover_1.buildHover)(openDocuments, params.textDocument.uri, params.position.line, params.position.character));
|
|
63
|
+
connection.onCompletion((params) => (0, completion_1.buildCompletionItems)(openDocuments, params.textDocument.uri, params.position.line));
|
|
64
|
+
function publishDiagnostics() {
|
|
65
|
+
for (const [uri, diagnostics] of (0, diagnostics_1.collectDiagnosticsByUri)(openDocuments).entries()) {
|
|
66
|
+
connection.sendDiagnostics({
|
|
67
|
+
uri,
|
|
68
|
+
diagnostics: [...diagnostics]
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
connection.listen();
|
|
73
|
+
return connection;
|
|
74
|
+
}
|
|
75
|
+
if (require.main === module) {
|
|
76
|
+
startServer();
|
|
77
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runBootstrapTest = runBootstrapTest;
|
|
7
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
8
|
+
const index_1 = require("../src/index");
|
|
9
|
+
function runBootstrapTest() {
|
|
10
|
+
strict_1.default.equal(index_1.projectName, "merls");
|
|
11
|
+
}
|