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)
- [Number patterns](#number-patterns)
- [Array patterns](#array-patterns)
- [Pattern variables](#pattern-variables)
- [Patterns (EXPERIMENTAL)](#patterns-experimental)
- [JSON compatibility](#json-compatibility)
- [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*.
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)
`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

103
diff.js
View File

@ -58,7 +58,7 @@ var MIN_TEXT_LENGTH = 100
// NAMED(<name>[, pattern])
//
// 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 = {
__cmp__: function(obj, cmp){
__context__: null,
context: function(){
return (this.__context__ = this.__context__ || {}) },
__cmp__: function(obj, cmp, context){
return false },
// XXX need to track loops...
cmp: function(obj, cmp, cache){
cmp: function(obj, cmp, context){
cmp = cmp || function(a, b){
return a === b
|| a == b
|| (a.__cmp__ && a.__cmp__(b, cmp, cache))
|| (b.__cmp__ && b.__cmp__(a, cmp, cache)) }
|| (a.__cmp__ && a.__cmp__(b, cmp, context))
|| (b.__cmp__ && b.__cmp__(a, cmp, context)) }
// create a pattern context...
var context = context || this.context()
// cache...
cache = cache || new Map()
var cache = context.cache = context.cache || new Map()
var c = cache.get(this) || new Map()
cache.has(c)
|| cache.set(this, c)
@ -305,10 +312,10 @@ var LogicTypePrototype = {
return c.get(obj)
}
var res = this.__cmp__(obj, cmp, cache)
var res = this.__cmp__(obj, cmp, context)
|| (obj != null
&& obj.__cmp__
&& obj.__cmp__(this, cmp, cache))
&& obj.__cmp__(this, cmp, context))
c.set(obj, !!res)
return !!res
@ -460,7 +467,7 @@ module.NUMBER =
var ARRAY =
module.ARRAY =
makeCIPattern('ARRAY',
function(obj, cmp){
function(obj, cmp, context){
return obj === ARRAY
//|| (obj instanceof Array && this.value.length == 0)
|| (obj instanceof Array
@ -485,8 +492,8 @@ module.ARRAY =
var NOT =
module.NOT =
object.makeConstructor('NOT', Object.assign(new LogicType(), {
__cmp__: function(obj, cmp, cache){
return !cmp(this.value, obj, cache) },
__cmp__: function(obj, cmp, context){
return !cmp(this.value, obj, context) },
__init__: function(value){
this.value = value
},
@ -497,9 +504,9 @@ object.makeConstructor('NOT', Object.assign(new LogicType(), {
var OR =
module.OR =
object.makeConstructor('OR', Object.assign(new LogicType(), {
__cmp__: function(obj, cmp, cache){
__cmp__: function(obj, cmp, context){
for(var m of this.members){
if(cmp(m, obj, cache)){
if(cmp(m, obj, context)){
return true
}
}
@ -515,9 +522,9 @@ object.makeConstructor('OR', Object.assign(new LogicType(), {
var AND =
module.AND =
object.makeConstructor('AND', Object.assign(new LogicType(), {
__cmp__: function(obj, cmp, cache){
__cmp__: function(obj, cmp, context){
for(var m of this.members){
if(!cmp(m, obj, cache)){
if(!cmp(m, obj, context)){
return false
}
}
@ -538,16 +545,19 @@ object.makeConstructor('AND', Object.assign(new LogicType(), {
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, cache){
// XXX make this a break-on-match and not a go-through-the-whole-thing
// XXX should we check inherited stuff???
__cmp__: function(obj, cmp, context){
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 (obj instanceof Array ? obj
: obj instanceof Map || obj instanceof Set ? [...obj.values()]
: [])
.concat(Object.values(obj))
.reduce(function(res, e){
return res === false ?
cmp(p, e, cache)
: res }), false) },
cmp(p, e, context)
: res
}, false) },
__init__: function(value){
this.value = value
},
@ -567,8 +577,8 @@ object.makeConstructor('IN', Object.assign(new LogicType(), {
var AT =
module.AT =
object.makeConstructor('AT', Object.assign(new LogicType(), {
__cmp__: function(obj, cmp, cache){
if(cmp(obj != null ? obj[this.key] : null, this.value, cache)){
__cmp__: function(obj, cmp, context){
if(cmp(obj != null ? obj[this.key] : null, this.value, context)){
return true
}
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...

View File

@ -16,6 +16,7 @@ var {
BOOL, NUMBER, STRING, ARRAY, FUNCTION,
OR, AND, NOT,
AT, OF, IN,
VAR, LIKE,
EMPTY, NONE,
} = diff
@ -35,8 +36,8 @@ var {
var VALUE =
module.VALUE = OR(
// XXX use these taken from .placeholders...
EMPTY,
NONE,
OR(EMPTY, LIKE('EMPTY')),
OR(NONE, LIKE('NONE')),
ANY)
@ -154,8 +155,10 @@ module.DIFF_OBJECT = AND(
AT('placeholders', AND(
// 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('NONE',
VAR('NONE', ANY)),
AT('EMPTY',
VAR('EMPTY', ANY)))),
AT('timestamp', NUMBER),
// diff...