nearley.js grammar is not handling multiplication operator (*) when using functions parsing

33 views Asked by At

I've built a nearley.js grammar - it mostly works for arithmetic (2+3*5) and function calls (round(5.43)) but when I mix the two together, it does not understand arithmetic.

Maybe its something obvious, but I've been trying a lot of different things and haven't found a solution yet. Appreciate any additional suggestions to try.

passing test cases:

        p.feed('2 + 3 * 5 - 4');
        expect(p.finish()[0]).toEqual(13);


        p.feed('round(4.55123, 2)');
        expect(p.finish()[0]).toEqual(4.55)

failing:


        p.feed('round(38 div 5) * 5');
        expect(p.finish()[0][0]).toEqual(35);

Error: Syntax error at line 1 col 17:

1 round(38 div 5) * 5
                  ^

Unexpected "*". Instead, I was expecting to see one of the following:
@builtin "whitespace.ne"
@builtin "string.ne"

@{%

function log(scope, item){
    console.log('LOG:' +scope, JSON.stringify(item, null, 4))
}

function getFirst(d){
    while(Array.isArray(d)){
        d = d[0];
    }
    return d;
}

// IF gets handled in 2 steps
// step 1: if  _ ifParams
// step 2: "(" _ EQ _ "," _ param _ "," _ param ")"
function handleIf(d){ 
    const ifParams = d[2];
    const [condition, trueValue,falseValue] = ifParams;
    if(condition){
        return getFirst(trueValue);
    } 
    return getFirst(falseValue);
}

// "concat" _ commaparentheses
// "(" _ commaparams _ ")"
// (param _ ",":? _):+
function handleConcat(d) {
    return d[2].map(item => ''+item).join(''); 
}

// "selected" _ commaparentheses
function handleSelected(d){
    return d[2][0] === d[2][1];
}


function handleRound(d){
    const value = d[2][0];
    const places = d[2][1] || 1;
    const multiplier = Math.pow(10, places);

    return Math.round(value * multiplier) / multiplier;
}

%}

main -> _ EQ _ {% function(d) {return d[1]; } %}
    | _ MixedFunction _ {% function(d) { return d[1]; } %}
    | _ AS _ {% function(d) { return d[1]; } %}

# PEMDAS!
# We define each level of precedence as a nonterminal.

# Parentheses
P -> "(" _ AS _ ")" {% function(d) {return d[2]; } %}
    | "(" _ ")" {% function(d){ return null; } %}
    | param             {% id %}

# Exponents
E -> P _ "^" _ E    {% function(d) {return Math.pow(d[0], d[4]); } %}
    | P             {% id %}

# Multiplication and division
MD -> MD _ "*" _ E  {% function(d) {return getFirst(d[0]) * getFirst(d[4]); } %}
    | MD _ "div" _ E  {% function(d) {return getFirst(d[0]) / getFirst(d[4]); } %}
    | MD _ "mod" _ E  {% function(d) {return getFirst(d[0]) % getFirst(d[4]); } %}
    | E             {% id %}

# Addition and subtraction
AS -> AS _ "+" _ MD {% function(d) {return getFirst(d[0]) + getFirst(d[4]); } %}
    | AS _ "-" _ MD {% function(d) {return getFirst(d[0]) - getFirst(d[4]); } %}
    | MD            {% id %}

# equality checks
EQ -> EQ _ "=" _ param {% function(d) { return getFirst(d[0]) == getFirst(d[4]); } %}
    | EQ _ ">" _ param {% function(d) { return getFirst(d[0]) > getFirst(d[4]); } %}
    | EQ _ ">=" _ param {% function(d) { return getFirst(d[0]) >= getFirst(d[4]); } %}
    | EQ _ "<" _ param {% function(d) { return getFirst(d[0]) < getFirst(d[4]); } %}
    | EQ _ "<=" _ param {% function(d) { return getFirst(d[0]) <= getFirst(d[4]); } %}
    | EQ _ "and" _ EQ {% function(d) { return getFirst(d[0]) && getFirst(d[4]) } %}
    | EQ _ "or" _ EQ {% function(d) { return getFirst(d[0]) || getFirst(d[4]) } %}
    | AS            {% id %}

# other functions
MixedFunction -> 
      "concat" _ commaparentheses {% handleConcat %}
    | "if" _ ifParams {% handleIf %}
    | "selected" _ commaparentheses {% handleSelected %}
    | "today" _ commaparentheses {% function() { return new Date(); } %}
    | "now" _ commaparentheses {% function() { return new Date(); } %}
    | "once" _ commaparentheses {% function(d){ return d[2]; } %}

    | "sin" _ P     {% function(d) {return Math.sin(d[2]); } %}
    | "cos" _ P     {% function(d) {return Math.cos(d[2]); } %}
    | "tan" _ P     {% function(d) {return Math.tan(d[2]); } %}
    
    | "asin" _ P    {% function(d) {return Math.asin(d[2]); } %}
    | "acos" _ P    {% function(d) {return Math.acos(d[2]); } %}
    | "atan" _ P    {% function(d) {return Math.atan(d[2]); } %}

    | "pi" _ P      {% function(d) {return Math.PI; } %}
    | "sqrt" _ P    {% function(d) {return Math.sqrt(d[2]); } %}
    | "log" _ P      {% function(d) {return Math.log(d[2]); }  %}
    | "round" _ commaparentheses      {% handleRound  %}

ifParams -> "(" _ EQ _ "," _ complexParam _ "," _ complexParam ")" {% function(d){
    return [
        d[2],
        d[6],
        d[10],
    ]
} %}

# function helpers
commaparentheses -> "(" _ commaparams:? _ ")" {% function(d){ return d[2]; } %}

commaparams -> (complexParam _ ",":? _):+ {% function(d) { 
    return d[0].map(item => getFirst(item)); 
}%}

complexParam -> param | MixedFunction | EQ {% id %}

param -> float | bool | string  {% id %}

bool -> "true"i | "false"i {% id %}

float ->
      int "." int   {% function(d) {return parseFloat(d[0] + d[1] + d[2])} %}
    | int           {% function(d) {return parseInt(d[0])} %}

int -> [0-9]:+        {% function(d) {return d[0].join(""); } %}

string -> dqstring |sqstring {% id %}
0

There are 0 answers