ohm-js
Version:
177 lines (154 loc) • 4.76 kB
JavaScript
import {Grammar} from './Grammar.js';
import {GrammarDecl} from './GrammarDecl.js';
import * as pexprs from './pexprs.js';
// --------------------------------------------------------------------
// Private stuff
// --------------------------------------------------------------------
export class Builder {
constructor() {
this.currentDecl = null;
this.currentRuleName = null;
}
newGrammar(name) {
return new GrammarDecl(name);
}
grammar(metaInfo, name, superGrammar, defaultStartRule, rules) {
const gDecl = new GrammarDecl(name);
if (superGrammar) {
// `superGrammar` may be a recipe (i.e. an Array), or an actual grammar instance.
gDecl.withSuperGrammar(
superGrammar instanceof Grammar ? superGrammar : this.fromRecipe(superGrammar),
);
}
if (defaultStartRule) {
gDecl.withDefaultStartRule(defaultStartRule);
}
if (metaInfo && metaInfo.source) {
gDecl.withSource(metaInfo.source);
}
this.currentDecl = gDecl;
Object.keys(rules).forEach(ruleName => {
this.currentRuleName = ruleName;
const ruleRecipe = rules[ruleName];
const action = ruleRecipe[0]; // define/extend/override
const metaInfo = ruleRecipe[1];
const description = ruleRecipe[2];
const formals = ruleRecipe[3];
const body = this.fromRecipe(ruleRecipe[4]);
let source;
if (gDecl.source && metaInfo && metaInfo.sourceInterval) {
source = gDecl.source.subInterval(
metaInfo.sourceInterval[0],
metaInfo.sourceInterval[1] - metaInfo.sourceInterval[0],
);
}
gDecl[action](ruleName, formals, body, description, source);
});
this.currentRuleName = this.currentDecl = null;
return gDecl.build();
}
terminal(x) {
return new pexprs.Terminal(x);
}
range(from, to) {
return new pexprs.Range(from, to);
}
param(index) {
return new pexprs.Param(index);
}
alt(...termArgs) {
let terms = [];
for (let arg of termArgs) {
if (!(arg instanceof pexprs.PExpr)) {
arg = this.fromRecipe(arg);
}
if (arg instanceof pexprs.Alt) {
terms = terms.concat(arg.terms);
} else {
terms.push(arg);
}
}
return terms.length === 1 ? terms[0] : new pexprs.Alt(terms);
}
seq(...factorArgs) {
let factors = [];
for (let arg of factorArgs) {
if (!(arg instanceof pexprs.PExpr)) {
arg = this.fromRecipe(arg);
}
if (arg instanceof pexprs.Seq) {
factors = factors.concat(arg.factors);
} else {
factors.push(arg);
}
}
return factors.length === 1 ? factors[0] : new pexprs.Seq(factors);
}
star(expr) {
if (!(expr instanceof pexprs.PExpr)) {
expr = this.fromRecipe(expr);
}
return new pexprs.Star(expr);
}
plus(expr) {
if (!(expr instanceof pexprs.PExpr)) {
expr = this.fromRecipe(expr);
}
return new pexprs.Plus(expr);
}
opt(expr) {
if (!(expr instanceof pexprs.PExpr)) {
expr = this.fromRecipe(expr);
}
return new pexprs.Opt(expr);
}
not(expr) {
if (!(expr instanceof pexprs.PExpr)) {
expr = this.fromRecipe(expr);
}
return new pexprs.Not(expr);
}
lookahead(expr) {
if (!(expr instanceof pexprs.PExpr)) {
expr = this.fromRecipe(expr);
}
return new pexprs.Lookahead(expr);
}
lex(expr) {
if (!(expr instanceof pexprs.PExpr)) {
expr = this.fromRecipe(expr);
}
return new pexprs.Lex(expr);
}
app(ruleName, optParams) {
if (optParams && optParams.length > 0) {
optParams = optParams.map(function(param) {
return param instanceof pexprs.PExpr ? param : this.fromRecipe(param);
}, this);
}
return new pexprs.Apply(ruleName, optParams);
}
// Note that unlike other methods in this class, this method cannot be used as a
// convenience constructor. It only works with recipes, because it relies on
// `this.currentDecl` and `this.currentRuleName` being set.
splice(beforeTerms, afterTerms) {
return new pexprs.Splice(
this.currentDecl.superGrammar,
this.currentRuleName,
beforeTerms.map(term => this.fromRecipe(term)),
afterTerms.map(term => this.fromRecipe(term)),
);
}
fromRecipe(recipe) {
// the meta-info of 'grammar' is processed in Builder.grammar
const args = recipe[0] === 'grammar' ? recipe.slice(1) : recipe.slice(2);
const result = this[recipe[0]](...args);
const metaInfo = recipe[1];
if (metaInfo) {
if (metaInfo.sourceInterval && this.currentDecl) {
result.withSource(this.currentDecl.sourceInterval(...metaInfo.sourceInterval));
}
}
return result;
}
}