Module imandrax_api.lib.twine

Functions

def optional(d: Decoder,
d0: Callable[..., T],
off: offset) ‑> T | None
Expand source code
def optional[T](d: Decoder, d0: Callable[..., T], off: offset) -> T | None:
    match d.shallow_value(off=off):
        case None:
            return None
        case (c,):
            return d0(d=d, off=c)
        case v:
            raise Error(f"expected optional, got {v}")
def value_to_json(v: value) ‑> str
Expand source code
def value_to_json(v: value) -> str:
    j = json.dumps(v)
    return j

Classes

class ArrayCursor (dec: Decoder,
offset: offset,
num_items: int)
Expand source code
@dataclass(slots=True)
class ArrayCursor(Cursor):
    def __next__(self) -> offset:
        if self.num_items == 0:
            raise StopIteration
        off = self.offset
        self.offset = self.dec._skip(off=self.offset)
        self.num_items -= 1
        return off

ArrayCursor(dec: 'Decoder', offset: 'offset', num_items: 'int')

Ancestors

class Constructor (idx: int, args: Args)
Expand source code
@dataclass(slots=True, frozen=True)
class Constructor[Args]:
    """A constructor for an ADT"""

    idx: int
    args: Args

A constructor for an ADT

Ancestors

  • typing.Generic

Instance variables

var args : Args
Expand source code
@dataclass(slots=True, frozen=True)
class Constructor[Args]:
    """A constructor for an ADT"""

    idx: int
    args: Args
var idx : int
Expand source code
@dataclass(slots=True, frozen=True)
class Constructor[Args]:
    """A constructor for an ADT"""

    idx: int
    args: Args
class Cursor (dec: Decoder,
offset: offset,
num_items: int)
Expand source code
@dataclass(slots=True)
class Cursor:
    dec: Decoder
    offset: offset
    num_items: int

    def __iter__(self):
        return self

    def __len__(self) -> int:
        return self.num_items

Cursor(dec: 'Decoder', offset: 'offset', num_items: 'int')

Subclasses

Instance variables

var decDecoder
Expand source code
@dataclass(slots=True)
class Cursor:
    dec: Decoder
    offset: offset
    num_items: int

    def __iter__(self):
        return self

    def __len__(self) -> int:
        return self.num_items
var num_items : int
Expand source code
@dataclass(slots=True)
class Cursor:
    dec: Decoder
    offset: offset
    num_items: int

    def __iter__(self):
        return self

    def __len__(self) -> int:
        return self.num_items
var offset : int
Expand source code
@dataclass(slots=True)
class Cursor:
    dec: Decoder
    offset: offset
    num_items: int

    def __iter__(self):
        return self

    def __len__(self) -> int:
        return self.num_items
