11import type {
22 Program ,
33 Statement ,
4+ Comment ,
45 If ,
56 For ,
67 SetStatement ,
@@ -9,9 +10,9 @@ import type {
910 MemberExpression ,
1011 CallExpression ,
1112 Identifier ,
12- NumericLiteral ,
13+ FloatLiteral ,
14+ IntegerLiteral ,
1315 StringLiteral ,
14- BooleanLiteral ,
1516 ArrayLiteral ,
1617 TupleLiteral ,
1718 ObjectLiteral ,
@@ -20,20 +21,33 @@ import type {
2021 SelectExpression ,
2122 TestExpression ,
2223 UnaryExpression ,
23- LogicalNegationExpression ,
2424 SliceExpression ,
2525 KeywordArgumentExpression ,
26+ CallStatement ,
27+ FilterStatement ,
28+ SpreadExpression ,
29+ Ternary ,
2630} from "./ast" ;
2731
2832const NEWLINE = "\n" ;
2933const OPEN_STATEMENT = "{%- " ;
3034const CLOSE_STATEMENT = " -%}" ;
3135
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+ }
3751
3852export function format ( program : Program , indent : string | number = "\t" ) : string {
3953 const indentStr = typeof indent === "number" ? " " . repeat ( indent ) : indent ;
@@ -66,6 +80,12 @@ function formatStatement(node: Statement, depth: number, indentStr: string): str
6680 return pad + createStatement ( "break" ) ;
6781 case "Continue" :
6882 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 + " #}" ;
6989 default :
7090 return pad + "{{- " + formatExpression ( node as Expression ) + " -}}" ;
7191 }
@@ -93,7 +113,7 @@ function formatIf(node: If, depth: number, indentStr: string): string {
93113 formatStatements ( clauses [ 0 ] . body , depth + 1 , indentStr ) ;
94114
95115 // ELIF(s)
96- for ( let i = 1 ; i < clauses . length ; i ++ ) {
116+ for ( let i = 1 ; i < clauses . length ; ++ i ) {
97117 out +=
98118 NEWLINE +
99119 pad +
@@ -119,7 +139,7 @@ function formatFor(node: For, depth: number, indentStr: string): string {
119139 if ( node . iterable . type === "SelectExpression" ) {
120140 // Handle special case: e.g., `for x in [1, 2, 3] if x > 2`
121141 const n = node . iterable as SelectExpression ;
122- formattedIterable = `${ formatExpression ( n . iterable ) } if ${ formatExpression ( n . test ) } ` ;
142+ formattedIterable = `${ formatExpression ( n . lhs ) } if ${ formatExpression ( n . test ) } ` ;
123143 } else {
124144 formattedIterable = formatExpression ( node . iterable ) ;
125145 }
@@ -166,20 +186,46 @@ function formatMacro(node: Macro, depth: number, indentStr: string): string {
166186 ) ;
167187}
168188
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+
169212function formatExpression ( node : Expression , parentPrec : number = - 1 ) : string {
170213 switch ( node . type ) {
214+ case "SpreadExpression" : {
215+ const n = node as SpreadExpression ;
216+ return `*${ formatExpression ( n . argument ) } ` ;
217+ }
171218 case "Identifier" :
172219 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 } ` ;
178224 case "StringLiteral" :
179225 return JSON . stringify ( ( node as StringLiteral ) . value ) ;
180226 case "BinaryExpression" : {
181227 const n = node as BinaryExpression ;
182- const thisPrecedence = OPERATOR_PRECEDENCE [ n . operator . type ] ?? 0 ;
228+ const thisPrecedence = getBinaryOperatorPrecedence ( n ) ;
183229 const left = formatExpression ( n . left , thisPrecedence ) ;
184230 const right = formatExpression ( n . right , thisPrecedence + 1 ) ;
185231 const expr = `${ left } ${ n . operator . value } ${ right } ` ;
@@ -190,20 +236,31 @@ function formatExpression(node: Expression, parentPrec: number = -1): string {
190236 const val = n . operator . value + ( n . operator . value === "not" ? " " : "" ) + formatExpression ( n . argument , Infinity ) ;
191237 return val ;
192238 }
193- case "LogicalNegationExpression" :
194- return `not ${ formatExpression ( ( node as LogicalNegationExpression ) . argument , Infinity ) } ` ;
195239 case "CallExpression" : {
196240 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 } )` ;
199243 }
200244 case "MemberExpression" : {
201245 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+ ) {
204261 obj = `(${ obj } )` ;
205262 }
206- let prop = formatExpression ( n . property , - 1 ) ;
263+ let prop = formatExpression ( n . property ) ;
207264 if ( ! n . computed && n . property . type !== "Identifier" ) {
208265 prop = `(${ prop } )` ;
209266 }
@@ -213,48 +270,47 @@ function formatExpression(node: Expression, parentPrec: number = -1): string {
213270 const n = node as FilterExpression ;
214271 const operand = formatExpression ( n . operand , Infinity ) ;
215272 if ( n . filter . type === "CallExpression" ) {
216- return `${ operand } | ${ formatExpression ( n . filter , - 1 ) } ` ;
273+ return `${ operand } | ${ formatExpression ( n . filter ) } ` ;
217274 }
218275 return `${ operand } | ${ ( n . filter as Identifier ) . value } ` ;
219276 }
220277 case "SelectExpression" : {
221278 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 ) } ` ;
223280 }
224281 case "TestExpression" : {
225282 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 } ` ;
227284 }
228285 case "ArrayLiteral" :
229286 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 ) ;
231288 const brackets = node . type === "ArrayLiteral" ? "[]" : "()" ;
232289 return `${ brackets [ 0 ] } ${ elems . join ( ", " ) } ${ brackets [ 1 ] } ` ;
233290 }
234291 case "ObjectLiteral" : {
235292 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 ) } `
237294 ) ;
238- return `{ ${ entries . join ( ", " ) } }` ;
295+ return `{${ entries . join ( ", " ) } }` ;
239296 }
240297 case "SliceExpression" : {
241298 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 ) } ` : "" ;
245302 return `${ s } :${ t } ${ st } ` ;
246303 }
247304 case "KeywordArgumentExpression" : {
248305 const n = node as KeywordArgumentExpression ;
249- return `${ n . key . value } =${ formatExpression ( n . value , - 1 ) } ` ;
306+ return `${ n . key . value } =${ formatExpression ( n . value ) } ` ;
250307 }
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 ;
258314 }
259315 default :
260316 throw new Error ( `Unknown expression type: ${ node . type } ` ) ;
0 commit comments