cgo: refactor constant expressions
Put them in a separate file for separation of concerns (making them testable) and add some tests.
Этот коммит содержится в:
родитель
992f1fa248
коммит
5987233b99
3 изменённых файлов: 109 добавлений и 45 удалений
59
cgo/const.go
Обычный файл
59
cgo/const.go
Обычный файл
|
@ -0,0 +1,59 @@
|
||||||
|
package cgo
|
||||||
|
|
||||||
|
// This file implements a parser of a subset of the C language, just enough to
|
||||||
|
// parse common #define statements to Go constant expressions.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// parseConst parses the given string as a C constant.
|
||||||
|
func parseConst(pos token.Pos, value string) *ast.BasicLit {
|
||||||
|
for len(value) != 0 && value[0] == '(' && value[len(value)-1] == ')' {
|
||||||
|
value = strings.TrimSpace(value[1 : len(value)-1])
|
||||||
|
}
|
||||||
|
if len(value) == 0 {
|
||||||
|
// Pretend it doesn't exist at all.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// For information about integer literals:
|
||||||
|
// https://en.cppreference.com/w/cpp/language/integer_literal
|
||||||
|
if value[0] == '"' {
|
||||||
|
// string constant
|
||||||
|
return &ast.BasicLit{ValuePos: pos, Kind: token.STRING, Value: value}
|
||||||
|
}
|
||||||
|
if value[0] == '\'' {
|
||||||
|
// char constant
|
||||||
|
return &ast.BasicLit{ValuePos: pos, Kind: token.CHAR, Value: value}
|
||||||
|
}
|
||||||
|
// assume it's a number (int or float)
|
||||||
|
value = strings.Replace(value, "'", "", -1) // remove ' chars
|
||||||
|
value = strings.TrimRight(value, "lu") // remove llu suffixes etc.
|
||||||
|
// find the first non-number
|
||||||
|
nonnum := byte(0)
|
||||||
|
for i := 0; i < len(value); i++ {
|
||||||
|
if value[i] < '0' || value[i] > '9' {
|
||||||
|
nonnum = value[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// determine number type based on the first non-number
|
||||||
|
switch nonnum {
|
||||||
|
case 0:
|
||||||
|
// no non-number found, must be an integer
|
||||||
|
return &ast.BasicLit{ValuePos: pos, Kind: token.INT, Value: value}
|
||||||
|
case 'x', 'X':
|
||||||
|
// hex integer constant
|
||||||
|
// TODO: may also be a floating point number per C++17.
|
||||||
|
return &ast.BasicLit{ValuePos: pos, Kind: token.INT, Value: value}
|
||||||
|
case '.', 'e':
|
||||||
|
// float constant
|
||||||
|
value = strings.TrimRight(value, "fFlL")
|
||||||
|
return &ast.BasicLit{ValuePos: pos, Kind: token.FLOAT, Value: value}
|
||||||
|
default:
|
||||||
|
// unknown type, ignore
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
45
cgo/const_test.go
Обычный файл
45
cgo/const_test.go
Обычный файл
|
@ -0,0 +1,45 @@
|
||||||
|
package cgo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"go/format"
|
||||||
|
"go/token"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseConst(t *testing.T) {
|
||||||
|
// Test converting a C constant to a Go constant.
|
||||||
|
for _, tc := range []struct {
|
||||||
|
C string
|
||||||
|
Go string
|
||||||
|
}{
|
||||||
|
{`5`, `5`},
|
||||||
|
{`(5)`, `5`},
|
||||||
|
{`(((5)))`, `5`},
|
||||||
|
{`5.8f`, `5.8`},
|
||||||
|
{`foo`, `<invalid>`}, // identifiers unimplemented
|
||||||
|
{``, `<invalid>`}, // empty constants not allowed in Go
|
||||||
|
{`"foo"`, `"foo"`},
|
||||||
|
{`'a'`, `'a'`},
|
||||||
|
{`0b10`, `<invalid>`}, // binary number literals unimplemented
|
||||||
|
} {
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
startPos := fset.AddFile("test.c", -1, 1000).Pos(0)
|
||||||
|
expr := parseConst(startPos, tc.C)
|
||||||
|
s := "<invalid>"
|
||||||
|
if expr != nil {
|
||||||
|
// Serialize the Go constant to a string, for more readable test
|
||||||
|
// cases.
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
err := format.Node(buf, fset, expr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not format expr from C constant %#v: %v", tc.C, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s = buf.String()
|
||||||
|
}
|
||||||
|
if s != tc.Go {
|
||||||
|
t.Errorf("C constant %#v was parsed to %#v while expecting %#v", tc.C, s, tc.Go)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -246,51 +246,11 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
value := strings.TrimSpace(source[len(name):])
|
value := strings.TrimSpace(source[len(name):])
|
||||||
for len(value) != 0 && value[0] == '(' && value[len(value)-1] == ')' {
|
// Try to convert this #define into a Go constant expression.
|
||||||
value = strings.TrimSpace(value[1 : len(value)-1])
|
expr := parseConst(pos, value)
|
||||||
}
|
if expr != nil {
|
||||||
if len(value) == 0 {
|
// Parsing was successful.
|
||||||
// Pretend it doesn't exist at all.
|
p.constants[name] = constantInfo{expr, pos}
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
// For information about integer literals:
|
|
||||||
// https://en.cppreference.com/w/cpp/language/integer_literal
|
|
||||||
if value[0] == '"' {
|
|
||||||
// string constant
|
|
||||||
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.STRING, value}, pos}
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
if value[0] == '\'' {
|
|
||||||
// char constant
|
|
||||||
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.CHAR, value}, pos}
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
// assume it's a number (int or float)
|
|
||||||
value = strings.Replace(value, "'", "", -1) // remove ' chars
|
|
||||||
value = strings.TrimRight(value, "lu") // remove llu suffixes etc.
|
|
||||||
// find the first non-number
|
|
||||||
nonnum := byte(0)
|
|
||||||
for i := 0; i < len(value); i++ {
|
|
||||||
if value[i] < '0' || value[i] > '9' {
|
|
||||||
nonnum = value[i]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// determine number type based on the first non-number
|
|
||||||
switch nonnum {
|
|
||||||
case 0:
|
|
||||||
// no non-number found, must be an integer
|
|
||||||
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.INT, value}, pos}
|
|
||||||
case 'x', 'X':
|
|
||||||
// hex integer constant
|
|
||||||
// TODO: may also be a floating point number per C++17.
|
|
||||||
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.INT, value}, pos}
|
|
||||||
case '.', 'e':
|
|
||||||
// float constant
|
|
||||||
value = strings.TrimRight(value, "fFlL")
|
|
||||||
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.FLOAT, value}, pos}
|
|
||||||
default:
|
|
||||||
// unknown type, ignore
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return C.CXChildVisit_Continue
|
return C.CXChildVisit_Continue
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче