nswrap/main.go

282 lines
6.4 KiB
Go

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, "<<<NULL>>>", "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("Cannot open config file nswrap.yaml. %s\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)
}
}
}