@frotzinc/zmachine 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,151 @@
1
+ const fourCC = (s) => [...s].map(c => c.charCodeAt(0));
2
+ function chunk(type, data) {
3
+ const n = data.length;
4
+ const out = [...fourCC(type), (n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff, ...Array.from(data)];
5
+ if (n % 2)
6
+ out.push(0); // chunks are padded to an even length (IFF)
7
+ return out;
8
+ }
9
+ /** Dynamic memory XORed with the original, zero runs as `0, n − 1` (n ≤ 256), trailing zeros left out. */
10
+ export function compressMemory(dynamic, original) {
11
+ const out = [];
12
+ let zeros = 0;
13
+ for (let i = 0; i < dynamic.length; i++) {
14
+ const b = dynamic[i] ^ original[i];
15
+ if (b === 0) {
16
+ if (++zeros === 256) {
17
+ out.push(0, 255);
18
+ zeros = 0;
19
+ }
20
+ }
21
+ else {
22
+ if (zeros) {
23
+ out.push(0, zeros - 1);
24
+ zeros = 0;
25
+ }
26
+ out.push(b);
27
+ }
28
+ }
29
+ return out;
30
+ }
31
+ export function decompressMemory(data, original) {
32
+ const out = original.slice();
33
+ let j = 0;
34
+ for (let i = 0; i < data.length;) {
35
+ const b = data[i++];
36
+ if (b === 0)
37
+ j += 1 + (data[i++] ?? 0);
38
+ else {
39
+ if (j >= out.length)
40
+ throw new Error('its memory runs past the end of dynamic memory');
41
+ out[j] = b ^ original[j];
42
+ j++;
43
+ }
44
+ }
45
+ if (j > out.length)
46
+ throw new Error('its memory runs past the end of dynamic memory');
47
+ return out;
48
+ }
49
+ function stacks(frames) {
50
+ const out = [];
51
+ frames.forEach((f, i) => {
52
+ // The first frame is the dummy one for the main routine (§Quetzal 4.11): no locals, no result.
53
+ const discard = i > 0 && f.storeVariable === null;
54
+ out.push((f.returnPC >> 16) & 0xff, (f.returnPC >> 8) & 0xff, f.returnPC & 0xff);
55
+ out.push((f.locals.length & 0x0f) | (discard ? 0x10 : 0));
56
+ out.push(f.storeVariable ?? 0);
57
+ out.push((1 << Math.min(f.argCount, 7)) - 1);
58
+ out.push((f.evalStack.length >> 8) & 0xff, f.evalStack.length & 0xff);
59
+ for (const w of [...f.locals, ...f.evalStack])
60
+ out.push((w >> 8) & 0xff, w & 0xff);
61
+ });
62
+ return out;
63
+ }
64
+ function readStacks(data) {
65
+ const frames = [];
66
+ const word = (i) => (data[i] << 8) | data[i + 1];
67
+ for (let i = 0; i < data.length;) {
68
+ if (i + 8 > data.length)
69
+ throw new Error('its stack chunk ends in the middle of a frame');
70
+ const returnPC = (data[i] << 16) | (data[i + 1] << 8) | data[i + 2];
71
+ const flags = data[i + 3];
72
+ const variable = data[i + 4];
73
+ const args = data[i + 5];
74
+ const evalCount = word(i + 6);
75
+ const nLocals = flags & 0x0f;
76
+ i += 8;
77
+ if (i + (nLocals + evalCount) * 2 > data.length)
78
+ throw new Error('its stack chunk ends in the middle of a frame');
79
+ const locals = [];
80
+ for (let k = 0; k < nLocals; k++, i += 2)
81
+ locals.push(word(i));
82
+ const evalStack = [];
83
+ for (let k = 0; k < evalCount; k++, i += 2)
84
+ evalStack.push(word(i));
85
+ let argCount = 0;
86
+ while (argCount < 7 && args & (1 << argCount))
87
+ argCount++;
88
+ frames.push({ returnPC, locals, evalStack, storeVariable: frames.length === 0 || flags & 0x10 ? null : variable, argCount });
89
+ }
90
+ if (!frames.length)
91
+ throw new Error('its stack chunk is empty');
92
+ return frames;
93
+ }
94
+ /** A Quetzal file for this state. */
95
+ export function writeQuetzal(s, original) {
96
+ const ifhd = [(s.release >> 8) & 0xff, s.release & 0xff, ...s.serial, (s.checksum >> 8) & 0xff, s.checksum & 0xff, (s.pc >> 16) & 0xff, (s.pc >> 8) & 0xff, s.pc & 0xff];
97
+ const body = [...fourCC('IFZS'), ...chunk('IFhd', ifhd), ...chunk('CMem', compressMemory(s.dynamic, original)), ...chunk('Stks', stacks(s.frames))];
98
+ const n = body.length;
99
+ return Uint8Array.from([...fourCC('FORM'), (n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff, ...body]);
100
+ }
101
+ /** Read a Quetzal file; `original` is the story's dynamic memory as it starts. Throws an Error that says what's wrong, in words. */
102
+ export function readQuetzal(bytes, original) {
103
+ const str = (i) => String.fromCharCode(bytes[i], bytes[i + 1], bytes[i + 2], bytes[i + 3]);
104
+ const u32 = (i) => ((bytes[i] << 24) | (bytes[i + 1] << 16) | (bytes[i + 2] << 8) | bytes[i + 3]) >>> 0;
105
+ if (bytes.length < 12 || str(0) !== 'FORM' || str(8) !== 'IFZS')
106
+ throw new Error('this isn’t a Z-machine save file (Quetzal)');
107
+ const end = Math.min(bytes.length, 8 + u32(4));
108
+ let ifhd = null;
109
+ let memory = null;
110
+ let compressed = true;
111
+ let stks = null;
112
+ for (let i = 12; i + 8 <= end;) {
113
+ const type = str(i);
114
+ const n = u32(i + 4);
115
+ const data = bytes.subarray(i + 8, i + 8 + n);
116
+ if (data.length < n)
117
+ throw new Error('the file is cut short');
118
+ if (type === 'IFhd')
119
+ ifhd = data;
120
+ else if (type === 'CMem' || type === 'UMem') {
121
+ memory = data;
122
+ compressed = type === 'CMem';
123
+ }
124
+ else if (type === 'Stks')
125
+ stks = data;
126
+ i += 8 + n + (n % 2);
127
+ }
128
+ if (!ifhd || ifhd.length < 13)
129
+ throw new Error('it has no story header (IFhd)');
130
+ if (!memory)
131
+ throw new Error('it has no memory (CMem or UMem)');
132
+ if (!stks)
133
+ throw new Error('it has no stack (Stks)');
134
+ let dynamic;
135
+ if (compressed)
136
+ dynamic = decompressMemory(memory, original);
137
+ else {
138
+ if (memory.length !== original.length)
139
+ throw new Error('its memory is the wrong size for this story');
140
+ dynamic = memory.slice();
141
+ }
142
+ return {
143
+ release: (ifhd[0] << 8) | ifhd[1],
144
+ serial: ifhd.slice(2, 8),
145
+ checksum: (ifhd[8] << 8) | ifhd[9],
146
+ pc: (ifhd[10] << 16) | (ifhd[11] << 8) | ifhd[12],
147
+ dynamic,
148
+ frames: readStacks(stks),
149
+ };
150
+ }
151
+ //# sourceMappingURL=quetzal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quetzal.js","sourceRoot":"","sources":["../src/quetzal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @random (§2.4, §15), matching ZVM so the differential tests can compare stories that use it
3
+ * (docs/11 §4). ZVM's two modes:
4
+ *
5
+ * - xorshift seed 0 ("pure randomness"): `1 + ((Math.random() * range) | 0)`. Here the draw comes
6
+ * from the session's mulberry32 stream instead, and the test oracle feeds ZVM the same stream.
7
+ * - after `@random -n`: a 32-bit xorshift seeded with n (negative, as ZVM stores it), with signed
8
+ * shifts, `1 + ((s & 0x7FFF) % range)`. ZVM: ifvms.js zvm/runtime.js random() (MIT, Dannii
9
+ * Willis); the algorithm is copied here exactly.
10
+ *
11
+ * ZVM does not implement the Standard's recommended counting sequence for seeds under 1000
12
+ * (§2.4.1); neither does this, by the same reasoning (README, conformance notes).
13
+ */
14
+ import type { RandomState } from './types.js';
15
+ /** mulberry32 (Tommy Ettinger, public domain): one step from `state`; returns [value in [0, 1), next state]. */
16
+ export declare function mulberry32Step(state: number): [number, number];
17
+ /** A mulberry32 stream as a function, like Math.random (for the test oracle and tests). */
18
+ export declare function mulberry32(seed: number): () => number;
19
+ export declare function initialRandom(seed: number): RandomState;
20
+ /**
21
+ * @random range, with `range` as a signed 16-bit number. Positive: a number from 1 to range.
22
+ * Zero or negative: reseed (0: back to the session stream; -n: xorshift from -n) and return 0.
23
+ */
24
+ export declare function random(state: RandomState, range: number): number;
package/dist/random.js ADDED
@@ -0,0 +1,42 @@
1
+ /** mulberry32 (Tommy Ettinger, public domain): one step from `state`; returns [value in [0, 1), next state]. */
2
+ export function mulberry32Step(state) {
3
+ const a = (state + 0x6d2b79f5) | 0;
4
+ let t = Math.imul(a ^ (a >>> 15), 1 | a);
5
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
6
+ return [((t ^ (t >>> 14)) >>> 0) / 4294967296, a];
7
+ }
8
+ /** A mulberry32 stream as a function, like Math.random (for the test oracle and tests). */
9
+ export function mulberry32(seed) {
10
+ let s = seed | 0;
11
+ return () => {
12
+ const [v, next] = mulberry32Step(s);
13
+ s = next;
14
+ return v;
15
+ };
16
+ }
17
+ export function initialRandom(seed) {
18
+ return { mode: 'session', session: seed | 0, xorshift: 0 };
19
+ }
20
+ /**
21
+ * @random range, with `range` as a signed 16-bit number. Positive: a number from 1 to range.
22
+ * Zero or negative: reseed (0: back to the session stream; -n: xorshift from -n) and return 0.
23
+ */
24
+ export function random(state, range) {
25
+ if (range < 1) {
26
+ state.xorshift = range;
27
+ state.mode = range === 0 ? 'session' : 'xorshift';
28
+ return 0;
29
+ }
30
+ if (state.mode === 'session') {
31
+ const [v, next] = mulberry32Step(state.session);
32
+ state.session = next;
33
+ return (1 + v * range) | 0;
34
+ }
35
+ let seed = state.xorshift;
36
+ seed ^= seed << 13;
37
+ seed ^= seed >> 17;
38
+ seed ^= seed << 5;
39
+ state.xorshift = seed;
40
+ return 1 + ((seed & 0x7fff) % range);
41
+ }
42
+ //# sourceMappingURL=random.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"random.js","sourceRoot":"","sources":["../src/random.ts"],"names":[],"mappings":"AAeA,gHAAgH;AAChH,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAA;IAClC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;IACxC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IAC9C,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,UAAU,EAAE,CAAC,CAAC,CAAA;AACnD,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,CAAA;IAChB,OAAO,GAAG,EAAE;QACV,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAA;QACnC,CAAC,GAAG,IAAI,CAAA;QACR,OAAO,CAAC,CAAA;IACV,CAAC,CAAA;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,GAAG,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAA;AAC5D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,MAAM,CAAC,KAAkB,EAAE,KAAa;IACtD,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACd,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAA;QACtB,KAAK,CAAC,IAAI,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAA;QACjD,OAAO,CAAC,CAAA;IACV,CAAC;IACD,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QAC/C,KAAK,CAAC,OAAO,GAAG,IAAI,CAAA;QACpB,OAAO,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;IAC5B,CAAC;IACD,IAAI,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAA;IACzB,IAAI,IAAI,IAAI,IAAI,EAAE,CAAA;IAClB,IAAI,IAAI,IAAI,IAAI,EAAE,CAAA;IAClB,IAAI,IAAI,IAAI,IAAI,CAAC,CAAA;IACjB,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAA;IACrB,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,GAAG,MAAM,CAAC,GAAG,KAAK,CAAC,CAAA;AACtC,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The screen model (§8), as data: a lower window of styled runs and an upper window that is a
3
+ * character grid with a cursor. Nothing here draws; the player (`@frotzinc/player`) does, and the
4
+ * Node runner reads the lower window's text.
5
+ */
6
+ import type { ScreenModel } from './types.js';
7
+ export declare function initialScreen(width: number, height: number): ScreenModel;
8
+ export declare class Screen {
9
+ readonly model: ScreenModel;
10
+ /** Lower-window text since the runner last took it (the transcript). */
11
+ private pending;
12
+ constructor(model: ScreenModel);
13
+ /** Text to the current window (stream 1). */
14
+ print(text: string): void;
15
+ private printUpper;
16
+ /** Take the lower window's text printed since the last call. */
17
+ take(): string;
18
+ /** @split_window (§8.6.1, §8.7.2.1): the upper window is `lines` tall; v3 clears it. */
19
+ split(lines: number, version: number): void;
20
+ /** @set_window: selecting the upper window puts its cursor at the top left (§8.7.2.2). */
21
+ setWindow(w: number): void;
22
+ /** @erase_window (§8.7.3.2): -1 unsplits and clears both, -2 clears both, 0 lower, 1 upper. */
23
+ erase(w: number): void;
24
+ /** @erase_line 1: the upper window's line from the cursor to its end. */
25
+ eraseLine(): void;
26
+ /** @set_cursor (1-based; the upper window only: §8.7.2.3). */
27
+ setCursor(line: number, col: number): void;
28
+ /** @get_cursor: 1-based row and column. */
29
+ cursor(): [number, number];
30
+ /** The upper window's rows as text (for the "Read upper window" command and the v4+ status announcement). */
31
+ upperText(): string[];
32
+ }
package/dist/screen.js ADDED
@@ -0,0 +1,126 @@
1
+ export function initialScreen(width, height) {
2
+ return {
3
+ width,
4
+ height,
5
+ lower: { runs: [], bufferMode: true },
6
+ upper: { rows: 0, grid: [], cursor: { row: 0, col: 0 } },
7
+ window: 0,
8
+ style: 0,
9
+ colours: { fg: 1, bg: 1 },
10
+ font: 1,
11
+ beeps: [],
12
+ };
13
+ }
14
+ const blankRow = (s) => Array.from({ length: s.width }, () => ({ ch: ' ', style: 0, fg: s.colours.fg, bg: s.colours.bg }));
15
+ export class Screen {
16
+ model;
17
+ /** Lower-window text since the runner last took it (the transcript). */
18
+ pending = '';
19
+ constructor(model) {
20
+ this.model = model;
21
+ }
22
+ /** Text to the current window (stream 1). */
23
+ print(text) {
24
+ const s = this.model;
25
+ if (s.window === 1)
26
+ return this.printUpper(text);
27
+ this.pending += text;
28
+ const last = s.lower.runs.at(-1);
29
+ if (last && last.style === s.style && last.fg === s.colours.fg && last.bg === s.colours.bg && last.font === s.font)
30
+ last.text += text;
31
+ else
32
+ s.lower.runs.push({ text, style: s.style, fg: s.colours.fg, bg: s.colours.bg, font: s.font });
33
+ }
34
+ printUpper(text) {
35
+ const u = this.model.upper;
36
+ for (const ch of text) {
37
+ if (ch === '\n') {
38
+ u.cursor.row++;
39
+ u.cursor.col = 0;
40
+ continue;
41
+ }
42
+ const row = u.grid[u.cursor.row];
43
+ // Text past the right edge or below the window is lost (§8.6.2: the upper window doesn't scroll).
44
+ if (row && u.cursor.col < this.model.width) {
45
+ row[u.cursor.col] = { ch, style: this.model.style, fg: this.model.colours.fg, bg: this.model.colours.bg };
46
+ u.cursor.col++;
47
+ }
48
+ }
49
+ }
50
+ /** Take the lower window's text printed since the last call. */
51
+ take() {
52
+ const t = this.pending;
53
+ this.pending = '';
54
+ return t;
55
+ }
56
+ /** @split_window (§8.6.1, §8.7.2.1): the upper window is `lines` tall; v3 clears it. */
57
+ split(lines, version) {
58
+ const u = this.model.upper;
59
+ const n = Math.max(0, Math.min(lines, this.model.height));
60
+ while (u.grid.length < n)
61
+ u.grid.push(blankRow(this.model));
62
+ u.grid.length = n;
63
+ u.rows = n;
64
+ if (version === 3)
65
+ for (let r = 0; r < n; r++)
66
+ u.grid[r] = blankRow(this.model);
67
+ if (u.cursor.row >= n)
68
+ u.cursor = { row: 0, col: 0 };
69
+ if (n === 0)
70
+ this.model.window = 0;
71
+ }
72
+ /** @set_window: selecting the upper window puts its cursor at the top left (§8.7.2.2). */
73
+ setWindow(w) {
74
+ this.model.window = w === 1 ? 1 : 0;
75
+ if (w === 1)
76
+ this.model.upper.cursor = { row: 0, col: 0 };
77
+ }
78
+ /** @erase_window (§8.7.3.2): -1 unsplits and clears both, -2 clears both, 0 lower, 1 upper. */
79
+ erase(w) {
80
+ const s = this.model;
81
+ if (w === -1) {
82
+ this.split(0, 0);
83
+ s.lower.runs = [];
84
+ s.window = 0;
85
+ }
86
+ else if (w === -2) {
87
+ s.lower.runs = [];
88
+ for (let r = 0; r < s.upper.rows; r++)
89
+ s.upper.grid[r] = blankRow(s);
90
+ }
91
+ else if (w === 0)
92
+ s.lower.runs = [];
93
+ else if (w === 1) {
94
+ for (let r = 0; r < s.upper.rows; r++)
95
+ s.upper.grid[r] = blankRow(s);
96
+ s.upper.cursor = { row: 0, col: 0 };
97
+ }
98
+ }
99
+ /** @erase_line 1: the upper window's line from the cursor to its end. */
100
+ eraseLine() {
101
+ const u = this.model.upper;
102
+ const row = u.grid[u.cursor.row];
103
+ if (!row || this.model.window !== 1)
104
+ return;
105
+ for (let c = u.cursor.col; c < row.length; c++)
106
+ row[c] = { ch: ' ', style: 0, fg: this.model.colours.fg, bg: this.model.colours.bg };
107
+ }
108
+ /** @set_cursor (1-based; the upper window only: §8.7.2.3). */
109
+ setCursor(line, col) {
110
+ if (this.model.window !== 1)
111
+ return;
112
+ this.model.upper.cursor = { row: Math.max(0, line - 1), col: Math.max(0, col - 1) };
113
+ }
114
+ /** @get_cursor: 1-based row and column. */
115
+ cursor() {
116
+ if (this.model.window !== 1)
117
+ return [1, 1];
118
+ const c = this.model.upper.cursor;
119
+ return [c.row + 1, c.col + 1];
120
+ }
121
+ /** The upper window's rows as text (for the "Read upper window" command and the v4+ status announcement). */
122
+ upperText() {
123
+ return this.model.upper.grid.map(row => row.map(c => c.ch).join('').replace(/\s+$/, ''));
124
+ }
125
+ }
126
+ //# sourceMappingURL=screen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screen.js","sourceRoot":"","sources":["../src/screen.ts"],"names":[],"mappings":"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"}
package/dist/text.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Text for the interpreter (§3): ZSCII in and out, and encoding words for the dictionary (§3.7,
3
+ * §13). Decoding Z-characters is zcode's (`decodeZscii`); this file turns ZSCII into what the
4
+ * screen shows, and typed text back into ZSCII and Z-characters.
5
+ */
6
+ import { type TextContext } from '@frotzinc/zcode';
7
+ /** The ZSCII codes of the string at `address` (byte address). */
8
+ export declare function zsciiAt(ctx: TextContext, address: number): number[];
9
+ /** One ZSCII output code as screen text (§3.8); codes with no glyph show as '?'. */
10
+ export declare const zsciiText: (code: number, ctx: TextContext) => string;
11
+ /**
12
+ * A Unicode character typed or printed, as ZSCII (§3.8): ASCII as itself, the extra characters
13
+ * through the Unicode table, anything else '?' (63).
14
+ */
15
+ export declare function unicodeToZscii(ch: string, ctx: TextContext): number;
16
+ /**
17
+ * Typed text as ZSCII for a text buffer: lowercased first (§15 read: the interpreter converts
18
+ * input to lower case), then mapped.
19
+ */
20
+ export declare function inputToZscii(text: string, ctx: TextContext): number[];
21
+ /**
22
+ * ZSCII codes encoded as a dictionary word (§3.7): v3 6 Z-characters in 2 words, v4+ 9 in 3,
23
+ * truncated or padded with 5s, the end bit on the last word. Returns the bytes.
24
+ */
25
+ export declare function encodeWord(codes: number[], ctx: TextContext): Uint8Array;
package/dist/text.js ADDED
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Text for the interpreter (§3): ZSCII in and out, and encoding words for the dictionary (§3.7,
3
+ * §13). Decoding Z-characters is zcode's (`decodeZscii`); this file turns ZSCII into what the
4
+ * screen shows, and typed text back into ZSCII and Z-characters.
5
+ */
6
+ import { decodeZscii, readZchars, zsciiToText } from '@frotzinc/zcode';
7
+ /** The default alphabets as ZSCII (§3.5.3); A2's first two are the escape and newline. */
8
+ const DEFAULT_A = [
9
+ [...'abcdefghijklmnopqrstuvwxyz'].map(c => c.charCodeAt(0)),
10
+ [...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'].map(c => c.charCodeAt(0)),
11
+ [0, 13, ...[...'0123456789.,!?_#\'"/\\-:()'].map(c => c.charCodeAt(0))],
12
+ ];
13
+ /** The ZSCII codes of the string at `address` (byte address). */
14
+ export function zsciiAt(ctx, address) {
15
+ return decodeZscii(ctx, readZchars(ctx.bytes, address).zchars);
16
+ }
17
+ /** One ZSCII output code as screen text (§3.8); codes with no glyph show as '?'. */
18
+ export const zsciiText = (code, ctx) => zsciiToText(code, ctx);
19
+ /**
20
+ * A Unicode character typed or printed, as ZSCII (§3.8): ASCII as itself, the extra characters
21
+ * through the Unicode table, anything else '?' (63).
22
+ */
23
+ export function unicodeToZscii(ch, ctx) {
24
+ const cp = ch.codePointAt(0);
25
+ if (cp === 10 || cp === 13)
26
+ return 13;
27
+ if (cp >= 32 && cp <= 126)
28
+ return cp;
29
+ const i = ctx.extra.indexOf(cp);
30
+ return i >= 0 && i <= 96 ? 155 + i : 63;
31
+ }
32
+ /**
33
+ * Typed text as ZSCII for a text buffer: lowercased first (§15 read: the interpreter converts
34
+ * input to lower case), then mapped.
35
+ */
36
+ export function inputToZscii(text, ctx) {
37
+ return [...text.toLowerCase()].map(ch => unicodeToZscii(ch, ctx));
38
+ }
39
+ /**
40
+ * ZSCII codes encoded as a dictionary word (§3.7): v3 6 Z-characters in 2 words, v4+ 9 in 3,
41
+ * truncated or padded with 5s, the end bit on the last word. Returns the bytes.
42
+ */
43
+ export function encodeWord(codes, ctx) {
44
+ const n = ctx.version <= 3 ? 6 : 9;
45
+ const alphabets = ctx.alphabets ?? DEFAULT_A;
46
+ const z = [];
47
+ for (const c of codes) {
48
+ if (z.length >= n)
49
+ break;
50
+ const a0 = alphabets[0].indexOf(c);
51
+ if (a0 >= 0) {
52
+ z.push(a0 + 6);
53
+ continue;
54
+ }
55
+ const a1 = alphabets[1].indexOf(c);
56
+ if (a1 >= 0) {
57
+ z.push(4, a1 + 6);
58
+ continue;
59
+ }
60
+ // A2 positions 0 and 1 are the escape and newline, never characters (§3.5.3).
61
+ const a2 = alphabets[2].indexOf(c, 2);
62
+ if (a2 >= 2) {
63
+ z.push(5, a2 + 6);
64
+ continue;
65
+ }
66
+ z.push(5, 6, (c >> 5) & 31, c & 31);
67
+ }
68
+ z.length = Math.min(z.length, n);
69
+ while (z.length < n)
70
+ z.push(5);
71
+ const out = new Uint8Array((n / 3) * 2);
72
+ for (let i = 0; i < n; i += 3) {
73
+ let w = (z[i] << 10) | (z[i + 1] << 5) | z[i + 2];
74
+ if (i + 3 >= n)
75
+ w |= 0x8000;
76
+ out[(i / 3) * 2] = w >> 8;
77
+ out[(i / 3) * 2 + 1] = w & 0xff;
78
+ }
79
+ return out;
80
+ }
81
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"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"}