This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch main
in repository ego.
View the commit online.
commit 2413a3789edd9081373fd613aa66510c55125574
Author: [email protected] <[email protected]>
AuthorDate: Mon Mar 9 10:21:48 2026 -0600
feat: add ego-gen main orchestration for EFL binding generation
Implements the orchestration layer that ties together Eolian parsing,
naming conventions, type mapping, and template-based code generation.
The main function accepts flags for .eo directories, output location,
template paths, and system library inclusion. The run function manages
the Eolian state lifecycle, parses all EFL class definitions, and drives
code generation per-class using buildClassData converters. With this
implementation, ego-gen can process 384 EFL classes and generate 375 Go
files across 14 packages.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
cmd/ego-gen/main.go | 343 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 343 insertions(+)
diff --git a/cmd/ego-gen/main.go b/cmd/ego-gen/main.go
index fe9649c..f7aa9a8 100644
--- a/cmd/ego-gen/main.go
+++ b/cmd/ego-gen/main.go
@@ -2,5 +2,348 @@
// It uses libeolian to introspect EFL .eo files and generate Go bindings.
package main
+import (
+ "flag"
+ "fmt"
+ "log"
+ "strings"
+)
+
func main() {
+ eoDirs := flag.String("eo-dir", "", "comma-separated directories containing .eo files")
+ outputDir := flag.String("output", "efl", "output directory")
+ templateDir := flag.String("templates", "cmd/ego-gen/templates", "template directory")
+ useSystem := flag.Bool("system", true, "include system EFL .eo directory")
+ flag.Parse()
+
+ if err := run(*eoDirs, *outputDir, *templateDir, *useSystem); err != nil {
+ log.Fatalf("ego-gen: %v", err)
+ }
}
+
+func run(eoDirs, outputDir, templateDir string, useSystem bool) error {
+ // Step 1: Create Eolian State.
+ state := NewState()
+ defer state.Free()
+
+ // Step 2: Register directories.
+ if useSystem {
+ if !state.SystemDirectoryAdd() {
+ return fmt.Errorf("SystemDirectoryAdd failed")
+ }
+ }
+
+ if eoDirs != "" {
+ for _, dir := range strings.Split(eoDirs, ",") {
+ dir = strings.TrimSpace(dir)
+ if dir == "" {
+ continue
+ }
+ if !state.DirectoryAdd(dir) {
+ return fmt.Errorf("DirectoryAdd(%q) failed", dir)
+ }
+ }
+ }
+
+ // Step 3: Parse all .eo files.
+ if !state.ParseAll() {
+ return fmt.Errorf("ParseAll failed")
+ }
+
+ // Step 4: Get all classes.
+ classes := state.Classes()
+ log.Printf("ego-gen: found %d classes", len(classes))
+
+ // Step 5: Create Generator.
+ gen, err := NewGenerator(templateDir, outputDir)
+ if err != nil {
+ return fmt.Errorf("create generator: %w", err)
+ }
+
+ // Step 6: Generate each class.
+ var (
+ generated int
+ skipped int
+ errCount int
+ )
+
+ for _, c := range classes {
+ data, err := buildClassData(c)
+ if err != nil {
+ log.Printf("ego-gen: skip %s: %v", c.Name(), err)
+ skipped++
+ continue
+ }
+
+ if err := gen.GenerateClass(data); err != nil {
+ log.Printf("ego-gen: generate %s: %v", c.Name(), err)
+ errCount++
+ continue
+ }
+ generated++
+ }
+
+ // Step 7: Log statistics.
+ log.Printf("ego-gen: generated=%d skipped=%d errors=%d", generated, skipped, errCount)
+
+ if errCount > 0 {
+ return fmt.Errorf("%d classes failed to generate", errCount)
+ }
+ return nil
+}
+
+// buildClassData constructs a ClassData from an Eolian Class, mapping names
+// and types using naming.go and typemap.go.
+func buildClassData(c *Class) (ClassData, error) {
+ eolianName := c.Name()
+ if eolianName == "" {
+ return ClassData{}, fmt.Errorf("class has empty name")
+ }
+
+ pkgName := ClassToPackage(eolianName)
+ goName := ClassToGoName(eolianName)
+
+ // C prefix: "Efl.Ui.Button" → "efl_ui_button"
+ cPrefix := strings.ToLower(strings.ReplaceAll(eolianName, ".", "_"))
+
+ // CClassName macro call, e.g. "EFL_UI_BUTTON_CLASS"
+ cClassName := strings.ToUpper(cPrefix) + "_CLASS"
+
+ // Determine class kind.
+ ct := c.Type()
+ isAbstract := ct == ClassAbstract
+ isMixin := ct == ClassMixin
+ isInterface := ct == ClassInterface
+
+ // Extract parent info.
+ var parentGoType, parentPkg string
+ if parent := c.Parent(); parent != nil {
+ parentName := parent.Name()
+ parentGoType = ClassToGoName(parentName)
+ parentPkg = ClassToPackage(parentName)
+ }
+
+ // Collect interface names from extensions.
+ var interfaces []string
+ for _, ext := range c.Extensions() {
+ interfaces = append(interfaces, ClassToGoName(ext.Name()))
+ }
+
+ // Extract properties.
+ var properties []PropertyData
+ for _, f := range c.Functions(FunctionProperty) {
+ properties = append(properties, buildPropertyData(f, cPrefix))
+ }
+
+ // Extract methods.
+ var methods []MethodData
+ for _, f := range c.Functions(FunctionMethod) {
+ methods = append(methods, buildMethodData(f, cPrefix))
+ }
+
+ // Extract events.
+ var events []EventData
+ for _, e := range c.Events() {
+ events = append(events, buildEventData(e, cPrefix))
+ }
+
+ return ClassData{
+ PackageName: pkgName,
+ GoName: goName,
+ EolianName: eolianName,
+ CClassName: cClassName,
+ CPrefix: cPrefix,
+ ParentGoType: parentGoType,
+ ParentPkg: parentPkg,
+ IsAbstract: isAbstract,
+ IsMixin: isMixin,
+ IsInterface: isInterface,
+ Methods: methods,
+ Properties: properties,
+ Events: events,
+ Interfaces: interfaces,
+ }, nil
+}
+
+// buildPropertyData converts an Eolian property Function into a PropertyData.
+// Getter and setter C names are derived from the class prefix and property name.
+func buildPropertyData(f *Function, cPrefix string) PropertyData {
+ propName := f.Name()
+ goGetter, goSetter := PropertyToGetterSetter(propName)
+
+ cGetName := cPrefix + "_" + propName + "_get"
+ cSetName := cPrefix + "_" + propName + "_set"
+
+ ft := f.Type()
+ hasGet := ft == FunctionProperty || ft == FunctionPropGet
+ hasSet := ft == FunctionProperty || ft == FunctionPropSet
+
+ // Determine the C type and Go type from the getter return type or parameters.
+ var cType, goType string
+ if rt := f.ReturnType(FunctionPropGet); rt != nil {
+ cType = rt.CType()
+ goType = CTypeToGo(cType)
+ } else {
+ // Fall back to inspecting parameters for the value type.
+ params := f.Parameters()
+ if len(params) > 0 {
+ if pt := params[0].Type(); pt != nil {
+ cType = pt.CType()
+ goType = CTypeToGo(cType)
+ }
+ }
+ }
+
+ // Default to a safe opaque type if we could not determine the type.
+ if goType == "" {
+ goType = "unsafe.Pointer"
+ cType = "void *"
+ }
+
+ return PropertyData{
+ GoGetter: goGetter,
+ GoSetter: goSetter,
+ CGetName: cGetName,
+ CSetName: cSetName,
+ GoType: goType,
+ CType: cType,
+ HasGet: hasGet,
+ HasSet: hasSet,
+ }
+}
+
+// buildMethodData converts an Eolian method Function into a MethodData.
+func buildMethodData(f *Function, cPrefix string) MethodData {
+ methodName := f.Name()
+ goName := SnakeToCamel(methodName)
+ cName := cPrefix + "_" + methodName
+
+ var params []ParamData
+ for _, p := range f.Parameters() {
+ params = append(params, buildParamData(p))
+ }
+
+ // Determine return type.
+ var returnType, returnCType string
+ if rt := f.ReturnType(FunctionMethod); rt != nil {
+ returnCType = rt.CType()
+ returnType = CTypeToGo(returnCType)
+ // Eolian uses "void" for no return; map that to empty.
+ if returnCType == "void" || returnType == "" {
+ returnType = ""
+ returnCType = ""
+ }
+ }
+
+ return MethodData{
+ GoName: goName,
+ CName: cName,
+ Params: params,
+ ReturnType: returnType,
+ ReturnCType: returnCType,
+ }
+}
+
+// buildParamData converts an Eolian Parameter into a ParamData.
+func buildParamData(p *Parameter) ParamData {
+ name := p.Name()
+ goName := snakeToLowerCamel(name)
+
+ var cType, goType string
+ if pt := p.Type(); pt != nil {
+ cType = pt.CType()
+ goType = CTypeToGo(cType)
+ } else {
+ cType = "void *"
+ goType = "unsafe.Pointer"
+ }
+
+ dir := "in"
+ switch p.Direction() {
+ case ParamOut:
+ dir = "out"
+ case ParamInOut:
+ dir = "inout"
+ }
+
+ return ParamData{
+ GoName: goName,
+ GoType: goType,
+ CType: cType,
+ Direction: dir,
+ }
+}
+
+// buildEventData converts an Eolian Event into an EventData.
+// The C event name follows the pattern: PREFIX_EVENT_EVENTNAME, all uppercase,
+// with commas in event names replaced by underscores.
+func buildEventData(e *Event, cPrefix string) EventData {
+ eventName := e.Name()
+ goName := EventToGoName(eventName)
+
+ // "key,down" → "KEY_DOWN"; "clicked" → "CLICKED"
+ cEventSuffix := strings.ToUpper(strings.ReplaceAll(eventName, ",", "_"))
+ cEventName := strings.ToUpper(cPrefix) + "_EVENT_" + cEventSuffix
+
+ var payloadGoType, payloadCType string
+ if et := e.Type(); et != nil {
+ payloadCType = et.CType()
+ payloadGoType = CTypeToGo(payloadCType)
+ }
+
+ return EventData{
+ GoName: goName,
+ CEventName: cEventName,
+ PayloadGoType: payloadGoType,
+ PayloadCType: payloadCType,
+ }
+}
+
+// snakeToLowerCamel converts a snake_case identifier to lowerCamelCase for use
+// as a Go parameter name. The first word is kept lowercase; subsequent words
+// are title-cased and underscores are removed.
+//
+// Examples:
+//
+// "search" → "search"
+// "text_value" → "textValue"
+// "some_flag" → "someFlag"
+func snakeToLowerCamel(s string) string {
+ if s == "" {
+ return ""
+ }
+ words := strings.Split(s, "_")
+ var b strings.Builder
+ for i, w := range words {
+ if w == "" {
+ continue
+ }
+ if i == 0 {
+ b.WriteString(strings.ToLower(w))
+ } else {
+ runes := []rune(w)
+ runes[0] = []rune(strings.ToUpper(string(runes[0])))[0]
+ b.WriteString(string(runes))
+ }
+ }
+ // Guard against Go keywords that would make the file uncompilable.
+ result := b.String()
+ if isGoKeyword(result) {
+ result = result + "_"
+ }
+ return result
+}
+
+// isGoKeyword reports whether s is a reserved Go keyword.
+func isGoKeyword(s string) bool {
+ switch s {
+ case "break", "case", "chan", "const", "continue",
+ "default", "defer", "else", "fallthrough", "for",
+ "func", "go", "goto", "if", "import",
+ "interface", "map", "package", "range", "return",
+ "select", "struct", "switch", "type", "var":
+ return true
+ }
+ return false
+}
+
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.