const SRC_SELECT_SELECTOR = '#src-select'; const COMPILE_BUTTON_SELECTOR = '#compile'; const WORDLIST_SELECTOR = '#wordlist'; const STACK_SELECTOR = '#stack'; const CALLSTACK_SELECTOR = '#callstack'; const VARS_SELECTOR = '#vars'; const SRC_SELECTOR = '#src'; const IP_SELECTOR = '#wordlist .ip'; function selectorForIP(word, offset) { return `#wordlist x-bytecode[x-index='${word}'] x-op[x-index='${offset}']`; } function wordlistElts(wordlist) { return wordlist.map((bc, i) => { const bcElt = document.createElement('x-bytecode'); bcElt.setAttribute('x-index', i); bc.forEach((op, i) => { const opElt = document.createElement('x-op'); opElt.setAttribute('x-index', i); opElt.textContent = op; bcElt.appendChild(opElt); }) return bcElt; }) } export default class extends HTMLElement { static name = 'x-inspector'; static compileRequest = 'compile'; static register() { console.debug('registering custom element', this.name, this); self.customElements.define(this.name, this); } constructor() { super(); this.highRange = new Range(); this.highlight = new Highlight(this.highRange); CSS.highlights.set('exec', this.highlight); console.debug('bjc class var?', this.constructor.name, this.__proto__.name, Object.getPrototypeOf(this)) } connectedCallback() { self.foo = this; console.debug('connectedCallback()', this); [this.srcSelect, this.compileButton, this.wordlist, this.stack, this.callstack, this.vars, this.src] = [ SRC_SELECT_SELECTOR, COMPILE_BUTTON_SELECTOR, WORDLIST_SELECTOR, STACK_SELECTOR, CALLSTACK_SELECTOR, VARS_SELECTOR, SRC_SELECTOR, ].map(this.querySelector.bind(this)); this.compileButton.onclick = e => { console.debug('compile clicked', e); // always add a newline until i decide what to do with the parser. const text = this.src.textContent + '\n'; const compileEvent = new CustomEvent(this.constructor.compileRequest, { detail: { text } }); this.dispatchEvent(compileEvent); } this.srcSelect.onchange = _ => this.loadForth(this.srcSelect.value); this.loadForth(this.srcSelect.value); } attributeChangedCallback(name, old, v) { console.debug('attributeChangedCallback', this, name, old, v); } loadForth(taintedPath) { console.debug('loadForth', this, taintedPath); // ascii only + ‘-’, ‘_’, ‘.’, and ‘/’, but no ‘../’ const path = taintedPath .replace(/[^-_A-Za-z./]/g, '') .replace(/\.\.\//g, ''); fetch(`./samples/${path}`) .then(resp => { if (!resp.ok) { throw `http status ${resp.status}` } return resp.text() }) .then(text => { this.src.textContent = text; }) .catch(e => { console.error(`couldn't fetch ‘${path}’`, e); }); } render(robo, fresh=false) { if (fresh) { this.renderWordlist(robo); } this.renderTextHighlight(robo); this.renderWordlistHighlight(robo); this.renderVars(robo); this.renderStack(robo); this.renderCallstack(robo); } renderWordlist(vm) { while (this.wordlist.lastChild) { console.debug('removing child', this.wordlist.lastChild) this.wordlist.removeChild(this.wordlist.lastChild); } const wordlist = wordlistElts(vm.wordlist); wordlist.forEach(elt => this.wordlist.appendChild(elt)); } renderTextHighlight(vm) { const { word, offset } = vm.ip; const anno = vm.annos[word][offset]; const src = document.querySelector(SRC_SELECTOR); // this assumes the text node is the first child, maybe it isn't? this.highRange.setStart(src.childNodes[0], anno.start); this.highRange.setEnd(src.childNodes[0], anno.end); } renderWordlistHighlight(vm) { const { word, offset } = vm.ip; this.querySelectorAll(IP_SELECTOR).forEach(e => e.classList.remove('ip')); const sel = selectorForIP(word, offset); this.querySelectorAll(sel).forEach(e => { e.classList.add('ip'); }); } renderVars(vm) { while (this.vars.lastChild) { this.vars.removeChild(this.vars.lastChild); } ['out', 'heading', 'speed', 'doppler'].forEach(name => { const dt = document.createElement('dt'); dt.textContent = name; this.vars.appendChild(dt); const dd = document.createElement('dd'); dd.textContent = vm.vars[name]; this.vars.appendChild(dd); }); } renderStack(vm) { while (this.stack.lastChild) { this.stack.removeChild(this.stack.lastChild); } vm.stack.reverse() .forEach(datum => { const elt = document.createElement('li'); elt.textContent = datum; this.stack.appendChild(elt); return elt; }); } renderCallstack(vm) { while (this.callstack.lastChild) { this.callstack.removeChild(this.callstack.lastChild); } vm.callstack.reverse() .forEach(datum => { const elt = document.createElement('li'); elt.textContent = `${datum.word}@${datum.offset}`; this.callstack.appendChild(elt); return elt; }); } }