basic: WHILE start/end, fixed OPTION boolean, CONVERT, more type checking, set $loc offset, OPTION parsing

This commit is contained in:
Steven Hugg 2020-08-22 11:40:58 -05:00
parent b2df149fb3
commit 9e48b7f973
6 changed files with 172 additions and 107 deletions

View File

@ -15,6 +15,7 @@
}
.gutter-info {
width: 1em;
cursor: cell;
}
.currentpc-span {
background-color: #7e2a70;

View File

@ -22,7 +22,6 @@ export interface BASICOptions {
// VALUES AND OPERATORS
defaultValues : boolean; // initialize unset variables to default value? (0 or "")
stringConcat : boolean; // can concat strings with "+" operator?
typeConvert : boolean; // type convert strings <-> numbers? (NOT USED)
checkOverflow : boolean; // check for overflow of numerics?
bitwiseLogic : boolean; // -1 = TRUE, 0 = FALSE, AND/OR/NOT done with bitwise ops
maxStringLength : number; // maximum string length in chars
@ -52,7 +51,12 @@ export interface BASICOptions {
maxArrayElements? : number; // max array elements (all dimensions)
}
// objects that have source code position info
export interface SourceLocated {
$loc?: SourceLocation;
}
// statements also have the 'offset' (pc) field from SourceLine
export interface SourceLineLocated {
$loc?: SourceLine;
}
@ -115,7 +119,7 @@ export interface IndOp extends ExprBase {
args: Expr[];
}
export interface Statement extends SourceLocated {
export interface Statement extends SourceLineLocated {
command: string;
}
@ -245,6 +249,12 @@ export interface CHANGE_Statement extends Statement {
dest: IndOp;
}
export interface CONVERT_Statement extends Statement {
command: "CONVERT";
src: Expr;
dest: IndOp;
}
export interface NoArgStatement extends Statement {
command: string;
}
@ -331,12 +341,22 @@ function isUnOp(arg: Expr): arg is UnOp {
return (arg as any).op != null && (arg as any).expr != null;
}
function mergeLocs(a: SourceLocation, b: SourceLocation) : SourceLocation {
return {
line:Math.min(a.line, b.line),
start:Math.min(a.start, b.start),
end:Math.max(a.end, b.end),
label:a.label || b.label,
path:a.path || b.path,
}
}
///// BASIC PARSER
export class BASICParser {
opts : BASICOptions = DIALECTS['DEFAULT'];
optionCount : number; // how many OPTION stmts so far?
maxlinelen : number = 255; // maximum line length
maxlinelen : number = 255; // maximum line length (some like HP use 72 chars)
stmts : Statement[];
errors: WorkerError[];
listings: CodeListingMap;
@ -454,9 +474,11 @@ export class BASICParser {
return this.stmts.length;
}
addStatement(stmt: Statement, cmdtok: Token, endtok?: Token) {
// set location for statement
// set location for statement, adding offset (PC) field
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: this.stmts.length };
// check IF/THEN WHILE/WEND FOR/NEXT etc
this.modifyScope(stmt);
// add to list
@ -647,12 +669,12 @@ export class BASICParser {
var popidx = this.scopestack.pop();
var popstmt : ScopeStartStatement = popidx != null ? this.stmts[popidx] : null;
if (popstmt == null)
this.compileError(`There's a ${close.command} without a matching ${open}.`);
this.compileError(`There's a ${close.command} without a matching ${open}.`, close.$loc);
else if (popstmt.command != open)
this.compileError(`There's a ${close.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}.`, close.$loc);
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}).`);
this.compileError(`This NEXT statement is matched with the wrong FOR variable (${close.lexpr.name}).`, close.$loc);
// set start + end locations
close.startpc = popidx;
popstmt.endpc = this.getPC(); // has to be before adding statment to list
@ -667,8 +689,9 @@ export class BASICParser {
args = this.parseExprList();
this.expectToken(')', `There should be another expression or a ")" here.`);
}
var valtype = this.exprTypeForSubscript(tok.str, args);
return { valtype: valtype, name: tok.str, args: args };
var loc = mergeLocs(tok.$loc, this.lasttoken.$loc);
var valtype = this.exprTypeForSubscript(tok.str, args, loc);
return { valtype: valtype, name: tok.str, args: args, $loc:loc };
default:
this.compileError(`There should be a variable name here.`);
break;
@ -683,7 +706,7 @@ export class BASICParser {
parseForNextLexpr() : IndOp {
var lexpr = this.parseLexpr();
if (lexpr.args || lexpr.name.endsWith('$'))
this.compileError(`A FOR ... NEXT loop can only use numeric variables.`);
this.compileError(`A FOR ... NEXT loop can only use numeric variables.`, lexpr.$loc);
return lexpr;
}
parseList<T>(parseFunc:()=>T, delim:string): T[] {
@ -833,13 +856,17 @@ export class BASICParser {
// use logical operators instead of bitwise?
if (!this.opts.bitwiseLogic && op.str == 'AND') opfn = 'land';
if (!this.opts.bitwiseLogic && op.str == 'OR') opfn = 'lor';
var valtype = this.exprTypeForOp(opfn, left, right);
var valtype = this.exprTypeForOp(opfn, left, right, op);
left = { valtype:valtype, op:opfn, left: left, right: right };
}
return left;
}
parseExpr(): Expr {
return this.parseExpr1(this.parsePrimary(), 0);
var startloc = this.peekToken().$loc;
var expr = this.parseExpr1(this.parsePrimary(), 0);
var endloc = this.lasttoken.$loc;
expr.$loc = mergeLocs(startloc, endloc);
return expr;
}
parseExprWithType(expecttype: ValueType): Expr {
var expr = this.parseExpr();
@ -886,30 +913,32 @@ export class BASICParser {
callback(expr);
}
// type-checking
exprTypeForOp(fnname: string, left: Expr, right: Expr) : ValueType {
if (fnname == 'add' && (left.valtype == 'string' || right.valtype == 'string')) {
if (!this.opts.stringConcat) this.dialectErrorNoSupport(`the "+" operator to concatenate strings`);
return 'string'; // string concatenation
} else {
return 'number';
exprTypeForOp(fnname: string, left: Expr, right: Expr, optok: Token) : ValueType {
if (left.valtype == 'string' || right.valtype == 'string') {
if (fnname == 'add') {
if (this.opts.stringConcat) return 'string' // concat strings
else this.dialectErrorNoSupport(`the "+" operator to concatenate strings`, optok.$loc);
} else if (fnname.length != 2) // only relops are 2 chars long!
this.compileError(`You can't do math on strings until they're converted to numbers.`, optok.$loc);
}
return 'number';
}
exprTypeForSubscript(fnname: string, args: Expr[]) : ValueType {
exprTypeForSubscript(fnname: string, args: Expr[], loc: SourceLocation) : ValueType {
args = args || [];
// first check the built-in functions
var defs = BUILTIN_MAP[fnname];
if (defs != null) {
if (!this.validFunction(fnname)) this.dialectErrorNoSupport(`the ${fnname} function`);
if (!this.validFunction(fnname)) this.dialectErrorNoSupport(`the ${fnname} function`, loc);
for (var def of defs) {
if (args.length == def.args.length)
return def.result; // TODO: check arg types?
return def.result; // TODO: check arg types
}
// TODO: check func arg types
this.compileError(`The ${fnname} function takes ${def.args.length} arguments, but ${args.length} are given.`);
this.compileError(`The ${fnname} function takes ${def.args.length} arguments, but ${args.length} are given.`, loc);
}
// no function found, assume it's an array ref
// TODO: validateVarName() later?
this.varrefs[fnname] = this.lasttoken.$loc; // TODO?
this.varrefs[fnname] = loc;
return fnname.endsWith('$') ? 'string' : 'number';
}
@ -970,7 +999,7 @@ export class BASICParser {
}
stmt__IF(): void {
var cmdtok = this.lasttoken;
var cond = this.parseExpr();
var cond = this.parseExprWithType("number");
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)
@ -1010,12 +1039,12 @@ export class BASICParser {
stmt__FOR() : FOR_Statement {
var lexpr = this.parseForNextLexpr();
this.expectToken('=');
var init = this.parseExpr();
var init = this.parseExprWithType("number");
this.expectToken('TO');
var targ = this.parseExpr();
var targ = this.parseExprWithType("number");
if (this.peekToken().str == 'STEP') {
this.consumeToken();
var step = this.parseExpr();
var step = this.parseExprWithType("number");
}
return { command:'FOR', lexpr:lexpr, initial:init, target:targ, step:step };
}
@ -1034,7 +1063,7 @@ export class BASICParser {
return { command:'NEXT', lexpr:lexpr };
}
stmt__WHILE(): WHILE_Statement {
var cond = this.parseExpr();
var cond = this.parseExprWithType("number");
return { command:'WHILE', cond:cond };
}
stmt__WEND(): WEND_Statement {
@ -1048,8 +1077,10 @@ export class BASICParser {
else if (arr.args.length > this.opts.maxDimensions)
this.dialectErrorNoSupport(`arrays with more than ${this.opts.maxDimensions} dimensionals`);
for (var arrdim of arr.args) {
if (arrdim.valtype != 'number')
this.compileError(`Array dimensions must be numeric.`, arrdim.$loc);
if (isLiteral(arrdim) && arrdim.value < this.opts.defaultArrayBase)
this.compileError(`An array dimension cannot be less than ${this.opts.defaultArrayBase}.`);
this.compileError(`An array dimension cannot be less than ${this.opts.defaultArrayBase}.`, arrdim.$loc);
}
});
return { command:'DIM', args:lexprs };
@ -1097,7 +1128,7 @@ export class BASICParser {
return { command:'END' };
}
stmt__ON() : ONGO_Statement {
var expr = this.parseExpr();
var expr = this.parseExprWithType("number");
var gotok = this.consumeToken();
var cmd = {GOTO:'ONGOTO', THEN:'ONGOTO', GOSUB:'ONGOSUB'}[gotok.str]; // THEN only for DEC basic?
if (!cmd) this.compileError(`There should be a GOTO or GOSUB here.`);
@ -1107,8 +1138,8 @@ export class BASICParser {
stmt__DEF() : DEF_Statement {
var lexpr = this.parseVarSubscriptOrFunc();
if (lexpr.args && lexpr.args.length > this.opts.maxDefArgs)
this.compileError(`There can be no more than ${this.opts.maxDefArgs} arguments to a function or subscript.`);
if (!lexpr.name.startsWith('FN')) this.compileError(`Functions defined with DEF must begin with the letters "FN".`)
this.compileError(`There can be no more than ${this.opts.maxDefArgs} arguments to a function or subscript.`, lexpr.$loc);
if (!lexpr.name.startsWith('FN')) this.compileError(`Functions defined with DEF must begin with the letters "FN".`, lexpr.$loc)
// local variables need to be marked as referenced (TODO: only for this scope)
this.vardefs[lexpr.name] = lexpr;
if (lexpr.args != null)
@ -1154,35 +1185,34 @@ export class BASICParser {
var src = this.parseExpr();
this.expectToken('TO');
var dest = this.parseLexpr();
if (dest.valtype == src.valtype)
this.compileError(`CHANGE can only convert strings to numeric arrays, or vice-versa.`, mergeLocs(src.$loc, dest.$loc));
return { command:'CHANGE', src:src, dest:dest };
}
stmt__CONVERT() : CONVERT_Statement {
var src = this.parseExpr();
this.expectToken('TO');
var dest = this.parseLexpr();
if (dest.valtype == src.valtype)
this.compileError(`CONVERT can only convert strings to numbers, or vice-versa.`, mergeLocs(src.$loc, dest.$loc));
return { command:'CONVERT', src:src, dest:dest };
}
// TODO: CHANGE A TO A$ (4th edition, A(0) is len and A(1..) are chars)
stmt__OPTION() : OPTION_Statement {
var tokname = this.consumeToken();
if (tokname.type != TokenType.Ident) this.compileError(`There must be a name after the OPTION statement.`)
var list : string[] = [];
var tok;
do {
tok = this.consumeToken();
if (isEOS(tok)) break;
list.push(tok.str);
} while (true);
this.pushbackToken(tok);
var stmt : OPTION_Statement = { command:'OPTION', optname:tokname.str, optargs:list };
this.parseOptions(stmt);
return stmt;
}
parseOptions(stmt: OPTION_Statement) {
var arg = stmt.optargs[0];
this.optionCount++;
switch (stmt.optname) {
var tokname = this.consumeToken();
var optname = tokname.str.toUpperCase();
if (tokname.type != TokenType.Ident) this.compileError(`There must be a name after the OPTION statement.`)
var tokarg = this.consumeToken();
var arg = tokarg.str.toUpperCase();
switch (optname) {
case 'DIALECT':
if (this.optionCount > 1)
this.compileError(`OPTION DIALECT must be the first OPTION statement in the file.`);
if (this.optionCount > 1) this.compileError(`OPTION DIALECT must be the first OPTION statement in the file.`, tokname.$loc);
let dname = arg || "";
if (dname == "") this.compileError(`OPTION DIALECT requires a dialect name.`, tokname.$loc);
let dialect = DIALECTS[dname.toUpperCase()];
if (dialect) this.opts = dialect;
else this.compileError(`OPTION DIALECT ${dname} is not supported by this compiler.`);
else this.compileError(`${dname} is not a valid dialect.`);
break;
case 'BASE':
let base = parseInt(arg);
@ -1195,20 +1225,23 @@ export class BASICParser {
break;
default:
// maybe it's one of the options?
var name = Object.getOwnPropertyNames(this.opts).find((n) => n.toUpperCase() == stmt.optname);
if (name != null) switch (typeof this.opts[name]) {
case 'boolean' : this.opts[name] = arg ? true : false; return;
case 'number' : this.opts[name] = parseFloat(arg); return;
case 'string' : this.opts[name] = arg; return;
let propname = Object.getOwnPropertyNames(this.opts).find((n) => n.toUpperCase() == optname);
if (propname == null) this.compileError(`${optname} is not a valid option.`, tokname.$loc);
if (arg == null) this.compileError(`OPTION ${optname} requires a parameter.`);
switch (typeof this.opts[propname]) {
case 'boolean' : this.opts[propname] = arg.toUpperCase().startsWith("T") || (arg as any)>0; return;
case 'number' : this.opts[propname] = parseFloat(arg); return;
case 'string' : this.opts[propname] = arg; return;
case 'object' :
if (Array.isArray(this.opts[name]) && arg == 'ALL') {
this.opts[name] = null;
if (Array.isArray(this.opts[propname]) && arg == 'ALL') {
this.opts[propname] = null;
return;
}
this.compileError(`OPTION ${optname} ALL is the only option supported.`);
}
this.compileError(`OPTION ${stmt.optname} is not supported by this compiler.`);
break;
}
return { command:'OPTION', optname:optname, optargs:[arg]}
}
// for workermain
@ -1217,9 +1250,7 @@ export class BASICParser {
var laststmt : Statement;
program.stmts.forEach((stmt, idx) => {
laststmt = stmt;
var sl = stmt.$loc;
sl.offset = idx;
srclines.push(sl);
srclines.push(stmt.$loc);
});
if (this.opts.endStmtRequired && (laststmt == null || laststmt.command != 'END'))
this.dialectError(`All programs must have a final END statement`);
@ -1276,7 +1307,6 @@ export const ECMA55_MINIMAL : BASICOptions = {
defaultArraySize : 11,
defaultValues : false,
stringConcat : false,
typeConvert : false,
maxDimensions : 2,
maxDefArgs : 255,
maxStringLength : 255,
@ -1325,7 +1355,6 @@ export const DARTMOUTH_4TH_EDITION : BASICOptions = {
defaultArraySize : 11,
defaultValues : false,
stringConcat : false,
typeConvert : false,
maxDimensions : 2,
maxDefArgs : 255,
maxStringLength : 255,
@ -1377,7 +1406,6 @@ export const TINY_BASIC : BASICOptions = {
defaultArraySize : 0,
defaultValues : true,
stringConcat : false,
typeConvert : false,
maxDimensions : 0,
maxDefArgs : 255,
maxStringLength : 255,
@ -1413,7 +1441,7 @@ export const TINY_BASIC : BASICOptions = {
export const HP_TIMESHARED_BASIC : BASICOptions = {
dialectName: "HP2000",
asciiOnly : true,
uppercaseOnly : false, // the terminal is usually uppercase
uppercaseOnly : true, // the terminal is usually uppercase
optionalLabels : false,
optionalWhitespace : false,
multipleStmtsPerLine : true,
@ -1424,10 +1452,9 @@ export const HP_TIMESHARED_BASIC : BASICOptions = {
defaultArraySize : 11,
defaultValues : false,
stringConcat : false,
typeConvert : false,
maxDimensions : 2,
maxDefArgs : 255,
maxStringLength : 255,
maxStringLength : 255, // 72 for literals
tickComments : false, // TODO: HP BASIC has 'hh char constants
hexOctalConsts : false,
validKeywords : [
@ -1461,7 +1488,8 @@ export const HP_TIMESHARED_BASIC : BASICOptions = {
chainAssignments : true,
optionalLet : true,
compiledBlocks : true,
// TODO: max line number, array index 9999
maxArrayElements : 5000,
// TODO: max line number
}
export const DEC_BASIC_11 : BASICOptions = {
@ -1478,7 +1506,6 @@ export const DEC_BASIC_11 : BASICOptions = {
defaultArraySize : 11,
defaultValues : true,
stringConcat : true, // can also use &
typeConvert : false,
maxDimensions : 2,
maxDefArgs : 255, // ?
maxStringLength : 255,
@ -1533,7 +1560,6 @@ export const DEC_BASIC_PLUS : BASICOptions = {
defaultArraySize : 11,
defaultValues : true,
stringConcat : true, // can also use "&"
typeConvert : false,
maxDimensions : 2,
maxDefArgs : 255, // ?
maxStringLength : 255,
@ -1595,7 +1621,6 @@ export const BASICODE : BASICOptions = {
defaultArraySize : 11,
defaultValues : false,
stringConcat : true,
typeConvert : false,
maxDimensions : 2,
maxDefArgs : 255,
maxStringLength : 255,
@ -1646,7 +1671,6 @@ export const ALTAIR_BASIC41 : BASICOptions = {
defaultArraySize : 11,
defaultValues : true,
stringConcat : true,
typeConvert : false,
maxDimensions : 128, // "as many as will fit on a single line" ... ?
maxDefArgs : 255,
maxStringLength : 255,
@ -1705,7 +1729,6 @@ export const APPLESOFT_BASIC : BASICOptions = {
defaultArraySize : 11,
defaultValues : true,
stringConcat : true,
typeConvert : false,
maxDimensions : 88,
maxDefArgs : 1, // TODO: no string FNs
maxStringLength : 255,
@ -1765,7 +1788,6 @@ export const BASIC80 : BASICOptions = {
defaultArraySize : 11,
defaultValues : true,
stringConcat : true,
typeConvert : false,
maxDimensions : 255,
maxDefArgs : 255,
maxStringLength : 255,
@ -1826,7 +1848,6 @@ export const MODERN_BASIC : BASICOptions = {
defaultArraySize : 0, // DIM required
defaultValues : false,
stringConcat : true,
typeConvert : false,
maxDimensions : 255,
maxDefArgs : 255,
maxStringLength : 2048, // TODO?

View File

@ -362,11 +362,11 @@ export class BASICRuntime {
this.returnStack.pop();
}
valueToString(obj) : string {
valueToString(obj:basic.Value, padding:boolean) : string {
var str;
if (typeof obj === 'number') {
var numstr = this.float2str(obj, this.opts.printZoneLength - 4);
if (!this.opts.numericPadding)
if (!padding)
return numstr;
else if (numstr.startsWith('-'))
return `${numstr} `;
@ -403,7 +403,7 @@ export class BASICRuntime {
}
printExpr(obj) {
var str = this.valueToString(obj);
var str = this.valueToString(obj, this.opts.numericPadding);
this.column += str.length;
this.print(str);
}
@ -674,16 +674,22 @@ export class BASICRuntime {
if (!end) end = start;
start = this.ROUND(start);
end = this.ROUND(end);
if (start < 1 || end < 1) this.dialectError(`accept a string slice index less than 1`);
if (start < 1) this.dialectError(`accept a string slice index less than 1`);
if (end < start) this.dialectError(`accept a string slice index less than the starting index`);
return (orig + ' '.repeat(start)).substr(0, start-1) + add.substr(0, end+1-start) + orig.substr(end);
}
getStringSlice(s: string, start: number, end: number) {
s = this.checkString(s);
start = this.ROUND(start);
end = this.ROUND(end);
if (start < 1 || end < 1) this.dialectError(`accept a string slice index less than 1`);
return s.substr(start-1, end+1-start);
if (start < 1) this.dialectError(`accept a string slice index less than 1`);
if (end != null) {
end = this.ROUND(end);
if (end < start) this.dialectError(`accept a string slice index less than the starting index`);
return s.substr(start-1, end+1-start);
} else {
return s.substr(start-1);
}
}
checkOnGoto(value: number, labels: string[]) {
@ -804,11 +810,17 @@ export class BASICRuntime {
do__WHILE(stmt : basic.WHILE_Statement) {
var cond = this.expr2js(stmt.cond);
return `this.whileLoop(${cond})`;
if (stmt.endpc != null)
return `if (!(${cond})) { this.curpc = ${stmt.endpc+1}; }`;
else
return `this.whileLoop(${cond})`;
}
do__WEND() {
return `this.nextWhileLoop()`
do__WEND(stmt : basic.WEND_Statement) {
if (stmt.startpc != null)
return `this.curpc = ${stmt.startpc}`;
else
return `this.nextWhileLoop()`
}
do__DEF(stmt : basic.DEF_Statement) {
@ -826,12 +838,12 @@ export class BASICRuntime {
}
_DIM(dim : basic.IndOp) {
// HP BASIC doesn't really have string arrays
// HP BASIC doesn't really have string arrays, just strings
if (this.opts.arraysContainChars && dim.name.endsWith('$'))
return;
return '';
// dimension an array
var argsstr = '';
for (var arg of dim.args) {
// TODO: check for float (or at compile time)
argsstr += ', ' + this.expr2js(arg, {isconst: this.opts.staticArrays});
}
return `this.dimArray(${JSON.stringify(dim.name)}${argsstr});`;
@ -951,8 +963,18 @@ export class BASICRuntime {
}
}
do__CONVERT(stmt : basic.CONVERT_Statement) {
var num2str = stmt.dest.name.endsWith('$');
let src = this.expr2js(stmt.src);
let dest = this.assign2js(stmt.dest);
if (num2str) {
return `${dest} = this.valueToString(${src}, false)`;
} else {
return `${dest} = this.VAL(${src})`;
}
}
// TODO: ONERR, ON ERROR GOTO
// TODO: gosubs nested too deeply
// TODO: memory quota
// TODO: useless loop (! 4th edition)
// TODO: other 4th edition errors
@ -1210,7 +1232,7 @@ export class BASICRuntime {
return this.checkNum(Math.sqrt(arg));
}
STR$(arg : number) : string {
return this.valueToString(this.checkNum(arg));
return this.valueToString(this.checkNum(arg), false);
}
STRING$(len : number, chr : number|string) : string {
len = this.ROUND(len);

View File

@ -1648,7 +1648,7 @@ function setupDebugControls() {
uitoolbar.newGroup();
uitoolbar.grp.prop('id','debug_bar');
if (platform.runEval) {
uitoolbar.add('ctrl+alt+e', 'Restart Debugging', 'glyphicon-repeat', resetAndDebug).prop('id','dbg_restart');
uitoolbar.add('ctrl+alt+e', 'Reset and Debug', 'glyphicon-fast-backward', resetAndDebug).prop('id','dbg_restart');
}
if (platform.stepBack) {
uitoolbar.add('ctrl+alt+b', 'Step Backwards', 'glyphicon-step-backward', runStepBackwards).prop('id','dbg_stepback');

View File

@ -6,6 +6,7 @@ import * as views from "../ide/views";
import { BASICRuntime } from "../common/basic/runtime";
import { BASICProgram } from "../common/basic/compiler";
import { TeleTypeWithKeyboard } from "../common/teletype";
import { lpad } from "../common/util";
const BASIC_PRESETS = [
{ id: 'hello.bas', name: 'Tutorial' },
@ -27,7 +28,7 @@ class BASICPlatform implements Platform {
clock: number = 0;
timer: AnimationTimer;
tty: TeleTypeWithKeyboard;
hotReload: boolean = false;
hotReload: boolean = true;
animcount: number = 0;
constructor(mainElement: HTMLElement) {
@ -199,15 +200,35 @@ class BASICPlatform implements Platform {
}
}
inspect(sym: string) {
var o = this.runtime.vars[sym];
if (o != null) {
return o.toString();
}
let o = this.runtime.vars[sym];
if (o != null) return `${sym} = ${o}`;
}
showHelp(tool:string, ident:string) {
window.open("https://8bitworkshop.com/blog/platforms/basic/", "_help");
}
/*
getDebugCategories() {
return ['Variables'];
}
getDebugInfo(category:string, state) : string {
switch (category) {
case 'Variables': return this.varsToLongString();
}
}
varsToLongString() : string {
var s = '';
var vars = Object.keys(this.runtime.vars);
vars.sort();
for (var name of vars) {
var value = this.runtime.vars[name];
var valstr = value.toString();
if (valstr.length > 24) valstr = `${valstr.substr(0,24)}...(${valstr.length})`;
s += lpad(name,3) + " = " + valstr + "\n";
}
return s;
}
*/
// TODO: debugging (get running state, etc)
onBreakpointHit : BreakpointCallback;

View File

@ -45,13 +45,13 @@ function doBuild(msgs, callback, outlen, nlines, nerrors, options) {
}
assert.ok(err.msg);
}
assert.equal(nerrors, msg.errors.length, "errors");
assert.equal(nerrors, msg.errors.length);
} else {
assert.equal(nerrors||0, 0, "errors");
if (msg.output.lines) { // AST for BASIC
assert.equal(msg.output.lines.length, outlen, "output lines");
assert.equal(nerrors||0, 0);
if (msg.output.stmts) { // AST for BASIC
assert.equal(msg.output.stmts.length, outlen);
} else {
assert.equal(msg.output.code?msg.output.code.length:msg.output.length, outlen, "output binary");
assert.equal(msg.output.code?msg.output.code.length:msg.output.length, outlen);
assert.ok(msg.output.code || msg.output instanceof Uint8Array);
}
if (nlines) {
@ -63,7 +63,7 @@ function doBuild(msgs, callback, outlen, nlines, nerrors, options) {
lstkeys.sort();
for (var key of lstkeys) {
var listing = msg.listings[key];
assert.equal(listing.lines.length, nlines[i++], "listing lines");
assert.equal(listing.lines.length, nlines[i++]);
}
}
}
@ -336,7 +336,7 @@ describe('Worker', function() {
assert.ok(ast);
done(err, msg);
};
doBuild(msgs, done2, 222, 0, 0);
doBuild(msgs, done2, 205, 0, 0);
});
});