mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-06-15 21:29:28 +00:00
merged zmachine + basic use of teletype
This commit is contained in:
parent
fab1768b75
commit
4767ed8429
|
@ -808,6 +808,7 @@ export class BASICParser {
|
||||||
this.expectToken(',');
|
this.expectToken(',');
|
||||||
return this.stmt__INPUT();
|
return this.stmt__INPUT();
|
||||||
}
|
}
|
||||||
|
// TODO: DATA statement doesn't read unquoted strings
|
||||||
stmt__DATA() : DATA_Statement {
|
stmt__DATA() : DATA_Statement {
|
||||||
return { command:'DATA', datums:this.parseExprList() };
|
return { command:'DATA', datums:this.parseExprList() };
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,14 +161,16 @@ export class TeleType {
|
||||||
|
|
||||||
export class TeleTypeWithKeyboard extends TeleType {
|
export class TeleTypeWithKeyboard extends TeleType {
|
||||||
input : HTMLInputElement;
|
input : HTMLInputElement;
|
||||||
keepinput : boolean = true;
|
|
||||||
msecPerLine : number = 100; // IBM 1403
|
msecPerLine : number = 100; // IBM 1403
|
||||||
|
keepinput : boolean = true;
|
||||||
|
keephandler : boolean = true;
|
||||||
|
uppercaseOnly : boolean = false;
|
||||||
|
splitInput : boolean = false;
|
||||||
|
resolveInput : (inp) => void;
|
||||||
|
|
||||||
focused : boolean = true;
|
focused : boolean = true;
|
||||||
scrolling : number = 0;
|
scrolling : number = 0;
|
||||||
waitingfor : string;
|
waitingfor : string;
|
||||||
resolveInput;
|
|
||||||
uppercaseOnly : boolean;
|
|
||||||
|
|
||||||
constructor(page: HTMLElement, fixed: boolean, input: HTMLInputElement) {
|
constructor(page: HTMLElement, fixed: boolean, input: HTMLInputElement) {
|
||||||
super(page, fixed);
|
super(page, fixed);
|
||||||
|
@ -187,7 +189,6 @@ export class TeleTypeWithKeyboard extends TeleType {
|
||||||
this.page.onclick = (e) => {
|
this.page.onclick = (e) => {
|
||||||
this.input.focus();
|
this.input.focus();
|
||||||
};
|
};
|
||||||
this.hideinput();
|
|
||||||
}
|
}
|
||||||
clear() {
|
clear() {
|
||||||
super.clear();
|
super.clear();
|
||||||
|
@ -202,7 +203,9 @@ export class TeleTypeWithKeyboard extends TeleType {
|
||||||
$(this.input).css('visibility', 'visible');
|
$(this.input).css('visibility', 'visible');
|
||||||
else
|
else
|
||||||
$(this.input).appendTo(this.curline).show()[0];
|
$(this.input).appendTo(this.curline).show()[0];
|
||||||
|
// scroll to bottom
|
||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
|
// refocus?
|
||||||
if (this.focused) {
|
if (this.focused) {
|
||||||
$(this.input).focus();
|
$(this.input).focus();
|
||||||
}
|
}
|
||||||
|
@ -235,15 +238,14 @@ export class TeleTypeWithKeyboard extends TeleType {
|
||||||
}
|
}
|
||||||
sendinput(s: string) {
|
sendinput(s: string) {
|
||||||
if (this.resolveInput) {
|
if (this.resolveInput) {
|
||||||
//if (this.uppercaseOnly) // TODO: always uppercase?
|
if (this.uppercaseOnly) s = s.toUpperCase(); // TODO: always uppercase?
|
||||||
s = s.toUpperCase();
|
|
||||||
this.addtext(s, 4);
|
this.addtext(s, 4);
|
||||||
this.flushline();
|
this.flushline();
|
||||||
this.resolveInput(s.split(','));
|
this.clearinput();
|
||||||
this.resolveInput = null;
|
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) {
|
sendchar(code: number) {
|
||||||
this.sendinput(String.fromCharCode(code));
|
this.sendinput(String.fromCharCode(code));
|
||||||
|
|
|
@ -47,6 +47,10 @@ class BASICPlatform implements Platform {
|
||||||
//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);
|
//var printshield = $('<div id="printhead" class="transcript-print-shield"/>').appendTo(parent);
|
||||||
this.tty = new TeleTypeWithKeyboard(windowport[0], true, inputline[0] as HTMLInputElement);
|
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.scrolldiv = parent;
|
||||||
this.tty.bell = new Audio('res/ttybell.mp3');
|
this.tty.bell = new Audio('res/ttybell.mp3');
|
||||||
this.runtime.input = async (prompt:string, nargs:number) => {
|
this.runtime.input = async (prompt:string, nargs:number) => {
|
||||||
|
@ -116,7 +120,7 @@ class BASICPlatform implements Platform {
|
||||||
var didExit = this.runtime.exited;
|
var didExit = this.runtime.exited;
|
||||||
this.program = data;
|
this.program = data;
|
||||||
this.runtime.load(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;
|
views.textMapFunctions.input = this.program.opts.uppercaseOnly ? (s) => s.toUpperCase() : null;
|
||||||
// only reset if we exited, otherwise we try to resume
|
// only reset if we exited, otherwise we try to resume
|
||||||
if (!this.hotReload || didExit) this.reset();
|
if (!this.hotReload || didExit) this.reset();
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { Platform, BasePlatform, BaseDebugPlatform, Preset, EmuState, inspectSymbol } from "../common/baseplatform";
|
import { Platform, BasePlatform, BaseDebugPlatform, Preset, EmuState, inspectSymbol } from "../common/baseplatform";
|
||||||
import { PLATFORMS, EmuHalt } from "../common/emu";
|
import { PLATFORMS, EmuHalt } from "../common/emu";
|
||||||
import { loadScript } from "../ide/ui";
|
import { loadScript } from "../ide/ui";
|
||||||
|
import { TeleType, TeleTypeWithKeyboard } from "../common/teletype";
|
||||||
|
|
||||||
const ZMACHINE_PRESETS = [
|
const ZMACHINE_PRESETS = [
|
||||||
{ id: 'hello.inf', name: 'Hello World' },
|
{ id: 'hello.inf', name: 'Hello World' },
|
||||||
|
@ -49,142 +50,43 @@ function debug(...args: any[]) {
|
||||||
//console.log(arguments);
|
//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 = $('<div class="transcript-line"/>')[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 = $("<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 {
|
class GlkImpl {
|
||||||
vm: IFZVM;
|
vm: IFZVM;
|
||||||
input: HTMLInputElement;
|
input: HTMLInputElement;
|
||||||
curwnd: GlkWindow;
|
mainwnd : TeleTypeWithKeyboard;
|
||||||
windows: { [win: number]: GlkWindow };
|
curwnd: TeleType;
|
||||||
|
windows: { [win: number]: TeleType };
|
||||||
windowcount: number;
|
windowcount: number;
|
||||||
waitingfor: "line" | "char" | null;
|
|
||||||
focused = false;
|
|
||||||
exited = false;
|
exited = false;
|
||||||
|
|
||||||
constructor(page: HTMLElement, input: HTMLInputElement, upper: HTMLElement) {
|
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 = {
|
this.windows = {
|
||||||
1: new GlkWindow(page, 1),
|
1: this.mainwnd,
|
||||||
2: new GlkWindow(upper, 2),
|
2: new TeleType(upper, true),
|
||||||
3: new GlkWindow(null, 3), // fake window for resizing
|
3: new TeleType(null, true), // fake window for resizing
|
||||||
};
|
};
|
||||||
this.input = input;
|
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();
|
this.reset();
|
||||||
}
|
}
|
||||||
reset() {
|
reset() {
|
||||||
this.windowcount = 0;
|
this.windowcount = 0;
|
||||||
this.exited = false;
|
this.exited = false;
|
||||||
this.waitingfor = null;
|
|
||||||
this.hideinput();
|
|
||||||
this.windows[1].clear();
|
this.windows[1].clear();
|
||||||
this.windows[2].clear();
|
this.windows[2].clear();
|
||||||
this.curwnd = this.windows[1];
|
this.curwnd = this.windows[1];
|
||||||
|
@ -200,56 +102,7 @@ class GlkImpl {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
focusinput() {
|
focusinput() {
|
||||||
this.ensureline();
|
this.mainwnd.focusinput();
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
flushline() {
|
flushline() {
|
||||||
this.curwnd.flushline();
|
this.curwnd.flushline();
|
||||||
|
@ -265,12 +118,12 @@ class GlkImpl {
|
||||||
this.windows[win].clear();
|
this.windows[win].clear();
|
||||||
}
|
}
|
||||||
glk_request_line_event_uni(win, buf, initlen) {
|
glk_request_line_event_uni(win, buf, initlen) {
|
||||||
this.waitingfor = 'line';
|
this.mainwnd.waitingfor = 'line';
|
||||||
this.focusinput();
|
this.focusinput();
|
||||||
this.startinputtimer();
|
this.startinputtimer();
|
||||||
}
|
}
|
||||||
glk_request_char_event_uni(win, buf, initlen) {
|
glk_request_char_event_uni(win, buf, initlen) {
|
||||||
this.waitingfor = 'char';
|
this.mainwnd.waitingfor = 'char';
|
||||||
this.focusinput();
|
this.focusinput();
|
||||||
this.startinputtimer();
|
this.startinputtimer();
|
||||||
}
|
}
|
||||||
|
@ -287,15 +140,15 @@ class GlkImpl {
|
||||||
|
|
||||||
glk_put_jstring(val: string, allbytes) {
|
glk_put_jstring(val: string, allbytes) {
|
||||||
//debug('glk_put_jstring', arguments);
|
//debug('glk_put_jstring', arguments);
|
||||||
this.curwnd.put_jstring(val);
|
this.curwnd.print(val);
|
||||||
}
|
}
|
||||||
glk_put_jstring_stream(stream: number, val: string) {
|
glk_put_jstring_stream(stream: number, val: string) {
|
||||||
//debug('glk_put_jstring_stream', arguments);
|
//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) {
|
glk_put_char_stream_uni(stream: number, ch: number) {
|
||||||
//debug('glk_put_char_stream_uni', arguments);
|
//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) {
|
glk_set_style(val) {
|
||||||
this.curwnd.curstyle = val;
|
this.curwnd.curstyle = val;
|
||||||
|
@ -396,7 +249,7 @@ class GlkImpl {
|
||||||
}
|
}
|
||||||
glk_window_get_stream(win) {
|
glk_window_get_stream(win) {
|
||||||
debug('glk_window_get_stream', arguments);
|
debug('glk_window_get_stream', arguments);
|
||||||
return this.windows[win].stream;
|
return win;
|
||||||
}
|
}
|
||||||
glk_set_window(win) {
|
glk_set_window(win) {
|
||||||
debug('glk_set_window', arguments);
|
debug('glk_set_window', arguments);
|
||||||
|
@ -772,6 +625,7 @@ class ZmachinePlatform implements Platform {
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
await loadScript('./lib/zvm/ifvms.min.js');
|
await loadScript('./lib/zvm/ifvms.min.js');
|
||||||
|
await loadScript('./gen/common/teletype.js');
|
||||||
|
|
||||||
// create divs
|
// create divs
|
||||||
var parent = this.mainElement;
|
var parent = this.mainElement;
|
||||||
|
@ -780,20 +634,6 @@ class ZmachinePlatform implements Platform {
|
||||||
var windowport = $('<div id="windowport" class="transcript"/>').appendTo(gameport);
|
var windowport = $('<div id="windowport" class="transcript"/>').appendTo(gameport);
|
||||||
var inputline = $('<input class="transcript-input" type="text"/>').appendTo(gameport).hide();
|
var inputline = $('<input class="transcript-input" type="text"/>').appendTo(gameport).hide();
|
||||||
this.glk = new GlkImpl(windowport[0], inputline[0] as HTMLInputElement, upperwnd[0]);
|
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 = () => {
|
this.resize = () => {
|
||||||
// set font size proportional to window width
|
// set font size proportional to window width
|
||||||
var charwidth = $(gameport).width() * 1.6 / STATUS_NUM_COLS;
|
var charwidth = $(gameport).width() * 1.6 / STATUS_NUM_COLS;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user