zmachine: more split window stuff, ztrek

This commit is contained in:
Steven Hugg 2020-07-10 16:37:04 -05:00
parent 34aba0443e
commit ccc011e048
4 changed files with 1947 additions and 63 deletions

1815
presets/zmachine/ztrek.inf Normal file

File diff suppressed because it is too large Load Diff

View File

@ -122,6 +122,7 @@ export interface Platform {
getRasterScanline?() : number; getRasterScanline?() : number;
setBreakpoint?(id : string, cond : DebugCondition); setBreakpoint?(id : string, cond : DebugCondition);
clearBreakpoint?(id : string); clearBreakpoint?(id : string);
hasBreakpoint?(id : string) : boolean;
getCPUState?() : CpuState; getCPUState?() : CpuState;
debugSymbols? : DebugSymbols; debugSymbols? : DebugSymbols;
@ -226,6 +227,9 @@ export abstract class BaseDebugPlatform extends BasePlatform {
clearBreakpoint(id : string) { clearBreakpoint(id : string) {
delete this.breakpoints.id2bp[id]; delete this.breakpoints.id2bp[id];
} }
hasBreakpoint(id : string) {
return this.breakpoints.id2bp[id] != null;
}
getDebugCallback() : DebugCondition { getDebugCallback() : DebugCondition {
return this.breakpoints.getDebugCondition(); return this.breakpoints.getDebugCondition();
} }

View File

@ -399,14 +399,19 @@ export class SourceEditor implements ProjectView {
this.editor.execCommand('undo'); this.editor.execCommand('undo');
} }
toggleBreakpoint(lineno: number) { toggleBreakpoint(lineno: number) {
// TODO: we have to always start at beginning of frame
if (this.sourcefile != null) { if (this.sourcefile != null) {
var targetPC = this.sourcefile.line2offset[lineno+1]; var targetPC = this.sourcefile.line2offset[lineno+1];
/* TODO: breakpoints /*
var bpid = "pc" + targetPC; var bpid = "pc" + targetPC;
platform.setBreakpoint(bpid, () => { if (platform.hasBreakpoint(bpid)) {
return platform.getPC() == targetPC; platform.clearBreakpoint(bpid);
}); } else {
platform.setBreakpoint(bpid, () => {
return platform.getPC() == targetPC;
});
}
*/ */
runToPC(targetPC); runToPC(targetPC);
} }
@ -1352,6 +1357,7 @@ class TreeNode {
this.getDiv(); this.getDiv();
var text = ""; var text = "";
// is it a function? call it first, if we are expanded // is it a function? call it first, if we are expanded
// TODO: only call functions w/ signature
if (typeof obj == 'function' && this._content != null) { if (typeof obj == 'function' && this._content != null) {
obj = obj(); obj = obj();
} }
@ -1478,6 +1484,7 @@ export class DebugBrowserView extends TreeViewBase implements ProjectView {
interface CallGraphNode { interface CallGraphNode {
count : number; count : number;
SP : number; SP : number;
PC : number;
calls : {[id:string] : CallGraphNode}; calls : {[id:string] : CallGraphNode};
} }
@ -1508,7 +1515,7 @@ export class CallStackView extends ProbeViewBaseBase implements ProjectView {
} }
reset() { reset() {
this.stack = []; // TODO??? should continue across frames this.stack = [];
this.lastsp = -1; this.lastsp = -1;
this.jsr = false; this.jsr = false;
} }
@ -1518,28 +1525,34 @@ export class CallStackView extends ProbeViewBaseBase implements ProjectView {
switch (op) { switch (op) {
case ProbeFlags.SP_PUSH: case ProbeFlags.SP_PUSH:
// need a new root? // need a new root?
if (this.stack.length == 0 || addr > this.stack[0].SP) { if (this.stack.length == 0) {
this.graph = {count:0, SP:addr, calls:{}}; this.graph = {count:0, PC:null, SP:addr, calls:{}};
this.stack.unshift(this.graph);
} else if (addr > this.stack[0].SP) {
let calls = {};
if (this.stack[0].PC !== null) calls[this.stack[0].PC] = this.stack[0];
this.graph = {count:0, PC:null, SP:addr, calls:calls};
this.stack.unshift(this.graph); this.stack.unshift(this.graph);
} }
case ProbeFlags.SP_POP: case ProbeFlags.SP_POP:
if (this.stack.length) { if (this.stack.length) {
var top = this.stack[this.stack.length-1]; let top = this.stack[this.stack.length-1];
if ((this.lastsp - addr) == 2 && addr < top.SP) { // TODO: look for opcode? if ((this.lastsp - addr) == 2 && addr < top.SP) { // TODO: look for opcode?
this.jsr = true; this.jsr = true;
} }
if ((this.lastsp - addr) == -2 && this.stack.length > 1 && addr > top.SP) { if ((this.lastsp - addr) == -2 && this.stack.length > 1 && addr > top.SP) {
this.stack.pop(); this.stack.pop();
} }
this.lastsp = addr;
} }
this.lastsp = addr;
break; break;
case ProbeFlags.EXECUTE: case ProbeFlags.EXECUTE:
if (this.jsr && this.stack.length) { if (this.jsr && this.stack.length) {
var top = this.stack[this.stack.length-1]; let top = this.stack[this.stack.length-1];
var sym = this.addr2str(addr); let sym = this.addr2str(addr);
var child = top.calls[sym]; let child = top.calls[sym];
if (child == null) { child = top.calls[sym] = {count:0, SP:this.lastsp, calls:{}}; } if (child == null) { child = top.calls[sym] = {count:0, PC:addr, SP:this.lastsp, calls:{}}; }
else if (child.PC === null) child.PC = addr;
//this.stack.forEach((node) => node.count++); //this.stack.forEach((node) => node.count++);
this.stack.push(child); this.stack.push(child);
child.count++; child.count++;
@ -1548,6 +1561,7 @@ export class CallStackView extends ProbeViewBaseBase implements ProjectView {
break; break;
} }
}); });
if (this.graph) this.graph['_stack'] = this.stack;
return this.graph; return this.graph;
} }
} }

