-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathExpressionCodegen.ts
More file actions
134 lines (119 loc) · 3.97 KB
/
ExpressionCodegen.ts
File metadata and controls
134 lines (119 loc) · 3.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import * as util from './util';
import {Codegen} from '@jsonjoy.com/util/lib/codegen/Codegen';
import {type ExpressionResult, Literal} from './codegen-steps';
import {createEvaluate} from './createEvaluate';
import type {JavaScript} from '@jsonjoy.com/util/lib/codegen';
import {Vars} from './Vars';
import type * as types from './types';
import type {OperatorRegistry} from './OperatorRegistry';
export type ExpressionFn = (vars: types.JsonExpressionExecutionContext['vars']) => unknown;
export interface ExpressionCodegenOptions extends types.JsonExpressionCodegenContext {
expression: types.Expr;
}
/**
* Code generator for JSON expressions using a specified operator registry.
*/
export class ExpressionCodegen {
protected codegen: Codegen<ExpressionFn>;
protected evaluate: ReturnType<typeof createEvaluate>;
protected operatorMap: types.OperatorMap;
public constructor(
private registry: OperatorRegistry,
protected options: ExpressionCodegenOptions
) {
this.operatorMap = registry.asMap();
this.codegen = new Codegen<ExpressionFn>({
args: ['vars'],
epilogue: '',
});
this.evaluate = createEvaluate({
operators: this.operatorMap,
...options
});
}
private linkedOperandDeps: Set<string> = new Set();
private linkOperandDeps = (dependency: unknown, name?: string): string => {
if (name) {
if (this.linkedOperandDeps.has(name)) return name;
this.linkedOperandDeps.add(name);
} else {
name = this.codegen.getRegister();
}
this.codegen.linkDependency(dependency, name);
return name;
};
private operatorConst = (js: JavaScript<unknown>): string => {
return this.codegen.addConstant(js);
};
private subExpression = (expr: types.Expr): ExpressionFn => {
const codegen = new ExpressionCodegen(this.registry, {...this.options, expression: expr});
const fn = codegen.run().compile();
return fn;
};
protected onExpression(expr: types.Expr | unknown): ExpressionResult {
if (expr instanceof Array) {
if (expr.length === 1) return new Literal(expr[0]);
} else return new Literal(expr);
const def = this.operatorMap.get(expr[0]);
if (def) {
const [name, , arity, , codegen, impure] = def;
util.assertArity(name, arity, expr);
const operands = expr.slice(1).map((operand) => this.onExpression(operand));
if (!impure) {
const allLiterals = operands.every((expr) => expr instanceof Literal);
if (allLiterals) {
const result = this.evaluate(expr, {vars: new Vars(undefined)});
return new Literal(result);
}
}
const ctx: types.OperatorCodegenCtx<types.Expression> = {
expr,
operands,
createPattern: this.options.createPattern,
operand: (operand: types.Expression) => this.onExpression(operand),
link: this.linkOperandDeps,
const: this.operatorConst,
subExpression: this.subExpression,
var: (value: string) => this.codegen.var(value),
};
return codegen(ctx);
}
return new Literal(false);
}
public run(): this {
const expr = this.onExpression(this.options.expression);
this.codegen.js(`return ${expr};`);
return this;
}
public generate() {
return this.codegen.generate();
}
public compileRaw(): ExpressionFn {
return this.codegen.compile();
}
public compile(): ExpressionFn {
const fn = this.compileRaw();
return (vars: any) => {
try {
return fn(vars);
} catch (err) {
if (err instanceof Error) throw err;
const error = new Error('Expression evaluation error.');
(<any>error).value = err;
throw error;
}
};
}
/**
* Get the operator registry used by this codegen.
*/
getRegistry(): OperatorRegistry {
return this.registry;
}
/**
* Create a new codegen with a different registry.
*/
withRegistry(registry: OperatorRegistry): ExpressionCodegen {
return new ExpressionCodegen(registry, this.options);
}
}