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 doc = useDocument(createStringReader(lines)) const macros = schema.macros const handlers = [schema.root] const scopes = [{}] 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 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 (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 scopes[0] }