Skip to content

Commit 10eca5f

Browse files
committed
Replace string builders with structured Go IR for try/spawn/parallel
Introduce lightweight Go IR node types (goIIFE, goDefer, goGoroutine, goIf, goRaw, goUserCode) that represent the structure of generated Go code. The emitGoIR() method handles indentation uniformly. Refactored tryExpr(), spawnExpr(), and parallelExpr() to build IR trees instead of manually assembling strings with hardcoded tab characters. The generated output is structurally identical.
1 parent 674b04c commit 10eca5f

3 files changed

Lines changed: 232 additions & 74 deletions

File tree

compiler/codegen_expr.go

Lines changed: 73 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,6 @@ func (g *codeGen) tryExpr(e *ast.TryExpr) (string, error) {
645645
}
646646

647647
// Build the handler body as Go source in a temporary buffer.
648-
var handlerBuf strings.Builder
649648
handlerCode, cerr := g.captureOutput(func() error {
650649
g.pushScope()
651650
g.declareVar(e.ErrVar)
@@ -680,24 +679,22 @@ func (g *codeGen) tryExpr(e *ast.TryExpr) (string, error) {
680679
return "", cerr
681680
}
682681

683-
// Build the IIFE
684-
handlerBuf.WriteString("func() (r interface{}) {\n")
685-
handlerBuf.WriteString("\t\tdefer func() {\n")
686-
handlerBuf.WriteString("\t\t\tif e := recover(); e != nil {\n")
687-
handlerBuf.WriteString(fmt.Sprintf("\t\t\t\t%s := fmt.Sprint(e)\n", e.ErrVar))
688-
handlerBuf.WriteString(fmt.Sprintf("\t\t\t\t_ = %s\n", e.ErrVar))
689-
// Indent and write handler code
690-
for _, line := range strings.Split(handlerCode, "\n") {
691-
if line != "" {
692-
handlerBuf.WriteString("\t\t\t\t" + strings.TrimLeft(line, "\t") + "\n")
693-
}
682+
// Build the IIFE using Go IR
683+
ir := goIIFE{
684+
ReturnType: "(r interface{})",
685+
Body: []goNode{
686+
goDefer{Body: []goNode{
687+
goIf{Cond: "e := recover(); e != nil", Body: []goNode{
688+
goRaw{Code: fmt.Sprintf("%s := fmt.Sprint(e)", e.ErrVar)},
689+
goRaw{Code: fmt.Sprintf("_ = %s", e.ErrVar)},
690+
goUserCode{Code: handlerCode},
691+
}},
692+
}},
693+
},
694+
Return: exprStr,
694695
}
695-
handlerBuf.WriteString("\t\t\t}\n")
696-
handlerBuf.WriteString("\t\t}()\n")
697-
handlerBuf.WriteString(fmt.Sprintf("\t\treturn %s\n", exprStr))
698-
handlerBuf.WriteString("\t}()")
699696

700-
return handlerBuf.String(), nil
697+
return g.emitGoIR(ir), nil
701698
}
702699

703700
func (g *codeGen) spawnExpr(e *ast.SpawnExpr) (string, error) {
@@ -732,27 +729,24 @@ func (g *codeGen) spawnExpr(e *ast.SpawnExpr) (string, error) {
732729
return "", cerr
733730
}
734731

735-
// Build the IIFE that creates a rugoTask and launches a goroutine
736-
var buf strings.Builder
737-
buf.WriteString("func() interface{} {\n")
738-
buf.WriteString("\t\tt := &rugoTask{done: make(chan struct{})}\n")
739-
buf.WriteString("\t\tgo func() {\n")
740-
buf.WriteString("\t\t\tdefer func() {\n")
741-
buf.WriteString("\t\t\t\tif e := recover(); e != nil {\n")
742-
buf.WriteString("\t\t\t\t\tt.err = fmt.Sprint(e)\n")
743-
buf.WriteString("\t\t\t\t}\n")
744-
buf.WriteString("\t\t\t\tclose(t.done)\n")
745-
buf.WriteString("\t\t\t}()\n")
746-
for _, line := range strings.Split(bodyCode, "\n") {
747-
if line != "" {
748-
buf.WriteString("\t\t\t" + strings.TrimLeft(line, "\t") + "\n")
749-
}
750-
}
751-
buf.WriteString("\t\t}()\n")
752-
buf.WriteString("\t\treturn interface{}(t)\n")
753-
buf.WriteString("\t}()")
754-
755-
return buf.String(), nil
732+
// Build the IIFE using Go IR
733+
ir := goIIFE{
734+
Body: []goNode{
735+
goRaw{Code: "t := &rugoTask{done: make(chan struct{})}"},
736+
goGoroutine{Body: []goNode{
737+
goDefer{Body: []goNode{
738+
goIf{Cond: "e := recover(); e != nil", Body: []goNode{
739+
goRaw{Code: "t.err = fmt.Sprint(e)"},
740+
}},
741+
goRaw{Code: "close(t.done)"},
742+
}},
743+
goUserCode{Code: bodyCode},
744+
}},
745+
},
746+
Return: "interface{}(t)",
747+
}
748+
749+
return g.emitGoIR(ir), nil
756750
}
757751

758752
func (g *codeGen) fnExpr(e *ast.FnExpr) (string, error) {
@@ -895,42 +889,48 @@ func (g *codeGen) parallelExpr(e *ast.ParallelExpr) (string, error) {
895889
}
896890
}
897891

898-
var buf strings.Builder
899-
buf.WriteString("func() interface{} {\n")
900-
buf.WriteString(fmt.Sprintf("\t\t_results := make([]interface{}, %d)\n", n))
901-
buf.WriteString("\t\tvar _wg sync.WaitGroup\n")
902-
buf.WriteString("\t\tvar _parErr string\n")
903-
buf.WriteString("\t\tvar _parOnce sync.Once\n")
904-
buf.WriteString(fmt.Sprintf("\t\t_wg.Add(%d)\n", n))
905-
892+
// Build goroutine nodes for each parallel branch
893+
var goroutines []goNode
906894
for i, sc := range stmts {
907-
buf.WriteString("\t\tgo func() {\n")
908-
buf.WriteString("\t\t\tdefer _wg.Done()\n")
909-
buf.WriteString("\t\t\tdefer func() {\n")
910-
buf.WriteString("\t\t\t\tif e := recover(); e != nil {\n")
911-
buf.WriteString("\t\t\t\t\t_parOnce.Do(func() { _parErr = fmt.Sprint(e) })\n")
912-
buf.WriteString("\t\t\t\t}\n")
913-
buf.WriteString("\t\t\t}()\n")
895+
var bodyNode goNode
914896
if sc.isExpr {
915-
buf.WriteString(fmt.Sprintf("\t\t\t_results[%d] = %s\n", i, sc.code))
897+
bodyNode = goRaw{Code: fmt.Sprintf("_results[%d] = %s", i, sc.code)}
916898
} else {
917-
for _, line := range strings.Split(sc.code, "\n") {
918-
if line != "" {
919-
buf.WriteString("\t\t\t" + strings.TrimLeft(line, "\t") + "\n")
920-
}
921-
}
922-
}
923-
buf.WriteString("\t\t}()\n")
924-
}
925-
926-
buf.WriteString("\t\t_wg.Wait()\n")
927-
buf.WriteString("\t\tif _parErr != \"\" {\n")
928-
buf.WriteString("\t\t\tpanic(_parErr)\n")
929-
buf.WriteString("\t\t}\n")
930-
buf.WriteString("\t\tout := make([]interface{}, len(_results))\n")
931-
buf.WriteString("\t\tcopy(out, _results)\n")
932-
buf.WriteString("\t\treturn interface{}(out)\n")
933-
buf.WriteString("\t}()")
934-
935-
return buf.String(), nil
899+
bodyNode = goUserCode{Code: sc.code}
900+
}
901+
goroutines = append(goroutines, goGoroutine{Body: []goNode{
902+
goRaw{Code: "defer _wg.Done()"},
903+
goDefer{Body: []goNode{
904+
goIf{Cond: "e := recover(); e != nil", Body: []goNode{
905+
goRaw{Code: `_parOnce.Do(func() { _parErr = fmt.Sprint(e) })`},
906+
}},
907+
}},
908+
bodyNode,
909+
}})
910+
}
911+
912+
// Build the IIFE using Go IR
913+
body := []goNode{
914+
goRaw{Code: fmt.Sprintf("_results := make([]interface{}, %d)", n)},
915+
goRaw{Code: "var _wg sync.WaitGroup"},
916+
goRaw{Code: "var _parErr string"},
917+
goRaw{Code: "var _parOnce sync.Once"},
918+
goRaw{Code: fmt.Sprintf("_wg.Add(%d)", n)},
919+
}
920+
body = append(body, goroutines...)
921+
body = append(body,
922+
goRaw{Code: "_wg.Wait()"},
923+
goIf{Cond: `_parErr != ""`, Body: []goNode{
924+
goRaw{Code: "panic(_parErr)"},
925+
}},
926+
goRaw{Code: "out := make([]interface{}, len(_results))"},
927+
goRaw{Code: "copy(out, _results)"},
928+
)
929+
930+
ir := goIIFE{
931+
Body: body,
932+
Return: "interface{}(out)",
933+
}
934+
935+
return g.emitGoIR(ir), nil
936936
}

