mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-25 18:33:11 +00:00
basic: RANDOMIZE, WHILE/WEND, RESTORE line, BASIC80 (17, 56, 67)
This commit is contained in:
parent
0fd04658cb
commit
45ab88611e
@ -33,7 +33,9 @@ export interface BASICOptions {
|
||||
// CONTROL FLOW
|
||||
testInitialFor : boolean; // can we skip a NEXT statement? (can't interleave tho)
|
||||
optionalNextVar : boolean; // can do NEXT without variable
|
||||
ifElse : boolean; // IF...ELSE construct
|
||||
multipleNextVars : boolean; // NEXT J,I
|
||||
checkOnGotoIndex : boolean; // fatal error when ON..GOTO index out of bounds
|
||||
restoreWithLabel : boolean; // RESTORE <label>
|
||||
// MISC
|
||||
commandsPerSec? : number; // how many commands per second?
|
||||
}
|
||||
@ -43,9 +45,11 @@ export interface SourceLocated {
|
||||
}
|
||||
|
||||
export class CompileError extends Error {
|
||||
constructor(msg: string) {
|
||||
$loc : SourceLocation;
|
||||
constructor(msg: string, loc: SourceLocation) {
|
||||
super(msg);
|
||||
Object.setPrototypeOf(this, CompileError.prototype);
|
||||
this.$loc = loc;
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,6 +158,15 @@ export interface NEXT_Statement {
|
||||
lexpr?: IndOp;
|
||||
}
|
||||
|
||||
export interface WHILE_Statement {
|
||||
command: "WHILE";
|
||||
cond: Expr;
|
||||
}
|
||||
|
||||
export interface WEND_Statement {
|
||||
command: "WEND";
|
||||
}
|
||||
|
||||
export interface INPUT_Statement {
|
||||
command: "INPUT";
|
||||
prompt: Expr;
|
||||
@ -170,6 +183,11 @@ export interface READ_Statement {
|
||||
args: IndOp[];
|
||||
}
|
||||
|
||||
export interface RESTORE_Statement {
|
||||
command: "RESTORE";
|
||||
label: Expr;
|
||||
}
|
||||
|
||||
export interface DEF_Statement {
|
||||
command: "DEF";
|
||||
lexpr: IndOp;
|
||||
@ -182,7 +200,7 @@ export interface OPTION_Statement {
|
||||
optargs: string[];
|
||||
}
|
||||
|
||||
export interface GET_Statement {
|
||||
export interface GET_Statement { // applesoft only?
|
||||
command: "GET";
|
||||
lexpr: IndOp;
|
||||
}
|
||||
@ -194,7 +212,8 @@ export interface NoArgStatement {
|
||||
export type StatementTypes = PRINT_Statement | LET_Statement | GOTO_Statement | GOSUB_Statement
|
||||
| IF_Statement | FOR_Statement | NEXT_Statement | DIM_Statement
|
||||
| INPUT_Statement | READ_Statement | DEF_Statement | ONGOTO_Statement
|
||||
| DATA_Statement | OPTION_Statement | NoArgStatement;
|
||||
| DATA_Statement | OPTION_Statement | GET_Statement | RESTORE_Statement
|
||||
| NoArgStatement;
|
||||
|
||||
export type Statement = StatementTypes & SourceLocated;
|
||||
|
||||
@ -294,7 +313,7 @@ export class BASICParser {
|
||||
compileError(msg: string, loc?: SourceLocation) {
|
||||
if (!loc) loc = this.peekToken().$loc;
|
||||
this.errors.push({path:loc.path, line:loc.line, label:this.curlabel, start:loc.start, end:loc.end, msg:msg});
|
||||
throw new CompileError(`${msg} (line ${loc.line})`); // TODO: label too?
|
||||
throw new CompileError(msg, loc);
|
||||
}
|
||||
dialectError(what: string, loc?: SourceLocation) {
|
||||
this.compileError(`The selected BASIC dialect (${this.opts.dialectName}) doesn't support ${what}.`, loc); // TODO
|
||||
@ -376,7 +395,9 @@ export class BASICParser {
|
||||
tokenize(line: string) : void {
|
||||
this.lineno++;
|
||||
this.tokens = [];
|
||||
// split identifier regex (if token-crunching enabled)
|
||||
let splitre = this.opts.optionalWhitespace && new RegExp(this.opts.validKeywords.map(s => `^${s}`).join('|'));
|
||||
// iterate over each token via re_toks regex
|
||||
var m : RegExpMatchArray;
|
||||
while (m = re_toks.exec(line)) {
|
||||
for (var i = 1; i < TokenType._LAST; i++) {
|
||||
@ -389,7 +410,7 @@ export class BASICParser {
|
||||
// uppercase all identifiers, and maybe more
|
||||
if (i == TokenType.Ident || i == TokenType.HexOctalInt || this.opts.uppercaseOnly)
|
||||
s = s.toUpperCase();
|
||||
// un-crunch tokens?
|
||||
// un-crunch tokens? (TODO: still doesn't handle reserved words inside of variables)
|
||||
if (splitre) {
|
||||
while (i == TokenType.Ident) {
|
||||
let m2 = splitre.exec(s);
|
||||
@ -673,7 +694,7 @@ export class BASICParser {
|
||||
var thengoto = this.expectTokens(['THEN','GOTO']);
|
||||
var lineno = this.peekToken();
|
||||
// assume GOTO if number given after THEN
|
||||
if (lineno.type == TokenType.Int && thengoto.str == 'THEN') {
|
||||
if (lineno.type == TokenType.Int) {
|
||||
this.pushbackToken({type:TokenType.Ident, str:'GOTO', $loc:lineno.$loc});
|
||||
}
|
||||
// add fake ":"
|
||||
@ -681,7 +702,6 @@ export class BASICParser {
|
||||
return { command: "IF", cond: cond };
|
||||
}
|
||||
stmt__ELSE(): ELSE_Statement {
|
||||
if (!this.opts.ifElse) this.dialectError(`IF...ELSE statements`);
|
||||
var lineno = this.peekToken();
|
||||
// assume GOTO if number given after ELSE
|
||||
if (lineno.type == TokenType.Int) {
|
||||
@ -705,11 +725,25 @@ export class BASICParser {
|
||||
}
|
||||
stmt__NEXT() : NEXT_Statement {
|
||||
var lexpr = null;
|
||||
if (!isEOS(this.peekToken())) {
|
||||
// NEXT var might be optional
|
||||
if (!this.opts.optionalNextVar || !isEOS(this.peekToken())) {
|
||||
lexpr = this.parseForNextLexpr();
|
||||
// convert ',' to ':' 'NEXT'
|
||||
if (this.opts.multipleNextVars && this.peekToken().str == ',') {
|
||||
this.consumeToken(); // consume ','
|
||||
this.tokens.unshift({type:TokenType.Ident, str:'NEXT', $loc:this.peekToken().$loc});
|
||||
this.tokens.unshift({type:TokenType.Operator, str:':', $loc:this.peekToken().$loc});
|
||||
}
|
||||
}
|
||||
return { command:'NEXT', lexpr:lexpr };
|
||||
}
|
||||
stmt__WHILE(): WHILE_Statement {
|
||||
var cond = this.parseExpr();
|
||||
return { command:'WHILE', cond:cond };
|
||||
}
|
||||
stmt__WEND(): WEND_Statement {
|
||||
return { command:'WEND' };
|
||||
}
|
||||
stmt__DIM() : DIM_Statement {
|
||||
var lexprs = this.parseLexprList();
|
||||
lexprs.forEach((arr) => {
|
||||
@ -738,8 +772,11 @@ export class BASICParser {
|
||||
stmt__READ() : READ_Statement {
|
||||
return { command:'READ', args:this.parseLexprList() };
|
||||
}
|
||||
stmt__RESTORE() {
|
||||
return { command:'RESTORE' };
|
||||
stmt__RESTORE() : RESTORE_Statement {
|
||||
var label = null;
|
||||
if (this.opts.restoreWithLabel && !isEOS(this.peekToken()))
|
||||
label = this.parseLabel();
|
||||
return { command:'RESTORE', label:label };
|
||||
}
|
||||
stmt__RETURN() {
|
||||
return { command:'RETURN' };
|
||||
@ -779,6 +816,9 @@ export class BASICParser {
|
||||
stmt__CLEAR() : NoArgStatement {
|
||||
return { command:'CLEAR' };
|
||||
}
|
||||
stmt__RANDOMIZE() : NoArgStatement {
|
||||
return { command:'RANDOMIZE' };
|
||||
}
|
||||
// TODO: CHANGE A TO A$ (4th edition, A(0) is len and A(1..) are chars)
|
||||
stmt__OPTION() : OPTION_Statement {
|
||||
var tokname = this.consumeToken();
|
||||
@ -890,19 +930,26 @@ export const ECMA55_MINIMAL : BASICOptions = {
|
||||
maxStringLength : 255,
|
||||
tickComments : false,
|
||||
hexOctalConsts : false,
|
||||
validKeywords : ['BASE','DATA','DEF','DIM','END',
|
||||
validKeywords : [
|
||||
'BASE','DATA','DEF','DIM','END',
|
||||
'FOR','GO','GOSUB','GOTO','IF','INPUT','LET','NEXT','ON','OPTION','PRINT',
|
||||
'RANDOMIZE','READ','REM','RESTORE','RETURN','STEP','STOP','SUB','THEN','TO'
|
||||
],
|
||||
validFunctions : ['ABS','ATN','COS','EXP','INT','LOG','RND','SGN','SIN','SQR','TAB','TAN'],
|
||||
validOperators : ['=', '<>', '<', '>', '<=', '>=', '+', '-', '*', '/', '^'],
|
||||
validFunctions : [
|
||||
'ABS','ATN','COS','EXP','INT','LOG','RND','SGN','SIN','SQR','TAB','TAN'
|
||||
],
|
||||
validOperators : [
|
||||
'=', '<>', '<', '>', '<=', '>=', '+', '-', '*', '/', '^'
|
||||
],
|
||||
printZoneLength : 15,
|
||||
numericPadding : true,
|
||||
checkOverflow : true,
|
||||
testInitialFor : true,
|
||||
optionalNextVar : false,
|
||||
ifElse : false,
|
||||
multipleNextVars : false,
|
||||
bitwiseLogic : false,
|
||||
checkOnGotoIndex : true,
|
||||
restoreWithLabel : false,
|
||||
}
|
||||
|
||||
export const BASICODE : BASICOptions = {
|
||||
@ -924,20 +971,27 @@ export const BASICODE : BASICOptions = {
|
||||
maxStringLength : 255,
|
||||
tickComments : false,
|
||||
hexOctalConsts : false,
|
||||
validKeywords : ['BASE','DATA','DEF','DIM','END',
|
||||
validKeywords : [
|
||||
'BASE','DATA','DEF','DIM','END',
|
||||
'FOR','GO','GOSUB','GOTO','IF','INPUT','LET','NEXT','ON','OPTION','PRINT',
|
||||
'READ','REM','RESTORE','RETURN','STEP','STOP','SUB','THEN','TO'
|
||||
],
|
||||
validFunctions : ['ABS','ASC','ATN','CHR$','COS','EXP','INT','LEFT$','LEN','LOG',
|
||||
'MID$','RIGHT$','SGN','SIN','SQR','TAB','TAN','VAL'],
|
||||
validOperators : ['=', '<>', '<', '>', '<=', '>=', '+', '-', '*', '/', '^', 'AND', 'NOT', 'OR'],
|
||||
validFunctions : [
|
||||
'ABS','ASC','ATN','CHR$','COS','EXP','INT','LEFT$','LEN','LOG',
|
||||
'MID$','RIGHT$','SGN','SIN','SQR','TAB','TAN','VAL'
|
||||
],
|
||||
validOperators : [
|
||||
'=', '<>', '<', '>', '<=', '>=', '+', '-', '*', '/', '^', 'AND', 'NOT', 'OR'
|
||||
],
|
||||
printZoneLength : 15,
|
||||
numericPadding : true,
|
||||
checkOverflow : true,
|
||||
testInitialFor : true,
|
||||
optionalNextVar : false,
|
||||
ifElse : false,
|
||||
multipleNextVars : false,
|
||||
bitwiseLogic : false,
|
||||
checkOnGotoIndex : true,
|
||||
restoreWithLabel : false,
|
||||
}
|
||||
|
||||
export const ALTAIR_BASIC41 : BASICOptions = {
|
||||
@ -965,7 +1019,9 @@ export const ALTAIR_BASIC41 : BASICOptions = {
|
||||
'FOR','GOTO','GOSUB','IF','THEN','ELSE','INPUT','LET','LINE',
|
||||
'PRINT','LPRINT','USING','NEXT','ON','OUT','POKE',
|
||||
'READ','REM','RESTORE','RESUME','RETURN','STOP','SWAP',
|
||||
'TROFF','TRON','WAIT'],
|
||||
'TROFF','TRON','WAIT',
|
||||
'TO','STEP',
|
||||
],
|
||||
validFunctions : null, // all
|
||||
validOperators : null, // all
|
||||
printZoneLength : 15,
|
||||
@ -973,9 +1029,10 @@ export const ALTAIR_BASIC41 : BASICOptions = {
|
||||
checkOverflow : true,
|
||||
testInitialFor : false,
|
||||
optionalNextVar : true,
|
||||
//multipleNextVars : true, // TODO: not supported
|
||||
ifElse : true,
|
||||
multipleNextVars : true,
|
||||
bitwiseLogic : true,
|
||||
checkOnGotoIndex : false,
|
||||
restoreWithLabel : false,
|
||||
}
|
||||
|
||||
export const APPLESOFT_BASIC : BASICOptions = {
|
||||
@ -1000,25 +1057,81 @@ export const APPLESOFT_BASIC : BASICOptions = {
|
||||
validKeywords : [
|
||||
'OPTION',
|
||||
'CLEAR','LET','DIM','DEF','GOTO','GOSUB','RETURN','ON','POP',
|
||||
'FOR','TO','NEXT','IF','THEN','END','STOP','ONERR','RESUME',
|
||||
'FOR','NEXT','IF','THEN','END','STOP','ONERR','RESUME',
|
||||
'PRINT','INPUT','GET','HOME','HTAB','VTAB',
|
||||
'INVERSE','FLASH','NORMAL','TEXT',
|
||||
'GR','COLOR','PLOT','HLIN','VLIN',
|
||||
'HGR','HGR2','HPLOT','HCOLOR','AT',
|
||||
'DATA','READ','RESTORE',
|
||||
'REM','TRACE','NOTRACE'],
|
||||
'REM','TRACE','NOTRACE',
|
||||
'TO','STEP',
|
||||
],
|
||||
validFunctions : [
|
||||
'ABS','ATN','COS','EXP','INT','LOG','RND','SGN','SIN','SQR','TAN',
|
||||
'LEN','LEFT$','MID$','RIGHT$','STR$','VAL','CHR$','ASC',
|
||||
'FRE','SCRN','PDL','PEEK','POS'],
|
||||
validOperators : ['=', '<>', '<', '>', '<=', '>=', '+', '-', '*', '/', '^', 'AND', 'NOT', 'OR'],
|
||||
'FRE','SCRN','PDL','PEEK','POS'
|
||||
],
|
||||
validOperators : [
|
||||
'=', '<>', '<', '>', '<=', '>=', '+', '-', '*', '/', '^', 'AND', 'NOT', 'OR'
|
||||
],
|
||||
printZoneLength : 16,
|
||||
numericPadding : false,
|
||||
checkOverflow : true,
|
||||
testInitialFor : false,
|
||||
optionalNextVar : true,
|
||||
ifElse : false,
|
||||
multipleNextVars : true,
|
||||
bitwiseLogic : false,
|
||||
checkOnGotoIndex : false,
|
||||
restoreWithLabel : false,
|
||||
}
|
||||
|
||||
export const BASIC80 : BASICOptions = {
|
||||
dialectName: "BASIC80",
|
||||
asciiOnly : true,
|
||||
uppercaseOnly : false,
|
||||
optionalLabels : false,
|
||||
optionalWhitespace : true,
|
||||
varNaming : "*",
|
||||
staticArrays : false,
|
||||
sharedArrayNamespace : true,
|
||||
defaultArrayBase : 0,
|
||||
defaultArraySize : 11,
|
||||
defaultValues : true,
|
||||
stringConcat : true,
|
||||
typeConvert : false,
|
||||
maxDimensions : 255,
|
||||
maxDefArgs : 255,
|
||||
maxStringLength : 255,
|
||||
//maxElements : 32767, // TODO
|
||||
tickComments : true,
|
||||
hexOctalConsts : true,
|
||||
validKeywords : [
|
||||
'OPTION',
|
||||
'CONSOLE','DATA','DEF','DEFUSR','DIM','END','ERASE','ERROR',
|
||||
'FOR','GOTO','GOSUB','IF','THEN','ELSE','INPUT','LET','LINE',
|
||||
'PRINT','LPRINT','USING','NEXT','ON','OUT','POKE',
|
||||
'READ','REM','RESTORE','RESUME','RETURN','STOP','SWAP',
|
||||
'TROFF','TRON','WAIT',
|
||||
'CALL','CHAIN','COMMON','WHILE','WEND','WRITE','RANDOMIZE',
|
||||
'TO','STEP',
|
||||
],
|
||||
validFunctions : [
|
||||
'ABS','ASC','ATN','CDBL','CHR$','CINT','COS','CSNG','CVI','CVS','CVD',
|
||||
'EOF','EXP','FIX','FRE','HEX$','INP','INPUT$','INSTR','INT',
|
||||
'LEFT$','LEN','LOC','LOG','LPOS','MID$','MKI$','MKS$','MKD$',
|
||||
'OCT$','PEEK','POS','RIGHT$','RND','SGN','SIN','SPACE$','SPC',
|
||||
'SQR','STR$','STRING$','TAB','TAN','USR','VAL','VARPTR'
|
||||
],
|
||||
validOperators : null, // all
|
||||
printZoneLength : 14,
|
||||
numericPadding : true,
|
||||
checkOverflow : false, // TODO: message displayed when overflow, division by zero = ok
|
||||
testInitialFor : true,
|
||||
optionalNextVar : true,
|
||||
multipleNextVars : true,
|
||||
bitwiseLogic : true,
|
||||
checkOnGotoIndex : false,
|
||||
restoreWithLabel : true,
|
||||
}
|
||||
|
||||
export const MODERN_BASIC : BASICOptions = {
|
||||
@ -1048,8 +1161,10 @@ export const MODERN_BASIC : BASICOptions = {
|
||||
checkOverflow : true,
|
||||
testInitialFor : true,
|
||||
optionalNextVar : true,
|
||||
ifElse : true,
|
||||
multipleNextVars : true,
|
||||
bitwiseLogic : true,
|
||||
checkOnGotoIndex : false,
|
||||
restoreWithLabel : true,
|
||||
}
|
||||
|
||||
// TODO: integer vars
|
||||
@ -1063,5 +1178,6 @@ export const DIALECTS = {
|
||||
"MINIMAL": ECMA55_MINIMAL,
|
||||
"BASICODE": BASICODE,
|
||||
"APPLESOFT": APPLESOFT_BASIC,
|
||||
"BASIC80": BASIC80,
|
||||
"MODERN": MODERN_BASIC,
|
||||
};
|
||||
|
@ -38,11 +38,11 @@ try {
|
||||
var pgm = parser.parseFile(data, filename);
|
||||
} catch (e) {
|
||||
if (parser.errors.length == 0)
|
||||
console.log("@@@ " + e.msg);
|
||||
console.log(`@@@ ${e}`);
|
||||
else
|
||||
console.log(e);
|
||||
}
|
||||
parser.errors.forEach((err) => console.log("@@@ " + err.msg));
|
||||
parser.errors.forEach((err) => console.log(`@@@ ${err.msg} (line ${err.label})`));
|
||||
if (parser.errors.length) process.exit(2);
|
||||
|
||||
// run program
|
||||
@ -55,13 +55,16 @@ runtime.input = async (prompt:string) => {
|
||||
return new Promise( (resolve, reject) => {
|
||||
function answered(answer) {
|
||||
var vals = answer.toUpperCase().split(',');
|
||||
console.log(">>>",vals);
|
||||
//console.log(">>>",vals);
|
||||
resolve(vals);
|
||||
}
|
||||
fs.writeSync(1, prompt+"?");
|
||||
prompt += ' ?';
|
||||
if (inputlines.length) {
|
||||
fs.writeSync(1, prompt);
|
||||
fs.writeSync(1, '\n');
|
||||
answered(inputlines.shift());
|
||||
} else rl.question(prompt, (answer) => {
|
||||
fs.writeSync(1, '\n');
|
||||
answered(answer);
|
||||
});
|
||||
});
|
||||
@ -72,7 +75,7 @@ runtime.resume = function() {
|
||||
if (runtime.step()) {
|
||||
if (runtime.running) runtime.resume();
|
||||
} else if (runtime.exited) {
|
||||
console.log("*** PROGRAM EXITED ***");
|
||||
//console.log("*** PROGRAM EXITED ***");
|
||||
process.exit(0);
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -31,6 +31,38 @@ function isArray(obj) {
|
||||
return obj != null && (Array.isArray(obj) || obj.BYTES_PER_ELEMENT);
|
||||
}
|
||||
|
||||
class RNG {
|
||||
next : () => number;
|
||||
seed : (aa,bb,cc,dd) => void;
|
||||
randomize() {
|
||||
this.seed(Math.random()*0x7fffffff, Math.random()*0x7fffffff, Math.random()*0x7fffffff, Math.random()*0x7fffffff);
|
||||
}
|
||||
constructor() {
|
||||
let f = () => {
|
||||
var a, b, c, d : number;
|
||||
this.seed = function(aa,bb,cc,dd) {
|
||||
a = aa; b = bb; c = cc; d = dd;
|
||||
}
|
||||
this.next = function() {
|
||||
// sfc32
|
||||
a >>>= 0; b >>>= 0; c >>>= 0; d >>>= 0;
|
||||
var t = (a + b) | 0;
|
||||
a = b ^ b >>> 9;
|
||||
b = c + (c << 3) | 0;
|
||||
c = (c << 21 | c >>> 11);
|
||||
d = d + 1 | 0;
|
||||
t = t + d | 0;
|
||||
c = c + t | 0;
|
||||
return (t >>> 0) / 4294967296;
|
||||
}
|
||||
};
|
||||
f();
|
||||
this.seed(0x12345678, 0xdeadbeef, 0xf0d3984e, 0xfeed3660); //default seed
|
||||
this.next();
|
||||
this.next();
|
||||
}
|
||||
};
|
||||
|
||||
export class BASICRuntime {
|
||||
|
||||
program : basic.BASICProgram;
|
||||
@ -49,9 +81,11 @@ export class BASICRuntime {
|
||||
arrays : {};
|
||||
defs : {};
|
||||
forLoops : { [varname:string] : { $next:(name:string) => void, inner:string } };
|
||||
whileLoops : number[];
|
||||
topForLoopName : string;
|
||||
returnStack : number[];
|
||||
column : number;
|
||||
rng : RNG;
|
||||
|
||||
running : boolean = false;
|
||||
exited : boolean = true;
|
||||
@ -112,6 +146,8 @@ export class BASICRuntime {
|
||||
this.defs = {}; // TODO? only in interpreters
|
||||
this.forLoops = {};
|
||||
this.topForLoopName = null;
|
||||
this.whileLoops = [];
|
||||
this.rng = new RNG();
|
||||
// initialize arrays?
|
||||
if (this.opts && this.opts.staticArrays) {
|
||||
this.allstmts.filter((stmt) => stmt.command == 'DIM').forEach((dimstmt: basic.DIM_Statement) => {
|
||||
@ -120,6 +156,8 @@ export class BASICRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: saveState(), loadState()
|
||||
|
||||
getBuiltinFunctions() {
|
||||
var fnames = this.program && this.opts.validFunctions;
|
||||
// if no valid function list, look for ABC...() functions in prototype
|
||||
@ -213,11 +251,9 @@ export class BASICRuntime {
|
||||
do {
|
||||
// in Altair BASIC, ELSE is bound to the right-most IF
|
||||
// TODO: this is complicated, we should just have nested expressions
|
||||
if (this.opts.ifElse) {
|
||||
var cmd = this.allstmts[this.curpc].command;
|
||||
if (cmd == 'ELSE') { this.curpc++; break; }
|
||||
else if (cmd == 'IF') return this.skipToEOL();
|
||||
}
|
||||
var cmd = this.allstmts[this.curpc].command;
|
||||
if (cmd == 'ELSE') { this.curpc++; break; }
|
||||
else if (cmd == 'IF') return this.skipToEOL();
|
||||
this.curpc++;
|
||||
} while (this.curpc < this.allstmts.length && !this.pc2line.get(this.curpc));
|
||||
}
|
||||
@ -242,6 +278,26 @@ export class BASICRuntime {
|
||||
this.runtimeError(`I couldn't find a matching NEXT ${forname} to skip this for loop.`);
|
||||
}
|
||||
|
||||
skipToAfterWend() {
|
||||
var pc = this.curpc - 1;
|
||||
var nesting = 0;
|
||||
while (pc < this.allstmts.length) {
|
||||
var stmt = this.allstmts[pc];
|
||||
console.log(nesting, pc, stmt);
|
||||
if (stmt.command == 'WHILE') {
|
||||
nesting++;
|
||||
} else if (stmt.command == 'WEND') {
|
||||
nesting--;
|
||||
if (nesting == 0) {
|
||||
this.curpc = pc + 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
pc++;
|
||||
}
|
||||
this.runtimeError(`I couldn't find a matching WEND for this WHILE.`);
|
||||
}
|
||||
|
||||
gotoLabel(label) {
|
||||
var pc = this.label2pc[label];
|
||||
if (pc >= 0) {
|
||||
@ -252,7 +308,7 @@ export class BASICRuntime {
|
||||
}
|
||||
|
||||
gosubLabel(label) {
|
||||
if (this.returnStack.length > 65535)
|
||||
if (this.returnStack.length > 32767) // TODO: const?
|
||||
this.runtimeError(`I did too many GOSUBs without a RETURN.`)
|
||||
this.returnStack.push(this.curpc);
|
||||
this.gotoLabel(label);
|
||||
@ -424,10 +480,24 @@ export class BASICRuntime {
|
||||
|
||||
nextForLoop(name) {
|
||||
var fl = this.forLoops[name || (this.opts.optionalNextVar && this.topForLoopName)];
|
||||
if (!fl) this.runtimeError(`I couldn't find a FOR for this NEXT.`)
|
||||
if (!fl) this.runtimeError(`I couldn't find a matching FOR for this NEXT.`)
|
||||
fl.$next(name);
|
||||
}
|
||||
|
||||
whileLoop(cond) {
|
||||
if (cond) {
|
||||
this.whileLoops.push(this.curpc-1);
|
||||
} else {
|
||||
this.skipToAfterWend();
|
||||
}
|
||||
}
|
||||
|
||||
nextWhileLoop() {
|
||||
var pc = this.whileLoops.pop();
|
||||
if (pc == null) this.runtimeError(`I couldn't find a matching WHILE for this WEND.`);
|
||||
else this.curpc = pc;
|
||||
}
|
||||
|
||||
// converts a variable to string/number based on var name
|
||||
assign(name: string, right: number|string) : number|string {
|
||||
if (this.opts.typeConvert)
|
||||
@ -515,17 +585,24 @@ export class BASICRuntime {
|
||||
return (v as any) as basic.Value;
|
||||
}
|
||||
|
||||
onGotoLabel(value: number, ...labels: string[]) {
|
||||
checkOnGoto(value: number, labels: string[]) {
|
||||
value = this.ROUND(value);
|
||||
if (value < 1 || value > labels.length)
|
||||
if (value < 0) // > 255 ?
|
||||
this.runtimeError(`I needed a number between 1 and ${labels.length}, but I got ${value}.`);
|
||||
if (this.opts.checkOnGotoIndex && (value < 1 || value > labels.length))
|
||||
this.runtimeError(`I needed a number between 1 and ${labels.length}, but I got ${value}.`);
|
||||
else
|
||||
value = Math.min(Math.max(1, value), labels.length);
|
||||
return value;
|
||||
}
|
||||
|
||||
onGotoLabel(value: number, ...labels: string[]) {
|
||||
value = this.checkOnGoto(value, labels);
|
||||
this.gotoLabel(labels[value-1]);
|
||||
|
||||
}
|
||||
onGosubLabel(value: number, ...labels: string[]) {
|
||||
value = this.ROUND(value);
|
||||
if (value < 1 || value > labels.length)
|
||||
this.runtimeError(`I needed a number between 1 and ${labels.length}, but I got ${value}.`);
|
||||
value = this.checkOnGoto(value, labels);
|
||||
this.gosubLabel(labels[value-1]);
|
||||
}
|
||||
|
||||
@ -568,14 +645,13 @@ export class BASICRuntime {
|
||||
}
|
||||
|
||||
do__LET(stmt : basic.LET_Statement) {
|
||||
// TODO: range-checking for subscripts (get and set)
|
||||
var lexpr = this.assign2js(stmt.lexpr);
|
||||
var right = this.expr2js(stmt.right);
|
||||
return `${lexpr} = this.assign(${JSON.stringify(stmt.lexpr.name)}, ${right});`;
|
||||
}
|
||||
|
||||
do__FOR(stmt : basic.FOR_Statement) {
|
||||
var name = JSON.stringify(stmt.lexpr.name); // TODO: args?
|
||||
var name = JSON.stringify(stmt.lexpr.name);
|
||||
var init = this.expr2js(stmt.initial);
|
||||
var targ = this.expr2js(stmt.target);
|
||||
var step = stmt.step ? this.expr2js(stmt.step) : 'null';
|
||||
@ -583,7 +659,7 @@ export class BASICRuntime {
|
||||
}
|
||||
|
||||
do__NEXT(stmt : basic.NEXT_Statement) {
|
||||
var name = stmt.lexpr && JSON.stringify(stmt.lexpr.name); // TODO: args? lexpr == null?
|
||||
var name = stmt.lexpr && JSON.stringify(stmt.lexpr.name);
|
||||
return `this.nextForLoop(${name})`;
|
||||
}
|
||||
|
||||
@ -596,6 +672,15 @@ export class BASICRuntime {
|
||||
return `this.skipToEOL()`
|
||||
}
|
||||
|
||||
do__WHILE(stmt : basic.WHILE_Statement) {
|
||||
var cond = this.expr2js(stmt.cond);
|
||||
return `this.whileLoop(${cond})`;
|
||||
}
|
||||
|
||||
do__WEND() {
|
||||
return `this.nextWhileLoop()`
|
||||
}
|
||||
|
||||
do__DEF(stmt : basic.DEF_Statement) {
|
||||
var args = [];
|
||||
for (var arg of stmt.lexpr.args || []) {
|
||||
@ -661,8 +746,11 @@ export class BASICRuntime {
|
||||
return s;
|
||||
}
|
||||
|
||||
do__RESTORE() {
|
||||
return `this.dataptr = 0`;
|
||||
do__RESTORE(stmt : basic.RESTORE_Statement) {
|
||||
if (stmt.label != null)
|
||||
return `this.dataptr = this.label2pc[${this.expr2js(stmt.label, {isconst:true})}] || 0`;
|
||||
else
|
||||
return `this.dataptr = 0`;
|
||||
}
|
||||
|
||||
do__END() {
|
||||
@ -696,8 +784,11 @@ export class BASICRuntime {
|
||||
return 'this.clearVars()';
|
||||
}
|
||||
|
||||
do__RANDOMIZE() {
|
||||
return `this.rng.randomize()`;
|
||||
}
|
||||
|
||||
// TODO: ONERR, ON ERROR GOTO
|
||||
// TODO: "SUBSCRIPT ERROR" (range check)
|
||||
// TODO: gosubs nested too deeply
|
||||
// TODO: memory quota
|
||||
// TODO: useless loop (! 4th edition)
|
||||
@ -707,7 +798,7 @@ export class BASICRuntime {
|
||||
// FUNCTIONS
|
||||
|
||||
isValid(obj:number|string) : boolean {
|
||||
if (typeof obj === 'number' && !isNaN(obj) && isFinite(obj))
|
||||
if (typeof obj === 'number' && !isNaN(obj) && (!this.opts.checkOverflow || isFinite(obj)))
|
||||
return true;
|
||||
else if (typeof obj === 'string')
|
||||
return true;
|
||||
@ -841,6 +932,9 @@ export class BASICRuntime {
|
||||
CHR$(arg : number) : string {
|
||||
return String.fromCharCode(this.checkNum(arg));
|
||||
}
|
||||
CINT(arg : number) : number {
|
||||
return this.ROUND(arg);
|
||||
}
|
||||
COS(arg : number) : number {
|
||||
return this.checkNum(Math.cos(arg));
|
||||
}
|
||||
@ -854,7 +948,7 @@ export class BASICRuntime {
|
||||
return this.checkNum(arg < 0 ? Math.ceil(arg) : Math.floor(arg));
|
||||
}
|
||||
HEX$(arg : number) : string {
|
||||
return arg.toString(16);
|
||||
return this.ROUND(arg).toString(16);
|
||||
}
|
||||
INSTR(a, b, c) : number {
|
||||
if (c != null) {
|
||||
@ -882,11 +976,18 @@ export class BASICRuntime {
|
||||
if (count == 0) count = arg.length;
|
||||
return arg.substr(start-1, count);
|
||||
}
|
||||
OCT$(arg : number) : string {
|
||||
return this.ROUND(arg).toString(8);
|
||||
}
|
||||
POS(arg : number) : number { // arg ignored
|
||||
return this.column + 1;
|
||||
}
|
||||
RIGHT$(arg : string, count : number) : string {
|
||||
return arg.substr(arg.length - count, count);
|
||||
}
|
||||
RND(arg : number) : number {
|
||||
return Math.random(); // argument ignored
|
||||
// TODO: X<0 restart w/ seed, X=0 repeats
|
||||
return this.rng.next();
|
||||
}
|
||||
ROUND(arg : number) : number {
|
||||
return this.checkNum(Math.round(arg));
|
||||
@ -898,7 +999,11 @@ export class BASICRuntime {
|
||||
return this.checkNum(Math.sin(arg));
|
||||
}
|
||||
SPACE$(arg : number) : string {
|
||||
return (arg > 0) ? ' '.repeat(this.checkNum(arg)) : '';
|
||||
arg = this.ROUND(arg);
|
||||
return (arg > 0) ? ' '.repeat(arg) : '';
|
||||
}
|
||||
SPC(arg : number) : string {
|
||||
return this.SPACE$(arg);
|
||||
}
|
||||
SQR(arg : number) : number {
|
||||
if (arg < 0) this.runtimeError(`I can't take the square root of a negative number (${arg}).`)
|
||||
@ -907,6 +1012,12 @@ export class BASICRuntime {
|
||||
STR$(arg : number) : string {
|
||||
return this.valueToString(this.checkNum(arg));
|
||||
}
|
||||
STRING$(len : number, chr : number|string) : string {
|
||||
len = this.ROUND(len);
|
||||
if (len <= 0) return '';
|
||||
if (typeof chr === 'string') return chr.substr(0,1).repeat(len);
|
||||
else return String.fromCharCode(chr).repeat(len);
|
||||
}
|
||||
TAB(arg : number) : string {
|
||||
if (arg < 1) { arg = 1; } // TODO: SYSTEM MESSAGE IDENTIFYING THE EXCEPTION
|
||||
var spaces = this.ROUND(arg) - 1 - this.column;
|
||||
|
@ -177,6 +177,7 @@ class BASICPlatform implements Platform {
|
||||
Arrays: this.runtime.arrays,
|
||||
Functions: this.runtime.defs,
|
||||
ForLoops: this.runtime.forLoops,
|
||||
WhileLoops: this.runtime.whileLoops,
|
||||
ReturnStack: this.runtime.returnStack,
|
||||
NextDatum: this.runtime.datums[this.runtime.dataptr],
|
||||
Options: this.runtime.opts,
|
||||
|
Loading…
Reference in New Issue
Block a user