Repository: eliben/pycparser

Issue:
Unable to parse labels at end of block
Raises `pycparser.plyparser.ParseError` when a label is at the end of a block. For example, the following code fails:
```python
from pycparser import CParser

ast =  CParser().parse(
    """
    int main(){
        label:
    }
    """
)
```

As this is valid C, I think it shouldn't throw that exception.

Adding a `;` (or any other code line) after the label fixes this issue
```python
from pycparser import CParser

ast =  CParser().parse(
    """
    int main(){
        label:;
    }
    """
)
```

Hints from issue comments:
@eliben: Thanks for the report.

Strictly reading the standard, a `labeled-statement` requires a statement following the `label:`, e.g. see section A.2.3 in https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf

`gcc` also issues a warning here in "strict" mode.

I don't object to adding the ability to support this in pycparser, if a PR can be made that doesn't complicate the parser too much.@eliben: Fixed by #562 @dgutson: > Fixed by [#562](https://github.com/eliben/pycparser/pull/562)

any ETA to release?

Solution patch:
diff --git a/pycparser/c_parser.py b/pycparser/c_parser.py
index fa56c389..16aa9c4d 100644
--- a/pycparser/c_parser.py
+++ b/pycparser/c_parser.py
@@ -1580,6 +1580,10 @@ def p_labeled_statement_3(self, p):
         """ labeled_statement : DEFAULT COLON pragmacomp_or_statement """
         p[0] = c_ast.Default([p[3]], self._token_coord(p, 1))
 
+    def p_labeled_statement_4(self, p):
+        """ labeled_statement : ID COLON """
+        p[0] = c_ast.Label(p[1], c_ast.EmptyStatement(self._token_coord(p, 1)), self._token_coord(p, 1))
+
     def p_selection_statement_1(self, p):
         """ selection_statement : IF LPAREN expression RPAREN pragmacomp_or_statement """
         p[0] = c_ast.If(p[3], p[5], None, self._token_coord(p, 1))
diff --git a/utils/fake_libc_include/_fake_typedefs.h b/utils/fake_libc_include/_fake_typedefs.h
index 3be1462d..b15e50ee 100644
--- a/utils/fake_libc_include/_fake_typedefs.h
+++ b/utils/fake_libc_include/_fake_typedefs.h
@@ -171,6 +171,13 @@ typedef struct xcb_connection_t xcb_connection_t;
 typedef uint32_t xcb_window_t;
 typedef uint32_t xcb_visualid_t;
 
+/* dirent typedef */
+typedef void* DIR;
+
+/* socket typedefs */
+typedef	__uint32_t  __socklen_t;
+typedef __socklen_t socklen_t;
+
 /* C11 stdatomic.h types */
 typedef _Atomic(_Bool)              atomic_bool;
 typedef _Atomic(char)               atomic_char;


Test patch:
diff --git a/tests/test_c_parser.py b/tests/test_c_parser.py
index 13529852..947e1c55 100755
--- a/tests/test_c_parser.py
+++ b/tests/test_c_parser.py
@@ -2489,6 +2489,20 @@ def test_samescope_reuse_name(self):
             '''
         self.assertRaises(ParseError, self.parse, s2)
 
+    def test_label_empty_statement(self):
+        # Labels with empty statements and no semicolon should be parsed correctly
+        s1 = r'''
+            int main() {
+                int i = 0;
+                label0: i++;
+                label1:
+            }
+            '''
+        s1_ast : FileAST = self.parse(s1)
+        self.assertIsInstance(s1_ast.ext[0].body.block_items[1], Label)
+        self.assertIsInstance(s1_ast.ext[0].body.block_items[1].stmt, UnaryOp)
+        self.assertIsInstance(s1_ast.ext[0].body.block_items[2], Label)
+        self.assertIsInstance(s1_ast.ext[0].body.block_items[2].stmt, EmptyStatement)
 
 if __name__ == '__main__':
     #~ suite = unittest.TestLoader().loadTestsFromNames(