More work on array-based parser.

This commit is contained in:
Joshua Bemenderfer 2022-11-20 17:24:27 -05:00
parent c869c9c5d5
commit dce436558e
2 changed files with 61 additions and 199 deletions

View File

@ -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
} }

View File

@ -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'))
// })
}
} }