[pypy-commit] cffi cmacros: #if defined() now might work for functions if you dont look too hard

Alex Stapleton noreply at buildbot.pypy.org
Thu Jul 30 22:41:23 CEST 2015


Author: Alex Stapleton <alex at lyst.com>
Branch: cmacros
Changeset: r2232:fb67276a8bd2
Date: 2015-07-25 14:48 +0100
http://bitbucket.org/cffi/cffi/changeset/fb67276a8bd2/

Log:	#if defined() now might work for functions if you dont look too hard

diff --git a/cffi/cparser.py b/cffi/cparser.py
--- a/cffi/cparser.py
+++ b/cffi/cparser.py
@@ -24,7 +24,8 @@
 _r_words = re.compile(r"\w+|\S")
 _parser_cache = None
 _r_int_literal = re.compile(r"-?0?x?[0-9a-f]+[lu]*$", re.IGNORECASE)
-_r_c_macro = re.compile(r"^\s*#")
+_r_enter_c_macro = re.compile(r"^\s*#\s*(if|elif|ifdef|ifndef|else)\s*(.*)")
+_r_exit_c_macro = re.compile(r"^\s*#\s*(endif)")
 
 def _get_parser():
     global _parser_cache
@@ -36,12 +37,6 @@
     # 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 "[__dotdotdotarray__]"
     csource = _r_partial_array.sub('[__dotdotdotarray__]', csource)
     # Replace "...}" with "__dotdotdotNUM__}".  This construction should
@@ -63,7 +58,7 @@
                                                  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__ '), macros
+    return csource.replace('...', ' __dotdotdot__ ')
 
 def _common_type_names(csource):
     # Look in the source for what looks like usages of types from the
@@ -105,22 +100,51 @@
         self._recomplete = []
         self._uses_new_feature = None
 
-    def _extract_macros(self, csource):
+    def _extract_ifdefs(self, csource):
         """
-        Extract macros from csource.
-
-        :returns: [(ln, macro), ...]
+        Extract macros from csource. (Also defines.)
         """
 
-        macros = []
+        current = []
+        continuing = False
+
+        stack = []
+        ifdefs = []
+        defines = []
 
         for num, line in enumerate(csource.splitlines()):
-            if _r_c_macro.match(line):
-                macros.append((num, line))
+            if continuing or _r_enter_c_macro.match(line):
+                line_condition = ""
+                current.append(line)
+                continuing = line.endswith("\\")
 
-        return macros
+                if not continuing:
+                    macro = "".join(current)
+                    match = _r_enter_c_macro.match(macro)
 
