import re from typing import * T = TypeVar('T') @overload def part_prefix(line: str, char: str, from_right=False) -> Tuple[Optional[str], str]: pass @overload def part_prefix(line: str, char: str, missing: T, from_right=False) -> Tuple[Union[str, T], str]: pass def part_prefix(line: str, char: str, missing: Optional[T] = None, from_right=False) -> Tuple[Union[None, str, T], str]: try: if from_right: i = line.rindex(char) else: i = line.index(char) return line[:i], line[i + len(char):] except ValueError: return missing, line @overload def part_suffix(line: str, char: str, from_right=False) -> Tuple[str, Optional[str]]: pass @overload def part_suffix(line: str, char: str, missing: T, from_right=False) -> Tuple[str, Union[str, T]]: pass def part_suffix(line: str, char: str, missing: Optional[T] = None, from_right=False) -> Tuple[str, Union[None, str, T]]: try: if from_right: i = line.rindex(char) else: i = line.index(char) return line[:i], line[i + len(char):] except ValueError: return line, missing def hex_line(data: Union[bytes, Sequence[int]], split=' ', truncate=False) -> str: """ bytes value to str return example :: # input b'\x00ABC\x00' # output 0x00 0x65 0x66 0x67 0x00 # output truncate version 0x00 0x65 (skip 0x02) 0x00 :param data: bytes value :param split: char join between byte value :param truncate: truncate output if too long :return: byte value """ if truncate and len(data) > 16: line = list(map(lambda v: '%02X' % v, data[0:8])) line.append('(skip 0x%02X)' % (len(data) - 12)) line.extend(map(lambda v: '%02X' % v, data[-4:])) return split.join(line) else: return split.join(map(lambda v: '%02X' % v, data)) def endian_switch(value: int, bit: int) -> int: ret = 0 for _ in range(bit): ret = (ret << 1) | (value & 1) value >>= 1 return ret def append_buffer(buffer: List[int], shift: int, width: int, value: int, little_endian=False) -> int: if width == 0: return shift % 8 if len(buffer) == 0: buffer.append(0) if little_endian: value = endian_switch(value, width) while shift >= 8: buffer.append(0) shift -= 8 while shift + width >= 8: # high offset = 8 - shift mask = (1 << offset) - 1 buffer[-1] |= (value >> (width - offset)) & mask if shift + width == 8: return 8 # low buffer.append(0) shift = 0 width -= offset value &= (1 << width) - 1 offset = 8 - (shift + width) mask = (1 << width) - 1 buffer[-1] |= (value & mask) << offset return (shift + width) % 8 class InstructionContentWidth: """instruction width. **format** :: InstructionContentWidth :str = SIZE TYPE? ENDIAN? SIGN? base = BASE ")" SIGN = "+" **SIZE** bit width or byte length, default 1 '*' for array **TYPE** ==== ================================== TYPE description ==== ================================== b bits B bytes 2-base value o, O bytes 8-base value d, D bytes 10-base value x, X bytes 16-base value ==== ================================== **ENDIAN** ====== ================================== ENDIAN description ====== ================================== > big endian (default) < little endian ====== ================================== **BASE** value base, 2 for bit default, 16 for byte default. """ __slots__ = ('_size', '_bytes_type', '_signed', '_little_endian') BASE = { 'b': 2, 'B': 2, 'o': 8, 'O': 8, 'd': 10, 'D': 10, 'x': 16, 'X': 16, } def __init__(self, size: Optional[int], bytes_type: str, signed_value=False, little_endian=False): self._size = size self._bytes_type = bytes_type self._signed = signed_value self._little_endian = little_endian @property def size(self) -> Optional[int]: return self._size @property def little_endian(self) -> bool: return self._little_endian @property def signed(self) -> bool: # XXX InstructionContentWidth.signed not used return self._signed @property def bytes_unit(self) -> bool: return self._bytes_type in 'BODX' @property def is_array(self) -> bool: # XXX InstructionContentWidth.is_array not used return self._size is None @property def width(self) -> int: if self._bytes_type in 'bB': return self._size else: return self._size * 8 @property def value_base(self) -> int: return self.BASE[self._bytes_type] def __str__(self): return '%s%s%s%s' % (str(self._size) if self._size is not None else '*', self._bytes_type, '<' if self._little_endian else '>', '+' if self._signed else '') PATTERN = re.compile( r'(?P(\*|\d+))?(?P[XxDdOoBb])(?P[<>])?(?P\+)?(?P.+)') @classmethod def parse(cls, expr: str) -> Tuple['InstructionContentWidth', str]: m = cls.PATTERN.match(expr) if m is None: raise RuntimeError('illegal instruction content width : ' + expr) z = m.group('SIZE') t = m.group('TYPE') e = m.group('ENDIAN') s = m.group('SIGN') x = m.group('EXPR') if z is None: z = 1 elif z == '*': z = None else: z = int(z) return InstructionContentWidth(z, t, signed_value=s is not None, little_endian=e == '<'), x class InstructionContent: """ **format** :: InstructionContent = "#" ... # comment | InstructionContent "#" ... # instruction with comment | InstructionDataContent | InstructionConditionContent | CompactInstructionContent """ __slots__ = ('_comment',) def __init__(self, comment: Optional[str] = None): self._comment: Optional[str] = comment @property def width(self) -> int: return 0 def build_instruction(self, context: Dict[str, Any], buffer: List[int], shift: int) -> int: """ :param context: general case root :param buffer: instruction buffer :param shift: data offset :return: next data offset """ return shift def __str__(self): if self._comment is None: return '#' else: return '#' + self._comment class InstructionDataContent(InstructionContent): """ **format** :: InstructionDataContent = InstructionContentWidth instruction_content instruction_content = int # binary number | parameter_name :str | parameter_name[index:int] # array parameter access """ __slots__ = ('_width', '_value', '_index') def __init__(self, width: InstructionContentWidth, value: Union[int, str], comment: Optional[str] = None): super().__init__(comment) self._width: InstructionContentWidth = width if isinstance(value, str) and '[' in value and value.endswith(']'): value, index = part_suffix(value[:-1], '[') self._value: Union[int, str] = value self._index: str = index else: self._value: Union[int, str] = value self._index: str = None @property def width(self) -> int: return self._width.width def value(self, context: Dict[str, Any]) -> Union[int, List[int]]: value = self._value if isinstance(value, int): return value try: return int(value, self._width.value_base) except ValueError: ret = context[value] index = self._index if index is None: return ret else: try: index = int(index) except ValueError: index = int(context[index]) return ret[index] def build_instruction(self, context: Dict[str, Any], buffer: List[int], shift: int) -> int: if self._width.is_array: raise NotImplemented() else: value = int(self.value(context)) if self._width.bytes_unit: if self._width.size == 1: buffer.append(value) else: tmp = [] for _ in range(self._width.size): tmp.append(value & 0xFF) value >>= 8 if self._width.little_endian: buffer.extend(tmp) else: buffer.extend(tmp[::-1]) return 8 else: width = self._width.width return append_buffer(buffer, shift, width, value, little_endian=self._width.little_endian) def __str__(self): if isinstance(self._value, int): value = bin(self._value)[2:] else: if self._index is not None: value = '%s[%s]' % (self._value, str(self._index)) else: value = self._value if self._comment is None: return '%s%s' % (self._width, value) else: return '%s%s#%s' % (self._width, value, self._comment) class InstructionConditionContent(InstructionDataContent): """ **json format** :: InstructionConditionContent = InstructionContentWidth parameter_name "?" | InstructionContentWidth parameter_name "?:" if_false | InstructionContentWidth parameter_name "?" if_true ":" if_false if_true, if_false = int | parameter_name :str """ __slots__ = ('_true', '_false') def __init__(self, width: InstructionContentWidth, value: str, if_true: Union[None, int, str] = None, if_false: Union[None, int, str] = None, comment: Optional[str] = None): if if_true is not None and if_false is None: raise RuntimeError('lost if_true') super().__init__(width, value, comment) self._true = if_true self._false = if_false def value(self, context: Dict[str, Any]) -> Union[int, List[int]]: value = context[self._value] test = value is not None if self._true is None and self._false is None: return 1 if test else 0 elif self._true is None: assert self._false is not None if test: return value else: branch = self._false else: assert self._true is not None assert self._false is not None branch = self._true if test else self._false if isinstance(branch, int): return branch try: return int(branch, self._width.value_base) except ValueError: return context[branch] def __str__(self): if self._comment is None: c = '' else: c = '#' + self._comment if self._true is None and self._false is None: return "%s%s?%s" % (self._width, self._value, c) elif self._true is None: return "%s%s?:%s%s" % (self._width, self._value, self._false, c) else: return "%s%s?%s:%s%s" % (self._width, self._value, self._true, self._false, c) class CompactInstructionContent(InstructionContent): """ **json format** :: CompactInstructionContent = InstructionContent ";" InstructionContent ... """ __slots__ = ('_instruction',) def __init__(self, instruction: List[InstructionContent]): super().__init__() self._instruction = instruction @property def width(self) -> int: return sum(map(lambda i: i.width, self._instruction)) def build_instruction(self, context: Dict[str, Any], buffer: List[int], shift: int) -> int: for instruction in self._instruction: shift = instruction.build_instruction(context, buffer, shift) return shift def __str__(self) -> str: return ';'.join(map(str, self._instruction)) def parse_instruction(expr: str) -> InstructionContent: origin_expr = expr if expr == '#': return InstructionContent() elif expr.startswith('#'): return InstructionContent(comment=expr[1:]) elif ';' in expr: return CompactInstructionContent([parse_instruction(v) for v in expr.split(';')]) else: ins_argv = {} expr, comment = part_suffix(expr, '#') expr, condition = part_suffix(expr, '?') if condition is not None: ins_type = InstructionConditionContent if len(condition) == 0: ins_argv['if_true'] = None ins_argv['if_false'] = None else: t, f = part_prefix(condition, ':') if t is None: raise RuntimeError('illegal condition instruction : ' + origin_expr) elif len(t) == 0: t = None ins_argv['if_true'] = t ins_argv['if_false'] = f else: ins_type = InstructionDataContent width, expr = InstructionContentWidth.parse(expr) return ins_type(width, expr, **ins_argv, comment=comment) def eval_instruction(expr: str, context: Dict[str, Any], buffer: Optional[List[int]] = None) -> List[int]: ins = parse_instruction(expr) if buffer is None: buffer = [] ins.build_instruction(context, buffer, 0) return buffer def print_instruction(expr: str, context: Dict[str, Any], append_ris_type: bool = True): buffer = [] eval_instruction(expr, context, buffer) if append_ris_type: length = len(buffer) buffer.insert(0, 0x30) buffer.insert(1, length) print(hex_line(buffer))