├── .gitignore ├── docs ├── overview.png └── sphere.png ├── generator ├── Makefile ├── gen_union.go ├── gen_typedef.go ├── gen_declares.go ├── gen_files.go ├── gen_common.go ├── gen_const.go ├── gen_callbacks.go ├── gen_struct_helpers.go └── generator.go ├── go.mod ├── translator ├── array_spec.go ├── model_declaration.go ├── model_function.go ├── model_enum.go ├── model_type.go ├── model_struct_union.go ├── rules.go ├── type_mapping.go ├── model_go_type.go ├── helpers.go ├── ast_walker.go ├── ast_walker_test.go └── translator.go ├── LICENSE ├── README.md ├── main.go ├── go.sum ├── parser └── parser.go └── process.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /tmp/ 3 | c-for-go 4 | -------------------------------------------------------------------------------- /docs/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xlab/c-for-go/HEAD/docs/overview.png -------------------------------------------------------------------------------- /docs/sphere.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xlab/c-for-go/HEAD/docs/sphere.png -------------------------------------------------------------------------------- /generator/Makefile: -------------------------------------------------------------------------------- 1 | clean: 2 | rm -f test/foo.go test/cgo_helpers.go test/cgo_helpers.c test/cgo_helpers.h 3 | -------------------------------------------------------------------------------- /generator/gen_union.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import "io" 4 | 5 | func (gen *Generator) WriteUnions(wr io.Writer) int { 6 | return 0 7 | } 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/xlab/c-for-go 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/dlclark/regexp2 v1.11.0 7 | github.com/stretchr/testify v1.6.1 8 | github.com/tj/go-spin v1.1.0 9 | github.com/xlab/pkgconfig v0.0.0-20170226114623-cea12a0fd245 10 | golang.org/x/tools v0.1.12 11 | gopkg.in/yaml.v2 v2.4.0 12 | modernc.org/cc/v4 v4.21.4 13 | modernc.org/token v1.1.0 14 | ) 15 | 16 | require ( 17 | github.com/davecgh/go-spew v1.1.0 // indirect 18 | github.com/pmezard/go-difflib v1.0.0 // indirect 19 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 20 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect 21 | golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect 22 | gopkg.in/yaml.v3 v3.0.0 // indirect 23 | modernc.org/mathutil v1.6.0 // indirect 24 | modernc.org/opt v0.1.3 // indirect 25 | modernc.org/sortutil v1.2.0 // indirect 26 | modernc.org/strutil v1.2.0 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /translator/array_spec.go: -------------------------------------------------------------------------------- 1 | package translator 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | type ArraySpec string 10 | 11 | func (a ArraySpec) String() string { 12 | return string(a) 13 | } 14 | 15 | func (a *ArraySpec) AddSized(size uint64) { 16 | *a = ArraySpec(fmt.Sprintf("%s[%d]", a, size)) 17 | } 18 | 19 | func (a *ArraySpec) Prepend(spec ArraySpec) { 20 | *a = spec + *a 21 | } 22 | 23 | type ArraySizeSpec struct { 24 | N uint64 25 | Str string 26 | } 27 | 28 | func (a ArraySpec) Sizes() (sizes []ArraySizeSpec) { 29 | if len(a) == 0 { 30 | return 31 | } 32 | arr := string(a) 33 | for len(arr) > 0 { 34 | // get "n" from "[k][l][m][n]" 35 | p1 := strings.LastIndexByte(arr, '[') 36 | p2 := strings.LastIndexByte(arr, ']') 37 | part := arr[p1+1 : p2] 38 | // and try to convert uint64 39 | if u, err := strconv.ParseUint(part, 10, 64); err != nil || u == 0 { 40 | // use size spec as-is (i.e. unsafe.Sizeof(x)) 41 | sizes = append(sizes, ArraySizeSpec{Str: part}) 42 | } else { 43 | sizes = append(sizes, ArraySizeSpec{N: u}) 44 | } 45 | arr = arr[:p1] 46 | } 47 | return sizes 48 | } 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2015-2017 Maxim Kupriianov 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the “Software”), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /translator/model_declaration.go: -------------------------------------------------------------------------------- 1 | package translator 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "modernc.org/token" 8 | ) 9 | 10 | type CTypeKind int 11 | 12 | const ( 13 | TypeKind CTypeKind = iota 14 | PlainTypeKind 15 | StructKind 16 | OpaqueStructKind 17 | UnionKind 18 | FunctionKind 19 | EnumKind 20 | ) 21 | 22 | type CType interface { 23 | GetBase() string 24 | GetTag() string 25 | SetRaw(x string) 26 | CGoName() string 27 | GetPointers() uint8 28 | SetPointers(uint8) 29 | AddOuterArr(uint64) 30 | AddInnerArr(uint64) 31 | OuterArrays() ArraySpec 32 | InnerArrays() ArraySpec 33 | OuterArraySizes() []ArraySizeSpec 34 | InnerArraySizes() []ArraySizeSpec 35 | // 36 | IsConst() bool 37 | IsOpaque() bool 38 | IsComplete() bool 39 | Kind() CTypeKind 40 | String() string 41 | Copy() CType 42 | AtLevel(level int) CType 43 | } 44 | 45 | type ( 46 | Value interface{} 47 | ) 48 | 49 | type CDecl struct { 50 | Spec CType 51 | Name string 52 | Value Value 53 | Expression string 54 | IsStatic bool 55 | IsTypedef bool 56 | IsDefine bool 57 | Position token.Position 58 | Src string 59 | } 60 | 61 | func (c CDecl) String() string { 62 | buf := new(bytes.Buffer) 63 | switch { 64 | case len(c.Name) > 0: 65 | fmt.Fprintf(buf, "%s %s", c.Spec, c.Name) 66 | default: 67 | buf.WriteString(c.Spec.String()) 68 | } 69 | if len(c.Expression) > 0 { 70 | fmt.Fprintf(buf, " = %s", string(c.Expression)) 71 | } 72 | return buf.String() 73 | } 74 | -------------------------------------------------------------------------------- /translator/model_function.go: -------------------------------------------------------------------------------- 1 | package translator 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type CFunctionSpec struct { 9 | Raw string 10 | Typedef string 11 | Return CType 12 | Params []*CDecl 13 | Pointers uint8 14 | } 15 | 16 | func (c CFunctionSpec) String() string { 17 | if len(c.Raw) > 0 { 18 | return c.Raw 19 | } 20 | var params []string 21 | for i, param := range c.Params { 22 | if len(param.Name) == 0 { 23 | params = append(params, fmt.Sprintf("arg%d", i)) 24 | continue 25 | } 26 | params = append(params, param.String()) 27 | } 28 | paramList := strings.Join(params, ", ") 29 | if c.Return != nil { 30 | return fmt.Sprintf("%s (%s)", c.Return, paramList) 31 | } 32 | return fmt.Sprintf("void (%s)", paramList) 33 | } 34 | 35 | func (c *CFunctionSpec) SetPointers(n uint8) { 36 | c.Pointers = n 37 | } 38 | 39 | func (c *CFunctionSpec) Kind() CTypeKind { 40 | return FunctionKind 41 | } 42 | 43 | func (c *CFunctionSpec) IsComplete() bool { 44 | return true 45 | } 46 | 47 | func (c *CFunctionSpec) IsOpaque() bool { 48 | return len(c.Params) == 0 49 | } 50 | 51 | func (c CFunctionSpec) Copy() CType { 52 | return &c 53 | } 54 | 55 | func (c *CFunctionSpec) GetBase() string { 56 | return "" 57 | } 58 | 59 | func (c *CFunctionSpec) GetTag() string { 60 | return c.Raw 61 | } 62 | 63 | func (c *CFunctionSpec) CGoName() string { 64 | return c.Raw 65 | } 66 | 67 | func (c *CFunctionSpec) SetRaw(x string) { 68 | c.Raw = x 69 | } 70 | 71 | func (c *CFunctionSpec) AddOuterArr(uint64) {} 72 | func (c *CFunctionSpec) AddInnerArr(uint64) {} 73 | func (c *CFunctionSpec) OuterArraySizes() []ArraySizeSpec { 74 | return nil 75 | } 76 | func (c *CFunctionSpec) InnerArraySizes() []ArraySizeSpec { 77 | return nil 78 | } 79 | func (c *CFunctionSpec) OuterArrays() ArraySpec { 80 | return "" 81 | } 82 | func (c *CFunctionSpec) InnerArrays() ArraySpec { 83 | return "" 84 | } 85 | 86 | func (c *CFunctionSpec) GetPointers() uint8 { 87 | return c.Pointers 88 | } 89 | 90 | func (c *CFunctionSpec) IsConst() bool { 91 | return false 92 | } 93 | 94 | func (c CFunctionSpec) AtLevel(level int) CType { 95 | return &c 96 | } 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # c-for-go [![Go Report Card](https://goreportcard.com/badge/github.com/xlab/c-for-go)](https://goreportcard.com/report/github.com/xlab/c-for-go)[![go-recipes](https://raw.githubusercontent.com/nikolaydubina/go-recipes/main/badge.svg?raw=true)](https://github.com/nikolaydubina/go-recipes) 2 | _Automatic C-Go Bindings Generator for Go Programming Language_ 3 | 4 | This project allows to reuse existing C/C++ libraries in your Go applications, by automatically creating [c-go bindings](https://golang.org/cmd/cgo/) for a given set of C headers and the manifest file. We believe in component-based software engineering and think that reusing C/C++ code in Go applications could bring a huge boost to developer's productivity and system's performance. Read more about the motivation: [top reasons to use bindings](https://github.com/xlab/c-for-go/wiki/Top-5-reasons-to-use-bindings). 5 | 6 | ### Process overview 7 | 8 |

9 | c-for-go process overview 10 |

11 | 12 | The only component required to produce a Go package that will wrap the source C/C++ code is the YAML manifest file that defines parsing, translation and generation rules. The manifest can have just a few lines, however in order to match Go's naming conventions and provide enough tips for type conversions it usually contains about 100 lines, which is still better than producing tens of thousands lines of Go code by hand. 13 | 14 | The resulting bindings are as low-level as C code, i.e. it would require knowledge of memory management to carefully use the resulting code, however no more C code is needed to make things done. Eventually some functions can be replaced manually with pure-Go analogs. Also usually a high-level wrapper is created by hand, to introduce Object Oriented Design into API, manage inner state and memory, thus making things safe and lifting the mental overhead. 15 | 16 | Full documentation is available at https://github.com/xlab/c-for-go/wiki 17 | 18 | ### Installation 19 | 20 | ```bash 21 | $ go install github.com/xlab/c-for-go@latest 22 | ``` 23 | 24 | A simple [Makefile template](https://github.com/xlab/c-for-go/wiki/Makefile-template). 25 | 26 | ### In action 27 | 28 | * https://github.com/xlab/android-go 🌟 29 | * https://github.com/vulkan-go/vulkan 🌟 30 | * https://github.com/golang-ui/nuklear 31 | * https://github.com/xlab/pocketsphinx-go 32 | * https://github.com/xlab/libvpx-go 33 | * https://github.com/xlab/portaudio-go 34 | * https://github.com/xlab/portmidi 35 | * https://github.com/xlab/alac-go 36 | * https://github.com/xlab/vorbis-go 37 | * https://github.com/xlab/opus-go 38 | * https://github.com/xlab/libpd-go 39 | * https://github.com/zenhotels/lmdb-go 40 | * https://github.com/zenhotels/libpostal-go 41 | * https://github.com/xlab/hamlib-go 42 | * https://github.com/5k3105/nidaq 43 | * https://github.com/SphereSoftware/pdf-go 44 | * ... 45 | * [Request yours](max@kc.vc) 46 | 47 | ### Credits 48 | 49 | * [Jan Mercl](https://github.com/cznic) for his [cznic/cc](https://gitlab.com/cznic/cc) C99 compiler front end package. 50 | 51 | ### License 52 | 53 | MIT 54 | -------------------------------------------------------------------------------- /translator/model_enum.go: -------------------------------------------------------------------------------- 1 | package translator 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | type CEnumSpec struct { 10 | Tag string 11 | Typedef string 12 | Members []*CDecl 13 | Type CTypeSpec 14 | Pointers uint8 15 | InnerArr ArraySpec 16 | OuterArr ArraySpec 17 | } 18 | 19 | func (c *CEnumSpec) PromoteType(v Value) *CTypeSpec { 20 | // ANSI C requires int values for enum. Therefore, force enum type to be int. 21 | c.Type = CTypeSpec{Base: "int"} 22 | return &c.Type 23 | } 24 | 25 | func (spec CEnumSpec) String() string { 26 | buf := new(bytes.Buffer) 27 | writePrefix := func() { 28 | buf.WriteString("enum ") 29 | } 30 | 31 | switch { 32 | case len(spec.Typedef) > 0: 33 | buf.WriteString(spec.Typedef) 34 | case len(spec.Tag) > 0: 35 | writePrefix() 36 | buf.WriteString(spec.Tag) 37 | case len(spec.Members) > 0: 38 | var members []string 39 | for _, m := range spec.Members { 40 | members = append(members, m.String()) 41 | } 42 | membersColumn := strings.Join(members, ",\n") 43 | writePrefix() 44 | fmt.Fprintf(buf, " {%s}", membersColumn) 45 | default: 46 | writePrefix() 47 | } 48 | 49 | buf.WriteString(arrs(spec.OuterArr)) 50 | buf.WriteString(ptrs(spec.Pointers)) 51 | buf.WriteString(arrs(spec.InnerArr)) 52 | return buf.String() 53 | } 54 | 55 | func (c *CEnumSpec) SetPointers(n uint8) { 56 | c.Pointers = n 57 | } 58 | 59 | func (c *CEnumSpec) Kind() CTypeKind { 60 | return EnumKind 61 | } 62 | 63 | func (c *CEnumSpec) IsComplete() bool { 64 | return len(c.Members) > 0 65 | } 66 | 67 | func (c *CEnumSpec) IsOpaque() bool { 68 | return len(c.Members) == 0 69 | } 70 | 71 | func (c CEnumSpec) Copy() CType { 72 | return &c 73 | } 74 | 75 | func (c *CEnumSpec) GetBase() string { 76 | if len(c.Typedef) > 0 { 77 | return c.Typedef 78 | } 79 | return c.Tag 80 | } 81 | 82 | func (c *CEnumSpec) SetRaw(x string) { 83 | c.Typedef = x 84 | } 85 | 86 | func (c *CEnumSpec) GetTag() string { 87 | return c.Tag 88 | } 89 | 90 | func (c *CEnumSpec) CGoName() string { 91 | if len(c.Typedef) > 0 { 92 | return c.Typedef 93 | } 94 | return "enum_" + c.Tag 95 | } 96 | 97 | func (c *CEnumSpec) AddOuterArr(size uint64) { 98 | c.OuterArr.AddSized(size) 99 | } 100 | 101 | func (c *CEnumSpec) AddInnerArr(size uint64) { 102 | c.InnerArr.AddSized(size) 103 | } 104 | 105 | func (c *CEnumSpec) OuterArraySizes() []ArraySizeSpec { 106 | return c.OuterArr.Sizes() 107 | } 108 | 109 | func (c *CEnumSpec) InnerArraySizes() []ArraySizeSpec { 110 | return c.InnerArr.Sizes() 111 | } 112 | 113 | func (c *CEnumSpec) OuterArrays() ArraySpec { 114 | return c.OuterArr 115 | } 116 | 117 | func (c *CEnumSpec) InnerArrays() ArraySpec { 118 | return c.InnerArr 119 | } 120 | 121 | func (c *CEnumSpec) GetPointers() uint8 { 122 | return c.Pointers 123 | } 124 | 125 | func (c *CEnumSpec) IsConst() bool { 126 | // could be c.Const 127 | return false 128 | } 129 | 130 | func (c CEnumSpec) AtLevel(level int) CType { 131 | spec := c 132 | var outerArrSpec ArraySpec 133 | for i, size := range spec.OuterArr.Sizes() { 134 | if i < int(level) { 135 | continue 136 | } else if i == 0 { 137 | spec.Pointers = 1 138 | continue 139 | } 140 | outerArrSpec.AddSized(size.N) 141 | } 142 | if int(level) > len(spec.OuterArr) { 143 | if delta := int(spec.Pointers) + len(spec.OuterArr.Sizes()) - int(level); delta > 0 { 144 | spec.Pointers = uint8(delta) 145 | } 146 | } 147 | spec.OuterArr = outerArrSpec 148 | spec.InnerArr = ArraySpec("") 149 | return &spec 150 | } 151 | -------------------------------------------------------------------------------- /translator/model_type.go: -------------------------------------------------------------------------------- 1 | package translator 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | type CTypeSpec struct { 9 | Raw string 10 | Base string 11 | Const bool 12 | Signed bool 13 | Unsigned bool 14 | Short bool 15 | Long bool 16 | Complex bool 17 | Opaque bool 18 | Pointers uint8 19 | InnerArr ArraySpec 20 | OuterArr ArraySpec 21 | } 22 | 23 | func (spec CTypeSpec) String() string { 24 | buf := new(bytes.Buffer) 25 | if spec.Unsigned { 26 | buf.WriteString("unsigned ") 27 | } else if spec.Signed { 28 | buf.WriteString("signed ") 29 | } 30 | switch { 31 | case spec.Long: 32 | buf.WriteString("long ") 33 | case spec.Short: 34 | buf.WriteString("short ") 35 | case spec.Complex: 36 | buf.WriteString("complex ") 37 | } 38 | fmt.Fprint(buf, spec.Base) 39 | 40 | var unsafePointer uint8 41 | if spec.Base == "unsafe.Pointer" { 42 | unsafePointer = 1 43 | } 44 | 45 | buf.WriteString(arrs(spec.InnerArr)) 46 | buf.WriteString(ptrs(spec.Pointers - unsafePointer)) 47 | buf.WriteString(arrs(spec.OuterArr)) 48 | return buf.String() 49 | } 50 | 51 | func (c *CTypeSpec) SetPointers(n uint8) { 52 | c.Pointers = n 53 | } 54 | 55 | func (c *CTypeSpec) IsComplete() bool { 56 | return true 57 | } 58 | 59 | func (c *CTypeSpec) IsOpaque() bool { 60 | return c.Opaque 61 | } 62 | 63 | func (c *CTypeSpec) Kind() CTypeKind { 64 | return TypeKind 65 | } 66 | 67 | func (c CTypeSpec) Copy() CType { 68 | return &c 69 | } 70 | 71 | func (c *CTypeSpec) GetBase() string { 72 | return c.Base 73 | } 74 | 75 | func (c *CTypeSpec) GetTag() string { 76 | return "" 77 | } 78 | 79 | func (c *CTypeSpec) SetRaw(x string) { 80 | c.Raw = x 81 | } 82 | 83 | func (c *CTypeSpec) CGoName() (name string) { 84 | if len(c.Raw) > 0 { 85 | return c.Raw 86 | } 87 | switch c.Base { 88 | case "int", "short", "long", "char": 89 | if c.Unsigned { 90 | name += "u" 91 | } else if c.Signed { 92 | name += "s" 93 | } 94 | switch { 95 | case c.Long: 96 | name += "long" 97 | if c.Base == "long" { 98 | name += "long" 99 | } 100 | case c.Short: 101 | name += "short" 102 | default: 103 | name += c.Base 104 | } 105 | default: 106 | name = c.Base 107 | } 108 | return 109 | } 110 | 111 | func (c *CTypeSpec) AddOuterArr(size uint64) { 112 | c.OuterArr.AddSized(size) 113 | } 114 | 115 | func (c *CTypeSpec) AddInnerArr(size uint64) { 116 | c.InnerArr.AddSized(size) 117 | } 118 | 119 | func (c *CTypeSpec) OuterArraySizes() []ArraySizeSpec { 120 | return c.OuterArr.Sizes() 121 | } 122 | 123 | func (c *CTypeSpec) InnerArraySizes() []ArraySizeSpec { 124 | return c.InnerArr.Sizes() 125 | } 126 | 127 | func (c *CTypeSpec) OuterArrays() ArraySpec { 128 | return c.OuterArr 129 | } 130 | 131 | func (c *CTypeSpec) InnerArrays() ArraySpec { 132 | return c.InnerArr 133 | } 134 | 135 | func (c *CTypeSpec) GetPointers() uint8 { 136 | return c.Pointers 137 | } 138 | 139 | func (c *CTypeSpec) IsConst() bool { 140 | return c.Const 141 | } 142 | 143 | func (c CTypeSpec) AtLevel(level int) CType { 144 | spec := c 145 | var outerArrSpec ArraySpec 146 | for i, size := range spec.OuterArr.Sizes() { 147 | if i < int(level) { 148 | continue 149 | } else if i == 0 { 150 | spec.Pointers = 1 151 | continue 152 | } 153 | outerArrSpec.AddSized(size.N) 154 | } 155 | if int(level) > len(spec.OuterArr) { 156 | if delta := int(spec.Pointers) + len(spec.OuterArr.Sizes()) - int(level); delta > 0 { 157 | spec.Pointers = uint8(delta) 158 | } 159 | } 160 | spec.OuterArr = outerArrSpec 161 | spec.InnerArr = ArraySpec("") 162 | return &spec 163 | } 164 | -------------------------------------------------------------------------------- /translator/model_struct_union.go: -------------------------------------------------------------------------------- 1 | package translator 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | type CStructSpec struct { 10 | Tag string 11 | Typedef string 12 | IsUnion bool 13 | Members []*CDecl 14 | Pointers uint8 15 | InnerArr ArraySpec 16 | OuterArr ArraySpec 17 | } 18 | 19 | func (spec CStructSpec) String() string { 20 | buf := new(bytes.Buffer) 21 | writePrefix := func() { 22 | if spec.IsUnion { 23 | buf.WriteString("union ") 24 | } else { 25 | buf.WriteString("struct ") 26 | } 27 | } 28 | 29 | switch { 30 | case len(spec.Typedef) > 0: 31 | buf.WriteString(spec.Typedef) 32 | case len(spec.Tag) > 0: 33 | writePrefix() 34 | buf.WriteString(spec.Tag) 35 | case len(spec.Members) > 0: 36 | var members []string 37 | for _, m := range spec.Members { 38 | members = append(members, m.String()) 39 | } 40 | membersColumn := strings.Join(members, ",\n") 41 | writePrefix() 42 | fmt.Fprintf(buf, " {%s}", membersColumn) 43 | default: 44 | writePrefix() 45 | } 46 | 47 | buf.WriteString(arrs(spec.OuterArr)) 48 | buf.WriteString(ptrs(spec.Pointers)) 49 | buf.WriteString(arrs(spec.InnerArr)) 50 | return buf.String() 51 | } 52 | 53 | func (c *CStructSpec) SetPointers(n uint8) { 54 | c.Pointers = n 55 | } 56 | 57 | func (c *CStructSpec) Kind() CTypeKind { 58 | switch { 59 | case c.IsUnion: 60 | return UnionKind 61 | case len(c.Members) == 0: 62 | return OpaqueStructKind 63 | default: 64 | return StructKind 65 | } 66 | } 67 | 68 | func (c *CStructSpec) IsComplete() bool { 69 | return len(c.Members) > 0 70 | } 71 | 72 | func (c *CStructSpec) IsOpaque() bool { 73 | return len(c.Members) == 0 74 | } 75 | 76 | func (c CStructSpec) Copy() CType { 77 | return &c 78 | } 79 | 80 | func (c *CStructSpec) GetBase() string { 81 | if len(c.Typedef) > 0 { 82 | return c.Typedef 83 | } 84 | return c.Tag 85 | } 86 | 87 | func (c *CStructSpec) GetTag() string { 88 | return c.Tag 89 | } 90 | 91 | func (c *CStructSpec) SetRaw(x string) { 92 | c.Typedef = x 93 | } 94 | 95 | func (c *CStructSpec) CGoName() string { 96 | if len(c.Typedef) > 0 { 97 | return c.Typedef 98 | } 99 | if c.IsUnion { 100 | return "union_" + c.Tag 101 | } 102 | return "struct_" + c.Tag 103 | } 104 | 105 | func (c *CStructSpec) AddOuterArr(size uint64) { 106 | c.OuterArr.AddSized(size) 107 | } 108 | 109 | func (c *CStructSpec) AddInnerArr(size uint64) { 110 | c.InnerArr.AddSized(size) 111 | } 112 | 113 | func (c *CStructSpec) OuterArraySizes() []ArraySizeSpec { 114 | return c.OuterArr.Sizes() 115 | } 116 | 117 | func (c *CStructSpec) InnerArraySizes() []ArraySizeSpec { 118 | return c.InnerArr.Sizes() 119 | } 120 | 121 | func (c *CStructSpec) OuterArrays() ArraySpec { 122 | return c.OuterArr 123 | } 124 | 125 | func (c *CStructSpec) InnerArrays() ArraySpec { 126 | return c.InnerArr 127 | } 128 | 129 | func (c *CStructSpec) GetPointers() uint8 { 130 | return c.Pointers 131 | } 132 | 133 | func (c *CStructSpec) IsConst() bool { 134 | return false 135 | } 136 | 137 | func (c CStructSpec) AtLevel(level int) CType { 138 | spec := c 139 | var outerArrSpec ArraySpec 140 | for i, size := range spec.OuterArr.Sizes() { 141 | if i < int(level) { 142 | continue 143 | } else if i == 0 { 144 | spec.Pointers = 1 145 | continue 146 | } 147 | outerArrSpec.AddSized(size.N) 148 | } 149 | if int(level) > len(spec.OuterArr) { 150 | if delta := int(spec.Pointers) + len(spec.OuterArr.Sizes()) - int(level); delta > 0 { 151 | spec.Pointers = uint8(delta) 152 | } 153 | } 154 | spec.OuterArr = outerArrSpec 155 | spec.InnerArr = ArraySpec("") 156 | return &spec 157 | } 158 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | "sync" 10 | "time" 11 | 12 | "github.com/tj/go-spin" 13 | ) 14 | 15 | var ( 16 | outputPath = flag.String("out", "", "Specify a `dir` for the output.") 17 | noCGO = flag.Bool("nocgo", false, "Do not include a cgo-specific header in resulting files.") 18 | ccDefs = flag.Bool("ccdefs", false, "Use built-in defines from a hosted C-compiler.") 19 | ccIncl = flag.Bool("ccincl", false, "Use built-in sys include paths from a hosted C-compiler.") 20 | maxMem = flag.String("maxmem", "0x7fffffff", "Specifies platform's memory cap the generated code.") 21 | fancy = flag.Bool("fancy", true, "Enable fancy output in the term.") 22 | nostamp = flag.Bool("nostamp", false, "Disable printing timestamps in the output files.") 23 | debug = flag.Bool("debug", false, "Enable some debug info.") 24 | ) 25 | 26 | const logo = `Copyright (c) 2015-2017 Maxim Kupriianov 27 | Based on a C99 compiler front end by Jan Mercl <0xjnml@gmail.com> 28 | 29 | ` 30 | 31 | func init() { 32 | if *debug { 33 | log.SetFlags(log.Lshortfile) 34 | } else { 35 | log.SetFlags(0) 36 | } 37 | flag.Usage = func() { 38 | fmt.Print(logo) 39 | fmt.Printf("Usage: %s package1.yml [package2.yml] ...\n", os.Args[0]) 40 | fmt.Printf("See https://github.com/xlab/c-for-go for examples and documentation.\n\n") 41 | fmt.Println("Options:") 42 | flag.PrintDefaults() 43 | } 44 | flag.Parse() 45 | if len(flag.Args()) == 0 { 46 | flag.Usage() 47 | fmt.Println() 48 | log.Fatalln("[ERR] no package configuration files have been provided.") 49 | } 50 | } 51 | 52 | func main() { 53 | s := spin.New() 54 | 55 | var wg sync.WaitGroup 56 | doneChan := make(chan struct{}) 57 | for _, cfgPath := range getConfigPaths() { 58 | if *fancy { 59 | wg.Add(1) 60 | go func() { 61 | for { 62 | select { 63 | case <-doneChan: 64 | doneChan = make(chan struct{}) 65 | fmt.Printf("\r \033[36mprocessing %s\033[m done.\n", cfgPath) 66 | wg.Done() 67 | return 68 | default: 69 | fmt.Printf("\r \033[36mprocessing %s\033[m %s", cfgPath, s.Next()) 70 | time.Sleep(100 * time.Millisecond) 71 | } 72 | } 73 | }() 74 | } 75 | 76 | var t0 time.Time 77 | if *debug { 78 | t0 = time.Now() 79 | } 80 | process, err := NewProcess(cfgPath, *outputPath) 81 | if err != nil { 82 | log.Fatalln("[ERR]", err) 83 | } 84 | process.Generate(*noCGO) 85 | if err := process.Flush(*noCGO); err != nil { 86 | log.Fatalln("[ERR]", err) 87 | } 88 | if *debug { 89 | fmt.Printf("done in %v\n", time.Now().Sub(t0)) 90 | } 91 | if *fancy { 92 | close(doneChan) 93 | wg.Wait() 94 | } 95 | } 96 | } 97 | 98 | func getConfigPaths() (paths []string) { 99 | for _, path := range flag.Args() { 100 | if info, err := os.Stat(path); err != nil { 101 | log.Fatalln("[ERR] cannot locate the specified path:", path) 102 | } else if info.IsDir() { 103 | if path, ok := configFromDir(path); ok { 104 | paths = append(paths, path) 105 | continue 106 | } 107 | log.Fatalln("[ERR] cannot find any config file in:", path) 108 | } 109 | paths = append(paths, path) 110 | } 111 | return 112 | } 113 | 114 | func configFromDir(path string) (string, bool) { 115 | possibleNames := []string{"c-for-go.yaml", "c-for-go.yml"} 116 | if base := filepath.Base(path); len(base) > 0 { 117 | possibleNames = append(possibleNames, 118 | fmt.Sprintf("%s.yaml", base), fmt.Sprintf("%s.yml", base)) 119 | } 120 | for _, name := range possibleNames { 121 | path := filepath.Join(path, name) 122 | if info, err := os.Stat(path); err == nil && !info.IsDir() { 123 | return path, true 124 | } 125 | } 126 | return "", false 127 | } 128 | -------------------------------------------------------------------------------- /translator/rules.go: -------------------------------------------------------------------------------- 1 | package translator 2 | 3 | import ( 4 | "github.com/dlclark/regexp2" 5 | ) 6 | 7 | type Rules map[RuleTarget][]RuleSpec 8 | type ConstRules map[ConstScope]ConstRule 9 | type PtrTips map[TipScope][]TipSpec 10 | type TypeTips map[TipScope][]TipSpec 11 | type MemTips []TipSpec 12 | 13 | type Validations []ValidationSpec 14 | 15 | type ValidationSpec struct { 16 | ValidateFunc string 17 | Ret string 18 | MatchedFunc string 19 | } 20 | 21 | func (v ValidationSpec) MatchFunc(name string) bool { 22 | reg := regexp2.MustCompile(v.MatchedFunc, 0) 23 | matched, _ := reg.MatchString(name) 24 | return matched 25 | } 26 | 27 | type RuleSpec struct { 28 | From, To string 29 | Action RuleAction 30 | Transform RuleTransform 31 | Load string 32 | } 33 | 34 | func (r *RuleSpec) LoadSpec(r2 RuleSpec) { 35 | if len(r.From) == 0 { 36 | r.From = r2.From 37 | } 38 | if len(r.To) == 0 { 39 | r.To = r2.To 40 | } 41 | if len(r.Action) == 0 { 42 | r.Action = r2.Action 43 | } 44 | if len(r.Transform) == 0 { 45 | r.Transform = r2.Transform 46 | } 47 | } 48 | 49 | type RuleAction string 50 | 51 | const ( 52 | ActionNone RuleAction = "" 53 | ActionAccept RuleAction = "accept" 54 | ActionIgnore RuleAction = "ignore" 55 | ActionReplace RuleAction = "replace" 56 | ActionDocument RuleAction = "doc" 57 | ) 58 | 59 | var ruleActions = []RuleAction{ 60 | ActionAccept, ActionIgnore, ActionReplace, ActionDocument, 61 | } 62 | 63 | type RuleTransform string 64 | 65 | const ( 66 | TransformLower RuleTransform = "lower" 67 | TransformTitle RuleTransform = "title" 68 | TransformExport RuleTransform = "export" 69 | TransformUnexport RuleTransform = "unexport" 70 | TransformUpper RuleTransform = "upper" 71 | ) 72 | 73 | type RuleTarget string 74 | 75 | const ( 76 | NoTarget RuleTarget = "" 77 | TargetGlobal RuleTarget = "global" 78 | TargetPostGlobal RuleTarget = "post-global" 79 | // 80 | TargetConst RuleTarget = "const" 81 | TargetType RuleTarget = "type" 82 | TargetFunction RuleTarget = "function" 83 | // 84 | TargetPublic RuleTarget = "public" 85 | TargetPrivate RuleTarget = "private" 86 | ) 87 | 88 | type ConstRule string 89 | 90 | const ( 91 | ConstCGOAlias ConstRule = "cgo" 92 | ConstExpand ConstRule = "expand" 93 | ConstEval ConstRule = "eval" 94 | ) 95 | 96 | type ConstScope string 97 | 98 | const ( 99 | ConstEnum ConstScope = "enum" 100 | ConstDecl ConstScope = "decl" 101 | ConstDefines ConstScope = "defines" 102 | ) 103 | 104 | type Tip string 105 | 106 | const ( 107 | TipPtrSRef Tip = "sref" 108 | TipPtrRef Tip = "ref" 109 | TipPtrArr Tip = "arr" 110 | TipPtrInst Tip = "inst" 111 | TipMemRaw Tip = "raw" 112 | TipTypeNamed Tip = "named" 113 | TipTypePlain Tip = "plain" 114 | TipTypeString Tip = "string" 115 | TipTypeUnsigned Tip = "unsigned" 116 | NoTip Tip = "" 117 | ) 118 | 119 | type TipKind string 120 | 121 | const ( 122 | TipKindUnknown TipKind = "unknown" 123 | TipKindPtr TipKind = "ptr" 124 | TipKindType TipKind = "type" 125 | TipKindMem TipKind = "mem" 126 | ) 127 | 128 | func (t Tip) Kind() TipKind { 129 | switch t { 130 | case TipPtrArr, TipPtrRef, TipPtrSRef, TipPtrInst: 131 | return TipKindPtr 132 | case TipTypePlain, TipTypeNamed, TipTypeString, TipTypeUnsigned: 133 | return TipKindType 134 | case TipMemRaw: 135 | return TipKindMem 136 | default: 137 | return TipKindUnknown 138 | } 139 | } 140 | 141 | func (t Tip) IsValid() bool { 142 | switch t { 143 | case TipPtrArr, TipPtrRef, TipPtrSRef, TipPtrInst: 144 | return true 145 | case TipTypePlain, TipTypeNamed, TipTypeString: 146 | return true 147 | case TipMemRaw: 148 | return true 149 | case TipTypeUnsigned: 150 | return true 151 | default: 152 | return false 153 | } 154 | } 155 | 156 | type TipSpec struct { 157 | Target string 158 | Tips Tips 159 | Self Tip 160 | Default Tip 161 | } 162 | 163 | type TipScope string 164 | 165 | const ( 166 | TipScopeAny TipScope = "any" 167 | TipScopeStruct TipScope = "struct" 168 | TipScopeType TipScope = "type" 169 | TipScopeEnum TipScope = "enum" 170 | TipScopeFunction TipScope = "function" 171 | ) 172 | 173 | type Tips []Tip 174 | 175 | var builtinRules = map[string]RuleSpec{ 176 | "snakecase": {Action: ActionReplace, From: "_([^_]+)", To: "$1", Transform: TransformTitle}, 177 | "doc.file": {Action: ActionDocument, To: "$path:$line"}, 178 | "doc.google": {Action: ActionDocument, To: "https://google.com/search?q=$file+$name"}, 179 | } 180 | -------------------------------------------------------------------------------- /translator/type_mapping.go: -------------------------------------------------------------------------------- 1 | package translator 2 | 3 | type CTypeMap map[CTypeSpec]GoTypeSpec 4 | type GoTypeMap map[string]GoTypeSpec 5 | 6 | var ( 7 | BoolSpec = GoTypeSpec{Base: "bool"} 8 | IntSpec = GoTypeSpec{Base: "int"} 9 | UintSpec = GoTypeSpec{Base: "int", Unsigned: true} 10 | Int8Spec = GoTypeSpec{Base: "int", Bits: 8} 11 | Uint8Spec = GoTypeSpec{Base: "int", Bits: 8, Unsigned: true} 12 | Int16Spec = GoTypeSpec{Base: "int", Bits: 16} 13 | Uint16Spec = GoTypeSpec{Base: "int", Bits: 16, Unsigned: true} 14 | Int32Spec = GoTypeSpec{Base: "int", Bits: 32} 15 | Uint32Spec = GoTypeSpec{Base: "int", Bits: 32, Unsigned: true} 16 | Int64Spec = GoTypeSpec{Base: "int", Bits: 64} 17 | Uint64Spec = GoTypeSpec{Base: "int", Bits: 64, Unsigned: true} 18 | RuneSpec = GoTypeSpec{Base: "rune"} 19 | ByteSpec = GoTypeSpec{Base: "byte"} 20 | UByteSpec = GoTypeSpec{Base: "byte", Unsigned: true} 21 | StringSpec = GoTypeSpec{Base: "string"} 22 | UStringSpec = GoTypeSpec{Base: "string", Unsigned: true} 23 | Float32Spec = GoTypeSpec{Base: "float", Bits: 32} 24 | Float64Spec = GoTypeSpec{Base: "float", Bits: 64} 25 | Complex64Spec = GoTypeSpec{Base: "complex", Bits: 64} 26 | Complex128Spec = GoTypeSpec{Base: "complex", Bits: 128} 27 | UnsafePointerSpec = GoTypeSpec{Base: "unsafe.Pointer", Pointers: 1} 28 | VoidSpec = GoTypeSpec{Base: "byte", OuterArr: "[0]"} 29 | // 30 | InterfaceSliceSpec = GoTypeSpec{Base: "[]interface{}"} 31 | ) 32 | 33 | func getCTypeMap(constCharIsString, constUCharIsString, longIs64Bit bool) CTypeMap { 34 | config := make(CTypeMap, len(builtinCTypeMap)+2) 35 | for k, v := range builtinCTypeMap { 36 | config[k] = v 37 | } 38 | 39 | if constCharIsString { 40 | // const char* -> string 41 | config[CTypeSpec{Base: "char", Const: true, Pointers: 1}] = StringSpec 42 | } 43 | 44 | if constUCharIsString { 45 | // const unsigned char* -> string 46 | config[CTypeSpec{Base: "char", Const: true, Unsigned: true, Pointers: 1}] = UStringSpec 47 | } 48 | 49 | if longIs64Bit { 50 | config[CTypeSpec{Base: "long", Unsigned: true}] = Uint64Spec 51 | config[CTypeSpec{Base: "long", Signed: true}] = Int64Spec 52 | config[CTypeSpec{Base: "int", Long: true, Unsigned: true}] = Uint64Spec 53 | config[CTypeSpec{Base: "int", Long: true}] = Int64Spec 54 | } 55 | 56 | return config 57 | } 58 | 59 | // https://en.wikipedia.org/wiki/C_data_types 60 | var builtinCTypeMap = CTypeMap{ 61 | // char -> byte 62 | CTypeSpec{Base: "char"}: ByteSpec, 63 | // signed char -> int8 64 | CTypeSpec{Base: "char", Signed: true}: Int8Spec, 65 | // unsigned char -> unsigned byte 66 | CTypeSpec{Base: "char", Unsigned: true}: ByteSpec, 67 | // short -> int16 68 | CTypeSpec{Base: "short"}: Int16Spec, 69 | // unsigned short -> uint16 70 | CTypeSpec{Base: "short", Unsigned: true}: Uint16Spec, 71 | // long -> int 72 | CTypeSpec{Base: "long"}: Int32Spec, 73 | // unsigned long -> uint 74 | CTypeSpec{Base: "long", Unsigned: true}: Uint32Spec, 75 | // signed long -> int 76 | CTypeSpec{Base: "long", Signed: true}: Int32Spec, 77 | // long long -> int64 78 | CTypeSpec{Base: "long", Long: true}: Int64Spec, 79 | // unsigned long long -> uint64 80 | CTypeSpec{Base: "long", Long: true, Unsigned: true}: Uint64Spec, 81 | // signed long long -> int64 82 | CTypeSpec{Base: "long", Long: true, Signed: true}: Int64Spec, 83 | // int -> int32 84 | CTypeSpec{Base: "int"}: Int32Spec, 85 | // unsigned int -> uint32 86 | CTypeSpec{Base: "int", Unsigned: true}: Uint32Spec, 87 | // signed int -> int32 88 | CTypeSpec{Base: "int", Signed: true}: Int32Spec, 89 | // short int -> int16 90 | CTypeSpec{Base: "int", Short: true}: Int16Spec, 91 | // unsigned short int -> uint16 92 | CTypeSpec{Base: "int", Short: true, Unsigned: true}: Uint16Spec, 93 | // signed short int -> uint16 94 | CTypeSpec{Base: "int", Short: true, Signed: true}: Int16Spec, 95 | // long int -> int 96 | CTypeSpec{Base: "int", Long: true}: Int32Spec, 97 | // unsigned long int -> uint 98 | CTypeSpec{Base: "int", Long: true, Unsigned: true}: Uint32Spec, 99 | // signed long int -> uint 100 | CTypeSpec{Base: "int", Long: true, Signed: true}: Int32Spec, 101 | // float -> float32 102 | CTypeSpec{Base: "float"}: Float32Spec, 103 | // double -> float64 104 | CTypeSpec{Base: "double"}: Float64Spec, 105 | // long double -> float64 106 | CTypeSpec{Base: "double", Long: true}: Float64Spec, 107 | // complex float -> complex164 108 | CTypeSpec{Base: "float", Complex: true}: Complex64Spec, 109 | // complex double -> complex128 110 | CTypeSpec{Base: "double", Complex: true}: Complex128Spec, 111 | // long complex double -> complex128 112 | CTypeSpec{Base: "double", Long: true, Complex: true}: Complex128Spec, 113 | // void* -> unsafe.Pointer 114 | CTypeSpec{Base: "void*"}: UnsafePointerSpec, 115 | // void* -> unsafe.Pointer 116 | CTypeSpec{Base: "void", Pointers: 1}: UnsafePointerSpec, 117 | // void -> [0]byte 118 | CTypeSpec{Base: "void"}: VoidSpec, 119 | // _Bool -> bool 120 | CTypeSpec{Base: "_Bool"}: BoolSpec, 121 | } 122 | -------------------------------------------------------------------------------- /generator/gen_typedef.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "path/filepath" 7 | 8 | tl "github.com/xlab/c-for-go/translator" 9 | ) 10 | 11 | func (gen *Generator) writeTypeTypedef(wr io.Writer, decl *tl.CDecl, seenNames map[string]bool) { 12 | goSpec := gen.tr.TranslateSpec(decl.Spec) 13 | goTypeName := gen.tr.TransformName(tl.TargetType, decl.Name) 14 | if seenNames[string(goTypeName)] { 15 | return 16 | } else { 17 | seenNames[string(goTypeName)] = true 18 | } 19 | fmt.Fprintf(wr, "// %s type as declared in %s\n", goTypeName, 20 | filepath.ToSlash(gen.tr.SrcLocation(tl.TargetType, decl.Name, decl.Position))) 21 | fmt.Fprintf(wr, "type %s %s", goTypeName, goSpec.UnderlyingString()) 22 | writeSpace(wr, 1) 23 | } 24 | 25 | func (gen *Generator) writeEnumTypedef(wr io.Writer, decl *tl.CDecl) { 26 | cName, ok := getName(decl) 27 | if !ok { 28 | return 29 | } 30 | goName := gen.tr.TransformName(tl.TargetType, cName) 31 | typeRef := gen.tr.TranslateSpec(decl.Spec).UnderlyingString() 32 | if typeName := string(goName); typeName != typeRef { 33 | fmt.Fprintf(wr, "// %s as declared in %s\n", goName, 34 | filepath.ToSlash(gen.tr.SrcLocation(tl.TargetConst, cName, decl.Position))) 35 | fmt.Fprintf(wr, "type %s %s", goName, typeRef) 36 | writeSpace(wr, 1) 37 | } 38 | } 39 | 40 | func (gen *Generator) writeFunctionTypedef(wr io.Writer, decl *tl.CDecl, seenNames map[string]bool) { 41 | var returnRef string 42 | funcSpec := decl.Spec.Copy().(*tl.CFunctionSpec) 43 | funcSpec.Pointers = 0 // function pointers not supported here 44 | 45 | if funcSpec.Return != nil { 46 | // defaults to ref for the return values 47 | ptrTip := tl.TipPtrRef 48 | if ptrTipRx, ok := gen.tr.PtrTipRx(tl.TipScopeFunction, decl.Name); ok { 49 | if tip := ptrTipRx.Self(); tip.IsValid() { 50 | ptrTip = tip 51 | } 52 | } 53 | typeTip := tl.TipTypeNamed 54 | if typeTipRx, ok := gen.tr.TypeTipRx(tl.TipScopeFunction, decl.Name); ok { 55 | if tip := typeTipRx.Self(); tip.IsValid() { 56 | typeTip = tip 57 | } 58 | } 59 | returnRef = gen.tr.TranslateSpec(funcSpec.Return, ptrTip, typeTip).String() 60 | } 61 | 62 | ptrTipRx, _ := gen.tr.PtrTipRx(tl.TipScopeFunction, decl.Name) 63 | typeTipRx, _ := gen.tr.TypeTipRx(tl.TipScopeFunction, decl.Name) 64 | goFuncName := gen.tr.TransformName(tl.TargetType, decl.Name) 65 | if seenNames[string(goFuncName)] { 66 | return 67 | } else { 68 | seenNames[string(goFuncName)] = true 69 | } 70 | goSpec := gen.tr.TranslateSpec(funcSpec, ptrTipRx.Self(), typeTipRx.Self()) 71 | goSpec.Raw = "" // not used in func typedef 72 | fmt.Fprintf(wr, "// %s type as declared in %s\n", goFuncName, 73 | filepath.ToSlash(gen.tr.SrcLocation(tl.TargetFunction, decl.Name, decl.Position))) 74 | fmt.Fprintf(wr, "type %s %s", goFuncName, goSpec) 75 | gen.writeFunctionParams(wr, decl.Name, decl.Spec) 76 | if len(returnRef) > 0 { 77 | fmt.Fprintf(wr, " %s", returnRef) 78 | } 79 | for _, helper := range gen.getCallbackHelpers(string(goFuncName), decl.Name, decl.Spec) { 80 | gen.submitHelper(helper) 81 | } 82 | writeSpace(wr, 1) 83 | } 84 | 85 | func getName(decl *tl.CDecl) (string, bool) { 86 | if len(decl.Name) > 0 { 87 | return decl.Name, true 88 | } 89 | if base := decl.Spec.GetBase(); len(base) > 0 { 90 | return base, true 91 | } 92 | return "", false 93 | } 94 | 95 | func (gen *Generator) writeStructTypedef(wr io.Writer, decl *tl.CDecl, raw bool, seenNames map[string]bool) { 96 | cName, ok := getName(decl) 97 | if !ok { 98 | return 99 | } 100 | goName := gen.tr.TransformName(tl.TargetType, cName) 101 | if seenNames[string(goName)] { 102 | return 103 | } else { 104 | seenNames[string(goName)] = true 105 | } 106 | if raw || !decl.Spec.IsComplete() { 107 | // opaque struct 108 | fmt.Fprintf(wr, "// %s as declared in %s\n", goName, 109 | filepath.ToSlash(gen.tr.SrcLocation(tl.TargetType, cName, decl.Position))) 110 | fmt.Fprintf(wr, "type %s C.%s", goName, decl.Spec.CGoName()) 111 | writeSpace(wr, 1) 112 | for _, helper := range gen.getRawStructHelpers(goName, cName, decl.Spec) { 113 | gen.submitHelper(helper) 114 | } 115 | return 116 | } 117 | 118 | fmt.Fprintf(wr, "// %s as declared in %s\n", goName, 119 | filepath.ToSlash(gen.tr.SrcLocation(tl.TargetType, cName, decl.Position))) 120 | fmt.Fprintf(wr, "type %s struct {", goName) 121 | writeSpace(wr, 1) 122 | gen.submitHelper(cgoAllocMap) 123 | gen.writeStructMembers(wr, cName, decl.Spec) 124 | writeEndStruct(wr) 125 | writeSpace(wr, 1) 126 | for _, helper := range gen.getStructHelpers(goName, cName, decl.Spec) { 127 | gen.submitHelper(helper) 128 | } 129 | } 130 | 131 | func (gen *Generator) writeUnionTypedef(wr io.Writer, decl *tl.CDecl) { 132 | cName, ok := getName(decl) 133 | if !ok { 134 | return 135 | } 136 | goName := gen.tr.TransformName(tl.TargetType, cName) 137 | typeRef := gen.tr.TranslateSpec(decl.Spec).UnderlyingString() 138 | 139 | if typeName := string(goName); typeName != typeRef { 140 | fmt.Fprintf(wr, "// %s as declared in %s\n", goName, 141 | filepath.ToSlash(gen.tr.SrcLocation(tl.TargetType, cName, decl.Position))) 142 | fmt.Fprintf(wr, "const sizeof%s = unsafe.Sizeof(C.%s{})\n", goName, decl.Spec.CGoName()) 143 | fmt.Fprintf(wr, "type %s [sizeof%s]byte\n", goName, goName) 144 | writeSpace(wr, 1) 145 | return 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /generator/gen_declares.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "path/filepath" 7 | 8 | tl "github.com/xlab/c-for-go/translator" 9 | ) 10 | 11 | func checkName(name []byte) []byte { 12 | if len(name) > 0 { 13 | return name 14 | } 15 | return skipName 16 | } 17 | 18 | func (gen *Generator) writeTypeDeclaration(wr io.Writer, decl *tl.CDecl, 19 | ptrTip, typeTip tl.Tip, public bool, seenNames map[string]bool) { 20 | 21 | cName, _ := getName(decl) 22 | goName := checkName(gen.tr.TransformName(tl.TargetType, cName, public)) 23 | goSpec := gen.tr.TranslateSpec(decl.Spec, ptrTip, typeTip) 24 | fmt.Fprintf(wr, "%s %s", goName, goSpec) 25 | } 26 | 27 | func (gen *Generator) writeArgType(wr io.Writer, decl *tl.CDecl, 28 | ptrTip, typeTip tl.Tip, public bool) { 29 | 30 | cName, _ := getName(decl) 31 | goName := checkName(gen.tr.TransformName(tl.TargetType, cName, public)) 32 | goSpec := gen.tr.TranslateSpec(decl.Spec, ptrTip, typeTip) 33 | if len(goSpec.OuterArr) > 0 { 34 | fmt.Fprintf(wr, "%s *%s", goName, goSpec) 35 | } else { 36 | fmt.Fprintf(wr, "%s %s", goName, goSpec) 37 | } 38 | } 39 | 40 | func (gen *Generator) writeEnumDeclaration(wr io.Writer, decl *tl.CDecl, 41 | ptrTip, typeTip tl.Tip, public bool) { 42 | cName, _ := getName(decl) 43 | goName := checkName(gen.tr.TransformName(tl.TargetType, cName, public)) 44 | typeRef := gen.tr.TranslateSpec(decl.Spec, ptrTip, typeTip).String() 45 | fmt.Fprintf(wr, "%s %s", goName, typeRef) 46 | writeSpace(wr, 1) 47 | } 48 | 49 | func (gen *Generator) writeFunctionAsArg(wr io.Writer, decl *tl.CDecl, 50 | ptrTip, typeTip tl.Tip, public bool) { 51 | 52 | var returnRef string 53 | cName, _ := getName(decl) 54 | goName := checkName(gen.tr.TransformName(tl.TargetFunction, cName, public)) 55 | spec := decl.Spec.(*tl.CFunctionSpec) 56 | if len(spec.Typedef) > 0 { 57 | funcName := checkName(gen.tr.TransformName(tl.TargetType, spec.Typedef, true)) 58 | fmt.Fprintf(wr, "%s %s", goName, funcName) 59 | return 60 | } 61 | if spec.Return != nil { 62 | returnRef = gen.tr.TranslateSpec(spec.Return, ptrTip, typeTip).String() 63 | } 64 | goSpec := gen.tr.TranslateSpec(decl.Spec, ptrTip, typeTip) 65 | fmt.Fprintf(wr, "%s %s", goName, goSpec) 66 | gen.writeFunctionParams(wr, cName, decl.Spec) 67 | if len(returnRef) > 0 { 68 | fmt.Fprintf(wr, " %s", returnRef) 69 | } 70 | } 71 | 72 | func (gen *Generator) writeFunctionDeclaration(wr io.Writer, decl *tl.CDecl, 73 | ptrTip, typeTip tl.Tip, public bool) { 74 | 75 | var returnRef string 76 | spec := decl.Spec.(*tl.CFunctionSpec) 77 | if spec.Return != nil { 78 | returnRef = gen.tr.TranslateSpec(spec.Return, ptrTip, typeTip).String() 79 | } 80 | cName, _ := getName(decl) 81 | goName := checkName(gen.tr.TransformName(tl.TargetFunction, cName, public)) 82 | if returnRef == string(goName) { 83 | goName = gen.tr.TransformName(tl.TargetFunction, "new_"+cName, public) 84 | } 85 | fmt.Fprintf(wr, "// %s function as declared in %s\n", goName, 86 | filepath.ToSlash(gen.tr.SrcLocation(tl.TargetFunction, decl.Name, decl.Position))) 87 | fmt.Fprintf(wr, "func") 88 | gen.writeInstanceObjectParam(wr, cName, decl.Spec) 89 | fmt.Fprintf(wr, " %s", goName) 90 | gen.writeFunctionParams(wr, cName, decl.Spec) 91 | if len(returnRef) > 0 { 92 | fmt.Fprintf(wr, " %s", returnRef) 93 | } 94 | gen.writeFunctionBody(wr, decl) 95 | writeSpace(wr, 1) 96 | } 97 | 98 | func (gen *Generator) writeArgStruct(wr io.Writer, decl *tl.CDecl, 99 | ptrTip, typeTip tl.Tip, public bool) { 100 | 101 | cName, _ := getName(decl) 102 | goName := checkName(gen.tr.TransformName(tl.TargetType, cName, public)) 103 | if tag := decl.Spec.GetBase(); len(tag) > 0 { 104 | goSpec := gen.tr.TranslateSpec(decl.Spec, ptrTip, typeTip) 105 | fmt.Fprintf(wr, "%s %s", goName, goSpec) 106 | return 107 | } 108 | if !decl.Spec.IsComplete() { 109 | return 110 | } 111 | 112 | fmt.Fprintf(wr, "%s struct {", goName) 113 | writeSpace(wr, 1) 114 | gen.submitHelper(cgoAllocMap) 115 | gen.writeStructMembers(wr, cName, decl.Spec) 116 | writeEndStruct(wr) 117 | } 118 | 119 | func (gen *Generator) writeArgUnion(wr io.Writer, decl *tl.CDecl, 120 | ptrTip, typeTip tl.Tip, public bool) { 121 | 122 | cName, _ := getName(decl) 123 | goName := checkName(gen.tr.TransformName(tl.TargetType, cName, public)) 124 | if tag := decl.Spec.GetBase(); len(tag) > 0 { 125 | goSpec := gen.tr.TranslateSpec(decl.Spec, ptrTip, typeTip) 126 | fmt.Fprintf(wr, "%s %s", goName, goSpec) 127 | } 128 | } 129 | 130 | func (gen *Generator) writeStructDeclaration(wr io.Writer, decl *tl.CDecl, 131 | ptrTip, typeTip tl.Tip, public bool) { 132 | 133 | cName, _ := getName(decl) 134 | goName := checkName(gen.tr.TransformName(tl.TargetType, cName, public)) 135 | if tag := decl.Spec.GetBase(); len(tag) > 0 { 136 | goSpec := gen.tr.TranslateSpec(decl.Spec, ptrTip, typeTip) 137 | if string(goName) != goSpec.String() { 138 | fmt.Fprintf(wr, "var %s %s", goName, goSpec) 139 | } 140 | return 141 | } 142 | if !decl.Spec.IsComplete() { 143 | return 144 | } 145 | 146 | fmt.Fprintf(wr, "var %s struct {", goName) 147 | writeSpace(wr, 1) 148 | gen.submitHelper(cgoAllocMap) 149 | gen.writeStructMembers(wr, cName, decl.Spec) 150 | writeEndStruct(wr) 151 | writeSpace(wr, 1) 152 | } 153 | 154 | func (gen *Generator) writeUnionDeclaration(wr io.Writer, decl *tl.CDecl, 155 | ptrTip, typeTip tl.Tip, public bool) { 156 | 157 | cName, _ := getName(decl) 158 | goName := checkName(gen.tr.TransformName(tl.TargetType, cName, public)) 159 | if tag := decl.Spec.GetBase(); len(tag) > 0 { 160 | goSpec := gen.tr.TranslateSpec(decl.Spec, ptrTip, typeTip) 161 | if string(goName) != goSpec.String() { 162 | fmt.Fprintf(wr, "var %s %s", goName, goSpec) 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /translator/model_go_type.go: -------------------------------------------------------------------------------- 1 | package translator 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | type GoTypeSpec struct { 10 | Slices uint8 11 | Pointers uint8 12 | InnerArr ArraySpec 13 | OuterArr ArraySpec 14 | Unsigned bool 15 | Kind CTypeKind 16 | Base string 17 | Raw string 18 | Bits uint16 19 | } 20 | 21 | func (spec *GoTypeSpec) splitPointers(ptrTip Tip, n uint8) { 22 | if n == 0 { 23 | return 24 | } 25 | switch ptrTip { 26 | case TipPtrRef: 27 | spec.Slices = spec.Slices + n - 1 28 | spec.Pointers++ 29 | case TipPtrSRef, TipPtrInst: 30 | spec.Pointers += n 31 | case TipPtrArr: 32 | spec.Slices += n 33 | default: // TipPtrArr 34 | spec.Slices += n 35 | } 36 | } 37 | 38 | func (spec GoTypeSpec) IsPlainKind() bool { 39 | switch spec.Kind { 40 | case PlainTypeKind, OpaqueStructKind, EnumKind, UnionKind: 41 | return true 42 | } 43 | return false 44 | } 45 | 46 | func (spec GoTypeSpec) IsPlain() bool { 47 | switch spec.Base { 48 | case "int", "byte", "rune", "float", "unsafe.Pointer", "bool": 49 | return true 50 | case "string": 51 | return false 52 | } 53 | return false 54 | } 55 | 56 | func (spec GoTypeSpec) IsGoString() bool { 57 | if spec.Base == "string" && 58 | spec.Slices == 0 && 59 | len(spec.InnerArr) == 0 && 60 | len(spec.OuterArr) == 0 { 61 | return true 62 | } 63 | return false 64 | } 65 | 66 | func (spec *GoTypeSpec) PlainType() string { 67 | if len(spec.Raw) > 0 { 68 | return spec.Raw 69 | } 70 | buf := new(bytes.Buffer) 71 | if spec.Unsigned { 72 | switch spec.Base { 73 | case "char", "short", "long", "int": 74 | buf.WriteString("u") 75 | } 76 | } 77 | buf.WriteString(spec.Base) 78 | if spec.Bits > 0 { 79 | fmt.Fprintf(buf, "%d", int(spec.Bits)) 80 | } 81 | return buf.String() 82 | } 83 | 84 | func (spec GoTypeSpec) String() string { 85 | buf := new(bytes.Buffer) 86 | buf.WriteString(slcs(spec.Slices)) 87 | buf.WriteString(arrs(spec.OuterArr)) 88 | 89 | var unsafePointer uint8 90 | if spec.Base == "unsafe.Pointer" && len(spec.Raw) == 0 { 91 | unsafePointer = 1 92 | } 93 | if spec.Pointers > 0 { 94 | buf.WriteString(ptrs(spec.Pointers - unsafePointer)) 95 | } 96 | buf.WriteString(arrs(spec.InnerArr)) 97 | 98 | if len(spec.Raw) > 0 { 99 | buf.WriteString(spec.Raw) 100 | return buf.String() 101 | } 102 | if spec.Unsigned { 103 | switch spec.Base { 104 | case "char", "short", "long", "int": 105 | buf.WriteString("u") 106 | } 107 | } 108 | buf.WriteString(spec.Base) 109 | if spec.Bits > 0 { 110 | fmt.Fprintf(buf, "%d", int(spec.Bits)) 111 | } 112 | return buf.String() 113 | } 114 | 115 | func (spec GoTypeSpec) UnderlyingString() string { 116 | buf := new(bytes.Buffer) 117 | buf.WriteString(slcs(spec.Slices)) 118 | buf.WriteString(arrs(spec.OuterArr)) 119 | 120 | var unsafePointer uint8 121 | if spec.Base == "unsafe.Pointer" { 122 | unsafePointer = 1 123 | } 124 | if spec.Pointers > 0 { 125 | buf.WriteString(ptrs(spec.Pointers - unsafePointer)) 126 | } 127 | buf.WriteString(arrs(spec.InnerArr)) 128 | 129 | if spec.Unsigned { 130 | buf.WriteString("u") 131 | } 132 | buf.WriteString(spec.Base) 133 | if spec.Bits > 0 { 134 | fmt.Fprintf(buf, "%d", int(spec.Bits)) 135 | } 136 | 137 | return buf.String() 138 | } 139 | 140 | type CGoSpec struct { 141 | Base string 142 | Pointers uint8 143 | OuterArr ArraySpec 144 | InnerArr ArraySpec 145 | } 146 | 147 | func (spec CGoSpec) String() string { 148 | buf := new(bytes.Buffer) 149 | buf.WriteString(arrs(spec.OuterArr)) 150 | buf.WriteString(ptrs(spec.Pointers)) 151 | buf.WriteString(arrs(spec.InnerArr)) 152 | buf.WriteString(spec.Base) 153 | return buf.String() 154 | } 155 | 156 | func (spec *CGoSpec) PointersAtLevel(level uint8) uint8 { 157 | if int(level) > len(spec.OuterArr) { 158 | if delta := int(spec.Pointers) + len(spec.OuterArr.Sizes()) - int(level); delta > 0 { 159 | return uint8(delta) 160 | } 161 | } 162 | if level <= spec.Pointers { 163 | return spec.Pointers - level 164 | } 165 | return 0 166 | } 167 | 168 | func (spec *CGoSpec) AtLevel(level uint8) string { 169 | buf := new(bytes.Buffer) 170 | outerArrSizes := spec.OuterArr.Sizes() 171 | for i, size := range outerArrSizes { 172 | if i < int(level) { 173 | continue 174 | } else if i == 0 { 175 | buf.WriteRune('*') 176 | continue 177 | } 178 | if len(size.Str) > 0 { 179 | fmt.Fprintf(buf, "[%s]", size.Str) 180 | } else { 181 | fmt.Fprintf(buf, "[%d]", size.N) 182 | } 183 | } 184 | if int(level) > len(outerArrSizes) { 185 | if delta := int(spec.Pointers) + len(outerArrSizes) - int(level); delta > 0 { 186 | buf.WriteString(ptrs(uint8(delta))) 187 | } 188 | } else { 189 | buf.WriteString(ptrs(spec.Pointers)) 190 | } 191 | // drop inner arrays at levels 192 | // buf.WriteString(arrs(spec.InnerArr)) 193 | buf.WriteString(spec.Base) 194 | 195 | return buf.String() 196 | } 197 | 198 | func (spec *CGoSpec) SpecAtLevel(level uint8) CGoSpec { 199 | buf := CGoSpec{ 200 | // drop inner arrays at levels 201 | // InnerArr: spec.InnerArr, 202 | Base: spec.Base, 203 | } 204 | for i, size := range spec.OuterArr.Sizes() { 205 | if i < int(level) { 206 | continue 207 | } else if i == 0 { 208 | buf.Pointers = 1 209 | continue 210 | } 211 | buf.OuterArr.AddSized(size.N) 212 | } 213 | if int(level) > len(spec.OuterArr) { 214 | if delta := int(spec.Pointers) + len(spec.OuterArr.Sizes()) - int(level); delta > 0 { 215 | buf.Pointers += uint8(delta) 216 | } 217 | } else { 218 | buf.Pointers += spec.Pointers 219 | } 220 | return buf 221 | } 222 | 223 | func ptrs(n uint8) string { 224 | return strings.Repeat("*", int(n)) 225 | } 226 | 227 | func slcs(slcs uint8) string { 228 | return strings.Repeat("[]", int(slcs)) 229 | } 230 | 231 | func arrs(spec ArraySpec) string { 232 | return string(spec) 233 | } 234 | -------------------------------------------------------------------------------- /generator/gen_files.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | func genLabel(noTimestamps bool) string { 11 | // These headers comply with a recognized format for generated code, so that 12 | // static analysis tools may recognize the code as generated and skip 13 | // processing of these files. 14 | // 15 | // For more information, see: 16 | // https://github.com/golang/go/issues/13560#issuecomment-276866852. 17 | 18 | if noTimestamps { 19 | tpl := "WARNING: This file has automatically been generated \nCode generated by https://git.io/c-for-go. DO NOT EDIT." 20 | return tpl 21 | } 22 | tpl := "WARNING: This file has automatically been generated on %s.\nCode generated by https://git.io/c-for-go. DO NOT EDIT." 23 | return fmt.Sprintf(tpl, time.Now().Format(time.RFC1123)) 24 | } 25 | 26 | func (gen *Generator) WriteDoc(wr io.Writer) bool { 27 | var hasDoc bool 28 | if len(gen.cfg.PackageLicense) > 0 { 29 | writeTextBlock(wr, gen.cfg.PackageLicense) 30 | writeSpace(wr, 1) 31 | hasDoc = true 32 | } 33 | writeTextBlock(wr, genLabel(gen.noTimestamps)) 34 | writeSpace(wr, 1) 35 | if len(gen.cfg.PackageDescription) > 0 { 36 | writeLongTextBlock(wr, gen.cfg.PackageDescription) 37 | hasDoc = true 38 | } 39 | writePackageName(wr, gen.pkg) 40 | writeSpace(wr, 1) 41 | return hasDoc 42 | } 43 | 44 | func (gen *Generator) WriteIncludes(wr io.Writer) { 45 | writeStartComment(wr) 46 | writePkgConfig(wr, gen.cfg.PkgConfigOpts) 47 | for _, group := range gen.cfg.FlagGroups { 48 | writeFlagGroup(wr, group) 49 | } 50 | for _, path := range gen.cfg.SysIncludes { 51 | writeSysInclude(wr, path) 52 | } 53 | for _, path := range gen.cfg.Includes { 54 | writeInclude(wr, path) 55 | } 56 | writeCStdIncludes(wr, gen.cfg.SysIncludes) 57 | fmt.Fprintln(wr, `#include "cgo_helpers.h"`) 58 | writeEndComment(wr) 59 | fmt.Fprintln(wr, `import "C"`) 60 | writeSpace(wr, 1) 61 | } 62 | 63 | func hasLib(paths []string, lib string) bool { 64 | for i := range paths { 65 | if paths[i] == lib { 66 | return true 67 | } 68 | } 69 | return false 70 | } 71 | 72 | func (gen *Generator) writeGoHelpersHeader(wr io.Writer) { 73 | writeTextBlock(wr, gen.cfg.PackageLicense) 74 | writeSpace(wr, 1) 75 | writeTextBlock(wr, genLabel(gen.noTimestamps)) 76 | writeSpace(wr, 1) 77 | writePackageName(wr, gen.pkg) 78 | writeSpace(wr, 1) 79 | gen.WriteIncludes(wr) 80 | } 81 | 82 | func (gen *Generator) writeCHHelpersHeader(wr io.Writer) { 83 | if len(gen.cfg.PackageLicense) > 0 { 84 | writeTextBlock(wr, gen.cfg.PackageLicense) 85 | writeSpace(wr, 1) 86 | } 87 | writeTextBlock(wr, genLabel(gen.noTimestamps)) 88 | writeSpace(wr, 1) 89 | for _, path := range gen.cfg.SysIncludes { 90 | writeSysInclude(wr, path) 91 | } 92 | for _, path := range gen.cfg.Includes { 93 | writeInclude(wr, path) 94 | } 95 | writeCStdIncludes(wr, gen.cfg.SysIncludes) 96 | writeCHPragma(wr) 97 | writeSpace(wr, 1) 98 | } 99 | 100 | func (gen *Generator) writeCCHelpersHeader(wr io.Writer) { 101 | if len(gen.cfg.PackageLicense) > 0 { 102 | writeTextBlock(wr, gen.cfg.PackageLicense) 103 | writeSpace(wr, 1) 104 | } 105 | writeTextBlock(wr, genLabel(gen.noTimestamps)) 106 | writeSpace(wr, 1) 107 | writeCGOIncludes(wr) 108 | writeSpace(wr, 1) 109 | } 110 | 111 | func writeCGOIncludes(wr io.Writer) { 112 | fmt.Fprintln(wr, `#include "_cgo_export.h"`) 113 | fmt.Fprintln(wr, `#include "cgo_helpers.h"`) 114 | } 115 | 116 | func writeCHPragma(wr io.Writer) { 117 | fmt.Fprintln(wr, "#pragma once") 118 | } 119 | 120 | func writeCStdIncludes(wr io.Writer, sysIncludes []string) { 121 | if !hasLib(sysIncludes, "stdlib.h") { 122 | fmt.Fprintln(wr, "#include ") 123 | } 124 | // if !hasLib(sysIncludes, "stdbool.h") { 125 | // fmt.Fprintln(wr, "#include ") 126 | // } 127 | } 128 | 129 | func (gen *Generator) WritePackageHeader(wr io.Writer) { 130 | writeTextBlock(wr, gen.cfg.PackageLicense) 131 | writeSpace(wr, 1) 132 | writeTextBlock(wr, genLabel(gen.noTimestamps)) 133 | writeSpace(wr, 1) 134 | writePackageName(wr, gen.pkg) 135 | writeSpace(wr, 1) 136 | } 137 | 138 | func writeFlagGroup(wr io.Writer, group TraitFlagGroup) { 139 | if len(group.Name) == 0 { 140 | return 141 | } 142 | if len(group.Flags) == 0 { 143 | return 144 | } 145 | if len(group.Traits) == 0 { 146 | fmt.Fprintf(wr, "#cgo %s: %s\n", group.Name, strings.Join(group.Flags, " ")) 147 | return 148 | } 149 | traits := strings.Join(group.Traits, " ") 150 | fmt.Fprintf(wr, "#cgo %s %s: %s\n", traits, group.Name, strings.Join(group.Flags, " ")) 151 | } 152 | 153 | func writeSysInclude(wr io.Writer, path string) { 154 | fmt.Fprintf(wr, "#include <%s>\n", path) 155 | } 156 | 157 | func writeInclude(wr io.Writer, path string) { 158 | fmt.Fprintf(wr, "#include \"%s\"\n", path) 159 | } 160 | 161 | func writePkgConfig(wr io.Writer, opts []string) { 162 | if len(opts) == 0 { 163 | return 164 | } 165 | fmt.Fprintf(wr, "#cgo pkg-config: %s\n", strings.Join(opts, " ")) 166 | } 167 | 168 | func writeStartComment(wr io.Writer) { 169 | fmt.Fprintln(wr, "/*") 170 | } 171 | 172 | func writeEndComment(wr io.Writer) { 173 | fmt.Fprintln(wr, "*/") 174 | } 175 | 176 | func writePackageName(wr io.Writer, name string) { 177 | if len(name) == 0 { 178 | name = "main" 179 | } 180 | fmt.Fprintf(wr, "package %s\n", name) 181 | } 182 | 183 | func writeLongTextBlock(wr io.Writer, text string) { 184 | if len(text) == 0 { 185 | return 186 | } 187 | writeStartComment(wr) 188 | fmt.Fprint(wr, text) 189 | writeSpace(wr, 1) 190 | writeEndComment(wr) 191 | } 192 | 193 | func writeTextBlock(wr io.Writer, text string) { 194 | if len(text) == 0 { 195 | return 196 | } 197 | lines := strings.Split(text, "\n") 198 | for _, line := range lines { 199 | fmt.Fprintf(wr, "// %s\n", line) 200 | } 201 | } 202 | 203 | func writeSourceBlock(wr io.Writer, src string) { 204 | if len(src) == 0 { 205 | return 206 | } 207 | fmt.Fprint(wr, src) 208 | writeSpace(wr, 1) 209 | } 210 | -------------------------------------------------------------------------------- /generator/gen_common.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | 8 | tl "github.com/xlab/c-for-go/translator" 9 | ) 10 | 11 | const validationTemplate = "if %s(\"%s\") != nil { \n return %s \n}\n" 12 | 13 | var ( 14 | skipName = []byte("_") 15 | skipNameStr = "_" 16 | ) 17 | 18 | func (gen *Generator) writeStructMembers(wr io.Writer, structName string, spec tl.CType) { 19 | structSpec := spec.(*tl.CStructSpec) 20 | ptrTipRx, typeTipRx, memTipRx := gen.tr.TipRxsForSpec(tl.TipScopeType, structName, structSpec) 21 | const public = true 22 | for i, member := range structSpec.Members { 23 | ptrTip := ptrTipRx.TipAt(i) 24 | if !ptrTip.IsValid() { 25 | ptrTip = tl.TipPtrArr 26 | } 27 | typeTip := typeTipRx.TipAt(i) 28 | if !typeTip.IsValid() { 29 | typeTip = tl.TipTypeNamed 30 | } 31 | memTip := memTipRx.TipAt(i) 32 | if !memTip.IsValid() { 33 | memTip = gen.MemTipOf(member) 34 | } 35 | if memTip == tl.TipMemRaw { 36 | ptrTip = tl.TipPtrSRef 37 | } 38 | declName := checkName(gen.tr.TransformName(tl.TargetType, member.Name, public)) 39 | switch member.Spec.Kind() { 40 | case tl.TypeKind: 41 | goSpec := gen.tr.TranslateSpec(member.Spec, ptrTip, typeTip) 42 | fmt.Fprintf(wr, "%s %s", declName, goSpec) 43 | case tl.StructKind, tl.OpaqueStructKind, tl.UnionKind: 44 | if !gen.tr.IsAcceptableName(tl.TargetType, member.Spec.GetBase()) { 45 | continue 46 | } 47 | goSpec := gen.tr.TranslateSpec(member.Spec, ptrTip, typeTip) 48 | fmt.Fprintf(wr, "%s %s", declName, goSpec) 49 | case tl.EnumKind: 50 | if !gen.tr.IsAcceptableName(tl.TargetType, member.Spec.GetBase()) { 51 | continue 52 | } 53 | typeRef := gen.tr.TranslateSpec(member.Spec, ptrTip, typeTip).String() 54 | fmt.Fprintf(wr, "%s %s", declName, typeRef) 55 | case tl.FunctionKind: 56 | gen.writeFunctionAsArg(wr, member, ptrTip, typeTip, public) 57 | } 58 | writeSpace(wr, 1) 59 | } 60 | 61 | if memTipRx.Self() == tl.TipMemRaw { 62 | return 63 | } 64 | 65 | crc := getRefCRC(structSpec) 66 | cgoSpec := gen.tr.CGoSpec(structSpec, false) 67 | if len(cgoSpec.Base) == 0 { 68 | return 69 | } 70 | fmt.Fprintf(wr, "ref%2x *%s\n", crc, cgoSpec) 71 | fmt.Fprintf(wr, "allocs%2x interface{}\n", crc) 72 | } 73 | 74 | func (gen *Generator) writeInstanceObjectParam(wr io.Writer, funcName string, funcSpec tl.CType) { 75 | spec := funcSpec.(*tl.CFunctionSpec) 76 | ptrTipSpecRx, _ := gen.tr.PtrTipRx(tl.TipScopeFunction, funcName) 77 | typeTipSpecRx, _ := gen.tr.TypeTipRx(tl.TipScopeFunction, funcName) 78 | 79 | for i, param := range spec.Params { 80 | ptrTip := ptrTipSpecRx.TipAt(i) 81 | 82 | if ptrTip != tl.TipPtrInst { 83 | continue 84 | } 85 | 86 | ptrTip = tl.TipPtrRef 87 | typeTip := typeTipSpecRx.TipAt(i) 88 | if !typeTip.IsValid() { 89 | // try to use type tip for the type itself 90 | if tip, ok := gen.tr.TypeTipRx(tl.TipScopeType, param.Spec.CGoName()); ok { 91 | if tip := tip.Self(); tip.IsValid() { 92 | typeTip = tip 93 | } 94 | } 95 | } 96 | 97 | writeSpace(wr, 1) 98 | writeStartParams(wr) 99 | gen.writeFunctionParam(wr, param, ptrTip, typeTip) 100 | writeEndParams(wr) 101 | 102 | break 103 | } 104 | } 105 | 106 | func (gen *Generator) writeFunctionParam(wr io.Writer, param *tl.CDecl, ptrTip tl.Tip, typeTip tl.Tip) { 107 | const public = false 108 | 109 | declName := checkName(gen.tr.TransformName(tl.TargetType, param.Name, public)) 110 | switch param.Spec.Kind() { 111 | case tl.TypeKind: 112 | goSpec := gen.tr.TranslateSpec(param.Spec, ptrTip, typeTip) 113 | if len(goSpec.OuterArr) > 0 { 114 | fmt.Fprintf(wr, "%s *%s", declName, goSpec) 115 | } else { 116 | fmt.Fprintf(wr, "%s %s", declName, goSpec) 117 | } 118 | case tl.StructKind, tl.OpaqueStructKind, tl.UnionKind: 119 | goSpec := gen.tr.TranslateSpec(param.Spec, ptrTip, typeTip) 120 | if len(goSpec.OuterArr) > 0 { 121 | fmt.Fprintf(wr, "%s *%s", declName, goSpec) 122 | } else { 123 | fmt.Fprintf(wr, "%s %s", declName, goSpec) 124 | } 125 | case tl.EnumKind: 126 | typeRef := gen.tr.TranslateSpec(param.Spec, ptrTip, typeTip).String() 127 | fmt.Fprintf(wr, "%s %s", declName, typeRef) 128 | case tl.FunctionKind: 129 | gen.writeFunctionAsArg(wr, param, ptrTip, typeTip, public) 130 | } 131 | } 132 | 133 | func (gen *Generator) writeFunctionParams(wr io.Writer, funcName string, funcSpec tl.CType) { 134 | spec := funcSpec.(*tl.CFunctionSpec) 135 | ptrTipSpecRx, _ := gen.tr.PtrTipRx(tl.TipScopeFunction, funcName) 136 | typeTipSpecRx, _ := gen.tr.TypeTipRx(tl.TipScopeFunction, funcName) 137 | 138 | writeStartParams(wr) 139 | for i, param := range spec.Params { 140 | ptrTip := ptrTipSpecRx.TipAt(i) 141 | 142 | if ptrTip == tl.TipPtrInst { 143 | continue 144 | } 145 | 146 | if !ptrTip.IsValid() { 147 | ptrTip = tl.TipPtrArr 148 | } 149 | 150 | typeTip := typeTipSpecRx.TipAt(i) 151 | if !typeTip.IsValid() { 152 | // try to use type tip for the type itself 153 | if tip, ok := gen.tr.TypeTipRx(tl.TipScopeType, param.Spec.CGoName()); ok { 154 | if tip := tip.Self(); tip.IsValid() { 155 | typeTip = tip 156 | } 157 | } 158 | } 159 | 160 | gen.writeFunctionParam(wr, param, ptrTip, typeTip) 161 | 162 | if i < len(spec.Params)-1 && ptrTipSpecRx.TipAt(i+1) != tl.TipPtrInst { 163 | fmt.Fprintf(wr, ", ") 164 | } 165 | } 166 | writeEndParams(wr) 167 | } 168 | 169 | func writeStartParams(wr io.Writer) { 170 | fmt.Fprint(wr, "(") 171 | } 172 | 173 | func writeEndParams(wr io.Writer) { 174 | fmt.Fprint(wr, ")") 175 | } 176 | 177 | func writeEndStruct(wr io.Writer) { 178 | fmt.Fprint(wr, "}") 179 | } 180 | 181 | func writeStartFuncBody(wr io.Writer) { 182 | fmt.Fprintln(wr, "{") 183 | } 184 | 185 | func writeEndFuncBody(wr io.Writer) { 186 | fmt.Fprintln(wr, "}") 187 | } 188 | 189 | func writeSpace(wr io.Writer, n int) { 190 | fmt.Fprint(wr, strings.Repeat("\n", n)) 191 | } 192 | 193 | func writeError(wr io.Writer, err error) { 194 | fmt.Fprintf(wr, "// error: %v\n", err) 195 | } 196 | 197 | func writeValidation(wr io.Writer, validateFunc, funcName, retStr string) { 198 | fmt.Fprintf(wr, validationTemplate, validateFunc, funcName, retStr) 199 | } 200 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= 4 | github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 5 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 6 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 7 | github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= 8 | github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= 9 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 10 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 11 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= 12 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 13 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 14 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 15 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 16 | github.com/tj/go-spin v1.1.0 h1:lhdWZsvImxvZ3q1C5OIB7d72DuOwP4O2NdBg9PyzNds= 17 | github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= 18 | github.com/xlab/pkgconfig v0.0.0-20170226114623-cea12a0fd245 h1:Sw125DKxZhPUI4JLlWugkzsrlB50jR9v2khiD9FxuSo= 19 | github.com/xlab/pkgconfig v0.0.0-20170226114623-cea12a0fd245/go.mod h1:C+diUUz7pxhNY6KAoLgrTYARGWnt82zWTylZlxT92vk= 20 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 21 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 22 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 23 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= 24 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 25 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 26 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 27 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 28 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 29 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 30 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 31 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 32 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 33 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 34 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 35 | golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM= 36 | golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 37 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 38 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 39 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 40 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 41 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 42 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 43 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 44 | golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= 45 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 46 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 47 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 48 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 49 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 50 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 51 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 52 | gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= 53 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 54 | modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= 55 | modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= 56 | modernc.org/ccorpus2 v1.5.1 h1:y/aCOKCHsBy2cAemkZnsuQq/a0eXuf4TTLBNKaiZKso= 57 | modernc.org/ccorpus2 v1.5.1/go.mod h1:Wifvo4Q/qS/h1aRoC2TffcHsnxwTikmi1AuLANuucJQ= 58 | modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= 59 | modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= 60 | modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= 61 | modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= 62 | modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= 63 | modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= 64 | modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= 65 | modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= 66 | modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= 67 | modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= 68 | -------------------------------------------------------------------------------- /generator/gen_const.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "path/filepath" 8 | 9 | tl "github.com/xlab/c-for-go/translator" 10 | ) 11 | 12 | func (gen *Generator) writeDefinesGroup(wr io.Writer, defines []*tl.CDecl) (n int) { 13 | writeStartConst(wr) 14 | for _, decl := range defines { 15 | if !decl.IsDefine { 16 | continue 17 | } 18 | name := gen.tr.TransformName(tl.TargetConst, decl.Name) 19 | if decl.Value == nil && string(name) == decl.Expression { 20 | continue 21 | } 22 | fmt.Fprintf(wr, "// %s as defined in %s\n", name, 23 | filepath.ToSlash(gen.tr.SrcLocation(tl.TargetConst, decl.Name, decl.Position))) 24 | 25 | if decl.Value != nil { 26 | fmt.Fprintf(wr, "%s = %v", name, decl.Value) 27 | } else if len(decl.Expression) > 0 { 28 | fmt.Fprintf(wr, "%s = %s", name, decl.Expression) 29 | } else { 30 | // In this case, it's nil or the expression is zero length. 31 | // fmt.Fprint(wr, name) 32 | } 33 | writeSpace(wr, 1) 34 | n++ 35 | } 36 | writeEndConst(wr) 37 | return 38 | } 39 | 40 | func (gen *Generator) writeConstDeclaration(wr io.Writer, decl *tl.CDecl) { 41 | declName := gen.tr.TransformName(tl.TargetConst, decl.Name) 42 | if decl.Value == nil && string(declName) == decl.Expression { 43 | return 44 | } 45 | fmt.Fprintf(wr, "// %s as declared in %s\n", declName, 46 | filepath.ToSlash(gen.tr.SrcLocation(tl.TargetConst, decl.Name, decl.Position))) 47 | goSpec := gen.tr.TranslateSpec(decl.Spec) 48 | 49 | if decl.Value != nil { 50 | fmt.Fprintf(wr, "const %s %s = %v", declName, goSpec, decl.Value) 51 | return 52 | } else if len(decl.Expression) > 0 { 53 | fmt.Fprintf(wr, "const %s %s = %s", declName, goSpec, decl.Expression) 54 | return 55 | } 56 | // const must have values, otherwise variable 57 | fmt.Fprintf(wr, "var %s %s", declName, goSpec) 58 | } 59 | 60 | func (gen *Generator) expandEnumAnonymous(wr io.Writer, decl *tl.CDecl, namesSeen map[string]bool) { 61 | var typeName []byte 62 | var hasType bool 63 | if decl.IsTypedef { 64 | if typeName = gen.tr.TransformName(tl.TargetType, decl.Name); len(typeName) > 0 { 65 | hasType = true 66 | } 67 | } 68 | 69 | spec := decl.Spec.(*tl.CEnumSpec) 70 | if hasType { 71 | enumType := gen.tr.TranslateSpec(&spec.Type) 72 | if tips, ok := gen.tr.TypeTipRx(tl.TipScopeEnum, string(typeName)); ok { 73 | if tips.HasTip(tl.TipTypeUnsigned) { 74 | enumType.Unsigned = true 75 | } 76 | } 77 | fmt.Fprintf(wr, "// %s as declared in %s\n", typeName, 78 | filepath.ToSlash(gen.tr.SrcLocation(tl.TargetConst, decl.Name, decl.Position))) 79 | fmt.Fprintf(wr, "type %s %s\n", typeName, enumType) 80 | writeSpace(wr, 1) 81 | fmt.Fprintf(wr, "// %s enumeration from %s\n", typeName, 82 | filepath.ToSlash(gen.tr.SrcLocation(tl.TargetConst, decl.Name, decl.Position))) 83 | } 84 | writeStartConst(wr) 85 | for i, m := range spec.Members { 86 | if !gen.tr.IsAcceptableName(tl.TargetConst, m.Name) { 87 | continue 88 | } 89 | mName := gen.tr.TransformName(tl.TargetConst, m.Name) 90 | if len(mName) == 0 { 91 | continue 92 | } else if namesSeen[string(mName)] { 93 | continue 94 | } else { 95 | namesSeen[string(mName)] = true 96 | } 97 | if !hasType { 98 | fmt.Fprintf(wr, "// %s as declared in %s\n", mName, 99 | filepath.ToSlash(gen.tr.SrcLocation(tl.TargetConst, m.Name, m.Position))) 100 | } 101 | switch { 102 | case m.Value != nil: 103 | if hasType { 104 | fmt.Fprintf(wr, "%s %s = %s\n", mName, typeName, iotaOnZero(i, m.Value)) 105 | continue 106 | } 107 | fmt.Fprintf(wr, "%s = %s\n", mName, iotaOnZero(i, m.Value)) 108 | case len(m.Expression) > 0: 109 | if hasType { 110 | fmt.Fprintf(wr, "%s %s = %s\n", mName, typeName, iotaOnZero(i, m.Expression)) 111 | continue 112 | } 113 | fmt.Fprintf(wr, "%s = %s\n", mName, iotaOnZero(i, m.Expression)) 114 | default: 115 | if i == 0 && hasType { 116 | fmt.Fprintf(wr, "%s %s = iota\n", mName, typeName) 117 | continue 118 | } else if i == 0 { 119 | fmt.Fprintf(wr, "%s = iota\n", mName) 120 | } 121 | fmt.Fprintf(wr, "%s\n", mName) 122 | } 123 | } 124 | writeEndConst(wr) 125 | writeSpace(wr, 1) 126 | } 127 | 128 | func (gen *Generator) expandEnum(wr io.Writer, decl *tl.CDecl, namesSeen map[string]bool) { 129 | var declName []byte 130 | var isTypedef bool 131 | if decl.IsTypedef { 132 | if declName = gen.tr.TransformName(tl.TargetType, decl.Name); len(declName) > 0 { 133 | isTypedef = true 134 | } 135 | } 136 | 137 | spec := decl.Spec.(*tl.CEnumSpec) 138 | tagName := gen.tr.TransformName(tl.TargetType, decl.Spec.GetBase()) 139 | enumType := gen.tr.TranslateSpec(&spec.Type) 140 | if tips, ok := gen.tr.TypeTipRx(tl.TipScopeEnum, string(tagName)); ok { 141 | if tips.HasTip(tl.TipTypeUnsigned) { 142 | enumType.Unsigned = true 143 | } 144 | } 145 | fmt.Fprintf(wr, "// %s as declared in %s\n", tagName, 146 | filepath.ToSlash(gen.tr.SrcLocation(tl.TargetConst, decl.Name, decl.Position))) 147 | fmt.Fprintf(wr, "type %s %s\n", tagName, enumType) 148 | writeSpace(wr, 1) 149 | if isTypedef { 150 | if !bytes.Equal(tagName, declName) && len(declName) > 0 { 151 | // alias type decl name to the tag 152 | fmt.Fprintf(wr, "// %s as declared in %s\n", declName, 153 | filepath.ToSlash(gen.tr.SrcLocation(tl.TargetConst, decl.Name, decl.Position))) 154 | fmt.Fprintf(wr, "type %s %s", declName, tagName) 155 | writeSpace(wr, 1) 156 | } 157 | } 158 | 159 | fmt.Fprintf(wr, "// %s enumeration from %s\n", tagName, 160 | filepath.ToSlash(gen.tr.SrcLocation(tl.TargetConst, decl.Name, decl.Position))) 161 | writeStartConst(wr) 162 | for i, m := range spec.Members { 163 | if !gen.tr.IsAcceptableName(tl.TargetConst, m.Name) { 164 | continue 165 | } 166 | mName := gen.tr.TransformName(tl.TargetConst, m.Name) 167 | if len(mName) == 0 { 168 | continue 169 | } else if namesSeen[string(mName)] { 170 | continue 171 | } else { 172 | namesSeen[string(mName)] = true 173 | } 174 | switch { 175 | case m.Value != nil: 176 | fmt.Fprintf(wr, "%s %s = %v\n", mName, declName, iotaOnZero(i, m.Value)) 177 | case len(m.Expression) > 0: 178 | fmt.Fprintf(wr, "%s %s = %v\n", mName, declName, iotaOnZero(i, m.Expression)) 179 | default: 180 | if i == 0 { 181 | fmt.Fprintf(wr, "%s %s = iota\n", mName, declName) 182 | continue 183 | } 184 | fmt.Fprintf(wr, "%s\n", mName) 185 | } 186 | } 187 | writeEndConst(wr) 188 | writeSpace(wr, 1) 189 | } 190 | 191 | func iotaOnZero(i int, v interface{}) string { 192 | result := fmt.Sprintf("%v", v) 193 | if i == 0 { 194 | if result == "0" { 195 | return "iota" 196 | } 197 | } 198 | return result 199 | } 200 | 201 | func writeStartConst(wr io.Writer) { 202 | fmt.Fprintln(wr, "const (") 203 | } 204 | 205 | func writeEndConst(wr io.Writer) { 206 | fmt.Fprintln(wr, ")") 207 | } 208 | -------------------------------------------------------------------------------- /parser/parser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "runtime" 11 | "strings" 12 | 13 | "modernc.org/cc/v4" 14 | ) 15 | 16 | type Config struct { 17 | Arch string `yaml:"Arch"` 18 | IncludePaths []string `yaml:"IncludePaths"` 19 | SysIncludePaths []string `yaml:"SysIncludePaths"` 20 | SourcesPaths []string `yaml:"SourcesPaths"` 21 | IgnoredPaths []string `yaml:"IgnoredPaths"` 22 | 23 | Defines map[string]interface{} `yaml:"Defines"` 24 | 25 | CCDefs bool `yaml:"-"` 26 | CCIncl bool `yaml:"-"` 27 | } 28 | 29 | func ParseWith(cfg *Config) (*cc.AST, error) { 30 | if len(cfg.SourcesPaths) == 0 { 31 | return nil, errors.New("parser: no target paths specified") 32 | } 33 | cfg, err := checkConfig(cfg) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | var predefined string 39 | // user-provided defines take precedence 40 | for name, value := range cfg.Defines { 41 | switch v := value.(type) { 42 | case string: 43 | predefined += fmt.Sprintf("\n#define %s \"%s\"", name, v) 44 | case int, int16, int32, int64, uint, uint16, uint32, uint64: 45 | predefined += fmt.Sprintf("\n#define %s %d", name, v) 46 | case float32, float64: 47 | predefined += fmt.Sprintf("\n#define %s %ff", name, v) 48 | case map[interface{}]interface{}: 49 | if len(v) == 0 { // the special case for override a definition beforehand 50 | predefined += fmt.Sprintf("\n#define %s", name) 51 | } 52 | } 53 | } 54 | var ( 55 | ccDefs string 56 | ccDefsOK bool 57 | ) 58 | if cfg.CCDefs || cfg.CCIncl { 59 | cppPath := "cpp" 60 | if v, ok := os.LookupEnv("CPP"); ok { 61 | cppPath = v 62 | } else if v, ok = os.LookupEnv("CC"); ok { 63 | cppPath = v 64 | } 65 | // if expanded, err := exec.LookPath(cppPath); err == nil { 66 | // cppPath = expanded 67 | // } 68 | ccpredef, includePaths, sysIncludePaths, err := hostCppConfig(cppPath) 69 | if err != nil { 70 | log.Println("[WARN] `cpp -dM` failed:", err) 71 | } else { 72 | if cfg.CCIncl && len(includePaths) > 0 { 73 | // add on top of includePaths if allowed by config 74 | cfg.IncludePaths = append(includePaths, cfg.IncludePaths...) 75 | } 76 | if cfg.CCIncl && len(sysIncludePaths) > 0 { 77 | // add on top of sysIncludePaths if allowed by config 78 | cfg.SysIncludePaths = append(sysIncludePaths, cfg.SysIncludePaths...) 79 | } 80 | ccDefs = ccpredef 81 | ccDefsOK = true 82 | } 83 | } 84 | if cfg.CCDefs && ccDefsOK { 85 | predefined += fmt.Sprintf("\n%s", ccDefs) 86 | } 87 | ccConfig, _ := cc.NewConfig(runtime.GOOS, cfg.Arch) 88 | // Let cc provide all predefines and builtins. Only append custom definitions. 89 | ccConfig.Predefined += predefined 90 | ccConfig.IncludePaths = append(ccConfig.IncludePaths, cfg.IncludePaths...) 91 | ccConfig.SysIncludePaths = append(ccConfig.SysIncludePaths, cfg.SysIncludePaths...) 92 | var sources []cc.Source 93 | sources = append(sources, cc.Source{Name: "", Value: ccConfig.Predefined}) 94 | sources = append(sources, cc.Source{Name: "", Value: cc.Builtin}) 95 | for _, sourceEntry := range cfg.SourcesPaths { 96 | sources = append(sources, cc.Source{ 97 | Name: sourceEntry, 98 | }) 99 | } 100 | return cc.Translate(ccConfig, sources) 101 | } 102 | 103 | func checkConfig(cfg *Config) (*Config, error) { 104 | if cfg == nil { 105 | cfg = &Config{} 106 | } 107 | if len(cfg.Arch) == 0 { 108 | cfg.Arch = runtime.GOARCH 109 | } 110 | // workaround for cznic's cc (it panics if supplied path is a dir) 111 | var saneFiles []string 112 | for _, path := range cfg.SourcesPaths { 113 | if !filepath.IsAbs(path) { 114 | if hPath, err := findFile(path, cfg.IncludePaths); err != nil { 115 | err = fmt.Errorf("parser: file specified but not found: %s (include paths: %s)", 116 | path, strings.Join(cfg.IncludePaths, ", ")) 117 | return nil, err 118 | } else { 119 | path = hPath 120 | } 121 | } 122 | if info, err := os.Stat(path); err != nil || info.IsDir() { 123 | continue 124 | } 125 | if absPath, err := filepath.Abs(path); err != nil { 126 | path = absPath 127 | } 128 | saneFiles = append(saneFiles, path) 129 | } 130 | cfg.SourcesPaths = saneFiles 131 | return cfg, nil 132 | } 133 | 134 | func findFile(path string, includePaths []string) (string, error) { 135 | if _, err := os.Stat(path); err == nil { 136 | return path, nil 137 | } 138 | for _, inc := range includePaths { 139 | result := filepath.Join(inc, path) 140 | if _, err := os.Stat(result); err == nil { 141 | return result, nil 142 | } 143 | } 144 | return "", errors.New("not found") 145 | } 146 | 147 | // hostCppConfig returns the system C preprocessor configuration, or an error, 148 | // if any. The configuration is obtained by running the cpp command. For the 149 | // predefined macros list the '-dM' options is added. For the include paths 150 | // lists, the option '-v' is added and the output is parsed to extract the 151 | // "..." include and <...> include paths. To add any other options to cpp, list 152 | // them in opts. 153 | // 154 | // The function relies on a POSIX compatible C preprocessor installed. 155 | // Execution of HostConfig is not free, so caching the results is recommended 156 | // whenever possible. 157 | func hostCppConfig(cpp string, opts ...string) (predefined string, includePaths, sysIncludePaths []string, err error) { 158 | nullPath := "/dev/null" 159 | newLine := "\n" 160 | if runtime.GOOS == "windows" { 161 | nullPath = "nul" 162 | newLine = "\r\n" 163 | } 164 | args := append(append([]string{"-dM"}, opts...), nullPath) 165 | pre, err := exec.Command(cpp, args...).CombinedOutput() 166 | if err != nil { 167 | return "", nil, nil, err 168 | } 169 | 170 | args = append(append([]string{"-v"}, opts...), nullPath) 171 | out, err := exec.Command(cpp, args...).CombinedOutput() 172 | if err != nil { 173 | return "", nil, nil, err 174 | } 175 | 176 | a := strings.Split(string(out), newLine) 177 | for i := 0; i < len(a); { 178 | switch a[i] { 179 | case "#include \"...\" search starts here:": 180 | loop: 181 | for i = i + 1; i < len(a); { 182 | switch v := a[i]; { 183 | case strings.HasPrefix(v, "#") || v == "End of search list.": 184 | break loop 185 | default: 186 | includePaths = append(includePaths, strings.TrimSpace(v)) 187 | i++ 188 | } 189 | } 190 | case "#include <...> search starts here:": 191 | for i = i + 1; i < len(a); { 192 | switch v := a[i]; { 193 | case strings.HasPrefix(v, "#") || v == "End of search list.": 194 | return string(pre), includePaths, sysIncludePaths, nil 195 | default: 196 | sysIncludePaths = append(sysIncludePaths, strings.TrimSpace(v)) 197 | i++ 198 | } 199 | } 200 | default: 201 | i++ 202 | } 203 | } 204 | return "", nil, nil, fmt.Errorf("failed parsing %s -v output", cpp) 205 | } 206 | -------------------------------------------------------------------------------- /translator/helpers.go: -------------------------------------------------------------------------------- 1 | package translator 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "path/filepath" 7 | "regexp" 8 | "strings" 9 | "sync" 10 | 11 | "modernc.org/token" 12 | ) 13 | 14 | var ( 15 | qualConst = []byte("const") 16 | specStruct = []byte("struct") 17 | specUnion = []byte("union") 18 | specUnsigned = []byte("unsigned") 19 | specSigned = []byte("signed") 20 | specLong = []byte("long") 21 | specShort = []byte("short") 22 | ptrStr = []byte("*") 23 | sliceStr = []byte("[]") 24 | spaceStr = []byte(" ") 25 | skipStr = []byte("_") 26 | emptyStr = []byte{} 27 | restrictedNames = bytes.Join([][]byte{ 28 | qualConst, specStruct, specUnion, specUnsigned, specSigned, specShort, 29 | }, spaceStr) 30 | ) 31 | 32 | // narrowPath reduces full path to file name and parent dir only. 33 | func narrowPath(fp string) string { 34 | if !filepath.IsAbs(fp) { 35 | if abs, err := filepath.Abs(fp); err != nil { 36 | // seems to be reduced already 37 | return fp 38 | } else { 39 | fp = abs 40 | } 41 | } 42 | return filepath.Join(filepath.Base(filepath.Dir(fp)), filepath.Base(fp)) 43 | } 44 | 45 | func replaceBytes(buf []byte, idx []int, piece []byte) []byte { 46 | a, b := idx[0], idx[1] 47 | altered := make([]byte, len(buf)-(b-a)+len(piece)) 48 | copy(altered[:a], buf[:a]) 49 | pLen := copy(altered[a:], piece) 50 | copy(altered[a+pLen:], buf[b:]) 51 | return altered 52 | } 53 | 54 | var srcReferenceRx = regexp.MustCompile(`(?P[^;]+);(?P[^;]+);(?P[^;]+);(?P[^;]+);(?P[^;]+);`) 55 | 56 | func (t *Translator) IsTokenIgnored(p token.Position) bool { 57 | if len(t.ignoredFiles) == 0 { 58 | return false 59 | } 60 | for suffix := range t.ignoredFiles { 61 | if strings.HasSuffix(p.Filename, suffix) { 62 | return true 63 | } 64 | } 65 | return false 66 | } 67 | 68 | func (t *Translator) SrcLocation(docTarget RuleTarget, name string, p token.Position) string { 69 | filename := filepath.Base(p.Filename) 70 | defaultLocation := func() string { 71 | return fmt.Sprintf("%s:%d", narrowPath(p.Filename), p.Line) 72 | } 73 | rxs, ok := t.compiledRxs[ActionDocument][docTarget] 74 | if !ok { 75 | global, ok1 := t.compiledRxs[ActionDocument][TargetGlobal] 76 | post, ok2 := t.compiledRxs[ActionDocument][TargetPostGlobal] 77 | // other targets won't be supported 78 | switch { 79 | case ok1: 80 | rxs = global 81 | case ok2: 82 | rxs = post 83 | default: 84 | return defaultLocation() 85 | } 86 | } 87 | 88 | var template string 89 | for i := range rxs { 90 | if rxs[i].From.MatchString(name) { 91 | template = string(rxs[i].To) 92 | break 93 | } 94 | } 95 | if len(template) == 0 { 96 | return defaultLocation() 97 | } 98 | 99 | goName := t.TransformName(TargetGlobal, name, true) 100 | goName = t.TransformName(TargetPostGlobal, string(goName), true) 101 | values := fmt.Sprintf("%s;%s;%d;%s;%s;", narrowPath(p.Filename), 102 | filename, p.Line, name, string(goName)) 103 | location := srcReferenceRx.ReplaceAllString(values, template) 104 | return location 105 | } 106 | 107 | type TypeCache struct { 108 | mux sync.RWMutex 109 | cache map[string]struct{} 110 | } 111 | 112 | func (t *TypeCache) Get(id string) bool { 113 | t.mux.RLock() 114 | defer t.mux.RUnlock() 115 | _, ok := t.cache[id] 116 | return ok 117 | } 118 | 119 | func (t *TypeCache) Set(id string) { 120 | t.mux.Lock() 121 | defer t.mux.Unlock() 122 | if t.cache == nil { 123 | t.cache = make(map[string]struct{}) 124 | } 125 | t.cache[id] = struct{}{} 126 | } 127 | 128 | func (t *TypeCache) Delete(id string) { 129 | t.mux.Lock() 130 | defer t.mux.Unlock() 131 | delete(t.cache, id) 132 | } 133 | 134 | type CachedNameTransform struct { 135 | Target RuleTarget 136 | Visibility RuleTarget 137 | Name string 138 | } 139 | 140 | type NameTransformCache struct { 141 | mux sync.RWMutex 142 | cache map[CachedNameTransform][]byte 143 | } 144 | 145 | func (n *NameTransformCache) Get(target, visibility RuleTarget, name string) ([]byte, bool) { 146 | n.mux.RLock() 147 | entry := CachedNameTransform{ 148 | Target: target, 149 | Visibility: visibility, 150 | Name: name, 151 | } 152 | if cached, ok := n.cache[entry]; ok { 153 | n.mux.RUnlock() 154 | return cached, true 155 | } 156 | n.mux.RUnlock() 157 | return nil, false 158 | } 159 | 160 | func (n *NameTransformCache) Set(target, visibility RuleTarget, name string, result []byte) { 161 | n.mux.Lock() 162 | if n.cache == nil { 163 | n.cache = make(map[CachedNameTransform][]byte) 164 | } 165 | entry := CachedNameTransform{ 166 | Target: target, 167 | Visibility: visibility, 168 | Name: name, 169 | } 170 | n.cache[entry] = result 171 | n.mux.Unlock() 172 | } 173 | 174 | type TipCache struct { 175 | mux sync.RWMutex 176 | cache map[struct { 177 | TipScope 178 | string 179 | }]TipSpecRx 180 | } 181 | 182 | func (n *TipCache) Get(scope TipScope, name string) (TipSpecRx, bool) { 183 | n.mux.RLock() 184 | if cached, ok := n.cache[struct { 185 | TipScope 186 | string 187 | }{scope, name}]; ok { 188 | n.mux.RUnlock() 189 | return cached, true 190 | } 191 | n.mux.RUnlock() 192 | return TipSpecRx{}, false 193 | } 194 | 195 | func (n *TipCache) Set(scope TipScope, name string, result TipSpecRx) { 196 | n.mux.Lock() 197 | if n.cache == nil { 198 | n.cache = make(map[struct { 199 | TipScope 200 | string 201 | }]TipSpecRx) 202 | } 203 | n.cache[struct { 204 | TipScope 205 | string 206 | }{scope, name}] = result 207 | n.mux.Unlock() 208 | } 209 | 210 | var builtinNames = func() map[string]struct{} { 211 | names := []string{ 212 | "break", "default", "func", "interface", "select", "case", "defer", 213 | "map", "var", "chan", "else", "goto", "package", "switch", "const", 214 | "fallthrough", "if", "range", "type", "continue", "for", "import", 215 | "return", "go", "struct", 216 | } 217 | set := make(map[string]struct{}, len(names)) 218 | for _, name := range names { 219 | set[name] = struct{}{} 220 | } 221 | return set 222 | }() 223 | 224 | // blessName transforms the name to be a valid name in Go and not a keyword. 225 | func blessName(name string) string { 226 | if _, ok := builtinNames[name]; ok { 227 | return "_" + name 228 | } 229 | return name 230 | } 231 | 232 | func isBuiltinName(name []byte) bool { 233 | if _, ok := builtinNames[string(name)]; ok { 234 | return true 235 | } 236 | return false 237 | } 238 | 239 | var nameRewrites = map[string][]byte{ 240 | "type": []byte("kind"), 241 | } 242 | 243 | func rewriteName(name []byte) []byte { 244 | if n, ok := nameRewrites[string(name)]; ok { 245 | return n 246 | } 247 | // e.g. type -> _type 248 | return append(skipStr, name...) 249 | } 250 | 251 | // readNumeric checks if the value looks like a numeric value, i.e. -1000.f 252 | func isNumeric(v []rune) bool { 253 | for _, r := range v { 254 | switch r { 255 | case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 256 | return true 257 | case '-', '+', '.': 258 | continue 259 | default: 260 | return false 261 | } 262 | } 263 | return false 264 | } 265 | 266 | // readNumeric reads numeric part of the value, ex: -1000.f being read as -1000. 267 | func readNumeric(v []rune) string { 268 | long := false 269 | unsigned := false 270 | result := make([]rune, 0, len(v)) 271 | 272 | wrap := func() string { 273 | switch { 274 | case unsigned && long: 275 | return "uint64(" + string(result) + ")" 276 | case unsigned: 277 | return "uint32(" + string(result) + ")" 278 | case long: 279 | return "int64(" + string(result) + ")" 280 | default: 281 | return string(result) 282 | } 283 | } 284 | 285 | var isHex bool 286 | for _, r := range v { 287 | switch r { 288 | case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 289 | result = append(result, r) 290 | case 'x': 291 | isHex = true 292 | result = append(result, r) 293 | case '-', '+', '.', 'e', 'b': 294 | result = append(result, r) 295 | case 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'c', 'd', 'f': 296 | if isHex { 297 | result = append(result, r) 298 | } 299 | // c'mon, get some roman numerals here 300 | case 'L', 'l': 301 | long = true 302 | continue 303 | case 'U', 'u': 304 | unsigned = true 305 | continue 306 | default: 307 | return wrap() 308 | } 309 | } 310 | return wrap() 311 | } 312 | -------------------------------------------------------------------------------- /process.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | "sync" 13 | 14 | "github.com/xlab/c-for-go/generator" 15 | "github.com/xlab/c-for-go/parser" 16 | "github.com/xlab/c-for-go/translator" 17 | "github.com/xlab/pkgconfig/pkg" 18 | "golang.org/x/tools/imports" 19 | "gopkg.in/yaml.v2" 20 | "modernc.org/cc/v4" 21 | ) 22 | 23 | type Buf int 24 | 25 | const ( 26 | BufDoc Buf = iota 27 | BufConst 28 | BufTypes 29 | BufUnions 30 | BufHelpers 31 | BufMain 32 | ) 33 | 34 | var goBufferNames = map[Buf]string{ 35 | BufDoc: "doc", 36 | BufConst: "const", 37 | BufTypes: "types", 38 | BufUnions: "unions", 39 | BufHelpers: "cgo_helpers", 40 | } 41 | 42 | type Process struct { 43 | cfg ProcessConfig 44 | gen *generator.Generator 45 | genSync sync.WaitGroup 46 | goBuffers map[Buf]*bytes.Buffer 47 | chHelpersBuf *bytes.Buffer 48 | ccHelpersBuf *bytes.Buffer 49 | outputPath string 50 | } 51 | 52 | type ProcessConfig struct { 53 | Generator *generator.Config `yaml:"GENERATOR"` 54 | Translator *translator.Config `yaml:"TRANSLATOR"` 55 | Parser *parser.Config `yaml:"PARSER"` 56 | } 57 | 58 | func NewProcess(configPath, outputPath string) (*Process, error) { 59 | cfgData, err := ioutil.ReadFile(configPath) 60 | if err != nil { 61 | return nil, err 62 | } 63 | var cfg ProcessConfig 64 | if err := yaml.Unmarshal(cfgData, &cfg); err != nil { 65 | return nil, err 66 | } 67 | if cfg.Generator != nil { 68 | paths := includePathsFromPkgConfig(cfg.Generator.PkgConfigOpts) 69 | if cfg.Parser == nil { 70 | cfg.Parser = &parser.Config{} 71 | } 72 | cfg.Parser.CCDefs = *ccDefs 73 | cfg.Parser.CCIncl = *ccIncl 74 | cfg.Parser.IncludePaths = append(cfg.Parser.IncludePaths, paths...) 75 | cfg.Parser.IncludePaths = append(cfg.Parser.IncludePaths, filepath.Dir(configPath)) 76 | } else { 77 | return nil, errors.New("process: generator config was not specified") 78 | } 79 | 80 | // parse the headers 81 | unit, err := parser.ParseWith(cfg.Parser) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | if cfg.Translator == nil { 87 | cfg.Translator = &translator.Config{} 88 | } 89 | cfg.Translator.IgnoredFiles = cfg.Parser.IgnoredPaths 90 | cfg.Translator.LongIs64Bit = unit.ABI.Types[cc.Long].Size == 8 91 | // learn the model 92 | tl, err := translator.New(cfg.Translator) 93 | if err != nil { 94 | return nil, err 95 | } 96 | tl.Learn(unit) 97 | 98 | // begin generation 99 | pkg := filepath.Base(cfg.Generator.PackageName) 100 | gen, err := generator.New(pkg, cfg.Generator, tl) 101 | if err != nil { 102 | return nil, err 103 | } 104 | gen.SetMaxMemory(generator.NewMemSpec(*maxMem)) 105 | 106 | if *nostamp { 107 | gen.DisableTimestamps() 108 | } 109 | c := &Process{ 110 | cfg: cfg, 111 | gen: gen, 112 | goBuffers: make(map[Buf]*bytes.Buffer), 113 | chHelpersBuf: new(bytes.Buffer), 114 | ccHelpersBuf: new(bytes.Buffer), 115 | outputPath: outputPath, 116 | } 117 | c.goBuffers[BufMain] = new(bytes.Buffer) 118 | for opt := range goBufferNames { 119 | c.goBuffers[opt] = new(bytes.Buffer) 120 | } 121 | goHelpersBuf := c.goBuffers[BufHelpers] 122 | go func() { 123 | c.genSync.Add(1) 124 | c.gen.MonitorAndWriteHelpers(goHelpersBuf, c.chHelpersBuf, c.ccHelpersBuf) 125 | c.genSync.Done() 126 | }() 127 | return c, nil 128 | } 129 | 130 | func (c *Process) Generate(noCGO bool) { 131 | main := c.goBuffers[BufMain] 132 | if wr, ok := c.goBuffers[BufDoc]; ok { 133 | if !c.gen.WriteDoc(wr) { 134 | c.goBuffers[BufDoc] = nil 135 | } 136 | c.gen.WritePackageHeader(main) 137 | } else { 138 | c.gen.WriteDoc(main) 139 | } 140 | if !noCGO { 141 | c.gen.WriteIncludes(main) 142 | } 143 | if wr, ok := c.goBuffers[BufConst]; ok { 144 | c.gen.WritePackageHeader(wr) 145 | if !noCGO { 146 | c.gen.WriteIncludes(wr) 147 | } 148 | if n := c.gen.WriteConst(wr); n == 0 { 149 | c.goBuffers[BufConst] = nil 150 | } 151 | } else { 152 | c.gen.WriteConst(main) 153 | } 154 | if wr, ok := c.goBuffers[BufTypes]; ok { 155 | c.gen.WritePackageHeader(wr) 156 | if !noCGO { 157 | c.gen.WriteIncludes(wr) 158 | } 159 | if n := c.gen.WriteTypedefs(wr); n == 0 { 160 | c.goBuffers[BufTypes] = nil 161 | } 162 | } else { 163 | c.gen.WriteTypedefs(main) 164 | } 165 | if !noCGO { 166 | if wr, ok := c.goBuffers[BufUnions]; ok { 167 | c.gen.WritePackageHeader(wr) 168 | c.gen.WriteIncludes(wr) 169 | if n := c.gen.WriteUnions(wr); n == 0 { 170 | c.goBuffers[BufUnions] = nil 171 | } 172 | } else { 173 | c.gen.WriteUnions(main) 174 | } 175 | c.gen.WriteDeclares(main) 176 | } 177 | } 178 | 179 | func (c *Process) Flush(noCGO bool) error { 180 | c.gen.Close() 181 | c.genSync.Wait() 182 | filePrefix := filepath.Join(c.outputPath, c.cfg.Generator.PackageName) 183 | if err := os.MkdirAll(filePrefix, 0755); err != nil { 184 | return err 185 | } 186 | createCHFile := func(name string) (*os.File, error) { 187 | return os.Create(filepath.Join(filePrefix, fmt.Sprintf("%s.h", name))) 188 | } 189 | createCCFile := func(name string) (*os.File, error) { 190 | return os.Create(filepath.Join(filePrefix, fmt.Sprintf("%s.c", name))) 191 | } 192 | createGoFile := func(name string) (*os.File, error) { 193 | return os.Create(filepath.Join(filePrefix, fmt.Sprintf("%s.go", name))) 194 | } 195 | writeGoFile := func(opt Buf, name string) error { 196 | if buf := c.goBuffers[opt]; buf != nil && buf.Len() > 0 { 197 | if f, err := createGoFile(name); err == nil { 198 | if err := flushBufferToFile(buf.Bytes(), f, true); err != nil { 199 | f.Close() 200 | return err 201 | } 202 | f.Close() 203 | } else { 204 | return err 205 | } 206 | } 207 | return nil 208 | } 209 | writeCHFile := func(buf *bytes.Buffer, name string) error { 210 | if f, err := createCHFile(name); err != nil { 211 | return err 212 | } else if err := flushBufferToFile(buf.Bytes(), f, false); err != nil { 213 | f.Close() 214 | return err 215 | } else { 216 | return f.Close() 217 | } 218 | } 219 | writeCCFile := func(buf *bytes.Buffer, name string) error { 220 | if f, err := createCCFile(name); err != nil { 221 | return err 222 | } else if err := flushBufferToFile(buf.Bytes(), f, false); err != nil { 223 | f.Close() 224 | return err 225 | } else { 226 | return f.Close() 227 | } 228 | } 229 | 230 | if !noCGO { 231 | pkg := filepath.Base(c.cfg.Generator.PackageName) 232 | if err := writeGoFile(BufMain, pkg); err != nil { 233 | return err 234 | } 235 | } 236 | for opt, name := range goBufferNames { 237 | if err := writeGoFile(opt, name); err != nil { 238 | return err 239 | } 240 | } 241 | if noCGO { 242 | return nil 243 | } 244 | if c.chHelpersBuf.Len() > 0 { 245 | if err := writeCHFile(c.chHelpersBuf, "cgo_helpers"); err != nil { 246 | return err 247 | } 248 | } 249 | if c.ccHelpersBuf.Len() > 0 { 250 | if err := writeCCFile(c.ccHelpersBuf, "cgo_helpers"); err != nil { 251 | return err 252 | } 253 | } 254 | return nil 255 | } 256 | 257 | func flushBufferToFile(buf []byte, f *os.File, fmt bool) error { 258 | if fmt { 259 | if fmtBuf, err := imports.Process(f.Name(), buf, nil); err == nil { 260 | _, err = f.Write(fmtBuf) 261 | return err 262 | } else { 263 | log.Printf("[WARN] cannot gofmt %s: %s\n", f.Name(), err.Error()) 264 | f.Write(buf) 265 | return nil 266 | } 267 | } 268 | _, err := f.Write(buf) 269 | return err 270 | } 271 | 272 | func includePathsFromPkgConfig(opts []string) []string { 273 | if len(opts) == 0 { 274 | return nil 275 | } 276 | pc, err := pkg.NewConfig(nil) 277 | if err != nil { 278 | log.Println("[WARN]", err) 279 | return nil 280 | } 281 | for _, opt := range opts { 282 | if strings.HasPrefix(opt, "-") || strings.HasPrefix(opt, "--") { 283 | continue 284 | } 285 | if pcPath, err := pc.Locate(opt); err == nil { 286 | if err := pc.Load(pcPath, true); err != nil { 287 | log.Println("[WARN] pkg-config:", err) 288 | } 289 | } else { 290 | log.Printf("[WARN] %s.pc referenced in pkg-config options but cannot be found: %s", opt, err.Error()) 291 | } 292 | } 293 | flags := pc.CFlags() 294 | includePaths := make([]string, 0, len(flags)) 295 | for _, flag := range flags { 296 | if idx := strings.Index(flag, "-I"); idx >= 0 { 297 | includePaths = append(includePaths, strings.TrimSpace(flag[idx+2:])) 298 | } 299 | } 300 | return includePaths 301 | } 302 | -------------------------------------------------------------------------------- /generator/gen_callbacks.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "strings" 8 | 9 | tl "github.com/xlab/c-for-go/translator" 10 | ) 11 | 12 | func unexportName(name string) string { 13 | if len(name) == 0 { 14 | return name 15 | } 16 | return strings.ToLower(name[:1]) + name[1:] 17 | } 18 | 19 | func (gen *Generator) getCallbackHelpers(goFuncName, cFuncName string, spec tl.CType) (helpers []*Helper) { 20 | crc := getRefCRC(spec) 21 | cbCName := fmt.Sprintf("%s_%2x", cFuncName, crc) 22 | cbGoName := fmt.Sprintf("%s%2X", unexportName(goFuncName), crc) 23 | funcSpec := spec.(*tl.CFunctionSpec) 24 | 25 | var params []string 26 | var paramNames []string 27 | var paramNamesGo []string 28 | for i, param := range funcSpec.Params { 29 | if len(param.Name) == 0 { 30 | param.Name = fmt.Sprintf("arg%d", i) 31 | } 32 | paramSpec := gen.tr.NormalizeSpecPointers(param.Spec) 33 | params = append(params, fmt.Sprintf("%s %s", paramSpec.AtLevel(0), param.Name)) 34 | paramNames = append(paramNames, param.Name) 35 | goName := checkName(gen.tr.TransformName(tl.TargetType, param.Name, false)) 36 | paramNamesGo = append(paramNamesGo, fmt.Sprintf("%s%2x", goName, crc)) 37 | } 38 | paramList := strings.Join(params, ", ") 39 | paramNamesList := strings.Join(paramNames, ", ") 40 | paramNamesGoList := strings.Join(paramNamesGo, ", ") 41 | 42 | buf := new(bytes.Buffer) 43 | retSpec := "void" 44 | if funcSpec.Return != nil { 45 | retSpec = gen.tr.NormalizeSpecPointers(funcSpec.Return).String() 46 | } 47 | fmt.Fprintf(buf, "%s %s(%s);", retSpec, cbCName, paramList) 48 | helpers = append(helpers, &Helper{ 49 | Name: cbCName, 50 | Description: fmt.Sprintf("%s is a proxy for callback %s.", cbCName, cFuncName), 51 | Source: buf.String(), 52 | Side: CHSide, 53 | }) 54 | 55 | var ret string 56 | if funcSpec.Return != nil { 57 | ret = "return " 58 | } 59 | buf = new(bytes.Buffer) 60 | fmt.Fprintf(buf, "%s %s(%s) {\n", retSpec, cbCName, paramList) 61 | fmt.Fprintf(buf, "\t%s%s(%s);\n", ret, cbGoName, paramNamesList) 62 | buf.WriteRune('}') 63 | helpers = append(helpers, &Helper{ 64 | Name: cbCName, 65 | Source: buf.String(), 66 | Side: CCSide, 67 | }) 68 | 69 | cgoSpec := gen.tr.CGoSpec(&tl.CTypeSpec{ 70 | Base: cFuncName, 71 | }, true) 72 | buf = new(bytes.Buffer) 73 | fmt.Fprintf(buf, "func (x %s) PassRef() (ref *%s, allocs *cgoAllocMap)", goFuncName, cgoSpec) 74 | fmt.Fprintf(buf, `{ 75 | if x == nil { 76 | return nil, nil 77 | } 78 | if %sFunc == nil { 79 | %sFunc = x 80 | } 81 | return (*%s)(C.%s), nil 82 | }`, cbGoName, cbGoName, cgoSpec, cbCName) 83 | helpers = append(helpers, &Helper{ 84 | Name: fmt.Sprintf("%s.PassRef", goFuncName), 85 | Source: buf.String(), 86 | }) 87 | 88 | if spec.GetPointers() > 0 { 89 | buf = new(bytes.Buffer) 90 | fmt.Fprintf(buf, "func (x %s) PassValue() (ref %s, allocs *cgoAllocMap)", goFuncName, cgoSpec) 91 | fmt.Fprintf(buf, `{ 92 | if x == nil { 93 | return nil, nil 94 | } 95 | if %sFunc == nil { 96 | %sFunc = x 97 | } 98 | return (%s)(C.%s), nil 99 | }`, cbGoName, cbGoName, cgoSpec, cbCName) 100 | helpers = append(helpers, &Helper{ 101 | Name: fmt.Sprintf("%s.PassValue", goFuncName), 102 | Source: buf.String(), 103 | }) 104 | } 105 | 106 | buf = new(bytes.Buffer) 107 | fmt.Fprintf(buf, "func New%sRef(ref unsafe.Pointer) *%s", goFuncName, goFuncName) 108 | fmt.Fprintf(buf, `{ 109 | return (*%s)(ref) 110 | }`, goFuncName) 111 | helpers = append(helpers, &Helper{ 112 | Name: fmt.Sprintf("New%sRef", goFuncName), 113 | Source: buf.String(), 114 | }) 115 | 116 | buf = new(bytes.Buffer) 117 | fmt.Fprintf(buf, "//export %s\n", cbGoName) 118 | cbGoDecl := &tl.CDecl{ 119 | Name: cbGoName, 120 | Spec: spec, 121 | } 122 | 123 | proxyLines := gen.createCallbackProxies(cFuncName, funcSpec) 124 | proxySrc := new(bytes.Buffer) 125 | for i := range proxyLines { 126 | proxySrc.WriteString(proxyLines[i].Decl) 127 | } 128 | 129 | gen.writeCallbackProxyFunc(buf, cbGoDecl) 130 | fmt.Fprintln(buf, "{") 131 | fmt.Fprintf(buf, "if %sFunc != nil {\n", cbGoName) 132 | buf.WriteString(proxySrc.String()) 133 | if funcSpec.Return != nil { 134 | ret := fmt.Sprintf("ret%2x", crc) 135 | fmt.Fprintf(buf, "%s := %sFunc(%s)\n", ret, cbGoName, paramNamesGoList) 136 | ptrTipRx, typeTipRx, memTipRx := gen.tr.TipRxsForSpec(tl.TipScopeFunction, cFuncName, funcSpec) 137 | retGoSpec := gen.tr.TranslateSpec(funcSpec.Return, ptrTipRx.Self(), typeTipRx.Self()) 138 | retCGoSpec := gen.tr.CGoSpec(funcSpec.Return, true) // asArg? 139 | retProxy, _ := gen.proxyArgFromGo(memTipRx.Self(), ret, retGoSpec, retCGoSpec) 140 | fmt.Fprintf(buf, "ret, _ := %s\n", retProxy) 141 | fmt.Fprintf(buf, "return ret\n") 142 | } else { 143 | fmt.Fprintf(buf, "%sFunc(%s)\n", cbGoName, paramNamesGoList) 144 | fmt.Fprintln(buf, "return") 145 | } 146 | fmt.Fprintln(buf, "}") 147 | fmt.Fprintln(buf, `panic("callback func has not been set (race?)")`) 148 | fmt.Fprintln(buf, "}") 149 | 150 | fmt.Fprintf(buf, "\n\nvar %sFunc %s", cbGoName, goFuncName) 151 | helpers = append(helpers, &Helper{ 152 | Name: cbGoName, 153 | Source: buf.String(), 154 | }) 155 | return 156 | } 157 | 158 | func (gen *Generator) writeCallbackProxyFunc(wr io.Writer, decl *tl.CDecl) { 159 | var returnRef string 160 | funcSpec := decl.Spec.(*tl.CFunctionSpec) 161 | if funcSpec.Return != nil { 162 | cgoSpec := gen.tr.CGoSpec(funcSpec.Return, false) 163 | returnRef = cgoSpec.String() 164 | } 165 | fmt.Fprintf(wr, "func %s", decl.Name) 166 | gen.writeCallbackProxyFuncParams(wr, decl.Spec) 167 | if len(returnRef) > 0 { 168 | fmt.Fprintf(wr, " %s", returnRef) 169 | } 170 | } 171 | 172 | func (gen *Generator) writeCallbackProxyFuncParams(wr io.Writer, spec tl.CType) { 173 | funcSpec := spec.(*tl.CFunctionSpec) 174 | const public = false 175 | 176 | writeStartParams(wr) 177 | for i, param := range funcSpec.Params { 178 | declName := checkName(gen.tr.TransformName(tl.TargetType, param.Name, public)) 179 | if len(param.Name) == 0 { 180 | declName = []byte(fmt.Sprintf("arg%d", i)) 181 | } 182 | cgoSpec := gen.tr.CGoSpec(param.Spec, true) 183 | fmt.Fprintf(wr, "c%s %s", declName, cgoSpec.AtLevel(0)) 184 | 185 | if i < len(funcSpec.Params)-1 { 186 | fmt.Fprintf(wr, ", ") 187 | } 188 | } 189 | writeEndParams(wr) 190 | } 191 | 192 | func (gen *Generator) createCallbackProxies(funcName string, funcSpec tl.CType) (to []proxyDecl) { 193 | spec := funcSpec.(*tl.CFunctionSpec) 194 | to = make([]proxyDecl, 0, len(spec.Params)) 195 | 196 | cNamesSeen := make(map[string]struct{}, len(spec.Params)) 197 | for _, param := range spec.Params { 198 | if len(param.Name) > 0 { 199 | cNamesSeen[param.Name] = struct{}{} 200 | } 201 | } 202 | 203 | crc := getRefCRC(funcSpec) 204 | ptrTipRx, typeTipRx, memTipRx := gen.tr.TipRxsForSpec(tl.TipScopeFunction, funcName, funcSpec) 205 | for i, param := range spec.Params { 206 | var goSpec tl.GoTypeSpec 207 | ptrTip := ptrTipRx.TipAt(i) 208 | typeTip := typeTipRx.TipAt(i) 209 | goSpec = gen.tr.TranslateSpec(param.Spec, ptrTip, typeTip) 210 | cgoSpec := gen.tr.CGoSpec(param.Spec, true) 211 | const public = false 212 | refName := string(gen.tr.TransformName(tl.TargetType, param.Name, public)) 213 | if len(param.Name) == 0 { 214 | refName = fmt.Sprintf("arg%d", i) 215 | } 216 | toBuf := new(bytes.Buffer) 217 | 218 | cname := "c" + refName 219 | _, seen := cNamesSeen[cname] 220 | for seen { 221 | cname = "c" + cname 222 | _, seen = cNamesSeen[cname] 223 | } 224 | cNamesSeen[cname] = struct{}{} 225 | 226 | refName = fmt.Sprintf("%s%2x", refName, crc) 227 | toProxy, _ := gen.proxyCallbackArgToGo(memTipRx.TipAt(i), refName, cname, goSpec, cgoSpec) 228 | if len(toProxy) > 0 { 229 | fmt.Fprintln(toBuf, toProxy) 230 | to = append(to, proxyDecl{Name: cname, Decl: toBuf.String()}) 231 | } 232 | } 233 | return 234 | } 235 | 236 | func (gen *Generator) proxyCallbackArgToGo(memTip tl.Tip, varName, ptrName string, 237 | goSpec tl.GoTypeSpec, cgoSpec tl.CGoSpec) (proxy string, nillable bool) { 238 | nillable = true 239 | 240 | if goSpec.IsGoString() { 241 | helper := gen.getPackStringHelper(cgoSpec) 242 | gen.submitHelper(helper) 243 | proxy = fmt.Sprintf("%s := %s(%s)", varName, helper.Name, ptrName) 244 | return proxy, helper.Nillable 245 | } else if getHelper, ok := toGoHelperMap[goSpec]; ok { 246 | helper := getHelper(gen, cgoSpec) 247 | gen.submitHelper(helper) 248 | proxy = fmt.Sprintf("%s := %s(%s)", varName, helper.Name, ptrName) 249 | return proxy, helper.Nillable 250 | } 251 | gen.submitHelper(cgoAllocMap) 252 | 253 | isPlain := (memTip == tl.TipMemRaw) || goSpec.IsPlain() || goSpec.IsPlainKind() 254 | switch { 255 | case !isPlain && (goSpec.Slices > 0 || len(goSpec.OuterArr) > 0), // ex: []string 256 | isPlain && goSpec.Slices > 0 && len(goSpec.OuterArr) > 0, // ex: [4][]byte 257 | isPlain && goSpec.Slices > 1: // ex: [][]byte 258 | helper := gen.getPackHelper(memTip, goSpec, cgoSpec) 259 | gen.submitHelper(helper) 260 | if len(goSpec.OuterArr)+len(goSpec.InnerArr) != 0 { 261 | ptrName = fmt.Sprintf("%s := (*%s)(unsafe.Pointer(&%s))", varName, cgoSpec, ptrName) 262 | } 263 | gen.submitHelper(sliceHeader) 264 | proxy = fmt.Sprintf("var %s %s\n%s(%s, %s)", varName, goSpec, helper.Name, varName, ptrName) 265 | return proxy, helper.Nillable 266 | case isPlain && goSpec.Slices != 0: // ex: []byte, [][4]byte 267 | gen.submitHelper(sliceHeader) 268 | buf := new(bytes.Buffer) 269 | postfix := gen.randPostfix() 270 | fmt.Fprintf(buf, "var %s %s\n", varName, goSpec) 271 | fmt.Fprintf(buf, "hx%2x := (*sliceHeader)(unsafe.Pointer(&%s))\n", postfix, varName) 272 | fmt.Fprintf(buf, "hx%2x.Data = unsafe.Pointer(%s)\n", postfix, ptrName) 273 | fmt.Fprintf(buf, "hx%2x.Cap = %s\n", postfix, gen.maxMem) 274 | fmt.Fprintf(buf, "// hx%2x.Len = ?\n", postfix) 275 | proxy = buf.String() 276 | return 277 | case isPlain: // ex: byte, [4]byte 278 | var ref, ptr string 279 | if (goSpec.Kind == tl.PlainTypeKind || goSpec.Kind == tl.EnumKind) && 280 | len(goSpec.OuterArr)+len(goSpec.InnerArr) == 0 && goSpec.Pointers == 0 { 281 | proxy = fmt.Sprintf("%s := (%s)(%s)", varName, goSpec, ptrName) 282 | return 283 | } else if goSpec.Kind == tl.FunctionKind { 284 | proxy = fmt.Sprintf("// %s is a callback func", varName) 285 | return 286 | } else if goSpec.Pointers == 0 { 287 | ref = "&" 288 | ptr = "*" 289 | } 290 | proxy = fmt.Sprintf("%s := %s(%s%s)(unsafe.Pointer(%s%s))", varName, ptr, ptr, goSpec, ref, ptrName) 291 | return 292 | default: // ex: *SomeType 293 | var ref, deref string 294 | if cgoSpec.Pointers == 0 { 295 | deref = "*" 296 | ref = "&" 297 | } 298 | proxy = fmt.Sprintf("%s := %sNew%sRef(unsafe.Pointer(%s%s))", varName, deref, goSpec.Raw, ref, ptrName) 299 | return 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /generator/gen_struct_helpers.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "hash/crc32" 7 | 8 | tl "github.com/xlab/c-for-go/translator" 9 | ) 10 | 11 | func (gen *Generator) getStructHelpers(goStructName []byte, cStructName string, spec tl.CType) (helpers []*Helper) { 12 | crc := getRefCRC(spec) 13 | cgoSpec := gen.tr.CGoSpec(spec, true) 14 | 15 | buf := new(bytes.Buffer) 16 | fmt.Fprintf(buf, "func (x *%s) Ref() *%s", goStructName, cgoSpec) 17 | fmt.Fprintf(buf, `{ 18 | if x == nil { 19 | return nil 20 | } 21 | return x.ref%2x 22 | }`, crc) 23 | helpers = append(helpers, &Helper{ 24 | Name: fmt.Sprintf("%s.Ref", goStructName), 25 | Description: "Ref returns the underlying reference to C object or nil if struct is nil.", 26 | Source: buf.String(), 27 | }) 28 | 29 | buf.Reset() 30 | fmt.Fprintf(buf, "func (x *%s) Free()", goStructName) 31 | fmt.Fprintf(buf, `{ 32 | if x != nil && x.allocs%2x != nil { 33 | x.allocs%2x.(*cgoAllocMap).Free() 34 | x.ref%2x = nil 35 | } 36 | }`, crc, crc, crc) 37 | helpers = append(helpers, &Helper{ 38 | Name: fmt.Sprintf("%s.Free", goStructName), 39 | Description: "Free invokes alloc map's free mechanism that cleanups any allocated memory using C free.\n" + 40 | "Does nothing if struct is nil or has no allocation map.", 41 | Source: buf.String(), 42 | }) 43 | 44 | buf.Reset() 45 | fmt.Fprintf(buf, "func New%sRef(ref unsafe.Pointer) *%s", goStructName, goStructName) 46 | fmt.Fprintf(buf, `{ 47 | if ref == nil { 48 | return nil 49 | } 50 | obj := new(%s) 51 | obj.ref%2x = (*%s)(unsafe.Pointer(ref)) 52 | return obj 53 | }`, goStructName, crc, cgoSpec) 54 | 55 | name := fmt.Sprintf("New%sRef", goStructName) 56 | helpers = append(helpers, &Helper{ 57 | Name: name, 58 | Description: name + " creates a new wrapper struct with underlying reference set to the original C object.\n" + 59 | "Returns nil if the provided pointer to C object is nil too.", 60 | Source: buf.String(), 61 | }) 62 | 63 | buf.Reset() 64 | fmt.Fprintf(buf, "func (x *%s) PassRef() (*%s, *cgoAllocMap) {\n", goStructName, cgoSpec) 65 | buf.Write(gen.getPassRefSource(goStructName, cStructName, spec)) 66 | buf.WriteRune('}') 67 | helpers = append(helpers, &Helper{ 68 | Name: fmt.Sprintf("%s.PassRef", goStructName), 69 | Description: "PassRef returns the underlying C object, otherwise it will allocate one and set its values\n" + 70 | "from this wrapping struct, counting allocations into an allocation map.", 71 | Source: buf.String(), 72 | }) 73 | 74 | buf.Reset() 75 | fmt.Fprintf(buf, "func (x %s) PassValue() (%s, *cgoAllocMap) {\n", goStructName, cgoSpec) 76 | buf.Write(gen.getPassValueSource(goStructName, spec)) 77 | buf.WriteRune('}') 78 | helpers = append(helpers, &Helper{ 79 | Name: fmt.Sprintf("%s.PassValue", goStructName), 80 | Description: "PassValue does the same as PassRef except that it will try to dereference the returned pointer.", 81 | Source: buf.String(), 82 | }) 83 | 84 | buf.Reset() 85 | fmt.Fprintf(buf, "func (x *%s) Deref() {\n", goStructName) 86 | buf.Write(gen.getDerefSource(goStructName, cStructName, spec)) 87 | buf.WriteRune('}') 88 | helpers = append(helpers, &Helper{ 89 | Name: fmt.Sprintf("%s.Deref", goStructName), 90 | Description: "Deref uses the underlying reference to C object and fills the wrapping struct with values.\n" + 91 | "Do not forget to call this method whether you get a struct for C object and want to read its values.", 92 | Source: buf.String(), 93 | }) 94 | return 95 | } 96 | 97 | func (gen *Generator) getRawStructHelpers(goStructName []byte, cStructName string, spec tl.CType) (helpers []*Helper) { 98 | if spec.GetPointers() > 0 { 99 | return nil // can't addess a pointer receiver 100 | } 101 | cgoSpec := gen.tr.CGoSpec(spec, true) 102 | structSpec := spec.(*tl.CStructSpec) 103 | 104 | buf := new(bytes.Buffer) 105 | fmt.Fprintf(buf, "func (x *%s) Ref() *%s", goStructName, cgoSpec) 106 | fmt.Fprintf(buf, `{ 107 | if x == nil { 108 | return nil 109 | } 110 | return (*%s)(unsafe.Pointer(x)) 111 | }`, cgoSpec) 112 | helpers = append(helpers, &Helper{ 113 | Name: fmt.Sprintf("%s.Ref", goStructName), 114 | Description: "Ref returns a reference to C object as it is.", 115 | Source: buf.String(), 116 | }) 117 | 118 | buf.Reset() 119 | fmt.Fprintf(buf, "func (x *%s) Free()", goStructName) 120 | fmt.Fprint(buf, `{ 121 | if x != nil { 122 | C.free(unsafe.Pointer(x)) 123 | } 124 | }`) 125 | helpers = append(helpers, &Helper{ 126 | Name: fmt.Sprintf("%s.Free", goStructName), 127 | Description: "Free cleanups the referenced memory using C free.", 128 | Source: buf.String(), 129 | }) 130 | 131 | buf.Reset() 132 | fmt.Fprintf(buf, "func New%sRef(ref unsafe.Pointer) *%s", goStructName, goStructName) 133 | fmt.Fprintf(buf, `{ 134 | return (*%s)(ref) 135 | }`, goStructName) 136 | name := fmt.Sprintf("New%sRef", goStructName) 137 | helpers = append(helpers, &Helper{ 138 | Name: name, 139 | Description: name + " converts the C object reference into a raw struct reference without wrapping.", 140 | Source: buf.String(), 141 | }) 142 | 143 | buf.Reset() 144 | allocHelper := gen.getAllocMemoryHelper(cgoSpec) 145 | fmt.Fprintf(buf, "func New%s() *%s", goStructName, goStructName) 146 | fmt.Fprintf(buf, `{ 147 | return (*%s)(%s(1)) 148 | }`, goStructName, allocHelper.Name) 149 | name = fmt.Sprintf("New%s", goStructName) 150 | helpers = append(helpers, &Helper{ 151 | Name: name, 152 | Description: name + " allocates a new C object of this type and converts the reference into\n" + 153 | "a raw struct reference without wrapping.", 154 | Source: buf.String(), 155 | Requires: []*Helper{allocHelper}, 156 | }) 157 | 158 | buf.Reset() 159 | fmt.Fprintf(buf, "func (x *%s) PassRef() *%s", goStructName, cgoSpec) 160 | fmt.Fprintf(buf, `{ 161 | if x == nil { 162 | x = (*%s)(%s(1)) 163 | } 164 | return (*%s)(unsafe.Pointer(x)) 165 | }`, goStructName, allocHelper.Name, cgoSpec) 166 | helpers = append(helpers, &Helper{ 167 | Name: fmt.Sprintf("%s.PassRef", goStructName), 168 | Description: "PassRef returns a reference to C object as it is or allocates a new C object of this type.", 169 | Source: buf.String(), 170 | Requires: []*Helper{allocHelper}, 171 | }) 172 | 173 | if !gen.cfg.Options.StructAccessors { 174 | return 175 | } 176 | ptrTipRx, typeTipRx, memTipRx := gen.tr.TipRxsForSpec(tl.TipScopeType, cStructName, spec) 177 | for i, m := range structSpec.Members { 178 | if len(m.Name) == 0 { 179 | continue 180 | } 181 | buf.Reset() 182 | typeName := m.Spec.GetBase() 183 | switch m.Spec.Kind() { 184 | case tl.StructKind, tl.OpaqueStructKind, tl.UnionKind, tl.EnumKind: 185 | if !gen.tr.IsAcceptableName(tl.TargetType, typeName) { 186 | continue 187 | } 188 | } 189 | memTip := memTipRx.TipAt(i) 190 | if !memTip.IsValid() { 191 | memTip = gen.MemTipOf(m) 192 | } 193 | ptrTip := ptrTipRx.TipAt(i) 194 | if memTip == tl.TipMemRaw { 195 | ptrTip = tl.TipPtrSRef 196 | } 197 | typeTip := typeTipRx.TipAt(i) 198 | goSpec := gen.tr.TranslateSpec(m.Spec, ptrTip, typeTip) 199 | cgoSpec := gen.tr.CGoSpec(m.Spec, false) 200 | // does not work for function pointers 201 | if goSpec.Pointers > 0 && goSpec.Base == "func" { 202 | continue 203 | } 204 | const public = true 205 | goName := string(gen.tr.TransformName(tl.TargetType, m.Name, public)) 206 | arr := len(goSpec.OuterArr.Sizes()) > 0 || len(goSpec.InnerArr.Sizes()) > 0 207 | if !arr { 208 | goSpec.Pointers += 1 209 | cgoSpec.Pointers += 1 210 | } 211 | fmt.Fprintf(buf, "func (s *%s) Get%s() %s {\n", goStructName, goName, goSpec) 212 | lenField := getLenField(gen, structSpec, m) 213 | toProxy, _ := gen.proxyValueToGo(memTip, "ret", "&s."+m.Name, goSpec, cgoSpec, lenField) 214 | fmt.Fprintf(buf, "\tvar ret %s\n", goSpec) 215 | fmt.Fprintf(buf, "\t%s\n", toProxy) 216 | fmt.Fprintf(buf, "\treturn ret\n") 217 | fmt.Fprintf(buf, "}\n") 218 | helpers = append(helpers, &Helper{ 219 | Name: fmt.Sprintf("%s.Get%s", goStructName, goName), 220 | Description: fmt.Sprintf("Get%s returns a reference to C object within a struct", goName), 221 | Source: buf.String(), 222 | }) 223 | } 224 | return 225 | } 226 | 227 | func (gen *Generator) getPassRefSource(goStructName []byte, cStructName string, spec tl.CType) []byte { 228 | cgoSpec := gen.tr.CGoSpec(spec, false) 229 | structSpec := spec.(*tl.CStructSpec) 230 | buf := new(bytes.Buffer) 231 | crc := getRefCRC(spec) 232 | fmt.Fprintf(buf, `if x == nil { 233 | return nil, nil 234 | } else if x.ref%2x != nil { 235 | return x.ref%2x, nil 236 | }`, crc, crc) 237 | writeSpace(buf, 1) 238 | 239 | h := gen.getAllocMemoryHelper(tl.CGoSpec{Base: cgoSpec.Base}) 240 | gen.submitHelper(h) 241 | 242 | fmt.Fprintf(buf, "mem%2x := %s(1)\n", crc, h.Name) 243 | fmt.Fprintf(buf, "ref%2x := (*%s)(mem%2x)\n", crc, cgoSpec.Base, crc) 244 | fmt.Fprintf(buf, "allocs%2x := new(cgoAllocMap)\n", crc) 245 | fmt.Fprintf(buf, "allocs%2x.Add(mem%2x)\n", crc, crc) 246 | 247 | writeSpace(buf, 1) 248 | 249 | ptrTipRx, typeTipRx, memTipRx := gen.tr.TipRxsForSpec(tl.TipScopeType, cStructName, spec) 250 | for i, m := range structSpec.Members { 251 | if len(m.Name) == 0 { 252 | continue 253 | // TODO: generate setters 254 | } 255 | 256 | typeName := m.Spec.GetBase() 257 | switch m.Spec.Kind() { 258 | case tl.StructKind, tl.OpaqueStructKind, tl.UnionKind, tl.EnumKind: 259 | if !gen.tr.IsAcceptableName(tl.TargetType, typeName) { 260 | continue 261 | } 262 | } 263 | memTip := memTipRx.TipAt(i) 264 | if !memTip.IsValid() { 265 | memTip = gen.MemTipOf(m) 266 | } 267 | ptrTip := ptrTipRx.TipAt(i) 268 | if memTip == tl.TipMemRaw { 269 | ptrTip = tl.TipPtrSRef 270 | } 271 | typeTip := typeTipRx.TipAt(i) 272 | goSpec := gen.tr.TranslateSpec(m.Spec, ptrTip, typeTip) 273 | cgoSpec := gen.tr.CGoSpec(m.Spec, false) 274 | const public = true 275 | goName := "x." + string(gen.tr.TransformName(tl.TargetType, m.Name, public)) 276 | fromProxy, nillable := gen.proxyValueFromGo(memTip, goName, goSpec, cgoSpec) 277 | if nillable { 278 | fmt.Fprintf(buf, "if %s != nil {\n", goName) 279 | } 280 | fmt.Fprintf(buf, "var c%s_allocs *cgoAllocMap\n", m.Name) 281 | fmt.Fprintf(buf, "ref%2x.%s, c%s_allocs = %s\n", crc, m.Name, m.Name, fromProxy) 282 | fmt.Fprintf(buf, "allocs%2x.Borrow(c%s_allocs)\n", crc, m.Name) 283 | if nillable { 284 | fmt.Fprintf(buf, "}\n\n") 285 | } else { 286 | fmt.Fprint(buf, "\n") 287 | } 288 | } 289 | fmt.Fprintf(buf, "x.ref%2x = ref%2x\n", crc, crc) 290 | fmt.Fprintf(buf, "x.allocs%2x = allocs%2x\n", crc, crc) 291 | fmt.Fprintf(buf, "return ref%2x, allocs%2x\n", crc, crc) 292 | writeSpace(buf, 1) 293 | return buf.Bytes() 294 | } 295 | 296 | func (gen *Generator) getPassValueSource(goStructName []byte, spec tl.CType) []byte { 297 | buf := new(bytes.Buffer) 298 | crc := getRefCRC(spec) 299 | fmt.Fprintf(buf, `if x.ref%2x != nil { 300 | return *x.ref%2x, nil 301 | }`, crc, crc) 302 | writeSpace(buf, 1) 303 | fmt.Fprintf(buf, "ref, allocs := x.PassRef()\n") 304 | fmt.Fprintf(buf, "return *ref, allocs\n") 305 | return buf.Bytes() 306 | } 307 | 308 | func getRefCRC(spec tl.CType) uint32 { 309 | return crc32.ChecksumIEEE([]byte(spec.String())) 310 | } 311 | 312 | func (gen *Generator) getDerefSource(goStructName []byte, cStructName string, spec tl.CType) []byte { 313 | structSpec := spec.(*tl.CStructSpec) 314 | buf := new(bytes.Buffer) 315 | crc := getRefCRC(spec) 316 | fmt.Fprintf(buf, `if x.ref%2x == nil { 317 | return 318 | }`, crc) 319 | writeSpace(buf, 1) 320 | 321 | ptrTipRx, typeTipRx, memTipRx := gen.tr.TipRxsForSpec(tl.TipScopeType, cStructName, spec) 322 | for i, m := range structSpec.Members { 323 | if len(m.Name) == 0 { 324 | continue 325 | // TODO: generate getters 326 | } 327 | 328 | typeName := m.Spec.GetBase() 329 | switch m.Spec.Kind() { 330 | case tl.StructKind, tl.OpaqueStructKind, tl.UnionKind, tl.EnumKind: 331 | if !gen.tr.IsAcceptableName(tl.TargetType, typeName) { 332 | continue 333 | } 334 | } 335 | memTip := memTipRx.TipAt(i) 336 | if !memTip.IsValid() { 337 | memTip = gen.MemTipOf(m) 338 | } 339 | ptrTip := ptrTipRx.TipAt(i) 340 | if memTip == tl.TipMemRaw { 341 | ptrTip = tl.TipPtrSRef 342 | } 343 | typeTip := typeTipRx.TipAt(i) 344 | goSpec := gen.tr.TranslateSpec(m.Spec, ptrTip, typeTip) 345 | const public = true 346 | goName := "x." + string(gen.tr.TransformName(tl.TargetType, m.Name, public)) 347 | cgoName := fmt.Sprintf("x.ref%2x.%s", crc, m.Name) 348 | cgoSpec := gen.tr.CGoSpec(m.Spec, false) 349 | lenField := getLenField(gen, structSpec, m) 350 | toProxy, _ := gen.proxyValueToGo(memTip, goName, cgoName, goSpec, cgoSpec, lenField) 351 | fmt.Fprintln(buf, toProxy) 352 | } 353 | return buf.Bytes() 354 | } 355 | 356 | func getLenField(gen *Generator, structSpec *tl.CStructSpec, m *tl.CDecl) string { 357 | lenField := "" 358 | if gen.tr.LenFields() != nil { 359 | lenField = gen.tr.LenFields()[structSpec.Typedef+"."+m.Name] 360 | } 361 | return lenField 362 | } 363 | -------------------------------------------------------------------------------- /generator/generator.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "math/rand" 7 | "sort" 8 | 9 | tl "github.com/xlab/c-for-go/translator" 10 | ) 11 | 12 | type Generator struct { 13 | pkg string 14 | cfg *Config 15 | tr *tl.Translator 16 | // 17 | closed bool 18 | closeC, doneC chan struct{} 19 | helpersChan chan *Helper 20 | rand *rand.Rand 21 | noTimestamps bool 22 | maxMem MemSpec 23 | } 24 | 25 | func (g *Generator) DisableTimestamps() { 26 | g.noTimestamps = true 27 | } 28 | 29 | type TraitFlagGroup struct { 30 | Name string `yaml:"name"` 31 | Traits []string `yaml:"traits"` 32 | Flags []string `yaml:"flags"` 33 | } 34 | 35 | type Config struct { 36 | PackageName string `yaml:"PackageName"` 37 | PackageDescription string `yaml:"PackageDescription"` 38 | PackageLicense string `yaml:"PackageLicense"` 39 | PkgConfigOpts []string `yaml:"PkgConfigOpts"` 40 | FlagGroups []TraitFlagGroup `yaml:"FlagGroups"` 41 | SysIncludes []string `yaml:"SysIncludes"` 42 | Includes []string `yaml:"Includes"` 43 | Options GenOptions `yaml:"Options"` 44 | } 45 | 46 | type GenOptions struct { 47 | SafeStrings bool `yaml:"SafeStrings"` 48 | StructAccessors bool `yaml:"StructAccessors"` 49 | KeepAlive bool `yaml:"KeepAlive"` 50 | } 51 | 52 | func New(pkg string, cfg *Config, tr *tl.Translator) (*Generator, error) { 53 | if cfg == nil || len(cfg.PackageName) == 0 { 54 | return nil, errors.New("no package name provided") 55 | } else if tr == nil { 56 | return nil, errors.New("no translator provided") 57 | } 58 | gen := &Generator{ 59 | pkg: pkg, 60 | cfg: cfg, 61 | tr: tr, 62 | // 63 | helpersChan: make(chan *Helper, 1), 64 | closeC: make(chan struct{}), 65 | doneC: make(chan struct{}), 66 | rand: rand.New(rand.NewSource(+79269965690)), 67 | maxMem: MemSpecDefault, 68 | } 69 | return gen, nil 70 | } 71 | 72 | type declList []*tl.CDecl 73 | 74 | func (s declList) Len() int { return len(s) } 75 | func (s declList) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 76 | func (s declList) Less(i, j int) bool { 77 | if s[i].Position.Filename != s[j].Position.Filename { 78 | return s[i].Position.Filename < s[j].Position.Filename 79 | } else if s[i].Position.Offset != s[j].Position.Offset { 80 | return s[i].Position.Offset < s[j].Position.Offset 81 | } else { 82 | return s[i].Name < s[j].Name 83 | } 84 | } 85 | 86 | func (gen *Generator) WriteConst(wr io.Writer) int { 87 | var count int 88 | if defines := gen.tr.Defines(); len(defines) > 0 { 89 | n := gen.writeDefinesGroup(wr, defines) 90 | count = count + n 91 | } 92 | writeSpace(wr, 1) 93 | tagsSeen := make(map[string]bool) 94 | namesSeen := make(map[string]bool) 95 | 96 | gen.submitHelper(cgoGenTag) 97 | expandEnum := func(decl *tl.CDecl) bool { 98 | if tag := decl.Spec.GetTag(); len(tag) == 0 { 99 | gen.expandEnumAnonymous(wr, decl, namesSeen) 100 | return true 101 | } else if tagsSeen[tag] { 102 | return false 103 | } else { 104 | gen.expandEnum(wr, decl, namesSeen) 105 | if decl.Spec.IsComplete() { 106 | tagsSeen[tag] = true 107 | } 108 | return true 109 | } 110 | } 111 | 112 | enumList := make([]*tl.CDecl, 0, len(gen.tr.TagMap())) 113 | for tag, decl := range gen.tr.TagMap() { 114 | if decl.Spec.Kind() != tl.EnumKind { 115 | continue 116 | } else if !decl.Spec.IsComplete() { 117 | continue 118 | } 119 | if !gen.tr.IsAcceptableName(tl.TargetType, tag) { 120 | continue 121 | } 122 | enumList = append(enumList, decl) 123 | } 124 | sort.Sort(declList(enumList)) 125 | for i := range enumList { 126 | if expandEnum(enumList[i]) { 127 | count++ 128 | } 129 | } 130 | 131 | for _, decl := range gen.tr.Typedefs() { 132 | if decl.Spec.Kind() != tl.EnumKind { 133 | continue 134 | } else if !decl.Spec.IsComplete() { 135 | continue 136 | } 137 | if !gen.tr.IsAcceptableName(tl.TargetType, decl.Name) { 138 | continue 139 | } 140 | if expandEnum(decl) { 141 | count++ 142 | } 143 | } 144 | for _, decl := range gen.tr.Declares() { 145 | if decl.Spec.Kind() == tl.EnumKind { 146 | if !decl.Spec.IsComplete() { 147 | continue 148 | } 149 | if len(decl.Name) > 0 { 150 | if !gen.tr.IsAcceptableName(tl.TargetType, decl.Name) { 151 | continue 152 | } 153 | } 154 | if expandEnum(decl) { 155 | count++ 156 | } 157 | continue 158 | } 159 | if decl.IsStatic { 160 | continue 161 | } 162 | if len(decl.Name) == 0 { 163 | continue 164 | } 165 | if decl.Spec.Kind() != tl.TypeKind { 166 | continue 167 | } else if !decl.Spec.IsConst() { 168 | continue 169 | } 170 | if !gen.tr.IsAcceptableName(tl.TargetPublic, decl.Name) { 171 | continue 172 | } 173 | gen.writeConstDeclaration(wr, decl) 174 | writeSpace(wr, 1) 175 | count++ 176 | } 177 | return count 178 | } 179 | 180 | func (gen *Generator) MemTipOf(decl *tl.CDecl) tl.Tip { 181 | var memTip tl.Tip 182 | if name := decl.Spec.CGoName(); len(name) > 0 { 183 | if memTipRx, ok := gen.tr.MemTipRx(name); ok { 184 | memTip = memTipRx.Self() 185 | } 186 | } 187 | return memTip 188 | } 189 | 190 | func (gen *Generator) WriteTypedefs(wr io.Writer) int { 191 | var count int 192 | typedefs := gen.tr.Typedefs() 193 | seenStructTags := make(map[string]bool, len(typedefs)) 194 | seenUnionTags := make(map[string]bool, len(typedefs)) 195 | seenTypeNames := make(map[string]bool, len(typedefs)) 196 | seenStructNames := make(map[string]bool, len(typedefs)) 197 | seenUnionNames := make(map[string]bool, len(typedefs)) 198 | seenFunctionNames := make(map[string]bool, len(typedefs)) 199 | for _, decl := range typedefs { 200 | if !gen.tr.IsAcceptableName(tl.TargetType, decl.Name) { 201 | continue 202 | } 203 | switch decl.Spec.Kind() { 204 | case tl.StructKind, tl.OpaqueStructKind: 205 | if tag := decl.Spec.GetTag(); len(tag) > 0 { 206 | if len(decl.Name) == 0 || decl.Name == tag { 207 | if seenStructTags[tag] { 208 | continue 209 | } 210 | } 211 | seenStructTags[tag] = true 212 | } 213 | memTip := gen.MemTipOf(decl) 214 | gen.writeStructTypedef(wr, decl, memTip == tl.TipMemRaw, seenStructNames) 215 | case tl.UnionKind: 216 | if len(decl.Name) > 0 { 217 | if seenUnionNames[decl.Name] { 218 | continue 219 | } 220 | seenUnionNames[decl.Name] = true 221 | } 222 | if tag := decl.Spec.GetTag(); len(tag) > 0 { 223 | if len(decl.Name) == 0 || decl.Name == tag { 224 | if seenUnionTags[tag] { 225 | continue 226 | } 227 | } 228 | seenUnionTags[tag] = true 229 | } 230 | gen.writeUnionTypedef(wr, decl) 231 | case tl.EnumKind: 232 | if !decl.Spec.IsComplete() { 233 | gen.writeEnumTypedef(wr, decl) 234 | } 235 | case tl.TypeKind: 236 | gen.writeTypeTypedef(wr, decl, seenTypeNames) 237 | case tl.FunctionKind: 238 | gen.writeFunctionTypedef(wr, decl, seenFunctionNames) 239 | } 240 | writeSpace(wr, 1) 241 | count++ 242 | } 243 | 244 | tagDefs := sortedTagDefs(gen.tr.TagMap()) 245 | for _, def := range tagDefs { 246 | decl := def.tagDecl 247 | tag := def.tagName 248 | switch decl.Spec.Kind() { 249 | case tl.StructKind, tl.OpaqueStructKind: 250 | if seenStructTags[tag] { 251 | continue 252 | } 253 | if !gen.tr.IsAcceptableName(tl.TargetPublic, tag) { 254 | continue 255 | } else if !gen.tr.IsAcceptableName(tl.TargetType, tag) { 256 | continue 257 | } 258 | if memTipRx, ok := gen.tr.MemTipRx(tag); ok { 259 | gen.writeStructTypedef(wr, decl, memTipRx.Self() == tl.TipMemRaw, seenStructNames) 260 | } else { 261 | gen.writeStructTypedef(wr, decl, false, seenStructNames) 262 | } 263 | writeSpace(wr, 1) 264 | count++ 265 | case tl.UnionKind: 266 | if seenUnionTags[tag] { 267 | continue 268 | } 269 | if !gen.tr.IsAcceptableName(tl.TargetPublic, tag) { 270 | continue 271 | } else if !gen.tr.IsAcceptableName(tl.TargetType, tag) { 272 | continue 273 | } 274 | gen.writeUnionTypedef(wr, decl) 275 | writeSpace(wr, 1) 276 | count++ 277 | } 278 | } 279 | return count 280 | } 281 | 282 | func (gen *Generator) WriteDeclares(wr io.Writer) int { 283 | var count int 284 | declares := gen.tr.Declares() 285 | seenStructs := make(map[string]bool, len(declares)) 286 | seenUnions := make(map[string]bool, len(declares)) 287 | seenEnums := make(map[string]bool, len(declares)) 288 | seenFunctions := make(map[string]bool, len(declares)) 289 | for _, decl := range declares { 290 | const public = true 291 | switch decl.Spec.Kind() { 292 | case tl.StructKind, tl.OpaqueStructKind: 293 | if len(decl.Name) == 0 { 294 | continue 295 | } else if !gen.tr.IsAcceptableName(tl.TargetPublic, decl.Name) { 296 | continue 297 | } else if seenStructs[decl.Name] { 298 | continue 299 | } else { 300 | seenStructs[decl.Name] = true 301 | } 302 | gen.writeStructDeclaration(wr, decl, tl.NoTip, tl.NoTip, public) 303 | case tl.UnionKind: 304 | if len(decl.Name) == 0 { 305 | continue 306 | } else if !gen.tr.IsAcceptableName(tl.TargetPublic, decl.Name) { 307 | continue 308 | } else if seenUnions[decl.Name] { 309 | continue 310 | } else { 311 | seenUnions[decl.Name] = true 312 | } 313 | gen.writeUnionDeclaration(wr, decl, tl.NoTip, tl.NoTip, public) 314 | case tl.EnumKind: 315 | if !decl.Spec.IsComplete() { 316 | if !gen.tr.IsAcceptableName(tl.TargetPublic, decl.Name) { 317 | continue 318 | } else if seenEnums[decl.Name] { 319 | continue 320 | } else { 321 | seenEnums[decl.Name] = true 322 | } 323 | gen.writeEnumDeclaration(wr, decl, tl.NoTip, tl.NoTip, public) 324 | } 325 | case tl.FunctionKind: 326 | if !gen.tr.IsAcceptableName(tl.TargetFunction, decl.Name) { 327 | continue 328 | } else if seenFunctions[decl.Name] { 329 | continue 330 | } else { 331 | seenFunctions[decl.Name] = true 332 | } 333 | // defaults to ref for the returns 334 | ptrTip := tl.TipPtrRef 335 | if ptrTipRx, ok := gen.tr.PtrTipRx(tl.TipScopeFunction, decl.Name); ok { 336 | if tip := ptrTipRx.Self(); tip.IsValid() { 337 | ptrTip = tip 338 | } 339 | } 340 | typeTip := tl.TipTypeNamed 341 | if typeTipRx, ok := gen.tr.TypeTipRx(tl.TipScopeFunction, decl.Name); ok { 342 | if tip := typeTipRx.Self(); tip.IsValid() { 343 | typeTip = tip 344 | } 345 | } 346 | gen.writeFunctionDeclaration(wr, decl, ptrTip, typeTip, public) 347 | } 348 | writeSpace(wr, 1) 349 | count++ 350 | } 351 | return count 352 | } 353 | 354 | func (gen *Generator) Close() { 355 | if gen.closed { 356 | return 357 | } 358 | gen.closed = true 359 | gen.closeC <- struct{}{} 360 | <-gen.doneC 361 | } 362 | 363 | func (gen *Generator) MonitorAndWriteHelpers(goWr, chWr io.Writer, ccWr io.Writer, initWrFunc ...func() (io.Writer, error)) { 364 | seenHelperNames := make(map[string]bool) 365 | var seenGoHelper bool 366 | var seenCHHelper bool 367 | var seenCCHelper bool 368 | for { 369 | select { 370 | case <-gen.closeC: 371 | close(gen.helpersChan) 372 | case helper, ok := <-gen.helpersChan: 373 | if !ok { 374 | close(gen.doneC) 375 | return 376 | } 377 | if seenHelperNames[string(helper.Side)+helper.Name] { 378 | continue 379 | } 380 | seenHelperNames[string(helper.Side)+helper.Name] = true 381 | 382 | var wr io.Writer 383 | switch helper.Side { 384 | case NoSide, GoSide: 385 | if goWr != nil { 386 | wr = goWr 387 | } else if len(initWrFunc) < 1 { 388 | continue 389 | } else if w, err := initWrFunc[0](); err != nil { 390 | continue 391 | } else { 392 | wr = w 393 | } 394 | if !seenGoHelper { 395 | gen.writeGoHelpersHeader(wr) 396 | seenGoHelper = true 397 | } 398 | case CHSide: 399 | if chWr != nil { 400 | wr = chWr 401 | } else if len(initWrFunc) < 2 { 402 | continue 403 | } else if w, err := initWrFunc[1](); err != nil { 404 | continue 405 | } else { 406 | wr = w 407 | } 408 | if !seenCHHelper { 409 | gen.writeCHHelpersHeader(wr) 410 | seenCHHelper = true 411 | } 412 | case CCSide: 413 | if ccWr != nil { 414 | wr = ccWr 415 | } else if len(initWrFunc) < 3 { 416 | continue 417 | } else if w, err := initWrFunc[2](); err != nil { 418 | continue 419 | } else { 420 | wr = w 421 | } 422 | if !seenCCHelper { 423 | gen.writeCCHelpersHeader(wr) 424 | seenCCHelper = true 425 | } 426 | default: 427 | continue 428 | } 429 | 430 | if len(helper.Description) > 0 { 431 | writeTextBlock(wr, helper.Description) 432 | } 433 | writeSourceBlock(wr, helper.Source) 434 | writeSpace(wr, 1) 435 | } 436 | } 437 | } 438 | 439 | // randPostfix generates a simply random 4-byte postfix. Doesn't require a crypto package. 440 | func (gen *Generator) randPostfix() int32 { 441 | return 0x0f000000 + gen.rand.Int31n(0x00ffffff) 442 | } 443 | 444 | type tagDef struct { 445 | tagName string 446 | tagDecl *tl.CDecl 447 | } 448 | 449 | func sortedTagDefs(m map[string]*tl.CDecl) []tagDef { 450 | tagDefs := make([]tagDef, 0, len(m)) 451 | for tag, decl := range m { 452 | tagDefs = append(tagDefs, tagDef{ 453 | tagName: tag, 454 | tagDecl: decl, 455 | }) 456 | } 457 | sort.Slice(tagDefs, func(i, j int) bool { 458 | return tagDefs[i].tagName < tagDefs[j].tagName 459 | }) 460 | return tagDefs 461 | } 462 | 463 | func NewMemSpec(spec string) MemSpec { 464 | switch s := MemSpec(spec); s { 465 | case MemSpec1, 466 | MemSpec2, 467 | MemSpec3, 468 | MemSpec4, 469 | MemSpec5, 470 | MemSpec6, 471 | MemSpec7, 472 | MemSpec8, 473 | MemSpec9, 474 | MemSpecA, 475 | MemSpecB, 476 | MemSpecC, 477 | MemSpecD, 478 | MemSpecE, 479 | MemSpecF: 480 | return s 481 | default: 482 | return MemSpecDefault 483 | } 484 | } 485 | 486 | type MemSpec string 487 | 488 | const ( 489 | MemSpec1 MemSpec = "0x1fffffff" 490 | MemSpec2 MemSpec = "0x2fffffff" 491 | MemSpec3 MemSpec = "0x3fffffff" 492 | MemSpec4 MemSpec = "0x4fffffff" 493 | MemSpec5 MemSpec = "0x5fffffff" 494 | MemSpec6 MemSpec = "0x6fffffff" 495 | MemSpec7 MemSpec = "0x7fffffff" 496 | MemSpec8 MemSpec = "0x8fffffff" 497 | MemSpec9 MemSpec = "0x9fffffff" 498 | MemSpecA MemSpec = "0xafffffff" 499 | MemSpecB MemSpec = "0xbfffffff" 500 | MemSpecC MemSpec = "0xcfffffff" 501 | MemSpecD MemSpec = "0xdfffffff" 502 | MemSpecE MemSpec = "0xefffffff" 503 | MemSpecF MemSpec = "0xffffffff" 504 | 505 | MemSpecDefault MemSpec = "0x7fffffff" 506 | ) 507 | 508 | func (g *Generator) SetMaxMemory(spec MemSpec) { 509 | g.maxMem = spec 510 | } 511 | -------------------------------------------------------------------------------- /translator/ast_walker.go: -------------------------------------------------------------------------------- 1 | package translator 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "modernc.org/cc/v4" 8 | "modernc.org/token" 9 | ) 10 | 11 | func (t *Translator) walkTranslationUnit(unit *cc.TranslationUnit) { 12 | for unit != nil { 13 | t.walkExternalDeclaration(unit.ExternalDeclaration) 14 | unit = unit.TranslationUnit 15 | } 16 | } 17 | 18 | func (t *Translator) walkExternalDeclaration(d *cc.ExternalDeclaration) { 19 | if t.IsTokenIgnored(d.Position()) { 20 | return 21 | } 22 | switch d.Case { 23 | case cc.ExternalDeclarationFuncDef: 24 | var decl *CDecl 25 | if declr := d.FunctionDefinition.Declarator; declr != nil { 26 | decl = t.declarator(declr.Type(), identifierOf(declr.DirectDeclarator), declr.IsTypename(), declr.IsStatic(), declr.IsConst(), declr.Position()) 27 | t.registerTagsOf(decl) 28 | } else { 29 | return 30 | } 31 | if decl.IsTypedef { 32 | t.typedefs = append(t.typedefs, decl) 33 | t.typedefsSet[decl.Name] = struct{}{} 34 | return 35 | } 36 | t.declares = append(t.declares, decl) 37 | case cc.ExternalDeclarationDecl: 38 | declares := t.walkDeclaration(d.Declaration) 39 | for _, decl := range declares { 40 | if decl.IsTypedef { 41 | t.typedefs = append(t.typedefs, decl) 42 | t.typedefsSet[decl.Name] = struct{}{} 43 | continue 44 | } 45 | t.declares = append(t.declares, decl) 46 | } 47 | } 48 | } 49 | 50 | func (t *Translator) walkDeclaration(d *cc.Declaration) (declared []*CDecl) { 51 | if t.IsTokenIgnored(d.Position()) { 52 | return 53 | } 54 | list := d.InitDeclaratorList 55 | if list != nil { 56 | for list != nil { 57 | declr := list.InitDeclarator.Declarator 58 | decl := t.declarator(declr.Type(), identifierOf(declr.DirectDeclarator), declr.IsTypename(), declr.IsStatic(), declr.IsConst(), declr.Position()) 59 | init := list.InitDeclarator.Initializer 60 | if init != nil && init.AssignmentExpression != nil { 61 | decl.Value = init.AssignmentExpression.Value 62 | decl.Expression = blessName(init.Token.SrcStr()) 63 | } 64 | t.registerTagsOf(decl) 65 | declared = append(declared, decl) 66 | if init != nil { 67 | t.valueMap[decl.Name] = decl.Value 68 | t.exprMap[decl.Name] = decl.Expression 69 | } 70 | list = list.InitDeclaratorList 71 | } 72 | } else if declr := d.Declarator; declr != nil { 73 | decl := t.declarator(declr.Type(), identifierOf(declr.DirectDeclarator), declr.IsTypename(), declr.IsStatic(), declr.IsConst(), declr.Position()) 74 | t.registerTagsOf(decl) 75 | declared = append(declared, decl) 76 | } else if dspec := d.DeclarationSpecifiers; dspec != nil { 77 | typeSpec := dspec.TypeSpecifier 78 | if typeSpec != nil { 79 | if structSpec := typeSpec.StructOrUnionSpecifier; structSpec != nil { 80 | decl := t.declarator(structSpec.Type(), "", structSpec.Type().Typedef() != nil, false, false, structSpec.Position()) 81 | t.registerTagsOf(decl) 82 | declared = append(declared, decl) 83 | } else if enumSpec := typeSpec.EnumSpecifier; enumSpec != nil { 84 | decl := t.declarator(enumSpec.Type(), "", enumSpec.Type().Typedef() != nil, false, false, enumSpec.Position()) 85 | t.registerTagsOf(decl) 86 | declared = append(declared, decl) 87 | } 88 | } 89 | } 90 | return 91 | } 92 | 93 | func (t *Translator) declarator(typ cc.Type, name string, isTypedef bool, isStatic bool, isConst bool, position token.Position) *CDecl { 94 | decl := &CDecl{ 95 | Spec: t.typeSpec(typ, name, 0, isConst, false), 96 | Name: name, 97 | IsTypedef: isTypedef, 98 | IsStatic: isStatic, 99 | Position: position, 100 | } 101 | return decl 102 | } 103 | 104 | func (t *Translator) getStructTag(typ cc.Type) (tag string) { 105 | var tagToken cc.Token 106 | if structType, ok := typ.(*cc.StructType); ok { 107 | tagToken = structType.Tag() 108 | } else if unionType, ok := typ.(*cc.UnionType); ok { 109 | tagToken = unionType.Tag() 110 | } 111 | return blessName(tagToken.SrcStr()) 112 | } 113 | 114 | func (t *Translator) enumSpec(base *CTypeSpec, typ cc.Type) *CEnumSpec { 115 | tag := blessName(typ.Typedef().Name()) 116 | spec := &CEnumSpec{ 117 | Tag: tag, 118 | Pointers: base.Pointers, 119 | OuterArr: base.OuterArr, 120 | InnerArr: base.InnerArr, 121 | } 122 | if enumType, ok := typ.(*cc.EnumType); ok { 123 | list := enumType.Enumerators() 124 | for _, en := range list { 125 | name := blessName(en.Token.SrcStr()) 126 | m := &CDecl{ 127 | Name: name, 128 | Position: en.Token.Position(), 129 | } 130 | switch { 131 | case t.constRules[ConstEnum] == ConstCGOAlias: 132 | m.Expression = fmt.Sprintf("C.%s", name) 133 | case t.constRules[ConstEnum] == ConstExpand: 134 | enTokens := cc.NodeTokens(en.ConstantExpression) 135 | srcParts := make([]string, 0, len(enTokens)) 136 | exprParts := make([]string, 0, len(enTokens)) 137 | valid := true 138 | 139 | // TODO: needsTypecast is never true here 140 | // TODO: some state machine 141 | needsTypecast := false 142 | typecastValue := false 143 | typecastValueParens := 0 144 | 145 | for _, token := range enTokens { 146 | src := token.SrcStr() 147 | srcParts = append(srcParts, src) 148 | switch token.Ch { 149 | case rune(cc.IDENTIFIER): 150 | exprParts = append(exprParts, string(t.TransformName(TargetConst, src, true))) 151 | default: 152 | // TODO: state machine 153 | const ( 154 | lparen = rune(40) 155 | rparen = rune(41) 156 | ) 157 | switch { 158 | case needsTypecast && token.Ch == rparen: 159 | typecastValue = true 160 | needsTypecast = false 161 | exprParts = append(exprParts, src+"(") 162 | case typecastValue && token.Ch == lparen: 163 | typecastValueParens++ 164 | case typecastValue && token.Ch == rparen: 165 | if typecastValueParens == 0 { 166 | typecastValue = false 167 | exprParts = append(exprParts, ")"+src) 168 | } else { 169 | typecastValueParens-- 170 | } 171 | default: 172 | // somewhere in the world a helpless kitten died because of this 173 | if token.Ch == '~' { 174 | src = "^" 175 | } 176 | if runes := []rune(src); len(runes) > 0 && isNumeric(runes) { 177 | // TODO(xlab): better const handling 178 | src = readNumeric(runes) 179 | } 180 | exprParts = append(exprParts, src) 181 | } 182 | } 183 | if !valid { 184 | break 185 | } 186 | } 187 | if typecastValue { 188 | // still in typecast value, need to close paren 189 | exprParts = append(exprParts, ")") 190 | typecastValue = false 191 | } 192 | 193 | if len(exprParts) > 0 { 194 | m.Expression = strings.Join(exprParts, " ") 195 | } else { 196 | m.Value = en.Value() 197 | } 198 | m.Src = strings.Join(srcParts, " ") 199 | default: 200 | m.Value = en.Value() 201 | } 202 | m.Spec = spec.PromoteType(en.Value()) 203 | spec.Members = append(spec.Members, m) 204 | t.valueMap[m.Name] = en.Value() 205 | t.exprMap[m.Name] = m.Expression 206 | } 207 | } 208 | 209 | return spec 210 | } 211 | 212 | const maxDeepLevel = 3 213 | 214 | func (t *Translator) structSpec(base *CTypeSpec, typ cc.Type, deep int) *CStructSpec { 215 | tag := t.getStructTag(typ) 216 | spec := &CStructSpec{ 217 | Tag: tag, 218 | IsUnion: typ.Kind() == cc.Union, 219 | Pointers: base.Pointers, 220 | OuterArr: base.OuterArr, 221 | InnerArr: base.InnerArr, 222 | } 223 | if deep > maxDeepLevel { 224 | return spec 225 | } 226 | if structType, ok := typ.(*cc.StructType); ok { 227 | for i := 0; i < structType.NumFields(); i++ { 228 | m := structType.FieldByIndex(i) 229 | var position token.Position 230 | var name string 231 | var isConst bool 232 | if m.Declarator() != nil { 233 | position = m.Declarator().Position() 234 | name = identifierOf(m.Declarator().DirectDeclarator) 235 | isConst = m.Declarator().IsConst() 236 | } 237 | spec.Members = append(spec.Members, &CDecl{ 238 | Name: memberName(i, m), 239 | Spec: t.typeSpec(m.Type(), name, deep+1, isConst, false), 240 | Position: position, 241 | }) 242 | } 243 | } 244 | return spec 245 | } 246 | 247 | func (t *Translator) functionSpec(base *CTypeSpec, typ cc.Type, name string, isConst bool, deep int) *CFunctionSpec { 248 | spec := &CFunctionSpec{ 249 | Pointers: base.Pointers, 250 | } 251 | if deep > 2 { // a function inside params of another function 252 | // Replicate cc v1 behaviour. 253 | spec.Raw = base.Raw 254 | spec.Typedef = base.Raw 255 | } else { 256 | spec.Raw = name 257 | } 258 | if deep > maxDeepLevel { 259 | return spec 260 | } 261 | if funcType, ok := typ.(*cc.FunctionType); ok { 262 | if ret := funcType.Result(); ret != nil && ret.Kind() != cc.Void { 263 | // function result type cannot be declarator of a function definition 264 | // so we use typedef here 265 | var name string 266 | var isResultConst = isConst 267 | if funcType.Result().Typedef() != nil { 268 | name = identifierOf(funcType.Result().Typedef().DirectDeclarator) 269 | isResultConst = isResultConst || funcType.Result().Typedef().IsConst() 270 | } 271 | spec.Return = t.typeSpec(ret, name, deep+1, isResultConst, true) 272 | } 273 | params := funcType.Parameters() 274 | for i, p := range params { 275 | if p.Type().Kind() != cc.Void { // cc v4 may return a "void" parameter, which means no parameters. 276 | var name string 277 | var isParamConst bool 278 | if p.Declarator != nil { 279 | name = identifierOf(p.Declarator.DirectDeclarator) 280 | isParamConst = p.Declarator.IsConst() 281 | } 282 | spec.Params = append(spec.Params, &CDecl{ 283 | Name: paramName(i, p), 284 | Spec: t.typeSpec(p.Type(), name, deep+1, isParamConst, false), 285 | Position: p.Declarator.Position(), 286 | }) 287 | } 288 | } 289 | } 290 | return spec 291 | } 292 | 293 | func (t *Translator) typeSpec(typ cc.Type, name string, deep int, isConst bool, isRet bool) CType { 294 | spec := &CTypeSpec{ 295 | Const: isConst, 296 | } 297 | if !isRet { 298 | spec.Raw = typedefNameOf(typ) 299 | } 300 | 301 | for outerArrayType, ok := typ.(*cc.ArrayType); ok; outerArrayType, ok = typ.(*cc.ArrayType) { 302 | size := outerArrayType.Len() 303 | typ = outerArrayType.Elem() 304 | if size >= 0 { 305 | spec.AddOuterArr(uint64(size)) 306 | } 307 | } 308 | var isVoidPtr bool 309 | for pointerType, ok := typ.(*cc.PointerType); ok; pointerType, ok = typ.(*cc.PointerType) { 310 | if next := pointerType.Elem(); next.Kind() == cc.Void { 311 | isVoidPtr = true 312 | spec.Base = "void*" 313 | break 314 | } 315 | typ = pointerType.Elem() 316 | spec.Pointers++ 317 | } 318 | for innerArrayType, ok := typ.(*cc.ArrayType); ok; innerArrayType, ok = typ.(*cc.ArrayType) { 319 | size := innerArrayType.Len() 320 | typ = innerArrayType.Elem() 321 | if size >= 0 { 322 | spec.AddInnerArr(uint64(size)) 323 | } 324 | } 325 | if isVoidPtr { 326 | return spec 327 | } 328 | 329 | switch typ.Kind() { 330 | case cc.Void: 331 | spec.Base = "void" 332 | case cc.Char: 333 | spec.Base = "char" 334 | case cc.SChar: 335 | spec.Base = "char" 336 | spec.Signed = true 337 | case cc.UChar: 338 | spec.Base = "char" 339 | spec.Unsigned = true 340 | case cc.Short: 341 | spec.Base = "int" 342 | spec.Short = true 343 | case cc.UShort: 344 | spec.Base = "int" 345 | spec.Short = true 346 | spec.Unsigned = true 347 | case cc.Int: 348 | spec.Base = "int" 349 | case cc.UInt: 350 | spec.Base = "int" 351 | spec.Unsigned = true 352 | case cc.Int128: 353 | spec.Base = "int" 354 | spec.Long = true 355 | case cc.Long: 356 | spec.Base = "int" 357 | spec.Long = true 358 | case cc.ULong: 359 | spec.Base = "int" 360 | spec.Long = true 361 | spec.Unsigned = true 362 | case cc.UInt128: 363 | spec.Base = "int" 364 | spec.Long = true 365 | spec.Unsigned = true 366 | case cc.LongLong: 367 | spec.Base = "long" 368 | spec.Long = true 369 | case cc.ULongLong: 370 | spec.Base = "long" 371 | spec.Long = true 372 | spec.Unsigned = true 373 | case cc.Float, cc.Float32, cc.Float32x: 374 | spec.Base = "float" 375 | case cc.Double, cc.Float64, cc.Float64x: 376 | spec.Base = "double" 377 | case cc.LongDouble, cc.Float128, cc.Float128x: 378 | spec.Base = "double" 379 | spec.Long = true 380 | case cc.Bool: 381 | spec.Base = "_Bool" 382 | // according to C99, _Bool is unsigned, but the flag is not used here. 383 | case cc.ComplexFloat: 384 | spec.Base = "complexfloat" 385 | spec.Complex = true 386 | case cc.ComplexDouble: 387 | spec.Base = "complexdouble" 388 | spec.Complex = true 389 | case cc.ComplexLongDouble: 390 | spec.Base = "complexdouble" 391 | spec.Long = true 392 | spec.Complex = true 393 | case cc.Enum: 394 | s := t.enumSpec(spec, typ) 395 | if !isRet { 396 | s.Typedef = typedefNameOf(typ) 397 | } 398 | return s 399 | case cc.Union: 400 | return &CStructSpec{ 401 | Tag: t.getStructTag(typ), 402 | IsUnion: true, 403 | Pointers: spec.Pointers, 404 | OuterArr: spec.OuterArr, 405 | InnerArr: spec.InnerArr, 406 | Typedef: typedefNameOf(typ), 407 | } 408 | case cc.Struct: 409 | s := t.structSpec(spec, typ, deep+1) 410 | if !isRet { 411 | s.Typedef = typedefNameOf(typ) 412 | } 413 | return s 414 | case cc.Function: 415 | s := t.functionSpec(spec, typ, name, isConst, deep+1) 416 | if !isRet && typ.Typedef() == nil { 417 | if funcType, ok := typ.(*cc.FunctionType); ok { 418 | // According to comparison with cc v1 behavior, typedef is set to return value typedef 419 | // if this is not a function typedef. 420 | // This is somehow strange, and probably causes some bugs, but I'm trying to replicate previous behaviour here. 421 | retTypedef := typedefNameOf(funcType.Result()) 422 | if s.Typedef == "" { 423 | s.Typedef = retTypedef 424 | } 425 | if s.Return != nil { 426 | s.Return.SetRaw(retTypedef) 427 | } 428 | } 429 | } 430 | return s 431 | default: 432 | panic("unknown type " + typ.String()) 433 | } 434 | 435 | return spec 436 | } 437 | 438 | func paramName(n int, p *cc.Parameter) string { 439 | if len(p.Name()) == 0 { 440 | return fmt.Sprintf("arg%d", n) 441 | } 442 | return blessName(p.Name()) 443 | } 444 | 445 | func memberName(n int, f *cc.Field) string { 446 | if len(f.Name()) == 0 { 447 | return fmt.Sprintf("field%d", n) 448 | } 449 | return blessName(f.Name()) 450 | } 451 | 452 | func typedefNameOf(typ cc.Type) string { 453 | var name string 454 | typeDef := typ.Typedef() 455 | if typeDef != nil { 456 | name = blessName(typeDef.Name()) 457 | 458 | if len(name) == 0 { 459 | name = identifierOf(typeDef.DirectDeclarator) 460 | } 461 | } 462 | 463 | if len(name) == 0 { 464 | if pointerType, ok := typ.(*cc.PointerType); ok { 465 | name = typedefNameOf(pointerType.Elem()) 466 | } else if arrayType, ok := typ.(*cc.ArrayType); ok { 467 | name = typedefNameOf(arrayType.Elem()) 468 | } 469 | } 470 | 471 | return name 472 | } 473 | 474 | func identifierOf(dd *cc.DirectDeclarator) string { 475 | if dd == nil { 476 | return "" 477 | } 478 | switch dd.Case { 479 | case cc.DirectDeclaratorIdent: // IDENTIFIER 480 | return blessName(dd.Token.SrcStr()) 481 | case cc.DirectDeclaratorDecl: // '(' Declarator ')' 482 | return identifierOf(dd.Declarator.DirectDeclarator) 483 | default: 484 | // DirectDeclarator '[' TypeQualifierListOpt ExpressionOpt ']' 485 | // DirectDeclarator '[' "static" TypeQualifierListOpt Expression ']' 486 | // DirectDeclarator '[' TypeQualifierList "static" Expression ']' 487 | // DirectDeclarator '[' TypeQualifierListOpt '*' ']' 488 | // DirectDeclarator '(' ParameterTypeList ')' 489 | // DirectDeclarator '(' IdentifierListOpt ')' 490 | return identifierOf(dd.DirectDeclarator) 491 | } 492 | } 493 | -------------------------------------------------------------------------------- /translator/ast_walker_test.go: -------------------------------------------------------------------------------- 1 | package translator 2 | 3 | import ( 4 | "runtime" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "modernc.org/cc/v4" 9 | ) 10 | 11 | func NewTestTranslator(t *testing.T, code string, useSimplePredefs bool) *Translator { 12 | config, _ := cc.NewConfig(runtime.GOOS, runtime.GOARCH) 13 | var sources []cc.Source 14 | if useSimplePredefs { 15 | // easier to debug and sufficient for most tests 16 | sources = append(sources, cc.Source{Name: "", Value: "typedef unsigned size_t; int __predefined_declarator;"}) 17 | } else { 18 | sources = append(sources, cc.Source{Name: "", Value: config.Predefined}) 19 | sources = append(sources, cc.Source{Name: "", Value: cc.Builtin}) 20 | } 21 | sources = append(sources, cc.Source{Value: code}) 22 | ast, err := cc.Translate(config, sources) 23 | assert.Equal(t, nil, err) 24 | translator, err := New( 25 | &Config{ 26 | Rules: Rules(map[RuleTarget][]RuleSpec{"const": {{From: "^TEST", Action: "accept"}}}), 27 | }, 28 | ) 29 | assert.Equal(t, nil, err) 30 | translator.Learn(ast) 31 | return translator 32 | } 33 | 34 | func TestFunctionVoidParam(t *testing.T) { 35 | translator := NewTestTranslator(t, "void testfunc(void);", true) 36 | declarationFound := false 37 | for _, d := range translator.declares { 38 | if d.Name == "testfunc" { 39 | assert.False(t, declarationFound) 40 | declarationFound = true 41 | assert.Equal(t, false, d.IsStatic) 42 | assert.Equal(t, false, d.IsTypedef) 43 | assert.Equal(t, false, d.IsDefine) 44 | assert.Equal(t, "testfunc", d.Spec.GetTag()) 45 | assert.Equal(t, "", d.Spec.GetBase()) 46 | assert.Equal(t, FunctionKind, d.Spec.Kind()) 47 | assert.Equal(t, false, d.Spec.IsConst()) 48 | assert.Equal(t, true, d.Spec.IsComplete()) 49 | assert.Equal(t, true, d.Spec.IsOpaque()) 50 | funcSpec, ok := d.Spec.(*CFunctionSpec) 51 | if assert.True(t, ok) { 52 | assert.Equal(t, 0, len(funcSpec.Params)) 53 | assert.Equal(t, "", funcSpec.Typedef) 54 | assert.Nil(t, funcSpec.Return) 55 | } 56 | } 57 | } 58 | assert.Equal(t, true, declarationFound) 59 | } 60 | 61 | func TestFunctionNoParam(t *testing.T) { 62 | translator := NewTestTranslator(t, "void testfunc();", true) 63 | declarationFound := false 64 | for _, d := range translator.declares { 65 | if d.Name == "testfunc" { 66 | assert.False(t, declarationFound) 67 | declarationFound = true 68 | assert.Equal(t, false, d.IsStatic) 69 | assert.Equal(t, false, d.IsTypedef) 70 | assert.Equal(t, false, d.IsDefine) 71 | assert.Equal(t, "testfunc", d.Spec.GetTag()) 72 | assert.Equal(t, "", d.Spec.GetBase()) 73 | assert.Equal(t, FunctionKind, d.Spec.Kind()) 74 | assert.Equal(t, false, d.Spec.IsConst()) 75 | assert.Equal(t, true, d.Spec.IsComplete()) 76 | assert.Equal(t, true, d.Spec.IsOpaque()) 77 | funcSpec, ok := d.Spec.(*CFunctionSpec) 78 | if assert.True(t, ok) { 79 | assert.Equal(t, 0, len(funcSpec.Params)) 80 | assert.Equal(t, "", funcSpec.Typedef) 81 | assert.Nil(t, funcSpec.Return) 82 | } 83 | } 84 | } 85 | assert.Equal(t, true, declarationFound) 86 | } 87 | 88 | func TestFunctionVoidPtrParam(t *testing.T) { 89 | translator := NewTestTranslator(t, "void testfunc(void *);", true) 90 | declarationFound := false 91 | for _, d := range translator.declares { 92 | if d.Name == "testfunc" { 93 | assert.False(t, declarationFound) 94 | declarationFound = true 95 | assert.Equal(t, false, d.IsStatic) 96 | assert.Equal(t, false, d.IsTypedef) 97 | assert.Equal(t, false, d.IsDefine) 98 | assert.Equal(t, "testfunc", d.Spec.GetTag()) 99 | assert.Equal(t, "", d.Spec.GetBase()) 100 | assert.Equal(t, FunctionKind, d.Spec.Kind()) 101 | assert.Equal(t, false, d.Spec.IsConst()) 102 | assert.Equal(t, true, d.Spec.IsComplete()) 103 | assert.Equal(t, false, d.Spec.IsOpaque()) 104 | funcSpec, ok := d.Spec.(*CFunctionSpec) 105 | if assert.True(t, ok) { 106 | assert.Equal(t, 1, len(funcSpec.Params)) 107 | assert.Equal(t, "", funcSpec.Typedef) 108 | assert.Nil(t, funcSpec.Return) 109 | param := funcSpec.Params[0] 110 | assert.Equal(t, "arg0", param.Name) 111 | assert.Equal(t, false, param.IsStatic) 112 | assert.Equal(t, false, param.IsTypedef) 113 | assert.Equal(t, false, param.IsDefine) 114 | assert.Equal(t, TypeKind, param.Spec.Kind()) 115 | paramSpec, ok := param.Spec.(*CTypeSpec) 116 | if assert.True(t, ok) { 117 | assert.Equal(t, false, paramSpec.IsConst()) 118 | assert.Equal(t, true, paramSpec.IsComplete()) 119 | assert.Equal(t, false, paramSpec.IsOpaque()) 120 | assert.Equal(t, "void*", paramSpec.GetBase()) 121 | // void pointer is special 122 | assert.Equal(t, uint8(0), paramSpec.Pointers) 123 | assert.Equal(t, "", paramSpec.Raw) 124 | } 125 | } 126 | } 127 | } 128 | assert.Equal(t, true, declarationFound) 129 | } 130 | 131 | func TestFunctionVoidPtrTypedefParam(t *testing.T) { 132 | translator := NewTestTranslator(t, "typedef void* testID; void testfunc(testID id);", true) 133 | declarationFound := false 134 | for _, d := range translator.declares { 135 | if d.Name == "testfunc" { 136 | assert.False(t, declarationFound) 137 | declarationFound = true 138 | assert.Equal(t, false, d.IsStatic) 139 | assert.Equal(t, false, d.IsTypedef) 140 | assert.Equal(t, false, d.IsDefine) 141 | assert.Equal(t, "testfunc", d.Spec.GetTag()) 142 | assert.Equal(t, "", d.Spec.GetBase()) 143 | assert.Equal(t, FunctionKind, d.Spec.Kind()) 144 | assert.Equal(t, false, d.Spec.IsConst()) 145 | assert.Equal(t, true, d.Spec.IsComplete()) 146 | assert.Equal(t, false, d.Spec.IsOpaque()) 147 | funcSpec, ok := d.Spec.(*CFunctionSpec) 148 | if assert.True(t, ok) { 149 | assert.Equal(t, 1, len(funcSpec.Params)) 150 | assert.Equal(t, "", funcSpec.Typedef) 151 | assert.Nil(t, funcSpec.Return) 152 | param := funcSpec.Params[0] 153 | assert.Equal(t, "id", param.Name) 154 | assert.Equal(t, false, param.IsStatic) 155 | assert.Equal(t, false, param.IsTypedef) 156 | assert.Equal(t, false, param.IsDefine) 157 | assert.Equal(t, TypeKind, param.Spec.Kind()) 158 | paramSpec, ok := param.Spec.(*CTypeSpec) 159 | if assert.True(t, ok) { 160 | assert.Equal(t, false, paramSpec.IsConst()) 161 | assert.Equal(t, true, paramSpec.IsComplete()) 162 | assert.Equal(t, false, paramSpec.IsOpaque()) 163 | assert.Equal(t, "void*", paramSpec.GetBase()) 164 | // void pointer is special 165 | assert.Equal(t, uint8(0), paramSpec.Pointers) 166 | assert.Equal(t, "testID", paramSpec.Raw) 167 | } 168 | } 169 | } 170 | } 171 | assert.Equal(t, true, declarationFound) 172 | } 173 | 174 | func TestFunctionIntParam(t *testing.T) { 175 | translator := NewTestTranslator(t, "void testfunc(int testParam);", true) 176 | declarationFound := false 177 | for _, d := range translator.declares { 178 | if d.Name == "testfunc" { 179 | assert.False(t, declarationFound) 180 | declarationFound = true 181 | assert.Equal(t, false, d.IsStatic) 182 | assert.Equal(t, false, d.IsTypedef) 183 | assert.Equal(t, false, d.IsDefine) 184 | assert.Equal(t, "testfunc", d.Spec.GetTag()) 185 | assert.Equal(t, "", d.Spec.GetBase()) 186 | assert.Equal(t, FunctionKind, d.Spec.Kind()) 187 | assert.Equal(t, false, d.Spec.IsConst()) 188 | assert.Equal(t, true, d.Spec.IsComplete()) 189 | assert.Equal(t, false, d.Spec.IsOpaque()) 190 | funcSpec, ok := d.Spec.(*CFunctionSpec) 191 | if assert.True(t, ok) { 192 | assert.Equal(t, "", funcSpec.Typedef) 193 | assert.Nil(t, funcSpec.Return) 194 | if assert.Equal(t, 1, len(funcSpec.Params)) { 195 | param := funcSpec.Params[0] 196 | assert.Equal(t, "testParam", param.Name) 197 | assert.Equal(t, false, param.IsStatic) 198 | assert.Equal(t, false, param.IsTypedef) 199 | assert.Equal(t, false, param.IsDefine) 200 | assert.Equal(t, TypeKind, param.Spec.Kind()) 201 | paramSpec, ok := param.Spec.(*CTypeSpec) 202 | if assert.True(t, ok) { 203 | assert.Equal(t, false, paramSpec.IsConst()) 204 | assert.Equal(t, true, paramSpec.IsComplete()) 205 | assert.Equal(t, false, paramSpec.IsOpaque()) 206 | assert.Equal(t, "int", paramSpec.GetBase()) 207 | assert.Equal(t, uint8(0), paramSpec.Pointers) 208 | assert.Equal(t, "", paramSpec.Raw) 209 | } 210 | } 211 | } 212 | } 213 | } 214 | assert.Equal(t, true, declarationFound) 215 | } 216 | 217 | func TestFunctionStringReturn(t *testing.T) { 218 | translator := NewTestTranslator(t, "const char* testfunc();", true) 219 | declarationFound := false 220 | for _, d := range translator.declares { 221 | if d.Name == "testfunc" { 222 | assert.False(t, declarationFound) 223 | declarationFound = true 224 | assert.Equal(t, false, d.IsStatic) 225 | assert.Equal(t, false, d.IsTypedef) 226 | assert.Equal(t, false, d.IsDefine) 227 | assert.Equal(t, "testfunc", d.Spec.GetTag()) 228 | assert.Equal(t, "", d.Spec.GetBase()) 229 | assert.Equal(t, FunctionKind, d.Spec.Kind()) 230 | assert.Equal(t, false, d.Spec.IsConst()) 231 | assert.Equal(t, true, d.Spec.IsComplete()) 232 | assert.Equal(t, true, d.Spec.IsOpaque()) 233 | funcSpec, ok := d.Spec.(*CFunctionSpec) 234 | if assert.True(t, ok) { 235 | assert.Equal(t, 0, len(funcSpec.Params)) 236 | assert.Equal(t, "", funcSpec.Typedef) 237 | if assert.NotNil(t, funcSpec.Return) { 238 | returnSpec, ok := funcSpec.Return.(*CTypeSpec) 239 | if assert.True(t, ok) { 240 | assert.Equal(t, "char", returnSpec.Base) 241 | assert.Equal(t, "", returnSpec.Raw) 242 | assert.Equal(t, true, returnSpec.IsConst()) 243 | assert.Equal(t, uint8(1), returnSpec.Pointers) 244 | } 245 | } 246 | } 247 | } 248 | } 249 | assert.Equal(t, true, declarationFound) 250 | } 251 | 252 | func TestTypedefInt(t *testing.T) { 253 | translator := NewTestTranslator(t, "typedef int TestVal;", true) 254 | typedefFound := false 255 | for _, d := range translator.declares { 256 | assert.NotEqual(t, "TestVal", d.Name, "Typedefs should not be in the list of declarations.") 257 | } 258 | for _, d := range translator.typedefs { 259 | if d.Name == "TestVal" { 260 | assert.False(t, typedefFound) 261 | typedefFound = true 262 | assert.Equal(t, false, d.IsStatic) 263 | assert.Equal(t, true, d.IsTypedef) 264 | assert.Equal(t, false, d.IsDefine) 265 | assert.Equal(t, "", d.Spec.GetTag()) 266 | assert.Equal(t, "int", d.Spec.GetBase()) 267 | assert.Equal(t, TypeKind, d.Spec.Kind()) 268 | assert.Equal(t, false, d.Spec.IsConst()) 269 | assert.Equal(t, true, d.Spec.IsComplete()) 270 | assert.Equal(t, false, d.Spec.IsOpaque()) 271 | typeSpec, ok := d.Spec.(*CTypeSpec) 272 | if assert.True(t, ok) { 273 | assert.Equal(t, "TestVal", typeSpec.Raw) 274 | assert.Equal(t, false, typeSpec.Signed) // not explicitly signed 275 | assert.Equal(t, false, typeSpec.Unsigned) 276 | assert.Equal(t, false, typeSpec.Short) 277 | assert.Equal(t, false, typeSpec.Long) 278 | assert.Equal(t, false, typeSpec.Complex) 279 | assert.Equal(t, false, typeSpec.Opaque) 280 | } 281 | } 282 | } 283 | assert.Equal(t, true, typedefFound) 284 | } 285 | 286 | func TestStructBoolField(t *testing.T) { 287 | translator := NewTestTranslator(t, "#include \nstruct TestStruct { bool TestField; };", true) 288 | declarationFound := false 289 | for _, d := range translator.declares { 290 | if d.Spec.GetTag() == "TestStruct" { 291 | assert.False(t, declarationFound) 292 | declarationFound = true 293 | assert.Equal(t, "", d.Name) // Name is for typedef, upper struct has Tag only 294 | assert.Equal(t, false, d.IsStatic) 295 | assert.Equal(t, false, d.IsTypedef) 296 | assert.Equal(t, false, d.IsDefine) 297 | assert.Equal(t, "TestStruct", d.Spec.GetBase()) 298 | assert.Equal(t, StructKind, d.Spec.Kind()) 299 | assert.Equal(t, false, d.Spec.IsConst()) 300 | assert.Equal(t, true, d.Spec.IsComplete()) 301 | assert.Equal(t, false, d.Spec.IsOpaque()) 302 | structSpec, ok := d.Spec.(*CStructSpec) 303 | if assert.True(t, ok) { 304 | assert.Equal(t, "TestStruct", structSpec.Tag) 305 | assert.Equal(t, false, structSpec.IsUnion) 306 | assert.Equal(t, "", structSpec.Typedef) 307 | if assert.Equal(t, 1, len(structSpec.Members)) { 308 | assert.Equal(t, "TestField", structSpec.Members[0].Name) 309 | assert.Equal(t, "_Bool", structSpec.Members[0].Spec.GetBase()) 310 | typeSpec, ok := structSpec.Members[0].Spec.(*CTypeSpec) 311 | if assert.True(t, ok) { 312 | // _Bool is unsigned, but the flag is not set in this case 313 | assert.Equal(t, false, typeSpec.Unsigned) 314 | } 315 | } 316 | } 317 | } 318 | } 319 | assert.Equal(t, true, declarationFound) 320 | } 321 | 322 | func TestStructUnsignedField(t *testing.T) { 323 | translator := NewTestTranslator(t, "struct TestStruct { unsigned TestField1; int TestField2; };", true) 324 | declarationFound := false 325 | for _, d := range translator.declares { 326 | if d.Spec.GetTag() == "TestStruct" { 327 | assert.False(t, declarationFound) 328 | declarationFound = true 329 | assert.Equal(t, "", d.Name) // Name is for typedef, upper struct has Tag only 330 | assert.Equal(t, false, d.IsStatic) 331 | assert.Equal(t, false, d.IsTypedef) 332 | assert.Equal(t, false, d.IsDefine) 333 | assert.Equal(t, "TestStruct", d.Spec.GetBase()) 334 | assert.Equal(t, StructKind, d.Spec.Kind()) 335 | assert.Equal(t, false, d.Spec.IsConst()) 336 | assert.Equal(t, true, d.Spec.IsComplete()) 337 | assert.Equal(t, false, d.Spec.IsOpaque()) 338 | structSpec, ok := d.Spec.(*CStructSpec) 339 | if assert.True(t, ok) { 340 | assert.Equal(t, "TestStruct", structSpec.Tag) 341 | assert.Equal(t, false, structSpec.IsUnion) 342 | assert.Equal(t, "", structSpec.Typedef) 343 | if assert.Equal(t, 2, len(structSpec.Members)) { 344 | assert.Equal(t, "TestField1", structSpec.Members[0].Name) 345 | assert.Equal(t, "int", structSpec.Members[0].Spec.GetBase()) 346 | typeSpec1, ok := structSpec.Members[0].Spec.(*CTypeSpec) 347 | if assert.True(t, ok) { 348 | assert.Equal(t, true, typeSpec1.Unsigned) 349 | } 350 | assert.Equal(t, "TestField2", structSpec.Members[1].Name) 351 | assert.Equal(t, "int", structSpec.Members[1].Spec.GetBase()) 352 | typeSpec2, ok := structSpec.Members[1].Spec.(*CTypeSpec) 353 | if assert.True(t, ok) { 354 | assert.Equal(t, false, typeSpec2.Unsigned) 355 | } 356 | } 357 | } 358 | } 359 | } 360 | assert.Equal(t, true, declarationFound) 361 | } 362 | 363 | func TestFunctionTypedefReturn(t *testing.T) { 364 | translator := NewTestTranslator(t, "typedef unsigned int RetInt; RetInt* testfunc();", true) 365 | typedefFound := false 366 | for _, d := range translator.typedefs { 367 | if d.Name == "RetInt" { 368 | assert.False(t, typedefFound) 369 | typedefFound = true 370 | assert.Equal(t, false, d.IsStatic) 371 | assert.Equal(t, true, d.IsTypedef) 372 | assert.Equal(t, false, d.IsDefine) 373 | assert.Equal(t, "", d.Spec.GetTag()) 374 | assert.Equal(t, "int", d.Spec.GetBase()) 375 | assert.Equal(t, TypeKind, d.Spec.Kind()) 376 | assert.Equal(t, false, d.Spec.IsConst()) 377 | assert.Equal(t, true, d.Spec.IsComplete()) 378 | assert.Equal(t, false, d.Spec.IsOpaque()) 379 | typeSpec, ok := d.Spec.(*CTypeSpec) 380 | if assert.True(t, ok) { 381 | assert.Equal(t, "RetInt", typeSpec.Raw) 382 | assert.Equal(t, false, typeSpec.Signed) 383 | assert.Equal(t, true, typeSpec.Unsigned) 384 | assert.Equal(t, false, typeSpec.Short) 385 | assert.Equal(t, false, typeSpec.Long) 386 | assert.Equal(t, false, typeSpec.Complex) 387 | assert.Equal(t, false, typeSpec.Opaque) 388 | } 389 | } 390 | } 391 | declarationFound := false 392 | for _, d := range translator.declares { 393 | if d.Name == "testfunc" { 394 | assert.False(t, declarationFound) 395 | declarationFound = true 396 | assert.Equal(t, false, d.IsStatic) 397 | assert.Equal(t, false, d.IsTypedef) 398 | assert.Equal(t, false, d.IsDefine) 399 | assert.Equal(t, "testfunc", d.Spec.GetTag()) 400 | assert.Equal(t, "", d.Spec.GetBase()) 401 | assert.Equal(t, FunctionKind, d.Spec.Kind()) 402 | assert.Equal(t, false, d.Spec.IsConst()) 403 | assert.Equal(t, true, d.Spec.IsComplete()) 404 | assert.Equal(t, true, d.Spec.IsOpaque()) 405 | funcSpec, ok := d.Spec.(*CFunctionSpec) 406 | if assert.True(t, ok) { 407 | assert.Equal(t, 0, len(funcSpec.Params)) 408 | assert.Equal(t, "RetInt", funcSpec.Typedef) 409 | if assert.NotNil(t, funcSpec.Return) { 410 | returnSpec, ok := funcSpec.Return.(*CTypeSpec) 411 | if assert.True(t, ok) { 412 | assert.Equal(t, "int", returnSpec.Base) 413 | assert.Equal(t, "RetInt", returnSpec.Raw) 414 | assert.Equal(t, false, returnSpec.IsConst()) 415 | assert.Equal(t, uint8(1), returnSpec.Pointers) 416 | } 417 | } 418 | } 419 | } 420 | } 421 | assert.Equal(t, true, typedefFound) 422 | assert.Equal(t, true, declarationFound) 423 | } 424 | 425 | func TestFunctionTypedefParam(t *testing.T) { 426 | translator := NewTestTranslator(t, "typedef float ParamFloat; void testfunc(ParamFloat testparam);", true) 427 | typedefFound := false 428 | for _, d := range translator.typedefs { 429 | if d.Name == "ParamFloat" { 430 | assert.False(t, typedefFound) 431 | typedefFound = true 432 | assert.Equal(t, false, d.IsStatic) 433 | assert.Equal(t, true, d.IsTypedef) 434 | assert.Equal(t, false, d.IsDefine) 435 | assert.Equal(t, "", d.Spec.GetTag()) 436 | assert.Equal(t, "float", d.Spec.GetBase()) 437 | assert.Equal(t, TypeKind, d.Spec.Kind()) 438 | assert.Equal(t, false, d.Spec.IsConst()) 439 | assert.Equal(t, true, d.Spec.IsComplete()) 440 | assert.Equal(t, false, d.Spec.IsOpaque()) 441 | typeSpec, ok := d.Spec.(*CTypeSpec) 442 | if assert.True(t, ok) { 443 | assert.Equal(t, "ParamFloat", typeSpec.Raw) 444 | assert.Equal(t, false, typeSpec.Signed) 445 | assert.Equal(t, false, typeSpec.Unsigned) 446 | assert.Equal(t, false, typeSpec.Short) 447 | assert.Equal(t, false, typeSpec.Long) 448 | assert.Equal(t, false, typeSpec.Complex) 449 | assert.Equal(t, false, typeSpec.Opaque) 450 | } 451 | } 452 | } 453 | declarationFound := false 454 | for _, d := range translator.declares { 455 | if d.Name == "testfunc" { 456 | assert.False(t, declarationFound) 457 | declarationFound = true 458 | assert.Equal(t, false, d.IsStatic) 459 | assert.Equal(t, false, d.IsTypedef) 460 | assert.Equal(t, false, d.IsDefine) 461 | assert.Equal(t, "testfunc", d.Spec.GetTag()) 462 | assert.Equal(t, "", d.Spec.GetBase()) 463 | assert.Equal(t, FunctionKind, d.Spec.Kind()) 464 | assert.Equal(t, false, d.Spec.IsConst()) 465 | assert.Equal(t, true, d.Spec.IsComplete()) 466 | assert.Equal(t, false, d.Spec.IsOpaque()) 467 | funcSpec, ok := d.Spec.(*CFunctionSpec) 468 | if assert.True(t, ok) { 469 | assert.Equal(t, "", funcSpec.Typedef) 470 | assert.Nil(t, funcSpec.Return) 471 | if assert.Equal(t, 1, len(funcSpec.Params)) { 472 | param := funcSpec.Params[0] 473 | assert.Equal(t, "testparam", param.Name) 474 | assert.Equal(t, false, param.IsStatic) 475 | assert.Equal(t, false, param.IsTypedef) 476 | assert.Equal(t, false, param.IsDefine) 477 | assert.Equal(t, TypeKind, param.Spec.Kind()) 478 | paramSpec, ok := param.Spec.(*CTypeSpec) 479 | if assert.True(t, ok) { 480 | assert.Equal(t, false, paramSpec.IsConst()) 481 | assert.Equal(t, true, paramSpec.IsComplete()) 482 | assert.Equal(t, false, paramSpec.IsOpaque()) 483 | assert.Equal(t, "float", paramSpec.GetBase()) 484 | assert.Equal(t, uint8(0), paramSpec.Pointers) 485 | assert.Equal(t, "ParamFloat", paramSpec.Raw) 486 | } 487 | } 488 | } 489 | } 490 | } 491 | assert.Equal(t, true, typedefFound) 492 | assert.Equal(t, true, declarationFound) 493 | } 494 | 495 | func TestFunctionStdFileParam(t *testing.T) { 496 | translator := NewTestTranslator(t, "#include \nvoid testfunc(FILE *fileparam);", false) 497 | declarationFound := false 498 | for _, d := range translator.declares { 499 | if d.Name == "testfunc" { 500 | assert.False(t, declarationFound) 501 | declarationFound = true 502 | assert.Equal(t, false, d.IsStatic) 503 | assert.Equal(t, false, d.IsTypedef) 504 | assert.Equal(t, false, d.IsDefine) 505 | assert.Equal(t, "testfunc", d.Spec.GetTag()) 506 | assert.Equal(t, "", d.Spec.GetBase()) 507 | assert.Equal(t, FunctionKind, d.Spec.Kind()) 508 | assert.Equal(t, false, d.Spec.IsConst()) 509 | assert.Equal(t, true, d.Spec.IsComplete()) 510 | assert.Equal(t, false, d.Spec.IsOpaque()) 511 | funcSpec, ok := d.Spec.(*CFunctionSpec) 512 | if assert.True(t, ok) { 513 | assert.Equal(t, "", funcSpec.Typedef) 514 | assert.Nil(t, funcSpec.Return) 515 | if assert.Equal(t, 1, len(funcSpec.Params)) { 516 | param := funcSpec.Params[0] 517 | assert.Equal(t, "fileparam", param.Name) 518 | assert.Equal(t, false, param.IsStatic) 519 | assert.Equal(t, false, param.IsTypedef) 520 | assert.Equal(t, false, param.IsDefine) 521 | assert.Equal(t, StructKind, param.Spec.Kind()) 522 | structSpec, ok := param.Spec.(*CStructSpec) 523 | if assert.True(t, ok) { 524 | assert.Equal(t, "FILE", structSpec.Typedef) 525 | assert.Equal(t, false, structSpec.IsUnion) 526 | assert.Equal(t, uint8(1), structSpec.Pointers) 527 | } 528 | } 529 | } 530 | } 531 | } 532 | assert.Equal(t, true, declarationFound) 533 | } 534 | 535 | func TestFunctionTypedef(t *testing.T) { 536 | translator := NewTestTranslator(t, "typedef void (*TestCallback)(const int* testlist);\nvoid TestSetCallback(TestCallback callback);", true) 537 | typedefFound := false 538 | for _, d := range translator.typedefs { 539 | if d.Name == "TestCallback" { 540 | assert.False(t, typedefFound) 541 | typedefFound = true 542 | assert.Equal(t, false, d.IsStatic) 543 | assert.Equal(t, true, d.IsTypedef) 544 | assert.Equal(t, false, d.IsDefine) 545 | assert.Equal(t, "TestCallback", d.Spec.GetTag()) 546 | assert.Equal(t, "", d.Spec.GetBase()) 547 | assert.Equal(t, FunctionKind, d.Spec.Kind()) 548 | assert.Equal(t, false, d.Spec.IsConst()) 549 | assert.Equal(t, true, d.Spec.IsComplete()) 550 | assert.Equal(t, false, d.Spec.IsOpaque()) 551 | funcSpec, ok := d.Spec.(*CFunctionSpec) 552 | if assert.True(t, ok) { 553 | assert.Equal(t, "TestCallback", funcSpec.Raw) 554 | assert.Equal(t, "", funcSpec.Typedef) 555 | assert.Equal(t, uint8(1), funcSpec.Pointers) 556 | assert.Equal(t, nil, funcSpec.Return) 557 | assert.Equal(t, 1, len(funcSpec.Params)) 558 | } 559 | } 560 | } 561 | declarationFound := false 562 | for _, d := range translator.declares { 563 | if d.Name == "TestSetCallback" { 564 | assert.False(t, declarationFound) 565 | declarationFound = true 566 | assert.Equal(t, false, d.IsStatic) 567 | assert.Equal(t, false, d.IsTypedef) 568 | assert.Equal(t, false, d.IsDefine) 569 | assert.Equal(t, "TestSetCallback", d.Spec.GetTag()) 570 | assert.Equal(t, "", d.Spec.GetBase()) 571 | assert.Equal(t, FunctionKind, d.Spec.Kind()) 572 | assert.Equal(t, false, d.Spec.IsConst()) 573 | assert.Equal(t, true, d.Spec.IsComplete()) 574 | assert.Equal(t, false, d.Spec.IsOpaque()) 575 | funcSpec, ok := d.Spec.(*CFunctionSpec) 576 | if assert.True(t, ok) { 577 | assert.Equal(t, "", funcSpec.Typedef) 578 | assert.Nil(t, funcSpec.Return) 579 | if assert.Equal(t, 1, len(funcSpec.Params)) { 580 | param := funcSpec.Params[0] 581 | assert.Equal(t, "callback", param.Name) 582 | assert.Equal(t, false, param.IsStatic) 583 | assert.Equal(t, false, param.IsTypedef) 584 | assert.Equal(t, false, param.IsDefine) 585 | assert.Equal(t, false, param.Spec.IsConst()) 586 | assert.Equal(t, FunctionKind, param.Spec.Kind()) 587 | functionSpec, ok := param.Spec.(*CFunctionSpec) 588 | if assert.True(t, ok) { 589 | assert.Equal(t, "TestCallback", functionSpec.Raw) 590 | assert.Equal(t, "TestCallback", functionSpec.Typedef) 591 | } 592 | } 593 | } 594 | } 595 | } 596 | assert.Equal(t, true, typedefFound) 597 | assert.Equal(t, true, declarationFound) 598 | } 599 | 600 | func TestFunctionStructTypedef(t *testing.T) { 601 | translator := NewTestTranslator(t, "typedef struct TestStruct TestStruct;\nstruct TestStruct { float x; };\nvoid TestFunc(TestStruct* testStruct);", true) 602 | typedefFound := false 603 | for _, d := range translator.typedefs { 604 | if d.Name == "TestStruct" { 605 | assert.False(t, typedefFound) 606 | typedefFound = true 607 | assert.Equal(t, false, d.IsStatic) 608 | assert.Equal(t, true, d.IsTypedef) 609 | assert.Equal(t, false, d.IsDefine) 610 | assert.Equal(t, "TestStruct", d.Spec.GetTag()) 611 | assert.Equal(t, "TestStruct", d.Spec.GetBase()) 612 | assert.Equal(t, StructKind, d.Spec.Kind()) 613 | assert.Equal(t, false, d.Spec.IsConst()) 614 | assert.Equal(t, true, d.Spec.IsComplete()) 615 | assert.Equal(t, false, d.Spec.IsOpaque()) 616 | structSpec, ok := d.Spec.(*CStructSpec) 617 | if assert.True(t, ok) { 618 | assert.Equal(t, "TestStruct", structSpec.Tag) 619 | assert.Equal(t, "TestStruct", structSpec.Typedef) 620 | assert.Equal(t, uint8(0), structSpec.Pointers) 621 | if assert.Equal(t, 1, len(structSpec.Members)) { 622 | assert.Equal(t, "x", structSpec.Members[0].Name) 623 | assert.Equal(t, "float", structSpec.Members[0].Spec.GetBase()) 624 | typeSpec, ok := structSpec.Members[0].Spec.(*CTypeSpec) 625 | if assert.True(t, ok) { 626 | assert.Equal(t, false, typeSpec.Const) 627 | } 628 | } 629 | } 630 | } 631 | } 632 | declarationFound := false 633 | for _, d := range translator.declares { 634 | if d.Spec.GetTag() == "TestStruct" { 635 | assert.False(t, declarationFound) 636 | declarationFound = true 637 | assert.Equal(t, "", d.Name) // Name is for typedef 638 | assert.Equal(t, false, d.IsStatic) 639 | assert.Equal(t, false, d.IsTypedef) 640 | assert.Equal(t, false, d.IsDefine) 641 | assert.Equal(t, "TestStruct", d.Spec.GetBase()) 642 | assert.Equal(t, StructKind, d.Spec.Kind()) 643 | assert.Equal(t, false, d.Spec.IsConst()) 644 | assert.Equal(t, true, d.Spec.IsComplete()) 645 | assert.Equal(t, false, d.Spec.IsOpaque()) 646 | structSpec, ok := d.Spec.(*CStructSpec) 647 | if assert.True(t, ok) { 648 | assert.Equal(t, "TestStruct", structSpec.Tag) 649 | assert.Equal(t, false, structSpec.IsUnion) 650 | assert.Equal(t, "", structSpec.Typedef) 651 | if assert.Equal(t, 1, len(structSpec.Members)) { 652 | assert.Equal(t, "x", structSpec.Members[0].Name) 653 | assert.Equal(t, "float", structSpec.Members[0].Spec.GetBase()) 654 | typeSpec, ok := structSpec.Members[0].Spec.(*CTypeSpec) 655 | if assert.True(t, ok) { 656 | assert.Equal(t, false, typeSpec.Const) 657 | } 658 | } 659 | } 660 | } 661 | } 662 | functionFound := false 663 | for _, d := range translator.declares { 664 | if d.Name == "TestFunc" { 665 | assert.False(t, functionFound) 666 | functionFound = true 667 | assert.Equal(t, false, d.IsStatic) 668 | assert.Equal(t, false, d.IsTypedef) 669 | assert.Equal(t, false, d.IsDefine) 670 | assert.Equal(t, "TestFunc", d.Spec.GetTag()) 671 | assert.Equal(t, "", d.Spec.GetBase()) 672 | assert.Equal(t, FunctionKind, d.Spec.Kind()) 673 | assert.Equal(t, false, d.Spec.IsConst()) 674 | assert.Equal(t, true, d.Spec.IsComplete()) 675 | assert.Equal(t, false, d.Spec.IsOpaque()) 676 | funcSpec, ok := d.Spec.(*CFunctionSpec) 677 | if assert.True(t, ok) { 678 | assert.Equal(t, "", funcSpec.Typedef) 679 | assert.Nil(t, funcSpec.Return) 680 | if assert.Equal(t, 1, len(funcSpec.Params)) { 681 | param := funcSpec.Params[0] 682 | assert.Equal(t, "testStruct", param.Name) 683 | assert.Equal(t, false, param.IsStatic) 684 | assert.Equal(t, false, param.IsTypedef) 685 | assert.Equal(t, false, param.IsDefine) 686 | assert.Equal(t, false, param.Spec.IsConst()) 687 | assert.Equal(t, StructKind, param.Spec.Kind()) 688 | structSpec, ok := param.Spec.(*CStructSpec) 689 | if assert.True(t, ok) { 690 | assert.Equal(t, "TestStruct", structSpec.Typedef) 691 | assert.Equal(t, "TestStruct", structSpec.Tag) 692 | assert.Equal(t, uint8(1), structSpec.Pointers) 693 | } 694 | } 695 | } 696 | } 697 | } 698 | assert.Equal(t, true, typedefFound) 699 | assert.Equal(t, true, declarationFound) 700 | assert.Equal(t, true, functionFound) 701 | } 702 | 703 | func TestStructNestedTypedef(t *testing.T) { 704 | translator := NewTestTranslator(t, "typedef struct TestLongNameStruct { int TestArray[8]; } TestLongNameStruct;\ntypedef TestLongNameStruct TestStruct;", true) 705 | typedefFound := false 706 | for _, d := range translator.typedefs { 707 | if d.Name == "TestStruct" { 708 | assert.False(t, typedefFound) 709 | typedefFound = true 710 | assert.Equal(t, false, d.IsStatic) 711 | assert.Equal(t, true, d.IsTypedef) 712 | assert.Equal(t, false, d.IsDefine) 713 | assert.Equal(t, "TestLongNameStruct", d.Spec.GetTag()) 714 | assert.Equal(t, "TestStruct", d.Spec.GetBase()) 715 | assert.Equal(t, StructKind, d.Spec.Kind()) 716 | assert.Equal(t, false, d.Spec.IsConst()) 717 | assert.Equal(t, true, d.Spec.IsComplete()) 718 | assert.Equal(t, false, d.Spec.IsOpaque()) 719 | structSpec, ok := d.Spec.(*CStructSpec) 720 | if assert.True(t, ok) { 721 | assert.Equal(t, "TestLongNameStruct", structSpec.Tag) 722 | assert.Equal(t, "TestStruct", structSpec.Typedef) // this is different from v1 but I think it's more correct. 723 | assert.Equal(t, uint8(0), structSpec.Pointers) 724 | if assert.Equal(t, 1, len(structSpec.Members)) { 725 | assert.Equal(t, "TestArray", structSpec.Members[0].Name) 726 | assert.Equal(t, "int", structSpec.Members[0].Spec.GetBase()) 727 | typeSpec, ok := structSpec.Members[0].Spec.(*CTypeSpec) 728 | if assert.True(t, ok) { 729 | assert.Equal(t, false, typeSpec.Const) 730 | assert.Equal(t, "", typeSpec.Raw) 731 | assert.Equal(t, "", typeSpec.InnerArr.String()) 732 | assert.Equal(t, "[8]", typeSpec.OuterArr.String()) 733 | } 734 | } 735 | } 736 | } 737 | } 738 | baseTypedefFound := false 739 | for _, d := range translator.typedefs { 740 | if d.Name == "TestLongNameStruct" { 741 | assert.False(t, baseTypedefFound) 742 | baseTypedefFound = true 743 | assert.Equal(t, false, d.IsStatic) 744 | assert.Equal(t, true, d.IsTypedef) 745 | assert.Equal(t, false, d.IsDefine) 746 | assert.Equal(t, "TestLongNameStruct", d.Spec.GetTag()) 747 | assert.Equal(t, "TestLongNameStruct", d.Spec.GetBase()) 748 | assert.Equal(t, StructKind, d.Spec.Kind()) 749 | assert.Equal(t, false, d.Spec.IsConst()) 750 | assert.Equal(t, true, d.Spec.IsComplete()) 751 | assert.Equal(t, false, d.Spec.IsOpaque()) 752 | structSpec, ok := d.Spec.(*CStructSpec) 753 | if assert.True(t, ok) { 754 | assert.Equal(t, "TestLongNameStruct", structSpec.Tag) 755 | assert.Equal(t, "TestLongNameStruct", structSpec.Typedef) 756 | assert.Equal(t, uint8(0), structSpec.Pointers) 757 | if assert.Equal(t, 1, len(structSpec.Members)) { 758 | assert.Equal(t, "TestArray", structSpec.Members[0].Name) 759 | assert.Equal(t, "int", structSpec.Members[0].Spec.GetBase()) 760 | typeSpec, ok := structSpec.Members[0].Spec.(*CTypeSpec) 761 | if assert.True(t, ok) { 762 | assert.Equal(t, false, typeSpec.Const) 763 | assert.Equal(t, "", typeSpec.Raw) 764 | assert.Equal(t, "", typeSpec.InnerArr.String()) 765 | assert.Equal(t, "[8]", typeSpec.OuterArr.String()) 766 | } 767 | } 768 | } 769 | } 770 | } 771 | assert.Equal(t, true, typedefFound) 772 | assert.Equal(t, true, baseTypedefFound) 773 | } 774 | 775 | func TestDefineConst(t *testing.T) { 776 | translator := NewTestTranslator(t, "#define TEST_ABC 10", true) 777 | defineFound := false 778 | for _, d := range translator.defines { 779 | if d.Name == "TEST_ABC" { 780 | assert.False(t, defineFound) 781 | defineFound = true 782 | assert.Equal(t, false, d.IsStatic) 783 | assert.Equal(t, false, d.IsTypedef) 784 | assert.Equal(t, true, d.IsDefine) 785 | assert.Equal(t, "10", d.Expression) 786 | assert.Nil(t, d.Spec) // there is no spec for macros 787 | } 788 | } 789 | assert.Equal(t, true, defineFound) 790 | } 791 | -------------------------------------------------------------------------------- /translator/translator.go: -------------------------------------------------------------------------------- 1 | package translator 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "regexp" 7 | "sort" 8 | "strings" 9 | 10 | "github.com/dlclark/regexp2" 11 | "modernc.org/cc/v4" 12 | ) 13 | 14 | type Translator struct { 15 | validations Validations 16 | rules Rules 17 | compiledRxs map[RuleAction]RxMap 18 | compiledPtrTipRxs PtrTipRxMap 19 | compiledTypeTipRxs TypeTipRxMap 20 | compiledMemTipRxs MemTipRxList 21 | constRules ConstRules 22 | typemap CTypeMap 23 | builtinTypemap CTypeMap 24 | // builtinTypemap2 is an optional 25 | // typemap with overriden const (u)char rules 26 | builtinTypemap2 CTypeMap 27 | ignoredFiles map[string]struct{} 28 | 29 | valueMap map[string]Value 30 | exprMap map[string]string 31 | tagMap map[string]*CDecl 32 | lenFields map[string]string 33 | 34 | defines []*CDecl 35 | typedefs []*CDecl 36 | declares []*CDecl 37 | 38 | typedefsSet map[string]struct{} 39 | typedefKinds map[string]CTypeKind 40 | transformCache *NameTransformCache 41 | 42 | ptrTipCache *TipCache 43 | typeTipCache *TipCache 44 | memTipCache *TipCache 45 | } 46 | 47 | type RxMap map[RuleTarget][]Rx 48 | 49 | type Rx struct { 50 | From *regexp.Regexp 51 | To []byte 52 | // 53 | Transform RuleTransform 54 | } 55 | 56 | type PtrTipRxMap map[TipScope][]TipSpecRx 57 | type TypeTipRxMap map[TipScope][]TipSpecRx 58 | type MemTipRxList []TipSpecRx 59 | 60 | type TipSpecRx struct { 61 | Target *regexp.Regexp 62 | Default Tip 63 | tips Tips 64 | self Tip 65 | } 66 | 67 | func (t TipSpecRx) TipAt(i int) Tip { 68 | if i < len(t.tips) { 69 | if tip := t.tips[i]; tip.IsValid() { 70 | return tip 71 | } 72 | } 73 | return t.Default 74 | } 75 | 76 | func (t TipSpecRx) HasTip(q Tip) bool { 77 | for _, tip := range t.tips { 78 | if tip.IsValid() && tip == q { 79 | return true 80 | } 81 | } 82 | return false 83 | } 84 | 85 | func (t TipSpecRx) Self() Tip { 86 | if t.self.IsValid() { 87 | return t.self 88 | } 89 | return t.Default 90 | } 91 | 92 | type Config struct { 93 | Validations Validations `yaml:"Validations"` 94 | Rules Rules `yaml:"Rules"` 95 | ConstRules ConstRules `yaml:"ConstRules"` 96 | PtrTips PtrTips `yaml:"PtrTips"` 97 | TypeTips TypeTips `yaml:"TypeTips"` 98 | MemTips MemTips `yaml:"MemTips"` 99 | Typemap CTypeMap `yaml:"Typemap"` 100 | ConstCharIsString *bool `yaml:"ConstCharIsString"` 101 | ConstUCharIsString *bool `yaml:"ConstUCharIsString"` 102 | LenFields map[string]string `yaml:"LenFields"` 103 | 104 | IgnoredFiles []string `yaml:"-"` 105 | LongIs64Bit bool `yaml:"-"` 106 | } 107 | 108 | func New(cfg *Config) (*Translator, error) { 109 | if cfg == nil { 110 | cfg = &Config{} 111 | } 112 | 113 | var constCharAsString bool 114 | if cfg.ConstCharIsString == nil { 115 | constCharAsString = true 116 | } else { 117 | constCharAsString = *cfg.ConstCharIsString 118 | } 119 | 120 | var constUCharAsString bool 121 | if cfg.ConstUCharIsString == nil { 122 | constUCharAsString = false 123 | } else { 124 | constUCharAsString = *cfg.ConstUCharIsString 125 | } 126 | 127 | t := &Translator{ 128 | validations: cfg.Validations, 129 | rules: cfg.Rules, 130 | constRules: cfg.ConstRules, 131 | typemap: cfg.Typemap, 132 | builtinTypemap: getCTypeMap(constCharAsString, constUCharAsString, cfg.LongIs64Bit), 133 | builtinTypemap2: getCTypeMap(true, false, cfg.LongIs64Bit), 134 | compiledRxs: make(map[RuleAction]RxMap), 135 | compiledPtrTipRxs: make(PtrTipRxMap), 136 | compiledTypeTipRxs: make(TypeTipRxMap), 137 | valueMap: make(map[string]Value), 138 | exprMap: make(map[string]string), 139 | tagMap: make(map[string]*CDecl), 140 | lenFields: cfg.LenFields, 141 | typedefsSet: make(map[string]struct{}), 142 | typedefKinds: make(map[string]CTypeKind), 143 | ignoredFiles: make(map[string]struct{}), 144 | transformCache: &NameTransformCache{}, 145 | ptrTipCache: &TipCache{}, 146 | typeTipCache: &TipCache{}, 147 | memTipCache: &TipCache{}, 148 | } 149 | for _, p := range cfg.IgnoredFiles { 150 | t.ignoredFiles[p] = struct{}{} 151 | } 152 | for _, action := range ruleActions { 153 | if rxMap, err := getRuleActionRxs(t.rules, action); err != nil { 154 | return nil, err 155 | } else { 156 | t.compiledRxs[action] = rxMap 157 | } 158 | } 159 | if rxMap, err := getPtrTipRxs(cfg.PtrTips); err != nil { 160 | return nil, err 161 | } else { 162 | t.compiledPtrTipRxs = rxMap 163 | } 164 | if rxMap, err := getTypeTipRxs(cfg.TypeTips); err != nil { 165 | return nil, err 166 | } else { 167 | t.compiledTypeTipRxs = rxMap 168 | } 169 | if rxList, err := getMemTipRxs(cfg.MemTips); err != nil { 170 | return nil, err 171 | } else { 172 | t.compiledMemTipRxs = rxList 173 | } 174 | 175 | for _, v := range t.validations { 176 | if _, err := regexp2.Compile(v.MatchedFunc, 0); err != nil { 177 | return nil, fmt.Errorf("translator: %s, invalid regexp %s", err.Error(), v.MatchedFunc) 178 | } 179 | } 180 | 181 | return t, nil 182 | } 183 | 184 | func getRuleActionRxs(rules Rules, action RuleAction) (RxMap, error) { 185 | rxMap := make(RxMap, len(rules)) 186 | for target, specs := range rules { 187 | for _, spec := range specs { 188 | if len(spec.Load) > 0 { 189 | if s, ok := builtinRules[spec.Load]; ok { 190 | spec.LoadSpec(s) 191 | } else { 192 | return nil, fmt.Errorf("no builtin rule found: %s", spec.Load) 193 | } 194 | } 195 | if spec.Action == ActionNone { 196 | spec.Action = ActionReplace 197 | spec.To = "${_src}" 198 | if len(spec.From) == 0 { 199 | spec.From = "(?P<_src>.*)" 200 | } else { 201 | spec.From = fmt.Sprintf("(?P<_src>%s)", spec.From) 202 | } 203 | } else if len(spec.From) == 0 { 204 | spec.From = "(.*)" 205 | } 206 | if spec.Action != action { 207 | continue 208 | } 209 | rxFrom, err := regexp.Compile(spec.From) 210 | if err != nil { 211 | return nil, fmt.Errorf("translator: %s rules: invalid regexp %s", target, spec.From) 212 | } 213 | rx := Rx{From: rxFrom, To: []byte(spec.To)} 214 | if spec.Action == ActionReplace { 215 | rx.Transform = spec.Transform 216 | } 217 | rxMap[target] = append(rxMap[target], rx) 218 | } 219 | } 220 | return rxMap, nil 221 | } 222 | 223 | func getPtrTipRxs(tips PtrTips) (PtrTipRxMap, error) { 224 | rxMap := make(PtrTipRxMap, len(tips)) 225 | for scope, specs := range tips { 226 | for _, spec := range specs { 227 | if len(spec.Target) == 0 { 228 | continue 229 | } 230 | rx, err := regexp.Compile(spec.Target) 231 | if err != nil { 232 | return nil, fmt.Errorf("translator: ptr tip in %s scope: invalid regexp %s", 233 | scope, spec.Target) 234 | } 235 | specRx := TipSpecRx{ 236 | Target: rx, 237 | Default: spec.Default, 238 | tips: spec.Tips, 239 | self: spec.Self, 240 | } 241 | rxMap[scope] = append(rxMap[scope], specRx) 242 | if scope == TipScopeStruct { 243 | rxMap[TipScopeType] = append(rxMap[TipScopeType], specRx) 244 | } 245 | } 246 | } 247 | return rxMap, nil 248 | } 249 | 250 | func getTypeTipRxs(tips TypeTips) (TypeTipRxMap, error) { 251 | rxMap := make(TypeTipRxMap, len(tips)) 252 | for scope, specs := range tips { 253 | for _, spec := range specs { 254 | if len(spec.Target) == 0 { 255 | continue 256 | } 257 | rx, err := regexp.Compile(spec.Target) 258 | if err != nil { 259 | return nil, fmt.Errorf("translator: type tip in %s scope: invalid regexp %s", 260 | scope, spec.Target) 261 | } 262 | specRx := TipSpecRx{ 263 | Target: rx, 264 | Default: spec.Default, 265 | tips: spec.Tips, 266 | self: spec.Self, 267 | } 268 | rxMap[scope] = append(rxMap[scope], specRx) 269 | if scope == TipScopeStruct { 270 | rxMap[TipScopeType] = append(rxMap[TipScopeType], specRx) 271 | } 272 | } 273 | } 274 | return rxMap, nil 275 | } 276 | 277 | func getMemTipRxs(tips MemTips) (MemTipRxList, error) { 278 | var list MemTipRxList 279 | for _, spec := range tips { 280 | if len(spec.Target) == 0 { 281 | continue 282 | } 283 | rx, err := regexp.Compile(spec.Target) 284 | if err != nil { 285 | return nil, fmt.Errorf("translator: mem tip: invalid regexp %s", spec.Target) 286 | } 287 | specRx := TipSpecRx{ 288 | Target: rx, 289 | Default: spec.Default, 290 | tips: spec.Tips, 291 | self: spec.Self, 292 | } 293 | list = append(list, specRx) 294 | } 295 | return list, nil 296 | } 297 | 298 | type declList []*CDecl 299 | 300 | func (s declList) Len() int { return len(s) } 301 | func (s declList) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 302 | func (s declList) Less(i, j int) bool { 303 | if s[i].Position.Filename != s[j].Position.Filename { 304 | return s[i].Position.Filename < s[j].Position.Filename 305 | } else if s[i].Position.Offset != s[j].Position.Offset { 306 | return s[i].Position.Offset < s[j].Position.Offset 307 | } else { 308 | return s[i].Name < s[j].Name 309 | } 310 | } 311 | 312 | func (t *Translator) Learn(ast *cc.AST) { 313 | t.walkTranslationUnit(ast.TranslationUnit) 314 | t.resolveTypedefs(t.typedefs) 315 | sort.Sort(declList(t.declares)) 316 | sort.Sort(declList(t.typedefs)) 317 | t.collectDefines(t.declares, ast.Macros) 318 | sort.Sort(declList(t.defines)) 319 | } 320 | 321 | // This has been left intentionally. 322 | // 323 | // func (t *Translator) Report() { 324 | // fmt.Printf("[!] TAGS:\n") 325 | // for tag, decl := range t.tagMap { 326 | // fmt.Printf("%s refers to %v\n", tag, decl) 327 | // } 328 | // fmt.Printf("\n\n\n[!] TYPEDEFs:\n") 329 | // for _, decl := range t.typedefs { 330 | // fmt.Printf("%v\n", decl) 331 | // } 332 | // fmt.Printf("\n\n\n[!] DECLARATIONS:\n") 333 | // for _, decl := range t.declares { 334 | // fmt.Printf("%v\n", decl) 335 | // } 336 | // fmt.Printf("\n\n\n[!] const (") 337 | // for _, line := range t.defines { 338 | // fmt.Printf("\n// %s\n// > define %s %v\n%s = %s", 339 | // SrcLocation(line.Pos), line.Name, line.Src, 340 | // t.TransformName(TargetConst, string(line.Name)), line.Expression) 341 | // } 342 | // fmt.Printf("\n)\n\n") 343 | // } 344 | 345 | func (t *Translator) collectDefines(declares []*CDecl, defines map[string]*cc.Macro) { 346 | seen := make(map[string]struct{}, len(defines)+len(declares)) 347 | 348 | // traverse declared constants because macro can reference them, 349 | // so we need to collect a map of valid references beforehand 350 | for _, decl := range declares { 351 | if t.IsTokenIgnored(decl.Position) { 352 | continue 353 | } 354 | if decl.Spec.Kind() == EnumKind { 355 | enumMembers := decl.Spec.(*CEnumSpec).Members 356 | for _, member := range enumMembers { 357 | if !t.IsAcceptableName(TargetConst, member.Name) { 358 | continue 359 | } 360 | seen[member.Name] = struct{}{} 361 | } 362 | } 363 | } 364 | 365 | // double traverse because macros can depend on each other and the map 366 | // brings a randomized order of them. 367 | for _, macro := range defines { 368 | if t.IsTokenIgnored(macro.Position()) { 369 | continue 370 | } else if macro.IsFnLike { 371 | continue 372 | } 373 | name := string(macro.Name.SrcStr()) 374 | if !t.IsAcceptableName(TargetConst, name) { 375 | continue 376 | } 377 | seen[name] = struct{}{} 378 | } 379 | 380 | for _, macro := range defines { 381 | if t.IsTokenIgnored(macro.Position()) { 382 | continue 383 | } 384 | name := string(macro.Name.SrcStr()) 385 | if _, ok := seen[name]; !ok { 386 | continue 387 | } 388 | expand := false 389 | if t.constRules[ConstDefines] == ConstExpand { 390 | expand = true 391 | } 392 | 393 | if !expand { 394 | switch macro.Type().Kind() { 395 | case cc.InvalidKind: // unresolved value -> try to expand 396 | expand = true 397 | case cc.Bool: // ban bools 398 | continue 399 | } 400 | if !expand { 401 | t.defines = append(t.defines, &CDecl{ 402 | IsDefine: true, 403 | Name: name, 404 | Value: Value(macro.Value()), 405 | Position: macro.Position(), 406 | }) 407 | continue 408 | } 409 | } else if macro.Type().Kind() == cc.Bool { 410 | // ban bools 411 | continue 412 | } 413 | tokens := macro.ReplacementList() 414 | srcParts := make([]string, 0, len(tokens)) 415 | exprParts := make([]string, 0, len(tokens)) 416 | valid := true 417 | 418 | // TODO: some state machine 419 | needsTypecast := false 420 | typecastValue := false 421 | typecastValueParens := 0 422 | 423 | for _, token := range tokens { 424 | src := token.SrcStr() 425 | srcParts = append(srcParts, src) 426 | switch token.Ch { 427 | case rune(cc.IDENTIFIER): 428 | if _, ok := seen[src]; ok { 429 | // const reference 430 | exprParts = append(exprParts, string(t.TransformName(TargetConst, src, true))) 431 | } else if _, ok := t.typedefsSet[src]; ok { 432 | // type reference 433 | needsTypecast = true 434 | exprParts = append(exprParts, string(t.TransformName(TargetType, src, true))) 435 | } else { 436 | // an unresolved reference 437 | valid = false 438 | break 439 | } 440 | default: 441 | // TODO: state machine 442 | const ( 443 | lparen = rune(40) 444 | rparen = rune(41) 445 | ) 446 | switch { 447 | case needsTypecast && token.Ch == rparen: 448 | typecastValue = true 449 | needsTypecast = false 450 | exprParts = append(exprParts, src+"(") 451 | case typecastValue && token.Ch == lparen: 452 | typecastValueParens++ 453 | case typecastValue && token.Ch == rparen: 454 | if typecastValueParens == 0 { 455 | typecastValue = false 456 | exprParts = append(exprParts, ")"+src) 457 | } else { 458 | typecastValueParens-- 459 | } 460 | default: 461 | // somewhere in the world a kitten died because of this 462 | if token.Ch == '~' { 463 | src = "^" 464 | } 465 | if runes := []rune(src); len(runes) > 0 && isNumeric(runes) { 466 | // TODO(xlab): better const handling 467 | src = readNumeric(runes) 468 | } 469 | exprParts = append(exprParts, src) 470 | } 471 | } 472 | if !valid { 473 | break 474 | } 475 | } 476 | if typecastValue { 477 | // still in typecast value, need to close paren 478 | exprParts = append(exprParts, ")") 479 | typecastValue = false 480 | } 481 | if !valid { 482 | if macro.Type().Kind() == cc.InvalidKind { 483 | // fallback to the evaluated value 484 | t.defines = append(t.defines, &CDecl{ 485 | IsDefine: true, 486 | Name: name, 487 | Value: Value(macro.Value), 488 | Position: macro.Position(), 489 | }) 490 | } 491 | continue 492 | } 493 | t.defines = append(t.defines, &CDecl{ 494 | IsDefine: true, 495 | Name: name, 496 | Expression: strings.Join(exprParts, " "), 497 | Src: strings.Join(srcParts, " "), 498 | Position: macro.Position(), 499 | }) 500 | } 501 | } 502 | 503 | func (t *Translator) resolveTypedefs(typedefs []*CDecl) { 504 | for _, decl := range typedefs { 505 | if t.IsTokenIgnored(decl.Position) { 506 | continue 507 | } 508 | if decl.Spec.Kind() != TypeKind { 509 | t.typedefKinds[decl.Name] = decl.Spec.Kind() 510 | continue 511 | } 512 | if goSpec := t.TranslateSpec(decl.Spec); goSpec.IsPlain() { 513 | t.typedefKinds[decl.Name] = PlainTypeKind 514 | } else if goSpec.Kind != TypeKind { 515 | t.typedefKinds[decl.Name] = goSpec.Kind 516 | } 517 | } 518 | } 519 | 520 | func (t *Translator) TransformName(target RuleTarget, str string, publicOpt ...bool) []byte { 521 | if len(str) == 0 { 522 | return emptyStr 523 | } 524 | targetVisibility := NoTarget 525 | if len(publicOpt) > 0 { 526 | if publicOpt[0] { 527 | targetVisibility = TargetPublic 528 | } else { 529 | targetVisibility = TargetPrivate 530 | } 531 | } 532 | if name, ok := t.transformCache.Get(target, targetVisibility, str); ok { 533 | return name 534 | } 535 | 536 | var name []byte 537 | switch target { 538 | case TargetGlobal, TargetPostGlobal, TargetPrivate, TargetPublic: 539 | name = []byte(str) 540 | default: 541 | // apply the global rules first 542 | name = t.TransformName(TargetGlobal, str) 543 | } 544 | 545 | for _, rx := range t.compiledRxs[ActionReplace][target] { 546 | indices := rx.From.FindAllSubmatchIndex(name, -1) 547 | reference := make([]byte, len(name)) 548 | copy(reference, name) 549 | 550 | // Itrate submatches backwards since we need to insert expanded 551 | // versions into the original name and doing so from beginning will shift indices 552 | // for latter inserts. 553 | // 554 | // Example flow: 555 | // doing title at _partitions in vpx_error_resilient_partitions 556 | // doing title at _resilient in vpx_error_resilientPartitions 557 | // doing title at _error in vpx_errorResilientPartitions 558 | // -> vpxErrorResilientPartitions 559 | for i := len(indices) - 1; i >= 0; i-- { 560 | idx := indices[i] 561 | buf := rx.From.Expand([]byte{}, rx.To, reference, idx) 562 | switch rx.Transform { 563 | case TransformLower: 564 | buf = bytes.ToLower(buf) 565 | case TransformTitle, TransformExport: 566 | if len(buf) > 0 { 567 | buf[0] = bytes.ToUpper(buf[:1])[0] 568 | } 569 | case TransformUnexport: 570 | if len(buf) > 0 { 571 | buf[0] = bytes.ToLower(buf[:1])[0] 572 | } 573 | case TransformUpper: 574 | buf = bytes.ToUpper(buf) 575 | } 576 | name = replaceBytes(name, idx, buf) 577 | } 578 | } 579 | switch target { 580 | case TargetGlobal, TargetPostGlobal, TargetPrivate, TargetPublic: 581 | default: 582 | // apply post-global & visibility rules in the end 583 | name = t.TransformName(TargetPostGlobal, string(name)) 584 | switch targetVisibility { 585 | case TargetPrivate, TargetPublic: 586 | name = t.TransformName(targetVisibility, string(name)) 587 | } 588 | if isBuiltinName(name) { 589 | name = rewriteName(name) 590 | } 591 | t.transformCache.Set(target, targetVisibility, str, name) 592 | return name 593 | } 594 | return name 595 | } 596 | 597 | func (t *Translator) lookupSpec(spec CTypeSpec, useConstCharTypemap bool) (GoTypeSpec, bool) { 598 | if gospec, ok := t.typemap[spec]; ok { 599 | return gospec, true 600 | } 601 | if useConstCharTypemap { 602 | if gospec, ok := t.builtinTypemap2[spec]; ok { 603 | return gospec, true 604 | } 605 | } else { 606 | if gospec, ok := t.builtinTypemap[spec]; ok { 607 | return gospec, true 608 | } 609 | } 610 | 611 | if spec.Const { 612 | spec.Const = false 613 | if gospec, ok := t.typemap[spec]; ok { 614 | return gospec, true 615 | } 616 | if gospec, ok := t.builtinTypemap[spec]; ok { 617 | return gospec, true 618 | } 619 | } 620 | return GoTypeSpec{}, false 621 | } 622 | 623 | func (t *Translator) PtrTipRx(scope TipScope, name string) (TipSpecRx, bool) { 624 | if rx, ok := t.ptrTipCache.Get(scope, name); ok { 625 | return rx, true 626 | } 627 | for _, rx := range t.compiledPtrTipRxs[scope] { 628 | if rx.Target.MatchString(name) { 629 | t.ptrTipCache.Set(scope, name, rx) 630 | return rx, true 631 | } 632 | } 633 | if scope != TipScopeAny { 634 | for _, rx := range t.compiledPtrTipRxs[TipScopeAny] { 635 | if rx.Target.MatchString(name) { 636 | t.ptrTipCache.Set(scope, name, rx) 637 | return rx, true 638 | } 639 | } 640 | } 641 | return TipSpecRx{}, false 642 | } 643 | 644 | func (t *Translator) TypeTipRx(scope TipScope, name string) (TipSpecRx, bool) { 645 | if rx, ok := t.typeTipCache.Get(scope, name); ok { 646 | return rx, true 647 | } 648 | for _, rx := range t.compiledTypeTipRxs[scope] { 649 | if rx.Target.MatchString(name) { 650 | t.typeTipCache.Set(scope, name, rx) 651 | return rx, true 652 | } 653 | } 654 | if scope != TipScopeAny { 655 | for _, rx := range t.compiledTypeTipRxs[TipScopeAny] { 656 | if rx.Target.MatchString(name) { 657 | t.typeTipCache.Set(scope, name, rx) 658 | return rx, true 659 | } 660 | } 661 | } 662 | return TipSpecRx{}, false 663 | } 664 | 665 | func (t *Translator) MemTipRx(name string) (TipSpecRx, bool) { 666 | if rx, ok := t.memTipCache.Get(TipScopeType, name); ok { 667 | return rx, true 668 | } 669 | if rx, ok := t.memTipCache.Get(TipScopeStruct, name); ok { 670 | return rx, true 671 | } 672 | for _, rx := range t.compiledMemTipRxs { 673 | if rx.Target.MatchString(name) { 674 | t.memTipCache.Set(TipScopeType, name, rx) 675 | t.memTipCache.Set(TipScopeStruct, name, rx) 676 | return rx, true 677 | } 678 | } 679 | return TipSpecRx{}, false 680 | } 681 | 682 | func (t *Translator) TipRxsForSpec(scope TipScope, 683 | name string, spec CType) (ptr, typ, mem TipSpecRx) { 684 | var ptrOk, typOk, memOk bool 685 | if tag := spec.GetBase(); len(tag) > 0 { 686 | ptr, ptrOk = t.PtrTipRx(scope, tag) 687 | typ, typOk = t.TypeTipRx(scope, tag) 688 | mem, memOk = t.MemTipRx(tag) 689 | } 690 | if !ptrOk { 691 | ptr, _ = t.PtrTipRx(scope, name) 692 | } 693 | if !typOk { 694 | typ, _ = t.TypeTipRx(scope, name) 695 | } 696 | if !memOk { 697 | mem, _ = t.MemTipRx(name) 698 | } 699 | return 700 | } 701 | 702 | func (t *Translator) TranslateSpec(spec CType, tips ...Tip) GoTypeSpec { 703 | var ptrTip Tip 704 | var typeTip Tip 705 | for _, tip := range tips { 706 | switch tip.Kind() { 707 | case TipKindPtr: 708 | ptrTip = tip 709 | case TipKindType: 710 | typeTip = tip 711 | } 712 | } 713 | if len(spec.OuterArrays()) > 0 { 714 | ptrTip = TipPtrSRef 715 | } 716 | 717 | switch spec.Kind() { 718 | case TypeKind: 719 | typeSpec := spec.(*CTypeSpec) 720 | lookupSpec := CTypeSpec{ 721 | Base: typeSpec.Base, 722 | Const: typeSpec.Const, 723 | Unsigned: typeSpec.Unsigned, 724 | Short: typeSpec.Short, 725 | Long: typeSpec.Long, 726 | Complex: typeSpec.Complex, 727 | // Arrays skip 728 | // VarArrays skip 729 | Pointers: typeSpec.Pointers, 730 | } 731 | if typeSpec.Base == "char" { 732 | lookupSpec.Unsigned = typeSpec.Unsigned 733 | lookupSpec.Signed = typeSpec.Signed 734 | } 735 | 736 | if gospec, ok := t.lookupSpec(lookupSpec, typeTip == TipTypeString); ok { 737 | tag := typeSpec.CGoName() 738 | if gospec == UnsafePointerSpec && len(tag) > 0 { 739 | if decl, ok := t.tagMap[tag]; ok { 740 | if decl.Spec.GetPointers() <= gospec.Pointers { 741 | gospec.Pointers = gospec.Pointers - decl.Spec.GetPointers() 742 | } 743 | } 744 | } 745 | gospec.OuterArr.Prepend(typeSpec.OuterArr) 746 | gospec.InnerArr.Prepend(typeSpec.InnerArr) 747 | if gospec.Pointers == 0 && gospec.Slices > 0 { 748 | switch ptrTip { 749 | case TipPtrRef: 750 | gospec.Slices-- 751 | gospec.Pointers++ 752 | case TipPtrSRef: 753 | gospec.Pointers = gospec.Slices 754 | gospec.Slices = 0 755 | } 756 | } 757 | if gospec.Kind == TypeKind { 758 | if gospec.IsPlain() { 759 | gospec.Kind = PlainTypeKind 760 | } else if gospec.Kind = t.typedefKinds[gospec.Base]; gospec.Kind == TypeKind { 761 | if kind := t.typedefKinds[lookupSpec.Base]; kind != PlainTypeKind { 762 | gospec.Kind = kind 763 | } 764 | } 765 | } 766 | if typeTip != TipTypePlain { 767 | if t.IsAcceptableName(TargetType, typeSpec.Raw) { 768 | gospec.Raw = string(t.TransformName(TargetType, typeSpec.Raw)) 769 | if gospec.Base != "unsafe.Pointer" { 770 | gospec.splitPointers(ptrTip, typeSpec.Pointers) 771 | } 772 | } 773 | } 774 | 775 | return gospec 776 | } 777 | wrapper := GoTypeSpec{ 778 | Kind: t.typedefKinds[lookupSpec.Base], 779 | OuterArr: typeSpec.OuterArr, 780 | InnerArr: typeSpec.InnerArr, 781 | } 782 | if lookupSpec.Pointers > 0 { 783 | for lookupSpec.Pointers > 0 { 784 | switch ptrTip { 785 | case TipPtrSRef: 786 | if lookupSpec.Pointers > 1 { 787 | wrapper.Slices++ 788 | } else { 789 | wrapper.Pointers++ 790 | } 791 | case TipPtrRef: 792 | wrapper.Pointers++ 793 | default: 794 | wrapper.Slices++ 795 | } 796 | lookupSpec.Pointers-- 797 | if gospec, ok := t.lookupSpec(lookupSpec, typeTip == TipTypeString); ok { 798 | tag := typeSpec.CGoName() 799 | if gospec == UnsafePointerSpec && len(tag) > 0 { 800 | if decl, ok := t.tagMap[tag]; ok { 801 | if decl.Spec.GetPointers() <= gospec.Pointers { 802 | gospec.Pointers = gospec.Pointers - decl.Spec.GetPointers() 803 | } 804 | } 805 | } 806 | gospec.Slices += wrapper.Slices 807 | gospec.Pointers += wrapper.Pointers 808 | gospec.OuterArr.Prepend(wrapper.OuterArr) 809 | gospec.InnerArr.Prepend(wrapper.InnerArr) 810 | if gospec.Kind == TypeKind { 811 | if gospec.IsPlain() { 812 | gospec.Kind = PlainTypeKind 813 | } else if wrapper.Kind == TypeKind || wrapper.Kind == PlainTypeKind { 814 | if kind := t.typedefKinds[lookupSpec.Base]; kind != PlainTypeKind { 815 | gospec.Kind = kind 816 | } 817 | } else { 818 | gospec.Kind = wrapper.Kind 819 | } 820 | } 821 | if typeTip != TipTypePlain { 822 | if t.IsAcceptableName(TargetType, typeSpec.Raw) { 823 | gospec.Raw = string(t.TransformName(TargetType, typeSpec.Raw)) 824 | if gospec.Base != "unsafe.Pointer" { 825 | gospec.Pointers = 0 826 | gospec.Slices = 0 827 | gospec.splitPointers(ptrTip, typeSpec.Pointers) 828 | } 829 | } 830 | } 831 | return gospec 832 | } 833 | } 834 | } 835 | if t.IsAcceptableName(TargetType, typeSpec.Raw) { 836 | wrapper.Raw = string(t.TransformName(TargetType, typeSpec.Raw)) 837 | } 838 | wrapper.Base = string(t.TransformName(TargetType, lookupSpec.Base)) 839 | switch wrapper.Kind { 840 | case TypeKind: 841 | if wrapper.IsPlain() { 842 | wrapper.Kind = PlainTypeKind 843 | } 844 | case FunctionKind: 845 | wrapper.Slices = 0 846 | wrapper.OuterArr = ArraySpec("") 847 | wrapper.InnerArr = ArraySpec("") 848 | wrapper.Pointers = spec.GetPointers() 849 | } 850 | return wrapper 851 | case FunctionKind: 852 | wrapper := GoTypeSpec{ 853 | Kind: spec.Kind(), 854 | Pointers: spec.GetPointers(), 855 | } 856 | fspec := spec.(*CFunctionSpec) 857 | decl, known := t.tagMap[fspec.Raw] 858 | if !known { 859 | // another try using the type 860 | decl, known = t.tagMap[fspec.Typedef] 861 | } 862 | if known { 863 | if decl.Spec.GetPointers() <= wrapper.Pointers { 864 | wrapper.Pointers = wrapper.Pointers - decl.Spec.GetPointers() 865 | } 866 | } 867 | if t.IsAcceptableName(TargetType, fspec.Raw) { 868 | wrapper.Raw = string(t.TransformName(TargetType, fspec.Raw)) 869 | } 870 | wrapper.Base = "func" 871 | return wrapper 872 | default: 873 | wrapper := GoTypeSpec{ 874 | Kind: spec.Kind(), 875 | OuterArr: spec.OuterArrays(), 876 | InnerArr: spec.InnerArrays(), 877 | } 878 | switch wrapper.Kind { 879 | case OpaqueStructKind, StructKind, FunctionKind, UnionKind: 880 | decl, tagKnown := t.tagMap[spec.GetTag()] 881 | if !tagKnown && wrapper.Kind == FunctionKind { 882 | // another try using the type 883 | fspec := spec.(*CFunctionSpec) 884 | decl, tagKnown = t.tagMap[fspec.Typedef] 885 | } 886 | if tagKnown { 887 | if !decl.Spec.IsOpaque() { 888 | wrapper.Kind = StructKind 889 | } 890 | // count in the pointers of the base type under typedef 891 | // e.g. typedef struct a_T* a; int f(a *arg); 892 | // -> go_F(C.a *arg) but not go_F(C.a **arg) because of residual pointers. 893 | wrapper.Pointers = 0 894 | wrapper.splitPointers(ptrTip, spec.GetPointers()-decl.Spec.GetPointers()) 895 | } else { 896 | wrapper.splitPointers(ptrTip, spec.GetPointers()) 897 | } 898 | default: 899 | wrapper.splitPointers(ptrTip, spec.GetPointers()) 900 | } 901 | if base := spec.GetBase(); len(base) > 0 { 902 | wrapper.Raw = string(t.TransformName(TargetType, base)) 903 | } else if cgoName := spec.CGoName(); len(cgoName) > 0 { 904 | wrapper.Raw = "C." + cgoName 905 | } 906 | return wrapper 907 | } 908 | } 909 | 910 | func (t *Translator) NormalizeSpecPointers(spec CType) CType { 911 | if spec.Kind() == OpaqueStructKind { 912 | decl, tagKnown := t.tagMap[spec.GetTag()] 913 | if tagKnown { 914 | spec := spec.Copy() 915 | spec.SetPointers(spec.GetPointers() - decl.Spec.GetPointers()) 916 | return spec 917 | } 918 | } 919 | return spec 920 | } 921 | 922 | func (t *Translator) CGoSpec(spec CType, asArg bool) CGoSpec { 923 | cgo := CGoSpec{ 924 | Pointers: spec.GetPointers(), 925 | OuterArr: spec.OuterArrays(), 926 | InnerArr: spec.InnerArrays(), 927 | } 928 | if decl, ok := t.tagMap[spec.GetTag()]; ok { 929 | // count in the pointers of the base type under typedef 930 | cgo.Pointers = spec.GetPointers() - decl.Spec.GetPointers() 931 | } 932 | if typ, ok := spec.(*CTypeSpec); ok { 933 | if typ.Base == "void*" { 934 | if len(typ.Raw) == 0 { 935 | cgo.Base = "unsafe.Pointer" 936 | return cgo 937 | } else if !asArg { 938 | cgo.Pointers++ 939 | } 940 | } 941 | } 942 | cgo.Base = "C." + spec.CGoName() 943 | return cgo 944 | } 945 | 946 | func (t *Translator) registerTagsOf(decl *CDecl) { 947 | switch decl.Spec.Kind() { 948 | case TypeKind, EnumKind, StructKind, OpaqueStructKind, UnionKind: 949 | tag := decl.Spec.GetTag() 950 | if decl.Spec.Kind() == TypeKind { 951 | tag = decl.Spec.CGoName() 952 | } 953 | if len(tag) > 0 { 954 | prev, hasPrev := t.tagMap[tag] 955 | switch { 956 | case !hasPrev: 957 | // first time seen -> store the tag 958 | t.tagMap[tag] = decl 959 | case decl.IsTypedef && !prev.IsTypedef: 960 | // a typedef for tag prevails simple tag declarations 961 | t.tagMap[tag] = decl 962 | case decl.Spec.IsComplete() && !prev.Spec.IsComplete(): 963 | // replace an opaque struct with the complete template 964 | t.tagMap[tag] = decl 965 | } 966 | } 967 | case FunctionKind: 968 | if tag := decl.Spec.GetTag(); len(tag) > 0 { 969 | if _, ok := t.tagMap[tag]; !ok { 970 | t.tagMap[tag] = decl 971 | } 972 | } 973 | } 974 | switch typ := decl.Spec.(type) { 975 | case *CStructSpec: 976 | for _, m := range typ.Members { 977 | switch m.Spec.Kind() { 978 | case TypeKind, StructKind, OpaqueStructKind, UnionKind: 979 | tag := decl.Spec.GetTag() 980 | if decl.Spec.Kind() == TypeKind { 981 | tag = decl.Spec.CGoName() 982 | } 983 | if len(tag) > 0 { 984 | if prev, ok := t.tagMap[tag]; !ok { 985 | // first time seen -> store the tag 986 | t.tagMap[tag] = m 987 | } else if m.Spec.IsComplete() && !prev.Spec.IsComplete() { 988 | // replace an opaque struct with the complete template 989 | t.tagMap[tag] = m 990 | } 991 | } 992 | case FunctionKind: 993 | if tag := decl.Spec.GetTag(); len(tag) > 0 { 994 | if _, ok := t.tagMap[tag]; !ok { 995 | t.tagMap[tag] = decl 996 | } 997 | } 998 | } 999 | } 1000 | } 1001 | } 1002 | 1003 | func (t *Translator) IsAcceptableName(target RuleTarget, name string) bool { 1004 | if rxs, ok := t.compiledRxs[ActionAccept][target]; ok { 1005 | for _, rx := range rxs { 1006 | if rx.From.MatchString(name) { 1007 | // try to find explicit ignore rules 1008 | if rxs, ok := t.compiledRxs[ActionIgnore][target]; ok { 1009 | for _, rx := range rxs { 1010 | if rx.From.MatchString(name) { 1011 | // found an ignore rule, ignore the name 1012 | return false 1013 | } 1014 | } 1015 | } 1016 | // no ignore rules found, accept the name 1017 | return true 1018 | } 1019 | } 1020 | } 1021 | // try to find explicit ignore rules 1022 | if rxs, ok := t.compiledRxs[ActionIgnore][target]; ok { 1023 | for _, rx := range rxs { 1024 | if rx.From.MatchString(name) { 1025 | // found an ignore rule, ignore the name 1026 | return false 1027 | } 1028 | } 1029 | } 1030 | if target != TargetGlobal { 1031 | // we don't have any specific rules for this target, check global rules 1032 | return t.IsAcceptableName(TargetGlobal, name) 1033 | } 1034 | // default to ignore 1035 | return false 1036 | } 1037 | 1038 | func (t *Translator) TagMap() map[string]*CDecl { 1039 | return t.tagMap 1040 | } 1041 | 1042 | func (t *Translator) LenFields() map[string]string { 1043 | return t.lenFields 1044 | } 1045 | 1046 | func (t *Translator) ExpressionMap() map[string]string { 1047 | return t.exprMap 1048 | } 1049 | 1050 | func (t *Translator) ValueMap() map[string]Value { 1051 | return t.valueMap 1052 | } 1053 | 1054 | func (t *Translator) Defines() []*CDecl { 1055 | return t.defines 1056 | } 1057 | 1058 | func (t *Translator) Declares() []*CDecl { 1059 | return t.declares 1060 | } 1061 | 1062 | func (t *Translator) Typedefs() []*CDecl { 1063 | return t.typedefs 1064 | } 1065 | 1066 | func (t *Translator) GetLibrarySymbolValidation(name string) (string, string, bool) { 1067 | for _, v := range t.validations { 1068 | if v.MatchFunc(name) { 1069 | return v.ValidateFunc, v.Ret, true 1070 | } 1071 | } 1072 | return "", "", false 1073 | } 1074 | --------------------------------------------------------------------------------