@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,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
|
+
}
|