mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2025-02-16 17:30:27 +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 {
|
||||
color: #ff66ee;
|
||||
}
|
||||
.currentpc-span-blocked {
|
||||
background-color: #7e2a70;
|
||||
}
|
||||
.currentpc-marker-blocked {
|
||||
color: #ffee33;
|
||||
}
|
||||
.tooltipbox {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
@ -55,7 +55,8 @@
|
||||
"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-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",
|
||||
"keywords": [
|
||||
|
@ -1,3 +1,5 @@
|
||||
OPTION DIALECT DEC
|
||||
0 REM from the DEC EduSystem Handbook
|
||||
100 REM***23 MATCHES
|
||||
110 LET M=23
|
||||
115 PRINT
|
||||
|
@ -1,5 +1,5 @@
|
||||
OPTION DIALECT HP2000
|
||||
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
|
||||
20 REM 1) MAXIMUM BANK IS $1000
|
||||
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
|
||||
20 REM *** FOR EDUSYSTEM 70 BY DAVID AHL, DIGITAL
|
||||
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."
|
||||
00005 DIM B$(30)
|
||||
00010 F=0
|
||||
|
@ -1,8 +1,9 @@
|
||||
OPTION DIALECT DARTMOUTH
|
||||
10 PRINT "HELLO! LET'S PROGRAM IN BASIC."
|
||||
15 PRINT
|
||||
20 INPUT "WOULD YOU MIND TYPING IN YOUR NAME";A$
|
||||
25 PRINT
|
||||
30 PRINT "THANKS, ";A$;"! THIS WILL BE FUN!";CHR$(7)
|
||||
30 PRINT "THANKS, ";A$;"! THIS WILL BE FUN!"
|
||||
35 PRINT
|
||||
40 INPUT "NOW TELL ME YOUR FAVORITE NUMBER";N
|
||||
45 PRINT
|
||||
|
@ -1,4 +1,4 @@
|
||||
0 OPTION DIALECT HP
|
||||
OPTION DIALECT HP
|
||||
1 REM **** HP BASIC PROGRAM LIBRARY **************************
|
||||
2 REM
|
||||
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"
|
||||
20 PRINT "MINI-COMPUTOR 102"
|
||||
30 PRINT "SUBJECT: MORTGAGE PAYMENT"
|
||||
|
@ -1,5 +1,5 @@
|
||||
001 OPTION DIALECT MODERN
|
||||
002 OPTION CPUSPEED MAX
|
||||
OPTION DIALECT MODERN
|
||||
OPTION CPUSPEED MAX
|
||||
010 REM***A PRIME NUMBER SIEVE BENCHMARK
|
||||
020 T = TIMER
|
||||
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
|
||||
2 REM***http://www.dunnington.info/public/basicgames/
|
||||
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]
|
||||
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
|
||||
|
@ -1,6 +1,6 @@
|
||||
OPTION DIALECT HP
|
||||
1 REM from: http://www.dunnington.info/public/basicgames/WUMPUS.hp
|
||||
2 REM extracted from HP library tape
|
||||
5 OPTION DIALECT HP
|
||||
10 REM- HUNT THE WUMPUS
|
||||
20 PRINT "INSTRUCTIONS (Y-N)";
|
||||
30 INPUT I$
|
||||
|
@ -238,7 +238,6 @@ CodeMirror.defineMode("basic", function(conf, parserConf) {
|
||||
}
|
||||
|
||||
var external = {
|
||||
electricChars:"dDpPtTfFeE ",
|
||||
startState: function() {
|
||||
return {
|
||||
tokenize: tokenBase,
|
||||
@ -246,8 +245,6 @@ CodeMirror.defineMode("basic", function(conf, parserConf) {
|
||||
currentIndent: 0,
|
||||
nextLineIndent: 0,
|
||||
doInCurrentLine: false
|
||||
|
||||
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -11,6 +11,7 @@ export interface BASICOptions {
|
||||
squareBrackets : boolean; // "[" and "]" interchangable with "(" and ")"?
|
||||
tickComments : boolean; // support 'comments?
|
||||
hexOctalConsts : boolean; // support &H and &O integer constants?
|
||||
optionalLet : boolean; // LET is optional
|
||||
chainAssignments : boolean; // support A = B = value (HP2000)
|
||||
validKeywords : string[]; // valid keywords (or null for accept all)
|
||||
validFunctions : string[]; // valid functions (or null for accept all)
|
||||
@ -18,7 +19,7 @@ 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?
|
||||
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
|
||||
@ -390,15 +391,17 @@ export class BASICParser {
|
||||
let tok = this.consumeToken();
|
||||
switch (tok.type) {
|
||||
case TokenType.Ident:
|
||||
if (this.opts.optionalLabels) {
|
||||
if (this.peekToken().str == ':') { // is it a label:
|
||||
if (this.opts.optionalLabels || tok.str == 'OPTION') {
|
||||
// is it a "label :" and not a keyword like "PRINT : "
|
||||
if (this.peekToken().str == ':' && !this.supportsCommand(tok.str)) {
|
||||
this.consumeToken(); // eat the ":"
|
||||
// fall through to the next case
|
||||
} else {
|
||||
this.pushbackToken(tok); // nope
|
||||
break;
|
||||
}
|
||||
} else this.dialectError(`optional line numbers`);
|
||||
} else
|
||||
this.dialectError(`optional line numbers`);
|
||||
case TokenType.Int:
|
||||
this.setCurrentLabel(line, tok.str);
|
||||
break;
|
||||
@ -406,9 +409,16 @@ export class BASICParser {
|
||||
case TokenType.Float:
|
||||
this.compileError(`Line numbers must be positive integers.`);
|
||||
break;
|
||||
case TokenType.Operator:
|
||||
if (this.supportsCommand(tok.str) && this.validKeyword(tok.str)) {
|
||||
this.pushbackToken(tok);
|
||||
break; // "?" is allowed
|
||||
}
|
||||
default:
|
||||
if (this.opts.optionalLabels) this.compileError(`A line must start with a line number, command, or label.`);
|
||||
else this.compileError(`A line must start with a line number.`);
|
||||
if (this.opts.optionalLabels)
|
||||
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:
|
||||
break;
|
||||
}
|
||||
@ -525,9 +535,9 @@ export class BASICParser {
|
||||
validKeyword(keyword: string) : string {
|
||||
return (this.opts.validKeywords && this.opts.validKeywords.indexOf(keyword) < 0) ? null : keyword;
|
||||
}
|
||||
supportsKeyword(keyword: string) {
|
||||
if (this['stmt__' + keyword] != null) return true;
|
||||
return false;
|
||||
supportsCommand(cmd: string) : () => Statement {
|
||||
if (cmd == '?') return this.stmt__PRINT;
|
||||
else return this['stmt__' + cmd];
|
||||
}
|
||||
parseStatement(): Statement | null {
|
||||
// eat extra ":" (should have separate property for this)
|
||||
@ -555,13 +565,15 @@ export class BASICParser {
|
||||
cmd = 'GOSUB';
|
||||
}
|
||||
// lookup JS function for command
|
||||
var fn = this['stmt__' + cmd];
|
||||
var fn = this.supportsCommand(cmd);
|
||||
if (fn) {
|
||||
if (this.validKeyword(cmd) == null)
|
||||
this.dialectError(`the ${cmd} keyword`);
|
||||
stmt = fn.bind(this)() as Statement;
|
||||
this.dialectError(`the ${cmd} statement`);
|
||||
stmt = fn.bind(this)();
|
||||
break;
|
||||
} 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'
|
||||
this.pushbackToken(cmdtok);
|
||||
stmt = this.stmt__LET();
|
||||
@ -631,23 +643,22 @@ export class BASICParser {
|
||||
if (this.opts.computedGoto) {
|
||||
// parse expression, but still add to list of label targets if constant
|
||||
var expr = this.parseExpr();
|
||||
if ((expr as Literal).value != null) {
|
||||
this.targets[(expr as Literal).value] = this.lasttoken.$loc;
|
||||
}
|
||||
if (isLiteral(expr)) this.targets[expr.value] = this.lasttoken.$loc;
|
||||
return expr;
|
||||
}
|
||||
// parse a single number or ident label
|
||||
var tok = this.consumeToken();
|
||||
switch (tok.type) {
|
||||
case TokenType.Ident:
|
||||
if (!this.opts.optionalLabels) this.dialectError(`labels other than line numbers`)
|
||||
case TokenType.Int:
|
||||
var label = tok.str;
|
||||
this.targets[label] = tok.$loc;
|
||||
return {value:label};
|
||||
default:
|
||||
if (this.opts.optionalLabels) this.compileError(`There should be a line number or label here.`);
|
||||
else this.compileError(`There should be a line number here.`);
|
||||
} else {
|
||||
// parse a single number or ident label
|
||||
var tok = this.consumeToken();
|
||||
switch (tok.type) {
|
||||
case TokenType.Ident:
|
||||
if (!this.opts.optionalLabels) this.dialectError(`labels other than line numbers`)
|
||||
case TokenType.Int:
|
||||
var label = tok.str;
|
||||
this.targets[label] = tok.$loc;
|
||||
return {value:label};
|
||||
default:
|
||||
var what = this.opts.optionalLabels ? "label or line number" : "line number";
|
||||
this.compileError(`There should be a ${what} here.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
parseDatumList(): Literal[] {
|
||||
@ -841,13 +852,14 @@ export class BASICParser {
|
||||
__GO(cmd: "GOTO"|"GOSUB"): GOTO_Statement | GOSUB_Statement | ONGO_Statement {
|
||||
var expr = this.parseLabel();
|
||||
// GOTO (expr) OF (labels...)
|
||||
if (this.opts.computedGoto && this.peekToken().str == 'OF') {
|
||||
if (this.peekToken().str == this.validKeyword('OF')) {
|
||||
this.expectToken('OF');
|
||||
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 {
|
||||
var cond = this.parseExpr();
|
||||
@ -1100,7 +1112,8 @@ export class BASICParser {
|
||||
checkLabels() {
|
||||
for (let targ in this.targets) {
|
||||
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,
|
||||
endStmtRequired : true,
|
||||
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
|
||||
@ -1191,15 +1254,15 @@ export const TINY_BASIC : BASICOptions = {
|
||||
multipleNextVars : false,
|
||||
bitwiseLogic : false,
|
||||
checkOnGotoIndex : false,
|
||||
computedGoto : true, // TODO: is it really though?
|
||||
computedGoto : true,
|
||||
restoreWithLabel : false,
|
||||
squareBrackets : false,
|
||||
arraysContainChars : false,
|
||||
endStmtRequired : false,
|
||||
chainAssignments : false,
|
||||
optionalLet : false,
|
||||
}
|
||||
|
||||
|
||||
export const HP_TIMESHARED_BASIC : BASICOptions = {
|
||||
dialectName: "HP2000",
|
||||
asciiOnly : true,
|
||||
@ -1242,12 +1305,13 @@ export const HP_TIMESHARED_BASIC : BASICOptions = {
|
||||
multipleNextVars : false,
|
||||
bitwiseLogic : false,
|
||||
checkOnGotoIndex : false,
|
||||
computedGoto : true,
|
||||
computedGoto : true, // not really, but we do parse expressions for GOTO ... OF
|
||||
restoreWithLabel : true,
|
||||
squareBrackets : true,
|
||||
arraysContainChars : true,
|
||||
endStmtRequired : true,
|
||||
chainAssignments : true,
|
||||
optionalLet : true,
|
||||
// TODO: max line number, array index 9999
|
||||
}
|
||||
|
||||
@ -1298,6 +1362,7 @@ export const DEC_BASIC_11 : BASICOptions = {
|
||||
arraysContainChars : false,
|
||||
endStmtRequired : false,
|
||||
chainAssignments : false,
|
||||
optionalLet : true,
|
||||
// TODO: max line number 32767
|
||||
// TODO: \ separator, % int vars and constants, 'single' quoted
|
||||
// TODO: can't compare strings and numbers
|
||||
@ -1356,6 +1421,7 @@ export const DEC_BASIC_PLUS : BASICOptions = {
|
||||
arraysContainChars : false,
|
||||
endStmtRequired : false,
|
||||
chainAssignments : false, // TODO: can chain with "," not "="
|
||||
optionalLet : true,
|
||||
// TODO: max line number 32767
|
||||
// TODO: \ separator, % int vars and constants, 'single' quoted
|
||||
// TODO: can't compare strings and numbers
|
||||
@ -1408,6 +1474,7 @@ export const BASICODE : BASICOptions = {
|
||||
arraysContainChars : false,
|
||||
endStmtRequired : false,
|
||||
chainAssignments : false,
|
||||
optionalLet : true,
|
||||
}
|
||||
|
||||
export const ALTAIR_BASIC41 : BASICOptions = {
|
||||
@ -1464,6 +1531,7 @@ export const ALTAIR_BASIC41 : BASICOptions = {
|
||||
arraysContainChars : false,
|
||||
endStmtRequired : false,
|
||||
chainAssignments : false,
|
||||
optionalLet : true,
|
||||
}
|
||||
|
||||
export const APPLESOFT_BASIC : BASICOptions = {
|
||||
@ -1521,6 +1589,7 @@ export const APPLESOFT_BASIC : BASICOptions = {
|
||||
arraysContainChars : false,
|
||||
endStmtRequired : false,
|
||||
chainAssignments : false,
|
||||
optionalLet : true,
|
||||
}
|
||||
|
||||
export const BASIC80 : BASICOptions = {
|
||||
@ -1579,6 +1648,7 @@ export const BASIC80 : BASICOptions = {
|
||||
arraysContainChars : false,
|
||||
endStmtRequired : false,
|
||||
chainAssignments : false,
|
||||
optionalLet : true,
|
||||
}
|
||||
|
||||
export const MODERN_BASIC : BASICOptions = {
|
||||
@ -1594,7 +1664,7 @@ export const MODERN_BASIC : BASICOptions = {
|
||||
defaultArraySize : 0, // DIM required
|
||||
defaultValues : false,
|
||||
stringConcat : true,
|
||||
typeConvert : true,
|
||||
typeConvert : false,
|
||||
maxDimensions : 255,
|
||||
maxDefArgs : 255,
|
||||
maxStringLength : 2048, // TODO?
|
||||
@ -1611,12 +1681,13 @@ export const MODERN_BASIC : BASICOptions = {
|
||||
multipleNextVars : true,
|
||||
bitwiseLogic : true,
|
||||
checkOnGotoIndex : true,
|
||||
computedGoto : true,
|
||||
computedGoto : false,
|
||||
restoreWithLabel : true,
|
||||
squareBrackets : true,
|
||||
arraysContainChars : false,
|
||||
endStmtRequired : false,
|
||||
chainAssignments : false,
|
||||
chainAssignments : true,
|
||||
optionalLet : true,
|
||||
}
|
||||
|
||||
// TODO: integer vars
|
||||
@ -1624,7 +1695,9 @@ export const MODERN_BASIC : BASICOptions = {
|
||||
// TODO: excess INPUT ignored, error msg
|
||||
|
||||
export const DIALECTS = {
|
||||
"DEFAULT": ALTAIR_BASIC41,
|
||||
"DEFAULT": MODERN_BASIC,
|
||||
"DARTMOUTH": DARTMOUTH_4TH_EDITION,
|
||||
"DARTMOUTH4": DARTMOUTH_4TH_EDITION,
|
||||
"ALTAIR": ALTAIR_BASIC41,
|
||||
"ALTAIR4": 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 runtime = new BASICRuntime();
|
||||
|
||||
function getCurrentLabel() {
|
||||
var loc = runtime.getCurrentSourceLocation();
|
||||
return loc ? loc.label : "?";
|
||||
}
|
||||
|
||||
// parse args
|
||||
var filename = '/dev/stdin';
|
||||
var args = process.argv.slice(2);
|
||||
var force = false;
|
||||
for (var i=0; i<args.length; i++) {
|
||||
if (args[i] == '-v')
|
||||
runtime.trace = true;
|
||||
else if (args[i] == '-d')
|
||||
parser.opts = DIALECTS[args[++i]] || Error('no such dialect');
|
||||
else if (args[i] == '-f')
|
||||
force = true;
|
||||
else if (args[i] == '--dialects')
|
||||
dumpDialectInfo();
|
||||
else
|
||||
@ -45,13 +53,13 @@ try {
|
||||
console.log(`@@@ ${e}`);
|
||||
}
|
||||
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
|
||||
try {
|
||||
runtime.load(pgm);
|
||||
} catch (e) {
|
||||
console.log(`### ${e.message} (line ${runtime.getCurrentSourceLocation().label})`);
|
||||
console.log(`### ${e.message} (line ${getCurrentLabel()})`);
|
||||
process.exit(1);
|
||||
}
|
||||
runtime.reset();
|
||||
@ -86,7 +94,7 @@ runtime.resume = function() {
|
||||
process.exit(0);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`### ${e.message} (line ${runtime.getCurrentSourceLocation().label})`);
|
||||
console.log(`### ${e.message} (line ${getCurrentLabel()})`);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
@ -98,7 +106,7 @@ runtime.resume();
|
||||
function dumpDialectInfo() {
|
||||
var dialects = new Set<BASICOptions>();
|
||||
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) => {
|
||||
dialects.add(DIALECTS[dkey]);
|
||||
});
|
||||
@ -121,7 +129,7 @@ function dumpDialectInfo() {
|
||||
});
|
||||
dialects.forEach((dialect) => {
|
||||
ALL_KEYWORDS.forEach((keyword) => {
|
||||
if (parser.supportsKeyword(keyword)) {
|
||||
if (parser.supportsCommand(keyword)) {
|
||||
var has = dialect.validKeywords == null || dialect.validKeywords.indexOf(keyword) >= 0;
|
||||
keyword = '`'+keyword+'`'
|
||||
if (!array[keyword]) array[keyword] = [];
|
||||
|
@ -473,7 +473,7 @@ export class BASICRuntime {
|
||||
this.runtimeError(`I can't call a function here.`);
|
||||
// is it a subscript?
|
||||
if (expr.args) {
|
||||
// set array slice (HP BASIC)
|
||||
// TODO: set array slice (HP BASIC)
|
||||
if (this.opts.arraysContainChars && expr.name.endsWith('$')) {
|
||||
this.runtimeError(`I can't set array slices via this command yet.`);
|
||||
} else {
|
||||
@ -566,8 +566,8 @@ export class BASICRuntime {
|
||||
// converts a variable to string/number based on var name
|
||||
assign(name: string, right: number|string, isRead?:boolean) : number|string {
|
||||
// convert data? READ always converts if read into string
|
||||
if (this.opts.typeConvert || (isRead && name.endsWith("$")))
|
||||
return this.convert(name, right);
|
||||
if (isRead && name.endsWith("$"))
|
||||
return this.checkValue(this.convert(name, right), name);
|
||||
// TODO: use options
|
||||
if (name.endsWith("$")) {
|
||||
return this.convertToString(right, name);
|
||||
@ -604,9 +604,9 @@ export class BASICRuntime {
|
||||
if (this.opts.staticArrays) return;
|
||||
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.`);
|
||||
}
|
||||
var isstring = name.endsWith('$');
|
||||
// if numeric value, we use Float64Array which inits to 0
|
||||
var arrcons = isstring ? Array : Float64Array;
|
||||
@ -635,7 +635,7 @@ export class BASICRuntime {
|
||||
getArray(name: string, order: number) : [] {
|
||||
if (!this.arrays[name]) {
|
||||
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)
|
||||
this.dimArray(name, this.opts.defaultArraySize-1);
|
||||
else if (order == 2)
|
||||
@ -648,7 +648,7 @@ export class BASICRuntime {
|
||||
|
||||
arrayGet(name: string, ...indices: number[]) : basic.Value {
|
||||
var arr = this.getArray(name, indices.length);
|
||||
indices = indices.map(Math.round);
|
||||
indices = indices.map(this.ROUND.bind(this));
|
||||
var v = arr;
|
||||
for (var i=0; i<indices.length; i++) {
|
||||
var idx = indices[i];
|
||||
@ -668,10 +668,20 @@ export class BASICRuntime {
|
||||
// for HP BASIC string slicing (TODO?)
|
||||
modifyStringSlice(orig: string, add: string, start: number, end: number) : string {
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -707,7 +717,8 @@ export class BASICRuntime {
|
||||
var s = '';
|
||||
for (var arg of stmt.args) {
|
||||
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;
|
||||
}
|
||||
@ -751,7 +762,7 @@ export class BASICRuntime {
|
||||
if (this.opts.arraysContainChars && lexpr.args && lexpr.name.endsWith('$')) {
|
||||
s += `this.vars.${lexpr.name} = this.modifyStringSlice(this.vars.${lexpr.name}, _right, `
|
||||
s += lexpr.args.map((arg) => this.expr2js(arg)).join(', ');
|
||||
s += ')';
|
||||
s += ');';
|
||||
} else {
|
||||
var ljs = this.assign2js(lexpr);
|
||||
s += `${ljs} = this.assign(${JSON.stringify(lexpr.name)}, _right);`;
|
||||
@ -956,7 +967,7 @@ export class BASICRuntime {
|
||||
return exprname.endsWith("$") ? "" : 0;
|
||||
}
|
||||
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) {
|
||||
this.runtimeError(`I got an invalid value for ${exprname}: ${obj}`);
|
||||
} else {
|
||||
@ -1193,7 +1204,7 @@ export class BASICRuntime {
|
||||
len = this.ROUND(len);
|
||||
if (len <= 0) return '';
|
||||
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')
|
||||
return chr.substr(0,1).repeat(len);
|
||||
else
|
||||
|
@ -1901,7 +1901,8 @@ function globalErrorHandler(msgevent) {
|
||||
if (msg.indexOf("QuotaExceededError") >= 0) {
|
||||
requestPersistPermission(false, false);
|
||||
} else {
|
||||
var err = msgevent.error;
|
||||
var err = msgevent.error || msgevent.reason;
|
||||
msg = err.message || msg;
|
||||
showExceptionAsError(err, msg);
|
||||
}
|
||||
}
|
||||
@ -1909,10 +1910,12 @@ function globalErrorHandler(msgevent) {
|
||||
// catch errors
|
||||
function installErrorHandler() {
|
||||
window.addEventListener('error', globalErrorHandler);
|
||||
window.addEventListener('unhandledrejection', globalErrorHandler);
|
||||
}
|
||||
|
||||
function uninstallErrorHandler() {
|
||||
window.removeEventListener('error', globalErrorHandler);
|
||||
window.removeEventListener('unhandledrejection', globalErrorHandler);
|
||||
}
|
||||
|
||||
function gotoNewLocation(replaceHistory? : boolean) {
|
||||
|
@ -329,10 +329,12 @@ export class SourceEditor implements ProjectView {
|
||||
}
|
||||
|
||||
setCurrentLine(line:SourceLocation, moveCursor:boolean) {
|
||||
var blocked = platform.isBlocked && platform.isBlocked();
|
||||
|
||||
var addCurrentMarker = (line:SourceLocation) => {
|
||||
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"));
|
||||
this.editor.setGutterMarker(line.line-1, "gutter-info", div);
|
||||
}
|
||||
@ -343,7 +345,8 @@ export class SourceEditor implements ProjectView {
|
||||
if (moveCursor) {
|
||||
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)
|
||||
this.markCurrentPC = this.editor.markText({line:line.line-1,ch:line.start}, {line:line.line-1,ch:line.end||line.start+1}, markOpts);
|
||||
else
|
||||
@ -379,7 +382,7 @@ export class SourceEditor implements ProjectView {
|
||||
|
||||
refreshDebugState(moveCursor:boolean) {
|
||||
// TODO: only if line changed
|
||||
// TODO: remove after compilation restarts platform
|
||||
// TODO: remove after compilation
|
||||
this.clearCurrentLine(moveCursor);
|
||||
var line = this.getActiveLine();
|
||||
if (line) {
|
||||
|
@ -125,14 +125,14 @@ class BASICPlatform implements Platform {
|
||||
// TODO: only hot reload when we hit a label?
|
||||
var didExit = this.runtime.exited;
|
||||
this.program = data;
|
||||
this.runtime.load(data);
|
||||
var resumePC = this.runtime.load(data);
|
||||
this.tty.uppercaseOnly = true; // this.program.opts.uppercaseOnly; //TODO?
|
||||
// map editor to uppercase-only if need be
|
||||
views.textMapFunctions.input = this.program.opts.uppercaseOnly ? (s) => s.toUpperCase() : null;
|
||||
// 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';
|
||||
// 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();
|
||||
}
|
||||
|
||||
@ -156,7 +156,7 @@ class BASICPlatform implements Platform {
|
||||
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(); }
|
||||
getDefaultExtension() { return ".bas"; }
|
||||
getToolForFilename() { return "basic"; }
|
||||
|
Loading…
x
Reference in New Issue
Block a user