import ApplesoftDecompiler from 'js/applesoft/decompiler'; import ApplesoftCompiler from 'js/applesoft/compiler'; import RAM from 'js/ram'; import { Memory } from 'js/types'; function decompileFromMemory(ram: Memory): string { const decompiler = ApplesoftDecompiler.decompilerFromMemory(ram); return decompiler.list(); } describe('ApplesoftDecompiler', () => { it('decompiles one-line program from memory', () => { const ram = new RAM(0x00, 0xff); // 64K ApplesoftCompiler.compileToMemory(ram, '10 PRINT "Hello, World!"'); const program = decompileFromMemory(ram); expect(program).toEqual(' 10 PRINT "Hello, World!"\n'); }); it('decompiles REM statements correctly', () => { const ram = new RAM(0x00, 0xff); // 64K ApplesoftCompiler.compileToMemory(ram, '10 REMNo space before\n20 REM with space'); const program = decompileFromMemory(ram); expect(program).toEqual(' 10 REM No space before\n 20 REM with space\n'); }); it('lists a one-line program', () => { const compiler = new ApplesoftCompiler(); compiler.compile('10 PRINT "Hello, World!"'); const decompiler = new ApplesoftDecompiler(compiler.program()); const program = decompiler.list(); expect(program).toEqual(' 10 PRINT "Hello, World!"\n'); }); it('correctly computes the base address when 0 is passed in', () => { const compiler = new ApplesoftCompiler(); compiler.compile('10 PRINT "Hello, World!"\n20 GOTO 10'); const decompiler = new ApplesoftDecompiler(compiler.program(), 0); const program = decompiler.list(); expect(program).toEqual(' 10 PRINT "Hello, World!"\n 20 GOTO 10\n'); }); it('lists a program with a long line', () => { const compiler = new ApplesoftCompiler(); compiler.compile('10 PRINT "Hello, World!"\n' + '20 PRINT "Hello, again, with a much longer line this time."\n' + '30 REM1234567890123456789012345678901234567890'); const decompiler = new ApplesoftDecompiler(compiler.program()); const program = decompiler.list(); expect(program).toEqual(' 10 PRINT "Hello, World!"\n' + ' 20 PRINT "Hello, again, with a \n' + ' much longer line this time."\n' + ' \n' + ' 30 REM 123456789012345678901234\n' + ' 5678901234567890\n'); }); it('lists a program with a long line Apple ][+-style', () => { const compiler = new ApplesoftCompiler(); compiler.compile('10 PRINT "Hello, World!"\n' + '20 PRINT "Hello, again, with a much longer line this time."\n' + '30 REM1234567890123456789012345678901234567890'); const decompiler = new ApplesoftDecompiler(compiler.program()); const program = decompiler.list({ apple2: 'plus' }); expect(program).toEqual('10 PRINT "Hello, World!"\n' + '20 PRINT "Hello, again, with a m\n' + ' uch longer line this time."\n' + '30 REM 1234567890123456789012345\n' + ' 678901234567890\n'); }); it('lists a range of lines', () => { const compiler = new ApplesoftCompiler(); compiler.compile('10 PRINT "Hello, World!"\n' + '20 PRINT "Hello, again, with a much longer line this time."\n' + '30 REM1234567890123456789012345678901234567890'); const decompiler = new ApplesoftDecompiler(compiler.program()); const program = decompiler.list({}, 10, 20); expect(program).toEqual(' 10 PRINT "Hello, World!"\n' + ' 20 PRINT "Hello, again, with a \n' + ' much longer line this time."\n' + ' \n'); }); it('lists weird code correctly', () => { const compiler = new ApplesoftCompiler(); compiler.compile('10 NOT RACE A THEN B'); const decompiler = new ApplesoftDecompiler(compiler.program()); const program = decompiler.list(); expect(program).toEqual(' 10 NOTRACE AT HENB\n'); }); it('lists 10ATOZ correctly', () => { const compiler = new ApplesoftCompiler(); compiler.compile('10ATOZ'); const decompiler = new ApplesoftDecompiler(compiler.program()); const program = decompiler.list(); expect(program).toEqual(' 10 A TO Z\n'); }); it('wraps correctly in 80-column mode', () => { const compiler = new ApplesoftCompiler(); compiler.compile('10 ?:?:?:?:?:?:?:?:?:?:?:?:?:?:?:?'); const decompiler = new ApplesoftDecompiler(compiler.program()); const program = decompiler.list({ columns: 80 }); expect(program).toEqual(' 10 PRINT : PRINT : PRINT : PRINT : ' + 'PRINT : PRINT : PRINT : PRINT : PRINT \n' + ' : PRINT : PRINT : PRINT : PRINT : PRINT : PRINT : ' + 'PRINT \n'); }); it('decompiles compactly', () => { const compiler = new ApplesoftCompiler(); compiler.compile('10 ?:?:?:?:?:?:?:?:?:?:?:?:?:?:?:?'); const decompiler = new ApplesoftDecompiler(compiler.program()); const program = decompiler.decompile({ style: 'compact' }); expect(program).toEqual('10?:?:?:?:?:?:?:?:?:?:?:?:?:?:?:?'); }); it('when decompiling compactly, adds a space after the line', () => { const compiler = new ApplesoftCompiler(); compiler.compile('10 12345'); const decompiler = new ApplesoftDecompiler(compiler.program()); const program = decompiler.decompile({ style: 'compact' }); expect(program).toEqual('10 12345'); }); it('when decompiling compactly, adds a space after AT for token', () => { const compiler = new ApplesoftCompiler(); compiler.compile('10 AT NEXT'); const decompiler = new ApplesoftDecompiler(compiler.program()); const program = decompiler.decompile({ style: 'compact' }); expect(program).toEqual('10AT NEXT'); }); it('when decompiling compactly, adds a space after AT for literal', () => { const compiler = new ApplesoftCompiler(); compiler.compile('10 AT n'); const decompiler = new ApplesoftDecompiler(compiler.program()); const program = decompiler.decompile({ style: 'compact' }); expect(program).toEqual('10AT N'); }); it('when decompiling compactly, decompiles 10ATOZ correctly', () => { const compiler = new ApplesoftCompiler(); compiler.compile('10ATOZ'); const decompiler = new ApplesoftDecompiler(compiler.program()); const program = decompiler.decompile({ style: 'compact' }); expect(program).toEqual('10ATOZ'); }); it('when decompiling compactly, adds a space to disambiguate tokens', () => { 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')); const decompiler = new ApplesoftDecompiler(compiler.program()); const program = decompiler.decompile({ style: 'compact' }); expect(program).toEqual([ '10ATHENB', '30ATOZ', '40AT N', '50ATN', '60NOTRACE', '70NOTRACE'].join('\n')); }); it('when decompiling prettily, formats reasonably well', () => { const compiler = new ApplesoftCompiler(); compiler.compile('10 FORI=1TO10:PRINTI:NEXT'); const decompiler = new ApplesoftDecompiler(compiler.program()); const program = decompiler.decompile({ style: 'pretty' }); expect(program).toEqual('10 FOR I = 1 TO 10 : PRINT I : NEXT'); }); it('when decompiling prettily, formats relations', () => { const compiler = new ApplesoftCompiler(); compiler.compile('10 IFA=BORB<=AORB=A THEN'); const decompiler = new ApplesoftDecompiler(compiler.program()); const program = decompiler.decompile({ style: 'pretty' }); expect(program).toEqual('10 IF A < B OR A >= B OR B <= A OR B = AT HEN'); }); it('when decompiling prettily, decompiles 10ATOZ correctly', () => { const compiler = new ApplesoftCompiler(); compiler.compile('10ATOZ'); const decompiler = new ApplesoftDecompiler(compiler.program()); const program = decompiler.decompile({ style: 'pretty' }); expect(program).toEqual('10 A TO Z'); }); it('when decompiling prettily, does not insert extra spaces in strings', () => { const compiler = new ApplesoftCompiler(); compiler.compile('10A="::::":B=","'); const decompiler = new ApplesoftDecompiler(compiler.program()); const program = decompiler.decompile({ style: 'pretty' }); expect(program).toEqual('10 A = "::::" : B = ","'); }); it('when decompiling prettily, inserts space after comma', () => { const compiler = new ApplesoftCompiler(); compiler.compile('10 HPLOTX,Y:GOTO10'); const decompiler = new ApplesoftDecompiler(compiler.program()); const program = decompiler.decompile({ style: 'pretty' }); expect(program).toEqual('10 HPLOT X, Y : GOTO 10'); }); });