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