Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,9 @@ Optional Fields:
`pkg-config --cflags`
+ `pointer-expansion` (experimental): Used to provide either a regex
or a list of pointer types to expand or not expand in the output.
+ `enum-expansion`: By default, cl-bindgen does not expand enum types
into their converted lisp type, and instead converts them to their
underlying type. This allows you to specify which enums should get
expanded to their lisp type.
+ `enum-constants`: By default, cl-bindgen expands enum value
symbols into keywords. Types captured here will be expanded to
constants instead.
+ `make-inline` (experimental): Used to provide either a regex
or a list of names matching functions that should be declared
`inline`.
Expand Down
17 changes: 14 additions & 3 deletions cl_bindgen/inclusion_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,26 @@ def make_batch_determiner(whitelist=None, blacklist=None, include_matcher=None,
includer = _compile_regexes(include_matcher)
def fn(typename):
in_white = (typename in allowed_set or _match_regex_list(includer, typename))
not_black = (not _match_regex_list(excluder, typename))
not_black = not (_match_regex_list(excluder, typename) or
typename in blacklist)
return in_white and not_black
return fn
elif exclude_matcher is not None:
excluder = _compile_regexes(exclude_matcher)
return lambda x: x in allowed_set and not _match_regex_list(excluder, x)
def fn(typename):
in_white = typename in allowed_set
not_black = not (_match_regex_list(excluder, typename) or
typename in blacklist)
return in_white and not_black
return fn
elif include_matcher:
includer = _compile_regexes(include_matcher)
return lambda x: x in allowed_set or _match_regex_list(includer, x)
def fn(typename):
in_white = (typename in allowed_set
or _match_regex_list(includer, typename))
not_black = typename not in blacklist
return in_white and not_black
return fn
else:
return lambda x: x in allowed_set
else:
Expand Down
56 changes: 39 additions & 17 deletions cl_bindgen/processfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class ProcessOptions:
default_factory=lambda: lambda s: True)
return_str_p: typing.Callable[[str], bool] = dataclasses.field(
default_factory=lambda: lambda s: False)
expand_enum_p: typing.Callable[[str], bool] = dataclasses.field(
enum_constant_p: typing.Callable[[str], bool] = dataclasses.field(
default_factory=lambda: lambda s: False)

output: str = dataclasses.field(default_factory=lambda: ":stdout")
Expand Down Expand Up @@ -151,18 +151,18 @@ def _explicitly_typed_enum_p(decl: clang.Cursor):
return True
return False

def _emit_enum_type(decl: clang.Cursor, options: ProcessOptions, location: clang.SourceLocation):
mangled_name = _mangle_string(decl.spelling, options.type_manglers)
def _emit_enum_type(decl: clang.Cursor, options: ProcessOptions, location: clang.SourceLocation, name=None):
if name is not None:
mangled_name = name
else:
mangled_name = _mangle_string(decl.spelling, options.type_manglers)
# Because the default underlying enum type is dependent on the
# system and the enum's value, we can't use the underlying type
# and must use the enum as declared unless the underlying type
# was explicity set:
if _explicitly_typed_enum_p(decl):
enum_type = _cursor_lisp_type_str(decl.enum_type, options, location)
if options.expand_enum_p(decl.spelling):
return f"{mangled_name} #| {enum_type} |#"
else:
return f"{enum_type} #| {mangled_name} |#"
return f"{mangled_name} #| {enum_type} |#"
else:
return f"{mangled_name}"

Expand All @@ -172,8 +172,8 @@ def _determine_elaborated_field(field, inner_name, output, options, found_record
actual_elaborated_type = _determine_elaborated_type(field.type)
if actual_elaborated_type == _ElaboratedType.ENUM:
decl = field.type.get_declaration()
_process_enum_as_constants(decl, output, options)
return _emit_enum_type(decl, options, field.location)
_process_realized_enum(inner_name, decl, output, options, as_constants=True)
return _emit_enum_type(decl, options, field.location, name=inner_name)
elif actual_elaborated_type == _ElaboratedType.UNION:
_process_record(inner_name, actual_elaborated_type, field, output, options, found_records)
return "(:union " + inner_name + ")"
Expand Down Expand Up @@ -208,7 +208,7 @@ def _cursor_typedef_str(type_obj, options):
else:
return _mangle_string(type_decl_str, options.typedef_manglers)

def _cursor_lisp_type_str(type_obj, options, location=None):
def _cursor_lisp_type_str(type_obj, options, location=None, field=False):
def process_record_type():
type_decl = type_obj.get_declaration()
mangled_name = _mangle_string(type_decl.spelling, options.type_manglers)
Expand Down Expand Up @@ -245,7 +245,8 @@ def process_record_type():
if named_type_kind == TypeKind.RECORD:
return process_record_type()
elif named_type_kind == TypeKind.ENUM:
return f":int #| {_mangle_string(named_type.spelling, options.type_manglers)} |#"
enum_decl = type_obj.get_declaration()
return _emit_enum_type(enum_decl, options, location)
elif named_type_kind == TypeKind.TYPEDEF:
return _cursor_typedef_str(type_obj, options)
elif kind == TypeKind.RECORD:
Expand All @@ -257,7 +258,10 @@ def process_record_type():
elem_type = type_obj.element_type
num_elems = type_obj.element_count
type_str = _cursor_lisp_type_str(elem_type, options, location)
return f"{type_str} :count {num_elems}"
if field:
return f"{type_str} :count {num_elems}"
else:
return f":pointer #| {type_str} :count {num_elems} |#"
elif kind == TypeKind.FUNCTIONPROTO:
return f":void #| {type_obj.spelling} |#"
elif kind == TypeKind.FUNCTIONNOPROTO:
Expand Down Expand Up @@ -359,7 +363,7 @@ def _extract_record_fields(name, cursor, text_stream, output, options, found_rec
raise ProcessingError("Uknown typekind: " + str(field.type.kind),
cursor.location)
else:
field_type = _cursor_lisp_type_str(field.type, options, cursor.location)
field_type = _cursor_lisp_type_str(field.type, options, cursor.location, field=True)
text_stream.write(f"\n ({field_name} {field_type})")

def _process_record(name, actual_type, cursor, output, options, found_records: set):
Expand Down Expand Up @@ -405,16 +409,24 @@ def _process_union_decl(cursor, data: _ParseData, output, options):
else:
data.skipped_records[cursor.hash] = (_ElaboratedType.UNION, cursor)

def _process_realized_enum(name, cursor, output, options):
def _process_realized_enum(name, cursor, output, options, as_constants=False):
if _explicitly_typed_enum_p(cursor):
type_name = _cursor_lisp_type_str(cursor.enum_type, options, cursor.location)
output.write(f"(cffi:defcenum ({name} {type_name})")
else:
output.write(f"(cffi:defcenum {name}")

_output_comment(cursor, output, before='\n',after='')

if as_constants or options.enum_constant_p(name):
manglers = options.constant_manglers
else:
manglers = options.enum_manglers
for field in cursor.get_children():
name = _mangle_string(field.spelling, options.enum_manglers)
if as_constants:
name = _mangle_string(field.spelling, manglers)
else:
name = _mangle_string(field.spelling, manglers)

output.write(f"\n ({name} {field.enum_value})")
output.write(")\n\n")
Expand All @@ -428,10 +440,20 @@ def _process_enum_as_constants(cursor, output, options):
def _process_enum_decl(cursor, data, output, options):
name = cursor.spelling
if name:
name = _mangle_string(name, options.type_manglers)
_process_realized_enum(name, cursor, output, options)
if cursor.is_anonymous():
# Although we could just emit a constant here,
# CFFI might need to do something to the definition
# because it's an enum. To be safe, emit that too. It won't
# affect the API at all.
name = f'anon-enum-{_process_enum_decl.anon_count}'
_process_enum_decl.anon_count = _process_enum_decl.anon_count + 1
_process_realized_enum(name, cursor, output, options, as_constants=True)
else:
name = _mangle_string(name, options.type_manglers)
_process_realized_enum(name, cursor, output, options, as_constants=False)
else:
data.skipped_enums[cursor.hash] = cursor
_process_enum_decl.anon_count = 0

def _use_string_ret_type(name, ret_type, options):
kind = ret_type.kind
Expand Down
4 changes: 2 additions & 2 deletions cl_bindgen/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def _process_batch_options(option, dictionary):
force = dictionary.get('force')
pkg_config = dictionary.get('pkg-config')
ptr_handling = dictionary.get('pointer-expansion')
enum_handling = dictionary.get('enum-expansion')
enum_handling = dictionary.get('enum-constants')
inline_handling = dictionary.get('make-inline')
return_str = dictionary.get('string-return')
if ptr_handling:
Expand All @@ -77,7 +77,7 @@ def _process_batch_options(option, dictionary):
))
option.declaim_inline_rules.extend(rules)
if enum_handling:
option.expand_enum_p = process_inclusion_rules(enum_handling)
option.enum_constant_p = process_inclusion_rules(enum_handling)
if return_str:
option.return_str_p = process_inclusion_rules(return_str)
if output:
Expand Down
2 changes: 2 additions & 0 deletions integrated/inputs/constant_array_in_param.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

void test(float args[4], int ints[10]);
12 changes: 11 additions & 1 deletion integrated/inputs/enums.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,25 @@ enum test_enum {
TEST_ENUM_FIVE = 5,
};

enum {
ANNON_ENUM_CONSTANT = 20
};

enum typed_enum : short {
TYPED_ENUM_TEST
};

struct outer {
enum test_enum test_enum;
enum typed_enum typed;
enum : short {
enum {
OUTER_INNER_TEN = 10,
OUTER_INNER_OTHER
} inner_enum;
};

typedef enum { TYPEDEF_ENUM_VAL } typedef_enum;

typedef enum inner_typedef_enum {
INNER_TYPEDEF_VAL
} inner_typedef;
5 changes: 5 additions & 0 deletions integrated/outputs/constant-array-in-param.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
;; next section imported from file inputs/constant_array_in_param.h

(cffi:defcfun "test" :void
(args :pointer #| :float :count 4 |#)
(ints :pointer #| :int :count 10 |#))
22 changes: 18 additions & 4 deletions integrated/outputs/enums.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,27 @@
(:test-enum-two 1)
(:test-enum-five 5))

(cffi:defcenum anon-enum-0
(+annon-enum-constant+ 20))

(cffi:defcenum (typed-enum :short)
(:typed-enum-test 0))

(defconstant +outer-inner-ten+ 10)
(defconstant +outer-inner-other+ 11)
(cffi:defcenum outer-inner-enum
(+outer-inner-ten+ 10)
(+outer-inner-other+ 11))

(cffi:defcstruct outer
(test-enum test-enum)
(typed :short #| typed-enum |#)
(inner-enum :short #| enum (unnamed at inputs/enums.h:14:3) |#))
(typed typed-enum #| :short |#)
(inner-enum outer-inner-enum))

(cffi:defcenum typedef-enum
(:typedef-enum-val 0))

(cffi:defctype typedef-enum typedef-enum)

(cffi:defcenum inner-typedef-enum
(:inner-typedef-val 0))

(cffi:defctype inner-typedef inner-typedef-enum)
1 change: 1 addition & 0 deletions integrated/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def gen_fn(inputfile, outputfile):
('inputs/capitalization.h', 'outputs/capitalization.lisp', {}),
('inputs/multiple_forward_decls.h', 'outputs/multiple_forward_decls.lisp', {}),
('inputs/enums.h', 'outputs/enums.lisp', {}),
('inputs/constant_array_in_param.h', 'outputs/constant-array-in-param.lisp', {}),
]

def test_file_generation():
Expand Down
15 changes: 15 additions & 0 deletions test/test_inclusion_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,18 @@ def test_whitelist_blacklist_list_works(self):
actual_allowed = ['cheese', 'curds']
self.assertTrue(all([ result(i) for i in actual_allowed]))
self.assertFalse(result('fish'))

def test_exclude_list_overrides_include_match(self):
banned = ['asdf', 'fdsa']
rules = {
'exclude': {
'names': banned
},
'include': {
'match': '.*'
}
}
result = process_inclusion_rules(rules, 'names')
self.assertTrue(all([ not result(i) for i in banned]))
actual_allowed = ['cheese', 'curds']
self.assertTrue(all([ result(i) for i in actual_allowed]))
Loading