mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2025-02-18 00:30:43 +00:00
basic: HPBASIC, TINY, computedGoto, required END (18, 57, 111)
This commit is contained in:
parent
e96f7e8b49
commit
ff780236d3
@ -1,6 +1,8 @@
|
|||||||
005 OPTION CPUSPEED MAX
|
001 OPTION DIALECT MODERN
|
||||||
|
002 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
|
||||||
025 N = 8191
|
025 N = 8191
|
||||||
030 DIM F(N+1)
|
030 DIM F(N+1)
|
||||||
040 FOR I = 0 TO N : F(I)=1 : NEXT I
|
040 FOR I = 0 TO N : F(I)=1 : NEXT I
|
||||||
|
@ -7,7 +7,8 @@ export interface BASICOptions {
|
|||||||
uppercaseOnly : boolean; // convert everything to uppercase?
|
uppercaseOnly : boolean; // convert everything to uppercase?
|
||||||
optionalLabels : boolean; // can omit line numbers and use labels?
|
optionalLabels : boolean; // can omit line numbers and use labels?
|
||||||
optionalWhitespace : boolean; // can "crunch" keywords?
|
optionalWhitespace : boolean; // can "crunch" keywords?
|
||||||
varNaming : 'A1'|'AA'|'*'; // only allow A0-9 for numerics, single letter for arrays/strings
|
varNaming : 'A'|'A1'|'AA'|'*'; // only allow A0-9 for numerics, single letter for arrays/strings
|
||||||
|
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?
|
||||||
validKeywords : string[]; // valid keywords (or null for accept all)
|
validKeywords : string[]; // valid keywords (or null for accept all)
|
||||||
@ -27,6 +28,7 @@ export interface BASICOptions {
|
|||||||
defaultArrayBase : number; // arrays start at this number (0 or 1)
|
defaultArrayBase : number; // arrays start at this number (0 or 1)
|
||||||
defaultArraySize : number; // arrays are allocated w/ this size (starting @ 0)
|
defaultArraySize : number; // arrays are allocated w/ this size (starting @ 0)
|
||||||
maxDimensions : number; // max number of dimensions for arrays
|
maxDimensions : number; // max number of dimensions for arrays
|
||||||
|
arraysContainChars : boolean; // HP BASIC array-slicing syntax
|
||||||
// PRINTING
|
// PRINTING
|
||||||
printZoneLength : number; // print zone length
|
printZoneLength : number; // print zone length
|
||||||
numericPadding : boolean; // " " or "-" before and " " after numbers?
|
numericPadding : boolean; // " " or "-" before and " " after numbers?
|
||||||
@ -35,7 +37,9 @@ export interface BASICOptions {
|
|||||||
optionalNextVar : boolean; // can do NEXT without variable
|
optionalNextVar : boolean; // can do NEXT without variable
|
||||||
multipleNextVars : boolean; // NEXT J,I
|
multipleNextVars : boolean; // NEXT J,I
|
||||||
checkOnGotoIndex : boolean; // fatal error when ON..GOTO index out of bounds
|
checkOnGotoIndex : boolean; // fatal error when ON..GOTO index out of bounds
|
||||||
|
computedGoto : boolean; // non-const expr GOTO label (and GOTO..OF expression)
|
||||||
restoreWithLabel : boolean; // RESTORE <label>
|
restoreWithLabel : boolean; // RESTORE <label>
|
||||||
|
endStmtRequired : boolean; // need END at end?
|
||||||
// MISC
|
// MISC
|
||||||
commandsPerSec? : number; // how many commands per second?
|
commandsPerSec? : number; // how many commands per second?
|
||||||
}
|
}
|
||||||
@ -55,7 +59,7 @@ export class CompileError extends Error {
|
|||||||
|
|
||||||
// Lexer regular expression -- each (capture group) handles a different token type
|
// Lexer regular expression -- each (capture group) handles a different token type
|
||||||
|
|
||||||
const re_toks = /([0-9.]+[E][+-]?\d+)|(\d*[.]\d*[E0-9]*)|[0]*(\d+)|&([OH][0-9A-F]+)|(['].*)|(\w+[$]?)|(".*?")|([<>]?[=<>])|([-+*/^,;:()?\\])|(\S+)/gi;
|
const re_toks = /([0-9.]+[E][+-]?\d+)|(\d*[.]\d*[E0-9]*)|[0]*(\d+)|&([OH][0-9A-F]+)|(['].*)|(\w+[$]?)|(".*?")|([<>]?[=<>#])|(\*\*)|([-+*/^,;:()\[\]?\\])|(\S+)/gi;
|
||||||
|
|
||||||
export enum TokenType {
|
export enum TokenType {
|
||||||
EOL = 0,
|
EOL = 0,
|
||||||
@ -67,6 +71,7 @@ export enum TokenType {
|
|||||||
Ident,
|
Ident,
|
||||||
String,
|
String,
|
||||||
Relational,
|
Relational,
|
||||||
|
DoubleStar,
|
||||||
Operator,
|
Operator,
|
||||||
CatchAll,
|
CatchAll,
|
||||||
_LAST,
|
_LAST,
|
||||||
@ -130,7 +135,7 @@ export interface RETURN_Statement {
|
|||||||
command: "RETURN";
|
command: "RETURN";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ONGOTO_Statement {
|
export interface ONGO_Statement {
|
||||||
command: "ONGOTO" | "ONGOSUB";
|
command: "ONGOTO" | "ONGOSUB";
|
||||||
expr: Expr;
|
expr: Expr;
|
||||||
labels: Expr[];
|
labels: Expr[];
|
||||||
@ -211,7 +216,7 @@ export interface NoArgStatement {
|
|||||||
|
|
||||||
export type StatementTypes = PRINT_Statement | LET_Statement | GOTO_Statement | GOSUB_Statement
|
export type StatementTypes = PRINT_Statement | LET_Statement | GOTO_Statement | GOSUB_Statement
|
||||||
| IF_Statement | FOR_Statement | NEXT_Statement | DIM_Statement
|
| IF_Statement | FOR_Statement | NEXT_Statement | DIM_Statement
|
||||||
| INPUT_Statement | READ_Statement | DEF_Statement | ONGOTO_Statement
|
| INPUT_Statement | READ_Statement | DEF_Statement | ONGO_Statement
|
||||||
| DATA_Statement | OPTION_Statement | GET_Statement | RESTORE_Statement
|
| DATA_Statement | OPTION_Statement | GET_Statement | RESTORE_Statement
|
||||||
| NoArgStatement;
|
| NoArgStatement;
|
||||||
|
|
||||||
@ -242,18 +247,25 @@ const OPERATORS = {
|
|||||||
'||': {f:'lor',p:17}, // not used
|
'||': {f:'lor',p:17}, // not used
|
||||||
'&&': {f:'land',p:18}, // not used
|
'&&': {f:'land',p:18}, // not used
|
||||||
'=': {f:'eq',p:50},
|
'=': {f:'eq',p:50},
|
||||||
|
//'==': {f:'eq',p:50},
|
||||||
'<>': {f:'ne',p:50},
|
'<>': {f:'ne',p:50},
|
||||||
|
'><': {f:'ne',p:50},
|
||||||
|
//'!=': {f:'ne',p:50},
|
||||||
|
'#': {f:'ne',p:50},
|
||||||
'<': {f:'lt',p:50},
|
'<': {f:'lt',p:50},
|
||||||
'>': {f:'gt',p:50},
|
'>': {f:'gt',p:50},
|
||||||
'<=': {f:'le',p:50},
|
'<=': {f:'le',p:50},
|
||||||
'>=': {f:'ge',p:50},
|
'>=': {f:'ge',p:50},
|
||||||
|
'MIN': {f:'min',p:75},
|
||||||
|
'MAX': {f:'max',p:75},
|
||||||
'+': {f:'add',p:100},
|
'+': {f:'add',p:100},
|
||||||
'-': {f:'sub',p:100},
|
'-': {f:'sub',p:100},
|
||||||
'%': {f:'mod',p:140},
|
'%': {f:'mod',p:140},
|
||||||
'\\': {f:'idiv',p:150},
|
'\\': {f:'idiv',p:150},
|
||||||
'*': {f:'mul',p:200},
|
'*': {f:'mul',p:200},
|
||||||
'/': {f:'div',p:200},
|
'/': {f:'div',p:200},
|
||||||
'^': {f:'pow',p:300}
|
'^': {f:'pow',p:300},
|
||||||
|
'**': {f:'pow',p:300},
|
||||||
};
|
};
|
||||||
|
|
||||||
function getOperator(op: string) {
|
function getOperator(op: string) {
|
||||||
@ -263,6 +275,7 @@ function getOperator(op: string) {
|
|||||||
function getPrecedence(tok: Token): number {
|
function getPrecedence(tok: Token): number {
|
||||||
switch (tok.type) {
|
switch (tok.type) {
|
||||||
case TokenType.Operator:
|
case TokenType.Operator:
|
||||||
|
case TokenType.DoubleStar:
|
||||||
case TokenType.Relational:
|
case TokenType.Relational:
|
||||||
case TokenType.Ident:
|
case TokenType.Ident:
|
||||||
let op = getOperator(tok.str);
|
let op = getOperator(tok.str);
|
||||||
@ -285,12 +298,11 @@ function stripQuotes(s: string) {
|
|||||||
///// BASIC PARSER
|
///// BASIC PARSER
|
||||||
|
|
||||||
export class BASICParser {
|
export class BASICParser {
|
||||||
opts : BASICOptions = ALTAIR_BASIC41;
|
opts : BASICOptions = DIALECTS['DEFAULT'];
|
||||||
errors: WorkerError[];
|
errors: WorkerError[];
|
||||||
listings: CodeListingMap;
|
listings: CodeListingMap;
|
||||||
labels: { [label: string]: BASICLine };
|
labels: { [label: string]: BASICLine };
|
||||||
targets: { [targetlabel: string]: SourceLocation };
|
targets: { [targetlabel: string]: SourceLocation };
|
||||||
decls: { [name: string]: SourceLocation }; // declared/set vars
|
|
||||||
refs: { [name: string]: SourceLocation }; // references
|
refs: { [name: string]: SourceLocation }; // references
|
||||||
|
|
||||||
path : string;
|
path : string;
|
||||||
@ -307,7 +319,6 @@ export class BASICParser {
|
|||||||
this.lineno = 0;
|
this.lineno = 0;
|
||||||
this.curlabel = null;
|
this.curlabel = null;
|
||||||
this.listings = {};
|
this.listings = {};
|
||||||
this.decls = {};
|
|
||||||
this.refs = {};
|
this.refs = {};
|
||||||
}
|
}
|
||||||
compileError(msg: string, loc?: SourceLocation) {
|
compileError(msg: string, loc?: SourceLocation) {
|
||||||
@ -377,7 +388,8 @@ export class BASICParser {
|
|||||||
}
|
}
|
||||||
parseFile(file: string, path: string) : BASICProgram {
|
parseFile(file: string, path: string) : BASICProgram {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
var pgmlines = file.split("\n").map((line) => this.parseLine(line));
|
var txtlines = file.split("\n");
|
||||||
|
var pgmlines = txtlines.map((line) => this.parseLine(line));
|
||||||
var program = { opts: this.opts, lines: pgmlines };
|
var program = { opts: this.opts, lines: pgmlines };
|
||||||
this.checkAll(program);
|
this.checkAll(program);
|
||||||
this.listings[path] = this.generateListing(file, program);
|
this.listings[path] = this.generateListing(file, program);
|
||||||
@ -410,6 +422,12 @@ export class BASICParser {
|
|||||||
// uppercase all identifiers, and maybe more
|
// uppercase all identifiers, and maybe more
|
||||||
if (i == TokenType.Ident || i == TokenType.HexOctalInt || this.opts.uppercaseOnly)
|
if (i == TokenType.Ident || i == TokenType.HexOctalInt || this.opts.uppercaseOnly)
|
||||||
s = s.toUpperCase();
|
s = s.toUpperCase();
|
||||||
|
// convert brackets
|
||||||
|
if (s == '[' || s == ']') {
|
||||||
|
if (!this.opts.squareBrackets) this.dialectError(`square brackets`);
|
||||||
|
if (s == '[') s = '(';
|
||||||
|
if (s == ']') s = ')';
|
||||||
|
}
|
||||||
// un-crunch tokens? (TODO: still doesn't handle reserved words inside of variables)
|
// un-crunch tokens? (TODO: still doesn't handle reserved words inside of variables)
|
||||||
if (splitre) {
|
if (splitre) {
|
||||||
while (i == TokenType.Ident) {
|
while (i == TokenType.Ident) {
|
||||||
@ -467,7 +485,7 @@ export class BASICParser {
|
|||||||
if (cmd == this.validKeyword('?')) cmd = 'PRINT';
|
if (cmd == this.validKeyword('?')) cmd = 'PRINT';
|
||||||
case TokenType.Ident:
|
case TokenType.Ident:
|
||||||
// remark? ignore all tokens to eol
|
// remark? ignore all tokens to eol
|
||||||
if (cmd == 'REM') {
|
if (cmd.startsWith('REM')) {
|
||||||
while (this.consumeToken().type != TokenType.EOL) { }
|
while (this.consumeToken().type != TokenType.EOL) { }
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -540,9 +558,7 @@ export class BASICParser {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
parseLexprList(): IndOp[] {
|
parseLexprList(): IndOp[] {
|
||||||
var list = this.parseList(this.parseLexpr, ',');
|
return this.parseList(this.parseLexpr, ',');
|
||||||
list.forEach((lexpr) => this.decls[lexpr.name] = this.lasttoken.$loc);
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
parseExprList(): Expr[] {
|
parseExprList(): Expr[] {
|
||||||
return this.parseList(this.parseExpr, ',');
|
return this.parseList(this.parseExpr, ',');
|
||||||
@ -551,6 +567,10 @@ export class BASICParser {
|
|||||||
return this.parseList(this.parseLabel, ',');
|
return this.parseList(this.parseLabel, ',');
|
||||||
}
|
}
|
||||||
parseLabel() : Expr {
|
parseLabel() : Expr {
|
||||||
|
// parse full expr?
|
||||||
|
if (this.opts.computedGoto)
|
||||||
|
return this.parseExpr();
|
||||||
|
// 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:
|
||||||
@ -596,7 +616,7 @@ export class BASICParser {
|
|||||||
} else if (tok.str == '+') {
|
} else if (tok.str == '+') {
|
||||||
return this.parsePrimary(); // ignore unary +
|
return this.parsePrimary(); // ignore unary +
|
||||||
}
|
}
|
||||||
case TokenType.EOL:
|
default:
|
||||||
this.compileError(`The expression is incomplete.`);
|
this.compileError(`The expression is incomplete.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -635,6 +655,10 @@ export class BASICParser {
|
|||||||
}
|
}
|
||||||
validateVarName(lexpr: IndOp) {
|
validateVarName(lexpr: IndOp) {
|
||||||
switch (this.opts.varNaming) {
|
switch (this.opts.varNaming) {
|
||||||
|
case 'A': // TINY BASIC, no strings
|
||||||
|
if (!/^[A-Z]$/i.test(lexpr.name))
|
||||||
|
this.dialectError(`variable names other than a single letter`);
|
||||||
|
break;
|
||||||
case 'A1':
|
case 'A1':
|
||||||
if (lexpr.args == null && !/^[A-Z][0-9]?[$]?$/i.test(lexpr.name))
|
if (lexpr.args == null && !/^[A-Z][0-9]?[$]?$/i.test(lexpr.name))
|
||||||
this.dialectError(`variable names other than a letter followed by an optional digit`);
|
this.dialectError(`variable names other than a letter followed by an optional digit`);
|
||||||
@ -645,6 +669,8 @@ export class BASICParser {
|
|||||||
if (lexpr.args == null && !/^[A-Z][A-Z0-9]?[$]?$/i.test(lexpr.name))
|
if (lexpr.args == null && !/^[A-Z][A-Z0-9]?[$]?$/i.test(lexpr.name))
|
||||||
this.dialectError(`variable names other than a letter followed by an optional letter or digit`);
|
this.dialectError(`variable names other than a letter followed by an optional letter or digit`);
|
||||||
break;
|
break;
|
||||||
|
case '*':
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -653,7 +679,6 @@ export class BASICParser {
|
|||||||
stmt__LET(): LET_Statement {
|
stmt__LET(): LET_Statement {
|
||||||
var lexpr = this.parseLexpr();
|
var lexpr = this.parseLexpr();
|
||||||
this.expectToken("=");
|
this.expectToken("=");
|
||||||
this.decls[lexpr.name] = this.lasttoken.$loc;
|
|
||||||
var right = this.parseExpr();
|
var right = this.parseExpr();
|
||||||
return { command: "LET", lexpr: lexpr, right: right };
|
return { command: "LET", lexpr: lexpr, right: right };
|
||||||
}
|
}
|
||||||
@ -681,11 +706,22 @@ export class BASICParser {
|
|||||||
}
|
}
|
||||||
return { command: "PRINT", args: list };
|
return { command: "PRINT", args: list };
|
||||||
}
|
}
|
||||||
stmt__GOTO(): GOTO_Statement {
|
stmt__GOTO(): GOTO_Statement | GOSUB_Statement | ONGO_Statement {
|
||||||
return { command: "GOTO", label: this.parseLabel() };
|
return this.__GO("GOTO");
|
||||||
}
|
}
|
||||||
stmt__GOSUB(): GOSUB_Statement {
|
stmt__GOSUB(): GOTO_Statement | GOSUB_Statement | ONGO_Statement {
|
||||||
return { command: "GOSUB", label: this.parseLabel() };
|
return this.__GO("GOSUB");
|
||||||
|
}
|
||||||
|
__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') {
|
||||||
|
this.expectToken('OF');
|
||||||
|
let newcmd : 'ONGOTO'|'ONGOSUB' = (cmd == 'GOTO') ? 'ONGOTO' : 'ONGOSUB';
|
||||||
|
return { command:newcmd, expr:expr, labels:this.parseLabelList() };
|
||||||
|
}
|
||||||
|
// 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();
|
||||||
@ -766,6 +802,14 @@ export class BASICParser {
|
|||||||
}
|
}
|
||||||
return { command:'INPUT', prompt:{ value: promptstr }, args:this.parseLexprList() };
|
return { command:'INPUT', prompt:{ value: promptstr }, args:this.parseLexprList() };
|
||||||
}
|
}
|
||||||
|
/* for HP BASIC only */
|
||||||
|
stmt__ENTER() : INPUT_Statement {
|
||||||
|
var secs = this.parseExpr();
|
||||||
|
this.expectToken(',');
|
||||||
|
var result = this.parseLexpr(); // TODO: this has to go somewheres
|
||||||
|
this.expectToken(',');
|
||||||
|
return this.stmt__INPUT();
|
||||||
|
}
|
||||||
stmt__DATA() : DATA_Statement {
|
stmt__DATA() : DATA_Statement {
|
||||||
return { command:'DATA', datums:this.parseExprList() };
|
return { command:'DATA', datums:this.parseExprList() };
|
||||||
}
|
}
|
||||||
@ -787,7 +831,7 @@ export class BASICParser {
|
|||||||
stmt__END() {
|
stmt__END() {
|
||||||
return { command:'END' };
|
return { command:'END' };
|
||||||
}
|
}
|
||||||
stmt__ON() : ONGOTO_Statement {
|
stmt__ON() : ONGO_Statement {
|
||||||
var expr = this.parseExpr();
|
var expr = this.parseExpr();
|
||||||
var gotok = this.consumeToken();
|
var gotok = this.consumeToken();
|
||||||
var cmd = {GOTO:'ONGOTO', GOSUB:'ONGOSUB'}[gotok.str];
|
var cmd = {GOTO:'ONGOTO', GOSUB:'ONGOSUB'}[gotok.str];
|
||||||
@ -801,7 +845,6 @@ export class BASICParser {
|
|||||||
this.compileError(`There can be no more than ${this.opts.maxDefArgs} arguments to a function or subscript.`);
|
this.compileError(`There can be no more than ${this.opts.maxDefArgs} arguments to a function or subscript.`);
|
||||||
if (!lexpr.name.startsWith('FN')) this.compileError(`Functions defined with DEF must begin with the letters "FN".`)
|
if (!lexpr.name.startsWith('FN')) this.compileError(`Functions defined with DEF must begin with the letters "FN".`)
|
||||||
this.expectToken("=");
|
this.expectToken("=");
|
||||||
this.decls[lexpr.name] = this.lasttoken.$loc;
|
|
||||||
var func = this.parseExpr();
|
var func = this.parseExpr();
|
||||||
return { command:'DEF', lexpr:lexpr, def:func };
|
return { command:'DEF', lexpr:lexpr, def:func };
|
||||||
}
|
}
|
||||||
@ -810,7 +853,6 @@ export class BASICParser {
|
|||||||
}
|
}
|
||||||
stmt__GET() : GET_Statement {
|
stmt__GET() : GET_Statement {
|
||||||
var lexpr = this.parseLexpr();
|
var lexpr = this.parseLexpr();
|
||||||
this.decls[lexpr.name] = this.lasttoken.$loc;
|
|
||||||
return { command:'GET', lexpr:lexpr };
|
return { command:'GET', lexpr:lexpr };
|
||||||
}
|
}
|
||||||
stmt__CLEAR() : NoArgStatement {
|
stmt__CLEAR() : NoArgStatement {
|
||||||
@ -875,13 +917,17 @@ export class BASICParser {
|
|||||||
generateListing(file: string, program: BASICProgram) {
|
generateListing(file: string, program: BASICProgram) {
|
||||||
var srclines = [];
|
var srclines = [];
|
||||||
var pc = 0;
|
var pc = 0;
|
||||||
|
var laststmt : Statement;
|
||||||
program.lines.forEach((line, idx) => {
|
program.lines.forEach((line, idx) => {
|
||||||
line.stmts.forEach((stmt) => {
|
line.stmts.forEach((stmt) => {
|
||||||
|
laststmt = stmt;
|
||||||
var sl = stmt.$loc;
|
var sl = stmt.$loc;
|
||||||
sl.offset = pc++; // TODO: could Statement have offset field?
|
sl.offset = pc++; // TODO: could Statement have offset field?
|
||||||
srclines.push(sl);
|
srclines.push(sl);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
if (this.opts.endStmtRequired && (laststmt == null || laststmt.command != 'END'))
|
||||||
|
this.dialectError(`programs without an final END statement`);
|
||||||
return { lines: srclines };
|
return { lines: srclines };
|
||||||
}
|
}
|
||||||
getListings() : CodeListingMap {
|
getListings() : CodeListingMap {
|
||||||
@ -891,7 +937,6 @@ export class BASICParser {
|
|||||||
// LINT STUFF
|
// LINT STUFF
|
||||||
checkAll(program : BASICProgram) {
|
checkAll(program : BASICProgram) {
|
||||||
this.checkLabels();
|
this.checkLabels();
|
||||||
//this.checkUnsetVars();
|
|
||||||
}
|
}
|
||||||
checkLabels() {
|
checkLabels() {
|
||||||
for (let targ in this.targets) {
|
for (let targ in this.targets) {
|
||||||
@ -900,17 +945,10 @@ export class BASICParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checkUnsetVars() {
|
|
||||||
for (var ref in this.refs) {
|
|
||||||
if (this.decls[ref] == null)
|
|
||||||
this.compileError(`The variable "${ref}" was used but not set with a LET, DIM, READ, or INPUT statement.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///// BASIC DIALECTS
|
///// BASIC DIALECTS
|
||||||
|
|
||||||
// TODO: require END statement
|
|
||||||
export const ECMA55_MINIMAL : BASICOptions = {
|
export const ECMA55_MINIMAL : BASICOptions = {
|
||||||
dialectName: "ECMA55",
|
dialectName: "ECMA55",
|
||||||
asciiOnly : true,
|
asciiOnly : true,
|
||||||
@ -949,7 +987,105 @@ export const ECMA55_MINIMAL : BASICOptions = {
|
|||||||
multipleNextVars : false,
|
multipleNextVars : false,
|
||||||
bitwiseLogic : false,
|
bitwiseLogic : false,
|
||||||
checkOnGotoIndex : true,
|
checkOnGotoIndex : true,
|
||||||
|
computedGoto : false,
|
||||||
restoreWithLabel : false,
|
restoreWithLabel : false,
|
||||||
|
squareBrackets : false,
|
||||||
|
arraysContainChars : false,
|
||||||
|
endStmtRequired : true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: only integers supported
|
||||||
|
export const TINY_BASIC : BASICOptions = {
|
||||||
|
dialectName: "TINY",
|
||||||
|
asciiOnly : true,
|
||||||
|
uppercaseOnly : true,
|
||||||
|
optionalLabels : false,
|
||||||
|
optionalWhitespace : false,
|
||||||
|
varNaming : "A",
|
||||||
|
staticArrays : false,
|
||||||
|
sharedArrayNamespace : true,
|
||||||
|
defaultArrayBase : 0,
|
||||||
|
defaultArraySize : 0,
|
||||||
|
defaultValues : true,
|
||||||
|
stringConcat : false,
|
||||||
|
typeConvert : false,
|
||||||
|
maxDimensions : 0,
|
||||||
|
maxDefArgs : 255,
|
||||||
|
maxStringLength : 255,
|
||||||
|
tickComments : false,
|
||||||
|
hexOctalConsts : false,
|
||||||
|
validKeywords : [
|
||||||
|
'OPTION',
|
||||||
|
'PRINT','IF','THEN','GOTO','INPUT','LET','GOSUB','RETURN','CLEAR','END'
|
||||||
|
],
|
||||||
|
validFunctions : [
|
||||||
|
],
|
||||||
|
validOperators : [
|
||||||
|
'=', '<>', '><', '<', '>', '<=', '>=', '+', '-', '*', '/',
|
||||||
|
],
|
||||||
|
printZoneLength : 1,
|
||||||
|
numericPadding : false,
|
||||||
|
checkOverflow : false,
|
||||||
|
testInitialFor : false,
|
||||||
|
optionalNextVar : false,
|
||||||
|
multipleNextVars : false,
|
||||||
|
bitwiseLogic : false,
|
||||||
|
checkOnGotoIndex : false,
|
||||||
|
computedGoto : true,
|
||||||
|
restoreWithLabel : false,
|
||||||
|
squareBrackets : false,
|
||||||
|
arraysContainChars : false,
|
||||||
|
endStmtRequired : false,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const HP_TIMESHARED_BASIC : BASICOptions = {
|
||||||
|
dialectName: "HPTSB",
|
||||||
|
asciiOnly : true,
|
||||||
|
uppercaseOnly : false,
|
||||||
|
optionalLabels : false,
|
||||||
|
optionalWhitespace : false,
|
||||||
|
varNaming : "A1",
|
||||||
|
staticArrays : true,
|
||||||
|
sharedArrayNamespace : false,
|
||||||
|
defaultArrayBase : 1,
|
||||||
|
defaultArraySize : 11,
|
||||||
|
defaultValues : false,
|
||||||
|
stringConcat : false,
|
||||||
|
typeConvert : false,
|
||||||
|
maxDimensions : 2,
|
||||||
|
maxDefArgs : 255,
|
||||||
|
maxStringLength : 255,
|
||||||
|
tickComments : false, // TODO: HP BASIC has 'hh char constants
|
||||||
|
hexOctalConsts : false,
|
||||||
|
validKeywords : [
|
||||||
|
'BASE','DATA','DEF','DIM','END',
|
||||||
|
'FOR','GO','GOSUB','GOTO','IF','INPUT','LET','NEXT','OPTION','PRINT',
|
||||||
|
'RANDOMIZE','READ','REM','RESTORE','RETURN','STEP','STOP','SUB','THEN','TO',
|
||||||
|
'ENTER','MAT','CONVERT','OF','IMAGE','USING'
|
||||||
|
],
|
||||||
|
validFunctions : [
|
||||||
|
'ABS','ATN','BRK','COS','CTL','EXP','INT','LEN','LIN','LOG','NUM',
|
||||||
|
'POS','RND','SGN','SIN','SPA','SQR','TAB','TAN','TIM','TYP','UPS$' // TODO: POS,
|
||||||
|
],
|
||||||
|
validOperators : [
|
||||||
|
'=', '<>', '<', '>', '<=', '>=', '+', '-', '*', '/', '^',
|
||||||
|
'**', '#', 'NOT', 'AND', 'OR', 'MIN', 'MAX',
|
||||||
|
],
|
||||||
|
printZoneLength : 15,
|
||||||
|
numericPadding : true,
|
||||||
|
checkOverflow : false,
|
||||||
|
testInitialFor : true,
|
||||||
|
optionalNextVar : false,
|
||||||
|
multipleNextVars : false,
|
||||||
|
bitwiseLogic : false,
|
||||||
|
checkOnGotoIndex : false,
|
||||||
|
computedGoto : true,
|
||||||
|
restoreWithLabel : true,
|
||||||
|
squareBrackets : true,
|
||||||
|
arraysContainChars : true,
|
||||||
|
endStmtRequired : true,
|
||||||
|
// TODO: max line number, array index 9999
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BASICODE : BASICOptions = {
|
export const BASICODE : BASICOptions = {
|
||||||
@ -991,7 +1127,11 @@ export const BASICODE : BASICOptions = {
|
|||||||
multipleNextVars : false,
|
multipleNextVars : false,
|
||||||
bitwiseLogic : false,
|
bitwiseLogic : false,
|
||||||
checkOnGotoIndex : true,
|
checkOnGotoIndex : true,
|
||||||
|
computedGoto : false,
|
||||||
restoreWithLabel : false,
|
restoreWithLabel : false,
|
||||||
|
squareBrackets : false,
|
||||||
|
arraysContainChars : false,
|
||||||
|
endStmtRequired : false,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ALTAIR_BASIC41 : BASICOptions = {
|
export const ALTAIR_BASIC41 : BASICOptions = {
|
||||||
@ -1022,8 +1162,17 @@ export const ALTAIR_BASIC41 : BASICOptions = {
|
|||||||
'TROFF','TRON','WAIT',
|
'TROFF','TRON','WAIT',
|
||||||
'TO','STEP',
|
'TO','STEP',
|
||||||
],
|
],
|
||||||
validFunctions : null, // all
|
validFunctions : [
|
||||||
validOperators : null, // all
|
'ABS','ASC','ATN','CDBL','CHR$','CINT','COS','ERL','ERR',
|
||||||
|
'EXP','FIX','FRE','HEX$','INP','INSTR','INT',
|
||||||
|
'LEFT$','LEN','LOG','LPOS','MID$',
|
||||||
|
'OCT$','POS','RIGHT$','RND','SGN','SIN','SPACE$','SPC',
|
||||||
|
'SQR','STR$','STRING$','TAB','TAN','USR','VAL','VARPTR'
|
||||||
|
],
|
||||||
|
validOperators : [
|
||||||
|
'=', '<>', '<', '>', '<=', '>=', '+', '-', '*', '/', '^', 'AND', 'NOT', 'OR',
|
||||||
|
'XOR', 'IMP', 'EQV', '\\', 'MOD'
|
||||||
|
],
|
||||||
printZoneLength : 15,
|
printZoneLength : 15,
|
||||||
numericPadding : true,
|
numericPadding : true,
|
||||||
checkOverflow : true,
|
checkOverflow : true,
|
||||||
@ -1032,7 +1181,11 @@ export const ALTAIR_BASIC41 : BASICOptions = {
|
|||||||
multipleNextVars : true,
|
multipleNextVars : true,
|
||||||
bitwiseLogic : true,
|
bitwiseLogic : true,
|
||||||
checkOnGotoIndex : false,
|
checkOnGotoIndex : false,
|
||||||
|
computedGoto : false,
|
||||||
restoreWithLabel : false,
|
restoreWithLabel : false,
|
||||||
|
squareBrackets : false,
|
||||||
|
arraysContainChars : false,
|
||||||
|
endStmtRequired : false,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const APPLESOFT_BASIC : BASICOptions = {
|
export const APPLESOFT_BASIC : BASICOptions = {
|
||||||
@ -1082,7 +1235,11 @@ export const APPLESOFT_BASIC : BASICOptions = {
|
|||||||
multipleNextVars : true,
|
multipleNextVars : true,
|
||||||
bitwiseLogic : false,
|
bitwiseLogic : false,
|
||||||
checkOnGotoIndex : false,
|
checkOnGotoIndex : false,
|
||||||
|
computedGoto : false,
|
||||||
restoreWithLabel : false,
|
restoreWithLabel : false,
|
||||||
|
squareBrackets : false,
|
||||||
|
arraysContainChars : false,
|
||||||
|
endStmtRequired : false,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BASIC80 : BASICOptions = {
|
export const BASIC80 : BASICOptions = {
|
||||||
@ -1131,7 +1288,11 @@ export const BASIC80 : BASICOptions = {
|
|||||||
multipleNextVars : true,
|
multipleNextVars : true,
|
||||||
bitwiseLogic : true,
|
bitwiseLogic : true,
|
||||||
checkOnGotoIndex : false,
|
checkOnGotoIndex : false,
|
||||||
|
computedGoto : false,
|
||||||
restoreWithLabel : true,
|
restoreWithLabel : true,
|
||||||
|
squareBrackets : false,
|
||||||
|
arraysContainChars : false,
|
||||||
|
endStmtRequired : false,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MODERN_BASIC : BASICOptions = {
|
export const MODERN_BASIC : BASICOptions = {
|
||||||
@ -1164,7 +1325,11 @@ export const MODERN_BASIC : BASICOptions = {
|
|||||||
multipleNextVars : true,
|
multipleNextVars : true,
|
||||||
bitwiseLogic : true,
|
bitwiseLogic : true,
|
||||||
checkOnGotoIndex : false,
|
checkOnGotoIndex : false,
|
||||||
|
computedGoto : false,
|
||||||
restoreWithLabel : true,
|
restoreWithLabel : true,
|
||||||
|
squareBrackets : true,
|
||||||
|
arraysContainChars : false,
|
||||||
|
endStmtRequired : false,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: integer vars
|
// TODO: integer vars
|
||||||
@ -1173,11 +1338,18 @@ export const MODERN_BASIC : BASICOptions = {
|
|||||||
export const DIALECTS = {
|
export const DIALECTS = {
|
||||||
"DEFAULT": ALTAIR_BASIC41,
|
"DEFAULT": ALTAIR_BASIC41,
|
||||||
"ALTAIR": ALTAIR_BASIC41,
|
"ALTAIR": ALTAIR_BASIC41,
|
||||||
|
"ALTAIR4": ALTAIR_BASIC41,
|
||||||
"ALTAIR41": ALTAIR_BASIC41,
|
"ALTAIR41": ALTAIR_BASIC41,
|
||||||
"ECMA55": ECMA55_MINIMAL,
|
"ECMA55": ECMA55_MINIMAL,
|
||||||
"MINIMAL": ECMA55_MINIMAL,
|
"MINIMAL": ECMA55_MINIMAL,
|
||||||
|
"HPTSB": HP_TIMESHARED_BASIC,
|
||||||
|
"HPB": HP_TIMESHARED_BASIC,
|
||||||
|
"HP2000": HP_TIMESHARED_BASIC,
|
||||||
|
"HPBASIC": HP_TIMESHARED_BASIC,
|
||||||
|
"HPACCESS": HP_TIMESHARED_BASIC,
|
||||||
"BASICODE": BASICODE,
|
"BASICODE": BASICODE,
|
||||||
"APPLESOFT": APPLESOFT_BASIC,
|
"APPLESOFT": APPLESOFT_BASIC,
|
||||||
"BASIC80": BASIC80,
|
"BASIC80": BASIC80,
|
||||||
"MODERN": MODERN_BASIC,
|
"MODERN": MODERN_BASIC,
|
||||||
|
"TINY": TINY_BASIC,
|
||||||
};
|
};
|
||||||
|
@ -102,7 +102,7 @@ export class BASICRuntime {
|
|||||||
this.pc2line = new Map();
|
this.pc2line = new Map();
|
||||||
this.datums = [];
|
this.datums = [];
|
||||||
this.builtins = this.getBuiltinFunctions();
|
this.builtins = this.getBuiltinFunctions();
|
||||||
// TODO: lines start @ 1?
|
// TODO: detect undeclared vars
|
||||||
program.lines.forEach((line, idx) => {
|
program.lines.forEach((line, idx) => {
|
||||||
// make lookup tables
|
// make lookup tables
|
||||||
if (line.label != null) this.label2lineidx[line.label] = idx;
|
if (line.label != null) this.label2lineidx[line.label] = idx;
|
||||||
@ -120,7 +120,7 @@ export class BASICRuntime {
|
|||||||
// parse DATA literals
|
// parse DATA literals
|
||||||
this.allstmts.filter((stmt) => stmt.command == 'DATA').forEach((datastmt) => {
|
this.allstmts.filter((stmt) => stmt.command == 'DATA').forEach((datastmt) => {
|
||||||
(datastmt as basic.DATA_Statement).datums.forEach(d => {
|
(datastmt as basic.DATA_Statement).datums.forEach(d => {
|
||||||
//this.curpc = datastmt.$loc.offset; // for error reporting
|
this.curpc = datastmt.$loc.offset; // for error reporting
|
||||||
var functext = this.expr2js(d, {isconst:true});
|
var functext = this.expr2js(d, {isconst:true});
|
||||||
var value = new Function(`return ${functext};`).bind(this)();
|
var value = new Function(`return ${functext};`).bind(this)();
|
||||||
this.datums.push({value:value});
|
this.datums.push({value:value});
|
||||||
@ -338,15 +338,17 @@ export class BASICRuntime {
|
|||||||
var str;
|
var str;
|
||||||
if (typeof obj === 'number') {
|
if (typeof obj === 'number') {
|
||||||
var numstr = obj.toString().toUpperCase();
|
var numstr = obj.toString().toUpperCase();
|
||||||
var numlen = this.opts.printZoneLength - 4;
|
if (this.opts.printZoneLength > 4) {
|
||||||
var prec = numlen;
|
var numlen = this.opts.printZoneLength - 4;
|
||||||
while (numstr.length > numlen) {
|
var prec = numlen;
|
||||||
numstr = obj.toPrecision(prec--);
|
while (numstr.length > numlen) {
|
||||||
|
numstr = obj.toPrecision(prec--);
|
||||||
|
}
|
||||||
|
if (numstr.startsWith('0.'))
|
||||||
|
numstr = numstr.substr(1);
|
||||||
|
else if (numstr.startsWith('-0.'))
|
||||||
|
numstr = '-'+numstr.substr(2);
|
||||||
}
|
}
|
||||||
if (numstr.startsWith('0.'))
|
|
||||||
numstr = numstr.substr(1);
|
|
||||||
else if (numstr.startsWith('-0.'))
|
|
||||||
numstr = '-'+numstr.substr(2);
|
|
||||||
if (!this.opts.numericPadding)
|
if (!this.opts.numericPadding)
|
||||||
str = numstr;
|
str = numstr;
|
||||||
else if (numstr.startsWith('-'))
|
else if (numstr.startsWith('-'))
|
||||||
@ -406,7 +408,10 @@ export class BASICRuntime {
|
|||||||
this.checkFuncArgs(expr, this.builtins[expr.name]);
|
this.checkFuncArgs(expr, this.builtins[expr.name]);
|
||||||
s += `this.builtins.${expr.name}(${jsargs})`;
|
s += `this.builtins.${expr.name}(${jsargs})`;
|
||||||
} else if (expr.args) {
|
} else if (expr.args) {
|
||||||
s += `this.arrayGet(${qname}, ${jsargs})`;
|
if (this.opts.arraysContainChars && expr.name.endsWith('$'))
|
||||||
|
s += `this.MID$(this.vars.${expr.name}, ${jsargs})`;
|
||||||
|
else
|
||||||
|
s += `this.arrayGet(${qname}, ${jsargs})`;
|
||||||
} else { // just a variable
|
} else { // just a variable
|
||||||
s += `this.vars.${expr.name}`;
|
s += `this.vars.${expr.name}`;
|
||||||
}
|
}
|
||||||
@ -593,25 +598,29 @@ export class BASICRuntime {
|
|||||||
return (v as any) as basic.Value;
|
return (v as any) as basic.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for HP BASIC string slicing
|
||||||
|
modifyStringSlice(orig: string, add: string, start: number, end: number) : string {
|
||||||
|
return orig.substr(0, start-1) + add + orig.substr(end);
|
||||||
|
}
|
||||||
|
|
||||||
checkOnGoto(value: number, labels: string[]) {
|
checkOnGoto(value: number, labels: string[]) {
|
||||||
value = this.ROUND(value);
|
value = this.ROUND(value);
|
||||||
if (value < 0) // > 255 ?
|
if (value < 0) // > 255 ?
|
||||||
this.runtimeError(`I needed a number between 1 and ${labels.length}, but I got ${value}.`);
|
this.runtimeError(`I needed a number between 1 and ${labels.length}, but I got ${value}.`);
|
||||||
if (this.opts.checkOnGotoIndex && (value < 1 || value > labels.length))
|
if (this.opts.checkOnGotoIndex && (value < 1 || value > labels.length))
|
||||||
this.runtimeError(`I needed a number between 1 and ${labels.length}, but I got ${value}.`);
|
this.runtimeError(`I needed a number between 1 and ${labels.length}, but I got ${value}.`);
|
||||||
else
|
if (value < 1 || value > labels.length)
|
||||||
value = Math.min(Math.max(1, value), labels.length);
|
return 0;
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
onGotoLabel(value: number, ...labels: string[]) {
|
onGotoLabel(value: number, ...labels: string[]) {
|
||||||
value = this.checkOnGoto(value, labels);
|
value = this.checkOnGoto(value, labels);
|
||||||
this.gotoLabel(labels[value-1]);
|
if (value) this.gotoLabel(labels[value-1]);
|
||||||
|
|
||||||
}
|
}
|
||||||
onGosubLabel(value: number, ...labels: string[]) {
|
onGosubLabel(value: number, ...labels: string[]) {
|
||||||
value = this.checkOnGoto(value, labels);
|
value = this.checkOnGoto(value, labels);
|
||||||
this.gosubLabel(labels[value-1]);
|
if (value) this.gosubLabel(labels[value-1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
nextDatum() : basic.Value {
|
nextDatum() : basic.Value {
|
||||||
@ -655,7 +664,16 @@ export class BASICRuntime {
|
|||||||
do__LET(stmt : basic.LET_Statement) {
|
do__LET(stmt : basic.LET_Statement) {
|
||||||
var lexpr = this.assign2js(stmt.lexpr);
|
var lexpr = this.assign2js(stmt.lexpr);
|
||||||
var right = this.expr2js(stmt.right);
|
var right = this.expr2js(stmt.right);
|
||||||
return `${lexpr} = this.assign(${JSON.stringify(stmt.lexpr.name)}, ${right});`;
|
// HP BASIC string-slice syntax?
|
||||||
|
if (this.opts.arraysContainChars && stmt.lexpr.args && stmt.lexpr.name.endsWith('$')) {
|
||||||
|
var s = `this.vars.${stmt.lexpr.name} = this.modifyStringSlice(this.vars.${stmt.lexpr.name}, ${right}, `
|
||||||
|
s += stmt.lexpr.args.map((arg) => this.expr2js(arg)).join(', ');
|
||||||
|
s += ')';
|
||||||
|
console.log(s);
|
||||||
|
return s;
|
||||||
|
} else {
|
||||||
|
return `${lexpr} = this.assign(${JSON.stringify(stmt.lexpr.name)}, ${right});`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
do__FOR(stmt : basic.FOR_Statement) {
|
do__FOR(stmt : basic.FOR_Statement) {
|
||||||
@ -704,6 +722,9 @@ export class BASICRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_DIM(dim : basic.IndOp) {
|
_DIM(dim : basic.IndOp) {
|
||||||
|
// HP BASIC doesn't really have string arrays
|
||||||
|
if (this.opts.arraysContainChars && dim.name.endsWith('$'))
|
||||||
|
return;
|
||||||
var argsstr = '';
|
var argsstr = '';
|
||||||
for (var arg of dim.args) {
|
for (var arg of dim.args) {
|
||||||
// TODO: check for float (or at compile time)
|
// TODO: check for float (or at compile time)
|
||||||
@ -720,12 +741,12 @@ export class BASICRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
do__GOTO(stmt : basic.GOTO_Statement) {
|
do__GOTO(stmt : basic.GOTO_Statement) {
|
||||||
var label = this.expr2js(stmt.label, {isconst:true});
|
var label = this.expr2js(stmt.label);
|
||||||
return `this.gotoLabel(${label})`;
|
return `this.gotoLabel(${label})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
do__GOSUB(stmt : basic.GOSUB_Statement) {
|
do__GOSUB(stmt : basic.GOSUB_Statement) {
|
||||||
var label = this.expr2js(stmt.label, {isconst:true});
|
var label = this.expr2js(stmt.label);
|
||||||
return `this.gosubLabel(${label})`;
|
return `this.gosubLabel(${label})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -733,7 +754,7 @@ export class BASICRuntime {
|
|||||||
return `this.returnFromGosub()`;
|
return `this.returnFromGosub()`;
|
||||||
}
|
}
|
||||||
|
|
||||||
do__ONGOTO(stmt : basic.ONGOTO_Statement) {
|
do__ONGOTO(stmt : basic.ONGO_Statement) {
|
||||||
var expr = this.expr2js(stmt.expr);
|
var expr = this.expr2js(stmt.expr);
|
||||||
var labels = stmt.labels.map((arg) => this.expr2js(arg, {isconst:true})).join(', ');
|
var labels = stmt.labels.map((arg) => this.expr2js(arg, {isconst:true})).join(', ');
|
||||||
if (stmt.command == 'ONGOTO')
|
if (stmt.command == 'ONGOTO')
|
||||||
@ -925,6 +946,12 @@ export class BASICRuntime {
|
|||||||
ge(a:number, b:number) : number {
|
ge(a:number, b:number) : number {
|
||||||
return a >= b ? (this.opts.bitwiseLogic ? -1 : 1) : 0;
|
return a >= b ? (this.opts.bitwiseLogic ? -1 : 1) : 0;
|
||||||
}
|
}
|
||||||
|
min(a:number, b:number) : number {
|
||||||
|
return a < b ? a : b;
|
||||||
|
}
|
||||||
|
max(a:number, b:number) : number {
|
||||||
|
return a > b ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
// FUNCTIONS (uppercase)
|
// FUNCTIONS (uppercase)
|
||||||
|
|
||||||
@ -949,6 +976,9 @@ export class BASICRuntime {
|
|||||||
COT(arg : number) : number {
|
COT(arg : number) : number {
|
||||||
return this.checkNum(1.0 / Math.tan(arg)); // 4th edition only
|
return this.checkNum(1.0 / Math.tan(arg)); // 4th edition only
|
||||||
}
|
}
|
||||||
|
CTL(arg : number) : string {
|
||||||
|
return this.CHR$(arg);
|
||||||
|
}
|
||||||
EXP(arg : number) : number {
|
EXP(arg : number) : number {
|
||||||
return this.checkNum(Math.exp(arg));
|
return this.checkNum(Math.exp(arg));
|
||||||
}
|
}
|
||||||
@ -972,7 +1002,10 @@ export class BASICRuntime {
|
|||||||
return arg.substr(0, count);
|
return arg.substr(0, count);
|
||||||
}
|
}
|
||||||
LEN(arg : string) : number {
|
LEN(arg : string) : number {
|
||||||
return arg.length;
|
return this.checkString(arg).length;
|
||||||
|
}
|
||||||
|
LIN(arg : number) : string {
|
||||||
|
return this.STRING$(arg, '\n');
|
||||||
}
|
}
|
||||||
LOG(arg : number) : number {
|
LOG(arg : number) : number {
|
||||||
if (arg == 0) this.runtimeError(`I can't take the logarithm of zero (${arg}).`)
|
if (arg == 0) this.runtimeError(`I can't take the logarithm of zero (${arg}).`)
|
||||||
@ -1034,9 +1067,30 @@ export class BASICRuntime {
|
|||||||
TAN(arg : number) : number {
|
TAN(arg : number) : number {
|
||||||
return this.checkNum(Math.tan(arg));
|
return this.checkNum(Math.tan(arg));
|
||||||
}
|
}
|
||||||
|
TIM(arg : number) { // only HP BASIC?
|
||||||
|
var d = new Date();
|
||||||
|
switch (this.ROUND(arg)) {
|
||||||
|
case 0: return d.getMinutes();
|
||||||
|
case 1: return d.getHours();
|
||||||
|
case 2:
|
||||||
|
var dayCount = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
|
||||||
|
var mn = d.getMonth();
|
||||||
|
var dn = d.getDate();
|
||||||
|
var dayOfYear = dayCount[mn] + dn;
|
||||||
|
var isLeapYear = (d.getFullYear() & 3) == 0; // TODO: wrong
|
||||||
|
if(mn > 1 && isLeapYear) dayOfYear++;
|
||||||
|
return dayOfYear;
|
||||||
|
case 3: return d.getFullYear() % 100; // Y@K!
|
||||||
|
case 4: return d.getSeconds();
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
TIMER() : number {
|
TIMER() : number {
|
||||||
return Date.now() / 1000;
|
return Date.now() / 1000;
|
||||||
}
|
}
|
||||||
|
UPS$(arg : string) : string {
|
||||||
|
return this.checkString(arg).toUpperCase();
|
||||||
|
}
|
||||||
VAL(arg : string) : number {
|
VAL(arg : string) : number {
|
||||||
var n = parseFloat(this.checkString(arg));
|
var n = parseFloat(this.checkString(arg));
|
||||||
return isNaN(n) ? 0 : n; // TODO? altair works this way
|
return isNaN(n) ? 0 : n; // TODO? altair works this way
|
||||||
|
@ -192,6 +192,7 @@ export class TeleTypeWithKeyboard extends TeleType {
|
|||||||
clear() {
|
clear() {
|
||||||
super.clear();
|
super.clear();
|
||||||
this.hideinput();
|
this.hideinput();
|
||||||
|
this.waitingfor = null;
|
||||||
}
|
}
|
||||||
focusinput() {
|
focusinput() {
|
||||||
this.ensureline();
|
this.ensureline();
|
||||||
@ -234,7 +235,7 @@ export class TeleTypeWithKeyboard extends TeleType {
|
|||||||
}
|
}
|
||||||
sendinput(s: string) {
|
sendinput(s: string) {
|
||||||
if (this.resolveInput) {
|
if (this.resolveInput) {
|
||||||
if (this.uppercaseOnly)
|
//if (this.uppercaseOnly) // TODO: always uppercase?
|
||||||
s = s.toUpperCase();
|
s = s.toUpperCase();
|
||||||
this.addtext(s, 4);
|
this.addtext(s, 4);
|
||||||
this.flushline();
|
this.flushline();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user