Reworked parser to be macro based.
This commit is contained in:
@@ -1,166 +1,135 @@
|
||||
import { createLineData, 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({ tail }) {
|
||||
return tail.toString()
|
||||
},
|
||||
number({ tail }) {
|
||||
const num = +tail
|
||||
if (isNaN(num) || tail === '') return
|
||||
return num
|
||||
},
|
||||
primitive({ macros }) {
|
||||
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 typeHandlers = {
|
||||
string (doc) {
|
||||
return doc.tail().slice(1)
|
||||
},
|
||||
number (doc) {
|
||||
return +doc.tail().slice(1)
|
||||
},
|
||||
object (doc, handlers, definition, level) {
|
||||
level = level != null ? level : doc.level() + 1
|
||||
definition = lookup(definition)
|
||||
|
||||
const object = {}
|
||||
for (let [key, child] of Object.entries(definition.values)) {
|
||||
child = lookup(child)
|
||||
if (key === 'any') key = null
|
||||
addHandler(handlers, level, key, child, (key, value) => {
|
||||
if (child.collate === 'array') {
|
||||
if (!object[key]) object[key] = []
|
||||
object[key].push(value)
|
||||
return
|
||||
} else if (child.collate === 'collection') {
|
||||
if (!object[key]) object[key] = []
|
||||
object[key].push({ [key]: value })
|
||||
return
|
||||
} else {
|
||||
object[key] = value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (definition.tail) {
|
||||
const tailKey = definition.tail
|
||||
const tailType = definition.values[tailKey]
|
||||
object[tailKey] = typeHandlers[tailType](doc, handlers)
|
||||
}
|
||||
|
||||
if (definition.text) {
|
||||
const textKey = definition.text
|
||||
const textType = 'string'
|
||||
object[textKey] = ''
|
||||
addHandler(handlers, level, '', textType, (key, value) => {
|
||||
object[textKey] += object[textKey] ? `\n${doc.line()}` : doc.line()
|
||||
})
|
||||
}
|
||||
|
||||
return object
|
||||
},
|
||||
array (doc, handlers, definition, level) {
|
||||
level = level != null ? level : doc.level() + 1
|
||||
definition = lookup(definition)
|
||||
|
||||
const array = []
|
||||
for (let [key, child] of Object.entries(definition.values)) {
|
||||
child = lookup(child)
|
||||
addHandler(handlers, level, key, child, (key, value) => {
|
||||
array.push(value)
|
||||
})
|
||||
}
|
||||
|
||||
// if (definition.tail) {
|
||||
// const tailKey = definition.tail
|
||||
// const tailType = definition.values[tailKey]
|
||||
// object[tailKey] = typeHandlers[tailType](doc, handlers)
|
||||
// }
|
||||
|
||||
// if (definition.text) {
|
||||
// const textKey = definition.text
|
||||
// const textType = definition.values[textKey]
|
||||
// object[textKey] = ''
|
||||
// addHandler(handlers, level, '', textType, (key, value) => {
|
||||
// object[textKey] += object[textKey] ? `\n${doc.line()}` : doc.line()
|
||||
// })
|
||||
// }
|
||||
|
||||
return array
|
||||
},
|
||||
collection (doc, handlers, definition, level) {
|
||||
level = level != null ? level : doc.level() + 1
|
||||
definition = lookup(definition)
|
||||
|
||||
const collection = []
|
||||
for (let [key, child] of Object.entries(definition.values)) {
|
||||
child = lookup(child)
|
||||
addHandler(handlers, level, key, child, (key, value) => {
|
||||
collection.push({ [key]: value })
|
||||
})
|
||||
}
|
||||
|
||||
// if (definition.tail) {
|
||||
// const tailKey = definition.tail
|
||||
// const tailType = definition.values[tailKey]
|
||||
// object[tailKey] = typeHandlers[tailType](doc, handlers)
|
||||
// }
|
||||
|
||||
// if (definition.text) {
|
||||
// const textKey = definition.text
|
||||
// const textType = definition.values[textKey]
|
||||
// object[textKey] = ''
|
||||
// addHandler(handlers, level, '', textType, (key, value) => {
|
||||
// object[textKey] += object[textKey] ? `\n${doc.line()}` : doc.line()
|
||||
// })
|
||||
// }
|
||||
|
||||
return collection
|
||||
},
|
||||
}
|
||||
|
||||
function lookup (definition) {
|
||||
const type = typeof definition === 'string' ? definition : definition.type
|
||||
const resolvedDefinition = schema.types[type] || definition
|
||||
return typeof definition === 'string' ? resolvedDefinition : Object.assign({}, definition, resolvedDefinition)
|
||||
}
|
||||
|
||||
function registerTypes(typeHandlers, types) {
|
||||
for (const [key, definition] of Object.entries(types)) {
|
||||
const existingType = typeof definition === 'string' ? definition : definition.type
|
||||
typeHandlers[key] = typeHandlers[existingType]
|
||||
}
|
||||
}
|
||||
|
||||
function addHandler(handlers, level, key, definition, resolve) {
|
||||
const type = typeof definition === 'string' ? definition : definition.type
|
||||
if (!handlers[level]) handlers[level] = []
|
||||
handlers[level].push({
|
||||
key,
|
||||
resolve,
|
||||
definition,
|
||||
handler: typeHandlers[type]
|
||||
})
|
||||
}
|
||||
|
||||
const doc = useDocument(createStringReader(lines))
|
||||
const handlers = []
|
||||
|
||||
registerTypes(typeHandlers, schema.types)
|
||||
const macros = schema.macros
|
||||
const handlers = [schema.root]
|
||||
const scopes = [{}]
|
||||
|
||||
const root = typeHandlers.object(doc, handlers, schema.root, 0)
|
||||
function addScope({ head, tail, line, definition }) {
|
||||
const scope = {}
|
||||
|
||||
if (definition[SYMBOLS.TAIL]) {
|
||||
const result = definition[SYMBOLS.TAIL]({ macros, scope, head, tail, line, addScope })
|
||||
Object.assign(scope, result)
|
||||
}
|
||||
|
||||
scopes[scopes.length] = scope
|
||||
handlers[handlers.length] = definition
|
||||
return scope
|
||||
}
|
||||
|
||||
let ended = false
|
||||
while (!ended) {
|
||||
ended = await doc.next()
|
||||
if (ended) break;
|
||||
if (doc.line() === '') continue;
|
||||
const level = doc.level()
|
||||
handlers.length = level + 1
|
||||
scopes.length = level + 1
|
||||
|
||||
const unmatchedHandler = handlers[level]?.find(h => h.key === '')
|
||||
let matched = false
|
||||
for (const { key, definition, resolve, handler } of handlers[level] || []) {
|
||||
if (key && doc.head() !== key) continue;
|
||||
if (!doc.line()) continue;
|
||||
resolve(doc.head(), handler(doc, handlers, definition))
|
||||
matched = true
|
||||
break
|
||||
const line = doc.line()
|
||||
const head = doc.head()
|
||||
const tail = doc.tail().slice(1)
|
||||
|
||||
const options = handlers[level]
|
||||
const scope = scopes[level]
|
||||
|
||||
const matches = {
|
||||
head: options?.[doc.head()],
|
||||
unmatched: options?.[SYMBOLS.UNMATCHED]
|
||||
}
|
||||
if (!matched && unmatchedHandler) {
|
||||
const { resolve, definition, handler } = unmatchedHandler
|
||||
resolve(doc.head(), handler(doc, handlers, definition))
|
||||
|
||||
if (matches.head) {
|
||||
const result = matches.head({macros, scope, head, tail, line, addScope })
|
||||
|
||||
if (!scope[head]) scope[head] = []
|
||||
scope[head].push(result)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if (matches.unmatched) {
|
||||
const result = matches.unmatched({ macros, scope, head, tail, line, addScope })
|
||||
Object.assign(scope, result)
|
||||
}
|
||||
}
|
||||
|
||||
return root
|
||||
return scopes[0]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user