class Decoder (a: bytearray | bytes)
Expand source code
class Decoder:
    """A twine decoder"""

    def __init__(self, a: bytearray | bytes):
        a = bytearray(a)
        self.bs = a

    def __first_byte(self, off: int) -> tuple[int, int]:
        c = self.bs[off]
        high = c >> 4
        low = c & 0xF
        return (high, low)

    def _leb128(self, off: offset) -> tuple[int, int]:
        """
        read an unsigned integer as LEB128, also returns how many bytes were consumed
        """
        res = 0
        shift = 0
        n_bytes = 0
        while True:
            n_bytes += 1
            x = self.bs[off]
            off += 1
            cur = x & 0x7F
            if x != cur:
                res = res | (cur << shift)
                shift += 7
            else:
                res = res | (x << shift)
                return res, n_bytes

    def __getint64(self, off: offset, low: int) -> tuple[int, int]:
        "returns (int, n_bytes_consumed)"
        if low < 15:
            return low, 0
        rest, consumed = self._leb128(off=off + 1)
        return rest + 15, consumed

    def _deref(self, off: offset) -> offset:
        "deref pointers until `off` is not a pointer"
        while True:
            high, low = self.__first_byte(off)
            if high == 15:
                p, _ = self.__getint64(off, low)
                off = off - p - 1
            else:
                return off

    def _skip(self, off: offset) -> offset:
        "skip the current immediate value"
        high, low = self.__first_byte(off=off)
        match high:
            case 0:
                return off + 1
            case 1 | 2:
                _, n_bytes = self.__getint64(off=off, low=low)
                return off + 1 + n_bytes
            case 3:
                return off + 5 if low == 0 else off + 9
            case 4 | 5:
                len, n_bytes = self.__getint64(off=off, low=low)
                return off + 1 + n_bytes + len
            case 6 | 7 | 8:
                raise Error(f"cannot skip over array/dict/tag (high={high}) at off=0x{off:x}")
            case 9 | 13 | 14:
                raise Error(f"tag {high} is reserved")
            case 10:
                _, n_bytes = self.__getint64(off=off, low=low)
                return off + 1 + n_bytes
            case 11 | 12:
                raise Error(f"cannot skip over constructor (high={high}) at off=0x{off:x}")
            case 15:
                _, n_bytes = self.__getint64(off=off, low=low)
                return off + 1 + n_bytes
            case _:
                assert False

    def get_int(self, off: offset) -> int:
        off = self._deref(off=off)
        high, low = self.__first_byte(off)
        if high == 1:
            x, _ = self.__getint64(off=off, low=low)
            return x
        elif high == 2:
            # negative int
            x, _ = self.__getint64(off=off, low=low)
            return -x - 1
        elif high == 5:
            # bigint
            bytes = self.get_bytes(off)
            sign = 1 if bytes == b'' or bytes[0] & 0b1 == 0 else -1
            absvalue = int.from_bytes(bytes, byteorder='little', signed=False) >> 1
            return sign * absvalue
        else:
            raise Error(f"expected integer, but high={high} at off=0x{off:x}")

    def get_bool(self, off: offset) -> bool:
        off = self._deref(off=off)
        high, low = self.__first_byte(off=off)
        if high != 0:
            raise Error(f"expected bool, but high={high} at off=0x{off:x}")
        if low == 0:
            return False
        elif low == 1:
            return True
        else:
            raise Error(f"expected bool, but high={high}, low={low} at off=0x{off:x}")

    def get_null(self, off: offset) -> None:
        off = self._deref(off=off)
        high, low = self.__first_byte(off=off)
        if high == 0 and low == 2:
            return None
        else:
            raise Error(f"expected bool, but high={high}, low={low} at off=0x{off:x}")

    def get_float(self, off: offset) -> float:
        off = self._deref(off=off)
        high, low = self.__first_byte(off=off)
        if high != 3:
            raise Error(f"expected float, but high={high} at off=0x{off:x}")

        isf32 = low == 0
        if isf32:
            return struct.unpack_from("<f", self.bs, offset=off)[0]
        else:
            return struct.unpack_from("<d", self.bs, offset=off)[0]

    def get_str(self, off: offset) -> str:
        off = self._deref(off=off)
        high, low = self.__first_byte(off=off)
        if high != 4:
            raise Error(f"expected string, but high={high} at off=0x{off:x}")
        len, n_bytes = self.__getint64(off=off, low=low)
        off = off + 1 + n_bytes
        s = self.bs[off : off + len].decode("utf8")
        return s

    def get_bytes(self, off: offset) -> bytes:
        off = self._deref(off=off)
        high, low = self.__first_byte(off=off)
        if high != 5:
            raise Error(f"expected bytes, but high={high} at off=0x{off:x}")
        len, n_bytes = self.__getint64(off=off, low=low)
        off = off + 1 + n_bytes
        return bytes(self.bs[off : off + len])

    def get_array(self, off: offset) -> ArrayCursor:
        off = self._deref(off=off)
        high, low = self.__first_byte(off=off)
        if high != 6:
            raise Error(f"expected array, but high={high} at off=0x{off:x}")
        len, n_bytes = self.__getint64(off=off, low=low)
        off = off + 1 + n_bytes
        return ArrayCursor(dec=self, offset=off, num_items=len)

    def get_dict(self, off: offset) -> DictCursor:
        off = self._deref(off=off)
        high, low = self.__first_byte(off=off)
        if high != 7:
            raise Error(f"expected dict, but high={high} at off=0x{off:x}")
        len, n_bytes = self.__getint64(off=off, low=low)
        off = off + 1 + n_bytes
        return DictCursor(dec=self, offset=off, num_items=len)

    def get_tag(self, off: offset) -> Tag[offset]:
        off = self._deref(off=off)
        high, low = self.__first_byte(off=off)
        if high != 8:
            raise Error(f"expected tag, but high={high} at off=0x{off:x}")
        tag, n_bytes = self.__getint64(off=off, low=low)
        off = off + 1 + n_bytes
        return Tag[offset](tag=tag, arg=off)

    def get_cstor(self, off: offset) -> Constructor[ArrayCursor]:
        """
        dec.get_cstor(off) returns the constructor index, and a cursor over its arguments
        """
        off = self._deref(off=off)
        high, low = self.__first_byte(off=off)

        if high == 10:
            idx, _ = self.__getint64(off=off, low=low)
            return Constructor[ArrayCursor](
                idx=idx, args=ArrayCursor(dec=self, offset=off, num_items=0)
            )
        elif high == 11:
            idx, n_bytes = self.__getint64(off=off, low=low)
            return Constructor[ArrayCursor](
                idx=idx,
                args=ArrayCursor(dec=self, offset=off + 1 + n_bytes, num_items=1),
            )
        elif high == 12:
            idx, n_bytes = self.__getint64(off=off, low=low)
            off = off + 1 + n_bytes
            num_items, n_bytes = self._leb128(off=off)
            return Constructor[ArrayCursor](
                idx=idx,
                args=ArrayCursor(dec=self, offset=off + n_bytes, num_items=num_items),
            )
        else:
            raise Error(f"expected constructor (high={high}) at off=0x{off:x}")

    def shallow_value(self, off: offset) -> shallow_value:
        """Read an arbitrary (shallow) value, non-recursively"""
        off = self._deref(off=off)
        high, low = self.__first_byte(off=off)
        match high:
            case 0:
                if low == 2:
                    return None
                elif high == 0:
                    return False
                elif high == 1:
                    return True
                else:
                    raise Error(f"expected true/false/None at off=0x{off:x}")
            case 1 | 2:
                return self.get_int(off=off)
            case 3:
                return self.get_float(off=off)
            case 4:
                return self.get_str(off=off)
            case 5:
                return self.get_bytes(off=off)
            case 6:
                c = self.get_array(off=off)
                return tuple(c)
            case 7:
                c = self.get_dict(off=off)
                d = dict(c)
                return d
            case 8:
                return self.get_tag(off=off)
            case 10 | 11 | 12:
                c = self.get_cstor(off=off)
                return Constructor(idx=c.idx, args=tuple(c.args))
            case 15:
                assert False
            case _:
                raise Error(f"invalid twine value (high={high}) at off=0x{off:x}")

    def value(self, off: offset) -> value:
        """Read an arbitrary value"""
        off = self._deref(off=off)
        high, low = self.__first_byte(off=off)
        match high:
            case 0:
                if low == 2:
                    return None
                elif high == 0:
                    return False
                elif high == 1:
                    return True
                else:
                    raise Error(f"expected true/false/None at off=0x{off:x}")
            case 1 | 2:
                return self.get_int(off=off)
            case 3:
                return self.get_float(off=off)
            case 4:
                return self.get_str(off=off)
            case 5:
                return self.get_bytes(off=off)
            case 6:
                c = self.get_array(off=off)
                return tuple(self.value(off) for off in c)
            case 7:
                c = self.get_dict(off=off)
                d = {self.value(k): self.value(v) for k, v in c}
                return d
            case 8:
                tag = self.get_tag(off=off)
                return Tag(tag=tag.tag, arg=self.value(tag.arg))
            case 10 | 11 | 12:
                c = self.get_cstor(off=off)
                args: tuple[value, ...] = tuple(self.value(x) for x in c.args)
                return Constructor(idx=c.idx, args=args)
            case 15:
                assert False
            case _:
                raise Error(f"invalid twine value (high={high}) at off=0x{off:x}")

    def entrypoint(self) -> offset:
        last = len(self.bs) - 1
        offset = last - int(self.bs[last]) - 1
        # print(f"offset = 0x{offset:x}")
        return self._deref(off=offset)

