Files
past-project-past_code_cc2650/python_test_code/devlib/util.py
T
2019-05-24 12:29:49 +08:00

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))