Skip to content

Commit f95b499

Browse files
SchamperCopilot
andcommitted
Process review feedback
Co-authored-by: Copilot <copilot@github.com>
1 parent b6299af commit f95b499

7 files changed

Lines changed: 37 additions & 67 deletions

File tree

dissect/cstruct/cstruct.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ def __init__(self, load: str = "", *, endian: str = "<", pointer: str | None = N
5454
self.endian = endian
5555

5656
self.consts = {}
57-
self.lookups = {}
5857
self.includes = []
5958
# fmt: off
6059
self.typedefs = {

dissect/cstruct/lexer.py

Lines changed: 11 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class TokenType(enum.Enum):
2727
SEMICOLON = ";"
2828
COMMA = ","
2929
COLON = ":"
30+
QUESTION = "?"
3031
STAR = "*"
3132
EQUALS = "="
3233

@@ -63,7 +64,6 @@ class TokenType(enum.Enum):
6364
PP_FLAGS = "PP_FLAGS"
6465

6566
# Special
66-
LOOKUP = "LOOKUP"
6767
EOF = "EOF"
6868

6969

@@ -112,6 +112,7 @@ def __repr__(self) -> str:
112112
";": TokenType.SEMICOLON,
113113
",": TokenType.COMMA,
114114
":": TokenType.COLON,
115+
"?": TokenType.QUESTION,
115116
"*": TokenType.STAR,
116117
"=": TokenType.EQUALS,
117118
"+": TokenType.PLUS,
@@ -203,7 +204,7 @@ def _take(self, num: int = 1) -> str:
203204

204205
return result
205206

206-
def _expect(self, *chars: str) -> None:
207+
def _expect(self, *chars: str) -> str:
207208
"""Consume the expected characters or raise an error."""
208209
if self._current() not in chars:
209210
actual = "end of input" if self.eof else repr(self._current())
@@ -287,11 +288,11 @@ def _read_identifier(self) -> str:
287288
return ""
288289

289290
def _read_number(self) -> str:
290-
"""Read a numeric literal, supporting decimal, hex (0x), octal (0), binary (0b), and C-style suffixes."""
291+
"""Read a numeric literal, supporting decimal, hex (0x), octal (0, 0o), binary (0b), and C-style suffixes."""
291292
start = self._pos
292293
is_float = False
293294

294-
if self._current() == "0" and self._peek() in ("x", "X", "b", "B"):
295+
if self._current() == "0" and self._peek() in ("x", "X", "b", "B", "o", "O"):
295296
self._expect("0") # Consume leading 0
296297
suffix = self._take().lower()
297298

@@ -301,6 +302,9 @@ def _read_number(self) -> str:
301302
if suffix == "b" and not self._read_while("01"):
302303
raise self._error("invalid binary literal")
303304

305+
if suffix == "o" and not self._read_while("01234567"):
306+
raise self._error("invalid octal literal")
307+
304308
else:
305309
# Consume decimal/octal digits
306310
self._read_while("0123456789")
@@ -318,7 +322,9 @@ def _read_number(self) -> str:
318322
self._read_while("uUlL")
319323

320324
# Convert octal: leading 0 without 0x/0b → insert 'o'
321-
if len(raw) > 1 and raw[0] == "0" and raw[1].lower() not in ("x", "b"):
325+
if len(raw) > 1 and raw[0] == "0" and raw[1].lower() not in ("x", "b", "o"):
326+
if raw[1] not in "01234567":
327+
raise self._error("invalid octal literal")
322328
raw = raw[0] + "o" + raw[1:]
323329

324330
return raw
@@ -396,30 +402,6 @@ def _read_preprocessor(self) -> None:
396402

397403
self._emit(TokenType.STRING, value, line)
398404

399-
def _read_lookup(self) -> None:
400-
"""Read a lookup definition: ``$name = { dict }``."""
401-
line = self._line
402-
col = self._column
403-
start = self._pos
404-
405-
self._expect("$") # Consume `$`
406-
407-
# Read until end of the {...} block
408-
brace_depth = 0
409-
while not self.eof:
410-
ch = self._current()
411-
if ch == "{":
412-
brace_depth += 1
413-
elif ch == "}":
414-
brace_depth -= 1
415-
if brace_depth == 0:
416-
self._expect("}") # Consume final `}`
417-
break
418-
self._take()
419-
420-
value = self._get(start, self._pos)
421-
self._emit(TokenType.LOOKUP, value.strip(), line, col)
422-
423405
def tokenize(self) -> list[Token]:
424406
"""Tokenize the input data and return a list of tokens."""
425407
while not self.eof:
@@ -490,10 +472,6 @@ def tokenize(self) -> list[Token]:
490472
elif ch in _SINGLE_CHARS:
491473
self._emit(_SINGLE_CHARS[ch], self._take(), line, col)
492474

493-
elif ch == "$":
494-
# Custom lookup definition
495-
self._read_lookup()
496-
497475
else:
498476
raise self._error(f"unexpected character {ch!r}", line=line)
499477

dissect/cstruct/parser.py

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,6 @@ def _parse(self) -> None:
137137
while (token := self._current()).type != TokenType.EOF:
138138
if token.type == TokenType.PP_FLAGS:
139139
self._parse_config_flags()
140-
elif token.type == TokenType.LOOKUP:
141-
self._parse_lookup()
142140
elif token.type == TokenType.TYPEDEF:
143141
self._parse_typedef()
144142
elif token.type in (TokenType.STRUCT, TokenType.UNION):
@@ -173,7 +171,7 @@ def _parse_define(self) -> None:
173171
while (token := self._current()).type != TokenType.EOF and token.line == name_token.line:
174172
parts.append(self._take().value)
175173

176-
value = " ".join(parts).strip()
174+
value = "".join(parts).strip()
177175
try:
178176
# Lazy mode, try to evaluate as a Python literal first (for simple constants)
179177
value = ast.literal_eval(value)
@@ -508,17 +506,3 @@ def _parse_type_spec(self) -> type[BaseType]:
508506
break
509507

510508
return self.cs.resolve(" ".join(parts))
511-
512-
# Custom lookup definitions
513-
514-
def _parse_lookup(self) -> None:
515-
"""Parse a lookup definition."""
516-
value = self._take().value
517-
518-
# Parse $name = { dict }
519-
# Find the name and dict parts
520-
dollar_rest = value.lstrip("$")
521-
name, _, lookup = dollar_rest.partition("=")
522-
523-
d = ast.literal_eval(lookup.strip())
524-
self.cs.lookups[name.strip()] = {self.cs.consts[k]: v for k, v in d.items()}

tests/test_basic.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -150,16 +150,6 @@ def test_typedef(cs: cstruct) -> None:
150150
assert cs.resolve("test") == cs.uint32
151151

152152

153-
def test_lookups(cs: cstruct, compiled: bool) -> None:
154-
cdef = """
155-
#define test_1 1
156-
#define test_2 2
157-
$a = {'test_1': 3, 'test_2': 4}
158-
"""
159-
cs.load(cdef, compiled=compiled)
160-
assert cs.lookups["a"] == {1: 3, 2: 4}
161-
162-
163153
def test_config_flag_nocompile(cs: cstruct, compiled: bool) -> None:
164154
cdef = """
165155
struct compiled_global

tests/test_expression.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,9 @@ def test_expression(cs_with_consts: cstruct, expression: str, answer: int) -> No
112112
),
113113
pytest.param(
114114
"$",
115-
ExpressionParserError,
116-
"Unmatched token: '\\$'",
117-
id="invalid-token",
115+
LexerError,
116+
"unexpected character '\\$'",
117+
id="unexpected-character",
118118
),
119119
pytest.param(
120120
"-",

tests/test_lexer.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
("0b1010", [TokenType.NUMBER], ["0b1010"]),
3131
("0B1100", [TokenType.NUMBER], ["0B1100"]),
3232
("0755", [TokenType.NUMBER], ["0o755"]),
33+
("0o777", [TokenType.NUMBER], ["0o777"]),
3334
("3.14", [TokenType.NUMBER], ["3.14"]),
3435
("1.", [TokenType.NUMBER], ["1."]),
3536
("{", [TokenType.LBRACE], ["{"]),
@@ -103,9 +104,6 @@
103104
("_my_var", [TokenType.IDENTIFIER], ["_my_var"]),
104105
("UINT32", [TokenType.IDENTIFIER], ["UINT32"]),
105106
("uint32_t", [TokenType.IDENTIFIER], ["uint32_t"]),
106-
# Lookup
107-
("$my_lookup = {1: 'a', 2: 'b'}", [TokenType.LOOKUP], ["$my_lookup = {1: 'a', 2: 'b'}"]),
108-
("$tbl = {\n 1: 'x',\n 2: 'y'\n}", [TokenType.LOOKUP], ["$tbl = {\n 1: 'x',\n 2: 'y'\n}"]),
109107
# Combination
110108
(
111109
"uint32_t bit0:1;",
@@ -148,6 +146,7 @@ def test_token(src: str, types: list[TokenType], values: list[str]) -> None:
148146
[
149147
("0b", "invalid binary literal"),
150148
("0x", "invalid hexadecimal literal"),
149+
("0888", "invalid octal literal"),
151150
('"unterminated', "unexpected end of input"),
152151
("'unterminated", "unexpected end of input"),
153152
("#foobar", "unknown preprocessor directive"),

tests/test_parser.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,26 @@ def test_typedef_pointer(cs: cstruct) -> None:
199199
assert cs.PIMAGE_DATA_DIRECTORY.type == cs._IMAGE_DATA_DIRECTORY
200200

201201

202+
def test_define(cs: cstruct) -> None:
203+
cdef = """
204+
#define MY_CONST 42
205+
#define MY_EXPR (1 + 2 * 3)
206+
#define MY_STR "hello"
207+
#define MY_BYTES b"world"
208+
#define MY_FUNC(x) ( x == 0 )
209+
#define MY_TERNARY(x) ( x ? 1 : 0 )
210+
"""
211+
cs.load(cdef)
212+
213+
assert cs.consts["MY_CONST"] == 42
214+
assert cs.consts["MY_EXPR"] == 7
215+
assert cs.consts["MY_STR"] == "hello"
216+
assert cs.consts["MY_BYTES"] == b"world"
217+
# We don't evaluate function-like macros yet, so they should be stored as their raw string representation
218+
assert cs.consts["MY_FUNC"] == "(x)(x==0)"
219+
assert cs.consts["MY_TERNARY"] == "(x)(x?1:0)"
220+
221+
202222
def test_undef(cs: cstruct) -> None:
203223
cdef = """
204224
#define MY_CONST 42

0 commit comments

Comments
 (0)