diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 000000000..e77de230f
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,17 @@
+{
+ "rules": {
+ "curly": [2, "multi-line"],
+ "eol-last": [2],
+ "indent": [2, 4],
+ "quotes": [2, "single"],
+ "linebreak-style": [2, "unix"],
+ "max-len": [2, 80, 4],
+ "semi": [2, "always"],
+ "strict": [2, "never"]
+ },
+ "env": {
+ "node": true,
+ "browser": true
+ },
+ "extends": "eslint:recommended"
+}
diff --git a/.npmrc b/.npmrc
deleted file mode 100644
index 9285cdf79..000000000
--- a/.npmrc
+++ /dev/null
@@ -1,5 +0,0 @@
-engine-strict=true
-save-exact=true
-save-prefix=~
-init-license=BSD-3-Clause
-init-author-name=Massachusetts Institute of Technology
diff --git a/Makefile b/Makefile
index 7d2fd8630..4ed181119 100644
--- a/Makefile
+++ b/Makefile
@@ -1,12 +1,21 @@
ESLINT=./node_modules/.bin/eslint
NODE=node
TAP=./node_modules/.bin/tap
+WEBPACK=./node_modules/.bin/webpack --progress --colors
+
+# ------------------------------------------------------------------------------
+
+build:
+ $(WEBPACK)
+
+watch:
+ $(WEBPACK) --watch
# ------------------------------------------------------------------------------
lint:
- $(ESLINT) ./*.js
- $(ESLINT) ./lib/*.js
+ $(ESLINT) ./src/*.js
+ $(ESLINT) ./src/**/*.js
$(ESLINT) ./test/**/*.js
test:
@@ -21,4 +30,4 @@ benchmark:
# ------------------------------------------------------------------------------
-.PHONY: lint test coverage benchmark
+.PHONY: build lint test coverage benchmark
diff --git a/README.md b/README.md
index a93d11bdc..8933d7c7b 100644
--- a/README.md
+++ b/README.md
@@ -5,9 +5,29 @@
npm install scratch-vm
```
-## Integration
+## Setup
```js
+var VirtualMachine = require('scratch-vm');
+var vm = new VirtualMachine();
+// Block events
+
+// UI events
+
+// Listen for events
+```
+
+## Standalone Build
+```bash
+make build
+```
+
+```html
+
+
```
## Testing
diff --git a/index.js b/index.js
deleted file mode 100644
index e69de29bb..000000000
diff --git a/package.json b/package.json
index 870ef2bb2..90f5105db 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "scratch-vm",
"version": "1.0.0",
- "description": "",
+ "description": "Virtual Machine for Scratch 3.0",
"author": "Massachusetts Institute of Technology",
"license": "BSD-3-Clause",
"homepage": "https://github.com/LLK/scratch-vm#readme",
@@ -9,13 +9,15 @@
"type": "git",
"url": "git+ssh://git@github.com/LLK/scratch-vm.git"
},
- "main": "index.js",
+ "main": "./src/index.js",
"scripts": {
"test": "make test"
},
+ "dependencies": {},
"devDependencies": {
"benchmark": "2.1.0",
"eslint": "2.7.0",
- "tap": "5.7.1"
+ "tap": "5.7.1",
+ "webpack": "1.13.0"
}
}
diff --git a/src/engine/primatives.js b/src/engine/primatives.js
new file mode 100644
index 000000000..c8ee6c193
--- /dev/null
+++ b/src/engine/primatives.js
@@ -0,0 +1,41 @@
+function Primitives () {
+
+}
+
+Primitives.prototype.event_whenflagclicked = function (thread, runtime) {
+ // No-op: flags are started by the interpreter but don't do any action
+ // Take 1/3 second to show running state
+ if (Date.now() - thread.blockFirstTime < 300) {
+ thread.yield = true;
+ return;
+ }
+};
+
+Primitives.prototype.control_repeat = function (thread, runtime) {
+ // Take 1/3 second to show running state
+ if (Date.now() - thread.blockFirstTime < 300) {
+ thread.yield = true;
+ return;
+ }
+ if (thread.repeatCounter == -1) {
+ thread.repeatCounter = 10; // @todo from the arg
+ }
+ if (thread.repeatCounter > 0) {
+ thread.repeatCounter -= 1;
+ runtime.interpreter.startSubstack(thread);
+ } else {
+ thread.repeatCounter = -1;
+ thread.nextBlock = runtime.getNextBlock(thread.blockPointer);
+ }
+};
+
+Primitives.prototype.control_forever = function (thread, runtime) {
+ // Take 1/3 second to show running state
+ if (Date.now() - thread.blockFirstTime < 300) {
+ thread.yield = true;
+ return;
+ }
+ runtime.interpreter.startSubstack(thread);
+};
+
+module.exports = Primitives;
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
new file mode 100644
index 000000000..e4f94f8f3
--- /dev/null
+++ b/src/engine/runtime.js
@@ -0,0 +1,119 @@
+var Primitives = require('./primatives');
+var Sequencer = require('./sequencer');
+var Thread = require('./thread');
+
+var STEP_THREADS_INTERVAL = 1000 / 30;
+
+/**
+ * A simple runtime for blocks.
+ */
+function Runtime () {
+ this.sequencer = new Sequencer(this);
+ this.primitives = new Primitives();
+
+ // State
+ this.blocks = {};
+ this.stacks = [];
+}
+
+Runtime.prototype.createBlock = function (e) {
+ // Create new block
+ this.blocks[e.id] = {
+ id: e.id,
+ opcode: e.opcode,
+ next: null,
+ inputs: {}
+ };
+
+ // Push block id to stacks array. New blocks are always a stack even if only
+ // momentary. If the new block is added to an existing stack this stack will
+ // be removed by the `moveBlock` method below.
+ this.stacks.push(e.id);
+};
+
+Runtime.prototype.moveBlock = function (e) {
+ var _this = this;
+
+ // Block has a new parent
+ if (e.oldParent === undefined && e.newParent !== undefined) {
+ // Remove stack
+ _this._deleteStack(e.id);
+
+ // Update new parent
+ if (e.newInput === undefined) {
+ _this.blocks[e.newParent].next = e.id;
+ } else {
+ _this.blocks[e.newParent].inputs[e.newInput] = e.id;
+ }
+ }
+
+ // Block was removed from parent
+ if (e.newParentId === undefined && e.oldParent !== undefined) {
+ // Add stack
+ _this.stacks.push(e.id);
+
+ // Update old parent
+ if (e.oldInput === undefined) {
+ _this.blocks[e.oldParent].next = null;
+ } else {
+ delete _this.blocks[e.oldParent].inputs[e.oldInput];
+ }
+ }
+};
+
+Runtime.prototype.changeBlock = function (e) {
+ // @todo
+};
+
+Runtime.prototype.deleteBlock = function (e) {
+ // @todo Stop threads running on this stack
+
+ // Delete children
+ var block = this.blocks[e.id];
+ if (block.next !== null) {
+ this.deleteBlock({id: block.next});
+ }
+
+ // Delete inputs
+ for (var i in block.inputs) {
+ this.deleteBlock({id: block.inputs[i]});
+ }
+
+ // Delete stack
+ this._deleteStack(e.id);
+
+ // Delete block
+ delete this.blocks[e.id];
+};
+
+Runtime.prototype.runAllStacks = function () {
+ // @todo
+};
+
+Runtime.prototype.runStack = function () {
+ // @todo
+};
+
+Runtime.prototype.stopAllStacks = function () {
+ // @todo
+};
+
+// -----------------------------------------------------------------------------
+// -----------------------------------------------------------------------------
+
+Runtime.prototype._deleteStack = function (id) {
+ var i = this.stacks.indexOf(id);
+ if (i > -1) this.stacks.splice(i, 1);
+};
+
+Runtime.prototype._getNextBlock = function (id) {
+ if (typeof this.blocks[id] === 'undefined') return null;
+ return this.blocks[id].next;
+};
+
+Runtime.prototype._getSubstack = function (id) {
+ if (typeof this.blocks[id] === 'undefined') return null;
+ return this.blocks[id].inputs['SUBSTACK'];
+};
+
+module.exports = Runtime;
diff --git a/src/engine/sequencer.js b/src/engine/sequencer.js
new file mode 100644
index 000000000..09723614f
--- /dev/null
+++ b/src/engine/sequencer.js
@@ -0,0 +1,29 @@
+var Timer = require('../util/timer');
+
+/**
+ * Constructor
+ */
+function Sequencer (runtime) {
+ // Bi-directional binding for runtime
+ this.runtime = runtime;
+
+ // State
+ this.runningThreads = [];
+ this.workTime = 30;
+ this.timer = new Timer();
+ this.currentTime = 0;
+}
+
+Sequencer.prototype.stepAllThreads = function () {
+
+};
+
+Sequencer.prototype.stepThread = function (thread) {
+
+};
+
+Sequencer.prototype.startSubstack = function (thread) {
+
+};
+
+module.exports = Sequencer;
diff --git a/src/engine/thread.js b/src/engine/thread.js
new file mode 100644
index 000000000..a3ce684ab
--- /dev/null
+++ b/src/engine/thread.js
@@ -0,0 +1,19 @@
+/**
+ * Thread is an internal data structure used by the interpreter. It holds the
+ * state of a thread so it can continue from where it left off, and it has
+ * a stack to support nested control structures and procedure calls.
+ *
+ * @param {String} Unique block identifier
+ */
+function Thread (id) {
+ this.topBlockId = id;
+ this.blockPointer = id;
+ this.blockFirstTime = -1;
+ this.nextBlock = null;
+ this.waiting = null;
+ this.runningDeviceBlock = false;
+ this.stack = [];
+ this.repeatCounter = -1;
+}
+
+module.exports = Thread;
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 000000000..ecbcecdbd
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,148 @@
+var EventEmitter = require('events');
+var util = require('util');
+
+var Runtime = require('./engine/runtime');
+
+/**
+ * Handles connections between blocks, stage, and extensions.
+ *
+ * @author Andrew Sliwinski
+ */
+function VirtualMachine () {
+ var instance = this;
+
+ // Bind event emitter and runtime to VM instance
+ EventEmitter.call(instance);
+ instance.runtime = new Runtime();
+
+ /**
+ * Event listener for blockly. Handles validation and serves as a generic
+ * adapter between the blocks and the runtime interface.
+ *
+ * @param {Object} Blockly "block" event
+ */
+ instance.blockListener = function (e) {
+ // Validate event
+ if (typeof e !== 'object') return;
+ if (typeof e.blockId !== 'string') return;
+
+ // Blocks
+ switch (e.type) {
+ case 'create':
+ instance.runtime.createBlock({
+ id: e.blockId,
+ opcode: e.xml.attributes.type.value
+ });
+ break;
+ case 'move':
+ instance.runtime.moveBlock({
+ id: e.blockId,
+ oldParent: e.oldParentId,
+ oldInput: e.oldInputName,
+ newParent: e.newParentId,
+ newInput: e.newInputName
+ });
+ break;
+ case 'change':
+ instance.runtime.changeBlock({
+ id: e.blockId
+ });
+ break;
+ case 'delete':
+ instance.runtime.deleteBlock({
+ id: e.blockId
+ });
+ break;
+ }
+ };
+
+ // @todo UI listener
+
+ // @todo Forward runtime events
+
+ // Event dispatcher
+ // this.types = keymirror({
+ // // Messages to runtime
+ // CREATE_BLOCK: null,
+ // MOVE_BLOCK: null,
+ // CHANGE_BLOCK: null,
+ // DELETE_BLOCK: null,
+ //
+ // ADD_DEVICE: null,
+ // REMOVE_DEVICE: null,
+ //
+ // RUN_STRIP: null,
+ // RUN_ALL_STRIPS: null,
+ // STOP_ALL_STRIPS: null,
+ // RUN_PALETTE_BLOCK: null,
+ //
+ // // Messages from runtime - subscribe to these
+ // FEEDBACK_EXECUTING_BLOCK: null,
+ // FEEDBACK_STOPPED_EXECUTING_BLOCK: null,
+ // DEVICE_RUN_OP: null,
+ // DEVICE_STOP_OP: null,
+ //
+ // // Tell back the interpreter device has finished an op
+ // DEVICE_FINISHED_OP: null
+ // });
+
+ // Bind block event stream
+ // setTimeout(function () {
+ // _this.emit('foo', 'bar');
+ // }, 1000);
+}
+
+/**
+ * Inherit from EventEmitter
+ */
+util.inherits(VirtualMachine, EventEmitter);
+
+// VirtualMachine.prototype.changeListener = function (e) {
+// var _this = this;
+// console.dir(this);
+//
+// switch (e.type) {
+// case 'create':
+// console.dir(e);
+// _this.runtime.createBlock(
+// e.blockId,
+// event.xml.attributes.type.value
+// );
+// break;
+// case 'change':
+// // @todo
+// break;
+// case 'move':
+// // @todo
+// break;
+// case 'delete':
+// // @todo
+// break;
+// }
+// };
+//
+// VirtualMachine.prototype.tapListener = function (e) {
+// // @todo
+// };
+
+VirtualMachine.prototype.start = function () {
+ this.runtime.runAllGreenFlags();
+};
+
+VirtualMachine.prototype.stop = function () {
+ this.runtime.stop();
+};
+
+VirtualMachine.prototype.save = function () {
+ // @todo Serialize runtime state
+};
+
+VirtualMachine.prototype.load = function () {
+ // @todo Deserialize and apply runtime state
+};
+
+/**
+ * Export and bind to `window`
+ */
+module.exports = VirtualMachine;
+if (typeof window !== 'undefined') window.VirtualMachine = module.exports;
diff --git a/src/util/timer.js b/src/util/timer.js
new file mode 100644
index 000000000..f093c3e53
--- /dev/null
+++ b/src/util/timer.js
@@ -0,0 +1,21 @@
+/**
+ * Constructor
+ * @todo Swap out Date.now() with microtime module that works in node & browsers
+ */
+function Timer () {
+ this.startTime = 0;
+}
+
+Timer.prototype.time = function () {
+ return Date.now();
+};
+
+Timer.prototype.start = function () {
+ this.startTime = this.time();
+};
+
+Timer.prototype.stop = function () {
+ return this.startTime - this.time();
+};
+
+module.exports = Timer;
diff --git a/test/fixtures/blocks.js b/test/fixtures/blocks.js
new file mode 100644
index 000000000..0b5c396b0
--- /dev/null
+++ b/test/fixtures/blocks.js
@@ -0,0 +1,26 @@
+var events = require('events');
+var util = require('util');
+
+/**
+ * Simulates event emitter / listener patterns from Scratch Blocks.
+ *
+ * @author Andrew Sliwinski
+ */
+function Blocks () {
+
+}
+
+/**
+ * Inherit from EventEmitter to enable messaging.
+ */
+util.inherits(VirtualMachine, events.EventEmitter);
+
+Blocks.prototype.spaghetti = function () {
+ this.emit('');
+};
+
+Blocks.prototype.spam = function () {
+ this.emit('');
+};
+
+module.exports = Blocks;
diff --git a/test/unit/runtime.js b/test/unit/runtime.js
new file mode 100644
index 000000000..ac58d4e6b
--- /dev/null
+++ b/test/unit/runtime.js
@@ -0,0 +1,81 @@
+var test = require('tap').test;
+var Runtime = require('../../src/engine/runtime');
+
+test('spec', function (t) {
+ var r = new Runtime();
+
+ t.type(Runtime, 'function');
+ t.type(r, 'object');
+ t.ok(r instanceof Runtime);
+
+ t.type(r.blocks, 'object');
+ t.type(r.stacks, 'object');
+ t.ok(Array.isArray(r.stacks));
+
+ t.type(r.createBlock, 'function');
+ t.type(r.moveBlock, 'function');
+ t.type(r.changeBlock, 'function');
+ t.type(r.deleteBlock, 'function');
+
+ t.end();
+});
+
+test('create', function (t) {
+ var r = new Runtime();
+ r.createBlock({
+ id: 'foo',
+ opcode: 'TEST_BLOCK'
+ });
+
+ t.type(r.blocks['foo'], 'object');
+ t.equal(r.blocks['foo'].opcode, 'TEST_BLOCK');
+ t.notEqual(r.stacks.indexOf('foo'), -1);
+ t.end();
+});
+
+test('move', function (t) {
+ var r = new Runtime();
+ r.createBlock({
+ id: 'foo',
+ opcode: 'TEST_BLOCK'
+ });
+ r.createBlock({
+ id: 'bar',
+ opcode: 'TEST_BLOCK'
+ });
+
+ // Attach 'bar' to the end of 'foo'
+ r.moveBlock({
+ id: 'bar',
+ newParent: 'foo'
+ });
+ t.equal(r.stacks.length, 1);
+ t.equal(Object.keys(r.blocks).length, 2);
+ t.equal(r.blocks['foo'].next, 'bar');
+
+ // Detach 'bar' from 'foo'
+ r.moveBlock({
+ id: 'bar',
+ oldParent: 'foo'
+ });
+ t.equal(r.stacks.length, 2);
+ t.equal(Object.keys(r.blocks).length, 2);
+ t.equal(r.blocks['foo'].next, null);
+
+ t.end();
+});
+
+test('delete', function (t) {
+ var r = new Runtime();
+ r.createBlock({
+ id: 'foo',
+ opcode: 'TEST_BLOCK'
+ });
+ r.deleteBlock({
+ id: 'foo'
+ });
+
+ t.type(r.blocks['foo'], 'undefined');
+ t.equal(r.stacks.indexOf('foo'), -1);
+ t.end();
+});
diff --git a/test/unit/spec.js b/test/unit/spec.js
index c7627a3ec..796c165fe 100644
--- a/test/unit/spec.js
+++ b/test/unit/spec.js
@@ -1,6 +1,18 @@
var test = require('tap').test;
-var vm = require('../../index');
+var VirtualMachine = require('../../src/index');
test('spec', function (t) {
+ var vm = new VirtualMachine('foo');
+
+ t.type(VirtualMachine, 'function');
+ t.type(vm, 'object');
+
+ t.type(vm.blockListener, 'function');
+ // t.type(vm.uiListener, 'function');
+ // t.type(vm.start, 'function');
+ // t.type(vm.stop, 'function');
+ // t.type(vm.save, 'function');
+ // t.type(vm.load, 'function');
+
t.end();
});
diff --git a/test/unit/thread.js b/test/unit/thread.js
new file mode 100644
index 000000000..3e5cede60
--- /dev/null
+++ b/test/unit/thread.js
@@ -0,0 +1,10 @@
+var test = require('tap').test;
+var Thread = require('../../src/engine/thread');
+
+test('spec', function (t) {
+ var thread = new Thread('foo');
+
+ t.type(Thread, 'function');
+ t.type(thread, 'object');
+ t.end();
+});
diff --git a/test/unit/timer.js b/test/unit/timer.js
new file mode 100644
index 000000000..3993e8e72
--- /dev/null
+++ b/test/unit/timer.js
@@ -0,0 +1,41 @@
+var test = require('tap').test;
+var Timer = require('../../src/util/timer');
+
+test('spec', function (t) {
+ var timer = new Timer();
+
+ t.type(Timer, 'function');
+ t.type(timer, 'object');
+
+ t.type(timer.startTime, 'number');
+ t.type(timer.time, 'function');
+ t.type(timer.start, 'function');
+ t.type(timer.stop, 'function');
+
+ t.end();
+});
+
+test('time', function (t) {
+ var timer = new Timer();
+ var time = timer.time();
+
+ t.ok(Date.now() >= time);
+ t.end();
+});
+
+test('start / stop', function (t) {
+ var timer = new Timer();
+ var start = timer.time();
+ var delay = 100;
+ var threshold = 1000 / 60; // 60 hz
+
+ // Start timer
+ timer.start();
+
+ // Wait and stop timer
+ setTimeout(function () {
+ var stop = timer.stop();
+ t.ok(stop >= -(delay + threshold) && stop <= -(delay - threshold));
+ t.end();
+ }, delay);
+});
diff --git a/vm.js b/vm.js
new file mode 100644
index 000000000..430c27449
--- /dev/null
+++ b/vm.js
@@ -0,0 +1,1491 @@
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+
+
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var EventEmitter = __webpack_require__(1);
+ var util = __webpack_require__(2);
+
+ var Runtime = __webpack_require__(6);
+
+ /**
+ * Handles connections between blocks, stage, and extensions.
+ *
+ * @author Andrew Sliwinski
+ */
+ function VirtualMachine () {
+ var instance = this;
+
+ // Bind event emitter and runtime to VM instance
+ EventEmitter.call(instance);
+ instance.runtime = new Runtime();
+
+ /**
+ * Event listener for blockly. Handles validation and serves as a generic
+ * adapter between the blocks and the runtime interface.
+ *
+ * @param {Object} Blockly "block" event
+ */
+ instance.blockListener = function (e) {
+ // Validate event
+ if (typeof e !== 'object') return;
+ if (typeof e.blockId !== 'string') return;
+
+ // Blocks
+ switch (e.type) {
+ case 'create':
+ instance.runtime.createBlock({
+ id: e.blockId,
+ opcode: e.xml.attributes.type.value
+ });
+ break;
+ case 'move':
+ instance.runtime.moveBlock({
+ id: e.blockId,
+ oldParent: e.oldParentId,
+ oldInput: e.oldInputName,
+ newParent: e.newParentId,
+ newInput: e.newInputName
+ });
+ break;
+ case 'change':
+ instance.runtime.changeBlock({
+ id: e.blockId
+ });
+ break;
+ case 'delete':
+ instance.runtime.deleteBlock({
+ id: e.blockId
+ });
+ break;
+ }
+ };
+
+ // @todo UI listener
+
+ // @todo Forward runtime events
+
+ // Event dispatcher
+ // this.types = keymirror({
+ // // Messages to runtime
+ // CREATE_BLOCK: null,
+ // MOVE_BLOCK: null,
+ // CHANGE_BLOCK: null,
+ // DELETE_BLOCK: null,
+ //
+ // ADD_DEVICE: null,
+ // REMOVE_DEVICE: null,
+ //
+ // RUN_STRIP: null,
+ // RUN_ALL_STRIPS: null,
+ // STOP_ALL_STRIPS: null,
+ // RUN_PALETTE_BLOCK: null,
+ //
+ // // Messages from runtime - subscribe to these
+ // FEEDBACK_EXECUTING_BLOCK: null,
+ // FEEDBACK_STOPPED_EXECUTING_BLOCK: null,
+ // DEVICE_RUN_OP: null,
+ // DEVICE_STOP_OP: null,
+ //
+ // // Tell back the interpreter device has finished an op
+ // DEVICE_FINISHED_OP: null
+ // });
+
+ // Bind block event stream
+ // setTimeout(function () {
+ // _this.emit('foo', 'bar');
+ // }, 1000);
+ }
+
+ /**
+ * Inherit from EventEmitter
+ */
+ util.inherits(VirtualMachine, EventEmitter);
+
+ // VirtualMachine.prototype.changeListener = function (e) {
+ // var _this = this;
+ // console.dir(this);
+ //
+ // switch (e.type) {
+ // case 'create':
+ // console.dir(e);
+ // _this.runtime.createBlock(
+ // e.blockId,
+ // event.xml.attributes.type.value
+ // );
+ // break;
+ // case 'change':
+ // // @todo
+ // break;
+ // case 'move':
+ // // @todo
+ // break;
+ // case 'delete':
+ // // @todo
+ // break;
+ // }
+ // };
+ //
+ // VirtualMachine.prototype.tapListener = function (e) {
+ // // @todo
+ // };
+
+ VirtualMachine.prototype.start = function () {
+ this.runtime.runAllGreenFlags();
+ };
+
+ VirtualMachine.prototype.stop = function () {
+ this.runtime.stop();
+ };
+
+ VirtualMachine.prototype.save = function () {
+ // @todo Serialize runtime state
+ };
+
+ VirtualMachine.prototype.load = function () {
+ // @todo Deserialize and apply runtime state
+ };
+
+ /**
+ * Export and bind to `window`
+ */
+ module.exports = VirtualMachine;
+ if (typeof window !== 'undefined') window.VirtualMachine = module.exports;
+
+
+/***/ },
+/* 1 */
+/***/ function(module, exports) {
+
+ // Copyright Joyent, Inc. and other Node contributors.
+ //
+ // 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.
+
+ function EventEmitter() {
+ this._events = this._events || {};
+ this._maxListeners = this._maxListeners || undefined;
+ }
+ module.exports = EventEmitter;
+
+ // Backwards-compat with node 0.10.x
+ EventEmitter.EventEmitter = EventEmitter;
+
+ EventEmitter.prototype._events = undefined;
+ EventEmitter.prototype._maxListeners = undefined;
+
+ // By default EventEmitters will print a warning if more than 10 listeners are
+ // added to it. This is a useful default which helps finding memory leaks.
+ EventEmitter.defaultMaxListeners = 10;
+
+ // Obviously not all Emitters should be limited to 10. This function allows
+ // that to be increased. Set to zero for unlimited.
+ EventEmitter.prototype.setMaxListeners = function(n) {
+ if (!isNumber(n) || n < 0 || isNaN(n))
+ throw TypeError('n must be a positive number');
+ this._maxListeners = n;
+ return this;
+ };
+
+ EventEmitter.prototype.emit = function(type) {
+ var er, handler, len, args, i, listeners;
+
+ if (!this._events)
+ this._events = {};
+
+ // If there is no 'error' event listener then throw.
+ if (type === 'error') {
+ if (!this._events.error ||
+ (isObject(this._events.error) && !this._events.error.length)) {
+ er = arguments[1];
+ if (er instanceof Error) {
+ throw er; // Unhandled 'error' event
+ }
+ throw TypeError('Uncaught, unspecified "error" event.');
+ }
+ }
+
+ handler = this._events[type];
+
+ if (isUndefined(handler))
+ return false;
+
+ if (isFunction(handler)) {
+ switch (arguments.length) {
+ // fast cases
+ case 1:
+ handler.call(this);
+ break;
+ case 2:
+ handler.call(this, arguments[1]);
+ break;
+ case 3:
+ handler.call(this, arguments[1], arguments[2]);
+ break;
+ // slower
+ default:
+ args = Array.prototype.slice.call(arguments, 1);
+ handler.apply(this, args);
+ }
+ } else if (isObject(handler)) {
+ args = Array.prototype.slice.call(arguments, 1);
+ listeners = handler.slice();
+ len = listeners.length;
+ for (i = 0; i < len; i++)
+ listeners[i].apply(this, args);
+ }
+
+ return true;
+ };
+
+ EventEmitter.prototype.addListener = function(type, listener) {
+ var m;
+
+ if (!isFunction(listener))
+ throw TypeError('listener must be a function');
+
+ if (!this._events)
+ this._events = {};
+
+ // To avoid recursion in the case that type === "newListener"! Before
+ // adding it to the listeners, first emit "newListener".
+ if (this._events.newListener)
+ this.emit('newListener', type,
+ isFunction(listener.listener) ?
+ listener.listener : listener);
+
+ if (!this._events[type])
+ // Optimize the case of one listener. Don't need the extra array object.
+ this._events[type] = listener;
+ else if (isObject(this._events[type]))
+ // If we've already got an array, just append.
+ this._events[type].push(listener);
+ else
+ // Adding the second element, need to change to array.
+ this._events[type] = [this._events[type], listener];
+
+ // Check for listener leak
+ if (isObject(this._events[type]) && !this._events[type].warned) {
+ if (!isUndefined(this._maxListeners)) {
+ m = this._maxListeners;
+ } else {
+ m = EventEmitter.defaultMaxListeners;
+ }
+
+ if (m && m > 0 && this._events[type].length > m) {
+ this._events[type].warned = true;
+ console.error('(node) warning: possible EventEmitter memory ' +
+ 'leak detected. %d listeners added. ' +
+ 'Use emitter.setMaxListeners() to increase limit.',
+ this._events[type].length);
+ if (typeof console.trace === 'function') {
+ // not supported in IE 10
+ console.trace();
+ }
+ }
+ }
+
+ return this;
+ };
+
+ EventEmitter.prototype.on = EventEmitter.prototype.addListener;
+
+ EventEmitter.prototype.once = function(type, listener) {
+ if (!isFunction(listener))
+ throw TypeError('listener must be a function');
+
+ var fired = false;
+
+ function g() {
+ this.removeListener(type, g);
+
+ if (!fired) {
+ fired = true;
+ listener.apply(this, arguments);
+ }
+ }
+
+ g.listener = listener;
+ this.on(type, g);
+
+ return this;
+ };
+
+ // emits a 'removeListener' event iff the listener was removed
+ EventEmitter.prototype.removeListener = function(type, listener) {
+ var list, position, length, i;
+
+ if (!isFunction(listener))
+ throw TypeError('listener must be a function');
+
+ if (!this._events || !this._events[type])
+ return this;
+
+ list = this._events[type];
+ length = list.length;
+ position = -1;
+
+ if (list === listener ||
+ (isFunction(list.listener) && list.listener === listener)) {
+ delete this._events[type];
+ if (this._events.removeListener)
+ this.emit('removeListener', type, listener);
+
+ } else if (isObject(list)) {
+ for (i = length; i-- > 0;) {
+ if (list[i] === listener ||
+ (list[i].listener && list[i].listener === listener)) {
+ position = i;
+ break;
+ }
+ }
+
+ if (position < 0)
+ return this;
+
+ if (list.length === 1) {
+ list.length = 0;
+ delete this._events[type];
+ } else {
+ list.splice(position, 1);
+ }
+
+ if (this._events.removeListener)
+ this.emit('removeListener', type, listener);
+ }
+
+ return this;
+ };
+
+ EventEmitter.prototype.removeAllListeners = function(type) {
+ var key, listeners;
+
+ if (!this._events)
+ return this;
+
+ // not listening for removeListener, no need to emit
+ if (!this._events.removeListener) {
+ if (arguments.length === 0)
+ this._events = {};
+ else if (this._events[type])
+ delete this._events[type];
+ return this;
+ }
+
+ // emit removeListener for all listeners on all events
+ if (arguments.length === 0) {
+ for (key in this._events) {
+ if (key === 'removeListener') continue;
+ this.removeAllListeners(key);
+ }
+ this.removeAllListeners('removeListener');
+ this._events = {};
+ return this;
+ }
+
+ listeners = this._events[type];
+
+ if (isFunction(listeners)) {
+ this.removeListener(type, listeners);
+ } else if (listeners) {
+ // LIFO order
+ while (listeners.length)
+ this.removeListener(type, listeners[listeners.length - 1]);
+ }
+ delete this._events[type];
+
+ return this;
+ };
+
+ EventEmitter.prototype.listeners = function(type) {
+ var ret;
+ if (!this._events || !this._events[type])
+ ret = [];
+ else if (isFunction(this._events[type]))
+ ret = [this._events[type]];
+ else
+ ret = this._events[type].slice();
+ return ret;
+ };
+
+ EventEmitter.prototype.listenerCount = function(type) {
+ if (this._events) {
+ var evlistener = this._events[type];
+
+ if (isFunction(evlistener))
+ return 1;
+ else if (evlistener)
+ return evlistener.length;
+ }
+ return 0;
+ };
+
+ EventEmitter.listenerCount = function(emitter, type) {
+ return emitter.listenerCount(type);
+ };
+
+ function isFunction(arg) {
+ return typeof arg === 'function';
+ }
+
+ function isNumber(arg) {
+ return typeof arg === 'number';
+ }
+
+ function isObject(arg) {
+ return typeof arg === 'object' && arg !== null;
+ }
+
+ function isUndefined(arg) {
+ return arg === void 0;
+ }
+
+
+/***/ },
+/* 2 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* WEBPACK VAR INJECTION */(function(global, process) {// Copyright Joyent, Inc. and other Node contributors.
+ //
+ // 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.
+
+ var formatRegExp = /%[sdj%]/g;
+ exports.format = function(f) {
+ if (!isString(f)) {
+ var objects = [];
+ for (var i = 0; i < arguments.length; i++) {
+ objects.push(inspect(arguments[i]));
+ }
+ return objects.join(' ');
+ }
+
+ var i = 1;
+ var args = arguments;
+ var len = args.length;
+ var str = String(f).replace(formatRegExp, function(x) {
+ if (x === '%%') return '%';
+ if (i >= len) return x;
+ switch (x) {
+ case '%s': return String(args[i++]);
+ case '%d': return Number(args[i++]);
+ case '%j':
+ try {
+ return JSON.stringify(args[i++]);
+ } catch (_) {
+ return '[Circular]';
+ }
+ default:
+ return x;
+ }
+ });
+ for (var x = args[i]; i < len; x = args[++i]) {
+ if (isNull(x) || !isObject(x)) {
+ str += ' ' + x;
+ } else {
+ str += ' ' + inspect(x);
+ }
+ }
+ return str;
+ };
+
+
+ // Mark that a method should not be used.
+ // Returns a modified function which warns once by default.
+ // If --no-deprecation is set, then it is a no-op.
+ exports.deprecate = function(fn, msg) {
+ // Allow for deprecating things in the process of starting up.
+ if (isUndefined(global.process)) {
+ return function() {
+ return exports.deprecate(fn, msg).apply(this, arguments);
+ };
+ }
+
+ if (process.noDeprecation === true) {
+ return fn;
+ }
+
+ var warned = false;
+ function deprecated() {
+ if (!warned) {
+ if (process.throwDeprecation) {
+ throw new Error(msg);
+ } else if (process.traceDeprecation) {
+ console.trace(msg);
+ } else {
+ console.error(msg);
+ }
+ warned = true;
+ }
+ return fn.apply(this, arguments);
+ }
+
+ return deprecated;
+ };
+
+
+ var debugs = {};
+ var debugEnviron;
+ exports.debuglog = function(set) {
+ if (isUndefined(debugEnviron))
+ debugEnviron = process.env.NODE_DEBUG || '';
+ set = set.toUpperCase();
+ if (!debugs[set]) {
+ if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) {
+ var pid = process.pid;
+ debugs[set] = function() {
+ var msg = exports.format.apply(exports, arguments);
+ console.error('%s %d: %s', set, pid, msg);
+ };
+ } else {
+ debugs[set] = function() {};
+ }
+ }
+ return debugs[set];
+ };
+
+
+ /**
+ * Echos the value of a value. Trys to print the value out
+ * in the best way possible given the different types.
+ *
+ * @param {Object} obj The object to print out.
+ * @param {Object} opts Optional options object that alters the output.
+ */
+ /* legacy: obj, showHidden, depth, colors*/
+ function inspect(obj, opts) {
+ // default options
+ var ctx = {
+ seen: [],
+ stylize: stylizeNoColor
+ };
+ // legacy...
+ if (arguments.length >= 3) ctx.depth = arguments[2];
+ if (arguments.length >= 4) ctx.colors = arguments[3];
+ if (isBoolean(opts)) {
+ // legacy...
+ ctx.showHidden = opts;
+ } else if (opts) {
+ // got an "options" object
+ exports._extend(ctx, opts);
+ }
+ // set default options
+ if (isUndefined(ctx.showHidden)) ctx.showHidden = false;
+ if (isUndefined(ctx.depth)) ctx.depth = 2;
+ if (isUndefined(ctx.colors)) ctx.colors = false;
+ if (isUndefined(ctx.customInspect)) ctx.customInspect = true;
+ if (ctx.colors) ctx.stylize = stylizeWithColor;
+ return formatValue(ctx, obj, ctx.depth);
+ }
+ exports.inspect = inspect;
+
+
+ // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
+ inspect.colors = {
+ 'bold' : [1, 22],
+ 'italic' : [3, 23],
+ 'underline' : [4, 24],
+ 'inverse' : [7, 27],
+ 'white' : [37, 39],
+ 'grey' : [90, 39],
+ 'black' : [30, 39],
+ 'blue' : [34, 39],
+ 'cyan' : [36, 39],
+ 'green' : [32, 39],
+ 'magenta' : [35, 39],
+ 'red' : [31, 39],
+ 'yellow' : [33, 39]
+ };
+
+ // Don't use 'blue' not visible on cmd.exe
+ inspect.styles = {
+ 'special': 'cyan',
+ 'number': 'yellow',
+ 'boolean': 'yellow',
+ 'undefined': 'grey',
+ 'null': 'bold',
+ 'string': 'green',
+ 'date': 'magenta',
+ // "name": intentionally not styling
+ 'regexp': 'red'
+ };
+
+
+ function stylizeWithColor(str, styleType) {
+ var style = inspect.styles[styleType];
+
+ if (style) {
+ return '\u001b[' + inspect.colors[style][0] + 'm' + str +
+ '\u001b[' + inspect.colors[style][1] + 'm';
+ } else {
+ return str;
+ }
+ }
+
+
+ function stylizeNoColor(str, styleType) {
+ return str;
+ }
+
+
+ function arrayToHash(array) {
+ var hash = {};
+
+ array.forEach(function(val, idx) {
+ hash[val] = true;
+ });
+
+ return hash;
+ }
+
+
+ function formatValue(ctx, value, recurseTimes) {
+ // Provide a hook for user-specified inspect functions.
+ // Check that value is an object with an inspect function on it
+ if (ctx.customInspect &&
+ value &&
+ isFunction(value.inspect) &&
+ // Filter out the util module, it's inspect function is special
+ value.inspect !== exports.inspect &&
+ // Also filter out any prototype objects using the circular check.
+ !(value.constructor && value.constructor.prototype === value)) {
+ var ret = value.inspect(recurseTimes, ctx);
+ if (!isString(ret)) {
+ ret = formatValue(ctx, ret, recurseTimes);
+ }
+ return ret;
+ }
+
+ // Primitive types cannot have properties
+ var primitive = formatPrimitive(ctx, value);
+ if (primitive) {
+ return primitive;
+ }
+
+ // Look up the keys of the object.
+ var keys = Object.keys(value);
+ var visibleKeys = arrayToHash(keys);
+
+ if (ctx.showHidden) {
+ keys = Object.getOwnPropertyNames(value);
+ }
+
+ // IE doesn't make error fields non-enumerable
+ // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx
+ if (isError(value)
+ && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) {
+ return formatError(value);
+ }
+
+ // Some type of object without properties can be shortcutted.
+ if (keys.length === 0) {
+ if (isFunction(value)) {
+ var name = value.name ? ': ' + value.name : '';
+ return ctx.stylize('[Function' + name + ']', 'special');
+ }
+ if (isRegExp(value)) {
+ return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
+ }
+ if (isDate(value)) {
+ return ctx.stylize(Date.prototype.toString.call(value), 'date');
+ }
+ if (isError(value)) {
+ return formatError(value);
+ }
+ }
+
+ var base = '', array = false, braces = ['{', '}'];
+
+ // Make Array say that they are Array
+ if (isArray(value)) {
+ array = true;
+ braces = ['[', ']'];
+ }
+
+ // Make functions say that they are functions
+ if (isFunction(value)) {
+ var n = value.name ? ': ' + value.name : '';
+ base = ' [Function' + n + ']';
+ }
+
+ // Make RegExps say that they are RegExps
+ if (isRegExp(value)) {
+ base = ' ' + RegExp.prototype.toString.call(value);
+ }
+
+ // Make dates with properties first say the date
+ if (isDate(value)) {
+ base = ' ' + Date.prototype.toUTCString.call(value);
+ }
+
+ // Make error with message first say the error
+ if (isError(value)) {
+ base = ' ' + formatError(value);
+ }
+
+ if (keys.length === 0 && (!array || value.length == 0)) {
+ return braces[0] + base + braces[1];
+ }
+
+ if (recurseTimes < 0) {
+ if (isRegExp(value)) {
+ return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
+ } else {
+ return ctx.stylize('[Object]', 'special');
+ }
+ }
+
+ ctx.seen.push(value);
+
+ var output;
+ if (array) {
+ output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
+ } else {
+ output = keys.map(function(key) {
+ return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
+ });
+ }
+
+ ctx.seen.pop();
+
+ return reduceToSingleString(output, base, braces);
+ }
+
+
+ function formatPrimitive(ctx, value) {
+ if (isUndefined(value))
+ return ctx.stylize('undefined', 'undefined');
+ if (isString(value)) {
+ var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
+ .replace(/'/g, "\\'")
+ .replace(/\\"/g, '"') + '\'';
+ return ctx.stylize(simple, 'string');
+ }
+ if (isNumber(value))
+ return ctx.stylize('' + value, 'number');
+ if (isBoolean(value))
+ return ctx.stylize('' + value, 'boolean');
+ // For some reason typeof null is "object", so special case here.
+ if (isNull(value))
+ return ctx.stylize('null', 'null');
+ }
+
+
+ function formatError(value) {
+ return '[' + Error.prototype.toString.call(value) + ']';
+ }
+
+
+ function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
+ var output = [];
+ for (var i = 0, l = value.length; i < l; ++i) {
+ if (hasOwnProperty(value, String(i))) {
+ output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
+ String(i), true));
+ } else {
+ output.push('');
+ }
+ }
+ keys.forEach(function(key) {
+ if (!key.match(/^\d+$/)) {
+ output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
+ key, true));
+ }
+ });
+ return output;
+ }
+
+
+ function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
+ var name, str, desc;
+ desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] };
+ if (desc.get) {
+ if (desc.set) {
+ str = ctx.stylize('[Getter/Setter]', 'special');
+ } else {
+ str = ctx.stylize('[Getter]', 'special');
+ }
+ } else {
+ if (desc.set) {
+ str = ctx.stylize('[Setter]', 'special');
+ }
+ }
+ if (!hasOwnProperty(visibleKeys, key)) {
+ name = '[' + key + ']';
+ }
+ if (!str) {
+ if (ctx.seen.indexOf(desc.value) < 0) {
+ if (isNull(recurseTimes)) {
+ str = formatValue(ctx, desc.value, null);
+ } else {
+ str = formatValue(ctx, desc.value, recurseTimes - 1);
+ }
+ if (str.indexOf('\n') > -1) {
+ if (array) {
+ str = str.split('\n').map(function(line) {
+ return ' ' + line;
+ }).join('\n').substr(2);
+ } else {
+ str = '\n' + str.split('\n').map(function(line) {
+ return ' ' + line;
+ }).join('\n');
+ }
+ }
+ } else {
+ str = ctx.stylize('[Circular]', 'special');
+ }
+ }
+ if (isUndefined(name)) {
+ if (array && key.match(/^\d+$/)) {
+ return str;
+ }
+ name = JSON.stringify('' + key);
+ if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
+ name = name.substr(1, name.length - 2);
+ name = ctx.stylize(name, 'name');
+ } else {
+ name = name.replace(/'/g, "\\'")
+ .replace(/\\"/g, '"')
+ .replace(/(^"|"$)/g, "'");
+ name = ctx.stylize(name, 'string');
+ }
+ }
+
+ return name + ': ' + str;
+ }
+
+
+ function reduceToSingleString(output, base, braces) {
+ var numLinesEst = 0;
+ var length = output.reduce(function(prev, cur) {
+ numLinesEst++;
+ if (cur.indexOf('\n') >= 0) numLinesEst++;
+ return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1;
+ }, 0);
+
+ if (length > 60) {
+ return braces[0] +
+ (base === '' ? '' : base + '\n ') +
+ ' ' +
+ output.join(',\n ') +
+ ' ' +
+ braces[1];
+ }
+
+ return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
+ }
+
+
+ // NOTE: These type checking functions intentionally don't use `instanceof`
+ // because it is fragile and can be easily faked with `Object.create()`.
+ function isArray(ar) {
+ return Array.isArray(ar);
+ }
+ exports.isArray = isArray;
+
+ function isBoolean(arg) {
+ return typeof arg === 'boolean';
+ }
+ exports.isBoolean = isBoolean;
+
+ function isNull(arg) {
+ return arg === null;
+ }
+ exports.isNull = isNull;
+
+ function isNullOrUndefined(arg) {
+ return arg == null;
+ }
+ exports.isNullOrUndefined = isNullOrUndefined;
+
+ function isNumber(arg) {
+ return typeof arg === 'number';
+ }
+ exports.isNumber = isNumber;
+
+ function isString(arg) {
+ return typeof arg === 'string';
+ }
+ exports.isString = isString;
+
+ function isSymbol(arg) {
+ return typeof arg === 'symbol';
+ }
+ exports.isSymbol = isSymbol;
+
+ function isUndefined(arg) {
+ return arg === void 0;
+ }
+ exports.isUndefined = isUndefined;
+
+ function isRegExp(re) {
+ return isObject(re) && objectToString(re) === '[object RegExp]';
+ }
+ exports.isRegExp = isRegExp;
+
+ function isObject(arg) {
+ return typeof arg === 'object' && arg !== null;
+ }
+ exports.isObject = isObject;
+
+ function isDate(d) {
+ return isObject(d) && objectToString(d) === '[object Date]';
+ }
+ exports.isDate = isDate;
+
+ function isError(e) {
+ return isObject(e) &&
+ (objectToString(e) === '[object Error]' || e instanceof Error);
+ }
+ exports.isError = isError;
+
+ function isFunction(arg) {
+ return typeof arg === 'function';
+ }
+ exports.isFunction = isFunction;
+
+ function isPrimitive(arg) {
+ return arg === null ||
+ typeof arg === 'boolean' ||
+ typeof arg === 'number' ||
+ typeof arg === 'string' ||
+ typeof arg === 'symbol' || // ES6 symbol
+ typeof arg === 'undefined';
+ }
+ exports.isPrimitive = isPrimitive;
+
+ exports.isBuffer = __webpack_require__(4);
+
+ function objectToString(o) {
+ return Object.prototype.toString.call(o);
+ }
+
+
+ function pad(n) {
+ return n < 10 ? '0' + n.toString(10) : n.toString(10);
+ }
+
+
+ var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
+ 'Oct', 'Nov', 'Dec'];
+
+ // 26 Feb 16:19:34
+ function timestamp() {
+ var d = new Date();
+ var time = [pad(d.getHours()),
+ pad(d.getMinutes()),
+ pad(d.getSeconds())].join(':');
+ return [d.getDate(), months[d.getMonth()], time].join(' ');
+ }
+
+
+ // log is just a thin wrapper to console.log that prepends a timestamp
+ exports.log = function() {
+ console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments));
+ };
+
+
+ /**
+ * Inherit the prototype methods from one constructor into another.
+ *
+ * The Function.prototype.inherits from lang.js rewritten as a standalone
+ * function (not on Function.prototype). NOTE: If this file is to be loaded
+ * during bootstrapping this function needs to be rewritten using some native
+ * functions as prototype setup using normal JavaScript does not work as
+ * expected during bootstrapping (see mirror.js in r114903).
+ *
+ * @param {function} ctor Constructor function which needs to inherit the
+ * prototype.
+ * @param {function} superCtor Constructor function to inherit prototype from.
+ */
+ exports.inherits = __webpack_require__(5);
+
+ exports._extend = function(origin, add) {
+ // Don't do anything if add isn't an object
+ if (!add || !isObject(add)) return origin;
+
+ var keys = Object.keys(add);
+ var i = keys.length;
+ while (i--) {
+ origin[keys[i]] = add[keys[i]];
+ }
+ return origin;
+ };
+
+ function hasOwnProperty(obj, prop) {
+ return Object.prototype.hasOwnProperty.call(obj, prop);
+ }
+
+ /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()), __webpack_require__(3)))
+
+/***/ },
+/* 3 */
+/***/ function(module, exports) {
+
+ // shim for using process in browser
+
+ var process = module.exports = {};
+ var queue = [];
+ var draining = false;
+ var currentQueue;
+ var queueIndex = -1;
+
+ function cleanUpNextTick() {
+ draining = false;
+ if (currentQueue.length) {
+ queue = currentQueue.concat(queue);
+ } else {
+ queueIndex = -1;
+ }
+ if (queue.length) {
+ drainQueue();
+ }
+ }
+
+ function drainQueue() {
+ if (draining) {
+ return;
+ }
+ var timeout = setTimeout(cleanUpNextTick);
+ draining = true;
+
+ var len = queue.length;
+ while(len) {
+ currentQueue = queue;
+ queue = [];
+ while (++queueIndex < len) {
+ if (currentQueue) {
+ currentQueue[queueIndex].run();
+ }
+ }
+ queueIndex = -1;
+ len = queue.length;
+ }
+ currentQueue = null;
+ draining = false;
+ clearTimeout(timeout);
+ }
+
+ process.nextTick = function (fun) {
+ var args = new Array(arguments.length - 1);
+ if (arguments.length > 1) {
+ for (var i = 1; i < arguments.length; i++) {
+ args[i - 1] = arguments[i];
+ }
+ }
+ queue.push(new Item(fun, args));
+ if (queue.length === 1 && !draining) {
+ setTimeout(drainQueue, 0);
+ }
+ };
+
+ // v8 likes predictible objects
+ function Item(fun, array) {
+ this.fun = fun;
+ this.array = array;
+ }
+ Item.prototype.run = function () {
+ this.fun.apply(null, this.array);
+ };
+ process.title = 'browser';
+ process.browser = true;
+ process.env = {};
+ process.argv = [];
+ process.version = ''; // empty string to avoid regexp issues
+ process.versions = {};
+
+ function noop() {}
+
+ process.on = noop;
+ process.addListener = noop;
+ process.once = noop;
+ process.off = noop;
+ process.removeListener = noop;
+ process.removeAllListeners = noop;
+ process.emit = noop;
+
+ process.binding = function (name) {
+ throw new Error('process.binding is not supported');
+ };
+
+ process.cwd = function () { return '/' };
+ process.chdir = function (dir) {
+ throw new Error('process.chdir is not supported');
+ };
+ process.umask = function() { return 0; };
+
+
+/***/ },
+/* 4 */
+/***/ function(module, exports) {
+
+ module.exports = function isBuffer(arg) {
+ return arg && typeof arg === 'object'
+ && typeof arg.copy === 'function'
+ && typeof arg.fill === 'function'
+ && typeof arg.readUInt8 === 'function';
+ }
+
+/***/ },
+/* 5 */
+/***/ function(module, exports) {
+
+ if (typeof Object.create === 'function') {
+ // implementation from standard node.js 'util' module
+ module.exports = function inherits(ctor, superCtor) {
+ ctor.super_ = superCtor
+ ctor.prototype = Object.create(superCtor.prototype, {
+ constructor: {
+ value: ctor,
+ enumerable: false,
+ writable: true,
+ configurable: true
+ }
+ });
+ };
+ } else {
+ // old school shim for old browsers
+ module.exports = function inherits(ctor, superCtor) {
+ ctor.super_ = superCtor
+ var TempCtor = function () {}
+ TempCtor.prototype = superCtor.prototype
+ ctor.prototype = new TempCtor()
+ ctor.prototype.constructor = ctor
+ }
+ }
+
+
+/***/ },
+/* 6 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var Primitives = __webpack_require__(10);
+ var Sequencer = __webpack_require__(7);
+ var Thread = __webpack_require__(9);
+
+ var STEP_THREADS_INTERVAL = 1000 / 30;
+
+ /**
+ * A simple runtime for blocks.
+ */
+ function Runtime () {
+ this.sequencer = new Sequencer(this);
+ this.primitives = new Primitives();
+
+ // State
+ this.blocks = {};
+ this.stacks = [];
+ }
+
+ Runtime.prototype.createBlock = function (e) {
+ // Create new block
+ this.blocks[e.id] = {
+ id: e.id,
+ opcode: e.opcode,
+ next: null,
+ inputs: {}
+ };
+
+ // Push block id to stacks array. New blocks are always a stack even if only
+ // momentary. If the new block is added to an existing stack this stack will
+ // be removed by the `moveBlock` method below.
+ this.stacks.push(e.id);
+ };
+
+ Runtime.prototype.moveBlock = function (e) {
+ var _this = this;
+
+ // Block has a new parent
+ if (e.oldParent === undefined && e.newParent !== undefined) {
+ // Remove stack
+ _this._deleteStack(e.id);
+
+ // Update new parent
+ if (e.newInput === undefined) {
+ _this.blocks[e.newParent].next = e.id;
+ } else {
+ _this.blocks[e.newParent].inputs[e.newInput] = e.id;
+ }
+ }
+
+ // Block was removed from parent
+ if (e.newParentId === undefined && e.oldParent !== undefined) {
+ // Add stack
+ _this.stacks.push(e.id);
+
+ // Update old parent
+ if (e.oldInput === undefined) {
+ _this.blocks[e.oldParent].next = null;
+ } else {
+ delete _this.blocks[e.oldParent].inputs[e.oldInput];
+ }
+ }
+ };
+
+ Runtime.prototype.changeBlock = function (e) {
+ // @todo
+ };
+
+ Runtime.prototype.deleteBlock = function (e) {
+ // @todo Stop threads running on this stack
+
+ // Delete children
+ var block = this.blocks[e.id];
+ if (block.next !== null) {
+ this.deleteBlock({id: block.next});
+ }
+
+ // Delete inputs
+ for (var i in block.inputs) {
+ this.deleteBlock({id: block.inputs[i]});
+ }
+
+ // Delete stack
+ this._deleteStack(e.id);
+
+ // Delete block
+ delete this.blocks[e.id];
+ };
+
+ Runtime.prototype.runAllStacks = function () {
+ // @todo
+ };
+
+ Runtime.prototype.runStack = function () {
+ // @todo
+ };
+
+ Runtime.prototype.stopAllStacks = function () {
+ // @todo
+ };
+
+ // -----------------------------------------------------------------------------
+ // -----------------------------------------------------------------------------
+
+ Runtime.prototype._deleteStack = function (id) {
+ var i = this.stacks.indexOf(id);
+ if (i > -1) this.stacks.splice(i, 1);
+ };
+
+ Runtime.prototype._getNextBlock = function (id) {
+ if (typeof this.blocks[id] === 'undefined') return null;
+ return this.blocks[id].next;
+ };
+
+ Runtime.prototype._getSubstack = function (id) {
+ if (typeof this.blocks[id] === 'undefined') return null;
+ return this.blocks[id].inputs['SUBSTACK'];
+ };
+
+ module.exports = Runtime;
+
+
+/***/ },
+/* 7 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var Timer = __webpack_require__(8);
+
+ /**
+ * Constructor
+ */
+ function Sequencer (runtime) {
+ // Bi-directional binding for runtime
+ this.runtime = runtime;
+
+ // State
+ this.runningThreads = [];
+ this.workTime = 30;
+ this.timer = new Timer();
+ this.currentTime = 0;
+ }
+
+ Sequencer.prototype.stepAllThreads = function () {
+
+ };
+
+ Sequencer.prototype.stepThread = function (thread) {
+
+ };
+
+ Sequencer.prototype.startSubstack = function (thread) {
+
+ };
+
+ module.exports = Sequencer;
+
+
+/***/ },
+/* 8 */
+/***/ function(module, exports) {
+
+ /**
+ * Constructor
+ * @todo Swap out Date.now() with microtime module that works in node & browsers
+ */
+ function Timer () {
+ this.startTime = 0;
+ }
+
+ Timer.prototype.time = function () {
+ return Date.now();
+ };
+
+ Timer.prototype.start = function () {
+ this.startTime = this.time();
+ };
+
+ Timer.prototype.stop = function () {
+ return this.startTime - this.time();
+ };
+
+ module.exports = Timer;
+
+
+/***/ },
+/* 9 */
+/***/ function(module, exports) {
+
+ /**
+ * Thread is an internal data structure used by the interpreter. It holds the
+ * state of a thread so it can continue from where it left off, and it has
+ * a stack to support nested control structures and procedure calls.
+ *
+ * @param {String} Unique block identifier
+ */
+ function Thread (id) {
+ this.topBlockId = id;
+ this.blockPointer = id;
+ this.blockFirstTime = -1;
+ this.nextBlock = null;
+ this.waiting = null;
+ this.runningDeviceBlock = false;
+ this.stack = [];
+ this.repeatCounter = -1;
+ }
+
+ module.exports = Thread;
+
+
+/***/ },
+/* 10 */
+/***/ function(module, exports) {
+
+ function Primitives () {
+
+ }
+
+ Primitives.prototype.event_whenflagclicked = function (thread, runtime) {
+ // No-op: flags are started by the interpreter but don't do any action
+ // Take 1/3 second to show running state
+ if (Date.now() - thread.blockFirstTime < 300) {
+ thread.yield = true;
+ return;
+ }
+ };
+
+ Primitives.prototype.control_repeat = function (thread, runtime) {
+ // Take 1/3 second to show running state
+ if (Date.now() - thread.blockFirstTime < 300) {
+ thread.yield = true;
+ return;
+ }
+ if (thread.repeatCounter == -1) {
+ thread.repeatCounter = 10; // @todo from the arg
+ }
+ if (thread.repeatCounter > 0) {
+ thread.repeatCounter -= 1;
+ runtime.interpreter.startSubstack(thread);
+ } else {
+ thread.repeatCounter = -1;
+ thread.nextBlock = runtime.getNextBlock(thread.blockPointer);
+ }
+ };
+
+ Primitives.prototype.control_forever = function (thread, runtime) {
+ // Take 1/3 second to show running state
+ if (Date.now() - thread.blockFirstTime < 300) {
+ thread.yield = true;
+ return;
+ }
+ runtime.interpreter.startSubstack(thread);
+ };
+
+ module.exports = Primitives;
+
+
+/***/ }
+/******/ ]);
\ No newline at end of file
diff --git a/vm.min.js b/vm.min.js
new file mode 100644
index 000000000..033dd0718
--- /dev/null
+++ b/vm.min.js
@@ -0,0 +1 @@
+!function(t){function e(r){if(n[r])return n[r].exports;var i=n[r]={exports:{},id:r,loaded:!1};return t[r].call(i.exports,i,i.exports,e),i.loaded=!0,i.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){function r(){var t=this;i.call(t),t.runtime=new s,t.blockListener=function(e){if("object"==typeof e&&"string"==typeof e.blockId)switch(e.type){case"create":t.runtime.createBlock({id:e.blockId,opcode:e.xml.attributes.type.value});break;case"move":t.runtime.moveBlock({id:e.blockId,oldParent:e.oldParentId,oldInput:e.oldInputName,newParent:e.newParentId,newInput:e.newInputName});break;case"change":t.runtime.changeBlock({id:e.blockId});break;case"delete":t.runtime.deleteBlock({id:e.blockId})}}}var i=n(1),o=n(2),s=n(6);o.inherits(r,i),r.prototype.start=function(){this.runtime.runAllGreenFlags()},r.prototype.stop=function(){this.runtime.stop()},r.prototype.save=function(){},r.prototype.load=function(){},t.exports=r,"undefined"!=typeof window&&(window.VirtualMachine=t.exports)},function(t,e){function n(){this._events=this._events||{},this._maxListeners=this._maxListeners||void 0}function r(t){return"function"==typeof t}function i(t){return"number"==typeof t}function o(t){return"object"==typeof t&&null!==t}function s(t){return void 0===t}t.exports=n,n.EventEmitter=n,n.prototype._events=void 0,n.prototype._maxListeners=void 0,n.defaultMaxListeners=10,n.prototype.setMaxListeners=function(t){if(!i(t)||0>t||isNaN(t))throw TypeError("n must be a positive number");return this._maxListeners=t,this},n.prototype.emit=function(t){var e,n,i,u,c,l;if(this._events||(this._events={}),"error"===t&&(!this._events.error||o(this._events.error)&&!this._events.error.length)){if(e=arguments[1],e instanceof Error)throw e;throw TypeError('Uncaught, unspecified "error" event.')}if(n=this._events[t],s(n))return!1;if(r(n))switch(arguments.length){case 1:n.call(this);break;case 2:n.call(this,arguments[1]);break;case 3:n.call(this,arguments[1],arguments[2]);break;default:u=Array.prototype.slice.call(arguments,1),n.apply(this,u)}else if(o(n))for(u=Array.prototype.slice.call(arguments,1),l=n.slice(),i=l.length,c=0;i>c;c++)l[c].apply(this,u);return!0},n.prototype.addListener=function(t,e){var i;if(!r(e))throw TypeError("listener must be a function");return this._events||(this._events={}),this._events.newListener&&this.emit("newListener",t,r(e.listener)?e.listener:e),this._events[t]?o(this._events[t])?this._events[t].push(e):this._events[t]=[this._events[t],e]:this._events[t]=e,o(this._events[t])&&!this._events[t].warned&&(i=s(this._maxListeners)?n.defaultMaxListeners:this._maxListeners,i&&i>0&&this._events[t].length>i&&(this._events[t].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[t].length),"function"==typeof console.trace&&console.trace())),this},n.prototype.on=n.prototype.addListener,n.prototype.once=function(t,e){function n(){this.removeListener(t,n),i||(i=!0,e.apply(this,arguments))}if(!r(e))throw TypeError("listener must be a function");var i=!1;return n.listener=e,this.on(t,n),this},n.prototype.removeListener=function(t,e){var n,i,s,u;if(!r(e))throw TypeError("listener must be a function");if(!this._events||!this._events[t])return this;if(n=this._events[t],s=n.length,i=-1,n===e||r(n.listener)&&n.listener===e)delete this._events[t],this._events.removeListener&&this.emit("removeListener",t,e);else if(o(n)){for(u=s;u-- >0;)if(n[u]===e||n[u].listener&&n[u].listener===e){i=u;break}if(0>i)return this;1===n.length?(n.length=0,delete this._events[t]):n.splice(i,1),this._events.removeListener&&this.emit("removeListener",t,e)}return this},n.prototype.removeAllListeners=function(t){var e,n;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[t]&&delete this._events[t],this;if(0===arguments.length){for(e in this._events)"removeListener"!==e&&this.removeAllListeners(e);return this.removeAllListeners("removeListener"),this._events={},this}if(n=this._events[t],r(n))this.removeListener(t,n);else if(n)for(;n.length;)this.removeListener(t,n[n.length-1]);return delete this._events[t],this},n.prototype.listeners=function(t){var e;return e=this._events&&this._events[t]?r(this._events[t])?[this._events[t]]:this._events[t].slice():[]},n.prototype.listenerCount=function(t){if(this._events){var e=this._events[t];if(r(e))return 1;if(e)return e.length}return 0},n.listenerCount=function(t,e){return t.listenerCount(e)}},function(t,e,n){(function(t,r){function i(t,n){var r={seen:[],stylize:s};return arguments.length>=3&&(r.depth=arguments[2]),arguments.length>=4&&(r.colors=arguments[3]),v(n)?r.showHidden=n:n&&e._extend(r,n),_(r.showHidden)&&(r.showHidden=!1),_(r.depth)&&(r.depth=2),_(r.colors)&&(r.colors=!1),_(r.customInspect)&&(r.customInspect=!0),r.colors&&(r.stylize=o),c(r,t,r.depth)}function o(t,e){var n=i.styles[e];return n?"["+i.colors[n][0]+"m"+t+"["+i.colors[n][1]+"m":t}function s(t,e){return t}function u(t){var e={};return t.forEach(function(t,n){e[t]=!0}),e}function c(t,n,r){if(t.customInspect&&n&&E(n.inspect)&&n.inspect!==e.inspect&&(!n.constructor||n.constructor.prototype!==n)){var i=n.inspect(r,t);return b(i)||(i=c(t,i,r)),i}var o=l(t,n);if(o)return o;var s=Object.keys(n),v=u(s);if(t.showHidden&&(s=Object.getOwnPropertyNames(n)),L(n)&&(s.indexOf("message")>=0||s.indexOf("description")>=0))return p(n);if(0===s.length){if(E(n)){var y=n.name?": "+n.name:"";return t.stylize("[Function"+y+"]","special")}if(w(n))return t.stylize(RegExp.prototype.toString.call(n),"regexp");if(S(n))return t.stylize(Date.prototype.toString.call(n),"date");if(L(n))return p(n)}var g="",m=!1,k=["{","}"];if(d(n)&&(m=!0,k=["[","]"]),E(n)){var _=n.name?": "+n.name:"";g=" [Function"+_+"]"}if(w(n)&&(g=" "+RegExp.prototype.toString.call(n)),S(n)&&(g=" "+Date.prototype.toUTCString.call(n)),L(n)&&(g=" "+p(n)),0===s.length&&(!m||0==n.length))return k[0]+g+k[1];if(0>r)return w(n)?t.stylize(RegExp.prototype.toString.call(n),"regexp"):t.stylize("[Object]","special");t.seen.push(n);var x;return x=m?a(t,n,r,v,s):s.map(function(e){return f(t,n,r,v,e,m)}),t.seen.pop(),h(x,g,k)}function l(t,e){if(_(e))return t.stylize("undefined","undefined");if(b(e)){var n="'"+JSON.stringify(e).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return t.stylize(n,"string")}return m(e)?t.stylize(""+e,"number"):v(e)?t.stylize(""+e,"boolean"):y(e)?t.stylize("null","null"):void 0}function p(t){return"["+Error.prototype.toString.call(t)+"]"}function a(t,e,n,r,i){for(var o=[],s=0,u=e.length;u>s;++s)z(e,String(s))?o.push(f(t,e,n,r,String(s),!0)):o.push("");return i.forEach(function(i){i.match(/^\d+$/)||o.push(f(t,e,n,r,i,!0))}),o}function f(t,e,n,r,i,o){var s,u,l;if(l=Object.getOwnPropertyDescriptor(e,i)||{value:e[i]},l.get?u=l.set?t.stylize("[Getter/Setter]","special"):t.stylize("[Getter]","special"):l.set&&(u=t.stylize("[Setter]","special")),z(r,i)||(s="["+i+"]"),u||(t.seen.indexOf(l.value)<0?(u=y(n)?c(t,l.value,null):c(t,l.value,n-1),u.indexOf("\n")>-1&&(u=o?u.split("\n").map(function(t){return" "+t}).join("\n").substr(2):"\n"+u.split("\n").map(function(t){return" "+t}).join("\n"))):u=t.stylize("[Circular]","special")),_(s)){if(o&&i.match(/^\d+$/))return u;s=JSON.stringify(""+i),s.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(s=s.substr(1,s.length-2),s=t.stylize(s,"name")):(s=s.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),s=t.stylize(s,"string"))}return s+": "+u}function h(t,e,n){var r=0,i=t.reduce(function(t,e){return r++,e.indexOf("\n")>=0&&r++,t+e.replace(/\u001b\[\d\d?m/g,"").length+1},0);return i>60?n[0]+(""===e?"":e+"\n ")+" "+t.join(",\n ")+" "+n[1]:n[0]+e+" "+t.join(", ")+" "+n[1]}function d(t){return Array.isArray(t)}function v(t){return"boolean"==typeof t}function y(t){return null===t}function g(t){return null==t}function m(t){return"number"==typeof t}function b(t){return"string"==typeof t}function k(t){return"symbol"==typeof t}function _(t){return void 0===t}function w(t){return x(t)&&"[object RegExp]"===O(t)}function x(t){return"object"==typeof t&&null!==t}function S(t){return x(t)&&"[object Date]"===O(t)}function L(t){return x(t)&&("[object Error]"===O(t)||t instanceof Error)}function E(t){return"function"==typeof t}function j(t){return null===t||"boolean"==typeof t||"number"==typeof t||"string"==typeof t||"symbol"==typeof t||"undefined"==typeof t}function O(t){return Object.prototype.toString.call(t)}function T(t){return 10>t?"0"+t.toString(10):t.toString(10)}function I(){var t=new Date,e=[T(t.getHours()),T(t.getMinutes()),T(t.getSeconds())].join(":");return[t.getDate(),P[t.getMonth()],e].join(" ")}function z(t,e){return Object.prototype.hasOwnProperty.call(t,e)}var B=/%[sdj%]/g;e.format=function(t){if(!b(t)){for(var e=[],n=0;n=o)return t;switch(t){case"%s":return String(r[n++]);case"%d":return Number(r[n++]);case"%j":try{return JSON.stringify(r[n++])}catch(e){return"[Circular]"}default:return t}}),u=r[n];o>n;u=r[++n])s+=y(u)||!x(u)?" "+u:" "+i(u);return s},e.deprecate=function(n,i){function o(){if(!s){if(r.throwDeprecation)throw new Error(i);r.traceDeprecation?console.trace(i):console.error(i),s=!0}return n.apply(this,arguments)}if(_(t.process))return function(){return e.deprecate(n,i).apply(this,arguments)};if(r.noDeprecation===!0)return n;var s=!1;return o};var A,D={};e.debuglog=function(t){if(_(A)&&(A=r.env.NODE_DEBUG||""),t=t.toUpperCase(),!D[t])if(new RegExp("\\b"+t+"\\b","i").test(A)){var n=r.pid;D[t]=function(){var r=e.format.apply(e,arguments);console.error("%s %d: %s",t,n,r)}}else D[t]=function(){};return D[t]},e.inspect=i,i.colors={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]},i.styles={special:"cyan",number:"yellow","boolean":"yellow",undefined:"grey","null":"bold",string:"green",date:"magenta",regexp:"red"},e.isArray=d,e.isBoolean=v,e.isNull=y,e.isNullOrUndefined=g,e.isNumber=m,e.isString=b,e.isSymbol=k,e.isUndefined=_,e.isRegExp=w,e.isObject=x,e.isDate=S,e.isError=L,e.isFunction=E,e.isPrimitive=j,e.isBuffer=n(4);var P=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];e.log=function(){console.log("%s - %s",I(),e.format.apply(e,arguments))},e.inherits=n(5),e._extend=function(t,e){if(!e||!x(e))return t;for(var n=Object.keys(e),r=n.length;r--;)t[n[r]]=e[n[r]];return t}}).call(e,function(){return this}(),n(3))},function(t,e){function n(){l=!1,s.length?c=s.concat(c):p=-1,c.length&&r()}function r(){if(!l){var t=setTimeout(n);l=!0;for(var e=c.length;e;){for(s=c,c=[];++p1)for(var n=1;n-1&&this.stacks.splice(e,1)},r.prototype._getNextBlock=function(t){return"undefined"==typeof this.blocks[t]?null:this.blocks[t].next},r.prototype._getSubstack=function(t){return"undefined"==typeof this.blocks[t]?null:this.blocks[t].inputs.SUBSTACK},t.exports=r},function(t,e,n){function r(t){this.runtime=t,this.runningThreads=[],this.workTime=30,this.timer=new i,this.currentTime=0}var i=n(8);r.prototype.stepAllThreads=function(){},r.prototype.stepThread=function(t){},r.prototype.startSubstack=function(t){},t.exports=r},function(t,e){function n(){this.startTime=0}n.prototype.time=function(){return Date.now()},n.prototype.start=function(){this.startTime=this.time()},n.prototype.stop=function(){return this.startTime-this.time()},t.exports=n},function(t,e){function n(t){this.topBlockId=t,this.blockPointer=t,this.blockFirstTime=-1,this.nextBlock=null,this.waiting=null,this.runningDeviceBlock=!1,this.stack=[],this.repeatCounter=-1}t.exports=n},function(t,e){function n(){}n.prototype.event_whenflagclicked=function(t,e){return Date.now()-t.blockFirstTime<300?void(t["yield"]=!0):void 0},n.prototype.control_repeat=function(t,e){return Date.now()-t.blockFirstTime<300?void(t["yield"]=!0):(-1==t.repeatCounter&&(t.repeatCounter=10),void(t.repeatCounter>0?(t.repeatCounter-=1,e.interpreter.startSubstack(t)):(t.repeatCounter=-1,t.nextBlock=e.getNextBlock(t.blockPointer))))},n.prototype.control_forever=function(t,e){return Date.now()-t.blockFirstTime<300?void(t["yield"]=!0):void e.interpreter.startSubstack(t)},t.exports=n}]);
\ No newline at end of file
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 000000000..5a9e1dd8c
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,18 @@
+var webpack = require('webpack');
+
+module.exports = {
+ entry: {
+ 'vm': './src/index.js',
+ 'vm.min': './src/index.js'
+ },
+ output: {
+ path: __dirname,
+ filename: '[name].js'
+ },
+ plugins: [
+ new webpack.optimize.UglifyJsPlugin({
+ include: /\.min\.js$/,
+ minimize: true
+ })
+ ]
+};