diff options
Diffstat (limited to 'editors/vscode/client/vmproc.js')
| -rw-r--r-- | editors/vscode/client/vmproc.js | 81 |
1 files changed, 81 insertions, 0 deletions
diff --git a/editors/vscode/client/vmproc.js b/editors/vscode/client/vmproc.js new file mode 100644 index 0000000..d4806e4 --- /dev/null +++ b/editors/vscode/client/vmproc.js @@ -0,0 +1,81 @@ +// runs the language server inside the extension, in the tinyvm javascript vm, +// so nothing has to be built or installed for the server to start. it presents +// the same surface the client uses on a child process: stdin to write, stdout +// and stderr to listen to, an exit event and kill(). +// +// the vm is streaming: the server runs until it wants input, the client's bytes +// are fed in and the server goes on from there. each step is synchronous, so a +// long check holds the extension host for as long as it takes. +// +// opts.log(level, text) gets what the vm does: the image it loads, each halt, +// and the messages the server writes to its error stream. +const path = require('path'); +const { EventEmitter } = require('events'); + +function vmProcess(dir, opts = {}) { + const log = opts.log || (() => {}); + const { TinyVM } = require(path.join(dir, 'tinyvm.js')); + const image = require(path.join(dir, 'lsp.json')); + const proc = new EventEmitter(); + const out = new EventEmitter(); + const err = new EventEmitter(); + const pending = []; + let dead = false; + let bytesIn = 0, bytesOut = 0, steps = 0; + + const vm = new TinyVM({ + streaming: true, + memoryWords: image.layout.HEAP_HI, // the initial heap; the machine grows on the heap's request + layout: image.layout, + onOutput: b => { pending.push(b); }, + onMessage: m => { + log('problem', 'server says: ' + String(m).slice(0, 500)); + err.emit('data', Buffer.from(m, 'utf8')); + }, + }); + vm.loadGenome(image.words, image.gorg); + log('message', 'loaded the image: ' + image.words.length + ' words, code at ' + image.gorg); + + const flush = () => { + if (pending.length) { + const chunk = Buffer.from(pending); + bytesOut += chunk.length; + pending.length = 0; + out.emit('data', chunk); + } + }; + const after = status => { + steps++; + flush(); + if (status !== 3 && !dead) { // it halted: the server has exited + dead = true; + log(status === 0 ? 'message' : 'problem', 'the vm halted with status ' + status + ' after ' + steps + ' steps, ' + bytesIn + ' bytes in, ' + bytesOut + ' out'); + proc.emit('exit', status); + } + }; + + proc.stdin = { + writable: true, + write(buf) { + if (dead) { log('problem', 'write to a halted vm dropped (' + buf.length + ' bytes)'); return false; } + bytesIn += buf.length; + vm.feed(buf); + after(vm.resume()); + return true; + }, + }; + proc.stdout = out; + proc.stderr = err; + proc.kill = () => { + if (dead) return; + dead = true; + log('message', 'vm killed after ' + steps + ' steps'); + proc.emit('exit', null); + }; + const t0 = Date.now(); + after(vm.run(image.gorg)); + log('message', 'vm ran to its first wait in ' + (Date.now() - t0) + ' ms'); + return proc; +} + +module.exports = { vmProcess }; |
