diff --git a/presets/basic/skeleton.basic b/presets/basic/skeleton.basic index e86e9f18..109a60b6 100644 --- a/presets/basic/skeleton.basic +++ b/presets/basic/skeleton.basic @@ -1 +1,2 @@ -10 PRINT "EXAMPLE BASIC PROGRAM" +OPTION DIALECT MODERN +print "This is an example BASIC program" diff --git a/src/common/baseplatform.ts b/src/common/baseplatform.ts index fda1eba4..630d3f96 100644 --- a/src/common/baseplatform.ts +++ b/src/common/baseplatform.ts @@ -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')); } diff --git a/src/common/basic/compiler.ts b/src/common/basic/compiler.ts index 964c156a..8c339114 100644 --- a/src/common/basic/compiler.ts +++ b/src/common/basic/compiler.ts @@ -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); } diff --git a/src/common/basic/runtime.ts b/src/common/basic/runtime.ts index 5df112b4..2aaddfc9 100644 --- a/src/common/basic/runtime.ts +++ b/src/common/basic/runtime.ts @@ -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) { diff --git a/src/ide/views.ts b/src/ide/views.ts index 7ff7c52d..d67adcd8 100644 --- a/src/ide/views.ts +++ b/src/ide/views.ts @@ -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) {