Skip to content

Commit

Permalink
Merge pull request #2 from ZainVirani/CAP
Browse files Browse the repository at this point in the history
Instrument logic parser
  • Loading branch information
jacobpenny authored Jun 15, 2017
2 parents 569632d + 6eca372 commit 46ce77d
Show file tree
Hide file tree
Showing 10 changed files with 1,324 additions and 24 deletions.
11 changes: 0 additions & 11 deletions jsx/lib/InstrumentLogicParser.js

This file was deleted.

120 changes: 120 additions & 0 deletions jsx/lib/Parser/README.md
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 |
2 changes: 2 additions & 0 deletions jsx/lib/Parser/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import Evaluator from './js/Evaluator';
export { Evaluator };
142 changes: 142 additions & 0 deletions jsx/lib/Parser/jison/logicParser.jison
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]}; }
;
48 changes: 48 additions & 0 deletions jsx/lib/Parser/js/Evaluator.js
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);
}
Loading

0 comments on commit 46ce77d

Please sign in to comment.