diff --git a/docs/experiments/parsers/v3/core.js b/docs/experiments/parsers/v3/core.js index 82a3255..502fc3e 100644 --- a/docs/experiments/parsers/v3/core.js +++ b/docs/experiments/parsers/v3/core.js @@ -1,121 +1,79 @@ -import { createLineData, useDocument } from '@terrace/core' +import { 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 ({ line }) { - // return tail(line).toString() - // }, - // number ({ line }) { - // const num = +tail(line) - // if (isNaN(num) || line === '') return - // return num - // }, - // primitive (args) { - // 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 scopes = [[]] - - function addScope() { - const scope = [] - scopes[scopes.length] = scope - return scope + 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 - let lastLevel = 0 while (!ended) { ended = await doc.next() if (ended) break; - const level = doc.level() - const scope = scopes[level] || [] - scopes.length = level + 1 + const level = doc.level() + 1 - let entry = [doc.line()] + // Trigger macros for closed scopes. + for (let i = scopes.length - 1; i >= level; --i) { + const scope = scopes[i] + const parent = scopes[i - 1] - Object.keys(schema.root).forEach(key => { - if (entry[0] === key || entry[0].startsWith(`${key} `)) { - entry = schema.root[key]({ entry, macros }) - } - }) + // Remove empty arrays from scopes without children. + if (scope.block[1] && !scope.block[1].length) scope.block.length = 1 - if (!scopes[level]) { - scopes[level] = scope - scopes[level - 1].at(-1)[1] = scopes[level] - } - scope.push(entry) + // Postprocess scope with relevant macros. + scope.block = scope.handlers.reduce((block, handler) => { + return handler(block) + }, [...scope.block]) - if (lastLevel == level) { - console.log('DECREASE: ', doc.line(), scope) + // Add to parent block. + parent.block[1].push(scope.block) } - lastLevel = level + // 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] -} + return scopes[0].block +} \ No newline at end of file diff --git a/docs/experiments/parsers/v3/example.js b/docs/experiments/parsers/v3/example.js index e49ca24..e0bde0f 100644 --- a/docs/experiments/parsers/v3/example.js +++ b/docs/experiments/parsers/v3/example.js @@ -1,102 +1,6 @@ -import { SYMBOLS, parse, isScope, isMacro, getTail, getText, getUnmatched, isCollection } from './core.js' - -const schemaTCE = ` -macros - primitive match number string - section - pos number tail - content string unmatched - position number - options - parameter1 number - parameter2 string - literal unmatched string - unmatched primitive - -root - title string - options options collection - subsection section - list - - string array - collection - section collection - collection2 - unmatched collection -` - -function head(line) { - return line.split(' ')[0] -} - -function tail(line) { - return line.split(' ').slice(1).join(' ') -} - -function chain(...macroNames) { - return (args) => { - for (const macroName of macroNames) { - if (args.macros[macroName]) args.entry = args.macros[macroName](args) - } - return args.entry - } -} +import { parse } from './core.js' const schema = { - macros: { - tail({ entry }) { - return [head(entry[0]), tail(entry[0])] - }, - string({ entry }) { - return [entry[0], entry[1].toString()] - }, - number({ entry }) { - const num = +entry[1] - if (isNaN(num) || entry[1] === '') return entry - return [entry[0], num] - }, - primitive(args) { - const num = macros.number(args) - return num !== undefined ? num : macros.string(args) - }, - scope({ addScope, head, tail, entry }, definition) { - return addScope({ definition, head, tail, entry }) - }, - section: isScope({ - [SYMBOLS.TAIL]: getTail('pos', isMacro('number')), - [SYMBOLS.UNMATCHED]: getText('content', isMacro('string')), - position: isMacro('number') - }), - options: isScope({ - parameter1: isMacro('number'), - parameter2: isMacro('string'), - unmatched: isMacro('string'), - [SYMBOLS.UNMATCHED]: getUnmatched(isMacro('any')), - }), - 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.entry - return args.macros.string(args) - }, - }, - root: { - // Move tail into value position and cast it to string. - title: chain('tail', 'string'), - // options: isMacro('options'), - // subsection: isMacro('section'), - // list: isScope({ - // '-': isMacro('string') - // }), - // collection: isScope({ - // section: isCollection(isMacro('section')) - // }), - // collection2: isScope({ - // [SYMBOLS.UNMATCHED]: getUnmatched(isMacro('any')) - // }) - } }