main.go raw

   1  package main
   2  
   3  import (
   4  	"bytes"
   5  	"flag"
   6  	"fmt"
   7  	"go/parser"
   8  	"go/printer"
   9  	"go/token"
  10  	"os"
  11  	"path/filepath"
  12  	"strings"
  13  )
  14  
  15  func main() {
  16  	outDir := flag.String("o", "./mx-out", "output directory")
  17  	flag.Parse()
  18  
  19  	args := flag.Args()
  20  	if len(args) == 0 {
  21  		fmt.Fprintf(os.Stderr, "usage: mx-transpile [-o outdir] ./path/to/package/\n")
  22  		os.Exit(1)
  23  	}
  24  
  25  	srcDir := args[0]
  26  	entries, err := os.ReadDir(srcDir)
  27  	if err != nil {
  28  		fmt.Fprintf(os.Stderr, "error: %v\n", err)
  29  		os.Exit(1)
  30  	}
  31  
  32  	var goFiles []string
  33  	for _, e := range entries {
  34  		name := e.Name()
  35  		if !e.IsDir() && strings.HasSuffix(name, ".go") && !strings.HasSuffix(name, "_test.go") {
  36  			goFiles = append(goFiles, name)
  37  		}
  38  	}
  39  	if len(goFiles) == 0 {
  40  		fmt.Fprintf(os.Stderr, "no .go files in %s\n", srcDir)
  41  		os.Exit(1)
  42  	}
  43  
  44  	os.MkdirAll(*outDir, 0755)
  45  
  46  	var processed, todos, failed int
  47  
  48  	for _, name := range goFiles {
  49  		fset := token.NewFileSet()
  50  		f, err := parser.ParseFile(fset, filepath.Join(srcDir, name), nil, parser.ParseComments)
  51  		if err != nil {
  52  			fmt.Fprintf(os.Stderr, "parse error: %s: %v\n", name, err)
  53  			failed++
  54  			continue
  55  		}
  56  
  57  		n := transformAST(fset, f)
  58  
  59  		var buf bytes.Buffer
  60  		printer.Fprint(&buf, fset, f)
  61  
  62  		out, m := postProcess(buf.String())
  63  		n += m
  64  
  65  		mxName := strings.TrimSuffix(name, ".go") + ".mx"
  66  		outPath := filepath.Join(*outDir, mxName)
  67  
  68  		header := "// transpiled from Go by mx-transpile - review TODOs before use\n\n"
  69  		if err := os.WriteFile(outPath, []byte(header+out), 0644); err != nil {
  70  			fmt.Fprintf(os.Stderr, "write error: %s: %v\n", outPath, err)
  71  			failed++
  72  			continue
  73  		}
  74  
  75  		processed++
  76  		todos += n
  77  		fmt.Printf("  %s -> %s", name, mxName)
  78  		if n > 0 {
  79  			fmt.Printf(" (%d TODOs)", n)
  80  		}
  81  		fmt.Println()
  82  	}
  83  
  84  	fmt.Printf("\n%d files processed, %d TODOs generated, %d files failed\n",
  85  		processed, todos, failed)
  86  }
  87  
  88  func postProcess(src string) (string, int) {
  89  	todos := 0
  90  	lines := strings.Split(src, "\n")
  91  	var out []string
  92  
  93  	// remove empty import blocks
  94  	for i := 0; i < len(lines); i++ {
  95  		t := strings.TrimSpace(lines[i])
  96  		if t == "import ()" || t == "import (\n)" {
  97  			continue
  98  		}
  99  		if t == "import (" && i+1 < len(lines) && strings.TrimSpace(lines[i+1]) == ")" {
 100  			i++
 101  			continue
 102  		}
 103  		out = append(out, lines[i])
 104  	}
 105  	lines = out
 106  	out = nil
 107  
 108  	for _, line := range lines {
 109  		trimmed := strings.TrimLeft(line, " \t")
 110  
 111  		// strip build tags
 112  		if strings.HasPrefix(trimmed, "//go:build ") || strings.HasPrefix(trimmed, "// +build ") {
 113  			continue
 114  		}
 115  
 116  		// go -> spawn
 117  		if strings.HasPrefix(trimmed, "go ") && !strings.HasPrefix(trimmed, "goto ") {
 118  			idx := strings.Index(line, "go ")
 119  			line = line[:idx] + "spawn " + line[idx+3:]
 120  		}
 121  
 122  		// comment out reflect references
 123  		if strings.Contains(line, "reflect.") && !strings.HasPrefix(trimmed, "//") {
 124  			indent := line[:len(line)-len(trimmed)]
 125  			out = append(out, indent+"// TODO: reflect removed: "+trimmed)
 126  			todos++
 127  			continue
 128  		}
 129  
 130  		// fix trailing commas from zero-position type nodes
 131  		line = strings.ReplaceAll(line, ",)", ")")
 132  
 133  		// flag fmt import
 134  		t := strings.TrimSpace(line)
 135  		if t == `"fmt"` || t == `import "fmt"` {
 136  			line = strings.TrimRight(line, " \t") +
 137  				" // TODO: verify fmt compatibility with Moxie"
 138  			todos++
 139  		}
 140  
 141  		// flag unsafe import
 142  		if t == `"unsafe"` || t == `import "unsafe"` {
 143  			line = strings.TrimRight(line, " \t") +
 144  				" // TODO: unsafe needs review for Moxie"
 145  			todos++
 146  		}
 147  
 148  		out = append(out, line)
 149  	}
 150  
 151  	return strings.Join(out, "\n"), todos
 152  }
 153