Full Code of JoshData/jot for AI

primary ebf8c6110e55 cached
24 files
178.7 KB
51.0k tokens
38 symbols
1 requests
Download .txt
Repository: JoshData/jot
Branch: primary
Commit: ebf8c6110e55
Files: 24
Total size: 178.7 KB

Directory structure:
gitextract_o8ilj5l0/

├── .gitignore
├── README.md
├── TODO.md
├── browser_example/
│   ├── browserfy_root.js
│   └── example.html
├── example.js
├── index.js
├── jot/
│   ├── copies.js
│   ├── diff.js
│   ├── index.js
│   ├── lists.js
│   ├── merge.js
│   ├── objects.js
│   ├── sequences.js
│   └── values.js
├── package.json
├── test.js
└── tests/
    ├── diff.js
    ├── merge.js
    ├── meta.js
    ├── objects.js
    ├── random.js
    ├── sequences.js
    └── values.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
*~
node_modules
browser_example/jot.js
json_editor_example/img/
json_editor_example/jot.js
json_editor_example/jsoneditor.css
json_editor_example/jsoneditor.js


================================================
FILE: README.md
================================================
JSON Operational Transformation (JOT)
=====================================

By Joshua Tauberer <https://razor.occams.info>.

August 2013.

License: GPL v3 <http://choosealicense.com/licenses/gpl-v3/>

This module implements operational transformation (OT) on a JSON data model,
written in JavaScript for use either in node.js or browsers.

While most collaborative editing models operate on plain text documents with
operations like insert and delete on strings, the document model in JOT is JSON
--- i.e. the value space of null, booleans, numbers, strings, arrays, and
objects (key-value pairs with string keys). JOT includes the basic insert/delete
operations on strings but adds many other operations that make JOT useful
for tracking changes to any sort of data that can be encoded in JSON.

Basically, this is the core of real time simultaneous editing, like Etherpad,
but for structured data rather than just plain text. Since everything can
be represented in JSON, this provides plain text collaboration functionality
and much more.

This is a work in progress. There is no UI or collaboration framework here.

Why JOT?
--------

### Introduction

The core problem addressed by operational transformation libraries like JOT
is merging edits made simultaneously, i.e. asynchronously, by two or more
users, and the handling of potential conflicts that arise when multiple
users edit the same part of the document.

To illustrate the problem, imagine two users open the following JSON document:

	{ "title": "Hello World!", "count": 10 }

Each user now has a copy of this document in their local memory. The first user
modfies their copy by changing the title and incrementing the count:

 	{ "title": "It's a Small World!", "count": 20 }

At the same time, the second user changes their copy of the document by changing
`Hello World!` to `Hello, Small World!` also incrementing the count by 5, yielding:

 	{ "title": "Hello, Small World!", "count": 15 }

### Structured representation of changes

In order to merge these changes, there needs to be a structured representation of the
changes. In the flat land of plain text, you are probably used to diffs and patches
as structured representations of changes --- e.g. at lines 5 through 10, replace with
new content. In JOT, it is up to the library user to form structured representations
of changes using JOT's classes. The changes in the example above are constructed as:

	var user1 = new jot.LIST([
		new jot.APPLY("title", new jot.SPLICE(0, 5, "It's a Small")),
		new jot.APPLY("count", new jot.MATH("add", 10))
	]);

	var user2 = new jot.LIST([
		new jot.APPLY("title", new jot.SPLICE(5, 1, ", Small ")),
		new jot.APPLY("count", new jot.MATH('add', 5))
	]);

In other words, user 1 makes a change to the `title` property by replacing the 5 characters
starting at position 0 with `It's a Small` and increments the `count` property by 10.
User 2 makes a change to the `title` property by replacing the 1 character at position 5,
i.e. the first space, with `, Small ` and increments the `count` property by 5.

These changes cannot yet be combined. If they were applied in order we would get a corrupted
document because the character positions that user 2's operation referred to are shifted once
user 1's changes are applied. After applying user 1's changes, we have the document:

	{ title: "It's a Small World!", count: 20 }

But then if we apply user 2's changes, which say to replace the character at position 5, we
would get:

	{ title: "It's , Small  Small World!", count: 25 }

That's not what user 2 intended. **The second user's changes must be "transformed" to take into
account the changes to the document made by the first user before they can be applied.**

### Transformation

JOT provides an algorithm to transform the structured representation of changes so 
that simultaneous changes can be combined sequentially.

Continuing the example, we desire to transform the second user's changes so that
they can be applied in sequence after the first user's changes.

Instead of

	... new jot.APPLY("title", new jot.SPLICE(5, 1, ", Small "))  ...

we want the second user's changes to look like

	... new jot.APPLY("title", new jot.SPLICE(12, 1, ", Small "))  ...

Note how the character index has changed. These changes now _can_ be applied after the first
user's changes and achieve the _intent_ of user 2's change.

JOT provides a `rebase` function on operation objects that can make this
transformation. (The transformation is named after [git's rebase](https://git-scm.com/book/en/v2/Git-Branching-Rebasing).) The `rebase` function transforms the operation and yields a new operation that should be applied instead, taking as an argument the operations executed by another user concurrently that have already applied to the document:

	user2 = user2.rebase(user1)

These changes can now be merged using `compose`:

	var all_changes = user1.compose(user2);

and then applied to the base document:

	document = all_changes.apply(document)

after which the base document will include both user's changes:

	{ title: "It's a Small, Small World!", count: 25 }

It would also have been possible to rebase `user1` and then compose the operations in the other order, for the exact same result.

See [example.js](example.js) for the complete example.

### Compared to other OT libraries

Operational transformation libraries often operate only over strings. JOT has
those operations too. For instance, start with the document:

	Hello world!

Two simultaneous changes might be:

	User 1: REPLACE CHARS 0-4 WITH "Brave new"

	User 2: REPLACE CHARS 11-11 WITH "."

To merge these changes, the second user's changes must be rebased to:

	User 2: REPLACE CHARS 15-15 WITH "."

JOT's rebase algorithm can handle this case too:

	// Construct operations
	var document = "Hello world!";
	var user1 = new jot.SPLICE(0, 5, "Brave new");
	var user2 = new jot.SPLICE(11, 1, ".");

	// Rebase user 2
	user2 = user2.rebase(user1, { document: document })

	// user2 now holds:
	// new jot.SPLICE(15, 1, ".")

	// Merge
	user1.compose(user2).apply(document);
	> 'Brave new world.'

Unlike most collaborative editing models where there are only operations like insert and delete that apply to strings, the document model in JOT is JSON --- i.e. the value space of null, booleans, numbers, strings, arrays, and objects (key-value pairs with string keys). Operations are provided that manipulate all of these data types. This makes JOT useful when tracking changes to data, rather than simply to plain text.


Installation
------------

The code is written for the node.js platform and can also be used client-side in modern browsers.

First install node, then install this package:

	npm install git+https://github.com/joshdata/jot.git

In a node script, import the library:

	var jot = require("jot");

To build the library for browsers, run:

	npm install -g browserify
	browserify browser_example/browserfy_root.js -d -o dist/jot.js

Then use the library in your HTML page (see [the example](browser_example/example.html) for details):

	<html>
		<body>
			<script src="jot.js"></script>
			<script>
				// see the example below, but skip the 'require' line
			</script>
		</body>
	</html>


Operations
----------

The operations in JOT are instantiated as `new jot.OPERATION(arguments)`. The available operations are...

### General operations

* `SET(new_value)`: Replaces any value with any other JSON-able value. `new_value` is the new value after the operation applies.
* `LIST([op1, op2, op3, ...])`: Executes a series of operations in order. `op1`, `op2`, `op3`, ... are other JOT operations. Equivalent to `op1.compose(op2).compose(op3)...`.

### Operations on booleans and numbers

* `MATH(op, value)`: Applies an arithmetic or boolean operation to a value. `op` is one of "add", "mult" (multiply), "rot" (increment w/ modulus), "and" (boolean or bitwise and), "or" (boolean or bitwise or), "xor" (boolean or bitwise exclusive-or), "not" (boolean or bitwise negation). For `rot`, `value` is given as an array of `[increment, modulus]`. For `not`, `value` is ignored and should be `null`. `add` and `mult` apply to any number, `rot` applies to integers only, and the boolean/bitwise operations only apply to integers and booleans. Because of rounding, operations on floating-point numbers or with floating-point operands could result in inconsistent state depending on the order of execution of the operations.

### Operations on strings and arrays

The same operation is used for both strings and arrays:

* `SPLICE(index, length, new_value)`: Replaces text in a string or array elements in an array at the given index and length in the original. To delete, `new_value` should be an empty string or zero-length array. To insert, `length` should be zero.
* `ATINDEX(index, operation)`: Apply any operation to a particular array element at `index`. `operation` is any operation. Operations at multiple indexes can be applied simultaneously using `ATINDEX({ index1: op1, index2: op2, ... })`.
* `MAP(operation)`: Apply any operation to all elements of an array (or all characters in a string). `operation` is any operation created by these constructors.

`SPLICE` is the only operation you need for basic plain text concurrent
editing. JOT includes the entire text editing model in the `SPLICE`
operations plus it adds new operations for non-string data structures!

(Note that internally `SPLICE` and `ATINDEX` are sub-cases of an internal PATCH operation that maintains an ordered list of edits to a string or array.)

### Operations on objects

* `PUT(key, value)`: Adds a new property to an object. `key` is any valid JSON key (a string) and `value` is any valid JSON object. Equivalent to `APPLY(key, SET(value))`.
* `REM(key)`: Remove a property from an object. Equivalent to `APPLY(key, SET(~))` where `~` is a special internal value.
* `APPLY(key, operation)`: Apply any operation to a particular property named `key`. `operation` is any operation. The operation can also take a mapping from keys to operations, as `APPLY({key: operation, ...})`.

### Operations that affect document structure

* `COPY([ [source1, target1], [source2, target2], ... ])`: Copies a value from one location in the document to another. The source and target parameters are [JSON Pointer](https://tools.ietf.org/html/rfc6901) strings (but `/-` is not allowed). Use in combination with other operations to move parts of the document, e.g. a `COPY` plus a `REM` can be used to rename an object property.

Methods
-------

### Instance Methods

Each operation object provides the following instance methods:

* `op.inspect()` returns a human-readable string representation of the operation. (A helper method so you can do `console.log(op)`.)
* `op.isNoOp()` returns a boolean indicating whether the operation does nothing.
* `op.apply(document)` applies the operation to the document and returns the new value of the document. Does not modify `document`.
* `op.simplify()` attempts to simplify complex operations. Returns a new operation or the operation unchanged. Useful primarily for `LIST`s.
* `op.drilldown(index_or_key)` looks inside an operation on a string, array, or object and returns the operation that represents the effect of this operation on a particular index or key.
* `op.inverse(document)` returns the inverse operation, given the document value *before* the operation applied.
* `op.compose(other)` composes two operations into a single operation instance, sometimes a `LIST` operation.
* `op.rebase(other)` rebases an operation. Returns null if the operations conflict, otherwise a new operation instance.
* `op.rebase(other, { document: ... })` rebases an operation in conflictless mode. The document value provided is the value of the document *before* either operation applied. Returns a new operation instance. See further documentation below.
* `op.toJSON()` turns the operation into a JSON-able data structure (made up of objects, arrays, strings, etc). See `jot.opFromJSON()`. (A helper method so you can do `JSON.stringify(op)`.)
* `op.serialize()` serializes the operation to a string. See `jot.deserialize()`.

### Global Methods

The `jot` library itself offers several global methods:

* `jot.diff(a, b, options)` compares two documents, `a` and `b`, and returns a JOT operation that when applied to `a` gives `b`.  `options`, if given, is an object that controls how the diff is performed. Any data type that can be a JOT document (i.e. any JSON-like data type) can be compared, and the comparison follows the document structure recursively. If the keys `words`, `lines`, or `sentences` is set to a truthy value, then strings are compared word-by-word, line-by-line, or sentence-by-sentence, instead of character-by-character. There is no general purpose structured diff algorithm that works well on all documents --- this one probably works fine on relatively small structured data.
* `jot.opFromJSON(opdata)` is the inverse of `op.toJSON()`.
* `jot.deserialize(string)` is the inverse of `op.serialize()`.

Conflictless Rebase
-------------------

What makes JOT useful is that each operation knows how to "rebase" itself against
every other operation. This is the "transformation" part of operational transformation,
and it's what you do when you have two concurrent edits that need to be merged.

The rebase operation guarantees that any two operations can be combined in any order
and result in the same document. In other words, rebase satisfies the constraints
`A ○ (B/A) == B ○ (A/B)` and `C / (A○B) == (C/A) / B`, where `○` is `compose`
and `/` is rebase.

### Rebase conflicts

In general, not all rebases are possible in a way that preserves the logical intent
of each change. This is what results in a merge conflict in source code control
software like git. The conflict indicates where two operations could not be merged
without losing the logical intent of the changes and intervention by a human is
necessary. `rebase` will return `null` in these cases.

For example, two `MATH` operations with different operators will conflict because
the order that these operations apply is significant:

	> new jot.MATH("add", 1)
	    .rebase( new jot.MATH("mult", 2) )
	null

(10 + 1) * 2 = 22 but (10 * 2) + 1 == 21. A vanilla `rebase` will return `null` in this case
to signal that human intervention is needed to choose which operation should apply
first.

### Using conflictless rebase

However, JOT provides a way to guarantee that `rebase` will return *some* operation,
so that a merge conflict cannot occur. We call this "conflictless" rebase. The result
of a conflictless rebase comes *close* to preserving the logical intent of the
operations by choosing one operation over the other *or* choosing an order that
the operations will apply in.

To get a conflictless rebase, pass a second options argument to `rebase` with the
`document` option set to the content of the document prior to both operations applying:

	> new jot.MATH("add", 1)
	    .rebase( new jot.MATH("mult", 2),
	             { document: 10 } )
	<values.SET 22>

The rebase returns a valid operation now, in this case telling us that to add 1 *after the multiplication has applied*, we should simply set the
result to 22 instead of adding 1. In other words, the rebase has chosen the order
where multiplication goes second.

Rebasing the other way around yields a consistent operation:

	> new jot.MATH("mult", 2)
	    .rebase( new jot.MATH("add", 1),
	             { document: 10 } )
	<values.MATH mult:2>

In other words, if we're multing by 2 *after the addition has applied*, we should
continue to multiply by 2. That's the same order as rebase chose above.

Development and Testing
-----------------------

Run code coverage tests with `npm test` or:

	npm test -- --coverage-report=html

Notes
-----

Thanks to @konklone for some inspiration and the first pull request.

Similar work: [ShareDB](https://github.com/share/sharedb), [ottypes/json0](https://github.com/ottypes/json0), [Apache Wave](http://incubator.apache.org/wave/) (formerly Google Wave), [Substance Operator](https://github.com/substance/operator) (defunct).

================================================
FILE: TODO.md
================================================
* PATCH inside PATCH must go away in simplfy in case a PATCH ends up inside a PATCH from rebasing a MAP
* Document diff.
* Versioning of serialized objects.
* Strucutued output of conflicts from failed rebases would make it easier to implement a git merge driver.
* Developer documentation.
* Floating point operations yield inconsistent results due to rounding. The random.js tests fail because of this.
* Better/more tests.
* Test coverage.


================================================
FILE: browser_example/browserfy_root.js
================================================
// This is for building jot for use in browsers. Expose
// the library in a global 'jot' object.
global.jot = require("../jot")

================================================
FILE: browser_example/example.html
================================================
<html>
	<head>
		<script src="jot.js"> </script>
	</head>
	<body>
		<textarea id="mybox" style="width: 100%; height: 40%;" placeholder="type here!"></textarea>
		<textarea id="eventlog" style="width: 100%; height: 40%;" readonly placeholder="event log"></textarea>

		<script>
		function parseText(text) {
			if (text === "")
				return "";
			return text.split(/\n/) // split up lines
				.map(function(line) { // split lines into words
					return line.split(/(\W+)/)
				});
		}

		var last_text = "";
		function watch_for_changes() {
			// Get the current text in the box.
			var textarea = document.getElementById("mybox");
			var text = textarea.value;

			// While we can do a diff on the raw text, it's more interesting
			// to parse it into a data structure and do a structured diff.
			text = parseText(text);

			// Compare it with the last text.
			var op = jot.diff(last_text, text);
			if (!op.isNoOp()) {
				// If there was a change, show a structured representation
				// of the change in the eventlog textarea.
				var eventlog = document.getElementById("eventlog");
				eventlog.value = op.inspect() + "\n" + eventlog.value
			}

			// Update state, schedule next check.
			last_text = text;
			setTimeout(watch_for_changes, 1000);
		}
		setTimeout(watch_for_changes, 0);
		</script>
	</body>
</html>

================================================
FILE: example.js
================================================
/* load libraries (test if 'jot' is defined already, so we can use this in the browser where 'require' is not available) */
var jot = jot || require("./jot");

/* The Base Document */

var doc = {
	title: "Hello World!",
	count: 10,
};

console.log("Original Document")
console.log(doc); // { title: 'Hello World!', count: 10 }
console.log("")

/* User 1 revises the title and increments the documents's count.
 *
 * { title: 'It\'s a Small World!', count: 20 }
 *
 */

var user1 = new jot.LIST([
	new jot.APPLY("title", new jot.SPLICE(0, 5, "It's a Small")),
	new jot.APPLY("count", new jot.MATH("add", 10))
]);

console.log("User 1")
console.log(user1.apply(doc)); // { title: 'Hello, User 2!', count: 20 }
console.log("")

/* User 2 makes changes to the original ocument's values so
 * that the document becomes:
 *
 * { title: 'Hello, Small World!', count: 15 }
 *
 */

var user2 = new jot.LIST([
	new jot.APPLY("title", new jot.SPLICE(5, 1, ", Small ")),
	new jot.APPLY("count", new jot.MATH('add', 5))
]);

console.log("User 2")
console.log(user2.apply(doc)); // { key1: 'My Program', key2: 20 }
console.log("")

/* Don't do this! */

console.log("The Wrong Way")
console.log(user1.compose(user2).apply(doc));
console.log("")

/* You must rebase user2's operations before composing them. */

user2 = user2.rebase(user1);
if (user2 == null) throw new Error("hmm");
console.log("Rebased User 2")
console.log(user2);
console.log("")

console.log("Merged")
console.log(user1.compose(user2).apply(doc)); // { title: 'I am, User 2!', count: 25 }
console.log("")



================================================
FILE: index.js
================================================
module.exports = require("./jot")


================================================
FILE: jot/copies.js
================================================
/*  This module defines one operation:
	
	COPY([[source, target], ...])
	
	Clones values from source to target for each source-target pair.
	Source and target are strings that are paths in the JSON document
	to a value following the JSONPointer specification (RFC 6901).
	The paths must exist --- a final dash in a path to refer to the
	nonexistentent element after the end of an array is not valid.
	*/
	
var util = require("util");

var jot = require("./index.js");

var JSONPointer = require('jsonpatch').JSONPointer;

exports.module_name = 'copies'; // for serialization/deserialization

exports.COPY = function (pathpairs) {
	if (!Array.isArray(pathpairs)) throw new Error("argument must be a list");
	this.pathpairs = pathpairs.map(function(pathpair) {
		if (!Array.isArray(pathpair) || pathpair.length != 2)
			throw new Error("each element in pathpairs must be an array of two string elements")
		if (pathpair[0] instanceof JSONPointer && pathpair[1] instanceof JSONPointer) {
			// for internal calls only
			return pathpair;
		} else {
			if (typeof pathpair[0] != "string" || typeof pathpair[1] != "string")
				throw new Error("each element in pathpairs must be an array of two strings")
			if (pathpair[0] == pathpair[1])
				throw new Error("can't copy a path to itself")
			return [
				new JSONPointer(pathpair[0]),
				new JSONPointer(pathpair[1])
			]
		}
	});
	Object.freeze(this);
}
exports.COPY.prototype = Object.create(jot.Operation.prototype); // inherit
jot.add_op(exports.COPY, exports, 'COPY');

function serialize_pointer(jp) {
    return (jp.path.map(function(part) { return "/" + part.replace(/~/g,'~0').replace(/\//g,'~1') })
    	.join(""));
}

exports.COPY.prototype.inspect = function(depth) {
	return util.format("<COPY %s>", this.pathpairs.map(function(pathpair) {
		return serialize_pointer(pathpair[0]) + " => " + serialize_pointer(pathpair[1]);
	}).join(", "));
}

exports.COPY.prototype.visit = function(visitor) {
	// A simple visitor paradigm. Replace this operation instance itself
	// and any operation within it with the value returned by calling
	// visitor on itself, or if the visitor returns anything falsey
	// (probably undefined) then return the operation unchanged.
	return visitor(this) || this;
}

exports.COPY.prototype.internalToJSON = function(json, protocol_version) {
	json.pathpairs = this.pathpairs.map(function(pathpair) {
		return [serialize_pointer(pathpair[0]), serialize_pointer(pathpair[1])];
	});
}

exports.COPY.internalFromJSON = function(json, protocol_version, op_map) {
	return new exports.COPY(json.pathpairs);
}

exports.COPY.prototype.apply = function (document) {
	/* Applies the operation to a document.*/
	this.pathpairs.forEach(function(pathpair) {
		var val = pathpair[0].get(document);
		document = pathpair[1].replace(document, val);
	});
	return document;
}

exports.COPY.prototype.simplify = function (aggressive) {
	// Simplifies the operation. Later targets in pathpairs overwrite
	// earlier ones at the same location or a descendant of the
	// location.
	// TODO.
	return this;
}

function parse_path(jp, document) {
	var path = [];
	for (var i = 0; i < jp.length; i++) {
		var p = jp.path[i];
		if (Array.isArray(document))
			p = parseInt(p)
		path.push(p);
		document = document[p];
	}
	return path;
}

function drilldown_op(jp, document, op) {
	var path = parse_path(jp, document);
	for (var i = 0; i < path.length; i++)
		op = op.drilldown(path[i]);
	return op;
}

function wrap_op_in_path(jp, document, op) {
	var path = parse_path(jp, document);
	var i = path.length-1;
	while (i >= 0) {
		if (typeof path[i] == "string")
			op = new jot.APPLY(path[i], op)
		else
			op = new jot.ATINDEX(path[i], op)
		i--;
	}
	return op;
}

exports.COPY.prototype.inverse = function (document) {
	// Create a SET operation for every target.
	return new jot.LIST(this.pathpairs.map(function(pathpair) {
		return wrap_op_in_path(pathpair[1], document, new jot.SET(pathpair[1].get(document)));
	}))
}

exports.COPY.prototype.atomic_compose = function (other) {
	// Return a single COPY that combines the effect of this
	// and other. Concatenate the pathpairs lists, then
	// run simplify().
	if (other instanceof exports.COPY)
		return new exports.COPY(this.pathpairs.concat(other.pathpairs)).simplify();
}

exports.rebase = function(base, ops, conflictless, debug) {
	
}

exports.COPY.prototype.clone_operation = function(op, document) {
	// Return a list of operations that includes op and
	// also for any way that op affects a copied path,
	// then an identical operation at the target path.
	var ret = [op];
	this.pathpairs.forEach(function(pathpair) {
		var src_op = drilldown_op(pathpair[0], document, op);
		if (src_op.isNoOp()) return;
		ret.push(wrap_op_in_path(pathpair[1], document, src_op));
	});
	return new jot.LIST(ret).simplify();
}

exports.COPY.prototype.drilldown = function(index_or_key) {
	// This method is supposed to return an operation that
	// has the same effect as this but is relative to index_or_key.
	// Can we do that? If a target is at or in index_or_key,
	// then we affect that location. If source is also at or
	// in index_or_key, we can drill-down both. But if source
	// is somewhere else in the document, we can't really do
	// this.
	throw "hmm";
}

function make_random_path(doc) {
	var path = [];
	if (typeof doc === "string" || Array.isArray(doc)) {
		if (doc.length == 0) return path;
		var idx = Math.floor(Math.random() * doc.length);
		path.push(""+idx);
		try {
			if (Math.random() < .5 && typeof doc !== "string")
				path = path.concat(make_random_path(doc[idx]));
		} catch (e) {
			// ignore - can't create path on inner value
		}
	} else if (typeof doc === "object" && doc !== null) {
		var keys = Object.keys(doc);
		if (keys.length == 0) return path;
		var key = keys[Math.floor(Math.random() * keys.length)];
		path.push(key);
		try {
			if (Math.random() < .5)
				path = path.concat(make_random_path(doc[key]));
		} catch (e) {
			// ignore - can't create path on inner value
		}
	} else {
		throw new Error("COPY cannot apply to this document type: " + doc);
	}
	return path;
}

exports.createRandomOp = function(doc, context) {
	// Create a random COPY that could apply to doc. Choose
	// a random path for a source and a target.
	var pathpairs = [];
	while (1) {
		var pp = [ serialize_pointer({ path: make_random_path(doc) }),
		           serialize_pointer({ path: make_random_path(doc) }) ];
		if (pp[0] != pp[1])
			pathpairs.push(pp);
		if (Math.random() < .5)
			break;
	}
	return new exports.COPY(pathpairs);
}


================================================
FILE: jot/diff.js
================================================
// Construct JOT operations by performing a diff on
// standard data types.

var deepEqual = require("deep-equal");

var jot = require("./index.js");

function diff(a, b, options) {
	// Compares two JSON-able data instances and returns
	// information about the difference:
	//
	// {
	//   op:   a JOT operation representing the change from a to b
	//   pct:  a number from 0 to 1 representing the proportion
	//         of content that is different
	//   size: an integer representing the approximate size of the
	//         content in characters, which is used for weighting
	// }


	// Run the diff method appropriate for the pair of data types.
	// Do a type-check for valid types early, before deepEqual is called.
	// We can't call JSON.stringify below if we get a non-JSONable
	// data type.

	function typename(val) {
		if (typeof val == "undefined")
			throw new Error("Illegal argument: undefined passed to diff");
		if (val === null)
			return "null";
		if (typeof val == "string" || typeof val == "number" || typeof val == "boolean")
			return typeof val;
		if (Array.isArray(val))
			return "array";
		if (typeof val != "object")
			throw new Error("Illegal argument: " + typeof val + " passed to diff");
		return "object";
	}

	var ta = typename(a);
	var tb = typename(b);

	// Return fast if the objects are equal. This is muuuuuch
	// faster than doing our stuff recursively.

	if (deepEqual(a, b, { strict: true })) {
		return {
			op: new jot.NO_OP(),
			pct: 0.0,
			size: JSON.stringify(a).length
		};
	}
	
	if (ta == "string" && tb == "string")
		return diff_strings(a, b, options);

	if (ta == "array" && tb == "array")
		return diff_arrays(a, b, options);
	
	if (ta == "object" && tb == "object")
		return diff_objects(a, b, options);

	// If the data types of the two values are different,
	// or if we don't recognize the data type (which is
	// not good), then only an atomic SET operation is possible.
	return {
		op: new jot.SET(b),
		pct: 1.0,
		size: (JSON.stringify(a)+JSON.stringify(b)).length / 2
	}
}

exports.diff = function(a, b, options) {
	// Ensure options are defined.
	options = options || { };

	// Call diff() and just return the operation.
	return diff(a, b, options).op;
}

function diff_strings(a, b, options) {
	// Use the 'diff' package to compare two strings and convert
	// the output to a jot.LIST.
	var diff = require("diff");
	
	var method = "Chars";
	if (options.words)
		method = "Words";
	if (options.lines)
		method = "Lines";
	if (options.sentences)
		method = "Sentences";
	
	var total_content = 0;
	var changed_content = 0;

	var offset = 0;
	var hunks = diff["diff" + method](a, b)
		.map(function(change) {
			// Increment counter of total characters encountered.
			total_content += change.value.length;
			
			if (change.added || change.removed) {
				// Increment counter of changed characters.
				changed_content += change.value.length;

				// Create a hunk for this change.
				var length = 0, new_value = "";
				if (change.removed) length = change.value.length;
				if (change.added) new_value = change.value;
				var ret = { offset: offset, length: length, op: new jot.SET(new_value) };
				offset = 0;
				return ret;
			} else {
				// Advance character position index. Don't generate a hunk here.
				offset += change.value.length;
				return null;
			}
		})
		.filter(function(item) { return item != null; });

	// Form the PATCH operation.
	var op = new jot.PATCH(hunks).simplify();
	return {
		op: op,
		pct: (changed_content+1)/(total_content+1), // avoid divizion by zero
		size: total_content
	};
}

function diff_arrays(a, b, options) {
	// Use the 'generic-diff' package to compare two arrays,
	// but using a custom equality function. This gives us
	// a relation between the elements in the arrays. Then
	// we can compute the operations for the diffs for the
	// elements that are lined up (and INS/DEL operations
	// for elements that are added/removed).
	
	var generic_diff = require("generic-diff");

	// We'll run generic_diff over an array of indices
	// into a and b, rather than on the elements themselves.
	var ai = a.map(function(item, i) { return i });
	var bi = b.map(function(item, i) { return i });

	var ops = [ ];
	var total_content = 0;
	var changed_content = 0;
	var pos = 0;

	function do_diff(ai, bi, level) {
		// Run generic-diff using a custom equality function that
		// treats two things as equal if their difference percent
		// is less than or equal to level.
		//
		// We get back a sequence of add/remove/equal operations.
		// Merge these into changed/same hunks.

		var hunks = [];
		var a_index = 0;
		var b_index = 0;
		generic_diff(
			ai, bi,
			function(ai, bi) { return diff(a[ai], b[bi], options).pct <= level; }
			).forEach(function(change) {
				if (!change.removed && !change.added) {
					// Same.
					if (a_index+change.items.length > ai.length) throw new Error("out of range");
					if (b_index+change.items.length > bi.length) throw new Error("out of range");
					hunks.push({ type: 'equal', ai: ai.slice(a_index, a_index+change.items.length), bi: bi.slice(b_index, b_index+change.items.length) })
					a_index += change.items.length;
					b_index += change.items.length;
				} else {
					if (hunks.length == 0 || hunks[hunks.length-1].type == 'equal')
						hunks.push({ type: 'unequal', ai: [], bi: [] })
					if (change.added) {
						// Added.
						hunks[hunks.length-1].bi = hunks[hunks.length-1].bi.concat(change.items);
						b_index += change.items.length;
					} else if (change.removed) {
						// Removed.
						hunks[hunks.length-1].ai = hunks[hunks.length-1].ai.concat(change.items);
						a_index += change.items.length;
					}
				}
			});

		// Process each hunk.
		hunks.forEach(function(hunk) {
			//console.log(level, hunk.type, hunk.ai.map(function(i) { return a[i]; }), hunk.bi.map(function(i) { return b[i]; }));

			if (level < 1 && hunk.ai.length > 0 && hunk.bi.length > 0
				&& (level > 0 || hunk.type == "unequal")) {
				// Recurse at a less strict comparison level to
				// tease out more correspondences. We do this both
				// for 'equal' and 'unequal' hunks because even for
				// equal the pairs may not really correspond when
				// level > 0.
				do_diff(
					hunk.ai,
					hunk.bi,
					(level+1.1)/2);
				return;
			}

			if (hunk.ai.length != hunk.bi.length) {
				// The items aren't in correspondence, so we'll just return
				// a whole SPLICE from the left subsequence to the right
				// subsequence.
				var op = new jot.SPLICE(
					pos,
					hunk.ai.length,
					hunk.bi.map(function(i) { return b[i]; }));
				ops.push(op);
				//console.log(op);

				// Increment counters.
				var dd = (JSON.stringify(hunk.ai.map(function(i) { return a[i]; }))
				         + JSON.stringify(hunk.bi.map(function(i) { return b[i]; })));
				dd = dd.length/2;
				total_content += dd;
				changed_content += dd;

			} else {
				// The items in the arrays are in correspondence.
				// They may not be identical, however, if level > 0.
				for (var i = 0; i < hunk.ai.length; i++) {
					var d = diff(a[hunk.ai[i]], b[hunk.bi[i]], options);

					// Add an operation.
					if (!d.op.isNoOp())
						ops.push(new jot.ATINDEX(hunk.bi[i], d.op));

					// Increment counters.
					total_content += d.size;
					changed_content += d.size*d.pct;
				}
			}

			pos += hunk.bi.length;
		});
	}

	// Go.

	do_diff(ai, bi, 0);

	return {
		op: new jot.LIST(ops).simplify(),
		pct: (changed_content+1)/(total_content+1), // avoid divizion by zero
		size: total_content
	};		
}

function diff_objects(a, b, options) {
	// Compare two objects.

	var ops = [ ];
	var total_content = 0;
	var changed_content = 0;
	
	// If a key exists in both objects, then assume the key
	// has not been renamed.
	for (var key in a) {
		if (key in b) {
			// Compute diff.
			d = diff(a[key], b[key], options);

			// Add operation if there were any changes.
			if (!d.op.isNoOp())
				ops.push(new jot.APPLY(key, d.op));

			// Increment counters.
			total_content += d.size;
			changed_content += d.size*d.pct;
		}
	}

	// Do comparisons between all pairs of unmatched
	// keys to see what best lines up with what. Don't
	// store pairs with nothing in common.
	var pairs = [ ];
	/*
	for (var key1 in a) {
		if (key1 in b) continue;
		for (var key2 in b) {
			if (key2 in a) continue;
			var d = diff(a[key1], b[key2], options);
			if (d.pct == 1) continue;
			pairs.push({
				a_key: key1,
				b_key: key2,
				diff: d
			});
		}
	}
	*/

	// Sort the pairs to choose the best matches first.
	// (This is a greedy approach. May not be optimal.)
	var used_a = { };
	var used_b = { };
	pairs.sort(function(a,b) { return ((a.diff.pct*a.diff.size) - (b.diff.pct*b.diff.size)); })
	pairs.forEach(function(item) {
		// Have we already generated an operation renaming
		// the key in a or renaming something to the key in b?
		// If so, this pair can't be used.
		if (item.a_key in used_a) return;
		if (item.b_key in used_b) return;
		used_a[item.a_key] = 1;
		used_b[item.b_key] = 1;

		// Use this pair.
		ops.push(new jot.REN(item.a_key, item.b_key));
		if (!item.diff.op.isNoOp())
			ops.push(new jot.APPLY(item.b_key, item.diff.op));

		// Increment counters.
		total_content += item.diff.size;
		changed_content += item.diff.size*item.diff.pct;
	})

	// Delete/create any keys that didn't match up.
	for (var key in a) {
		if (key in b || key in used_a) continue;
		ops.push(new jot.REM(key));
	}
	for (var key in b) {
		if (key in a || key in used_b) continue;
		ops.push(new jot.PUT(key, b[key]));
	}

	return {
		op: new jot.LIST(ops).simplify(),
		pct: (changed_content+1)/(total_content+1), // avoid divizion by zero
		size: total_content
	};
}



================================================
FILE: jot/index.js
================================================
/* Base functions for the operational transformation library. */

var util = require('util');
var shallow_clone = require('shallow-clone');

// Must define this ahead of any imports below so that this constructor
// is available to the operation classes.
exports.Operation = function() {
}
exports.add_op = function(constructor, module, opname) {
	// utility.
	constructor.prototype.type = [module.module_name, opname];
	if (!('op_map' in module))
		module['op_map'] = { };
	module['op_map'][opname] = constructor;
}


// Expose the operation classes through the jot library.
var values = require("./values.js");
var sequences = require("./sequences.js");
var objects = require("./objects.js");
var lists = require("./lists.js");
var copies = require("./copies.js");

exports.NO_OP = values.NO_OP;
exports.SET = values.SET;
exports.MATH = values.MATH;
exports.PATCH = sequences.PATCH;
exports.SPLICE = sequences.SPLICE;
exports.ATINDEX = sequences.ATINDEX;
exports.MAP = sequences.MAP;
exports.PUT = objects.PUT;
exports.REM = objects.REM;
exports.APPLY = objects.APPLY;
exports.LIST = lists.LIST;
exports.COPY = copies.COPY;

// Expose the diff function too.
exports.diff = require('./diff.js').diff;

/////////////////////////////////////////////////////////////////////

exports.Operation.prototype.isNoOp = function() {
	return this instanceof values.NO_OP;
}

exports.Operation.prototype.visit = function(visitor) {
	// A simple visitor paradigm. Replace this operation instance itself
	// and any operation within it with the value returned by calling
	// visitor on itself, or if the visitor returns anything falsey
	// (probably undefined) then return the operation unchanged.
	return visitor(this) || this;
}

exports.Operation.prototype.toJSON = function(__key__, protocol_version) {
	// The first argument __key__ is used when this function is called by
	// JSON.stringify. For reasons unclear, we get the name of the property
	// that this object is stored in in its parent? Doesn't matter. We
	// leave a slot so that this function can be correctly called by JSON.
	// stringify, but we don't use it.

	// The return value.
	var repr = { };

	// If protocol_version is unspecified, then this is a top-level call.
	// Choose the latest (and only) protocol version and write it into
	// the output data structure, and pass it down recursively.
	//
	// If protocol_version was specified, this is a recursive call and
	// we don't need to write it out. Sanity check it's a valid value.
	if (typeof protocol_version == "undefined") {
		protocol_version = 1;
		repr["_ver"] = protocol_version;
	} else {
		if (protocol_version !== 1) throw new Error("Invalid protocol version: " + protocol_version);
	}

	// Set the module and operation name.
	repr['_type'] = this.type[0] + "." + this.type[1];

	// Call the operation's toJSON function.
	this.internalToJSON(repr, protocol_version);

	// Return.
	return repr;
}

exports.opFromJSON = function(obj, protocol_version, op_map) {
	// Sanity check.
	if (typeof obj !== "object") throw new Error("Not an operation.");

	// If protocol_version is unspecified, then this is a top-level call.
	// The version must be encoded in the object, and we pass it down
	// recursively.
	//
	// If protocol_version is specified, this is a recursive call and
	// we don't need to read it from the object.
	if (typeof protocol_version === "undefined") {
		protocol_version = obj['_ver'];
		if (protocol_version !== 1)
			throw new Error("JOT serialized data structure is missing protocol version and one wasn't provided as an argument.");
	} else {
		if (protocol_version !== 1)
			throw new Error("Invalid protocol version provided: " + protocol_version)
		if ("_ver" in obj)
			throw new Error("JOT serialized data structure should not have protocol version because it was provided as an argument.");
	}

	// Create a default mapping from encoded types to constructors
	// allowing all operations to be deserialized.
	if (!op_map) {
		op_map = { };

		function extend_op_map(module) {
			op_map[module.module_name] = { };
			for (var key in module.op_map)
				op_map[module.module_name][key] = module.op_map[key];
		}

		extend_op_map(values);
		extend_op_map(sequences);
		extend_op_map(objects);
		extend_op_map(lists);
		extend_op_map(copies);
	}

	// Get the operation class.
	if (typeof obj['_type'] !== "string") throw new Error("Not an operation.");
	var dottedclassparts = obj._type.split(/\./g, 2);
	if (dottedclassparts.length != 2) throw new Error("Not an operation.");
	var clazz = op_map[dottedclassparts[0]][dottedclassparts[1]];

	// Call the deserializer function on the class.
	return clazz.internalFromJSON(obj, protocol_version, op_map);
}

exports.Operation.prototype.serialize = function() {
	// JSON.stringify will use the object's toJSON method
	// implicitly.
	return JSON.stringify(this);
}
exports.deserialize = function(op_json) {
	return exports.opFromJSON(JSON.parse(op_json));
}

exports.Operation.prototype.compose = function(other, no_list) {
	if (!(other instanceof exports.Operation))
		throw new Error("Argument must be an operation.");

	// A NO_OP composed with anything just gives the other thing.
	if (this instanceof values.NO_OP)
		return other;

	// Composing with a NO_OP does nothing.
	if (other instanceof values.NO_OP)
		return this;

	// Composing with a SET obliterates this operation.
	if (other instanceof values.SET)
		return other;

	// Attempt an atomic composition if this defines the method.
	if (this.atomic_compose) {
		var op = this.atomic_compose(other);
		if (op != null)
			return op;
	}

	if (no_list)
		return null;

	// Fall back to creating a LIST. Call simplify() to weed out
	// anything equivalent to a NO_OP.
	return new lists.LIST([this, other]).simplify();
}

exports.Operation.prototype.rebase = function(other, conflictless, debug) {
	/* Transforms this operation so that it can be composed *after* the other
	   operation to yield the same logical effect as if it had been executed
	   in parallel (rather than in sequence). Returns null on conflict.
	   If conflictless is true, tries extra hard to resolve a conflict in a
	   sensible way but possibly by killing one operation or the other.
	   Returns the rebased version of this. */

	// Run the rebase operation in a's prototype. If a doesn't define it,
	// check b's prototype. If neither define a rebase operation, then there
	// is a conflict.
	for (var i = 0; i < ((this.rebase_functions!=null) ? this.rebase_functions.length : 0); i++) {
		if (other instanceof this.rebase_functions[i][0]) {
			var r = this.rebase_functions[i][1].call(this, other, conflictless);
			if (r != null && r[0] != null) {
				if (debug) debug("rebase", this, "on", other, (conflictless ? "conflictless" : ""), ("document" in conflictless ? JSON.stringify(conflictless.document) : ""), "=>", r[0]);
				return r[0];
			}
		}
	}

	// Either a didn't define a rebase function for b's data type, or else
	// it returned null above. We can try running the same logic backwards on b.
	for (var i = 0; i < ((other.rebase_functions!=null) ? other.rebase_functions.length : 0); i++) {
		if (this instanceof other.rebase_functions[i][0]) {
			var r = other.rebase_functions[i][1].call(other, this, conflictless);
			if (r != null && r[1] != null) {
				if (debug) debug("rebase", this, "on", other, (conflictless ? "conflictless" : ""), ("document" in conflictless ? JSON.stringify(conflictless.document) : ""), "=>", r[0]);
				return r[1];
			}
		}
	}

	// Everything can rebase against a LIST and vice versa.
	// This has higher precedence than the this instanceof SET fallback.
	if (this instanceof lists.LIST || other instanceof lists.LIST) {
		var ret = lists.rebase(other, this, conflictless, debug);
		if (debug) debug("rebase", this, "on", other, "=>", ret);
		return ret;
	}

	if (conflictless) {
		// Everything can rebase against a COPY in conflictless mode when
		// a previous document content is given --- the document is needed
		// to parse a JSONPointer and know whether the path components are
		// for objects or arrays. If this's operation affects a path that
		// is copied, the operation is cloned to the target path.
		// This has higher precedence than the this instanceof SET fallback.
		if (other instanceof copies.COPY && typeof conflictless.document != "undefined")
			return other.clone_operation(this, conflictless.document);

		// Everything can rebase against a SET in a conflictless way.
		// Note that to resolve ties, SET rebased against SET is handled
		// in SET's rebase_functions.

		// The SET always wins!
		if (this instanceof values.SET) {
			if (debug) debug("rebase", this, "on", other, "=>", this);
			return this;
		}
		if (other instanceof values.SET) {
			if (debug) debug("rebase", this, "on", other, "=>", new values.NO_OP());
			return new values.NO_OP();
		}

		// If conflictless rebase would fail, raise an error.
		throw new Error("Rebase failed between " + this.inspect() + " and " + other.inspect() + ".");
	}

	return null;
}

exports.createRandomValue = function(depth) {
	var values = [];

	// null
	values.push(null);

	// boolean
	values.push(false);
	values.push(true);

	// number (integer, float)
	values.push(1000 * Math.floor(Math.random() - .5));
	values.push(Math.random() - .5);
	values.push(1000 * (Math.random() - .5));

	// string
	values.push(Math.random().toString(36).substring(7));

	// array (make nesting exponentially less likely at each level of recursion)
	if (Math.random() < Math.exp(-(depth||0))) {
		var n = Math.floor(Math.exp(3*Math.random()))-1;
		var array = [];
		while (array.length < n)
			array.push(exports.createRandomValue((depth||0)+1));
		values.push(array);
	}

	// object (make nesting exponentially less likely at each level of recursion)
	if (Math.random() < Math.exp(-(depth||0))) {
		var n = Math.floor(Math.exp(2.5*Math.random()))-1;
		var obj = { };
		while (Object.keys(obj).length < n)
			obj[Math.random().toString(36).substring(7)] = exports.createRandomValue((depth||0)+1);
		values.push(obj);
	}

	return values[Math.floor(Math.random() * values.length)];
}

exports.createRandomOp = function(doc, context) {
	// Creates a random operation that could apply to doc. Just
	// chain off to the modules that can handle the data type.

	var modules = [];

	// The values module can handle any data type.
	modules.push(values);

	// sequences applies to strings and arrays.
	if (typeof doc === "string" || Array.isArray(doc)) {
		modules.push(sequences);
		//modules.push(copies);
	}

	// objects applies to objects (but not Array objects or null)
	else if (typeof doc === "object" && doc !== null) {
		modules.push(objects);
		//modules.push(copies);
	}

	// the lists module only defines LIST which can also
	// be applied to any data type but gives us stack
	// overflows
	//modules.push(lists);

	return modules[Math.floor(Math.random() * modules.length)]
		.createRandomOp(doc, context);
}

exports.createRandomOpSequence = function(value, count) {
	// Create a random sequence of operations starting with a given value.
	var ops = [];
	while (ops.length < count) {
		// Create random operation.
		var op = exports.createRandomOp(value);

		// Make the result of applying the op the initial value
		// for the next operation. createRandomOp sometimes returns
		// invalid operations, in which case we'll try again.
		// TODO: Make createRandomOp always return a valid operation
		// and remove the try block.
		try {
			value = op.apply(value);
		} catch (e) {
			continue; // retry
		}

		ops.push(op);
	}
	return new lists.LIST(ops);
}

exports.type_name = function(x) {
	if (typeof x == 'object') {
		if (Array.isArray(x))
			return 'array';
		return 'object';
	}
	return typeof x;
}

// Utility function to compare values for the purposes of
// setting sort orders that resolve conflicts.
exports.cmp = function(a, b) {
	// For objects.MISSING, make sure we try object identity.
	if (a === b)
		return 0;

	// objects.MISSING has a lower sort order so that it tends to get clobbered.
	if (a === objects.MISSING)
		return -1;
	if (b === objects.MISSING)
		return 1;

	// Comparing strings to numbers, numbers to objects, etc.
	// just sort based on the type name.
	if (exports.type_name(a) != exports.type_name(b)) {
		return exports.cmp(exports.type_name(a), exports.type_name(b));
	
	} else if (typeof a == "number") {
		if (a < b)
			return -1;
		if (a > b)
			return 1;
		return 0;
		
	} else if (typeof a == "string") {
		return a.localeCompare(b);
	
	} else if (Array.isArray(a)) {
		// First compare on length.
		var x = exports.cmp(a.length, b.length);
		if (x != 0) return x;

		// Same length, compare on values.
		for (var i = 0; i < a.length; i++) {
			x = exports.cmp(a[i], b[i]);
			if (x != 0) return x;
		}

		return 0;
	}

	// Compare on strings.
	// TODO: Find a better way to sort objects.
	return JSON.stringify(a).localeCompare(JSON.stringify(b));
}



================================================
FILE: jot/lists.js
================================================
/*  This module defines one operation:
	
	LIST([op1, op2, ...])
	
	A composition of zero or more operations, given as an array.

	*/
	
var util = require("util");

var shallow_clone = require('shallow-clone');

var jot = require("./index.js");
var values = require('./values.js');

exports.module_name = 'lists'; // for serialization/deserialization

exports.LIST = function (ops) {
	if (!Array.isArray(ops)) throw new Error("Argument must be an array.");
	ops.forEach(function(op) {
		if (!(op instanceof jot.Operation))
			throw new Error("Argument must be an array containing operations (found " + op + ").");
	})
	this.ops = ops; // TODO: How to ensure this array is immutable?
	Object.freeze(this);
}
exports.LIST.prototype = Object.create(jot.Operation.prototype); // inherit
jot.add_op(exports.LIST, exports, 'LIST');

exports.LIST.prototype.inspect = function(depth) {
	return util.format("<LIST [%s]>",
		this.ops.map(function(item) { return item.inspect(depth-1) }).join(", "));
}

exports.LIST.prototype.visit = function(visitor) {
	// A simple visitor paradigm. Replace this operation instance itself
	// and any operation within it with the value returned by calling
	// visitor on itself, or if the visitor returns anything falsey
	// (probably undefined) then return the operation unchanged.
	var ret = new exports.LIST(this.ops.map(function(op) { return op.visit(visitor); }));
	return visitor(ret) || ret;
}

exports.LIST.prototype.internalToJSON = function(json, protocol_version) {
	json.ops = this.ops.map(function(op) {
		return op.toJSON(undefined, protocol_version);
	});
}

exports.LIST.internalFromJSON = function(json, protocol_version, op_map) {
	var ops = json.ops.map(function(op) {
		return jot.opFromJSON(op, protocol_version, op_map);
	});
	return new exports.LIST(ops);
}

exports.LIST.prototype.apply = function (document) {
	/* Applies the operation to a document.*/
	for (var i = 0; i < this.ops.length; i++)
		document = this.ops[i].apply(document);
	return document;
}

exports.LIST.prototype.simplify = function (aggressive) {
	/* Returns a new operation that is a simpler version
	   of this operation. Composes consecutive operations where
	   possible and removes no-ops. Returns NO_OP if the result
	   would be an empty list of operations. Returns an
	   atomic (non-LIST) operation if possible. */
	var new_ops = [];
	for (var i = 0; i < this.ops.length; i++) {
		var op = this.ops[i];

		// simplify the inner op
		op = op.simplify();

		// if this isn't the first operation, try to atomic_compose the operation
		// with the previous one.
		while (new_ops.length > 0) {
			// Don't bother with atomic_compose if the op to add is a no-op.
			if (op.isNoOp())
				break;

			var c = new_ops[new_ops.length-1].compose(op, true);

			// If there is no atomic composition, there's nothing more we can do.
			if (!c)
				break;

			// The atomic composition was successful. Remove the old previous operation.
			new_ops.pop();

			// Use the atomic_composition as the next op to add. On the next iteration
			// try composing it with the new last element of new_ops.
			op = c;
		}

		// Don't add to the new list if it is a no-op.
		if (op.isNoOp())
			continue;

		// if it's the first operation, or atomic_compose failed, add it to the new_ops list
		new_ops.push(op);
	}

	if (new_ops.length == 0)
		return new values.NO_OP();
	if (new_ops.length == 1)
		return new_ops[0];

	return new exports.LIST(new_ops);
}

exports.LIST.prototype.inverse = function (document) {
	/* Returns a new atomic operation that is the inverse of this operation:
	   the inverse of each operation in reverse order. */
	var new_ops = [];
	this.ops.forEach(function(op) {
		new_ops.push(op.inverse(document));
		document = op.apply(document);
	})
	new_ops.reverse();
	return new exports.LIST(new_ops);
}

exports.LIST.prototype.atomic_compose = function (other) {
	/* Returns a LIST operation that has the same result as this
	   and other applied in sequence (this first, other after). */

	// Nothing here anyway, return the other.
	if (this.ops.length == 0)
		return other;

	// the next operation is an empty list, so the composition is just this
	if (other instanceof exports.LIST) {
		if (other.ops.length == 0)
			return this;
		
		// concatenate
		return new exports.LIST(this.ops.concat(other.ops));
	}


	// append
	var new_ops = this.ops.slice(); // clone
	new_ops.push(other);
	return new exports.LIST(new_ops);
}

exports.rebase = function(base, ops, conflictless, debug) {
	// Ensure the operations are simplified, since rebase
	// is much more expensive than simplified.

	base = base.simplify();
	ops = ops.simplify();

	// Turn each argument into an array of operations.
	// If an argument is a LIST, unwrap it.

	if (base instanceof exports.LIST)
		base = base.ops;
	else
		base = [base];

	if (ops instanceof exports.LIST)
		ops = ops.ops;
	else
		ops = [ops];

	// Run the rebase algorithm.

	var ret = rebase_array(base, ops, conflictless, debug);

	// The rebase may have failed.
	if (ret == null) return null;

	// ...or yielded no operations --- turn it into a NO_OP operation.
	if (ret.length == 0) return new values.NO_OP();

	// ...or yielded a single operation --- return it.
	if (ret.length == 1) return ret[0];

	// ...or yielded a list of operations --- re-wrap it in a LIST operation.
	return new exports.LIST(ret).simplify();
}

function rebase_array(base, ops, conflictless, debug) {
	/* This is one of the core functions of the library: rebasing a sequence
	   of operations against another sequence of operations. */

	/*
	* To see the logic, it will help to put this in a symbolic form.
	*
	*   Let a + b == compose(a, b)
	*   and a / b == rebase(b, a)
	*
	* The contract of rebase has two parts;
	*
	* 	1) a + (b/a) == b + (a/b)
	* 	2) x/(a + b) == (x/a)/b
	*
	* Also note that the compose operator is associative, so
	*
	*	a + (b+c) == (a+b) + c
	*
	* Our return value here in symbolic form is:
	*
	*   (op1/base) + (op2/(base/op1))
	*   where ops = op1 + op2
	*
	* To see that we've implemented rebase correctly, let's look
	* at what happens when we compose our result with base as per
	* the rebase rule:
	*
	*   base + (ops/base)
	*
	* And then do some algebraic manipulations:
	*
	*   base + [ (op1/base) + (op2/(base/op1)) ]   (substituting our hypothesis for self/base)
	*   [ base + (op1/base) ] + (op2/(base/op1))   (associativity)
	*   [ op1 + (base/op1) ] + (op2/(base/op1))    (rebase's contract on the left side)
	*   op1 + [ (base/op1)  + (op2/(base/op1)) ]   (associativity)
	*   op1 + [ op2 + ((base/op1)/op2) ]           (rebase's contract on the right side)
	*   (op1 + op2) + ((base/op1)/op2)             (associativity)
	*   self + [(base/op1)/op2]                    (substituting self for (op1+op2))
	*   self + [base/(op1+op2)]                    (rebase's second contract)
	*   self + (base/self)                         (substitution)
	*
	* Thus we've proved that the rebase contract holds for our return value.
	*/

	if (ops.length == 0 || base.length == 0)
		return ops;

	if (ops.length == 1 && base.length == 1) {
		// This is the recursive base case: Rebasing a single operation against a single
		// operation. Wrap the result in an array.
		var op = ops[0].rebase(base[0], conflictless, debug);
		if (!op) return null; // conflict
		if (op instanceof jot.NO_OP) return [];
		if (op instanceof jot.LIST) return op.ops;
		return [op];
	}
	
	if (debug) {
		// Wrap the debug function to emit an extra argument to show depth.
		debug("rebasing", ops, "on", base, conflictless ? "conflictless" : "", "document" in conflictless ? JSON.stringify(conflictless.document) : "", "...");
		var original_debug = debug;
		debug = function() { var args = [">"].concat(Array.from(arguments)); original_debug.apply(null, args); }
	}
	
	if (base.length == 1) {
		// Rebase more than one operation (ops) against a single operation (base[0]).

		// Nothing to do if it is a no-op.
		if (base[0] instanceof values.NO_OP)
			return ops;

		// The result is the first operation in ops rebased against the base concatenated with
		// the remainder of ops rebased against the-base-rebased-against-the-first-operation:
		// (op1/base) + (op2/(base/op1))

		var op1 = ops.slice(0, 1); // first operation
		var op2 = ops.slice(1); // remaining operations
		
		var r1 = rebase_array(base, op1, conflictless, debug);
		if (r1 == null) return null; // rebase failed
		
		var r2 = rebase_array(op1, base, conflictless, debug);
		if (r2 == null) return null; // rebase failed (must be the same as r1, so this test should never succeed)
		
		// For the remainder operations, we have to adjust the 'conflictless' object.
		// If it provides the base document state, then we have to advance the document
		// for the application of op1.
		var conflictless2 = null;
		if (conflictless) {
			conflictless2 = shallow_clone(conflictless);
			if ("document" in conflictless2)
				conflictless2.document = op1[0].apply(conflictless2.document);
		}

		var r3 = rebase_array(r2, op2, conflictless2, debug);
		if (r3 == null) return null; // rebase failed
		
		// returns a new array
		return r1.concat(r3);

	} else {
		// Rebase one or more operations (ops) against more than one operation (base).
		//
		// From the second part of the rebase contract, we can rebase ops
		// against each operation in the base sequentially (base[0], base[1], ...).
		
		// shallow clone
		conflictless = !conflictless ? null : shallow_clone(conflictless);

		for (var i = 0; i < base.length; i++) {
			ops = rebase_array([base[i]], ops, conflictless, debug);
			if (ops == null) return null; // conflict

			// Adjust the 'conflictless' object if it provides the base document state
			// since for later operations we're assuming base[i] has now been applied.
			if (conflictless && "document" in conflictless)
				conflictless.document = base[i].apply(conflictless.document);
		}

		return ops;
	}
}

exports.LIST.prototype.drilldown = function(index_or_key) {
	return new exports.LIST(this.ops.map(function(op) {
		return op.drilldown(index_or_key)
	})).simplify();
}

exports.createRandomOp = function(doc, context) {
	// Create a random LIST that could apply to doc.
	var ops = [];
	while (ops.length == 0 || Math.random() < .75) {
		ops.push(jot.createRandomOp(doc, context));
		doc = ops[ops.length-1].apply(doc);
	}
	return new exports.LIST(ops);
}


================================================
FILE: jot/merge.js
================================================
// Performs a merge, i.e. given a directed graph of operations with in-degree
// of 1 or 2, and two nodes 'source' and 'target' on the graph which have a
// common ancestor, compute the operation that applies the changes in source,
// relative to the common ancestor, to target.
//
// In the simplest case of a merge where the source and target share a common
// immediate parent, then the merge is simply the rebase of the source on the
// target. In more complex operation graphs, we find the least common ancestor
// (i.e. the nearest common ancestor) and then rebase around that.

const jot = require("./index.js");

function keys(obj) {
  // Get all of the own-keys of obj including string keys and symbols.
  return Object.keys(obj).concat(Object.getOwnPropertySymbols(obj));
}

function node_name_str(node) {
  if (typeof node === "Symbol" && node.description)
    return node.description;
  return node.toString();
}

exports.merge = function(branch1, branch2, graph) {
  // 'graph' is an object whose keys are identifiers of nodes
  // 'branch1' and 'branch2' are keys in 'graph'
  // The values of 'graph' are objects of the form:
  // {
  //   parents: an array of keys in graph
  //   op: an array of JOT operations, each giving the difference between
  //       the document at this node and the *corresponding* parent
  //   document: the document content at this node, required at least for
  //       root nodes, but can be set on any node
  // }
  // 
  // This method returns an array of two operations: The first merges
  // branch2 into branch1 and the second merges branch1 into branch2.

  // Find the lowest common ancestor(s) of branch1 and branch2.
  var lca = lowest_common_ancestors(branch1, branch2, graph);
  if (lca.length == 0)
    throw "NO_COMMON_ANCESTOR";

 function get_document_content_at(n) {
    // Node n may have a 'document' key set. If not, get the content at its first parent
    // and apply its operations.
    let path = [];
    while (typeof graph[n].document === "undefined") {
      if (!graph[n].parents)
        throw "ROOT_MISSING_DOCUMENT";
      path.unshift(graph[n].op[0]);
      n = graph[n].parents[0];
    }
    let document = graph[n].document;
    path.forEach(op => document = op.apply(document));
    return document;
  }

  /*
  console.log("Merging", branch1, "and", branch2);
  keys(graph).forEach(node =>
    console.log(" ", node, "=>", (graph[node].parents || []).map((p, i) =>
      (p.toString() + " + " + graph[node].op[i].inspect())).join(" X "))
  )
  */

  // If there is more than one common ancestor, then use the git merge
  // "recursive" strategy, which successively merges pairs of common
  // ancestors until there is just one left.
  while (lca.length > 1) {
    // Take the top two ancestors off the stack and compute the operations
    // to merge them.
    let lca1 = lca.pop();
    let lca2 = lca.pop();
    //console.log("Recursive merging...");
    let merge_ops = exports.merge(lca1.node, lca2.node, graph);

    // Form a new virtual node in the graph that represents this merge and put
    // this node back onto the stack. Along with the node, we also store the
    // path through nodes to branch1 and branch2. Of course there is no path
    // from this virtual node to anywhere, so we make one by rebasing the
    // path from one of the ancestor nodes (it doesn't matter which) to the
    // target onto the merge operation from that ancestor to the new node.
    // (Maybe choosing the shortest paths will produce better merges?)
    var node = Symbol(/*node_name_str(lca1.node) + "|" + node_name_str(lca2.node)*/);
    graph[node] = {
      parents: [lca1.node, lca2.node],
      op: merge_ops
    }
    
    let document1 = get_document_content_at(lca1.node);

    /*
    console.log("Rebasing")
    keys(lca1.paths).map(target => console.log(" ", target, lca1.paths[target].inspect()));
    console.log("against the merge commit's first operation");
    console.log("which is at", lca1.node, "document:", document1);
    */
  
    lca.push({
      node: node,
      paths: {
        [branch1]: lca1.paths[branch1].rebase(merge_ops[0], { document: document1 }),
        [branch2]: lca1.paths[branch2].rebase(merge_ops[0], { document: document1 })
      }
    });

    /*
    console.log("Making virtual node (", lca1.node, lca2.node, ") with paths to targets:");
    keys(lca[lca.length-1].paths).map(target => console.log(" ", target, lca[lca.length-1].paths[target].inspect()));
    */
  }

  // Take the one common ancestors.
  lca = lca.pop();

  /*
  console.log("Common ancestor:", lca.node, "Paths to targets:");
  keys(lca.paths).map(target => console.log(" ", target, lca.paths[target].inspect()));
  */

  // Get the JOT operations from the ancestor to branch1 and branch2.
  var branch1_op = lca.paths[branch1];
  var branch2_op = lca.paths[branch2];

  // Get the document content at the common ancestor.
  let document = get_document_content_at(lca.node);
  //console.log("document:", document);

  // Rebase and return the operations that would 1) merge branch2 into branch1
  // and 2) branch1 into branch2.
  let merge = [
    branch2_op.rebase(branch1_op, { document: document }),
    branch1_op.rebase(branch2_op, { document: document })
  ];
  //console.log("Merge:", merge[0].inspect(), merge[1].inspect());
  return merge;
}

function lowest_common_ancestors(a, b, graph) {
  // Find the lowest common ancestors of a and b (i.e. an
  // ancestor of both that is not the ancestor of another common
  // ancestor). There may be more than one equally-near ancestors.
  //
  // For each such ancestor, return the path(s) to a and b. There
  // may be multiple paths to each of a and b.
  //
  // We do this by traversing the graph starting first at a and then at b.
  let reach_paths = { };
  let descendants = { };
  let queue = [ // node  paths to a & b  descendant nodes
                 [ a,    { [a]: [[]] },  {} ],
                 [ b,    { [b]: [[]] },  {} ]];
  while (queue.length > 0) {
    // Pop an item from the queue.
    let [node, paths, descs] = queue.shift();
    if (!graph.hasOwnProperty(node)) throw "Invalid node: " + node;
    
    // Update its reach_paths flags.
    if (!reach_paths.hasOwnProperty(node)) reach_paths[node] = { };
    keys(paths).forEach(target => {
      if (!reach_paths[node].hasOwnProperty(target)) reach_paths[node][target] = [ ];
      paths[target].forEach(path =>
        reach_paths[node][target].push(path))
    });

    // Update its descendants.
    if (!descendants.hasOwnProperty(node)) descendants[node] = { };
    Object.assign(descendants[node], descs);

    // Queue its parents, passing the reachability paths and descendants.
    // Add this node to the descendants and path. To the path, add the
    // index too.
    let de = { [node]: true };
    Object.assign(de, descs);
    (graph[node].parents || []).forEach((p, i) => {
      let pa = { };
      keys(paths).forEach(pkey => {
        pa[pkey] = paths[pkey].map(path => [[node, i]].concat(path));
      })

      queue.push([p, pa, de])
    });
  }

  // Take the common ancetors.
  var common_ancestors = keys(reach_paths).filter(
    n => reach_paths[n].hasOwnProperty(a) && reach_paths[n].hasOwnProperty(b)
  );

  // Remove the common ancestors that have common ancestors as descendants.
  function object_contains_any(obj, elems) {
    for (let i = 0; i < elems.length; i++)
      if (obj.hasOwnProperty(elems[i]))
        return true;
    return false;
  }
  var lowest_common_ancestors = common_ancestors.filter(
    n => !object_contains_any(descendants[n], common_ancestors)
  );

  // Return the lowest common ancestors and, for each, return an
  // object that holds the node id plus the operations from the
  // ancestor to the target nodes a and b. Each item on the computed
  // path is a node ID and the index of the incoming edge to the node
  // on the path. For simple nodes, the edge index is always zero. For
  // merges, there are multiple parents, and we need to get the operation
  // that represents the change from the *corresponding* parent.
  function get_paths_op(n) {
    let paths = { };
    keys(reach_paths[n]).forEach(target => {
      // There may be multiple paths from the ancestor to the target, but
      // we only need one. Take the first one. Maybe taking the shortest
      // will make better diffs in very complex scenarios?
      let path = reach_paths[n][target][0];
      paths[target] = new jot.LIST(path.map(path_item => graph[path_item[0]].op[path_item[1]])).simplify();
    });
    return paths;
  }
  return lowest_common_ancestors.map(n => { return {
    'node': n,
    'paths': get_paths_op(n)
  }});
}

exports.Revision = class {
  constructor(ops, parents, document) {
    this.ops = ops;
    this.parents = parents || [];
    this.document = document;
    this.id = Symbol(/*Math.floor(Math.random() * 10000)*/);
  }

  add_to_graph(graph) {
    // Add the parents.
    this.parents.forEach(p => p.add_to_graph(graph));

    // Add this node.
    graph[this.id] = {
      parents: this.parents.map(p => p.id),
      op: this.ops,
      document: this.document
    };
  }
}

exports.Document = class {
  constructor(name) {
    this.name = name || "";
    this.history = [new exports.Revision(null, null, null, "singularity")];
    this.commit_count = 0;
    this.branch_count = 0;
  }

  commit(op) {
    if (!(op instanceof jot.Operation)) {
      // If 'op' is not an operation, it is document content. Do a diff
      // against the last content to make an operation.
      op = jot.diff(this.content(), op);
    }

    let rev = new exports.Revision(
      [op],
      [this.head()],
      op.apply(this.content()),
      (this.name||"") + "+" + (++this.commit_count)
    );
    this.history.push(rev);
    this.branch_count = 0;
    return rev;
  }
  
  branch(name) {
    let br = new exports.Document(
        (this.name ? (this.name + "_") : "")
      + (name || (++this.branch_count)));
    br.history = [].concat(this.history); // shallow clone
    return br;
  }

  head() {
    return this.history[this.history.length-1];
  }

  content() {
    return this.head().document;
  }

  merge(b) {
    // Form a graph of the complete known history of the two branches.
    let graph = { };
    this.head().add_to_graph(graph);
    b.head().add_to_graph(graph);

    // Perform the merge. Two operations are returned. The first merges
    // b into this, and the second merges this into b.
    let op = exports.merge(this.head().id, b.head().id, graph);

    // Make a commit on this branch that merges b into this.
    let rev = this.commit(op[0])

    // Add the second parent and the second merge operation.
    rev.parents.push(b.head());
    rev.ops.push(op[1]);
    return rev;
  }
};



================================================
FILE: jot/objects.js
================================================
/* A library of operations for objects (i.e. JSON objects/Javascript associative arrays).

   new objects.PUT(key, value)
    
    Creates a property with the given value. This is an alias for
    new objects.APPLY(key, new values.SET(value)).

   new objects.REM(key)
    
    Removes a property from an object. This is an alias for
    new objects.APPLY(key, new values.SET(objects.MISSING)).

   new objects.APPLY(key, operation)
   new objects.APPLY({key: operation, ...})

    Applies any operation to a property, or multiple operations to various
    properties, on the object.

    Use any operation defined in any of the modules depending on the data type
    of the property. For instance, the operations in values.js can be
    applied to any property. The operations in sequences.js can be used
    if the property's value is a string or array. And the operations in
    this module can be used if the value is another object.

    Supports a conflictless rebase with itself with the inner operations
    themselves support a conflictless rebase. It does not generate conflicts
    with any other operations in this module.

    Example:
    
    To replace the value of a property with a new value:
    
      new objects.APPLY("key1", new values.SET("value"))

	or

      new objects.APPLY({ key1: new values.SET("value") })

   */
   
var util = require('util');

var deepEqual = require("deep-equal");
var shallow_clone = require('shallow-clone');

var jot = require("./index.js");
var values = require("./values.js");
var LIST = require("./lists.js").LIST;

//////////////////////////////////////////////////////////////////////////////

exports.module_name = 'objects'; // for serialization/deserialization

exports.APPLY = function () {
	if (arguments.length == 1 && typeof arguments[0] == "object") {
		// Dict form.
		this.ops = arguments[0];
	} else if (arguments.length == 2 && typeof arguments[0] == "string") {
		// key & operation form.
		this.ops = { };
		this.ops[arguments[0]] = arguments[1];
	} else {
		throw new Error("invalid arguments");
	}
	Object.freeze(this);
	Object.freeze(this.ops);
}
exports.APPLY.prototype = Object.create(jot.Operation.prototype); // inherit
jot.add_op(exports.APPLY, exports, 'APPLY');

// The MISSING object is a sentinel to signal the state of an Object property
// that does not exist. It is the old_value to SET when adding a new property
// and the value when removing a property.
exports.MISSING = new Object();
Object.freeze(exports.MISSING);

exports.PUT = function (key, value) {
	exports.APPLY.apply(this, [key, new values.SET(value)]);
}
exports.PUT.prototype = Object.create(exports.APPLY.prototype); // inherit prototype

exports.REM = function (key) {
	exports.APPLY.apply(this, [key, new values.SET(exports.MISSING)]);
}
exports.REM.prototype = Object.create(exports.APPLY.prototype); // inherit prototype

//////////////////////////////////////////////////////////////////////////////

exports.APPLY.prototype.inspect = function(depth) {
	var inner = [];
	var ops = this.ops;
	Object.keys(ops).forEach(function(key) {
		inner.push(util.format("%j:%s", key, ops[key].inspect(depth-1)));
	});
	return util.format("<APPLY %s>", inner.join(", "));
}

exports.APPLY.prototype.visit = function(visitor) {
	// A simple visitor paradigm. Replace this operation instance itself
	// and any operation within it with the value returned by calling
	// visitor on itself, or if the visitor returns anything falsey
	// (probably undefined) then return the operation unchanged.
	var ops = { };
	for (var key in this.ops)
		ops[key] = this.ops[key].visit(visitor);
	var ret = new exports.APPLY(ops);
	return visitor(ret) || ret;
}

exports.APPLY.prototype.internalToJSON = function(json, protocol_version) {
	json.ops = { };
	for (var key in this.ops)
		json.ops[key] = this.ops[key].toJSON(undefined, protocol_version);
}

exports.APPLY.internalFromJSON = function(json, protocol_version, op_map) {
	var ops = { };
	for (var key in json.ops)
		ops[key] = jot.opFromJSON(json.ops[key], protocol_version, op_map);
	return new exports.APPLY(ops);
}

exports.APPLY.prototype.apply = function (document) {
	/* Applies the operation to a document. Returns a new object that is
	   the same type as document but with the change made. */

	// Clone first.
	var d = { };
	for (var k in document)
		d[k] = document[k];

	// Apply. Pass the object and key down in the second argument
	// to apply so that values.SET can handle the special MISSING
	// value.
	for (var key in this.ops) {
		var value = this.ops[key].apply(d[key], [d, key]);
		if (value === exports.MISSING)
			delete d[key]; // key was removed
		else
			d[key] = value;
	}
	return d;
}

exports.APPLY.prototype.simplify = function () {
	/* Returns a new atomic operation that is a simpler version
	   of this operation. If there is no sub-operation that is
	   not a NO_OP, then return a NO_OP. Otherwise, simplify all
	   of the sub-operations. */
	var new_ops = { };
	var had_non_noop = false;
	for (var key in this.ops) {
		new_ops[key] = this.ops[key].simplify();
		if (!(new_ops[key] instanceof values.NO_OP))
			// Remember that we have a substantive operation.
			had_non_noop = true;
		else
			// Drop internal NO_OPs.
			delete new_ops[key];
	}
	if (!had_non_noop)
		return new values.NO_OP();
	return new exports.APPLY(new_ops);
}

exports.APPLY.prototype.inverse = function (document) {
	/* Returns a new atomic operation that is the inverse of this operation,
	   given the state of the document before this operation applies. */
	var new_ops = { };
	for (var key in this.ops) {
		new_ops[key] = this.ops[key].inverse(key in document ? document[key] : exports.MISSING);
	}
	return new exports.APPLY(new_ops);
}

exports.APPLY.prototype.atomic_compose = function (other) {
	/* Creates a new atomic operation that has the same result as this
	   and other applied in sequence (this first, other after). Returns
	   null if no atomic operation is possible. */

	// two APPLYs
	if (other instanceof exports.APPLY) {
		// Start with a clone of this operation's suboperations.
		var new_ops = shallow_clone(this.ops);

		// Now compose with other.
		for (var key in other.ops) {
			if (!(key in new_ops)) {
				// Operation in other applies to a key not present
				// in this, so we can just merge - the operations
				// happen in parallel and don't affect each other.
				new_ops[key] = other.ops[key];
			} else {
				// Compose.
				var op2 = new_ops[key].compose(other.ops[key]);

				// They composed to a no-op, so delete the
				// first operation.
				if (op2 instanceof values.NO_OP)
					delete new_ops[key];

				else
					new_ops[key] = op2;
			}
		}

		return new exports.APPLY(new_ops).simplify();
	}

	// No composition possible.
	return null;
}

exports.APPLY.prototype.rebase_functions = [
	[exports.APPLY, function(other, conflictless) {
		// Rebase the sub-operations on corresponding keys.
		// If any rebase fails, the whole rebase fails.

		// When conflictless is supplied with a prior document state,
		// the state represents the object, so before we call rebase
		// on inner operations, we have to go in a level on the prior
		// document.
		function build_conflictless(key) {
			if (!conflictless || !("document" in conflictless))
				return conflictless;
			var ret = shallow_clone(conflictless);
			if (!(key in conflictless.document))
				// The key being modified isn't present yet.
				ret.document = exports.MISSING;
			else
				ret.document = conflictless.document[key];
			return ret;
		}

		var new_ops_left = { };
		for (var key in this.ops) {
			new_ops_left[key] = this.ops[key];
			if (key in other.ops)
				new_ops_left[key] = new_ops_left[key].rebase(other.ops[key], build_conflictless(key));
			if (new_ops_left[key] === null)
				return null;
		}

		var new_ops_right = { };
		for (var key in other.ops) {
			new_ops_right[key] = other.ops[key];
			if (key in this.ops)
				new_ops_right[key] = new_ops_right[key].rebase(this.ops[key], build_conflictless(key));
			if (new_ops_right[key] === null)
				return null;
		}

		return [
			new exports.APPLY(new_ops_left).simplify(),
			new exports.APPLY(new_ops_right).simplify()
		];
	}]
]

exports.APPLY.prototype.drilldown = function(index_or_key) {
	if (typeof index_or_key == "string" && index_or_key in this.ops)
		return this.ops[index_or_key];
	return new values.NO_OP();
}

exports.createRandomOp = function(doc, context) {
	// Create a random operation that could apply to doc.
	// Choose uniformly across various options.
	var ops = [];

	// Add a random key with a random value.
	ops.push(function() { return new exports.PUT("k"+Math.floor(1000*Math.random()), jot.createRandomValue()); });

	// Apply random operations to individual keys.
	Object.keys(doc).forEach(function(key) {
		ops.push(function() { return jot.createRandomOp(doc[key], "object") });
	});

	// Select randomly.
	return ops[Math.floor(Math.random() * ops.length)]();
}


================================================
FILE: jot/sequences.js
================================================
/* An operational transformation library for sequence-like objects,
   i.e. strings and arrays.

   The main operation provided by this library is PATCH, which represents
   a set of non-overlapping changes to a string or array. Each change,
   called a hunk, applies an operation to a subsequence -- i.e. a sub-string
   or a slice of the array. The operation's .apply method yields a new
   sub-sequence, and they are stitched together (along with unchanged elements)
   to form the new document that results from the PATCH operation.

   The internal structure of the PATCH operation is an array of hunks as
   follows:

   new sequences.PATCH(
     [
       { offset: ..., # unchanged elements to skip before this hunk
         length: ..., # length of subsequence modified by this hunk
         op: ...      # jot operation to apply to the subsequence
       },
       ...
     ]
    )

   The inner operation must be one of NO_OP, SET, and MAP (or any
   operation that defines "get_length_change" and "decompose" functions
   and whose rebase always yields an operation that also satisfies these
   same constraints.)


   This library also defines the MAP operation, which applies a jot
   operation to every element of a sequence. The MAP operation is
   also used with length-one hunks to apply an operation to a single
   element. On strings, the MAP operation only accepts inner operations
   that yield back single characters so that a MAP on a string does
   not change the string's length.

   The internal structure of the MAP operation is:

   new sequences.MAP(op)
 
   Shortcuts for constructing useful PATCH operations are provided:

		new sequences.SPLICE(pos, length, value)

			 Equivalent to:

			 PATCH([{
				 offset: pos,
				 length: length,
				 op: new values.SET(value)
				 }])
			 
			 i.e. replace elements with other elements
		
		new sequences.ATINDEX(pos, op)

			 Equivalent to:

			 PATCH([{
				 offset: pos,
				 length: 1,
				 op: new sequences.MAP(op)
				 }])
			 
			 i.e. apply the operation to the single element at pos

		new sequences.ATINDEX({ pos: op, ... })

			 Similar to the above but for multiple operations at once.

		Supports a conflictless rebase with other PATCH operations.

	 */
	 
var util = require('util');

var deepEqual = require("deep-equal");
var shallow_clone = require('shallow-clone');

var jot = require("./index.js");
var values = require("./values.js");
var LIST = require("./lists.js").LIST;

// utilities

function elem(seq, pos) {
	// get an element of the sequence
	if (typeof seq == "string")
		return seq.charAt(pos);
	else // is an array
		return seq[pos];
}
function concat2(item1, item2) {
	if (item1 instanceof String)
		return item1 + item2;
	return item1.concat(item2);
}
function concat3(item1, item2, item3) {
	if (item1 instanceof String)
		return item1 + item2 + item3;
	return item1.concat(item2).concat(item3);
}
function map_index(pos, move_op) {
	if (pos >= move_op.pos && pos < move_op.pos+move_op.count) return (pos-move_op.pos) + move_op.new_pos; // within the move
	if (pos < move_op.pos && pos < move_op.new_pos) return pos; // before the move
	if (pos < move_op.pos) return pos + move_op.count; // a moved around by from right to left
	if (pos > move_op.pos && pos >= move_op.new_pos) return pos; // after the move
	if (pos > move_op.pos) return pos - move_op.count; // a moved around by from left to right
	throw new Error("unhandled problem");
}

//////////////////////////////////////////////////////////////////////////////

exports.module_name = 'sequences'; // for serialization/deserialization

exports.PATCH = function () {
	/* An operation that replaces a subrange of the sequence with new elements. */
	if (arguments[0] === "__hmm__") return; // used for subclassing
	if (arguments.length != 1)
		throw new Error("Invaid Argument");

	this.hunks = arguments[0];

	// Sanity check & freeze hunks.
	if (!Array.isArray(this.hunks))
		throw new Error("Invaid Argument");
	this.hunks.forEach(function(hunk) {
		if (typeof hunk.offset != "number")
			throw new Error("Invalid Argument (hunk offset not a number)");
		if (hunk.offset < 0)
			throw new Error("Invalid Argument (hunk offset is negative)");
		if (typeof hunk.length != "number")
			throw new Error("Invalid Argument (hunk length is not a number)");
		if (hunk.length < 0)
			throw new Error("Invalid Argument (hunk length is negative)");
		if (!(hunk.op instanceof jot.Operation))
			throw new Error("Invalid Argument (hunk operation is not an operation)");
		if (typeof hunk.op.get_length_change != "function")
			throw new Error("Invalid Argument (hunk operation " + hunk.op.inspect() + " does not support get_length_change)");
		if (typeof hunk.op.decompose != "function")
			throw new Error("Invalid Argument (hunk operation " + hunk.op.inspect() + " does not support decompose)");
		Object.freeze(hunk);
	});

	Object.freeze(this);
}
exports.PATCH.prototype = Object.create(jot.Operation.prototype); // inherit
jot.add_op(exports.PATCH, exports, 'PATCH');

	// shortcuts

	exports.SPLICE = function (pos, length, value) {
		// value.slice(0,0) is a shorthand for constructing an empty string or empty list, generically
		exports.PATCH.apply(this, [[{
			offset: pos,
			length: length,
			op: new values.SET(value)
		}]]);
	}
	exports.SPLICE.prototype = new exports.PATCH("__hmm__"); // inherit prototype

	exports.ATINDEX = function () {
		var indexes;
		var op_map;
		if (arguments.length == 1) {
			// The argument is a mapping from indexes to operations to apply
			// at those indexes. Collect all of the integer indexes in sorted
			// order.
			op_map = arguments[0];
			indexes = [];
			Object.keys(op_map).forEach(function(index) { indexes.push(parseInt(index)); });
			indexes.sort();
		} else if (arguments.length == 2) {
			// The arguments are just a single position and operation.
			indexes = [arguments[0]];
			op_map = { };
			op_map[arguments[0]] = arguments[1];
		} else {
			throw new Error("Invalid Argument")
		}

		// Form hunks.
		var hunks = [];
		var offset = 0;
		indexes.forEach(function(index) {
			hunks.push({
				offset: index-offset,
				length: 1,
				op: new exports.MAP(op_map[index])
			})
			offset = index+1;
		});
		exports.PATCH.apply(this, [hunks]);
	}
	exports.ATINDEX.prototype = new exports.PATCH("__hmm__"); // inherit prototype

exports.MAP = function (op) {
	if (op == null) throw new Error("Invalid Argument");
	this.op = op;
	Object.freeze(this);
}
exports.MAP.prototype = Object.create(jot.Operation.prototype); // inherit
jot.add_op(exports.MAP, exports, 'MAP');

//////////////////////////////////////////////////////////////////////////////

exports.PATCH.prototype.inspect = function(depth) {
	return util.format("<PATCH%s>",
		this.hunks.map(function(hunk) {
			if ((hunk.length == 1) && (hunk.op instanceof exports.MAP))
				// special format
				return util.format(" +%d %s",
					hunk.offset,
					hunk.op.op.inspect(depth-1));

			return util.format(" +%dx%d %s",
				hunk.offset,
				hunk.length,
				hunk.op instanceof values.SET
					? util.format("%j", hunk.op.value)
					: hunk.op.inspect(depth-1))
		}).join(","));
}

exports.PATCH.prototype.visit = function(visitor) {
	// A simple visitor paradigm. Replace this operation instance itself
	// and any operation within it with the value returned by calling
	// visitor on itself, or if the visitor returns anything falsey
	// (probably undefined) then return the operation unchanged.
	var ret = new exports.PATCH(this.hunks.map(function(hunk) {
		var ret = shallow_clone(hunk);
		ret.op = ret.op.visit(visitor);
		return ret;
	}));
	return visitor(ret) || ret;
}

exports.PATCH.prototype.internalToJSON = function(json, protocol_version) {
	json.hunks = this.hunks.map(function(hunk) {
		var ret = shallow_clone(hunk);
		ret.op = ret.op.toJSON(undefined, protocol_version);
		return ret;
	});
}

exports.PATCH.internalFromJSON = function(json, protocol_version, op_map) {
	var hunks = json.hunks.map(function(hunk) {
		var ret = shallow_clone(hunk);
		ret.op = jot.opFromJSON(hunk.op, protocol_version, op_map);
		return ret;
	});
	return new exports.PATCH(hunks);
}

exports.PATCH.prototype.apply = function (document) {
	/* Applies the operation to a document. Returns a new sequence that is
		 the same type as document but with the hunks applied. */
	
	var index = 0;
	var ret = document.slice(0,0); // start with an empty document
	
	this.hunks.forEach(function(hunk) {
		if (index + hunk.offset + hunk.length > document.length)
			throw new Error("offset past end of document");

		// Append unchanged content before this hunk.
		ret = concat2(ret, document.slice(index, index+hunk.offset));
		index += hunk.offset;

		// Append new content.
		var new_value = hunk.op.apply(document.slice(index, index+hunk.length));

		if (typeof document == "string" && typeof new_value != "string")
			throw new Error("operation yielded invalid substring");
		if (Array.isArray(document) && !Array.isArray(new_value))
			throw new Error("operation yielded invalid subarray");

		ret = concat2(ret, new_value);

		// Advance counter.
		index += hunk.length;
	});
	
	// Append unchanged content after the last hunk.
	ret = concat2(ret, document.slice(index));
	
	return ret;
}

exports.PATCH.prototype.simplify = function () {
	/* Returns a new atomic operation that is a simpler version
		 of this operation.*/

	// Simplify the hunks by removing any that don't make changes.
	// Adjust offsets.

	// Some of the composition methods require knowing if these operations
	// are operating on a string or an array. We might not know if the PATCH
	// only has sub-operations where we can't tell, like a MAP.
	var doctype = null;
	this.hunks.forEach(function (hunk) {
		if (hunk.op instanceof values.SET) {
			if (typeof hunk.op.value == "string")
				doctype = "string";
			else if (Array.isArray(hunk.op.value))
				doctype = "array";
		}
	});

	// Form a new set of merged hunks.

	var hunks = [];
	var doffset = 0;

	function handle_hunk(hunk) {
		var op = hunk.op.simplify();
		
		if (op.isNoOp()) {
			// Drop it, but adjust future offsets.
			doffset += hunk.offset + hunk.length;
			return;

		} else if (hunk.length == 0 && hunk.op.get_length_change(hunk.length) == 0) {
			// The hunk does nothing. Drop it, but adjust future offsets.
			doffset += hunk.offset;
			return;

		} else if (hunks.length > 0
			&& hunk.offset == 0
			&& doffset == 0
			) {
			
			// The hunks are adjacent. We can combine them
			// if one of the operations is a SET and the other
			// is a SET or a MAP containing a SET.
			// We can't combine two adjancent MAP->SET's because
			// we wouldn't know whether the combined value (in
			// a SET) should be a string or an array.
			if ((hunks[hunks.length-1].op instanceof values.SET
				|| (hunks[hunks.length-1].op instanceof exports.MAP && hunks[hunks.length-1].op.op instanceof values.SET))
			 && (hunk.op instanceof values.SET || 
			 	  (hunk.op instanceof exports.MAP && hunk.op.op instanceof values.SET) )
			 && doctype != null) {

				function get_value(hunk) {
				 	if (hunk.op instanceof values.SET) {
				 		// The value is just the SET's value.
				 		return hunk.op.value;
				 	} else {
				 		// The value is a sequence of the hunk's length
				 		// where each element is the value of the inner
				 		// SET's value.
					 	var value = [];
					 	for (var i = 0; i < hunk.length; i++)
					 		value.push(hunk.op.op.value);

					 	// If the outer value is a string, reform it as
					 	// a string.
					 	if (doctype == "string")
					 		value = value.join("");
					 	return value;
				 	}
				}

				hunks[hunks.length-1] = {
					offset: hunks[hunks.length-1].offset,
					length: hunks[hunks.length-1].length + hunk.length,
					op: new values.SET(
						concat2(
							get_value(hunks[hunks.length-1]),
							get_value(hunk))
						)
				};

				return;
			}

		}

		// Preserve but adjust offset.
		hunks.push({
			offset: hunk.offset+doffset,
			length: hunk.length,
			op: op
		});
		doffset = 0;
	}
	
	this.hunks.forEach(handle_hunk);
	if (hunks.length == 0)
		return new values.NO_OP();
	
	return new exports.PATCH(hunks);
}

exports.PATCH.prototype.drilldown = function(index_or_key) {
	if (!Number.isInteger(index_or_key) || index_or_key < 0)
		return new values.NO_OP();
	var index = 0;
	var ret = null;
	this.hunks.forEach(function(hunk) {
		index += hunk.offset;
		if (index <= index_or_key && index_or_key < index+hunk.length)
			ret = hunk.op.drilldown(index_or_key-index);
		index += hunk.length;
	})
	return ret ? ret : new values.NO_OP();
}

exports.PATCH.prototype.inverse = function (document) {
	/* Returns a new atomic operation that is the inverse of this operation,
	   given the state of the document before this operation applies.
	   The inverse simply inverts the operations on the hunks, but the
	   lengths have to be fixed. */
	var offset = 0;
	return new exports.PATCH(this.hunks.map(function(hunk) {
		var newhunk = {
			offset: hunk.offset,
			length: hunk.length + hunk.op.get_length_change(hunk.length),
			op: hunk.op.inverse(document.slice(offset+hunk.offset, offset+hunk.offset+hunk.length))
		}
		offset += hunk.offset + hunk.length;
		return newhunk;
	}));
}

function compose_patches(a, b) {
	// Compose two patches. We do this as if we are zipping up two sequences,
	// where the index into the (hypothetical) sequence that results *after*
	// a is applied lines up with the index into the (hypothetical) sequence
	// before b is applied.
	
	var hunks = [];
	var index = 0;

	function make_state(op, side) {
		return {
			index: 0,
			hunks: op.hunks.slice(), // clone
			empty: function() { return this.hunks.length == 0; },
			take: function() {
				var curend = this.end();
				var h = this.hunks.shift();
				hunks.push({
					offset: this.index + h.offset - index,
					length: h.length,
					op: h.op
				});
				this.index = curend;
				index = this.index;
			},
			skip: function() {
				this.index = this.end();
				this.hunks.shift();
			},
			start: function() {
				return this.index + this.hunks[0].offset;
			},
			end: function() {
				var h = this.hunks[0];
				var ret = this.index + h.offset + h.length;
				if (side == 0)
					ret += h.op.get_length_change(h.length);
				return ret;
			}
		}
	}
	
	var a_state = make_state(a, 0),
	    b_state = make_state(b, 1);
	
	while (!a_state.empty() || !b_state.empty()) {
		// Only operations in 'a' are remaining.
		if (b_state.empty()) {
			a_state.take();
			continue;
		}

		// Only operations in 'b' are remaining.
		if (a_state.empty()) {
			b_state.take();
			continue;
		}

		// The next hunk in 'a' precedes the next hunk in 'b'.
		if (a_state.end() <= b_state.start()) {
			a_state.take();
			continue;
		}

		// The next hunk in 'b' precedes the next hunk in 'a'.
		if (b_state.end() <= a_state.start()) {
			b_state.take();
			continue;
		}

		// There's overlap.

		var dx_start = b_state.start() - a_state.start();
		var dx_end = b_state.end() - a_state.end();
		if (dx_start >= 0 && dx_end <= 0) {
			// 'a' wholly encompasses 'b', including the case where they
			// changed the exact same elements.

			// Compose a's and b's suboperations using
			// atomic_compose. If the two hunks changed the exact same
			// elements, then we can compose the two operations directly.
			var b_op = b_state.hunks[0].op;
			var dx = b_op.get_length_change(b_state.hunks[0].length);
			if (dx_start != 0 || dx_end != 0) {
				// If a starts before b, wrap b_op in a PATCH operation
				// so that they can be considered to start at the same
				// location.
				b_op = new exports.PATCH([{ offset: dx_start, length: b_state.hunks[0].length, op: b_op }]);
			}

			// Try an atomic composition.
			var ab = a_state.hunks[0].op.atomic_compose(b_op);
			if (!ab && dx_start == 0 && dx_end == 0 && b_op instanceof exports.MAP && b_op.op instanceof values.SET)
				ab = b_op;

			if (ab) {
				// Replace the 'a' operation with itself composed with b's operation.
				// Don't take it yet because there could be more coming on b's
				// side that is within the range of 'a'.
				a_state.hunks[0] = {
					offset: a_state.hunks[0].offset,
					length: a_state.hunks[0].length,
					op: ab
				};

				// Since the a_state hunks have been rewritten, the indexing needs
				// to be adjusted.
				b_state.index += dx;

				// Drop b.
				b_state.skip();
				continue;
			}

			// If no atomic composition is possible, another case may work below
			// by decomposing the operations.
		}

		// There is some sort of other overlap. We can handle this by attempting
		// to decompose the operations.
		if (dx_start > 0) {
			// 'a' begins first. Attempt to decompose it into two operations.
			// Indexing of dx_start is based on the value *after* 'a' applies,
			// so we have to decompose it based on new-value indexes.
			var decomp = a_state.hunks[0].op.decompose(true, dx_start);

			// But we need to know the length of the original hunk so that
			// the operation causes its final length to be dx_start.
			var alen0;
			if (a_state.hunks[0].op.get_length_change(a_state.hunks[0].length) == 0)
				// This is probably a MAP. If the hunk's length is dx_start
				// and the operation causes no length change, then that's
				// the right length!
				alen0 = dx_start;
			else
				return null;

			// Take the left part of the decomposition.
			hunks.push({
				offset: a_state.index + a_state.hunks[0].offset - index,
				length: alen0,
				op: decomp[0]
			});
			a_state.index = a_state.start() + dx_start;
			index = a_state.index;

			// Return the right part of the decomposition to the hunks array.
			a_state.hunks[0] = {
				offset: 0,
				length: a_state.hunks[0].length - alen0,
				op: decomp[1]
			};
			continue;
		}

		if (dx_start < 0) {
			// 'b' begins first. Attempt to decompose it into two operations.
			var decomp = b_state.hunks[0].op.decompose(false, -dx_start);

			// Take the left part of the decomposition.
			hunks.push({
				offset: b_state.index + b_state.hunks[0].offset - index,
				length: (-dx_start),
				op: decomp[0]
			});
			b_state.index = b_state.start() + (-dx_start);
			index = b_state.index;

			// Return the right part of the decomposition to the hunks array.
			b_state.hunks[0] = {
				offset: 0,
				length: b_state.hunks[0].length - (-dx_start),
				op: decomp[1]
			};
			continue;
		}

		// The two hunks start at the same location but have different
		// lengths.
		if (dx_end > 0) {
			// 'b' wholly encompasses 'a'.
			if (b_state.hunks[0].op instanceof values.SET) {
				// 'b' is replacing everything 'a' touched with
				// new elements, so the changes in 'a' can be
				// dropped. But b's length has to be updated
				// if 'a' changed the length of its subsequence.
				var dx = a_state.hunks[0].op.get_length_change(a_state.hunks[0].length);
				b_state.hunks[0] = {
					offset: b_state.hunks[0].offset,
					length: b_state.hunks[0].length - dx,
					op: b_state.hunks[0].op
				};
				a_state.skip();
				a_state.index -= dx;
				continue;
			}
		}

		// TODO.

		// There is no atomic composition.
		return null;
	}

	return new exports.PATCH(hunks).simplify();
}

exports.PATCH.prototype.atomic_compose = function (other) {
	/* Creates a new atomic operation that has the same result as this
		 and other applied in sequence (this first, other after). Returns
		 null if no atomic operation is possible. */

	// a PATCH composes with a PATCH
	if (other instanceof exports.PATCH)
		return compose_patches(this, other);

	// No composition possible.
	return null;
}

function rebase_patches(a, b, conflictless) {
	// Rebasing two PATCHes works like compose, except that we are aligning
	// 'a' and 'b' both on the state of the document before each has applied.
	//
	// We do this as if we are zipping up two sequences, where the index into
	// the (hypothetical) sequence, before either operation applies, lines
	// up across the two operations.
	
	function make_state(op) {
		return {
			old_index: 0,
			old_hunks: op.hunks.slice(),
			dx_index: 0,
			new_hunks: [],
			empty: function() { return this.old_hunks.length == 0; },
			take: function(other, hold_dx_index) {
				var h = this.old_hunks.shift();
				this.new_hunks.push({
					offset: h.offset + this.dx_index,
					length: h.length+(h.dlength||0),
					op: h.op
				});
				this.dx_index = 0;
				this.old_index += h.offset + h.length;
				if (!hold_dx_index) other.dx_index += h.op.get_length_change(h.length);
			},
			skip: function() {
				this.old_index = this.end();
				this.old_hunks.shift();
			},
			start: function() {
				return this.old_index + this.old_hunks[0].offset;
			},
			end: function() {
				var h = this.old_hunks[0];
				return this.old_index + h.offset + h.length;
			}
		}
	}
	
	var a_state = make_state(a),
	    b_state = make_state(b);
	
	while (!a_state.empty() || !b_state.empty()) {
		// Only operations in 'a' are remaining.
		if (b_state.empty()) {
			a_state.take(b_state);
			continue;
		}

		// Only operations in 'b' are remaining.
		if (a_state.empty()) {
			b_state.take(a_state);
			continue;
		}

		// Two insertions at the same location.
		if (a_state.start() == b_state.start()
			&& a_state.old_hunks[0].length == 0
			&& b_state.old_hunks[0].length == 0) {
			
			// This is a conflict because we don't know which side
			// gets inserted first.
			if (!conflictless)
				return null;

			// Or we can resolve the conflict.
			if (jot.cmp(a_state.old_hunks[0].op, b_state.old_hunks[0].op) == 0) {
				// If the inserted values are identical, we can't make a decision
				// about which goes first, so we only take one. Which one we take
				// doesn't matter because the document comes out the same either way.
				// This logic is actually required to get complex merges to work.
				b_state.take(b_state);
				a_state.skip();
			} else if (jot.cmp(a_state.old_hunks[0].op, b_state.old_hunks[0].op) < 0) {
				a_state.take(b_state);
			} else {
				b_state.take(a_state);
			}
			continue;
		}


		// The next hunk in 'a' precedes the next hunk in 'b'.
		// Take 'a' and adjust b's next offset.
		if (a_state.end() <= b_state.start()) {
			a_state.take(b_state);
			continue;
		}

		// The next hunk in 'b' precedes the next hunk in 'a'.
		// Take 'b' and adjust a's next offset.
		if (b_state.end() <= a_state.start()) {
			b_state.take(a_state);
			continue;
		}

		// There's overlap.

		var dx_start = b_state.start() - a_state.start();
		var dx_end = b_state.end() - a_state.end();

		// They both affected the exact same region, so just rebase the
		// inner operations and update lengths.
		if (dx_start == 0 && dx_end == 0) {
			// When conflictless is supplied with a prior document state,
			// the state represents the sequence, so we have to dig into
			// it and pass an inner value
			var conflictless2 = !conflictless ? null : shallow_clone(conflictless);
			if (conflictless2 && "document" in conflictless2)
				conflictless2.document = conflictless2.document.slice(a_state.start(), a_state.end());

			var ar = a_state.old_hunks[0].op.rebase(b_state.old_hunks[0].op, conflictless2);
			var br = b_state.old_hunks[0].op.rebase(a_state.old_hunks[0].op, conflictless2);
			if (ar == null || br == null)
				return null;

			a_state.old_hunks[0] = {
				offset: a_state.old_hunks[0].offset,
				length: a_state.old_hunks[0].length,
				dlength: b_state.old_hunks[0].op.get_length_change(b_state.old_hunks[0].length),
				op: ar
			}
			b_state.old_hunks[0] = {
				offset: b_state.old_hunks[0].offset,
				length: b_state.old_hunks[0].length,
				dlength: a_state.old_hunks[0].op.get_length_change(a_state.old_hunks[0].length),
				op: br
			}
			a_state.take(b_state, true);
			b_state.take(a_state, true);
			continue;
		}

		// Other overlaps generate conflicts.
		if (!conflictless)
			return null;

		// Decompose whichever one starts first into two operations.
		if (dx_start > 0) {
			// a starts first.
			var hunk = a_state.old_hunks.shift();
			var decomp = hunk.op.decompose(false, dx_start);

			// Unshift the right half of the decomposition.
			a_state.old_hunks.unshift({
				offset: 0,
				length: hunk.length-dx_start,
				op: decomp[1]
			});

			// Unshift the left half of the decomposition.
			a_state.old_hunks.unshift({
				offset: hunk.offset,
				length: dx_start,
				op: decomp[0]
			});

			// Since we know the left half occurs first, take it.
			a_state.take(b_state)

			// Start the iteration over -- we should end up at the block
			// for two hunks that modify the exact same range.
			continue;

		} else if (dx_start < 0) {
			// b starts first.
			var hunk = b_state.old_hunks.shift();
			var decomp = hunk.op.decompose(false, -dx_start);

			// Unshift the right half of the decomposition.
			b_state.old_hunks.unshift({
				offset: 0,
				length: hunk.length+dx_start,
				op: decomp[1]
			});

			// Unshift the left half of the decomposition.
			b_state.old_hunks.unshift({
				offset: hunk.offset,
				length: -dx_start,
				op: decomp[0]
			});

			// Since we know the left half occurs first, take it.
			b_state.take(a_state)

			// Start the iteration over -- we should end up at the block
			// for two hunks that modify the exact same range.
			continue;
		}

		// They start at the same point, but don't end at the same
		// point. Decompose the longer one.
		else if (dx_end < 0) {
			// a is longer.
			var hunk = a_state.old_hunks.shift();
			var decomp = hunk.op.decompose(false, hunk.length+dx_end);

			// Unshift the right half of the decomposition.
			a_state.old_hunks.unshift({
				offset: 0,
				length: -dx_end,
				op: decomp[1]
			});

			// Unshift the left half of the decomposition.
			a_state.old_hunks.unshift({
				offset: hunk.offset,
				length: hunk.length+dx_end,
				op: decomp[0]
			});

			// Start the iteration over -- we should end up at the block
			// for two hunks that modify the exact same range.
			continue;
		} else if (dx_end > 0) {
			// b is longer.
			var hunk = b_state.old_hunks.shift();
			var decomp = hunk.op.decompose(false, hunk.length-dx_end);

			// Unshift the right half of the decomposition.
			b_state.old_hunks.unshift({
				offset: 0,
				length: dx_end,
				op: decomp[1]
			});

			// Unshift the left half of the decomposition.
			b_state.old_hunks.unshift({
				offset: hunk.offset,
				length: hunk.length-dx_end,
				op: decomp[0]
			});

			// Start the iteration over -- we should end up at the block
			// for two hunks that modify the exact same range.
			continue;
		}

		throw new Error("We thought this line was not reachable.");
	}

	return [
		new exports.PATCH(a_state.new_hunks).simplify(),
		new exports.PATCH(b_state.new_hunks).simplify() ];
}

exports.PATCH.prototype.rebase_functions = [
	/* Transforms this operation so that it can be composed *after* the other
		 operation to yield the same logical effect. Returns null on conflict. */

	[exports.PATCH, function(other, conflictless) {
		// Return the new operations.
		return rebase_patches(this, other, conflictless);
	}]
];


exports.PATCH.prototype.get_length_change = function (old_length) {
	// Support routine for PATCH that returns the change in
	// length to a sequence if this operation is applied to it.
	var dlen = 0;
	this.hunks.forEach(function(hunk) {
		dlen += hunk.op.get_length_change(hunk.length);
	});
	return dlen;
}

//////////////////////////////////////////////////////////////////////////////

exports.MAP.prototype.inspect = function(depth) {
	return util.format("<MAP %s>", this.op.inspect(depth-1));
}

exports.MAP.prototype.visit = function(visitor) {
	// A simple visitor paradigm. Replace this operation instance itself
	// and any operation within it with the value returned by calling
	// visitor on itself, or if the visitor returns anything falsey
	// (probably undefined) then return the operation unchanged.
	var ret = new exports.MAP(this.op.visit(visitor));
	return visitor(ret) || ret;
}

exports.MAP.prototype.internalToJSON = function(json, protocol_version) {
	json.op = this.op.toJSON(undefined, protocol_version);
}

exports.MAP.internalFromJSON = function(json, protocol_version, op_map) {
	return new exports.MAP(jot.opFromJSON(json.op, protocol_version, op_map));
}

exports.MAP.prototype.apply = function (document) {
	/* Applies the operation to a document. Returns a new sequence that is
		 the same type as document but with the element modified. */

 	// Turn string into array of characters.
	var d;
	if (typeof document == 'string')
		d = document.split(/.{0}/)

	// Clone array.
	else
		d = document.slice(); // clone
	
	// Apply operation to each element.
	for (var i = 0; i < d.length; i++) {
		d[i] = this.op.apply(d[i])

		// An operation on strings must return a single character.
		if (typeof document == 'string' && (typeof d[i] != 'string' || d[i].length != 1))
			throw new Error("Invalid operation: String type or length changed.")
	}

	// Turn the array of characters back into a string.
	if (typeof document == 'string')
		return d.join("");

	return d;
}

exports.MAP.prototype.simplify = function () {
	/* Returns a new atomic operation that is a simpler version
		 of this operation.*/
	var op = this.op.simplify();
	if (op instanceof values.NO_OP)
		return new values.NO_OP();	   
	return this;
}

exports.MAP.prototype.drilldown = function(index_or_key) {
	if (!Number.isInteger(index_or_key) || index_or_key < 0)
		new values.NO_OP();
	return this.op;
}

exports.MAP.prototype.inverse = function (document) {
	/* Returns a new atomic operation that is the inverse of this operation. */

	if (document.length == 0)
		return new exports.NO_OP();
	if (document.length == 1)
		return new exports.MAP(this.op.inverse(document[0]));

	// Since the inverse depends on the value of the document and the
	// elements of document may not all be the same, we have to explode
	// this out into individual operations.
	var hunks = [];
	if (typeof document == 'string')
		document = document.split(/.{0}/);
	document.forEach(function(element) {
		hunks.append({
			offset: 0,
			length: 1,
			op: this.op.inverse(element)
		});
	});
	return new exports.PATCH(hunks);
}

exports.MAP.prototype.atomic_compose = function (other) {
	/* Creates a new atomic operation that has the same result as this
		 and other applied in sequence (this first, other after). Returns
		 null if no atomic operation is possible. */

	// two MAPs with atomically composable sub-operations
	if (other instanceof exports.MAP) {
		var op2 = this.op.atomic_compose(other.op);
		if (op2)
			return new exports.MAP(op2);
	}

	// No composition possible.
	return null;
}

exports.MAP.prototype.rebase_functions = [
	[exports.MAP, function(other, conflictless) {
		// Two MAPs. The rebase succeeds only if a rebase on the
		// inner operations succeeds.
		var opa;
		var opb;

		// If conflictless is null or there is no prior document
		// state, then it's safe to pass conflictless into the
		// inner operations.
		if (!conflictless || !("document" in conflictless)) {
			opa = this.op.rebase(other.op, conflictless);
			opb = other.op.rebase(this.op, conflictless);

		// If there is a single element in the prior document
		// state, then unwrap it for the inner operations.
		} else if (conflictless.document.length == 1) {
			var conflictless2 = shallow_clone(conflictless);
			conflictless2.document = conflictless2.document[0];

			opa = this.op.rebase(other.op, conflictless2);
			opb = other.op.rebase(this.op, conflictless2);

		// If the prior document state is an empty array, then
		// we know these operations are NO_OPs anyway.
		} else if (conflictless.document.length == 0) {
			return [
				new jot.NO_OP(),
				new jot.NO_OP()
			];

		// The prior document state is an array of more than one
		// element. In order to pass the prior document state into
		// the inner operations, we have to try it for each element
		// of the prior document state. If they all yield the same
		// operation, then we can use that operation. Otherwise the
		// rebases are too sensitive on prior document state and
		// we can't rebase.
		} else {
			var ok = true;
			for (var i = 0; i < conflictless.document.length; i++) {
				var conflictless2 = shallow_clone(conflictless);
				conflictless2.document = conflictless.document[i];

				var a = this.op.rebase(other.op, conflictless2);
				var b = other.op.rebase(this.op, conflictless2);
				if (i == 0) {
					opa = a;
					opb = b;
				} else {
					if (!deepEqual(opa, a, { strict: true }))
						ok = false;
					if (!deepEqual(opb, b, { strict: true }))
						ok = false;
				}
			}

			if (!ok) {
				// The rebases were not the same for all elements. Decompose
				// the MAPs into PATCHes with individual hunks for each index,
				// and then rebase those.
				var _this = this;
				opa = new exports.PATCH(
					conflictless.document.map(function(item) {
						return {
							offset: 0,
							length: 1,
							op: _this
						}
					}));
				opb = new exports.PATCH(
					conflictless.document.map(function(item) {
						return {
							offset: 0,
							length: 1,
							op: other
						}
					}));
				return rebase_patches(opa, opb, conflictless);
			}
		}


		if (opa && opb)
			return [
				(opa instanceof values.NO_OP) ? new values.NO_OP() : new exports.MAP(opa),
				(opb instanceof values.NO_OP) ? new values.NO_OP() : new exports.MAP(opb)
			];
	}],

	[exports.PATCH, function(other, conflictless) {
		// Rebase MAP and PATCH.

		// If the PATCH has no hunks, then the rebase is trivial.
		if (other.hunks.length == 0)
			return [this, other];

		// If the PATCH hunks are all MAP operations and the rebase
		// between this and the hunk operations are all the same,
		// *and* the rebase of this is the same as this, then we can
		// use that. If the rebase is different from this operation,
		// then we can't use it because it wouldn't have the same
		// effect on parts of the sequence that the PATCH does not
		// affect.
		var _this = this;
		var rebase_result;
		other.hunks.forEach(function(hunk) {
			if (!(hunk.op instanceof exports.MAP)) {
				// Rebase is not possible. Flag that it is not possible.
				rebase_result = null;
				return;
			}

			var r = _this.rebase_functions[0][1].call(_this, hunk.op);
			if (!r) {
				// Rebase failed. Flag it.
				rebase_result = null;
				return;
			}

			if (typeof rebase_result == "undefined") {
				// This is the first one.
				rebase_result = r;
				return;
			}

			// Check that it is equal to the last one. If not, flag.
			if (!deepEqual(rebase_result[0], r[0], { strict: true })
				|| !deepEqual(rebase_result[1], r[1], { strict: true }))
				rebase_result = null;
		})
		if (rebase_result != null && deepEqual(rebase_result[0], this, { strict: true })) {
			// Rebase was possible and the same for every operation.
			return [
				rebase_result[0],
				new exports.PATCH(other.hunks.map(function(hunk) {
					hunk = shallow_clone(hunk);
					hunk.op = rebase_result[1];
					return hunk;
				})),
			]
		}

		// Only a conflictless rebase is possible in other cases,
		// and prior document state is required.
		if (conflictless && "document" in conflictless) {
			// Wrap MAP in a PATCH that spans the whole sequence, and then
			// use rebase_patches. This will jump ahead to comparing the
			// MAP to the PATCH's inner operations.
			//
			// NOTE: Operations that are allowed inside PATCH (including MAP)
			// normally must not rebase to an operation that is not allowed
			// inside PATCH. Returning a PATCH here would therefore normally
			// not be valid. We've partially satisfied the contract for PATCH
			// by defining PATCH.get_length_change, but not PATCH.decompose.
			// That seems to be enough.
			return rebase_patches(
				new exports.PATCH([{ offset: 0, length: conflictless.document.length, op: this}]),
				other,
				conflictless);

			/*
			// Alternatively:
			// Since the MAP doesn't change the number of elements in the sequence,
			// it makes sense to have the MAP go first.
			// But we don't do this because we have to return a SET so that LIST.rebase
			// doesn't go into infinite recursion by returning a LIST from a rebase,
			// and SET loses logical structure.
			return [
				// MAP is coming second, so create an operation that undoes
				// the patch, applies the map, and then applies the patch.
				// See values.MATH.rebase for why we return a SET.
				new jot.SET(this.compose(other).apply(conflictless.document)),
				//other.inverse(conflictless.document).compose(this).compose(other),

				// PATCH is coming second, which is right
				other
			];
			*/
		}
	}]
];

exports.MAP.prototype.get_length_change = function (old_length) {
	// Support routine for PATCH that returns the change in
	// length to a sequence if this operation is applied to it.
	return 0;
}

exports.MAP.prototype.decompose = function (in_out, at_index) {
	// Support routine for when this operation is used as a hunk's
	// op in sequences.PATCH (i.e. its document is a string or array
	// sub-sequence) that returns a decomposition of the operation
	// into two operations, one that applies on the left of the
	// sequence and one on the right of the sequence, such that
	// the length of the input (if !in_out) or output (if in_out)
	// of the left operation is at_index, i.e. the split point
	// at_index is relative to the document either before (if
	// !in_out) or after (if in_out) this operation applies.
	//
	// Since MAP applies to all elements, the decomposition
	// is trivial.
	return [this, this];
}

////

exports.createRandomOp = function(doc, context) {
	// Not all inner operations are valid for PATCH and MAP. When they
	// apply to arrays, any inner operation is valid. But when they
	// apply to strings, the inner operations must yield a string
	// and the inner operation of a MAP must yield a length-one string.
	context = (typeof doc == "string") ? "string" : "array";

	// Create a random operation that could apply to doc.
	// Choose uniformly across various options.
	var ops = [];

	// Construct a PATCH.
	ops.push(function() {
		var hunks = [];
		var dx = 0;

		while (dx < doc.length) {
			// Construct a random hunk. First select a range in the
			// document to modify. We can start at any element index,
			// or one past the end to insert at the end.
			var start = dx + Math.floor(Math.random() * (doc.length+1-dx));
			var old_length = (start < doc.length) ? Math.floor(Math.random() * (doc.length - start + 1)) : 0;
			var old_value = doc.slice(start, start+old_length);

			// Choose an inner operation. Only ops in values can be used
			// because ops within PATCH must support get_length_change.
			var op = values.createRandomOp(old_value, context);

			// Push the hunk.
			hunks.push({
				offset: start-dx,
				length: old_length,
				op: op
			});

			dx = start + old_length;

			// Create another hunk?
			if (Math.random() < .25)
				break;
		}

		return new exports.PATCH(hunks);
	});

	// Construct a MAP.
	ops.push(function() {
		while (true) {
			// Choose a random element to use as the template for the
			// random operation. If the sequence is empty, use "?" or null.
			// Don't use an empty string because we can't replace an
			// element of the string with an empty string.
			var random_elem;
			if (doc.length == 0) {
				if (typeof doc === "string")
					random_elem = "?";
				else if (Array.isArray(doc))
					random_elem = null;
			} else {
				random_elem = elem(doc, Math.floor(Math.random() * doc.length));
			}

			// Construct a random operation.
			var op = values.createRandomOp(random_elem, context+"-elem");

			// Test that it is valid on all elements of doc.
			try {
				if (typeof doc === "string") doc = doc.split(''); // convert to array
				doc.forEach(function(item) {
					op.apply(item);
				});
				return new exports.MAP(op);
			} catch (e) {
				// It's invalid. Try again to find a valid operation
				// that can apply to all elements, looping indefinitely
				// until one can be found. SET is always valid and is
				// highly probable to be selected so this shouldn't
				// take long.
			}
		}
	});

	// Select randomly.
	return ops[Math.floor(Math.random() * ops.length)]();
}


================================================
FILE: jot/values.js
================================================
/*  An operational transformation library for atomic values.

	This library provides three operations: NO_OP (an operation
	that leaves the value unchanged), SET (replaces the value
	with a new value), and MATH (apply one of several mathematical
	functions to the value). These functions are generic over
	various sorts of atomic data types that they may apply to.


	new values.NO_OP()

	This operation does nothing. It is the return value of various
	functions throughout the library, e.g. when operations cancel
	out. NO_OP is conflictless: It never creates a conflict when
	rebased against or operations or when other operations are
	rebased against it.
	

	new values.SET(value)
	
	The atomic replacement of one value with another. Works for
	any data type. The SET operation supports a conflictless
	rebase with all other operations.
	

	new values.MATH(operator, operand)
	
	Applies a commutative arithmetic function to a number or boolean.
	
	"add": addition (use a negative number to decrement) (over numbers only)
	
	"mult": multiplication (use the reciprocal to divide) (over numbers only)
	
	"rot": addition followed by modulus (the operand is given
	       as a tuple of the increment and the modulus). The document
	       object must be a non-negative integer and less than the modulus.

	"and": bitwise and (over integers and booleans only)

	"or": bitwise or (over integers and booleans only)
	
	"xor": bitwise exclusive-or (over integers and booleans
	       only)

	"not": bitwise not (over integers and booleans only; the operand
	       is ignored)
	
	Note that by commutative we mean that the operation is commutative
	under composition, i.e. add(1)+add(2) == add(2)+add(1).

	The operators are also guaranteed to not change the data type of the
	document. Numbers remain numbers and booleans remain booleans.

	MATH supports a conflictless rebase with all other operations if
	prior document state is provided in the conflictless argument object.
	
	*/
	
var util = require('util');
var deepEqual = require("deep-equal");
var jot = require("./index.js");
var MISSING = require("./objects.js").MISSING;

//////////////////////////////////////////////////////////////////////////////

exports.module_name = 'values'; // for serialization/deserialization

exports.NO_OP = function() {
	/* An operation that makes no change to the document. */
	Object.freeze(this);
}
exports.NO_OP.prototype = Object.create(jot.Operation.prototype); // inherit
jot.add_op(exports.NO_OP, exports, 'NO_OP');

exports.SET = function(value) {
	/* An operation that replaces the document with a new (atomic) value. */
	this.value = value;
	Object.freeze(this);
}
exports.SET.prototype = Object.create(jot.Operation.prototype); // inherit
jot.add_op(exports.SET, exports, 'SET');

exports.MATH = function(operator, operand) {
	/* An operation that applies addition, multiplication, or rotation (modulus addition)
	   to a numeric document. */
	this.operator = operator;
	this.operand = operand;

	if (this.operator == "add" || this.operator == "mult") {
		if (typeof this.operand != "number")
			throw new Error("MATH[add] and MATH[mult]'s operand must be a number.")
	}

	if (this.operator == "and" || this.operator == "or" || this.operator == "xor") {
		if (!Number.isInteger(this.operand) && typeof this.operand != "boolean")
			throw new Error("MATH[and] and MATH[or] and MATH[xor]'s operand must be a boolean or integer.")
	}

	if (this.operator == "not") {
		if (this.operand !== null)
			throw new Error("MATH[not]'s operand must be null --- it is not used.")
	}

	if (this.operator == "rot") {
		if (   !Array.isArray(this.operand)
			|| this.operand.length != 2
			|| !Number.isInteger(this.operand[0])
			|| !Number.isInteger(this.operand[1]))
			throw new Error("MATH[rot] operand must be an array with two integer elements.")
		if (this.operand[1] <= 1)
			throw new Error("MATH[rot]'s second operand, the modulus, must be greater than one.")
		if (this.operand[0] >= Math.abs(this.operand[1]))
			throw new Error("MATH[rot]'s first operand, the increment, must be less than its second operand, the modulus.")
	}

	Object.freeze(this);
}
exports.MATH.prototype = Object.create(jot.Operation.prototype); // inherit
jot.add_op(exports.MATH, exports, 'MATH');


//////////////////////////////////////////////////////////////////////////////

exports.NO_OP.prototype.inspect = function(depth) {
	return "<NO_OP>"
}

exports.NO_OP.prototype.internalToJSON = function(json, protocol_version) {
	// Nothing to set.
}

exports.NO_OP.internalFromJSON = function(json, protocol_version, op_map) {
	return new exports.NO_OP();
}

exports.NO_OP.prototype.apply = function (document) {
	/* Applies the operation to a document. Returns the document
	   unchanged. */
	return document;
}

exports.NO_OP.prototype.simplify = function () {
	/* Returns a new atomic operation that is a simpler version
	   of this operation.*/
	return this;
}

exports.NO_OP.prototype.drilldown = function(index_or_key) {
	return new values.NO_OP();
};

exports.NO_OP.prototype.inverse = function (document) {
	/* Returns a new atomic operation that is the inverse of this operation,
	given the state of the document before the operation applies. */
	return this;
}

exports.NO_OP.prototype.atomic_compose = function (other) {
	/* Creates a new atomic operation that has the same result as this
	   and other applied in sequence (this first, other after). Returns
	   null if no atomic operation is possible. */
	return other;
}

exports.NO_OP.prototype.rebase_functions = [
	[jot.Operation, function(other, conflictless) {
		// NO_OP operations do not affect any other operation.
		return [this, other];
	}]
];

exports.NO_OP.prototype.get_length_change = function (old_length) {
	// Support routine for sequences.PATCH that returns the change in
	// length to a sequence if this operation is applied to it.
	return 0;
}

exports.NO_OP.prototype.decompose = function (in_out, at_index) {
	// Support routine for when this operation is used as a hunk's
	// op in sequences.PATCH (i.e. its document is a string or array
	// sub-sequence) that returns a decomposition of the operation
	// into two operations, one that applies on the left of the
	// sequence and one on the right of the sequence, such that
	// the length of the input (if !in_out) or output (if in_out)
	// of the left operation is at_index, i.e. the split point
	// at_index is relative to the document either before (if
	// !in_out) or after (if in_out) this operation applies.
	//
	// Since NO_OP has no effect, its decomposition is trivial.
	return [this, this];
}

//////////////////////////////////////////////////////////////////////////////

exports.SET.prototype.inspect = function(depth) {
	function str(v) {
		// Render the special MISSING value from objects.js
		// not as a JSON object.
		if (v === MISSING)
			return "~";

		// Render any other value as a JSON string.
		return util.format("%j", v);
	}
	return util.format("<SET %s>", str(this.value));
}

exports.SET.prototype.internalToJSON = function(json, protocol_version) {
	if (this.value === MISSING)
		json.value_missing = true;
	else
		json.value = this.value;
}

exports.SET.internalFromJSON = function(json, protocol_version, op_map) {
	if (json.value_missing)
		return new exports.SET(MISSING);
	else
		return new exports.SET(json.value);
}

exports.SET.prototype.apply = function (document) {
	/* Applies the operation to a document. Returns the new
	   value, regardless of the document. */
	return this.value;
}

exports.SET.prototype.simplify = function () {
	/* Returns a new atomic operation that is a simpler version
	   of another operation. There is nothing to simplify for
	   a SET. */
	return this;
}

exports.SET.prototype.drilldown = function(index_or_key) {
	// If the SET sets an array or object value, then drilling down
	// sets the inner value to the element or property value.
	if (typeof this.value == "object" && Array.isArray(this.value))
		if (Number.isInteger(index_or_key) && index_or_key < this.value.length)
			return new exports.SET(this.value[index_or_key]);
	if (typeof this.value == "object" && !Array.isArray(this.value) && this.value !== null)
		if (typeof index_or_key == "string" && index_or_key in this.value)
			return new exports.SET(this.value[index_or_key]);

	// Signal that anything that used to be an array element or
	// object property is now nonexistent.
	return new exports.SET(MISSING);
};

exports.SET.prototype.inverse = function (document) {
	/* Returns a new atomic operation that is the inverse of this operation,
	   given the state of the document before this operation applies. */
	return new exports.SET(document);
}

exports.SET.prototype.atomic_compose = function (other) {
	/* Creates a new atomic operation that has the same result as this
	   and other applied in sequence (this first, other after). Returns
	   null if no atomic operation is possible.
	   Returns a new SET operation that simply sets the value to what
	   the value would be when the two operations are composed. */
	return new exports.SET(other.apply(this.value)).simplify();
}

exports.SET.prototype.rebase_functions = [
	// Rebase this against other and other against this.

	[exports.SET, function(other, conflictless) {
		// SET and SET.

		// If they both set the the document to the same value, then the one
		// applied second (the one being rebased) becomes a no-op. Since the
		// two parts of the return value are for each rebased against the
		// other, both are returned as no-ops.
		if (deepEqual(this.value, other.value, { strict: true }))
			return [new exports.NO_OP(), new exports.NO_OP()];
		
		// If they set the document to different values and conflictless is
		// true, then we clobber the one whose value has a lower sort order.
		if (conflictless && jot.cmp(this.value, other.value) < 0)
			return [new exports.NO_OP(), new exports.SET(other.value)];

		// cmp > 0 is handled by a call to this function with the arguments
		// reversed, so we don't need to explicltly code that logic.

		// If conflictless is false, then we can't rebase the operations
		// because we can't preserve the meaning of both. Return null to
		// signal conflict.
		return null;
	}],

	[exports.MATH, function(other, conflictless) {
		// SET (this) and MATH (other). To get a consistent effect no matter
		// which order the operations are applied in, we say the SET comes
		// second. i.e. If the SET is already applied, the MATH becomes a
		// no-op. If the MATH is already applied, the SET is applied unchanged.
		return [
			this,
			new exports.NO_OP()
			];
	}]
];

exports.SET.prototype.get_length_change = function (old_length) {
	// Support routine for sequences.PATCH that returns the change in
	// length to a sequence if this operation is applied to it.
	if (typeof this.value == "string" || Array.isArray(this.value))
		return this.value.length - old_length;
	throw new Error("not applicable: new value is of type " + typeof this.value);
}

exports.SET.prototype.decompose = function (in_out, at_index) {
	// Support routine for when this operation is used as a hunk's
	// op in sequences.PATCH (i.e. its document is a string or array
	// sub-sequence) that returns a decomposition of the operation
	// into two operations, one that applies on the left of the
	// sequence and one on the right of the sequence, such that
	// the length of the input (if !in_out) or output (if in_out)
	// of the left operation is at_index, i.e. the split point
	// at_index is relative to the document either before (if
	// !in_out) or after (if in_out) this operation applies.
	if (typeof this.value != "string" && !Array.isArray(this.value))
		throw new Error("invalid value type for call");
	if (!in_out) {
		// Decompose into a delete and a replace with the value
		// lumped on the right.
		return [
			new exports.SET(this.value.slice(0,0)), // create empty string or array
			this
		];
	} else {
		// Split the new value at the given index.
		return [
			new exports.SET(this.value.slice(0, at_index)),
			new exports.SET(this.value.slice(at_index))
		];
	}
}

//////////////////////////////////////////////////////////////////////////////

exports.MATH.prototype.inspect = function(depth) {
	return util.format("<MATH %s:%s>",
		this.operator,
			(typeof this.operand == "number" && (this.operator == "and" || this.operator == "or" || this.operator == "xor"))
			?
				("0x" + this.operand.toString(16))
			:
				util.format("%j", this.operand)
		);
}

exports.MATH.prototype.internalToJSON = function(json, protocol_version) {
	json.operator = this.operator;
	json.operand = this.operand;
}

exports.MATH.internalFromJSON = function(json, protocol_version, op_map) {
	return new exports.MATH(json.operator, json.operand);
}

exports.MATH.prototype.apply = function (document) {
	/* Applies the operation to this.operand. Applies the operator/operand
	   as a function to the document. */
	if (typeof document == "number") {
		if (this.operator == "add")
			return document + this.operand;
		if (this.operator == "mult")
			return document * this.operand;
		if (Number.isInteger(document)) {
			if (this.operator == "rot")
				return (document + this.operand[0]) % this.operand[1];
			if (this.operator == "and")
				return document & this.operand;
			if (this.operator == "or")
				return document | this.operand;
			if (this.operator == "xor")
				return document ^ this.operand;
			if (this.operator == "not")
				return ~document;
		}
		throw new Error("MATH operator " + this.operator + " cannot apply to " + document + ".");
	
	} else if (typeof document == "boolean") {
		if (this.operator == "and")
			return document && this.operand;
		if (this.operator == "or")
			return document || this.operand;
		if (this.operator == "xor")
			return !!(document ^ this.operand); // convert arithmetic result to boolean
		if (this.operator == "not")
			return !document;
		throw new Error("MATH operator " + this.operator + " does not apply to boolean values.")
	
	} else {
		throw new Error("MATH operations only apply to number and boolean values, not " + jot.type_name(document) + ".")
	}
}

exports.MATH.prototype.simplify = function () {
	/* Returns a new atomic operation that is a simpler version
	   of another operation. If the operation is a degenerate case,
	   return NO_OP. */
	if (this.operator == "add" && this.operand == 0)
		return new exports.NO_OP();
	if (this.operator == "rot" && this.operand[0] == 0)
		return new exports.NO_OP();
	if (this.operator == "mult" && this.operand == 1)
		return new exports.NO_OP();
	if (this.operator == "and" && this.operand === 0)
		return new exports.SET(0);
	if (this.operator == "and" && this.operand === false)
		return new exports.SET(false);
	if (this.operator == "or" && this.operand === 0)
		return new exports.NO_OP();
	if (this.operator == "or" && this.operand === false)
		return new exports.NO_OP();
	if (this.operator == "xor" && this.operand == 0)
		return new exports.NO_OP();
	return this;
}

exports.MATH.prototype.drilldown = function(index_or_key) {
	// MATH operations only apply to scalars, so drilling down
	// doesn't make any sense. But we can say a MATH operation
	// doesn't affect any sub-components of the value.
	return new exports.NO_OP();
};

exports.MATH.prototype.inverse = function (document) {
	/* Returns a new atomic operation that is the inverse of this operation,
	given the state of the document before the operation applies.
	For most of these operations the value of document doesn't
	matter. */
	if (this.operator == "add")
		return new exports.MATH("add", -this.operand);
	if (this.operator == "rot")
		return new exports.MATH("rot", [-this.operand[0], this.operand[1]]);
	if (this.operator == "mult")
		return new exports.MATH("mult", 1.0/this.operand);
	if (this.operator == "and")
		return new exports.MATH("or", document & (~this.operand));
	if (this.operator == "or")
		return new exports.MATH("xor", ~document & this.operand);
	if (this.operator == "xor")
		return this; // is its own inverse
	if (this.operator == "not")
		return this; // is its own inverse
}

exports.MATH.prototype.atomic_compose = function (other) {
	/* Creates a new atomic operation that has the same result as this
	   and other applied in sequence (this first, other after). Returns
	   null if no atomic operation is possible. */

	if (other instanceof exports.MATH) {
		// two adds just add the operands
		if (this.operator == other.operator && this.operator == "add")
			return new exports.MATH("add", this.operand + other.operand).simplify();

		// two rots with the same modulus add the operands
		if (this.operator == other.operator && this.operator == "rot" && this.operand[1] == other.operand[1])
			return new exports.MATH("rot", [this.operand[0] + other.operand[0], this.operand[1]]).simplify();

		// two multiplications multiply the operands
		if (this.operator == other.operator && this.operator == "mult")
			return new exports.MATH("mult", this.operand * other.operand).simplify();

		// two and's and the operands
		if (this.operator == other.operator && this.operator == "and" && typeof this.operand == typeof other.operand && typeof this.operand == "number")
			return new exports.MATH("and", this.operand & other.operand).simplify();
		if (this.operator == other.operator && this.operator == "and" && typeof this.operand == typeof other.operand && typeof this.operand == "boolean")
			return new exports.MATH("and", this.operand && other.operand).simplify();

		// two or's or the operands
		if (this.operator == other.operator && this.operator == "or" && typeof this.operand == typeof other.operand && typeof this.operand == "number")
			return new exports.MATH("or", this.operand | other.operand).simplify();
		if (this.operator == other.operator && this.operator == "or" && typeof this.operand == typeof other.operand && typeof this.operand == "boolean")
			return new exports.MATH("or", this.operand || other.operand).simplify();

		// two xor's xor the operands
		if (this.operator == other.operator && this.operator == "xor" && typeof this.operand == typeof other.operand && typeof this.operand == "number")
			return new exports.MATH("xor", this.operand ^ other.operand).simplify();
		if (this.operator == other.operator && this.operator == "xor" && typeof this.operand == typeof other.operand && typeof this.operand == "boolean")
			return new exports.MATH("xor", !!(this.operand ^ other.operand)).simplify();

		// two not's cancel each other out
		if (this.operator == other.operator && this.operator == "not")
			return new exports.NO_OP();

		// and+or with the same operand is SET(operand)
		if (this.operator == "and" && other.operator == "or" && this.operand === other.operand)
			return new exports.SET(this.operand);

		// or+xor with the same operand is AND(~operand)
		if (this.operator == "or" && other.operator == "xor" && this.operand === other.operand && typeof this.operand == "number")
			return new exports.MATH("and", ~this.operand);
		if (this.operator == "or" && other.operator == "xor" && this.operand === other.operand && typeof this.operand == "boolean")
			return new exports.MATH("and", !this.operand);

	}
	
	return null; // no composition is possible
}

exports.MATH.prototype.rebase_functions = [
	// Rebase this against other and other against this.

	[exports.MATH, function(other, conflictless) {
		// If this and other are MATH operations with the same operator (i.e. two
		// add's; two rot's with the same modulus), then since they are commutative
		// their order does not matter and the rebase returns each operation
		// unchanged.
		if (this.operator == other.operator
			&& (this.operator != "rot" || this.operand[1] == other.operand[1]))
				return [this, other];

		// When two different operators ocurr simultaneously, then the order matters.
		// Since operators preserve the data type of the document, we know that both
		// orders are valid. Choose an order based on the operations: We'll put this
		// first and other second.
		if (conflictless && "document" in conflictless) {
			if (jot.cmp([this.operator, this.operand], [other.operator, other.operand]) < 0) {
				return [
					// this came second, so replace it with an operation that
					// inverts the existing other operation, then applies this,
					// then re-applies other. Although a composition of operations
					// is logically sensible, returning a LIST will cause LIST.rebase
					// to go into an infinite regress in some cases.
					new exports.SET(this.compose(other).apply(conflictless.document)),
					//other.inverse(conflictless.document).compose(this).compose(other),

					// no need to rewrite other because it's supposed to come second
					other
				]
			}
		}

		// The other order is handled by the converse call handled by jot.rebase.
		return null;
	}]
];

exports.createRandomOp = function(doc, context) {
	// Create a random operation that could apply to doc.
	// Choose uniformly across various options depending on
	// the data type of doc.
	var ops = [];

	// NO_OP is always a possibility.
	ops.push(function() { return new exports.NO_OP() });

	// An identity SET is always a possibility.
	ops.push(function() { return new exports.SET(doc) });

	// Set to another random value of a different type.
	// Can't do this in a context where changing the type is not valid,
	// i.e. when in a PATCH or MAP operation on a string.
	if (context != "string-elem" && context != "string")
		ops.push(function() { return new exports.SET(jot.createRandomValue()) });

	// Clear the key, if we're in an object.
	if (context == "object")
		ops.push(function() { return new exports.SET(MISSING) });

	// Set to another value of the same type.
	if (typeof doc === "boolean")
		ops.push(function() { return new exports.SET(!doc) });
	if (typeof doc === "number") {
		if (Number.isInteger(doc)) {
			ops.push(function() { return new exports.SET(doc + Math.floor((Math.random()+.5) * 100)) });
		} else {
			ops.push(function() { return new exports.SET(doc * (Math.random()+.5)) });
		}
	}

	if ((typeof doc === "string" || Array.isArray(doc)) && context != "string-elem") {
		// Delete (if not already empty).
		if (doc.length > 0)
			ops.push(function() { return new exports.SET(doc.slice(0, 0)) });

		if (doc.length >= 1) {
			// shorten at start
			ops.push(function() { return new exports.SET(doc.slice(Math.floor(Math.random()*(doc.length-1)), doc.length)) });

			// shorten at end
			ops.push(function() { return new exports.SET(doc.slice(0, Math.floor(Math.random()*(doc.length-1)))) });
		}

		if (doc.length >= 2) {
			// shorten by on both sides
			var a = Math.floor(Math.random()*doc.length-1);
			var b = Math.floor(Math.random()*(doc.length-a));
			ops.push(function() { return new exports.SET(doc.slice(a, a+b)) });
		}

		if (doc.length > 0) {
			// expand by copying existing elements from document

			function concat2(item1, item2) {
				if (item1 instanceof String)
					return item1 + item2;
				return item1.concat(item2);
			}
			function concat3(item1, item2, item3) {
				if (item1 instanceof String)
					return item1 + item2 + item3;
				return item1.concat(item2).concat(item3);
			}
		
			// expand by elements at start
			ops.push(function() { return new exports.SET(concat2(doc.slice(0, 1+Math.floor(Math.random()*(doc.length-1))), doc)) });
			// expand by elements at end
			ops.push(function() { return new exports.SET(concat2(doc, doc.slice(0, 1+Math.floor(Math.random()*(doc.length-1))))); });
			// expand by elements on both sides
			ops.push(function() { return new exports.SET(concat3(doc.slice(0, 1+Math.floor(Math.random()*(doc.length-1))), doc, doc.slice(0, 1+Math.floor(Math.random()*(doc.length-1))))); });
		} else {
			// expand by generating new elements
			if (typeof doc === "string")
				ops.push(function() { return new exports.SET((Math.random()+"").slice(2)); });
			else if (Array.isArray(doc))
				ops.push(function() { return new exports.SET([null,null,null].map(function() { return Math.random() })); });
		}
	}

	if (typeof doc === "string") {
		// reverse
		if (doc != doc.split("").reverse().join(""))
			ops.push(function() { return new exports.SET(doc.split("").reverse().join("")); });

		// replace with new elements of the same length
		if (doc.length > 0) {
			var newvalue = "";
			for (var i = 0; i < doc.length; i++)
				newvalue += (Math.random()+"").slice(2, 3);
			ops.push(function() { return new exports.SET(newvalue); });
		}
	}

	// Math
	if (typeof doc === "number") {
		if (Number.isInteger(doc)) {
			ops.push(function() { return new exports.MATH("add", Math.floor(100 * (Math.random() - .25))); })
			ops.push(function() { return new exports.MATH("mult", Math.floor(Math.exp(Math.random()+.5))); })
			if (doc > 1)
				ops.push(function() { return new exports.MATH("rot", [1, Math.min(13, doc)]); })
			ops.push(function() { return new exports.MATH("and", 0xF1); })
			ops.push(function() { return new exports.MATH("or", 0xF1); })
			ops.push(function() { return new exports.MATH("xor", 0xF1); })
			ops.push(function() { return new exports.MATH("not", null); })
		} else {
			// floating point math yields inexact/inconsistent results if operation
			// order changes, so you may want to disable these in testing
			ops.push(function() { return new exports.MATH("add", 100 * (Math.random() - .25)); })
			ops.push(function() { return new exports.MATH("mult", Math.exp(Math.random()+.5)); })
		}
	}
	if (typeof doc === "boolean") {
		ops.push(function() { return new exports.MATH("and", true); })
		ops.push(function() { return new exports.MATH("and", false); })
		ops.push(function() { return new exports.MATH("or", true); })
		ops.push(function() { return new exports.MATH("or", false); })
		ops.push(function() { return new exports.MATH("xor", true); })
		ops.push(function() { return new exports.MATH("xor", false); })
		ops.push(function() { return new exports.MATH("not", null); })
	}

	// Select randomly.
	return ops[Math.floor(Math.random() * ops.length)]();
}


================================================
FILE: package.json
================================================
{
  "name": "jot",
  "version": "0.0.1",
  "description": "JSON Operational Transformation",
  "dependencies": {
    "deep-equal": "^2.0.3",
    "diff": "^4.0.2",
    "generic-diff": "*",
    "jsonpatch": "*",
    "shallow-clone": "^3.0.1"
  },
  "repository": {
    "type": "git",
    "url": "git://github.com/joshdata/jot.git"
  },
  "author": "Joshua Tauberer",
  "scripts": {
    "test": "tap --cov tests/*.js"
  },
  "devDependencies": {
    "connect": "*",
    "tap": "^14.10.7"
  }
}


================================================
FILE: test.js
================================================
var jot = require("./jot");
var deepEqual = require("deep-equal");
var createRandomOp = jot.createRandomOp; // require('./jot/values.js').createRandomOp;

// Create two complex simultaneous edits.
var opsize = 1;

while (true) {
	var initial_value = jot.createRandomValue();
	var op1 = jot.createRandomOpSequence(initial_value, opsize);
	var op2 = jot.createRandomOpSequence(initial_value, opsize);

	/*
	var initial_value = false;
	var op1 = jot.opFromJSON({"_type":"meta.LIST","ops":[{"_type":"values.SET","new_value":-226.62491332471424},{"_type":"values.NO_OP"}]});
	var op2 = jot.opFromJSON({"_type":"meta.LIST","ops":[{"_type":"values.MATH","operator":"and","operand":true},{"_type":"values.MATH","operator":"and","operand":false}]});
	*/
	//console.log(initial_value)
	//console.log(op1)
	//console.log(op2)

	try {

		// Compute the end results.
		var val1 = op1.apply(initial_value);
		var val2 = op2.apply(initial_value);

		// Check that the parallel rebases match.
		var op2r = op2.rebase(op1, { document: initial_value });
		var val1b = op2r ? op2r.apply(val1) : null;

		var op1r = op1.rebase(op2, { document: initial_value });
		var val2b = op1r ? op1r.apply(val2) : null;

		// Check that they also match using composition.
		var val1c = op2r ? op1.compose(op2r).apply(initial_value) : null;
		var val2c = op1r ? op2.compose(op1r).apply(initial_value) : null;

		// Check that we can compute a diff.
		var d = jot.diff(initial_value, val1b);

		if (op2r === null || op1r === null
		 || !deepEqual(val1b, val2b, { strict: true })
		 ) {
		  console.log("rebase failed or did not have same result");
		  console.log("init", initial_value)
		  console.log();
		  console.log("op1", JSON.stringify(op1.toJSON()));
		  console.log("val", val1);
		  console.log("op2r", op2r);
		  console.log("val", val1b);
		  console.log();
		  console.log("op2", JSON.stringify(op2.toJSON()));
		  console.log("val", val2);
		  console.log("op1r", op1r);
		  console.log("val", val2b);
		  break;
		} else if (!deepEqual(val1b, val1c, { strict: true }) || !deepEqual(val1c, val2c, { strict: true })) {
		  console.log("composition did not have same result");
		  console.log("init", initial_value)
		  console.log();
		  console.log("op1", JSON.stringify(op1.toJSON()));
		  console.log("val", val1);
		  console.log("op2r", op2r);
		  console.log("val", val1c);
		  console.log();
		  console.log("op2", JSON.stringify(op2.toJSON()));
		  console.log("val", val2);
		  console.log("op1r", op1r);
		  console.log("val", val2c);
		  break;
		}
	} catch (e) {
		console.error(e);
		console.log("init", initial_value)
		console.log("op1", JSON.stringify(op1.toJSON()));
		console.log("op2", JSON.stringify(op2.toJSON()));
		break;
	}
}

================================================
FILE: tests/diff.js
================================================
var test = require('tap').test;
var jot = require('../jot')
var diff = require("../jot/diff.js");

test('diff', function(t) {

	function test(a, b, options) {
		var op = diff.diff(a, b, options);
		t.deepEqual(op.apply(a), b);
		t.deepEqual(op.inverse(a).apply(b), a);
	}

	// values (these just turn into SET operations)

	test(5, { });

	// strings

	test("This is a test.", "That is not a test of string comparison.");
	test("This is a test.", "I know. This is a test.");
	test("This is a test.", "This is a test. Yes, I know.");

	// arrays

	test([1, 2, 3], [1, 2, 3])
	test([1, 2, 3], [0.5, 1, 1.5, 2, 2.5, 3, 3.5])
	test([0.5, 1, 1.5, 1.75, 2, 2.5, 3, 3.5], [1, 2, 3])

	// objects

	test({ "a": "Hello!" }, { "a": "Goodbye!" });
	test({ "a": "Hello!" }, { "b": "Hello!" });

	// recursive

	test({ "a": ["Hello!", ["Goodbye!"]] }, { "b": ["Hola!", ["Adios!"]] }, { words: true });

    t.end();
});


================================================
FILE: tests/merge.js
================================================
const test = require('tap').test;
const jot = require('../jot')
const merge = require("../jot/merge.js");

test('merge', function(t) {
  t.deepEqual(
    merge.merge(
      'source',
      'target',
      {
        source: { parents: ['root'], op: [new jot.SET('Hello')] },
        target: { parents: ['root'], op: [new jot.SET('World')] },
        root: { document: null },
      }
    )[1],
    new jot.NO_OP()
  );

  t.deepEqual(
    merge.merge(
      'source',
      'target',
      {
        source: { parents: ['root'], op: [new jot.SET('World')] },
        target: { parents: ['root'], op: [new jot.SET('Hello')] },
        root: { document: null },
      }
    )[1],
    new jot.SET("World")
  );

  t.deepEqual(
    merge.merge(
      'source',
      'target',
      {
        source: { parents: ['a'], op: [new jot.MATH('add', 1)] },
        target: { parents: ['a'], op: [new jot.MATH('add', 2)] },
        a: { parents: ['root'], op: [new jot.MATH('add', 3)] },
        root: { document: 0 },
      }
    )[1],
    new jot.MATH('add', 1)
  );

  t.deepEqual(
    merge.merge(
      'source',
      'target',
      {
        source: { parents: ['a'], op: [new jot.SPLICE(0, 5, "Goodbye")] },
        target: { parents: ['a'], op: [new jot.SPLICE(12, 5, "universe")] },
        a: { parents: ['root'], op: [new jot.SPLICE(6, 0, "cruel ")] },
        root: { document: "Hello world." },
      }
    )[1],
    new jot.SPLICE(0, 5, "Goodbye")
  );


  t.deepEqual(
    merge.merge(
      'source',
      'target',
      {
        source: { parents: ['a'], op: [new jot.SPLICE(12, 5, "universe")] },
        target: { parents: ['a'], op: [new jot.SPLICE(0, 5, "Goodbye")] },
        a: { parents: ['root'], op: [new jot.SPLICE(6, 0, "cruel ")] },
        root: { document: "Hello world." },
      }
    )[1],
    new jot.SPLICE(14, 5, "universe")
  );

  t.deepEqual(
    merge.merge(
      'source',
      'target',
      {
        target: { parents: ['a', 'source'], op: [new jot.SPLICE(8, 5, "universe"), new jot.SPLICE(0, 5, "Goodbye")] },
        source: { parents: ['root'], op: [new jot.SPLICE(6, 5, "universe")] },
        a: { parents: ['root'], op: [new jot.SPLICE(0, 5, "Goodbye")] },
        root: { document: "Hello world." },
      }
    )[1],
    new jot.NO_OP()
  );

  t.deepEqual(
    merge.merge(
      'source',
      'target',
      {
        target: { parents: ['a', 'b'], op: [new jot.SPLICE(8, 5, "universe"), new jot.SPLICE(0, 5, "Goodbye")] },
        source: { parents: ['b'], op: [new jot.SPLICE(14, 1, "!")] },
        b: { parents: ['root'], op: [new jot.SPLICE(6, 5, "universe")] },
        a: { parents: ['root'], op: [new jot.SPLICE(0, 5, "Goodbye")] },
        root: { document: "Hello world." },
      }
    )[1],
    new jot.SPLICE(16, 1, "!")
  );


  function test_merge(branch_a, branch_b, expected_content) {
    // Merge branch_b into branch_a. Merges aren't necessarily symmetric
    // in complex multi-common-ancestor scenarios, so we don't check
    // that merging bran_a into branch_b.
    branch_a.merge(branch_b);
    t.deepEqual(branch_a.content(), expected_content);
  }

  {
    let root = new merge.Document();

    let a = root.branch();
    a.commit("Hello world.");

    let b = root.branch();
    b.commit("Goodbye world.");
    
    test_merge(a, b, "Hello world.")
  }

  {
    let root = new merge.Document();
    root.commit("Hello world.");

    let a = root.branch();
    a.commit("Hello cruel world.");

    let b = root.branch();
    b.commit("Goodbye world.");
    b.commit("Goodbye world of mine.");
    
    test_merge(a, b, "Goodbye cruel world of mine.")
    test_merge(a, b, "Goodbye cruel world of mine.") // testing that repeat does nothing

    b.commit("Goodbye love of yesterday.");
    test_merge(a, b, "Goodbye cruel love of yesterday.")
    test_merge(b, a, "Goodbye cruel love of yesterday.")
    test_merge(a, b, "Goodbye cruel love of yesterday.")

    a.commit("Farewall, my love of chocolate.");
    b.commit("He said, 'Goodbye cruel love of yesterday,' before leaving.");
    test_merge(a, b, "He said, 'Farewall, my love of chocolate,' before leaving.")
  }


  {
    // This is the "When is merge recursive needed?" example
    // in http://blog.plasticscm.com/2011/09/merge-recursive-strategy.html.

    let a = new merge.Document();
    
    a.commit("10");

    let b = a.branch();

    a.commit("10 11")
    let a11 = a.branch();
    a.commit("10 11 13")
    b.commit("10 12")
    let b12 = b.branch();
    b.commit("10 12 14")

    test_merge(a, b12, "10 11 13 12");
    test_merge(b, a11, "10 11 12 14");

    //test_merge(a, b, "10 11 12 14 13");
  }

  {
    // This is the "Why merge recursive is better: a step by step example"
    // in http://blog.plasticscm.com/2011/09/merge-recursive-strategy.html.

    let a = new merge.Document();
    
    a.commit("bcd");

    let b = a.branch();
    b.commit("bcde");
    b.commit("bcdE");

    a.commit("bCd");

    let c = a.branch();
    c.commit("abCd");

    a.commit("bcd");

    test_merge(c, b, "abCdE");
    test_merge(a, b, "bcdE");
    test_merge(c, a, "abcdE");
  }

  t.end();
});


================================================
FILE: tests/meta.js
================================================
var test = require('tap').test;
var jot = require("../jot");
var lists = require("../jot/lists.js");

test('lists', function(t) {
    // inspect

    t.deepEqual(
        new lists.LIST([]).inspect(),
        "<LIST []>");

    t.deepEqual(
        new lists.LIST([new jot.SET("X")]).inspect(),
        "<LIST [<SET \"X\">]>");

    t.deepEqual(
        new lists.LIST([new jot.SET("X"), new jot.SET("Y")]).inspect(),
        "<LIST [<SET \"X\">, <SET \"Y\">]>");

	// simplify

	t.deepEqual(
		new lists.LIST([]).simplify(),
		new jot.NO_OP());

	t.deepEqual(
		new lists.LIST([ new jot.SET("X"), new jot.SET("Y") ]).simplify(),
		new jot.SET("Y") );

	t.deepEqual(
		new lists.LIST([ new jot.MATH("add", 1), new jot.MATH("add", 2) ]).simplify(),
		new jot.MATH("add", 3) );

	t.deepEqual(
		new lists.LIST([ new jot.MATH("add", 1), new jot.MATH("mult", 2) ]).simplify(),
		new lists.LIST([ new jot.MATH("add", 1), new jot.MATH("mult", 2) ]));

	t.deepEqual(
		new lists.LIST([ new jot.MATH("add", 1), new jot.MATH("mult", 2), new jot.MATH("xor", 1) ]).simplify(),
		new lists.LIST([ new jot.MATH("add", 1), new jot.MATH("mult", 2), new jot.MATH("xor", 1) ]));

    // drilldown

    t.deepEqual(
        new lists.LIST([ new jot.PUT("key1", "value1"), new jot.PUT("key2", "value2") ]).drilldown("key"),
        new jot.NO_OP());
    t.deepEqual(
        new lists.LIST([ new jot.PUT("key1", "value1"), new jot.PUT("key2", "value2") ]).drilldown("key1"),
        new jot.SET("value1"));
    t.deepEqual(
        new lists.LIST([ new jot.APPLY("key1", new jot.MATH("add", 1)), new jot.APPLY("key1", new jot.MATH("mult", 2)) ]).drilldown("key1"),
        new lists.LIST([ new jot.MATH("add", 1), new jot.MATH("mult", 2) ]));

    // compose

    t.deepEqual(
        new lists.LIST([ ])
            .compose(
                new jot.PUT('x', 'y')
            ),
        new jot.PUT('x', 'y')
    )

    t.deepEqual(
        new lists.LIST([ new jot.PUT('x', 'y') ])
            .compose(
                new lists.LIST([ ])
            ),
        new lists.LIST([ new jot.PUT('x', 'y') ])
    )

    t.deepEqual(
        new lists.LIST([ new jot.PUT('x', 'y') ])
            .compose(
                new jot.PUT('x', 'z')
            ),
        new lists.LIST([ new jot.PUT('x', 'y'), new jot.PUT('x', 'z') ])
    )

    t.deepEqual(
        new lists.LIST([ new jot.PUT('x', 'y') ])
            .compose(
                new lists.LIST([ new jot.PUT('x', 'z') ])
            ),
        new lists.LIST([ new jot.PUT('x', 'y'), new jot.PUT('x', 'z') ])
    )

    // (de)serialization

    t.deepEqual(
        new jot.deserialize(
            new lists.LIST([
                new jot.APPLY(
                    'foo', new jot.PUT(
                        'x', 'y'
                    )
                ),
                new jot.APPLY(
                    'bar', new jot.SPLICE(
                        0, 0, [{baz: 'quux'}]
                    )
                )
            ]).serialize()
        ),
        new lists.LIST([
            new jot.APPLY(
                'foo', new jot.PUT(
                    'x', 'y'
                )
            ),
            new jot.APPLY(
                'bar', new jot.SPLICE(
                    0, 0, [{baz: 'quux'}]
                )
            )
        ])
    );

    // rebase
    t.deepEqual( // empty list
        new lists.LIST([ ])
            .rebase(
                new jot.PUT('x', 'y')
            ),
        new jot.NO_OP()
    )

    t.deepEqual( // unrelated changes, unwrapping of the list
        new lists.LIST([ new jot.PUT('x', 'y') ])
            .rebase(
                new jot.PUT('a', 'b')
            ),
        new jot.PUT('x', 'y')
    )

    t.deepEqual( // conflictless (A)
        new lists.LIST([
            new jot.SET('a')
        ])
            .rebase(
                new lists.LIST([
                    new jot.SET('b')
                ]),
                true
            ),
        new jot.NO_OP()
    )

    t.deepEqual( // conflictless (B)
        new lists.LIST([
            new jot.SET('b')
        ])
            .rebase(
                new lists.LIST([
                    new jot.SET('a')
                ]),
                true
            ),
        new jot.SET('b')
    )

    t.end();
});


================================================
FILE: tests/objects.js
================================================
var test = require('tap').test;
var jot = require("../jot");
var values = require("../jot/values.js");
var seqs = require("../jot/sequences.js");
var objs = require("../jot/objects.js");
var lists = require("../jot/lists.js");

test('objects', function(t) {

// inspect

t.equal(
	new objs.PUT("0", "1").inspect(),
	'<APPLY "0":<SET "1">>');
t.equal(
	new objs.REM("0", "1").inspect(),
	'<APPLY "0":<SET ~>>');
t.equal(
	new objs.APPLY("0", new values.SET(2)).inspect(),
	'<APPLY "0":<SET 2>>');

// serialization

t.deepEqual(
	jot.opFromJSON(new objs.PUT("0", "1").toJSON()),
	new objs.PUT("0", "1"));
t.deepEqual(
	jot.opFromJSON(new objs.REM("0", "1").toJSON()),
	new objs.REM("0", "1"));
t.deepEqual(
	jot.opFromJSON(new objs.APPLY("0", new values.SET(2)).toJSON()),
	new objs.APPLY("0", new values.SET(2)));

// apply

t.deepEqual(
	new objs.PUT("a", "b")
		.apply({}),
	{ "a": "b" });
t.deepEqual(
	new objs.REM("a", "b")
		.apply({"a": "b"}),
	{});
t.deepEqual(
	new objs.APPLY(
		"a",
		new values.SET("c"))
		.apply({"a": "b"}),
	{ "a": "c" });
t.deepEqual(
	new objs.APPLY(
		"a",
		new seqs.SPLICE(0, 1, "Hello"))
		.apply({"a": "b"}),
	{ "a": "Hello" });
t.deepEqual(
	new objs.APPLY(
		"a",
		new seqs.ATINDEX(
			1,
			new values.MATH("add", 1)
			))
		.apply({"a": [0, 0]}),
	{ "a": [0, 1] });

// drilldown

t.deepEqual(
	new objs.PUT("key", "value").drilldown("other"),
	new values.NO_OP());
t.deepEqual(
	new objs.PUT("key", "value").drilldown("key"),
	new values.SET("value"));

// invert

// ...

// compose

t.deepEqual(
	new objs.PUT("key", "value").compose(new values.SET("123")),
	new values.SET("123"));
t.deepEqual(
	new objs.REM("key", "oldvalue").compose(new values.SET("123")),
	new values.SET("123"));
t.deepEqual(
	new objs.APPLY("key", new values.MATH('add', 1)).compose(new values.SET("123")),
	new values.SET("123"));
t.deepEqual(
	new objs.APPLY("key", new values.MATH('add', 1)).compose(new objs.APPLY("key", new values.MATH('add', 1))),
	new objs.APPLY("key", new values.MATH('add', 2)));
t.deepEqual(
	new objs.APPLY("key", new values.MATH('add', 1)).compose(new objs.APPLY("key", new values.MATH('add', -1))),
	new values.NO_OP());
t.deepEqual(
	new objs.APPLY("key", new values.MATH('add', 1)).compose(new objs.APPLY("key", new values.MATH('mult', 1))),
	new objs.APPLY("key", new values.MATH('add', 1)));
t.deepEqual(
	new objs.APPLY("key1", new values.MATH('add', 1)).compose(new objs.APPLY("key2", new values.MATH('mult', 2))),
	new objs.APPLY({ "key1": new values.MATH('add', 1), "key2": new values.MATH('mult', 2)}));


// rebase

t.deepEqual(
	new objs.PUT("key", "value").rebase(
		new objs.PUT("key", "value")),
	new values.NO_OP()
	)
t.notOk(
	new objs.PUT("key", "value1").rebase(
		new objs.PUT("key", "value2")))
t.deepEqual(
	new objs.PUT("key", "value1").rebase(
		new objs.PUT("key", "value2"), {}),
	new values.NO_OP())
t.deepEqual(
	new objs.PUT("key", "value2").rebase(
		new objs.PUT("key", "value1"), {}),
	new objs.APPLY("key", new values.SET("value2")))

t.deepEqual(
	new objs.REM("key", "value").rebase(
		new objs.REM("key", "value")),
	new values.NO_OP()
	)
t.deepEqual(
	new objs.REM("key1", "value1").rebase(
		new objs.REM("key2", "value2")),
	new objs.REM("key1", "value1")
	)

t.deepEqual(
	new objs.REM("key", "old_value").rebase(
		new objs.APPLY("key", new values.SET("new_value")), {}),
	new values.NO_OP()
	)
t.deepEqual(
	new objs.APPLY("key", new values.SET("new_value")).rebase(
		new objs.REM("key", "old_value"), {}),
	new objs.PUT("key", "new_value")
	)

t.deepEqual(
	new objs.APPLY('key', new values.MATH("add", 3)).rebase(
		new objs.APPLY('key', new values.MATH("add", 1))),
	new objs.APPLY('key', new values.MATH("add", 3)));
t.notOk(
	new objs.APPLY('key', new values.SET("y")).rebase(
		new objs.APPLY('key', new values.SET("z"))))
t.deepEqual(
	new objs.APPLY('key', new values.SET("y")).rebase(
		new objs.APPLY('key', new values.SET("z")), {}),
	new values.NO_OP()
	)
t.deepEqual(
	new objs.APPLY('key', new values.SET("z")).rebase(
		new objs.APPLY('key', new values.SET("y")), {}),
	new objs.APPLY('key', new values.SET("z"))
	)

// lists

t.deepEqual(
	new objs.APPLY(
		"a",
		new lists.LIST([
			new seqs.ATINDEX(
				1,
				new values.MATH("add", 1)
				),
			new seqs.ATINDEX(
				2,
				new values.MATH("add", -1)
				)
		])
	).apply({"a": [0, 0, 0]}),
	{ "a": [0, 1, -1] });

// serialization

function test_serialization(op) {
	t.deepEqual(op.toJSON(), jot.opFromJSON(op.toJSON()).toJSON());
}

test_serialization(new objs.PUT("key", "value"))
test_serialization(new objs.REM("key", "old_value"))

//

    t.end();
});



================================================
FILE: tests/random.js
================================================
var test = require('tap').test;
var jot = require('../jot')

function assertEqual(t, v1, v2) {
	// After applying math operators, floating point
	// results can come out a little different. Round
	// numbers to about as much precision as floating
	// point numbers have anyway.
	if (typeof v1 == "number" && typeof v2 == "number") {
		v1 = v1.toPrecision(8);
		v2 = v2.toPrecision(8);
	}
	t.deepEqual(v1, v2);
}

test('random', function(tt) {
for (var i = 0; i < 1000; i++) {
		// Start with a random initial document.
		var initial_value = jot.createRandomValue();
		
		// Create two operations on the initial document.
		var op1 = jot.createRandomOpSequence(initial_value, Math.floor(10*Math.random())+1)
		var op2 = jot.createRandomOpSequence(initial_value, Math.floor(10*Math.random())+1)

		tt.test(
			JSON.stringify({
				"value": initial_value,
				"op1": op1.toJSON(),
				"op2": op2.toJSON(),
			}),
			function(t) {

		// Apply each to get the intermediate values.
		var val1 = op1.apply(initial_value);
		var val2 = op2.apply(initial_value);

		// Check that the parallel rebases match.
		var val1b = op2.rebase(op1, { document: initial_value }).apply(val1);
		var val2b = op1.rebase(op2, { document: initial_value }).apply(val2);
		assertEqual(t, val1b, val2b);

		// Check that they also match using composition.
		var val1c = op1.compose(op2.rebase(op1, { document: initial_value })).apply(initial_value);
		var val2c = op2.compose(op1.rebase(op2, { document: initial_value })).apply(initial_value);
		assertEqual(t, val1c, val2c);

		// Check that we can compute a diff.
		var d = jot.diff(initial_value, val1b);

	    t.end();

		});
}
tt.end();
});


================================================
FILE: tests/sequences.js
================================================
var test = require('tap').test;
var values = require("../jot/values.js");
var seqs = require("../jot/sequences.js");
var jot = require("../jot");

test('sequences', function(t) {

// inspect

t.equal(
	new seqs.SPLICE(0, 1, "4").inspect(),
	'<PATCH +0x1 "4">');
t.equal(
	new seqs.ATINDEX(0, new values.SET(2)).inspect(),
	'<PATCH +0 <SET 2>>');
t.equal(
	new seqs.ATINDEX({ 0: new values.SET(2), 4: new values.SET(10) }).inspect(),
	'<PATCH +0 <SET 2>, +3 <SET 10>>');
t.equal(
	new seqs.MAP(new values.MATH('add', 1)).inspect(),
	'<MAP <MATH add:1>>');

// serialization

t.deepEqual(
	jot.opFromJSON(new seqs.SPLICE(0, 1, "4").toJSON()),
	new seqs.SPLICE(0, 1, "4"));
t.deepEqual(
	jot.opFromJSON(new seqs.ATINDEX(0, new values.SET(2)).toJSON()),
	new seqs.ATINDEX(0, new values.SET(2)));
t.deepEqual(
	jot.opFromJSON(new seqs.ATINDEX({ 0: new values.SET(2), 4: new values.SET(10) }).toJSON()),
	new seqs.ATINDEX({ 0: new values.SET(2), 4: new values.SET(10) }));
t.deepEqual(
	jot.opFromJSON(new seqs.MAP(new values.MATH('add', 1)).toJSON()),
	new seqs.MAP(new values.MATH('add', 1)));

// apply

t.equal(
	new seqs.SPLICE(0, 1, "4").apply("123"),
	"423");
t.equal(
	new seqs.SPLICE(0, 1, "").apply("123"),
	"23");
t.equal(
	new seqs.SPLICE(0, 1, "44").apply("123"),
	"4423");
t.equal(
	new seqs.SPLICE(3, 0, "44").apply("123"),
	"12344");

t.deepEqual(
	new seqs.ATINDEX(0, new values.SET(4)).apply([1, 2, 3]),
	[4, 2, 3]);
t.deepEqual(
	new seqs.ATINDEX(1, new values.SET(4)).apply([1, 2, 3]),
	[1, 4, 3]);
t.deepEqual(
	new seqs.ATINDEX(2, new values.SET(4)).apply([1, 2, 3]),
	[1, 2, 4]);
t.deepEqual(
	new seqs.ATINDEX({ 0: new values.SET(4), 1: new values.SET(5) })
	.apply([1, 2, 3]),
	[4, 5, 3]);

t.deepEqual(
	new seqs.ATINDEX(0, new values.SET("d")).apply("abc"),
	"dbc");
t.deepEqual(
	new seqs.ATINDEX(1, new values.SET("d")).apply("abc"),
	"adc");
t.deepEqual(
	new seqs.ATINDEX(2, new values.SET("d")).apply("abc"),
	"abd");
t.deepEqual(
	new seqs.ATINDEX({ 0: new values.SET("d"), 1: new values.SET("e") })
	.apply("abc"),
	"dec");

// simplify

t.deepEqual(
	new seqs.SPLICE(3, 0, "").simplify(),
	new values.NO_OP());
t.deepEqual(
	new seqs.SPLICE(3, 3, "456").simplify(),
	new seqs.SPLICE(3, 3, "456"));
t.deepEqual(
	new seqs.ATINDEX(0, new values.SET(2)).simplify(),
	new seqs.ATINDEX(0, new values.SET(2)));
t.deepEqual(
	new seqs.ATINDEX({
		0: new values.SET(1),
		1: new jot.LIST([]) }).simplify(),
	new seqs.ATINDEX(0, new values.SET(1)));

// drilldown

t.deepEqual(
	new seqs.ATINDEX(1, new values.SET(-1)).drilldown(0),
	new values.NO_OP());
t.deepEqual(
	new seqs.ATINDEX(1, new values.SET(-1)).drilldown(1),
	new values.SET(-1));
t.deepEqual(
	new seqs.MAP(new values.SET(-1)).drilldown(1),
	new values.SET(-1));

// invert

t.deepEqual(
	new seqs.SPLICE(3, 3, "456").inverse("xxx123"),
	new seqs.SPLICE(3, 3, "123"));
t.deepEqual(
	new seqs.ATINDEX(0, new values.SET(2)).inverse([1]),
	new seqs.ATINDEX(0, new values.SET(1)));
t.deepEqual(
	new seqs.ATINDEX({ 0: new values.SET("d"), 1: new values.SET("e") }).inverse(['a', 'b']),
	new seqs.ATINDEX({ 0: new values.SET("a"), 1: new values.SET("b") }));


// atomic_compose

t.deepEqual(
	new seqs.SPLICE(0, 4, "1234").atomic_compose(new seqs.SPLICE(5, 4, "FGHI")),
	new seqs.PATCH([{offset: 0, length: 4, op: new values.SET("1234")}, {offset: 1, length: 4, op: new values.SET("FGHI")}]));
t.deepEqual(
	new seqs.SPLICE(0, 4, "1234").atomic_compose(new seqs.SPLICE(4, 4, "EFGH")),
	new seqs.SPLICE(0, 8, "1234EFGH"));
t.deepEqual(
	new seqs.SPLICE(0, 4, "1234").atomic_compose(new seqs.SPLICE(2, 4, "CDEF")),
	new seqs.SPLICE(0, 6, "12CDEF"));  // This isn't good.
t.deepEqual(
	new seqs.SPLICE(0, 4, "1234").atomic_compose(new seqs.SPLICE(2, 2, "CD")),
	new seqs.SPLICE(0, 4, "12CD"));
t.deepEqual(
	new seqs.SPLICE(0, 4, "1234").atomic_compose(new seqs.SPLICE(0, 4, "ABCD")),
	new seqs.SPLICE(0, 4, "ABCD"));
t.deepEqual(
	new seqs.SPLICE(0, 4, "1234").atomic_compose(new seqs.SPLICE(1, 2, "BC")),
	new seqs.SPLICE(0, 4, "1BC4"));
t.deepEqual(
	new seqs.SPLICE(0, 4, "1234").atomic_compose(new seqs.SPLICE(0, 2, "AB")),
	new seqs.SPLICE(0, 4, "AB34"));
t.deepEqual(
	new seqs.SPLICE(0, 4, "1234").atomic_compose(new seqs.SPLICE(0, 6, "ABCDEF")),
	new seqs.SPLICE(0, 6, "ABCDEF"));
t.deepEqual(
	new seqs.SPLICE(2, 4, "1234").atomic_compose(new seqs.SPLICE(0, 8, "YZABCDEF")),
	new seqs.SPLICE(0, 8, "YZABCDEF"));
t.deepEqual(
	new seqs.SPLICE(2, 2, "1234").atomic_compose(new seqs.SPLICE(0, 8, "YZABCDEF")),
	new seqs.SPLICE(0, 6, "YZABCDEF"));
t.deepEqual(
	new seqs.SPLICE(2, 4, "1234").atomic_compose(new seqs.SPLICE(0, 6, "YZABCD")),
	new seqs.SPLICE(0, 6, "YZABCD"));
t.deepEqual(
	new seqs.SPLICE(2, 4, "1234").atomic_compose(new seqs.SPLICE(0, 4, "YZAB")),
	new seqs.SPLICE(0, 6, "YZAB34"));
t.deepEqual(
	new seqs.SPLICE(2, 4, "1234").atomic_compose(new seqs.SPLICE(0, 2, "YZ")),
	new seqs.SPLICE(0, 6, "YZ1234"));
t.deepEqual(
	new seqs.SPLICE(2, 4, "1234").atomic_compose(new seqs.SPLICE(0, 1, "Y")),
	new seqs.PATCH([{offset: 0, length: 1, op: new values.SET("Y")}, {offset: 1, length: 4, op: new values.SET("1234")}]));
t.deepEqual(
	new seqs.SPLICE(0, 4, "1234").atomic_compose(new seqs.PATCH([{offset: 0, length: 1, op: new values.SET("A")}, {offset: 2, length: 1, op: new values.SET("D")}])),
	new seqs.SPLICE(0, 4, "A23D"));

t.deepEqual(
	new seqs.PATCH([{offset: 0, length: 4, op: new values.SET("ab")}, {offset: 1, length: 4, op: new values.SET("defg")}])
		.atomic_compose(new seqs.ATINDEX(4, new values.SET("E"))),
	new seqs.PATCH([{offset: 0, length: 4, op: new values.SET("ab")}, {offset: 1, length: 4, op: new values.SET("dEfg")}]));
t.deepEqual(
	new seqs.SPLICE(0, 4, "5678").atomic_compose(new seqs.ATINDEX(1, new values.SET("0"))),
	new seqs.SPLICE(0, 4, "5078"));
t.deepEqual(
	new seqs.SPLICE(0, 4, "5678").atomic_compose(new seqs.ATINDEX(4, new values.SET("0"))),
	new seqs.SPLICE(0, 5, "56780"));

t.deepEqual(
	new seqs.ATINDEX(0, new values.SET("0")).atomic_compose(new seqs.SPLICE(0, 4, "5678")),
	new seqs.SPLICE(0, 4, "5678"));
t.deepEqual(
	new seqs.ATINDEX(555, new values.SET("B"))
		.atomic_compose(new seqs.ATINDEX(555, new values.SET("C"))),
	new seqs.ATINDEX(555, new values.SET("C")));
t.deepEqual(
	new seqs.ATINDEX(555, new values.MATH("add", 1))
		.atomic_compose(new seqs.ATINDEX(555, new values.MATH("mult", 2))),
	null);
t.deepEqual(
	new seqs.ATINDEX(555, new values.MATH("add", 1))
		.atomic_compose(new seqs.ATINDEX(555, new values.SET(2))),
	new seqs.ATINDEX(555, new values.SET(2)));
t.deepEqual(
	new seqs.ATINDEX(0, new values.SET("d")).atomic_compose(new seqs.ATINDEX(1, new values.SET("e"))),
	new seqs.ATINDEX({ 0: new values.SET("d"), 1: new values.SET("e") }));
t.deepEqual(
	new seqs.ATINDEX({ 0: new values.SET("d"), 1: new values.SET("e") }).atomic_compose(new seqs.ATINDEX(0, new values.SET("f"))),
	new seqs.ATINDEX({ 0: new values.SET("f"), 1: new values.SET("e") }));

// rebase

t.deepEqual(
	new seqs.SPLICE(0, 3, "456").rebase(
		new seqs.SPLICE(0, 3, "456")),
	new values.NO_OP());
t.notOk(
	new seqs.SPLICE(0, 0, "123").rebase(
		new seqs.SPLICE(0, 0, "456")));
t.deepEqual(
	new seqs.SPLICE(0, 0, "123").rebase(
		new seqs.SPLICE(0, 0, "456"), { }),
	new seqs.SPLICE(0, 0, "123"));
t.deepEqual(
	new seqs.SPLICE(0, 0, "456").rebase(
		new seqs.SPLICE(0, 0, "123"), { }),
	new seqs.SPLICE(3, 0, "456"));
t.notOk(
	new seqs.SPLICE(0, 3, "456").rebase(
		new seqs.SPLICE(0, 3, "789")));
t.deepEqual(
	new seqs.SPLICE(0, 3, "456").rebase(
		new seqs.SPLICE(0, 3, "789"), { }),
	new values.NO_OP());
t.deepEqual(
	new seqs.SPLICE(0, 3, "789").rebase(
		new seqs.SPLICE(0, 3, "456"), { }),
	new seqs.SPLICE(0, 3, "789"));
t.deepEqual(
	new seqs.SPLICE(0, 3, "456").rebase(
		new seqs.SPLICE(3, 3, "")),
	new seqs.SPLICE(0, 3, "456"));
t.deepEqual(
	new seqs.SPLICE(3, 3, "456").rebase(
		new seqs.SPLICE(0, 3, "AC")),
	new seqs.SPLICE(2, 3, "456"));

// one encompasses the other
t.deepEqual(
	new seqs.SPLICE(3, 3, "456").rebase(
		new seqs.SPLICE(3, 1, "ABC"), { }),
	new seqs.SPLICE(6, 2, "456"));
t.deepEqual(
	new seqs.SPLICE(3, 3, "456").rebase(
		new seqs.SPLICE(4, 1, "ABC"), { }),
	new seqs.SPLICE(3, 1, "").compose(new seqs.SPLICE(6, 1, "456")));
t.deepEqual(
	new seqs.SPLICE(3, 3, "456").rebase(
		new seqs.SPLICE(5, 1, "ABC"), { }),
	new seqs.SPLICE(3, 2, ""));
t.deepEqual(
	new seqs.SPLICE(3, 1, "ABC").rebase(
		new seqs.SPLICE(3, 3, "456"), { }),
	new seqs.SPLICE(3, 0, "ABC"));
t.deepEqual(
	new seqs.SPLICE(4, 1, "ABC").rebase(
		new seqs.SPLICE(3, 3, "456"), { }),
	new seqs.SPLICE(3, 0, "ABC"));
t.deepEqual(
	new seqs.SPLICE(5, 1, "ABC").rebase(
		new seqs.SPLICE(3, 3, "456"), { }),
	new seqs.SPLICE(3, 3, "ABC"));
t.deepEqual(
	new seqs.SPLICE(3, 3, "456").rebase(
		new seqs.SPLICE(2, 4, "ABC"), { }),
	new values.NO_OP());
t.deepEqual(
	new seqs.SPLICE(3, 3, "456").rebase(
		new seqs.SPLICE(2, 5, "ABC"), { }),
	new seqs.SPLICE(2, 0, "456"));

// partial overlap
t.deepEqual(
	new seqs.SPLICE(3, 3, "456").rebase(
		new seqs.SPLICE(2, 2, "ABC"), { }),
	new seqs.SPLICE(5, 2, "456"));
t.deepEqual(
	new seqs.SPLICE(3, 3, "456").rebase(
		new seqs.SPLICE(5, 2, "ABC"), { }),
	new seqs.SPLICE(3, 2, "456"));
t.deepEqual(
	new seqs.SPLICE(3, 3, "456").rebase(
		new seqs.SPLICE(4, 3, "AB"), { }),
	new seqs.SPLICE(3, 1, "456"));
t.deepEqual(
	new seqs.SPLICE(2, 2, "ABC").rebase(
		new seqs.SPLICE(3, 3, "46"), { }),
	new seqs.SPLICE(2, 1, "ABC"));
t.deepEqual(
	new seqs.SPLICE(5, 2, "ABC").rebase(
		new seqs.SPLICE(3, 3, "46"), { }),
	new seqs.SPLICE(5, 1, "ABC"));
t.deepEqual(
	new seqs.SPLICE(4, 3, "ABC").rebase(
		new seqs.SPLICE(3, 3, "46"), { }),
	new seqs.SPLICE(5, 1, "ABC"));

// splice vs apply

t.deepEqual(
	new seqs.SPLICE(0, 3, [4,5,6]).rebase(
		new seqs.ATINDEX(0, new values.MATH("add", 1)), { }),
	new seqs.SPLICE(0, 3, [4,5,6]));

// splice vs map

t.notOk(
	new seqs.SPLICE(1, 3, [4,5]).rebase(
		new seqs.MAP(new values.MATH("add", 1))));
t.notOk(
	new seqs.MAP(new values.MATH("add", 1)).rebase(
		new seqs.SPLICE(1, 3, [4,5])));

// apply vs splice

t.deepEqual(
	new seqs.ATINDEX(555, new values.MATH("add", 3)).rebase(
		new seqs.SPLICE(555, 0, [5])),
	new seqs.ATINDEX(556, new values.MATH("add", 3)));

// apply vs apply

t.deepEqual(
	new seqs.ATINDEX(555, new values.MATH("add", 3)).rebase(
		new seqs.ATINDEX(555, new values.MATH("add", 1))),
	new seqs.ATINDEX(555, new values.MATH("add", 3)));
t.notOk(
	new seqs.ATINDEX(555, new values.SET("y")).rebase(
		new seqs.ATINDEX(555, new values.SET("z"))))
t.deepEqual(
	new seqs.ATINDEX(555, new values.SET("y")).rebase(
		new seqs.ATINDEX(555, new values.SET("z")), { }),
	new values.NO_OP()
	)
t.deepEqual(
	new seqs.ATINDEX(555, new values.SET("z")).rebase(
		new seqs.ATINDEX(555, new values.SET("y")), { }),
	new seqs.ATINDEX(555, new values.SET("z"))
	)
t.deepEqual(
	new seqs.ATINDEX({0: new values.SET("z"), 1: new values.SET("b"), 2: new values.SET("N")}).rebase(
		new seqs.ATINDEX({0: new values.SET("y"), 1: new values.SET(" ")}), { }),
	new seqs.ATINDEX({0: new values.SET("z"), 1: new values.SET("b"), 2: new values.SET("N")})
	)

// apply vs map

t.deepEqual(
	new seqs.ATINDEX(555, new values.MATH("add", 3)).rebase(
		new seqs.MAP(new values.MATH("add", 1))),
	new seqs.ATINDEX(555, new values.MATH("add", 3)));

// map vs apply

t.deepEqual(
	new seqs.MAP(new values.MATH("add", 1)).rebase(
		new seqs.ATINDEX(555, new values.MATH("add", 3))),
	new seqs.MAP(new values.MATH("add", 1)));
t.notOk(
	new seqs.MAP(new values.MATH("add", 1)).rebase(
		new seqs.ATINDEX(555, new values.MATH("mult", 2))));

// map vs map

t.deepEqual(
	new seqs.MAP(new values.MATH("add", 1)).rebase(
		new seqs.MAP(new values.MATH("add", 3))),
	new seqs.MAP(new values.MATH("add", 1)));
t.notOk(
	new seqs.MAP(new values.MATH("add", 1)).rebase(
		new seqs.MAP(new values.MATH("mult", 3))));

t.end();

});


================================================
FILE: tests/values.js
================================================
var test = require('tap').test;
var jot = require('../jot')
var values = require("../jot/values.js");
var MISSING = require("../jot/objects.js").MISSING;
var LIST = require("../jot/lists.js").LIST;

test('values', function(t) {

// inspect

t.equal(
	new values.NO_OP().inspect(),
	"<NO_OP>");
t.equal(
	new values.SET(4).inspect(),
	'<SET 4>');
t.equal(
	new values.MATH('add', 4).inspect(),
	'<MATH add:4>');

// serialization

t.deepEqual(
	jot.deserialize(new values.NO_OP().serialize()),
	new values.NO_OP());
t.deepEqual(
	jot.opFromJSON(new values.NO_OP().toJSON()),
	new values.NO_OP());
t.deepEqual(
	jot.opFromJSON(new values.SET(4).toJSON()),
	new values.SET(4));
t.deepEqual(
	jot.opFromJSON(new values.MATH('add', 4).toJSON()),
	new values.MATH('add', 4));

// apply

t.equal(
	new values.NO_OP().apply("1"),
	"1");

t.equal(
	new values.SET("2").apply("1"),
	"2");

t.equal(
	new values.MATH("add", 5).apply(1),
	6);
t.equal(
	new values.MATH("rot", [2, 3]).apply(1),
	0);
t.equal(
	new values.MATH("mult", 5).apply(2),
	10);
t.equal(
	new values.MATH("and", 0xF0).apply(0xF1),
	0xF0);
t.equal(
	new values.MATH("or", 0xF0).apply(0x1),
	0xF1);
t.equal(
	new values.MATH("xor", 12).apply(25),
	21);
t.equal(
	new values.MATH("xor", 1).apply(true),
	false);
t.equal(
	new values.MATH("xor", 1).apply(false),
	true);
t.equal(
	new values.MATH("not", null).apply(0xF1),
	~0xF1);

// simplify

t.deepEqual(
	new values.NO_OP().simplify(),
	new values.NO_OP());

t.deepEqual(
	new values.SET(1).simplify(),
	new values.SET(1));

t.deepEqual(
	new values.MATH("add", 5).simplify(),
	new values.MATH("add", 5));
t.deepEqual(
	new values.MATH("add", 0).simplify(),
	new values.NO_OP());
t.deepEqual(
	new values.MATH("rot", [0, 999]).simplify(),
	new values.NO_OP());
t.deepEqual(
	new values.MATH("mult", 0).simplify(),
	new values.MATH("mult", 0));
t.deepEqual(
	new values.MATH("mult", 1).simplify(),
	new values.NO_OP());
t.deepEqual(
	new values.MATH("and", 0).simplify(),
	new values.SET(0));
t.deepEqual(
	new values.MATH("or", 0).simplify(),
	new values.NO_OP());
t.deepEqual(
	new values.MATH("xor", 0).simplify(),
	new values.NO_OP());

// invert

t.deepEqual(
	new values.NO_OP().inverse('anything here'),
	new values.NO_OP());

t.deepEqual(
	new values.SET(1).inverse(0),
	new values.SET(0));

t.deepEqual(
	new values.MATH("add", 5).inverse(0),
	new values.MATH("add", -5));
t.deepEqual(
	new values.MATH("rot", [5, 20]).inverse(0),
	new values.MATH("rot", [-5, 20]));
t.deepEqual(
	new values.MATH("mult", 5).inverse(0),
	new values.MATH("mult", 1/5));
t.deepEqual(
	new values.MATH("and", 0xF0).inverse(0xF),
	new values.MATH("or", 0xF));
t.deepEqual(
	new values.MATH("or", 0xF0).inverse(0xF),
	new values.MATH("xor", 0xF0));
t.deepEqual(
	new values.MATH("xor", 5).inverse(0),
	new values.MATH("xor", 5));

// drilldown

t.deepEqual(
	new values.SET({ 5: "A" }).drilldown('4'),
	new values.SET(MISSING));
t.deepEqual(
	new values.SET({ 5: "A" }).drilldown('5'),
	new values.SET("A"));
t.deepEqual(
	new values.SET([0, -1, -2]).drilldown(3),
	new values.SET(MISSING));
t.deepEqual(
	new values.SET([0, -1, -2]).drilldown(2),
	new values.SET(-2));

// compose

t.deepEqual(
	new values.NO_OP().compose(
		new values.SET(2) ),
	new values.SET(2));
t.deepEqual(
	new values.SET(2).compose(
		new values.NO_OP() ),
	new values.SET(2));

t.deepEqual(
	new values.SET(1).compose(
		new values.SET(2) ),
	new values.SET(2));

t.deepEqual(
	new values.MATH("add", 1).compose(
		new values.MATH("add", 1) ),
	new values.MATH("add", 2));
t.deepEqual(
	new values.MATH("rot", [3, 13]).compose(
		new values.MATH("rot", [4, 13]) ),
	new values.MATH("rot", [7, 13]));
t.deepEqual(
	new values.MATH("mult", 2).compose(
		new values.MATH("mult", 3) ),
	new values.MATH("mult", 6));
t.deepEqual(
	new values.MATH("add", 1).atomic_compose(
		new values.MATH("mult", 2) ),
	null);
t.deepEqual(
	new values.MATH("and", 0x1).atomic_compose(
		new values.MATH("and", 0x2) ),
	new values.SET(0));
t.deepEqual(
	new values.MATH("or", 0x1).atomic_compose(
		new values.MATH("or", 0x2) ),
	new values.MATH("or", 0x3));
t.deepEqual(
	new values.MATH("xor", 12).compose(
		new values.MATH("xor", 3) ),
	new values.MATH("xor", 15));
t.deepEqual(
	new values.MATH("not", null).compose(
		new values.MATH("not", null) ),
	new values.NO_OP());

t.deepEqual(
	new values.MATH("add", 1).compose(
		new values.SET(3) ),
	new values.SET(3));

// rebase

t.deepEqual(
	new values.NO_OP().rebase(new values.NO_OP() ),
	new values.NO_OP());
t.deepEqual(
	new values.NO_OP().rebase(new values.MATH("add", 1) ),
	new values.NO_OP());

t.deepEqual(
	new values.SET(1).rebase(new values.SET(1) ),
	new values.NO_OP());
t.deepEqual(
	new values.SET(2).rebase(new values.SET(1) ),
	null);
t.deepEqual(
	new values.SET(2).rebase(new values.SET(1), true),
	new values.SET(2));
t.deepEqual(
	new values.SET(1).rebase(new values.SET(2), true),
	new values.NO_OP());

t.deepEqual(
	new values.SET(2).rebase(new values.MATH("add", 3)),
	new values.SET(2));
t.deepEqual(
	new values.SET("2").rebase(new values.MATH("add", 3), true),
	new values.SET("2"));

t.deepEqual(
	new values.MATH("add", 1).rebase(new values.NO_OP() ),
	new values.MATH("add", 1));
t.deepEqual(
	new values.MATH("add", 2).rebase(new values.MATH("add", 1) ),
	new values.MATH("add", 2));
t.deepEqual(
	new values.MATH("rot", [1, 3]).rebase(new values.MATH("rot", [2, 3]) ),
	new values.MATH("rot", [1, 3]));
t.notOk(
	new values.MATH("mult", 2).rebase(new values.MATH("add", 1) )
	);
t.deepEqual(
	new values.MATH("xor", 3).rebase(new values.MATH("xor", 12) ),
	new values.MATH("xor", 3));

t.deepEqual(
	new values.MATH("add", 3).rebase(new values.SET(2)),
	new values.NO_OP());
t.deepEqual(
	new values.MATH("add", 3).rebase(new values.SET("2"), true),
	new values.NO_OP());


    t.end();
});
Download .txt
gitextract_o8ilj5l0/

├── .gitignore
├── README.md
├── TODO.md
├── browser_example/
│   ├── browserfy_root.js
│   └── example.html
├── example.js
├── index.js
├── jot/
│   ├── copies.js
│   ├── diff.js
│   ├── index.js
│   ├── lists.js
│   ├── merge.js
│   ├── objects.js
│   ├── sequences.js
│   └── values.js
├── package.json
├── test.js
└── tests/
    ├── diff.js
    ├── merge.js
    ├── meta.js
    ├── objects.js
    ├── random.js
    ├── sequences.js
    └── values.js
Download .txt
SYMBOL INDEX (38 symbols across 12 files)

FILE: jot/copies.js
  function serialize_pointer (line 44) | function serialize_pointer(jp) {
  function parse_path (line 90) | function parse_path(jp, document) {
  function drilldown_op (line 102) | function drilldown_op(jp, document, op) {
  function wrap_op_in_path (line 109) | function wrap_op_in_path(jp, document, op) {
  function make_random_path (line 165) | function make_random_path(doc) {

FILE: jot/diff.js
  function diff (line 8) | function diff(a, b, options) {
  function diff_strings (line 81) | function diff_strings(a, b, options) {
  function diff_arrays (line 131) | function diff_arrays(a, b, options) {
  function diff_objects (line 255) | function diff_objects(a, b, options) {

FILE: jot/index.js
  function extend_op_map (line 115) | function extend_op_map(module) {

FILE: jot/lists.js
  function rebase_array (line 187) | function rebase_array(base, ops, conflictless, debug) {

FILE: jot/merge.js
  function keys (line 13) | function keys(obj) {
  function node_name_str (line 18) | function node_name_str(node) {
  function get_document_content_at (line 44) | function get_document_content_at(n) {
  function lowest_common_ancestors (line 140) | function lowest_common_ancestors(a, b, graph) {
  method constructor (line 227) | constructor(ops, parents, document) {
  method add_to_graph (line 234) | add_to_graph(graph) {
  method constructor (line 248) | constructor(name) {
  method commit (line 255) | commit(op) {
  method branch (line 273) | branch(name) {
  method head (line 281) | head() {
  method content (line 285) | content() {
  method merge (line 289) | merge(b) {

FILE: jot/objects.js
  function build_conflictless (line 223) | function build_conflictless(key) {

FILE: jot/sequences.js
  function elem (line 86) | function elem(seq, pos) {
  function concat2 (line 93) | function concat2(item1, item2) {
  function concat3 (line 98) | function concat3(item1, item2, item3) {
  function map_index (line 103) | function map_index(pos, move_op) {
  function handle_hunk (line 315) | function handle_hunk(hunk) {
  function compose_patches (line 427) | function compose_patches(a, b) {
  function rebase_patches (line 647) | function rebase_patches(a, b, conflictless) {

FILE: jot/values.js
  function str (line 196) | function str(v) {
  function concat2 (line 600) | function concat2(item1, item2) {
  function concat3 (line 605) | function concat3(item1, item2, item3) {

FILE: tests/diff.js
  function test (line 7) | function test(a, b, options) {

FILE: tests/merge.js
  function test_merge (line 105) | function test_merge(branch_a, branch_b, expected_content) {

FILE: tests/objects.js
  function test_serialization (line 183) | function test_serialization(op) {

FILE: tests/random.js
  function assertEqual (line 4) | function assertEqual(t, v1, v2) {
Condensed preview — 24 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (199K chars).
[
  {
    "path": ".gitignore",
    "chars": 160,
    "preview": "*~\nnode_modules\nbrowser_example/jot.js\njson_editor_example/img/\njson_editor_example/jot.js\njson_editor_example/jsonedito"
  },
  {
    "path": "README.md",
    "chars": 16189,
    "preview": "JSON Operational Transformation (JOT)\n=====================================\n\nBy Joshua Tauberer <https://razor.occams.in"
  },
  {
    "path": "TODO.md",
    "chars": 443,
    "preview": "* PATCH inside PATCH must go away in simplfy in case a PATCH ends up inside a PATCH from rebasing a MAP\n* Document diff."
  },
  {
    "path": "browser_example/browserfy_root.js",
    "chars": 127,
    "preview": "// This is for building jot for use in browsers. Expose\n// the library in a global 'jot' object.\nglobal.jot = require(\"."
  },
  {
    "path": "browser_example/example.html",
    "chars": 1321,
    "preview": "<html>\n\t<head>\n\t\t<script src=\"jot.js\"> </script>\n\t</head>\n\t<body>\n\t\t<textarea id=\"mybox\" style=\"width: 100%; height: 40%"
  },
  {
    "path": "example.js",
    "chars": 1563,
    "preview": "/* load libraries (test if 'jot' is defined already, so we can use this in the browser where 'require' is not available)"
  },
  {
    "path": "index.js",
    "chars": 34,
    "preview": "module.exports = require(\"./jot\")\n"
  },
  {
    "path": "jot/copies.js",
    "chars": 6562,
    "preview": "/*  This module defines one operation:\n\t\n\tCOPY([[source, target], ...])\n\t\n\tClones values from source to target for each "
  },
  {
    "path": "jot/diff.js",
    "chars": 9695,
    "preview": "// Construct JOT operations by performing a diff on\n// standard data types.\n\nvar deepEqual = require(\"deep-equal\");\n\nvar"
  },
  {
    "path": "jot/index.js",
    "chars": 12930,
    "preview": "/* Base functions for the operational transformation library. */\n\nvar util = require('util');\nvar shallow_clone = requir"
  },
  {
    "path": "jot/lists.js",
    "chars": 10391,
    "preview": "/*  This module defines one operation:\n\t\n\tLIST([op1, op2, ...])\n\t\n\tA composition of zero or more operations, given as an"
  },
  {
    "path": "jot/merge.js",
    "chars": 10792,
    "preview": "// Performs a merge, i.e. given a directed graph of operations with in-degree\n// of 1 or 2, and two nodes 'source' and '"
  },
  {
    "path": "jot/objects.js",
    "chars": 8966,
    "preview": "/* A library of operations for objects (i.e. JSON objects/Javascript associative arrays).\n\n   new objects.PUT(key, value"
  },
  {
    "path": "jot/sequences.js",
    "chars": 40186,
    "preview": "/* An operational transformation library for sequence-like objects,\n   i.e. strings and arrays.\n\n   The main operation p"
  },
  {
    "path": "jot/values.js",
    "chars": 26027,
    "preview": "/*  An operational transformation library for atomic values.\n\n\tThis library provides three operations: NO_OP (an operati"
  },
  {
    "path": "package.json",
    "chars": 491,
    "preview": "{\n  \"name\": \"jot\",\n  \"version\": \"0.0.1\",\n  \"description\": \"JSON Operational Transformation\",\n  \"dependencies\": {\n    \"de"
  },
  {
    "path": "test.js",
    "chars": 2729,
    "preview": "var jot = require(\"./jot\");\nvar deepEqual = require(\"deep-equal\");\nvar createRandomOp = jot.createRandomOp; // require('"
  },
  {
    "path": "tests/diff.js",
    "chars": 907,
    "preview": "var test = require('tap').test;\nvar jot = require('../jot')\nvar diff = require(\"../jot/diff.js\");\n\ntest('diff', function"
  },
  {
    "path": "tests/merge.js",
    "chars": 5143,
    "preview": "const test = require('tap').test;\nconst jot = require('../jot')\nconst merge = require(\"../jot/merge.js\");\n\ntest('merge',"
  },
  {
    "path": "tests/meta.js",
    "chars": 4291,
    "preview": "var test = require('tap').test;\nvar jot = require(\"../jot\");\nvar lists = require(\"../jot/lists.js\");\n\ntest('lists', func"
  },
  {
    "path": "tests/objects.js",
    "chars": 4623,
    "preview": "var test = require('tap').test;\nvar jot = require(\"../jot\");\nvar values = require(\"../jot/values.js\");\nvar seqs = requir"
  },
  {
    "path": "tests/random.js",
    "chars": 1667,
    "preview": "var test = require('tap').test;\nvar jot = require('../jot')\n\nfunction assertEqual(t, v1, v2) {\n\t// After applying math o"
  },
  {
    "path": "tests/sequences.js",
    "chars": 11863,
    "preview": "var test = require('tap').test;\nvar values = require(\"../jot/values.js\");\nvar seqs = require(\"../jot/sequences.js\");\nvar"
  },
  {
    "path": "tests/values.js",
    "chars": 5838,
    "preview": "var test = require('tap').test;\nvar jot = require('../jot')\nvar values = require(\"../jot/values.js\");\nvar MISSING = requ"
  }
]

About this extraction

This page contains the full source code of the JoshData/jot GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 24 files (178.7 KB), approximately 51.0k tokens, and a symbol index with 38 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!