This package offers both a portable {py3} module and a command-line
tool.
-WARNING: This version of Normand is 0.6, meaning both the Normand
+WARNING: This version of Normand is 0.7, meaning both the Normand
language and the module/CLI interface aren't stable.
ifdef::env-github[]
00 79 65 61 68 00 79 65 61 68 00 79 65 61 68 00 ┆ •yeah•yeah•yeah•
----
+Alignment::
++
+Input:
++
+----
+{be}
+
+ {199:32}
+@64 {43:64}
+@16 {-123:16}
+@32~255 {5584:32}
+----
++
+Output:
++
+----
+00 00 00 c7 00 00 00 00 00 00 00 00 00 00 00 2b
+ff 85 ff ff 00 00 15 d0
+----
Multilevel grouping::
+
Each generated byte increments the current offset.
A <<current-offset-setting,current offset setting>> may change the
-current offset.
+current offset without generating data.
+
+An <<current-offset-alignment,current offset alignment>> generates
+padding bytes to make the current offset satisfy a given alignment.
|`init_offset` parameter of the `parse()` function.
|`--offset` option.
* A <<current-offset-setting,current offset setting>>.
+* A <<current-offset-alignment,current offset alignment>>.
+
* A <<label,label>>, that is, a named constant holding the current
offset.
+
comment may exist:
----
-! @ / \ ? & : ; . , + [ ] _ = | -
+! / \ ? & : ; . , + [ ] _ = | -
----
The latter serve to improve readability so that you may write, for
----
====
+=== Current offset alignment
+
+An _current offset alignment_ represents zero or more padding bytes to
+make the <<cur-offset,current offset>> meet a given
+https://en.wikipedia.org/wiki/Data_structure_alignment[alignment] value.
+
+More specifically, for an alignment value of{nbsp}__**N**__{nbsp}bits,
+a current offset alignment represents the required padding bytes until
+the current offset is a multiple of __**N**__{nbsp}/{nbsp}8.
+
+A current offset alignment is:
+
+. The `@` prefix.
+
+. A positive integer (hexadecimal starting with `0x` or `0X` accepted)
+ which is the alignment value in _bits_.
++
+This value must be greater than zero and a multiple of{nbsp}8.
+
+. **Optional**:
++
+--
+. The ``pass:[~]`` prefix.
+. A positive integer (hexadecimal starting with `0x` or `0X` accepted)
+ which is the value of the byte to use as padding to align the
+ <<cur-offset,current offset>>.
+--
++
+Without this section, the padding byte value is zero.
+
+====
+Input:
+
+----
+11 22 (@32 aa bb cc) * 3
+----
+
+Output:
+
+----
+11 22 00 00 aa bb cc 00 aa bb cc 00 aa bb cc
+----
+====
+
+====
+Input:
+
+----
+{le}
+77 88
+@32~0xcc {-893.5:32}
+@128~0x55 "meow"
+----
+
+Output:
+
+----
+77 88 cc cc 00 60 5f c4 55 55 55 55 55 55 55 55 ┆ w••••`_•UUUUUUUU
+6d 65 6f 77 ┆ meow
+----
+====
+
+====
+Input:
+
+----
+aa bb cc <29> @64~255 "zoom"
+----
+
+Output:
+
+----
+aa bb cc ff ff ff 7a 6f 6f 6d ┆ ••••••zoom
+----
+====
+
=== Label
A _label_ associates a name to the <<cur-offset,current offset>>.
A repetition is:
-. Any item.
+. Any item except:
+
+** A <<current-byte-order-setting,current byte order setting>>.
+** A <<current-offset-setting,current offset setting>>.
+** A <<label,label>>.
+** A <<offset-alignment,offset alignment>>.
+** A <<variable-assignment,variable assignment>>.
. The ``pass:[*]`` character.
# Upstream repository: <https://github.com/efficios/normand>.
__author__ = "Philippe Proulx"
-__version__ = "0.6.2"
+__version__ = "0.7.0"
__all__ = [
"ByteOrder",
"parse",
Set,
Dict,
List,
- Tuple,
Union,
Pattern,
Callable,
return 1
def __repr__(self):
- return "_Byte({}, {})".format(hex(self._val), self._text_loc)
+ return "_Byte({}, {})".format(hex(self._val), repr(self._text_loc))
# String.
return len(self._data)
def __repr__(self):
- return "_Str({}, {})".format(repr(self._data), self._text_loc)
+ return "_Str({}, {})".format(repr(self._data), repr(self._text_loc))
# Byte order.
return self._bo
def __repr__(self):
- return "_SetBo({}, {})".format(repr(self._bo), self._text_loc)
+ return "_SetBo({}, {})".format(repr(self._bo), repr(self._text_loc))
# Label.
return self._name
def __repr__(self):
- return "_Label({}, {})".format(repr(self._name), self._text_loc)
+ return "_Label({}, {})".format(repr(self._name), repr(self._text_loc))
# Offset setting.
super().__init__(text_loc)
self._val = val
- # Offset value.
+ # Offset value (bytes).
@property
def val(self):
return self._val
def __repr__(self):
- return "_SetOffset({}, {})".format(repr(self._val), self._text_loc)
+ return "_SetOffset({}, {})".format(repr(self._val), repr(self._text_loc))
+
+
+# Offset alignment.
+class _AlignOffset(_Item):
+ def __init__(self, val: int, pad_val: int, text_loc: TextLoc):
+ super().__init__(text_loc)
+ self._val = val
+ self._pad_val = pad_val
+
+ # Alignment value (bits).
+ @property
+ def val(self):
+ return self._val
+
+ # Padding byte value.
+ @property
+ def pad_val(self):
+ return self._pad_val
+
+ def __repr__(self):
+ return "_AlignOffset({}, {}, {})".format(
+ repr(self._val), repr(self._pad_val), repr(self._text_loc)
+ )
# Mixin of containing an AST expression and its string.
def __repr__(self):
return "_VarAssign({}, {}, {}, {})".format(
- repr(self._name), repr(self._expr_str), repr(self._expr), self._text_loc
+ repr(self._name),
+ repr(self._expr_str),
+ repr(self._expr),
+ repr(self._text_loc),
)
def __repr__(self):
return "_FlNum({}, {}, {}, {})".format(
- repr(self._expr_str), repr(self._expr), repr(self._len), self._text_loc
+ repr(self._expr_str),
+ repr(self._expr),
+ repr(self._len),
+ repr(self._text_loc),
)
self.__class__.__name__,
repr(self._expr_str),
repr(self._expr),
- self._text_loc,
+ repr(self._text_loc),
)
return self._items
def __repr__(self):
- return "_Group({}, {})".format(repr(self._items), self._text_loc)
+ return "_Group({}, {})".format(repr(self._items), repr(self._text_loc))
# Repetition item.
def __repr__(self):
return "_Rep({}, {}, {}, {})".format(
- repr(self._item), repr(self._expr_str), repr(self._expr), self._text_loc
+ repr(self._item),
+ repr(self._expr_str),
+ repr(self._expr),
+ repr(self._text_loc),
)
# Pattern for _skip_ws_and_comments()
_ws_or_syms_or_comments_pat = re.compile(
- r"(?:[\s!@/\\?&:;.,+[\]_=|-]|#[^#]*?(?:\n|#))*"
+ r"(?:[\s!/\\?&:;.,+[\]_=|-]|#[^#]*?(?:\n|#))*"
)
# Skips as many whitespaces, insignificant symbol characters, and
self._expect_pat(self._label_set_offset_suffix_pat, "Expecting `>`")
return item
+ # Patterns for _try_parse_align_offset()
+ _align_offset_prefix_pat = re.compile(r"@\s*")
+ _align_offset_val_pat = re.compile(r"(\d+)\s*")
+ _align_offset_pad_val_prefix_pat = re.compile(r"~\s*")
+
+ # Tries to parse an offset alignment, returning an offset alignment
+ # item on success.
+ def _try_parse_align_offset(self):
+ begin_text_loc = self._text_loc
+
+ # Match prefix
+ if self._try_parse_pat(self._align_offset_prefix_pat) is None:
+ # No match
+ return
+
+ align_text_loc = self._text_loc
+ m = self._expect_pat(
+ self._align_offset_val_pat,
+ "Expecting an alignment (positive multiple of eight bits)",
+ )
+
+ # Validate alignment
+ val = int(m.group(1))
+
+ if val <= 0 or (val % 8) != 0:
+ _raise_error(
+ "Invalid alignment value {} (not a positive multiple of eight)".format(
+ val
+ ),
+ align_text_loc,
+ )
+
+ # Padding value?
+ pad_val = 0
+
+ if self._try_parse_pat(self._align_offset_pad_val_prefix_pat) is not None:
+ pad_val_text_loc = self._text_loc
+ m = self._expect_pat(self._pos_const_int_pat, "Expecting a byte value")
+
+ # Validate
+ pad_val = int(m.group(0), 0)
+
+ if pad_val > 255:
+ _raise_error(
+ "Invalid padding byte value {}".format(pad_val),
+ pad_val_text_loc,
+ )
+
+ # Return item
+ return _AlignOffset(val, pad_val, begin_text_loc)
+
# Tries to parse a base item (anything except a repetition),
# returning it on success.
def _try_parse_base_item(self):
# Label or offset setting item?
item = self._try_parse_label_or_set_offset()
+ if item is not None:
+ return item
+
+ # Offset alignment item?
+ item = self._try_parse_align_offset()
+
if item is not None:
return item
# Seven bits per byte
return math.ceil(bits / 7)
+ # Returns the offset `offset` aligned according to `item`.
+ @staticmethod
+ def _align_offset(offset: int, item: _AlignOffset):
+ align_bytes = item.val // 8
+ return (offset + align_bytes - 1) // align_bytes * align_bytes
+
# Computes the effective value for each repetition and LEB128
# integer instance, filling `instance_vals` (if not `None`) and
# returning `instance_vals`.
)
elif type(item) is _SetOffset:
state.offset = item.val
+ elif type(item) is _AlignOffset:
+ state.offset = _Gen._align_offset(state.offset, item)
elif isinstance(item, _Leb128Int):
# Evaluate the expression
val = _Gen._eval_item_expr(item, state, True)
return instance_vals
- def _zero_item_size(self, item: _Item, next_vl_instance: int):
- return 0, next_vl_instance
+ def _update_offset_noop(self, item: _Item, state: _GenState, next_vl_instance: int):
+ return next_vl_instance
- def _scalar_item_size(self, item: _ScalarItem, next_vl_instance: int):
- return item.size, next_vl_instance
+ def _dry_handle_scalar_item(
+ self, item: _ScalarItem, state: _GenState, next_vl_instance: int
+ ):
+ state.offset += item.size
+ return next_vl_instance
- def _leb128_int_item_size(self, item: _Leb128Int, next_vl_instance: int):
+ def _dry_handle_leb128_int_item(
+ self, item: _Leb128Int, state: _GenState, next_vl_instance: int
+ ):
# Get the value from `self._vl_instance_vals` _before_
# incrementing `next_vl_instance` to honor the order of
# _compute_vl_instance_vals().
- return (
- self._leb128_size_for_val(
- self._vl_instance_vals[next_vl_instance], type(item) is _SLeb128Int
- ),
- next_vl_instance + 1,
+ state.offset += self._leb128_size_for_val(
+ self._vl_instance_vals[next_vl_instance], type(item) is _SLeb128Int
)
- def _group_item_size(self, item: _Group, next_vl_instance: int):
- size = 0
+ return next_vl_instance + 1
+ def _dry_handle_group_item(
+ self, item: _Group, state: _GenState, next_vl_instance: int
+ ):
for subitem in item.items:
- subitem_size, next_vl_instance = self._item_size(subitem, next_vl_instance)
- size += subitem_size
+ next_vl_instance = self._dry_handle_item(subitem, state, next_vl_instance)
- return size, next_vl_instance
+ return next_vl_instance
- def _rep_item_size(self, item: _Rep, next_vl_instance: int):
+ def _dry_handle_rep_item(self, item: _Rep, state: _GenState, next_vl_instance: int):
# Get the value from `self._vl_instance_vals` _before_
# incrementing `next_vl_instance` to honor the order of
# _compute_vl_instance_vals().
mul = self._vl_instance_vals[next_vl_instance]
next_vl_instance += 1
- size = 0
for _ in range(mul):
- iter_size, next_vl_instance = self._item_size(item.item, next_vl_instance)
- size += iter_size
+ next_vl_instance = self._dry_handle_item(item.item, state, next_vl_instance)
- return size, next_vl_instance
+ return next_vl_instance
- # Returns the size of `item` and the new next repetition instance.
- def _item_size(self, item: _Item, next_vl_instance: int):
- return self._item_size_funcs[type(item)](item, next_vl_instance)
+ def _dry_handle_align_offset_item(
+ self, item: _AlignOffset, state: _GenState, next_vl_instance: int
+ ):
+ state.offset = self._align_offset(state.offset, item)
+ return next_vl_instance
+
+ def _dry_handle_set_offset_item(
+ self, item: _SetOffset, state: _GenState, next_vl_instance: int
+ ):
+ state.offset = item.val
+ return next_vl_instance
+
+ # Updates `state.offset` considering the generated data of `item`,
+ # without generating any, and returns the updated next
+ # variable-length item instance.
+ def _dry_handle_item(self, item: _Item, state: _GenState, next_vl_instance: int):
+ return self._dry_handle_item_funcs[type(item)](item, state, next_vl_instance)
# Handles the byte item `item`.
def _handle_byte_item(self, item: _Byte, state: _GenState, next_vl_instance: int):
):
# Compute the values of the immediate (not nested) labels. Those
# labels are reachable by any expression within the group.
- offset = state.offset
+ tmp_state = _GenState({}, {}, state.offset, None)
immediate_label_names = set() # type: Set[str]
tmp_next_vl_instance = next_vl_instance
for subitem in item.items:
- if type(subitem) is _SetOffset:
- # Update offset
- offset = subitem.val
- elif type(subitem) is _Label:
+ if type(subitem) is _Label:
# New immediate label
- state.labels[subitem.name] = offset
+ state.labels[subitem.name] = tmp_state.offset
immediate_label_names.add(subitem.name)
- subitem_size, tmp_next_vl_instance = self._item_size(
- subitem, tmp_next_vl_instance
+ tmp_next_vl_instance = self._dry_handle_item(
+ subitem, tmp_state, tmp_next_vl_instance
)
- offset += subitem_size
# Handle each item now with the actual state
for subitem in item.items:
state.offset = item.val
return next_vl_instance
+ # Handles offset alignment item `item` (adds padding).
+ def _handle_align_offset_item(
+ self, item: _AlignOffset, state: _GenState, next_vl_instance: int
+ ):
+ init_offset = state.offset
+ state.offset = self._align_offset(state.offset, item)
+ self._data += bytes([item.pad_val] * (state.offset - init_offset))
+ return next_vl_instance
+
# Handles the label item `item`.
def _handle_label_item(self, item: _Label, state: _GenState, next_vl_instance: int):
return next_vl_instance
# Item handlers
self._item_handlers = {
+ _AlignOffset: self._handle_align_offset_item,
_Byte: self._handle_byte_item,
_FlNum: self._handle_fl_num_item,
_Group: self._handle_group_item,
_VarAssign: self._handle_var_assign_item,
} # type: Dict[type, Callable[[Any, _GenState, int], int]]
- # Item size getters
- self._item_size_funcs = {
- _Byte: self._scalar_item_size,
- _FlNum: self._scalar_item_size,
- _Group: self._group_item_size,
- _Label: self._zero_item_size,
- _Rep: self._rep_item_size,
- _SetBo: self._zero_item_size,
- _SetOffset: self._zero_item_size,
- _SLeb128Int: self._leb128_int_item_size,
- _Str: self._scalar_item_size,
- _ULeb128Int: self._leb128_int_item_size,
- _VarAssign: self._zero_item_size,
- } # type: Dict[type, Callable[[Any, int], Tuple[int, int]]]
+ # Dry item handlers (only updates the state offset)
+ self._dry_handle_item_funcs = {
+ _AlignOffset: self._dry_handle_align_offset_item,
+ _Byte: self._dry_handle_scalar_item,
+ _FlNum: self._dry_handle_scalar_item,
+ _Group: self._dry_handle_group_item,
+ _Label: self._update_offset_noop,
+ _Rep: self._dry_handle_rep_item,
+ _SetBo: self._update_offset_noop,
+ _SetOffset: self._dry_handle_set_offset_item,
+ _SLeb128Int: self._dry_handle_leb128_int_item,
+ _Str: self._dry_handle_scalar_item,
+ _ULeb128Int: self._dry_handle_leb128_int_item,
+ _VarAssign: self._update_offset_noop,
+ } # type: Dict[type, Callable[[Any, _GenState, int], int]]
# Handle the group item, _not_ removing the immediate labels
# because the `labels` property offers them.