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

Add Support for Stack Traces #5

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
252 changes: 241 additions & 11 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ function Client(host, port) {
}


/*****************************************************************
General Utilities
*****************************************************************/
/* Reads a string value from a raw lisp source */
function readString(raw){
return raw.slice(1, -1) //remove leading/trailing quotes
.replace(/\\(\"|\\)/g, "$1"); //resolve escapes
}



/*****************************************************************
Low-level data handling protocol
Expand Down Expand Up @@ -133,7 +143,7 @@ Client.prototype.on_swank_message = function(msg) {
if (cmd == ":return") {
this.swank_message_rex_return_handler(sexp);
} else if (cmd == ':write-string') {
this.on_handlers.print_string(sexp.children[1].source.slice(1,-1).replace(/\\\\/g, "\\"));
this.on_handlers.print_string(readString(sexp.children[1].source));
} else if (cmd == ':presentation-start') {
var presentation_id = sexp.children[1].source;
this.on_handlers.presentation_start(presentation_id);
Expand All @@ -142,7 +152,7 @@ Client.prototype.on_swank_message = function(msg) {
// console.log(presentation_id);
this.on_handlers.presentation_end(presentation_id);
} else if (cmd == ":new-package") {
this.on_handlers.new_package(sexp.children[1].source.slice(1, -1).replace(/\\\\/g, "\\"));
this.on_handlers.new_package(readString(sexp.children[1].source));
} else if (cmd == ":debug") {
this.debug_setup_handler(sexp);
} else if (cmd == ":debug-activate") {
Expand Down Expand Up @@ -279,7 +289,7 @@ Client.prototype.autocomplete = function(prefix, pkg) {
.then(function (ast) {
try {
return ast.children[0].children.map(function(competion) {
return competion.source.slice(1, -1);
return readString(competion.source);
});
} catch (e) {
return [];
Expand All @@ -298,16 +308,28 @@ Client.prototype.debug_setup_handler = function(sexp) {
var obj = {};
obj.thread = sexp.children[1].source;
obj.level = sexp.children[2].source;
obj.title = sexp.children[3].children[0].source.slice(1, -1);
obj.type = sexp.children[3].children[1].source.slice(1, -1);
obj.title = readString(sexp.children[3].children[0].source);
obj.type = readString(sexp.children[3].children[1].source);
obj.restarts = [];
sexp.children[4].children.forEach(function(restart_sexp) {
obj.restarts.push({
cmd: restart_sexp.children[0].source.slice(1, -1),
description: restart_sexp.children[1].source.slice(1, -1)
cmd: readString(restart_sexp.children[0].source),
description: readString(restart_sexp.children[1].source)
});
});
obj.stack_frames = [];
sexp.children[5].children.forEach(function(frame_sexp){
if (frame_sexp.children.length >= 3) {
restartable = frame_sexp.children[2].children[1].source.toLowerCase() != 'nil';
} else {
restartable = false;
}
obj.stack_frames.push({
frame_number: frame_sexp.children[0].source,
description: readString(frame_sexp.children[1].source),
restartable: restartable
});
});
// TODO: stack trace

this.on_handlers.debug_setup(obj);
}
Expand Down Expand Up @@ -335,6 +357,214 @@ Client.prototype.debug_escape_all = function() {
return this.rex(cmd, "COMMON-LISP-USER", "1"); //# TODO - is "1" ok here?
}

/* Abort the current debug level */
Client.prototype.debug_abort_current_level = function (level, thread) {
var cmd;
if (level == 1) {
cmd = '(SWANK:THROW-TO-TOPLEVEL)';
} else {
cmd = '(SWANK:SLDB-ABORT)';
}
return this.rex(cmd, 'COMMON-LISP-USER', thread)
}

/* Get the entire stack trace
Returns a promise of the list of objects, each containing a stack frame*/
Client.prototype.debug_get_stack_trace = function(thread) {
var cmd = '(SWANK:BACKTRACE 0 NIL)';
return this.rex(cmd, "COMMON-LISP-USER", thread).then(function(sexp) {
stack_frames = [];
sexp.children.forEach(function(frame_sexp){
if (frame_sexp.children.length >= 3) {
restartable = frame_sexp.children[2].children[1].source.toLowerCase() != 'nil';
} else {
restartable = false;
}
stack_frames.push({
frame_number: frame_sexp.children[0].source,
description: readString(frame_sexp.children[1].source),
restartable: restartable
});
});
return stack_frames;
});
}

/* Retrieve the stack frame details for the specified frame.
The existing stack frame object will be updated.
Returns a promise of the updated stack frame object. */
Client.prototype.debug_stack_frame_details = function(index, stack_frames, thread) {
frame_info = stack_frames.find(function(frame) {
return Number(frame.frame_number) === Number(index);
});
if (frame_info.hasOwnProperty('locals')) {
//frame details have already been fetched
return Promise.resolve(frame_info);
} else {
var cmd = '(SWANK:FRAME-LOCALS-AND-CATCH-TAGS ' + index + ')';
return this.rex(cmd, "COMMON-LISP-USER", thread).then(function(sexp) {
if (sexp.children[0].type.toLowerCase() == 'list'){
frame_info.locals = sexp.children[0].children.map(function(local_sexp) {
return {
name: readString(local_sexp.children[1].source),
id: local_sexp.children[3].source,
value: local_sexp.children[5].source
};
});
} else {
frame_info.locals = [];
}

if (sexp.children[1].type.toLowerCase() == 'list') {
frame_info.catch_tags = sexp.children[1].children.map(function(tag_sexp) {
return readString(tag_sexp.source);
});
} else {
frame_info.catch_tags = []
}

return frame_info;
});
}
}

/* Restart the specified frame.
May not be supported on some implementations. */
Client.prototype.debug_restart_frame = function(frame, thread) {
var cmd = '(SWANK:RESTART-FRAME ' + frame + ')';
return this.rex(cmd, "COMMON-LISP-USER", thread);
}

/* Return the given value from the specified frame.
May not be supported on some implementations.
Returns a function that provides the error message on an error*/
Client.prototype.debug_return_from_frame = function(frame, value, thread) {
var cmd = '(SWANK:SLDB-RETURN-FROM-FRAME ' + frame + ' "' + value.replace(/\\/g, "\\\\").replace(/\"/g, "\\\"") + '")';
return this.rex(cmd, "COMMON-LISP-USER", thread).then(function(errorMessage) {
return readString(errorMessage.source);
});
}

/* Gets information to display the frame's source */
Client.prototype.debug_frame_source = function(frame, thread) {
var cmd = '(SWANK:FRAME-SOURCE-LOCATION ' + frame + ')';
return this.rex(cmd, "COMMON-LISP-USER", thread).then(function(location_sexp) {
//(:location <buffer> <position> <hints>)
location = {};

if(location_sexp.children[0].source.toLowerCase() == ":error"){
location.buffer_type = "error";
location.error = readString(location_sexp.children[1].source);
} else {
var raw_buffer_sexp = location_sexp.children[1];
location.buffer_type = raw_buffer_sexp.children[0].source.slice(1).toLowerCase();
if (location.buffer_type == 'file') {
location.file = readString(raw_buffer_sexp.children[1].source);
} else if (location.buffer_type == 'buffer') {
location.buffer_name = readString(raw_buffer_sexp.children[1].source);
} else if (location.buffer_type == 'buffer-and-file') {
location.buffer_name = readString(raw_buffer_sexp.children[1].source);
location.file = readString(raw_buffer_sexp.children[2].source);
} else if (location.buffer_type == 'source-form') {
location.source_form = readString(raw_buffer_sexp.children[1].source);
} else if (location.buffer_type == 'zip') {
location.zip_file = readString(raw_buffer_sexp.children[1].source);
location.zip_entry = readString(raw_buffer_sexp.children[1].source);
}

var raw_position_sexp = location_sexp.children[2];

location.position_type = raw_position_sexp.children[0].source.slice(1).toLowerCase();
if (location.position_type == 'position') {
location.position_offset = Number(raw_position_sexp.children[1].source);
} else if (location.position_type == 'offset') {
location.position_type = 'position'
location.position_offset = Number(raw_position_sexp.children[1].source)
+ Number(raw_position_sexp.children[2].source);
} else if (location.position_type == 'line') {
location.position_line = Number(raw_position_sexp.children[1]);
if (raw_position_sexp.children.length >= 3) {
location.position_column = Number(raw_position_sexp.children[2]);
}
} else if (location.position_type == 'function-name') {
location.position_function = raw_position_sexp.children[1].source;
} else if (location.position_type == 'source_path') {
if (raw_position_sexp.children[1].type.toLowerCase == 'list') {
location.position_source_path_list = raw_position_sexp.children[1].map(function(elt) {
return elt.source;
});
} else {
location.position_source_path_list = [];
}
location.position_source_path_start = raw_position_sexp.children[2].source;
} else if (location.position_type == 'method') {
location.position_method_name = raw_position_sexp.children[1].source;
if (raw_position_sexp.children[2].type.toLowerCase == 'list') {
location.position_specializers = raw_position_sexp.children[2].map(function(elt) {
return elt.source;
});
} else {
location.position_specializers = [];
}
location.position_qualifiers = raw_position_sexp.children.slice(3).map(function(elt) {
return elt.source;
});
}
}

return location;
});
}

/* Disassembles the specified frame. */
Client.prototype.debug_disassemble_frame = function(frame, thread) {
var cmd = '(SWANK:SLDB-DISASSEMBLE ' + frame + ')';
return this.rex(cmd, "COMMON-LISP-USER", thread).then(function(sexp) {
return readString(sexp.source);
});
}

/* Evaluate the given string in the specified frame.
Returns a promise of the results of the evaluation as a string. */
Client.prototype.debug_eval_in_frame = function(frame, expr, thread) {
var cmd = '(SWANK:EVAL-STRING-IN-FRAME "' + expr.replace(/\\/g, "\\\\").replace(/\"/g, "\\\"") + '" '
+ frame + ' "COMMON-LISP-USER")';
return this.rex(cmd, "COMMON-LISP-USER", thread).then(function(result) {
return readString(result.source);
});
}

/* Steps the debugger to the next expression in the frame. */
Client.prototype.debug_step = function(frame, thread) {
var cmd = '(SWANK:SLDB-STEP ' + frame + ')';
return this.rex(cmd, "COMMON-LISP-USER", thread);
}

/* Steps the debugger to the next form in the function. */
Client.prototype.debug_next = function(frame, thread) {
var cmd = '(SWANK:SLDB-NEXT ' + frame + ')';
return this.rex(cmd, "COMMON-LISP-USER", thread);
}

/* Complete the current function then resume stepping. */
Client.prototype.debug_step_out = function(frame, thread) {
var cmd = '(SWANK:SLDB-OUT ' + frame + ')';
return this.rex(cmd, "COMMON-LISP-USER", thread);
}

/* Insert a breakpoint at the end of the frame. */
Client.prototype.debug_break_on_return = function(frame, thread) {
var cmd = '(SWANK:SLDB-BREAK-ON-RETURN ' + frame + ')';
return this.rex(cmd, "COMMON-LISP-USER", thread);
}

/* Insert a breakpoint at the specified function. */
Client.prototype.debug_break = function(function_name, thread) {
var cmd = '(SWANK:SLDB-BREAK "' + function_name.replace(/\\/g, "\\\\").replace(/\"/g, "\\\"") + '")';
return this.rex(cmd, "COMMON-LISP-USER", thread);
}


/*************
* Profiling *
*************/
Expand Down Expand Up @@ -391,14 +621,14 @@ Client.prototype.find_definitions = function(fn, pkg) {
// Extract the filename depending on the response
var filename = null;
if (ast.children[i].children[1].children[1].children[0].source.toLowerCase() == ":file") {
filename = ast.children[i].children[1].children[1].children[1].source.slice(1, -1);
filename = readString(ast.children[i].children[1].children[1].children[1].source);
} else if (ast.children[i].children[1].children[1].children[0].source.toLowerCase() == ":buffer-and-file") {
filename = ast.children[i].children[1].children[1].children[2].source.slice(1, -1);
filename = readString(ast.children[i].children[1].children[1].children[2].source);
}

// Push an appropriate reference!
refs.push({
label: ast.children[i].children[0].source.slice(1, -1),
label: readString(ast.children[i].children[0].source),
filename: filename,
index: parseInt(ast.children[i].children[1].children[2].children[1].source)
});
Expand Down