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

[PORT] add string interpolation feature #1759

Merged
merged 2 commits into from
Feb 21, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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