Skip to main content
Module

x/ohm_js/src/buildGrammar.js

A library and language for building parsers, interpreters, compilers, etc.
Go to Latest
File
import ohmGrammar from '../dist/ohm-grammar.js';import {Builder} from './Builder.js';import * as common from './common.js';import * as errors from './errors.js';import {Grammar} from './Grammar.js';import * as pexprs from './pexprs.js';
const superSplicePlaceholder = Object.create(pexprs.PExpr.prototype);
function namespaceHas(ns, name) { // Look for an enumerable property, anywhere in the prototype chain. for (const prop in ns) { if (prop === name) return true; } return false;}
// Returns a Grammar instance (i.e., an object with a `match` method) for// `tree`, which is the concrete syntax tree of a user-written grammar.// The grammar will be assigned into `namespace` under the name of the grammar// as specified in the source.export function buildGrammar(match, namespace, optOhmGrammarForTesting) { const builder = new Builder(); let decl; let currentRuleName; let currentRuleFormals; let overriding = false; const metaGrammar = optOhmGrammarForTesting || ohmGrammar;
// A visitor that produces a Grammar instance from the CST. const helpers = metaGrammar.createSemantics().addOperation('visit', { Grammars(grammarIter) { return grammarIter.children.map(c => c.visit()); }, Grammar(id, s, _open, rules, _close) { const grammarName = id.visit(); decl = builder.newGrammar(grammarName); s.child(0) && s.child(0).visit(); rules.children.map(c => c.visit()); const g = decl.build(); g.source = this.source.trimmed(); if (namespaceHas(namespace, grammarName)) { throw errors.duplicateGrammarDeclaration(g, namespace); } namespace[grammarName] = g; return g; },
SuperGrammar(_, n) { const superGrammarName = n.visit(); if (superGrammarName === 'null') { decl.withSuperGrammar(null); } else { if (!namespace || !namespaceHas(namespace, superGrammarName)) { throw errors.undeclaredGrammar(superGrammarName, namespace, n.source); } decl.withSuperGrammar(namespace[superGrammarName]); } },
Rule_define(n, fs, d, _, b) { currentRuleName = n.visit(); currentRuleFormals = fs.children.map(c => c.visit())[0] || []; // If there is no default start rule yet, set it now. This must be done before visiting // the body, because it might contain an inline rule definition. if (!decl.defaultStartRule && decl.ensureSuperGrammar() !== Grammar.ProtoBuiltInRules) { decl.withDefaultStartRule(currentRuleName); } const body = b.visit(); const description = d.children.map(c => c.visit())[0]; const source = this.source.trimmed(); return decl.define(currentRuleName, currentRuleFormals, body, description, source); }, Rule_override(n, fs, _, b) { currentRuleName = n.visit(); currentRuleFormals = fs.children.map(c => c.visit())[0] || [];
const source = this.source.trimmed(); decl.ensureSuperGrammarRuleForOverriding(currentRuleName, source);
overriding = true; const body = b.visit(); overriding = false; return decl.override(currentRuleName, currentRuleFormals, body, null, source); }, Rule_extend(n, fs, _, b) { currentRuleName = n.visit(); currentRuleFormals = fs.children.map(c => c.visit())[0] || []; const body = b.visit(); const source = this.source.trimmed(); return decl.extend(currentRuleName, currentRuleFormals, body, null, source); }, RuleBody(_, terms) { return builder.alt(...terms.visit()).withSource(this.source); }, OverrideRuleBody(_, terms) { const args = terms.visit();
// Check if the super-splice operator (`...`) appears in the terms. const expansionPos = args.indexOf(superSplicePlaceholder); if (expansionPos >= 0) { const beforeTerms = args.slice(0, expansionPos); const afterTerms = args.slice(expansionPos + 1);
// Ensure it appears no more than once. afterTerms.forEach(t => { if (t === superSplicePlaceholder) throw errors.multipleSuperSplices(t); });
return new pexprs.Splice( decl.superGrammar, currentRuleName, beforeTerms, afterTerms, ).withSource(this.source); } else { return builder.alt(...args).withSource(this.source); } }, Formals(opointy, fs, cpointy) { return fs.visit(); },
Params(opointy, ps, cpointy) { return ps.visit(); },
Alt(seqs) { return builder.alt(...seqs.visit()).withSource(this.source); },
TopLevelTerm_inline(b, n) { const inlineRuleName = currentRuleName + '_' + n.visit(); const body = b.visit(); const source = this.source.trimmed(); const isNewRuleDeclaration = !( decl.superGrammar && decl.superGrammar.rules[inlineRuleName] ); if (overriding && !isNewRuleDeclaration) { decl.override(inlineRuleName, currentRuleFormals, body, null, source); } else { decl.define(inlineRuleName, currentRuleFormals, body, null, source); } const params = currentRuleFormals.map(formal => builder.app(formal)); return builder.app(inlineRuleName, params).withSource(body.source); }, OverrideTopLevelTerm_superSplice(_) { return superSplicePlaceholder; },
Seq(expr) { return builder.seq(...expr.children.map(c => c.visit())).withSource(this.source); },
Iter_star(x, _) { return builder.star(x.visit()).withSource(this.source); }, Iter_plus(x, _) { return builder.plus(x.visit()).withSource(this.source); }, Iter_opt(x, _) { return builder.opt(x.visit()).withSource(this.source); },
Pred_not(_, x) { return builder.not(x.visit()).withSource(this.source); }, Pred_lookahead(_, x) { return builder.lookahead(x.visit()).withSource(this.source); },
Lex_lex(_, x) { return builder.lex(x.visit()).withSource(this.source); },
Base_application(rule, ps) { const params = ps.children.map(c => c.visit())[0] || []; return builder.app(rule.visit(), params).withSource(this.source); }, Base_range(from, _, to) { return builder.range(from.visit(), to.visit()).withSource(this.source); }, Base_terminal(expr) { return builder.terminal(expr.visit()).withSource(this.source); }, Base_paren(open, x, close) { return x.visit(); },
ruleDescr(open, t, close) { return t.visit(); }, ruleDescrText(_) { return this.sourceString.trim(); },
caseName(_, space1, n, space2, end) { return n.visit(); },
name(first, rest) { return this.sourceString; }, nameFirst(expr) {}, nameRest(expr) {},
terminal(open, cs, close) { return cs.children.map(c => c.visit()).join(''); },
oneCharTerminal(open, c, close) { return c.visit(); },
escapeChar(c) { try { return common.unescapeCodePoint(this.sourceString); } catch (err) { if (err instanceof RangeError && err.message.startsWith('Invalid code point ')) { throw errors.invalidCodePoint(c); } throw err; // Rethrow } },
NonemptyListOf(x, _, xs) { return [x.visit()].concat(xs.children.map(c => c.visit())); }, EmptyListOf() { return []; },
_terminal() { return this.sourceString; }, }); return helpers(match).visit();}