Compare commits

..

8 Commits

Author SHA1 Message Date
a59ddf61a9 still fighting async recursion...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2026-07-05 16:30:10 +03:00
18daeb27ff tweaking...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2026-07-05 15:35:02 +03:00
9341e45346 moved recursion testing back -- diffrent macros may will need to control this differently...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2026-07-05 15:32:40 +03:00
b98e4ca5c0 moved recursion testing up one level...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2026-07-05 15:30:50 +03:00
67db497eab refactoring parser -- still not 100%...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2026-07-05 14:56:45 +03:00
7786ddaeb6 minor fix...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2026-07-05 12:43:12 +03:00
382c3f4350 minor fix...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2026-07-05 12:39:52 +03:00
5c2a7ca7d1 tweaks...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2026-07-05 11:42:24 +03:00
3 changed files with 120 additions and 75 deletions

12
v3/package-lock.json generated
View File

@ -32,9 +32,9 @@
}
},
"node_modules/brace-expansion": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
"integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
@ -231,9 +231,9 @@
}
},
"node_modules/ig-types": {
"version": "6.26.2",
"resolved": "https://registry.npmjs.org/ig-types/-/ig-types-6.26.2.tgz",
"integrity": "sha512-VGg8MVmpblVCmZK52bJcDtPG3uRFiEyPnlDEIJv3MymTm+aUNWR/Th20NU2wfGV/Ux3KlsbGlhe+0c9ZKTYPDw==",
"version": "6.26.3",
"resolved": "https://registry.npmjs.org/ig-types/-/ig-types-6.26.3.tgz",
"integrity": "sha512-WDGHnC2up/7x5agsS2HfQbegfY97mMGM4xNv1HII3OiidcoPFDjp7nLdOOsWfZOtXA7nGWdAriGeVCOOJxEhew==",
"license": "BSD-3-Clause",
"dependencies": {
"ig-object": "^6.0.0",

View File

@ -608,6 +608,9 @@ module.BaseParser = {
// sync (text) and async (DOM) rendering
// in the simplest form: take the expanded AST and merge
// into a single string
// XXX BUG: .wait can resolve before everything in the tree is resolved...
// to reproduce:
// @include(/async/recursive/SelfOther recursive="recursion found")
expand: function(page, ast, state={}, nested_handlers={}){
var that = this
ast = typeof(ast) != 'object' ?
@ -1263,6 +1266,8 @@ module.parser = {
// to the same context...
// XXX need a way to make encode option transparent...
// XXX do we want to load a specific slot/block???
// XXX REVISE...
INCLUDE_LIMIT: 20,
include: Macro(
['src', 'recursive', 'join',
['s', 'strict', 'isolated']],
@ -1279,13 +1284,6 @@ module.parser = {
function(page, args, body, state, handler){
var that = this
/* XXX see .joinBlocks(..) join caching for more info...
// cache body ast...
body = body ?
this.ast(body)
: body
//*/
var recursive =
state.recursive =
args.recursive
@ -1300,19 +1298,6 @@ module.parser = {
return Promise.awaitOrRun(
this.parseNested(page, src, state),
function(src){
// check for recursion...
// XXX will this work for @source(..)???
var stack = state.include_stack ??= []
if(stack.includes(src)){
if(!recursive){
throw new Error('Recursive macro: '+stack) }
return that.expand(page, recursive, state) }
stack.push(src)
var cache = state.cache ??= {}
if(cache[src]){
return cache[src] }
// XXX should this be a tree??
// ...need to at least split direct and
// indirect dependencies...
@ -1325,60 +1310,75 @@ module.parser = {
// content handler...
handler ??=
function(page, src, text, state){
page = page.get(src)
return args.isolated ?
function(page, body, path, text, state){
// re-include limit...
// XXX HACK???
if( ++(state.included ??= {[path]: 0})[path]
> this.macros.INCLUDE_LIMIT ?? 20){
// XXX BUG: for some reason for async recursion this
// breaks returning [object Object] overriding
// the actual return value
if(!recursive){
throw new Error(path +': include limit reached: '+ state.included[path]) }
return that.expand(page, recursive, state) }
// handle recursion...
// XXX BUG: for some reason this does not work for async...
// ...and works quite differently in tests and
// in console -- returns [object Object] in the
// former and hangs in the later...
var include_stack = state.include_stack ??= []
if(include_stack.includes(path)){
if(!recursive){
throw new Error('Recursive macro: '+include_stack) }
return that.expand(page, recursive, state) }
include_stack.push(path)
// XXX check cache???
var res = args.isolated ?
this.resolve(
page,
text,
Object.assign(
args.isolated == 'partial' ?
serialize.partialDeepCopy(state)
: {},
{stack: state.stack ?? []}))
: this.expand(page, text, state) }
args.isolated == 'partial' ?
serialize.partialDeepCopy(state)
: {})
: this.expand(page, text, state)
Promise.awaitOrRun(
state.waitAll,
function(){
// handle recursion...
state.include_stack.at(-1) == src
&& state.include_stack.pop()
// cleanup...
if(state.include_stack.length == 0){
delete state.include_stack
delete state.recursive } })
return res }
var pageHandler =
function([path, text]){
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 = handler.call(that, page, src, text, state) } }),
function(text){
// if no <content/> present we still
// need to handle the included page...
content_handled
|| handler.call(that, page, src, text, state)
return text })
// place as-is...
: handler.call(that, page, src, text, state) }
var resultHandler =
function(pages){
state.include_stack.at(-1) == src
&& state.include_stack.pop()
// cleanup...
if(state.include_stack.length == 0){
delete state.include_stack
delete state.recursive }
// cache the final result...
cache[src] = pages
return pages }
// handle nested promises...
return Promise.awaitOrRun(
text,
function(text){
return handler.call(that, page, body, path, text, state) }) }
// get and run things...
return Promise.awaitOrRun(
page.get(src).matched,
page.get(src).raw,
function(paths, pages){
paths = paths instanceof Array ?
paths
: [paths]
pages = pages instanceof Array ?
pages
: [pages]
function(paths, texts){
texts =
// XXX how do we handle paths returning non-strings???
// special case: list page...
paths.length == 1
&& texts instanceof Array ?
[texts]
: texts instanceof Array ?
texts
: [texts ?? '']
return Promise.awaitOrRun(
// handle pages...
Promise
@ -1386,13 +1386,17 @@ module.parser = {
that.joinBlocks(
page,
Array
.zip(paths, pages)
.zip(paths, texts)
.map(pageHandler),
args.join,
state))
.flat()
.sync(),
resultHandler ) }) }) })),
function(pages){
/* XXX cache the final result...
cache[src] = pages
//*/
return pages }) }) }) })),
// NOTE: the main difference between this and @include is that
// this renders the src in the context of current page while
@ -1544,6 +1548,7 @@ module.parser = {
// ...this seems to effect non-pattern pages...
// XXX should macro:index be 0 or 1 (current) based???
// XXX SORT sorting not implemented yet...
// XXX check macro recursion...
macro: Macro(
['name', 'src', 'sort', 'text', 'join', 'else',
['strict', 'isolated', 'inheritmacros', 'inheritvars']],
@ -1561,7 +1566,38 @@ module.parser = {
body = (state.macros ?? {})[name] }
return args.src && body ?
// run macro...
that.macros.include.call(that, page, args, body, state)
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) })
: '' }) })),
// nesting rules...

View File

@ -87,7 +87,13 @@ module.exports.P = {
return res.length == 1 ?
res[0]
/*/ if at least one page is a promise wait for all...
: Promise
.iter(res)
.sync() },
/*/
: res },
//*/
// XXX should this return an arrya for a multi-match???
get text(){
@ -278,13 +284,14 @@ test.Setups({
],
} },
//*/
// content...
/*/ XXX content...
include_content: function(assert, path='/page'){
return {
page: P,
code: [
'<include "'+ path +'">[[ <content/> ]]</include>',
'[[ '+ P.get(path).raw +' ]]', ], } },
//*/
// XXX
// recursion...
@ -294,6 +301,7 @@ test.Setups({
page: P,
code:[
'@include(/recursive/Self recursive="recursion found")',
// XXX this hangs...
'@include(/async/recursive/Self recursive="recursion found")',
'<< recursion found >>', ], } },
include_recursive_b: function(assert){
@ -301,6 +309,7 @@ test.Setups({
page: P,
code:[
'@include(/recursive/SelfOther recursive="recursion found")',
// XXX this hangs...
'@include(/async/recursive/SelfOther recursive="recursion found")',
'<< << recursion found >> >>', ], } },