Commits

Armin Rigo committed 02aa72e

Add support for constants, either via "static sometype const x;"
or via "#define X ...".

Comments (0)

Files changed (6)

 import pycparser, weakref, re
 
 _r_comment = re.compile(r"/\*.*?\*/|//.*?$", re.DOTALL | re.MULTILINE)
+_r_define  = re.compile(r"^\s*#\s*define\s+([A-Za-z_][A-Za-z_0-9]*)\s+(.*?)$",
+                        re.MULTILINE)
 _r_partial_enum = re.compile(r"\.\.\.\s*\}")
 _r_enum_dotdotdot = re.compile(r"__dotdotdot\d+__$")
 _parser_cache = None
     # Remove comments.  NOTE: this only work because the cdef() section
     # should not contain any string literal!
     csource = _r_comment.sub(' ', csource)
+    # Remove the "#define FOO x" lines
+    macros = {}
+    for match in _r_define.finditer(csource):
+        macroname, macrovalue = match.groups()
+        macros[macroname] = macrovalue
+    csource = _r_define.sub('', csource)
     # Replace "...}" with "__dotdotdotNUM__}".  This construction should
     # occur only at the end of enums; at the end of structs we have "...;}"
     # and at the end of vararg functions "...);"
                                              csource[p+3:])
     # Replace all remaining "..." with the same name, "__dotdotdot__",
     # which is declared with a typedef for the purpose of C parsing.
-    return csource.replace('...', ' __dotdotdot__ ')
+    return csource.replace('...', ' __dotdotdot__ '), macros
 
 class Parser(object):
     def __init__(self):
             if name.startswith('typedef '):
                 csourcelines.append('typedef int %s;' % (name[8:],))
         csourcelines.append('typedef int __dotdotdot__;')
-        csourcelines.append(_preprocess(csource))
+        csource, macros = _preprocess(csource)
+        csourcelines.append(csource)
         csource = '\n'.join(csourcelines)
         ast = _get_parser().parse(csource)
-        return ast
+        return ast, macros
 
     def parse(self, csource):
-        ast = self._parse(csource)
+        ast, macros = self._parse(csource)
+        # add the macros
+        for key, value in macros.items():
+            value = value.strip()
+            if value != '...':
+                raise api.CDefError('only supports the syntax "#define '
+                                    '%s ..." for now (literally)' % key)
+            self._declare('macro ' + key, value)
         # find the first "__dotdotdot__" and use that as a separator
         # between the repeated typedefs and the real csource
         iterator = iter(ast.ext)
             #
             if decl.name:
                 tp = self._get_type(node)
-                if 'const' in decl.quals:
+                if self._is_constant_declaration(node):
                     self._declare('constant ' + decl.name, tp)
                 else:
                     self._declare('variable ' + decl.name, tp)
 
     def parse_type(self, cdecl, force_pointer=False):
-        ast = self._parse('void __dummy(%s);' % cdecl)
+        ast, macros = self._parse('void __dummy(%s);' % cdecl)
+        assert not macros
         typenode = ast.ext[-1].type.args.params[0].type
         return self._get_type(typenode, force_pointer=force_pointer)
 
             const = (isinstance(typenode.type, pycparser.c_ast.TypeDecl)
                      and 'const' in typenode.type.quals)
             return self._get_type_pointer(self._get_type(typenode.type), const)
-                                          
         #
         if isinstance(typenode, pycparser.c_ast.TypeDecl):
             type = typenode.type
         result = self._get_type(typenode.type)
         return model.FunctionType(tuple(args), result, ellipsis)
 
+    def _is_constant_declaration(self, typenode, const=False):
+        if isinstance(typenode, pycparser.c_ast.ArrayDecl):
+            return self._is_constant_declaration(typenode.type)
+        if isinstance(typenode, pycparser.c_ast.PtrDecl):
+            const = 'const' in typenode.quals
+            return self._is_constant_declaration(typenode.type, const)
+        if isinstance(typenode, pycparser.c_ast.TypeDecl):
+            return const or 'const' in typenode.quals
+        return False
+
     def _get_struct_or_union_type(self, kind, type, typenode=None):
         # First, a level of caching on the exact 'type' node of the AST.
         # This is obscure, but needed because pycparser "unrolls" declarations
             tp.partial = False
 
     # ----------
+    # macros: for now only for integers
+
+    def generate_cpy_macro_decl(self, tp, name):
+        assert tp == '...'
+        self._generate_cpy_const(True, name)
+
+    generate_cpy_macro_method = generate_nothing
+    loading_cpy_macro = loaded_noop
+    loaded_cpy_macro  = loaded_noop
+
+    # ----------
 
 cffimod_header = r'''
 #include <Python.h>

doc/source/index.rst

    with structs, an ``enum`` that does not end in ``...`` is assumed to
    be exact, and this is checked.
 
+*  integer macros: you can write in the ``cdef`` the line
+   "``#define FOO ...``", with any macro name FOO.  Provided the macro
+   is defined to be an integer value, this value will be available via
+   an attribute of the library object returned by ``verify()``.  The
+   same effect can be achieved by writing a declaration
+   ``static const int FOO;``.  The latter is more general because it
+   supports other types than integer types (note: the syntax is then
+   to write the ``const`` together with the variable name, as in
+   ``static char *const FOO;``).
+
 
 Working with pointers, structures and arrays
 --------------------------------------------

testing/backend_tests.py

 
     def test_new_struct_containing_array_varsize(self):
         py.test.skip("later?")
-        ffi = FFI(backend=_ffi_backend)
+        ffi = FFI(backend=self.Backend())
         ffi.cdef("struct foo_s { int len; short data[]; };")
         p = ffi.new("struct foo_s", 10)     # a single integer is the length
         assert p.len == 0

testing/test_parsing.py

-from cffi import FFI
+import py
+from cffi import FFI, CDefError
 
 class FakeBackend(object):
 
     func = m.sin
     assert func.name == 'sin'
     assert func.BType == '<func (<double>, <double>), <double>, False>'
+
+def test_define_not_supported_for_now():
+    ffi = FFI(backend=FakeBackend())
+    e = py.test.raises(CDefError, ffi.cdef, "#define FOO 42")
+    assert str(e.value) == \
+           'only supports the syntax "#define FOO ..." for now (literally)'

testing/test_verify.py

         assert lib.AA == value
         assert type(lib.AA) is type(int(lib.AA))
 
+def test_global_constants_non_int():
+    ffi = FFI()
+    ffi.cdef("static char *const PP;")
+    lib = ffi.verify('static char *const PP = "testing!";\n')
+    assert ffi.typeof(lib.PP) == ffi.typeof("char *")
+    assert str(lib.PP) == "testing!"
+
 def test_nonfull_enum():
     ffi = FFI()
     ffi.cdef("enum ee { EE1, EE2, EE3, ... \n \t };")
     ffi.errno = 15
     assert lib.foo(6) == 42
     assert ffi.errno == 16
+
+def test_define_int():
+    ffi = FFI()
+    ffi.cdef("#define FOO ...\n"
+             "#define BAR ...")
+    lib = ffi.verify("#define FOO 42\n"
+                     "#define BAR (-44)\n")
+    assert lib.FOO == 42
+    assert lib.BAR == -44