Repository: fox-it/dissect.cstruct

Issue:
Keyword conflicting with Python
When the parsed structure contains keywords of Python (e.g., `in`), the compiler complains.

```bash
  File "<some-path>/python3.10/site-packages/dissect/cstruct/compiler.py", line 86, in compile
    code_object = compile(
  File "<compiled in>", line 2
    class in(Structure):
          ^^
SyntaxError: invalid syntax
```

A temporary solution I took is to replace all `in` to `_in` then back, which seems a bit ad-hoc.
I wonder if it's possible to fix this in the `cstruct` level?

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/fox-it/dissect.cstruct/pull/66?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=fox-it) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`ffbf4f8`)](https://app.codecov.io/gh/fox-it/dissect.cstruct/commit/ffbf4f83169d5c6db2448d42c0e2fa5cec5c874a?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=fox-it) 89.00% compared to head [(`d1b024f`)](https://app.codecov.io/gh/fox-it/dissect.cstruct/pull/66?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=fox-it) 89.03%.
> Report is 1 commits behind head on main.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main      #66      +/-   ##
==========================================
+ Coverage   89.00%   89.03%   +0.02%     
==========================================
  Files          20       20              
  Lines        1855     1860       +5     
==========================================
+ Hits         1651     1656       +5     
  Misses        204      204              
```

| [Flag](https://app.codecov.io/gh/fox-it/dissect.cstruct/pull/66/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=fox-it) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/fox-it/dissect.cstruct/pull/66/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=fox-it) | `89.03% <100.00%> (+0.02%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=fox-it#carryforward-flags-in-the-pull-request-comment) to find out more.


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/fox-it/dissect.cstruct/pull/66?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=fox-it).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=fox-it).


Patch:
diff --git a/dissect/cstruct/compiler.py b/dissect/cstruct/compiler.py
index 296d944..2ea6778 100644
--- a/dissect/cstruct/compiler.py
+++ b/dissect/cstruct/compiler.py
@@ -1,5 +1,6 @@
 from __future__ import annotations
 
+import keyword
 import struct
 from collections import OrderedDict
 from textwrap import dedent
@@ -75,6 +76,8 @@ class {name}(Structure):
             return structure
 
         structure_name = structure.name
+        if keyword.iskeyword(structure_name):
+            structure_name += "_"
 
         try:
             # Generate struct class based on provided structure type
@@ -104,7 +107,9 @@ class {name}(Structure):
         }
 
         exec(code_object, env)
-        return env[structure_name](self.cstruct, structure, source)
+        klass = env[structure_name]
+        klass.__name__ = structure.name
+        return klass(self.cstruct, structure, source)
 
     def gen_struct_class(self, name: str, structure: Structure) -> str:
         blocks = []


Test patch:
diff --git a/tests/test_basic.py b/tests/test_basic.py
index 3b8b9ea..5ed33eb 100644
--- a/tests/test_basic.py
+++ b/tests/test_basic.py
@@ -546,3 +546,28 @@ def test_report_array_size_mismatch():
 
     with pytest.raises(ArraySizeError):
         a.dumps()
+
+
+@pytest.mark.parametrize("compiled", [True, False])
+def test_reserved_keyword(compiled: bool):
+    cdef = """
+    struct in {
+        uint8 a;
+    };
+
+    struct class {
+        uint8 a;
+    };
+
+    struct for {
+        uint8 a;
+    };
+    """
+    cs = cstruct.cstruct(endian="<")
+    cs.load(cdef, compiled=compiled)
+
+    for name in ["in", "class", "for"]:
+        assert name in cs.typedefs
+        assert verify_compiled(cs.resolve(name), compiled)
+
+        assert cs.resolve(name)(b"\x01").a == 1