From 4767ed8429e64abbf5852acb93d7e4450d53e4aa Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Thu, 13 Aug 2020 20:50:41 -0500 Subject: [PATCH] merged zmachine + basic use of teletype --- src/common/basic/compiler.ts | 1 + src/common/teletype.ts | 22 ++-- src/platform/basic.ts | 6 +- src/platform/zmachine.ts | 222 +++++------------------------------ 4 files changed, 49 insertions(+), 202 deletions(-) diff --git a/src/common/basic/compiler.ts b/src/common/basic/compiler.ts index b547f53e..5b534e3b 100644 --- a/src/common/basic/compiler.ts +++ b/src/common/basic/compiler.ts @@ -808,6 +808,7 @@ export class BASICParser { this.expectToken(','); return this.stmt__INPUT(); } + // TODO: DATA statement doesn't read unquoted strings stmt__DATA() : DATA_Statement { return { command:'DATA', datums:this.parseExprList() }; } diff --git a/src/common/teletype.ts b/src/common/teletype.ts index ebb47d8d..b2b73f9f 100644 --- a/src/common/teletype.ts +++ b/src/common/teletype.ts @@ -161,14 +161,16 @@ export class TeleType { export class TeleTypeWithKeyboard extends TeleType { input : HTMLInputElement; - keepinput : boolean = true; msecPerLine : number = 100; // IBM 1403 + keepinput : boolean = true; + keephandler : boolean = true; + uppercaseOnly : boolean = false; + splitInput : boolean = false; + resolveInput : (inp) => void; focused : boolean = true; scrolling : number = 0; waitingfor : string; - resolveInput; - uppercaseOnly : boolean; constructor(page: HTMLElement, fixed: boolean, input: HTMLInputElement) { super(page, fixed); @@ -187,7 +189,6 @@ export class TeleTypeWithKeyboard extends TeleType { this.page.onclick = (e) => { this.input.focus(); }; - this.hideinput(); } clear() { super.clear(); @@ -202,7 +203,9 @@ export class TeleTypeWithKeyboard extends TeleType { $(this.input).css('visibility', 'visible'); else $(this.input).appendTo(this.curline).show()[0]; + // scroll to bottom this.scrollToBottom(); + // refocus? if (this.focused) { $(this.input).focus(); } @@ -235,15 +238,14 @@ export class TeleTypeWithKeyboard extends TeleType { } sendinput(s: string) { if (this.resolveInput) { - //if (this.uppercaseOnly) // TODO: always uppercase? - s = s.toUpperCase(); + if (this.uppercaseOnly) s = s.toUpperCase(); // TODO: always uppercase? this.addtext(s, 4); this.flushline(); - this.resolveInput(s.split(',')); - this.resolveInput = null; + this.clearinput(); + this.hideinput(); // keep from losing input handlers + this.resolveInput(this.splitInput ? s.split(',') : s); + if (!this.keephandler) this.resolveInput = null; } - this.clearinput(); - this.hideinput(); // keep from losing input handlers } sendchar(code: number) { this.sendinput(String.fromCharCode(code)); diff --git a/src/platform/basic.ts b/src/platform/basic.ts index f027aec3..61cc8d3d 100644 --- a/src/platform/basic.ts +++ b/src/platform/basic.ts @@ -47,6 +47,10 @@ class BASICPlatform implements Platform { //var printhead = $('
').appendTo(parent); //var printshield = $('
').appendTo(parent); this.tty = new TeleTypeWithKeyboard(windowport[0], true, inputline[0] as HTMLInputElement); + this.tty.keepinput = true; // input stays @ bottom + this.tty.splitInput = true; // split into arguments + this.tty.keephandler = false; // set handler each input + this.tty.hideinput(); this.tty.scrolldiv = parent; this.tty.bell = new Audio('res/ttybell.mp3'); this.runtime.input = async (prompt:string, nargs:number) => { @@ -116,7 +120,7 @@ class BASICPlatform implements Platform { var didExit = this.runtime.exited; this.program = data; this.runtime.load(data); - this.tty.uppercaseOnly = this.program.opts.uppercaseOnly; + this.tty.uppercaseOnly = true; // this.program.opts.uppercaseOnly; //TODO? views.textMapFunctions.input = this.program.opts.uppercaseOnly ? (s) => s.toUpperCase() : null; // only reset if we exited, otherwise we try to resume if (!this.hotReload || didExit) this.reset(); diff --git a/src/platform/zmachine.ts b/src/platform/zmachine.ts index e4cd0f11..17acb577 100644 --- a/src/platform/zmachine.ts +++ b/src/platform/zmachine.ts @@ -2,6 +2,7 @@ import { Platform, BasePlatform, BaseDebugPlatform, Preset, EmuState, inspectSymbol } from "../common/baseplatform"; import { PLATFORMS, EmuHalt } from "../common/emu"; import { loadScript } from "../ide/ui"; +import { TeleType, TeleTypeWithKeyboard } from "../common/teletype"; const ZMACHINE_PRESETS = [ { id: 'hello.inf', name: 'Hello World' }, @@ -49,142 +50,43 @@ function debug(...args: any[]) { //console.log(arguments); } -class GlkWindow { - page: HTMLElement; - stream: number; - fixed: boolean; - - curline: HTMLElement; - curstyle: number; - reverse: boolean; - col: number; - row: number; - lines: HTMLElement[]; - - constructor(page: HTMLElement, stream: number) { - this.page = page; - this.stream = stream; - this.fixed = stream > 1; - this.clear(); - } - clear() { - this.curline = null; - this.curstyle = 0; - this.reverse = false; - this.col = 0; - this.row = -1; - this.lines = []; - $(this.page).empty(); - } - ensureline() { - if (this.curline == null) { - this.curline = this.lines[++this.row]; - if (this.curline == null) { - this.curline = $('
')[0]; - this.page.appendChild(this.curline); - this.lines[this.row] = this.curline; - } - } - } - flushline() { - this.curline = null; - } - // TODO: support fixed-width window (use CSS grid?) - addtext(line: string, style: number) { - this.ensureline(); - if (line.length) { - // in fixed mode, only do characters - if (this.fixed && line.length > 1) { - for (var i = 0; i < line.length; i++) - this.addtext(line[i], style); - return; - } - var span = $("").text(line); - for (var i = 0; i < 8; i++) { - if (style & (1 << i)) - span.addClass("transcript-style-" + (1 << i)); - } - if (this.reverse) span.addClass("transcript-reverse"); - //span.data('vmip', this.vm.pc); - // in fixed mode, we can overwrite individual characters - if (this.fixed && line.length == 1 && this.col < this.curline.childNodes.length) { - this.curline.replaceChild(span[0], this.curline.childNodes[this.col]); - } else { - span.appendTo(this.curline); - } - this.col += line.length; - } - } - newline() { - this.flushline(); - this.col = 0; - } - // TODO: bug in interpreter where it tracks cursor position but maybe doesn't do newlines? - put_jstring(val: string) { - // split by newlines - var lines = val.split("\n"); - for (var i = 0; i < lines.length; i++) { - if (i > 0) this.newline(); - this.addtext(lines[i], this.curstyle); - } - } - move_cursor(col: number, row: number) { - if (!this.fixed) return; // fixed windows only - // ensure enough row elements - while (this.lines.length <= row) { - this.flushline(); - this.ensureline(); - } - // select row element - this.curline = this.lines[row]; - this.row = row; - // get children in row (individual text cells) - var children = $(this.curline).children(); - // add whitespace to line? - if (children.length > col) { - this.col = col; - } else { - while (this.col < col) - this.addtext(' ', this.curstyle); - } - } - setrows(size: number) { - if (!this.fixed) return; // fixed windows only - // truncate rows? - var allrows = $(this.page).children(); - if (allrows.length > size) { - this.flushline(); - allrows.slice(size).remove(); - this.lines = this.lines.slice(0, size); - //this.move_cursor(0,0); - } - } -} - class GlkImpl { vm: IFZVM; input: HTMLInputElement; - curwnd: GlkWindow; - windows: { [win: number]: GlkWindow }; + mainwnd : TeleTypeWithKeyboard; + curwnd: TeleType; + windows: { [win: number]: TeleType }; windowcount: number; - waitingfor: "line" | "char" | null; - focused = false; exited = false; constructor(page: HTMLElement, input: HTMLInputElement, upper: HTMLElement) { + this.mainwnd = new TeleTypeWithKeyboard(page, false, input); + this.mainwnd.keepinput = false; // input moves w/ cursor + this.mainwnd.splitInput = false; + this.mainwnd.uppercaseOnly = true; + this.mainwnd.hideinput(); this.windows = { - 1: new GlkWindow(page, 1), - 2: new GlkWindow(upper, 2), - 3: new GlkWindow(null, 3), // fake window for resizing + 1: this.mainwnd, + 2: new TeleType(upper, true), + 3: new TeleType(null, true), // fake window for resizing }; this.input = input; + this.mainwnd.resolveInput = (s:string) => { + if (this.vm.read_data.buffer) { + for (var i = 0; i < s.length; i++) { + this.vm.read_data.buffer[i] = s.charCodeAt(i) & 0xff; + } + this.vm.handle_line_input(s.length); + } else { + this.vm.handle_char_input(s.charCodeAt(0)); + } + this.vm.run(); + } this.reset(); } reset() { this.windowcount = 0; this.exited = false; - this.waitingfor = null; - this.hideinput(); this.windows[1].clear(); this.windows[2].clear(); this.curwnd = this.windows[1]; @@ -200,56 +102,7 @@ class GlkImpl { // TODO } focusinput() { - this.ensureline(); - // don't steal focus while editing - $(this.input).appendTo(this.curwnd.curline).show()[0].scrollIntoView(); - if (this.focused) { - $(this.input).focus(); - } - // change size - if (this.waitingfor == 'char') - $(this.input).addClass('transcript-input-char') - else - $(this.input).removeClass('transcript-input-char') - } - hideinput() { - $(this.input).appendTo($(this.windows[1].page).parent()).hide(); - } - clearinput() { - this.input.value = ''; - this.waitingfor = null; - } - sendkey(e: KeyboardEvent) { - if (this.waitingfor == 'line') { - if (e.key == "Enter") { - this.sendinput(this.input.value.toString()); - } - } else if (this.waitingfor == 'char') { - this.sendchar(e.keyCode); - e.preventDefault(); - } - } - sendinput(s: string) { - this.curwnd.addtext(s, Const.style_Input); - this.flushline(); - if (this.vm.read_data.buffer) { - for (var i = 0; i < s.length; i++) { - this.vm.read_data.buffer[i] = s.charCodeAt(i) & 0xff; - } - this.vm.handle_line_input(s.length); - } - this.clearinput(); - this.hideinput(); // keep from losing input handlers - this.vm.run(); - } - sendchar(code: number) { - this.vm.handle_char_input(code); - this.hideinput(); // keep from losing input handlers - this.vm.run(); - } - ensureline() { - $(this.input).hide(); - this.curwnd.ensureline(); + this.mainwnd.focusinput(); } flushline() { this.curwnd.flushline(); @@ -265,12 +118,12 @@ class GlkImpl { this.windows[win].clear(); } glk_request_line_event_uni(win, buf, initlen) { - this.waitingfor = 'line'; + this.mainwnd.waitingfor = 'line'; this.focusinput(); this.startinputtimer(); } glk_request_char_event_uni(win, buf, initlen) { - this.waitingfor = 'char'; + this.mainwnd.waitingfor = 'char'; this.focusinput(); this.startinputtimer(); } @@ -287,15 +140,15 @@ class GlkImpl { glk_put_jstring(val: string, allbytes) { //debug('glk_put_jstring', arguments); - this.curwnd.put_jstring(val); + this.curwnd.print(val); } glk_put_jstring_stream(stream: number, val: string) { //debug('glk_put_jstring_stream', arguments); - this.windows[stream].put_jstring(val); + this.windows[stream].print(val); } glk_put_char_stream_uni(stream: number, ch: number) { //debug('glk_put_char_stream_uni', arguments); - this.windows[stream].put_jstring(String.fromCharCode(ch)); + this.windows[stream].print(String.fromCharCode(ch)); } glk_set_style(val) { this.curwnd.curstyle = val; @@ -396,7 +249,7 @@ class GlkImpl { } glk_window_get_stream(win) { debug('glk_window_get_stream', arguments); - return this.windows[win].stream; + return win; } glk_set_window(win) { debug('glk_set_window', arguments); @@ -772,6 +625,7 @@ class ZmachinePlatform implements Platform { async start() { await loadScript('./lib/zvm/ifvms.min.js'); + await loadScript('./gen/common/teletype.js'); // create divs var parent = this.mainElement; @@ -780,20 +634,6 @@ class ZmachinePlatform implements Platform { var windowport = $('
').appendTo(gameport); var inputline = $('').appendTo(gameport).hide(); this.glk = new GlkImpl(windowport[0], inputline[0] as HTMLInputElement, upperwnd[0]); - inputline.on('keypress', (e) => { - this.glk.sendkey(e); - }); - inputline.on('focus', (e) => { - this.glk.focused = true; - console.log('inputline gained focus'); - }); - $("#workspace").on('click', (e) => { - this.glk.focused = false; - console.log('inputline lost focus'); - }); - windowport.on('click', (e) => { - inputline.focus(); - }); this.resize = () => { // set font size proportional to window width var charwidth = $(gameport).width() * 1.6 / STATUS_NUM_COLS;