Skip to content

Commit

Permalink
feat: add reverse translator
Browse files Browse the repository at this point in the history
  • Loading branch information
koladilip committed May 30, 2024
1 parent e7b37fa commit a9e591a
Show file tree
Hide file tree
Showing 17 changed files with 604 additions and 53 deletions.
20 changes: 15 additions & 5 deletions src/engine.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BINDINGS_PARAM_KEY, DATA_PARAM_KEY } from './constants';
import { JsonTemplateLexer } from './lexer';
import { JsonTemplateParser } from './parser';
import { JsonTemplateReverseTranslator } from './reverse_translator';
import { JsonTemplateTranslator } from './translator';
import { EngineOptions, Expression, FlatMappingPaths } from './types';
import { CreateAsyncFunction, convertToObjectMapping } from './utils';
Expand Down Expand Up @@ -36,7 +37,10 @@ export class JsonTemplateEngine {
return translator.translate();
}

static parseMappingPaths(mappings: FlatMappingPaths[], options?: EngineOptions): Expression {
private static parseMappingPaths(
mappings: FlatMappingPaths[],
options?: EngineOptions,
): Expression {
const flatMappingAST = mappings.map((mapping) => ({
input: JsonTemplateEngine.parse(mapping.input, options).statements[0],
output: JsonTemplateEngine.parse(mapping.output, options).statements[0],
Expand All @@ -58,7 +62,10 @@ export class JsonTemplateEngine {
return new JsonTemplateEngine(this.compileAsSync(templateOrExpr, options));
}

static parse(template: string, options?: EngineOptions): Expression {
static parse(template: string | FlatMappingPaths[], options?: EngineOptions): Expression {
if (Array.isArray(template)) {
return this.parseMappingPaths(template, options);
}
const lexer = new JsonTemplateLexer(template);
const parser = new JsonTemplateParser(lexer, options);
return parser.parse();
Expand All @@ -69,14 +76,17 @@ export class JsonTemplateEngine {
options?: EngineOptions,
): string {
let templateExpr = template as Expression;
if (typeof template === 'string') {
if (typeof template === 'string' || Array.isArray(template)) {
templateExpr = this.parse(template, options);
} else if (Array.isArray(template)) {
templateExpr = this.parseMappingPaths(template, options);
}
return this.translateExpression(templateExpr);
}

static reverseTranslate(expr: Expression, options?: EngineOptions): string {
const translator = new JsonTemplateReverseTranslator(options);
return translator.translate(expr);
}

evaluate(data: unknown, bindings: Record<string, unknown> = {}): unknown {
return this.fn(data ?? {}, bindings);
}
Expand Down
2 changes: 1 addition & 1 deletion src/lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export class JsonTemplateLexer {
}

matchPath(): boolean {
return this.matchPathType() || this.matchPathSelector() || this.matchID();
return this.matchPathSelector() || this.matchID();
}

matchSpread(): boolean {
Expand Down
83 changes: 59 additions & 24 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,24 @@ import {
TokenType,
UnaryExpression,
} from './types';
import { convertToStatementsExpr, getLastElement, toArray } from './utils/common';
import {
convertToStatementsExpr,
createBlockExpression,
getLastElement,
toArray,
} from './utils/common';

type PathTypeResult = {
pathType: PathType;
inferredPathType: PathType;
};

export class JsonTemplateParser {
private lexer: JsonTemplateLexer;

private options?: EngineOptions;

private pathTypesStack: PathType[] = [];
private pathTypesStack: PathTypeResult[] = [];

// indicates currently how many loops being parsed
private loopCount = 0;
Expand Down Expand Up @@ -141,7 +151,7 @@ export class JsonTemplateParser {
if (!JsonTemplateParser.isSimplePath(expr as PathExpression)) {
throw new JsonTemplateParserError('Invalid assignment path');
}
path.pathType = PathType.SIMPLE;
path.inferredPathType = PathType.SIMPLE;
return {
type: SyntaxType.ASSIGNMENT_EXPR,
value: this.parseBaseExpr(),
Expand Down Expand Up @@ -266,7 +276,10 @@ export class JsonTemplateParser {
};
}

private parsePathRoot(pathType: PathType, root?: Expression): Expression | string | undefined {
private parsePathRoot(
pathType: PathTypeResult,
root?: Expression,
): Expression | string | undefined {
if (root) {
return root;
}
Expand All @@ -276,7 +289,7 @@ export class JsonTemplateParser {
const nextToken = this.lexer.lookahead();
const tokenReturnValues = {
'^': DATA_PARAM_KEY,
$: pathType === PathType.JSON ? DATA_PARAM_KEY : BINDINGS_PARAM_KEY,
$: pathType.inferredPathType === PathType.JSON ? DATA_PARAM_KEY : BINDINGS_PARAM_KEY,
'@': undefined,
};
if (Object.prototype.hasOwnProperty.call(tokenReturnValues, nextToken.value)) {
Expand All @@ -285,44 +298,60 @@ export class JsonTemplateParser {
}
}

private getCurrentPathType(): PathType | undefined {
private getInferredPathType(): PathTypeResult {
if (this.pathTypesStack.length > 0) {
return this.pathTypesStack[this.pathTypesStack.length - 1];
}
return undefined;
return {
pathType: PathType.UNKNOWN,
inferredPathType: this.options?.defaultPathType || PathType.RICH,
};
}

private createPathResult(pathType: PathType) {
return {
pathType,
inferredPathType: pathType,
};
}

private parsePathType(): PathType {
private parsePathType(): PathTypeResult {
if (this.lexer.matchSimplePath()) {
this.lexer.ignoreTokens(1);
return PathType.SIMPLE;
return this.createPathResult(PathType.SIMPLE);
}
if (this.lexer.matchRichPath()) {
this.lexer.ignoreTokens(1);
return PathType.RICH;
return this.createPathResult(PathType.RICH);
}
if (this.lexer.matchJsonPath()) {
this.lexer.ignoreTokens(1);
return PathType.JSON;
return this.createPathResult(PathType.JSON);
}

return this.getCurrentPathType() ?? this.options?.defaultPathType ?? PathType.RICH;
return this.getInferredPathType();
}

private parsePathTypeExpr(): Expression {
const pathTypeResult = this.parsePathType();
this.pathTypesStack.push(pathTypeResult);
const expr = this.parseBaseExpr();
this.pathTypesStack.pop();
return expr;
}

private parsePath(options?: { root?: Expression }): PathExpression | Expression {
const pathType = this.parsePathType();
this.pathTypesStack.push(pathType);
const pathTypeResult = this.parsePathType();
const expr: PathExpression = {
type: SyntaxType.PATH,
root: this.parsePathRoot(pathType, options?.root),
root: this.parsePathRoot(pathTypeResult, options?.root),
parts: this.parsePathParts(),
pathType,
...pathTypeResult,
};
if (!expr.parts.length) {
expr.pathType = PathType.SIMPLE;
expr.inferredPathType = PathType.SIMPLE;
return expr;
}
this.pathTypesStack.pop();
return JsonTemplateParser.updatePathExpr(expr);
}

Expand Down Expand Up @@ -433,7 +462,7 @@ export class JsonTemplateParser {

private parseObjectFilter(): IndexFilterExpression | ObjectFilterExpression {
let exclude = false;
if (this.lexer.match('~')) {
if (this.lexer.match('~') || this.lexer.match('!')) {
this.lexer.ignoreTokens(1);
exclude = true;
}
Expand Down Expand Up @@ -741,7 +770,7 @@ export class JsonTemplateParser {
return {
type: SyntaxType.UNARY_EXPR,
op: '!',
arg: this.parseInExpr(expr),
arg: createBlockExpression(this.parseInExpr(expr)),
};
}

Expand All @@ -750,11 +779,11 @@ export class JsonTemplateParser {
return {
type: SyntaxType.UNARY_EXPR,
op: '!',
arg: {
arg: createBlockExpression({
type: SyntaxType.COMPARISON_EXPR,
op: Keyword.ANYOF,
args: [expr, this.parseRelationalExpr()],
},
}),
};
}

Expand Down Expand Up @@ -1186,6 +1215,7 @@ export class JsonTemplateParser {
body: convertToStatementsExpr(expr),
params: ['...args'],
async: asyncFn,
lambda: true,
};
}

Expand Down Expand Up @@ -1333,6 +1363,10 @@ export class JsonTemplateParser {
return this.parseArrayExpr();
}

if (this.lexer.matchPathType()) {
return this.parsePathTypeExpr();
}

if (this.lexer.matchPath()) {
return this.parsePath();
}
Expand Down Expand Up @@ -1453,15 +1487,16 @@ export class JsonTemplateParser {
newPathExpr.returnAsArray = lastPart.options?.toArray;
}
newPathExpr.parts = JsonTemplateParser.combinePathOptionParts(newPathExpr.parts);

let expr: Expression = newPathExpr;
if (fnExpr) {
expr = JsonTemplateParser.convertToFunctionCallExpr(fnExpr, newPathExpr);
}
if (shouldConvertAsBlock) {
expr = JsonTemplateParser.convertToBlockExpr(expr);
newPathExpr.pathType = PathType.RICH;
newPathExpr.inferredPathType = PathType.RICH;
} else if (this.isRichPath(newPathExpr)) {
newPathExpr.pathType = PathType.RICH;
newPathExpr.inferredPathType = PathType.RICH;
}
return expr;
}
Expand Down
Loading

0 comments on commit a9e591a

Please sign in to comment.