aboutsummaryrefslogtreecommitdiff
path: root/editors/vscode/client/vmproc.js
diff options
context:
space:
mode:
Diffstat (limited to 'editors/vscode/client/vmproc.js')
-rw-r--r--editors/vscode/client/vmproc.js81
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 };