import { createLineData, useDocument } from '@terrace/core' import { createStringReader } from '@terrace/core/readers/js-string' 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 root = typeHandlers.object(doc, handlers, schema.root, 0) let ended = false while (!ended) { ended = await doc.next() if (ended) break; const level = doc.level() handlers.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 } if (!matched && unmatchedHandler) { const { resolve, definition, handler } = unmatchedHandler resolve(doc.head(), handler(doc, handlers, definition)) } } return root }