Core API: Conditional

construct.Union(parsefrom, *subcons, **subconskw)

Treats the same data as multiple constructs (similar to C union) so you can look at the data in multiple views. Fields are usually named (so parsed values are inserted into dictionary under same name). Embedded fields do not need to (and should not) be named.

Parses subcons in sequence, and reverts the stream back to original position after each subcon. Afterwards, advances the stream by selected subcon. Builds from first subcon that has a matching key in given dict. Size is undefined (because parsefrom is not used for building).

This class does context nesting, meaning its members are given access to a new dictionary where the “_” entry points to the outer context. When parsing, each member gets parsed and subcon parse return value is inserted into context under matching key only if the member was named. When building, the matching entry gets inserted into context before subcon gets build, and if subcon build returns a new value (not None) that gets replaced in the context.

This class supports embedding. Embedded semantics dictate, that during instance creation (in ctor), each field is checked for embedded flag, and its subcon members are merged. This changes behavior of some code examples. Only few classes are supported: Struct Sequence FocusedSeq Union LazyStruct, although those can be used interchangably (a Struct can embed a Sequence, or rather its members).

This class exposes subcons as attributes. You can refer to subcons that were inlined (and therefore do not exist as variable in the namespace) by accessing the struct attributes, under same name. Also note that compiler does not support this feature. See examples.

This class exposes subcons in the context. You can refer to subcons that were inlined (and therefore do not exist as variable in the namespace) within other inlined fields using the context. Note that you need to use a lambda (this expression is not supported). Also note that compiler does not support this feature. See examples.

Warning

If you skip parsefrom parameter then stream will be left back at starting offset, not seeked to any common denominator.

Parameters:
  • parsefrom – how to leave stream after parsing, can be integer index or string name selecting a subcon, or None (leaves stream at initial offset, the default), or context lambda
  • *subcons – Construct instances, list of members, some can be anonymous
  • **subconskw – Construct instances, list of members (requires Python 3.6)
Raises:
  • StreamError – requested reading negative amount, could not read enough bytes, requested writing different amount than actual data, or could not write all bytes
  • StreamError – stream is not seekable and tellable
  • UnionError – selector does not match any subcon, or dict given to build does not contain any keys matching any subcon
  • IndexError – selector does not match any subcon
  • KeyError – selector does not match any subcon

Can propagate any exception from the lambda, possibly non-ConstructError.

Example:

>>> d = Union(0, 
...     "raw" / Bytes(8),
...     "ints" / Int32ub[2],
...     "shorts" / Int16ub[4],
...     "chars" / Byte[8],
... )
>>> d.parse(b"12345678")
Container(raw=b'12345678', ints=[825373492, 892745528], shorts=[12594, 13108, 13622, 14136], chars=[49, 50, 51, 52, 53, 54, 55, 56])
>>> d.build(dict(chars=range(8)))
b'\x00\x01\x02\x03\x04\x05\x06\x07'

>>> d = Union(None,
...     "animal" / Enum(Byte, giraffe=1),
... )
>>> d.animal.giraffe
'giraffe'
>>> d = Union(None,
...     "chars" / Byte[4],
...     "data" / Bytes(lambda this: this._subcons.chars.sizeof()),
... )
>>> d.parse(b"\x01\x02\x03\x04")
Container(chars=[1, 2, 3, 4])(data=b'\x01\x02\x03\x04')

Alternative syntax, but requires Python 3.6 or any PyPy:
>>> Union(0, raw=Bytes(8), ints=Int32ub[2], shorts=Int16ub[4], chars=Byte[8])
construct.Select(*subcons, **subconskw)

Selects the first matching subconstruct.

Parses and builds by literally trying each subcon in sequence until one of them parses or builds without exception. Stream gets reverted back to original position after each failed attempt, but not if parsing succeeds. Size is not defined.

Parameters:
  • *subcons – Construct instances, list of members, some can be anonymous
  • **subconskw – Construct instances, list of members (requires Python 3.6)
Raises:
  • StreamError – requested reading negative amount, could not read enough bytes, requested writing different amount than actual data, or could not write all bytes
  • StreamError – stream is not seekable and tellable
  • SelectError – neither subcon succeded when parsing or building

Example:

>>> d = Select(Int32ub, CString("utf8"))
>>> d.build(1)
b'\x00\x00\x00\x01'
>>> d.build(u"Афон")
b'\xd0\x90\xd1\x84\xd0\xbe\xd0\xbd\x00'

Alternative syntax, but requires Python 3.6 or any PyPy:
>>> Select(num=Int32ub, text=CString("utf8"))
construct.Optional(subcon)

Makes an optional field.

Parsing attempts to parse subcon. If sub-parsing fails, returns None and reports success. Building attempts to build subcon. If sub-building fails, writes nothing and reports success. Size is undefined, because whether bytes would be consumed or produced depends on actual data and actual context.

Parameters:subcon – Construct instance

Example:

Optional  <-->  Select(subcon, Pass)

>>> d = Optional(Int64ul)
>>> d.parse(b"12345678")
4050765991979987505
>>> d.parse(b"")
None
>>> d.build(1)
b'\x01\x00\x00\x00\x00\x00\x00\x00'
>>> d.build(None)
b''
construct.If(condfunc, subcon)

