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 %}