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