More work on array-based parser.
This commit is contained in:
parent
c869c9c5d5
commit
dce436558e
@ -1,121 +1,79 @@
|
|||||||
import { createLineData, useDocument } from '@terrace/core'
|
import { useDocument } from '@terrace/core'
|
||||||
import { createStringReader } from '@terrace/core/readers/js-string'
|
import { createStringReader } from '@terrace/core/readers/js-string'
|
||||||
|
|
||||||
export const SYMBOLS = {
|
|
||||||
TAIL: Symbol('TAIL'),
|
|
||||||
UNMATCHED: Symbol('UNMATCHED')
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BASE_MACROS = {
|
|
||||||
// string ({ line }) {
|
|
||||||
// return tail(line).toString()
|
|
||||||
// },
|
|
||||||
// number ({ line }) {
|
|
||||||
// const num = +tail(line)
|
|
||||||
// if (isNaN(num) || line === '') return
|
|
||||||
// return num
|
|
||||||
// },
|
|
||||||
// primitive (args) {
|
|
||||||
// const num = macros.number(args)
|
|
||||||
// return num !== undefined ? num : macros.string(args)
|
|
||||||
// },
|
|
||||||
// any (args) {
|
|
||||||
// const macro = args.macros[args.head]
|
|
||||||
// if (macro) return macro(args)
|
|
||||||
// const numResult = args.macros.number(args)
|
|
||||||
// if (numResult !== undefined) return numResult
|
|
||||||
// args.tail = args.line
|
|
||||||
// return args.macros.string(args)
|
|
||||||
// },
|
|
||||||
// scope ({ addScope, head, tail, line }, definition) {
|
|
||||||
// return addScope({ definition, head, tail, line })
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTail(key, macro) {
|
|
||||||
return args => {
|
|
||||||
const scope = args.scope
|
|
||||||
const result = macro(args)
|
|
||||||
if (result === undefined) return
|
|
||||||
if (!scope[key]) scope[key] = []
|
|
||||||
scope[key].push(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getText(key, macro) {
|
|
||||||
return (args) => {
|
|
||||||
const scope = args.scope
|
|
||||||
const result = macro({...args, tail: args.line })
|
|
||||||
if (result === undefined) return
|
|
||||||
if (!scope[key]) scope[key] = []
|
|
||||||
scope[key].push(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getUnmatched(macro) {
|
|
||||||
return (args) => {
|
|
||||||
const key = args.head
|
|
||||||
const scope = args.scope
|
|
||||||
const result = macro(args)
|
|
||||||
if (result === undefined) return
|
|
||||||
if (!scope[key]) scope[key] = []
|
|
||||||
scope[key].push(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isMacro (macro) {
|
|
||||||
return (args) => args.macros[macro](args)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCollection (macro) {
|
|
||||||
return (args) => ({ [args.head]: macro(args) })
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isScope (definition) {
|
|
||||||
return (args) => args.macros.scope(args, definition)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function parse(lines, schema) {
|
export async function parse(lines, schema) {
|
||||||
const doc = useDocument(createStringReader(lines))
|
const doc = useDocument(createStringReader(lines))
|
||||||
|
|
||||||
const macros = schema.macros
|
function createScope(line, matchers) {
|
||||||
const scopes = [[]]
|
return {
|
||||||
|
block: [line, []],
|
||||||
function addScope() {
|
handlers: [],
|
||||||
const scope = []
|
matchers: matchers
|
||||||
scopes[scopes.length] = scope
|
}
|
||||||
return scope
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const macros = schema.macros
|
||||||
|
|
||||||
|
const scopes = [createScope('root', {
|
||||||
|
title ({ addHandler }) {
|
||||||
|
addHandler(scope => [scope[0].split(' ')[0], scope[0].split(' ').slice(1).join(' ')])
|
||||||
|
},
|
||||||
|
options ({ addMatcher }) {
|
||||||
|
addMatcher({
|
||||||
|
parameter1({ addHandler }) {
|
||||||
|
addHandler(scope => [scope[0].split(' ')[0], +scope[0].split(' ').slice(1).join(' ')])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})]
|
||||||
|
|
||||||
let ended = false
|
let ended = false
|
||||||
let lastLevel = 0
|
|
||||||
while (!ended) {
|
while (!ended) {
|
||||||
ended = await doc.next()
|
ended = await doc.next()
|
||||||
if (ended) break;
|
if (ended) break;
|
||||||
const level = doc.level()
|
const level = doc.level() + 1
|
||||||
const scope = scopes[level] || []
|
|
||||||
scopes.length = level + 1
|
|
||||||
|
|
||||||
let entry = [doc.line()]
|
// Trigger macros for closed scopes.
|
||||||
|
for (let i = scopes.length - 1; i >= level; --i) {
|
||||||
|
const scope = scopes[i]
|
||||||
|
const parent = scopes[i - 1]
|
||||||
|
|
||||||
Object.keys(schema.root).forEach(key => {
|
// Remove empty arrays from scopes without children.
|
||||||
if (entry[0] === key || entry[0].startsWith(`${key} `)) {
|
if (scope.block[1] && !scope.block[1].length) scope.block.length = 1
|
||||||
entry = schema.root[key]({ entry, macros })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!scopes[level]) {
|
// Postprocess scope with relevant macros.
|
||||||
scopes[level] = scope
|
scope.block = scope.handlers.reduce((block, handler) => {
|
||||||
scopes[level - 1].at(-1)[1] = scopes[level]
|
return handler(block)
|
||||||
}
|
}, [...scope.block])
|
||||||
scope.push(entry)
|
|
||||||
|
|
||||||
if (lastLevel == level) {
|
// Add to parent block.
|
||||||
console.log('DECREASE: ', doc.line(), scope)
|
parent.block[1].push(scope.block)
|
||||||
}
|
}
|
||||||
|
|
||||||
lastLevel = level
|
// Reset scope length to avoid dangling scopes.
|
||||||
|
scopes.length = level
|
||||||
|
|
||||||
|
// Define current scope
|
||||||
|
const scope = scopes[level] = createScope(doc.line(), {})
|
||||||
|
// Determine parent for this scope.
|
||||||
|
const parent = scopes[level - 1]
|
||||||
|
|
||||||
|
// Add a postprocess handler for this scope.
|
||||||
|
// Ie. When we leave the scope, rewrite it using this handler.
|
||||||
|
function addHandler(handler) {
|
||||||
|
scope.handlers.push(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add matchers for this scope's children.
|
||||||
|
function addMatcher(definition) {
|
||||||
|
scope.matchers = definition
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run matching matchers for this scope.
|
||||||
|
if (parent.matchers[doc.head()]) {
|
||||||
|
parent.matchers[doc.head()]({ addHandler, addMatcher })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return scopes[0]
|
return scopes[0].block
|
||||||
}
|
}
|
@ -1,102 +1,6 @@
|
|||||||
import { SYMBOLS, parse, isScope, isMacro, getTail, getText, getUnmatched, isCollection } from './core.js'
|
import { parse } from './core.js'
|
||||||
|
|
||||||
const schemaTCE = `
|
|
||||||
macros
|
|
||||||
primitive match number string
|
|
||||||
section
|
|
||||||
pos number tail
|
|
||||||
content string unmatched
|
|
||||||
position number
|
|
||||||
options
|
|
||||||
parameter1 number
|
|
||||||
parameter2 string
|
|
||||||
literal unmatched string
|
|
||||||
unmatched primitive
|
|
||||||
|
|
||||||
root
|
|
||||||
title string
|
|
||||||
options options collection
|
|
||||||
subsection section
|
|
||||||
list
|
|
||||||
- string array
|
|
||||||
collection
|
|
||||||
section collection
|
|
||||||
collection2
|
|
||||||
unmatched collection
|
|
||||||
`
|
|
||||||
|
|
||||||
function head(line) {
|
|
||||||
return line.split(' ')[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
function tail(line) {
|
|
||||||
return line.split(' ').slice(1).join(' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
function chain(...macroNames) {
|
|
||||||
return (args) => {
|
|
||||||
for (const macroName of macroNames) {
|
|
||||||
if (args.macros[macroName]) args.entry = args.macros[macroName](args)
|
|
||||||
}
|
|
||||||
return args.entry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const schema = {
|
const schema = {
|
||||||
macros: {
|
|
||||||
tail({ entry }) {
|
|
||||||
return [head(entry[0]), tail(entry[0])]
|
|
||||||
},
|
|
||||||
string({ entry }) {
|
|
||||||
return [entry[0], entry[1].toString()]
|
|
||||||
},
|
|
||||||
number({ entry }) {
|
|
||||||
const num = +entry[1]
|
|
||||||
if (isNaN(num) || entry[1] === '') return entry
|
|
||||||
return [entry[0], num]
|
|
||||||
},
|
|
||||||
primitive(args) {
|
|
||||||
const num = macros.number(args)
|
|
||||||
return num !== undefined ? num : macros.string(args)
|
|
||||||
},
|
|
||||||
scope({ addScope, head, tail, entry }, definition) {
|
|
||||||
return addScope({ definition, head, tail, entry })
|
|
||||||
},
|
|
||||||
section: isScope({
|
|
||||||
[SYMBOLS.TAIL]: getTail('pos', isMacro('number')),
|
|
||||||
[SYMBOLS.UNMATCHED]: getText('content', isMacro('string')),
|
|
||||||
position: isMacro('number')
|
|
||||||
}),
|
|
||||||
options: isScope({
|
|
||||||
parameter1: isMacro('number'),
|
|
||||||
parameter2: isMacro('string'),
|
|
||||||
unmatched: isMacro('string'),
|
|
||||||
[SYMBOLS.UNMATCHED]: getUnmatched(isMacro('any')),
|
|
||||||
}),
|
|
||||||
any(args) {
|
|
||||||
const macro = args.macros[args.head]
|
|
||||||
if (macro) return macro(args)
|
|
||||||
const numResult = args.macros.number(args)
|
|
||||||
if (numResult !== undefined) return numResult
|
|
||||||
args.tail = args.entry
|
|
||||||
return args.macros.string(args)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
root: {
|
|
||||||
// Move tail into value position and cast it to string.
|
|
||||||
title: chain('tail', 'string'),
|
|
||||||
// options: isMacro('options'),
|
|
||||||
// subsection: isMacro('section'),
|
|
||||||
// list: isScope({
|
|
||||||
// '-': isMacro('string')
|
|
||||||
// }),
|
|
||||||
// collection: isScope({
|
|
||||||
// section: isCollection(isMacro('section'))
|
|
||||||
// }),
|
|
||||||
// collection2: isScope({
|
|
||||||
// [SYMBOLS.UNMATCHED]: getUnmatched(isMacro('any'))
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user