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 + }) + ] +};