1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2024-05-31 13:41:32 +00:00

basic: fixes for non-line-number mode (24, 62, 81), handle unhandledrejection, MODERN default dialect, DARTMOUTH

This commit is contained in:
Steven Hugg 2020-08-16 12:16:29 -05:00
parent 88fa924507
commit 9cedb1af08
21 changed files with 228 additions and 102 deletions

View File

@ -22,6 +22,12 @@
.currentpc-marker { .currentpc-marker {
color: #ff66ee; color: #ff66ee;
} }
.currentpc-span-blocked {
background-color: #7e2a70;
}
.currentpc-marker-blocked {
color: #ffee33;
}
.tooltipbox { .tooltipbox {
position: relative; position: relative;
display: inline-block; display: inline-block;

View File

@ -55,7 +55,8 @@
"test-worker": "NODE_PATH=$(pwd) mocha --recursive --timeout 60000 test/cli/testworker.js", "test-worker": "NODE_PATH=$(pwd) mocha --recursive --timeout 60000 test/cli/testworker.js",
"test-platforms": "NODE_PATH=$(pwd) mocha --recursive --timeout 60000 test/cli/testplatforms.js", "test-platforms": "NODE_PATH=$(pwd) mocha --recursive --timeout 60000 test/cli/testplatforms.js",
"test-profile": "NODE_PATH=$(pwd) mocha --recursive --timeout 60000 --prof test/cli", "test-profile": "NODE_PATH=$(pwd) mocha --recursive --timeout 60000 --prof test/cli",
"start": "electron ." "start": "electron .",
"fuzzbasic": "jsfuzz gen/common/basic/fuzz.js ~/basic/corpus/ --versifier false"
}, },
"main": "electron.js", "main": "electron.js",
"keywords": [ "keywords": [

View File

@ -1,3 +1,5 @@
OPTION DIALECT DEC
0 REM from the DEC EduSystem Handbook
100 REM***23 MATCHES 100 REM***23 MATCHES
110 LET M=23 110 LET M=23
115 PRINT 115 PRINT

View File

@ -1,5 +1,5 @@
OPTION DIALECT HP2000
0 REM http://www.bitsavers.org/bits/HP/paperTapes/JeffM/CRAPS.BAS 0 REM http://www.bitsavers.org/bits/HP/paperTapes/JeffM/CRAPS.BAS
1 OPTION DIALECT HP2000
10 REM THIS IS A SPECIAL VERSION OF CRAPS 10 REM THIS IS A SPECIAL VERSION OF CRAPS
20 REM 1) MAXIMUM BANK IS $1000 20 REM 1) MAXIMUM BANK IS $1000
30 REM 2) THE PLAYER GETS 10 CHANCES TO PLACE A BET 30 REM 2) THE PLAYER GETS 10 CHANCES TO PLACE A BET

View File

@ -1,3 +1,4 @@
OPTION DIALECT ALTAIR
10 REM *** CONVERTED FROM THE ORIGINAL FOCAL PROGRAM AND MODIFIED 10 REM *** CONVERTED FROM THE ORIGINAL FOCAL PROGRAM AND MODIFIED
20 REM *** FOR EDUSYSTEM 70 BY DAVID AHL, DIGITAL 20 REM *** FOR EDUSYSTEM 70 BY DAVID AHL, DIGITAL
30 REM *** MODIFIED FOR 8K MICROSOFT BASIC BY PETER TURNBULL 30 REM *** MODIFIED FOR 8K MICROSOFT BASIC BY PETER TURNBULL

View File

@ -1,4 +1,4 @@
0 OPTION DIALECT DEC:REM http://www.bitsavers.org/bits/DEC/pdp10/games/ OPTION DIALECT DEC:REM http://www.bitsavers.org/bits/DEC/pdp10/games/
00001 PRINT " This is the latest version as of 2-Jun-82." 00001 PRINT " This is the latest version as of 2-Jun-82."
00005 DIM B$(30) 00005 DIM B$(30)
00010 F=0 00010 F=0

View File

@ -1,8 +1,9 @@
OPTION DIALECT DARTMOUTH
10 PRINT "HELLO! LET'S PROGRAM IN BASIC." 10 PRINT "HELLO! LET'S PROGRAM IN BASIC."
15 PRINT 15 PRINT
20 INPUT "WOULD YOU MIND TYPING IN YOUR NAME";A$ 20 INPUT "WOULD YOU MIND TYPING IN YOUR NAME";A$
25 PRINT 25 PRINT
30 PRINT "THANKS, ";A$;"! THIS WILL BE FUN!";CHR$(7) 30 PRINT "THANKS, ";A$;"! THIS WILL BE FUN!"
35 PRINT 35 PRINT
40 INPUT "NOW TELL ME YOUR FAVORITE NUMBER";N 40 INPUT "NOW TELL ME YOUR FAVORITE NUMBER";N
45 PRINT 45 PRINT

View File

@ -1,4 +1,4 @@
0 OPTION DIALECT HP OPTION DIALECT HP
1 REM **** HP BASIC PROGRAM LIBRARY ************************** 1 REM **** HP BASIC PROGRAM LIBRARY **************************
2 REM 2 REM
3 REM LANDER: ROCKET LANDING VEHICLE 3 REM LANDER: ROCKET LANDING VEHICLE

View File

