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:
parent
88fa924507
commit
9cedb1af08
|
@ -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;
|
||||||
|
|
|
@ -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": [
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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$
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -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
44
src/common/basic/fuzz.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 = [];
|
|
||||||
}
|
|
||||||
})
|
|
|
@ -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] = [];
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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"; }
|
||||||
|
|
Loading…
Reference in New Issue
Block a user