mabuchilab / NiceLib

A Python package for rapidly developing "nice" bindings for C libraries, using cffi
GNU General Public License v3.0
24 stars 8 forks source link

Feature Request: Enhanche `generate_bindings` s `niceobject_prefix` with something like `niceobject_firstarg` #10

Open Tillsten opened 5 years ago

Tillsten commented 5 years ago

For the libraries I wrote a binding, the functions taking a handle are not identifiable by its prefix, but are always identifiable by its first arg. Therefore something like niceobject_firstarg taking the name of the handle arg would be really nice.

natezb commented 5 years ago

I'm not sure exactly what you're asking for, so providing a set of example functions might help me understand. _prefix_ isn't used for grouping functions automatically, it's just something to strip prefixes off of names. Functions are manually grouped as "methods" by creating Sig definitions within a NiceObject subclass. The first arg of these functions is already automatically treated as the handle.

I feel like I'm missing something. Did you instead mean to say that you'd like to identify the handle arg by name rather than position?

Tillsten commented 5 years ago

It could be that I misunderstood the docstring of generate_bindings. My understanding was that niceobj_prefix='Motor' will automatically put all functions with the prefix into a NiceObj. This should work well for example code given below.

// Example header file
typedef void* HANDLE;

int GeneralGetDeviceList(uint* devList, uint listSize);
void GeneralGetErrorString(int errCode, char *recvBuf, uint bufSize);
int GeneralOpenMotor(uint motorID, HANDLE *phMotor);

int MotorClose(HANDLE hMotor);
...

But if the prefix of these function would be also General the approach with the prefix won't work:

// Example header file
typedef void* HANDLE;

int GeneralGetDeviceList(uint* devList, uint listSize);
void GeneralGetErrorString(int errCode, char *recvBuf, uint bufSize);
int GeneralOpenMotor(uint motorID, HANDLE *phMotor);

int GeneralClose(HANDLE hMotor);
...

Instead one could look at the name of first argument to determine if it belongs in the NiceObj subclass. E.g. niceobj_first_arg = {'Motor': 'hMotor'}.

Tillsten commented 5 years ago

By the way, it seems the current version of generate_bindings is not working at all, correct? It calls

header_paths, predef_path = handle_header_path(header_info)

But handle_header_path also requires the basedir.

Tillsten commented 5 years ago

As a stop-gap solution I wrote a simpler binding generator based on the argnames dict of the low-level binding.

natezb commented 5 years ago

I see, I didn't realize you were talking about generate_bindings(). Yes, it's definitely fallen behind the rest of nicelib's development; it doesn't even use the new Sig system. I'll keep this feature in mind when I get around to updating it (and thanks for the example code).

Tillsten commented 5 years ago

Also related, it there a way to access the original type of a function argument? The types which are accessible via cffi are already in a reduced form (e.g. most basic type and missing consts). The original types can be helpful to automate to generate the mid-level bindings. For example the library I am currently rewriting the binding uses const pointer for inputs and normal one for outputs.

natezb commented 5 years ago

If they're not accessible via cffi, then no. I'd be willing to accept a PR that adds this kind of information to the compiled module. Check out the enum branch (e.g. efae58a65b92d2d96caf1f6de32290c477892311) for how you might do this.

The basic idea would be to use a c_ast.NodeVisitor to walk the AST, and extract info from each function and store it in a metadata object (named something like FunctionInfo).

If you do this, it's probably best to work on the enum branch, since there are some changes (namely the addition of the HeaderInfo class) that should make this easier to do.

Tillsten commented 5 years ago

Is it possible to just modify the ArgNameGrabber? My problem is I have no experience with parsing and don't really what kind of node I am looking for.

Playing around, I think it is enough to change the following visitor in ArgNameGrabber from:

    def visit_TypeDecl(self, node):
        return node.declname

to

    def visit_TypeDecl(self, node):
        return node.declname, ' '.join(node.quals) + node.type().names[0])

is enough? But I don't understand why names is a list yet.

natezb commented 5 years ago

You don't really need to know anything about parsing, you just need to traverse an already-parsed AST from pycparser. You can explore the typical structure of a pycparser AST by playing around with some test strings of your own, e.g.

>>> from pycparser.c_parser import CParser
>>> p = CParser()
>>> p.parse('int f(a, b, c);')
FileAST(ext=[Decl(name='f',
                  quals=[
                        ],
                  storage=[
                          ],
                  funcspec=[
                           ],
                  type=FuncDecl(args=ParamList(params=[ID(name='a'
                                                          ),
                                                       ID(name='b'
                                                          ),
                                                       ID(name='c'
                                                          )
                                                      ]
                                               ),
                                type=TypeDecl(declname='f',
                                              quals=[
                                                    ],
                                              type=IdentifierType(names=['int'
                                                                        ]
                                                                  )
                                              )
                                ),
                  init=None,
                  bitsize=None
                  )
            ]
        )

And yes, it would be natural to modify the ArgNameGrabber for this, to make it a more general function info grabber. I would suggest creating a simple FunctionInfo class (similar to EnumInfo) for storing the information in a clearer way.