1
1
import type {
2
2
Program ,
3
3
Statement ,
4
+ Comment ,
4
5
If ,
5
6
For ,
6
7
SetStatement ,
@@ -9,9 +10,9 @@ import type {
9
10
MemberExpression ,
10
11
CallExpression ,
11
12
Identifier ,
12
- NumericLiteral ,
13
+ FloatLiteral ,
14
+ IntegerLiteral ,
13
15
StringLiteral ,
14
- BooleanLiteral ,
15
16
ArrayLiteral ,
16
17
TupleLiteral ,
17
18
ObjectLiteral ,
@@ -20,20 +21,33 @@ import type {
20
21
SelectExpression ,
21
22
TestExpression ,
22
23
UnaryExpression ,
23
- LogicalNegationExpression ,
24
24
SliceExpression ,
25
25
KeywordArgumentExpression ,
26
+ CallStatement ,
27
+ FilterStatement ,
28
+ SpreadExpression ,
29
+ Ternary ,
26
30
} from "./ast" ;
27
31
28
32
const NEWLINE = "\n" ;
29
33
const OPEN_STATEMENT = "{%- " ;
30
34
const CLOSE_STATEMENT = " -%}" ;
31
35
32
- const OPERATOR_PRECEDENCE : Record < string , number > = {
33
- MultiplicativeBinaryOperator : 2 ,
34
- AdditiveBinaryOperator : 1 ,
35
- ComparisonBinaryOperator : 0 ,
36
- } ;
36
+ function getBinaryOperatorPrecedence ( expr : BinaryExpression ) : number {
37
+ switch ( expr . operator . type ) {
38
+ case "MultiplicativeBinaryOperator" :
39
+ return 4 ;
40
+ case "AdditiveBinaryOperator" :
41
+ return 3 ;
42
+ case "ComparisonBinaryOperator" :
43
+ return 2 ;
44
+ case "Identifier" :
45
+ if ( expr . operator . value === "and" ) return 1 ;
46
+ if ( expr . operator . value === "in" || expr . operator . value === "not in" ) return 2 ;
47
+ return 0 ;
48
+ }
49
+ return 0 ;
50
+ }
37
51
38
52
export function format ( program : Program , indent : string | number = "\t" ) : string {
39
53
const indentStr = typeof indent === "number" ? " " . repeat ( indent ) : indent ;
@@ -66,6 +80,12 @@ function formatStatement(node: Statement, depth: number, indentStr: string): str
66
80
return pad + createStatement ( "break" ) ;
67
81
case "Continue" :
68
82
return pad + createStatement ( "continue" ) ;
83
+ case "CallStatement" :
84
+ return formatCallStatement ( node as CallStatement , depth , indentStr ) ;
85
+ case "FilterStatement" :
86
+ return formatFilterStatement ( node as FilterStatement , depth , indentStr ) ;
87
+ case "Comment" :
88
+ return pad + "{# " + ( node as Comment ) . value + " #}" ;
69
89
default :
70
90
return pad + "{{- " + formatExpression ( node as Expression ) + " -}}" ;
71
91
}
@@ -93,7 +113,7 @@ function formatIf(node: If, depth: number, indentStr: string): string {
93
113
formatStatements ( clauses [ 0 ] . body , depth + 1 , indentStr ) ;
94
114
95
115
// ELIF(s)
96
- for ( let i = 1 ; i < clauses . length ; i ++ ) {
116
+ for ( let i = 1 ; i < clauses . length ; ++ i ) {
97
117
out +=
98
118
NEWLINE +
99
119
pad +
@@ -119,7 +139,7 @@ function formatFor(node: For, depth: number, indentStr: string): string {
119
139
if ( node . iterable . type === "SelectExpression" ) {
120
140
// Handle special case: e.g., `for x in [1, 2, 3] if x > 2`
121
141
const n = node . iterable as SelectExpression ;
122
- formattedIterable = `${ formatExpression ( n . iterable ) } if ${ formatExpression ( n . test ) } ` ;
142
+ formattedIterable = `${ formatExpression ( n . lhs ) } if ${ formatExpression ( n . test ) } ` ;
123
143
} else {
124
144
formattedIterable = formatExpression ( node . iterable ) ;
125
145
}
@@ -166,20 +186,46 @@ function formatMacro(node: Macro, depth: number, indentStr: string): string {
166
186
) ;
167
187
}
168
188
189
+ function formatCallStatement ( node : CallStatement , depth : number , indentStr : string ) : string {
190
+ const pad = indentStr . repeat ( depth ) ;
191
+ const params =
192
+ node . callerArgs && node . callerArgs . length > 0 ? `(${ node . callerArgs . map ( formatExpression ) . join ( ", " ) } )` : "" ;
193
+ const callExpr = formatExpression ( node . call ) ;
194
+ let out = pad + createStatement ( `call${ params } ` , callExpr ) + NEWLINE ;
195
+ out += formatStatements ( node . body , depth + 1 , indentStr ) + NEWLINE ;
196
+ out += pad + createStatement ( "endcall" ) ;
197
+ return out ;
198
+ }
199
+
200
+ function formatFilterStatement ( node : FilterStatement , depth : number , indentStr : string ) : string {
201
+ const pad = indentStr . repeat ( depth ) ;
202
+ const spec =
203
+ node . filter . type === "Identifier"
204
+ ? ( node . filter as Identifier ) . value
205
+ : formatExpression ( node . filter as CallExpression ) ;
206
+ let out = pad + createStatement ( "filter" , spec ) + NEWLINE ;
207
+ out += formatStatements ( node . body , depth + 1 , indentStr ) + NEWLINE ;
208
+ out += pad + createStatement ( "endfilter" ) ;
209
+ return out ;
210
+ }
211
+
169
212
function formatExpression ( node : Expression , parentPrec : number = - 1 ) : string {
170
213
switch ( node . type ) {
214
+ case "SpreadExpression" : {
215
+ const n = node as SpreadExpression ;
216
+ return `*${ formatExpression ( n . argument ) } ` ;
217
+ }
171
218
case "Identifier" :
172
219
return ( node as Identifier ) . value ;
173
- case "NullLiteral" :
174
- return "none" ;
175
- case "NumericLiteral" :
176
- case "BooleanLiteral" :
177
- return `${ ( node as NumericLiteral | BooleanLiteral ) . value } ` ;
220
+ case "IntegerLiteral" :
221
+ return `${ ( node as IntegerLiteral ) . value } ` ;
222
+ case "FloatLiteral" :
223
+ return `${ ( node as FloatLiteral ) . value } ` ;
178
224
case "StringLiteral" :
179
225
return JSON . stringify ( ( node as StringLiteral ) . value ) ;
180
226
case "BinaryExpression" : {
181
227
const n = node as BinaryExpression ;
182
- const thisPrecedence = OPERATOR_PRECEDENCE [ n . operator . type ] ?? 0 ;
228
+ const thisPrecedence = getBinaryOperatorPrecedence ( n ) ;
183
229
const left = formatExpression ( n . left , thisPrecedence ) ;
184
230
const right = formatExpression ( n . right , thisPrecedence + 1 ) ;
185
231
const expr = `${ left } ${ n . operator . value } ${ right } ` ;
@@ -190,20 +236,31 @@ function formatExpression(node: Expression, parentPrec: number = -1): string {
190
236
const val = n . operator . value + ( n . operator . value === "not" ? " " : "" ) + formatExpression ( n . argument , Infinity ) ;
191
237
return val ;
192
238
}
193
- case "LogicalNegationExpression" :
194
- return `not ${ formatExpression ( ( node as LogicalNegationExpression ) . argument , Infinity ) } ` ;
195
239
case "CallExpression" : {
196
240
const n = node as CallExpression ;
197
- const args = n . args . map ( ( a ) => formatExpression ( a , - 1 ) ) . join ( ", " ) ;
198
- return `${ formatExpression ( n . callee , - 1 ) } (${ args } )` ;
241
+ const args = n . args . map ( formatExpression ) . join ( ", " ) ;
242
+ return `${ formatExpression ( n . callee ) } (${ args } )` ;
199
243
}
200
244
case "MemberExpression" : {
201
245
const n = node as MemberExpression ;
202
- let obj = formatExpression ( n . object , - 1 ) ;
203
- if ( n . object . type !== "Identifier" ) {
246
+ let obj = formatExpression ( n . object ) ;
247
+ // only wrap if it's not a simple or chained access/call
248
+ if (
249
+ ! [
250
+ "Identifier" ,
251
+ "MemberExpression" ,
252
+ "CallExpression" ,
253
+ "StringLiteral" ,
254
+ "IntegerLiteral" ,
255
+ "FloatLiteral" ,
256
+ "ArrayLiteral" ,
257
+ "TupleLiteral" ,
258
+ "ObjectLiteral" ,
259
+ ] . includes ( n . object . type )
260
+ ) {
204
261
obj = `(${ obj } )` ;
205
262
}
206
- let prop = formatExpression ( n . property , - 1 ) ;
263
+ let prop = formatExpression ( n . property ) ;
207
264
if ( ! n . computed && n . property . type !== "Identifier" ) {
208
265
prop = `(${ prop } )` ;
209
266
}
@@ -213,48 +270,47 @@ function formatExpression(node: Expression, parentPrec: number = -1): string {
213
270
const n = node as FilterExpression ;
214
271
const operand = formatExpression ( n . operand , Infinity ) ;
215
272
if ( n . filter . type === "CallExpression" ) {
216
- return `${ operand } | ${ formatExpression ( n . filter , - 1 ) } ` ;
273
+ return `${ operand } | ${ formatExpression ( n . filter ) } ` ;
217
274
}
218
275
return `${ operand } | ${ ( n . filter as Identifier ) . value } ` ;
219
276
}
220
277
case "SelectExpression" : {
221
278
const n = node as SelectExpression ;
222
- return `${ formatExpression ( n . iterable , - 1 ) } | select( ${ formatExpression ( n . test , - 1 ) } ) ` ;
279
+ return `${ formatExpression ( n . lhs ) } if ${ formatExpression ( n . test ) } ` ;
223
280
}
224
281
case "TestExpression" : {
225
282
const n = node as TestExpression ;
226
- return `${ formatExpression ( n . operand , - 1 ) } is${ n . negate ? " not" : "" } ${ n . test . value } ` ;
283
+ return `${ formatExpression ( n . operand ) } is${ n . negate ? " not" : "" } ${ n . test . value } ` ;
227
284
}
228
285
case "ArrayLiteral" :
229
286
case "TupleLiteral" : {
230
- const elems = ( ( node as ArrayLiteral | TupleLiteral ) . value as Expression [ ] ) . map ( ( e ) => formatExpression ( e , - 1 ) ) ;
287
+ const elems = ( ( node as ArrayLiteral | TupleLiteral ) . value as Expression [ ] ) . map ( formatExpression ) ;
231
288
const brackets = node . type === "ArrayLiteral" ? "[]" : "()" ;
232
289
return `${ brackets [ 0 ] } ${ elems . join ( ", " ) } ${ brackets [ 1 ] } ` ;
233
290
}
234
291
case "ObjectLiteral" : {
235
292
const entries = Array . from ( ( node as ObjectLiteral ) . value . entries ( ) ) . map (
236
- ( [ k , v ] ) => `${ formatExpression ( k , - 1 ) } : ${ formatExpression ( v , - 1 ) } `
293
+ ( [ k , v ] ) => `${ formatExpression ( k ) } : ${ formatExpression ( v ) } `
237
294
) ;
238
- return `{ ${ entries . join ( ", " ) } }` ;
295
+ return `{${ entries . join ( ", " ) } }` ;
239
296
}
240
297
case "SliceExpression" : {
241
298
const n = node as SliceExpression ;
242
- const s = n . start ? formatExpression ( n . start , - 1 ) : "" ;
243
- const t = n . stop ? formatExpression ( n . stop , - 1 ) : "" ;
244
- const st = n . step ? `:${ formatExpression ( n . step , - 1 ) } ` : "" ;
299
+ const s = n . start ? formatExpression ( n . start ) : "" ;
300
+ const t = n . stop ? formatExpression ( n . stop ) : "" ;
301
+ const st = n . step ? `:${ formatExpression ( n . step ) } ` : "" ;
245
302
return `${ s } :${ t } ${ st } ` ;
246
303
}
247
304
case "KeywordArgumentExpression" : {
248
305
const n = node as KeywordArgumentExpression ;
249
- return `${ n . key . value } =${ formatExpression ( n . value , - 1 ) } ` ;
306
+ return `${ n . key . value } =${ formatExpression ( n . value ) } ` ;
250
307
}
251
- case "If" : {
252
- // Special case for ternary operator (If as an expression, not a statement)
253
- const n = node as If ;
254
- const test = formatExpression ( n . test , - 1 ) ;
255
- const body = formatExpression ( n . body [ 0 ] , 0 ) ; // Ternary operators have a single body and alternate
256
- const alternate = formatExpression ( n . alternate [ 0 ] , - 1 ) ;
257
- return `${ body } if ${ test } else ${ alternate } ` ;
308
+ case "Ternary" : {
309
+ const n = node as Ternary ;
310
+ const expr = `${ formatExpression ( n . trueExpr ) } if ${ formatExpression ( n . condition , 0 ) } else ${ formatExpression (
311
+ n . falseExpr
312
+ ) } `;
313
+ return parentPrec > - 1 ? `(${ expr } )` : expr ;
258
314
}
259
315
default :
260
316
throw new Error ( `Unknown expression type: ${ node . type } ` ) ;
0 commit comments