Add quick and dirty Applesoft compiler.

This commit is contained in:
Will Scullin 2019-12-02 18:55:01 -08:00
parent 060b9845c6
commit 409e627abc
No known key found for this signature in database
GPG Key ID: 9092A5C0A673416B
3 changed files with 298 additions and 12 deletions

287
js/applesoft/compiler.js Normal file
View File

@ -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);
}
};
}

View File

@ -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;
}

View File

@ -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('<pre>');
wind.document.write(dumper.toString());
wind.document.write('</pre>');
wind.document.close();
debug(dumper.toString());
}
export function compileProgram(program) {
compiler.compile(program);
}
export function step()