added pattern variables + doc...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2018-09-04 22:07:03 +03:00
parent 4ebe5f96d3
commit 257322badc
3 changed files with 124 additions and 32 deletions

View File

@ -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
View File

@ -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...

View File

@ -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...