diff --git a/diff.js b/diff.js index b3173bc..beda689 100644 --- a/diff.js +++ b/diff.js @@ -251,6 +251,14 @@ var proxy = function(path, func){ // XXX need to avoid recursion... // XXX should we avoid backtracking when pattern matching??? // ...specifically when working with IN and OF... +// XXX diffing a mismatching pattern should yield the exact position +// (sub-pattern/rule) that failed and not just the whole pattern... +// ...usually a pattern chain fails, i.e. the nested failing pattern +// also fails its parent and so on, so it is not a trivial task +// getting the source and probably the whole failed chain... +// ...might be a good idea to build a trace failure pattern and +// store it in .trace in the diff... +// // // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - var LogicTypeClassPrototype = { @@ -280,9 +288,9 @@ var LogicTypePrototype = { || (obj != null && obj.__cmp__ && obj.__cmp__(this, cmp, cache)) - c.set(obj, res) + c.set(obj, !!res) - return res + return !!res }, } @@ -456,8 +464,8 @@ module.ARRAY = var NOT = module.NOT = object.makeConstructor('NOT', Object.assign(new LogicType(), { - __cmp__: function(obj, cmp){ - return !cmp(this.value, obj) }, + __cmp__: function(obj, cmp, cache){ + return !cmp(this.value, obj, cache) }, __init__: function(value){ this.value = value }, @@ -468,9 +476,9 @@ object.makeConstructor('NOT', Object.assign(new LogicType(), { var OR = module.OR = object.makeConstructor('OR', Object.assign(new LogicType(), { - __cmp__: function(obj, cmp){ + __cmp__: function(obj, cmp, cache){ for(var m of this.members){ - if(cmp(m, obj)){ + if(cmp(m, obj, cache)){ return true } } @@ -486,9 +494,9 @@ object.makeConstructor('OR', Object.assign(new LogicType(), { var AND = module.AND = object.makeConstructor('AND', Object.assign(new LogicType(), { - __cmp__: function(obj, cmp){ + __cmp__: function(obj, cmp, cache){ for(var m of this.members){ - if(!cmp(m, obj)){ + if(!cmp(m, obj, cache)){ return false } } @@ -510,14 +518,14 @@ var IN = module.IN = object.makeConstructor('IN', Object.assign(new LogicType(), { // XXX add support for other stuff like sets and maps... - __cmp__: function(obj, cmp){ + __cmp__: function(obj, cmp, cache){ var p = this.value // XXX make this a break-on-match and not a go-through-the-whole-thing return typeof(obj) == typeof({}) && (p in obj || obj.reduce(function(res, e){ return res === false ? - cmp(p, e) + cmp(p, e, cache) : res }), false) }, __init__: function(value){ this.value = value @@ -538,8 +546,8 @@ object.makeConstructor('IN', Object.assign(new LogicType(), { var AT = module.AT = object.makeConstructor('AT', Object.assign(new LogicType(), { - __cmp__: function(obj, cmp){ - if(cmp(obj != null ? obj[this.key] : null, this.value)){ + __cmp__: function(obj, cmp, cache){ + if(cmp(obj != null ? obj[this.key] : null, this.value, cache)){ return true } return false @@ -1033,6 +1041,7 @@ module.Types = { // the above. // // NOTE: array path also supports patterns... + // XXX should this use cmp(..) or this.cmp(..) filter: function(diff, filter){ // string filter... filter = typeof(filter) == typeof('str') ? @@ -1169,10 +1178,10 @@ module.Types = { // pass options and cache down. // see cache setup below... || (diff(a, b) == null) } - // cache... //cache = this.__cache = cache || this.__cache || new Map() cache = cache || new Map() + // cached diff... var diff = cache.diff = cache.diff || function(a, b){ var l2 = cache.get(a) || new Map() var d = l2.get(b) || that.diff(a, b, options, cache) diff --git a/format.js b/format.js index 3058727..33f4e5e 100644 --- a/format.js +++ b/format.js @@ -20,9 +20,14 @@ var { /*********************************************************************/ -// XXX need better mismatch checking -- ideally stating the exact spot -// where we did not match... // +// XXX need better mismatch checking -- ideally stating the exact spot +// where we did not match and the path of fails it created... +// XXX idea: would be nice to be able to use patterns to extract values +// from structures (parsing)... +// +//--------------------------------------------------------------------- +// Flat diff... var VALUE = module.VALUE = OR( @@ -31,39 +36,108 @@ module.VALUE = OR( ANY) +var SIDE_VALUES = +module.SIDE_VALUES = OR( + // A and B... + AND( + AT('A', VALUE), + AT('B', VALUE)), + // only A... + AT('A', VALUE), + // only B... + AT('B', VALUE)) + var CHANGE = module.CHANGE = AND( AT('path', ARRAY), // XXX optional... // ...see DIFF_OBJECT's options for description... AT('type', OR(STRING, undefined)), - OR( - // A ans B... - AND( - AT('A', VALUE), - AT('B', VALUE)), - // only A... - AT('A', VALUE), - // only B... - AT('B', VALUE))) + SIDE_VALUES) -var DIFF_FORMAT_FLAT = -module.DIFF_FORMAT_FLAT = ARRAY(CHANGE) +var DIFF_FLAT = +module.DIFF_FLAT = OR( + ARRAY(CHANGE), + null) +//--------------------------------------------------------------------- +// Tree diff... + +var BASIC_CHANGE = +module.BASIC_CHANGE = AND( + AT('type', 'Basic'), + SIDE_VALUES) + + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +var OBJECT_ITEM = +module.OBJECT_ITEM = OR( + [STRING, DIFF_TREE], + [STRING, STRING, DIFF_TREE]) + +var OBJECT_CHANGE = +module.OBJECT_CHANGE = AND( + AT('type', 'Object'), + AT('items', ARRAY(OBJECT_ITEM)), + // XXX + AT('item_order', undefined)) + + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +var ARRAY_ITEM = +module.ARRAY_ITEM = OR( + [ANY, ANY, DIFF_TREE], + [[ANY, NUMBER], [ANY, NUMBER], DIFF_TREE]) + +var ARRAY_ITEMS = +module.ARRAY_ITEMS = AND( + AT('length', OR( + [NUMBER, NUMBER], + undefined)), + AT('items', ARRAY( + OR( + ARRAY_ITEM, + OBJECT_ITEM))), + // XXX + AT('item_order', undefined)) + +var ARRAY_CHANGE = +module.ARRAY_CHANGE = AND( + AT('type', 'Array'), + ARRAY_ITEMS) + + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +var TEXT_CHANGE = +module.TEXT_CHANGE = AND( + AT('type', 'Text'), + ARRAY_ITEMS) + // XXX it makes sense to make this a recursive pattern... // ...need to check if we stop on a recursive pattern... -var DIFF_FORMAT_TREE = -module.DIFF_FORMAT_TREE = ANY +// XXX TEST!!! +var DIFF_TREE = +module.DIFF_TREE = OR( + BASIC_CHANGE, + OBJECT_CHANGE, + ARRAY_CHANGE, + TEXT_CHANGE, + null) +//--------------------------------------------------------------------- +// Diff... + var DIFF_OBJECT = module.DIFF_OBJECT = AND( + // format metadata... AT('format', diff.FORMAT_NAME), //AT('version', STRING(/\d+\.\d+\.\d+/)), AT('version', diff.FORMAT_VERSION), + // instance metadata... AT('options', AND( AT('tree_diff', OR(BOOL, null)), AT('keep_none', OR(BOOL, null)), @@ -74,18 +148,20 @@ module.DIFF_OBJECT = AND( AT('no_length', OR(BOOL, null)), AT('cmp', OR(FUNCTION, null)) )), AT('placeholders', AND( - // XXX would be nice to store these and to use them to test later... + // XXX would be nice to store these and to use them to test + // deeper stuff (i.e. VALUE)... AT('NONE', ANY), AT('EMPTY', ANY))), - AT('timestamp', NUMBER), + + // diff... OR( AND( AT('structure', 'flat'), - AT('diff', DIFF_FORMAT_FLAT)), + AT('diff', DIFF_FLAT)), AND( AT('structure', 'tree'), - AT('diff', DIFF_FORMAT_TREE))) ) + AT('diff', DIFF_TREE))) )