UNPKG

cm-tarnation

Version:

An alternative parser for CodeMirror 6

178 lines 6.16 kB
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { NodeID } from "../constants"; import { Node } from "./node"; import { Chain } from "./rules/chain"; import { LookupRule } from "./rules/lookup"; import { PatternRule } from "./rules/pattern"; import { Rule } from "./rules/rule"; import { State } from "./rules/state"; /** Holds the rules, states, etc. for a {@link Grammar}. */ export class Repository { grammar; variables; ignoreCase; /** Map of names to objects stored in this repository. */ map = new Map(); /** Current {@link Node} ID. */ curID = NodeID.SAFE; constructor(grammar, variables, ignoreCase = false) { this.grammar = grammar; this.variables = variables; this.ignoreCase = ignoreCase; } /** Returns every {@link Node} in the repository, sorted by ID. */ nodes() { const nodes = new Set(); for (const obj of this.map.values()) { if (obj instanceof Node) nodes.add(obj); if ("node" in obj) nodes.add(obj.node); if (obj instanceof State && obj.inside instanceof Node) nodes.add(obj.inside); } return Array.from(nodes) .filter(v => v !== Node.None) .sort((a, b) => a.id - b.id); } /** Returns a fresh ID for use by a {@link Node}. */ id() { const id = this.curID; this.curID++; return id; } add(obj, name) { // match pattern shorthand if (typeof obj === "string") { if (!name) throw new Error("name is required for shorthands"); const pattern = { type: name, emit: false, match: obj }; return this.add(pattern, name); } // node open bracket shorthand if ("open" in obj) { const node = { ...obj, type: `${obj.open}Open`, closedBy: `${obj.open}Close` }; // @ts-ignore delete node.open; return this.add(node, name); } // node close bracket shorthand if ("close" in obj) { const node = { ...obj, type: `${obj.close}Close`, openedBy: `${obj.close}Open` }; // @ts-ignore delete node.close; return this.add(node, name); } // reused node if ("is" in obj) { const result = this.get(obj.is); if (!result) throw new Error(`reused node ${obj.is} not found`); return "node" in result ? result.node : result; } // add name to node if it doesn't have one explicitly if (!obj.type && name) obj.type = name; // prevents duplication when doing things out of order if (obj.type && this.map.get(obj.type)) { return this.map.get(obj.type); } // lookup if ("lookup" in obj) { const lookup = new LookupRule(this, obj); this.map.set(lookup.name, lookup); return lookup; } // pattern if ("match" in obj) { const pattern = new PatternRule(this, obj); this.map.set(pattern.name, pattern); return pattern; } // chain if ("chain" in obj) { const chain = new Chain(this, obj); this.map.set(chain.name, chain); return chain; } // state if ("begin" in obj) { const state = new State(this, obj); this.map.set(state.name, state); return state; } // must be a node const node = new Node(this.id(), obj); this.map.set(node.name, node); return node; } /** * Gets an object from this repository. If it doesn't exist already, the * grammar definition will be checked. Returns `undefined` if nothing can * be found. * * @param key - The name of the object to get. */ get(key) { const result = this.map.get(key); // add missing item if possible if (!result) { if (this.grammar.def.repository?.[key]) { return this.add(this.grammar.def.repository[key], key); } } return result; } /** * Processes an `include` from the grammar definition, by name. * * @param str - The name of the `include` to process. */ include(str) { if (!this.grammar.def.includes) throw new Error("no includes defined"); if (!this.grammar.def.includes[str]) throw new Error(`include ${str} not found`); return this.grammar.def.includes[str] .map(name => this.get(name)) .filter(rule => !(rule instanceof Node)); } /** * Processes an "inside" list of rules/states/includes, returning a resolved list. * * @param rules - The list of rules/states/includes to process. */ inside(rules) { const inside = []; for (const rule of rules) { // specifier for a rule if (typeof rule === "string") { const resolved = this.get(rule); if (!(resolved instanceof Rule) && !(resolved instanceof State)) { throw new Error(`Invalid inside rule`); } inside.push(resolved); } // include else if ("include" in rule) { inside.push(...this.include(rule.include)); } // state or rule else { inside.push(this.add(rule)); } } return inside; } } //# sourceMappingURL=repository.js.map