Skip to content

Commit 21b6052

Browse files
committed
codegen for withmeta nodes
Signed-off-by: James Hamlin <jfhamlin@gmail.com>
1 parent 2b6f48a commit 21b6052

4 files changed

Lines changed: 171 additions & 45 deletions

File tree

pkg/codegen/codegen.go

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -148,15 +148,17 @@ func (g *Generator) generateValue(value any) string {
148148
case *runtime.Fn:
149149
return g.generateFn(v)
150150
case *lang.Map:
151-
return g.generateMap(v)
151+
return g.generateMapValue(v)
152152
case *lang.Vector:
153-
return g.generateVector(v)
153+
return g.generateVectorValue(v)
154154
case lang.Keyword:
155155
if ns := v.Namespace(); ns != "" {
156156
return fmt.Sprintf("lang.NewKeyword(\"%s/%s\")", ns, v.Name())
157157
} else {
158158
return fmt.Sprintf("lang.NewKeyword(\"%s\")", v.Name())
159159
}
160+
case *lang.Symbol:
161+
return fmt.Sprintf("lang.NewSymbol(\"%s\")", v.FullName())
160162
case string:
161163
// just return the string as a Go string literal
162164
return fmt.Sprintf("%#v", v)
@@ -185,8 +187,8 @@ func (g *Generator) generateValue(value any) string {
185187
}
186188
}
187189

