Skip to content
Snippets Groups Projects
Forked from Glasgow Haskell Compiler / GHC
5395 commits behind the upstream repository.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
ghc-interp.js 2.84 KiB
/*
  GHC JS Interpreter

  See Note [The JS interpreter] in GHC.Runtime.Interpreter.JS

  Read commands on stdin (ending with \n):
    LOAD foo.js             : load foo.js file
    RUN_SERVER ghci_unit_id : run ghci_unit_id:GHCi.Server.defaultServer

  Once the Haskell server is started with RUN_SERVER, the JS server no longer
  reads commands on stdin. Everything must go through the Haskell server (which
  uses pipes for communication)
*/

var h$THfs = require('fs');
var h$THvm = require('vm');

function h$debug_log(s) {
  // uncomment the following line to enable some debug messages
  // console.log("[JS interpreter] " + s);
}

// load and exec JS file
function h$loadJS(path) {
  h$debug_log("Loading file: " + path);
  var data = h$THfs.readFileSync(path);
  const script = new h$THvm.Script(data);
  script.runInThisContext();
}

// Lookup a static closure by its name
function h$lookupClosure(v) {
  h$debug_log("Looking up closure: " + v);
  const r = eval(v);
  h$debug_log("  -> Result: " + r);
  if (!r) return 0;
  // a RemoteRef is just the offset of a stable pointer
  return h$makeStablePtr(r);
}

// give access to these functions to the dynamically linked code
globalThis.h$loadJS = h$loadJS;
globalThis.h$lookupClosure = h$lookupClosure;
global.require = require;
global.module  = module;


function h$initInterp() {
  h$debug_log("Welcome to GHC's JS interpreter");

  function stdin_end() {
    h$debug_log('GHC disconnected: goodbye.');
    process.exit(1);
  };

  // read until we find '\n'
  // Accumulate bytes in "bytes" array
  let bytes = [];
  let decoder = new TextDecoder('utf8');

  function stdin_readable() {
    // read until we find '\n'
    while (null !== (bs = process.stdin.read(1))) {
      let b = bs[0];
      switch(b) {
        case 10: // `\n` found. `bytes` must contain a command
          let cmd = decoder.decode(new Uint8Array(bytes));
          bytes = [];
          // we only supports 2 commands: LOAD, RUN_SERVER
          if (cmd.startsWith("LOAD ")) {
            h$loadJS(cmd.slice(5));
          }
          else if (cmd.startsWith("RUN_SERVER ")) {
            let uid = cmd.slice(11);
            let root = eval("h$" + uid + "ZCGHCiziServerzidefaultServer");
            // remove listeners
            process.stdin.removeListener('end',      stdin_end);
            process.stdin.removeListener('readable', stdin_readable);
            // run the server
            h$debug_log("Run server");
            h$main(root);
            // break the loop
            return;
          }
          else {
            console.log("[JS interpreter] Invalid command received: " + cmd);
            process.exit(1);
          }
          break;
        default:
          bytes.push(b);
      }
    }
  };

  // read commands on STDIN
  process.stdin.on('end',      stdin_end);
  process.stdin.on('readable', stdin_readable);
}

h$initInterp();