Skip to content

Commit 93e4bf8

Browse files
committed
Clean up vars, don't save gold output as go files
Signed-off-by: James Hamlin <jfhamlin@gmail.com>
1 parent ae1b27c commit 93e4bf8

File tree

20 files changed

+560
-467
lines changed

20 files changed

+560
-467
lines changed

pkg/codegen/codegen.go

Lines changed: 91 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,20 @@ type liftedValue struct {
4747
varName string
4848
}
4949

50+
type varInfo struct {
51+
ns string
52+
sym string
53+
}
54+
5055
// Generator handles the conversion of AST nodes to Go code
5156
type Generator struct {
5257
originalWriter io.Writer
5358
w io.Writer
5459
varScopes []varScope // stack of variable scopes
5560
recurStack []recurContext // stack of recur contexts for nested loops
5661

57-
imports map[string]string // set of imported packages with their aliases
62+
imports map[string]string // set of imported packages with their aliases
63+
varVariables map[varInfo]string // map of vars to their Go variable names
5864

5965
// Fields for handling closures
6066
liftedValues map[liftedKey]*liftedValue // Dedupe by composite key
@@ -80,6 +86,7 @@ func New(w io.Writer) *Generator {
8086
imports: make(map[string]string),
8187
liftedValues: make(map[liftedKey]*liftedValue),
8288
liftedCounter: 0,
89+
varVariables: make(map[varInfo]string),
8390
}
8491
}
8592

