correct nested filter handling....

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2026-07-11 01:23:37 +03:00
parent 8ad2f12fc8
commit b1c1c4024f
2 changed files with 100 additions and 66 deletions

View File

@ -273,7 +273,8 @@ module.BaseParser = {
return f in (that.filters ?? {})}) return f in (that.filters ?? {})})
var handle = function(str){ var handle = function(str){
// skip non-basic data... // skip non-basic data...
if(typeof(str) == 'object'){ if(typeof(str) == 'object'
|| typeof(str) == 'function'){
return str } return str }
return filters return filters
.reduce(function(res, filter){ .reduce(function(res, filter){
@ -686,6 +687,7 @@ module.BaseParser = {
// sync... // sync...
} else { } else {
elem.value = res } elem.value = res }
elems.push(elem) } elems.push(elem) }
// cleanup... // cleanup...
@ -797,18 +799,15 @@ module.BaseParser = {
// Merge and apply global filters (stage III)... // Merge and apply global filters (stage III)...
// //
// - ensure the ast is fully resolved // - ensure the ast is fully resolved
// - apply nested stage III handlers // - apply stage III pre handlers
// - apply filters on whole ast // - apply global filters
// - apply stage III post handlers
// //
// //
// XXX RECURSION might be a good idea to limit recursion/nesting depth // XXX RECURSION might be a good idea to limit recursion/nesting depth
// of inner merge(..)... // of inner resolve(..)...
// XXX to allow nested filter blocks to self-exclude form global/uppaer // XXX RENAME...
// filters would be nice to be able to do the filtering before finalize: function(page, ast, state={}, nested_handlers={}, wait='wait'){
// we are fully resolved, i.e. after all the promises but before
// all the functions are gone from the ast...
// ...not yet sure how to get this through the merge(..)
merge: function(page, ast, state={}, nested_handlers={}, wait='wait'){
var that = this var that = this
var stage3 = function(ast){ var stage3 = function(ast){
@ -818,46 +817,56 @@ module.BaseParser = {
e.call(that, state) e.call(that, state)
: e }) : e })
.flat() } .flat() }
var merge = function(ast){ var resolve = function(ast){
return Promise.awaitOrRun( return Promise.awaitOrRun(
...(state.unresolved ?? []), ...(state.unresolved ?? []),
function(){ function(){
delete state.unresolved delete state.unresolved
// re-resolve... // re-resolve...
ast = stage3( ast = that.resolve(page, ast, state, nested_handlers)
that.resolve(page, ast, state, nested_handlers))
// NOTE: this is essentially running in the same frame // NOTE: this is essentially running in the same frame
// as .resolve(..) above so there should not be // as .resolve(..) above so there should not be
// any races to delete .unresolved... // any races to delete .unresolved...
return state.unresolved ? return state.unresolved ?
merge(ast) resolve(ast)
: (ast ?? '').join('') }) } : ast }) }
ast = stage3( ast = this.resolve(page, ast, state, nested_handlers)
this.resolve(page, ast, state, nested_handlers))
return Promise.awaitOrRun( return Promise.awaitOrRun(
state[wait], ...[state[wait]].flat(),
function(){ function(){
// NOTE: in an async world where any promised macro can // NOTE: in an async world where any promised macro can
// call .exec(..) / .execNested(..) we can't trust // call .exec(..) / .execNested(..) we can't trust
// the lack of .unresolved in state... // the lack of .unresolved in state...
return that.applyFilters( return Promise.awaitOrRun(
state.filters ?? [],
(state.unresolved (state.unresolved
|| !that.isResolved(ast)) ? || !that.isResolved(ast)) ?
merge(ast) resolve(ast)
: (ast ?? '').join(''), : ast,
state) }) }, function(ast){
return (
// stage III post...
stage3(
that.applyFilters(
state.filters ?? [],
// stage III pre...
stage3( ast ),
state))) }) }) },
exec: function(page, ast, state={}, nested_handlers={}, wait='wait'){ exec: function(page, ast, state={}, nested_handlers={}, wait='wait'){
return this.merge(...arguments) }, return Promise.awaitOrRun(
this.finalize(...arguments),
function(res){
return res.join('') }) },
// XXX
execNested: function(page, ast, state={}, nested_handlers={}){ execNested: function(page, ast, state={}, nested_handlers={}){
//* XXX waitNested here deadlocks the parser -- not sure why... /* XXX waitNested here deadlocks the parser -- not sure why...
//return this.exec(page, ast, state, nested_handlers, 'unresolved') },
return this.exec(page, ast, state, nested_handlers, 'waitNested') }, return this.exec(page, ast, state, nested_handlers, 'waitNested') },
/*/ /*/
return this.exec(page, ast, state, nested_handlers, 'unresolved') }, return this.exec(page, ast, state, nested_handlers, '') },
//*/ //*/
@ -870,7 +879,6 @@ module.BaseParser = {
} }
// XXX do we need anything else like .doc, attrs???
var Macro = var Macro =
module.Macro = module.Macro =
function(spec, func){ function(spec, func){
@ -921,23 +929,23 @@ module.parser = {
// detected. // detected.
RECURSION_STRING: '', RECURSION_STRING: '',
// XXX should this be here, in page, or both? // Filters...
filters: { //
upper: function(str){ // NOE: filters can't be named 'body', 'text', or 'clear' -- they
return str.toUpperCase() }, // will be shadowed by @filter(..)'s keyword arguments...
}, filters: {},
// Macros...
// //
// <macro>(<args>, <body>, <state>){ .. } // <macro>(<args>, <body>, <state>){ .. }
// -> undefined // -> undefined
// -> <text> // -> <text>
// -> <array> // -> <array>
// -> <iterator> // -> <iterator> XXX ???
// -> <promise>
// -> <func>(<state>) // -> <func>(<state>)
// -> ... // -> ...
// //
// XXX do we need to make .macro.__proto__ module level object???
// XXX ASYNC make these support async page getters...
macros: { macros: {
// Filter... // Filter...
@ -953,14 +961,18 @@ module.parser = {
// <filter> <filter-spec> // <filter> <filter-spec>
// | -<filter> <filter-spec> // | -<filter> <filter-spec>
// //
// XXX need a way to exclude some filters in some nested locks... // XXX should we include the global filters (current) or exclude
// XXX LOCAL_FILTERS do we combine local filters with state filters??? // them by default???
filter: function(page, args, body, state){ filter: Macro(
[['clear']],
function(page, args, body, state){
var that = this var that = this
// get filters... // get filters...
var clear = args.clear
delete args.text delete args.text
delete args.body delete args.body
delete args.clear
var filters = Object.keys(args) var filters = Object.keys(args)
// local filter... // local filter...
@ -974,17 +986,34 @@ module.parser = {
// stage III // stage III
return function(state){ return function(state){
return Promise.awaitOrRun( return Promise.awaitOrRun(
that.merge(page, body, state),
function(body){
// apply the filters... // apply the filters...
// XXX LOCAL_FILTERS combine with state.filters??? that.finalize(
return that.applyFilters(filters, body, state) }) } } page,
body,
{
...state,
filters: clear ?
filters
: [...filters, ...state.filters ?? []],
}),
function(body){
// stage III post...
// NOTE: we are protecting the result from
// global filters...
return function(){
return body } }) } }
// global filter... // global filter...
} else if(filters.length > 0){ } else if(filters.length > 0){
// NOTE: we are pushing this past the expand stage so as to
// avoid messing up all the small .exec*(..) calls used
// to handle macro attributes asn the like...
// ...but we need to do this before stage III so as not
// to race with applying local filters...
return function(state){
(state.filters = (state.filters ??= [])) (state.filters = (state.filters ??= []))
.push(...filters) } .push(...filters) } } }),
return '' },
// Args... // Args...
@ -1332,7 +1361,7 @@ module.parser = {
var nested var nested
return args.isolated ? return args.isolated ?
//this.resolve( //this.resolve(
this.merge( this.finalize(
page, page,
text, text,
nested = args.isolated == 'partial' ? nested = args.isolated == 'partial' ?
@ -1340,8 +1369,8 @@ module.parser = {
: {}) : {})
// XXX FILTER need to localize target page // XXX FILTER need to localize target page
// filters to it, somehow... // filters to it, somehow...
// XXX should this be .merge(..)??? // XXX should this be .finalize(..)???
// .merge(..) here breaks things... // .finalize(..) here breaks things...
: this.expand(page, text, state) } : this.expand(page, text, state) }
var pageHandler = var pageHandler =

View File

@ -11,6 +11,11 @@ var parser = require('../parser').parser
//--------------------------------------------------------------------- //---------------------------------------------------------------------
;(parser.filters ??= {}).upper =
function(str){
return str.toUpperCase() }
var PAGES = var PAGES =
module.exports.PAGES = { module.exports.PAGES = {
'/blank': '', '/blank': '',