rewritten the handler code -- now simpler yet more flexible...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2021-06-12 03:08:04 +03:00
parent 91bc0ce704
commit 5e32c539ba

371
diff2.js
View File

@ -34,6 +34,11 @@ var types = require('ig-types')
// //
/*********************************************************************/ /*********************************************************************/
var STOP =
module.STOP =
types.STOP
var EMPTY = var EMPTY =
module.EMPTY = module.EMPTY =
//Symbol.EMPTY = //Symbol.EMPTY =
@ -48,133 +53,148 @@ module.CONTENT =
Symbol('CONTENT') Symbol('CONTENT')
//---------------------------------------------------------------------
// //
// Format: // Format:
// { // {
// <name>: { // <name>: {
// // optional
// final: <bool>,
//
// match: <name> | <func>,
//
// handle: <name> | <func>, // handle: <name> | <func>,
//
// ...
// }, // },
// } // }
// //
// //
// .handle(obj, res, next, stop)
// -> [path, res]
// -> undefined
//
// res ::= [path] | [path, value]
//
// next([path, value], ..)
// -> true
//
// stop(value)
// stop(path, value)
//
//
//
// NOTE: this is more of a grammar than a set of object handlers, nother // NOTE: this is more of a grammar than a set of object handlers, nother
// way to think of this is as a set of handlrs of aspects of objects // way to think of this is as a set of handlrs of aspects of objects
// and not full objects... // and not full objects...
// XXX not sure if this is how this is going to continue though as // XXX not sure if this is how this is going to continue though as
// we'll need to organize constructors preferably within this // we'll need to organize constructors preferably within this
// structure and keep it extensible... // structure and keep it extensible...
// //
// XXX might be nice to have conditional stopping...
// a-la event.preventDefault()
// XXX need option threading... // XXX need option threading...
// XXX need to deal with functions... // XXX need to deal with functions...
// XXX add a tree mode -- containers as levels...
// XXX need a way to index the path...
// ...and to filter paths by pattern...
// XXX might be a good idea to generate "structural hashes" for objects...
var HANDLERS = var HANDLERS =
module.HANDLERS = { module.HANDLERS = {
// null... // null...
// //
null: { null: {
final: true, handle: function(obj, res, next, stop){
match: function(obj){ obj === null
return obj === null }, && stop(obj) }, },
handle: 'value', },
// Functions... // Functions...
// //
// XXX EXPERIMENTAL... // XXX EXPERIMENTAL...
// XXX STUB... // XXX STUB...
// XXX can we reuse object's .handle(..) here???
func: { func: {
match: function(obj){ handle: function(obj, res, next, stop){
return typeof(obj) == 'function' }, return typeof(obj) == 'function' ?
handle: 'object', }, {
// XXX need to check if a constructor is built-in...
type: obj.constructor.name,
// Object generations:
// 1 - directly constructed objects
// 2 - objects at least one level deeper than gen 1
gen: obj.constructor.prototype === obj.__proto__ ? 1 : 2,
// XXX
source: obj,
}
: undefined }, },
// Text... // Text...
// //
// XXX EXPERIMENTAL... // XXX EXPERIMENTAL...
text: { text: {
final: true, handle: function(obj, res, next, stop){
match: function(obj){ typeof(obj) == 'string'
return typeof(obj) == 'string'
// XXX make this more optimal... // XXX make this more optimal...
&& obj.includes('\n') }, && obj.includes('\n')
handle: function(obj){ && next(
return [[], ...obj.split(/\n/g)
{ .map(function(line, i){
type: 'Text', return [[module.CONTENT, i], line] }))
source: obj, && stop({
}, type: 'Text',
obj.split(/\n/g) source: obj,
.map(function(line, i){ }) }, },
return [[module.CONTENT, i], line] }) ] }, },
// Non-Objects... // Non-Objects...
// //
// NOTE: this will include undefined and NaN... // NOTE: this will include undefined and NaN...
value: { value: {
final: true, handle: function(obj, res, next, stop){
match: function(obj){ typeof(obj) != 'object'
return typeof(obj) != 'object' && typeof(obj) != 'function'
&& typeof(obj) != 'function' }, && stop(obj) }, },
handle: function(obj){
return [[], obj] }, },
// Base objects... // Base objects...
// //
object: { object: {
match: function(obj){ handle: function(obj, res, next, stop){
return typeof(obj) == 'object' }, return typeof(obj) == 'object' ?
handle: function(obj){ {
return [[], { // XXX need to check if a constructor is built-in...
// XXX need to check if a constructor is built-in... type: obj.constructor.name,
type: obj.constructor.name, // Object generations:
// 1 - directly constructed objects
// Object generations: // 2 - objects at least one level deeper than gen 1
// 1 - directly constructed objects gen: obj.constructor.prototype === obj.__proto__ ? 1 : 2,
// 2 - objects at least one level deeper than gen 1 // XXX
gen: obj.constructor.prototype === obj.__proto__ ? 1 : 2, source: obj,
}
// XXX : undefined }, },
source: obj,
}] }, },
// special keys... // special keys...
proto: { proto: {
match: function(obj){ handle: function(obj, res, next, stop){
return typeof(obj) == 'object' typeof(obj) == 'object'
&& obj.constructor.prototype !== obj.__proto__ }, && obj.constructor.prototype !== obj.__proto__
handle: function(obj){ && next([['__proto__'], obj.__proto__]) }, },
return [[ [['__proto__'], obj.__proto__], ]] }, },
// XXX any other special keys??? // XXX any other special keys???
// - non-iterable? // - non-iterable?
// Entries / Non-attribute (encapsulated) content... // Entries / Non-attribute (encapsulated) content...
// //
setEntries: { setEntries: {
match: function(obj){ handle: function(obj, res, next, stop){
return obj instanceof Set }, obj instanceof Set
// NOTE: we are indexing sets... && next(
handle: function(obj){ ...obj.values()
return [ obj.values() .map(function(v, i){
.map(function(v, i){ return [[module.CONTENT, i], v] })) }, },
return [[module.CONTENT, i], v] })
.toArray() ] }, },
mapEntries: { mapEntries: {
match: function(obj){ handle: function(obj, res, next, stop){
return obj instanceof Map }, obj instanceof Map
handle: function(obj, path, options){ && next(
return [ obj.entries() ...obj.entries()
.map(function([k, v], i){ .map(function([k, v], i){
return [ return [
[[module.CONTENT, i +'@key'], k], [[module.CONTENT, i +'@key'], k],
[[module.CONTENT, i], v], [[module.CONTENT, i], v],
] }) ] })
.flat() .flat()) }, },
.toArray() ] }, },
// Keys / Attributes... // Keys / Attributes...
// //
@ -186,15 +206,15 @@ module.HANDLERS = {
// - skip repeating keys // - skip repeating keys
// ...this could be done on the root handler level... // ...this could be done on the root handler level...
keys: { keys: {
//match: 'object', handle: function(obj, res, next, stop){
//match: ['object', 'func'], ;(typeof(obj) == 'object'
match: function(obj, handlers){ || typeof(obj) == 'function')
return handlers.object.match(obj) && next(
|| handlers.func.match(obj) }, ...Object.entries(obj)
handle: function(obj){ .map(function([k, v]){
return [ Object.entries(obj) return [[k], v] })) }, },
.map(function([k, v]){
return [[k], v] }), ] }, },
/* XXX /* XXX
props: { props: {
//match: 'object', //match: 'object',
@ -227,157 +247,74 @@ module.HANDLERS = {
//false: { match: false }, //false: { match: false },
} }
//
// 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 =
module.getHandlers =
function(obj, handlers=module.HANDLERS){
return [...Object.entries(handlers)
.iter()
.filter(function([k, v]){
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(obj, handlers)){
throw types.STOP(true) }
// normal match...
return v.match
&& v.match(obj, handlers) })
.map(function([k, v]){
return v })] }
//
// Format: // Format:
// [ // [
// [<path>, {type: <name>}], // // primitive value...
//
// [<path>, ['LINK', <path>]],
//
// [<path>, <value>], // [<path>, <value>],
//
// // constructed object...
// [<path>, {
// type: <name>,
// gen: <generation>,
// source: <obj>,
// }],
//
// // link...
// [<path>, 'LINK', <path>],
//
// ] // ]
// //
// XXX add a tree mode -- containers as levels... var handle =
// XXX need a way to index the path... module.handle =
// ...and to filter paths by pattern...
// XXX might be a good idea to generate "structural hashes" for objects...
// XXX can we combine .handle(..) and .match(..) ???
// ...for example via .handle(obj, '?') protocol...
// .....or even simpler, just thread the object through all the
// handlers in one go -- unless there is a fast way to test and
// classify object predictably there is no point in a test stage...
// .....would also be nice to support a STOP(res) instead of .final
var handle =
module.handle =
function*(obj, path=[], options={}){ function*(obj, path=[], options={}){
// handle recursive structures... // handle object loops...
var seen = options.seen = var seen = options.seen =
options.seen || new Map() options.seen || new Map()
if(seen.has(obj)){ if(seen.has(obj)){
yield [path, ['LINK', seen.get(obj)]] yield [path, 'LINK', seen.get(obj)]
return } return }
typeof(obj) == 'object' typeof(obj) == 'object'
&& seen.set(obj, path) && seen.set(obj, path)
// get compatible handler list... var __next = []
var cache = options.cache = var next = function(...values){
options.cache || new Map() __next.splice(__next.length, 0, ...values)
var HANDLERS = options.handlers || module.HANDLERS return true }
var handlers = module.getHandlers(obj, HANDLERS) var stop = function(p, v){
throw module.STOP(arguments.length == 1 ?
[path, arguments[0]]
: [p, v]) }
// XXX might be a good idea to move this up (or into options) so as // handle the object...
// not to define this on each call... var handlers = options.handlers || module.HANDLERS
// ...we'll need to somehow curry in the path which is now passed var res = [path]
// via a closure... yield* Object.values(handlers)
var subtree = function*(data){
// a handler just returned a list of next objects to handle...
if(data.length == 1){
var next = data.pop()
var p = path
// a normal handler...
} else {
var [k, v, next] = data
var p = path.concat(k)
yield [p, v] }
// process queued/next objects...
yield* (next || [])
.iter()
.map(function*([k, v]){
yield* handle(v, p.concat(k), options) }) }
// apply the handlers...
yield* handlers
.iter() .iter()
.filter(function(handler){ .filter(function(handler){
return !!handler.handle }) return !!handler.handle })
.map(function*(handler){ .map(function*(handler){
var h = handler
// expand aliases... // expand aliases...
var h = handler
while(h && typeof(h.handle) == 'string'){ while(h && typeof(h.handle) == 'string'){
h = HANDLERS[h.handle] } h = handlers[h.handle] }
yield* h.handle instanceof types.Generator ? // XXX should .handle(..) be called in the context of h or handler???
// XXX should .handle(..) be called in the context of h or handler??? res = h.handle.call(handler, obj, res, next, stop, options)
h.handle.call(handler, obj, path, options) yield res
.map(subtree) && [path, res] })
: subtree(h.handle.call(handler, obj, path, options)) }) } .filter(function(e){
return !!e })
var HANDLERS2 = // handle the next stuff...
module.HANDLERS2 = { yield* __next.splice(0, __next.length)
null: { .iter()
handle: function(obj){ .map(function*([k, v]){
if(obj === null){ yield* handle(v, path.concat(k), options) }) }
throw module.STOP(obj) } }, },
value: {
handle: function(obj){
if(typeof(obj) != 'object'
&& typeof(obj) != 'function'){
throw module.STOP(obj) } },
object: {
handle: function(obj){
return typeof(obj) == 'object' ?
[[], {
// XXX need to check if a constructor is built-in...
type: obj.constructor.name,
// Object generations:
// 1 - directly constructed objects
// 2 - objects at least one level deeper than gen 1
gen: obj.constructor.prototype === obj.__proto__ ? 1 : 2,
// XXX
source: obj,
}]
: undefined }, },
}
var handle2 =
module.handle2 =
function(){
}
//---------------------------------------------------------------------
// path2str(..) // path2str(..)
// //
// XXX need to figure out a way to avoid clashes with module.CONTENT in // XXX need to figure out a way to avoid clashes with module.CONTENT in
@ -454,11 +391,10 @@ function(str){
var serializePaths = var serializePaths =
module.serializePaths = module.serializePaths =
types.generator.iter types.generator.iter
.map(function([p, v]){ .map(function([p, v, ...rest]){
return v instanceof Array && v[0] == 'LINK' ? return rest.length > 0 && v == 'LINK' ?
// link... // link...
[path2str(p), [path2str(p), v, path2str(rest[0])]
'LINK', path2str(v[1])]
: [path2str(p), v] }) : [path2str(p), v] })
@ -711,6 +647,7 @@ var o = module.o = {
o.object.y = o.object o.object.y = o.object
// generate spec...
console.log([ console.log([
...handle(o) ...handle(o)
.chain( .chain(
@ -720,6 +657,7 @@ console.log([
)]) )])
// use spec to create a new object...
console.log('\n\n---\n', console.log('\n\n---\n',
[...handle(write(null, handle(o))) [...handle(write(null, handle(o)))
.chain( .chain(
@ -730,6 +668,5 @@ console.log('\n\n---\n',
/********************************************************************** /**********************************************************************
* vim:set ts=4 sw=4 : */ return module }) * vim:set ts=4 sw=4 : */ return module })