import { useDocument } from '@terrace/core' import { createStringReader } from '@terrace/core/readers/js-string' export async function parse(lines, schema) { const doc = useDocument(createStringReader(lines)) function createScope(line, matchers) { return { block: [line, []], handlers: [], matchers: matchers } } const macros = schema.macros const scopes = [createScope('root', { title ({ addHandler }) { addHandler(scope => [scope[0].split(' ')[0], scope[0].split(' ').slice(1).join(' ')]) }, options ({ addMatcher }) { addMatcher({ parameter1({ addHandler }) { addHandler(scope => [scope[0].split(' ')[0], +scope[0].split(' ').slice(1).join(' ')]) } }) } })] let ended = false while (!ended) { ended = await doc.next() if (ended) break; const level = doc.level() + 1 // Trigger macros for closed scopes. for (let i = scopes.length - 1; i >= level; --i) { const scope = scopes[i] const parent = scopes[i - 1] // Remove empty arrays from scopes without children. if (scope.block[1] && !scope.block[1].length) scope.block.length = 1 // Postprocess scope with relevant macros. scope.block = scope.handlers.reduce((block, handler) => { return handler(block) }, [...scope.block]) // Add to parent block. parent.block[1].push(scope.block) } // Reset scope length to avoid dangling scopes. scopes.length = level // Define current scope const scope = scopes[level] = createScope(doc.line(), {}) // Determine parent for this scope. const parent = scopes[level - 1] // Add a postprocess handler for this scope. // Ie. When we leave the scope, rewrite it using this handler. function addHandler(handler) { scope.handlers.push(handler) } // Add matchers for this scope's children. function addMatcher(definition) { scope.matchers = definition } // Run matching matchers for this scope. if (parent.matchers[doc.head()]) { parent.matchers[doc.head()]({ addHandler, addMatcher }) } } return scopes[0].block }