mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
d67f3d8086
* Add tests for Applesoft compiler in preparation for refactoring While refactoring the compiler, I found several small bugs: * Lower-case letters in strings and REM statements were converted to upper-case. * Lines are stored in the order received, not sorted by line number. * Does not prefer `ATN` to `AT`. * Does not prefer `TO` to `AT`. * `DATA` statements don't preserve spaces. * `DATA` statements don't preserve lowercase. These will be fixed in the upcoming refactoring. * Refactor the Applesoft Compiler Before, the compiler had a few bugs that were not trivial to solve because the implementation was in one heavily-nested function. In this refactoring of the compiler, things like tokenization have been split into separate methods which makes them a bit easier to understand. This refactoring also passes all of the tests. * Set `PRGEND` when compiling to memory Before, `PRGEND` was not adjusted which made round-tripping from the Applesoft compiler to the decompiler not work. This change now updates `PRGEND` with the end-of-program + 2 bytes which seems to be the most frequent value that I have observed. * Fix two compiler bugs In debugging the decompiler, I noticed two bugs in the compiler: * The first character after a line number was skipped. * `?` was not accepted as a shortcut for `PRINT`. This change fixes these two problems and adds tests. * Ignore spaces more aggressively It turns out that Applesoft happily accepts 'T H E N' for `THEN` but the parser did not. This change fixes that and adds tests for some odd cases. Interestingly, this means that there are some valid statements that Applesoft can never parse correctly because it is greedy and ignores (most) spaces. For example, `NOT RACE` will always parse as `NOTRACE` even though `NOT RACE` is a valid expression. * Move tokens into a separate file Because the token lists are just maps in opposite directions, put them in the same file. In the future, maybe we can build one automatically. * Fix `apple2.ts` I had neglected to actually update `apple2.ts` to use the new compiler and decompiler. They now do. Also, the decompiler can be created from `Memory`. It assumes, though, that the zero page pointers to the start and end of the program are correct. * Address comments * No more `as const` for tokens. * Extracted zero page constants to their own file. Co-authored-by: Will Scullin <scullin@scullin.com>
432 lines
20 KiB
TypeScript
432 lines
20 KiB
TypeScript
import ApplesoftCompiler from 'js/applesoft/compiler';
|
|
import RAM from 'js/ram';
|
|
import { Memory } from 'js/types';
|
|
|
|
// Zero page locations used by Applesoft. The names come from
|
|
// the commented decompilation produced by the Merlin Pro
|
|
// assembler, revision 4/27/84. There is evidence from
|
|
// https://www.pagetable.com/?p=774 that the original Microsoft
|
|
// BASIC source code used these names as well.
|
|
const TXTTAB = 0x67; // start of program, word
|
|
const VARTAB = 0x69; // start of variables, word
|
|
const ARYTAB = 0x6B; // start of arrays, word
|
|
const STREND = 0x6D; // end of strings, word
|
|
const PRGEND = 0xAF; // end of program, word
|
|
|
|
function compileToMemory(ram: Memory, program: string) {
|
|
ApplesoftCompiler.compileToMemory(ram, program);
|
|
}
|
|
|
|
// Manual decompilation based on "Applesoft Internal Structure"
|
|
// by C.K. Mesztenyi/Washington Apple Pi, from Call—A.P.P.L.E.,
|
|
// January, 1982. Archived at:
|
|
// https://archive.org/details/DTCA2DOC-045_applesoft_internal
|
|
// Decompilation verified on the emulator by typing in the
|
|
// program, then:
|
|
// ]CALL -151
|
|
// *800.820
|
|
// and comparing the resulting bytes (starting at 801).
|
|
|
|
describe('ApplesoftCompiler', () => {
|
|
it('compiles a one-line hello world', () => {
|
|
const compiler = new ApplesoftCompiler();
|
|
compiler.compile('10 PRINT "HELLO, WORLD!"');
|
|
expect(compiler.program()).toEqual(new Uint8Array([
|
|
0x16, 0x08, 0x0a, 0x00, 0xba, 0x22, 0x48, 0x45,
|
|
0x4c, 0x4c, 0x4f, 0x2c, 0x20, 0x57, 0x4f, 0x52,
|
|
0x4c, 0x44, 0x21, 0x22, 0x00, 0x00, 0x00
|
|
]));
|
|
});
|
|
|
|
it('compiles a one-line hello world into memory', () => {
|
|
const ram = new RAM(0, 0xff); // 64K of RAM
|
|
|
|
compileToMemory(ram, '10 PRINT "HELLO, WORLD!"');
|
|
expect(ram.read(0x08, 0x01)).toBe(0x16); // pointer to next line low
|
|
expect(ram.read(0x08, 0x02)).toBe(0x08); // pointer to next line high
|
|
expect(ram.read(0x08, 0x03)).toBe(10); // line number low
|
|
expect(ram.read(0x08, 0x04)).toBe(0); // line number high
|
|
expect(ram.read(0x08, 0x05)).toBe(0xba); // PRINT
|
|
expect(ram.read(0x08, 0x06)).toBe(0x22); // "
|
|
expect(ram.read(0x08, 0x07)).toBe(0x48); // H
|
|
expect(ram.read(0x08, 0x08)).toBe(0x45); // E
|
|
expect(ram.read(0x08, 0x09)).toBe(0x4C); // L
|
|
expect(ram.read(0x08, 0x0a)).toBe(0x4C); // L
|
|
expect(ram.read(0x08, 0x0b)).toBe(0x4F); // O
|
|
expect(ram.read(0x08, 0x0c)).toBe(0x2C); // ,
|
|
expect(ram.read(0x08, 0x0d)).toBe(0x20); // space
|
|
expect(ram.read(0x08, 0x0e)).toBe(0x57); // W
|
|
expect(ram.read(0x08, 0x0f)).toBe(0x4F); // O
|
|
expect(ram.read(0x08, 0x10)).toBe(0x52); // R
|
|
expect(ram.read(0x08, 0x11)).toBe(0x4C); // L
|
|
expect(ram.read(0x08, 0x12)).toBe(0x44); // D
|
|
expect(ram.read(0x08, 0x13)).toBe(0x21); // !
|
|
expect(ram.read(0x08, 0x14)).toBe(0x22); // "
|
|
expect(ram.read(0x08, 0x15)).toBe(0x00); // end of line
|
|
expect(ram.read(0x08, 0x16)).toBe(0x00); // end of program low
|
|
expect(ram.read(0x08, 0x17)).toBe(0x00); // end of program high
|
|
|
|
expect(ram.read(0x00, TXTTAB)).toBe(0x01); // start of program low
|
|
expect(ram.read(0x00, TXTTAB + 1)).toBe(0x08); // start of program high
|
|
expect(ram.read(0x00, VARTAB)).toBe(0x19); // start of variables low
|
|
expect(ram.read(0x00, VARTAB + 1)).toBe(0x08); // start of variables high
|
|
expect(ram.read(0x00, ARYTAB)).toBe(0x19); // start of arrays low
|
|
expect(ram.read(0x00, ARYTAB + 1)).toBe(0x08); // start of arrays high
|
|
expect(ram.read(0x00, STREND)).toBe(0x19); // end of strings low
|
|
expect(ram.read(0x00, STREND + 1)).toBe(0x08); // end of strings high
|
|
expect(ram.read(0x00, PRGEND)).toBe(0x19); // end of program low
|
|
expect(ram.read(0x00, PRGEND + 1)).toBe(0x08); // end of program high
|
|
});
|
|
|
|
it('uppercases normal-mode text, like variables', () => {
|
|
const ram = new RAM(0, 0xff); // 64K of RAM
|
|
|
|
compileToMemory(ram, '10 fori=xtoz');
|
|
expect(ram.read(0x08, 0x03)).toBe(10); // line number low
|
|
expect(ram.read(0x08, 0x04)).toBe(0); // line number high
|
|
expect(ram.read(0x08, 0x05)).toBe(0x81); // FOR
|
|
expect(ram.read(0x08, 0x06)).toBe(0x49); // I
|
|
expect(ram.read(0x08, 0x07)).toBe(0xd0); // = (token)
|
|
expect(ram.read(0x08, 0x08)).toBe(0x58); // X
|
|
expect(ram.read(0x08, 0x09)).toBe(0xc1); // TO
|
|
expect(ram.read(0x08, 0x0a)).toBe(0x5a); // Z
|
|
expect(ram.read(0x08, 0x0b)).toBe(0x00); // end of line
|
|
});
|
|
|
|
it('allows lower-case characters in strings', () => {
|
|
const ram = new RAM(0, 0xff); // 64K of RAM
|
|
|
|
compileToMemory(ram, '10 PRINT "Hello!"');
|
|
expect(ram.read(0x08, 0x03)).toBe(10); // line number low
|
|
expect(ram.read(0x08, 0x04)).toBe(0); // line number high
|
|
expect(ram.read(0x08, 0x05)).toBe(0xba); // PRINT
|
|
expect(ram.read(0x08, 0x06)).toBe(0x22); // "
|
|
expect(ram.read(0x08, 0x07)).toBe(0x48); // H
|
|
expect(ram.read(0x08, 0x08)).toBe(0x65); // e
|
|
expect(ram.read(0x08, 0x09)).toBe(0x6C); // l
|
|
expect(ram.read(0x08, 0x0a)).toBe(0x6C); // l
|
|
expect(ram.read(0x08, 0x0b)).toBe(0x6F); // o
|
|
});
|
|
|
|
it('allows lower-case characters in comments', () => {
|
|
const ram = new RAM(0, 0xff); // 64K of RAM
|
|
|
|
compileToMemory(ram, '10 REM Hello!');
|
|
expect(ram.read(0x08, 0x03)).toBe(10); // line number low
|
|
expect(ram.read(0x08, 0x04)).toBe(0); // line number high
|
|
expect(ram.read(0x08, 0x05)).toBe(0xb2); // REM
|
|
expect(ram.read(0x08, 0x06)).toBe(0x20); // space
|
|
expect(ram.read(0x08, 0x07)).toBe(0x48); // H
|
|
expect(ram.read(0x08, 0x08)).toBe(0x65); // e
|
|
expect(ram.read(0x08, 0x09)).toBe(0x6C); // l
|
|
expect(ram.read(0x08, 0x0a)).toBe(0x6C); // l
|
|
expect(ram.read(0x08, 0x0b)).toBe(0x6F); // o
|
|
});
|
|
|
|
it('allows lower-case tokens', () => {
|
|
const ram = new RAM(0, 0xff); // 64K of RAM
|
|
|
|
compileToMemory(ram, '10 print "Hello!"');
|
|
expect(ram.read(0x08, 0x03)).toBe(10); // line number low
|
|
expect(ram.read(0x08, 0x04)).toBe(0); // line number high
|
|
expect(ram.read(0x08, 0x05)).toBe(0xba); // PRINT
|
|
});
|
|
|
|
it('accepts out-of-order lines', () => {
|
|
const ram = new RAM(0, 0xff); // 64K of RAM
|
|
|
|
compileToMemory(ram, '20 GOTO 10\n10 PRINT "HELLO');
|
|
expect(ram.read(0x08, 0x01)).toBe(0x0d); // pointer to next line low
|
|
expect(ram.read(0x08, 0x02)).toBe(0x08); // pointer to next line high
|
|
expect(ram.read(0x08, 0x03)).toBe(10); // line number low
|
|
expect(ram.read(0x08, 0x04)).toBe(0); // line number high
|
|
expect(ram.read(0x08, 0x05)).toBe(0xba); // PRINT
|
|
expect(ram.read(0x08, 0x06)).toBe(0x22); // "
|
|
expect(ram.read(0x08, 0x07)).toBe(0x48); // H
|
|
expect(ram.read(0x08, 0x08)).toBe(0x45); // E
|
|
expect(ram.read(0x08, 0x09)).toBe(0x4C); // L
|
|
expect(ram.read(0x08, 0x0a)).toBe(0x4C); // L
|
|
expect(ram.read(0x08, 0x0b)).toBe(0x4F); // O
|
|
expect(ram.read(0x08, 0x0c)).toBe(0x00); // end of line
|
|
expect(ram.read(0x08, 0x0d)).toBe(0x15); // pointer to next line low
|
|
expect(ram.read(0x08, 0x0e)).toBe(0x08); // pointer to next line high
|
|
expect(ram.read(0x08, 0x0f)).toBe(0x14); // line number low
|
|
expect(ram.read(0x08, 0x10)).toBe(0x00); // line number high
|
|
expect(ram.read(0x08, 0x11)).toBe(0xab); // GOTO
|
|
expect(ram.read(0x08, 0x12)).toBe(0x31); // 1
|
|
expect(ram.read(0x08, 0x13)).toBe(0x30); // 0
|
|
expect(ram.read(0x08, 0x14)).toBe(0x00); // end of line
|
|
expect(ram.read(0x08, 0x15)).toBe(0x00); // end of program low
|
|
expect(ram.read(0x08, 0x16)).toBe(0x00); // end of program high
|
|
});
|
|
|
|
it('prefers ATN to AT', () => {
|
|
const ram = new RAM(0, 0xff); // 64K of RAM
|
|
|
|
compileToMemory(ram, '10 X = ATN(20)');
|
|
expect(ram.read(0x08, 0x03)).toBe(10); // line number low
|
|
expect(ram.read(0x08, 0x04)).toBe(0); // line number high
|
|
expect(ram.read(0x08, 0x05)).toBe(0x58); // X
|
|
expect(ram.read(0x08, 0x06)).toBe(0xd0); // = (token)
|
|
expect(ram.read(0x08, 0x07)).toBe(0xe1); // ATN
|
|
expect(ram.read(0x08, 0x08)).toBe(0x28); // (
|
|
expect(ram.read(0x08, 0x09)).toBe(0x32); // 2
|
|
expect(ram.read(0x08, 0x0a)).toBe(0x30); // 0
|
|
expect(ram.read(0x08, 0x0b)).toBe(0x29); // )
|
|
expect(ram.read(0x08, 0x0c)).toBe(0x00); // end of line
|
|
});
|
|
|
|
it('prefers TO to AT', () => {
|
|
const ram = new RAM(0, 0xff); // 64K of RAM
|
|
|
|
compileToMemory(ram, '10 FORI=ATOZ');
|
|
expect(ram.read(0x08, 0x03)).toBe(10); // line number low
|
|
expect(ram.read(0x08, 0x04)).toBe(0); // line number high
|
|
expect(ram.read(0x08, 0x05)).toBe(0x81); // FOR
|
|
expect(ram.read(0x08, 0x06)).toBe(0x49); // I
|
|
expect(ram.read(0x08, 0x07)).toBe(0xd0); // = (token)
|
|
expect(ram.read(0x08, 0x08)).toBe(0x41); // A
|
|
expect(ram.read(0x08, 0x09)).toBe(0xc1); // TO
|
|
expect(ram.read(0x08, 0x0a)).toBe(0x5a); // Z
|
|
expect(ram.read(0x08, 0x0b)).toBe(0x00); // end of line
|
|
});
|
|
|
|
it('parses DATA statements that start with space', () => {
|
|
const ram = new RAM(0, 0xff); // 64K of RAM
|
|
|
|
compileToMemory(ram, '10 DATA 1,2,3');
|
|
expect(ram.read(0x08, 0x03)).toBe(10); // line number low
|
|
expect(ram.read(0x08, 0x04)).toBe(0); // line number high
|
|
expect(ram.read(0x08, 0x05)).toBe(0x83); // DATA
|
|
expect(ram.read(0x08, 0x06)).toBe(0x20); // space
|
|
expect(ram.read(0x08, 0x07)).toBe(0x31); // 1
|
|
expect(ram.read(0x08, 0x08)).toBe(0x2c); // ,
|
|
expect(ram.read(0x08, 0x09)).toBe(0x32); // 2
|
|
expect(ram.read(0x08, 0x0a)).toBe(0x2c); // ,
|
|
expect(ram.read(0x08, 0x0b)).toBe(0x33); // 3
|
|
expect(ram.read(0x08, 0x0c)).toBe(0x00); // end of line
|
|
});
|
|
|
|
it('parses DATA statements with numbers', () => {
|
|
const ram = new RAM(0, 0xff); // 64K of RAM
|
|
|
|
compileToMemory(ram, '10 DATA1,2,3');
|
|
expect(ram.read(0x08, 0x03)).toBe(10); // line number low
|
|
expect(ram.read(0x08, 0x04)).toBe(0); // line number high
|
|
expect(ram.read(0x08, 0x05)).toBe(0x83); // DATA
|
|
expect(ram.read(0x08, 0x06)).toBe(0x31); // 1
|
|
expect(ram.read(0x08, 0x07)).toBe(0x2c); // ,
|
|
expect(ram.read(0x08, 0x08)).toBe(0x32); // 2
|
|
expect(ram.read(0x08, 0x09)).toBe(0x2c); // ,
|
|
expect(ram.read(0x08, 0x0a)).toBe(0x33); // 3
|
|
expect(ram.read(0x08, 0x0b)).toBe(0x00); // end of line
|
|
});
|
|
|
|
it('parses DATA statements with strings including lower-case', () => {
|
|
const ram = new RAM(0, 0xff); // 64K of RAM
|
|
|
|
compileToMemory(ram, '10 DATA"abc"');
|
|
expect(ram.read(0x08, 0x03)).toBe(10); // line number low
|
|
expect(ram.read(0x08, 0x04)).toBe(0); // line number high
|
|
expect(ram.read(0x08, 0x05)).toBe(0x83); // DATA
|
|
expect(ram.read(0x08, 0x06)).toBe(0x22); // "
|
|
expect(ram.read(0x08, 0x07)).toBe(0x61); // a
|
|
expect(ram.read(0x08, 0x08)).toBe(0x62); // b
|
|
expect(ram.read(0x08, 0x09)).toBe(0x63); // c
|
|
expect(ram.read(0x08, 0x0a)).toBe(0x22); // "
|
|
expect(ram.read(0x08, 0x0b)).toBe(0x00); // end of line
|
|
});
|
|
|
|
it('parses DATA statements with literals', () => {
|
|
const ram = new RAM(0, 0xff); // 64K of RAM
|
|
|
|
compileToMemory(ram, '10 DATAHELLO');
|
|
expect(ram.read(0x08, 0x03)).toBe(10); // line number low
|
|
expect(ram.read(0x08, 0x04)).toBe(0); // line number high
|
|
expect(ram.read(0x08, 0x05)).toBe(0x83); // DATA
|
|
expect(ram.read(0x08, 0x06)).toBe(0x48); // H
|
|
expect(ram.read(0x08, 0x07)).toBe(0x45); // E
|
|
expect(ram.read(0x08, 0x08)).toBe(0x4C); // L
|
|
expect(ram.read(0x08, 0x09)).toBe(0x4C); // L
|
|
expect(ram.read(0x08, 0x0a)).toBe(0x4F); // O
|
|
expect(ram.read(0x08, 0x0b)).toBe(0x00); // end of line
|
|
});
|
|
|
|
it('parses DATA statements with literals including lower-case', () => {
|
|
const ram = new RAM(0, 0xff); // 64K of RAM
|
|
|
|
compileToMemory(ram, '10 DATAHello');
|
|
expect(ram.read(0x08, 0x03)).toBe(10); // line number low
|
|
expect(ram.read(0x08, 0x04)).toBe(0); // line number high
|
|
expect(ram.read(0x08, 0x05)).toBe(0x83); // DATA
|
|
expect(ram.read(0x08, 0x06)).toBe(0x48); // H
|
|
expect(ram.read(0x08, 0x07)).toBe(0x65); // e
|
|
expect(ram.read(0x08, 0x08)).toBe(0x6C); // l
|
|
expect(ram.read(0x08, 0x09)).toBe(0x6C); // l
|
|
expect(ram.read(0x08, 0x0a)).toBe(0x6F); // o
|
|
expect(ram.read(0x08, 0x0b)).toBe(0x00); // end of line
|
|
});
|
|
|
|
it('parses DATA statements with literals including quotes', () => {
|
|
const ram = new RAM(0, 0xff); // 64K of RAM
|
|
|
|
compileToMemory(ram, '10 DATAAA"B');
|
|
expect(ram.read(0x08, 0x03)).toBe(10); // line number low
|
|
expect(ram.read(0x08, 0x04)).toBe(0); // line number high
|
|
expect(ram.read(0x08, 0x05)).toBe(0x83); // DATA
|
|
expect(ram.read(0x08, 0x06)).toBe(0x41); // A
|
|
expect(ram.read(0x08, 0x07)).toBe(0x41); // A
|
|
expect(ram.read(0x08, 0x08)).toBe(0x22); // "
|
|
expect(ram.read(0x08, 0x09)).toBe(0x42); // B
|
|
expect(ram.read(0x08, 0x0a)).toBe(0x00); // end of line
|
|
});
|
|
|
|
it('parses DATA statements with literals including spaces', () => {
|
|
const ram = new RAM(0, 0xff); // 64K of RAM
|
|
|
|
compileToMemory(ram, '10 DATAA B');
|
|
expect(ram.read(0x08, 0x03)).toBe(10); // line number low
|
|
expect(ram.read(0x08, 0x04)).toBe(0); // line number high
|
|
expect(ram.read(0x08, 0x05)).toBe(0x83); // DATA
|
|
expect(ram.read(0x08, 0x06)).toBe(0x41); // A
|
|
expect(ram.read(0x08, 0x07)).toBe(0x20); // space
|
|
expect(ram.read(0x08, 0x08)).toBe(0x20); // space
|
|
expect(ram.read(0x08, 0x09)).toBe(0x42); // B
|
|
expect(ram.read(0x08, 0x0a)).toBe(0x00); // end of line
|
|
});
|
|
|
|
it('terminates DATA statements at colons', () => {
|
|
const ram = new RAM(0, 0xff); // 64K of RAM
|
|
|
|
compileToMemory(ram, '10 DATAAA:FORI=1TO1');
|
|
expect(ram.read(0x08, 0x03)).toBe(10); // line number low
|
|
expect(ram.read(0x08, 0x04)).toBe(0); // line number high
|
|
expect(ram.read(0x08, 0x05)).toBe(0x83); // DATA
|
|
expect(ram.read(0x08, 0x06)).toBe(0x41); // A
|
|
expect(ram.read(0x08, 0x07)).toBe(0x41); // A
|
|
expect(ram.read(0x08, 0x08)).toBe(0x3a); // :
|
|
expect(ram.read(0x08, 0x09)).toBe(0x81); // FOR
|
|
expect(ram.read(0x08, 0x0a)).toBe(0x49); // I
|
|
expect(ram.read(0x08, 0x0b)).toBe(0xd0); // = (token)
|
|
expect(ram.read(0x08, 0x0c)).toBe(0x31); // 1
|
|
expect(ram.read(0x08, 0x0d)).toBe(0xc1); // TO
|
|
expect(ram.read(0x08, 0x0e)).toBe(0x31); // 1
|
|
expect(ram.read(0x08, 0x0f)).toBe(0x00); // end of line
|
|
});
|
|
|
|
it('does not terminate DATA statements with a literal with a quote at colon', () => {
|
|
const ram = new RAM(0, 0xff); // 64K of RAM
|
|
|
|
compileToMemory(ram, '10 DATAA":FORI=1TO1');
|
|
expect(ram.read(0x08, 0x03)).toBe(10); // line number low
|
|
expect(ram.read(0x08, 0x04)).toBe(0); // line number high
|
|
expect(ram.read(0x08, 0x05)).toBe(0x83); // DATA
|
|
expect(ram.read(0x08, 0x06)).toBe(0x41); // A
|
|
expect(ram.read(0x08, 0x07)).toBe(0x22); // "
|
|
expect(ram.read(0x08, 0x08)).toBe(0x3a); // :
|
|
expect(ram.read(0x08, 0x09)).toBe(0x46); // F
|
|
expect(ram.read(0x08, 0x0a)).toBe(0x4F); // O
|
|
expect(ram.read(0x08, 0x0b)).toBe(0x52); // R
|
|
expect(ram.read(0x08, 0x0c)).toBe(0x49); // I
|
|
expect(ram.read(0x08, 0x0d)).toBe(0x3D); // =
|
|
expect(ram.read(0x08, 0x0e)).toBe(0x31); // 1
|
|
expect(ram.read(0x08, 0x0f)).toBe(0x54); // T
|
|
expect(ram.read(0x08, 0x10)).toBe(0x4F); // O
|
|
expect(ram.read(0x08, 0x11)).toBe(0x31); // 1
|
|
expect(ram.read(0x08, 0x12)).toBe(0x00); // end of line
|
|
});
|
|
|
|
it('does terminate DATA statements with a literal with two quotes at colon', () => {
|
|
const ram = new RAM(0, 0xff); // 64K of RAM
|
|
|
|
compileToMemory(ram, '10 DATAA"":FORI=1TO1');
|
|
expect(ram.read(0x08, 0x03)).toBe(10); // line number low
|
|
expect(ram.read(0x08, 0x04)).toBe(0); // line number high
|
|
expect(ram.read(0x08, 0x05)).toBe(0x83); // DATA
|
|
expect(ram.read(0x08, 0x06)).toBe(0x41); // A
|
|
expect(ram.read(0x08, 0x07)).toBe(0x22); // "
|
|
expect(ram.read(0x08, 0x08)).toBe(0x22); // "
|
|
expect(ram.read(0x08, 0x09)).toBe(0x3a); // :
|
|
expect(ram.read(0x08, 0x0a)).toBe(0x81); // FOR
|
|
expect(ram.read(0x08, 0x0b)).toBe(0x49); // I
|
|
expect(ram.read(0x08, 0x0c)).toBe(0xd0); // = (token)
|
|
expect(ram.read(0x08, 0x0d)).toBe(0x31); // 1
|
|
expect(ram.read(0x08, 0x0e)).toBe(0xc1); // TO
|
|
expect(ram.read(0x08, 0x0f)).toBe(0x31); // 1
|
|
expect(ram.read(0x08, 0x10)).toBe(0x00); // end of line
|
|
});
|
|
|
|
it('does not require a space after line number', () => {
|
|
const compiler = new ApplesoftCompiler();
|
|
compiler.compile('10PRINT"HELLO, WORLD!"');
|
|
expect(compiler.program()).toEqual(new Uint8Array([
|
|
0x16, 0x08, 0x0a, 0x00, 0xba, 0x22, 0x48, 0x45,
|
|
0x4c, 0x4c, 0x4f, 0x2c, 0x20, 0x57, 0x4f, 0x52,
|
|
0x4c, 0x44, 0x21, 0x22, 0x00, 0x00, 0x00
|
|
]));
|
|
});
|
|
|
|
it('parses ? as PRINT', () => {
|
|
const compiler = new ApplesoftCompiler();
|
|
compiler.compile('10 ?"HELLO, WORLD!"');
|
|
expect(compiler.program()).toEqual(new Uint8Array([
|
|
0x16, 0x08, 0x0a, 0x00, 0xba, 0x22, 0x48, 0x45,
|
|
0x4c, 0x4c, 0x4f, 0x2c, 0x20, 0x57, 0x4f, 0x52,
|
|
0x4c, 0x44, 0x21, 0x22, 0x00, 0x00, 0x00
|
|
]));
|
|
});
|
|
|
|
it('skips spaces when reading tokens', () => {
|
|
const compiler = new ApplesoftCompiler();
|
|
compiler.compile('10 T H E N');
|
|
expect(compiler.program()).toEqual(new Uint8Array([
|
|
0x07, 0x08, 0x0a, 0x00, 0xc4, 0x00, 0x00, 0x00,
|
|
]));
|
|
});
|
|
|
|
it('skips spaces and ignores case when reading tokens', () => {
|
|
const compiler = new ApplesoftCompiler();
|
|
compiler.compile('10 T h E n');
|
|
expect(compiler.program()).toEqual(new Uint8Array([
|
|
0x07, 0x08, 0x0a, 0x00, 0xc4, 0x00, 0x00, 0x00,
|
|
]));
|
|
});
|
|
|
|
it('smashes tokens together', () => {
|
|
const compiler = new ApplesoftCompiler();
|
|
compiler.compile('10 NOT RACE A THEN B');
|
|
expect(compiler.program()).toEqual(new Uint8Array([
|
|
0x0c, 0x08, 0x0a, 0x00, 0x9c, 0xc5, 0x48, 0x45,
|
|
0x4e, 0x42, 0x00, 0x00, 0x00,
|
|
]));
|
|
});
|
|
|
|
it('parses 10ATOZ correctly', () => {
|
|
const compiler = new ApplesoftCompiler();
|
|
compiler.compile('10ATOZ');
|
|
expect(compiler.program()).toEqual(new Uint8Array([
|
|
0x09, 0x08, 0x0a, 0x00, 0x41, 0xc1, 0x5a, 0x00,
|
|
0x00, 0x00,
|
|
]));
|
|
});
|
|
|
|
it('parses a bunch of crazy correctly', () => {
|
|
const compiler = new ApplesoftCompiler();
|
|
compiler.compile([
|
|
'10 A THEN B',
|
|
'30 A TO Z',
|
|
'40 AT N',
|
|
'50 A TN',
|
|
'60 N O T R A C E',
|
|
'70 NOT RACE'].join('\n'));
|
|
expect(compiler.program()).toEqual(new Uint8Array([
|
|
0x0b, 0x08, 0x0a, 0x00, 0xc5, 0x48, 0x45,
|
|
0x4e, 0x42, 0x00, 0x13, 0x08, 0x1e, 0x00, 0x41,
|
|
0xc1, 0x5a, 0x00, 0x1a, 0x08, 0x28, 0x00, 0xc5,
|
|
0x4e, 0x00, 0x20, 0x08, 0x32, 0x00, 0xe1, 0x00,
|
|
0x26, 0x08, 0x3c, 0x00, 0x9c, 0x00, 0x2c, 0x08,
|
|
0x46, 0x00, 0x9c, 0x00, 0x00, 0x00,
|
|
]));
|
|
});
|
|
});
|