reforked the exec flow, now async stuff works...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2026-07-02 18:55:54 +03:00
parent e7dc0194ad
commit cc94cdad03
2 changed files with 152 additions and 86 deletions

View File

@ -574,14 +574,6 @@ module.BaseParser = {
ast ast
: ast.iter() : 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 = [] var elems = []
for(let elem of ast){ for(let elem of ast){
// text block... // text block...
@ -621,9 +613,10 @@ module.BaseParser = {
var res = that.callMacro(page, name, args, body, state) var res = that.callMacro(page, name, args, body, state)
// async... // async...
if(res instanceof Promise){ if(res instanceof Promise){
let all, nested
all = state.waitAll =
Promise.all([state.waitAll, res])
elem.resolving = res elem.resolving = res
wait.add(res)
state.waitAll = res
// XXX do we need to wait till the last .waitNested is // XXX do we need to wait till the last .waitNested is
// resolved? // resolved?
// ...should it's handlers complete?? // ...should it's handlers complete??
@ -632,13 +625,13 @@ module.BaseParser = {
// func to the macro... // func to the macro...
// ...do we need this? // ...do we need this?
if(!res.isolated){ if(!res.isolated){
state.waitNested = res } nested = state.waitNested =
Promise.all([state.waitNested, res]) }
res.then( res.then(
function(value){ function(value){
wait.delete(res) if(state.waitAll === all){
if(state.waitAll === res){
delete state.waitAll } delete state.waitAll }
if(state.waitNested === res){ if(state.waitNested === nested){
delete state.waitNested } delete state.waitNested }
delete elem.resolving delete elem.resolving
elem.value = value elem.value = value
@ -659,25 +652,52 @@ module.BaseParser = {
elem = res } elem = res }
elems.push(elem) } elems.push(elem) }
// handle wait for isolated macros to resolve... // cleanup...
var done = function(){ var wait
resolve(state) var waitAll = state.waitAll
delete state.wait } state.waitAll
if(wait.size > 0){ && state.waitAll
Promise.all(wait) .then(function(){
.then( // only cleanup our own mess =)
done, waitAll === state.waitAll
function(err){ && (delete state.waitAll) })
reject(err) }) && (wait = state.wait = waitAll
// done... .then(function(){
} else { wait === state.wait
done() } && (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 }, return elems },
// resolve stage II macros and merge results... // 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,
// '<slot X>@include(/async/page)</slot> <slot X value>',
// s = {})
// s.wait.then(function(){
// console.log(r) }) // -> ['Page '] (err)
// and:
// r = parser.expand(
// tests.P,
// '<slot X>@include(/async/page)</slot> <slot X slot/>',
// 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={}){ resolve: function(page, ast, state={}){
var that = this var that = this
ast = typeof(ast) != 'object' ? ast = typeof(ast) != 'object' ?
@ -705,29 +725,89 @@ module.BaseParser = {
prev = '' } prev = '' }
if(args.length > 0){ if(args.length > 0){
elems.push(args.shift()) } } } 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 can ast be a promise???
// XXX elems can be unresolved -- need a merge strategy for them... // XXX elems can be unresolved -- need a merge strategy for them...
var elems = []
for(var elem of ast){ for(var elem of ast){
// nesting... // nesting...
while(elem && elem.value){ while(elem && elem.value){
elem = elem.value
// exec stage II macros... // exec stage II macros...
if(typeof(elem) == 'function'){ if(typeof(elem.value) == 'function'){
elem = elem(state) } } 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){ if(elem == null){
continue } 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... // atomic values...
if(typeof(elem) != 'object'){ if(typeof(elem) != 'object'){
merge(elem) elems.push(elem)
continue } continue }
// value is resolved but "empty" -> skip... // value is resolved but "empty" -> skip...
if('value' in elem if('value' in elem
@ -736,61 +816,48 @@ module.BaseParser = {
continue } continue }
// expand ast... // expand ast...
if(elem instanceof Array){ if(elem instanceof Array){
// XXX this can containe promises... // XXX this can be or containe promises...
merge(...this.resolve(page, elem, state)) elems.push(...that.resolve(page, elem, state))
continue } continue }
// expand .body attribute... // expand .body attribute...
if((elem.attrs ?? {}).body instanceof Array){ /* XXX is this needed here???
// XXX this can containe promises... if(elem.body instanceof Array){
elem.attrs.body = this.resolve(page, elem.attrs.body, state) } 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... // nested macro with no value set -- skip...
if(this.macros[elem.name] instanceof Array){ if(that.macros[elem.name] instanceof Array){
continue } continue }
// unresolved... // unresolved...
merge(elem) } elems.push(elem) }
return elems }, return elems },
// XXX render api... // XXX render api...
// XXX how should this play with filters??? // XXX how should this play with filters???
// ...should filters be client-side only?? // ...should filters be client-side only??
render: function*(page, ast, callback, state={}){ render: function*(page, ast, callback, state={}){
ast = this.resolve(page, ast, state) // XXX
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( return Promise.awaitOrRun(
this.resolve(page, ast, state), this.resolve(page, ast, state),
function(ast){ state[wait],
// XXX function(ast, reresolve){
if(ast.length > 1){ ast = reresolve ?
throw new Error('!!!!') that.resolve(page, ast, state)
} : ast
return ast[0] return (ast ?? '').join('') }) },
?? '' }) },
// XXX
// XXX how should this play with filters??? // XXX how should this play with filters???
// ...should filters be client-side only?? // ...should filters be client-side only??
parseNested: function(page, ast, state={}){ parseNested: function(page, ast, state={}){
return Promise.awaitOrRun( return this.parse(page, ast, state, 'waitNestedi') },
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 }) },
@ -1505,7 +1572,7 @@ module.parser = {
// content handler... // content handler...
handler ??= handler ??=
function(page, text, state){ function(page, text, state){
return this._parse(page, text, state) } return this.expand(page, text, state) }
var pageHandler = var pageHandler =
function(text, i, l){ function(text, i, l){
@ -1525,7 +1592,7 @@ module.parser = {
// join... // join...
(args.join (args.join
&& i < l.length - 1) ? && i < l.length - 1) ?
that._parse(page, args.join, state) that.expand(page, args.join, state)
: [] : []
].flat() } ].flat() }
var resultHandler = var resultHandler =

View File

@ -181,7 +181,6 @@ test.Setups({
// XXX // XXX
// include... // include...
// XXX these do not play well with modifiers...
include: function(assert, path='/blank', expected){ include: function(assert, path='/blank', expected){
return { return {
page: P, page: P,
@ -190,18 +189,18 @@ test.Setups({
// XXX for some reason this does not parse int a macro.... // XXX for some reason this does not parse int a macro....
//'<include /blank />', //'<include /blank />',
`<include "${path}" />`, `<include "${path}" />`,
expected `<include src="${path}" />`,
?? P.get(path).raw,
expected
?? P.get(path).raw,
], ],
}}, }},
include_page: function(assert){ include_page: function(assert){
return this.include(assert, '/page') }, return this.include(assert, '/page') },
include_include_page: function(assert){ include_include_page: function(assert){
return this.include(assert, '/includePage', 'Page') }, return this.include(assert, '/includePage', 'Page') },
/* XXX parser.resolve(..) does not handle promises correctly yet...
include_async: function(assert){ 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({ test.Tests({
parse: function(assert, state){ parse: async function(assert, state){
var {page, code, st} = state var {page, code, st} = state
page ??= {} page ??= {}
st ??= {} st ??= {}
@ -247,7 +246,7 @@ test.Tests({
var p = serialize.partialDeepCopy(page) var p = serialize.partialDeepCopy(page)
var s = serialize.partialDeepCopy(st) var s = serialize.partialDeepCopy(st)
assert( assert(
(res = parser.parse( (res = await parser.parse(
p, p,
input, input,
s)) s))