If-then conditional construct.

Parsing evaluates condition, if True then subcon is parsed, otherwise just returns None. Building also evaluates condition, if True then subcon gets build from, otherwise does nothing. Size is either same as subcon or 0, depending how condfunc evaluates.

Parameters:
  • condfunc – bool or context lambda (or a truthy value)
  • subcon – Construct instance, used if condition indicates True
Raises:

StreamError – requested reading negative amount, could not read enough bytes, requested writing different amount than actual data, or could not write all bytes

Can propagate any exception from the lambda, possibly non-ConstructError.

Example:

If <--> IfThenElse(condfunc, subcon, Pass)

>>> d = If(this.x > 0, Byte)
>>> d.build(255, x=1)
b'\xff'
>>> d.build(255, x=0)
b''
construct.IfThenElse(condfunc, thensubcon, elsesubcon)

If-then-else conditional construct, similar to ternary operator.

Parsing and building evaluates condition, and defers to either subcon depending on the value. Size is computed the same way.

Parameters:
  • condfunc – bool or context lambda (or a truthy value)
  • thensubcon – Construct instance, used if condition indicates True
  • elsesubcon – Construct instance, used if condition indicates False
Raises:

StreamError – requested reading negative amount, could not read enough bytes, requested writing different amount than actual data, or could not write all bytes

Can propagate any exception from the lambda, possibly non-ConstructError.

Example:

>>> d = IfThenElse(this.x > 0, VarInt, Byte)
>>> d.build(255, dict(x=1))
b'\xff\x01'
>>> d.build(255, dict(x=0))
b'\xff'
construct.Switch(keyfunc, cases, default=None)

A conditional branch.

Parsing and building evaluate keyfunc and select a subcon based on the value and dictionary entries. Dictionary (cases) maps values into subcons. If no case matches then default is used (that is Pass by default). Note that default is a Construct instance, not a dictionary key. Size is evaluated in same way as parsing and building, by evaluating keyfunc and selecting a field accordingly.

Parameters:
  • keyfunc – context lambda or constant, that matches some key in cases
  • cases – dict mapping keys to Construct instances
  • default – optional, Construct instance, used when keyfunc is not found in cases, Pass is default value for this parameter, Error is a possible value for this parameter
Raises:

StreamError – requested reading negative amount, could not read enough bytes, requested writing different amount than actual data, or could not write all bytes

Can propagate any exception from the lambda, possibly non-ConstructError.

Example:

>>> d = Switch(this.n, { 1:Int8ub, 2:Int16ub, 4:Int32ub })
>>> d.build(5, n=1)
b'\x05'
>>> d.build(5, n=4)
b'\x00\x00\x00\x05'

>>> d = Switch(this.n, {}, default=Byte)
>>> d.parse(b"\x01", n=255)
1
>>> d.build(1, n=255)
b"\x01"
construct.EmbeddedSwitch(merged, selector, mapping)

Macro that simulates embedding Switch, which under new embedding semantics is not possible. This macro does NOT produce a Switch. It generates classes that behave the same way as you would expect from embedded Switch, only that. Instance created by this macro CAN be embedded.

Both merged and all values in mapping must be Struct instances. Macro re-creates a single struct that contains all fields, where each field is wrapped in If(selector == key, …). Note that resulting dictionary contains None values for fields that would not be chosen by switch. Note also that if selector does not match any cases, it passes successfully (default Switch behavior).

All fields should have unique names. Otherwise fields that were not selected during parsing may return None and override other fields context entries that have same name. This is because If field returns None value if condition is not met, but the Struct inserts that None value into the context entry regardless.

Parameters:
  • merged – Struct instance
  • selector – this expression, that references one of merged fields
  • mapping – dict with values being Struct instances

Example:

d = EmbeddedSwitch(
    Struct(
        "type" / Byte,
    ),
    this.type,
    {
        0: Struct("name" / PascalString(Byte, "utf8")),
        1: Struct("value" / Byte),
    }
)

# generates essentially following
d = Struct(
    "type" / Byte,
    "name" / If(this.type == 0, PascalString(Byte, "utf8")),
    "value" / If(this.type == 1, Byte),
)

# both parse like following
>>> d.parse(b"\x00\x00")
Container(type=0, name=u'', value=None)
>>> d.parse(b"\x01\x00")
Container(type=1, name=None, value=0)
construct.StopIf(condfunc)

Checks for a condition, and stops certain classes (Struct Sequence GreedyRange) from parsing or building further.

Parsing and building check the condition, and raise StopFieldError if indicated. Size is undefined.

Parameters:condfunc – bool or context lambda (or truthy value)
Raises:StopFieldError – used internally

Can propagate any exception from the lambda, possibly non-ConstructError.

Example:

>>> Struct('x'/Byte, StopIf(this.x == 0), 'y'/Byte)
>>> Sequence('x'/Byte, StopIf(this.x == 0), 'y'/Byte)
>>> GreedyRange(FocusedSeq(0, 'x'/Byte, StopIf(this.x == 0)))