mirror of
https://github.com/flynx/diff.js.git
synced 2025-10-29 02:50:10 +00:00
added pattern variables + doc...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
4ebe5f96d3
commit
257322badc
42
README.md
42
README.md
@ -24,6 +24,7 @@
|
|||||||
- [String patterns](#string-patterns)
|
- [String patterns](#string-patterns)
|
||||||
- [Number patterns](#number-patterns)
|
- [Number patterns](#number-patterns)
|
||||||
- [Array patterns](#array-patterns)
|
- [Array patterns](#array-patterns)
|
||||||
|
- [Pattern variables](#pattern-variables)
|
||||||
- [Patterns (EXPERIMENTAL)](#patterns-experimental)
|
- [Patterns (EXPERIMENTAL)](#patterns-experimental)
|
||||||
- [JSON compatibility](#json-compatibility)
|
- [JSON compatibility](#json-compatibility)
|
||||||
- [Extending Diff](#extending-diff)
|
- [Extending Diff](#extending-diff)
|
||||||
@ -458,16 +459,51 @@ Match if `pattern` matches each array item.
|
|||||||
A combination of the above where `x`, `y` and `..` may be any of *length*, *functions* or *patterns*.
|
A combination of the above where `x`, `y` and `..` may be any of *length*, *functions* or *patterns*.
|
||||||
This is a shorthand for: `AND(ARRAY(x), ARRAY(y), ..)`
|
This is a shorthand for: `AND(ARRAY(x), ARRAY(y), ..)`
|
||||||
|
|
||||||
XXX examples...
|
|
||||||
|
### Pattern variables
|
||||||
|
|
||||||
|
Patterns support variables, the namespae/context is persistent per diff / compare call.
|
||||||
|
|
||||||
|
`VAR(name, pattern)`
|
||||||
|
`VAR(name)`
|
||||||
|
A `VAR` is uniquely identified by name.
|
||||||
|
This works in stages:
|
||||||
|
1. Matches `pattern` until *first successful match*,
|
||||||
|
2. On first successful match the matched object is *cached*,
|
||||||
|
3. Matches the *cached* object on all subsequent matches.
|
||||||
|
|
||||||
|
If no `pattern` is given `ANY` is assumed.
|
||||||
|
|
||||||
|
Note that if the *cached* object is not a pattern it will not be matched structurally, i.e. first `===` and then `==` are used instead of `cmp(..)`.
|
||||||
|
|
||||||
|
`LIKE(name, pattern)`
|
||||||
|
`LIKE(name)`
|
||||||
|
This is the same as `VAR(..)` bud does a structural match (i.e. via `cmp(..)`).
|
||||||
|
|
||||||
|
Note that `VAR(..)` and `LIKE(..)` use the same namespace and can be used interchangeably depending on the type of matching desired.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
```javascript
|
||||||
|
var P = [VAR('x', ANY), VAR('x'), LIKE('x')]
|
||||||
|
|
||||||
|
// this will fail because {} !== {}
|
||||||
|
cmp(P, [{}, {}, {}]) // -> false
|
||||||
|
|
||||||
|
var o = {}
|
||||||
|
// this cuccessds because o === o and cmp(o, {}) is true...
|
||||||
|
cmp(P, [o, o, {}]) // -> true
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Patterns (EXPERIMENTAL)
|
## Patterns (EXPERIMENTAL)
|
||||||
|
|
||||||
`IN(A)`
|
`IN(A)`
|
||||||
|
Matches a *container* if it contains `A`.
|
||||||
|
|
||||||
`AT(A, K)`
|
`AT(K, A)`
|
||||||
|
Matches a *container* if it contains `A` *at* index/key `K`
|
||||||
|
|
||||||
`OF(A, N)`
|
~~`OF(A, N)`~~
|
||||||
|
|
||||||
|
|
||||||
## JSON compatibility
|
## JSON compatibility
|
||||||
|
|||||||
103
diff.js
103
diff.js
@ -58,7 +58,7 @@ var MIN_TEXT_LENGTH = 100
|
|||||||
// NAMED(<name>[, pattern])
|
// NAMED(<name>[, pattern])
|
||||||
//
|
//
|
||||||
// this would also require a means to pass the context to
|
// this would also require a means to pass the context to
|
||||||
// nested patterns...
|
// nested patterns and to access it...
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
@ -286,18 +286,25 @@ var LogicTypeClassPrototype = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var LogicTypePrototype = {
|
var LogicTypePrototype = {
|
||||||
__cmp__: function(obj, cmp){
|
__context__: null,
|
||||||
|
context: function(){
|
||||||
|
return (this.__context__ = this.__context__ || {}) },
|
||||||
|
|
||||||
|
__cmp__: function(obj, cmp, context){
|
||||||
return false },
|
return false },
|
||||||
// XXX need to track loops...
|
// XXX need to track loops...
|
||||||
cmp: function(obj, cmp, cache){
|
cmp: function(obj, cmp, context){
|
||||||
cmp = cmp || function(a, b){
|
cmp = cmp || function(a, b){
|
||||||
return a === b
|
return a === b
|
||||||
|| a == b
|
|| a == b
|
||||||
|| (a.__cmp__ && a.__cmp__(b, cmp, cache))
|
|| (a.__cmp__ && a.__cmp__(b, cmp, context))
|
||||||
|| (b.__cmp__ && b.__cmp__(a, cmp, cache)) }
|
|| (b.__cmp__ && b.__cmp__(a, cmp, context)) }
|
||||||
|
|
||||||
|
// create a pattern context...
|
||||||
|
var context = context || this.context()
|
||||||
|
|
||||||
// cache...
|
// cache...
|
||||||
cache = cache || new Map()
|
var cache = context.cache = context.cache || new Map()
|
||||||
var c = cache.get(this) || new Map()
|
var c = cache.get(this) || new Map()
|
||||||
cache.has(c)
|
cache.has(c)
|
||||||
|| cache.set(this, c)
|
|| cache.set(this, c)
|
||||||
@ -305,10 +312,10 @@ var LogicTypePrototype = {
|
|||||||
return c.get(obj)
|
return c.get(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
var res = this.__cmp__(obj, cmp, cache)
|
var res = this.__cmp__(obj, cmp, context)
|
||||||
|| (obj != null
|
|| (obj != null
|
||||||
&& obj.__cmp__
|
&& obj.__cmp__
|
||||||
&& obj.__cmp__(this, cmp, cache))
|
&& obj.__cmp__(this, cmp, context))
|
||||||
c.set(obj, !!res)
|
c.set(obj, !!res)
|
||||||
|
|
||||||
return !!res
|
return !!res
|
||||||
@ -460,7 +467,7 @@ module.NUMBER =
|
|||||||
var ARRAY =
|
var ARRAY =
|
||||||
module.ARRAY =
|
module.ARRAY =
|
||||||
makeCIPattern('ARRAY',
|
makeCIPattern('ARRAY',
|
||||||
function(obj, cmp){
|
function(obj, cmp, context){
|
||||||
return obj === ARRAY
|
return obj === ARRAY
|
||||||
//|| (obj instanceof Array && this.value.length == 0)
|
//|| (obj instanceof Array && this.value.length == 0)
|
||||||
|| (obj instanceof Array
|
|| (obj instanceof Array
|
||||||
@ -485,8 +492,8 @@ module.ARRAY =
|
|||||||
var NOT =
|
var NOT =
|
||||||
module.NOT =
|
module.NOT =
|
||||||
object.makeConstructor('NOT', Object.assign(new LogicType(), {
|
object.makeConstructor('NOT', Object.assign(new LogicType(), {
|
||||||
__cmp__: function(obj, cmp, cache){
|
__cmp__: function(obj, cmp, context){
|
||||||
return !cmp(this.value, obj, cache) },
|
return !cmp(this.value, obj, context) },
|
||||||
__init__: function(value){
|
__init__: function(value){
|
||||||
this.value = value
|
this.value = value
|
||||||
},
|
},
|
||||||
@ -497,9 +504,9 @@ object.makeConstructor('NOT', Object.assign(new LogicType(), {
|
|||||||
var OR =
|
var OR =
|
||||||
module.OR =
|
module.OR =
|
||||||
object.makeConstructor('OR', Object.assign(new LogicType(), {
|
object.makeConstructor('OR', Object.assign(new LogicType(), {
|
||||||
__cmp__: function(obj, cmp, cache){
|
__cmp__: function(obj, cmp, context){
|
||||||
for(var m of this.members){
|
for(var m of this.members){
|
||||||
if(cmp(m, obj, cache)){
|
if(cmp(m, obj, context)){
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -515,9 +522,9 @@ object.makeConstructor('OR', Object.assign(new LogicType(), {
|
|||||||
var AND =
|
var AND =
|
||||||
module.AND =
|
module.AND =
|
||||||
object.makeConstructor('AND', Object.assign(new LogicType(), {
|
object.makeConstructor('AND', Object.assign(new LogicType(), {
|
||||||
__cmp__: function(obj, cmp, cache){
|
__cmp__: function(obj, cmp, context){
|
||||||
for(var m of this.members){
|
for(var m of this.members){
|
||||||
if(!cmp(m, obj, cache)){
|
if(!cmp(m, obj, context)){
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -538,16 +545,19 @@ object.makeConstructor('AND', Object.assign(new LogicType(), {
|
|||||||
var IN =
|
var IN =
|
||||||
module.IN =
|
module.IN =
|
||||||
object.makeConstructor('IN', Object.assign(new LogicType(), {
|
object.makeConstructor('IN', Object.assign(new LogicType(), {
|
||||||
// XXX add support for other stuff like sets and maps...
|
// XXX make this a break-on-match and not a go-through-the-whole-thing
|
||||||
__cmp__: function(obj, cmp, cache){
|
// XXX should we check inherited stuff???
|
||||||
|
__cmp__: function(obj, cmp, context){
|
||||||
var p = this.value
|
var p = this.value
|
||||||
// XXX make this a break-on-match and not a go-through-the-whole-thing
|
return (obj instanceof Array ? obj
|
||||||
return typeof(obj) == typeof({})
|
: obj instanceof Map || obj instanceof Set ? [...obj.values()]
|
||||||
&& (p in obj
|
: [])
|
||||||
|| obj.reduce(function(res, e){
|
.concat(Object.values(obj))
|
||||||
|
.reduce(function(res, e){
|
||||||
return res === false ?
|
return res === false ?
|
||||||
cmp(p, e, cache)
|
cmp(p, e, context)
|
||||||
: res }), false) },
|
: res
|
||||||
|
}, false) },
|
||||||
__init__: function(value){
|
__init__: function(value){
|
||||||
this.value = value
|
this.value = value
|
||||||
},
|
},
|
||||||
@ -567,8 +577,8 @@ object.makeConstructor('IN', Object.assign(new LogicType(), {
|
|||||||
var AT =
|
var AT =
|
||||||
module.AT =
|
module.AT =
|
||||||
object.makeConstructor('AT', Object.assign(new LogicType(), {
|
object.makeConstructor('AT', Object.assign(new LogicType(), {
|
||||||
__cmp__: function(obj, cmp, cache){
|
__cmp__: function(obj, cmp, context){
|
||||||
if(cmp(obj != null ? obj[this.key] : null, this.value, cache)){
|
if(cmp(obj != null ? obj[this.key] : null, this.value, context)){
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@ -596,6 +606,49 @@ object.makeConstructor('OF', Object.assign(new LogicType(), {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
var VAR =
|
||||||
|
module.VAR =
|
||||||
|
object.makeConstructor('VAR', Object.assign(new LogicType(), {
|
||||||
|
__cmp__: function(obj, cmp, context){
|
||||||
|
var context = context || this.context()
|
||||||
|
var ns = context.ns = context.ns || {}
|
||||||
|
var pattern = ns[this.name] =
|
||||||
|
this.name in ns ?
|
||||||
|
ns[this.name]
|
||||||
|
: this.pattern
|
||||||
|
|
||||||
|
if(cmp(pattern, obj)){
|
||||||
|
ns[this.name] = obj
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
__init__: function(name, pattern){
|
||||||
|
this.name = name
|
||||||
|
this.pattern = arguments.length < 2 ? ANY : pattern
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
// this is like VAR(..) but will do a structural compare...
|
||||||
|
var LIKE =
|
||||||
|
module.LIKE =
|
||||||
|
object.makeConstructor('LIKE', Object.assign(new VAR(), {
|
||||||
|
// XXX reuse...
|
||||||
|
__cmp__: function(obj, cmp, context){
|
||||||
|
var context = context || this.context()
|
||||||
|
|
||||||
|
return VAR.prototype.__cmp__.call(this, obj, cmp, context)
|
||||||
|
|| Diff.cmp(
|
||||||
|
this.name in context.ns ?
|
||||||
|
context.ns[this.name]
|
||||||
|
: this.pattern,
|
||||||
|
obj) },
|
||||||
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------
|
//---------------------------------------------------------------------
|
||||||
// Placeholders...
|
// Placeholders...
|
||||||
|
|||||||
11
format.js
11
format.js
@ -16,6 +16,7 @@ var {
|
|||||||
BOOL, NUMBER, STRING, ARRAY, FUNCTION,
|
BOOL, NUMBER, STRING, ARRAY, FUNCTION,
|
||||||
OR, AND, NOT,
|
OR, AND, NOT,
|
||||||
AT, OF, IN,
|
AT, OF, IN,
|
||||||
|
VAR, LIKE,
|
||||||
|
|
||||||
EMPTY, NONE,
|
EMPTY, NONE,
|
||||||
} = diff
|
} = diff
|
||||||
@ -35,8 +36,8 @@ var {
|
|||||||
var VALUE =
|
var VALUE =
|
||||||
module.VALUE = OR(
|
module.VALUE = OR(
|
||||||
// XXX use these taken from .placeholders...
|
// XXX use these taken from .placeholders...
|
||||||
EMPTY,
|
OR(EMPTY, LIKE('EMPTY')),
|
||||||
NONE,
|
OR(NONE, LIKE('NONE')),
|
||||||
ANY)
|
ANY)
|
||||||
|
|
||||||
|
|
||||||
@ -154,8 +155,10 @@ module.DIFF_OBJECT = AND(
|
|||||||
AT('placeholders', AND(
|
AT('placeholders', AND(
|
||||||
// XXX would be nice to store these and to use them to test
|
// XXX would be nice to store these and to use them to test
|
||||||
// deeper stuff (i.e. VALUE)...
|
// deeper stuff (i.e. VALUE)...
|
||||||
AT('NONE', ANY),
|
AT('NONE',
|
||||||
AT('EMPTY', ANY))),
|
VAR('NONE', ANY)),
|
||||||
|
AT('EMPTY',
|
||||||
|
VAR('EMPTY', ANY)))),
|
||||||
AT('timestamp', NUMBER),
|
AT('timestamp', NUMBER),
|
||||||
|
|
||||||
// diff...
|
// diff...
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user