@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/float.js
ADDED
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
// Commodore BASIC's floating point, moonbase's own: the five-byte numbers BASIC keeps, and its
|
|
2
|
+
// arithmetic on them, computing what BASIC's ROM computes, to the bit. It works where the ROM's
|
|
3
|
+
// does, in the zero page, so that moonbase's BASIC ROM (basic-rom.js) can answer a program's
|
|
4
|
+
// call into the ROM's arithmetic at its own address and leave memory as the ROM leaves it. Every
|
|
5
|
+
// routine here is held to Commodore's, run on the processor, from thousands of random starting
|
|
6
|
+
// states.
|
|
7
|
+
//
|
|
8
|
+
// const f = new Floats(memory) memory: read(address) and write(address, value)
|
|
9
|
+
//
|
|
10
|
+
// A number in memory is five bytes: its exponent (128 + the power of two, 0 for zero), then
|
|
11
|
+
// its 32-bit mantissa, highest byte first, whose top bit, always 1, is the sign's place. In the
|
|
12
|
+
// accumulator (FAC, $61 to $66) the mantissa's top bit is 1 and the sign has its own byte, $66;
|
|
13
|
+
// a sixth byte, $70, holds the bits below the mantissa, which an operation keeps, and only
|
|
14
|
+
// storing a number (or ROUND) rounds them in. The other operand (ARG, $69 to $6E) is the same,
|
|
15
|
+
// its own low bits at $56 while an addition lines the two up. $6F is the two signs' EOR.
|
|
16
|
+
//
|
|
17
|
+
// The ROM's routines have their ways, and these keep them: the low bits are kept, not rounded,
|
|
18
|
+
// until a number is stored; a shift by whole bytes brings in $68 at the top, whatever an earlier
|
|
19
|
+
// routine left there; a shift's length depends on the carry it is entered with. Each method
|
|
20
|
+
// says the ROM's address it stands for; its results are the ROM's, in memory and in the
|
|
21
|
+
// registers its callers read (`regs`).
|
|
22
|
+
export const FAC = 0x61, ARG = 0x69, FACOV = 0x70, ARGOV = 0x56, ARISGN = 0x6f, BITS = 0x68, RESHO = 0x26;
|
|
23
|
+
|
|
24
|
+
// Constants of BASIC's that its routines take from the ROM, by their place there: their values,
|
|
25
|
+
// as five bytes, each named as Microsoft's source names it (BASIC-M6502). A game that reads one
|
|
26
|
+
// reads it from moonbase's ROM image too (rom.js).
|
|
27
|
+
const words = ns => ns.flatMap(n => [n >>> 24 & 0xff, n >>> 16 & 0xff, n >>> 8 & 0xff, n & 0xff]);
|
|
28
|
+
export const CONSTANTS = {
|
|
29
|
+
0xb9bc: [0x81, 0x00, 0x00, 0x00, 0x00], // 1 (Microsoft's FONE)
|
|
30
|
+
0xbf11: [0x80, 0x00, 0x00, 0x00, 0x00], // 0.5 (FHALF)
|
|
31
|
+
0xbaf9: [0x84, 0x20, 0x00, 0x00, 0x00], // 10 (TENZC)
|
|
32
|
+
// LOG's: its series' (a count, then the coefficients of x squared, highest first, for POLY1),
|
|
33
|
+
// the square roots of a half and of two, -0.5, and the logarithm of two (LOGCN2, SQRHLF, SQRTWO,
|
|
34
|
+
// NEGHLF, LOG2)
|
|
35
|
+
0xb9c1: [0x03, 0x7f, 0x5e, 0x56, 0xcb, 0x79, 0x80, 0x13, 0x9b, 0x0b, 0x64, 0x80, 0x76, 0x38, 0x93, 0x16, 0x82, 0x38, 0xaa, 0x3b, 0x20],
|
|
36
|
+
0xb9d6: [0x80, 0x35, 0x04, 0xf3, 0x34], // 0.707...
|
|
37
|
+
0xb9db: [0x81, 0x35, 0x04, 0xf3, 0x34], // 1.414...
|
|
38
|
+
0xb9e0: [0x80, 0x80, 0x00, 0x00, 0x00], // -0.5
|
|
39
|
+
0xb9e5: [0x80, 0x31, 0x72, 0x17, 0xf8], // 0.693...
|
|
40
|
+
// EXP's: one over the logarithm of two, and its series (for POLY2) (LOGEB2, EXPCON)
|
|
41
|
+
0xbfbf: [0x81, 0x38, 0xaa, 0x3b, 0x29], // 1.442...
|
|
42
|
+
0xbfc4: [0x07, 0x71, 0x34, 0x58, 0x3e, 0x56, 0x74, 0x16, 0x7e, 0xb3, 0x1b, 0x77, 0x2f, 0xee, 0xe3, 0x85, 0x7a, 0x1d, 0x84, 0x1c, 0x2a,
|
|
43
|
+
0x7c, 0x63, 0x59, 0x58, 0x0a, 0x7e, 0x75, 0xfd, 0xe7, 0xc6, 0x80, 0x31, 0x72, 0x18, 0x10, 0x81, 0x00, 0x00, 0x00, 0x00],
|
|
44
|
+
// FOUT's: the powers of ten its digits are counted out with, and TI$'s steps of the clock (hours
|
|
45
|
+
// to seconds, in jiffies), each a 32-bit whole number, alternately subtracted and added (FOUTBL)
|
|
46
|
+
0xbf16: words([-100000000, 10000000, -1000000, 100000, -10000, 1000, -100, 10, -1, -2160000, 216000, -36000, 3600, -600, 60]),
|
|
47
|
+
// and the bounds it brings a number between, 99999999.9 and 999999999 (both rounded up a
|
|
48
|
+
// little), and 1E9, a small number's first step up (NZ0999, NZ9999, NZMIL)
|
|
49
|
+
0xbdb3: [0x9b, 0x3e, 0xbc, 0x1f, 0xfd],
|
|
50
|
+
0xbdb8: [0x9e, 0x6e, 0x6b, 0x27, 0xfd],
|
|
51
|
+
0xbdbd: [0x9e, 0x6e, 0x6b, 0x28, 0x00],
|
|
52
|
+
// SIN's and COS's: a half pi, two pi, a quarter, and SIN's series (for POLY1) (PI2, TWOPI, FR4,
|
|
53
|
+
// SINCON)
|
|
54
|
+
0xe2e0: [0x81, 0x49, 0x0f, 0xda, 0xa2], // 1.570...
|
|
55
|
+
0xe2e5: [0x83, 0x49, 0x0f, 0xda, 0xa2], // 6.283...
|
|
56
|
+
0xe2ea: [0x7f, 0x00, 0x00, 0x00, 0x00], // 0.25
|
|
57
|
+
0xe2ef: [0x05, 0x84, 0xe6, 0x1a, 0x2d, 0x1b, 0x86, 0x28, 0x07, 0xfb, 0xf8, 0x87, 0x99, 0x68, 0x89, 0x01, 0x87, 0x23, 0x35, 0xdf, 0xe1,
|
|
58
|
+
0x86, 0xa5, 0x5d, 0xe7, 0x28, 0x83, 0x49, 0x0f, 0xda, 0xa2],
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// a number as the five bytes BASIC stores, from JavaScript's (for tests and for constants)
|
|
62
|
+
export function pack(x) {
|
|
63
|
+
if (x === 0) return [0, 0, 0, 0, 0];
|
|
64
|
+
const a = Math.abs(x);
|
|
65
|
+
let ex = Math.floor(Math.log2(a)) + 1, f = a / 2 ** ex;
|
|
66
|
+
if (f >= 1) { f /= 2; ex++; } else if (f < 0.5) { f *= 2; ex--; }
|
|
67
|
+
let m = Math.round(f * 2 ** 32); if (m >= 2 ** 32) { m /= 2; ex++; }
|
|
68
|
+
return [ex + 128, (m >>> 24 & 0x7f) | (x < 0 ? 0x80 : 0), m >>> 16 & 0xff, m >>> 8 & 0xff, m & 0xff];
|
|
69
|
+
}
|
|
70
|
+
// a number into the accumulator as BASIC holds it: $61 its exponent, $62 to $65 its mantissa,
|
|
71
|
+
// $66 its sign, its low bits ($70) 0
|
|
72
|
+
export function setFac(memory, x) {
|
|
73
|
+
const b = pack(x);
|
|
74
|
+
memory.write(0x70, 0);
|
|
75
|
+
if (!b[0]) { for (let a = 0x61; a <= 0x66; a++) memory.write(a, 0); return; }
|
|
76
|
+
memory.write(0x61, b[0]); memory.write(0x62, b[1] | 0x80); memory.write(0x63, b[2]); memory.write(0x64, b[3]); memory.write(0x65, b[4]); memory.write(0x66, x < 0 ? 0xff : 0);
|
|
77
|
+
}
|
|
78
|
+
// and back: the number five bytes hold
|
|
79
|
+
export function unpack(b) {
|
|
80
|
+
if (!b[0]) return 0;
|
|
81
|
+
const m = ((b[1] | 0x80) * 2 ** 24) + b[2] * 65536 + b[3] * 256 + b[4];
|
|
82
|
+
return (b[1] & 0x80 ? -1 : 1) * m / 2 ** 32 * 2 ** (b[0] - 128);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export class Floats {
|
|
86
|
+
constructor(memory) { this.mem = memory; }
|
|
87
|
+
r(a) { return this.mem.read(a & 0xffff); }
|
|
88
|
+
w(a, v) { this.mem.write(a & 0xffff, v & 0xff); }
|
|
89
|
+
// a register's mantissa, four bytes from `at`, as a number, and back
|
|
90
|
+
mant(at) { return ((this.r(at) * 256 + this.r(at + 1)) * 256 + this.r(at + 2)) * 256 + this.r(at + 3); }
|
|
91
|
+
setMant(at, m) { this.w(at, m / 2 ** 24); this.w(at + 1, m / 65536); this.w(at + 2, m / 256); this.w(at + 3, m); }
|
|
92
|
+
// (the FAC's value, for this module's own use and for tests)
|
|
93
|
+
fac() { const e = this.r(FAC); return e ? (this.r(0x66) & 0x80 ? -1 : 1) * (this.mant(0x62) + this.r(FACOV) / 256) / 2 ** 32 * 2 ** (e - 128) : 0; }
|
|
94
|
+
|
|
95
|
+
// ---- moving numbers ----
|
|
96
|
+
|
|
97
|
+
// CONUPK ($BA8C): ARG from memory at A/Y; $6F the two signs' EOR; A the FAC's exponent
|
|
98
|
+
conupk(at) {
|
|
99
|
+
this.w(0x22, at); this.w(0x23, at >> 8);
|
|
100
|
+
const b = [0, 1, 2, 3, 4].map(i => this.r(at + i));
|
|
101
|
+
this.w(0x6d, b[4]); this.w(0x6c, b[3]); this.w(0x6b, b[2]); this.w(0x6e, b[1]);
|
|
102
|
+
this.w(ARISGN, b[1] ^ this.r(0x66)); this.w(0x6a, b[1] | 0x80); this.w(ARG, b[0]);
|
|
103
|
+
return { a: this.r(FAC), y: 0 };
|
|
104
|
+
}
|
|
105
|
+
// MOVFM ($BBA2): the FAC from memory at A/Y, its low bits 0; A its exponent
|
|
106
|
+
movfm(at) {
|
|
107
|
+
this.w(0x22, at); this.w(0x23, at >> 8);
|
|
108
|
+
const b = [0, 1, 2, 3, 4].map(i => this.r(at + i));
|
|
109
|
+
this.w(0x65, b[4]); this.w(0x64, b[3]); this.w(0x63, b[2]); this.w(0x66, b[1]); this.w(0x62, b[1] | 0x80); this.w(FAC, b[0]); this.w(FACOV, 0);
|
|
110
|
+
return { a: b[0], y: 0 };
|
|
111
|
+
}
|
|
112
|
+
// MOVMF ($BBD4): the FAC, rounded, into memory at X/Y; its low bits 0 after
|
|
113
|
+
movmf(at) {
|
|
114
|
+
this.round();
|
|
115
|
+
this.w(0x22, at); this.w(0x23, at >> 8);
|
|
116
|
+
this.w(at + 4, this.r(0x65)); this.w(at + 3, this.r(0x64)); this.w(at + 2, this.r(0x63));
|
|
117
|
+
this.w(at + 1, (this.r(0x66) | 0x7f) & this.r(0x62)); this.w(at, this.r(FAC)); this.w(FACOV, 0);
|
|
118
|
+
return { a: this.r(FAC), y: 0 };
|
|
119
|
+
}
|
|
120
|
+
// MOVFA ($BBFC): the FAC from ARG; MOVAF ($BC0C): ARG from the FAC, rounded first
|
|
121
|
+
movfa() { this.w(0x66, this.r(0x6e)); for (let i = 5; i >= 1; i--) this.w(0x60 + i, this.r(0x68 + i)); this.w(FACOV, 0); return { a: this.r(ARG), x: 0 }; }
|
|
122
|
+
movaf() { this.round(); return this.movef(); }
|
|
123
|
+
// ($BC0F, MOVAF after its ROUND: the FAC copied as it is)
|
|
124
|
+
movef() { for (let i = 6; i >= 1; i--) this.w(0x68 + i, this.r(0x60 + i)); this.w(FACOV, 0); return { a: this.r(FAC), x: 0 }; }
|
|
125
|
+
|
|
126
|
+
// ---- rounding, sign, comparing ----
|
|
127
|
+
|
|
128
|
+
// ROUND ($BC1B): the low bits' top bit added in at the mantissa's foot; the low bits shifted
|
|
129
|
+
// left, as the ROM leaves them; a carry out of the mantissa a power of two more
|
|
130
|
+
round() {
|
|
131
|
+
if (!this.r(FAC)) return;
|
|
132
|
+
const ov = this.r(FACOV); this.w(FACOV, ov << 1);
|
|
133
|
+
if (!(ov & 0x80)) return;
|
|
134
|
+
const m = this.mant(0x62) + 1;
|
|
135
|
+
if (m < 2 ** 32) { this.setMant(0x62, m); return; }
|
|
136
|
+
this.setMant(0x62, 0);
|
|
137
|
+
this.carryIn(1);
|
|
138
|
+
}
|
|
139
|
+
// ($B938) the exponent one more, and the mantissa and low bits shifted right with C in at the top
|
|
140
|
+
carryIn(c) {
|
|
141
|
+
const e = this.r(FAC) + 1; if (e > 255) throw overflow();
|
|
142
|
+
this.w(FAC, e);
|
|
143
|
+
const m = this.mant(0x62), ov = this.r(FACOV);
|
|
144
|
+
this.setMant(0x62, Math.floor(m / 2) + (c ? 2 ** 31 : 0)); this.w(FACOV, (ov >> 1) | ((m & 1) << 7));
|
|
145
|
+
}
|
|
146
|
+
// SIGN ($BC2B): 0 for zero, 1 for positive, $FF for negative
|
|
147
|
+
sign() { return !this.r(FAC) ? 0 : this.r(0x66) & 0x80 ? 0xff : 1; }
|
|
148
|
+
// FCOMP ($BC5B, the address in A and Y; $BC5D with $24 set): the FAC against a number in memory,
|
|
149
|
+
// 1 if the FAC is more, $FF if less, 0 if the same; the FAC's low bits, rounded, count only in
|
|
150
|
+
// the mantissa's last byte, as the ROM's subtraction there borrows them
|
|
151
|
+
fcomp(at) { return this.compare(at).a; }
|
|
152
|
+
// (and Y as FCOMP leaves it: the byte it decided at, 1 for the exponent or the sign, 4 if equal)
|
|
153
|
+
compare(at) {
|
|
154
|
+
this.w(0x24, at); this.w(0x25, at >> 8);
|
|
155
|
+
const ex = this.r(at);
|
|
156
|
+
if (!ex) return { a: this.sign(), y: 1 };
|
|
157
|
+
const b1 = this.r(at + 1), s = this.r(0x66);
|
|
158
|
+
const memMore = (more, y) => { const neg = s & 0x80 ? 1 : 0; return { a: (more ? neg ^ 1 : neg) ? 0xff : 1, y }; }; // more: memory > FAC in size
|
|
159
|
+
if ((b1 ^ s) & 0x80) return { a: s & 0x80 ? 0xff : 1, y: 1 };
|
|
160
|
+
if (ex !== this.r(FAC)) return memMore(ex > this.r(FAC), 1);
|
|
161
|
+
for (const [k, fac] of [[1, 0x62], [2, 0x63], [3, 0x64]]) {
|
|
162
|
+
const mb = k === 1 ? b1 | 0x80 : this.r(at + k), fb = this.r(fac);
|
|
163
|
+
if (mb !== fb) return memMore(mb > fb, k);
|
|
164
|
+
}
|
|
165
|
+
const borrow = this.r(FACOV) > 0x7f ? 1 : 0, d = this.r(at + 4) - this.r(0x65) - borrow;
|
|
166
|
+
if ((d & 0xff) === 0) return { a: 0, y: 4 };
|
|
167
|
+
return memMore(d >= 0, 4);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ---- adding ----
|
|
171
|
+
|
|
172
|
+
// FADD ($B867, the address in A and Y: memory plus the FAC; $B86A, ARG plus the FAC, A the
|
|
173
|
+
// FAC's exponent): the result normalised, its low bits kept
|
|
174
|
+
fadd(at) { this.conupk(at); return this.faddt(); }
|
|
175
|
+
// FSUB ($B850): memory minus the FAC; FSUBT ($B853): ARG minus the FAC
|
|
176
|
+
fsub(at) { this.conupk(at); return this.fsubt(); }
|
|
177
|
+
fsubt() { this.w(0x66, this.r(0x66) ^ 0xff); this.w(ARISGN, this.r(0x66) ^ this.r(0x6e)); return this.faddt(); }
|
|
178
|
+
faddt() {
|
|
179
|
+
if (!this.r(FAC)) return this.movfa();
|
|
180
|
+
this.w(ARGOV, this.r(FACOV));
|
|
181
|
+
const ey = this.r(ARG);
|
|
182
|
+
if (!ey) return { a: 0 };
|
|
183
|
+
let d = (ey - this.r(FAC)) & 0xff, x = ARG, c = ey >= this.r(FAC) ? 1 : 0, a = 0;
|
|
184
|
+
if (d !== 0) {
|
|
185
|
+
if (c) { // ARG the larger: the FAC takes its exponent and sign, and is the one shifted
|
|
186
|
+
this.w(FAC, ey); this.w(0x66, this.r(0x6e));
|
|
187
|
+
d = (~d + 1) & 0xff; this.w(ARGOV, 0); x = FAC;
|
|
188
|
+
} else this.w(FACOV, 0);
|
|
189
|
+
// (under a byte by the ROM's own signed test of A - $F9, $B897: bit by bit, the first
|
|
190
|
+
// shifted plainly, the rest keeping the top bit, $B89B, which for a count the test misreads
|
|
191
|
+
// is up to 255 bits; otherwise whole bytes first, $B862)
|
|
192
|
+
a = ((d - 0xf9) & 0x80) ? this.shift(x, d, 0) : this.bitShift(x, d, this.r(FACOV), false);
|
|
193
|
+
}
|
|
194
|
+
if (this.r(ARISGN) & 0x80) {
|
|
195
|
+
// the signs differ: the one not shifted less the one shifted, into the FAC
|
|
196
|
+
const minus = x === ARG ? FAC : ARG;
|
|
197
|
+
const low = this.r(ARGOV) - a, lowByte = low & 0xff, borrow = low < 0 ? 1 : 0;
|
|
198
|
+
const m = this.mant(minus + 1) - this.mant(x + 1) - borrow;
|
|
199
|
+
this.w(FACOV, lowByte); this.setMant(0x62, (m + 2 ** 32) % 2 ** 32);
|
|
200
|
+
if (m < 0) this.negate();
|
|
201
|
+
return this.normal();
|
|
202
|
+
}
|
|
203
|
+
// the same signs: the sum, a carry out a power of two more; with the exponents the same,
|
|
204
|
+
// the carry their comparison left adds one more to the low bits ($B8FE)
|
|
205
|
+
const low = a + this.r(ARGOV) + (d === 0 ? 1 : 0), m = this.mant(0x62) + this.mant(0x6a) + (low > 255 ? 1 : 0);
|
|
206
|
+
this.w(FACOV, low); this.setMant(0x62, m % 2 ** 32);
|
|
207
|
+
if (m >= 2 ** 32) this.carryIn(1);
|
|
208
|
+
return { a: this.r(FAC) };
|
|
209
|
+
}
|
|
210
|
+
// the FAC's mantissa and low bits two's-complemented, its sign turned over ($B947); $B94D the
|
|
211
|
+
// same without the sign
|
|
212
|
+
negate() { this.w(0x66, this.r(0x66) ^ 0xff); this.complement(); }
|
|
213
|
+
complement() {
|
|
214
|
+
const ov = (~this.r(FACOV) & 0xff) + 1, m = 2 ** 32 - 1 - this.mant(0x62) + (ov > 255 ? 1 : 0);
|
|
215
|
+
this.w(FACOV, ov); this.setMant(0x62, m % 2 ** 32);
|
|
216
|
+
}
|
|
217
|
+
// NORMAL ($B8D7): the mantissa shifted up until its top bit is 1, a byte at a time and then a
|
|
218
|
+
// bit, the low bits coming in below it, and the exponent that much less; zero if it was zero,
|
|
219
|
+
// or if the exponent would go under
|
|
220
|
+
normal() {
|
|
221
|
+
let n = 0;
|
|
222
|
+
while (!this.r(0x62)) {
|
|
223
|
+
this.w(0x62, this.r(0x63)); this.w(0x63, this.r(0x64)); this.w(0x64, this.r(0x65)); this.w(0x65, this.r(FACOV)); this.w(FACOV, 0);
|
|
224
|
+
n += 8; if (n === 32) return this.zero();
|
|
225
|
+
}
|
|
226
|
+
while (!(this.r(0x62) & 0x80)) {
|
|
227
|
+
const ov = this.r(FACOV), m = this.mant(0x62);
|
|
228
|
+
this.w(FACOV, ov << 1); this.setMant(0x62, (m * 2 + (ov >> 7)) % 2 ** 32); n++;
|
|
229
|
+
}
|
|
230
|
+
if (n >= this.r(FAC)) return this.zero();
|
|
231
|
+
this.w(FAC, this.r(FAC) - n);
|
|
232
|
+
return { a: this.r(FAC) };
|
|
233
|
+
}
|
|
234
|
+
zero() { this.w(FAC, 0); this.w(0x66, 0); return { a: 0 }; }
|
|
235
|
+
// The ROM's shift right of a register (X: $61 the FAC, $69 ARG, $25 the product) by -A bits,
|
|
236
|
+
// A a negative count; its low bits are $70, or the A register (the return here) in a shift of
|
|
237
|
+
// under a byte. Whole bytes first, each bringing $68 in at the top ($B985), as long as A + 8
|
|
238
|
+
// (and the carry) stays under 1; then the bits, the top one kept, as for a signed number ($B9A6).
|
|
239
|
+
shift(x, a, c = 0) {
|
|
240
|
+
for (;;) {
|
|
241
|
+
const sum = a + 8 + c; a = sum & 0xff; c = sum > 255 ? 1 : 0;
|
|
242
|
+
if (a & 0x80 || !a) { this.byteShift(x); continue; }
|
|
243
|
+
const diff = a - 8 - (1 - c); a = diff & 0xff; c = diff >= 0 ? 1 : 0;
|
|
244
|
+
if (c) return this.r(FACOV);
|
|
245
|
+
return this.bitShift(x, a, this.r(FACOV), true);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
byteShift(x) {
|
|
249
|
+
this.w(FACOV, this.r(x + 4)); this.w(x + 4, this.r(x + 3)); this.w(x + 3, this.r(x + 2)); this.w(x + 2, this.r(x + 1)); this.w(x + 1, this.r(BITS));
|
|
250
|
+
}
|
|
251
|
+
// bits: Y counting up from -n, each a shift right of the register's four bytes and A below them,
|
|
252
|
+
// the top byte's top bit kept (signed) or, for the first of an addition's, cleared
|
|
253
|
+
bitShift(x, y, a, signed) {
|
|
254
|
+
for (; y !== 0; y = (y + 1) & 0xff) {
|
|
255
|
+
const top = this.r(x + 1), m = this.mant(x + 1);
|
|
256
|
+
const keep = signed ? top & 0x80 : 0;
|
|
257
|
+
this.setMant(x + 1, Math.floor(m / 2) + keep * 2 ** 24);
|
|
258
|
+
a = (a >> 1) | ((m & 1) << 7);
|
|
259
|
+
signed = true;
|
|
260
|
+
}
|
|
261
|
+
return a;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ---- multiplying and dividing ----
|
|
265
|
+
|
|
266
|
+
// FMULT ($BA28, memory times the FAC; $BA2B, ARG times the FAC): the mantissas multiplied a
|
|
267
|
+
// byte of the FAC's at a time, its low bits first, the product's bits shifted out below lost
|
|
268
|
+
fmult(at) { this.conupk(at); return this.fmultt(); }
|
|
269
|
+
fmultt() {
|
|
270
|
+
if (!this.r(FAC)) return { a: 0 };
|
|
271
|
+
const md = this.muldiv();
|
|
272
|
+
if (md.zero) return { a: 0 };
|
|
273
|
+
for (const r of [0x26, 0x27, 0x28, 0x29]) this.w(r, 0);
|
|
274
|
+
let c = md.c;
|
|
275
|
+
for (const at of [FACOV, 0x65, 0x64, 0x63, 0x62]) c = this.mltply(this.r(at), c, at === 0x62);
|
|
276
|
+
this.w(0x62, this.r(0x26)); this.w(0x63, this.r(0x27)); this.w(0x64, this.r(0x28)); this.w(0x65, this.r(0x29));
|
|
277
|
+
return this.normal();
|
|
278
|
+
}
|
|
279
|
+
// one byte of the multiplier ($BA59): a zero byte a shift of the product by bytes; another, its
|
|
280
|
+
// bits from the lowest, ARG's mantissa added in for each 1, the product shifted right each time
|
|
281
|
+
mltply(b, c, last) {
|
|
282
|
+
if (!b && !last) { this.byteShift(0x25); this.shift(0x25, 0, c); return 0; }
|
|
283
|
+
for (let bits = (b >> 1) | 0x80, bit = b & 1; ; ) {
|
|
284
|
+
let p = this.mant(0x26), carry = 0;
|
|
285
|
+
if (bit) { p += this.mant(0x6a); carry = p >= 2 ** 32 ? 1 : 0; p %= 2 ** 32; }
|
|
286
|
+
const ov = this.r(FACOV);
|
|
287
|
+
this.w(FACOV, (ov >> 1) | ((p & 1) << 7)); this.setMant(0x26, Math.floor(p / 2) + carry * 2 ** 31);
|
|
288
|
+
bit = bits & 1; bits >>= 1;
|
|
289
|
+
if (!bits) return 1;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// MULDIV ($BAB7; $BAB9 with A in place of ARG's exponent, `ey`): the exponents added for a product or quotient, and its sign. `zero` when ARG is
|
|
293
|
+
// zero or the sum too small (the FAC zeroed, and the caller done); too large is an overflow. An
|
|
294
|
+
// exponent of exactly 0 goes on, its sign 0, the caller working the mantissa out all the same.
|
|
295
|
+
// `c` the carry it leaves, which a product begins with.
|
|
296
|
+
muldiv(ey = this.r(ARG)) {
|
|
297
|
+
if (!ey) { this.zero(); return { zero: true }; }
|
|
298
|
+
const sum = ey + this.r(FAC), low = sum & 0xff;
|
|
299
|
+
if (sum < 256 && low < 0x80) { this.zero(); return { zero: true }; }
|
|
300
|
+
if (sum >= 256 && (low & 0x80)) throw overflow();
|
|
301
|
+
const e = (low + 0x80) & 0xff, c = sum < 256 ? 1 : 0;
|
|
302
|
+
this.w(FAC, e);
|
|
303
|
+
this.w(0x66, e ? this.r(ARISGN) : 0);
|
|
304
|
+
return { zero: false, c };
|
|
305
|
+
}
|
|
306
|
+
// FDIV ($BB0F, memory divided by the FAC; $BB12, ARG by the FAC): the FAC rounded first, then
|
|
307
|
+
// 34 bits of quotient by subtracting, 32 as the mantissa and two as the low bits' top
|
|
308
|
+
fdiv(at) { this.conupk(at); return this.fdivt(); }
|
|
309
|
+
fdivt() {
|
|
310
|
+
if (!this.r(FAC)) throw new FloatError(0x14); // DIVISION BY ZERO
|
|
311
|
+
this.round();
|
|
312
|
+
this.w(FAC, (0 - this.r(FAC)) & 0xff);
|
|
313
|
+
if (this.muldiv().zero) return { a: 0 };
|
|
314
|
+
const e = this.r(FAC) + 1; if (e > 255) throw overflow();
|
|
315
|
+
this.w(FAC, e);
|
|
316
|
+
let a = this.mant(0x6a), carry = 0, q = 0;
|
|
317
|
+
const f = this.mant(0x62);
|
|
318
|
+
for (let i = 0; i < 34; i++) {
|
|
319
|
+
const bit = carry || a >= f ? 1 : 0;
|
|
320
|
+
if (bit) a = (a - f + 2 ** 32) % 2 ** 32;
|
|
321
|
+
q = q * 2 + bit;
|
|
322
|
+
carry = a >= 2 ** 31 ? 1 : 0; a = (a * 2) % 2 ** 32;
|
|
323
|
+
}
|
|
324
|
+
this.setMant(0x6a, a);
|
|
325
|
+
this.w(FACOV, (q % 4) << 6); this.setMant(0x62, Math.floor(q / 4));
|
|
326
|
+
return this.normal();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ---- whole numbers ----
|
|
330
|
+
|
|
331
|
+
// QINT ($BC9B): the FAC as a 32-bit whole number in $62 to $65, two's complement, its fraction
|
|
332
|
+
// shifted out (so, for a negative, the next whole number down); $68 0 after
|
|
333
|
+
qint() {
|
|
334
|
+
if (!this.r(FAC)) { for (const r of [0x62, 0x63, 0x64, 0x65]) this.w(r, 0); return { a: 0, y: 0 }; }
|
|
335
|
+
const a = (this.r(FAC) - 0xa0) & 0xff;
|
|
336
|
+
if (this.r(0x66) & 0x80) { this.w(BITS, 0xff); this.complement(); }
|
|
337
|
+
if (((a - 0xf9) & 0xff) & 0x80) this.shift(FAC, a, 0);
|
|
338
|
+
else {
|
|
339
|
+
// (under a byte: the first shift brings the sign in at the top, the rest keep it)
|
|
340
|
+
const m = this.mant(0x62);
|
|
341
|
+
this.setMant(0x62, Math.floor(m / 2) + ((this.r(0x66) & 0x80) ? 2 ** 31 : 0));
|
|
342
|
+
this.bitShift(FAC, (a + 1) & 0xff, 0, true);
|
|
343
|
+
}
|
|
344
|
+
this.w(BITS, 0);
|
|
345
|
+
return { y: 0 };
|
|
346
|
+
}
|
|
347
|
+
// INT ($BCCC): the FAC's whole part, the next whole number down, as a number again
|
|
348
|
+
int() {
|
|
349
|
+
if (this.r(FAC) >= 0xa0) return { a: this.r(FAC) };
|
|
350
|
+
this.qint();
|
|
351
|
+
this.w(FACOV, 0);
|
|
352
|
+
const s = this.r(0x66); this.w(0x66, 0);
|
|
353
|
+
return this.float32(s);
|
|
354
|
+
}
|
|
355
|
+
// ($BCDB) a 32-bit two's complement whole number in $62 to $65 as a number: negative when A's top
|
|
356
|
+
// bit is set, as INT and Blitz! enter it; $07 its lowest byte, as the ROM keeps it
|
|
357
|
+
float32(a) {
|
|
358
|
+
this.w(FAC, 0xa0); this.w(0x07, this.r(0x65));
|
|
359
|
+
if (a & 0x80) this.negate();
|
|
360
|
+
return this.normal();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// (the methods for building a number from its digits, FIN's: basic-rom.js)
|
|
365
|
+
Object.assign(Floats.prototype, {
|
|
366
|
+
// MUL10 ($BAE2): the FAC ten times, as the ROM does it: ARG the FAC (rounded), then ($BAED) with
|
|
367
|
+
// the exponent two more: five times it, doubled
|
|
368
|
+
mul10() {
|
|
369
|
+
this.movaf();
|
|
370
|
+
const e = this.r(FAC);
|
|
371
|
+
if (!e) return { a: 0 };
|
|
372
|
+
if (e + 2 > 255) throw overflow();
|
|
373
|
+
return this.addDoubled(e + 2);
|
|
374
|
+
},
|
|
375
|
+
// ($BAED) ARG added to the FAC with its exponent `a`, then doubled: FADDT entered at $B877 as if
|
|
376
|
+
// ARG's exponent were `a` (one or two more than its own: ARG the FAC, it is three or five times
|
|
377
|
+
// it), the FAC shifted right by the difference and ARG added, and the exponent one more
|
|
378
|
+
addDoubled(a) {
|
|
379
|
+
const by = (this.r(ARG) - a) & 0xff;
|
|
380
|
+
this.w(ARISGN, 0);
|
|
381
|
+
this.w(FAC, a); this.w(0x66, this.r(0x6e)); this.w(ARGOV, 0);
|
|
382
|
+
const low = this.bitShift(FAC, by, this.r(FACOV), false) + this.r(ARGOV), m = this.mant(0x62) + this.mant(0x6a) + (low > 255 ? 1 : 0);
|
|
383
|
+
this.w(FACOV, low); this.setMant(0x62, m % 2 ** 32);
|
|
384
|
+
if (m >= 2 ** 32) this.carryIn(1);
|
|
385
|
+
const e2 = this.r(FAC) + 1; if (e2 > 255) throw overflow();
|
|
386
|
+
this.w(FAC, e2);
|
|
387
|
+
return { a: e2 };
|
|
388
|
+
},
|
|
389
|
+
// DIV10 ($BAFE): the FAC a tenth: ARG the FAC (rounded), the FAC 10 (the ROM's constant at $BAF9),
|
|
390
|
+
// the signs' EOR 0, and ARG divided by it
|
|
391
|
+
div10() { this.movaf(); return this.divInto(0xbaf9, 0); },
|
|
392
|
+
// ($BB07) ARG divided by a number in memory: the signs' EOR `sign` (X), the FAC the number, FDIVT
|
|
393
|
+
divInto(at, sign) { this.w(ARISGN, sign); this.movfm(at); return this.fdivt(); },
|
|
394
|
+
// ($BC3C) a byte, 0 to 127, as a number in the FAC (a digit, for FIN)
|
|
395
|
+
byte(v) {
|
|
396
|
+
this.w(0x62, v); this.w(0x63, 0);
|
|
397
|
+
for (const at of [0x64, 0x65, FACOV, 0x66]) this.w(at, 0);
|
|
398
|
+
this.w(FAC, 0x88);
|
|
399
|
+
if (v & 0x80) this.negate();
|
|
400
|
+
return this.normal();
|
|
401
|
+
},
|
|
402
|
+
// ($BD7E) a digit added to the FAC: ARG the FAC (rounded), the FAC the digit, and FADDT
|
|
403
|
+
addDigit(d) { this.movaf(); this.byte(d); this.w(ARISGN, this.r(0x6e) ^ this.r(0x66)); return this.faddt(); },
|
|
404
|
+
// ---- the series and the functions on them ----
|
|
405
|
+
|
|
406
|
+
// POLY2 ($E059, the table's address in $71/$72): the FAC, x, kept at $5C (rounded), then from the
|
|
407
|
+
// table's count and coefficients, highest first, x times each and the next added (Horner's)
|
|
408
|
+
poly2(at) { this.w(0x71, at); this.w(0x72, at >> 8); return this.poly(); },
|
|
409
|
+
poly() {
|
|
410
|
+
this.movmf(0x5c);
|
|
411
|
+
let p = this.r(0x71) | this.r(0x72) << 8;
|
|
412
|
+
this.w(0x67, this.r(p)); p++; this.w(0x71, p); this.w(0x72, p >> 8);
|
|
413
|
+
for (let from = p; ; from = 0x5c) {
|
|
414
|
+
this.fmult(from);
|
|
415
|
+
p = (this.r(0x71) | this.r(0x72) << 8) + 5; this.w(0x71, p); this.w(0x72, p >> 8);
|
|
416
|
+
this.fadd(p);
|
|
417
|
+
this.w(0x67, this.r(0x67) - 1); if (!this.r(0x67)) return { a: this.r(FAC) };
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
// POLY1 ($E043): a series of x squared, x kept at $57, times x
|
|
421
|
+
poly1(at) { this.w(0x71, at); this.w(0x72, at >> 8); this.movmf(0x57); this.fmult(0x57); this.poly(); return this.fmult(0x57); },
|
|
422
|
+
// LOG ($B9EA): ILLEGAL QUANTITY for zero or less; the exponent kept, less 128 (SIGN's carry
|
|
423
|
+
// clear), the mantissa as a number from a half to one, and log2 of that from LOG's series of
|
|
424
|
+
// (sqrt 2 / (x + sqrt .5)), from 1; the exponent added (as $BD7E adds a digit), times log 2
|
|
425
|
+
log() {
|
|
426
|
+
if (this.sign() !== 1) throw new FloatError(0x0e);
|
|
427
|
+
const n = (this.r(FAC) - 0x80) & 0xff;
|
|
428
|
+
this.w(FAC, 0x80);
|
|
429
|
+
this.fadd(0xb9d6); this.fdiv(0xb9db); this.fsub(0xb9bc); this.poly1(0xb9c1); this.fadd(0xb9e0);
|
|
430
|
+
this.addDigit(n);
|
|
431
|
+
return this.fmult(0xb9e5);
|
|
432
|
+
},
|
|
433
|
+
// EXP ($BFED, `c` the carry it is entered with): the FAC over log 2; its low bits rounded in, a
|
|
434
|
+
// carry from $70 + $50 (and the carry FMULT leaves: clear for a product, as entered for a zero)
|
|
435
|
+
// the mantissa one more; past 2^7 an overflow, or 0 for a negative; the whole part (INT, $07
|
|
436
|
+
// its low byte) and the fraction, from the swapped FAC and ARG; EXP's series of the fraction, and
|
|
437
|
+
// 2 to the whole part, by MULDIV with that exponent ($BAB9)
|
|
438
|
+
exp(c = 0) {
|
|
439
|
+
const ex = this.r(FAC);
|
|
440
|
+
this.fmult(0xbfbf);
|
|
441
|
+
if (ex && this.r(FAC)) c = 0;
|
|
442
|
+
else if (ex) { const sum = 0x81 + ex; c = sum < 256 && (sum & 0xff) < 0x80 ? 0 : 1; }
|
|
443
|
+
const s = this.r(FACOV) + 0x50 + c;
|
|
444
|
+
if (s > 255) { const m = this.mant(0x62) + 1; this.setMant(0x62, m % 2 ** 32); if (m >= 2 ** 32) this.carryIn(1); }
|
|
445
|
+
this.w(ARGOV, s);
|
|
446
|
+
this.movef();
|
|
447
|
+
const tooBig = () => { if (!(this.r(0x66) & 0x80)) throw overflow(); return this.zero(); };
|
|
448
|
+
if (this.r(FAC) >= 0x88) return tooBig();
|
|
449
|
+
this.int();
|
|
450
|
+
const a = (this.r(0x07) + 0x81) & 0xff;
|
|
451
|
+
if (!a) return tooBig();
|
|
452
|
+
for (let k = 0; k < 6; k++) { const v = this.r(ARG + k); this.w(ARG + k, this.r(FAC + k)); this.w(FAC + k, v); }
|
|
453
|
+
this.w(FACOV, this.r(ARGOV));
|
|
454
|
+
this.fsubt(); this.negop();
|
|
455
|
+
this.poly2(0xbfc4);
|
|
456
|
+
this.w(ARISGN, 0);
|
|
457
|
+
const e = (a - 1) & 0xff;
|
|
458
|
+
if (!e) return this.zero();
|
|
459
|
+
this.muldiv(e);
|
|
460
|
+
return { a: 0 };
|
|
461
|
+
},
|
|
462
|
+
// FPWRT ($BF7B, ARG to the power of the FAC; `z` the Z flag it is entered with, set for a power
|
|
463
|
+
// of 0, `c` the carry): EXP(0) for that; 0 for a base of 0; the power kept at $4E; a negative base
|
|
464
|
+
// only to a whole power, its sign then dropped (as FCOMP's Y, 4, for the FAC's) and the power's
|
|
465
|
+
// low bit kept (in $07): EXP of the power times LOG of the base, negated for an odd power. (A
|
|
466
|
+
// power not whole is FCOMP's answer for the sign, its Y for the oddness: an ILLEGAL QUANTITY,
|
|
467
|
+
// unless INT, from the ROM's fill byte $68, made the power more.)
|
|
468
|
+
fpwrt(z, c) {
|
|
469
|
+
if (z) return this.exp(c);
|
|
470
|
+
if (!this.r(ARG)) { this.w(FAC, 0); this.w(0x66, 0); return { a: 0 }; }
|
|
471
|
+
this.movmf(0x4e);
|
|
472
|
+
let sign = this.r(0x6e), odd = 0;
|
|
473
|
+
if (sign & 0x80) {
|
|
474
|
+
this.int();
|
|
475
|
+
const { a, y } = this.compare(0x4e);
|
|
476
|
+
if (a) { sign = a; odd = y & 1; } else { sign = y; odd = this.r(0x07) & 1; }
|
|
477
|
+
}
|
|
478
|
+
this.w(0x66, sign); for (let i = 5; i >= 1; i--) this.w(0x60 + i, this.r(0x68 + i)); this.w(FACOV, 0);
|
|
479
|
+
this.log(); this.fmult(0x4e);
|
|
480
|
+
this.exp(0);
|
|
481
|
+
if (odd && this.r(FAC)) this.w(0x66, this.r(0x66) ^ 0xff);
|
|
482
|
+
return { a: this.r(FAC) };
|
|
483
|
+
},
|
|
484
|
+
// SIN ($E26B): the FAC over two pi; its fraction, from a quarter, and folded (a negative a half
|
|
485
|
+
// on, and if then positive, the TAN's flag at $12 turned over), a quarter on, its sign back; and
|
|
486
|
+
// SIN's series. COS ($E264): a half pi on, then SIN.
|
|
487
|
+
sin() {
|
|
488
|
+
this.movaf(); this.divInto(0xe2e5, this.r(0x6e));
|
|
489
|
+
this.movaf(); this.int(); this.w(ARISGN, 0); this.fsubt(); this.fsub(0xe2ea);
|
|
490
|
+
const s = this.r(0x66);
|
|
491
|
+
if (!(s & 0x80)) this.negop();
|
|
492
|
+
else { this.fadd(0xbf11); if (!(this.r(0x66) & 0x80)) { this.w(0x12, this.r(0x12) ^ 0xff); this.negop(); } }
|
|
493
|
+
this.fadd(0xe2ea);
|
|
494
|
+
if (s & 0x80) this.negop();
|
|
495
|
+
return this.poly1(0xe2ef);
|
|
496
|
+
},
|
|
497
|
+
cos() { this.fadd(0xe2e0); return this.sin(); },
|
|
498
|
+
// FOUT ($BDDD): the FAC as text at $0100, a zero after, as Microsoft's FOUT writes it (its
|
|
499
|
+
// source's steps, BASIC-M6502): its sign or a space at $0100; the number brought
|
|
500
|
+
// between 99999999.9 and 999999999 by tens, $5D counting them; a half added, as a whole number;
|
|
501
|
+
// its nine digits counted out by adding the powers of ten, each in turn, the point placed among
|
|
502
|
+
// them; the zeros at the end taken off, and the exponent ($5E) last. $71 where it writes, $47
|
|
503
|
+
// where it is in the table. A and Y the text's address.
|
|
504
|
+
fout() {
|
|
505
|
+
const buf = (y, v) => this.w(0xff + y, v);
|
|
506
|
+
let y = 1;
|
|
507
|
+
buf(y, this.r(0x66) & 0x80 ? 0x2d : 0x20);
|
|
508
|
+
this.w(0x66, this.r(0xff + y)); this.w(0x71, y); y++;
|
|
509
|
+
if (!this.r(FAC)) { buf(y, 0x30); buf(y + 1, 0); return { a: 0x00, y: 0x01 }; }
|
|
510
|
+
let a = 0;
|
|
511
|
+
if (this.r(FAC) <= 0x80) { this.fmult(0xbdbd); a = 0xf7; }
|
|
512
|
+
this.w(0x5d, a);
|
|
513
|
+
// (between the bounds: smaller by tens, or bigger; the half not added when it is exactly the top)
|
|
514
|
+
let step = 'FOUT4';
|
|
515
|
+
for (;;) {
|
|
516
|
+
if (step === 'FOUT4') {
|
|
517
|
+
const c = this.fcomp(0xbdb8);
|
|
518
|
+
if (c === 0) { step = 'BIGGES'; break; }
|
|
519
|
+
step = c === 1 ? 'FOUT9' : 'FOUT3';
|
|
520
|
+
} else if (step === 'FOUT3') {
|
|
521
|
+
const c = this.fcomp(0xbdb3);
|
|
522
|
+
if (c === 1) { step = 'FOUT5'; break; }
|
|
523
|
+
this.mul10(); this.w(0x5d, (this.r(0x5d) - 1) & 0xff);
|
|
524
|
+
step = this.r(0x5d) ? 'FOUT3' : 'FOUT9';
|
|
525
|
+
} else {
|
|
526
|
+
this.div10(); this.w(0x5d, (this.r(0x5d) + 1) & 0xff);
|
|
527
|
+
if (this.r(0x5d)) step = 'FOUT4'; else { step = 'FOUT5'; break; }
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (step === 'FOUT5') this.fadd(0xbf11);
|
|
531
|
+
this.qint();
|
|
532
|
+
// the form: E notation below 0.01 or from 1E9 ($5E the exponent), else the places before the point
|
|
533
|
+
let x = 1, e;
|
|
534
|
+
a = (this.r(0x5d) + 10) & 0xff;
|
|
535
|
+
if (a & 0x80) e = a - 2;
|
|
536
|
+
else if (a >= 11) e = a - 2;
|
|
537
|
+
else { x = (a - 1) & 0xff; e = 0; }
|
|
538
|
+
this.w(0x5e, e & 0xff); this.w(0x5d, x);
|
|
539
|
+
if (x === 0 || x & 0x80) {
|
|
540
|
+
y = this.r(0x71); y++; buf(y, 0x2e);
|
|
541
|
+
if (x) { y++; buf(y, 0x30); }
|
|
542
|
+
this.w(0x71, y);
|
|
543
|
+
}
|
|
544
|
+
// each digit: a power of ten added, or taken away, until the mantissa's sign turns
|
|
545
|
+
y = 0; x = 0x80;
|
|
546
|
+
for (;;) {
|
|
547
|
+
let c = 0;
|
|
548
|
+
for (const k of [3, 2, 1, 0]) { const v = this.r(0x62 + k) + this.r(0xbf16 + y + k) + c; this.w(0x62 + k, v & 0xff); c = v >> 8; }
|
|
549
|
+
x = (x + 1) & 0xff;
|
|
550
|
+
if (c ? x & 0x80 : !(x & 0x80)) continue;
|
|
551
|
+
a = x;
|
|
552
|
+
if (c) { const v = (a ^ 0xff) + 0x0a + 1; a = v & 0xff; c = v >> 8; }
|
|
553
|
+
a = (a + 0x2f + c) & 0xff;
|
|
554
|
+
y += 4; this.w(0x47, y);
|
|
555
|
+
y = this.r(0x71) + 1; x = a;
|
|
556
|
+
buf(y, a & 0x7f);
|
|
557
|
+
this.w(0x5d, (this.r(0x5d) - 1) & 0xff);
|
|
558
|
+
if (!this.r(0x5d)) { y++; buf(y, 0x2e); }
|
|
559
|
+
this.w(0x71, y);
|
|
560
|
+
y = this.r(0x47);
|
|
561
|
+
x = (x ^ 0xff) & 0x80;
|
|
562
|
+
if (y === 0x24 || y === 0x3c) break;
|
|
563
|
+
}
|
|
564
|
+
// the zeros at the end off, and a point left last; then the exponent
|
|
565
|
+
y = this.r(0x71);
|
|
566
|
+
let ch; do { ch = this.r(0xff + y); y--; } while (ch === 0x30);
|
|
567
|
+
if (ch !== 0x2e) y++;
|
|
568
|
+
let ex = this.r(0x5e);
|
|
569
|
+
if (!ex) { buf(y + 1, 0); return { a: 0x00, y: 0x01 }; }
|
|
570
|
+
let sign = 0x2b;
|
|
571
|
+
if (ex & 0x80) { ex = (0 - ex) & 0xff; sign = 0x2d; }
|
|
572
|
+
buf(y + 2, sign); buf(y + 1, 0x45);
|
|
573
|
+
let hi = 0x2f, c = 1;
|
|
574
|
+
do { hi++; const t = ex - 10 - (1 - c); c = t >= 0 ? 1 : 0; ex = t & 0xff; } while (c);
|
|
575
|
+
buf(y + 4, (ex + 0x3a) & 0xff); buf(y + 3, hi); buf(y + 5, 0);
|
|
576
|
+
return { a: 0x00, y: 0x01 };
|
|
577
|
+
},
|
|
578
|
+
// NEGOP ($BFB4): the FAC's sign turned over, unless it is zero
|
|
579
|
+
negop() { if (this.r(FAC)) this.w(0x66, this.r(0x66) ^ 0xff); return { a: this.r(0x66) }; },
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
// an overflow, and BASIC's errors by their number in its table (X for its error routine, $A437)
|
|
583
|
+
export class FloatError extends Error { constructor(x) { super('BASIC error ' + x); this.x = x; } }
|
|
584
|
+
const overflow = () => new FloatError(0x0f);
|
package/glyph-hash.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// A character's eight bytes (its rows, the left dot the top bit) as a 32-bit FNV-1a hash: how
|
|
2
|
+
// glyph-hashes.js keeps the 64's characters, and how a cell of a picture is recognised as one of
|
|
3
|
+
// them.
|
|
4
|
+
export function glyphHash(rows) {
|
|
5
|
+
let h = 0x811c9dc5;
|
|
6
|
+
for (let i = 0; i < 8; i++) { h ^= rows[i]; h = Math.imul(h, 0x01000193) >>> 0; }
|
|
7
|
+
return h;
|
|
8
|
+
}
|
package/glyph-hashes.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// The 64's 512 characters as hashes: the character ROM's first set (screen codes 0 to 255, the last 128
|
|
2
|
+
// reversed), then its second, each a 32-bit FNV-1a hash of its eight bytes (glyph-hash.js).
|
|
3
|
+
// Generated from Commodore's character ROM; numbers only, no character can be drawn from them.
|
|
4
|
+
// Not edited by hand.
|
|
5
|
+
export const GLYPH_HASHES = Uint32Array.of(
|
|
6
|
+
0xa63acec9, 0xcfb72db7, 0xc1892941, 0xd80b2795, 0x1b055853, 0x733925ed, 0xb76faaf3, 0x94217edd,
|
|
7
|
+
0xe71b097b, 0xcc9f3afd, 0xb1af50c7, 0x6f9d9a15, 0x61fb6d9b, 0x464b312c, 0x85f6570b, 0x96070b83,
|
|
8
|
+
0xb876df6d, 0xbdd92233, 0x5295e507, 0x32cadce7, 0x4dd2b86b, 0xe0a58b69, 0xb909704f, 0x0a4ca684,
|
|
9
|
+
0x090b885d, 0x391a859f, 0x1efef677, 0x3b8708c5, 0x2401ada1, 0xd655d051, 0xb19651ef, 0xeb166297,
|
|
10
|
+
0x9be17165, 0xccb1980d, 0x760cedb3, 0x63e18eff, 0x0294278d, 0xa3ebed65, 0x49817fb1, 0xf278feff,
|
|
11
|
+
0x987ef385, 0x52c0c411, 0x99e73ab0, 0xbed544ab, 0x730351a5, 0x93a47d1b, 0x63033875, 0x2dc56d46,
|
|
12
|
+
0xf5f3b30b, 0xbe43f89b, 0xae016c73, 0x52bd07e1, 0xb6f6f3fe, 0xff3909cd, 0x557295c7, 0xade16941,
|
|
13
|
+
0xf0fb1a01, 0x0161eb3b, 0x64320035, 0x8445ba4d, 0x710653c5, 0x0c491265, 0xd1adc673, 0xb34aabed,
|
|
14
|
+
0xb700ca17, 0x22397953, 0x3c19df65, 0xb700ca17, 0xbe9abf9b, 0xfe689467, 0xc712d72b, 0x0ff37de5,
|
|
15
|
+
0xefec4d45, 0x931ca24d, 0x62103ab3, 0xfa82e19d, 0x49efc6bb, 0xa706e569, 0xcc6ef1e5, 0x084b2e8b,
|
|
16
|
+
0xa637d02d, 0xf466251d, 0xfd160a47, 0xfbed4f22, 0x1e3ba9e5, 0x03bc99db, 0x937537f9, 0x8c73847d,
|
|
17
|
+
0x1df09969, 0x5d6969e5, 0x05f85cb4, 0xee667567, 0xa3db00e5, 0x3c19df65, 0xd5bf523e, 0xf607d477,
|
|
18
|
+
0x9be17165, 0x99f161e5, 0xb4512201, 0xe802ca7a, 0x4ce28808, 0x58e31265, 0xb2c0a219, 0x461f842d,
|
|
19
|
+
0x1d055413, 0xd33f53e4, 0x461f842d, 0xc051b967, 0x581deaa1, 0x1e2ddc7f, 0x36f8d78d, 0x4c4ff03b,
|
|
20
|
+
0xa300624f, 0x4c42987f, 0x34dd1f8f, 0x6a2e9b65, 0x58e31265, 0xf5528fe5, 0x7e413aad, 0x2505bf8b,
|
|
21
|
+
0x292df97c, 0x02147f26, 0xcaefc185, 0x6044a0a5, 0x940b4261, 0x099fd55d, 0xe0455ea5, 0x9c81d7e1,
|
|
22
|
+
0xd4b39b79, 0xdc8da58b, 0x538ca0cd, 0xfc193485, 0x0d506237, 0x64e1ab41, 0x64f248cb, 0xd27b4ccd,
|
|
23
|
+
0xf43f3a73, 0xd240f335, 0x9ae62617, 0x432d4101, 0xf2c122cf, 0xc379e674, 0x84d795e3, 0xb34cb6f7,
|
|
24
|
+
0xd3fcf7ad, 0xd6841827, 0x112d3413, 0x9c712aff, 0xc43b5f63, 0xa22875e5, 0x1fb4d0cf, 0x20c02064,
|
|
25
|
+
0xbf7a9239, 0x9d5ca57f, 0xf0de6067, 0xbb7ae8dd, 0xc45b03c9, 0xe2b4e829, 0x426d54d7, 0xa759948f,
|
|
26
|
+
0x6cae0a5d, 0xeae53e95, 0x8f14b49b, 0xaf792ac7, 0x9a0280a1, 0x5f6a9421, 0x0f9d9745, 0x30f21787,
|
|
27
|
+
0xc87f5d8d, 0xf48ead51, 0x5b0c5cb8, 0x27bf827b, 0xf389198d, 0x24c7045b, 0x038932bd, 0xf4b2cc5e,
|
|
28
|
+
0xdcb56f9f, 0x353848ef, 0xe4e4cf5f, 0xf2a5976d, 0xd84a1f36, 0x8f42ce19, 0xe6e0319f, 0xcc9d583d,
|
|
29
|
+
0x6061d50d, 0x74a966cf, 0x618c4ead, 0x31c82d45, 0xc7adac99, 0x581a4af1, 0x798d5a93, 0xd29d4d6d,
|
|
30
|
+
0xe319f30f, 0xdbab8ea3, 0x5383376d, 0xe319f30f, 0xdf630227, 0xa95eb15f, 0xcd40fc97, 0xd6d6ff3d,
|
|
31
|
+
0xe9c4fd6d, 0x1df55185, 0x1ba82113, 0x5d3b77b5, 0x26170287, 0xe26129c9, 0xc075e735, 0xbc1077b7,
|
|
32
|
+
0x74c89f8d, 0xe8063525, 0xcea2193f, 0xf8fc836a, 0x12c5d25d, 0x9a04a2db, 0x8702da71, 0xcd17a8c5,
|
|
33
|
+
0xdab3d03d, 0xd23a719d, 0x4f6b1528, 0xf706ca4f, 0xcfd5ec5d, 0x5383376d, 0xd9381ec2, 0xc813957f,
|
|
34
|
+
0x6cae0a5d, 0x40a3e13d, 0x84bf99c1, 0x35f2d172, 0xcdaea310, 0x6ac9fbdd, 0xf6f39359, 0x3b0bc5c5,
|
|
35
|
+
0x929224ef, 0xa650b140, 0x3b0bc5c5, 0x24bfc80f, 0x6905c661, 0xe6460697, 0x67b30275, 0xe75cdb87,
|
|
36
|
+
0xe444ee97, 0xb889e2d7, 0x3f4d1217, 0x1f6cbaad, 0x6ac9fbdd, 0xf2c9775d, 0x273093e5, 0xac1d7c37,
|
|
37
|
+
0x7eec1c74, 0x83a3362e, 0x78676dcd, 0x3158389d, 0xedfde341, 0xa33c8f45, 0x255796fd, 0x21af5301,
|
|
38
|
+
0xa63acec9, 0x673c3eb9, 0xca581dfd, 0x9d3e9be5, 0x33baa295, 0x2d1fe45d, 0xcc4cbbd5, 0x285415f7,
|
|
39
|
+
0xe6493f87, 0xab3ae759, 0x72d77a37, 0xae73670b, 0x7674dbb1, 0xed13a8c7, 0x57ca9f29, 0xd280ad53,
|
|
40
|
+
0xf4a4184d, 0x9254bcd5, 0x9dd85cef, 0x6199a3dd, 0xb581d065, 0xab4edadb, 0x2a68d62f, 0x734fcf38,
|
|
41
|
+
0x766f48fd, 0x982b9639, 0x32bbe4f9, 0x3b8708c5, 0x2401ada1, 0xd655d051, 0xb19651ef, 0xeb166297,
|
|
42
|
+
0x9be17165, 0xccb1980d, 0x760cedb3, 0x63e18eff, 0x0294278d, 0xa3ebed65, 0x49817fb1, 0xf278feff,
|
|
43
|
+
0x987ef385, 0x52c0c411, 0x99e73ab0, 0xbed544ab, 0x730351a5, 0x93a47d1b, 0x63033875, 0x2dc56d46,
|
|
44
|
+
0xf5f3b30b, 0xbe43f89b, 0xae016c73, 0x52bd07e1, 0xb6f6f3fe, 0xff3909cd, 0x557295c7, 0xade16941,
|
|
45
|
+
0xf0fb1a01, 0x0161eb3b, 0x64320035, 0x8445ba4d, 0x710653c5, 0x0c491265, 0xd1adc673, 0xb34aabed,
|
|
46
|
+
0xb700ca17, 0xcfb72db7, 0xc1892941, 0xd80b2795, 0x1b055853, 0x733925ed, 0xb76faaf3, 0x94217edd,
|
|
47
|
+
0xe71b097b, 0xcc9f3afd, 0xb1af50c7, 0x6f9d9a15, 0x61fb6d9b, 0x464b312c, 0x85f6570b, 0x96070b83,
|
|
48
|
+
0xb876df6d, 0xbdd92233, 0x5295e507, 0x32cadce7, 0x4dd2b86b, 0xe0a58b69, 0xb909704f, 0x0a4ca684,
|
|
49
|
+
0x090b885d, 0x391a859f, 0x1efef677, 0xee667567, 0xa3db00e5, 0x3c19df65, 0xf6f39359, 0x9fa87cb9,
|
|
50
|
+
0x9be17165, 0x99f161e5, 0xb4512201, 0xe802ca7a, 0x4ce28808, 0x58e31265, 0xb2c0a219, 0x461f842d,
|
|
51
|
+
0x1d055413, 0x3f02d529, 0x461f842d, 0xc051b967, 0x581deaa1, 0x1e2ddc7f, 0x36f8d78d, 0x4c4ff03b,
|
|
52
|
+
0xa300624f, 0x4c42987f, 0x34dd1f8f, 0x6a2e9b65, 0x58e31265, 0xf5528fe5, 0x7e413aad, 0x2505bf8b,
|
|
53
|
+
0x292df97c, 0x02147f26, 0xef56af2f, 0x6044a0a5, 0x940b4261, 0x099fd55d, 0xe0455ea5, 0x9c81d7e1,
|
|
54
|
+
0xd4b39b79, 0x714f19f9, 0xab5825e9, 0x2e72d0e5, 0xefc916bd, 0xf026a62d, 0x7999cad1, 0xd1c47a1b,
|
|
55
|
+
0xab98cd3f, 0x4ab842e1, 0x8225944b, 0x7d050287, 0x00942f29, 0xbba2f25b, 0xfebe3ab9, 0xbfcf382f,
|
|
56
|
+
0x8ef2c8cd, 0x9bc42519, 0xc873ae67, 0x4eadea0d, 0xbeea2dd5, 0x9f665097, 0x7c4d047b, 0x850c239c,
|
|
57
|
+
0x42907bdd, 0x2b960839, 0xed57b699, 0xbb7ae8dd, 0xc45b03c9, 0xe2b4e829, 0x426d54d7, 0xa759948f,
|
|
58
|
+
0x6cae0a5d, 0xeae53e95, 0x8f14b49b, 0xaf792ac7, 0x9a0280a1, 0x5f6a9421, 0x0f9d9745, 0x30f21787,
|
|
59
|
+
0xc87f5d8d, 0xf48ead51, 0x5b0c5cb8, 0x27bf827b, 0xf389198d, 0x24c7045b, 0x038932bd, 0xf4b2cc5e,
|
|
60
|
+
0xdcb56f9f, 0x353848ef, 0xe4e4cf5f, 0xf2a5976d, 0xd84a1f36, 0x8f42ce19, 0xe6e0319f, 0xcc9d583d,
|
|
61
|
+
0x6061d50d, 0x74a966cf, 0x618c4ead, 0x31c82d45, 0xc7adac99, 0x581a4af1, 0x798d5a93, 0xd29d4d6d,
|
|
62
|
+
0xe319f30f, 0xdc8da58b, 0x538ca0cd, 0xfc193485, 0x0d506237, 0x64e1ab41, 0x64f248cb, 0xd27b4ccd,
|
|
63
|
+
0xf43f3a73, 0xd240f335, 0x9ae62617, 0x432d4101, 0xf2c122cf, 0xc379e674, 0x84d795e3, 0xb34cb6f7,
|
|
64
|
+
0xd3fcf7ad, 0xd6841827, 0x112d3413, 0x9c712aff, 0xc43b5f63, 0xa22875e5, 0x1fb4d0cf, 0x20c02064,
|
|
65
|
+
0xbf7a9239, 0x9d5ca57f, 0xf0de6067, 0xf706ca4f, 0xcfd5ec5d, 0x5383376d, 0xb2c0a219, 0xd4d06f39,
|
|
66
|
+
0x6cae0a5d, 0x40a3e13d, 0x84bf99c1, 0x35f2d172, 0xcdaea310, 0x6ac9fbdd, 0xf6f39359, 0x3b0bc5c5,
|
|
67
|
+
0x929224ef, 0x402c8be1, 0x3b0bc5c5, 0x24bfc80f, 0x6905c661, 0xe6460697, 0x67b30275, 0xe75cdb87,
|
|
68
|
+
0xe444ee97, 0xb889e2d7, 0x3f4d1217, 0x1f6cbaad, 0x6ac9fbdd, 0xf2c9775d, 0x273093e5, 0xac1d7c37,
|
|
69
|
+
0x7eec1c74, 0x83a3362e, 0x86ee680f, 0x3158389d, 0xedfde341, 0xa33c8f45, 0x255796fd, 0x21af5301,
|
|
70
|
+
);
|