@ -1,4 +1,4 @@
1 OPTION DIALECT HP OPTION DIALECT HP
10 PRINT "DANIEL O'ROURKE FEB. 23, 1977" 10 PRINT "DANIEL O'ROURKE FEB. 23, 1977"
20 PRINT "MINI-COMPUTOR 102" 20 PRINT "MINI-COMPUTOR 102"
30 PRINT "SUBJECT: MORTGAGE PAYMENT" 30 PRINT "SUBJECT: MORTGAGE PAYMENT"

View File

@ -1,5 +1,5 @@
001 OPTION DIALECT MODERN OPTION DIALECT MODERN
002 OPTION CPUSPEED MAX OPTION CPUSPEED MAX
010 REM***A PRIME NUMBER SIEVE BENCHMARK 010 REM***A PRIME NUMBER SIEVE BENCHMARK
020 T = TIMER 020 T = TIMER
022 C = 0 022 C = 0

View File

@ -1,8 +1,8 @@
OPTION DIALECT HP
OPTION BASE 0:REM I GUESS HP HAS ZERO BASE???
1 REM***STAR TRADER FROM 1 REM***STAR TRADER FROM
2 REM***http://www.dunnington.info/public/basicgames/ 2 REM***http://www.dunnington.info/public/basicgames/
3 REM***2 chain files merged and ported to 8bitworkshop 3 REM***2 chain files merged and ported to 8bitworkshop
5 OPTION DIALECT HP
6 OPTION BASE 0:REM I GUESS HP HAS ZERO BASE???
10010 DIM S[12,15],T[12,12],T$[72],B[3,12] 10010 DIM S[12,15],T[12,12],T$[72],B[3,12]
10020 REM COM W,D9,K9,X9,D1,X1,P9,T9,S9,Y9,H 10020 REM COM W,D9,K9,X9,D1,X1,P9,T9,S9,Y9,H
10030 DIM M[6,3],C[6,3]:REM COM Y1,R9,G9,Q 10030 DIM M[6,3],C[6,3]:REM COM Y1,R9,G9,Q

View File

@ -1,6 +1,6 @@
OPTION DIALECT HP
1 REM from: http://www.dunnington.info/public/basicgames/WUMPUS.hp 1 REM from: http://www.dunnington.info/public/basicgames/WUMPUS.hp
2 REM extracted from HP library tape 2 REM extracted from HP library tape
5 OPTION DIALECT HP
10 REM- HUNT THE WUMPUS 10 REM- HUNT THE WUMPUS
20 PRINT "INSTRUCTIONS (Y-N)"; 20 PRINT "INSTRUCTIONS (Y-N)";
30 INPUT I$ 30 INPUT I$

View File

