Skip to content

Commit

Permalink
Add custom irunner.js to serve GHCJSi sessions with static files
Browse files Browse the repository at this point in the history
  • Loading branch information
fizruk committed Dec 1, 2017
1 parent 75bded1 commit 961b0e3
Showing 1 changed file with 208 additions and 0 deletions.
208 changes: 208 additions & 0 deletions ghcjs/lib/etc/irunner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/*
GHCJSi communication
reads messages from stdin, sends over stderr
*/

var h$GHCJSiRecord = // true ||
!!process.env['GHCJS_RECORD_GHCJSI'];

var h$GHCJSiPort = process.env['GHCJSI_PORT'] || 6401;

var h$GHCJSiStaticDir = process.env['GHCJSI_STATIC_DIR'];

var h$GHCJSiReplay = process.argv.length > 0 &&
process.argv[process.argv.length-1] === 'replay';

var h$GHCJSi = { data: null
, loadedSymbols: {}
, current: null
, sendMessage: h$sendMessage
, done: h$GHCJSiDone
, clientHtml: ''
, clientJS: ''
, socket: null
};

global.h$GHCJSi = h$GHCJSi;
global.require = require;
global.module = module;

var fs = require('fs');
var server = require('http').createServer(h$handleHTTP);
var url = require('url');
var io = null;
try {
io = require('socket.io')(server);
} catch (e) { }

if (h$GHCJSiStaticDir) {
try {
/* Try to set up a static file server for GHCJSi */
var finalhandler = require('finalhandler');
var serveStatic = require('serve-static');
var serveStaticDir = serveStatic(h$GHCJSiStaticDir);
} catch (e) {
console.log("\ncan't set up static file server for " + h$GHCJSiStaticDir);
console.log("\nmake sure you have filehandler and serve-static installed:");
console.log("\n\n npm install filehandler serve-static\n");
}
}

// start listening
function h$initGHCJSi() {
process.stdin.setEncoding('utf8');
process.stderr.setEncoding('binary');
process.on('uncaughtException', function(err) {
console.log(err);
console.log(err.stack);
});
if(h$GHCJSiReplay) {
h$replayMessage(1);
} else {
h$startHTTPServer();
process.stdin.on('readable', function() {
while(true) {
var str = process.stdin.read();
if(str) {
var buf = new Buffer(str, 'hex');
h$GHCJSi.data = h$GHCJSi.data ? Buffer.concat([h$GHCJSi.data, buf]) : buf;
h$processInput();
} else {
return;
}
}
});
process.stdin.on('close', function() { process.exit(0); });
}
}

function h$replayMessage(n) {
try {
var buffer = fs.readFileSync("ghcjsimessage." + n + ".dat");
var msgType = buffer.readUInt32BE(0);
h$processMessage(msgType, buffer.slice(4));
setTimeout(function() { h$replayMessage(n+1); }, 1500);
} catch(e) { }
}

function h$startHTTPServer() {
if(!io) {
console.log("\nsocket.io not found, browser session not available");
return;
} else {
console.log("\nsocket.io found, browser session available at http://localhost:" + h$GHCJSiPort);
if (typeof serveStaticDir !== 'undefined') {
console.log("\nserving static files from " + h$GHCJSiStaticDir);
}
}

io.on('connection', function(socket) {
// console.log("\nbrowser connected, code runs in browser from now on");
h$GHCJSi.socket = io;
socket.on('msg', function(msg) {
h$GHCJSi.sendMessage(msg.type, msg.payload);
});
socket.on('out', function(data) {
process.stdout.write(data);
});
socket.on('disconnect', function() {
// console.log('browser disconnected');
});
});
h$GHCJSi.clientHtml = fs.readFileSync(__dirname + '/ghcjsiClient.html');
h$GHCJSi.clientJs = fs.readFileSync(__dirname + '/ghcjsiClient.js');
server.listen(h$GHCJSiPort);
}

function h$handleHTTP(req, res) {
var u = url.parse(req.url);
if(u.pathname === '/' || u.pathname === '/index.html') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(h$GHCJSi.clientHtml);
} else if(u.pathname === '/client.js') {
res.writeHead(200, { 'Content-Type': 'application/javascript' });
res.end(h$GHCJSi.clientJs);
} else if (typeof serveStaticDir !== 'undefined') {
serveStaticDir(req, res, finalhandler(req, res));
} else {
res.statusCode = 404;
res.statusMessage = 'not found';
res.end();
}
}

var h$GHCJSiMessageN = 0;
function h$processInput() {
while(h$GHCJSi.data && h$GHCJSi.data.length >= 8) {
var msgLength = h$GHCJSi.data.readUInt32BE(0);
var msgType = h$GHCJSi.data.readUInt32BE(4);
if(h$GHCJSi.data.length >= msgLength + 8) {
if(h$GHCJSiRecord && !h$GHCJSiReplay) {
fs.writeFileSync("ghcjsimessage." + (++h$GHCJSiMessageN) + ".dat"
,h$GHCJSi.data.slice(4, msgLength+8));
}
var msgPayload = h$GHCJSi.data.slice(8, msgLength + 8);
h$GHCJSi.data = h$GHCJSi.data.slice(msgLength + 8);
if(h$GHCJSi.socket) {
h$GHCJSi.socket.emit('msg', { type: msgType, payload: msgPayload });
} else {
h$processMessage(msgType, msgPayload);
}
} else return;
}
}

function h$processMessage(msgType, msgPayload) {
switch(msgType) {
case 0: // load initial code/rts and init
h$loadInitialCode(msgPayload.toString('utf8'));
h$sendMessage(0);
break;
case 1: // load code
h$loadCodeStr(msgPayload.toString('utf8'));
h$sendMessage(0);
break;
case 2: // run action
var symb = msgPayload.toString('utf8');
h$GHCJSi.current = h$main(h$GHCJSi.loadedSymbols[msgPayload.toString('utf8')]);
break;
case 3: // abort
if(h$GHCJSi.current)
h$killThread( h$GHCJSi.current
, h$baseZCControlziExceptionziBasezinonTermination);
break;
default:
throw new Error("unknown message type: " + msgType);
}
}

function h$GHCJSiDone(thread) {
h$sendMessage(0);
h$GHCJSi.current = null;
}

function h$sendMessage(msgType, msg, c) {
var hdr = new Buffer(8);
hdr.writeUInt32BE(msg ? msg.length : 0, 0);
hdr.writeUInt32BE(msgType, 4);
process.stderr.write( msg ? Buffer.concat([hdr, msg]) : hdr, 'binary'
, function() { if(c) c(); });
}

// load the RTS and set up the standard streams
function h$loadInitialCode(code) {
h$loadCodeStr(code, true);

// don't allow Haskell to read from stdin (fixme!)
h$base_stdin_fd.read = function(fd, fdo, buf, buf_offset, n, c) { c(0); }

// redirect Haskell's stderr to stdout since we use stderr to communicate (fixme!)
h$base_stderr_fd.write = h$base_stdout_fd.write;
}

function h$loadCodeStr(str) {
eval.call(null, str);
}

h$initGHCJSi();

0 comments on commit 961b0e3

Please sign in to comment.