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 }