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: Addiitonally, there are some convenience methods and classes for the execution.

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

An UnwindException 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:

Scope

A scope object represents the lexical scope of a frame. It consists of two properties: During execution or interpretation, bindings are looked up from the most inner scope towards the outer scope. If a binding cannot be found in the current scope, the parent scope is asked and so on.
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öderCommunications Design Group, SAP Labs
Robert KrahnCommunications Design Group, SAP Labs

A special thanks to Christopher Schuster who did the initial work and wrote his master's thesis on this topic.

References

[1] http://toddmotto.com/everything-you-wanted-to-know-about-javascript-scope/