From b1eaedb315886c100966920ea38ce2408dfa876b Mon Sep 17 00:00:00 2001 From: yangit Date: Tue, 7 Oct 2014 18:04:50 +0800 Subject: [PATCH 1/7] Editor does not know about menu Menu API simplified Renamed navigator => menuRenderer Menu does not know about menuRenderer --- puddle-editor/source/lib/editor.js | 30 +- puddle-editor/source/lib/main.js | 7 + puddle-editor/source/lib/menu.js | 260 ------------------ puddle-editor/source/lib/menu/menu.js | 153 +++++++++++ puddle-editor/source/lib/menu/menuRenderer.js | 235 ++++++++++++++++ puddle-editor/source/lib/navigate.js | 234 ---------------- 6 files changed, 420 insertions(+), 499 deletions(-) delete mode 100644 puddle-editor/source/lib/menu.js create mode 100644 puddle-editor/source/lib/menu/menu.js create mode 100644 puddle-editor/source/lib/menu/menuRenderer.js delete mode 100644 puddle-editor/source/lib/navigate.js diff --git a/puddle-editor/source/lib/editor.js b/puddle-editor/source/lib/editor.js index d7a013e..2b0caa4 100644 --- a/puddle-editor/source/lib/editor.js +++ b/puddle-editor/source/lib/editor.js @@ -5,9 +5,10 @@ var $ = require('jquery'); var io = require('socket.io-client'); var syntax = require('./puddle-syntax-0.1.2'); var syntaxNew = require('puddle-syntax'); +var EventEmitter = require('events').EventEmitter; +var emitter = new EventEmitter(); var assert = require('./assert'); var view = require('./view'); -var menu = global.menu = require('./menu'); var corpus = require('./corpus'); var debug = require('debug')('puddle:editor'); var trace = require('./trace')(debug); @@ -75,7 +76,7 @@ var insertLine = function (line, done, fail) { }; // incoming create -var onInsertLine = function (id,line) { +var onInsertLine = function (id, line) { ids.push(id); var churchTerm = syntaxNew.compiler.load(line); var root = syntax.tree.load(churchTerm); @@ -223,6 +224,7 @@ var initCursor = function () { var moveCursorLine = function (delta) { trace('moveCursorLine', arguments); + if (lineChanged) { commitLine(); } @@ -250,7 +252,9 @@ var moveCursor = function (direction) { } }; }; -shared.actions = { + + +var pureActions = { commitLine: commitLine, revertLine: function () { trace('revertLine', arguments); @@ -324,6 +328,13 @@ shared.actions = { widenSelection: moveCursor('U') }; +var sharedActions = _.each(pureActions, function (value, key, hash) { + hash[key] = function () { + value.apply(this,_.toArray(arguments)); + emitter.emit('update'); + }; +}); + module.exports = { main: function () { loadAllLines(); @@ -343,7 +354,6 @@ module.exports = { } }); initCursor(); - menu.init(shared); }, getTerms: function () { return ids.map(function (id) { @@ -355,6 +365,16 @@ module.exports = { remove: onRemoveLine, update: onUpdateLine }, - shared: shared + getActions: function () { + return sharedActions; + }, + on: _.bind(emitter.on, emitter), + getCursor: function () { + return shared.cursor; + }, + //TODO this function has to be replaced by methods of forest. + getCorpus: function () { + return corpus; + } }; diff --git a/puddle-editor/source/lib/main.js b/puddle-editor/source/lib/main.js index 0445c9a..814e2da 100644 --- a/puddle-editor/source/lib/main.js +++ b/puddle-editor/source/lib/main.js @@ -2,6 +2,8 @@ var corpus = global.corpus = require('./corpus'); var editor = global.editor = require('./editor'); +var Menu = require('./menu/menu'); +var menuRenderer = require('./menu/menuRenderer'); var serverSyntax = require('./server-syntax'); var _ = require('lodash'); var puddleSocket = global.puddleSocket; @@ -9,6 +11,7 @@ var puddleSocket = global.puddleSocket; var debug = require('debug')('puddle:editor:main'); var trace = require('./trace')(debug); +//TODO refactor editor to expose reset method var initEditor = _.once(function () { trace('Init editor'); var newData = []; @@ -20,6 +23,9 @@ var initEditor = _.once(function () { corpus.loadAll(newData); editor.main(); + //TODO refactor for better API + var menu = Menu(editor); + menuRenderer(menu, editor); }); var reinitEditor = function () { @@ -45,3 +51,4 @@ puddleSocket.on('update', function (id, obj) { + diff --git a/puddle-editor/source/lib/menu.js b/puddle-editor/source/lib/menu.js deleted file mode 100644 index b941597..0000000 --- a/puddle-editor/source/lib/menu.js +++ /dev/null @@ -1,260 +0,0 @@ -'use strict'; - -var _ = require('lodash'); -var $ = require('jquery'); -var assert = require('assert'); -var syntax = require('./puddle-syntax-0.1.2'); -var renderTerm = require('./render-term.js'); -var navigate = require('./navigate'); -var corpus = require('./corpus'); -var debug = require('debug')('puddle:editor:menu'); -var trace = require('./trace')(debug); -var actions; // set by init -var editorShared; // set by init - -var symbols = syntax.compiler.symbols; - -var HOLE = symbols.HOLE; -var TOP = symbols.TOP; -var BOT = symbols.BOT; -var VAR = symbols.VAR; -var LAMBDA = symbols.LAMBDA; -var LETREC = symbols.LETREC; -var APP = symbols.APP; -var JOIN = symbols.JOIN; -var RAND = symbols.RAND; -var QUOTE = symbols.QUOTE; -var EQUAL = symbols.EQUAL; -var LESS = symbols.LESS; -var NLESS = symbols.NLESS; -var ASSERT = symbols.ASSERT; -var DEFINE = symbols.DEFINE; -var CURSOR = symbols.CURSOR; -var DASH = VAR('—'); - -var generic; -var genericBare; -var render = function (term) { - trace('render'); - return $('
').html(renderTerm(term));
-};
-
-var action = function (cb) {
-    trace('init action');
-    return function () {
-        trace('action');
-        cb();
-        build();
-    };
-};
-
-var searchGlobals = function () {
-    trace('searchGlobals');
-    var names = corpus.findAllNames();
-    var accept = function (name) {
-        assert(name !== undefined, 'name not found: ' + name);
-        actions.replaceBelow(VAR(name));
-        build();
-    };
-    var cancel = build;
-    var render = function (name) {
-        return renderTerm(VAR(name));
-    };
-    navigate.search(names, accept, cancel, render);
-};
-
-var chooseDefine = function () {
-    trace('chooseDefine');
-    var accept = function (name) {
-        actions.insertDefine(name, build);
-    };
-    var cancel = build;
-    navigate.choose(corpus.canDefine, accept, cancel);
-};
-
-
-var off = function () {
-    trace('off');
-    navigate.off();
-    _.each(generic, function (g) {
-        navigate.on.apply(null, g);
-    });
-};
-
-var on = function (name, term, subsForDash) {
-    trace('on', arguments);
-    var callback = function () {
-        actions.replaceBelow(term, subsForDash);
-        build();
-    };
-    var description = render(term);
-    navigate.on(name, callback, description);
-};
-
-var build = function () {
-    trace('build');
-    var term = editorShared.cursor.below[0];
-    var name = term.name;
-    var varName = syntax.tree.getFresh(term);
-    var fresh = VAR(varName);
-
-    off();
-    if (name === 'ASSERT') {
-        navigate.on('X', actions.removeLine, 'delete line');
-    } else if (name === 'DEFINE') {
-        if (!corpus.hasOccurrences(term.below[0].varName)) {
-            navigate.on('X', actions.removeLine, 'delete line');
-        }
-    } else if (name === 'HOLE') {
-        on('X', HOLE); // TODO define context-specific deletions
-        on('T', TOP);
-        on('_', BOT);
-        on('\\', LAMBDA(fresh, CURSOR(HOLE)));
-        on('W', LETREC(fresh, CURSOR(HOLE), HOLE));
-        on('L', LETREC(fresh, HOLE, CURSOR(HOLE)));
-        on('space', APP(HOLE, CURSOR(HOLE)));
-        on('(', APP(CURSOR(HOLE), HOLE));
-        on('|', JOIN(CURSOR(HOLE), HOLE));
-        on('+', RAND(CURSOR(HOLE), HOLE));
-        on('{', QUOTE(CURSOR(HOLE)));
-        on('=', EQUAL(CURSOR(HOLE), HOLE));
-        on('<', LESS(CURSOR(HOLE), HOLE));
-        on('>', NLESS(CURSOR(HOLE), HOLE));
-
-        // TODO filter globals and locals by future validity
-        navigate.on('/', searchGlobals, render(VAR('global.variable')));
-        var locals = syntax.tree.getLocals(term);
-        locals.forEach(function (varName) {
-            on(varName, VAR(varName));
-            // TODO deal with >26 variables
-        });
-
-    } else {
-        var dumped = syntax.tree.dump(term);
-
-        // TODO define context-specific deletions
-        on('X', HOLE);
-
-        on('\\', LAMBDA(fresh, CURSOR(DASH)), dumped);
-        on('W', LETREC(fresh, CURSOR(HOLE), DASH), dumped);
-        on('L', LETREC(fresh, DASH, CURSOR(HOLE)), dumped);
-        on('space', APP(DASH, CURSOR(HOLE)), dumped);
-        on('(', APP(CURSOR(HOLE), DASH), dumped);
-        on('|', JOIN(DASH, CURSOR(HOLE)), dumped);
-        on('+', RAND(DASH, CURSOR(HOLE)), dumped);
-        on('{', QUOTE(CURSOR(DASH)), dumped);
-        on('=', EQUAL(DASH, CURSOR(HOLE)), dumped);
-        on('<', LESS(DASH, CURSOR(HOLE)), dumped);
-        on('>', NLESS(DASH, CURSOR(HOLE)), dumped);
-    }
-};
-var initGeneric = function () {
-    genericBare = [
-        ['enter', actions.commitLine, 'commit line'],
-        ['tab', actions.revertLine, 'revert line'],
-        ['up', actions.moveUp, 'move up'],
-        ['down', actions.moveDown, 'move down'],
-        ['left', actions.moveLeft, 'move left'],
-        ['right', actions.moveRight, 'move right'],
-        ['shift+left', actions.widenSelection, 'widen selection'],
-        ['shift+right', actions.widenSelection, 'widen selection']
-    ];
-    generic = genericBare.map(function (g) {
-        g[1] = action(g[1]);
-        return g;
-    });
-    genericBare.push(
-        ['A', _.bind(actions.insertAssert, build),
-            ASSERT(CURSOR(HOLE))]);
-    genericBare.push(
-        ['D', chooseDefine, DEFINE(CURSOR(VAR('...')), HOLE)]);
-    generic.push(
-        ['A', _.bind(actions.insertAssert, build),
-            render(ASSERT(CURSOR(HOLE)))]);
-    generic.push(
-        ['D', chooseDefine, render(DEFINE(CURSOR(VAR('...')), HOLE))]);
-};
-module.exports = {
-    init: function (shared) {
-        trace('Menu init');
-        actions = shared.actions;
-        editorShared = shared;
-        initGeneric();
-        build();
-        navigate.hookEvents();
-    },
-    build: build,
-    getActions: function () {
-
-        trace('getActions');
-        var actionsArray = [];
-        var on = function (name, term, subsForDash) {
-            actionsArray.push([
-                name,
-                function () {
-                    actions.replaceBelow(term, subsForDash);
-                },
-                term
-            ]);
-        };
-        var term = editorShared.cursor.below[0];
-        var name = term.name;
-        var varName = syntax.tree.getFresh(term);
-        var fresh = VAR(varName);
-
-        if (name === 'ASSERT') {
-            actionsArray.push(['X', actions.removeLine, 'delete line']);
-        } else if (name === 'DEFINE') {
-            if (!corpus.hasOccurrences(term.below[0].varName)) {
-                actionsArray.push(['X', actions.removeLine, 'delete line']);
-            }
-        } else if (name === 'HOLE') {
-            on('X', HOLE); // TODO define context-specific deletions
-            on('T', TOP);
-            on('_', BOT);
-            on('\\', LAMBDA(fresh, CURSOR(HOLE)));
-            on('W', LETREC(fresh, CURSOR(HOLE), HOLE));
-            on('L', LETREC(fresh, HOLE, CURSOR(HOLE)));
-            on('space', APP(HOLE, CURSOR(HOLE)));
-            on('(', APP(CURSOR(HOLE), HOLE));
-            on('|', JOIN(CURSOR(HOLE), HOLE));
-            on('+', RAND(CURSOR(HOLE), HOLE));
-            on('{', QUOTE(CURSOR(HOLE)));
-            on('=', EQUAL(CURSOR(HOLE), HOLE));
-            on('<', LESS(CURSOR(HOLE), HOLE));
-            on('>', NLESS(CURSOR(HOLE), HOLE));
-
-            // TODO filter globals and locals by future validity
-            actionsArray.push([
-                '/',
-                searchGlobals,
-                VAR('global.variable')
-            ]);
-            var locals = syntax.tree.getLocals(term);
-            locals.forEach(function (varName) {
-                on(varName, VAR(varName));
-                // TODO deal with >26 variables
-            });
-
-        } else {
-            var dumped = syntax.tree.dump(term);
-
-            // TODO define context-specific deletions
-            on('X', HOLE);
-
-            on('\\', LAMBDA(fresh, CURSOR(DASH)), dumped);
-            on('W', LETREC(fresh, CURSOR(HOLE), DASH), dumped);
-            on('L', LETREC(fresh, DASH, CURSOR(HOLE)), dumped);
-            on('space', APP(DASH, CURSOR(HOLE)), dumped);
-            on('(', APP(CURSOR(HOLE), DASH), dumped);
-            on('|', JOIN(DASH, CURSOR(HOLE)), dumped);
-            on('+', RAND(DASH, CURSOR(HOLE)), dumped);
-            on('{', QUOTE(CURSOR(DASH)), dumped);
-            on('=', EQUAL(DASH, CURSOR(HOLE)), dumped);
-            on('<', LESS(DASH, CURSOR(HOLE)), dumped);
-            on('>', NLESS(DASH, CURSOR(HOLE)), dumped);
-        }
-        return genericBare.concat(actionsArray);
-    }
-};
-
diff --git a/puddle-editor/source/lib/menu/menu.js b/puddle-editor/source/lib/menu/menu.js
new file mode 100644
index 0000000..cc78d7f
--- /dev/null
+++ b/puddle-editor/source/lib/menu/menu.js
@@ -0,0 +1,153 @@
+'use strict';
+
+var _ = require('lodash');
+var $ = require('jquery');
+var assert = require('assert');
+var syntax = require('../puddle-syntax-0.1.2/index.js');
+var renderTerm = require('../render-term.js');
+
+var debug = require('debug')('puddle:editor:menu');
+var trace = require('../trace')(debug);
+var EventEmitter = require('events').EventEmitter;
+var emitter = new EventEmitter();
+
+var symbols = syntax.compiler.symbols;
+
+var HOLE = symbols.HOLE;
+var TOP = symbols.TOP;
+var BOT = symbols.BOT;
+var VAR = symbols.VAR;
+var LAMBDA = symbols.LAMBDA;
+var LETREC = symbols.LETREC;
+var APP = symbols.APP;
+var JOIN = symbols.JOIN;
+var RAND = symbols.RAND;
+var QUOTE = symbols.QUOTE;
+var EQUAL = symbols.EQUAL;
+var LESS = symbols.LESS;
+var NLESS = symbols.NLESS;
+var ASSERT = symbols.ASSERT;
+var DEFINE = symbols.DEFINE;
+var CURSOR = symbols.CURSOR;
+var DASH = VAR('—');
+
+var generic;
+
+module.exports = function (editor) {
+    trace('Menu init');
+    var actions = editor.getActions();
+
+    var render = function (term) {
+        trace('render');
+        return $('
').html(renderTerm(term));
+    };
+
+    var initGeneric = function () {
+        generic = [
+            ['enter', actions.commitLine, 'commit line'],
+            ['tab', actions.revertLine, 'revert line'],
+            ['up', actions.moveUp, 'move up'],
+            ['down', actions.moveDown, 'move down'],
+            ['left', actions.moveLeft, 'move left'],
+            ['right', actions.moveRight, 'move right'],
+            ['shift+left', actions.widenSelection, 'widen selection'],
+            ['shift+right', actions.widenSelection, 'widen selection']
+        ];
+        generic.push(
+            ['A', actions.insertAssert,
+                render(ASSERT(CURSOR(HOLE)))]);
+        generic.push(
+            ['D', actions.insertDefine,
+                render(DEFINE(CURSOR(VAR('...')), HOLE))]);
+    };
+
+    var getActions = function () {
+        trace('getActions');
+        var actionsArray = [];
+        var on = function (name, term, subsForDash) {
+            actionsArray.push([
+                name,
+                function () {
+                    actions.replaceBelow(term, subsForDash);
+                },
+                render(term)
+            ]);
+        };
+        var term = editor.getCursor().below[0];
+        var name = term.name;
+        var varName = syntax.tree.getFresh(term);
+        var fresh = VAR(varName);
+
+        if (name === 'ASSERT') {
+            actionsArray.push(['X', actions.removeLine, 'delete line']);
+        } else if (name === 'DEFINE') {
+            if (!editor.getCorpus().hasOccurrences(term.below[0].varName)) {
+                actionsArray.push(['X', actions.removeLine, 'delete line']);
+            }
+        } else if (name === 'HOLE') {
+            on('X', HOLE); // TODO define context-specific deletions
+            on('T', TOP);
+            on('_', BOT);
+            on('\\', LAMBDA(fresh, CURSOR(HOLE)));
+            on('W', LETREC(fresh, CURSOR(HOLE), HOLE));
+            on('L', LETREC(fresh, HOLE, CURSOR(HOLE)));
+            on('space', APP(HOLE, CURSOR(HOLE)));
+            on('(', APP(CURSOR(HOLE), HOLE));
+            on('|', JOIN(CURSOR(HOLE), HOLE));
+            on('+', RAND(CURSOR(HOLE), HOLE));
+            on('{', QUOTE(CURSOR(HOLE)));
+            on('=', EQUAL(CURSOR(HOLE), HOLE));
+            on('<', LESS(CURSOR(HOLE), HOLE));
+            on('>', NLESS(CURSOR(HOLE), HOLE));
+
+            // TODO filter globals and locals by future validity
+            actionsArray.push([
+                '/',
+                function (name) {
+                    assert(name !== undefined, 'name not found: ' + name);
+                    actions.replaceBelow(VAR(name));
+                },
+                VAR('global.variable')
+            ]);
+
+            var locals = syntax.tree.getLocals(term);
+            locals.forEach(function (varName) {
+                on(varName, VAR(varName));
+                // TODO deal with >26 variables
+            });
+
+        } else {
+            var dumped = syntax.tree.dump(term);
+
+            // TODO define context-specific deletions
+            on('X', HOLE);
+
+            on('\\', LAMBDA(fresh, CURSOR(DASH)), dumped);
+            on('W', LETREC(fresh, CURSOR(HOLE), DASH), dumped);
+            on('L', LETREC(fresh, DASH, CURSOR(HOLE)), dumped);
+            on('space', APP(DASH, CURSOR(HOLE)), dumped);
+            on('(', APP(CURSOR(HOLE), DASH), dumped);
+            on('|', JOIN(DASH, CURSOR(HOLE)), dumped);
+            on('+', RAND(DASH, CURSOR(HOLE)), dumped);
+            on('{', QUOTE(CURSOR(DASH)), dumped);
+            on('=', EQUAL(DASH, CURSOR(HOLE)), dumped);
+            on('<', LESS(DASH, CURSOR(HOLE)), dumped);
+            on('>', NLESS(DASH, CURSOR(HOLE)), dumped);
+        }
+        return generic.concat(actionsArray);
+    };
+
+    var build = function () {
+        trace('build');
+        emitter.emit('update', getActions());
+    };
+
+    initGeneric();
+    editor.on('update', build);
+
+    return {
+        on: _.bind(emitter.on, emitter),
+        getActions: getActions
+    };
+};
+
diff --git a/puddle-editor/source/lib/menu/menuRenderer.js b/puddle-editor/source/lib/menu/menuRenderer.js
new file mode 100644
index 0000000..c714fc2
--- /dev/null
+++ b/puddle-editor/source/lib/menu/menuRenderer.js
@@ -0,0 +1,235 @@
+'use strict';
+
+var debug = require('debug')('puddle:editor:menuRenderer');
+var _ = require('lodash');
+var $ = require('jquery');
+var io = require('socket.io-client');
+var assert = require('../assert');
+var keycode = require('../keycode');
+var trace = require('../trace')(debug);
+var socket = io();
+var renderTerm = require('../render-term.js');
+var syntax = require('../puddle-syntax-0.1.2/index.js');
+var symbols = syntax.compiler.symbols;
+var VAR = symbols.VAR;
+
+
+module.exports = function (menu, editor) {
+    /** @constructor */
+    var KeyEvent = function (which, modifiers) {
+        if (modifiers === undefined) {
+            modifiers = {};
+        }
+        this.state = [
+            which,
+                modifiers.shift || false,
+                modifiers.ctrl || false,
+                modifiers.alt || false,
+                modifiers.meta || false
+        ];
+    };
+
+    KeyEvent.prototype = {
+        match: function (event) {
+            var state = this.state;
+            return (
+                state[0] === event.which &&
+                state[1] === event.shiftKey &&
+                state[2] === event.ctrlKey &&
+                state[3] === event.altKey &&
+                state[4] === event.metaKey
+                );
+        }
+    };
+
+    var cases = (function () {
+        var cases = {};
+        for (var name in keycode) {
+            var which = keycode[name];
+            cases[name] = new KeyEvent(which);
+            cases['shift+' + name] = new KeyEvent(which, {'shift': true});
+            cases['ctrl+' + name] = new KeyEvent(which, {'ctrl': true});
+        }
+
+        _.forEach('ABCDEFGHIJKLMNOPQRSTUVWXYZ', function (name) {
+            cases[name] = cases['shift+' + name.toLowerCase()];
+        });
+
+        var aliases = {
+            ' ': 'space',
+            '{': 'shift+openbracket',
+            '\\': 'backslash',
+            '/': 'slash',
+            '|': 'shift+backslash',
+            '=': 'equal',
+            '+': 'shift+equal',
+            '<': 'shift+comma',
+            '>': 'shift+period',
+            '_': 'shift+dash',
+            '.': 'period',
+            '(': 'shift+9',
+            ')': 'shift+0',
+            '?': 'shift+slash'
+        };
+        _.each(aliases, function (alias, actual) {
+            cases[actual] = cases[alias];
+        });
+
+        return cases;
+    })();
+
+    var icons = {};
+    _.each(cases, function (unused, name) {
+        var escaped = name.replace(/\b\+\b/g, '+');
+        icons[name] = $('').html('' + escaped + '');
+    });
+
+//--------------------------------------------------------------------------
+// Event Handling
+
+    var events = [];
+    var callbacks = [];
+    var search = function (acceptMatch) {
+        trace('Search init');
+        var strings = [];
+        var $input;
+        var matches = [];
+        var $matches;
+        var render = _.identity;
+
+        var update = function () {
+            var re = new RegExp($input.val());
+            matches = [];
+            $matches.empty();
+            strings.forEach(function (string) {
+                if (re.test(string)) {
+                    matches.push(string);
+                    $matches.append($('
').html(render(string)));
+                }
+            });
+            $matches.children().first().addClass('selected');
+            debug('DEBUG ' + matches);
+        };
+
+
+        var accept = function () {
+            if (matches.length) {
+                debug('DEBUG accepting ' + matches[0]);
+                acceptMatch(matches[0]);
+            }
+        };
+
+        return function () {
+            var rankedStrings = editor.getCorpus().findAllNames();
+            trace('Search');
+            strings = rankedStrings;
+            render = function (name) {
+                return renderTerm(VAR(name));
+            };
+
+
+            off();
+            on('enter', accept, 'accept');
+            on('tab', reRender, 'cancel');
+            $input = $('');
+            $matches = $('
'); + $('#navigate').append($input, $matches); + $input.focus().on('keydown', _.debounce(update)); + update(); + }; + }; + + var choose = function (acceptName) { + trace('choose init'); + var $input; + var input; + var valid; + + var update = function () { + input = $input.val(); + valid = editor.getCorpus().canDefine(input); + $input.attr({'class': valid ? 'valid' : 'invalid'}); + }; + + return function () { + trace('choose'); + off(); + + on('enter', function () { + if (valid) { + debug('DEBUG choosing ' + input); + acceptName(input); + } + }, 'accept'); + + on('tab', reRender, 'cancel'); + + $input = $(''); + $('#navigate').append($input); + $input.focus().on('keydown', _.debounce(update)); + update(); + }; + }; + + var on = function (name, callback, description) { + trace('on'); + assert(_.has(cases, name)); + events.push(cases[name]); + + //TODO this is a hack to add UI wrapper for some functions + //which require special input parameters or extra UI + if (name === 'D') { + callback = choose(callback); + } + if (name === '/') { + callback = search(callback); + } + + var loggedCallback = function () { + socket.emit('action', name); + callback(); + }; + callbacks.push(loggedCallback); + if (description !== undefined) { + $('#navigate table').append( + $('') + .on('click', loggedCallback) + .append(icons[name], $('') + .html(description))); + } + }; + + var off = function () { + trace('off'); + events = []; + callbacks = []; + $('#navigate').empty().append($('')); + }; + + var render = function (actions) { + debug('Menu render', _.toArray(actions).length); + off(); + actions.forEach(function (action) { + on(action[0], action[1], action[2]); + }); + }; + + //Hook events dispatcher to keydown event + $(window).off('keydown').on('keydown', function (event) { + for (var i = 0; i < events.length; ++i) { + if (events[i].match(event)) { + event.preventDefault(); + trace('matched', event.which); + callbacks[i](); + return; + } + } + trace('unmatched ', event.which); + }); + + var reRender = function () { + render(menu.getActions()); + }; + reRender(); + menu.on('update', render); +}; \ No newline at end of file diff --git a/puddle-editor/source/lib/navigate.js b/puddle-editor/source/lib/navigate.js deleted file mode 100644 index 6c7b29a..0000000 --- a/puddle-editor/source/lib/navigate.js +++ /dev/null @@ -1,234 +0,0 @@ -/* jshint unused: false */ -'use strict'; - -var _ = require('lodash'); -var $ = require('jquery'); -var io = require('socket.io-client'); -var assert = require('./assert'); -var keycode = require('./keycode'); -var debug = require('debug')('puddle:editor:navigate'); -var trace = require('./trace')(debug); - -var socket = io(); - -//-------------------------------------------------------------------------- -// Events - -/** @constructor */ -var KeyEvent = function (which, modifiers) { - if (modifiers === undefined) { - modifiers = {}; - } - this.state = [ - which, - modifiers.shift || false, - modifiers.ctrl || false, - modifiers.alt || false, - modifiers.meta || false - ]; -}; - -KeyEvent.prototype = { - match: function (event, match) { - var state = this.state; - return ( - state[0] === event.which && - state[1] === event.shiftKey && - state[2] === event.ctrlKey && - state[3] === event.altKey && - state[4] === event.metaKey - ); - } -}; - -var cases = (function () { - var cases = {}; - for (var name in keycode) { - var which = keycode[name]; - cases[name] = new KeyEvent(which); - cases['shift+' + name] = new KeyEvent(which, {'shift': true}); - cases['ctrl+' + name] = new KeyEvent(which, {'ctrl': true}); - } - - _.forEach('ABCDEFGHIJKLMNOPQRSTUVWXYZ', function (name) { - cases[name] = cases['shift+' + name.toLowerCase()]; - }); - - var aliases = { - ' ': 'space', - '{': 'shift+openbracket', - '\\': 'backslash', - '/': 'slash', - '|': 'shift+backslash', - '=': 'equal', - '+': 'shift+equal', - '<': 'shift+comma', - '>': 'shift+period', - '_': 'shift+dash', - '.': 'period', - '(': 'shift+9', - ')': 'shift+0', - '?': 'shift+slash' - }; - _.each(aliases, function (alias, actual) { - cases[actual] = cases[alias]; - }); - - return cases; -})(); - -var icons = {}; -_.each(cases, function (unused, name) { - var escaped = name.replace(/\b\+\b/g, '+'); - icons[name] = $('') - .on('click', loggedCallback) - .append(icons[name], $('
').html('' + escaped + ''); -}); - -//-------------------------------------------------------------------------- -// Event Handling - -var events = []; -var callbacks = []; - -var on = function (name, callback, description) { - trace('on'); - assert(_.has(cases, name)); - events.push(cases[name]); - var loggedCallback = function () { - socket.emit('action', name); - callback(); - }; - callbacks.push(loggedCallback); - if (description !== undefined) { - $('#navigate table').append( - $('
') - .html(description))); - } -}; - -var off = function () { - trace('off'); - events = []; - callbacks = []; - $('#navigate').empty().append($('')); -}; - - -//-------------------------------------------------------------------------- -// Selection via search - -var search = (function () { - trace('Search init'); - var strings = []; - var $input; - var matches = []; - var $matches; - var render = _.identity; - - var update = function () { - var re = new RegExp($input.val()); - matches = []; - $matches.empty(); - strings.forEach(function (string) { - if (re.test(string)) { - matches.push(string); - $matches.append($('
').html(render(string)));
-            }
-        });
-        $matches.children().first().addClass('selected');
-        debug('DEBUG ' + matches);
-    };
-
-    var cancelCallback;
-    var acceptCallback;
-    var accept = function () {
-        if (matches.length) {
-            debug('DEBUG accepting ' + matches[0]);
-            acceptCallback(matches[0]);
-        } else {
-            cancelCallback();
-        }
-    };
-
-    return function (rankedStrings, acceptMatch, cancel, renderString) {
-        trace('Search');
-        strings = rankedStrings;
-        render = renderString;
-        acceptCallback = acceptMatch;
-        cancelCallback = cancel;
-
-        off();
-        on('enter', accept, 'accept');
-        on('tab', cancel, 'cancel');
-        $input = $('');
-        $matches = $('
'); - $('#navigate').append($input, $matches); - $input.focus().on('keydown', _.debounce(update)); - update(); - }; -})(); - -var choose = (function () { - trace('choose init'); - var $input; - var input; - var isValid; - var valid; - - var update = function () { - input = $input.val(); - valid = isValid(input); - $input.attr({'class': valid ? 'valid' : 'invalid'}); - }; - - var cancelCallback; - var acceptCallback; - var accept = function () { - if (valid) { - debug('DEBUG choosing ' + input); - acceptCallback(input); - } else { - cancelCallback(); - } - }; - - return function (isValidFilter, acceptName, cancel) { - trace('choose'); - isValid = isValidFilter; - acceptCallback = acceptName; - cancelCallback = cancel; - - off(); - on('enter', accept, 'accept'); - on('tab', cancel, 'cancel'); - $input = $(''); - $('#navigate').append($input); - $input.focus().on('keydown', _.debounce(update)); - update(); - }; -})(); - -//-------------------------------------------------------------------------- -// Interface - -module.exports = { - on: on, - off: off, - search: search, - choose: choose, - hookEvents: function () { - trace('hookEvents to window'); - $(window).off('keydown').on('keydown', function (event) { - for (var i = 0; i < events.length; ++i) { - if (events[i].match(event)) { - event.preventDefault(); - trace('matched', event.which); - callbacks[i](); - return; - } - } - trace('unmatched ', event.which); - }); - } -}; From 41ea672521b436d2ce801123ccc755b747183376 Mon Sep 17 00:00:00 2001 From: yangit Date: Tue, 7 Oct 2014 18:14:32 +0800 Subject: [PATCH 2/7] * editor does not care about view or menu * old puddle-syntax removed * view and menu code refactored * keycode replaced with keypressJS lib --- puddle-editor/.jshintignore | 2 +- puddle-editor/gulpfile.js | 2 +- puddle-editor/source/app.js | 7 - puddle-editor/source/{lib => app}/TODO.js | 0 puddle-editor/source/{lib => app}/assert.js | 0 puddle-editor/source/{lib => app}/corpus.js | 2 +- puddle-editor/source/{lib => app}/editor.js | 114 +- puddle-editor/source/{lib => app}/main.js | 13 +- .../source/{lib => app}/menu/menu.js | 102 +- .../source/{lib => app}/menu/menuRenderer.js | 135 +- .../source/{lib => app}/render-term.js | 6 +- .../source/{lib => app}/render-validity.js | 0 .../source/{lib => app}/server-syntax.js | 0 puddle-editor/source/{lib => app}/trace.js | 0 puddle-editor/source/app/view.js | 35 + puddle-editor/source/index.html | 2 +- puddle-editor/source/lib/keycode.js | 99 -- puddle-editor/source/lib/keypress.js | 1099 ++++++++++++ .../source/lib/puddle-syntax-0.1.2/.jshintrc | 18 - .../source/lib/puddle-syntax-0.1.2/.npmignore | 25 - .../lib/puddle-syntax-0.1.2/.travis.yml | 3 - .../source/lib/puddle-syntax-0.1.2/LICENSE | 21 - .../source/lib/puddle-syntax-0.1.2/README.md | 205 --- .../source/lib/puddle-syntax-0.1.2/index.js | 8 - .../lib/puddle-syntax-0.1.2/lib/TODO.js | 16 - .../lib/puddle-syntax-0.1.2/lib/assert.js | 90 - .../lib/puddle-syntax-0.1.2/lib/basis.js | 48 - .../lib/puddle-syntax-0.1.2/lib/compiler.js | 1506 ----------------- .../lib/puddle-syntax-0.1.2/lib/cursor.js | 174 -- .../lib/puddle-syntax-0.1.2/lib/grammar.js | 56 - .../lib/puddle-syntax-0.1.2/lib/map-assert.js | 44 - .../lib/puddle-syntax-0.1.2/lib/pattern.js | 193 --- .../lib/puddle-syntax-0.1.2/lib/pretty.js | 48 - .../lib/puddle-syntax-0.1.2/lib/stats.js | 83 - .../lib/puddle-syntax-0.1.2/lib/template.js | 19 - .../lib/puddle-syntax-0.1.2/lib/test.js | 44 - .../lib/puddle-syntax-0.1.2/lib/tokens.js | 117 -- .../lib/puddle-syntax-0.1.2/lib/tree.js | 172 -- .../node_modules/debug/.jshintrc | 3 - .../node_modules/debug/.npmignore | 6 - .../node_modules/debug/History.md | 144 -- .../node_modules/debug/Makefile | 33 - .../node_modules/debug/Readme.md | 156 -- .../node_modules/debug/browser.js | 147 -- .../node_modules/debug/component.json | 19 - .../node_modules/debug/debug.js | 197 --- .../node_modules/debug/node.js | 129 -- .../debug/node_modules/ms/.npmignore | 5 - .../debug/node_modules/ms/README.md | 33 - .../debug/node_modules/ms/index.js | 111 -- .../debug/node_modules/ms/package.json | 46 - .../node_modules/debug/package.json | 71 - .../node_modules/underscore/LICENSE | 23 - .../node_modules/underscore/README.md | 22 - .../node_modules/underscore/package.json | 69 - .../node_modules/underscore/underscore-min.js | 6 - .../node_modules/underscore/underscore.js | 1415 ---------------- .../lib/puddle-syntax-0.1.2/package.json | 56 - .../puddle-syntax-0.1.2/test/lib/datasets.js | 21 - .../lib/puddle-syntax-0.1.2/test/main.js | 7 - .../lib/puddle-syntax-0.1.2/test/pretty.js | 24 - puddle-editor/source/lib/view.js | 70 - puddle-server/server/server.js | 8 + 63 files changed, 1288 insertions(+), 6041 deletions(-) delete mode 100644 puddle-editor/source/app.js rename puddle-editor/source/{lib => app}/TODO.js (100%) rename puddle-editor/source/{lib => app}/assert.js (100%) rename puddle-editor/source/{lib => app}/corpus.js (99%) rename puddle-editor/source/{lib => app}/editor.js (80%) rename puddle-editor/source/{lib => app}/main.js (82%) rename puddle-editor/source/{lib => app}/menu/menu.js (66%) rename puddle-editor/source/{lib => app}/menu/menuRenderer.js (54%) rename puddle-editor/source/{lib => app}/render-term.js (92%) rename puddle-editor/source/{lib => app}/render-validity.js (100%) rename puddle-editor/source/{lib => app}/server-syntax.js (100%) rename puddle-editor/source/{lib => app}/trace.js (100%) create mode 100644 puddle-editor/source/app/view.js delete mode 100644 puddle-editor/source/lib/keycode.js create mode 100755 puddle-editor/source/lib/keypress.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/.jshintrc delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/.npmignore delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/.travis.yml delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/LICENSE delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/README.md delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/index.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/lib/TODO.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/lib/assert.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/lib/basis.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/lib/compiler.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/lib/cursor.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/lib/grammar.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/lib/map-assert.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/lib/pattern.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/lib/pretty.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/lib/stats.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/lib/template.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/lib/test.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/lib/tokens.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/lib/tree.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/.jshintrc delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/.npmignore delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/History.md delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/Makefile delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/Readme.md delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/browser.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/component.json delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/debug.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/node.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/node_modules/ms/.npmignore delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/node_modules/ms/README.md delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/node_modules/ms/index.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/node_modules/ms/package.json delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/package.json delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/underscore/LICENSE delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/underscore/README.md delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/underscore/package.json delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/underscore/underscore-min.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/underscore/underscore.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/package.json delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/test/lib/datasets.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/test/main.js delete mode 100644 puddle-editor/source/lib/puddle-syntax-0.1.2/test/pretty.js delete mode 100644 puddle-editor/source/lib/view.js diff --git a/puddle-editor/.jshintignore b/puddle-editor/.jshintignore index 52b3a86..543a6e8 100644 --- a/puddle-editor/.jshintignore +++ b/puddle-editor/.jshintignore @@ -1,4 +1,4 @@ node_modules coverage build -source/lib/puddle-syntax-0.1.2/node_modules \ No newline at end of file +source/lib \ No newline at end of file diff --git a/puddle-editor/gulpfile.js b/puddle-editor/gulpfile.js index 012995c..0fc31af 100644 --- a/puddle-editor/gulpfile.js +++ b/puddle-editor/gulpfile.js @@ -40,7 +40,7 @@ gulp.task('copyHtml', function () { }); gulp.task('browserify', function () { - return gulp.src('./source/app.js') + return gulp.src('./source/app/main.js') .pipe(browserify({ exclude: ['mocha'], debug: true diff --git a/puddle-editor/source/app.js b/puddle-editor/source/app.js deleted file mode 100644 index 7ebc420..0000000 --- a/puddle-editor/source/app.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; -global.debug = require('debug'); -var io = require('socket.io-client'); -global.puddleSocket = require('puddle-socket').client(io); -global.syntaxNew = require('puddle-syntax'); - -require('./lib/main.js'); diff --git a/puddle-editor/source/lib/TODO.js b/puddle-editor/source/app/TODO.js similarity index 100% rename from puddle-editor/source/lib/TODO.js rename to puddle-editor/source/app/TODO.js diff --git a/puddle-editor/source/lib/assert.js b/puddle-editor/source/app/assert.js similarity index 100% rename from puddle-editor/source/lib/assert.js rename to puddle-editor/source/app/assert.js diff --git a/puddle-editor/source/lib/corpus.js b/puddle-editor/source/app/corpus.js similarity index 99% rename from puddle-editor/source/lib/corpus.js rename to puddle-editor/source/app/corpus.js index 6ec394a..fddf09c 100644 --- a/puddle-editor/source/lib/corpus.js +++ b/puddle-editor/source/app/corpus.js @@ -7,7 +7,7 @@ var _ = require('lodash'); var uuid = require('node-uuid'); -var tokens = require('./puddle-syntax-0.1.2').tokens; +var tokens = require('puddle-syntax').tokens; var assert = require('./assert'); var debug = require('debug')('puddle:editor:corpus'); var puddleSocket = global.puddleSocket; diff --git a/puddle-editor/source/lib/editor.js b/puddle-editor/source/app/editor.js similarity index 80% rename from puddle-editor/source/lib/editor.js rename to puddle-editor/source/app/editor.js index 2b0caa4..b8eb706 100644 --- a/puddle-editor/source/lib/editor.js +++ b/puddle-editor/source/app/editor.js @@ -3,12 +3,10 @@ var _ = require('lodash'); var $ = require('jquery'); var io = require('socket.io-client'); -var syntax = require('./puddle-syntax-0.1.2'); -var syntaxNew = require('puddle-syntax'); +var syntax = require('puddle-syntax'); var EventEmitter = require('events').EventEmitter; var emitter = new EventEmitter(); var assert = require('./assert'); -var view = require('./view'); var corpus = require('./corpus'); var debug = require('debug')('puddle:editor'); var trace = require('./trace')(debug); @@ -50,7 +48,6 @@ var insertLine = function (line, done, fail) { line, function (line) { syntax.cursor.remove(cursor); - view.update(ids[cursorPos]); cursorPos += 1; var id = line.id; ids = ids.slice(0, cursorPos).concat([id], ids.slice(cursorPos)); @@ -60,8 +57,6 @@ var insertLine = function (line, done, fail) { trees[id] = root; validities[id] = _.clone(UNKNOWN); pollValidities(); - view.insertAfter(ids[cursorPos - 1], id); - scrollToCursor(); if (done !== undefined) { done(); } @@ -78,13 +73,11 @@ var insertLine = function (line, done, fail) { // incoming create var onInsertLine = function (id, line) { ids.push(id); - var churchTerm = syntaxNew.compiler.load(line); + var churchTerm = syntax.compiler.load(line); var root = syntax.tree.load(churchTerm); trees[id] = root; validities[id] = _.clone(UNKNOWN); pollValidities(); - view.insertAfter(ids[ids.length - 2], id); - scrollToCursor(); }; // incoming remove @@ -98,8 +91,6 @@ var onRemoveLine = function (id) { ids = ids.slice(0, pos).concat(ids.slice(pos + 1)); delete trees[id]; delete validities[id]; - view.remove(id); - scrollToCursor(); }; // outgoing update @@ -110,12 +101,11 @@ var onUpdateLine = function (id, line) { } else if (pos < cursorPos) { cursorPos -= 1; } - var churchTerm = syntaxNew.compiler.load(line); + var churchTerm = syntax.compiler.load(line); var root = syntax.tree.load(churchTerm); trees[id] = root; validities[id] = _.clone(UNKNOWN); pollValidities(); - view.update(id); }; @@ -142,7 +132,6 @@ var commitLine = function () { validities[id] = _.clone(UNKNOWN); } pollValidities(); - view.update(id); lineChanged = false; }; @@ -178,7 +167,7 @@ var pollValidities = (function () { if (oldValidity !== undefined) { if (!_.isEqual(oldValidity, validity)) { validities[id] = validity; - view.update(id); + emitter.emit('updateValidity'); } } }); @@ -204,12 +193,6 @@ var pollValidities = (function () { //-------------------------------------------------------------------------- // Cursor Movement -var scrollToCursor = function () { - trace('scrollToCursor', arguments); - var pos = $('span.cursor').offset().top - $(window).height() / 2; - $(document.body).animate({scrollTop: pos}, 50); -}; - var initCursor = function () { trace('initCursor', arguments); cursor = shared.cursor = syntax.cursor.create(); @@ -218,25 +201,18 @@ var initCursor = function () { var id = ids[cursorPos]; socket.emit('action', {'moveTo': id}); syntax.cursor.insertAbove(cursor, trees[id]); - view.update(id); - scrollToCursor(); }; var moveCursorLine = function (delta) { - trace('moveCursorLine', arguments); - if (lineChanged) { commitLine(); } if (0 <= cursorPos + delta && cursorPos + delta < ids.length) { syntax.cursor.remove(cursor); - view.update(ids[cursorPos]); cursorPos = (cursorPos + ids.length + delta) % ids.length; var id = ids[cursorPos]; socket.emit('action', {'moveTo': id}); syntax.cursor.insertAbove(cursor, trees[id]); - view.update(id); - scrollToCursor(); } }; @@ -246,10 +222,7 @@ var moveCursorLine = function (delta) { var moveCursor = function (direction) { return function () { - trace('moveCursor', arguments); - if (syntax.cursor.tryMove(cursor, direction)) { - view.update(ids[cursorPos]); - } + syntax.cursor.tryMove(cursor, direction); }; }; @@ -265,7 +238,6 @@ var pureActions = { syntax.cursor.remove(cursor); syntax.cursor.insertAbove(cursor, root); trees[id] = root; - view.update(id); lineChanged = false; }, removeLine: function () { @@ -277,19 +249,16 @@ var pureActions = { .concat(ids.slice(cursorPos + 1)); delete trees[id]; delete validities[id]; - view.remove(id); if (cursorPos === ids.length) { cursorPos -= 1; } id = ids[cursorPos]; syntax.cursor.insertAbove(cursor, trees[id]); - view.update(id); - scrollToCursor(); }, insertAssert: function (done, fail) { trace('insertAssert', arguments); - var HOLE = syntax.compiler.symbols.HOLE; - var ASSERT = syntax.compiler.symbols.ASSERT; + var HOLE = syntax.compiler.fragments.church.HOLE; + var ASSERT = syntax.compiler.fragments.church.ASSERT; var churchTerm = ASSERT(HOLE); var line = syntax.compiler.dumpLine(churchTerm); line.name = null; @@ -297,9 +266,9 @@ var pureActions = { }, insertDefine: function (varName, done, fail) { trace('insertDefine', arguments); - var VAR = syntax.compiler.symbols.VAR; - var HOLE = syntax.compiler.symbols.HOLE; - var DEFINE = syntax.compiler.symbols.DEFINE; + var VAR = syntax.compiler.fragments.church.VAR; + var HOLE = syntax.compiler.fragments.church.HOLE; + var DEFINE = syntax.compiler.fragments.church.DEFINE; var churchTerm = DEFINE(VAR(varName), HOLE); var line = syntax.compiler.dumpLine(churchTerm); insertLine(line, done, fail); @@ -315,7 +284,6 @@ var pureActions = { cursor = shared.cursor = syntax.cursor .replaceBelow(cursor, newTerm); lineChanged = true; - view.update(ids[cursorPos]); }, moveUp: function () { moveCursorLine(-1); @@ -325,45 +293,51 @@ var pureActions = { }, moveLeft: moveCursor('L'), moveRight: moveCursor('R'), + moveCursorLine: moveCursorLine, widenSelection: moveCursor('U') }; -var sharedActions = _.each(pureActions, function (value, key, hash) { - hash[key] = function () { - value.apply(this,_.toArray(arguments)); +var wrap = function (callback) { + return function () { + trace('editor update'); + callback.apply(this, _.toArray(arguments)); emitter.emit('update'); }; +}; + +var sharedActions = {}; +_.each(pureActions, function (value, key) { + sharedActions[key] = wrap(value); }); +var sortLines = function (lines) { + /* + Return a heuristically sorted list of definitions. + + TODO use approximately topologically-sorted order. + (R1) "A Technique for Drawing Directed Graphs" -Gansner et al + http://www.graphviz.org/Documentation/TSE93.pdf + (R2) "Combinatorial Algorithms for Feedback Problems in Directed Graphs" + -Demetrescu and Finocchi + http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.1.9435 + */ + return lines; +}; + module.exports = { main: function () { loadAllLines(); - view.init({ - lines: corpus.findAllLines(), - events: {'click': function (id) { - trace('moveCursorTo', arguments); - var newPos = _.indexOf(ids, id); - var delta = newPos - cursorPos; - moveCursorLine(delta); - }}, - getLine: function (id) { - return syntax.tree.dump(syntax.tree.getRoot(trees[id])); - }, - getValidity: function (id) { - return validities[id]; - } - }); initCursor(); }, getTerms: function () { return ids.map(function (id) { - return syntaxNew.tree.dump(trees[id]); + return syntax.tree.dump(trees[id]); }); }, crud: { - create: onInsertLine, - remove: onRemoveLine, - update: onUpdateLine + create: wrap(onInsertLine), + remove: wrap(onRemoveLine), + update: wrap(onUpdateLine) }, getActions: function () { return sharedActions; @@ -375,6 +349,18 @@ module.exports = { //TODO this function has to be replaced by methods of forest. getCorpus: function () { return corpus; + }, + getValidity: function (id) { + return validities[id]; + }, + getLine: function (id) { + return syntax.tree.dump(syntax.tree.getRoot(trees[id])); + }, + getIds: function () { + return sortLines(_.cloneDeep(ids)); + }, + getCursorId: function () { + return ids[cursorPos]; } }; diff --git a/puddle-editor/source/lib/main.js b/puddle-editor/source/app/main.js similarity index 82% rename from puddle-editor/source/lib/main.js rename to puddle-editor/source/app/main.js index 814e2da..17b2e6e 100644 --- a/puddle-editor/source/lib/main.js +++ b/puddle-editor/source/app/main.js @@ -1,14 +1,16 @@ 'use strict'; -var corpus = global.corpus = require('./corpus'); -var editor = global.editor = require('./editor'); var Menu = require('./menu/menu'); var menuRenderer = require('./menu/menuRenderer'); +var View = require('./view'); var serverSyntax = require('./server-syntax'); var _ = require('lodash'); -var puddleSocket = global.puddleSocket; - -var debug = require('debug')('puddle:editor:main'); +var io = require('socket.io-client'); +var puddleSocket = global.puddleSocket = require('puddle-socket').client(io); +var corpus = global.corpus = require('./corpus'); +var editor = global.editor = require('./editor'); +var debugModule = global.debug = require('debug'); +var debug = debugModule('puddle:editor:main'); var trace = require('./trace')(debug); //TODO refactor editor to expose reset method @@ -26,6 +28,7 @@ var initEditor = _.once(function () { //TODO refactor for better API var menu = Menu(editor); menuRenderer(menu, editor); + View(editor); }); var reinitEditor = function () { diff --git a/puddle-editor/source/lib/menu/menu.js b/puddle-editor/source/app/menu/menu.js similarity index 66% rename from puddle-editor/source/lib/menu/menu.js rename to puddle-editor/source/app/menu/menu.js index cc78d7f..c1e89b8 100644 --- a/puddle-editor/source/lib/menu/menu.js +++ b/puddle-editor/source/app/menu/menu.js @@ -1,65 +1,66 @@ 'use strict'; var _ = require('lodash'); -var $ = require('jquery'); var assert = require('assert'); -var syntax = require('../puddle-syntax-0.1.2/index.js'); -var renderTerm = require('../render-term.js'); - +var syntax = require('puddle-syntax'); +var io = require('socket.io-client'); +var socket = io(); var debug = require('debug')('puddle:editor:menu'); var trace = require('../trace')(debug); var EventEmitter = require('events').EventEmitter; var emitter = new EventEmitter(); -var symbols = syntax.compiler.symbols; - -var HOLE = symbols.HOLE; -var TOP = symbols.TOP; -var BOT = symbols.BOT; -var VAR = symbols.VAR; -var LAMBDA = symbols.LAMBDA; -var LETREC = symbols.LETREC; -var APP = symbols.APP; -var JOIN = symbols.JOIN; -var RAND = symbols.RAND; -var QUOTE = symbols.QUOTE; -var EQUAL = symbols.EQUAL; -var LESS = symbols.LESS; -var NLESS = symbols.NLESS; -var ASSERT = symbols.ASSERT; -var DEFINE = symbols.DEFINE; -var CURSOR = symbols.CURSOR; +var fragments = syntax.compiler.fragments.church; + +var HOLE = fragments.HOLE; +var TOP = fragments.TOP; +var BOT = fragments.BOT; +var VAR = fragments.VAR; +var LAMBDA = fragments.LAMBDA; +var LETREC = fragments.LETREC; +var APP = fragments.APP; +var JOIN = fragments.JOIN; +var RAND = fragments.RAND; +var QUOTE = fragments.QUOTE; +var EQUAL = fragments.EQUAL; +var LESS = fragments.LESS; +var NLESS = fragments.NLESS; +var ASSERT = fragments.ASSERT; +var DEFINE = fragments.DEFINE; +var CURSOR = fragments.CURSOR; var DASH = VAR('—'); -var generic; module.exports = function (editor) { trace('Menu init'); var actions = editor.getActions(); - - var render = function (term) { - trace('render'); - return $('
').html(renderTerm(term));
+    var socketLogWrapper = function (actions) {
+        return actions.map(function (action) {
+            var name = action[0];
+            var callback = function () {
+                socket.emit('action', action[0]);
+                action[1].apply(this, _.toArray(arguments));
+            };
+            var description = action[2];
+            return [name, callback, description];
+        });
     };
 
-    var initGeneric = function () {
-        generic = [
-            ['enter', actions.commitLine, 'commit line'],
-            ['tab', actions.revertLine, 'revert line'],
-            ['up', actions.moveUp, 'move up'],
-            ['down', actions.moveDown, 'move down'],
-            ['left', actions.moveLeft, 'move left'],
-            ['right', actions.moveRight, 'move right'],
-            ['shift+left', actions.widenSelection, 'widen selection'],
-            ['shift+right', actions.widenSelection, 'widen selection']
-        ];
-        generic.push(
-            ['A', actions.insertAssert,
-                render(ASSERT(CURSOR(HOLE)))]);
-        generic.push(
-            ['D', actions.insertDefine,
-                render(DEFINE(CURSOR(VAR('...')), HOLE))]);
-    };
+
+    var generic = [
+        ['enter', actions.commitLine, 'commit line'],
+        ['tab', actions.revertLine, 'revert line'],
+        ['up', actions.moveUp, 'move up'],
+        ['down', actions.moveDown, 'move down'],
+        ['left', actions.moveLeft, 'move left'],
+        ['right', actions.moveRight, 'move right'],
+        ['shift+left', actions.widenSelection, 'widen selection'],
+        ['shift+right', actions.widenSelection, 'widen selection'],
+        ['A', actions.insertAssert,
+            ASSERT(CURSOR(HOLE))],
+        ['D', actions.insertDefine,
+            DEFINE(CURSOR(VAR('...')), HOLE)]
+    ];
 
     var getActions = function () {
         trace('getActions');
@@ -70,7 +71,7 @@ module.exports = function (editor) {
                 function () {
                     actions.replaceBelow(term, subsForDash);
                 },
-                render(term)
+                term
             ]);
         };
         var term = editor.getCursor().below[0];
@@ -134,16 +135,13 @@ module.exports = function (editor) {
             on('<', LESS(DASH, CURSOR(HOLE)), dumped);
             on('>', NLESS(DASH, CURSOR(HOLE)), dumped);
         }
-        return generic.concat(actionsArray);
+        return socketLogWrapper(generic.concat(actionsArray));
     };
 
-    var build = function () {
+    editor.on('update', function () {
         trace('build');
         emitter.emit('update', getActions());
-    };
-
-    initGeneric();
-    editor.on('update', build);
+    });
 
     return {
         on: _.bind(emitter.on, emitter),
diff --git a/puddle-editor/source/lib/menu/menuRenderer.js b/puddle-editor/source/app/menu/menuRenderer.js
similarity index 54%
rename from puddle-editor/source/lib/menu/menuRenderer.js
rename to puddle-editor/source/app/menu/menuRenderer.js
index c714fc2..ad47a09 100644
--- a/puddle-editor/source/lib/menu/menuRenderer.js
+++ b/puddle-editor/source/app/menu/menuRenderer.js
@@ -3,92 +3,18 @@
 var debug = require('debug')('puddle:editor:menuRenderer');
 var _ = require('lodash');
 var $ = require('jquery');
-var io = require('socket.io-client');
-var assert = require('../assert');
-var keycode = require('../keycode');
+var keypress = require('../../lib/keypress').keypress;
+var keyListener = new keypress.Listener();
 var trace = require('../trace')(debug);
-var socket = io();
+
 var renderTerm = require('../render-term.js');
-var syntax = require('../puddle-syntax-0.1.2/index.js');
-var symbols = syntax.compiler.symbols;
-var VAR = symbols.VAR;
+var VAR = require('puddle-syntax').compiler.fragments.church.VAR;
 
 
 module.exports = function (menu, editor) {
-    /** @constructor */
-    var KeyEvent = function (which, modifiers) {
-        if (modifiers === undefined) {
-            modifiers = {};
-        }
-        this.state = [
-            which,
-                modifiers.shift || false,
-                modifiers.ctrl || false,
-                modifiers.alt || false,
-                modifiers.meta || false
-        ];
-    };
-
-    KeyEvent.prototype = {
-        match: function (event) {
-            var state = this.state;
-            return (
-                state[0] === event.which &&
-                state[1] === event.shiftKey &&
-                state[2] === event.ctrlKey &&
-                state[3] === event.altKey &&
-                state[4] === event.metaKey
-                );
-        }
-    };
-
-    var cases = (function () {
-        var cases = {};
-        for (var name in keycode) {
-            var which = keycode[name];
-            cases[name] = new KeyEvent(which);
-            cases['shift+' + name] = new KeyEvent(which, {'shift': true});
-            cases['ctrl+' + name] = new KeyEvent(which, {'ctrl': true});
-        }
-
-        _.forEach('ABCDEFGHIJKLMNOPQRSTUVWXYZ', function (name) {
-            cases[name] = cases['shift+' + name.toLowerCase()];
-        });
-
-        var aliases = {
-            ' ': 'space',
-            '{': 'shift+openbracket',
-            '\\': 'backslash',
-            '/': 'slash',
-            '|': 'shift+backslash',
-            '=': 'equal',
-            '+': 'shift+equal',
-            '<': 'shift+comma',
-            '>': 'shift+period',
-            '_': 'shift+dash',
-            '.': 'period',
-            '(': 'shift+9',
-            ')': 'shift+0',
-            '?': 'shift+slash'
-        };
-        _.each(aliases, function (alias, actual) {
-            cases[actual] = cases[alias];
-        });
-
-        return cases;
-    })();
-
-    var icons = {};
-    _.each(cases, function (unused, name) {
-        var escaped = name.replace(/\b\+\b/g, '+');
-        icons[name] = $('
') - .on('click', loggedCallback) - .append(icons[name], $('
').html('' + escaped + ''); - }); - //-------------------------------------------------------------------------- // Event Handling - var events = []; - var callbacks = []; var search = function (acceptMatch) { trace('Search init'); var strings = []; @@ -170,11 +96,27 @@ module.exports = function (menu, editor) { update(); }; }; - + var upperCaseAliases = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ?><\":{}|~+_!@#$%^&*()'; var on = function (name, callback, description) { trace('on'); - assert(_.has(cases, name)); - events.push(cases[name]); + var listenKey; + if (_.contains(upperCaseAliases, name)) { + listenKey = 'shift ' + name.toLowerCase(); + } else { + listenKey = name.replace(/\+/g, ' '); + } + + //Ignore because of non_camel_case code. + /* jshint ignore:start */ + keyListener.register_combo({ + 'keys': listenKey, + 'on_keydown': function () { + callback(); + }, + is_solitary: true + }); + /* jshint ignore:end */ + //TODO this is a hack to add UI wrapper for some functions //which require special input parameters or extra UI @@ -185,24 +127,24 @@ module.exports = function (menu, editor) { callback = search(callback); } - var loggedCallback = function () { - socket.emit('action', name); - callback(); - }; - callbacks.push(loggedCallback); + if (description !== undefined) { + if (_.isArray(description)) { + description = renderTerm(description); + } + var escaped = name.replace(/\b\+\b/g, '+'); + var icon = $('').html('' + escaped + ''); $('#navigate table').append( $('
') + .on('click', callback) + .append(icon, $('') .html(description))); } }; var off = function () { trace('off'); - events = []; - callbacks = []; + keyListener.reset(); $('#navigate').empty().append($('')); }; @@ -214,19 +156,6 @@ module.exports = function (menu, editor) { }); }; - //Hook events dispatcher to keydown event - $(window).off('keydown').on('keydown', function (event) { - for (var i = 0; i < events.length; ++i) { - if (events[i].match(event)) { - event.preventDefault(); - trace('matched', event.which); - callbacks[i](); - return; - } - } - trace('unmatched ', event.which); - }); - var reRender = function () { render(menu.getActions()); }; diff --git a/puddle-editor/source/lib/render-term.js b/puddle-editor/source/app/render-term.js similarity index 92% rename from puddle-editor/source/lib/render-term.js rename to puddle-editor/source/app/render-term.js index 7eff888..d3ee6dd 100644 --- a/puddle-editor/source/lib/render-term.js +++ b/puddle-editor/source/app/render-term.js @@ -2,7 +2,7 @@ var _ = require('lodash'); var assert = require('assert'); -var syntax = require('./puddle-syntax-0.1.2'); +var syntax = require('puddle-syntax'); var template = function (string) { return function (args) { @@ -41,9 +41,7 @@ var templates = { } }; -var render = syntax.compiler.fold(function (token, args) { +module.exports = syntax.compiler.fold(function (token, args) { assert(_.has(templates, token), 'unknown token: ' + token); return templates[token](args); }); - -module.exports = render; diff --git a/puddle-editor/source/lib/render-validity.js b/puddle-editor/source/app/render-validity.js similarity index 100% rename from puddle-editor/source/lib/render-validity.js rename to puddle-editor/source/app/render-validity.js diff --git a/puddle-editor/source/lib/server-syntax.js b/puddle-editor/source/app/server-syntax.js similarity index 100% rename from puddle-editor/source/lib/server-syntax.js rename to puddle-editor/source/app/server-syntax.js diff --git a/puddle-editor/source/lib/trace.js b/puddle-editor/source/app/trace.js similarity index 100% rename from puddle-editor/source/lib/trace.js rename to puddle-editor/source/app/trace.js diff --git a/puddle-editor/source/app/view.js b/puddle-editor/source/app/view.js new file mode 100644 index 0000000..4d2f4bf --- /dev/null +++ b/puddle-editor/source/app/view.js @@ -0,0 +1,35 @@ +'use strict'; + +var _ = require('lodash'); +var $ = require('jquery'); +var syntax = require('puddle-syntax'); +var renderTerm = require('./render-term.js'); +var renderValidity = require('./render-validity.js'); + + +module.exports = function (editor) { + var render = function () { + var div = $('#code').empty()[0]; + editor.getIds().forEach(function (id, index, array) { + var line = editor.getLine(id); + var validity = editor.getValidity(id); + var $line = $('
').attr('id', 'line' + id).appendTo(div);
+            line = syntax.compiler.parenthesize(line);
+            $line.html(renderValidity(validity) + renderTerm(line));
+
+            $line.on('click', function () {
+                var newPos = _.indexOf(array, id);
+                var oldPos = _.indexOf(array, editor.getCursorId());
+                var delta = newPos - oldPos;
+                editor.getActions().moveCursorLine(delta);
+            });
+
+        });
+        var pos = $('span.cursor').offset().top - $(window).height() / 2;
+        $(document.body).animate({scrollTop: pos}, 50);
+    };
+
+    render();
+    editor.on('update', render);
+    editor.on('updateValidity', render);
+};
\ No newline at end of file
diff --git a/puddle-editor/source/index.html b/puddle-editor/source/index.html
index 6c3ad4d..c033461 100644
--- a/puddle-editor/source/index.html
+++ b/puddle-editor/source/index.html
@@ -5,10 +5,10 @@
     Pomagma Editor
     
     
-    
 
 
 
+ diff --git a/puddle-editor/source/lib/keycode.js b/puddle-editor/source/lib/keycode.js deleted file mode 100644 index 9782970..0000000 --- a/puddle-editor/source/lib/keycode.js +++ /dev/null @@ -1,99 +0,0 @@ -module.exports = { - 'backspace': 8, - 'tab': 9, - 'enter': 13, - 'shift': 16, - 'ctrl': 17, - 'alt': 18, - 'pause': 19, - 'capslock': 20, - 'escape': 27, - 'space': 32, - 'pageup': 33, - 'pagedown': 34, - 'end': 35, - 'home': 36, - 'left': 37, - 'up': 38, - 'right': 39, - 'down': 40, - 'insert': 45, - 'delete': 46, - '0': 48, - '1': 49, - '2': 50, - '3': 51, - '4': 52, - '5': 53, - '6': 54, - '7': 55, - '8': 56, - '9': 57, - 'a': 65, - 'b': 66, - 'c': 67, - 'd': 68, - 'e': 69, - 'f': 70, - 'g': 71, - 'h': 72, - 'i': 73, - 'j': 74, - 'k': 75, - 'l': 76, - 'm': 77, - 'n': 78, - 'o': 79, - 'p': 80, - 'q': 81, - 'r': 82, - 's': 83, - 't': 84, - 'u': 85, - 'v': 86, - 'w': 87, - 'x': 88, - 'y': 89, - 'z': 90, - 'leftwindow': 91, - 'rightwindow': 92, - 'select': 93, - 'numpad0': 96, - 'numpad1': 97, - 'numpad2': 98, - 'numpad3': 99, - 'numpad4': 100, - 'numpad5': 101, - 'numpad6': 102, - 'numpad7': 103, - 'numpad8': 104, - 'numpad9': 105, - 'times': 106, - 'plus': 107, - 'minus': 109, - 'dot': 110, - 'divide': 111, - 'f1': 112, - 'f2': 113, - 'f3': 114, - 'f4': 115, - 'f5': 116, - 'f6': 117, - 'f7': 118, - 'f8': 119, - 'f9': 120, - 'f10': 121, - 'f11': 122, - 'f12': 123, - 'semicolon': 186, - 'equal': 187, - 'comma': 188, - 'dash': 189, - 'period': 190, - 'slash': 191, - 'grave': 192, - 'openbracket': 219, - 'backslash': 220, - 'closebracket': 221, - 'quote': 222 -}; diff --git a/puddle-editor/source/lib/keypress.js b/puddle-editor/source/lib/keypress.js new file mode 100755 index 0000000..464b93a --- /dev/null +++ b/puddle-editor/source/lib/keypress.js @@ -0,0 +1,1099 @@ +// Generated by CoffeeScript 1.7.1 + +/* +Copyright 2014 David Mauro + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Keypress is a robust keyboard input capturing Javascript utility +focused on input for games. + +version 2.0.3 + */ + + +/* +Combo options available and their defaults: + keys : [] - An array of the keys pressed together to activate combo. + count : 0 - The number of times a counting combo has been pressed. Reset on release. + is_unordered : false - Unless this is set to true, the keys can be pressed down in any order. + is_counting : false - Makes this a counting combo (see documentation). + is_exclusive : false - This combo will replace other exclusive combos when true. + is_solitary : false - This combo will only fire if ONLY it's keys are pressed down. + is_sequence : false - Rather than a key combo, this is an ordered key sequence. + prevent_default : false - Prevent default behavior for all component key keypresses. + prevent_repeat : false - Prevent the combo from repeating when keydown is held. + on_keydown : null - A function that is called when the combo is pressed. + on_keyup : null - A function that is called when the combo is released. + on_release : null - A function that is called when all keys in the combo are released. + this : undefined - Defines the scope for your callback functions. + */ + +(function() { + var Combo, keypress, _change_keycodes_by_browser, _compare_arrays, _compare_arrays_sorted, _convert_key_to_readable, _convert_to_shifted_key, _decide_meta_key, _factory_defaults, _filter_array, _index_of_in_array, _is_array_in_array, _is_array_in_array_sorted, _key_is_valid, _keycode_alternate_names, _keycode_dictionary, _keycode_shifted_keys, _log_error, _metakey, _modifier_event_mapping, _modifier_keys, _validate_combo, + __hasProp = {}.hasOwnProperty, + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + _factory_defaults = { + is_unordered: false, + is_counting: false, + is_exclusive: false, + is_solitary: false, + prevent_default: false, + prevent_repeat: false + }; + + _modifier_keys = ["meta", "alt", "option", "ctrl", "shift", "cmd"]; + + _metakey = "ctrl"; + + keypress = {}; + + keypress.debug = false; + + Combo = (function() { + function Combo(dictionary) { + var property, value; + for (property in dictionary) { + if (!__hasProp.call(dictionary, property)) continue; + value = dictionary[property]; + if (value !== false) { + this[property] = value; + } + } + this.keys = this.keys || []; + this.count = this.count || 0; + } + + Combo.prototype.allows_key_repeat = function() { + return !this.prevent_repeat && typeof this.on_keydown === "function"; + }; + + Combo.prototype.reset = function() { + this.count = 0; + return this.keyup_fired = null; + }; + + return Combo; + + })(); + + keypress.Listener = (function() { + function Listener(element, defaults) { + var attach_handler, property, value; + this.should_suppress_event_defaults = false; + this.should_force_event_defaults = false; + this.sequence_delay = 800; + this._registered_combos = []; + this._keys_down = []; + this._active_combos = []; + this._sequence = []; + this._sequence_timer = null; + this._prevent_capture = false; + this._defaults = defaults || {}; + for (property in _factory_defaults) { + if (!__hasProp.call(_factory_defaults, property)) continue; + value = _factory_defaults[property]; + this._defaults[property] = this._defaults[property] || value; + } + element = element || document.body; + attach_handler = function(target, event, handler) { + if (target.addEventListener) { + return target.addEventListener(event, handler); + } else if (target.attachEvent) { + return target.attachEvent("on" + event, handler); + } + }; + attach_handler(element, "keydown", (function(_this) { + return function(e) { + e = e || window.event; + _this._receive_input(e, true); + return _this._bug_catcher(e); + }; + })(this)); + attach_handler(element, "keyup", (function(_this) { + return function(e) { + e = e || window.event; + return _this._receive_input(e, false); + }; + })(this)); + attach_handler(window, "blur", (function(_this) { + return function() { + var key, _i, _len, _ref; + _ref = _this._keys_down; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + key = _ref[_i]; + _this._key_up(key, {}); + } + return _this._keys_down = []; + }; + })(this)); + } + + Listener.prototype._bug_catcher = function(e) { + var _ref; + if (_metakey === "cmd" && __indexOf.call(this._keys_down, "cmd") >= 0 && ((_ref = _convert_key_to_readable(e.keyCode)) !== "cmd" && _ref !== "shift" && _ref !== "alt" && _ref !== "caps" && _ref !== "tab")) { + return this._receive_input(e, false); + } + }; + + Listener.prototype._cmd_bug_check = function(combo_keys) { + if (_metakey === "cmd" && __indexOf.call(this._keys_down, "cmd") >= 0 && __indexOf.call(combo_keys, "cmd") < 0) { + return false; + } + return true; + }; + + Listener.prototype._prevent_default = function(e, should_prevent) { + if ((should_prevent || this.should_suppress_event_defaults) && !this.should_force_event_defaults) { + if (e.preventDefault) { + e.preventDefault(); + } else { + e.returnValue = false; + } + if (e.stopPropagation) { + return e.stopPropagation(); + } + } + }; + + Listener.prototype._get_active_combos = function(key) { + var active_combos, keys_down; + active_combos = []; + keys_down = _filter_array(this._keys_down, function(down_key) { + return down_key !== key; + }); + keys_down.push(key); + this._match_combo_arrays(keys_down, (function(_this) { + return function(match) { + if (_this._cmd_bug_check(match.keys)) { + return active_combos.push(match); + } + }; + })(this)); + this._fuzzy_match_combo_arrays(keys_down, (function(_this) { + return function(match) { + if (__indexOf.call(active_combos, match) >= 0) { + return; + } + if (!(match.is_solitary || !_this._cmd_bug_check(match.keys))) { + return active_combos.push(match); + } + }; + })(this)); + return active_combos; + }; + + Listener.prototype._get_potential_combos = function(key) { + var combo, potentials, _i, _len, _ref; + potentials = []; + _ref = this._registered_combos; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + combo = _ref[_i]; + if (combo.is_sequence) { + continue; + } + if (__indexOf.call(combo.keys, key) >= 0 && this._cmd_bug_check(combo.keys)) { + potentials.push(combo); + } + } + return potentials; + }; + + Listener.prototype._add_to_active_combos = function(combo) { + var active_combo, active_key, active_keys, already_replaced, combo_key, i, should_prepend, should_replace, _i, _j, _k, _len, _len1, _ref, _ref1; + should_replace = false; + should_prepend = true; + already_replaced = false; + if (__indexOf.call(this._active_combos, combo) >= 0) { + return true; + } else if (this._active_combos.length) { + for (i = _i = 0, _ref = this._active_combos.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + active_combo = this._active_combos[i]; + if (!(active_combo && active_combo.is_exclusive && combo.is_exclusive)) { + continue; + } + active_keys = active_combo.keys; + if (!should_replace) { + for (_j = 0, _len = active_keys.length; _j < _len; _j++) { + active_key = active_keys[_j]; + should_replace = true; + if (__indexOf.call(combo.keys, active_key) < 0) { + should_replace = false; + break; + } + } + } + if (should_prepend && !should_replace) { + _ref1 = combo.keys; + for (_k = 0, _len1 = _ref1.length; _k < _len1; _k++) { + combo_key = _ref1[_k]; + should_prepend = false; + if (__indexOf.call(active_keys, combo_key) < 0) { + should_prepend = true; + break; + } + } + } + if (should_replace) { + if (already_replaced) { + active_combo = this._active_combos.splice(i, 1)[0]; + if (active_combo != null) { + active_combo.reset(); + } + } else { + active_combo = this._active_combos.splice(i, 1, combo)[0]; + if (active_combo != null) { + active_combo.reset(); + } + already_replaced = true; + } + should_prepend = false; + } + } + } + if (should_prepend) { + this._active_combos.unshift(combo); + } + return should_replace || should_prepend; + }; + + Listener.prototype._remove_from_active_combos = function(combo) { + var active_combo, i, _i, _ref; + for (i = _i = 0, _ref = this._active_combos.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + active_combo = this._active_combos[i]; + if (active_combo === combo) { + combo = this._active_combos.splice(i, 1)[0]; + combo.reset(); + break; + } + } + }; + + Listener.prototype._get_possible_sequences = function() { + var combo, i, j, match, matches, sequence, _i, _j, _k, _len, _ref, _ref1, _ref2; + matches = []; + _ref = this._registered_combos; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + combo = _ref[_i]; + for (j = _j = 1, _ref1 = this._sequence.length; 1 <= _ref1 ? _j <= _ref1 : _j >= _ref1; j = 1 <= _ref1 ? ++_j : --_j) { + sequence = this._sequence.slice(-j); + if (!combo.is_sequence) { + continue; + } + if (__indexOf.call(combo.keys, "shift") < 0) { + sequence = _filter_array(sequence, function(key) { + return key !== "shift"; + }); + if (!sequence.length) { + continue; + } + } + for (i = _k = 0, _ref2 = sequence.length; 0 <= _ref2 ? _k < _ref2 : _k > _ref2; i = 0 <= _ref2 ? ++_k : --_k) { + if (combo.keys[i] === sequence[i]) { + match = true; + } else { + match = false; + break; + } + } + if (match) { + matches.push(combo); + } + } + } + return matches; + }; + + Listener.prototype._add_key_to_sequence = function(key, e) { + var combo, sequence_combos, _i, _len; + this._sequence.push(key); + sequence_combos = this._get_possible_sequences(); + if (sequence_combos.length) { + for (_i = 0, _len = sequence_combos.length; _i < _len; _i++) { + combo = sequence_combos[_i]; + this._prevent_default(e, combo.prevent_default); + } + if (this._sequence_timer) { + clearTimeout(this._sequence_timer); + } + if (this.sequence_delay > -1) { + this._sequence_timer = setTimeout(function() { + return this._sequence = []; + }, this.sequence_delay); + } + } else { + this._sequence = []; + } + }; + + Listener.prototype._get_sequence = function(key) { + var combo, i, j, match, seq_key, sequence, _i, _j, _k, _len, _ref, _ref1, _ref2; + _ref = this._registered_combos; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + combo = _ref[_i]; + if (!combo.is_sequence) { + continue; + } + for (j = _j = 1, _ref1 = this._sequence.length; 1 <= _ref1 ? _j <= _ref1 : _j >= _ref1; j = 1 <= _ref1 ? ++_j : --_j) { + sequence = (_filter_array(this._sequence, function(seq_key) { + if (__indexOf.call(combo.keys, "shift") >= 0) { + return true; + } + return seq_key !== "shift"; + })).slice(-j); + if (combo.keys.length !== sequence.length) { + continue; + } + for (i = _k = 0, _ref2 = sequence.length; 0 <= _ref2 ? _k < _ref2 : _k > _ref2; i = 0 <= _ref2 ? ++_k : --_k) { + seq_key = sequence[i]; + if (__indexOf.call(combo.keys, "shift") < 0) { + if (seq_key === "shift") { + continue; + } + } + if (key === "shift" && __indexOf.call(combo.keys, "shift") < 0) { + continue; + } + if (combo.keys[i] === seq_key) { + match = true; + } else { + match = false; + break; + } + } + } + if (match) { + return combo; + } + } + return false; + }; + + Listener.prototype._receive_input = function(e, is_keydown) { + var key; + if (this._prevent_capture) { + if (this._keys_down.length) { + this._keys_down = []; + } + return; + } + key = _convert_key_to_readable(e.keyCode); + if (!is_keydown && !this._keys_down.length && (key === "alt" || key === _metakey)) { + return; + } + if (!key) { + return; + } + if (is_keydown) { + return this._key_down(key, e); + } else { + return this._key_up(key, e); + } + }; + + Listener.prototype._fire = function(event, combo, key_event, is_autorepeat) { + if (typeof combo["on_" + event] === "function") { + this._prevent_default(key_event, combo["on_" + event].call(combo["this"], key_event, combo.count, is_autorepeat) !== true); + } + if (event === "release") { + combo.count = 0; + } + if (event === "keyup") { + return combo.keyup_fired = true; + } + }; + + Listener.prototype._match_combo_arrays = function(potential_match, match_handler) { + var source_combo, _i, _len, _ref; + _ref = this._registered_combos; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + source_combo = _ref[_i]; + if ((!source_combo.is_unordered && _compare_arrays_sorted(potential_match, source_combo.keys)) || (source_combo.is_unordered && _compare_arrays(potential_match, source_combo.keys))) { + match_handler(source_combo); + } + } + }; + + Listener.prototype._fuzzy_match_combo_arrays = function(potential_match, match_handler) { + var source_combo, _i, _len, _ref; + _ref = this._registered_combos; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + source_combo = _ref[_i]; + if ((!source_combo.is_unordered && _is_array_in_array_sorted(source_combo.keys, potential_match)) || (source_combo.is_unordered && _is_array_in_array(source_combo.keys, potential_match))) { + match_handler(source_combo); + } + } + }; + + Listener.prototype._keys_remain = function(combo) { + var key, keys_remain, _i, _len, _ref; + _ref = combo.keys; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + key = _ref[_i]; + if (__indexOf.call(this._keys_down, key) >= 0) { + keys_remain = true; + break; + } + } + return keys_remain; + }; + + Listener.prototype._key_down = function(key, e) { + var combo, combos, event_mod, i, mod, potential, potential_combos, sequence_combo, shifted_key, _i, _j, _k, _len, _len1, _ref; + shifted_key = _convert_to_shifted_key(key, e); + if (shifted_key) { + key = shifted_key; + } + this._add_key_to_sequence(key, e); + sequence_combo = this._get_sequence(key); + if (sequence_combo) { + this._fire("keydown", sequence_combo, e); + } + for (mod in _modifier_event_mapping) { + event_mod = _modifier_event_mapping[mod]; + if (!e[event_mod]) { + continue; + } + if (mod === key || __indexOf.call(this._keys_down, mod) >= 0) { + continue; + } + this._keys_down.push(mod); + } + for (mod in _modifier_event_mapping) { + event_mod = _modifier_event_mapping[mod]; + if (mod === key) { + continue; + } + if (__indexOf.call(this._keys_down, mod) >= 0 && !e[event_mod]) { + if (mod === "cmd" && _metakey !== "cmd") { + continue; + } + for (i = _i = 0, _ref = this._keys_down.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + if (this._keys_down[i] === mod) { + this._keys_down.splice(i, 1); + } + } + } + } + combos = this._get_active_combos(key); + potential_combos = this._get_potential_combos(key); + for (_j = 0, _len = combos.length; _j < _len; _j++) { + combo = combos[_j]; + this._handle_combo_down(combo, potential_combos, key, e); + } + if (potential_combos.length) { + for (_k = 0, _len1 = potential_combos.length; _k < _len1; _k++) { + potential = potential_combos[_k]; + this._prevent_default(e, potential.prevent_default); + } + } + if (__indexOf.call(this._keys_down, key) < 0) { + this._keys_down.push(key); + } + }; + + Listener.prototype._handle_combo_down = function(combo, potential_combos, key, e) { + var is_autorepeat, is_other_exclusive, potential_combo, result, _i, _len; + if (__indexOf.call(combo.keys, key) < 0) { + return false; + } + this._prevent_default(e, combo && combo.prevent_default); + is_autorepeat = false; + if (__indexOf.call(this._keys_down, key) >= 0) { + is_autorepeat = true; + if (!combo.allows_key_repeat()) { + return false; + } + } + result = this._add_to_active_combos(combo, key); + combo.keyup_fired = false; + is_other_exclusive = false; + if (combo.is_exclusive) { + for (_i = 0, _len = potential_combos.length; _i < _len; _i++) { + potential_combo = potential_combos[_i]; + if (potential_combo.is_exclusive && potential_combo.keys.length > combo.keys.length) { + is_other_exclusive = true; + break; + } + } + } + if (!is_other_exclusive) { + if (combo.is_counting && typeof combo.on_keydown === "function") { + combo.count += 1; + } + if (result) { + return this._fire("keydown", combo, e, is_autorepeat); + } + } + }; + + Listener.prototype._key_up = function(key, e) { + var active_combo, active_combos_length, combo, combos, i, sequence_combo, shifted_key, unshifted_key, _i, _j, _k, _l, _len, _len1, _len2, _ref, _ref1, _ref2, _ref3; + unshifted_key = key; + shifted_key = _convert_to_shifted_key(key, e); + if (shifted_key) { + key = shifted_key; + } + shifted_key = _keycode_shifted_keys[unshifted_key]; + if (e.shiftKey) { + if (!(shifted_key && __indexOf.call(this._keys_down, shifted_key) >= 0)) { + key = unshifted_key; + } + } else { + if (!(unshifted_key && __indexOf.call(this._keys_down, unshifted_key) >= 0)) { + key = shifted_key; + } + } + sequence_combo = this._get_sequence(key); + if (sequence_combo) { + this._fire("keyup", sequence_combo, e); + } + if (__indexOf.call(this._keys_down, key) < 0) { + return false; + } + for (i = _i = 0, _ref = this._keys_down.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + if ((_ref1 = this._keys_down[i]) === key || _ref1 === shifted_key || _ref1 === unshifted_key) { + this._keys_down.splice(i, 1); + break; + } + } + active_combos_length = this._active_combos.length; + combos = []; + _ref2 = this._active_combos; + for (_j = 0, _len = _ref2.length; _j < _len; _j++) { + active_combo = _ref2[_j]; + if (__indexOf.call(active_combo.keys, key) >= 0) { + combos.push(active_combo); + } + } + for (_k = 0, _len1 = combos.length; _k < _len1; _k++) { + combo = combos[_k]; + this._handle_combo_up(combo, e, key); + } + if (active_combos_length > 1) { + _ref3 = this._active_combos; + for (_l = 0, _len2 = _ref3.length; _l < _len2; _l++) { + active_combo = _ref3[_l]; + if (active_combo === void 0 || __indexOf.call(combos, active_combo) >= 0) { + continue; + } + if (!this._keys_remain(active_combo)) { + this._remove_from_active_combos(active_combo); + } + } + } + }; + + Listener.prototype._handle_combo_up = function(combo, e, key) { + var keys_down, keys_remaining; + this._prevent_default(e, combo && combo.prevent_default); + keys_remaining = this._keys_remain(combo); + if (!combo.keyup_fired) { + keys_down = this._keys_down.slice(); + keys_down.push(key); + if (!combo.is_solitary || _compare_arrays(keys_down, combo.keys)) { + this._fire("keyup", combo, e); + if (combo.is_counting && typeof combo.on_keyup === "function" && typeof combo.on_keydown !== "function") { + combo.count += 1; + } + } + } + if (!keys_remaining) { + this._fire("release", combo, e); + this._remove_from_active_combos(combo); + } + }; + + Listener.prototype.simple_combo = function(keys, callback) { + return this.register_combo({ + keys: keys, + on_keydown: callback + }); + }; + + Listener.prototype.counting_combo = function(keys, count_callback) { + return this.register_combo({ + keys: keys, + is_counting: true, + is_unordered: false, + on_keydown: count_callback + }); + }; + + Listener.prototype.sequence_combo = function(keys, callback) { + return this.register_combo({ + keys: keys, + on_keydown: callback, + is_sequence: true + }); + }; + + Listener.prototype.register_combo = function(combo_dictionary) { + var combo, property, value, _ref; + if (typeof combo_dictionary["keys"] === "string") { + combo_dictionary["keys"] = combo_dictionary["keys"].split(" "); + } + _ref = this._defaults; + for (property in _ref) { + if (!__hasProp.call(_ref, property)) continue; + value = _ref[property]; + if (combo_dictionary[property] === void 0) { + combo_dictionary[property] = value; + } + } + combo = new Combo(combo_dictionary); + if (_validate_combo(combo)) { + this._registered_combos.push(combo); + return combo; + } + }; + + Listener.prototype.register_many = function(combo_array) { + var combo, _i, _len, _results; + _results = []; + for (_i = 0, _len = combo_array.length; _i < _len; _i++) { + combo = combo_array[_i]; + _results.push(this.register_combo(combo)); + } + return _results; + }; + + Listener.prototype.unregister_combo = function(keys_or_combo) { + var combo, unregister_combo, _i, _len, _ref, _results; + if (!keys_or_combo) { + return false; + } + unregister_combo = (function(_this) { + return function(combo) { + var i, _i, _ref, _results; + _results = []; + for (i = _i = 0, _ref = _this._registered_combos.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + if (combo === _this._registered_combos[i]) { + _this._registered_combos.splice(i, 1); + break; + } else { + _results.push(void 0); + } + } + return _results; + }; + })(this); + if (keys_or_combo.keys != null) { + return unregister_combo(keys_or_combo); + } else { + if (typeof keys_or_combo === "string") { + keys_or_combo = keys_or_combo.split(" "); + } + _ref = this._registered_combos; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + combo = _ref[_i]; + if (combo == null) { + continue; + } + if ((combo.is_unordered && _compare_arrays(keys_or_combo, combo.keys)) || (!combo.is_unordered && _compare_arrays_sorted(keys_or_combo, combo.keys))) { + _results.push(unregister_combo(combo)); + } else { + _results.push(void 0); + } + } + return _results; + } + }; + + Listener.prototype.unregister_many = function(combo_array) { + var combo, _i, _len, _results; + _results = []; + for (_i = 0, _len = combo_array.length; _i < _len; _i++) { + combo = combo_array[_i]; + _results.push(this.unregister_combo(combo)); + } + return _results; + }; + + Listener.prototype.get_registered_combos = function() { + return this._registered_combos; + }; + + Listener.prototype.reset = function() { + return this._registered_combos = []; + }; + + Listener.prototype.listen = function() { + return this._prevent_capture = false; + }; + + Listener.prototype.stop_listening = function() { + return this._prevent_capture = true; + }; + + Listener.prototype.get_meta_key = function() { + return _metakey; + }; + + return Listener; + + })(); + + _decide_meta_key = function() { + if (navigator.userAgent.indexOf("Mac OS X") !== -1) { + _metakey = "cmd"; + } + }; + + _change_keycodes_by_browser = function() { + if (navigator.userAgent.indexOf("Opera") !== -1) { + _keycode_dictionary["17"] = "cmd"; + } + }; + + _convert_key_to_readable = function(k) { + return _keycode_dictionary[k]; + }; + + _filter_array = function(array, callback) { + var element; + if (array.filter) { + return array.filter(callback); + } else { + return (function() { + var _i, _len, _results; + _results = []; + for (_i = 0, _len = array.length; _i < _len; _i++) { + element = array[_i]; + if (callback(element)) { + _results.push(element); + } + } + return _results; + })(); + } + }; + + _compare_arrays = function(a1, a2) { + var item, _i, _len; + if (a1.length !== a2.length) { + return false; + } + for (_i = 0, _len = a1.length; _i < _len; _i++) { + item = a1[_i]; + if (__indexOf.call(a2, item) >= 0) { + continue; + } + return false; + } + return true; + }; + + _compare_arrays_sorted = function(a1, a2) { + var i, _i, _ref; + if (a1.length !== a2.length) { + return false; + } + for (i = _i = 0, _ref = a1.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + if (a1[i] !== a2[i]) { + return false; + } + } + return true; + }; + + _is_array_in_array = function(a1, a2) { + var item, _i, _len; + for (_i = 0, _len = a1.length; _i < _len; _i++) { + item = a1[_i]; + if (__indexOf.call(a2, item) < 0) { + return false; + } + } + return true; + }; + + _index_of_in_array = Array.prototype.indexOf || function(a, item) { + var i, _i, _ref; + for (i = _i = 0, _ref = a.length; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) { + if (a[i] === item) { + return i; + } + } + return -1; + }; + + _is_array_in_array_sorted = function(a1, a2) { + var index, item, prev, _i, _len; + prev = 0; + for (_i = 0, _len = a1.length; _i < _len; _i++) { + item = a1[_i]; + index = _index_of_in_array.call(a2, item); + if (index >= prev) { + prev = index; + } else { + return false; + } + } + return true; + }; + + _log_error = function() { + if (keypress.debug) { + return console.log.apply(console, arguments); + } + }; + + _key_is_valid = function(key) { + var valid, valid_key, _; + valid = false; + for (_ in _keycode_dictionary) { + valid_key = _keycode_dictionary[_]; + if (key === valid_key) { + valid = true; + break; + } + } + if (!valid) { + for (_ in _keycode_shifted_keys) { + valid_key = _keycode_shifted_keys[_]; + if (key === valid_key) { + valid = true; + break; + } + } + } + return valid; + }; + + _validate_combo = function(combo) { + var alt_name, i, key, mod_key, non_modifier_keys, property, validated, value, _i, _j, _k, _len, _len1, _ref, _ref1; + validated = true; + if (!combo.keys.length) { + _log_error("You're trying to bind a combo with no keys:", combo); + } + for (i = _i = 0, _ref = combo.keys.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + key = combo.keys[i]; + alt_name = _keycode_alternate_names[key]; + if (alt_name) { + key = combo.keys[i] = alt_name; + } + if (key === "meta") { + combo.keys.splice(i, 1, _metakey); + } + if (key === "cmd") { + _log_error("Warning: use the \"meta\" key rather than \"cmd\" for Windows compatibility"); + } + } + _ref1 = combo.keys; + for (_j = 0, _len = _ref1.length; _j < _len; _j++) { + key = _ref1[_j]; + if (!_key_is_valid(key)) { + _log_error("Do not recognize the key \"" + key + "\""); + validated = false; + } + } + if (__indexOf.call(combo.keys, "meta") >= 0 || __indexOf.call(combo.keys, "cmd") >= 0) { + non_modifier_keys = combo.keys.slice(); + for (_k = 0, _len1 = _modifier_keys.length; _k < _len1; _k++) { + mod_key = _modifier_keys[_k]; + if ((i = _index_of_in_array.call(non_modifier_keys, mod_key)) > -1) { + non_modifier_keys.splice(i, 1); + } + } + if (non_modifier_keys.length > 1) { + _log_error("META and CMD key combos cannot have more than 1 non-modifier keys", combo, non_modifier_keys); + validated = false; + } + } + for (property in combo) { + value = combo[property]; + if (_factory_defaults[property] === "undefined") { + _log_error("The property " + property + " is not a valid combo property. Your combo has still been registered."); + } + } + return validated; + }; + + _convert_to_shifted_key = function(key, e) { + var k; + if (!e.shiftKey) { + return false; + } + k = _keycode_shifted_keys[key]; + if (k != null) { + return k; + } + return false; + }; + + _modifier_event_mapping = { + "cmd": "metaKey", + "ctrl": "ctrlKey", + "shift": "shiftKey", + "alt": "altKey" + }; + + _keycode_alternate_names = { + "escape": "esc", + "control": "ctrl", + "command": "cmd", + "break": "pause", + "windows": "cmd", + "option": "alt", + "caps_lock": "caps", + "apostrophe": "\'", + "semicolon": ";", + "tilde": "~", + "accent": "`", + "scroll_lock": "scroll", + "num_lock": "num" + }; + + _keycode_shifted_keys = { + "/": "?", + ".": ">", + ",": "<", + "\'": "\"", + ";": ":", + "[": "{", + "]": "}", + "\\": "|", + "`": "~", + "=": "+", + "-": "_", + "1": "!", + "2": "@", + "3": "#", + "4": "$", + "5": "%", + "6": "^", + "7": "&", + "8": "*", + "9": "(", + "0": ")" + }; + + _keycode_dictionary = { + 0: "\\", + 8: "backspace", + 9: "tab", + 12: "num", + 13: "enter", + 16: "shift", + 17: "ctrl", + 18: "alt", + 19: "pause", + 20: "caps", + 27: "esc", + 32: "space", + 33: "pageup", + 34: "pagedown", + 35: "end", + 36: "home", + 37: "left", + 38: "up", + 39: "right", + 40: "down", + 44: "print", + 45: "insert", + 46: "delete", + 48: "0", + 49: "1", + 50: "2", + 51: "3", + 52: "4", + 53: "5", + 54: "6", + 55: "7", + 56: "8", + 57: "9", + 65: "a", + 66: "b", + 67: "c", + 68: "d", + 69: "e", + 70: "f", + 71: "g", + 72: "h", + 73: "i", + 74: "j", + 75: "k", + 76: "l", + 77: "m", + 78: "n", + 79: "o", + 80: "p", + 81: "q", + 82: "r", + 83: "s", + 84: "t", + 85: "u", + 86: "v", + 87: "w", + 88: "x", + 89: "y", + 90: "z", + 91: "cmd", + 92: "cmd", + 93: "cmd", + 96: "num_0", + 97: "num_1", + 98: "num_2", + 99: "num_3", + 100: "num_4", + 101: "num_5", + 102: "num_6", + 103: "num_7", + 104: "num_8", + 105: "num_9", + 106: "num_multiply", + 107: "num_add", + 108: "num_enter", + 109: "num_subtract", + 110: "num_decimal", + 111: "num_divide", + 124: "print", + 144: "num", + 145: "scroll", + 186: ";", + 187: "=", + 188: ",", + 189: "-", + 190: ".", + 191: "/", + 192: "`", + 219: "[", + 220: "\\", + 221: "]", + 222: "\'", + 223: "`", + 224: "cmd", + 225: "alt", + 57392: "ctrl", + 63289: "num", + 59: ";" + }; + + _decide_meta_key(); + + _change_keycodes_by_browser(); + + if (typeof define === "function" && define.amd) { + define([], function() { + return keypress; + }); + } else if (typeof exports !== "undefined" && exports !== null) { + exports.keypress = keypress; + } else { + window.keypress = keypress; + } + +}).call(this); diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/.jshintrc b/puddle-editor/source/lib/puddle-syntax-0.1.2/.jshintrc deleted file mode 100644 index d4a8c26..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/.jshintrc +++ /dev/null @@ -1,18 +0,0 @@ -{ - "camelcase": true, - "curly": true, - "eqeqeq": true, - "freeze": true, - "indent": 4, - "newcap": true, - "quotmark": "single", - "maxdepth": 4, - "maxstatements": 100, - "maxlen": 80, - "eqnull": true, - "funcscope": true, - "strict": true, - "undef": true, - "unused": true, - "node": true -} diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/.npmignore b/puddle-editor/source/lib/puddle-syntax-0.1.2/.npmignore deleted file mode 100644 index da23d0d..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/.npmignore +++ /dev/null @@ -1,25 +0,0 @@ -# Logs -logs -*.log - -# Runtime data -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directory -# Deployed apps should consider commenting this line out: -# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git -node_modules diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/.travis.yml b/puddle-editor/source/lib/puddle-syntax-0.1.2/.travis.yml deleted file mode 100644 index 6e5919d..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/.travis.yml +++ /dev/null @@ -1,3 +0,0 @@ -language: node_js -node_js: - - "0.10" diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/LICENSE b/puddle-editor/source/lib/puddle-syntax-0.1.2/LICENSE deleted file mode 100644 index f18b26b..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Fritz Obermeyer - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/README.md b/puddle-editor/source/lib/puddle-syntax-0.1.2/README.md deleted file mode 100644 index 4bdaf40..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/README.md +++ /dev/null @@ -1,205 +0,0 @@ -[![Build Status](https://travis-ci.org/fritzo/puddle-syntax.svg?branch=master)](http://travis-ci.org/fritzo/puddle-syntax) -[![NPM Version](https://badge.fury.io/js/puddle-syntax.svg)](https://www.npmjs.org/package/puddle-syntax) -[![NPM Dependencies](https://david-dm.org/fritzo/puddle-syntax.svg)](https://www.npmjs.org/package/puddle-syntax) - -# puddle-syntax - -Syntax tools for the -[Puddle](https://github.com/fritzo/puddle) coding environment - -## APi Reference - - var syntax = require('puddle-syntax'); - -* [Data Formats](#formats) -* [Module `syntax.compiler`](#compiler) -* [Module `syntax.pretty`](#pretty) -* [Module `syntax.tree`](#tree) -* [Module `syntax.cursor`](#cursor) - -### Data Formats - -This module deals with four formats of data. - -1. Codes - Immutable space-delimited lists of tokens. - - **JSON Serializable:** yes - - **Examples:** - - "I" - - "VAR x" - - "APP VAR f VAR x" - - "DEFINE VAR two LAMBDA VAR f LAMBDA VAR x APP VAR f APP VAR f VAR x" - - "ASSERT EQUAL APP VAR two I I" - -2. Lines - Immutable objects with a `code` field and - a `name` field that is either `null` or a string. - Each line is either an assertion or a definition; - assertions have `name = null` and definitions define `name` by their `code`. - - **JSON Serializable:** yes - - **Examples:** - - // a definition - { - "name": "two", - "code": "LAMBDA VAR f LAMBDA VAR x APP VAR f APP VAR x VAR x" - } - - // an assertion - { - "name": null, - "code": "EQUAL APP VAR two I I" - } - -3. Terms - Immutable array-representations of abstract syntax trees. - - **JSON Serializable:** yes - - **Examples:** - - [ - "DEFINE", ["VAR", "two"], [ - "LAMBDA", ["VAR", "f"], [ - "LAMBDA", ["VAR", "x"], [ - "APP", ["VAR" "f"], [ - "APP", ["VAR" "f"], ["VAR" "x"], - ] - ] - ] - ] - ] - - [ - "ASSERT", [ - "EQUAL", ["APP", ["VAR", "two"], "I"], "I" - ] - ] - -4. Trees - Mutable cross-linked abstract syntax trees for easy traversal. - - **JSON Serializable:** no, because of cycles - - **Examples:** - - {"name": "I", "above": null, "below": []} - - {"name": "VAR", "varName": "x", "above": null, "below": []} - - var fx = { - "name": "APP", - "above": null, - "below": [ - {"name": "VAR", "varName": "f", "below": [], "above": fx}, - {"name": "VAR", "varName": "x", "below": [], "above": fx} - ] - }; - -### Module `syntax.compiler` - -Signature: - - compiler.symbols : object(string | function) (constructors for terms) - compiler.load : code -> term - compiler.dump : term -> code - compiler.loadLine : line -> term - compiler.dumpLine : term -> line - compiler.print : term -> string - compiler.enumerateFresh : int -> string (a variable name) - compiler.substitute : name * term * term -> nil - compiler.parenthesize : term -> term - compiler.fold : /\f: (string * any -> t). term -> t - -Examples: - - compiler.load("APP VAR f VAR x"); - // = ["APP", ["VAR", "f"], ["VAR", "x"]] - - compiler.dump(["APP", ["VAR", "f"], ["VAR", "x"]]); - // = "APP VAR f VAR x" - - compiler.enumerateFresh(0); // = "a" - compiler.enumerateFresh(1); // = "b" - compiler.enumerateFresh(2); // = "c" - - compiler.substitute(name, def, body); - -### Function `syntax.pretty` - -Signature: - - pretty : term -> string - -Examples: - - pretty(["LAMBDA, ["VAR", "x"], ["APP", ["VAR", "f"], ["VAR", "x"]]]); - // = "\ x f x" - -### Module `syntax.tree` - -Signature: - - tree.load : term -> tree node - tree.dump : tree node -> term - getRoot : tree node -> tree node - getLocals : tree node -> Array of strings (variable names) - getFresh : tree node -> string (a variable name) - -Examples: - - tree.load(["VAR", "x"]); - // = {"name": "VAR", "varName": "x", "above": null, "below": []} - - tree.dump({"name": "VAR", "varName": "x", "above": null, "below": []}); - // = ["VAR", "x"] - - var root = tree.getRoot(node); - var varList = tree.getBoundAbove(term); // -> ["a", "b"] - var varSet = tree.getVars(term); // -> {"a": null, "b": null} - var name = tree.getFresh(term); // -> "c" - -### Module `syntax.cursor` - -Signature: - - create : nil -> cursor node - remove : cursor node -> nil - insertAbove : cursor node * tree node -> nil - replaceBelow : cursor node * tree node -> nil - tryMove : cursor node * direction -> bool (direction one of "U" "D" "L" "R") - -Examples: - - var direction = "U"; // or "D", "L", "R" - var success = syntax.cursor.tryMove(cursor, direction); - -### Module `syntax.tokens` - -Signature: - - tokens.isToken : string -> bool - tokens.isKeyword : string -> bool - tokens.isLocal : string -> bool - tokens.isGlobal : string -> bool - tokens.isFreeVariables : string -> object (set of free vars) - -Examples: - - assert(tokens.isToken("a")); - assert(tokens.isKeyword("JOIN")); - assert(tokens.isLocal("a")); - assert(tokens.isGlobal("util.pair")); - - tokens.getFreeVariables("APP VAR unqualified VAR fully.qualified.name"); - // -> {"fully.qualified.name": null} - -## License - -Copyright 2013-2014 Fritz Obermeyer.
-Puddle is licensed under the [MIT license](/LICENSE). diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/index.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/index.js deleted file mode 100644 index dad7b98..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/index.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - compiler: require('./lib/compiler'), - grammar: require('./lib/grammar'), - pretty: require('./lib/pretty'), - tree: require('./lib/tree'), - cursor: require('./lib/cursor'), - tokens: require('./lib/tokens'), -}; diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/TODO.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/TODO.js deleted file mode 100644 index 6ace329..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/TODO.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -/** @constructor */ -var TodoException = function (message) { - this.message = message || '(unfinished code)'; -}; - -TodoException.prototype.toString = function () { - return 'TODO: ' + this.message; -}; - -var TODO = function (message) { - throw new TodoException(message); -}; - -module.exports = TODO; diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/assert.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/assert.js deleted file mode 100644 index 050a81d..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/assert.js +++ /dev/null @@ -1,90 +0,0 @@ -'use strict'; - -var _ = require('underscore'); - -var TOL = 1e-8; - -/** @constructor */ -var AssertException = function (message) { - this.message = message || '(unspecified)'; -}; - -AssertException.prototype.toString = function () { - return 'Assertion Failed: ' + this.message; -}; - -var assert = function (condition, message) { - if (!condition) { - throw new AssertException(message); - } -}; - -assert.Exception = AssertException; - -assert.equal = function (actual, expected, message) { - assert(_.isEqual(actual, expected), - (message || '') + - '\n actual = ' + JSON.stringify(actual) + - '\n expected = ' + JSON.stringify(expected)); -}; - -assert.close = function (x, y, message, tol) { - message = message || 'Not close:'; - tol = tol || TOL; - assert.equal(typeof x, typeof y); - if (_.isNumber(x)) { - assert(Math.abs(x - y) < tol, - message + - '\n actual = ' + JSON.stringify(x) + - '\n expected = ' + JSON.stringify(y)); - } else if (_.isArray(x)) { - assert.equal(x.length, y.length, message + ' lengths differ', tol); - _.each(x, function (actual, pos) { - var expected = y[pos]; - assert.close(actual, expected, message + ' [' + pos + ']', tol); - }); - } else if (_.isObject(x)) { - assert.equal(_.keys(x).sort(), _.keys(y).sort(), ' keys differ', tol); - _.each(x, function (actual, key) { - var expected = y[key]; - assert.close(actual, expected, message + ' [' + key + ']'); - }); - } else { - assert.equal(x, y, message); - } -}; - -assert.forward = function (fwd, pairs) { - pairs.forEach(function (pair, lineno) { - try { - assert.equal(fwd(pair[0]), pair[1]); - } catch (e) { - e.message += '\nforward example ' + (1 + lineno); - throw e; - } - }); -}; - -assert.backward = function (bwd, pairs) { - pairs.forEach(function (pair, lineno) { - try { - assert.equal(bwd(pair[1]), pair[0]); - } catch (e) { - e.message += '\nbackward example ' + (1 + lineno); - throw e; - } - }); -}; - -assert.inverses = function (fwd, bwd, items) { - items.forEach(function (item, lineno) { - try { - assert.equal(bwd(fwd(item)), item); - } catch (e) { - e.message += '\ninverses example ' + (1 + lineno); - throw e; - } - }); -}; - -module.exports = assert; diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/basis.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/basis.js deleted file mode 100644 index 1a9c2e5..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/basis.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; - -var _ = require('underscore'); -var assert = require('./assert'); -var test = require('./test').suite('basis'); - -// this was copied from pomagma/src/language/skrj.json -var basis = { - 'BINARY': { - 'APP': 0.34, - 'COMP': 0.18 - }, - 'NULLARY': { - 'A': 0.0014706292063274857, - 'B': 0.0802936340501919, - 'BOT': 0.01086656170627965, - 'C': 0.05922894396443991, - 'CB': 0.05632735749888164, - 'CI': 0.03825370203150625, - 'I': 0.02374282407764064, - 'J': 0.013014190521849024, - 'K': 0.015184730350639612, - 'P': 0.005439299807496911, - 'R': 0.013014190521849024, - 'S': 0.014747894704886794, - 'TOP': 0.01086656170627965, - 'U': 0.00037168459053155167, - 'V': 0.012334748254411805, - 'W': 0.0027803041848474147, - 'Y': 0.022062742821940824 - }, - 'SYMMETRIC': { - 'JOIN': 0.05, - 'RAND': 0.05 - } -}; - -test('normalized', function () { - var total = 0; - _.each(basis, function (symbols) { - _.each(symbols, function (weight) { - total += weight; - }); - }); - assert.close(total, 1.0); -}); - -module.exports = basis; diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/compiler.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/compiler.js deleted file mode 100644 index 5404ee5..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/compiler.js +++ /dev/null @@ -1,1506 +0,0 @@ -/* jshint unused: false */ -'use strict'; - -var _ = require('underscore'); -var assert = require('./assert'); -var test = require('./test').suite('compiler'); -var TODO = require('./TODO'); -var pattern = require('./pattern'); - -//-------------------------------------------------------------------------- -// Parse - -var parse = (function () { - - var parseSymbol = {}; - - var symbolParser = function (name, arity) { - if (arity === 0) { - return function (parser) { - return name; - }; - } else { - return function (parser) { - var parsed = [name]; - for (var i = 0; i < arity; ++i) { - parsed.push(parser.parse()); - } - return parsed; - }; - } - }; - - var Parser = function (tokens) { - if (_.isString(tokens)) { - tokens = tokens.split(' '); - } - this.tokens = tokens; - this.pos = 0; - }; - - Parser.prototype.pop = function () { - return this.tokens[this.pos++]; - }; - - Parser.prototype.parse = function () { - var head = this.pop(); - var parser = parseSymbol[head]; - assert(parser !== undefined, 'unrecognized token: ' + head); - return parser(this); - }; - - var parse = function (string) { - var parser = new Parser(string); - return parser.parse(); - }; - - parse.declareSymbol = function (name, arity, parser) { - assert( - parseSymbol[name] === undefined, - 'duplicate symbol: ' + name); - parseSymbol[name] = parser || symbolParser(name, arity); - }; - - return parse; -})(); - -var parseLine = function (line) { - var name = line.name; - var body = parse(line.code); - if (name !== null) { - return DEFINE(VAR(name), body); - } else { - return ASSERT(body); - } -}; - -//-------------------------------------------------------------------------- -// Serialize - -var print = (function () { - var pushTokens = function (tokens, expr) { - if (_.isString(expr)) { - tokens.push(expr); - } else { - tokens.push(expr[0]); - for (var i = 1; i < expr.length; ++i) { - pushTokens(tokens, expr[i]); - } - } - }; - return function (expr) { - var tokens = []; - pushTokens(tokens, expr); - return tokens.join(' '); - }; -})(); - -test('parse', function () { - var examples = [ - 'VAR x', - 'QUOTE APP LAMBDA CURSOR VAR x VAR x HOLE', - 'LETREC VAR i LAMBDA VAR x VAR x APP VAR i VAR i', - ]; - assert.inverses(parse, print, examples); -}); - -var printLine = function (term) { - var token = term[0]; - if (token === 'ASSERT') { - return { - token: null, - code: print(term[1]) - }; - } else if (token === 'DEFINE') { - return { - name: term[1][1], - code: print(term[2]) - }; - } -}; - -//-------------------------------------------------------------------------- -// Symbols - -var symbols = {}; -var newSymbol = function (token, arity, parser) { - arity = arity || 0; - parse.declareSymbol(token, arity, parser); - var symbol; - if (arity === 0) { - symbol = token; - } else { - var errorMessage = token + - '(...) called with wrong number of arguments'; - symbol = function () { - assert.equal(arguments.length, arity, errorMessage); - return [token].concat(_.toArray(arguments)); - }; - symbol.token = token; - symbol.arity = arity; - } - symbols[token] = symbol; - return symbol; -}; - -var TOP = newSymbol('TOP'); -var BOT = newSymbol('BOT'); -var I = newSymbol('I'); -var K = newSymbol('K'); -var B = newSymbol('B'); -var C = newSymbol('C'); -var CI = newSymbol('CI'); -var CB = newSymbol('CB'); -var W = newSymbol('W'); -var S = newSymbol('S'); -var Y = newSymbol('Y'); -var U = newSymbol('U'); -var V = newSymbol('V'); -var P = newSymbol('P'); -var A = newSymbol('A'); -var J = newSymbol('J'); -var R = newSymbol('R'); -var QLESS = newSymbol('QLESS'); -var QNLESS = newSymbol('QNLESS'); -var QEQUAL = newSymbol('QEQUAL'); -var HOLE = newSymbol('HOLE'); -var PAREN = newSymbol('PAREN', 1); -var QUOTE = newSymbol('QUOTE', 1); -var CURSOR = newSymbol('CURSOR', 1); -var ASSERT = newSymbol('ASSERT', 1); -var VAR = newSymbol('VAR', 1, function (tokens) { - var name = tokens.pop(); - return ['VAR', name]; -}); -var APP = newSymbol('APP', 2); -var COMP = newSymbol('COMP', 2); -var JOIN = newSymbol('JOIN', 2); -var RAND = newSymbol('RAND', 2); -var LAMBDA = newSymbol('LAMBDA', 2); -var DEFINE = newSymbol('DEFINE', 2); -var STACK = newSymbol('STACK', 2); -var LETREC = newSymbol('LETREC', 3); -var LESS = newSymbol('LESS', 2); -var NLESS = newSymbol('NLESS', 2); -var EQUAL = newSymbol('EQUAL', 2); - -var app = function (term) { - for (var i = 1; i < arguments.length; ++i) { - term = ['APP', term, arguments[i]]; - } - return term; -}; - -test('app', function () { - assert.equal( - app('w', 'x', 'y', 'z'), - APP(APP(APP('w', 'x'), 'y'), 'z')); -}); - -var comp = function (term) { - for (var i = 1; i < arguments.length; ++i) { - term = ['COMP', term, arguments[i]]; - } - return term; -}; - -test('comp', function () { - assert.equal(comp('x', 'y'), COMP('x', 'y')); - assert.equal(comp('x', 'y', 'z'), COMP(COMP('x', 'y'), 'z')); -}); - -var stack = (function () { - var pop = Array.prototype.pop; - return function () { - var tail = pop.call(arguments); - while (arguments.length) { - tail = ['STACK', pop.call(arguments), tail]; - } - return tail; - }; -})(); - -test('stack', function () { - assert.equal( - stack('x', 'y', 'z', []), - STACK('x', STACK('y', STACK('z', [])))); -}); - -//---------------------------------------------------------------------------- -// Fragments - -var fragments = (function () { - - var combinators = {}; - var fragments = { - combinators: combinators, - }; - - _.each(symbols, function (symbol) { - if (_.isString(symbol)) { - combinators[symbol] = symbol; - } - }); - - combinators.APP = APP; - combinators.COMP = COMP; - combinators.JOIN = JOIN; - combinators.RAND = RAND; - combinators.QUOTE = QUOTE; - combinators.VAR = VAR; - - return fragments; -})(); - -//-------------------------------------------------------------------------- -// Conversion : appTree <-> code - -var definitions = {}; -definitions.CI = app(C, I); -definitions.CB = app(C, B); -definitions.U = comp(Y, comp(app(S, B), app(J, app(C, B, I)), app(C, B))); -definitions.V = comp(definitions.U, app(J, I)); -definitions.P = comp(app(B, definitions.V), J); -definitions.A = HOLE; // TODO define - -//-------------------------------------------------------------------------- -// Convert : appTree <-> stack - -var toStack = (function () { - var x = pattern.variable('x'); - var y = pattern.variable('y'); - var tail = pattern.variable('tail'); - var t = pattern.match( - app(x, y), function (match, tail) { - return t(match.x, stack(match.y, tail)); - }, - x, function (match, tail) { - return stack(match.x, tail); - } - ); - var pop = Array.prototype.pop; - var head = 'STACK'; - return function (appTree) { - var tail; - if (arguments.length === 1) { - tail = []; - } else { - tail = pop.call(arguments); - } - while (arguments.length > 1) { - tail = [head, pop.call(arguments), tail]; - } - return t(appTree, tail); - }; -})(); - -var fromStack = (function () { - var x = pattern.variable('x'); - var y = pattern.variable('y'); - var tail = pattern.variable('tail'); - var t = pattern.match( - stack(x, y, tail), function (match) { - return t(stack(app(match.x, match.y), match.tail)); - }, - stack(x, []), function (match) { - return match.x; - } - ); - return t; -})(); - -test('toStack, fromStack', function () { - var x = VAR('x'); - var y = VAR('y'); - var z = VAR('z'); - var examples = [ - [I, stack(I, [])], - [app(x, y), stack(x, y, [])], - [app(x, y, z), stack(x, y, z, [])], - [app(S, app(K, x), y, z), stack(S, app(K, x), y, z, [])] - ]; - assert.forward(toStack, examples); - assert.backward(fromStack, examples); -}); - -//-------------------------------------------------------------------------- -// Simplify : stack -> simple stack -// -// Implements affine-beta-eta-alpha reduction for lambda-letrec terms. - -var fresh = (function () { - var alphabet = 'abcdefghijklmnopqrstuvwxyz'; - var count = 0; - var enumerate = function (i) { - var name = alphabet[i % alphabet.length]; - var number = Math.floor(i / alphabet.length); - if (number > 0) { - name += number; - } - return name; - }; - var fresh = function () { - var name = enumerate(count); - count += 1; - return VAR(name); - }; - fresh.reset = function () { - count = 0; - }; - fresh.enumerate = enumerate; - return fresh; -})(); - -var normalizeAlpha = (function () { - var x = pattern.variable('x'); - var y = pattern.variable('y'); - var z = pattern.variable('z'); - var string = pattern.variable('string'); - var array = pattern.variable('array', _.isArray); - - var renamePattern = pattern.match( - VAR(string), function (match, map) { - assert(!_.has(map, match.string)); - var result = fresh(); - map[match.string] = result[1]; - return result; - }, - array, function (match, map) { - var array = [].concat(match.array); - for (var i = 1; i < array.length; ++i) { - array[i] = renamePattern(array[i], map); - } - return array; - } - ); - - var renameTerm = pattern.match( - VAR(string), function (match, map) { - return VAR(map[match.string] || match.string); - }, - LAMBDA(x, y), function (match, map) { - map = _.extend({}, map); - var x = renamePattern(match.x, map); - var y = renameTerm(match.y, map); - return LAMBDA(x, y); - }, - LETREC(x, y, z), function (match, map) { - map = _.extend({}, map); - var x = renamePattern(match.x, map); - var y = renameTerm(match.y, map); - var z = renameTerm(match.z, map); - return LETREC(x, y, z); - }, - array, function (match, map) { - var array = [].concat(match.array); - for (var i = 1; i < array.length; ++i) { - array[i] = renameTerm(array[i], map); - } - return array; - }, - string, function (match) { - return match.string; - } - ); - - return function (term) { - fresh.reset(); - return renameTerm(term, {}); - }; -})(); - -test('normalizeAlpha', function () { - var a = VAR('a'); - var b = VAR('b'); - var c = VAR('c'); - var x = VAR('x'); - var y = VAR('y'); - var z = VAR('z'); - var examples = [ - [x, x], - [LAMBDA(x, x), LAMBDA(a, a)], - [LETREC(x, y, x), LETREC(a, y, a)], - [app(LAMBDA(x, x), LETREC(x, x, x)), - app(LAMBDA(a, a), LETREC(b, b, b))], - [app(LAMBDA(x, x), x), app(LAMBDA(a, a), x)] - ]; - assert.forward(normalizeAlpha, examples); -}); - -var substitute = (function () { - var string = pattern.variable('string'); - var array = pattern.variable('array', _.isArray); - - var t = pattern.match( - VAR(string), function (match, varName, def) { - return match.string === varName ? def : VAR(match.string); - }, - // TODO take care with binders, - // or ensure variables are globally unique - array, function (match, varName, def) { - var array = [].concat(match.array); - for (var i = 1; i < array.length; ++i) { - array[i] = t(array[i], varName, def); - } - return array; - }, - string, function (match) { - return match.string; - } - ); - - return function (varName, def, body) { - return t(body, varName, def); - }; -})(); - -test('substitute', function () { - var x = VAR('x'); - var y = VAR('y'); - var z = VAR('z'); - var fun = function (args) { - return substitute.apply(null, args); - }; - var examples = [ - [['x', y, z], z], - [['x', y, x], y], - [['x', app(x, y, z), app(x, y, z)], app(app(x, y, z), y, z)], - [['x', z, LAMBDA(y, app(x, y))], LAMBDA(y, app(z, y))] - ]; - assert.forward(fun, examples); -}); - -var simplifyLetrec = (function () { - var x = pattern.variable('x'); - var y = pattern.variable('y'); - var name = pattern.variable('name'); - var array = pattern.variable('array', _.isArray); - var notFound = {}; - - var t = pattern.match( - VAR(name), function (match, varName, def) { - if (match.name !== varName) { - return notFound; - } else if (countOccurrences(varName, def) === 0) { - return def; - } else { - return LETREC(VAR(varName), def, VAR(varName)); - } - }, - APP(x, y), function (match, varName, def) { - var tx = t(match.x, varName, def); - var ty = t(match.y, varName, def); - if (tx === notFound) { - if (ty === notFound) { - // unused - return APP(match.x, match.y); - } else { - // narrow scope - return APP(match.x, ty); - } - } else { - if (ty === notFound) { - // narrow scope - return APP(tx, match.y); - } else { - // no-op - return LETREC(VAR(varName), def, APP(match.x, match.y)); - } - } - }, - x, function (match) { - TODO('handle ' + JSON.stringify(match.x)); - } - ); - - return function (varName, def, body) { - return t(body, varName, def); - }; -})(); - -var countOccurrences = (function () { - var string = pattern.variable('string'); - var array = pattern.variable('array', _.isArray); - - var t = pattern.match( - VAR(string), function (match, varName) { - return match.string === varName ? 1 : 0; - }, - array, function (match, varName) { - var array = match.array; - var result = 0; - for (var i = 1; i < array.length; ++i) { - result += t(array[i], varName); - } - return result; - }, - string, function () { - return 0; - } - ); - - return function (varName, body) { - return t(body, varName); - }; -})(); - -test('countOccurrences', function () { - var x = VAR('x'); - var y = VAR('y'); - var fun = function (args) { - return countOccurrences.apply(null, args); - }; - var examples = [ - [['x', x], 1], - [['x', y], 0], - [['x', I], 0], - [['x', app(y, y)], 0], - [['x', app(x, y)], 1], - [['x', app(x, x, x, y, x)], 4] - ]; - assert.forward(fun, examples); -}); - -var normalizeAffineBetaEta = (function () { - var x = pattern.variable('x'); - var y = pattern.variable('y'); - var z = pattern.variable('z'); - var name = pattern.variable('name'); - var name2 = pattern.variable('name2'); - var tail = pattern.variable('tail'); - var array = pattern.variable('array', _.isArray); - - var normalizeStack = pattern.match( - stack(TOP, tail), function (match) { - return TOP; - }, - stack(BOT, tail), function (match) { - return BOT; - }, - stack(JOIN(TOP, x), tail), function () { - return TOP; - }, - stack(JOIN(x, TOP), tail), function () { - return TOP; - }, - stack(JOIN(BOT, x), tail), function (match) { - return normalizeStack(toStack(match.x, match.tail)); - }, - stack(JOIN(x, BOT), tail), function (match) { - return normalizeStack(toStack(match.x, match.tail)); - }, - stack(LAMBDA(VAR(name), app(x, VAR(name2))), tail), function (match) { - if (match.name === match.name2 && - countOccurrences(match.name, match.x) === 0) - { - return normalizeStack(stack(match.x, match.tail)); - } - }, - stack(LAMBDA(VAR(name), x), VAR(name2), tail), function (match) { - var head = substitute(match.name, VAR(match.name2), match.x); - return normalizeStack(toStack(head, match.tail)); - }, - stack(LAMBDA(VAR(name), x), y, tail), function (match) { - var head; - var tail; - switch (countOccurrences(match.name, match.x)) { - case 0: - return normalizeStack(toStack(match.x, match.tail)); - case 1: - head = substitute(match.name, match.y, match.x); - return normalizeStack(toStack(head, match.tail)); - default: - head = LAMBDA(VAR(match.name), match.x); - tail = normalizeTail(stack(match.y, match.tail)); - return fromStack(stack(head, tail)); - } - }, - // TODO implement LETREC simplification - //stack(LETREC(VAR(name), x, y), tail), function (match) { - // var head = normalizeLetrec(match.name. match.x, match.y); - // var tail = normalizeTail(tail); - // return fromStack(stack(head, tail)); - //}, - stack(x, tail), function (match) { - return fromStack(stack(match.x, normalizeTail(match.tail))); - } - ); - - var normalizeTail = pattern.match( - [], function () { - return []; - }, - stack(x, y), function (match) { - var tx = normalize(match.x); - var ty = normalizeTail(match.y); - return stack(tx, ty); - } - ); - - var normalize = function (term) { - return normalizeStack(toStack(term)); - }; - - return normalize; -})(); - -test('normalizeAffineBetaEta', function () { - var x = VAR('x'); - var y = VAR('y'); - var z = VAR('z'); - var xx = app(x, x); - var yy = app(y, y); - var yz = app(y, z); - var examples = [ - [x, x], - [app(TOP, x, y, z), TOP], - [app(BOT, x, y, z), BOT], - [JOIN(TOP, x), TOP], - [JOIN(x, TOP), TOP], - [JOIN(BOT, x), x], - [JOIN(x, BOT), x], - [app(LAMBDA(x, app(y, x)), app(y, z)), app(y, app(y, z))], - [app(LAMBDA(x, app(x, x)), y), app(y, y)], - [app(LAMBDA(x, app(x, x)), app(y, z)), - app(LAMBDA(x, app(x, x)), app(y, z))], - [LAMBDA(x, app(y, x)), y], - [LAMBDA(x, app(x, x)), LAMBDA(x, app(x, x))], - [HOLE, HOLE], - [app(HOLE, x, app(TOP, y)), app(HOLE, x, TOP)] - ]; - assert.forward(normalizeAffineBetaEta, examples); -}); - -var simplify = function (term) { - term = normalizeAffineBetaEta(term); - term = normalizeAlpha(term); - return term; -}; - -//-------------------------------------------------------------------------- -// Convert : simple appTree -> lambda - -var lambdaSymbols = (function () { - var subset = [ - 'HOLE', 'TOP', 'BOT', - 'I', //'K', 'B', 'C', 'W', 'S', 'Y', 'U', 'V', 'P', 'A', 'J', 'R', - 'VAR', 'APP', 'COMP', 'LAMBDA', 'LETREC', 'JOIN', 'RAND', - 'QUOTE', 'QLESS', 'QNLESS', 'QEQUAL', 'LESS', 'NLESS', 'EQUAL', - 'ASSERT', 'DEFINE', 'CURSOR', - ]; - var lambdaSymbols = {}; - subset.forEach(function (name) { - lambdaSymbols[name] = symbols[name]; - }); - return lambdaSymbols; -})(); - -var decompile = (function () { - - var x = pattern.variable('x'); - var y = pattern.variable('y'); - var z = pattern.variable('z'); - var head = pattern.variable('head'); - var tail = pattern.variable('tail'); - var name = pattern.variable('name'); - var array = pattern.variable('array', _.isArray); - var atom = pattern.variable('atom', function (struct) { - return _.isString(struct) && _.has(definitions, struct); - }); - - var ensureVar = function (v, handler) { - var name = v.name; - return function (match) { - var tail = match.tail; - if (tail.length === 0) { - var v = fresh(); - match[name] = v; - return LAMBDA(v, handler(match)); - } else { - match[name] = decompile(tail[1]); // FIXME, - // incorrect decompile call - match.tail = tail[2]; - return handler(match); - } - }; - }; - - var ensure = function () { - var pop = Array.prototype.pop; - var handler = pop.call(arguments); - while (arguments.length) { - handler = ensureVar(pop.call(arguments), handler); - } - return function (match) { - var head = handler(match); - var tail = decompileTail(match.tail); - return fromStack(stack(head, tail)); - }; - }; - - var decompileStack = pattern.match( - stack(COMP(x, y), tail), function (match) { - return decompileStack(stack(B, match.x, match.y, match.tail)); - }, - stack(HOLE, tail), function (match) { - return fromStack(stack(HOLE, decompileTail(match.tail))); - }, - stack(TOP, tail), function () { - return TOP; - }, - stack(BOT, tail), function () { - return BOT; - }, - // TODO fix ensure() to simplify cases - //stack(K, tail), ensure(x, y, function (match) { - // return match.x; - //}), - //stack(C, I, tail), ensure(x, y, function (match) { - // return app(match.y, match.x); - //}), - //stack(B, tail), ensure(x, y, z, function (match) { - // return app(match.x, app(match.y, match.z)); - //}), - //stack(C, tail), ensure(x, y, z, function (match) { - // return app(match.x, match.z, match.y); - //}), - stack(I, []), function (match) { - var x = fresh(); - return LAMBDA(x, x); - }, - stack(I, x, tail), function (match) { - return decompileStack(toStack(match.x, match.tail)); - }, - stack(K, []), function (match) { - var x = fresh(); - var y = fresh(); - return LAMBDA(x, LAMBDA(y, x)); - }, - stack(K, x, []), function (match) { - var y = fresh(); - var tx = decompileStack(toStack(match.x)); - return LAMBDA(y, tx); - }, - stack(K, x, y, tail), function (match) { - return decompileStack(toStack(match.x, match.tail)); - }, - stack(B, []), function () { - var x = fresh(); - var y = fresh(); - var z = fresh(); - return LAMBDA(x, LAMBDA(y, LAMBDA(z, app(x, app(y, z))))); - }, - stack(B, x, []), function (match) { - var y = fresh(); - var z = fresh(); - var xyz = decompileStack(toStack(match.x, app(y, z), [])); - return LAMBDA(y, LAMBDA(z, xyz)); - }, - stack(B, x, y, []), function (match) { - var z = fresh(); - var xyz = decompileStack(toStack(match.x, app(match.y, z), [])); - return LAMBDA(z, xyz); - }, - stack(B, x, y, z, tail), function (match) { - return decompileStack( - toStack(match.x, app(match.y, match.z), match.tail)); - }, - stack(C, []), function () { - var x = fresh(); - var y = fresh(); - var z = fresh(); - return LAMBDA(x, LAMBDA(y, LAMBDA(z, app(x, z, y)))); - }, - stack(C, x, []), function (match) { - var y = fresh(); - var z = fresh(); - var xzy = decompileStack(toStack(match.x, z, y, [])); - return LAMBDA(y, LAMBDA(z, xzy)); - }, - stack(C, x, y, []), function (match) { - var z = fresh(); - var xzy = decompileStack(toStack(match.x, z, match.y, [])); - return LAMBDA(z, xzy); - }, - stack(C, x, y, z, tail), function (match) { - return decompileStack( - toStack(match.x, match.z, match.y, match.tail)); - }, - stack(W, []), function () { - var x = fresh(); - var y = fresh(); - return LAMBDA(x, LAMBDA(y, app(x, y, y))); - }, - stack(W, x, []), function (match) { - var y = fresh(); - var tx = decompile(match.x); - return LAMBDA(y, app(tx, y, y)); - }, - stack(W, x, VAR(name), tail), function (match) { - var y = VAR(match.name); - var head = decompile(app(match.x, y, y)); - var tail = decompileTail(match.tail); - return fromStack(stack(head, tail)); - }, - stack(W, x, y, tail), function (match) { - var y = fresh(); - var ty = decompile(match.y); - var head = LETREC(y, ty, decompile(app(match.x, y, y))); - var tail = decompileTail(match.tail); - return fromStack(stack(head, tail)); - }, - stack(S, []), function () { - var x = fresh(); - var y = fresh(); - var z = fresh(); - return LAMBDA(x, LAMBDA(y, LAMBDA(z, app(x, z, app(y, z))))); - }, - stack(S, x, []), function (match) { - var y = fresh(); - var z = fresh(); - var tx = decompile(match.x); - return LAMBDA(y, LAMBDA(z, app(tx, z, app(y, z)))); - }, - stack(S, x, y, []), function (match) { - var z = fresh(); - var tx = decompile(match.x); - var ty = decompile(match.y); - return LAMBDA(z, app(tx, z, app(ty, z))); - }, - stack(S, x, y, VAR(name), tail), function (match) { - var z = VAR(match.name); - var head = decompile(app(match.x, z, app(match.y, z))); - var tail = decompileTail(match.tail); - return fromStack(stack(head, tail)); - }, - stack(S, x, y, z, tail), function (match) { - var z = fresh(); - var tz = decompile(match.z); - var xz = app(match.x, z); - var yz = app(match.y, z); - var head = LETREC(z, tz, decompile(app(xz, yz))); - var tail = decompileTail(match.tail); - return fromStack(stack(head, tail)); - }, - stack(Y, tail), ensure(x, function (match) { - var y = fresh(); - var z = fresh(); - return LETREC(y, LAMBDA(z, app(match.x, app(y, z))), y); - }), - stack(J, tail), ensure(x, y, function (match) { - return JOIN(match.x, match.y); - }), - stack(R, tail), ensure(x, y, function (match) { - return RAND(match.x, match.y); - }), - // TODO reimplement via ensureQuoted - stack(QLESS, []), function (match) { - var x = fresh(); - var y = fresh(); - return LAMBDA(QUOTE(x), LAMBDA(QUOTE(y), LESS(x, y))); - }, - stack(QLESS, QUOTE(x), []), function (match) { - var y = fresh(); - return LAMBDA(QUOTE(y), LESS(match.x, y)); - }, - stack(QLESS, x, []), function (match) { - var x = fresh(); - var y = fresh(); - return LETREC(QUOTE(x), match.x, LAMBDA(QUOTE(y), LESS(x, y))); - }, - // ... other cases omitted: (QUOTE(x), y); (x, QUOTE(y)) - stack(QLESS, QUOTE(x), QUOTE(y), tail), function (match) { - var head = LESS(match.x, match.y); - var tail = decompileTail(match.tail); - return fromStack(stack(head, tail)); - }, - stack(QLESS, x, y, tail), function (match) { - var x = fresh(); - var y = fresh(); - var head = - LETREC(QUOTE(x), match.x, - LETREC(QUOTE(y), match.y, LESS(x, y))); - var tail = decompileTail(match.tail); - return fromStack(stack(head, tail)); - }, - stack(QNLESS, QUOTE(x), QUOTE(y), tail), function (match) { - var head = NLESS(match.x, match.y); - var tail = decompileTail(match.tail); - return fromStack(stack(head, tail)); - }, - stack(QEQUAL, QUOTE(x), QUOTE(y), tail), function (match) { - var head = EQUAL(match.x, match.y); - var tail = decompileTail(match.tail); - return fromStack(stack(head, tail)); - }, - stack(VAR(name), tail), function (match) { - var head = VAR(match.name); - var tail = decompileTail(match.tail); - return fromStack(stack(head, tail)); - }, - stack(atom, tail), function (match) { - var head = definitions[match.atom]; - return decompileStack(toStack(head, match.tail)); - }, - stack(array, tail), function (match) { - var head = match.array; - assert(_.isString(head[0])); - head = [].concat(head); - for (var i = 1; i < head.length; ++i) { - head[i] = decompile(head[i]); - } - var tail = decompileTail(match.tail); - return fromStack(stack(head, tail)); - } - ); - - var decompileTail = pattern.match( - [], function () { - return []; - }, - stack(x, y), function (match) { - var tx = decompile(match.x); - var ty = decompileTail(match.y); - return stack(tx, ty); - } - ); - - var decompile = function (code) { - return decompileStack(toStack(code)); - }; - - return function (code) { - fresh.reset(); - var term = decompile(code); - term = simplify(term); - return term; - }; -})(); - -//-------------------------------------------------------------------------- -// Abstract : varName -> simple appTree -> simple appTree - -var tryAbstract = (function () { - var x = pattern.variable('x'); - var y = pattern.variable('y'); - var name = pattern.variable('name'); - var notFound = {}; - - var t = pattern.match( - VAR(name), function (match, varName) { - if (match.name !== varName) { - return notFound; - } else { - return I; - } - }, - APP(x, VAR(name)), function (match, varName) { - var tx = t(match.x, varName); - if (tx === notFound) { - if (match.name !== varName) { - return notFound; - } else { - return match.x; - } - } else { - if (match.name !== varName) { - return app(C, tx, VAR(match.name)); - } else { - return app(W, tx); - } - } - }, - APP(x, y), function (match, varName) { - var tx = t(match.x, varName); - var ty = t(match.y, varName); - if (tx === notFound) { - if (ty === notFound) { - return notFound; - } else { - return comp(match.x, ty); - } - } else { - if (ty === notFound) { - return app(C, tx, match.y); - } else { - return app(S, tx, ty); - } - } - }, - COMP(x, y), function (match, varName) { - var tx = t(match.x, varName); - var ty = t(match.y, varName); - if (tx === notFound) { - if (ty === notFound) { - return notFound; - } else { - if (_.isEqual(match.y, VAR(varName))) { - return app(B, match.x); - } else { - return comp(app(B, match.x), ty); - } - } - } else { - if (ty === notFound) { - return comp(app(CB, match.y), tx); - } else { - return app(S, app(B, tx), ty); - } - } - }, - JOIN(x, y), function (match, varName) { - var tx = t(match.x, varName); - var ty = t(match.y, varName); - if (tx === notFound) { - if (ty === notFound) { - return notFound; - } else { - // this hack will be obsoleted by simplifyLambda - if (ty === I) { // HACK - return app(J, match.x); // HACK - } else { // HACK - return comp(app(J, match.x), ty); - } // HACK - } - } else { - if (ty === notFound) { - return comp(app(J, match.y), tx); - } else { - return JOIN(tx, ty); - } - } - }, - RAND(x, y), function (match, varName) { - var tx = t(match.x, varName); - var ty = t(match.y, varName); - if (tx === notFound) { - if (ty === notFound) { - return notFound; - } else { - return comp(app(R, match.x), ty); - } - } else { - if (ty === notFound) { - return comp(app(R, match.y), tx); - } else { - return RAND(tx, ty); - } - } - }, - QUOTE(x), function (match, varName) { - var tx = t(match.x, varName); - if (tx === notFound) { - return notFound; - } else { - TODO('implement quoted tryAbstraction'); - } - }, - LESS(x, y), function (match, varName) { - var tx = t(match.x, varName); - var ty = t(match.y, varName); - if (tx === notFound && ty === notFound) { - return notFound; - } else { - TODO('implement quoted tryAbstraction'); - } - }, - NLESS(x, y), function (match, varName) { - var tx = t(match.x, varName); - var ty = t(match.y, varName); - if (tx === notFound && ty === notFound) { - return notFound; - } else { - TODO('implement quoted tryAbstraction'); - } - }, - EQUAL(x, y), function (match, varName) { - var tx = t(match.x, varName); - var ty = t(match.y, varName); - if (tx === notFound && ty === notFound) { - return notFound; - } else { - TODO('implement quoted tryAbstraction'); - } - }, - x, function () { - return notFound; - } - ); - - t.notFound = notFound; - - return t; -})(); - -var compileLambda = function (varName, body) { - var result = tryAbstract(body, varName); - if (result === tryAbstract.notFound) { - return app(K, body); - } else { - return result; - } -}; - -test('compileLambda', function () { - var a = VAR('a'); - var x = VAR('x'); - var y = VAR('y'); - var lambdaA = _.partial(compileLambda, 'a'); - var examples = [ - [a, I], - [app(x, a), x], - [app(x, a, a), app(W, x)], - [app(y, app(x, a)), COMP(y, x)], - [app(x, a, y), app(C, x, y)], - [app(x, a, app(x, a)), app(S, x, x)], - [x, app(K, x)] - ]; - assert.forward(lambdaA, examples); -}); - -var compileLetrec = function (varName, def, body) { - var bodyResult = tryAbstract(body, varName); - if (bodyResult === tryAbstract.notFound) { - return body; - } else { - var defResult = tryAbstract(def, varName); - if (defResult === tryAbstract.notFound) { - return app(bodyResult, def); - } else { - return app(bodyResult, app(Y, defResult)); - } - } -}; - -test('compileLetrec', function () { - var a = VAR('a'); - var x = VAR('x'); - var y = VAR('y'); - var letrecA = function (pair) { - var def = pair[0]; - var body = pair[1]; - return compileLetrec('a', def, body); - }; - var examples = [ - [['bomb', x], x], - [[I, app(x, a)], app(x, I)], - [[a, app(x, a)], app(x, app(Y, I))], - [[app(y, a), app(x, a)], app(x, app(Y, y))] - ]; - assert.forward(letrecA, examples); -}); - -var compile = (function () { - var x = pattern.variable('x'); - var y = pattern.variable('y'); - var z = pattern.variable('z'); - var name = pattern.variable('name'); - - var t = pattern.match( - VAR(name), function (match) { - return VAR(match.name); - }, - LAMBDA(VAR(name), x), function (match) { - return compileLambda(match.name, t(match.x)); - }, - LETREC(VAR(name), x, y), function (match) { - return compileLetrec(match.name, t(match.x), t(match.y)); - }, - x, function (match) { - var x = match.x; - if (_.isString(x)) { - return x; - } else { - assert(_.isArray(x), x); - var result = [x[0]]; - for (var i = 1; i < x.length; ++i) { - result.push(t(x[i])); - } - return result; - } - } - ); - - return t; -})(); - -test('decompile, compile', function () { - var a = VAR('a'); - var b = VAR('b'); - var c = VAR('c'); - var x = VAR('x'); - var y = VAR('y'); - var z = VAR('z'); - var xy = app(x, y); // just something that is not a variable - var examples = [ - [COMP(x, y), LAMBDA(a, app(x, app(y, a)))], - [TOP, TOP], - [BOT, BOT], - [I, LAMBDA(a, a)], - [K, LAMBDA(a, LAMBDA(b, a))], - [app(K, x), LAMBDA(a, x)], - [app(C, I), LAMBDA(a, LAMBDA(b, app(b, a)))], - [app(C, I, x), LAMBDA(a, app(a, x))], - [B, LAMBDA(a, LAMBDA(b, LAMBDA(c, app(a, app(b, c)))))], - [app(B, x), LAMBDA(a, LAMBDA(b, app(x, app(a, b))))], - [C, LAMBDA(a, LAMBDA(b, LAMBDA(c, app(a, c, b))))], - [app(C, x), LAMBDA(a, LAMBDA(b, app(x, b, a)))], - [app(C, x, y), LAMBDA(a, app(x, a, y))], - [W, LAMBDA(a, LAMBDA(b, app(a, b, b)))], - [app(W, x), LAMBDA(a, app(x, a, a))], - [app(W, x, xy), LETREC(a, xy, app(x, a, a))], - [S, LAMBDA(a, LAMBDA(b, LAMBDA(c, app(a, c, app(b, c)))))], - [app(S, x), LAMBDA(a, LAMBDA(b, app(x, b, app(a, b))))], - [app(S, x, y), LAMBDA(a, app(x, a, app(y, a)))], - [app(S, x, y, xy), LETREC(a, xy, app(x, a, app(y, a)))], - [J, LAMBDA(a, LAMBDA(b, JOIN(a, b)))], - [app(J, x), LAMBDA(a, JOIN(x, a))], - // TODO add these after simplifyLambda works - //[app(J, x, y), JOIN(x, y)], - //[app(J, x, y, I), app(JOIN(x, y), LAMBDA(a, a))], - //[R, LAMBDA(a, LAMBDA(b, RAND(a, b)))], - //[app(R, x), LAMBDA(a, RAND(x, a))], - //[app(R, x, y), RAND(x, y)], - //[app(R, x, y, I), app(RAND(x, y), LAMBDA(a, a))], - [QUOTE(K), QUOTE(LAMBDA(a, LAMBDA(b, a)))], - [app(QUOTE(x), K), app(QUOTE(x), LAMBDA(a, LAMBDA(b, a)))], - [LESS(x, y), LESS(x, y)], - [NLESS(x, y), NLESS(x, y)], - [EQUAL(x, y), EQUAL(x, y)], - [VAR(x), VAR(x)], - [app(VAR(x), K), app(VAR(x), LAMBDA(a, LAMBDA(b, a)))], - [HOLE, HOLE] - ]; - assert.forward(decompile, examples); - assert.backward(compile, examples); -}); - -test('decompile', function () { - // compile would fail these because they involve pattern matching - var a = VAR('a'); - var b = VAR('b'); - var c = VAR('c'); - var x = VAR('x'); - var y = VAR('y'); - var z = VAR('z'); - var xy = app(x, y); // just something that is not a variable - var k = LAMBDA(a, LAMBDA(b, a)); - var examples = [ - [app(B, x, y), LAMBDA(a, app(x, app(y, a)))], - [app(C, x, y, z), app(x, z, y)], - [comp(C, app(C, I)), LAMBDA(a, LAMBDA(b, LAMBDA(c, app(c, a, b))))], - [app(W, x, y), app(x, y, y)], - [app(W, x, y, K), app(x, y, y, k)], - [app(S, x, y, z), app(x, z, app(y, z))], - [app(S, x, y, z, K), app(x, z, app(y, z), k)], - [Y, LAMBDA(a, LETREC(b, LAMBDA(c, app(a, app(b, c))), b))], - [app(QLESS, QUOTE(x), QUOTE(y)), LESS(x, y)], - [app(QNLESS, QUOTE(x), QUOTE(y)), NLESS(x, y)], - [app(QEQUAL, QUOTE(x), QUOTE(y)), EQUAL(x, y)], - // TODO move this back above - [app(J, x, y), JOIN(x, y)], - [app(J, x, y, K), app(JOIN(x, y), k)], - [HOLE, HOLE] - ]; - assert.forward(decompile, examples); -}); - -test('compile', function () { - // decompile would fail these because input is not simple - var a = VAR('a'); - var x = VAR('x'); - var y = VAR('y'); - var examples = [ - [LAMBDA(a, app(x, app(y, a))), COMP(x, y)], - [app(LAMBDA(a, a), x), app(I, x)], - [HOLE, HOLE] - ]; - assert.forward(compile, examples); -}); - -//-------------------------------------------------------------------------- -// Parenthesize - -var parenthesize = (function () { - - var x = pattern.variable('x'); - var y = pattern.variable('y'); - var z = pattern.variable('z'); - var name = pattern.variable('name'); - var atom = pattern.variable('atom', _.isString); - - var tPatt = pattern.match( - VAR(name), function (match) { - return VAR(match.name); - }, - QUOTE(x), function (match) { - return QUOTE(tPatt(match.x)); - }, - CURSOR(x), function (match) { - return CURSOR(tPatt(match.x)); - } - ); - - var tAtom = pattern.match( - atom, function (match) { - return match.atom; - }, - VAR(name), function (match) { - return VAR(match.name); - }, - RAND(x, y), function (match) { - return RAND(tInline(match.x), tInline(match.y)); - }, - QUOTE(x), function (match) { - return QUOTE(tInline(match.x)); - }, - LESS(x, y), function (match) { - return LESS(tJoin(match.x), tJoin(match.y)); - }, - NLESS(x, y), function (match) { - return NLESS(tJoin(match.x), tJoin(match.y)); - }, - EQUAL(x, y), function (match) { - return EQUAL(tJoin(match.x), tJoin(match.y)); - }, - CURSOR(x), function (match) { - return CURSOR(tAtom(match.x)); - }, - x, function (match) { - return PAREN(tInline(match.x)); - } - ); - - var tComp = pattern.match( - COMP(x, y), function (match) { - return COMP(tComp(match.x), tComp(match.y)); - }, - CURSOR(x), function (match) { - return CURSOR(tComp(match.x)); - }, - x, function (match) { - return tAtom(match.x); - } - ); - - var tApp = pattern.match( - APP(x, y), function (match) { - return APP(tApp(match.x), tAtom(match.y)); - }, - CURSOR(x), function (match) { - return CURSOR(tApp(match.x)); - }, - x, function (match) { - return tComp(match.x); - } - ); - - var tJoin = pattern.match( - JOIN(x, y), function (match) { - return JOIN(tJoin(match.x), tJoin(match.y)); - }, - CURSOR(x), function (match) { - return CURSOR(tJoin(match.x)); - }, - x, function (match) { - return tApp(match.x); - } - ); - - var tInline = pattern.match( - LAMBDA(x, y), function (match) { - return LAMBDA(tPatt(match.x), tInline(match.y)); - }, - LETREC(x, y, z), function (match) { - return LETREC( - tPatt(match.x), - tJoin(match.y), - tInline(match.z)); - }, - CURSOR(x), function (match) { - return CURSOR(tInline(match.x)); - }, - x, function (match) { - return tJoin(match.x); - } - ); - - var t = pattern.match( - DEFINE(x, y), function (match) { - return DEFINE(tAtom(match.x), tJoin(match.y)); - }, - ASSERT(x), function (match) { - return ASSERT(tJoin(match.x)); - }, - CURSOR(x), function (match) { - return CURSOR(t(match.x)); - }, - x, function (match) { - return tInline(match.x); - } - ); - - return t; -})(); - -//------------------------------------------------------------------------ -// Folding - -var fold = function (fun) { - var array = pattern.variable('array', _.isArray); - var string = pattern.variable('string', _.isString); - - var t = pattern.match( - VAR(string), function (match) { - return fun('VAR', match.string); - }, - array, function (match) { - var array = match.array; - var args = []; - for (var i = 1; i < array.length; ++i) { - args.push(t(array[i])); - } - return fun(array[0], args); - }, - string, function (match) { - return fun(match.string, []); - } - ); - - return t; -}; - -test('fold', function () { - // TODO add tests -}); - -//------------------------------------------------------------------------ - -module.exports = { - fragments: fragments, - symbols: lambdaSymbols, - load: function (string) { - var code = parse(string); - var term = decompile(code); - return term; - }, - loadLine: function (line) { - var code = parseLine(line); - var term = decompile(code); - return term; - }, - dumpLine: function (term) { - var code = compile(term); - var line = printLine(code); - return line; - }, - dump: function (term) { - var code = compile(term); - return print(code); - }, - print: print, - enumerateFresh: fresh.enumerate, - substitute: substitute, - parenthesize: parenthesize, - fold: fold, -}; diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/cursor.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/cursor.js deleted file mode 100644 index 319dab7..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/cursor.js +++ /dev/null @@ -1,174 +0,0 @@ -'use strict'; - -var _ = require('underscore'); -var assert = require('assert'); - -var create = function () { - return { - name: 'CURSOR', - below: [undefined], - above: null - }; -}; - -var remove = function (cursor) { - var above = cursor.above; - cursor.below[0].above = above; - if (above) { - var pos = above.below.indexOf(cursor); - above.below[pos] = cursor.below[0]; - return pos; - } - cursor.below[0] = undefined; - cursor.above = null; -}; - -var insertBelow = function (cursor, above, pos) { - var below = above.below[pos]; - above.below[pos] = cursor; - cursor.above = above; - below.above = cursor; - cursor.below[0] = below; -}; - -var insertAbove = function (cursor, below) { - cursor.below[0] = below; - var above = below.above; - below.above = cursor; - cursor.above = above; - if (above !== null) { - var pos = above.below.indexOf(below); - above.below[pos] = cursor; - } -}; - -var replaceBelow = (function () { - var findCursor = function (term) { - if (term.name === 'CURSOR') { - return term; - } else { - for (var i = 0; i < term.below.length; ++i) { - var cursor = findCursor(term.below[i]); - if (cursor !== undefined) { - return cursor; - } - } - } - }; - return function (oldCursor, newTerm) { - var newCursor = findCursor(newTerm); - if (newCursor === undefined) { - newCursor = create(); - insertAbove(newCursor, newTerm); - newTerm = newCursor; - } - var above = oldCursor.above; - assert(above !== null, 'tried to replace with cursor at root'); - var pos = remove(oldCursor); - above.below[pos] = newTerm; - newTerm.above = above; - return newCursor; - }; -})(); - -var tryMove = (function () { - - var traverseDownLeft = function (node) { - while (node.below.length) { - node = _.first(node.below); - } - return node; - }; - - var traverseDownRight = function (node) { - while (node.below.length) { - node = _.last(node.below); - } - return node; - }; - - var traverseLeftDown = function (node) { - var above = node.above; - while (above !== null) { - var pos = _.indexOf(above.below, node); - assert(pos >= 0, 'node not found in node.above.below'); - if (pos > 0) { - return traverseDownRight(above.below[pos - 1]); - } - node = above; - above = node.above; - } - return traverseDownRight(node); - }; - - var traverseRightDown = function (node) { - var above = node.above; - while (above !== null) { - var pos = _.indexOf(above.below, node); - assert(pos >= 0, 'node not found in node.above.below'); - if (pos < above.below.length - 1) { - return traverseDownLeft(above.below[pos + 1]); - } - node = above; - above = node.above; - } - return traverseDownLeft(node); - }; - - var tryMoveLeft = function (cursor) { - var node = cursor.below[0]; - remove(cursor); - var next = traverseLeftDown(node); - insertAbove(cursor, next); - return true; - }; - - var tryMoveRight = function (cursor) { - var node = cursor.below[0]; - remove(cursor); - var next = traverseRightDown(node); - insertAbove(cursor, next); - return true; - }; - - var tryMoveUp = function (cursor) { - if (cursor.above !== null) { - var pivot = cursor.above; - remove(cursor); - insertAbove(cursor, pivot); - return true; - } else { - return false; - } - }; - - var tryMoveDown = function (cursor) { - var pivot = cursor.below[0]; - if (pivot.below.length > 0) { - remove(cursor); - insertBelow(cursor, pivot, 0); - return true; - } else { - return false; - } - }; - - var directions = { - U: tryMoveUp, - L: tryMoveLeft, - D: tryMoveDown, - R: tryMoveRight - }; - - return function (cursor, direction) { - return directions[direction](cursor); - }; -})(); - -module.exports = { - create: create, - remove: remove, - insertAbove: insertAbove, - replaceBelow: replaceBelow, - tryMove: tryMove, -}; diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/grammar.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/grammar.js deleted file mode 100644 index 7e8f717..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/grammar.js +++ /dev/null @@ -1,56 +0,0 @@ -'use strict'; - -var _ = require('underscore'); -var stats = require('./stats'); -var compiler = require('./compiler'); -var basis = require('./basis'); - -var atoms = []; -var symbols = []; -var atomProbs = []; -var symbolProbs = []; - -_.each(basis, function (symbolsOfArity, arity) { - _.each(symbolsOfArity, function (prob, token) { - var symbol = compiler.fragments.combinators[token]; - symbols.push(symbol); - symbolProbs.push(prob); - if (arity === 'NULLARY') { - atoms.push(symbol); - atomProbs.push(prob); - } - }); -}); - -stats.normalize(atomProbs); -stats.normalize(symbolProbs); - -var sampleTerm = function (maxDepth) { - maxDepth = maxDepth || 5; - var symbol; - if (maxDepth > 1) { - symbol = stats.sampleDiscrete(symbols, symbolProbs); - } else { - symbol = stats.sampleDiscrete(atoms, atomProbs); - } - if (_.isString(symbol)) { - return symbol; - } else { - var args = []; - for (var i = 0; i < symbol.arity; ++i) { - args.push(sampleTerm(maxDepth - 1)); - } - return symbol.apply(null, args); - } -}; - -var sampleCode = _.compose( - compiler.dump, - compiler.load, compiler.dump, // HACK simplify - compiler.load, compiler.dump, // HACK simplify - sampleTerm); - -module.exports = { - sampleCode: sampleCode, - sampleTerm: sampleTerm, -}; diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/map-assert.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/map-assert.js deleted file mode 100644 index c1be63f..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/map-assert.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; - -var assert = require('assert'); -var _ = require('underscore'); - -exports.forward = function (fwd, pairs) { - assert(_.isFunction(fwd), 'Expected fwd function, actual: ' + fwd); - assert(_.isArray(pairs), 'Expected pairs array, actual: ' + pairs); - pairs.forEach(function (pair, lineno) { - try { - assert.equal(fwd(pair[0]), pair[1]); - } catch (err) { - err.message += '\nin forward example ' + (1 + lineno); - throw err; - } - }); -}; - -exports.backward = function (bwd, pairs) { - assert(_.isFunction(bwd), 'Expected bwd function, actual: ' + bwd); - assert(_.isArray(pairs), 'Expected pairs array, actual: ' + pairs); - pairs.forEach(function (pair, lineno) { - try { - assert.equal(bwd(pair[1]), pair[0]); - } catch (err) { - err.message += '\nin backward example ' + (1 + lineno); - throw err; - } - }); -}; - -exports.inverses = function (fwd, bwd, items) { - assert(_.isFunction(fwd), 'Expected fwd function, actual: ' + fwd); - assert(_.isFunction(bwd), 'Expected bwd function, actual: ' + bwd); - assert(_.isArray(items), 'Expected items array, actual: ' + items); - items.forEach(function (item, lineno) { - try { - assert.equal(bwd(fwd(item)), item); - } catch (err) { - err.message += '\nin inverses example ' + (1 + lineno); - throw err; - } - }); -}; diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/pattern.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/pattern.js deleted file mode 100644 index 4317e0d..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/pattern.js +++ /dev/null @@ -1,193 +0,0 @@ -/* jshint unused: false */ -'use strict'; - -var _ = require('underscore'); -var assert = require('./assert'); -var test = require('./test').suite('pattern'); - -/** - * @constructor - * @param {string} - * @param {(function (?): boolean)=} - */ -var Variable = function Variable (name, constraint) { - this.name = name; - this.constraint = (constraint !== undefined) ? constraint : null; -}; - -Variable.prototype.toString = function () { - return 'Variable(' + this.name + ')'; -}; - -var variable = function (name, constraint) { - return new Variable(name, constraint); -}; - -var isVariable = function (thing) { - return !!(thing && thing.constructor === Variable); -}; - -test('isVariable', function () { - var examples = [ - [variable('a'), true], - ['asdf', false], - [{}, false], - [[], false], - [undefined, false] - ]; - examples.forEach(function (pair) { - var thing = pair[0]; - assert(isVariable(thing) === pair[1], 'isVariable failed on ' + thing); - }); -}); - -var isPattern = (function () { - var isPattern = function (patt, avoid) { - if (isVariable(patt)) { - if (_.has(avoid, patt.name)) { - return false; - } else { - avoid[patt.name] = null; - return true; - } - } else if (_.isString(patt)) { - return true; - } else if (_.isArray(patt)) { - for (var i = 0; i < patt.length; ++i) { - if (!isPattern(patt[i], avoid)) { - return false; - } - } - return true; - } else { - return false; - } - }; - return function (patt) { - return isPattern(patt, {}); - }; -})(); - -test('isPattern', function () { - var examples = [ - [variable('a'), true], - ['asdf', true], - [{}, false], - [[['asdf', variable('x')], variable('y')], true], - [[variable('x'), variable('x')], false], - [undefined, false] - ]; - assert.forward(isPattern, examples); -}); - -var unify = function (patt, struct, matched, allowBacktracking) { - if (isVariable(patt)) { - if (patt.constraint === null || patt.constraint(struct)) { - if (allowBacktracking) { - matched = _.extend({}, matched); - } - matched[patt.name] = struct; - return matched; - } - } else if (_.isArray(patt) && _.isArray(struct)) { - if (patt.length === struct.length) { - for (var i = 0; i < struct.length; ++i) { - matched = unify(patt[i], struct[i], matched); - if (matched === undefined) { - return; - } - } - return matched; - } - } else if (patt === struct) { - return matched; - } -}; - -var match = function () { - // check statically - assert(arguments.length % 2 === 0, 'bad pattern,handler list'); - var lineCount = arguments.length / 2; - var patts = []; - var handlers = []; - for (var line = 0; line < lineCount; ++line) { - var patt = arguments[2 * line]; - var handler = arguments[2 * line + 1]; - assert( - isPattern(patt), - 'bad pattern at line ' + line + ':\n ' + patt); - assert(_.isFunction(handler), 'bad handler at line ' + line); - patts.push(patt); - handlers.push(handler); - } - // run optimized - var slice = Array.prototype.slice; - return function (struct) { - for (var line = 0; line < lineCount; ++line) { - var matched = unify(patts[line], struct, {}); - if (matched !== undefined) { - var args = slice.call(arguments); - args[0] = matched; - var result = handlers[line].apply(null, args); - if (result !== undefined) { - return result; - } - } - } - throw new Error('Unmatched Expression:\n ' + JSON.stringify(struct)); - }; -}; - -test('match', function () { - var x = variable('x'); - var y = variable('y'); - var z = variable('z'); - var string = variable('string', _.isString); - var array = variable('array', _.isArray); - - var t = match( - ['APP', 'I', x], function (match) { - var tx = t(match.x); - return tx; - }, - ['APP', ['APP', 'K', x], y], function (match) { - var tx = t(match.x); - return tx; - }, - ['APP', ['APP', ['APP', 'B', x], y], z], function (match) { - var xyz = ['APP', match.x, ['APP', match.y, match.z]]; - return t(xyz); - }, - ['APP', x, y], function (match) { - var tx = t(match.x); - var ty = t(match.y); - return ['APP', tx, ty]; - }, - ['typed:', string], function (match) { - return 'string'; - }, - ['typed:', array], function (match) { - return 'array'; - }, - x, function (match) { - return match.x; - } - ); - - var examples = [ - [['APP', 'I', 'a'], 'a'], - [['APP', ['APP', 'K', 'a'], 'b'], 'a'], - [['typed:', 'test'], 'string'], - [['typed:', []], 'array'] - ]; - assert.forward(t, examples); -}); - -module.exports = { - variable: variable, - unify: function (patt, struct) { - assert(isPattern(patt), 'bad pattern: ' + patt); - return unify(patt, struct, {}); - }, - match: match -}; diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/pretty.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/pretty.js deleted file mode 100644 index 4295d7f..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/pretty.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; - -var _ = require('underscore'); -var test = require('./test'); -var assert = require('./assert'); -var template = require('./template'); -var compiler = require('./compiler'); - -var templates = { - HOLE: template('?'), - TOP: template('T'), - BOT: template('_'), - I: template('1'), - VAR: template('{0}'), - APP: template('{0} {1}'), - COMP: template('{0}*{1}'), - JOIN: template('{0}|{1}'), - RAND: template('({0}+{1})'), - LAMBDA: template('\\ {0} {1}'), - LETREC: template('let {0} = {1}. {2}'), - PAREN: template('({0})'), - QUOTE: template('{{0}}'), - LESS: template('{{0} [= {1}}'), - NLESS: template('{{0} [!= {1}}'), - EQUAL: template('{{0} = {1}}'), - DEFINE: template('Define {0} = {1}.'), - ASSERT: template('Assert {0}.'), - CURSOR: template('[{0}]') -}; - -var print = compiler.fold(function (token, args) { - return templates[token](args); -}); - -var pretty = function (term) { - return print(compiler.parenthesize(term)); -}; - -test('pretty', function () { - var examples = [ - ['LAMBDA VAR a VAR a', '\\ a a'], - ['APP APP VAR a VAR b VAR c', 'a b c'], - ['APP VAR a APP VAR b VAR c', 'a (b c)'] - ]; - assert.forward(_.compose(pretty, compiler.load), examples); -}); - -module.exports = pretty; diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/stats.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/stats.js deleted file mode 100644 index 77f19ff..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/stats.js +++ /dev/null @@ -1,83 +0,0 @@ -'use strict'; - -var _ = require('underscore'); -var assert = require('assert'); -var debug = require('debug')('puddle-syntax:stats'); - -var sum = function (samples) { - var total = 0; - for (var i = 0; i < samples.length; ++i) { - total += samples[i]; - } - return total; -}; - -var mean = function (samples) { - return sum(samples) / samples.length; -}; - -var normalize = function (probs) { - var scale = 1 / sum(probs); - for (var i = 0; i < probs.length; ++i) { - probs[i] = scale * probs[i]; - } -}; - -var iid = function (sampler, count) { - var samples = []; - for (var i = 0; i < count; ++i) { - samples.push(sampler()); - } - return samples; -}; - -var sampleUnique = function (sampler, count) { - var samples = []; - var seen = {}; - for (var i = 0; i < count; ++i) { - var sample = sampler(); - while (_.has(seen, sample)) { - sample = sampler(); - } - seen[sample] = null; - samples.push(sample); - debug(sample); - } - return samples; -}; - -var sampleUnif01 = _.bind(Math.random, Math); - -var sampleInt = function (max) { - return Math.floor(Math.random() * (max + 1)); -}; - -var sampleUnifChoice = function (array) { - assert(array.length > 0, 'Cannot sample from empty array'); - return array[sampleInt(array.length - 1)]; -}; - -var sampleDiscrete = function (values, probs) { - var attempts = 10; - for (var i = 0; i < attempts; ++i) { - var dart = -Math.random(); - for (var pos = 0; pos < probs.length; ++pos) { - dart += probs[pos]; - if (dart > 0) { - return values[pos]; - } - } - } - throw new Error('sampleDiscrete imprecision'); -}; - -module.exports = { - sum: sum, - mean: mean, - normalize: normalize, - iid: iid, - sampleUnique: sampleUnique, - sampleUnif01: sampleUnif01, - sampleUnifChoice: sampleUnifChoice, - sampleDiscrete: sampleDiscrete, -}; diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/template.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/template.js deleted file mode 100644 index c68b016..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/template.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -var test = require('./test'); -var assert = require('assert'); - -var template = function (string) { - return function (args) { - return string.replace(/{(\d+)}/g, function (match, pos) { - return args[pos]; - }); - }; -}; - -test('template', function () { - var t = template('test {0} test {1} test {0} test'); - assert.equal(t(['a', 'b']), 'test a test b test a test'); -}); - -module.exports = template; diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/test.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/test.js deleted file mode 100644 index faef642..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/test.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; - -var assert = require('assert'); - -var suites = []; -var running = false; - -var suite = function (name) { - assert(!running, 'Suite started after runAll: ' + name); - var tests = []; - - suites.push({ - 'name': name, - 'callback': function (mocha) { - tests.forEach(function (args) { - mocha.test(args.name, args.callback); - }); - } - }); - - var test = function (name, callback) { - assert(!running, 'Test started after runAll: ' + name); - tests.push({ - 'name': name, - 'callback': callback - }); - }; - - return test; -}; - -var test = suite('misc'); -test.suite = suite; - -test.runAll = function (mocha) { - running = true; - suites.forEach(function (args) { - mocha.suite(args.name, function () { - args.callback(mocha); - }); - }); -}; - -module.exports = test; diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/tokens.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/tokens.js deleted file mode 100644 index 2df8e20..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/tokens.js +++ /dev/null @@ -1,117 +0,0 @@ -'use strict'; - -var _ = require('underscore'); -var assert = require('./assert'); -var test = require('./test').suite('tokens'); - -var matcher = function (re) { - return function (token) { - return !!(_.isString(token) && token.match(re)); - }; -}; - -var isToken = matcher(/^[^\d\W]\w*(\.[^\d\W]\w*)*$/); -var isKeyword = matcher(/^[A-Z]+$/); -var isLocal = matcher(/^[a-z][a-z0-9]*$/); -var isGlobal = matcher(/^[^\d\W]\w*(\.[^\d\W]\w*)+$/); - -test('isToken', function () { - var examples = [ - ['()', false], - ['', false], - ['1', false], - ['1a', false], - ['1.1', false], - ['1a', false], - ['.', false], - ['...', false] - ]; - assert.forward(isToken, examples); -}); - -test('isKeyword', function () { - var examples = [ - ['asdf', false], - ['ASDF', true], - ['ASDF.ASDF', false], - ['a123', false], - ['A123', false] - ]; - assert.forward(isKeyword, examples); - - examples.forEach(function (pair) { - pair[1] = true; - }); - assert.forward(isToken, examples); -}); - -test('isLocal', function () { - var examples = [ - ['asdf', true], - ['ASDF', false], - ['asdf.asdf', false], - ['a123', true], - ['A123', false] - ]; - assert.forward(isLocal, examples); - - examples.forEach(function (pair) { - pair[1] = true; - }); - assert.forward(isToken, examples); -}); - -test('isGlobal', function () { - var examples = [ - ['asdf', false], - ['ASDF', false], - ['mod.asdf', true], - ['mod.mod.mod.mod.mod.asdf', true], - ['mod.asdf123', true], - ['MOD.ASDF', true], - ]; - assert.forward(isGlobal, examples); - - examples.forEach(function (pair) { - pair[1] = true; - }); - assert.forward(isToken, examples); -}); - -var getFreeVariables = function (code) { - var free = {}; - code.split(/\s+/).forEach(function (token) { - assert(isToken(token), 'invalid token: ' + token); - if (!isKeyword(token)) { - assert(isGlobal(token), 'invalid global: ' + token); - free[token] = null; - } - }); - return free; -}; - -test('no-free-variables', function () { - var code = 'APP J I'; - var free = {}; - assert.equal(getFreeVariables(code), free); -}); - -test('one-free-variables', function () { - var code = 'APP CI types.div'; - var free = {'types.div': null}; - assert.equal(getFreeVariables(code), free); -}); - -test('many-free-variables', function () { - var code = 'APP APP P COMP types.div types.semi types.div'; - var free = {'types.div': null, 'types.semi': null}; - assert.equal(getFreeVariables(code), free); -}); - -module.exports = { - isToken: isToken, - isKeyword: isKeyword, - isLocal: isLocal, - isGlobal: isGlobal, - getFreeVariables: getFreeVariables, -}; diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/tree.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/tree.js deleted file mode 100644 index 1618a18..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/lib/tree.js +++ /dev/null @@ -1,172 +0,0 @@ -/** - * Mutable abstract syntax trees with crosslinks for constant time traversal. - * - * example tree node: - * { - * name: 'VAR', - * varName: term[1], // optional, only VAR nodes have this field - * below: [], - * above: null - * }; - */ -'use strict'; - -var _ = require('underscore'); -var assert = require('./assert'); -var compiler = require('./compiler'); -var test = require('./test').suite('tree'); - -var loadSymbol = {}; -var dumpSymbol = {}; - -var load = function (term) { - if (_.isString(term)) { - return loadSymbol[term](); - } else { - return loadSymbol[term[0]](term); - } -}; - -var dump = function (node) { - return dumpSymbol[node.name](node); -}; - -_.each(compiler.symbols, function (symbol, name) { - if (_.isString(symbol)) { - loadSymbol[name] = function () { - return { - name: name, - below: [], - above: null - }; - }; - dumpSymbol[name] = function (node) { - return node.name; - }; - } else { - var arity = symbol.arity; - loadSymbol[name] = function (term) { - assert(term !== undefined); - assert.equal(term.length, 1 + arity, name); - var node = { - name: name, - below: [], - above: null - }; - for (var i = 1; i <= arity; ++i) { - var below = load(term[i]); - node.below.push(below); - below.above = node; - } - return node; - }; - dumpSymbol[name] = function (node) { - var below = node.below; - var term = [node.name]; - for (var i = 0; i < arity; ++i) { - term.push(dump(below[i])); - } - return term; - }; - } -}); - -// special case: VAR -loadSymbol.VAR = function (term) { - return { - name: 'VAR', - varName: term[1], - below: [], - above: null - }; -}; -dumpSymbol.VAR = function (node) { - return ['VAR', node.varName]; -}; - -test('load, dump', function () { - var examples = [ - 'VAR x', - 'QUOTE APP LAMBDA CURSOR VAR x VAR x HOLE', - 'LETREC VAR i LAMBDA VAR x VAR x APP VAR i VAR i' - ]; - for (var i = 0; i < examples.length; ++i) { - var lineno = 1 + i; - var string = examples[i]; - var term = compiler.load(string); - var node = load(term); - var term2 = dump(node); - assert.equal(term2, term, 'Example ' + lineno); - } -}); - -var getRoot = function (node) { - while (node.above !== null) { - node = node.above; - } - return node; -}; - -var pushPatternVars = function (patt, vars) { - switch (patt.name) { - case 'VAR': - vars.push(patt.varName); - break; - - case 'QUOTE': - pushPatternVars(patt.below[0], vars); - break; - - default: - break; - } -}; - -var getLocals = function (node) { - var result = []; - for (var above = node; above !== null; above = above.above) { - if (above.name === 'LAMBDA' || above.name === 'LETREC') { - var patt = above.below[0]; - pushPatternVars(patt, result); - } - } - return result; -}; - -var getVarsBelow = function (node, vars) { - if (node.name === 'VAR') { - vars[node.varName] = null; - } else { - var below = node.below; - for (var i = 0; i < below.length; ++i) { - getVarsBelow(below[i], vars); - } - } -}; - -var getVarsAround = function (node) { - var vars = {}; - var root = getRoot(node); - getVarsBelow(root, vars); - return vars; -}; - -var getFresh = function (node) { - var avoid = getVarsAround(node); - for (var i = 0; true; ++i) { - var name = compiler.enumerateFresh(i); - if (!_.has(avoid, name)) { - return name; - } - } -}; - -// TODO add tests - -module.exports = { - load: load, - dump: dump, - getRoot: getRoot, - getLocals: getLocals, - getFresh: getFresh, -}; diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/.jshintrc b/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/.jshintrc deleted file mode 100644 index 299877f..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/.jshintrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "laxbreak": true -} diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/.npmignore b/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/.npmignore deleted file mode 100644 index 7e6163d..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -support -test -examples -example -*.sock -dist diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/History.md b/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/History.md deleted file mode 100644 index 3b96560..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/History.md +++ /dev/null @@ -1,144 +0,0 @@ - -1.0.4 / 2014-07-15 -================== - - * dist: recompile - * example: remove `console.info()` log usage - * example: add "Content-Type" UTF-8 header to browser example - * browser: place %c marker after the space character - * browser: reset the "content" color via `color: inherit` - * browser: add colors support for Firefox >= v31 - * debug: prefer an instance `log()` function over the global one (#119) - * Readme: update documentation about styled console logs for FF v31 (#116, @wryk) - -1.0.3 / 2014-07-09 -================== - - * Add support for multiple wildcards in namespaces (#122, @seegno) - * browser: fix lint - -1.0.2 / 2014-06-10 -================== - - * browser: update color palette (#113, @gscottolson) - * common: make console logging function configurable (#108, @timoxley) - * node: fix %o colors on old node <= 0.8.x - * Makefile: find node path using shell/which (#109, @timoxley) - -1.0.1 / 2014-06-06 -================== - - * browser: use `removeItem()` to clear localStorage - * browser, node: don't set DEBUG if namespaces is undefined (#107, @leedm777) - * package: add "contributors" section - * node: fix comment typo - * README: list authors - -1.0.0 / 2014-06-04 -================== - - * make ms diff be global, not be scope - * debug: ignore empty strings in enable() - * node: make DEBUG_COLORS able to disable coloring - * *: export the `colors` array - * npmignore: don't publish the `dist` dir - * Makefile: refactor to use browserify - * package: add "browserify" as a dev dependency - * Readme: add Web Inspector Colors section - * node: reset terminal color for the debug content - * node: map "%o" to `util.inspect()` - * browser: map "%j" to `JSON.stringify()` - * debug: add custom "formatters" - * debug: use "ms" module for humanizing the diff - * Readme: add "bash" syntax highlighting - * browser: add Firebug color support - * browser: add colors for WebKit browsers - * node: apply log to `console` - * rewrite: abstract common logic for Node & browsers - * add .jshintrc file - -0.8.1 / 2014-04-14 -================== - - * package: re-add the "component" section - -0.8.0 / 2014-03-30 -================== - - * add `enable()` method for nodejs. Closes #27 - * change from stderr to stdout - * remove unnecessary index.js file - -0.7.4 / 2013-11-13 -================== - - * remove "browserify" key from package.json (fixes something in browserify) - -0.7.3 / 2013-10-30 -================== - - * fix: catch localStorage security error when cookies are blocked (Chrome) - * add debug(err) support. Closes #46 - * add .browser prop to package.json. Closes #42 - -0.7.2 / 2013-02-06 -================== - - * fix package.json - * fix: Mobile Safari (private mode) is broken with debug - * fix: Use unicode to send escape character to shell instead of octal to work with strict mode javascript - -0.7.1 / 2013-02-05 -================== - - * add repository URL to package.json - * add DEBUG_COLORED to force colored output - * add browserify support - * fix component. Closes #24 - -0.7.0 / 2012-05-04 -================== - - * Added .component to package.json - * Added debug.component.js build - -0.6.0 / 2012-03-16 -================== - - * Added support for "-" prefix in DEBUG [Vinay Pulim] - * Added `.enabled` flag to the node version [TooTallNate] - -0.5.0 / 2012-02-02 -================== - - * Added: humanize diffs. Closes #8 - * Added `debug.disable()` to the CS variant - * Removed padding. Closes #10 - * Fixed: persist client-side variant again. Closes #9 - -0.4.0 / 2012-02-01 -================== - - * Added browser variant support for older browsers [TooTallNate] - * Added `debug.enable('project:*')` to browser variant [TooTallNate] - * Added padding to diff (moved it to the right) - -0.3.0 / 2012-01-26 -================== - - * Added millisecond diff when isatty, otherwise UTC string - -0.2.0 / 2012-01-22 -================== - - * Added wildcard support - -0.1.0 / 2011-12-02 -================== - - * Added: remove colors unless stderr isatty [TooTallNate] - -0.0.1 / 2010-01-03 -================== - - * Initial release diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/Makefile b/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/Makefile deleted file mode 100644 index b0bde6e..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/Makefile +++ /dev/null @@ -1,33 +0,0 @@ - -# get Makefile directory name: http://stackoverflow.com/a/5982798/376773 -THIS_MAKEFILE_PATH:=$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)) -THIS_DIR:=$(shell cd $(dir $(THIS_MAKEFILE_PATH));pwd) - -# BIN directory -BIN := $(THIS_DIR)/node_modules/.bin - -# applications -NODE ?= $(shell which node) -NPM ?= $(NODE) $(shell which npm) -BROWSERIFY ?= $(NODE) $(BIN)/browserify - -all: dist/debug.js - -install: node_modules - -clean: - @rm -rf node_modules dist - -dist: - @mkdir -p $@ - -dist/debug.js: node_modules browser.js debug.js dist - @$(BROWSERIFY) \ - --standalone debug \ - . > $@ - -node_modules: package.json - @NODE_ENV= $(NPM) install - @touch node_modules - -.PHONY: all install clean diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/Readme.md b/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/Readme.md deleted file mode 100644 index e59b9ad..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/Readme.md +++ /dev/null @@ -1,156 +0,0 @@ -# debug - - tiny node.js debugging utility modelled after node core's debugging technique. - -## Installation - -```bash -$ npm install debug -``` - -## Usage - - With `debug` you simply invoke the exported function to generate your debug function, passing it a name which will determine if a noop function is returned, or a decorated `console.error`, so all of the `console` format string goodies you're used to work fine. A unique color is selected per-function for visibility. - -Example _app.js_: - -```js -var debug = require('debug')('http') - , http = require('http') - , name = 'My App'; - -// fake app - -debug('booting %s', name); - -http.createServer(function(req, res){ - debug(req.method + ' ' + req.url); - res.end('hello\n'); -}).listen(3000, function(){ - debug('listening'); -}); - -// fake worker of some kind - -require('./worker'); -``` - -Example _worker.js_: - -```js -var debug = require('debug')('worker'); - -setInterval(function(){ - debug('doing some work'); -}, 1000); -``` - - The __DEBUG__ environment variable is then used to enable these based on space or comma-delimited names. Here are some examples: - - ![debug http and worker](http://f.cl.ly/items/18471z1H402O24072r1J/Screenshot.png) - - ![debug worker](http://f.cl.ly/items/1X413v1a3M0d3C2c1E0i/Screenshot.png) - -## Millisecond diff - - When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the "+NNNms" will show you how much time was spent between calls. - - ![](http://f.cl.ly/items/2i3h1d3t121M2Z1A3Q0N/Screenshot.png) - - When stdout is not a TTY, `Date#toUTCString()` is used, making it more useful for logging the debug information as shown below: - - ![](http://f.cl.ly/items/112H3i0e0o0P0a2Q2r11/Screenshot.png) - -## Conventions - - If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use ":" to separate features. For example "bodyParser" from Connect would then be "connect:bodyParser". - -## Wildcards - - The `*` character may be used as a wildcard. Suppose for example your library has debuggers named "connect:bodyParser", "connect:compress", "connect:session", instead of listing all three with `DEBUG=connect:bodyParser,connect.compress,connect:session`, you may simply do `DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`. - - You can also exclude specific debuggers by prefixing them with a "-" character. For example, `DEBUG=*,-connect:*` would include all debuggers except those starting with "connect:". - -## Browser support - - Debug works in the browser as well, currently persisted by `localStorage`. For example if you have `worker:a` and `worker:b` as shown below, and wish to debug both type `debug.enable('worker:*')` in the console and refresh the page, this will remain until you disable with `debug.disable()`. - -```js -a = debug('worker:a'); -b = debug('worker:b'); - -setInterval(function(){ - a('doing some work'); -}, 1000); - -setInterval(function(){ - b('doing some work'); -}, 1200); -``` - -#### Web Inspector Colors - - Colors are also enabled on "Web Inspectors" that understand the `%c` formatting - option. These are WebKit web inspectors, Firefox ([since version - 31](https://hacks.mozilla.org/2014/05/editable-box-model-multiple-selection-sublime-text-keys-much-more-firefox-developer-tools-episode-31/)) - and the Firebug plugin for Firefox (any version). - - Colored output looks something like: - - ![](https://cloud.githubusercontent.com/assets/71256/3139768/b98c5fd8-e8ef-11e3-862a-f7253b6f47c6.png) - -### stderr vs stdout - -You can set an alternative logging method per-namespace by overriding the `log` method on a per-namespace or globally: - -Example _stderr.js_: - -```js -var debug = require('../'); -var log = debug('app:log'); - -// by default console.log is used -log('goes to stdout!'); - -var error = debug('app:error'); -// set this namespace to log via console.error -error.log = console.error.bind(console); // don't forget to bind to console! -error('goes to stderr'); -log('still goes to stdout!'); - -// set all output to go via console.warn -// overrides all per-namespace log settings -debug.log = console.warn.bind(console); -log('now goes to stderr via console.warn'); -error('still goes to stderr, but via console.warn now'); -``` - -## Authors - - - TJ Holowaychuk - - Nathan Rajlich - -## License - -(The MIT License) - -Copyright (c) 2014 TJ Holowaychuk <tj@vision-media.ca> - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/browser.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/browser.js deleted file mode 100644 index ce6369f..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/browser.js +++ /dev/null @@ -1,147 +0,0 @@ - -/** - * This is the web browser implementation of `debug()`. - * - * Expose `debug()` as the module. - */ - -exports = module.exports = require('./debug'); -exports.log = log; -exports.formatArgs = formatArgs; -exports.save = save; -exports.load = load; -exports.useColors = useColors; - -/** - * Colors. - */ - -exports.colors = [ - 'lightseagreen', - 'forestgreen', - 'goldenrod', - 'dodgerblue', - 'darkorchid', - 'crimson' -]; - -/** - * Currently only WebKit-based Web Inspectors, Firefox >= v31, - * and the Firebug extension (any Firefox version) are known - * to support "%c" CSS customizations. - * - * TODO: add a `localStorage` variable to explicitly enable/disable colors - */ - -function useColors() { - // is webkit? http://stackoverflow.com/a/16459606/376773 - return ('WebkitAppearance' in document.documentElement.style) || - // is firebug? http://stackoverflow.com/a/398120/376773 - (window.console && (console.firebug || (console.exception && console.table))) || - // is firefox >= v31? - // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages - (navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31); -} - -/** - * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. - */ - -exports.formatters.j = function(v) { - return JSON.stringify(v); -}; - - -/** - * Colorize log arguments if enabled. - * - * @api public - */ - -function formatArgs() { - var args = arguments; - var useColors = this.useColors; - - args[0] = (useColors ? '%c' : '') - + this.namespace - + (useColors ? ' %c' : ' ') - + args[0] - + (useColors ? '%c ' : ' ') - + '+' + exports.humanize(this.diff); - - if (!useColors) return args; - - var c = 'color: ' + this.color; - args = [args[0], c, 'color: inherit'].concat(Array.prototype.slice.call(args, 1)); - - // the final "%c" is somewhat tricky, because there could be other - // arguments passed either before or after the %c, so we need to - // figure out the correct index to insert the CSS into - var index = 0; - var lastC = 0; - args[0].replace(/%[a-z%]/g, function(match) { - if ('%%' === match) return; - index++; - if ('%c' === match) { - // we only are interested in the *last* %c - // (the user may have provided their own) - lastC = index; - } - }); - - args.splice(lastC, 0, c); - return args; -} - -/** - * Invokes `console.log()` when available. - * No-op when `console.log` is not a "function". - * - * @api public - */ - -function log() { - // This hackery is required for IE8, - // where the `console.log` function doesn't have 'apply' - return 'object' == typeof console - && 'function' == typeof console.log - && Function.prototype.apply.call(console.log, console, arguments); -} - -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ - -function save(namespaces) { - try { - if (null == namespaces) { - localStorage.removeItem('debug'); - } else { - localStorage.debug = namespaces; - } - } catch(e) {} -} - -/** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ - -function load() { - var r; - try { - r = localStorage.debug; - } catch(e) {} - return r; -} - -/** - * Enable namespaces listed in `localStorage.debug` initially. - */ - -exports.enable(load()); diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/component.json b/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/component.json deleted file mode 100644 index ab5f3ee..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/component.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "debug", - "repo": "visionmedia/debug", - "description": "small debugging utility", - "version": "1.0.4", - "keywords": [ - "debug", - "log", - "debugger" - ], - "main": "browser.js", - "scripts": [ - "browser.js", - "debug.js" - ], - "dependencies": { - "guille/ms.js": "0.6.1" - } -} diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/debug.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/debug.js deleted file mode 100644 index 7571a86..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/debug.js +++ /dev/null @@ -1,197 +0,0 @@ - -/** - * This is the common logic for both the Node.js and web browser - * implementations of `debug()`. - * - * Expose `debug()` as the module. - */ - -exports = module.exports = debug; -exports.coerce = coerce; -exports.disable = disable; -exports.enable = enable; -exports.enabled = enabled; -exports.humanize = require('ms'); - -/** - * The currently active debug mode names, and names to skip. - */ - -exports.names = []; -exports.skips = []; - -/** - * Map of special "%n" handling functions, for the debug "format" argument. - * - * Valid key names are a single, lowercased letter, i.e. "n". - */ - -exports.formatters = {}; - -/** - * Previously assigned color. - */ - -var prevColor = 0; - -/** - * Previous log timestamp. - */ - -var prevTime; - -/** - * Select a color. - * - * @return {Number} - * @api private - */ - -function selectColor() { - return exports.colors[prevColor++ % exports.colors.length]; -} - -/** - * Create a debugger with the given `namespace`. - * - * @param {String} namespace - * @return {Function} - * @api public - */ - -function debug(namespace) { - - // define the `disabled` version - function disabled() { - } - disabled.enabled = false; - - // define the `enabled` version - function enabled() { - - var self = enabled; - - // set `diff` timestamp - var curr = +new Date(); - var ms = curr - (prevTime || curr); - self.diff = ms; - self.prev = prevTime; - self.curr = curr; - prevTime = curr; - - // add the `color` if not set - if (null == self.useColors) self.useColors = exports.useColors(); - if (null == self.color && self.useColors) self.color = selectColor(); - - var args = Array.prototype.slice.call(arguments); - - args[0] = exports.coerce(args[0]); - - if ('string' !== typeof args[0]) { - // anything else let's inspect with %o - args = ['%o'].concat(args); - } - - // apply any `formatters` transformations - var index = 0; - args[0] = args[0].replace(/%([a-z%])/g, function(match, format) { - // if we encounter an escaped % then don't increase the array index - if (match === '%%') return match; - index++; - var formatter = exports.formatters[format]; - if ('function' === typeof formatter) { - var val = args[index]; - match = formatter.call(self, val); - - // now we need to remove `args[index]` since it's inlined in the `format` - args.splice(index, 1); - index--; - } - return match; - }); - - if ('function' === typeof exports.formatArgs) { - args = exports.formatArgs.apply(self, args); - } - var logFn = enabled.log || exports.log || console.log.bind(console); - logFn.apply(self, args); - } - enabled.enabled = true; - - var fn = exports.enabled(namespace) ? enabled : disabled; - - fn.namespace = namespace; - - return fn; -} - -/** - * Enables a debug mode by namespaces. This can include modes - * separated by a colon and wildcards. - * - * @param {String} namespaces - * @api public - */ - -function enable(namespaces) { - exports.save(namespaces); - - var split = (namespaces || '').split(/[\s,]+/); - var len = split.length; - - for (var i = 0; i < len; i++) { - if (!split[i]) continue; // ignore empty strings - namespaces = split[i].replace(/\*/g, '.*?'); - if (namespaces[0] === '-') { - exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); - } else { - exports.names.push(new RegExp('^' + namespaces + '$')); - } - } -} - -/** - * Disable debug output. - * - * @api public - */ - -function disable() { - exports.enable(''); -} - -/** - * Returns true if the given mode name is enabled, false otherwise. - * - * @param {String} name - * @return {Boolean} - * @api public - */ - -function enabled(name) { - var i, len; - for (i = 0, len = exports.skips.length; i < len; i++) { - if (exports.skips[i].test(name)) { - return false; - } - } - for (i = 0, len = exports.names.length; i < len; i++) { - if (exports.names[i].test(name)) { - return true; - } - } - return false; -} - -/** - * Coerce `val`. - * - * @param {Mixed} val - * @return {Mixed} - * @api private - */ - -function coerce(val) { - if (val instanceof Error) return val.stack || val.message; - return val; -} diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/node.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/node.js deleted file mode 100644 index c94f7d1..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/node.js +++ /dev/null @@ -1,129 +0,0 @@ - -/** - * Module dependencies. - */ - -var tty = require('tty'); -var util = require('util'); - -/** - * This is the Node.js implementation of `debug()`. - * - * Expose `debug()` as the module. - */ - -exports = module.exports = require('./debug'); -exports.log = log; -exports.formatArgs = formatArgs; -exports.save = save; -exports.load = load; -exports.useColors = useColors; - -/** - * Colors. - */ - -exports.colors = [6, 2, 3, 4, 5, 1]; - -/** - * Is stdout a TTY? Colored output is enabled when `true`. - */ - -function useColors() { - var debugColors = (process.env.DEBUG_COLORS || '').trim().toLowerCase(); - if (0 === debugColors.length) { - return tty.isatty(1); - } else { - return '0' !== debugColors - && 'no' !== debugColors - && 'false' !== debugColors - && 'disabled' !== debugColors; - } -} - -/** - * Map %o to `util.inspect()`, since Node doesn't do that out of the box. - */ - -var inspect = (4 === util.inspect.length ? - // node <= 0.8.x - function (v, colors) { - return util.inspect(v, void 0, void 0, colors); - } : - // node > 0.8.x - function (v, colors) { - return util.inspect(v, { colors: colors }); - } -); - -exports.formatters.o = function(v) { - return inspect(v, this.useColors) - .replace(/\s*\n\s*/g, ' '); -}; - -/** - * Adds ANSI color escape codes if enabled. - * - * @api public - */ - -function formatArgs() { - var args = arguments; - var useColors = this.useColors; - var name = this.namespace; - - if (useColors) { - var c = this.color; - - args[0] = ' \u001b[9' + c + 'm' + name + ' ' - + '\u001b[0m' - + args[0] + '\u001b[3' + c + 'm' - + ' +' + exports.humanize(this.diff) + '\u001b[0m'; - } else { - args[0] = new Date().toUTCString() - + ' ' + name + ' ' + args[0]; - } - return args; -} - -/** - * Invokes `console.log()` with the specified arguments. - */ - -function log() { - return console.log.apply(console, arguments); -} - -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ - -function save(namespaces) { - if (null == namespaces) { - // If you set a process.env field to null or undefined, it gets cast to the - // string 'null' or 'undefined'. Just delete instead. - delete process.env.DEBUG; - } else { - process.env.DEBUG = namespaces; - } -} - -/** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ - -function load() { - return process.env.DEBUG; -} - -/** - * Enable namespaces listed in `process.env.DEBUG` initially. - */ - -exports.enable(load()); diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/node_modules/ms/.npmignore b/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/node_modules/ms/.npmignore deleted file mode 100644 index d1aa0ce..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/node_modules/ms/.npmignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules -test -History.md -Makefile -component.json diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/node_modules/ms/README.md b/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/node_modules/ms/README.md deleted file mode 100644 index d4ab12a..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/node_modules/ms/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# ms.js: miliseconds conversion utility - -```js -ms('1d') // 86400000 -ms('10h') // 36000000 -ms('2h') // 7200000 -ms('1m') // 60000 -ms('5s') // 5000 -ms('100') // 100 -``` - -```js -ms(60000) // "1m" -ms(2 * 60000) // "2m" -ms(ms('10 hours')) // "10h" -``` - -```js -ms(60000, { long: true }) // "1 minute" -ms(2 * 60000, { long: true }) // "2 minutes" -ms(ms('10 hours', { long: true })) // "10 hours" -``` - -- Node/Browser compatible. Published as `ms` in NPM. -- If a number is supplied to `ms`, a string with a unit is returned. -- If a string that contains the number is supplied, it returns it as -a number (e.g: it returns `100` for `'100'`). -- If you pass a string with a number and a valid unit, the number of -equivalent ms is returned. - -## License - -MIT \ No newline at end of file diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/node_modules/ms/index.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/node_modules/ms/index.js deleted file mode 100644 index c5847f8..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/node_modules/ms/index.js +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Helpers. - */ - -var s = 1000; -var m = s * 60; -var h = m * 60; -var d = h * 24; -var y = d * 365.25; - -/** - * Parse or format the given `val`. - * - * Options: - * - * - `long` verbose formatting [false] - * - * @param {String|Number} val - * @param {Object} options - * @return {String|Number} - * @api public - */ - -module.exports = function(val, options){ - options = options || {}; - if ('string' == typeof val) return parse(val); - return options.long - ? long(val) - : short(val); -}; - -/** - * Parse the given `str` and return milliseconds. - * - * @param {String} str - * @return {Number} - * @api private - */ - -function parse(str) { - var match = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str); - if (!match) return; - var n = parseFloat(match[1]); - var type = (match[2] || 'ms').toLowerCase(); - switch (type) { - case 'years': - case 'year': - case 'y': - return n * y; - case 'days': - case 'day': - case 'd': - return n * d; - case 'hours': - case 'hour': - case 'h': - return n * h; - case 'minutes': - case 'minute': - case 'm': - return n * m; - case 'seconds': - case 'second': - case 's': - return n * s; - case 'ms': - return n; - } -} - -/** - * Short format for `ms`. - * - * @param {Number} ms - * @return {String} - * @api private - */ - -function short(ms) { - if (ms >= d) return Math.round(ms / d) + 'd'; - if (ms >= h) return Math.round(ms / h) + 'h'; - if (ms >= m) return Math.round(ms / m) + 'm'; - if (ms >= s) return Math.round(ms / s) + 's'; - return ms + 'ms'; -} - -/** - * Long format for `ms`. - * - * @param {Number} ms - * @return {String} - * @api private - */ - -function long(ms) { - return plural(ms, d, 'day') - || plural(ms, h, 'hour') - || plural(ms, m, 'minute') - || plural(ms, s, 'second') - || ms + ' ms'; -} - -/** - * Pluralization helper. - */ - -function plural(ms, n, name) { - if (ms < n) return; - if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name; - return Math.ceil(ms / n) + ' ' + name + 's'; -} diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/node_modules/ms/package.json b/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/node_modules/ms/package.json deleted file mode 100644 index c4dd654..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/node_modules/ms/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "ms", - "version": "0.6.2", - "description": "Tiny ms conversion utility", - "repository": { - "type": "git", - "url": "git://github.com/guille/ms.js.git" - }, - "main": "./index", - "devDependencies": { - "mocha": "*", - "expect.js": "*", - "serve": "*" - }, - "component": { - "scripts": { - "ms/index.js": "index.js" - } - }, - "readme": "# ms.js: miliseconds conversion utility\n\n```js\nms('1d') // 86400000\nms('10h') // 36000000\nms('2h') // 7200000\nms('1m') // 60000\nms('5s') // 5000\nms('100') // 100\n```\n\n```js\nms(60000) // \"1m\"\nms(2 * 60000) // \"2m\"\nms(ms('10 hours')) // \"10h\"\n```\n\n```js\nms(60000, { long: true }) // \"1 minute\"\nms(2 * 60000, { long: true }) // \"2 minutes\"\nms(ms('10 hours', { long: true })) // \"10 hours\"\n```\n\n- Node/Browser compatible. Published as `ms` in NPM.\n- If a number is supplied to `ms`, a string with a unit is returned.\n- If a string that contains the number is supplied, it returns it as\na number (e.g: it returns `100` for `'100'`).\n- If you pass a string with a number and a valid unit, the number of\nequivalent ms is returned.\n\n## License\n\nMIT", - "readmeFilename": "README.md", - "bugs": { - "url": "https://github.com/guille/ms.js/issues" - }, - "_id": "ms@0.6.2", - "dist": { - "shasum": "d89c2124c6fdc1353d65a8b77bf1aac4b193708c", - "tarball": "http://registry.npmjs.org/ms/-/ms-0.6.2.tgz" - }, - "_from": "ms@0.6.2", - "_npmVersion": "1.2.30", - "_npmUser": { - "name": "rauchg", - "email": "rauchg@gmail.com" - }, - "maintainers": [ - { - "name": "rauchg", - "email": "rauchg@gmail.com" - } - ], - "directories": {}, - "_shasum": "d89c2124c6fdc1353d65a8b77bf1aac4b193708c", - "_resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", - "homepage": "https://github.com/guille/ms.js" -} diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/package.json b/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/package.json deleted file mode 100644 index 01656ed..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/debug/package.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "name": "debug", - "version": "1.0.4", - "repository": { - "type": "git", - "url": "git://github.com/visionmedia/debug.git" - }, - "description": "small debugging utility", - "keywords": [ - "debug", - "log", - "debugger" - ], - "author": { - "name": "TJ Holowaychuk", - "email": "tj@vision-media.ca" - }, - "contributors": [ - { - "name": "Nathan Rajlich", - "email": "nathan@tootallnate.net", - "url": "http://n8.io" - } - ], - "dependencies": { - "ms": "0.6.2" - }, - "devDependencies": { - "browserify": "4.1.6", - "mocha": "*" - }, - "main": "./node.js", - "browser": "./browser.js", - "component": { - "scripts": { - "debug/index.js": "browser.js", - "debug/debug.js": "debug.js" - } - }, - "gitHead": "abc10a5912f79d251752d18350e269fe0b0fbbf9", - "bugs": { - "url": "https://github.com/visionmedia/debug/issues" - }, - "homepage": "https://github.com/visionmedia/debug", - "_id": "debug@1.0.4", - "scripts": {}, - "_shasum": "5b9c256bd54b6ec02283176fa8a0ede6d154cbf8", - "_from": "debug@^1.0.4", - "_npmVersion": "1.4.14", - "_npmUser": { - "name": "tootallnate", - "email": "nathan@tootallnate.net" - }, - "maintainers": [ - { - "name": "tjholowaychuk", - "email": "tj@vision-media.ca" - }, - { - "name": "tootallnate", - "email": "nathan@tootallnate.net" - } - ], - "dist": { - "shasum": "5b9c256bd54b6ec02283176fa8a0ede6d154cbf8", - "tarball": "http://registry.npmjs.org/debug/-/debug-1.0.4.tgz" - }, - "directories": {}, - "_resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz", - "readme": "ERROR: No README data found!" -} diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/underscore/LICENSE b/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/underscore/LICENSE deleted file mode 100644 index 0d6b873..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/underscore/LICENSE +++ /dev/null @@ -1,23 +0,0 @@ -Copyright (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative -Reporters & Editors - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/underscore/README.md b/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/underscore/README.md deleted file mode 100644 index c2ba259..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/underscore/README.md +++ /dev/null @@ -1,22 +0,0 @@ - __ - /\ \ __ - __ __ ___ \_\ \ __ _ __ ____ ___ ___ _ __ __ /\_\ ____ - /\ \/\ \ /' _ `\ /'_ \ /'__`\/\ __\/ ,__\ / ___\ / __`\/\ __\/'__`\ \/\ \ /',__\ - \ \ \_\ \/\ \/\ \/\ \ \ \/\ __/\ \ \//\__, `\/\ \__//\ \ \ \ \ \//\ __/ __ \ \ \/\__, `\ - \ \____/\ \_\ \_\ \___,_\ \____\\ \_\\/\____/\ \____\ \____/\ \_\\ \____\/\_\ _\ \ \/\____/ - \/___/ \/_/\/_/\/__,_ /\/____/ \/_/ \/___/ \/____/\/___/ \/_/ \/____/\/_//\ \_\ \/___/ - \ \____/ - \/___/ - -Underscore.js is a utility-belt library for JavaScript that provides -support for the usual functional suspects (each, map, reduce, filter...) -without extending any core JavaScript objects. - -For Docs, License, Tests, and pre-packed downloads, see: -http://underscorejs.org - -Underscore is an open-sourced component of DocumentCloud: -https://github.com/documentcloud - -Many thanks to our contributors: -https://github.com/jashkenas/underscore/contributors diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/underscore/package.json b/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/underscore/package.json deleted file mode 100644 index 086ad62..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/underscore/package.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "name": "underscore", - "description": "JavaScript's functional programming helper library.", - "homepage": "http://underscorejs.org", - "keywords": [ - "util", - "functional", - "server", - "client", - "browser" - ], - "author": { - "name": "Jeremy Ashkenas", - "email": "jeremy@documentcloud.org" - }, - "repository": { - "type": "git", - "url": "git://github.com/jashkenas/underscore.git" - }, - "main": "underscore.js", - "version": "1.7.0", - "devDependencies": { - "docco": "0.6.x", - "phantomjs": "1.9.7-1", - "uglify-js": "2.4.x", - "eslint": "0.6.x" - }, - "scripts": { - "test": "phantomjs test/vendor/runner.js test/index.html?noglobals=true && eslint underscore.js test/*.js test/vendor/runner.js", - "build": "uglifyjs underscore.js -c \"evaluate=false\" --comments \"/ .*/\" -m --source-map underscore-min.map -o underscore-min.js", - "doc": "docco underscore.js" - }, - "licenses": [ - { - "type": "MIT", - "url": "https://raw.github.com/jashkenas/underscore/master/LICENSE" - } - ], - "files": [ - "underscore.js", - "underscore-min.js", - "LICENSE" - ], - "gitHead": "da996e665deb0b69b257e80e3e257c04fde4191c", - "bugs": { - "url": "https://github.com/jashkenas/underscore/issues" - }, - "_id": "underscore@1.7.0", - "_shasum": "6bbaf0877500d36be34ecaa584e0db9fef035209", - "_from": "underscore@^1.6.0", - "_npmVersion": "1.4.24", - "_npmUser": { - "name": "jashkenas", - "email": "jashkenas@gmail.com" - }, - "maintainers": [ - { - "name": "jashkenas", - "email": "jashkenas@gmail.com" - } - ], - "dist": { - "shasum": "6bbaf0877500d36be34ecaa584e0db9fef035209", - "tarball": "http://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz" - }, - "directories": {}, - "_resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", - "readme": "ERROR: No README data found!" -} diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/underscore/underscore-min.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/underscore/underscore-min.js deleted file mode 100644 index 11f1d96..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/underscore/underscore-min.js +++ /dev/null @@ -1,6 +0,0 @@ -// Underscore.js 1.7.0 -// http://underscorejs.org -// (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors -// Underscore may be freely distributed under the MIT license. -(function(){var n=this,t=n._,r=Array.prototype,e=Object.prototype,u=Function.prototype,i=r.push,a=r.slice,o=r.concat,l=e.toString,c=e.hasOwnProperty,f=Array.isArray,s=Object.keys,p=u.bind,h=function(n){return n instanceof h?n:this instanceof h?void(this._wrapped=n):new h(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=h),exports._=h):n._=h,h.VERSION="1.7.0";var g=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}};h.iteratee=function(n,t,r){return null==n?h.identity:h.isFunction(n)?g(n,t,r):h.isObject(n)?h.matches(n):h.property(n)},h.each=h.forEach=function(n,t,r){if(null==n)return n;t=g(t,r);var e,u=n.length;if(u===+u)for(e=0;u>e;e++)t(n[e],e,n);else{var i=h.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},h.map=h.collect=function(n,t,r){if(null==n)return[];t=h.iteratee(t,r);for(var e,u=n.length!==+n.length&&h.keys(n),i=(u||n).length,a=Array(i),o=0;i>o;o++)e=u?u[o]:o,a[o]=t(n[e],e,n);return a};var v="Reduce of empty array with no initial value";h.reduce=h.foldl=h.inject=function(n,t,r,e){null==n&&(n=[]),t=g(t,e,4);var u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length,o=0;if(arguments.length<3){if(!a)throw new TypeError(v);r=n[i?i[o++]:o++]}for(;a>o;o++)u=i?i[o]:o,r=t(r,n[u],u,n);return r},h.reduceRight=h.foldr=function(n,t,r,e){null==n&&(n=[]),t=g(t,e,4);var u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;if(arguments.length<3){if(!a)throw new TypeError(v);r=n[i?i[--a]:--a]}for(;a--;)u=i?i[a]:a,r=t(r,n[u],u,n);return r},h.find=h.detect=function(n,t,r){var e;return t=h.iteratee(t,r),h.some(n,function(n,r,u){return t(n,r,u)?(e=n,!0):void 0}),e},h.filter=h.select=function(n,t,r){var e=[];return null==n?e:(t=h.iteratee(t,r),h.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e)},h.reject=function(n,t,r){return h.filter(n,h.negate(h.iteratee(t)),r)},h.every=h.all=function(n,t,r){if(null==n)return!0;t=h.iteratee(t,r);var e,u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;for(e=0;a>e;e++)if(u=i?i[e]:e,!t(n[u],u,n))return!1;return!0},h.some=h.any=function(n,t,r){if(null==n)return!1;t=h.iteratee(t,r);var e,u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;for(e=0;a>e;e++)if(u=i?i[e]:e,t(n[u],u,n))return!0;return!1},h.contains=h.include=function(n,t){return null==n?!1:(n.length!==+n.length&&(n=h.values(n)),h.indexOf(n,t)>=0)},h.invoke=function(n,t){var r=a.call(arguments,2),e=h.isFunction(t);return h.map(n,function(n){return(e?t:n[t]).apply(n,r)})},h.pluck=function(n,t){return h.map(n,h.property(t))},h.where=function(n,t){return h.filter(n,h.matches(t))},h.findWhere=function(n,t){return h.find(n,h.matches(t))},h.max=function(n,t,r){var e,u,i=-1/0,a=-1/0;if(null==t&&null!=n){n=n.length===+n.length?n:h.values(n);for(var o=0,l=n.length;l>o;o++)e=n[o],e>i&&(i=e)}else t=h.iteratee(t,r),h.each(n,function(n,r,e){u=t(n,r,e),(u>a||u===-1/0&&i===-1/0)&&(i=n,a=u)});return i},h.min=function(n,t,r){var e,u,i=1/0,a=1/0;if(null==t&&null!=n){n=n.length===+n.length?n:h.values(n);for(var o=0,l=n.length;l>o;o++)e=n[o],i>e&&(i=e)}else t=h.iteratee(t,r),h.each(n,function(n,r,e){u=t(n,r,e),(a>u||1/0===u&&1/0===i)&&(i=n,a=u)});return i},h.shuffle=function(n){for(var t,r=n&&n.length===+n.length?n:h.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=h.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},h.sample=function(n,t,r){return null==t||r?(n.length!==+n.length&&(n=h.values(n)),n[h.random(n.length-1)]):h.shuffle(n).slice(0,Math.max(0,t))},h.sortBy=function(n,t,r){return t=h.iteratee(t,r),h.pluck(h.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var m=function(n){return function(t,r,e){var u={};return r=h.iteratee(r,e),h.each(t,function(e,i){var a=r(e,i,t);n(u,e,a)}),u}};h.groupBy=m(function(n,t,r){h.has(n,r)?n[r].push(t):n[r]=[t]}),h.indexBy=m(function(n,t,r){n[r]=t}),h.countBy=m(function(n,t,r){h.has(n,r)?n[r]++:n[r]=1}),h.sortedIndex=function(n,t,r,e){r=h.iteratee(r,e,1);for(var u=r(t),i=0,a=n.length;a>i;){var o=i+a>>>1;r(n[o])t?[]:a.call(n,0,t)},h.initial=function(n,t,r){return a.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},h.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:a.call(n,Math.max(n.length-t,0))},h.rest=h.tail=h.drop=function(n,t,r){return a.call(n,null==t||r?1:t)},h.compact=function(n){return h.filter(n,h.identity)};var y=function(n,t,r,e){if(t&&h.every(n,h.isArray))return o.apply(e,n);for(var u=0,a=n.length;a>u;u++){var l=n[u];h.isArray(l)||h.isArguments(l)?t?i.apply(e,l):y(l,t,r,e):r||e.push(l)}return e};h.flatten=function(n,t){return y(n,t,!1,[])},h.without=function(n){return h.difference(n,a.call(arguments,1))},h.uniq=h.unique=function(n,t,r,e){if(null==n)return[];h.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=h.iteratee(r,e));for(var u=[],i=[],a=0,o=n.length;o>a;a++){var l=n[a];if(t)a&&i===l||u.push(l),i=l;else if(r){var c=r(l,a,n);h.indexOf(i,c)<0&&(i.push(c),u.push(l))}else h.indexOf(u,l)<0&&u.push(l)}return u},h.union=function(){return h.uniq(y(arguments,!0,!0,[]))},h.intersection=function(n){if(null==n)return[];for(var t=[],r=arguments.length,e=0,u=n.length;u>e;e++){var i=n[e];if(!h.contains(t,i)){for(var a=1;r>a&&h.contains(arguments[a],i);a++);a===r&&t.push(i)}}return t},h.difference=function(n){var t=y(a.call(arguments,1),!0,!0,[]);return h.filter(n,function(n){return!h.contains(t,n)})},h.zip=function(n){if(null==n)return[];for(var t=h.max(arguments,"length").length,r=Array(t),e=0;t>e;e++)r[e]=h.pluck(arguments,e);return r},h.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},h.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=h.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}for(;u>e;e++)if(n[e]===t)return e;return-1},h.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=n.length;for("number"==typeof r&&(e=0>r?e+r+1:Math.min(e,r+1));--e>=0;)if(n[e]===t)return e;return-1},h.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=r||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=Array(e),i=0;e>i;i++,n+=r)u[i]=n;return u};var d=function(){};h.bind=function(n,t){var r,e;if(p&&n.bind===p)return p.apply(n,a.call(arguments,1));if(!h.isFunction(n))throw new TypeError("Bind must be called on a function");return r=a.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(a.call(arguments)));d.prototype=n.prototype;var u=new d;d.prototype=null;var i=n.apply(u,r.concat(a.call(arguments)));return h.isObject(i)?i:u}},h.partial=function(n){var t=a.call(arguments,1);return function(){for(var r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===h&&(e[u]=arguments[r++]);for(;r=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=h.bind(n[r],n);return n},h.memoize=function(n,t){var r=function(e){var u=r.cache,i=t?t.apply(this,arguments):e;return h.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},h.delay=function(n,t){var r=a.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},h.defer=function(n){return h.delay.apply(h,[n,1].concat(a.call(arguments,1)))},h.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var l=function(){o=r.leading===!1?0:h.now(),a=null,i=n.apply(e,u),a||(e=u=null)};return function(){var c=h.now();o||r.leading!==!1||(o=c);var f=t-(c-o);return e=this,u=arguments,0>=f||f>t?(clearTimeout(a),a=null,o=c,i=n.apply(e,u),a||(e=u=null)):a||r.trailing===!1||(a=setTimeout(l,f)),i}},h.debounce=function(n,t,r){var e,u,i,a,o,l=function(){var c=h.now()-a;t>c&&c>0?e=setTimeout(l,t-c):(e=null,r||(o=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,a=h.now();var c=r&&!e;return e||(e=setTimeout(l,t)),c&&(o=n.apply(i,u),i=u=null),o}},h.wrap=function(n,t){return h.partial(t,n)},h.negate=function(n){return function(){return!n.apply(this,arguments)}},h.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},h.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},h.before=function(n,t){var r;return function(){return--n>0?r=t.apply(this,arguments):t=null,r}},h.once=h.partial(h.before,2),h.keys=function(n){if(!h.isObject(n))return[];if(s)return s(n);var t=[];for(var r in n)h.has(n,r)&&t.push(r);return t},h.values=function(n){for(var t=h.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},h.pairs=function(n){for(var t=h.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},h.invert=function(n){for(var t={},r=h.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},h.functions=h.methods=function(n){var t=[];for(var r in n)h.isFunction(n[r])&&t.push(r);return t.sort()},h.extend=function(n){if(!h.isObject(n))return n;for(var t,r,e=1,u=arguments.length;u>e;e++){t=arguments[e];for(r in t)c.call(t,r)&&(n[r]=t[r])}return n},h.pick=function(n,t,r){var e,u={};if(null==n)return u;if(h.isFunction(t)){t=g(t,r);for(e in n){var i=n[e];t(i,e,n)&&(u[e]=i)}}else{var l=o.apply([],a.call(arguments,1));n=new Object(n);for(var c=0,f=l.length;f>c;c++)e=l[c],e in n&&(u[e]=n[e])}return u},h.omit=function(n,t,r){if(h.isFunction(t))t=h.negate(t);else{var e=h.map(o.apply([],a.call(arguments,1)),String);t=function(n,t){return!h.contains(e,t)}}return h.pick(n,t,r)},h.defaults=function(n){if(!h.isObject(n))return n;for(var t=1,r=arguments.length;r>t;t++){var e=arguments[t];for(var u in e)n[u]===void 0&&(n[u]=e[u])}return n},h.clone=function(n){return h.isObject(n)?h.isArray(n)?n.slice():h.extend({},n):n},h.tap=function(n,t){return t(n),n};var b=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof h&&(n=n._wrapped),t instanceof h&&(t=t._wrapped);var u=l.call(n);if(u!==l.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]===n)return e[i]===t;var a=n.constructor,o=t.constructor;if(a!==o&&"constructor"in n&&"constructor"in t&&!(h.isFunction(a)&&a instanceof a&&h.isFunction(o)&&o instanceof o))return!1;r.push(n),e.push(t);var c,f;if("[object Array]"===u){if(c=n.length,f=c===t.length)for(;c--&&(f=b(n[c],t[c],r,e)););}else{var s,p=h.keys(n);if(c=p.length,f=h.keys(t).length===c)for(;c--&&(s=p[c],f=h.has(t,s)&&b(n[s],t[s],r,e)););}return r.pop(),e.pop(),f};h.isEqual=function(n,t){return b(n,t,[],[])},h.isEmpty=function(n){if(null==n)return!0;if(h.isArray(n)||h.isString(n)||h.isArguments(n))return 0===n.length;for(var t in n)if(h.has(n,t))return!1;return!0},h.isElement=function(n){return!(!n||1!==n.nodeType)},h.isArray=f||function(n){return"[object Array]"===l.call(n)},h.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},h.each(["Arguments","Function","String","Number","Date","RegExp"],function(n){h["is"+n]=function(t){return l.call(t)==="[object "+n+"]"}}),h.isArguments(arguments)||(h.isArguments=function(n){return h.has(n,"callee")}),"function"!=typeof/./&&(h.isFunction=function(n){return"function"==typeof n||!1}),h.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},h.isNaN=function(n){return h.isNumber(n)&&n!==+n},h.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===l.call(n)},h.isNull=function(n){return null===n},h.isUndefined=function(n){return n===void 0},h.has=function(n,t){return null!=n&&c.call(n,t)},h.noConflict=function(){return n._=t,this},h.identity=function(n){return n},h.constant=function(n){return function(){return n}},h.noop=function(){},h.property=function(n){return function(t){return t[n]}},h.matches=function(n){var t=h.pairs(n),r=t.length;return function(n){if(null==n)return!r;n=new Object(n);for(var e=0;r>e;e++){var u=t[e],i=u[0];if(u[1]!==n[i]||!(i in n))return!1}return!0}},h.times=function(n,t,r){var e=Array(Math.max(0,n));t=g(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},h.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},h.now=Date.now||function(){return(new Date).getTime()};var _={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},w=h.invert(_),j=function(n){var t=function(t){return n[t]},r="(?:"+h.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};h.escape=j(_),h.unescape=j(w),h.result=function(n,t){if(null==n)return void 0;var r=n[t];return h.isFunction(r)?n[t]():r};var x=0;h.uniqueId=function(n){var t=++x+"";return n?n+t:t},h.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var A=/(.)^/,k={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},O=/\\|'|\r|\n|\u2028|\u2029/g,F=function(n){return"\\"+k[n]};h.template=function(n,t,r){!t&&r&&(t=r),t=h.defaults({},t,h.templateSettings);var e=RegExp([(t.escape||A).source,(t.interpolate||A).source,(t.evaluate||A).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,a,o){return i+=n.slice(u,o).replace(O,F),u=o+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":a&&(i+="';\n"+a+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var a=new Function(t.variable||"obj","_",i)}catch(o){throw o.source=i,o}var l=function(n){return a.call(this,n,h)},c=t.variable||"obj";return l.source="function("+c+"){\n"+i+"}",l},h.chain=function(n){var t=h(n);return t._chain=!0,t};var E=function(n){return this._chain?h(n).chain():n};h.mixin=function(n){h.each(h.functions(n),function(t){var r=h[t]=n[t];h.prototype[t]=function(){var n=[this._wrapped];return i.apply(n,arguments),E.call(this,r.apply(h,n))}})},h.mixin(h),h.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=r[n];h.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],E.call(this,r)}}),h.each(["concat","join","slice"],function(n){var t=r[n];h.prototype[n]=function(){return E.call(this,t.apply(this._wrapped,arguments))}}),h.prototype.value=function(){return this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return h})}).call(this); -//# sourceMappingURL=underscore-min.map \ No newline at end of file diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/underscore/underscore.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/underscore/underscore.js deleted file mode 100644 index b4f49a0..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/node_modules/underscore/underscore.js +++ /dev/null @@ -1,1415 +0,0 @@ -// Underscore.js 1.7.0 -// http://underscorejs.org -// (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors -// Underscore may be freely distributed under the MIT license. - -(function() { - - // Baseline setup - // -------------- - - // Establish the root object, `window` in the browser, or `exports` on the server. - var root = this; - - // Save the previous value of the `_` variable. - var previousUnderscore = root._; - - // Save bytes in the minified (but not gzipped) version: - var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; - - // Create quick reference variables for speed access to core prototypes. - var - push = ArrayProto.push, - slice = ArrayProto.slice, - concat = ArrayProto.concat, - toString = ObjProto.toString, - hasOwnProperty = ObjProto.hasOwnProperty; - - // All **ECMAScript 5** native function implementations that we hope to use - // are declared here. - var - nativeIsArray = Array.isArray, - nativeKeys = Object.keys, - nativeBind = FuncProto.bind; - - // Create a safe reference to the Underscore object for use below. - var _ = function(obj) { - if (obj instanceof _) return obj; - if (!(this instanceof _)) return new _(obj); - this._wrapped = obj; - }; - - // Export the Underscore object for **Node.js**, with - // backwards-compatibility for the old `require()` API. If we're in - // the browser, add `_` as a global object. - if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) { - exports = module.exports = _; - } - exports._ = _; - } else { - root._ = _; - } - - // Current version. - _.VERSION = '1.7.0'; - - // Internal function that returns an efficient (for current engines) version - // of the passed-in callback, to be repeatedly applied in other Underscore - // functions. - var createCallback = function(func, context, argCount) { - if (context === void 0) return func; - switch (argCount == null ? 3 : argCount) { - case 1: return function(value) { - return func.call(context, value); - }; - case 2: return function(value, other) { - return func.call(context, value, other); - }; - case 3: return function(value, index, collection) { - return func.call(context, value, index, collection); - }; - case 4: return function(accumulator, value, index, collection) { - return func.call(context, accumulator, value, index, collection); - }; - } - return function() { - return func.apply(context, arguments); - }; - }; - - // A mostly-internal function to generate callbacks that can be applied - // to each element in a collection, returning the desired result — either - // identity, an arbitrary callback, a property matcher, or a property accessor. - _.iteratee = function(value, context, argCount) { - if (value == null) return _.identity; - if (_.isFunction(value)) return createCallback(value, context, argCount); - if (_.isObject(value)) return _.matches(value); - return _.property(value); - }; - - // Collection Functions - // -------------------- - - // The cornerstone, an `each` implementation, aka `forEach`. - // Handles raw objects in addition to array-likes. Treats all - // sparse array-likes as if they were dense. - _.each = _.forEach = function(obj, iteratee, context) { - if (obj == null) return obj; - iteratee = createCallback(iteratee, context); - var i, length = obj.length; - if (length === +length) { - for (i = 0; i < length; i++) { - iteratee(obj[i], i, obj); - } - } else { - var keys = _.keys(obj); - for (i = 0, length = keys.length; i < length; i++) { - iteratee(obj[keys[i]], keys[i], obj); - } - } - return obj; - }; - - // Return the results of applying the iteratee to each element. - _.map = _.collect = function(obj, iteratee, context) { - if (obj == null) return []; - iteratee = _.iteratee(iteratee, context); - var keys = obj.length !== +obj.length && _.keys(obj), - length = (keys || obj).length, - results = Array(length), - currentKey; - for (var index = 0; index < length; index++) { - currentKey = keys ? keys[index] : index; - results[index] = iteratee(obj[currentKey], currentKey, obj); - } - return results; - }; - - var reduceError = 'Reduce of empty array with no initial value'; - - // **Reduce** builds up a single result from a list of values, aka `inject`, - // or `foldl`. - _.reduce = _.foldl = _.inject = function(obj, iteratee, memo, context) { - if (obj == null) obj = []; - iteratee = createCallback(iteratee, context, 4); - var keys = obj.length !== +obj.length && _.keys(obj), - length = (keys || obj).length, - index = 0, currentKey; - if (arguments.length < 3) { - if (!length) throw new TypeError(reduceError); - memo = obj[keys ? keys[index++] : index++]; - } - for (; index < length; index++) { - currentKey = keys ? keys[index] : index; - memo = iteratee(memo, obj[currentKey], currentKey, obj); - } - return memo; - }; - - // The right-associative version of reduce, also known as `foldr`. - _.reduceRight = _.foldr = function(obj, iteratee, memo, context) { - if (obj == null) obj = []; - iteratee = createCallback(iteratee, context, 4); - var keys = obj.length !== + obj.length && _.keys(obj), - index = (keys || obj).length, - currentKey; - if (arguments.length < 3) { - if (!index) throw new TypeError(reduceError); - memo = obj[keys ? keys[--index] : --index]; - } - while (index--) { - currentKey = keys ? keys[index] : index; - memo = iteratee(memo, obj[currentKey], currentKey, obj); - } - return memo; - }; - - // Return the first value which passes a truth test. Aliased as `detect`. - _.find = _.detect = function(obj, predicate, context) { - var result; - predicate = _.iteratee(predicate, context); - _.some(obj, function(value, index, list) { - if (predicate(value, index, list)) { - result = value; - return true; - } - }); - return result; - }; - - // Return all the elements that pass a truth test. - // Aliased as `select`. - _.filter = _.select = function(obj, predicate, context) { - var results = []; - if (obj == null) return results; - predicate = _.iteratee(predicate, context); - _.each(obj, function(value, index, list) { - if (predicate(value, index, list)) results.push(value); - }); - return results; - }; - - // Return all the elements for which a truth test fails. - _.reject = function(obj, predicate, context) { - return _.filter(obj, _.negate(_.iteratee(predicate)), context); - }; - - // Determine whether all of the elements match a truth test. - // Aliased as `all`. - _.every = _.all = function(obj, predicate, context) { - if (obj == null) return true; - predicate = _.iteratee(predicate, context); - var keys = obj.length !== +obj.length && _.keys(obj), - length = (keys || obj).length, - index, currentKey; - for (index = 0; index < length; index++) { - currentKey = keys ? keys[index] : index; - if (!predicate(obj[currentKey], currentKey, obj)) return false; - } - return true; - }; - - // Determine if at least one element in the object matches a truth test. - // Aliased as `any`. - _.some = _.any = function(obj, predicate, context) { - if (obj == null) return false; - predicate = _.iteratee(predicate, context); - var keys = obj.length !== +obj.length && _.keys(obj), - length = (keys || obj).length, - index, currentKey; - for (index = 0; index < length; index++) { - currentKey = keys ? keys[index] : index; - if (predicate(obj[currentKey], currentKey, obj)) return true; - } - return false; - }; - - // Determine if the array or object contains a given value (using `===`). - // Aliased as `include`. - _.contains = _.include = function(obj, target) { - if (obj == null) return false; - if (obj.length !== +obj.length) obj = _.values(obj); - return _.indexOf(obj, target) >= 0; - }; - - // Invoke a method (with arguments) on every item in a collection. - _.invoke = function(obj, method) { - var args = slice.call(arguments, 2); - var isFunc = _.isFunction(method); - return _.map(obj, function(value) { - return (isFunc ? method : value[method]).apply(value, args); - }); - }; - - // Convenience version of a common use case of `map`: fetching a property. - _.pluck = function(obj, key) { - return _.map(obj, _.property(key)); - }; - - // Convenience version of a common use case of `filter`: selecting only objects - // containing specific `key:value` pairs. - _.where = function(obj, attrs) { - return _.filter(obj, _.matches(attrs)); - }; - - // Convenience version of a common use case of `find`: getting the first object - // containing specific `key:value` pairs. - _.findWhere = function(obj, attrs) { - return _.find(obj, _.matches(attrs)); - }; - - // Return the maximum element (or element-based computation). - _.max = function(obj, iteratee, context) { - var result = -Infinity, lastComputed = -Infinity, - value, computed; - if (iteratee == null && obj != null) { - obj = obj.length === +obj.length ? obj : _.values(obj); - for (var i = 0, length = obj.length; i < length; i++) { - value = obj[i]; - if (value > result) { - result = value; - } - } - } else { - iteratee = _.iteratee(iteratee, context); - _.each(obj, function(value, index, list) { - computed = iteratee(value, index, list); - if (computed > lastComputed || computed === -Infinity && result === -Infinity) { - result = value; - lastComputed = computed; - } - }); - } - return result; - }; - - // Return the minimum element (or element-based computation). - _.min = function(obj, iteratee, context) { - var result = Infinity, lastComputed = Infinity, - value, computed; - if (iteratee == null && obj != null) { - obj = obj.length === +obj.length ? obj : _.values(obj); - for (var i = 0, length = obj.length; i < length; i++) { - value = obj[i]; - if (value < result) { - result = value; - } - } - } else { - iteratee = _.iteratee(iteratee, context); - _.each(obj, function(value, index, list) { - computed = iteratee(value, index, list); - if (computed < lastComputed || computed === Infinity && result === Infinity) { - result = value; - lastComputed = computed; - } - }); - } - return result; - }; - - // Shuffle a collection, using the modern version of the - // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). - _.shuffle = function(obj) { - var set = obj && obj.length === +obj.length ? obj : _.values(obj); - var length = set.length; - var shuffled = Array(length); - for (var index = 0, rand; index < length; index++) { - rand = _.random(0, index); - if (rand !== index) shuffled[index] = shuffled[rand]; - shuffled[rand] = set[index]; - } - return shuffled; - }; - - // Sample **n** random values from a collection. - // If **n** is not specified, returns a single random element. - // The internal `guard` argument allows it to work with `map`. - _.sample = function(obj, n, guard) { - if (n == null || guard) { - if (obj.length !== +obj.length) obj = _.values(obj); - return obj[_.random(obj.length - 1)]; - } - return _.shuffle(obj).slice(0, Math.max(0, n)); - }; - - // Sort the object's values by a criterion produced by an iteratee. - _.sortBy = function(obj, iteratee, context) { - iteratee = _.iteratee(iteratee, context); - return _.pluck(_.map(obj, function(value, index, list) { - return { - value: value, - index: index, - criteria: iteratee(value, index, list) - }; - }).sort(function(left, right) { - var a = left.criteria; - var b = right.criteria; - if (a !== b) { - if (a > b || a === void 0) return 1; - if (a < b || b === void 0) return -1; - } - return left.index - right.index; - }), 'value'); - }; - - // An internal function used for aggregate "group by" operations. - var group = function(behavior) { - return function(obj, iteratee, context) { - var result = {}; - iteratee = _.iteratee(iteratee, context); - _.each(obj, function(value, index) { - var key = iteratee(value, index, obj); - behavior(result, value, key); - }); - return result; - }; - }; - - // Groups the object's values by a criterion. Pass either a string attribute - // to group by, or a function that returns the criterion. - _.groupBy = group(function(result, value, key) { - if (_.has(result, key)) result[key].push(value); else result[key] = [value]; - }); - - // Indexes the object's values by a criterion, similar to `groupBy`, but for - // when you know that your index values will be unique. - _.indexBy = group(function(result, value, key) { - result[key] = value; - }); - - // Counts instances of an object that group by a certain criterion. Pass - // either a string attribute to count by, or a function that returns the - // criterion. - _.countBy = group(function(result, value, key) { - if (_.has(result, key)) result[key]++; else result[key] = 1; - }); - - // Use a comparator function to figure out the smallest index at which - // an object should be inserted so as to maintain order. Uses binary search. - _.sortedIndex = function(array, obj, iteratee, context) { - iteratee = _.iteratee(iteratee, context, 1); - var value = iteratee(obj); - var low = 0, high = array.length; - while (low < high) { - var mid = low + high >>> 1; - if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; - } - return low; - }; - - // Safely create a real, live array from anything iterable. - _.toArray = function(obj) { - if (!obj) return []; - if (_.isArray(obj)) return slice.call(obj); - if (obj.length === +obj.length) return _.map(obj, _.identity); - return _.values(obj); - }; - - // Return the number of elements in an object. - _.size = function(obj) { - if (obj == null) return 0; - return obj.length === +obj.length ? obj.length : _.keys(obj).length; - }; - - // Split a collection into two arrays: one whose elements all satisfy the given - // predicate, and one whose elements all do not satisfy the predicate. - _.partition = function(obj, predicate, context) { - predicate = _.iteratee(predicate, context); - var pass = [], fail = []; - _.each(obj, function(value, key, obj) { - (predicate(value, key, obj) ? pass : fail).push(value); - }); - return [pass, fail]; - }; - - // Array Functions - // --------------- - - // Get the first element of an array. Passing **n** will return the first N - // values in the array. Aliased as `head` and `take`. The **guard** check - // allows it to work with `_.map`. - _.first = _.head = _.take = function(array, n, guard) { - if (array == null) return void 0; - if (n == null || guard) return array[0]; - if (n < 0) return []; - return slice.call(array, 0, n); - }; - - // Returns everything but the last entry of the array. Especially useful on - // the arguments object. Passing **n** will return all the values in - // the array, excluding the last N. The **guard** check allows it to work with - // `_.map`. - _.initial = function(array, n, guard) { - return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); - }; - - // Get the last element of an array. Passing **n** will return the last N - // values in the array. The **guard** check allows it to work with `_.map`. - _.last = function(array, n, guard) { - if (array == null) return void 0; - if (n == null || guard) return array[array.length - 1]; - return slice.call(array, Math.max(array.length - n, 0)); - }; - - // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. - // Especially useful on the arguments object. Passing an **n** will return - // the rest N values in the array. The **guard** - // check allows it to work with `_.map`. - _.rest = _.tail = _.drop = function(array, n, guard) { - return slice.call(array, n == null || guard ? 1 : n); - }; - - // Trim out all falsy values from an array. - _.compact = function(array) { - return _.filter(array, _.identity); - }; - - // Internal implementation of a recursive `flatten` function. - var flatten = function(input, shallow, strict, output) { - if (shallow && _.every(input, _.isArray)) { - return concat.apply(output, input); - } - for (var i = 0, length = input.length; i < length; i++) { - var value = input[i]; - if (!_.isArray(value) && !_.isArguments(value)) { - if (!strict) output.push(value); - } else if (shallow) { - push.apply(output, value); - } else { - flatten(value, shallow, strict, output); - } - } - return output; - }; - - // Flatten out an array, either recursively (by default), or just one level. - _.flatten = function(array, shallow) { - return flatten(array, shallow, false, []); - }; - - // Return a version of the array that does not contain the specified value(s). - _.without = function(array) { - return _.difference(array, slice.call(arguments, 1)); - }; - - // Produce a duplicate-free version of the array. If the array has already - // been sorted, you have the option of using a faster algorithm. - // Aliased as `unique`. - _.uniq = _.unique = function(array, isSorted, iteratee, context) { - if (array == null) return []; - if (!_.isBoolean(isSorted)) { - context = iteratee; - iteratee = isSorted; - isSorted = false; - } - if (iteratee != null) iteratee = _.iteratee(iteratee, context); - var result = []; - var seen = []; - for (var i = 0, length = array.length; i < length; i++) { - var value = array[i]; - if (isSorted) { - if (!i || seen !== value) result.push(value); - seen = value; - } else if (iteratee) { - var computed = iteratee(value, i, array); - if (_.indexOf(seen, computed) < 0) { - seen.push(computed); - result.push(value); - } - } else if (_.indexOf(result, value) < 0) { - result.push(value); - } - } - return result; - }; - - // Produce an array that contains the union: each distinct element from all of - // the passed-in arrays. - _.union = function() { - return _.uniq(flatten(arguments, true, true, [])); - }; - - // Produce an array that contains every item shared between all the - // passed-in arrays. - _.intersection = function(array) { - if (array == null) return []; - var result = []; - var argsLength = arguments.length; - for (var i = 0, length = array.length; i < length; i++) { - var item = array[i]; - if (_.contains(result, item)) continue; - for (var j = 1; j < argsLength; j++) { - if (!_.contains(arguments[j], item)) break; - } - if (j === argsLength) result.push(item); - } - return result; - }; - - // Take the difference between one array and a number of other arrays. - // Only the elements present in just the first array will remain. - _.difference = function(array) { - var rest = flatten(slice.call(arguments, 1), true, true, []); - return _.filter(array, function(value){ - return !_.contains(rest, value); - }); - }; - - // Zip together multiple lists into a single array -- elements that share - // an index go together. - _.zip = function(array) { - if (array == null) return []; - var length = _.max(arguments, 'length').length; - var results = Array(length); - for (var i = 0; i < length; i++) { - results[i] = _.pluck(arguments, i); - } - return results; - }; - - // Converts lists into objects. Pass either a single array of `[key, value]` - // pairs, or two parallel arrays of the same length -- one of keys, and one of - // the corresponding values. - _.object = function(list, values) { - if (list == null) return {}; - var result = {}; - for (var i = 0, length = list.length; i < length; i++) { - if (values) { - result[list[i]] = values[i]; - } else { - result[list[i][0]] = list[i][1]; - } - } - return result; - }; - - // Return the position of the first occurrence of an item in an array, - // or -1 if the item is not included in the array. - // If the array is large and already in sort order, pass `true` - // for **isSorted** to use binary search. - _.indexOf = function(array, item, isSorted) { - if (array == null) return -1; - var i = 0, length = array.length; - if (isSorted) { - if (typeof isSorted == 'number') { - i = isSorted < 0 ? Math.max(0, length + isSorted) : isSorted; - } else { - i = _.sortedIndex(array, item); - return array[i] === item ? i : -1; - } - } - for (; i < length; i++) if (array[i] === item) return i; - return -1; - }; - - _.lastIndexOf = function(array, item, from) { - if (array == null) return -1; - var idx = array.length; - if (typeof from == 'number') { - idx = from < 0 ? idx + from + 1 : Math.min(idx, from + 1); - } - while (--idx >= 0) if (array[idx] === item) return idx; - return -1; - }; - - // Generate an integer Array containing an arithmetic progression. A port of - // the native Python `range()` function. See - // [the Python documentation](http://docs.python.org/library/functions.html#range). - _.range = function(start, stop, step) { - if (arguments.length <= 1) { - stop = start || 0; - start = 0; - } - step = step || 1; - - var length = Math.max(Math.ceil((stop - start) / step), 0); - var range = Array(length); - - for (var idx = 0; idx < length; idx++, start += step) { - range[idx] = start; - } - - return range; - }; - - // Function (ahem) Functions - // ------------------ - - // Reusable constructor function for prototype setting. - var Ctor = function(){}; - - // Create a function bound to a given object (assigning `this`, and arguments, - // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if - // available. - _.bind = function(func, context) { - var args, bound; - if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); - if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); - args = slice.call(arguments, 2); - bound = function() { - if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); - Ctor.prototype = func.prototype; - var self = new Ctor; - Ctor.prototype = null; - var result = func.apply(self, args.concat(slice.call(arguments))); - if (_.isObject(result)) return result; - return self; - }; - return bound; - }; - - // Partially apply a function by creating a version that has had some of its - // arguments pre-filled, without changing its dynamic `this` context. _ acts - // as a placeholder, allowing any combination of arguments to be pre-filled. - _.partial = function(func) { - var boundArgs = slice.call(arguments, 1); - return function() { - var position = 0; - var args = boundArgs.slice(); - for (var i = 0, length = args.length; i < length; i++) { - if (args[i] === _) args[i] = arguments[position++]; - } - while (position < arguments.length) args.push(arguments[position++]); - return func.apply(this, args); - }; - }; - - // Bind a number of an object's methods to that object. Remaining arguments - // are the method names to be bound. Useful for ensuring that all callbacks - // defined on an object belong to it. - _.bindAll = function(obj) { - var i, length = arguments.length, key; - if (length <= 1) throw new Error('bindAll must be passed function names'); - for (i = 1; i < length; i++) { - key = arguments[i]; - obj[key] = _.bind(obj[key], obj); - } - return obj; - }; - - // Memoize an expensive function by storing its results. - _.memoize = function(func, hasher) { - var memoize = function(key) { - var cache = memoize.cache; - var address = hasher ? hasher.apply(this, arguments) : key; - if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); - return cache[address]; - }; - memoize.cache = {}; - return memoize; - }; - - // Delays a function for the given number of milliseconds, and then calls - // it with the arguments supplied. - _.delay = function(func, wait) { - var args = slice.call(arguments, 2); - return setTimeout(function(){ - return func.apply(null, args); - }, wait); - }; - - // Defers a function, scheduling it to run after the current call stack has - // cleared. - _.defer = function(func) { - return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); - }; - - // Returns a function, that, when invoked, will only be triggered at most once - // during a given window of time. Normally, the throttled function will run - // as much as it can, without ever going more than once per `wait` duration; - // but if you'd like to disable the execution on the leading edge, pass - // `{leading: false}`. To disable execution on the trailing edge, ditto. - _.throttle = function(func, wait, options) { - var context, args, result; - var timeout = null; - var previous = 0; - if (!options) options = {}; - var later = function() { - previous = options.leading === false ? 0 : _.now(); - timeout = null; - result = func.apply(context, args); - if (!timeout) context = args = null; - }; - return function() { - var now = _.now(); - if (!previous && options.leading === false) previous = now; - var remaining = wait - (now - previous); - context = this; - args = arguments; - if (remaining <= 0 || remaining > wait) { - clearTimeout(timeout); - timeout = null; - previous = now; - result = func.apply(context, args); - if (!timeout) context = args = null; - } else if (!timeout && options.trailing !== false) { - timeout = setTimeout(later, remaining); - } - return result; - }; - }; - - // Returns a function, that, as long as it continues to be invoked, will not - // be triggered. The function will be called after it stops being called for - // N milliseconds. If `immediate` is passed, trigger the function on the - // leading edge, instead of the trailing. - _.debounce = function(func, wait, immediate) { - var timeout, args, context, timestamp, result; - - var later = function() { - var last = _.now() - timestamp; - - if (last < wait && last > 0) { - timeout = setTimeout(later, wait - last); - } else { - timeout = null; - if (!immediate) { - result = func.apply(context, args); - if (!timeout) context = args = null; - } - } - }; - - return function() { - context = this; - args = arguments; - timestamp = _.now(); - var callNow = immediate && !timeout; - if (!timeout) timeout = setTimeout(later, wait); - if (callNow) { - result = func.apply(context, args); - context = args = null; - } - - return result; - }; - }; - - // Returns the first function passed as an argument to the second, - // allowing you to adjust arguments, run code before and after, and - // conditionally execute the original function. - _.wrap = function(func, wrapper) { - return _.partial(wrapper, func); - }; - - // Returns a negated version of the passed-in predicate. - _.negate = function(predicate) { - return function() { - return !predicate.apply(this, arguments); - }; - }; - - // Returns a function that is the composition of a list of functions, each - // consuming the return value of the function that follows. - _.compose = function() { - var args = arguments; - var start = args.length - 1; - return function() { - var i = start; - var result = args[start].apply(this, arguments); - while (i--) result = args[i].call(this, result); - return result; - }; - }; - - // Returns a function that will only be executed after being called N times. - _.after = function(times, func) { - return function() { - if (--times < 1) { - return func.apply(this, arguments); - } - }; - }; - - // Returns a function that will only be executed before being called N times. - _.before = function(times, func) { - var memo; - return function() { - if (--times > 0) { - memo = func.apply(this, arguments); - } else { - func = null; - } - return memo; - }; - }; - - // Returns a function that will be executed at most one time, no matter how - // often you call it. Useful for lazy initialization. - _.once = _.partial(_.before, 2); - - // Object Functions - // ---------------- - - // Retrieve the names of an object's properties. - // Delegates to **ECMAScript 5**'s native `Object.keys` - _.keys = function(obj) { - if (!_.isObject(obj)) return []; - if (nativeKeys) return nativeKeys(obj); - var keys = []; - for (var key in obj) if (_.has(obj, key)) keys.push(key); - return keys; - }; - - // Retrieve the values of an object's properties. - _.values = function(obj) { - var keys = _.keys(obj); - var length = keys.length; - var values = Array(length); - for (var i = 0; i < length; i++) { - values[i] = obj[keys[i]]; - } - return values; - }; - - // Convert an object into a list of `[key, value]` pairs. - _.pairs = function(obj) { - var keys = _.keys(obj); - var length = keys.length; - var pairs = Array(length); - for (var i = 0; i < length; i++) { - pairs[i] = [keys[i], obj[keys[i]]]; - } - return pairs; - }; - - // Invert the keys and values of an object. The values must be serializable. - _.invert = function(obj) { - var result = {}; - var keys = _.keys(obj); - for (var i = 0, length = keys.length; i < length; i++) { - result[obj[keys[i]]] = keys[i]; - } - return result; - }; - - // Return a sorted list of the function names available on the object. - // Aliased as `methods` - _.functions = _.methods = function(obj) { - var names = []; - for (var key in obj) { - if (_.isFunction(obj[key])) names.push(key); - } - return names.sort(); - }; - - // Extend a given object with all the properties in passed-in object(s). - _.extend = function(obj) { - if (!_.isObject(obj)) return obj; - var source, prop; - for (var i = 1, length = arguments.length; i < length; i++) { - source = arguments[i]; - for (prop in source) { - if (hasOwnProperty.call(source, prop)) { - obj[prop] = source[prop]; - } - } - } - return obj; - }; - - // Return a copy of the object only containing the whitelisted properties. - _.pick = function(obj, iteratee, context) { - var result = {}, key; - if (obj == null) return result; - if (_.isFunction(iteratee)) { - iteratee = createCallback(iteratee, context); - for (key in obj) { - var value = obj[key]; - if (iteratee(value, key, obj)) result[key] = value; - } - } else { - var keys = concat.apply([], slice.call(arguments, 1)); - obj = new Object(obj); - for (var i = 0, length = keys.length; i < length; i++) { - key = keys[i]; - if (key in obj) result[key] = obj[key]; - } - } - return result; - }; - - // Return a copy of the object without the blacklisted properties. - _.omit = function(obj, iteratee, context) { - if (_.isFunction(iteratee)) { - iteratee = _.negate(iteratee); - } else { - var keys = _.map(concat.apply([], slice.call(arguments, 1)), String); - iteratee = function(value, key) { - return !_.contains(keys, key); - }; - } - return _.pick(obj, iteratee, context); - }; - - // Fill in a given object with default properties. - _.defaults = function(obj) { - if (!_.isObject(obj)) return obj; - for (var i = 1, length = arguments.length; i < length; i++) { - var source = arguments[i]; - for (var prop in source) { - if (obj[prop] === void 0) obj[prop] = source[prop]; - } - } - return obj; - }; - - // Create a (shallow-cloned) duplicate of an object. - _.clone = function(obj) { - if (!_.isObject(obj)) return obj; - return _.isArray(obj) ? obj.slice() : _.extend({}, obj); - }; - - // Invokes interceptor with the obj, and then returns obj. - // The primary purpose of this method is to "tap into" a method chain, in - // order to perform operations on intermediate results within the chain. - _.tap = function(obj, interceptor) { - interceptor(obj); - return obj; - }; - - // Internal recursive comparison function for `isEqual`. - var eq = function(a, b, aStack, bStack) { - // Identical objects are equal. `0 === -0`, but they aren't identical. - // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). - if (a === b) return a !== 0 || 1 / a === 1 / b; - // A strict comparison is necessary because `null == undefined`. - if (a == null || b == null) return a === b; - // Unwrap any wrapped objects. - if (a instanceof _) a = a._wrapped; - if (b instanceof _) b = b._wrapped; - // Compare `[[Class]]` names. - var className = toString.call(a); - if (className !== toString.call(b)) return false; - switch (className) { - // Strings, numbers, regular expressions, dates, and booleans are compared by value. - case '[object RegExp]': - // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') - case '[object String]': - // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is - // equivalent to `new String("5")`. - return '' + a === '' + b; - case '[object Number]': - // `NaN`s are equivalent, but non-reflexive. - // Object(NaN) is equivalent to NaN - if (+a !== +a) return +b !== +b; - // An `egal` comparison is performed for other numeric values. - return +a === 0 ? 1 / +a === 1 / b : +a === +b; - case '[object Date]': - case '[object Boolean]': - // Coerce dates and booleans to numeric primitive values. Dates are compared by their - // millisecond representations. Note that invalid dates with millisecond representations - // of `NaN` are not equivalent. - return +a === +b; - } - if (typeof a != 'object' || typeof b != 'object') return false; - // Assume equality for cyclic structures. The algorithm for detecting cyclic - // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. - var length = aStack.length; - while (length--) { - // Linear search. Performance is inversely proportional to the number of - // unique nested structures. - if (aStack[length] === a) return bStack[length] === b; - } - // Objects with different constructors are not equivalent, but `Object`s - // from different frames are. - var aCtor = a.constructor, bCtor = b.constructor; - if ( - aCtor !== bCtor && - // Handle Object.create(x) cases - 'constructor' in a && 'constructor' in b && - !(_.isFunction(aCtor) && aCtor instanceof aCtor && - _.isFunction(bCtor) && bCtor instanceof bCtor) - ) { - return false; - } - // Add the first object to the stack of traversed objects. - aStack.push(a); - bStack.push(b); - var size, result; - // Recursively compare objects and arrays. - if (className === '[object Array]') { - // Compare array lengths to determine if a deep comparison is necessary. - size = a.length; - result = size === b.length; - if (result) { - // Deep compare the contents, ignoring non-numeric properties. - while (size--) { - if (!(result = eq(a[size], b[size], aStack, bStack))) break; - } - } - } else { - // Deep compare objects. - var keys = _.keys(a), key; - size = keys.length; - // Ensure that both objects contain the same number of properties before comparing deep equality. - result = _.keys(b).length === size; - if (result) { - while (size--) { - // Deep compare each member - key = keys[size]; - if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; - } - } - } - // Remove the first object from the stack of traversed objects. - aStack.pop(); - bStack.pop(); - return result; - }; - - // Perform a deep comparison to check if two objects are equal. - _.isEqual = function(a, b) { - return eq(a, b, [], []); - }; - - // Is a given array, string, or object empty? - // An "empty" object has no enumerable own-properties. - _.isEmpty = function(obj) { - if (obj == null) return true; - if (_.isArray(obj) || _.isString(obj) || _.isArguments(obj)) return obj.length === 0; - for (var key in obj) if (_.has(obj, key)) return false; - return true; - }; - - // Is a given value a DOM element? - _.isElement = function(obj) { - return !!(obj && obj.nodeType === 1); - }; - - // Is a given value an array? - // Delegates to ECMA5's native Array.isArray - _.isArray = nativeIsArray || function(obj) { - return toString.call(obj) === '[object Array]'; - }; - - // Is a given variable an object? - _.isObject = function(obj) { - var type = typeof obj; - return type === 'function' || type === 'object' && !!obj; - }; - - // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. - _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { - _['is' + name] = function(obj) { - return toString.call(obj) === '[object ' + name + ']'; - }; - }); - - // Define a fallback version of the method in browsers (ahem, IE), where - // there isn't any inspectable "Arguments" type. - if (!_.isArguments(arguments)) { - _.isArguments = function(obj) { - return _.has(obj, 'callee'); - }; - } - - // Optimize `isFunction` if appropriate. Work around an IE 11 bug. - if (typeof /./ !== 'function') { - _.isFunction = function(obj) { - return typeof obj == 'function' || false; - }; - } - - // Is a given object a finite number? - _.isFinite = function(obj) { - return isFinite(obj) && !isNaN(parseFloat(obj)); - }; - - // Is the given value `NaN`? (NaN is the only number which does not equal itself). - _.isNaN = function(obj) { - return _.isNumber(obj) && obj !== +obj; - }; - - // Is a given value a boolean? - _.isBoolean = function(obj) { - return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; - }; - - // Is a given value equal to null? - _.isNull = function(obj) { - return obj === null; - }; - - // Is a given variable undefined? - _.isUndefined = function(obj) { - return obj === void 0; - }; - - // Shortcut function for checking if an object has a given property directly - // on itself (in other words, not on a prototype). - _.has = function(obj, key) { - return obj != null && hasOwnProperty.call(obj, key); - }; - - // Utility Functions - // ----------------- - - // Run Underscore.js in *noConflict* mode, returning the `_` variable to its - // previous owner. Returns a reference to the Underscore object. - _.noConflict = function() { - root._ = previousUnderscore; - return this; - }; - - // Keep the identity function around for default iteratees. - _.identity = function(value) { - return value; - }; - - _.constant = function(value) { - return function() { - return value; - }; - }; - - _.noop = function(){}; - - _.property = function(key) { - return function(obj) { - return obj[key]; - }; - }; - - // Returns a predicate for checking whether an object has a given set of `key:value` pairs. - _.matches = function(attrs) { - var pairs = _.pairs(attrs), length = pairs.length; - return function(obj) { - if (obj == null) return !length; - obj = new Object(obj); - for (var i = 0; i < length; i++) { - var pair = pairs[i], key = pair[0]; - if (pair[1] !== obj[key] || !(key in obj)) return false; - } - return true; - }; - }; - - // Run a function **n** times. - _.times = function(n, iteratee, context) { - var accum = Array(Math.max(0, n)); - iteratee = createCallback(iteratee, context, 1); - for (var i = 0; i < n; i++) accum[i] = iteratee(i); - return accum; - }; - - // Return a random integer between min and max (inclusive). - _.random = function(min, max) { - if (max == null) { - max = min; - min = 0; - } - return min + Math.floor(Math.random() * (max - min + 1)); - }; - - // A (possibly faster) way to get the current timestamp as an integer. - _.now = Date.now || function() { - return new Date().getTime(); - }; - - // List of HTML entities for escaping. - var escapeMap = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - '`': '`' - }; - var unescapeMap = _.invert(escapeMap); - - // Functions for escaping and unescaping strings to/from HTML interpolation. - var createEscaper = function(map) { - var escaper = function(match) { - return map[match]; - }; - // Regexes for identifying a key that needs to be escaped - var source = '(?:' + _.keys(map).join('|') + ')'; - var testRegexp = RegExp(source); - var replaceRegexp = RegExp(source, 'g'); - return function(string) { - string = string == null ? '' : '' + string; - return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; - }; - }; - _.escape = createEscaper(escapeMap); - _.unescape = createEscaper(unescapeMap); - - // If the value of the named `property` is a function then invoke it with the - // `object` as context; otherwise, return it. - _.result = function(object, property) { - if (object == null) return void 0; - var value = object[property]; - return _.isFunction(value) ? object[property]() : value; - }; - - // Generate a unique integer id (unique within the entire client session). - // Useful for temporary DOM ids. - var idCounter = 0; - _.uniqueId = function(prefix) { - var id = ++idCounter + ''; - return prefix ? prefix + id : id; - }; - - // By default, Underscore uses ERB-style template delimiters, change the - // following template settings to use alternative delimiters. - _.templateSettings = { - evaluate : /<%([\s\S]+?)%>/g, - interpolate : /<%=([\s\S]+?)%>/g, - escape : /<%-([\s\S]+?)%>/g - }; - - // When customizing `templateSettings`, if you don't want to define an - // interpolation, evaluation or escaping regex, we need one that is - // guaranteed not to match. - var noMatch = /(.)^/; - - // Certain characters need to be escaped so that they can be put into a - // string literal. - var escapes = { - "'": "'", - '\\': '\\', - '\r': 'r', - '\n': 'n', - '\u2028': 'u2028', - '\u2029': 'u2029' - }; - - var escaper = /\\|'|\r|\n|\u2028|\u2029/g; - - var escapeChar = function(match) { - return '\\' + escapes[match]; - }; - - // JavaScript micro-templating, similar to John Resig's implementation. - // Underscore templating handles arbitrary delimiters, preserves whitespace, - // and correctly escapes quotes within interpolated code. - // NB: `oldSettings` only exists for backwards compatibility. - _.template = function(text, settings, oldSettings) { - if (!settings && oldSettings) settings = oldSettings; - settings = _.defaults({}, settings, _.templateSettings); - - // Combine delimiters into one regular expression via alternation. - var matcher = RegExp([ - (settings.escape || noMatch).source, - (settings.interpolate || noMatch).source, - (settings.evaluate || noMatch).source - ].join('|') + '|$', 'g'); - - // Compile the template source, escaping string literals appropriately. - var index = 0; - var source = "__p+='"; - text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { - source += text.slice(index, offset).replace(escaper, escapeChar); - index = offset + match.length; - - if (escape) { - source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; - } else if (interpolate) { - source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; - } else if (evaluate) { - source += "';\n" + evaluate + "\n__p+='"; - } - - // Adobe VMs need the match returned to produce the correct offest. - return match; - }); - source += "';\n"; - - // If a variable is not specified, place data values in local scope. - if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; - - source = "var __t,__p='',__j=Array.prototype.join," + - "print=function(){__p+=__j.call(arguments,'');};\n" + - source + 'return __p;\n'; - - try { - var render = new Function(settings.variable || 'obj', '_', source); - } catch (e) { - e.source = source; - throw e; - } - - var template = function(data) { - return render.call(this, data, _); - }; - - // Provide the compiled source as a convenience for precompilation. - var argument = settings.variable || 'obj'; - template.source = 'function(' + argument + '){\n' + source + '}'; - - return template; - }; - - // Add a "chain" function. Start chaining a wrapped Underscore object. - _.chain = function(obj) { - var instance = _(obj); - instance._chain = true; - return instance; - }; - - // OOP - // --------------- - // If Underscore is called as a function, it returns a wrapped object that - // can be used OO-style. This wrapper holds altered versions of all the - // underscore functions. Wrapped objects may be chained. - - // Helper function to continue chaining intermediate results. - var result = function(obj) { - return this._chain ? _(obj).chain() : obj; - }; - - // Add your own custom functions to the Underscore object. - _.mixin = function(obj) { - _.each(_.functions(obj), function(name) { - var func = _[name] = obj[name]; - _.prototype[name] = function() { - var args = [this._wrapped]; - push.apply(args, arguments); - return result.call(this, func.apply(_, args)); - }; - }); - }; - - // Add all of the Underscore functions to the wrapper object. - _.mixin(_); - - // Add all mutator Array functions to the wrapper. - _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { - var method = ArrayProto[name]; - _.prototype[name] = function() { - var obj = this._wrapped; - method.apply(obj, arguments); - if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; - return result.call(this, obj); - }; - }); - - // Add all accessor Array functions to the wrapper. - _.each(['concat', 'join', 'slice'], function(name) { - var method = ArrayProto[name]; - _.prototype[name] = function() { - return result.call(this, method.apply(this._wrapped, arguments)); - }; - }); - - // Extracts the result from a wrapped and chained object. - _.prototype.value = function() { - return this._wrapped; - }; - - // AMD registration happens at the end for compatibility with AMD loaders - // that may not enforce next-turn semantics on modules. Even though general - // practice for AMD registration is to be anonymous, underscore registers - // as a named module because, like jQuery, it is a base library that is - // popular enough to be bundled in a third party lib, but not be part of - // an AMD load request. Those cases could generate an error when an - // anonymous define() is called outside of a loader request. - if (typeof define === 'function' && define.amd) { - define('underscore', [], function() { - return _; - }); - } -}.call(this)); diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/package.json b/puddle-editor/source/lib/puddle-syntax-0.1.2/package.json deleted file mode 100644 index f5ea8a9..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/package.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "puddle-syntax", - "version": "0.1.2", - "description": "Syntax tools the Puddle coding environment", - "main": "index.js", - "dependencies": { - "debug": "^1.0.4", - "underscore": "^1.6.0" - }, - "devDependencies": { - "jshint": "^2.5.4", - "mocha": "^1.21.4" - }, - "scripts": { - "lint": "jshint lib test *.js", - "test": "npm run lint && mocha" - }, - "repository": { - "type": "git", - "url": "https://github.com/fritzo/puddle-lang.git" - }, - "keywords": [ - "pomagma", - "puddle", - "language" - ], - "author": { - "name": "Fritz Obermeyer" - }, - "license": "MIT", - "bugs": { - "url": "https://github.com/fritzo/puddle-lang/issues" - }, - "homepage": "https://github.com/fritzo/puddle-lang", - "gitHead": "4e02cfb91b220ed22a2bd5702152beaa332e6040", - "_id": "puddle-syntax@0.1.2", - "_shasum": "de80a31dff1579dda918727c9f2b3e4ccf216ecf", - "_from": "puddle-syntax@0.1.2", - "_npmVersion": "1.4.23", - "_npmUser": { - "name": "fritzo", - "email": "fritz.obermeyer@gmail.com" - }, - "maintainers": [ - { - "name": "fritzo", - "email": "fritz.obermeyer@gmail.com" - } - ], - "dist": { - "shasum": "de80a31dff1579dda918727c9f2b3e4ccf216ecf", - "tarball": "http://registry.npmjs.org/puddle-syntax/-/puddle-syntax-0.1.2.tgz" - }, - "directories": {}, - "_resolved": "https://registry.npmjs.org/puddle-syntax/-/puddle-syntax-0.1.2.tgz" -} diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/test/lib/datasets.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/test/lib/datasets.js deleted file mode 100644 index 70ab492..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/test/lib/datasets.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -var stats = require('../../lib/stats'); -var grammar = require('../../lib/grammar'); -var _ = require('underscore'); -var compiler = require('../../lib/compiler'); -var tree = require('../../lib/tree'); - -var SAMPLE_COUNT = 1000; - -var codes = stats.sampleUnique(grammar.sampleCode, SAMPLE_COUNT); -var terms = _.map(codes, compiler.load); -//var lines = _.map(codes, compiler.loadLine); -var nodes = _.map(terms, tree.load); - -module.exports = { - codes: codes, - terms: terms, - //lines: lines, - nodes: nodes, -}; diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/test/main.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/test/main.js deleted file mode 100644 index f88432b..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/test/main.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -var mocha = require('mocha'); -var test = require('../lib/test.js'); -require('../index.js'); - -test.runAll(mocha); diff --git a/puddle-editor/source/lib/puddle-syntax-0.1.2/test/pretty.js b/puddle-editor/source/lib/puddle-syntax-0.1.2/test/pretty.js deleted file mode 100644 index fd59ba4..0000000 --- a/puddle-editor/source/lib/puddle-syntax-0.1.2/test/pretty.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -var _ = require('underscore'); -var assert = require('assert'); -var mocha = require('mocha'); -var compiler = require('../lib/compiler'); -var pretty = require('../lib/pretty'); -var datasets = require('./lib/datasets'); - -mocha.test('pretty is one-to-one', function () { - var seen = {}; - datasets.codes.forEach(function (code) { - var term = compiler.load(code); - var result = pretty(term); - assert( - !_.has(seen, result), - 'pretty conflict:' + - '\n ' + seen[result] + - '\n ' + code + - '\nboth map to ' + - '\n ' + result); - seen[result] = code; - }); -}); diff --git a/puddle-editor/source/lib/view.js b/puddle-editor/source/lib/view.js deleted file mode 100644 index 782a55a..0000000 --- a/puddle-editor/source/lib/view.js +++ /dev/null @@ -1,70 +0,0 @@ -'use strict'; - -var _ = require('lodash'); -var $ = require('jquery'); -var syntax = require('./puddle-syntax-0.1.2'); -var renderTerm = require('./render-term.js'); -var renderValidity = require('./render-validity.js'); - -var $lines = {}; // id -> dom node -var getLine; -var getValidity; -var events; - -var init = function (config) { - $lines = {}; - getLine = config.getLine; - getValidity = config.getValidity; - events = config.events || {}; - var div = $('#code').empty()[0]; - var lines = sortLines(config.lines); - lines.forEach(function (id) { - $lines[id] = $('
').attr('id', 'line' + id).appendTo(div);
-        update(id);
-    });
-};
-
-var update = function (id) {
-    var line = getLine(id);
-    var validity = getValidity(id);
-    var $line = $lines[id];
-    line = syntax.compiler.parenthesize(line);
-    $line.html(renderValidity(validity) + renderTerm(line));
-    _.each(events, function (callback, eventName) {
-        $line.on(eventName, function () {
-            callback(id);
-        });
-    });
-};
-
-var sortLines = function (lines) {
-    /*
-     Return a heuristically sorted list of definitions.
-
-     TODO use approximately topologically-sorted order.
-     (R1) "A Technique for Drawing Directed Graphs" -Gansner et al
-     http://www.graphviz.org/Documentation/TSE93.pdf
-     (R2) "Combinatorial Algorithms for Feedback Problems in Directed Graphs"
-     -Demetrescu and Finocchi
-     http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.1.9435
-     */
-    return lines;
-};
-
-var insertAfter = function (prevId, id) {
-    var $prev = $lines[prevId];
-    $lines[id] = $('
').attr('id', 'line' + id).insertAfter($prev);
-    update(id);
-};
-
-var remove = function (id) {
-    $lines[id].remove();
-    delete $lines[id];
-};
-
-module.exports = {
-    init: init,
-    insertAfter: insertAfter,
-    update: update,
-    remove: remove
-};
diff --git a/puddle-server/server/server.js b/puddle-server/server/server.js
index 11727c6..00d6b04 100644
--- a/puddle-server/server/server.js
+++ b/puddle-server/server/server.js
@@ -88,6 +88,14 @@ http.listen(PORT, FROM_LOCALHOST, function () {
     console.log('serving puddle at http://localhost:' + PORT);
 });
 
+io.on('connection', function (socket) {
+    console.log('a user connected');
+    socket.on('action', function (action) {
+        debug('Action', action);
+    });
+
+});
+
 
 
 

From ff218371c8d1d15d249890514b94b230564abeea Mon Sep 17 00:00:00 2001
From: yangit 
Date: Tue, 21 Oct 2014 09:59:04 +0800
Subject: [PATCH 3/7] This is a big change to client side libraries API.
 Editor.js is now split among cursor.js, menu-builder.js, forest.js Event
 listener is used across libraries to notify each other of events. See
 reference.md for better description.

---
 README.md                                     |  35 +-
 doc/reference.md                              | 173 ++----
 package.json                                  |   5 +-
 puddle-editor/readme.md                       |   4 +-
 puddle-editor/source/app/corpus.js            | 550 ++++++++++--------
 puddle-editor/source/app/cursor.js            | 193 ++++++
 puddle-editor/source/app/editor.js            | 366 ------------
 puddle-editor/source/app/forest.js            | 106 ++++
 puddle-editor/source/app/main.js              |  60 +-
 puddle-editor/source/app/menu-builder.js      | 183 ++++++
 puddle-editor/source/app/menu-renderer.js     | 162 ++++++
 puddle-editor/source/app/menu/menu.js         | 151 -----
 puddle-editor/source/app/menu/menuRenderer.js | 164 ------
 puddle-editor/source/app/render-validity.js   |   3 +-
 puddle-editor/source/app/server-syntax.js     |   8 +
 puddle-editor/source/app/trace.js             |   2 +-
 puddle-editor/source/app/view.js              | 121 +++-
 puddle-server/package.json                    |   1 +
 puddle-server/readme.md                       |   2 +-
 puddle-server/server/server.js                |  48 +-
 20 files changed, 1187 insertions(+), 1150 deletions(-)
 create mode 100644 puddle-editor/source/app/cursor.js
 delete mode 100644 puddle-editor/source/app/editor.js
 create mode 100644 puddle-editor/source/app/forest.js
 create mode 100644 puddle-editor/source/app/menu-builder.js
 create mode 100644 puddle-editor/source/app/menu-renderer.js
 delete mode 100644 puddle-editor/source/app/menu/menu.js
 delete mode 100644 puddle-editor/source/app/menu/menuRenderer.js

diff --git a/README.md b/README.md
index 765294c..e51226d 100644
--- a/README.md
+++ b/README.md
@@ -27,22 +27,33 @@ Please refer to the [./doc](./doc) for features, architecture, contributing, etc
 
 ## Quick Start
 
-Install:
- 
-    $ npm install
-Run:
- 
-    $ npm start
+###Install:
+
+    cd puddle-server
+    npm install
+    cd ..
+    cd puddle-editor
+    npm install
+
+###Run:
+   Two terminal windows:
+
+   One:
+
+    cd puddle-server
+    npm run develop            # Ctrl-C to stop
+
+   Two:
+
+    cd puddle-editor
+    npm run develop            # Ctrl-C to stop
 
 Then navigate to 
     
-## Develop mode:
-    
-    $ npm run develop-editor       # Ctrl-C to stop
-        
-In another terminal
+## Deploy mode:
+
+  Same as above but you can use `npm start` istead of `npm run develop`
 
-    $ npm run develop-server       # Ctrl-C to stop
 
 
 
diff --git a/doc/reference.md b/doc/reference.md
index b88e68a..dc041f8 100644
--- a/doc/reference.md
+++ b/doc/reference.md
@@ -5,20 +5,18 @@
 * [Server socket.io API](#server-socketio)
 * [Client `TODO.js`](#todojs)
 * [Client `assert.js`](#assertjs)
-* [Client `log.js`](#logjs)
-* [Client `keycode.js`](#keycodejs)
-* [Client `test.js`](#testjs)
+* [Client `trace.js`](#tracejs)
 * [Client `corpus.js`](#corpusjs)
-* [Client `navigate.js`](#navigatejs)
+* [Client `forest.js`](#forestjs)
+* [Client `cursor.js`](#cursorjs)
 * [Client `view.js`](#viewjs)
-* [Client `menu.js`](#menujs)
-* [Client `editor.js`](#editorjs)
-* [Client `main.js`](#mainjs)
+* [Client `menu-builder.js`](#menubuilder)
+* [Client `menu-renderer.js`](#menurenderer)
 
 Related documentation:
 
-* [puddle-syntax](https://github.com/fritzo/puddle-syntax)
-* [pomagma client](https://github.com/fritzo/pomagma/blob/master/doc/client.md)
+* [puddle-syntax](https://github.com/pomagma/puddle-syntax)
+* [pomagma client](https://github.com/pomagma/blob/master/doc/client.md)
 
 ## Server
 
@@ -131,118 +129,73 @@ Related documentation:
     var assert = require('./assert');
     assert(condition, message);
     assert.equal(actual, expected);
-    assert.forward(fwd, listOfPairs);  // fwd(x) === y for each pair [x, y]
-    assert.backward(bwd, listOfPairs);  // x === bwd(y) for each pair [x, y]
-    assert.inverses(fwd, bwd, listOfPairs);  // fwd(x) === y && x === bwd(y)
 
-### `log.js` 
+### `trace.js` 
 
-    var log = require('./log');
-    log('just like console.log, bug works in web workers');
+Convenience method for debugging.
+Allows to quikly show trace and arguments passed into function.
 
-### `keycode.js` 
-
-Just a static dictionary of ascii key codes.
-
-    {
-        'backspace': 8,
-        'tab': 9,
-        'enter': 13,
-        'shift': 16,
-        ...
-    }
-
-### `test.js` 
-
-Unit testing library.
-
-    var test = require('./test');
-
-    test('test title', callback);       // declares synchronous test
-    test.async('test title', callback); // declares async test
-    test.runAll();                      // run all unit tests
-
-    console.log(test.testing());        // prints whether tests are being run
-    console.log(test.hasRun());         // prints whether tests have finished
-    console.log(test.testCount());      // prints cumulative test count
-    console.log(test.failCount());      // prints cumulative failed test count
-
-Utilities for performing functions on trees.
-
-    var root = arborist.getRoot(indexed);
-    var varList = arborist.getBoundAbove(term);  // -> ['a', 'b']
-    var varSet = arborist.getVars(term);         // -> {'a': null, 'b': null}
-    var name = arborist.getFresh(term);          // -> 'c'
+    var debug = require('debug')('puddle:editor');
+    var trace = require('./trace')(debug);
+    
+    //Will output to console 'puddle:editor Hello'
+    //arguments converted to array.
+    //if third parameter is true will do console.trace() as well. 
+    //Second and third arguments are optional
+    trace('Hello', arguments, true)
+    
 
 ### `corpus.js` 
+	
+    Uses puddle-socket to fetch data from server
+    Stores corpus as `lines` internally
+    Provides knowledge of defenitions/occurrences
+    Fetches validities, emits 'updateValidity' event.
+    Exposes CRUD API and emits CRUD events for corpus lines.
+    Handles checkin/checkout of lines (w/o real blocking on server yet)
+    Does not care about any other modules.
+	
+
+### `forest.js` 
+
+    Stores corpus as trees internally
+    Keeps trees sorted
+    Listens for CRUD events from Corpus and reemits them as trees
+
+
+### `cursor.js` 
+	
+    Stores/shares pointer to node in a Forest.
+    Does not affect nodes in any way.
+    Exposes various methods to move itself between nodes of Forest
+    Notifies corpus of check-ins, check-outs
+    Exposes .replaceBelow to alter trees.
+    Listens to Forest changes to avoid being in orphan nodes.
+    Emits `move` event.
 
-Editor's view of the server-side corpus.
-Each client stores an in-memory copy.
-
-    var corpus = require('./corpus');
-    corpus.ready(cb);       // calls cb after client loads corpus from server
-    corpus.validate();      // validates corpus, throws AssertError if invalid
-    var line = corpus.findLine(id);
-    var lines = corpus.findAllLines();
-    var names = corpus.findAllNames();
-    var id = corpus.findDefinition(name);
-    if (corpus.canDefine(name)) {
-        // we can create a new definition of name
-    }
-    var ids = corpus.findOccurrences(name);
-    if (corpus.hasOccurrences(name)) {
-        // we cannot delete the definition of name
-    }
-    corpus.insert(line, done, fail);
-    corpus.update(newLine);
-    corpus.remove(id);
-
-### `navigate.js` 
-
-    var navigate = require('./navagate');
-    navigate.on(name, callback, description);   // add callback
-    navigate.off();                             // clear callbacks
-    navigate.trigger(event);
-
-    // search for global variable
-    navigate.search(rankedStrings, acceptMatch, cancel, renderString);
-
-    // create new global variable name
-    navigate.choose(isValidFilter, acceptName, cancel);
 
 ### `view.js` 
 
-The view object is the pane on the left showing the corpus.
-
-    var view = require('./view');
-    view.init({
-        getLine: ...,
-        getValidity: ...,
-        lines: ...
-    });
-
-    view.insertAfter(prevId, id);
-    view.update(id);
-    view.remove(id);
-
-### `menu.js` 
-
-The menu object is the pane on the right.
-It rebuilds itself at every action.
-The menu is the sole form of input to puddle, by design.
+    The view object is the pane on the left showing the corpus.
+    Renders corpus using trees from Forest
+    Listens for forest CRUD events.
+    Listens for cursor moves.
+    Listens for validities update.	
+    Uses DOM as internal state.
+    Does not have API   
 
-    var menu = require('./menu');
-    menu.init({
-        actions = {...},                // callbacks bound to actions
-        getCursor = function () {...}   // returns cursor
-    });
 
-### `editor.js` 
+### `menu-builder.js` 
+ 
+    Aware of cursor position in a forest
+    Knows mapping of keys <=> UI actions
+    Builds UI action functions using Forest and Cursor API and module.exports them	 
+    Logs every user action to server via socket.io
+    Does not have internal state.
 
-    var editor = require('./editor');
-    editor.main();                      // start puddle editor
 
-### `main.js` 
+### `menu-renderer.js`  
 
-Main entry point, either starts unit tests or starts editor,
-depending on whether `location.hash === '#test'`.
+    Takes array of actions/keys from menu-builder and renders it into HTML with onKeyPress events.
+    Listens for Cursor 'move' event to rerender menu.
+    Does not have internal state or API
\ No newline at end of file
diff --git a/package.json b/package.json
index 4e77c7d..cc3c8de 100644
--- a/package.json
+++ b/package.json
@@ -4,10 +4,7 @@
   "description": "A responsive editor built on Pomagma",
   "main": "",
   "scripts": {
-    "test": "cd $MODULE; npm install; npm test;",
-    "start": "cd puddle-server; npm start",
-    "develop-server": "cd puddle-server; DEBUG='puddle:*' npm run develop",
-    "develop-editor": "cd puddle-editor; npm run develop"
+    "test": "cd $MODULE; npm install; npm test;"
   },
   "repository": {
     "type": "git",
diff --git a/puddle-editor/readme.md b/puddle-editor/readme.md
index 0fb0d0e..073c69f 100644
--- a/puddle-editor/readme.md
+++ b/puddle-editor/readme.md
@@ -5,8 +5,8 @@ HTML app to be served by puddle-server
 
 ###Features:    
 
-    [ ] Working version of editor, navigator using same render as before
-    [ ] Built on top of puddle-hub
+    [X] Working version of editor, navigator using same render as before
+    [X] Built on top of puddle-hub
     
     
 ###Installation:
diff --git a/puddle-editor/source/app/corpus.js b/puddle-editor/source/app/corpus.js
index fddf09c..d66ada7 100644
--- a/puddle-editor/source/app/corpus.js
+++ b/puddle-editor/source/app/corpus.js
@@ -1,20 +1,23 @@
 /**
  * Corpus of lines of code.
- *
- * FIXME this is all concurrency-unsafe; client assumes it is the only writer.
  */
 'use strict';
 
 var _ = require('lodash');
 var uuid = require('node-uuid');
-var tokens = require('puddle-syntax').tokens;
+var syntax = require('puddle-syntax');
+var tokens = syntax.tokens;
 var assert = require('./assert');
 var debug = require('debug')('puddle:editor:corpus');
-var puddleSocket = global.puddleSocket;
+var io = require('socket.io-client');
+var puddleSocket = require('puddle-socket').client(io);
+var EventEmitter = require('events').EventEmitter;
+var emitter = new EventEmitter();
 var trace = require('./trace')(debug);
+var $ = require('jquery');
 var serverSyntax = require('./server-syntax');
-
-
+trace('corpus init');
+var checkedOut = {};
 //--------------------------------------------------------------------------
 // client state
 
@@ -24,269 +27,356 @@ var serverSyntax = require('./server-syntax');
     'name': 'div',      // or null for anonymous lines
     'code': 'APP V K',  // compiled code
     'free': {}          // set of free variables in line : string -> null
+    'validity': {is_top: null, is_bot: null, pending: true} //default value
 };
  */
 
+
 //--------------------------------------------------------------------------
-// change propagation
-
-
-var sync = {
-    create: function (line) {
-        trace('Sync Create', arguments);
-        puddleSocket.create(line.id, serverSyntax.dumpStatement(line),'editor');
-    },
-    remove: function (line) {
-        trace('Sync Remove', arguments);
-        puddleSocket.remove(line.id,'editor');
-    },
-    update: function (line) {
-        trace('Sync Update', arguments);
-        puddleSocket.update(line.id, serverSyntax.dumpStatement(line),'editor');
+var corpus = {};
+
+// These maps fail with names like 'constructor';
+// as a hack we require a '.' in all names in corpus.
+var lines = corpus.lines = {};  // id -> line
+var definitions = {};  // name -> id
+var occurrences = {};  // name -> (set id)
+
+var insertDefinition = function (name, id) {
+    assert(tokens.isGlobal(name));
+    assert(definitions[name] === undefined);
+    assert(occurrences[name] === undefined);
+    definitions[name] = id;
+    occurrences[name] = {};
+};
+
+var removeDefinition = function (name) {
+    assert(definitions[name] !== undefined);
+    assert(occurrences[name] !== undefined);
+    assert(_.isEmpty(occurrences[name]));
+    delete definitions[name];
+    delete occurrences[name];
+};
+
+var insertOccurrence = function (name, id) {
+    var occurrencesName = occurrences[name];
+    assert(occurrencesName !== undefined);
+    assert(occurrencesName[id] === undefined);
+    occurrences[name][id] = null;
+};
+
+var removeOccurrence = function (name, id) {
+    var occurrencesName = occurrences[name];
+    assert(occurrencesName !== undefined);
+    assert(occurrencesName[id] !== undefined);
+    delete occurrencesName[id];
+};
+
+var createLine = function (line) {
+    trace('insertLine', arguments);
+    var id = line.id;
+    /* jshint camelcase: false */
+    line.validity = {is_top: null, is_bot: null, pending: true};
+    /* jshint camelcase: true */
+    assert(!_.has(lines, id));
+    lines[id] = line;
+    if (line.name !== null) {
+        insertDefinition(line.name, id);
     }
+    line.free = tokens.getFreeVariables(line.code);
+    for (var name in line.free) {
+        insertOccurrence(name, id);
+    }
+    pollValidities();
+    emitter.emit('create', line);
 };
 
+var removeLine = function (id) {
+    trace('remove', arguments);
+    assert(_.has(lines, id));
+    var line = lines[id];
+    delete lines[id];
+    for (var name in line.free) {
+        removeOccurrence(name, id);
+    }
+    if (line.name !== null) {
+        removeDefinition(line.name);
+    }
+    pollValidities();
+    emitter.emit('remove', id);
+};
 
-//--------------------------------------------------------------------------
-// interface
+var updateLine = function (newline) {
+    trace('update', arguments);
+    var name;
+    var id = newline.id;
+    assert(id !== undefined, 'expected .id field in updated line');
+    var line = lines[id];
+    for (name in line.free) {
+        removeOccurrence(name, id);
+    }
+    line.code = newline.code;
+    line.free = tokens.getFreeVariables(line.code);
+    for (name in line.free) {
+        insertOccurrence(name, id);
+    }
+    pollValidities();
+    emitter.emit('update', line);
+};
 
-module.exports = (function () {
-    trace('State init', arguments);
-    var state = {};
 
-    // These maps fail with names like 'constructor';
-    // as a hack we require a '.' in all names in corpus.
-    var lines = state.lines = {};  // id -> line
-    var definitions = state.definitions = {};  // name -> id
-    var occurrences = state.occurrences = {};  // name -> (set id)
+puddleSocket.on('create', function (id, code) {
+    trace('Hub incoming create');
+    var line = serverSyntax.loadStatement(code);
+    line.id = id;
+    createLine(line);
+}, 'corpus');
 
-    state.canDefine = function (name) {
-        return tokens.isGlobal(name) && definitions[name] === undefined;
-    };
+puddleSocket.on('remove', function (id) {
+    trace('Hub incoming remove');
+    removeLine(id);
+}, 'corpus');
 
-    var insertDefinition = function (name, id) {
-        trace('insertDefinition', arguments);
-        assert(tokens.isGlobal(name));
-        assert(definitions[name] === undefined);
-        assert(occurrences[name] === undefined);
-        definitions[name] = id;
-        occurrences[name] = {};
-    };
+puddleSocket.on('update', function (id, code) {
+    trace('Hub incoming update');
+    var line = serverSyntax.loadStatement(code);
+    line.id = id;
+    updateLine(line);
+}, 'corpus');
 
-    var insertOccurrence = function (name, id) {
-        trace('insertOccurrence', arguments);
-        var occurrencesName = occurrences[name];
-        assert(occurrencesName !== undefined);
-        assert(occurrencesName[id] === undefined);
-        occurrences[name][id] = null;
-    };
 
-    var removeOccurrence = function (name, id) {
-        trace('removeOccurrence', arguments);
-        var occurrencesName = occurrences[name];
-        assert(occurrencesName !== undefined);
-        assert(occurrencesName[id] !== undefined);
-        delete occurrencesName[id];
-    };
+puddleSocket.on('reset', function (codes) {
+    trace('Hub reset');
 
-    var removeDefinition = function (name) {
-        trace('removeDefinition', arguments);
-        assert(definitions[name] !== undefined);
-        assert(occurrences[name] !== undefined);
-        assert(_.isEmpty(occurrences[name]));
-        delete definitions[name];
-        delete occurrences[name];
-    };
+    lines = corpus.lines = {};
+    definitions = {};
+    occurrences = {};
 
-    var insertLine = function (line) {
-        trace('insertLine', arguments);
-        var id = line.id;
-        assert(!_.has(lines, id));
+    _.each(codes, function (code, id) {
+        var line = serverSyntax.loadStatement(code);
+        line.id = id;
         lines[id] = line;
+    });
+
+    //insert all defenitions
+    _.each(lines, function (line, id) {
         if (line.name !== null) {
             insertDefinition(line.name, id);
         }
+    });
+
+    //insert all occurences of these defenitions
+    _.each(lines, function (line, id) {
         line.free = tokens.getFreeVariables(line.code);
         for (var name in line.free) {
             insertOccurrence(name, id);
         }
-    };
+    });
+    emitter.emit('reset', lines);
+    validate();
+    pollValidities();
+});
 
-    var removeLine = function (line) {
-        trace('removeLine', arguments);
-        var id = line.id;
-        assert(_.has(lines, id));
-        delete lines[id];
-        for (var name in line.free) {
-            removeOccurrence(name, id);
+
+var validate = function () {
+    trace('validating corpus', arguments);
+    for (var id in lines) {
+        var line = lines[id];
+        var name = line.name;
+        if (name !== null) {
+            assert(
+                tokens.isGlobal(name),
+                    'name is not global: ' + name);
+            assert(
+                !tokens.isKeyword(name),
+                    'name is keyword: ' + name);
+            assert(
+                    definitions[name] === line.id,
+                    'missing definition: ' + name);
         }
-        if (line.name !== null) {
-            removeDefinition(line.name);
+        var free = tokens.getFreeVariables(line.code);
+        assert.equal(line.free, free, 'wrong free variables:');
+        for (name in free) {
+            assert(
+                tokens.isGlobal(name),
+                    'name is not global: ' + name);
+            var occurrencesName = occurrences[name];
+            assert(
+                    occurrencesName !== undefined,
+                    'missing occurrences: ' + name);
+            assert(
+                    occurrencesName[id] === null,
+                    'missing occurrence: ' + name);
         }
-    };
+    }
+};
 
-    state.ready = (function () {
-        trace('State ready', arguments);
-        var isReady = false;
-        var readyQueue = [];
-        var ready = function (cb) {
-            if (isReady) {
-                setTimeout(cb, 0);
-            } else {
-                readyQueue.push(cb);
-            }
-        };
-        ready.set = function () {
-            trace('State ready set', arguments);
-            isReady = true;
-            while (readyQueue.length) {
-                setTimeout(readyQueue.pop(), 0);
-            }
-        };
-        return ready;
-    }());
-
-    state.loadAll = function (linesToLoad) {
-        trace('State loadAll', arguments);
-        lines = state.lines = {};
-        definitions = state.definitions = {};
-        occurrences = state.occurrences = {};
-        linesToLoad.forEach(function (line) {
-            var id = line.id;
-            lines[id] = line;
-            if (line.name !== null) {
-                insertDefinition(line.name, id);
-            }
+var pollValidities = (function () {
+    var delay = 500;
+    var delayFail = 15000;
+    var polling = false;
+
+    var poll = function () {
+        polling = true;
+        debug('...polling...');
+        var ids = [];
+        var linesForAnalyst = _.map(lines, function (line, id) {
+            ids.push(id);
+            //TODO why line.name is undefined after syntax.compiler.dumpLine?
+            //Should not dumpLine be compatible with analyst?
+            return {name: line.name || null, code: line.code};
         });
-        linesToLoad.forEach(function (line) {
-            var id = line.id;
-            line.free = tokens.getFreeVariables(line.code);
-            for (var name in line.free) {
-                insertOccurrence(name, id);
-            }
-        });
-        state.ready.set();
-    };
-
-    state.insert = function (line, done) {
-        trace('State insert', arguments);
-        line.id = uuid();
-        sync.create(line);
-        insertLine(line);
-        done(line);
+        $.ajax({
+            url: '/analyst/validities',
+            cache: false,
+            type: 'POST',
+            contentType: 'application/json',
+            data: JSON.stringify(linesForAnalyst)
+        })
+            /*jslint unparam: true*/
+            .fail(function (jqXHR, textStatus) {
+                debug('pollValidities failed: ' + textStatus);
+                setTimeout(poll, delayFail);
+            })
+            /*jslint unparam: false*/
+            .done(function (validities) {
+                var updated = [];
+                polling = false;
+                debug('...validities received');
+
+                validities.forEach(function (validity, index) {
+                    var line = lines[ids[index]];
+                    if (!_.isEqual(line.validity, validity)) {
+                        line.validity = validity;
+                        updated.push(line);
+                    }
+                });
+
+                if (updated.length) {
+                    emitter.emit('updateValidity', updated);
+                }
+                if (_.find(validities, 'pending')) {
+                    polling = true;
+                    setTimeout(poll, delay);
+                }
+
+            });
     };
-
-    state.update = function (newline) {
-        trace('State update', arguments);
-        var name;
-        var id = newline.id;
-        assert(id !== undefined, 'expected .id field in updated line');
-        var line = lines[id];
-        assert(line !== undefined, 'bad id: ' + id);
-        for (name in line.free) {
-            removeOccurrence(name, id);
-        }
-        line.code = newline.code;
-        line.free = tokens.getFreeVariables(line.code);
-        for (name in line.free) {
-            insertOccurrence(name, id);
+    return function () {
+        trace('PollingValidities...');
+        if (!polling) {
+            poll();
+        } else {
+            debug('...skip polling');
         }
-        sync.update(line);
-        return line;
     };
+})();
 
-    state.remove = function (id) {
-        trace('remove', arguments);
-        assert(_.has(lines, id));
-        var line = lines[id];
-        removeLine(line);
-        sync.remove(line);
-    };
+// API
 
-    state.validate = function () {
-        trace('validating corpus', arguments);
-        for (var id in lines) {
-            var line = lines[id];
-            var name = line.name;
-            if (name !== null) {
-                assert(
-                    tokens.isGlobal(name),
-                        'name is not global: ' + name);
-                assert(
-                    !tokens.isKeyword(name),
-                        'name is keyword: ' + name);
-                assert(
-                        definitions[name] === line.id,
-                        'missing definition: ' + name);
-            }
-            var free = tokens.getFreeVariables(line.code);
-            assert.equal(line.free, free, 'wrong free variables:');
-            for (name in free) {
-                assert(
-                    tokens.isGlobal(name),
-                        'name is not global: ' + name);
-                var occurrencesName = occurrences[name];
-                assert(
-                        occurrencesName !== undefined,
-                        'missing occurrences: ' + name);
-                assert(
-                        occurrencesName[id] === null,
-                        'missing occurrence: ' + name);
-            }
-        }
-        trace('corpus is valid', arguments);
-    };
+corpus.create = function (line) {
+    line.id = uuid();
+    createLine(line);
+    puddleSocket.create(line.id, serverSyntax.dumpStatement(line), 'corpus');
+    return line;
+};
 
+corpus.remove = function (id) {
+    removeLine(id);
+    puddleSocket.remove(id, 'corpus');
+};
 
-    state.findLine = function (id) {
-        trace('findLine', arguments);
-        var line = lines[id];
-        return {
-            name: line.name,
-            code: line.code,
-            free: _.extend({}, line.free)
-        };
-    };
+//not exposed as API, called by corpus.checkIn
+var update = function (line) {
+    updateLine(line);
+    puddleSocket.update(line.id, serverSyntax.dumpStatement(line), 'corpus');
+    return line;
+};
 
-    state.findAllLines = function () {
-        trace('findAllLines', arguments);
-        return _.keys(lines);
-    };
+corpus.id = function (id) {
+    return lines[id];
+};
 
-    state.findAllNames = function () {
-        trace('findAllNames', arguments);
-        var result = [];
-        for (var id in lines) {
-            var name = lines[id].name;
-            if (name) {
-                result.push(name);
-            }
-        }
-        return result;
-    };
+corpus.findAllLines = function () {
+    trace('findAllLines', arguments);
+    return _.keys(lines);
+};
+corpus.findDefinition = function (name) {
+    trace('findDefinition', arguments);
+    var id = definitions[name];
+    if (id !== undefined) {
+        return id;
+    } else {
+        return null;
+    }
+};
+corpus.canDefine = function (name) {
+    return syntax.tokens.isGlobal(name) &&
+        definitions[name] === undefined;
+};
+corpus.getDefinitions = function () {
+    return definitions;
+};
 
-    state.findDefinition = function (name) {
-        trace('findDefinition', arguments);
-        var id = definitions[name];
-        if (id !== undefined) {
-            return id;
-        } else {
-            return null;
-        }
-    };
+corpus.findOccurrences = function (name) {
+    trace('findOccurrences', arguments);
+    assert(_.has(definitions, name));
+    return _.keys(occurrences[name]);
+};
 
-    state.findOccurrences = function (name) {
-        trace('findOccurrences', arguments);
-        assert(_.has(definitions, name));
-        return _.keys(occurrences[name]);
-    };
+corpus.hasOccurrences = function (name) {
+    trace('hasOccurrences', arguments);
+    assert(_.has(definitions, name));
+    return !_.isEmpty(occurrences[name]);
+};
 
-    state.hasOccurrences = function (name) {
-        trace('hasOccurrences', arguments);
-        assert(_.has(definitions, name));
-        return !_.isEmpty(occurrences[name]);
-    };
+corpus.insertDefine = function (varName) {
+    var VAR = syntax.compiler.fragments.church.VAR;
+    var HOLE = syntax.compiler.fragments.church.HOLE;
+    var DEFINE = syntax.compiler.fragments.church.DEFINE;
+    var churchTerm = DEFINE(VAR(varName), HOLE);
+    var line = syntax.compiler.dumpLine(churchTerm);
+    return corpus.create(line);
+};
 
-    state.DEBUG_LINES = lines;
-    return state;
-})();
+corpus.insertAssert = function () {
+    var HOLE = syntax.compiler.fragments.church.HOLE;
+    var ASSERT = syntax.compiler.fragments.church.ASSERT;
+    var churchTerm = ASSERT(HOLE);
+    var line = syntax.compiler.dumpLine(churchTerm);
+    line.name = null;
+    return corpus.create(line);
+};
+
+corpus.checkOut = function (tree) {
+    trace('checkout');
+    var id = tree.id;
+    assert(id);
+    assert(!checkedOut[id]);
+    checkedOut[id] = syntax.compiler.dumpLine(syntax.tree.dump(tree));
+};
+
+//used when there was no change to the line
+corpus.unCheckOut = function (tree) {
+    trace('unCheckOut');
+    var id = tree.id;
+    assert(id);
+    assert(checkedOut[id]);
+    delete checkedOut[id];
+};
+
+corpus.checkIn = function (updatedTree) {
+    trace('checkin');
+    var id = updatedTree.id;
+    var oldLine = checkedOut[id];
+    assert(oldLine);
+    var newLine = syntax.compiler.dumpLine(syntax.tree.dump(updatedTree));
+    if (!_.isEqual(oldLine, newLine)) {
+        newLine.id = id;
+        update(newLine);
+    }
+    delete checkedOut[id];
+};
+
+corpus.on = _.bind(emitter.on, emitter);
+
+module.exports = corpus;
diff --git a/puddle-editor/source/app/cursor.js b/puddle-editor/source/app/cursor.js
new file mode 100644
index 0000000..5540d1f
--- /dev/null
+++ b/puddle-editor/source/app/cursor.js
@@ -0,0 +1,193 @@
+'use strict';
+
+var _ = require('lodash');
+var assert = require('assert');
+var EventEmitter = require('events').EventEmitter;
+var emitter = new EventEmitter();
+var syntax = require('puddle-syntax');
+var debug = require('debug')('puddle:editor:cursor');
+var trace = require('./trace')(debug);
+var forest = require('./forest');
+var cursor = {};
+trace('cursor init');
+
+var lineIndex = 0;
+cursor.tree = function () {
+    return syntax.tree.getRoot(cursor.node);
+};
+
+cursor.moveTo = function (newNode) {
+    assert(newNode);
+    if (newNode !== cursor.node) {
+        trace('cursor moveTo');
+        var oldNode = cursor.node;
+        cursor.node = newNode;
+        lineIndex = forest.trees.indexOf(cursor.tree());
+        checkInCheckOut(newNode, oldNode);
+        emitter.emit('move', newNode, oldNode);
+    }
+};
+
+//route = [-1,2,4]
+//'-1' to go above, positive numbers to go below
+//node parameter is optional.
+cursor.getRelative = function (route, node) {
+    assert(_.isArray(route));
+    var finish = node || cursor.node;
+    _.forEach(route, function (direction) {
+        if (direction === -1) {
+            finish = finish.above;
+        } else {
+            finish = finish.below[direction];
+        }
+        assert(finish);
+    });
+    return finish;
+};
+
+cursor.replaceBelow = function (node) {
+    var above = cursor.node.above;
+    if (above) {
+        var pos = above.below.indexOf(cursor.node);
+        assert(pos !== -1);
+        above.below[pos] = node;
+        node.above = above;
+        cursor.moveTo(node);
+    }
+};
+
+cursor.moveLine = function (delta) {
+    return function () {
+        trace('cursor moveLine');
+        var newPos = lineIndex + delta;
+        //out of bounds handling
+        while (newPos < 0) {
+            newPos += forest.trees.length;
+        }
+        while (newPos >= forest.trees.length) {
+            newPos -= forest.trees.length;
+        }
+        var newLine = forest.trees[newPos];
+        assert(newLine);
+        cursor.moveTo(newLine);
+    };
+};
+
+//TODO this is taken from syntax.cursor.tryMove
+var traverseDownLeft = function (node) {
+    while (node.below.length) {
+        node = _.first(node.below);
+    }
+    return node;
+};
+
+var traverseDownRight = function (node) {
+    while (node.below.length) {
+        node = _.last(node.below);
+    }
+    return node;
+};
+
+
+cursor.moveLeft = function () {
+    trace('cursor moveLeft');
+    var node = cursor.node;
+    var above = node.above;
+    while (above !== null) {
+        var pos = _.indexOf(above.below, node);
+        assert(pos >= 0, 'node not found in node.above.below');
+        if (pos > 0) {
+            return cursor.moveTo(traverseDownRight(above.below[pos - 1]));
+        }
+        node = above;
+        above = node.above;
+    }
+    return cursor.moveTo(traverseDownRight(node));
+};
+
+cursor.moveRight = function () {
+    trace('cursor moveRight');
+    var node = cursor.node;
+    var above = node.above;
+    while (above !== null) {
+        var pos = _.indexOf(above.below, node);
+        assert(pos >= 0, 'node not found in node.above.below');
+        if (pos < above.below.length - 1) {
+            return cursor.moveTo(traverseDownLeft(above.below[pos + 1]));
+        }
+        node = above;
+        above = node.above;
+    }
+    return cursor.moveTo(traverseDownLeft(node));
+};
+
+cursor.widenSelection = function () {
+    if (cursor.node.above !== null) {
+        cursor.moveTo(cursor.node.above);
+    }
+};
+
+cursor.on = _.bind(emitter.on, emitter);
+
+
+forest.on('remove', function (id) {
+    trace('Forest remove');
+    if (cursor.tree().id === id) {
+        cursor.moveLine(-1)();
+    }
+});
+
+forest.on('update', function (tree) {
+    trace('Forest update');
+    if (cursor.tree().id === tree.id) {
+        cursor.moveTo(tree);
+    }
+});
+
+forest.on('reset', function (trees) {
+    trace('Forest reset');
+    var firstTree = trees[0];
+    cursor.moveTo(firstTree);
+});
+
+
+module.exports = cursor;
+
+var corpus = require('./corpus');
+
+//checkIn or checkOut lines in corpus according to type of move.
+var checkInCheckOut = function (toNode, fromNode) {
+    assert(toNode !== fromNode);
+    var toTree = syntax.tree.getRoot(toNode);
+    var fromTree;
+    if (fromNode) {
+        fromTree = syntax.tree.getRoot(fromNode);
+    }
+
+    //CHECK-IN
+    if (fromTree !== fromNode) { //only if moving from non root node
+        if (toTree !== fromTree) { //and changing the tree
+            //make sure fromTree is still part of the forest
+            if (forest.isOrphan(fromTree)) {
+                corpus.unCheckOut(fromTree);
+            } else {
+                corpus.checkIn(fromTree);
+            }
+        } else { //or same tree but moving to root node
+            if (toNode === toTree) {
+                corpus.checkIn(fromTree);
+            }
+        }
+    }
+
+    //CHECK-OUT
+    if (toTree !== toNode) { //only if moving to non root node
+        if (toTree !== fromTree) { //and changing the tree
+            corpus.checkOut(toTree);
+        } else {  //or same tree but from root node
+            if (fromNode === fromTree) {
+                corpus.checkOut(toTree);
+            }
+        }
+    }
+};
\ No newline at end of file
diff --git a/puddle-editor/source/app/editor.js b/puddle-editor/source/app/editor.js
deleted file mode 100644
index b8eb706..0000000
--- a/puddle-editor/source/app/editor.js
+++ /dev/null
@@ -1,366 +0,0 @@
-'use strict';
-
-var _ = require('lodash');
-var $ = require('jquery');
-var io = require('socket.io-client');
-var syntax = require('puddle-syntax');
-var EventEmitter = require('events').EventEmitter;
-var emitter = new EventEmitter();
-var assert = require('./assert');
-var corpus = require('./corpus');
-var debug = require('debug')('puddle:editor');
-var trace = require('./trace')(debug);
-var shared = {};
-var ids = shared.ids = [];
-var trees = shared.trees = {};  // id -> tree
-var validities = {}; // id -> {'is_top': _, 'is_bot': _, 'pending': _}
-var cursor = shared.cursor = null;
-var cursorPos = 0;
-var lineChanged = false;
-var socket = io();
-var TODO = require('./TODO');
-
-var UNKNOWN = {'is_top': null, 'is_bot': null, 'pending': true};
-
-//--------------------------------------------------------------------------
-// Corpus Management
-
-var loadAllLines = function () {
-    trace('loadAllLines');
-    ids = shared.ids = [];
-    trees = shared.trees = {};
-    validities = {};
-    corpus.findAllLines().forEach(function (id) {
-        ids.push(id);
-        var line = corpus.findLine(id);
-        var churchTerm = syntax.compiler.loadLine(line);
-        trees[id] = syntax.tree.load(churchTerm);
-        validities[id] = _.clone(UNKNOWN);
-    });
-    pollValidities();
-    assert(ids.length > 0, 'corpus is empty');
-};
-
-// outgoing create
-var insertLine = function (line, done, fail) {
-    trace('insertLine', arguments);
-    corpus.insert(
-        line,
-        function (line) {
-            syntax.cursor.remove(cursor);
-            cursorPos += 1;
-            var id = line.id;
-            ids = ids.slice(0, cursorPos).concat([id], ids.slice(cursorPos));
-            var churchTerm = syntax.compiler.loadLine(line);
-            var root = syntax.tree.load(churchTerm);
-            syntax.cursor.insertAbove(cursor, _.last(root.below));  // HACK
-            trees[id] = root;
-            validities[id] = _.clone(UNKNOWN);
-            pollValidities();
-            if (done !== undefined) {
-                done();
-            }
-        },
-        function () {
-            debug('failed to insert line');
-            if (fail !== undefined) {
-                fail();
-            }
-        }
-    );
-};
-
-// incoming create
-var onInsertLine = function (id, line) {
-    ids.push(id);
-    var churchTerm = syntax.compiler.load(line);
-    var root = syntax.tree.load(churchTerm);
-    trees[id] = root;
-    validities[id] = _.clone(UNKNOWN);
-    pollValidities();
-};
-
-// incoming remove
-var onRemoveLine = function (id) {
-    var pos = _.indexOf(ids, id);
-    if (pos === cursorPos) {
-        TODO('implement locking to avoid this situation');
-    } else if (pos < cursorPos) {
-        cursorPos -= 1;
-    }
-    ids = ids.slice(0, pos).concat(ids.slice(pos + 1));
-    delete trees[id];
-    delete validities[id];
-};
-
-// outgoing update
-var onUpdateLine = function (id, line) {
-    var pos = _.indexOf(ids, id);
-    if (pos === cursorPos) {
-        TODO('implement locking to avoid this situation');
-    } else if (pos < cursorPos) {
-        cursorPos -= 1;
-    }
-    var churchTerm = syntax.compiler.load(line);
-    var root = syntax.tree.load(churchTerm);
-    trees[id] = root;
-    validities[id] = _.clone(UNKNOWN);
-    pollValidities();
-};
-
-
-var commitLine = function () {
-    trace('commitLine', arguments);
-    var id = ids[cursorPos];
-    var below = cursor.below[0];
-    syntax.cursor.remove(cursor);
-    var root = syntax.tree.getRoot(below);
-    var churchTerm = syntax.tree.dump(root);
-    var line = syntax.compiler.dumpLine(churchTerm);
-    line.id = id;
-    line = corpus.update(line);
-    churchTerm = syntax.compiler.loadLine(line);
-    root = syntax.tree.load(churchTerm);
-    syntax.cursor.insertAbove(cursor, root);
-    trees[id] = root;
-    var lineIsDefinition = (line.name !== null);
-    if (lineIsDefinition) {
-        ids.forEach(function (id) {
-            validities[id] = _.clone(UNKNOWN);
-        });
-    } else {
-        validities[id] = _.clone(UNKNOWN);
-    }
-    pollValidities();
-    lineChanged = false;
-};
-
-
-var pollValidities = (function () {
-    trace('Init pollValidities', arguments);
-
-    var delay = 500;
-    var delayFail = 15000;
-    var polling = false;
-
-    var poll = function () {
-        polling = false;
-        debug('polling');
-        $.ajax({
-            type: 'GET',
-            url: '/corpus/validities',
-            cache: false
-        })
-            /*jslint unparam: true*/
-            .fail(function (jqXHR, textStatus) {
-                debug('pollValidities GET failed: ' + textStatus);
-                polling = true;
-                setTimeout(poll, delayFail);
-            })
-            /*jslint unparam: false*/
-            .done(function (data) {
-                debug('pollValidities GET succeeded');
-                data.data.forEach(function (validity) {
-                    var id = validity.id;
-                    delete validity.id;
-                    var oldValidity = validities[id];
-                    if (oldValidity !== undefined) {
-                        if (!_.isEqual(oldValidity, validity)) {
-                            validities[id] = validity;
-                            emitter.emit('updateValidity');
-                        }
-                    }
-                });
-                for (var id in validities) {
-                    if (validities[id].pending) {
-                        polling = true;
-                        setTimeout(poll, delay);
-                        return;
-                    }
-                }
-            });
-    };
-
-    return function () {
-        trace('pollValidities');
-        if (!polling) {
-            polling = true;
-            setTimeout(poll, 0);
-        }
-    };
-})();
-
-//--------------------------------------------------------------------------
-// Cursor Movement
-
-var initCursor = function () {
-    trace('initCursor', arguments);
-    cursor = shared.cursor = syntax.cursor.create();
-    cursorPos = 0;
-    lineChanged = false;
-    var id = ids[cursorPos];
-    socket.emit('action', {'moveTo': id});
-    syntax.cursor.insertAbove(cursor, trees[id]);
-};
-
-var moveCursorLine = function (delta) {
-    if (lineChanged) {
-        commitLine();
-    }
-    if (0 <= cursorPos + delta && cursorPos + delta < ids.length) {
-        syntax.cursor.remove(cursor);
-        cursorPos = (cursorPos + ids.length + delta) % ids.length;
-        var id = ids[cursorPos];
-        socket.emit('action', {'moveTo': id});
-        syntax.cursor.insertAbove(cursor, trees[id]);
-    }
-};
-
-
-//--------------------------------------------------------------------------
-// Interface
-
-var moveCursor = function (direction) {
-    return function () {
-        syntax.cursor.tryMove(cursor, direction);
-    };
-};
-
-
-var pureActions = {
-    commitLine: commitLine,
-    revertLine: function () {
-        trace('revertLine', arguments);
-        var id = ids[cursorPos];
-        var line = corpus.findLine(id);
-        var churchTerm = syntax.compiler.loadLine(line);
-        var root = syntax.tree.load(churchTerm);
-        syntax.cursor.remove(cursor);
-        syntax.cursor.insertAbove(cursor, root);
-        trees[id] = root;
-        lineChanged = false;
-    },
-    removeLine: function () {
-        trace('removeLine', arguments);
-        var id = ids[cursorPos];
-        corpus.remove(id);
-        syntax.cursor.remove(cursor);
-        ids = ids.slice(0, cursorPos)
-            .concat(ids.slice(cursorPos + 1));
-        delete trees[id];
-        delete validities[id];
-        if (cursorPos === ids.length) {
-            cursorPos -= 1;
-        }
-        id = ids[cursorPos];
-        syntax.cursor.insertAbove(cursor, trees[id]);
-    },
-    insertAssert: function (done, fail) {
-        trace('insertAssert', arguments);
-        var HOLE = syntax.compiler.fragments.church.HOLE;
-        var ASSERT = syntax.compiler.fragments.church.ASSERT;
-        var churchTerm = ASSERT(HOLE);
-        var line = syntax.compiler.dumpLine(churchTerm);
-        line.name = null;
-        insertLine(line, done, fail);
-    },
-    insertDefine: function (varName, done, fail) {
-        trace('insertDefine', arguments);
-        var VAR = syntax.compiler.fragments.church.VAR;
-        var HOLE = syntax.compiler.fragments.church.HOLE;
-        var DEFINE = syntax.compiler.fragments.church.DEFINE;
-        var churchTerm = DEFINE(VAR(varName), HOLE);
-        var line = syntax.compiler.dumpLine(churchTerm);
-        insertLine(line, done, fail);
-    },
-    replaceBelow: function (newChurchTerm, subsForDash) {
-        trace('replaceBelow', arguments);
-        if (subsForDash !== undefined) {
-            newChurchTerm = syntax.compiler.substitute(
-                '—',
-                subsForDash, newChurchTerm);
-        }
-        var newTerm = syntax.tree.load(newChurchTerm);
-        cursor = shared.cursor = syntax.cursor
-            .replaceBelow(cursor, newTerm);
-        lineChanged = true;
-    },
-    moveUp: function () {
-        moveCursorLine(-1);
-    },
-    moveDown: function () {
-        moveCursorLine(1);
-    },
-    moveLeft: moveCursor('L'),
-    moveRight: moveCursor('R'),
-    moveCursorLine: moveCursorLine,
-    widenSelection: moveCursor('U')
-};
-
-var wrap = function (callback) {
-    return function () {
-        trace('editor update');
-        callback.apply(this, _.toArray(arguments));
-        emitter.emit('update');
-    };
-};
-
-var sharedActions = {};
-_.each(pureActions, function (value, key) {
-    sharedActions[key] = wrap(value);
-});
-
-var sortLines = function (lines) {
-    /*
-     Return a heuristically sorted list of definitions.
-
-     TODO use approximately topologically-sorted order.
-     (R1) "A Technique for Drawing Directed Graphs" -Gansner et al
-     http://www.graphviz.org/Documentation/TSE93.pdf
-     (R2) "Combinatorial Algorithms for Feedback Problems in Directed Graphs"
-     -Demetrescu and Finocchi
-     http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.1.9435
-     */
-    return lines;
-};
-
-module.exports = {
-    main: function () {
-        loadAllLines();
-        initCursor();
-    },
-    getTerms: function () {
-        return ids.map(function (id) {
-            return syntax.tree.dump(trees[id]);
-        });
-    },
-    crud: {
-        create: wrap(onInsertLine),
-        remove: wrap(onRemoveLine),
-        update: wrap(onUpdateLine)
-    },
-    getActions: function () {
-        return sharedActions;
-    },
-    on: _.bind(emitter.on, emitter),
-    getCursor: function () {
-        return shared.cursor;
-    },
-    //TODO this function has to be replaced by methods of forest.
-    getCorpus: function () {
-        return corpus;
-    },
-    getValidity: function (id) {
-        return validities[id];
-    },
-    getLine: function (id) {
-        return syntax.tree.dump(syntax.tree.getRoot(trees[id]));
-    },
-    getIds: function () {
-        return sortLines(_.cloneDeep(ids));
-    },
-    getCursorId: function () {
-        return ids[cursorPos];
-    }
-
-};
diff --git a/puddle-editor/source/app/forest.js b/puddle-editor/source/app/forest.js
new file mode 100644
index 0000000..b010f34
--- /dev/null
+++ b/puddle-editor/source/app/forest.js
@@ -0,0 +1,106 @@
+'use strict';
+var assert = require('assert');
+var _ = require('lodash');
+var EventEmitter = require('events').EventEmitter;
+var emitter = new EventEmitter();
+var syntax = require('puddle-syntax');
+var corpus = require('./corpus');
+var debug = require('debug')('puddle:editor:forest');
+var trace = require('./trace')(debug);
+var trees = []; //array to keep forest sorted in particular order
+var treesHash = {}; //id => tree, hash to access same trees by id.
+trace('forest init');
+
+
+var sortForest = function () {
+    /*
+     Return a heuristically sorted list of definitions.
+
+     TODO use approximately topologically-sorted order.
+     (R1) "A Technique for Drawing Directed Graphs" -Gansner et al
+     http://www.graphviz.org/Documentation/TSE93.pdf
+     (R2) "Combinatorial Algorithms for Feedback Problems in Directed Graphs"
+     -Demetrescu and Finocchi
+     http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.1.9435
+     */
+
+    //simple by ID sort
+    trees.sort(function (a, b) {
+        var keyA = a.id,
+            keyB = b.id;
+        if (keyA < keyB) {
+            return -1;
+        }
+        if (keyA > keyB) {
+            return 1;
+        }
+        return 0;
+    });
+};
+
+var update = function (line) {
+    assert(line.id);
+    var churchTerm = syntax.compiler.loadLine(line);
+    var tree = syntax.tree.load(churchTerm);
+    tree.id = line.id;
+    var oldTree = treesHash[tree.id];
+    assert(oldTree);
+    trees[trees.indexOf(oldTree)] = tree;
+    treesHash[tree.id] = tree;
+    sortForest();
+    emitter.emit('update', tree);
+};
+
+var createLine = function (line) {
+    var id = line.id;
+    var churchTerm = syntax.compiler.loadLine(line);
+    var tree = syntax.tree.load(churchTerm);
+    tree.id = id;
+    trees.push(tree);
+    treesHash[id] = tree;
+    return tree;
+};
+
+corpus.on('create', function (line) {
+    var tree = createLine(line);
+    sortForest();
+    emitter.emit('create', tree);
+});
+
+corpus.on('remove', function (id) {
+    var oldTree = treesHash[id];
+    assert(oldTree);
+    _.pull(trees, oldTree);
+    delete treesHash[id];
+    emitter.emit('remove', id);
+});
+
+corpus.on('update', update);
+
+corpus.on('reset', function (linesHash) {
+    trace('Corpus reset');
+
+    trees.length = 0;
+    _.each(treesHash, function (value, key) {
+        delete treesHash[key];
+    });
+
+    _.each(linesHash, createLine);
+    assert(trees.length > 0, 'corpus is empty');
+    sortForest();
+    emitter.emit('reset', trees);
+});
+
+
+module.exports = {
+    trees: trees,
+    id: function (id) {
+        return treesHash[id];
+    },
+    on: _.bind(emitter.on, emitter),
+    update: update,
+    isOrphan: function (node) {
+        var tree = syntax.tree.getRoot(node);
+        return (trees.indexOf(tree) === -1);
+    }
+};
diff --git a/puddle-editor/source/app/main.js b/puddle-editor/source/app/main.js
index 17b2e6e..5ade9f2 100644
--- a/puddle-editor/source/app/main.js
+++ b/puddle-editor/source/app/main.js
@@ -1,56 +1,14 @@
 'use strict';
+//globals are only defined here and are only used for debug
+global._ = require('lodash');
+global.$ = require('jquery');
+global.debug = require('debug');
+global.corpus = require('./corpus');
+global.forest = require('./forest');
+global.cursor = require('./cursor');
+require('./view');
+require('./menu-renderer.js');
 
-var Menu = require('./menu/menu');
-var menuRenderer = require('./menu/menuRenderer');
-var View = require('./view');
-var serverSyntax = require('./server-syntax');
-var _ = require('lodash');
-var io = require('socket.io-client');
-var puddleSocket = global.puddleSocket = require('puddle-socket').client(io);
-var corpus = global.corpus = require('./corpus');
-var editor = global.editor = require('./editor');
-var debugModule = global.debug = require('debug');
-var debug = debugModule('puddle:editor:main');
-var trace = require('./trace')(debug);
-
-//TODO refactor editor to expose reset method
-var initEditor = _.once(function () {
-    trace('Init editor');
-    var newData = [];
-    _.each(puddleSocket.getState(), function (code, id) {
-        var line = serverSyntax.loadStatement(code);
-        line.id = id;
-        newData.push(line);
-    });
-
-    corpus.loadAll(newData);
-    editor.main();
-    //TODO refactor for better API
-    var menu = Menu(editor);
-    menuRenderer(menu, editor);
-    View(editor);
-});
-
-var reinitEditor = function () {
-    trace('reInit editor');
-    initEditor();
-};
-puddleSocket.on('reset', reinitEditor);
-
-puddleSocket.on('create', function (id, obj) {
-    trace('Hub create');
-    editor.crud.create(id, obj);
-}, 'editor');
-
-puddleSocket.on('remove', function (id) {
-    trace('Hub remove');
-    editor.crud.remove(id);
-}, 'editor');
-
-puddleSocket.on('update', function (id, obj) {
-    trace('Hub update');
-    editor.crud.update(id, obj);
-}, 'editor');
 
 
 
diff --git a/puddle-editor/source/app/menu-builder.js b/puddle-editor/source/app/menu-builder.js
new file mode 100644
index 0000000..46ad1dd
--- /dev/null
+++ b/puddle-editor/source/app/menu-builder.js
@@ -0,0 +1,183 @@
+'use strict';
+
+var _ = require('lodash');
+var assert = require('assert');
+var syntax = require('puddle-syntax');
+var io = require('socket.io-client');
+var socket = io();
+var debug = require('debug')('puddle:editor:menu');
+var trace = require('./trace')(debug);
+var corpus = require('./corpus');
+var cursor = require('./cursor');
+var forest = require('./forest');
+
+var fragments = syntax.compiler.fragments.church;
+
+var HOLE = fragments.HOLE;
+var TOP = fragments.TOP;
+var BOT = fragments.BOT;
+var VAR = fragments.VAR;
+var LAMBDA = fragments.LAMBDA;
+var LETREC = fragments.LETREC;
+var APP = fragments.APP;
+var JOIN = fragments.JOIN;
+var RAND = fragments.RAND;
+var QUOTE = fragments.QUOTE;
+var EQUAL = fragments.EQUAL;
+var LESS = fragments.LESS;
+var NLESS = fragments.NLESS;
+var ASSERT = fragments.ASSERT;
+var DEFINE = fragments.DEFINE;
+var CURSOR = fragments.CURSOR;
+var DASH = VAR('—');
+
+
+var actions = {
+    revertLine: function () {
+        if (cursor.tree() !== cursor.node) {
+            forest.update(corpus.id(cursor.tree().id));
+        }
+    },
+    commitLine: function () {
+        if (cursor.tree() !== cursor.node) {
+            cursor.moveTo(cursor.tree());
+        }
+    },
+    removeLine: function () {
+        corpus.remove(cursor.tree().id);
+    },
+    insertDefine: function (varName) {
+        var line = corpus.insertDefine(varName);
+        cursor.moveTo(forest.id(line.id));
+    },
+    insertAssert: function () {
+        var newLine = corpus.insertAssert();
+        var newTree = forest.id(newLine.id);
+        cursor.moveTo(cursor.getRelative([0], newTree));
+    }
+};
+
+var socketLogWrapper = function (actions) {
+    return actions.map(function (action) {
+        var name = action[0];
+        var callback = function () {
+            socket.emit('action', action[0]);
+            action[1].apply(this, _.toArray(arguments));
+        };
+        var description = action[2];
+        return [name, callback, description];
+    });
+};
+
+var generic = [
+    ['enter', actions.commitLine, 'commit line'],
+    ['tab', actions.revertLine, 'revert line'],
+    ['up', cursor.moveLine(-1), 'move up'],
+    ['down', cursor.moveLine(1), 'move down'],
+    ['left', cursor.moveLeft, 'move left'],
+    ['right', cursor.moveRight, 'move right'],
+    ['shift+left', cursor.widenSelection, 'widen selection'],
+    ['shift+right', cursor.widenSelection, 'widen selection'],
+    ['A', actions.insertAssert,
+        ASSERT(CURSOR(HOLE))],
+    ['D', actions.insertDefine,
+        DEFINE(CURSOR(VAR('...')), HOLE)]
+];
+
+var getActions = function () {
+    trace('getActions');
+    var node = cursor.node;
+    var actionsArray = [];
+    var on = function (name, term, cursorAddress, subsForDash) {
+        actionsArray.push([
+            name,
+            function () {
+                if (subsForDash) {
+                    term = syntax.compiler.substitute(
+                        '—',
+                        subsForDash, term);
+                }
+                var newNode = syntax.tree.load(term);
+
+                //TODO reconsider cursor rendering
+                //so that we do not have to remove it here
+                if (cursorAddress) {
+                    var cursorNode = cursor.getRelative(cursorAddress, newNode);
+                    assert(cursorNode.name === 'CURSOR');
+                    syntax.cursor.remove(cursorNode);
+                }
+                cursor.replaceBelow(newNode);
+
+                if (cursorAddress) {
+                    cursor.moveTo(cursor.getRelative(cursorAddress));
+                }
+            },
+            term
+        ]);
+    };
+
+    var name = node.name;
+    var varName = syntax.tree.getFresh(node);
+    var fresh = VAR(varName);
+    if (name === 'ASSERT') {
+        actionsArray.push(['X', actions.removeLine, 'delete line']);
+    } else if (name === 'DEFINE') {
+        if (!corpus.hasOccurrences(node.below[0].varName)) {
+            actionsArray.push(['X', actions.removeLine, 'delete line']);
+        }
+    } else if (name === 'HOLE') {
+        on('X', HOLE); // TODO define context-specific deletions
+        on('T', TOP);
+        on('_', BOT);
+
+        on('\\', LAMBDA(fresh, CURSOR(HOLE)), [1]);
+        on('W', LETREC(fresh, CURSOR(HOLE), HOLE), [1]);
+        on('L', LETREC(fresh, HOLE, CURSOR(HOLE)), [2]);
+        on('space', APP(HOLE, CURSOR(HOLE)), [1]);
+        on('(', APP(CURSOR(HOLE), HOLE), [0]);
+        on('|', JOIN(CURSOR(HOLE), HOLE), [0]);
+        on('+', RAND(CURSOR(HOLE), HOLE), [0]);
+        on('{', QUOTE(CURSOR(HOLE)), [0]);
+        on('=', EQUAL(CURSOR(HOLE), HOLE), [0]);
+        on('<', LESS(CURSOR(HOLE), HOLE), [0]);
+        on('>', NLESS(CURSOR(HOLE), HOLE), [0]);
+
+        // TODO filter globals and locals by future validity
+        actionsArray.push([
+            '/',
+            function (name) {
+                assert(name !== undefined, 'name not found: ' + name);
+                var newNode = syntax.tree.load(VAR(name));
+                cursor.replaceBelow(newNode);
+            },
+            VAR('global.variable')
+        ]);
+
+        var locals = syntax.tree.getLocals(node);
+        locals.forEach(function (varName) {
+            on(varName, VAR(varName));
+            // TODO deal with >26 variables
+        });
+
+    } else {
+        var dumped = syntax.tree.dump(node);
+
+        // TODO define context-specific deletions
+        on('X', HOLE);
+
+        on('\\', LAMBDA(fresh, CURSOR(DASH)), [1], dumped);
+        on('W', LETREC(fresh, CURSOR(HOLE), DASH), [1], dumped);
+        on('L', LETREC(fresh, DASH, CURSOR(HOLE)), [2], dumped);
+        on('space', APP(DASH, CURSOR(HOLE)), [1], dumped);
+        on('(', APP(CURSOR(HOLE), DASH), [0], dumped);
+        on('|', JOIN(DASH, CURSOR(HOLE)), [1], dumped);
+        on('+', RAND(DASH, CURSOR(HOLE)), [1], dumped);
+        on('{', QUOTE(CURSOR(DASH)), [0], dumped);
+        on('=', EQUAL(DASH, CURSOR(HOLE)), [1], dumped);
+        on('<', LESS(DASH, CURSOR(HOLE)), [1], dumped);
+        on('>', NLESS(DASH, CURSOR(HOLE)), [1], dumped);
+    }
+    return socketLogWrapper(generic.concat(actionsArray));
+};
+
+module.exports = getActions;
diff --git a/puddle-editor/source/app/menu-renderer.js b/puddle-editor/source/app/menu-renderer.js
new file mode 100644
index 0000000..6bfdda4
--- /dev/null
+++ b/puddle-editor/source/app/menu-renderer.js
@@ -0,0 +1,162 @@
+'use strict';
+
+var debug = require('debug')('puddle:editor:menuRenderer');
+var _ = require('lodash');
+var $ = require('jquery');
+var keypress = require('../lib/keypress').keypress;
+var keyListener = new keypress.Listener();
+var trace = require('./trace')(debug);
+var renderTerm = require('./render-term.js');
+var cursor = require('./cursor');
+var corpus = require('./corpus');
+
+var VAR = require('puddle-syntax').compiler.fragments.church.VAR;
+var menuBuilder = require('./menu-builder');
+
+
+//--------------------------------------------------------------------------
+// Event Handling
+
+var search = function (acceptMatch) {
+    trace('Search init');
+    var strings = [];
+    var $input;
+    var matches = [];
+    var $matches;
+    var render = _.identity;
+
+    var update = function () {
+        var re = new RegExp($input.val());
+        matches = [];
+        $matches.empty();
+        strings.forEach(function (string) {
+            if (re.test(string)) {
+                matches.push(string);
+                $matches.append($('
').html(render(string)));
+            }
+        });
+        $matches.children().first().addClass('selected');
+        debug('DEBUG ' + matches);
+    };
+
+
+    var accept = function () {
+        if (matches.length) {
+            debug('DEBUG accepting ' + matches[0]);
+            acceptMatch(matches[0]);
+        }
+    };
+
+    return function () {
+        trace('Search');
+        strings = _.keys(corpus.getDefinitions());
+        render = function (name) {
+            return renderTerm(VAR(name));
+        };
+
+
+        off();
+        on('enter', accept, 'accept');
+        on('tab', render, 'cancel');
+        $input = $('');
+        $matches = $('
'); + $('#navigate').append($input, $matches); + $input.focus().on('keydown', _.debounce(update)); + update(); + }; +}; + +//wrapper to show choose dialog for DEFINE function +var choose = function (acceptName) { + var $input; + var input; + var valid; + + var update = function () { + input = $input.val(); + valid = corpus.canDefine(input); + $input.attr({'class': valid ? 'valid' : 'invalid'}); + }; + + return function () { + trace('choose'); + off(); + + on('enter', function () { + if (valid) { + debug('DEBUG choosing ' + input); + acceptName(input); + } + }, 'accept'); + + on('tab', render, 'cancel'); + + $input = $(''); + $('#navigate').append($input); + $input.focus().on('keydown', _.debounce(update)); + update(); + }; +}; +var upperCaseAliases = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ?><\":{}|~+_!@#$%^&*()'; +var on = function (name, callback, description) { + var listenKey; + if (_.contains(upperCaseAliases, name)) { + listenKey = 'shift ' + name.toLowerCase(); + } else { + listenKey = name.replace(/\+/g, ' '); + } + + /* jshint camelcase:false */ + keyListener.register_combo({ + 'keys': listenKey, + 'on_keydown': function () { + callback(); + }, + is_solitary: true + }); + /* jshint camelcase:true */ + + + //TODO this is a hack to add UI wrapper for some functions + //which require special input parameters or extra UI + if (name === 'D') { + callback = choose(callback); + } + if (name === '/') { + callback = search(callback); + } + + + if (description !== undefined) { + if (_.isArray(description)) { + description = renderTerm(description); + } + var escaped = name.replace(/\b\+\b/g, '+'); + var icon = $('
') + .on('click', callback) + .append(icon, $('
').html('' + escaped + ''); + $('#navigate table').append( + $('
') + .html(description))); + } +}; + +var off = function () { + trace('off'); + keyListener.reset(); + $('#navigate').empty().append($('')); +}; + +var render = function () { + trace('MenuRender'); + off(); + menuBuilder().forEach(function (action) { + on(action[0], action[1], action[2]); + }); +}; + +cursor.on('move', function () { + trace('Cursor move'); + render(); +}); + diff --git a/puddle-editor/source/app/menu/menu.js b/puddle-editor/source/app/menu/menu.js deleted file mode 100644 index c1e89b8..0000000 --- a/puddle-editor/source/app/menu/menu.js +++ /dev/null @@ -1,151 +0,0 @@ -'use strict'; - -var _ = require('lodash'); -var assert = require('assert'); -var syntax = require('puddle-syntax'); -var io = require('socket.io-client'); -var socket = io(); -var debug = require('debug')('puddle:editor:menu'); -var trace = require('../trace')(debug); -var EventEmitter = require('events').EventEmitter; -var emitter = new EventEmitter(); - -var fragments = syntax.compiler.fragments.church; - -var HOLE = fragments.HOLE; -var TOP = fragments.TOP; -var BOT = fragments.BOT; -var VAR = fragments.VAR; -var LAMBDA = fragments.LAMBDA; -var LETREC = fragments.LETREC; -var APP = fragments.APP; -var JOIN = fragments.JOIN; -var RAND = fragments.RAND; -var QUOTE = fragments.QUOTE; -var EQUAL = fragments.EQUAL; -var LESS = fragments.LESS; -var NLESS = fragments.NLESS; -var ASSERT = fragments.ASSERT; -var DEFINE = fragments.DEFINE; -var CURSOR = fragments.CURSOR; -var DASH = VAR('—'); - - -module.exports = function (editor) { - trace('Menu init'); - var actions = editor.getActions(); - var socketLogWrapper = function (actions) { - return actions.map(function (action) { - var name = action[0]; - var callback = function () { - socket.emit('action', action[0]); - action[1].apply(this, _.toArray(arguments)); - }; - var description = action[2]; - return [name, callback, description]; - }); - }; - - - var generic = [ - ['enter', actions.commitLine, 'commit line'], - ['tab', actions.revertLine, 'revert line'], - ['up', actions.moveUp, 'move up'], - ['down', actions.moveDown, 'move down'], - ['left', actions.moveLeft, 'move left'], - ['right', actions.moveRight, 'move right'], - ['shift+left', actions.widenSelection, 'widen selection'], - ['shift+right', actions.widenSelection, 'widen selection'], - ['A', actions.insertAssert, - ASSERT(CURSOR(HOLE))], - ['D', actions.insertDefine, - DEFINE(CURSOR(VAR('...')), HOLE)] - ]; - - var getActions = function () { - trace('getActions'); - var actionsArray = []; - var on = function (name, term, subsForDash) { - actionsArray.push([ - name, - function () { - actions.replaceBelow(term, subsForDash); - }, - term - ]); - }; - var term = editor.getCursor().below[0]; - var name = term.name; - var varName = syntax.tree.getFresh(term); - var fresh = VAR(varName); - - if (name === 'ASSERT') { - actionsArray.push(['X', actions.removeLine, 'delete line']); - } else if (name === 'DEFINE') { - if (!editor.getCorpus().hasOccurrences(term.below[0].varName)) { - actionsArray.push(['X', actions.removeLine, 'delete line']); - } - } else if (name === 'HOLE') { - on('X', HOLE); // TODO define context-specific deletions - on('T', TOP); - on('_', BOT); - on('\\', LAMBDA(fresh, CURSOR(HOLE))); - on('W', LETREC(fresh, CURSOR(HOLE), HOLE)); - on('L', LETREC(fresh, HOLE, CURSOR(HOLE))); - on('space', APP(HOLE, CURSOR(HOLE))); - on('(', APP(CURSOR(HOLE), HOLE)); - on('|', JOIN(CURSOR(HOLE), HOLE)); - on('+', RAND(CURSOR(HOLE), HOLE)); - on('{', QUOTE(CURSOR(HOLE))); - on('=', EQUAL(CURSOR(HOLE), HOLE)); - on('<', LESS(CURSOR(HOLE), HOLE)); - on('>', NLESS(CURSOR(HOLE), HOLE)); - - // TODO filter globals and locals by future validity - actionsArray.push([ - '/', - function (name) { - assert(name !== undefined, 'name not found: ' + name); - actions.replaceBelow(VAR(name)); - }, - VAR('global.variable') - ]); - - var locals = syntax.tree.getLocals(term); - locals.forEach(function (varName) { - on(varName, VAR(varName)); - // TODO deal with >26 variables - }); - - } else { - var dumped = syntax.tree.dump(term); - - // TODO define context-specific deletions - on('X', HOLE); - - on('\\', LAMBDA(fresh, CURSOR(DASH)), dumped); - on('W', LETREC(fresh, CURSOR(HOLE), DASH), dumped); - on('L', LETREC(fresh, DASH, CURSOR(HOLE)), dumped); - on('space', APP(DASH, CURSOR(HOLE)), dumped); - on('(', APP(CURSOR(HOLE), DASH), dumped); - on('|', JOIN(DASH, CURSOR(HOLE)), dumped); - on('+', RAND(DASH, CURSOR(HOLE)), dumped); - on('{', QUOTE(CURSOR(DASH)), dumped); - on('=', EQUAL(DASH, CURSOR(HOLE)), dumped); - on('<', LESS(DASH, CURSOR(HOLE)), dumped); - on('>', NLESS(DASH, CURSOR(HOLE)), dumped); - } - return socketLogWrapper(generic.concat(actionsArray)); - }; - - editor.on('update', function () { - trace('build'); - emitter.emit('update', getActions()); - }); - - return { - on: _.bind(emitter.on, emitter), - getActions: getActions - }; -}; - diff --git a/puddle-editor/source/app/menu/menuRenderer.js b/puddle-editor/source/app/menu/menuRenderer.js deleted file mode 100644 index ad47a09..0000000 --- a/puddle-editor/source/app/menu/menuRenderer.js +++ /dev/null @@ -1,164 +0,0 @@ -'use strict'; - -var debug = require('debug')('puddle:editor:menuRenderer'); -var _ = require('lodash'); -var $ = require('jquery'); -var keypress = require('../../lib/keypress').keypress; -var keyListener = new keypress.Listener(); -var trace = require('../trace')(debug); - -var renderTerm = require('../render-term.js'); -var VAR = require('puddle-syntax').compiler.fragments.church.VAR; - - -module.exports = function (menu, editor) { -//-------------------------------------------------------------------------- -// Event Handling - - var search = function (acceptMatch) { - trace('Search init'); - var strings = []; - var $input; - var matches = []; - var $matches; - var render = _.identity; - - var update = function () { - var re = new RegExp($input.val()); - matches = []; - $matches.empty(); - strings.forEach(function (string) { - if (re.test(string)) { - matches.push(string); - $matches.append($('
').html(render(string)));
-                }
-            });
-            $matches.children().first().addClass('selected');
-            debug('DEBUG ' + matches);
-        };
-
-
-        var accept = function () {
-            if (matches.length) {
-                debug('DEBUG accepting ' + matches[0]);
-                acceptMatch(matches[0]);
-            }
-        };
-
-        return function () {
-            var rankedStrings = editor.getCorpus().findAllNames();
-            trace('Search');
-            strings = rankedStrings;
-            render = function (name) {
-                return renderTerm(VAR(name));
-            };
-
-
-            off();
-            on('enter', accept, 'accept');
-            on('tab', reRender, 'cancel');
-            $input = $('');
-            $matches = $('
'); - $('#navigate').append($input, $matches); - $input.focus().on('keydown', _.debounce(update)); - update(); - }; - }; - - var choose = function (acceptName) { - trace('choose init'); - var $input; - var input; - var valid; - - var update = function () { - input = $input.val(); - valid = editor.getCorpus().canDefine(input); - $input.attr({'class': valid ? 'valid' : 'invalid'}); - }; - - return function () { - trace('choose'); - off(); - - on('enter', function () { - if (valid) { - debug('DEBUG choosing ' + input); - acceptName(input); - } - }, 'accept'); - - on('tab', reRender, 'cancel'); - - $input = $(''); - $('#navigate').append($input); - $input.focus().on('keydown', _.debounce(update)); - update(); - }; - }; - var upperCaseAliases = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ?><\":{}|~+_!@#$%^&*()'; - var on = function (name, callback, description) { - trace('on'); - var listenKey; - if (_.contains(upperCaseAliases, name)) { - listenKey = 'shift ' + name.toLowerCase(); - } else { - listenKey = name.replace(/\+/g, ' '); - } - - //Ignore because of non_camel_case code. - /* jshint ignore:start */ - keyListener.register_combo({ - 'keys': listenKey, - 'on_keydown': function () { - callback(); - }, - is_solitary: true - }); - /* jshint ignore:end */ - - - //TODO this is a hack to add UI wrapper for some functions - //which require special input parameters or extra UI - if (name === 'D') { - callback = choose(callback); - } - if (name === '/') { - callback = search(callback); - } - - - if (description !== undefined) { - if (_.isArray(description)) { - description = renderTerm(description); - } - var escaped = name.replace(/\b\+\b/g, '+'); - var icon = $('
') - .on('click', callback) - .append(icon, $('
').html('' + escaped + ''); - $('#navigate table').append( - $('
') - .html(description))); - } - }; - - var off = function () { - trace('off'); - keyListener.reset(); - $('#navigate').empty().append($('')); - }; - - var render = function (actions) { - debug('Menu render', _.toArray(actions).length); - off(); - actions.forEach(function (action) { - on(action[0], action[1], action[2]); - }); - }; - - var reRender = function () { - render(menu.getActions()); - }; - reRender(); - menu.on('update', render); -}; \ No newline at end of file diff --git a/puddle-editor/source/app/render-validity.js b/puddle-editor/source/app/render-validity.js index cd9192b..158d999 100644 --- a/puddle-editor/source/app/render-validity.js +++ b/puddle-editor/source/app/render-validity.js @@ -23,10 +23,11 @@ var table = { 'false-null-false': svg('yellow', 'delta'), 'null-null-true': svg('gray', 'square'), 'null-false-true': svg('gray', 'nabla'), - 'false-null-true': svg('gray', 'delta'), + 'false-null-true': svg('gray', 'delta') }; module.exports = function (validity) { + validity = validity || {is_top: null, is_bot: null, pending: true}; return table[ validity.is_top + '-' + validity.is_bot + '-' + validity.pending ]; diff --git a/puddle-editor/source/app/server-syntax.js b/puddle-editor/source/app/server-syntax.js index 263889c..2c43b93 100644 --- a/puddle-editor/source/app/server-syntax.js +++ b/puddle-editor/source/app/server-syntax.js @@ -21,6 +21,14 @@ module.exports = { }; })(), dumpStatement : function (statement) { + //TODO this is an API difference hot-fix + //syntax.compiler.dumpLine should give output compatible + //with analyst and server-syntax? + delete statement.token; + if (!statement.name) { + statement.name = null; + } + if (statement.name === null) { return 'ASSERT ' + statement.code; } else { diff --git a/puddle-editor/source/app/trace.js b/puddle-editor/source/app/trace.js index 163a3ed..92b9803 100644 --- a/puddle-editor/source/app/trace.js +++ b/puddle-editor/source/app/trace.js @@ -3,7 +3,7 @@ var _ = require('lodash'); module.exports = function (debug) { return function (name, args, trace) { - debug.apply(debug, ['Trace:', name].concat(_.toArray(args))); + debug.apply(debug, [name].concat(_.toArray(args))); if (trace) { console.trace(); } diff --git a/puddle-editor/source/app/view.js b/puddle-editor/source/app/view.js index 4d2f4bf..e40ea5a 100644 --- a/puddle-editor/source/app/view.js +++ b/puddle-editor/source/app/view.js @@ -2,34 +2,113 @@ var _ = require('lodash'); var $ = require('jquery'); +var forest = require('./forest'); +var corpus = require('./corpus'); +var cursor = require('./cursor'); var syntax = require('puddle-syntax'); var renderTerm = require('./render-term.js'); var renderValidity = require('./render-validity.js'); +var debug = require('debug')('puddle:editor:view'); +var trace = require('./trace')(debug); +trace('view init'); +var renderLine = function (tree) { + var id = tree.id; + var term; + //TODO reconsider cursor rendering + //injecting old style cursor if it is in the same tree + if (cursor.tree() === tree) { + var tempCursor = syntax.cursor.create(); + syntax.cursor.insertAbove(tempCursor, cursor.node); + term = syntax.tree.dump(syntax.tree.getRoot(tree)); + syntax.cursor.remove(tempCursor); + } else { + term = syntax.tree.dump(tree); + } -module.exports = function (editor) { - var render = function () { - var div = $('#code').empty()[0]; - editor.getIds().forEach(function (id, index, array) { - var line = editor.getLine(id); - var validity = editor.getValidity(id); - var $line = $('
').attr('id', 'line' + id).appendTo(div);
-            line = syntax.compiler.parenthesize(line);
-            $line.html(renderValidity(validity) + renderTerm(line));
+    var line = syntax.compiler.parenthesize(term);
+    var $line = $('
').attr('id', 'line' + id);
+    var validityHtml = renderValidity(corpus.id(id).validity);
+    $line.html(validityHtml + renderTerm(line));
+    $line.on('click', function () {
+        cursor.moveTo(tree);
+    });
+    return $line;
+};
 
-            $line.on('click', function () {
-                var newPos = _.indexOf(array, id);
-                var oldPos = _.indexOf(array, editor.getCursorId());
-                var delta = newPos - oldPos;
-                editor.getActions().moveCursorLine(delta);
-            });
+var scrollToCursor = function () {
+    var cursorOffset = $('span.cursor').offset();
+    if (cursorOffset) {
+        var pos = cursorOffset.top - $(window).height() / 2;
+        $(document.body).animate({scrollTop: pos}, 50);
+    }
+};
 
+var render = function () {
+    var div = $('#code').empty()[0];
+    forest.trees
+        .map(renderLine)
+        .forEach(function ($line) {
+            $line.appendTo(div);
         });
-        var pos = $('span.cursor').offset().top - $(window).height() / 2;
-        $(document.body).animate({scrollTop: pos}, 50);
+    scrollToCursor();
+};
+
+var renderValidities = function (updatedLines) {
+    updatedLines.forEach(function (line) {
+        var $validity = $('#line' + line.id + ' .validity');
+        $validity.html(renderValidity(line.validity));
+    });
+};
+
+var createLine = function (tree) {
+    var div = $('#code');
+    var $line = renderLine(tree);
+    var index = forest.trees.indexOf(tree) - 1;
+
+    if (!index) { //if zero or not found
+        div.prepend($line);
+    } else {
+        var prevLineId = forest.trees[index].id;
+        $('#line' + prevLineId).after($line);
+    }
+};
+
+var removeLine = function (id) {
+    $('#line' + id).remove();
+};
+
+var updateLine = function (tree) {
+    $('#line' + tree.id).replaceWith(renderLine(tree));
+};
+
+var cursorMove = function (newNode, oldNode) {
+    var newTree = syntax.tree.getRoot(newNode);
+    updateLine(newTree);
+
+    if (oldNode) {
+        var oldTree = syntax.tree.getRoot(oldNode);
+        if (newTree !== oldTree && !forest.isOrphan(oldNode)) {
+            updateLine(oldTree);
+        }
+    }
+    scrollToCursor();
+};
+
+//this is nice to see how long does rendering take
+var logger = function (event, cb) {
+    return function () {
+        trace('Render ' + event + ' start...');
+        cb.apply(this, _.toArray(arguments));
+        trace('...render ' + event + ' end');
     };
+};
+
+forest.on('create', logger('forest create', createLine));
+forest.on('remove', logger('forest remove', removeLine));
+forest.on('update', logger('forest update', updateLine));
+forest.on('reset', logger('forest reset', render));
+
+cursor.on('move', logger('cursor move', cursorMove));
 
-    render();
-    editor.on('update', render);
-    editor.on('updateValidity', render);
-};
\ No newline at end of file
+corpus.on('updateValidity', logger('updateValidity', renderValidities));
diff --git a/puddle-server/package.json b/puddle-server/package.json
index aa6d224..74ab34d 100644
--- a/puddle-server/package.json
+++ b/puddle-server/package.json
@@ -28,6 +28,7 @@
   },
   "homepage": "https://github.com/pomagma/puddle-server",
   "dependencies": {
+    "body-parser": "^1.9.0",
     "debug": "^2.0.0",
     "express": "^4.9.0",
     "lodash": "^2.4.1",
diff --git a/puddle-server/readme.md b/puddle-server/readme.md
index 405ea20..1c89319 100644
--- a/puddle-server/readme.md
+++ b/puddle-server/readme.md
@@ -5,7 +5,7 @@ Server to store corpus and sync it over multiple clients and pomagma backend.
 
 ###Features:    
 
-    [X] Serves puddle-html5    
+    [X] Serves puddle-editor
     [X] Uses puddle-corpus for corpus storage 
     [X] Uses puddle-hub for corpus sync
     [X] Has livereload feature to speed up development of client-side code
diff --git a/puddle-server/server/server.js b/puddle-server/server/server.js
index 00d6b04..2bf0d86 100644
--- a/puddle-server/server/server.js
+++ b/puddle-server/server/server.js
@@ -4,13 +4,12 @@ var argv = require('yargs').argv;
 var path = require('path');
 var express = require('express');
 var app = express();
-var _ = require('lodash');
 var assert = require('assert');
-var syntax = require('puddle-syntax');
 var http = require('http').Server(app);
 var io = require('socket.io')(http);
 var pomagma = require('pomagma');
 var FROM_LOCALHOST = '127.0.0.1';
+var bodyParser = require('body-parser');
 
 var PORT = process.env.PUDDLE_PORT || 34934;
 var Debug = require('debug');
@@ -19,9 +18,7 @@ var debug = Debug('puddle:server');
 var corpus = require('puddle-corpus')(
     path.join(__dirname, '../corpus/main.json')
 );
-debug('Corpus crud id', corpus.nodeId);
 var puddleSocket = require('puddle-socket').server(io);
-debug('puddleSocket id', puddleSocket.nodeId);
 puddleSocket.connect(corpus);
 
 var analyst = pomagma.analyst.connect(
@@ -33,63 +30,42 @@ process.on('SIGINT', function () {
 });
 process.on('uncaughtException', function (err) {
     if (err.errno === 'EADDRINUSE') {
-        console.log(
+        debug(
                 'ERROR port ' + PORT + ' is already in use.\n' +
                 '    Stop existing puddle server or try another port, e.g.\n' +
                 '    PUDDLE_PORT=' + (PORT + 10) + ' nodejs main.js'
         );
     } else {
-        console.log('Uncaught exception: ' + err);
+        debug('Uncaught exception: ' + err);
     }
     process.exit(1);
 });
 
 
 if (argv.withLiveReload) {
-    console.log('livereload enabled');
+    debug('livereload enabled');
     var LIVERELOAD_PORT = 34939;
     var liveReload = require('connect-livereload')({port: LIVERELOAD_PORT});
     app.use(liveReload);
 }
 
-
-app.get('/corpus/validities', function (req, res) {
-    debug('GET validities');
-    var state = puddleSocket.getState();
-    var lines = [];
-    var ids = [];
-    //TODO fix inconsistency between Analyst, puddle-hub and puddle-syntax API
-    _.each(state, function (code, id) {
-        var line = syntax.compiler.dumpLine(syntax.compiler.load(code));
-        if (!line.name) {
-            line.name = null;
-        }
-        delete line.token;
-        lines.push(line);
-        ids.push(id);
-    });
-
-    analyst.validateCorpus(lines, function (validities) {
-        debug('Validities response');
-        assert.equal(validities.length, ids.length);
-        validities = _.map(_.zip(validities, ids), function (pair) {
-            var validity = pair[0];
-            validity.id = pair[1];
-            return validity;
-        });
-        res.send({'data': validities});
+app.post('/analyst/validities', bodyParser.json(), function (req, res) {
+    debug('Validities requested...');
+    analyst.validateCorpus(req.body, function (validities) {
+        debug('...validities received from analyst and  sent to client');
+        assert.equal(validities.length, req.body.length);
+        res.send(validities);
     });
 });
 
-
 app.use(express.static(path.join(__dirname, '../public')));
 
 http.listen(PORT, FROM_LOCALHOST, function () {
-    console.log('serving puddle at http://localhost:' + PORT);
+    debug('serving puddle at http://localhost:' + PORT);
 });
 
 io.on('connection', function (socket) {
-    console.log('a user connected');
+    debug('a user connected');
     socket.on('action', function (action) {
         debug('Action', action);
     });

From 8d9a1b5f9b8f2344920d3178c4bc43078ba2be47 Mon Sep 17 00:00:00 2001
From: yangit 
Date: Tue, 9 Dec 2014 13:15:57 +0800
Subject: [PATCH 4/7] Fix rendering issue #20

---
 puddle-editor/source/app/view.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/puddle-editor/source/app/view.js b/puddle-editor/source/app/view.js
index e40ea5a..c348eb5 100644
--- a/puddle-editor/source/app/view.js
+++ b/puddle-editor/source/app/view.js
@@ -57,7 +57,7 @@ var render = function () {
 var renderValidities = function (updatedLines) {
     updatedLines.forEach(function (line) {
         var $validity = $('#line' + line.id + ' .validity');
-        $validity.html(renderValidity(line.validity));
+        $validity.replaceWith(renderValidity(line.validity));
     });
 };
 

From eaf43f538343ca6337cf4932274dee5a02e504a1 Mon Sep 17 00:00:00 2001
From: yangit 
Date: Tue, 9 Dec 2014 15:06:46 +0800
Subject: [PATCH 5/7] Fix #18, puddle-hub too verbose in server console.

---
 puddle-editor/package.json | 2 +-
 puddle-server/package.json | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/puddle-editor/package.json b/puddle-editor/package.json
index 5e0867a..0ebb0a5 100644
--- a/puddle-editor/package.json
+++ b/puddle-editor/package.json
@@ -34,7 +34,7 @@
     "jquery": "^2.1.1",
     "lodash": "^2.4.1",
     "node-uuid": "^1.4.1",
-    "puddle-socket": "0.0.1",
+    "puddle-socket": "0.0.2",
     "puddle-syntax": "^0.1.9",
     "socket.io-client": "^1.1.0"
   },
diff --git a/puddle-server/package.json b/puddle-server/package.json
index 74ab34d..9c0fc23 100644
--- a/puddle-server/package.json
+++ b/puddle-server/package.json
@@ -36,7 +36,7 @@
     "pomagma": ">=0.1.8",
     "puddle-corpus": "^1.0.2",
     "puddle-editor": "^1.0.0",
-    "puddle-socket": "0.0.1",
+    "puddle-socket": "0.0.2",
     "puddle-syntax": "^0.1.9",
     "socket.io": "^1.1.0",
     "yargs": "^1.3.1"

From 3de8cca8e45e03dd2103fa759e949a4b285c30f8 Mon Sep 17 00:00:00 2001
From: yangit 
Date: Thu, 11 Dec 2014 10:26:06 +0800
Subject: [PATCH 6/7] Fix #17 Issue Refactor the way cursor is injected into
 the term and removed from term. Cursor injection simplified in view.js

---
 puddle-editor/package.json               |  2 +-
 puddle-editor/source/app/cursor.js       | 13 +++++
 puddle-editor/source/app/main.js         |  1 +
 puddle-editor/source/app/menu-builder.js | 63 ++++++++++--------------
 puddle-editor/source/app/view.js         | 12 ++---
 puddle-server/package.json               |  2 +-
 6 files changed, 46 insertions(+), 47 deletions(-)

diff --git a/puddle-editor/package.json b/puddle-editor/package.json
index 0ebb0a5..3b8ae16 100644
--- a/puddle-editor/package.json
+++ b/puddle-editor/package.json
@@ -35,7 +35,7 @@
     "lodash": "^2.4.1",
     "node-uuid": "^1.4.1",
     "puddle-socket": "0.0.2",
-    "puddle-syntax": "^0.1.9",
+    "puddle-syntax": "^0.2.0",
     "socket.io-client": "^1.1.0"
   },
   "devDependencies": {
diff --git a/puddle-editor/source/app/cursor.js b/puddle-editor/source/app/cursor.js
index 5540d1f..dbd52dd 100644
--- a/puddle-editor/source/app/cursor.js
+++ b/puddle-editor/source/app/cursor.js
@@ -44,6 +44,19 @@ cursor.getRelative = function (route, node) {
     });
     return finish;
 };
+cursor.getAddressInTree = function () {
+    var tree = cursor.tree();
+    var current = cursor.node;
+    var last;
+    var address = [];
+    while (current !== tree) {
+        last = current;
+        current = current.above;
+        //note adding +1, this is because in TERM 0-th index is always string
+        address.unshift(current.below.indexOf(last) + 1);
+    }
+    return address;
+};
 
 cursor.replaceBelow = function (node) {
     var above = cursor.node.above;
diff --git a/puddle-editor/source/app/main.js b/puddle-editor/source/app/main.js
index 5ade9f2..745da6e 100644
--- a/puddle-editor/source/app/main.js
+++ b/puddle-editor/source/app/main.js
@@ -4,6 +4,7 @@ global._ = require('lodash');
 global.$ = require('jquery');
 global.debug = require('debug');
 global.corpus = require('./corpus');
+global.syntax = require('puddle-syntax');
 global.forest = require('./forest');
 global.cursor = require('./cursor');
 require('./view');
diff --git a/puddle-editor/source/app/menu-builder.js b/puddle-editor/source/app/menu-builder.js
index 46ad1dd..a3b1460 100644
--- a/puddle-editor/source/app/menu-builder.js
+++ b/puddle-editor/source/app/menu-builder.js
@@ -88,7 +88,7 @@ var getActions = function () {
     trace('getActions');
     var node = cursor.node;
     var actionsArray = [];
-    var on = function (name, term, cursorAddress, subsForDash) {
+    var on = function (name, term, subsForDash) {
         actionsArray.push([
             name,
             function () {
@@ -97,19 +97,10 @@ var getActions = function () {
                         '—',
                         subsForDash, term);
                 }
-                var newNode = syntax.tree.load(term);
-
-                //TODO reconsider cursor rendering
-                //so that we do not have to remove it here
-                if (cursorAddress) {
-                    var cursorNode = cursor.getRelative(cursorAddress, newNode);
-                    assert(cursorNode.name === 'CURSOR');
-                    syntax.cursor.remove(cursorNode);
-                }
-                cursor.replaceBelow(newNode);
-
-                if (cursorAddress) {
-                    cursor.moveTo(cursor.getRelative(cursorAddress));
+                var decomposed = syntax.cursorTerm.removeCursor(term);
+                cursor.replaceBelow(syntax.tree.load(decomposed.term));
+                if (decomposed.address) {
+                    cursor.moveTo(cursor.getRelative(decomposed.address));
                 }
             },
             term
@@ -130,17 +121,17 @@ var getActions = function () {
         on('T', TOP);
         on('_', BOT);
 
-        on('\\', LAMBDA(fresh, CURSOR(HOLE)), [1]);
-        on('W', LETREC(fresh, CURSOR(HOLE), HOLE), [1]);
-        on('L', LETREC(fresh, HOLE, CURSOR(HOLE)), [2]);
-        on('space', APP(HOLE, CURSOR(HOLE)), [1]);
-        on('(', APP(CURSOR(HOLE), HOLE), [0]);
-        on('|', JOIN(CURSOR(HOLE), HOLE), [0]);
-        on('+', RAND(CURSOR(HOLE), HOLE), [0]);
-        on('{', QUOTE(CURSOR(HOLE)), [0]);
-        on('=', EQUAL(CURSOR(HOLE), HOLE), [0]);
-        on('<', LESS(CURSOR(HOLE), HOLE), [0]);
-        on('>', NLESS(CURSOR(HOLE), HOLE), [0]);
+        on('\\', LAMBDA(fresh, CURSOR(HOLE)));
+        on('W', LETREC(fresh, CURSOR(HOLE), HOLE));
+        on('L', LETREC(fresh, HOLE, CURSOR(HOLE)));
+        on('space', APP(HOLE, CURSOR(HOLE)));
+        on('(', APP(CURSOR(HOLE), HOLE));
+        on('|', JOIN(CURSOR(HOLE), HOLE));
+        on('+', RAND(CURSOR(HOLE), HOLE));
+        on('{', QUOTE(CURSOR(HOLE)));
+        on('=', EQUAL(CURSOR(HOLE), HOLE));
+        on('<', LESS(CURSOR(HOLE), HOLE));
+        on('>', NLESS(CURSOR(HOLE), HOLE));
 
         // TODO filter globals and locals by future validity
         actionsArray.push([
@@ -165,17 +156,17 @@ var getActions = function () {
         // TODO define context-specific deletions
         on('X', HOLE);
 
-        on('\\', LAMBDA(fresh, CURSOR(DASH)), [1], dumped);
-        on('W', LETREC(fresh, CURSOR(HOLE), DASH), [1], dumped);
-        on('L', LETREC(fresh, DASH, CURSOR(HOLE)), [2], dumped);
-        on('space', APP(DASH, CURSOR(HOLE)), [1], dumped);
-        on('(', APP(CURSOR(HOLE), DASH), [0], dumped);
-        on('|', JOIN(DASH, CURSOR(HOLE)), [1], dumped);
-        on('+', RAND(DASH, CURSOR(HOLE)), [1], dumped);
-        on('{', QUOTE(CURSOR(DASH)), [0], dumped);
-        on('=', EQUAL(DASH, CURSOR(HOLE)), [1], dumped);
-        on('<', LESS(DASH, CURSOR(HOLE)), [1], dumped);
-        on('>', NLESS(DASH, CURSOR(HOLE)), [1], dumped);
+        on('\\', LAMBDA(fresh, CURSOR(DASH)), dumped);
+        on('W', LETREC(fresh, CURSOR(HOLE), DASH), dumped);
+        on('L', LETREC(fresh, DASH, CURSOR(HOLE)), dumped);
+        on('space', APP(DASH, CURSOR(HOLE)), dumped);
+        on('(', APP(CURSOR(HOLE), DASH), dumped);
+        on('|', JOIN(DASH, CURSOR(HOLE)),  dumped);
+        on('+', RAND(DASH, CURSOR(HOLE)), dumped);
+        on('{', QUOTE(CURSOR(DASH)), dumped);
+        on('=', EQUAL(DASH, CURSOR(HOLE)), dumped);
+        on('<', LESS(DASH, CURSOR(HOLE)), dumped);
+        on('>', NLESS(DASH, CURSOR(HOLE)), dumped);
     }
     return socketLogWrapper(generic.concat(actionsArray));
 };
diff --git a/puddle-editor/source/app/view.js b/puddle-editor/source/app/view.js
index c348eb5..c167725 100644
--- a/puddle-editor/source/app/view.js
+++ b/puddle-editor/source/app/view.js
@@ -14,16 +14,10 @@ trace('view init');
 
 var renderLine = function (tree) {
     var id = tree.id;
-    var term;
-    //TODO reconsider cursor rendering
-    //injecting old style cursor if it is in the same tree
+    var term  = syntax.tree.dump(tree);
+    //inject CURSOR in term if necessary.
     if (cursor.tree() === tree) {
-        var tempCursor = syntax.cursor.create();
-        syntax.cursor.insertAbove(tempCursor, cursor.node);
-        term = syntax.tree.dump(syntax.tree.getRoot(tree));
-        syntax.cursor.remove(tempCursor);
-    } else {
-        term = syntax.tree.dump(tree);
+        term = syntax.cursorTerm.insertCursor(term, cursor.getAddressInTree());
     }
 
     var line = syntax.compiler.parenthesize(term);
diff --git a/puddle-server/package.json b/puddle-server/package.json
index 9c0fc23..89ac8be 100644
--- a/puddle-server/package.json
+++ b/puddle-server/package.json
@@ -37,7 +37,7 @@
     "puddle-corpus": "^1.0.2",
     "puddle-editor": "^1.0.0",
     "puddle-socket": "0.0.2",
-    "puddle-syntax": "^0.1.9",
+    "puddle-syntax": "^0.2.0",
     "socket.io": "^1.1.0",
     "yargs": "^1.3.1"
   },

From 5553be83ae905cf0badaf9577b4ccf17423139e6 Mon Sep 17 00:00:00 2001
From: yangit 
Date: Thu, 11 Dec 2014 14:31:59 +0800
Subject: [PATCH 7/7] Fix #19 Added line checkout notification across multiple
 clients.

---
 puddle-editor/source/app/corpus.js |  4 +++
 puddle-editor/source/app/main.js   |  2 ++
 puddle-editor/source/app/view.js   | 47 +++++++++++++++++++++++++-----
 puddle-server/server/server.js     | 23 +++++++++++++++
 4 files changed, 68 insertions(+), 8 deletions(-)

diff --git a/puddle-editor/source/app/corpus.js b/puddle-editor/source/app/corpus.js
index d66ada7..00915d1 100644
--- a/puddle-editor/source/app/corpus.js
+++ b/puddle-editor/source/app/corpus.js
@@ -10,6 +10,7 @@ var tokens = syntax.tokens;
 var assert = require('./assert');
 var debug = require('debug')('puddle:editor:corpus');
 var io = require('socket.io-client');
+var socket = io();
 var puddleSocket = require('puddle-socket').client(io);
 var EventEmitter = require('events').EventEmitter;
 var emitter = new EventEmitter();
@@ -350,6 +351,7 @@ corpus.insertAssert = function () {
 corpus.checkOut = function (tree) {
     trace('checkout');
     var id = tree.id;
+    socket.emit('checkOut', id);
     assert(id);
     assert(!checkedOut[id]);
     checkedOut[id] = syntax.compiler.dumpLine(syntax.tree.dump(tree));
@@ -359,6 +361,7 @@ corpus.checkOut = function (tree) {
 corpus.unCheckOut = function (tree) {
     trace('unCheckOut');
     var id = tree.id;
+    socket.emit('checkIn', id);
     assert(id);
     assert(checkedOut[id]);
     delete checkedOut[id];
@@ -367,6 +370,7 @@ corpus.unCheckOut = function (tree) {
 corpus.checkIn = function (updatedTree) {
     trace('checkin');
     var id = updatedTree.id;
+    socket.emit('checkIn', id);
     var oldLine = checkedOut[id];
     assert(oldLine);
     var newLine = syntax.compiler.dumpLine(syntax.tree.dump(updatedTree));
diff --git a/puddle-editor/source/app/main.js b/puddle-editor/source/app/main.js
index 745da6e..5316e8a 100644
--- a/puddle-editor/source/app/main.js
+++ b/puddle-editor/source/app/main.js
@@ -5,6 +5,8 @@ global.$ = require('jquery');
 global.debug = require('debug');
 global.corpus = require('./corpus');
 global.syntax = require('puddle-syntax');
+var io = require('socket.io-client');
+global.socket = io();
 global.forest = require('./forest');
 global.cursor = require('./cursor');
 require('./view');
diff --git a/puddle-editor/source/app/view.js b/puddle-editor/source/app/view.js
index c167725..ecb44d4 100644
--- a/puddle-editor/source/app/view.js
+++ b/puddle-editor/source/app/view.js
@@ -6,15 +6,18 @@ var forest = require('./forest');
 var corpus = require('./corpus');
 var cursor = require('./cursor');
 var syntax = require('puddle-syntax');
+var io = require('socket.io-client');
+var socket = io();
 var renderTerm = require('./render-term.js');
 var renderValidity = require('./render-validity.js');
 var debug = require('debug')('puddle:editor:view');
 var trace = require('./trace')(debug);
 trace('view init');
+var checkInOutState = {};
 
 var renderLine = function (tree) {
     var id = tree.id;
-    var term  = syntax.tree.dump(tree);
+    var term = syntax.tree.dump(tree);
     //inject CURSOR in term if necessary.
     if (cursor.tree() === tree) {
         term = syntax.cursorTerm.insertCursor(term, cursor.getAddressInTree());
@@ -24,6 +27,16 @@ var renderLine = function (tree) {
     var $line = $('
').attr('id', 'line' + id);
     var validityHtml = renderValidity(corpus.id(id).validity);
     $line.html(validityHtml + renderTerm(line));
+
+    //add marker if line is checked out.
+    if (checkInOutState[id]) {
+        $line = $line.prepend(
+            '' +
+            '' +
+            '');
+    }
+
     $line.on('click', function () {
         cursor.moveTo(tree);
     });
@@ -90,7 +103,7 @@ var cursorMove = function (newNode, oldNode) {
 };
 
 //this is nice to see how long does rendering take
-var logger = function (event, cb) {
+var timeLogger = function (event, cb) {
     return function () {
         trace('Render ' + event + ' start...');
         cb.apply(this, _.toArray(arguments));
@@ -98,11 +111,29 @@ var logger = function (event, cb) {
     };
 };
 
-forest.on('create', logger('forest create', createLine));
-forest.on('remove', logger('forest remove', removeLine));
-forest.on('update', logger('forest update', updateLine));
-forest.on('reset', logger('forest reset', render));
+forest.on('create', timeLogger('forest create', createLine));
+forest.on('remove', timeLogger('forest remove', removeLine));
+forest.on('update', timeLogger('forest update', updateLine));
+forest.on('reset', timeLogger('forest reset', render));
+
+cursor.on('move', timeLogger('cursor move', cursorMove));
+
+corpus.on('updateValidity', timeLogger('updateValidity', renderValidities));
 
-cursor.on('move', logger('cursor move', cursorMove));
+socket.on('checkInOutUpdate', timeLogger('checkInOutUpdate', function (state) {
+
+    var toBeUpdated = _.uniq(_.keys(checkInOutState).concat(_.values(state)));
+
+    //inverse hash from clientId=>lineId to lineId=>clientId
+    checkInOutState = {};
+    _.each(state, function (value, key) {
+        checkInOutState[value] = key;
+    });
+    toBeUpdated.forEach(function (id) {
+        var tree = forest.id(id);
+        if (tree) {
+            updateLine(tree);
+        }
+    });
 
-corpus.on('updateValidity', logger('updateValidity', renderValidities));
+}));
\ No newline at end of file
diff --git a/puddle-server/server/server.js b/puddle-server/server/server.js
index 2bf0d86..28c8a6b 100644
--- a/puddle-server/server/server.js
+++ b/puddle-server/server/server.js
@@ -64,12 +64,35 @@ http.listen(PORT, FROM_LOCALHOST, function () {
     debug('serving puddle at http://localhost:' + PORT);
 });
 
+
+var checkInOutState = {};
+var checkInOutUpdate = function () {
+    io.emit('checkInOutUpdate', checkInOutState);
+};
+
 io.on('connection', function (socket) {
     debug('a user connected');
     socket.on('action', function (action) {
         debug('Action', action);
     });
 
+    checkInOutUpdate();
+    var checkIn = function (id) {
+        debug('checkIn', id, 'by', socket.id);
+        delete checkInOutState[socket.id];
+        checkInOutUpdate();
+    };
+    socket.on('checkIn', checkIn);
+    socket.on('checkOut', function (id) {
+        debug('checkOut', id, 'by', socket.id);
+        checkInOutState[socket.id] = id;
+        checkInOutUpdate();
+    });
+    socket.on('disconnect', function () {
+        checkIn();
+        debug('Client',socket.id, 'disconnected');
+    });
+
 });