Skip to content

Commit

Permalink
feat: add support for template strings
Browse files Browse the repository at this point in the history
  • Loading branch information
koladilip committed Aug 21, 2024
1 parent e724061 commit 03092f1
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 3 deletions.
10 changes: 10 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ Give the JSON template engine a try in our [playground](https://transformers-wor

The template consists of multiple statements, with the output being the result of the final statement.


### Variables

```js
Expand All @@ -106,6 +107,15 @@ a + b;

Refer this [example](test/scenarios/assignments/template.jt) for more details.

#### Template Strings

```js
let a = `Input a=${.a}`;
let b = `Input b=${.b}`;
`${a}, ${b}`;
```
Refer this [example](test/scenarios/template_strings/template.jt) for more details.

### Basic Expressions

#### Conditions
Expand Down
6 changes: 5 additions & 1 deletion src/lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ export class JsonTemplateLexer {
return JsonTemplateLexer.isLiteralToken(this.lookahead());
}

matchTemplate(): boolean {
return this.matchTokenType(TokenType.TEMPLATE);
}

matchINT(steps = 0): boolean {
return this.matchTokenType(TokenType.INT, steps);
}
Expand Down Expand Up @@ -463,7 +467,7 @@ export class JsonTemplateLexer {

if (eosFound) {
return {
type: TokenType.STR,
type: orig === '`' ? TokenType.TEMPLATE : TokenType.STR,
value: str,
range: [start, this.idx],
};
Expand Down
40 changes: 40 additions & 0 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
SpreadExpression,
StatementsExpression,
SyntaxType,
TemplateExpression,
ThrowExpression,
Token,
TokenType,
Expand Down Expand Up @@ -973,6 +974,40 @@ export class JsonTemplateParser {
return JsonTemplateParser.createLiteralExpr(this.lexer.lex());
}

private parseTemplateExpr(): TemplateExpression {
const template = this.lexer.value() as string;
let idx = 0;
const parts: Expression[] = [];
while (idx < template.length) {
const start = template.indexOf('${', idx);
if (start === -1) {
parts.push({
type: SyntaxType.LITERAL,
value: template.slice(idx),
tokenType: TokenType.STR,
});
break;
}
const end = template.indexOf('}', start);
if (end === -1) {
throw new JsonTemplateParserError('Invalid template expression');
}
if (start > idx) {
parts.push({
type: SyntaxType.LITERAL,
value: template.slice(idx, start),
tokenType: TokenType.STR,
});
}
parts.push(JsonTemplateEngine.parse(template.slice(start + 2, end), this.options));
idx = end + 1;
}
return {
type: SyntaxType.TEMPLATE_EXPR,
parts,
};
}

