Source: Grammar.js

/*
 *  2016 Maciej Wiecierzewski
 */

/**
 * @constructor
 * @classdesc Rule data
 * @param {type} symbol
 * @param {type} string
 * @param {type} probability
 * @returns {Rule}
 */
function Rule(symbol, string, probability)
{
    this.symbol = symbol;
    this.string = string;
    this.probability = probability;
    this.operator = null;
}

/**
 * @contructor
 * @classdesc Symbol data
 * @param {type} str Symbol's character
 * @param {type} terminal  Is symbol terminal
 * @returns {Symbol}
 */
function Symbol(str, terminal)
{
    this.str = str;
    this.terminal = terminal;
}

/**
 * Generates the grammar given the operators
 * @constructor
 * @param {type} operators
 * @param {type} rulesProb
 * @returns {Grammar}
 */
function Grammar(operators, rulesProb)
{
    if(rulesProb === undefined)
    {
        rulesProb = [];
        rulesProb['endRule'] = 1;
        rulesProb['nextSymbol'] = 1;
        rulesProb['0-aryOperator'] = 1;
        rulesProb['1-aryOperator'] = 1;
        rulesProb['2-aryOperator'] = 1;
        rulesProb['otherwise'] = 1;
    }
    
    function compare(a, b)
    {
        if(a.precedence > b.precedence)
            return 1;
        else if(a.precedence < b.precedence)
            return -1;
        else
            return 0;
    }

    function ruleProb(type, i)
    {
        if(type === "endRule")
            //return 0.01*((1-i*i/(operators.length*operators.length))+10);
            return rulesProb['endRule'];
        else if(type === "nextSymbol")
            //return 1*((1-i*i/(operators.length*operators.length))+0.1);
            return rulesProb['nextSymbol'];
        else if(type === "0-aryOperator")
            return rulesProb['0-aryOperator'];
        else if(type === "1-aryOperator")
            return rulesProb['1-aryOperator'];
        else if(type === "2-aryOperator")
            return rulesProb['2-aryOperator'];
        else
            return rulesProb['otherwise'];
    }

    operators.sort(compare);

    var symbols = [];
    symbols.push(new Symbol("S", false));

    var terminalSymbol = 0;

    var charCode = "A".charCodeAt(0);
    for(var i = 0; i < operators.length; i++)
    {
        var isTerminal = (operators[i].arity == 0) ? true : false;
        symbols.push(new Symbol(String.fromCharCode(charCode+i), isTerminal));

        if(isTerminal)
            terminalSymbol = (symbols.length-1);
    }

    var rules = [];
    var endRules = [];
    var lastSymbol = 0;

    for(var i = 0; i < operators.length; i++)
    {
        var symbol = lastSymbol+1;

        if(symbol === terminalSymbol)
        {var rule = new Rule(symbols[0].str, symbols[symbol].str, 0);
            endRules.push(rules.length-1);
            rules.push(rule);
        }
        else
        {
            var rule = new Rule(symbols[0].str, symbols[symbol].str, ruleProb("nextSymbol", i));
            rules.push(rule);
        }

        lastSymbol = symbol;
    }

    lastSymbol = 0;
    for(var i = 0; i < operators.length; i++)
    {
        var symbol = lastSymbol+1;

        if(symbol != terminalSymbol && lastSymbol > 0)
        {
            var rule = new Rule(symbols[lastSymbol].str, symbols[symbol].str, ruleProb("nextSymbol", i));
            rules.push(rule);
        }

        if(!symbols[lastSymbol].terminal && lastSymbol > 0)
        {
            var rule = new Rule(symbols[lastSymbol].str, symbols[terminalSymbol].str, ruleProb("endRule", i));
            rules.push(rule);
            endRules.push(rules.length-1);
        }

        if(operators[i].arity == 0)
        {
            var rule = new Rule(symbols[symbol].str, operators[i].symbol,
                operators[i].probability);
            rule.operator = operators[i].symbol;
            rules.push(rule);
        }
        else if(operators[i].arity == 1)
        {
            if(operators[i].placement == "prefix")
            {
                var rule = new Rule(symbols[symbol].str,
                    operators[i].symbol+symbols[symbol].str,
                    operators[i].probability);

                rule.operator = operators[i].symbol;
                rules.push(rule);
            }
            else if(operators[i].placement == "postfix")
            {
                var rule = new Rule(symbols[symbol].str,
                    symbols[symbol].str+operators[i].symbol,
                    operators[i].probability);

                rule.operator = operators[i].symbol;
                rules.push(rule);
            }
        }
        else if(operators[i].arity == 2)
        {
            if(operators[i].placement == "prefix")
            {
                var rule = new Rule(symbols[symbol].str,
                    operators[i].symbol+symbols[0].str+symbols[symbol].str,
                    operators[i].probability);

                rule.operator = operators[i].symbol;
                rules.push(rule);
            }
            else if(operators[i].placement == "infix")
            {
                var rule = new Rule(symbols[symbol].str, "", operators[i].probability);

                if(operators[i].associativity == "right")
                    rule.string = symbols[symbol+1].str+operators[i].symbol+symbols[symbol].str;
                else
                    rule.string = symbols[symbol].str+operators[i].symbol+symbols[symbol+1].str;

                rule.operator = operators[i].symbol;
                rules.push(rule);
            }
            else if(operators[i].placement == "postfix")
            {
                var rule = new Rule(symbols[symbol].str,
                    symbols[symbol].str+symbols[0].str+operators[i].symbol,
                    operators[i].probability);
                rule.operator = operators[i].symbol;
                rules.push(rule);
            }
        }

        lastSymbol = symbol;
    }
    
    this.symbols = symbols;
    this.rules = rules;
    this.startSymbol = 0;
    this.endRules = endRules;
    this.operators = operators;
    
    var str = "";
    for(var i = 0; i < rules.length; i++)
    {
        str += i + ". " + rules[i].symbol + " -> " + rules[i].string
                + " (" + rules[i].probability + ")\n";
    }
    console.log(str);
}

/**
 * Returns rules applicable for given symbol
 * @param {type} symbol
 * @returns {Array|Grammar.prototype.getRules.rules}
 */
Grammar.prototype.getRules = function(symbol)
{
    var rules = [];

    for(var i = 0; i < this.rules.length; i++)
    {
        if(this.rules[i].symbol == symbol)
            rules.push(this.rules[i]);
    }

    return rules;
}

/**
 * Returns indexes of rules applicable for given symbol
 * @param {type} symbol
 * @returns {Array|Grammar.prototype.getRules.rules}
 */
Grammar.prototype.getRulesN = function(symbol)
{
    var rulesN = [];

    for(var i = 0; i < this.rules.length; i++)
    {
        if(this.rules[i].symbol == symbol)
            rulesN.push(i);
                    //  ^ here's the difference
    }

    return rulesN;
}