compiler/goir.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package compiler
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
8+
// Go IR nodes represent the structure of generated Go code fragments.
9+
// They separate "what to generate" from "how to format it", eliminating
10+
// manual string builder code with hardcoded indentation.
11+
12+
// goNode is a piece of Go code that can be emitted with proper indentation.
13+
type goNode interface {
14+
goNode()
15+
}
16+
17+
// goRaw is a literal line of Go code.
18+
type goRaw struct {
19+
Code string
20+
}
21+
22+
func (goRaw) goNode() {}
23+
24+
// goBlock is a sequence of Go nodes emitted in order.
25+
type goBlock struct {
26+
Nodes []goNode
27+
}
28+
29+
func (goBlock) goNode() {}
30+
31+
// goIIFE represents a self-calling function: func() T { ... }()
32+
type goIIFE struct {
33+
ReturnType string // e.g. "interface{}", "(r interface{})"
34+
Body []goNode // statements inside the IIFE
35+
Return string // final return expression (empty = omit)
36+
}
37+
38+
func (goIIFE) goNode() {}
39+
40+
// goDefer represents: defer func() { ... }()
41+
type goDefer struct {
42+
Body []goNode
43+
}
44+
45+
func (goDefer) goNode() {}
46+
47+
// goGoroutine represents: go func() { ... }()
48+
type goGoroutine struct {
49+
Body []goNode
50+
}
51+
52+
func (goGoroutine) goNode() {}
53+
54+
// goIf represents: if cond { ... }
55+
type goIf struct {
56+
Cond string
57+
Body []goNode
58+
}
59+
60+
func (goIf) goNode() {}
61+
62+
// goUserCode is pre-generated Go code from codegen (e.g., handler body).
63+
// Lines are re-indented to match the surrounding context.
64+
type goUserCode struct {
65+
Code string
66+
}
67+
68+
func (goUserCode) goNode() {}
69+
70+
// emitGoIR renders a Go IR node tree into a string suitable for use as
71+
// an expression (e.g., the right side of an assignment). The first line
72+
// has no indentation; subsequent lines are indented relative to g.indent.
73+
func (g *codeGen) emitGoIR(node goNode) string {
74+
var sb strings.Builder
75+
emitNode(&sb, node, g.indent, true)
76+
// Trim trailing newline so caller controls line ending
77+
s := sb.String()
78+
return strings.TrimRight(s, "\n")
79+
}
80+
81+
func emitNode(sb *strings.Builder, node goNode, indent int, firstLine bool) {
82+
switch n := node.(type) {
83+
case goRaw:
84+
if firstLine {
85+
sb.WriteString(n.Code)
86+
} else {
87+
writeIndented(sb, indent, n.Code)
88+
}
89+
sb.WriteByte('\n')
90+
91+
case goBlock:
92+
for i, child := range n.Nodes {
93+
emitNode(sb, child, indent, firstLine && i == 0)
94+
}
95+
96+
case goIIFE:
97+
retType := n.ReturnType
98+
if retType == "" {
99+
retType = "interface{}"
100+
}
101+
opening := fmt.Sprintf("func() %s {", retType)
102+
if firstLine {
103+
sb.WriteString(opening)
104+
} else {
105+
writeIndented(sb, indent, opening)
106+
}
107+
sb.WriteByte('\n')
108+
for _, child := range n.Body {
109+
emitNode(sb, child, indent+1, false)
110+
}
111+
if n.Return != "" {
112+
writeIndented(sb, indent+1, fmt.Sprintf("return %s", n.Return))
113+
sb.WriteByte('\n')
114+
}
115+
writeIndented(sb, indent, "}()")
116+
sb.WriteByte('\n')
117+
118+
case goDefer:
119+
writeIndented(sb, indent, "defer func() {")
120+
sb.WriteByte('\n')
121+
for _, child := range n.Body {
122+
emitNode(sb, child, indent+1, false)
123+
}
124+
writeIndented(sb, indent, "}()")
125+
sb.WriteByte('\n')
126+
127+
case goGoroutine:
128+
writeIndented(sb, indent, "go func() {")
129+
sb.WriteByte('\n')
130+
for _, child := range n.Body {
131+
emitNode(sb, child, indent+1, false)
132+
}
133+
writeIndented(sb, indent, "}()")
134+
sb.WriteByte('\n')
135+
136+
case goIf:
137+
writeIndented(sb, indent, fmt.Sprintf("if %s {", n.Cond))
138+
sb.WriteByte('\n')
139+
for _, child := range n.Body {
140+
emitNode(sb, child, indent+1, false)
141+
}
142+
writeIndented(sb, indent, "}")
143+
sb.WriteByte('\n')
144+
145+
case goUserCode:
146+
for _, line := range strings.Split(n.Code, "\n") {
147+
if line != "" {
148+
writeIndented(sb, indent, strings.TrimLeft(line, "\t"))
149+
sb.WriteByte('\n')
150+
}
151+
}
152+
}
153+
}
154+
155+
func writeIndented(sb *strings.Builder, indent int, code string) {
156+
sb.WriteString(strings.Repeat("\t", indent))
157+
sb.WriteString(code)
158+
}

compiler/source_embed.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ import "embed"
55
// Sources embeds all non-test Go source files and templates needed to
66
// reconstruct the compiler package in an external module cache.
77
//
8-
//go:embed compiler.go codegen.go codegen_expr.go codegen_func.go codegen_runtime.go codegen_scope.go codegen_stmt.go ext.go infer.go types.go visitor.go
8+
//go:embed compiler.go codegen.go codegen_expr.go codegen_func.go codegen_runtime.go codegen_scope.go codegen_stmt.go ext.go goir.go infer.go types.go visitor.go
99
//go:embed templates/runtime_core_pre.go.tmpl templates/runtime_core_post.go.tmpl templates/runtime_spawn.go.tmpl
1010
var Sources embed.FS

0 commit comments

Comments
 (0)