nswrap/main.go

338 lines
7.6 KiB
Go
Raw Normal View History

2019-04-09 11:52:21 -04:00
package main
import (
"fmt"
"io/ioutil"
"log"
2019-04-09 11:52:21 -04:00
"os"
"os/exec"
"regexp"
2019-04-09 11:52:21 -04:00
"runtime"
"runtime/pprof"
2019-04-09 11:52:21 -04:00
"strings"
"gopkg.in/yaml.v2"
2019-05-29 12:57:53 -04:00
"git.wow.st/gmp/nswrap/ast"
"git.wow.st/gmp/nswrap/wrap"
2019-04-09 11:52:21 -04:00
)
var Debug = false
var Profile = false
2019-04-09 11:52:21 -04:00
type conf struct {
Positions bool
Package string
Inputfiles []string
Astfile string
Debugast bool
Classes []string
Functions []string
Enums []string
Delegates map[string]map[string][]string
Subclasses map[string]map[string][]string
Frameworks []string
Imports []string
Sysimports []string
Pragma []string
Vaargs int
//Arc flag for debugging only, builds will break
Arc bool
2019-04-09 11:52:21 -04:00
}
var Config conf
2019-04-09 11:52:21 -04:00
func readAST(data []byte) []string {
return strings.Split(string(data), "\n")
}
type treeNode struct {
indent int
node ast.Node
}
func printLinesWithContext(lines []string,i int) {
b := i - 3
if b < 0 { b = 0 }
var flag string
for x := b; (x < b + 6) && (x < len(lines)); x++ {
if x == i {
flag = "--> "
} else {
flag = " "
}
fmt.Printf("%s%s\n",flag,lines[x])
}
}
2019-04-09 11:52:21 -04:00
func convertLinesToNodes(lines []string) []treeNode {
nodes := make([]treeNode, len(lines))
var counter int
unknowns := 0
for i, line := range lines {
2019-04-09 11:52:21 -04:00
if strings.TrimSpace(line) == "" {
continue
}
// It is tempting to discard null AST nodes, but these may
// have semantic importance: for example, they represent omitted
// for-loop conditions, as in for(;;).
line = strings.Replace(line, "<<<NULL>>>", "NullStmt", 1)
trimmed := strings.TrimLeft(line, "|\\- `")
node := ast.Parse(trimmed)
indentLevel := (len(line) - len(trimmed)) / 2
nodes[counter] = treeNode{indentLevel, node}
counter++
if !Config.Debugast {
continue
}
switch node.(type) {
case *ast.Unknown:
fmt.Printf("Unrecognized node:\n")
printLinesWithContext(lines,i)
fmt.Printf("\n")
unknowns++
if unknowns > 5 {
fmt.Printf("nswrap failed due to unrecognized nodes.\n")
os.Exit(-1)
}
}
2019-04-09 11:52:21 -04:00
}
nodes = nodes[0:counter]
if Config.Debugast && unknowns > 0 {
fmt.Printf("\nswrap failed due to unrecognized nodes.\n")
os.Exit(-1)
}
2019-04-09 11:52:21 -04:00
return nodes
}
func convertLinesToNodesParallel(lines []string) []treeNode {
// function f separate full list on 2 parts and
// then each part can recursive run function f
var f func([]string, int) []treeNode
f = func(lines []string, deep int) []treeNode {
deep = deep - 2
part := len(lines) / 2
var tr1 = make(chan []treeNode)
var tr2 = make(chan []treeNode)
go func(lines []string, deep int) {
if deep <= 0 || len(lines) < deep {
tr1 <- convertLinesToNodes(lines)
return
}
tr1 <- f(lines, deep)
}(lines[0:part], deep)
go func(lines []string, deep int) {
if deep <= 0 || len(lines) < deep {
tr2 <- convertLinesToNodes(lines)
return
}
tr2 <- f(lines, deep)
}(lines[part:], deep)
defer close(tr1)
defer close(tr2)
return append(<-tr1, <-tr2...)
}
// Parameter of deep - can be any, but effective to use
// same amount of CPU
return f(lines, runtime.NumCPU())
}
// buildTree converts an array of nodes, each prefixed with a depth into a tree.
func buildTree(nodes []treeNode, depth int) []ast.Node {
if len(nodes) == 0 {
return []ast.Node{}
}
// Split the list into sections, treat each section as a tree with its own
// root.
sections := [][]treeNode{}
for _, node := range nodes {
if node.indent == depth {
sections = append(sections, []treeNode{node})
} else {
sections[len(sections)-1] = append(sections[len(sections)-1], node)
}
}
results := []ast.Node{}
for _, section := range sections {
slice := []treeNode{}
for _, n := range section {
if n.indent > depth {
slice = append(slice, n)
}
}
children := buildTree(slice, depth+1)
for _, child := range children {
section[0].node.AddChild(child)
}
results = append(results, section[0].node)
}
return results
}
func matches(x string, rs []string) bool {
for _,r := range rs {
if m, _ := regexp.MatchString("^" + r + "$",x); m {
return true
}
}
return false
}
2019-04-09 11:52:21 -04:00
// Start begins transpiling an input file.
func Start() (err error) {
astPP := []byte{}
if Config.Astfile != "" {
fmt.Printf("Reading ast file %s\n",Config.Astfile)
_, err = os.Stat(Config.Astfile)
2019-04-09 11:52:21 -04:00
if err != nil {
return fmt.Errorf("Input AST file %s not found",Config.Astfile)
}
astPP, err = ioutil.ReadFile(Config.Astfile)
if err != nil {
return err
}
} else {
for _, in := range Config.Inputfiles {
_, err = os.Stat(in)
if err != nil {
return fmt.Errorf("Input file %s is not found", in)
}
2019-04-09 11:52:21 -04:00
}
// Generate AST
cargs := []string{"-xobjective-c", "-Xclang", "-ast-dump",
"-fsyntax-only","-fno-color-diagnostics"}
if Config.Arc {
cargs = append(cargs,"-fobjc-arc")
}
cargs = append(cargs,Config.Inputfiles...)
fmt.Printf("Generating AST\n")
astPP, err = exec.Command("clang",cargs...).Output()
if err != nil {
// If clang fails it still prints out the AST, so we have to run it
// again to get the real error.
//errBody, _ := exec.Command("clang", cargs...).CombinedOutput()
var txt string
switch x := err.(type) {
case *exec.ExitError:
txt = string(x.Stderr)
default:
txt = err.Error()
}
fmt.Printf("clang failed:\n%s\n", txt)
os.Exit(-1)
}
2019-04-09 11:52:21 -04:00
}
lines := readAST(astPP)
// Converting to nodes
fmt.Printf("Building nodes\n")
if Config.Positions {
ast.TrackPositions = true
}
if Config.Arc {
wrap.Arc = true
}
//NOTE: converting in parallel is slower on my system
//nodes := convertLinesToNodesParallel(lines)
nodes := convertLinesToNodes(lines)
2019-04-09 11:52:21 -04:00
// build tree
fmt.Printf("Assembling tree\n")
2019-04-09 11:52:21 -04:00
tree := buildTree(nodes, 0)
w := wrap.NewWrapper(Debug)
w.Package = Config.Package
w.Frameworks(Config.Frameworks)
w.Import(Config.Imports)
w.SysImport(Config.Sysimports)
w.Pragma(Config.Pragma)
w.Delegate(Config.Delegates)
w.Subclass(Config.Subclasses)
if Config.Vaargs == 0 {
Config.Vaargs = 16
}
w.Vaargs = Config.Vaargs
for _, u := range tree {
fmt.Printf("--processing translation unit\n")
for _, n := range(u.Children()) {
switch x := n.(type) {
case *ast.ObjCInterfaceDecl:
w.AddInterface(x)
for _,ss := range Config.Subclasses {
if sc,ok := ss["superclass"]; ok {
if matches(x.Name,sc) {
Config.Classes = append(Config.Classes,x.Name)
}
}
}
case *ast.ObjCCategoryDecl:
w.AddCategory(x)
case *ast.TypedefDecl:
w.AddTypedef(x.Name,x.Type)
case *ast.FunctionDecl:
if matches(x.Name,Config.Functions) {
w.AddFunction(x)
}
case *ast.ObjCProtocolDecl:
w.AddProtocol(x)
case *ast.EnumDecl:
w.AddEnum(x,Config.Enums)
}
2019-04-09 11:52:21 -04:00
}
}
w.Wrap(Config.Classes)
2019-04-09 11:52:21 -04:00
return nil
}
func main() {
if Profile {
f1, err := os.Create("cpuprofile.pprof")
if err != nil {
log.Fatal("could not create CPU profile: ", err)
}
defer f1.Close()
if err := pprof.StartCPUProfile(f1); err != nil {
log.Fatal("could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()
}
confbytes, err := ioutil.ReadFile("nswrap.yaml")
if err != nil {
fmt.Printf("%s\n\nFATAL ERROR: Configuration file must be present in directory where nswrap\nis invoked.\n",err)
os.Exit(-1)
}
if err = yaml.UnmarshalStrict(confbytes,&Config); err != nil {
fmt.Printf("Cannot decode config file nswrap.yaml. %s\n",err)
os.Exit(-1)
2019-04-09 11:52:21 -04:00
}
if err := Start(); err != nil {
2019-04-09 11:52:21 -04:00
fmt.Printf("Error: %v\n", err)
os.Exit(-1)
2019-04-09 11:52:21 -04:00
}
if Profile {
f2, err := os.Create("memprofile.pprof")
if err != nil {
log.Fatal("could not create memory profile: ", err)
}
defer f2.Close()
runtime.GC() // get up-to-date statistics
if err := pprof.WriteHeapProfile(f2); err != nil {
log.Fatal("could not write memory profile: ", err)
}
}
2019-04-09 11:52:21 -04:00
}