/
goast.go
437 lines (389 loc) · 11.3 KB
/
goast.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
// This file contains utility and helper methods for making it easier to
// generate parts of the Go AST.
package util
import (
"bytes"
"fmt"
goast "go/ast"
"go/parser"
"go/token"
"strconv"
"strings"
)
// NewExprStmt returns a new ExprStmt from an expression. It is used when
// converting a single expression into a statement for another receiver.
//
// It is recommended you use this method of instantiating the ExprStmt yourself
// because NewExprStmt will check that the expr is not nil (or panic). This is
// much more helpful when trying to debug why the Go source build crashes
// because of a nil pointer - which eventually leads back to a nil expr.
func NewExprStmt(expr goast.Expr) *goast.ExprStmt {
PanicIfNil(expr, "expr is nil")
return &goast.ExprStmt{
X: expr,
}
}
// IsAValidFunctionName performs a check to see if a string would make a
// valid function name in Go. Go allows unicode characters, but C doesn't.
func IsAValidFunctionName(s string) bool {
return GetRegex(`^[a-zA-Z_]`).
Match([]byte(s))
}
// Convert a type as a string into a Go AST expression.
func typeToExpr(t string) goast.Expr {
defer func() {
if r := recover(); r != nil {
panic(fmt.Sprintf("bad type: '%v'", t))
}
}()
return internalTypeToExpr(t)
}
func internalTypeToExpr(goType string) goast.Expr {
// I'm not sure if this is an error or not. It is caused by processing the
// resolved type of "void" which is "". It is used on functions to denote
// that it does not have a return type.
if goType == "" {
return nil
}
separator := make([]bool, len(goType)+1)
for i := range goType {
switch goType[i] {
case '.', '*', '(', ')', '-', '+', '&', '{', '}', ' ', '[', ']':
separator[i] = true
separator[i+1] = true
}
}
// Specific case for 'interface{}'
// remove all separator inside that word
specials := [][]byte{[]byte("func("), []byte("interface{}")}
for _, special := range specials {
input := []byte(goType)
again:
index := bytes.Index(input, special)
if index >= 0 {
for i := index + 1; i < index+len(special); i++ {
separator[i] = false
}
input = input[index+len(special)-1:]
goto again
}
}
separator[0] = true
separator[len(separator)-1] = true
// Separation string 'goType' to slice of bytes
var indexes []int
for i := range separator {
if separator[i] {
indexes = append(indexes, i)
}
}
var lines [][]byte
for i := 0; i < len(indexes)-1; i++ {
lines = append(lines, []byte(goType[indexes[i]:indexes[i+1]]))
}
// Checking
for i := range lines {
if IsGoKeyword(string(lines[i])) {
lines[i] = []byte(string(lines[i]) + "_")
}
}
goType = string(bytes.Join(lines, []byte("")))
return goast.NewIdent(goType)
}
// NewCallExpr creates a new *"go/ast".CallExpr with each of the arguments
// (after the function name) being each of the expressions that represent the
// individual arguments.
//
// The function name is checked with IsAValidFunctionName and will panic if the
// function name is deemed to be not valid.
func NewCallExpr(functionName string, args ...goast.Expr) *goast.CallExpr {
for i := range args {
// Argument of function is cannot be nil
if args[i] == nil {
args[i] = goast.NewIdent("C4GO_NOT_TRANSPILED_EXPRESSION")
}
}
index := strings.Index(functionName, ".")
if index > 0 {
if IsGoPackage(functionName[:index]) {
return &goast.CallExpr{
Fun: goast.NewIdent(functionName),
Args: args,
}
}
}
return &goast.CallExpr{
Fun: typeToExpr(functionName),
Args: args,
}
}
// NewFuncClosure creates a new *"go/ast".CallExpr that calls a function
// literal closure. The first argument is the Go return type of the
// closure, and the remainder of the arguments are the statements of the
// closure body.
func NewFuncClosure(returnType string, stmts ...goast.Stmt) *goast.CallExpr {
return &goast.CallExpr{
Fun: &goast.FuncLit{
Type: NewFuncType(&goast.FieldList{}, returnType, false),
Body: &goast.BlockStmt{
List: stmts,
},
},
Args: []goast.Expr{},
}
}
// NewBinaryExpr create a new Go AST binary expression with a left, operator and
// right operand.
//
// You should use this instead of BinaryExpr directly so that nil left and right
// operands can be caught (and panic) before Go tried to render the source -
// which would result in a very hard to debug error.
//
// Assignment operators in C can be nested inside other expressions, like:
//
// a + (b += 3)
//
// In Go this is not allowed. Since the operators mutate variables it is not
// possible in some cases to move the statements before or after. The only safe
// (and generic) way around this is to create an immediately executing closure,
// like:
//
// a + (func () int { b += 3; return b }())
//
// In a lot of cases this may be unnecessary and obfuscate the Go output but
// these will have to be optimised over time and be strict about the
// situation they are simplifying.
//
// If stmt is true then the binary expression is the whole statement. This means
// that the closure above does not need to applied. This makes the output code
// much neater.
func NewBinaryExpr(left goast.Expr, operator token.Token, right goast.Expr,
returnType string, stmt bool) goast.Expr {
PanicIfNil(left, "left is nil")
PanicIfNil(right, "right is nil")
var b goast.Expr = &goast.BinaryExpr{
X: left,
Op: operator,
Y: right,
}
return b
}
const C4GoPostFixForAvoid string = "_c4go_postfix"
// NewIdent - create a new Go ast Ident
func NewIdent(name string) *goast.Ident {
// TODO: The name of a variable or field cannot be a reserved word
// https://github.com/Konstantin8105/c4go/issues/83
// Search for this issue in other areas of the codebase.
if IsGoKeyword(name) {
name += C4GoPostFixForAvoid
}
// Remove const prefix as it has no equivalent in Go.
name = strings.TrimPrefix(name, "const ")
if !IsAValidFunctionName(name) {
// Normally we do not panic because we want the transpiler to recover as
// much as possible so that we always get Go output - even if it's
// wrong. However, in this case we must panic because we know that this
// identity will cause the AST renderer in Go to panic with a very
// unhelpful error message.
//
// Panic now so that we can see where the bad identifier is coming from.
panic(fmt.Sprintf("invalid identity: '%s'", name))
}
return goast.NewIdent(name)
}
// NewTypeIdent created a new Go identity that is to be used for a Go type. This
// is different from NewIdent in how the input string is validated.
func NewTypeIdent(name string) goast.Expr {
return typeToExpr(name)
}
// NewStringLit returns a new Go basic literal with a string value.
func NewStringLit(value string) *goast.BasicLit {
return &goast.BasicLit{
Kind: token.STRING,
Value: value,
}
}
// NewIntLit - create a Go ast BasicLit for `INT` value
func NewIntLit(value int) *goast.BasicLit {
return &goast.BasicLit{
Kind: token.INT,
Value: strconv.Itoa(value),
}
}
// NewFloatLit creates a new Float Literal.
func NewFloatLit(value float64) *goast.BasicLit {
return &goast.BasicLit{
Kind: token.FLOAT,
Value: strconv.FormatFloat(value, 'g', -1, 64),
}
}
// NewNil returns a Go AST identity that can be used to represent "nil".
func NewNil() *goast.Ident {
return NewIdent("nil")
}
// NewUnaryExpr creates a new Go unary expression. You should use this function
// instead of instantiating the UnaryExpr directly because this function has
// extra error checking.
func NewUnaryExpr(expr goast.Expr, operator token.Token) *goast.UnaryExpr {
if expr == nil {
expr = goast.NewIdent("C4GO_RECOVER_EXPR_NIL")
}
return &goast.UnaryExpr{
X: expr,
Op: operator,
}
}
// IsGoKeyword will return true if a word is one of the reserved words in Go.
// This means that it cannot be used as an identifier, function name, etc.
//
// The list of reserved words has been taken from the spec at
// https://golang.org/ref/spec#Keywords
func IsGoKeyword(w string) bool {
switch w {
case "break", "default", "func", "interface", "select", "case", "defer",
"go", "map", "struct", "chan", "else", "goto", "package", "switch",
"const", "fallthrough", "if", "range", "type", "continue", "for",
"import", "return", "var", "_", "init":
return true
}
if IsGoPackage(w) {
return true
}
return false
}
// IsGoPackage Go packages
func IsGoPackage(w string) bool {
switch w {
case "fmt", "os", "math", "testing", "unsafe", "ioutil":
return true
}
return false
}
// ConvertFunctionNameFromCtoGo - convert function name fromC to Go
func ConvertFunctionNameFromCtoGo(name string) string {
if name == "_" {
return "__"
}
return name
}
// NewFuncType - create a new function type, example:
// func ...(fieldList)(returnType)
func NewFuncType(fieldList *goast.FieldList, returnType string, addDefaultReturn bool) *goast.FuncType {
returnTypes := []*goast.Field{}
if returnType != "" {
field := goast.Field{
Type: NewTypeIdent(returnType),
}
if addDefaultReturn {
field.Names = []*goast.Ident{NewIdent("c4goDefaultReturn")}
}
returnTypes = append(returnTypes, &field)
}
return &goast.FuncType{
Params: fieldList,
Results: &goast.FieldList{
List: returnTypes,
},
}
}
// NewGoExpr is used to facilitate the creation of go AST
//
// It should not be used to transpile user code.
func NewGoExpr(expr string) goast.Expr {
e, err := parser.ParseExpr(expr)
if err != nil {
panic("programming error: " + expr)
}
return e
}
// NewAnonymousFunction - create a new anonymous function.
// Example:
//
// func() returnType{
// defer func(){
// deferBody
// }()
// body
// return returnValue
// }
func NewAnonymousFunction(body, deferBody []goast.Stmt,
returnValue goast.Expr,
returnType string) *goast.CallExpr {
if deferBody != nil {
body = append(body, []goast.Stmt{&goast.DeferStmt{
Defer: 1,
Call: &goast.CallExpr{
Fun: &goast.FuncLit{
Type: &goast.FuncType{},
Body: &goast.BlockStmt{List: deferBody},
},
Lparen: 1,
},
}}...)
}
return &goast.CallExpr{Fun: &goast.FuncLit{
Type: &goast.FuncType{
Results: &goast.FieldList{List: []*goast.Field{
{Type: goast.NewIdent(returnType)},
}},
},
Body: &goast.BlockStmt{
List: append(body, &goast.ReturnStmt{
Results: []goast.Expr{returnValue},
}),
},
}}
}
// ConvertToUnsigned - return convertion from signed to unsigned type
//
// s := func() uint {
// var x int64
// x = -1
// return uint(x)
// }()
func ConvertToUnsigned(expr goast.Expr, returnType string) goast.Expr {
varName := "c4go_temp_name"
if u, ok := expr.(*goast.CallExpr); ok {
if i, ok := u.Fun.(*goast.Ident); ok {
switch i.Name {
case "uint32", "uint", "uint64":
expr = u.Args[0]
}
}
}
return &goast.CallExpr{Fun: &goast.FuncLit{
Type: &goast.FuncType{
Results: &goast.FieldList{List: []*goast.Field{
{Type: goast.NewIdent(returnType)},
}},
},
Body: &goast.BlockStmt{
List: []goast.Stmt{
&goast.DeclStmt{
Decl: &goast.GenDecl{
Tok: token.VAR,
Specs: []goast.Spec{
&goast.ValueSpec{
Names: []*goast.Ident{goast.NewIdent(varName)},
Type: goast.NewIdent("int64"),
},
},
},
},
&goast.AssignStmt{
Lhs: []goast.Expr{goast.NewIdent(varName)},
Tok: token.ASSIGN,
Rhs: []goast.Expr{expr},
},
&goast.ReturnStmt{
Results: []goast.Expr{
&goast.CallExpr{
Fun: goast.NewIdent(returnType),
Args: []goast.Expr{goast.NewIdent(varName)},
},
},
},
},
},
}}
}