refactoring and cleanup....

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2022-04-26 16:20:25 +03:00
parent 08844b8ae0
commit bdc1bbe00c

259
pwiki2.js
View File

@ -480,9 +480,12 @@ object.Constructor('BasePage', {
//--------------------------------------------------------------------- //---------------------------------------------------------------------
var parser =
module.parser = {
// NOTE: the actual macro pattern is not stored as it depends on
// the macro name list which is page dependant...
// XXX add escaping... // XXX add escaping...
var MACRO_PATTERN_STR = MACRO_PATTERN_STR: [[
[[
// @macro(arg ..) // @macro(arg ..)
// XXX add support for '\)' in args... // XXX add support for '\)' in args...
'\\\\?@(?<nameInline>MACROS)\\((?<argsInline>([^)])*)\\)', '\\\\?@(?<nameInline>MACROS)\\((?<argsInline>([^)])*)\\)',
@ -491,23 +494,24 @@ var MACRO_PATTERN_STR =
'<\\s*(?<nameOpen>MACROS)(?<argsOpen>\\s+([^>/])*)?/?>', '<\\s*(?<nameOpen>MACROS)(?<argsOpen>\\s+([^>/])*)?/?>',
// </macro> // </macro>
'</\\s*(?<nameClose>MACROS)\\s*>', '</\\s*(?<nameClose>MACROS)\\s*>',
].join('|'), 'smig'] ].join('|'), 'smig'],
var MACRO_PATTERN // NOTE: this depends on .MACRO_PATTERN_STR and thus is lazily generated...
var MACRO_PATTERN_GROUPS = __MACRO_PATTERN_GROUPS: undefined,
'<MACROS>'.split(new RegExp(`(${ MACRO_PATTERN_STR })`)).length-2 get MACRO_PATTERN_GROUPS(){
return this.__MACRO_PATTERN_GROUPS
?? (this.__MACRO_PATTERN_GROUPS =
'<MACROS>'.split(new RegExp(`(${ this.MACRO_PATTERN_STR })`)).length-2) },
// XXX still buggy... // XXX still buggy...
var MACRO_ARGS_PATTERN = MACRO_ARGS_PATTERN: RegExp('('+[
RegExp('('+[
// named args... // named args...
'(?<nameQuoted>[a-zA-Z-_]+)\\s*=([\'"])(?<valueQuoted>([^\\3]|\\\\3)*)\\3\\s*', '(?<nameQuoted>[a-zA-Z-_]+)\\s*=([\'"])(?<valueQuoted>([^\\3]|\\\\3)*)\\3\\s*',
'(?<nameUnquoted>[a-zA-Z-_]+)\\s*=(?<valueUnquoted>[^\\s]*)', '(?<nameUnquoted>[a-zA-Z-_]+)\\s*=(?<valueUnquoted>[^\\s]*)',
// positional args... // positional args...
'([\'"])(?<argQuoted>([^\\8]|\\\\8)*)\\8', '([\'"])(?<argQuoted>([^\\8]|\\\\8)*)\\8',
'(?<arg>[^\\s]+)', '(?<arg>[^\\s]+)',
].join('|') +')', 'smig') ].join('|') +')', 'smig'),
// XXX do we need basic inline and block commets a-la lisp??? // XXX do we need basic inline and block commets a-la lisp???
var COMMENT_PATTERN = COMMENT_PATTERN: RegExp('('+[
RegExp('('+[
// <!--[pwiki[ .. ]]--> // <!--[pwiki[ .. ]]-->
'<!--\\[pwiki\\[(?<uncomment>.*)\\]\\]-->', '<!--\\[pwiki\\[(?<uncomment>.*)\\]\\]-->',
@ -515,19 +519,18 @@ var COMMENT_PATTERN =
'<\\s*pwiki-comment[^>]*>.*<\\/\\s*pwiki-comment\\s*>', '<\\s*pwiki-comment[^>]*>.*<\\/\\s*pwiki-comment\\s*>',
// <pwiki-comment .. /> // <pwiki-comment .. />
'<\\s*pwiki-comment[^\\/>]*\\/>', '<\\s*pwiki-comment[^\\/>]*\\/>',
].join('|') +')', 'smig') ].join('|') +')', 'smig'),
// General parser API...
//
var clearComments = clearComments: function(str){
module.clearComments =
function(str){
return str return str
.replace(COMMENT_PATTERN, .replace(this.COMMENT_PATTERN,
function(...a){ function(...a){
return a.pop().uncomment return a.pop().uncomment
|| '' }) } || '' }) },
// //
// <item> ::= // <item> ::=
@ -550,27 +553,26 @@ function(str){
// NOTE: this internally uses macros' keys to generate the lexing pattern. // NOTE: this internally uses macros' keys to generate the lexing pattern.
// //
// XXX feels a bit ugly... // XXX feels a bit ugly...
var lex = lex: function*(page, str){
module.lex = str = str
function*(str, macros){ ?? page.raw
// NOTE: we are doing a separate pass for comments to completely // NOTE: we are doing a separate pass for comments to completely
// decouple them from the base macro syntax, making them fully // decouple them from the base macro syntax, making them fully
// transparent... // transparent...
str = clearComments(str) str = this.clearComments(str)
var lst = str.split( // XXX should this be cached???
module.MACRO_PATTERN var MACRO_PATTERN = new RegExp(
?? (MACRO_PATTERN = module.MACRO_PATTERN = '('+ this.MACRO_PATTERN_STR[0]
new RegExp( .replace(/MACROS/g, Object.keys(page.macros).join('|')) +')',
'('+ MACRO_PATTERN_STR[0] this.MACRO_PATTERN_STR[1])
.replace(/MACROS/g,
Object.keys(macros).join('|')) +')', var lst = str.split(MACRO_PATTERN)
MACRO_PATTERN_STR[1])))
var macro = false var macro = false
while(lst.length > 0){ while(lst.length > 0){
if(macro){ if(macro){
var match = lst.splice(0, MACRO_PATTERN_GROUPS)[0] var match = lst.splice(0, this.MACRO_PATTERN_GROUPS)[0]
// NOTE: we essentially are parsing the detected macro a // NOTE: we essentially are parsing the detected macro a
// second time here, this gives us access to named groups // second time here, this gives us access to named groups
// avoiding maintaining match indexes with the .split(..) // avoiding maintaining match indexes with the .split(..)
@ -587,7 +589,7 @@ function*(str, macros){
var i = -1 var i = -1
for(var {groups} for(var {groups}
of (cur.argsInline ?? cur.argsOpen ?? '') of (cur.argsInline ?? cur.argsOpen ?? '')
.matchAll(MACRO_ARGS_PATTERN)){ .matchAll(this.MACRO_ARGS_PATTERN)){
i++ i++
args[groups.nameQuoted ?? groups.nameUnquoted ?? i] = args[groups.nameQuoted ?? groups.nameUnquoted ?? i] =
groups.valueQuoted groups.valueQuoted
@ -617,9 +619,9 @@ function*(str, macros){
// skip empty strings from output... // skip empty strings from output...
if(str != ''){ if(str != ''){
yield str } yield str }
macro = true } } } macro = true } } },
// Group block elements...
// //
// <item> ::= // <item> ::=
// <string> // <string>
@ -640,9 +642,12 @@ function*(str, macros){
// NOTE: this internaly uses macros to check for propper nesting // NOTE: this internaly uses macros to check for propper nesting
// //
// XXX normalize lex to be a generator (???) // XXX normalize lex to be a generator (???)
var group = group: function*(page, lex, to=false){
module.group = lex = lex
function*(lex, to=false, macros){ ?? this.lex(page)
lex = typeof(lex) == 'string' ?
this.lex(page, lex)
: lex
// NOTE: we are not using for .. of .. here as it depletes the // NOTE: we are not using for .. of .. here as it depletes the
// generator even if the end is not reached... // generator even if the end is not reached...
while(true){ while(true){
@ -654,8 +659,8 @@ function*(lex, to=false, macros){
'Premature end of unpit: Expected closing "'+ to +'"') } 'Premature end of unpit: Expected closing "'+ to +'"') }
return } return }
// assert nesting rules... // assert nesting rules...
if(macros[value.name] instanceof Array if(page.macros[value.name] instanceof Array
&& macros[value.name].includes(to)){ && page.macros[value.name].includes(to)){
throw new Error( throw new Error(
'Unexpected "'+ value.name +'" macro' 'Unexpected "'+ value.name +'" macro'
+(to ? +(to ?
@ -663,7 +668,7 @@ function*(lex, to=false, macros){
: '')) } : '')) }
// open block... // open block...
if(value.type == 'opening'){ if(value.type == 'opening'){
value.body = [...group(lex, value.name, macros)] value.body = [...this.group(page, lex, value.name)]
value.type = 'block' value.type = 'block'
yield value yield value
continue continue
@ -673,7 +678,91 @@ function*(lex, to=false, macros){
throw new Error('Unexpected closing "'+ value.name +'"') } throw new Error('Unexpected closing "'+ value.name +'"') }
// NOTE: we are intentionally not yielding the value here... // NOTE: we are intentionally not yielding the value here...
return } return }
yield value } } yield value } },
// Expand macros...
//
expand: function*(page, ast, state={}){
ast = ast == null ?
this.group(page)
: typeof(ast) == 'string' ?
this.group(page, ast)
: ast instanceof types.Generator ?
ast
: ast.iter()
while(true){
var {value, done} = ast.next()
if(done){
return }
// text block...
if(typeof(value) == 'string'){
yield value
continue }
// macro...
var {name, args, body} = value
var res =
page.macros[name].call(page, args, body, state)
?? ''
if(res instanceof Array
|| page.macros[name] instanceof types.Generator){
yield* res
} else {
yield res } } },
// Fully parse a page...
//
// This runs in two stages:
// - expand the page text
// - apply filters
//
// XXX add a special filter to clear pending filters... (???)
// XXX rename???
parse: function(page, ast, state={}){
var that = this
// XXX should we handle strings as input???
ast = ast
?? this.expand(page, null, state)
var _normalize = function(filters){
var skip = new Set()
return filters
.flat()
.tailUnique()
.filter(function(filter){
filter[0] == '-'
&& skip.add(filter.slice(1))
return filter[0] != '-' })
.filter(function(filter){
return !skip.has(filter) })}
return [...ast]
.map(function(section){
var filters = state.filters
// nested filters...
if(typeof(section) != 'string'){
state.filters = _normalize(section.filters)
var res = [...that.parse(page, section.data, state)]
.flat()
.join('')
state.filters = filters
return res
// local filters...
} else {
return filters ?
_normalize(filters)
.reduce(function(res, filter){
if(page.filters[filter] == null){
throw new Error(
'.parse(..): unsupported filter: '+ filter) }
return page.filters[filter].call(page, res)
?? res }, section)
: section } })
.flat()
.join('') },
}
// XXX PATH_VARS need to handle path variables... // XXX PATH_VARS need to handle path variables...
@ -770,7 +859,7 @@ object.Constructor('Page', BasePage, {
// serialize the block for later processing... // serialize the block for later processing...
var res = { var res = {
filters: state.filters, filters: state.filters,
data: [...this.expand(body, state)], data: [...this.parser.expand(this, body, state)],
} }
// restore global filters... // restore global filters...
state.filters = parent_filters state.filters = parent_filters
@ -789,87 +878,9 @@ object.Constructor('Page', BasePage, {
'else': ['macro'], 'else': ['macro'],
}, },
parse: function*(str){ parser: module.parser,
yield* group( parse: function(state={}){
lex( return this.parser.parse(this, null, state) },
str
?? this.raw,
this.macros),
false,
this.macros) },
expand: function*(ast, state={}){
ast = ast
?? this.parse()
ast = ast instanceof types.Generator ?
ast
: ast.iter()
while(true){
var {value, done} = ast.next()
if(done){
return }
// text block...
if(typeof(value) == 'string'){
yield value
continue }
// macro...
var {name, args, body} = value
var res =
this.macros[name].call(this, args, body, state)
?? ''
if(res instanceof Array
|| this.macros[name] instanceof types.Generator){
yield* res
} else {
yield res } } },
// XXX add a special filter to clear pending filters... (???)
// XXX rename and use this instead of render...
postProcess: function(ast, state={}){
var that = this
ast = ast
?? this.expand(null, state)
var _normalize = function(filters){
var skip = new Set()
return filters
.flat()
.tailUnique()
.filter(function(filter){
filter[0] == '-'
&& skip.add(filter.slice(1))
return filter[0] != '-' })
.filter(function(filter){
return !skip.has(filter) })}
return [...ast]
.map(function(section){
var filters = state.filters
// nested filters...
if(typeof(section) != 'string'){
state.filters = _normalize(section.filters)
var res = [...that.postProcess(section.data, state)]
.flat()
.join('')
state.filters = filters
return res
// local filters...
} else {
return filters ?
_normalize(filters)
.reduce(function(res, filter){
if(that.filters[filter] == null){
throw new Error(
'.postProcess(..): unsupported filter: '+ filter) }
return that.filters[filter].call(that, res)
?? res }, section)
: section } })
.flat()
.join('') },
// XXX do we need boht this and .postProcess(..) ???
render: function(state={}){
return this.postProcess(null, state) },
// raw page text... // raw page text...
@ -892,7 +903,7 @@ object.Constructor('Page', BasePage, {
// XXX FUNC handle functions as pages... // XXX FUNC handle functions as pages...
// XXX need to support pattern pages... // XXX need to support pattern pages...
get text(){ get text(){
return this.render() }, return this.parse() },
set text(value){ set text(value){
this.store.update(this.location, {text: value}) }, this.store.update(this.location, {text: value}) },