add instruction eval
This commit is contained in:
@@ -0,0 +1,582 @@
|
||||
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))
|
||||
Reference in New Issue
Block a user