Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Expression with adding type checking functions #1911

Merged
merged 11 commits into from
Mar 17, 2020
37 changes: 31 additions & 6 deletions libraries/adaptive-expressions/src/expressionFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2818,11 +2818,6 @@ export class ExpressionFunctions {
ReturnType.Number,
ExpressionFunctions.validateBinaryNumber),
new ExpressionEvaluator(ExpressionType.CreateArray, ExpressionFunctions.apply((args: any []): any[] => Array.from(args)), ReturnType.Object),
new ExpressionEvaluator(
ExpressionType.Array,
ExpressionFunctions.apply((args: any []): any[] => [args[0]], ExpressionFunctions.verifyString),
ReturnType.Object,
ExpressionFunctions.validateUnary),
new ExpressionEvaluator(
ExpressionType.Binary,
ExpressionFunctions.apply((args: any []): string => this.toBinary(args[0]), ExpressionFunctions.verifyString),
Expand Down Expand Up @@ -2988,7 +2983,37 @@ export class ExpressionFunctions {
return {value, error};
}),
ReturnType.Boolean,
ExpressionFunctions.validateIsMatch)
ExpressionFunctions.validateIsMatch),

// Type Checking Functions
new ExpressionEvaluator(ExpressionType.isString, ExpressionFunctions.apply(
(args: any[]): boolean => typeof args[0] === 'string'),
ReturnType.Boolean,
ExpressionFunctions.validateUnary),
Danieladu marked this conversation as resolved.
Show resolved Hide resolved
new ExpressionEvaluator(ExpressionType.isInteger, ExpressionFunctions.apply(
(args: any[]): boolean => typeof args[0] === 'number' && args[0] % 1 === 0),
cosmicshuai marked this conversation as resolved.
Show resolved Hide resolved
cosmicshuai marked this conversation as resolved.
Show resolved Hide resolved
ReturnType.Boolean,
ExpressionFunctions.validateUnary),
Danieladu marked this conversation as resolved.
Show resolved Hide resolved
new ExpressionEvaluator(ExpressionType.isFloat, ExpressionFunctions.apply(
(args: any[]): boolean => typeof args[0] === 'number' && args[0] % 1 !== 0),
cosmicshuai marked this conversation as resolved.
Show resolved Hide resolved
ReturnType.Boolean,
ExpressionFunctions.validateUnary),
Danieladu marked this conversation as resolved.
Show resolved Hide resolved
new ExpressionEvaluator(ExpressionType.isArray, ExpressionFunctions.apply(
(args: any[]): boolean => Array.isArray(args[0])),
ReturnType.Boolean,
ExpressionFunctions.validateUnary),
Danieladu marked this conversation as resolved.
Show resolved Hide resolved
new ExpressionEvaluator(ExpressionType.isObject, ExpressionFunctions.apply(
(args: any[]): boolean => typeof args[0] === 'object'),
ReturnType.Boolean,
ExpressionFunctions.validateUnary),
Danieladu marked this conversation as resolved.
Show resolved Hide resolved
new ExpressionEvaluator(ExpressionType.isBoolean, ExpressionFunctions.apply(
(args: any[]): boolean => typeof args[0] === 'boolean'),
ReturnType.Boolean,
ExpressionFunctions.validateUnary),
Danieladu marked this conversation as resolved.
Show resolved Hide resolved
new ExpressionEvaluator(ExpressionType.isDateTime, ExpressionFunctions.apply(
(args: any[]): boolean => typeof args[0] === 'string' && this.verifyISOTimestamp(args[0]) === undefined),
ReturnType.Boolean,
ExpressionFunctions.validateUnary)
Danieladu marked this conversation as resolved.
Show resolved Hide resolved
];

