Skip to content

Commit 6a61aa7

Browse files
committed
Implement codegen for case
Signed-off-by: James Hamlin <jfhamlin@gmail.com>
1 parent b76a8d7 commit 6a61aa7

File tree

22 files changed

+680
-275
lines changed

22 files changed

+680
-275
lines changed

pkg/runtime/codegen.go

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -764,8 +764,6 @@ func (g *Generator) generateSetValue(s lang.IPersistentSet) string {
764764
}
765765

766766
func (g *Generator) generateMultiFn(mf *lang.MultiFn) string {
767-
fmt.Println("Generating MultiFn:", mf.GetName())
768-
769767
// Allocate a variable for the MultiFn
770768
mfVar := g.allocateTempVar()
771769

@@ -986,8 +984,7 @@ func (g *Generator) generateASTNode(node *ast.Node) (res string) {
986984
fmt.Println("Go not yet implemented; returning nil")
987985
return "nil"
988986
case ast.OpCase:
989-
fmt.Println("Case not yet implemented; returning nil")
990-
return "nil"
987+
return g.generateCase(node)
991988
case ast.OpTry:
992989
return g.generateTry(node)
993990
case ast.OpThrow:
@@ -1040,7 +1037,6 @@ func (g *Generator) generateASTNode(node *ast.Node) (res string) {
10401037
case ast.OpNew:
10411038
return g.generateNew(node)
10421039
default:
1043-
fmt.Printf("Generating code for AST node: %T %+v\n", node.Sub, node.Sub)
10441040
panic(fmt.Sprintf("unsupported AST node type %T", node.Sub))
10451041
}
10461042
}
@@ -1135,6 +1131,55 @@ func (g *Generator) generateIf(node *ast.Node) string {
11351131
return resultVar
11361132
}
11371133

1134+
func (g *Generator) generateCase(node *ast.Node) string {
1135+
caseNode := node.Sub.(*ast.CaseNode)
1136+
1137+
testExpr := g.generateASTNode(caseNode.Test)
1138+
resultVar := g.allocateTempVar()
1139+
1140+
g.writef("// case\n")
1141+
g.writef("var %s any\n", resultVar)
1142+
// implement as if-else chain; evaluation of case clauses is order-dependent
1143+
// case tests are evaluated lazily, so we need to generate them in the if conditions
1144+
// moreover, the text expressions may produce multiple statements, so we need to generate them inline
1145+
// therefore we can't use a switch statement or || operator
1146+
// instead we generate a series of if-else statements
1147+
// each test expression is compared to the testExpr using lang.Equals
1148+
// if a test matches, we evaluate the corresponding body and assign to resultVar
1149+
// if no tests match, we evaluate the default body (if any) and assign to resultVar
1150+
// if no default body, panic
1151+
first := true
1152+
for i, node := range caseNode.Nodes {
1153+
caseNodeNode := node.Sub.(*ast.CaseNodeNode)
1154+
tests := caseNodeNode.Tests
1155+
g.writef("// case clause %d\n", i)
1156+
for _, test := range tests {
1157+
caseTestExpr := g.generateASTNode(test)
1158+
if first {
1159+
g.writef("if lang.Equals(%s, %s) {\n", testExpr, caseTestExpr)
1160+
first = false
1161+
} else {
1162+
g.writef("} else if lang.Equals(%s, %s) {\n", testExpr, caseTestExpr)
1163+
}
1164+
// Generate the then body
1165+
thenExpr := g.generateASTNode(caseNodeNode.Then)
1166+
g.writeAssign(resultVar, thenExpr)
1167+
}
1168+
}
1169+
if caseNode.Default != nil {
1170+
g.writef("} else {\n")
1171+
defaultExpr := g.generateASTNode(caseNode.Default)
1172+
g.writeAssign(resultVar, defaultExpr)
1173+
g.writef("}\n")
1174+
} else {
1175+
g.writef("} else {\n")
1176+
g.writef(" panic(fmt.Sprintf(\"no matching case clause: %%v\", %s))\n", testExpr)
1177+
g.writef("}\n")
1178+
}
1179+
1180+
return resultVar
1181+
}
1182+
11381183
// generateLet generates code for a Let node
11391184
func (g *Generator) generateLet(node *ast.Node, isLoop bool) string {
11401185
letNode := node.Sub.(*ast.LetNode)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
(ns codegen.test.case-switch)
2+
3+
(defn case-test [x]
4+
(case x
5+
1 :one
6+
2 :two
7+
3 :three
8+
:other))
9+
10+
(defn case-test-throw [x]
11+
(try
12+
(case x
13+
1 :nope)
14+
(catch go/any e
15+
:caught)))
16+
17+
(defn ^{:expected-output [:two :other :caught]} -main []
18+
[(case-test 2) (case-test 42) (case-test-throw 42)])
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// Code generated by glojure codegen. DO NOT EDIT.
2+
3+
package case_DASH_switch
4+
5+
import (
6+
fmt "fmt"
7+
lang "github.com/glojurelang/glojure/pkg/lang"
8+
runtime "github.com/glojurelang/glojure/pkg/runtime"
9+
reflect "reflect"
10+
)
11+
12+
func init() {
13+
runtime.RegisterNSLoader("codegen/test/case_switch", LoadNS)
14+
}
15+
16+
func checkDerefVar(v *lang.Var) any {
17+
if v.IsMacro() {
18+
panic(lang.NewIllegalArgumentError(fmt.Sprintf("can't take value of macro: %v", v)))
19+
}
20+
return v.Get()
21+
}
22+
23+
func checkArity(args []any, expected int) {
24+
if len(args) != expected {
25+
panic(lang.NewIllegalArgumentError("wrong number of arguments (" + fmt.Sprint(len(args)) + ")"))
26+
}
27+
}
28+
29+
func checkArityGTE(args []any, min int) {
30+
if len(args) < min {
31+
panic(lang.NewIllegalArgumentError("wrong number of arguments (" + fmt.Sprint(len(args)) + ")"))
32+
}
33+
}
34+
35+
// LoadNS initializes the namespace "codegen.test.case-switch"
36+
func LoadNS() {
37+
sym__DASH_main := lang.NewSymbol("-main")
38+
sym_case_DASH_test := lang.NewSymbol("case-test")
39+
sym_case_DASH_test_DASH_throw := lang.NewSymbol("case-test-throw")
40+
sym_codegen_DOT_test_DOT_case_DASH_switch := lang.NewSymbol("codegen.test.case-switch")
41+
sym_glojure_DOT_core := lang.NewSymbol("glojure.core")
42+
sym_str := lang.NewSymbol("str")
43+
sym_x := lang.NewSymbol("x")
44+
kw_arglists := lang.NewKeyword("arglists")
45+
kw_caught := lang.NewKeyword("caught")
46+
kw_column := lang.NewKeyword("column")
47+
kw_end_DASH_column := lang.NewKeyword("end-column")
48+
kw_end_DASH_line := lang.NewKeyword("end-line")
49+
kw_expected_DASH_output := lang.NewKeyword("expected-output")
50+
kw_file := lang.NewKeyword("file")
51+
kw_line := lang.NewKeyword("line")
52+
kw_nope := lang.NewKeyword("nope")
53+
kw_ns := lang.NewKeyword("ns")
54+
kw_one := lang.NewKeyword("one")
55+
kw_other := lang.NewKeyword("other")
56+
kw_rettag := lang.NewKeyword("rettag")
57+
kw_three := lang.NewKeyword("three")
58+
kw_two := lang.NewKeyword("two")
59+
// var codegen.test.case-switch/-main
60+
var_codegen_DOT_test_DOT_case_DASH_switch__DASH_main := lang.InternVarName(sym_codegen_DOT_test_DOT_case_DASH_switch, sym__DASH_main)
61+
// var codegen.test.case-switch/case-test
62+
var_codegen_DOT_test_DOT_case_DASH_switch_case_DASH_test := lang.InternVarName(sym_codegen_DOT_test_DOT_case_DASH_switch, sym_case_DASH_test)
63+
// var codegen.test.case-switch/case-test-throw
64+
var_codegen_DOT_test_DOT_case_DASH_switch_case_DASH_test_DASH_throw := lang.InternVarName(sym_codegen_DOT_test_DOT_case_DASH_switch, sym_case_DASH_test_DASH_throw)
65+
// var glojure.core/str
66+
var_glojure_DOT_core_str := lang.InternVarName(sym_glojure_DOT_core, sym_str)
67+
// reference fmt to avoid unused import error
68+
_ = fmt.Printf
69+
// reference reflect to avoid unused import error
70+
_ = reflect.TypeOf
71+
ns := lang.FindOrCreateNamespace(sym_codegen_DOT_test_DOT_case_DASH_switch)
72+
_ = ns
73+
// case-test
74+
{
75+
tmp0 := sym_case_DASH_test.WithMeta(lang.NewMap(kw_file, "codegen/test/case_switch.glj", kw_line, int(3), kw_column, int(7), kw_end_DASH_line, int(3), kw_end_DASH_column, int(15), kw_arglists, lang.NewList(lang.NewVector(sym_x)), kw_ns, lang.FindOrCreateNamespace(sym_codegen_DOT_test_DOT_case_DASH_switch))).(*lang.Symbol)
76+
var tmp1 lang.FnFunc
77+
tmp1 = lang.NewFnFunc(func(args ...any) any {
78+
checkArity(args, 1)
79+
v2 := args[0]
80+
_ = v2
81+
var tmp3 any
82+
{ // let
83+
// let binding "G__366"
84+
var v4 any = v2
85+
_ = v4
86+
// case
87+
var tmp5 any
88+
// case clause 0
89+
if lang.Equals(v4, int(1)) {
90+
tmp5 = kw_one
91+
// case clause 1
92+
} else if lang.Equals(v4, int(2)) {
93+
tmp5 = kw_two
94+
// case clause 2
95+
} else if lang.Equals(v4, int(3)) {
96+
tmp5 = kw_three
97+
} else {
98+
tmp5 = kw_other
99+
}
100+
tmp3 = tmp5
101+
} // end let
102+
return tmp3
103+
})
104+
tmp1 = tmp1.WithMeta(lang.NewMap(kw_rettag, nil)).(lang.FnFunc)
105+
var_codegen_DOT_test_DOT_case_DASH_switch_case_DASH_test = ns.InternWithValue(tmp0, tmp1, true)
106+
if tmp0.Meta() != nil {
107+
var_codegen_DOT_test_DOT_case_DASH_switch_case_DASH_test.SetMeta(tmp0.Meta().(lang.IPersistentMap))
108+
}
109+
}
110+
// -main
111+
{
112+
tmp0 := sym__DASH_main.WithMeta(lang.NewMap(kw_expected_DASH_output, lang.NewVector(kw_two, kw_other, kw_caught), kw_file, "codegen/test/case_switch.glj", kw_line, int(17), kw_column, int(7), kw_end_DASH_line, int(17), kw_end_DASH_column, int(53), kw_arglists, lang.NewList(lang.NewVector()), kw_ns, lang.FindOrCreateNamespace(sym_codegen_DOT_test_DOT_case_DASH_switch))).(*lang.Symbol)
113+
var tmp1 lang.FnFunc
114+
tmp1 = lang.NewFnFunc(func(args ...any) any {
115+
checkArity(args, 0)
116+
tmp2 := checkDerefVar(var_codegen_DOT_test_DOT_case_DASH_switch_case_DASH_test)
117+
tmp3 := lang.Apply(tmp2, []any{int64(2)})
118+
tmp4 := checkDerefVar(var_codegen_DOT_test_DOT_case_DASH_switch_case_DASH_test)
119+
tmp5 := lang.Apply(tmp4, []any{int64(42)})
120+
tmp6 := checkDerefVar(var_codegen_DOT_test_DOT_case_DASH_switch_case_DASH_test_DASH_throw)
121+
tmp7 := lang.Apply(tmp6, []any{int64(42)})
122+
tmp8 := lang.NewVector(tmp3, tmp5, tmp7)
123+
tmp9 := lang.NewMap(kw_file, "codegen/test/case_switch.glj", kw_line, int(18), kw_column, int(3), kw_end_DASH_line, int(18), kw_end_DASH_column, int(53))
124+
tmp10, err := lang.WithMeta(tmp8, tmp9.(lang.IPersistentMap))
125+
if err != nil {
126+
panic(err)
127+
}
128+
return tmp10
129+
})
130+
tmp1 = tmp1.WithMeta(lang.NewMap(kw_rettag, nil)).(lang.FnFunc)
131+
var_codegen_DOT_test_DOT_case_DASH_switch__DASH_main = ns.InternWithValue(tmp0, tmp1, true)
132+
if tmp0.Meta() != nil {
133+
var_codegen_DOT_test_DOT_case_DASH_switch__DASH_main.SetMeta(tmp0.Meta().(lang.IPersistentMap))
134+
}
135+
}
136+
// case-test-throw
137+
{
138+
tmp0 := sym_case_DASH_test_DASH_throw.WithMeta(lang.NewMap(kw_file, "codegen/test/case_switch.glj", kw_line, int(10), kw_column, int(7), kw_end_DASH_line, int(10), kw_end_DASH_column, int(21), kw_arglists, lang.NewList(lang.NewVector(sym_x)), kw_ns, lang.FindOrCreateNamespace(sym_codegen_DOT_test_DOT_case_DASH_switch))).(*lang.Symbol)
139+
var tmp1 lang.FnFunc
140+
tmp1 = lang.NewFnFunc(func(args ...any) any {
141+
checkArity(args, 1)
142+
v2 := args[0]
143+
_ = v2
144+
var tmp3 any
145+
func() {
146+
defer func() {
147+
if r := recover(); r != nil {
148+
if lang.CatchMatches(r, lang.Builtins["any"]) {
149+
v4 := r
150+
_ = v4
151+
tmp3 = kw_caught
152+
} else {
153+
panic(r)
154+
}
155+
}
156+
}()
157+
var tmp4 any
158+
{ // let
159+
// let binding "G__367"
160+
var v5 any = v2
161+
_ = v5
162+
// case
163+
var tmp6 any
164+
// case clause 0
165+
if lang.Equals(v5, int(1)) {
166+
tmp6 = kw_nope
167+
} else {
168+
tmp7 := checkDerefVar(var_glojure_DOT_core_str)
169+
tmp8 := lang.Apply(tmp7, []any{"No matching clause: ", v5})
170+
tmp9 := lang.Apply(lang.NewIllegalArgumentError, []any{tmp8})
171+
panic(tmp9)
172+
}
173+
tmp4 = tmp6
174+
} // end let
175+
tmp3 = tmp4
176+
}()
177+
return tmp3
178+
})
179+
tmp1 = tmp1.WithMeta(lang.NewMap(kw_rettag, nil)).(lang.FnFunc)
180+
var_codegen_DOT_test_DOT_case_DASH_switch_case_DASH_test_DASH_throw = ns.InternWithValue(tmp0, tmp1, true)
181+
if tmp0.Meta() != nil {
182+
var_codegen_DOT_test_DOT_case_DASH_switch_case_DASH_test_DASH_throw.SetMeta(tmp0.Meta().(lang.IPersistentMap))
183+
}
184+
}
185+
}

pkg/runtime/testdata/codegen/test/const_keyword/load.go.out

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ func checkArity(args []any, expected int) {
2626
}
2727
}
2828

29+
func checkArityGTE(args []any, min int) {
30+
if len(args) < min {
31+
panic(lang.NewIllegalArgumentError("wrong number of arguments (" + fmt.Sprint(len(args)) + ")"))
32+
}
33+
}
34+
2935
// LoadNS initializes the namespace "codegen.test.const-keyword"
3036
func LoadNS() {
3137
sym_codegen_DOT_test_DOT_const_DASH_keyword := lang.NewSymbol("codegen.test.const-keyword")

pkg/runtime/testdata/codegen/test/const_number/load.go.out

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ func checkArity(args []any, expected int) {
2626
}
2727
}
2828

29+
func checkArityGTE(args []any, min int) {
30+
if len(args) < min {
31+
panic(lang.NewIllegalArgumentError("wrong number of arguments (" + fmt.Sprint(len(args)) + ")"))
32+
}
33+
}
34+
2935
// LoadNS initializes the namespace "codegen.test.const-number"
3036
func LoadNS() {
3137
sym_codegen_DOT_test_DOT_const_DASH_number := lang.NewSymbol("codegen.test.const-number")

pkg/runtime/testdata/codegen/test/const_string/load.go.out

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ func checkArity(args []any, expected int) {
2626
}
2727
}
2828

29+
func checkArityGTE(args []any, min int) {
30+
if len(args) < min {
31+
panic(lang.NewIllegalArgumentError("wrong number of arguments (" + fmt.Sprint(len(args)) + ")"))
32+
}
33+
}
34+
2935
// LoadNS initializes the namespace "codegen.test.const-string"
3036
func LoadNS() {
3137
sym_codegen_DOT_test_DOT_const_DASH_string := lang.NewSymbol("codegen.test.const-string")

0 commit comments

Comments
 (0)