-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Access to Python class attributes in struct definition
- Loading branch information
Showing
15 changed files
with
321 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,16 @@ | ||
# .readthedocs.yaml | ||
version: 2 | ||
|
||
# Set the OS and Python version | ||
build: | ||
os: ubuntu-22.04 | ||
tools: | ||
python: "3.12" | ||
|
||
mkdocs: | ||
configuration: mkdocs.yml | ||
|
||
# Declare the Python requirements required to build the documentation | ||
python: | ||
install: | ||
- requirements: requirements-dev.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
#!/usr/bin/env python | ||
# Copyright (c) 2013-2019 Andrea Bonomi <[email protected]> | ||
# Copyright (c) 2013-2025 Andrea Bonomi <[email protected]> | ||
# | ||
# Published under the terms of the MIT license. | ||
# | ||
|
@@ -24,7 +24,7 @@ | |
|
||
__author__ = "Andrea Bonomi <[email protected]>" | ||
__license__ = "MIT" | ||
__version__ = "5.3" | ||
__version__ = "6.0" | ||
__date__ = "15 August 2013" | ||
|
||
from typing import Any, Dict, Optional, Type, Union | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,5 @@ | ||
#!/usr/bin/env python | ||
# -*- coding: utf-8 -*- | ||
# | ||
# Copyright (c) 2013-2019 Andrea Bonomi <[email protected]> | ||
# Copyright (c) 2013-2025 Andrea Bonomi <[email protected]> | ||
# | ||
# Published under the terms of the MIT license. | ||
# | ||
|
@@ -163,7 +161,7 @@ def set_flexible_array_length(self, flexible_array_length: Optional[int]) -> Non | |
flexible_array: Optional[FieldType] = [x for x in self.__fields_types__.values() if x.flexible_array][0] | ||
if flexible_array is None: | ||
raise CStructException("Flexible array not found in struct") | ||
flexible_array.vlen = flexible_array_length | ||
flexible_array.vlen_ex = flexible_array_length | ||
|
||
def unpack(self, buffer: Optional[Union[bytes, BinaryIO]], flexible_array_length: Optional[int] = None) -> bool: | ||
""" | ||
|
@@ -202,6 +200,17 @@ def pack(self) -> bytes: # pragma: no cover | |
""" | ||
raise NotImplementedError | ||
|
||
def pack_into(self, buffer: bytearray, offset: int = 0) -> None: | ||
""" | ||
Pack the structure data into a buffer | ||
Args: | ||
buffer: target buffer (must be large enough to contain the packed structure) | ||
offset: optional buffer offset | ||
""" | ||
tmp = self.pack() | ||
buffer[offset : offset + len(tmp)] = tmp | ||
|
||
def clear(self) -> None: | ||
self.unpack(None) | ||
|
||
|
@@ -300,6 +309,9 @@ def __setstate__(self, state: bytes) -> bool: | |
|
||
|
||
class CEnumMeta(EnumMeta): | ||
__size__: int | ||
__native_format__: str | ||
|
||
class WrapperDict(_EnumDict): | ||
def __setitem__(self, key: str, value: Any) -> None: | ||
env = None | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
#!/usr/bin/env python | ||
# Copyright (c) 2013-2019 Andrea Bonomi <[email protected]> | ||
# Copyright (c) 2013-2025 Andrea Bonomi <[email protected]> | ||
# | ||
# Published under the terms of the MIT license. | ||
# | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
#!/usr/bin/env python | ||
# Copyright (c) 2013-2019 Andrea Bonomi <[email protected]> | ||
# Copyright (c) 2013-2025 Andrea Bonomi <[email protected]> | ||
# | ||
# Published under the terms of the MIT license. | ||
# | ||
|
@@ -23,11 +23,12 @@ | |
# | ||
|
||
import ast | ||
import inspect | ||
import operator | ||
from typing import TYPE_CHECKING, Any, Callable, Dict, Type, Union | ||
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Type, Union | ||
|
||
from .base import DEFINES, STRUCTS | ||
from .exceptions import EvalError | ||
from .exceptions import ContextNotFound, EvalError | ||
|
||
if TYPE_CHECKING: | ||
from .abstract import AbstractCStruct | ||
|
@@ -65,7 +66,36 @@ def c_eval(expr: str) -> Union[int, float]: | |
raise EvalError | ||
|
||
|
||
def eval_attribute_node(node: ast.Attribute) -> Union[int, float]: | ||
""" | ||
Evaluate node attribute, e.g. 'self.x' | ||
Only 'self' is allowed. The attribute must be a number. | ||
Args: | ||
node: attribute node | ||
Returns: | ||
result: the attribute value | ||
Raises: | ||
EvalError: expression result is not a number, or not self attribute | ||
ContextNotFound: context is not defined | ||
""" | ||
if not node.value or node.value.id != "self": # type: ignore | ||
raise EvalError("only self is allowed") | ||
context = get_cstruct_context() | ||
if context is None: | ||
raise ContextNotFound("context is not defined") | ||
result = getattr(context, node.attr) | ||
if not isinstance(result, (int, float)): | ||
raise EvalError("expression result is not a number") | ||
return result | ||
|
||
|
||
def eval_node(node: ast.stmt) -> Union[int, float]: | ||
if isinstance(node, ast.Attribute): | ||
return eval_attribute_node(node) | ||
|
||
handler = OPS[type(node)] | ||
result = handler(node) | ||
if isinstance(result, bool): # convert bool to int | ||
|
@@ -116,6 +146,20 @@ def eval_call(node) -> Union[int, float]: | |
raise KeyError(node.func.id) | ||
|
||
|
||
def get_cstruct_context() -> Optional["AbstractCStruct"]: | ||
""" | ||
Get the calling CStruct instance from the stack (if any) | ||
""" | ||
from .abstract import AbstractCStruct | ||
|
||
stack = inspect.stack() | ||
for frame in stack: | ||
caller_self = frame.frame.f_locals.get("self") | ||
if isinstance(caller_self, AbstractCStruct): | ||
return caller_self | ||
return None | ||
|
||
|
||
try: | ||
Constant = ast.Constant | ||
except AttributeError: # python < 3.8 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
#!/usr/bin/env python | ||
# Copyright (c) 2013-2019 Andrea Bonomi <[email protected]> | ||
# Copyright (c) 2013-2025 Andrea Bonomi <[email protected]> | ||
# | ||
# Published under the terms of the MIT license. | ||
# | ||
|
@@ -24,11 +24,21 @@ | |
|
||
import re | ||
from collections import OrderedDict | ||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union | ||
from typing import ( | ||
TYPE_CHECKING, | ||
Any, | ||
Callable, | ||
Dict, | ||
List, | ||
Optional, | ||
Tuple, | ||
Type, | ||
Union, | ||
) | ||
|
||
from .base import DEFINES, ENUMS, STRUCTS, TYPEDEFS | ||
from .c_expr import c_eval | ||
from .exceptions import CStructException, ParserError | ||
from .exceptions import CStructException, EvalError, ParserError | ||
from .field import FieldType, Kind, calculate_padding | ||
from .native_types import get_native_type | ||
|
||
|
@@ -41,7 +51,7 @@ | |
SPACES = [" ", "\t", "\n"] | ||
|
||
|
||
class Tokens(object): | ||
class Tokens: | ||
def __init__(self, text: str) -> None: | ||
# remove the comments | ||
text = re.sub(r"//.*?$|/\*.*?\*/", "", text, flags=re.S | re.MULTILINE) | ||
|
@@ -59,7 +69,7 @@ def __init__(self, text: str) -> None: | |
text = "\n".join(lines) | ||
self.tokens = self.tokenize(text) | ||
|
||
def tokenize(self, text) -> List[str]: | ||
def tokenize(self, text: str) -> List[str]: | ||
tokens: List[str] = [] | ||
t: List[str] = [] | ||
for c in text: | ||
|
@@ -72,7 +82,7 @@ def tokenize(self, text) -> List[str]: | |
else: | ||
t.append(c) | ||
if t: | ||
tokens.append(t.getvalue()) | ||
tokens.append("".join(t)) | ||
return tokens | ||
|
||
def pop(self) -> str: | ||
|
@@ -101,7 +111,8 @@ def __str__(self) -> str: | |
return str(self.tokens) | ||
|
||
|
||
def parse_length(tokens: Tokens, next_token: str, vlen: int, flexible_array: bool) -> Tuple[str, int, bool]: | ||
def parse_length(tokens: Tokens, next_token: str, flexible_array: bool) -> Tuple[str, Union[int, Callable[[], int]], bool]: | ||
# Extract t_vlen | ||
t = next_token.split("[") | ||
if len(t) != 2: | ||
raise ParserError(f"Error parsing: `{next_token}`") | ||
|
@@ -114,14 +125,19 @@ def parse_length(tokens: Tokens, next_token: str, vlen: int, flexible_array: boo | |
t_vlen = vlen_part.split("]")[0].strip() | ||
vlen_expr.append(vlen_part.split("]")[0].strip()) | ||
t_vlen = " ".join(vlen_expr) | ||
# Evaluate t_vlen | ||
vlen: Union[int, Callable[[], int]] | ||
if not t_vlen: | ||
# If the length expression is empty, this is a flex array | ||
flexible_array = True | ||
vlen = 0 | ||
else: | ||
# Evaluate the length expression | ||
# If the length expression is not a constant, it is evaluated at runtime | ||
try: | ||
vlen = c_eval(t_vlen) | ||
except (ValueError, TypeError): | ||
vlen = int(t_vlen) | ||
vlen = int(c_eval(t_vlen)) | ||
except EvalError: | ||
vlen = lambda: int(c_eval(t_vlen)) | ||
return next_token, vlen, flexible_array | ||
|
||
|
||
|
@@ -133,7 +149,7 @@ def parse_type(tokens: Tokens, __cls__: Type["AbstractCStruct"], byte_order: Opt | |
if c_type in ["signed", "unsigned", "struct", "union", "enum"] and len(tokens) > 1: | ||
c_type = c_type + " " + tokens.pop() | ||
|
||
vlen = 1 | ||
vlen: Union[int, Callable[[], int]] = 1 | ||
flexible_array = False | ||
|
||
if not c_type.endswith("{"): | ||
|
@@ -148,20 +164,21 @@ def parse_type(tokens: Tokens, __cls__: Type["AbstractCStruct"], byte_order: Opt | |
c_type = "void *" | ||
# parse length | ||
if "[" in next_token: | ||
next_token, vlen, flexible_array = parse_length(tokens, next_token, vlen, flexible_array) | ||
next_token, vlen, flexible_array = parse_length(tokens, next_token, flexible_array) | ||
tokens.push(next_token) | ||
# resolve typedefs | ||
while c_type in TYPEDEFS: | ||
c_type = TYPEDEFS[c_type] | ||
|
||
# calculate fmt | ||
ref: Union[None, Type[AbstractCEnum], Type[AbstractCStruct]] | ||
if c_type.startswith("struct ") or c_type.startswith("union "): # struct/union | ||
c_type, tail = c_type.split(" ", 1) | ||
kind = Kind.STRUCT if c_type == "struct" else Kind.UNION | ||
if tokens.get() == "{": # Named nested struct | ||
tokens.push(tail) | ||
tokens.push(c_type) | ||
ref: Union[Type[AbstractCEnum], Type[AbstractCStruct]] = __cls__.parse(tokens, __name__=tail, __byte_order__=byte_order) | ||
ref = __cls__.parse(tokens, __name__=tail, __byte_order__=byte_order) | ||
elif tail == "{": # Unnamed nested struct | ||
tokens.push(tail) | ||
tokens.push(c_type) | ||
|
@@ -428,7 +445,7 @@ def parse_struct( | |
raise ParserError(f"Invalid reserved member name `{vname}`") | ||
# parse length | ||
if "[" in vname: | ||
vname, field_type.vlen, field_type.flexible_array = parse_length(tokens, vname, 1, flexible_array) | ||
vname, field_type.vlen_ex, field_type.flexible_array = parse_length(tokens, vname, flexible_array) | ||
flexible_array = flexible_array or field_type.flexible_array | ||
# anonymous nested union | ||
if vname == ";" and field_type.ref is not None and (__is_union__ or field_type.ref.__is_union__): | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
#!/usr/bin/env python | ||
# Copyright (c) 2013-2019 Andrea Bonomi <[email protected]> | ||
# Copyright (c) 2013-2025 Andrea Bonomi <[email protected]> | ||
# | ||
# Published under the terms of the MIT license. | ||
# | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
#!/usr/bin/env python | ||
# Copyright (c) 2013-2019 Andrea Bonomi <[email protected]> | ||
# Copyright (c) 2013-2025 Andrea Bonomi <[email protected]> | ||
# | ||
# Published under the terms of the MIT license. | ||
# | ||
|
@@ -27,6 +27,7 @@ | |
"CStructException", | ||
"ParserError", | ||
"EvalError", | ||
"ContextNotFound", | ||
] | ||
|
||
|
||
|
@@ -44,3 +45,7 @@ class ParserError(CStructException): | |
|
||
class EvalError(CStructException): | ||
pass | ||
|
||
|
||
class ContextNotFound(EvalError): | ||
pass |
Oops, something went wrong.