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:
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) {
|
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'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user