diff --git a/v3/pwiki/parser.js b/v3/pwiki/parser.js index fbcaded..310d9fe 100644 --- a/v3/pwiki/parser.js +++ b/v3/pwiki/parser.js @@ -574,14 +574,6 @@ module.BaseParser = { ast : ast.iter() - var wait = new Set() - state.wait instanceof Promise - && wait.add(state.wait) - var resolve, reject - state.wait = new Promise( - function(){ - ;[resolve, reject] = arguments }) - var elems = [] for(let elem of ast){ // text block... @@ -621,9 +613,10 @@ module.BaseParser = { var res = that.callMacro(page, name, args, body, state) // async... if(res instanceof Promise){ + let all, nested + all = state.waitAll = + Promise.all([state.waitAll, res]) elem.resolving = res - wait.add(res) - state.waitAll = res // XXX do we need to wait till the last .waitNested is // resolved? // ...should it's handlers complete?? @@ -632,13 +625,13 @@ module.BaseParser = { // func to the macro... // ...do we need this? if(!res.isolated){ - state.waitNested = res } + nested = state.waitNested = + Promise.all([state.waitNested, res]) } res.then( function(value){ - wait.delete(res) - if(state.waitAll === res){ + if(state.waitAll === all){ delete state.waitAll } - if(state.waitNested === res){ + if(state.waitNested === nested){ delete state.waitNested } delete elem.resolving elem.value = value @@ -659,25 +652,52 @@ module.BaseParser = { elem = res } elems.push(elem) } - // handle wait for isolated macros to resolve... - var done = function(){ - resolve(state) - delete state.wait } - if(wait.size > 0){ - Promise.all(wait) - .then( - done, - function(err){ - reject(err) }) - // done... - } else { - done() } + // cleanup... + var wait + var waitAll = state.waitAll + state.waitAll + && state.waitAll + .then(function(){ + // only cleanup our own mess =) + waitAll === state.waitAll + && (delete state.waitAll) }) + && (wait = state.wait = waitAll + .then(function(){ + wait === state.wait + && (delete state.wait) + return elems })) + var waitNested = state.waitNested + state.waitNested + && state.waitNested + .then(function(){ + // only cleanup our own mess =) + waitNested === state.waitNested + && (delete state.waitNested) }) return elems }, // resolve stage II macros and merge results... // - // XXX BUG: sync/async paths seem to diverge... + /* XXX + // XXX these yeild different results: + // r = parser.resolve( + // tests.P, + // '@include(/async/page) ', + // s = {}) + // s.wait.then(function(){ + // console.log(r) }) // -> ['Page '] (err) + // and: + // r = parser.expand( + // tests.P, + // '@include(/async/page) ', + // s = {}) + // s.wait.then(function(){ + // r = parser.resolve(test.P, r, s) + // console.log(r) }) // -> ['slot '] (correct) + // -> promises seem to not be sequenced correctly here... + // XXX it looks like the first slot is resolved before the last + // slot has a chance to set the value as it is waiting for + // the first slot to finish... resolve: function(page, ast, state={}){ var that = this ast = typeof(ast) != 'object' ? @@ -705,29 +725,89 @@ module.BaseParser = { prev = '' } if(args.length > 0){ elems.push(args.shift()) } } } - var unpack = function(elem){ - // XXX - } + // XXX can we make this more granular and wait only where we need + // to wait? + // ....the wait is logical as we need to return a string here + // eventually. + // should this be split into .resolve(..) nad .merge(..)??? + return Promise.awaitOrRun( + state.wait, + function(){ + // XXX can ast be a promise??? + // XXX elems can be unresolved -- need a merge strategy for them... + //var waiting = [] + for(var elem of ast){ + // nesting... + while(elem && elem.value){ + elem = elem.value + // exec stage II macros... + // XXX this should be called when all the promises before + // are resolved... + if(typeof(elem) == 'function'){ + elem = elem(state) } } + if(elem == null){ + continue } + // atomic values... + if(typeof(elem) != 'object'){ + merge(elem) + continue } + // value is resolved but "empty" -> skip... + if('value' in elem + && (elem.value == null + || elem.value == '')){ + continue } + // expand ast... + if(elem instanceof Array){ + // XXX this can be or containe promises... + merge(...that.resolve(page, elem, state)) + continue } + // expand .body attribute... + // XXX is this needed here??? + if(elem.body instanceof Array){ + console.warn('!!! RESOLVE_BODY') + // XXX this can be or containe promises... + elem.body = that.resolve(page, elem.body, state) } + // nested macro with no value set -- skip... + if(that.macros[elem.name] instanceof Array){ + continue } + // unresolved... + merge(elem) } + + return elems }) }, + //*/ + resolve: function(page, ast, state={}){ + var that = this + ast = typeof(ast) != 'object' ? + this.expand(page, ast, state) + : ast instanceof types.Generator ? + ast + : ast.iter() + + // merge resolved elements into the last item of elems... // XXX can ast be a promise??? // XXX elems can be unresolved -- need a merge strategy for them... + var elems = [] for(var elem of ast){ // nesting... while(elem && elem.value){ - elem = elem.value // exec stage II macros... - if(typeof(elem) == 'function'){ - elem = elem(state) } } + if(typeof(elem.value) == 'function'){ + let e = elem + // NOTE: if not everything is resolved, delay the stage II + // callbacks till .wait is done... + Promise.awaitOrRun( + state.wait, + function(){ + return elem = e.value = + e.value(state) }) + break } + elem = elem.value } if(elem == null){ continue } - // XXX do a delayed merge... - // ...but for this we need to also apply the rest of this iteration to the result... - if(elem.resolving){ - console.log('!!!!!!!!!!!!!!!!!!!') - } // atomic values... if(typeof(elem) != 'object'){ - merge(elem) + elems.push(elem) continue } // value is resolved but "empty" -> skip... if('value' in elem @@ -736,61 +816,48 @@ module.BaseParser = { continue } // expand ast... if(elem instanceof Array){ - // XXX this can containe promises... - merge(...this.resolve(page, elem, state)) + // XXX this can be or containe promises... + elems.push(...that.resolve(page, elem, state)) continue } // expand .body attribute... - if((elem.attrs ?? {}).body instanceof Array){ - // XXX this can containe promises... - elem.attrs.body = this.resolve(page, elem.attrs.body, state) } + /* XXX is this needed here??? + if(elem.body instanceof Array){ + console.warn('!!! RESOLVE_BODY') + // XXX this can be or containe promises... + elem.body = that.resolve(page, elem.body, state) } + //*/ // nested macro with no value set -- skip... - if(this.macros[elem.name] instanceof Array){ + if(that.macros[elem.name] instanceof Array){ continue } // unresolved... - merge(elem) } + elems.push(elem) } return elems }, + // XXX render api... // XXX how should this play with filters??? // ...should filters be client-side only?? render: function*(page, ast, callback, state={}){ - ast = this.resolve(page, ast, state) - for(var elem of ast){ - yield Promise.awaitOrRun( - elem, - callback) } }, + // XXX + }, // XXX - _parse: function(page, ast, state={}){ + parse: function(page, ast, state={}, wait='wait'){ + var that = this + var reresolve = !!state.wait return Promise.awaitOrRun( this.resolve(page, ast, state), - function(ast){ - // XXX - if(ast.length > 1){ - throw new Error('!!!!') - } - return ast[0] - ?? '' }) }, - // XXX + state[wait], + function(ast, reresolve){ + ast = reresolve ? + that.resolve(page, ast, state) + : ast + return (ast ?? '').join('') }) }, // XXX how should this play with filters??? // ...should filters be client-side only?? parseNested: function(page, ast, state={}){ - return Promise.awaitOrRun( - state.waitNested, - this._parse(page, ast, state), - function(_, ast){ - return ast }) }, - - // XXX this can't be used from within macros -- will deadlock the results... - // XXX how should this play with filters??? - // ...should filters be client-side only?? - parse: function(page, ast, state={}){ - return Promise.awaitOrRun( - state.wait, - this._parse(page, ast, state), - function(_, ast){ - return ast }) }, + return this.parse(page, ast, state, 'waitNestedi') }, @@ -1505,7 +1572,7 @@ module.parser = { // content handler... handler ??= function(page, text, state){ - return this._parse(page, text, state) } + return this.expand(page, text, state) } var pageHandler = function(text, i, l){ @@ -1525,7 +1592,7 @@ module.parser = { // join... (args.join && i < l.length - 1) ? - that._parse(page, args.join, state) + that.expand(page, args.join, state) : [] ].flat() } var resultHandler = diff --git a/v3/pwiki/test/parser.js b/v3/pwiki/test/parser.js index 092bfac..f8e5ae8 100755 --- a/v3/pwiki/test/parser.js +++ b/v3/pwiki/test/parser.js @@ -181,7 +181,6 @@ test.Setups({ // XXX // include... - // XXX these do not play well with modifiers... include: function(assert, path='/blank', expected){ return { page: P, @@ -190,18 +189,18 @@ test.Setups({ // XXX for some reason this does not parse int a macro.... //'', ``, - expected - ?? P.get(path).raw, + ``, + + expected + ?? P.get(path).raw, ], }}, include_page: function(assert){ return this.include(assert, '/page') }, include_include_page: function(assert){ return this.include(assert, '/includePage', 'Page') }, - /* XXX parser.resolve(..) does not handle promises correctly yet... include_async: function(assert){ - return this.include(assert, '/async/page') }, - //*/ + return this.include(assert, '/async/page', 'Page') }, }) @@ -234,7 +233,7 @@ test.Modifiers({ test.Tests({ - parse: function(assert, state){ + parse: async function(assert, state){ var {page, code, st} = state page ??= {} st ??= {} @@ -247,7 +246,7 @@ test.Tests({ var p = serialize.partialDeepCopy(page) var s = serialize.partialDeepCopy(st) assert( - (res = parser.parse( + (res = await parser.parse( p, input, s))