mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
* Expand Tinymath grammar, including named arguments * Add tsconfig project * Fix tests * Allow named arguments with numeric types * Remove dashes from named argument validation * Fix license header Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> # Conflicts: # .github/CODEOWNERS Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
852bf31683
commit
4fe5fe0704
17 changed files with 711 additions and 377 deletions
|
@ -4,6 +4,7 @@
|
|||
"license": "SSPL-1.0 OR Elastic License 2.0",
|
||||
"private": true,
|
||||
"main": "src/index.js",
|
||||
"types": "tinymath.d.ts",
|
||||
"scripts": {
|
||||
"kbn:bootstrap": "yarn build",
|
||||
"build": "../../node_modules/.bin/pegjs -o src/grammar.js src/grammar.pegjs"
|
||||
|
|
|
@ -156,11 +156,21 @@ function peg$parse(input, options) {
|
|||
peg$c12 = function(literal) {
|
||||
return literal;
|
||||
},
|
||||
peg$c13 = function(first, rest) { // We can open this up later. Strict for now.
|
||||
return first + rest.join('');
|
||||
peg$c13 = function(chars) {
|
||||
return {
|
||||
type: 'variable',
|
||||
value: chars.join(''),
|
||||
location: simpleLocation(location()),
|
||||
text: text()
|
||||
};
|
||||
},
|
||||
peg$c14 = function(first, mid) {
|
||||
return first + mid.map(m => m[0].join('') + m[1].join('')).join('')
|
||||
peg$c14 = function(rest) {
|
||||
return {
|
||||
type: 'variable',
|
||||
value: rest.join(''),
|
||||
location: simpleLocation(location()),
|
||||
text: text()
|
||||
};
|
||||
},
|
||||
peg$c15 = "+",
|
||||
peg$c16 = peg$literalExpectation("+", false),
|
||||
|
@ -168,8 +178,11 @@ function peg$parse(input, options) {
|
|||
peg$c18 = peg$literalExpectation("-", false),
|
||||
peg$c19 = function(left, rest) {
|
||||
return rest.reduce((acc, curr) => ({
|
||||
type: 'function',
|
||||
name: curr[0] === '+' ? 'add' : 'subtract',
|
||||
args: [acc, curr[1]]
|
||||
args: [acc, curr[1]],
|
||||
location: simpleLocation(location()),
|
||||
text: text()
|
||||
}), left)
|
||||
},
|
||||
peg$c20 = "*",
|
||||
|
@ -178,8 +191,11 @@ function peg$parse(input, options) {
|
|||
peg$c23 = peg$literalExpectation("/", false),
|
||||
peg$c24 = function(left, rest) {
|
||||
return rest.reduce((acc, curr) => ({
|
||||
type: 'function',
|
||||
name: curr[0] === '*' ? 'multiply' : 'divide',
|
||||
args: [acc, curr[1]]
|
||||
args: [acc, curr[1]],
|
||||
location: simpleLocation(location()),
|
||||
text: text()
|
||||
}), left)
|
||||
},
|
||||
peg$c25 = "(",
|
||||
|
@ -196,25 +212,51 @@ function peg$parse(input, options) {
|
|||
peg$c34 = function(first, rest) {
|
||||
return [first].concat(rest);
|
||||
},
|
||||
peg$c35 = peg$otherExpectation("function"),
|
||||
peg$c36 = /^[a-z]/,
|
||||
peg$c37 = peg$classExpectation([["a", "z"]], false, false),
|
||||
peg$c38 = function(name, args) {
|
||||
return {name: name.join(''), args: args || []};
|
||||
peg$c35 = /^["]/,
|
||||
peg$c36 = peg$classExpectation(["\""], false, false),
|
||||
peg$c37 = function(value) { return value.join(''); },
|
||||
peg$c38 = /^[']/,
|
||||
peg$c39 = peg$classExpectation(["'"], false, false),
|
||||
peg$c40 = /^[a-zA-Z_]/,
|
||||
peg$c41 = peg$classExpectation([["a", "z"], ["A", "Z"], "_"], false, false),
|
||||
peg$c42 = "=",
|
||||
peg$c43 = peg$literalExpectation("=", false),
|
||||
peg$c44 = function(name, value) {
|
||||
return {
|
||||
type: 'namedArgument',
|
||||
name: name.join(''),
|
||||
value: value,
|
||||
location: simpleLocation(location()),
|
||||
text: text()
|
||||
};
|
||||
},
|
||||
peg$c45 = peg$otherExpectation("function"),
|
||||
peg$c46 = /^[a-zA-Z_\-]/,
|
||||
peg$c47 = peg$classExpectation([["a", "z"], ["A", "Z"], "_", "-"], false, false),
|
||||
peg$c48 = function(name, args) {
|
||||
return {
|
||||
type: 'function',
|
||||
name: name.join(''),
|
||||
args: args || [],
|
||||
location: simpleLocation(location()),
|
||||
text: text()
|
||||
};
|
||||
},
|
||||
peg$c39 = peg$otherExpectation("number"),
|
||||
peg$c40 = function() { return parseFloat(text()); },
|
||||
peg$c41 = /^[eE]/,
|
||||
peg$c42 = peg$classExpectation(["e", "E"], false, false),
|
||||
peg$c43 = peg$otherExpectation("exponent"),
|
||||
peg$c44 = ".",
|
||||
peg$c45 = peg$literalExpectation(".", false),
|
||||
peg$c46 = "0",
|
||||
peg$c47 = peg$literalExpectation("0", false),
|
||||
peg$c48 = /^[1-9]/,
|
||||
peg$c49 = peg$classExpectation([["1", "9"]], false, false),
|
||||
peg$c50 = /^[0-9]/,
|
||||
peg$c51 = peg$classExpectation([["0", "9"]], false, false),
|
||||
peg$c49 = peg$otherExpectation("number"),
|
||||
peg$c50 = function() {
|
||||
return parseFloat(text());
|
||||
},
|
||||
peg$c51 = /^[eE]/,
|
||||
peg$c52 = peg$classExpectation(["e", "E"], false, false),
|
||||
peg$c53 = peg$otherExpectation("exponent"),
|
||||
peg$c54 = ".",
|
||||
peg$c55 = peg$literalExpectation(".", false),
|
||||
peg$c56 = "0",
|
||||
peg$c57 = peg$literalExpectation("0", false),
|
||||
peg$c58 = /^[1-9]/,
|
||||
peg$c59 = peg$classExpectation([["1", "9"]], false, false),
|
||||
peg$c60 = /^[0-9]/,
|
||||
peg$c61 = peg$classExpectation([["0", "9"]], false, false),
|
||||
|
||||
peg$currPos = 0,
|
||||
peg$savedPos = 0,
|
||||
|
@ -456,10 +498,7 @@ function peg$parse(input, options) {
|
|||
if (s1 !== peg$FAILED) {
|
||||
s2 = peg$parseNumber();
|
||||
if (s2 === peg$FAILED) {
|
||||
s2 = peg$parseVariableWithQuote();
|
||||
if (s2 === peg$FAILED) {
|
||||
s2 = peg$parseVariable();
|
||||
}
|
||||
s2 = peg$parseVariable();
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
s3 = peg$parse_();
|
||||
|
@ -489,129 +528,33 @@ function peg$parse(input, options) {
|
|||
}
|
||||
|
||||
function peg$parseVariable() {
|
||||
var s0, s1, s2, s3, s4;
|
||||
|
||||
s0 = peg$currPos;
|
||||
s1 = peg$parse_();
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = peg$parseStartChar();
|
||||
if (s2 !== peg$FAILED) {
|
||||
s3 = [];
|
||||
s4 = peg$parseValidChar();
|
||||
while (s4 !== peg$FAILED) {
|
||||
s3.push(s4);
|
||||
s4 = peg$parseValidChar();
|
||||
}
|
||||
if (s3 !== peg$FAILED) {
|
||||
s4 = peg$parse_();
|
||||
if (s4 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c13(s2, s3);
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
|
||||
return s0;
|
||||
}
|
||||
|
||||
function peg$parseVariableWithQuote() {
|
||||
var s0, s1, s2, s3, s4, s5, s6, s7, s8;
|
||||
var s0, s1, s2, s3, s4, s5;
|
||||
|
||||
s0 = peg$currPos;
|
||||
s1 = peg$parse_();
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = peg$parseQuote();
|
||||
if (s2 !== peg$FAILED) {
|
||||
s3 = peg$parseStartChar();
|
||||
s3 = [];
|
||||
s4 = peg$parseValidChar();
|
||||
if (s4 === peg$FAILED) {
|
||||
s4 = peg$parseSpace();
|
||||
}
|
||||
while (s4 !== peg$FAILED) {
|
||||
s3.push(s4);
|
||||
s4 = peg$parseValidChar();
|
||||
if (s4 === peg$FAILED) {
|
||||
s4 = peg$parseSpace();
|
||||
}
|
||||
}
|
||||
if (s3 !== peg$FAILED) {
|
||||
s4 = [];
|
||||
s5 = peg$currPos;
|
||||
s6 = [];
|
||||
s7 = peg$parseSpace();
|
||||
while (s7 !== peg$FAILED) {
|
||||
s6.push(s7);
|
||||
s7 = peg$parseSpace();
|
||||
}
|
||||
if (s6 !== peg$FAILED) {
|
||||
s7 = [];
|
||||
s8 = peg$parseValidChar();
|
||||
if (s8 !== peg$FAILED) {
|
||||
while (s8 !== peg$FAILED) {
|
||||
s7.push(s8);
|
||||
s8 = peg$parseValidChar();
|
||||
}
|
||||
} else {
|
||||
s7 = peg$FAILED;
|
||||
}
|
||||
if (s7 !== peg$FAILED) {
|
||||
s6 = [s6, s7];
|
||||
s5 = s6;
|
||||
} else {
|
||||
peg$currPos = s5;
|
||||
s5 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s5;
|
||||
s5 = peg$FAILED;
|
||||
}
|
||||
while (s5 !== peg$FAILED) {
|
||||
s4.push(s5);
|
||||
s5 = peg$currPos;
|
||||
s6 = [];
|
||||
s7 = peg$parseSpace();
|
||||
while (s7 !== peg$FAILED) {
|
||||
s6.push(s7);
|
||||
s7 = peg$parseSpace();
|
||||
}
|
||||
if (s6 !== peg$FAILED) {
|
||||
s7 = [];
|
||||
s8 = peg$parseValidChar();
|
||||
if (s8 !== peg$FAILED) {
|
||||
while (s8 !== peg$FAILED) {
|
||||
s7.push(s8);
|
||||
s8 = peg$parseValidChar();
|
||||
}
|
||||
} else {
|
||||
s7 = peg$FAILED;
|
||||
}
|
||||
if (s7 !== peg$FAILED) {
|
||||
s6 = [s6, s7];
|
||||
s5 = s6;
|
||||
} else {
|
||||
peg$currPos = s5;
|
||||
s5 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s5;
|
||||
s5 = peg$FAILED;
|
||||
}
|
||||
}
|
||||
s4 = peg$parseQuote();
|
||||
if (s4 !== peg$FAILED) {
|
||||
s5 = peg$parseQuote();
|
||||
s5 = peg$parse_();
|
||||
if (s5 !== peg$FAILED) {
|
||||
s6 = peg$parse_();
|
||||
if (s6 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c14(s3, s4);
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c13(s3);
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
|
@ -632,6 +575,39 @@ function peg$parse(input, options) {
|
|||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$currPos;
|
||||
s1 = peg$parse_();
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = [];
|
||||
s3 = peg$parseValidChar();
|
||||
if (s3 !== peg$FAILED) {
|
||||
while (s3 !== peg$FAILED) {
|
||||
s2.push(s3);
|
||||
s3 = peg$parseValidChar();
|
||||
}
|
||||
} else {
|
||||
s2 = peg$FAILED;
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
s3 = peg$parse_();
|
||||
if (s3 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c14(s2);
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
return s0;
|
||||
}
|
||||
|
@ -911,114 +887,102 @@ function peg$parse(input, options) {
|
|||
return s0;
|
||||
}
|
||||
|
||||
function peg$parseArguments() {
|
||||
var s0, s1, s2, s3, s4, s5, s6, s7, s8;
|
||||
function peg$parseArgument_List() {
|
||||
var s0, s1, s2, s3, s4, s5, s6, s7;
|
||||
|
||||
peg$silentFails++;
|
||||
s0 = peg$currPos;
|
||||
s1 = peg$parse_();
|
||||
s1 = peg$parseArgument();
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = peg$parseAddSubtract();
|
||||
if (s2 !== peg$FAILED) {
|
||||
s3 = [];
|
||||
s4 = peg$currPos;
|
||||
s5 = peg$parse_();
|
||||
s2 = [];
|
||||
s3 = peg$currPos;
|
||||
s4 = peg$parse_();
|
||||
if (s4 !== peg$FAILED) {
|
||||
if (input.charCodeAt(peg$currPos) === 44) {
|
||||
s5 = peg$c31;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s5 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c32); }
|
||||
}
|
||||
if (s5 !== peg$FAILED) {
|
||||
if (input.charCodeAt(peg$currPos) === 44) {
|
||||
s6 = peg$c31;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s6 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c32); }
|
||||
}
|
||||
s6 = peg$parse_();
|
||||
if (s6 !== peg$FAILED) {
|
||||
s7 = peg$parse_();
|
||||
s7 = peg$parseArgument();
|
||||
if (s7 !== peg$FAILED) {
|
||||
s8 = peg$parseAddSubtract();
|
||||
if (s8 !== peg$FAILED) {
|
||||
peg$savedPos = s4;
|
||||
s5 = peg$c33(s2, s8);
|
||||
s4 = s5;
|
||||
} else {
|
||||
peg$currPos = s4;
|
||||
s4 = peg$FAILED;
|
||||
}
|
||||
peg$savedPos = s3;
|
||||
s4 = peg$c33(s1, s7);
|
||||
s3 = s4;
|
||||
} else {
|
||||
peg$currPos = s4;
|
||||
s4 = peg$FAILED;
|
||||
peg$currPos = s3;
|
||||
s3 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s4;
|
||||
s4 = peg$FAILED;
|
||||
peg$currPos = s3;
|
||||
s3 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s4;
|
||||
s4 = peg$FAILED;
|
||||
peg$currPos = s3;
|
||||
s3 = peg$FAILED;
|
||||
}
|
||||
while (s4 !== peg$FAILED) {
|
||||
s3.push(s4);
|
||||
s4 = peg$currPos;
|
||||
s5 = peg$parse_();
|
||||
} else {
|
||||
peg$currPos = s3;
|
||||
s3 = peg$FAILED;
|
||||
}
|
||||
while (s3 !== peg$FAILED) {
|
||||
s2.push(s3);
|
||||
s3 = peg$currPos;
|
||||
s4 = peg$parse_();
|
||||
if (s4 !== peg$FAILED) {
|
||||
if (input.charCodeAt(peg$currPos) === 44) {
|
||||
s5 = peg$c31;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s5 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c32); }
|
||||
}
|
||||
if (s5 !== peg$FAILED) {
|
||||
if (input.charCodeAt(peg$currPos) === 44) {
|
||||
s6 = peg$c31;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s6 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c32); }
|
||||
}
|
||||
s6 = peg$parse_();
|
||||
if (s6 !== peg$FAILED) {
|
||||
s7 = peg$parse_();
|
||||
s7 = peg$parseArgument();
|
||||
if (s7 !== peg$FAILED) {
|
||||
s8 = peg$parseAddSubtract();
|
||||
if (s8 !== peg$FAILED) {
|
||||
peg$savedPos = s4;
|
||||
s5 = peg$c33(s2, s8);
|
||||
s4 = s5;
|
||||
} else {
|
||||
peg$currPos = s4;
|
||||
s4 = peg$FAILED;
|
||||
}
|
||||
peg$savedPos = s3;
|
||||
s4 = peg$c33(s1, s7);
|
||||
s3 = s4;
|
||||
} else {
|
||||
peg$currPos = s4;
|
||||
s4 = peg$FAILED;
|
||||
peg$currPos = s3;
|
||||
s3 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s4;
|
||||
s4 = peg$FAILED;
|
||||
peg$currPos = s3;
|
||||
s3 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s4;
|
||||
s4 = peg$FAILED;
|
||||
peg$currPos = s3;
|
||||
s3 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s3;
|
||||
s3 = peg$FAILED;
|
||||
}
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
s3 = peg$parse_();
|
||||
if (s3 !== peg$FAILED) {
|
||||
s4 = peg$parse_();
|
||||
if (input.charCodeAt(peg$currPos) === 44) {
|
||||
s4 = peg$c31;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s4 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c32); }
|
||||
}
|
||||
if (s4 === peg$FAILED) {
|
||||
s4 = null;
|
||||
}
|
||||
if (s4 !== peg$FAILED) {
|
||||
if (input.charCodeAt(peg$currPos) === 44) {
|
||||
s5 = peg$c31;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s5 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c32); }
|
||||
}
|
||||
if (s5 === peg$FAILED) {
|
||||
s5 = null;
|
||||
}
|
||||
if (s5 !== peg$FAILED) {
|
||||
s6 = peg$parse_();
|
||||
if (s6 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c34(s2, s3);
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c34(s1, s2);
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
|
@ -1044,6 +1008,199 @@ function peg$parse(input, options) {
|
|||
return s0;
|
||||
}
|
||||
|
||||
function peg$parseString() {
|
||||
var s0, s1, s2, s3;
|
||||
|
||||
s0 = peg$currPos;
|
||||
if (peg$c35.test(input.charAt(peg$currPos))) {
|
||||
s1 = input.charAt(peg$currPos);
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c36); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = [];
|
||||
s3 = peg$parseValidChar();
|
||||
if (s3 !== peg$FAILED) {
|
||||
while (s3 !== peg$FAILED) {
|
||||
s2.push(s3);
|
||||
s3 = peg$parseValidChar();
|
||||
}
|
||||
} else {
|
||||
s2 = peg$FAILED;
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
if (peg$c35.test(input.charAt(peg$currPos))) {
|
||||
s3 = input.charAt(peg$currPos);
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s3 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c36); }
|
||||
}
|
||||
if (s3 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c37(s2);
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$currPos;
|
||||
if (peg$c38.test(input.charAt(peg$currPos))) {
|
||||
s1 = input.charAt(peg$currPos);
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c39); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = [];
|
||||
s3 = peg$parseValidChar();
|
||||
if (s3 !== peg$FAILED) {
|
||||
while (s3 !== peg$FAILED) {
|
||||
s2.push(s3);
|
||||
s3 = peg$parseValidChar();
|
||||
}
|
||||
} else {
|
||||
s2 = peg$FAILED;
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
if (peg$c38.test(input.charAt(peg$currPos))) {
|
||||
s3 = input.charAt(peg$currPos);
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s3 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c39); }
|
||||
}
|
||||
if (s3 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c37(s2);
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$currPos;
|
||||
s1 = [];
|
||||
s2 = peg$parseValidChar();
|
||||
if (s2 !== peg$FAILED) {
|
||||
while (s2 !== peg$FAILED) {
|
||||
s1.push(s2);
|
||||
s2 = peg$parseValidChar();
|
||||
}
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c37(s1);
|
||||
}
|
||||
s0 = s1;
|
||||
}
|
||||
}
|
||||
|
||||
return s0;
|
||||
}
|
||||
|
||||
function peg$parseArgument() {
|
||||
var s0, s1, s2, s3, s4, s5, s6;
|
||||
|
||||
s0 = peg$currPos;
|
||||
s1 = [];
|
||||
if (peg$c40.test(input.charAt(peg$currPos))) {
|
||||
s2 = input.charAt(peg$currPos);
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s2 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c41); }
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
while (s2 !== peg$FAILED) {
|
||||
s1.push(s2);
|
||||
if (peg$c40.test(input.charAt(peg$currPos))) {
|
||||
s2 = input.charAt(peg$currPos);
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s2 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c41); }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = peg$parse_();
|
||||
if (s2 !== peg$FAILED) {
|
||||
if (input.charCodeAt(peg$currPos) === 61) {
|
||||
s3 = peg$c42;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s3 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c43); }
|
||||
}
|
||||
if (s3 !== peg$FAILED) {
|
||||
s4 = peg$parse_();
|
||||
if (s4 !== peg$FAILED) {
|
||||
s5 = peg$parseNumber();
|
||||
if (s5 === peg$FAILED) {
|
||||
s5 = peg$parseString();
|
||||
}
|
||||
if (s5 !== peg$FAILED) {
|
||||
s6 = peg$parse_();
|
||||
if (s6 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c44(s1, s5);
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$parseAddSubtract();
|
||||
}
|
||||
|
||||
return s0;
|
||||
}
|
||||
|
||||
function peg$parseFunction() {
|
||||
var s0, s1, s2, s3, s4, s5, s6, s7, s8;
|
||||
|
||||
|
@ -1052,22 +1209,22 @@ function peg$parse(input, options) {
|
|||
s1 = peg$parse_();
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = [];
|
||||
if (peg$c36.test(input.charAt(peg$currPos))) {
|
||||
if (peg$c46.test(input.charAt(peg$currPos))) {
|
||||
s3 = input.charAt(peg$currPos);
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s3 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c37); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c47); }
|
||||
}
|
||||
if (s3 !== peg$FAILED) {
|
||||
while (s3 !== peg$FAILED) {
|
||||
s2.push(s3);
|
||||
if (peg$c36.test(input.charAt(peg$currPos))) {
|
||||
if (peg$c46.test(input.charAt(peg$currPos))) {
|
||||
s3 = input.charAt(peg$currPos);
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s3 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c37); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c47); }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -1084,7 +1241,7 @@ function peg$parse(input, options) {
|
|||
if (s3 !== peg$FAILED) {
|
||||
s4 = peg$parse_();
|
||||
if (s4 !== peg$FAILED) {
|
||||
s5 = peg$parseArguments();
|
||||
s5 = peg$parseArgument_List();
|
||||
if (s5 === peg$FAILED) {
|
||||
s5 = null;
|
||||
}
|
||||
|
@ -1102,7 +1259,7 @@ function peg$parse(input, options) {
|
|||
s8 = peg$parse_();
|
||||
if (s8 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c38(s2, s5);
|
||||
s1 = peg$c48(s2, s5);
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
|
@ -1139,7 +1296,7 @@ function peg$parse(input, options) {
|
|||
peg$silentFails--;
|
||||
if (s0 === peg$FAILED) {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c35); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c45); }
|
||||
}
|
||||
|
||||
return s0;
|
||||
|
@ -1174,7 +1331,7 @@ function peg$parse(input, options) {
|
|||
}
|
||||
if (s4 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c40();
|
||||
s1 = peg$c50();
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
|
@ -1195,7 +1352,7 @@ function peg$parse(input, options) {
|
|||
peg$silentFails--;
|
||||
if (s0 === peg$FAILED) {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c39); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c49); }
|
||||
}
|
||||
|
||||
return s0;
|
||||
|
@ -1204,12 +1361,12 @@ function peg$parse(input, options) {
|
|||
function peg$parseE() {
|
||||
var s0;
|
||||
|
||||
if (peg$c41.test(input.charAt(peg$currPos))) {
|
||||
if (peg$c51.test(input.charAt(peg$currPos))) {
|
||||
s0 = input.charAt(peg$currPos);
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s0 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c42); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c52); }
|
||||
}
|
||||
|
||||
return s0;
|
||||
|
@ -1261,7 +1418,7 @@ function peg$parse(input, options) {
|
|||
peg$silentFails--;
|
||||
if (s0 === peg$FAILED) {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c43); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c53); }
|
||||
}
|
||||
|
||||
return s0;
|
||||
|
@ -1272,11 +1429,11 @@ function peg$parse(input, options) {
|
|||
|
||||
s0 = peg$currPos;
|
||||
if (input.charCodeAt(peg$currPos) === 46) {
|
||||
s1 = peg$c44;
|
||||
s1 = peg$c54;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c45); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c55); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = [];
|
||||
|
@ -1308,20 +1465,20 @@ function peg$parse(input, options) {
|
|||
var s0, s1, s2, s3;
|
||||
|
||||
if (input.charCodeAt(peg$currPos) === 48) {
|
||||
s0 = peg$c46;
|
||||
s0 = peg$c56;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s0 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c47); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c57); }
|
||||
}
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$currPos;
|
||||
if (peg$c48.test(input.charAt(peg$currPos))) {
|
||||
if (peg$c58.test(input.charAt(peg$currPos))) {
|
||||
s1 = input.charAt(peg$currPos);
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c49); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c59); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = [];
|
||||
|
@ -1349,17 +1506,30 @@ function peg$parse(input, options) {
|
|||
function peg$parseDigit() {
|
||||
var s0;
|
||||
|
||||
if (peg$c50.test(input.charAt(peg$currPos))) {
|
||||
if (peg$c60.test(input.charAt(peg$currPos))) {
|
||||
s0 = input.charAt(peg$currPos);
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s0 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c51); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c61); }
|
||||
}
|
||||
|
||||
return s0;
|
||||
}
|
||||
|
||||
|
||||
function simpleLocation (location) {
|
||||
// Returns an object representing the position of the function within the expression,
|
||||
// demarcated by the position of its first character and last character. We calculate these values
|
||||
// using the offset because the expression could span multiple lines, and we don't want to deal
|
||||
// with column and line values.
|
||||
return {
|
||||
min: location.start.offset,
|
||||
max: location.end.offset
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
peg$result = peg$startRuleFunction();
|
||||
|
||||
if (peg$result !== peg$FAILED && peg$currPos === input.length) {
|
||||
|
|
|
@ -1,5 +1,18 @@
|
|||
// tinymath parsing grammar
|
||||
|
||||
{
|
||||
function simpleLocation (location) {
|
||||
// Returns an object representing the position of the function within the expression,
|
||||
// demarcated by the position of its first character and last character. We calculate these values
|
||||
// using the offset because the expression could span multiple lines, and we don't want to deal
|
||||
// with column and line values.
|
||||
return {
|
||||
min: location.start.offset,
|
||||
max: location.end.offset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start
|
||||
= Expression
|
||||
|
||||
|
@ -23,18 +36,28 @@ ValidChar
|
|||
// literals and variables
|
||||
|
||||
Literal "literal"
|
||||
= _ literal:(Number / VariableWithQuote / Variable) _ {
|
||||
= _ literal:(Number / Variable) _ {
|
||||
return literal;
|
||||
}
|
||||
|
||||
// Quoted variables are interpreted as strings
|
||||
// but unquoted variables are more restrictive
|
||||
Variable
|
||||
= _ first:StartChar rest:ValidChar* _ { // We can open this up later. Strict for now.
|
||||
return first + rest.join('');
|
||||
= _ Quote chars:(ValidChar / Space)* Quote _ {
|
||||
return {
|
||||
type: 'variable',
|
||||
value: chars.join(''),
|
||||
location: simpleLocation(location()),
|
||||
text: text()
|
||||
};
|
||||
}
|
||||
|
||||
VariableWithQuote
|
||||
= _ Quote first:StartChar mid:(Space* ValidChar+)* Quote _ {
|
||||
return first + mid.map(m => m[0].join('') + m[1].join('')).join('')
|
||||
/ _ rest:ValidChar+ _ {
|
||||
return {
|
||||
type: 'variable',
|
||||
value: rest.join(''),
|
||||
location: simpleLocation(location()),
|
||||
text: text()
|
||||
};
|
||||
}
|
||||
|
||||
// expressions
|
||||
|
@ -45,16 +68,22 @@ Expression
|
|||
AddSubtract
|
||||
= _ left:MultiplyDivide rest:(('+' / '-') MultiplyDivide)* _ {
|
||||
return rest.reduce((acc, curr) => ({
|
||||
type: 'function',
|
||||
name: curr[0] === '+' ? 'add' : 'subtract',
|
||||
args: [acc, curr[1]]
|
||||
args: [acc, curr[1]],
|
||||
location: simpleLocation(location()),
|
||||
text: text()
|
||||
}), left)
|
||||
}
|
||||
|
||||
MultiplyDivide
|
||||
= _ left:Factor rest:(('*' / '/') Factor)* _ {
|
||||
return rest.reduce((acc, curr) => ({
|
||||
type: 'function',
|
||||
name: curr[0] === '*' ? 'multiply' : 'divide',
|
||||
args: [acc, curr[1]]
|
||||
args: [acc, curr[1]],
|
||||
location: simpleLocation(location()),
|
||||
text: text()
|
||||
}), left)
|
||||
}
|
||||
|
||||
|
@ -68,20 +97,46 @@ Group
|
|||
return expr
|
||||
}
|
||||
|
||||
Arguments "arguments"
|
||||
= _ first:Expression rest:(_ ',' _ arg:Expression {return arg})* _ ','? _ {
|
||||
Argument_List "arguments"
|
||||
= first:Argument rest:(_ ',' _ arg:Argument {return arg})* _ ','? {
|
||||
return [first].concat(rest);
|
||||
}
|
||||
|
||||
String
|
||||
= [\"] value:(ValidChar)+ [\"] { return value.join(''); }
|
||||
/ [\'] value:(ValidChar)+ [\'] { return value.join(''); }
|
||||
/ value:(ValidChar)+ { return value.join(''); }
|
||||
|
||||
|
||||
Argument
|
||||
= name:[a-zA-Z_]+ _ '=' _ value:(Number / String) _ {
|
||||
return {
|
||||
type: 'namedArgument',
|
||||
name: name.join(''),
|
||||
value: value,
|
||||
location: simpleLocation(location()),
|
||||
text: text()
|
||||
};
|
||||
}
|
||||
/ arg:Expression
|
||||
|
||||
Function "function"
|
||||
= _ name:[a-z]+ '(' _ args:Arguments? _ ')' _ {
|
||||
return {name: name.join(''), args: args || []};
|
||||
= _ name:[a-zA-Z_-]+ '(' _ args:Argument_List? _ ')' _ {
|
||||
return {
|
||||
type: 'function',
|
||||
name: name.join(''),
|
||||
args: args || [],
|
||||
location: simpleLocation(location()),
|
||||
text: text()
|
||||
};
|
||||
}
|
||||
|
||||
// Numbers. Lol.
|
||||
|
||||
Number "number"
|
||||
= '-'? Integer Fraction? Exp? { return parseFloat(text()); }
|
||||
= '-'? Integer Fraction? Exp? {
|
||||
return parseFloat(text());
|
||||
}
|
||||
|
||||
E
|
||||
= [eE]
|
||||
|
|
|
@ -38,17 +38,22 @@ function interpret(node, scope, injectedFunctions) {
|
|||
return exec(node);
|
||||
|
||||
function exec(node) {
|
||||
const type = getType(node);
|
||||
if (typeof node === 'number') {
|
||||
return node;
|
||||
}
|
||||
|
||||
if (type === 'function') return invoke(node);
|
||||
if (node.type === 'function') return invoke(node);
|
||||
|
||||
if (type === 'string') {
|
||||
const val = getValue(scope, node);
|
||||
if (typeof val === 'undefined') throw new Error(`Unknown variable: ${node}`);
|
||||
if (node.type === 'variable') {
|
||||
const val = getValue(scope, node.value);
|
||||
if (typeof val === 'undefined') throw new Error(`Unknown variable: ${node.value}`);
|
||||
return val;
|
||||
}
|
||||
|
||||
return node; // Can only be a number at this point
|
||||
if (node.type === 'namedArgument') {
|
||||
// We are ignoring named arguments in the interpreter
|
||||
throw new Error(`Named arguments are not supported in tinymath itself, at ${node.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
function invoke(node) {
|
||||
|
@ -67,17 +72,6 @@ function getValue(scope, node) {
|
|||
return typeof val !== 'undefined' ? val : scope[node];
|
||||
}
|
||||
|
||||
function getType(x) {
|
||||
const type = typeof x;
|
||||
if (type === 'object') {
|
||||
const keys = Object.keys(x);
|
||||
if (keys.length !== 2 || !x.name || !x.args) throw new Error('Invalid AST object');
|
||||
return 'function';
|
||||
}
|
||||
if (type === 'string' || type === 'number') return type;
|
||||
throw new Error(`Unknown AST property type: ${type}`);
|
||||
}
|
||||
|
||||
function isOperable(args) {
|
||||
return args.every((arg) => {
|
||||
if (Array.isArray(arg)) return isOperable(arg);
|
||||
|
|
|
@ -11,7 +11,19 @@
|
|||
Need tests for spacing, etc
|
||||
*/
|
||||
|
||||
const { evaluate, parse } = require('..');
|
||||
import { evaluate, parse } from '..';
|
||||
|
||||
function variableEqual(value) {
|
||||
return expect.objectContaining({ type: 'variable', value });
|
||||
}
|
||||
|
||||
function functionEqual(name, args) {
|
||||
return expect.objectContaining({ type: 'function', name, args });
|
||||
}
|
||||
|
||||
function namedArgumentEqual(name, value) {
|
||||
return expect.objectContaining({ type: 'namedArgument', name, value });
|
||||
}
|
||||
|
||||
describe('Parser', () => {
|
||||
describe('Numbers', () => {
|
||||
|
@ -31,96 +43,144 @@ describe('Parser', () => {
|
|||
|
||||
describe('Variables', () => {
|
||||
it('strings', () => {
|
||||
expect(parse('f')).toEqual('f');
|
||||
expect(parse('foo')).toEqual('foo');
|
||||
expect(parse('f')).toEqual(variableEqual('f'));
|
||||
expect(parse('foo')).toEqual(variableEqual('foo'));
|
||||
expect(parse('foo1')).toEqual(variableEqual('foo1'));
|
||||
expect(() => parse('1foo1')).toThrow('but "f" found');
|
||||
});
|
||||
|
||||
it('strings with spaces', () => {
|
||||
expect(parse(' foo ')).toEqual(variableEqual('foo'));
|
||||
expect(() => parse(' foo bar ')).toThrow('but "b" found');
|
||||
});
|
||||
|
||||
it('allowed characters', () => {
|
||||
expect(parse('_foo')).toEqual('_foo');
|
||||
expect(parse('@foo')).toEqual('@foo');
|
||||
expect(parse('.foo')).toEqual('.foo');
|
||||
expect(parse('-foo')).toEqual('-foo');
|
||||
expect(parse('_foo0')).toEqual('_foo0');
|
||||
expect(parse('@foo0')).toEqual('@foo0');
|
||||
expect(parse('.foo0')).toEqual('.foo0');
|
||||
expect(parse('-foo0')).toEqual('-foo0');
|
||||
expect(parse('_foo')).toEqual(variableEqual('_foo'));
|
||||
expect(parse('@foo')).toEqual(variableEqual('@foo'));
|
||||
expect(parse('.foo')).toEqual(variableEqual('.foo'));
|
||||
expect(parse('-foo')).toEqual(variableEqual('-foo'));
|
||||
expect(parse('_foo0')).toEqual(variableEqual('_foo0'));
|
||||
expect(parse('@foo0')).toEqual(variableEqual('@foo0'));
|
||||
expect(parse('.foo0')).toEqual(variableEqual('.foo0'));
|
||||
expect(parse('-foo0')).toEqual(variableEqual('-foo0'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('quoted variables', () => {
|
||||
it('strings with double quotes', () => {
|
||||
expect(parse('"foo"')).toEqual('foo');
|
||||
expect(parse('"f b"')).toEqual('f b');
|
||||
expect(parse('"foo bar"')).toEqual('foo bar');
|
||||
expect(parse('"foo bar fizz buzz"')).toEqual('foo bar fizz buzz');
|
||||
expect(parse('"foo bar baby"')).toEqual('foo bar baby');
|
||||
expect(parse('"foo"')).toEqual(variableEqual('foo'));
|
||||
expect(parse('"f b"')).toEqual(variableEqual('f b'));
|
||||
expect(parse('"foo bar"')).toEqual(variableEqual('foo bar'));
|
||||
expect(parse('"foo bar fizz buzz"')).toEqual(variableEqual('foo bar fizz buzz'));
|
||||
expect(parse('"foo bar baby"')).toEqual(variableEqual('foo bar baby'));
|
||||
});
|
||||
|
||||
it('strings with single quotes', () => {
|
||||
/* eslint-disable prettier/prettier */
|
||||
expect(parse("'foo'")).toEqual('foo');
|
||||
expect(parse("'f b'")).toEqual('f b');
|
||||
expect(parse("'foo bar'")).toEqual('foo bar');
|
||||
expect(parse("'foo bar fizz buzz'")).toEqual('foo bar fizz buzz');
|
||||
expect(parse("'foo bar baby'")).toEqual('foo bar baby');
|
||||
expect(parse("'foo'")).toEqual(variableEqual('foo'));
|
||||
expect(parse("'f b'")).toEqual(variableEqual('f b'));
|
||||
expect(parse("'foo bar'")).toEqual(variableEqual('foo bar'));
|
||||
expect(parse("'foo bar fizz buzz'")).toEqual(variableEqual('foo bar fizz buzz'));
|
||||
expect(parse("'foo bar baby'")).toEqual(variableEqual('foo bar baby'));
|
||||
expect(parse("' foo bar'")).toEqual(variableEqual(" foo bar"));
|
||||
expect(parse("'foo bar '")).toEqual(variableEqual("foo bar "));
|
||||
expect(parse("'0foo'")).toEqual(variableEqual("0foo"));
|
||||
expect(parse("' foo bar'")).toEqual(variableEqual(" foo bar"));
|
||||
expect(parse("'foo bar '")).toEqual(variableEqual("foo bar "));
|
||||
expect(parse("'0foo'")).toEqual(variableEqual("0foo"));
|
||||
/* eslint-enable prettier/prettier */
|
||||
});
|
||||
|
||||
it('allowed characters', () => {
|
||||
expect(parse('"_foo bar"')).toEqual('_foo bar');
|
||||
expect(parse('"@foo bar"')).toEqual('@foo bar');
|
||||
expect(parse('".foo bar"')).toEqual('.foo bar');
|
||||
expect(parse('"-foo bar"')).toEqual('-foo bar');
|
||||
expect(parse('"_foo0 bar1"')).toEqual('_foo0 bar1');
|
||||
expect(parse('"@foo0 bar1"')).toEqual('@foo0 bar1');
|
||||
expect(parse('".foo0 bar1"')).toEqual('.foo0 bar1');
|
||||
expect(parse('"-foo0 bar1"')).toEqual('-foo0 bar1');
|
||||
});
|
||||
|
||||
it('invalid characters in double quotes', () => {
|
||||
const check = (str) => () => parse(str);
|
||||
expect(check('" foo bar"')).toThrow('but "\\"" found');
|
||||
expect(check('"foo bar "')).toThrow('but "\\"" found');
|
||||
expect(check('"0foo"')).toThrow('but "\\"" found');
|
||||
expect(check('" foo bar"')).toThrow('but "\\"" found');
|
||||
expect(check('"foo bar "')).toThrow('but "\\"" found');
|
||||
expect(check('"0foo"')).toThrow('but "\\"" found');
|
||||
});
|
||||
|
||||
it('invalid characters in single quotes', () => {
|
||||
const check = (str) => () => parse(str);
|
||||
/* eslint-disable prettier/prettier */
|
||||
expect(check("' foo bar'")).toThrow('but "\'" found');
|
||||
expect(check("'foo bar '")).toThrow('but "\'" found');
|
||||
expect(check("'0foo'")).toThrow('but "\'" found');
|
||||
expect(check("' foo bar'")).toThrow('but "\'" found');
|
||||
expect(check("'foo bar '")).toThrow('but "\'" found');
|
||||
expect(check("'0foo'")).toThrow('but "\'" found');
|
||||
/* eslint-enable prettier/prettier */
|
||||
expect(parse('"_foo bar"')).toEqual(variableEqual('_foo bar'));
|
||||
expect(parse('"@foo bar"')).toEqual(variableEqual('@foo bar'));
|
||||
expect(parse('".foo bar"')).toEqual(variableEqual('.foo bar'));
|
||||
expect(parse('"-foo bar"')).toEqual(variableEqual('-foo bar'));
|
||||
expect(parse('"_foo0 bar1"')).toEqual(variableEqual('_foo0 bar1'));
|
||||
expect(parse('"@foo0 bar1"')).toEqual(variableEqual('@foo0 bar1'));
|
||||
expect(parse('".foo0 bar1"')).toEqual(variableEqual('.foo0 bar1'));
|
||||
expect(parse('"-foo0 bar1"')).toEqual(variableEqual('-foo0 bar1'));
|
||||
expect(parse('" foo bar"')).toEqual(variableEqual(' foo bar'));
|
||||
expect(parse('"foo bar "')).toEqual(variableEqual('foo bar '));
|
||||
expect(parse('"0foo"')).toEqual(variableEqual('0foo'));
|
||||
expect(parse('" foo bar"')).toEqual(variableEqual(' foo bar'));
|
||||
expect(parse('"foo bar "')).toEqual(variableEqual('foo bar '));
|
||||
expect(parse('"0foo"')).toEqual(variableEqual('0foo'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Functions', () => {
|
||||
it('no arguments', () => {
|
||||
expect(parse('foo()')).toEqual({ name: 'foo', args: [] });
|
||||
expect(parse('foo()')).toEqual(functionEqual('foo', []));
|
||||
});
|
||||
|
||||
it('arguments', () => {
|
||||
expect(parse('foo(5,10)')).toEqual({ name: 'foo', args: [5, 10] });
|
||||
expect(parse('foo(5,10)')).toEqual(functionEqual('foo', [5, 10]));
|
||||
});
|
||||
|
||||
it('arguments with strings', () => {
|
||||
expect(parse('foo("string with spaces")')).toEqual({
|
||||
name: 'foo',
|
||||
args: ['string with spaces'],
|
||||
});
|
||||
expect(parse('foo("string with spaces")')).toEqual(
|
||||
functionEqual('foo', [variableEqual('string with spaces')])
|
||||
);
|
||||
|
||||
/* eslint-disable prettier/prettier */
|
||||
expect(parse("foo('string with spaces')")).toEqual({
|
||||
name: 'foo',
|
||||
args: ['string with spaces'],
|
||||
});
|
||||
/* eslint-enable prettier/prettier */
|
||||
expect(parse("foo('string with spaces')")).toEqual(
|
||||
functionEqual('foo', [variableEqual('string with spaces')])
|
||||
);
|
||||
});
|
||||
|
||||
it('named only', () => {
|
||||
expect(parse('foo(q=10)')).toEqual(functionEqual('foo', [namedArgumentEqual('q', 10)]));
|
||||
});
|
||||
|
||||
it('named argument is numeric', () => {
|
||||
expect(parse('foo(q=10.1234e5)')).toEqual(
|
||||
functionEqual('foo', [namedArgumentEqual('q', 10.1234e5)])
|
||||
);
|
||||
});
|
||||
|
||||
it('named and positional', () => {
|
||||
expect(parse('foo(ref, q="bar")')).toEqual(
|
||||
functionEqual('foo', [variableEqual('ref'), namedArgumentEqual('q', 'bar')])
|
||||
);
|
||||
});
|
||||
|
||||
it('numerically named', () => {
|
||||
expect(() => parse('foo(1=2)')).toThrow('but "(" found');
|
||||
});
|
||||
|
||||
it('multiple named', () => {
|
||||
expect(parse('foo(q_param="bar", offset="1d")')).toEqual(
|
||||
functionEqual('foo', [
|
||||
namedArgumentEqual('q_param', 'bar'),
|
||||
namedArgumentEqual('offset', '1d'),
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('multiple named and positional', () => {
|
||||
expect(parse('foo(q="bar", ref, offset="1d", 100)')).toEqual(
|
||||
functionEqual('foo', [
|
||||
namedArgumentEqual('q', 'bar'),
|
||||
variableEqual('ref'),
|
||||
namedArgumentEqual('offset', '1d'),
|
||||
100,
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('duplicate named', () => {
|
||||
expect(parse('foo(q="bar", q="test")')).toEqual(
|
||||
functionEqual('foo', [namedArgumentEqual('q', 'bar'), namedArgumentEqual('q', 'test')])
|
||||
);
|
||||
});
|
||||
|
||||
it('incomplete named', () => {
|
||||
expect(() => parse('foo(a=)')).toThrow('but "(" found');
|
||||
expect(() => parse('foo(=a)')).toThrow('but "(" found');
|
||||
});
|
||||
|
||||
it('invalid named', () => {
|
||||
expect(() => parse('foo(offset-type="1d")')).toThrow('but "(" found');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -155,7 +215,7 @@ describe('Evaluate', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('valiables with dots', () => {
|
||||
it('variables with dots', () => {
|
||||
expect(evaluate('foo.bar', { 'foo.bar': 20 })).toEqual(20);
|
||||
expect(evaluate('"is.null"', { 'is.null': null })).toEqual(null);
|
||||
expect(evaluate('"is.false"', { 'is.null': null, 'is.false': false })).toEqual(false);
|
||||
|
@ -210,6 +270,10 @@ describe('Evaluate', () => {
|
|||
expect(evaluate('sum("space name")', { 'space name': [1, 2, 21] })).toEqual(24);
|
||||
});
|
||||
|
||||
it('throws on named arguments', () => {
|
||||
expect(() => evaluate('sum(invalid=a)')).toThrow('Named arguments are not supported');
|
||||
});
|
||||
|
||||
it('equations with injected functions', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
|
|
45
packages/kbn-tinymath/tinymath.d.ts
vendored
Normal file
45
packages/kbn-tinymath/tinymath.d.ts
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export function parse(expression: string): TinymathAST;
|
||||
export function evaluate(
|
||||
expression: string | null,
|
||||
context: Record<string, any>
|
||||
): number | number[];
|
||||
|
||||
// Named arguments are not top-level parts of the grammar, but can be nested
|
||||
export type TinymathAST = number | TinymathVariable | TinymathFunction | TinymathNamedArgument;
|
||||
|
||||
// Zero-indexed location
|
||||
export interface TinymathLocation {
|
||||
min: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
export interface TinymathFunction {
|
||||
type: 'function';
|
||||
name: string;
|
||||
text: string;
|
||||
args: TinymathAST[];
|
||||
location: TinymathLocation;
|
||||
}
|
||||
|
||||
export interface TinymathVariable {
|
||||
type: 'variable';
|
||||
value: string;
|
||||
text: string;
|
||||
location: TinymathLocation;
|
||||
}
|
||||
|
||||
export interface TinymathNamedArgument {
|
||||
type: 'namedArgument';
|
||||
name: string;
|
||||
value: string;
|
||||
text: string;
|
||||
location: TinymathLocation;
|
||||
}
|
7
packages/kbn-tinymath/tsconfig.json
Normal file
7
packages/kbn-tinymath/tsconfig.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "../../build/tsbuildinfo/packages/kbn-tinymath"
|
||||
},
|
||||
"include": ["tinymath.d.ts"]
|
||||
}
|
|
@ -134,9 +134,7 @@ describe('math(resp, panel, series)', () => {
|
|||
series
|
||||
)(await mathAgg(resp, panel, series)((results) => results))([]);
|
||||
} catch (e) {
|
||||
expect(e.message).toEqual(
|
||||
'Failed to parse expression. Expected "*", "+", "-", "/", or end of input but "(" found.'
|
||||
);
|
||||
expect(e.message).toEqual('No such function: notExistingFn');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
// @ts-expect-error no @typed def; Elastic library
|
||||
import { evaluate } from '@kbn/tinymath';
|
||||
import { pivotObjectArray } from '../../../common/lib/pivot_object_array';
|
||||
import { Datatable, isDatatable, ExpressionFunctionDefinition } from '../../../types';
|
||||
|
|
|
@ -11,7 +11,7 @@ import { getExpressionType } from './pointseries/lib/get_expression_type';
|
|||
describe('getExpressionType', () => {
|
||||
it('returns the result type of an evaluated math expression', () => {
|
||||
expect(getExpressionType(testTable.columns, '2')).toBe('number');
|
||||
expect(getExpressionType(testTable.colunns, '2 + 3')).toBe('number');
|
||||
expect(getExpressionType(testTable.columns, '2 + 3')).toBe('number');
|
||||
expect(getExpressionType(testTable.columns, 'name')).toBe('string');
|
||||
expect(getExpressionType(testTable.columns, 'time')).toBe('date');
|
||||
expect(getExpressionType(testTable.columns, 'price')).toBe('number');
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
// @ts-expect-error untyped library
|
||||
import { parse } from '@kbn/tinymath';
|
||||
import { getFieldNames } from './pointseries/lib/get_field_names';
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
// @ts-expect-error Untyped Elastic library
|
||||
import { evaluate } from '@kbn/tinymath';
|
||||
import { groupBy, zipObject, omit, uniqBy } from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
@ -20,7 +19,6 @@ import {
|
|||
import { pivotObjectArray } from '../../../../common/lib/pivot_object_array';
|
||||
import { unquoteString } from '../../../../common/lib/unquote_string';
|
||||
import { isColumnReference } from './lib/is_column_reference';
|
||||
// @ts-expect-error untyped local
|
||||
import { getExpressionType } from './lib/get_expression_type';
|
||||
import { getFunctionHelp, getFunctionErrors } from '../../../../i18n';
|
||||
|
||||
|
@ -132,16 +130,17 @@ export function pointseries(): ExpressionFunctionDefinition<
|
|||
[PRIMARY_KEY]: i,
|
||||
}));
|
||||
|
||||
function normalizeValue(expression: string, value: string) {
|
||||
function normalizeValue(expression: string, value: number | number[], index: number) {
|
||||
const numberValue = Array.isArray(value) ? value[index] : value;
|
||||
switch (getExpressionType(input.columns, expression)) {
|
||||
case 'string':
|
||||
return String(value);
|
||||
return String(numberValue);
|
||||
case 'number':
|
||||
return Number(value);
|
||||
return Number(numberValue);
|
||||
case 'date':
|
||||
return moment(value).valueOf();
|
||||
return moment(numberValue).valueOf();
|
||||
default:
|
||||
return value;
|
||||
return numberValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,7 +152,7 @@ export function pointseries(): ExpressionFunctionDefinition<
|
|||
(acc: Record<string, string | number>, { name, value }) => {
|
||||
try {
|
||||
acc[name] = args[name]
|
||||
? normalizeValue(value, evaluate(value, mathScope)[i])
|
||||
? normalizeValue(value, evaluate(value, mathScope), i)
|
||||
: '_all';
|
||||
} catch (e) {
|
||||
// TODO: handle invalid column names...
|
||||
|
|
|
@ -6,11 +6,12 @@
|
|||
*/
|
||||
|
||||
import { parse } from '@kbn/tinymath';
|
||||
import { DatatableColumn } from 'src/plugins/expressions/common';
|
||||
import { getFieldType } from '../../../../../common/lib/get_field_type';
|
||||
import { isColumnReference } from './is_column_reference';
|
||||
import { getFieldNames } from './get_field_names';
|
||||
|
||||
export function getExpressionType(columns, mathExpression) {
|
||||
export function getExpressionType(columns: DatatableColumn[], mathExpression: string) {
|
||||
// if isColumnReference returns true, then mathExpression is just a string
|
||||
// referencing a column in a datatable
|
||||
if (isColumnReference(mathExpression)) {
|
||||
|
@ -19,7 +20,7 @@ export function getExpressionType(columns, mathExpression) {
|
|||
|
||||
const parsedMath = parse(mathExpression);
|
||||
|
||||
if (parsedMath.args) {
|
||||
if (typeof parsedMath !== 'number' && parsedMath.type === 'function') {
|
||||
const fieldNames = parsedMath.args.reduce(getFieldNames, []);
|
||||
|
||||
if (fieldNames.length > 0) {
|
||||
|
@ -30,7 +31,7 @@ export function getExpressionType(columns, mathExpression) {
|
|||
}
|
||||
|
||||
return types;
|
||||
}, []);
|
||||
}, [] as string[]);
|
||||
|
||||
return fieldTypes.length === 1 ? fieldTypes[0] : 'string';
|
||||
}
|
|
@ -5,21 +5,19 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
type Arg =
|
||||
| string
|
||||
| number
|
||||
| {
|
||||
name: string;
|
||||
args: Arg[];
|
||||
};
|
||||
import { TinymathAST } from '@kbn/tinymath';
|
||||
|
||||
export function getFieldNames(names: string[], arg: Arg): string[] {
|
||||
if (typeof arg === 'object' && arg.args !== undefined) {
|
||||
return names.concat(arg.args.reduce(getFieldNames, []));
|
||||
export function getFieldNames(names: string[], ast: TinymathAST): string[] {
|
||||
if (typeof ast === 'number') {
|
||||
return names;
|
||||
}
|
||||
|
||||
if (typeof arg === 'string') {
|
||||
return names.concat(arg);
|
||||
if (ast.type === 'function') {
|
||||
return names.concat(ast.args.reduce(getFieldNames, []));
|
||||
}
|
||||
|
||||
if (ast.type === 'variable') {
|
||||
return names.concat(ast.value);
|
||||
}
|
||||
|
||||
return names;
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
// @ts-expect-error untyped library
|
||||
import { parse } from '@kbn/tinymath';
|
||||
|
||||
export function isColumnReference(mathExpression: string | null): boolean {
|
||||
|
@ -13,5 +12,5 @@ export function isColumnReference(mathExpression: string | null): boolean {
|
|||
mathExpression = 'null';
|
||||
}
|
||||
const parsedMath = parse(mathExpression);
|
||||
return typeof parsedMath === 'string';
|
||||
return typeof parsedMath !== 'number' && parsedMath.type === 'variable';
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import { parse } from '@kbn/tinymath';
|
|||
import { unquoteString } from '../../../../common/lib/unquote_string';
|
||||
|
||||
// break out into separate function, write unit tests first
|
||||
export function getFormObject(argValue) {
|
||||
export function getFormObject(argValue: string) {
|
||||
if (argValue === '') {
|
||||
return {
|
||||
fn: '',
|
||||
|
@ -20,23 +20,28 @@ export function getFormObject(argValue) {
|
|||
// check if the value is a math expression, and set its type if it is
|
||||
const mathObj = parse(argValue);
|
||||
// A symbol node is a plain string, so we guess that they're looking for a column.
|
||||
if (typeof mathObj === 'string') {
|
||||
if (typeof mathObj === 'number') {
|
||||
throw new Error(`Cannot render scalar values or complex math expressions`);
|
||||
}
|
||||
|
||||
if (mathObj.type === 'variable') {
|
||||
return {
|
||||
fn: '',
|
||||
column: unquoteString(argValue),
|
||||
column: unquoteString(mathObj.value),
|
||||
};
|
||||
}
|
||||
|
||||
// Check if its a simple function, eg a function wrapping a symbol node
|
||||
// check for only one arg of type string
|
||||
if (
|
||||
typeof mathObj === 'object' &&
|
||||
mathObj.type === 'function' &&
|
||||
mathObj.args.length === 1 &&
|
||||
typeof mathObj.args[0] === 'string'
|
||||
typeof mathObj.args[0] !== 'number' &&
|
||||
mathObj.args[0].type === 'variable'
|
||||
) {
|
||||
return {
|
||||
fn: mathObj.name,
|
||||
column: unquoteString(mathObj.args[0]),
|
||||
column: unquoteString(mathObj.args[0].value),
|
||||
};
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue