@8bitscript/pet 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/text.8bs ADDED
@@ -0,0 +1,165 @@
1
+ // @8bitscript/pet/text — the PET's implementation of @8bitscript/text.
2
+ //
3
+ // Named by "8bitscript".exports["./text"] in this package's package.json,
4
+ // and what
5
+ //
6
+ // import { text } from "@8bitscript/text";
7
+ //
8
+ // resolves to when the build is for the PET. Built on the register
9
+ // @8bitscript/pet exports. Every machine's text.8bs exports this same
10
+ // namespace — the same names, the same shapes, ASCII codes, and a cell 0
11
+ // at the top-left corner of the screen — so a program that imports it from
12
+ // @8bitscript/text draws the same thing on every target.
13
+ import { viaPeripheralControl } from "./index.8bs";
14
+ import { Video } from "./geometry.8bs";
15
+
16
+ // PET screen memory is at $8000 on every model: 1000 cells for the 40x25
17
+ // display of the 2001/3xxx/4xxx machines, 2000 for the 80x25 of the 8032 —
18
+ // which is the build's `--profile` to say (PET_PROFILES in
19
+ // packages/backend-6502), and reaches this file as ./geometry.8bs's
20
+ // `Video` namespace, whose 8032 version the profile selects. There is no
21
+ // colour RAM to go with it — a character's screen code alone decides how it
22
+ // looks — so `putColor` exists only so portable code written against every
23
+ // machine's `text` namespace still compiles; it does nothing, the same way
24
+ // this package's `screen.setColors()` does nothing.
25
+ //
26
+ // `text.putChar` takes ASCII on every machine — space (32), '0'-'9'
27
+ // (48-57), 'A'-'Z' (65-90) and the punctuation `! , - . : ?` — upper case
28
+ // only: that is the portable set, the characters every target's character
29
+ // set has (the NES ships its own font, and that is what it has). The PET
30
+ // wants screen codes: codes 32-63 are those same ASCII values, and 'A'-'Z'
31
+ // are 1-26, so asciiToScreenCode() moves 64-95 down by 64 and leaves 32-63
32
+ // alone. Whether 1-26 then LOOK like 'A'-'Z' or 'a'-'z' is the character
33
+ // set's choice, and on the PET that is the editor ROM's boot state: the
34
+ // graphics-keyboard 3032/4032 boot in the upper-case ("graphics") set, the
35
+ // business-keyboard 8032 (xpet's default model) in the lower-case ("text")
36
+ // set. So putChar() selects the graphics set itself, every call, through
37
+ // the VIA's peripheral control register (see @8bitscript/pet): one register
38
+ // write, and "TICK" reads as TICK here on every model the way it does on
39
+ // every other target. (LLVM-MOS's libc used to flip every Commodore machine
40
+ // to lower-case before main() with a hidden CHR$(14) through the KERNAL;
41
+ // packages/backend-6502's commodoreCharsetGuard() keeps that out of the
42
+ // link. See packages/pet/AGENTS.md.)
43
+ namespace CharacterSet {
44
+ const GRAPHICS: utinyint = 0x0C;
45
+ }
46
+
47
+ function asciiToScreenCode(code: utinyint): utinyint {
48
+ if (code >= 64 && code < 96) {
49
+ return code - 64;
50
+ }
51
+ return code;
52
+ }
53
+
54
+ // ---- print: strings and number fields ------------------------------------
55
+ //
56
+ // `text.print(cell, s)` writes a string's characters into consecutive
57
+ // cells from `cell`, and `text.printNumber(cell, value, width)` writes
58
+ // `value` as exactly `width` decimal digits, zero-padded and right-aligned,
59
+ // so a field on a HUD never shifts columns. Both draw in the current colour —
60
+ // white until `text.setColor(TextColor.CYAN)` changes it, and that one
61
+ // call then colours everything printed after it, on the machines that have
62
+ // per-cell colour. They are also the two
63
+ // functions the compiler's template layout targets: `text.print(0,
64
+ // \`TICK ${ticks:1}\`)` is laid out at compile time into these same calls
65
+ // (see packages/compiler/src/ir).
66
+ //
67
+ // Under them: `place()` puts one character at one cell in the current
68
+ // colour, and is all a run of text costs per character, with `prepare()` selecting the
69
+ // graphics set once for the run.
70
+ // `text.putChar` and `text.putColor` stay the one-cell pokes a caller can
71
+ // build anything from.
72
+ let currentColor: utinyint = 1; // white, until text.setColor() says otherwise
73
+ let currentReverse: bool = false; // until text.setReverse() says otherwise
74
+
75
+ // Reverse video is bit 7 of the screen code: 128-255 are the ROM's
76
+ // inverted copies of 0-127. A reverse space is a solid block.
77
+ function toScreen(code: utinyint): utinyint {
78
+ let screen: utinyint = asciiToScreenCode(code);
79
+ if (currentReverse) {
80
+ screen = screen + 128;
81
+ }
82
+ return screen;
83
+ }
84
+
85
+ // What a run of text needs once, before its first character.
86
+ function prepare(): void {
87
+ viaPeripheralControl = CharacterSet.GRAPHICS;
88
+ }
89
+
90
+ function place(cell: usmallint, code: utinyint): void {
91
+ memory.write(0x8000 + cell, toScreen(code));
92
+ }
93
+
94
+ // ---- digits -------------------------------------------------------------
95
+ //
96
+ // A number is written one place at a time, high to low, by subtracting the
97
+ // place value until it no longer fits: the 6502 has no divide instruction,
98
+ // and `value / 10` would link a 250-byte routine to do it. Places above the
99
+ // field are still taken off, so a field narrower than its number shows the
100
+ // low digits; places the number does not reach print as zeros.
101
+ const DIGIT_PLACES: array<usmallint, 5> = [10000, 1000, 100, 10, 1];
102
+
103
+ export namespace text {
104
+ const CELL_COUNT: usmallint = Video.CELL_COUNT; // 40 x 25, or 80 x 25 on the 8032 profile
105
+ const COLUMNS: utinyint = Video.COLUMNS; // cells per row, so cell = y * text.COLUMNS + x
106
+
107
+ function putChar(cell: usmallint, code: utinyint): void {
108
+ prepare();
109
+ memory.write(0x8000 + cell, toScreen(code));
110
+ }
111
+
112
+ function putColor(cell: usmallint, color: utinyint): void {
113
+ // Deliberately empty — the PET has no colour RAM.
114
+ }
115
+
116
+ function setColor(color: utinyint): void {
117
+ currentColor = color;
118
+ }
119
+
120
+ function setReverse(on: bool): void {
121
+ currentReverse = on;
122
+ }
123
+
124
+ function print(cell: usmallint, s: string): void {
125
+ prepare();
126
+ for (let i: utinyint = 0; i < s.length; i++) {
127
+ place(cell + i, s[i]);
128
+ }
129
+ }
130
+
131
+ function printNumber(cell: usmallint, value: usmallint, width: utinyint): void {
132
+ prepare();
133
+ let k: utinyint = width;
134
+ if (k < DIGIT_PLACES.length) {
135
+ k = DIGIT_PLACES.length;
136
+ }
137
+ while (k > 0) {
138
+ let digit: utinyint = 48; // '0'
139
+ if (k <= DIGIT_PLACES.length) {
140
+ while (value >= DIGIT_PLACES[DIGIT_PLACES.length - k]) {
141
+ value = value - DIGIT_PLACES[DIGIT_PLACES.length - k];
142
+ digit++;
143
+ }
144
+ }
145
+ if (k <= width) {
146
+ place(cell + width - k, digit);
147
+ }
148
+ k--;
149
+ }
150
+ }
151
+ }
152
+
153
+ // The colours `text.setColor()` takes. The PET has no colour RAM, so the
154
+ // names are here for a program that compiles everywhere and their values
155
+ // go nowhere.
156
+ export namespace TextColor {
157
+ const BLACK: utinyint = 0;
158
+ const WHITE: utinyint = 1;
159
+ const RED: utinyint = 2;
160
+ const CYAN: utinyint = 3;
161
+ const PURPLE: utinyint = 4;
162
+ const GREEN: utinyint = 5;
163
+ const BLUE: utinyint = 6;
164
+ const YELLOW: utinyint = 7;
165
+ }