@@ -138,7 +145,29 @@ func (g *Generator) Generate(ns *lang.Namespace) error {
138145

139146
// Now construct the complete init function
140147
var initBuf bytes.Buffer
141-
initBuf.WriteString("func init() {\n")
148+
initBuf.WriteString(fmt.Sprintf("// LoadNS initializes the namespace %q\n", ns.Name().String()))
149+
initBuf.WriteString("func LoadNS() {\n")
150+
initBuf.WriteString(`checkDerefMacro := func (v *lang.Var) {
151+
if v.IsMacro() {
152+
panic(lang.NewIllegalArgumentError(fmt.Sprintf("can't take value of macro: %v", v)))
153+
}
154+
}
155+
_ = checkDerefMacro
156+
`)
157+
158+
// initialize all the vars first
159+
var varNames []string
160+
var inverseVarMap = make(map[string]varInfo)
161+
for vi, varName := range g.varVariables {
162+
varNames = append(varNames, varName)
163+
inverseVarMap[varName] = vi
164+
}
165+
sort.Strings(varNames) // Sort for deterministic output
166+
for _, varName := range varNames {
167+
vi := inverseVarMap[varName]
168+
initBuf.WriteString(fmt.Sprintf("// var %s/%s\n", vi.ns, vi.sym))
169+
initBuf.WriteString(fmt.Sprintf("%s := lang.InternVarName(lang.NewSymbol(%q), lang.NewSymbol(%q))\n", varName, vi.ns, vi.sym))
170+
}
142171

143172
// Generate lifted values at the beginning of init() if any
144173
if len(g.liftedValues) > 0 {
@@ -180,8 +209,8 @@ func (g *Generator) Generate(ns *lang.Namespace) error {
180209
initBuf.WriteString("}\n")
181210

182211
// Prepare the final source
183-
sourceBytes := []byte(g.header()) // Package declaration and imports
184-
sourceBytes = append(sourceBytes, initBuf.Bytes()...) // The complete init function
212+
sourceBytes := []byte(g.header(mungeID(getLastNSPart(ns.Name().String())))) // File header with package and imports
213+
sourceBytes = append(sourceBytes, initBuf.Bytes()...) // The complete init function
185214

186215
// Format the generated code
187216
formatted, err := format.Source(sourceBytes)
@@ -866,18 +895,10 @@ func (g *Generator) generateVarDeref(node *ast.Node) string {
866895
varNamespace := varNode.Var.Namespace()
867896
varSymbol := varNode.Var.Symbol()
868897

869-
// generate code to look up the var in the namespace
870-
nsVar := g.allocateTempVar()
871-
g.writef("%s := lang.FindNamespace(lang.NewSymbol(\"%s\"))\n", nsVar, varNamespace.Name())
872-
// look up the var in the namespace
873-
varId := g.allocateTempVar()
874-
g.writef("%s := %s.FindInternedVar(lang.NewSymbol(\"%s\"))\n", varId, nsVar, varSymbol.Name())
898+
// Look up the var variable
899+
varId := g.allocateVarVariable(varNamespace.Name().String(), varSymbol.String())
875900

876-
// if macro, panic with 'can't take value of macro: %v'
877-
g.writef("if %s.IsMacro() {\n", varId)
878-
g.writef(" panic(lang.NewIllegalArgumentError(fmt.Sprintf(\"can't take value of macro: %%v\", %s)))\n", varId)
879-
g.writef("}\n")
880-
// else, return Get()
901+
g.writef("checkDerefMacro(%s)\n", varId)
881902
resultId := g.allocateTempVar()
882903
g.writef("%s := %s.Get()\n", resultId, varId)
883904

@@ -1416,13 +1437,13 @@ func (g *Generator) addImportWithAlias(pkg string) string {
14161437
return alias
14171438
}
14181439

1419-
func (g *Generator) header() string {
1420-
header := `// Code generated by glojure codegen. DO NOT EDIT.
1440+
func (g *Generator) header(pkgName string) string {
1441+
header := fmt.Sprintf(`// Code generated by glojure codegen. DO NOT EDIT.
14211442
1422-
package generated
1443+
package %s
14231444
14241445
import (
1425-
`
1446+
`, pkgName)
14261447

14271448
// sort the imports by their package name for deterministic output
14281449
keys := make([]string, 0, len(g.imports))
@@ -1456,7 +1477,7 @@ func (g *Generator) writeAssign(varName, rValue string) {
14561477
}
14571478

14581479
////////////////////////////////////////////////////////////////////////////////
1459-
// Variable Scope Management
1480+
// Variable scope management and other helpers
14601481

14611482
// PushVarScope creates a new variable scope
14621483
func (g *Generator) pushVarScope() {
@@ -1587,8 +1608,47 @@ func (g *Generator) allocateTempVar() string {
15871608
return varName
15881609
}
15891610

1611+
var (
1612+
replacements = map[rune]string{
1613+
'!': "_BANG_",
1614+
'?': "_QMARK_",
1615+
'-': "_",
1616+
'+': "_PLUS_",
1617+
'*': "_STAR_",
1618+
'/': "_SLASH_",
1619+
'=': "_EQ_",
1620+
'<': "_LT_",
1621+
'>': "_GT_",
1622+
'&': "_AMP_",
1623+
'%': "_PCT_",
1624+
'$': "_DOLLAR_",
1625+
'^': "_CARET_",
1626+
'~': "_TILDE_",
1627+
'.': "_DOT_",
1628+
':': "_COLON_",
1629+
'@': "_AT_",
1630+
'#': "_HASH_",
1631+
}
1632+
)
1633+
15901634
func mungeID(name string) string {
1591-
return strings.ReplaceAll(name, "-", "__")
1635+
var sb strings.Builder
1636+
for _, ch := range name {
1637+
if repl, ok := replacements[ch]; ok {
1638+
sb.WriteString(repl)
1639+
} else if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_' {
1640+
sb.WriteRune(ch)
1641+
} else {
1642+
// Replace any other non-alphanumeric character with its Unicode code point
1643+
sb.WriteString(fmt.Sprintf("_U%04X_", ch))
1644+
}
1645+
}
1646+
return sb.String()
1647+
}
1648+
1649+
func getLastNSPart(ns string) string {
1650+
parts := strings.Split(ns, ".")
1651+
return parts[len(parts)-1]
15921652
}
15931653

15941654
func (g *Generator) pushRecurContext(loopID *lang.Symbol, bindings []string, useGoto bool) {
@@ -1612,3 +1672,13 @@ func (g *Generator) currentRecurContext() *recurContext {
16121672
}
16131673
return &g.recurStack[len(g.recurStack)-1]
16141674
}
1675+
1676+
func (g *Generator) allocateVarVariable(ns, sym string) string {
1677+
varInfo := varInfo{ns: ns, sym: sym}
1678+
if v, ok := g.varVariables[varInfo]; ok {
1679+
return v
1680+
}
1681+
varName := mungeID(ns) + "_" + mungeID(sym)
1682+
g.varVariables[varInfo] = varName
1683+
return varName
1684+
}

pkg/codegen/codegen_test.go

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,11 @@ func TestCodegen(t *testing.T) {
5656

5757
ns := lang.FindNamespace(lang.NewSymbol(nsName))
5858

59-
generateAndTestNamespace(t, ns, strings.TrimSuffix(testFile, ".glj")+".go")
59+
outputDir := strings.TrimSuffix(testFile, ".glj")
60+
if err := os.MkdirAll(outputDir, 0755); err != nil {
61+
t.Fatalf("failed to create output directory: %v", err)
62+
}
63+
generateAndTestNamespace(t, ns, filepath.Join(outputDir, "load.go.out"))
6064
})
6165
}
6266

@@ -67,7 +71,10 @@ func TestCodegen(t *testing.T) {
6771
t.Fatal("glojure.core namespace not found")
6872
}
6973

70-
goldenFile := "testdata/codegen/test/core.go"
74+
if err := os.MkdirAll("testdata/codegen/test/core", 0755); err != nil {
75+
t.Fatalf("failed to create output directory: %v", err)
76+
}
77+
goldenFile := "testdata/codegen/test/core/load.go.out"
7178
generateAndTestNamespace(t, ns, goldenFile)
7279
})
7380
}
@@ -110,12 +117,28 @@ func generateAndTestNamespace(t *testing.T, ns *lang.Namespace, goldenFile strin
110117
generated, expected)
111118
}
112119

