diff --git a/bench/.gitignore b/bench/.gitignore
new file mode 100644
index 0000000..074a1ba
--- /dev/null
+++ b/bench/.gitignore
@@ -0,0 +1,2 @@
+elm-stuff
+node_modules
\ No newline at end of file
diff --git a/bench/LICENCE b/bench/LICENCE
new file mode 100644
index 0000000..4b9de32
--- /dev/null
+++ b/bench/LICENCE
@@ -0,0 +1,27 @@
+Copyright (c) 2016, Evan Czaplicki
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the {organization} nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/bench/elm-package.json b/bench/elm-package.json
new file mode 100644
index 0000000..34b77a8
--- /dev/null
+++ b/bench/elm-package.json
@@ -0,0 +1,15 @@
+{
+ "version": "1.0.0",
+ "summary": "helpful summary of your project, less than 80 characters",
+ "repository": "https://github.com/user/project.git",
+ "license": "BSD3",
+ "source-directories": [
+ "."
+ ],
+ "exposed-modules": [],
+ "dependencies": {
+ "elm-lang/core": "4.0.5 <= v < 5.0.0",
+ "elm-lang/html": "1.1.0 <= v < 2.0.0"
+ },
+ "elm-version": "0.17.1 <= v < 0.18.0"
+}
diff --git a/bench/implementations/angular-1.5.8-optimized/build.min.js b/bench/implementations/angular-1.5.8-optimized/build.min.js
new file mode 100755
index 0000000..e94ad0f
--- /dev/null
+++ b/bench/implementations/angular-1.5.8-optimized/build.min.js
@@ -0,0 +1 @@
+(function(){"use strict";angular.module("todoCtrl",[]).controller("TodoCtrl",function TodoCtrl($scope,$location){var TC=this;var todos=TC.todos=[];TC.ESCAPE_KEY=27;TC.editedTodo={};TC.updateRemainingCount=function(){TC.remainingCount=todos.filter(function(todo){return!todo.completed}).length;TC.allChecked=TC.remainingCount===0};function resetTodo(){TC.newTodo={title:"",completed:false}}resetTodo();if($location.path()===""){$location.path("/")}TC.location=$location;$scope.$watch("TC.location.path()",function(path){TC.statusFilter={"/active":{completed:false},"/completed":{completed:true}}[path]});TC.addTodo=function(){var newTitle=TC.newTodo.title=TC.newTodo.title.trim();if(newTitle.length===0){return}todos.push(TC.newTodo);resetTodo();TC.updateRemainingCount()};TC.editTodo=function(todo){TC.editedTodo=todo;TC.originalTodo=angular.copy(todo)};TC.doneEditing=function(todo,index){TC.editedTodo={};todo.title=todo.title.trim();if(!todo.title){TC.removeTodo(index)}TC.updateRemainingCount()};TC.revertEditing=function(index){TC.editedTodo={};todos[index]=TC.originalTodo;TC.updateRemainingCount()};TC.removeTodo=function(index){todos.splice(index,1);TC.updateRemainingCount()};TC.clearCompletedTodos=function(){TC.todos=todos=todos.filter(function(val){return!val.completed});TC.updateRemainingCount()};TC.markAll=function(completed){todos.forEach(function(todo){todo.completed=completed});TC.updateRemainingCount()}});angular.module("todoFocus",[]).directive("todoFocus",function($timeout){return function(scope,elem,attrs){scope.$watch(attrs.todoFocus,function(newVal){if(newVal){$timeout(function(){elem[0].focus()},0,false)}})}});angular.module("todomvc",["todoCtrl","todoFocus"])})();
\ No newline at end of file
diff --git a/bench/implementations/angular-1.5.8-optimized/index.html b/bench/implementations/angular-1.5.8-optimized/index.html
new file mode 100755
index 0000000..de0c93f
--- /dev/null
+++ b/bench/implementations/angular-1.5.8-optimized/index.html
@@ -0,0 +1,77 @@
+
+
+
+
+
+ AngularJS • TodoMVC
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bench/implementations/angular-1.5.8-optimized/js/app.js b/bench/implementations/angular-1.5.8-optimized/js/app.js
new file mode 100755
index 0000000..6c93010
--- /dev/null
+++ b/bench/implementations/angular-1.5.8-optimized/js/app.js
@@ -0,0 +1,118 @@
+/* jshint undef: true, unused: true */
+/*global angular */
+(function () {
+ 'use strict';
+
+ angular.module('todoCtrl', [])
+
+ /**
+ * The main controller for the app. The controller:
+ * - retrieves and persists the model via the todoStorage service
+ * - exposes the model to the template and provides event handlers
+ */
+ .controller('TodoCtrl', function TodoCtrl($scope, $location) {
+ var TC = this;
+ var todos = TC.todos = [];
+
+ TC.ESCAPE_KEY = 27;
+ TC.editedTodo = {};
+
+ TC.updateRemainingCount = function () {
+ TC.remainingCount = todos.filter(function (todo) { return !todo.completed; }).length;
+ TC.allChecked = (TC.remainingCount === 0);
+ }
+
+ function resetTodo() {
+ TC.newTodo = {title: '', completed: false};
+ }
+
+ resetTodo();
+
+ if ($location.path() === '') {
+ $location.path('/');
+ }
+
+ TC.location = $location;
+
+ $scope.$watch('TC.location.path()', function (path) {
+ TC.statusFilter = { '/active': {completed: false}, '/completed': {completed: true} }[path];
+ });
+
+ TC.addTodo = function () {
+ var newTitle = TC.newTodo.title = TC.newTodo.title.trim();
+ if (newTitle.length === 0) {
+ return;
+ }
+
+ todos.push(TC.newTodo);
+ resetTodo();
+ TC.updateRemainingCount();
+ };
+
+ TC.editTodo = function (todo) {
+ TC.editedTodo = todo;
+
+ // Clone the original todo to restore it on demand.
+ TC.originalTodo = angular.copy(todo);
+ };
+
+ TC.doneEditing = function (todo, index) {
+ TC.editedTodo = {};
+ todo.title = todo.title.trim();
+
+ if (!todo.title) {
+ TC.removeTodo(index);
+ }
+ TC.updateRemainingCount();
+ };
+
+ TC.revertEditing = function (index) {
+ TC.editedTodo = {};
+ todos[index] = TC.originalTodo;
+ TC.updateRemainingCount();
+ };
+
+ TC.removeTodo = function (index) {
+ todos.splice(index, 1);
+ TC.updateRemainingCount();
+ };
+
+ TC.clearCompletedTodos = function () {
+ TC.todos = todos = todos.filter(function (val) {
+ return !val.completed;
+ });
+ TC.updateRemainingCount();
+ };
+
+ TC.markAll = function (completed) {
+ todos.forEach(function (todo) {
+ todo.completed = completed;
+ });
+ TC.updateRemainingCount();
+ };
+ });
+
+
+ angular.module('todoFocus', [])
+
+ /**
+ * Directive that places focus on the element it is applied to when the expression it binds to evaluates to true
+ */
+ .directive('todoFocus', function ($timeout) {
+ return function (scope, elem, attrs) {
+ scope.$watch(attrs.todoFocus, function (newVal) {
+ if (newVal) {
+ $timeout(function () {
+ elem[0].focus();
+ }, 0, false);
+ }
+ });
+ };
+ });
+ /**
+ * The main TodoMVC app module that pulls all dependency modules declared in same named files
+ *
+ * @type {angular.Module}
+ */
+ angular.module('todomvc', ['todoCtrl', 'todoFocus']);
+})();
diff --git a/bench/implementations/angular-1.5.8-optimized/package.json b/bench/implementations/angular-1.5.8-optimized/package.json
new file mode 100644
index 0000000..f23e1dc
--- /dev/null
+++ b/bench/implementations/angular-1.5.8-optimized/package.json
@@ -0,0 +1,14 @@
+{
+ "private": true,
+ "scripts": {
+ "make": "uglifyjs js/app.js -o build.min.js"
+ },
+ "dependencies": {
+ "angular": "^1.5.8",
+ "todomvc-app-css": "^2.0.6",
+ "todomvc-common": "^1.0.2"
+ },
+ "devDependencies": {
+ "uglify-js": "^2.6.4"
+ }
+}
diff --git a/bench/implementations/angular-1.5.8-optimized/readme.md b/bench/implementations/angular-1.5.8-optimized/readme.md
new file mode 100755
index 0000000..14e4142
--- /dev/null
+++ b/bench/implementations/angular-1.5.8-optimized/readme.md
@@ -0,0 +1,25 @@
+# Angular TodoMVC for Benchmarking
+
+This is an update of the
+_[Angular TodoMVC example](https://github.com/tastejs/todomvc/tree/gh-pages/examples/angularjs-perf)_
+with the following changes:
+
+* Upgrade to Angular 1.5.7
+* Update todomvc-app-css to 2.0.6 to match other examples in this benchmark
+* Remove localStorage functionality - not relevant to measuring render performance
+* Concat JS into one file
+* Minify with uglify
+
+and additional performance optimizations:
+
+#### Removed the deep $watch in favor of direct function calls
+The original example uses $scope.$watch with object equality enabled to track changes to the array of todos and perform actions when changes are detected. This is not a good practice as it causes a deep array comparison to happen every digest cycle, which can significantly degrade performance when working with large collections. Instead we can perform the required actions directly whenever todos are modified, since we control all points of possible modification.
+
+#### Removed "track by $index" from the ng-repeat directive
+The original example uses the ng-repeat directive with a "track by $index" clause to display the list of todos. _[Refer to section "Tracking and Duplicates" here for information on using "track by".](https://code.angularjs.org/1.5.7/docs/api/ng/directive/ngRepeat)_ In this particular case it is not necessary to use it, as long as care is taken not to persist the "$$hashKey" property added by Angular to local storage, which would cause duplicates to appear when the collection is loaded and new items are added, triggering an exception. The version without the "track by" clause could reasonably be written by someone implementing this from scratch and demonstrates better performance.
+
+# Building
+
+1. `npm install`
+2. `npm run make`
+3. Open a local server (e.g. with `npm install -g http-server`) and open index.html
diff --git a/bench/implementations/angular-1.5.8/build.min.js b/bench/implementations/angular-1.5.8/build.min.js
new file mode 100644
index 0000000..d0d056c
--- /dev/null
+++ b/bench/implementations/angular-1.5.8/build.min.js
@@ -0,0 +1 @@
+(function(){"use strict";angular.module("todoCtrl",[]).controller("TodoCtrl",function TodoCtrl($scope,$location){var TC=this;var todos=TC.todos=[];TC.ESCAPE_KEY=27;TC.editedTodo={};function resetTodo(){TC.newTodo={title:"",completed:false}}resetTodo();if($location.path()===""){$location.path("/")}TC.location=$location;$scope.$watch("TC.location.path()",function(path){TC.statusFilter={"/active":{completed:false},"/completed":{completed:true}}[path]});$scope.$watch("TC.todos",function(){TC.remainingCount=todos.filter(function(todo){return!todo.completed}).length;TC.allChecked=TC.remainingCount===0},true);TC.addTodo=function(){var newTitle=TC.newTodo.title=TC.newTodo.title.trim();if(newTitle.length===0){return}todos.push(TC.newTodo);resetTodo()};TC.editTodo=function(todo){TC.editedTodo=todo;TC.originalTodo=angular.copy(todo)};TC.doneEditing=function(todo,index){TC.editedTodo={};todo.title=todo.title.trim();if(!todo.title){TC.removeTodo(index)}};TC.revertEditing=function(index){TC.editedTodo={};todos[index]=TC.originalTodo};TC.removeTodo=function(index){todos.splice(index,1)};TC.clearCompletedTodos=function(){TC.todos=todos=todos.filter(function(val){return!val.completed})};TC.markAll=function(completed){todos.forEach(function(todo){todo.completed=completed})}});angular.module("todoFocus",[]).directive("todoFocus",function($timeout){return function(scope,elem,attrs){scope.$watch(attrs.todoFocus,function(newVal){if(newVal){$timeout(function(){elem[0].focus()},0,false)}})}});angular.module("todomvc",["todoCtrl","todoFocus"])})();
\ No newline at end of file
diff --git a/bench/implementations/angular-1.5.8/index.html b/bench/implementations/angular-1.5.8/index.html
new file mode 100644
index 0000000..327d016
--- /dev/null
+++ b/bench/implementations/angular-1.5.8/index.html
@@ -0,0 +1,77 @@
+
+
+
+
+
+ AngularJS • TodoMVC
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bench/implementations/angular-1.5.8/js/app.js b/bench/implementations/angular-1.5.8/js/app.js
new file mode 100644
index 0000000..3e4a810
--- /dev/null
+++ b/bench/implementations/angular-1.5.8/js/app.js
@@ -0,0 +1,113 @@
+/* jshint undef: true, unused: true */
+/*global angular */
+(function () {
+ 'use strict';
+
+
+ angular.module('todoCtrl', [])
+
+ /**
+ * The main controller for the app. The controller:
+ * - retrieves and persists the model via the todoStorage service
+ * - exposes the model to the template and provides event handlers
+ */
+ .controller('TodoCtrl', function TodoCtrl($scope, $location) {
+ var TC = this;
+ var todos = TC.todos = [];
+
+ TC.ESCAPE_KEY = 27;
+ TC.editedTodo = {};
+
+ function resetTodo() {
+ TC.newTodo = {title: '', completed: false};
+ }
+
+ resetTodo();
+
+ if ($location.path() === '') {
+ $location.path('/');
+ }
+
+ TC.location = $location;
+
+ $scope.$watch('TC.location.path()', function (path) {
+ TC.statusFilter = { '/active': {completed: false}, '/completed': {completed: true} }[path];
+ });
+
+ // 3rd argument `true` for deep object watching
+ $scope.$watch('TC.todos', function () {
+ TC.remainingCount = todos.filter(function (todo) { return !todo.completed; }).length;
+ TC.allChecked = (TC.remainingCount === 0);
+ }, true);
+
+ TC.addTodo = function () {
+ var newTitle = TC.newTodo.title = TC.newTodo.title.trim();
+ if (newTitle.length === 0) {
+ return;
+ }
+
+ todos.push(TC.newTodo);
+ resetTodo();
+ };
+
+ TC.editTodo = function (todo) {
+ TC.editedTodo = todo;
+
+ // Clone the original todo to restore it on demand.
+ TC.originalTodo = angular.copy(todo);
+ };
+
+ TC.doneEditing = function (todo, index) {
+ TC.editedTodo = {};
+ todo.title = todo.title.trim();
+
+ if (!todo.title) {
+ TC.removeTodo(index);
+ }
+ };
+
+ TC.revertEditing = function (index) {
+ TC.editedTodo = {};
+ todos[index] = TC.originalTodo;
+ };
+
+ TC.removeTodo = function (index) {
+ todos.splice(index, 1);
+ };
+
+ TC.clearCompletedTodos = function () {
+ TC.todos = todos = todos.filter(function (val) {
+ return !val.completed;
+ });
+ };
+
+ TC.markAll = function (completed) {
+ todos.forEach(function (todo) {
+ todo.completed = completed;
+ });
+ };
+ });
+
+ angular.module('todoFocus', [])
+
+ /**
+ * Directive that places focus on the element it is applied to when the expression it binds to evaluates to true
+ */
+ .directive('todoFocus', function ($timeout) {
+ return function (scope, elem, attrs) {
+ scope.$watch(attrs.todoFocus, function (newVal) {
+ if (newVal) {
+ $timeout(function () {
+ elem[0].focus();
+ }, 0, false);
+ }
+ });
+ };
+ });
+ /**
+ * The main TodoMVC app module that pulls all dependency modules declared in same named files
+ *
+ * @type {angular.Module}
+ */
+ angular.module('todomvc', ['todoCtrl', 'todoFocus']);
+})();
diff --git a/bench/implementations/angular-1.5.8/package.json b/bench/implementations/angular-1.5.8/package.json
new file mode 100644
index 0000000..f23e1dc
--- /dev/null
+++ b/bench/implementations/angular-1.5.8/package.json
@@ -0,0 +1,14 @@
+{
+ "private": true,
+ "scripts": {
+ "make": "uglifyjs js/app.js -o build.min.js"
+ },
+ "dependencies": {
+ "angular": "^1.5.8",
+ "todomvc-app-css": "^2.0.6",
+ "todomvc-common": "^1.0.2"
+ },
+ "devDependencies": {
+ "uglify-js": "^2.6.4"
+ }
+}
diff --git a/bench/implementations/angular-1.5.8/readme.md b/bench/implementations/angular-1.5.8/readme.md
new file mode 100644
index 0000000..0b688c9
--- /dev/null
+++ b/bench/implementations/angular-1.5.8/readme.md
@@ -0,0 +1,19 @@
+# Angular TodoMVC for Benchmarking
+
+This is an update of the
+_[Angular TodoMVC example](https://github.com/tastejs/todomvc/tree/gh-pages/examples/angularjs-perf)_
+with the following changes:
+
+* Upgrade to Angular 1.5.7
+* Update todomvc-app-css to 2.0.6 to match other examples in this benchmark
+* Remove localStorage functionality - not relevant to measuring render performance
+* Concat JS into one file
+* Minify with uglify
+
+This example is listed as "performance optimized" in the TodoMVC repository, but doesn't appear to make any performance improvements to the basic example other than excluding all features not mentioned in the app specification. Therefore, it should count as having no optimizations for the purposes of this benchmark.
+
+# Building
+
+1. `npm install`
+2. `npm run make`
+3. Open a local server (e.g. with `npm install -g http-server`) and open index.html
diff --git a/bench/implementations/angular-2-optimized/.gitignore b/bench/implementations/angular-2-optimized/.gitignore
new file mode 100644
index 0000000..64ee7d5
--- /dev/null
+++ b/bench/implementations/angular-2-optimized/.gitignore
@@ -0,0 +1,28 @@
+node_modules/.bin
+
+# ignore the source / documentation of each node module, but retain the
+# distribution builds
+
+node_modules/systemjs
+!node_modules/systemjs/dist/system.src.js
+
+node_modules/todomvc-app-css
+!node_modules/todomvc-app-css/index.css
+
+node_modules/todomvc-common
+!node_modules/todomvc-common/base.css
+
+node_modules/angular2
+!node_modules/angular2/bundles/angular2-polyfills.js
+!node_modules/angular2/bundles/angular2.dev.js
+
+node_modules/rxjs
+!node_modules/rxjs/bundles/Rx.js
+
+# Ignore development dependencies
+node_modules/tsd
+node_modules/typescript
+
+*.js
+*.js.map
+typings
diff --git a/bench/implementations/angular-2-optimized/app/app.html b/bench/implementations/angular-2-optimized/app/app.html
new file mode 100644
index 0000000..3caa060
--- /dev/null
+++ b/bench/implementations/angular-2-optimized/app/app.html
@@ -0,0 +1,23 @@
+
diff --git a/bench/implementations/angular-2-optimized/app/app.ts b/bench/implementations/angular-2-optimized/app/app.ts
new file mode 100644
index 0000000..a59feca
--- /dev/null
+++ b/bench/implementations/angular-2-optimized/app/app.ts
@@ -0,0 +1,62 @@
+import {Component} from 'angular2/core';
+import {TodoStore, Todo} from './services/store';
+
+@Component({
+ selector: 'todo-app',
+ templateUrl: 'app/app.html'
+})
+export default class TodoApp {
+ todoStore: TodoStore;
+ newTodoText = '';
+
+ constructor(todoStore: TodoStore) {
+ this.todoStore = todoStore;
+ }
+
+ identify(index: number) {
+ return index;
+ }
+
+ stopEditing(todo: Todo, editedTitle: string) {
+ todo.title = editedTitle;
+ todo.editing = false;
+ }
+
+ cancelEditingTodo(todo: Todo) {
+ todo.editing = false;
+ }
+
+ updateEditingTodo(todo: Todo, editedTitle: string) {
+ editedTitle = editedTitle.trim();
+ todo.editing = false;
+
+ if (editedTitle.length === 0) {
+ return this.todoStore.remove(todo);
+ }
+
+ todo.title = editedTitle;
+ }
+
+ editTodo(todo: Todo) {
+ todo.editing = true;
+ }
+
+ removeCompleted() {
+ this.todoStore.removeCompleted();
+ }
+
+ toggleCompletion(todo: Todo) {
+ this.todoStore.toggleCompletion(todo);
+ }
+
+ remove(todo: Todo){
+ this.todoStore.remove(todo);
+ }
+
+ addTodo() {
+ if (this.newTodoText.trim().length) {
+ this.todoStore.add(this.newTodoText);
+ this.newTodoText = '';
+ }
+ }
+}
diff --git a/bench/implementations/angular-2-optimized/app/bootstrap.ts b/bench/implementations/angular-2-optimized/app/bootstrap.ts
new file mode 100644
index 0000000..fd300af
--- /dev/null
+++ b/bench/implementations/angular-2-optimized/app/bootstrap.ts
@@ -0,0 +1,9 @@
+///
+
+import {bootstrap} from 'angular2/platform/browser';
+import TodoApp from './app'
+import {TodoStore} from './services/store';
+import {enableProdMode} from 'angular2/core';
+
+enableProdMode();
+bootstrap(TodoApp, [TodoStore]);
diff --git a/bench/implementations/angular-2-optimized/app/services/store.ts b/bench/implementations/angular-2-optimized/app/services/store.ts
new file mode 100644
index 0000000..ca4e3b6
--- /dev/null
+++ b/bench/implementations/angular-2-optimized/app/services/store.ts
@@ -0,0 +1,62 @@
+export class Todo {
+ completed: Boolean;
+ editing: Boolean;
+
+ private _title: String;
+ get title() {
+ return this._title;
+ }
+ set title(value: String) {
+ this._title = value.trim();
+ }
+
+ constructor(title: String) {
+ this.completed = false;
+ this.editing = false;
+ this.title = title.trim();
+ }
+}
+
+export class TodoStore {
+ todos: Array;
+
+ constructor() {
+ this.todos = [];
+ }
+
+ private getWithCompleted(completed: Boolean) {
+ return this.todos.filter((todo: Todo) => todo.completed === completed);
+ }
+
+ allCompleted() {
+ return this.todos.length === this.getCompleted().length;
+ }
+
+ setAllTo(completed: Boolean) {
+ this.todos.forEach((t: Todo) => t.completed = completed);
+ }
+
+ removeCompleted() {
+ this.todos = this.getWithCompleted(false);
+ }
+
+ getRemaining() {
+ return this.getWithCompleted(false);
+ }
+
+ getCompleted() {
+ return this.getWithCompleted(true);
+ }
+
+ toggleCompletion(todo: Todo) {
+ todo.completed = !todo.completed;
+ }
+
+ remove(todo: Todo) {
+ this.todos.splice(this.todos.indexOf(todo), 1);
+ }
+
+ add(title: String) {
+ this.todos.push(new Todo(title));
+ }
+}
diff --git a/bench/implementations/angular-2-optimized/index.html b/bench/implementations/angular-2-optimized/index.html
new file mode 100644
index 0000000..563d5af
--- /dev/null
+++ b/bench/implementations/angular-2-optimized/index.html
@@ -0,0 +1,35 @@
+
+
+
+
+ Angular2 • TodoMVC
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bench/implementations/angular-2-optimized/package.json b/bench/implementations/angular-2-optimized/package.json
new file mode 100644
index 0000000..a12d63e
--- /dev/null
+++ b/bench/implementations/angular-2-optimized/package.json
@@ -0,0 +1,19 @@
+{
+ "private": true,
+ "scripts": {
+ "dev": "tsc"
+ },
+ "devDependencies": {
+ "typescript": "^1.8.10"
+ },
+ "dependencies": {
+ "angular2": "^2.0.0-beta.17",
+ "es6-shim": "^0.35.1",
+ "reflect-metadata": "0.1.2",
+ "rxjs": "5.0.0-beta.6",
+ "systemjs": "0.19.6",
+ "todomvc-app-css": "^2.0.0",
+ "todomvc-common": "^1.0.1",
+ "zone.js": "^0.6.17"
+ }
+}
diff --git a/bench/implementations/angular-2-optimized/readme.md b/bench/implementations/angular-2-optimized/readme.md
new file mode 100644
index 0000000..aace877
--- /dev/null
+++ b/bench/implementations/angular-2-optimized/readme.md
@@ -0,0 +1,32 @@
+# Angular 2 • [TodoMVC](http://todomvc.com)
+
+> Angular is a development platform for creating applications using modern web standards. Angular includes a wealth of essential features such as mobile gestures, animations, filtering, routing, data binding, security, internationalization, and beautiful UI components. It's extremely modular, lightweight, and easy to learn.
+
+## Resources
+
+- [Website](https://angular.io/)
+- [Documentation](https://angular.io/docs/ts/latest/)
+
+### Articles
+
+- [Angular 2 Beta Announcement](http://angularjs.blogspot.co.uk/2015/12/angular-2-beta.html)
+
+### Support
+
+- [StackOverflow](http://stackoverflow.com/questions/tagged/angular2)
+- [Google Groups](https://groups.google.com/forum/#!forum/angular)
+- [Twitter](http://twitter.com/angularjs)
+- [Google+](https://plus.sandbox.google.com/+AngularJS/posts)
+
+*Let us [know](https://github.com/tastejs/todomvc/issues) if you discover anything worth sharing.*
+
+## Implementation
+
+This app was built using TypeScript and Angular 2 beta. To make changes simply
+
+* `npm i`
+* `npm run dev`
+
+## Credit
+
+Created by [Sam Saccone](http://github.com/samccone)
diff --git a/bench/implementations/angular-2-optimized/tsconfig.json b/bench/implementations/angular-2-optimized/tsconfig.json
new file mode 100644
index 0000000..8b3de5f
--- /dev/null
+++ b/bench/implementations/angular-2-optimized/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "target": "ES5",
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "sourceMap": false,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "removeComments": false,
+ "noImplicitAny": true
+ },
+ "files": [
+ "app/bootstrap.ts"
+ ]
+}
diff --git a/bench/implementations/angular-2/.gitignore b/bench/implementations/angular-2/.gitignore
new file mode 100755
index 0000000..64ee7d5
--- /dev/null
+++ b/bench/implementations/angular-2/.gitignore
@@ -0,0 +1,28 @@
+node_modules/.bin
+
+# ignore the source / documentation of each node module, but retain the
+# distribution builds
+
+node_modules/systemjs
+!node_modules/systemjs/dist/system.src.js
+
+node_modules/todomvc-app-css
+!node_modules/todomvc-app-css/index.css
+
+node_modules/todomvc-common
+!node_modules/todomvc-common/base.css
+
+node_modules/angular2
+!node_modules/angular2/bundles/angular2-polyfills.js
+!node_modules/angular2/bundles/angular2.dev.js
+
+node_modules/rxjs
+!node_modules/rxjs/bundles/Rx.js
+
+# Ignore development dependencies
+node_modules/tsd
+node_modules/typescript
+
+*.js
+*.js.map
+typings
diff --git a/bench/implementations/angular-2/app/app.html b/bench/implementations/angular-2/app/app.html
new file mode 100755
index 0000000..9718565
--- /dev/null
+++ b/bench/implementations/angular-2/app/app.html
@@ -0,0 +1,23 @@
+
diff --git a/bench/implementations/angular-2/app/app.ts b/bench/implementations/angular-2/app/app.ts
new file mode 100755
index 0000000..74b8391
--- /dev/null
+++ b/bench/implementations/angular-2/app/app.ts
@@ -0,0 +1,58 @@
+import {Component} from 'angular2/core';
+import {TodoStore, Todo} from './services/store';
+
+@Component({
+ selector: 'todo-app',
+ templateUrl: 'app/app.html'
+})
+export default class TodoApp {
+ todoStore: TodoStore;
+ newTodoText = '';
+
+ constructor(todoStore: TodoStore) {
+ this.todoStore = todoStore;
+ }
+
+ stopEditing(todo: Todo, editedTitle: string) {
+ todo.title = editedTitle;
+ todo.editing = false;
+ }
+
+ cancelEditingTodo(todo: Todo) {
+ todo.editing = false;
+ }
+
+ updateEditingTodo(todo: Todo, editedTitle: string) {
+ editedTitle = editedTitle.trim();
+ todo.editing = false;
+
+ if (editedTitle.length === 0) {
+ return this.todoStore.remove(todo);
+ }
+
+ todo.title = editedTitle;
+ }
+
+ editTodo(todo: Todo) {
+ todo.editing = true;
+ }
+
+ removeCompleted() {
+ this.todoStore.removeCompleted();
+ }
+
+ toggleCompletion(todo: Todo) {
+ this.todoStore.toggleCompletion(todo);
+ }
+
+ remove(todo: Todo){
+ this.todoStore.remove(todo);
+ }
+
+ addTodo() {
+ if (this.newTodoText.trim().length) {
+ this.todoStore.add(this.newTodoText);
+ this.newTodoText = '';
+ }
+ }
+}
diff --git a/bench/implementations/angular-2/app/bootstrap.ts b/bench/implementations/angular-2/app/bootstrap.ts
new file mode 100755
index 0000000..fd300af
--- /dev/null
+++ b/bench/implementations/angular-2/app/bootstrap.ts
@@ -0,0 +1,9 @@
+///
+
+import {bootstrap} from 'angular2/platform/browser';
+import TodoApp from './app'
+import {TodoStore} from './services/store';
+import {enableProdMode} from 'angular2/core';
+
+enableProdMode();
+bootstrap(TodoApp, [TodoStore]);
diff --git a/bench/implementations/angular-2/app/services/store.ts b/bench/implementations/angular-2/app/services/store.ts
new file mode 100755
index 0000000..ca4e3b6
--- /dev/null
+++ b/bench/implementations/angular-2/app/services/store.ts
@@ -0,0 +1,62 @@
+export class Todo {
+ completed: Boolean;
+ editing: Boolean;
+
+ private _title: String;
+ get title() {
+ return this._title;
+ }
+ set title(value: String) {
+ this._title = value.trim();
+ }
+
+ constructor(title: String) {
+ this.completed = false;
+ this.editing = false;
+ this.title = title.trim();
+ }
+}
+
+export class TodoStore {
+ todos: Array;
+
+ constructor() {
+ this.todos = [];
+ }
+
+ private getWithCompleted(completed: Boolean) {
+ return this.todos.filter((todo: Todo) => todo.completed === completed);
+ }
+
+ allCompleted() {
+ return this.todos.length === this.getCompleted().length;
+ }
+
+ setAllTo(completed: Boolean) {
+ this.todos.forEach((t: Todo) => t.completed = completed);
+ }
+
+ removeCompleted() {
+ this.todos = this.getWithCompleted(false);
+ }
+
+ getRemaining() {
+ return this.getWithCompleted(false);
+ }
+
+ getCompleted() {
+ return this.getWithCompleted(true);
+ }
+
+ toggleCompletion(todo: Todo) {
+ todo.completed = !todo.completed;
+ }
+
+ remove(todo: Todo) {
+ this.todos.splice(this.todos.indexOf(todo), 1);
+ }
+
+ add(title: String) {
+ this.todos.push(new Todo(title));
+ }
+}
diff --git a/bench/implementations/angular-2/index.html b/bench/implementations/angular-2/index.html
new file mode 100755
index 0000000..563d5af
--- /dev/null
+++ b/bench/implementations/angular-2/index.html
@@ -0,0 +1,35 @@
+
+
+
+
+ Angular2 • TodoMVC
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bench/implementations/angular-2/package.json b/bench/implementations/angular-2/package.json
new file mode 100755
index 0000000..a12d63e
--- /dev/null
+++ b/bench/implementations/angular-2/package.json
@@ -0,0 +1,19 @@
+{
+ "private": true,
+ "scripts": {
+ "dev": "tsc"
+ },
+ "devDependencies": {
+ "typescript": "^1.8.10"
+ },
+ "dependencies": {
+ "angular2": "^2.0.0-beta.17",
+ "es6-shim": "^0.35.1",
+ "reflect-metadata": "0.1.2",
+ "rxjs": "5.0.0-beta.6",
+ "systemjs": "0.19.6",
+ "todomvc-app-css": "^2.0.0",
+ "todomvc-common": "^1.0.1",
+ "zone.js": "^0.6.17"
+ }
+}
diff --git a/bench/implementations/angular-2/readme.md b/bench/implementations/angular-2/readme.md
new file mode 100755
index 0000000..aace877
--- /dev/null
+++ b/bench/implementations/angular-2/readme.md
@@ -0,0 +1,32 @@
+# Angular 2 • [TodoMVC](http://todomvc.com)
+
+> Angular is a development platform for creating applications using modern web standards. Angular includes a wealth of essential features such as mobile gestures, animations, filtering, routing, data binding, security, internationalization, and beautiful UI components. It's extremely modular, lightweight, and easy to learn.
+
+## Resources
+
+- [Website](https://angular.io/)
+- [Documentation](https://angular.io/docs/ts/latest/)
+
+### Articles
+
+- [Angular 2 Beta Announcement](http://angularjs.blogspot.co.uk/2015/12/angular-2-beta.html)
+
+### Support
+
+- [StackOverflow](http://stackoverflow.com/questions/tagged/angular2)
+- [Google Groups](https://groups.google.com/forum/#!forum/angular)
+- [Twitter](http://twitter.com/angularjs)
+- [Google+](https://plus.sandbox.google.com/+AngularJS/posts)
+
+*Let us [know](https://github.com/tastejs/todomvc/issues) if you discover anything worth sharing.*
+
+## Implementation
+
+This app was built using TypeScript and Angular 2 beta. To make changes simply
+
+* `npm i`
+* `npm run dev`
+
+## Credit
+
+Created by [Sam Saccone](http://github.com/samccone)
diff --git a/bench/implementations/angular-2/tsconfig.json b/bench/implementations/angular-2/tsconfig.json
new file mode 100755
index 0000000..8b3de5f
--- /dev/null
+++ b/bench/implementations/angular-2/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "target": "ES5",
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "sourceMap": false,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "removeComments": false,
+ "noImplicitAny": true
+ },
+ "files": [
+ "app/bootstrap.ts"
+ ]
+}
diff --git a/bench/implementations/elm-0.16-optimized/Todo.elm b/bench/implementations/elm-0.16-optimized/Todo.elm
new file mode 100755
index 0000000..4b6c07a
--- /dev/null
+++ b/bench/implementations/elm-0.16-optimized/Todo.elm
@@ -0,0 +1,339 @@
+module Todo where
+{-| TodoMVC implemented in Elm, using plain HTML and CSS for rendering.
+
+This application is broken up into four distinct parts:
+
+ 1. Model - a full definition of the application's state
+ 2. Update - a way to step the application state forward
+ 3. View - a way to visualize our application state with HTML
+ 4. Inputs - the signals necessary to manage events
+
+This clean division of concerns is a core part of Elm. You can read more about
+this in the Pong tutorial: http://elm-lang.org/blog/making-pong
+
+This program is not particularly large, so definitely see the following
+for notes on structuring more complex GUIs with Elm:
+https://github.com/evancz/elm-architecture-tutorial/
+-}
+
+import Html exposing (..)
+import Html.Attributes exposing (..)
+import Html.Events exposing (..)
+import Html.Lazy exposing (lazy2, lazy3)
+import Json.Decode as Json
+import Signal exposing (Signal, Address)
+import String
+import Window
+
+
+---- MODEL ----
+
+-- The full application state of our todo app.
+type alias Model =
+ { tasks : List Task
+ , field : String
+ , uid : Int
+ , visibility : String
+ }
+
+
+type alias Task =
+ { description : String
+ , completed : Bool
+ , editing : Bool
+ , id : Int
+ }
+
+
+newTask : String -> Int -> Task
+newTask desc id =
+ { description = desc
+ , completed = False
+ , editing = False
+ , id = id
+ }
+
+
+emptyModel : Model
+emptyModel =
+ { tasks = []
+ , visibility = "All"
+ , field = ""
+ , uid = 0
+ }
+
+
+---- UPDATE ----
+
+-- A description of the kinds of actions that can be performed on the model of
+-- our application. See the following for more info on this pattern and
+-- some alternatives: https://github.com/evancz/elm-architecture-tutorial/
+type Action
+ = NoOp
+ | UpdateField String
+ | EditingTask Int Bool
+ | UpdateTask Int String
+ | Add
+ | Delete Int
+ | DeleteComplete
+ | Check Int Bool
+ | CheckAll Bool
+ | ChangeVisibility String
+
+
+-- How we update our Model on a given Action?
+update : Action -> Model -> Model
+update action model =
+ case action of
+ NoOp -> model
+
+ Add ->
+ { model |
+ uid = model.uid + 1,
+ field = "",
+ tasks =
+ if String.isEmpty model.field
+ then model.tasks
+ else model.tasks ++ [newTask model.field model.uid]
+ }
+
+ UpdateField str ->
+ { model | field = str }
+
+ EditingTask id isEditing ->
+ let updateTask t = if t.id == id then { t | editing = isEditing } else t
+ in
+ { model | tasks = List.map updateTask model.tasks }
+
+ UpdateTask id task ->
+ let updateTask t = if t.id == id then { t | description = task } else t
+ in
+ { model | tasks = List.map updateTask model.tasks }
+
+ Delete id ->
+ { model | tasks = List.filter (\t -> t.id /= id) model.tasks }
+
+ DeleteComplete ->
+ { model | tasks = List.filter (not << .completed) model.tasks }
+
+ Check id isCompleted ->
+ let updateTask t = if t.id == id then { t | completed = isCompleted } else t
+ in
+ { model | tasks = List.map updateTask model.tasks }
+
+ CheckAll isCompleted ->
+ let updateTask t = { t | completed = isCompleted }
+ in
+ { model | tasks = List.map updateTask model.tasks }
+
+ ChangeVisibility visibility ->
+ { model | visibility = visibility }
+
+
+---- VIEW ----
+
+view : Address Action -> Model -> Html
+view address model =
+ div
+ [ class "todomvc-wrapper"
+ , style [ ("visibility", "hidden") ]
+ ]
+ [ section
+ [ class "todoapp" ]
+ [ lazy2 taskEntry address model.field
+ , lazy3 taskList address model.visibility model.tasks
+ , lazy3 controls address model.visibility model.tasks
+ ]
+ , infoFooter
+ ]
+
+
+onEnter : Address a -> a -> Attribute
+onEnter address value =
+ on "keydown"
+ (Json.customDecoder keyCode is13)
+ (\_ -> Signal.message address value)
+
+
+is13 : Int -> Result String ()
+is13 code =
+ if code == 13 then Ok () else Err "not the right key code"
+
+
+taskEntry : Address Action -> String -> Html
+taskEntry address task =
+ header
+ [ class "header" ]
+ [ h1 [] [ text "todos" ]
+ , input
+ [ class "new-todo"
+ , placeholder "What needs to be done?"
+ , autofocus True
+ , value task
+ , name "newTodo"
+ , on "input" targetValue (Signal.message address << UpdateField)
+ , onEnter address Add
+ ]
+ []
+ ]
+
+
+taskList : Address Action -> String -> List Task -> Html
+taskList address visibility tasks =
+ let isVisible todo =
+ case visibility of
+ "Completed" -> todo.completed
+ "Active" -> not todo.completed
+ _ -> True
+
+ allCompleted = List.all .completed tasks
+
+ cssVisibility = if List.isEmpty tasks then "hidden" else "visible"
+ in
+ section
+ [ class "main"
+ , style [ ("visibility", cssVisibility) ]
+ ]
+ [ input
+ [ class "toggle-all"
+ , type' "checkbox"
+ , name "toggle"
+ , checked allCompleted
+ , onClick address (CheckAll (not allCompleted))
+ ]
+ []
+ , label
+ [ for "toggle-all" ]
+ [ text "Mark all as complete" ]
+ , ul
+ [ class "todo-list" ]
+ (List.map (todoItem address) (List.filter isVisible tasks))
+ ]
+
+
+todoItem : Address Action -> Task -> Html
+todoItem address todo =
+ li
+ [ classList [ ("completed", todo.completed), ("editing", todo.editing) ]
+ , key (toString todo.id)
+ ]
+ [ div
+ [ class "view" ]
+ [ input
+ [ class "toggle"
+ , type' "checkbox"
+ , checked todo.completed
+ , onClick address (Check todo.id (not todo.completed))
+ ]
+ []
+ , label
+ [ onDoubleClick address (EditingTask todo.id True) ]
+ [ text todo.description ]
+ , button
+ [ class "destroy"
+ , onClick address (Delete todo.id)
+ ]
+ []
+ ]
+ , input
+ [ class "edit"
+ , value todo.description
+ , name "title"
+ , id ("todo-" ++ toString todo.id)
+ , on "input" targetValue (Signal.message address << UpdateTask todo.id)
+ , onBlur address (EditingTask todo.id False)
+ , onEnter address (EditingTask todo.id False)
+ ]
+ []
+ ]
+
+
+controls : Address Action -> String -> List Task -> Html
+controls address visibility tasks =
+ let tasksCompleted = List.length (List.filter .completed tasks)
+ tasksLeft = List.length tasks - tasksCompleted
+ item_ = if tasksLeft == 1 then " item" else " items"
+ in
+ footer
+ [ class "footer"
+ , hidden (List.isEmpty tasks)
+ ]
+ [ span
+ [ class "todo-count" ]
+ [ strong [] [ text (toString tasksLeft) ]
+ , text (item_ ++ " left")
+ ]
+ , ul
+ [ class "filters" ]
+ [ visibilitySwap address "#/" "All" visibility
+ , text " "
+ , visibilitySwap address "#/active" "Active" visibility
+ , text " "
+ , visibilitySwap address "#/completed" "Completed" visibility
+ ]
+ , button
+ [ class "clear-completed"
+ , hidden (tasksCompleted == 0)
+ , onClick address DeleteComplete
+ ]
+ [ text ("Clear completed (" ++ toString tasksCompleted ++ ")") ]
+ ]
+
+
+visibilitySwap : Address Action -> String -> String -> String -> Html
+visibilitySwap address uri visibility actualVisibility =
+ li
+ [ onClick address (ChangeVisibility visibility) ]
+ [ a [ href uri, classList [("selected", visibility == actualVisibility)] ] [ text visibility ] ]
+
+
+infoFooter : Html
+infoFooter =
+ footer [ class "info" ]
+ [ p [] [ text "Double-click to edit a todo" ]
+ , p []
+ [ text "Written by "
+ , a [ href "https://github.com/evancz" ] [ text "Evan Czaplicki" ]
+ ]
+ , p []
+ [ text "Part of "
+ , a [ href "http://todomvc.com" ] [ text "TodoMVC" ]
+ ]
+ ]
+
+
+---- INPUTS ----
+
+-- wire the entire application together
+main : Signal Html
+main =
+ Signal.map (view actions.address) model
+
+
+-- manage the model of our application over time
+model : Signal Model
+model =
+ Signal.foldp update emptyModel actions.signal
+
+
+-- actions from user input
+actions : Signal.Mailbox Action
+actions =
+ Signal.mailbox NoOp
+
+
+port focus : Signal String
+port focus =
+ let needsFocus act =
+ case act of
+ EditingTask id bool -> bool
+ _ -> False
+
+ toSelector act =
+ case act of
+ EditingTask id _ -> "#todo-" ++ toString id
+ _ -> ""
+ in
+ actions.signal
+ |> Signal.filter needsFocus (EditingTask 0 True)
+ |> Signal.map toSelector
diff --git a/bench/implementations/elm-0.16-optimized/elm-package.json b/bench/implementations/elm-0.16-optimized/elm-package.json
new file mode 100644
index 0000000..0c8a4c4
--- /dev/null
+++ b/bench/implementations/elm-0.16-optimized/elm-package.json
@@ -0,0 +1,15 @@
+{
+ "version": "1.0.0",
+ "summary": "TodoMVC created with Elm and elm-html",
+ "repository": "https://github.com/evancz/elm-todomvc.git",
+ "license": "BSD3",
+ "source-directories": [
+ "."
+ ],
+ "exposed-modules": [],
+ "dependencies": {
+ "elm-lang/core": "3.0.0 <= v < 4.0.0",
+ "evancz/elm-html": "4.0.2 <= v < 5.0.0"
+ },
+ "elm-version": "0.16.0 <= v < 0.17.0"
+}
diff --git a/bench/implementations/elm-0.16-optimized/elm.js b/bench/implementations/elm-0.16-optimized/elm.js
new file mode 100755
index 0000000..416e212
--- /dev/null
+++ b/bench/implementations/elm-0.16-optimized/elm.js
@@ -0,0 +1,10560 @@
+var Elm = Elm || { Native: {} };
+Elm.Native.Array = {};
+Elm.Native.Array.make = function(localRuntime) {
+
+ localRuntime.Native = localRuntime.Native || {};
+ localRuntime.Native.Array = localRuntime.Native.Array || {};
+ if (localRuntime.Native.Array.values)
+ {
+ return localRuntime.Native.Array.values;
+ }
+ if ('values' in Elm.Native.Array)
+ {
+ return localRuntime.Native.Array.values = Elm.Native.Array.values;
+ }
+
+ var List = Elm.Native.List.make(localRuntime);
+
+ // A RRB-Tree has two distinct data types.
+ // Leaf -> "height" is always 0
+ // "table" is an array of elements
+ // Node -> "height" is always greater than 0
+ // "table" is an array of child nodes
+ // "lengths" is an array of accumulated lengths of the child nodes
+
+ // M is the maximal table size. 32 seems fast. E is the allowed increase
+ // of search steps when concatting to find an index. Lower values will
+ // decrease balancing, but will increase search steps.
+ var M = 32;
+ var E = 2;
+
+ // An empty array.
+ var empty = {
+ ctor: '_Array',
+ height: 0,
+ table: []
+ };
+
+
+ function get(i, array)
+ {
+ if (i < 0 || i >= length(array))
+ {
+ throw new Error(
+ 'Index ' + i + ' is out of range. Check the length of ' +
+ 'your array first or use getMaybe or getWithDefault.');
+ }
+ return unsafeGet(i, array);
+ }
+
+
+ function unsafeGet(i, array)
+ {
+ for (var x = array.height; x > 0; x--)
+ {
+ var slot = i >> (x * 5);
+ while (array.lengths[slot] <= i)
+ {
+ slot++;
+ }
+ if (slot > 0)
+ {
+ i -= array.lengths[slot - 1];
+ }
+ array = array.table[slot];
+ }
+ return array.table[i];
+ }
+
+
+ // Sets the value at the index i. Only the nodes leading to i will get
+ // copied and updated.
+ function set(i, item, array)
+ {
+ if (i < 0 || length(array) <= i)
+ {
+ return array;
+ }
+ return unsafeSet(i, item, array);
+ }
+
+
+ function unsafeSet(i, item, array)
+ {
+ array = nodeCopy(array);
+
+ if (array.height === 0)
+ {
+ array.table[i] = item;
+ }
+ else
+ {
+ var slot = getSlot(i, array);
+ if (slot > 0)
+ {
+ i -= array.lengths[slot - 1];
+ }
+ array.table[slot] = unsafeSet(i, item, array.table[slot]);
+ }
+ return array;
+ }
+
+
+ function initialize(len, f)
+ {
+ if (len <= 0)
+ {
+ return empty;
+ }
+ var h = Math.floor( Math.log(len) / Math.log(M) );
+ return initialize_(f, h, 0, len);
+ }
+
+ function initialize_(f, h, from, to)
+ {
+ if (h === 0)
+ {
+ var table = new Array((to - from) % (M + 1));
+ for (var i = 0; i < table.length; i++)
+ {
+ table[i] = f(from + i);
+ }
+ return {
+ ctor: '_Array',
+ height: 0,
+ table: table
+ };
+ }
+
+ var step = Math.pow(M, h);
+ var table = new Array(Math.ceil((to - from) / step));
+ var lengths = new Array(table.length);
+ for (var i = 0; i < table.length; i++)
+ {
+ table[i] = initialize_(f, h - 1, from + (i * step), Math.min(from + ((i + 1) * step), to));
+ lengths[i] = length(table[i]) + (i > 0 ? lengths[i-1] : 0);
+ }
+ return {
+ ctor: '_Array',
+ height: h,
+ table: table,
+ lengths: lengths
+ };
+ }
+
+ function fromList(list)
+ {
+ if (list === List.Nil)
+ {
+ return empty;
+ }
+
+ // Allocate M sized blocks (table) and write list elements to it.
+ var table = new Array(M);
+ var nodes = [];
+ var i = 0;
+
+ while (list.ctor !== '[]')
+ {
+ table[i] = list._0;
+ list = list._1;
+ i++;
+
+ // table is full, so we can push a leaf containing it into the
+ // next node.
+ if (i === M)
+ {
+ var leaf = {
+ ctor: '_Array',
+ height: 0,
+ table: table
+ };
+ fromListPush(leaf, nodes);
+ table = new Array(M);
+ i = 0;
+ }
+ }
+
+ // Maybe there is something left on the table.
+ if (i > 0)
+ {
+ var leaf = {
+ ctor: '_Array',
+ height: 0,
+ table: table.splice(0, i)
+ };
+ fromListPush(leaf, nodes);
+ }
+
+ // Go through all of the nodes and eventually push them into higher nodes.
+ for (var h = 0; h < nodes.length - 1; h++)
+ {
+ if (nodes[h].table.length > 0)
+ {
+ fromListPush(nodes[h], nodes);
+ }
+ }
+
+ var head = nodes[nodes.length - 1];
+ if (head.height > 0 && head.table.length === 1)
+ {
+ return head.table[0];
+ }
+ else
+ {
+ return head;
+ }
+ }
+
+ // Push a node into a higher node as a child.
+ function fromListPush(toPush, nodes)
+ {
+ var h = toPush.height;
+
+ // Maybe the node on this height does not exist.
+ if (nodes.length === h)
+ {
+ var node = {
+ ctor: '_Array',
+ height: h + 1,
+ table: [],
+ lengths: []
+ };
+ nodes.push(node);
+ }
+
+ nodes[h].table.push(toPush);
+ var len = length(toPush);
+ if (nodes[h].lengths.length > 0)
+ {
+ len += nodes[h].lengths[nodes[h].lengths.length - 1];
+ }
+ nodes[h].lengths.push(len);
+
+ if (nodes[h].table.length === M)
+ {
+ fromListPush(nodes[h], nodes);
+ nodes[h] = {
+ ctor: '_Array',
+ height: h + 1,
+ table: [],
+ lengths: []
+ };
+ }
+ }
+
+ // Pushes an item via push_ to the bottom right of a tree.
+ function push(item, a)
+ {
+ var pushed = push_(item, a);
+ if (pushed !== null)
+ {
+ return pushed;
+ }
+
+ var newTree = create(item, a.height);
+ return siblise(a, newTree);
+ }
+
+ // Recursively tries to push an item to the bottom-right most
+ // tree possible. If there is no space left for the item,
+ // null will be returned.
+ function push_(item, a)
+ {
+ // Handle resursion stop at leaf level.
+ if (a.height === 0)
+ {
+ if (a.table.length < M)
+ {
+ var newA = {
+ ctor: '_Array',
+ height: 0,
+ table: a.table.slice()
+ };
+ newA.table.push(item);
+ return newA;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ // Recursively push
+ var pushed = push_(item, botRight(a));
+
+ // There was space in the bottom right tree, so the slot will
+ // be updated.
+ if (pushed !== null)
+ {
+ var newA = nodeCopy(a);
+ newA.table[newA.table.length - 1] = pushed;
+ newA.lengths[newA.lengths.length - 1]++;
+ return newA;
+ }
+
+ // When there was no space left, check if there is space left
+ // for a new slot with a tree which contains only the item
+ // at the bottom.
+ if (a.table.length < M)
+ {
+ var newSlot = create(item, a.height - 1);
+ var newA = nodeCopy(a);
+ newA.table.push(newSlot);
+ newA.lengths.push(newA.lengths[newA.lengths.length - 1] + length(newSlot));
+ return newA;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ // Converts an array into a list of elements.
+ function toList(a)
+ {
+ return toList_(List.Nil, a);
+ }
+
+ function toList_(list, a)
+ {
+ for (var i = a.table.length - 1; i >= 0; i--)
+ {
+ list =
+ a.height === 0
+ ? List.Cons(a.table[i], list)
+ : toList_(list, a.table[i]);
+ }
+ return list;
+ }
+
+ // Maps a function over the elements of an array.
+ function map(f, a)
+ {
+ var newA = {
+ ctor: '_Array',
+ height: a.height,
+ table: new Array(a.table.length)
+ };
+ if (a.height > 0)
+ {
+ newA.lengths = a.lengths;
+ }
+ for (var i = 0; i < a.table.length; i++)
+ {
+ newA.table[i] =
+ a.height === 0
+ ? f(a.table[i])
+ : map(f, a.table[i]);
+ }
+ return newA;
+ }
+
+ // Maps a function over the elements with their index as first argument.
+ function indexedMap(f, a)
+ {
+ return indexedMap_(f, a, 0);
+ }
+
+ function indexedMap_(f, a, from)
+ {
+ var newA = {
+ ctor: '_Array',
+ height: a.height,
+ table: new Array(a.table.length)
+ };
+ if (a.height > 0)
+ {
+ newA.lengths = a.lengths;
+ }
+ for (var i = 0; i < a.table.length; i++)
+ {
+ newA.table[i] =
+ a.height === 0
+ ? A2(f, from + i, a.table[i])
+ : indexedMap_(f, a.table[i], i == 0 ? from : from + a.lengths[i - 1]);
+ }
+ return newA;
+ }
+
+ function foldl(f, b, a)
+ {
+ if (a.height === 0)
+ {
+ for (var i = 0; i < a.table.length; i++)
+ {
+ b = A2(f, a.table[i], b);
+ }
+ }
+ else
+ {
+ for (var i = 0; i < a.table.length; i++)
+ {
+ b = foldl(f, b, a.table[i]);
+ }
+ }
+ return b;
+ }
+
+ function foldr(f, b, a)
+ {
+ if (a.height === 0)
+ {
+ for (var i = a.table.length; i--; )
+ {
+ b = A2(f, a.table[i], b);
+ }
+ }
+ else
+ {
+ for (var i = a.table.length; i--; )
+ {
+ b = foldr(f, b, a.table[i]);
+ }
+ }
+ return b;
+ }
+
+ // TODO: currently, it slices the right, then the left. This can be
+ // optimized.
+ function slice(from, to, a)
+ {
+ if (from < 0)
+ {
+ from += length(a);
+ }
+ if (to < 0)
+ {
+ to += length(a);
+ }
+ return sliceLeft(from, sliceRight(to, a));
+ }
+
+ function sliceRight(to, a)
+ {
+ if (to === length(a))
+ {
+ return a;
+ }
+
+ // Handle leaf level.
+ if (a.height === 0)
+ {
+ var newA = { ctor:'_Array', height:0 };
+ newA.table = a.table.slice(0, to);
+ return newA;
+ }
+
+ // Slice the right recursively.
+ var right = getSlot(to, a);
+ var sliced = sliceRight(to - (right > 0 ? a.lengths[right - 1] : 0), a.table[right]);
+
+ // Maybe the a node is not even needed, as sliced contains the whole slice.
+ if (right === 0)
+ {
+ return sliced;
+ }
+
+ // Create new node.
+ var newA = {
+ ctor: '_Array',
+ height: a.height,
+ table: a.table.slice(0, right),
+ lengths: a.lengths.slice(0, right)
+ };
+ if (sliced.table.length > 0)
+ {
+ newA.table[right] = sliced;
+ newA.lengths[right] = length(sliced) + (right > 0 ? newA.lengths[right - 1] : 0);
+ }
+ return newA;
+ }
+
+ function sliceLeft(from, a)
+ {
+ if (from === 0)
+ {
+ return a;
+ }
+
+ // Handle leaf level.
+ if (a.height === 0)
+ {
+ var newA = { ctor:'_Array', height:0 };
+ newA.table = a.table.slice(from, a.table.length + 1);
+ return newA;
+ }
+
+ // Slice the left recursively.
+ var left = getSlot(from, a);
+ var sliced = sliceLeft(from - (left > 0 ? a.lengths[left - 1] : 0), a.table[left]);
+
+ // Maybe the a node is not even needed, as sliced contains the whole slice.
+ if (left === a.table.length - 1)
+ {
+ return sliced;
+ }
+
+ // Create new node.
+ var newA = {
+ ctor: '_Array',
+ height: a.height,
+ table: a.table.slice(left, a.table.length + 1),
+ lengths: new Array(a.table.length - left)
+ };
+ newA.table[0] = sliced;
+ var len = 0;
+ for (var i = 0; i < newA.table.length; i++)
+ {
+ len += length(newA.table[i]);
+ newA.lengths[i] = len;
+ }
+
+ return newA;
+ }
+
+ // Appends two trees.
+ function append(a,b)
+ {
+ if (a.table.length === 0)
+ {
+ return b;
+ }
+ if (b.table.length === 0)
+ {
+ return a;
+ }
+
+ var c = append_(a, b);
+
+ // Check if both nodes can be crunshed together.
+ if (c[0].table.length + c[1].table.length <= M)
+ {
+ if (c[0].table.length === 0)
+ {
+ return c[1];
+ }
+ if (c[1].table.length === 0)
+ {
+ return c[0];
+ }
+
+ // Adjust .table and .lengths
+ c[0].table = c[0].table.concat(c[1].table);
+ if (c[0].height > 0)
+ {
+ var len = length(c[0]);
+ for (var i = 0; i < c[1].lengths.length; i++)
+ {
+ c[1].lengths[i] += len;
+ }
+ c[0].lengths = c[0].lengths.concat(c[1].lengths);
+ }
+
+ return c[0];
+ }
+
+ if (c[0].height > 0)
+ {
+ var toRemove = calcToRemove(a, b);
+ if (toRemove > E)
+ {
+ c = shuffle(c[0], c[1], toRemove);
+ }
+ }
+
+ return siblise(c[0], c[1]);
+ }
+
+ // Returns an array of two nodes; right and left. One node _may_ be empty.
+ function append_(a, b)
+ {
+ if (a.height === 0 && b.height === 0)
+ {
+ return [a, b];
+ }
+
+ if (a.height !== 1 || b.height !== 1)
+ {
+ if (a.height === b.height)
+ {
+ a = nodeCopy(a);
+ b = nodeCopy(b);
+ var appended = append_(botRight(a), botLeft(b));
+
+ insertRight(a, appended[1]);
+ insertLeft(b, appended[0]);
+ }
+ else if (a.height > b.height)
+ {
+ a = nodeCopy(a);
+ var appended = append_(botRight(a), b);
+
+ insertRight(a, appended[0]);
+ b = parentise(appended[1], appended[1].height + 1);
+ }
+ else
+ {
+ b = nodeCopy(b);
+ var appended = append_(a, botLeft(b));
+
+ var left = appended[0].table.length === 0 ? 0 : 1;
+ var right = left === 0 ? 1 : 0;
+ insertLeft(b, appended[left]);
+ a = parentise(appended[right], appended[right].height + 1);
+ }
+ }
+
+ // Check if balancing is needed and return based on that.
+ if (a.table.length === 0 || b.table.length === 0)
+ {
+ return [a, b];
+ }
+
+ var toRemove = calcToRemove(a, b);
+ if (toRemove <= E)
+ {
+ return [a, b];
+ }
+ return shuffle(a, b, toRemove);
+ }
+
+ // Helperfunctions for append_. Replaces a child node at the side of the parent.
+ function insertRight(parent, node)
+ {
+ var index = parent.table.length - 1;
+ parent.table[index] = node;
+ parent.lengths[index] = length(node);
+ parent.lengths[index] += index > 0 ? parent.lengths[index - 1] : 0;
+ }
+
+ function insertLeft(parent, node)
+ {
+ if (node.table.length > 0)
+ {
+ parent.table[0] = node;
+ parent.lengths[0] = length(node);
+
+ var len = length(parent.table[0]);
+ for (var i = 1; i < parent.lengths.length; i++)
+ {
+ len += length(parent.table[i]);
+ parent.lengths[i] = len;
+ }
+ }
+ else
+ {
+ parent.table.shift();
+ for (var i = 1; i < parent.lengths.length; i++)
+ {
+ parent.lengths[i] = parent.lengths[i] - parent.lengths[0];
+ }
+ parent.lengths.shift();
+ }
+ }
+
+ // Returns the extra search steps for E. Refer to the paper.
+ function calcToRemove(a, b)
+ {
+ var subLengths = 0;
+ for (var i = 0; i < a.table.length; i++)
+ {
+ subLengths += a.table[i].table.length;
+ }
+ for (var i = 0; i < b.table.length; i++)
+ {
+ subLengths += b.table[i].table.length;
+ }
+
+ var toRemove = a.table.length + b.table.length;
+ return toRemove - (Math.floor((subLengths - 1) / M) + 1);
+ }
+
+ // get2, set2 and saveSlot are helpers for accessing elements over two arrays.
+ function get2(a, b, index)
+ {
+ return index < a.length
+ ? a[index]
+ : b[index - a.length];
+ }
+
+ function set2(a, b, index, value)
+ {
+ if (index < a.length)
+ {
+ a[index] = value;
+ }
+ else
+ {
+ b[index - a.length] = value;
+ }
+ }
+
+ function saveSlot(a, b, index, slot)
+ {
+ set2(a.table, b.table, index, slot);
+
+ var l = (index === 0 || index === a.lengths.length)
+ ? 0
+ : get2(a.lengths, a.lengths, index - 1);
+
+ set2(a.lengths, b.lengths, index, l + length(slot));
+ }
+
+ // Creates a node or leaf with a given length at their arrays for perfomance.
+ // Is only used by shuffle.
+ function createNode(h, length)
+ {
+ if (length < 0)
+ {
+ length = 0;
+ }
+ var a = {
+ ctor: '_Array',
+ height: h,
+ table: new Array(length)
+ };
+ if (h > 0)
+ {
+ a.lengths = new Array(length);
+ }
+ return a;
+ }
+
+ // Returns an array of two balanced nodes.
+ function shuffle(a, b, toRemove)
+ {
+ var newA = createNode(a.height, Math.min(M, a.table.length + b.table.length - toRemove));
+ var newB = createNode(a.height, newA.table.length - (a.table.length + b.table.length - toRemove));
+
+ // Skip the slots with size M. More precise: copy the slot references
+ // to the new node
+ var read = 0;
+ while (get2(a.table, b.table, read).table.length % M === 0)
+ {
+ set2(newA.table, newB.table, read, get2(a.table, b.table, read));
+ set2(newA.lengths, newB.lengths, read, get2(a.lengths, b.lengths, read));
+ read++;
+ }
+
+ // Pulling items from left to right, caching in a slot before writing
+ // it into the new nodes.
+ var write = read;
+ var slot = new createNode(a.height - 1, 0);
+ var from = 0;
+
+ // If the current slot is still containing data, then there will be at
+ // least one more write, so we do not break this loop yet.
+ while (read - write - (slot.table.length > 0 ? 1 : 0) < toRemove)
+ {
+ // Find out the max possible items for copying.
+ var source = get2(a.table, b.table, read);
+ var to = Math.min(M - slot.table.length, source.table.length);
+
+ // Copy and adjust size table.
+ slot.table = slot.table.concat(source.table.slice(from, to));
+ if (slot.height > 0)
+ {
+ var len = slot.lengths.length;
+ for (var i = len; i < len + to - from; i++)
+ {
+ slot.lengths[i] = length(slot.table[i]);
+ slot.lengths[i] += (i > 0 ? slot.lengths[i - 1] : 0);
+ }
+ }
+
+ from += to;
+
+ // Only proceed to next slots[i] if the current one was
+ // fully copied.
+ if (source.table.length <= to)
+ {
+ read++; from = 0;
+ }
+
+ // Only create a new slot if the current one is filled up.
+ if (slot.table.length === M)
+ {
+ saveSlot(newA, newB, write, slot);
+ slot = createNode(a.height - 1, 0);
+ write++;
+ }
+ }
+
+ // Cleanup after the loop. Copy the last slot into the new nodes.
+ if (slot.table.length > 0)
+ {
+ saveSlot(newA, newB, write, slot);
+ write++;
+ }
+
+ // Shift the untouched slots to the left
+ while (read < a.table.length + b.table.length )
+ {
+ saveSlot(newA, newB, write, get2(a.table, b.table, read));
+ read++;
+ write++;
+ }
+
+ return [newA, newB];
+ }
+
+ // Navigation functions
+ function botRight(a)
+ {
+ return a.table[a.table.length - 1];
+ }
+ function botLeft(a)
+ {
+ return a.table[0];
+ }
+
+ // Copies a node for updating. Note that you should not use this if
+ // only updating only one of "table" or "lengths" for performance reasons.
+ function nodeCopy(a)
+ {
+ var newA = {
+ ctor: '_Array',
+ height: a.height,
+ table: a.table.slice()
+ };
+ if (a.height > 0)
+ {
+ newA.lengths = a.lengths.slice();
+ }
+ return newA;
+ }
+
+ // Returns how many items are in the tree.
+ function length(array)
+ {
+ if (array.height === 0)
+ {
+ return array.table.length;
+ }
+ else
+ {
+ return array.lengths[array.lengths.length - 1];
+ }
+ }
+
+ // Calculates in which slot of "table" the item probably is, then
+ // find the exact slot via forward searching in "lengths". Returns the index.
+ function getSlot(i, a)
+ {
+ var slot = i >> (5 * a.height);
+ while (a.lengths[slot] <= i)
+ {
+ slot++;
+ }
+ return slot;
+ }
+
+ // Recursively creates a tree with a given height containing
+ // only the given item.
+ function create(item, h)
+ {
+ if (h === 0)
+ {
+ return {
+ ctor: '_Array',
+ height: 0,
+ table: [item]
+ };
+ }
+ return {
+ ctor: '_Array',
+ height: h,
+ table: [create(item, h - 1)],
+ lengths: [1]
+ };
+ }
+
+ // Recursively creates a tree that contains the given tree.
+ function parentise(tree, h)
+ {
+ if (h === tree.height)
+ {
+ return tree;
+ }
+
+ return {
+ ctor: '_Array',
+ height: h,
+ table: [parentise(tree, h - 1)],
+ lengths: [length(tree)]
+ };
+ }
+
+ // Emphasizes blood brotherhood beneath two trees.
+ function siblise(a, b)
+ {
+ return {
+ ctor: '_Array',
+ height: a.height + 1,
+ table: [a, b],
+ lengths: [length(a), length(a) + length(b)]
+ };
+ }
+
+ function toJSArray(a)
+ {
+ var jsArray = new Array(length(a));
+ toJSArray_(jsArray, 0, a);
+ return jsArray;
+ }
+
+ function toJSArray_(jsArray, i, a)
+ {
+ for (var t = 0; t < a.table.length; t++)
+ {
+ if (a.height === 0)
+ {
+ jsArray[i + t] = a.table[t];
+ }
+ else
+ {
+ var inc = t === 0 ? 0 : a.lengths[t - 1];
+ toJSArray_(jsArray, i + inc, a.table[t]);
+ }
+ }
+ }
+
+ function fromJSArray(jsArray)
+ {
+ if (jsArray.length === 0)
+ {
+ return empty;
+ }
+ var h = Math.floor(Math.log(jsArray.length) / Math.log(M));
+ return fromJSArray_(jsArray, h, 0, jsArray.length);
+ }
+
+ function fromJSArray_(jsArray, h, from, to)
+ {
+ if (h === 0)
+ {
+ return {
+ ctor: '_Array',
+ height: 0,
+ table: jsArray.slice(from, to)
+ };
+ }
+
+ var step = Math.pow(M, h);
+ var table = new Array(Math.ceil((to - from) / step));
+ var lengths = new Array(table.length);
+ for (var i = 0; i < table.length; i++)
+ {
+ table[i] = fromJSArray_(jsArray, h - 1, from + (i * step), Math.min(from + ((i + 1) * step), to));
+ lengths[i] = length(table[i]) + (i > 0 ? lengths[i - 1] : 0);
+ }
+ return {
+ ctor: '_Array',
+ height: h,
+ table: table,
+ lengths: lengths
+ };
+ }
+
+ Elm.Native.Array.values = {
+ empty: empty,
+ fromList: fromList,
+ toList: toList,
+ initialize: F2(initialize),
+ append: F2(append),
+ push: F2(push),
+ slice: F3(slice),
+ get: F2(get),
+ set: F3(set),
+ map: F2(map),
+ indexedMap: F2(indexedMap),
+ foldl: F3(foldl),
+ foldr: F3(foldr),
+ length: length,
+
+ toJSArray: toJSArray,
+ fromJSArray: fromJSArray
+ };
+
+ return localRuntime.Native.Array.values = Elm.Native.Array.values;
+};
+
+Elm.Native.Basics = {};
+Elm.Native.Basics.make = function(localRuntime) {
+ localRuntime.Native = localRuntime.Native || {};
+ localRuntime.Native.Basics = localRuntime.Native.Basics || {};
+ if (localRuntime.Native.Basics.values)
+ {
+ return localRuntime.Native.Basics.values;
+ }
+
+ var Utils = Elm.Native.Utils.make(localRuntime);
+
+ function div(a, b)
+ {
+ return (a / b) | 0;
+ }
+ function rem(a, b)
+ {
+ return a % b;
+ }
+ function mod(a, b)
+ {
+ if (b === 0)
+ {
+ throw new Error('Cannot perform mod 0. Division by zero error.');
+ }
+ var r = a % b;
+ var m = a === 0 ? 0 : (b > 0 ? (a >= 0 ? r : r + b) : -mod(-a, -b));
+
+ return m === b ? 0 : m;
+ }
+ function logBase(base, n)
+ {
+ return Math.log(n) / Math.log(base);
+ }
+ function negate(n)
+ {
+ return -n;
+ }
+ function abs(n)
+ {
+ return n < 0 ? -n : n;
+ }
+
+ function min(a, b)
+ {
+ return Utils.cmp(a, b) < 0 ? a : b;
+ }
+ function max(a, b)
+ {
+ return Utils.cmp(a, b) > 0 ? a : b;
+ }
+ function clamp(lo, hi, n)
+ {
+ return Utils.cmp(n, lo) < 0 ? lo : Utils.cmp(n, hi) > 0 ? hi : n;
+ }
+
+ function xor(a, b)
+ {
+ return a !== b;
+ }
+ function not(b)
+ {
+ return !b;
+ }
+ function isInfinite(n)
+ {
+ return n === Infinity || n === -Infinity;
+ }
+
+ function truncate(n)
+ {
+ return n | 0;
+ }
+
+ function degrees(d)
+ {
+ return d * Math.PI / 180;
+ }
+ function turns(t)
+ {
+ return 2 * Math.PI * t;
+ }
+ function fromPolar(point)
+ {
+ var r = point._0;
+ var t = point._1;
+ return Utils.Tuple2(r * Math.cos(t), r * Math.sin(t));
+ }
+ function toPolar(point)
+ {
+ var x = point._0;
+ var y = point._1;
+ return Utils.Tuple2(Math.sqrt(x * x + y * y), Math.atan2(y, x));
+ }
+
+ return localRuntime.Native.Basics.values = {
+ div: F2(div),
+ rem: F2(rem),
+ mod: F2(mod),
+
+ pi: Math.PI,
+ e: Math.E,
+ cos: Math.cos,
+ sin: Math.sin,
+ tan: Math.tan,
+ acos: Math.acos,
+ asin: Math.asin,
+ atan: Math.atan,
+ atan2: F2(Math.atan2),
+
+ degrees: degrees,
+ turns: turns,
+ fromPolar: fromPolar,
+ toPolar: toPolar,
+
+ sqrt: Math.sqrt,
+ logBase: F2(logBase),
+ negate: negate,
+ abs: abs,
+ min: F2(min),
+ max: F2(max),
+ clamp: F3(clamp),
+ compare: Utils.compare,
+
+ xor: F2(xor),
+ not: not,
+
+ truncate: truncate,
+ ceiling: Math.ceil,
+ floor: Math.floor,
+ round: Math.round,
+ toFloat: function(x) { return x; },
+ isNaN: isNaN,
+ isInfinite: isInfinite
+ };
+};
+
+Elm.Native.Port = {};
+
+Elm.Native.Port.make = function(localRuntime) {
+ localRuntime.Native = localRuntime.Native || {};
+ localRuntime.Native.Port = localRuntime.Native.Port || {};
+ if (localRuntime.Native.Port.values)
+ {
+ return localRuntime.Native.Port.values;
+ }
+
+ var NS;
+
+ // INBOUND
+
+ function inbound(name, type, converter)
+ {
+ if (!localRuntime.argsTracker[name])
+ {
+ throw new Error(
+ 'Port Error:\n' +
+ 'No argument was given for the port named \'' + name + '\' with type:\n\n' +
+ ' ' + type.split('\n').join('\n ') + '\n\n' +
+ 'You need to provide an initial value!\n\n' +
+ 'Find out more about ports here '
+ );
+ }
+ var arg = localRuntime.argsTracker[name];
+ arg.used = true;
+
+ return jsToElm(name, type, converter, arg.value);
+ }
+
+
+ function inboundSignal(name, type, converter)
+ {
+ var initialValue = inbound(name, type, converter);
+
+ if (!NS)
+ {
+ NS = Elm.Native.Signal.make(localRuntime);
+ }
+ var signal = NS.input('inbound-port-' + name, initialValue);
+
+ function send(jsValue)
+ {
+ var elmValue = jsToElm(name, type, converter, jsValue);
+ setTimeout(function() {
+ localRuntime.notify(signal.id, elmValue);
+ }, 0);
+ }
+
+ localRuntime.ports[name] = { send: send };
+
+ return signal;
+ }
+
+
+ function jsToElm(name, type, converter, value)
+ {
+ try
+ {
+ return converter(value);
+ }
+ catch(e)
+ {
+ throw new Error(
+ 'Port Error:\n' +
+ 'Regarding the port named \'' + name + '\' with type:\n\n' +
+ ' ' + type.split('\n').join('\n ') + '\n\n' +
+ 'You just sent the value:\n\n' +
+ ' ' + JSON.stringify(value) + '\n\n' +
+ 'but it cannot be converted to the necessary type.\n' +
+ e.message
+ );
+ }
+ }
+
+
+ // OUTBOUND
+
+ function outbound(name, converter, elmValue)
+ {
+ localRuntime.ports[name] = converter(elmValue);
+ }
+
+
+ function outboundSignal(name, converter, signal)
+ {
+ var subscribers = [];
+
+ function subscribe(handler)
+ {
+ subscribers.push(handler);
+ }
+ function unsubscribe(handler)
+ {
+ subscribers.pop(subscribers.indexOf(handler));
+ }
+
+ function notify(elmValue)
+ {
+ var jsValue = converter(elmValue);
+ var len = subscribers.length;
+ for (var i = 0; i < len; ++i)
+ {
+ subscribers[i](jsValue);
+ }
+ }
+
+ if (!NS)
+ {
+ NS = Elm.Native.Signal.make(localRuntime);
+ }
+ NS.output('outbound-port-' + name, notify, signal);
+
+ localRuntime.ports[name] = {
+ subscribe: subscribe,
+ unsubscribe: unsubscribe
+ };
+
+ return signal;
+ }
+
+
+ return localRuntime.Native.Port.values = {
+ inbound: inbound,
+ outbound: outbound,
+ inboundSignal: inboundSignal,
+ outboundSignal: outboundSignal
+ };
+};
+
+if (!Elm.fullscreen) {
+ (function() {
+ 'use strict';
+
+ var Display = {
+ FULLSCREEN: 0,
+ COMPONENT: 1,
+ NONE: 2
+ };
+
+ Elm.fullscreen = function(module, args)
+ {
+ var container = document.createElement('div');
+ document.body.appendChild(container);
+ return init(Display.FULLSCREEN, container, module, args || {});
+ };
+
+ Elm.embed = function(module, container, args)
+ {
+ var tag = container.tagName;
+ if (tag !== 'DIV')
+ {
+ throw new Error('Elm.node must be given a DIV, not a ' + tag + '.');
+ }
+ return init(Display.COMPONENT, container, module, args || {});
+ };
+
+ Elm.worker = function(module, args)
+ {
+ return init(Display.NONE, {}, module, args || {});
+ };
+
+ function init(display, container, module, args, moduleToReplace)
+ {
+ // defining state needed for an instance of the Elm RTS
+ var inputs = [];
+
+ /* OFFSET
+ * Elm's time traveling debugger lets you pause time. This means
+ * "now" may be shifted a bit into the past. By wrapping Date.now()
+ * we can manage this.
+ */
+ var timer = {
+ programStart: Date.now(),
+ now: function()
+ {
+ return Date.now();
+ }
+ };
+
+ var updateInProgress = false;
+ function notify(id, v)
+ {
+ if (updateInProgress)
+ {
+ throw new Error(
+ 'The notify function has been called synchronously!\n' +
+ 'This can lead to frames being dropped.\n' +
+ 'Definitely report this to \n');
+ }
+ updateInProgress = true;
+ var timestep = timer.now();
+ for (var i = inputs.length; i--; )
+ {
+ inputs[i].notify(timestep, id, v);
+ }
+ updateInProgress = false;
+ }
+ function setTimeout(func, delay)
+ {
+ return window.setTimeout(func, delay);
+ }
+
+ var listeners = [];
+ function addListener(relevantInputs, domNode, eventName, func)
+ {
+ domNode.addEventListener(eventName, func);
+ var listener = {
+ relevantInputs: relevantInputs,
+ domNode: domNode,
+ eventName: eventName,
+ func: func
+ };
+ listeners.push(listener);
+ }
+
+ var argsTracker = {};
+ for (var name in args)
+ {
+ argsTracker[name] = {
+ value: args[name],
+ used: false
+ };
+ }
+
+ // create the actual RTS. Any impure modules will attach themselves to this
+ // object. This permits many Elm programs to be embedded per document.
+ var elm = {
+ notify: notify,
+ setTimeout: setTimeout,
+ node: container,
+ addListener: addListener,
+ inputs: inputs,
+ timer: timer,
+ argsTracker: argsTracker,
+ ports: {},
+
+ isFullscreen: function() { return display === Display.FULLSCREEN; },
+ isEmbed: function() { return display === Display.COMPONENT; },
+ isWorker: function() { return display === Display.NONE; }
+ };
+
+ function swap(newModule)
+ {
+ removeListeners(listeners);
+ var div = document.createElement('div');
+ var newElm = init(display, div, newModule, args, elm);
+ inputs = [];
+
+ return newElm;
+ }
+
+ function dispose()
+ {
+ removeListeners(listeners);
+ inputs = [];
+ }
+
+ var Module = {};
+ try
+ {
+ Module = module.make(elm);
+ checkInputs(elm);
+ }
+ catch (error)
+ {
+ if (typeof container.appendChild === "function")
+ {
+ container.appendChild(errorNode(error.message));
+ }
+ else
+ {
+ console.error(error.message);
+ }
+ throw error;
+ }
+
+ if (display !== Display.NONE)
+ {
+ var graphicsNode = initGraphics(elm, Module);
+ }
+
+ var rootNode = { kids: inputs };
+ trimDeadNodes(rootNode);
+ inputs = rootNode.kids;
+ filterListeners(inputs, listeners);
+
+ addReceivers(elm.ports);
+
+ if (typeof moduleToReplace !== 'undefined')
+ {
+ hotSwap(moduleToReplace, elm);
+
+ // rerender scene if graphics are enabled.
+ if (typeof graphicsNode !== 'undefined')
+ {
+ graphicsNode.notify(0, true, 0);
+ }
+ }
+
+ return {
+ swap: swap,
+ ports: elm.ports,
+ dispose: dispose
+ };
+ }
+
+ function checkInputs(elm)
+ {
+ var argsTracker = elm.argsTracker;
+ for (var name in argsTracker)
+ {
+ if (!argsTracker[name].used)
+ {
+ throw new Error(
+ "Port Error:\nYou provided an argument named '" + name +
+ "' but there is no corresponding port!\n\n" +
+ "Maybe add a port '" + name + "' to your Elm module?\n" +
+ "Maybe remove the '" + name + "' argument from your initialization code in JS?"
+ );
+ }
+ }
+ }
+
+ function errorNode(message)
+ {
+ var code = document.createElement('code');
+
+ var lines = message.split('\n');
+ code.appendChild(document.createTextNode(lines[0]));
+ code.appendChild(document.createElement('br'));
+ code.appendChild(document.createElement('br'));
+ for (var i = 1; i < lines.length; ++i)
+ {
+ code.appendChild(document.createTextNode('\u00A0 \u00A0 ' + lines[i].replace(/ /g, '\u00A0 ')));
+ code.appendChild(document.createElement('br'));
+ }
+ code.appendChild(document.createElement('br'));
+ code.appendChild(document.createTextNode('Open the developer console for more details.'));
+ return code;
+ }
+
+
+ //// FILTER SIGNALS ////
+
+ // TODO: move this code into the signal module and create a function
+ // Signal.initializeGraph that actually instantiates everything.
+
+ function filterListeners(inputs, listeners)
+ {
+ loop:
+ for (var i = listeners.length; i--; )
+ {
+ var listener = listeners[i];
+ for (var j = inputs.length; j--; )
+ {
+ if (listener.relevantInputs.indexOf(inputs[j].id) >= 0)
+ {
+ continue loop;
+ }
+ }
+ listener.domNode.removeEventListener(listener.eventName, listener.func);
+ }
+ }
+
+ function removeListeners(listeners)
+ {
+ for (var i = listeners.length; i--; )
+ {
+ var listener = listeners[i];
+ listener.domNode.removeEventListener(listener.eventName, listener.func);
+ }
+ }
+
+ // add receivers for built-in ports if they are defined
+ function addReceivers(ports)
+ {
+ if ('title' in ports)
+ {
+ if (typeof ports.title === 'string')
+ {
+ document.title = ports.title;
+ }
+ else
+ {
+ ports.title.subscribe(function(v) { document.title = v; });
+ }
+ }
+ if ('redirect' in ports)
+ {
+ ports.redirect.subscribe(function(v) {
+ if (v.length > 0)
+ {
+ window.location = v;
+ }
+ });
+ }
+ }
+
+
+ // returns a boolean representing whether the node is alive or not.
+ function trimDeadNodes(node)
+ {
+ if (node.isOutput)
+ {
+ return true;
+ }
+
+ var liveKids = [];
+ for (var i = node.kids.length; i--; )
+ {
+ var kid = node.kids[i];
+ if (trimDeadNodes(kid))
+ {
+ liveKids.push(kid);
+ }
+ }
+ node.kids = liveKids;
+
+ return liveKids.length > 0;
+ }
+
+
+ //// RENDERING ////
+
+ function initGraphics(elm, Module)
+ {
+ if (!('main' in Module))
+ {
+ throw new Error("'main' is missing! What do I display?!");
+ }
+
+ var signalGraph = Module.main;
+
+ // make sure the signal graph is actually a signal & extract the visual model
+ if (!('notify' in signalGraph))
+ {
+ signalGraph = Elm.Signal.make(elm).constant(signalGraph);
+ }
+ var initialScene = signalGraph.value;
+
+ // Figure out what the render functions should be
+ var render;
+ var update;
+ if (initialScene.ctor === 'Element_elm_builtin')
+ {
+ var Element = Elm.Native.Graphics.Element.make(elm);
+ render = Element.render;
+ update = Element.updateAndReplace;
+ }
+ else
+ {
+ var VirtualDom = Elm.Native.VirtualDom.make(elm);
+ render = VirtualDom.render;
+ update = VirtualDom.updateAndReplace;
+ }
+
+ // Add the initialScene to the DOM
+ var container = elm.node;
+ var node = render(initialScene);
+ while (container.firstChild)
+ {
+ container.removeChild(container.firstChild);
+ }
+ container.appendChild(node);
+
+ var _requestAnimationFrame =
+ typeof requestAnimationFrame !== 'undefined'
+ ? requestAnimationFrame
+ : function(cb) { setTimeout(cb, 1000 / 60); }
+ ;
+
+ // domUpdate is called whenever the main Signal changes.
+ //
+ // domUpdate and drawCallback implement a small state machine in order
+ // to schedule only 1 draw per animation frame. This enforces that
+ // once draw has been called, it will not be called again until the
+ // next frame.
+ //
+ // drawCallback is scheduled whenever
+ // 1. The state transitions from PENDING_REQUEST to EXTRA_REQUEST, or
+ // 2. The state transitions from NO_REQUEST to PENDING_REQUEST
+ //
+ // Invariants:
+ // 1. In the NO_REQUEST state, there is never a scheduled drawCallback.
+ // 2. In the PENDING_REQUEST and EXTRA_REQUEST states, there is always exactly 1
+ // scheduled drawCallback.
+ var NO_REQUEST = 0;
+ var PENDING_REQUEST = 1;
+ var EXTRA_REQUEST = 2;
+ var state = NO_REQUEST;
+ var savedScene = initialScene;
+ var scheduledScene = initialScene;
+
+ function domUpdate(newScene)
+ {
+ scheduledScene = newScene;
+
+ update(elm.node.firstChild, savedScene, scheduledScene);
+ if (elm.Native.Window)
+ {
+ elm.Native.Window.values.resizeIfNeeded();
+ }
+ savedScene = scheduledScene;
+ }
+
+ var renderer = Elm.Native.Signal.make(elm).output('main', domUpdate, signalGraph);
+
+ // must check for resize after 'renderer' is created so
+ // that changes show up.
+ if (elm.Native.Window)
+ {
+ elm.Native.Window.values.resizeIfNeeded();
+ }
+
+ return renderer;
+ }
+
+ //// HOT SWAPPING ////
+
+ // Returns boolean indicating if the swap was successful.
+ // Requires that the two signal graphs have exactly the same
+ // structure.
+ function hotSwap(from, to)
+ {
+ function similar(nodeOld, nodeNew)
+ {
+ if (nodeOld.id !== nodeNew.id)
+ {
+ return false;
+ }
+ if (nodeOld.isOutput)
+ {
+ return nodeNew.isOutput;
+ }
+ return nodeOld.kids.length === nodeNew.kids.length;
+ }
+ function swap(nodeOld, nodeNew)
+ {
+ nodeNew.value = nodeOld.value;
+ return true;
+ }
+ var canSwap = depthFirstTraversals(similar, from.inputs, to.inputs);
+ if (canSwap)
+ {
+ depthFirstTraversals(swap, from.inputs, to.inputs);
+ }
+ from.node.parentNode.replaceChild(to.node, from.node);
+
+ return canSwap;
+ }
+
+ // Returns false if the node operation f ever fails.
+ function depthFirstTraversals(f, queueOld, queueNew)
+ {
+ if (queueOld.length !== queueNew.length)
+ {
+ return false;
+ }
+ queueOld = queueOld.slice(0);
+ queueNew = queueNew.slice(0);
+
+ var seen = [];
+ while (queueOld.length > 0 && queueNew.length > 0)
+ {
+ var nodeOld = queueOld.pop();
+ var nodeNew = queueNew.pop();
+ if (seen.indexOf(nodeOld.id) < 0)
+ {
+ if (!f(nodeOld, nodeNew))
+ {
+ return false;
+ }
+ queueOld = queueOld.concat(nodeOld.kids || []);
+ queueNew = queueNew.concat(nodeNew.kids || []);
+ seen.push(nodeOld.id);
+ }
+ }
+ return true;
+ }
+ }());
+
+ function F2(fun)
+ {
+ function wrapper(a) { return function(b) { return fun(a,b); }; }
+ wrapper.arity = 2;
+ wrapper.func = fun;
+ return wrapper;
+ }
+
+ function F3(fun)
+ {
+ function wrapper(a) {
+ return function(b) { return function(c) { return fun(a, b, c); }; };
+ }
+ wrapper.arity = 3;
+ wrapper.func = fun;
+ return wrapper;
+ }
+
+ function F4(fun)
+ {
+ function wrapper(a) { return function(b) { return function(c) {
+ return function(d) { return fun(a, b, c, d); }; }; };
+ }
+ wrapper.arity = 4;
+ wrapper.func = fun;
+ return wrapper;
+ }
+
+ function F5(fun)
+ {
+ function wrapper(a) { return function(b) { return function(c) {
+ return function(d) { return function(e) { return fun(a, b, c, d, e); }; }; }; };
+ }
+ wrapper.arity = 5;
+ wrapper.func = fun;
+ return wrapper;
+ }
+
+ function F6(fun)
+ {
+ function wrapper(a) { return function(b) { return function(c) {
+ return function(d) { return function(e) { return function(f) {
+ return fun(a, b, c, d, e, f); }; }; }; }; };
+ }
+ wrapper.arity = 6;
+ wrapper.func = fun;
+ return wrapper;
+ }
+
+ function F7(fun)
+ {
+ function wrapper(a) { return function(b) { return function(c) {
+ return function(d) { return function(e) { return function(f) {
+ return function(g) { return fun(a, b, c, d, e, f, g); }; }; }; }; }; };
+ }
+ wrapper.arity = 7;
+ wrapper.func = fun;
+ return wrapper;
+ }
+
+ function F8(fun)
+ {
+ function wrapper(a) { return function(b) { return function(c) {
+ return function(d) { return function(e) { return function(f) {
+ return function(g) { return function(h) {
+ return fun(a, b, c, d, e, f, g, h); }; }; }; }; }; }; };
+ }
+ wrapper.arity = 8;
+ wrapper.func = fun;
+ return wrapper;
+ }
+
+ function F9(fun)
+ {
+ function wrapper(a) { return function(b) { return function(c) {
+ return function(d) { return function(e) { return function(f) {
+ return function(g) { return function(h) { return function(i) {
+ return fun(a, b, c, d, e, f, g, h, i); }; }; }; }; }; }; }; };
+ }
+ wrapper.arity = 9;
+ wrapper.func = fun;
+ return wrapper;
+ }
+
+ function A2(fun, a, b)
+ {
+ return fun.arity === 2
+ ? fun.func(a, b)
+ : fun(a)(b);
+ }
+ function A3(fun, a, b, c)
+ {
+ return fun.arity === 3
+ ? fun.func(a, b, c)
+ : fun(a)(b)(c);
+ }
+ function A4(fun, a, b, c, d)
+ {
+ return fun.arity === 4
+ ? fun.func(a, b, c, d)
+ : fun(a)(b)(c)(d);
+ }
+ function A5(fun, a, b, c, d, e)
+ {
+ return fun.arity === 5
+ ? fun.func(a, b, c, d, e)
+ : fun(a)(b)(c)(d)(e);
+ }
+ function A6(fun, a, b, c, d, e, f)
+ {
+ return fun.arity === 6
+ ? fun.func(a, b, c, d, e, f)
+ : fun(a)(b)(c)(d)(e)(f);
+ }
+ function A7(fun, a, b, c, d, e, f, g)
+ {
+ return fun.arity === 7
+ ? fun.func(a, b, c, d, e, f, g)
+ : fun(a)(b)(c)(d)(e)(f)(g);
+ }
+ function A8(fun, a, b, c, d, e, f, g, h)
+ {
+ return fun.arity === 8
+ ? fun.func(a, b, c, d, e, f, g, h)
+ : fun(a)(b)(c)(d)(e)(f)(g)(h);
+ }
+ function A9(fun, a, b, c, d, e, f, g, h, i)
+ {
+ return fun.arity === 9
+ ? fun.func(a, b, c, d, e, f, g, h, i)
+ : fun(a)(b)(c)(d)(e)(f)(g)(h)(i);
+ }
+}
+
+Elm.Native = Elm.Native || {};
+Elm.Native.Utils = {};
+Elm.Native.Utils.make = function(localRuntime) {
+ localRuntime.Native = localRuntime.Native || {};
+ localRuntime.Native.Utils = localRuntime.Native.Utils || {};
+ if (localRuntime.Native.Utils.values)
+ {
+ return localRuntime.Native.Utils.values;
+ }
+
+
+ // COMPARISONS
+
+ function eq(l, r)
+ {
+ var stack = [{'x': l, 'y': r}];
+ while (stack.length > 0)
+ {
+ var front = stack.pop();
+ var x = front.x;
+ var y = front.y;
+ if (x === y)
+ {
+ continue;
+ }
+ if (typeof x === 'object')
+ {
+ var c = 0;
+ for (var i in x)
+ {
+ ++c;
+ if (i in y)
+ {
+ if (i !== 'ctor')
+ {
+ stack.push({ 'x': x[i], 'y': y[i] });
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+ if ('ctor' in x)
+ {
+ stack.push({'x': x.ctor, 'y': y.ctor});
+ }
+ if (c !== Object.keys(y).length)
+ {
+ return false;
+ }
+ }
+ else if (typeof x === 'function')
+ {
+ throw new Error('Equality error: general function equality is ' +
+ 'undecidable, and therefore, unsupported');
+ }
+ else
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // code in Generate/JavaScript.hs depends on the particular
+ // integer values assigned to LT, EQ, and GT
+ var LT = -1, EQ = 0, GT = 1, ord = ['LT', 'EQ', 'GT'];
+
+ function compare(x, y)
+ {
+ return {
+ ctor: ord[cmp(x, y) + 1]
+ };
+ }
+
+ function cmp(x, y) {
+ var ord;
+ if (typeof x !== 'object')
+ {
+ return x === y ? EQ : x < y ? LT : GT;
+ }
+ else if (x.isChar)
+ {
+ var a = x.toString();
+ var b = y.toString();
+ return a === b
+ ? EQ
+ : a < b
+ ? LT
+ : GT;
+ }
+ else if (x.ctor === '::' || x.ctor === '[]')
+ {
+ while (true)
+ {
+ if (x.ctor === '[]' && y.ctor === '[]')
+ {
+ return EQ;
+ }
+ if (x.ctor !== y.ctor)
+ {
+ return x.ctor === '[]' ? LT : GT;
+ }
+ ord = cmp(x._0, y._0);
+ if (ord !== EQ)
+ {
+ return ord;
+ }
+ x = x._1;
+ y = y._1;
+ }
+ }
+ else if (x.ctor.slice(0, 6) === '_Tuple')
+ {
+ var n = x.ctor.slice(6) - 0;
+ var err = 'cannot compare tuples with more than 6 elements.';
+ if (n === 0) return EQ;
+ if (n >= 1) { ord = cmp(x._0, y._0); if (ord !== EQ) return ord;
+ if (n >= 2) { ord = cmp(x._1, y._1); if (ord !== EQ) return ord;
+ if (n >= 3) { ord = cmp(x._2, y._2); if (ord !== EQ) return ord;
+ if (n >= 4) { ord = cmp(x._3, y._3); if (ord !== EQ) return ord;
+ if (n >= 5) { ord = cmp(x._4, y._4); if (ord !== EQ) return ord;
+ if (n >= 6) { ord = cmp(x._5, y._5); if (ord !== EQ) return ord;
+ if (n >= 7) throw new Error('Comparison error: ' + err); } } } } } }
+ return EQ;
+ }
+ else
+ {
+ throw new Error('Comparison error: comparison is only defined on ints, ' +
+ 'floats, times, chars, strings, lists of comparable values, ' +
+ 'and tuples of comparable values.');
+ }
+ }
+
+
+ // TUPLES
+
+ var Tuple0 = {
+ ctor: '_Tuple0'
+ };
+
+ function Tuple2(x, y)
+ {
+ return {
+ ctor: '_Tuple2',
+ _0: x,
+ _1: y
+ };
+ }
+
+
+ // LITERALS
+
+ function chr(c)
+ {
+ var x = new String(c);
+ x.isChar = true;
+ return x;
+ }
+
+ function txt(str)
+ {
+ var t = new String(str);
+ t.text = true;
+ return t;
+ }
+
+
+ // GUID
+
+ var count = 0;
+ function guid(_)
+ {
+ return count++;
+ }
+
+
+ // RECORDS
+
+ function update(oldRecord, updatedFields)
+ {
+ var newRecord = {};
+ for (var key in oldRecord)
+ {
+ var value = (key in updatedFields) ? updatedFields[key] : oldRecord[key];
+ newRecord[key] = value;
+ }
+ return newRecord;
+ }
+
+
+ // MOUSE COORDINATES
+
+ function getXY(e)
+ {
+ var posx = 0;
+ var posy = 0;
+ if (e.pageX || e.pageY)
+ {
+ posx = e.pageX;
+ posy = e.pageY;
+ }
+ else if (e.clientX || e.clientY)
+ {
+ posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
+ posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
+ }
+
+ if (localRuntime.isEmbed())
+ {
+ var rect = localRuntime.node.getBoundingClientRect();
+ var relx = rect.left + document.body.scrollLeft + document.documentElement.scrollLeft;
+ var rely = rect.top + document.body.scrollTop + document.documentElement.scrollTop;
+ // TODO: figure out if there is a way to avoid rounding here
+ posx = posx - Math.round(relx) - localRuntime.node.clientLeft;
+ posy = posy - Math.round(rely) - localRuntime.node.clientTop;
+ }
+ return Tuple2(posx, posy);
+ }
+
+
+ //// LIST STUFF ////
+
+ var Nil = { ctor: '[]' };
+
+ function Cons(hd, tl)
+ {
+ return {
+ ctor: '::',
+ _0: hd,
+ _1: tl
+ };
+ }
+
+ function list(arr)
+ {
+ var out = Nil;
+ for (var i = arr.length; i--; )
+ {
+ out = Cons(arr[i], out);
+ }
+ return out;
+ }
+
+ function range(lo, hi)
+ {
+ var list = Nil;
+ if (lo <= hi)
+ {
+ do
+ {
+ list = Cons(hi, list);
+ }
+ while (hi-- > lo);
+ }
+ return list;
+ }
+
+ function append(xs, ys)
+ {
+ // append Strings
+ if (typeof xs === 'string')
+ {
+ return xs + ys;
+ }
+
+ // append Text
+ if (xs.ctor.slice(0, 5) === 'Text:')
+ {
+ return {
+ ctor: 'Text:Append',
+ _0: xs,
+ _1: ys
+ };
+ }
+
+
+ // append Lists
+ if (xs.ctor === '[]')
+ {
+ return ys;
+ }
+ var root = Cons(xs._0, Nil);
+ var curr = root;
+ xs = xs._1;
+ while (xs.ctor !== '[]')
+ {
+ curr._1 = Cons(xs._0, Nil);
+ xs = xs._1;
+ curr = curr._1;
+ }
+ curr._1 = ys;
+ return root;
+ }
+
+
+ // CRASHES
+
+ function crash(moduleName, region)
+ {
+ return function(message) {
+ throw new Error(
+ 'Ran into a `Debug.crash` in module `' + moduleName + '` ' + regionToString(region) + '\n'
+ + 'The message provided by the code author is:\n\n '
+ + message
+ );
+ };
+ }
+
+ function crashCase(moduleName, region, value)
+ {
+ return function(message) {
+ throw new Error(
+ 'Ran into a `Debug.crash` in module `' + moduleName + '`\n\n'
+ + 'This was caused by the `case` expression ' + regionToString(region) + '.\n'
+ + 'One of the branches ended with a crash and the following value got through:\n\n ' + toString(value) + '\n\n'
+ + 'The message provided by the code author is:\n\n '
+ + message
+ );
+ };
+ }
+
+ function regionToString(region)
+ {
+ if (region.start.line == region.end.line)
+ {
+ return 'on line ' + region.start.line;
+ }
+ return 'between lines ' + region.start.line + ' and ' + region.end.line;
+ }
+
+
+ // BAD PORTS
+
+ function badPort(expected, received)
+ {
+ throw new Error(
+ 'Runtime error when sending values through a port.\n\n'
+ + 'Expecting ' + expected + ' but was given ' + formatValue(received)
+ );
+ }
+
+ function formatValue(value)
+ {
+ // Explicity format undefined values as "undefined"
+ // because JSON.stringify(undefined) unhelpfully returns ""
+ return (value === undefined) ? "undefined" : JSON.stringify(value);
+ }
+
+
+ // TO STRING
+
+ var _Array;
+ var Dict;
+ var List;
+
+ var toString = function(v)
+ {
+ var type = typeof v;
+ if (type === 'function')
+ {
+ var name = v.func ? v.func.name : v.name;
+ return '';
+ }
+ else if (type === 'boolean')
+ {
+ return v ? 'True' : 'False';
+ }
+ else if (type === 'number')
+ {
+ return v + '';
+ }
+ else if ((v instanceof String) && v.isChar)
+ {
+ return '\'' + addSlashes(v, true) + '\'';
+ }
+ else if (type === 'string')
+ {
+ return '"' + addSlashes(v, false) + '"';
+ }
+ else if (type === 'object' && 'ctor' in v)
+ {
+ if (v.ctor.substring(0, 6) === '_Tuple')
+ {
+ var output = [];
+ for (var k in v)
+ {
+ if (k === 'ctor') continue;
+ output.push(toString(v[k]));
+ }
+ return '(' + output.join(',') + ')';
+ }
+ else if (v.ctor === '_Array')
+ {
+ if (!_Array)
+ {
+ _Array = Elm.Array.make(localRuntime);
+ }
+ var list = _Array.toList(v);
+ return 'Array.fromList ' + toString(list);
+ }
+ else if (v.ctor === '::')
+ {
+ var output = '[' + toString(v._0);
+ v = v._1;
+ while (v.ctor === '::')
+ {
+ output += ',' + toString(v._0);
+ v = v._1;
+ }
+ return output + ']';
+ }
+ else if (v.ctor === '[]')
+ {
+ return '[]';
+ }
+ else if (v.ctor === 'RBNode_elm_builtin' || v.ctor === 'RBEmpty_elm_builtin' || v.ctor === 'Set_elm_builtin')
+ {
+ if (!Dict)
+ {
+ Dict = Elm.Dict.make(localRuntime);
+ }
+ var list;
+ var name;
+ if (v.ctor === 'Set_elm_builtin')
+ {
+ if (!List)
+ {
+ List = Elm.List.make(localRuntime);
+ }
+ name = 'Set';
+ list = A2(List.map, function(x) {return x._0; }, Dict.toList(v._0));
+ }
+ else
+ {
+ name = 'Dict';
+ list = Dict.toList(v);
+ }
+ return name + '.fromList ' + toString(list);
+ }
+ else if (v.ctor.slice(0, 5) === 'Text:')
+ {
+ return '';
+ }
+ else if (v.ctor === 'Element_elm_builtin')
+ {
+ return ''
+ }
+ else if (v.ctor === 'Form_elm_builtin')
+ {
+ return '