UNPKG

ima-parse

Version:

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

201 lines (200 loc) 9.35 kB
"use strict"; 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; }