[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