-
-
Save bmeck/7f5bf6b0672e9d9aa1a4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var EventEmitter = require('events').EventEmitter; | |
var readline = require('readline'); | |
var tty = require('tty'); | |
function DebuggerConnection(conn, toolname) { | |
EventEmitter.call(this); | |
this.seq = 1; | |
this.conn = conn; | |
this.toolname = toolname || "node"; | |
this.outstanding = {}; | |
this.state = DebuggerConnection.READING_HEADER; | |
this._parser_tmp_buffer = null; | |
this._parser_tmp_prev_header = null; | |
this._response = null; | |
this.on('message', this.onmessage); | |
this.conn.on('readable', this.onreadable.bind(this)); | |
return this; | |
} | |
require('util').inherits(DebuggerConnection, EventEmitter); | |
DebuggerConnection.ERROR = 0; | |
DebuggerConnection.READING_HEADER = 2; | |
DebuggerConnection.READING_CONTENT = 4; | |
DebuggerConnection.prototype.onmessage = function (msg) { | |
if (msg.body) { | |
var body = msg.body; | |
var fn = this.outstanding[body.request_seq] | |
if (fn) { | |
if (body.success) { | |
fn(null, msg); | |
} | |
else { | |
fn(new Error(body.message, msg)); | |
} | |
} | |
} | |
} | |
DebuggerConnection.prototype.onreadable = function () { | |
var unread = this.conn.read(); | |
if (unread) this._read(unread); | |
} | |
DebuggerConnection.prototype._read = function (buff) { | |
var state = this.state; | |
if (state === DebuggerConnection.ERROR) { | |
self.conn.removeListener('readable', this.onreadable); | |
return; | |
} | |
var remaining_buff = this._parser_tmp_buffer ? | |
Buffer.concat([this._parser_tmp_buffer, buff]) : | |
buff; | |
while (true) { | |
if (this.state === DebuggerConnection.READING_HEADER) { | |
var line_and_buff = this._readLine(remaining_buff); | |
if (!line_and_buff) { | |
break; | |
} | |
else { | |
var line = String(line_and_buff.line); | |
remaining_buff = line_and_buff.remaining; | |
if (line === '') { | |
this.state = DebuggerConnection.READING_CONTENT; | |
} | |
else if (/^\s/.test(line)) { | |
this._response[this._parser_tmp_prev_header] += line.replace(/^\s+/,' ').replace(/\s+$/, ''); | |
} | |
else { | |
var index = line.indexOf(':'); | |
var name = line.substr(0, index).trim().toLowerCase(); | |
var value = line.substr(index+1).trim(); | |
this.setupResponse(); | |
this._response.headers[name] = value; | |
} | |
} | |
} | |
else if (this.state === DebuggerConnection.READING_CONTENT) { | |
var content_and_buff = this._readNumberOfBytes(remaining_buff, +this._response.headers['content-length']); | |
if (!content_and_buff) { | |
break; | |
} | |
else { | |
this.state = DebuggerConnection.READING_HEADER; | |
var content = content_and_buff.content; | |
remaining_buff = content_and_buff.remaining; | |
this.setupResponse(); | |
if (content.length > 0) { | |
try { | |
var msg = JSON.parse(String(content)); | |
} | |
catch (e) { | |
this.emit('error', e); | |
break; | |
} | |
this._response.body = msg; | |
} | |
this.emit('message', this._response); | |
this._response = null; | |
} | |
} | |
} | |
this._parser_tmp_buffer = remaining_buff; | |
} | |
DebuggerConnection.prototype.setupResponse = function () { | |
this._response = this._response || {headers:{},body:null}; | |
} | |
DebuggerConnection.prototype._readLine = function (buff) { | |
for (var i = 0; i < buff.length; i++) { | |
if (buff[i] === 0x0A) { | |
var end = i; | |
if (end > 0 && buff[end-1] === 0x0D) end--; | |
return { | |
line: buff.slice(0, end), | |
remaining: buff.slice(i+1, buff.length) | |
} | |
} | |
} | |
return null; | |
} | |
DebuggerConnection.prototype._readNumberOfBytes = function (buff, length) { | |
if (buff.length >= length) { | |
return { | |
content: buff.slice(0, length), | |
remaining: buff.slice(length, buff.length) | |
} | |
} | |
return null; | |
} | |
DebuggerConnection.prototype.send = function (headers, msg, cb) { | |
var conn = this.conn; | |
if (headers) for (var k in headers) { | |
var v = headers[k]; | |
if (Array.isArray(v)) { | |
v.forEach(function (v) { | |
conn.write(k + ': ' + v + '\r\n'); | |
}); | |
} | |
else { | |
conn.write(k + ': ' + v + '\r\n'); | |
} | |
} | |
var obj = {}; | |
for (var k in msg) obj[k] = msg[k]; | |
obj.seq = this.seq++; | |
obj.type = 'request'; | |
this.outstanding[obj.seq] = cb; | |
var str = JSON.stringify(obj); | |
conn.write('Tool: '+this.toolname.replace(/[\r\n\v\f\x85\u2028\u2029]/g,'')+'\r\n'); | |
conn.write('Content-Length: '+str.length+'\r\n\r\n'); | |
conn.write(str); | |
} | |
function DebuggerCommandLine(debugger_conn, options) { | |
debugger_conn.on('message', function (msg) { | |
//console.log(msg) | |
}) | |
this.debuggerConn = debugger_conn; | |
this.readlineInterface = readline.createInterface({ | |
input: options.input, | |
output: options.output, | |
completer: this.autoComplete.bind(this), | |
terminal: options.input instanceof tty.ReadStream | |
}); | |
this.readlineInterface.prompt(false); | |
var self = this; | |
this._delegate = null; | |
this.readlineInterface.on('line', function (line) { | |
if (self._delegate) { | |
self._delegate(line); | |
return; | |
} | |
self.parse(line, function (err) { | |
if (err) { | |
self.readlineInterface.output.write(err.message+'\n'); | |
} | |
self.readlineInterface.prompt(false); | |
}); | |
}); | |
return this; | |
} | |
DebuggerCommandLine.prototype.commands = { | |
backtrace: { | |
completer: function (preCursor, callback) { | |
if (!preCursor) { | |
callback(null, [['full'], preCursor]); | |
return; | |
} | |
var options = []; | |
if (preCursor.length <= 'full'.length && 'full'.slice(0, preCursor.length) == preCursor) { | |
options.push('full') | |
} | |
callback(null, [options, preCursor]); | |
}, | |
handler: function (dbg_cli, line, callback) { | |
if (line) line = line.trim(); | |
var args = {}; | |
if (line === 'full') { | |
args.fromFrame = 0; | |
args.toFrame = 1024 * 1024; | |
} | |
var msg = {command:'backtrace', arguments: args}; | |
dbg_cli.debuggerConn.send(null, msg, function (err, msg) { | |
if (msg.body.body.frames) msg.body.body.frames.forEach(function (frame) { | |
dbg_cli.readlineInterface.output.write(frame.text+'\n'); | |
}); | |
else { | |
var description = 'no JS frames were found. JS engine is '; | |
if (!msg.running) { | |
// could be due to GC | |
// could be due to breakpoint | |
description += 'paused\n' | |
} | |
else { | |
// does not mean JS code is currently running | |
description += 'running\n' | |
} | |
dbg_cli.readlineInterface.output.write(description); | |
} | |
callback(null); | |
}); | |
} | |
}, | |
"break": { | |
handler: function (dbg_cli, line, callback) { | |
var msg = {command:'suspend'}; | |
dbg_cli.debuggerConn.send(null, msg, function (err, msg) { | |
callback(null); | |
}); | |
} | |
}, | |
"continue": { | |
handler: function (dbg_cli, line, callback) { | |
var msg = {command:'continue'}; | |
dbg_cli.debuggerConn.send(null, msg, function (err, msg) { | |
callback(null); | |
}); | |
} | |
}, | |
"finish": { | |
handler: function (dbg_cli, line, callback) { | |
var msg = {command:'continue',arguments:{stepaction:'out',stepcount:1}}; | |
dbg_cli.debuggerConn.send(null, msg, function (err, msg) { | |
callback(null); | |
}); | |
} | |
}, | |
"step": { | |
handler: function (dbg_cli, line, callback) { | |
var msg = {command:'continue',arguments:{stepaction:'min',stepcount:1}}; | |
dbg_cli.debuggerConn.send(null, msg, function (err, msg) { | |
callback(null); | |
}); | |
} | |
}, | |
"help": { | |
handler: function (debug_cli, line, callback) { | |
debug_cli.readlineInterface.output.write('COMMANDS: ' + Object.keys(debug_cli.commands).sort().join(', ') + '\n'); | |
} | |
}, | |
"next": { | |
handler: function (dbg_cli, line, callback) { | |
var msg = {command:'continue',arguments:{stepaction:'next',stepcount:1}}; | |
dbg_cli.debuggerConn.send(null, msg, function (err, msg) { | |
callback(null); | |
}); | |
} | |
}, | |
"print": { | |
handler: function (dbg_cli, line, callback) { | |
var msg = {command:'evaluate',arguments:{expression:line}}; | |
dbg_cli.debuggerConn.send(null, msg, function (err, msg) { | |
if (err) { | |
callback(err); | |
return; | |
} | |
dbg_cli.showValue(msg.body.body); | |
callback(null); | |
}); | |
} | |
}, | |
"where": { | |
handler: function (dbg_cli, line, callback) { | |
var msg = {command:'backtrace',arguments:{fromFrame:0,toFrame:1}}; | |
dbg_cli.debuggerConn.send(null, msg, function (err, msg) { | |
if (msg.body.body.frames) msg.body.body.frames.forEach(function (frame) { | |
dbg_cli.readlineInterface.output.write(frame.text+'\n'); | |
}); | |
else { | |
var description = 'no JS frames were found. JS engine is '; | |
if (!msg.running) { | |
// could be due to GC | |
// could be due to breakpoint | |
description += 'paused\n' | |
} | |
else { | |
// does not mean JS code is currently running | |
description += 'running\n' | |
} | |
dbg_cli.readlineInterface.output.write(description); | |
} | |
callback(null); | |
}); | |
} | |
}, | |
'define': { | |
handler: function (dbg_cli, line, callback) { | |
line = line || ''; | |
var name = line.trim(); | |
if (!name) { | |
callback(new Error('Command must have a name')); | |
} | |
if (/\s/.test(name)) { | |
callback(new Error('Commands cannot contain whitespace')); | |
return; | |
} | |
if (dbg_cli.commands[name]) { | |
callback(new Error('Command already exists')); | |
return; | |
} | |
var lines = []; | |
dbg_cli.delegate(function _(line) { | |
line = line || ''; | |
line = line.trim(); | |
if (line !== 'end') { | |
lines.push(line); | |
} | |
else { | |
dbg_cli.undelegate(_); | |
dbg_cli.commands[name] = { | |
handler: function (fn_dbg_cli, fn_line, fn_callback) { | |
var i = 0; | |
var done = false; | |
function next() { | |
if (done) { | |
return; | |
} | |
if (i >= lines.length) { | |
done = true; | |
fn_callback(null); | |
return; | |
} | |
var torun = lines[i]; | |
i++; | |
dbg_cli.parse(torun, function (err) { | |
if (err) { | |
done = true; | |
fn_callback(err); | |
return; | |
} | |
next(); | |
}); | |
} | |
next(); | |
} | |
}; | |
} | |
dbg_cli.readlineInterface.prompt(false); | |
}); | |
dbg_cli.readlineInterface.prompt(false); | |
} | |
} | |
}; | |
;(function(commands) { | |
commands.bt = commands.backtrace; | |
commands.c = commands["continue"]; | |
commands.n = commands.next; | |
commands.p = commands.print; | |
commands.s = commands.step; | |
commands.br = commands["break"]; | |
})(DebuggerCommandLine.prototype.commands); | |
// for when something needs to take over the readline | |
DebuggerCommandLine.prototype.delegate = function (handler) { | |
if (this._delegate) { | |
throw new Error('Already delegating'); | |
} | |
this._delegate = handler; | |
} | |
DebuggerCommandLine.prototype.undelegate = function (handler) { | |
if (this._delegate === handler) { | |
this._delegate = null; | |
} | |
} | |
DebuggerCommandLine.prototype.autoComplete = function (preCursor, callback) { | |
var command_and_remaining = preCursor.match(/^(\S*)(?:\s+([\s\S]*))?$/); | |
if (!command_and_remaining) { | |
callback(null, [[], preCursor]); | |
return; | |
} | |
var command = command_and_remaining[1]; | |
var remaining = command_and_remaining[2]; | |
// still typing a command? | |
if (command.length === preCursor.length) { | |
callback(null, [Object.keys(this.commands).filter(function (definedCommand) { | |
return command === definedCommand.slice(0, command.length); | |
}), preCursor]); | |
return; | |
} | |
if (!this.commands[command]) { | |
callback(null, [[], preCursor]); | |
return; | |
} | |
var completer = this.commands[command].completer; | |
if (completer) completer(remaining, callback); | |
else callback(null, [[], preCursor]); | |
} | |
DebuggerCommandLine.prototype.parse = function (line, callback) { | |
var command_and_remaining = line.match(/^(\S*)(?:\s+([\s\S]+))?$/); | |
if (!command_and_remaining) { | |
this.readlineInterface.output.write('Unable to parse command\n'); | |
callback(null); | |
return; | |
} | |
var command = command_and_remaining[1]; | |
var remaining = command_and_remaining[2]; | |
if (!this.commands[command]) { | |
this.readlineInterface.output.write('Unable to find command '+JSON.stringify(command)+'\n'); | |
callback(null); | |
return; | |
} | |
this.commands[command].handler(this, remaining, callback); | |
} | |
DebuggerCommandLine.prototype.showValue = function (value) { | |
var output = this.readlineInterface.output; | |
if (value.type === 'object'){ | |
output.write('[' + value.type + ' ' + value.className + ']'+(value.name || value.inferredName || '')+' {\n'); | |
output.write(value.properties.map(function (prop) { | |
return ' ' + prop.name | |
}).join('\n')+'\n'); | |
output.write('}\n'); | |
} | |
else if (value.type === 'function') { | |
output.write(value.source+'\n'); | |
} | |
else { | |
output.write(JSON.stringify(value.value)+'\n'); | |
} | |
} | |
var dbg_conn = new DebuggerConnection(require('net').connect(5858)); | |
new DebuggerCommandLine(dbg_conn, {input: process.stdin, output: process.stdout}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment