init
This commit is contained in:
commit
0313c8d938
|
@ -0,0 +1,6 @@
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
.mypy_cache
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
thumbs.db
|
|
@ -0,0 +1,21 @@
|
||||||
|
from .core import parse, dump, sizeof, offsetof, default
|
||||||
|
from .core.base import Context, Type
|
||||||
|
from .core.io import Stream, Segment
|
||||||
|
from .core.meta import Wrapper, Generic
|
||||||
|
#from .core.expr import Ref, RefSource
|
||||||
|
|
||||||
|
from .types.data import Data, data
|
||||||
|
from .types.int import *
|
||||||
|
from .types.struct import StructType, Struct
|
||||||
|
from .types.seq import Arr, Tuple
|
||||||
|
from .types.transforms import Default, Sized, Ref, Transform, Mapped
|
||||||
|
|
||||||
|
__all__ = [x.__name__ for x in {
|
||||||
|
parse, dump, sizeof, offsetof, default,
|
||||||
|
Context, Type, Stream, Segment,
|
||||||
|
Wrapper, Default, Sized, Ref, Transform, Mapped,
|
||||||
|
Data,
|
||||||
|
Int, Bool,
|
||||||
|
Arr, Tuple,
|
||||||
|
StructType, Struct, Generic,
|
||||||
|
}] + ['data', 'bool', 'int8', 'uint8', 'uint16le', 'int32le', 'uint32le']
|
|
@ -0,0 +1,109 @@
|
||||||
|
from typing import Callable, Union, BinaryIO, Any, Optional as O, Sequence
|
||||||
|
from types import FunctionType
|
||||||
|
from io import BytesIO
|
||||||
|
import math
|
||||||
|
|
||||||
|
from .base import PathElement, Context, Params, Type, Error
|
||||||
|
from .io import Stream, Segment, ceil_sizes
|
||||||
|
|
||||||
|
|
||||||
|
PossibleType = Union[Type, list, tuple, Callable[[O[Any]], Type]]
|
||||||
|
|
||||||
|
def to_type(type: PossibleType, ident: O[Any] = None) -> Type:
|
||||||
|
if isinstance(type, Type):
|
||||||
|
return type
|
||||||
|
t = getattr(type, '_sx_type_', None)
|
||||||
|
if t:
|
||||||
|
return t
|
||||||
|
getter = getattr(type, '_get_sx_type_', None)
|
||||||
|
if getter:
|
||||||
|
return getter(ident)
|
||||||
|
if isinstance(type, FunctionType):
|
||||||
|
return type(ident)
|
||||||
|
|
||||||
|
raise ValueError('Could not figure out specification from argument {}.'.format(type))
|
||||||
|
|
||||||
|
PossibleStream = Union[BinaryIO, Stream, None, bytes, bytearray]
|
||||||
|
|
||||||
|
def to_stream(value: PossibleStream) -> Stream:
|
||||||
|
if isinstance(value, Stream):
|
||||||
|
return value
|
||||||
|
if value is None:
|
||||||
|
value = BytesIO()
|
||||||
|
if isinstance(value, (bytes, bytearray)):
|
||||||
|
value = BytesIO(value)
|
||||||
|
return Stream(value)
|
||||||
|
|
||||||
|
|
||||||
|
def parse(type: PossibleType, stream: PossibleStream, params: O[Params] = None) -> Any:
|
||||||
|
type = to_type(type)
|
||||||
|
stream = to_stream(stream)
|
||||||
|
ctx = Context(type, None, params=params)
|
||||||
|
try:
|
||||||
|
return ctx.parse(type, stream)
|
||||||
|
except Error as e:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise Error(ctx, e) from e
|
||||||
|
|
||||||
|
def dump(type: PossibleType, value: Any, stream: PossibleStream = None, params: O[Params] = None) -> BinaryIO:
|
||||||
|
type = to_type(type)
|
||||||
|
stream = to_stream(stream)
|
||||||
|
ctx = Context(type, value, params=params)
|
||||||
|
try:
|
||||||
|
ctx.dump(type, stream, value)
|
||||||
|
except Error:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise Error(ctx, e) from e
|
||||||
|
return stream.root
|
||||||
|
|
||||||
|
def sizeof(type: PossibleType, value: O[Any] = None, params: O[Params] = None, segment: O[Segment] = None) -> O[int]:
|
||||||
|
type = to_type(type)
|
||||||
|
ctx = Context(type, value, params=params)
|
||||||
|
try:
|
||||||
|
sizes = ceil_sizes(ctx.sizeof(type, value))
|
||||||
|
except Error:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise Error(ctx, e) from e
|
||||||
|
|
||||||
|
if segment:
|
||||||
|
return sizes.get(segment, None)
|
||||||
|
else:
|
||||||
|
size = 0
|
||||||
|
for v in sizes.values():
|
||||||
|
if v is None:
|
||||||
|
return None
|
||||||
|
size += v
|
||||||
|
return size
|
||||||
|
|
||||||
|
def offsetof(type: PossibleType, path: Sequence[PathElement], value: O[Any] = None, params: O[Params] = None, segment: O[Segment] = None) -> O[int]:
|
||||||
|
type = to_type(type)
|
||||||
|
ctx = Context(type, value, params=params)
|
||||||
|
try:
|
||||||
|
offsets = ctx.offsetof(type, path, value)
|
||||||
|
except Error:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise Error(ctx, e) from e
|
||||||
|
|
||||||
|
segment = segment or ctx.params.default_segment
|
||||||
|
|
||||||
|
off = offsets.get(segment, None)
|
||||||
|
if off is None:
|
||||||
|
return None
|
||||||
|
segoff = ctx.segment_offset(segment)
|
||||||
|
if segoff is None:
|
||||||
|
return None
|
||||||
|
return math.ceil(segoff + off)
|
||||||
|
|
||||||
|
def default(type: PossibleType, params: O[Params] = None) -> O[Any]:
|
||||||
|
type = to_type(type)
|
||||||
|
ctx = Context(type, None, params=params)
|
||||||
|
try:
|
||||||
|
return ctx.default(type)
|
||||||
|
except Error:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise Error(ctx, e) from e
|
|
@ -0,0 +1,199 @@
|
||||||
|
import os
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from typing import Any, Generic, Generator, Iterable, List, Mapping, Dict, Sequence, Tuple, TypeVar, Union as U, Optional as O, Generic as G, cast
|
||||||
|
|
||||||
|
from .util import seeking
|
||||||
|
from .io import Segment, Stream, Pos
|
||||||
|
|
||||||
|
|
||||||
|
class Params:
|
||||||
|
__slots__ = ('segments', 'default_segment', 'user')
|
||||||
|
|
||||||
|
def __init__(self, segments: Sequence[Segment] = None):
|
||||||
|
default = segments[0] if segments else Segment('default')
|
||||||
|
self.segments = {s.name: s for s in (segments or [default, Segment('refs', [default])])}
|
||||||
|
self.default_segment = default
|
||||||
|
self.user = SimpleNamespace()
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
for s in self.segments.values():
|
||||||
|
s.reset()
|
||||||
|
|
||||||
|
|
||||||
|
PathElement = U[str, int]
|
||||||
|
PathEntry = Tuple[PathElement, 'Type']
|
||||||
|
|
||||||
|
def format_path(path: Iterable[PathElement]) -> str:
|
||||||
|
s = ''
|
||||||
|
first = True
|
||||||
|
for p in path:
|
||||||
|
sep = '.'
|
||||||
|
if isinstance(p, int):
|
||||||
|
p = '[' + str(p) + ']'
|
||||||
|
sep = ''
|
||||||
|
if sep and not first:
|
||||||
|
s += sep
|
||||||
|
s += p
|
||||||
|
first = False
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
|
PT = TypeVar('PT')
|
||||||
|
|
||||||
|
|
||||||
|
class PossibleDynamic(Generic[T]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Context:
|
||||||
|
__slots__ = ('root', 'value', 'params', 'path', 'segment_path')
|
||||||
|
|
||||||
|
def __init__(self, root: 'Type', value: O[Any] = None, params: O[Params] = None) -> None:
|
||||||
|
self.root = root
|
||||||
|
self.value = value
|
||||||
|
self.params = params or Params()
|
||||||
|
self.path: List[PathEntry] = []
|
||||||
|
self.segment_path: List[Segment] = []
|
||||||
|
|
||||||
|
def copy(self) -> 'Context':
|
||||||
|
c = self.__class__(root=self.root, value=self.value, params=self.params)
|
||||||
|
c.path = self.path.copy()
|
||||||
|
c.segment_path = self.segment_path.copy()
|
||||||
|
return c
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def segment(self) -> Segment:
|
||||||
|
return self.segment_path[-1] if self.segment_path else self.params.default_segment
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def enter(self, entry: PathElement, type: 'Type') -> Generator:
|
||||||
|
self.path.append((entry, type))
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
except EOFError as e:
|
||||||
|
raise EOF(self, e) from e
|
||||||
|
self.path.pop()
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def enter_segment(self, segment: Segment, stream: O[Stream] = None, pos: O[Pos] = None, reference = os.SEEK_SET) -> Generator[O[Stream], None, None]:
|
||||||
|
if stream:
|
||||||
|
if pos is None:
|
||||||
|
if segment.offset is None:
|
||||||
|
segment.offset = self.segment_offset(segment)
|
||||||
|
segment.pos = segment.offset
|
||||||
|
pos = segment.pos
|
||||||
|
if pos is None:
|
||||||
|
raise Error(self, ValueError('could not enter segment {}: could not calculate offset'.format(segment)))
|
||||||
|
with seeking(stream.root, pos, reference) as s, stream.wrapped(s) as f:
|
||||||
|
self.segment_path.append(segment)
|
||||||
|
yield f
|
||||||
|
self.segment_path.pop()
|
||||||
|
segment.pos = f.tell()
|
||||||
|
else:
|
||||||
|
self.segment_path.append(segment)
|
||||||
|
yield stream
|
||||||
|
self.segment_path.pop()
|
||||||
|
|
||||||
|
def segment_offset(self, segment: Segment) -> O[Pos]:
|
||||||
|
size: Pos = 0
|
||||||
|
for s in segment.dependents:
|
||||||
|
sz = self.segment_size(s)
|
||||||
|
if sz is None:
|
||||||
|
return None
|
||||||
|
off = self.segment_offset(s)
|
||||||
|
if off is None:
|
||||||
|
return None
|
||||||
|
size += off + sz
|
||||||
|
return size
|
||||||
|
|
||||||
|
def segment_size(self, segment: Segment) -> O[Pos]:
|
||||||
|
sizes = self.sizeof(self.root, self.value)
|
||||||
|
return sizes.get(segment, None)
|
||||||
|
|
||||||
|
def format_path(self) -> str:
|
||||||
|
return format_path(name for name, _ in self.path)
|
||||||
|
|
||||||
|
def to_size(self, value: Any) -> Dict[Segment, Pos]:
|
||||||
|
if not isinstance(value, dict):
|
||||||
|
stream = self.segment_path[-1] if self.segment_path else self.params.default_segment
|
||||||
|
value = {stream: value}
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def get(self, value: U[T, PossibleDynamic[T]]) -> T:
|
||||||
|
from .expr import Expr, get
|
||||||
|
if isinstance(value, Expr):
|
||||||
|
value = get(value)
|
||||||
|
return cast(T, value)
|
||||||
|
|
||||||
|
def peek(self, value: U[T, PossibleDynamic[T]]) -> O[T]:
|
||||||
|
from .expr import Expr, peek
|
||||||
|
if isinstance(value, Expr):
|
||||||
|
value = peek(value)
|
||||||
|
return cast(T, value)
|
||||||
|
|
||||||
|
def put(self, value: U[T, PossibleDynamic[T]], new: T) -> None:
|
||||||
|
from .expr import Expr, put
|
||||||
|
if isinstance(value, Expr):
|
||||||
|
put(value, new)
|
||||||
|
|
||||||
|
|
||||||
|
def parse(self, type: 'Type[PT]', stream: Stream) -> PT:
|
||||||
|
return type.parse(self, stream)
|
||||||
|
|
||||||
|
def dump(self, type: 'Type[PT]', stream: Stream, value: PT) -> None:
|
||||||
|
return type.dump(self, stream, value)
|
||||||
|
|
||||||
|
def sizeof(self, type: 'Type[PT]', value: O[PT] = None) -> Dict[Segment, Pos]:
|
||||||
|
return self.to_size(type.sizeof(self, value))
|
||||||
|
|
||||||
|
def offsetof(self, type: 'Type[PT]', path: Sequence[PathElement], value: O[PT] = None) -> Dict[Segment, Pos]:
|
||||||
|
return self.to_size(type.offsetof(self, path, value))
|
||||||
|
|
||||||
|
def default(self, type: 'Type[PT]') -> PT:
|
||||||
|
return type.default(self)
|
||||||
|
|
||||||
|
|
||||||
|
class Type(G[PT]):
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def parse(self, context: Context, stream: Stream) -> PT:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def dump(self, context: Context, stream: Stream, value: PT) -> None:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def sizeof(self, context: Context, value: O[PT]) -> U[Mapping[str, int], O[int]]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def offsetof(self, context: Context, path: Sequence[PathElement], value: O[PT]) -> O[int]:
|
||||||
|
if path:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def default(self, context: Context) -> PT:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class Error(Exception):
|
||||||
|
__slots__ = ('context',)
|
||||||
|
|
||||||
|
def __init__(self, context: Context, exception: Exception) -> None:
|
||||||
|
path = context.format_path()
|
||||||
|
if path:
|
||||||
|
path = '[' + path + '] '
|
||||||
|
if not isinstance(exception, Exception):
|
||||||
|
exception = ValueError(exception)
|
||||||
|
|
||||||
|
super().__init__('{}{}: {}'.format(
|
||||||
|
path, exception.__class__.__name__, str(exception),
|
||||||
|
))
|
||||||
|
self.exception = exception
|
||||||
|
self.context = context.copy()
|
||||||
|
|
||||||
|
class EOF(Error):
|
||||||
|
pass
|
|
@ -0,0 +1,294 @@
|
||||||
|
import os
|
||||||
|
import math
|
||||||
|
import operator
|
||||||
|
import functools
|
||||||
|
from typing import Any, Optional as O, Sequence, Mapping, Callable, Generic as G, TypeVar, Tuple
|
||||||
|
|
||||||
|
from .base import Type, Context, PathElement
|
||||||
|
from .io import Stream, Segment, Pos
|
||||||
|
from .meta import Wrapper
|
||||||
|
from . import to_type
|
||||||
|
|
||||||
|
|
||||||
|
class VarSource(Wrapper):
|
||||||
|
def __init__(self, child: Type, count: int) -> None:
|
||||||
|
super().__init__(child)
|
||||||
|
self.stack: list[Tuple[Context, Segment, Stream, Pos, Any]] = []
|
||||||
|
self.pstack: list[Any] = []
|
||||||
|
self.count = count
|
||||||
|
|
||||||
|
def parse(self, context: Context, stream: Stream) -> Any:
|
||||||
|
pos = stream.tell()
|
||||||
|
value = super().parse(context, stream)
|
||||||
|
for _ in range(self.count):
|
||||||
|
self.stack.append((context, context.segment, stream, pos, value))
|
||||||
|
return value
|
||||||
|
|
||||||
|
def dump(self, context: Context, stream: Stream, value: Any) -> None:
|
||||||
|
pos = stream.tell()
|
||||||
|
for _ in range(self.count):
|
||||||
|
self.stack.append((context, context.segment, stream, pos, value))
|
||||||
|
super().dump(context, stream, value)
|
||||||
|
|
||||||
|
def sizeof(self, context: Context, value: O[Any]) -> None:
|
||||||
|
for _ in range(self.count):
|
||||||
|
self.pstack.append(value)
|
||||||
|
return super().sizeof(context, value)
|
||||||
|
|
||||||
|
def offsetof(self, context: Context, path: Sequence[PathElement], value: O[Any]) -> None:
|
||||||
|
for _ in range(self.count):
|
||||||
|
self.pstack.append(value)
|
||||||
|
return super().offsetof(context, path, value)
|
||||||
|
|
||||||
|
def default(self, context: Context) -> Any:
|
||||||
|
value = super().default(context)
|
||||||
|
for _ in range(self.count):
|
||||||
|
self.pstack.append(value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
symbols = {
|
||||||
|
operator.lt: '<',
|
||||||
|
operator.le: '<=',
|
||||||
|
operator.eq: '==',
|
||||||
|
operator.ne: '!=',
|
||||||
|
operator.ge: '>=',
|
||||||
|
operator.gt: '>',
|
||||||
|
|
||||||
|
operator.not_: 'not ',
|
||||||
|
operator.truth: 'bool ',
|
||||||
|
operator.abs: 'abs ',
|
||||||
|
operator.index: 'int ',
|
||||||
|
operator.inv: '~',
|
||||||
|
operator.neg: '-',
|
||||||
|
operator.pos: '+',
|
||||||
|
|
||||||
|
operator.add: '+',
|
||||||
|
operator.and_: '&',
|
||||||
|
operator.floordiv: '//',
|
||||||
|
operator.lshift: '<<',
|
||||||
|
operator.mod: '%',
|
||||||
|
operator.mul: '*',
|
||||||
|
operator.matmul: '@',
|
||||||
|
operator.or_: '|',
|
||||||
|
operator.pow: '**',
|
||||||
|
operator.rshift: '>>',
|
||||||
|
operator.sub: '-',
|
||||||
|
operator.truediv: '/',
|
||||||
|
operator.xor: '^',
|
||||||
|
}
|
||||||
|
reverse = {
|
||||||
|
operator.not_: operator.not_,
|
||||||
|
|
||||||
|
operator.add: operator.sub,
|
||||||
|
operator.sub: operator.add,
|
||||||
|
operator.truediv: operator.mul,
|
||||||
|
operator.mul: operator.truediv,
|
||||||
|
operator.pos: operator.pos,
|
||||||
|
operator.neg: operator.neg,
|
||||||
|
operator.pow: lambda x, y: math.log(x) / math.log(y),
|
||||||
|
|
||||||
|
operator.inv: operator.inv,
|
||||||
|
operator.lshift: operator.rshift,
|
||||||
|
operator.rshift: operator.lshift,
|
||||||
|
}
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
|
||||||
|
class Expr(G[T]):
|
||||||
|
def _sx_get_(self) -> T:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _sx_peek_(self) -> T:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _sx_put_(self, value: T) -> None:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def __getattr__(self, name: str) -> 'AttrExpr':
|
||||||
|
return AttrExpr(self, name)
|
||||||
|
|
||||||
|
def __getitem__(self, item: Any) -> 'ItemExpr':
|
||||||
|
return ItemExpr(self, item)
|
||||||
|
|
||||||
|
def __call__(self, *args: Any, **kwargs: Any) -> 'CallExpr':
|
||||||
|
return CallExpr(self, args, kwargs)
|
||||||
|
|
||||||
|
for x in ('lt', 'le', 'eq', 'ne', 'ge', 'gt'):
|
||||||
|
locals()['__' + x + '__'] = functools.partialmethod(lambda self, x, other: CompExpr(getattr(operator, x), self, other), x)
|
||||||
|
for x in ('not_', 'truth', 'abs', 'index', 'inv', 'neg', 'pos'):
|
||||||
|
locals()['__' + x + '__'] = functools.partialmethod(lambda self, x: UnaryExpr(getattr(operator, x), self), x)
|
||||||
|
for x in (
|
||||||
|
'add', 'and_', 'floordiv', 'lshift', 'mod', 'mul', 'matmul', 'or_', 'pow', 'rshift', 'sub', 'truediv', 'xor',
|
||||||
|
'concat', 'contains', 'delitem', 'getitem', 'delitem', 'getitem', 'setitem',
|
||||||
|
):
|
||||||
|
locals()['__' + x + '__'] = functools.partialmethod(lambda self, x, other: BinExpr(getattr(operator, x), self, other), x)
|
||||||
|
del x
|
||||||
|
|
||||||
|
class AttrExpr(G[T], Expr[T]):
|
||||||
|
def __init__(self, parent: Expr, attr: str) -> None:
|
||||||
|
self.__parent = parent
|
||||||
|
self.__attr = attr
|
||||||
|
|
||||||
|
def _sx_get_(self) -> T:
|
||||||
|
return getattr(get(self.__parent), get(self.__attr))
|
||||||
|
|
||||||
|
def _sx_peek_(self) -> T:
|
||||||
|
return getattr(peek(self.__parent), peek(self.__attr))
|
||||||
|
|
||||||
|
def _sx_put_(self, value: T) -> None:
|
||||||
|
parent = peek(self.__parent)
|
||||||
|
setattr(parent, peek(self.__attr), value)
|
||||||
|
put(self.__parent, parent)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f'{self.__parent}.{self.__attr}'
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'{self.__parent!r}.{self.__attr}'
|
||||||
|
|
||||||
|
class ItemExpr(G[T], Expr[T]):
|
||||||
|
def __init__(self, parent: Expr, item: Any) -> None:
|
||||||
|
self.__parent = parent
|
||||||
|
self.__item = item
|
||||||
|
|
||||||
|
def _sx_get_(self) -> T:
|
||||||
|
return get(self.__parent)[get(self.__item)]
|
||||||
|
|
||||||
|
def _sx_peek_(self) -> T:
|
||||||
|
return peek(self.__parent)[peek(self.__item)]
|
||||||
|
|
||||||
|
def _sx_put_(self, value: T) -> None:
|
||||||
|
parent = peek(self.__parent)
|
||||||
|
parent[peek(self.__item)] = value
|
||||||
|
put(self.__parent, parent)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'{self.__parent}[{self.__item}]'
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'{self.__parent!r}[{self.__item!r}]'
|
||||||
|
|
||||||
|
class VarExpr(G[T], Expr[T]):
|
||||||
|
def __init__(self, name: str) -> None:
|
||||||
|
self.__name = name
|
||||||
|
self.__source: O[VarSource] = None
|
||||||
|
|
||||||
|
def _sx_resolve_(self, value: VarSource) -> None:
|
||||||
|
self.__source = value
|
||||||
|
|
||||||
|
def _sx_get_(self) -> T:
|
||||||
|
_, _, _, _, value = self.__source.stack.pop()
|
||||||
|
return value
|
||||||
|
|
||||||
|
def _sx_peek_(self) -> T:
|
||||||
|
_, _, _, _, value = self.__source.stack[0]
|
||||||
|
return value
|
||||||
|
|
||||||
|
def _sx_put_(self, value: T) -> None:
|
||||||
|
context, segment, stream, pos, _ = self.__source.stack.pop()
|
||||||
|
with context.enter_segment(segment, stream, pos, os.SEEK_SET) as f:
|
||||||
|
context.dump(to_type(self.__source.child), f, value)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f'${self.__name}:{self.__source}'
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'${self.__name}(=> {self.__source!r})'
|
||||||
|
|
||||||
|
class CallExpr(G[T], Expr[T]):
|
||||||
|
def __init__(self, parent: Expr, args: Sequence[Any], kwargs: Mapping[str, Any]) -> None:
|
||||||
|
self.__parent = parent
|
||||||
|
self.__args = args
|
||||||
|
self.__kwargs = kwargs
|
||||||
|
|
||||||
|
def _sx_get_(self) -> T:
|
||||||
|
return get(self.__parent)(*(get(a) for a in self.__args), **{k: get(v) for k, v in self.__kwargs.items()})
|
||||||
|
|
||||||
|
def _sx_peek_(self) -> T:
|
||||||
|
return peek(self.__parent)(*(peek(a) for a in self.__args), **{k: peek(v) for k, v in self.__kwargs.items()})
|
||||||
|
|
||||||
|
def _sx_put_(self, value: T) -> None:
|
||||||
|
raise NotImplementedError(f'{self.__class__.__name__} is not invertible')
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
args = [repr(a) for a in self.__args]
|
||||||
|
args += [f'{k}: {v}' for k, v in self.__kwargs.items()]
|
||||||
|
a = ', '.join(args)
|
||||||
|
return f'{self.__parent}({a})'
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
args = [repr(a) for a in self.__args]
|
||||||
|
args += [f'{k} = {v!r}' for k, v in self.__kwargs.items()]
|
||||||
|
a = ', '.join(args)
|
||||||
|
return f'{self.__parent!r}({a})'
|
||||||
|
|
||||||
|
|
||||||
|
class UnaryExpr(G[T], Expr[T]):
|
||||||
|
def __init__(self, op: Callable[[Expr], T], value: Expr) -> None:
|
||||||
|
self.__op = op
|
||||||
|
self.__value = value
|
||||||
|
|
||||||
|
def _sx_get_(self) -> T:
|
||||||
|
return self.__op(get(self.__value))
|
||||||
|
|
||||||
|
def _sx_peek_(self) -> T:
|
||||||
|
return self.__op(peek(self.__value))
|
||||||
|
|
||||||
|
def _sx_put_(self, value: T) -> None:
|
||||||
|
if self.__op not in reverse:
|
||||||
|
raise NotImplementedError(f'{self.__class__.__name__} {symbols[self.__op]!r} is not invertible')
|
||||||
|
put(self.__value, reverse[self.__op](value))
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f'({symbols[self.__op]}{self.__value})'
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'({symbols[self.__op]}{self.__value!r})'
|
||||||
|
|
||||||
|
class BinExpr(G[T], Expr[T]):
|
||||||
|
def __init__(self, op: Callable[[Expr, Expr], T], left: Expr, right: Expr) -> None:
|
||||||
|
self.__op = op
|
||||||
|
self.__left = left
|
||||||
|
self.__right = right
|
||||||
|
|
||||||
|
def _sx_get_(self) -> T:
|
||||||
|
return self.__op(get(self.__left), get(self.__right))
|
||||||
|
|
||||||
|
def _sx_peek_(self) -> T:
|
||||||
|
return self.__op(peek(self.__left), peek(self.__right))
|
||||||
|
|
||||||
|
def _sx_put_(self, value: T) -> None:
|
||||||
|
if not isinstance(self.__left, Expr):
|
||||||
|
operand = self.__left
|
||||||
|
target = self.__right
|
||||||
|
elif not isinstance(self.__right, Expr):
|
||||||
|
operand = self.__right
|
||||||
|
target = self.__left
|
||||||
|
else:
|
||||||
|
raise NotImplementedError(f'{self.__class__.__name__} has two expression operands and is not invertible')
|
||||||
|
if self.__op not in reverse:
|
||||||
|
raise NotImplementedError(f'{self.__class__.__name__} {symbols[self.__op]!r} is not invertible')
|
||||||
|
put(target, reverse[self.__op](value, operand))
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f'({self.__left} {symbols[self.__op]} {self.__right})'
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'({self.__left!r} {symbols[self.__op]} {self.__right!r})'
|
||||||
|
|
||||||
|
|
||||||
|
def get(expr: Any) -> Any:
|
||||||
|
if isinstance(expr, Expr):
|
||||||
|
return expr._sx_get_()
|
||||||
|
return expr
|
||||||
|
|
||||||
|
def peek(expr: Any) -> Any:
|
||||||
|
if isinstance(expr, Expr):
|
||||||
|
return expr._sx_peek_()
|
||||||
|
return expr
|
||||||
|
|
||||||
|
def put(expr: Any, value: Any) -> None:
|
||||||
|
if isinstance(expr, Expr):
|
||||||
|
expr._sx_put_(value)
|
|
@ -0,0 +1,166 @@
|
||||||
|
import os
|
||||||
|
import enum
|
||||||
|
import math
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from typing import BinaryIO, Sequence, Optional as O, Union, Mapping, Callable, Dict
|
||||||
|
|
||||||
|
from .util import bits
|
||||||
|
|
||||||
|
|
||||||
|
class BitAlignment(enum.Enum):
|
||||||
|
No = enum.auto()
|
||||||
|
Fill = enum.auto()
|
||||||
|
Yes = enum.auto()
|
||||||
|
|
||||||
|
class Endian(enum.Enum):
|
||||||
|
Little = enum.auto()
|
||||||
|
Big = enum.auto()
|
||||||
|
|
||||||
|
def to_python(self):
|
||||||
|
return {self.Little: 'little', self.Big: 'big'}[self]
|
||||||
|
|
||||||
|
|
||||||
|
Pos = Union[int, float]
|
||||||
|
|
||||||
|
|
||||||
|
class Stream:
|
||||||
|
def __init__(self, handle: BinaryIO, bit_align: BitAlignment = BitAlignment.No, bit_endian: Endian = Endian.Big) -> None:
|
||||||
|
self.handle = handle
|
||||||
|
self.bit_pos: O[int] = None
|
||||||
|
self.bit_val: O[int] = None
|
||||||
|
self.bit_align = bit_align
|
||||||
|
self.bit_endian = bit_endian
|
||||||
|
|
||||||
|
@property
|
||||||
|
def root(self):
|
||||||
|
h = self.handle
|
||||||
|
while isinstance(h, self.__class__):
|
||||||
|
h = self.handle
|
||||||
|
return h
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def wrapped(self, handle):
|
||||||
|
#self.flush()
|
||||||
|
old = self.handle
|
||||||
|
self.handle = handle
|
||||||
|
yield self
|
||||||
|
self.handle = old
|
||||||
|
|
||||||
|
|
||||||
|
def read_bits(self, n: int):
|
||||||
|
if n <= 0:
|
||||||
|
return (0, 0)
|
||||||
|
|
||||||
|
if self.bit_pos is None or self.bit_val is None:
|
||||||
|
self.bit_val = self.read(1)[0]
|
||||||
|
self.bit_pos = 0
|
||||||
|
|
||||||
|
nb = min(8 - self.bit_pos, n)
|
||||||
|
if self.bit_endian == Endian.Big:
|
||||||
|
val = bits(self.bit_val, self.bit_pos, nb)
|
||||||
|
else:
|
||||||
|
val = bits(self.bit_val, 8 - self.bit_pos - nb, nb)
|
||||||
|
|
||||||
|
self.bit_pos += nb
|
||||||
|
if self.bit_pos == 8:
|
||||||
|
self.bit_pos = self.bit_val = None
|
||||||
|
|
||||||
|
return val, n - nb
|
||||||
|
|
||||||
|
def read(self, n: int = -1, bits=False) -> bytes:
|
||||||
|
if bits:
|
||||||
|
val, nl = self.read_bits(n)
|
||||||
|
if self.bit_endian == Endian.Big:
|
||||||
|
val <<= nl
|
||||||
|
if nl >= 8:
|
||||||
|
rounds = nl // 8
|
||||||
|
v = int.from_bytes(self.read(rounds), byteorder=self.bit_endian.to_python())
|
||||||
|
if self.bit_endian == Endian.Big:
|
||||||
|
nl -= rounds * 8
|
||||||
|
v <<= nl
|
||||||
|
else:
|
||||||
|
v <<= (n - nl)
|
||||||
|
nl -= rounds * 8
|
||||||
|
val |= v
|
||||||
|
if nl > 0:
|
||||||
|
v, _ = self.read_bits(nl)
|
||||||
|
if self.bit_endian != Endian.Big:
|
||||||
|
v <<= (n - nl)
|
||||||
|
val |= v
|
||||||
|
return val
|
||||||
|
else:
|
||||||
|
bs = self.handle.read(n)
|
||||||
|
if n > 0 and len(bs) != n:
|
||||||
|
raise EOFError
|
||||||
|
return bs
|
||||||
|
|
||||||
|
def write(self, value: Union[bytes, int], *, bits: O[int] = None) -> None:
|
||||||
|
if bits is not None:
|
||||||
|
return
|
||||||
|
if self.bit_pos:
|
||||||
|
return
|
||||||
|
if isinstance(value, int):
|
||||||
|
raise TypeError
|
||||||
|
self.handle.write(value)
|
||||||
|
|
||||||
|
def tell(self) -> Pos:
|
||||||
|
pos: Pos = self.handle.tell()
|
||||||
|
if self.bit_pos:
|
||||||
|
pos -= 1
|
||||||
|
pos += self.bit_pos / 8
|
||||||
|
return pos
|
||||||
|
|
||||||
|
def seek(self, n: Pos, whence: int = os.SEEK_SET) -> None:
|
||||||
|
if isinstance(n, float):
|
||||||
|
bp = int((n % 1) * 8)
|
||||||
|
n = int(n)
|
||||||
|
else:
|
||||||
|
bp = 0
|
||||||
|
self.handle.seek(n, whence)
|
||||||
|
if bp:
|
||||||
|
self.read_bits(bp)
|
||||||
|
|
||||||
|
class Segment:
|
||||||
|
__slots__ = ('name', 'offset', 'dependents', 'pos')
|
||||||
|
|
||||||
|
def __init__(self, name: str, dependents: Sequence['Segment'] = None) -> None:
|
||||||
|
self.name = name
|
||||||
|
self.offset: O[Pos] = None
|
||||||
|
self.dependents = dependents or []
|
||||||
|
self.pos: O[Pos] = None
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.offset = self.pos = None
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'<{__name__}.{self.__class__.__name__}: {self.name}>'
|
||||||
|
|
||||||
|
|
||||||
|
def process_sizes(s: Sequence[Mapping[Segment, Pos]], cb: Callable[[Pos, Pos], Pos]) -> Dict[Segment, O[Pos]]:
|
||||||
|
sizes: Dict[Segment, O[Pos]] = {}
|
||||||
|
for prev in s:
|
||||||
|
for k, n in prev.items():
|
||||||
|
p = sizes.get(k, 0)
|
||||||
|
if p is None or n is None:
|
||||||
|
sizes[k] = None
|
||||||
|
else:
|
||||||
|
sizes[k] = cb(p, n)
|
||||||
|
return sizes
|
||||||
|
|
||||||
|
def min_sizes(*s: Mapping[Segment, Pos]) -> Dict[Segment, O[Pos]]:
|
||||||
|
return process_sizes(s, min)
|
||||||
|
|
||||||
|
def max_sizes(*s: Mapping[Segment, Pos]) -> Dict[Segment, O[Pos]]:
|
||||||
|
return process_sizes(s, max)
|
||||||
|
|
||||||
|
def add_sizes(*s: Mapping[Segment, Pos]) -> Dict[Segment, O[Pos]]:
|
||||||
|
return process_sizes(s, lambda a, b: a + b)
|
||||||
|
|
||||||
|
def ceil_sizes(s: Mapping[Segment, O[Pos]]) -> Dict[Segment, O[int]]:
|
||||||
|
d: Dict[Segment, O[int]] = {}
|
||||||
|
for k, v in s.items():
|
||||||
|
if v is not None:
|
||||||
|
d[k] = math.ceil(v)
|
||||||
|
else:
|
||||||
|
d[k] = v
|
||||||
|
return d
|
|
@ -0,0 +1,87 @@
|
||||||
|
from typing import Optional as O, Generic as G, Sequence, TypeVar, Any
|
||||||
|
from .base import Type, Context, PathElement, Error
|
||||||
|
from .io import Stream
|
||||||
|
from . import to_type
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
class Wrapper(G[T], Type[T]):
|
||||||
|
def __init__(self, child: Type[T]) -> None:
|
||||||
|
self.child = child
|
||||||
|
|
||||||
|
def parse(self, context: Context, stream: Stream) -> T:
|
||||||
|
return context.parse(to_type(self.child), stream)
|
||||||
|
|
||||||
|
def dump(self, context: Context, stream: Stream, value: O[T]) -> None:
|
||||||
|
context.dump(to_type(self.child), stream, value)
|
||||||
|
|
||||||
|
def sizeof(self, context: Context, value: O[T]) -> O[int]:
|
||||||
|
return context.sizeof(to_type(self.child), value)
|
||||||
|
|
||||||
|
def offsetof(self, context: Context, path: Sequence[PathElement], value: O[T]) -> O[int]:
|
||||||
|
return context.offsetof(to_type(self.child), path, value)
|
||||||
|
|
||||||
|
def default(self, context: Context) -> T:
|
||||||
|
return context.default(to_type(self.child))
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return str(self.child)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return repr(self.child)
|
||||||
|
|
||||||
|
class Generic(Type):
|
||||||
|
__slots__ = ('name', 'stack')
|
||||||
|
|
||||||
|
def __init__(self, name: str) -> None:
|
||||||
|
self.name = name
|
||||||
|
self.stack = []
|
||||||
|
|
||||||
|
def push(self, value: Any) -> None:
|
||||||
|
if isinstance(value, Generic):
|
||||||
|
self.stack.append(value.stack[-1])
|
||||||
|
else:
|
||||||
|
self.stack.append(value)
|
||||||
|
|
||||||
|
def pop(self) -> None:
|
||||||
|
self.stack.pop()
|
||||||
|
|
||||||
|
def _get_sx_type_(self, ident: Any) -> Type:
|
||||||
|
return to_type(self.stack[-1])
|
||||||
|
|
||||||
|
def parse(self, context: Context, stream: Stream) -> Any:
|
||||||
|
if not self.stack:
|
||||||
|
raise Error(context, 'unresolved generic')
|
||||||
|
return context.parse(to_type(self.stack[-1]), stream)
|
||||||
|
|
||||||
|
def dump(self, context: Context, stream: Stream, value: O[Any]) -> None:
|
||||||
|
if not self.stack:
|
||||||
|
raise Error(context, 'unresolved generic')
|
||||||
|
context.dump(to_type(self.stack[-1]), stream, value)
|
||||||
|
|
||||||
|
def sizeof(self, context: Context, value: O[Any]) -> O[int]:
|
||||||
|
if not self.stack:
|
||||||
|
return None
|
||||||
|
return context.sizeof(to_type(self.stack[-1]), value)
|
||||||
|
|
||||||
|
def offsetof(self, context: Context, path: Sequence[PathElement], value: O[Any]) -> O[int]:
|
||||||
|
if not self.stack:
|
||||||
|
return None
|
||||||
|
return context.offsetof(to_type(self.stack[-1]), path, value)
|
||||||
|
|
||||||
|
def default(self, context: Context) -> Any:
|
||||||
|
if not self.stack:
|
||||||
|
raise Error(context, 'unresolved generic')
|
||||||
|
return context.default(to_type(self.stack[-1]))
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
if self.stack:
|
||||||
|
return f'${self.name}:{to_type(self.stack[-1])}'
|
||||||
|
return f'${self.name}:unresolved'
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'<{__name__}.{self.__class__.__name__}({self.stack!r})>'
|
||||||
|
|
||||||
|
def __deepcopy__(self, memo: Any) -> Any:
|
||||||
|
return self
|
|
@ -0,0 +1,67 @@
|
||||||
|
import os
|
||||||
|
import math
|
||||||
|
import collections
|
||||||
|
from typing import BinaryIO, Generator, Callable, Union as U, Any, cast
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
|
||||||
|
def bits(v: int, s: int, l: int) -> int:
|
||||||
|
return (v >> s) & ((1 << l) - 1)
|
||||||
|
|
||||||
|
def bit(v: int, s: int) -> int:
|
||||||
|
return bits(v, s, 1)
|
||||||
|
|
||||||
|
Pos = U[int, float]
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def seeking(fd: BinaryIO, pos: Pos, whence: int = os.SEEK_SET) -> Generator[BinaryIO, None, None]:
|
||||||
|
oldpos = fd.tell()
|
||||||
|
fd.seek(cast(int, pos), whence)
|
||||||
|
try:
|
||||||
|
yield fd
|
||||||
|
finally:
|
||||||
|
fd.seek(oldpos, os.SEEK_SET)
|
||||||
|
|
||||||
|
|
||||||
|
def indent(s: str, count: int, start: bool = False) -> str:
|
||||||
|
""" Indent all lines of a string. """
|
||||||
|
lines = s.splitlines()
|
||||||
|
for i in range(0 if start else 1, len(lines)):
|
||||||
|
lines[i] = ' ' * count + lines[i]
|
||||||
|
return '\n'.join(lines)
|
||||||
|
|
||||||
|
def format_bytes(bs: bytes) -> str:
|
||||||
|
return '[' + ' '.join(hex(b)[2:].zfill(2) for b in bs) + ']'
|
||||||
|
|
||||||
|
def format_value(value: Any, formatter: Callable[[Any], str], indentation: int = 0) -> str:
|
||||||
|
""" Format containers to use the given formatter function instead of always repr(). """
|
||||||
|
if isinstance(value, (dict, collections.Mapping)):
|
||||||
|
if value:
|
||||||
|
fmt = '{{\n{}\n}}'
|
||||||
|
values = [indent(',\n'.join('{}: {}'.format(
|
||||||
|
format_value(k, formatter),
|
||||||
|
format_value(v, formatter)
|
||||||
|
) for k, v in value.items()), 2, True)]
|
||||||
|
else:
|
||||||
|
fmt = '{{}}'
|
||||||
|
values = []
|
||||||
|
elif isinstance(value, (list, set, frozenset)):
|
||||||
|
l = len(value)
|
||||||
|
is_set = isinstance(value, (set, frozenset))
|
||||||
|
if l > 3:
|
||||||
|
fmt = '{{\n{}\n}}' if is_set else '[\n{}\n]'
|
||||||
|
values = [indent(',\n'.join(format_value(v, formatter) for v in value), 2, True)]
|
||||||
|
elif l > 0:
|
||||||
|
fmt = '{{{}}}' if is_set else '[{}]'
|
||||||
|
values = [', '.join(format_value(v, formatter) for v in value)]
|
||||||
|
else:
|
||||||
|
fmt = '{{}}' if is_set else '[]'
|
||||||
|
values = []
|
||||||
|
elif isinstance(value, (bytes, bytearray)):
|
||||||
|
fmt = '{}'
|
||||||
|
values = [format_bytes(value)]
|
||||||
|
else:
|
||||||
|
fmt = '{}'
|
||||||
|
values = [formatter(value)]
|
||||||
|
return indent(fmt.format(*values), indentation)
|
|
@ -0,0 +1,29 @@
|
||||||
|
from typing import Optional as O, Union as U
|
||||||
|
from ..core.base import Type, Context, PossibleDynamic as D
|
||||||
|
from ..core.io import Stream
|
||||||
|
|
||||||
|
class Data(Type[bytes]):
|
||||||
|
__slots__ = ('size',)
|
||||||
|
|
||||||
|
def __init__(self, size: U[D, O[int]] = None) -> None:
|
||||||
|
self.size = size
|
||||||
|
|
||||||
|
def parse(self, context: Context, stream: Stream) -> bytes:
|
||||||
|
size = context.get(self.size)
|
||||||
|
if size is None:
|
||||||
|
size = -1
|
||||||
|
return stream.read(size)
|
||||||
|
|
||||||
|
def dump(self, context: Context, stream: Stream, value: bytes) -> None:
|
||||||
|
stream.write(value)
|
||||||
|
|
||||||
|
def default(self, context: Context) -> bytes:
|
||||||
|
size = context.peek(self.size)
|
||||||
|
if size is None:
|
||||||
|
size = 0
|
||||||
|
return bytes(size)
|
||||||
|
|
||||||
|
def sizeof(self, context: Context, value: O[bytes]) -> O[int]:
|
||||||
|
return context.peek(self.size)
|
||||||
|
|
||||||
|
data = Data()
|
|
@ -0,0 +1,73 @@
|
||||||
|
from typing import Optional as O, Union as U
|
||||||
|
from ..core.base import Type, Context, PossibleDynamic
|
||||||
|
from ..core.io import Stream, Endian
|
||||||
|
from .transforms import Mapped
|
||||||
|
|
||||||
|
|
||||||
|
class Int(Type[int]):
|
||||||
|
__slots__ = ('bits', 'endian', 'signed')
|
||||||
|
|
||||||
|
def __init__(self, bits: PossibleDynamic[int], endian: PossibleDynamic[Endian], signed: PossibleDynamic[bool]) -> None:
|
||||||
|
self.bits = bits
|
||||||
|
self.endian = endian
|
||||||
|
self.signed = signed
|
||||||
|
|
||||||
|
def parse(self, context: Context, stream: Stream) -> int:
|
||||||
|
n = context.get(self.bits)
|
||||||
|
bs = stream.read(n // 8)
|
||||||
|
return int.from_bytes(bs, byteorder=context.get(self.endian).to_python(), signed=context.get(self.signed))
|
||||||
|
|
||||||
|
def dump(self, context: Context, stream: Stream, value: U[int, float]) -> None:
|
||||||
|
if isinstance(value, float):
|
||||||
|
if value.is_integer():
|
||||||
|
value = int(value)
|
||||||
|
else:
|
||||||
|
raise ValueError(f'can not encode float {value!r} as integer')
|
||||||
|
n = context.get(self.bits)
|
||||||
|
bs = value.to_bytes(n // 8, byteorder=context.get(self.endian).to_python(), signed=context.get(self.signed))
|
||||||
|
return stream.write(bs)
|
||||||
|
|
||||||
|
def default(self, context: Context) -> int:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def sizeof(self, context: Context, value: O[int]) -> O[int]:
|
||||||
|
size = context.peek(self.bits)
|
||||||
|
if size is not None:
|
||||||
|
size //= 8
|
||||||
|
return size
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
endian = {Endian.Big: 'be', Endian.Little: 'le'}.get(self.endian, self.endian) if self.bits != 8 else ''
|
||||||
|
sign = {True: '', False: 'u'}.get(self.signed, self.signed)
|
||||||
|
return f'{sign}int{self.bits}{endian}'
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'<{__name__}.{self.__class__.__name__}({self.bits!r}, {self.endian!r}, signed: {self.signed!r})>'
|
||||||
|
|
||||||
|
int8 = Int(8, endian=Endian.Little, signed=True)
|
||||||
|
uint8 = Int(8, endian=Endian.Little, signed=False)
|
||||||
|
|
||||||
|
int16le = Int(16, endian=Endian.Little, signed=True)
|
||||||
|
int16be = Int(16, endian=Endian.Big, signed=True)
|
||||||
|
uint16le = Int(16, endian=Endian.Little, signed=False)
|
||||||
|
uint16be = Int(16, endian=Endian.Big, signed=False)
|
||||||
|
|
||||||
|
int32le = Int(32, endian=Endian.Little, signed=True)
|
||||||
|
int32be = Int(32, endian=Endian.Big, signed=True)
|
||||||
|
uint32le = Int(32, endian=Endian.Little, signed=False)
|
||||||
|
uint32be = Int(32, endian=Endian.Big, signed=False)
|
||||||
|
|
||||||
|
int64le = Int(64, endian=Endian.Little, signed=True)
|
||||||
|
int64be = Int(64, endian=Endian.Big, signed=True)
|
||||||
|
uint64le = Int(64, endian=Endian.Little, signed=False)
|
||||||
|
uint64be = Int(64, endian=Endian.Big, signed=False)
|
||||||
|
|
||||||
|
|
||||||
|
class Bool(Type[bool]):
|
||||||
|
def __new__(self, child: Type, true_value: int = 1, false_value: int = 0) -> Mapped:
|
||||||
|
return Mapped(child, {true_value: True, false_value: False},
|
||||||
|
str='bool',
|
||||||
|
repr=f'<{__name__}.Bool({child!r}, true: {true_value!r}, false: {false_value!r})>',
|
||||||
|
)
|
||||||
|
|
||||||
|
bool = Bool(uint8)
|
|
@ -0,0 +1,200 @@
|
||||||
|
from typing import Optional as O, Union as U, Callable, Any, List, Sequence, Mapping, Generic as G, TypeVar, Tuple as Tu
|
||||||
|
from types import FunctionType
|
||||||
|
|
||||||
|
from ..core.base import PossibleDynamic as D, Type, Context, PathElement
|
||||||
|
from ..core.io import Stream, add_sizes
|
||||||
|
from ..core import to_type
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar('T', bound=Type)
|
||||||
|
|
||||||
|
class Arr(G[T], Type[List[T]]):
|
||||||
|
def __init__(self, child: D[T], count: O[D[int]] = None, stop: O[U[D[Any], Callable[[Any], bool]]] = None, include_stop: D[bool] = False) -> None:
|
||||||
|
self.child = child
|
||||||
|
self.count = count
|
||||||
|
self.stop = stop
|
||||||
|
self.include_stop = include_stop
|
||||||
|
|
||||||
|
def parse(self, context: Context, stream: Stream) -> List[T]:
|
||||||
|
child = context.get(self.child)
|
||||||
|
count = context.get(self.count)
|
||||||
|
stop = context.get(self.stop)
|
||||||
|
include_stop = context.get(self.include_stop)
|
||||||
|
|
||||||
|
value = []
|
||||||
|
while True:
|
||||||
|
i = len(value)
|
||||||
|
if count is not None and i >= count:
|
||||||
|
break
|
||||||
|
c = to_type(child)
|
||||||
|
with context.enter(i, c):
|
||||||
|
try:
|
||||||
|
elem = context.parse(c, stream)
|
||||||
|
except EOFError:
|
||||||
|
if count is None:
|
||||||
|
break
|
||||||
|
raise
|
||||||
|
if stop is not None:
|
||||||
|
if isinstance(stop, FunctionType):
|
||||||
|
should_stop = stop(elem)
|
||||||
|
else:
|
||||||
|
should_stop = elem == stop
|
||||||
|
if should_stop:
|
||||||
|
if include_stop:
|
||||||
|
value.append(elem)
|
||||||
|
break
|
||||||
|
value.append(elem)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def dump(self, context: Context, stream: Stream, value: List[T]) -> None:
|
||||||
|
child = context.get(self.child)
|
||||||
|
count = context.get(self.count)
|
||||||
|
stop = context.get(self.stop)
|
||||||
|
include_stop = context.get(self.include_stop)
|
||||||
|
|
||||||
|
if stop is not None and not isinstance(stop, FunctionType) and not include_stop:
|
||||||
|
value += [stop]
|
||||||
|
|
||||||
|
for i, elem in enumerate(value):
|
||||||
|
c = to_type(child)
|
||||||
|
with context.enter(i, c):
|
||||||
|
context.dump(child, stream, elem)
|
||||||
|
|
||||||
|
context.put(self.count, len(value))
|
||||||
|
|
||||||
|
def get_sizes(self, context: Context, value: O[List[T]], n: int) -> Mapping[str, int]:
|
||||||
|
child = context.peek(self.child)
|
||||||
|
stop = context.peek(self.stop)
|
||||||
|
|
||||||
|
sizes = []
|
||||||
|
for i in range(n):
|
||||||
|
c = to_type(child)
|
||||||
|
if value is not None:
|
||||||
|
elem = value[i]
|
||||||
|
else:
|
||||||
|
elem = None
|
||||||
|
with context.enter(i, c):
|
||||||
|
size = context.sizeof(c, elem)
|
||||||
|
sizes.append(size)
|
||||||
|
|
||||||
|
if stop is not None and not isinstance(stop, FunctionType):
|
||||||
|
sizes.append(context.sizeof(child, stop))
|
||||||
|
|
||||||
|
return sizes
|
||||||
|
|
||||||
|
def sizeof(self, context: Context, value: O[List[T]]) -> O[Mapping[str, int]]:
|
||||||
|
if value is not None:
|
||||||
|
count = len(value)
|
||||||
|
else:
|
||||||
|
count = context.peek(self.count)
|
||||||
|
if count is None:
|
||||||
|
return None
|
||||||
|
sizes = self.get_sizes(context, value, count)
|
||||||
|
return add_sizes(*sizes) if sizes else 0
|
||||||
|
|
||||||
|
def offsetof(self, context: Context, path: Sequence[PathElement], value: O[List[T]]) -> O[int]:
|
||||||
|
if not path:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
i = path[0]
|
||||||
|
path = path[1:]
|
||||||
|
if not isinstance(i, int):
|
||||||
|
raise ValueError('path element for array must be integer')
|
||||||
|
|
||||||
|
child = context.peek(self.child)
|
||||||
|
sizes = self.get_sizes(context, value, i)
|
||||||
|
if path:
|
||||||
|
c = to_type(child)
|
||||||
|
with context.enter(i, c):
|
||||||
|
sizes.append(context.offsetof(c, path, value[i] if value is not None else None))
|
||||||
|
return add_sizes(*sizes) if sizes else 0
|
||||||
|
|
||||||
|
def default(self, context: Context) -> List[T]:
|
||||||
|
child = context.peek(self.child)
|
||||||
|
count = context.peek(self.count)
|
||||||
|
if count is None or child is None:
|
||||||
|
value = []
|
||||||
|
else:
|
||||||
|
value = [context.default(child) for _ in range(count)]
|
||||||
|
return value
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
if self.count is not None:
|
||||||
|
count = repr(self.count)
|
||||||
|
else:
|
||||||
|
count = ''
|
||||||
|
return f'{self.child}[{count}]'
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'<{__name__}.{self.__class__.__name__}({self.child!r}, count: {self.count!r}, stop: {self.stop!r}, include_stop: {self.include_stop!r})>'
|
||||||
|
|
||||||
|
|
||||||
|
class Tuple(Type):
|
||||||
|
def __init__(self, *children: Type) -> None:
|
||||||
|
self.children = children
|
||||||
|
|
||||||
|
def parse(self, context: Context, stream: Stream) -> Tu:
|
||||||
|
values = []
|
||||||
|
|
||||||
|
for i, child in enumerate(self.children):
|
||||||
|
c = to_type(child)
|
||||||
|
with context.enter(i, c):
|
||||||
|
elem = context.parse(c, stream)
|
||||||
|
values.append(elem)
|
||||||
|
|
||||||
|
return tuple(values)
|
||||||
|
|
||||||
|
def dump(self, context: Context, stream: Stream, value: Tu) -> None:
|
||||||
|
for i, (child, elem) in enumerate(zip(self.children, value)):
|
||||||
|
c = to_type(child)
|
||||||
|
with context.enter(i, c):
|
||||||
|
context.dump(c, stream, elem)
|
||||||
|
|
||||||
|
def sizeof(self, context: Context, value: O[Tu]) -> O[int]:
|
||||||
|
sizes = []
|
||||||
|
|
||||||
|
if value is None:
|
||||||
|
value = [None] * len(self.children)
|
||||||
|
for i, (child, elem) in enumerate(zip(self.children, value)):
|
||||||
|
c = to_type(child)
|
||||||
|
with context.enter(i, c):
|
||||||
|
sizes.append(context.sizeof(c, elem))
|
||||||
|
|
||||||
|
return add_sizes(*sizes)
|
||||||
|
|
||||||
|
def offsetof(self, context: Context, path: Sequence[PathElement], value: O[Tu]) -> O[int]:
|
||||||
|
if not path:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
n = path[0]
|
||||||
|
path = path[1:]
|
||||||
|
if not isinstance(n, int):
|
||||||
|
raise ValueError('path element for tuple must be integer')
|
||||||
|
|
||||||
|
sizes = []
|
||||||
|
|
||||||
|
if value is None:
|
||||||
|
value = [None] * len(self.children)
|
||||||
|
for i, (child, elem) in enumerate(zip(self.children, value)):
|
||||||
|
if i >= n:
|
||||||
|
break
|
||||||
|
c = to_type(child)
|
||||||
|
with context.enter(i, c):
|
||||||
|
sizes.append(context.sizeof(c, elem))
|
||||||
|
|
||||||
|
if path:
|
||||||
|
c = to_type(child)
|
||||||
|
with context.enter(n, c):
|
||||||
|
sizes.append(context.offsetof(c, path, elem))
|
||||||
|
|
||||||
|
return add_sizes(*sizes)
|
||||||
|
|
||||||
|
def default(self, context: Context) -> Tu:
|
||||||
|
return tuple(context.default(c) for c in self.children)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return '(' + ', '.join(str(c) for c in self.children) + ')'
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'<{__name__}.{self.__class__.__name__}(' + ', '.join(repr(c) for c in self.children) + ')>'
|
|
@ -0,0 +1,54 @@
|
||||||
|
import enum
|
||||||
|
from typing import Optional as O, Union as U
|
||||||
|
from ..core.base import PossibleDynamic as D, Type, Context
|
||||||
|
from ..core.io import Stream
|
||||||
|
from .int import uint8
|
||||||
|
|
||||||
|
|
||||||
|
class StrType(enum.Enum):
|
||||||
|
Raw = enum.auto()
|
||||||
|
ZeroTerminated = C = enum.auto()
|
||||||
|
LengthPrefixed = Pascal = enum.auto()
|
||||||
|
|
||||||
|
class Str(Type[str]):
|
||||||
|
def __init__(self, type: U[D, StrType], length: U[D, O[int]] = None, encoding: U[D, str] = 'utf-8', char_size: U[D, int] = 1, length_type: U[D, Type] = uint8, terminator: U[D, bytes] = b'\x00') -> None:
|
||||||
|
self.type = type
|
||||||
|
self.length = length
|
||||||
|
self.encoding = encoding
|
||||||
|
self.char_size = char_size
|
||||||
|
self.length_type = length_type
|
||||||
|
self.terminator = terminator
|
||||||
|
|
||||||
|
def parse(self, context: Context, stream: Stream) -> str:
|
||||||
|
type = context.get(self.type)
|
||||||
|
length = context.get(self.length)
|
||||||
|
encoding = context.get(self.encoding)
|
||||||
|
char_size = context.get(self.char_size)
|
||||||
|
|
||||||
|
if type == StrType.Raw:
|
||||||
|
if length is None:
|
||||||
|
raise ValueError('tried to parse raw string with no specified length')
|
||||||
|
data = stream.read(length * char_size)
|
||||||
|
elif type == StrType.C:
|
||||||
|
terminator = context.get(self.terminator)
|
||||||
|
data = b''
|
||||||
|
while True:
|
||||||
|
d = stream.read(char_size)
|
||||||
|
if d == terminator:
|
||||||
|
break
|
||||||
|
data += d
|
||||||
|
if length is not None and len(data) >= length:
|
||||||
|
break
|
||||||
|
elif type == StrType.Pascal:
|
||||||
|
length_type = context.get(self.length_type)
|
||||||
|
plength = context.parse(length_type, stream)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def dump(self, context: Context, stream: Stream, value: str) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def default(self, context: Context) -> str:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def sizeof(self, context: Context, value: O[str]) -> O[int]:
|
||||||
|
return 0
|
|
@ -0,0 +1,292 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from typing import (
|
||||||
|
Any, Callable, Iterator, Annotated,
|
||||||
|
Union as U, Optional as O, Generic as G, TypeVar, Type as Ty,
|
||||||
|
Tuple, List, Mapping, Sequence,
|
||||||
|
)
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
import sx
|
||||||
|
from ..core import to_type
|
||||||
|
from ..core.base import Context, Type, PathElement
|
||||||
|
from ..core.io import Stream, Pos, add_sizes
|
||||||
|
from ..core.util import indent, format_value
|
||||||
|
from ..core.meta import Generic
|
||||||
|
from ..core.expr import VarExpr, VarSource
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
class StructType(G[T], Type[T]):
|
||||||
|
__slots__ = ('fields', 'cls', 'partial', 'union', 'generics', 'bound')
|
||||||
|
|
||||||
|
def __init__(self, fields, cls: Ty[T], generics: Sequence[Generic] = (), union: bool = False, partial: bool = False, bound: Sequence[Type] = ()) -> None:
|
||||||
|
self.fields = fields
|
||||||
|
self.cls = cls
|
||||||
|
self.union = union
|
||||||
|
self.partial = partial
|
||||||
|
self.generics = generics
|
||||||
|
self.bound = bound
|
||||||
|
|
||||||
|
def __getitem__(self, item: U[Type, Tuple[Type, ...]]) -> StructType[T]:
|
||||||
|
if not isinstance(item, tuple):
|
||||||
|
item = (item,)
|
||||||
|
|
||||||
|
bound = self.bound + item
|
||||||
|
if len(bound) > len(self.generics):
|
||||||
|
raise TypeError('too many generics arguments for {}: {}'.format(
|
||||||
|
self.__class__.__name__, len(bound)
|
||||||
|
))
|
||||||
|
|
||||||
|
subtype = self.__class__(self.fields, self.cls, self.generics, self.union, self.partial, bound=bound)
|
||||||
|
return subtype
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def enter(self):
|
||||||
|
for g, child in zip(self.generics, self.bound):
|
||||||
|
g.push(child)
|
||||||
|
yield
|
||||||
|
for g, _ in zip(self.generics, self.bound):
|
||||||
|
g.pop()
|
||||||
|
|
||||||
|
def parse(self, context: Context, stream: Stream) -> T:
|
||||||
|
n: Pos = 0
|
||||||
|
pos = stream.tell()
|
||||||
|
|
||||||
|
c = self.cls.__new__(self.cls)
|
||||||
|
did_eof = False
|
||||||
|
with self.enter():
|
||||||
|
for name, type in self.fields.items():
|
||||||
|
if did_eof:
|
||||||
|
setattr(c, name, None)
|
||||||
|
continue
|
||||||
|
|
||||||
|
with context.enter(name, type):
|
||||||
|
if type is None:
|
||||||
|
continue
|
||||||
|
if self.union:
|
||||||
|
stream.seek(pos, os.SEEK_SET)
|
||||||
|
|
||||||
|
try:
|
||||||
|
val = context.parse(to_type(type), stream)
|
||||||
|
except EOFError:
|
||||||
|
if self.partial:
|
||||||
|
did_eof = True
|
||||||
|
setattr(c, name, None)
|
||||||
|
continue
|
||||||
|
raise
|
||||||
|
|
||||||
|
nbytes = stream.tell() - pos
|
||||||
|
if self.union:
|
||||||
|
n = max(n, nbytes)
|
||||||
|
else:
|
||||||
|
n = nbytes
|
||||||
|
|
||||||
|
setattr(c, name, val)
|
||||||
|
hook = 'on_parse_' + name
|
||||||
|
if hasattr(c, hook):
|
||||||
|
getattr(c, hook)(self.fields, context)
|
||||||
|
|
||||||
|
stream.seek(pos + n, os.SEEK_SET)
|
||||||
|
return c
|
||||||
|
|
||||||
|
def dump(self, context: Context, stream: Stream, value: T) -> None:
|
||||||
|
n: Pos = 0
|
||||||
|
pos = stream.tell()
|
||||||
|
|
||||||
|
with self.enter():
|
||||||
|
for name, type in self.fields.items():
|
||||||
|
with context.enter(name, type):
|
||||||
|
if self.union:
|
||||||
|
stream.seek(pos, os.SEEK_SET)
|
||||||
|
|
||||||
|
hook = 'on_dump_' + name
|
||||||
|
if hasattr(value, hook):
|
||||||
|
getattr(value, hook)(self.fields, context)
|
||||||
|
|
||||||
|
field = getattr(value, name)
|
||||||
|
context.dump(to_type(type), stream, field)
|
||||||
|
|
||||||
|
nbytes = stream.tell() - pos
|
||||||
|
if self.union:
|
||||||
|
n = max(n, nbytes)
|
||||||
|
else:
|
||||||
|
n = nbytes
|
||||||
|
|
||||||
|
stream.seek(pos + n, os.SEEK_SET)
|
||||||
|
|
||||||
|
def default(self, context: Context) -> T:
|
||||||
|
return self.cls()
|
||||||
|
|
||||||
|
def get_sizes(self, context: Context, value: O[Any], n: str) -> List[Mapping[str, int]]:
|
||||||
|
sizes = []
|
||||||
|
for field, child in self.fields.items():
|
||||||
|
if field == n:
|
||||||
|
break
|
||||||
|
if value is not None:
|
||||||
|
elem = getattr(value, field)
|
||||||
|
else:
|
||||||
|
elem = None
|
||||||
|
with context.enter(field, child):
|
||||||
|
size = context.sizeof(child, elem)
|
||||||
|
sizes.append(size)
|
||||||
|
return sizes
|
||||||
|
|
||||||
|
def sizeof(self, context: Context, value: O[T]) -> O[Mapping[str, int]]:
|
||||||
|
with self.enter():
|
||||||
|
sizes = self.get_sizes(context, value, None)
|
||||||
|
return add_sizes(*sizes) if sizes else 0
|
||||||
|
|
||||||
|
def offsetof(self, context: Context, path: Sequence[PathElement], value: O[T]) -> O[int]:
|
||||||
|
if not path:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
field = path[0]
|
||||||
|
path = path[1:]
|
||||||
|
if not isinstance(field, str):
|
||||||
|
raise ValueError('path element for struct must be string')
|
||||||
|
if field not in self.fields:
|
||||||
|
raise ValueError(f'field {field!r} invalid for {self.cls.__name__}')
|
||||||
|
|
||||||
|
child = self.fields[field]
|
||||||
|
with self.enter():
|
||||||
|
sizes = self.get_sizes(context, value, field)
|
||||||
|
if path:
|
||||||
|
with context.enter(field, child):
|
||||||
|
sizes.append(context.offsetof(child, path, getattr(value, field) if value is not None else None))
|
||||||
|
return add_sizes(*sizes) if sizes else 0
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
if self.fields:
|
||||||
|
with self.enter():
|
||||||
|
fields = '{\n'
|
||||||
|
for f, v in self.fields.items():
|
||||||
|
fields += ' ' + f + ': ' + indent(format_value(to_type(v), str), 2) + ',\n'
|
||||||
|
fields += '}'
|
||||||
|
else:
|
||||||
|
fields = '{}'
|
||||||
|
return f'{self.cls.__name__} {fields}'
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
type = 'Union' if self.union else 'Struct'
|
||||||
|
if self.fields:
|
||||||
|
with self.enter():
|
||||||
|
fields = '{\n'
|
||||||
|
for f, v in self.fields.items():
|
||||||
|
fields += ' ' + f + ': ' + indent(format_value(to_type(v), repr), 2) + ',\n'
|
||||||
|
fields += '}'
|
||||||
|
else:
|
||||||
|
fields = '{}'
|
||||||
|
return f'<{__name__}.{type}({self.cls.__name__}) {fields}>'
|
||||||
|
|
||||||
|
class Struct:
|
||||||
|
__slots__ = ()
|
||||||
|
_sx_type_ = None
|
||||||
|
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
st = self._sx_type_
|
||||||
|
with st.enter():
|
||||||
|
for k, t in st.fields.items():
|
||||||
|
if k not in kwargs:
|
||||||
|
v = sx.default(t)
|
||||||
|
else:
|
||||||
|
v = kwargs.pop(k)
|
||||||
|
setattr(self, k, v)
|
||||||
|
for name, value in kwargs.items():
|
||||||
|
setattr(self, name, value)
|
||||||
|
|
||||||
|
def __init_subclass__(cls, *, inject: bool = True, generics: Sequence[Generic] = (), **kwargs: Any):
|
||||||
|
super().__init_subclass__()
|
||||||
|
|
||||||
|
# Get all generics definition
|
||||||
|
parent: O[StructType] = getattr(cls, '_sx_type_', None)
|
||||||
|
bound: Sequence[Type] = ()
|
||||||
|
generics = tuple(generics)
|
||||||
|
if parent:
|
||||||
|
generics = parent.generics + generics
|
||||||
|
bound = parent.bound + bound
|
||||||
|
|
||||||
|
# Get all annotations
|
||||||
|
annots = {}
|
||||||
|
localns = {'Self': cls}
|
||||||
|
for c in reversed(cls.__mro__):
|
||||||
|
globalns = sys.modules[c.__module__].__dict__
|
||||||
|
annots.update({k: (globalns, v) for k, v in getattr(c, '__annotations__', {}).items()})
|
||||||
|
localns[c.__name__] = c
|
||||||
|
if inject:
|
||||||
|
localns.update({x: getattr(sx, x) for x in sx.__all__})
|
||||||
|
localns.update({g.name: g for g in generics})
|
||||||
|
|
||||||
|
# Evaluate annotations into fields
|
||||||
|
fields = {}
|
||||||
|
refs: Mapping[str, VarExpr] = {}
|
||||||
|
for name, (globalns, value) in annots.items():
|
||||||
|
val = eval(value, globalns, localns)
|
||||||
|
if isinstance(val, Annotated):
|
||||||
|
val = next(v for v in val.__metadata__ if isinstance(v, Type))
|
||||||
|
fields[name] = val
|
||||||
|
localns[name] = refs[name] = VarExpr(name)
|
||||||
|
|
||||||
|
del localns
|
||||||
|
for name, r in refs.items():
|
||||||
|
count = sys.getrefcount(r) - 4 # cursed
|
||||||
|
if count:
|
||||||
|
fields[name] = VarSource(fields[name], count)
|
||||||
|
r._sx_resolve_(fields[name])
|
||||||
|
|
||||||
|
cls._sx_type_ = StructType(fields, cls, generics=generics, bound=bound, **kwargs)
|
||||||
|
|
||||||
|
def __class_getitem__(cls, item) -> Type:
|
||||||
|
if not isinstance(item, tuple):
|
||||||
|
item = (item,)
|
||||||
|
subtype = cls._sx_type_[item]
|
||||||
|
new_name = '{}[{}]'.format(cls.__name__, ', '.join(str(g) for g in subtype.bound))
|
||||||
|
new = type(new_name, (cls,), {})
|
||||||
|
new._sx_type_ = subtype
|
||||||
|
new.__slots__ = cls.__slots__
|
||||||
|
new.__module__ = cls.__module__
|
||||||
|
subtype.cls = new
|
||||||
|
return new
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[Any]:
|
||||||
|
return iter(self._sx_type_.fields)
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(tuple((k, getattr(self, k)) for k in self))
|
||||||
|
|
||||||
|
def __eq__(self, other) -> bool:
|
||||||
|
if type(self) != type(other):
|
||||||
|
return False
|
||||||
|
if self.__slots__ != other.__slots__:
|
||||||
|
return False
|
||||||
|
for k in self:
|
||||||
|
ov = getattr(self, k)
|
||||||
|
tv = getattr(other, k)
|
||||||
|
if ov != tv:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _format_(self, fieldfunc: Callable[[Any], str]) -> str:
|
||||||
|
args = []
|
||||||
|
for k in self:
|
||||||
|
if k.startswith('_'):
|
||||||
|
continue
|
||||||
|
val = getattr(self, k)
|
||||||
|
val = format_value(val, fieldfunc, 2)
|
||||||
|
args.append(' {}: {}'.format(k, val))
|
||||||
|
args = ',\n'.join(args)
|
||||||
|
# Format final value.
|
||||||
|
if args:
|
||||||
|
return f'{self.__class__.__name__} {{\n{args}\n}}'
|
||||||
|
else:
|
||||||
|
return f'{self.__class__.__name__} {{}}'
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self._format_(str)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return self._format_(repr)
|
|
@ -0,0 +1,196 @@
|
||||||
|
import os
|
||||||
|
import errno
|
||||||
|
from typing import Any, Optional as O, Generic as G, Union as U, TypeVar, Callable, Sequence, Mapping
|
||||||
|
from ..core.base import Type, Context, PathElement, PossibleDynamic
|
||||||
|
from ..core.io import Stream, Segment, Pos
|
||||||
|
from ..core.meta import Wrapper
|
||||||
|
from ..core.util import seeking
|
||||||
|
from ..core import to_type
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
|
V = TypeVar('V')
|
||||||
|
|
||||||
|
class Default(G[T], Wrapper[T]):
|
||||||
|
def __init__(self, child: Type[T], default: T) -> None:
|
||||||
|
super().__init__(child)
|
||||||
|
self._default = default
|
||||||
|
|
||||||
|
def default(self, context: Context) -> T:
|
||||||
|
return self._default
|
||||||
|
|
||||||
|
|
||||||
|
class SizedStream:
|
||||||
|
def __init__(self, stream: Stream, limit: Pos) -> None:
|
||||||
|
self._stream = stream
|
||||||
|
self._pos: Pos = 0
|
||||||
|
self._limit = limit
|
||||||
|
self._start = stream.tell()
|
||||||
|
|
||||||
|
def read(self, n: int = -1, bits=False) -> U[bytes, int]:
|
||||||
|
remaining = max(0, self._limit - self._pos)
|
||||||
|
if bits:
|
||||||
|
remaining *= 8
|
||||||
|
if n < 0:
|
||||||
|
n = remaining
|
||||||
|
|
||||||
|
if n > remaining:
|
||||||
|
raise EOFError
|
||||||
|
if bits:
|
||||||
|
self._pos += n / 8
|
||||||
|
else:
|
||||||
|
self._pos += n
|
||||||
|
return self._stream.read(n, bits=bits)
|
||||||
|
|
||||||
|
def write(self, data: U[bytes, int], *, bits: O[int] = None) -> None:
|
||||||
|
remaining = self._limit - self._pos
|
||||||
|
if bits is not None:
|
||||||
|
n = bits / 8
|
||||||
|
else:
|
||||||
|
n = len(data)
|
||||||
|
if n > remaining:
|
||||||
|
raise EOFError
|
||||||
|
self._pos += n
|
||||||
|
return self._file.write(data, bits=bits)
|
||||||
|
|
||||||
|
def seek(self, offset: Pos, whence: int) -> None:
|
||||||
|
if whence == os.SEEK_SET:
|
||||||
|
pos = offset
|
||||||
|
elif whence == os.SEEK_CUR:
|
||||||
|
pos = self._start + self._pos + offset
|
||||||
|
elif whence == os.SEEK_END:
|
||||||
|
pos = self._start + self._limit - offset
|
||||||
|
if pos < self._start:
|
||||||
|
raise OSError(errno.EINVAL, os.strerror(errno.EINVAL), offset)
|
||||||
|
self._pos = pos - self._start
|
||||||
|
return self._file.seek(pos, os.SEEK_SET)
|
||||||
|
|
||||||
|
def tell(self) -> Pos:
|
||||||
|
return self._start + self._pos
|
||||||
|
|
||||||
|
def __getattr__(self, attr: str) -> Any:
|
||||||
|
return getattr(self._stream, attr)
|
||||||
|
|
||||||
|
class Sized(G[T], Wrapper[T]):
|
||||||
|
def __init__(self, child: Type[T], limit: U[Pos, PossibleDynamic]):
|
||||||
|
super().__init__(child)
|
||||||
|
self.limit = limit
|
||||||
|
|
||||||
|
def parse(self, context: Context, stream: Stream) -> T:
|
||||||
|
limit = max(0, context.get(self.limit))
|
||||||
|
start = stream.tell()
|
||||||
|
value = super().parse(context, SizedStream(stream, limit))
|
||||||
|
stream.seek(start + limit)
|
||||||
|
return value
|
||||||
|
|
||||||
|
def dump(self, context: Context, stream: Stream, value: O[T]) -> None:
|
||||||
|
limit = max(0, context.get(self.limit))
|
||||||
|
start = stream.tell()
|
||||||
|
super().dump(context, SizedStream(stream, limit), value)
|
||||||
|
stream.seek(start + limit)
|
||||||
|
|
||||||
|
def sizeof(self, context: Context, value: O[T]) -> O[Pos]:
|
||||||
|
return context.peek(self.limit)
|
||||||
|
|
||||||
|
|
||||||
|
class Ref(G[T], Wrapper[T]):
|
||||||
|
def __init__(self, child: Type[T], pos: U[PossibleDynamic, Pos], whence: int = os.SEEK_SET, segment: O[Segment] = None) -> None:
|
||||||
|
super().__init__(child)
|
||||||
|
self.pos = pos
|
||||||
|
self.whence = whence
|
||||||
|
self.segment = segment
|
||||||
|
|
||||||
|
def parse(self, context: Context, stream: Stream) -> T:
|
||||||
|
point = context.get(self.pos)
|
||||||
|
whence = context.get(self.whence)
|
||||||
|
segment = context.get(self.segment) or context.params.segments['refs']
|
||||||
|
with context.enter_segment(segment, stream, point, whence) as f:
|
||||||
|
return super().parse(context, f)
|
||||||
|
|
||||||
|
def dump(self, context: Context, stream: Stream, value: T) -> None:
|
||||||
|
whence = context.get(self.whence)
|
||||||
|
segment = context.get(self.segment) or context.params.segments['refs']
|
||||||
|
|
||||||
|
with context.enter_segment(segment, stream) as f:
|
||||||
|
pos = f.tell()
|
||||||
|
super().dump(context, f, value)
|
||||||
|
|
||||||
|
if whence == os.SEEK_CUR:
|
||||||
|
pos -= stream.tell()
|
||||||
|
elif whence == os.SEEK_END:
|
||||||
|
with seeking(stream, 0, os.SEEK_END) as f:
|
||||||
|
pos -= f.tell()
|
||||||
|
|
||||||
|
context.put(self.pos, pos)
|
||||||
|
|
||||||
|
def sizeof(self, context: Context, value: O[T]) -> O[Pos]:
|
||||||
|
segment = context.peek(self.segment) or context.params.segments['refs']
|
||||||
|
with context.enter_segment(segment):
|
||||||
|
return super().sizeof(context, value)
|
||||||
|
|
||||||
|
def offsetof(self, context: Context, path: Sequence[PathElement], value: O[T]) -> O[Pos]:
|
||||||
|
segment = context.peek(self.segment) or context.params.segments['refs']
|
||||||
|
with context.enter_segment(segment):
|
||||||
|
return super().contextof(context, path, value)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
indicator = {os.SEEK_SET: '', os.SEEK_CUR: '+', os.SEEK_END: '-'}.get(self.whence, self.whence)
|
||||||
|
segment = f'{self.segment}:' if self.segment else ''
|
||||||
|
return f'&({super().__str__()} @ {indicator}{segment}{self.pos})'
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
indicator = {os.SEEK_SET: '', os.SEEK_CUR: '+', os.SEEK_END: '-'}.get(self.whence, self.whence)
|
||||||
|
segment = f'{self.segment!r}:' if self.segment else ''
|
||||||
|
return f'<{__name__}.{self.__class__.__name__}({super().__repr__()}, pos: {indicator}{segment}{self.pos!r})>'
|
||||||
|
|
||||||
|
class Transform(G[T, V], Type[V]):
|
||||||
|
def __init__(self, child: Type[T], parse: U[Callable[[T], V], Callable[[T, Context], V]], dump: U[Callable[[V], T], Callable[[V, Context], T]], context: bool = False, str: O[str] = None, repr: O[str] = None) -> None:
|
||||||
|
self.child = child
|
||||||
|
self.on_parse = parse
|
||||||
|
self.on_dump = dump
|
||||||
|
self.context = context
|
||||||
|
self.on_str = str or repr
|
||||||
|
self.on_repr = repr or str
|
||||||
|
|
||||||
|
def parse(self, context: Context, stream: Stream) -> V:
|
||||||
|
value = context.parse(to_type(self.child), stream)
|
||||||
|
return self.on_parse(value, context) if self.context else self.on_parse(value)
|
||||||
|
|
||||||
|
def dump(self, context: Context, stream: Stream, value: V) -> None:
|
||||||
|
value = self.on_dump(value, context) if self.context else self.on_dump(value)
|
||||||
|
context.dump(to_type(self.child), stream, value)
|
||||||
|
|
||||||
|
def sizeof(self, context: Context, value: O[V]) -> O[int]:
|
||||||
|
if value is not None:
|
||||||
|
value = self.on_dump(value, context) if self.context else self.on_dump(value)
|
||||||
|
return context.sizeof(to_type(self.child), value)
|
||||||
|
|
||||||
|
def offsetof(self, context: Context, path: Sequence[PathElement], value: O[V]) -> O[int]:
|
||||||
|
if value is not None:
|
||||||
|
value = self.on_dump(value, context) if self.context else self.on_dump(value)
|
||||||
|
return context.offsetof(to_type(self.child), path, value)
|
||||||
|
|
||||||
|
def default(self, context: Context) -> V:
|
||||||
|
value = context.default(to_type(self.child))
|
||||||
|
return self.on_parse(value, context) if self.context else self.on_parse(value)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
if self.on_str is not None:
|
||||||
|
return self.on_str
|
||||||
|
return f'λ({self.child})'
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
if self.on_repr is not None:
|
||||||
|
return self.on_repr
|
||||||
|
return f'<{__name__}.{self.__class__.__name__}({self.child!r}, parse: {self.on_parse!r}, dump: {self.on_dump!r})>'
|
||||||
|
|
||||||
|
|
||||||
|
class Mapped(G[T, V], Type[T]):
|
||||||
|
def __new__(self, child: Type[T], mapping: Mapping[T, V], str: O[str] = None, repr: O[str] = None) -> Transform:
|
||||||
|
reverse = {v: k for k, v in mapping.items()}
|
||||||
|
return Transform(child,
|
||||||
|
parse=mapping.__getitem__,
|
||||||
|
dump=reverse.__getitem__,
|
||||||
|
str=str or f'{mapping}[{child}]',
|
||||||
|
repr=repr or f'<{__name__}.Mapped({child!r}, {mapping!r}'
|
||||||
|
)
|
Loading…
Reference in New Issue