mirror of
https://github.com/flynx/pWiki.git
synced 2025-12-21 10:31:39 +00:00
refactoring and cleanup....
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
08844b8ae0
commit
bdc1bbe00c
545
pwiki2.js
545
pwiki2.js
@ -480,200 +480,289 @@ object.Constructor('BasePage', {
|
|||||||
|
|
||||||
//---------------------------------------------------------------------
|
//---------------------------------------------------------------------
|
||||||
|
|
||||||
// XXX add escaping...
|
var parser =
|
||||||
var MACRO_PATTERN_STR =
|
module.parser = {
|
||||||
[[
|
// NOTE: the actual macro pattern is not stored as it depends on
|
||||||
// @macro(arg ..)
|
// the macro name list which is page dependant...
|
||||||
// XXX add support for '\)' in args...
|
// XXX add escaping...
|
||||||
'\\\\?@(?<nameInline>MACROS)\\((?<argsInline>([^)])*)\\)',
|
MACRO_PATTERN_STR: [[
|
||||||
// <macro ..> | <macro ../>
|
// @macro(arg ..)
|
||||||
// XXX revise escaped > and />
|
// XXX add support for '\)' in args...
|
||||||
'<\\s*(?<nameOpen>MACROS)(?<argsOpen>\\s+([^>/])*)?/?>',
|
'\\\\?@(?<nameInline>MACROS)\\((?<argsInline>([^)])*)\\)',
|
||||||
// </macro>
|
// <macro ..> | <macro ../>
|
||||||
'</\\s*(?<nameClose>MACROS)\\s*>',
|
// XXX revise escaped > and />
|
||||||
].join('|'), 'smig']
|
'<\\s*(?<nameOpen>MACROS)(?<argsOpen>\\s+([^>/])*)?/?>',
|
||||||
var MACRO_PATTERN
|
// </macro>
|
||||||
var MACRO_PATTERN_GROUPS =
|
'</\\s*(?<nameClose>MACROS)\\s*>',
|
||||||
'<MACROS>'.split(new RegExp(`(${ MACRO_PATTERN_STR })`)).length-2
|
].join('|'), 'smig'],
|
||||||
// XXX still buggy...
|
// NOTE: this depends on .MACRO_PATTERN_STR and thus is lazily generated...
|
||||||
var MACRO_ARGS_PATTERN =
|
__MACRO_PATTERN_GROUPS: undefined,
|
||||||
RegExp('('+[
|
get MACRO_PATTERN_GROUPS(){
|
||||||
// named args...
|
return this.__MACRO_PATTERN_GROUPS
|
||||||
'(?<nameQuoted>[a-zA-Z-_]+)\\s*=([\'"])(?<valueQuoted>([^\\3]|\\\\3)*)\\3\\s*',
|
?? (this.__MACRO_PATTERN_GROUPS =
|
||||||
'(?<nameUnquoted>[a-zA-Z-_]+)\\s*=(?<valueUnquoted>[^\\s]*)',
|
'<MACROS>'.split(new RegExp(`(${ this.MACRO_PATTERN_STR })`)).length-2) },
|
||||||
// positional args...
|
// XXX still buggy...
|
||||||
'([\'"])(?<argQuoted>([^\\8]|\\\\8)*)\\8',
|
MACRO_ARGS_PATTERN: RegExp('('+[
|
||||||
'(?<arg>[^\\s]+)',
|
// named args...
|
||||||
].join('|') +')', 'smig')
|
'(?<nameQuoted>[a-zA-Z-_]+)\\s*=([\'"])(?<valueQuoted>([^\\3]|\\\\3)*)\\3\\s*',
|
||||||
// XXX do we need basic inline and block commets a-la lisp???
|
'(?<nameUnquoted>[a-zA-Z-_]+)\\s*=(?<valueUnquoted>[^\\s]*)',
|
||||||
var COMMENT_PATTERN =
|
// positional args...
|
||||||
RegExp('('+[
|
'([\'"])(?<argQuoted>([^\\8]|\\\\8)*)\\8',
|
||||||
// <!--[pwiki[ .. ]]-->
|
'(?<arg>[^\\s]+)',
|
||||||
'<!--\\[pwiki\\[(?<uncomment>.*)\\]\\]-->',
|
].join('|') +')', 'smig'),
|
||||||
|
// XXX do we need basic inline and block commets a-la lisp???
|
||||||
|
COMMENT_PATTERN: RegExp('('+[
|
||||||
|
// <!--[pwiki[ .. ]]-->
|
||||||
|
'<!--\\[pwiki\\[(?<uncomment>.*)\\]\\]-->',
|
||||||
|
|
||||||
// <pwiki-comment> .. </pwiki-comment>
|
// <pwiki-comment> .. </pwiki-comment>
|
||||||
'<\\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 =
|
return str
|
||||||
function(str){
|
.replace(this.COMMENT_PATTERN,
|
||||||
return str
|
function(...a){
|
||||||
.replace(COMMENT_PATTERN,
|
return a.pop().uncomment
|
||||||
function(...a){
|
|| '' }) },
|
||||||
return a.pop().uncomment
|
|
||||||
|| '' }) }
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// <item> ::=
|
||||||
|
// <string>
|
||||||
|
// | {
|
||||||
|
// name: <string>,
|
||||||
|
// type: 'inline'
|
||||||
|
// | 'element'
|
||||||
|
// | 'opening'
|
||||||
|
// | 'closing',
|
||||||
|
// args: {
|
||||||
|
// <index>: <value>,
|
||||||
|
// <key>: <value>,
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
// match: <string>,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// NOTE: this internally uses macros' keys to generate the lexing pattern.
|
||||||
|
//
|
||||||
|
// XXX feels a bit ugly...
|
||||||
|
lex: function*(page, str){
|
||||||
|
str = str
|
||||||
|
?? page.raw
|
||||||
|
// NOTE: we are doing a separate pass for comments to completely
|
||||||
|
// decouple them from the base macro syntax, making them fully
|
||||||
|
// transparent...
|
||||||
|
str = this.clearComments(str)
|
||||||
|
|
||||||
//
|
// XXX should this be cached???
|
||||||
// <item> ::=
|
var MACRO_PATTERN = new RegExp(
|
||||||
// <string>
|
'('+ this.MACRO_PATTERN_STR[0]
|
||||||
// | {
|
.replace(/MACROS/g, Object.keys(page.macros).join('|')) +')',
|
||||||
// name: <string>,
|
this.MACRO_PATTERN_STR[1])
|
||||||
// type: 'inline'
|
|
||||||
// | 'element'
|
|
||||||
// | 'opening'
|
|
||||||
// | 'closing',
|
|
||||||
// args: {
|
|
||||||
// <index>: <value>,
|
|
||||||
// <key>: <value>,
|
|
||||||
// ...
|
|
||||||
// }
|
|
||||||
// match: <string>,
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// NOTE: this internally uses macros' keys to generate the lexing pattern.
|
|
||||||
//
|
|
||||||
// XXX feels a bit ugly...
|
|
||||||
var lex =
|
|
||||||
module.lex =
|
|
||||||
function*(str, macros){
|
|
||||||
// NOTE: we are doing a separate pass for comments to completely
|
|
||||||
// decouple them from the base macro syntax, making them fully
|
|
||||||
// transparent...
|
|
||||||
str = clearComments(str)
|
|
||||||
|
|
||||||
var lst = str.split(
|
var lst = str.split(MACRO_PATTERN)
|
||||||
module.MACRO_PATTERN
|
|
||||||
?? (MACRO_PATTERN = module.MACRO_PATTERN =
|
|
||||||
new RegExp(
|
|
||||||
'('+ MACRO_PATTERN_STR[0]
|
|
||||||
.replace(/MACROS/g,
|
|
||||||
Object.keys(macros).join('|')) +')',
|
|
||||||
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(..)
|
||||||
// output...
|
// output...
|
||||||
// XXX for some reason .match(..) here returns a list with a string...
|
// XXX for some reason .match(..) here returns a list with a string...
|
||||||
var cur = [...match.matchAll(MACRO_PATTERN)][0].groups
|
var cur = [...match.matchAll(MACRO_PATTERN)][0].groups
|
||||||
// special case: escaped inline macro -> keep as text...
|
// special case: escaped inline macro -> keep as text...
|
||||||
if(match.startsWith('\\@')){
|
if(match.startsWith('\\@')){
|
||||||
yield match
|
yield match
|
||||||
macro = false
|
macro = false
|
||||||
continue }
|
continue }
|
||||||
// args...
|
// args...
|
||||||
var args = {}
|
var args = {}
|
||||||
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
|
||||||
?? groups.valueUnquoted
|
?? groups.valueUnquoted
|
||||||
?? groups.argQuoted
|
?? groups.argQuoted
|
||||||
?? groups.arg }
|
?? groups.arg }
|
||||||
// macro-spec...
|
// macro-spec...
|
||||||
yield {
|
yield {
|
||||||
name: (cur.nameInline
|
name: (cur.nameInline
|
||||||
?? cur.nameOpen
|
?? cur.nameOpen
|
||||||
?? cur.nameClose)
|
?? cur.nameClose)
|
||||||
.toLowerCase(),
|
.toLowerCase(),
|
||||||
type: match[0] == '@' ?
|
type: match[0] == '@' ?
|
||||||
'inline'
|
'inline'
|
||||||
: match[1] == '/' ?
|
: match[1] == '/' ?
|
||||||
'closing'
|
'closing'
|
||||||
: match[match.length-2] == '/' ?
|
: match[match.length-2] == '/' ?
|
||||||
'element'
|
'element'
|
||||||
: 'opening',
|
: 'opening',
|
||||||
args,
|
args,
|
||||||
match,
|
match,
|
||||||
}
|
}
|
||||||
macro = false
|
macro = false
|
||||||
// normal text...
|
// normal text...
|
||||||
} else {
|
} else {
|
||||||
var str = lst.shift()
|
var str = lst.shift()
|
||||||
// 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>
|
||||||
// | {
|
// | {
|
||||||
// type: 'inline'
|
// type: 'inline'
|
||||||
// | 'element'
|
// | 'element'
|
||||||
// | 'block',
|
// | 'block',
|
||||||
// body: [
|
// body: [
|
||||||
// <item>,
|
// <item>,
|
||||||
// ...
|
// ...
|
||||||
// ],
|
// ],
|
||||||
//
|
//
|
||||||
// // rest of items are the same as for lex(..)
|
// // rest of items are the same as for lex(..)
|
||||||
// ...
|
// ...
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// 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)
|
||||||
// NOTE: we are not using for .. of .. here as it depletes the
|
lex = typeof(lex) == 'string' ?
|
||||||
// generator even if the end is not reached...
|
this.lex(page, lex)
|
||||||
while(true){
|
: lex
|
||||||
var {value, done} = lex.next()
|
// NOTE: we are not using for .. of .. here as it depletes the
|
||||||
// check if unclosed blocks remaining...
|
// generator even if the end is not reached...
|
||||||
if(done){
|
while(true){
|
||||||
if(to){
|
var {value, done} = lex.next()
|
||||||
|
// check if unclosed blocks remaining...
|
||||||
|
if(done){
|
||||||
|
if(to){
|
||||||
|
throw new Error(
|
||||||
|
'Premature end of unpit: Expected closing "'+ to +'"') }
|
||||||
|
return }
|
||||||
|
// assert nesting rules...
|
||||||
|
if(page.macros[value.name] instanceof Array
|
||||||
|
&& page.macros[value.name].includes(to)){
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Premature end of unpit: Expected closing "'+ to +'"') }
|
'Unexpected "'+ value.name +'" macro'
|
||||||
return }
|
+(to ?
|
||||||
// assert nesting rules...
|
' in "'+to+'"'
|
||||||
if(macros[value.name] instanceof Array
|
: '')) }
|
||||||
&& macros[value.name].includes(to)){
|
// open block...
|
||||||
throw new Error(
|
if(value.type == 'opening'){
|
||||||
'Unexpected "'+ value.name +'" macro'
|
value.body = [...this.group(page, lex, value.name)]
|
||||||
+(to ?
|
value.type = 'block'
|
||||||
' in "'+to+'"'
|
yield value
|
||||||
: '')) }
|
continue
|
||||||
// open block...
|
// close block...
|
||||||
if(value.type == 'opening'){
|
} else if(value.type == 'closing'){
|
||||||
value.body = [...group(lex, value.name, macros)]
|
if(value.name != to){
|
||||||
value.type = 'block'
|
throw new Error('Unexpected closing "'+ value.name +'"') }
|
||||||
yield value
|
// NOTE: we are intentionally not yielding the value here...
|
||||||
continue
|
return }
|
||||||
// close block...
|
yield value } },
|
||||||
} else if(value.type == 'closing'){
|
|
||||||
if(value.name != to){
|
// Expand macros...
|
||||||
throw new Error('Unexpected closing "'+ value.name +'"') }
|
//
|
||||||
// NOTE: we are intentionally not yielding the value here...
|
expand: function*(page, ast, state={}){
|
||||||
return }
|
ast = ast == null ?
|
||||||
yield value } }
|
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}) },
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user