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  }