UNPKG

ohm-js

Version:

An object-oriented language for parsing and pattern matching

177 lines (154 loc) 4.76 kB
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; } }