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  }