# Upstream repository: <https://github.com/efficios/normand>.
__author__ = "Philippe Proulx"
-__version__ = "0.20.0"
+__version__ = "0.21.0"
__all__ = [
"__author__",
"__version__",
import re
import abc
import ast
+import bz2
import sys
import copy
import enum
+import gzip
import math
+import base64
+import quopri
import struct
import typing
+import functools
from typing import Any, Set, Dict, List, Union, Pattern, Callable, NoReturn, Optional
def __repr__(self):
return "_Str({}, {}, {}, {})".format(
- self.__class__.__name__,
repr(self._expr_str),
repr(self._expr),
repr(self._codec),
# Repetition item.
-class _Rep(_Item, _ExprMixin):
+class _Rep(_Group, _ExprMixin):
def __init__(
- self, item: _Item, expr_str: str, expr: ast.Expression, text_loc: TextLocation
+ self,
+ items: List[_Item],
+ expr_str: str,
+ expr: ast.Expression,
+ text_loc: TextLocation,
):
- super().__init__(text_loc)
+ super().__init__(items, text_loc)
_ExprMixin.__init__(self, expr_str, expr)
- self._item = item
-
- # Item to repeat.
- @property
- def item(self):
- return self._item
def __repr__(self):
return "_Rep({}, {}, {}, {})".format(
- repr(self._item),
+ repr(self._items),
repr(self._expr_str),
repr(self._expr),
repr(self._text_loc),
class _Cond(_Item, _ExprMixin):
def __init__(
self,
- true_item: _Item,
- false_item: _Item,
+ true_item: _Group,
+ false_item: _Group,
expr_str: str,
expr: ast.Expression,
text_loc: TextLocation,
)
+# Transformation.
+class _Trans(_Group, _RepableItem):
+ def __init__(
+ self,
+ items: List[_Item],
+ name: str,
+ func: Callable[[Union[bytes, bytearray]], bytes],
+ text_loc: TextLocation,
+ ):
+ super().__init__(items, text_loc)
+ self._name = name
+ self._func = func
+
+ @property
+ def name(self):
+ return self._name
+
+ # Transforms the data `data`.
+ def trans(self, data: Union[bytes, bytearray]):
+ return self._func(data)
+
+ def __repr__(self):
+ return "_Trans({}, {}, {}, {})".format(
+ repr(self._items),
+ repr(self._name),
+ repr(self._func),
+ repr(self._text_loc),
+ )
+
+
# Macro definition item.
-class _MacroDef(_Item):
+class _MacroDef(_Group):
def __init__(
- self, name: str, param_names: List[str], group: _Group, text_loc: TextLocation
+ self,
+ name: str,
+ param_names: List[str],
+ items: List[_Item],
+ text_loc: TextLocation,
):
- super().__init__(text_loc)
+ super().__init__(items, text_loc)
self._name = name
self._param_names = param_names
- self._group = group
# Name.
@property
def param_names(self):
return self._param_names
- # Contained items.
- @property
- def group(self):
- return self._group
-
def __repr__(self):
return "_MacroDef({}, {}, {}, {})".format(
repr(self._name),
repr(self._param_names),
- repr(self._group),
+ repr(self._items),
repr(self._text_loc),
)
# Parse items
self._skip_ws_and_comments_and_syms()
- items_text_loc = self._text_loc
items = self._parse_items()
# Expect end of block
)
# Return item
- return _Rep(_Group(items, items_text_loc), expr_str, expr, begin_text_loc)
+ return _Rep(items, expr_str, expr, begin_text_loc)
# Pattern for _try_parse_cond_block()
_cond_block_prefix_pat = re.compile(r"!if\b")
begin_text_loc,
)
+ # Pattern for _try_parse_trans_block()
+ _trans_block_prefix_pat = re.compile(r"!t(?:ransform)?\b")
+ _trans_block_type_pat = re.compile(
+ r"(?:(?:base|b)64(?:u)?|(?:base|b)(?:16|32)|(?:ascii|a|base|b)85(?:p)?|(?:quopri|qp)(?:t)?|gzip|gz|bzip2|bz2)\b"
+ )
+
+ # Tries to parse a transformation block, returning a transformation
+ # block item on success.
+ def _try_parse_trans_block(self):
+ begin_text_loc = self._text_loc
+
+ # Match prefix
+ if self._try_parse_pat(self._trans_block_prefix_pat) is None:
+ # No match
+ return
+
+ # Expect type
+ self._skip_ws_and_comments()
+ m = self._expect_pat(
+ self._trans_block_type_pat, "Expecting a known transformation type"
+ )
+
+ # Parse items
+ self._skip_ws_and_comments_and_syms()
+ items = self._parse_items()
+
+ # Expect end of block
+ self._expect_pat(
+ self._block_end_pat,
+ "Expecting an item or `!end` (end of transformation block)",
+ )
+
+ # Choose encoding function
+ enc = m.group(0)
+
+ if enc in ("base64", "b64"):
+ func = base64.standard_b64encode
+ name = "standard Base64"
+ elif enc in ("base64u", "b64u"):
+ func = base64.urlsafe_b64encode
+ name = "URL-safe Base64"
+ elif enc in ("base32", "b32"):
+ func = base64.b32encode
+ name = "Base32"
+ elif enc in ("base16", "b16"):
+ func = base64.b16encode
+ name = "Base16"
+ elif enc in ("ascii85", "a85"):
+ func = base64.a85encode
+ name = "Ascii85"
+ elif enc in ("ascii85p", "a85p"):
+ func = functools.partial(base64.a85encode, pad=True)
+ name = "padded Ascii85"
+ elif enc in ("base85", "b85"):
+ func = base64.b85encode
+ name = "Base85"
+ elif enc in ("base85p", "b85p"):
+ func = functools.partial(base64.b85encode, pad=True)
+ name = "padded Base85"
+ elif enc in ("quopri", "qp"):
+ func = quopri.encodestring
+ name = "MIME quoted-printable"
+ elif enc in ("quoprit", "qpt"):
+ func = functools.partial(quopri.encodestring, quotetabs=True)
+ name = "MIME quoted-printable (with quoted tabs)"
+ elif enc in ("gzip", "gz"):
+ func = gzip.compress
+ name = "gzip"
+ else:
+ assert enc in ("bzip2", "bz2")
+ func = bz2.compress
+ name = "bzip2"
+
+ # Return item
+ return _Trans(
+ items,
+ name,
+ func,
+ begin_text_loc,
+ )
+
# Common left parenthesis pattern
_left_paren_pat = re.compile(r"\(")
# Expect items
self._skip_ws_and_comments_and_syms()
- items_text_loc = self._text_loc
old_var_names = self._var_names.copy()
old_label_names = self._label_names.copy()
self._var_names = set() # type: Set[str]
)
# Register macro
- self._macro_defs[name] = _MacroDef(
- name, param_names, _Group(items, items_text_loc), begin_text_loc
- )
+ self._macro_defs[name] = _MacroDef(name, param_names, items, begin_text_loc)
return True
if item is not None:
return item
- # Macro expansion?
+ # Macro expansion item?
item = self._try_parse_macro_exp()
if item is not None:
return item
+ # Transformation block item?
+ item = self._try_parse_trans_block()
+
+ if item is not None:
+ return item
+
# Pattern for _try_parse_rep_post()
_rep_post_prefix_pat = re.compile(r"\*")
rep_ret = self._try_parse_rep_post()
if rep_ret is not None:
- item = _Rep(item, *rep_ret, text_loc=rep_text_loc)
+ item = _Rep([item], *rep_ret, text_loc=rep_text_loc)
items.append(item)
return True
self._macro_defs = macro_defs
self._fl_num_item_insts = [] # type: List[_FlNumItemInst]
self._parse_error_msgs = [] # type: List[ParseErrorMessage]
+ self._in_trans = False
self._gen(group, _GenState(variables, labels, offset, bo))
# Generated bytes.
try:
data = self._gen_fl_num_item_inst_data(item, state)
except Exception:
+ if self._in_trans:
+ _raise_error_for_item(
+ "Invalid expression `{}`: failed to evaluate within a transformation block".format(
+ item.expr_str
+ ),
+ item,
+ )
+
self._fl_num_item_insts.append(
_FlNumItemInst(
item,
item,
)
- # Generate item data `mul` times
+ # Generate group data `mul` times
for _ in range(mul):
- self._handle_item(item.item, state)
+ self._handle_group_item(item, state)
# Handles the conditional item `item`.
def _handle_cond_item(self, item: _Cond, state: _GenState):
# Compute the conditional value
val = _Gen._eval_item_expr(item, state)
- # Generate item data if needed
+ # Generate selected group data
if val:
- self._handle_item(item.true_item, state)
+ self._handle_group_item(item.true_item, state)
else:
- self._handle_item(item.false_item, state)
+ self._handle_group_item(item.false_item, state)
+
+ # Handles the transformation item `item`.
+ def _handle_trans_item(self, item: _Trans, state: _GenState):
+ init_in_trans = self._in_trans
+ self._in_trans = True
+ init_data_len = len(self._data)
+ init_offset = state.offset
+
+ # Generate group data
+ self._handle_group_item(item, state)
+
+ # Remove and keep group data
+ to_trans = self._data[init_data_len:]
+ del self._data[init_data_len:]
+
+ # Encode group data and append to current data
+ try:
+ transformed = item.trans(to_trans)
+ except Exception as exc:
+ _raise_error_for_item(
+ "Cannot apply the {} transformation to this data: {}".format(
+ item.name, exc
+ ),
+ item,
+ )
+
+ self._data += transformed
+
+ # Update offset and restore
+ state.offset = init_offset + len(transformed)
+ self._in_trans = init_in_trans
# Evaluates the parameters of the macro expansion item `item`
# considering the initial state `init_state` and returns a new state
)
)
self._parse_error_msgs.append(parse_error_msg)
- self._handle_item(self._macro_defs[item.name].group, exp_state)
+ self._handle_group_item(self._macro_defs[item.name], exp_state)
self._parse_error_msgs.pop()
except ParseError as exc:
_augment_error(exc, parse_error_msg_text, item.text_loc)
_SetOffset: self._handle_set_offset_item,
_SLeb128Int: self._handle_leb128_int_item,
_Str: self._handle_str_item,
+ _Trans: self._handle_trans_item,
_ULeb128Int: self._handle_leb128_int_item,
_VarAssign: self._handle_var_assign_item,
} # type: Dict[type, Callable[[Any, _GenState], None]]