0.9.2 • Published 8 years ago
tevale v0.9.2
tevale
Tevale is the tiny evaluator of expressions, which accepts a set of variables (named objects with properties) and an expression, validates its syntax and evaluates the expression.
Developed in Codefresh.
Its goal is to allow the evaluations of expressions (in a variable context) so as to generate a boolean true/false result. It was designed to be embeded into scripts that contain various entities and require the evaluation of an expression to make a yes/no decision.
Features
- Validation and evaluation of expressions
- Supports variables in expressions, including object variables (nesting)
- Supports popular functions and operators for number and string manipulation
- Supports string searching and regexp matching functions
Installation
$ npm install cf-expression-evaluator --saveUsage
const tevale = require('tevale');
const variables = {
"author": {
"name": "Alon Diamant",
"homepage": "http://www.alondiamant.com",
"github": {
"repositories": 12,
"url": "http://www.github.com/advance512"
},
},
"company": {
"name": "Codefresh",
"homepage": "http://www.codefresh.io",
"phoneNumber": "+972-99-999-9999"
}
}
console.log(
tevale.evaluateExpression(
'match(author.name, "alon", true) && ' +
'author.github.repositories > 10 && ' +
'Number(substring(company.phoneNumber, 1, 4)) / 2 == 486',
variables
)
);Reference
Types
| Type | Example | True/False |
|---|---|---|
| String | \"hello\"'there' | Empty string is false: ''Non-empty string is true: 'something'String comparison is lexicographic |
| Number | 53.41.79E+308 | 0 is falseany non-0 number is true |
| Boolean | truefalse | true is truefalse is false |
| null | Null | always false |
Variables and Members
- You can then use the members of each variable.
- To access variables that have a non-standard (i.e. only alphanumeric and _ characters) names, use the Variable() function.
- To access variable members that have a non-standard (i.e. only alphanumeric and _ characters) names, use the Member() function.
Unary Operators
| Operator | Operation |
|---|---|
| - | Negation of numbers |
| ! | Logical NOT |
Binary Operators
| Operator | Operation |
|---|---|
| + | Add, String Concatenation |
| - | Subtract |
| * | Multiply |
| / | Divide |
| % | Modulus |
| && | Logical AND |
| || | Logical OR |
Comparisons
| Operator | Operation |
|---|---|
| == | Equal |
| != | Not equal |
| > | Greater than |
| >= | Greater than or equal |
| < | Less than |
| >= | Less than or equal |
Functions
| Function Name | Parameters | Return value | Example |
|---|---|---|---|
| String | 0: number or string | string of input value | String(40) == '40' |
| Number | 0: number or string | number of input value | Number('50') == 50Number('hello') is invalid |
| Boolean | 0: number or string | boolean of input value | Boolean('123') == trueBoolean('') == falseBoolean(583) == trueBoolean(0) == false |
| round | 0: number | rounded number | round(1.3) == 1round(1.95) == 2 |
| floor | 0: number | number rounded to floor | floor(1.3) == 1floor(1.95) == 1 |
| upper | 0: string | string in upper case | upper('hello') == 'HELLO' |
| lower | 0: string | string in lower case | lower('BYE BYE') == 'bye bye' |
| trim | 0: string | trimmed string | trim(\" abc \") == \"abc\" |
| trimLeft | 0: string | left trimmed string | trimLeft(\" abc \") == \"abc \" |
| trimRight | 0: string | right trimmed string | trimRight(\" abc \") == \" abc\" |
| replace | 0: string - main string1: string - substring to find2: string - substring to replace | replace all instances of the substring (1) in the main string (0) with the substring (2) | replace('hello there', 'e', 'a') == 'hallo thara' |
| substring | 0: string - main string1: string - index to start2: string - index to end | returns a substring of a string | substring(\"hello world\", 6, 11) == \"world\" |
| length | string | length of a string | length(\"gump\") == 4 |
| includes | 0: string - main string1: string - string to search for | whether a search string is found inside the main string | includes(\"codefresh\", \"odef\") == true |
| indexOf | 0: string - main string1: string - string to search for | index of a search string if it is found inside the main string | indexOf(\"codefresh\", \"odef\") == 1 |
| match | 0: string - main string1: string - regular expression string, JS style2: boolean - ignore case | search for a regular expression inside a string, ignoring or not ignoring case | match(\"hello there you\", \"..ll.\", false) == truematch(\"hello there you\", \"..LL.\", false) == falsematch(\"hello there you\", \"hell$\", true) == falsematch(\"hello there you\", \"^hell\", true) == truematch(\"hello there you\", \"bye\", false) == false |
| Variable | string | Lookup the value of a variable | Variable('someVariable') |
| Member | 0: string - variable name1: string - member name | Lookup the value of a member of a variable | Member('someVariable', 'workingDirectory') |
Examples
Using the variables:
const exampleVariables = {
'somePositiveNumericValue': 123,
'someZeroValue': 0,
'someNegativeNumericValue': -50,
'stringValue': 'hello',
'emptyStringValue': '',
'longStringValue': 'mA26EKMPupgvp6XYlGAVJZKv6yvaD3aobXMyExvyMBa2Hi9LlJXTUaBveMR9ErHtSfXNHHW5xAKbz2DVfBOqQ8CaSMNMQRBrJRpEpsO7FygKZmKpKHvvtPviOTfyUE0HGhnSPYHb9Hbz1CMxab4T0iQxPLCwrg57Qi0sTW1sJhVSygD9ivCfhJwJmD9PNb8bV0rJJ9aWp84LeaC7PDkj5hAozkrrJVA5hozLSXGZb0A4JLKiPOe9ITvxcIqvPNaMPA2SF4AQasE01TeGyuHQICuAMTGFFAP9y0HJBm7N0XmU',
'trueBooleanValue': true,
'falseBooleanValue': false,
'nullBooleanValue': null,
'simpleObjectValue': {
'someNumericValue': 123,
'someStringValue': 'hello there',
'someNullValue': null,
'someBooleanValue': false,
},
'complexObjectValue': {
'internalObjectValue': {
'someNumericValue': 123,
'someStringValue': 'hello there',
'someNullValue': null,
'someBooleanValue': false,
},
},
'superComplexObjectValue': {
'internalObjectValue': {
'moreInternalObjectValue': {
'someNumericValue': 123,
'someStringValue': 'hello there',
'someNullValue': null,
'someBooleanValue': false,
},
},
},
'typicalObjectValue': {
'branch_name': 'master',
'working_dir': '/tmp/whatever',
'success': true,
},
'emptyObjectValue': {},
'author': {
'name': 'Alon Diamant',
'homepage': 'http://www.alondiamant.com',
'github': {
'repositories': 12,
'url': 'http://www.github.com/advance512',
},
},
'company': {
'name': 'Codefresh',
'homepage': 'http://www.codefresh.io',
'phoneNumber': '+972-99-999-9999',
},
};The result for the following expressions is:
| Expression | Result or Error Message |
|---|---|
| '' | false |
| 'null' | false |
| 'false' | false |
| '1234' | true |
| '-1234' | true |
| '0000000' | false |
| '""' | false |
| '"asdasd"' | true |
| ''asdasd'' | true |
'asdasd' | Unexpected "`" at character 0 |
| 'somePositiveNumericValue' | true |
| 'someZeroValue' | false |
| 'someNegativeNumericValue' | true |
| 'stringValue' | true |
| 'emptyStringValue' | false |
| 'longStringValue' | true |
| 'trueBooleanValue' | true |
| 'falseBooleanValue' | false |
| '!falseBooleanValue' | true |
| 'nullBooleanValue' | false |
| 'invalidIdentifer' | Invalid identifier: 'invalidIdentifer' |
| 'somePositiveNumericValue > someZeroValue' | true |
| 'somePositiveNumericValue < someZeroValue' | false |
| 'somePositiveNumericValue >= someZeroValue' | true |
| 'somePositiveNumericValue <= someZeroValue' | false |
| 'somePositiveNumericValue >= somePositiveNumericValue' | true |
| 'somePositiveNumericValue <= somePositiveNumericValue' | true |
| 'longStringValue > emptyStringValue' | true |
| 'longStringValue < emptyStringValue' | false |
| 'longStringValue == longStringValue' | true |
| 'longStringValue === longStringValue' | Invalid binary operator: === |
| 'longStringValue > stringValue' | true |
| 'longStringValue < stringValue' | false |
| 'simpleObjectValue.someNumericValue == 123' | true |
| 'simpleObjectValue.someNumericValue' | true |
| 'simpleObjectValue.someNullValue' | false |
| 'simpleObjectValue.someBooleanValue' | false |
| 'simpleObjectValue.someBooleanValue == false' | true |
| 'complexObjectValue.internalObjectValue.someNumericValue == 123' | true |
| 'complexObjectValue.internalObjectValue.someNumericValue' | true |
| 'complexObjectValue.internalObjectValue.someNullValue' | false |
| 'complexObjectValue.internalObjectValue.someBooleanValue' | false |
| 'complexObjectValue.internalObjectValue.someBooleanValue == false' | true |
| 'superComplexObjectValue.internalObjectValue.moreInternalObjectValue.someNumericValue == 123' | true |
| 'superComplexObjectValue.internalObjectValue.moreInternalObjectValue.someNumericValue' | true |
| 'superComplexObjectValue.internalObjectValue.moreInternalObjectValue.someNullValue' | false |
| 'superComplexObjectValue.internalObjectValue.moreInternalObjectValue.someBooleanValue' | false |
| 'superComplexObjectValue.internalObjectValue.moreInternalObjectValue.someBooleanValue == false' | true |
| 'typicalObjectValue.branch_name == "master"' | true |
| 'typicalObjectValue.working_dir == "/var/tmp"' | false |
| '!!typicalObjectValue.success' | true |
| 'typicalObjectValue.success == true' | true |
| 'typicalObjectValue.nonExistant' | Undefined identifier member: 'nonExistant' |
| 'typicalObjectValue' | Cannot evaluate object variable directly |
| 'emptyObjectValue.someBooleanValue == false' | Undefined identifier member: 'someBooleanValue' |
| 'emptyObjectValue.someBooleanValue' | Undefined identifier member: 'someBooleanValue' |
| 'emptyObjectValue' | Cannot evaluate object variable directly |
| 'superComplexObjectValue.' | Unexpected at character 24 |
| 'superComplexObjectValue..' | Unexpected . at character 24 |
| 'superComplexObjectValue.internalObjectValue' | Cannot evaluate object variable directly |
| 'superComplexObjectValue.internalObjectValue.' | Unexpected at character 44 |
| 'superComplexObjectValue.internalObjectValue.moreInternalObjectValue' | Cannot evaluate object variable directly |
| 'superComplexObjectValue.internalObjectValue.moreInternalObjectValue.' | Unexpected at character 68 |
| 'superComplexObjectValue.internalObjectValue.moreInternalObjectValue.nonExistant' | Undefined identifier member: 'nonExistant' |
| 'superComplexObjectValue.internal Object Value.moreInternalObjectValue.someNumericValue' | Cannot handle compound expressions. |
| 'superComplexObjectValue.internalObject.Value.moreInternalObjectValue.someNumericValue' | Undefined identifier member: 'internalObject' |
| 'somePositiveNumericValue > null' | The operator > cannot be used with number,null. It can only be used with the types: number,string |
| 'somePositiveNumericValue < stringValue' | The operator < cannot be used with number,string. It can only be used with the types: number,string |
| 'somePositiveNumericValue > true' | The operator > cannot be used with number,boolean. It can only be used with the types: number,string |
| 'somePositiveNumericValue > simpleObjectValue' | The operator > cannot be used with number,object. It can only be used with the types: number,string |
| 'complexObjectValue > simpleObjectValue' | The operator > cannot be used with object,object. It can only be used with the types: number,string |
| 'false > null' | The operator > cannot be used with boolean,null. It can only be used with the types: number,string |
| 'false > true' | The operator > cannot be used with boolean,boolean. It can only be used with the types: number,string |
| 'someStringValue > null' | Invalid identifier: 'someStringValue' |
| 'internalObjectValue.someNumericValue > null' | Invalid identifier: 'internalObjectValue' |
| 'typicalObjectValue.working_dir > 82' | The operator > cannot be used with string,number. It can only be used with the types: number,string |
| 'typicalObjectValue.working_dir > complexObjectValue.internalObjectValue.someNumericValue' | The operator > cannot be used with string,number. It can only be used with the types: number,string |
| '(somePositiveNumericValue > someZeroValue)' | true |
| '(somePositiveNumericValue >= someZeroValue) && true' | true |
| '(longStringValue > emptyStringValue) && (longStringValue >= emptyStringValue)' | true |
| '((longStringValue > emptyStringValue) && (longStringValue >= emptyStringValue))' | true |
| '(longStringValue > emptyStringValue && (longStringValue >= emptyStringValue))' | true |
| '60 > 35 && (50 > 22 && ((90 > 33) && ((26 > 10) && 15 > 30)) || (80 > 32))' | true |
| '60 > 35 && (50 > 22 && ((90 > 33) && ((26 > 10) && 15 > 30)) || (80 < 32))' | false |
| '60 > 35 && (50 > 22 && ((90 > 33) && ((26 > 10) && 15 > 30)) || (somePositiveNumericValue > 32))' | true |
| '60 > 35 && (50 > 22 && ((90 > 33) && ((26 > 10) && 15 > 30)) || (somePositiveNumericValue < 32))' | false |
| 'somePositiveNumericValue / someZeroValue' | Division of 123 by 0 |
| 'somePositiveNumericValue % someZeroValue' | Modulo of 123 by 0 |
| 'somePositiveNumericValue / somePositiveNumericValue' | true |
| '9007199254740991 * 9007199254740991' | true |
| '1.231 * 5.2342' | true |
| '+true' | The operator + cannot be used with boolean. It can only be used with the types: number |
| '+234' | true |
| '~234' | Invalid unary operator: ~ |
| '555+true' | The operator + cannot be used with number,boolean. It can only be used with the types: number,string |
| '23423-223' | true |
| '123-123' | false |
| '!true' | false |
| '!!true' | true |
| '!!!!!!!!!!true' | true |
| '!33' | false |
| '!!33' | true |
| '!0' | true |
| '!!0' | false |
| '55 % 10 == 5' | true |
| '1.79E+308 * 1.79E+308' | Numeric overflow |
| '15 | 33' | Invalid binary operator: | |
| '5555 @ 33333' | Unexpected "@" at character 5 |
| '5555 $ 33333' | Cannot handle compound expressions. |
| '5555 \ 33333' | Unexpected "\" at character 5 |
| '5555 : 33333' | Unexpected ":" at character 5 |
| '5555 :::: 33333' | Unexpected ":" at character 5 |
| 'stringValue / 5' | The operator / cannot be used with string,number. It can only be used with the types: number |
| 'stringValue / stringValue' | The operator / cannot be used with string,string. It can only be used with the types: number |
| 'stringValue + 5' | The operator + cannot be used with string,number. It can only be used with the types: number,string |
| 'stringValue + 5' | The operator + cannot be used with string,number. It can only be used with the types: number,string |
| 'this' | keyword 'this' not supported. |
| '5 > 2 ? 4 : 1' | Trinary operator (?:) currently not supported. |
| '1, 2, 3' | Array literals (1, 2, 3) currently not supported. |
| 'upper(stringValue)' | true |
| 'substring(stringValue, 2, 4)' | true |
| 'substring(stringValue, 20, 40)' | false |
| 'upper()' | Expected 1 arguments for function 'upper'. |
| 'lower(123)' | Invalid argument #0 of type number for function 'lower': expected string |
| 'lower("asd", 3, 5)' | Expected 1 arguments for function 'lower'. |
| 'trim(null)' | Invalid argument #0 of type null for function 'trim': expected string |
| 'Number(false)' | Invalid argument #0 of type boolean for function 'Number': expected number,string |
| 'hello(false)' | Unknown function: 'hello' |
| '123(false)' | Cannot handle compound expressions. |
| 'String("123")' | true |
| 'String(123)' | true |
| 'String("0")' | true |
| 'String(0)' | true |
| 'Number("123")' | true |
| 'Number(123)' | true |
| 'Number("0")' | false |
| 'Number(0)' | false |
| 'Number("hello there")' | Error converting value 'hello there' to number |
| 'Boolean("123")' | true |
| 'Boolean(123)' | true |
| 'Boolean("0")' | true |
| 'Boolean(0)' | false |
| 'Boolean("hello there")' | true |
| 'Boolean("")' | false |
| 'Boolean(Number(String(0)))' | false |
| 'Boolean(Number(String(1)))' | true |
| 'round("asd")' | Invalid argument #0 of type string for function 'round': expected number |
| 'round("13")' | Invalid argument #0 of type string for function 'round': expected number |
| 'round(13)' | true |
| 'round(13) == round(13.1222)' | true |
| 'round(13) != round(13.9922)' | true |
| 'round(13) == floor(13.9922)' | true |
| 'floor(13E8)' | true |
| 'floor(1359 / 342)' | true |
| 'upper("alon") == "ALON"' | true |
| 'upper("asd") != "ALON"' | true |
| 'upper("asd") != 333' | The operator != cannot be used with string,number. It can only be used with the types: number,string,boolean |
| 'lower("DIAMANT") == "diamant"' | true |
| 'lower("asd") != "ALON"' | true |
| 'lower("ASD") == "asd"' | true |
| 'trim(" ASD ") == "ASD"' | true |
| 'trim(lower(" ASD ")) == "asd"' | true |
| 'lower(trim(" ASD ")) == "asd"' | true |
| 'trimLeft(" ASD ") == "ASD "' | true |
| 'trimRight(" ASD ") == " ASD"' | true |
| 'replace("test me and you", "me", "her") == "test her and you"' | true |
| 'replace("test ME and you", "me", "her") == "test ME and you"' | true |
| 'replace("test ME and you", 123, "her") == "test ME and you"' | Invalid argument #1 of type number for function 'replace': expected string |
| 'replace("test ME and you", 123) == "test ME and you"' | Expected 3 arguments for function 'replace'. |
| 'replace("test ME and you", 123, 234, "her") == "test ME and you"' | Expected 3 arguments for function 'replace'. |
| 'substring("test ME and you", 0, 4) == "test"' | true |
| 'substring("test ME and you", 4, 8) == " ME "' | true |
| 'substring("test ME and you", 4, null) == " ME and you"' | Invalid argument #2 of type null for function 'substring': expected number |
| 'substring("test ME and you", 4, -1) == " ME and you"' | true |
| 'length("hello") == 5' | true |
| 'length(12345) == 5' | Invalid argument #0 of type number for function 'length': expected string |
| 'includes("team", "i")' | false |
| 'includes("team", "ea")' | true |
| 'includes("team", 456)' | Invalid argument #1 of type number for function 'includes': expected string |
| 'indexOf("team", "i") == -1' | true |
| 'indexOf("team", "ea") == 1' | true |
| 'indexOf("team", 456)' | Invalid argument #1 of type number for function 'indexOf': expected string |
| 'indexOf(234234, "234")' | Invalid argument #0 of type number for function 'indexOf': expected string |
| 'indexOf(String(999234234.23423), "234")' | true |
| 'match("hello there you", "..ll.", false)' | true |
| 'match("hello there you", "..LL.", false)' | false |
| 'match("hello there you", "hell$", true)' | false |
| 'match("hello there you", "^hell", true)' | true |
| 'match("hello there you", "bye", false)' | false |
| 'match("hello there you", 123, false)' | Invalid argument #1 of type number for function 'match': expected string |
| 'match(457457, 123, false)' | Invalid argument #0 of type number for function 'match': expected string |
| 'match("hello there you", "^hell", null)' | Invalid argument #2 of type null for function 'match': expected boolean |
| 'match(123, false)' | Expected 3 arguments for function 'match'. |
| 'match()' | Expected 3 arguments for function 'match'. |
| 'match( lower(String(round(99.234234)) + " is the number to call") + " in case of emergencies", "100", true) == ( 99 2000 > 99 999 && trueBooleanValue && somePositiveNumericValue < someNegativeNumericValue) ' | true |
| 'match(author.name, "alon", true) && author.github.repositories > 10 && Number(substring(company.phoneNumber, 1, 4)) / 2 == 486' | true |
| 'hello there.testVariable == 567' | Cannot handle compound expressions. |
| 'hello there.badly-named-variable == 890' | Cannot handle compound expressions. |
| 'Variable("hello there").testVariable == 567' | Invalid identifier: 'hello there' |
| 'Member(Variable("hello there"), "badly-named-variable") == 890' | Invalid identifier: 'hello there' |
| 'Variable("author").name == "Alon Diamant"' | true |
| 'Member(author.github, "repositories") == 12' | true |
| 'author.toString' | Undefined identifier member: 'toString' |
| 'author.eval("")' | Invalid function name type: 'MemberExpression' |
Available gulp tasks
gulp lint- runs eslintgulp test:unit- runs mocha unit testsgulp coverage- runs unit tests and generates coverage reportgulp test:integration- runs karma testsgulp test- runs unit and integration tests and generates code coverage reportgulp- default task, runs lint and test
Running tests
Install dev dependencies and run the test:
$ npm install -d && gulpAuthor
Alon Diamant (advance512)
License
Copyright © 2016, Codefresh. Released under the MIT license.
0.9.2
8 years ago