A twine decoder

Methods

def entrypoint(self) ‑> int
Expand source code
def entrypoint(self) -> offset:
    last = len(self.bs) - 1
    offset = last - int(self.bs[last]) - 1
    # print(f"offset = 0x{offset:x}")
    return self._deref(off=offset)
def get_array(self, off: offset) ‑> ArrayCursor
Expand source code
def get_array(self, off: offset) -> ArrayCursor:
    off = self._deref(off=off)
    high, low = self.__first_byte(off=off)
    if high != 6:
        raise Error(f"expected array, but high={high} at off=0x{off:x}")
    len, n_bytes = self.__getint64(off=off, low=low)
    off = off + 1 + n_bytes
    return ArrayCursor(dec=self, offset=off, num_items=len)
def get_bool(self, off: offset) ‑> bool
Expand source code
def get_bool(self, off: offset) -> bool:
    off = self._deref(off=off)
    high, low = self.__first_byte(off=off)
    if high != 0:
        raise Error(f"expected bool, but high={high} at off=0x{off:x}")
    if low == 0:
        return False
    elif low == 1:
        return True
    else:
        raise Error(f"expected bool, but high={high}, low={low} at off=0x{off:x}")
def get_bytes(self, off: offset) ‑> bytes
Expand source code
def get_bytes(self, off: offset) -> bytes:
    off = self._deref(off=off)
    high, low = self.__first_byte(off=off)
    if high != 5:
        raise Error(f"expected bytes, but high={high} at off=0x{off:x}")
    len, n_bytes = self.__getint64(off=off, low=low)
    off = off + 1 + n_bytes
    return bytes(self.bs[off : off + len])