@ -238,7 +238,6 @@ CodeMirror.defineMode("basic", function(conf, parserConf) {
} }
var external = { var external = {
electricChars:"dDpPtTfFeE ",
startState: function() { startState: function() {
return { return {
tokenize: tokenBase, tokenize: tokenBase,
@ -246,8 +245,6 @@ CodeMirror.defineMode("basic", function(conf, parserConf) {
currentIndent: 0, currentIndent: 0,
nextLineIndent: 0, nextLineIndent: 0,
doInCurrentLine: false doInCurrentLine: false
}; };
}, },

View File

@ -11,6 +11,7 @@ export interface BASICOptions {
squareBrackets : boolean; // "[" and "]" interchangable with "(" and ")"? squareBrackets : boolean; // "[" and "]" interchangable with "(" and ")"?
tickComments : boolean; // support 'comments? tickComments : boolean; // support 'comments?
hexOctalConsts : boolean; // support &H and &O integer constants? hexOctalConsts : boolean; // support &H and &O integer constants?
optionalLet : boolean; // LET is optional
chainAssignments : boolean; // support A = B = value (HP2000) chainAssignments : boolean; // support A = B = value (HP2000)
validKeywords : string[]; // valid keywords (or null for accept all) validKeywords : string[]; // valid keywords (or null for accept all)
validFunctions : string[]; // valid functions (or null for accept all) validFunctions : string[]; // valid functions (or null for accept all)
@ -18,7 +19,7 @@ export interface BASICOptions {
// VALUES AND OPERATORS // VALUES AND OPERATORS
defaultValues : boolean; // initialize unset variables to default value? (0 or "") defaultValues : boolean; // initialize unset variables to default value? (0 or "")
stringConcat : boolean; // can concat strings with "+" operator? stringConcat : boolean; // can concat strings with "+" operator?
typeConvert : boolean; // type convert strings <-> numbers? typeConvert : boolean; // type convert strings <-> numbers? (NOT USED)
checkOverflow : boolean; // check for overflow of numerics? checkOverflow : boolean; // check for overflow of numerics?
bitwiseLogic : boolean; // -1 = TRUE, 0 = FALSE, AND/OR/NOT done with bitwise ops bitwiseLogic : boolean; // -1 = TRUE, 0 = FALSE, AND/OR/NOT done with bitwise ops
maxStringLength : number; // maximum string length in chars maxStringLength : number; // maximum string length in chars
@ -390,15 +391,17 @@ export class BASICParser {
let tok = this.consumeToken(); let tok = this.consumeToken();
switch (tok.type) { switch (tok.type) {
case TokenType.Ident: case TokenType.Ident:
if (this.opts.optionalLabels) { if (this.opts.optionalLabels || tok.str == 'OPTION') {
if (this.peekToken().str == ':') { // is it a label: // is it a "label :" and not a keyword like "PRINT : "
if (this.peekToken().str == ':' && !this.supportsCommand(tok.str)) {
this.consumeToken(); // eat the ":" this.consumeToken(); // eat the ":"
// fall through to the next case // fall through to the next case
} else { } else {
this.pushbackToken(tok); // nope this.pushbackToken(tok); // nope
break; break;
} }
} else this.dialectError(`optional line numbers`); } else
this.dialectError(`optional line numbers`);
case TokenType.Int: case TokenType.Int:
this.setCurrentLabel(line, tok.str); this.setCurrentLabel(line, tok.str);
break; break;
@ -406,9 +409,16 @@ export class BASICParser {
case TokenType.Float: case TokenType.Float:
this.compileError(`Line numbers must be positive integers.`); this.compileError(`Line numbers must be positive integers.`);
break; break;
case TokenType.Operator:
if (this.supportsCommand(tok.str) && this.validKeyword(tok.str)) {
this.pushbackToken(tok);
break; // "?" is allowed
}
default: default:
if (this.opts.optionalLabels) this.compileError(`A line must start with a line number, command, or label.`); if (this.opts.optionalLabels)
else this.compileError(`A line must start with a line number.`); this.compileError(`A line must start with a line number, command, or label.`);
else
this.compileError(`A line must start with a line number.`);
case TokenType.Remark: case TokenType.Remark:
break; break;
} }
@ -525,9 +535,9 @@ export class BASICParser {
validKeyword(keyword: string) : string { validKeyword(keyword: string) : string {
return (this.opts.validKeywords && this.opts.validKeywords.indexOf(keyword) < 0) ? null : keyword; return (this.opts.validKeywords && this.opts.validKeywords.indexOf(keyword) < 0) ? null : keyword;
} }
supportsKeyword(keyword: string) { supportsCommand(cmd: string) : () => Statement {
if (this['stmt__' + keyword] != null) return true; if (cmd == '?') return this.stmt__PRINT;
return false; else return this['stmt__' + cmd];
} }
parseStatement(): Statement | null { parseStatement(): Statement | null {
// eat extra ":" (should have separate property for this) // eat extra ":" (should have separate property for this)
@ -555,13 +565,15 @@ export class BASICParser {
cmd = 'GOSUB'; cmd = 'GOSUB';
} }
// lookup JS function for command // lookup JS function for command
var fn = this['stmt__' + cmd]; var fn = this.supportsCommand(cmd);
if (fn) { if (fn) {
if (this.validKeyword(cmd) == null) if (this.validKeyword(cmd) == null)
this.dialectError(`the ${cmd} keyword`); this.dialectError(`the ${cmd} statement`);
stmt = fn.bind(this)() as Statement; stmt = fn.bind(this)();
break; break;
} else if (this.peekToken().str == '=' || this.peekToken().str == '(') { } else if (this.peekToken().str == '=' || this.peekToken().str == '(') {
if (!this.opts.optionalLet)
this.dialectError(`assignments without a preceding LET`);
// 'A = expr' or 'A(X) = expr' // 'A = expr' or 'A(X) = expr'
this.pushbackToken(cmdtok); this.pushbackToken(cmdtok);
stmt = this.stmt__LET(); stmt = this.stmt__LET();
@ -631,23 +643,22 @@ export class BASICParser {
if (this.opts.computedGoto) { if (this.opts.computedGoto) {
// parse expression, but still add to list of label targets if constant // parse expression, but still add to list of label targets if constant
var expr = this.parseExpr(); var expr = this.parseExpr();
if ((expr as Literal).value != null) { if (isLiteral(expr)) this.targets[expr.value] = this.lasttoken.$loc;
this.targets[(expr as Literal).value] = this.lasttoken.$loc;
}
return expr; return expr;
} } else {
// parse a single number or ident label // parse a single number or ident label
var tok = this.consumeToken(); var tok = this.consumeToken();
switch (tok.type) { switch (tok.type) {
case TokenType.Ident: case TokenType.Ident:
if (!this.opts.optionalLabels) this.dialectError(`labels other than line numbers`) if (!this.opts.optionalLabels) this.dialectError(`labels other than line numbers`)
case TokenType.Int: case TokenType.Int:
var label = tok.str; var label = tok.str;
this.targets[label] = tok.$loc; this.targets[label] = tok.$loc;
return {value:label}; return {value:label};
default: default:
if (this.opts.optionalLabels) this.compileError(`There should be a line number or label here.`); var what = this.opts.optionalLabels ? "label or line number" : "line number";
else this.compileError(`There should be a line number here.`); this.compileError(`There should be a ${what} here.`);
}
} }
} }
parseDatumList(): Literal[] { parseDatumList(): Literal[] {
@ -841,13 +852,14 @@ export class BASICParser {
__GO(cmd: "GOTO"|"GOSUB"): GOTO_Statement | GOSUB_Statement | ONGO_Statement { __GO(cmd: "GOTO"|"GOSUB"): GOTO_Statement | GOSUB_Statement | ONGO_Statement {
var expr = this.parseLabel(); var expr = this.parseLabel();
// GOTO (expr) OF (labels...) // GOTO (expr) OF (labels...)
if (this.opts.computedGoto && this.peekToken().str == 'OF') { if (this.peekToken().str == this.validKeyword('OF')) {
this.expectToken('OF'); this.expectToken('OF');
let newcmd : 'ONGOTO'|'ONGOSUB' = (cmd == 'GOTO') ? 'ONGOTO' : 'ONGOSUB'; let newcmd : 'ONGOTO'|'ONGOSUB' = (cmd == 'GOTO') ? 'ONGOTO' : 'ONGOSUB';
return { command:newcmd, expr:expr, labels:this.parseLabelList() }; return { command: newcmd, expr: expr, labels: this.parseLabelList() };
} else {
// regular GOTO or GOSUB
return { command: cmd, label: expr };
} }
// regular GOTO or GOSUB
return { command: cmd, label: expr };
} }
stmt__IF(): IF_Statement { stmt__IF(): IF_Statement {
var cond = this.parseExpr(); var cond = this.parseExpr();
@ -1100,7 +1112,8 @@ export class BASICParser {
checkLabels() { checkLabels() {
for (let targ in this.targets) { for (let targ in this.targets) {
if (this.labels[targ] == null) { if (this.labels[targ] == null) {
this.addError(`There isn't a line number ${targ}.`, this.targets[targ]); var what = this.opts.optionalLabels && isNaN(parseInt(targ)) ? "label named" : "line number";
this.addError(`There isn't a ${what} ${targ}.`, this.targets[targ]);
} }
} }
} }
@ -1152,6 +1165,56 @@ export const ECMA55_MINIMAL : BASICOptions = {
arraysContainChars : false, arraysContainChars : false,
endStmtRequired : true, endStmtRequired : true,
chainAssignments : false, chainAssignments : false,
optionalLet : false,
}
export const DARTMOUTH_4TH_EDITION : BASICOptions = {
dialectName: "DARTMOUTH4",
asciiOnly : true,
uppercaseOnly : true,
optionalLabels : false,
optionalWhitespace : false,
varNaming : "A1",
staticArrays : true,
sharedArrayNamespace : false,
defaultArrayBase : 0,
defaultArraySize : 11,
defaultValues : false,
stringConcat : false,
typeConvert : false,
maxDimensions : 2,
maxDefArgs : 255,
maxStringLength : 255,
tickComments : true,
hexOctalConsts : false,
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',
'CHANGE','MAT','RANDOM','RESTORE$','RESTORE*',
],
validFunctions : [
'ABS','ATN','COS','EXP','INT','LOG','RND','SGN','SIN','SQR','TAB','TAN',
'TRN','INV','DET','NUM','ZER', // NUM = # of strings input for MAT INPUT
],
validOperators : [
'=', '<>', '<', '>', '<=', '>=', '+', '-', '*', '/', '^'
],
printZoneLength : 15,
numericPadding : true,
checkOverflow : true,
testInitialFor : true,
optionalNextVar : false,
multipleNextVars : false,
bitwiseLogic : false,
checkOnGotoIndex : true,
computedGoto : false,
restoreWithLabel : false,
squareBrackets : false,
arraysContainChars : false,
endStmtRequired : true,
chainAssignments : true,
optionalLet : false,
} }
// TODO: only integers supported // TODO: only integers supported
@ -1191,15 +1254,15 @@ export const TINY_BASIC : BASICOptions = {
multipleNextVars : false, multipleNextVars : false,
bitwiseLogic : false, bitwiseLogic : false,
checkOnGotoIndex : false, checkOnGotoIndex : false,
computedGoto : true, // TODO: is it really though? computedGoto : true,
restoreWithLabel : false, restoreWithLabel : false,
squareBrackets : false, squareBrackets : false,
arraysContainChars : false, arraysContainChars : false,
endStmtRequired : false, endStmtRequired : false,
chainAssignments : false, chainAssignments : false,
optionalLet : false,
} }
export const HP_TIMESHARED_BASIC : BASICOptions = { export const HP_TIMESHARED_BASIC : BASICOptions = {
dialectName: "HP2000", dialectName: "HP2000",
asciiOnly : true, asciiOnly : true,
@ -1242,12 +1305,13 @@ export const HP_TIMESHARED_BASIC : BASICOptions = {
multipleNextVars : false, multipleNextVars : false,
bitwiseLogic : false, bitwiseLogic : false,
checkOnGotoIndex : false, checkOnGotoIndex : false,
computedGoto : true, computedGoto : true, // not really, but we do parse expressions for GOTO ... OF
restoreWithLabel : true, restoreWithLabel : true,
squareBrackets : true, squareBrackets : true,
arraysContainChars : true, arraysContainChars : true,
endStmtRequired : true, endStmtRequired : true,
chainAssignments : true, chainAssignments : true,
optionalLet : true,
// TODO: max line number, array index 9999 // TODO: max line number, array index 9999
} }
@ -1298,6 +1362,7 @@ export const DEC_BASIC_11 : BASICOptions = {
arraysContainChars : false, arraysContainChars : false,
endStmtRequired : false, endStmtRequired : false,
chainAssignments : false, chainAssignments : false,
optionalLet : true,
// TODO: max line number 32767 // TODO: max line number 32767
// TODO: \ separator, % int vars and constants, 'single' quoted // TODO: \ separator, % int vars and constants, 'single' quoted
// TODO: can't compare strings and numbers // TODO: can't compare strings and numbers
@ -1356,6 +1421,7 @@ export const DEC_BASIC_PLUS : BASICOptions = {
arraysContainChars : false, arraysContainChars : false,
endStmtRequired : false, endStmtRequired : false,
chainAssignments : false, // TODO: can chain with "," not "=" chainAssignments : false, // TODO: can chain with "," not "="
optionalLet : true,
// TODO: max line number 32767 // TODO: max line number 32767
// TODO: \ separator, % int vars and constants, 'single' quoted // TODO: \ separator, % int vars and constants, 'single' quoted
// TODO: can't compare strings and numbers // TODO: can't compare strings and numbers
@ -1408,6 +1474,7 @@ export const BASICODE : BASICOptions = {
arraysContainChars : false, arraysContainChars : false,
endStmtRequired : false, endStmtRequired : false,
chainAssignments : false, chainAssignments : false,
optionalLet : true,
} }
export const ALTAIR_BASIC41 : BASICOptions = { export const ALTAIR_BASIC41 : BASICOptions = {
@ -1464,6 +1531,7 @@ export const ALTAIR_BASIC41 : BASICOptions = {
arraysContainChars : false, arraysContainChars : false,
endStmtRequired : false, endStmtRequired : false,
chainAssignments : false, chainAssignments : false,
optionalLet : true,
} }
export const APPLESOFT_BASIC : BASICOptions = { export const APPLESOFT_BASIC : BASICOptions = {
@ -1521,6 +1589,7 @@ export const APPLESOFT_BASIC : BASICOptions = {
arraysContainChars : false, arraysContainChars : false,
endStmtRequired : false, endStmtRequired : false,
chainAssignments : false, chainAssignments : false,
optionalLet : true,
} }
export const BASIC80 : BASICOptions = { export const BASIC80 : BASICOptions = {
@ -1579,6 +1648,7 @@ export const BASIC80 : BASICOptions = {
arraysContainChars : false, arraysContainChars : false,
endStmtRequired : false, endStmtRequired : false,
chainAssignments : false, chainAssignments : false,
optionalLet : true,
} }
export const MODERN_BASIC : BASICOptions = { export const MODERN_BASIC : BASICOptions = {
@ -1594,7 +1664,7 @@ export const MODERN_BASIC : BASICOptions = {
defaultArraySize : 0, // DIM required defaultArraySize : 0, // DIM required
defaultValues : false, defaultValues : false,
stringConcat : true, stringConcat : true,
typeConvert : true, typeConvert : false,
maxDimensions : 255, maxDimensions : 255,
maxDefArgs : 255, maxDefArgs : 255,
maxStringLength : 2048, // TODO? maxStringLength : 2048, // TODO?
@ -1611,12 +1681,13 @@ export const MODERN_BASIC : BASICOptions = {
multipleNextVars : true, multipleNextVars : true,
bitwiseLogic : true, bitwiseLogic : true,
checkOnGotoIndex : true, checkOnGotoIndex : true,
computedGoto : true, computedGoto : false,
restoreWithLabel : true, restoreWithLabel : true,
squareBrackets : true, squareBrackets : true,
arraysContainChars : false, arraysContainChars : false,
endStmtRequired : false, endStmtRequired : false,
chainAssignments : false, chainAssignments : true,
optionalLet : true,
} }
// TODO: integer vars // TODO: integer vars
@ -1624,7 +1695,9 @@ export const MODERN_BASIC : BASICOptions = {
// TODO: excess INPUT ignored, error msg // TODO: excess INPUT ignored, error msg
export const DIALECTS = { export const DIALECTS = {
"DEFAULT": ALTAIR_BASIC41, "DEFAULT": MODERN_BASIC,
"DARTMOUTH": DARTMOUTH_4TH_EDITION,
"DARTMOUTH4": DARTMOUTH_4TH_EDITION,
"ALTAIR": ALTAIR_BASIC41, "ALTAIR": ALTAIR_BASIC41,
"ALTAIR4": ALTAIR_BASIC41, "ALTAIR4": ALTAIR_BASIC41,
"ALTAIR41": ALTAIR_BASIC41, "ALTAIR41": ALTAIR_BASIC41,

44
src/common/basic/fuzz.ts Normal file
View File

@ -0,0 +1,44 @@
import { BASICParser, DIALECTS, BASICOptions, CompileError } from "./compiler";
import { BASICRuntime } from "./runtime";
import { EmuHalt } from "../emu";
process.on('unhandledRejection', (reason, promise) => {
if (!(reason instanceof EmuHalt))
console.log('Unhandled Rejection at:', promise, 'reason:', reason);
// Application specific logging, throwing an error, or other logic here
});
export function fuzz(buf) {
var parser = new BASICParser();
var str = buf.toString();
try {
var pgm = parser.parseFile(str, "test.bas");
var runtime = new BASICRuntime();
runtime.load(pgm);
runtime.reset();
runtime.print = (s) => {
if (s == null) throw new Error("PRINT null string");
}
runtime.input = function(prompt: string, nargs: number) : Promise<string[]> {
var p = new Promise<string[]>( (resolve, reject) => {
var arr = [];
for (var i=0; i<Math.random()*10; i++)
arr.push(i+"");
resolve(arr);
});
return p;
}
for (var i=0; i<50000; i++) {
if (!runtime.step()) break;
}
if (Math.random() < 0.001) runtime.load(pgm);
for (var i=0; i<50000; i++) {
if (!runtime.step()) break;
}
} catch (e) {
if (e instanceof EmuHalt) return;
if (e instanceof CompileError) return;
throw e;
}
}

View File

@ -1,24 +0,0 @@
import { BASICParser } from "./compiler";
var parser = new BASICParser();
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
rl.on('line', function (line) {
parser.tokenize(line);
console.log(parser.tokens);
try {
var ast = parser.parse();
console.log(JSON.stringify(ast, null, 4));
} catch (e) {
console.log(e);
}
if (parser.errors.length) {
console.log(parser.errors);
parser.errors = [];
}
})

View File

@ -21,14 +21,22 @@ var fs = require('fs');
var parser = new BASICParser(); var parser = new BASICParser();
var runtime = new BASICRuntime(); var runtime = new BASICRuntime();
function getCurrentLabel() {
var loc = runtime.getCurrentSourceLocation();
return loc ? loc.label : "?";
}
// parse args // parse args
var filename = '/dev/stdin'; var filename = '/dev/stdin';
var args = process.argv.slice(2); var args = process.argv.slice(2);
var force = false;
for (var i=0; i<args.length; i++) { for (var i=0; i<args.length; i++) {
if (args[i] == '-v') if (args[i] == '-v')
runtime.trace = true; runtime.trace = true;
else if (args[i] == '-d') else if (args[i] == '-d')
parser.opts = DIALECTS[args[++i]] || Error('no such dialect'); parser.opts = DIALECTS[args[++i]] || Error('no such dialect');
else if (args[i] == '-f')
force = true;
else if (args[i] == '--dialects') else if (args[i] == '--dialects')
dumpDialectInfo(); dumpDialectInfo();
else else
@ -45,13 +53,13 @@ try {
console.log(`@@@ ${e}`); console.log(`@@@ ${e}`);
} }
parser.errors.forEach((err) => console.log(`@@@ ${err.msg} (line ${err.label})`)); parser.errors.forEach((err) => console.log(`@@@ ${err.msg} (line ${err.label})`));
if (parser.errors.length) process.exit(2); if (parser.errors.length && !force) process.exit(2);
// run program // run program
try { try {
runtime.load(pgm); runtime.load(pgm);
} catch (e) { } catch (e) {
console.log(`### ${e.message} (line ${runtime.getCurrentSourceLocation().label})`); console.log(`### ${e.message} (line ${getCurrentLabel()})`);
process.exit(1); process.exit(1);
} }
runtime.reset(); runtime.reset();
@ -86,7 +94,7 @@ runtime.resume = function() {
process.exit(0); process.exit(0);
} }
} catch (e) { } catch (e) {
console.log(`### ${e.message} (line ${runtime.getCurrentSourceLocation().label})`); console.log(`### ${e.message} (line ${getCurrentLabel()})`);
process.exit(1); process.exit(1);
} }
}); });
@ -98,7 +106,7 @@ runtime.resume();
function dumpDialectInfo() { function dumpDialectInfo() {
var dialects = new Set<BASICOptions>(); var dialects = new Set<BASICOptions>();
var array = {}; var array = {};
var SELECTED_DIALECTS = ['TINY','ECMA55','HP','DEC','ALTAIR','BASIC80','MODERN']; var SELECTED_DIALECTS = ['TINY','ECMA55','DARTMOUTH','HP','DEC','ALTAIR','BASIC80','MODERN'];
SELECTED_DIALECTS.forEach((dkey) => { SELECTED_DIALECTS.forEach((dkey) => {
dialects.add(DIALECTS[dkey]); dialects.add(DIALECTS[dkey]);
}); });
@ -121,7 +129,7 @@ function dumpDialectInfo() {
}); });
dialects.forEach((dialect) => { dialects.forEach((dialect) => {
ALL_KEYWORDS.forEach((keyword) => { ALL_KEYWORDS.forEach((keyword) => {
if (parser.supportsKeyword(keyword)) { if (parser.supportsCommand(keyword)) {
var has = dialect.validKeywords == null || dialect.validKeywords.indexOf(keyword) >= 0; var has = dialect.validKeywords == null || dialect.validKeywords.indexOf(keyword) >= 0;
keyword = '`'+keyword+'`' keyword = '`'+keyword+'`'
if (!array[keyword]) array[keyword] = []; if (!array[keyword]) array[keyword] = [];

View File

@ -473,7 +473,7 @@ export class BASICRuntime {
this.runtimeError(`I can't call a function here.`); this.runtimeError(`I can't call a function here.`);
// is it a subscript? // is it a subscript?
if (expr.args) { if (expr.args) {
// set array slice (HP BASIC) // TODO: set array slice (HP BASIC)
if (this.opts.arraysContainChars && expr.name.endsWith('$')) { if (this.opts.arraysContainChars && expr.name.endsWith('$')) {
this.runtimeError(`I can't set array slices via this command yet.`); this.runtimeError(`I can't set array slices via this command yet.`);
} else { } else {
@ -566,8 +566,8 @@ export class BASICRuntime {
// converts a variable to string/number based on var name // converts a variable to string/number based on var name
assign(name: string, right: number|string, isRead?:boolean) : number|string { assign(name: string, right: number|string, isRead?:boolean) : number|string {
// convert data? READ always converts if read into string // convert data? READ always converts if read into string
if (this.opts.typeConvert || (isRead && name.endsWith("$"))) if (isRead && name.endsWith("$"))
return this.convert(name, right); return this.checkValue(this.convert(name, right), name);
// TODO: use options // TODO: use options
if (name.endsWith("$")) { if (name.endsWith("$")) {
return this.convertToString(right, name); return this.convertToString(right, name);
@ -604,9 +604,9 @@ export class BASICRuntime {
if (this.opts.staticArrays) return; if (this.opts.staticArrays) return;
else this.runtimeError(`I already dimensioned this array (${name}) earlier.`) else this.runtimeError(`I already dimensioned this array (${name}) earlier.`)
} }
if (this.getTotalArrayLength(dims) > this.opts.maxArrayElements) { var total = this.getTotalArrayLength(dims);
if (total > this.opts.maxArrayElements)
this.runtimeError(`I can't create an array with this many elements.`); this.runtimeError(`I can't create an array with this many elements.`);
}
var isstring = name.endsWith('$'); var isstring = name.endsWith('$');
// if numeric value, we use Float64Array which inits to 0 // if numeric value, we use Float64Array which inits to 0
var arrcons = isstring ? Array : Float64Array; var arrcons = isstring ? Array : Float64Array;
@ -635,7 +635,7 @@ export class BASICRuntime {
getArray(name: string, order: number) : [] { getArray(name: string, order: number) : [] {
if (!this.arrays[name]) { if (!this.arrays[name]) {
if (this.opts.defaultArraySize == 0) if (this.opts.defaultArraySize == 0)
this.dialectError(`automatically declare arrays without a DIM statement`); this.dialectError(`automatically declare arrays without a DIM statement (or did you mean to call a function?)`);
if (order == 1) if (order == 1)
this.dimArray(name, this.opts.defaultArraySize-1); this.dimArray(name, this.opts.defaultArraySize-1);
else if (order == 2) else if (order == 2)
@ -648,7 +648,7 @@ export class BASICRuntime {
arrayGet(name: string, ...indices: number[]) : basic.Value { arrayGet(name: string, ...indices: number[]) : basic.Value {
var arr = this.getArray(name, indices.length); var arr = this.getArray(name, indices.length);
indices = indices.map(Math.round); indices = indices.map(this.ROUND.bind(this));
var v = arr; var v = arr;
for (var i=0; i<indices.length; i++) { for (var i=0; i<indices.length; i++) {
var idx = indices[i]; var idx = indices[i];
@ -668,10 +668,20 @@ export class BASICRuntime {
// for HP BASIC string slicing (TODO?) // for HP BASIC string slicing (TODO?)
modifyStringSlice(orig: string, add: string, start: number, end: number) : string { modifyStringSlice(orig: string, add: string, start: number, end: number) : string {
orig = orig || ""; orig = orig || "";
return (orig + ' '.repeat(start)).substr(0, start-1) + add + orig.substr(end); this.checkString(orig);
this.checkString(add);
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`);
return (orig + ' '.repeat(start)).substr(0, start-1) + add.substr(0, end+1-start) + orig.substr(end);
} }
getStringSlice(s: string, start: number, end: number) { getStringSlice(s: string, start: number, end: number) {
s = this.checkString(s); 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); return s.substr(start-1, end+1-start);
} }
@ -707,7 +717,8 @@ export class BASICRuntime {
var s = ''; var s = '';
for (var arg of stmt.args) { for (var arg of stmt.args) {
var expr = this.expr2js(arg); var expr = this.expr2js(arg);
s += `this.printExpr(${expr});`; var name = (expr as any).name;
s += `this.printExpr(this.checkValue(${expr}, ${JSON.stringify(name)}));`;
} }
return s; return s;
} }
@ -751,7 +762,7 @@ export class BASICRuntime {
if (this.opts.arraysContainChars && lexpr.args && lexpr.name.endsWith('$')) { if (this.opts.arraysContainChars && lexpr.args && lexpr.name.endsWith('$')) {
s += `this.vars.${lexpr.name} = this.modifyStringSlice(this.vars.${lexpr.name}, _right, ` s += `this.vars.${lexpr.name} = this.modifyStringSlice(this.vars.${lexpr.name}, _right, `
s += lexpr.args.map((arg) => this.expr2js(arg)).join(', '); s += lexpr.args.map((arg) => this.expr2js(arg)).join(', ');
s += ')'; s += ');';
} else { } else {
var ljs = this.assign2js(lexpr); var ljs = this.assign2js(lexpr);
s += `${ljs} = this.assign(${JSON.stringify(lexpr.name)}, _right);`; s += `${ljs} = this.assign(${JSON.stringify(lexpr.name)}, _right);`;
@ -956,7 +967,7 @@ export class BASICRuntime {
return exprname.endsWith("$") ? "" : 0; return exprname.endsWith("$") ? "" : 0;
} }
if (exprname != null && obj == null) { if (exprname != null && obj == null) {
this.runtimeError(`I haven't set a value for ${exprname}.`); this.runtimeError(`I haven't assigned a value to ${exprname}.`);
} else if (exprname != null) { } else if (exprname != null) {
this.runtimeError(`I got an invalid value for ${exprname}: ${obj}`); this.runtimeError(`I got an invalid value for ${exprname}: ${obj}`);
} else { } else {
@ -1193,7 +1204,7 @@ export class BASICRuntime {
len = this.ROUND(len); len = this.ROUND(len);
if (len <= 0) return ''; if (len <= 0) return '';
if (len > this.opts.maxStringLength) if (len > this.opts.maxStringLength)
this.runtimeError(`I can't create a string longer than ${this.opts.maxStringLength} characters.`); this.dialectError(`create a string longer than ${this.opts.maxStringLength} characters`);
if (typeof chr === 'string') if (typeof chr === 'string')
return chr.substr(0,1).repeat(len); return chr.substr(0,1).repeat(len);
else else

View File

@ -1901,7 +1901,8 @@ function globalErrorHandler(msgevent) {
if (msg.indexOf("QuotaExceededError") >= 0) { if (msg.indexOf("QuotaExceededError") >= 0) {
requestPersistPermission(false, false); requestPersistPermission(false, false);
} else { } else {
var err = msgevent.error; var err = msgevent.error || msgevent.reason;
msg = err.message || msg;
showExceptionAsError(err, msg); showExceptionAsError(err, msg);
} }
} }
@ -1909,10 +1910,12 @@ function globalErrorHandler(msgevent) {
// catch errors // catch errors
function installErrorHandler() { function installErrorHandler() {
window.addEventListener('error', globalErrorHandler); window.addEventListener('error', globalErrorHandler);
window.addEventListener('unhandledrejection', globalErrorHandler);
} }
function uninstallErrorHandler() { function uninstallErrorHandler() {
window.removeEventListener('error', globalErrorHandler); window.removeEventListener('error', globalErrorHandler);
window.removeEventListener('unhandledrejection', globalErrorHandler);
} }
function gotoNewLocation(replaceHistory? : boolean) { function gotoNewLocation(replaceHistory? : boolean) {

View File

@ -329,10 +329,12 @@ export class SourceEditor implements ProjectView {
} }
setCurrentLine(line:SourceLocation, moveCursor:boolean) { setCurrentLine(line:SourceLocation, moveCursor:boolean) {
var blocked = platform.isBlocked && platform.isBlocked();
var addCurrentMarker = (line:SourceLocation) => { var addCurrentMarker = (line:SourceLocation) => {
var div = document.createElement("div"); var div = document.createElement("div");
div.classList.add('currentpc-marker'); var cls = blocked ? 'currentpc-marker-blocked' : 'currentpc-marker';
div.classList.add(cls);
div.appendChild(document.createTextNode("\u25b6")); div.appendChild(document.createTextNode("\u25b6"));
this.editor.setGutterMarker(line.line-1, "gutter-info", div); this.editor.setGutterMarker(line.line-1, "gutter-info", div);
} }
@ -343,7 +345,8 @@ export class SourceEditor implements ProjectView {
if (moveCursor) { if (moveCursor) {
this.editor.setCursor({line:line.line-1,ch:line.start||0}, {scroll:true}); this.editor.setCursor({line:line.line-1,ch:line.start||0}, {scroll:true});
} }
var markOpts = {className:'currentpc-span', inclusiveLeft:true}; var cls = blocked ? 'currentpc-span-blocked' : 'currentpc-span';
var markOpts = {className:cls, inclusiveLeft:true};
if (line.start || line.end) if (line.start || line.end)
this.markCurrentPC = this.editor.markText({line:line.line-1,ch:line.start}, {line:line.line-1,ch:line.end||line.start+1}, markOpts); this.markCurrentPC = this.editor.markText({line:line.line-1,ch:line.start}, {line:line.line-1,ch:line.end||line.start+1}, markOpts);
else else
@ -379,7 +382,7 @@ export class SourceEditor implements ProjectView {
refreshDebugState(moveCursor:boolean) { refreshDebugState(moveCursor:boolean) {
// TODO: only if line changed // TODO: only if line changed
// TODO: remove after compilation restarts platform // TODO: remove after compilation
this.clearCurrentLine(moveCursor); this.clearCurrentLine(moveCursor);
var line = this.getActiveLine(); var line = this.getActiveLine();
if (line) { if (line) {

View File

@ -125,14 +125,14 @@ class BASICPlatform implements Platform {
// TODO: only hot reload when we hit a label? // TODO: only hot reload when we hit a label?
var didExit = this.runtime.exited; var didExit = this.runtime.exited;
this.program = data; this.program = data;
this.runtime.load(data); var resumePC = this.runtime.load(data);
this.tty.uppercaseOnly = true; // this.program.opts.uppercaseOnly; //TODO? this.tty.uppercaseOnly = true; // this.program.opts.uppercaseOnly; //TODO?
// map editor to uppercase-only if need be // map editor to uppercase-only if need be
views.textMapFunctions.input = this.program.opts.uppercaseOnly ? (s) => s.toUpperCase() : null; views.textMapFunctions.input = this.program.opts.uppercaseOnly ? (s) => s.toUpperCase() : null;
// HP 2000 has cute lil small caps (TODO: messes up grid alignment tho) // HP 2000 has cute lil small caps (TODO: messes up grid alignment tho)
//this.tty.page.style.fontVariant = (this.program.opts.dialectName == 'HP2000') ? 'small-caps' : 'normal'; //this.tty.page.style.fontVariant = (this.program.opts.dialectName == 'HP2000') ? 'small-caps' : 'normal';
// only reset if we exited, or couldn't restart at label (PC reset to 0) // only reset if we exited, or couldn't restart at label (PC reset to 0)
if (!this.hotReload || didExit || this.runtime.curpc == 0) if (!this.hotReload || didExit || !resumePC)
this.reset(); this.reset();
} }
@ -156,7 +156,7 @@ class BASICPlatform implements Platform {
this.timer.start(); this.timer.start();
} }
isBlocked() { return this.tty.waitingfor != null; } // is blocked for input? isBlocked() { return this.tty.waitingfor != null || this.runtime.exited; } // is blocked for input?
isRunning() { return this.timer.isRunning(); } isRunning() { return this.timer.isRunning(); }
getDefaultExtension() { return ".bas"; } getDefaultExtension() { return ".bas"; }
getToolForFilename() { return "basic"; } getToolForFilename() { return "basic"; }