583 lines
14 KiB
Python
583 lines
14 KiB
Python
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<SIZE>(\*|\d+))?(?P<TYPE>[XxDdOoBb])(?P<ENDIAN>[<>])?(?P<SIGN>\+)?(?P<EXPR>.+)')
|
|
|
|
@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))
|