def get_cstor(self, off: offset) ‑> Constructor[ArrayCursor]
Expand source code
def get_cstor(self, off: offset) -> Constructor[ArrayCursor]:
    """
    dec.get_cstor(off) returns the constructor index, and a cursor over its arguments
    """
    off = self._deref(off=off)
    high, low = self.__first_byte(off=off)

    if high == 10:
        idx, _ = self.__getint64(off=off, low=low)
        return Constructor[ArrayCursor](
            idx=idx, args=ArrayCursor(dec=self, offset=off, num_items=0)
        )
    elif high == 11:
        idx, n_bytes = self.__getint64(off=off, low=low)
        return Constructor[ArrayCursor](
            idx=idx,
            args=ArrayCursor(dec=self, offset=off + 1 + n_bytes, num_items=1),
        )
    elif high == 12:
        idx, n_bytes = self.__getint64(off=off, low=low)
        off = off + 1 + n_bytes
        num_items, n_bytes = self._leb128(off=off)
        return Constructor[ArrayCursor](
            idx=idx,
            args=ArrayCursor(dec=self, offset=off + n_bytes, num_items=num_items),
        )
    else:
        raise Error(f"expected constructor (high={high}) at off=0x{off:x}")

dec.get_cstor(off) returns the constructor index, and a cursor over its arguments

def get_dict(self, off: offset) ‑> DictCursor
Expand source code
def get_dict(self, off: offset) -> DictCursor:
    off = self._deref(off=off)
    high, low = self.__first_byte(off=off)
    if high != 7:
        raise Error(f"expected dict, but high={high} at off=0x{off:x}")
    len, n_bytes = self.__getint64(off=off, low=low)
    off = off + 1 + n_bytes
    return DictCursor(dec=self, offset=off, num_items=len)
def get_float(self, off: offset) ‑> float
Expand source code
def get_float(self, off: offset) -> float:
    off = self._deref(off=off)
    high, low = self.__first_byte(off=off)
    if high != 3:
        raise Error(f"expected float, but high={high} at off=0x{off:x}")

    isf32 = low == 0
    if isf32:
        return struct.unpack_from("<f", self.bs, offset=off)[0]
    else:
        return struct.unpack_from("<d", self.bs, offset=off)[0]
def get_int(self, off: offset) ‑> int
Expand source code
def get_int(self, off: offset) -> int:
    off = self._deref(off=off)
    high, low = self.__first_byte(off)
    if high == 1:
        x, _ = self.__getint64(off=off, low=low)
        return x
    elif high == 2:
        # negative int
        x, _ = self.__getint64(off=off, low=low)
        return -x - 1
    elif high == 5:
        # bigint
        bytes = self.get_bytes(off)
        sign = 1 if bytes == b'' or bytes[0] & 0b1 == 0 else -1
        absvalue = int.from_bytes(bytes, byteorder='little', signed=False) >> 1
        return sign * absvalue
    else:
        raise Error(f"expected integer, but high={high} at off=0x{off:x}")
def get_null(self, off: offset) ‑> None
Expand source code
def get_null(self, off: offset) -> None:
    off = self._deref(off=off)
    high, low = self.__first_byte(off=off)
    if high == 0 and low == 2:
        return None
    else:
        raise Error(f"expected bool, but high={high}, low={low} at off=0x{off:x}")
def get_str(self, off: offset) ‑> str
Expand source code
def get_str(self, off: offset) -> str:
    off = self._deref(off=off)
    high, low = self.__first_byte(off=off)
    if high != 4:
        raise Error(f"expected string, but high={high} at off=0x{off:x}")
    len, n_bytes = self.__getint64(off=off, low=low)
    off = off + 1 + n_bytes
    s = self.bs[off : off + len].decode("utf8")
    return s
