1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2024-12-29 11:31:42 +00:00

basic: better DATA parsing, RESTORE <line> fixed, REM back as token (24, 57, 76)

This commit is contained in:
Steven Hugg 2020-08-13 23:34:54 -05:00
parent 4767ed8429
commit a12cebfde4
2 changed files with 80 additions and 23 deletions

View File

@ -58,13 +58,12 @@ export class CompileError extends Error {
} }
// Lexer regular expression -- each (capture group) handles a different token type // Lexer regular expression -- each (capture group) handles a different token type
// FLOAT INT HEXOCTAL REMARK IDENT STRING RELOP EXP OPERATORS OTHER WS
const re_toks = /([0-9.]+[E][+-]?\d+)|(\d*[.]\d*[E0-9]*)|[0]*(\d+)|&([OH][0-9A-F]+)|(['].*|REM.*)|(\w+[$]?)|(".*?")|([<>]?[=<>#])|(\*\*)|([-+*/^,;:()\[\]?\\])|(\S+)/gi; const re_toks = /([0-9.]+[E][+-]?\d+|\d+[.][E0-9]*|[.][E0-9]+)|[0]*(\d+)|&([OH][0-9A-F]+)|(['].*)|(\w+[$]?)|(".*?")|([<>]?[=<>#])|(\*\*)|([-+*/^,;:()\[\]?\\])|(\S+)|(\s+)/gi;
export enum TokenType { export enum TokenType {
EOL = 0, EOL = 0,
Float1, Float,
Float2,
Int, Int,
HexOctalInt, HexOctalInt,
Remark, Remark,
@ -74,6 +73,7 @@ export enum TokenType {
DoubleStar, DoubleStar,
Operator, Operator,
CatchAll, CatchAll,
Whitespace,
_LAST, _LAST,
} }
@ -180,7 +180,7 @@ export interface INPUT_Statement {
export interface DATA_Statement { export interface DATA_Statement {
command: "DATA"; command: "DATA";
datums: Expr[]; datums: Literal[];
} }
export interface READ_Statement { export interface READ_Statement {
@ -376,8 +376,7 @@ export class BASICParser {
this.curlabel = tok.str; this.curlabel = tok.str;
break; break;
case TokenType.HexOctalInt: case TokenType.HexOctalInt:
case TokenType.Float1: case TokenType.Float:
case TokenType.Float2:
this.compileError(`Line numbers must be positive integers.`); this.compileError(`Line numbers must be positive integers.`);
break; break;
default: default:
@ -388,7 +387,7 @@ export class BASICParser {
} }
parseFile(file: string, path: string) : BASICProgram { parseFile(file: string, path: string) : BASICProgram {
this.path = path; this.path = path;
var txtlines = file.split("\n"); var txtlines = file.split(/\n|\r\n/);
var pgmlines = txtlines.map((line) => this.parseLine(line)); var pgmlines = txtlines.map((line) => this.parseLine(line));
var program = { opts: this.opts, lines: pgmlines }; var program = { opts: this.opts, lines: pgmlines };
this.checkAll(program); this.checkAll(program);
@ -410,9 +409,10 @@ export class BASICParser {
// split identifier regex (if token-crunching enabled) // split identifier regex (if token-crunching enabled)
let splitre = this.opts.optionalWhitespace && new RegExp('('+this.opts.validKeywords.map(s => `${s}`).join('|')+')'); let splitre = this.opts.optionalWhitespace && new RegExp('('+this.opts.validKeywords.map(s => `${s}`).join('|')+')');
// iterate over each token via re_toks regex // iterate over each token via re_toks regex
var lastTokType = TokenType.CatchAll;
var m : RegExpMatchArray; var m : RegExpMatchArray;
while (m = re_toks.exec(line)) { while (m = re_toks.exec(line)) {
for (var i = 1; i < TokenType._LAST; i++) { for (var i = 1; i <= lastTokType; i++) {
let s : string = m[i]; let s : string = m[i];
if (s != null) { if (s != null) {
let loc = { path: this.path, line: this.lineno, start: m.index, end: m.index+s.length, label: this.curlabel }; let loc = { path: this.path, line: this.lineno, start: m.index, end: m.index+s.length, label: this.curlabel };
@ -420,8 +420,16 @@ export class BASICParser {
if (this.opts.asciiOnly && !/^[\x00-\x7F]*$/.test(s)) if (this.opts.asciiOnly && !/^[\x00-\x7F]*$/.test(s))
this.dialectError(`non-ASCII characters`); this.dialectError(`non-ASCII characters`);
// uppercase all identifiers, and maybe more // uppercase all identifiers, and maybe more
if (i == TokenType.Ident || i == TokenType.HexOctalInt || this.opts.uppercaseOnly) if (i == TokenType.Ident || i == TokenType.HexOctalInt || this.opts.uppercaseOnly) {
s = s.toUpperCase(); s = s.toUpperCase();
// DATA statement captures whitespace too
if (s == 'DATA') lastTokType = TokenType.Whitespace;
// REM means ignore rest of statement
if (lastTokType == TokenType.CatchAll && s.startsWith('REM')) {
s = 'REM';
lastTokType = TokenType.EOL;
}
}
// convert brackets // convert brackets
if (s == '[' || s == ']') { if (s == '[' || s == ']') {
if (!this.opts.squareBrackets) this.dialectError(`square brackets`); if (!this.opts.squareBrackets) this.dialectError(`square brackets`);
@ -487,6 +495,8 @@ export class BASICParser {
case TokenType.Operator: case TokenType.Operator:
if (cmd == this.validKeyword('?')) cmd = 'PRINT'; if (cmd == this.validKeyword('?')) cmd = 'PRINT';
case TokenType.Ident: case TokenType.Ident:
// ignore remarks
if (cmd == 'REM') return null;
// look for "GO TO" and "GO SUB" // look for "GO TO" and "GO SUB"
if (cmd == 'GO' && this.peekToken().str == 'TO') { if (cmd == 'GO' && this.peekToken().str == 'TO') {
this.consumeToken(); this.consumeToken();
@ -582,19 +592,60 @@ export class BASICParser {
else this.compileError(`There should be a line number here.`); else this.compileError(`There should be a line number here.`);
} }
} }
parsePrimary(): Expr { parseDatumList(): Literal[] {
let tok = this.consumeToken(); return this.parseList(this.parseDatum, ',');
}
parseDatum(): Literal {
var tok = this.consumeToken();
// get rid of leading whitespace
while (tok.type == TokenType.Whitespace)
tok = this.consumeToken();
if (isEOS(tok)) this.compileError(`There should be a datum here.`);
// parse constants
if (tok.type <= TokenType.HexOctalInt) {
return this.parseValue(tok);
}
if (tok.str == '-' && this.peekToken().type <= TokenType.HexOctalInt) {
tok = this.consumeToken();
return { value: -this.parseValue(tok).value };
}
if (tok.str == '+' && this.peekToken().type <= TokenType.HexOctalInt) {
tok = this.consumeToken();
return this.parseValue(tok);
}
// concat all stuff including whitespace
// TODO: should trim whitespace only if not quoted string
var s = '';
while (!isEOS(tok) && tok.str != ',') {
s += this.parseValue(tok).value;
tok = this.consumeToken();
}
this.pushbackToken(tok);
return { value: s }; // trim leading and trailing whitespace
}
parseValue(tok: Token): Literal {
switch (tok.type) { switch (tok.type) {
case TokenType.HexOctalInt: case TokenType.HexOctalInt:
if (!this.opts.hexOctalConsts) this.dialectError(`hex/octal constants`); if (!this.opts.hexOctalConsts) this.dialectError(`hex/octal constants`);
let base = tok.str.startsWith('H') ? 16 : 8; let base = tok.str.startsWith('H') ? 16 : 8;
return { value: parseInt(tok.str.substr(1), base) }; return { value: parseInt(tok.str.substr(1), base) };
case TokenType.Int: case TokenType.Int:
case TokenType.Float1: case TokenType.Float:
case TokenType.Float2:
return { value: this.parseNumber(tok.str) }; return { value: this.parseNumber(tok.str) };
case TokenType.String: case TokenType.String:
return { value: stripQuotes(tok.str) }; return { value: stripQuotes(tok.str) };
default:
return { value: tok.str }; // only used in DATA statement
}
}
parsePrimary(): Expr {
let tok = this.consumeToken();
switch (tok.type) {
case TokenType.HexOctalInt:
case TokenType.Int:
case TokenType.Float:
case TokenType.String:
return this.parseValue(tok);
case TokenType.Ident: case TokenType.Ident:
if (tok.str == 'NOT') { if (tok.str == 'NOT') {
let expr = this.parsePrimary(); let expr = this.parsePrimary();
@ -810,7 +861,7 @@ export class BASICParser {
} }
// TODO: DATA statement doesn't read unquoted strings // TODO: DATA statement doesn't read unquoted strings
stmt__DATA() : DATA_Statement { stmt__DATA() : DATA_Statement {
return { command:'DATA', datums:this.parseExprList() }; return { command:'DATA', datums:this.parseDatumList() };
} }
stmt__READ() : READ_Statement { stmt__READ() : READ_Statement {
return { command:'READ', args:this.parseLexprList() }; return { command:'READ', args:this.parseLexprList() };

View File

@ -71,6 +71,7 @@ export class BASICRuntime {
pc2line : Map<number,number>; pc2line : Map<number,number>;
label2lineidx : {[label : string] : number}; label2lineidx : {[label : string] : number};
label2pc : {[label : string] : number}; label2pc : {[label : string] : number};
label2dataptr : {[label : string] : number};
datums : basic.Literal[]; datums : basic.Literal[];
builtins : {}; builtins : {};
opts : basic.BASICOptions; opts : basic.BASICOptions;
@ -97,6 +98,7 @@ export class BASICRuntime {
this.opts = program.opts; this.opts = program.opts;
this.label2lineidx = {}; this.label2lineidx = {};
this.label2pc = {}; this.label2pc = {};
this.label2dataptr = {};
this.allstmts = []; this.allstmts = [];
this.line2pc = []; this.line2pc = [];
this.pc2line = new Map(); this.pc2line = new Map();
@ -119,11 +121,10 @@ export class BASICRuntime {
}); });
// parse DATA literals // parse DATA literals
this.allstmts.filter((stmt) => stmt.command == 'DATA').forEach((datastmt) => { this.allstmts.filter((stmt) => stmt.command == 'DATA').forEach((datastmt) => {
(datastmt as basic.DATA_Statement).datums.forEach(d => { (datastmt as basic.DATA_Statement).datums.forEach(datum => {
this.curpc = datastmt.$loc.offset; // for error reporting this.curpc = datastmt.$loc.offset; // for error reporting
var functext = this.expr2js(d, {isconst:true}); this.label2dataptr[datastmt.$loc.label] = this.datums.length;
var value = new Function(`return ${functext};`).bind(this)(); this.datums.push(datum);
this.datums.push({value:value});
}); });
}); });
// try to resume where we left off after loading // try to resume where we left off after loading
@ -512,8 +513,9 @@ export class BASICRuntime {
} }
// converts a variable to string/number based on var name // converts a variable to string/number based on var name
assign(name: string, right: number|string) : number|string { assign(name: string, right: number|string, isRead?:boolean) : number|string {
if (this.opts.typeConvert) // convert data? READ always converts if read into string
if (this.opts.typeConvert || (isRead && name.endsWith("$")))
return this.convert(name, right); return this.convert(name, right);
// TODO: use options // TODO: use options
if (name.endsWith("$")) { if (name.endsWith("$")) {
@ -763,6 +765,10 @@ export class BASICRuntime {
return `this.onGosubLabel(${expr}, ${labels})`; return `this.onGosubLabel(${expr}, ${labels})`;
} }
do__ONGOSUB(stmt : basic.ONGO_Statement) {
return this.do__ONGOTO(stmt);
}
do__DATA() { do__DATA() {
// data is preprocessed // data is preprocessed
} }
@ -770,14 +776,14 @@ export class BASICRuntime {
do__READ(stmt : basic.READ_Statement) { do__READ(stmt : basic.READ_Statement) {
var s = ''; var s = '';
stmt.args.forEach((arg) => { stmt.args.forEach((arg) => {
s += `${this.assign2js(arg)} = this.assign(${JSON.stringify(arg.name)}, this.nextDatum());`; s += `${this.assign2js(arg)} = this.assign(${JSON.stringify(arg.name)}, this.nextDatum(), true);`;
}); });
return s; return s;
} }
do__RESTORE(stmt : basic.RESTORE_Statement) { do__RESTORE(stmt : basic.RESTORE_Statement) {
if (stmt.label != null) if (stmt.label != null)
return `this.dataptr = this.label2pc[${this.expr2js(stmt.label, {isconst:true})}] || 0`; return `this.dataptr = this.label2dataptr[${this.expr2js(stmt.label, {isconst:true})}] || 0`;
else else
return `this.dataptr = 0`; return `this.dataptr = 0`;
} }