@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.
Files changed (104) hide show
  1. package/.serena/memories/conventions.md +6 -0
  2. package/.serena/memories/core.md +8 -0
  3. package/.serena/memories/memory_maintenance.md +33 -0
  4. package/.serena/memories/suggested_commands.md +4 -0
  5. package/.serena/memories/task_completion.md +7 -0
  6. package/.serena/memories/tech_stack.md +6 -0
  7. package/.serena/project.yml +132 -0
  8. package/AGENTS.md +63 -0
  9. package/README.md +137 -0
  10. package/dist/src/asm/diagnostics.js +202 -0
  11. package/dist/src/asm/document.js +26 -0
  12. package/dist/src/asm/expression.js +163 -0
  13. package/dist/src/asm/lexer.js +122 -0
  14. package/dist/src/asm/local-labels.js +140 -0
  15. package/dist/src/asm/metadata.js +101 -0
  16. package/dist/src/asm/parser.js +118 -0
  17. package/dist/src/asm/symbols.js +40 -0
  18. package/dist/src/asm/syntax.js +44 -0
  19. package/dist/src/asm/workspace.js +73 -0
  20. package/dist/src/cli.js +21 -0
  21. package/dist/src/index.js +4 -0
  22. package/dist/src/lsp/completion.js +32 -0
  23. package/dist/src/lsp/diagnostics.js +63 -0
  24. package/dist/src/lsp/document-symbols.js +80 -0
  25. package/dist/src/lsp/hover.js +75 -0
  26. package/dist/src/lsp/symbol-navigation.js +181 -0
  27. package/dist/src/lsp/workspace-symbols.js +17 -0
  28. package/dist/src/server.js +77 -0
  29. package/dist/test/bootstrap.test.js +11 -0
  30. package/dist/test/cli-contract.test.js +74 -0
  31. package/dist/test/coc-config.test.js +21 -0
  32. package/dist/test/completion.test.js +126 -0
  33. package/dist/test/definition-references.test.js +126 -0
  34. package/dist/test/diagnostics.test.js +66 -0
  35. package/dist/test/document-model.test.js +30 -0
  36. package/dist/test/document-symbol.test.js +107 -0
  37. package/dist/test/expression.test.js +100 -0
  38. package/dist/test/fixture-corpus.test.js +33 -0
  39. package/dist/test/hover.test.js +142 -0
  40. package/dist/test/lexer.test.js +53 -0
  41. package/dist/test/line-parser.test.js +67 -0
  42. package/dist/test/local-labels.test.js +43 -0
  43. package/dist/test/metadata.test.js +27 -0
  44. package/dist/test/publish-diagnostics.test.js +137 -0
  45. package/dist/test/run-tests.js +132 -0
  46. package/dist/test/server-entrypoint.test.js +14 -0
  47. package/dist/test/server-initialize.test.js +77 -0
  48. package/dist/test/symbols.test.js +37 -0
  49. package/dist/test/syntax-shape.test.js +18 -0
  50. package/dist/test/workspace-symbol.test.js +113 -0
  51. package/dist/test/workspace.test.js +24 -0
  52. package/examples/coc-settings.json +18 -0
  53. package/package.json +26 -0
  54. package/publish.ps1 +9 -0
  55. package/src/asm/diagnostics.ts +294 -0
  56. package/src/asm/document.ts +43 -0
  57. package/src/asm/expression.ts +242 -0
  58. package/src/asm/lexer.ts +197 -0
  59. package/src/asm/local-labels.ts +204 -0
  60. package/src/asm/metadata.ts +150 -0
  61. package/src/asm/parser.ts +197 -0
  62. package/src/asm/symbols.ts +55 -0
  63. package/src/asm/syntax.ts +76 -0
  64. package/src/asm/workspace.ts +105 -0
  65. package/src/cli.ts +24 -0
  66. package/src/index.ts +1 -0
  67. package/src/lsp/completion.ts +42 -0
  68. package/src/lsp/diagnostics.ts +82 -0
  69. package/src/lsp/document-symbols.ts +111 -0
  70. package/src/lsp/hover.ts +90 -0
  71. package/src/lsp/symbol-navigation.ts +244 -0
  72. package/src/lsp/workspace-symbols.ts +24 -0
  73. package/src/server.ts +121 -0
  74. package/test/bootstrap.test.ts +7 -0
  75. package/test/cli-contract.test.ts +94 -0
  76. package/test/coc-config.test.ts +28 -0
  77. package/test/completion.test.ts +151 -0
  78. package/test/definition-references.test.ts +152 -0
  79. package/test/diagnostics.test.ts +129 -0
  80. package/test/document-model.test.ts +29 -0
  81. package/test/document-symbol.test.ts +131 -0
  82. package/test/expression.test.ts +111 -0
  83. package/test/fixture-corpus.test.ts +33 -0
  84. package/test/fixtures/invalid/65816-bank-ops.asm +17 -0
  85. package/test/fixtures/invalid/65816-long-addressing.asm +26 -0
  86. package/test/fixtures/valid/merlin32-linkscript.asm +16 -0
  87. package/test/fixtures/valid/merlin32-main-6502.asm +103 -0
  88. package/test/fixtures/valid/smoke-test.asm +7 -0
  89. package/test/hover.test.ts +175 -0
  90. package/test/lexer.test.ts +87 -0
  91. package/test/line-parser.test.ts +69 -0
  92. package/test/local-labels.test.ts +47 -0
  93. package/test/metadata.test.ts +27 -0
  94. package/test/publish-diagnostics.test.ts +206 -0
  95. package/test/run-tests.ts +139 -0
  96. package/test/server-entrypoint.test.ts +11 -0
  97. package/test/server-initialize.test.ts +101 -0
  98. package/test/smoke/run-smoke.ps1 +177 -0
  99. package/test/smoke/vimrc +17 -0
  100. package/test/symbols.test.ts +41 -0
  101. package/test/syntax-shape.test.ts +18 -0
  102. package/test/workspace-symbol.test.ts +139 -0
  103. package/test/workspace.test.ts +29 -0
  104. 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
+ }
@@ -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,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.projectName = void 0;
4
+ exports.projectName = "merls";
@@ -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
+ }