@moonbase-machines/c64 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +11 -2
- package/basic-compile.js +171 -0
- package/basic-lang.js +394 -0
- package/basic-list.js +21 -0
- package/basic-rom.js +572 -0
- package/basic.js +275 -0
- package/browser.js +116 -0
- package/d64.js +191 -0
- package/dist/moonbase-c64.js +9 -0
- package/dist/moonbase-c64.js.map +7 -0
- package/drive.js +209 -0
- package/editor.js +267 -0
- package/float.js +584 -0
- package/glyph-hash.js +8 -0
- package/glyph-hashes.js +70 -0
- package/index.js +27 -0
- package/kernal.js +319 -0
- package/keys.js +21 -0
- package/machine.js +209 -0
- package/numbers.js +64 -0
- package/package.json +14 -4
- package/paint.js +23 -0
- package/petscii.js +54 -0
- package/printer.js +95 -0
- package/rom.js +537 -0
- package/saves.js +10 -0
- package/sid.js +49 -0
- package/start.js +33 -0
- package/test/disks.test.js +35 -0
- package/vic.js +122 -0
package/basic-rom.js
ADDED
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
// BASIC's ROM, moonbase's own: the routines of Commodore BASIC that a program's machine code calls,
|
|
2
|
+
// answered at their addresses, as the ROM's would leave the registers and memory. Shared by every
|
|
3
|
+
// layer that runs such code: the original on moonbase's ROMs (rom.js, on the 6510) and a remake
|
|
4
|
+
// (on its stand-in for the processor), as kernal.js is for the KERNAL. Its rules for numbers are
|
|
5
|
+
// numbers.js's, its arithmetic float.js's.
|
|
6
|
+
//
|
|
7
|
+
// install({ cpu, read, write, evaluate, chrout })
|
|
8
|
+
// cpu a processor, or a stand-in for one: its registers, and `hooks`, which this fills
|
|
9
|
+
// read, write memory as the processor sees it
|
|
10
|
+
// evaluate the expression in BASIC's text at an address: { value, end, place }, its value (a
|
|
11
|
+
// number or a string), where it ends, and for a string written in the text, where
|
|
12
|
+
// its characters are. The remake takes its compiled parameters' (compiled with
|
|
13
|
+
// the program, basic-compile.js); the original reads the text and BASIC's
|
|
14
|
+
// variables in memory.
|
|
15
|
+
// chrout CHROUT, a character in A to the output (the KERNAL's $FFD2 unless given)
|
|
16
|
+
//
|
|
17
|
+
// A routine that returns from its caller too (`'return'`: $E206, at a statement's end) leaves
|
|
18
|
+
// that to the layer running it.
|
|
19
|
+
import { BasicError, ERRORS } from './numbers.js';
|
|
20
|
+
import { setFac, Floats, FloatError, unpack } from './float.js';
|
|
21
|
+
import { Basic } from './basic.js';
|
|
22
|
+
import { expression } from './basic-lang.js';
|
|
23
|
+
import { evaluator } from './basic-compile.js';
|
|
24
|
+
import { seedRandom, nextRandom } from './numbers.js';
|
|
25
|
+
|
|
26
|
+
const hex4 = n => n.toString(16).toUpperCase().padStart(4, '0');
|
|
27
|
+
const FAC_EXP = 0x61;
|
|
28
|
+
// BASIC's functions' routines, SGN to MID$, as its table at $A052 holds them
|
|
29
|
+
const FUNCTIONS = [0xbc39, 0xbccc, 0xbc58, 0x0310, 0xb37d, 0xb39e, 0xbf71, 0xe097, 0xb9ea, 0xbfed, 0xe264, 0xe26b, 0xe2b4, 0xe30e,
|
|
30
|
+
0xb80d, 0xb77c, 0xb465, 0xb7ad, 0xb78b, 0xb6ec, 0xb700, 0xb72c, 0xb737];
|
|
31
|
+
|
|
32
|
+
export function install({ cpu, read, write, evaluate, chrout = c => cpu.hooks.get(0xffd2)(c) }) {
|
|
33
|
+
// BASIC's text pointer ($7A), and what it reads there: CHRGOT ($0079) and CHRGET ($0073) as
|
|
34
|
+
// BASIC's text pointer reads, spaces passed over, Z at a statement's end, C clear for a digit
|
|
35
|
+
const txt = () => read(0x7a) | read(0x7b) << 8, setTxt = a => { write(0x7a, a & 0xff); write(0x7b, a >> 8 & 0xff); };
|
|
36
|
+
const syntax = () => { throw new BasicError('?SYNTAX ERROR'); };
|
|
37
|
+
const chrgot = c => { let a = txt(); while (read(a) === 0x20) a++; setTxt(a); const v = read(a); c.a = v; c.z = v === 0 || v === 0x3a ? 1 : 0; c.c = v >= 0x30 && v <= 0x39 ? 0 : 1; c.n = v >> 7; };
|
|
38
|
+
const chkcom = c => { chrgot(c); if (c.a !== 0x2c) syntax(); setTxt(txt() + 1); chrgot(c); };
|
|
39
|
+
// the expression at the text pointer: a number into the accumulator ($0D 0, numeric), a string
|
|
40
|
+
// as BASIC's descriptor at $19 ($0D $FF, string)
|
|
41
|
+
let last = 0;
|
|
42
|
+
const frm = (c, number) => {
|
|
43
|
+
chrgot(c);
|
|
44
|
+
const at = txt(), { value, end, place } = evaluate(at);
|
|
45
|
+
if (typeof value === 'string') {
|
|
46
|
+
if (number) throw new BasicError('?TYPE MISMATCH ERROR');
|
|
47
|
+
if (place === undefined) throw new Error('a string parameter not in the text, at $' + hex4(at) + ': not handled');
|
|
48
|
+
write(0x19, value.length); write(0x1a, place & 0xff); write(0x1b, place >> 8); write(0x64, 0x19); write(0x65, 0); write(0x0d, 0xff);
|
|
49
|
+
} else { last = value; write(0x0d, 0); setFac({ read, write }, value); }
|
|
50
|
+
setTxt(end); chrgot(c);
|
|
51
|
+
};
|
|
52
|
+
const whole = (lo, hi) => { if (last !== Math.floor(last) || last < lo || last > hi) throw new BasicError('?ILLEGAL QUANTITY ERROR'); return last; };
|
|
53
|
+
const getbyt = c => { frm(c, true); c.x = whole(0, 255); };
|
|
54
|
+
// (A left as the last character out, as the ROM's leave it)
|
|
55
|
+
const out = ch => { cpu.a = ch; chrout(cpu); };
|
|
56
|
+
const H = {
|
|
57
|
+
0x79: chrgot, 0x73: c => { setTxt(txt() + 1); chrgot(c); },
|
|
58
|
+
// CHKCOM ($AEFD), FRMNUM ($AD8A), FRMEVL ($AD9E), GETADR ($B7F7), GETBYT ($B79E); FRESTR's below ($B6A3)
|
|
59
|
+
0xaefd: chkcom, 0xad8a: c => frm(c, true), 0xad9e: c => frm(c, false),
|
|
60
|
+
0xb7f7: c => { const x = whole(0, 65535); write(0x14, x & 0xff); write(0x15, x >> 8); c.y = x & 0xff; c.a = x >> 8; },
|
|
61
|
+
0xb79e: getbyt,
|
|
62
|
+
// the LOAD and OPEN parameters': $E200 a comma and a byte, $E206 the statement's end, which
|
|
63
|
+
// returns from its caller, $E20E a comma and more
|
|
64
|
+
0xe200: c => { chkcom(c); getbyt(c); },
|
|
65
|
+
0xe206: c => { chrgot(c); if (c.z) return 'return'; },
|
|
66
|
+
0xe20e: c => { chkcom(c); if (c.z) syntax(); },
|
|
67
|
+
// STROUT ($AB1E), the text at A/Y to its 0; the screen cleared ($E544) and a line of it ($E9FF)
|
|
68
|
+
0xab1e: c => { for (let a = c.a | c.y << 8, k = 0; read(a + k) && k < 256; k++) out(read(a + k)); },
|
|
69
|
+
0xe544: () => out(0x93),
|
|
70
|
+
0xe9ff: c => { const at = (read(0x288) << 8) + c.x * 40; for (let k = 0; k < 40; k++) { write(at + k, 0x20); write(0xd800 + c.x * 40 + k, read(0x286)); } },
|
|
71
|
+
};
|
|
72
|
+
// ---- the routines Blitz!'s runtime calls (Goldfields' GAME), on the original only ----
|
|
73
|
+
|
|
74
|
+
// ($B194) where an array's elements begin, its header ($5F) and two bytes for each of its
|
|
75
|
+
// dimensions ($0B) past it: $58/$59, and A/Y
|
|
76
|
+
H[0xb194] = c => {
|
|
77
|
+
let t = read(0x0b) << 1, carry = t >> 8; t &= 0xff;
|
|
78
|
+
t += 5 + carry; carry = t >> 8; t &= 0xff;
|
|
79
|
+
t += read(0x5f) + carry; carry = t >> 8; t &= 0xff;
|
|
80
|
+
let y = read(0x60); if (carry) y = (y + 1) & 0xff;
|
|
81
|
+
write(0x58, t); write(0x59, y); c.a = t; c.y = y; c.c = carry;
|
|
82
|
+
const flag = carry ? y : t; c.z = flag ? 0 : 1; c.n = flag >> 7;
|
|
83
|
+
};
|
|
84
|
+
// REASON ($A408): whether the address in A/Y is below the strings' bottom (FRETOP, $33), the
|
|
85
|
+
// flags as the comparison leaves them (C clear); if not, the strings collected (GARBAG, $B526)
|
|
86
|
+
// and the comparison again, its registers kept across it; and if still not, OUT OF MEMORY
|
|
87
|
+
const below = (a, y) => { const top = read(0x34); if (y !== top) return y < top; return a < read(0x33); };
|
|
88
|
+
const compare = (c, a, y) => { const top = read(0x34), v = y !== top ? y - top : a - read(0x33); c.c = v >= 0 ? 1 : 0; c.z = (v & 0xff) ? 0 : 1; c.n = v >> 7 & 1; };
|
|
89
|
+
H[0xa408] = c => {
|
|
90
|
+
const { a, y } = c;
|
|
91
|
+
if (below(a, y)) return compare(c, a, y);
|
|
92
|
+
const kept = []; for (let k = 0x57; k <= 0x60; k++) kept.push(read(k));
|
|
93
|
+
H[0xb526](c);
|
|
94
|
+
kept.forEach((v, k) => write(0x57 + k, v));
|
|
95
|
+
Object.assign(c, { a, y });
|
|
96
|
+
if (below(a, y)) return compare(c, a, y);
|
|
97
|
+
throw new BasicError('?OUT OF MEMORY ERROR');
|
|
98
|
+
};
|
|
99
|
+
// UMULT ($B34C): an array's index ($71/$72) times its dimension's size, read from its header at
|
|
100
|
+
// ($5F)+Y (the low byte) and Y-1 (the high), into X/Y (A the high byte too); past 16 bits is OUT
|
|
101
|
+
// OF MEMORY. $71/$72 are shifted out to 0, $5D counted down to 0, as the ROM leaves them.
|
|
102
|
+
H[0xb34c] = c => {
|
|
103
|
+
write(0x22, c.y);
|
|
104
|
+
const base = read(0x5f) | read(0x60) << 8, size = read(base + c.y) | read(base + ((c.y - 1) & 0xff)) << 8;
|
|
105
|
+
write(0x28, size & 0xff); write(0x29, size >> 8);
|
|
106
|
+
const index = read(0x71) | read(0x72) << 8, product = index * size;
|
|
107
|
+
// (the ROM's own test: a shift or a sum carrying out of 16 bits on the way, which for a
|
|
108
|
+
// product past 16 bits it always meets)
|
|
109
|
+
if (product > 0xffff) throw new BasicError('?OUT OF MEMORY ERROR');
|
|
110
|
+
write(0x71, 0); write(0x72, 0); write(0x5d, 0);
|
|
111
|
+
Object.assign(c, { x: product & 0xff, y: product >> 8, a: product >> 8, c: 0, z: 1, n: 0 });
|
|
112
|
+
};
|
|
113
|
+
// ($B2AA) DIM's end: the array's elements, A/X bytes of them (high, low) from $58/$59, past the
|
|
114
|
+
// end of memory OUT OF MEMORY; then REASON for its end, the arrays' end (STREND, $31) there,
|
|
115
|
+
// its elements cleared to 0 (a page at a time down from the last, as the ROM does), and its
|
|
116
|
+
// length into its header, at ($5F)+2. DIM's flag ($0C) set, back to DIM; an array made by its
|
|
117
|
+
// first use, which goes on to find its element, is not handled (an error).
|
|
118
|
+
H[0xb2aa] = c => {
|
|
119
|
+
const start = read(0x58) | read(0x59) << 8;
|
|
120
|
+
let hi = c.a + read(0x59) + c.c;
|
|
121
|
+
if (hi > 255) throw new BasicError('?OUT OF MEMORY ERROR');
|
|
122
|
+
write(0x59, hi);
|
|
123
|
+
let lo = c.x + read(0x58);
|
|
124
|
+
if (lo > 255) { lo &= 0xff; hi = (hi + 1) & 0xff; if (!hi) throw new BasicError('?OUT OF MEMORY ERROR'); }
|
|
125
|
+
const kept = { a: c.a, x: c.x };
|
|
126
|
+
Object.assign(c, { a: lo, y: hi }); H[0xa408](c);
|
|
127
|
+
write(0x31, lo); write(0x32, hi);
|
|
128
|
+
const end = lo | hi << 8;
|
|
129
|
+
for (let a = start; a < end; a++) write(a, 0);
|
|
130
|
+
write(0x59, start >> 8); write(0x72, 0);
|
|
131
|
+
const base = read(0x5f) | read(0x60) << 8, length = end - base;
|
|
132
|
+
write(base + 2, length & 0xff); write(base + 3, length >> 8);
|
|
133
|
+
if (!read(0x0c)) throw new Error('an array made by its first use ($B2AA, DIM\'s flag clear): not handled');
|
|
134
|
+
const f = read(0x0c);
|
|
135
|
+
Object.assign(c, { a: f, x: kept.x, y: 3, c: 1, z: f ? 0 : 1, n: f >> 7 });
|
|
136
|
+
};
|
|
137
|
+
// SYS's own ($E130): a return pushed to $E147, the registers from $030C to $030F (A, X, Y, the
|
|
138
|
+
// flags), and on to the address at $14/$15 (GETADR's), where the machine code runs; and $E147, as
|
|
139
|
+
// it comes back (or as a routine ends that jumps there), the registers kept at $030C again
|
|
140
|
+
H[0xe130] = c => {
|
|
141
|
+
c.push(0xe1); c.push(0x46);
|
|
142
|
+
Object.assign(c, { a: read(0x30c), x: read(0x30d), y: read(0x30e) }); c.p = read(0x30f);
|
|
143
|
+
c.pc = read(0x14) | read(0x15) << 8;
|
|
144
|
+
return false;
|
|
145
|
+
};
|
|
146
|
+
H[0xe147] = c => { write(0x30c, c.a); write(0x30d, c.x); write(0x30e, c.y); write(0x30f, c.p); };
|
|
147
|
+
// The accumulator onto the processor's stack, for the evaluator ($AE38): the caller's return
|
|
148
|
+
// taken into $22/$23, its low byte one on (only the low byte: the ROM's INC carries no further),
|
|
149
|
+
// A pushed (the sign), then ($AE43) the accumulator rounded and its five bytes pushed, the
|
|
150
|
+
// exponent last, and on to the caller at ($22)
|
|
151
|
+
const f = new Floats({ read, write });
|
|
152
|
+
// (a routine called from the ROM's code at `from`, its JSR's return address on the stack for the
|
|
153
|
+
// while, as an observer sees the ROM's own calls)
|
|
154
|
+
const call = (c, at, from) => { const r = from + 2; c.push(r >> 8); c.push(r & 0xff); try { return cpu.hooks.get(at)(c); } finally { c.pull(); c.pull(); } };
|
|
155
|
+
// (the floating point's errors, an overflow or a division by zero, as BASIC's)
|
|
156
|
+
const arith = g => { try { return g(); } catch (err) { if (err instanceof FloatError) throw new BasicError('?' + ERRORS[err.x - 1] + ' ERROR'); throw err; } };
|
|
157
|
+
H[0xae38] = c => {
|
|
158
|
+
const y = c.a, lo = c.pull(), hi = c.pull();
|
|
159
|
+
write(0x22, (lo + 1) & 0xff); write(0x23, hi); c.y = y; c.push(y);
|
|
160
|
+
return H[0xae43](c);
|
|
161
|
+
};
|
|
162
|
+
H[0xae43] = c => {
|
|
163
|
+
f.round();
|
|
164
|
+
for (const at of [0x65, 0x64, 0x63, 0x62, 0x61]) c.push(read(at));
|
|
165
|
+
c.a = read(0x61); c.z = c.a ? 0 : 1; c.n = c.a >> 7;
|
|
166
|
+
c.pc = read(0x22) | read(0x23) << 8;
|
|
167
|
+
return false;
|
|
168
|
+
};
|
|
169
|
+
// The floating point's entries (float.js, each held to the ROM's): the address of a
|
|
170
|
+
// number in memory in A/Y (X/Y for a store, FORPNT $49 for BBD0's, $5C and $57 for BBC7's and
|
|
171
|
+
// BBCA's); A the result's exponent, or the comparison's or the sign's answer, its flags N and Z.
|
|
172
|
+
// An overflow or a division by zero is BASIC's error.
|
|
173
|
+
const ay = c => c.a | c.y << 8, nz = (c, v) => { c.z = v & 0xff ? 0 : 1; c.n = v >> 7 & 1; };
|
|
174
|
+
const floating = (run, regs = () => ({})) => c => {
|
|
175
|
+
const r = arith(() => run(c));
|
|
176
|
+
Object.assign(c, regs(c, r) ?? {});
|
|
177
|
+
nz(c, c.a);
|
|
178
|
+
};
|
|
179
|
+
const A = (c, r) => ({ a: r?.a ?? read(0x61) });
|
|
180
|
+
Object.assign(H, {
|
|
181
|
+
0xbba2: floating(c => f.movfm(ay(c)), (c, r) => ({ a: r.a, y: 0 })),
|
|
182
|
+
0xbbd4: floating(c => f.movmf(c.x | c.y << 8), (c, r) => ({ a: r.a, y: 0 })),
|
|
183
|
+
0xbbd0: floating(c => { c.x = read(0x49); c.y = read(0x4a); return f.movmf(c.x | c.y << 8); }, (c, r) => ({ a: r.a, y: 0 })),
|
|
184
|
+
0xbbc7: floating(c => { c.x = 0x5c; return f.movmf(0x5c); }, (c, r) => ({ a: r.a, y: 0 })),
|
|
185
|
+
0xbbca: floating(c => { c.x = 0x57; return f.movmf(0x57); }, (c, r) => ({ a: r.a, y: 0 })),
|
|
186
|
+
0xbbfc: floating(() => f.movfa(), () => ({ a: read(0x69), x: 0 })),
|
|
187
|
+
0xbc0c: floating(() => f.movaf(), () => ({ a: read(0x61), x: 0 })),
|
|
188
|
+
0xbc0f: floating(() => f.movef(), () => ({ a: read(0x61), x: 0 })),
|
|
189
|
+
0xbc1b: floating(() => f.round(), () => ({ a: read(0x61) })),
|
|
190
|
+
0xbc2b: floating(() => ({ a: f.sign() }), (c, r) => ({ a: r.a, c: read(0x66) >> 7 })),
|
|
191
|
+
0xbc5b: floating(c => f.compare(ay(c)), (c, r) => r),
|
|
192
|
+
0xbc5d: floating(c => f.compare(read(0x24) | c.y << 8), (c, r) => r),
|
|
193
|
+
0xb867: floating(c => f.fadd(ay(c)), A), 0xb86a: floating(() => f.faddt(), A),
|
|
194
|
+
0xb850: floating(c => f.fsub(ay(c)), A), 0xb853: floating(() => f.fsubt(), A),
|
|
195
|
+
0xba28: floating(c => f.fmult(ay(c)), A), 0xba2b: floating(() => f.fmultt(), A),
|
|
196
|
+
0xbb0f: floating(c => f.fdiv(ay(c)), A), 0xbb12: floating(() => f.fdivt(), A),
|
|
197
|
+
0xbccc: floating(() => f.int(), A), 0xbc9b: floating(() => f.qint(), () => ({ y: 0 })),
|
|
198
|
+
// ($BCDB) a 32-bit whole number in $62 to $65 as a number, negative when A's top bit is set,
|
|
199
|
+
// as INT enters it and Blitz! does
|
|
200
|
+
0xbcdb: floating(c => f.float32(c.a), A),
|
|
201
|
+
// the functions on the series: LOG, EXP, the power (entered past its test of the FAC, at $BF7B,
|
|
202
|
+
// its Z flag that test's), SQR (a half's power), SIN and COS
|
|
203
|
+
0xb9ea: floating(() => f.log(), A), 0xbfed: floating(c => f.exp(c.c), A),
|
|
204
|
+
0xbf7b: floating(c => f.fpwrt(c.z, c.c), A), 0xbf71: floating(c => { f.movaf(); f.movfm(0xbf11); return f.fpwrt(0, c.c); }, A),
|
|
205
|
+
0xe26b: floating(() => f.sin(), A), 0xe264: floating(() => f.cos(), A),
|
|
206
|
+
});
|
|
207
|
+
// RND ($E097): the next of the seed's numbers (numbers.js, the remake's own) into the
|
|
208
|
+
// accumulator, whatever RND's argument; A its exponent, so that the original and the remake draw
|
|
209
|
+
// the same numbers.
|
|
210
|
+
let randomState = null;
|
|
211
|
+
H[0xe097] = c => {
|
|
212
|
+
randomState = nextRandom(randomState ?? seedRandom(0x11));
|
|
213
|
+
setFac({ read, write }, randomState / 2 ** 32);
|
|
214
|
+
c.a = read(0x61); nz(c, c.a);
|
|
215
|
+
};
|
|
216
|
+
// ($BF04) FOUT's end, which Blitz! calls to end a number's text: A at $00FF+Y, a 0 after it, and
|
|
217
|
+
// the text's address, $0100, in A/Y
|
|
218
|
+
H[0xbf04] = c => { write(0xff + c.y, c.a); write(0x100 + c.y, 0); Object.assign(c, { a: 0, y: 1, z: 0, n: 0 }); };
|
|
219
|
+
// FIN ($BCF3): the number in BASIC's text at its pointer, read with CHRGET, into the FAC, A its
|
|
220
|
+
// first character and C clear for a digit, as CHRGOT leaves them. As the ROM builds it (each
|
|
221
|
+
// step float.js's): $5D to $67 cleared, the FAC with them; a sign; each digit the FAC ten
|
|
222
|
+
// times and the digit added, those after the point counted ($5D); a second point the end; an E,
|
|
223
|
+
// its sign ($60) and its digits ($5E), capped at 100 when negative and an overflow past 9 when
|
|
224
|
+
// not; then ten times or a tenth for each power left, and the sign last.
|
|
225
|
+
H[0xbcf3] = c => {
|
|
226
|
+
for (let x = 0x0a; x >= 0; x--) write(0x5d + x, 0);
|
|
227
|
+
const next = () => H[0x73](c), digit = () => !c.c;
|
|
228
|
+
if (!digit()) {
|
|
229
|
+
if (c.a === 0x2d) { write(0x67, 0xff); next(); }
|
|
230
|
+
else if (c.a === 0x2b) next();
|
|
231
|
+
}
|
|
232
|
+
let power;
|
|
233
|
+
for (;;) {
|
|
234
|
+
if (digit()) {
|
|
235
|
+
const ch = c.a;
|
|
236
|
+
if (read(0x5f) & 0x80) write(0x5d, read(0x5d) + 1);
|
|
237
|
+
arith(() => { f.mul10(); f.addDigit(ch - 0x30); });
|
|
238
|
+
next(); continue;
|
|
239
|
+
}
|
|
240
|
+
if (c.a === 0x2e) {
|
|
241
|
+
write(0x5f, (read(0x5f) >> 1) | 0x80);
|
|
242
|
+
if (read(0x5f) & 0x40) { power = read(0x5e); break; }
|
|
243
|
+
next(); continue;
|
|
244
|
+
}
|
|
245
|
+
if (c.a === 0x45) {
|
|
246
|
+
next();
|
|
247
|
+
if (c.c && (c.a === 0xab || c.a === 0x2d)) { write(0x60, (read(0x60) >> 1) | 0x80); next(); }
|
|
248
|
+
else if (c.c && (c.a === 0xaa || c.a === 0x2b)) next();
|
|
249
|
+
while (digit()) {
|
|
250
|
+
const e = read(0x5e);
|
|
251
|
+
if (e >= 10) { if (!(read(0x60) & 0x80)) throw new BasicError('?OVERFLOW ERROR'); write(0x5e, 100); }
|
|
252
|
+
else write(0x5e, e * 10 + c.a - 0x30);
|
|
253
|
+
next();
|
|
254
|
+
}
|
|
255
|
+
power = read(0x60) & 0x80 ? (0 - read(0x5e)) & 0xff : read(0x5e);
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
power = read(0x5e); break;
|
|
259
|
+
}
|
|
260
|
+
let n = (power - read(0x5d)) & 0xff; write(0x5e, n);
|
|
261
|
+
if (n && n < 0x80) for (; n; n = (n - 1) & 0xff) { arith(() => f.mul10()); write(0x5e, n - 1); }
|
|
262
|
+
else if (n) for (; n; n = (n + 1) & 0xff) { arith(() => f.div10()); write(0x5e, (n + 1) & 0xff); }
|
|
263
|
+
if (read(0x67) & 0x80) f.negop();
|
|
264
|
+
c.a = read(read(0x67) & 0x80 ? 0x66 : 0x67); nz(c, c.a);
|
|
265
|
+
};
|
|
266
|
+
// TI ($AF7B): the clock read (RDTIM, $FFDE: A, X, Y its low, middle and high bytes) as a 24-bit
|
|
267
|
+
// whole number in the FAC ($62 0, $63 to $65 the clock), exponent $A0, its low bits and sign 0,
|
|
268
|
+
// then normalised; as the ROM goes on through $B8D2, a carry clear on entry has it negated first
|
|
269
|
+
// ($AF84) the clock (RDTIM) into the FAC's mantissa, $63 to $65, $62 0; Y 0
|
|
270
|
+
const clock = c => { call(c, 0xffde, 0xaf84); write(0x64, c.x); write(0x63, c.y); write(0x65, c.a); write(0x62, 0); c.y = 0; };
|
|
271
|
+
H[0xaf7b] = c => {
|
|
272
|
+
const carry = c.c;
|
|
273
|
+
clock(c);
|
|
274
|
+
write(FAC_EXP, 0xa0); write(0x70, 0); write(0x66, 0);
|
|
275
|
+
if (!carry) f.negate();
|
|
276
|
+
const r = f.normal(); Object.assign(c, { a: r.a, y: 0 }); nz(c, c.a);
|
|
277
|
+
};
|
|
278
|
+
// WAIT's loop ($B83C, X the bits to flip, $49 the mask, $14 the address): at $B840, the address
|
|
279
|
+
// read until its bits, flipped, meet the mask; the machine goes on meanwhile, as the ROM's loop
|
|
280
|
+
// does
|
|
281
|
+
H[0xb83c] = c => { write(0x4a, c.x); c.y = 0; c.pc = 0xb840; return false; };
|
|
282
|
+
H[0xb840] = c => {
|
|
283
|
+
const v = (read((read(0x14) | read(0x15) << 8) + c.y) ^ read(0x4a)) & read(0x49);
|
|
284
|
+
if (!v) return 'wait';
|
|
285
|
+
c.a = v; nz(c, v);
|
|
286
|
+
};
|
|
287
|
+
// FREFAC ($B6A6): the string whose descriptor is at $64/$65, its length in A, its characters'
|
|
288
|
+
// address in $22/$23 (X/Y); its descriptor let go if it is the last temporary one (FRETMS,
|
|
289
|
+
// $B6DB: $16 and $17 three back), and its characters too if they are the strings' bottom
|
|
290
|
+
// (FRETOP, $33, moved up past them)
|
|
291
|
+
H[0xb6a6] = c => {
|
|
292
|
+
const at = read(0x64) | read(0x65) << 8;
|
|
293
|
+
write(0x22, at & 0xff); write(0x23, at >> 8);
|
|
294
|
+
const freed = H[0xb6db]({ a: at & 0xff, y: at >> 8 });
|
|
295
|
+
const len = read(at), x = read(at + 1), y = read(at + 2);
|
|
296
|
+
if (freed && y === read(0x34) && x === read(0x33)) { const top = read(0x33) + len; write(0x33, top & 0xff); if (top > 255) write(0x34, read(0x34) + 1); }
|
|
297
|
+
write(0x22, x); write(0x23, y);
|
|
298
|
+
Object.assign(c, { a: len, x, y }); nz(c, len);
|
|
299
|
+
};
|
|
300
|
+
H[0xb6db] = c => {
|
|
301
|
+
if (c.y !== read(0x18) || c.a !== read(0x17)) { c.z = 0; return false; }
|
|
302
|
+
write(0x16, c.a); write(0x17, (c.a - 3) & 0xff); c.y = 0; c.z = 1;
|
|
303
|
+
return true;
|
|
304
|
+
};
|
|
305
|
+
// ($AFD5) a function's call, the evaluator's: A its token times two, its routine's address from
|
|
306
|
+
// BASIC's table ($A052) into JMPER's ($55/$56) and called there; then CHKNUM ($AD8D), its
|
|
307
|
+
// result a number or TYPE MISMATCH
|
|
308
|
+
H[0xafd5] = c => {
|
|
309
|
+
const to = FUNCTIONS[(c.a - 0x68) >> 1];
|
|
310
|
+
if (to === undefined || c.a & 1) throw new Error('a function at $' + hex4(0x9fea + c.a) + ' ($AFD5): not handled');
|
|
311
|
+
write(0x55, to & 0xff); write(0x56, to >> 8);
|
|
312
|
+
c.y = c.a; c.push(0xaf); c.push(0xe2); c.pc = to;
|
|
313
|
+
return false;
|
|
314
|
+
};
|
|
315
|
+
H[0xafe3] = c => { const t = read(0x0d); if (t & 0x80) throw new BasicError('?TYPE MISMATCH ERROR'); c.n = 1 & t >> 7; c.v = 1 & t >> 6; c.z = c.a & t ? 0 : 1; };
|
|
316
|
+
// VAL's own ($B7B0), past LEN's start ($B782: A the string's length, $22/$23 its characters):
|
|
317
|
+
// none, the FAC 0 ($B8F7); else the text pointer kept at $71/$72 and moved to the string, a 0
|
|
318
|
+
// put past its end ($24/$25) for the while, and FIN; then the byte and the pointer back
|
|
319
|
+
H[0xb7b0] = c => {
|
|
320
|
+
if (!c.a) { write(FAC_EXP, 0); write(0x66, 0); c.a = 0; c.z = 1; c.n = 0; return; }
|
|
321
|
+
const from = read(0x22) | read(0x23) << 8, end = from + c.a;
|
|
322
|
+
write(0x71, read(0x7a)); write(0x72, read(0x7b));
|
|
323
|
+
setTxt(from); write(0x24, end & 0xff); write(0x25, end >> 8 & 0xff);
|
|
324
|
+
const kept = read(end); write(end, 0);
|
|
325
|
+
H[0x79](c); H[0xbcf3](c);
|
|
326
|
+
write(end, kept);
|
|
327
|
+
Object.assign(c, { a: kept, x: read(0x71), y: read(0x72) }); nz(c, c.y);
|
|
328
|
+
setTxt(c.x | c.y << 8);
|
|
329
|
+
};
|
|
330
|
+
// TI$ set ($A9E0, the string evaluated): FREFAC, six characters or ILLEGAL QUANTITY; the FAC 0,
|
|
331
|
+
// then for each pair a character added ($AA1D), ten times, the next added and six times (twice
|
|
332
|
+
// over, then $BAED, unless 0): the time in jiffies over ten, ten times again, QINT, and the
|
|
333
|
+
// clock set (SETTIM, $FFDB) from $65, $64 and $63. A character is a digit for the ROM if it is
|
|
334
|
+
// "0" or more, CHRGOT entered past its colon test ($0080); a space goes on to CHRGET itself.
|
|
335
|
+
H[0xa9e0] = c => {
|
|
336
|
+
H[0xb6a6](c);
|
|
337
|
+
if (c.a !== 6) throw new BasicError('?ILLEGAL QUANTITY ERROR');
|
|
338
|
+
write(FAC_EXP, 0); write(0x66, 0);
|
|
339
|
+
const character = y => {
|
|
340
|
+
c.a = read((read(0x22) | read(0x23) << 8) + y);
|
|
341
|
+
if (c.a === 0x20) H[0x73](c); else c.c = c.a >= 0x30 ? 0 : 1;
|
|
342
|
+
if (c.c) throw new BasicError('?ILLEGAL QUANTITY ERROR');
|
|
343
|
+
arith(() => f.addDigit((c.a - 0x30) & 0xff));
|
|
344
|
+
};
|
|
345
|
+
for (let y = 0; y < 6; y += 2) {
|
|
346
|
+
write(0x71, y); character(y); arith(() => f.mul10());
|
|
347
|
+
write(0x71, y + 1); character(y + 1);
|
|
348
|
+
const { a } = f.movaf(); if (a) arith(() => f.addDoubled(a + 1));
|
|
349
|
+
}
|
|
350
|
+
arith(() => f.mul10()); f.qint();
|
|
351
|
+
Object.assign(c, { x: read(0x64), y: read(0x63), a: read(0x65) });
|
|
352
|
+
return cpu.hooks.get(0xffdb)(c);
|
|
353
|
+
};
|
|
354
|
+
// FOUT's digits ($BE68, Y the table's place, $BF16 on): the 32-bit whole number in $62 to $65
|
|
355
|
+
// counted down (or up) by each power until it passes 0, the count a digit, to $00FF on at $71+1
|
|
356
|
+
// (a point after the $5D'th); to Y $24, or $3C (TI$'s). Then ($BEC4) the zeros at the end let go,
|
|
357
|
+
// and a point there, an exponent from $5E, and a 0 after
|
|
358
|
+
const digits = c => {
|
|
359
|
+
let x = 0x80, y = c.y, cy = 0;
|
|
360
|
+
for (;;) {
|
|
361
|
+
do {
|
|
362
|
+
let sum = 0; cy = 0;
|
|
363
|
+
for (let k = 3; k >= 0; k--) { sum = read(0x62 + k) + read(0xbf16 + y + k) + cy; write(0x62 + k, sum); cy = sum >> 8; }
|
|
364
|
+
x = (x + 1) & 0xff;
|
|
365
|
+
} while (cy ? x & 0x80 : !(x & 0x80));
|
|
366
|
+
let a = x;
|
|
367
|
+
if (cy) { const t = (a ^ 0xff) + 0x0a + 1; a = t & 0xff; cy = t >> 8; }
|
|
368
|
+
a = (a + 0x2f + cy) & 0xff;
|
|
369
|
+
y = (y + 4) & 0xff; write(0x47, y);
|
|
370
|
+
let at = (read(0x71) + 1) & 0xff;
|
|
371
|
+
write(0xff + at, a & 0x7f);
|
|
372
|
+
write(0x5d, read(0x5d) - 1);
|
|
373
|
+
if (!read(0x5d)) { at = (at + 1) & 0xff; write(0xff + at, 0x2e); }
|
|
374
|
+
write(0x71, at);
|
|
375
|
+
x = (a ^ 0xff) & 0x80;
|
|
376
|
+
if (y === 0x24 || y === 0x3c) break;
|
|
377
|
+
}
|
|
378
|
+
let at = read(0x71), ch;
|
|
379
|
+
do { ch = read(0xff + at); at = (at - 1) & 0xff; } while (ch === 0x30);
|
|
380
|
+
if (ch !== 0x2e) at = (at + 1) & 0xff;
|
|
381
|
+
let e = read(0x5e);
|
|
382
|
+
if (e) {
|
|
383
|
+
let sign = 0x2b; if (e & 0x80) { e = (0 - e) & 0xff; sign = 0x2d; }
|
|
384
|
+
write(0x101 + at, sign); write(0x100 + at, 0x45);
|
|
385
|
+
write(0x103 + at, 0x30 + e % 10); write(0x102 + at, 0x30 + Math.floor(e / 10)); write(0x104 + at, 0);
|
|
386
|
+
} else write(0x100 + at, 0);
|
|
387
|
+
Object.assign(c, { a: 0, y: 1, z: 0, n: 0 });
|
|
388
|
+
};
|
|
389
|
+
H[0xbe68] = digits;
|
|
390
|
+
// GETSPA ($B4F4): A bytes of the strings' space, down from FRETOP ($33), FRESPC ($35) too, X/Y
|
|
391
|
+
// their address; below STREND ($31), the strings collected first ($0F the flag), then OUT OF
|
|
392
|
+
// MEMORY
|
|
393
|
+
H[0xb4f4] = c => {
|
|
394
|
+
const len = c.a;
|
|
395
|
+
write(0x0f, read(0x0f) >> 1);
|
|
396
|
+
for (;;) {
|
|
397
|
+
const at = r16(0x33) - len;
|
|
398
|
+
if (at >= r16(0x31) && at >= 0) { w16(0x33, at); w16(0x35, at); Object.assign(c, { a: len, x: at & 0xff, y: at >> 8 }); return; }
|
|
399
|
+
if (read(0x0f) & 0x80) throw new BasicError('?OUT OF MEMORY ERROR');
|
|
400
|
+
H[0xb526](c); write(0x0f, 0x80);
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
// STRINI ($B475: $50/$51 the descriptor's at $64/$65 kept; $B47D): a string of A bytes made
|
|
404
|
+
// room for, its descriptor in the FAC's $61 to $63
|
|
405
|
+
H[0xb47d] = c => { H[0xb4f4](c); write(0x62, c.x); write(0x63, c.y); write(0x61, c.a); };
|
|
406
|
+
H[0xb475] = c => { write(0x50, read(0x64)); write(0x51, read(0x65)); return H[0xb47d](c); };
|
|
407
|
+
// MOVSTR ($B688): A bytes from X/Y to FRESPC ($35), and FRESPC on past them
|
|
408
|
+
H[0xb688] = c => {
|
|
409
|
+
write(0x22, c.x); write(0x23, c.y);
|
|
410
|
+
const from = c.x | c.y << 8, to = r16(0x35), len = c.a;
|
|
411
|
+
for (let k = len - 1; k >= 0; k--) write(to + k, read(from + k));
|
|
412
|
+
w16(0x35, to + len);
|
|
413
|
+
};
|
|
414
|
+
// PUTNEW ($B4CA): the FAC's descriptor ($61 to $63) pushed on the temporary ones (TEMPPT, $16;
|
|
415
|
+
// full at $22, FORMULA TOO COMPLEX), its address at $64/$65, LASTPT ($17) it, $0D a string
|
|
416
|
+
H[0xb4ca] = c => {
|
|
417
|
+
const x = read(0x16);
|
|
418
|
+
if (x === 0x22) throw new BasicError('?FORMULA TOO COMPLEX ERROR');
|
|
419
|
+
write(x, read(0x61)); write(x + 1, read(0x62)); write(x + 2, read(0x63));
|
|
420
|
+
write(0x64, x); write(0x65, 0); write(0x70, 0); write(0x0d, 0xff); write(0x17, x); write(0x16, x + 3);
|
|
421
|
+
Object.assign(c, { x: x + 3, y: 0xff });
|
|
422
|
+
};
|
|
423
|
+
// STRLIT ($B487, the text at A/Y; $B46F at $00FF, FOUT's): to a 0 or a quote; copied into the
|
|
424
|
+
// strings' space if it is in the zero page or the input buffer (page 0 or 2), else left where it
|
|
425
|
+
// is; and PUTNEW
|
|
426
|
+
H[0xb487] = c => {
|
|
427
|
+
write(0x07, 0x22); write(0x08, 0x22);
|
|
428
|
+
const from = c.a | c.y << 8;
|
|
429
|
+
write(0x6f, c.a); write(0x70, c.y); write(0x62, c.a); write(0x63, c.y);
|
|
430
|
+
let y = 0, quote = 0;
|
|
431
|
+
for (; ; y++) { const ch = read(from + y); if (!ch) break; if (ch === 0x22) { quote = 1; break; } }
|
|
432
|
+
write(0x61, y);
|
|
433
|
+
const end = from + y + quote; write(0x71, end & 0xff); write(0x72, end >> 8 & 0xff);
|
|
434
|
+
if (c.y === 0 || c.y === 2) { c.a = y; H[0xb475](c); Object.assign(c, { a: y, x: from & 0xff, y: from >> 8 }); H[0xb688](c); }
|
|
435
|
+
return H[0xb4ca](c);
|
|
436
|
+
};
|
|
437
|
+
H[0xb46f] = c => { Object.assign(c, { a: 0xff, y: 0 }); return H[0xb487](c); };
|
|
438
|
+
// TI$ ($AF48): the clock, as TI's, counted out to its six digits (hours, minutes, seconds; $5E
|
|
439
|
+
// no exponent, $71 the text's start, a point after the sixth and let go), and a string of them
|
|
440
|
+
H[0xaf48] = c => {
|
|
441
|
+
clock(c);
|
|
442
|
+
write(0x5e, 0); write(0x71, 0xff); write(0x5d, 6);
|
|
443
|
+
c.y = 0x24; digits(c);
|
|
444
|
+
return H[0xb46f](c);
|
|
445
|
+
};
|
|
446
|
+
// ($B248) ILLEGAL QUANTITY, as Blitz! jumps there
|
|
447
|
+
H[0xb248] = () => { throw new BasicError('?ILLEGAL QUANTITY ERROR'); };
|
|
448
|
+
// FRESTR ($B6A3): CHKSTR ($AD8F), a string or TYPE MISMATCH, then FREFAC
|
|
449
|
+
H[0xb6a3] = c => { if (!(read(0x0d) & 0x80)) throw new BasicError('?TYPE MISMATCH ERROR'); return H[0xb6a6](c); };
|
|
450
|
+
// ($E25A) a file's name, the string evaluated: FRESTR, and SETNAM ($FFBD) with it
|
|
451
|
+
H[0xe25a] = c => { H[0xb6a3](c); c.x = read(0x22); c.y = read(0x23); return cpu.hooks.get(0xffbd)(c); };
|
|
452
|
+
// GARBAG ($B526), the strings collected, as the ROM does it, byte for byte: the strings' bottom
|
|
453
|
+
// (FRETOP, $33) at the top of memory (MEMSIZ, $37), then, over and over, the highest string
|
|
454
|
+
// below it still to move found (its address $5F/$60, from STREND $31 up; its descriptor's
|
|
455
|
+
// $4E/$4F, and $55 the step it was found with) among the temporary descriptors ($19 to TEMPPT,
|
|
456
|
+
// $16, a step of $53 as it stands), the variables (VARTAB $2D to ARYTAB $2F, seven bytes each, a
|
|
457
|
+
// string's two name bytes top bit clear then set) and the string arrays' elements (to STREND,
|
|
458
|
+
// three bytes each, past each one's header), and moved up to just under the bottom (BLTUC,
|
|
459
|
+
// $A3BF), its descriptor pointed there, and that the bottom; until none is left
|
|
460
|
+
const r16 = a => read(a) | read(a + 1) << 8, w16 = (a, v) => { write(a, v & 0xff); write(a + 1, v >> 8 & 0xff); };
|
|
461
|
+
// (one descriptor at ($22)+y ($B5C7): a string, below the bottom and above the best so far, the
|
|
462
|
+
// best; then $22 on by the step, $53)
|
|
463
|
+
const candidate = y => {
|
|
464
|
+
const at = r16(0x22);
|
|
465
|
+
if (read(at + y)) {
|
|
466
|
+
const lo = read(at + y + 1), hi = read(at + y + 2);
|
|
467
|
+
const below = hi < read(0x34) || (hi === read(0x34) && lo < read(0x33));
|
|
468
|
+
const above = hi > read(0x60) || (hi === read(0x60) && lo >= read(0x5f));
|
|
469
|
+
if (below && above) { write(0x5f, lo); write(0x60, hi); w16(0x4e, at); write(0x55, read(0x53)); }
|
|
470
|
+
}
|
|
471
|
+
w16(0x22, at + read(0x53));
|
|
472
|
+
};
|
|
473
|
+
// BLTUC ($A3BF): the bytes from $5F/$60 to $5A/$5B moved to end at $58/$59, from the top down,
|
|
474
|
+
// a page at a time, those pointers left as the ROM's loop leaves them
|
|
475
|
+
const bltuc = () => {
|
|
476
|
+
const n = (r16(0x5a) - r16(0x5f)) & 0xffff;
|
|
477
|
+
let y = n & 0xff, x = ((n >> 8) + 1) & 0xff;
|
|
478
|
+
write(0x22, y);
|
|
479
|
+
const copy = k => write(r16(0x58) + k, read(r16(0x5a) + k));
|
|
480
|
+
if (y) { w16(0x5a, r16(0x5a) - y); w16(0x58, r16(0x58) - y); }
|
|
481
|
+
for (let first = true; ; first = false) {
|
|
482
|
+
if (!first || y) { do { y = (y - 1) & 0xff; if (y) copy(y); } while (y); copy(0); }
|
|
483
|
+
write(0x5b, (read(0x5b) - 1) & 0xff); write(0x59, (read(0x59) - 1) & 0xff);
|
|
484
|
+
x = (x - 1) & 0xff; if (!x) break;
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
H[0xb526] = c => {
|
|
488
|
+
let bottom = r16(0x37);
|
|
489
|
+
for (;;) {
|
|
490
|
+
w16(0x33, bottom); w16(0x4e, 0); w16(0x5f, r16(0x31));
|
|
491
|
+
for (w16(0x22, 0x19); read(0x22) !== read(0x16);) candidate(0);
|
|
492
|
+
write(0x53, 7);
|
|
493
|
+
for (w16(0x22, r16(0x2d)); r16(0x22) !== r16(0x2f);) {
|
|
494
|
+
const at = r16(0x22);
|
|
495
|
+
if (!(read(at) & 0x80) && read(at + 1) & 0x80) candidate(2); else w16(0x22, at + 7);
|
|
496
|
+
}
|
|
497
|
+
w16(0x58, r16(0x2f));
|
|
498
|
+
write(0x53, 3);
|
|
499
|
+
while (r16(0x58) !== r16(0x31)) {
|
|
500
|
+
const at = r16(0x58); w16(0x22, at);
|
|
501
|
+
// (its length added with the carry the comparison with STREND left, clear below it)
|
|
502
|
+
w16(0x58, at + r16(at + 2) + (at >= r16(0x31) ? 1 : 0));
|
|
503
|
+
if (read(at) & 0x80 || !(read(at + 1) & 0x80)) continue;
|
|
504
|
+
// (past its header: five bytes and two for each dimension, the ROM's sum carrying once only)
|
|
505
|
+
const v = ((read(at + 4) << 1) & 0xff) + 5 + (read(at + 4) >> 7);
|
|
506
|
+
w16(0x22, at + (v & 0xff) + (v >> 8));
|
|
507
|
+
while (r16(0x22) !== r16(0x58)) candidate(0);
|
|
508
|
+
}
|
|
509
|
+
if (!r16(0x4e)) { Object.assign(c, { a: 0, x: read(0x23), y: 0, z: 1, n: 0 }); return; }
|
|
510
|
+
const y = (read(0x55) & 4) >> 1; write(0x55, y);
|
|
511
|
+
w16(0x5a, r16(0x5f) + read(r16(0x4e) + y));
|
|
512
|
+
w16(0x58, r16(0x33));
|
|
513
|
+
bltuc();
|
|
514
|
+
write(r16(0x4e) + y + 1, read(0x58));
|
|
515
|
+
write(0x59, (read(0x59) + 1) & 0xff);
|
|
516
|
+
write(r16(0x4e) + y + 2, read(0x59));
|
|
517
|
+
bottom = r16(0x58);
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
for (const [a, f] of Object.entries(H)) cpu.hooks.set(+a, f);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// A BASIC error, as the ROM's routine gives it ($A43A, through the vector at $0300): the output
|
|
524
|
+
// back to the screen (CLRCHN), a new line, ? and the message, ERROR, IN and the "line" (for
|
|
525
|
+
// Blitz!'s code its program counter at the instruction that failed), then READY. False if the
|
|
526
|
+
// message is not one of BASIC's.
|
|
527
|
+
export function printError({ cpu, write }, message, line) {
|
|
528
|
+
const what = /^\?(.*) ERROR$/.exec(message)?.[1];
|
|
529
|
+
if (!ERRORS.includes(what)) return false;
|
|
530
|
+
const out = ch => { cpu.a = ch; cpu.hooks.get(0xffd2)(cpu); };
|
|
531
|
+
const text = s => { for (const ch of s) out(ch.charCodeAt(0)); };
|
|
532
|
+
cpu.hooks.get(0xffcc)(cpu); write(0x13, 0);
|
|
533
|
+
out(0x0d); out(0x3f); text(what); text(' ERROR');
|
|
534
|
+
if (line !== undefined && line >> 8 !== 0xff) { text(' IN '); text(String(line)); }
|
|
535
|
+
text('\r\nREADY.\r\n');
|
|
536
|
+
write(0x9d, 0x80);
|
|
537
|
+
return true;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// The evaluator on the original: the expression in BASIC's text at an address read
|
|
541
|
+
// (basic-lang.js), made a function by the compiler's own code (basic-compile.js),
|
|
542
|
+
// and worked out by BASIC's own rules (basic.js's), its variables read where BASIC keeps
|
|
543
|
+
// them: its table from VARTAB ($2D) to ARYTAB ($2F), seven bytes each, two of name (a whole
|
|
544
|
+
// number's both with the top bit set, a string's the second) and five of value (a number's five
|
|
545
|
+
// bytes; a whole number's high and low; a string's length and where it is). RND is the ROM's
|
|
546
|
+
// routine at $E097, called as the ROM's evaluator calls it. Each address's function is kept.
|
|
547
|
+
export function evaluateInMemory({ cpu, read, write, ram }) {
|
|
548
|
+
const f = new Floats({ read, write }), made = new Map();
|
|
549
|
+
const variable = n => {
|
|
550
|
+
const kind = n.endsWith('%') ? '%' : n.endsWith('$') ? '$' : '', name = kind ? n.slice(0, -1) : n;
|
|
551
|
+
const c1 = name.charCodeAt(0) | (kind === '%' ? 0x80 : 0), c2 = (name.charCodeAt(1) || 0) | (kind ? 0x80 : 0);
|
|
552
|
+
const end = read(0x2f) | read(0x30) << 8;
|
|
553
|
+
for (let at = read(0x2d) | read(0x2e) << 8; at < end; at += 7) {
|
|
554
|
+
if (read(at) !== c1 || read(at + 1) !== c2) continue;
|
|
555
|
+
const v = [2, 3, 4, 5, 6].map(k => read(at + k));
|
|
556
|
+
if (kind === '%') return (v[0] << 8 | v[1]) << 16 >> 16;
|
|
557
|
+
if (kind === '$') { let s = ''; const p = v[1] | v[2] << 8; for (let k = 0; k < v[0]; k++) s += String.fromCharCode(read(p + k)); return s; }
|
|
558
|
+
return unpack(v);
|
|
559
|
+
}
|
|
560
|
+
throw new Error(`the variable ${n} not in BASIC's table: its making by PTRGET not handled`);
|
|
561
|
+
};
|
|
562
|
+
const b = Object.assign(Object.create(Basic.prototype), {
|
|
563
|
+
get: variable,
|
|
564
|
+
rnd: () => { cpu.hooks.get(0xe097)(cpu); return f.fac(); },
|
|
565
|
+
});
|
|
566
|
+
// (the text read from RAM, where a program's is: `ram`)
|
|
567
|
+
return at => {
|
|
568
|
+
const { e, end } = expression(ram, at);
|
|
569
|
+
let fn = made.get(at); if (!fn) made.set(at, fn = evaluator(e));
|
|
570
|
+
return { value: fn(b), end, place: e.k === 'str' ? e.at : undefined };
|
|
571
|
+
};
|
|
572
|
+
}
|