Author: Armin Rigo <[email protected]>
Branch: 
Changeset: r2929:cc66a9e5ae92
Date: 2017-05-09 17:32 +0200
http://bitbucket.org/cffi/cffi/changeset/cc66a9e5ae92/

Log:    Issue 314

        Fix the line numbers by discovering that ``# NUMBER "LINE"`` is
        supported by pycparser and using it.

diff --git a/cffi/cparser.py b/cffi/cparser.py
--- a/cffi/cparser.py
+++ b/cffi/cparser.py
@@ -258,15 +258,21 @@
                 ctn.discard(name)
         typenames += sorted(ctn)
         #
-        csourcelines = ['typedef int %s;' % typename for typename in typenames]
+        csourcelines = []
+        csourcelines.append('# 1 "<cdef automatic initialization code>"')
+        for typename in typenames:
+            csourcelines.append('typedef int %s;' % typename)
         csourcelines.append('typedef int __dotdotdotint__, __dotdotdotfloat__,'
                             ' __dotdotdot__;')
+        # this forces pycparser to consider the following in the file
+        # called <cdef source string> from line 1
+        csourcelines.append('# 1 "<cdef source string>"')
         csourcelines.append(csource)
-        csource = '\n'.join(csourcelines)
+        fullcsource = '\n'.join(csourcelines)
         if lock is not None:
             lock.acquire()     # pycparser is not thread-safe...
         try:
-            ast = _get_parser().parse(csource)
+            ast = _get_parser().parse(fullcsource)
         except pycparser.c_parser.ParseError as e:
             self.convert_pycparser_error(e, csource)
         finally:
@@ -276,17 +282,17 @@
         return ast, macros, csource
 
     def _convert_pycparser_error(self, e, csource):
-        # xxx look for ":NUM:" at the start of str(e) and try to interpret
-        # it as a line number
+        # xxx look for "<cdef source string>:NUM:" at the start of str(e)
+        # and interpret that as a line number.  This will not work if
+        # the user gives explicit ``# NUM "FILE"`` directives.
         line = None
         msg = str(e)
-        if msg.startswith(':') and ':' in msg[1:]:
-            linenum = msg[1:msg.find(':',1)]
-            if linenum.isdigit():
-                linenum = int(linenum, 10)
-                csourcelines = csource.splitlines()
-                if 1 <= linenum <= len(csourcelines):
-                    line = csourcelines[linenum-1]
+        match = re.match(r"<cdef source string>:(\d+):", msg)
+        if match:
+            linenum = int(match.group(1), 10)
+            csourcelines = csource.splitlines()
+            if 1 <= linenum <= len(csourcelines):
+                line = csourcelines[linenum-1]
         return line
 
     def convert_pycparser_error(self, e, csource):
diff --git a/doc/source/cdef.rst b/doc/source/cdef.rst
--- a/doc/source/cdef.rst
+++ b/doc/source/cdef.rst
@@ -248,6 +248,16 @@
         );
       """))
 
+Note also that pycparser, the underlying C parser, recognizes
+preprocessor-like directives in the following format: ``# NUMBER
+"FILE"``.  For example, if you put ``# 42 "foo.h"`` in the middle of the
+string passed to ``cdef()`` and there is an error two lines later, then
+it is reported with an error message that starts with ``foo.h:43:`` (the
+line which is given the number 42 is the line immediately after the
+directive).  *New in version 1.11:*  CFFI automatically puts the line
+``# 1 "<cdef source string>"`` just before the string you give to
+``cdef()``.
+
 
 .. _`ffi.set_unicode()`:
 
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
@@ -234,7 +234,20 @@
 def test_parse_error():
     ffi = FFI()
     e = py.test.raises(CDefError, ffi.cdef, " x y z ")
-    assert re.match(r'cannot parse "x y z"\n:\d+:', str(e.value))
+    assert str(e.value).startswith(
+        'cannot parse "x y z"\n<cdef source string>:1:')
+    e = py.test.raises(CDefError, ffi.cdef, "\n\n\n x y z ")
+    assert str(e.value).startswith(
+        'cannot parse "x y z"\n<cdef source string>:4:')
+
+def test_error_custom_lineno():
+    ffi = FFI()
+    e = py.test.raises(CDefError, ffi.cdef, """
+# 42 "foobar"
+
+    a b c d
+    """)
+    assert str(e.value).startswith('parse error\nfoobar:43:')
 
 def test_cannot_declare_enum_later():
     ffi = FFI()
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to