cm-tarnation
Version:
An alternative parser for CodeMirror 6
178 lines • 6.16 kB
JavaScript
/* 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