Skip to content

Commit

Permalink
Merge pull request #12 from sdilts/fix/c-func-capitalization
Browse files Browse the repository at this point in the history
Fix transformation of C names that have capitalized letters
  • Loading branch information
sdilts authored Jan 19, 2025
2 parents dead9ed + 032f114 commit fe2c644
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 23 deletions.
3 changes: 0 additions & 3 deletions cl_bindgen/__main__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import sys

from cl_bindgen.processfile import ProcessOptions
from cl_bindgen.macro_util import macro_matches_file_path
import cl_bindgen.mangler as mangler
import cl_bindgen.util as util

def main():
Expand Down
31 changes: 28 additions & 3 deletions cl_bindgen/mangler.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"""

import re
import io

class PrefixMangler:
""" Mangler to replace the prefix of a string wtih a given string."""
Expand Down Expand Up @@ -60,14 +61,15 @@ def mangle(self, string):
if ':' in string:
# Remove any possible package prefix and save it:
(pkg_prefix, colon, symb) = string.rpartition(':')
return pkg_prefix + ':+' + symb + '+'
result = pkg_prefix + ':+' + symb + '+'
else:
return "+" + string + "+"
result = "+" + string + "+"
return result.lower()

class UnderscoreMangler:
""" Converts underscores to dashes"""

def __init(self):
def __init__(self):
pass

def can_mangle(self, string):
Expand All @@ -92,3 +94,26 @@ def can_mangle(self, string):

def mangle(self, string):
return re.sub(self.regex, self.replace, string)

class CamelCaseConverter:
"""" Convert camelCase and TitleCase to common lisp case.
Doesn't seperate consecutive capitilzations into spaces, i.e.
IString gets converted into istring, ThingDNE gets converted
to thing-dne
"""
def __init__(self):
pass

def can_mangle(self, string):
return len(string) > 0

def mangle(self, string):
builder = io.StringIO()
builder.write(string[0].lower())
for i in range(1, len(string)):
cur_char = string[i]
if cur_char.isupper() and not string[i-1].isupper():
builder.write('-')
builder.write(cur_char.lower())
return builder.getvalue()
24 changes: 12 additions & 12 deletions cl_bindgen/processfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ def _output_unknown_macro_def(spelling, cursor, output):
output.write("|#\n\n")

def _process_macro_def(cursor, data, output, options):
spelling = _mangle_string(cursor.spelling.lower(), options.constant_manglers)
spelling = _mangle_string(cursor.spelling, options.constant_manglers)
# first token is always the macro name, so we can skip it.
tokens = [i for i in itertools.islice(cursor.get_tokens(), 1, 3)]
if len(tokens) > 1:
Expand All @@ -286,7 +286,7 @@ def _extract_record_fields(name, cursor, text_stream, output, options):
this_type = cursor.type
anon_count = 0
for field in this_type.get_fields():
field_name = _mangle_string(field.spelling.lower(), options.name_manglers)
field_name = _mangle_string(field.spelling, options.name_manglers)
if _is_anonymous_record_decl(field):
field_name = 'anon-' + str(anon_count)
anon_count = anon_count + 1
Expand Down Expand Up @@ -320,15 +320,15 @@ def _process_record(name, actual_type, cursor, output, options):
def _process_struct_decl(cursor, data, output, options):
name = cursor.spelling
if name:
mangled_name = _mangle_string(name.lower(), options.type_manglers)
mangled_name = _mangle_string(name, options.type_manglers)
_process_record(mangled_name, _ElaboratedType.STRUCT, cursor, output, options)
else:
data.skipped_records[cursor.hash] = (_ElaboratedType.STRUCT, cursor)

def _process_union_decl(cursor, data, output, options):
name = cursor.spelling
if name:
mangled_name = _mangle_string(name.lower(), options.type_manglers)
mangled_name = _mangle_string(name, options.type_manglers)
_process_record(mangled_name, _ElaboratedType.UNION, cursor, output, options)
else:
data.skipped_records[cursor.hash] = (_ElaboratedType.UNION, cursor)
Expand All @@ -337,27 +337,27 @@ def _process_realized_enum(name, cursor, output, options):
output.write(f"(cffi:defcenum {name}")
_output_comment(cursor, output, before='\n',after='')
for field in cursor.get_children():
name = _mangle_string(field.spelling.lower(), options.enum_manglers)
name = _mangle_string(field.spelling, options.enum_manglers)

output.write(f"\n ({name} {field.enum_value})")
output.write(")\n\n")

def _process_enum_as_constants(cursor, output, options):
for field in cursor.get_children():
field_name = _mangle_string(field.spelling.lower(), options.constant_manglers)
field_name = _mangle_string(field.spelling, options.constant_manglers)
output.write(f"(defconstant {field_name} {field.enum_value})\n")
output.write("\n")

def _process_enum_decl(cursor, data, output, options):
name = cursor.spelling
if name:
name = _mangle_string(name.lower(), options.type_manglers)
name = _mangle_string(name, options.type_manglers)
_process_realized_enum(name, cursor, output, options)
else:
data.skipped_enums[cursor.hash] = cursor

def _process_func_decl(cursor, data, output, options):
name = cursor.spelling.lower()
name = cursor.spelling
# mangle function names the same way as typenames:
mangled_name = _mangle_string(name, options.type_manglers)

Expand All @@ -378,7 +378,7 @@ def _process_func_decl(cursor, data, output, options):
if arg_name == "" or arg_name == None:
arg_name = "unknown"
else:
arg_name = arg.spelling.lower()
arg_name = arg.spelling

arg_type_name = _cursor_lisp_type_str(arg.type, options)
arg_mangled_name = _mangle_string(arg_name, options.name_manglers)
Expand Down Expand Up @@ -414,20 +414,20 @@ def _expand_skipped_type(name, s_type, data, output, options):


def _process_typedef_decl(cursor, data, output, options):
name = cursor.spelling.lower()
name = cursor.spelling
underlying_type = cursor.underlying_typedef_type
base_type_name = _expand_skipped_type(name, underlying_type, data, output, options)
if not base_type_name:
base_type_name = _cursor_lisp_type_str(underlying_type, options)
mangled_name = _mangle_string(cursor.spelling.lower(),
mangled_name = _mangle_string(cursor.spelling,
options.typedef_manglers)
output.write(f"(cffi:defctype {mangled_name} {base_type_name})\n\n")

def _no_op(cursor, data, output, options):
pass

def _process_var_decl(cursor, data, output, options):
name = cursor.spelling.lower()
name = cursor.spelling
underlying_type = cursor.type
base_type_name = _expand_skipped_type(name, underlying_type, data, output, options)
if not base_type_name:
Expand Down
9 changes: 5 additions & 4 deletions cl_bindgen/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@ def build_default_options():
u_mangler = mangler.UnderscoreMangler()
k_mangler = mangler.KeywordMangler()
const_mangler = mangler.ConstantMangler()
case_mangler = mangler.CamelCaseConverter()

# manglers are applied in the order that they are given in these lists:
# enum manglers transform enum fields, e.g. FOO, BAR in enum { FOO, BAR }
enum_manglers = [k_mangler, u_mangler]
enum_manglers = [case_mangler, k_mangler, u_mangler]
# type mangers are applied to struct names, function names, and type names
type_manglers = [u_mangler]
type_manglers = [case_mangler, u_mangler]
# name manglers are applied to parameters and variables
name_manglers = [u_mangler]
name_manglers = [case_mangler, u_mangler]
# typedef manglers are applied to typedefs
typedef_manglers = [u_mangler]
typedef_manglers = [case_mangler, u_mangler]
constant_manglers = [u_mangler, const_mangler]

return processfile.ProcessOptions(typedef_manglers=typedef_manglers,
Expand Down
17 changes: 17 additions & 0 deletions test/inputs/capitalization.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

struct Foo {
int aBear;
int B;
};

int camelCaseFunctionName(int Test);

int TitleCaseFunction(int TestFoo);

int stringDNE();

void IString();

int snake_case_function(double weird_name);

int SDL_testThing();
20 changes: 20 additions & 0 deletions test/outputs/capitalization.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
;; next section imported from file inputs/capitalization.h

(cffi:defcstruct foo
(a-bear :int)
(b :int))

(cffi:defcfun ("camelCaseFunctionName" camel-case-function-name) :int
(test :int))

(cffi:defcfun ("TitleCaseFunction" title-case-function) :int
(test-foo :int))

(cffi:defcfun ("stringDNE" string-dne) :int)

(cffi:defcfun ("IString" istring) :void)

(cffi:defcfun ("snake_case_function" snake-case-function) :int
(weird-name :double))

(cffi:defcfun ("SDL_testThing" sdl-test-thing) :int)
3 changes: 2 additions & 1 deletion test/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ def gen_fn(inputfile, outputfile):
('inputs/typedef.h', 'outputs/typedef.lisp', {}),
('inputs/constant_array_in_struct.h', 'outputs/constant-array-in-struct.lisp', {}),
('inputs/standard_types.h', 'outputs/standard_types.lisp', {}),
('inputs/forward_declaration.h', 'outputs/forward_declaration.lisp', {})
('inputs/forward_declaration.h', 'outputs/forward_declaration.lisp', {}),
('inputs/capitalization.h', 'outputs/capitalization.lisp', {})
]

def test_file_generation():
Expand Down

0 comments on commit fe2c644

Please sign in to comment.