const lookup: Map<string, ExpressionEvaluator> = new Map<string, ExpressionEvaluator>();
Expand Down
10 changes: 9 additions & 1 deletion libraries/adaptive-expressions/src/expressionType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ export class ExpressionType {
public static readonly Int: string = 'int';
public static readonly String: string = 'string';
public static readonly Bool: string = 'bool';
public static readonly Array: string = 'array';
public static readonly Binary: string = 'binary';
public static readonly DataUri: string = 'dataUri';
public static readonly DataUriToBinary: string = 'dataUriToBinary';
Expand Down Expand Up @@ -154,4 +153,13 @@ export class ExpressionType {

// Regar expression
public static readonly IsMatch: string = 'isMatch';

//Type Checking
public static readonly isString: string = 'isString';
public static readonly isInteger: string = 'isInteger';
public static readonly isArray: string = 'isArray';
public static readonly isObject: string = 'isObject';
public static readonly isFloat: string = 'isFloat';
public static readonly isDateTime: string = 'isDateTime';
public static readonly isBoolean: string = 'isBoolean';
}
20 changes: 10 additions & 10 deletions libraries/adaptive-expressions/src/parser/ExpressionAntlrParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ options { tokenVocab=ExpressionAntlrLexer; }
file: expression EOF;

expression
: (NON|SUBSTRACT|PLUS) expression #unaryOpExp
| <assoc=right> expression XOR expression #binaryOpExp
| expression (ASTERISK|SLASH|PERCENT) expression #binaryOpExp
| expression (PLUS|SUBSTRACT) expression #binaryOpExp
| expression (DOUBLE_EQUAL|NOT_EQUAL) expression #binaryOpExp
| expression (SINGLE_AND) expression #binaryOpExp
| expression (LESS_THAN|LESS_OR_EQUAl|MORE_THAN|MORE_OR_EQUAL) expression #binaryOpExp
| expression DOUBLE_AND expression #binaryOpExp
| expression DOUBLE_VERTICAL_CYLINDER expression #binaryOpExp
| primaryExpression #primaryExp
: (NON|SUBSTRACT|PLUS) expression #unaryOpExp
| <assoc=right> expression XOR expression #binaryOpExp
| expression (ASTERISK|SLASH|PERCENT) expression #binaryOpExp
| expression (PLUS|SUBSTRACT) expression #binaryOpExp
| expression (DOUBLE_EQUAL|NOT_EQUAL) expression #binaryOpExp
| expression (SINGLE_AND) expression #binaryOpExp
| expression (LESS_THAN|LESS_OR_EQUAl|MORE_THAN|MORE_OR_EQUAL) expression #binaryOpExp
| expression DOUBLE_AND expression #binaryOpExp
| expression DOUBLE_VERTICAL_CYLINDER expression #binaryOpExp
| primaryExpression #primaryExp
;

primaryExpression
Expand Down
11 changes: 10 additions & 1 deletion libraries/adaptive-expressions/tests/badExpression.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,16 @@ const badExpressions =

// SetPathToValue tests
'setPathToValue(2+3, 4)', // Not a real path
'setPathToValue(a)' // Missing value
'setPathToValue(a)', // Missing value

//Type Checking
'isString(hello, hello)', // should have one parameter
'isInteger(one, hello)', // should have one parameter
'isFloat(1.324, hello)', // should have one parameter
'isArray(createArrat(1,2,3), hello)', // should have one parameter
'isBoolean(true, false)', // should have one parameter
'isDateTime("2018-03-15T13:00:00.111Z", hello)', // should have one parameter
'isObject({}, false)', // should have one parameter
];

