diff --git a/src/common/basic/compiler.ts b/src/common/basic/compiler.ts index 8c339114..a4d90b97 100644 --- a/src/common/basic/compiler.ts +++ b/src/common/basic/compiler.ts @@ -10,7 +10,7 @@ export interface BASICOptions { optionalLabels : boolean; // can omit line numbers and use labels? optionalWhitespace : boolean; // can "crunch" keywords? also, eat extra ":" delims multipleStmtsPerLine : boolean; // multiple statements separated by ":" - varNaming : 'A'|'A1'|'AA'|'*'; // only allow A0-9 for numerics, single letter for arrays/strings + varNaming : 'A'|'A1'|'A1$'|'AA'|'*'; // only allow A0-9 for numerics, single letter for arrays/strings squareBrackets : boolean; // "[" and "]" interchangable with "(" and ")"? tickComments : boolean; // support 'comments? hexOctalConsts : boolean; // support &H and &O integer constants? @@ -198,6 +198,13 @@ export interface INPUT_Statement extends Statement { command: "INPUT"; prompt: Expr; args: IndOp[]; + timeout?: Expr; + elapsed?: IndOp; +} + +export interface ENTER_Statement extends INPUT_Statement { + timeout: Expr; + elapsed: IndOp; } export interface DATA_Statement extends Statement { @@ -852,6 +859,10 @@ export class BASICParser { if (lexpr.args != null && !/^[A-Z]?[$]?$/i.test(lexpr.name)) this.dialectErrorNoSupport(`array names other than a single letter`); break; + 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; case 'AA': if (lexpr.args == null && !/^[A-Z][A-Z0-9]?[$]?$/i.test(lexpr.name)) this.dialectErrorNoSupport(`variable names other than a letter followed by an optional letter or digit`); @@ -1057,11 +1068,11 @@ export class BASICParser { } /* for HP BASIC only */ stmt__ENTER() : INPUT_Statement { - var secs = this.parseExpr(); + var timeout = this.parseExpr(); this.expectToken(','); - var result = this.parseLexpr(); // TODO: this has to go somewheres + var elapsed = this.parseLexpr(); // TODO: this has to go somewheres this.expectToken(','); - return this.stmt__INPUT(); + return { command:'INPUT', prompt:null, args:this.parseLexprList(), timeout:timeout, elapsed:elapsed }; } // TODO: DATA statement doesn't read unquoted strings stmt__DATA() : DATA_Statement { @@ -1406,7 +1417,7 @@ export const HP_TIMESHARED_BASIC : BASICOptions = { optionalLabels : false, optionalWhitespace : false, multipleStmtsPerLine : true, - varNaming : "A1", + varNaming : "A1$", staticArrays : true, sharedArrayNamespace : false, defaultArrayBase : 1, @@ -1874,6 +1885,7 @@ const BUILTIN_DEFS : BuiltinFunctionDef[] = [ ['OCT$', ['number'], 'string' ], ['PI', [], 'number'], ['POS', ['number'], 'number' ], // arg ignored + ['POS', ['string','string'], 'number' ], // HP POS ['LEFT$', ['string', 'number'], 'string' ], ['RND', [], 'number' ], ['RND', ['number'], 'number' ], diff --git a/src/common/basic/fuzz.ts b/src/common/basic/fuzz.ts index fbb32806..4bbddd1c 100644 --- a/src/common/basic/fuzz.ts +++ b/src/common/basic/fuzz.ts @@ -1,6 +1,6 @@ import { BASICParser, DIALECTS, BASICOptions, CompileError } from "./compiler"; -import { BASICRuntime } from "./runtime"; +import { BASICRuntime, InputResponse } from "./runtime"; import { EmuHalt } from "../emu"; process.on('unhandledRejection', (reason, promise) => { @@ -20,12 +20,12 @@ export function fuzz(buf) { runtime.print = (s) => { if (s == null) throw new Error("PRINT null string"); } - runtime.input = function(prompt: string, nargs: number) : Promise { - var p = new Promise( (resolve, reject) => { + runtime.input = function(prompt: string, nargs: number) : Promise { + var p = new Promise( (resolve, reject) => { var arr = []; for (var i=0; i { runtime.input = async (prompt:string) => { return new Promise( (resolve, reject) => { function answered(answer) { - var vals = answer.toUpperCase().split(','); + var line = answer.toUpperCase(); + var vals = line.split(','); //console.log(">>>",vals); - resolve(vals); + resolve({line:line, vals:vals}); } prompt += ' ?'; if (inputlines.length) { diff --git a/src/common/basic/runtime.ts b/src/common/basic/runtime.ts index 2aaddfc9..3b63a6c4 100644 --- a/src/common/basic/runtime.ts +++ b/src/common/basic/runtime.ts @@ -16,6 +16,12 @@ function isUnOp(arg: basic.Expr): arg is basic.UnOp { return (arg as any).op != null && (arg as any).expr != null; } +export interface InputResponse { + line: string; + vals: string[]; + elapsed?: number; +} + // expr2js() options class ExprOptions { isconst?: boolean; // only allow constant operations @@ -408,8 +414,8 @@ export class BASICRuntime { } // override this - async input(prompt: string, nargs: number) : Promise { - return []; + async input(prompt: string, nargs: number) : Promise { + return {line:"", vals:[]}; } // override this @@ -730,22 +736,24 @@ export class BASICRuntime { } do__INPUT(stmt : basic.INPUT_Statement) { - var prompt = this.expr2js(stmt.prompt); + var prompt = stmt.prompt != null ? this.expr2js(stmt.prompt) : '""'; + var elapsed = stmt.elapsed != null ? this.assign2js(stmt.elapsed) : "let ___"; var setvals = ''; stmt.args.forEach((arg, index) => { var lexpr = this.assign2js(arg); setvals += ` - var value = this.convert(${JSON.stringify(arg.name)}, vals[${index}]); + var value = this.convert(${JSON.stringify(arg.name)}, response.vals[${index}]); valid &= this.isValid(value); ${lexpr} = value; ` }); return `this.preInput(); - this.input(${prompt}, ${stmt.args.length}).then((vals) => { + this.input(${prompt}, ${stmt.args.length}).then((response) => { let valid = 1; ${setvals} this.postInput(valid); this.column = 0; // assume linefeed + ${elapsed} = response.elapsed; })`; } @@ -1165,8 +1173,11 @@ export class BASICRuntime { return Math.PI; } // TODO: POS(haystack, needle, start) - POS(arg : number) : number { // arg ignored - return this.column + 1; + POS(arg1, arg2) { // arg ignored + if (typeof arg1 == 'string' && typeof arg2 == 'string') + return arg1.indexOf(arg2) >= 0 + 1; + else + return this.column + 1; } RIGHT$(arg : string, count : number) : string { arg = this.checkString(arg); diff --git a/src/common/teletype.ts b/src/common/teletype.ts index b2b73f9f..6e741a54 100644 --- a/src/common/teletype.ts +++ b/src/common/teletype.ts @@ -1,4 +1,6 @@ +import { InputResponse } from "./basic/runtime"; + export class TeleType { page: HTMLElement; fixed: boolean; @@ -166,11 +168,12 @@ export class TeleTypeWithKeyboard extends TeleType { keephandler : boolean = true; uppercaseOnly : boolean = false; splitInput : boolean = false; - resolveInput : (inp) => void; + resolveInput : (InputResponse) => void; focused : boolean = true; scrolling : number = 0; waitingfor : string; + lastInputRequestTime : number; constructor(page: HTMLElement, fixed: boolean, input: HTMLInputElement) { super(page, fixed); @@ -214,6 +217,7 @@ export class TeleTypeWithKeyboard extends TeleType { $(this.input).addClass('transcript-input-char') else $(this.input).removeClass('transcript-input-char') + this.lastInputRequestTime = Date.now(); } hideinput() { this.showPrintHead(true); @@ -238,12 +242,14 @@ export class TeleTypeWithKeyboard extends TeleType { } sendinput(s: string) { if (this.resolveInput) { + var elapsed = Date.now() - this.lastInputRequestTime; if (this.uppercaseOnly) s = s.toUpperCase(); // TODO: always uppercase? this.addtext(s, 4); this.flushline(); this.clearinput(); this.hideinput(); // keep from losing input handlers - this.resolveInput(this.splitInput ? s.split(',') : s); + var vals = this.splitInput ? s.split(',') : null; + this.resolveInput({line:s, vals:vals, elapsed:elapsed/1000}); if (!this.keephandler) this.resolveInput = null; } } diff --git a/src/ide/views.ts b/src/ide/views.ts index d67adcd8..7424e08b 100644 --- a/src/ide/views.ts +++ b/src/ide/views.ts @@ -262,7 +262,6 @@ export class SourceEditor implements ProjectView { } clearErrors() { - this.refreshDebugState(false); // TODO: why? this.dirtylisting = true; // clear line widgets this.editor.clearGutter("gutter-info"); diff --git a/src/platform/zmachine.ts b/src/platform/zmachine.ts index 17acb577..e998d350 100644 --- a/src/platform/zmachine.ts +++ b/src/platform/zmachine.ts @@ -3,6 +3,7 @@ import { Platform, BasePlatform, BaseDebugPlatform, Preset, EmuState, inspectSym import { PLATFORMS, EmuHalt } from "../common/emu"; import { loadScript } from "../ide/ui"; import { TeleType, TeleTypeWithKeyboard } from "../common/teletype"; +import { InputResponse } from "../common/basic/runtime"; const ZMACHINE_PRESETS = [ { id: 'hello.inf', name: 'Hello World' }, @@ -71,7 +72,8 @@ class GlkImpl { 3: new TeleType(null, true), // fake window for resizing }; this.input = input; - this.mainwnd.resolveInput = (s:string) => { + this.mainwnd.resolveInput = (resp:InputResponse) => { + var s = resp.line; if (this.vm.read_data.buffer) { for (var i = 0; i < s.length; i++) { this.vm.read_data.buffer[i] = s.charCodeAt(i) & 0xff;