forked from aces/Loris
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from ZainVirani/CAP
Instrument logic parser
- Loading branch information
Showing
10 changed files
with
1,324 additions
and
24 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
# Parser | ||
|
||
The Parser provides a human readable syntax for front end equation building. | ||
|
||
This Readme breaks down the different parts of the Parser and lists syntax rules. | ||
|
||
# Prerequisites for Development | ||
|
||
* [momentJS](https://momentjs.com/) | ||
* [Jison](jison.org) (only required for changes in syntax) | ||
|
||
Note that end users do not require Jison, only the Jison-generated parser file. | ||
|
||
# Development and Use | ||
|
||
### Syntax and Operator Changes | ||
Changing syntax (or adding unary/binary operators) requires changes to `jison/logicParser.jison`. | ||
Tokens are defined at the top, precedence and assertions are set below that, | ||
and finally the grammar itself is defined below that. | ||
See Jison documentation for grammar and Flex pattern matchign specifications. | ||
|
||
After your changes are made run `jison jison/logicParser.jison` and replace `js/logicParser.js` with the outputted file. | ||
|
||
### Function Changes | ||
To add or edit functions, simply edit `js/Functions.js`. | ||
|
||
### Evaluator Changes | ||
To add new types of operations, add a case to the switch statement in `js/Evaluator.js`. | ||
|
||
### Unit Testing | ||
Tests can be added to `Loris/test/js-tests/Parser.test.js`. Run tests with `npm run tests:unit:js:watch`. | ||
|
||
### Use | ||
At the top of your JS file add `import { Evaluator } from './jsx/lib/Parser';` (change the path based on your directory location). | ||
|
||
Call `Evaluator(LOGIC_STRING)` to evaluate an equation. | ||
|
||
# Syntax | ||
Note that all whitespace (spaces or tabs) is ignored in the parser. | ||
|
||
### Value Inputs | ||
| Type | Syntax | Notes | | ||
|------------------- |------------------------ |---------------------------------------------------- | | ||
| number | 1; 900; 123.456 | | | ||
| text | "this is my text!_123" | empty text is supported; ' can be used instead of " | | ||
| variable | [my_variable_name] | | | ||
| nested expression | (expression) | | | ||
|
||
### Constants | ||
| Constant | Syntax | | ||
|-------------------- |-------- | | ||
| null | null | | ||
| true | true | | ||
| false | false | | ||
| Euler's number (e) | E | | ||
| pi | PI | | ||
|
||
### Numerical Operations | ||
| Operation | Syntax | Notes | | ||
|----------- |-------- |-------------------------------------------------------------------- | | ||
| add | a + b | can be used to concatenate strings | | ||
| subtract | a - b | | | ||
| negate | - a | | | ||
| multiply | a * b | | | ||
| divide | a / b | cannot divide by 0 | | ||
| exponent | a ^ b | | | ||
| percentage | a % | divides the value of a by 100 | | ||
| factorial | a ! | returns a factorial; supports 0 or positive numbers divisible by 0.5 | | ||
|
||
### Boolean/Comparison Operations (returns true or false) | ||
| Operation | Syntax | Notes | | ||
|------------------ |--------- |---------------------------------- | | ||
| equivalency | a = b | | | ||
| inequivalency | a <> b | | | ||
| greater than | a > b | | | ||
| less than | a < b | | | ||
| greater or equal | a >= b | | | ||
| less or equal | a <= b | | | ||
| and | a and b | returns true if a and b are true | | ||
| or | a or b | returns true if a or b is true | | ||
| not | not a | returns true if a is false | | ||
|
||
### If Logic | ||
| Operation | Syntax | Notes | | ||
|----------- |-------------- |---------------------------------------------------------------------------------- | | ||
| if | if(cond,x,y) | If the condition 'cond' evaluates to true, x is returned. If not, y is returned. | | ||
|
||
### Functions | ||
| Operation | Syntax | Notes | | ||
|-------------------- |---------------------- |-------------------------------------------------------------------------------------- | | ||
| not a number | isNaN(a) | returns true if a is not a number | | ||
| modulo | mod(a, b) | returns the remainder of a / b | | ||
| round | round(a, b) | rounds a to b decimal places; note all rounding functions support trailing 0s | | ||
| round up | roundup(a, b) | rounds a up to b decimal places | | ||
| round down | rounddown(a,b) | rounds a down to b decimal places | | ||
| square root | sqrt(a) | | | ||
| absolute value | abs(a) | returns the positive value of a | | ||
| minimum | min(a,b,c,d...) | returns the smallest value of its arguments | | ||
| maximum | max(a,b,c,d...) | returns the largest value of its arguments | | ||
| mean | mean(a,b,c,d...) | returns the average value of its arguments | | ||
| median | median(a,b,c,d...) | sorts its arguments in ascending order and returns the median | | ||
| sum | sum(a,b,c,d...) | returns the sum of its arguments | | ||
| product | product(a,b,c,d...) | returns the product of its arguments | | ||
| variance | variance(a,b,c,d...) | returns the population variance of its arguments | | ||
| standard deviation | stdev(a,b,c,d...) | returns the population standard deviation (square root of variance) of its arguments | | ||
|
||
### Date Operations | ||
| Operation | Syntax | | ||
|----------------- |----------------------------------------------- | | ||
| date difference | datediff(date1, date2, units, format, signed) | | ||
|
||
| Argument | Syntax | Notes | | ||
|-------------- |--------------------------------------- |------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| date | "YYYY-MM-DD HH:MM:SS" or "YYYY-MM-DD" | valid formats for date include YMD (default/ISO standard), MDY, DMY; note that time is optional | | ||
| | "YYYY-MM" or "MM-YYYY" | note that day and time together are optional for all formats | | ||
| | "YYYY" | note that month, day, and time together are optional for all formats | | ||
| units | "y","M","d","h","m","s" | specifies the return value unit: years, months, days, hours, minutes, and seconds respectively. Any other input will return milliseconds. | | ||
| format | "ymd", "mdy". "dmy" | YMD is default | | ||
| signed | true/false | if true, negative differences will be allowed. if false, the difference will always be positive | | ||
| return value | | returns date1 - date 2 in the specified unit | |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
import Evaluator from './js/Evaluator'; | ||
export { Evaluator }; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
/* using JISON Lexical analysis with Flex pattern matching (http://dinosaur.compilertools.net/flex/flex_11.html) */ | ||
|
||
/* description: Parses end executes mathematical expressions. */ | ||
|
||
/* lexical grammar */ | ||
%lex | ||
%% | ||
|
||
\s+ /* skip whitespace */ | ||
"null" return 'null' | ||
"true" return 'true' | ||
"false" return 'false' | ||
"E" return 'E' | ||
"PI" return 'PI' | ||
\d+("."\d+)?\b return 'NUMBER' | ||
"*" return '*' | ||
"/" return '/' | ||
"-" return '-' | ||
"+" return '+' | ||
"^" return '^' | ||
"=" return '=' | ||
"!" return '!' | ||
"%" return '%' | ||
"(" return '(' | ||
")" return ')' | ||
"," return ',' | ||
"<>" return '<>' | ||
"<=" return '<=' | ||
">=" return '>=' | ||
"<" return '<' | ||
">" return '>' | ||
"and" return 'and' | ||
"or" return 'or' | ||
"not" return 'not' | ||
[_a-zA-Z]\w* return 'VARIABLE' | ||
"\""[^"]*"\"" return 'ESTRING' | ||
"'"[^']*"'" return 'STRING' | ||
"[" return '[' | ||
"]" return ']' | ||
<<EOF>> return 'EOF' | ||
. return 'INVALID' | ||
|
||
/lex | ||
|
||
/* operator associations and precedence */ | ||
|
||
%left 'and' 'or' | ||
%right 'not' | ||
%left '=' '<' '>' '<>' '<=' '>=' | ||
%left '+' '-' | ||
%left '*' '/' | ||
%left '^' | ||
%right '!' | ||
%right '%' | ||
%left UMINUS | ||
|
||
%start expressions | ||
|
||
%% /* language grammar */ | ||
|
||
expressions | ||
: e EOF | ||
{ return $1; } | ||
; | ||
|
||
arguments | ||
: e ',' arguments | ||
{ $$ = [$1].concat($3); } | ||
| e | ||
{ $$ = [$1]; } | ||
; | ||
|
||
variable | ||
: VARIABLE | ||
{ $$ = yytext; } | ||
; | ||
|
||
constant | ||
: 'E' | ||
{ $$ = Math.E } | ||
| 'PI' | ||
{ $$ = Math.PI } | ||
; | ||
|
||
e | ||
: e '=' e | ||
{ $$ = {tag: 'BinaryOp', op: 'eq', args: [$1, $3]}; } | ||
| e '<' e | ||
{ $$ = {tag: 'BinaryOp', op: 'lt', args: [$1, $3]}; } | ||
| e '>' e | ||
{ $$ = {tag: 'BinaryOp', op: 'gt', args: [$1, $3]}; } | ||
| e '<>' e | ||
{ $$ = {tag: 'BinaryOp', op: 'neq', args: [$1, $3]}; } | ||
| e '<=' e | ||
{ $$ = {tag: 'BinaryOp', op: 'leq', args: [$1, $3]}; } | ||
| e '>=' e | ||
{ $$ = {tag: 'BinaryOp', op: 'geq', args: [$1, $3]}; } | ||
| e '+' e | ||
{ $$ = {tag: 'BinaryOp', op: 'add', args: [$1, $3]}; } | ||
| e '-' e | ||
{ $$ = {tag: 'BinaryOp', op: 'sub', args: [$1, $3]}; } | ||
| e '*' e | ||
{ $$ = {tag: 'BinaryOp', op: 'mul', args: [$1, $3]}; } | ||
| e '/' e | ||
{ $$ = {tag: 'BinaryOp', op: 'div', args: [$1, $3]}; } | ||
| e '^' e | ||
{ $$ = {tag: 'BinaryOp', op: 'pow', args: [$1, $3]}; } | ||
| e '%' e | ||
{ $$ = {tag: 'BinaryOp', op: 'mod', args: [$1, $3]}; } | ||
| e 'and' e | ||
{ $$ = {tag: 'BinaryOp', op: 'and', args: [$1, $3]}; } | ||
| e 'or' e | ||
{ $$ = {tag: 'BinaryOp', op: 'or', args: [$1, $3]}; } | ||
| 'not' e | ||
{ $$ = {tag: 'UnaryOp', op: 'not', args: [$2]}; } | ||
| e '%' | ||
{ $$ = {tag: 'UnaryOp', op: 'per', args: [$1]}; } | ||
| e '!' | ||
{ $$ = {tag: 'UnaryOp', op: 'fact', args: [$1]}; } | ||
| '-' e %prec UMINUS | ||
{ $$ = {tag: 'UnaryOp', op: 'negate', args: [$2]}; } | ||
| '(' e ')' | ||
{ $$ = {tag: 'NestedExpression', args: [$2]}; } | ||
| variable '(' arguments ')' | ||
{ $$ = {tag: 'FuncApplication', args:[$1, $3]}; } | ||
| "[" variable "]" | ||
{ $$ = {tag: 'Variable', args: [$2]}; } | ||
| constant | ||
{ $$ = {tag: 'Literal', args: [$1]}; } | ||
| NUMBER | ||
{ $$ = {tag: 'Literal', args: [Number(yytext)]}; } | ||
| STRING | ||
{ $$ = {tag: 'String', args: [yytext]}; } | ||
| ESTRING | ||
{ $$ = {tag: 'String', args: [yytext]}; } | ||
| 'false' | ||
{ $$ = {tag: 'Literal', args: [false]}; } | ||
| 'true' | ||
{ $$ = {tag: 'Literal', args: [true]}; } | ||
| 'null' | ||
{ $$ = {tag: 'Literal', args: [null]}; } | ||
; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import Functions from './Functions'; | ||
import { parser } from './logicParser'; | ||
|
||
|
||
function evalAST(tree, scope) { | ||
switch(tree.tag) { | ||
case 'String': { | ||
return String(tree.args[0].slice(1,-1)); | ||
} | ||
case 'Literal': { | ||
return tree.args[0]; | ||
} | ||
case 'Variable': { | ||
if (typeof scope[tree.args[0]] === 'undefined') { | ||
throw `Unbound variable: ${tree.args[0]}`; | ||
} | ||
return scope[tree.args[0]]; | ||
} | ||
case 'FuncApplication': { | ||
if (tree.args[0] === 'if') { | ||
if (evalAST(tree.args[1][0], scope)) { | ||
return evalAST(tree.args[1][1],scope); | ||
} | ||
return evalAST(tree.args[1][2],scope); | ||
} | ||
if (!Functions[tree.args[0]]) { | ||
throw `'${tree.args[0]}' is not a defined function.`; | ||
} | ||
const funcArgs = tree.args[1].map(ast => evalAST(ast, scope)); | ||
return Functions[tree.args[0]](...funcArgs); | ||
} | ||
case 'NestedExpression': { | ||
return evalAST(tree.args[0], scope); | ||
} | ||
case 'UnaryOp': { | ||
return Functions[tree.op](evalAST(tree.args[0],scope)); | ||
} | ||
case 'BinaryOp': { | ||
const funcArgs = tree.args.map(ast => evalAST(ast, scope)); | ||
return Functions[tree.op](...funcArgs); | ||
} | ||
} | ||
} | ||
|
||
export default function Evaluator(stringExpression, scope = {}) { | ||
const tree = parser.parse(stringExpression); | ||
return evalAST(tree, scope); | ||
} |
Oops, something went wrong.