ima-parse
Version:
Easy Simple Parser, that only requires a Grammar JSON to output an AST.
201 lines (200 loc) • 9.35 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.getTypesAndBuildersFromGrammar = exports.buildContentTree = void 0;
const helpers_1 = require("../helpers/helpers");
/**
* Fill a tree, that adheres to the before created types (`getTypesAndBuildersFromGrammar()`), with the read data that's in a RuleParser.
*/
function buildContentTree(ruleBuilders, topLevelParser) {
const ruleName = topLevelParser.rule.name;
if (!ruleBuilders.has(topLevelParser.rule))
throw new Error(`Can't build content tree: no builder found for: '${ruleName}'`);
return ruleBuilders.get(topLevelParser.rule)(topLevelParser, ruleBuilders);
}
exports.buildContentTree = buildContentTree;
/**
* Read the `Grammar` and generate types that parsed data will adhere to. The rulebuilders should be used to actually build the content tree
*/
function getTypesAndBuildersFromGrammar(grammar) {
const grammarRules = getAllGrammarRules(grammar);
const ruleBuilders = new Map();
const ruleTypeDefs = [];
const typeNames = [];
for (const rule of grammarRules) {
const [ruleTypeTree, buildRuleFn] = getRuleTypeTreeAndBuilder(rule);
typeNames.push(rule.name);
ruleBuilders.set(rule, buildRuleFn);
let objectDef = `export type ${rule.name} = $RuleContentTreeBase & {\n`;
objectDef += ` $type: "${rule.name}";\n`;
if (ruleTypeTree.$keywords) {
const optionalSign = ruleTypeTree.$keywords.optional ? "?" : "";
objectDef += ` $keywords${optionalSign}: ${getValueInfoFromTypeAsString(ruleTypeTree.$keywords)};\n`;
}
for (const [key, valueTypeInfo] of ruleTypeTree.parts) {
objectDef += ` "${key}"${valueTypeInfo.optional ? "?" : ""}: ${getValueInfoFromTypeAsString(valueTypeInfo)};\n`;
}
objectDef += "};";
ruleTypeDefs.push(objectDef);
}
// These base types are helpers and start with a $ because of that. Beware when changing these, since they're strings.
const baseTypes = [
"export type $Cursor = { ln: number; col: number };",
"export type $Position = { start: $Cursor; end: $Cursor };",
"export type $RuleContentTreeBase = { $type: string; $position: $Position; }",
`export type $RuleContentTree = ${typeNames.join(" | ")}`
];
return { types: [...baseTypes, ...ruleTypeDefs], ruleBuilders };
}
exports.getTypesAndBuildersFromGrammar = getTypesAndBuildersFromGrammar;
/**
* Get the types and builder function for a specific rule.
*/
function getRuleTypeTreeAndBuilder(rule) {
const ruleTypeTree = { $type: rule.name, parts: new Map() };
const rulePartBuilders = prepareParts(rule.definition, ruleTypeTree);
const buildRuleFn = (ruleParser, ruleBuilders) => {
const ruleContentTree = { $type: rule.name, $position: ruleParser.getPosition() };
ruleParser.parsedParts.forEach(parsedPart => rulePartBuilders[parsedPart.index](parsedPart, ruleContentTree, ruleBuilders));
return ruleContentTree;
};
return [ruleTypeTree, buildRuleFn];
}
function getValueInfoFromTypeAsString(typeInfo) {
return `{ type: "${typeInfo.type}"; value: ${typeInfo.value}; position: $Position; }${typeInfo.isArray ? "[]" : ""}`;
}
function prepareParts(parts, typeTree, path) {
const partBuilders = parts.map(definition => {
switch (definition.type) {
case "identifier":
return prepareIdentifierPart(definition, typeTree, path);
case "modifiers":
return prepareModifierPart(definition, typeTree);
case "number":
return prepareNumberPart(definition, typeTree, path);
case "text":
return prepareTextPart(definition, typeTree, path);
case "keyword":
return prepareKeywordPart(typeTree, path);
case "paths":
return preparePathsPart(definition, typeTree);
case "rules":
return prepareRulesPart(definition, typeTree);
default:
(0, helpers_1.assertNever)(definition);
}
});
return partBuilders;
}
/**
* Get the types and builder function for a rules definition part. This is a part that refers to rules, not the rule itself.
*/
function prepareRulesPart(part, typeTree) {
const rulesUnionType = (0, helpers_1.getRules)(part)
.map(r => r.name)
.join(" | ");
typeTree.parts.set(part.key, part.singular
? { type: "child", value: rulesUnionType, optional: part.optional }
: { type: "children", value: `(${rulesUnionType})[]`, optional: part.optional });
return (parsedRule, parentRuleContent, ruleBuilders) => {
const childRuleContent = ruleBuilders.get(parsedRule.childParser.rule)(parsedRule.childParser, ruleBuilders);
if (part.singular) {
parentRuleContent[part.key] = { type: "child", value: childRuleContent, position: parsedRule.childParser.getPosition() };
}
else if (!parentRuleContent[part.key]) {
parentRuleContent[part.key] = { type: "children", value: [childRuleContent], position: parsedRule.childParser.getPosition() };
}
else {
parentRuleContent[part.key].position.end = parsedRule.childParser.getEndCursor();
parentRuleContent[part.key].value.push(childRuleContent);
}
};
}
/**
* Get the types and builder function for a specific paths definition part.
*/
function preparePathsPart(part, typeTree) {
const pathPartBuilders = new Map();
part.paths.forEach(path => pathPartBuilders.set(path, prepareParts(path, typeTree, true)));
return (parsedPath, ruleTree, ruleBuilders) => {
const partBuilders = pathPartBuilders.get(parsedPath.pathsProgress[0].path);
const path = parsedPath.pathsProgress.at(0);
if (!partBuilders || !path)
throw new Error("Something went wrong when retrieving the typings for a parsed path");
path.parsedParts.forEach(parsedPart => partBuilders.at(parsedPart.index)?.(parsedPart, ruleTree, ruleBuilders));
};
}
/**
* Get the types and builder function for a specific keyword definition part.
*/
function prepareKeywordPart(result, inPath) {
if (!result.$keywords)
result.$keywords = { type: "keyword", value: "string", isArray: true, optional: inPath };
return (parsedPart, ruleTree) => {
if (!ruleTree.$keywords)
ruleTree.$keywords = [];
ruleTree.$keywords?.push({ type: "keyword", value: parsedPart.value[0], position: getPositionForSimplePart(parsedPart) });
};
}
/**
* Get the types and builder function for a specific identifier definition part.
*/
function prepareIdentifierPart(part, result, inPath) {
result.parts.set(part.key, { type: "identifier", value: "string", optional: inPath });
return (parsedPart, ruleTree) => {
ruleTree[part.key] = { type: "identifier", value: parsedPart.value[0], position: getPositionForSimplePart(parsedPart) };
};
}
/**
* Get the types and builder function for a specific number definition part.
*/
function prepareNumberPart(part, result, inPath) {
result.parts.set(part.key, { type: "number", value: "number", optional: inPath });
return (parsedPart, ruleTree) => {
ruleTree[part.key] = { type: "number", value: Number(parsedPart.value[0]), position: getPositionForSimplePart(parsedPart) };
};
}
/**
* Get the types and builder function for a specific text definition part.
*/
function prepareTextPart(part, result, inPath) {
result.parts.set(part.key, { type: "text", value: "string", optional: part.optional || inPath });
return (parsedPart, ruleTree) => {
ruleTree[part.key] = { type: "text", value: parsedPart.value[0], position: getPositionForSimplePart(parsedPart) };
};
}
/**
* Get the types and builder function for a specific modifiers definition part. Each value will result in an individual key with bool value.
*/
function prepareModifierPart(part, result) {
const type = { type: "modifier", value: "boolean", optional: true };
if (part.key) {
result.parts.set(part.key, type);
}
else {
part.phrases.forEach(phrase => result.parts.set(phrase, type));
}
return (parsedPart, ruleTree) => {
parsedPart.value.forEach(modifier => {
ruleTree[modifier] = { type: "modifier", value: true, position: getPositionForSimplePart(parsedPart) };
});
};
}
function getPositionForSimplePart(parsedPart) {
return { start: { ...parsedPart.startPos }, end: { ...parsedPart.endPos } };
}
function getAllGrammarRules(grammar) {
const uniqueDefinitions = new Set([grammar.TopLevel]);
const uniqueDefinitionNames = new Set([grammar.TopLevel.name]);
for (const grammarRule of uniqueDefinitions) {
for (const definitionPart of grammarRule.definition) {
if (definitionPart.type !== "rules")
continue;
(0, helpers_1.getRules)(definitionPart).forEach(rule => {
if (uniqueDefinitionNames.has(rule.name))
throw new Error(`Found a rule definition with a duplicate name: '${rule.name}'`);
uniqueDefinitions.add(rule);
});
}
}
return uniqueDefinitions;
}
;