main.go raw
1 package main
2
3 import (
4 "fmt"
5 "os"
6 "path/filepath"
7 "unsafe"
8
9 "github.com/ebitengine/purego"
10 )
11
12 func main() {
13 if len(os.Args) < 2 {
14 fmt.Fprintf(os.Stderr, "usage: %s <path-to-syntax.so>\n", os.Args[0])
15 os.Exit(1)
16 }
17 soPath := os.Args[1]
18 if !filepath.IsAbs(soPath) {
19 wd, _ := os.Getwd()
20 soPath = filepath.Join(wd, soPath)
21 }
22
23 lib, err := purego.Dlopen(soPath, purego.RTLD_NOW|purego.RTLD_GLOBAL)
24 if err != nil {
25 fmt.Fprintf(os.Stderr, "dlopen %s: %v\n", soPath, err)
26 os.Exit(1)
27 }
28
29 var parseFile func(uintptr, int32) int32
30 purego.RegisterLibFunc(&parseFile, lib, "moxie_parse_file")
31
32 var parseBytes func(uintptr, int32, uintptr, int32) int32
33 purego.RegisterLibFunc(&parseBytes, lib, "moxie_parse_bytes")
34
35 var filePkgName func(int32, uintptr, int32) int32
36 purego.RegisterLibFunc(&filePkgName, lib, "moxie_file_pkg_name")
37
38 var fileDeclCount func(int32) int32
39 purego.RegisterLibFunc(&fileDeclCount, lib, "moxie_file_decl_count")
40
41 var fileDeclKind func(int32, int32) int32
42 purego.RegisterLibFunc(&fileDeclKind, lib, "moxie_file_decl_kind")
43
44 var fileImportPath func(int32, int32, uintptr, int32) int32
45 purego.RegisterLibFunc(&fileImportPath, lib, "moxie_file_import_path")
46
47 var fileFuncName func(int32, int32, uintptr, int32) int32
48 purego.RegisterLibFunc(&fileFuncName, lib, "moxie_file_func_name")
49
50 var fileFuncHasBody func(int32, int32) int32
51 purego.RegisterLibFunc(&fileFuncHasBody, lib, "moxie_file_func_has_body")
52
53 var fileFuncParamCount func(int32, int32) int32
54 purego.RegisterLibFunc(&fileFuncParamCount, lib, "moxie_file_func_param_count")
55
56 var fileFuncResultCount func(int32, int32) int32
57 purego.RegisterLibFunc(&fileFuncResultCount, lib, "moxie_file_func_result_count")
58
59 var fileFuncBodyStmtCount func(int32, int32) int32
60 purego.RegisterLibFunc(&fileFuncBodyStmtCount, lib, "moxie_file_func_body_stmt_count")
61
62 var fileFuncHasRecv func(int32, int32) int32
63 purego.RegisterLibFunc(&fileFuncHasRecv, lib, "moxie_file_func_has_recv")
64
65 var fileTypeName func(int32, int32, uintptr, int32) int32
66 purego.RegisterLibFunc(&fileTypeName, lib, "moxie_file_type_name")
67
68 var fileTypeIsAlias func(int32, int32) int32
69 purego.RegisterLibFunc(&fileTypeIsAlias, lib, "moxie_file_type_is_alias")
70
71 var fileVarNameCount func(int32, int32) int32
72 purego.RegisterLibFunc(&fileVarNameCount, lib, "moxie_file_var_name_count")
73
74 var fileConstNameCount func(int32, int32) int32
75 purego.RegisterLibFunc(&fileConstNameCount, lib, "moxie_file_const_name_count")
76
77 var fileGoVersion func(int32, uintptr, int32) int32
78 purego.RegisterLibFunc(&fileGoVersion, lib, "moxie_file_go_version")
79
80 var fileFree func(int32)
81 purego.RegisterLibFunc(&fileFree, lib, "moxie_file_free")
82
83 // Helper to read a string from a function that writes to a buffer
84 readStr := func(fn func(int32, uintptr, int32) int32, h int32) string {
85 buf := make([]byte, 256)
86 n := fn(h, uintptr(unsafe.Pointer(&buf[0])), 256)
87 return string(buf[:n])
88 }
89 readStr2 := func(fn func(int32, int32, uintptr, int32) int32, h, idx int32) string {
90 buf := make([]byte, 256)
91 n := fn(h, idx, uintptr(unsafe.Pointer(&buf[0])), 256)
92 return string(buf[:n])
93 }
94
95 pass := 0
96 fail := 0
97 assert := func(name string, cond bool) {
98 if cond {
99 pass++
100 } else {
101 fail++
102 fmt.Fprintf(os.Stderr, "FAIL: %s\n", name)
103 }
104 }
105
106 // Parse inline test source
107 testSrc := []byte(`package testpkg
108
109 import "fmt"
110 import "os"
111
112 const maxSize = 1024
113
114 type Point struct {
115 X, Y int32
116 }
117
118 type Stringer interface {
119 String() string
120 }
121
122 var globalCounter int32
123
124 func add(a, b int32) int32 {
125 return a + b
126 }
127
128 func (p *Point) String() string {
129 return fmt.Sprintf("(%d, %d)", p.X, p.Y)
130 }
131
132 func main() {
133 p := &Point{X: 1, Y: 2}
134 fmt.Println(p.String())
135 os.Exit(0)
136 }
137 `)
138 testName := []byte("testdata.mx")
139 h := parseBytes(
140 uintptr(unsafe.Pointer(&testSrc[0])), int32(len(testSrc)),
141 uintptr(unsafe.Pointer(&testName[0])), int32(len(testName)),
142 )
143 assert("parse returns valid handle", h >= 0)
144
145 if h < 0 {
146 fmt.Fprintf(os.Stderr, "parse failed, aborting\n")
147 os.Exit(1)
148 }
149
150 // Package name
151 pkgName := readStr(filePkgName, h)
152 assert("pkg name is testpkg", pkgName == "testpkg")
153
154 // Declaration count
155 // testdata.mx has: 2 imports, 1 const, 2 types (Point, Stringer), 1 var, 3 funcs (add, String method, main) = 9
156 declCount := fileDeclCount(h)
157 assert(fmt.Sprintf("decl count is 9, got %d", declCount), declCount == 9)
158
159 // Check decl kinds
160 // Decl kinds: 1=import, 2=const, 3=type, 4=var, 5=func
161 expectedKinds := []int32{1, 1, 2, 3, 3, 4, 5, 5, 5}
162 for i, expected := range expectedKinds {
163 kind := fileDeclKind(h, int32(i))
164 assert(fmt.Sprintf("decl[%d] kind=%d, got %d", i, expected, kind), kind == expected)
165 }
166
167 // Import paths (decl 0 and 1)
168 imp0 := readStr2(fileImportPath, h, 0)
169 assert("import[0] path is \"fmt\", got "+imp0, imp0 == "\"fmt\"")
170 imp1 := readStr2(fileImportPath, h, 1)
171 assert("import[1] path is \"os\", got "+imp1, imp1 == "\"os\"")
172
173 // Const name count (decl 2)
174 constCount := fileConstNameCount(h, 2)
175 assert(fmt.Sprintf("const name count=1, got %d", constCount), constCount == 1)
176
177 // Type names (decl 3 and 4)
178 typeName0 := readStr2(fileTypeName, h, 3)
179 assert("type[3] name is Point, got "+typeName0, typeName0 == "Point")
180 typeName1 := readStr2(fileTypeName, h, 4)
181 assert("type[4] name is Stringer, got "+typeName1, typeName1 == "Stringer")
182
183 // Type alias check
184 isAlias := fileTypeIsAlias(h, 3)
185 assert("Point is not alias", isAlias == 0)
186
187 // Var name count (decl 5)
188 varCount := fileVarNameCount(h, 5)
189 assert(fmt.Sprintf("var name count=1, got %d", varCount), varCount == 1)
190
191 // Function names (decl 6, 7, 8)
192 fn0 := readStr2(fileFuncName, h, 6)
193 assert("func[6] name is add, got "+fn0, fn0 == "add")
194 fn1 := readStr2(fileFuncName, h, 7)
195 assert("func[7] name is String, got "+fn1, fn1 == "String")
196 fn2 := readStr2(fileFuncName, h, 8)
197 assert("func[8] name is main, got "+fn2, fn2 == "main")
198
199 // Function properties
200 assert("add has body", fileFuncHasBody(h, 6) == 1)
201 assert("add has 2 params", fileFuncParamCount(h, 6) == 2)
202 assert("add has 1 result", fileFuncResultCount(h, 6) == 1)
203 assert("add body has 1 stmt", fileFuncBodyStmtCount(h, 6) == 1)
204 assert("add has no receiver", fileFuncHasRecv(h, 6) == 0)
205
206 // Method check
207 assert("String has receiver", fileFuncHasRecv(h, 7) == 1)
208 assert("String has 0 params", fileFuncParamCount(h, 7) == 0)
209 assert("String has 1 result", fileFuncResultCount(h, 7) == 1)
210
211 // main
212 assert("main has body", fileFuncHasBody(h, 8) == 1)
213 assert("main has 0 params", fileFuncParamCount(h, 8) == 0)
214 assert("main has 0 results", fileFuncResultCount(h, 8) == 0)
215
216 // Free the handle
217 fileFree(h)
218
219 // Go version (no //go:build in testdata, should be empty)
220 goVer := readStr(fileGoVersion, h)
221 assert("go version is empty", goVer == "")
222
223 fileFree(h)
224
225 fmt.Printf("%d/%d tests passed\n", pass, pass+fail)
226 if fail > 0 {
227 os.Exit(1)
228 }
229 }
230