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