Skip to content

Commit

Permalink
feat: add support for array coalescing (#15)
Browse files Browse the repository at this point in the history
* feat: add support for array coalescing

Added support ??? as coalesing using arrays.
This is for getting rid of getOneByPaths to further improve the performance.
  • Loading branch information
koladilip authored Dec 1, 2022
1 parent 02ffb67 commit 2f3bf0f
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 33 deletions.
26 changes: 18 additions & 8 deletions src/lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
};
}
Expand Down Expand Up @@ -550,7 +559,8 @@ export class JsonTemplateLexer {
this.scanPunctuatorForDots() ||
this.scanPunctuatorForQuestionMarks() ||
this.scanPunctuatorForEquality() ||
this.scanPunctuatorForRepeatedTokens() ||
this.scanPunctuatorForRepeatedTokens('?', 3) ||
this.scanPunctuatorForRepeatedTokens('|&*.=>?<', 2) ||
this.scanSingleCharPunctuators()
);
}
Expand Down
71 changes: 47 additions & 24 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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('{')) {
Expand All @@ -394,15 +376,24 @@ 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)) {
this.lexer.ignoreTokens(1);
}
}

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 {
Expand Down Expand Up @@ -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()],
};
}

Expand Down Expand Up @@ -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();
}
Expand Down
1 change: 1 addition & 0 deletions test/scenarios/bad_templates/bad_array_coalese_expr.jt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
??? []
4 changes: 4 additions & 0 deletions test/scenarios/bad_templates/data.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand Down
10 changes: 9 additions & 1 deletion test/scenarios/logics/template.jt
Original file line number Diff line number Diff line change
@@ -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,
];

0 comments on commit 2f3bf0f

Please sign in to comment.