diff --git a/v3/bootstrap/Doc/Macros.md b/v3/bootstrap/Doc/Macros.md
index be3aa44..2b10594 100755
--- a/v3/bootstrap/Doc/Macros.md
+++ b/v3/bootstrap/Doc/Macros.md
@@ -260,6 +260,7 @@ If `isolated` is given, prevent slots from included page from affecting
the including page.
**Example:**
+
`/some/page`:
```
diff --git a/v3/pwiki/parser.js b/v3/pwiki/parser.js
index 0057f46..fc9db21 100644
--- a/v3/pwiki/parser.js
+++ b/v3/pwiki/parser.js
@@ -442,7 +442,6 @@ module.BaseParser = {
// .body -> .args.body -> .args.text
//
// NOTE: this internaly uses .macros to check for propper nesting
- //group: function*(page, lex, to=false){
group: function*(lex, to=false, context){
lex = typeof(lex) != 'object' ?
this.lex(lex)
@@ -526,6 +525,9 @@ module.BaseParser = {
// normal value...
yield value } },
+ // NOTE: the output of this can be safely cached, it does not depend
+ // on anything external and as long as the code stays the same
+ // this will not change.
// XXX do we need a pre-parse stage???
// - expand local macros
// - collect links
@@ -558,11 +560,20 @@ module.BaseParser = {
// waitAll: | null,
//
// // wait for all...
+ // // NOTE: this is set just before .expand(..) returns and is
+ // // not available during the expansion process.
// wait: | null,
//
// ...
// }
//
+ // NOTE: .waitNested and .waitAll are "live", i.e. while expanding,
+ // at any given time they contain the promise of the last async
+ // element upto the point of read. .wait however is set at the
+ // end of .expand(..), i.e. it has no meaning until .expand(..)
+ // finishes, and represents the last .waitAll but will return
+ // the whole expanded ast.
+ //
//
// XXX the parser is always sync
// - macros always return sync but can be resolved or not resolved
@@ -819,236 +830,6 @@ module.BaseParser = {
// ...should filters be client-side only??
parseNested: function(page, ast, state={}, nested_handlers={}){
return this.parse(page, ast, state, nested_handlers, 'waitNestedi') },
-
-
-
- // Expand macros...
- //
- // ::= [ - , .. ]
- //
- ::=
- //
- // |
- // |
- // | { skip: true, ... }
- // | { data: }
- // |
- //
- // XXX macros: we are mixing up ast state and parse state...
- // one should only be used for parsing and be forgotten after
- // the ast is constructed the other should be part of the ast...
- // XXX DOC inconsistent await behavior...
- // in var macro, as an example, there are two paths: the assign and
- // the get, the assign calls .parse(..) and awaits for the result
- // while the get path is "sync", this results in the first exec path
- // getting pushed to the next execution frame while the second is run
- // in sync with the caller, here is a simplified demo:
- // console.log(1)
- // // note that we are NOTE await'ing for the function here...
- // (async function f(){
- // console.log(2)})()
- // console.log(3)
- // -> prints 1, 2, 3
- // and:
- // console.log(1)
- // (async function f(){
- // // note the await -- this is the only difference...
- // console.log(await 2)})()
- // console.log(3)
- // -> prints 1, 3, 2
- // this could both be a bug or a feature depending on how you look
- // at it, but it makes promise sequencing very unpredictable...
- // ...another problem here is that in the var macro, simply adding
- // an await of something does not fix the issue, we need to await
- // for something significant -- await this.parse('') works, while
- // await vars[name] does not -- to the contrary of the above example...
- /* XXX
- expand: function(page, ast, state={}){
- var that = this
- ast = ast == null ?
- Promise.awaitOrRun(
- page.raw,
- function(raw){
- return that.group(page, raw ?? '') })
- : typeof(ast) != 'object' ?
- this.group(page, ast)
- : ast instanceof types.Generator ?
- ast
- : ast.iter()
-
- // NOTE this must execute sequentially for things that depend on
- // lexical scope not to get lost in the mess...
- return Promise.seqiter(ast,
- function(value){
- // text block...
- if(typeof(value) == 'string'){
- return value }
- // macro...
- var {name, args, body} = value
- // nested macro -- skip...
- if(typeof(that.macros[name]) != 'function'){
- return {...value, skip: true} }
- // macro call...
- return Promise.awaitOrRun(
- that.callMacro(page, name, args, body, state),
- function(res){
- res = res ?? ''
- // result...
- if(res instanceof Array
- || that.macros[name] instanceof types.Generator){
- return res
- } else {
- return [res] } }) },
- function(err){
- console.error(err)
- return [page.parse(
- // XXX add line number and page path...
- '@include("./ParseError'
- +':path='
- // XXX use pwpath.encodeElem(..) ???
- + page.path
- +':msg='
- + err.message
- // quote html stuff...
- .replace(/&/g, '&')
- .replace(//g, '>')
- // quote argument syntax...
- .replace(/["']/g, function(c){
- return '%'+ c.charCodeAt().toString(16) })
- .replace(/:/g, ':')
- .replace(/=/g, '=')
- +'")')] })
- .sync() },
- //*/
-
-
- // recursively resolve and enumerate the ast...
- //
- // ::= [
- , .. ]
- //
- ::=
- //
- // | { data: }
- // |
- //
- // (state)
- // ->
- //
- //
- // NOTE: (..) is called in the context of page...
- //
- // XXX should this also resolve e.data???
- /* XXX
- resolve: function(page, ast, state={}){
- var that = this
- ast = ast
- ?? this.expand(page, null, state)
- ast = typeof(ast) != 'object' ?
- this.expand(page, ast, state)
- : ast
- // XXX .awaitOrRun(..) will check inside the input array for promises, do
- // we need to do this???
- ast = Promise.awaitOrRun(
- ast,
- function(ast){
- var async_content = false
- return ast
- .map(function(e){
- // expand delayed sections...
- e = typeof(e) == 'function' ?
- e.call(page, state)
- : e
- // promise...
- if(e instanceof Promise){
- async_content = true
- return e
- // expand arrays...
- } else if(e instanceof Array
- || e instanceof types.Generator){
- return that.resolve(page, e, state)
- // data -- unwrap content...
- } else if(e instanceof Object && 'data' in e){
- var res = Promise.awaitOrRun(
- that.resolve(page, e.data, state),
- function(e){
- return { data: e } })
- res instanceof Promise
- && (async_content = true)
- return res
- // skipped items...
- } else if(e instanceof Object && e.skip){
- return []
- } else {
- return [e] } })
- // NOTE: if we still have promises in the ast, wrap the
- // whole thing in a promise...
- .run(function(){
- return async_content ?
- Promise.iter(this)
- : this })
- .flat() })
- return ast instanceof Promise ?
- // keep the API consistently array-like...
- ast.iter()
- : ast },
- //*/
-
- // Fully parse a page...
- //
- // This runs in two stages:
- // - resolve the page
- // - lex the page -- .lex(..)
- // - group block elements -- .group(..)
- // - expand macros -- .expand(..)
- // - resolve ast -- .resolve(..)
- // - apply filters
- //
- // NOTE: this has to synchronize everything between stage 1 (up to
- // and including expand) and stage 2 (post-handlers, filters, ...)
- // because the former need a fully loaded and expanded page if
- // we want to do this in 2 stages and not 3...
- // XXX might be fun to try a load-and-tweak approach the first
- // version used -- i.e. setting placeholders and replacing
- // them on demand rather than on encounter (as is now), e.g.
- // a slot when loaded will replace the prior occurrences...
- //
- // XXX add a special filter to clear pending filters... (???)
- /* XXX
- parse: function(page, ast, state={}){
- var that = this
- return this.resolve(page, ast, state)
- // filters...
- .map(function(section){
- // normalize types...
- section =
- typeof(section) == 'number' ?
- section + ''
- : section == null ?
- ''
- : section
- return (
- // expand section...
- typeof(section) != 'string' ?
- section.data
- // global filters...
- : state.filters ?
- that.normalizeFilters(state.filters)
- .reduce(function(res, filter){
- // unknown filter...
- // NOTE: we try not to break on user errors
- // if we can help it...
- if(page.filters[filter] == null){
- console.warn(
- '.parse(..): unsupported filter: '+ filter)
- return res }
- // NOTE: if a filter returns falsy then it
- // will have no effect on the result...
- return page.filters[filter].call(page, res)
- ?? res }, section)
- // no global filters...
- : section ) })
- .flat()
- .join('') },
- //*/
}
@@ -1088,9 +869,6 @@ function(macro){
-
-
-
// XXX RENAME...
// ...this is more of an expander/executer...
// ...might be a good idea to also do a check without executing...
@@ -1511,6 +1289,8 @@ module.parser = {
state.recursive =
args.recursive
?? state.recursive
+ // XXX get default...
+ recursive ??= ''
var base = page.basepath
// XXX we do this before or after we parse???
@@ -1523,10 +1303,7 @@ module.parser = {
// XXX will this work for @source(..)???
var stack = state.include_stack ??= []
if(stack.includes(src)){
- if(recursive){
- return that.expand(page, recursive, state) }
- throw new Error('Recursion:\n\t'+
- [...stack, src].join('\n\t\t-> ')) }
+ return that.expand(page, recursive, state) }
stack.push(src)
var cache = state.cache ??= {}
@@ -1559,12 +1336,12 @@ module.parser = {
: this.expand(page, text, state) }
var pageHandler =
- function(text){
+ function([path, text]){
var content_handled = false
return body ?
// handle body / ...
Promise.awaitOrRun(
- that.expand(page, body, state,
+ that.expand(page.get(path), body, state,
{ content: function(){
content_handled = true
return text = handler.call(that, page, src, text, state) } }),
@@ -1590,8 +1367,12 @@ module.parser = {
// get and run things...
return Promise.awaitOrRun(
+ page.get(src).matched,
page.get(src).raw,
- function(pages){
+ function(paths, pages){
+ paths = paths instanceof Array ?
+ paths
+ : [paths]
pages = pages instanceof Array ?
pages
: [pages]
@@ -1601,7 +1382,9 @@ module.parser = {
.iter(
that.joinBlocks(
page,
- pages.map(pageHandler),
+ Array
+ .zip(paths, pages)
+ .map(pageHandler),
args.join,
state))
.flat()
@@ -1718,6 +1501,7 @@ module.parser = {
filters.splice(filters.length, 0, ...Object.keys(args)) },
//*/
+ // expand the body in the context of a page...
//
// > ..
//
@@ -1759,6 +1543,12 @@ module.parser = {
// XXX SORT sorting not implemented yet...
// XXX UPDATE...
macro: Macro(
+ ['name', 'src', 'sort', 'text', 'join', 'else',
+ ['strict', 'isolated', 'inheritmacros', 'inheritvars']],
+ lazy(
+ function(page, args, body, state){
+ })),
+ _macro: Macro(
['name', 'src', 'sort', 'text', 'join', 'else',
['strict', 'isolated', 'inheritmacros', 'inheritvars']],
async function*(args, body, state){
diff --git a/v3/pwiki/test/parser.js b/v3/pwiki/test/parser.js
index 864ac54..7ad83ab 100755
--- a/v3/pwiki/test/parser.js
+++ b/v3/pwiki/test/parser.js
@@ -53,8 +53,43 @@ module.exports.P = {
get basepath(){
return path.dirname(this.path ?? '') },
+ get matched(){
+ var path_action_pattern = /\/path\/?/
+ var path = this.path.replace(path_action_pattern, '')
+ var path_action = path != this.path
+
+ if(path.includes('*')){
+ var that = this
+ var re = new RegExp(path
+ .replace(/\//, '\\/')
+ .replace(/\*/g, '[^\\/]+'))
+ return Object.keys(this.__pages__)
+ .map(function(p){
+ return re.test(p) ?
+ [path_action ?
+ p +'/path'
+ :p]
+ : [] })
+ .flat() }
+ return [this.path] },
+
+ // * -> a promise or a list of promises???
get raw(){
- return this.__pages__[this.path] ?? '' },
+ var res = []
+ var path_action_pattern = /\/path\/?/
+ var path_action = this.path.match(path_action_pattern)
+
+ for(var path of this.matched){
+ res.push(
+ path_action ?
+ path.replace(path_action_pattern, '')
+ : this.__pages__[path]) }
+
+ return res.length == 1 ?
+ res[0]
+ : res },
+
+ // XXX should this return an arrya for a multi-match???
get text(){
return this.__parser__.parse(this, this.raw, {}) },
@@ -194,12 +229,6 @@ test.Setups({
return i +' | @slot(slot shown)' }),
' | value' ]} },
- // XXX var...
- // XXX
-
- // XXX arg...
- // XXX
-
// include...
// XXX see inside...
include: function(assert, path='/blank', expected){
@@ -239,13 +268,23 @@ test.Setups({
'original overloaded',
],
} },
+ /* XXX
+ include_partial_isolated: function(assert){
+ return {
+ page: P,
+ code:[
+ '@include(isolated="partial" /isolated) @slot(slot overloaded)',
+ 'original overloaded',
+ ],
+ } },
+ //*/
// content...
- include_content: function(assert){
+ include_content: function(assert, path='/page'){
return {
page: P,
code: [
- '[[ ]]',
- '[[ '+ P.get('/page').raw +' ]]', ], } },
+ '[[ ]]',
+ '[[ '+ P.get(path).raw +' ]]', ], } },
// XXX
// recursion...
@@ -268,6 +307,22 @@ test.Setups({
// quote...
// for inline quoting see: test.Modifiers.quote
// XXX
+ quote_content: function(assert, path='/isolated'){
+ return {
+ page: P,
+ code: [
+ '[[ ]]
',
+ '[[ '+ P.get(path).raw +' ]]', ], } },
+
+ // XXX macro...
+ // XXX
+
+ // XXX var...
+ // XXX
+
+ // XXX arg...
+ // XXX
+
})