ima-parse
Version:
Easy Simple Parser, that only requires a Grammar JSON to output an AST.
183 lines (182 loc) • 9.01 kB
JavaScript
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;
;