fix lspWorker and attach App properties

This commit is contained in:
Rachel Powers
2024-04-06 03:21:22 -07:00
parent 3437c9b194
commit 00887575ce
6 changed files with 522 additions and 451 deletions

View File

@@ -1,11 +1,16 @@
import { HTMLTemplateResult, html, css, CSSResultGroup } from "lit"; import { HTMLTemplateResult, html, css, CSSResultGroup } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property, query } from "lit/decorators.js";
import { BaseElement, defaultCss } from "../components"; import { BaseElement, defaultCss } from "../components";
import "./nav.ts"; import "./nav.ts";
import "@shoelace-style/shoelace/dist/components/split-panel/split-panel.js"; import "@shoelace-style/shoelace/dist/components/split-panel/split-panel.js";
import "../editor"; import "../editor";
import { IC10Editor } from "../editor";
import { Session } from "../session";
import { VirtualMachine } from "../virtual_machine";
@customElement("ic10emu-app") @customElement("ic10emu-app")
export class App extends BaseElement { export class App extends BaseElement {
@@ -34,8 +39,21 @@ export class App extends BaseElement {
`, `,
]; ];
editorSettings: { fontSize: number; relativeLineNumbers: boolean };
get editor() {
return this.renderRoot.querySelector('ace-ic10') as IC10Editor;
}
vm!: VirtualMachine;
session!: Session;
constructor() { constructor() {
super(); super();
window.App = this;
this.session = new Session();
this.vm = new VirtualMachine();
} }
protected render(): HTMLTemplateResult { protected render(): HTMLTemplateResult {
@@ -57,3 +75,10 @@ export class App extends BaseElement {
`; `;
} }
} }
declare global {
interface Window {
App?: App;
}
}

View File

