generate.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. // Package types is responsible for parsing the Godot headers for type definitions
  2. // and generating Go wrappers around that structure.
  3. package types
  4. import (
  5. "bytes"
  6. "log"
  7. "os"
  8. "os/exec"
  9. "path/filepath"
  10. "sort"
  11. "strings"
  12. "text/template"
  13. "git.alfi.li/gamelang/gdnative-go/cmd/generate/methods"
  14. "github.com/pinzolo/casee"
  15. )
  16. // don't try to use goimports if its missing (nowadays goreturns is used mainly)
  17. var noGoImport bool
  18. // View is a structure that holds the api struct, so it can be used inside
  19. // our template.
  20. type View struct {
  21. Headers []string
  22. TypeDefinitions []TypeDef
  23. MethodDefinitions []Method
  24. IgnoreMethods []string
  25. }
  26. // Debug will allow you to log inside the running template.
  27. func (v View) Debug(itm string) string {
  28. log.Println("Template Log:", itm)
  29. return ""
  30. }
  31. // IsValidProperty will determine if we should be generating the given property
  32. // in our Go structure.
  33. func (v View) IsValidProperty(prop TypeDef) bool {
  34. return !strings.Contains(prop.Name, "_touch_that")
  35. }
  36. // IsGodotBaseType will check to see if the given simple type definition is defining
  37. // a built-in C type or a Godot type.
  38. func (v View) IsGodotBaseType(typeDef TypeDef) bool {
  39. return strings.Contains(typeDef.Base, "godot_")
  40. }
  41. // ToGoBaseType will convert a base type name to the correct Go base type.
  42. func (v View) ToGoBaseType(base string) string {
  43. switch base {
  44. case "float":
  45. return "float64"
  46. case "wchar_t":
  47. return "string"
  48. }
  49. return base
  50. }
  51. // ToGoName will convert a prefixed string to the correct Go name
  52. func (v View) ToGoName(str string) string {
  53. str = strings.Replace(str, "godot_", "", 1)
  54. str = strings.Replace(str, "GODOT_", "", 1)
  55. return casee.ToPascalCase(str)
  56. }
  57. // ToGoReturnType will remove void return types
  58. func (v View) ToGoReturnType(str string) string {
  59. str = v.ToGoArgType(str, true)
  60. if strings.Contains(str, "Void") {
  61. return ""
  62. }
  63. return str
  64. }
  65. // HasReturn returns true if the given string is void
  66. func (v View) HasReturn(str string) bool {
  67. if str == "void" || str == "Void" || strings.Contains(str, "void") {
  68. return false
  69. }
  70. return true
  71. }
  72. // HasPointerReturn returns true if the given string contains an indirection operator
  73. func (v View) HasPointerReturn(str string) bool {
  74. return strings.Contains(str, "*")
  75. }
  76. // IsVoidPointerType returns true if the given string matches godot object void types
  77. func (v View) IsVoidPointerType(str string) bool {
  78. switch str {
  79. case "godot_object *", "const godot_object *":
  80. return true
  81. }
  82. return false
  83. }
  84. // IsWcharT returns true if the given strig contains wchar_t type
  85. func (v View) IsWcharT(str string) bool {
  86. return strings.Contains(str, "wchar_t")
  87. }
  88. // IsDoublePointer returns true if the given string contains two indirection
  89. // operators one beside another
  90. func (v View) IsDoublePointer(str string) bool {
  91. return strings.Contains(str, "**")
  92. }
  93. // ToGoArgType converts arguments types to Go valid types
  94. func (v View) ToGoArgType(str string, parseArray bool) string {
  95. str = strings.Replace(str, "const ", "", -1)
  96. str = v.ToGoName(str)
  97. str = strings.Replace(str, "*", "", 1)
  98. str = strings.TrimSpace(str)
  99. // If the string still contains a *, it is a list.
  100. if strings.Contains(str, "*") {
  101. str = strings.Replace(str, "*", "", 1)
  102. if parseArray {
  103. str = "[]" + str
  104. }
  105. }
  106. return str
  107. }
  108. // ToGoArgName converts argument names to idiomatic Go ones removing any prefixes
  109. func (v View) ToGoArgName(str string) string {
  110. if strings.HasPrefix(str, "p_") {
  111. str = strings.Replace(str, "p_", "", 1)
  112. }
  113. if strings.HasPrefix(str, "r_") {
  114. str = strings.Replace(str, "r_", "", 1)
  115. }
  116. str = casee.ToCamelCase(str)
  117. // Check for any reserved names
  118. switch str {
  119. case "type":
  120. return "aType"
  121. case "default":
  122. return "aDefault"
  123. case "var":
  124. return "variable"
  125. case "func":
  126. return "function"
  127. case "return":
  128. return "returns"
  129. case "interface":
  130. return "intrfce"
  131. case "string":
  132. return "str"
  133. }
  134. return str
  135. }
  136. // IsBasicType returns true if the given string is part of our defined basic types
  137. func (v View) IsBasicType(str string) bool {
  138. switch str {
  139. case "Uint", "WcharT", "Bool", "Double", "Error", "Int", "Int64T", "Uint64T", "Uint8T", "Uint32T", "Real", "MethodRpcMode", "PropertyHint", "SignedChar", "UnsignedChar", "Vector3Axis":
  140. return true
  141. }
  142. return false
  143. }
  144. // OutputCArg will determine if we need to reference, dereference, etc. an argument
  145. // before passing it to a C function.
  146. func (v View) OutputCArg(arg []string) string {
  147. argType := arg[0]
  148. // For basic types, we usually don't pass by pointer.
  149. if v.IsBasicType(v.ToGoArgType(argType, true)) {
  150. if v.HasPointerReturn(argType) {
  151. return "&"
  152. }
  153. if argType == "wchar_t" && !v.HasPointerReturn(argType) {
  154. return "*"
  155. }
  156. return ""
  157. }
  158. // Non-basic types are returned as pointers. If the C function doesn't want
  159. // a pointer, we need to dereference the argument.
  160. if !v.HasPointerReturn(argType) {
  161. return "*"
  162. }
  163. return ""
  164. }
  165. // MethodsList returns all of the methods that match this typedef.
  166. func (v View) MethodsList(typeDef TypeDef) []Method {
  167. methods := []Method{}
  168. // Look for all methods that match this typedef name.
  169. for _, method := range v.MethodDefinitions {
  170. ignoreMethod := false
  171. for _, ignMethod := range v.IgnoreMethods {
  172. if method.Name == ignMethod {
  173. ignoreMethod = true
  174. }
  175. }
  176. if ignoreMethod {
  177. continue
  178. }
  179. for _, arg := range method.Arguments {
  180. argName := arg[1]
  181. argType := strings.Replace(arg[0], "const", "", 1)
  182. argType = strings.Replace(argType, "*", "", 1)
  183. argType = strings.TrimSpace(argType)
  184. if argType == typeDef.Name && argName == "p_self" {
  185. methods = append(methods, method)
  186. break
  187. } else if strings.Contains(method.Name, typeDef.Name) && v.MethodIsConstructor(method) {
  188. methods = append(methods, method)
  189. break
  190. }
  191. }
  192. }
  193. return methods
  194. }
  195. // MethodIsConstructor returns true if the given method contains the `_new` sub string
  196. func (v View) MethodIsConstructor(method Method) bool {
  197. return strings.Contains(method.Name, "_new")
  198. }
  199. // NotSelfArg return false if the given string contains any reference to self or p_self
  200. func (v View) NotSelfArg(str string) bool {
  201. if str == "self" || str == "p_self" {
  202. return false
  203. }
  204. return true
  205. }
  206. // StripPointer strips the indirection operator from a given string
  207. func (v View) StripPointer(str string) string {
  208. str = strings.Replace(str, "*", "", 1)
  209. str = strings.TrimSpace(str)
  210. return str
  211. }
  212. // ToGoMethodName cleans names from typed definitions and adapt to Go
  213. func (v View) ToGoMethodName(typeDef TypeDef, method Method) string {
  214. methodName := method.Name
  215. // Replace the typedef in the method name
  216. methodName = strings.Replace(methodName, typeDef.Name, "", 1)
  217. // Swap some things around if this is a constructor
  218. if v.MethodIsConstructor(method) {
  219. methodName = strings.Replace(methodName, "_new", "", 1)
  220. methodName = "new_" + typeDef.GoName + "_" + methodName
  221. }
  222. if strings.HasPrefix(methodName, "2") {
  223. methodName = "T" + methodName
  224. }
  225. return casee.ToPascalCase(methodName)
  226. }
  227. // Method defines a regular method components
  228. type Method struct {
  229. Name string
  230. ReturnType string
  231. Arguments [][]string
  232. }
  233. // Generate will generate Go wrappers for all Godot base types
  234. func Generate() {
  235. // Get the API Path so we can localize the godot api JSON.
  236. apiPath := os.Getenv("API_PATH")
  237. if apiPath == "" {
  238. panic("$API_PATH is not defined.")
  239. }
  240. packagePath := apiPath
  241. // Set up headers/structures to ignore. Definitions in the given headers
  242. // with the given name will not be added to the returned list of type definitions.
  243. // We'll need to manually create these structures.
  244. ignoreHeaders := []string{
  245. filepath.Join("pluginscript", "godot_pluginscript.h"),
  246. filepath.Join("net/godot_net.h"),
  247. filepath.Join("net/godot_webrtc.h"),
  248. }
  249. ignoreStructs := []string{
  250. "godot_char_type",
  251. "godot_gdnative_api_struct",
  252. "godot_gdnative_core_api_struct",
  253. "godot_gdnative_ext_arvr_api_struct",
  254. "godot_gdnative_ext_nativescript_1_1_api_struct",
  255. "godot_gdnative_ext_nativescript_api_struct",
  256. "godot_gdnative_ext_pluginscript_api_struct",
  257. "godot_gdnative_init_options",
  258. "godot_gdnative_ext_net_3_2_api_struct",
  259. "godot_instance_binding_functions",
  260. "godot_instance_create_func",
  261. "godot_instance_destroy_func",
  262. "godot_instance_method",
  263. "godot_method_attributes",
  264. "godot_property_get_func",
  265. "godot_property_set_func",
  266. "godot_property_usage_flags",
  267. }
  268. ignoreMethods := []string{
  269. "godot_string_new_with_wide_string",
  270. "godot_string_new",
  271. "godot_string_new_copy",
  272. "godot_string_name_new",
  273. "godot_string_name_new_data",
  274. "godot_transform2d_new",
  275. "godot_transform2d_new_axis_origin",
  276. "godot_transform2d_new_identity",
  277. }
  278. // Parse all available methods
  279. gdnativeAPI := methods.Parse()
  280. // Convert the API definitions into a method struct
  281. allMethodDefinitions := []Method{}
  282. for _, api := range gdnativeAPI.Core.API {
  283. method := Method{
  284. Name: api.Name,
  285. ReturnType: api.ReturnType,
  286. Arguments: api.Arguments,
  287. }
  288. allMethodDefinitions = append(allMethodDefinitions, method)
  289. }
  290. // Parse the Godot header files for type definitions
  291. allTypeDefinitions := Parse(ignoreHeaders, ignoreStructs)
  292. // Create a map of the type definitions by header name
  293. defMap := map[string][]TypeDef{}
  294. // Organize the type definitions by header name
  295. for _, typeDef := range allTypeDefinitions {
  296. _, ok := defMap[typeDef.HeaderName]
  297. if ok {
  298. defMap[typeDef.HeaderName] = append(defMap[typeDef.HeaderName], typeDef)
  299. } else {
  300. defMap[typeDef.HeaderName] = []TypeDef{typeDef}
  301. }
  302. }
  303. // pretty.Println(defMap)
  304. // Loop through each header name and generate the Go code in a file based
  305. // on the header name.
  306. log.Println("Generating Go wrappers for Godot base types...")
  307. for headerName, typeDefs := range defMap {
  308. // Convert the header name into the Go filename
  309. _, headerFile := filepath.Split(headerName)
  310. outFileName := strings.Replace(headerFile, ".h", ".gen.go", 1)
  311. // outFileName = strings.Replace(outFileName, "godot_", "", 1)
  312. log.Printf(" Generating Go code for: \x1b[32m%s\x1b[0m...\n", outFileName)
  313. // Create a structure for our template view. This will contain all of
  314. // the data we need to construct our Go wrappers.
  315. var view View
  316. // Add the type definitions for this file to our view.
  317. view.MethodDefinitions = allMethodDefinitions
  318. view.TypeDefinitions = typeDefs
  319. view.Headers = []string{}
  320. view.IgnoreMethods = ignoreMethods
  321. // Collect all of the headers we need to use in our template.
  322. headers := map[string]bool{}
  323. for _, typeDef := range view.TypeDefinitions {
  324. headers[typeDef.HeaderName] = true
  325. }
  326. for header := range headers {
  327. view.Headers = append(view.Headers, header)
  328. }
  329. sort.Strings(view.Headers)
  330. // Write the file using our template.
  331. WriteTemplate(
  332. filepath.Join(packagePath, "cmd", "generate", "templates", "types.go.tmpl"),
  333. filepath.Join(packagePath, "gdnative", outFileName),
  334. view,
  335. )
  336. // Run gofmt on the generated Go file.
  337. log.Println(" Running gofmt on output:", outFileName+"...")
  338. if !noGoImport {
  339. GoFmt(packagePath + "/gdnative/" + outFileName)
  340. }
  341. log.Println(" Running goimports on output:", outFileName+"...")
  342. err := GoImports(packagePath + "/gdnative/" + outFileName)
  343. if err != nil {
  344. log.Println(" Trying to run goreturns on output:", outFileName+"...")
  345. GoReturns(packagePath + "/gdnative/" + outFileName)
  346. noGoImport = true
  347. }
  348. }
  349. // pretty.Println(allMethodDefinitions)
  350. }
  351. // WriteTemplate writes the result from our template file
  352. func WriteTemplate(templatePath, outputPath string, view View) {
  353. // Create a template from our template file.
  354. t, err := template.ParseFiles(templatePath)
  355. if err != nil {
  356. log.Fatal("Error parsing template:", err)
  357. }
  358. // Open the output file for writing
  359. f, err := os.Create(outputPath)
  360. if err != nil {
  361. panic(err)
  362. }
  363. defer f.Close()
  364. // Write the template with the given view.
  365. err = t.Execute(f, view)
  366. if err != nil {
  367. panic(err)
  368. }
  369. }
  370. // GoFmt runs gofmt on the given filepath
  371. func GoFmt(filePath string) {
  372. cmd := exec.Command("gofmt", "-w", filePath)
  373. var stdErr bytes.Buffer
  374. cmd.Stderr = &stdErr
  375. err := cmd.Run()
  376. if err != nil {
  377. log.Println("Error running gofmt:", err)
  378. panic(stdErr.String())
  379. }
  380. }
  381. // GoImports runs goimports in the given filepath
  382. func GoImports(filePath string) error {
  383. cmd := exec.Command("goimports", "-w", filePath)
  384. var stdErr bytes.Buffer
  385. cmd.Stderr = &stdErr
  386. err := cmd.Run()
  387. return err
  388. }
  389. // GoReturns runs goreturns in the given filepath
  390. func GoReturns(filepath string) {
  391. cmd := exec.Command("goreturns", "-w", filepath)
  392. var stdErr bytes.Buffer
  393. cmd.Stderr = &stdErr
  394. err := cmd.Run()
  395. if err != nil {
  396. log.Println("Error running goreturns and goimports is not installed", err)
  397. panic(stdErr.String())
  398. }
  399. }