// 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 }