136 lines
3.2 KiB
JavaScript
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]
|
|
}
|