Compare commits

..

4 Commits

Author SHA1 Message Date
31b22be60b cleanup...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2026-07-06 02:06:13 +03:00
5f64d70fbb cleanup + notes...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2026-07-06 00:02:04 +03:00
d24257d3e2 cleanup and notes...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2026-07-05 21:25:05 +03:00
c7be62a48f brute-force-fixed the issue, not yet happy with the code...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2026-07-05 21:11:44 +03:00
2 changed files with 93 additions and 101 deletions

View File

@ -730,27 +730,21 @@ module.BaseParser = {
// resolve stage II macros and merge results...
//
// XXX DNF
// <ast> ::= [ <item>, ... ]
// <item> ::=
// <basic-value>
// | <elem>
//
// 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...
// <elem> is returned if its value is not resolved yet.
//
//
// NOTE: to fully resolve the ast this may need to be called several
// times...
//
//
// XXX can we prevent reaces over state.unresolved???
// it can be deleted when calling .parse(..) / .parseNested(..)
// while parsing, for example from within a macro...
resolve: function(page, ast, state={}, nested_handlers={}){
var that = this
ast = typeof(ast) != 'object' ?
@ -760,8 +754,6 @@ module.BaseParser = {
: 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...
@ -769,9 +761,9 @@ module.BaseParser = {
// exec stage II macros...
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(
// if not everything is resolved, delay the stage II
// callbacks till .wait is done...
state.wait,
function(){
return elem = e.value =
@ -791,24 +783,29 @@ module.BaseParser = {
continue }
// expand ast...
if(elem instanceof Array){
// XXX this can be or containe promises...
elems.push(...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...
;(state.unresolved ??= [])
.push(elem.resolving instanceof Promise ?
elem.resolving
: elem)
//*/
// NOTE: we do not need to expand .body attributes as these
// are the responsibility of the respective macros...
elems.push(elem) }
return elems },
isResolved: function(ast){
if(!(ast instanceof Array)){
return false }
for(var e of ast){
if(typeof(e) == 'object'){
return false } }
return true },
// XXX render api...
// XXX how should this play with filters???
@ -817,18 +814,37 @@ module.BaseParser = {
// XXX
},
// XXX
parse: function(page, ast, state={}, nested_handlers={}, wait='wait'){
var that = this
var reresolve = !!state.wait
// XXX might be a good idea to limit recursion depth here...
var merge = function(ast){
return Promise.awaitOrRun(
...(state.unresolved ?? []),
function(){
delete state.unresolved
// re-resolve...
ast = that.resolve(page, ast, state, nested_handlers)
// NOTE: this is essentially running in the same frame
// as .resolve(..) above so there should not be
// any races to delete .unresolved...
return state.unresolved ?
merge(ast)
: (ast ?? '').join('') }) }
return Promise.awaitOrRun(
this.resolve(page, ast, state, nested_handlers),
state[wait],
function(ast, reresolve){
ast = reresolve ?
that.resolve(page, ast, state, nested_handlers)
: ast
return (ast ?? '').join('') }) },
function(ast){
// NOTE: in an async world where any promised macro can
// call .parse(..) / .parseNested(..) we can't trust
// the lack of .unresolved in state...
return (state.unresolved
|| !that.isResolved(ast)) ?
merge(ast)
: (ast ?? '').join('') }) },
// XXX how should this play with filters???
// ...should filters be client-side only??
parseNested: function(page, ast, state={}, nested_handlers={}){
@ -1292,12 +1308,11 @@ module.parser = {
recursive ??= ''
var base = page.basepath
// XXX we do this before or after we parse???
var src = page.resolvePathVars(args.src)
return Promise.awaitOrRun(
this.parseNested(page, src, state),
this.parseNested(page, args.src, state),
function(src){
//src = page.resolvePathVars(src)
// XXX should this be a tree??
// ...need to at least split direct and
// indirect dependencies...
@ -1335,19 +1350,21 @@ module.parser = {
// XXX check cache???
var nested
var res = args.isolated ?
this.resolve(
page,
text,
args.isolated == 'partial' ?
nested = args.isolated == 'partial' ?
serialize.partialDeepCopy(state)
: {})
: this.expand(page, text, state)
// handle recursion...
Promise.awaitOrRun(
(nested ?? {}).waitAll,
state.waitAll,
function(){
// handle recursion...
state.include_stack.at(-1) == src
&& state.include_stack.pop()
// cleanup...
@ -1413,7 +1430,7 @@ module.parser = {
var that = this
return this.macros['include'].call(this,
page, args, body, state,
function(page, src, text, state){
function(page, body, path, text, state){
return that.expand(page, text, state) }) }),
// Load macro and slot definitions but ignore the page text...
@ -1424,7 +1441,8 @@ module.parser = {
function(page, args, body, state){
var that = this
return Promise.awaitOrRun(
this.macros['include'].call(this, page, args, body, state),
this.macros['include'].call(this,
page, {src: args.src}, body, state),
function(){
return '' }) }),
@ -1467,6 +1485,7 @@ module.parser = {
args.src
&& this.parseNested(page, args.src, state),
function(src){
//src = page.resolvePathVars(src)
var text =
src ?
page.get(src).raw
@ -1476,20 +1495,6 @@ module.parser = {
return Promise.awaitOrRun(
text,
function(text){
// XXX not sure I like that this has two "modes"...
text = src && body ?
// XXX do we need to account for generators???
(text instanceof Array ?
text
: [text])
.map(function(text){
return that.expand(
page,
that.ast(body.join(''), false, 'quote'),
state,
{ content: function(){
return text }, }) })
: text
return that.joinBlocks(
page,
that.filterBlocks(
@ -1564,40 +1569,20 @@ module.parser = {
// get macro...
} else if(name){
body = (state.macros ?? {})[name] }
// else...
if(args.src
&& !page.get(args.src).exists()){
for(var elem of body){
if(elem.name == 'else'){
body = elem.body
return that.expand(page.get(args.src), elem.body, state) } } }
return args.src && body ?
// run macro...
that.macros.include.call(that, page, args, body, state,
function(page, body, path, text, state){
var that = this
var handle = function(page, text, state){
return args.isolated ?
that.resolve(
page,
text,
Object.assign(
args.isolated == 'partial' ?
serialize.partialDeepCopy(state)
: {},
{include_stack: state.include_stack ?? []}))
: that.expand(page, text, state) }
//var content_handled = false
return body ?
// handle body / <content/>...
Promise.awaitOrRun(
that.expand(page.get(path), body, state,
{ content: function(){
content_handled = true
return text = handle(page, text, state) } }),
function(text){
// if no <content/> present we still
// need to handle the included page...
//content_handled
// || handle.call(that, page, text, state)
return text })
// place as-is...
: handle(page, text, state) })
return this.expand(page.get(path), body, state) })
: '' }) })),
// nesting rules...

View File

@ -56,6 +56,9 @@ module.exports.P = {
get matched(){
var path_action_pattern = /\/path\/?/
var path = this.path.replace(path_action_pattern, '')
path = path == '' ?
'/'
: path
var path_action = path != this.path
if(path.includes('*')){
@ -72,17 +75,27 @@ module.exports.P = {
: [] })
.flat() }
return [this.path] },
exists: function(path){
var that = this
return (path ?
this.get(path).matched
: this.matched)
.filter(function(p){
return p in that.__pages__ })
.length > 0 },
// * -> a promise or a list of promises???
get raw(){
var res = []
var path_action_pattern = /\/path\/?/
var path_action = this.path.match(path_action_pattern)
var path_action = path_action_pattern.test(this.path)
for(var path of this.matched){
res.push(
path_action ?
path.replace(path_action_pattern, '')
((path = path.replace(path_action_pattern, '')) == '' ?
'/'
: path)
: this.__pages__[path]) }
return res.length == 1 ?
@ -315,13 +328,7 @@ test.Setups({
// quote...
// for inline quoting see: test.Modifiers.quote
// XXX <quote src=.. />
quote_content: function(assert, path='/isolated'){
return {
page: P,
code: [
'<quote "'+ path +'">[[ <content/> ]]</quote>',
'[[ '+ P.get(path).raw +' ]]', ], } },
// XXX
// XXX macro...
// XXX