def get_tag(self, off: offset) ‑> Tag[int]
Expand source code
def get_tag(self, off: offset) -> Tag[offset]:
    off = self._deref(off=off)
    high, low = self.__first_byte(off=off)
    if high != 8:
        raise Error(f"expected tag, but high={high} at off=0x{off:x}")
    tag, n_bytes = self.__getint64(off=off, low=low)
    off = off + 1 + n_bytes
    return Tag[offset](tag=tag, arg=off)
def shallow_value(self, off: offset) ‑> shallow_value
Expand source code
def shallow_value(self, off: offset) -> shallow_value:
    """Read an arbitrary (shallow) value, non-recursively"""
    off = self._deref(off=off)
    high, low = self.__first_byte(off=off)
    match high:
        case 0:
            if low == 2:
                return None
            elif high == 0:
                return False
            elif high == 1:
                return True
            else:
                raise Error(f"expected true/false/None at off=0x{off:x}")
        case 1 | 2:
            return self.get_int(off=off)
        case 3:
            return self.get_float(off=off)
        case 4:
            return self.get_str(off=off)
        case 5:
            return self.get_bytes(off=off)
        case 6:
            c = self.get_array(off=off)
            return tuple(c)
        case 7:
            c = self.get_dict(off=off)
            d = dict(c)
            return d
        case 8:
            return self.get_tag(off=off)
        case 10 | 11 | 12:
            c = self.get_cstor(off=off)
            return Constructor(idx=c.idx, args=tuple(c.args))
        case 15:
            assert False
        case _:
            raise Error(f"invalid twine value (high={high}) at off=0x{off:x}")

Read an arbitrary (shallow) value, non-recursively

def value(self, off: offset) ‑> value
Expand source code
def value(self, off: offset) -> value:
    """Read an arbitrary value"""
    off = self._deref(off=off)
    high, low = self.__first_byte(off=off)
    match high:
        case 0:
            if low == 2:
                return None
            elif high == 0:
                return False
            elif high == 1:
                return True
            else:
                raise Error(f"expected true/false/None at off=0x{off:x}")
        case 1 | 2:
            return self.get_int(off=off)
        case 3:
            return self.get_float(off=off)
        case 4:
            return self.get_str(off=off)
        case 5:
            return self.get_bytes(off=off)
        case 6:
            c = self.get_array(off=off)
            return tuple(self.value(off) for off in c)
        case 7:
            c = self.get_dict(off=off)
            d = {self.value(k): self.value(v) for k, v in c}
            return d
        case 8:
            tag = self.get_tag(off=off)
            return Tag(tag=tag.tag, arg=self.value(tag.arg))
        case 10 | 11 | 12:
            c = self.get_cstor(off=off)
            args: tuple[value, ...] = tuple(self.value(x) for x in c.args)
            return Constructor(idx=c.idx, args=args)
        case 15:
            assert False
        case _:
            raise Error(f"invalid twine value (high={high}) at off=0x{off:x}")

Read an arbitrary value

class DictCursor (dec: Decoder,
offset: offset,
num_items: int)
Expand source code
@dataclass(slots=True)
class DictCursor(Cursor):
    def __next__(self) -> tuple[offset, offset]:
        if self.num_items == 0:
            raise StopIteration
        off_key = self.offset
        self.offset = self.dec._skip(off=self.offset)
        off_value = self.offset
        self.offset = self.dec._skip(off=self.offset)
        self.num_items -= 1
        return off_key, off_value

DictCursor(dec: 'Decoder', offset: 'offset', num_items: 'int')

Ancestors

class Error (msg: str)
Expand source code
@dataclass
class Error(Exception):
    msg: str

Error(msg: 'str')

Ancestors

  • builtins.Exception
  • builtins.BaseException

Class variables

var msg : str
class Tag (tag: int, arg: Arg)
Expand source code
@dataclass(slots=True, frozen=True)
class Tag[Arg]:
    """A tagged value"""

    tag: int
    arg: Arg

A tagged value

Ancestors

  • typing.Generic

Instance variables

var arg : Arg
Expand source code
@dataclass(slots=True, frozen=True)
class Tag[Arg]:
    """A tagged value"""

    tag: int
    arg: Arg
var tag : int
Expand source code
@dataclass(slots=True, frozen=True)
class Tag[Arg]:
    """A tagged value"""

    tag: int
    arg: Arg