1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2024-06-01 05:41:31 +00:00

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) { getCPUReg(reg:string) {
if (!this.loaded) return 0; // TODO
this.initlua(); this.initlua();
return parseInt(this.luacall('return cpu.state.'+reg+'.value')); return parseInt(this.luacall('return cpu.state.'+reg+'.value'));
} }

View File

@ -119,6 +119,14 @@ export interface Statement extends SourceLocated {
command: string; command: string;
} }
export interface ScopeStartStatement extends Statement {
endpc?: number;
}
export interface ScopeEndStatement extends Statement {
startpc?: number;
}
export interface PRINT_Statement extends Statement { export interface PRINT_Statement extends Statement {
command: "PRINT"; command: "PRINT";
args: Expr[]; args: Expr[];
@ -155,16 +163,16 @@ export interface ONGO_Statement extends Statement {
labels: Expr[]; labels: Expr[];
} }
export interface IF_Statement extends Statement { export interface IF_Statement extends ScopeStartStatement {
command: "IF"; command: "IF";
cond: Expr; cond: Expr;
} }
export interface ELSE_Statement extends Statement { export interface ELSE_Statement extends ScopeStartStatement {
command: "ELSE"; command: "ELSE";
} }
export interface FOR_Statement extends Statement { export interface FOR_Statement extends ScopeStartStatement {
command: "FOR"; command: "FOR";
lexpr: IndOp; lexpr: IndOp;
initial: Expr; initial: Expr;
@ -172,17 +180,17 @@ export interface FOR_Statement extends Statement {
step?: Expr; step?: Expr;
} }
export interface NEXT_Statement extends Statement { export interface NEXT_Statement extends ScopeEndStatement {
command: "NEXT"; command: "NEXT";
lexpr?: IndOp; lexpr?: IndOp;
} }
export interface WHILE_Statement extends Statement { export interface WHILE_Statement extends ScopeStartStatement {
command: "WHILE"; command: "WHILE";
cond: Expr; cond: Expr;
} }
export interface WEND_Statement extends Statement { export interface WEND_Statement extends ScopeEndStatement {
command: "WEND"; command: "WEND";
} }
@ -234,10 +242,6 @@ export interface NoArgStatement extends Statement {
command: string; command: string;
} }
export type ScopeStartStatement = (IF_Statement | FOR_Statement | WHILE_Statement) & SourceLocated;
export type ScopeEndStatement = NEXT_Statement | WEND_Statement;
export interface BASICProgram { export interface BASICProgram {
opts: BASICOptions; opts: BASICOptions;
stmts: Statement[]; stmts: Statement[];
@ -334,7 +338,7 @@ export class BASICParser {
vardefs: { [name: string]: IndOp }; // LET or DIM vardefs: { [name: string]: IndOp }; // LET or DIM
varrefs: { [name: string]: SourceLocation }; // variable references varrefs: { [name: string]: SourceLocation }; // variable references
fnrefs: { [name: string]: string[] }; // DEF FN call graph fnrefs: { [name: string]: string[] }; // DEF FN call graph
scopestack: ScopeStartStatement[]; scopestack: number[];
path : string; path : string;
lineno : number; lineno : number;
@ -443,11 +447,12 @@ export class BASICParser {
return this.stmts.length; return this.stmts.length;
} }
addStatement(stmt: Statement, cmdtok: Token, endtok?: Token) { addStatement(stmt: Statement, cmdtok: Token, endtok?: Token) {
// check IF/THEN WHILE/WEND FOR/NEXT etc
this.modifyScope(stmt);
// set location for statement // set location for statement
if (endtok == null) endtok = this.peekToken(); 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 }; 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); this.stmts.push(stmt);
} }
addLabel(str: string) { addLabel(str: string) {
@ -623,7 +628,7 @@ export class BASICParser {
if (this.opts.compiledBlocks) { if (this.opts.compiledBlocks) {
var cmd = stmt.command; var cmd = stmt.command;
if (cmd == 'FOR' || cmd == 'WHILE') { 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') { } else if (cmd == 'NEXT') {
this.popScope(stmt as NEXT_Statement, 'FOR'); this.popScope(stmt as NEXT_Statement, 'FOR');
} else if (cmd == 'WEND') { } else if (cmd == 'WEND') {
@ -631,18 +636,19 @@ export class BASICParser {
} }
} }
} }
pushScope(stmt: ScopeStartStatement) { popScope(close: WEND_Statement|NEXT_Statement, open: string) {
this.scopestack.push(stmt); var popidx = this.scopestack.pop();
} var popstmt : ScopeStartStatement = popidx != null ? this.stmts[popidx] : null;
popScope(stmt: ScopeEndStatement, open: string) {
var popstmt = this.scopestack.pop();
if (popstmt == 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) else if (popstmt.command != open)
this.compileError(`There's a ${stmt.command} paired with ${popstmt.command}, but it should be paired with ${open}.`); this.compileError(`There's a ${close.command} paired with ${popstmt.command}, but it should be paired with ${open}.`);
else if (stmt.command == 'NEXT' && !this.opts.optionalNextVar else if (close.command == 'NEXT' && !this.opts.optionalNextVar
&& stmt.lexpr.name != (popstmt as FOR_Statement).lexpr.name) && close.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(`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 { parseVarSubscriptOrFunc(): IndOp {
var tok = this.consumeToken(); var tok = this.consumeToken();
@ -954,7 +960,7 @@ export class BASICParser {
stmt__IF(): void { stmt__IF(): void {
var cmdtok = this.lasttoken; var cmdtok = this.lasttoken;
var cond = this.parseExpr(); var cond = this.parseExpr();
var ifstmt = { command: "IF", cond: cond }; var ifstmt : IF_Statement = { command: "IF", cond: cond };
this.addStatement(ifstmt, cmdtok); this.addStatement(ifstmt, cmdtok);
// we accept GOTO or THEN if line number provided (DEC accepts GO TO) // we accept GOTO or THEN if line number provided (DEC accepts GO TO)
var thengoto = this.expectTokens(['THEN','GOTO','GO']); 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 // gotta parse it now because it's an end-of-statement token
if (this.peekToken().str == 'ELSE') { if (this.peekToken().str == 'ELSE') {
this.expectToken('ELSE'); this.expectToken('ELSE');
ifstmt.endpc = this.getPC() + 1;
this.stmt__ELSE(); this.stmt__ELSE();
} else {
ifstmt.endpc = this.getPC();
} }
} }
stmt__ELSE(): void { 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 // parse line number or statement clause
this.parseGotoOrStatements(); this.parseGotoOrStatements();
elsestmt.endpc = this.getPC();
} }
parseGotoOrStatements() { parseGotoOrStatements() {
var lineno = this.peekToken(); var lineno = this.peekToken();
@ -1223,7 +1234,7 @@ export class BASICParser {
} }
checkScopes() { checkScopes() {
if (this.opts.compiledBlocks && this.scopestack.length) { 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"}; var close = {FOR:"NEXT", WHILE:"WEND", IF:"ENDIF"};
this.compileError(`Don't forget to add a matching ${close[open.command]} statement.`, open.$loc); 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}.`); 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) // 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 looppc = this.curpc - 1;
var looplabel = this.pc2label.get(looppc); var looplabel = this.pc2label.get(looppc);
@ -507,8 +507,12 @@ export class BASICRuntime {
return step >= 0 ? this.vars[forname] > targ : this.vars[forname] < targ; return step >= 0 ? this.vars[forname] > targ : this.vars[forname] < targ;
} }
// skip entire for loop before first iteration? (Minimal BASIC) // skip entire for loop before first iteration? (Minimal BASIC)
if (this.opts.testInitialFor && loopdone()) if (this.opts.testInitialFor && loopdone()) {
return this.skipToAfterNext(forname); if (endpc != null)
this.curpc = endpc+1;
else
this.skipToAfterNext(forname);
}
// save for var name on stack, remove existing entry // save for var name on stack, remove existing entry
if (this.forLoopStack[forname] != null) if (this.forLoopStack[forname] != null)
this.forLoopStack = this.forLoopStack.filter((n) => n == forname); this.forLoopStack = this.forLoopStack.filter((n) => n == forname);
@ -767,7 +771,7 @@ export class BASICRuntime {
var init = this.expr2js(stmt.initial); var init = this.expr2js(stmt.initial);
var targ = this.expr2js(stmt.target); var targ = this.expr2js(stmt.target);
var step = stmt.step ? this.expr2js(stmt.step) : 'null'; 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) { do__NEXT(stmt : basic.NEXT_Statement) {
@ -777,11 +781,17 @@ export class BASICRuntime {
do__IF(stmt : basic.IF_Statement) { do__IF(stmt : basic.IF_Statement) {
var cond = this.expr2js(stmt.cond); 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() { do__ELSE(stmt : basic.ELSE_Statement) {
return `this.skipToEOL()` if (stmt.endpc != null)
return `this.curpc = ${stmt.endpc}`
else
return `this.skipToEOL()`
} }
do__WHILE(stmt : basic.WHILE_Statement) { do__WHILE(stmt : basic.WHILE_Statement) {

View File

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