@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,163 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseExpression = parseExpression;
4
+ exports.parseOperand = parseOperand;
5
+ const binaryPrecedence = new Map([
6
+ ["+", 10],
7
+ ["-", 10],
8
+ ["*", 20],
9
+ ["/", 20]
10
+ ]);
11
+ function parseExpression(tokens, startIndex = 0, minimumPrecedence = 0) {
12
+ let { expression: left, nextTokenIndex } = parsePrefix(tokens, startIndex);
13
+ while (nextTokenIndex < tokens.length) {
14
+ const operatorToken = tokens[nextTokenIndex];
15
+ if (operatorToken?.kind !== "expressionOperator") {
16
+ break;
17
+ }
18
+ const precedence = binaryPrecedence.get(operatorToken.lexeme);
19
+ if (precedence === undefined || precedence < minimumPrecedence) {
20
+ break;
21
+ }
22
+ const parsedRight = parseExpression(tokens, nextTokenIndex + 1, precedence + 1);
23
+ left = {
24
+ kind: "binary",
25
+ operator: operatorToken.lexeme,
26
+ left,
27
+ right: parsedRight.expression
28
+ };
29
+ nextTokenIndex = parsedRight.nextTokenIndex;
30
+ }
31
+ return {
32
+ expression: left,
33
+ nextTokenIndex
34
+ };
35
+ }
36
+ function parseOperand(tokens, startIndex = 0) {
37
+ let index = startIndex;
38
+ let immediate = false;
39
+ let indirect = false;
40
+ let indexRegister = null;
41
+ if (tokens[index]?.kind === "expressionOperator" && tokens[index]?.lexeme === "#") {
42
+ immediate = true;
43
+ index += 1;
44
+ }
45
+ if (tokens[index]?.kind === "numericLiteral" && tokens[index]?.lexeme.startsWith("#")) {
46
+ immediate = true;
47
+ const token = tokens[index];
48
+ const expression = {
49
+ kind: "numericLiteral",
50
+ value: token.lexeme.slice(1)
51
+ };
52
+ return {
53
+ operand: {
54
+ immediate,
55
+ indirect,
56
+ indexRegister,
57
+ expression
58
+ },
59
+ nextTokenIndex: index + 1
60
+ };
61
+ }
62
+ let parsedExpression;
63
+ if (tokens[index]?.kind === "expressionOperator" && tokens[index]?.lexeme === "(") {
64
+ indirect = true;
65
+ parsedExpression = parseExpression(tokens, index + 1);
66
+ index = parsedExpression.nextTokenIndex;
67
+ if (tokens[index]?.kind === "expressionOperator" && tokens[index]?.lexeme === ",") {
68
+ indexRegister = parseIndexRegister(tokens[index + 1]);
69
+ index += 2;
70
+ }
71
+ expectOperator(tokens[index], ")");
72
+ index += 1;
73
+ if (tokens[index]?.kind === "expressionOperator" && tokens[index]?.lexeme === ",") {
74
+ indexRegister = parseIndexRegister(tokens[index + 1]);
75
+ index += 2;
76
+ }
77
+ }
78
+ else {
79
+ parsedExpression = parseExpression(tokens, index);
80
+ index = parsedExpression.nextTokenIndex;
81
+ if (tokens[index]?.kind === "expressionOperator" && tokens[index]?.lexeme === ",") {
82
+ indexRegister = parseIndexRegister(tokens[index + 1]);
83
+ index += 2;
84
+ }
85
+ }
86
+ return {
87
+ operand: {
88
+ immediate,
89
+ indirect,
90
+ indexRegister,
91
+ expression: parsedExpression.expression
92
+ },
93
+ nextTokenIndex: index
94
+ };
95
+ }
96
+ function parsePrefix(tokens, startIndex) {
97
+ const token = tokens[startIndex];
98
+ if (token === undefined) {
99
+ throw new Error("expected expression token");
100
+ }
101
+ if (token.kind === "numericLiteral") {
102
+ return {
103
+ expression: {
104
+ kind: "numericLiteral",
105
+ value: token.lexeme
106
+ },
107
+ nextTokenIndex: startIndex + 1
108
+ };
109
+ }
110
+ if (token.kind === "identifier" || token.kind === "label" || token.kind === "localLabel") {
111
+ return {
112
+ expression: {
113
+ kind: "identifier",
114
+ value: token.lexeme
115
+ },
116
+ nextTokenIndex: startIndex + 1
117
+ };
118
+ }
119
+ if (token.kind === "string") {
120
+ return {
121
+ expression: {
122
+ kind: "string",
123
+ value: token.lexeme.slice(1, -1)
124
+ },
125
+ nextTokenIndex: startIndex + 1
126
+ };
127
+ }
128
+ if (token.kind === "modifier") {
129
+ const parsedInner = parsePrefix(tokens, startIndex + 1);
130
+ return {
131
+ expression: {
132
+ kind: "modifier",
133
+ operator: token.lexeme,
134
+ expression: parsedInner.expression
135
+ },
136
+ nextTokenIndex: parsedInner.nextTokenIndex
137
+ };
138
+ }
139
+ if (token.kind === "expressionOperator" && token.lexeme === "(") {
140
+ const parsedInner = parseExpression(tokens, startIndex + 1);
141
+ expectOperator(tokens[parsedInner.nextTokenIndex], ")");
142
+ return {
143
+ expression: parsedInner.expression,
144
+ nextTokenIndex: parsedInner.nextTokenIndex + 1
145
+ };
146
+ }
147
+ if (token.kind === "expressionOperator" && token.lexeme === "#") {
148
+ return parsePrefix(tokens, startIndex + 1);
149
+ }
150
+ throw new Error(`unexpected expression token: ${token.lexeme}`);
151
+ }
152
+ function expectOperator(token, lexeme) {
153
+ if (token?.kind !== "expressionOperator" || token.lexeme !== lexeme) {
154
+ throw new Error(`expected operator ${lexeme}`);
155
+ }
156
+ }
157
+ function parseIndexRegister(token) {
158
+ const normalized = token?.lexeme.toLowerCase();
159
+ if (token?.kind === "identifier" && (normalized === "x" || normalized === "y")) {
160
+ return normalized;
161
+ }
162
+ throw new Error("expected index register");
163
+ }
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.lexSource = lexSource;
4
+ const metadata_1 = require("./metadata");
5
+ const operatorCharacters = new Set(["(", ")", ",", "#", "+", "-", "*", "/", "=", "<", ">", "^"]);
6
+ function lexSource(source) {
7
+ const lines = source.split(/\r?\n/).map((text, index) => lexLine(text, index));
8
+ return { lines };
9
+ }
10
+ function lexLine(text, line) {
11
+ const tokens = [];
12
+ const firstNonWhitespace = text.search(/\S/);
13
+ if (firstNonWhitespace === -1) {
14
+ return { line, text, tokens };
15
+ }
16
+ const trimmed = text.slice(firstNonWhitespace);
17
+ if (trimmed.startsWith(";") || (firstNonWhitespace === 0 && trimmed.startsWith("*"))) {
18
+ tokens.push(createToken("comment", text.slice(firstNonWhitespace), firstNonWhitespace, text.length));
19
+ return { line, text, tokens };
20
+ }
21
+ let index = firstNonWhitespace;
22
+ let sawOperation = false;
23
+ while (index < text.length) {
24
+ const char = text[index];
25
+ if (char === " " || char === "\t") {
26
+ index += 1;
27
+ continue;
28
+ }
29
+ if (char === ";") {
30
+ tokens.push(createToken("comment", text.slice(index), index, text.length));
31
+ break;
32
+ }
33
+ if (char === '"' || char === "'") {
34
+ const end = consumeString(text, index);
35
+ tokens.push(createToken("string", text.slice(index, end), index, end));
36
+ index = end;
37
+ continue;
38
+ }
39
+ if (operatorCharacters.has(char)) {
40
+ const numericLiteral = consumeNumericLiteral(text, index);
41
+ if (numericLiteral !== null) {
42
+ tokens.push(createToken("numericLiteral", numericLiteral.lexeme, index, numericLiteral.end));
43
+ index = numericLiteral.end;
44
+ continue;
45
+ }
46
+ const operatorKind = char === "<" || char === ">" || char === "^"
47
+ ? "modifier"
48
+ : "expressionOperator";
49
+ tokens.push(createToken(operatorKind, char, index, index + 1));
50
+ index += 1;
51
+ continue;
52
+ }
53
+ const end = consumeWord(text, index);
54
+ const lexeme = text.slice(index, end);
55
+ const kind = classifyWord(text, lexeme, index, end, tokens.length === 0, sawOperation, firstNonWhitespace);
56
+ tokens.push(createToken(kind, lexeme, index, end));
57
+ sawOperation ||= kind === "directive" || kind === "mnemonic";
58
+ index = end;
59
+ }
60
+ return { line, text, tokens };
61
+ }
62
+ function createToken(kind, lexeme, start, end) {
63
+ return {
64
+ kind,
65
+ lexeme,
66
+ start,
67
+ end
68
+ };
69
+ }
70
+ function consumeString(text, start) {
71
+ const quote = text[start];
72
+ let index = start + 1;
73
+ while (index < text.length) {
74
+ if (text[index] === quote) {
75
+ return index + 1;
76
+ }
77
+ index += 1;
78
+ }
79
+ return text.length;
80
+ }
81
+ function consumeNumericLiteral(text, start) {
82
+ const prefixed = text.slice(start).match(/^#?(?:\$[0-9A-Fa-f]+|%[01]+|\d+)/);
83
+ if (prefixed !== null) {
84
+ return {
85
+ lexeme: prefixed[0],
86
+ end: start + prefixed[0].length
87
+ };
88
+ }
89
+ return null;
90
+ }
91
+ function consumeWord(text, start) {
92
+ let index = start;
93
+ while (index < text.length) {
94
+ const char = text[index];
95
+ if (char === " " || char === "\t" || char === ";" || operatorCharacters.has(char)) {
96
+ break;
97
+ }
98
+ index += 1;
99
+ }
100
+ return index;
101
+ }
102
+ function classifyWord(text, lexeme, start, end, isFirstToken, sawOperation, firstNonWhitespace) {
103
+ const normalized = lexeme.toLowerCase();
104
+ if (metadata_1.opcodeTable.has(normalized)) {
105
+ return "mnemonic";
106
+ }
107
+ if (metadata_1.directiveTable.has(normalized)) {
108
+ return "directive";
109
+ }
110
+ if (lexeme.startsWith("]") || lexeme.startsWith(":")) {
111
+ return "localLabel";
112
+ }
113
+ if (/^(?:\$[0-9A-Fa-f]+|%[01]+|\d+)$/.test(lexeme)) {
114
+ return "numericLiteral";
115
+ }
116
+ const trailingText = text.slice(end);
117
+ const looksLikeLabelBoundary = trailingText.length === 0 || /^[\t ]/.test(trailingText);
118
+ if (isFirstToken && !sawOperation && firstNonWhitespace === 0 && looksLikeLabelBoundary) {
119
+ return "label";
120
+ }
121
+ return "identifier";
122
+ }
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveLocalLabels = resolveLocalLabels;
4
+ function resolveLocalLabels(document) {
5
+ const definitions = new Map();
6
+ const references = new Map();
7
+ const definitionsByAnchor = new Map();
8
+ let currentAnchor = null;
9
+ for (const line of document.lines) {
10
+ currentAnchor = updateAnchor(currentAnchor, line.node, line.line);
11
+ const localDefinition = getLocalDefinition(line.node);
12
+ if (currentAnchor === null || localDefinition === null) {
13
+ continue;
14
+ }
15
+ const qualifiedName = qualifyName(localDefinition, currentAnchor.line);
16
+ const definition = {
17
+ name: localDefinition,
18
+ line: line.line,
19
+ anchor: currentAnchor.name,
20
+ qualifiedName
21
+ };
22
+ definitions.set(qualifiedName, definition);
23
+ let anchorDefinitions = definitionsByAnchor.get(currentAnchor.name);
24
+ if (anchorDefinitions === undefined) {
25
+ anchorDefinitions = new Map();
26
+ definitionsByAnchor.set(currentAnchor.name, anchorDefinitions);
27
+ }
28
+ anchorDefinitions.set(localDefinition, definition);
29
+ }
30
+ currentAnchor = null;
31
+ for (const line of document.lines) {
32
+ currentAnchor = updateAnchor(currentAnchor, line.node, line.line);
33
+ if (currentAnchor === null) {
34
+ continue;
35
+ }
36
+ const anchorDefinitions = definitionsByAnchor.get(currentAnchor.name);
37
+ if (anchorDefinitions === undefined) {
38
+ continue;
39
+ }
40
+ for (const localName of findLocalReferences(line.node)) {
41
+ const target = anchorDefinitions.get(localName);
42
+ if (target === undefined) {
43
+ continue;
44
+ }
45
+ references.set(qualifyName(localName, line.line), {
46
+ name: localName,
47
+ line: line.line,
48
+ anchor: currentAnchor.name,
49
+ qualifiedName: target.qualifiedName,
50
+ targetLine: target.line
51
+ });
52
+ }
53
+ }
54
+ return {
55
+ definitions,
56
+ references
57
+ };
58
+ }
59
+ function updateAnchor(currentAnchor, node, line) {
60
+ const label = getGlobalLabel(node);
61
+ if (label === null) {
62
+ return currentAnchor;
63
+ }
64
+ return {
65
+ name: label,
66
+ line
67
+ };
68
+ }
69
+ function getGlobalLabel(node) {
70
+ if (node.shape === "equate" && !isLocalLabel(node.label)) {
71
+ return node.label;
72
+ }
73
+ if (node.shape === "labelOnly" && !isLocalLabel(node.label)) {
74
+ return node.label;
75
+ }
76
+ if (node.shape === "instruction" && node.label !== null && !isLocalLabel(node.label)) {
77
+ return node.label;
78
+ }
79
+ if (node.shape === "directive" && node.label !== null && !isLocalLabel(node.label)) {
80
+ return node.label;
81
+ }
82
+ if (node.shape === "data" && node.label !== null && !isLocalLabel(node.label)) {
83
+ return node.label;
84
+ }
85
+ return null;
86
+ }
87
+ function getLocalDefinition(node) {
88
+ if (node.shape === "labelOnly" && isLocalLabel(node.label)) {
89
+ return node.label;
90
+ }
91
+ if (node.shape === "instruction" && node.label !== null && isLocalLabel(node.label)) {
92
+ return node.label;
93
+ }
94
+ if (node.shape === "directive" && node.label !== null && isLocalLabel(node.label)) {
95
+ return node.label;
96
+ }
97
+ if (node.shape === "data" && node.label !== null && isLocalLabel(node.label)) {
98
+ return node.label;
99
+ }
100
+ if (node.shape === "equate" && isLocalLabel(node.label)) {
101
+ return node.label;
102
+ }
103
+ return null;
104
+ }
105
+ function findLocalReferences(node) {
106
+ if (node.shape === "instruction" && node.operand !== null) {
107
+ return findLocalNamesInOperand(node.operand);
108
+ }
109
+ if (node.shape === "directive" && node.operand !== null) {
110
+ return findLocalNamesInExpression(node.operand);
111
+ }
112
+ if (node.shape === "equate") {
113
+ return findLocalNamesInExpression(node.expression);
114
+ }
115
+ return [];
116
+ }
117
+ function findLocalNamesInOperand(operand) {
118
+ return findLocalNamesInExpression(operand.expression);
119
+ }
120
+ function findLocalNamesInExpression(expression) {
121
+ switch (expression.kind) {
122
+ case "identifier":
123
+ return isLocalLabel(expression.value) ? [expression.value] : [];
124
+ case "modifier":
125
+ return findLocalNamesInExpression(expression.expression);
126
+ case "binary":
127
+ return [
128
+ ...findLocalNamesInExpression(expression.left),
129
+ ...findLocalNamesInExpression(expression.right)
130
+ ];
131
+ default:
132
+ return [];
133
+ }
134
+ }
135
+ function isLocalLabel(name) {
136
+ return name.startsWith("]") || name.startsWith(":");
137
+ }
138
+ function qualifyName(name, line) {
139
+ return `${name}@${line}`;
140
+ }
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.directiveTable = exports.directiveDefinitions = exports.opcodeTable = exports.opcodeDefinitions = void 0;
4
+ function defineOpcode(mnemonic, modes) {
5
+ return {
6
+ mnemonic,
7
+ modes
8
+ };
9
+ }
10
+ function defineDirective(name, kind, supported, summary) {
11
+ return {
12
+ name,
13
+ kind,
14
+ supported,
15
+ summary
16
+ };
17
+ }
18
+ exports.opcodeDefinitions = [
19
+ defineOpcode("adc", ["immediate", "zeroPage", "zeroPageX", "absolute", "absoluteX", "absoluteY", "indexedIndirect", "indirectIndexed"]),
20
+ defineOpcode("and", ["immediate", "zeroPage", "zeroPageX", "absolute", "absoluteX", "absoluteY", "indexedIndirect", "indirectIndexed"]),
21
+ defineOpcode("asl", ["accumulator", "zeroPage", "zeroPageX", "absolute", "absoluteX"]),
22
+ defineOpcode("bcc", ["relative"]),
23
+ defineOpcode("bcs", ["relative"]),
24
+ defineOpcode("beq", ["relative"]),
25
+ defineOpcode("bit", ["zeroPage", "absolute"]),
26
+ defineOpcode("bmi", ["relative"]),
27
+ defineOpcode("bne", ["relative"]),
28
+ defineOpcode("bpl", ["relative"]),
29
+ defineOpcode("brk", ["implied"]),
30
+ defineOpcode("bvc", ["relative"]),
31
+ defineOpcode("bvs", ["relative"]),
32
+ defineOpcode("clc", ["implied"]),
33
+ defineOpcode("cld", ["implied"]),
34
+ defineOpcode("cli", ["implied"]),
35
+ defineOpcode("clv", ["implied"]),
36
+ defineOpcode("cmp", ["immediate", "zeroPage", "zeroPageX", "absolute", "absoluteX", "absoluteY", "indexedIndirect", "indirectIndexed"]),
37
+ defineOpcode("cpx", ["immediate", "zeroPage", "absolute"]),
38
+ defineOpcode("cpy", ["immediate", "zeroPage", "absolute"]),
39
+ defineOpcode("dec", ["zeroPage", "zeroPageX", "absolute", "absoluteX"]),
40
+ defineOpcode("dex", ["implied"]),
41
+ defineOpcode("dey", ["implied"]),
42
+ defineOpcode("eor", ["immediate", "zeroPage", "zeroPageX", "absolute", "absoluteX", "absoluteY", "indexedIndirect", "indirectIndexed"]),
43
+ defineOpcode("inc", ["zeroPage", "zeroPageX", "absolute", "absoluteX"]),
44
+ defineOpcode("inx", ["implied"]),
45
+ defineOpcode("iny", ["implied"]),
46
+ defineOpcode("jmp", ["absolute", "indirect"]),
47
+ defineOpcode("jsr", ["absolute"]),
48
+ defineOpcode("lda", ["immediate", "zeroPage", "zeroPageX", "absolute", "absoluteX", "absoluteY", "indexedIndirect", "indirectIndexed"]),
49
+ defineOpcode("ldx", ["immediate", "zeroPage", "zeroPageY", "absolute", "absoluteY"]),
50
+ defineOpcode("ldy", ["immediate", "zeroPage", "zeroPageX", "absolute", "absoluteX"]),
51
+ defineOpcode("lsr", ["accumulator", "zeroPage", "zeroPageX", "absolute", "absoluteX"]),
52
+ defineOpcode("nop", ["implied"]),
53
+ defineOpcode("ora", ["immediate", "zeroPage", "zeroPageX", "absolute", "absoluteX", "absoluteY", "indexedIndirect", "indirectIndexed"]),
54
+ defineOpcode("pha", ["implied"]),
55
+ defineOpcode("php", ["implied"]),
56
+ defineOpcode("pla", ["implied"]),
57
+ defineOpcode("plp", ["implied"]),
58
+ defineOpcode("rol", ["accumulator", "zeroPage", "zeroPageX", "absolute", "absoluteX"]),
59
+ defineOpcode("ror", ["accumulator", "zeroPage", "zeroPageX", "absolute", "absoluteX"]),
60
+ defineOpcode("rti", ["implied"]),
61
+ defineOpcode("rts", ["implied"]),
62
+ defineOpcode("sbc", ["immediate", "zeroPage", "zeroPageX", "absolute", "absoluteX", "absoluteY", "indexedIndirect", "indirectIndexed"]),
63
+ defineOpcode("sec", ["implied"]),
64
+ defineOpcode("sed", ["implied"]),
65
+ defineOpcode("sei", ["implied"]),
66
+ defineOpcode("sta", ["zeroPage", "zeroPageX", "absolute", "absoluteX", "absoluteY", "indexedIndirect", "indirectIndexed"]),
67
+ defineOpcode("stx", ["zeroPage", "zeroPageY", "absolute"]),
68
+ defineOpcode("sty", ["zeroPage", "zeroPageX", "absolute"]),
69
+ defineOpcode("tax", ["implied"]),
70
+ defineOpcode("tay", ["implied"]),
71
+ defineOpcode("tsx", ["implied"]),
72
+ defineOpcode("txa", ["implied"]),
73
+ defineOpcode("txs", ["implied"]),
74
+ defineOpcode("tya", ["implied"])
75
+ ];
76
+ exports.opcodeTable = new Map(exports.opcodeDefinitions.map((definition) => [definition.mnemonic, definition]));
77
+ exports.directiveDefinitions = [
78
+ defineDirective("asm", "include", true, "Assemble another source file immediately."),
79
+ defineDirective("asc", "data", true, "Emit an ASCII string."),
80
+ defineDirective("da", "data", true, "Emit an address-sized value."),
81
+ defineDirective("db", "data", true, "Emit byte data."),
82
+ defineDirective("dend", "storage", true, "End a DUM storage section."),
83
+ defineDirective("dsk", "build", true, "Set the output disk or image name."),
84
+ defineDirective("ds", "storage", true, "Reserve storage bytes."),
85
+ defineDirective("dum", "storage", true, "Begin a DUM storage section."),
86
+ defineDirective("end", "assembler", true, "End assembly."),
87
+ defineDirective("equ", "assembler", true, "Define a symbolic constant."),
88
+ defineDirective("err", "assembler", true, "Force an assembly error."),
89
+ defineDirective("hex", "data", true, "Emit raw hexadecimal bytes."),
90
+ defineDirective("mac", "assembler", true, "Begin a macro definition."),
91
+ defineDirective("mx", "mode", false, "65816-only accumulator and index width control."),
92
+ defineDirective("org", "assembler", true, "Set or restore the assembly origin."),
93
+ defineDirective("put", "include", true, "Include another source file."),
94
+ defineDirective("sav", "build", true, "Save an output file."),
95
+ defineDirective("sna", "build", true, "Set the output file name."),
96
+ defineDirective("str", "data", true, "Emit a Merlin string."),
97
+ defineDirective("typ", "build", true, "Set the output file type."),
98
+ defineDirective("use", "include", true, "Include a library-style source file."),
99
+ defineDirective("xc", "mode", false, "65816-only extended instruction mode control.")
100
+ ];
101
+ exports.directiveTable = new Map(exports.directiveDefinitions.map((definition) => [definition.name, definition]));
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseSourceLines = parseSourceLines;
4
+ exports.parseLexedLine = parseLexedLine;
5
+ const metadata_1 = require("./metadata");
6
+ const expression_1 = require("./expression");
7
+ const lexer_1 = require("./lexer");
8
+ const dataDirectiveKinds = new Set(["data"]);
9
+ function parseSourceLines(source) {
10
+ return (0, lexer_1.lexSource)(source).lines.map(parseLexedLine);
11
+ }
12
+ function parseLexedLine(line) {
13
+ const tokens = stripTrailingComment(line.tokens);
14
+ if (tokens.length === 0) {
15
+ const commentToken = line.tokens[0];
16
+ if (commentToken?.kind === "comment") {
17
+ return {
18
+ shape: "commentOnly",
19
+ text: line.text,
20
+ comment: commentToken.lexeme
21
+ };
22
+ }
23
+ return {
24
+ shape: "empty",
25
+ text: line.text
26
+ };
27
+ }
28
+ try {
29
+ return parseStructuredLine(line.text, tokens);
30
+ }
31
+ catch (error) {
32
+ return {
33
+ shape: "malformed",
34
+ text: line.text,
35
+ message: error instanceof Error ? error.message : "unknown parse failure"
36
+ };
37
+ }
38
+ }
39
+ function parseStructuredLine(text, tokens) {
40
+ let index = 0;
41
+ let label = null;
42
+ if (tokens[index]?.kind === "label" || tokens[index]?.kind === "localLabel") {
43
+ label = tokens[index]?.lexeme ?? null;
44
+ index += 1;
45
+ }
46
+ const token = tokens[index];
47
+ if (token === undefined) {
48
+ if (label !== null) {
49
+ return {
50
+ shape: "labelOnly",
51
+ text,
52
+ label
53
+ };
54
+ }
55
+ return {
56
+ shape: "empty",
57
+ text
58
+ };
59
+ }
60
+ if (token.kind === "expressionOperator" && token.lexeme === "=") {
61
+ if (label === null) {
62
+ throw new Error("equate requires a label");
63
+ }
64
+ const parsed = (0, expression_1.parseExpression)(tokens, index + 1);
65
+ return {
66
+ shape: "equate",
67
+ text,
68
+ label,
69
+ expression: parsed.expression
70
+ };
71
+ }
72
+ if (token.kind === "mnemonic") {
73
+ const operandTokens = tokens.slice(index + 1);
74
+ const operand = operandTokens.length > 0 ? (0, expression_1.parseOperand)(operandTokens).operand : null;
75
+ return {
76
+ shape: "instruction",
77
+ text,
78
+ label,
79
+ mnemonic: token.lexeme.toLowerCase(),
80
+ operand
81
+ };
82
+ }
83
+ if (token.kind === "directive") {
84
+ const directive = metadata_1.directiveTable.get(token.lexeme.toLowerCase());
85
+ if (directive !== undefined && dataDirectiveKinds.has(directive.kind)) {
86
+ return {
87
+ shape: "data",
88
+ text,
89
+ label,
90
+ directive: token.lexeme.toLowerCase(),
91
+ payload: tokens.slice(index + 1).map((current) => current.lexeme).join("")
92
+ };
93
+ }
94
+ const operandTokens = tokens.slice(index + 1);
95
+ const directiveName = token.lexeme.toLowerCase();
96
+ if (operandTokens.length > 0 && (directiveName === "end" || directiveName === "dend" || directiveName === "xc")) {
97
+ throw new Error(`unexpected operand for ${directiveName}`);
98
+ }
99
+ const operand = operandTokens.length > 0
100
+ ? (0, expression_1.parseExpression)(operandTokens).expression
101
+ : null;
102
+ return {
103
+ shape: "directive",
104
+ text,
105
+ label,
106
+ directive: directiveName,
107
+ operand
108
+ };
109
+ }
110
+ throw new Error(`unsupported line start: ${token.lexeme}`);
111
+ }
112
+ function stripTrailingComment(tokens) {
113
+ const commentIndex = tokens.findIndex((token) => token.kind === "comment");
114
+ if (commentIndex === -1) {
115
+ return tokens;
116
+ }
117
+ return tokens.slice(0, commentIndex);
118
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.collectSymbols = collectSymbols;
4
+ const metadata_1 = require("./metadata");
5
+ function collectSymbols(document) {
6
+ const symbols = new Map();
7
+ for (const line of document.lines) {
8
+ const node = line.node;
9
+ if (node.shape === "equate") {
10
+ symbols.set(node.label, defineSymbol(node.label, "equate", line.line));
11
+ continue;
12
+ }
13
+ if (node.shape === "labelOnly") {
14
+ symbols.set(node.label, defineSymbol(node.label, "label", line.line));
15
+ continue;
16
+ }
17
+ if (node.shape === "instruction" && node.label !== null) {
18
+ symbols.set(node.label, defineSymbol(node.label, "label", line.line));
19
+ continue;
20
+ }
21
+ if (node.shape === "directive" && node.label !== null) {
22
+ const directive = metadata_1.directiveTable.get(node.directive);
23
+ if (directive?.kind === "data" || directive?.kind === "storage") {
24
+ symbols.set(node.label, defineSymbol(node.label, "data", line.line));
25
+ }
26
+ continue;
27
+ }
28
+ if (node.shape === "data" && node.label !== null) {
29
+ symbols.set(node.label, defineSymbol(node.label, "data", line.line));
30
+ }
31
+ }
32
+ return symbols;
33
+ }
34
+ function defineSymbol(name, kind, line) {
35
+ return {
36
+ name,
37
+ kind,
38
+ line
39
+ };
40
+ }