Created
November 18, 2013 05:39
-
-
Save ptomato/7523058 to your computer and use it in GitHub Desktop.
GJS debugger
This file contains hidden or 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
const GLib = imports.gi.GLib; | |
const Lang = imports.lang; | |
const System = imports.system; | |
let _breakpoints = 0; | |
function _getCurrentStack() { | |
try { | |
throw new Error(); | |
} catch (e) { | |
let stack = e.stack.split('\n'); | |
stack.pop(); // remove last newline | |
stack.shift(); // remove _getCurrentStack's own frame | |
return stack; | |
} | |
return []; // should never be reached | |
} | |
function _interpretStackFrame(frame) { | |
let [location, fileLine] = frame.split('@'); | |
let [file, line] = fileLine.split(':'); | |
if (location === '') | |
location = 'anonymous function'; | |
return [location, file, line]; | |
} | |
function _resolveNameInScope(ident, scope) { | |
let attrChain = ident.split('.'); | |
let attrName = attrChain.pop(); | |
let parentObj = attrChain.reduce(function (prev, curr) { | |
return prev[curr]; | |
}, scope); | |
return [parentObj, attrName]; | |
} | |
// inspired by Python's functools.wraps | |
// A decorator function has the following properties: | |
// - isDecorator: true | |
// - wraps: the inner function (may be another decorator) | |
// - decoratedFunc: the innermost function, inside of all decorators | |
// Additionally, a decorator function gives the inner function it wraps a | |
// "wrapper" property, pointing to the decorator | |
function _makeDecorator(innerFunc, decoratorFunc, otherProperties) { | |
decoratorFunc.isDecorator = true; | |
if (innerFunc.isDecorator) { | |
innerFunc.wrapper = decoratorFunc; | |
decoratorFunc.decoratedFunc = innerFunc.decoratedFunc; | |
} else { | |
decoratorFunc.decoratedFunc = innerFunc; | |
} | |
decoratorFunc.wraps = innerFunc; | |
// decoratorFunc.name = innerFunc.name; // "name" is read-only :-( | |
Lang.copyProperties(otherProperties, decoratorFunc); | |
return decoratorFunc; | |
} | |
// Returns whether @func, or any functions that @func decorates if it is a | |
// decorator, has a "tag" property equal to @tag. | |
function _isDecoratedWithTag(func, tag) { | |
for (; func.isDecorator; func = func.wraps) { | |
if (func.tag === tag) | |
return true; | |
} | |
return false; | |
} | |
function _prettyPrint(obj) { | |
let retval = obj.toString(); | |
if (retval.length > 15) | |
retval = retval.substr(0, 12) + '...'; | |
if (obj.length !== undefined) | |
retval += ' (length ' + obj.length + ')'; | |
return retval; | |
} | |
// PUBLIC API | |
/** | |
* DEBUG: | |
* | |
* Defaults to true. Set to false at the beginning of your program to disable | |
* all debugging commands. | |
*/ | |
let DEBUG = true; | |
/** | |
* traceDecorator: | |
* @func: The function to decorate | |
* @funcName: (optional) a string identifying @func. | |
* | |
* Decorates a function so that it prints out a message when the function is | |
* called (mentioning its arguments and its recursion depth) and when it returns | |
* (mentioning its return value.) | |
* | |
* If @funcName is not given, this will attempt to find @func's name through the | |
* "name" property, and if that fails, it will be called 'anonymous function'. | |
* | |
* The trace() function will attach this decorator to an existing function. | |
*/ | |
function traceDecorator(func, funcName) { | |
if (!DEBUG || _isDecoratedWithTag(func, 'trace')) | |
return func; | |
if (funcName === undefined) | |
funcName = func.name || 'anonymous function'; | |
return _makeDecorator(func, function () { | |
let depth = ++arguments.callee.recursionDepth; | |
let traceString = (funcName + | |
'(' + Array.map(arguments, _prettyPrint).join(', ') + ')'); | |
if (depth > 1) | |
traceString += ' [' + depth + ']'; | |
printerr('Entering', traceString); | |
let retval = func.apply(this, arguments); | |
printerr('Leaving', traceString, '->', _prettyPrint(retval)); | |
arguments.callee.recursionDepth--; | |
return retval; | |
}, { | |
recursionDepth: 0, | |
tag: 'trace' | |
}); | |
} | |
/** | |
* timeDecorator: | |
* @func: The function to decorate | |
* @funcName: (optional) a string identifying @func. | |
* | |
* Decorates a function so that it prints out a message when the function | |
* returns, saying how long the function took to execute. | |
* | |
* If @funcName is not given, this will attempt to find @func's name through the | |
* "name" property, and if that fails, it will be called 'anonymous function'. | |
* | |
* The time() function will attach this decorator to an existing function. | |
*/ | |
function timeDecorator(func, funcName) { | |
if (!DEBUG || _isDecoratedWithTag(func, 'time')) | |
return func; | |
if (funcName === undefined) | |
funcName = func.name || 'anonymous function'; | |
return _makeDecorator(func, function () { | |
let timer = GLib.get_monotonic_time(); | |
let retval = func.apply(this, arguments); | |
let time = GLib.get_monotonic_time() - timer; | |
printerr(funcName, 'executed in', time, 'microseconds'); | |
return retval; | |
}, { | |
tag: 'time' | |
}); | |
} | |
/** | |
* breakBeforeDecorator: | |
* @func: The function to decorate | |
* | |
* Decorates a function so that it sets a System.breakpoint() before the | |
* function executes. The breakpoint will abort the program unless run under a | |
* debugger such as GDB, so be careful. | |
* | |
* The breakBefore() function will attach this decorator to an existing | |
* function. | |
*/ | |
function breakBeforeDecorator(func) { | |
if (!DEBUG || _isDecoratedWithTag(func, 'breakBefore')) | |
return func; | |
return _makeDecorator(func, function () { | |
printerr('Breakpoint', arguments.callee.breakpointNum, 'reached'); | |
System.breakpoint(); | |
return func.apply(this, arguments); | |
}, { | |
breakpointNum: ++_breakpoints, | |
tag: 'breakBefore' | |
}); | |
} | |
/** | |
* trace: | |
* @ident: a string consisting of dotted identifiers that resolves within @scope | |
* @scope: (optional) an object (the global object if not given) | |
* | |
* Put a trace (see traceDecorator()) on a function. Examples: | |
* | |
* let myObj = new MyClass(); | |
* Debug.trace('myObj.myMethod'); | |
* Debug.trace('print'); | |
* let myArray = []; | |
* Debug.trace('push', myArray); | |
* | |
* Returns: the decorator function | |
*/ | |
function trace(ident, scope) { | |
scope = (scope === undefined)? window : scope; | |
let [parentObj, attrName] = _resolveNameInScope(ident, scope); | |
parentObj[attrName] = traceDecorator(parentObj[attrName], attrName); | |
return parentObj[attrName]; | |
} | |
/** | |
* time: | |
* @ident: a string consisting of dotted identifiers that resolves within @scope | |
* @scope: (optional) an object (the global object if not given) | |
* | |
* Time how long a function takes to execute (see timeDecorator().) See trace() | |
* for examples of the use of @ident and @scope. | |
* | |
* Returns: the decorator function | |
*/ | |
function time(ident, scope) { | |
scope = (scope === undefined)? window : scope; | |
let [parentObj, attrName] = _resolveNameInScope(ident, scope); | |
parentObj[attrName] = timeDecorator(parentObj[attrName], attrName); | |
return parentObj[attrName]; | |
} | |
/** | |
* breakBefore: | |
* @ident: a string consisting of dotted identifiers that resolves within @scope | |
* @scope: (optional) an object (the global object if not given) | |
* | |
* Set a breakpoint at the beginning of a function (see breakBeforeDecorator().) | |
* See trace() for examples of the use of @ident and @scope. | |
* | |
* Returns: the decorator function | |
*/ | |
function breakBefore(ident, scope) { | |
scope = (scope === undefined)? window : scope; | |
let [parentObj, attrName] = _resolveNameInScope(ident, scope); | |
parentObj[attrName] = breakBeforeDecorator(parentObj[attrName]); | |
printerr('Breakpoint', parentObj[attrName].breakpointNum, 'set on', ident); | |
return parentObj[attrName]; | |
} |
This file contains hidden or 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
const Lang = imports.lang; | |
const Debug = imports.debug; | |
const MyClass = new Lang.Class({ | |
Name: 'MyClass', | |
_initialValue: 5, | |
myMethod: function (foo, bar) { | |
let buffer = []; | |
Debug.time('map', buffer); | |
for (let count = 0; count < foo; count++) { | |
buffer.push(this._initialValue); | |
} | |
return buffer.map(function (item) { | |
return item * bar; | |
}); | |
}, | |
myRecursiveMethod: function (foo) { | |
if (foo < 1) | |
return 1; | |
return foo * this.myRecursiveMethod(foo - 1); | |
} | |
}); | |
let obj = new MyClass(); | |
// Debug.DEBUG = false; // uncomment to turn off debugging | |
Debug.trace('obj.myRecursiveMethod'); | |
Debug.trace('obj.myMethod'); | |
Debug.time('obj.myMethod'); | |
// Debug.breakBefore('print'); // run under gdb or lldb to see breakpoints | |
print(obj.myMethod(17355, 57291)[0]); | |
print(obj.myRecursiveMethod(5)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment