Skip to content

Commit

Permalink
Switching up the vocabulary once again, hopefully this is the last ti…
Browse files Browse the repository at this point in the history
…me. I don't like doing this, but I think this is the most clear. s/change/edit/
  • Loading branch information
fitzgen committed Apr 26, 2011
1 parent ce05e8e commit dfd0ec1
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 74 deletions.
82 changes: 41 additions & 41 deletions operations.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Operations are a stream of individual changes which span the whole document
// from start to finish. Changes have a type which is one of retain, insert, or
// Operations are a stream of individual edits which span the whole document
// from start to finish. Edits have a type which is one of retain, insert, or
// delete, and have associated data based on their type.


Expand All @@ -11,7 +11,7 @@

define(function () {

// Simple change constructors.
// Simple edit constructors.

function insert (chars) {
return "i" + chars;
Expand All @@ -25,30 +25,30 @@ define(function () {
return "r" + String(n);
}

function type (change) {
switch ( change.charAt(0) ) {
function type (edit) {
switch ( edit.charAt(0) ) {
case "r":
return "retain";
case "d":
return "delete";
case "i":
return "insert";
default:
throw new TypeError("Unknown type of change: ", change);
throw new TypeError("Unknown type of edit: ", edit);
}
}

function val (change) {
return type(change) === "r"
? Number(change.slice(1))
: change.slice(1);
function val (edit) {
return type(edit) === "r"
? Number(edit.slice(1))
: edit.slice(1);
}

// We don't want to copy arrays all the time, aren't mutating lists, and
// only need O(1) prepend and length, we can get away with a custom singly
// linked list implementation.

// TODO: keep track of number of non-retain changes and use this instead of
// TODO: keep track of number of non-retain edits and use this instead of
// length when choosing which path to take.

var theEmptyList = {
Expand Down Expand Up @@ -77,23 +77,23 @@ define(function () {
};
}

// Abstract out the table in case I want to change the implementation to
// Abstract out the table in case I want to edit the implementation to
// arrays of arrays or something.

function put (table, x, y, changes) {
return (table[String(x) + "," + String(y)] = changes);
function put (table, x, y, edits) {
return (table[String(x) + "," + String(y)] = edits);
}

function get (table, x, y) {
var changes = table[String(x) + "," + String(y)];
if ( changes ) {
return changes;
var edits = table[String(x) + "," + String(y)];
if ( edits ) {
return edits;
} else {
throw new TypeError("No operations at " + String(x) + "," + String(y));
throw new TypeError("No operation at " + String(x) + "," + String(y));
}
}

function makeChangesTable (s, t) {
function makeEditsTable (s, t) {
var table = {},
n = s.length,
m = t.length,
Expand All @@ -112,53 +112,53 @@ define(function () {
}

function chooseCell (table, x, y, k) {
var prevChanges = get(table, x, y-1),
min = prevChanges.length,
var prevEdits = get(table, x, y-1),
min = prevEdits.length,
direction = "up";

if ( get(table, x-1, y).length < min ) {
prevChanges = get(table, x-1, y);
min = prevChanges.length;
prevEdits = get(table, x-1, y);
min = prevEdits.length;
direction = "left";
}

if ( get(table, x-1, y-1).length < min ) {
prevChanges = get(table, x-1, y-1);
min = prevChanges.length;
prevEdits = get(table, x-1, y-1);
min = prevEdits.length;
direction = "diagonal";
}

return k(direction, prevChanges);
return k(direction, prevEdits);
}

return {

// Constructor for operations (which are a stream of changes). Uses
// Constructor for operations (which are a stream of edits). Uses
// variation of Levenshtein Distance.
operation: function (s, t) {
var n = s.length,
m = t.length,
i,
j,
changes = makeChangesTable(s, t);
edits = makeEditsTable(s, t);

for ( i = 1; i <= m; i += 1 ) {
for ( j = 1; j <= n; j += 1 ) {
chooseCell(changes, i, j, function (direction, prevChanges) {
chooseCell(edits, i, j, function (direction, prevEdits) {
switch ( direction ) {
case "left":
put(changes, i, j, cons(insert(t.charAt(i-1)), prevChanges));
put(edits, i, j, cons(insert(t.charAt(i-1)), prevEdits));
break;
case "up":
put(changes, i, j, cons(del(s.charAt(j-1)), prevChanges));
put(edits, i, j, cons(del(s.charAt(j-1)), prevEdits));
break;
case "diagonal":
if ( s.charAt(j-1) === t.charAt(i-1) ) {
put(changes, i, j, cons(retain(1), prevChanges));
put(edits, i, j, cons(retain(1), prevEdits));
} else {
put(changes, i, j, cons(insert(t.charAt(i-1)),
put(edits, i, j, cons(insert(t.charAt(i-1)),
cons(del(s.charAt(j-1)),
prevChanges)));
prevEdits)));
}
break;
default:
Expand All @@ -168,7 +168,7 @@ define(function () {
}
}

return get(changes, m, n).toArray().reverse();
return get(edits, m, n).toArray().reverse();
},

insert: insert,
Expand All @@ -177,16 +177,16 @@ define(function () {
type: type,
val: val,

isDelete: function (change) {
return typeof change === "object" && type(change) === "delete";
isDelete: function (edit) {
return typeof edit === "object" && type(edit) === "delete";
},

isRetain: function (change) {
return typeof change === "object" && type(change) === "retain";
isRetain: function (edit) {
return typeof edit === "object" && type(edit) === "retain";
},

isInsert: function (change) {
return typeof change === "object" && type(change) === "insert";
isInsert: function (edit) {
return typeof edit === "object" && type(edit) === "insert";
}

};
Expand Down
66 changes: 33 additions & 33 deletions xform.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,74 +9,74 @@

define(["./operations"], function (ops) {

// Pattern match on two changes by looking up their transforming function in
// Pattern match on two edits by looking up their transforming function in
// the `xformTable`. Each function in the table should take arguments like
// the following:
//
// xformer(changeA, changeB, indexA, indexB, continuation)
// xformer(editA, editB, indexA, indexB, continuation)
//
// and should return the results by calling the continuation
//
// return continuation(changeAPrime || null, changeBPrime || null, newIndexA, newIndexB);
// return continuation(editAPrime || null, editBPrime || null, newIndexA, newIndexB);

var xformTable = {};

function join (a, b) {
return a + "," + b;
}

// Define a transformation function for when we are comparing two changes of
// Define a transformation function for when we are comparing two edits of
// typeA and typeB.
function defXformer (typeA, typeB, xformer) {
xformTable[join(typeA, typeB)] = xformer;
}

// Assumptions currently made by all of the xformer functions: that all of
// the individual changes only deal with one character at a time.
// the individual edits only deal with one character at a time.

defXformer("retain", "retain", function (changeA, changeB, indexA, indexB, k) {
k(changeA, changeB, indexA+1, indexB+1);
defXformer("retain", "retain", function (editA, editB, indexA, indexB, k) {
k(editA, editB, indexA+1, indexB+1);
});

defXformer("delete", "delete", function (changeA, changeB, indexA, indexB, k) {
if ( ops.val(changeA) === ops.val(changeB) ) {
defXformer("delete", "delete", function (editA, editB, indexA, indexB, k) {
if ( ops.val(editA) === ops.val(editB) ) {
k(null, null, indexA+1, indexB+1);
} else {
throw new TypeError("Document state mismatch: delete("
+ ops.val(changeA) + ") !== delete(" + ops.val(changeB) + ")");
+ ops.val(editA) + ") !== delete(" + ops.val(editB) + ")");
}
});

defXformer("insert", "insert", function (changeA, changeB, indexA, indexB, k) {
if ( ops.val(changeA) === ops.val(changeB) ) {
defXformer("insert", "insert", function (editA, editB, indexA, indexB, k) {
if ( ops.val(editA) === ops.val(editB) ) {
k(ops.retain(1), ops.retain(1), indexA+1, indexB+1);
} else {
k(changeA, ops.retain(1), indexA+1, indexB);
k(editA, ops.retain(1), indexA+1, indexB);
}
});

defXformer("retain", "delete", function (changeA, changeB, indexA, indexB, k) {
k(null, changeB, indexA+1, indexB+1);
defXformer("retain", "delete", function (editA, editB, indexA, indexB, k) {
k(null, editB, indexA+1, indexB+1);
});

defXformer("delete", "retain", function (changeA, changeB, indexA, indexB, k) {
k(changeA, null, indexA+1, indexB+1);
defXformer("delete", "retain", function (editA, editB, indexA, indexB, k) {
k(editA, null, indexA+1, indexB+1);
});

defXformer("insert", "retain", function (changeA, changeB, indexA, indexB, k) {
k(changeA, changeB, indexA+1, indexB);
defXformer("insert", "retain", function (editA, editB, indexA, indexB, k) {
k(editA, editB, indexA+1, indexB);
});

defXformer("retain", "insert", function (changeA, changeB, indexA, indexB, k) {
k(changeA, changeB, indexA, indexB+1);
defXformer("retain", "insert", function (editA, editB, indexA, indexB, k) {
k(editA, editB, indexA, indexB+1);
});

defXformer("insert", "delete", function (changeA, changeB, indexA, indexB, k) {
k(changeA, ops.retain(1), indexA+1, indexB);
defXformer("insert", "delete", function (editA, editB, indexA, indexB, k) {
k(editA, ops.retain(1), indexA+1, indexB);
});

defXformer("delete", "insert", function (changeA, changeB, indexA, indexB, k) {
k(ops.retain(1), changeB, indexA, indexB+1);
defXformer("delete", "insert", function (editA, editB, indexA, indexB, k) {
k(ops.retain(1), editB, indexA, indexB+1);
});

return function (operationA, operationB, k) {
Expand All @@ -86,8 +86,8 @@ define(["./operations"], function (ops) {
lenB = operationB.length,
indexA = 0,
indexB = 0,
changeA,
changeB,
editA,
editB,
xformer;

// Continuation for the xformer.
Expand All @@ -103,18 +103,18 @@ define(["./operations"], function (ops) {
}

while ( indexA < lenA && indexB < lenB ) {
changeA = operationA[indexA];
changeB = operationB[indexB];
xformer = xformTable[join(ops.type(changeA), ops.type(changeB))];
editA = operationA[indexA];
editB = operationB[indexB];
xformer = xformTable[join(ops.type(editA), ops.type(editB))];
if ( xformer ) {
xformer(changeA, changeB, indexA, indexB, kk);
xformer(editA, editB, indexA, indexB, kk);
} else {
throw new TypeError("Unknown combination to transform: "
+ join(ops.type(changeA), ops.type(changeB)));
+ join(ops.type(editA), ops.type(editB)));
}
}

// If either operation contains more changes than the other, we just
// If either operation contains more edits than the other, we just
// pass them on to the prime version.

for ( ; indexA < lenA; indexA++ ) {
Expand Down

0 comments on commit dfd0ec1

Please sign in to comment.