mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-26 10:49:17 +00:00
basic: GET, POP, fixed func/builtins, dialect stuff, print head, ELSE (44, 44, 68)
This commit is contained in:
parent
a1efa8eebd
commit
bef0c6e7e3
19
css/ui.css
19
css/ui.css
@ -353,7 +353,7 @@ div.replaydiv {
|
|||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: 50%;
|
background-position: 50%;
|
||||||
pointer-events:auto;
|
pointer-events:auto;
|
||||||
z-index:2;
|
z-index:4;
|
||||||
}
|
}
|
||||||
.gutter.gutter-horizontal {
|
.gutter.gutter-horizontal {
|
||||||
background-image: url('grips/vertical.png');
|
background-image: url('grips/vertical.png');
|
||||||
@ -424,7 +424,7 @@ div.markdown th {
|
|||||||
user-select: auto;
|
user-select: auto;
|
||||||
}
|
}
|
||||||
.alert {
|
.alert {
|
||||||
z-index:2;
|
z-index:8;
|
||||||
}
|
}
|
||||||
.segment {
|
.segment {
|
||||||
border: 2px solid rgba(0,0,0,0.2);
|
border: 2px solid rgba(0,0,0,0.2);
|
||||||
@ -637,7 +637,20 @@ div.asset_toolbar {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 1;
|
z-index: 6;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.transcript-print-head.printing {
|
||||||
|
height: 5em;
|
||||||
|
}
|
||||||
|
.transcript-print-shield {
|
||||||
|
background: '#ffffff';
|
||||||
|
background: linear-gradient(0deg, rgba(0,0,0,0.5) 0%, rgba(255,255,255,0) 56%, rgba(0,0,0,0) 100%);
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
height: 3em;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 5;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.tree-header {
|
.tree-header {
|
||||||
|
@ -13,7 +13,7 @@ class CompileError extends Error {
|
|||||||
|
|
||||||
// Lexer regular expression -- each (capture group) handles a different token type
|
// Lexer regular expression -- each (capture group) handles a different token type
|
||||||
|
|
||||||
const re_toks = /([0-9.]+[E][+-]?\d+)|(\d*[.]\d*[E0-9]*)|(\d+)|(['].*)|(\w+[$]?)|(".*?")|([<>]?[=<>])|([-+*/^,;:()])|(\S+)/gi;
|
const re_toks = /([0-9.]+[E][+-]?\d+)|(\d*[.]\d*[E0-9]*)|[0]*(\d+)|(['].*)|(\w+[$]?)|(".*?")|([<>]?[=<>])|([-+*/^,;:()?])|(\S+)/gi;
|
||||||
|
|
||||||
export enum TokenType {
|
export enum TokenType {
|
||||||
EOL = 0,
|
EOL = 0,
|
||||||
@ -68,6 +68,11 @@ export interface LET_Statement {
|
|||||||
right: Expr;
|
right: Expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DIM_Statement {
|
||||||
|
command: "DIM";
|
||||||
|
args: IndOp[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface GOTO_Statement {
|
export interface GOTO_Statement {
|
||||||
command: "GOTO";
|
command: "GOTO";
|
||||||
label: Expr;
|
label: Expr;
|
||||||
@ -82,11 +87,21 @@ export interface RETURN_Statement {
|
|||||||
command: "RETURN";
|
command: "RETURN";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ONGOTO_Statement {
|
||||||
|
command: "ONGOTO";
|
||||||
|
expr: Expr;
|
||||||
|
labels: Expr[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface IF_Statement {
|
export interface IF_Statement {
|
||||||
command: "IF";
|
command: "IF";
|
||||||
cond: Expr;
|
cond: Expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ELSE_Statement {
|
||||||
|
command: "ELSE";
|
||||||
|
}
|
||||||
|
|
||||||
export interface FOR_Statement {
|
export interface FOR_Statement {
|
||||||
command: "FOR";
|
command: "FOR";
|
||||||
lexpr: IndOp;
|
lexpr: IndOp;
|
||||||
@ -100,19 +115,19 @@ export interface NEXT_Statement {
|
|||||||
lexpr?: IndOp;
|
lexpr?: IndOp;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DIM_Statement {
|
|
||||||
command: "DIM";
|
|
||||||
args: IndOp[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface INPUT_Statement {
|
export interface INPUT_Statement {
|
||||||
command: "INPUT";
|
command: "INPUT";
|
||||||
prompt: Expr;
|
prompt: Expr;
|
||||||
args: IndOp[];
|
args: IndOp[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DATA_Statement {
|
||||||
|
command: "DATA";
|
||||||
|
datums: Expr[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface READ_Statement {
|
export interface READ_Statement {
|
||||||
command: "INPUT";
|
command: "READ";
|
||||||
args: IndOp[];
|
args: IndOp[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,25 +137,25 @@ export interface DEF_Statement {
|
|||||||
def: Expr;
|
def: Expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ONGOTO_Statement {
|
|
||||||
command: "ONGOTO";
|
|
||||||
expr: Expr;
|
|
||||||
labels: Expr[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DATA_Statement {
|
|
||||||
command: "DATA";
|
|
||||||
datums: Expr[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OPTION_Statement {
|
export interface OPTION_Statement {
|
||||||
command: "OPTION";
|
command: "OPTION";
|
||||||
optname: string;
|
optname: string;
|
||||||
optargs: string[];
|
optargs: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GET_Statement {
|
||||||
|
command: "GET";
|
||||||
|
lexpr: IndOp;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NoArgStatement {
|
||||||
|
command: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type StatementTypes = PRINT_Statement | LET_Statement | GOTO_Statement | GOSUB_Statement
|
export type StatementTypes = PRINT_Statement | LET_Statement | GOTO_Statement | GOSUB_Statement
|
||||||
| IF_Statement | FOR_Statement | DATA_Statement;
|
| IF_Statement | FOR_Statement | NEXT_Statement | DIM_Statement
|
||||||
|
| INPUT_Statement | READ_Statement | DEF_Statement | ONGOTO_Statement
|
||||||
|
| DATA_Statement | OPTION_Statement | NoArgStatement;
|
||||||
|
|
||||||
export type Statement = StatementTypes & SourceLocated;
|
export type Statement = StatementTypes & SourceLocated;
|
||||||
|
|
||||||
@ -195,7 +210,8 @@ function getPrecedence(tok: Token): number {
|
|||||||
|
|
||||||
// is token an end of statement marker? (":" or end of line)
|
// is token an end of statement marker? (":" or end of line)
|
||||||
function isEOS(tok: Token) {
|
function isEOS(tok: Token) {
|
||||||
return (tok.type == TokenType.EOL) || (tok.type == TokenType.Operator && tok.str == ':');
|
return tok.type == TokenType.EOL || tok.type == TokenType.Remark
|
||||||
|
|| tok.str == ':' || tok.str == 'ELSE'; // TODO: only ELSE if ifElse==true
|
||||||
}
|
}
|
||||||
|
|
||||||
function stripQuotes(s: string) {
|
function stripQuotes(s: string) {
|
||||||
@ -205,6 +221,8 @@ function stripQuotes(s: string) {
|
|||||||
|
|
||||||
// TODO: implement these
|
// TODO: implement these
|
||||||
export interface BASICOptions {
|
export interface BASICOptions {
|
||||||
|
dialectName : string; // use this to select the dialect
|
||||||
|
asciiOnly : boolean; // reject non-ASCII chars?
|
||||||
uppercaseOnly : boolean; // convert everything to uppercase?
|
uppercaseOnly : boolean; // convert everything to uppercase?
|
||||||
optionalLabels : boolean; // can omit line numbers and use labels?
|
optionalLabels : boolean; // can omit line numbers and use labels?
|
||||||
strictVarNames : boolean; // only allow A0-9 for numerics, single letter for arrays/strings
|
strictVarNames : boolean; // only allow A0-9 for numerics, single letter for arrays/strings
|
||||||
@ -213,24 +231,26 @@ export interface BASICOptions {
|
|||||||
defaultArraySize : number; // arrays are allocated w/ this size (starting @ 0)
|
defaultArraySize : number; // arrays are allocated w/ this size (starting @ 0)
|
||||||
maxDimensions : number; // max number of dimensions for arrays
|
maxDimensions : number; // max number of dimensions for arrays
|
||||||
stringConcat : boolean; // can concat strings with "+" operator?
|
stringConcat : boolean; // can concat strings with "+" operator?
|
||||||
typeConvert : boolean; // type convert strings <-> numbers?
|
typeConvert : boolean; // type convert strings <-> numbers? (TODO)
|
||||||
maxArguments : number; // maximum # of arguments for user-defined functions
|
maxDefArgs : number; // maximum # of arguments for user-defined functions
|
||||||
sparseArrays : boolean; // true == don't require DIM for arrays
|
maxStringLength : number; // maximum string length in chars
|
||||||
|
sparseArrays : boolean; // true == don't require DIM for arrays (TODO)
|
||||||
tickComments : boolean; // support 'comments?
|
tickComments : boolean; // support 'comments?
|
||||||
validKeywords : string[]; // valid keywords (or null for accept all)
|
validKeywords : string[]; // valid keywords (or null for accept all)
|
||||||
validFunctions : string[]; // valid functions (or null for accept all)
|
validFunctions : string[]; // valid functions (or null for accept all)
|
||||||
validOperators : string[]; // valid operators (or null for accept all)
|
validOperators : string[]; // valid operators (or null for accept all)
|
||||||
printZoneLength : number; // print zone length
|
printZoneLength : number; // print zone length
|
||||||
printPrecision : number; // print precision # of digits
|
numericPadding : boolean; // " " or "-" before and " " after numbers?
|
||||||
checkOverflow : boolean; // check for overflow of numerics?
|
checkOverflow : boolean; // check for overflow of numerics?
|
||||||
defaultValues : boolean; // initialize unset variables to default value? (0 or "")
|
defaultValues : boolean; // initialize unset variables to default value? (0 or "")
|
||||||
multipleNextVars : boolean; // NEXT Y,X
|
multipleNextVars : boolean; // NEXT Y,X (TODO)
|
||||||
|
ifElse : boolean; // IF...ELSE construct
|
||||||
}
|
}
|
||||||
|
|
||||||
///// BASIC PARSER
|
///// BASIC PARSER
|
||||||
|
|
||||||
export class BASICParser {
|
export class BASICParser {
|
||||||
opts : BASICOptions = ALTAIR_BASIC40;
|
opts : BASICOptions = MAX8_BASIC;
|
||||||
errors: WorkerError[];
|
errors: WorkerError[];
|
||||||
listings: CodeListingMap;
|
listings: CodeListingMap;
|
||||||
labels: { [label: string]: BASICLine };
|
labels: { [label: string]: BASICLine };
|
||||||
@ -285,6 +305,16 @@ export class BASICParser {
|
|||||||
parseOptLabel(line: BASICLine) {
|
parseOptLabel(line: BASICLine) {
|
||||||
let tok = this.consumeToken();
|
let tok = this.consumeToken();
|
||||||
switch (tok.type) {
|
switch (tok.type) {
|
||||||
|
case TokenType.Ident:
|
||||||
|
if (this.opts.optionalLabels) {
|
||||||
|
if (this.peekToken().str == ':') { // is it a label:
|
||||||
|
this.consumeToken(); // eat the ":"
|
||||||
|
// fall through to the next case
|
||||||
|
} else {
|
||||||
|
this.pushbackToken(tok); // nope
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else this.dialectError(`optional line numbers`);
|
||||||
case TokenType.Int:
|
case TokenType.Int:
|
||||||
if (this.labels[tok.str] != null) this.compileError(`There's a duplicated label "${tok.str}".`);
|
if (this.labels[tok.str] != null) this.compileError(`There's a duplicated label "${tok.str}".`);
|
||||||
this.labels[tok.str] = line;
|
this.labels[tok.str] = line;
|
||||||
@ -296,8 +326,8 @@ export class BASICParser {
|
|||||||
this.compileError(`Line numbers must be positive integers.`);
|
this.compileError(`Line numbers must be positive integers.`);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (this.opts.optionalLabels) this.pushbackToken(tok);
|
if (this.opts.optionalLabels) this.compileError(`A line must start with a line number, command, or label.`);
|
||||||
else this.dialectError(`optional line numbers`);
|
else this.compileError(`A line must start with a line number.`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -325,6 +355,9 @@ export class BASICParser {
|
|||||||
for (var i = 1; i < TokenType._LAST; i++) {
|
for (var i = 1; i < TokenType._LAST; i++) {
|
||||||
let s : string = m[i];
|
let s : string = m[i];
|
||||||
if (s != null) {
|
if (s != null) {
|
||||||
|
// maybe we don't support unicode in 1975?
|
||||||
|
if (this.opts.asciiOnly && !/^[\x00-\x7F]*$/.test(s))
|
||||||
|
this.dialectError(`non-ASCII characters`);
|
||||||
// uppercase all identifiers, and maybe more
|
// uppercase all identifiers, and maybe more
|
||||||
if (i == TokenType.Ident || this.opts.uppercaseOnly)
|
if (i == TokenType.Ident || this.opts.uppercaseOnly)
|
||||||
s = s.toUpperCase();
|
s = s.toUpperCase();
|
||||||
@ -354,9 +387,17 @@ export class BASICParser {
|
|||||||
}
|
}
|
||||||
parseCompoundStatement(): Statement[] {
|
parseCompoundStatement(): Statement[] {
|
||||||
var list = this.parseList(this.parseStatement, ':');
|
var list = this.parseList(this.parseStatement, ':');
|
||||||
if (!isEOS(this.peekToken())) this.compileError(`Expected end of line or ':'`, this.peekToken().$loc);
|
var next = this.peekToken();
|
||||||
|
if (!isEOS(next))
|
||||||
|
this.compileError(`Expected end of line or ':'`, next.$loc);
|
||||||
|
if (next.str == 'ELSE')
|
||||||
|
return list.concat(this.parseCompoundStatement());
|
||||||
|
else
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
validKeyword(keyword: string) : string {
|
||||||
|
return (this.opts.validKeywords && this.opts.validKeywords.indexOf(keyword) < 0) ? null : keyword;
|
||||||
|
}
|
||||||
parseStatement(): Statement | null {
|
parseStatement(): Statement | null {
|
||||||
var cmdtok = this.consumeToken();
|
var cmdtok = this.consumeToken();
|
||||||
var cmd = cmdtok.str;
|
var cmd = cmdtok.str;
|
||||||
@ -365,6 +406,8 @@ export class BASICParser {
|
|||||||
case TokenType.Remark:
|
case TokenType.Remark:
|
||||||
if (!this.opts.tickComments) this.dialectError(`tick remarks`);
|
if (!this.opts.tickComments) this.dialectError(`tick remarks`);
|
||||||
return null;
|
return null;
|
||||||
|
case TokenType.Operator:
|
||||||
|
if (cmd == this.validKeyword('?')) cmd = 'PRINT';
|
||||||
case TokenType.Ident:
|
case TokenType.Ident:
|
||||||
// remark? ignore all tokens to eol
|
// remark? ignore all tokens to eol
|
||||||
if (cmd == 'REM') {
|
if (cmd == 'REM') {
|
||||||
@ -382,7 +425,7 @@ export class BASICParser {
|
|||||||
// lookup JS function for command
|
// lookup JS function for command
|
||||||
var fn = this['stmt__' + cmd];
|
var fn = this['stmt__' + cmd];
|
||||||
if (fn) {
|
if (fn) {
|
||||||
if (this.opts.validKeywords && this.opts.validKeywords.indexOf(cmd) < 0)
|
if (this.validKeyword(cmd) == null)
|
||||||
this.dialectError(`the ${cmd} keyword`);
|
this.dialectError(`the ${cmd} keyword`);
|
||||||
stmt = fn.bind(this)() as Statement;
|
stmt = fn.bind(this)() as Statement;
|
||||||
break;
|
break;
|
||||||
@ -409,8 +452,6 @@ export class BASICParser {
|
|||||||
if (this.peekToken().str == '(') {
|
if (this.peekToken().str == '(') {
|
||||||
this.expectToken('(');
|
this.expectToken('(');
|
||||||
args = this.parseExprList();
|
args = this.parseExprList();
|
||||||
if (args && args.length > this.opts.maxArguments)
|
|
||||||
this.compileError(`There can be no more than ${this.opts.maxArguments} arguments to a function or subscript.`);
|
|
||||||
this.expectToken(')');
|
this.expectToken(')');
|
||||||
}
|
}
|
||||||
return { name: tok.str, args: args, $loc: tok.$loc };
|
return { name: tok.str, args: args, $loc: tok.$loc };
|
||||||
@ -449,13 +490,15 @@ export class BASICParser {
|
|||||||
parseLabel() : Expr {
|
parseLabel() : Expr {
|
||||||
var tok = this.consumeToken();
|
var tok = this.consumeToken();
|
||||||
switch (tok.type) {
|
switch (tok.type) {
|
||||||
|
case TokenType.Ident:
|
||||||
|
if (!this.opts.optionalLabels) this.dialectError(`labels other than line numbers`)
|
||||||
case TokenType.Int:
|
case TokenType.Int:
|
||||||
var label = parseInt(tok.str).toString();
|
var label = tok.str;
|
||||||
this.targets[label] = tok.$loc;
|
this.targets[label] = tok.$loc;
|
||||||
return {value:label};
|
return {value:label};
|
||||||
default:
|
default:
|
||||||
this.compileError(`There should be a line number here.`);
|
if (this.opts.optionalLabels) this.compileError(`There should be a line number or label here.`);
|
||||||
return;
|
else this.compileError(`There should be a line number here.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
parsePrimary(): Expr {
|
parsePrimary(): Expr {
|
||||||
@ -580,6 +623,17 @@ export class BASICParser {
|
|||||||
this.pushbackToken({type:TokenType.Operator, str:':', $loc:lineno.$loc});
|
this.pushbackToken({type:TokenType.Operator, str:':', $loc:lineno.$loc});
|
||||||
return { command: "IF", cond: cond };
|
return { command: "IF", cond: cond };
|
||||||
}
|
}
|
||||||
|
stmt__ELSE(): ELSE_Statement {
|
||||||
|
if (!this.opts.ifElse) this.dialectError(`IF...ELSE statements`);
|
||||||
|
var lineno = this.peekToken();
|
||||||
|
// assume GOTO if number given after ELSE
|
||||||
|
if (lineno.type == TokenType.Int) {
|
||||||
|
this.pushbackToken({type:TokenType.Ident, str:'GOTO', $loc:lineno.$loc});
|
||||||
|
}
|
||||||
|
// add fake ":"
|
||||||
|
this.pushbackToken({type:TokenType.Operator, str:':', $loc:lineno.$loc});
|
||||||
|
return { command: "ELSE" };
|
||||||
|
}
|
||||||
stmt__FOR() : FOR_Statement {
|
stmt__FOR() : FOR_Statement {
|
||||||
var lexpr = this.parseLexpr(); // TODO: parseNumVar()
|
var lexpr = this.parseLexpr(); // TODO: parseNumVar()
|
||||||
this.expectToken('=');
|
this.expectToken('=');
|
||||||
@ -624,7 +678,7 @@ export class BASICParser {
|
|||||||
stmt__DATA() : DATA_Statement {
|
stmt__DATA() : DATA_Statement {
|
||||||
return { command:'DATA', datums:this.parseExprList() };
|
return { command:'DATA', datums:this.parseExprList() };
|
||||||
}
|
}
|
||||||
stmt__READ() {
|
stmt__READ() : READ_Statement {
|
||||||
return { command:'READ', args:this.parseLexprList() };
|
return { command:'READ', args:this.parseLexprList() };
|
||||||
}
|
}
|
||||||
stmt__RESTORE() {
|
stmt__RESTORE() {
|
||||||
@ -647,15 +701,26 @@ export class BASICParser {
|
|||||||
}
|
}
|
||||||
stmt__DEF() : DEF_Statement {
|
stmt__DEF() : DEF_Statement {
|
||||||
var lexpr = this.parseVarSubscriptOrFunc();
|
var lexpr = this.parseVarSubscriptOrFunc();
|
||||||
|
if (lexpr.args && lexpr.args.length > this.opts.maxDefArgs)
|
||||||
|
this.compileError(`There can be no more than ${this.opts.maxDefArgs} arguments to a function or subscript.`);
|
||||||
if (!lexpr.name.startsWith('FN')) this.compileError(`Functions defined with DEF must begin with the letters "FN".`)
|
if (!lexpr.name.startsWith('FN')) this.compileError(`Functions defined with DEF must begin with the letters "FN".`)
|
||||||
this.expectToken("=");
|
this.expectToken("=");
|
||||||
this.decls[lexpr.name] = this.lasttoken.$loc;
|
this.decls[lexpr.name] = this.lasttoken.$loc;
|
||||||
var func = this.parseExpr();
|
var func = this.parseExpr();
|
||||||
return { command:'DEF', lexpr:lexpr, def:func };
|
return { command:'DEF', lexpr:lexpr, def:func };
|
||||||
}
|
}
|
||||||
|
stmt__POP() : NoArgStatement {
|
||||||
|
return { command:'POP' };
|
||||||
|
}
|
||||||
|
stmt__GET() : GET_Statement {
|
||||||
|
var lexpr = this.parseLexpr();
|
||||||
|
this.decls[lexpr.name] = this.lasttoken.$loc;
|
||||||
|
return { command:'GET', lexpr:lexpr };
|
||||||
|
}
|
||||||
|
// TODO: CHANGE A TO A$ (4th edition, A(0) is len and A(1..) are chars)
|
||||||
stmt__OPTION() : OPTION_Statement {
|
stmt__OPTION() : OPTION_Statement {
|
||||||
var tokname = this.consumeToken();
|
var tokname = this.consumeToken();
|
||||||
if (tokname.type != TokenType.Ident) this.compileError(`There should be a name after the OPTION statement.`)
|
if (tokname.type != TokenType.Ident) this.compileError(`There must be a name after the OPTION statement.`)
|
||||||
var list : string[] = [];
|
var list : string[] = [];
|
||||||
var tok;
|
var tok;
|
||||||
do {
|
do {
|
||||||
@ -677,7 +742,7 @@ export class BASICParser {
|
|||||||
break;
|
break;
|
||||||
case 'DIALECT':
|
case 'DIALECT':
|
||||||
let dname = stmt.optargs[0] || "";
|
let dname = stmt.optargs[0] || "";
|
||||||
let dialect = DIALECTS[dname];
|
let dialect = DIALECTS[dname.toUpperCase()];
|
||||||
if (dialect) this.opts = dialect;
|
if (dialect) this.opts = dialect;
|
||||||
else this.compileError(`The dialect named "${dname}" is not supported by this compiler.`);
|
else this.compileError(`The dialect named "${dname}" is not supported by this compiler.`);
|
||||||
break;
|
break;
|
||||||
@ -726,6 +791,8 @@ export class BASICParser {
|
|||||||
// TODO
|
// TODO
|
||||||
|
|
||||||
export const ECMA55_MINIMAL : BASICOptions = {
|
export const ECMA55_MINIMAL : BASICOptions = {
|
||||||
|
dialectName: "ECMA55",
|
||||||
|
asciiOnly : true,
|
||||||
uppercaseOnly : true,
|
uppercaseOnly : true,
|
||||||
optionalLabels : false,
|
optionalLabels : false,
|
||||||
strictVarNames : true,
|
strictVarNames : true,
|
||||||
@ -736,7 +803,8 @@ export const ECMA55_MINIMAL : BASICOptions = {
|
|||||||
stringConcat : false,
|
stringConcat : false,
|
||||||
typeConvert : false,
|
typeConvert : false,
|
||||||
maxDimensions : 2,
|
maxDimensions : 2,
|
||||||
maxArguments : 255,
|
maxDefArgs : 255,
|
||||||
|
maxStringLength : 255,
|
||||||
sparseArrays : false,
|
sparseArrays : false,
|
||||||
tickComments : false,
|
tickComments : false,
|
||||||
validKeywords : ['BASE','DATA','DEF','DIM','END',
|
validKeywords : ['BASE','DATA','DEF','DIM','END',
|
||||||
@ -746,38 +814,102 @@ export const ECMA55_MINIMAL : BASICOptions = {
|
|||||||
validFunctions : ['ABS','ATN','COS','EXP','INT','LOG','RND','SGN','SIN','SQR','TAB','TAN'],
|
validFunctions : ['ABS','ATN','COS','EXP','INT','LOG','RND','SGN','SIN','SQR','TAB','TAN'],
|
||||||
validOperators : ['=', '<>', '<', '>', '<=', '>=', '+', '-', '*', '/', '^'],
|
validOperators : ['=', '<>', '<', '>', '<=', '>=', '+', '-', '*', '/', '^'],
|
||||||
printZoneLength : 15,
|
printZoneLength : 15,
|
||||||
printPrecision : 6,
|
numericPadding : true,
|
||||||
checkOverflow : true,
|
checkOverflow : true,
|
||||||
multipleNextVars : false,
|
multipleNextVars : false,
|
||||||
|
ifElse : false,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ALTAIR_BASIC40 : BASICOptions = {
|
export const ALTAIR_BASIC40 : BASICOptions = {
|
||||||
|
dialectName: "ALTAIR40",
|
||||||
|
asciiOnly : true,
|
||||||
uppercaseOnly : true,
|
uppercaseOnly : true,
|
||||||
optionalLabels : false,
|
optionalLabels : false,
|
||||||
strictVarNames : true,
|
strictVarNames : false,
|
||||||
sharedArrayNamespace : true,
|
sharedArrayNamespace : true,
|
||||||
defaultArrayBase : 0,
|
defaultArrayBase : 0,
|
||||||
defaultArraySize : 11,
|
defaultArraySize : 11,
|
||||||
defaultValues : false,
|
defaultValues : true,
|
||||||
stringConcat : false,
|
stringConcat : true,
|
||||||
typeConvert : false,
|
typeConvert : false,
|
||||||
maxDimensions : 2,
|
maxDimensions : 128, // "as many as will fit on a single line" ... ?
|
||||||
maxArguments : 255,
|
maxDefArgs : 255,
|
||||||
|
maxStringLength : 255,
|
||||||
sparseArrays : false,
|
sparseArrays : false,
|
||||||
tickComments : false,
|
tickComments : false,
|
||||||
validKeywords : null, // all
|
validKeywords : null, // all
|
||||||
validFunctions : null, // all
|
validFunctions : null, // all
|
||||||
validOperators : null, // all ['\\','MOD','NOT','AND','OR','XOR','EQV','IMP'],
|
validOperators : null, // all ['\\','MOD','NOT','AND','OR','XOR','EQV','IMP'],
|
||||||
printZoneLength : 15,
|
printZoneLength : 15,
|
||||||
printPrecision : 6,
|
numericPadding : true,
|
||||||
|
checkOverflow : true,
|
||||||
|
multipleNextVars : true, // TODO: not supported
|
||||||
|
ifElse : true,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const APPLESOFT_BASIC : BASICOptions = {
|
||||||
|
dialectName: "APPLESOFT",
|
||||||
|
asciiOnly : true,
|
||||||
|
uppercaseOnly : false,
|
||||||
|
optionalLabels : false,
|
||||||
|
strictVarNames : false, // TODO: first two alphanum chars
|
||||||
|
sharedArrayNamespace : false,
|
||||||
|
defaultArrayBase : 0,
|
||||||
|
defaultArraySize : 9, // A(0) to A(8)
|
||||||
|
defaultValues : true,
|
||||||
|
stringConcat : true,
|
||||||
|
typeConvert : false,
|
||||||
|
maxDimensions : 88,
|
||||||
|
maxDefArgs : 1, // TODO: no string FNs
|
||||||
|
maxStringLength : 255,
|
||||||
|
sparseArrays : false,
|
||||||
|
tickComments : false,
|
||||||
|
validKeywords : null, // all
|
||||||
|
validFunctions : ['ABS','ATN','COS','EXP','INT','LOG','RND','SGN','SIN','SQR','TAN',
|
||||||
|
'LEN','LEFT$','MID$','RIGHT$','STR$','VAL','CHR$','ASC',
|
||||||
|
'FRE','SCRN','PDL','PEEK'], // TODO
|
||||||
|
validOperators : ['=', '<>', '<', '>', '<=', '>=', '+', '-', '*', '/', '^', 'AND', 'NOT', 'OR'],
|
||||||
|
printZoneLength : 16,
|
||||||
|
numericPadding : false,
|
||||||
|
checkOverflow : true,
|
||||||
|
multipleNextVars : false,
|
||||||
|
ifElse : false,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MAX8_BASIC : BASICOptions = {
|
||||||
|
dialectName: "MAX8",
|
||||||
|
asciiOnly : false,
|
||||||
|
uppercaseOnly : false,
|
||||||
|
optionalLabels : true,
|
||||||
|
strictVarNames : false, // TODO: first two alphanum chars
|
||||||
|
sharedArrayNamespace : false,
|
||||||
|
defaultArrayBase : 0,
|
||||||
|
defaultArraySize : 11,
|
||||||
|
defaultValues : true,
|
||||||
|
stringConcat : true,
|
||||||
|
typeConvert : true,
|
||||||
|
maxDimensions : 255,
|
||||||
|
maxDefArgs : 255, // TODO: no string FNs
|
||||||
|
maxStringLength : 1024*1024,
|
||||||
|
sparseArrays : false,
|
||||||
|
tickComments : true,
|
||||||
|
validKeywords : null, // all
|
||||||
|
validFunctions : null,
|
||||||
|
validOperators : null,
|
||||||
|
printZoneLength : 15,
|
||||||
|
numericPadding : false,
|
||||||
checkOverflow : true,
|
checkOverflow : true,
|
||||||
multipleNextVars : true,
|
multipleNextVars : true,
|
||||||
|
ifElse : true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: integer vars
|
||||||
|
|
||||||
export const DIALECTS = {
|
export const DIALECTS = {
|
||||||
"DEFAULT": ALTAIR_BASIC40,
|
"DEFAULT": ALTAIR_BASIC40,
|
||||||
"ALTAIR": ALTAIR_BASIC40,
|
"ALTAIR": ALTAIR_BASIC40,
|
||||||
"ALTAIR40": ALTAIR_BASIC40,
|
"ALTAIR40": ALTAIR_BASIC40,
|
||||||
"ECMA55": ECMA55_MINIMAL,
|
"ECMA55": ECMA55_MINIMAL,
|
||||||
"MINIMAL": ECMA55_MINIMAL,
|
"MINIMAL": ECMA55_MINIMAL,
|
||||||
|
"APPLESOFT": APPLESOFT_BASIC,
|
||||||
};
|
};
|
||||||
|
@ -34,6 +34,7 @@ export class BASICRuntime {
|
|||||||
label2lineidx : {[label : string] : number};
|
label2lineidx : {[label : string] : number};
|
||||||
label2pc : {[label : string] : number};
|
label2pc : {[label : string] : number};
|
||||||
datums : basic.Literal[];
|
datums : basic.Literal[];
|
||||||
|
builtins : {};
|
||||||
|
|
||||||
curpc : number;
|
curpc : number;
|
||||||
dataptr : number;
|
dataptr : number;
|
||||||
@ -57,6 +58,7 @@ export class BASICRuntime {
|
|||||||
this.line2pc = [];
|
this.line2pc = [];
|
||||||
this.pc2line = new Map();
|
this.pc2line = new Map();
|
||||||
this.datums = [];
|
this.datums = [];
|
||||||
|
this.builtins = this.getBuiltinFunctions();
|
||||||
// TODO: lines start @ 1?
|
// TODO: lines start @ 1?
|
||||||
program.lines.forEach((line, idx) => {
|
program.lines.forEach((line, idx) => {
|
||||||
// make lookup tables
|
// make lookup tables
|
||||||
@ -76,7 +78,7 @@ export class BASICRuntime {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
// TODO: compile statements?
|
// TODO: compile statements?
|
||||||
//line.stmts.forEach((stmt) => this.compileStatement(stmt));
|
line.stmts.forEach((stmt) => this.compileStatement(stmt));
|
||||||
});
|
});
|
||||||
// try to resume where we left off after loading
|
// try to resume where we left off after loading
|
||||||
this.curpc = this.label2pc[prevlabel] || 0;
|
this.curpc = this.label2pc[prevlabel] || 0;
|
||||||
@ -88,7 +90,7 @@ export class BASICRuntime {
|
|||||||
this.dataptr = 0;
|
this.dataptr = 0;
|
||||||
this.vars = {};
|
this.vars = {};
|
||||||
this.arrays = {};
|
this.arrays = {};
|
||||||
this.defs = this.getBuiltinFunctions();
|
this.defs = {};
|
||||||
this.forLoops = [];
|
this.forLoops = [];
|
||||||
this.returnStack = [];
|
this.returnStack = [];
|
||||||
this.column = 0;
|
this.column = 0;
|
||||||
@ -110,6 +112,9 @@ export class BASICRuntime {
|
|||||||
// TODO: pass source location to error
|
// TODO: pass source location to error
|
||||||
throw new EmuHalt(`${msg} (line ${this.getLabelForPC(this.curpc)})`);
|
throw new EmuHalt(`${msg} (line ${this.getLabelForPC(this.curpc)})`);
|
||||||
}
|
}
|
||||||
|
dialectError(what : string) {
|
||||||
|
this.runtimeError(`I can't ${what} in this dialect of BASIC.`);
|
||||||
|
}
|
||||||
|
|
||||||
getLineForPC(pc:number) {
|
getLineForPC(pc:number) {
|
||||||
var line;
|
var line;
|
||||||
@ -150,11 +155,16 @@ export class BASICRuntime {
|
|||||||
|
|
||||||
compileStatement(stmt: basic.Statement & CompiledStatement) {
|
compileStatement(stmt: basic.Statement & CompiledStatement) {
|
||||||
if (stmt.$run == null) {
|
if (stmt.$run == null) {
|
||||||
|
try {
|
||||||
var stmtfn = this['do__' + stmt.command];
|
var stmtfn = this['do__' + stmt.command];
|
||||||
if (stmtfn == null) this.runtimeError(`I don't know how to "${stmt.command}".`);
|
if (stmtfn == null) this.runtimeError(`I don't know how to "${stmt.command}".`);
|
||||||
var functext = stmtfn.bind(this)(stmt);
|
var functext = stmtfn.bind(this)(stmt);
|
||||||
if (this.trace) console.log(functext);
|
if (this.trace) console.log(functext);
|
||||||
stmt.$run = new Function(functext).bind(this);
|
stmt.$run = new Function(functext).bind(this);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(functext);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
executeStatement(stmt: basic.Statement & CompiledStatement) {
|
executeStatement(stmt: basic.Statement & CompiledStatement) {
|
||||||
@ -168,6 +178,19 @@ export class BASICRuntime {
|
|||||||
} while (this.curpc < this.allstmts.length && !this.pc2line.get(this.curpc));
|
} while (this.curpc < this.allstmts.length && !this.pc2line.get(this.curpc));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
skipToElse() {
|
||||||
|
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) {
|
||||||
|
var cmd = this.allstmts[this.curpc].command;
|
||||||
|
if (cmd == 'ELSE') { this.curpc++; break; }
|
||||||
|
else if (cmd == 'IF') return this.skipToEOL();
|
||||||
|
}
|
||||||
|
this.curpc++;
|
||||||
|
} while (this.curpc < this.allstmts.length && !this.pc2line.get(this.curpc));
|
||||||
|
}
|
||||||
|
|
||||||
skipToEOF() {
|
skipToEOF() {
|
||||||
this.curpc = this.allstmts.length;
|
this.curpc = this.allstmts.length;
|
||||||
}
|
}
|
||||||
@ -193,29 +216,37 @@ export class BASICRuntime {
|
|||||||
this.curpc = pc;
|
this.curpc = pc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
popReturnStack() {
|
||||||
|
if (this.returnStack.length == 0)
|
||||||
|
this.runtimeError("I tried to POP, but there wasn't a corresponding GOSUB.");
|
||||||
|
this.returnStack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
valueToString(obj) : string {
|
valueToString(obj) : string {
|
||||||
var str;
|
var str;
|
||||||
if (typeof obj === 'number') {
|
if (typeof obj === 'number') {
|
||||||
var numstr = obj.toString().toUpperCase();
|
var numstr = obj.toString().toUpperCase();
|
||||||
var prec = 11;
|
var numlen = this.program.opts.printZoneLength - 4;
|
||||||
while (numstr.length > 11) {
|
var prec = numlen;
|
||||||
|
while (numstr.length > numlen) {
|
||||||
numstr = obj.toPrecision(prec--);
|
numstr = obj.toPrecision(prec--);
|
||||||
}
|
}
|
||||||
if (numstr.startsWith('0.'))
|
if (numstr.startsWith('0.'))
|
||||||
numstr = numstr.substr(1);
|
numstr = numstr.substr(1);
|
||||||
else if (numstr.startsWith('-0.'))
|
else if (numstr.startsWith('-0.'))
|
||||||
numstr = '-'+numstr.substr(2);
|
numstr = '-'+numstr.substr(2);
|
||||||
if (numstr.startsWith('-')) {
|
if (!this.program.opts.numericPadding)
|
||||||
|
str = numstr;
|
||||||
|
else if (numstr.startsWith('-'))
|
||||||
str = `${numstr} `;
|
str = `${numstr} `;
|
||||||
} else {
|
else
|
||||||
str = ` ${numstr} `;
|
str = ` ${numstr} `;
|
||||||
}
|
|
||||||
} else if (obj == '\n') {
|
} else if (obj == '\n') {
|
||||||
this.column = 0;
|
this.column = 0;
|
||||||
str = obj;
|
str = obj;
|
||||||
} else if (obj == '\t') {
|
} else if (obj == '\t') {
|
||||||
var curgroup = Math.floor(this.column / 15);
|
var curgroup = Math.floor(this.column / this.program.opts.printZoneLength);
|
||||||
var nextcol = (curgroup + 1) * 15;
|
var nextcol = (curgroup + 1) * this.program.opts.printZoneLength;
|
||||||
str = this.TAB(nextcol);
|
str = this.TAB(nextcol);
|
||||||
} else {
|
} else {
|
||||||
str = `${obj}`;
|
str = `${obj}`;
|
||||||
@ -251,15 +282,16 @@ export class BASICRuntime {
|
|||||||
} else {
|
} else {
|
||||||
if (opts.isconst) this.runtimeError(`I expected a constant value here`);
|
if (opts.isconst) this.runtimeError(`I expected a constant value here`);
|
||||||
var s = '';
|
var s = '';
|
||||||
if (this.defs[expr.name]) { // is it a function?
|
if (expr.name.startsWith("FN")) { // is it a user-defined function?
|
||||||
s += `this.defs.${expr.name}(`;
|
let jsargs = expr.args && expr.args.map((arg) => this.expr2js(arg, opts)).join(', ');
|
||||||
if (expr.args) s += expr.args.map((arg) => this.expr2js(arg, opts)).join(', ');
|
s += `this.defs.${expr.name}(${jsargs})`; // TODO: what if no exist?
|
||||||
s += ')';
|
} 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?
|
} else if (expr.args) { // is it a subscript?
|
||||||
s += `this.getArray(${JSON.stringify(expr.name)}, ${expr.args.length})`;
|
s += `this.getArray(${JSON.stringify(expr.name)}, ${expr.args.length})`;
|
||||||
s += expr.args.map((arg) => '[this.ROUND('+this.expr2js(arg, opts)+')]').join('');
|
s += expr.args.map((arg) => '[this.ROUND('+this.expr2js(arg, opts)+')]').join('');
|
||||||
} else {
|
} else { // just a variable
|
||||||
// just a variable
|
|
||||||
s = `this.vars.${expr.name}`;
|
s = `this.vars.${expr.name}`;
|
||||||
}
|
}
|
||||||
if (opts.check)
|
if (opts.check)
|
||||||
@ -409,6 +441,7 @@ export class BASICRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
do__LET(stmt : basic.LET_Statement) {
|
do__LET(stmt : basic.LET_Statement) {
|
||||||
|
// TODO: range-checking for subscripts (get and set)
|
||||||
var lexpr = this.expr2js(stmt.lexpr, {check:false});
|
var lexpr = this.expr2js(stmt.lexpr, {check:false});
|
||||||
var right = this.expr2js(stmt.right, {check:true});
|
var right = this.expr2js(stmt.right, {check:true});
|
||||||
return `${lexpr} = this.assign(${JSON.stringify(stmt.lexpr.name)}, ${right});`;
|
return `${lexpr} = this.assign(${JSON.stringify(stmt.lexpr.name)}, ${right});`;
|
||||||
@ -429,11 +462,14 @@ export class BASICRuntime {
|
|||||||
|
|
||||||
do__IF(stmt : basic.IF_Statement) {
|
do__IF(stmt : basic.IF_Statement) {
|
||||||
var cond = this.expr2js(stmt.cond, {check:true});
|
var cond = this.expr2js(stmt.cond, {check:true});
|
||||||
return `if (!(${cond})) { this.skipToEOL(); }`
|
return `if (!(${cond})) { this.skipToElse(); }`
|
||||||
|
}
|
||||||
|
|
||||||
|
do__ELSE() {
|
||||||
|
return `this.skipToEOL()`
|
||||||
}
|
}
|
||||||
|
|
||||||
do__DEF(stmt : basic.DEF_Statement) {
|
do__DEF(stmt : basic.DEF_Statement) {
|
||||||
var lexpr = `this.defs.${stmt.lexpr.name}`;
|
|
||||||
var args = [];
|
var args = [];
|
||||||
for (var arg of stmt.lexpr.args || []) {
|
for (var arg of stmt.lexpr.args || []) {
|
||||||
if (isLookup(arg)) {
|
if (isLookup(arg)) {
|
||||||
@ -443,6 +479,8 @@ export class BASICRuntime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var functext = this.expr2js(stmt.def, {check:true, locals:args});
|
var functext = this.expr2js(stmt.def, {check:true, 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 `${lexpr} = function(${args.join(',')}) { return ${functext}; }.bind(this)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,9 +547,28 @@ export class BASICRuntime {
|
|||||||
// already parsed in compiler
|
// already parsed in compiler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
do__POP() {
|
||||||
|
return `this.popReturnStack()`;
|
||||||
|
}
|
||||||
|
|
||||||
|
do__GET(stmt : basic.GET_Statement) {
|
||||||
|
var lexpr = this.expr2js(stmt.lexpr, {check:false});
|
||||||
|
// TODO: single key input
|
||||||
|
return `this.running=false;
|
||||||
|
this.input().then((vals) => {
|
||||||
|
${lexpr} = this.convert(${JSON.stringify(stmt.lexpr.name)}, vals[0]);
|
||||||
|
this.running=true;
|
||||||
|
this.resume();
|
||||||
|
})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: ONERR, ON ERROR GOTO
|
||||||
// TODO: "SUBSCRIPT ERROR" (range check)
|
// TODO: "SUBSCRIPT ERROR" (range check)
|
||||||
// TODO: gosubs nested too deeply
|
// TODO: gosubs nested too deeply
|
||||||
// TODO: memory quota
|
// TODO: memory quota
|
||||||
|
// TODO: useless loop (! 4th edition)
|
||||||
|
// TODO: other 4th edition errors
|
||||||
|
|
||||||
|
|
||||||
// FUNCTIONS
|
// FUNCTIONS
|
||||||
|
|
||||||
@ -548,7 +605,10 @@ export class BASICRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkString(s:string) : string {
|
checkString(s:string) : string {
|
||||||
if (typeof s !== 'string') this.runtimeError(`I expected a string here.`);
|
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`);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -556,8 +616,10 @@ export class BASICRuntime {
|
|||||||
// TODO: if string-concat
|
// TODO: if string-concat
|
||||||
if (typeof a === 'number' && typeof b === 'number')
|
if (typeof a === 'number' && typeof b === 'number')
|
||||||
return this.checkNum(a + b);
|
return this.checkNum(a + b);
|
||||||
|
else if (this.program.opts.stringConcat)
|
||||||
|
return this.checkString(a + b);
|
||||||
else
|
else
|
||||||
return a + b;
|
this.dialectError(`use the "+" operator to concatenate strings`)
|
||||||
}
|
}
|
||||||
sub(a:number, b:number) : number {
|
sub(a:number, b:number) : number {
|
||||||
return this.checkNum(a - b);
|
return this.checkNum(a - b);
|
||||||
@ -597,23 +659,23 @@ export class BASICRuntime {
|
|||||||
bor(a:number, b:number) : number {
|
bor(a:number, b:number) : number {
|
||||||
return a | b;
|
return a | b;
|
||||||
}
|
}
|
||||||
eq(a:number, b:number) : boolean {
|
eq(a:number, b:number) : number {
|
||||||
return a == b;
|
return a == b ? 1 : 0;
|
||||||
}
|
}
|
||||||
ne(a:number, b:number) : boolean {
|
ne(a:number, b:number) : number {
|
||||||
return a != b;
|
return a != b ? 1 : 0;
|
||||||
}
|
}
|
||||||
lt(a:number, b:number) : boolean {
|
lt(a:number, b:number) : number {
|
||||||
return a < b;
|
return a < b ? 1 : 0;
|
||||||
}
|
}
|
||||||
gt(a:number, b:number) : boolean {
|
gt(a:number, b:number) : number {
|
||||||
return a > b;
|
return a > b ? 1 : 0;
|
||||||
}
|
}
|
||||||
le(a:number, b:number) : boolean {
|
le(a:number, b:number) : number {
|
||||||
return a <= b;
|
return a <= b ? 1 : 0;
|
||||||
}
|
}
|
||||||
ge(a:number, b:number) : boolean {
|
ge(a:number, b:number) : number {
|
||||||
return a >= b;
|
return a >= b ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FUNCTIONS (uppercase)
|
// FUNCTIONS (uppercase)
|
||||||
@ -633,6 +695,9 @@ export class BASICRuntime {
|
|||||||
COS(arg : number) : number {
|
COS(arg : number) : number {
|
||||||
return this.checkNum(Math.cos(arg));
|
return this.checkNum(Math.cos(arg));
|
||||||
}
|
}
|
||||||
|
COT(arg : number) : number {
|
||||||
|
return this.checkNum(1.0 / Math.tan(arg)); // 4th edition only
|
||||||
|
}
|
||||||
EXP(arg : number) : number {
|
EXP(arg : number) : number {
|
||||||
return this.checkNum(Math.exp(arg));
|
return this.checkNum(Math.exp(arg));
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ class TeleType {
|
|||||||
this.lines = [];
|
this.lines = [];
|
||||||
this.ncharsout = 0;
|
this.ncharsout = 0;
|
||||||
$(this.page).empty();
|
$(this.page).empty();
|
||||||
|
this.showPrintHead(true);
|
||||||
}
|
}
|
||||||
ensureline() {
|
ensureline() {
|
||||||
if (this.curline == null) {
|
if (this.curline == null) {
|
||||||
@ -75,13 +76,15 @@ class TeleType {
|
|||||||
span.appendTo(this.curline);
|
span.appendTo(this.curline);
|
||||||
}
|
}
|
||||||
this.col += line.length;
|
this.col += line.length;
|
||||||
|
// TODO: wrap @ 80 columns
|
||||||
this.ncharsout += line.length;
|
this.ncharsout += line.length;
|
||||||
//this.movePrintHead();
|
this.movePrintHead(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newline() {
|
newline() {
|
||||||
this.flushline();
|
this.flushline();
|
||||||
this.col = 0;
|
this.col = 0;
|
||||||
|
this.movePrintHead(false);
|
||||||
}
|
}
|
||||||
// TODO: bug in interpreter where it tracks cursor position but maybe doesn't do newlines?
|
// TODO: bug in interpreter where it tracks cursor position but maybe doesn't do newlines?
|
||||||
print(val: string) {
|
print(val: string) {
|
||||||
@ -129,9 +132,21 @@ class TeleType {
|
|||||||
scrollToBottom() {
|
scrollToBottom() {
|
||||||
this.curline.scrollIntoView();
|
this.curline.scrollIntoView();
|
||||||
}
|
}
|
||||||
movePrintHead() {
|
movePrintHead(printing: boolean) {
|
||||||
var x = $(this.page).position().left + this.col * ($(this.page).width() / 80);
|
/*
|
||||||
$("#printhead").offset({left: x});
|
var ph = $("#printhead"); // TODO: speed?
|
||||||
|
var x = $(this.page).position().left + this.col * ($(this.page).width() / 80) - 200;
|
||||||
|
ph.stop().animate({left: x}, {duration:20});
|
||||||
|
//ph.offset({left: x});
|
||||||
|
if (printing) ph.addClass("printing");
|
||||||
|
else ph.removeClass("printing");
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
showPrintHead(show: boolean) {
|
||||||
|
/*
|
||||||
|
var ph = $("#printhead"); // TODO: speed?
|
||||||
|
if (show) ph.show(); else ph.hide();
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,11 +166,15 @@ class TeleTypeWithKeyboard extends TeleType {
|
|||||||
this.input = input;
|
this.input = input;
|
||||||
this.platform = platform;
|
this.platform = platform;
|
||||||
this.runtime = platform.runtime;
|
this.runtime = platform.runtime;
|
||||||
this.runtime.input = async (prompt:string) => {
|
this.runtime.input = async (prompt:string, nargs:number) => {
|
||||||
return new Promise( (resolve, reject) => {
|
return new Promise( (resolve, reject) => {
|
||||||
|
if (prompt != null) {
|
||||||
this.addtext(prompt, 0);
|
this.addtext(prompt, 0);
|
||||||
this.addtext('? ', 0);
|
this.addtext('? ', 0);
|
||||||
this.waitingfor = 'line';
|
this.waitingfor = 'line';
|
||||||
|
} else {
|
||||||
|
this.waitingfor = 'char';
|
||||||
|
}
|
||||||
this.focusinput();
|
this.focusinput();
|
||||||
this.resolveInput = resolve;
|
this.resolveInput = resolve;
|
||||||
});
|
});
|
||||||
@ -176,8 +195,13 @@ class TeleTypeWithKeyboard extends TeleType {
|
|||||||
};
|
};
|
||||||
this.hideinput();
|
this.hideinput();
|
||||||
}
|
}
|
||||||
|
clear() {
|
||||||
|
super.clear();
|
||||||
|
this.hideinput();
|
||||||
|
}
|
||||||
focusinput() {
|
focusinput() {
|
||||||
this.ensureline();
|
this.ensureline();
|
||||||
|
this.showPrintHead(false);
|
||||||
// don't steal focus while editing
|
// don't steal focus while editing
|
||||||
if (this.keepinput)
|
if (this.keepinput)
|
||||||
$(this.input).css('visibility', 'visible');
|
$(this.input).css('visibility', 'visible');
|
||||||
@ -194,6 +218,7 @@ class TeleTypeWithKeyboard extends TeleType {
|
|||||||
$(this.input).removeClass('transcript-input-char')
|
$(this.input).removeClass('transcript-input-char')
|
||||||
}
|
}
|
||||||
hideinput() {
|
hideinput() {
|
||||||
|
this.showPrintHead(true);
|
||||||
if (this.keepinput)
|
if (this.keepinput)
|
||||||
$(this.input).css('visibility','hidden');
|
$(this.input).css('visibility','hidden');
|
||||||
else
|
else
|
||||||
@ -215,16 +240,18 @@ class TeleTypeWithKeyboard extends TeleType {
|
|||||||
}
|
}
|
||||||
sendinput(s: string) {
|
sendinput(s: string) {
|
||||||
if (this.resolveInput) {
|
if (this.resolveInput) {
|
||||||
|
if (this.platform.program.opts.uppercaseOnly)
|
||||||
s = s.toUpperCase();
|
s = s.toUpperCase();
|
||||||
this.addtext(s, 4);
|
this.addtext(s, 4);
|
||||||
this.flushline();
|
this.flushline();
|
||||||
this.resolveInput(s.split(','));
|
this.resolveInput(s.split(',')); // TODO: should parse quotes, etc
|
||||||
this.resolveInput = null;
|
this.resolveInput = null;
|
||||||
}
|
}
|
||||||
this.clearinput();
|
this.clearinput();
|
||||||
this.hideinput(); // keep from losing input handlers
|
this.hideinput(); // keep from losing input handlers
|
||||||
}
|
}
|
||||||
sendchar(code: number) {
|
sendchar(code: number) {
|
||||||
|
this.sendinput(String.fromCharCode(code));
|
||||||
}
|
}
|
||||||
ensureline() {
|
ensureline() {
|
||||||
if (!this.keepinput) $(this.input).hide();
|
if (!this.keepinput) $(this.input).hide();
|
||||||
@ -277,6 +304,7 @@ class BASICPlatform implements Platform {
|
|||||||
var windowport = $('<div id="windowport" class="transcript transcript-style-2"/>').appendTo(gameport);
|
var windowport = $('<div id="windowport" class="transcript transcript-style-2"/>').appendTo(gameport);
|
||||||
var inputline = $('<input class="transcript-input transcript-style-2" type="text" style="max-width:95%"/>').appendTo(parent);
|
var inputline = $('<input class="transcript-input transcript-style-2" type="text" style="max-width:95%"/>').appendTo(parent);
|
||||||
//var printhead = $('<div id="printhead" class="transcript-print-head"/>').appendTo(parent);
|
//var printhead = $('<div id="printhead" class="transcript-print-head"/>').appendTo(parent);
|
||||||
|
//var printshield = $('<div id="printhead" class="transcript-print-shield"/>').appendTo(parent);
|
||||||
this.tty = new TeleTypeWithKeyboard(windowport[0], true, inputline[0] as HTMLInputElement, this);
|
this.tty = new TeleTypeWithKeyboard(windowport[0], true, inputline[0] as HTMLInputElement, this);
|
||||||
this.tty.scrolldiv = parent;
|
this.tty.scrolldiv = parent;
|
||||||
this.timer = new AnimationTimer(60, this.animate.bind(this));
|
this.timer = new AnimationTimer(60, this.animate.bind(this));
|
||||||
@ -323,6 +351,7 @@ class BASICPlatform implements Platform {
|
|||||||
exitmsg() {
|
exitmsg() {
|
||||||
this.tty.print("\n\n");
|
this.tty.print("\n\n");
|
||||||
this.tty.addtext("*** END OF PROGRAM ***", 1);
|
this.tty.addtext("*** END OF PROGRAM ***", 1);
|
||||||
|
this.tty.showPrintHead(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
resize: () => void;
|
resize: () => void;
|
||||||
|
Loading…
Reference in New Issue
Block a user