mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-26 10:49:17 +00:00
basic: added scope start/end index, use for IF/ELSE and to skip FOR
This commit is contained in:
parent
e9d3fbcb62
commit
7d806850ee
@ -1 +1,2 @@
|
||||
10 PRINT "EXAMPLE BASIC PROGRAM"
|
||||
OPTION DIALECT MODERN
|
||||
print "This is an example BASIC program"
|
||||
|
@ -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'));
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user