-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcodegen.ts
More file actions
109 lines (96 loc) · 3.38 KB
/
codegen.ts
File metadata and controls
109 lines (96 loc) · 3.38 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
import * as util from './util';
import {Codegen} from '@jsonjoy.com/codegen/lib/Codegen';
import {type ExpressionResult, Literal} from './codegen-steps';
import {createEvaluate} from './createEvaluate';
import {Vars} from './Vars';
import type {JavaScript} from '@jsonjoy.com/codegen';
import type * as types from './types';
export type JsonExpressionFn = (vars: types.JsonExpressionExecutionContext['vars']) => unknown;
export interface JsonExpressionCodegenOptions extends types.JsonExpressionCodegenContext {
expression: types.Expr;
operators: types.OperatorMap;
}
export class JsonExpressionCodegen {
protected codegen: Codegen<JsonExpressionFn>;
protected evaluate: ReturnType<typeof createEvaluate>;
public constructor(protected options: JsonExpressionCodegenOptions) {
this.codegen = new Codegen<JsonExpressionFn>({
args: ['vars'],
epilogue: '',
});
this.evaluate = createEvaluate({...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): JsonExpressionFn => {
const codegen = new JsonExpressionCodegen({...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.options.operators.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(): JsonExpressionFn {
return this.codegen.compile();
}
public compile(): JsonExpressionFn {
const fn = this.compileRaw();
return (vars) => {
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;
}
};
}
}