Last active
May 9, 2022 15:58
-
-
Save patrickroberts/35366b0ffb824e533a3bfb8fb28cad31 to your computer and use it in GitHub Desktop.
Math expression compiler using PEG.js parser generator to build expression tree
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* parser.pegjs -> parser.js | |
* https://pegjs.org/online | |
* Use results cache, Optimize parsing speed | |
*/ | |
// import parser generator for grammar defined in parser.pegjs | |
const { parse } = require('./parser') | |
// accepts mapping to convert parameter list into named parameters based on position | |
const args = (expression, map) => (...args) => expression(map(...args)) | |
// convert string expression into function that accepts parameter list | |
const compile = (string, map = () => {}) => args(parse(string), map) | |
// export parser and compiler | |
module.exports = { parse, compile } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Example Usage | |
*/ | |
// import compiler defined in compiler.js | |
const { compile } = require('./compiler') | |
// use this mapping for named parameters | |
// we must also define the identifier for `sqrt` | |
const map = (a, b, c) => ({ a, b, c, sqrt: Math.sqrt }) | |
// compile these expressions | |
const positive = compile('[-b+sqrt(b^2-4a c)]/(2a)', map) | |
const negative = compile('[-b-sqrt(b^2-4a c)]/(2a)', map) | |
// output solutions as tuple | |
const solver = (a, b, c) => [positive, negative].map(fn => fn(a, b, c)) | |
// x^2 -x -2 = 0 | |
console.log(solver(1, -1, -2)) | |
// [2, -1] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// factories to initialize value expressions | |
const constant = (v) => () => v | |
const variable = (label) => (o) => o[label] | |
// factory to initialize operator expressions | |
const operation = (fn) => (...args) => { | |
// if all arguments of this expression are constant expressions | |
if (args.every(expression => expression.length === 0)) { | |
// return constant expression based on precomputed value | |
return constant(fn(...args.map(expression => expression()))) | |
} | |
// return variable expression that accepts named parameters | |
return (o) => fn(...args.map(expression => expression(o))) | |
} | |
// initialize operator expressions | |
const [ | |
add, subtract, | |
multiply, divide, | |
mod, power, | |
negate, invoke | |
] = [ | |
(a, b) => a + b, (a, b) => a - b, | |
(a, b) => a * b, (a, b) => a / b, | |
(a, b) => a % b, (a, b) => a ** b, | |
(a) => -a, (fn, ...a) => fn(...a) | |
].map(operation) | |
// export expression factories and all expressions | |
module.exports = { operation, constant, variable, add, subtract, multiply, divide, mod, power, negate, invoke } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
// import all expressions defined in expressions.js | |
const { constant, variable, add, subtract, multiply, divide, mod, power, negate, invoke } = require('./expressions') | |
} | |
Expression 'expression' | |
= _ expression:TranslateExpression _ | |
{ return expression } | |
TranslateExpression | |
= left:ScaleExpression _ operator:TranslateOperator _ right:NextTranslateExpression | |
{ return right(left, operator) } | |
/ left:ScaleExpression _ operator:TranslateOperator _ right:ScaleExpression | |
{ return operator(left, right) } | |
/ ScaleExpression | |
NextTranslateExpression | |
= middle:ScaleExpression _ operator:TranslateOperator _ right:NextTranslateExpression | |
{ return (left, op) => right(op(left, middle), operator) } | |
/ middle:ScaleExpression _ operator:TranslateOperator _ right:ScaleExpression | |
{ return (left, op) => operator(op(left, middle), right) } | |
TranslateOperator 'operator' | |
= '+' { return add } | |
/ '-' { return subtract } | |
ScaleExpression | |
= left:NegateExpression _ operator:ScaleOperator _ right:NextScaleExpression | |
{ return right(left, operator) } | |
/ left:NegateExpression _ operator:ScaleOperator _ right:NegateExpression | |
{ return operator(left, right) } | |
/ left:NegateExpression _ right:NextScaleAfterImplicitExpression | |
{ return right(left, multiply) } | |
/ left:NegateExpression _ right:PowerExpression | |
{ return multiply(left, right) } | |
/ NegateExpression | |
NextScaleExpression | |
= middle:NegateExpression _ operator:ScaleOperator _ right:NextScaleExpression | |
{ return (left, op) => right(op(left, middle), operator) } | |
/ middle:NegateExpression _ operator:ScaleOperator _ right:NegateExpression | |
{ return (left, op) => operator(op(left, middle), right) } | |
/ middle:NegateExpression _ right:NextScaleAfterImplicitExpression | |
{ return (left, op) => right(op(left, middle), multiply) } | |
/ middle:NegateExpression _ right:PowerExpression | |
{ return (left, op) => multiply(op(left, middle), right) } | |
NextScaleAfterImplicitExpression | |
= middle:PowerExpression _ operator:ScaleOperator _ right:NextScaleExpression | |
{ return (left, op) => right(op(left, middle), operator) } | |
/ middle:PowerExpression _ operator:ScaleOperator _ right:NegateExpression | |
{ return (left, op) => operator(op(left, middle), right) } | |
/ middle:PowerExpression _ right:NextScaleAfterImplicitExpression | |
{ return (left, op) => right(op(left, middle), multiply) } | |
/ middle:PowerExpression _ right:PowerExpression | |
{ return (left, op) => multiply(op(left, middle), right) } | |
ScaleOperator 'operator' | |
= '*' { return multiply } | |
/ '/' { return divide } | |
/ '%' { return mod } | |
NegateExpression | |
= '-' _ expression:NegateExpression | |
{ return negate(expression) } | |
/ PowerExpression | |
PowerExpression | |
= left:GroupExpression _ PowerOperator _ right:NegateExpression | |
{ return power(left, right) } | |
/ GroupExpression | |
PowerOperator 'operator' | |
= '^' | |
/ '**' | |
GroupExpression 'group' | |
= '(' expression:Expression ')' | |
{ return expression } | |
/ '[' expression:Expression ']' | |
{ return expression } | |
/ '{' expression:Expression '}' | |
{ return expression } | |
/ Function | |
Function 'call' | |
= id:Identifier _ '(' parameters:Parameters ')' | |
{ return invoke(id, ...parameters) } | |
/ Value | |
Parameters 'parameters' | |
= parameter:Expression ',' rest:Parameters | |
{ return [parameter].concat(rest) } | |
/ expression:Expression | |
{ return [expression] } | |
/ _ | |
{ return [] } | |
Value | |
= Identifier | |
/ Number | |
Identifier 'identifier' | |
= [A-Za-z][A-Za-z0-9]* | |
{ return variable(text()) } | |
Number 'number' | |
= Float ([Ee] [+-]? Integer)? | |
{ return constant(Number(text())) } | |
Float | |
= Integer '.'? Integer? | |
Integer | |
= [0-9]+ | |
_ 'whitespace' | |
= [ \t\r\n]* | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment