basic: added scope start/end index, use for IF/ELSE and to skip FOR

This commit is contained in:
Steven Hugg 2020-08-20 14:56:28 -05:00
parent e9d3fbcb62
commit 7d806850ee
5 changed files with 62 additions and 39 deletions

View File

@ -1 +1,2 @@
10 PRINT "EXAMPLE BASIC PROGRAM"
OPTION DIALECT MODERN
print "This is an example BASIC program"

View File

@ -862,6 +862,7 @@ export abstract class BaseMAMEPlatform {
}
getCPUReg(reg:string) {
if (!this.loaded) return 0; // TODO
this.initlua();
return parseInt(this.luacall('return cpu.state.'+reg+'.value'));
}

View File

@ -119,6 +119,14 @@ export interface Statement extends SourceLocated {
command: string;
}
export interface ScopeStartStatement extends Statement {
endpc?: number;
}
export interface ScopeEndStatement extends Statement {
startpc?: number;
}
export interface PRINT_Statement extends Statement {
command: "PRINT";
args: Expr[];
@ -155,16 +163,16 @@ export interface ONGO_Statement extends Statement {
labels: Expr[];
}
export interface IF_Statement extends Statement {
export interface IF_Statement extends ScopeStartStatement {
command: "IF";
cond: Expr;
}
export interface ELSE_Statement extends Statement {
export interface ELSE_Statement extends ScopeStartStatement {
command: "ELSE";
}
export interface FOR_Statement extends Statement {
export interface FOR_Statement extends ScopeStartStatement {
command: "FOR";
lexpr: IndOp;
initial: Expr;
@ -172,17 +180,17 @@ export interface FOR_Statement extends Statement {
step?: Expr;
}
export interface NEXT_Statement extends Statement {
export interface NEXT_Statement extends ScopeEndStatement {
command: "NEXT";
lexpr?: IndOp;
}
export interface WHILE_Statement extends Statement {
export interface WHILE_Statement extends ScopeStartStatement {
command: "WHILE";
cond: Expr;
}
export interface WEND_Statement extends Statement {
export interface WEND_Statement extends ScopeEndStatement {
command: "WEND";
}
@ -234,10 +242,6 @@ export interface NoArgStatement extends Statement {
command: string;
}
export type ScopeStartStatement = (IF_Statement | FOR_Statement | WHILE_Statement) & SourceLocated;
export type ScopeEndStatement = NEXT_Statement | WEND_Statement;
export interface BASICProgram {
opts: BASICOptions;
stmts: Statement[];
@ -334,7 +338,7 @@ export class BASICParser {
vardefs: { [name: string]: IndOp }; // LET or DIM
varrefs: { [name: string]: SourceLocation }; // variable references
fnrefs: { [name: string]: string[] }; // DEF FN call graph
scopestack: ScopeStartStatement[];
scopestack: number[];
path : string;
lineno : number;
@ -443,11 +447,12 @@ export class BASICParser {
return this.stmts.length;
}
addStatement(stmt: Statement, cmdtok: Token, endtok?: Token) {
// check IF/THEN WHILE/WEND FOR/NEXT etc
this.modifyScope(stmt);
// set location for statement
if (endtok == null) endtok = this.peekToken();
stmt.$loc = { path: cmdtok.$loc.path, line: cmdtok.$loc.line, start: cmdtok.$loc.start, end: endtok.$loc.start, label: this.curlabel, offset: null };
// check IF/THEN WHILE/WEND FOR/NEXT etc
this.modifyScope(stmt);
// add to list
this.stmts.push(stmt);
}
addLabel(str: string) {
@ -623,7 +628,7 @@ export class BASICParser {
if (this.opts.compiledBlocks) {
var cmd = stmt.command;
if (cmd == 'FOR' || cmd == 'WHILE') {
this.pushScope(stmt as ScopeStartStatement);
this.scopestack.push(this.getPC()); // has to be before adding statment to list
} else if (cmd == 'NEXT') {
this.popScope(stmt as NEXT_Statement, 'FOR');
} else if (cmd == 'WEND') {
@ -631,18 +636,19 @@ export class BASICParser {
}
}
}
pushScope(stmt: ScopeStartStatement) {
this.scopestack.push(stmt);
}
popScope(stmt: ScopeEndStatement, open: string) {
var popstmt = this.scopestack.pop();
popScope(close: WEND_Statement|NEXT_Statement, open: string) {
var popidx = this.scopestack.pop();
var popstmt : ScopeStartStatement = popidx != null ? this.stmts[popidx] : null;
if (popstmt == null)
this.compileError(`There's a ${stmt.command} without a matching ${open}.`);
this.compileError(`There's a ${close.command} without a matching ${open}.`);
else if (popstmt.command != open)
this.compileError(`There's a ${stmt.command} paired with ${popstmt.command}, but it should be paired with ${open}.`);
else if (stmt.command == 'NEXT' && !this.opts.optionalNextVar
&& stmt.lexpr.name != (popstmt as FOR_Statement).lexpr.name)
this.compileError(`This NEXT statement is matched with the wrong FOR variable (${stmt.lexpr.name}).`);
this.compileError(`There's a ${close.command} paired with ${popstmt.command}, but it should be paired with ${open}.`);
else if (close.command == 'NEXT' && !this.opts.optionalNextVar
&& close.lexpr.name != (popstmt as FOR_Statement).lexpr.name)
this.compileError(`This NEXT statement is matched with the wrong FOR variable (${close.lexpr.name}).`);
// set start + end locations
close.startpc = popidx;
popstmt.endpc = this.getPC(); // has to be before adding statment to list
}
parseVarSubscriptOrFunc(): IndOp {
var tok = this.consumeToken();
@ -954,7 +960,7 @@ export class BASICParser {
stmt__IF(): void {
var cmdtok = this.lasttoken;
var cond = this.parseExpr();
var ifstmt = { command: "IF", cond: cond };
var ifstmt : IF_Statement = { command: "IF", cond: cond };
this.addStatement(ifstmt, cmdtok);
// we accept GOTO or THEN if line number provided (DEC accepts GO TO)
var thengoto = this.expectTokens(['THEN','GOTO','GO']);
@ -965,13 +971,18 @@ export class BASICParser {
// gotta parse it now because it's an end-of-statement token
if (this.peekToken().str == 'ELSE') {
this.expectToken('ELSE');
ifstmt.endpc = this.getPC() + 1;
this.stmt__ELSE();
} else {
ifstmt.endpc = this.getPC();
}
}
stmt__ELSE(): void {
this.addStatement({ command: "ELSE" }, this.lasttoken);
var elsestmt : ELSE_Statement = { command: "ELSE" };
this.addStatement(elsestmt, this.lasttoken);
// parse line number or statement clause
this.parseGotoOrStatements();
elsestmt.endpc = this.getPC();
}
parseGotoOrStatements() {
var lineno = this.peekToken();
@ -1223,7 +1234,7 @@ export class BASICParser {
}
checkScopes() {
if (this.opts.compiledBlocks && this.scopestack.length) {
var open = this.scopestack.pop();
var open = this.stmts[this.scopestack.pop()];
var close = {FOR:"NEXT", WHILE:"WEND", IF:"ENDIF"};
this.compileError(`Don't forget to add a matching ${close[open.command]} statement.`, open.$loc);
}

View File

@ -495,7 +495,7 @@ export class BASICRuntime {
this.runtimeError(`I expected ${fn.length} arguments for the ${expr.name} function, but I got ${nargs}.`);
}
startForLoop(forname, init, targ, step) {
startForLoop(forname:string, init:number, targ:number, step?:number, endpc?:number) {
// save start PC and label in case of hot reload (only works if FOR is first stmt in line)
var looppc = this.curpc - 1;
var looplabel = this.pc2label.get(looppc);
@ -507,8 +507,12 @@ export class BASICRuntime {
return step >= 0 ? this.vars[forname] > targ : this.vars[forname] < targ;
}
// skip entire for loop before first iteration? (Minimal BASIC)
if (this.opts.testInitialFor && loopdone())
return this.skipToAfterNext(forname);
if (this.opts.testInitialFor && loopdone()) {
if (endpc != null)
this.curpc = endpc+1;
else
this.skipToAfterNext(forname);
}
// save for var name on stack, remove existing entry
if (this.forLoopStack[forname] != null)
this.forLoopStack = this.forLoopStack.filter((n) => n == forname);
@ -767,7 +771,7 @@ export class BASICRuntime {
var init = this.expr2js(stmt.initial);
var targ = this.expr2js(stmt.target);
var step = stmt.step ? this.expr2js(stmt.step) : 'null';
return `this.startForLoop(${name}, ${init}, ${targ}, ${step})`;
return `this.startForLoop(${name}, ${init}, ${targ}, ${step}, ${stmt.endpc})`;
}
do__NEXT(stmt : basic.NEXT_Statement) {
@ -777,11 +781,17 @@ export class BASICRuntime {
do__IF(stmt : basic.IF_Statement) {
var cond = this.expr2js(stmt.cond);
return `if (!(${cond})) { this.skipToElse(); }`
if (stmt.endpc != null)
return `if (!(${cond})) { this.curpc = ${stmt.endpc}; }`
else
return `if (!(${cond})) { this.skipToElse(); }`
}
do__ELSE() {
return `this.skipToEOL()`
do__ELSE(stmt : basic.ELSE_Statement) {
if (stmt.endpc != null)
return `this.curpc = ${stmt.endpc}`
else
return `this.skipToEOL()`
}
do__WHILE(stmt : basic.WHILE_Statement) {

View File

@ -262,10 +262,10 @@ export class SourceEditor implements ProjectView {
}
clearErrors() {
this.editor.clearGutter("gutter-info");
this.refreshDebugState(false);
this.refreshDebugState(false); // TODO: why?
this.dirtylisting = true;
// clear line widgets
this.editor.clearGutter("gutter-info");
this.errormsgs = [];
while (this.errorwidgets.length) this.errorwidgets.shift().clear();
while (this.errormarks.length) this.errormarks.shift().clear();
@ -276,7 +276,7 @@ export class SourceEditor implements ProjectView {
updateListing() {
// update editor annotations
// TODO: recreate editor if gutter-bytes is used (verilog)
this.editor.clearGutter("gutter-info");
this.clearErrors();
this.editor.clearGutter("gutter-bytes");
this.editor.clearGutter("gutter-offset");
this.editor.clearGutter("gutter-clock");
@ -410,8 +410,8 @@ export class SourceEditor implements ProjectView {
this.dirtylisting = true;
}
if (!this.sourcefile || !this.dirtylisting) return;
this.dirtylisting = false;
this.updateListing();
this.dirtylisting = false;
}
refresh(moveCursor: boolean) {