diff --git a/chore/clangast/clangast.go b/chore/clangast/clangast.go new file mode 100644 index 00000000..38c9e5f5 --- /dev/null +++ b/chore/clangast/clangast.go @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "encoding/json" + "flag" + "fmt" + "os" + + "github.com/goplus/llgo/x/clang/parser" +) + +var ( + dump = flag.Bool("dump", false, "dump AST") +) + +func usage() { + fmt.Fprintf(os.Stderr, "Usage: clangast [-dump] source.i\n") + flag.PrintDefaults() +} + +func main() { + flag.Usage = usage + flag.Parse() + if flag.NArg() < 1 { + usage() + return + } + var file = flag.Arg(0) + var err error + if *dump { + doc, _, e := parser.DumpAST(file, nil) + if e == nil { + os.Stdout.Write(doc) + return + } + err = e + } else { + doc, _, e := parser.ParseFile(file, 0) + if e == nil { + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + enc.Encode(doc) + return + } + err = e + } + fmt.Fprintln(os.Stderr, err) + os.Exit(1) +} diff --git a/chore/clangpp/clangpp.go b/chore/clangpp/clangpp.go new file mode 100644 index 00000000..2213e143 --- /dev/null +++ b/chore/clangpp/clangpp.go @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "fmt" + "os" + + "github.com/goplus/llgo/x/clang/preprocessor" +) + +func usage() { + fmt.Fprintf(os.Stderr, "Usage: clangpp source.c\n") +} + +func main() { + if len(os.Args) < 2 { + usage() + return + } + infile := os.Args[1] + outfile := infile + ".i" + if err := preprocessor.Do(infile, outfile, nil); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/go.mod b/go.mod index 08c19e4a..4a721e69 100644 --- a/go.mod +++ b/go.mod @@ -7,11 +7,14 @@ require ( github.com/goplus/gogen v1.15.2 github.com/goplus/llvm v0.7.5 github.com/goplus/mod v0.13.10 + github.com/json-iterator/go v1.1.12 github.com/qiniu/x v1.13.10 golang.org/x/tools v0.20.0 ) require ( + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/sync v0.7.0 // indirect ) diff --git a/go.sum b/go.sum index 1985c5c2..043864ad 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,25 @@ github.com/aykevl/go-wasm v0.0.1 h1:lPxy8l48P39W7I0tLrtCrLfZBOUq9IWZ7odGdyJP2AM= github.com/aykevl/go-wasm v0.0.1/go.mod h1:b4nggwg3lEkNKOU4wzhtLKz2q2sLxSHFnc98aGt6z/Y= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/goplus/gogen v1.15.2 h1:Q6XaSx/Zi5tWnjfAziYsQI6Jv6MgODRpFtOYqNkiiqM= github.com/goplus/gogen v1.15.2/go.mod h1:92qEzVgv7y8JEFICWG9GvYI5IzfEkxYdsA1DbmnTkqk= github.com/goplus/llvm v0.7.5 h1:ges8WcUdu4FBi0mkZUs27p/4qDQlj28N1UpMg3VQUoE= github.com/goplus/llvm v0.7.5/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4= github.com/goplus/mod v0.13.10 h1:5Om6KOvo31daN7N30kWU1vC5zhsJPM+uPbcEN/FnlzE= github.com/goplus/mod v0.13.10/go.mod h1:HDuPZgpWiaTp3PUolFgsiX+Q77cbUWB/mikVHfYND3c= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/qiniu/x v1.13.10 h1:J4Z3XugYzAq85SlyAfqlKVrbf05glMbAOh+QncsDQpE= github.com/qiniu/x v1.13.10/go.mod h1:INZ2TSWSJVWO/RuELQROERcslBwVgFG7MkTfEdaQz9E= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= diff --git a/x/clang/ast/ast.go b/x/clang/ast/ast.go new file mode 100644 index 00000000..dfef4cc7 --- /dev/null +++ b/x/clang/ast/ast.go @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ast + +// ----------------------------------------------------------------------------- + +type IncludedFrom struct { + File string `json:"file"` +} + +type Loc struct { + Offset int64 `json:"offset,omitempty"` // 432 + File string `json:"file,omitempty"` // "sqlite3.i" + Line int `json:"line,omitempty"` + PresumedFile string `json:"presumedFile,omitempty"` + PresumedLine int `json:"presumedLine,omitempty"` + Col int `json:"col,omitempty"` + TokLen int `json:"tokLen,omitempty"` + IncludedFrom *IncludedFrom `json:"includedFrom,omitempty"` // "sqlite3.c" +} + +type Pos struct { + Offset int64 `json:"offset,omitempty"` + Col int `json:"col,omitempty"` + TokLen int `json:"tokLen,omitempty"` + IncludedFrom *IncludedFrom `json:"includedFrom,omitempty"` // "sqlite3.c" + SpellingLoc *Loc `json:"spellingLoc,omitempty"` + ExpansionLoc *Loc `json:"expansionLoc,omitempty"` +} + +type Range struct { + Begin Pos `json:"begin"` + End Pos `json:"end"` +} + +// ----------------------------------------------------------------------------- + +type ID string + +type Kind string + +const ( + TranslationUnitDecl Kind = "TranslationUnitDecl" + TypedefType Kind = "TypedefType" + TypedefDecl Kind = "TypedefDecl" + ElaboratedType Kind = "ElaboratedType" + BuiltinType Kind = "BuiltinType" + ConstantArrayType Kind = "ConstantArrayType" + IncompleteArrayType Kind = "IncompleteArrayType" + PointerType Kind = "PointerType" + RecordType Kind = "RecordType" + RecordDecl Kind = "RecordDecl" + FieldDecl Kind = "FieldDecl" + IndirectFieldDecl Kind = "IndirectFieldDecl" + VarDecl Kind = "VarDecl" + EmptyDecl Kind = "EmptyDecl" + EnumDecl Kind = "EnumDecl" + EnumConstantDecl Kind = "EnumConstantDecl" + AlwaysInlineAttr Kind = "AlwaysInlineAttr" + AsmLabelAttr Kind = "AsmLabelAttr" + AvailabilityAttr Kind = "AvailabilityAttr" + DeprecatedAttr Kind = "DeprecatedAttr" + BuiltinAttr Kind = "BuiltinAttr" + FormatAttr Kind = "FormatAttr" + FormatArgAttr Kind = "FormatArgAttr" + ColdAttr Kind = "ColdAttr" + ConstAttr Kind = "ConstAttr" + PureAttr Kind = "PureAttr" + PackedAttr Kind = "PackedAttr" + GNUInlineAttr Kind = "GNUInlineAttr" + StrictFPAttr Kind = "StrictFPAttr" + ReturnsTwiceAttr Kind = "ReturnsTwiceAttr" + RestrictAttr Kind = "RestrictAttr" + NoThrowAttr Kind = "NoThrowAttr" + NoInlineAttr Kind = "NoInlineAttr" + NoSanitizeAttr Kind = "NoSanitizeAttr" + NonNullAttr Kind = "NonNullAttr" + MayAliasAttr Kind = "MayAliasAttr" + MSAllocatorAttr Kind = "MSAllocatorAttr" + MaxFieldAlignmentAttr Kind = "MaxFieldAlignmentAttr" + WarnUnusedResultAttr Kind = "WarnUnusedResultAttr" + AllocSizeAttr Kind = "AllocSizeAttr" + AlignedAttr Kind = "AlignedAttr" + VisibilityAttr Kind = "VisibilityAttr" + C11NoReturnAttr Kind = "C11NoReturnAttr" + FunctionProtoType Kind = "FunctionProtoType" + FunctionDecl Kind = "FunctionDecl" + ParmVarDecl Kind = "ParmVarDecl" + ParenType Kind = "ParenType" + DeclStmt Kind = "DeclStmt" + CompoundStmt Kind = "CompoundStmt" + NullStmt Kind = "NullStmt" + ForStmt Kind = "ForStmt" + WhileStmt Kind = "WhileStmt" + DoStmt Kind = "DoStmt" + GotoStmt Kind = "GotoStmt" + BreakStmt Kind = "BreakStmt" + ContinueStmt Kind = "ContinueStmt" + LabelStmt Kind = "LabelStmt" + IfStmt Kind = "IfStmt" + SwitchStmt Kind = "SwitchStmt" + CaseStmt Kind = "CaseStmt" + DefaultStmt Kind = "DefaultStmt" + ReturnStmt Kind = "ReturnStmt" + GCCAsmStmt Kind = "GCCAsmStmt" + ParenExpr Kind = "ParenExpr" + CallExpr Kind = "CallExpr" + ConstantExpr Kind = "ConstantExpr" + InitListExpr Kind = "InitListExpr" + CStyleCastExpr Kind = "CStyleCastExpr" + DeclRefExpr Kind = "DeclRefExpr" + MemberExpr Kind = "MemberExpr" + ImplicitCastExpr Kind = "ImplicitCastExpr" + ImplicitValueInitExpr Kind = "ImplicitValueInitExpr" + UnaryExprOrTypeTraitExpr Kind = "UnaryExprOrTypeTraitExpr" + OffsetOfExpr Kind = "OffsetOfExpr" + ArraySubscriptExpr Kind = "ArraySubscriptExpr" + AtomicExpr Kind = "AtomicExpr" + VAArgExpr Kind = "VAArgExpr" + CompoundAssignOperator Kind = "CompoundAssignOperator" + BinaryOperator Kind = "BinaryOperator" + UnaryOperator Kind = "UnaryOperator" + ConditionalOperator Kind = "ConditionalOperator" + CompoundLiteralExpr Kind = "CompoundLiteralExpr" + PredefinedExpr Kind = "PredefinedExpr" + CharacterLiteral Kind = "CharacterLiteral" + IntegerLiteral Kind = "IntegerLiteral" + StringLiteral Kind = "StringLiteral" + FloatingLiteral Kind = "FloatingLiteral" + ImaginaryLiteral Kind = "ImaginaryLiteral" + AllocAlignAttr Kind = "AllocAlignAttr" + DisableTailCallsAttr Kind = "DisableTailCallsAttr" + StaticAssertDecl Kind = "StaticAssertDecl" +) + +type ValueCategory string + +const ( + RValue ValueCategory = "rvalue" + PRValue ValueCategory = "prvalue" + LValue ValueCategory = "lvalue" +) + +type CC string + +const ( + CDecl CC = "cdecl" +) + +type StorageClass string + +const ( + Static StorageClass = "static" + Extern StorageClass = "extern" +) + +type CastKind string + +const ( + LValueToRValue CastKind = "LValueToRValue" + BitCast CastKind = "BitCast" + FloatingToIntegral CastKind = "FloatingToIntegral" + FloatingComplexCast CastKind = "FloatingComplexCast" + FloatingRealToComplex CastKind = "FloatingRealToComplex" + IntegralRealToComplex CastKind = "IntegralRealToComplex" + FloatingCast CastKind = "FloatingCast" + IntegralCast CastKind = "IntegralCast" + IntegralToPointer CastKind = "IntegralToPointer" + IntegralToFloating CastKind = "IntegralToFloating" + IntegralToBoolean CastKind = "IntegralToBoolean" + FloatingToBoolean CastKind = "FloatingToBoolean" + IntegralComplexToBoolean CastKind = "IntegralComplexToBoolean" + FloatingComplexToBoolean CastKind = "FloatingComplexToBoolean" + PointerToBoolean CastKind = "PointerToBoolean" + PointerToIntegral CastKind = "PointerToIntegral" + FunctionToPointerDecay CastKind = "FunctionToPointerDecay" + ArrayToPointerDecay CastKind = "ArrayToPointerDecay" + BuiltinFnToFnPtr CastKind = "BuiltinFnToFnPtr" + ToVoid CastKind = "ToVoid" + NullToPointer CastKind = "NullToPointer" + NoOp CastKind = "NoOp" +) + +type ( + // OpCode can be: + // + - * / || >= -- ++ etc + OpCode string +) + +type Type struct { + // QualType can be: + // unsigned int + // struct ConstantString + // volatile uint32_t + // int (*)(void *, int, char **, char **) + // int (*)(const char *, ...) + // int (*)(void) + // const char *restrict + // const char [7] + // char * + // void + // ... + QualType string `json:"qualType"` + DesugaredQualType string `json:"desugaredQualType,omitempty"` + TypeAliasDeclID ID `json:"typeAliasDeclId,omitempty"` +} + +type Node struct { + ID ID `json:"id,omitempty"` + Kind Kind `json:"kind,omitempty"` + Loc *Loc `json:"loc,omitempty"` + Range *Range `json:"range,omitempty"` + ReferencedMemberDecl ID `json:"referencedMemberDecl,omitempty"` + PreviousDecl ID `json:"previousDecl,omitempty"` + ParentDeclContextID ID `json:"parentDeclContextId,omitempty"` + IsImplicit bool `json:"isImplicit,omitempty"` // is this type implicit defined + IsReferenced bool `json:"isReferenced,omitempty"` // is this type refered or not + IsUsed bool `json:"isUsed,omitempty"` // is this variable used or not + IsArrow bool `json:"isArrow,omitempty"` // is ptr->member not obj.member + IsPostfix bool `json:"isPostfix,omitempty"` + IsPartOfExplicitCast bool `json:"isPartOfExplicitCast,omitempty"` + IsBitfield bool `json:"isBitfield,omitempty"` + Inline bool `json:"inline,omitempty"` + StorageClass StorageClass `json:"storageClass,omitempty"` + TagUsed string `json:"tagUsed,omitempty"` // struct | union + HasElse bool `json:"hasElse,omitempty"` + CompleteDefinition bool `json:"completeDefinition,omitempty"` + Complicated bool `json:"-"` // complicated statement + Variadic bool `json:"variadic,omitempty"` + Name string `json:"name,omitempty"` + MangledName string `json:"mangledName,omitempty"` + Type *Type `json:"type,omitempty"` + CC CC `json:"cc,omitempty"` + Field *Node `json:"field,omitempty"` + Decl *Node `json:"decl,omitempty"` + OwnedTagDecl *Node `json:"ownedTagDecl,omitempty"` + ReferencedDecl *Node `json:"referencedDecl,omitempty"` + OpCode OpCode `json:"opcode,omitempty"` + Init string `json:"init,omitempty"` + ValueCategory ValueCategory `json:"valueCategory,omitempty"` + Value interface{} `json:"value,omitempty"` + CastKind CastKind `json:"castKind,omitempty"` + Size int `json:"size,omitempty"` // array size + Inner []*Node `json:"inner,omitempty"` + ArrayFiller []*Node `json:"array_filler,omitempty"` +} + +// ----------------------------------------------------------------------------- diff --git a/x/clang/clang.go b/x/clang/clang.go index a87739d1..d55b7f4a 100644 --- a/x/clang/clang.go +++ b/x/clang/clang.go @@ -24,7 +24,7 @@ import ( // ----------------------------------------------------------------------------- -// Cmd represents a nm command. +// Cmd represents a clang command. type Cmd struct { app string @@ -32,7 +32,7 @@ type Cmd struct { Stderr io.Writer } -// New creates a new nm command. +// New creates a new clang command. func New(app string) *Cmd { if app == "" { app = "clang" diff --git a/x/clang/parser/pages.go b/x/clang/parser/pages.go new file mode 100644 index 00000000..44844d22 --- /dev/null +++ b/x/clang/parser/pages.go @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package parser + +// ----------------------------------------------------------------------------- + +const pageSize = 1024 * 1024 + +type PagedWriter struct { + pages []*[pageSize]byte + last *[pageSize]byte + off int +} + +func NewPagedWriter() *PagedWriter { + return &PagedWriter{last: new([pageSize]byte)} +} + +func (p *PagedWriter) Write(buf []byte) (written int, err error) { + for { + n := copy(p.last[p.off:], buf[written:]) + written += n + if written >= len(buf) { + p.off += n + return + } + p.pages = append(p.pages, p.last) + p.last, p.off = new([pageSize]byte), 0 + } +} + +func (p *PagedWriter) Len() int { + return len(p.pages)*pageSize + p.off +} + +func (p *PagedWriter) Bytes() []byte { + out, n := make([]byte, p.Len()), 0 + for _, page := range p.pages { + n += copy(out[n:], page[:]) + } + copy(out[n:], p.last[:p.off]) + return out +} + +/* +func (p *PagedWriter) ToReader() *PagedReader { + return &PagedReader{src: p, curr: p.getPage(0)} +} + +func (p *PagedWriter) getPage(ipage int) []byte { + if ipage == len(p.pages) { // last page + return p.last[:p.off] + } + return p.pages[ipage][:] +} + +// ----------------------------------------------------------------------------- + +type PagedReader struct { + src *PagedWriter + curr []byte + off int + ipage int +} + +func (p *PagedReader) WriteTo(w io.Writer) (written int64, err error) { + n, err := w.Write(p.curr[p.off:]) + written = int64(n) + if err != nil { + return + } + src, ipage := p.src, p.ipage + for { + if ipage == len(src.pages) { // last page + p.ipage, p.off = ipage, len(p.curr) + return + } + ipage++ + page := src.getPage(ipage) + n, err = w.Write(page) + written += int64(n) + if err != nil { + p.ipage, p.curr, p.off = ipage, page, n + return + } + } +} + +func (p *PagedReader) Read(buf []byte) (nread int, err error) { + for { + n := copy(buf[nread:], p.curr[p.off:]) + nread += n + p.off += n + if nread >= len(buf) { + return + } + src := p.src + if p.ipage == len(src.pages) { // last page + err = io.EOF + return + } + p.ipage++ + p.curr, p.off = src.getPage(p.ipage), 0 + } +} +*/ + +// ----------------------------------------------------------------------------- diff --git a/x/clang/parser/parse.go b/x/clang/parser/parse.go new file mode 100644 index 00000000..1969fed2 --- /dev/null +++ b/x/clang/parser/parse.go @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package parser + +import ( + "bytes" + "os" + "os/exec" + "strings" + + "github.com/goplus/llgo/x/clang/ast" + jsoniter "github.com/json-iterator/go" +) + +type Mode uint + +// ----------------------------------------------------------------------------- + +type ParseError struct { + Err error + Stderr []byte +} + +func (p *ParseError) Error() string { + if len(p.Stderr) > 0 { + return string(p.Stderr) + } + return p.Err.Error() +} + +// ----------------------------------------------------------------------------- + +type Config struct { + Json *[]byte + Flags []string + Stderr bool +} + +func DumpAST(filename string, conf *Config) (result []byte, warning []byte, err error) { + if conf == nil { + conf = new(Config) + } + skiperr := strings.HasSuffix(filename, "vfprintf.c.i") + stdout := NewPagedWriter() + stderr := new(bytes.Buffer) + args := []string{"-Xclang", "-ast-dump=json", "-fsyntax-only", filename} + if len(conf.Flags) != 0 { + args = append(conf.Flags, args...) + } + cmd := exec.Command("clang", args...) + cmd.Stdin = os.Stdin + cmd.Stdout = stdout + if conf.Stderr && !skiperr { + cmd.Stderr = os.Stderr + } else { + cmd.Stderr = stderr + } + err = cmd.Run() + errmsg := stderr.Bytes() + if err != nil && !skiperr { + return nil, nil, &ParseError{Err: err, Stderr: errmsg} + } + return stdout.Bytes(), errmsg, nil +} + +// ----------------------------------------------------------------------------- + +var json = jsoniter.ConfigCompatibleWithStandardLibrary + +func ParseFileEx(filename string, mode Mode, conf *Config) (file *ast.Node, warning []byte, err error) { + out, warning, err := DumpAST(filename, conf) + if err != nil { + return + } + if conf != nil && conf.Json != nil { + *conf.Json = out + } + file = new(ast.Node) + err = json.Unmarshal(out, file) + if err != nil { + err = &ParseError{Err: err} + } + return +} + +func ParseFile(filename string, mode Mode) (file *ast.Node, warning []byte, err error) { + return ParseFileEx(filename, mode, nil) +} + +// ----------------------------------------------------------------------------- diff --git a/x/clang/pathutil/pathutil.go b/x/clang/pathutil/pathutil.go new file mode 100644 index 00000000..6b83df07 --- /dev/null +++ b/x/clang/pathutil/pathutil.go @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package pathutil + +import ( + "path/filepath" +) + +func Canonical(baseDir string, uri string) string { + if filepath.IsAbs(uri) { + return filepath.Clean(uri) + } + return filepath.Join(baseDir, uri) +} diff --git a/x/clang/preprocessor/preprocessor.go b/x/clang/preprocessor/preprocessor.go new file mode 100644 index 00000000..748b9733 --- /dev/null +++ b/x/clang/preprocessor/preprocessor.go @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package preprocessor + +import ( + "log" + "os" + "os/exec" + "path/filepath" + + "github.com/goplus/llgo/x/clang/pathutil" +) + +const ( + DbgFlagExecCmd = 1 << iota + DbgFlagAll = DbgFlagExecCmd +) + +var ( + debugExecCmd bool +) + +func SetDebug(flags int) { + debugExecCmd = (flags & DbgFlagExecCmd) != 0 +} + +// ----------------------------------------------------------------------------- + +type Config struct { + Compiler string // default: clang + PPFlag string // default: -E + BaseDir string // base of include searching directory, should be absolute path + IncludeDirs []string + Defines []string + Flags []string +} + +func Do(infile, outfile string, conf *Config) (err error) { + if infile, err = filepath.Abs(infile); err != nil { + return + } + if outfile, err = filepath.Abs(outfile); err != nil { + return + } + if conf == nil { + conf = new(Config) + } + base := conf.BaseDir + if base == "" { + if base, err = os.Getwd(); err != nil { + return + } + } + compiler := conf.Compiler + if compiler == "" { + compiler = "clang" + } + ppflag := conf.PPFlag + if ppflag == "" { + ppflag = "-E" + } + n := 4 + len(conf.Flags) + len(conf.IncludeDirs) + len(conf.Defines) + args := make([]string, 3, n) + args[0] = ppflag + args[1], args[2] = "-o", outfile + args = append(args, conf.Flags...) + for _, def := range conf.Defines { + args = append(args, "-D"+def) + } + for _, inc := range conf.IncludeDirs { + args = append(args, "-I"+pathutil.Canonical(base, inc)) + } + args = append(args, infile) + if debugExecCmd { + log.Println("==> runCmd:", compiler, args) + } + cmd := exec.Command(compiler, args...) + cmd.Dir = filepath.Dir(infile) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +// ----------------------------------------------------------------------------- diff --git a/x/clang/types/parser/_parser_test.go b/x/clang/types/parser/_parser_test.go new file mode 100644 index 00000000..44ca0de3 --- /dev/null +++ b/x/clang/types/parser/_parser_test.go @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package parser + +import ( + "go/token" + "go/types" + "testing" + + ctypes "github.com/goplus/llgo/x/clang/types" +) + +// ----------------------------------------------------------------------------- + +var ( + pkg = types.NewPackage("", "foo") + scope = pkg.Scope() +) + +var ( + nameInt128 = types.NewTypeName(token.NoPos, pkg, "__int128", nil) + nameUint128 = types.NewTypeName(token.NoPos, pkg, "__uint128", nil) + tyInt128 = types.NewNamed(nameInt128, types.Typ[types.String], nil) + tyUint128 = types.NewNamed(nameUint128, types.Typ[types.Rune], nil) +) + +func init() { + aliasType(scope, pkg, "char", types.Typ[types.Int8]) + aliasType(scope, pkg, "void", ctypes.Void) + aliasType(scope, pkg, "float", types.Typ[types.Float32]) + aliasType(scope, pkg, "double", types.Typ[types.Float64]) + aliasType(scope, pkg, "uint", types.Typ[types.Uint32]) + aliasType(scope, pkg, ctypes.MangledName("struct", "ConstantString"), tyConstantString) + aliasType(scope, pkg, ctypes.MangledName("union", "arg"), tyArg) + aliasType(scope, pkg, "va_list", ctypes.Valist) + + scope.Insert(nameInt128) +} + +func aliasType(scope *types.Scope, pkg *types.Package, name string, typ types.Type) { + o := types.NewTypeName(token.NoPos, pkg, name, typ) + scope.Insert(o) +} + +var ( + tnameConstantString = types.NewTypeName(token.NoPos, pkg, "ConstantString", nil) + tnameArg = types.NewTypeName(token.NoPos, pkg, "UnionArg", nil) +) + +var ( + tyChar = types.Typ[types.Int8] + tyUchar = types.Typ[types.Uint8] + tyInt16 = types.Typ[types.Int16] + tyUint16 = types.Typ[types.Uint16] + tyInt32 = types.Typ[types.Int32] + tyUint32 = types.Typ[types.Uint32] + tyInt64 = types.Typ[types.Int64] + tyUint64 = types.Typ[types.Uint64] + tyInt = ctypes.Int + tyInt100 = types.NewArray(tyInt, 100) + tyInt3 = types.NewArray(tyInt, 3) + tyInt3_100 = types.NewArray(tyInt3, 100) + tyPInt3_100 = types.NewPointer(tyInt3_100) + tyPInt100 = types.NewPointer(tyInt100) + tyUint = ctypes.Uint + tyString = types.Typ[types.String] + tyCharPtr = types.NewPointer(tyChar) + tyCharPtrPtr = types.NewPointer(tyCharPtr) + tyConstantString = types.NewNamed(tnameConstantString, tyString, nil) + tyArg = types.NewNamed(tnameArg, tyString, nil) + tyEmptyInterface = types.NewInterfaceType(nil, nil) +) + +var ( + paramInt = types.NewParam(token.NoPos, pkg, "", tyInt) + paramVoidPtr = types.NewParam(token.NoPos, pkg, "", ctypes.UnsafePointer) + paramCharPtrPtr = types.NewParam(token.NoPos, pkg, "", tyCharPtrPtr) + paramAnySlice = types.NewParam(token.NoPos, pkg, "", types.NewSlice(tyEmptyInterface)) + paramPAnySlice = types.NewParam(token.NoPos, pkg, "", types.NewPointer(types.NewSlice(tyEmptyInterface))) +) + +var ( + typesInt = types.NewTuple(paramInt) + typesIntVA = types.NewTuple(paramInt, paramAnySlice) + typesIntPVA = types.NewTuple(paramInt, paramPAnySlice) + typesVoidPtr = types.NewTuple(paramVoidPtr) + typesPICC = types.NewTuple(paramVoidPtr, paramInt, paramCharPtrPtr, paramCharPtrPtr) +) + +func newFn(in, out *types.Tuple) types.Type { + return types.NewSignature(nil, in, out, false) +} + +func newFnv(in, out *types.Tuple) types.Type { + return types.NewSignature(nil, in, out, true) +} + +func newFnProto(in, out *types.Tuple, variadic bool) types.Type { + return ctypes.NewFunc(in, out, variadic) +} + +var ( + tyFnHandle = newFn(typesInt, nil) + paramFnHandle = types.NewParam(token.NoPos, pkg, "", tyFnHandle) + typesIF = types.NewTuple(paramInt, paramFnHandle) + typesF = types.NewTuple(paramFnHandle) +) + +// ----------------------------------------------------------------------------- + +type testCase struct { + qualType string + flags int + anonym types.Type + typ types.Type + err string +} + +var cases = []testCase{ + {qualType: "int", typ: tyInt}, + {qualType: "unsigned int", typ: tyUint}, + {qualType: "struct ConstantString", typ: tyConstantString}, + {qualType: "union arg", typ: tyArg}, + {qualType: "volatile signed int", typ: tyInt}, + {qualType: "__int128", typ: tyInt128}, + {qualType: "signed", typ: tyInt}, + {qualType: "signed short", typ: tyInt16}, + {qualType: "signed long", typ: ctypes.Long}, + {qualType: "unsigned", typ: tyUint}, + {qualType: "uint", typ: tyUint32}, + {qualType: "unsigned char", typ: tyUchar}, + {qualType: "unsigned __int128", typ: tyUint128}, + {qualType: "unsigned long", typ: ctypes.Ulong}, + {qualType: "unsigned long long", typ: tyUint64}, + {qualType: "long double", typ: ctypes.LongDouble}, + {qualType: "_Complex float", typ: types.Typ[types.Complex64]}, + {qualType: "_Complex double", typ: types.Typ[types.Complex128]}, + {qualType: "_Complex long double", typ: types.Typ[types.Complex128]}, + {qualType: "int (*)(void)", typ: newFn(nil, typesInt)}, + {qualType: "int (void)", typ: newFnProto(nil, typesInt, false)}, + {qualType: "void (void) __attribute__((noreturn))", typ: newFnProto(nil, nil, false)}, + {qualType: "void (*)(void *)", typ: newFn(typesVoidPtr, nil)}, + {qualType: "void (^ _Nonnull)(void)", typ: newFn(nil, nil)}, + {qualType: "void (int, ...)", typ: newFnProto(typesIntVA, nil, true)}, + {qualType: "void (int, va_list*)", typ: newFn(typesIntPVA, nil)}, + {qualType: "va_list *", typ: types.NewPointer(types.NewSlice(tyEmptyInterface))}, + {qualType: "int (*)()", typ: newFn(nil, typesInt)}, + {qualType: "int (*)(int, ...)", typ: newFnv(typesIntVA, typesInt)}, + {qualType: "int (*)(int, struct __va_list_tag*)", typ: newFn(typesIntVA, typesInt)}, + {qualType: "int (*volatile)(int, struct __va_list_tag* restrict)", typ: newFn(typesIntVA, typesInt)}, + {qualType: "int (const char *, const char *, unsigned int)", flags: FlagGetRetType, typ: tyInt}, + {qualType: "const char *restrict", typ: tyCharPtr}, + {qualType: "const char [7]", typ: types.NewArray(tyChar, 7)}, + {qualType: "const char [7]", flags: FlagIsParam, typ: tyCharPtr}, + {qualType: "char []", flags: FlagIsStructField, typ: types.NewArray(tyChar, 0)}, + {qualType: "char []", flags: FlagIsExtern, typ: types.NewArray(tyChar, -1)}, + {qualType: "char []", flags: 0, err: emsgDefArrWithoutLen}, + {qualType: "char []", flags: FlagIsTypedef, typ: types.NewArray(tyChar, -1)}, + {qualType: "char []", flags: FlagIsParam, typ: tyCharPtr}, + {qualType: "int [100][3]", typ: tyInt3_100}, + {qualType: "int (*)[100][3]", typ: tyPInt3_100}, + {qualType: "int (*)[100]", typ: tyPInt100}, + {qualType: "int (*const [2])(void *)", typ: types.NewArray(newFn(typesVoidPtr, typesInt), 2)}, + {qualType: "char *", typ: tyCharPtr}, + {qualType: "void", typ: ctypes.Void}, + {qualType: "void *", typ: ctypes.UnsafePointer}, + {qualType: "int (*_Nullable)(void *, int, char **, char **)", typ: newFn(typesPICC, typesInt)}, + {qualType: "void (*(*)(int, void (*)(int)))(int)", typ: newFn(typesIF, typesF)}, + {qualType: "void (*(int, void (*)(int)))(int)", typ: newFnProto(typesIF, typesF, false)}, + {qualType: "void (*(int, void (*)(int)))(int)", flags: FlagGetRetType, typ: tyFnHandle}, + {qualType: "int (*)(void *, int, const char *, void (**)(void *, int, void **), void **)"}, + {qualType: "struct (anonymous) [2]", anonym: tyInt, typ: types.NewArray(tyInt, 2)}, + {qualType: "enum a", typ: ctypes.Int}, +} + +type baseEnv struct { + pkg *types.Package + tyInt128 types.Type + tyUint128 types.Type +} + +func (p *baseEnv) Pkg() *types.Package { + return p.pkg +} + +func (p *baseEnv) Int128() types.Type { + return p.tyInt128 +} + +func (p *baseEnv) Uint128() types.Type { + return p.tyUint128 +} + +func TestCases(t *testing.T) { + sel := "" + for _, c := range cases { + if sel != "" && c.qualType != sel { + continue + } + t.Run(c.qualType, func(t *testing.T) { + conf := &Config{ + Scope: scope, Flags: c.flags, Anonym: c.anonym, + ParseEnv: &baseEnv{pkg: pkg, tyInt128: tyInt128, tyUint128: tyUint128}, + } + typ, _, err := ParseType(c.qualType, conf) + if err != nil { + if errMsgOf(err) != c.err { + t.Fatal("ParseType:", err, ", expected:", c.err) + } + } else if c.typ != nil && !ctypes.Identical(typ, c.typ) { + t.Fatal("ParseType:", typ, ", expected:", c.typ) + } + }) + } +} + +func errMsgOf(err error) string { + if e, ok := err.(*ParseTypeError); ok { + return e.ErrMsg + } + return err.Error() +} + +// ----------------------------------------------------------------------------- + +func TestIsArrayWithoutLen(t *testing.T) { + _, _, err := ParseType("byte[]", &Config{Scope: types.Universe}) + if !IsArrayWithoutLen(err) { + t.Fatal("ParseType:", err) + } + _, _, err = ParseType("byte[]", &Config{Scope: types.Universe, Flags: FlagIsExtern}) + if IsArrayWithoutLen(err) { + t.Fatal("ParseType:", err) + } +} + +// ----------------------------------------------------------------------------- diff --git a/x/clang/types/parser/parser.go b/x/clang/types/parser/parser.go new file mode 100644 index 00000000..6a270517 --- /dev/null +++ b/x/clang/types/parser/parser.go @@ -0,0 +1,595 @@ +/* + * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package parser + +import ( + "errors" + "fmt" + "go/token" + "go/types" + "io" + "log" + "strconv" + + "github.com/goplus/gogen" + "github.com/goplus/llgo/x/clang/types/scanner" + + ctypes "github.com/goplus/llgo/x/clang/types" +) + +const ( + emsgDefArrWithoutLen = "define array without length" +) + +var ( + ErrInvalidType = errors.New("invalid type") +) + +type TypeNotFound struct { + Literal string + StructOrUnion bool +} + +func (p *TypeNotFound) Error() string { + return fmt.Sprintf("type %s not found", p.Literal) +} + +type ParseTypeError struct { + QualType string + ErrMsg string +} + +func (p *ParseTypeError) Error() string { + return p.ErrMsg // TODO +} + +func IsArrayWithoutLen(err error) bool { + if e, ok := err.(*ParseTypeError); ok { + return e.ErrMsg == emsgDefArrWithoutLen + } + return false +} + +// ----------------------------------------------------------------------------- + +const ( + FlagIsParam = 1 << iota + FlagIsStructField + FlagIsExtern + FlagIsTypedef + FlagGetRetType +) + +func getRetType(flags int) bool { + return (flags & FlagGetRetType) != 0 +} + +type ParseEnv interface { + Pkg() *types.Package + Int128() types.Type + Uint128() types.Type +} + +type Config struct { + ParseEnv + Scope *types.Scope + Anonym types.Type + Flags int +} + +const ( + KindFConst = 1 << iota + KindFVolatile + KindFAnonymous + KindFVariadic +) + +// qualType can be: +// - unsigned int +// - struct ConstantString +// - volatile uint32_t +// - int (*)(void *, int, char **, char **) +// - int (*)(const char *, ...) +// - int (*)(void) +// - void (*(int, void (*)(int)))(int) +// - const char *restrict +// - const char [7] +// - char * +// - void +// - ... +func ParseType(qualType string, conf *Config) (t types.Type, kind int, err error) { + p := newParser(qualType, conf) + if t, kind, err = p.parse(conf.Flags); err != nil { + return + } + if p.tok != token.EOF { + err = p.newError("unexpect token " + p.tok.String()) + } + return +} + +// ----------------------------------------------------------------------------- + +type parser struct { + s scanner.Scanner + scope *types.Scope + conf *Config + + tok token.Token + lit string + old struct { + tok token.Token + lit string + } +} + +const ( + invalidTok token.Token = -1 +) + +func newParser(qualType string, conf *Config) *parser { + p := &parser{scope: conf.Scope, conf: conf} + p.old.tok = invalidTok + p.s.Init(qualType) + return p +} + +func (p *parser) peek() token.Token { + if p.old.tok == invalidTok { + p.old.tok, p.old.lit = p.s.Scan() + } + return p.old.tok +} + +func (p *parser) next() { + if p.old.tok != invalidTok { // support unget + p.tok, p.lit = p.old.tok, p.old.lit + p.old.tok = invalidTok + return + } + p.tok, p.lit = p.s.Scan() +} + +func (p *parser) unget(tok token.Token, lit string) { + p.old.tok, p.old.lit = p.tok, p.lit + p.tok, p.lit = tok, lit +} + +func (p *parser) skipUntil(tok token.Token) bool { + for { + p.next() + switch p.tok { + case tok: + return true + case token.EOF: + return false + } + } +} + +func (p *parser) newErrorf(format string, args ...interface{}) *ParseTypeError { + return p.newError(fmt.Sprintf(format, args...)) +} + +func (p *parser) newError(errMsg string) *ParseTypeError { + return &ParseTypeError{QualType: p.s.Source(), ErrMsg: errMsg} +} + +func (p *parser) expect(tokExp token.Token) error { + p.next() + if p.tok != tokExp { + return p.newErrorf("expect %v, got %v", tokExp, p.tok) + } + return nil +} + +const ( + flagShort = 1 << iota + flagLong + flagLongLong + flagUnsigned + flagSigned + flagComplex + flagStructOrUnion +) + +func (p *parser) lookupType(tylit string, flags int) (t types.Type, err error) { + structOrUnion := (flags & flagStructOrUnion) != 0 + _, o := gogen.LookupParent(p.scope, tylit, token.NoPos) + if o == nil { + return nil, &TypeNotFound{Literal: tylit, StructOrUnion: structOrUnion} + } + t = o.Type() + if !structOrUnion && flags != 0 { + tt, ok := t.(*types.Basic) + if !ok { + tyInt128 := p.conf.Int128() + if t == tyInt128 { + switch flags { + case flagSigned: + return tyInt128, nil + case flagUnsigned: + return p.conf.Uint128(), nil + } + } + } else if (flags & flagComplex) != 0 { + switch tt.Kind() { + case types.Float32: + return types.Typ[types.Complex64], nil + case types.Float64: + return types.Typ[types.Complex128], nil + case types.Int: + return types.Typ[types.Complex128], nil + } + } else { + switch tt.Kind() { + case types.Int: + if t = intTypes[flags&^flagSigned]; t != nil { + return + } + case types.Int8: + switch flags { + case flagUnsigned: + return types.Typ[types.Uint8], nil + case flagSigned: + return types.Typ[types.Int8], nil + } + case types.Float64: + switch flags { + case flagLong: + return ctypes.LongDouble, nil + } + } + } + log.Panicln("lookupType: TODO - invalid type") + return nil, ErrInvalidType + } + if t == types.Typ[types.Int] { + return ctypes.Int, nil + } + return +} + +var intTypes = [...]types.Type{ + 0: ctypes.Int, + flagShort: types.Typ[types.Int16], + flagLong: ctypes.Long, + flagLong | flagLongLong: types.Typ[types.Int64], + flagUnsigned: ctypes.Uint, + flagShort | flagUnsigned: types.Typ[types.Uint16], + flagLong | flagUnsigned: ctypes.Ulong, + flagLong | flagLongLong | flagUnsigned: types.Typ[types.Uint64], + flagShort | flagLong | flagLongLong | flagUnsigned: nil, +} + +func (p *parser) parseArray(t types.Type, inFlags int) (types.Type, error) { + var n int64 + var err error + p.next() + switch p.tok { + case token.RBRACK: // ] + if (inFlags & FlagIsStructField) != 0 { + n = 0 + } else { + n = -1 + } + case token.INT: + if n, err = strconv.ParseInt(p.lit, 10, 64); err != nil { + return nil, p.newError(err.Error()) + } + if err = p.expect(token.RBRACK); err != nil { // ] + return nil, err + } + default: + return nil, p.newError("array length not an integer") + } + if n >= 0 || (inFlags&(FlagIsExtern|FlagIsTypedef|FlagIsParam)) != 0 { + t = types.NewArray(t, n) + } else { + return nil, p.newError(emsgDefArrWithoutLen) + } + return t, nil +} + +func (p *parser) parseArrays(t types.Type, inFlags int) (ret types.Type, err error) { + if t == nil { + return nil, p.newError("array to nil") + } + var tyArr types.Type + for { + if tyArr, err = p.parseArray(tyArr, inFlags); err != nil { + return + } + if p.peek() != token.LBRACK { + return newArraysEx(t, tyArr, inFlags), nil + } + p.next() + } +} + +func (p *parser) parseFunc(pkg *types.Package, t types.Type, inFlags int) (ret types.Type, err error) { + var results *types.Tuple + if ctypes.NotVoid(t) { + results = types.NewTuple(types.NewParam(token.NoPos, pkg, "", t)) + } + args, variadic, err := p.parseArgs(pkg) + if err != nil { + return + } + return ctypes.NewFunc(types.NewTuple(args...), results, variadic), nil +} + +func (p *parser) parseArgs(pkg *types.Package) (args []*types.Var, variadic bool, err error) { + for { + arg, kind, e := p.parse(FlagIsParam) + if e != nil { + return nil, false, e + } + if ctypes.NotVoid(arg) { + args = append(args, types.NewParam(token.NoPos, pkg, "", arg)) + } + if p.tok != token.COMMA { + variadic = (kind & KindFVariadic) != 0 + break + } + } + if p.tok != token.RPAREN { // ) + return nil, false, p.newError("expect )") + } + return +} + +func (p *parser) parseStars() (nstar int) { + for isPtr(p.peek()) { + p.next() + nstar++ + } + return +} + +func (p *parser) parse(inFlags int) (t types.Type, kind int, err error) { + flags := 0 + for { + p.next() + retry: + switch p.tok { + case token.IDENT: + ident: + switch lit := p.lit; lit { + case "unsigned": + flags |= flagUnsigned + case "short": + flags |= flagShort + case "long": + if (flags & flagLong) != 0 { + flags |= flagLongLong + } else { + flags |= flagLong + } + case "signed": + flags |= flagSigned + case "const": + kind |= KindFConst + case "volatile": + kind |= KindFVolatile + case "_Complex": + flags |= flagComplex + case "restrict", "_Nullable", "_Nonnull": + case "enum": + if err = p.expect(token.IDENT); err != nil { + return + } + if t != nil { + return nil, 0, p.newError("illegal syntax: multiple types?") + } + t = ctypes.Int + continue + case "struct", "union": + p.next() + switch p.tok { + case token.IDENT: + case token.LPAREN: + if t == nil && p.conf.Anonym != nil { + p.skipUntil(token.RPAREN) + t = p.conf.Anonym + kind |= KindFAnonymous + continue + } + fallthrough + default: + log.Panicln("c.types.ParseType: struct/union - TODO:", p.lit) + } + lit = ctypes.MangledName(lit, p.lit) + flags |= flagStructOrUnion + fallthrough + default: + if t != nil { + return nil, 0, p.newError("illegal syntax: multiple types?") + } + if t, err = p.lookupType(lit, flags); err != nil { + return + } + flags = 0 + } + if flags != 0 { + p.next() + if p.tok == token.IDENT { + goto ident + } + if t != nil { + return nil, 0, p.newError("illegal syntax: multiple types?") + } + if t, err = p.lookupType("int", flags); err != nil { + return + } + flags = 0 + goto retry + } + case token.MUL: // * + if t == nil { + return nil, 0, p.newError("pointer to nil") + } + t = ctypes.NewPointer(t) + case token.LBRACK: // [ + if t, err = p.parseArrays(t, inFlags); err != nil { + return + } + case token.LPAREN: // ( + if t == nil { + log.Panicln("TODO") + return nil, 0, p.newError("no function return type") + } + var nstar = p.parseStars() + var nstarRet int + var tyArr types.Type + var pkg, isFn = p.conf.Pkg(), false + var args []*types.Var + var variadic bool + if nstar == 0 { + if getRetType(inFlags) { + err = nil + p.tok = token.EOF + return + } + if args, variadic, err = p.parseArgs(pkg); err != nil { + return + } + isFn = true + } else { + nextTok: + p.next() + switch p.tok { + case token.RPAREN: // ) + case token.LPAREN: // ( + if !isFn { + nstar, nstarRet = p.parseStars(), nstar + if nstar != 0 { + p.expect(token.RPAREN) // ) + p.expect(token.LPAREN) // ( + } + if args, variadic, err = p.parseArgs(pkg); err != nil { + return + } + isFn = true + goto nextTok + } + return nil, 0, p.newError("expect )") + case token.LBRACK: + if tyArr, err = p.parseArrays(ctypes.Void, 0); err != nil { + return + } + p.expect(token.RPAREN) // ) + case token.IDENT: + switch p.lit { + case "_Nullable", "_Nonnull", "const", "volatile": + goto nextTok + } + fallthrough + default: + return nil, 0, p.newError("expect )") + } + } + p.next() + switch p.tok { + case token.LPAREN: // ( + if t, err = p.parseFunc(pkg, t, inFlags); err != nil { + return + } + case token.LBRACK: // [ + if t, err = p.parseArrays(t, 0); err != nil { + return + } + case token.EOF: + case token.IDENT: + if p.lit == "__attribute__" { + p.tok, p.lit = token.EOF, "" + p.unget(token.EOF, "") + break + } + fallthrough + default: + return nil, 0, p.newError("unexpected " + p.tok.String()) + } + t = newPointers(t, nstarRet) + if isFn { + if getRetType(inFlags) { + p.tok = token.EOF + return + } + var results *types.Tuple + if ctypes.NotVoid(t) { + results = types.NewTuple(types.NewParam(token.NoPos, pkg, "", t)) + } + t = ctypes.NewFunc(types.NewTuple(args...), results, variadic) + } + t = newPointers(t, nstar) + t = newArrays(t, tyArr) + case token.RPAREN: + if t == nil { + t = ctypes.Void + } + return + case token.COMMA, token.EOF: + if t == nil { + err = io.ErrUnexpectedEOF + } + return + case token.ELLIPSIS: + if t != nil { + return nil, 0, p.newError("illegal syntax: multiple types?") + } + t = ctypes.Valist + kind |= KindFVariadic + default: + log.Panicln("c.types.ParseType: unknown -", p.tok, p.lit) + } + } +} + +func newPointers(t types.Type, nstar int) types.Type { + for nstar > 0 { + t = ctypes.NewPointer(t) + nstar-- + } + return t +} + +func isPtr(tok token.Token) bool { + return tok == token.MUL || tok == token.XOR // * or ^ +} + +func newArrays(t types.Type, tyArr types.Type) types.Type { +retry: + if arr, ok := tyArr.(*types.Array); ok { + t = types.NewArray(t, arr.Len()) + tyArr = arr.Elem() + goto retry + } + return t +} + +func newArraysEx(t types.Type, tyArr types.Type, inFlags int) types.Type { + t = newArrays(t, tyArr) + if arr, ok := t.(*types.Array); ok { + if (inFlags & FlagIsParam) != 0 { + t = ctypes.NewPointer(arr.Elem()) + } + } + return t +} + +// ----------------------------------------------------------------------------- diff --git a/x/clang/types/scanner/scanner.go b/x/clang/types/scanner/scanner.go new file mode 100644 index 00000000..842372f1 --- /dev/null +++ b/x/clang/types/scanner/scanner.go @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package scanner + +import ( + "fmt" + "go/token" + "unicode" + "unicode/utf8" +) + +// An ErrorHandler may be provided to Scanner.Init. If a syntax error is +// encountered and a handler was installed, the handler is called with a +// position and an error message. The position points to the beginning of +// the offending token. +type ErrorHandler func(pos token.Position, msg string) + +// A Scanner holds the scanner's internal state while processing +// a given text. It can be allocated as part of another data +// structure but must be initialized via Init before use. +type Scanner struct { + // immutable state + src string + + // scanning state + ch rune // current character + offset int // character offset + rdOffset int // reading offset (position after current character) + + // public state - ok to modify + ErrorCount int // number of errors encountered + OnErr func(msg string) +} + +const ( + bom = 0xFEFF // byte order mark, only permitted as very first character + eof = -1 // end of file +) + +// Read the next Unicode char into s.ch. +// s.ch < 0 means end-of-file. +// +// For optimization, there is some overlap between this method and +// s.scanIdentifier. +func (s *Scanner) next() { + if s.rdOffset < len(s.src) { + s.offset = s.rdOffset + r, w := rune(s.src[s.rdOffset]), 1 + switch { + case r == 0: + s.error("illegal character NUL") + case r >= utf8.RuneSelf: + // not ASCII + r, w = utf8.DecodeRuneInString(s.src[s.rdOffset:]) + if r == utf8.RuneError && w == 1 { + s.error("illegal UTF-8 encoding") + } else if r == bom && s.offset > 0 { + s.error("illegal byte order mark") + } + } + s.rdOffset += w + s.ch = r + } else { + s.offset = len(s.src) + s.ch = eof + } +} + +// peek returns the byte following the most recently read character without +// advancing the scanner. If the scanner is at EOF, peek returns 0. +func (s *Scanner) peek() byte { + if s.rdOffset < len(s.src) { + return s.src[s.rdOffset] + } + return 0 +} + +func (s *Scanner) Init(src string) { + s.src = src + s.ch = ' ' + s.offset = 0 + s.rdOffset = 0 + s.ErrorCount = 0 + + s.next() + if s.ch == bom { + s.next() // ignore BOM at file beginning + } +} + +func (s *Scanner) Source() string { + return s.src +} + +func (s *Scanner) error(msg string) { + if s.OnErr != nil { + s.OnErr(msg) + } + s.ErrorCount++ +} + +func (s *Scanner) errorf(format string, args ...interface{}) { + s.error(fmt.Sprintf(format, args...)) +} + +func isLetter(ch rune) bool { + return 'a' <= lower(ch) && lower(ch) <= 'z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch) +} + +func isDigit(ch rune) bool { + return isDecimal(ch) || ch >= utf8.RuneSelf && unicode.IsDigit(ch) +} + +// scanIdentifier reads the string of valid identifier characters at s.offset. +// It must only be called when s.ch is known to be a valid letter. +// +// Be careful when making changes to this function: it is optimized and affects +// scanning performance significantly. +func (s *Scanner) scanIdentifier() string { + offs := s.offset + + // Optimize for the common case of an ASCII identifier. + // + // Ranging over s.src[s.rdOffset:] lets us avoid some bounds checks, and + // avoids conversions to runes. + // + // In case we encounter a non-ASCII character, fall back on the slower path + // of calling into s.next(). + for rdOffset, b := range s.src[s.rdOffset:] { + if 'a' <= b && b <= 'z' || 'A' <= b && b <= 'Z' || b == '_' || '0' <= b && b <= '9' { + // Avoid assigning a rune for the common case of an ascii character. + continue + } + s.rdOffset += rdOffset + if 0 < b && b < utf8.RuneSelf { + // Optimization: we've encountered an ASCII character that's not a letter + // or number. Avoid the call into s.next() and corresponding set up. + // + // Note that s.next() does some line accounting if s.ch is '\n', so this + // shortcut is only possible because we know that the preceding character + // is not '\n'. + s.ch = rune(b) + s.offset = s.rdOffset + s.rdOffset++ + goto exit + } + // We know that the preceding character is valid for an identifier because + // scanIdentifier is only called when s.ch is a letter, so calling s.next() + // at s.rdOffset resets the scanner state. + s.next() + for isLetter(s.ch) || isDigit(s.ch) { + s.next() + } + goto exit + } + s.offset = len(s.src) + s.rdOffset = len(s.src) + s.ch = eof + +exit: + return string(s.src[offs:s.offset]) +} + +func lower(ch rune) rune { return ('a' - 'A') | ch } // returns lower-case ch iff ch is ASCII letter +func isDecimal(ch rune) bool { return '0' <= ch && ch <= '9' } +func isHex(ch rune) bool { return '0' <= ch && ch <= '9' || 'a' <= lower(ch) && lower(ch) <= 'f' } + +func (s *Scanner) digits(base int, invalid *int) (digsep int) { + if base <= 10 { + max := rune('0' + base) + for isDecimal(s.ch) { + if s.ch >= max && *invalid < 0 { + *invalid = s.offset // record invalid rune offset + } + digsep = 1 + s.next() + } + } else { + for isHex(s.ch) { + digsep = 1 + s.next() + } + } + return +} + +func (s *Scanner) scanNumber() (token.Token, string) { + offs := s.offset + + base := 10 // number base + prefix := rune(0) // one of 0 (decimal), '0' (0-octal), 'x', 'o', or 'b' + digsep := 0 // bit 0: digit present, bit 1: '_' present + invalid := -1 // index of invalid digit in literal, or < 0 + + if s.ch == '0' { + s.next() + switch lower(s.ch) { + case 'x': + s.next() + base, prefix = 16, 'x' + case 'o': + s.next() + base, prefix = 8, 'o' + case 'b': + s.next() + base, prefix = 2, 'b' + default: + base, prefix = 8, '0' + digsep = 1 // leading 0 + } + } + digsep |= s.digits(base, &invalid) + if digsep&1 == 0 { + s.error(litname(prefix) + " has no digits") + } + + lit := string(s.src[offs:s.offset]) + if invalid >= 0 { + s.errorf("invalid digit %q in %s", lit[invalid-offs], litname(prefix)) + } + return token.INT, lit +} + +func litname(prefix rune) string { + switch prefix { + case 'x': + return "hexadecimal literal" + case 'o', '0': + return "octal literal" + case 'b': + return "binary literal" + } + return "decimal literal" +} + +func (s *Scanner) skipWhitespace() { + for s.ch == ' ' || s.ch == '\t' || s.ch == '\n' || s.ch == '\r' { + s.next() + } +} + +func (s *Scanner) Scan() (tok token.Token, lit string) { + s.skipWhitespace() + + // determine token value + switch ch := s.ch; { + case isLetter(ch): + lit = s.scanIdentifier() + tok = token.IDENT + case isDecimal(ch): + tok, lit = s.scanNumber() + default: + s.next() // always make progress + switch ch { + case -1: + tok = token.EOF + case '.': + // fractions starting with a '.' are handled by outer switch + tok = token.PERIOD + if s.ch == '.' && s.peek() == '.' { + s.next() + s.next() // consume last '.' + tok = token.ELLIPSIS + } + case ',': + tok = token.COMMA + case '(': + tok = token.LPAREN + case ')': + tok = token.RPAREN + case '[': + tok = token.LBRACK + case ']': + tok = token.RBRACK + case '*': + tok = token.MUL + case '^': + tok = token.XOR + default: + // next reports unexpected BOMs - don't repeat + if ch != bom { + s.errorf("illegal character %#U", ch) + } + tok = token.ILLEGAL + lit = string(ch) + } + } + return +} diff --git a/x/clang/types/types.go b/x/clang/types/types.go new file mode 100644 index 00000000..272541ef --- /dev/null +++ b/x/clang/types/types.go @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package types + +import ( + "go/token" + "go/types" + "unsafe" + + "github.com/goplus/gogen" +) + +// ----------------------------------------------------------------------------- + +var ( + Void = types.Typ[types.UntypedNil] + UnsafePointer = types.Typ[types.UnsafePointer] + + Int = types.Typ[types.Int32] + Uint = types.Typ[types.Uint32] + Long = types.Typ[uintptr(types.Int32)+unsafe.Sizeof(0)>>3] // int32/int64 + Ulong = types.Typ[uintptr(types.Uint32)+unsafe.Sizeof(0)>>3] // uint32/uint64 + NotImpl = UnsafePointer + + LongDouble = types.Typ[types.Float64] +) + +func NotVoid(t types.Type) bool { + return t != Void +} + +func MangledName(tag, name string) string { + return tag + "_" + name // TODO: use sth to replace _ +} + +// ----------------------------------------------------------------------------- + +var ( + ValistTag types.Type + Valist types.Type = types.NewSlice(gogen.TyEmptyInterface) +) + +func init() { + vaTag := types.NewTypeName(token.NoPos, types.Unsafe, MangledName("struct", "__va_list_tag"), nil) + ValistTag = types.NewNamed(vaTag, types.NewStruct(nil, nil), nil) + types.Universe.Insert(vaTag) +} + +// ----------------------------------------------------------------------------- + +func NewFunc(params, results *types.Tuple, variadic bool) *types.Signature { + return gogen.NewCSignature(params, results, variadic) +} + +func NewPointer(typ types.Type) types.Type { + switch t := typ.(type) { + case *types.Basic: + if t == Void { + return types.Typ[types.UnsafePointer] + } + case *types.Signature: + if gogen.IsCSignature(t) { + return types.NewSignature(nil, t.Params(), t.Results(), t.Variadic()) + } + case *types.Named: + if typ == ValistTag { + return Valist + } + } + return types.NewPointer(typ) +} + +func IsFunc(typ types.Type) bool { + sig, ok := typ.(*types.Signature) + if ok { + ok = gogen.IsCSignature(sig) + } + return ok +} + +func Identical(typ1, typ2 types.Type) bool { + return types.Identical(typ1, typ2) +} + +// -----------------------------------------------------------------------------