2022-11-15 17:10:17 -05:00

136 lines
3.2 KiB
JavaScript

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