private parseIDPath(): string {
const idParts: string[] = [];
while (this.lexer.matchID()) {
Expand Down Expand Up @@ -1380,6 +1415,7 @@ export class JsonTemplateParser {
return JsonTemplateEngine.parseMappingPaths(flatMappings);
}

// eslint-disable-next-line sonarjs/cognitive-complexity
private parsePrimaryExpr(): Expression {
if (this.lexer.match(';')) {
return EMPTY_EXPR;
Expand Down Expand Up @@ -1412,6 +1448,10 @@ export class JsonTemplateParser {
return this.parseLiteralExpr();
}

if (this.lexer.matchTemplate()) {
return this.parseTemplateExpr();
}

if (this.lexer.matchCompileTimeExpr()) {
return this.parseCompileTimeExpr();
}
Expand Down
16 changes: 16 additions & 0 deletions src/reverse_translator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
SpreadExpression,
StatementsExpression,
SyntaxType,
TemplateExpression,
ThrowExpression,
TokenType,
UnaryExpression,
Expand Down Expand Up @@ -69,6 +70,9 @@ export class JsonTemplateReverseTranslator {
return this.translateBinaryExpression(expr as BinaryExpression);
case SyntaxType.ARRAY_EXPR:
return this.translateArrayExpression(expr as ArrayExpression);
case SyntaxType.TEMPLATE_EXPR:
return this.translateTemplateExpression(expr as TemplateExpression);

case SyntaxType.OBJECT_EXPR:
return this.translateObjectExpression(expr as ObjectExpression);
case SyntaxType.SPREAD_EXPR:
Expand Down Expand Up @@ -120,6 +124,18 @@ export class JsonTemplateReverseTranslator {
}
}

translateTemplateExpression(expr: TemplateExpression): string {
const code: string[] = [];
for (const part of expr.parts) {
if (part.type === SyntaxType.LITERAL) {
code.push(part.value);
} else {
code.push(this.translateWithWrapper(part, '${', '}'));
}
}
return `\`${code.join('')}\``;
}

translateArrayFilterExpression(expr: ArrayFilterExpression): string {
return this.translateExpression(expr.filter);
}
Expand Down
16 changes: 16 additions & 0 deletions src/translator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
IncrementExpression,
LoopControlExpression,
ObjectPropExpression,
TemplateExpression,
} from './types';
import { convertToStatementsExpr, escapeStr } from './utils/common';
import { translateLiteral } from './utils/translator';
Expand Down Expand Up @@ -141,6 +142,9 @@ export class JsonTemplateTranslator {
case SyntaxType.LITERAL:
return this.translateLiteralExpr(expr as LiteralExpression, dest, ctx);

case SyntaxType.TEMPLATE_EXPR:
return this.translateTemplateExpr(expr as TemplateExpression, dest, ctx);

case SyntaxType.ARRAY_EXPR:
return this.translateArrayExpr(expr as ArrayExpression, dest, ctx);

Expand Down Expand Up @@ -626,6 +630,18 @@ export class JsonTemplateTranslator {
return JsonTemplateTranslator.generateAssignmentCode(dest, literalCode);
}

private translateTemplateExpr(expr: TemplateExpression, dest: string, ctx: string): string {
const code: string[] = [];
const partVars: string[] = [];
for (const part of expr.parts) {
const partVar = this.acquireVar();
code.push(this.translateExpr(part, partVar, ctx));
partVars.push(partVar);
}
code.push(JsonTemplateTranslator.generateAssignmentCode(dest, partVars.join(' + ')));
return code.join('');
}

private getSimplePathSelector(expr: SelectorExpression, isAssignment: boolean): string {
if (expr.prop?.type === TokenType.STR) {
return `${isAssignment ? '' : '?.'}[${escapeStr(expr.prop?.value)}]`;
Expand Down
6 changes: 4 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export enum TokenType {
ID = 'id',
INT = 'int',
FLOAT = 'float',
TEMPLATE = 'template',
STR = 'str',
BOOL = 'bool',
NULL = 'null',
Expand Down Expand Up @@ -95,6 +96,7 @@ export enum SyntaxType {
STATEMENTS_EXPR = 'statements_expr',
LOOP_CONTROL_EXPR = 'loop_control_expr',
LOOP_EXPR = 'loop_expr',
TEMPLATE_EXPR = 'TEMPLATE_EXPR',
}

export enum PathType {
Expand Down Expand Up @@ -176,8 +178,8 @@ export interface BinaryExpression extends Expression {
op: string;
}

export interface ConcatExpression extends Expression {
args: Expression[];
export interface TemplateExpression extends Expression {
parts: Expression[];
}

export interface AssignmentExpression extends Expression {
Expand Down
17 changes: 17 additions & 0 deletions test/scenarios/template_strings/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Scenario } from '../../types';

export const data: Scenario[] = [
{
input: {
a: 'foo',
},
bindings: {
b: 'bar',
},
output: 'Input a=foo, Binding b=bar',
},
{
template: '`unclosed template ${`',
error: 'Invalid template expression',
},
];
3 changes: 3 additions & 0 deletions test/scenarios/template_strings/template.jt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
let a = `Input a=${.a}`;
let b = `Binding b=${$.b}`;
`${a}, ${b}`;

0 comments on commit 03092f1

Please sign in to comment.