2020-08-11 02:35:25 +00:00
|
|
|
import { WorkerError, CodeListingMap, SourceLocation, SourceLine } from "../workertypes";
|
2020-08-05 04:48:29 +00:00
|
|
|
|
2020-08-11 17:29:31 +00:00
|
|
|
export interface BASICOptions {
|
|
|
|
dialectName : string; // use this to select the dialect
|
|
|
|
// SYNTAX AND PARSING
|
|
|
|
asciiOnly : boolean; // reject non-ASCII chars?
|
|
|
|
uppercaseOnly : boolean; // convert everything to uppercase?
|
|
|
|
optionalLabels : boolean; // can omit line numbers and use labels?
|
2020-08-15 20:03:56 +00:00
|
|
|
optionalWhitespace : boolean; // can "crunch" keywords? also, eat extra ":" delims
|
2020-08-19 17:05:30 +00:00
|
|
|
multipleStmtsPerLine : boolean; // multiple statements separated by ":"
|
2020-08-21 03:20:53 +00:00
|
|
|
varNaming : 'A'|'A1'|'A1$'|'AA'|'*'; // only allow A0-9 for numerics, single letter for arrays/strings
|
2020-08-13 17:51:35 +00:00
|
|
|
squareBrackets : boolean; // "[" and "]" interchangable with "(" and ")"?
|
2020-08-11 17:29:31 +00:00
|
|
|
tickComments : boolean; // support 'comments?
|
2020-08-12 15:51:18 +00:00
|
|
|
hexOctalConsts : boolean; // support &H and &O integer constants?
|
2020-08-16 17:16:29 +00:00
|
|
|
optionalLet : boolean; // LET is optional
|
2020-08-15 11:53:13 +00:00
|
|
|
chainAssignments : boolean; // support A = B = value (HP2000)
|
2020-08-11 17:29:31 +00:00
|
|
|
validKeywords : string[]; // valid keywords (or null for accept all)
|
|
|
|
validFunctions : string[]; // valid functions (or null for accept all)
|
|
|
|
validOperators : string[]; // valid operators (or null for accept all)
|
|
|
|
// VALUES AND OPERATORS
|
|
|
|
defaultValues : boolean; // initialize unset variables to default value? (0 or "")
|
|
|
|
stringConcat : boolean; // can concat strings with "+" operator?
|
|
|
|
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
|
|
|
|
maxDefArgs : number; // maximum # of arguments for user-defined functions
|
|
|
|
// ARRAYS
|
|
|
|
staticArrays : boolean; // can only DIM with constant value? (and never redim)
|
|
|
|
sharedArrayNamespace : boolean; // arrays and variables have same namespace? (TODO)
|
|
|
|
defaultArrayBase : number; // arrays start at this number (0 or 1)
|
|
|
|
defaultArraySize : number; // arrays are allocated w/ this size (starting @ 0)
|
|
|
|
maxDimensions : number; // max number of dimensions for arrays
|
2020-08-13 17:51:35 +00:00
|
|
|
arraysContainChars : boolean; // HP BASIC array-slicing syntax
|
2020-08-11 17:29:31 +00:00
|
|
|
// PRINTING
|
|
|
|
printZoneLength : number; // print zone length
|
|
|
|
numericPadding : boolean; // " " or "-" before and " " after numbers?
|
|
|
|
// CONTROL FLOW
|
2020-08-19 01:06:23 +00:00
|
|
|
compiledBlocks : boolean; // assume blocks are statically compiled, not interpreted
|
2020-08-11 17:29:31 +00:00
|
|
|
testInitialFor : boolean; // can we skip a NEXT statement? (can't interleave tho)
|
2020-08-12 15:11:10 +00:00
|
|
|
optionalNextVar : boolean; // can do NEXT without variable
|
2020-08-12 16:57:54 +00:00
|
|
|
multipleNextVars : boolean; // NEXT J,I
|
|
|
|
checkOnGotoIndex : boolean; // fatal error when ON..GOTO index out of bounds
|
2020-08-13 17:51:35 +00:00
|
|
|
computedGoto : boolean; // non-const expr GOTO label (and GOTO..OF expression)
|
2020-08-12 16:57:54 +00:00
|
|
|
restoreWithLabel : boolean; // RESTORE <label>
|
2020-08-13 17:51:35 +00:00
|
|
|
endStmtRequired : boolean; // need END at end?
|
2020-08-11 17:29:31 +00:00
|
|
|
// MISC
|
2020-08-24 16:51:47 +00:00
|
|
|
multilineIfThen? : boolean; // multi-line IF .. ELSE .. END IF?
|
2020-08-11 17:29:31 +00:00
|
|
|
commandsPerSec? : number; // how many commands per second?
|
2020-08-15 20:03:56 +00:00
|
|
|
maxLinesPerFile? : number; // limit on # of lines
|
|
|
|
maxArrayElements? : number; // max array elements (all dimensions)
|
2020-08-11 17:29:31 +00:00
|
|
|
}
|
|
|
|
|
2020-08-22 16:40:58 +00:00
|
|
|
// objects that have source code position info
|
2020-08-05 04:48:29 +00:00
|
|
|
export interface SourceLocated {
|
2020-08-22 16:40:58 +00:00
|
|
|
$loc?: SourceLocation;
|
|
|
|
}
|
|
|
|
// statements also have the 'offset' (pc) field from SourceLine
|
|
|
|
export interface SourceLineLocated {
|
2020-08-11 02:35:25 +00:00
|
|
|
$loc?: SourceLine;
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
|
2020-08-11 17:29:31 +00:00
|
|
|
export class CompileError extends Error {
|
2020-08-12 16:57:54 +00:00
|
|
|
$loc : SourceLocation;
|
|
|
|
constructor(msg: string, loc: SourceLocation) {
|
2020-08-05 04:48:29 +00:00
|
|
|
super(msg);
|
|
|
|
Object.setPrototypeOf(this, CompileError.prototype);
|
2020-08-12 16:57:54 +00:00
|
|
|
this.$loc = loc;
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lexer regular expression -- each (capture group) handles a different token type
|
2020-08-15 20:03:56 +00:00
|
|
|
// FLOAT INT HEXOCTAL REMARK IDENT STRING RELOP EXP OPERATORS OTHER WS
|
|
|
|
const re_toks = /([0-9.]+[E][+-]?\d+|\d+[.][E0-9]*|[.][E0-9]+)|[0]*(\d+)|&([OH][0-9A-F]+)|(['].*)|([A-Z_]\w*[$]?)|(".*?")|([<>]?[=<>#])|(\*\*)|([-+*/^,;:()\[\]\?\\])|(\S+)|(\s+)/gi;
|
2020-08-05 04:48:29 +00:00
|
|
|
|
|
|
|
export enum TokenType {
|
|
|
|
EOL = 0,
|
2020-08-14 04:34:54 +00:00
|
|
|
Float,
|
2020-08-05 04:48:29 +00:00
|
|
|
Int,
|
2020-08-12 15:51:18 +00:00
|
|
|
HexOctalInt,
|
2020-08-05 04:48:29 +00:00
|
|
|
Remark,
|
|
|
|
Ident,
|
|
|
|
String,
|
|
|
|
Relational,
|
2020-08-13 17:51:35 +00:00
|
|
|
DoubleStar,
|
2020-08-05 04:48:29 +00:00
|
|
|
Operator,
|
|
|
|
CatchAll,
|
2020-08-14 04:34:54 +00:00
|
|
|
Whitespace,
|
2020-08-05 04:48:29 +00:00
|
|
|
_LAST,
|
|
|
|
}
|
|
|
|
|
|
|
|
export type ExprTypes = BinOp | UnOp | IndOp | Literal;
|
2020-08-10 01:40:17 +00:00
|
|
|
export type Expr = ExprTypes; // & SourceLocated;
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
export type Opcode = string;
|
2020-08-20 17:58:14 +00:00
|
|
|
export type Value = number | string;
|
|
|
|
export type ValueType = 'number' | 'string' | 'label';
|
2020-08-05 04:48:29 +00:00
|
|
|
|
2020-08-20 17:58:14 +00:00
|
|
|
export interface ExprBase extends SourceLocated {
|
|
|
|
valtype: ValueType;
|
|
|
|
}
|
2020-08-05 04:48:29 +00:00
|
|
|
|
2020-08-20 17:58:14 +00:00
|
|
|
export interface Literal extends ExprBase {
|
2020-08-05 04:48:29 +00:00
|
|
|
value: Value;
|
|
|
|
}
|
|
|
|
|
2020-08-20 17:58:14 +00:00
|
|
|
export interface BinOp extends ExprBase {
|
2020-08-05 04:48:29 +00:00
|
|
|
op: Opcode;
|
|
|
|
left: Expr;
|
|
|
|
right: Expr;
|
|
|
|
}
|
|
|
|
|
2020-08-20 17:58:14 +00:00
|
|
|
export interface UnOp extends ExprBase {
|
2020-08-09 16:23:49 +00:00
|
|
|
op: 'neg' | 'lnot' | 'bnot';
|
2020-08-05 04:48:29 +00:00
|
|
|
expr: Expr;
|
|
|
|
}
|
|
|
|
|
2020-08-20 17:58:14 +00:00
|
|
|
export interface IndOp extends ExprBase {
|
2020-08-05 04:48:29 +00:00
|
|
|
name: string;
|
|
|
|
args: Expr[];
|
|
|
|
}
|
|
|
|
|
2020-08-22 16:40:58 +00:00
|
|
|
export interface Statement extends SourceLineLocated {
|
2020-08-19 01:06:23 +00:00
|
|
|
command: string;
|
|
|
|
}
|
|
|
|
|
2020-08-20 19:56:28 +00:00
|
|
|
export interface ScopeStartStatement extends Statement {
|
|
|
|
endpc?: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface ScopeEndStatement extends Statement {
|
|
|
|
startpc?: number;
|
|
|
|
}
|
|
|
|
|
2020-08-19 01:06:23 +00:00
|
|
|
export interface PRINT_Statement extends Statement {
|
2020-08-05 04:48:29 +00:00
|
|
|
command: "PRINT";
|
|
|
|
args: Expr[];
|
|
|
|
}
|
|
|
|
|
2020-08-19 01:06:23 +00:00
|
|
|
export interface LET_Statement extends Statement {
|
2020-08-05 04:48:29 +00:00
|
|
|
command: "LET";
|
2020-08-15 11:53:13 +00:00
|
|
|
lexprs: IndOp[];
|
2020-08-05 04:48:29 +00:00
|
|
|
right: Expr;
|
|
|
|
}
|
|
|
|
|
2020-08-19 01:06:23 +00:00
|
|
|
export interface DIM_Statement extends Statement {
|
2020-08-09 02:36:21 +00:00
|
|
|
command: "DIM";
|
|
|
|
args: IndOp[];
|
|
|
|
}
|
|
|
|
|
2020-08-19 01:06:23 +00:00
|
|
|
export interface GOTO_Statement extends Statement {
|
2020-08-05 04:48:29 +00:00
|
|
|
command: "GOTO";
|
|
|
|
label: Expr;
|
|
|
|
}
|
|
|
|
|
2020-08-19 01:06:23 +00:00
|
|
|
export interface GOSUB_Statement extends Statement {
|
2020-08-05 04:48:29 +00:00
|
|
|
command: "GOSUB";
|
|
|
|
label: Expr;
|
|
|
|
}
|
|
|
|
|
2020-08-19 01:06:23 +00:00
|
|
|
export interface RETURN_Statement extends Statement {
|
2020-08-05 04:48:29 +00:00
|
|
|
command: "RETURN";
|
|
|
|
}
|
|
|
|
|
2020-08-19 01:06:23 +00:00
|
|
|
export interface ONGO_Statement extends Statement {
|
2020-08-10 13:07:09 +00:00
|
|
|
command: "ONGOTO" | "ONGOSUB";
|
2020-08-09 02:36:21 +00:00
|
|
|
expr: Expr;
|
|
|
|
labels: Expr[];
|
|
|
|
}
|
|
|
|
|
2020-08-20 19:56:28 +00:00
|
|
|
export interface IF_Statement extends ScopeStartStatement {
|
2020-08-05 04:48:29 +00:00
|
|
|
command: "IF";
|
|
|
|
cond: Expr;
|
|
|
|
}
|
|
|
|
|
2020-08-20 19:56:28 +00:00
|
|
|
export interface ELSE_Statement extends ScopeStartStatement {
|
2020-08-09 02:36:21 +00:00
|
|
|
command: "ELSE";
|
|
|
|
}
|
|
|
|
|
2020-08-20 19:56:28 +00:00
|
|
|
export interface FOR_Statement extends ScopeStartStatement {
|
2020-08-05 04:48:29 +00:00
|
|
|
command: "FOR";
|
|
|
|
lexpr: IndOp;
|
|
|
|
initial: Expr;
|
|
|
|
target: Expr;
|
|
|
|
step?: Expr;
|
|
|
|
}
|
|
|
|
|
2020-08-20 19:56:28 +00:00
|
|
|
export interface NEXT_Statement extends ScopeEndStatement {
|
2020-08-05 04:48:29 +00:00
|
|
|
command: "NEXT";
|
|
|
|
lexpr?: IndOp;
|
|
|
|
}
|
|
|
|
|
2020-08-20 19:56:28 +00:00
|
|
|
export interface WHILE_Statement extends ScopeStartStatement {
|
2020-08-12 16:57:54 +00:00
|
|
|
command: "WHILE";
|
|
|
|
cond: Expr;
|
|
|
|
}
|
|
|
|
|
2020-08-20 19:56:28 +00:00
|
|
|
export interface WEND_Statement extends ScopeEndStatement {
|
2020-08-12 16:57:54 +00:00
|
|
|
command: "WEND";
|
|
|
|
}
|
|
|
|
|
2020-08-24 16:51:47 +00:00
|
|
|
export interface END_Statement extends ScopeEndStatement {
|
|
|
|
command: "END";
|
|
|
|
}
|
|
|
|
|
2020-08-19 01:06:23 +00:00
|
|
|
export interface INPUT_Statement extends Statement {
|
2020-08-05 04:48:29 +00:00
|
|
|
command: "INPUT";
|
|
|
|
prompt: Expr;
|
|
|
|
args: IndOp[];
|
2020-08-21 03:20:53 +00:00
|
|
|
timeout?: Expr;
|
|
|
|
elapsed?: IndOp;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface ENTER_Statement extends INPUT_Statement {
|
|
|
|
timeout: Expr;
|
|
|
|
elapsed: IndOp;
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
|
2020-08-19 01:06:23 +00:00
|
|
|
export interface DATA_Statement extends Statement {
|
2020-08-09 02:36:21 +00:00
|
|
|
command: "DATA";
|
2020-08-14 04:34:54 +00:00
|
|
|
datums: Literal[];
|
2020-08-09 02:36:21 +00:00
|
|
|
}
|
|
|
|
|
2020-08-19 01:06:23 +00:00
|
|
|
export interface READ_Statement extends Statement {
|
2020-08-09 02:36:21 +00:00
|
|
|
command: "READ";
|
2020-08-05 04:48:29 +00:00
|
|
|
args: IndOp[];
|
|
|
|
}
|
|
|
|
|
2020-08-19 01:06:23 +00:00
|
|
|
export interface RESTORE_Statement extends Statement {
|
2020-08-12 16:57:54 +00:00
|
|
|
command: "RESTORE";
|
|
|
|
label: Expr;
|
|
|
|
}
|
|
|
|
|
2020-08-19 01:06:23 +00:00
|
|
|
export interface DEF_Statement extends Statement {
|
2020-08-05 04:48:29 +00:00
|
|
|
command: "DEF";
|
|
|
|
lexpr: IndOp;
|
|
|
|
def: Expr;
|
|
|
|
}
|
|
|
|
|
2020-08-24 16:51:47 +00:00
|
|
|
export interface SUB_Statement extends ScopeStartStatement {
|
|
|
|
command: "SUB";
|
|
|
|
lexpr: IndOp;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface CALL_Statement {
|
|
|
|
command: "CALL";
|
|
|
|
call: IndOp;
|
|
|
|
}
|
|
|
|
|
2020-08-19 01:06:23 +00:00
|
|
|
export interface OPTION_Statement extends Statement {
|
2020-08-05 04:48:29 +00:00
|
|
|
command: "OPTION";
|
|
|
|
optname: string;
|
|
|
|
optargs: string[];
|
|
|
|
}
|
|
|
|
|
2020-08-19 01:06:23 +00:00
|
|
|
export interface GET_Statement extends Statement { // applesoft only?
|
2020-08-09 02:36:21 +00:00
|
|
|
command: "GET";
|
|
|
|
lexpr: IndOp;
|
|
|
|
}
|
|
|
|
|
2020-08-19 01:06:23 +00:00
|
|
|
export interface CHANGE_Statement extends Statement {
|
2020-08-15 11:53:13 +00:00
|
|
|
command: "CHANGE";
|
|
|
|
src: Expr;
|
|
|
|
dest: IndOp;
|
|
|
|
}
|
|
|
|
|
2020-08-22 16:40:58 +00:00
|
|
|
export interface CONVERT_Statement extends Statement {
|
|
|
|
command: "CONVERT";
|
|
|
|
src: Expr;
|
|
|
|
dest: IndOp;
|
|
|
|
}
|
|
|
|
|
2020-08-19 01:06:23 +00:00
|
|
|
export interface NoArgStatement extends Statement {
|
2020-08-09 02:36:21 +00:00
|
|
|
command: string;
|
|
|
|
}
|
|
|
|
|
2020-08-05 04:48:29 +00:00
|
|
|
export interface BASICProgram {
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
opts: BASICOptions;
|
2020-08-19 17:05:30 +00:00
|
|
|
stmts: Statement[];
|
|
|
|
labels: { [label: string]: number }; // label -> PC
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
|
2020-08-24 16:51:47 +00:00
|
|
|
class Token implements SourceLocated {
|
2020-08-05 04:48:29 +00:00
|
|
|
str: string;
|
|
|
|
type: TokenType;
|
|
|
|
$loc: SourceLocation;
|
|
|
|
}
|
|
|
|
|
|
|
|
const OPERATORS = {
|
2020-08-09 16:23:49 +00:00
|
|
|
'IMP': {f:'bimp',p:4},
|
|
|
|
'EQV': {f:'beqv',p:5},
|
|
|
|
'XOR': {f:'bxor',p:6},
|
2020-08-11 17:29:31 +00:00
|
|
|
'OR': {f:'bor',p:7}, // or "lor" for logical
|
|
|
|
'AND': {f:'band',p:8}, // or "land" for logical
|
|
|
|
'||': {f:'lor',p:17}, // not used
|
|
|
|
'&&': {f:'land',p:18}, // not used
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
'=': {f:'eq',p:50},
|
2020-08-15 11:53:13 +00:00
|
|
|
'==': {f:'eq',p:50},
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
'<>': {f:'ne',p:50},
|
2020-08-13 17:51:35 +00:00
|
|
|
'><': {f:'ne',p:50},
|
2020-08-15 11:53:13 +00:00
|
|
|
'!=': {f:'ne',p:50},
|
2020-08-13 17:51:35 +00:00
|
|
|
'#': {f:'ne',p:50},
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
'<': {f:'lt',p:50},
|
|
|
|
'>': {f:'gt',p:50},
|
|
|
|
'<=': {f:'le',p:50},
|
|
|
|
'>=': {f:'ge',p:50},
|
2020-08-13 17:51:35 +00:00
|
|
|
'MIN': {f:'min',p:75},
|
|
|
|
'MAX': {f:'max',p:75},
|
2020-08-05 04:48:29 +00:00
|
|
|
'+': {f:'add',p:100},
|
|
|
|
'-': {f:'sub',p:100},
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
'%': {f:'mod',p:140},
|
2020-08-24 16:51:47 +00:00
|
|
|
'MOD': {f:'mod',p:140},
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
'\\': {f:'idiv',p:150},
|
2020-08-05 04:48:29 +00:00
|
|
|
'*': {f:'mul',p:200},
|
|
|
|
'/': {f:'div',p:200},
|
2020-08-13 17:51:35 +00:00
|
|
|
'^': {f:'pow',p:300},
|
|
|
|
'**': {f:'pow',p:300},
|
2020-08-05 04:48:29 +00:00
|
|
|
};
|
|
|
|
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
function getOperator(op: string) {
|
|
|
|
return OPERATORS[op];
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function getPrecedence(tok: Token): number {
|
|
|
|
switch (tok.type) {
|
|
|
|
case TokenType.Operator:
|
2020-08-13 17:51:35 +00:00
|
|
|
case TokenType.DoubleStar:
|
2020-08-05 04:48:29 +00:00
|
|
|
case TokenType.Relational:
|
|
|
|
case TokenType.Ident:
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
let op = getOperator(tok.str);
|
2020-08-05 04:48:29 +00:00
|
|
|
if (op) return op.p;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// is token an end of statement marker? (":" or end of line)
|
|
|
|
function isEOS(tok: Token) {
|
2020-08-09 02:36:21 +00:00
|
|
|
return tok.type == TokenType.EOL || tok.type == TokenType.Remark
|
|
|
|
|| tok.str == ':' || tok.str == 'ELSE'; // TODO: only ELSE if ifElse==true
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function stripQuotes(s: string) {
|
|
|
|
// TODO: assert
|
|
|
|
return s.substr(1, s.length-2);
|
|
|
|
}
|
|
|
|
|
2020-08-15 20:03:56 +00:00
|
|
|
function isLiteral(arg: Expr): arg is Literal {
|
|
|
|
return (arg as any).value != null;
|
|
|
|
}
|
|
|
|
function isLookup(arg: Expr): arg is IndOp {
|
|
|
|
return (arg as any).name != null;
|
|
|
|
}
|
|
|
|
function isBinOp(arg: Expr): arg is BinOp {
|
|
|
|
return (arg as any).op != null && (arg as any).left != null && (arg as any).right != null;
|
|
|
|
}
|
|
|
|
function isUnOp(arg: Expr): arg is UnOp {
|
|
|
|
return (arg as any).op != null && (arg as any).expr != null;
|
|
|
|
}
|
|
|
|
|
2020-08-22 16:40:58 +00:00
|
|
|
function mergeLocs(a: SourceLocation, b: SourceLocation) : SourceLocation {
|
|
|
|
return {
|
|
|
|
line:Math.min(a.line, b.line),
|
|
|
|
start:Math.min(a.start, b.start),
|
|
|
|
end:Math.max(a.end, b.end),
|
|
|
|
label:a.label || b.label,
|
|
|
|
path:a.path || b.path,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-05 04:48:29 +00:00
|
|
|
///// BASIC PARSER
|
|
|
|
|
|
|
|
export class BASICParser {
|
2020-08-13 17:51:35 +00:00
|
|
|
opts : BASICOptions = DIALECTS['DEFAULT'];
|
2020-08-19 01:06:23 +00:00
|
|
|
optionCount : number; // how many OPTION stmts so far?
|
2020-08-22 16:40:58 +00:00
|
|
|
maxlinelen : number = 255; // maximum line length (some like HP use 72 chars)
|
2020-08-19 17:05:30 +00:00
|
|
|
stmts : Statement[];
|
2020-08-05 04:48:29 +00:00
|
|
|
errors: WorkerError[];
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
listings: CodeListingMap;
|
2020-08-19 17:05:30 +00:00
|
|
|
labels: { [label: string]: number }; // label -> PC
|
2020-08-20 17:58:14 +00:00
|
|
|
targets: { [targetlabel: string]: SourceLocation }; // targets of GOTOs etc
|
|
|
|
vardefs: { [name: string]: IndOp }; // LET or DIM
|
|
|
|
varrefs: { [name: string]: SourceLocation }; // variable references
|
2020-08-15 20:03:56 +00:00
|
|
|
fnrefs: { [name: string]: string[] }; // DEF FN call graph
|
2020-08-20 19:56:28 +00:00
|
|
|
scopestack: number[];
|
2020-08-24 16:51:47 +00:00
|
|
|
elseifcount: number;
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
|
2020-08-09 21:32:52 +00:00
|
|
|
path : string;
|
2020-08-05 04:48:29 +00:00
|
|
|
lineno : number;
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
tokens: Token[];
|
|
|
|
eol: Token;
|
2020-08-05 04:48:29 +00:00
|
|
|
curlabel: string;
|
|
|
|
lasttoken: Token;
|
|
|
|
|
|
|
|
constructor() {
|
2020-08-19 01:06:23 +00:00
|
|
|
this.optionCount = 0;
|
2020-08-19 17:05:30 +00:00
|
|
|
this.lineno = 0;
|
|
|
|
this.curlabel = null;
|
|
|
|
this.stmts = [];
|
2020-08-05 04:48:29 +00:00
|
|
|
this.labels = {};
|
|
|
|
this.targets = {};
|
|
|
|
this.errors = [];
|
|
|
|
this.listings = {};
|
2020-08-20 17:58:14 +00:00
|
|
|
this.vardefs = {};
|
2020-08-15 20:03:56 +00:00
|
|
|
this.varrefs = {};
|
|
|
|
this.fnrefs = {};
|
2020-08-19 01:06:23 +00:00
|
|
|
this.scopestack = [];
|
2020-08-24 16:51:47 +00:00
|
|
|
this.elseifcount = 0;
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
2020-08-14 14:26:43 +00:00
|
|
|
addError(msg: string, loc?: SourceLocation) {
|
2020-08-20 16:41:58 +00:00
|
|
|
var tok = this.lasttoken || this.peekToken();
|
|
|
|
if (!loc) loc = tok.$loc;
|
2020-08-12 15:11:10 +00:00
|
|
|
this.errors.push({path:loc.path, line:loc.line, label:this.curlabel, start:loc.start, end:loc.end, msg:msg});
|
2020-08-14 14:26:43 +00:00
|
|
|
}
|
2020-08-24 16:51:47 +00:00
|
|
|
compileError(msg: string, loc?: SourceLocation, loc2?: SourceLocation) {
|
2020-08-14 14:26:43 +00:00
|
|
|
this.addError(msg, loc);
|
2020-08-24 16:51:47 +00:00
|
|
|
//if (loc2 != null) this.addError(`...`, loc2);
|
2020-08-12 16:57:54 +00:00
|
|
|
throw new CompileError(msg, loc);
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
dialectError(what: string, loc?: SourceLocation) {
|
2020-08-19 01:06:23 +00:00
|
|
|
this.compileError(`${what} in this dialect of BASIC (${this.opts.dialectName}).`, loc);
|
|
|
|
}
|
|
|
|
dialectErrorNoSupport(what: string, loc?: SourceLocation) {
|
|
|
|
this.compileError(`You can't use ${what} in this dialect of BASIC (${this.opts.dialectName}).`, loc); // TODO
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
consumeToken(): Token {
|
|
|
|
var tok = this.lasttoken = (this.tokens.shift() || this.eol);
|
|
|
|
return tok;
|
|
|
|
}
|
2020-08-09 16:23:49 +00:00
|
|
|
expectToken(str: string, msg?: string) : Token {
|
2020-08-05 04:48:29 +00:00
|
|
|
var tok = this.consumeToken();
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
var tokstr = tok.str;
|
2020-08-05 04:48:29 +00:00
|
|
|
if (str != tokstr) {
|
2020-08-09 16:23:49 +00:00
|
|
|
this.compileError(msg || `There should be a "${str}" here.`);
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
return tok;
|
|
|
|
}
|
2020-08-12 15:11:10 +00:00
|
|
|
expectTokens(strlist: string[], msg?: string) : Token {
|
|
|
|
var tok = this.consumeToken();
|
|
|
|
var tokstr = tok.str;
|
|
|
|
if (strlist.indexOf(tokstr) < 0) {
|
2020-08-12 15:51:18 +00:00
|
|
|
this.compileError(msg || `There should be a ${strlist.map((s) => `"${s}"`).join(' or ')} here.`);
|
2020-08-12 15:11:10 +00:00
|
|
|
}
|
|
|
|
return tok;
|
|
|
|
}
|
2020-08-15 11:53:13 +00:00
|
|
|
peekToken(lookahead?: number): Token {
|
|
|
|
var tok = this.tokens[lookahead || 0];
|
2020-08-05 04:48:29 +00:00
|
|
|
return tok ? tok : this.eol;
|
|
|
|
}
|
|
|
|
pushbackToken(tok: Token) {
|
|
|
|
this.tokens.unshift(tok);
|
|
|
|
}
|
2020-08-19 17:05:30 +00:00
|
|
|
// this parses either a line number or "label:" -- or adds a default label to a line
|
|
|
|
parseOptLabel() {
|
2020-08-05 04:48:29 +00:00
|
|
|
let tok = this.consumeToken();
|
|
|
|
switch (tok.type) {
|
2020-08-09 02:36:21 +00:00
|
|
|
case TokenType.Ident:
|
2020-08-16 17:16:29 +00:00
|
|
|
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)) {
|
2020-08-09 02:36:21 +00:00
|
|
|
this.consumeToken(); // eat the ":"
|
|
|
|
// fall through to the next case
|
|
|
|
} else {
|
|
|
|
this.pushbackToken(tok); // nope
|
|
|
|
break;
|
|
|
|
}
|
2020-08-16 17:16:29 +00:00
|
|
|
} else
|
2020-08-19 01:06:23 +00:00
|
|
|
this.dialectError(`Each line must begin with a line number`);
|
2020-08-05 04:48:29 +00:00
|
|
|
case TokenType.Int:
|
2020-08-19 17:05:30 +00:00
|
|
|
this.addLabel(tok.str);
|
|
|
|
return;
|
|
|
|
// label added, return from function... other cases add default label
|
2020-08-12 15:51:18 +00:00
|
|
|
case TokenType.HexOctalInt:
|
2020-08-14 04:34:54 +00:00
|
|
|
case TokenType.Float:
|
2020-08-09 01:03:48 +00:00
|
|
|
this.compileError(`Line numbers must be positive integers.`);
|
|
|
|
break;
|
2020-08-16 17:16:29 +00:00
|
|
|
case TokenType.Operator:
|
|
|
|
if (this.supportsCommand(tok.str) && this.validKeyword(tok.str)) {
|
|
|
|
this.pushbackToken(tok);
|
|
|
|
break; // "?" is allowed
|
|
|
|
}
|
2020-08-05 04:48:29 +00:00
|
|
|
default:
|
2020-08-16 17:16:29 +00:00
|
|
|
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.`);
|
2020-08-15 11:53:13 +00:00
|
|
|
case TokenType.Remark:
|
2020-08-05 04:48:29 +00:00
|
|
|
break;
|
|
|
|
}
|
2020-08-19 17:05:30 +00:00
|
|
|
// add default label
|
|
|
|
this.addLabel('#'+this.lineno);
|
|
|
|
}
|
|
|
|
getPC() : number {
|
|
|
|
return this.stmts.length;
|
|
|
|
}
|
|
|
|
addStatement(stmt: Statement, cmdtok: Token, endtok?: Token) {
|
2020-08-22 16:40:58 +00:00
|
|
|
// set location for statement, adding offset (PC) field
|
2020-08-19 17:05:30 +00:00
|
|
|
if (endtok == null) endtok = this.peekToken();
|
2020-08-22 16:40:58 +00:00
|
|
|
stmt.$loc = { path: cmdtok.$loc.path, line: cmdtok.$loc.line, start: cmdtok.$loc.start, end: endtok.$loc.start,
|
|
|
|
label: this.curlabel,
|
|
|
|
offset: this.stmts.length };
|
2020-08-20 19:56:28 +00:00
|
|
|
// check IF/THEN WHILE/WEND FOR/NEXT etc
|
|
|
|
this.modifyScope(stmt);
|
|
|
|
// add to list
|
2020-08-19 17:05:30 +00:00
|
|
|
this.stmts.push(stmt);
|
|
|
|
}
|
2020-08-24 16:51:47 +00:00
|
|
|
addLabel(str: string, offset?: number) {
|
2020-08-19 17:05:30 +00:00
|
|
|
if (this.labels[str] != null) this.compileError(`There's a duplicated label named "${str}".`);
|
2020-08-24 16:51:47 +00:00
|
|
|
this.labels[str] = this.getPC() + (offset || 0);
|
2020-08-15 20:03:56 +00:00
|
|
|
this.curlabel = str;
|
|
|
|
this.tokens.forEach((tok) => tok.$loc.label = str);
|
|
|
|
}
|
2020-08-05 04:48:29 +00:00
|
|
|
parseFile(file: string, path: string) : BASICProgram {
|
2020-08-09 21:32:52 +00:00
|
|
|
this.path = path;
|
2020-08-15 20:03:56 +00:00
|
|
|
var txtlines = file.split(/\n|\r\n?/);
|
2020-08-19 17:05:30 +00:00
|
|
|
txtlines.forEach((line) => this.parseLine(line));
|
|
|
|
var program = { opts: this.opts, stmts: this.stmts, labels: this.labels };
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
this.checkAll(program);
|
2020-08-05 04:48:29 +00:00
|
|
|
this.listings[path] = this.generateListing(file, program);
|
|
|
|
return program;
|
|
|
|
}
|
2020-08-19 17:05:30 +00:00
|
|
|
parseLine(line: string) : void {
|
2020-08-05 04:48:29 +00:00
|
|
|
try {
|
|
|
|
this.tokenize(line);
|
2020-08-19 17:05:30 +00:00
|
|
|
this.parse();
|
2020-08-05 04:48:29 +00:00
|
|
|
} catch (e) {
|
|
|
|
if (!(e instanceof CompileError)) throw e; // ignore compile errors since errors[] list captures them
|
|
|
|
}
|
|
|
|
}
|
2020-08-15 20:03:56 +00:00
|
|
|
_tokenize(line: string) : void {
|
2020-08-12 16:57:54 +00:00
|
|
|
// split identifier regex (if token-crunching enabled)
|
2020-08-13 23:53:11 +00:00
|
|
|
let splitre = this.opts.optionalWhitespace && new RegExp('('+this.opts.validKeywords.map(s => `${s}`).join('|')+')');
|
2020-08-12 16:57:54 +00:00
|
|
|
// iterate over each token via re_toks regex
|
2020-08-14 04:34:54 +00:00
|
|
|
var lastTokType = TokenType.CatchAll;
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
var m : RegExpMatchArray;
|
2020-08-05 04:48:29 +00:00
|
|
|
while (m = re_toks.exec(line)) {
|
2020-08-14 04:34:54 +00:00
|
|
|
for (var i = 1; i <= lastTokType; i++) {
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
let s : string = m[i];
|
2020-08-05 04:48:29 +00:00
|
|
|
if (s != null) {
|
2020-08-15 20:03:56 +00:00
|
|
|
let loc = { path: this.path, line: this.lineno, start: m.index, end: m.index+s.length };
|
2020-08-09 02:36:21 +00:00
|
|
|
// maybe we don't support unicode in 1975?
|
|
|
|
if (this.opts.asciiOnly && !/^[\x00-\x7F]*$/.test(s))
|
2020-08-19 01:06:23 +00:00
|
|
|
this.dialectErrorNoSupport(`non-ASCII characters`);
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
// uppercase all identifiers, and maybe more
|
2020-08-14 04:34:54 +00:00
|
|
|
if (i == TokenType.Ident || i == TokenType.HexOctalInt || this.opts.uppercaseOnly) {
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
s = s.toUpperCase();
|
2020-08-14 04:34:54 +00:00
|
|
|
// DATA statement captures whitespace too
|
|
|
|
if (s == 'DATA') lastTokType = TokenType.Whitespace;
|
2020-08-15 20:03:56 +00:00
|
|
|
// certain keywords shouldn't split for rest of line
|
|
|
|
if (s == 'DATA') splitre = null;
|
|
|
|
if (s == 'OPTION') splitre = null;
|
2020-08-14 04:34:54 +00:00
|
|
|
// REM means ignore rest of statement
|
|
|
|
if (lastTokType == TokenType.CatchAll && s.startsWith('REM')) {
|
|
|
|
s = 'REM';
|
|
|
|
lastTokType = TokenType.EOL;
|
|
|
|
}
|
|
|
|
}
|
2020-08-13 17:51:35 +00:00
|
|
|
// convert brackets
|
|
|
|
if (s == '[' || s == ']') {
|
2020-08-19 01:06:23 +00:00
|
|
|
if (!this.opts.squareBrackets) this.dialectErrorNoSupport(`square brackets`);
|
2020-08-13 17:51:35 +00:00
|
|
|
if (s == '[') s = '(';
|
|
|
|
if (s == ']') s = ')';
|
|
|
|
}
|
2020-08-13 23:53:11 +00:00
|
|
|
// un-crunch tokens?
|
|
|
|
if (splitre && i == TokenType.Ident) {
|
2020-08-15 20:03:56 +00:00
|
|
|
var splittoks = s.split(splitre).filter((s) => s != ''); // only non-empties
|
|
|
|
if (splittoks.length > 1) {
|
|
|
|
splittoks.forEach((ss) => {
|
|
|
|
// check to see if leftover might be integer, or identifier
|
|
|
|
if (/^[0-9]+$/.test(ss)) i = TokenType.Int;
|
|
|
|
else if (/^[A-Z_]\w*[$]?$/.test(ss)) i = TokenType.Ident;
|
|
|
|
else this.compileError(`Try adding whitespace before "${ss}".`);
|
2020-08-13 23:53:11 +00:00
|
|
|
this.tokens.push({str: ss, type: i, $loc:loc});
|
2020-08-15 20:03:56 +00:00
|
|
|
});
|
|
|
|
s = null;
|
|
|
|
}
|
2020-08-11 17:29:31 +00:00
|
|
|
}
|
2020-08-15 20:03:56 +00:00
|
|
|
// add token to list
|
|
|
|
if (s) this.tokens.push({str: s, type: i, $loc:loc});
|
2020-08-05 04:48:29 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-15 20:03:56 +00:00
|
|
|
}
|
|
|
|
tokenize(line: string) : void {
|
|
|
|
this.lineno++;
|
|
|
|
this.tokens = []; // can't have errors until this is set
|
|
|
|
this.eol = { type: TokenType.EOL, str: "", $loc: { path: this.path, line: this.lineno, start: line.length } };
|
|
|
|
if (line.length > this.maxlinelen) this.compileError(`A line should be no more than ${this.maxlinelen} characters long.`);
|
|
|
|
this._tokenize(line);
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
2020-08-19 17:05:30 +00:00
|
|
|
parse() : void {
|
2020-08-05 04:48:29 +00:00
|
|
|
// not empty line?
|
|
|
|
if (this.tokens.length) {
|
2020-08-19 17:05:30 +00:00
|
|
|
this.parseOptLabel();
|
2020-08-09 01:03:48 +00:00
|
|
|
if (this.tokens.length) {
|
2020-08-19 17:05:30 +00:00
|
|
|
this.parseCompoundStatement();
|
2020-08-09 01:03:48 +00:00
|
|
|
}
|
2020-08-19 17:05:30 +00:00
|
|
|
var next = this.peekToken();
|
|
|
|
if (!isEOS(next)) this.compileError(`Expected end of line or ':'`, next.$loc);
|
2020-08-10 01:40:17 +00:00
|
|
|
this.curlabel = null;
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
2020-08-19 17:05:30 +00:00
|
|
|
}
|
|
|
|
parseCompoundStatement() : void {
|
|
|
|
if (this.opts.multipleStmtsPerLine) {
|
|
|
|
this.parseList(this.parseStatement, ':');
|
|
|
|
} else {
|
|
|
|
this.parseList(this.parseStatement, '\0');
|
|
|
|
if (this.peekToken().str == ':') this.dialectErrorNoSupport(`multiple statements on a line`);
|
|
|
|
}
|
2020-08-09 02:36:21 +00:00
|
|
|
}
|
|
|
|
validKeyword(keyword: string) : string {
|
|
|
|
return (this.opts.validKeywords && this.opts.validKeywords.indexOf(keyword) < 0) ? null : keyword;
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
2020-08-20 17:58:14 +00:00
|
|
|
validFunction(funcname: string) : string {
|
|
|
|
return (this.opts.validFunctions && this.opts.validFunctions.indexOf(funcname) < 0) ? null : funcname;
|
|
|
|
}
|
2020-08-16 17:16:29 +00:00
|
|
|
supportsCommand(cmd: string) : () => Statement {
|
|
|
|
if (cmd == '?') return this.stmt__PRINT;
|
|
|
|
else return this['stmt__' + cmd];
|
2020-08-15 11:53:13 +00:00
|
|
|
}
|
2020-08-05 04:48:29 +00:00
|
|
|
parseStatement(): Statement | null {
|
2020-08-15 20:03:56 +00:00
|
|
|
// eat extra ":" (should have separate property for this)
|
|
|
|
if (this.opts.optionalWhitespace && this.peekToken().str == ':') return null;
|
|
|
|
// get the command word
|
2020-08-05 04:48:29 +00:00
|
|
|
var cmdtok = this.consumeToken();
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
var cmd = cmdtok.str;
|
2020-08-19 01:06:23 +00:00
|
|
|
var stmt : Statement;
|
2020-08-05 04:48:29 +00:00
|
|
|
switch (cmdtok.type) {
|
|
|
|
case TokenType.Remark:
|
2020-08-19 01:06:23 +00:00
|
|
|
if (cmdtok.str.startsWith("'") && !this.opts.tickComments)
|
|
|
|
this.dialectErrorNoSupport(`tick comments`);
|
2020-08-05 04:48:29 +00:00
|
|
|
return null;
|
2020-08-09 02:36:21 +00:00
|
|
|
case TokenType.Operator:
|
2020-08-15 20:03:56 +00:00
|
|
|
// "?" is alias for "PRINT" on some platforms
|
2020-08-09 02:36:21 +00:00
|
|
|
if (cmd == this.validKeyword('?')) cmd = 'PRINT';
|
2020-08-05 04:48:29 +00:00
|
|
|
case TokenType.Ident:
|
2020-08-14 04:34:54 +00:00
|
|
|
// ignore remarks
|
|
|
|
if (cmd == 'REM') return null;
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
// look for "GO TO" and "GO SUB"
|
2020-08-05 04:48:29 +00:00
|
|
|
if (cmd == 'GO' && this.peekToken().str == 'TO') {
|
|
|
|
this.consumeToken();
|
|
|
|
cmd = 'GOTO';
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
} else if (cmd == 'GO' && this.peekToken().str == 'SUB') {
|
|
|
|
this.consumeToken();
|
|
|
|
cmd = 'GOSUB';
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
// lookup JS function for command
|
2020-08-16 17:16:29 +00:00
|
|
|
var fn = this.supportsCommand(cmd);
|
2020-08-05 04:48:29 +00:00
|
|
|
if (fn) {
|
2020-08-09 02:36:21 +00:00
|
|
|
if (this.validKeyword(cmd) == null)
|
2020-08-19 01:06:23 +00:00
|
|
|
this.dialectErrorNoSupport(`the ${cmd} statement`);
|
2020-08-16 17:16:29 +00:00
|
|
|
stmt = fn.bind(this)();
|
2020-08-05 04:48:29 +00:00
|
|
|
break;
|
|
|
|
} else if (this.peekToken().str == '=' || this.peekToken().str == '(') {
|
2020-08-16 17:16:29 +00:00
|
|
|
if (!this.opts.optionalLet)
|
2020-08-19 01:06:23 +00:00
|
|
|
this.dialectError(`Assignments must have a preceding LET`);
|
2020-08-05 04:48:29 +00:00
|
|
|
// 'A = expr' or 'A(X) = expr'
|
|
|
|
this.pushbackToken(cmdtok);
|
|
|
|
stmt = this.stmt__LET();
|
|
|
|
break;
|
2020-08-15 20:03:56 +00:00
|
|
|
} else {
|
|
|
|
this.compileError(`I don't understand the command "${cmd}".`);
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
case TokenType.EOL:
|
2020-08-15 20:03:56 +00:00
|
|
|
if (this.opts.optionalWhitespace) return null;
|
2020-08-05 04:48:29 +00:00
|
|
|
default:
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
this.compileError(`There should be a command here.`);
|
2020-08-05 04:48:29 +00:00
|
|
|
return null;
|
|
|
|
}
|
2020-08-19 17:05:30 +00:00
|
|
|
// add statement to list
|
|
|
|
if (stmt != null) this.addStatement(stmt, cmdtok);
|
2020-08-05 04:48:29 +00:00
|
|
|
return stmt;
|
|
|
|
}
|
2020-08-19 01:06:23 +00:00
|
|
|
// check scope stuff (if compiledBlocks is true)
|
|
|
|
modifyScope(stmt: Statement) {
|
|
|
|
if (this.opts.compiledBlocks) {
|
|
|
|
var cmd = stmt.command;
|
2020-08-24 16:51:47 +00:00
|
|
|
if (cmd == 'FOR' || cmd == 'WHILE' || cmd == 'SUB') {
|
2020-08-20 19:56:28 +00:00
|
|
|
this.scopestack.push(this.getPC()); // has to be before adding statment to list
|
2020-08-19 01:06:23 +00:00
|
|
|
} else if (cmd == 'NEXT') {
|
|
|
|
this.popScope(stmt as NEXT_Statement, 'FOR');
|
|
|
|
} else if (cmd == 'WEND') {
|
|
|
|
this.popScope(stmt as WEND_Statement, 'WHILE');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-24 16:51:47 +00:00
|
|
|
popScope(close: WEND_Statement|NEXT_Statement|END_Statement, open: string) {
|
2020-08-20 19:56:28 +00:00
|
|
|
var popidx = this.scopestack.pop();
|
|
|
|
var popstmt : ScopeStartStatement = popidx != null ? this.stmts[popidx] : null;
|
2020-08-19 01:06:23 +00:00
|
|
|
if (popstmt == null)
|
2020-08-22 16:40:58 +00:00
|
|
|
this.compileError(`There's a ${close.command} without a matching ${open}.`, close.$loc);
|
2020-08-19 01:06:23 +00:00
|
|
|
else if (popstmt.command != open)
|
2020-08-24 16:51:47 +00:00
|
|
|
this.compileError(`There's a ${close.command} paired with ${popstmt.command}, but it should be paired with ${open}.`, close.$loc, popstmt.$loc);
|
2020-08-20 19:56:28 +00:00
|
|
|
else if (close.command == 'NEXT' && !this.opts.optionalNextVar
|
|
|
|
&& close.lexpr.name != (popstmt as FOR_Statement).lexpr.name)
|
2020-08-24 16:51:47 +00:00
|
|
|
this.compileError(`This NEXT statement is matched with the wrong FOR variable (${close.lexpr.name}).`, close.$loc, popstmt.$loc);
|
2020-08-20 19:56:28 +00:00
|
|
|
// set start + end locations
|
|
|
|
close.startpc = popidx;
|
|
|
|
popstmt.endpc = this.getPC(); // has to be before adding statment to list
|
2020-08-19 01:06:23 +00:00
|
|
|
}
|
2020-08-24 16:51:47 +00:00
|
|
|
popIfThenScope(nextpc?: number) {
|
|
|
|
var popidx = this.scopestack.pop();
|
|
|
|
var popstmt : ScopeStartStatement = popidx != null ? this.stmts[popidx] : null;
|
|
|
|
if (popstmt == null)
|
|
|
|
this.compileError(`There's an END IF without a matching IF or ELSE.`);
|
|
|
|
if (popstmt.command == 'ELSE') {
|
|
|
|
popstmt.endpc = this.getPC();
|
|
|
|
this.popIfThenScope(popidx + 1); // IF goes to ELSE+1
|
|
|
|
} else if (popstmt.command == 'IF') {
|
|
|
|
popstmt.endpc = nextpc != null ? nextpc : this.getPC();
|
|
|
|
} else {
|
|
|
|
this.compileError(`There's an END IF paired with a ${popstmt.command}, not IF or ELSE.`, this.lasttoken.$loc, popstmt.$loc);
|
|
|
|
}
|
|
|
|
}
|
2020-08-09 01:25:58 +00:00
|
|
|
parseVarSubscriptOrFunc(): IndOp {
|
2020-08-05 04:48:29 +00:00
|
|
|
var tok = this.consumeToken();
|
|
|
|
switch (tok.type) {
|
|
|
|
case TokenType.Ident:
|
|
|
|
let args = null;
|
|
|
|
if (this.peekToken().str == '(') {
|
|
|
|
this.expectToken('(');
|
|
|
|
args = this.parseExprList();
|
2020-08-09 16:23:49 +00:00
|
|
|
this.expectToken(')', `There should be another expression or a ")" here.`);
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
2020-08-22 16:40:58 +00:00
|
|
|
var loc = mergeLocs(tok.$loc, this.lasttoken.$loc);
|
|
|
|
var valtype = this.exprTypeForSubscript(tok.str, args, loc);
|
|
|
|
return { valtype: valtype, name: tok.str, args: args, $loc:loc };
|
2020-08-05 04:48:29 +00:00
|
|
|
default:
|
2020-08-09 01:25:58 +00:00
|
|
|
this.compileError(`There should be a variable name here.`);
|
2020-08-05 04:48:29 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2020-08-09 01:25:58 +00:00
|
|
|
parseLexpr(): IndOp {
|
|
|
|
var lexpr = this.parseVarSubscriptOrFunc();
|
2020-08-20 17:58:14 +00:00
|
|
|
this.vardefs[lexpr.name] = lexpr;
|
2020-08-09 01:25:58 +00:00
|
|
|
this.validateVarName(lexpr);
|
|
|
|
return lexpr;
|
|
|
|
}
|
2020-08-11 17:29:31 +00:00
|
|
|
parseForNextLexpr() : IndOp {
|
|
|
|
var lexpr = this.parseLexpr();
|
|
|
|
if (lexpr.args || lexpr.name.endsWith('$'))
|
2020-08-22 16:40:58 +00:00
|
|
|
this.compileError(`A FOR ... NEXT loop can only use numeric variables.`, lexpr.$loc);
|
2020-08-11 17:29:31 +00:00
|
|
|
return lexpr;
|
|
|
|
}
|
2020-08-05 04:48:29 +00:00
|
|
|
parseList<T>(parseFunc:()=>T, delim:string): T[] {
|
|
|
|
var sep;
|
|
|
|
var list = [];
|
|
|
|
do {
|
2020-08-09 01:25:58 +00:00
|
|
|
var el = parseFunc.bind(this)(); // call parse function
|
|
|
|
if (el != null) list.push(el); // add parsed element to list
|
|
|
|
sep = this.consumeToken(); // consume seperator token
|
2020-08-05 04:48:29 +00:00
|
|
|
} while (sep.str == delim);
|
|
|
|
this.pushbackToken(sep);
|
|
|
|
return list;
|
|
|
|
}
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
parseLexprList(): IndOp[] {
|
2020-08-13 17:51:35 +00:00
|
|
|
return this.parseList(this.parseLexpr, ',');
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
parseExprList(): Expr[] {
|
|
|
|
return this.parseList(this.parseExpr, ',');
|
|
|
|
}
|
|
|
|
parseLabelList(): Expr[] {
|
|
|
|
return this.parseList(this.parseLabel, ',');
|
|
|
|
}
|
|
|
|
parseLabel() : Expr {
|
2020-08-13 17:51:35 +00:00
|
|
|
// parse full expr?
|
2020-08-14 14:26:43 +00:00
|
|
|
if (this.opts.computedGoto) {
|
|
|
|
// parse expression, but still add to list of label targets if constant
|
|
|
|
var expr = this.parseExpr();
|
2020-08-16 17:16:29 +00:00
|
|
|
if (isLiteral(expr)) this.targets[expr.value] = this.lasttoken.$loc;
|
2020-08-14 14:26:43 +00:00
|
|
|
return expr;
|
2020-08-16 17:16:29 +00:00
|
|
|
} else {
|
|
|
|
// parse a single number or ident label
|
|
|
|
var tok = this.consumeToken();
|
|
|
|
switch (tok.type) {
|
|
|
|
case TokenType.Ident:
|
2020-08-19 01:06:23 +00:00
|
|
|
if (!this.opts.optionalLabels)
|
|
|
|
this.dialectError(`All labels must be line numbers`)
|
2020-08-16 17:16:29 +00:00
|
|
|
case TokenType.Int:
|
|
|
|
var label = tok.str;
|
|
|
|
this.targets[label] = tok.$loc;
|
2020-08-20 17:58:14 +00:00
|
|
|
return {valtype:'label', value:label};
|
2020-08-16 17:16:29 +00:00
|
|
|
default:
|
|
|
|
var what = this.opts.optionalLabels ? "label or line number" : "line number";
|
|
|
|
this.compileError(`There should be a ${what} here.`);
|
|
|
|
}
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
}
|
2020-08-14 04:34:54 +00:00
|
|
|
parseDatumList(): Literal[] {
|
|
|
|
return this.parseList(this.parseDatum, ',');
|
|
|
|
}
|
|
|
|
parseDatum(): Literal {
|
|
|
|
var tok = this.consumeToken();
|
|
|
|
// get rid of leading whitespace
|
|
|
|
while (tok.type == TokenType.Whitespace)
|
|
|
|
tok = this.consumeToken();
|
|
|
|
if (isEOS(tok)) this.compileError(`There should be a datum here.`);
|
|
|
|
// parse constants
|
|
|
|
if (tok.type <= TokenType.HexOctalInt) {
|
|
|
|
return this.parseValue(tok);
|
|
|
|
}
|
|
|
|
if (tok.str == '-' && this.peekToken().type <= TokenType.HexOctalInt) {
|
|
|
|
tok = this.consumeToken();
|
2020-08-20 17:58:14 +00:00
|
|
|
return { valtype:'number', value: -this.parseValue(tok).value };
|
2020-08-14 04:34:54 +00:00
|
|
|
}
|
|
|
|
if (tok.str == '+' && this.peekToken().type <= TokenType.HexOctalInt) {
|
|
|
|
tok = this.consumeToken();
|
|
|
|
return this.parseValue(tok);
|
|
|
|
}
|
|
|
|
// concat all stuff including whitespace
|
|
|
|
// TODO: should trim whitespace only if not quoted string
|
|
|
|
var s = '';
|
|
|
|
while (!isEOS(tok) && tok.str != ',') {
|
|
|
|
s += this.parseValue(tok).value;
|
|
|
|
tok = this.consumeToken();
|
|
|
|
}
|
|
|
|
this.pushbackToken(tok);
|
2020-08-20 17:58:14 +00:00
|
|
|
return { valtype:'string', value: s }; // trim leading and trailing whitespace
|
2020-08-14 04:34:54 +00:00
|
|
|
}
|
|
|
|
parseValue(tok: Token): Literal {
|
2020-08-05 04:48:29 +00:00
|
|
|
switch (tok.type) {
|
2020-08-12 15:51:18 +00:00
|
|
|
case TokenType.HexOctalInt:
|
2020-08-19 01:06:23 +00:00
|
|
|
if (!this.opts.hexOctalConsts)
|
|
|
|
this.dialectErrorNoSupport(`hex/octal constants`);
|
2020-08-12 15:51:18 +00:00
|
|
|
let base = tok.str.startsWith('H') ? 16 : 8;
|
2020-08-20 17:58:14 +00:00
|
|
|
return { valtype:'number', value: parseInt(tok.str.substr(1), base) };
|
2020-08-05 04:48:29 +00:00
|
|
|
case TokenType.Int:
|
2020-08-14 04:34:54 +00:00
|
|
|
case TokenType.Float:
|
2020-08-20 17:58:14 +00:00
|
|
|
return { valtype:'number', value: this.parseNumber(tok.str) };
|
2020-08-05 04:48:29 +00:00
|
|
|
case TokenType.String:
|
2020-08-20 17:58:14 +00:00
|
|
|
return { valtype:'string', value: stripQuotes(tok.str) };
|
2020-08-14 04:34:54 +00:00
|
|
|
default:
|
2020-08-20 17:58:14 +00:00
|
|
|
return { valtype:'string', value: tok.str }; // only used in DATA statement
|
2020-08-14 04:34:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
parsePrimary(): Expr {
|
|
|
|
let tok = this.consumeToken();
|
|
|
|
switch (tok.type) {
|
|
|
|
case TokenType.HexOctalInt:
|
|
|
|
case TokenType.Int:
|
|
|
|
case TokenType.Float:
|
|
|
|
case TokenType.String:
|
|
|
|
return this.parseValue(tok);
|
2020-08-05 04:48:29 +00:00
|
|
|
case TokenType.Ident:
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
if (tok.str == 'NOT') {
|
|
|
|
let expr = this.parsePrimary();
|
2020-08-20 17:58:14 +00:00
|
|
|
return { valtype:'number', op: this.opts.bitwiseLogic ? 'bnot' : 'lnot', expr: expr };
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
} else {
|
|
|
|
this.pushbackToken(tok);
|
2020-08-09 01:25:58 +00:00
|
|
|
return this.parseVarSubscriptOrFunc();
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
}
|
2020-08-05 04:48:29 +00:00
|
|
|
case TokenType.Operator:
|
|
|
|
if (tok.str == '(') {
|
|
|
|
let expr = this.parseExpr();
|
2020-08-09 16:23:49 +00:00
|
|
|
this.expectToken(')', `There should be another expression or a ")" here.`);
|
2020-08-05 04:48:29 +00:00
|
|
|
return expr;
|
|
|
|
} else if (tok.str == '-') {
|
|
|
|
let expr = this.parsePrimary(); // TODO: -2^2=-4 and -2-2=-4
|
2020-08-20 17:58:14 +00:00
|
|
|
return { valtype:'number', op: 'neg', expr: expr };
|
2020-08-05 04:48:29 +00:00
|
|
|
} else if (tok.str == '+') {
|
2020-08-09 16:23:49 +00:00
|
|
|
return this.parsePrimary(); // ignore unary +
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
2020-08-13 17:51:35 +00:00
|
|
|
default:
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
this.compileError(`The expression is incomplete.`);
|
|
|
|
return;
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
}
|
|
|
|
parseNumber(str: string) : number {
|
|
|
|
var n = parseFloat(str);
|
|
|
|
if (isNaN(n))
|
|
|
|
this.compileError(`The number ${str} is not a valid floating-point number.`);
|
|
|
|
if (this.opts.checkOverflow && !isFinite(n))
|
|
|
|
this.compileError(`The number ${str} is too big to fit into a floating-point value.`);
|
|
|
|
return n;
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
parseExpr1(left: Expr, minPred: number): Expr {
|
|
|
|
let look = this.peekToken();
|
|
|
|
while (getPrecedence(look) >= minPred) {
|
|
|
|
let op = this.consumeToken();
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
if (this.opts.validOperators && this.opts.validOperators.indexOf(op.str) < 0)
|
2020-08-19 01:06:23 +00:00
|
|
|
this.dialectErrorNoSupport(`the "${op.str}" operator`);
|
2020-08-05 04:48:29 +00:00
|
|
|
let right: Expr = this.parsePrimary();
|
|
|
|
look = this.peekToken();
|
|
|
|
while (getPrecedence(look) > getPrecedence(op)) {
|
|
|
|
right = this.parseExpr1(right, getPrecedence(look));
|
|
|
|
look = this.peekToken();
|
|
|
|
}
|
2020-08-09 16:23:49 +00:00
|
|
|
var opfn = getOperator(op.str).f;
|
2020-08-11 17:29:31 +00:00
|
|
|
// use logical operators instead of bitwise?
|
|
|
|
if (!this.opts.bitwiseLogic && op.str == 'AND') opfn = 'land';
|
|
|
|
if (!this.opts.bitwiseLogic && op.str == 'OR') opfn = 'lor';
|
2020-08-22 16:40:58 +00:00
|
|
|
var valtype = this.exprTypeForOp(opfn, left, right, op);
|
2020-08-20 17:58:14 +00:00
|
|
|
left = { valtype:valtype, op:opfn, left: left, right: right };
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
return left;
|
|
|
|
}
|
|
|
|
parseExpr(): Expr {
|
2020-08-22 16:40:58 +00:00
|
|
|
var startloc = this.peekToken().$loc;
|
|
|
|
var expr = this.parseExpr1(this.parsePrimary(), 0);
|
|
|
|
var endloc = this.lasttoken.$loc;
|
|
|
|
expr.$loc = mergeLocs(startloc, endloc);
|
|
|
|
return expr;
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
2020-08-20 17:58:14 +00:00
|
|
|
parseExprWithType(expecttype: ValueType): Expr {
|
|
|
|
var expr = this.parseExpr();
|
|
|
|
if (expr.valtype != expecttype)
|
|
|
|
this.compileError(`There should be a ${expecttype} here, but this expression evaluates to a ${expr.valtype}.`, expr.$loc);
|
|
|
|
return expr;
|
|
|
|
}
|
2020-08-09 01:25:58 +00:00
|
|
|
validateVarName(lexpr: IndOp) {
|
2020-08-11 17:29:31 +00:00
|
|
|
switch (this.opts.varNaming) {
|
2020-08-13 17:51:35 +00:00
|
|
|
case 'A': // TINY BASIC, no strings
|
|
|
|
if (!/^[A-Z]$/i.test(lexpr.name))
|
2020-08-19 01:06:23 +00:00
|
|
|
this.dialectErrorNoSupport(`variable names other than a single letter`);
|
2020-08-13 17:51:35 +00:00
|
|
|
break;
|
2020-08-11 17:29:31 +00:00
|
|
|
case 'A1':
|
|
|
|
if (lexpr.args == null && !/^[A-Z][0-9]?[$]?$/i.test(lexpr.name))
|
2020-08-19 01:06:23 +00:00
|
|
|
this.dialectErrorNoSupport(`variable names other than a letter followed by an optional digit`);
|
2020-08-11 17:29:31 +00:00
|
|
|
if (lexpr.args != null && !/^[A-Z]?[$]?$/i.test(lexpr.name))
|
2020-08-19 01:06:23 +00:00
|
|
|
this.dialectErrorNoSupport(`array names other than a single letter`);
|
2020-08-11 17:29:31 +00:00
|
|
|
break;
|
2020-08-21 03:20:53 +00:00
|
|
|
case 'A1$':
|
|
|
|
if (!/^[A-Z][0-9]?[$]?$/i.test(lexpr.name))
|
|
|
|
this.dialectErrorNoSupport(`variable names other than a letter followed by an optional digit`);
|
|
|
|
break;
|
2020-08-11 17:29:31 +00:00
|
|
|
case 'AA':
|
|
|
|
if (lexpr.args == null && !/^[A-Z][A-Z0-9]?[$]?$/i.test(lexpr.name))
|
2020-08-19 01:06:23 +00:00
|
|
|
this.dialectErrorNoSupport(`variable names other than a letter followed by an optional letter or digit`);
|
2020-08-11 17:29:31 +00:00
|
|
|
break;
|
2020-08-13 17:51:35 +00:00
|
|
|
case '*':
|
|
|
|
break;
|
2020-08-09 01:25:58 +00:00
|
|
|
}
|
|
|
|
}
|
2020-08-15 20:03:56 +00:00
|
|
|
visitExpr(expr: Expr, callback: (expr:Expr) => void) {
|
|
|
|
if (isBinOp(expr)) {
|
|
|
|
this.visitExpr(expr.left, callback);
|
|
|
|
this.visitExpr(expr.right, callback);
|
|
|
|
}
|
|
|
|
if (isUnOp(expr)) {
|
|
|
|
this.visitExpr(expr.expr, callback);
|
|
|
|
}
|
|
|
|
if (isLookup(expr) && expr.args != null) {
|
|
|
|
for (var arg of expr.args)
|
|
|
|
this.visitExpr(arg, callback);
|
|
|
|
}
|
|
|
|
callback(expr);
|
|
|
|
}
|
2020-08-20 17:58:14 +00:00
|
|
|
// type-checking
|
2020-08-22 16:40:58 +00:00
|
|
|
exprTypeForOp(fnname: string, left: Expr, right: Expr, optok: Token) : ValueType {
|
|
|
|
if (left.valtype == 'string' || right.valtype == 'string') {
|
|
|
|
if (fnname == 'add') {
|
|
|
|
if (this.opts.stringConcat) return 'string' // concat strings
|
|
|
|
else this.dialectErrorNoSupport(`the "+" operator to concatenate strings`, optok.$loc);
|
|
|
|
} else if (fnname.length != 2) // only relops are 2 chars long!
|
|
|
|
this.compileError(`You can't do math on strings until they're converted to numbers.`, optok.$loc);
|
2020-08-20 17:58:14 +00:00
|
|
|
}
|
2020-08-22 16:40:58 +00:00
|
|
|
return 'number';
|
2020-08-20 17:58:14 +00:00
|
|
|
}
|
2020-08-22 16:40:58 +00:00
|
|
|
exprTypeForSubscript(fnname: string, args: Expr[], loc: SourceLocation) : ValueType {
|
2020-08-20 17:58:14 +00:00
|
|
|
args = args || [];
|
|
|
|
// first check the built-in functions
|
|
|
|
var defs = BUILTIN_MAP[fnname];
|
|
|
|
if (defs != null) {
|
2020-08-22 16:40:58 +00:00
|
|
|
if (!this.validFunction(fnname)) this.dialectErrorNoSupport(`the ${fnname} function`, loc);
|
2020-08-20 17:58:14 +00:00
|
|
|
for (var def of defs) {
|
|
|
|
if (args.length == def.args.length)
|
2020-08-22 16:40:58 +00:00
|
|
|
return def.result; // TODO: check arg types
|
2020-08-20 17:58:14 +00:00
|
|
|
}
|
|
|
|
// TODO: check func arg types
|
2020-08-22 16:40:58 +00:00
|
|
|
this.compileError(`The ${fnname} function takes ${def.args.length} arguments, but ${args.length} are given.`, loc);
|
2020-08-20 17:58:14 +00:00
|
|
|
}
|
|
|
|
// no function found, assume it's an array ref
|
|
|
|
// TODO: validateVarName() later?
|
2020-08-22 16:40:58 +00:00
|
|
|
this.varrefs[fnname] = loc;
|
2020-08-20 17:58:14 +00:00
|
|
|
return fnname.endsWith('$') ? 'string' : 'number';
|
|
|
|
}
|
2020-08-05 04:48:29 +00:00
|
|
|
|
|
|
|
//// STATEMENTS
|
|
|
|
|
|
|
|
stmt__LET(): LET_Statement {
|
2020-08-15 11:53:13 +00:00
|
|
|
var lexprs = [ this.parseLexpr() ];
|
2020-08-05 04:48:29 +00:00
|
|
|
this.expectToken("=");
|
2020-08-15 11:53:13 +00:00
|
|
|
// look for A=B=expr (TODO: doesn't work on arrays)
|
|
|
|
while (this.opts.chainAssignments && this.peekToken().type == TokenType.Ident && this.peekToken(1).str == '=') {
|
|
|
|
lexprs.push(this.parseLexpr());
|
|
|
|
this.expectToken("=");
|
|
|
|
}
|
2020-08-20 17:58:14 +00:00
|
|
|
var right = this.parseExprWithType(lexprs[0].valtype);
|
2020-08-15 11:53:13 +00:00
|
|
|
return { command: "LET", lexprs: lexprs, right: right };
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
stmt__PRINT(): PRINT_Statement {
|
|
|
|
var sep, lastsep;
|
|
|
|
var list = [];
|
|
|
|
do {
|
|
|
|
sep = this.peekToken();
|
|
|
|
if (isEOS(sep)) {
|
|
|
|
break;
|
|
|
|
} else if (sep.str == ';') {
|
|
|
|
this.consumeToken();
|
|
|
|
lastsep = sep;
|
|
|
|
} else if (sep.str == ',') {
|
|
|
|
this.consumeToken();
|
|
|
|
list.push({value:'\t'});
|
|
|
|
lastsep = sep;
|
|
|
|
} else {
|
|
|
|
list.push(this.parseExpr());
|
|
|
|
lastsep = null;
|
|
|
|
}
|
|
|
|
} while (true);
|
|
|
|
if (!(lastsep && (lastsep.str == ';' || sep.str != ','))) {
|
|
|
|
list.push({value:'\n'});
|
|
|
|
}
|
|
|
|
return { command: "PRINT", args: list };
|
|
|
|
}
|
2020-08-13 17:51:35 +00:00
|
|
|
stmt__GOTO(): GOTO_Statement | GOSUB_Statement | ONGO_Statement {
|
|
|
|
return this.__GO("GOTO");
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
2020-08-13 17:51:35 +00:00
|
|
|
stmt__GOSUB(): GOTO_Statement | GOSUB_Statement | ONGO_Statement {
|
|
|
|
return this.__GO("GOSUB");
|
|
|
|
}
|
|
|
|
__GO(cmd: "GOTO"|"GOSUB"): GOTO_Statement | GOSUB_Statement | ONGO_Statement {
|
|
|
|
var expr = this.parseLabel();
|
|
|
|
// GOTO (expr) OF (labels...)
|
2020-08-16 17:16:29 +00:00
|
|
|
if (this.peekToken().str == this.validKeyword('OF')) {
|
2020-08-13 17:51:35 +00:00
|
|
|
this.expectToken('OF');
|
|
|
|
let newcmd : 'ONGOTO'|'ONGOSUB' = (cmd == 'GOTO') ? 'ONGOTO' : 'ONGOSUB';
|
2020-08-16 17:16:29 +00:00
|
|
|
return { command: newcmd, expr: expr, labels: this.parseLabelList() };
|
|
|
|
} else {
|
|
|
|
// regular GOTO or GOSUB
|
|
|
|
return { command: cmd, label: expr };
|
2020-08-13 17:51:35 +00:00
|
|
|
}
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
2020-08-19 17:05:30 +00:00
|
|
|
stmt__IF(): void {
|
|
|
|
var cmdtok = this.lasttoken;
|
2020-08-22 16:40:58 +00:00
|
|
|
var cond = this.parseExprWithType("number");
|
2020-08-20 19:56:28 +00:00
|
|
|
var ifstmt : IF_Statement = { command: "IF", cond: cond };
|
2020-08-19 17:05:30 +00:00
|
|
|
this.addStatement(ifstmt, cmdtok);
|
2020-08-15 11:53:13 +00:00
|
|
|
// we accept GOTO or THEN if line number provided (DEC accepts GO TO)
|
|
|
|
var thengoto = this.expectTokens(['THEN','GOTO','GO']);
|
|
|
|
if (thengoto.str == 'GO') this.expectToken('TO');
|
2020-08-24 16:51:47 +00:00
|
|
|
// multiline IF .. THEN? push it to scope stack
|
|
|
|
if (this.opts.multilineIfThen && isEOS(this.peekToken())) {
|
|
|
|
this.scopestack.push(this.getPC() - 1); // we already added stmt to list, so - 1
|
2020-08-20 19:56:28 +00:00
|
|
|
} else {
|
2020-08-24 16:51:47 +00:00
|
|
|
// parse line number or statement clause
|
|
|
|
this.parseGotoOrStatements();
|
|
|
|
// is the next statement an ELSE?
|
|
|
|
// gotta parse it now because it's an end-of-statement token
|
|
|
|
if (this.peekToken().str == 'ELSE') {
|
|
|
|
this.expectToken('ELSE');
|
|
|
|
ifstmt.endpc = this.getPC() + 1;
|
|
|
|
this.stmt__ELSE();
|
|
|
|
} else {
|
|
|
|
ifstmt.endpc = this.getPC();
|
|
|
|
}
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
}
|
2020-08-19 17:05:30 +00:00
|
|
|
stmt__ELSE(): void {
|
2020-08-20 19:56:28 +00:00
|
|
|
var elsestmt : ELSE_Statement = { command: "ELSE" };
|
|
|
|
this.addStatement(elsestmt, this.lasttoken);
|
2020-08-24 16:51:47 +00:00
|
|
|
// multiline ELSE? or ELSE IF?
|
|
|
|
var nexttok = this.peekToken();
|
|
|
|
if (this.opts.multilineIfThen && isEOS(nexttok)) {
|
|
|
|
this.scopestack.push(this.getPC() - 1); // we already added stmt to list, so - 1
|
|
|
|
} else if (this.opts.multilineIfThen && nexttok.str == 'IF') {
|
|
|
|
this.scopestack.push(this.getPC() - 1); // we already added stmt to list, so - 1
|
|
|
|
this.parseGotoOrStatements();
|
|
|
|
this.elseifcount++;
|
|
|
|
} else {
|
|
|
|
// parse line number or statement clause
|
|
|
|
this.parseGotoOrStatements();
|
|
|
|
elsestmt.endpc = this.getPC();
|
|
|
|
}
|
2020-08-19 17:05:30 +00:00
|
|
|
}
|
|
|
|
parseGotoOrStatements() {
|
2020-08-09 02:36:21 +00:00
|
|
|
var lineno = this.peekToken();
|
2020-08-19 17:05:30 +00:00
|
|
|
// assume GOTO if number given after THEN
|
2020-08-09 02:36:21 +00:00
|
|
|
if (lineno.type == TokenType.Int) {
|
2020-08-19 17:05:30 +00:00
|
|
|
this.parseLabel();
|
2020-08-20 17:58:14 +00:00
|
|
|
var gotostmt : GOTO_Statement = { command:'GOTO', label: {valtype:'label', value:lineno.str} }
|
2020-08-19 17:05:30 +00:00
|
|
|
this.addStatement(gotostmt, lineno);
|
|
|
|
} else {
|
|
|
|
// parse rest of IF clause
|
|
|
|
this.parseCompoundStatement();
|
2020-08-09 02:36:21 +00:00
|
|
|
}
|
|
|
|
}
|
2020-08-05 04:48:29 +00:00
|
|
|
stmt__FOR() : FOR_Statement {
|
2020-08-11 17:29:31 +00:00
|
|
|
var lexpr = this.parseForNextLexpr();
|
2020-08-05 04:48:29 +00:00
|
|
|
this.expectToken('=');
|
2020-08-22 16:40:58 +00:00
|
|
|
var init = this.parseExprWithType("number");
|
2020-08-05 04:48:29 +00:00
|
|
|
this.expectToken('TO');
|
2020-08-22 16:40:58 +00:00
|
|
|
var targ = this.parseExprWithType("number");
|
2020-08-05 04:48:29 +00:00
|
|
|
if (this.peekToken().str == 'STEP') {
|
|
|
|
this.consumeToken();
|
2020-08-22 16:40:58 +00:00
|
|
|
var step = this.parseExprWithType("number");
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
return { command:'FOR', lexpr:lexpr, initial:init, target:targ, step:step };
|
|
|
|
}
|
|
|
|
stmt__NEXT() : NEXT_Statement {
|
|
|
|
var lexpr = null;
|
2020-08-12 16:57:54 +00:00
|
|
|
// NEXT var might be optional
|
|
|
|
if (!this.opts.optionalNextVar || !isEOS(this.peekToken())) {
|
2020-08-11 17:29:31 +00:00
|
|
|
lexpr = this.parseForNextLexpr();
|
2020-08-12 16:57:54 +00:00
|
|
|
// convert ',' to ':' 'NEXT'
|
|
|
|
if (this.opts.multipleNextVars && this.peekToken().str == ',') {
|
|
|
|
this.consumeToken(); // consume ','
|
|
|
|
this.tokens.unshift({type:TokenType.Ident, str:'NEXT', $loc:this.peekToken().$loc});
|
|
|
|
this.tokens.unshift({type:TokenType.Operator, str:':', $loc:this.peekToken().$loc});
|
|
|
|
}
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
return { command:'NEXT', lexpr:lexpr };
|
|
|
|
}
|
2020-08-12 16:57:54 +00:00
|
|
|
stmt__WHILE(): WHILE_Statement {
|
2020-08-22 16:40:58 +00:00
|
|
|
var cond = this.parseExprWithType("number");
|
2020-08-12 16:57:54 +00:00
|
|
|
return { command:'WHILE', cond:cond };
|
|
|
|
}
|
|
|
|
stmt__WEND(): WEND_Statement {
|
|
|
|
return { command:'WEND' };
|
|
|
|
}
|
2020-08-05 04:48:29 +00:00
|
|
|
stmt__DIM() : DIM_Statement {
|
2020-08-09 01:25:58 +00:00
|
|
|
var lexprs = this.parseLexprList();
|
|
|
|
lexprs.forEach((arr) => {
|
|
|
|
if (arr.args == null || arr.args.length == 0)
|
|
|
|
this.compileError(`An array defined by DIM must have at least one dimension.`)
|
|
|
|
else if (arr.args.length > this.opts.maxDimensions)
|
2020-08-19 01:06:23 +00:00
|
|
|
this.dialectErrorNoSupport(`arrays with more than ${this.opts.maxDimensions} dimensionals`);
|
2020-08-15 20:03:56 +00:00
|
|
|
for (var arrdim of arr.args) {
|
2020-08-22 16:40:58 +00:00
|
|
|
if (arrdim.valtype != 'number')
|
|
|
|
this.compileError(`Array dimensions must be numeric.`, arrdim.$loc);
|
2020-08-15 20:03:56 +00:00
|
|
|
if (isLiteral(arrdim) && arrdim.value < this.opts.defaultArrayBase)
|
2020-08-22 16:40:58 +00:00
|
|
|
this.compileError(`An array dimension cannot be less than ${this.opts.defaultArrayBase}.`, arrdim.$loc);
|
2020-08-15 20:03:56 +00:00
|
|
|
}
|
2020-08-09 01:25:58 +00:00
|
|
|
});
|
|
|
|
return { command:'DIM', args:lexprs };
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
stmt__INPUT() : INPUT_Statement {
|
|
|
|
var prompt = this.consumeToken();
|
|
|
|
var promptstr;
|
|
|
|
if (prompt.type == TokenType.String) {
|
2020-08-12 15:11:10 +00:00
|
|
|
this.expectTokens([';', ',']);
|
2020-08-05 04:48:29 +00:00
|
|
|
promptstr = stripQuotes(prompt.str);
|
|
|
|
} else {
|
|
|
|
this.pushbackToken(prompt);
|
|
|
|
promptstr = "";
|
|
|
|
}
|
2020-08-20 17:58:14 +00:00
|
|
|
return { command:'INPUT', prompt:{ valtype:'string', value: promptstr }, args:this.parseLexprList() };
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
2020-08-13 17:51:35 +00:00
|
|
|
/* for HP BASIC only */
|
|
|
|
stmt__ENTER() : INPUT_Statement {
|
2020-08-21 03:20:53 +00:00
|
|
|
var timeout = this.parseExpr();
|
2020-08-13 17:51:35 +00:00
|
|
|
this.expectToken(',');
|
2020-08-21 03:20:53 +00:00
|
|
|
var elapsed = this.parseLexpr(); // TODO: this has to go somewheres
|
2020-08-13 17:51:35 +00:00
|
|
|
this.expectToken(',');
|
2020-08-21 03:20:53 +00:00
|
|
|
return { command:'INPUT', prompt:null, args:this.parseLexprList(), timeout:timeout, elapsed:elapsed };
|
2020-08-13 17:51:35 +00:00
|
|
|
}
|
2020-08-14 01:50:41 +00:00
|
|
|
// TODO: DATA statement doesn't read unquoted strings
|
2020-08-05 04:48:29 +00:00
|
|
|
stmt__DATA() : DATA_Statement {
|
2020-08-14 04:34:54 +00:00
|
|
|
return { command:'DATA', datums:this.parseDatumList() };
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
2020-08-09 02:36:21 +00:00
|
|
|
stmt__READ() : READ_Statement {
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
return { command:'READ', args:this.parseLexprList() };
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
2020-08-12 16:57:54 +00:00
|
|
|
stmt__RESTORE() : RESTORE_Statement {
|
|
|
|
var label = null;
|
|
|
|
if (this.opts.restoreWithLabel && !isEOS(this.peekToken()))
|
|
|
|
label = this.parseLabel();
|
|
|
|
return { command:'RESTORE', label:label };
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
stmt__RETURN() {
|
|
|
|
return { command:'RETURN' };
|
|
|
|
}
|
|
|
|
stmt__STOP() {
|
|
|
|
return { command:'STOP' };
|
|
|
|
}
|
|
|
|
stmt__END() {
|
2020-08-24 16:51:47 +00:00
|
|
|
if (this.opts.multilineIfThen && this.scopestack.length) {
|
|
|
|
let endtok = this.expectTokens(['IF','SUB']);
|
|
|
|
if (endtok.str == 'IF') {
|
|
|
|
this.popIfThenScope();
|
|
|
|
while (this.elseifcount--) this.popIfThenScope(); // pop additional ELSE IF blocks?
|
|
|
|
this.elseifcount = 0;
|
|
|
|
} else if (endtok.str == 'SUB') {
|
|
|
|
this.addStatement( { command: 'RETURN' }, endtok );
|
|
|
|
this.popScope( { command: 'END' }, 'SUB'); // fake command to avoid null
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return { command:'END' };
|
|
|
|
}
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
2020-08-13 17:51:35 +00:00
|
|
|
stmt__ON() : ONGO_Statement {
|
2020-08-22 16:40:58 +00:00
|
|
|
var expr = this.parseExprWithType("number");
|
2020-08-10 13:07:09 +00:00
|
|
|
var gotok = this.consumeToken();
|
2020-08-15 11:53:13 +00:00
|
|
|
var cmd = {GOTO:'ONGOTO', THEN:'ONGOTO', GOSUB:'ONGOSUB'}[gotok.str]; // THEN only for DEC basic?
|
2020-08-10 13:07:09 +00:00
|
|
|
if (!cmd) this.compileError(`There should be a GOTO or GOSUB here.`);
|
2020-08-05 04:48:29 +00:00
|
|
|
var labels = this.parseLabelList();
|
2020-08-10 13:07:09 +00:00
|
|
|
return { command:cmd, expr:expr, labels:labels };
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
stmt__DEF() : DEF_Statement {
|
2020-08-24 16:51:47 +00:00
|
|
|
var lexpr = this.parseVarSubscriptOrFunc(); // TODO: only allow parameter names, not exprs
|
2020-08-09 02:36:21 +00:00
|
|
|
if (lexpr.args && lexpr.args.length > this.opts.maxDefArgs)
|
2020-08-22 16:40:58 +00:00
|
|
|
this.compileError(`There can be no more than ${this.opts.maxDefArgs} arguments to a function or subscript.`, lexpr.$loc);
|
|
|
|
if (!lexpr.name.startsWith('FN')) this.compileError(`Functions defined with DEF must begin with the letters "FN".`, lexpr.$loc)
|
2020-08-24 16:51:47 +00:00
|
|
|
this.markVarDefs(lexpr); // local variables need to be marked as referenced (TODO: only for this scope)
|
2020-08-05 04:48:29 +00:00
|
|
|
this.expectToken("=");
|
|
|
|
var func = this.parseExpr();
|
2020-08-15 20:03:56 +00:00
|
|
|
// build call graph to detect cycles
|
|
|
|
this.visitExpr(func, (expr:Expr) => {
|
|
|
|
if (isLookup(expr) && expr.name.startsWith('FN')) {
|
|
|
|
if (!this.fnrefs[lexpr.name])
|
|
|
|
this.fnrefs[lexpr.name] = [];
|
|
|
|
this.fnrefs[lexpr.name].push(expr.name);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
this.checkCallGraph(lexpr.name, new Set());
|
2020-08-05 04:48:29 +00:00
|
|
|
return { command:'DEF', lexpr:lexpr, def:func };
|
|
|
|
}
|
2020-08-24 16:51:47 +00:00
|
|
|
stmt__SUB() : SUB_Statement {
|
|
|
|
var lexpr = this.parseVarSubscriptOrFunc(); // TODO: only allow parameter names, not exprs
|
|
|
|
this.markVarDefs(lexpr); // local variables need to be marked as referenced (TODO: only for this scope)
|
|
|
|
this.addLabel(lexpr.name, 1); // offset +1 to skip SUB command
|
|
|
|
return { command:'SUB', lexpr:lexpr };
|
|
|
|
}
|
|
|
|
stmt__CALL() : CALL_Statement {
|
|
|
|
return { command:'CALL', call:this.parseVarSubscriptOrFunc() };
|
|
|
|
}
|
|
|
|
markVarDefs(lexpr: IndOp) {
|
|
|
|
this.vardefs[lexpr.name] = lexpr;
|
|
|
|
if (lexpr.args != null)
|
|
|
|
for (let arg of lexpr.args) {
|
|
|
|
if (isLookup(arg) && arg.args == null)
|
|
|
|
this.vardefs[arg.name] = arg;
|
|
|
|
else
|
|
|
|
this.compileError(`A definition can only define symbols, not expressions.`);
|
|
|
|
}
|
|
|
|
}
|
2020-08-15 20:03:56 +00:00
|
|
|
// detect cycles in call graph starting at function 'name'
|
|
|
|
checkCallGraph(name: string, visited: Set<string>) {
|
|
|
|
if (visited.has(name)) this.compileError(`There was a cycle in the function definition graph for ${name}.`);
|
|
|
|
visited.add(name);
|
|
|
|
var refs = this.fnrefs[name] || [];
|
|
|
|
for (var ref of refs)
|
|
|
|
this.checkCallGraph(ref, visited); // recurse
|
|
|
|
visited.delete(name);
|
|
|
|
}
|
2020-08-09 02:36:21 +00:00
|
|
|
stmt__POP() : NoArgStatement {
|
|
|
|
return { command:'POP' };
|
|
|
|
}
|
|
|
|
stmt__GET() : GET_Statement {
|
|
|
|
var lexpr = this.parseLexpr();
|
|
|
|
return { command:'GET', lexpr:lexpr };
|
|
|
|
}
|
2020-08-10 13:07:09 +00:00
|
|
|
stmt__CLEAR() : NoArgStatement {
|
|
|
|
return { command:'CLEAR' };
|
|
|
|
}
|
2020-08-12 16:57:54 +00:00
|
|
|
stmt__RANDOMIZE() : NoArgStatement {
|
|
|
|
return { command:'RANDOMIZE' };
|
|
|
|
}
|
2020-08-15 11:53:13 +00:00
|
|
|
stmt__CHANGE() : CHANGE_Statement {
|
|
|
|
var src = this.parseExpr();
|
|
|
|
this.expectToken('TO');
|
|
|
|
var dest = this.parseLexpr();
|
2020-08-22 16:40:58 +00:00
|
|
|
if (dest.valtype == src.valtype)
|
|
|
|
this.compileError(`CHANGE can only convert strings to numeric arrays, or vice-versa.`, mergeLocs(src.$loc, dest.$loc));
|
2020-08-15 11:53:13 +00:00
|
|
|
return { command:'CHANGE', src:src, dest:dest };
|
|
|
|
}
|
2020-08-22 16:40:58 +00:00
|
|
|
stmt__CONVERT() : CONVERT_Statement {
|
|
|
|
var src = this.parseExpr();
|
|
|
|
this.expectToken('TO');
|
|
|
|
var dest = this.parseLexpr();
|
|
|
|
if (dest.valtype == src.valtype)
|
|
|
|
this.compileError(`CONVERT can only convert strings to numbers, or vice-versa.`, mergeLocs(src.$loc, dest.$loc));
|
|
|
|
return { command:'CONVERT', src:src, dest:dest };
|
|
|
|
}
|
2020-08-09 02:36:21 +00:00
|
|
|
// TODO: CHANGE A TO A$ (4th edition, A(0) is len and A(1..) are chars)
|
2020-08-05 04:48:29 +00:00
|
|
|
stmt__OPTION() : OPTION_Statement {
|
2020-08-22 16:40:58 +00:00
|
|
|
this.optionCount++;
|
2020-08-05 04:48:29 +00:00
|
|
|
var tokname = this.consumeToken();
|
2020-08-22 16:40:58 +00:00
|
|
|
var optname = tokname.str.toUpperCase();
|
2020-08-09 02:36:21 +00:00
|
|
|
if (tokname.type != TokenType.Ident) this.compileError(`There must be a name after the OPTION statement.`)
|
2020-08-22 16:40:58 +00:00
|
|
|
var tokarg = this.consumeToken();
|
|
|
|
var arg = tokarg.str.toUpperCase();
|
|
|
|
switch (optname) {
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
case 'DIALECT':
|
2020-08-22 16:40:58 +00:00
|
|
|
if (this.optionCount > 1) this.compileError(`OPTION DIALECT must be the first OPTION statement in the file.`, tokname.$loc);
|
2020-08-10 03:19:27 +00:00
|
|
|
let dname = arg || "";
|
2020-08-22 16:40:58 +00:00
|
|
|
if (dname == "") this.compileError(`OPTION DIALECT requires a dialect name.`, tokname.$loc);
|
2020-08-09 02:36:21 +00:00
|
|
|
let dialect = DIALECTS[dname.toUpperCase()];
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
if (dialect) this.opts = dialect;
|
2020-08-22 16:40:58 +00:00
|
|
|
else this.compileError(`${dname} is not a valid dialect.`);
|
2020-08-10 03:19:27 +00:00
|
|
|
break;
|
2020-08-15 11:53:13 +00:00
|
|
|
case 'BASE':
|
|
|
|
let base = parseInt(arg);
|
|
|
|
if (base == 0 || base == 1) this.opts.defaultArrayBase = base;
|
|
|
|
else this.compileError("OPTION BASE can only be 0 or 1.");
|
|
|
|
break;
|
2020-08-10 03:19:27 +00:00
|
|
|
case 'CPUSPEED':
|
|
|
|
if (!(this.opts.commandsPerSec = Math.min(1e7, arg=='MAX' ? Infinity : parseFloat(arg))))
|
|
|
|
this.compileError(`OPTION CPUSPEED takes a positive number or MAX.`);
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
break;
|
|
|
|
default:
|
2020-08-11 17:29:31 +00:00
|
|
|
// maybe it's one of the options?
|
2020-08-22 16:40:58 +00:00
|
|
|
let propname = Object.getOwnPropertyNames(this.opts).find((n) => n.toUpperCase() == optname);
|
|
|
|
if (propname == null) this.compileError(`${optname} is not a valid option.`, tokname.$loc);
|
|
|
|
if (arg == null) this.compileError(`OPTION ${optname} requires a parameter.`);
|
|
|
|
switch (typeof this.opts[propname]) {
|
|
|
|
case 'boolean' : this.opts[propname] = arg.toUpperCase().startsWith("T") || (arg as any)>0; return;
|
|
|
|
case 'number' : this.opts[propname] = parseFloat(arg); return;
|
|
|
|
case 'string' : this.opts[propname] = arg; return;
|
2020-08-11 17:29:31 +00:00
|
|
|
case 'object' :
|
2020-08-22 16:40:58 +00:00
|
|
|
if (Array.isArray(this.opts[propname]) && arg == 'ALL') {
|
|
|
|
this.opts[propname] = null;
|
2020-08-11 17:29:31 +00:00
|
|
|
return;
|
|
|
|
}
|
2020-08-22 16:40:58 +00:00
|
|
|
this.compileError(`OPTION ${optname} ALL is the only option supported.`);
|
2020-08-11 17:29:31 +00:00
|
|
|
}
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
break;
|
|
|
|
}
|
2020-08-22 16:40:58 +00:00
|
|
|
return { command:'OPTION', optname:optname, optargs:[arg]}
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// for workermain
|
|
|
|
generateListing(file: string, program: BASICProgram) {
|
|
|
|
var srclines = [];
|
2020-08-13 17:51:35 +00:00
|
|
|
var laststmt : Statement;
|
2020-08-19 17:05:30 +00:00
|
|
|
program.stmts.forEach((stmt, idx) => {
|
|
|
|
laststmt = stmt;
|
2020-08-22 16:40:58 +00:00
|
|
|
srclines.push(stmt.$loc);
|
2020-08-05 04:48:29 +00:00
|
|
|
});
|
2020-08-13 17:51:35 +00:00
|
|
|
if (this.opts.endStmtRequired && (laststmt == null || laststmt.command != 'END'))
|
2020-08-19 01:06:23 +00:00
|
|
|
this.dialectError(`All programs must have a final END statement`);
|
2020-08-05 04:48:29 +00:00
|
|
|
return { lines: srclines };
|
|
|
|
}
|
|
|
|
getListings() : CodeListingMap {
|
|
|
|
return this.listings;
|
|
|
|
}
|
|
|
|
|
|
|
|
// LINT STUFF
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
checkAll(program : BASICProgram) {
|
|
|
|
this.checkLabels();
|
2020-08-19 01:06:23 +00:00
|
|
|
this.checkScopes();
|
2020-08-20 17:58:14 +00:00
|
|
|
this.checkVarRefs();
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
}
|
2020-08-05 04:48:29 +00:00
|
|
|
checkLabels() {
|
|
|
|
for (let targ in this.targets) {
|
|
|
|
if (this.labels[targ] == null) {
|
2020-08-16 17:16:29 +00:00
|
|
|
var what = this.opts.optionalLabels && isNaN(parseInt(targ)) ? "label named" : "line number";
|
|
|
|
this.addError(`There isn't a ${what} ${targ}.`, this.targets[targ]);
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-19 01:06:23 +00:00
|
|
|
checkScopes() {
|
|
|
|
if (this.opts.compiledBlocks && this.scopestack.length) {
|
2020-08-20 19:56:28 +00:00
|
|
|
var open = this.stmts[this.scopestack.pop()];
|
2020-08-24 16:51:47 +00:00
|
|
|
var close = {FOR:"NEXT", WHILE:"WEND", IF:"END IF", SUB:"END SUB"};
|
2020-08-19 01:06:23 +00:00
|
|
|
this.compileError(`Don't forget to add a matching ${close[open.command]} statement.`, open.$loc);
|
|
|
|
}
|
|
|
|
}
|
2020-08-20 17:58:14 +00:00
|
|
|
checkVarRefs() {
|
|
|
|
if (!this.opts.defaultValues) {
|
|
|
|
for (var varname in this.varrefs) {
|
|
|
|
if (this.vardefs[varname] == null)
|
|
|
|
this.compileError(`The variable ${varname} isn't defined anywhere in the program.`, this.varrefs[varname]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///// BASIC DIALECTS
|
|
|
|
|
|
|
|
export const ECMA55_MINIMAL : BASICOptions = {
|
2020-08-09 02:36:21 +00:00
|
|
|
dialectName: "ECMA55",
|
|
|
|
asciiOnly : true,
|
2020-08-05 04:48:29 +00:00
|
|
|
uppercaseOnly : true,
|
2020-08-09 01:03:48 +00:00
|
|
|
optionalLabels : false,
|
2020-08-11 17:29:31 +00:00
|
|
|
optionalWhitespace : false,
|
2020-08-19 17:05:30 +00:00
|
|
|
multipleStmtsPerLine : false,
|
2020-08-11 17:29:31 +00:00
|
|
|
varNaming : "A1",
|
|
|
|
staticArrays : true,
|
2020-08-05 04:48:29 +00:00
|
|
|
sharedArrayNamespace : true,
|
|
|
|
defaultArrayBase : 0,
|
|
|
|
defaultArraySize : 11,
|
|
|
|
defaultValues : false,
|
|
|
|
stringConcat : false,
|
|
|
|
maxDimensions : 2,
|
2020-08-09 02:36:21 +00:00
|
|
|
maxDefArgs : 255,
|
|
|
|
maxStringLength : 255,
|
2020-08-05 04:48:29 +00:00
|
|
|
tickComments : false,
|
2020-08-12 15:51:18 +00:00
|
|
|
hexOctalConsts : false,
|
2020-08-12 16:57:54 +00:00
|
|
|
validKeywords : [
|
|
|
|
'BASE','DATA','DEF','DIM','END',
|
2020-08-05 04:48:29 +00:00
|
|
|
'FOR','GO','GOSUB','GOTO','IF','INPUT','LET','NEXT','ON','OPTION','PRINT',
|
2020-08-24 16:51:47 +00:00
|
|
|
'RANDOMIZE','READ','REM','RESTORE','RETURN','STEP','STOP','THEN','TO' // 'SUB'
|
2020-08-11 17:29:31 +00:00
|
|
|
],
|
2020-08-12 16:57:54 +00:00
|
|
|
validFunctions : [
|
|
|
|
'ABS','ATN','COS','EXP','INT','LOG','RND','SGN','SIN','SQR','TAB','TAN'
|
|
|
|
],
|
|
|
|
validOperators : [
|
|
|
|
'=', '<>', '<', '>', '<=', '>=', '+', '-', '*', '/', '^'
|
|
|
|
],
|
2020-08-05 04:48:29 +00:00
|
|
|
printZoneLength : 15,
|
2020-08-09 02:36:21 +00:00
|
|
|
numericPadding : true,
|
2020-08-05 04:48:29 +00:00
|
|
|
checkOverflow : true,
|
2020-08-11 17:29:31 +00:00
|
|
|
testInitialFor : true,
|
2020-08-12 15:11:10 +00:00
|
|
|
optionalNextVar : false,
|
2020-08-12 16:57:54 +00:00
|
|
|
multipleNextVars : false,
|
2020-08-09 16:23:49 +00:00
|
|
|
bitwiseLogic : false,
|
2020-08-12 16:57:54 +00:00
|
|
|
checkOnGotoIndex : true,
|
2020-08-13 17:51:35 +00:00
|
|
|
computedGoto : false,
|
|
|
|
restoreWithLabel : false,
|
|
|
|
squareBrackets : false,
|
|
|
|
arraysContainChars : false,
|
|
|
|
endStmtRequired : true,
|
2020-08-15 11:53:13 +00:00
|
|
|
chainAssignments : false,
|
2020-08-16 17:16:29 +00:00
|
|
|
optionalLet : false,
|
2020-08-19 01:06:23 +00:00
|
|
|
compiledBlocks : true,
|
2020-08-16 17:16:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export const DARTMOUTH_4TH_EDITION : BASICOptions = {
|
|
|
|
dialectName: "DARTMOUTH4",
|
|
|
|
asciiOnly : true,
|
|
|
|
uppercaseOnly : true,
|
|
|
|
optionalLabels : false,
|
|
|
|
optionalWhitespace : false,
|
2020-08-19 17:05:30 +00:00
|
|
|
multipleStmtsPerLine : false,
|
2020-08-16 17:16:29 +00:00
|
|
|
varNaming : "A1",
|
|
|
|
staticArrays : true,
|
|
|
|
sharedArrayNamespace : false,
|
|
|
|
defaultArrayBase : 0,
|
|
|
|
defaultArraySize : 11,
|
|
|
|
defaultValues : false,
|
|
|
|
stringConcat : 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',
|
2020-08-24 16:51:47 +00:00
|
|
|
'RANDOMIZE','READ','REM','RESTORE','RETURN','STEP','STOP','THEN','TO', //'SUB',
|
2020-08-16 17:16:29 +00:00
|
|
|
'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,
|
2020-08-19 01:06:23 +00:00
|
|
|
compiledBlocks : true,
|
2020-08-13 17:51:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: only integers supported
|
|
|
|
export const TINY_BASIC : BASICOptions = {
|
|
|
|
dialectName: "TINY",
|
|
|
|
asciiOnly : true,
|
|
|
|
uppercaseOnly : true,
|
|
|
|
optionalLabels : false,
|
|
|
|
optionalWhitespace : false,
|
2020-08-19 17:05:30 +00:00
|
|
|
multipleStmtsPerLine : false,
|
2020-08-13 17:51:35 +00:00
|
|
|
varNaming : "A",
|
|
|
|
staticArrays : false,
|
|
|
|
sharedArrayNamespace : true,
|
|
|
|
defaultArrayBase : 0,
|
|
|
|
defaultArraySize : 0,
|
|
|
|
defaultValues : true,
|
|
|
|
stringConcat : 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,
|
2020-08-16 17:16:29 +00:00
|
|
|
computedGoto : true,
|
2020-08-12 16:57:54 +00:00
|
|
|
restoreWithLabel : false,
|
2020-08-13 17:51:35 +00:00
|
|
|
squareBrackets : false,
|
|
|
|
arraysContainChars : false,
|
|
|
|
endStmtRequired : false,
|
2020-08-15 11:53:13 +00:00
|
|
|
chainAssignments : false,
|
2020-08-16 17:16:29 +00:00
|
|
|
optionalLet : false,
|
2020-08-19 01:06:23 +00:00
|
|
|
compiledBlocks : false,
|
2020-08-13 17:51:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export const HP_TIMESHARED_BASIC : BASICOptions = {
|
2020-08-14 14:26:43 +00:00
|
|
|
dialectName: "HP2000",
|
2020-08-13 17:51:35 +00:00
|
|
|
asciiOnly : true,
|
2020-08-22 16:40:58 +00:00
|
|
|
uppercaseOnly : true, // the terminal is usually uppercase
|
2020-08-13 17:51:35 +00:00
|
|
|
optionalLabels : false,
|
|
|
|
optionalWhitespace : false,
|
2020-08-19 17:05:30 +00:00
|
|
|
multipleStmtsPerLine : true,
|
2020-08-21 03:20:53 +00:00
|
|
|
varNaming : "A1$",
|
2020-08-13 17:51:35 +00:00
|
|
|
staticArrays : true,
|
|
|
|
sharedArrayNamespace : false,
|
|
|
|
defaultArrayBase : 1,
|
|
|
|
defaultArraySize : 11,
|
|
|
|
defaultValues : false,
|
|
|
|
stringConcat : false,
|
|
|
|
maxDimensions : 2,
|
|
|
|
maxDefArgs : 255,
|
2020-08-22 16:40:58 +00:00
|
|
|
maxStringLength : 255, // 72 for literals
|
2020-08-13 17:51:35 +00:00
|
|
|
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',
|
2020-08-24 16:51:47 +00:00
|
|
|
'RANDOMIZE','READ','REM','RESTORE','RETURN','STEP','STOP','THEN','TO', //'SUB',
|
2020-08-13 17:51:35 +00:00
|
|
|
'ENTER','MAT','CONVERT','OF','IMAGE','USING'
|
|
|
|
],
|
|
|
|
validFunctions : [
|
|
|
|
'ABS','ATN','BRK','COS','CTL','EXP','INT','LEN','LIN','LOG','NUM',
|
2020-08-14 21:21:32 +00:00
|
|
|
'POS','RND','SGN','SIN','SPA','SQR','TAB','TAN','TIM','TYP','UPS$', // TODO: POS,
|
|
|
|
'NFORMAT$', // non-standard, substitute for PRINT USING
|
2020-08-13 17:51:35 +00:00
|
|
|
],
|
|
|
|
validOperators : [
|
|
|
|
'=', '<>', '<', '>', '<=', '>=', '+', '-', '*', '/', '^',
|
|
|
|
'**', '#', 'NOT', 'AND', 'OR', 'MIN', 'MAX',
|
|
|
|
],
|
|
|
|
printZoneLength : 15,
|
|
|
|
numericPadding : true,
|
|
|
|
checkOverflow : false,
|
|
|
|
testInitialFor : true,
|
|
|
|
optionalNextVar : false,
|
|
|
|
multipleNextVars : false,
|
|
|
|
bitwiseLogic : false,
|
|
|
|
checkOnGotoIndex : false,
|
2020-08-16 17:16:29 +00:00
|
|
|
computedGoto : true, // not really, but we do parse expressions for GOTO ... OF
|
2020-08-13 17:51:35 +00:00
|
|
|
restoreWithLabel : true,
|
|
|
|
squareBrackets : true,
|
|
|
|
arraysContainChars : true,
|
|
|
|
endStmtRequired : true,
|
2020-08-15 11:53:13 +00:00
|
|
|
chainAssignments : true,
|
2020-08-16 17:16:29 +00:00
|
|
|
optionalLet : true,
|
2020-08-19 01:06:23 +00:00
|
|
|
compiledBlocks : true,
|
2020-08-22 16:40:58 +00:00
|
|
|
maxArrayElements : 5000,
|
|
|
|
// TODO: max line number
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
|
|
|
|
2020-08-15 11:53:13 +00:00
|
|
|
export const DEC_BASIC_11 : BASICOptions = {
|
|
|
|
dialectName: "DEC11",
|
|
|
|
asciiOnly : true,
|
|
|
|
uppercaseOnly : true, // translates all lower to upper
|
|
|
|
optionalLabels : false,
|
|
|
|
optionalWhitespace : false,
|
2020-08-19 17:05:30 +00:00
|
|
|
multipleStmtsPerLine : false, // actually "\"
|
2020-08-15 11:53:13 +00:00
|
|
|
varNaming : "A1",
|
|
|
|
staticArrays : true,
|
|
|
|
sharedArrayNamespace : false,
|
|
|
|
defaultArrayBase : 0,
|
|
|
|
defaultArraySize : 11,
|
|
|
|
defaultValues : true,
|
|
|
|
stringConcat : true, // can also use &
|
|
|
|
maxDimensions : 2,
|
|
|
|
maxDefArgs : 255, // ?
|
|
|
|
maxStringLength : 255,
|
|
|
|
tickComments : false,
|
|
|
|
hexOctalConsts : false,
|
|
|
|
validKeywords : [
|
|
|
|
'OPTION',
|
|
|
|
'DATA','DEF','DIM','END','FOR','STEP','GOSUB','GOTO','GO','TO',
|
|
|
|
'IF','THEN','INPUT','LET','NEXT','ON','PRINT','RANDOMIZE',
|
|
|
|
'READ','REM','RESET','RESTORE','RETURN','STOP',
|
|
|
|
],
|
|
|
|
validFunctions : [
|
|
|
|
'ABS','ATN','COS','EXP','INT','LOG','LOG10','PI','RND','SGN','SIN','SQR','TAB',
|
|
|
|
'ASC','BIN','CHR$','CLK$','DAT$','LEN','OCT','POS','SEG$','STR$','TRM$','VAL',
|
|
|
|
'NFORMAT$', // non-standard, substitute for PRINT USING
|
|
|
|
],
|
|
|
|
validOperators : [
|
|
|
|
'=', '<>', '><', '<', '>', '<=', '>=', '+', '-', '*', '/', '^',
|
|
|
|
],
|
|
|
|
printZoneLength : 14,
|
|
|
|
numericPadding : true,
|
|
|
|
checkOverflow : true, // non-fatal; subst 0 and continue
|
|
|
|
testInitialFor : true,
|
|
|
|
optionalNextVar : false,
|
|
|
|
multipleNextVars : false,
|
|
|
|
bitwiseLogic : false,
|
|
|
|
checkOnGotoIndex : true, // might continue
|
|
|
|
computedGoto : false,
|
|
|
|
restoreWithLabel : false,
|
|
|
|
squareBrackets : false,
|
|
|
|
arraysContainChars : false,
|
|
|
|
endStmtRequired : false,
|
|
|
|
chainAssignments : false,
|
2020-08-16 17:16:29 +00:00
|
|
|
optionalLet : true,
|
2020-08-19 01:06:23 +00:00
|
|
|
compiledBlocks : true,
|
2020-08-15 11:53:13 +00:00
|
|
|
// TODO: max line number 32767
|
|
|
|
// TODO: \ separator, % int vars and constants, 'single' quoted
|
|
|
|
// TODO: can't compare strings and numbers
|
|
|
|
}
|
|
|
|
|
|
|
|
export const DEC_BASIC_PLUS : BASICOptions = {
|
|
|
|
dialectName: "DECPLUS",
|
|
|
|
asciiOnly : true,
|
|
|
|
uppercaseOnly : false,
|
|
|
|
optionalLabels : false,
|
|
|
|
optionalWhitespace : false,
|
2020-08-19 17:05:30 +00:00
|
|
|
multipleStmtsPerLine : true,
|
2020-08-15 11:53:13 +00:00
|
|
|
varNaming : "A1",
|
|
|
|
staticArrays : true,
|
|
|
|
sharedArrayNamespace : false,
|
|
|
|
defaultArrayBase : 0,
|
|
|
|
defaultArraySize : 11,
|
|
|
|
defaultValues : true,
|
|
|
|
stringConcat : true, // can also use "&"
|
|
|
|
maxDimensions : 2,
|
|
|
|
maxDefArgs : 255, // ?
|
|
|
|
maxStringLength : 255,
|
|
|
|
tickComments : true, // actually use "!"
|
|
|
|
hexOctalConsts : false,
|
|
|
|
validKeywords : [
|
|
|
|
'OPTION',
|
|
|
|
'REM','LET','DIM','RANDOM','RANDOMIZE','IF','THEN','ELSE',
|
|
|
|
'FOR','TO','STEP','WHILE','UNTIL','NEXT','DEF','ON','GOTO','GOSUB',
|
|
|
|
'RETURN','CHANGE','READ','DATA','RESTORE','PRINT','USING',
|
|
|
|
'INPUT','LINE','NAME','AS','ERROR','RESUME','CHAIN','STOP','END',
|
|
|
|
'MAT','UNLESS','SLEEP','WAIT',
|
|
|
|
],
|
|
|
|
validFunctions : [
|
|
|
|
'ABS','ATN','COS','EXP','INT','LOG','LOG10','PI','RND','SGN','SIN','SQR','TAB','TAN',
|
|
|
|
'POS','TAB','ASCII','CHR$','CVT%$','CVTF$','CVT$%','CVT$F',
|
|
|
|
'LEFT$','RIGHT$','MID$','LEN','INSTR','SPACE$','NUM$','VAL','XLATE',
|
|
|
|
'DATE$','TIME$','TIME','ERR','ERL','SWAP%','RAD$',
|
|
|
|
'NFORMAT$', // non-standard, substitute for PRINT USING
|
|
|
|
],
|
|
|
|
validOperators : [
|
|
|
|
'=', '<>', '<', '>', '<=', '>=', '+', '-', '*', '/', '^',
|
|
|
|
'**', '==',
|
|
|
|
'NOT', 'AND', 'OR', 'XOR', 'IMP', 'EQV',
|
|
|
|
],
|
|
|
|
printZoneLength : 14,
|
|
|
|
numericPadding : true,
|
|
|
|
checkOverflow : true, // non-fatal; subst 0 and continue
|
|
|
|
testInitialFor : true,
|
|
|
|
optionalNextVar : false,
|
|
|
|
multipleNextVars : false,
|
|
|
|
bitwiseLogic : false,
|
|
|
|
checkOnGotoIndex : true, // might continue
|
|
|
|
computedGoto : false,
|
|
|
|
restoreWithLabel : false,
|
|
|
|
squareBrackets : false,
|
|
|
|
arraysContainChars : false,
|
|
|
|
endStmtRequired : false,
|
|
|
|
chainAssignments : false, // TODO: can chain with "," not "="
|
2020-08-16 17:16:29 +00:00
|
|
|
optionalLet : true,
|
2020-08-19 01:06:23 +00:00
|
|
|
compiledBlocks : true,
|
2020-08-15 11:53:13 +00:00
|
|
|
// TODO: max line number 32767
|
|
|
|
// TODO: \ separator, % int vars and constants, 'single' quoted
|
|
|
|
// TODO: can't compare strings and numbers
|
|
|
|
// TODO: WHILE/UNTIL/FOR extra statements, etc
|
|
|
|
}
|
|
|
|
|
2020-08-11 17:29:31 +00:00
|
|
|
export const BASICODE : BASICOptions = {
|
|
|
|
dialectName: "BASICODE",
|
2020-08-09 02:36:21 +00:00
|
|
|
asciiOnly : true,
|
2020-08-13 17:32:47 +00:00
|
|
|
uppercaseOnly : false,
|
2020-08-09 01:03:48 +00:00
|
|
|
optionalLabels : false,
|
2020-08-11 17:29:31 +00:00
|
|
|
optionalWhitespace : true,
|
2020-08-19 17:05:30 +00:00
|
|
|
multipleStmtsPerLine : true,
|
2020-08-11 17:29:31 +00:00
|
|
|
varNaming : "AA",
|
|
|
|
staticArrays : true,
|
|
|
|
sharedArrayNamespace : false,
|
|
|
|
defaultArrayBase : 0,
|
|
|
|
defaultArraySize : 11,
|
|
|
|
defaultValues : false,
|
|
|
|
stringConcat : true,
|
|
|
|
maxDimensions : 2,
|
|
|
|
maxDefArgs : 255,
|
|
|
|
maxStringLength : 255,
|
|
|
|
tickComments : false,
|
2020-08-12 15:51:18 +00:00
|
|
|
hexOctalConsts : false,
|
2020-08-12 16:57:54 +00:00
|
|
|
validKeywords : [
|
|
|
|
'BASE','DATA','DEF','DIM','END',
|
2020-08-11 17:29:31 +00:00
|
|
|
'FOR','GO','GOSUB','GOTO','IF','INPUT','LET','NEXT','ON','OPTION','PRINT',
|
2020-08-24 16:51:47 +00:00
|
|
|
'READ','REM','RESTORE','RETURN','STEP','STOP','THEN','TO', // 'SUB',
|
2020-08-13 23:53:11 +00:00
|
|
|
'AND', 'NOT', 'OR'
|
2020-08-11 17:29:31 +00:00
|
|
|
],
|
2020-08-12 16:57:54 +00:00
|
|
|
validFunctions : [
|
|
|
|
'ABS','ASC','ATN','CHR$','COS','EXP','INT','LEFT$','LEN','LOG',
|
|
|
|
'MID$','RIGHT$','SGN','SIN','SQR','TAB','TAN','VAL'
|
|
|
|
],
|
|
|
|
validOperators : [
|
|
|
|
'=', '<>', '<', '>', '<=', '>=', '+', '-', '*', '/', '^', 'AND', 'NOT', 'OR'
|
|
|
|
],
|
2020-08-11 17:29:31 +00:00
|
|
|
printZoneLength : 15,
|
|
|
|
numericPadding : true,
|
|
|
|
checkOverflow : true,
|
|
|
|
testInitialFor : true,
|
2020-08-12 15:11:10 +00:00
|
|
|
optionalNextVar : false,
|
2020-08-12 16:57:54 +00:00
|
|
|
multipleNextVars : false,
|
2020-08-11 17:29:31 +00:00
|
|
|
bitwiseLogic : false,
|
2020-08-12 16:57:54 +00:00
|
|
|
checkOnGotoIndex : true,
|
2020-08-13 17:51:35 +00:00
|
|
|
computedGoto : false,
|
2020-08-12 16:57:54 +00:00
|
|
|
restoreWithLabel : false,
|
2020-08-13 17:51:35 +00:00
|
|
|
squareBrackets : false,
|
|
|
|
arraysContainChars : false,
|
|
|
|
endStmtRequired : false,
|
2020-08-15 11:53:13 +00:00
|
|
|
chainAssignments : false,
|
2020-08-16 17:16:29 +00:00
|
|
|
optionalLet : true,
|
2020-08-19 01:06:23 +00:00
|
|
|
compiledBlocks : false,
|
2020-08-11 17:29:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export const ALTAIR_BASIC41 : BASICOptions = {
|
|
|
|
dialectName: "ALTAIR41",
|
|
|
|
asciiOnly : true,
|
|
|
|
uppercaseOnly : true,
|
|
|
|
optionalLabels : false,
|
|
|
|
optionalWhitespace : true,
|
2020-08-19 17:05:30 +00:00
|
|
|
multipleStmtsPerLine : true,
|
2020-08-11 17:29:31 +00:00
|
|
|
varNaming : "*", // or AA
|
|
|
|
staticArrays : false,
|
2020-08-05 04:48:29 +00:00
|
|
|
sharedArrayNamespace : true,
|
|
|
|
defaultArrayBase : 0,
|
|
|
|
defaultArraySize : 11,
|
2020-08-09 02:36:21 +00:00
|
|
|
defaultValues : true,
|
|
|
|
stringConcat : true,
|
|
|
|
maxDimensions : 128, // "as many as will fit on a single line" ... ?
|
|
|
|
maxDefArgs : 255,
|
|
|
|
maxStringLength : 255,
|
2020-08-05 04:48:29 +00:00
|
|
|
tickComments : false,
|
2020-08-12 15:51:18 +00:00
|
|
|
hexOctalConsts : false,
|
2020-08-11 17:29:31 +00:00
|
|
|
validKeywords : [
|
|
|
|
'OPTION',
|
|
|
|
'CONSOLE','DATA','DEF','DEFUSR','DIM','END','ERASE','ERROR',
|
|
|
|
'FOR','GOTO','GOSUB','IF','THEN','ELSE','INPUT','LET','LINE',
|
|
|
|
'PRINT','LPRINT','USING','NEXT','ON','OUT','POKE',
|
|
|
|
'READ','REM','RESTORE','RESUME','RETURN','STOP','SWAP',
|
2020-08-12 16:57:54 +00:00
|
|
|
'TROFF','TRON','WAIT',
|
|
|
|
'TO','STEP',
|
2020-08-24 16:51:47 +00:00
|
|
|
'AND', 'NOT', 'OR', 'XOR', 'IMP', 'EQV', 'MOD',
|
|
|
|
'RANDOMIZE' // not in Altair BASIC, but we add it anyway
|
2020-08-12 16:57:54 +00:00
|
|
|
],
|
2020-08-13 17:51:35 +00:00
|
|
|
validFunctions : [
|
|
|
|
'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 : [
|
2020-08-13 23:53:11 +00:00
|
|
|
'=', '<>', '<', '>', '<=', '>=', '+', '-', '*', '/', '^', '\\',
|
|
|
|
'AND', 'NOT', 'OR', 'XOR', 'IMP', 'EQV', 'MOD'
|
2020-08-13 17:51:35 +00:00
|
|
|
],
|
2020-08-05 04:48:29 +00:00
|
|
|
printZoneLength : 15,
|
2020-08-09 02:36:21 +00:00
|
|
|
numericPadding : true,
|
|
|
|
checkOverflow : true,
|
2020-08-11 17:29:31 +00:00
|
|
|
testInitialFor : false,
|
2020-08-12 15:11:10 +00:00
|
|
|
optionalNextVar : true,
|
2020-08-12 16:57:54 +00:00
|
|
|
multipleNextVars : true,
|
2020-08-09 16:23:49 +00:00
|
|
|
bitwiseLogic : true,
|
2020-08-12 16:57:54 +00:00
|
|
|
checkOnGotoIndex : false,
|
2020-08-13 17:51:35 +00:00
|
|
|
computedGoto : false,
|
2020-08-12 16:57:54 +00:00
|
|
|
restoreWithLabel : false,
|
2020-08-13 17:51:35 +00:00
|
|
|
squareBrackets : false,
|
|
|
|
arraysContainChars : false,
|
|
|
|
endStmtRequired : false,
|
2020-08-15 11:53:13 +00:00
|
|
|
chainAssignments : false,
|
2020-08-16 17:16:29 +00:00
|
|
|
optionalLet : true,
|
2020-08-19 01:06:23 +00:00
|
|
|
compiledBlocks : false,
|
2020-08-09 02:36:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export const APPLESOFT_BASIC : BASICOptions = {
|
|
|
|
dialectName: "APPLESOFT",
|
|
|
|
asciiOnly : true,
|
|
|
|
uppercaseOnly : false,
|
|
|
|
optionalLabels : false,
|
2020-08-11 17:29:31 +00:00
|
|
|
optionalWhitespace : true,
|
2020-08-19 17:05:30 +00:00
|
|
|
multipleStmtsPerLine : true,
|
2020-08-11 17:29:31 +00:00
|
|
|
varNaming : "*", // or AA
|
|
|
|
staticArrays : false,
|
2020-08-09 02:36:21 +00:00
|
|
|
sharedArrayNamespace : false,
|
|
|
|
defaultArrayBase : 0,
|
2020-08-11 17:29:31 +00:00
|
|
|
defaultArraySize : 11,
|
2020-08-09 02:36:21 +00:00
|
|
|
defaultValues : true,
|
|
|
|
stringConcat : true,
|
|
|
|
maxDimensions : 88,
|
|
|
|
maxDefArgs : 1, // TODO: no string FNs
|
|
|
|
maxStringLength : 255,
|
|
|
|
tickComments : false,
|
2020-08-12 15:51:18 +00:00
|
|
|
hexOctalConsts : false,
|
2020-08-10 13:07:09 +00:00
|
|
|
validKeywords : [
|
2020-08-10 16:08:43 +00:00
|
|
|
'OPTION',
|
2020-08-11 17:29:31 +00:00
|
|
|
'CLEAR','LET','DIM','DEF','GOTO','GOSUB','RETURN','ON','POP',
|
2020-08-12 16:57:54 +00:00
|
|
|
'FOR','NEXT','IF','THEN','END','STOP','ONERR','RESUME',
|
2020-08-10 13:07:09 +00:00
|
|
|
'PRINT','INPUT','GET','HOME','HTAB','VTAB',
|
|
|
|
'INVERSE','FLASH','NORMAL','TEXT',
|
|
|
|
'GR','COLOR','PLOT','HLIN','VLIN',
|
|
|
|
'HGR','HGR2','HPLOT','HCOLOR','AT',
|
|
|
|
'DATA','READ','RESTORE',
|
2020-08-12 16:57:54 +00:00
|
|
|
'REM','TRACE','NOTRACE',
|
|
|
|
'TO','STEP',
|
2020-08-13 23:53:11 +00:00
|
|
|
'AND', 'NOT', 'OR'
|
2020-08-12 16:57:54 +00:00
|
|
|
],
|
2020-08-10 13:07:09 +00:00
|
|
|
validFunctions : [
|
|
|
|
'ABS','ATN','COS','EXP','INT','LOG','RND','SGN','SIN','SQR','TAN',
|
|
|
|
'LEN','LEFT$','MID$','RIGHT$','STR$','VAL','CHR$','ASC',
|
2020-08-12 16:57:54 +00:00
|
|
|
'FRE','SCRN','PDL','PEEK','POS'
|
|
|
|
],
|
|
|
|
validOperators : [
|
2020-08-13 23:53:11 +00:00
|
|
|
'=', '<>', '<', '>', '<=', '>=', '+', '-', '*', '/', '^',
|
|
|
|
'AND', 'NOT', 'OR'
|
2020-08-12 16:57:54 +00:00
|
|
|
],
|
2020-08-09 02:36:21 +00:00
|
|
|
printZoneLength : 16,
|
|
|
|
numericPadding : false,
|
|
|
|
checkOverflow : true,
|
2020-08-11 17:29:31 +00:00
|
|
|
testInitialFor : false,
|
2020-08-12 15:11:10 +00:00
|
|
|
optionalNextVar : true,
|
2020-08-12 16:57:54 +00:00
|
|
|
multipleNextVars : true,
|
2020-08-09 16:23:49 +00:00
|
|
|
bitwiseLogic : false,
|
2020-08-12 16:57:54 +00:00
|
|
|
checkOnGotoIndex : false,
|
2020-08-13 17:51:35 +00:00
|
|
|
computedGoto : false,
|
2020-08-12 16:57:54 +00:00
|
|
|
restoreWithLabel : false,
|
2020-08-13 17:51:35 +00:00
|
|
|
squareBrackets : false,
|
|
|
|
arraysContainChars : false,
|
|
|
|
endStmtRequired : false,
|
2020-08-15 11:53:13 +00:00
|
|
|
chainAssignments : false,
|
2020-08-16 17:16:29 +00:00
|
|
|
optionalLet : true,
|
2020-08-19 01:06:23 +00:00
|
|
|
compiledBlocks : false,
|
2020-08-12 16:57:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export const BASIC80 : BASICOptions = {
|
|
|
|
dialectName: "BASIC80",
|
|
|
|
asciiOnly : true,
|
|
|
|
uppercaseOnly : false,
|
|
|
|
optionalLabels : false,
|
|
|
|
optionalWhitespace : true,
|
2020-08-19 17:05:30 +00:00
|
|
|
multipleStmtsPerLine : true,
|
2020-08-12 16:57:54 +00:00
|
|
|
varNaming : "*",
|
|
|
|
staticArrays : false,
|
|
|
|
sharedArrayNamespace : true,
|
|
|
|
defaultArrayBase : 0,
|
|
|
|
defaultArraySize : 11,
|
|
|
|
defaultValues : true,
|
|
|
|
stringConcat : true,
|
|
|
|
maxDimensions : 255,
|
|
|
|
maxDefArgs : 255,
|
|
|
|
maxStringLength : 255,
|
|
|
|
//maxElements : 32767, // TODO
|
|
|
|
tickComments : true,
|
|
|
|
hexOctalConsts : true,
|
|
|
|
validKeywords : [
|
|
|
|
'OPTION',
|
|
|
|
'CONSOLE','DATA','DEF','DEFUSR','DIM','END','ERASE','ERROR',
|
|
|
|
'FOR','GOTO','GOSUB','IF','THEN','ELSE','INPUT','LET','LINE',
|
|
|
|
'PRINT','LPRINT','USING','NEXT','ON','OUT','POKE',
|
|
|
|
'READ','REM','RESTORE','RESUME','RETURN','STOP','SWAP',
|
|
|
|
'TROFF','TRON','WAIT',
|
|
|
|
'CALL','CHAIN','COMMON','WHILE','WEND','WRITE','RANDOMIZE',
|
|
|
|
'TO','STEP',
|
2020-08-13 23:53:11 +00:00
|
|
|
'AND', 'NOT', 'OR', 'XOR', 'IMP', 'EQV', 'MOD'
|
2020-08-12 16:57:54 +00:00
|
|
|
],
|
|
|
|
validFunctions : [
|
|
|
|
'ABS','ASC','ATN','CDBL','CHR$','CINT','COS','CSNG','CVI','CVS','CVD',
|
|
|
|
'EOF','EXP','FIX','FRE','HEX$','INP','INPUT$','INSTR','INT',
|
|
|
|
'LEFT$','LEN','LOC','LOG','LPOS','MID$','MKI$','MKS$','MKD$',
|
|
|
|
'OCT$','PEEK','POS','RIGHT$','RND','SGN','SIN','SPACE$','SPC',
|
|
|
|
'SQR','STR$','STRING$','TAB','TAN','USR','VAL','VARPTR'
|
|
|
|
],
|
2020-08-13 23:53:11 +00:00
|
|
|
validOperators : [
|
|
|
|
'=', '<>', '<', '>', '<=', '>=', '+', '-', '*', '/', '^', '\\',
|
|
|
|
'AND', 'NOT', 'OR', 'XOR', 'IMP', 'EQV', 'MOD'
|
|
|
|
],
|
2020-08-12 16:57:54 +00:00
|
|
|
printZoneLength : 14,
|
|
|
|
numericPadding : true,
|
|
|
|
checkOverflow : false, // TODO: message displayed when overflow, division by zero = ok
|
|
|
|
testInitialFor : true,
|
|
|
|
optionalNextVar : true,
|
|
|
|
multipleNextVars : true,
|
|
|
|
bitwiseLogic : true,
|
|
|
|
checkOnGotoIndex : false,
|
2020-08-13 17:51:35 +00:00
|
|
|
computedGoto : false,
|
2020-08-12 16:57:54 +00:00
|
|
|
restoreWithLabel : true,
|
2020-08-13 17:51:35 +00:00
|
|
|
squareBrackets : false,
|
|
|
|
arraysContainChars : false,
|
|
|
|
endStmtRequired : false,
|
2020-08-15 11:53:13 +00:00
|
|
|
chainAssignments : false,
|
2020-08-16 17:16:29 +00:00
|
|
|
optionalLet : true,
|
2020-08-19 01:06:23 +00:00
|
|
|
compiledBlocks : false,
|
2020-08-09 02:36:21 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 16:08:43 +00:00
|
|
|
export const MODERN_BASIC : BASICOptions = {
|
|
|
|
dialectName: "MODERN",
|
2020-08-09 02:36:21 +00:00
|
|
|
asciiOnly : false,
|
|
|
|
uppercaseOnly : false,
|
|
|
|
optionalLabels : true,
|
2020-08-11 17:29:31 +00:00
|
|
|
optionalWhitespace : false,
|
2020-08-19 17:05:30 +00:00
|
|
|
multipleStmtsPerLine : true,
|
2020-08-11 17:29:31 +00:00
|
|
|
varNaming : "*",
|
|
|
|
staticArrays : false,
|
2020-08-09 02:36:21 +00:00
|
|
|
sharedArrayNamespace : false,
|
|
|
|
defaultArrayBase : 0,
|
2020-08-11 17:29:31 +00:00
|
|
|
defaultArraySize : 0, // DIM required
|
2020-08-09 16:23:49 +00:00
|
|
|
defaultValues : false,
|
2020-08-09 02:36:21 +00:00
|
|
|
stringConcat : true,
|
|
|
|
maxDimensions : 255,
|
2020-08-10 16:08:43 +00:00
|
|
|
maxDefArgs : 255,
|
2020-08-11 17:29:31 +00:00
|
|
|
maxStringLength : 2048, // TODO?
|
2020-08-09 02:36:21 +00:00
|
|
|
tickComments : true,
|
2020-08-12 15:51:18 +00:00
|
|
|
hexOctalConsts : true,
|
2020-08-09 02:36:21 +00:00
|
|
|
validKeywords : null, // all
|
2020-08-09 16:23:49 +00:00
|
|
|
validFunctions : null, // all
|
|
|
|
validOperators : null, // all
|
2020-08-11 17:29:31 +00:00
|
|
|
printZoneLength : 16,
|
2020-08-09 02:36:21 +00:00
|
|
|
numericPadding : false,
|
2020-08-05 04:48:29 +00:00
|
|
|
checkOverflow : true,
|
2020-08-11 17:29:31 +00:00
|
|
|
testInitialFor : true,
|
2020-08-12 15:11:10 +00:00
|
|
|
optionalNextVar : true,
|
2020-08-12 16:57:54 +00:00
|
|
|
multipleNextVars : true,
|
2020-08-09 16:23:49 +00:00
|
|
|
bitwiseLogic : true,
|
2020-08-13 23:53:11 +00:00
|
|
|
checkOnGotoIndex : true,
|
2020-08-16 17:16:29 +00:00
|
|
|
computedGoto : false,
|
2020-08-12 16:57:54 +00:00
|
|
|
restoreWithLabel : true,
|
2020-08-13 17:51:35 +00:00
|
|
|
squareBrackets : true,
|
|
|
|
arraysContainChars : false,
|
|
|
|
endStmtRequired : false,
|
2020-08-16 17:16:29 +00:00
|
|
|
chainAssignments : true,
|
|
|
|
optionalLet : true,
|
2020-08-19 01:06:23 +00:00
|
|
|
compiledBlocks : true,
|
2020-08-24 16:51:47 +00:00
|
|
|
multilineIfThen : true,
|
2020-08-05 04:48:29 +00:00
|
|
|
}
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
|
2020-08-09 02:36:21 +00:00
|
|
|
// TODO: integer vars
|
2020-08-11 17:29:31 +00:00
|
|
|
// TODO: DEFINT/DEFSTR
|
2020-08-15 11:53:13 +00:00
|
|
|
// TODO: excess INPUT ignored, error msg
|
2020-08-19 17:05:30 +00:00
|
|
|
// TODO: out of order line numbers
|
2020-08-09 02:36:21 +00:00
|
|
|
|
2020-08-20 17:58:14 +00:00
|
|
|
type BuiltinFunctionDef = [string, ValueType[], ValueType];
|
|
|
|
|
|
|
|
const BUILTIN_DEFS : BuiltinFunctionDef[] = [
|
|
|
|
['ABS', ['number'], 'number' ],
|
|
|
|
['ASC', ['string'], 'number' ],
|
|
|
|
['ATN', ['number'], 'number' ],
|
|
|
|
['CHR$', ['number'], 'string' ],
|
|
|
|
['CINT', ['number'], 'number' ],
|
|
|
|
['COS', ['number'], 'number' ],
|
|
|
|
['COT', ['number'], 'number' ],
|
|
|
|
['CTL', ['number'], 'string' ],
|
|
|
|
['EXP', ['number'], 'number' ],
|
|
|
|
['FIX', ['number'], 'number' ],
|
|
|
|
['HEX$', ['number'], 'string' ],
|
|
|
|
['INSTR', ['number', 'string', 'string'], 'number' ],
|
|
|
|
['INSTR', ['string', 'string'], 'number' ],
|
|
|
|
['INT', ['number'], 'number' ],
|
|
|
|
['LEFT$', ['string', 'number'], 'string' ],
|
|
|
|
['LEN', ['string'], 'number' ],
|
|
|
|
['LIN', ['number'], 'string' ],
|
|
|
|
['LOG', ['number'], 'number' ],
|
|
|
|
['LOG10', ['number'], 'number' ],
|
|
|
|
['MID$', ['string', 'number'], 'string'],
|
|
|
|
['MID$', ['string', 'number', 'number'], 'string'],
|
|
|
|
['OCT$', ['number'], 'string' ],
|
|
|
|
['PI', [], 'number'],
|
|
|
|
['POS', ['number'], 'number' ], // arg ignored
|
2020-08-21 03:20:53 +00:00
|
|
|
['POS', ['string','string'], 'number' ], // HP POS
|
2020-08-23 18:40:04 +00:00
|
|
|
['RIGHT$', ['string', 'number'], 'string' ],
|
2020-08-20 17:58:14 +00:00
|
|
|
['RND', [], 'number' ],
|
|
|
|
['RND', ['number'], 'number' ],
|
|
|
|
['ROUND', ['number'], 'number' ],
|
|
|
|
['SGN', ['number'], 'number' ],
|
|
|
|
['SIN', ['number'], 'number' ],
|
|
|
|
['SPACE$', ['number'], 'string' ],
|
|
|
|
['SPC', ['number'], 'string' ],
|
|
|
|
['SQR', ['number'], 'number' ],
|
|
|
|
['STR$', ['number'], 'string' ],
|
|
|
|
['STRING$', ['number', 'number'], 'string'],
|
|
|
|
['STRING$', ['number', 'string'], 'string'],
|
|
|
|
['TAB', ['number'], 'string' ],
|
|
|
|
['TAN', ['number'], 'number' ],
|
|
|
|
['TIM', ['number'], 'number' ], // only HP BASIC?
|
|
|
|
['TIMER', [], 'number' ],
|
|
|
|
['UPS$', ['string'], 'string' ],
|
|
|
|
['VAL', ['string'], 'number' ],
|
|
|
|
['LPAD$', ['string', 'number'], 'string' ],
|
|
|
|
['RPAD$', ['string', 'number'], 'string' ],
|
|
|
|
['NFORMAT$', ['number', 'number'], 'string' ],
|
|
|
|
];
|
|
|
|
|
|
|
|
var BUILTIN_MAP : { [name:string] : {args:ValueType[], result:ValueType}[] } = {};
|
|
|
|
BUILTIN_DEFS.forEach( (def, idx) => {
|
|
|
|
let [name, args, result] = def;
|
|
|
|
if (!BUILTIN_MAP[name]) BUILTIN_MAP[name] = [];
|
|
|
|
BUILTIN_MAP[name].push({args: args, result: result});
|
|
|
|
});
|
|
|
|
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
export const DIALECTS = {
|
2020-08-16 17:16:29 +00:00
|
|
|
"DEFAULT": MODERN_BASIC,
|
|
|
|
"DARTMOUTH": DARTMOUTH_4TH_EDITION,
|
|
|
|
"DARTMOUTH4": DARTMOUTH_4TH_EDITION,
|
2020-08-11 17:29:31 +00:00
|
|
|
"ALTAIR": ALTAIR_BASIC41,
|
2020-08-13 17:51:35 +00:00
|
|
|
"ALTAIR4": ALTAIR_BASIC41,
|
2020-08-11 17:29:31 +00:00
|
|
|
"ALTAIR41": ALTAIR_BASIC41,
|
2020-08-15 11:53:13 +00:00
|
|
|
"TINY": TINY_BASIC,
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
"ECMA55": ECMA55_MINIMAL,
|
|
|
|
"MINIMAL": ECMA55_MINIMAL,
|
2020-08-14 14:26:43 +00:00
|
|
|
"HP": HP_TIMESHARED_BASIC,
|
2020-08-13 17:51:35 +00:00
|
|
|
"HPB": HP_TIMESHARED_BASIC,
|
2020-08-14 14:26:43 +00:00
|
|
|
"HPTSB": HP_TIMESHARED_BASIC,
|
2020-08-13 17:51:35 +00:00
|
|
|
"HP2000": HP_TIMESHARED_BASIC,
|
|
|
|
"HPBASIC": HP_TIMESHARED_BASIC,
|
|
|
|
"HPACCESS": HP_TIMESHARED_BASIC,
|
2020-08-15 11:53:13 +00:00
|
|
|
"DEC11": DEC_BASIC_11,
|
|
|
|
"DEC": DEC_BASIC_PLUS,
|
|
|
|
"DECPLUS": DEC_BASIC_PLUS,
|
|
|
|
"BASICPLUS": DEC_BASIC_PLUS,
|
2020-08-11 17:29:31 +00:00
|
|
|
"BASICODE": BASICODE,
|
2020-08-09 02:36:21 +00:00
|
|
|
"APPLESOFT": APPLESOFT_BASIC,
|
2020-08-12 16:57:54 +00:00
|
|
|
"BASIC80": BASIC80,
|
2020-08-10 16:08:43 +00:00
|
|
|
"MODERN": MODERN_BASIC,
|
basic: added operators, check inputs, better line feed, fixed return, FOR stack, moved OPTION to compile-time, fixed DEFs, uppercase, hot-loading (43, 45, 36)
2020-08-08 16:22:56 +00:00
|
|
|
};
|