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'
|
||||
|
||||
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) {
|
||||
const doc = useDocument(createStringReader(lines))
|
||||
|
||||
const macros = schema.macros
|
||||
const scopes = [[]]
|
||||
|
||||
function addScope() {
|
||||
const scope = []
|
||||
scopes[scopes.length] = scope
|
||||
return scope
|
||||
function createScope(line, matchers) {
|
||||
return {
|
||||
block: [line, []],
|
||||
handlers: [],
|
||||
matchers: matchers
|
||||
}
|
||||
}
|
||||
|
||||
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 lastLevel = 0
|
||||
while (!ended) {
|
||||
ended = await doc.next()
|
||||
if (ended) break;
|
||||
const level = doc.level()
|
||||
const scope = scopes[level] || []
|
||||
scopes.length = level + 1
|
||||
const level = doc.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 => {
|
||||
if (entry[0] === key || entry[0].startsWith(`${key} `)) {
|
||||
entry = schema.root[key]({ entry, macros })
|
||||
}
|
||||
})
|
||||
// Remove empty arrays from scopes without children.
|
||||
if (scope.block[1] && !scope.block[1].length) scope.block.length = 1
|
||||
|
||||
if (!scopes[level]) {
|
||||
scopes[level] = scope
|
||||
scopes[level - 1].at(-1)[1] = scopes[level]
|
||||
}
|
||||
scope.push(entry)
|
||||
// Postprocess scope with relevant macros.
|
||||
scope.block = scope.handlers.reduce((block, handler) => {
|
||||
return handler(block)
|
||||
}, [...scope.block])
|
||||
|
||||
if (lastLevel == level) {
|
||||
console.log('DECREASE: ', doc.line(), scope)
|
||||
// Add to parent block.
|
||||
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)
|
||||
}
|
||||
|
||||
return scopes[0]
|
||||
// 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].block
|
||||
}
|
@ -1,102 +1,6 @@
|
||||
import { SYMBOLS, parse, isScope, isMacro, getTail, getText, getUnmatched, isCollection } 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
|
||||
}
|
||||
}
|
||||
import { parse } from './core.js'
|
||||
|
||||
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