188-
// generateMap generates Go code for a Clojure map
189-
func (g *Generator) generateMap(m *lang.Map) string {
190+
// generateMapValue generates Go code for a Clojure map
191+
func (g *Generator) generateMapValue(m *lang.Map) string {
190192
var buf bytes.Buffer
191193
buf.WriteString("lang.NewMap(")
192194

@@ -209,8 +211,8 @@ func (g *Generator) generateMap(m *lang.Map) string {
209211
return buf.String()
210212
}
211213

212-
// generateVector generates Go code for a Clojure vector
213-
func (g *Generator) generateVector(v *lang.Vector) string {
214+
// generateVectorValue generates Go code for a Clojure vector
215+
func (g *Generator) generateVectorValue(v *lang.Vector) string {
214216
var buf bytes.Buffer
215217
buf.WriteString("lang.NewVector(")
216218

@@ -340,10 +342,8 @@ func (g *Generator) generateASTNode(node *ast.Node) string {
340342
// OpDef
341343
// OpSetBang
342344
// OpMaybeClass
343-
// OpWithMeta
344345
// OpFn
345346
// OpMap
346-
// OpVector
347347
// OpSet
348348
// OpLetFn
349349
// OpQuote
@@ -361,6 +361,10 @@ func (g *Generator) generateASTNode(node *ast.Node) string {
361361
case ast.OpConst:
362362
constNode := node.Sub.(*ast.ConstNode)
363363
return g.generateValue(constNode.Value)
364+
case ast.OpVector:
365+
return g.generateVector(node)
366+
case ast.OpMap:
367+
return g.generateMap(node)
364368
case ast.OpLocal:
365369
localNode := node.Sub.(*ast.LocalNode)
366370
// Look up the variable in our scope
@@ -381,6 +385,8 @@ func (g *Generator) generateASTNode(node *ast.Node) string {
381385
return g.generateRecur(node)
382386
case ast.OpGoBuiltin:
383387
return g.generateGoBuiltin(node)
388+
case ast.OpWithMeta:
389+
return g.generateWithMeta(node)
384390
default:
385391
fmt.Printf("Generating code for AST node: %T %+v\n", node.Sub, node.Sub)
386392
panic(fmt.Sprintf("unsupported AST node type %T", node.Sub))
@@ -739,6 +745,58 @@ func (g *Generator) generateGoBuiltin(node *ast.Node) string {
739745
return "lang.Builtins[\"" + sym.Name() + "\"]"
740746
}
741747

748+
// generateWithMeta generates code for a WithMeta node
749+
func (g *Generator) generateWithMeta(node *ast.Node) string {
750+
wmNode := node.Sub.(*ast.WithMetaNode)
751+
752+
expr := wmNode.Expr
753+
meta := wmNode.Meta
754+
755+
exprVal := g.generateASTNode(expr)
756+
metaVal := g.generateASTNode(meta)
757+
758+
resultId := g.allocateTempVar()
759+
g.writef("%s, err := lang.WithMeta(%s, %s.(lang.IPersistentMap))\n", resultId, exprVal, metaVal)
760+
g.writef("if err != nil {\n")
761+
g.writef(" panic(err)\n")
762+
g.writef("}\n")
763+
764+
return resultId
765+
}
766+
767+
func (g *Generator) generateVector(node *ast.Node) string {
768+
vectorNode := node.Sub.(*ast.VectorNode)
769+
770+
itemIds := make([]string, len(vectorNode.Items))
771+
for i, item := range vectorNode.Items {
772+
itemId := g.generateASTNode(item)
773+
itemIds[i] = itemId
774+
}
775+
vecId := g.allocateTempVar()
776+
g.writef("%s := lang.NewVector(%s)\n", vecId, strings.Join(itemIds, ", "))
777+
778+
return vecId
779+
}
780+
781+
func (g *Generator) generateMap(node *ast.Node) string {
782+
mapNode := node.Sub.(*ast.MapNode)
783+
784+
keyValIds := make([]string, 2*len(mapNode.Keys))
785+
for i, key := range mapNode.Keys {
786+
keyId := g.generateASTNode(key)
787+
788+
valNode := mapNode.Vals[i]
789+
valId := g.generateASTNode(valNode)
790+
791+
keyValIds[2*i] = keyId // key
792+
keyValIds[2*i+1] = valId // value
793+
}
794+
mapId := g.allocateTempVar()
795+
g.writef("%s := lang.NewMap(%s)\n", mapId, strings.Join(keyValIds, ", "))
796+
797+
return mapId
798+
}
799+
742800
////////////////////////////////////////////////////////////////////////////////
743801

744802
func (g *Generator) addImport(pkg string) {

pkg/codegen/codegen_test.go

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package codegen_test
22

33
import (
44
"bytes"
5-
"flag"
65
"fmt"
76
"io/ioutil"
87
"os"
@@ -19,8 +18,6 @@ import (
1918
"github.com/glojurelang/glojure/pkg/runtime"
2019
)
2120

22-
var updateGolden = flag.Bool("update", false, "update golden files")
23-
2421
func TestCodegen(t *testing.T) {
2522
var testFiles []string
2623
err := filepath.Walk("testdata", func(path string, info os.FileInfo, err error) error {
@@ -59,45 +56,64 @@ func TestCodegen(t *testing.T) {
5956

6057
ns := lang.FindNamespace(lang.NewSymbol(nsName))
6158

62-
// Generate code for the namespace
63-
var buf bytes.Buffer
64-
gen := codegen.New(&buf)
65-
if err := gen.Generate(ns); err != nil {
66-
t.Fatalf("failed to generate code: %v", err)
67-
}
59+
generateAndTestNamespace(t, ns, strings.TrimSuffix(testFile, ".glj")+".go")
60+
})
61+
}
6862

69-
generated := buf.Bytes()
63+
t.Run("glojure.core", func(t *testing.T) {
64+
// Test the core namespace
65+
ns := lang.FindNamespace(lang.NewSymbol("glojure.core"))
66+
if ns == nil {
67+
t.Fatal("glojure.core namespace not found")
68+
}
7069

71-
// Compare with golden file
72-
goldenFile := strings.TrimSuffix(testFile, ".glj") + ".go"
73-
if *updateGolden {
74-
if err := ioutil.WriteFile(goldenFile, generated, 0644); err != nil {
75-
t.Fatal(err)
76-
}
77-
}
70+
goldenFile := "testdata/codegen/test/core.go"
71+
generateAndTestNamespace(t, ns, goldenFile)
72+
})
73+
}
7874

79-
expected, err := ioutil.ReadFile(goldenFile)
80-
if err != nil {
81-
t.Fatalf("failed to read golden file: %v", err)
82-
}
75+
func generateAndTestNamespace(t *testing.T, ns *lang.Namespace, goldenFile string) {
76+
t.Helper()
8377

84-
if !bytes.Equal(generated, expected) {
85-
t.Errorf("generated code does not match golden file.\nGenerated:\n%s\nExpected:\n%s",
86-
generated, expected)
87-
}
78+
// Generate code for the namespace
79+
var buf bytes.Buffer
80+
gen := codegen.New(&buf)
81+
if err := gen.Generate(ns); err != nil {
82+
t.Fatalf("failed to generate code: %v", err)
83+
}
8884

89-
// run go vet on the output file. print any errors from stderr
90-
cmd := exec.Command("go", "vet", "-all", goldenFile)
91-
var stderr bytes.Buffer
92-
cmd.Stderr = &stderr
93-
if err := cmd.Run(); err != nil {
94-
t.Errorf("go vet failed for %s: %v\nStderr:\n%s", goldenFile, err, stderr.String())
95-
}
85+
generated := buf.Bytes()
9686

97-
// Check if namespace has -main function with expected output
98-
testMainFunction(t, ns)
99-
})
87+
updateGolden := os.Getenv("UPDATE_SNAPSHOT") == "1"
88+
if updateGolden {
89+
if err := ioutil.WriteFile(goldenFile, generated, 0644); err != nil {
90+
t.Fatal(err)
91+
}
92+
}
93+
94+
// Compare with golden file
95+
expected, err := ioutil.ReadFile(goldenFile)
96+
if err != nil {
97+
t.Fatalf("failed to read golden file: %v", err)
98+
}
99+
100+
if !bytes.Equal(generated, expected) {
101+
t.Errorf("generated code does not match golden file.\nGenerated:\n%s\nExpected:\n%s",
102+
generated, expected)
103+
}
104+
105+
// run go vet on the output file. print any errors from stderr
106+
cmd := exec.Command("go", "vet", "-all", goldenFile)
107+
var stderr bytes.Buffer
108+
cmd.Stderr = &stderr
109+
if err := cmd.Run(); err != nil {
110+
t.Errorf("go vet failed for %s: %v\nStderr:\n%s", goldenFile, err, stderr.String())
100111
}
112+
113+
// Check if namespace has -main function with expected output
114+
// TODO: consider dropping this; we really just want to ensure
115+
// the interpreter, here, behaves the same as the generated code
116+
testMainFunction(t, ns)
101117
}
102118

103119
// getNamespaceFromFile attempts to extract the namespace declaration from a file
@@ -149,7 +165,7 @@ func testMainFunction(t *testing.T, ns *lang.Namespace) {
149165

150166
expectedOutput := meta.ValAt(lang.NewKeyword("expected-output"))
151167
expectedError := meta.ValAt(lang.NewKeyword("expected-error"))
152-
168+
153169
if expectedOutput == nil && expectedError == nil {
154170
return
155171
}
@@ -166,7 +182,7 @@ func testMainFunction(t *testing.T, ns *lang.Namespace) {
166182
t.Errorf("-main should have panicked with %v, but didn't", expectedError)
167183
}
168184
}()
169-
185+
170186
// Run -main - should panic
171187
mainVar.Invoke()
172188
return
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
(ns codegen.test.with-meta)
2+
3+
(defn ^{:expected-output :bar} -main []
4+
(:foo (meta ^{:foo :bar}[])))

pkg/codegen/testdata/codegen/test/with_meta.go

Lines changed: 48 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)