diff options
author | John Omar <me@johnomar.com> | 2016-10-13 11:31:25 -0400 |
---|---|---|
committer | John Omar <me@johnomar.com> | 2016-10-13 11:31:25 -0400 |
commit | 0c190e062e77b9c5012b7a693fa48861b68bcb29 (patch) | |
tree | e57780251f711dd6578f46422f3c7563047d5fcb |
webpack working.
-rw-r--r-- | .gitignore | 7 | ||||
-rw-r--r-- | README.md | 330 | ||||
-rw-r--r-- | elm-package.json | 15 | ||||
-rw-r--r-- | main.js | 39 | ||||
-rw-r--r-- | package.json | 19 | ||||
-rw-r--r-- | src/elm/Main.elm | 7 | ||||
-rw-r--r-- | src/static/bundle.js | 7446 | ||||
-rw-r--r-- | src/static/index.html | 10 | ||||
-rw-r--r-- | src/static/index.js | 3 | ||||
-rw-r--r-- | webpack.config.js | 19 |
10 files changed, 7895 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..040dcb5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules +# elm-package generated files +elm-stuff +# elm-repl generated files +repl-temp-* + +dist
\ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2e72c44 --- /dev/null +++ b/README.md @@ -0,0 +1,330 @@ +* Electron +* Elm +* webpack +* hot reloading + +# A guide, not a template +You can find starter templates for a lot of frameworks in the javascript ecosystem. They aren't helpful. +Templates make your life harder. When something goes wrong or your code veers slightly from the template, +you'll be fighting a beast that you likely do not understand. More so, the template probably includes +packages and settings that you don't need. + +So here it goes. + +# Create some folders and files +Whenever you see *Project-Name* you should substitute it with the name of your project. Per traditon, +let's put your project files in a folder called Project-Name: `mkdir Project-Name` + +Now let's make a few files and folders. + +``` +cd Project-Name +touch README.md webpack.config.js +mkdir src src/elm src/static +npm init +``` + +After `npm init` you'll be presented with a few questions about the project. Answer them or keep pressing enter. Now +is a good time to initialize git and make your first commit. + +Note that the `/elm` and `/static` folders inside `/src` are how I choose to structure my elm projects. Elm files have their +place, and js/html/css go into static. + + +# Electron +Electron allows you to use web technologies to build desktop apps. As of now, Elm compiles to javascript. That's why +we can use Elm to build desktop apps. The goal in this section is to get an electron window displaying "Hello Electron". We won't +be using Elm yet. + +First make sure electron is globally installed. + +``` +npm install -g electron-prebuilt +``` + +Electron has a Main process and a Renderer process. You can think of the Main process as the code that interacts with the +file system and desktop with node.js. It's a local server. The Renderer process is where you write the front end code that +the user interacts with. You can send data between processes using [ipc](http://electron.atom.io/docs/api/ipc-main/). + +Let's create two files to see this in action. + +``` +touch main.js src/static/index.html +``` + +Fill in the index.html file with text that shows you that this is working. + +``` +<html> + + <head> + <title>This title shows at the top</title> + </head> + + <body> + <h2>Hello Electron</h2> + </body> +</html> +``` + +And now the main.js file. + +``` +'use strict' +const electron = require('electron') + +const app = electron.app // this is our app +const BrowserWindow = electron.BrowserWindow // This is a Module that creates windows + +let mainWindow // saves a global reference to mainWindow so it doesn't get garbage collected + +app.on('ready', createWindow) // called when electron has initialized + +// This will create our app window, no surprise there +function createWindow () { + mainWindow = new BrowserWindow({ + width: 1024, + height: 768 + }) + + // display the index.html file + mainWindow.loadURL(`file://${ __dirname }/src/static/index.html`) + + // open dev tools by default so we can see any console errors + mainWindow.webContents.openDevTools() + + mainWindow.on('closed', function () { + mainWindow = null + }) +} + +/* Mac Specific things */ + +// when you close all the windows on a non-mac OS it quits the app +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { app.quit() } +}) + +// if there is no mainWindow it creates one (like when you click the dock icon) +app.on('activate', () => { + if (mainWindow === null) { createWindow() } +}) +``` + +This code is well commented. When we run this file, it will create a window of the specified size and +create other application lifecycle methods. Notice the line: +`mainWindow.loadURL(`file://${ __dirname }/src/static/index.html`)` +That's where we tell our electron app to load the html file we created earlier. + +``` +electron main.js +``` + +Glorious. + +# Elm +Now for the fun part. Here's how this goes when electron isn't in the picture. + +- write some elm code +- run `elm make Main.elm --output bundle.js` which transpiles the elm code into javascript +- import bundle.js into javascript and embed it into a div in your html. + +That's still the general flow. Let's do this for real. + +``` +touch src/elm/Main.elm +``` + +Finally some Elm code. + +``` +module Main exposing (..) + +import Html exposing (text) + + +main = + text "Hello Electron. I'm Elm." +``` + +Now let's turn this Elm code into javascript. Elm will also install some packages. + +``` +elm make src/elm/Main.elm --output src/static/bundle.js +``` + +The output file, bundle.js, is being put into the static folder. This is temporary for convenience. +It's easier to import into the html file for lazy people since it is in the same directory. Many future +decisions, but not all, are driven by laziness. But only when quality isn't at stake. + +Edit the index.html file to import the new bundle.js file and embed it into a div. + +``` +<html> + + <head> + <title>This title shows at the top</title> + </head> + + <body> + <div id='container'></div> + <script src="bundle.js"></script> + <script> + var Elm = require('./bundle.js'); + var container = document.getElementById('container'); + var app = Elm.Main.embed(container); + </script> +</html> +``` + +Here you are grabbing the container div and embedding the javascript code in there. Elm automatically +creates the Main.embed function during transpilation. + +A few more housekeeping items before we see this in action. Run + +When you ran `elm make` some Elm packages were downloaded and an elm-package.json file was created. +All is good, except elm doesn't know where to look for your elm files. Update the elm-package.json source-directories +to point to the location of your elm files. + +``` +"source-directories": [ + "src/elm" +], +``` + +Run `electron main.js` and you'll see "Hello Electron. I'm Elm." + +This setup is all you need to build electron apps in Elm. However, you'll be missing out on some of the +latest and greatest web dev tools like hot reloading and automatic transpilation. That's where webpack +comes in. + +# Webpack + +Webpack is hard to wrap your head around, but it's awesome so let's try to understand it. + +What is webpack? +The modern web stack is made up of many different parts. You could be using coffeescript or elm or +clojurescript, sass or less, jade, etc. And they all depend on each other. A coffee file might be +importing another coffee file, which is being used by a jade file. The dependency graph can get wild. +Webpack takes all your files and automatically transforms them in to static assets - a clean set of +javascript and css files. + +In our case, webpack is going to take all of our Elm, javascript, css (or sass if that's how you roll) files +and turn them in to static modules. Note that webpack can only consume javascript, so we need to use +loaders that convert our elm code into javascript. + +Let's get our Elm file working using webpack rather than reading the output of `elm make`. + +[Install webpack](http://webpack.github.io/docs/installation.html) + +You can use webpack from the command line, but to do anything serious (like converting Elm to js) +you need a config file. We already created one: *webpack.config.js*. Let's create a directory for the +webpack output file so things don't get messy in the root of our project. While we're at it, let's create +an index.js file because as I mentioned earlier, webpack can only consume js. It can't import our index.html +file unless we convert that to js with a loader. + +``` +mkdir dist +touch src/static/index.js +rm src/static/bundle.js +``` + +I removed the bundle.js file we created earlier. That will no longer be used because we are +going to use the one that webpack makes in /dist/bundle.js. + +And now edit the webpack.config.js file. + +``` +module.exports = { + entry: './src/static/index.js', + output: { + path: './dist', + filename: 'bundle.js' + } +} +``` + +That seems scary, but it's not so bad. Every time you run the *webpack* command, webpack will +check this file to see what it should do. We are saying that webpack should look for the index.js +file, do its magic, and then export a file named bundle.js to the /dist directory. + +Our index.js file is empty right now. It should include the javascript we used to embed Elm into +the div. Remove that js from the html file and put it in index.js like so. + +``` +var Elm = require('../elm/Main'); +var container = document.getElementById('container'); +var app = Elm.Main.embed(container); +``` + +This code is mostly the same except you need to require the Main.elm file directly. Run *webpack* +and see what happens. *Error: cannot resolve ... elm/Main ...* or something like that. What its +saying is that webpack doesn't know how to consume an Elm file. We need a loader to convert the +Elm file to a js file. + +``` +$ npm install --save elm-webpack-loader +``` + +And now configure webpack to use the loader. + +``` +module.exports = { + entry: './src/static/index.js', + output: { + path: './dist', + filename: 'bundle.js' + }, + module: { + loaders: [ + { + test: /\.elm$/, + exclude: [/elm-stuff/, /node_modules/], + loader: 'elm-webpack?verbose=true&warn=true', + } + ] + }, + resolve: { + extensions: ['', '.js', '.elm'] + } +} +``` + +The elm loader is going to transpile the .elm files into .js before webpack does its bundling magic. +Under the hood webpack uses *elm make* just like we did above. We could skip using the loader if we +wanted to manually make the elm files every time, but webpack automates it for us now. +webpack dev server + +If you run *webpack* now, you'll see the bundle file get created in /dist, but when you run electron +you won't see the "Hello Electron. I'm Elm." text. The reason is because the html file that electron +is running is not importing the new bundle.js file. In fact, it's not importing anything. Let's change +that. + +``` +<html> + + <head> + <title>This title shows at the top</title> + </head> + + <body> + <div id='container'></div> + <script src='../../dist/bundle.js'></script> +</html> +``` + +Try running `electron main.js` now and you'll see the message! + +Here's an overview: +- Elm files go to webpack loader and get turned into js +- New js files are consumed by webpack and turned into one file, bundle.js +- Electron opens our index.html file, which imports the new bundle.js + + + +// add $ to commands + + + + + diff --git a/elm-package.json b/elm-package.json new file mode 100644 index 0000000..19e9b02 --- /dev/null +++ b/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": [ + "src/elm" + ], + "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" +} @@ -0,0 +1,39 @@ +'use strict' +const electron = require('electron') + +const app = electron.app // this is our app +const BrowserWindow = electron.BrowserWindow // This is a Module that creates windows + +let mainWindow // saves a global reference to mainWindow so it doesn't get garbage collected + +app.on('ready', createWindow) // called when electron has initialized + +// This will create our app window, no surprise there +function createWindow () { + mainWindow = new BrowserWindow({ + width: 1024, + height: 768 + }) + + // display the index.html file + mainWindow.loadURL(`file://${ __dirname }/src/static/index.html`) + + // open dev tools by default so we can see any console errors + mainWindow.webContents.openDevTools() + + mainWindow.on('closed', function () { + mainWindow = null + }) +} + +/* Mac Specific things */ + +// when you close all the windows on a non-mac OS it quits the app +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { app.quit() } +}) + +// if there is no mainWindow it creates one (like when you click the dock icon) +app.on('activate', () => { + if (mainWindow === null) { createWindow() } +})
\ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..d05d0d4 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "elm-electron-webpack", + "version": "1.0.0", + "description": "A guide, not a template, for building electron apps with elm and webpack.", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "elm", + "webpack", + "electron" + ], + "author": "John Omar", + "license": "ISC", + "dependencies": { + "elm-webpack-loader": "^3.0.6" + } +} diff --git a/src/elm/Main.elm b/src/elm/Main.elm new file mode 100644 index 0000000..21bf797 --- /dev/null +++ b/src/elm/Main.elm @@ -0,0 +1,7 @@ +module Main exposing (..) + +import Html exposing (text) + + +main = + text "Hello Electron. I'm Elm." diff --git a/src/static/bundle.js b/src/static/bundle.js new file mode 100644 index 0000000..3ac37f2 --- /dev/null +++ b/src/static/bundle.js @@ -0,0 +1,7446 @@ + +(function() { +'use strict'; + +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); +} + +//import Native.List // + +var _elm_lang$core$Native_Array = function() { + +// 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.ctor === '[]') + { + 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_(_elm_lang$core$Native_List.Nil, a); +} + +function toList_(list, a) +{ + for (var i = a.table.length - 1; i >= 0; i--) + { + list = + a.height === 0 + ? _elm_lang$core$Native_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 in |