diff --git a/.snapshots/c2go-TestCLI-func1-TranspileHelpFlag b/.snapshots/c2go-TestCLI-func1-TranspileHelpFlag index e46011164..258bde4d4 100644 --- a/.snapshots/c2go-TestCLI-func1-TranspileHelpFlag +++ b/.snapshots/c2go-TestCLI-func1-TranspileHelpFlag @@ -1,7 +1,9 @@ -([]string) (len=8) { +([]string) (len=10) { (string) (len=59) "Usage: test transpile [-V] [-o file.go] [-p package] file.c", (string) (len=31) " -V\tprint progress as comments", (string) (len=27) " -h\tprint help information", + (string) (len=14) " -keep-unused", + (string) (len=105) " \tKeep unused constants, functions, variables, types and methods of unused types from C system headers", (string) (len=11) " -o string", (string) (len=51) " \toutput Go generated code to the specified file", (string) (len=11) " -p string", diff --git a/.snapshots/c2go-TestCLI-func1-TranspileNoFilesHelp b/.snapshots/c2go-TestCLI-func1-TranspileNoFilesHelp index e46011164..258bde4d4 100644 --- a/.snapshots/c2go-TestCLI-func1-TranspileNoFilesHelp +++ b/.snapshots/c2go-TestCLI-func1-TranspileNoFilesHelp @@ -1,7 +1,9 @@ -([]string) (len=8) { +([]string) (len=10) { (string) (len=59) "Usage: test transpile [-V] [-o file.go] [-p package] file.c", (string) (len=31) " -V\tprint progress as comments", (string) (len=27) " -h\tprint help information", + (string) (len=14) " -keep-unused", + (string) (len=105) " \tKeep unused constants, functions, variables, types and methods of unused types from C system headers", (string) (len=11) " -o string", (string) (len=51) " \toutput Go generated code to the specified file", (string) (len=11) " -p string", diff --git a/ast/alloc_size_attr.go b/ast/alloc_size_attr.go new file mode 100644 index 000000000..5f7cbb2c9 --- /dev/null +++ b/ast/alloc_size_attr.go @@ -0,0 +1,57 @@ +package ast + +import ( + "strings" + + "github.com/elliotchance/c2go/util" +) + +// AllocSizeAttr is a type of attribute that is optionally attached to a variable +// or struct field definition. +type AllocSizeAttr struct { + Addr Address + Pos Position + Inherited bool + A int + B int + ChildNodes []Node +} + +func parseAllocSizeAttr(line string) *AllocSizeAttr { + groups := groupsFromRegex( + `<(?P.*)>(?P Inherited)?(?P \d+)(?P \d+)?`, + line, + ) + + return &AllocSizeAttr{ + Addr: ParseAddress(groups["address"]), + Pos: NewPositionFromString(groups["position"]), + Inherited: len(groups["inherited"]) > 0, + A: util.Atoi(strings.TrimSpace(groups["a"])), + B: util.Atoi(strings.TrimSpace(groups["b"])), + ChildNodes: []Node{}, + } +} + +// AddChild adds a new child node. Child nodes can then be accessed with the +// Children attribute. +func (n *AllocSizeAttr) AddChild(node Node) { + n.ChildNodes = append(n.ChildNodes, node) +} + +// Address returns the numeric address of the node. See the documentation for +// the Address type for more information. +func (n *AllocSizeAttr) Address() Address { + return n.Addr +} + +// Children returns the child nodes. If this node does not have any children or +// this node does not support children it will always return an empty slice. +func (n *AllocSizeAttr) Children() []Node { + return n.ChildNodes +} + +// Position returns the position in the original source code. +func (n *AllocSizeAttr) Position() Position { + return n.Pos +} diff --git a/ast/alloc_size_attr_test.go b/ast/alloc_size_attr_test.go new file mode 100644 index 000000000..fa5983576 --- /dev/null +++ b/ast/alloc_size_attr_test.go @@ -0,0 +1,27 @@ +package ast + +import ( + "testing" +) + +func TestAllocSizeAttr(t *testing.T) { + nodes := map[string]Node{ + `0x7f8e390a5d38 1 2`: &AllocSizeAttr{ + Addr: 0x7f8e390a5d38, + Pos: NewPositionFromString("col:100, col:114"), + A: 1, + B: 2, + ChildNodes: []Node{}, + }, + `0x7fbd1a167f48 Inherited 1 0`: &AllocSizeAttr{ + Addr: 0x7fbd1a167f48, + Pos: NewPositionFromString("/usr/include/stdlib.h:342:37"), + Inherited: true, + A: 1, + B: 0, + ChildNodes: []Node{}, + }, + } + + runNodeTests(t, nodes) +} diff --git a/ast/ast.go b/ast/ast.go index 78244809d..b8cb298c5 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -45,6 +45,8 @@ func Parse(line string) Node { switch nodeName { case "AlignedAttr": return parseAlignedAttr(line) + case "AllocSizeAttr": + return parseAllocSizeAttr(line) case "AlwaysInlineAttr": return parseAlwaysInlineAttr(line) case "ArraySubscriptExpr": @@ -85,6 +87,8 @@ func Parse(line string) Node { return parseDeclStmt(line) case "DefaultStmt": return parseDefaultStmt(line) + case "DisableTailCallsAttr": + return parseDisableTailCallsAttr(line) case "DeprecatedAttr": return parseDeprecatedAttr(line) case "DoStmt": diff --git a/ast/ast_test.go b/ast/ast_test.go index 0f6f08072..984f88c8a 100644 --- a/ast/ast_test.go +++ b/ast/ast_test.go @@ -36,3 +36,23 @@ func runNodeTests(t *testing.T, tests map[string]Node) { }) } } + +func TestPrint(t *testing.T) { + cond := &ConditionalOperator{} + cond.AddChild(&ImplicitCastExpr{}) + cond.AddChild(&ImplicitCastExpr{}) + s := Atos(cond) + if len(s) == 0 { + t.Fatalf("Cannot convert AST tree : %#v", cond) + } + lines := strings.Split(s, "\n") + var amount int + for _, l := range lines { + if strings.Contains(l, "ImplicitCastExpr") { + amount++ + } + } + if amount != 2 { + t.Error("Not correct design of output") + } +} diff --git a/ast/disable_tail_calls_attr.go b/ast/disable_tail_calls_attr.go new file mode 100644 index 000000000..86007a154 --- /dev/null +++ b/ast/disable_tail_calls_attr.go @@ -0,0 +1,45 @@ +package ast + +// DisableTailCallsAttr is a type of attribute that is optionally attached to a variable +// or struct field definition. +type DisableTailCallsAttr struct { + Addr Address + Pos Position + ChildNodes []Node +} + +func parseDisableTailCallsAttr(line string) *DisableTailCallsAttr { + groups := groupsFromRegex( + "<(?P.*)>", + line, + ) + + return &DisableTailCallsAttr{ + Addr: ParseAddress(groups["address"]), + Pos: NewPositionFromString(groups["position"]), + ChildNodes: []Node{}, + } +} + +// AddChild adds a new child node. Child nodes can then be accessed with the +// Children attribute. +func (n *DisableTailCallsAttr) AddChild(node Node) { + n.ChildNodes = append(n.ChildNodes, node) +} + +// Address returns the numeric address of the node. See the documentation for +// the Address type for more information. +func (n *DisableTailCallsAttr) Address() Address { + return n.Addr +} + +// Children returns the child nodes. If this node does not have any children or +// this node does not support children it will always return an empty slice. +func (n *DisableTailCallsAttr) Children() []Node { + return n.ChildNodes +} + +// Position returns the position in the original source code. +func (n *DisableTailCallsAttr) Position() Position { + return n.Pos +} diff --git a/ast/disable_tail_calls_attr_test.go b/ast/disable_tail_calls_attr_test.go new file mode 100644 index 000000000..5168b33d0 --- /dev/null +++ b/ast/disable_tail_calls_attr_test.go @@ -0,0 +1,17 @@ +package ast + +import ( + "testing" +) + +func TestDisableTailCallsAttr(t *testing.T) { + nodes := map[string]Node{ + `0x7fc8fa094558 `: &DisableTailCallsAttr{ + Addr: 0x7fc8fa094558, + Pos: NewPositionFromString("col:107"), + ChildNodes: []Node{}, + }, + } + + runNodeTests(t, nodes) +} diff --git a/ast/floating_literal.go b/ast/floating_literal.go index 725c44217..495cef4a4 100644 --- a/ast/floating_literal.go +++ b/ast/floating_literal.go @@ -3,8 +3,9 @@ package ast import ( "errors" "fmt" - "github.com/elliotchance/c2go/cc" "reflect" + + "github.com/elliotchance/c2go/cc" ) type FloatingLiteral struct { diff --git a/ast/floating_literal_test.go b/ast/floating_literal_test.go index c388cde7c..496aa2015 100644 --- a/ast/floating_literal_test.go +++ b/ast/floating_literal_test.go @@ -13,6 +13,13 @@ func TestFloatingLiteral(t *testing.T) { Value: 1.23, ChildNodes: []Node{}, }, + `0x21c65b8 'double' 2.718282e+00`: &FloatingLiteral{ + Addr: 0x21c65b8, + Pos: NewPositionFromString("col:41"), + Type: "double", + Value: 2.718282e+00, + ChildNodes: []Node{}, + }, } runNodeTests(t, nodes) diff --git a/ast/position.go b/ast/position.go index b98c7794f..37f65ee12 100644 --- a/ast/position.go +++ b/ast/position.go @@ -217,6 +217,8 @@ func setPosition(node Node, position Position) { switch n := node.(type) { case *AlignedAttr: n.Pos = position + case *AllocSizeAttr: + n.Pos = position case *AlwaysInlineAttr: n.Pos = position case *ArraySubscriptExpr: @@ -255,6 +257,8 @@ func setPosition(node Node, position Position) { n.Pos = position case *DeprecatedAttr: n.Pos = position + case *DisableTailCallsAttr: + n.Pos = position case *DoStmt: n.Pos = position case *EnumConstantDecl: diff --git a/ast/util.go b/ast/util.go index 74a297caa..a1a147089 100644 --- a/ast/util.go +++ b/ast/util.go @@ -1,6 +1,9 @@ package ast import ( + "bytes" + "encoding/json" + "fmt" "strconv" "strings" ) @@ -33,3 +36,41 @@ func atof(s string) float64 { return f } + +// Atos - ASTree to string +// Typically using for debug +func Atos(node Node) string { + j, err := json.Marshal(node) + if err != nil { + panic(err) + } + var out bytes.Buffer + err = json.Indent(&out, j, "", "\t") + if err != nil { + panic(err) + } + var str string + str += fmt.Sprint("==== START OF AST tree ====\n") + str += out.String() + str += fmt.Sprintf("\nTypes tree:\n") + str += typesTree(node, 0) + str += fmt.Sprint("==== END OF AST tree ====\n") + return str +} + +func typesTree(node Node, depth int) (str string) { + if node == (Node)(nil) { + return "" + } + for i := 0; i < depth; i++ { + str += "\t" + } + str += fmt.Sprintf("%T\n", node) + depth++ + if len(node.Children()) > 0 { + for _, n := range node.Children() { + str += typesTree(n, depth) + } + } + return str +} diff --git a/linux/ctype.go b/linux/ctype.go index 52bf59d94..5d5037dd7 100644 --- a/linux/ctype.go +++ b/linux/ctype.go @@ -45,7 +45,7 @@ func generateCharacterTable() { c |= ((1 << (6)) << 8) } - // The IsSpace check is required becuase Go treats spaces as graphic + // The IsSpace check is required, because Go treats spaces as graphic // characters, which C does not. if unicode.IsGraphic(rune(i)) && !unicode.IsSpace(rune(i)) { c |= ((1 << (7)) << 8) @@ -83,12 +83,132 @@ func CtypeLoc() [][]uint16 { return [][]uint16{characterTable} } -// ToLower handles tolower(). -func ToLower(_c int) int { - return int(unicode.ToLower(rune(_c))) +const ( + cFalse int = 0 + cTrue int = 1 +) + +// IsAlpha handles isalpha(). +func IsAlpha(_c int) int { + if _c < 'A' || _c > 'z' { + return cFalse + } else if _c > 'Z' && _c < 'a' { + return cFalse + } + return cTrue +} + +// IsAlnum handles isalnum(). +func IsAlnum(_c int) int { + if IsDigit(_c) == cTrue { + return cTrue + } + if IsAlpha(_c) == cTrue { + return cTrue + } + return cFalse +} + +// IsCntrl handles iscnrl(). +func IsCntrl(_c int) int { + if unicode.IsControl(rune(_c)) { + return cTrue + } + return cFalse +} + +// IsDigit handles isdigit(). +func IsDigit(_c int) int { + if _c >= '0' && _c <= '9' { + return cTrue + } + return cFalse +} + +// IsGraph handles isgraph(). +func IsGraph(_c int) int { + if _c == ' ' { + return cFalse // Different implementation between C and Go + } + if unicode.IsGraphic(rune(_c)) { + return cTrue + } + return cFalse +} + +// IsLower handles islower(). +func IsLower(_c int) int { + if unicode.IsLower(rune(_c)) { + return cTrue + } + return cFalse +} + +// IsPrint handles isprint(). +func IsPrint(_c int) int { + if unicode.IsPrint(rune(_c)) { + return cTrue + } + return cFalse +} + +// IsPunct handles isprunct(). +func IsPunct(_c int) int { + if unicode.IsPunct(rune(_c)) { + return cTrue + } + return cFalse +} + +// IsSpace handles isspace(). +func IsSpace(_c int) int { + if unicode.IsSpace(rune(_c)) { + return cTrue + } + return cFalse +} + +// IsUpper handles isupper(). +func IsUpper(_c int) int { + if unicode.IsUpper(rune(_c)) { + return cTrue + } + return cFalse +} + +// IsXDigit handles isxdigit(). +func IsXDigit(_c int) int { + if _c >= '0' && _c <= '9' { + return cTrue + } + if _c >= 'A' && _c <= 'F' { + return cTrue + } + if _c >= 'a' && _c <= 'f' { + return cTrue + } + return cFalse } // ToUpper handles toupper(). func ToUpper(_c int) int { return int(unicode.ToUpper(rune(_c))) } + +// ToLower handles tolower(). +func ToLower(_c int) int { + return int(unicode.ToLower(rune(_c))) +} + +// IsASCII handles isascii(). +func IsASCII(_c int) int { + if _c >= 0x80 { + return cFalse + } + return cTrue +} + +// ToASCII handles toascii(). +func ToASCII(_c int) int { + return int(byte(_c)) +} diff --git a/linux/math.go b/linux/math.go index ebc7dffe3..24debc17e 100644 --- a/linux/math.go +++ b/linux/math.go @@ -6,14 +6,22 @@ import ( "github.com/elliotchance/c2go/noarch" ) +// IsNanf handles __isnanf(float) func IsNanf(x float32) int { return noarch.BoolToInt(math.IsNaN(float64(x))) } +// IsInff handles __isinff(float) func IsInff(x float32) int { return noarch.BoolToInt(math.IsInf(float64(x), 0)) } +// IsInfd handles __inline_isinfd(double) +func IsInfd(x float64) int { + return noarch.BoolToInt(math.IsInf(float64(x), 0)) +} + +// IsInf handles __inline_isinfl(long double) func IsInf(x float64) int { return noarch.BoolToInt(math.IsInf(x, 0)) } diff --git a/main.go b/main.go index 04261fcb2..da9abf936 100644 --- a/main.go +++ b/main.go @@ -11,7 +11,6 @@ package main import ( - "bytes" "flag" "fmt" "io" @@ -26,6 +25,7 @@ import ( "errors" "github.com/elliotchance/c2go/ast" + "github.com/elliotchance/c2go/preprocessor" "github.com/elliotchance/c2go/program" "github.com/elliotchance/c2go/transpiler" ) @@ -57,6 +57,9 @@ type ProgramArgs struct { // A private option to output the Go as a *_test.go file. outputAsTest bool + + // Keep unused entities + keepUnused bool } // DefaultProgramArgs default value of ProgramArgs @@ -66,6 +69,7 @@ func DefaultProgramArgs() ProgramArgs { ast: false, packageName: "main", outputAsTest: false, + keepUnused: false, } } @@ -158,20 +162,13 @@ func Start(args ProgramArgs) (err error) { if args.verbose { fmt.Println("Running clang preprocessor...") } - var pp []byte - { - // See : https://clang.llvm.org/docs/CommandGuide/clang.html - // clang -E Run the preprocessor stage. - cmd := exec.Command("clang", "-E", args.inputFile) - var out bytes.Buffer - var stderr bytes.Buffer - cmd.Stdout = &out - cmd.Stderr = &stderr - err = cmd.Run() - if err != nil { - return fmt.Errorf("preprocess failed: %v\nStdErr = %v", err, stderr.String()) - } - pp = out.Bytes() + + pp, userPosition, err := preprocessor.Analyze(args.inputFile) + if err != nil { + return err + } + if args.keepUnused { + userPosition = 0 } if args.verbose { @@ -218,6 +215,7 @@ func Start(args ProgramArgs) (err error) { p := program.NewProgram() p.Verbose = args.verbose p.OutputAsTest = args.outputAsTest + p.UserPosition = userPosition // Converting to nodes if args.verbose { @@ -277,6 +275,7 @@ var ( verboseFlag = transpileCommand.Bool("V", false, "print progress as comments") outputFlag = transpileCommand.String("o", "", "output Go generated code to the specified file") packageFlag = transpileCommand.String("p", "main", "set the name of the generated package") + keepUnused = transpileCommand.Bool("keep-unused", false, "Keep unused constants, functions, variables, types and methods of unused types from C system headers") transpileHelpFlag = transpileCommand.Bool("h", false, "print help information") astCommand = flag.NewFlagSet("ast", flag.ContinueOnError) astHelpFlag = astCommand.Bool("h", false, "print help information") @@ -291,7 +290,7 @@ func main() { func runCommand() int { flag.Usage = func() { - usage := "Usage: %s [-v] [] [] file.c\n\n" + usage := "Usage: %s [-v] [] [] [-keep-unused] file.c\n\n" usage += "Commands:\n" usage += " transpile\ttranspile an input C source file to Go\n" usage += " ast\t\tprint AST before translated Go code\n\n" @@ -318,6 +317,7 @@ func runCommand() int { } args := DefaultProgramArgs() + args.keepUnused = *keepUnused switch os.Args[1] { case "ast": diff --git a/noarch/ctype.go b/noarch/ctype.go new file mode 100644 index 000000000..77875433f --- /dev/null +++ b/noarch/ctype.go @@ -0,0 +1,18 @@ +// Package noarch contains low-level functions that apply to multiple platforms. +package noarch + +// Constants of `ctype.h` +const ( + ISupper uint16 = ((1 << 0) << 8) + ISlower uint16 = ((1 << 1) << 8) + ISalpha uint16 = ((1 << 2) << 8) + ISdigit uint16 = ((1 << 3) << 8) + ISxdigit uint16 = ((1 << 4) << 8) + ISspace uint16 = ((1 << 5) << 8) + ISprint uint16 = ((1 << 6) << 8) + ISgraph uint16 = ((1 << 7) << 8) + ISblank uint16 = ((1 << 8) >> 8) + IScntrl uint16 = ((1 << 9) >> 8) + ISpunct uint16 = ((1 << 10) >> 8) + ISalnum uint16 = ((1 << 11) >> 8) +) diff --git a/noarch/noarch.go b/noarch/noarch.go index 21acf344a..9f47dc447 100644 --- a/noarch/noarch.go +++ b/noarch/noarch.go @@ -30,7 +30,7 @@ func NotUint16(x uint16) uint16 { } // Ternary simulates the ternary (also known as the conditional operator). Go -// does not have the equivilent of using if statements as expressions or inline +// does not have the equivalent of using if statements as expressions or inline // if statements. This function takes the true and false parts as closures to be // sure that only the true or false condition is evaulated - to prevent side // effects. diff --git a/preprocessor/parse_include_list.go b/preprocessor/parse_include_list.go new file mode 100644 index 000000000..c146c079c --- /dev/null +++ b/preprocessor/parse_include_list.go @@ -0,0 +1,31 @@ +package preprocessor + +import ( + "strings" +) + +// parseIncludeList - parse list of includes +// Example : +// exit.o: exit.c /usr/include/stdlib.h /usr/include/features.h \ +// /usr/include/stdc-predef.h /usr/include/x86_64-linux-gnu/sys/cdefs.h +func parseIncludeList(line string) (lines []string, err error) { + line = strings.Replace(line, "\n", " ", -1) + line = strings.Replace(line, "\t", " ", -1) + line = strings.Replace(line, "\r", " ", -1) // Added for Mac endline symbol + line = strings.Replace(line, "\\", " ", -1) + line = strings.Replace(line, "\xFF", " ", -1) + line = strings.Replace(line, "\u0100", " ", -1) + parts := strings.Split(line, " ") + + for _, p := range parts { + p = strings.TrimSpace(p) + if p == "" { + continue + } + if p[len(p)-1] == ':' { + continue + } + lines = append(lines, p) + } + return +} diff --git a/preprocessor/parse_include_list_test.go b/preprocessor/parse_include_list_test.go new file mode 100644 index 000000000..66fc8c30b --- /dev/null +++ b/preprocessor/parse_include_list_test.go @@ -0,0 +1,47 @@ +package preprocessor + +import ( + "fmt" + "testing" +) + +func TestParseIncludeList(t *testing.T) { + testCases := []struct { + inputLine string + list []string + }{ + { + inputLine: ` exit.o: exit.c tests.h `, + list: []string{"exit.c", "tests.h"}, + }, + { + + inputLine: ` exit.o: exit.c /usr/include/stdlib.h /usr/include/features.h \ + /usr/include/stdc-predef.h /usr/include/x86_64-linux-gnu/sys/cdefs.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs-64.h \ + /usr/lib/llvm-3.8/bin/../lib/clang/3.8.0/include/stddef.h + `, + list: []string{"exit.c", "/usr/include/stdlib.h", "/usr/include/features.h", + "/usr/include/stdc-predef.h", "/usr/include/x86_64-linux-gnu/sys/cdefs.h", + "/usr/include/x86_64-linux-gnu/gnu/stubs-64.h", + "/usr/lib/llvm-3.8/bin/../lib/clang/3.8.0/include/stddef.h", + }, + }, + } + for i, tc := range testCases { + t.Run(fmt.Sprintf("Test:%d", i), func(t *testing.T) { + actual, err := parseIncludeList(tc.inputLine) + if err != nil { + t.Fatal(err) + } + if len(actual) != len(tc.list) { + t.Fatalf("Cannot parse line : %s. Actual result : %#v. Expected: %#v", tc.inputLine, actual, tc.list) + } + for i := range actual { + if actual[i] != tc.list[i] { + t.Fatalf("Cannot parse 'include' in line : %s. Actual result : %#v. Expected: %#v", tc.inputLine, actual[i], tc.list[i]) + } + } + }) + } +} diff --git a/preprocessor/parse_include_preprocessor_line.go b/preprocessor/parse_include_preprocessor_line.go new file mode 100644 index 000000000..1425232bb --- /dev/null +++ b/preprocessor/parse_include_preprocessor_line.go @@ -0,0 +1,47 @@ +package preprocessor + +import ( + "fmt" + "strconv" + "strings" +) + +// typically parse that line: +// # 11 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 2 3 4 +func parseIncludePreprocessorLine(line string) (item *entity, err error) { + if line[0] != '#' { + err = fmt.Errorf("Cannot parse: first symbol is not # in line %s", line) + return + } + i := strings.Index(line, "\"") + if i < 0 { + err = fmt.Errorf("First index is not correct on line %s", line) + return + } + l := strings.LastIndex(line, "\"") + if i >= l { + err = fmt.Errorf("Not allowable positions of symbol \" (%d and %d) in line : %s", i, l, line) + return + } + + pos, err := strconv.ParseInt(strings.TrimSpace(line[1:i]), 10, 64) + if err != nil { + err = fmt.Errorf("Cannot parse position in source : %v", err) + return + } + + if l+1 < len(line) { + item = &entity{ + positionInSource: int(pos), + include: line[i+1 : l], + other: line[l+1:], + } + } else { + item = &entity{ + positionInSource: int(pos), + include: line[i+1 : l], + } + } + + return +} diff --git a/preprocessor/parse_include_preprocessor_line_test.go b/preprocessor/parse_include_preprocessor_line_test.go new file mode 100644 index 000000000..d4d3f4bc7 --- /dev/null +++ b/preprocessor/parse_include_preprocessor_line_test.go @@ -0,0 +1,113 @@ +package preprocessor + +import ( + "fmt" + "testing" +) + +func TestParseIncludePreproccessorLine(t *testing.T) { + testCases := []struct { + inputLine string + out entity + }{ + { + inputLine: `# 1 "/usr/include/x86_64-linux-gnu/bits/sys_errlist.h" 1 3 4`, + out: entity{ + include: "/usr/include/x86_64-linux-gnu/bits/sys_errlist.h", + positionInSource: 1, + }, + }, + { + inputLine: `# 26 "/usr/include/x86_64-linux-gnu/bits/sys_errlist.h" 3 4`, + out: entity{ + include: "/usr/include/x86_64-linux-gnu/bits/sys_errlist.h", + positionInSource: 26, + }, + }, + { + inputLine: `# 854 "/usr/include/stdio.h" 2 3 4`, + out: entity{ + include: "/usr/include/stdio.h", + positionInSource: 854, + }, + }, + { + inputLine: `# 2 "f.c" 2`, + out: entity{ + include: "f.c", + positionInSource: 2, + }, + }, + { + inputLine: `# 2 "f.c"`, + out: entity{ + include: "f.c", + positionInSource: 2, + }, + }, + { + inputLine: `# 30 "/usr/lib/llvm-3.8/bin/../lib/clang/3.8.0/include/stdarg.h" 3 4`, + out: entity{ + include: "/usr/lib/llvm-3.8/bin/../lib/clang/3.8.0/include/stdarg.h", + positionInSource: 30, + }, + }, + } + for i, tc := range testCases { + t.Run(fmt.Sprintf("Test:%d", i), func(t *testing.T) { + actual, err := parseIncludePreprocessorLine(tc.inputLine) + if err != nil { + t.Fatal(err) + } + if len(actual.include) == 0 { + t.Fatal("Cannot parse, because result is empty") + } + if actual.include != tc.out.include { + t.Fatalf("Cannot parse line: \"%s\". Result: \"%s\". Expected: \"%s\"", tc.inputLine, actual.include, tc.out.include) + } + if actual.positionInSource != tc.out.positionInSource { + t.Fatalf("Cannot parse source position in line: \"%s\". Result: \"%d\". Expected: \"%d\"", tc.inputLine, actual.positionInSource, tc.out.positionInSource) + } + }) + } +} + +func TestParseIncludePreproccessorLineFail1(t *testing.T) { + inputLine := `# A "/usr/include/stdio.h" 2 3 4` + _, err := parseIncludePreprocessorLine(inputLine) + if err == nil { + t.Fatal("Cannot found error in positionInSource") + } +} + +func TestParseIncludePreproccessorLineFail2(t *testing.T) { + inputLine := ` # 850 "/usr/include/stdio.h" 2 3 4` + _, err := parseIncludePreprocessorLine(inputLine) + if err == nil { + t.Fatal("Cannot give error if first symbol is not #") + } +} + +func TestParseIncludePreproccessorLineFail3(t *testing.T) { + inputLine := `# 850` + _, err := parseIncludePreprocessorLine(inputLine) + if err == nil { + t.Fatal("Cannot give error if line hanen't include string") + } +} + +func TestParseIncludePreproccessorLineFail4(t *testing.T) { + inputLine := `# 850 "/usr/include` + _, err := parseIncludePreprocessorLine(inputLine) + if err == nil { + t.Fatal("Cannot give error if wrong format of include line") + } +} + +func TestParseIncludePreproccessorLineFail5(t *testing.T) { + inputLine := `# 850` + _, err := parseIncludePreprocessorLine(inputLine) + if err == nil { + t.Fatal("Cannot give error if haven`t include line") + } +} diff --git a/preprocessor/preprocessor.go b/preprocessor/preprocessor.go new file mode 100644 index 000000000..d0ca71580 --- /dev/null +++ b/preprocessor/preprocessor.go @@ -0,0 +1,145 @@ +package preprocessor + +import ( + "bufio" + "bytes" + "fmt" + "os/exec" + "strings" +) + +// One simple part of preprocessor code +type entity struct { + positionInSource int + include string + other string + + // Zero index of `lines` is look like that: + // # 11 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 2 3 4 + // After that 0 or more lines of codes + lines []*string +} + +// Analyze - separation preprocessor code to part +func Analyze(inputFile string) (pp []byte, userPosition int, err error) { + // See : https://clang.llvm.org/docs/CommandGuide/clang.html + // clang -E Run the preprocessor stage. + out, err := getPreprocessSource(inputFile) + if err != nil { + return + } + + // Parsing preprocessor file + r := bytes.NewReader(out.Bytes()) + scanner := bufio.NewScanner(r) + scanner.Split(bufio.ScanLines) + // counter - get position of line + var counter int + // item, items - entity of preprocess file + var item *entity + var items []entity + for scanner.Scan() { + line := scanner.Text() + if len(line) > 0 && line[0] == '#' && + (len(line) >= 7 && line[0:7] != "#pragma") { + if item != (*entity)(nil) { + items = append(items, *item) + } + item, err = parseIncludePreprocessorLine(line) + if err != nil { + err = fmt.Errorf("Cannot parse line : %s with error: %s", line, err) + return + } + if item.positionInSource == 0 { + // cannot by less 1 for avoid problem with + // indentification of "0" AST base element + item.positionInSource = 1 + } + item.lines = make([]*string, 0) + } + counter++ + item.lines = append(item.lines, &line) + } + if item != (*entity)(nil) { + items = append(items, *item) + } + + // Get list of include files + includeList, err := getIncludeList(inputFile) + if err != nil { + return + } + + // Renumbering positionInSource in source for user code to unique + // Let`s call that positionInSource - userPosition + // for example: if some entity(GenDecl,...) have positionInSource + // less userPosition, then that is from system library, but not + // from user source. + for _, item := range items { + if userPosition < item.positionInSource { + userPosition = item.positionInSource + } + } + for _, item := range items { + userPosition += len(item.lines) + } + for i := range items { + for _, inc := range includeList { + if inc == items[i].include { + items[i].positionInSource = (userPosition + 1) * (i + 1) + } + } + } + // Now, userPosition is unique and more then other + + // Merge the entities + lines := make([]string, 0, counter) + for _, item := range items { + header := fmt.Sprintf("# %d \"%s\"%s", item.positionInSource, item.include, item.other) + lines = append(lines, header) + if len(item.lines) > 0 { + for i, l := range item.lines { + if i == 0 { + continue + } + lines = append(lines, *l) + } + } + } + pp = ([]byte)(strings.Join(lines, "\n")) + + return +} + +// getIncludeList - Get list of include files +// Example: +// $ clang -MM -c exit.c +// exit.o: exit.c tests.h +func getIncludeList(inputFile string) (lines []string, err error) { + var out bytes.Buffer + var stderr bytes.Buffer + cmd := exec.Command("clang", "-MM", "-c", inputFile) + cmd.Stdout = &out + cmd.Stderr = &stderr + err = cmd.Run() + if err != nil { + err = fmt.Errorf("preprocess failed: %v\nStdErr = %v", err, stderr.String()) + return + } + return parseIncludeList(out.String()) +} + +// See : https://clang.llvm.org/docs/CommandGuide/clang.html +// clang -E Run the preprocessor stage. +func getPreprocessSource(inputFile string) (out bytes.Buffer, err error) { + var stderr bytes.Buffer + cmd := exec.Command("clang", "-E", inputFile) + cmd.Stdout = &out + cmd.Stderr = &stderr + err = cmd.Run() + if err != nil { + err = fmt.Errorf("preprocess failed: %v\nStdErr = %v", err, stderr.String()) + return + } + return +} diff --git a/program/function_definition.go b/program/function_definition.go index b9d3e3e2c..d2133a3eb 100644 --- a/program/function_definition.go +++ b/program/function_definition.go @@ -73,8 +73,22 @@ var builtInFunctionDefinitions = []string{ // linux/ctype.h "const unsigned short int** __ctype_b_loc() -> linux.CtypeLoc", - "int tolower(int) -> linux.ToLower", + // See https://opensource.apple.com/source/xnu/xnu-344.49/osfmk/libsa/ctype.h.auto.html + "int isalpha(int) -> linux.IsAlpha", + "int isalnum(int) -> linux.IsAlnum", + "int iscntrl(int) -> linux.IsCntrl", + "int isdigit(int) -> linux.IsDigit", + "int isgraph(int) -> linux.IsGraph", + "int islower(int) -> linux.IsLower", + "int isprint(int) -> linux.IsPrint", + "int ispunct(int) -> linux.IsPunct", + "int isspace(int) -> linux.IsSpace", + "int isupper(int) -> linux.IsUpper", + "int isxdigit(int) -> linux.IsXDigit", "int toupper(int) -> linux.ToUpper", + "int tolower(int) -> linux.ToLower", + "int isascii(int) -> linux.IsASCII", + "int toascii(int) -> linux.ToASCII", // linux/math.h "int __signbitf(float) -> noarch.Signbitf", @@ -84,11 +98,15 @@ var builtInFunctionDefinitions = []string{ "int __builtin_signbit(double) -> noarch.Signbitd", "int __builtin_signbitl(long double) -> noarch.Signbitl", "int __isnanf(float) -> linux.IsNanf", + "int __inline_isnanf(float) -> linux.IsNanf", "int __isnan(double) -> noarch.IsNaN", + "int __inline_isnand(double) -> noarch.IsNaN", + "int __inline_isnanl(long double) -> noarch.IsNaN", "int __isnanl(long double) -> noarch.IsNaN", "int __isinff(float) -> linux.IsInff", "int __isinf(double) -> linux.IsInf", "int __isinfl(long double) -> linux.IsInf", + "int __inline_isinfl(long double) -> linux.IsInf", // darwin/math.h "double __builtin_fabs(double) -> darwin.Fabs", @@ -106,6 +124,8 @@ var builtInFunctionDefinitions = []string{ "int __inline_signbitd(double) -> noarch.Signbitd", "int __inline_signbitl(long double) -> noarch.Signbitl", "double __builtin_nanf(const char*) -> darwin.NaN", + "int __inline_isinff(float) -> linux.IsInff", + "int __inline_isinfd(double) -> linux.IsInfd", // linux/assert.h "bool __assert_fail(const char*, const char*, unsigned int, const char*) -> linux.AssertFail", diff --git a/program/program.go b/program/program.go index 285d7a548..d9ddba310 100644 --- a/program/program.go +++ b/program/program.go @@ -75,6 +75,13 @@ type Program struct { // Go-test rather than a standalone Go file. OutputAsTest bool + // Renumbering positionInSource inside preprocessor package in source + // for user code to unique + // for example: if some entity(GenDecl,...) have line position + // less UserPosition, then that is from system library, but not + // from user source. + UserPosition int + // EnumConstantToEnum - a map with key="EnumConstant" and value="enum type" // clang don`t show enum constant with enum type, // so we have to use hack for repair the type diff --git a/tests/enum.c b/tests/enum.c index fab623370..1f9971096 100644 --- a/tests/enum.c +++ b/tests/enum.c @@ -5,8 +5,8 @@ enum number{zero, one, two, three}; enum { - _ISupper = ((0) < 8 ? ((1 << (0)) << 8) : ((1 << (0)) >> 8)), - _ISalnum = ((11) < 8 ? ((1 << (11)) << 8) : ((1 << (11)) >> 8)) + qupper = 256, + qalnum = 8 }; enum year{Jan, Feb, Mar, Apr, May, Jun, Jul, @@ -27,31 +27,31 @@ int main() { plan(17); - // step 1 + // step enum number n; n = two; is_eq(two ,2); is_eq(n ,2); - // step 2 - is_eq(_ISupper ,256); - is_eq(_ISalnum ,8 ); + // step + is_eq(qupper ,256); + is_eq(qalnum ,8 ); - // step 3 + // step for (int i=Jan; i < Feb; i++){ is_eq(i, Jan); } - // step 4 + // step is_eq( Working , 1); is_eq( Failed , 0); is_eq( Freezed , 0); - // step 5 + // step enum day d = thursday; is_eq( d , 10); - // step 6 + // step is_eq( sunday , 1); is_eq( monday , 2); is_eq( tuesday , 5); @@ -60,7 +60,7 @@ int main() is_eq( friday , 11); is_eq( saturday , 12); - // step 7 + // step is_eq( FindState() , FREEZED); done_testing(); diff --git a/tests/stdio.c b/tests/stdio.c index f18ba3321..52c17fe96 100644 --- a/tests/stdio.c +++ b/tests/stdio.c @@ -123,6 +123,8 @@ void test_fclose() pFile = fopen("/tmp/myfile.txt", "w"); fputs("fclose example", pFile); fclose(pFile); + // remove temp file + is_eq(remove("/tmp/myfile.txt"),0) } void test_fflush() @@ -136,6 +138,8 @@ void test_fflush() fflush(pFile); // flushing or repositioning required fgets(mybuffer, 80, pFile); fclose(pFile); + // remove temp file + is_eq(remove("/tmp/example.txt"),0) } void test_fprintf() @@ -153,6 +157,8 @@ void test_fprintf() } fclose(pFile); + // remove temp file + is_eq(remove("/tmp/myfile1.txt"),0) } void test_fscanf() @@ -172,6 +178,8 @@ void test_fscanf() is_eq(f, 3.1416); is_streq(str, "PI"); + // remove temp file + is_eq(remove("/tmp/myfile2.txt"),0) } void test_fgetc() @@ -226,6 +234,8 @@ void test_fputs() pFile = fopen("/tmp/mylog.txt", "w"); fputs(sentence, pFile); fclose(pFile); + // remove temp file + is_eq(remove("/tmp/mylog.txt"),0) } void test_getc() @@ -258,6 +268,8 @@ void test_putc() putc(c, pFile); } fclose(pFile); + // remove temp file + is_eq(remove("/tmp/whatever.txt"),0) } void test_fseek() @@ -268,6 +280,8 @@ void test_fseek() fseek(pFile, 9, SEEK_SET); fputs(" sam", pFile); fclose(pFile); + // remove temp file + is_eq(remove("/tmp/example.txt"),0) } void test_ftell() @@ -282,7 +296,7 @@ void test_ftell() size = ftell(pFile); fclose(pFile); - is_eq(size, 7971); + is_eq(size, 8546); } void test_fread() @@ -321,6 +335,8 @@ void test_fwrite() pFile = fopen("/tmp/myfile.bin", "w"); fwrite("xyz", 1, 3, pFile); fclose(pFile); + // remove temp file + is_eq(remove("/tmp/myfile.bin"),0) } void test_fgetpos() @@ -358,6 +374,8 @@ void test_fsetpos() fsetpos(pFile, &position); fputs("This", pFile); fclose(pFile); + // remove temp file + is_eq(remove("/tmp/myfile.txt"),0) } void test_rewind() @@ -375,6 +393,8 @@ void test_rewind() buffer[26] = '\0'; is_eq(strlen(buffer), 26); + // remove temp file + is_eq(remove("/tmp/myfile.txt"),0) } void test_feof() @@ -391,7 +411,7 @@ void test_feof() if (feof(pFile)) { pass("%s", "End-of-File reached."); - is_eq(n, 7971); + is_eq(n, 8546); } else { @@ -403,7 +423,7 @@ void test_feof() int main() { - plan(34); + plan(44); START_TEST(putchar) START_TEST(puts) diff --git a/transpiler/transpiler.go b/transpiler/transpiler.go index 77c0848a0..452463dbb 100644 --- a/transpiler/transpiler.go +++ b/transpiler/transpiler.go @@ -313,6 +313,12 @@ func transpileToStmt(node ast.Node, p *program.Program) ( } func transpileToNode(node ast.Node, p *program.Program) error { + // If that `ast` element from system headers, then + // not include in source + if node.Position().Line != 0 && node.Position().Line < p.UserPosition { + return nil + } + switch n := node.(type) { case *ast.TranslationUnitDecl: for _, c := range n.Children() { diff --git a/transpiler/variables.go b/transpiler/variables.go index e1863ba7e..f2ea6f9e6 100644 --- a/transpiler/variables.go +++ b/transpiler/variables.go @@ -36,6 +36,23 @@ func transpileDeclRefExpr(n *ast.DeclRefExpr, p *program.Program) ( // FIXME: This is for linux to make sure the globals have the right type. if n.Name == "stdout" || n.Name == "stdin" || n.Name == "stderr" { theType = "FILE *" + return &goast.Ident{Name: fmt.Sprintf("noarch.%s", util.Ucfirst(n.Name))}, theType, nil + } + // Added for darwin + if n.Name == "__stdoutp" || n.Name == "__stdinp" || n.Name == "__stderrp" { + theType = "FILE *" + return &goast.Ident{Name: fmt.Sprintf("noarch.%s", util.Ucfirst(n.Name[2:len(n.Name)-1]))}, theType, nil + } + + // For future : we don't check - that value from 'ctype.h' or not ? + // That is for constants from `ctype.h` + ctypeConstants := []string{"_ISupper", "_ISlower", "_ISalpha", "_ISdigit", "_ISxdigit", + "_ISspace", "_ISprint", "_ISgraph", "_ISblank", "_IScntrl", + "_ISpunct", "_ISalnum"} + for _, c := range ctypeConstants { + if n.Name == c { + return &goast.Ident{Name: fmt.Sprintf("noarch.%s", n.Name[1:])}, "uint16", nil + } } return util.NewIdent(n.Name), theType, nil @@ -249,7 +266,7 @@ func transpileMemberExpr(n *ast.MemberExpr, p *program.Program) ( structType = p.GetStruct("struct " + lhsType) } rhs := n.Name - rhsType := "void *" + rhsType := n.Type if structType == nil { // This case should not happen in the future. Any structs should be // either parsed correctly from the source or be manually setup when the diff --git a/types/cast.go b/types/cast.go index e8c6a294d..ebed00643 100644 --- a/types/cast.go +++ b/types/cast.go @@ -61,6 +61,13 @@ func GetArrayTypeAndSize(s string) (string, int) { // FILE where those function probably exist (or should exist) in the noarch // package. func CastExpr(p *program.Program, expr goast.Expr, fromType, toType string) (goast.Expr, error) { + // Replace for specific case of fromType for darwin: + // Fo : union (anonymous union at sqlite3.c:619241696:3) + if strings.Contains(fromType, "anonymous union") { + // I don't understood - How to change correctly + // Try change to : `union` , but it is FAIL with that + fromType = "" + } // convert enum to int and recursive if strings.Contains(fromType, "enum") && !strings.Contains(toType, "enum") { diff --git a/types/resolve.go b/types/resolve.go index e0819dad7..5d346da1e 100644 --- a/types/resolve.go +++ b/types/resolve.go @@ -80,6 +80,9 @@ var simpleResolveTypes = map[string]string{ "__sFILEX": "interface{}", "__va_list_tag": "interface{}", "FILE": "github.com/elliotchance/c2go/noarch.File", + + // from + "size_t": "uint64", } // ResolveType determines the Go type from a C type.