Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge the ghcjs --interactive branch into the ghc 8 branch #549

Open
wants to merge 12 commits into
base: ghc-8.0
Choose a base branch
from
11 changes: 11 additions & 0 deletions ghcjs.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ Library
Compiler.Settings,
Compiler.Utils,
Compiler.Variants,
Compiler.DriverPipeline,
Compiler.GhciMonad,
Compiler.GhciTags,
Compiler.InteractiveEval,
Compiler.InteractiveUI,
Compiler.GhcMake,
Compiler.JMacro,
Compiler.JMacro.Base,
Compiler.JMacro.Lens,
Expand Down Expand Up @@ -167,12 +173,17 @@ Library
stringsearch >= 0.3 && < 0.4,
base16-bytestring >= 0.1 && < 0.2,
cryptohash,
haskeline >= 0.7 && < 0.8,
-- for JMacro
regex-posix >= 0.90 && < 0.100,
safe >= 0.3 && < 0.4,
parsec >= 3.1 && < 3.2,
haskell-src-exts >= 1.16 && < 1.19,
haskell-src-meta >= 0.6.0.3 && < 0.8
if os(Windows)
build-depends: Win32
else
build-depends: unix
exposed: True
buildable: True
hs-source-dirs: src lib/ghcjs-prim
Expand Down
10 changes: 10 additions & 0 deletions lib/etc/ghcjsiClient.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>GHCJSi Session</title>
<script src="/socket.io/socket.io.js" language="javascript"></script>
</head>
<body>
</body>
<script src="/client.js" language="javascript"></script>
</html>
162 changes: 162 additions & 0 deletions lib/etc/ghcjsiClient.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
var h$GHCJSiSocket = io();
var global = window;

var h$GHCJSi = { socket: io()
, out: function(dat) {
h$GHCJSi.socket.emit('out', dat);
}
, msg: function(msgType, msgPayload) {
if(!msgPayload) msgPayload = new ArrayBuffer(0);
h$GHCJSi.socket.emit('msg', { type: msgType, payload: msgPayload });
}
, current: null
, loadedSymbols: {}
, done: function(thread) {
h$GHCJSi.msg(0);
h$GHCJSi.current = null;
}
};

h$GHCJSi.socket.on('msg', function(msg) {
h$processMessage(msg.type, msg.payload);
});

function h$processMessage(msgType, msgPayload) {
// console.log("processMessage: " + msgType);
switch(msgType) {
case 0: // load initial code/rts and init
h$loadInitialCode(h$decodeUtf8(h$wrapBuffer(msgPayload)));
h$GHCJSi.msg(0);
break;
case 1: // load code
h$loadCodeStr(h$decodeUtf8(h$wrapBuffer(msgPayload)));
h$GHCJSi.msg(0);
break;
case 2: // run action
var symb = h$decodeUtf8(h$wrapBuffer(msgPayload));
h$GHCJSi.current = h$main(h$GHCJSi.loadedSymbols[symb]);
break;
case 3: // abort
if(h$GHCJSi.current)
// fixme should probably be wrapped with Exception dict?
h$killThread( h$GHCJSi.current
, h$baseZCControlziExceptionziBasezinonTermination);
break;
default:
throw new Error("unknown message type: " + msgType);
}
}

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_stdout_fd.write = function(fd, fdo, buf, buf_offset, n, c) {
h$GHCJSi.out(buf.buf.slice(buf_offset, buf_offset+n));
c(n);
}
h$base_stderr_fd.write = h$base_stdout_fd.write;
}

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

/////////////////////////////////////////////////////////////////////////
// UTF-8 functions from shims/src/string.js and shims/src/mem.js
/////////////////////////////////////////////////////////////////////////

function h$wrapBuffer(buf, unalignedOk, offset, length) {
if(!unalignedOk && offset && offset % 8 !== 0) {
throw ("h$wrapBuffer: offset not aligned:" + offset);
}
if(!buf || !(buf instanceof ArrayBuffer))
throw "h$wrapBuffer: not an ArrayBuffer"
if(!offset) { offset = 0; }
if(!length || length < 0) { length = buf.byteLength - offset; }
return { buf: buf
, len: length
, i3: (offset%4) ? null : new Int32Array(buf, offset, length >> 2)
, u8: new Uint8Array(buf, offset, length)
, u1: (offset%2) ? null : new Uint16Array(buf, offset, length >> 1)
, f3: (offset%4) ? null : new Float32Array(buf, offset, length >> 2)
, f6: (offset%8) ? null : new Float64Array(buf, offset, length >> 3)
, dv: new DataView(buf, offset, length)
};
}

// decode a buffer with Utf8 chars to a JS string
// invalid characters are ignored
function h$decodeUtf8(v,n0,start) {
var n = n0 || v.len;
var arr = [];
var i = start || 0;
var code;
var u8 = v.u8;
while(i < n) {
var c = u8[i];
while((c & 0xC0) === 0x80) {
c = u8[++i];
}
if((c & 0x80) === 0) {
code = (c & 0x7F);
i++;
} else if((c & 0xE0) === 0xC0) {
code = ( ((c & 0x1F) << 6)
| (u8[i+1] & 0x3F)
);
i+=2;
} else if((c & 0xF0) === 0xE0) {
code = ( ((c & 0x0F) << 12)
| ((u8[i+1] & 0x3F) << 6)
| (u8[i+2] & 0x3F)
);
i+=3;
} else if ((c & 0xF8) === 0xF0) {
code = ( ((c & 0x07) << 18)
| ((u8[i+1] & 0x3F) << 12)
| ((u8[i+2] & 0x3F) << 6)
| (u8[i+3] & 0x3F)
);
i+=4;
} else if((c & 0xFC) === 0xF8) {
code = ( ((c & 0x03) << 24)
| ((u8[i+1] & 0x3F) << 18)
| ((u8[i+2] & 0x3F) << 12)
| ((u8[i+3] & 0x3F) << 6)
| (u8[i+4] & 0x3F)
);
i+=5;
} else {
code = ( ((c & 0x01) << 30)
| ((u8[i+1] & 0x3F) << 24)
| ((u8[i+2] & 0x3F) << 18)
| ((u8[i+3] & 0x3F) << 12)
| ((u8[i+4] & 0x3F) << 6)
| (u8[i+5] & 0x3F)
);
i+=6;
}
if(code > 0xFFFF) {
var offset = code - 0x10000;
arr.push(0xD800 + (offset >> 10), 0xDC00 + (offset & 0x3FF));
} else {
arr.push(code);
}
}
return h$charCodeArrayToString(arr);
}

function h$charCodeArrayToString(arr) {
if(arr.length <= 60000) {
return String.fromCharCode.apply(this, arr);
}
var r = '';
for(var i=0;i<arr.length;i+=60000) {
r += String.fromCharCode.apply(this, arr.slice(i, i+60000));
}
return r;
}
190 changes: 190 additions & 0 deletions lib/etc/irunner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
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'] || 6400;

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) { }

// 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;
}
io.on('connection', function(socket) {
if(h$GHCJSi.socket !== null) {
socket.close();
return;
}
h$GHCJSi.socket = socket;
console.log("\nbrowser connected, code runs in browser from now on");
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');
if(h$GHCJSi.socket === socket) h$GHCJSi.socket = null;
});
});
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 {
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();