123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- // Package types is a package that parses the GDNative headers for type definitions
- // to create wrapper structures for Go.
- package types
- import (
- "fmt"
- "io/ioutil"
- "log"
- "os"
- "path/filepath"
- "strings"
- "github.com/pinzolo/casee"
- )
- var verboseOutput = false
- // Parse will parse the GDNative headers. Takes a list of headers/structs to ignore.
- // Definitions in the given headers and definitions
- // with the given name will not be added to the returned list of type definitions.
- // We'll need to manually create these structures.
- func Parse(excludeHeaders, excludeStructs []string) []TypeDef {
- // Get the API Path so we can localte the godot api JSON.
- apiPath := os.Getenv("API_PATH")
- if apiPath == "" {
- panic("$API_PATH is not defined.")
- }
- packagePath := apiPath
- if os.Getenv("VERBOSE_OUTPUT") != "" {
- verboseOutput = true
- }
- // Walk through all of the godot header files
- searchDir := filepath.Join(packagePath, "godot_headers")
- fileList := []string{}
- err := filepath.Walk(searchDir, func(path string, f os.FileInfo, err error) error {
- if !f.IsDir() && strings.Contains(path, ".h") {
- fileList = append(fileList, path)
- }
- return nil
- })
- if err != nil {
- panic(err)
- }
- // Create a list of all our type definitions
- typeDefinitions := []TypeDef{}
- // Loop through all of the Godot header files and parse the type definitions
- for _, header := range fileList {
- log.Println("Parsing header:", header, "...")
- headerName := strings.Replace(header, searchDir+string(os.PathSeparator), "", 1)
- // Read the header
- content, err := ioutil.ReadFile(header)
- if err != nil {
- panic(err)
- }
- // Find all of the type definitions in the header file
- foundTypesLines := findTypeDefs(content)
- if verboseOutput {
- fmt.Println("")
- }
- // After extracting the lines, we can now parse the type definition to
- // a structure that we can use to build a Go wrapper.
- for _, foundTypeLines := range foundTypesLines {
- typeDef := parseTypeDef(foundTypeLines, headerName)
- // Only add the type if it's not in our exclude list.
- if !strInSlice(typeDef.Name, excludeStructs) && !strInSlice(typeDef.HeaderName, excludeHeaders) {
- typeDefinitions = append(typeDefinitions, typeDef)
- }
- }
- }
- return typeDefinitions
- }
- func parseTypeDef(typeLines []string, headerName string) TypeDef {
- // Create a structure for our type definition.
- typeDef := TypeDef{
- HeaderName: headerName,
- Properties: []TypeDef{},
- }
- // Small function for splitting a line to get the uncommented line and
- // get the comment itself.
- getComment := func(line string) (def, comment string) {
- halves := strings.Split(line, "//")
- def = halves[0]
- if len(halves) > 1 {
- comment = strings.TrimSpace(halves[1])
- }
- if strings.HasPrefix(comment, "/") {
- comment = strings.Replace(comment, "/", "", 1)
- }
- return def, comment
- }
- // If the type definition is a single line, handle it a little differently
- if len(typeLines) == 1 {
- // Extract the comment if there is one.
- line, comment := getComment(typeLines[0])
- // Check to see if the property is a pointer type
- if strings.Contains(line, "*") {
- line = strings.Replace(line, "*", "", 1)
- typeDef.IsPointer = true
- }
- // Get the words of the line
- words := strings.Split(line, " ")
- typeDef.Name = strings.Replace(words[len(words)-1], ";", "", 1)
- typeDef.GoName = casee.ToPascalCase(strings.Replace(typeDef.Name, "godot_", "", 1))
- typeDef.Base = words[len(words)-2]
- typeDef.Comment = comment
- typeDef.SimpleType = true
- return typeDef
- }
- // Extract the name of the type.
- lastLine := typeLines[len(typeLines)-1]
- words := strings.Split(lastLine, " ")
- typeDef.Name = strings.Replace(words[len(words)-1], ";", "", 1)
- // Convert the name of the type to a Go name
- typeDef.GoName = casee.ToPascalCase(strings.Replace(typeDef.Name, "godot_", "", 1))
- // Extract the base type
- firstLine := typeLines[0]
- words = strings.Split(firstLine, " ")
- typeDef.Base = words[1]
- // Extract the properties from the type
- properties := typeLines[1 : len(typeLines)-1]
- // Loop through each property line
- for _, line := range properties {
- // Skip function definitions
- if strings.Contains(line, "(*") {
- continue
- }
- // Create a type definition for the property
- property := TypeDef{}
- // Extract the comment if there is one.
- line, comment := getComment(line)
- property.Comment = comment
- // Sanitize the line
- line = strings.TrimSpace(line)
- line = strings.Split(line, ";")[0]
- line = strings.Replace(line, "unsigned ", "u", 1)
- line = strings.Replace(line, "const ", "", 1)
- // Split the line by spaces
- words = strings.Split(line, " ")
- // Check to see if the line is just a comment
- if words[0] == "//" {
- continue
- }
- // Set the property details
- if typeDef.Base == "enum" {
- // Strip any commas in the name
- words[0] = strings.Replace(words[0], ",", "", 1)
- property.Name = words[0]
- property.GoName = casee.ToPascalCase(strings.Replace(words[0], "GODOT_", "", 1))
- } else {
- if len(words) < 2 {
- if verboseOutput {
- fmt.Println("Skipping irregular line:", line)
- }
- continue
- }
- property.Base = words[0]
- property.Name = words[1]
- property.GoName = casee.ToPascalCase(strings.Replace(words[1], "godot_", "", 1))
- }
- // Check to see if the property is a pointer type
- if strings.Contains(property.Name, "*") {
- property.Name = strings.Replace(property.Name, "*", "", 1)
- property.GoName = strings.Replace(property.GoName, "*", "", 1)
- property.IsPointer = true
- }
- // Skip empty property names
- if property.GoName == "" {
- continue
- }
- // Append the property to the type definition
- typeDef.Properties = append(typeDef.Properties, property)
- }
- return typeDef
- }
- // findTypeDefs will return a list of type definition lines.
- func findTypeDefs(content []byte) [][]string {
- lines := strings.Split(string(content), "\n")
- // Create a structure that will hold the lines that define the
- // type.
- singleType := []string{}
- foundTypes := [][]string{}
- var typeFound = false
- for i, line := range lines {
- // Search the line for type definitions
- if strings.Contains(line, "typedef ") {
- if verboseOutput {
- fmt.Println("Line", i, ": Type found on line:", line)
- }
- typeFound = true
- // Check to see if this is a single line type. If it is,
- // we're done.
- if strings.Contains(line, ";") {
- // Skip if this is a function definition
- if strings.Contains(line, ")") {
- typeFound = false
- continue
- }
- if verboseOutput {
- fmt.Println("Line", i, ": Short type definition found.")
- }
- singleType = append(singleType, line)
- typeFound = false
- foundTypes = append(foundTypes, singleType)
- singleType = []string{}
- }
- }
- // If a type was found, keep appending our struct lines until we
- // reach the end of the definition.
- if typeFound {
- if verboseOutput {
- fmt.Println("Line", i, ": Appending line for type found:", line)
- }
- // Keep adding the lines to our list of lines until we
- // reach an end bracket.
- singleType = append(singleType, line)
- if strings.Contains(line, "}") {
- if verboseOutput {
- fmt.Println("Line", i, ": Found end of type definition.")
- }
- typeFound = false
- foundTypes = append(foundTypes, singleType)
- singleType = []string{}
- }
- }
- }
- return foundTypes
- }
- func strInSlice(a string, list []string) bool {
- for _, b := range list {
- if b == a {
- return true
- }
- }
- return false
- }
|