Skip to content

Commit

Permalink
Automatically update when a reserved keyword is used as an enum in AC…
Browse files Browse the repository at this point in the history
…T (Python bindings) (#207)

* Start with fixing the problematic enum in ACT. The current patch done after python binding generation is now automated inside act. The scope needs to be expanded across the script

* Extend the reserved keyword check across the entire Python binding
  • Loading branch information
vijaiaeroastro authored and gangatp committed Dec 16, 2024
1 parent f8cfe6c commit 13f9ced
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 7 deletions.
41 changes: 41 additions & 0 deletions Source/actutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,52 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package main

import (
"errors"
"fmt"
"log"
"os"
)

var (
ErrPythonBuildFailed = errors.New("failed to build dynamic Python implementation")
ErrFileDeletionFailed = errors.New("failed to write to output file")
ErrReservedKeyword = errors.New("failed to generate bindings as you are using a reserved keyword")
)

// Keep a map of reserved keywords in Python
var pythonReservedKeywords = map[string]bool{
"False": true, "None": true, "True": true, "and": true, "as": true, "assert": true, "async": true,
"await": true, "break": true, "class": true, "continue": true, "def": true, "del": true, "elif": true,
"else": true, "except": true, "finally": true, "for": true, "from": true, "global": true, "if": true,
"import": true, "in": true, "is": true, "lambda": true, "nonlocal": true, "not": true, "or": true,
"pass": true, "raise": true, "return": true, "try": true, "while": true, "with": true, "yield": true,
}

// FileExists returns true if and only if the file in a given path exists
func FileExists(path string) (bool) {
_, err := os.Stat(path);
return !os.IsNotExist(err);
}

// ReservedKeywordExit logs the formatted error, deletes the partially created file, and returns a named error.
func ReservedKeywordExit(bindingPath string, format string, a ...interface{}) error {
// Format the message using variadic arguments
msg := fmt.Sprintf(format, a...)

// Log the error message
log.Printf("%s", msg)

// Attempt to delete the partially created file
log.Printf("Deleting partially created binding file: %s", bindingPath)
err := os.Remove(bindingPath)
if err != nil {
// Log and return a wrapped error with context
log.Printf("Failed to delete incomplete file %s: %v", bindingPath, err)
return fmt.Errorf("%w: %v", ErrFileDeletionFailed, err)
}

log.Printf("Deleted binding file")

// Return the reserved keyword error
return ErrReservedKeyword
}
9 changes: 6 additions & 3 deletions Source/automaticcomponenttoolkit.go
Original file line number Diff line number Diff line change
Expand Up @@ -654,10 +654,13 @@ func main() {

err = createComponent(component, outfolderBase, bindingsDirectoryOverride, interfacesDirectoryOverride, stubDirectoryOverride, suppressBindings, suppressStub, suppressInterfaces, suppressSubcomponents, suppressLicense, suppressExamples)
if (err != nil) {
log.Println("Fatal error")
log.Fatal(err)
if err == ErrPythonBuildFailed {
log.Println("Python binding generation failed (Due to usage of reserved keywords)")
} else {
log.Println("Fatal error")
log.Fatal(err)
}
} else {
log.Println("Success")
}

}
45 changes: 41 additions & 4 deletions Source/buildbindingpython.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ import (
"strings"
)

// Store the python file path
var pythonBindingFile = "";


// BuildBindingPythonDynamic builds dynamic Python bindings of a library's API in form of explicitly loaded
// functions handles.
func BuildBindingPythonDynamic(componentdefinition ComponentDefinition, outputFolder string, outputFolderExample string, indentString string) error {
Expand All @@ -50,6 +54,7 @@ func BuildBindingPythonDynamic(componentdefinition ComponentDefinition, outputFo
libraryname := componentdefinition.LibraryName

DynamicPythonImpl := path.Join(outputFolder, namespace+".py");
pythonBindingFile = DynamicPythonImpl;
log.Printf("Creating \"%s\"", DynamicPythonImpl)
dynpythonfile, err := CreateLanguageFile (DynamicPythonImpl, indentString)
if err != nil {
Expand All @@ -62,6 +67,9 @@ func BuildBindingPythonDynamic(componentdefinition ComponentDefinition, outputFo

err = buildDynamicPythonImplementation(componentdefinition, dynpythonfile)
if err != nil {
if err == ErrReservedKeyword {
return ErrPythonBuildFailed
}
return err;
}

Expand Down Expand Up @@ -141,6 +149,9 @@ func buildDynamicPythonImplementation(componentdefinition ComponentDefinition, w
w.Writeln(" SUCCESS = 0")
for i := 0; i<len(componentdefinition.Errors.Errors); i++ {
merror := componentdefinition.Errors.Errors[i]
if pythonReservedKeywords[merror.Name] {
return ReservedKeywordExit(pythonBindingFile, "Error code uses a reserved keyword : %s", merror.Name)
}
w.Writeln(" %s = %d", merror.Name, merror.Code)
}
w.Writeln("")
Expand Down Expand Up @@ -174,12 +185,20 @@ func buildDynamicPythonImplementation(componentdefinition ComponentDefinition, w

for i := 0; i<len(componentdefinition.Enums); i++ {
enum := componentdefinition.Enums[i]
if pythonReservedKeywords[enum.Name] {
return ReservedKeywordExit(pythonBindingFile, "Class name for enum uses a reserved keyword : %s", enum.Name)
}
w.Writeln("'''Definition of %s", enum.Name)
w.Writeln("'''")
w.Writeln("class %s(CTypesEnum):", enum.Name)
for j:= 0; j<len(enum.Options); j++ {
option := enum.Options[j]
w.Writeln(" %s = %d", option.Name, option.Value)
if pythonReservedKeywords[option.Name] {
log.Printf("Invalid enum datatype as \"%s\" is a reserved keyword in Python. Replacing it with \"%s\"", option.Name, enum.Name + option.Name)
w.Writeln(" %s = %d", enum.Name + option.Name, option.Value)
} else {
w.Writeln(" %s = %d", option.Name, option.Value)
}
}
}
w.Writeln("")
Expand All @@ -190,6 +209,9 @@ func buildDynamicPythonImplementation(componentdefinition ComponentDefinition, w
w.Writeln("'''")
for i := 0; i<len(componentdefinition.Structs); i++ {
_struct := componentdefinition.Structs[i]
if pythonReservedKeywords[_struct.Name] {
return ReservedKeywordExit(pythonBindingFile, "Class name for the structure uses a reserved keyword : %s", _struct.Name)
}
w.Writeln("'''Definition of %s", _struct.Name)
w.Writeln("'''")
w.Writeln("class %s(ctypes.Structure):", _struct.Name)
Expand Down Expand Up @@ -231,6 +253,9 @@ func buildDynamicPythonImplementation(componentdefinition ComponentDefinition, w
w.Writeln("'''")
for i := 0; i<len(componentdefinition.Functions); i++ {
_func := componentdefinition.Functions[i]
if pythonReservedKeywords[_func.FunctionName] {
return ReservedKeywordExit(pythonBindingFile, "Function type definition uses a reserved keyword : %s", _func.FunctionName)
}
w.Writeln("'''Definition of %s", _func.FunctionName)
w.Writeln(" %s", _func.FunctionDescription)
w.Writeln("'''")
Expand Down Expand Up @@ -423,7 +448,9 @@ func writeFunctionTableMethod(method ComponentDefinitionMethod, w LanguageWriter
if err != nil {
return err
}

if pythonReservedKeywords[linearMethodName] {
return ReservedKeywordExit(pythonBindingFile, "Method name uses a reserved keyword : %s", linearMethodName)
}
w.Writeln("err = symbolLookupMethod(ctypes.c_char_p(str.encode(\"%s\")), methodAddress)", linearMethodName)
w.Writeln("if err != 0:")
w.Writeln(" raise E%sException(ErrorCodes.COULDNOTLOADLIBRARY, str(err))", NameSpace)
Expand Down Expand Up @@ -723,7 +750,9 @@ func generateCTypesParameter(param ComponentDefinitionParam, className string, m

func writePythonClass(component ComponentDefinition, class ComponentDefinitionClass, w LanguageWriter, NameSpace string) error {
pythonBaseClassName := fmt.Sprintf("%s", component.Global.BaseClassName)

if pythonReservedKeywords[pythonBaseClassName] {
return ReservedKeywordExit(pythonBindingFile, "Class implementation name uses a reserved keyword : %s", pythonBaseClassName)
}
w.Writeln("''' Class Implementation for %s", class.ClassName)
w.Writeln("'''")

Expand All @@ -737,8 +766,14 @@ func writePythonClass(component ComponentDefinition, class ComponentDefinitionCl
w.Writeln("class %s(%s):", class.ClassName, parentClass)
w.Writeln(" def __init__(self, handle, wrapper):")
w.Writeln(" %s.__init__(self, handle, wrapper)", parentClass)
if pythonReservedKeywords[class.ClassName] || pythonReservedKeywords[parentClass] {
return ReservedKeywordExit(pythonBindingFile, "Class implementation name uses a reserved keyword : %s, %s", class.ClassName, parentClass)
}

} else {
if pythonReservedKeywords[class.ClassName] {
return ReservedKeywordExit(pythonBindingFile, "Class implementation name uses a reserved keyword : %s", class.ClassName)
}
w.Writeln("class %s:", class.ClassName)
w.Writeln(" def __init__(self, handle, wrapper):")
w.Writeln(" if not handle or not wrapper:")
Expand Down Expand Up @@ -974,7 +1009,9 @@ func writeMethod(method ComponentDefinitionMethod, w LanguageWriter, NameSpace s
}

exportName := GetCExportName(NameSpace, ClassName, method, isGlobal)

if pythonReservedKeywords[method.MethodName] {
return ReservedKeywordExit(pythonBindingFile, "Method name uses a reserved keyword : %s", method.MethodName)
}
w.Writeln(" def %s(self%s):", method.MethodName, pythonInParams)
w.Writelns(" ", preCallLines)
if (doCheckCall) {
Expand Down

0 comments on commit 13f9ced

Please sign in to comment.