Skip to content

Commit

Permalink
[Expression]add string interpolation feature (#1759)
Browse files Browse the repository at this point in the history
  • Loading branch information
Danieladu authored Feb 21, 2020
1 parent a5eb5b2 commit 323053d
Show file tree
Hide file tree
Showing 19 changed files with 1,371 additions and 739 deletions.
15 changes: 12 additions & 3 deletions libraries/adaptive-expressions/src/expressionFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2077,9 +2077,18 @@ export class ExpressionFunctions {
ExpressionFunctions.validateUnary),
new ExpressionEvaluator(
ExpressionType.Concat,
ExpressionFunctions.apply((args: any []): string => ''.concat(...args.map((arg): string => ExpressionFunctions.parseStringOrNull(arg))), ExpressionFunctions.verifyStringOrNull),
ExpressionFunctions.apply((args: any []): string => {
let result = '';
for (const arg of args) {
if (arg !== undefined && arg !== null) {
result += arg.toString();
}
}

return result;
}),
ReturnType.String,
ExpressionFunctions.validateString),
ExpressionFunctions.validateAtLeastOne),
new ExpressionEvaluator(
ExpressionType.Length,
ExpressionFunctions.apply((args: any []): number => (ExpressionFunctions.parseStringOrNull(args[0])).length, ExpressionFunctions.verifyStringOrNull),
Expand Down Expand Up @@ -2936,7 +2945,7 @@ export class ExpressionFunctions {
lookup.set('lessOrEquals', lookup.get(ExpressionType.LessThanOrEqual));
lookup.set('not', lookup.get(ExpressionType.Not));
lookup.set('or', lookup.get(ExpressionType.Or));
lookup.set('concat', lookup.get(ExpressionType.Concat));
lookup.set('&', lookup.get(ExpressionType.Concat));

return lookup;
}
Expand Down
2 changes: 1 addition & 1 deletion libraries/adaptive-expressions/src/expressionType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class ExpressionType {
public static readonly Not: string = '!';

// String
public static readonly Concat: string = '&';
public static readonly Concat: string = 'concat';
public static readonly Length: string = 'length';
public static readonly Replace: string = 'replace';
public static readonly ReplaceIgnoreCase: string = 'replaceIgnoreCase';
Expand Down
48 changes: 0 additions & 48 deletions libraries/adaptive-expressions/src/parser/Expression.g4

This file was deleted.

82 changes: 82 additions & 0 deletions libraries/adaptive-expressions/src/parser/ExpressionLexer.g4
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
lexer grammar ExpressionLexer;

@lexer::members {
ignoreWS = true; // usually we ignore whitespace, but inside stringInterpolation, whitespace is significant
}

fragment LETTER : [a-zA-Z];
fragment DIGIT : [0-9];

STRING_INTERPOLATION_START : '`' { this.ignoreWS = false;} -> pushMode(STRING_INTERPOLATION_MODE);

// operators
PLUS: '+';

SUBSTRACT: '-';

NON: '!';

XOR: '^';

ASTERISK: '*';

SLASH: '/';

PERCENT: '%';

DOUBLE_EQUAL: '==';

NOT_EQUAL: '!=' | '<>';

SINGLE_AND: '&';

DOUBLE_AND: '&&';

DOUBLE_VERTICAL_CYLINDER: '||';

LESS_THAN: '<';

MORE_THAN: '>';

LESS_OR_EQUAl: '<=';

MORE_OR_EQUAL: '>=';

OPEN_BRACKET: '(';

CLOSE_BRACKET: ')';

DOT: '.';

OPEN_SQUARE_BRACKET: '[';

CLOSE_SQUARE_BRACKET: ']';

COMMA: ',';


NUMBER : DIGIT + ( '.' DIGIT +)? ;

WHITESPACE : (' '|'\t'|'\ufeff'|'\u00a0') {this.ignoreWS}? -> skip;

IDENTIFIER : (LETTER | '_' | '#' | '@' | '@@' | '$' | '%') (LETTER | DIGIT | '-' | '_')* '!'?;

NEWLINE : '\r'? '\n' -> skip;

STRING : ('\'' (~'\'')* '\'') | ('"' (~'"')* '"');

CONSTANT : ('[' WHITESPACE* ']') | ('{' WHITESPACE* '}');

INVALID_TOKEN_DEFAULT_MODE : . ;

mode STRING_INTERPOLATION_MODE;

STRING_INTERPOLATION_END : '`' {this.ignoreWS = true;} -> type(STRING_INTERPOLATION_START), popMode;

TEMPLATE : '$' '{' (STRING | ~[\r\n{}'"])*? '}';
ESCAPE_CHARACTER : '\\' ~[\r\n]?;
TEXT_CONTENT : '\\`' | ~[\r\n];
42 changes: 42 additions & 0 deletions libraries/adaptive-expressions/src/parser/ExpressionParser.g4
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
parser grammar ExpressionParser;

options { tokenVocab=ExpressionLexer; }

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
;

primaryExpression
: OPEN_BRACKET expression CLOSE_BRACKET #parenthesisExp
| CONSTANT #constantAtom
| NUMBER #numericAtom
| STRING #stringAtom
| IDENTIFIER #idAtom
| stringInterpolation #stringInterpolationAtom
| primaryExpression DOT IDENTIFIER #memberAccessExp
| primaryExpression OPEN_BRACKET argsList? CLOSE_BRACKET #funcInvokeExp
| primaryExpression OPEN_SQUARE_BRACKET expression CLOSE_SQUARE_BRACKET #indexAccessExp
;

stringInterpolation
: STRING_INTERPOLATION_START (ESCAPE_CHARACTER | TEMPLATE | textContent)+ STRING_INTERPOLATION_START
;

textContent
: TEXT_CONTENT+
;

argsList
: expression (COMMA expression)*
;
52 changes: 49 additions & 3 deletions libraries/adaptive-expressions/src/parser/expressionEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
*/
import { ANTLRInputStream, CommonTokenStream } from 'antlr4ts';
// tslint:disable-next-line: no-submodule-imports
import { AbstractParseTreeVisitor, ParseTree } from 'antlr4ts/tree';
import { AbstractParseTreeVisitor, ParseTree, TerminalNode } from 'antlr4ts/tree';
import { ExpressionFunctions } from '../expressionFunctions';
import { Constant } from '../constant';
import { Expression } from '../expression';
import { EvaluatorLookup } from '../expressionEvaluator';
import { ExpressionParserInterface } from '../expressionParser';
import { ExpressionType } from '../expressionType';
import { ExpressionLexer, ExpressionParser, ExpressionVisitor } from './generated';
import { ExpressionLexer, ExpressionParser, ExpressionParserVisitor } from './generated';
import * as ep from './generated/ExpressionParser';
import { ParseErrorListener } from './parseErrorListener';
import { Util } from './util';
Expand All @@ -30,7 +30,7 @@ export class ExpressionEngine implements ExpressionParserInterface {
public readonly EvaluatorLookup: EvaluatorLookup;

// tslint:disable-next-line: typedef
private readonly ExpressionTransformer = class extends AbstractParseTreeVisitor<Expression> implements ExpressionVisitor<Expression> {
private readonly ExpressionTransformer = class extends AbstractParseTreeVisitor<Expression> implements ExpressionParserVisitor<Expression> {

private readonly _lookup: EvaluatorLookup = undefined;
public constructor(lookup: EvaluatorLookup) {
Expand Down Expand Up @@ -120,6 +120,35 @@ export class ExpressionEngine implements ExpressionParserInterface {
}
}

public visitStringInterpolationAtom(context: ep.StringInterpolationAtomContext): Expression {
let children: Expression[] = [];

for (const node of context.stringInterpolation().children) {
if (node instanceof TerminalNode){
switch((node as TerminalNode).symbol.type) {
case ep.ExpressionParser.TEMPLATE:
const expressionString = this.trimExpression(node.text);
children.push(new ExpressionEngine(this._lookup).parse(expressionString));
break;
case ep.ExpressionParser.TEXT_CONTENT:
children.push(new Constant(node.text));
break;
case ep.ExpressionParser.ESCAPE_CHARACTER:
children.push(new Constant(Util.unescape(node.text)));
break;
default:
break;
}
} else {
children.push(new Constant(node.text));
}

}

return this.MakeExpression(ExpressionType.Concat, ...children);

}

public visitConstantAtom(context: ep.ConstantAtomContext): Expression {
let text: string = context.text;
if (text.startsWith('[') && text.endsWith(']')) {
Expand Down Expand Up @@ -154,6 +183,23 @@ export class ExpressionEngine implements ExpressionParserInterface {

return result;
}

private trimExpression(expression: string): string {
let result = expression.trim();
if (result.startsWith('$')) {
result = result.substr(1);
}

result = result.trim();

if (result.startsWith('{') && result.endsWith('}')) {
result = result.substr(1, result.length - 2);
}

return result.trim();
}


};

public constructor(lookup?: EvaluatorLookup) {
Expand Down
Loading

0 comments on commit 323053d

Please sign in to comment.