113-
// run go vet on the output file. print any errors from stderr
114-
cmd := exec.Command("go", "vet", "-all", goldenFile)
115-
var stderr bytes.Buffer
116-
cmd.Stderr = &stderr
117-
if err := cmd.Run(); err != nil {
118-
t.Errorf("go vet failed for %s: %v\nStderr:\n%s", goldenFile, err, stderr.String())
120+
{
121+
// Copy golden file to temp directory with .go extension for go vet
122+
tempFile, err := ioutil.TempFile("", "codegen_test_*.go")
123+
if err != nil {
124+
t.Fatalf("failed to create temp file: %v", err)
125+
}
126+
defer os.Remove(tempFile.Name())
127+
128+
if _, err := tempFile.Write(expected); err != nil {
129+
t.Fatalf("failed to write to temp file: %v", err)
130+
}
131+
if err := tempFile.Close(); err != nil {
132+
t.Fatalf("failed to close temp file: %v", err)
133+
}
134+
135+
// run go vet on the temp file with .go extension
136+
cmd := exec.Command("go", "vet", "-all", tempFile.Name())
137+
var stderr bytes.Buffer
138+
cmd.Stderr = &stderr
139+
if err := cmd.Run(); err != nil {
140+
t.Errorf("go vet failed for %s: %v\nStderr:\n%s", goldenFile, err, stderr.String())
141+
}
119142
}
120143

121144
// Check if namespace has -main function with expected output

pkg/codegen/testdata/codegen/test/const_keyword.go renamed to pkg/codegen/testdata/codegen/test/const_keyword/load.go.out

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
// Code generated by glojure codegen. DO NOT EDIT.
22

3-
package generated
3+
package const_keyword
44

55
import (
66
fmt "fmt"
77
lang "github.com/glojurelang/glojure/pkg/lang"
88
)
99

10-
func init() {
10+
// LoadNS initializes the namespace "codegen.test.const-keyword"
11+
func LoadNS() {
12+
checkDerefMacro := func(v *lang.Var) {
13+
if v.IsMacro() {
14+
panic(lang.NewIllegalArgumentError(fmt.Sprintf("can't take value of macro: %v", v)))
15+
}
16+
}
17+
_ = checkDerefMacro
1118
// reference fmt to avoid unused import error
1219
_ = fmt.Printf
1320
ns := lang.FindOrCreateNamespace(lang.NewSymbol("codegen.test.const-keyword"))

pkg/codegen/testdata/codegen/test/const_number.go renamed to pkg/codegen/testdata/codegen/test/const_number/load.go.out

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
// Code generated by glojure codegen. DO NOT EDIT.
22

3-
package generated
3+
package const_number
44

55
import (
66
fmt "fmt"
77
lang "github.com/glojurelang/glojure/pkg/lang"
88
)
99

10-
func init() {
10+
// LoadNS initializes the namespace "codegen.test.const-number"
11+
func LoadNS() {
12+
checkDerefMacro := func(v *lang.Var) {
13+
if v.IsMacro() {
14+
panic(lang.NewIllegalArgumentError(fmt.Sprintf("can't take value of macro: %v", v)))
15+
}
16+
}
17+
_ = checkDerefMacro
1118
// reference fmt to avoid unused import error
1219
_ = fmt.Printf
1320
ns := lang.FindOrCreateNamespace(lang.NewSymbol("codegen.test.const-number"))

pkg/codegen/testdata/codegen/test/const_string.go renamed to pkg/codegen/testdata/codegen/test/const_string/load.go.out

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
// Code generated by glojure codegen. DO NOT EDIT.
22

3-
package generated
3+
package const_string
44

55
import (
66
fmt "fmt"
77
lang "github.com/glojurelang/glojure/pkg/lang"
88
)
99

10-
func init() {
10+
// LoadNS initializes the namespace "codegen.test.const-string"
11+
func LoadNS() {
12+
checkDerefMacro := func(v *lang.Var) {
13+
if v.IsMacro() {
14+
panic(lang.NewIllegalArgumentError(fmt.Sprintf("can't take value of macro: %v", v)))
15+
}
16+
}
17+
_ = checkDerefMacro
1118
// reference fmt to avoid unused import error
1219
_ = fmt.Printf
1320
ns := lang.FindOrCreateNamespace(lang.NewSymbol("codegen.test.const-string"))

pkg/codegen/testdata/codegen/test/def_simple.go renamed to pkg/codegen/testdata/codegen/test/def_simple/load.go.out

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
// Code generated by glojure codegen. DO NOT EDIT.
22

3-
package generated
3+
package def_simple
44

55
import (
66
fmt "fmt"
77
lang "github.com/glojurelang/glojure/pkg/lang"
88
)
99

10-
func init() {
10+
// LoadNS initializes the namespace "codegen.test.def-simple"
11+
func LoadNS() {
12+
checkDerefMacro := func(v *lang.Var) {
13+
if v.IsMacro() {
14+
panic(lang.NewIllegalArgumentError(fmt.Sprintf("can't take value of macro: %v", v)))
15+
}
16+
}
17+
_ = checkDerefMacro
1118
// reference fmt to avoid unused import error
1219
_ = fmt.Printf
1320
ns := lang.FindOrCreateNamespace(lang.NewSymbol("codegen.test.def-simple"))

0 commit comments

Comments
 (0)