From 409e627abcb9eeeeaaf415d44402225fa18eab4d Mon Sep 17 00:00:00 2001 From: Will Scullin Date: Mon, 2 Dec 2019 18:55:01 -0800 Subject: [PATCH] Add quick and dirty Applesoft compiler. --- js/applesoft/compiler.js | 287 +++++++++++++++++++++++++++++++++++++ js/applesoft/decompiler.js | 8 +- js/main2e.js | 15 +- 3 files changed, 298 insertions(+), 12 deletions(-) create mode 100644 js/applesoft/compiler.js diff --git a/js/applesoft/compiler.js b/js/applesoft/compiler.js new file mode 100644 index 0000000..dbf0d4c --- /dev/null +++ b/js/applesoft/compiler.js @@ -0,0 +1,287 @@ +export default function ApplesoftCompiler(mem) +{ + var _mem = mem; + + var LOMEM = 0x69; + var ARRAY_START = 0x6B; + var ARRAY_END = 0x6D; + var PROGRAM_START = 0x801; + + var TOKENS = { + 'END': 0x80, + 'FOR': 0x81, + 'NEXT': 0x82, + 'DATA': 0x83, + 'INPUT': 0x84, + 'DEL': 0x85, + 'DIM': 0x86, + 'READ': 0x87, + 'GR': 0x88, + 'TEXT': 0x89, + 'PR#': 0x8a, + 'IN#': 0x8b, + 'CALL': 0x8c, + 'PLOT': 0x8d, + 'HLIN': 0x8e, + 'VLIN': 0x8f, + 'HGR2': 0x90, + 'HGR': 0x91, + 'HCOLOR=': 0x92, + 'HPLOT': 0x93, + 'DRAW': 0x94, + 'XDRAW': 0x95, + 'HTAB': 0x96, + 'HOME': 0x97, + 'ROT=': 0x98, + 'SCALE=': 0x99, + 'SHLOAD': 0x9a, + 'TRACE': 0x9b, + 'NOTRACE': 0x9c, + 'NORMAL': 0x9d, + 'INVERSE': 0x9e, + 'FLASH': 0x9f, + 'COLOR=': 0xa0, + 'POP=': 0xa1, + 'VTAB': 0xa2, + 'HIMEM:': 0xa3, + 'LOMEM:': 0xa4, + 'ONERR': 0xa5, + 'RESUME': 0xa6, + 'RECALL': 0xa7, + 'STORE': 0xa8, + 'SPEED=': 0xa9, + 'LET': 0xaa, + 'GOTO': 0xab, + 'RUN': 0xac, + 'IF': 0xad, + 'RESTORE': 0xae, + '&': 0xaf, + 'GOSUB': 0xb0, + 'RETURN': 0xb1, + 'REM': 0xb2, + 'STOP': 0xb3, + 'ON': 0xb4, + 'WAIT': 0xb5, + 'LOAD': 0xb6, + 'SAVE': 0xb7, + 'DEF': 0xb8, + 'POKE': 0xb9, + 'PRINT': 0xba, + 'CONT': 0xbb, + 'LIST': 0xbc, + 'CLEAR': 0xbd, + 'GET': 0xbe, + 'NEW': 0xbf, + 'TAB(': 0xc0, + 'TO': 0xc1, + 'FN': 0xc2, + 'SPC(': 0xc3, + 'THEN': 0xc4, + 'AT': 0xc5, + 'NOT': 0xc6, + 'STEP': 0xc7, + '+': 0xc8, + '-': 0xc9, + '*': 0xca, + '/': 0xcb, + '^': 0xcc, + 'AND': 0xcd, + 'OR': 0xce, + '>': 0xcf, + '=': 0xd0, + '<': 0xd1, + 'SGN': 0xd2, + 'INT': 0xd3, + 'ABS': 0xd4, + 'USR': 0xd5, + 'FRE': 0xd6, + 'SCRN(': 0xd7, + 'PDL': 0xd8, + 'POS': 0xd9, + 'SQR': 0xda, + 'RND': 0xdb, + 'LOG': 0xdc, + 'EXP': 0xdd, + 'COS': 0xde, + 'SIN': 0xdf, + 'TAN': 0xe0, + 'ATN': 0xe1, + 'PEEK': 0xe2, + 'LEN': 0xe3, + 'STR$': 0xe4, + 'VAL': 0xe5, + 'ASC': 0xe6, + 'CHR$': 0xe7, + 'LEFT$': 0xe8, + 'RIGHT$': 0xe9, + 'MID$': 0xea + }; + + var STATES = { + NORMAL: 0, + STRING: 1, + COMMENT: 2, + DATA: 3 + }; + + function writeByte(addr, val) { + var page = addr >> 8, + off = addr & 0xff; + + return _mem.write(page, off, val); + } + + function writeWord(addr, val) { + var lsb = val & 0xff; + var msb = val >> 8; + + writeByte(addr, lsb); + writeByte(addr + 1, msb); + } + + return { + compile: function(program) { + var lineNos = {}; + + function compileLine(line, offset) { + if (!line) { + return []; + } + + var state = STATES.NORMAL; + var result = [0, 0, 0, 0]; + var curChar = 0; + var character; + var lineNoStr = ''; + + while (line.length) { + character = line.charAt(curChar); + if (/\d/.test(character)) { + lineNoStr += character; + curChar++; + } else { + break; + } + } + + while (curChar < line.length) { + character = line.charAt(curChar).toUpperCase(); + switch (state) { + case STATES.NORMAL: + if (character !== ' ') { + if (character === '"') { + result.push(character.charCodeAt(0)); + state = STATES.STRING; + curChar++; + } else { + var foundToken = ''; + for (var possibleToken in TOKENS) { + if (possibleToken.charAt(0) == character) { + var tokenIdx = curChar + 1; + var idx = 1; + while (idx < possibleToken.length) { + if (line.charAt(tokenIdx) !== ' ') { + if (line.charAt(tokenIdx).toUpperCase() !== possibleToken.charAt(idx)) { + break; + } + idx++; + } + tokenIdx++; + } + if (idx === possibleToken.length) { + // Found a token + if (possibleToken === 'AT') { + var lookAhead = line.charAt(tokenIdx + 1).toUpperCase(); + // ATN takes precedence over AT + if (lookAhead === 'N') { + foundToken = 'ATN'; + tokenIdx++; + } + // TO takes precedence over AT + if (lookAhead === 'O') { + result.push(lookAhead.charCodeAt(0)); + foundToken = 'TO'; + tokenIdx++; foundToken = 'TO'; + } + } + foundToken = possibleToken; + } + } + if (foundToken) { + break; + } + } + if (foundToken) { + result.push(TOKENS[foundToken]); + curChar = tokenIdx; + if (foundToken === 'REM') { + state = STATES.COMMENT; + } + } else { + result.push(character.charCodeAt(0)); + curChar++; + } + } + } else { + curChar++; + } + break; + case STATES.COMMENT: + result.push(character.charCodeAt(0)); + curChar++; + break; + case STATES.STRING: + result.push(character.charCodeAt(0)); + if (character == '"') { + state = STATES.NORMAL; + } + curChar++; + break; + } + } + + if (lineNoStr.length) { + var lineNo = parseInt(lineNoStr, 10); + if (lineNo < 0 || lineNo > 65535) { + throw new Error('Line number out of range'); + } + if (lineNos[lineNoStr]) { + throw new Error('Duplicate line number'); + } + lineNos[lineNoStr] = result; + + // Next line pointer + result.push(0); + var nextLine = offset + result.length; + result[0] = nextLine & 0xff; + result[1] = nextLine >> 8; + + // Line number + result[2] = lineNo & 0xff; + result[3] = lineNo >> 8; + } else { + throw new Error('Missing line number'); + } + + return result; + } + + var compiled = []; + var lines = program.split(/[\r\n]+/g); + + while (lines.length) { + var line = lines.shift(); + var compiledLine = compileLine(line, PROGRAM_START + compiled.length); + compiled = compiled.concat(compiledLine); + } + compiled.push(0, 0); + + for (var idx = 0; idx < compiled.length; idx++) { + writeByte(PROGRAM_START + idx, compiled[idx]); + } + writeWord(LOMEM, PROGRAM_START + compiled.length); + writeWord(ARRAY_START, PROGRAM_START + compiled.length); + writeWord(ARRAY_END, PROGRAM_START + compiled.length); + } + }; +} diff --git a/js/applesoft/decompiler.js b/js/applesoft/decompiler.js index 8c4b42d..89d5a42 100644 --- a/js/applesoft/decompiler.js +++ b/js/applesoft/decompiler.js @@ -1,5 +1,3 @@ -import { debug } from '../util'; - export default function ApplesoftDump(mem) { var _mem = mem; @@ -123,14 +121,14 @@ export default function ApplesoftDump(mem) var page = addr >> 8, off = addr & 0xff; - return _mem.read(page, off); + return _mem.read(page, off, true); } function readWord(addr) { var lsb, msb; - lsb = readByte(addr, debug); - msb = readByte(addr + 1, debug); + lsb = readByte(addr); + msb = readByte(addr + 1); return (msb << 8) | lsb; } diff --git a/js/main2e.js b/js/main2e.js index f35811c..dbd679a 100644 --- a/js/main2e.js +++ b/js/main2e.js @@ -2,6 +2,7 @@ import MicroModal from 'micromodal'; import Apple2IO from './apple2io'; import ApplesoftDump from './applesoft/decompiler'; +import ApplesoftCompiler from './applesoft/compiler'; import { HiresPage, LoresPage, VideoModes } from './canvas'; import CPU6502 from './cpu6502'; import MMU from './mmu'; @@ -304,7 +305,7 @@ default: } var runTimer = null; -var cpu = new CPU6502({'65C02': enhanced}); +export var cpu = new CPU6502({'65C02': enhanced}); var context1, context2, context3, context4; @@ -339,6 +340,7 @@ var vm = new VideoModes(gr, hgr, gr2, hgr2, true); vm.enhanced(enhanced); vm.multiScreen(multiScreen); var dumper = new ApplesoftDump(cpu); +var compiler = new ApplesoftCompiler(cpu); driveLights = new DriveLights(); var io = new Apple2IO(cpu, vm); @@ -411,12 +413,11 @@ function dumpDisk(drive) { } export function dumpProgram() { - var wind = window.open('', '_blank'); - wind.document.title = 'Program Listing'; - wind.document.write('
');
-    wind.document.write(dumper.toString());
-    wind.document.write('
'); - wind.document.close(); + debug(dumper.toString()); +} + +export function compileProgram(program) { + compiler.compile(program); } export function step()