Repository: inducer/pycparserext

Issue:
GNU C array init with "..." not supported
This is a GNU extension, see [Designated Inits](https://gcc.gnu.org/onlinedocs/gcc/Designated-Inits.html#Designated-Inits) in the GCC manual. However, the GnuCParser doesn't cope:

```
>>> from pycparserext.ext_c_parser import GnuCParser
>>> p=GnuCParser()
>>> p.parse("int widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Program Files (x86)\Python27\lib\site-packages\pycparserext\ext_c_parser.py", line 64, in parse
    return self.cparser.parse(text, lexer=self.clex, debug=debuglevel)
  File "C:\Program Files (x86)\Python27\lib\site-packages\pycparser\ply\yacc.py", line 331, in parse
    return self.parseopt_notrack(input, lexer, debug, tracking, tokenfunc)
  File "C:\Program Files (x86)\Python27\lib\site-packages\pycparser\ply\yacc.py", line 1181, in parseopt_notrack
    tok = call_errorfunc(self.errorfunc, errtoken, self)
  File "C:\Program Files (x86)\Python27\lib\site-packages\pycparser\ply\yacc.py", line 193, in call_errorfunc
    r = errorfunc(token)
  File "C:\Program Files (x86)\Python27\lib\site-packages\pycparser\c_parser.py", line 1721, in p_error
    column=self.clex.find_tok_column(p)))
  File "C:\Program Files (x86)\Python27\lib\site-packages\pycparser\plyparser.py", line 55, in _parse_error
    raise ParseError("%s: %s" % (coord, msg))
pycparser.plyparser.ParseError: :1:21: before: ...

```

Tested with pycparserext 2016.2.

No idea if this is _supposed_ to be supported, but I had an instance of this in my code so I ran into it.


Comments:

Patch:
diff --git a/pycparserext/ext_c_generator.py b/pycparserext/ext_c_generator.py
index 751905a..3ecccad 100644
--- a/pycparserext/ext_c_generator.py
+++ b/pycparserext/ext_c_generator.py
@@ -138,6 +138,9 @@ class GnuCGenerator(AsmAndAttributesMixin, CGeneratorBase):
     def visit_TypeList(self, n):
         return ', '.join(self.visit(ch) for ch in n.types)
 
+    def visit_RangeExpression(self, n):
+        return '%s ... %s' % (self.visit(n.first), self.visit(n.last))
+
 
 class GNUCGenerator(GnuCGenerator):
     def __init__(self):
diff --git a/pycparserext/ext_c_parser.py b/pycparserext/ext_c_parser.py
index 51d9b64..00fad2a 100644
--- a/pycparserext/ext_c_parser.py
+++ b/pycparserext/ext_c_parser.py
@@ -158,6 +158,29 @@ class TypeOfExpression(c_ast.Node):
     attr_names = ()
 
 
+class RangeExpression(c_ast.Node):
+    def __init__(self, first, last, coord=None):
+        self.first = first
+        self.last = last
+        self.coord = coord
+
+    def children(self):
+        nodelist = []
+        if self.first is not None:
+            nodelist.append(("first", self.first))
+        if self.last is not None:
+            nodelist.append(("last", self.last))
+        return tuple(nodelist)
+
+    def __iter__(self):
+        if self.first is not None:
+            yield self.first
+        if self.last is not None:
+            yield self.last
+
+    attr_names = ()
+
+
 # These are the same as pycparser's, but it does *not* declare __slots__--
 # so we can poke in attributes at our leisure.
 class TypeDeclExt(c_ast.TypeDecl):
@@ -512,6 +535,11 @@ class GnuCParser(_AsmAndAttributesMixin, CParserBase):
         """ struct_declaration_list : empty """
         p[0] = None
 
+    def p_range_designator(self, p):
+        """ designator  : LBRACKET constant_expression ELLIPSIS constant_expression RBRACKET
+        """
+        p[0] = RangeExpression(p[2], p[4], coord=self._coord(p.lineno(1)))
+
 # }}}
 
 


Test patch:
diff --git a/test/test_pycparserext.py b/test/test_pycparserext.py
index f415930..0ab21f8 100644
--- a/test/test_pycparserext.py
+++ b/test/test_pycparserext.py
@@ -2,6 +2,53 @@ from __future__ import print_function
 import pytest
 
 
+# Inspired from pycparser's compare_asts test function
+def _compare_asts(first, second):
+    if type(first) is not type(second):
+        return False
+
+    if isinstance(first, tuple):
+        if first[0] != second[0]:
+            return False
+
+        return _compare_asts(first[1], second[1])
+
+    for attr in first.attr_names:
+        if getattr(first, attr) != getattr(second, attr):
+            return False
+
+    for i, c1 in enumerate(first.children()):
+        if not _compare_asts(c1, second.children()[i]):
+            return False
+    return True
+
+
+def _round_trip_matches(src):
+    from pycparserext.ext_c_parser import GnuCParser
+    from pycparserext.ext_c_generator import GnuCGenerator
+
+    p = GnuCParser()
+
+    first_ast = p.parse(src)
+
+    gen = GnuCGenerator().visit(first_ast)
+
+    second_ast = p.parse(gen)
+
+    if not _compare_asts(first_ast, second_ast):
+        print('First AST:')
+        first_ast.show()
+
+        print('Generated code:')
+        print(gen)
+
+        print('Second AST:')
+        second_ast.show()
+
+        return False
+    return True
+
+
 def test_asm_volatile_1():
     src = """
     void read_tsc(void) {
@@ -374,6 +421,23 @@ def test_double_pointer():
     assert gen.visit(ast).find("func_with_p2pp(const char *, Error **)") != -1
 
 
+def test_designated_initializers():
+    src = """
+    int a[6] = { [4] = 29, [2] = 15 };
+
+    int widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };
+
+    int v1 = 5, v2 = 6;
+    int b[] = { [1] = v1, v2, [4] = v4 };
+
+    struct foo { int x; int y; };
+    struct foo bar[10] = { [1].y = 5, [2].x = 1, [0].x = 3 };
+
+    char char_map[256] = { [0 ... 255] = '?', ['0' ... '9'] = 'X' };
+    """
+    assert _round_trip_matches(src)
+
+
 def test_node_visitor():
     from pycparser.c_ast import NodeVisitor