11#!/usr/bin/env python
2- # Copyright (c) 2013-2019 Andrea Bonomi <andrea.bonomi@gmail.com>
2+ # Copyright (c) 2013-2025 Andrea Bonomi <andrea.bonomi@gmail.com>
33#
44# Published under the terms of the MIT license.
55#
2424
2525import re
2626from collections import OrderedDict
27- from typing import TYPE_CHECKING , Any , Dict , List , Optional , Tuple , Type , Union
27+ from typing import (
28+ TYPE_CHECKING ,
29+ Any ,
30+ Callable ,
31+ Dict ,
32+ List ,
33+ Optional ,
34+ Tuple ,
35+ Type ,
36+ Union ,
37+ )
2838
2939from .base import DEFINES , ENUMS , STRUCTS , TYPEDEFS
3040from .c_expr import c_eval
31- from .exceptions import CStructException , ParserError
41+ from .exceptions import CStructException , EvalError , ParserError
3242from .field import FieldType , Kind , calculate_padding
3343from .native_types import get_native_type
3444
4151SPACES = [" " , "\t " , "\n " ]
4252
4353
44- class Tokens ( object ) :
54+ class Tokens :
4555 def __init__ (self , text : str ) -> None :
4656 # remove the comments
4757 text = re .sub (r"//.*?$|/\*.*?\*/" , "" , text , flags = re .S | re .MULTILINE )
@@ -59,7 +69,7 @@ def __init__(self, text: str) -> None:
5969 text = "\n " .join (lines )
6070 self .tokens = self .tokenize (text )
6171
62- def tokenize (self , text ) -> List [str ]:
72+ def tokenize (self , text : str ) -> List [str ]:
6373 tokens : List [str ] = []
6474 t : List [str ] = []
6575 for c in text :
@@ -72,7 +82,7 @@ def tokenize(self, text) -> List[str]:
7282 else :
7383 t .append (c )
7484 if t :
75- tokens .append (t . getvalue ( ))
85+ tokens .append ("" . join ( t ))
7686 return tokens
7787
7888 def pop (self ) -> str :
@@ -101,7 +111,8 @@ def __str__(self) -> str:
101111 return str (self .tokens )
102112
103113
104- def parse_length (tokens : Tokens , next_token : str , vlen : int , flexible_array : bool ) -> Tuple [str , int , bool ]:
114+ def parse_length (tokens : Tokens , next_token : str , flexible_array : bool ) -> Tuple [str , Union [int , Callable [[], int ]], bool ]:
115+ # Extract t_vlen
105116 t = next_token .split ("[" )
106117 if len (t ) != 2 :
107118 raise ParserError (f"Error parsing: `{ next_token } `" )
@@ -114,14 +125,19 @@ def parse_length(tokens: Tokens, next_token: str, vlen: int, flexible_array: boo
114125 t_vlen = vlen_part .split ("]" )[0 ].strip ()
115126 vlen_expr .append (vlen_part .split ("]" )[0 ].strip ())
116127 t_vlen = " " .join (vlen_expr )
128+ # Evaluate t_vlen
129+ vlen : Union [int , Callable [[], int ]]
117130 if not t_vlen :
131+ # If the length expression is empty, this is a flex array
118132 flexible_array = True
119133 vlen = 0
120134 else :
135+ # Evaluate the length expression
136+ # If the length expression is not a constant, it is evaluated at runtime
121137 try :
122- vlen = c_eval (t_vlen )
123- except ( ValueError , TypeError ) :
124- vlen = int (t_vlen )
138+ vlen = int ( c_eval (t_vlen ) )
139+ except EvalError :
140+ vlen = lambda : int (c_eval ( t_vlen ) )
125141 return next_token , vlen , flexible_array
126142
127143
@@ -133,7 +149,7 @@ def parse_type(tokens: Tokens, __cls__: Type["AbstractCStruct"], byte_order: Opt
133149 if c_type in ["signed" , "unsigned" , "struct" , "union" , "enum" ] and len (tokens ) > 1 :
134150 c_type = c_type + " " + tokens .pop ()
135151
136- vlen = 1
152+ vlen : Union [ int , Callable [[], int ]] = 1
137153 flexible_array = False
138154
139155 if not c_type .endswith ("{" ):
@@ -148,20 +164,21 @@ def parse_type(tokens: Tokens, __cls__: Type["AbstractCStruct"], byte_order: Opt
148164 c_type = "void *"
149165 # parse length
150166 if "[" in next_token :
151- next_token , vlen , flexible_array = parse_length (tokens , next_token , vlen , flexible_array )
167+ next_token , vlen , flexible_array = parse_length (tokens , next_token , flexible_array )
152168 tokens .push (next_token )
153169 # resolve typedefs
154170 while c_type in TYPEDEFS :
155171 c_type = TYPEDEFS [c_type ]
156172
157173 # calculate fmt
174+ ref : Union [None , Type [AbstractCEnum ], Type [AbstractCStruct ]]
158175 if c_type .startswith ("struct " ) or c_type .startswith ("union " ): # struct/union
159176 c_type , tail = c_type .split (" " , 1 )
160177 kind = Kind .STRUCT if c_type == "struct" else Kind .UNION
161178 if tokens .get () == "{" : # Named nested struct
162179 tokens .push (tail )
163180 tokens .push (c_type )
164- ref : Union [ Type [ AbstractCEnum ], Type [ AbstractCStruct ]] = __cls__ .parse (tokens , __name__ = tail , __byte_order__ = byte_order )
181+ ref = __cls__ .parse (tokens , __name__ = tail , __byte_order__ = byte_order )
165182 elif tail == "{" : # Unnamed nested struct
166183 tokens .push (tail )
167184 tokens .push (c_type )
@@ -428,7 +445,7 @@ def parse_struct(
428445 raise ParserError (f"Invalid reserved member name `{ vname } `" )
429446 # parse length
430447 if "[" in vname :
431- vname , field_type .vlen , field_type .flexible_array = parse_length (tokens , vname , 1 , flexible_array )
448+ vname , field_type .vlen_ex , field_type .flexible_array = parse_length (tokens , vname , flexible_array )
432449 flexible_array = flexible_array or field_type .flexible_array
433450 # anonymous nested union
434451 if vname == ";" and field_type .ref is not None and (__is_union__ or field_type .ref .__is_union__ ):
0 commit comments