Skip to main content
Module

x/ohm_js/src/Builder.js

A library and language for building parsers, interpreters, compilers, etc.
Go to Latest
File
'use strict';
// --------------------------------------------------------------------// Imports// --------------------------------------------------------------------
const Grammar = require('./Grammar');const GrammarDecl = require('./GrammarDecl');const pexprs = require('./pexprs');
// --------------------------------------------------------------------// Private stuff// --------------------------------------------------------------------
function Builder() {}
Builder.prototype = { currentDecl: null, 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); },
la(expr) { // TODO: temporary to still be able to read old recipes return this.lookahead(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; },};
// --------------------------------------------------------------------// Exports// --------------------------------------------------------------------
module.exports = Builder;