From 2f3bf0fba589edc403b76e5b1da9437fbe91524c Mon Sep 17 00:00:00 2001 From: Dilip Kola <33080863+koladilip@users.noreply.github.com> Date: Thu, 1 Dec 2022 13:20:29 +0530 Subject: [PATCH] feat: add support for array coalescing (#15) * feat: add support for array coalescing Added support ??? as coalesing using arrays. This is for getting rid of getOneByPaths to further improve the performance. --- src/lexer.ts | 26 ++++--- src/parser.ts | 71 ++++++++++++------- .../bad_templates/bad_array_coalese_expr.jt | 1 + test/scenarios/bad_templates/data.ts | 4 ++ test/scenarios/logics/template.jt | 10 ++- 5 files changed, 79 insertions(+), 33 deletions(-) create mode 100644 test/scenarios/bad_templates/bad_array_coalese_expr.jt diff --git a/src/lexer.ts b/src/lexer.ts index 25b9fc4..8e59d71 100644 --- a/src/lexer.ts +++ b/src/lexer.ts @@ -502,16 +502,25 @@ export class JsonTemplateLexer { } } - private scanPunctuatorForRepeatedTokens(): Token | undefined { - let start = this.idx, - ch1 = this.codeChars[this.idx], - ch2 = this.codeChars[this.idx + 1]; + private scanPunctuatorForRepeatedTokens( + validSymbols: string, + numRepeations = 2, + ): Token | undefined { + let start = this.idx; + let ch = this.codeChars[this.idx]; + let tokenVal = ch; + for (let i = 1; i < numRepeations; i++) { + if (this.codeChars[this.idx + i] !== ch) { + return; + } + tokenVal += ch; + } - if (ch1 === ch2 && '|&*.=>?<'.includes(ch1)) { - this.idx += 2; + if (validSymbols.includes(ch)) { + this.idx += numRepeations; return { type: TokenType.PUNCT, - value: ch1 + ch2, + value: tokenVal, range: [start, this.idx], }; } @@ -550,7 +559,8 @@ export class JsonTemplateLexer { this.scanPunctuatorForDots() || this.scanPunctuatorForQuestionMarks() || this.scanPunctuatorForEquality() || - this.scanPunctuatorForRepeatedTokens() || + this.scanPunctuatorForRepeatedTokens('?', 3) || + this.scanPunctuatorForRepeatedTokens('|&*.=>?<', 2) || this.scanSingleCharPunctuators() ); } diff --git a/src/parser.ts b/src/parser.ts index 3a60a28..0f95a65 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -106,7 +106,7 @@ export class JsonTemplateParser { case OperatorType.CONDITIONAL: return this.parseAssignmentExpr(); case OperatorType.ASSIGNMENT: - return this.parseCoalescingExpr(); + return this.parseCoalesceExpr(); case OperatorType.COALESCING: return this.parseLogicalORExpr(); case OperatorType.OR: @@ -366,26 +366,8 @@ export class JsonTemplateParser { }; } - private combineObjectFilters(objectFilters: ObjectFilterExpression[]): ObjectFilterExpression[] { - if (objectFilters.length <= 1) { - return objectFilters; - } - const expr1 = objectFilters.shift() as ObjectFilterExpression; - const expr2 = this.combineObjectFilters(objectFilters); - return [ - { - type: SyntaxType.OBJECT_FILTER_EXPR, - filter: { - type: SyntaxType.LOGICAL_AND_EXPR, - op: '&&', - args: [expr1.filter, expr2[0].filter], - }, - }, - ]; - } - private parseObjectFiltersExpr(): (ObjectFilterExpression | IndexFilterExpression)[] { - const objectFilters: ObjectFilterExpression[] = []; + const objectFilters: Expression[] = []; const indexFilters: IndexFilterExpression[] = []; while (this.lexer.match('{')) { @@ -394,7 +376,7 @@ export class JsonTemplateParser { if (expr.type === SyntaxType.OBJECT_INDEX_FILTER_EXPR) { indexFilters.push(expr as IndexFilterExpression); } else { - objectFilters.push(expr as ObjectFilterExpression); + objectFilters.push(expr.filter); } this.lexer.expect('}'); if (this.lexer.match('.') && this.lexer.match('{', 1)) { @@ -402,7 +384,16 @@ export class JsonTemplateParser { } } - return [...this.combineObjectFilters(objectFilters), ...indexFilters]; + if (!objectFilters.length) { + return indexFilters; + } + + const objectFilter: ObjectFilterExpression = { + type: SyntaxType.OBJECT_FILTER_EXPR, + filter: this.combineExpressionsAsBinaryExpr(objectFilters, SyntaxType.LOGICAL_AND_EXPR, '&&'), + }; + + return [objectFilter, ...indexFilters]; } private parseConditionalExpr(): ConditionalExpression | Expression { @@ -451,14 +442,42 @@ export class JsonTemplateParser { }; } - private parseCoalescingExpr(): BinaryExpression | Expression { + private combineExpressionsAsBinaryExpr( + values: Expression[], + type: SyntaxType, + op: string, + ): BinaryExpression | Expression { + if (!values?.length) { + throw new JsonTemplateParserError('expected at least 1 expression'); + } + if (values.length === 1) { + return values[0]; + } + return { + type, + op, + args: [values.shift(), this.combineExpressionsAsBinaryExpr(values, type, op)], + }; + } + + private parseArrayCoalesceExpr(): BinaryExpression | Expression { + this.lexer.ignoreTokens(1); + const expr = this.parseArrayExpr(); + return this.combineExpressionsAsBinaryExpr( + expr.elements, + SyntaxType.LOGICAL_COALESCE_EXPR, + '??', + ); + } + + private parseCoalesceExpr(): BinaryExpression | Expression { let expr = this.parseNextExpr(OperatorType.COALESCING); if (this.lexer.match('??')) { return { type: SyntaxType.LOGICAL_COALESCE_EXPR, op: this.lexer.value(), - args: [expr, this.parseCoalescingExpr()], + args: [expr, this.parseCoalesceExpr()], }; } @@ -963,6 +982,10 @@ export class JsonTemplateParser { return this.parseNumber(); } + if (this.lexer.match('???')) { + return this.parseArrayCoalesceExpr(); + } + if (this.lexer.match('.') && this.lexer.matchINT(1) && !this.lexer.match('.', 2)) { return this.parseFloatingNumber(); } diff --git a/test/scenarios/bad_templates/bad_array_coalese_expr.jt b/test/scenarios/bad_templates/bad_array_coalese_expr.jt new file mode 100644 index 0000000..b637b4e --- /dev/null +++ b/test/scenarios/bad_templates/bad_array_coalese_expr.jt @@ -0,0 +1 @@ +??? [] \ No newline at end of file diff --git a/test/scenarios/bad_templates/data.ts b/test/scenarios/bad_templates/data.ts index 548513a..ba0afcd 100644 --- a/test/scenarios/bad_templates/data.ts +++ b/test/scenarios/bad_templates/data.ts @@ -1,6 +1,10 @@ import { Sceanario } from '../../types'; export const data: Sceanario[] = [ + { + templatePath: 'bad_array_coalese_expr.jt', + error: 'expected at least 1 expression', + }, { templatePath: 'bad_async_usage.jt', error: 'Unexpected token', diff --git a/test/scenarios/logics/template.jt b/test/scenarios/logics/template.jt index 996ee4c..9aefda8 100644 --- a/test/scenarios/logics/template.jt +++ b/test/scenarios/logics/template.jt @@ -1 +1,9 @@ -[2 && 3, 0 && 3, 2 || 3, 0 || 3, 0 ?? 3, null ?? undefined ?? 3, !false, !!true]; +[ + 2 && 3, + 0 && 3, + 2 || 3, + 0 || 3, + 0 ?? 3, + ??? [null, undefined, 3], + !false, !!true, +];