-    def _clean_macros(self, csource):
+                    if match.group(1) == "else":
+                        stack.append("!({0})".format(stack.pop()))
+                    else:
+                        stack.append(match.group(2))
+
+                    current = []
+
+            elif _r_exit_c_macro.match(line) and stack:
+                line_condition = ""
+                stack.pop()
+
+            else:
+                line_condition = " && ".join(stack)
+
+                if _r_define.match(line):
+                    match = _r_define.match(line)
+                    defines.append((num, match.group(1), match.group(2)))
+
+            ifdefs.append(line_condition)
+
+        return ifdefs, defines
+
+    def _clean_ifdefs(self, csource):
         """
         Remove macros from the csource
 
@@ -130,7 +154,7 @@
         cleaned = []
 
         for line in csource.splitlines():
-            if _r_c_macro.match(line):
+            if _r_enter_c_macro.match(line) or _r_exit_c_macro.match(line) or _r_define.match(line):
                 cleaned.append("")
             else:
                 cleaned.append(line)
@@ -138,7 +162,8 @@
         return '\n'.join(cleaned)
 
     def _parse(self, csource):
-        csource, macros = _preprocess(csource)
+        csource = _preprocess(csource)
+
         # XXX: for more efficiency we would need to poke into the
         # internals of CParser...  the following registers the
         # typedefs, because their presence or absence influences the
@@ -151,12 +176,14 @@
                 typenames.append(name)
                 ctn.discard(name)
         typenames += sorted(ctn)
-        #
+
         csourcelines = ['typedef int %s;' % typename for typename in typenames]
         csourcelines.append('typedef int __dotdotdot__;')
         csourcelines.append(csource)
+        csource = '\n'.join(csourcelines)
 
-        csource = '\n'.join(csourcelines)
+        ifdefs, defines = self._extract_ifdefs(csource)
+        csource = self._clean_ifdefs(csource)
 
         if lock is not None:
             lock.acquire()     # pycparser is not thread-safe...
@@ -168,7 +195,7 @@
             if lock is not None:
                 lock.release()
         # csource will be used to find buggy source text
-        return ast, macros, csource
+        return ast, defines, csource, ifdefs
 
     def _convert_pycparser_error(self, e, csource):
         # xxx look for ":NUM:" at the start of str(e) and try to interpret
@@ -206,9 +233,13 @@
             self._packed = prev_packed
 
     def _internal_parse(self, csource):
-        ast, macros, csource = self._parse(csource)
-        # add the macros
-        self._process_macros(macros)
+        ast, defines, csource, ifdefs = self._parse(csource)
+
+        self._ifdefs = ifdefs
+
+        # add the defines
+        self._process_defines(defines)
+
         # find the first "__dotdotdot__" and use that as a separator
         # between the repeated typedefs and the real csource
         iterator = iter(ast.ext)
@@ -235,7 +266,9 @@
                         realtype = model.unknown_ptr_type(decl.name)
                     else:
                         realtype = self._get_type(decl.type, name=decl.name)
-                    self._declare('typedef ' + decl.name, realtype)
+
+                    ifdef = self._ifdefs[decl.coord.line - 1]
+                    self._declare(('typedef', decl.name, ifdef), realtype)
                 else:
                     raise api.CDefError("unrecognized construct", decl)
         except api.FFIError as e:
@@ -267,13 +300,13 @@
         self._add_constants(name, pyvalue)
         self._declare('macro ' + name, pyvalue)
 
-    def _process_macros(self, macros):
-        for key, value in macros.items():
+    def _process_defines(self, defines):
+        for ln, key, value in defines:
             value = value.strip()
             if _r_int_literal.match(value):
                 self._add_integer_constant(key, value)
-            elif value == '...':
-                self._declare('macro ' + key, value)
+            elif value == '__dotdotdot__':
+                self._declare(('macro', key), value)
             else:
                 raise api.CDefError(
                     'only supports one of the following syntax:\n'
@@ -285,12 +318,14 @@
                     % (key, key, key, value))
 
     def _parse_decl(self, decl):
+        ifdef = self._ifdefs[decl.coord.line - 1]
         node = decl.type
+
         if isinstance(node, pycparser.c_ast.FuncDecl):
             tp = self._get_type(node, name=decl.name)
             assert isinstance(tp, model.RawFunctionType)
             tp = self._get_type_pointer(tp)
-            self._declare('function ' + decl.name, tp)
+            self._declare(('function', decl.name, ifdef), tp)
         else:
             if isinstance(node, pycparser.c_ast.Struct):
                 self._get_struct_union_enum_type('struct', node)
@@ -306,7 +341,7 @@
                 tp = self._get_type(node, partial_length_ok=True)
                 if tp.is_raw_function:
                     tp = self._get_type_pointer(tp)
-                    self._declare('function ' + decl.name, tp)
+                    self._declare(('function', decl.name, ifdef), tp)
                 elif (tp.is_integer_type() and
                         hasattr(decl, 'init') and
                         hasattr(decl.init, 'value') and
@@ -320,9 +355,9 @@
                     self._add_integer_constant(decl.name,
                                                '-' + decl.init.expr.value)
                 elif self._is_constant_globalvar(node):
-                    self._declare('constant ' + decl.name, tp)
+                    self._declare(('constant', decl.name, ifdef), tp)
                 else:
-                    self._declare('variable ' + decl.name, tp)
+                    self._declare(('variable', decl.name, ifdef), tp)
 
     def parse_type(self, cdecl):
         ast, macros = self._parse('void __dummy(\n%s\n);' % cdecl)[:2]
@@ -332,16 +367,19 @@
             raise api.CDefError("unknown identifier '%s'" % (exprnode.name,))
         return self._get_type(exprnode.type)
 
-    def _declare(self, name, obj, included=False):
-        if name in self._declarations:
-            if self._declarations[name] is obj:
+    def _declare(self, key, obj, included=False):
+        assert isinstance(key, tuple)
+
+        if key in self._declarations:
+            if self._declarations[key] is obj:
                 return
+
             if not self._override:
                 raise api.FFIError(
-                    "multiple declarations of %s (for interactive usage, "
-                    "try cdef(xx, override=True))" % (name,))
-        assert '__dotdotdot__' not in name.split()
-        self._declarations[name] = obj
+                    "multiple declarations of %s %s (for interactive usage, "
+                    "try cdef(xx, override=True))" % (key[0], key[1]))
+        assert '__dotdotdot__' != key[1]
+        self._declarations[key] = obj
         if included:
             self._included_declarations.add(obj)
 
@@ -520,7 +558,7 @@
             tp = None
         else:
             explicit_name = name
-            key = '%s %s' % (kind, name)
+            key = (kind, name)
             tp = self._declarations.get(key, None)
         #
         if tp is None:
diff --git a/cffi/recompiler.py b/cffi/recompiler.py
--- a/cffi/recompiler.py
+++ b/cffi/recompiler.py
@@ -10,6 +10,19 @@
     int_type = int
 
 
+class Ifdef:
+    def __init__(self, ifdef, child):
+        self.name = child.name
+        self._ifdef = ifdef
+        self._child = child
+
+    def as_c_expr(self):
+        return """\
+        #if {0}
+        {1}
+        #endif
+        """.format(self._ifdef, self._child.as_c_expr())
+
 class GlobalExpr:
     def __init__(self, name, address, type_op, size=0, check_value=0):
         self.name = name
@@ -210,18 +223,20 @@
         return sorted(self.ffi._parser._declarations.items())
 
     def _generate(self, step_name):
-        for name, tp in self._get_declarations():
-            kind, realname = name.split(' ', 1)
+        for key, tp in self._get_declarations():
+            kind, realname, ifdef = key
+
             try:
                 method = getattr(self, '_generate_cpy_%s_%s' % (kind,
                                                                 step_name))
             except AttributeError:
                 raise ffiplatform.VerificationError(
-                    "not implemented in recompile(): %r" % name)
+                    "not implemented in recompile(): %r %r" % key[:2])
+
             try:
-                method(tp, realname)
+                method(tp, realname, ifdef)
             except Exception as e:
-                model.attach_exception_info(e, name)
+                model.attach_exception_info(e, key)
                 raise
 
     # ----------
@@ -356,7 +371,10 @@
                 prnt('  NULL,  /* no %ss */' % step_name)
         for step_name in self.ALL_STEPS:
             if step_name != "field":
-                prnt('  %d,  /* num_%ss */' % (nums[step_name], step_name))
+                if nums[step_name]:
+                    prnt('  sizeof(_cffi_{0}s)/sizeof(_cffi_{0}s[0]), /* num_{0}s */'.format(step_name))
+                else:
+                    prnt('  0, /* num_{0}s */'.format(step_name))
         if self.ffi._included_ffis:
             prnt('  _cffi_includes,')
         else:
@@ -574,12 +592,12 @@
     # ----------
     # function declarations
 
-    def _generate_cpy_function_collecttype(self, tp, name):
+    def _generate_cpy_function_collecttype(self, tp, name, ifdef):
         self._do_collect_type(tp.as_raw_function())
         if tp.ellipsis and not self.target_is_python:
             self._do_collect_type(tp)
 
-    def _generate_cpy_function_decl(self, tp, name):
+    def _generate_cpy_function_decl(self, tp, name, ifdef):
         assert not self.target_is_python
         assert isinstance(tp, model.FunctionPtrType)
         if tp.ellipsis:
@@ -596,6 +614,9 @@
             argname = 'arg0'
         else:
             argname = 'args'
+
+        prnt('#if {0}'.format(ifdef))
+
         #
         # ------------------------------
         # the 'd' version of the function, only for addressof(lib, 'func')
@@ -724,9 +745,10 @@
             prnt('#  define _cffi_f_%s _cffi_d_%s' % (name, name))
         #
         prnt('#endif')        # ------------------------------
+        prnt('#endif')
         prnt()
 
-    def _generate_cpy_function_ctx(self, tp, name):
+    def _generate_cpy_function_ctx(self, tp, name, ifdef):
         if tp.ellipsis and not self.target_is_python:
             self._generate_cpy_constant_ctx(tp, name)
             return
@@ -741,9 +763,12 @@
         else:
             meth_kind = OP_CPYTHON_BLTN_V   # 'METH_VARARGS'
         self._lsts["global"].append(
-            GlobalExpr(name, '_cffi_f_%s' % name,
-                       CffiOp(meth_kind, type_index),
-                       size='_cffi_d_%s' % name))
+            Ifdef(ifdef,
+                GlobalExpr(name, '_cffi_f_%s' % name,
+                    CffiOp(meth_kind, type_index),
+                    size='_cffi_d_%s' % name)
+            )
+        )
 
     # ----------
     # named structs or unions
diff --git a/testing/cffi0/test_parsing.py b/testing/cffi0/test_parsing.py
--- a/testing/cffi0/test_parsing.py
+++ b/testing/cffi0/test_parsing.py
@@ -143,36 +143,59 @@
         BType = ffi._get_cached_btype(type)
     assert str(BType) == '<func (<pointer to <int>>), <int>, False>'
 
-def test_extract_macros():
+def test_extract_ifdefs():
     parser = Parser()
-    macros = parser._extract_macros("""
+
+    macros = parser._extract_ifdefs("""
+    #if FOO
+    int q;
+    #else
     int x;
-    #if defined(FOO)
+    #if BAR
     int y;
     #endif
+    #endif
     int z;
     """)
 
     assert macros == [
-        (2, "    #if defined(FOO)"),
-        (4, "    #endif")
+        '',
+        '',
+        'FOO',
+        '',
+        '!(FOO)',
+        '',
+        '!(FOO) && BAR',
+        '',
+        '',
+        '',
+        ''
     ]
 
