UNPKG

ima-parse

Version:

Easy Simple Parser, that only requires a Grammar JSON to output an AST.

183 lines (182 loc) 9.01 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.matchRulePart = exports.isDefinitionSatisfied = exports.matchSimplePart = exports.matchPathsPart = exports.matchDefinition = exports.parseInput = void 0; const RuleParser_1 = require("./RuleParser"); const helpers_1 = require("../helpers/helpers"); function parseInput(input, context, previousParsedPart) { // We try to re-parse the previous parsed part, because some parts allow multiple content (comments, modifiers, paths, rules) if (previousParsedPart && previousParsedPart?.isFinished !== true) { const index = previousParsedPart.index; const definition = context.parts[index]; const parsedPart = matchDefinition(definition, index, input, context, previousParsedPart); if (parsedPart) return parsedPart; // If we already were parsing a paths part, then we require it to have at least one satisfied path to continue if (previousParsedPart.type === "paths" && !previousParsedPart.hasSatisfiedPath) return undefined; } // Process the parts starting after the previousParsedPart or if there is none; start at 0 let index = previousParsedPart ? previousParsedPart.index + 1 : 0; for (; index < context.parts.length; index++) { const definition = context.parts[index]; const parsedPart = matchDefinition(definition, index, input, context); if (parsedPart) return parsedPart; if (!definition.optional) return undefined; } return undefined; } exports.parseInput = parseInput; function matchDefinition(definition, index, input, context, previousParsedPart) { if (definition.type === "rules") { return matchRulePart({ definition, index, input, context, previousParsedPart: previousParsedPart }); } else if (definition.type === "paths") { return matchPathsPart({ definition, index, input, context, previousParsedPart: previousParsedPart }); } else { const parsedSimplePart = previousParsedPart; return matchSimplePart({ definition, index, input, context, previousParsedPart: parsedSimplePart }); } } exports.matchDefinition = matchDefinition; /** * @returns parse information if parsing was possible for one of the paths in the definition. If not possible, undefined. */ function matchPathsPart({ definition, input, index, previousParsedPart, context }) { const availablePaths = previousParsedPart?.pathsProgress.map(prog => prog.path) ?? definition.paths; const parsedPaths = []; for (let pathIndex = 0; pathIndex < availablePaths.length; pathIndex++) { // Make sure to keep this as a reference, since it is being used later to compare to the original const pathDefinition = availablePaths[pathIndex]; const partsContext = { grammar: context.grammar, parts: pathDefinition, parser: context.parser }; const parsedPartsFromPath = previousParsedPart?.pathsProgress[pathIndex]?.parsedParts; const previousParsedPartFromPath = parsedPartsFromPath?.at(-1); const parsedPart = parseInput(input, partsContext, previousParsedPartFromPath); const finalParsedParts = parsedPartsFromPath ?? []; if (parsedPart) { if (parsedPart.type !== "simple") throw new Error(`Paths only support 'simple' children ${parsedPart}`); finalParsedParts.push(parsedPart); parsedPaths.push({ path: pathDefinition, parsedParts: finalParsedParts }); } } if (parsedPaths.length) { const textMode = parsedPaths.some(({ parsedParts }) => parsedParts.at(-1).textMode); const hasSatisfiedPath = parsedPaths.some(({ path, parsedParts }) => isDefinitionSatisfied(path, parsedParts)); return { index, type: "paths", pathsProgress: parsedPaths, overrideSamePart: true, textMode, hasSatisfiedPath }; } // We make sure to return undefined when we can't continue, so that the potentially unfinished partly parsed path(s) can still be used return undefined; } exports.matchPathsPart = matchPathsPart; function matchSimplePart({ definition, input, index, previousParsedPart }) { const { chars, startPos, endPos, phraseKind } = input; switch (definition.type) { case "keyword": if (previousParsedPart) break; if (definition.phrase === chars) return { type: "simple", index, value: [chars], startPos, endPos, isFinished: true }; break; case "identifier": if (previousParsedPart) break; if (phraseKind === "word") return { type: "simple", index, value: [chars], startPos, endPos, isFinished: true }; break; case "number": if (previousParsedPart) break; if (phraseKind === "number") return { type: "simple", index, value: [chars], startPos, endPos, isFinished: true }; break; case "modifiers": const match = definition.phrases.includes(chars); if (previousParsedPart) { if (match && !previousParsedPart.value.includes(chars)) { previousParsedPart.value.push(chars); previousParsedPart.overrideSamePart = true; previousParsedPart.endPos = endPos; previousParsedPart.isFinished = previousParsedPart.value.length === definition.phrases.length; return previousParsedPart; } break; } if (match) { const isFinished = definition.singular || definition.phrases.length === 1; return { type: "simple", index, value: [chars], startPos, endPos, isFinished }; } break; case "text": { // TODO: Char escaping with \ let part; if (previousParsedPart) { part = previousParsedPart; } else if (!definition.startPhrase || chars === definition.startPhrase) { part = { type: "simple", index, value: [""], startPos, endPos, textMode: true }; } if (part) { let text = part.value[0] + chars; // Make sure to ignore the matched startprhase, if there was any if (text.slice(definition.startPhrase?.length).endsWith(definition.endPhrase)) { part.isFinished = true; part.textMode = false; part.ignoredPhrase = definition.excludeEndPhrase; if (definition.excludeEndPhrase) text = text.slice(0, text.length - definition.endPhrase.length); } part.value[0] = text; if (previousParsedPart) part.overrideSamePart = true; } return part; } default: (0, helpers_1.assertNever)(definition); } return undefined; } exports.matchSimplePart = matchSimplePart; function isDefinitionSatisfied(parts, parsedParts) { const lastParsedPart = parsedParts.at(-1); if (lastParsedPart?.type === "paths" && !lastParsedPart.hasSatisfiedPath) return false; for (let index = (lastParsedPart?.index ?? -1) + 1; index < parts.length; index++) { const nextPart = parts[index]; if (!nextPart.optional) return false; } return true; } exports.isDefinitionSatisfied = isDefinitionSatisfied; function matchRulePart({ definition, input, index, context, previousParsedPart }) { if (previousParsedPart) { if (!previousParsedPart.separatorSatisfied) { if (definition.separatorPhrase === input.chars) { previousParsedPart.separatorSatisfied = true; previousParsedPart.overrideSamePart = true; return previousParsedPart; } // We won't continue parsing rules if a separator is required but not given if (!previousParsedPart.separatorOptional) return undefined; } if (definition.singular) return undefined; } for (const grammarRule of (0, helpers_1.getRules)(definition)) { const childParser = new RuleParser_1.RuleParser(grammarRule, context.grammar, context.parser); const parseResult = childParser.parsePhrase(input); if (parseResult.success) { // If there is a separatorPhrase, then we can only continue with other rules if it's satisfied const separatorSatisfied = definition.separatorPhrase === undefined; const separatorOptional = definition.separatorOptional; return { type: "rule", index, childParser, successfulParser: parseResult.ruleParser, separatorSatisfied, separatorOptional }; } } return undefined; } exports.matchRulePart = matchRulePart;