@@ -18,11 +18,18 @@ type varScope struct {
1818 names map [string ]string // maps Clojure names to Go variable names
1919}
2020
21+ // recurContext represents the context for a loop/recur form
22+ type recurContext struct {
23+ loopID * lang.Symbol // The loop ID to match recur with its loop
24+ bindings []string // Go variable names for loop bindings (in order)
25+ }
26+
2127// Generator handles the conversion of AST nodes to Go code
2228type Generator struct {
2329 originalWriter io.Writer
2430 w io.Writer
25- varScopes []varScope // stack of variable scopes
31+ varScopes []varScope // stack of variable scopes
32+ recurStack []recurContext // stack of recur contexts for nested loops
2633}
2734
2835// New creates a new code generator
@@ -31,6 +38,7 @@ func New(w io.Writer) *Generator {
3138 originalWriter : w ,
3239 w : w ,
3340 varScopes : []varScope {{nextNum : 0 , names : make (map [string ]string )}},
41+ recurStack : []recurContext {},
3442 }
3543}
3644
@@ -259,7 +267,7 @@ func (g *Generator) generateFn(fn *runtime.Fn) string {
259267 g .writef (" }\n " )
260268
261269 // Generate method body
262- g .generateFnMethod (methodNode , "args" , 0 )
270+ g .generateFnMethod (methodNode , "args" )
263271
264272 g .writef ("})\n " )
265273 } else {
@@ -277,7 +285,7 @@ func (g *Generator) generateFn(fn *runtime.Fn) string {
277285 }
278286
279287 g .writef (" case %d:\n " , methodNode .FixedArity )
280- g .generateFnMethod (methodNode , "args" , 1 )
288+ g .generateFnMethod (methodNode , "args" )
281289 }
282290
283291 // Generate default case for variadic method
@@ -287,7 +295,7 @@ func (g *Generator) generateFn(fn *runtime.Fn) string {
287295 g .writef (" if len(args) < %d {\n " , variadicMethodNode .FixedArity )
288296 g .writef (" panic(lang.NewIllegalArgumentError(\" wrong number of arguments (\" + fmt.Sprint(len(args)) + \" )\" ))\n " )
289297 g .writef (" }\n " )
290- g .generateFnMethod (variadicMethodNode , "args" , 1 )
298+ g .generateFnMethod (variadicMethodNode , "args" )
291299 } else {
292300 // No variadic method - error on any other arity
293301 g .writef (" default:\n " )
@@ -311,17 +319,12 @@ func (g *Generator) generateFn(fn *runtime.Fn) string {
311319}
312320
313321// generateFnMethod generates the body of a function method
314- func (g * Generator ) generateFnMethod (methodNode * ast.FnMethodNode , argsVar string , indentLevel int ) {
315- indent := strings .Repeat (" " , indentLevel )
316-
322+ func (g * Generator ) generateFnMethod (methodNode * ast.FnMethodNode , argsVar string ) {
317323 // Push a new scope for the method body
318324 g .pushVarScope ()
319325 defer g .popVarScope ()
320326
321- // TODO: Handle recur with a label when we implement recur
322- // if methodNode.LoopID != nil {
323- // g.writef("%sRecur_%s:\n", indent, mungeID(methodNode.LoopID.Name()))
324- // }
327+ // TODO: Handle recur with a label
325328
326329 // Bind parameters
327330 for i , param := range methodNode .Params {
@@ -330,16 +333,16 @@ func (g *Generator) generateFnMethod(methodNode *ast.FnMethodNode, argsVar strin
330333
331334 if i < methodNode .FixedArity {
332335 // Regular parameter
333- g .writef ("%s %s := %s[%d]\n " , indent , paramVar , argsVar , i )
336+ g .writef ("%s := %s[%d]\n " , paramVar , argsVar , i )
334337 } else {
335338 // Variadic parameter - collect rest args
336- g .writef ("%s %s := lang.NewList(%s[%d:]...)\n " , indent , paramVar , argsVar , methodNode .FixedArity )
339+ g .writef ("%s := lang.NewList(%s[%d:]...)\n " , paramVar , argsVar , methodNode .FixedArity )
337340 }
338341 }
339342
340343 // Generate the body
341344 bodyVar := g .generateASTNode (methodNode .Body )
342- g .writef ("%s return %s\n " , indent , bodyVar )
345+ g .writef ("return %s\n " , bodyVar )
343346}
344347
345348// generateASTNode generates code for an AST node
@@ -354,6 +357,8 @@ func (g *Generator) generateASTNode(node *ast.Node) string {
354357 return g .allocateVar (localNode .Name .Name ())
355358 case ast .OpDo :
356359 return g .generateDo (node )
360+ case ast .OpLet :
361+ return g .generateLet (node , false )
357362 case ast .OpLoop :
358363 return g .generateLet (node , true )
359364 case ast .OpIf :
@@ -451,11 +456,11 @@ func (g *Generator) generateIf(node *ast.Node) string {
451456 testExpr := g .generateASTNode (ifNode .Test )
452457 g .writef ("if lang.IsTruthy(%s) {\n " , testExpr )
453458 thenExpr := g .generateASTNode (ifNode .Then )
454- g .writef ( " %s = %s \n " , resultVar , thenExpr )
459+ g .writeAssign ( resultVar , thenExpr )
455460 g .writef ("} else {\n " )
456461 if ifNode .Else != nil {
457462 elsExpr := g .generateASTNode (ifNode .Else )
458- g .writef ( " %s = %s \n " , resultVar , elsExpr )
463+ g .writeAssign ( resultVar , elsExpr )
459464 } else {
460465 g .writef (" %s = nil\n " , resultVar )
461466 }
@@ -523,6 +528,12 @@ func (g *Generator) generateLet(node *ast.Node, isLoop bool) string {
523528 g .pushVarScope ()
524529 defer g .popVarScope ()
525530
531+ // Collect binding variable names for recur context if this is a loop
532+ var bindingVars []string
533+ if isLoop {
534+ bindingVars = make ([]string , 0 , len (letNode .Bindings ))
535+ }
536+
526537 // Emit bindings directly to g.w
527538 for _ , binding := range letNode .Bindings {
528539 bindingNode := binding .Sub .(* ast.BindingNode )
@@ -534,19 +545,34 @@ func (g *Generator) generateLet(node *ast.Node, isLoop bool) string {
534545
535546 // Generate initialization code
536547 initCode := g .generateASTNode (init )
537- g .writef ("%s := %s\n " , varName , initCode )
548+ g .writef ("var %s any = %s\n " , varName , initCode )
549+
550+ // Collect binding variables for loop
551+ if isLoop {
552+ bindingVars = append (bindingVars , varName )
553+ }
538554 }
539555
540556 resultId := g .allocateVar ("letResult" )
541557 if isLoop {
558+ // Push recur context for this loop
559+ g .recurStack = append (g .recurStack , recurContext {
560+ loopID : letNode .LoopID ,
561+ bindings : bindingVars ,
562+ })
563+ defer func () {
564+ // Pop recur context when done
565+ g .recurStack = g .recurStack [:len (g .recurStack )- 1 ]
566+ }()
567+
542568 g .writef ("var %s any\n " , resultId )
543569 g .writef ("for {\n " )
544570 }
545571
546572 // Return the body expression (r-value)
547573 result := g .generateASTNode (letNode .Body )
548574 if isLoop {
549- g .writef ( " %s = %s \n " , resultId , result )
575+ g .writeAssign ( resultId , result )
550576 g .writef (" break\n " ) // Break out of the loop after the body
551577 g .writef ("}\n " )
552578 return resultId
@@ -558,14 +584,45 @@ func (g *Generator) generateLet(node *ast.Node, isLoop bool) string {
558584func (g * Generator ) generateRecur (node * ast.Node ) string {
559585 recurNode := node .Sub .(* ast.RecurNode )
560586
561- exprs := recurNode .Exprs
562- for _ , expr := range exprs {
563- val , err := noRecurEnv .EvalAST (expr )
564- if err != nil {
565- return nil , err
587+ // Find the matching recur context
588+ var ctx * recurContext
589+ for i := len (g .recurStack ) - 1 ; i >= 0 ; i -- {
590+ if lang .Equals (g .recurStack [i ].loopID , recurNode .LoopID ) {
591+ ctx = & g .recurStack [i ]
592+ break
566593 }
567- vals = append (vals , val )
568594 }
595+
596+ if ctx == nil {
597+ panic (fmt .Sprintf ("recur without matching loop for ID: %v" , recurNode .LoopID ))
598+ }
599+
600+ // Verify the number of recur expressions matches the number of loop bindings
601+ if len (recurNode .Exprs ) != len (ctx .bindings ) {
602+ panic (fmt .Sprintf ("recur expects %d arguments, got %d" , len (ctx .bindings ), len (recurNode .Exprs )))
603+ }
604+
605+ // Generate temporary variables to hold the new values
606+ // This prevents issues with bindings that reference each other
607+ tempVars := make ([]string , len (recurNode .Exprs ))
608+ for i , expr := range recurNode .Exprs {
609+ tempVar := g .allocateVar (fmt .Sprintf ("recurTemp%d" , i ))
610+ tempVars [i ] = tempVar
611+ exprCode := g .generateASTNode (expr )
612+ g .writef ("var %s any = %s\n " , tempVar , exprCode )
613+ }
614+
615+ // Assign the temporary values to the loop bindings
616+ for i , bindingVar := range ctx .bindings {
617+ g .writef ("%s = %s\n " , bindingVar , tempVars [i ])
618+ }
619+
620+ // Continue the loop
621+ g .writef ("continue\n " )
622+
623+ // Return empty string since recur doesn't produce a value
624+ // (control flow never reaches past the continue)
625+ return ""
569626}
570627
571628////////////////////////////////////////////////////////////////////////////////
@@ -594,6 +651,14 @@ func (g *Generator) writef(format string, args ...any) error {
594651 return err
595652}
596653
654+ // writeAssign writes an assignment iff the r-value string is non-empty
655+ func (g * Generator ) writeAssign (varName , rValue string ) {
656+ if rValue == "" {
657+ return
658+ }
659+ g .writef ("%s = %s\n " , varName , rValue )
660+ }
661+
597662////////////////////////////////////////////////////////////////////////////////
598663// Variable Scope Management
599664
0 commit comments