-def test_clean_macros():
+
+def test_clean_ifdefs():
     parser = Parser()
-    clean = parser._clean_macros("""
+    clean = parser._clean_ifdefs("""
+    #if FOO
+    int q;
+    #else
     int x;
-    #if defined(FOO)
+    #if BAR
     int y;
     #endif
+    #endif
     int z;
     """)
 
     assert clean == """
+
+    int q;
+
     int x;
 
     int y;
 
+
     int z;
     """
 
diff --git a/testing/cffi1/test_recompiler.py b/testing/cffi1/test_recompiler.py
--- a/testing/cffi1/test_recompiler.py
+++ b/testing/cffi1/test_recompiler.py
@@ -116,6 +116,17 @@
                      "(FUNCTION 1)(POINTER 3)(FUNCTION_END 0)(STRUCT_UNION 0)",
                      included="struct foo_s { int x, y; };")
 
+def test_math_sin_ifdef():
+    import math
+    ffi = FFI()
+    ffi.cdef("""
+    #if defined(MATH_PLEASE)
+    float sinxxx(double);
+    double cos(double);
+    #endif
+    """)
+    lib = verify(ffi, 'test_math_sin_ifdef', '#include <math.h>')
+    assert not hasattr(lib, "cos")
 
 def test_math_sin():
     import math


More information about the pypy-commit mailing list