now support recursive deffinitions...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2018-12-18 04:05:34 +03:00
parent 7722b881ff
commit 7784794e45

View File

@ -20,7 +20,8 @@
* - will it be faster? * - will it be faster?
* *
* *
* XXX should we .optimizeTags(tag) on .tag(tag)??? * XXX should we do .optimizeTags(tag) on .tag(tag)???
* ...this might lead to non-trivial behaviour...
* XXX should this serialize recursively down??? * XXX should this serialize recursively down???
* ...it might be a good idea to decide on a serialization * ...it might be a good idea to decide on a serialization
* protocol and use it throughout... * protocol and use it throughout...
@ -224,7 +225,8 @@ var BaseTagsClassPrototype = {
// XXX should we store normalized and non-normalized tags for reference??? // XXX should we store normalized and non-normalized tags for reference???
// ...there are two ways to think of this: // ...there are two ways to think of this:
// 1) both (a-la flickr) -- keep both, use normalized internally // 1) both (a-la flickr) -- keep both, use normalized internally
// 2) only normalized -- simpler but may surprise the user and not be as pretty... // 2) only normalized -- simpler but may surprise the user and
// not be as pretty...
// XXX should we split out the non-basic stuff??? // XXX should we split out the non-basic stuff???
// like: // like:
// .makePathsPersistent() // .makePathsPersistent()
@ -271,28 +273,15 @@ var BaseTagsPrototype = {
// Utils... // Utils...
// //
// XXX should these be proxies or simply refs??? // NOTE: these are not proxies to .constructor so as to make the
// - proxies would make it possible to change stuff in the // instance largely independent of its constructor and thus
// constructor and affect all the instances... // making it possible to use it as a mix-in (copy methods)
// - refs would make things a bit faster but would isolate // and other approaches...
// the instances from the constructor...
splitSet: BaseTagsClassPrototype.splitSet, splitSet: BaseTagsClassPrototype.splitSet,
splitPath: BaseTagsClassPrototype.splitPath, splitPath: BaseTagsClassPrototype.splitPath,
normalize: BaseTagsClassPrototype.normalize, normalize: BaseTagsClassPrototype.normalize,
subTags: BaseTagsClassPrototype.subTags, subTags: BaseTagsClassPrototype.subTags,
parseQuery: BaseTagsClassPrototype.parseQuery, parseQuery: BaseTagsClassPrototype.parseQuery,
/*// proxies to class methods...
splitSet: function(str){
return this.constructor.splitSet.call(this, str) },
splitPath: function(str){
return this.constructor.splitPath.call(this, str) },
normalize: function(...tags){
return this.constructor.normalize.call(this, ...tags) },
subTags: function(...tags){
return this.constructor.subTags.call(this, ...tags) },
parseQuery: function(query){
return this.constructor.parseQuery.call(this, query) },
//*/
// Tag matching and filtering... // Tag matching and filtering...
@ -399,22 +388,36 @@ var BaseTagsPrototype = {
return true return true
} }
// set/definition matching... // Expand definitions...
//
// NOTE: this does not care about infinite recursion...
var expand = function(tags, res){
if(!definitions){
return tags
}
res = (res || new Set()).unite(tags)
tags = tags
.map(function(tag){
return tag in definitions ?
definitions[tag]
: [] })
.flat()
var size = res.size
res = res.unite(tags)
return res.size != size ?
expand(tags, res)
: [...res]
}
// Set matching...
// a matches b iff each element of a exists in b. // a matches b iff each element of a exists in b.
// //
// NOTE: this does not care about order... // NOTE: this does not care about order...
// NOTE: this matches single tags too... // NOTE: this matches single tags too...
var matchSet = function(a, b){ var matchSet = function(a, b){
a = that.splitSet(a) a = that.splitSet(a)
b = that.splitSet(b) b = expand(that.splitSet(b))
// extend b with definitions...
b = b
.concat(!definitions ? [] : (b
.map(function(v){
return v in definitions ?
definitions[v]
: [] })
.flat()))
return a.length <= b.length return a.length <= b.length
// keep only the non-matches -> if at least one exists we fail... // keep only the non-matches -> if at least one exists we fail...
&& a.filter(function(e){ && a.filter(function(e){
@ -640,24 +643,20 @@ var BaseTagsPrototype = {
return this.values().length }, return this.values().length },
// Direct tag/value check... // Direct tag/value check...
// XXX can these be faster??? //
// NOTE: these are not using for loops so as to enable "fast abort",
// not sure who much performance this actually gains though.
// XXX should these take multiple values??? // XXX should these take multiple values???
hasTag: function(tag){ hasTag: function(tag){
for(var t of this.tags()){ for(var t of this.tags()){
if(this.match(tag, t)){ if(this.match(tag, t)){
return true return true } }
} return false },
}
return false
},
has: function(value){ has: function(value){
for(var v of Object.values(this.__index || {})){ for(var v of Object.values(this.__index || {})){
if(v.has(value)){ if(v.has(value)){
return true return true } }
} return false },
}
return false
},
// Tags present in the system... // Tags present in the system...
// //
@ -675,12 +674,13 @@ var BaseTagsPrototype = {
// .tags(value, [tag, ..]) // .tags(value, [tag, ..])
// -> bool // -> bool
// //
// // NOTE: this will return the list of actual tags used and will not
// generate any new values (like .singleTags(..) and friends do).
// NOTE: this includes all the persistent tags as well as all the // NOTE: this includes all the persistent tags as well as all the
// tags actually used. // tags actually used.
// //
// XXX should this return split values / combined with .singleTags(..)??? // XXX should we rename this to .usedTags(..) and .singleTags(..) to
// i.e. 'red:car' -> ['red', 'car'] // .tags(..) ???
tags: function(value, ...tags){ tags: function(value, ...tags){
var that = this var that = this
@ -749,7 +749,7 @@ var BaseTagsPrototype = {
tag: function(tags, value){ tag: function(tags, value){
var that = this var that = this
value = value instanceof Array ? value : [value] value = value instanceof Array ? value : [value]
tags = this.normalize(tags) tags = this.normalize(tags instanceof Array ? tags : [tags])
var index = this.__index = this.__index || {} var index = this.__index = this.__index || {}
tags tags
@ -768,7 +768,7 @@ var BaseTagsPrototype = {
value = value instanceof Array ? value : [value] value = value instanceof Array ? value : [value]
this this
.normalize(tags) .normalize(tags instanceof Array ? tags : [tags])
// resolve/match tags... // resolve/match tags...
.map(function(tag){ .map(function(tag){
return /\*/.test(tag) ? return /\*/.test(tag) ?
@ -935,7 +935,7 @@ var BaseTagsPrototype = {
// //
// XXX need to sanitize tag -- it can not contain regex characters... // XXX need to sanitize tag -- it can not contain regex characters...
// ...should we guard against this??? // ...should we guard against this???
// XXX should both sides of the alias be renamed??? // XXX should both sides of the definition be renamed???
rename: function(tag, to, ...tags){ rename: function(tag, to, ...tags){
var that = this var that = this
@ -1065,13 +1065,24 @@ var BaseTagsPrototype = {
// .define(tag, null) // .define(tag, null)
// -> this // -> this
// //
//
// Nested recursive definitions (i.e. left side is part of the right
// side) are supported, but literal definition recursion are not (i.e.
// left side is literally reachable in the definitionchain):
// a -> a:b:c - nested (recursive) definition, this is fine.
// a -> a - type A literal recursion, this will fail.
// a -> b -> a - type B literal recursion will fail too.
//
// //
// Example: // Example:
// // nested recursive definition...
// ts.define('bird', 'bird:one')
//
// ts.define('birds', 'bird:many') // ts.define('birds', 'bird:many')
// //
// // filter a list...
// ts.match('bird', ['bird', 'birds', 'animal']) // -> ['bird', 'birds'] // ts.match('bird', ['bird', 'birds', 'animal']) // -> ['bird', 'birds']
// //
// XXX revise recursion testing and if it is needed...
define: function(tag, value){ define: function(tag, value){
var definitions = this.definitions = this.definitions || {} var definitions = this.definitions = this.definitions || {}
@ -1088,7 +1099,7 @@ var BaseTagsPrototype = {
|| definitions[this.normalize(tag)] || definitions[this.normalize(tag)]
seen.push(tag) seen.push(tag)
return next != null ? return next != null ?
resolve(next, seen) resolve(next.join(':'), seen)
: seen.length > 1 ? : seen.length > 1 ?
tag tag
: undefined : undefined
@ -1536,6 +1547,7 @@ var BaseTagsPrototype = {
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
var BaseTags = var BaseTags =
module.BaseTags = module.BaseTags =
object.makeConstructor('BaseTags', object.makeConstructor('BaseTags',