View File

@ -22,6 +22,7 @@ const ZMACHINE_PRESETS = [
{ id: 'balances.inf', name: 'Balances' }, { id: 'balances.inf', name: 'Balances' },
{ id: 'museum.inf', name: 'Museum of Inform' }, { id: 'museum.inf', name: 'Museum of Inform' },
{ id: 'advent.inf', name: 'Colossal Cave Adventure' }, { id: 'advent.inf', name: 'Colossal Cave Adventure' },
{ id: 'ztrek.inf', name: 'Super Z Trek' },
]; ];
declare var ZVM; declare var ZVM;
@ -38,9 +39,14 @@ interface IFZVM {
pc: number; pc: number;
ram: DataView; ram: DataView;
stack: DataView; stack: DataView;
read_data: { buffer?}; read_data: { buffer?, routine?, time?};
handle_line_input(len: number); handle_line_input(len: number);
handle_char_input(charcode: number); handle_char_input(charcode: number);
handle_create_fileref(fref: number);
}
function debug(...args: any[]) {
//console.log(arguments);
} }
class GlkWindow { class GlkWindow {
@ -72,11 +78,12 @@ class GlkWindow {
} }
ensureline() { ensureline() {
if (this.curline == null) { if (this.curline == null) {
this.curline = $('<div class="transcript-line"/>')[0]; this.curline = this.lines[++this.row];
this.page.appendChild(this.curline); if (this.curline == null) {
this.row++; this.curline = $('<div class="transcript-line"/>')[0];
this.col = 0; this.page.appendChild(this.curline);
this.lines[this.row] = this.curline; this.lines[this.row] = this.curline;
}
} }
} }
flushline() { flushline() {
@ -86,24 +93,42 @@ class GlkWindow {
addtext(line: string, style: number) { addtext(line: string, style: number) {
this.ensureline(); this.ensureline();
if (line.length) { if (line.length) {
var span = $("<span/>").text(line).appendTo(this.curline); // 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++) { for (var i = 0; i < 8; i++) {
if (style & (1 << i)) if (style & (1 << i))
span.addClass("transcript-style-" + (1 << i)); span.addClass("transcript-style-" + (1 << i));
} }
if (this.reverse) span.addClass("transcript-reverse"); if (this.reverse) span.addClass("transcript-reverse");
//span.data('vmip', this.vm.pc); //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; 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) { put_jstring(val: string) {
// split by newlines
var lines = val.split("\n"); var lines = val.split("\n");
for (var i = 0; i < lines.length; i++) { for (var i = 0; i < lines.length; i++) {
if (i > 0) this.flushline(); if (i > 0) this.newline();
this.addtext(lines[i], this.curstyle); this.addtext(lines[i], this.curstyle);
} }
} }
move_cursor(col:number, row:number) { move_cursor(col: number, row: number) {
if (!this.fixed) return; // fixed windows only if (!this.fixed) return; // fixed windows only
// ensure enough row elements // ensure enough row elements
while (this.lines.length <= row) { while (this.lines.length <= row) {
@ -115,24 +140,23 @@ class GlkWindow {
this.row = row; this.row = row;
// get children in row (individual text cells) // get children in row (individual text cells)
var children = $(this.curline).children(); var children = $(this.curline).children();
// truncate line, or add whitespace // add whitespace to line?
if (children.length > col) { if (children.length > col) {
children.slice(col).remove();
this.col = col; this.col = col;
} else { } else {
while (this.col < col) while (this.col < col)
this.addtext(' ', this.curstyle); this.addtext(' ', this.curstyle);
} }
} }
setrows(size:number) { setrows(size: number) {
if (!this.fixed) return; // fixed windows only if (!this.fixed) return; // fixed windows only
this.flushline();
// truncate rows? // truncate rows?
var allrows = $(this.page).children(); var allrows = $(this.page).children();
if (allrows.length > size) { if (allrows.length > size) {
this.flushline();
allrows.slice(size).remove(); allrows.slice(size).remove();
this.lines = this.lines.slice(0, size); this.lines = this.lines.slice(0, size);
this.move_cursor(0,0); //this.move_cursor(0,0);
} }
} }
} }
@ -237,28 +261,40 @@ class GlkImpl {
this.windows[1].addtext("** Game exited **", 1); this.windows[1].addtext("** Game exited **", 1);
} }
glk_window_clear(win) { glk_window_clear(win) {
console.log('glk_window_clear', arguments); debug('glk_window_clear', arguments);
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.waitingfor = 'line';
this.focusinput(); this.focusinput();
this.startinputtimer();
} }
glk_request_char_event_uni(win, buf, initlen) { glk_request_char_event_uni(win, buf, initlen) {
this.waitingfor = 'char'; this.waitingfor = 'char';
this.focusinput(); this.focusinput();
this.startinputtimer();
}
startinputtimer() {
/* TODO?
var rd = this.vm.read_data;
if (rd.routine && rd.time) {
this.vm['call'](rd.routine);
//this.vm.run();
setTimeout(this.startinputtimer.bind(this), rd.time*10);
}
*/
} }
glk_put_jstring(val: string, allbytes) { glk_put_jstring(val: string, allbytes) {
//console.log('glk_put_jstring', arguments); //debug('glk_put_jstring', arguments);
this.curwnd.put_jstring(val); this.curwnd.put_jstring(val);
} }
glk_put_jstring_stream(stream: number, val: string) { glk_put_jstring_stream(stream: number, val: string) {
//console.log('glk_put_jstring_stream', arguments); //debug('glk_put_jstring_stream', arguments);
this.windows[stream].put_jstring(val); this.windows[stream].put_jstring(val);
} }
glk_put_char_stream_uni(stream: number, ch: number) { glk_put_char_stream_uni(stream: number, ch: number) {
//console.log('glk_put_char_stream_uni', arguments); //debug('glk_put_char_stream_uni', arguments);
this.windows[stream].put_jstring(String.fromCharCode(ch)); this.windows[stream].put_jstring(String.fromCharCode(ch));
} }
glk_set_style(val) { glk_set_style(val) {
@ -266,31 +302,31 @@ class GlkImpl {
} }
/* /*
glk_put_char(ch) { glk_put_char(ch) {
console.log('glk_put_char', arguments); debug('glk_put_char', arguments);
} }
glk_put_string(val) { glk_put_string(val) {
console.log('glk_put_string', arguments); debug('glk_put_string', arguments);
} }
glk_put_string_stream(str, val) { glk_put_string_stream(str, val) {
console.log('glk_put_string_stream', arguments); debug('glk_put_string_stream', arguments);
} }
glk_put_buffer(arr) { glk_put_buffer(arr) {
console.log('glk_put_buffer', arguments); debug('glk_put_buffer', arguments);
} }
glk_put_buffer_stream(str, arr) { glk_put_buffer_stream(str, arr) {
console.log('glk_put_buffer_stream', arguments); debug('glk_put_buffer_stream', arguments);
} }
glk_set_style_stream(str, val) { glk_set_style_stream(str, val) {
console.log('glk_set_style_stream', arguments); debug('glk_set_style_stream', arguments);
} }
glk_get_char_stream(str) { glk_get_char_stream(str) {
console.log('glk_get_char_stream', arguments); debug('glk_get_char_stream', arguments);
} }
glk_get_line_stream(str, buf) { glk_get_line_stream(str, buf) {
console.log('glk_get_line_stream', arguments); debug('glk_get_line_stream', arguments);
} }
glk_get_buffer_stream(str, buf) { glk_get_buffer_stream(str, buf) {
console.log('glk_get_buffer_stream', arguments); debug('glk_get_buffer_stream', arguments);
} }
*/ */
glk_char_to_lower(val) { glk_char_to_lower(val) {
@ -308,10 +344,10 @@ class GlkImpl {
return val; return val;
} }
glk_stylehint_set(wintype, styl, hint, value) { glk_stylehint_set(wintype, styl, hint, value) {
//console.log('glk_stylehint_set', arguments); //debug('glk_stylehint_set', arguments);
} }
glk_stylehint_clear(wintype, styl, hint) { glk_stylehint_clear(wintype, styl, hint) {
//console.log('glk_stylehint_clear', arguments); //debug('glk_stylehint_clear', arguments);
} }
glk_style_distinguish(win, styl1, styl2) { glk_style_distinguish(win, styl1, styl2) {
return 0; return 0;
@ -322,62 +358,76 @@ class GlkImpl {
return 0; return 0;
} }
glk_select(eventref) { glk_select(eventref) {
console.log('glk_select', arguments); debug('glk_select', arguments);
} }
glk_window_open(splitwin, method, size, wintype, rock) { glk_window_open(splitwin, method, size, wintype, rock) {
console.log('glk_window_open', arguments); debug('glk_window_open', arguments);
if (splitwin) { if (splitwin) {
// only support status lines for now if (method != 0x12 || wintype != 4) return 0;
if (method != 0x12 || wintype != 4 || size != 1) return 0; if (size) {
$(this.windows[2].page).show(); $(this.windows[2].page).show();
return 2; // split window
} else {
return 3; // fake window
}
} else {
return 1; // main window
} }
return ++this.windowcount;
} }
glk_window_close(win) { glk_window_close(win) {
console.log('glk_window_close', arguments); debug('glk_window_close', arguments);
if (win == 2) $(this.windows[win].page).hide(); if (win == 2) {
this.windows[win].clear();
$(this.windows[win].page).hide();
}
} }
glk_window_get_parent(win) { glk_window_get_parent(win) {
console.log('glk_window_get_parent', arguments); debug('glk_window_get_parent', arguments);
if (win == 1) return 0; if (win == 1) return 0;
else return 1; else return 1;
} }
glk_window_move_cursor(win, col, row) { glk_window_move_cursor(win, col, row) {
console.log('glk_window_move_cursor', arguments); debug('glk_window_move_cursor', arguments);
this.windows[win].move_cursor(col, row); this.windows[win].move_cursor(col, row);
} }
glk_window_set_arrangement(win, method, size, unknown) { glk_window_set_arrangement(win, method, size, unknown) {
console.log('glk_window_set_arrangement', arguments); debug('glk_window_set_arrangement', arguments);
// TODO? this.windows[win].setrows(size); if (win == 1) this.windows[2].setrows(size);
} }
glk_window_get_stream(win) { glk_window_get_stream(win) {
console.log('glk_window_get_stream', arguments); debug('glk_window_get_stream', arguments);
return this.windows[win].stream; return this.windows[win].stream;
} }
glk_set_window(win) { glk_set_window(win) {
console.log('glk_set_window', arguments); debug('glk_set_window', arguments);
this.curwnd = this.windows[win]; this.curwnd = this.windows[win];
if (this.curwnd == null) this.fatal_error("no window " + win); if (this.curwnd == null) this.fatal_error("no window " + win);
} }
glk_window_get_size(win, widthref: RefBox, heightref: RefBox) { glk_window_get_size(win, widthref: RefBox, heightref: RefBox) {
console.log('glk_window_get_size', arguments); debug('glk_window_get_size', arguments);
// TODO: made up sizes, only status line supported // TODO: made up sizes, only status line supported
if (widthref) widthref.set_value(STATUS_NUM_COLS); if (widthref) widthref.set_value(STATUS_NUM_COLS);
if (heightref) heightref.set_value(win == 1 ? 25 : 1); if (heightref) heightref.set_value(win == 1 ? 25 : 1);
} }
garglk_set_reversevideo(val) { garglk_set_reversevideo(val) {
console.log('garglk_set_reversevideo', arguments); debug('garglk_set_reversevideo', arguments);
this.curwnd.reverse = !!val; this.curwnd.reverse = !!val;
} }
garglk_set_reversevideo_stream(win, val) { garglk_set_reversevideo_stream(win, val) {
console.log('garglk_set_reversevideo_stream', arguments); debug('garglk_set_reversevideo_stream', arguments);
this.windows[win].reverse = !!val; // TODO: per window this.windows[win].reverse = !!val;
}
glk_fileref_create_by_prompt(usage, mode, rock) {
debug('glk_fileref_create_by_prompt', arguments);
// TODO: support files?
this.vm.handle_create_fileref(0);
this.vm.run();
} }
glk_gestalt(sel, val) { glk_gestalt(sel, val) {
return this.glk_gestalt_ext(sel, val, null); return this.glk_gestalt_ext(sel, val, null);
} }
glk_gestalt_ext(sel, val, arr) { glk_gestalt_ext(sel, val, arr) {
//console.log('glk_gestalt_ext', arguments); //debug('glk_gestalt_ext', arguments);
switch (sel) { switch (sel) {
case 0: // gestalt_Version case 0: // gestalt_Version
@ -750,7 +800,7 @@ class ZmachinePlatform implements Platform {
this.resize(); this.resize();
} }
resize : () => void; resize: () => void;
loadROM(title, data) { loadROM(title, data) {
this.zfile = data; this.zfile = data;
@ -833,6 +883,7 @@ class ZmachinePlatform implements Platform {
if (this.zvm != null) { if (this.zvm != null) {
root['Objects'] = () => this.getRootObjects(); root['Objects'] = () => this.getRootObjects();
root['Globals'] = () => this.getGlobalVariables(); root['Globals'] = () => this.getGlobalVariables();
//root['VM'] = () => this.zvm;
} }
return root; return root;
} }