parse.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. // Package types is a package that parses the GDNative headers for type definitions
  2. // to create wrapper structures for Go.
  3. package types
  4. import (
  5. "fmt"
  6. "io/ioutil"
  7. "log"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. "github.com/pinzolo/casee"
  12. )
  13. var verboseOutput = false
  14. // Parse will parse the GDNative headers. Takes a list of headers/structs to ignore.
  15. // Definitions in the given headers and definitions
  16. // with the given name will not be added to the returned list of type definitions.
  17. // We'll need to manually create these structures.
  18. func Parse(excludeHeaders, excludeStructs []string) []TypeDef {
  19. // Get the API Path so we can localte the godot api JSON.
  20. apiPath := os.Getenv("API_PATH")
  21. if apiPath == "" {
  22. panic("$API_PATH is not defined.")
  23. }
  24. packagePath := apiPath
  25. if os.Getenv("VERBOSE_OUTPUT") != "" {
  26. verboseOutput = true
  27. }
  28. // Walk through all of the godot header files
  29. searchDir := filepath.Join(packagePath, "godot_headers")
  30. fileList := []string{}
  31. err := filepath.Walk(searchDir, func(path string, f os.FileInfo, err error) error {
  32. if !f.IsDir() && strings.Contains(path, ".h") {
  33. fileList = append(fileList, path)
  34. }
  35. return nil
  36. })
  37. if err != nil {
  38. panic(err)
  39. }
  40. // Create a list of all our type definitions
  41. typeDefinitions := []TypeDef{}
  42. // Loop through all of the Godot header files and parse the type definitions
  43. for _, header := range fileList {
  44. log.Println("Parsing header:", header, "...")
  45. headerName := strings.Replace(header, searchDir+string(os.PathSeparator), "", 1)
  46. // Read the header
  47. content, err := ioutil.ReadFile(header)
  48. if err != nil {
  49. panic(err)
  50. }
  51. // Find all of the type definitions in the header file
  52. foundTypesLines := findTypeDefs(content)
  53. if verboseOutput {
  54. fmt.Println("")
  55. }
  56. // After extracting the lines, we can now parse the type definition to
  57. // a structure that we can use to build a Go wrapper.
  58. for _, foundTypeLines := range foundTypesLines {
  59. typeDef := parseTypeDef(foundTypeLines, headerName)
  60. // Only add the type if it's not in our exclude list.
  61. if !strInSlice(typeDef.Name, excludeStructs) && !strInSlice(typeDef.HeaderName, excludeHeaders) {
  62. typeDefinitions = append(typeDefinitions, typeDef)
  63. }
  64. }
  65. }
  66. return typeDefinitions
  67. }
  68. func parseTypeDef(typeLines []string, headerName string) TypeDef {
  69. // Create a structure for our type definition.
  70. typeDef := TypeDef{
  71. HeaderName: headerName,
  72. Properties: []TypeDef{},
  73. }
  74. // Small function for splitting a line to get the uncommented line and
  75. // get the comment itself.
  76. getComment := func(line string) (def, comment string) {
  77. halves := strings.Split(line, "//")
  78. def = halves[0]
  79. if len(halves) > 1 {
  80. comment = strings.TrimSpace(halves[1])
  81. }
  82. if strings.HasPrefix(comment, "/") {
  83. comment = strings.Replace(comment, "/", "", 1)
  84. }
  85. return def, comment
  86. }
  87. // If the type definition is a single line, handle it a little differently
  88. if len(typeLines) == 1 {
  89. // Extract the comment if there is one.
  90. line, comment := getComment(typeLines[0])
  91. // Check to see if the property is a pointer type
  92. if strings.Contains(line, "*") {
  93. line = strings.Replace(line, "*", "", 1)
  94. typeDef.IsPointer = true
  95. }
  96. // Get the words of the line
  97. words := strings.Split(line, " ")
  98. typeDef.Name = strings.Replace(words[len(words)-1], ";", "", 1)
  99. typeDef.GoName = casee.ToPascalCase(strings.Replace(typeDef.Name, "godot_", "", 1))
  100. typeDef.Base = words[len(words)-2]
  101. typeDef.Comment = comment
  102. typeDef.SimpleType = true
  103. return typeDef
  104. }
  105. // Extract the name of the type.
  106. lastLine := typeLines[len(typeLines)-1]
  107. words := strings.Split(lastLine, " ")
  108. typeDef.Name = strings.Replace(words[len(words)-1], ";", "", 1)
  109. // Convert the name of the type to a Go name
  110. typeDef.GoName = casee.ToPascalCase(strings.Replace(typeDef.Name, "godot_", "", 1))
  111. // Extract the base type
  112. firstLine := typeLines[0]
  113. words = strings.Split(firstLine, " ")
  114. typeDef.Base = words[1]
  115. // Extract the properties from the type
  116. properties := typeLines[1 : len(typeLines)-1]
  117. // Loop through each property line
  118. for _, line := range properties {
  119. // Skip function definitions
  120. if strings.Contains(line, "(*") {
  121. continue
  122. }
  123. // Create a type definition for the property
  124. property := TypeDef{}
  125. // Extract the comment if there is one.
  126. line, comment := getComment(line)
  127. property.Comment = comment
  128. // Sanitize the line
  129. line = strings.TrimSpace(line)
  130. line = strings.Split(line, ";")[0]
  131. line = strings.Replace(line, "unsigned ", "u", 1)
  132. line = strings.Replace(line, "const ", "", 1)
  133. // Split the line by spaces
  134. words = strings.Split(line, " ")
  135. // Check to see if the line is just a comment
  136. if words[0] == "//" {
  137. continue
  138. }
  139. // Set the property details
  140. if typeDef.Base == "enum" {
  141. // Strip any commas in the name
  142. words[0] = strings.Replace(words[0], ",", "", 1)
  143. property.Name = words[0]
  144. property.GoName = casee.ToPascalCase(strings.Replace(words[0], "GODOT_", "", 1))
  145. } else {
  146. if len(words) < 2 {
  147. if verboseOutput {
  148. fmt.Println("Skipping irregular line:", line)
  149. }
  150. continue
  151. }
  152. property.Base = words[0]
  153. property.Name = words[1]
  154. property.GoName = casee.ToPascalCase(strings.Replace(words[1], "godot_", "", 1))
  155. }
  156. // Check to see if the property is a pointer type
  157. if strings.Contains(property.Name, "*") {
  158. property.Name = strings.Replace(property.Name, "*", "", 1)
  159. property.GoName = strings.Replace(property.GoName, "*", "", 1)
  160. property.IsPointer = true
  161. }
  162. // Skip empty property names
  163. if property.GoName == "" {
  164. continue
  165. }
  166. // Append the property to the type definition
  167. typeDef.Properties = append(typeDef.Properties, property)
  168. }
  169. return typeDef
  170. }
  171. // findTypeDefs will return a list of type definition lines.
  172. func findTypeDefs(content []byte) [][]string {
  173. lines := strings.Split(string(content), "\n")
  174. // Create a structure that will hold the lines that define the
  175. // type.
  176. singleType := []string{}
  177. foundTypes := [][]string{}
  178. var typeFound = false
  179. for i, line := range lines {
  180. // Search the line for type definitions
  181. if strings.Contains(line, "typedef ") {
  182. if verboseOutput {
  183. fmt.Println("Line", i, ": Type found on line:", line)
  184. }
  185. typeFound = true
  186. // Check to see if this is a single line type. If it is,
  187. // we're done.
  188. if strings.Contains(line, ";") {
  189. // Skip if this is a function definition
  190. if strings.Contains(line, ")") {
  191. typeFound = false
  192. continue
  193. }
  194. if verboseOutput {
  195. fmt.Println("Line", i, ": Short type definition found.")
  196. }
  197. singleType = append(singleType, line)
  198. typeFound = false
  199. foundTypes = append(foundTypes, singleType)
  200. singleType = []string{}
  201. }
  202. }
  203. // If a type was found, keep appending our struct lines until we
  204. // reach the end of the definition.
  205. if typeFound {
  206. if verboseOutput {
  207. fmt.Println("Line", i, ": Appending line for type found:", line)
  208. }
  209. // Keep adding the lines to our list of lines until we
  210. // reach an end bracket.
  211. singleType = append(singleType, line)
  212. if strings.Contains(line, "}") {
  213. if verboseOutput {
  214. fmt.Println("Line", i, ": Found end of type definition.")
  215. }
  216. typeFound = false
  217. foundTypes = append(foundTypes, singleType)
  218. singleType = []string{}
  219. }
  220. }
  221. }
  222. return foundTypes
  223. }
  224. func strInSlice(a string, list []string) bool {
  225. for _, b := range list {
  226. if b == a {
  227. return true
  228. }
  229. }
  230. return false
  231. }