Repository: fox-it/dissect.cstruct

Issue:
Tabs in constant expression should be treated like spaces
For example:

```c
#define A<tab>(B + C)<tab>// comment
```

currently errors when parsing, even though tabs are legal whitespace separators.

Discussion:
d3dave: @DissectBot agree
codecov-commenter: ## [Codecov](https://app.codecov.io/gh/fox-it/dissect.cstruct/pull/88?dropdown=coverage&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:
> Project coverage is 89.32%. Comparing base [(`95a4928`)](https://app.codecov.io/gh/fox-it/dissect.cstruct/commit/95a4928dbb48ff8761163c21d576a055c4155a9f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=fox-it) to head [(`e580f6d`)](https://app.codecov.io/gh/fox-it/dissect.cstruct/pull/88?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=fox-it).


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


```diff
@@           Coverage Diff           @@
##             main      #88   +/-   ##
=======================================
  Coverage   89.32%   89.32%           
=======================================
  Files          21       21           
  Lines        1929     1929           
=======================================
  Hits         1723     1723           
  Misses        206      206           
```

| [Flag](https://app.codecov.io/gh/fox-it/dissect.cstruct/pull/88/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/88/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=fox-it) | `89.32% <100.00%> (ø)` | |

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/88?dropdown=coverage&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/expression.py b/dissect/cstruct/expression.py
index 53e1cf2..4727f26 100644
--- a/dissect/cstruct/expression.py
+++ b/dissect/cstruct/expression.py
@@ -1,7 +1,7 @@
 from __future__ import annotations
 
 import string
-from typing import TYPE_CHECKING, Callable, Optional, Union
+from typing import TYPE_CHECKING, Callable, Optional
 
 from dissect.cstruct.exceptions import ExpressionParserError, ExpressionTokenizerError
 
@@ -18,7 +18,7 @@ class ExpressionTokenizer:
         self.pos = 0
         self.tokens = []
 
-    def equal(self, token: str, expected: Union[str, str[str]]) -> bool:
+    def equal(self, token: str, expected: str | set[str]) -> bool:
         if isinstance(expected, set):
             return token in expected
         else:
@@ -42,7 +42,7 @@ class ExpressionTokenizer:
     def match(
         self,
         func: Optional[Callable[[str], bool]] = None,
-        expected: Optional[str] = None,
+        expected: Optional[str | set[str]] = None,
         consume: bool = True,
         append: bool = True,
     ) -> bool:
@@ -142,7 +142,7 @@ class ExpressionTokenizer:
                 self.tokens.append(">>")
             elif self.match(expected="<", append=False) and self.match(expected="<", append=False):
                 self.tokens.append("<<")
-            elif self.match(expected=" ", append=False):
+            elif self.match(expected={" ", "\t"}, append=False):
                 continue
             else:
                 raise ExpressionTokenizerError(


Test patch:
diff --git a/tests/test_expression.py b/tests/test_expression.py
index c1d0fc0..1495ecf 100644
--- a/tests/test_expression.py
+++ b/tests/test_expression.py
@@ -11,6 +11,7 @@ testdata = [
     ("7*8", 56),
     ("7 *8", 56),
     ("   7  *     8  ", 56),
+    ("\t7\t*\t8\t", 56),
     ("0 / 1", 0),
     ("1 / 1", 1),
     ("2 / 2", 1),