Skip to content

Commit

Permalink
feat: add support for index selectors
Browse files Browse the repository at this point in the history
New syntax supported .a.0.1 as an alternative to .a[0][1].
This is mainly done as source mapping files uses this syntax.
  • Loading branch information
koladilip committed Nov 29, 2022
1 parent 517f8cf commit c4610be
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 101 deletions.
84 changes: 22 additions & 62 deletions src/lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ export class JsonTemplateLexer {
return JsonTemplateLexer.isLiteralToken(this.lookahead());
}

matchINT(steps = 0): boolean {
return this.matchTokenType(TokenType.INT, steps);
}

matchToArray(): boolean {
return this.match('[') && this.match(']', 1);
}
Expand All @@ -58,8 +62,7 @@ export class JsonTemplateLexer {
matchPathPartSelector(): boolean {
let token = this.lookahead();
if (token.type === TokenType.PUNCT) {
let value = token.value;
return value === '.' || value === '..';
return token.value === '.' || token.value === '..';
}
return false;
}
Expand Down Expand Up @@ -91,41 +94,37 @@ export class JsonTemplateLexer {
return Object.values(Keyword).some((op) => op.toString() === id);
}

matchKeyword(op: string): boolean {
matchKeyword(): boolean {
return this.matchTokenType(TokenType.KEYWORD);
}

matchKeywordValue(val: string): boolean {
let token = this.lookahead();
return token.type === TokenType.KEYWORD && token.value === op;
return token.type === TokenType.KEYWORD && token.value === val;
}

matchIN(): boolean {
return this.matchKeyword(Keyword.IN);
return this.matchKeywordValue(Keyword.IN);
}

matchFunction(): boolean {
return this.matchKeyword(Keyword.FUNCTION);
return this.matchKeywordValue(Keyword.FUNCTION);
}

matchNew(): boolean {
return this.matchKeyword(Keyword.NEW);
return this.matchKeywordValue(Keyword.NEW);
}

matchTypeOf(): boolean {
return this.matchKeyword(Keyword.TYPEOF);
return this.matchKeywordValue(Keyword.TYPEOF);
}

matchAwait(): boolean {
return this.matchKeyword(Keyword.AWAIT);
}

matchAsync(): boolean {
return this.matchKeyword(Keyword.ASYNC);
return this.matchKeywordValue(Keyword.AWAIT);
}

matchLambda(): boolean {
return this.matchKeyword(Keyword.LAMBDA);
}

matchDefinition(): boolean {
return this.matchKeyword(Keyword.LET) || this.matchKeyword(Keyword.CONST);
return this.matchKeywordValue(Keyword.LAMBDA);
}

expect(value) {
Expand Down Expand Up @@ -217,7 +216,7 @@ export class JsonTemplateLexer {
};
}

let token = this.scanPunctuator() || this.scanID() || this.scanString() || this.scanNumeric();
let token = this.scanPunctuator() || this.scanID() || this.scanString() || this.scanInteger();
if (token) {
return token;
}
Expand Down Expand Up @@ -394,11 +393,11 @@ export class JsonTemplateLexer {
scanInteger(): Token | undefined {
let start = this.idx;
let ch = this.codeChars[this.idx];
if(!JsonTemplateLexer.isDigit(ch)) {
if (!JsonTemplateLexer.isDigit(ch)) {
return;
}
let num = ch;
while (++this.idx < this.codeChars.length ) {
while (++this.idx < this.codeChars.length) {
ch = this.codeChars[this.idx];
if (!JsonTemplateLexer.isDigit(ch)) {
break;
Expand All @@ -407,45 +406,9 @@ export class JsonTemplateLexer {
}
return {
type: TokenType.INT,
value: parseInt(num, 10),
value: num,
range: [start, this.idx],
}
}

private scanNumeric(): Token | undefined {
let start = this.idx,
ch = this.codeChars[this.idx],
isFloat = ch === '.';

if (isFloat || JsonTemplateLexer.isDigit(ch)) {
let num = ch;
while (++this.idx < this.codeChars.length) {
ch = this.codeChars[this.idx];
if (ch === '.') {
if (isFloat) {
this.throwUnexpectedToken();
}
isFloat = true;
} else if (!JsonTemplateLexer.isDigit(ch)) {
break;
}

num += ch;
}

if(isFloat) {
return {
type: TokenType.FLOAT,
value: parseFloat(num),
range: [start, this.idx],
}
}
return {
type: TokenType.INT,
value: parseInt(num, 10),
range: [start, this.idx],
}
}
};
}

private scanPunctuatorForDots(): Token | undefined {
Expand Down Expand Up @@ -480,9 +443,6 @@ export class JsonTemplateLexer {
range: [start, this.idx],
};
} else {
if (JsonTemplateLexer.isDigit(ch2)) {
return;
}
return {
type: TokenType.PUNCT,
value: '.',
Expand Down
98 changes: 68 additions & 30 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
BlockExpression,
PathOptions,
CompileTimeExpression,
Keyword,
} from './types';
import { JsonTemplateParserError } from './errors';
import { DATA_PARAM_KEY } from './constants';
Expand Down Expand Up @@ -134,7 +135,6 @@ export class JsonTemplateParser {
private parsePathPart(): Expression | Expression[] | undefined {
if (this.lexer.match('.()')) {
this.lexer.ignoreTokens(1);
return;
} else if (this.lexer.match('.') && this.lexer.match('(', 1)) {
this.lexer.ignoreTokens(1);
return this.parseBlockExpr();
Expand Down Expand Up @@ -256,18 +256,26 @@ export class JsonTemplateParser {
type: SyntaxType.ARRAY_INDEX_FILTER_EXPR,
indexes: {
type: SyntaxType.ARRAY_EXPR,
elements: [expr]
}
}
elements: [expr],
},
};
}

private createArrayFilterExpr(
expr: RangeFilterExpression | IndexFilterExpression,
): ArrayFilterExpression {
return {
type: SyntaxType.ARRAY_FILTER_EXPR,
filters: [expr],
};
}

private parseSelector(): SelectorExpression | IndexFilterExpression | Expression {
let selector = this.lexer.value();
let intExpr = this.parseIntegerExpr();
if(intExpr) {
return this.createArrayIndexFilterExpr(intExpr);
if (this.lexer.matchINT()) {
return this.createArrayFilterExpr(this.createArrayIndexFilterExpr(this.parseLiteralExpr()));
}

let prop: Token | undefined;
if (this.lexer.match('*') || this.lexer.matchID() || this.lexer.matchTokenType(TokenType.STR)) {
prop = this.lexer.lex();
Expand Down Expand Up @@ -653,14 +661,7 @@ export class JsonTemplateParser {
}

private parseLiteralExpr(): LiteralExpression {
return this.createLiteralExpr(this.lexer.lex())
}

private parseIntegerExpr(): LiteralExpression | undefined {
const token = this.lexer.scanInteger();
if(token) {
return this.createLiteralExpr(token);
}
return this.createLiteralExpr(this.lexer.lex());
}

private parseIDPath(): string {
Expand Down Expand Up @@ -897,6 +898,51 @@ export class JsonTemplateParser {
};
}

private parseNumber(): LiteralExpression {
let val = this.lexer.value();
if (this.lexer.match('.')) {
val += this.lexer.value();
if (this.lexer.matchINT()) {
val += this.lexer.value();
}
return {
type: SyntaxType.LITERAL,
value: parseFloat(val),
tokenType: TokenType.FLOAT,
};
}
return {
type: SyntaxType.LITERAL,
value: parseInt(val, 10),
tokenType: TokenType.INT,
};
}

private parseFloatingNumber(): LiteralExpression {
const val = this.lexer.value() + this.lexer.value();
return {
type: SyntaxType.LITERAL,
value: parseFloat(val),
tokenType: TokenType.FLOAT,
};
}

private parseKeywordBasedExpr(): Expression {
const token = this.lexer.lookahead();
switch (token.value) {
case Keyword.NEW:
return this.parseFunctionCallExpr();
case Keyword.LAMBDA:
return this.parseLambdaExpr();
case Keyword.ASYNC:
return this.parseAsyncFunctionExpr();
case Keyword.FUNCTION:
return this.parseFunctionExpr();
default:
return this.parseDefinitionExpr();
}
}

private parsePrimaryExpr(): Expression {
if (this.lexer.match(';')) {
return EMPTY_EXPR;
Expand All @@ -909,24 +955,16 @@ export class JsonTemplateParser {
};
}

if (this.lexer.matchNew()) {
return this.parseFunctionCallExpr();
if (this.lexer.matchKeyword()) {
return this.parseKeywordBasedExpr();
}

if (this.lexer.matchDefinition()) {
return this.parseDefinitionExpr();
}

if (this.lexer.matchLambda()) {
return this.parseLambdaExpr();
}

if (this.lexer.matchFunction()) {
return this.parseFunctionExpr();
if (this.lexer.matchINT()) {
return this.parseNumber();
}

if (this.lexer.matchAsync()) {
return this.parseAsyncFunctionExpr();
if (this.lexer.match('.') && this.lexer.matchINT(1) && !this.lexer.match('.', 2)) {
return this.parseFloatingNumber();
}

if (this.lexer.matchLiteral()) {
Expand Down
1 change: 1 addition & 0 deletions src/translator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,7 @@ export class JsonTemplateTranslator {
} else {
code.push(this.translateRangeFilterExpr(filter as RangeFilterExpression, dest, dest));
}
code.push(`if(!${dest}){continue;}`);
}
return code.join('');
}
Expand Down
2 changes: 1 addition & 1 deletion test/scenarios/arrays/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import { Sceanario } from '../../types';

export const data: Sceanario[] = [
{
output: [1, 2, 3, ['string1', 0.2], ['string2', 'string3', 'aa"a', true, false], 2],
output: [1, 2, 3, ['string1', 20.02], ['string2', 'string3', 'aa"a', true, false], 2],
},
];
2 changes: 1 addition & 1 deletion test/scenarios/arrays/template.jt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
let a = [
"string1", `string2`, 'string3', "aa\"a", true, false,
undefined, null, .2, 0.22, [1, 2, 3], {"b": [1, 2]}
undefined, null, 20.02, .22, [1., 2, 3], {"b": [1, 2]}
];
[...a[-2], a[0,8], a[1:6], a[-1].b[1]]

13 changes: 9 additions & 4 deletions test/scenarios/paths/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,24 @@ export const data: Sceanario[] = [
{
input: [
{
a: { d: 1 },
b: [{ d: 2 }, { d: 3 }],
c: { c: { d: 4 } },
a: { e: 1 },
b: [{ e: 2 }, { e: [3, 2] }],
c: { c: { e: 4 } },
d: [
[1, 2],
[3, 4],
],
},
],
output: [
[3],
'aa',
{
d: 1,
e: 1,
},
4,
3,
4,
1,
],
},
Expand Down
7 changes: 4 additions & 3 deletions test/scenarios/paths/template.jt
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
[
3[],
"aa"[0][][0][][0],
^.d ?? ^.a,
^.c.d ?? .c.c.d,
.b.1.d ?? .b.0.d,
^.e ?? ^.a,
^.c.e ?? .c.c.e,
.b.1.e.0 ?? .b.0.e,
.d.1.1,
^[].length
]

0 comments on commit c4610be

Please sign in to comment.