github.com/gotranspile/cxgo@v0.3.7/translate.go (about) 1 package cxgo 2 3 import ( 4 "bytes" 5 "fmt" 6 "go/format" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "regexp" 11 "strings" 12 13 "modernc.org/cc/v3" 14 "modernc.org/token" 15 16 "github.com/gotranspile/cxgo/libs" 17 "github.com/gotranspile/cxgo/types" 18 ) 19 20 type Config struct { 21 Root string 22 Package string 23 GoFile string 24 GoFilePref string 25 Include []string 26 SysInclude []string 27 MaxDecls int 28 Predef string 29 Define []Define 30 FlattenAll bool 31 ForwardDecl bool 32 SkipDecl map[string]bool 33 Idents []IdentConfig 34 Replace []Replacer 35 Hooks bool 36 FixImplicitReturns bool 37 IgnoreIncludeDir bool 38 UnexportedFields bool // do not export struct fields for Go 39 } 40 41 type TypeHint string 42 43 const ( 44 HintBool = TypeHint("bool") // force the type to Go bool 45 HintSlice = TypeHint("slice") // force type to Go slice (for pointers and arrays) 46 HintIface = TypeHint("iface") // force type to Go interface{} 47 HintString = TypeHint("string") // force type to Go string 48 ) 49 50 type IdentConfig struct { 51 Name string `yaml:"name" json:"name"` // identifier name in C 52 Index int `yaml:"index" json:"index"` // argument index, only for Fields in the function decl 53 Rename string `yaml:"rename" json:"rename"` // rename the identifier 54 Alias bool `yaml:"alias" json:"alias"` // omit declaration, use underlying type instead 55 Type TypeHint `yaml:"type" json:"type"` // changes the Go type of this identifier 56 Flatten *bool `yaml:"flatten" json:"flatten"` // flattens function control flow to workaround invalid gotos 57 Fields []IdentConfig `yaml:"fields" json:"fields"` // configs for struct fields or func arguments 58 } 59 60 type Replacer struct { 61 Old string 62 Re *regexp.Regexp 63 New string 64 } 65 66 type FileError struct { 67 Err error 68 Where token.Position 69 } 70 71 func (e *FileError) Unwrap() error { 72 return e.Err 73 } 74 75 func (e *FileError) Error() string { 76 return e.Where.String() + ": " + e.Err.Error() 77 } 78 79 func ErrorWithPos(err error, where token.Position) error { 80 if err == nil { 81 return nil 82 } 83 if e, ok := err.(*FileError); ok { 84 return e 85 } 86 return &FileError{Err: err, Where: where} 87 } 88 89 func ErrorfWithPos(where token.Position, format string, args ...any) error { 90 return ErrorWithPos(fmt.Errorf(format, args...), where) 91 } 92 93 func Translate(root, fname, out string, env *libs.Env, conf Config) error { 94 cname := fname 95 tu, err := Parse(env, root, cname, SourceConfig{ 96 Predef: conf.Predef, 97 Define: conf.Define, 98 Include: conf.Include, 99 SysInclude: conf.SysInclude, 100 IgnoreIncludeDir: conf.IgnoreIncludeDir, 101 }) 102 if err != nil { 103 return fmt.Errorf("parsing failed: %w", err) 104 } 105 decls, err := TranslateAST(cname, tu, env, conf) 106 if err != nil { 107 return err 108 } 109 pkg := conf.Package 110 if pkg == "" { 111 pkg = "lib" 112 } 113 _ = os.MkdirAll(out, 0755) 114 bbuf := bytes.NewBuffer(nil) 115 gofile := conf.GoFile 116 if gofile == "" { 117 gofile, err = filepath.Rel(root, fname) 118 if err != nil { 119 return err 120 } 121 if conf.GoFilePref != "" { 122 dir, base := filepath.Split(gofile) 123 gofile = dir + conf.GoFilePref + base 124 } 125 // flatten C source file path to make a single large Go package 126 // TODO: auto-generate Go packages based on dir structure 127 gofile = strings.ReplaceAll(gofile, string(filepath.Separator), "_") 128 gofile = strings.TrimSuffix(gofile, ".c") 129 gofile = strings.TrimSuffix(gofile, ".h") 130 gofile += ".go" 131 } 132 max := conf.MaxDecls 133 if max == 0 { 134 max = 100 135 } 136 // optionally split large files by N declaration per file 137 for i := 0; len(decls) > 0; i++ { 138 cur := decls 139 if max > 0 && len(cur) > max { 140 cur = cur[:max] 141 } 142 decls = decls[len(cur):] 143 144 // generate Go file header with a package name and a list of imports 145 header := ImportsFor(env, cur) 146 buf := make([]GoDecl, 0, len(header)+len(cur)) 147 buf = append(buf, header...) 148 buf = append(buf, cur...) 149 150 bbuf.Reset() 151 err = PrintGo(bbuf, pkg, buf) 152 if err != nil { 153 return err 154 } 155 suff := fmt.Sprintf("_p%d", i+1) 156 if i == 0 && len(decls) == 0 { 157 suff = "" 158 } 159 gopath := strings.TrimSuffix(gofile, ".go") + suff + ".go" 160 if !filepath.IsAbs(gopath) { 161 gopath = filepath.Join(out, gopath) 162 } 163 164 fdata := bbuf.Bytes() 165 // run replacements defined in the config 166 for _, rep := range conf.Replace { 167 if rep.Re != nil { 168 fdata = rep.Re.ReplaceAll(fdata, []byte(rep.New)) 169 } else { 170 fdata = bytes.ReplaceAll(fdata, []byte(rep.Old), []byte(rep.New)) 171 } 172 } 173 174 fmtdata, err := format.Source(fdata) 175 if err != nil { 176 // write anyway for examination 177 _ = ioutil.WriteFile(gopath, fdata, 0644) 178 return fmt.Errorf("error formatting %s: %v", filepath.Base(gofile), err) 179 } 180 err = ioutil.WriteFile(gopath, fmtdata, 0644) 181 if err != nil { 182 return err 183 } 184 } 185 return nil 186 } 187 188 // TranslateAST takes a C translation unit and converts it to a list of Go declarations. 189 func TranslateAST(fname string, tu *cc.AST, env *libs.Env, conf Config) ([]GoDecl, error) { 190 t := newTranslator(env, conf) 191 return t.translate(fname, tu), nil 192 } 193 194 // TranslateCAST takes a C translation unit and converts it to a list of cxgo declarations. 195 func TranslateCAST(fname string, tu *cc.AST, env *libs.Env, conf Config) ([]CDecl, error) { 196 t := newTranslator(env, conf) 197 return t.translateC(fname, tu), nil 198 } 199 200 func newTranslator(env *libs.Env, conf Config) *translator { 201 tr := &translator{ 202 env: env, 203 tenv: env.Clone(), 204 conf: conf, 205 idents: make(map[string]IdentConfig), 206 ctypes: make(map[cc.Type]types.Type), 207 decls: make(map[cc.Node]*types.Ident), 208 namedPtrs: make(map[string]types.PtrType), 209 named: make(map[string]types.Named), 210 aliases: make(map[string]types.Type), 211 macros: make(map[string]*types.Ident), 212 } 213 for _, v := range conf.Idents { 214 tr.idents[v.Name] = v 215 } 216 _, _ = tr.tenv.GetLibrary(libs.BuiltinH) 217 _, _ = tr.tenv.GetLibrary(libs.StdlibH) 218 _, _ = tr.tenv.GetLibrary(libs.StdioH) 219 return tr 220 } 221 222 type translator struct { 223 env *libs.Env 224 tenv *libs.Env // virtual env for stdlib forward declarations 225 conf Config 226 227 file *cc.AST 228 cur string 229 230 idents map[string]IdentConfig 231 ctypes map[cc.Type]types.Type 232 namedPtrs map[string]types.PtrType 233 named map[string]types.Named 234 aliases map[string]types.Type 235 macros map[string]*types.Ident 236 decls map[cc.Node]*types.Ident 237 } 238 239 func (g *translator) Nil() Nil { 240 return NewNil(g.env.PtrSize()) 241 } 242 243 func (g *translator) Iota() Expr { 244 return IdentExpr{g.env.Go().Iota()} 245 } 246 247 const ( 248 libcCStringSliceName = "libc.CStringSlice" 249 ) 250 251 func (g *translator) translateMain(d *CFuncDecl) { 252 osExit := g.env.Go().OsExitFunc() 253 if d.Type.ArgN() == 2 { 254 libcCSlice := types.NewIdent(libcCStringSliceName, g.env.FuncTT(g.env.PtrT(g.env.C().String()), types.SliceT(g.env.Go().String()))) 255 osArgs := types.NewIdent("os.Args", types.SliceT(g.env.Go().String())) 256 argsLen := &CallExpr{Fun: FuncIdent{g.env.Go().LenFunc()}, Args: []Expr{IdentExpr{osArgs}}} 257 argsPtr := &CallExpr{Fun: FuncIdent{libcCSlice}, Args: []Expr{IdentExpr{osArgs}}} 258 // define main args in the function body 259 args := d.Type.Args() 260 argc := g.NewCDeclStmt(&CVarDecl{CVarSpec: CVarSpec{ 261 g: g, 262 Type: args[0].Type(), 263 Names: []*types.Ident{args[0].Name}, 264 Inits: []Expr{g.cCast(args[0].Type(), argsLen)}, 265 }}) 266 argv := g.NewCDeclStmt(&CVarDecl{CVarSpec: CVarSpec{ 267 g: g, 268 Type: args[1].Type(), 269 Names: []*types.Ident{args[1].Name}, 270 Inits: []Expr{g.cCast(args[1].Type(), argsPtr)}, 271 }}) 272 var stmts []CStmt 273 stmts = append(stmts, argc...) 274 stmts = append(stmts, argv...) 275 stmts = append(stmts, d.Body.Stmts...) 276 d.Body.Stmts = stmts 277 d.Type = g.env.FuncT(d.Type.Return()) 278 } 279 d.Body.Stmts, _ = cReplaceEachStmt(func(s CStmt) ([]CStmt, bool) { 280 r, ok := s.(*CReturnStmt) 281 if !ok { 282 return []CStmt{s}, false 283 } 284 e := r.Expr 285 if e == nil { 286 e = cIntLit(0) 287 } 288 ex := g.NewCCallExpr(FuncIdent{osExit}, []Expr{g.cCast(g.env.Go().Int(), e)}) 289 return NewCExprStmt(ex), true 290 }, d.Body.Stmts) 291 d.Type = g.env.FuncT(nil, d.Type.Args()...) 292 } 293 294 func (g *translator) translate(cur string, ast *cc.AST) []GoDecl { 295 decl := g.translateC(cur, ast) 296 g.rewriteStatements(decl) 297 if g.conf.FixImplicitReturns { 298 g.fixImplicitReturns(decl) 299 } 300 // adapt well-known decls like main 301 decl = g.adaptMain(decl) 302 // run plugin hooks 303 decl = g.runASTPluginsC(cur, ast, decl) 304 // flatten functions, if needed 305 g.flatten(decl) 306 // fix unused variables 307 g.fixUnusedVars(decl) 308 // convert to Go AST 309 var gdecl []GoDecl 310 for _, d := range decl { 311 switch d := d.(type) { 312 case *CFuncDecl: 313 if g.conf.SkipDecl[d.Name.Name] { 314 continue 315 } 316 case *CVarDecl: 317 // TODO: skip any single one 318 if len(d.Names) == 1 && g.conf.SkipDecl[d.Names[0].Name] { 319 continue 320 } 321 case *CTypeDef: 322 if g.conf.SkipDecl[d.Name().Name] { 323 continue 324 } 325 } 326 gdecl = append(gdecl, d.AsDecl()...) 327 } 328 return gdecl 329 } 330 331 func (g *translator) translateC(cur string, ast *cc.AST) []CDecl { 332 g.file, g.cur = ast, strings.TrimLeft(cur, "./") 333 334 decl := g.convertMacros(ast) 335 336 tu := ast.TranslationUnit 337 for tu != nil { 338 d := tu.ExternalDeclaration 339 tu = tu.TranslationUnit 340 if d == nil { 341 continue 342 } 343 var cd []CDecl 344 switch d.Case { 345 case cc.ExternalDeclarationFuncDef: 346 cd = g.convertFuncDef(d.FunctionDefinition) 347 case cc.ExternalDeclarationDecl: 348 cd = g.convertDecl(d.Declaration) 349 case cc.ExternalDeclarationEmpty: 350 // TODO 351 default: 352 panic(d.Case.String() + " " + d.Position().String()) 353 } 354 decl = append(decl, cd...) 355 } 356 // remove forward declarations 357 m := make(map[string]CDecl) 358 skip := make(map[CDecl]struct{}) 359 for _, d := range decl { 360 switch d := d.(type) { 361 case *CFuncDecl: 362 d2, ok := m[d.Name.Name].(*CFuncDecl) 363 if !ok { 364 m[d.Name.Name] = d 365 continue 366 } 367 if d2.Body != nil { 368 skip[d] = struct{}{} 369 } else { 370 m[d.Name.Name] = d 371 skip[d2] = struct{}{} 372 } 373 case *CTypeDef: 374 d2, ok := m[d.Name().Name].(*CTypeDef) 375 if !ok { 376 m[d.Name().Name] = d 377 continue 378 } 379 if d.Underlying() == d2.Underlying() { 380 m[d.Name().Name] = d 381 skip[d] = struct{}{} 382 } 383 } 384 } 385 decl2 := make([]CDecl, 0, len(decl)) 386 for _, d := range decl { 387 if _, skip := skip[d]; skip { 388 continue 389 } 390 decl2 = append(decl2, d) 391 } 392 return decl2 393 }