const scope = {
Expand Down
59 changes: 52 additions & 7 deletions libraries/adaptive-expressions/tests/expressionParser.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,26 @@ const dataSource = [

// Operators tests
['1 + 2', 3],
['1 +\n 2', 3],
['1 \n+ 2', 3],
['1 +\r\n 2', 3],
['- 1 + 2', 1],
['-\r\n 1 + 2', 1],
['+ 1 + 2', 3],
['+\r\n 1 + 2', 3],
['1 - 2', -1],
['1 -\r\n 2', -1],
['1 - (-2)', 3],
['1 - (\r\n-2)', 3],
['1.0 + 2.0', 3.0],
['1 * 2 + 3', 5],
['1 *\r\n 2 + 3', 5],
['1 + 2 * 3', 7],
['4 / 2', 2],
['4 /\r\n 2', 2],
['1 + 3 / 2', 2],
['(1 + 3) / 2', 2],
['(1 +\r\n 3) / 2', 2],
['1 * (2 + 3)', 5],
['(1 + 2) * 3', 9],
['(one + two) * bag.three', 9.0, ['one', 'two', 'bag.three']],
Expand All @@ -43,14 +53,20 @@ const dataSource = [
['one + two + hello + one + two', '3hello12'],

['2^2', 4.0],
['2^\r\n2', 4.0],

['3^2^2', 81.0],
['one >\r\n 0.5', true],
['one > 0.5 && two < 2.5', true],
['one > 0.5 || two < 1.5', true],
['5 % 2', 1],
['5 %\r\n 2', 1],
['!(one == 1.0)', false],
['!\r\n(one == 1.0)', false],
['!!(one == 1.0)', true],
['!exists(xione) || !!exists(two)', true],
['(1 + 2) == (4 - 1)', true],
['(1 + 2) ==\r\n (4 - 1)', true],
['!!exists(one) == !!exists(one)', true],
['!(one == 1.0)', false, ['one']],
['!!(one == 1.0)', true, ['one']],
Expand All @@ -61,25 +77,35 @@ const dataSource = [
['hello == \'hello\'', true],
['hello == \'world\'', false],
['(1 + 2) != (4 - 1)', false],
['(1 + 2) !=\r\n (4 - 1)', false],
['!!exists(one) != !!exists(one)', false],
['hello != \'hello\'', false],
['hello != \'world\'', true],
['hello != "hello"', false],
['hello != "world"', true],
['(1 + 2) >= (4 - 1)', true],
['(2 + 2) >= (4 - 1)', true],
['(2 + 2) >=\r\n (4 - 1)', true],
['float(5.5) >= float(4 - 1)', true],
['(1 + 2) <= (4 - 1)', true],
['(2 + 2) <= (4 - 1)', false],
['(2 + 2) <=\r\n (4 - 1)', false],
['float(5.5) <= float(4 - 1)', false],
['\'string\'&\'builder\'', 'stringbuilder'],
['"string"&"builder"', 'stringbuilder'],
['"string"&\n"builder"', 'stringbuilder'],
['"string"&\r\n"builder"', 'stringbuilder'],
['one > 0.5 && two < 2.5', true, oneTwo],
['notThere > 4', false],
['float(5.5) && float(0.0)', true],
['hello && "hello"', true],
['hello &&\n "hello"', true],
['hello &&\r\n "hello"', true],
['items || ((2 + 2) <= (4 - 1))', true], // true || false
['0 || false', true], // true || false
['0 ||\n false', true], // true || false
['0 ||\r\n false', true], // true || false
['false ||\r\n false || \r\n true', true], // true || false
['!(hello)', false], // false
['!(10)', false],
['!(0)', false],
Expand All @@ -91,9 +117,11 @@ const dataSource = [
['concat(hello,world)', 'helloworld'],
['concat(hello,nullObj)', 'hello'],
['concat(\'hello\',\'world\')', 'helloworld'],
['concat(\'hello\'\r\n,\'world\')', 'helloworld'],
['concat("hello","world")', 'helloworld'],
['add(hello,world)', 'helloworld'],
['add(\'hello\',\'world\')', 'helloworld'],
['add(\'hello\',\r\n\'world\')', 'helloworld'],
['add(nullObj,\'world\')', 'world'],
['add(\'hello\',nullObj)', 'hello'],
['add("hello","world")', 'helloworld'],
Expand All @@ -104,7 +132,7 @@ const dataSource = [
['length(hello + world)', 10],
['count(\'hello\')', 5],
['count("hello")', 5],
['count(concat(hello,world))', 10],
['count(concat(hello,\r\nworld))', 10],
['replace(\'hello\', \'l\', \'k\')', 'hekko'],
['replace(\'hello\', \'L\', \'k\')', 'hello'],
['replace(nullObj, \'L\', \'k\')', ''],
Expand Down Expand Up @@ -276,7 +304,6 @@ const dataSource = [
['bool(\'hi\')', true],
['createArray(\'h\', \'e\', \'l\', \'l\', \'o\')', ['h', 'e', 'l', 'l', 'o']],
['createArray(1, bool(0), string(bool(1)), float(\'10\'))', [1, true, 'true', 10.0]],
['array(hello)', ['hello']],
['binary(hello)', '0110100001100101011011000110110001101111'],
['dataUri(hello)', 'data:text/plain;charset=utf-8;base64,aGVsbG8='],
['dataUriToBinary(dataUri(hello))', '011001000110000101110100011000010011101001110100011001010111100001110100001011110111000001101100011000010110100101101110001110110110001101101000011000010111001001110011011001010111010000111101011101010111010001100110001011010011100000111011011000100110000101110011011001010011011000110100001011000110000101000111010101100111001101100010010001110011100000111101'],
Expand Down Expand Up @@ -342,8 +369,8 @@ const dataSource = [
['formatDateTime(notISOTimestamp, \'ddd\')', 'Thu'],
['formatDateTime(notISOTimestamp, \'dddd\')', 'Thursday'],
['formatDateTime(\'2018-03-15T00:00:00.000Z\', \'yyyy\')', '2018'],
// ['formatDateTime(\'2018-03-15T00:00:00.000Z\', \'yyyy-MM-dd-\\\\d\')', '2018-03-15-4'],
// - Fails in the US
// ['formatDateTime(\'2018-03-15T00:00:00.000Z\', \'yyyy-MM-dd-\\\\d\')', '2018-03-15-4'],
// - Fails in the US
['formatDateTime(\'2018-03-15T00:00:00.010Z\', \'FFFF\')', '0100'],
['formatDateTime(\'2018-03-15T00:00:00.010Z\', \'FFFFFF\')', '010000'],
['formatDateTime(\'2018-03-15T00:00:00.010Z\', \'FFF\')', '010'],
Expand All @@ -366,9 +393,9 @@ const dataSource = [
['subtractFromTime(timestamp, 1, \'Hour\')', '2018-03-15T12:00:00.111Z'],
['subtractFromTime(timestamp, 1, \'Minute\')', '2018-03-15T12:59:00.111Z'],
['subtractFromTime(timestamp, 1, \'Second\')', '2018-03-15T12:59:59.111Z'],
// ['dateReadBack(timestamp, addDays(timestamp, 1))', 'tomorrow'],
// ['dateReadBack(addDays(timestamp, 1),timestamp)', 'yesterday'],
// - Fails in the US
// ['dateReadBack(timestamp, addDays(timestamp, 1))', 'tomorrow'],
// ['dateReadBack(addDays(timestamp, 1),timestamp)', 'yesterday'],
// - Fails in the US
['getTimeOfDay(\'2018-03-15T00:00:00.000Z\')', 'midnight'],
['getTimeOfDay(\'2018-03-15T08:00:00.000Z\')', 'morning'],
['getTimeOfDay(\'2018-03-15T12:00:00.000Z\')', 'noon'],
Expand Down Expand Up @@ -514,6 +541,24 @@ const dataSource = [
['isMatch("a", "\\\\w{1}")', true], // "\w" (match [a-zA-Z0-9_])
['isMatch("1", "\\\\d{1}")', true], // "\d" (match [0-9])

//Type Checking Tests
['isString(hello)', true],
['isString("Monday")', true],
['isString(one)', false],
['isInteger(one)', true],
['isInteger(1)', true],
['isInteger(1.23)', false],
['isFloat(one)', false],
['isFloat(1)', false],
['isFloat(1.23)', true],
['isArray(hello)', false],
['isArray(createArray(1,2,3))', true],
['isObject(hello)', false],
['isObject(dialog)', true],
['isBoolean(hello)', false],
['isBoolean(1 == one)', true],
['isDateTime(hello)', false],
['isDateTime(timestamp)', true],
// Empty expression
['', ''],

Expand Down
2 changes: 1 addition & 1 deletion libraries/botbuilder-lg/src/LGFileLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ fragment STRING_LITERAL : ('\'' (~['\r\n])* '\'') | ('"' (~["\r\n])* '"');

fragment STRING_INTERPOLATION : '`' ('\\`' | ~'`')* '`';

fragment EXPRESSION_FRAGMENT : '$' '{' (STRING_LITERAL | STRING_INTERPOLATION | EMPTY_OBJECT | ~[\r\n{}'"`] )+ '}'?;
fragment EXPRESSION_FRAGMENT : '$' '{' (STRING_LITERAL | STRING_INTERPOLATION | EMPTY_OBJECT | ~[{}'"`] )+ '}'?;

fragment ESCAPE_CHARACTER_FRAGMENT : '\\' ~[\r\n]?;

Expand Down
Loading