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