Permalink
Browse files

Major rewrite to incorporate browser interface.

  • Loading branch information...
1 parent 8882f41 commit e057515ea4ccae1b245b983fed9c7daee20d4a2c @int3 committed Feb 19, 2013
View
@@ -1,10 +1,17 @@
TESTS = $(wildcard tests/*.js)
ES6TESTS = $(wildcard tests/es6/*.js)
-INTERPRETER = interpreter.coffee
+LIB_COFFEE = $(wildcard lib/*.coffee)
+LIB_JS = $(LIB_COFFEE:.coffee=.js)
+BROWSER_COFFEE = $(wildcard browser/*.coffee)
-%.actual: %.js $(INTERPRETER)
- @echo "testing $< with $(INTERPRETER)... \c"
- @./$(INTERPRETER) $< > $@
+browser: $(BROWSER_COFFEE:.coffee=.js) browser/bundle.js
+
+test: $(TESTS:.js=.result) $(ES6TESTS:.js=.result) $(LIB_JS)
+ echo $(LIB_JS)
+
+%.actual: %.js $(LIB_JS) repl.js
+ @echo "testing $<... \c"
+ @node repl.js $< > $@
%.expected: %.js
@node $? > $@
@@ -13,11 +20,11 @@ INTERPRETER = interpreter.coffee
@diff $?
@echo "passed"
-test: $(TESTS:.js=.result)
+browser/bundle.js: $(LIB_JS)
+ browserify $^ -o browser/bundle.js --exports require
-test-es6: $(ES6TESTS:.js=.result)
+%.js: %.coffee
+ iced -c -I browserify $<
-test-all:
- @make test INTERPRETER=interpreter.coffee
- @make test INTERPRETER=cps-interpreter.coffee
- @make test-es6 INTERPRETER=cps-interpreter.coffee
+.SECONDARY: $(LIB_JS)
+.PHONY: browser
View
@@ -1,17 +1,16 @@
metajs
======
-A collection of simple metacircular AST interpreters for Javascript, written in
-Coffeescript and IcedCoffeeScript.
+A CPS-style Javascript metacircular interpreter, written in
+[IcedCoffeeScript][2].
[Esprima][1] is used for the parser.
Setup
-----
- npm install optimist
- cd node_modules
- git clone -b harmony https://github.com/ariya/esprima.git --depth 1
+ npm install
+ npm install -g browserify iced-coffee-script
Usage
-----
@@ -24,15 +23,23 @@ To execute a file:
./interpreter.coffee [filename]
+To run in the browser:
+
+ make browser
+ cd browser
+ python -m SimpleHTTPServer
+
+Then point your browser to http://localhost:8000/.
+
Testing
-------
-To test a single interpreter, do
-
- make test INTERPRETER=[interpreter filename]
+ make test
-To test all of them:
+Contributors
+------------
- make test-all
+[omphalos](github.com/omphalos)
[1]: http://esprima.org/
+[2]: http://maxtaco.github.com/coffee-script/
@@ -0,0 +1,15 @@
+var gen = function*() {
+ for (var i = 0; i < 3; i++) {
+ var inner = innerGen(i + 1);
+ yield* inner;
+ }
+};
+
+var innerGen = function*(j) {
+ for (var i = 0; i < 3; i++)
+ yield i * j;
+};
+
+for (var n of gen()) {
+ console.log(n);
+}
View
@@ -0,0 +1,16 @@
+var Y = function (F) {
+ return (function (x) {
+ return F(function (y) { return x(x)(y); });
+ })(function (x) {
+ return F(function (y) { return x(x)(y); });
+ });
+};
+
+var factorial = function (self) {
+ return function(n) {
+ return n === 0 ? 1 : n * self(n-1);
+ };
+};
+
+var result;
+console.log(result = Y(factorial)(4));
View
@@ -0,0 +1,41 @@
+<html>
+ <head>
+ <link rel='stylesheet' href='style.css'/>
+ <link rel='stylesheet' href='vendor/codemirror.css'/>
+ <script src='vendor/codemirror.js'></script>
+ <script src='vendor/javascript.js'></script>
+ <!-- TODO remove http prefixes before push -->
+ <!--
+ <script src='http://code.jquery.com/jquery-1.9.1.min.js'></script>
+ -->
+ <script src='vendor/jquery-1.9.1.min.js'></script>
+ <script src='setup.js'></script>
+ </head>
+ <body>
+ <h1>metajs: <small>visualize javascript AST execution</small></h1>
+ <form id='example-form'>
+ <label for='example-box'>Load Example:</label>
+ <select id='example-box'>
+ <option href='examples/ycomb.js'>Y Combinator</option>
+ <option href='examples/generators.js'>ES6 Generators</option>
+ </select>
+ </form>
+ <a id='about-link' href='https://github.com/int3/metajs/blob/master/README.md'>About</a>
+ <div style='clear:both'></div>
+ <textarea id='code'></textarea>
+ <div id='activeStates'><ul></ul></div>
+ <div id='currentEnv'></div>
+ <div style='clear:both'></div>
+ <div class='btn-group'>
+ <input id='run-btn' class='btn' type='button' value='Run'/>
+ <input id='step-btn' class='btn' type='button' value='Step'/>
+ <input id='auto-step-btn' class='btn' type='button' value='Auto Step'/>
+ </div>
+ <div class='modal hide' id='modal'>
+ <button id='modalClose' type='button' class='close'>&times;</button>
+ <div class='modal-body' id='modalContent'></div>
+ </div>
+ <script src='bundle.js'></script>
+ <script src='metajs.js'></script>
+ </body>
+</html>
View
@@ -0,0 +1,197 @@
+esprima = require 'esprima'
+{Util} = require './lib/util'
+interpreter = require './lib/interpreter'
+{Environment} = interpreter
+
+Message = do ->
+ messageMap = {}
+ silence = {}
+ {
+ listen: (msg, cb) -> (messageMap[msg] ?= []).push cb
+ once: (msg, cb) ->
+ @listen msg, (args) ->
+ cb args...
+ for fn, i in messageMap[msg]
+ if fn is cb
+ messageMap[msg].splice i, 1
+ break
+ squelch: (msg) -> silence[msg] = true
+ unsquelch: (msg) -> delete silence[msg]
+ send: (msg, args...) ->
+ return if msg of silence
+ for cb in messageMap[msg]
+ cb args...
+ }
+
+activeStates = []
+
+await $(document).ready defer()
+
+Continuers =
+ toFinish: (cont, v) -> cont v
+ toNextStep: (cont, v) ->
+ if cont is Continuations.bottom
+ cont v
+ else
+ Continuations.next = -> cont v
+ autoStep: (cont, v) ->
+ await setTimeout defer(), 400
+ cont v
+
+interpreter.evaluate = do (original = interpreter.evaluate) ->
+ interpreter.continuer = -> Continuers.toFinish
+ (node, env, cont, errCont) ->
+ Message.send 'interpreter:eval', node, env, cont
+ newCont = (v) ->
+ Message.send 'interpreter:continue', node, env, cont, v
+ await interpreter.continuer defer(w), v
+ Message.send 'interpreter:call-continue'
+ cont w
+ original node, env, newCont, errCont
+
+Message.listen 'interpreter:eval', (node, env, cont) ->
+ cont.id = activeStates.length
+ activeStates.push {node, env}
+
+Message.listen 'interpreter:continue', (node, env, cont, v) ->
+ activeStates.length = cont.id + 1
+ Util.last(activeStates).value = v
+ Message.send 'state:render'
+
+Message.listen 'interpreter:call-continue', ->
+ activeStates.pop()
+
+Message.listen 'interpreter:done', -> Message.send 'state:render'
+
+editor = $('.CodeMirror')[0].CodeMirror
+
+editor.on 'change', ->
+ activeStates = []
+ Message.send 'state:render'
+ Continuations.bottom()
+
+editor.on 'focus', ->
+ mark.clear() if (mark = $('#code').data('mark'))?
+
+loadFile = ->
+ url = $('#example-box option:selected')[0].getAttribute('href')
+ await $.ajax(url:url,dataType:'text').done defer(data)
+ editor.setValue data
+
+$('#example-box').change loadFile
+
+loadFile()
+
+class Continuations
+ @bottom: =>
+ Message.send 'interpreter:done'
+ @next = @top
+
+ @top: =>
+ ast = esprima.parse editor.getValue(), loc: true
+ interpreter.evaluate ast, new Environment, @bottom, (e) =>
+ console.log "Error: #{e}", @bottom()
+
+ @next: @top
+
+RenderUtils =
+ pprintNode: (node) ->
+ switch node.type
+ when 'Identifier'
+ "Identifier '#{node.name}'"
+ when 'VariableDeclarator'
+ "VariableDeclarator '#{node.id.name}'"
+ else
+ node.type
+
+ pprintValue: (value) ->
+ if value instanceof interpreter.CPSFunction or typeof value is 'function'
+ 'function'
+ else if value is null or value is undefined
+ $('<span>', text: value + '', class: 'atom-value')
+ else if typeof value is 'number'
+ $('<span>', text: value, class: 'number-value')
+ else if typeof value is 'object'
+ a = $('<a>', text: value, href: '#', class:'object')
+ a.data 'value', value
+ else
+ value + ""
+
+ htmlifyObject: (obj) ->
+ rv = $("<div>")
+ for k,v of obj
+ rv.append "#{k}: ", (@pprintValue v), "<br/>"
+ if rv.html() is ''
+ rv.append "No enumerable properties found"
+ else
+ rv
+
+ selectionFromNode: (node) ->
+ {start, end} = node.loc
+ [{line:start.line-1,ch:start.column}, {line:end.line-1,ch:end.column}]
+
+Message.listen 'state:render', ->
+ activeStatesDisplay = $('#activeStates > ul')
+ activeStatesDisplay.html('')
+ for state in activeStates
+ content = $('<span>', text: (RenderUtils.pprintNode state.node), class:'node')
+ content.data 'node', state.node
+ li = activeStatesDisplay.append $('<li>', html: content)
+ content.after " &rarr; #{RenderUtils.pprintValue state.value}" if state.value
+
+ envDisplay = $('#currentEnv')
+ envDisplay.html ''
+
+ unless activeStates.length > 0
+ editor.setSelection editor.getCursor() # deselect
+ else
+ mark.clear() if (mark = $('#code').data('mark'))?
+ latest = Util.last(activeStates)
+ $('#code').data 'mark',
+ editor.markText (RenderUtils.selectionFromNode latest.node)..., className: 'execlight'
+ for scope in latest.env.scopeChain
+ envDisplay.append $('<div>', html: ul = $('<ul>'))
+ for [k,v] in scope.items()
+ ul.append $('<li>', html: "#{k} &rarr; ").append(RenderUtils.pprintValue v)
+
+$('#activeStates').on 'mouseover', 'li > span.node', ->
+ mark = editor.markText (RenderUtils.selectionFromNode $(@).data 'node')..., className: 'mouselight'
+ $('#activeStates').one 'mouseout', 'li > span.node', -> mark.clear()
+
+$('#currentEnv, #modalContent').on 'click', 'a.object', ->
+ $('#modalContent').html(RenderUtils.htmlifyObject $(@).data 'value')
+ $('#modal').show()
+
+$(document).keydown (e) ->
+ if e.keyCode is 27 # ESC
+ $('#modal').hide()
+
+$('#modalClose').click -> $('#modal').hide()
+
+$('#run-btn').click ->
+ interpreter.continuer = Continuers.toFinish
+ Message.squelch 'state:render' # optimization
+ latestState = null
+ Message.listen 'interpreter:continue', (node, env, cont, v) ->
+ latestState = {node,env}
+ Message.once 'interpreter:done', ->
+ Message.unsquelch 'state:render'
+ activeStates.push latestState
+ Message.send 'state:render'
+ activeStates.pop()
+ Continuations.next()
+
+$('#step-btn').click ->
+ interpreter.continuer = Continuers.toNextStep
+ Continuations.next()
+
+$('#auto-step-btn').click ->
+ if $(@).attr('value') is 'Pause'
+ interpreter.continuer = Continuers.toNextStep
+ $(@).attr 'value', 'Auto Step'
+ else
+ interpreter.continuer = Continuers.autoStep
+ $(@).attr 'value', 'Pause'
+ Continuations.next()
+
+Message.listen 'interpreter:done', -> $('#auto-step-btn').attr 'value', 'Auto Step'
View
@@ -0,0 +1,2 @@
+$(document).ready ->
+ CodeMirror.fromTextArea(document.getElementById('code'), mode: 'javascript')
Oops, something went wrong.

0 comments on commit e057515

Please sign in to comment.