reqorking and experimenting...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2021-06-07 00:42:14 +03:00
parent e02e0fecf7
commit 5e47d4d3a5

304
diff2.js
View File

@ -11,10 +11,28 @@ var types = require('ig-types')
/*********************************************************************/
//
// XXX thinks this needs to do:
// - walk object tree - DONE
// - generate a spec - DONE
// - serializable
// - support props
// - build object via spec
// - update object via spec
// - subtract specs (diff)
// - full
// - relaxed -- ignore item order
//
//
/*********************************************************************/ /*********************************************************************/
var CONTENT_ATTR = // XXX need a way to uniquely serilaize this to a string path...
module.CONTENT_ATTR = '[CONTENT$]' // ...or some other way to use it in a convinient manner...
var CONTENT =
module.CONTENT =
//Symbol.CONTENT =
Symbol('CONTENT')
// XXX need to deal with functions... // XXX need to deal with functions...
@ -34,53 +52,75 @@ module.HANDLERS = {
return true }, return true },
handle: function(obj){ handle: function(obj){
... ...
return [key, value, next] }, return [key, value, next] }, },
},
//*/ //*/
// null...
//
null: { null: {
final: true, final: true,
match: function(obj){ match: function(obj){
return obj === null }, return obj === null },
handle: function(obj){ handle: function(obj){
return [[], obj] }, return [[], obj] }, },
},
value: { // Functions...
//
// XXX STUB...
func: {
match: function(obj){ match: function(obj){
return typeof(obj) != 'object' }, return typeof(obj) == 'function' },
handle: function(obj){ handle: function(obj){
return [[], obj] }, return [[], {
}, type: 'function',
gen: obj.constructor.prototype === obj.__proto__ ? 1 : 2,
// XXX
source: obj,
}] }, },
// Non-Objects...
//
// NOTE: this will include undefined and NaN...
value: {
final: true,
match: function(obj){
return typeof(obj) != 'object'
&& typeof(obj) != 'function' },
handle: function(obj){
return [[], obj] }, },
// Base objects...
//
object: { object: {
match: function(obj){ match: function(obj){
return typeof(obj) == 'object' }, return typeof(obj) == 'object' },
handle: function(obj){ handle: function(obj){
return [[], { return [[], {
// XXX revise... // XXX need to check if a constructor is built-in...
type: obj.constructor.name, type: obj.constructor.name,
// XXX
}] },
},
// XXX need to optionally treat special attributes... // Object generations:
// .__proto__ // 1 - directly constructed objects
specialKeys: { // 2 - objects at least one level deeper than gen 1
//match: function(obj){ gen: obj.constructor.prototype === obj.__proto__ ? 1 : 2,
// return typeof(obj) == 'object' },
// XXX
source: obj,
}] }, },
// special keys...
proto: {
match: function(obj){
return typeof(obj) == 'object'
&& obj.constructor.prototype !== obj.__proto__ },
handle: function(obj){ handle: function(obj){
// XXX return [[ [['__proto__'], obj.__proto__], ]] }, },
}, // XXX any other special keys???
}, // - non-iterable?
// XXX these still intersect with attrs...
// ...need a destinct way to encapsulate these to destinguish // Entries / Non-attribute (encapsulated) content...
// the data from attrs... //
// this is simple when nesting, i.e. just add the entries to
// .entries, attributes to .attrs and done, but in a flat format
// this is not obvious -- i.e. how do we destinguish attr 'x'
// from map key 'x'???
setEntries: { setEntries: {
match: function(obj){ match: function(obj){
return obj instanceof Set }, return obj instanceof Set },
@ -88,37 +128,25 @@ module.HANDLERS = {
handle: function(obj){ handle: function(obj){
return [ obj.values() return [ obj.values()
.map(function(v, i){ .map(function(v, i){
return [[i], v] }) return [[module.CONTENT, i], v] })
.toArray() ] }, .toArray() ] }, },
},
mapEntries: { mapEntries: {
// XXX should this be more generic and just check for .entries(..) ???
match: function(obj){ match: function(obj){
return obj instanceof Map }, return obj instanceof Map },
handle: function(obj, path, options){ handle: function(obj, path, options){
// NOTE: we store content in a special attribute...
var pattern = options.contentAttr || module.CONTENT_ATTR
var i = 0
do{
var attr = pattern
.replace('$', i == 0 ? '' : i)
i++
} while(attr in obj)
// XXX store the attr in parent spec...
// ...how can we get the parent spec???
// XXX
return [ obj.entries() return [ obj.entries()
.map(function([k, v], i){ .map(function([k, v], i){
return [ return [
// XXX not sure how to format these... [[module.CONTENT, i +'@key'], k],
[[attr, i +':key'], k], [[module.CONTENT, i], v],
[[attr, i], v],
] }) ] })
.flat() .flat()
.toArray() ] }, .toArray() ] }, },
},
// Keys / Attributes...
//
// NOTE: this includes array items...
//
// XXX do we need to treat array keys as a special case??? // XXX do we need to treat array keys as a special case???
// ...the best approach could be to simply: // ...the best approach could be to simply:
// - prioretize handlers -- already done // - prioretize handlers -- already done
@ -126,36 +154,73 @@ module.HANDLERS = {
// ...this could be done on the root handler level... // ...this could be done on the root handler level...
// XXX need to optionally handle props... // XXX need to optionally handle props...
keys: { keys: {
match: function(obj){ //match: 'object',
return typeof(obj) == 'object' }, //match: ['object', 'func'],
match: function(obj, handlers){
return handlers.object.match(obj)
|| handlers.func.match(obj) },
handle: function(obj){ handle: function(obj){
return [ Object.entries(obj) return [ Object.entries(obj)
.map(function([k, v]){ .map(function([k, v]){
return [[k], v] }), ] }, return [[k], v] }), ] }, },
}, /* XXX
} props: {
//match: 'object',
//match: ['object', 'func'],
match: function(obj, handlers){
return handlers.object.match(obj)
|| handlers.func.match(obj) },
handle: function(obj){
return [key, value, next] }, },
//*/
var getType =
module.getType = // Testing...
function*(obj){ //
// XXX // XXX alias loop...
//alias_loop: { match: 'alias_loop' },
//alias_loop_a: { match: 'alias_loop_b' },
//alias_loop_b: { match: 'alias_loop_a' },
// XXX orphaned alias...
//alias_orphan: { match: 'orphan' },
//false: { match: false },
} }
// XXX use STOP... // XXX use STOP...
// XXX might be good to cache output via some citeria (type?)...
// ...this criteria needs to be consistent with how .match(..) works...
// XXX does .match(..) need options???
// XXX do we warn of orphans???
var getHandlers = var getHandlers =
module.getHandlers = module.getHandlers =
function(obj, handlers=module.HANDLERS){ function(obj, handlers=module.HANDLERS){
return [...Object.entries(handlers) return [...Object.entries(handlers)
.iter() .iter()
.filter(function([k, v]){ .filter(function([k, v]){
if(v.final var stop = !!v.final
// expand aliases...
var seen = new Set()
while(v && typeof(v.match) == 'string'){
var n = v.match
if(seen.has(n)){
throw new Error('.match(..): alias loop detected:\n\t'
+ [...seen, n].join('\n \t -> ')) }
seen.add(n)
v = handlers[n] }
// orphan or falsy .match...
if(!v){
return false }
// handle .final/final...
if(stop
&& v.match && v.match
&& v.match(obj)){ && v.match(obj, handlers)){
throw types.STOP(true) } throw types.STOP(true) }
// normal match...
return v.match return v.match
&& v.match(obj) }) && v.match(obj, handlers) })
.map(function([k, v]){ .map(function([k, v]){
return v })] } return v })] }
@ -172,18 +237,14 @@ function(obj, handlers=module.HANDLERS){
// //
// XXX need a way to index the path... // XXX need a way to index the path...
// ...and to filter paths by pattern... // ...and to filter paths by pattern...
// XXX need to generate object UIDs for use in paths etc... // XXX might be a good idea to generate "structural hashes" for objects...
// XXX might be a good idea to include obj in the output to negate the
// need to get it via the path in client code...
var handle = var handle =
module.handle = module.handle =
function*(obj, path=[], options={}){ function*(obj, path=[], options={}){
// handle recursive structures... // handle recursive structures...
// XXX would be nice to index paths to make them unique...
var seen = options.seen = var seen = options.seen =
options.seen || new Map() options.seen || new Map()
if(seen.has(obj)){ if(seen.has(obj)){
// XXX revise format...
yield [path, ['LINK', seen.get(obj)]] yield [path, ['LINK', seen.get(obj)]]
return } return }
typeof(obj) == 'object' typeof(obj) == 'object'
@ -192,12 +253,7 @@ function*(obj, path=[], options={}){
// get compatible handler list... // get compatible handler list...
var cache = options.cache = var cache = options.cache =
options.cache || new Map() options.cache || new Map()
var type = getType(obj) var handlers = module.getHandlers(obj, options.handlers || module.HANDLERS)
var handlers =
(type && cache.get(type))
|| module.getHandlers(obj, options.handlers || module.HANDLERS)
type
&& cache.set(type, handlers)
// XXX might be a good idea to move this up (or into options) so as // XXX might be a good idea to move this up (or into options) so as
// not to define this on each call... // not to define this on each call...
@ -222,6 +278,8 @@ function*(obj, path=[], options={}){
// apply the handlers... // apply the handlers...
yield* handlers yield* handlers
.iter() .iter()
.filter(function(handler){
return !!handler.handle })
.map(function*(handler){ .map(function*(handler){
yield* handler.handle instanceof types.Generator ? yield* handler.handle instanceof types.Generator ?
handler.handle(obj, path, options) handler.handle(obj, path, options)
@ -230,56 +288,60 @@ function*(obj, path=[], options={}){
// XXX need a better way to serialize the path... // XXX need to figure out a way to avoid clashes with module.CONTENT in
var serializePathElem = function(p){ // path with actual attribute keys...
// ways to do this:
// - serialize CONTENT in a cleaver way
// - add a different path separator to indicate content and quote
// it in strings -- ':'???
var serializePathElem = function(p, i, l){
return typeof(p) == 'object' ? return typeof(p) == 'object' ?
JSON.stringify(p) JSON.stringify(p)
// quote special chars...
: typeof(p) == 'string' ?
p.replace(/([\/:])/g, '\\$1')
: p } : p }
var serializePath = function(p){ var serializePath = function(p){
//return '/'+ p.map(JSON.stringify).join('/') } return '/'+ p
return '/'+ p.map(serializePathElem).join('/') } .map(serializePathElem)
.reduce(function(res, e){
e = e === module.CONTENT ?
res.pop() + ':CONTENT'
: e
res.push(e)
return res }, [])
.join('/') }
/*/ XXX might also be a good idea to serialize the path into an
// arbitrary length as we always have exactly one value, e.g.:
// [ '/path/to/map', 'CONTENT', 'path/in/content', 123]
var serializePathElem = function(p, i, l){
return typeof(p) == 'object' ?
JSON.stringify(p)
// quote special chars...
: typeof(p) == 'string' ?
p.replace(/([\/])/g, '\\$1')
: p }
var serializePath = function(p){
return p
.map(serializePathElem)
.reduce(function(res, e){
e === module.CONTENT ?
res.splice(res.length, 0, 'CONTENT', '')
: (res[res.length-1] += '/'+ e)
return res }, ['']) }
//*/
var serializePaths = var serializePaths =
module.serializePaths = module.serializePaths =
types.generator.iter types.generator.iter
.map(function([p, v]){ .map(function([p, v]){
return ( return v instanceof Array && v[0] == 'LINK' ?
// XXX revise... // link...
v instanceof Array && v[0] == 'LINK' ?
[serializePath(p), [serializePath(p),
'LINK', serializePath(v[1])] 'LINK', serializePath(v[1])]
: [serializePath(p), v] ) }) : [serializePath(p), v] })
// XXX make this more generic...
// ...or move these to the HANDLERS as .build(..)...
var construct = function(spec){
return typeof(spec) != 'object' ?
spec
: spec.type == 'Object' ?
{}
: spec.type == 'Array' ?
[]
: spec.type == 'Set' ?
new Set()
: spec.type == 'Map' ?
new Map()
: undefined }
var has = function(root, path){
}
var get = function(root, path){
}
var set = function(root, path, value){
}
var build =
types.generator.iter
.reduce(function(root, [path, spec]){
return path.length == 0 ?
construct(spec)
: set(root, path, value)
}, undefined)
//--------------------------------------------------------------------- //---------------------------------------------------------------------
@ -292,6 +354,7 @@ var o = {
// XXX add a mode to unify these... // XXX add a mode to unify these...
'null': null, 'null': null,
'undefined': undefined, 'undefined': undefined,
'NaN': NaN,
empty_array: [], empty_array: [],
@ -311,6 +374,23 @@ var o = {
object: { object: {
x: {}, x: {},
}, },
object_gen2: Object.assign(
Object.create({
x: 'parent',
z: 'shadowed',
}),
{
y: 'local',
z: 'shadowing',
}),
// XXX
func: function(){},
func_with_attrs: Object.assign(
function(){},
{
x: 333,
}),
array_with_attrs: Object.assign( array_with_attrs: Object.assign(
[1, 2, 3], [1, 2, 3],
@ -319,7 +399,9 @@ var o = {
b: 'some other value', b: 'some other value',
// will overwrite 2... // will overwrite 2...
1: 333, 1: 333,
}) }),
'special/character\\in:key': [],
} }
// clone... // clone...