@@ -7,7 +7,6 @@ import { LanguageProvider } from "ace-linters/types/language-provider";
import { IC10EditorUI } from "./ui"; import { IC10EditorUI } from "./ui";
import { Range } from "ace-builds"; import { Range } from "ace-builds";
import { App } from "../index";
import { Session } from "../session"; import { Session } from "../session";
// import { Mode as TextMode } from 'ace-code/src/mode/text'; // import { Mode as TextMode } from 'ace-code/src/mode/text';
@@ -341,7 +340,6 @@ export class IC10Editor extends BaseElement {
// this.ui = new IC10EditorUI(this); // this.ui = new IC10EditorUI(this);
const that = this;
} }
protected render() { protected render() {
@@ -427,7 +425,7 @@ export class IC10Editor extends BaseElement {
this.initializeEditor(); this.initializeEditor();
App.session.onLoad(((e: CustomEvent) => { window.App!.session.onLoad(((e: CustomEvent) => {
const session = e.detail; const session = e.detail;
const updated_ids: number[] = []; const updated_ids: number[] = [];
for (const [id, _] of session.programs) { for (const [id, _] of session.programs) {
@@ -441,9 +439,9 @@ export class IC10Editor extends BaseElement {
} }
} }
}) as EventListener); }) as EventListener);
App.session.loadFromFragment(); window.App!.session.loadFromFragment();
App.session.onActiveLine(((e: CustomEvent) => { window.App!.session.onActiveLine(((e: CustomEvent) => {
const session = e.detail; const session = e.detail;
for (const id of session.programs.keys()) { for (const id of session.programs.keys()) {
const active_line = session.getActiveLine(id); const active_line = session.getActiveLine(id);

View File

@@ -12,10 +12,7 @@ export default class Bytes {
return decoder.decode(input); return decoder.decode(input);
} }
static append( static append(constructor: Uint8ArrayConstructor, ...arrays: Uint8Array[]) {
constructor: Uint8ArrayConstructor,
...arrays: Uint8Array[]
) {
let totalLength = 0; let totalLength = 0;
for (const arr of arrays) { for (const arr of arrays) {
totalLength += arr.length; totalLength += arr.length;
@@ -30,26 +27,37 @@ export default class Bytes {
} }
} }
export class AsyncStreamQueue implements AsyncIterator<Uint8Array, undefined, Uint8Array> { export class AsyncStreamQueueUint8Array
implements AsyncIterator<Uint8Array, undefined, Uint8Array> {
promises: Promise<Uint8Array>[] = []; promises: Promise<Uint8Array>[] = [];
resolvers: Promise<Uint8Array>[] = []; resolvers: ((value: Uint8Array) => void)[] = [];
observers: any = []; observers: any = [];
closed = false; closed = false;
tag = ""; tag = "";
stream: WritableStream<Uint8Array>; stream: WritableStream<Uint8Array>;
static __add(promises: any[], resolvers: any[]) { static __add(
promises.push(new Promise((resolve) => { promises: Promise<Uint8Array>[],
resolvers.push(resolve); resolvers: ((value: Uint8Array) => void)[],
})) ) {
promises.push(
new Promise((resolve: (value: Uint8Array) => void) => {
resolvers.push(resolve);
}),
);
} }
static __enqueue(closed: boolean, promises: any[], resolvers: any[], item: any) { static __enqueue(
closed: boolean,
promises: Promise<Uint8Array>[],
resolvers: ((value: Uint8Array) => void)[],
item: Uint8Array,
) {
if (!closed) { if (!closed) {
if (!resolvers.length) AsyncStreamQueue.__add(promises, resolvers); if (!resolvers.length)
const resolve = resolvers.shift(); AsyncStreamQueueUint8Array.__add(promises, resolvers);
const resolve = resolvers.shift()!;
resolve(item); resolve(item);
} }
} }
@@ -62,31 +70,37 @@ export class AsyncStreamQueue implements AsyncIterator<Uint8Array, undefined, Ui
const resolvers = this.resolvers; const resolvers = this.resolvers;
this.stream = new WritableStream({ this.stream = new WritableStream({
write(item) { write(item) {
AsyncStreamQueue.__enqueue(closed, promises, resolvers, item) AsyncStreamQueueUint8Array.__enqueue(closed, promises, resolvers, item);
},
} });
})
} }
_add() { _add() {
return AsyncStreamQueue.__add(this.promises, this.resolvers); return AsyncStreamQueueUint8Array.__add(this.promises, this.resolvers);
} }
enqueue(item: Uint8Array) { enqueue(item: Uint8Array) {
return AsyncStreamQueue.__enqueue(this.closed, this.promises, this.resolvers, item); return AsyncStreamQueueUint8Array.__enqueue(
this.closed,
this.promises,
this.resolvers,
item,
);
} }
dequeue() { dequeue() {
if (!this.promises.length) this._add(); if (!this.promises.length) this._add();
const item = this.promises.shift(); const item = this.promises.shift()!;
return item; return item;
} }
// now some utilities: // now some utilities:
isEmpty() { // there are no values available isEmpty() {
// there are no values available
return !this.promises.length; // this.length <= 0 return !this.promises.length; // this.length <= 0
} }
isBlocked() { // it's waiting for values isBlocked() {
// it's waiting for values
return !!this.resolvers.length; // this.length < 0 return !!this.resolvers.length; // this.length < 0
} }
@@ -116,7 +130,7 @@ export class AsyncStreamQueue implements AsyncIterator<Uint8Array, undefined, Ui
return this; return this;
} }
get locked() { get locked() {
return this.stream.locked; return this.stream.locked;
} }
@@ -131,77 +145,107 @@ export class AsyncStreamQueue implements AsyncIterator<Uint8Array, undefined, Ui
getWriter() { getWriter() {
return this.stream.getWriter(); return this.stream.getWriter();
} }
} }
let clientMsgStream = new AsyncStreamQueue("client"); let clientMsgStream = new AsyncStreamQueueUint8Array("client");
let serverMsgStream = new AsyncStreamQueue("server"); let serverMsgStream = new AsyncStreamQueueUint8Array("server");
async function start() { async function start() {
let config = new ServerConfig(clientMsgStream, serverMsgStream); let config = new ServerConfig(clientMsgStream, serverMsgStream);
await serve(config); await serve(config);
} }
function fixup(data: { hasOwnProperty: (arg0: string) => any; params: { hasOwnProperty: (arg0: string) => any; rootUri: string; textDocument: { hasOwnProperty: (arg0: string) => any; uri: string; }; }; }) { function fixup(data: {
if (data.hasOwnProperty("params") && data.params.hasOwnProperty("rootUri") && data.params.rootUri === "") { hasOwnProperty: (arg0: string) => any;
data.params.rootUri = null params: {
hasOwnProperty: (arg0: string) => any;
rootUri: string | null;
textDocument: { hasOwnProperty: (arg0: string) => any; uri: string };
};
}) {
if (
data.hasOwnProperty("params") &&
data.params.hasOwnProperty("rootUri") &&
data.params.rootUri === ""
) {
data.params.rootUri = null;
} }
if (data.hasOwnProperty("params") && data.params.hasOwnProperty("textDocument")) { if (
data.hasOwnProperty("params") &&
data.params.hasOwnProperty("textDocument")
) {
if (data.params.textDocument.hasOwnProperty("uri")) { if (data.params.textDocument.hasOwnProperty("uri")) {
const match = data.params.textDocument.uri.match(/^file:\/\/\/(.*)/); const match = data.params.textDocument.uri.match(/^file:\/\/\/(.*)/);
if (null == match) { if (null == match) {
data.params.textDocument.uri = `file:///${data.params.textDocument.uri}`; data.params.textDocument.uri = `file:///${data.params.textDocument.uri}`;
} }
} }
data.params.rootUri = null data.params.rootUri = null;
} }
return data return data;
} }
function sendClient(data: any) { function sendClient(data: any) {
data = fixup(data); data = fixup(data);
const data_j = JSON.stringify(data); const data_j = JSON.stringify(data);
const msg = `Content-Length: ${data_j.length}\r\n\r\n${data_j}` const msg = `Content-Length: ${data_j.length}\r\n\r\n${data_j}`;
clientMsgStream.enqueue(encoder.encode(msg)); clientMsgStream.enqueue(encoder.encode(msg));
} }
async function listen() { async function listen() {
let contentLength = null; let contentLength: number | null = null;
let buffer = new Uint8Array(); let buffer = new Uint8Array();
console.log("Worker: listening for lsp messages..."); console.log("Worker: listening for lsp messages...");
for await (const bytes of serverMsgStream) { for await (const bytes of serverMsgStream) {
buffer = Bytes.append(Uint8Array, buffer, bytes); buffer = Bytes.append(Uint8Array, buffer, bytes);
let waitingForFullContent = false;
let messagesThisLoop = 0;
// check if the content length is known // sometimes the buffer can get more than one message in it, loop untill we need to wait for more.
if (null == contentLength) { while (buffer.length > 0 && !waitingForFullContent) {
// if not, try to match the prefixed headers // check if the content length is known
const match = Bytes.decode(buffer).match(/^Content-Length:\s*(\d+)\s*/); if (null == contentLength) {
if (null == match) continue; // if not, try to match the prefixed headers
const match = Bytes.decode(buffer).match(/^Content-Length:\s*(\d+)\s*/);
if (null == match) continue;
// try to parse the content-length from the headers // try to parse the content-length from the headers
const length = parseInt(match[1]); const length = parseInt(match[1]);
if (isNaN(length)) throw new Error("invalid content length"); if (isNaN(length)) throw new Error("invalid content length");
// slice the headers since we now have the content length // slice the headers since we now have the content length
buffer = buffer.slice(match[0].length); buffer = buffer.slice(match[0].length);
// set the content length // set the content length
contentLength = length; contentLength = length;
}
// if the buffer doesn't contain a full message; await another iteration
if (buffer.length < contentLength) {
waitingForFullContent = true;
continue;
}
messagesThisLoop += 1;
// decode buffer up to `contentLength` to a string (leave the rest for the next message)
const delimited = Bytes.decode(buffer.slice(0, contentLength));
// reset the buffer
buffer = buffer.slice(contentLength);
// reset the contentLength
contentLength = null;
try {
const message = JSON.parse(delimited);
console.log(
"Lsp Message:",
`| This Loop: ${messagesThisLoop} |`,
message,
);
postMessage(message);
} catch (e) {
console.log("Error parsing Lsp Message:", e);
}
} }
// if the buffer doesn't contain a full message; await another iteration
if (buffer.length < contentLength) continue;
// decode buffer to a string
const delimited = Bytes.decode(buffer);
// reset the buffer
buffer = buffer.slice(contentLength);
// reset the contentLength
contentLength = null;
const message = JSON.parse(delimited);
console.log("Lsp Message:", message);
postMessage(message)
} }
console.log("Worker: lsp message queue done?"); console.log("Worker: lsp message queue done?");
} }
@@ -212,8 +256,8 @@ postMessage("ready");
onmessage = function (e) { onmessage = function (e) {
console.log("Client Message:", e.data); console.log("Client Message:", e.data);
sendClient(e.data) sendClient(e.data);
} };
console.log("Starting LSP..."); console.log("Starting LSP...");
start(); start();

View File

@@ -3,27 +3,6 @@ import { Session } from "./session";
import { VirtualMachine } from "./virtual_machine"; import { VirtualMachine } from "./virtual_machine";
import { docReady, openFile, saveFile } from "./utils"; import { docReady, openFile, saveFile } from "./utils";
// import { makeRequest } from "./utils"; // import { makeRequest } from "./utils";
declare global {
interface Window {
App: App;
}
}
type App = {
editorSettings: { fontSize: number; relativeLineNumbers: boolean };
editor: IC10Editor;
vm: VirtualMachine;
session: Session;
};
export const App: App = {
editor: null,
vm: null,
session: new Session(),
editorSettings: { fontSize: 16, relativeLineNumbers: false },
};
window.App = App;
// const dbPromise = makeRequest({ method: "GET", url: "/data/database.json"}); // const dbPromise = makeRequest({ method: "GET", url: "/data/database.json"});
// const dbPromise = fetch("/data/database.json").then(resp => resp.json()); // const dbPromise = fetch("/data/database.json").then(resp => resp.json());

View File

@@ -60,10 +60,6 @@ j ra
`; `;
interface CustomEvent extends Event {
detail: string
}
export class Session extends EventTarget { export class Session extends EventTarget {
_programs: Map<number, string>; _programs: Map<number, string>;
_activeSession: number; _activeSession: number;
@@ -142,8 +138,8 @@ export class Session extends EventTarget {
if (this._save_timeout) clearTimeout(this._save_timeout); if (this._save_timeout) clearTimeout(this._save_timeout);
this._save_timeout = setTimeout(() => { this._save_timeout = setTimeout(() => {
this.saveToFragment(); this.saveToFragment();
if (window.App.vm) { if (window.App!.vm) {
window.App.vm.updateCode(); window.App!.vm.updateCode();
} }
this._save_timeout = undefined; this._save_timeout = undefined;
}, 1000); }, 1000);

View File

@@ -1,417 +1,446 @@
import { DeviceRef, VM, init } from "ic10emu_wasm"; import { DeviceRef, VM, init } from "ic10emu_wasm";
import { VMDeviceUI } from "./device"; import { VMDeviceUI } from "./device";
import { BaseElement } from "../components";
// import { Card } from 'bootstrap'; // import { Card } from 'bootstrap';
declare global { declare global {
interface Window { VM: VirtualMachine } interface Window {
VM?: VirtualMachine;
}
} }
type DeviceDB = { type DeviceDB = {
logic_enabled: string[]; logic_enabled: string[];
slot_logic_enabled: string[]; slot_logic_enabled: string[];
devices: string[]; devices: string[];
items: { items: {
[key: string]: { [key: string]: {
name: string, name: string;
hash: number, hash: number;
desc: string, desc: string;
logic?: { [key: string]: string }, logic?: { [key: string]: string };
slots?: { name: string, type: string }[], slots?: { name: string; type: string }[];
modes?: { [key: string]: string }, modes?: { [key: string]: string };
conn?: { [key: string]: string[] }, conn?: { [key: string]: string[] };
} };
} };
} };
class VirtualMachine { class VirtualMachine {
ic10vm: VM; ic10vm: VM;
ui: VirtualMachineUI; ui: VirtualMachineUI;
_devices: Map<number, DeviceRef>; _devices: Map<number, DeviceRef>;
_ics: Map<number, DeviceRef>; _ics: Map<number, DeviceRef>;
db: DeviceDB; db: DeviceDB;
constructor() { constructor() {
const vm = init(); const vm = init();
window.VM = this; window.VM = this;
this.ic10vm = vm; this.ic10vm = vm;
this.ui = new VirtualMachineUI(this); // this.ui = new VirtualMachineUI(this);
this._devices = new Map(); this._devices = new Map();
this._ics = new Map(); this._ics = new Map();
this.updateDevices(); this.updateDevices();
this.updateCode() this.updateCode();
}
get devices() {
return this._devices;
}
get ics() {
return this._ics;
}
get activeIC() {
return this._ics.get(window.App!.session.activeSession);
}
updateDevices() {
const device_ids = this.ic10vm.devices;
for (const id of device_ids) {
if (!this._devices.has(id)) {
this._devices.set(id, this.ic10vm.getDevice(id)!);
}
}
for (const id of this._devices.keys()) {
if (!device_ids.includes(id)) {
this._devices.get(id)!.free();
this._devices.delete(id);
}
} }
get devices() { const ics = this.ic10vm.ics;
return this._devices; for (const id of ics) {
if (!this._ics.has(id)) {
this._ics.set(id, this._devices.get(id)!);
}
} }
for (const id of this._ics.keys()) {
get ics() { if (!ics.includes(id)) {
return this._ics; this._ics.get(id)!.free();
this._ics.delete(id);
}
} }
}
get activeIC() { updateCode() {
return this._ics.get(window.App.session.activeSession); const progs = window.App!.session.programs;
} for (const id of progs.keys()) {
const attempt = Date.now().toString(16);
updateDevices() { const ic = this._ics.get(id);
const prog = progs.get(id);
const device_ids = this.ic10vm.devices; if (ic && prog) {
for (const id of device_ids) { console.time(`CompileProgram_${id}_${attempt}`);
if (!this._devices.has(id)) {
this._devices.set(id, this.ic10vm.getDevice(id));
}
}
for (const id of this._devices.keys()) {
if (!device_ids.includes(id)) {
this._devices.get(id).free();
this._devices.delete(id);
}
}
const ics = this.ic10vm.ics;
for (const id of ics) {
if (!this._ics.has(id)) {
this._ics.set(id, this._devices.get(id));
}
}
for (const id of this._ics.keys()) {
if (!ics.includes(id)) {
this._ics.get(id).free();
this._ics.delete(id);
}
}
}
updateCode() {
const progs = window.App.session.programs;
for (const id of progs.keys()) {
const attempt = Date.now().toString(16)
const ic = this._ics.get(id);
const prog = progs.get(id);
if (ic && prog) {
console.time(`CompileProgram_${id}_${attempt}`);
try {
this.ics.get(id).setCode(progs.get(id));
} catch (e) {
console.log(e);
}
console.timeEnd(`CompileProgram_${id}_${attempt}`);
}
}
this.update();
}
step() {
const ic = this.activeIC;
if (ic) {
try {
ic.step(false);
} catch (e) {
console.log(e);
}
this.update();
}
}
run() {
const ic = this.activeIC;
if (ic) {
try {
ic.run(false);
} catch (e) {
console.log(e);
}
this.update();
}
}
reset() {
const ic = this.activeIC;
if (ic) {
ic.reset();
this.update();
}
}
update() {
this.updateDevices();
const ic = this.activeIC;
window.App.session.setActiveLine(window.App.session.activeSession, ic.ip);
this.ui.update(ic);
}
setRegister(index: number, val: number) {
const ic = this.activeIC;
try { try {
ic.setRegister(index, val); this.ics.get(id)!.setCode(progs.get(id)!);
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
console.timeEnd(`CompileProgram_${id}_${attempt}`);
}
} }
this.update();
}
setStack(addr: number, val: number) { step() {
const ic = this.activeIC; const ic = this.activeIC;
try { if (ic) {
ic.setStack(addr, val); try {
} catch (e) { ic.step(false);
console.log(e); } catch (e) {
} console.log(e);
}
this.update();
} }
}
setupDeviceDatabase(db: DeviceDB) { run() {
this.db = db; const ic = this.activeIC;
console.log("Loaded Device Database", this.db); if (ic) {
try {
ic.run(false);
} catch (e) {
console.log(e);
}
this.update();
} }
}
reset() {
const ic = this.activeIC;
if (ic) {
ic.reset();
this.update();
}
}
update() {
this.updateDevices();
const ic = this.activeIC!;
window.App!.session.setActiveLine(window.App!.session.activeSession, ic.ip!);
// this.ui.update(ic);
}
setRegister(index: number, val: number) {
const ic = this.activeIC!;
try {
ic.setRegister(index, val);
} catch (e) {
console.log(e);
}
}
setStack(addr: number, val: number) {
const ic = this.activeIC;
try {
ic!.setStack(addr, val);
} catch (e) {
console.log(e);
}
}
setupDeviceDatabase(db: DeviceDB) {
this.db = db;
console.log("Loaded Device Database", this.db);
}
} }
class VirtualMachineUI { class VirtualMachineUI {
vm: VirtualMachine; vm: VirtualMachine;
state: VMStateUI; state: VMStateUI;
registers: VMRegistersUI; registers: VMRegistersUI;
stack: VMStackUI; stack: VMStackUI;
devices: VMDeviceUI; devices: VMDeviceUI;
constructor(vm: VirtualMachine) { constructor(vm: VirtualMachine) {
this.vm = vm this.vm = vm;
this.state = new VMStateUI(this); this.state = new VMStateUI(this);
this.registers = new VMRegistersUI(this); this.registers = new VMRegistersUI(this);
this.stack = new VMStackUI(this); this.stack = new VMStackUI(this);
this.devices = new VMDeviceUI(this); this.devices = new VMDeviceUI(this);
const that = this; const that = this;
document.getElementById("vmControlRun").addEventListener('click', (_event) => { document.getElementById("vmControlRun")!.addEventListener(
that.vm.run(); "click",
}, { capture: true }); (_event) => {
document.getElementById("vmControlStep").addEventListener('click', (_event) => { that.vm.run();
that.vm.step(); },
}, { capture: true }); { capture: true },
document.getElementById("vmControlReset").addEventListener('click', (_event) => { );
that.vm.reset(); document.getElementById("vmControlStep")!.addEventListener(
}, { capture: true }); "click",
(_event) => {
} that.vm.step();
},
update(ic: DeviceRef) { { capture: true },
this.state.update(ic); );
this.registers.update(ic); document.getElementById("vmControlReset")!.addEventListener(
this.stack.update(ic); "click",
this.devices.update(ic); (_event) => {
} that.vm.reset();
},
{ capture: true },
);
}
update(ic: DeviceRef) {
this.state.update(ic);
this.registers.update(ic);
this.stack.update(ic);
this.devices.update(ic);
}
} }
class VMStateUI { class VMStateUI {
ui: VirtualMachineUI; ui: VirtualMachineUI;
instructionPointer: HTMLElement; instructionPointer: HTMLElement;
instructionCounter: HTMLElement; instructionCounter: HTMLElement;
lastState: HTMLElement; lastState: HTMLElement;
constructor(ui: VirtualMachineUI) { constructor(ui: VirtualMachineUI) {
this.ui = ui; this.ui = ui;
this.instructionPointer = document.getElementById("vmActiveICStateIP"); this.instructionPointer = document.getElementById("vmActiveICStateIP")!;
this.instructionCounter = document.getElementById("vmActiveICStateICount"); this.instructionCounter = document.getElementById("vmActiveICStateICount")!;
this.lastState = document.getElementById("vmActiveICStateLastRun"); this.lastState = document.getElementById("vmActiveICStateLastRun")!;
} }
update(ic: { ip: { toString: () => string; }; instructionCount: { toString: () => string; }; state: { toString: () => string; }; }) { update(ic: DeviceRef) {
if (ic) { if (ic) {
this.instructionPointer.innerText = ic.ip.toString(); this.instructionPointer.innerText = ic.ip!.toString();
this.instructionCounter.innerText = ic.instructionCount.toString(); this.instructionCounter.innerText = ic.instructionCount!.toString();
this.lastState.innerText = ic.state.toString(); this.lastState.innerText = ic.state!.toString();
}
} }
}
} }
class VMRegistersUI { class VMRegistersUI {
ui: VirtualMachineUI; ui: VirtualMachineUI;
tbl: HTMLDivElement; tbl: HTMLDivElement;
regCells: { regCells: {
cell: HTMLDivElement, cell: HTMLDivElement;
nameLabel: HTMLSpanElement, nameLabel: HTMLSpanElement;
aliasesLabel: HTMLSpanElement, aliasesLabel: HTMLSpanElement;
input: HTMLInputElement input: HTMLInputElement;
}[]; }[];
default_aliases: Map<string, number>; default_aliases: Map<string, number>;
ic_aliases: Map<string, number>; ic_aliases: Map<string, number>;
constructor(ui: VirtualMachineUI) { constructor(ui: VirtualMachineUI) {
const that = this; const that = this;
this.ui = ui; this.ui = ui;
const regDom = document.getElementById("vmActiveRegisters"); const regDom = document.getElementById("vmActiveRegisters")!;
this.tbl = document.createElement("div"); this.tbl = document.createElement("div");
this.tbl.classList.add("d-flex", "flex-wrap", "justify-content-start", "align-items-end",); this.tbl.classList.add(
this.regCells = []; "d-flex",
for (var i = 0; i < 18; i++) { "flex-wrap",
const container = document.createElement("div"); "justify-content-start",
container.classList.add("vm_reg_cel", "align-that-stretch"); "align-items-end",
const cell = document.createElement("div"); );
cell.classList.add("input-group", "input-group-sm") this.regCells = [];
// cell.style.width = "30%"; for (var i = 0; i < 18; i++) {
const nameLabel = document.createElement("span"); const container = document.createElement("div");
nameLabel.innerText = `r${i}`; container.classList.add("vm_reg_cel", "align-that-stretch");
nameLabel.classList.add("input-group-text") const cell = document.createElement("div");
cell.appendChild(nameLabel); cell.classList.add("input-group", "input-group-sm");
const input = document.createElement("input"); // cell.style.width = "30%";
input.type = "text" const nameLabel = document.createElement("span");
input.value = (0).toString(); nameLabel.innerText = `r${i}`;
input.dataset.index = i.toString(); nameLabel.classList.add("input-group-text");
cell.appendChild(input); cell.appendChild(nameLabel);
const aliasesLabel = document.createElement("span"); const input = document.createElement("input");
aliasesLabel.classList.add("input-group-text", "reg_label") input.type = "text";
cell.appendChild(aliasesLabel); input.value = (0).toString();
this.regCells.push({ input.dataset.index = i.toString();
cell, cell.appendChild(input);
nameLabel, const aliasesLabel = document.createElement("span");
aliasesLabel, aliasesLabel.classList.add("input-group-text", "reg_label");
input, cell.appendChild(aliasesLabel);
}); this.regCells.push({
container.appendChild(cell); cell,
this.tbl.appendChild(container); nameLabel,
aliasesLabel,
input,
});
container.appendChild(cell);
this.tbl.appendChild(container);
}
this.regCells.forEach((cell) => {
cell.input.addEventListener("change", that.onCellUpdate);
});
this.default_aliases = new Map([
["sp", 16],
["ra", 17],
]);
this.ic_aliases = new Map();
regDom.appendChild(this.tbl);
}
onCellUpdate(e: Event) {
let index: number;
let val: number;
let target = e.target as HTMLInputElement;
try {
index = parseInt(target.dataset.index!);
val = parseFloat(target.value);
} catch (e) {
// reset the edit
console.log(e);
window.VM!.update();
return;
}
window.VM!.setRegister(index, val);
}
update(ic: DeviceRef) {
if (ic) {
const registers = ic.registers;
if (registers) {
for (var i = 0; i < registers.length; i++) {
this.regCells[i].input.value = registers[i].toString();
} }
this.regCells.forEach(cell => { }
cell.input.addEventListener('change', that.onCellUpdate); const aliases = ic.aliases;
}); if (aliases) {
this.default_aliases = new Map([["sp", 16], ["ra", 17]]);
this.ic_aliases = new Map(); this.ic_aliases = new Map();
regDom.appendChild(this.tbl); aliases.forEach((target, alias, _map) => {
if (
"RegisterSpec" in target &&
target.RegisterSpec.indirection == 0
) {
const index = target.RegisterSpec.target;
this.ic_aliases.set(alias, index);
}
});
}
}
this.updateAliases();
}
updateAliases() {
const aliases = new Map([
...Array.from(this.default_aliases),
...Array.from(this.ic_aliases),
]);
const labels = new Map<number, string[]>();
for (const [alias, target] of aliases) {
if (labels.hasOwnProperty(target)) {
labels.get(target)!.push(alias);
} else {
labels.set(target, [alias]);
}
} }
onCellUpdate(e: Event) { for (const [index, label_list] of labels) {
let index; this.regCells[index].aliasesLabel.innerText = label_list.join(", ");
let val;
let target = (e.target as HTMLInputElement);
try {
index = parseInt(target.dataset.index);
val = parseFloat(target.value);
} catch (e) {
// reset the edit
console.log(e);
window.VM.update();
return;
}
window.VM.setRegister(index, val);
}
update(ic: DeviceRef) {
const that = this;
if (ic) {
const registers = ic.registers;
if (registers) {
for (var i = 0; i < registers.length; i++) {
this.regCells[i].input.value = registers[i].toString();
}
}
const aliases = ic.aliases;
if (aliases) {
this.ic_aliases = new Map();
aliases.forEach((target, alias, _map) => {
if (("RegisterSpec" in target) && target.RegisterSpec.indirection == 0) {
const index = target.RegisterSpec.target;
this.ic_aliases.set(alias, index);
}
})
}
}
this.updateAliases();
}
updateAliases() {
const aliases = new Map([...Array.from(this.default_aliases), ...Array.from(this.ic_aliases)]);
const labels = new Map<number, string[]>();
for (const [alias, target] of aliases) {
if (labels.hasOwnProperty(target)) {
labels.get(target).push(alias)
} else {
labels.set(target, [alias]);
}
}
for (const [index, label_list] of labels) {
this.regCells[index].aliasesLabel.innerText = label_list.join(", ")
}
} }
}
} }
class VMStackUI { class VMStackUI {
ui: VirtualMachineUI; ui: VirtualMachineUI;
tbl: HTMLDivElement; tbl: HTMLDivElement;
stackCells: { cell: HTMLDivElement, nameLabel: HTMLSpanElement, input: HTMLInputElement }[]; stackCells: {
constructor(ui: VirtualMachineUI) { cell: HTMLDivElement;
this.ui = ui; nameLabel: HTMLSpanElement;
const stackDom = document.getElementById("vmActiveStack"); input: HTMLInputElement;
this.tbl = document.createElement("div"); }[];
this.tbl.classList.add("d-flex", "flex-wrap", "justify-content-start", "align-items-end",); constructor(ui: VirtualMachineUI) {
this.stackCells = []; this.ui = ui;
for (var i = 0; i < 512; i++) { const stackDom = document.getElementById("vmActiveStack")!;
const container = document.createElement("div"); this.tbl = document.createElement("div");
container.classList.add("vm_stack_cel", "align-that-stretch"); this.tbl.classList.add(
const cell = document.createElement("div"); "d-flex",
cell.classList.add("input-group", "input-group-sm") "flex-wrap",
const nameLabel = document.createElement("span"); "justify-content-start",
nameLabel.innerText = `${i}`; "align-items-end",
nameLabel.classList.add("input-group-text") );
cell.appendChild(nameLabel); this.stackCells = [];
const input = document.createElement("input"); for (var i = 0; i < 512; i++) {
input.type = "text" const container = document.createElement("div");
input.value = (0).toString(); container.classList.add("vm_stack_cel", "align-that-stretch");
input.dataset.index = i.toString(); const cell = document.createElement("div");
cell.appendChild(input); cell.classList.add("input-group", "input-group-sm");
const nameLabel = document.createElement("span");
nameLabel.innerText = `${i}`;
nameLabel.classList.add("input-group-text");
cell.appendChild(nameLabel);
const input = document.createElement("input");
input.type = "text";
input.value = (0).toString();
input.dataset.index = i.toString();
cell.appendChild(input);
this.stackCells.push({ this.stackCells.push({
cell, cell,
nameLabel, nameLabel,
input, input,
}); });
container.appendChild(cell); container.appendChild(cell);
this.tbl.appendChild(container); this.tbl.appendChild(container);
}
this.stackCells.forEach(cell => {
cell.input.addEventListener('change', this.onCellUpdate);
});
stackDom.appendChild(this.tbl);
} }
this.stackCells.forEach((cell) => {
cell.input.addEventListener("change", this.onCellUpdate);
});
stackDom.appendChild(this.tbl);
}
onCellUpdate(e: Event) { onCellUpdate(e: Event) {
let index; let index: number;
let val; let val: number;
let target = e.target as HTMLInputElement; let target = e.target as HTMLInputElement;
try { try {
index = parseInt(target.dataset.index); index = parseInt(target.dataset.index!);
val = parseFloat(target.value); val = parseFloat(target.value);
} catch (e) { } catch (e) {
// reset the edit // reset the edit
window.VM.update(); window.VM!.update();
return; return;
}
window.VM.setStack(index, val);
} }
window.VM!.setStack(index, val);
}
update(ic: { stack: any; registers: any[]; }) { update(ic: DeviceRef) {
const that = this; if (ic) {
if (ic) { const stack = ic.stack;
const stack = ic.stack; const sp = ic.registers![16];
const sp = ic.registers[16]; if (stack) {
if (stack) { for (var i = 0; i < stack.length; i++) {
for (var i = 0; i < stack.length; i++) { this.stackCells[i].input.value = stack[i].toString();
this.stackCells[i].input.value = stack[i]; if (i == sp) {
if (i == sp) { this.stackCells[i].nameLabel.classList.add("stack_pointer");
this.stackCells[i].nameLabel.classList.add("stack_pointer"); } else {
} else { this.stackCells[i].nameLabel.classList.remove("stack_pointer");
this.stackCells[i].nameLabel.classList.remove("stack_pointer"); }
}
}
}
} }
}
} }
}
} }
export { VirtualMachine, VirtualMachineUI , DeviceDB }; export { VirtualMachine, VirtualMachineUI, DeviceDB };