basic: bitwise logic, check DEFs, assign2js, check args (41, 39, 68)

This commit is contained in:
Steven Hugg 2020-08-09 11:23:49 -05:00
parent bef0c6e7e3
commit 167d60d402
4 changed files with 171 additions and 112 deletions

View File

@ -13,7 +13,7 @@ class CompileError extends Error {
// Lexer regular expression -- each (capture group) handles a different token type
const re_toks = /([0-9.]+[E][+-]?\d+)|(\d*[.]\d*[E0-9]*)|[0]*(\d+)|(['].*)|(\w+[$]?)|(".*?")|([<>]?[=<>])|([-+*/^,;:()?])|(\S+)/gi;
const re_toks = /([0-9.]+[E][+-]?\d+)|(\d*[.]\d*[E0-9]*)|[0]*(\d+)|(['].*)|(\w+[$]?)|(".*?")|([<>]?[=<>])|([-+*/^,;:()?\\])|(\S+)/gi;
export enum TokenType {
EOL = 0,
@ -48,7 +48,7 @@ export interface BinOp {
}
export interface UnOp {
op: 'neg' | 'lnot';
op: 'neg' | 'lnot' | 'bnot';
expr: Expr;
}
@ -176,8 +176,11 @@ class Token {
}
const OPERATORS = {
'OR': {f:'bor',p:7},
'AND': {f:'band',p:8},
'IMP': {f:'bimp',p:4},
'EQV': {f:'beqv',p:5},
'XOR': {f:'bxor',p:6},
'OR': {f:'lor',p:7}, // or "bor"
'AND': {f:'land',p:8}, // or "band"
'=': {f:'eq',p:50},
'<>': {f:'ne',p:50},
'<': {f:'lt',p:50},
@ -226,31 +229,32 @@ export interface BASICOptions {
uppercaseOnly : boolean; // convert everything to uppercase?
optionalLabels : boolean; // can omit line numbers and use labels?
strictVarNames : boolean; // only allow A0-9 for numerics, single letter for arrays/strings
tickComments : boolean; // support 'comments?
defaultValues : boolean; // initialize unset variables to default value? (0 or "")
sharedArrayNamespace : boolean; // arrays and variables have same namespace? (conflict)
defaultArrayBase : number; // arrays start at this number (0 or 1)
defaultArrayBase : number; // arrays start at this number (0 or 1) (TODO: check)
defaultArraySize : number; // arrays are allocated w/ this size (starting @ 0)
maxDimensions : number; // max number of dimensions for arrays
stringConcat : boolean; // can concat strings with "+" operator?
typeConvert : boolean; // type convert strings <-> numbers? (TODO)
checkOverflow : boolean; // check for overflow of numerics?
sparseArrays : boolean; // true == don't require DIM for arrays (TODO)
printZoneLength : number; // print zone length
numericPadding : boolean; // " " or "-" before and " " after numbers?
multipleNextVars : boolean; // NEXT Y,X (TODO)
ifElse : boolean; // IF...ELSE construct
bitwiseLogic : boolean; // -1 = TRUE, 0 = FALSE, AND/OR/NOT done with bitwise ops
maxDimensions : number; // max number of dimensions for arrays
maxDefArgs : number; // maximum # of arguments for user-defined functions
maxStringLength : number; // maximum string length in chars
sparseArrays : boolean; // true == don't require DIM for arrays (TODO)
tickComments : boolean; // support 'comments?
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)
printZoneLength : number; // print zone length
numericPadding : boolean; // " " or "-" before and " " after numbers?
checkOverflow : boolean; // check for overflow of numerics?
defaultValues : boolean; // initialize unset variables to default value? (0 or "")
multipleNextVars : boolean; // NEXT Y,X (TODO)
ifElse : boolean; // IF...ELSE construct
}
///// BASIC PARSER
export class BASICParser {
opts : BASICOptions = MAX8_BASIC;
opts : BASICOptions = ALTAIR_BASIC40;
errors: WorkerError[];
listings: CodeListingMap;
labels: { [label: string]: BASICLine };
@ -287,11 +291,11 @@ export class BASICParser {
var tok = this.lasttoken = (this.tokens.shift() || this.eol);
return tok;
}
expectToken(str: string) : Token {
expectToken(str: string, msg?: string) : Token {
var tok = this.consumeToken();
var tokstr = tok.str;
if (str != tokstr) {
this.compileError(`There should be a "${str}" here.`);
this.compileError(msg || `There should be a "${str}" here.`);
}
return tok;
}
@ -452,7 +456,7 @@ export class BASICParser {
if (this.peekToken().str == '(') {
this.expectToken('(');
args = this.parseExprList();
this.expectToken(')');
this.expectToken(')', `There should be another expression or a ")" here.`);
}
return { name: tok.str, args: args, $loc: tok.$loc };
default:
@ -513,7 +517,7 @@ export class BASICParser {
case TokenType.Ident:
if (tok.str == 'NOT') {
let expr = this.parsePrimary();
return { op: 'lnot', expr: expr };
return { op: this.opts.bitwiseLogic ? 'bnot' : 'lnot', expr: expr };
} else {
this.pushbackToken(tok);
return this.parseVarSubscriptOrFunc();
@ -521,13 +525,13 @@ export class BASICParser {
case TokenType.Operator:
if (tok.str == '(') {
let expr = this.parseExpr();
this.expectToken(')');
this.expectToken(')', `There should be another expression or a ")" here.`);
return expr;
} else if (tok.str == '-') {
let expr = this.parsePrimary(); // TODO: -2^2=-4 and -2-2=-4
return { op: 'neg', expr: expr };
} else if (tok.str == '+') {
return this.parsePrimary(); // TODO?
return this.parsePrimary(); // ignore unary +
}
case TokenType.EOL:
this.compileError(`The expression is incomplete.`);
@ -555,7 +559,10 @@ export class BASICParser {
right = this.parseExpr1(right, getPrecedence(look));
look = this.peekToken();
}
left = { op: getOperator(op.str).f, left: left, right: right };
var opfn = getOperator(op.str).f;
if (this.opts.bitwiseLogic && opfn == 'land') opfn = 'band';
if (this.opts.bitwiseLogic && opfn == 'lor') opfn = 'bor';
left = { op:opfn, left: left, right: right };
}
return left;
}
@ -818,6 +825,7 @@ export const ECMA55_MINIMAL : BASICOptions = {
checkOverflow : true,
multipleNextVars : false,
ifElse : false,
bitwiseLogic : false,
}
export const ALTAIR_BASIC40 : BASICOptions = {
@ -845,6 +853,7 @@ export const ALTAIR_BASIC40 : BASICOptions = {
checkOverflow : true,
multipleNextVars : true, // TODO: not supported
ifElse : true,
bitwiseLogic : true,
}
export const APPLESOFT_BASIC : BASICOptions = {
@ -874,6 +883,7 @@ export const APPLESOFT_BASIC : BASICOptions = {
checkOverflow : true,
multipleNextVars : false,
ifElse : false,
bitwiseLogic : false,
}
export const MAX8_BASIC : BASICOptions = {
@ -885,22 +895,23 @@ export const MAX8_BASIC : BASICOptions = {
sharedArrayNamespace : false,
defaultArrayBase : 0,
defaultArraySize : 11,
defaultValues : true,
defaultValues : false,
stringConcat : true,
typeConvert : true,
maxDimensions : 255,
maxDefArgs : 255, // TODO: no string FNs
maxStringLength : 1024*1024,
maxStringLength : 1024, // TODO?
sparseArrays : false,
tickComments : true,
validKeywords : null, // all
validFunctions : null,
validOperators : null,
validFunctions : null, // all
validOperators : null, // all
printZoneLength : 15,
numericPadding : false,
checkOverflow : true,
multipleNextVars : true,
ifElse : true,
bitwiseLogic : true,
}
// TODO: integer vars

View File

@ -1,5 +1,5 @@
import { BASICParser, ECMA55_MINIMAL } from "./compiler";
import { BASICParser, DIALECTS } from "./compiler";
import { BASICRuntime } from "./runtime";
var readline = require('readline');
@ -8,11 +8,24 @@ var rl = readline.createInterface({
output: process.stdout,
//terminal: false
});
var fs = require('fs');
var parser = new BASICParser();
parser.opts = ECMA55_MINIMAL;
var fs = require('fs');
var filename = process.argv[2];
var runtime = new BASICRuntime();
// parse args
var filename = '/dev/stdin';
var args = process.argv.slice(2);
for (var i=0; i<args.length; i++) {
if (args[i] == '-v')
runtime.trace = true;
else if (args[i] == '-d')
parser.opts = DIALECTS[args[++i]] || Error('no such dialect');
else
filename = args[i];
}
// parse file
var data = fs.readFileSync(filename, 'utf-8');
try {
var pgm = parser.parseFile(data, filename);
@ -25,8 +38,7 @@ try {
parser.errors.forEach((err) => console.log("@@@ " + err.msg));
if (parser.errors.length) process.exit(2);
var runtime = new BASICRuntime();
runtime.trace = process.argv[3] == '-v';
// run program
runtime.load(pgm);
runtime.reset();
runtime.print = (s:string) => {

View File

@ -16,7 +16,6 @@ function isUnOp(arg: basic.Expr): arg is basic.UnOp {
}
class ExprOptions {
check: boolean;
isconst?: boolean;
locals?: string[];
}
@ -35,6 +34,7 @@ export class BASICRuntime {
label2pc : {[label : string] : number};
datums : basic.Literal[];
builtins : {};
opts : basic.BASICOptions;
curpc : number;
dataptr : number;
@ -52,6 +52,7 @@ export class BASICRuntime {
load(program: basic.BASICProgram) {
let prevlabel = this.label2pc && this.getLabelForPC(this.curpc);
this.program = program;
this.opts = program.opts;
this.label2lineidx = {};
this.label2pc = {};
this.allstmts = [];
@ -72,7 +73,7 @@ export class BASICRuntime {
// parse DATA literals
line.stmts.filter((stmt) => stmt.command == 'DATA').forEach((datastmt) => {
(datastmt as basic.DATA_Statement).datums.forEach(d => {
var functext = this.expr2js(d, {check:true, isconst:true});
var functext = this.expr2js(d, {isconst:true});
var value = new Function(`return ${functext};`).bind(this)();
this.datums.push({value:value});
});
@ -99,7 +100,7 @@ export class BASICRuntime {
}
getBuiltinFunctions() {
var fnames = this.program && this.program.opts.validFunctions;
var fnames = this.program && this.opts.validFunctions;
// if no valid function list, look for ABC...() functions in prototype
if (!fnames) fnames = Object.keys(BASICRuntime.prototype).filter((name) => /^[A-Z]{3,}[$]?$/.test(name));
var dict = {};
@ -182,7 +183,7 @@ export class BASICRuntime {
do {
// in Altair BASIC, ELSE is bound to the right-most IF
// TODO: this is complicated, we should just have nested expressions
if (this.program.opts.ifElse) {
if (this.opts.ifElse) {
var cmd = this.allstmts[this.curpc].command;
if (cmd == 'ELSE') { this.curpc++; break; }
else if (cmd == 'IF') return this.skipToEOL();
@ -226,7 +227,7 @@ export class BASICRuntime {
var str;
if (typeof obj === 'number') {
var numstr = obj.toString().toUpperCase();
var numlen = this.program.opts.printZoneLength - 4;
var numlen = this.opts.printZoneLength - 4;
var prec = numlen;
while (numstr.length > numlen) {
numstr = obj.toPrecision(prec--);
@ -235,7 +236,7 @@ export class BASICRuntime {
numstr = numstr.substr(1);
else if (numstr.startsWith('-0.'))
numstr = '-'+numstr.substr(2);
if (!this.program.opts.numericPadding)
if (!this.opts.numericPadding)
str = numstr;
else if (numstr.startsWith('-'))
str = `${numstr} `;
@ -245,8 +246,8 @@ export class BASICRuntime {
this.column = 0;
str = obj;
} else if (obj == '\t') {
var curgroup = Math.floor(this.column / this.program.opts.printZoneLength);
var nextcol = (curgroup + 1) * this.program.opts.printZoneLength;
var curgroup = Math.floor(this.column / this.opts.printZoneLength);
var nextcol = (curgroup + 1) * this.opts.printZoneLength;
str = this.TAB(nextcol);
} else {
str = `${obj}`;
@ -273,31 +274,17 @@ export class BASICRuntime {
// override this
resume() { }
expr2js(expr: basic.Expr, opts: ExprOptions) : string {
expr2js(expr: basic.Expr, opts?: ExprOptions) : string {
if (!opts) opts = {};
if (isLiteral(expr)) {
return JSON.stringify(expr.value);
} else if (isLookup(expr)) {
if (opts.locals && opts.locals.indexOf(expr.name) >= 0) {
if (!expr.args && opts.locals && opts.locals.indexOf(expr.name) >= 0) {
return expr.name; // local arg in DEF
} else {
if (opts.isconst) this.runtimeError(`I expected a constant value here`);
var s = '';
if (expr.name.startsWith("FN")) { // is it a user-defined function?
let jsargs = expr.args && expr.args.map((arg) => this.expr2js(arg, opts)).join(', ');
s += `this.defs.${expr.name}(${jsargs})`; // TODO: what if no exist?
} else if (this.builtins[expr.name]) { // is it a built-in function?
let jsargs = expr.args && expr.args.map((arg) => this.expr2js(arg, opts)).join(', ');
s += `this.builtins.${expr.name}(${jsargs})`;
} else if (expr.args) { // is it a subscript?
s += `this.getArray(${JSON.stringify(expr.name)}, ${expr.args.length})`;
s += expr.args.map((arg) => '[this.ROUND('+this.expr2js(arg, opts)+')]').join('');
} else { // just a variable
s = `this.vars.${expr.name}`;
}
if (opts.check)
return `this.checkValue(${s}, ${JSON.stringify(expr.name)})`;
else
return s;
if (opts.isconst) this.runtimeError(`I expected a constant value here.`);
var s = this.assign2js(expr, opts);
return `this.checkValue(${s}, ${JSON.stringify(expr.name)})`;
}
} else if (isBinOp(expr)) {
var left = this.expr2js(expr.left, opts);
@ -309,6 +296,38 @@ export class BASICRuntime {
}
}
assign2js(expr: basic.IndOp, opts?: ExprOptions) {
if (!opts) opts = {};
var s = '';
var qname = JSON.stringify(expr.name);
if (expr.name.startsWith("FN")) { // is it a user-defined function?
// TODO: check argument count?
let jsargs = expr.args && expr.args.map((arg) => this.expr2js(arg, opts)).join(', ');
s += `this.getDef(${qname})(${jsargs})`;
// TODO: detect recursion?
} else if (this.builtins[expr.name]) { // is it a built-in function?
this.checkFuncArgs(expr, this.builtins[expr.name]);
let jsargs = expr.args && expr.args.map((arg) => this.expr2js(arg, opts)).join(', ');
s += `this.builtins.${expr.name}(${jsargs})`;
} else if (expr.args) { // is it a subscript?
// TODO: check array bounds?
s += `this.getArray(${qname}, ${expr.args.length})`;
s += expr.args.map((arg) => '[this.ROUND('+this.expr2js(arg, opts)+')]').join('');
} else { // just a variable
s = `this.vars.${expr.name}`;
}
return s;
}
checkFuncArgs(expr: basic.IndOp, fn: Function) {
// TODO: check types?
var nargs = expr.args ? expr.args.length : 0;
if (expr.name == 'MID$' && nargs == 2) return;
if (expr.name == 'INSTR' && nargs == 2) return;
if (fn.length != nargs)
this.runtimeError(`I expected ${fn.length} arguments for the ${expr.name} function, but I got ${nargs}.`);
}
startForLoop(forname, init, targ, step) {
// TODO: support 0-iteration loops
var pc = this.curpc;
@ -339,7 +358,7 @@ export class BASICRuntime {
// converts a variable to string/number based on var name
assign(name: string, right: number|string) : number|string {
if (this.program.opts.typeConvert)
if (this.opts.typeConvert)
return this.convert(name, right);
// TODO: use options
if (name.endsWith("$")) {
@ -371,11 +390,12 @@ export class BASICRuntime {
// dimension array
dimArray(name: string, ...dims:number[]) {
// TODO: maybe do this check at compile-time?
if (this.arrays[name]) this.runtimeError(`I already dimensioned this array (${name}) earlier.`)
var isstring = name.endsWith('$');
// if defaultValues is true, we use Float64Array which inits to 0
var arrcons = isstring || !this.program.opts.defaultValues ? Array : Float64Array;
// TODO? var ab = this.program.opts.defaultArrayBase;
var arrcons = isstring || !this.opts.defaultValues ? Array : Float64Array;
// TODO? var ab = this.opts.defaultArrayBase;
if (dims.length == 1) {
this.arrays[name] = new arrcons(dims[0]+1);
} else if (dims.length == 2) {
@ -390,9 +410,9 @@ export class BASICRuntime {
getArray(name: string, order: number) : [] {
if (!this.arrays[name]) {
if (order == 1)
this.dimArray(name, this.program.opts.defaultArraySize);
this.dimArray(name, this.opts.defaultArraySize);
else if (order == 2)
this.dimArray(name, this.program.opts.defaultArraySize, this.program.opts.defaultArraySize);
this.dimArray(name, this.opts.defaultArraySize, this.opts.defaultArraySize);
else
this.runtimeError(`I only support arrays of one or two dimensions.`); // TODO
}
@ -417,17 +437,17 @@ export class BASICRuntime {
do__PRINT(stmt : basic.PRINT_Statement) {
var s = '';
for (var arg of stmt.args) {
var expr = this.expr2js(arg, {check:true});
var expr = this.expr2js(arg);
s += `this.printExpr(${expr});`;
}
return s;
}
do__INPUT(stmt : basic.INPUT_Statement) {
var prompt = this.expr2js(stmt.prompt, {check:true});
var prompt = this.expr2js(stmt.prompt);
var setvals = '';
stmt.args.forEach((arg, index) => {
var lexpr = this.expr2js(arg, {check:false});
var lexpr = this.assign2js(arg);
setvals += `valid &= this.isValid(${lexpr} = this.convert(${JSON.stringify(arg.name)}, vals[${index}]));`
});
return `this.running=false;
@ -442,16 +462,16 @@ export class BASICRuntime {
do__LET(stmt : basic.LET_Statement) {
// TODO: range-checking for subscripts (get and set)
var lexpr = this.expr2js(stmt.lexpr, {check:false});
var right = this.expr2js(stmt.right, {check:true});
var lexpr = this.assign2js(stmt.lexpr);
var right = this.expr2js(stmt.right);
return `${lexpr} = this.assign(${JSON.stringify(stmt.lexpr.name)}, ${right});`;
}
do__FOR(stmt : basic.FOR_Statement) {
var name = JSON.stringify(stmt.lexpr.name); // TODO: args?
var init = this.expr2js(stmt.initial, {check:true});
var targ = this.expr2js(stmt.target, {check:true});
var step = stmt.step ? this.expr2js(stmt.step, {check:true}) : 'null';
var init = this.expr2js(stmt.initial);
var targ = this.expr2js(stmt.target);
var step = stmt.step ? this.expr2js(stmt.step) : 'null';
return `this.startForLoop(${name}, ${init}, ${targ}, ${step})`;
}
@ -461,7 +481,7 @@ export class BASICRuntime {
}
do__IF(stmt : basic.IF_Statement) {
var cond = this.expr2js(stmt.cond, {check:true});
var cond = this.expr2js(stmt.cond);
return `if (!(${cond})) { this.skipToElse(); }`
}
@ -478,17 +498,16 @@ export class BASICRuntime {
this.runtimeError("I found a DEF statement with arguments other than variable names.");
}
}
var functext = this.expr2js(stmt.def, {check:true, locals:args});
var functext = this.expr2js(stmt.def, {locals:args});
//this.defs[stmt.lexpr.name] = new Function(args.join(','), functext).bind(this);
var lexpr = `this.defs.${stmt.lexpr.name}`;
return `${lexpr} = function(${args.join(',')}) { return ${functext}; }.bind(this)`;
return `this.defs.${stmt.lexpr.name} = function(${args.join(',')}) { return ${functext}; }.bind(this)`;
}
_DIM(dim : basic.IndOp) {
var argsstr = '';
for (var arg of dim.args) {
// TODO: check for float (or at compile time)
argsstr += ', ' + this.expr2js(arg, {check:true});
argsstr += ', ' + this.expr2js(arg);
}
return `this.dimArray(${JSON.stringify(dim.name)}${argsstr});`;
}
@ -500,12 +519,12 @@ export class BASICRuntime {
}
do__GOTO(stmt : basic.GOTO_Statement) {
var label = this.expr2js(stmt.label, {check:true});
var label = this.expr2js(stmt.label, {isconst:true});
return `this.gotoLabel(${label})`;
}
do__GOSUB(stmt : basic.GOSUB_Statement) {
var label = this.expr2js(stmt.label, {check:true});
var label = this.expr2js(stmt.label, {isconst:true});
return `this.gosubLabel(${label})`;
}
@ -514,8 +533,8 @@ export class BASICRuntime {
}
do__ONGOTO(stmt : basic.ONGOTO_Statement) {
var expr = this.expr2js(stmt.expr, {check:true});
var labels = stmt.labels.map((arg) => this.expr2js(arg, {check:true})).join(', ');
var expr = this.expr2js(stmt.expr);
var labels = stmt.labels.map((arg) => this.expr2js(arg, {isconst:true})).join(', ');
return `this.onGotoLabel(${expr}, ${labels})`;
}
@ -526,7 +545,7 @@ export class BASICRuntime {
do__READ(stmt : basic.READ_Statement) {
var s = '';
stmt.args.forEach((arg) => {
s += `${this.expr2js(arg, {check:false})} = this.assign(${JSON.stringify(arg.name)}, this.nextDatum());`;
s += `${this.assign2js(arg)} = this.assign(${JSON.stringify(arg.name)}, this.nextDatum());`;
});
return s;
}
@ -552,7 +571,7 @@ export class BASICRuntime {
}
do__GET(stmt : basic.GET_Statement) {
var lexpr = this.expr2js(stmt.lexpr, {check:false});
var lexpr = this.assign2js(stmt.lexpr);
// TODO: single key input
return `this.running=false;
this.input().then((vals) => {
@ -584,11 +603,11 @@ export class BASICRuntime {
// check for unreferenced value
if (typeof obj !== 'number' && typeof obj !== 'string') {
// assign default value?
if (obj == null && this.program.opts.defaultValues) {
if (obj == null && this.opts.defaultValues) {
return exprname.endsWith("$") ? "" : 0;
}
if (exprname != null && obj == null) {
this.runtimeError(`I didn't find a value for ${exprname}`);
this.runtimeError(`I haven't set a value for ${exprname}.`);
} else if (exprname != null) {
this.runtimeError(`I got an invalid value for ${exprname}: ${obj}`);
} else {
@ -597,18 +616,21 @@ export class BASICRuntime {
}
return obj;
}
getDef(exprname: string) {
var fn = this.defs[exprname];
if (!fn) this.runtimeError(`I haven't run a DEF statement for ${exprname}.`);
return fn;
}
checkNum(n:number) : number {
if (n === Infinity) this.runtimeError(`I computed a number too big to store.`);
if (isNaN(n)) this.runtimeError(`I computed an invalid number.`);
return n;
}
checkString(s:string) : string {
if (typeof s !== 'string')
this.runtimeError(`I expected a string here.`);
else if (s.length > this.program.opts.maxStringLength)
this.dialectError(`create strings longer than ${this.program.opts.maxStringLength} characters`);
else if (s.length > this.opts.maxStringLength)
this.dialectError(`create strings longer than ${this.opts.maxStringLength} characters`);
return s;
}
@ -616,7 +638,7 @@ export class BASICRuntime {
// TODO: if string-concat
if (typeof a === 'number' && typeof b === 'number')
return this.checkNum(a + b);
else if (this.program.opts.stringConcat)
else if (this.opts.stringConcat)
return this.checkString(a + b);
else
this.dialectError(`use the "+" operator to concatenate strings`)
@ -632,7 +654,7 @@ export class BASICRuntime {
return this.checkNum(a / b);
}
idiv(a:number, b:number) : number {
return this.div(Math.floor(a), Math.floor(b));
return this.FIX(this.INT(a) / this.INT(b));
}
mod(a:number, b:number) : number {
return this.checkNum(a % b);
@ -641,41 +663,53 @@ export class BASICRuntime {
if (a == 0 && b < 0) this.runtimeError(`I can't raise zero to a negative power.`);
return this.checkNum(Math.pow(a, b));
}
land(a:number, b:number) : number {
return a && b;
}
lor(a:number, b:number) : number {
return a || b;
}
lnot(a:number) : number {
return a ? 0 : 1;
}
neg(a:number) : number {
return -a;
}
band(a:number, b:number) : number {
return a & b;
}
bor(a:number, b:number) : number {
return a | b;
}
bnot(a:number) : number {
return ~a;
}
bxor(a:number, b:number) : number {
return a ^ b;
}
bimp(a:number, b:number) : number {
return this.bor(this.bnot(a), b);
}
beqv(a:number, b:number) : number {
return this.bnot(this.bxor(a, b));
}
land(a:number, b:number) : number {
return a && b ? (this.opts.bitwiseLogic ? -1 : 1) : 0;
}
lor(a:number, b:number) : number {
return a || b ? (this.opts.bitwiseLogic ? -1 : 1) : 0;
}
lnot(a:number) : number {
return a ? 0 : (this.opts.bitwiseLogic ? -1 : 1);
}
neg(a:number) : number {
return -a;
}
eq(a:number, b:number) : number {
return a == b ? 1 : 0;
return a == b ? (this.opts.bitwiseLogic ? -1 : 1) : 0;
}
ne(a:number, b:number) : number {
return a != b ? 1 : 0;
return a != b ? (this.opts.bitwiseLogic ? -1 : 1) : 0;
}
lt(a:number, b:number) : number {
return a < b ? 1 : 0;
return a < b ? (this.opts.bitwiseLogic ? -1 : 1) : 0;
}
gt(a:number, b:number) : number {
return a > b ? 1 : 0;
return a > b ? (this.opts.bitwiseLogic ? -1 : 1) : 0;
}
le(a:number, b:number) : number {
return a <= b ? 1 : 0;
return a <= b ? (this.opts.bitwiseLogic ? -1 : 1) : 0;
}
ge(a:number, b:number) : number {
return a >= b ? 1 : 0;
return a >= b ? (this.opts.bitwiseLogic ? -1 : 1) : 0;
}
// FUNCTIONS (uppercase)
@ -702,7 +736,7 @@ export class BASICRuntime {
return this.checkNum(Math.exp(arg));
}
FIX(arg : number) : number {
return this.checkNum(arg - Math.floor(arg));
return this.checkNum(arg < 0 ? Math.ceil(arg) : Math.floor(arg));
}
HEX$(arg : number) : string {
return arg.toString(16);
@ -729,7 +763,8 @@ export class BASICRuntime {
return this.checkNum(Math.log(arg));
}
MID$(arg : string, start : number, count : number) : string {
if (start < 1) this.runtimeError(`I tried to compute MID$ but the second parameter is less than zero (${start}).`)
if (start < 1) this.runtimeError(`I can't compute MID$ if the starting index is less than 1.`)
if (count == 0) count = arg.length;
return arg.substr(start-1, count);
}
RIGHT$(arg : string, count : number) : string {

View File

@ -357,6 +357,7 @@ class BASICPlatform implements Platform {
resize: () => void;
loadROM(title, data) {
// TODO: only hot reload when we hit a label?
var didExit = this.runtime.exited;
this.program = data;
this.runtime.load(data);