package main import ( "fmt" "io/ioutil" "log" "os" "os/exec" "regexp" "runtime" "runtime/pprof" "strings" "gopkg.in/yaml.v2" "git.wow.st/gmp/nswrap/ast" "git.wow.st/gmp/nswrap/wrap" ) var Debug = false var Profile = false type conf struct { Positions bool Package string Inputfiles []string 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 } var Config conf func readAST(data []byte) []string { return strings.Split(string(data), "\n") } type treeNode struct { indent int node ast.Node } func convertLinesToNodes(lines []string) []treeNode { nodes := make([]treeNode, len(lines)) var counter int for _, line := range lines { 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, "<<>>", "NullStmt", 1) trimmed := strings.TrimLeft(line, "|\\- `") node := ast.Parse(trimmed) indentLevel := (len(line) - len(trimmed)) / 2 nodes[counter] = treeNode{indentLevel, node} counter++ } nodes = nodes[0:counter] 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 } // Start begins transpiling an input file. func Start() (err error) { for _, in := range Config.Inputfiles { _, err := os.Stat(in) if err != nil { return fmt.Errorf("Input file %s is not found", in) } } // Generate AST cargs := []string{"-xobjective-c", "-Xclang", "-ast-dump", "-fsyntax-only","-fno-color-diagnostics"} 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) } lines := readAST(astPP) // Converting to nodes fmt.Printf("Building nodes\n") if Config.Positions { ast.TrackPositions = true } //NOTE: converting in parallel is slower on my system //nodes := convertLinesToNodesParallel(lines) nodes := convertLinesToNodes(lines) // build tree fmt.Printf("Assembling tree\n") 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) } } } w.Wrap(Config.Classes) 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.Unmarshal(confbytes,&Config); err != nil { fmt.Printf("Cannot decode config file nswrap.yaml. %s\n",err) os.Exit(-1) } if err := Start(); err != nil { fmt.Printf("Error: %v\n", err) os.Exit(-1) } 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) } } }