github.com/Konstantin8105/c4go@v0.0.0-20240505174241-768bb1c65a51/transpiler/bind.go (about)

     1  package transpiler
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	goast "go/ast"
     7  	"go/format"
     8  	"go/token"
     9  	"sort"
    10  	"strings"
    11  
    12  	"github.com/Konstantin8105/c4go/program"
    13  	"github.com/Konstantin8105/c4go/types"
    14  	"github.com/Konstantin8105/c4go/util"
    15  )
    16  
    17  func generateBinding(p *program.Program, clangFlags []string) (bindHeader, bindCode string) {
    18  	// outside called functions
    19  	ds := p.GetOutsideCalledFunctions()
    20  	if len(ds) == 0 {
    21  		return
    22  	}
    23  
    24  	sort.Slice(ds, func(i, j int) bool {
    25  		return ds[i].Name < ds[j].Name
    26  	})
    27  
    28  	// add clang flags
    29  	{
    30  		cflags := map[string]bool{}
    31  		ldflags := map[string]bool{}
    32  		for i := range clangFlags {
    33  			if strings.HasPrefix(clangFlags[i], "-I") {
    34  				cflags[clangFlags[i]] = true
    35  			}
    36  			if strings.HasPrefix(clangFlags[i], "-L") || strings.HasPrefix(clangFlags[i], "-l") {
    37  				ldflags[clangFlags[i]] = true
    38  			}
    39  		}
    40  		if 0 < len(cflags) {
    41  			bindHeader += "// #cgo CFLAGS : "
    42  			for k, _ := range cflags {
    43  				bindHeader += k + " "
    44  			}
    45  			bindHeader += "\n"
    46  		}
    47  		if 0 < len(ldflags) {
    48  			bindHeader += "// #cgo LDFLAGS : "
    49  			for k, _ := range ldflags {
    50  				bindHeader += k + " "
    51  			}
    52  			bindHeader += "\n"
    53  		}
    54  	}
    55  
    56  	// automatic binding of function
    57  	{
    58  		in := map[string]bool{}
    59  		for i := range ds {
    60  			y := ds[i].IncludeFile
    61  			in[y] = true
    62  			in[p.PreprocessorFile.GetBaseInclude(y)] = true
    63  		}
    64  		for header := range in {
    65  			if strings.Contains(header, "bits") {
    66  				continue
    67  			}
    68  			bindHeader += fmt.Sprintf("// #include <%s>\n", header)
    69  		}
    70  
    71  		bindHeader += "import \"C\"\n\n"
    72  	}
    73  
    74  	for i := range ds {
    75  		//
    76  		// Example:
    77  		//
    78  		// // #include <stdlib.h>
    79  		// // #include <stdio.h>
    80  		// // #include <errno.h>
    81  		// import "C"
    82  		//
    83  		// func Seed(i int) {
    84  		//   C.srandom(C.uint(i))
    85  		// }
    86  		//
    87  
    88  		// input data:
    89  		// {frexp double [double int *] true true  [] []}
    90  		//
    91  		// output:
    92  		// func  frexp(arg1 float64, arg2 []int) float64 {
    93  		//		return float64(C.frexp(C.double(arg1), unsafe.Pointer(arg2)))
    94  		// }
    95  
    96  		code, err := getBindFunction(p, ds[i])
    97  		if err != nil {
    98  			bindCode += p.GenerateWarningMessage(err, nil) + "\n"
    99  			continue
   100  		}
   101  		index := strings.Index(code, "\n")
   102  		if index < 0 {
   103  			continue
   104  		}
   105  		bindCode += code[index:] + "\n"
   106  	}
   107  
   108  	return
   109  }
   110  
   111  func getBindArgName(pos int) string {
   112  	return fmt.Sprintf("arg%d", pos)
   113  }
   114  
   115  func getBindFunction(p *program.Program, d program.DefinitionFunction) (code string, err error) {
   116  	var f goast.FuncDecl
   117  	f.Name = goast.NewIdent(d.Name)
   118  
   119  	// arguments types
   120  	var ft goast.FuncType
   121  	var fl goast.FieldList
   122  	var argResolvedType []string
   123  	for i := range d.ArgumentTypes {
   124  		if d.ArgumentTypes[i] == "void" {
   125  			continue
   126  		}
   127  		if i == len(d.ArgumentTypes)-1 && d.ArgumentTypes[i] == "..." {
   128  			argResolvedType[len(argResolvedType)-1] =
   129  				"..." + argResolvedType[len(argResolvedType)-1]
   130  			continue
   131  		}
   132  		if strings.TrimSpace(d.ArgumentTypes[i]) == "" {
   133  			continue
   134  		}
   135  		resolveType, err := types.ResolveType(p, d.ArgumentTypes[i])
   136  		if err != nil {
   137  			return "", fmt.Errorf("cannot generate argument binding function `%s`: %v", d.Name, err)
   138  		}
   139  		argResolvedType = append(argResolvedType, resolveType)
   140  	}
   141  	for i := range argResolvedType {
   142  		resolveType := argResolvedType[i]
   143  		fl.List = append(fl.List, &goast.Field{
   144  			Names: []*goast.Ident{goast.NewIdent(getBindArgName(i))},
   145  			Type:  goast.NewIdent(resolveType),
   146  		})
   147  	}
   148  	ft.Params = &fl
   149  	f.Type = &ft
   150  	// return type
   151  	var fr goast.FieldList
   152  	ft.Results = &fr
   153  	var returnResolvedType string
   154  	if d.ReturnType != "" {
   155  		resolveType, err := types.ResolveType(p, d.ReturnType)
   156  		if err != nil {
   157  			return "", fmt.Errorf("cannot generate return type binding function `%s`: %v", d.Name, err)
   158  		}
   159  		fr.List = append(fr.List, &goast.Field{
   160  			Type: goast.NewIdent(resolveType),
   161  		})
   162  		returnResolvedType = resolveType
   163  	}
   164  
   165  	// create body
   166  	var arg []goast.Expr
   167  	for i := range argResolvedType {
   168  		// convert from Go type to Cgo type
   169  		cgoExpr, err := ResolveCgoType(p, argResolvedType[i], goast.NewIdent(getBindArgName(i)))
   170  		if err != nil {
   171  			return "", fmt.Errorf("cannot resolve cgo type for function `%s`: %v", d.Name, err)
   172  		}
   173  
   174  		arg = append(arg, cgoExpr)
   175  	}
   176  
   177  	f.Body = &goast.BlockStmt{}
   178  
   179  	stmts := prepareIfForNilArgs(argResolvedType, returnResolvedType)
   180  	f.Body.List = append(f.Body.List, stmts...)
   181  	stmts = bindFromCtoGo(p, d.ReturnType, returnResolvedType, util.NewCallExpr(fmt.Sprintf("C.%s", d.Name), arg...))
   182  	f.Body.List = append(f.Body.List, stmts...)
   183  
   184  	// add comment for function
   185  	f.Doc = &goast.CommentGroup{
   186  		List: []*goast.Comment{
   187  			{
   188  				Text: fmt.Sprintf("// %s - add c-binding for implemention function", d.Name),
   189  			},
   190  		},
   191  	}
   192  
   193  	var buf bytes.Buffer
   194  	if err := format.Node(&buf, token.NewFileSet(), &goast.File{
   195  		Name:  goast.NewIdent("main"),
   196  		Decls: []goast.Decl{&f},
   197  	}); err != nil {
   198  		return "", fmt.Errorf("cannot get source of binding function : %s", d.Name)
   199  	}
   200  
   201  	return buf.String(), nil
   202  }
   203  
   204  func cgoTypes(goType string) (_ string, ok bool) {
   205  	goType = strings.TrimSpace(goType)
   206  	switch goType {
   207  	case "int":
   208  		return "int", true
   209  	case "int32":
   210  		return "int", true
   211  	case "int64":
   212  		return "long", true
   213  	case "float64":
   214  		return "double", true
   215  	case "byte":
   216  		return "char", true
   217  	case "uint":
   218  		return "ulong", true
   219  	case "noarch.Tm":
   220  		return "struct_tm", true
   221  	case "noarch.File":
   222  		return "FILE", true
   223  	case "uint32":
   224  		return "ulong", true
   225  	}
   226  	return "", false
   227  }
   228  
   229  // TODO : add implementation
   230  //
   231  // Example:
   232  // func write(arg0 int32, arg1 interface{}, arg2 uint) noarch.SsizeT {
   233  //      a := arg1.([]byte)
   234  //      b := string(a)
   235  //      c := C.CString(b)
   236  //      return noarch.SsizeT(C.write(C.int(arg0), (unsafe.Pointer(c)), C.ulong(arg2)))
   237  // }
   238  //
   239  // func read(arg0 int32, arg1 interface{}, arg2 uint) noarch.SsizeT {
   240  //      a := arg1.([]byte)
   241  //      b := string(a)
   242  //      c := C.CString(b)
   243  //      S := noarch.SsizeT(C.read(C.int(arg0), unsafe.Pointer(c), C.ulong(arg2)))
   244  //      d := C.GoString(c)
   245  //      arg1 = []byte(d)
   246  //      return S
   247  // }
   248  //
   249  //	func read(arg0 int32, arg1 interface{}, arg2 uint) noarch.SsizeT {
   250  //	   switch v := arg1.(type) {
   251  //	   case []byte:
   252  //	   	a := v
   253  //	   	b := string(a)
   254  //	   	c := C.CString(b)
   255  //	   	S := noarch.SsizeT(C.read(C.int(arg0), unsafe.Pointer(c), C.ulong(arg2)))
   256  //	   	d := C.GoString(c)
   257  //	   	arg1 = []byte(d)
   258  //	   	return S
   259  //	   case *[]byte:
   260  //	   	a := v
   261  //	   	b := string(*a)
   262  //	   	c := C.CString(b)
   263  //	   	S := noarch.SsizeT(C.read(C.int(arg0), unsafe.Pointer(c), C.ulong(arg2)))
   264  //	   	d := C.GoString(c)
   265  //	   	arg1 = []byte(d)
   266  //	   	return S
   267  //	   }
   268  //	   return noarch.SsizeT(C.read(C.int(arg0), unsafe.Pointer(&arg1), C.ulong(arg2)))
   269  //	}
   270  //
   271  // 	func write(arg0 int32, arg1 interface{}, arg2 uint) noarch.SsizeT {
   272  // 	   switch v := arg1.(type) {
   273  // 	   case []byte: // []uint8:
   274  // 	   	a := v
   275  // 	   	b := string(a)
   276  // 	   	c := C.CString(b)
   277  // 	   	return noarch.SsizeT(C.write(C.int(arg0), (unsafe.Pointer(c)), C.ulong(arg2)))
   278  // 	   }
   279  // 	   return noarch.SsizeT(C.write(C.int(arg0), (unsafe.Pointer(&arg1)), C.ulong(arg2)))
   280  // 	}
   281  
   282  func ResolveCgoType(p *program.Program, goType string, expr goast.Expr) (a goast.Expr, err error) {
   283  
   284  	var has3poins bool
   285  	if has3poins = strings.HasPrefix(goType, "..."); has3poins {
   286  		goType = goType[3:]
   287  	}
   288  
   289  	if has3poins {
   290  		expr = &goast.IndexExpr{
   291  			X:     expr,
   292  			Index: goast.NewIdent("0"),
   293  		}
   294  	}
   295  
   296  	if ct, ok := cgoTypes(goType); ok {
   297  		return util.NewCallExpr("C."+ct, expr), nil
   298  	}
   299  
   300  	t := goType
   301  
   302  	if strings.HasPrefix(goType, "[][]") {
   303  		t = "interface{}"
   304  	} else if strings.HasPrefix(goType, "[") {
   305  		// []int  -> * _Ctype_int
   306  		t = goType[2:]
   307  		var ok bool
   308  		t, ok = cgoTypes(t)
   309  		if !ok {
   310  			// TODO: check next
   311  			t = goType[2:]
   312  		}
   313  
   314  		if _, ok := p.Structs[t]; ok {
   315  			t = "( * C.struct_" + t + " ) "
   316  		} else {
   317  			t = "( * C." + t + " ) "
   318  		}
   319  		t = strings.Replace(t, " ", "", -1)
   320  
   321  		p.AddImport("unsafe")
   322  
   323  		return util.NewCallExpr(t, util.NewCallExpr("unsafe.Pointer",
   324  			util.NewUnaryExpr(&goast.IndexExpr{
   325  				X:      expr,
   326  				Lbrack: 1,
   327  				Index:  goast.NewIdent("0"),
   328  			}, token.AND))), nil
   329  
   330  	} else if strings.HasPrefix(goType, "*") {
   331  		// *int  -> * _Ctype_int
   332  		t = goType[1:]
   333  		var ok bool
   334  		t, ok = cgoTypes(t)
   335  		if !ok {
   336  			// TODO: check next
   337  			t = goType[1:]
   338  		}
   339  		t = "( * C." + t + " ) "
   340  		t = strings.Replace(t, " ", "", -1)
   341  
   342  		p.AddImport("unsafe")
   343  
   344  		return util.NewCallExpr(t, util.NewCallExpr("unsafe.Pointer",
   345  			util.NewUnaryExpr(expr, token.AND))), nil
   346  	}
   347  
   348  	if t == "interface{}" {
   349  
   350  		p.AddImport("unsafe")
   351  
   352  		return util.NewCallExpr("unsafe.Pointer",
   353  			util.NewUnaryExpr(expr, token.AND)), nil
   354  	}
   355  
   356  	return util.NewCallExpr("C."+t, expr), nil
   357  }
   358  
   359  // example:
   360  //
   361  // returnValue := ...
   362  // return cast_from_C_to_Go_type(returnValue)
   363  func bindFromCtoGo(p *program.Program, cType string, goType string, expr goast.Expr) (stmts []goast.Stmt) {
   364  
   365  	if expr == nil {
   366  		expr = goast.NewIdent("C4GO_UNDEFINE_EXPR")
   367  	}
   368  	if goType == "" {
   369  		goType = "C4GO_UNDEFINE_GO_TYPE"
   370  	}
   371  
   372  	if cType == "" || cType == "void" {
   373  		stmts = append(stmts, &goast.ExprStmt{expr})
   374  		return
   375  	}
   376  
   377  	// from documentation : https://golang.org/cmd/cgo/
   378  	//
   379  	// C string to Go string
   380  	// func C.GoString(*C.char) string
   381  	//
   382  
   383  	switch cType {
   384  	case "char *":
   385  		stmts = append(stmts, &goast.ReturnStmt{Results: []goast.Expr{
   386  			util.NewCallExpr("[]byte",
   387  				util.NewCallExpr("C.GoString", expr),
   388  			),
   389  		}})
   390  
   391  	default:
   392  		stmts = append(stmts, &goast.ReturnStmt{Results: []goast.Expr{
   393  			util.NewCallExpr(goType, expr),
   394  		}})
   395  	}
   396  
   397  	return
   398  }
   399  
   400  // add if`s for nil cases
   401  //
   402  // strtok - add c-binding for implementation function
   403  //
   404  //	func strtok(arg0 []byte, arg1 []byte) []byte {
   405  //		if arg0 == nil {
   406  //			return []byte{}
   407  //		}
   408  //		if arg1 == nil {
   409  //			return []byte{}
   410  //		}
   411  //		return (.....)
   412  //	}
   413  func prepareIfForNilArgs(argType []string, returnType string) (stmts []goast.Stmt) {
   414  	var ret goast.Stmt
   415  	switch {
   416  	case strings.Contains(returnType, "[]"):
   417  		ret = &goast.ReturnStmt{
   418  			Results: []goast.Expr{
   419  				goast.NewIdent(returnType + "{}"),
   420  			},
   421  		}
   422  
   423  	default:
   424  		return
   425  	}
   426  
   427  	for i := range argType {
   428  		// for slices : []byte, []int, ...
   429  		// 	if arg... == nil{
   430  		//		return []...{}
   431  		//	}
   432  		if strings.Contains(argType[i], "[]") {
   433  			stmts = append(stmts, &goast.IfStmt{
   434  				Cond: &goast.BinaryExpr{
   435  					X:  goast.NewIdent(getBindArgName(i)),
   436  					Op: token.EQL,
   437  					Y:  goast.NewIdent("nil"),
   438  				},
   439  				Body: &goast.BlockStmt{
   440  					List: []goast.Stmt{
   441  						ret,
   442  					},
   443  				},
   444  			})
   445  			continue
   446  		}
   447  	}
   448  	return
   449  }