c64bridge 0.5.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/.github/prompts/README.md +6 -0
- package/.github/prompts/assembly-routine-session.prompt.md +13 -0
- package/.github/prompts/basic-program-session.prompt.md +13 -0
- package/.github/prompts/drive-management-session.prompt.md +13 -0
- package/.github/prompts/petscii-art-session.prompt.md +13 -0
- package/.github/prompts/play-classic-game.prompt.md +13 -0
- package/.github/prompts/sid-music-session.prompt.md +13 -0
- package/AGENTS.md +173 -0
- package/LICENSE +339 -0
- package/README.md +582 -0
- package/data/README.md +16 -0
- package/data/api/basic-api-spec.md +154 -0
- package/data/api/kernal-api-spec.md +68 -0
- package/data/assembly/assembly-spec.md +196 -0
- package/data/assembly/examples/effects/sinewave.asm +5 -0
- package/data/assembly/examples/graphics/sprite/mover.asm +15 -0
- package/data/assembly/examples/sid-file-structure.md +8 -0
- package/data/assembly/examples/sid-overview.md +11 -0
- package/data/audio/examples/Biosphere.sid +0 -0
- package/data/audio/sid-file-structure.md +91 -0
- package/data/audio/sid-programming-best-practices.md +198 -0
- package/data/audio/sid-spec.md +166 -0
- package/data/audio/sidwave.md +131 -0
- package/data/audio/sidwave.schema.json +64 -0
- package/data/basic/basic-spec.md +230 -0
- package/data/basic/examples/audio/alle-meine-entchen-expressive.bas +41 -0
- package/data/basic/examples/audio/c-major-scale-pleasant.bas +29 -0
- package/data/basic/examples/io/joystick.bas +5 -0
- package/data/basic/examples/video/bounce.bas +8 -0
- package/data/basic/examples/video/entchen-petscii.bas +16 -0
- package/data/basic/examples/video/hello-world.bas +4 -0
- package/data/basic/examples/video/wave.bas +7 -0
- package/data/context/bootstrap.md +148 -0
- package/data/context/chat.md +36 -0
- package/data/drive/drive-spec.md +215 -0
- package/data/embeddings_asm.json +1 -0
- package/data/embeddings_basic.json +1 -0
- package/data/embeddings_hardware.json +1 -0
- package/data/embeddings_mixed.json +1 -0
- package/data/embeddings_other.json +1 -0
- package/data/io/cia-spec.md +115 -0
- package/data/io/io-spec.md +124 -0
- package/data/memory/kernal-memory-map.md +227 -0
- package/data/memory/low-memory-map.md +133 -0
- package/data/memory/memory-map.md +186 -0
- package/data/printer/printer-commodore-bitmap.md +31 -0
- package/data/printer/printer-commodore.md +28 -0
- package/data/printer/printer-epson-bitmap.md +29 -0
- package/data/printer/printer-epson.md +30 -0
- package/data/printer/printer-prompts.md +29 -0
- package/data/printer/printer-spec.md +180 -0
- package/data/video/character-set.csv +257 -0
- package/data/video/vic-spec.md +276 -0
- package/dist/assemblyConverter.js +1299 -0
- package/dist/audio/record_and_analyze_audio.js +292 -0
- package/dist/basicConverter.js +193 -0
- package/dist/bootstrap/stdio-logger.js +32 -0
- package/dist/c64Client.js +1052 -0
- package/dist/chargen.js +197 -0
- package/dist/config.js +142 -0
- package/dist/context.js +157 -0
- package/dist/device.js +413 -0
- package/dist/index.js +7 -0
- package/dist/knowledge.js +454 -0
- package/dist/logger.js +182 -0
- package/dist/loggingHttpClient.js +88 -0
- package/dist/mcp-server.js +426 -0
- package/dist/petscii.js +135 -0
- package/dist/petsciiArt.js +356 -0
- package/dist/platform.js +82 -0
- package/dist/prompts/registry.js +560 -0
- package/dist/rag/discover.config.json +41 -0
- package/dist/rag/discover.js +401 -0
- package/dist/rag/embeddings.js +60 -0
- package/dist/rag/externalFetcher.js +934 -0
- package/dist/rag/indexer.js +770 -0
- package/dist/rag/init.js +139 -0
- package/dist/rag/knowledgeIndex.js +361 -0
- package/dist/rag/loggingRetriever.js +46 -0
- package/dist/rag/rateLimiter.js +127 -0
- package/dist/rag/retriever.js +74 -0
- package/dist/rag/sources.csv +31 -0
- package/dist/rag/types.js +5 -0
- package/dist/rag/urlUtils.js +38 -0
- package/dist/sidplayRunner.js +240 -0
- package/dist/sidwave.js +101 -0
- package/dist/sidwaveCompiler.js +329 -0
- package/dist/tools/audio.js +944 -0
- package/dist/tools/developer.js +515 -0
- package/dist/tools/errors.js +59 -0
- package/dist/tools/graphics.js +469 -0
- package/dist/tools/machineControl.js +267 -0
- package/dist/tools/memory.js +224 -0
- package/dist/tools/printer.js +482 -0
- package/dist/tools/programRunners.js +315 -0
- package/dist/tools/rag.js +153 -0
- package/dist/tools/registry.js +58 -0
- package/dist/tools/responses.js +36 -0
- package/dist/tools/schema.js +304 -0
- package/dist/tools/storage.js +740 -0
- package/dist/tools/streaming.js +132 -0
- package/dist/tools/types.js +99 -0
- package/dist/viceRunner.js +239 -0
- package/doc/MCP_SETUP.md +153 -0
- package/doc/README.md +7 -0
- package/doc/developer.md +312 -0
- package/doc/img/logo.png +0 -0
- package/doc/img/prompts/duck_petscii.png +0 -0
- package/doc/img/prompts/duck_song.png +0 -0
- package/doc/img/vscode/vscode-copilot-c64-chat-mode.png +0 -0
- package/doc/img/vscode/vscode-copilot-hello-world.png +0 -0
- package/doc/prompts/README.md +96 -0
- package/doc/prompts/user-prompt-ideas.md +147 -0
- package/doc/rest/c64-openapi.yaml +979 -0
- package/doc/rest/c64-rest-api.md +106 -0
- package/doc/troubleshooting-mcp.md +177 -0
- package/generated/c64/index.js +857 -0
- package/mcp.json +106 -0
- package/package.json +79 -0
- package/scripts/c64-cli.mjs +142 -0
- package/scripts/c64-tool.sh +83 -0
- package/scripts/check-package.mjs +89 -0
- package/scripts/cli.js +61 -0
- package/scripts/fetch-external-sources.mjs +67 -0
- package/scripts/generate-changelog.mjs +131 -0
- package/scripts/postbuild.mjs +101 -0
- package/scripts/prepare-release.mjs +58 -0
- package/scripts/rag-rebuild.mjs +53 -0
- package/scripts/register-ts-node.mjs +6 -0
- package/scripts/run-mcp-check.sh +104 -0
- package/scripts/run-tests.mjs +195 -0
- package/scripts/sid2wav.sh +94 -0
- package/scripts/smoke-run.sh +65 -0
- package/scripts/start-mock-c64.mjs +51 -0
- package/scripts/start-mock.mjs +31 -0
- package/scripts/start.mjs +72 -0
- package/scripts/update-readme.ts +164 -0
- package/scripts/verify-package.mjs +147 -0
|
@@ -0,0 +1,1299 @@
|
|
|
1
|
+
/*
|
|
2
|
+
C64 Bridge - An MCP Server for the Commodore 64 Ultimate
|
|
3
|
+
Copyright (C) 2025 Christian Gleissner
|
|
4
|
+
|
|
5
|
+
Licensed under the GNU General Public License v2.0 or later.
|
|
6
|
+
See <https://www.gnu.org/licenses/> for details.
|
|
7
|
+
*/
|
|
8
|
+
import { Buffer } from "node:buffer";
|
|
9
|
+
export class AssemblyError extends Error {
|
|
10
|
+
constructor(message, location) {
|
|
11
|
+
super(`${location.file}:${location.line}: ${message}`);
|
|
12
|
+
this.location = location;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
var AddressingMode;
|
|
16
|
+
(function (AddressingMode) {
|
|
17
|
+
AddressingMode["Implied"] = "implied";
|
|
18
|
+
AddressingMode["Accumulator"] = "accumulator";
|
|
19
|
+
AddressingMode["Immediate"] = "immediate";
|
|
20
|
+
AddressingMode["ZeroPage"] = "zeroPage";
|
|
21
|
+
AddressingMode["ZeroPageX"] = "zeroPageX";
|
|
22
|
+
AddressingMode["ZeroPageY"] = "zeroPageY";
|
|
23
|
+
AddressingMode["Absolute"] = "absolute";
|
|
24
|
+
AddressingMode["AbsoluteX"] = "absoluteX";
|
|
25
|
+
AddressingMode["AbsoluteY"] = "absoluteY";
|
|
26
|
+
AddressingMode["Indirect"] = "indirect";
|
|
27
|
+
AddressingMode["IndirectX"] = "indirectX";
|
|
28
|
+
AddressingMode["IndirectY"] = "indirectY";
|
|
29
|
+
AddressingMode["Relative"] = "relative";
|
|
30
|
+
})(AddressingMode || (AddressingMode = {}));
|
|
31
|
+
const DEFAULT_LOAD_ADDRESS = 0x0801;
|
|
32
|
+
const MAX_ADDRESS = 0xffff;
|
|
33
|
+
const SINGLE_CHAR_TOKENS = new Set([",", ":", "#", "(", ")", "+", "-", "*", "/", "%", "&", "|", "^", "<", ">", "=", "!", "~"]);
|
|
34
|
+
const MULTI_CHAR_TOKENS = new Set([
|
|
35
|
+
"<<",
|
|
36
|
+
">>",
|
|
37
|
+
"<=",
|
|
38
|
+
">=",
|
|
39
|
+
"==",
|
|
40
|
+
"!=",
|
|
41
|
+
]);
|
|
42
|
+
const BINARY_OPERATOR_PRECEDENCE = {
|
|
43
|
+
"|": 1,
|
|
44
|
+
"^": 2,
|
|
45
|
+
"&": 3,
|
|
46
|
+
"==": 4,
|
|
47
|
+
"!=": 4,
|
|
48
|
+
"<": 5,
|
|
49
|
+
"<=": 5,
|
|
50
|
+
">": 5,
|
|
51
|
+
">=": 5,
|
|
52
|
+
"<<": 6,
|
|
53
|
+
">>": 6,
|
|
54
|
+
"+": 7,
|
|
55
|
+
"-": 7,
|
|
56
|
+
"*": 8,
|
|
57
|
+
"/": 8,
|
|
58
|
+
"%": 8,
|
|
59
|
+
};
|
|
60
|
+
const DIRECTIVE_ALIASES = {
|
|
61
|
+
".ORG": "org",
|
|
62
|
+
ORG: "org",
|
|
63
|
+
".BYTE": "byte",
|
|
64
|
+
"BYTE": "byte",
|
|
65
|
+
"DB": "byte",
|
|
66
|
+
".DB": "byte",
|
|
67
|
+
".WORD": "word",
|
|
68
|
+
"WORD": "word",
|
|
69
|
+
"DW": "word",
|
|
70
|
+
".DW": "word",
|
|
71
|
+
".RES": "reserve",
|
|
72
|
+
"RES": "reserve",
|
|
73
|
+
".DS": "reserve",
|
|
74
|
+
"DS": "reserve",
|
|
75
|
+
".INCLUDE": "include",
|
|
76
|
+
"INCLUDE": "include",
|
|
77
|
+
};
|
|
78
|
+
const RELATIVE_ONLY = new Set(["BCC", "BCS", "BEQ", "BMI", "BNE", "BPL", "BVC", "BVS"]);
|
|
79
|
+
export function assemblyToPrg(source, options) {
|
|
80
|
+
const normalized = normalizeSource(source);
|
|
81
|
+
const loadAddress = options?.loadAddress ?? DEFAULT_LOAD_ADDRESS;
|
|
82
|
+
const rootFile = options?.fileName ?? "(input)";
|
|
83
|
+
const resolver = options?.resolveInclude;
|
|
84
|
+
const parser = new Parser(resolver);
|
|
85
|
+
const statements = parser.parse(normalized, rootFile);
|
|
86
|
+
const assembler = new Assembler(statements, loadAddress);
|
|
87
|
+
assembler.runPass(0);
|
|
88
|
+
assembler.runPass(1);
|
|
89
|
+
return assembler.buildPrg();
|
|
90
|
+
}
|
|
91
|
+
function normalizeSource(input) {
|
|
92
|
+
return input.replace(/\r\n?/g, "\n");
|
|
93
|
+
}
|
|
94
|
+
class Parser {
|
|
95
|
+
constructor(includeResolver) {
|
|
96
|
+
this.includeResolver = includeResolver;
|
|
97
|
+
}
|
|
98
|
+
parse(source, fileName) {
|
|
99
|
+
const statements = [];
|
|
100
|
+
this.parseInto(statements, source, fileName, []);
|
|
101
|
+
return statements;
|
|
102
|
+
}
|
|
103
|
+
parseInto(statements, source, fileName, includeStack) {
|
|
104
|
+
const lines = source.split("\n");
|
|
105
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
106
|
+
const originalLine = lines[index] ?? "";
|
|
107
|
+
const location = { file: fileName, line: index + 1, text: originalLine };
|
|
108
|
+
const tokens = tokenize(originalLine);
|
|
109
|
+
if (tokens.length === 0) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const stream = new TokenStream(tokens);
|
|
113
|
+
const labels = [];
|
|
114
|
+
while (stream.peek()?.kind === "symbol" && stream.peek(1)?.kind === "punct" && stream.peek(1).value === ":") {
|
|
115
|
+
labels.push(stream.consumeSymbol());
|
|
116
|
+
stream.consumePunct(":");
|
|
117
|
+
}
|
|
118
|
+
const statement = this.parseStatement(stream, location, includeStack, statements);
|
|
119
|
+
statements.push({
|
|
120
|
+
labels,
|
|
121
|
+
statement,
|
|
122
|
+
location,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
parseStatement(stream, location, includeStack, statements) {
|
|
127
|
+
const first = stream.peek();
|
|
128
|
+
if (!first)
|
|
129
|
+
return undefined;
|
|
130
|
+
if (first.kind === "symbol" && stream.peek(1)?.kind === "punct" && stream.peek(1).value === "=") {
|
|
131
|
+
const target = stream.consumeSymbol();
|
|
132
|
+
stream.consumePunct("=");
|
|
133
|
+
const expr = parseExpression(stream, new Set());
|
|
134
|
+
stream.expectEnd();
|
|
135
|
+
return {
|
|
136
|
+
kind: "assignment",
|
|
137
|
+
target,
|
|
138
|
+
expr,
|
|
139
|
+
isLocationCounter: false,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (first.kind === "punct" && first.value === "*" && stream.peek(1)?.kind === "punct" && stream.peek(1).value === "=") {
|
|
143
|
+
stream.consumePunct("*");
|
|
144
|
+
stream.consumePunct("=");
|
|
145
|
+
const expr = parseExpression(stream, new Set());
|
|
146
|
+
stream.expectEnd();
|
|
147
|
+
return {
|
|
148
|
+
kind: "assignment",
|
|
149
|
+
target: "*",
|
|
150
|
+
expr,
|
|
151
|
+
isLocationCounter: true,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (first.kind !== "symbol") {
|
|
155
|
+
throw new AssemblyError("Expected an opcode, directive, or assignment", location);
|
|
156
|
+
}
|
|
157
|
+
const upper = stream.consumeSymbol().toUpperCase();
|
|
158
|
+
if (upper in DIRECTIVE_ALIASES) {
|
|
159
|
+
const directive = DIRECTIVE_ALIASES[upper];
|
|
160
|
+
switch (directive) {
|
|
161
|
+
case "org": {
|
|
162
|
+
const expr = parseExpression(stream, new Set());
|
|
163
|
+
stream.expectEnd();
|
|
164
|
+
return {
|
|
165
|
+
kind: "directive",
|
|
166
|
+
name: "org",
|
|
167
|
+
args: [{ kind: "expr", expr }],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
case "include": {
|
|
171
|
+
const includePath = stream.consumeString("include expects a string literal path");
|
|
172
|
+
stream.expectEnd();
|
|
173
|
+
if (!this.includeResolver) {
|
|
174
|
+
throw new AssemblyError("include directive requires a resolveInclude option", location);
|
|
175
|
+
}
|
|
176
|
+
if (includeStack.includes(includePath)) {
|
|
177
|
+
throw new AssemblyError(`Recursive include detected for "${includePath}"`, location);
|
|
178
|
+
}
|
|
179
|
+
const resolved = this.includeResolver(includePath, location.file);
|
|
180
|
+
const includeContent = typeof resolved === "string" ? resolved : resolved.contents;
|
|
181
|
+
const includeName = typeof resolved === "string" ? includePath : resolved.fileName ?? includePath;
|
|
182
|
+
this.parseInto(statements, normalizeSource(includeContent), includeName, [...includeStack, includePath]);
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
case "byte":
|
|
186
|
+
case "word": {
|
|
187
|
+
const args = [];
|
|
188
|
+
while (!stream.isAtEnd()) {
|
|
189
|
+
const next = stream.peek();
|
|
190
|
+
if (!next)
|
|
191
|
+
break;
|
|
192
|
+
if (next.kind === "string") {
|
|
193
|
+
args.push({ kind: "string", value: stream.consumeString() });
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
args.push({ kind: "expr", expr: parseExpression(stream, new Set()) });
|
|
197
|
+
}
|
|
198
|
+
if (stream.peek()?.kind === "punct" && stream.peek().value === ",") {
|
|
199
|
+
stream.consumePunct(",");
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
stream.expectEnd();
|
|
206
|
+
return {
|
|
207
|
+
kind: "directive",
|
|
208
|
+
name: directive,
|
|
209
|
+
args,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
case "reserve": {
|
|
213
|
+
const expr = parseExpression(stream, new Set());
|
|
214
|
+
stream.expectEnd();
|
|
215
|
+
return {
|
|
216
|
+
kind: "directive",
|
|
217
|
+
name: "reserve",
|
|
218
|
+
args: [{ kind: "expr", expr }],
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
default:
|
|
222
|
+
throw new AssemblyError(`Unhandled directive ${directive}`, location);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const mnemonic = upper;
|
|
226
|
+
const operand = parseOperand(stream);
|
|
227
|
+
stream.expectEnd();
|
|
228
|
+
return {
|
|
229
|
+
kind: "instruction",
|
|
230
|
+
mnemonic,
|
|
231
|
+
operand,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function tokenize(line) {
|
|
236
|
+
const tokens = [];
|
|
237
|
+
let index = 0;
|
|
238
|
+
const length = line.length;
|
|
239
|
+
while (index < length) {
|
|
240
|
+
const char = line[index] ?? "";
|
|
241
|
+
if (char === ";" || char === "\u201b") {
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
if (char === " " || char === "\t") {
|
|
245
|
+
index += 1;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (char === "\"" || char === "'") {
|
|
249
|
+
const quote = char;
|
|
250
|
+
index += 1;
|
|
251
|
+
let value = "";
|
|
252
|
+
let closed = false;
|
|
253
|
+
while (index < length) {
|
|
254
|
+
const current = line[index] ?? "";
|
|
255
|
+
if (current === "\\" && index + 1 < length) {
|
|
256
|
+
const escape = line[index + 1] ?? "";
|
|
257
|
+
const translated = translateEscape(escape);
|
|
258
|
+
value += translated;
|
|
259
|
+
index += 2;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (current === quote) {
|
|
263
|
+
index += 1;
|
|
264
|
+
closed = true;
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
value += current;
|
|
268
|
+
index += 1;
|
|
269
|
+
}
|
|
270
|
+
if (!closed) {
|
|
271
|
+
throw new Error("Unterminated string literal");
|
|
272
|
+
}
|
|
273
|
+
tokens.push({ kind: "string", value });
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (isDigit(char) || char === "$") {
|
|
277
|
+
const { value, nextIndex } = parseNumberToken(line, index);
|
|
278
|
+
tokens.push({ kind: "number", value });
|
|
279
|
+
index = nextIndex;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (isSymbolStart(char)) {
|
|
283
|
+
let symbol = char;
|
|
284
|
+
index += 1;
|
|
285
|
+
while (index < length && isSymbolPart(line[index] ?? "")) {
|
|
286
|
+
symbol += line[index];
|
|
287
|
+
index += 1;
|
|
288
|
+
}
|
|
289
|
+
tokens.push({ kind: "symbol", value: symbol });
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
const twoChar = line.slice(index, index + 2);
|
|
293
|
+
if (MULTI_CHAR_TOKENS.has(twoChar)) {
|
|
294
|
+
tokens.push({ kind: "punct", value: twoChar });
|
|
295
|
+
index += 2;
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (SINGLE_CHAR_TOKENS.has(char)) {
|
|
299
|
+
tokens.push({ kind: "punct", value: char });
|
|
300
|
+
index += 1;
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
throw new Error(`Unexpected character '${char}'`);
|
|
304
|
+
}
|
|
305
|
+
return tokens;
|
|
306
|
+
}
|
|
307
|
+
class TokenStream {
|
|
308
|
+
constructor(tokens) {
|
|
309
|
+
this.tokens = tokens;
|
|
310
|
+
this.index = 0;
|
|
311
|
+
}
|
|
312
|
+
peek(offset = 0) {
|
|
313
|
+
return this.tokens[this.index + offset];
|
|
314
|
+
}
|
|
315
|
+
consume() {
|
|
316
|
+
const token = this.tokens[this.index];
|
|
317
|
+
if (!token) {
|
|
318
|
+
throw new Error("Unexpected end of token stream");
|
|
319
|
+
}
|
|
320
|
+
this.index += 1;
|
|
321
|
+
return token;
|
|
322
|
+
}
|
|
323
|
+
consumeSymbol(errorMessage = "Expected a symbol token") {
|
|
324
|
+
const token = this.consume();
|
|
325
|
+
if (token.kind !== "symbol") {
|
|
326
|
+
throw new Error(errorMessage);
|
|
327
|
+
}
|
|
328
|
+
return token.value;
|
|
329
|
+
}
|
|
330
|
+
consumeString(errorMessage = "Expected a string literal") {
|
|
331
|
+
const token = this.consume();
|
|
332
|
+
if (token.kind !== "string") {
|
|
333
|
+
throw new Error(errorMessage);
|
|
334
|
+
}
|
|
335
|
+
return token.value;
|
|
336
|
+
}
|
|
337
|
+
consumePunct(expected, errorMessage) {
|
|
338
|
+
const token = this.consume();
|
|
339
|
+
if (token.kind !== "punct") {
|
|
340
|
+
throw new Error(errorMessage ?? "Expected punctuation token");
|
|
341
|
+
}
|
|
342
|
+
if (expected && token.value !== expected) {
|
|
343
|
+
throw new Error(errorMessage ?? `Expected '${expected}'`);
|
|
344
|
+
}
|
|
345
|
+
return token.value;
|
|
346
|
+
}
|
|
347
|
+
isAtEnd() {
|
|
348
|
+
return this.index >= this.tokens.length;
|
|
349
|
+
}
|
|
350
|
+
expectEnd() {
|
|
351
|
+
if (!this.isAtEnd()) {
|
|
352
|
+
throw new Error("Unexpected tokens at end of line");
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function translateEscape(char) {
|
|
357
|
+
switch (char) {
|
|
358
|
+
case "n":
|
|
359
|
+
return "\n";
|
|
360
|
+
case "r":
|
|
361
|
+
return "\r";
|
|
362
|
+
case "t":
|
|
363
|
+
return "\t";
|
|
364
|
+
case "\\":
|
|
365
|
+
return "\\";
|
|
366
|
+
case "\"":
|
|
367
|
+
return "\"";
|
|
368
|
+
case "'":
|
|
369
|
+
return "'";
|
|
370
|
+
default:
|
|
371
|
+
return char;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function isDigit(char) {
|
|
375
|
+
return char >= "0" && char <= "9";
|
|
376
|
+
}
|
|
377
|
+
function isHexDigit(char) {
|
|
378
|
+
return isDigit(char) || (char >= "a" && char <= "f") || (char >= "A" && char <= "F");
|
|
379
|
+
}
|
|
380
|
+
function isBinaryDigit(char) {
|
|
381
|
+
return char === "0" || char === "1";
|
|
382
|
+
}
|
|
383
|
+
function isSymbolStart(char) {
|
|
384
|
+
return ((char >= "a" && char <= "z") ||
|
|
385
|
+
(char >= "A" && char <= "Z") ||
|
|
386
|
+
char === "_" ||
|
|
387
|
+
char === ".");
|
|
388
|
+
}
|
|
389
|
+
function isSymbolPart(char) {
|
|
390
|
+
return isSymbolStart(char) || isDigit(char);
|
|
391
|
+
}
|
|
392
|
+
function parseNumberToken(line, startIndex) {
|
|
393
|
+
let index = startIndex;
|
|
394
|
+
let base = 10;
|
|
395
|
+
if (line[index] === "$") {
|
|
396
|
+
base = 16;
|
|
397
|
+
index += 1;
|
|
398
|
+
}
|
|
399
|
+
else if (line.startsWith("0x", index) || line.startsWith("0X", index)) {
|
|
400
|
+
base = 16;
|
|
401
|
+
index += 2;
|
|
402
|
+
}
|
|
403
|
+
else if (line.startsWith("0b", index) || line.startsWith("0B", index)) {
|
|
404
|
+
base = 2;
|
|
405
|
+
index += 2;
|
|
406
|
+
}
|
|
407
|
+
let value = 0;
|
|
408
|
+
let consumed = false;
|
|
409
|
+
while (index < line.length) {
|
|
410
|
+
const char = line[index] ?? "";
|
|
411
|
+
let digit = -1;
|
|
412
|
+
if (base === 16 && isHexDigit(char)) {
|
|
413
|
+
digit = parseInt(char, 16);
|
|
414
|
+
}
|
|
415
|
+
else if (base === 10 && isDigit(char)) {
|
|
416
|
+
digit = parseInt(char, 10);
|
|
417
|
+
}
|
|
418
|
+
else if (base === 2 && isBinaryDigit(char)) {
|
|
419
|
+
digit = parseInt(char, 2);
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
value = value * base + digit;
|
|
425
|
+
consumed = true;
|
|
426
|
+
index += 1;
|
|
427
|
+
}
|
|
428
|
+
if (!consumed) {
|
|
429
|
+
throw new Error("Malformed numeric literal");
|
|
430
|
+
}
|
|
431
|
+
return { value, nextIndex: index };
|
|
432
|
+
}
|
|
433
|
+
function parseExpression(stream, terminators) {
|
|
434
|
+
return parseBinaryExpression(stream, terminators, 1);
|
|
435
|
+
}
|
|
436
|
+
function parseBinaryExpression(stream, terminators, minPrecedence) {
|
|
437
|
+
let left = parseUnaryExpression(stream, terminators);
|
|
438
|
+
while (true) {
|
|
439
|
+
const next = stream.peek();
|
|
440
|
+
if (!next)
|
|
441
|
+
break;
|
|
442
|
+
if (next.kind === "punct" && terminators.has(next.value)) {
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
445
|
+
const op = asBinaryOperator(next);
|
|
446
|
+
if (!op) {
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
const precedence = BINARY_OPERATOR_PRECEDENCE[op];
|
|
450
|
+
if (precedence < minPrecedence) {
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
stream.consume();
|
|
454
|
+
const right = parseBinaryExpression(stream, terminators, precedence + 1);
|
|
455
|
+
left = { type: "binary", op, left, right };
|
|
456
|
+
}
|
|
457
|
+
return left;
|
|
458
|
+
}
|
|
459
|
+
function parseUnaryExpression(stream, terminators) {
|
|
460
|
+
const token = stream.consume();
|
|
461
|
+
if (!token) {
|
|
462
|
+
throw new Error("Unexpected end of expression");
|
|
463
|
+
}
|
|
464
|
+
if (token.kind === "punct" && (token.value === "+" || token.value === "-" || token.value === "!" || token.value === "~")) {
|
|
465
|
+
const operand = parseUnaryExpression(stream, terminators);
|
|
466
|
+
return { type: "unary", op: token.value, operand };
|
|
467
|
+
}
|
|
468
|
+
if (token.kind === "punct" && token.value === "(") {
|
|
469
|
+
const expr = parseExpression(stream, new Set([")"]));
|
|
470
|
+
const closing = stream.consume();
|
|
471
|
+
if (!closing || closing.kind !== "punct" || closing.value !== ")") {
|
|
472
|
+
throw new Error("Expected closing parenthesis");
|
|
473
|
+
}
|
|
474
|
+
return expr;
|
|
475
|
+
}
|
|
476
|
+
if (token.kind === "number") {
|
|
477
|
+
return { type: "number", value: token.value };
|
|
478
|
+
}
|
|
479
|
+
if (token.kind === "symbol") {
|
|
480
|
+
return { type: "symbol", name: token.value };
|
|
481
|
+
}
|
|
482
|
+
if (token.kind === "string") {
|
|
483
|
+
if (token.value.length !== 1) {
|
|
484
|
+
throw new Error("String constants in expressions must be exactly one character");
|
|
485
|
+
}
|
|
486
|
+
return { type: "number", value: token.value.charCodeAt(0) };
|
|
487
|
+
}
|
|
488
|
+
throw new Error("Unexpected token in expression");
|
|
489
|
+
}
|
|
490
|
+
function asBinaryOperator(token) {
|
|
491
|
+
if (!token || token.kind !== "punct")
|
|
492
|
+
return undefined;
|
|
493
|
+
if (["+", "-", "*", "/", "%", "<<", ">>", "&", "|", "^", "==", "!=", "<", "<=", ">", ">="].includes(token.value)) {
|
|
494
|
+
return token.value;
|
|
495
|
+
}
|
|
496
|
+
return undefined;
|
|
497
|
+
}
|
|
498
|
+
function parseOperand(stream) {
|
|
499
|
+
if (stream.isAtEnd()) {
|
|
500
|
+
return { kind: "none" };
|
|
501
|
+
}
|
|
502
|
+
const first = stream.peek();
|
|
503
|
+
if (!first)
|
|
504
|
+
return { kind: "none" };
|
|
505
|
+
if (first.kind === "symbol" && first.value.toUpperCase() === "A") {
|
|
506
|
+
stream.consume();
|
|
507
|
+
if (!stream.isAtEnd()) {
|
|
508
|
+
throw new Error("Unexpected tokens after accumulator operand");
|
|
509
|
+
}
|
|
510
|
+
return { kind: "accumulator" };
|
|
511
|
+
}
|
|
512
|
+
if (first.kind === "punct" && first.value === "#") {
|
|
513
|
+
stream.consumePunct("#");
|
|
514
|
+
const expr = parseExpression(stream, new Set());
|
|
515
|
+
return { kind: "immediate", expr };
|
|
516
|
+
}
|
|
517
|
+
if (first.kind === "punct" && first.value === "(") {
|
|
518
|
+
stream.consumePunct("(");
|
|
519
|
+
const expr = parseExpression(stream, new Set([")"]));
|
|
520
|
+
stream.consumePunct(")", "Expected ')' to close indirect operand");
|
|
521
|
+
if (stream.isAtEnd()) {
|
|
522
|
+
return { kind: "indirect", expr };
|
|
523
|
+
}
|
|
524
|
+
const next = stream.peek();
|
|
525
|
+
if (next && next.kind === "punct" && next.value === ",") {
|
|
526
|
+
stream.consumePunct(",");
|
|
527
|
+
const register = stream.consumeSymbol("Expected X or Y after comma").toUpperCase();
|
|
528
|
+
if (register === "X") {
|
|
529
|
+
return { kind: "indirect", expr, register: "X" };
|
|
530
|
+
}
|
|
531
|
+
if (register === "Y") {
|
|
532
|
+
return { kind: "indirect", expr, register: "Y" };
|
|
533
|
+
}
|
|
534
|
+
throw new Error("Only X or Y are allowed after indirect operand");
|
|
535
|
+
}
|
|
536
|
+
return { kind: "indirect", expr };
|
|
537
|
+
}
|
|
538
|
+
const expr = parseExpression(stream, new Set([","]));
|
|
539
|
+
if (stream.isAtEnd()) {
|
|
540
|
+
return { kind: "expression", expr };
|
|
541
|
+
}
|
|
542
|
+
const next = stream.peek();
|
|
543
|
+
if (next && next.kind === "punct" && next.value === ",") {
|
|
544
|
+
stream.consumePunct(",");
|
|
545
|
+
const register = stream.consumeSymbol("Expected X or Y after comma").toUpperCase();
|
|
546
|
+
if (register !== "X" && register !== "Y") {
|
|
547
|
+
throw new Error("Only X or Y are allowed after comma");
|
|
548
|
+
}
|
|
549
|
+
return { kind: "expression", expr, register };
|
|
550
|
+
}
|
|
551
|
+
throw new Error("Unexpected tokens after operand");
|
|
552
|
+
}
|
|
553
|
+
class SymbolTable {
|
|
554
|
+
constructor() {
|
|
555
|
+
this.globals = new Map();
|
|
556
|
+
this.locals = new Map();
|
|
557
|
+
}
|
|
558
|
+
reset() {
|
|
559
|
+
this.globals = new Map();
|
|
560
|
+
this.locals = new Map();
|
|
561
|
+
this.currentScope = undefined;
|
|
562
|
+
}
|
|
563
|
+
setLabel(label, value) {
|
|
564
|
+
if (label.startsWith(".")) {
|
|
565
|
+
if (!this.currentScope) {
|
|
566
|
+
throw new Error(`Local label "${label}" defined before any global label`);
|
|
567
|
+
}
|
|
568
|
+
const scoped = this.ensureLocalScope(this.currentScope);
|
|
569
|
+
scoped.set(label, value);
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
this.globals.set(label, value);
|
|
573
|
+
this.currentScope = label;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
ensureLabel(label, expected) {
|
|
577
|
+
const existing = this.get(label);
|
|
578
|
+
if (existing === undefined) {
|
|
579
|
+
throw new Error(`Symbol "${label}" was not defined in the first pass`);
|
|
580
|
+
}
|
|
581
|
+
if (existing !== expected) {
|
|
582
|
+
throw new Error(`Symbol "${label}" changed value between passes (expected ${formatHex(existing)}, got ${formatHex(expected)})`);
|
|
583
|
+
}
|
|
584
|
+
if (!label.startsWith(".")) {
|
|
585
|
+
this.currentScope = label;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
setAssignment(label, value) {
|
|
589
|
+
if (label.startsWith(".")) {
|
|
590
|
+
if (!this.currentScope) {
|
|
591
|
+
throw new Error(`Local constant "${label}" defined before any global label`);
|
|
592
|
+
}
|
|
593
|
+
const scoped = this.ensureLocalScope(this.currentScope);
|
|
594
|
+
scoped.set(label, value);
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
this.globals.set(label, value);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
get(label) {
|
|
601
|
+
if (label === "*") {
|
|
602
|
+
throw new Error("'*' should be resolved externally");
|
|
603
|
+
}
|
|
604
|
+
if (label.startsWith(".")) {
|
|
605
|
+
if (!this.currentScope)
|
|
606
|
+
return undefined;
|
|
607
|
+
return this.locals.get(this.currentScope)?.get(label);
|
|
608
|
+
}
|
|
609
|
+
return this.globals.get(label);
|
|
610
|
+
}
|
|
611
|
+
ensureLocalScope(scope) {
|
|
612
|
+
if (!this.locals.has(scope)) {
|
|
613
|
+
this.locals.set(scope, new Map());
|
|
614
|
+
}
|
|
615
|
+
return this.locals.get(scope);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
class Assembler {
|
|
619
|
+
constructor(lines, defaultLoadAddress) {
|
|
620
|
+
this.lines = lines;
|
|
621
|
+
this.symbolTable = new SymbolTable();
|
|
622
|
+
this.memory = new Uint8Array(0x10000);
|
|
623
|
+
this.written = new Uint8Array(0x10000);
|
|
624
|
+
this.pc = 0;
|
|
625
|
+
this.lowestWritten = MAX_ADDRESS;
|
|
626
|
+
this.highestWritten = 0;
|
|
627
|
+
this.loadAddress = defaultLoadAddress;
|
|
628
|
+
this.symbolTable.reset();
|
|
629
|
+
}
|
|
630
|
+
runPass(pass) {
|
|
631
|
+
this.pc = 0;
|
|
632
|
+
if (pass === 0) {
|
|
633
|
+
this.symbolTable.reset();
|
|
634
|
+
this.lowestWritten = MAX_ADDRESS;
|
|
635
|
+
this.highestWritten = 0;
|
|
636
|
+
}
|
|
637
|
+
for (const line of this.lines) {
|
|
638
|
+
try {
|
|
639
|
+
this.processLine(line, pass);
|
|
640
|
+
}
|
|
641
|
+
catch (error) {
|
|
642
|
+
if (error instanceof AssemblyError) {
|
|
643
|
+
throw error;
|
|
644
|
+
}
|
|
645
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
646
|
+
throw new AssemblyError(message, line.location);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
buildPrg() {
|
|
651
|
+
if (this.lowestWritten === MAX_ADDRESS || this.highestWritten < this.loadAddress) {
|
|
652
|
+
return Buffer.alloc(0);
|
|
653
|
+
}
|
|
654
|
+
const start = Math.min(this.lowestWritten, this.loadAddress);
|
|
655
|
+
const end = this.highestWritten;
|
|
656
|
+
const body = this.memory.subarray(start, end + 1);
|
|
657
|
+
const header = Buffer.alloc(2);
|
|
658
|
+
header.writeUInt16LE(start, 0);
|
|
659
|
+
return Buffer.concat([header, Buffer.from(body)]);
|
|
660
|
+
}
|
|
661
|
+
processLine(line, pass) {
|
|
662
|
+
for (const label of line.labels) {
|
|
663
|
+
if (pass === 0) {
|
|
664
|
+
this.symbolTable.setLabel(label, this.pc);
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
this.symbolTable.ensureLabel(label, this.pc);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if (!line.statement) {
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
switch (line.statement.kind) {
|
|
674
|
+
case "assignment":
|
|
675
|
+
this.handleAssignment(line.statement, pass, line.location);
|
|
676
|
+
break;
|
|
677
|
+
case "directive":
|
|
678
|
+
this.handleDirective(line.statement, pass, line.location);
|
|
679
|
+
break;
|
|
680
|
+
case "instruction":
|
|
681
|
+
this.handleInstruction(line.statement, pass, line.location);
|
|
682
|
+
break;
|
|
683
|
+
default:
|
|
684
|
+
throw new Error("Unknown statement kind");
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
handleAssignment(statement, pass, location) {
|
|
688
|
+
const value = this.evaluate(statement.expr, pass, location);
|
|
689
|
+
if (value === undefined) {
|
|
690
|
+
throw new AssemblyError("Assignment depends on an undefined symbol", location);
|
|
691
|
+
}
|
|
692
|
+
if (statement.isLocationCounter) {
|
|
693
|
+
this.pc = value & 0xffff;
|
|
694
|
+
if (pass === 0) {
|
|
695
|
+
this.loadAddress = this.pc;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
else if (pass === 0) {
|
|
699
|
+
this.symbolTable.setAssignment(statement.target, value);
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
this.symbolTable.ensureLabel(statement.target, value);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
handleDirective(statement, pass, location) {
|
|
706
|
+
switch (statement.name) {
|
|
707
|
+
case "org": {
|
|
708
|
+
if (statement.args.length !== 1 || statement.args[0]?.kind !== "expr") {
|
|
709
|
+
throw new AssemblyError("org directive expects a single expression", location);
|
|
710
|
+
}
|
|
711
|
+
const value = this.evaluate(statement.args[0]?.expr, pass, location);
|
|
712
|
+
if (value === undefined) {
|
|
713
|
+
throw new AssemblyError("org directive requires a defined value", location);
|
|
714
|
+
}
|
|
715
|
+
this.pc = value & 0xffff;
|
|
716
|
+
if (pass === 0) {
|
|
717
|
+
this.loadAddress = this.pc;
|
|
718
|
+
}
|
|
719
|
+
break;
|
|
720
|
+
}
|
|
721
|
+
case "byte": {
|
|
722
|
+
for (const arg of statement.args) {
|
|
723
|
+
if (arg.kind === "string") {
|
|
724
|
+
if (pass === 1) {
|
|
725
|
+
for (const char of arg.value) {
|
|
726
|
+
this.writeByte(char.charCodeAt(0), location);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
this.pc += arg.value.length;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
const value = this.evaluate(arg.expr, pass, location);
|
|
735
|
+
if (pass === 1) {
|
|
736
|
+
if (value === undefined) {
|
|
737
|
+
throw new AssemblyError("byte directive contains undefined expression", location);
|
|
738
|
+
}
|
|
739
|
+
this.writeByte(value, location);
|
|
740
|
+
}
|
|
741
|
+
else {
|
|
742
|
+
this.pc += 1;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
748
|
+
case "word": {
|
|
749
|
+
for (const arg of statement.args) {
|
|
750
|
+
if (arg.kind === "string") {
|
|
751
|
+
if (pass === 1) {
|
|
752
|
+
for (const char of arg.value) {
|
|
753
|
+
this.writeWord(char.charCodeAt(0), location);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
this.pc += arg.value.length * 2;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
const value = this.evaluate(arg.expr, pass, location);
|
|
762
|
+
if (value === undefined) {
|
|
763
|
+
if (pass === 1) {
|
|
764
|
+
throw new AssemblyError("word directive contains undefined expression", location);
|
|
765
|
+
}
|
|
766
|
+
this.pc += 2;
|
|
767
|
+
}
|
|
768
|
+
else if (pass === 1) {
|
|
769
|
+
this.writeWord(value, location);
|
|
770
|
+
}
|
|
771
|
+
else {
|
|
772
|
+
this.pc += 2;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
break;
|
|
777
|
+
}
|
|
778
|
+
case "reserve": {
|
|
779
|
+
if (statement.args.length !== 1 || statement.args[0]?.kind !== "expr") {
|
|
780
|
+
throw new AssemblyError("reserve directive expects a single expression", location);
|
|
781
|
+
}
|
|
782
|
+
const amount = this.evaluate(statement.args[0]?.expr, pass, location);
|
|
783
|
+
if (amount === undefined) {
|
|
784
|
+
throw new AssemblyError("reserve directive requires a defined value", location);
|
|
785
|
+
}
|
|
786
|
+
if (amount < 0) {
|
|
787
|
+
throw new AssemblyError("reserve directive requires a non-negative size", location);
|
|
788
|
+
}
|
|
789
|
+
if (pass === 1) {
|
|
790
|
+
for (let i = 0; i < amount; i += 1) {
|
|
791
|
+
this.writeByte(0, location);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
else {
|
|
795
|
+
this.pc += amount;
|
|
796
|
+
}
|
|
797
|
+
break;
|
|
798
|
+
}
|
|
799
|
+
case "include": {
|
|
800
|
+
// Include directives are expanded during parsing.
|
|
801
|
+
break;
|
|
802
|
+
}
|
|
803
|
+
default:
|
|
804
|
+
throw new AssemblyError(`Unsupported directive ${statement.name}`, location);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
handleInstruction(statement, pass, location) {
|
|
808
|
+
const info = determineInstruction(statement, pass, this.evaluate.bind(this), this.pc, location);
|
|
809
|
+
statement.resolvedMode = info.mode;
|
|
810
|
+
if (pass === 0) {
|
|
811
|
+
this.pc += info.size;
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
if (modeExpectsValue(info.mode) && info.value === undefined) {
|
|
815
|
+
throw new AssemblyError("Undefined expression", location);
|
|
816
|
+
}
|
|
817
|
+
const opcode = lookupOpcode(statement.mnemonic, info.mode, location);
|
|
818
|
+
this.writeByte(opcode, location);
|
|
819
|
+
switch (info.mode) {
|
|
820
|
+
case AddressingMode.Immediate:
|
|
821
|
+
case AddressingMode.ZeroPage:
|
|
822
|
+
case AddressingMode.ZeroPageX:
|
|
823
|
+
case AddressingMode.ZeroPageY:
|
|
824
|
+
case AddressingMode.IndirectX:
|
|
825
|
+
case AddressingMode.IndirectY:
|
|
826
|
+
this.writeByte(info.value ?? 0, location);
|
|
827
|
+
break;
|
|
828
|
+
case AddressingMode.Absolute:
|
|
829
|
+
case AddressingMode.AbsoluteX:
|
|
830
|
+
case AddressingMode.AbsoluteY:
|
|
831
|
+
case AddressingMode.Indirect:
|
|
832
|
+
this.writeWord(info.value ?? 0, location);
|
|
833
|
+
break;
|
|
834
|
+
case AddressingMode.Relative: {
|
|
835
|
+
const offset = computeRelativeOffset(this.pc - 1, info.value ?? 0, location);
|
|
836
|
+
this.writeByte(offset, location);
|
|
837
|
+
break;
|
|
838
|
+
}
|
|
839
|
+
case AddressingMode.Accumulator:
|
|
840
|
+
case AddressingMode.Implied:
|
|
841
|
+
break;
|
|
842
|
+
default:
|
|
843
|
+
throw new AssemblyError(`Unhandled addressing mode ${info.mode}`, location);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
evaluate(expr, pass, location) {
|
|
847
|
+
switch (expr.type) {
|
|
848
|
+
case "number":
|
|
849
|
+
return expr.value;
|
|
850
|
+
case "symbol":
|
|
851
|
+
if (expr.name === "*") {
|
|
852
|
+
return this.pc;
|
|
853
|
+
}
|
|
854
|
+
return this.symbolTable.get(expr.name);
|
|
855
|
+
case "unary": {
|
|
856
|
+
const value = this.evaluate(expr.operand, pass, location);
|
|
857
|
+
if (value === undefined)
|
|
858
|
+
return undefined;
|
|
859
|
+
switch (expr.op) {
|
|
860
|
+
case "+":
|
|
861
|
+
return value;
|
|
862
|
+
case "-":
|
|
863
|
+
return -value;
|
|
864
|
+
case "!":
|
|
865
|
+
return value ? 0 : 1;
|
|
866
|
+
case "~":
|
|
867
|
+
return ~value;
|
|
868
|
+
default:
|
|
869
|
+
throw new AssemblyError("Unknown unary operator", location);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
case "binary": {
|
|
873
|
+
const left = this.evaluate(expr.left, pass, location);
|
|
874
|
+
const right = this.evaluate(expr.right, pass, location);
|
|
875
|
+
if (left === undefined || right === undefined) {
|
|
876
|
+
return undefined;
|
|
877
|
+
}
|
|
878
|
+
switch (expr.op) {
|
|
879
|
+
case "+":
|
|
880
|
+
return left + right;
|
|
881
|
+
case "-":
|
|
882
|
+
return left - right;
|
|
883
|
+
case "*":
|
|
884
|
+
return left * right;
|
|
885
|
+
case "/":
|
|
886
|
+
if (right === 0)
|
|
887
|
+
throw new AssemblyError("Division by zero", location);
|
|
888
|
+
return Math.trunc(left / right);
|
|
889
|
+
case "%":
|
|
890
|
+
if (right === 0)
|
|
891
|
+
throw new AssemblyError("Modulo by zero", location);
|
|
892
|
+
return left % right;
|
|
893
|
+
case "<<":
|
|
894
|
+
return left << right;
|
|
895
|
+
case ">>":
|
|
896
|
+
return left >> right;
|
|
897
|
+
case "&":
|
|
898
|
+
return left & right;
|
|
899
|
+
case "|":
|
|
900
|
+
return left | right;
|
|
901
|
+
case "^":
|
|
902
|
+
return left ^ right;
|
|
903
|
+
case "==":
|
|
904
|
+
return left === right ? 1 : 0;
|
|
905
|
+
case "!=":
|
|
906
|
+
return left !== right ? 1 : 0;
|
|
907
|
+
case "<":
|
|
908
|
+
return left < right ? 1 : 0;
|
|
909
|
+
case "<=":
|
|
910
|
+
return left <= right ? 1 : 0;
|
|
911
|
+
case ">":
|
|
912
|
+
return left > right ? 1 : 0;
|
|
913
|
+
case ">=":
|
|
914
|
+
return left >= right ? 1 : 0;
|
|
915
|
+
default:
|
|
916
|
+
throw new AssemblyError("Unknown binary operator", location);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
default:
|
|
920
|
+
throw new AssemblyError("Invalid expression node", location);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
writeByte(value, location) {
|
|
924
|
+
const masked = value & 0xff;
|
|
925
|
+
this.memory[this.pc] = masked;
|
|
926
|
+
this.written[this.pc] = 1;
|
|
927
|
+
this.lowestWritten = Math.min(this.lowestWritten, this.pc);
|
|
928
|
+
this.highestWritten = Math.max(this.highestWritten, this.pc);
|
|
929
|
+
this.pc = (this.pc + 1) & 0xffff;
|
|
930
|
+
}
|
|
931
|
+
writeWord(value, location) {
|
|
932
|
+
const masked = value & 0xffff;
|
|
933
|
+
this.writeByte(masked & 0xff, location);
|
|
934
|
+
this.writeByte((masked >> 8) & 0xff, location);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
function determineInstruction(statement, pass, evaluator, locationCounter, location) {
|
|
938
|
+
if (statement.resolvedMode && pass === 1) {
|
|
939
|
+
const value = statement.operand.kind === "none" ? undefined : resolveOperandValue(statement.operand, evaluator, pass, location);
|
|
940
|
+
const size = instructionSize(statement.resolvedMode);
|
|
941
|
+
return {
|
|
942
|
+
mode: statement.resolvedMode,
|
|
943
|
+
size,
|
|
944
|
+
value: normalizeOperandValue(statement.resolvedMode, value, location),
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
const value = resolveOperandValue(statement.operand, evaluator, pass, location);
|
|
948
|
+
const mode = chooseAddressingMode(statement.mnemonic, statement.operand, value, pass, location);
|
|
949
|
+
const size = instructionSize(mode);
|
|
950
|
+
statement.resolvedMode = statement.resolvedMode ?? mode;
|
|
951
|
+
return {
|
|
952
|
+
mode,
|
|
953
|
+
size,
|
|
954
|
+
value: normalizeOperandValue(mode, value, locationCounter, location),
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
function resolveOperandValue(operand, evaluator, pass, location) {
|
|
958
|
+
switch (operand.kind) {
|
|
959
|
+
case "none":
|
|
960
|
+
case "accumulator":
|
|
961
|
+
return undefined;
|
|
962
|
+
case "immediate":
|
|
963
|
+
case "expression":
|
|
964
|
+
case "indirect":
|
|
965
|
+
return evaluator(operand.expr, pass, location);
|
|
966
|
+
default:
|
|
967
|
+
return undefined;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
function chooseAddressingMode(mnemonic, operand, value, pass, location) {
|
|
971
|
+
const available = INSTRUCTION_TABLE.get(mnemonic);
|
|
972
|
+
if (!available) {
|
|
973
|
+
throw new AssemblyError(`Unknown instruction "${mnemonic}"`, location);
|
|
974
|
+
}
|
|
975
|
+
switch (operand.kind) {
|
|
976
|
+
case "none":
|
|
977
|
+
if (available.has(AddressingMode.Implied))
|
|
978
|
+
return AddressingMode.Implied;
|
|
979
|
+
if (available.has(AddressingMode.Accumulator))
|
|
980
|
+
return AddressingMode.Accumulator;
|
|
981
|
+
throw new AssemblyError(`Instruction ${mnemonic} requires an operand`, location);
|
|
982
|
+
case "accumulator":
|
|
983
|
+
if (!available.has(AddressingMode.Accumulator)) {
|
|
984
|
+
throw new AssemblyError(`Instruction ${mnemonic} does not support accumulator addressing`, location);
|
|
985
|
+
}
|
|
986
|
+
return AddressingMode.Accumulator;
|
|
987
|
+
case "immediate":
|
|
988
|
+
if (!available.has(AddressingMode.Immediate)) {
|
|
989
|
+
throw new AssemblyError(`Instruction ${mnemonic} does not support immediate addressing`, location);
|
|
990
|
+
}
|
|
991
|
+
return AddressingMode.Immediate;
|
|
992
|
+
case "indirect": {
|
|
993
|
+
if (operand.register === "X") {
|
|
994
|
+
if (!available.has(AddressingMode.IndirectX)) {
|
|
995
|
+
throw new AssemblyError(`Instruction ${mnemonic} does not support (expr,X) addressing`, location);
|
|
996
|
+
}
|
|
997
|
+
return AddressingMode.IndirectX;
|
|
998
|
+
}
|
|
999
|
+
if (operand.register === "Y") {
|
|
1000
|
+
if (!available.has(AddressingMode.IndirectY)) {
|
|
1001
|
+
throw new AssemblyError(`Instruction ${mnemonic} does not support (expr),Y addressing`, location);
|
|
1002
|
+
}
|
|
1003
|
+
return AddressingMode.IndirectY;
|
|
1004
|
+
}
|
|
1005
|
+
if (!available.has(AddressingMode.Indirect)) {
|
|
1006
|
+
throw new AssemblyError(`Instruction ${mnemonic} does not support (expr) addressing`, location);
|
|
1007
|
+
}
|
|
1008
|
+
return AddressingMode.Indirect;
|
|
1009
|
+
}
|
|
1010
|
+
case "expression": {
|
|
1011
|
+
const register = operand.register;
|
|
1012
|
+
if (RELATIVE_ONLY.has(mnemonic)) {
|
|
1013
|
+
if (!available.has(AddressingMode.Relative)) {
|
|
1014
|
+
throw new AssemblyError(`Instruction ${mnemonic} does not support relative addressing`, location);
|
|
1015
|
+
}
|
|
1016
|
+
return AddressingMode.Relative;
|
|
1017
|
+
}
|
|
1018
|
+
if (register === "X") {
|
|
1019
|
+
if (available.has(AddressingMode.ZeroPageX) && value !== undefined && fitsByte(value)) {
|
|
1020
|
+
return AddressingMode.ZeroPageX;
|
|
1021
|
+
}
|
|
1022
|
+
if (available.has(AddressingMode.AbsoluteX)) {
|
|
1023
|
+
return AddressingMode.AbsoluteX;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
else if (register === "Y") {
|
|
1027
|
+
if (available.has(AddressingMode.ZeroPageY) && value !== undefined && fitsByte(value)) {
|
|
1028
|
+
return AddressingMode.ZeroPageY;
|
|
1029
|
+
}
|
|
1030
|
+
if (available.has(AddressingMode.AbsoluteY)) {
|
|
1031
|
+
return AddressingMode.AbsoluteY;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
else {
|
|
1035
|
+
if (available.has(AddressingMode.ZeroPage) && value !== undefined && fitsByte(value)) {
|
|
1036
|
+
return AddressingMode.ZeroPage;
|
|
1037
|
+
}
|
|
1038
|
+
if (available.has(AddressingMode.Absolute)) {
|
|
1039
|
+
return AddressingMode.Absolute;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
if (available.has(AddressingMode.Absolute)) {
|
|
1043
|
+
return AddressingMode.Absolute;
|
|
1044
|
+
}
|
|
1045
|
+
throw new AssemblyError(`Instruction ${mnemonic} cannot resolve addressing mode`, location);
|
|
1046
|
+
}
|
|
1047
|
+
default:
|
|
1048
|
+
throw new AssemblyError("Unsupported operand kind", location);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
function instructionSize(mode) {
|
|
1052
|
+
switch (mode) {
|
|
1053
|
+
case AddressingMode.Implied:
|
|
1054
|
+
case AddressingMode.Accumulator:
|
|
1055
|
+
return 1;
|
|
1056
|
+
case AddressingMode.Immediate:
|
|
1057
|
+
case AddressingMode.ZeroPage:
|
|
1058
|
+
case AddressingMode.ZeroPageX:
|
|
1059
|
+
case AddressingMode.ZeroPageY:
|
|
1060
|
+
case AddressingMode.IndirectX:
|
|
1061
|
+
case AddressingMode.IndirectY:
|
|
1062
|
+
case AddressingMode.Relative:
|
|
1063
|
+
return 2;
|
|
1064
|
+
case AddressingMode.Absolute:
|
|
1065
|
+
case AddressingMode.AbsoluteX:
|
|
1066
|
+
case AddressingMode.AbsoluteY:
|
|
1067
|
+
case AddressingMode.Indirect:
|
|
1068
|
+
return 3;
|
|
1069
|
+
default:
|
|
1070
|
+
return 0;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
function modeExpectsValue(mode) {
|
|
1074
|
+
switch (mode) {
|
|
1075
|
+
case AddressingMode.Implied:
|
|
1076
|
+
case AddressingMode.Accumulator:
|
|
1077
|
+
return false;
|
|
1078
|
+
default:
|
|
1079
|
+
return true;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
function normalizeOperandValue(mode, value, locationCounterOrLocation, locationMaybe) {
|
|
1083
|
+
const location = locationMaybe ?? locationCounterOrLocation;
|
|
1084
|
+
switch (mode) {
|
|
1085
|
+
case AddressingMode.Immediate:
|
|
1086
|
+
case AddressingMode.ZeroPage:
|
|
1087
|
+
case AddressingMode.ZeroPageX:
|
|
1088
|
+
case AddressingMode.ZeroPageY:
|
|
1089
|
+
case AddressingMode.IndirectX:
|
|
1090
|
+
case AddressingMode.IndirectY:
|
|
1091
|
+
if (value === undefined)
|
|
1092
|
+
return undefined;
|
|
1093
|
+
return value & 0xff;
|
|
1094
|
+
case AddressingMode.Relative:
|
|
1095
|
+
return value;
|
|
1096
|
+
case AddressingMode.Absolute:
|
|
1097
|
+
case AddressingMode.AbsoluteX:
|
|
1098
|
+
case AddressingMode.AbsoluteY:
|
|
1099
|
+
case AddressingMode.Indirect:
|
|
1100
|
+
if (value === undefined)
|
|
1101
|
+
return undefined;
|
|
1102
|
+
return value & 0xffff;
|
|
1103
|
+
case AddressingMode.Implied:
|
|
1104
|
+
case AddressingMode.Accumulator:
|
|
1105
|
+
return undefined;
|
|
1106
|
+
default:
|
|
1107
|
+
throw new AssemblyError(`Unsupported addressing mode ${mode}`, location);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
function computeRelativeOffset(currentPc, target, location) {
|
|
1111
|
+
const from = (currentPc + 2) & 0xffff;
|
|
1112
|
+
const delta = target - from;
|
|
1113
|
+
if (delta < -128 || delta > 127) {
|
|
1114
|
+
throw new AssemblyError(`Branch target out of range (offset ${delta})`, location);
|
|
1115
|
+
}
|
|
1116
|
+
return delta & 0xff;
|
|
1117
|
+
}
|
|
1118
|
+
function lookupOpcode(mnemonic, mode, location) {
|
|
1119
|
+
const info = INSTRUCTION_TABLE.get(mnemonic);
|
|
1120
|
+
if (!info) {
|
|
1121
|
+
throw new AssemblyError(`Unknown instruction "${mnemonic}"`, location);
|
|
1122
|
+
}
|
|
1123
|
+
const opcode = info.get(mode);
|
|
1124
|
+
if (opcode === undefined) {
|
|
1125
|
+
throw new AssemblyError(`Instruction ${mnemonic} does not support ${mode} addressing`, location);
|
|
1126
|
+
}
|
|
1127
|
+
return opcode;
|
|
1128
|
+
}
|
|
1129
|
+
function fitsByte(value) {
|
|
1130
|
+
return value >= 0 && value <= 0xff;
|
|
1131
|
+
}
|
|
1132
|
+
function formatHex(value) {
|
|
1133
|
+
return `$${value.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
1134
|
+
}
|
|
1135
|
+
function buildInstructionTable() {
|
|
1136
|
+
const table = new Map();
|
|
1137
|
+
for (const entry of INSTRUCTION_DATA) {
|
|
1138
|
+
const mnemonic = entry.mnemonic;
|
|
1139
|
+
if (!table.has(mnemonic)) {
|
|
1140
|
+
table.set(mnemonic, new Map());
|
|
1141
|
+
}
|
|
1142
|
+
table.get(mnemonic).set(entry.mode, entry.opcode);
|
|
1143
|
+
}
|
|
1144
|
+
return table;
|
|
1145
|
+
}
|
|
1146
|
+
const INSTRUCTION_DATA = [
|
|
1147
|
+
{ mnemonic: "ADC", mode: AddressingMode.Immediate, opcode: 0x69 },
|
|
1148
|
+
{ mnemonic: "ADC", mode: AddressingMode.ZeroPage, opcode: 0x65 },
|
|
1149
|
+
{ mnemonic: "ADC", mode: AddressingMode.ZeroPageX, opcode: 0x75 },
|
|
1150
|
+
{ mnemonic: "ADC", mode: AddressingMode.Absolute, opcode: 0x6d },
|
|
1151
|
+
{ mnemonic: "ADC", mode: AddressingMode.AbsoluteX, opcode: 0x7d },
|
|
1152
|
+
{ mnemonic: "ADC", mode: AddressingMode.AbsoluteY, opcode: 0x79 },
|
|
1153
|
+
{ mnemonic: "ADC", mode: AddressingMode.IndirectX, opcode: 0x61 },
|
|
1154
|
+
{ mnemonic: "ADC", mode: AddressingMode.IndirectY, opcode: 0x71 },
|
|
1155
|
+
{ mnemonic: "AND", mode: AddressingMode.Immediate, opcode: 0x29 },
|
|
1156
|
+
{ mnemonic: "AND", mode: AddressingMode.ZeroPage, opcode: 0x25 },
|
|
1157
|
+
{ mnemonic: "AND", mode: AddressingMode.ZeroPageX, opcode: 0x35 },
|
|
1158
|
+
{ mnemonic: "AND", mode: AddressingMode.Absolute, opcode: 0x2d },
|
|
1159
|
+
{ mnemonic: "AND", mode: AddressingMode.AbsoluteX, opcode: 0x3d },
|
|
1160
|
+
{ mnemonic: "AND", mode: AddressingMode.AbsoluteY, opcode: 0x39 },
|
|
1161
|
+
{ mnemonic: "AND", mode: AddressingMode.IndirectX, opcode: 0x21 },
|
|
1162
|
+
{ mnemonic: "AND", mode: AddressingMode.IndirectY, opcode: 0x31 },
|
|
1163
|
+
{ mnemonic: "ASL", mode: AddressingMode.Accumulator, opcode: 0x0a },
|
|
1164
|
+
{ mnemonic: "ASL", mode: AddressingMode.ZeroPage, opcode: 0x06 },
|
|
1165
|
+
{ mnemonic: "ASL", mode: AddressingMode.ZeroPageX, opcode: 0x16 },
|
|
1166
|
+
{ mnemonic: "ASL", mode: AddressingMode.Absolute, opcode: 0x0e },
|
|
1167
|
+
{ mnemonic: "ASL", mode: AddressingMode.AbsoluteX, opcode: 0x1e },
|
|
1168
|
+
{ mnemonic: "BIT", mode: AddressingMode.ZeroPage, opcode: 0x24 },
|
|
1169
|
+
{ mnemonic: "BIT", mode: AddressingMode.Absolute, opcode: 0x2c },
|
|
1170
|
+
{ mnemonic: "BPL", mode: AddressingMode.Relative, opcode: 0x10 },
|
|
1171
|
+
{ mnemonic: "BMI", mode: AddressingMode.Relative, opcode: 0x30 },
|
|
1172
|
+
{ mnemonic: "BVC", mode: AddressingMode.Relative, opcode: 0x50 },
|
|
1173
|
+
{ mnemonic: "BVS", mode: AddressingMode.Relative, opcode: 0x70 },
|
|
1174
|
+
{ mnemonic: "BCC", mode: AddressingMode.Relative, opcode: 0x90 },
|
|
1175
|
+
{ mnemonic: "BCS", mode: AddressingMode.Relative, opcode: 0xb0 },
|
|
1176
|
+
{ mnemonic: "BNE", mode: AddressingMode.Relative, opcode: 0xd0 },
|
|
1177
|
+
{ mnemonic: "BEQ", mode: AddressingMode.Relative, opcode: 0xf0 },
|
|
1178
|
+
{ mnemonic: "BRK", mode: AddressingMode.Implied, opcode: 0x00 },
|
|
1179
|
+
{ mnemonic: "CLC", mode: AddressingMode.Implied, opcode: 0x18 },
|
|
1180
|
+
{ mnemonic: "CLD", mode: AddressingMode.Implied, opcode: 0xd8 },
|
|
1181
|
+
{ mnemonic: "CLI", mode: AddressingMode.Implied, opcode: 0x58 },
|
|
1182
|
+
{ mnemonic: "CLV", mode: AddressingMode.Implied, opcode: 0xb8 },
|
|
1183
|
+
{ mnemonic: "CMP", mode: AddressingMode.Immediate, opcode: 0xc9 },
|
|
1184
|
+
{ mnemonic: "CMP", mode: AddressingMode.ZeroPage, opcode: 0xc5 },
|
|
1185
|
+
{ mnemonic: "CMP", mode: AddressingMode.ZeroPageX, opcode: 0xd5 },
|
|
1186
|
+
{ mnemonic: "CMP", mode: AddressingMode.Absolute, opcode: 0xcd },
|
|
1187
|
+
{ mnemonic: "CMP", mode: AddressingMode.AbsoluteX, opcode: 0xdd },
|
|
1188
|
+
{ mnemonic: "CMP", mode: AddressingMode.AbsoluteY, opcode: 0xd9 },
|
|
1189
|
+
{ mnemonic: "CMP", mode: AddressingMode.IndirectX, opcode: 0xc1 },
|
|
1190
|
+
{ mnemonic: "CMP", mode: AddressingMode.IndirectY, opcode: 0xd1 },
|
|
1191
|
+
{ mnemonic: "CPX", mode: AddressingMode.Immediate, opcode: 0xe0 },
|
|
1192
|
+
{ mnemonic: "CPX", mode: AddressingMode.ZeroPage, opcode: 0xe4 },
|
|
1193
|
+
{ mnemonic: "CPX", mode: AddressingMode.Absolute, opcode: 0xec },
|
|
1194
|
+
{ mnemonic: "CPY", mode: AddressingMode.Immediate, opcode: 0xc0 },
|
|
1195
|
+
{ mnemonic: "CPY", mode: AddressingMode.ZeroPage, opcode: 0xc4 },
|
|
1196
|
+
{ mnemonic: "CPY", mode: AddressingMode.Absolute, opcode: 0xcc },
|
|
1197
|
+
{ mnemonic: "DEC", mode: AddressingMode.ZeroPage, opcode: 0xc6 },
|
|
1198
|
+
{ mnemonic: "DEC", mode: AddressingMode.ZeroPageX, opcode: 0xd6 },
|
|
1199
|
+
{ mnemonic: "DEC", mode: AddressingMode.Absolute, opcode: 0xce },
|
|
1200
|
+
{ mnemonic: "DEC", mode: AddressingMode.AbsoluteX, opcode: 0xde },
|
|
1201
|
+
{ mnemonic: "DEX", mode: AddressingMode.Implied, opcode: 0xca },
|
|
1202
|
+
{ mnemonic: "DEY", mode: AddressingMode.Implied, opcode: 0x88 },
|
|
1203
|
+
{ mnemonic: "EOR", mode: AddressingMode.Immediate, opcode: 0x49 },
|
|
1204
|
+
{ mnemonic: "EOR", mode: AddressingMode.ZeroPage, opcode: 0x45 },
|
|
1205
|
+
{ mnemonic: "EOR", mode: AddressingMode.ZeroPageX, opcode: 0x55 },
|
|
1206
|
+
{ mnemonic: "EOR", mode: AddressingMode.Absolute, opcode: 0x4d },
|
|
1207
|
+
{ mnemonic: "EOR", mode: AddressingMode.AbsoluteX, opcode: 0x5d },
|
|
1208
|
+
{ mnemonic: "EOR", mode: AddressingMode.AbsoluteY, opcode: 0x59 },
|
|
1209
|
+
{ mnemonic: "EOR", mode: AddressingMode.IndirectX, opcode: 0x41 },
|
|
1210
|
+
{ mnemonic: "EOR", mode: AddressingMode.IndirectY, opcode: 0x51 },
|
|
1211
|
+
{ mnemonic: "INC", mode: AddressingMode.ZeroPage, opcode: 0xe6 },
|
|
1212
|
+
{ mnemonic: "INC", mode: AddressingMode.ZeroPageX, opcode: 0xf6 },
|
|
1213
|
+
{ mnemonic: "INC", mode: AddressingMode.Absolute, opcode: 0xee },
|
|
1214
|
+
{ mnemonic: "INC", mode: AddressingMode.AbsoluteX, opcode: 0xfe },
|
|
1215
|
+
{ mnemonic: "INX", mode: AddressingMode.Implied, opcode: 0xe8 },
|
|
1216
|
+
{ mnemonic: "INY", mode: AddressingMode.Implied, opcode: 0xc8 },
|
|
1217
|
+
{ mnemonic: "JMP", mode: AddressingMode.Absolute, opcode: 0x4c },
|
|
1218
|
+
{ mnemonic: "JMP", mode: AddressingMode.Indirect, opcode: 0x6c },
|
|
1219
|
+
{ mnemonic: "JSR", mode: AddressingMode.Absolute, opcode: 0x20 },
|
|
1220
|
+
{ mnemonic: "LDA", mode: AddressingMode.Immediate, opcode: 0xa9 },
|
|
1221
|
+
{ mnemonic: "LDA", mode: AddressingMode.ZeroPage, opcode: 0xa5 },
|
|
1222
|
+
{ mnemonic: "LDA", mode: AddressingMode.ZeroPageX, opcode: 0xb5 },
|
|
1223
|
+
{ mnemonic: "LDA", mode: AddressingMode.Absolute, opcode: 0xad },
|
|
1224
|
+
{ mnemonic: "LDA", mode: AddressingMode.AbsoluteX, opcode: 0xbd },
|
|
1225
|
+
{ mnemonic: "LDA", mode: AddressingMode.AbsoluteY, opcode: 0xb9 },
|
|
1226
|
+
{ mnemonic: "LDA", mode: AddressingMode.IndirectX, opcode: 0xa1 },
|
|
1227
|
+
{ mnemonic: "LDA", mode: AddressingMode.IndirectY, opcode: 0xb1 },
|
|
1228
|
+
{ mnemonic: "LDX", mode: AddressingMode.Immediate, opcode: 0xa2 },
|
|
1229
|
+
{ mnemonic: "LDX", mode: AddressingMode.ZeroPage, opcode: 0xa6 },
|
|
1230
|
+
{ mnemonic: "LDX", mode: AddressingMode.ZeroPageY, opcode: 0xb6 },
|
|
1231
|
+
{ mnemonic: "LDX", mode: AddressingMode.Absolute, opcode: 0xae },
|
|
1232
|
+
{ mnemonic: "LDX", mode: AddressingMode.AbsoluteY, opcode: 0xbe },
|
|
1233
|
+
{ mnemonic: "LDY", mode: AddressingMode.Immediate, opcode: 0xa0 },
|
|
1234
|
+
{ mnemonic: "LDY", mode: AddressingMode.ZeroPage, opcode: 0xa4 },
|
|
1235
|
+
{ mnemonic: "LDY", mode: AddressingMode.ZeroPageX, opcode: 0xb4 },
|
|
1236
|
+
{ mnemonic: "LDY", mode: AddressingMode.Absolute, opcode: 0xac },
|
|
1237
|
+
{ mnemonic: "LDY", mode: AddressingMode.AbsoluteX, opcode: 0xbc },
|
|
1238
|
+
{ mnemonic: "LSR", mode: AddressingMode.Accumulator, opcode: 0x4a },
|
|
1239
|
+
{ mnemonic: "LSR", mode: AddressingMode.ZeroPage, opcode: 0x46 },
|
|
1240
|
+
{ mnemonic: "LSR", mode: AddressingMode.ZeroPageX, opcode: 0x56 },
|
|
1241
|
+
{ mnemonic: "LSR", mode: AddressingMode.Absolute, opcode: 0x4e },
|
|
1242
|
+
{ mnemonic: "LSR", mode: AddressingMode.AbsoluteX, opcode: 0x5e },
|
|
1243
|
+
{ mnemonic: "NOP", mode: AddressingMode.Implied, opcode: 0xea },
|
|
1244
|
+
{ mnemonic: "ORA", mode: AddressingMode.Immediate, opcode: 0x09 },
|
|
1245
|
+
{ mnemonic: "ORA", mode: AddressingMode.ZeroPage, opcode: 0x05 },
|
|
1246
|
+
{ mnemonic: "ORA", mode: AddressingMode.ZeroPageX, opcode: 0x15 },
|
|
1247
|
+
{ mnemonic: "ORA", mode: AddressingMode.Absolute, opcode: 0x0d },
|
|
1248
|
+
{ mnemonic: "ORA", mode: AddressingMode.AbsoluteX, opcode: 0x1d },
|
|
1249
|
+
{ mnemonic: "ORA", mode: AddressingMode.AbsoluteY, opcode: 0x19 },
|
|
1250
|
+
{ mnemonic: "ORA", mode: AddressingMode.IndirectX, opcode: 0x01 },
|
|
1251
|
+
{ mnemonic: "ORA", mode: AddressingMode.IndirectY, opcode: 0x11 },
|
|
1252
|
+
{ mnemonic: "PHA", mode: AddressingMode.Implied, opcode: 0x48 },
|
|
1253
|
+
{ mnemonic: "PHP", mode: AddressingMode.Implied, opcode: 0x08 },
|
|
1254
|
+
{ mnemonic: "PLA", mode: AddressingMode.Implied, opcode: 0x68 },
|
|
1255
|
+
{ mnemonic: "PLP", mode: AddressingMode.Implied, opcode: 0x28 },
|
|
1256
|
+
{ mnemonic: "ROL", mode: AddressingMode.Accumulator, opcode: 0x2a },
|
|
1257
|
+
{ mnemonic: "ROL", mode: AddressingMode.ZeroPage, opcode: 0x26 },
|
|
1258
|
+
{ mnemonic: "ROL", mode: AddressingMode.ZeroPageX, opcode: 0x36 },
|
|
1259
|
+
{ mnemonic: "ROL", mode: AddressingMode.Absolute, opcode: 0x2e },
|
|
1260
|
+
{ mnemonic: "ROL", mode: AddressingMode.AbsoluteX, opcode: 0x3e },
|
|
1261
|
+
{ mnemonic: "ROR", mode: AddressingMode.Accumulator, opcode: 0x6a },
|
|
1262
|
+
{ mnemonic: "ROR", mode: AddressingMode.ZeroPage, opcode: 0x66 },
|
|
1263
|
+
{ mnemonic: "ROR", mode: AddressingMode.ZeroPageX, opcode: 0x76 },
|
|
1264
|
+
{ mnemonic: "ROR", mode: AddressingMode.Absolute, opcode: 0x6e },
|
|
1265
|
+
{ mnemonic: "ROR", mode: AddressingMode.AbsoluteX, opcode: 0x7e },
|
|
1266
|
+
{ mnemonic: "RTI", mode: AddressingMode.Implied, opcode: 0x40 },
|
|
1267
|
+
{ mnemonic: "RTS", mode: AddressingMode.Implied, opcode: 0x60 },
|
|
1268
|
+
{ mnemonic: "SBC", mode: AddressingMode.Immediate, opcode: 0xe9 },
|
|
1269
|
+
{ mnemonic: "SBC", mode: AddressingMode.ZeroPage, opcode: 0xe5 },
|
|
1270
|
+
{ mnemonic: "SBC", mode: AddressingMode.ZeroPageX, opcode: 0xf5 },
|
|
1271
|
+
{ mnemonic: "SBC", mode: AddressingMode.Absolute, opcode: 0xed },
|
|
1272
|
+
{ mnemonic: "SBC", mode: AddressingMode.AbsoluteX, opcode: 0xfd },
|
|
1273
|
+
{ mnemonic: "SBC", mode: AddressingMode.AbsoluteY, opcode: 0xf9 },
|
|
1274
|
+
{ mnemonic: "SBC", mode: AddressingMode.IndirectX, opcode: 0xe1 },
|
|
1275
|
+
{ mnemonic: "SBC", mode: AddressingMode.IndirectY, opcode: 0xf1 },
|
|
1276
|
+
{ mnemonic: "SEC", mode: AddressingMode.Implied, opcode: 0x38 },
|
|
1277
|
+
{ mnemonic: "SED", mode: AddressingMode.Implied, opcode: 0xf8 },
|
|
1278
|
+
{ mnemonic: "SEI", mode: AddressingMode.Implied, opcode: 0x78 },
|
|
1279
|
+
{ mnemonic: "STA", mode: AddressingMode.ZeroPage, opcode: 0x85 },
|
|
1280
|
+
{ mnemonic: "STA", mode: AddressingMode.ZeroPageX, opcode: 0x95 },
|
|
1281
|
+
{ mnemonic: "STA", mode: AddressingMode.Absolute, opcode: 0x8d },
|
|
1282
|
+
{ mnemonic: "STA", mode: AddressingMode.AbsoluteX, opcode: 0x9d },
|
|
1283
|
+
{ mnemonic: "STA", mode: AddressingMode.AbsoluteY, opcode: 0x99 },
|
|
1284
|
+
{ mnemonic: "STA", mode: AddressingMode.IndirectX, opcode: 0x81 },
|
|
1285
|
+
{ mnemonic: "STA", mode: AddressingMode.IndirectY, opcode: 0x91 },
|
|
1286
|
+
{ mnemonic: "STX", mode: AddressingMode.ZeroPage, opcode: 0x86 },
|
|
1287
|
+
{ mnemonic: "STX", mode: AddressingMode.ZeroPageY, opcode: 0x96 },
|
|
1288
|
+
{ mnemonic: "STX", mode: AddressingMode.Absolute, opcode: 0x8e },
|
|
1289
|
+
{ mnemonic: "STY", mode: AddressingMode.ZeroPage, opcode: 0x84 },
|
|
1290
|
+
{ mnemonic: "STY", mode: AddressingMode.ZeroPageX, opcode: 0x94 },
|
|
1291
|
+
{ mnemonic: "STY", mode: AddressingMode.Absolute, opcode: 0x8c },
|
|
1292
|
+
{ mnemonic: "TAX", mode: AddressingMode.Implied, opcode: 0xaa },
|
|
1293
|
+
{ mnemonic: "TAY", mode: AddressingMode.Implied, opcode: 0xa8 },
|
|
1294
|
+
{ mnemonic: "TSX", mode: AddressingMode.Implied, opcode: 0xba },
|
|
1295
|
+
{ mnemonic: "TXA", mode: AddressingMode.Implied, opcode: 0x8a },
|
|
1296
|
+
{ mnemonic: "TXS", mode: AddressingMode.Implied, opcode: 0x9a },
|
|
1297
|
+
{ mnemonic: "TYA", mode: AddressingMode.Implied, opcode: 0x98 },
|
|
1298
|
+
];
|
|
1299
|
+
const INSTRUCTION_TABLE = buildInstructionTable();
|