lively.context
lively.context provides a simple way to capture the context of a JavaScript execution and make it available inside of the JavaScript runtime. This enables inspection, modification, duplication and (stepwise) execution.What do I need this for?
There are many possible applications for it. A very simple one is to inspect the scope of a running application — at any time. Just mark any line in the source code below by clicking the on the line numbers on the left to inspect the state before that line gets executed.Captured Variables
- not run yet
// CODE
Try some more examples:
Another one is a (simple) debugger for JavaScript written in JavaScript:
Captured Variables (on exeception)
- not run yet
// CODE
Try it out yourself! Edit the source code above, add errors or a
debugger
statement and run it to see where it stops!
How does it work?
The whole package consists of:- a rewriter —
lively.ast.Rewriting
— (nowadays know as a transpiler), - an JavaScript interpreter —
lively.ast.AcornInterpreter
— and - some classes to model contexts, like scopes, frames, continuations, etc.
How can I use it?
Download the lively.context package either from GitHub or npm and use it in the browser or Node.JS.1. Loading the Library
In the browser:
<script src="./node_modules/lively.ast/lively.ast.dev-bundle.js" type="text/javascript" charset="utf-8"></script> <script src="./lib/rewriter.js" type="text/javascript" charset="utf-8"></script> <script src="./lib/exception.js" type="text/javascript" charset="utf-8"></script> <script src="./lib/interpreter.js" type="text/javascript" charset="utf-8"></script> <script src="./lib/stackReification.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript" charset="utf-8"> // either interpret or rewrite code and execute it here (see below) </script>
In Node.JS:
require('./lib/rewriter.js'); require('./lib/interpreter.js'); require('./lib/stackReification.js'); // either interpret or rewrite code and execute it here (see below)
2. Executing Code
To interpret code:
// provide source code, src in this example // if you have a function, as func(): // var src = func.toString(); var ast = lively.ast.parse(src, { sourceType: 'script' }), interpreter = new lively.ast.AcornInterpreter.Interpreter(); try { interpreter.run(ast); } catch (e) { if (e.isUnwindException) { // An UnwindException is thrown for the breakpoints (or errors), // so do what you want to handle it here } }
To run rewritten (compile) code:
// provide a function, func() in this example // if you have source code, as src: // var func = eval(src); // if src is not a function, wrap it into a function: // var func = eval('(function() {' + src + ' });'); try { var runtime = lively.ast.StackReification.run(func); if (runtime.isContinuation) { // if the execution was interruped, runtime is a continuation... // => do what you want with it here } } catch(e) { if (e.unwindException) { // might have been an UnwindException originally e = e.unwindException; // recover the UnwindException e.recreateFrames(); // now you can work with the frames of the error } }
3. Working With UnwindExceptions, Frames and Scopes
UnwindException
AnUnwindException
is thown whenever an error appears during code execution (or interpretation).
It can be detect either by checking for its type or by checking for the exception's property isUnwindException
.
Furthermore, an UnwindException
has two properties that resemble frames: top
and last
.top
is the frame where the execution stopped or the error happened.
last
is the last frame captured walking up all the frames towards where the execution started.
You can navigate through all the frames starting at top
and using its parentFrame
property. Eventually you will end up at last
.
Frames
Every frame consists of:- a scope captured in its
scope
property (see below for details), - a program counter captured in
pc
andpcStatement
naming the AST node the execution stopped, - a function captured in
func
that is represented by this frame, - a context/this object captured in
thisObject
and - a reference to the parent frame captured in
parentFrame
which can be null if it is the last frame captured.
Scope
A scope object represents the lexical scope of a frame. It consists of two properties:mapping
which is a dictionary of all the bindings — variables and functions — defined in that scope andparentScope
which is a reference to the lexical scope surrounding this scope - if there is no parent scope it's null.
A helper method
findScope(bindingName)
can be used to automatically go through all the scopes in order and find a binding. If it was found in the scope, an object structured like { val: [value], scope: [its scope] }
is returned. A second optional parameter defines what happens if a binding cannot be found. If set to true, { val: undefined, scope: [starting scope] }
is returned. Otherwise — and this is the default — a ReferenceError is thrown.Note: Since JavaScript distinguishes between lexical scope and function scope, the hierarchy of scopes and frames can be different! [1]
Authors
Marko Röder — Communications Design Group, SAP LabsRobert Krahn — Communications Design Group, SAP Labs
A special thanks to Christopher Schuster who did the initial work and wrote his master's thesis on this topic.