From 5103ad1d5cbf2ea18a45783d1789f2427b6824ec Mon Sep 17 00:00:00 2001 From: Ta-Shun Su Date: Fri, 24 May 2019 12:29:49 +0800 Subject: [PATCH] add instruction eval --- python_test_code/devlib/__init__.py | 0 python_test_code/devlib/main.py | 20 + python_test_code/devlib/util.py | 582 ++++++++++++++++++++++++++++ 3 files changed, 602 insertions(+) create mode 100644 python_test_code/devlib/__init__.py create mode 100644 python_test_code/devlib/main.py create mode 100644 python_test_code/devlib/util.py diff --git a/python_test_code/devlib/__init__.py b/python_test_code/devlib/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python_test_code/devlib/main.py b/python_test_code/devlib/main.py new file mode 100644 index 000000000..91c52ccaf --- /dev/null +++ b/python_test_code/devlib/main.py @@ -0,0 +1,20 @@ +from devlib.util import print_instruction + +STI_INSTRUCTION = { + 'GLOBAL': '3b001#header#;1b0;12bCURRENT', + 'LOCAL': '3b010#header;1b0;4b>CHANNEL;3b>STI_MODE;1b>PRECISION;12b>STI_FREQ;10b>STI_PW;10b>STI_PW_IPI;10b>STI_NUM' +} + +STI_PARAMETER = { + 'CURRENT': 10, + 'CHANNEL': 0, + 'STI_MODE': 3, + 'PRECISION': 0, + 'STI_FREQ': 10, + 'STI_PW': 10, + 'STI_PW_IPI': 10, + 'STI_NUM': 10, +} + +print_instruction(STI_INSTRUCTION['GLOBAL'], STI_PARAMETER) +print_instruction(STI_INSTRUCTION['LOCAL'], STI_PARAMETER) diff --git a/python_test_code/devlib/util.py b/python_test_code/devlib/util.py new file mode 100644 index 000000000..627e81505 --- /dev/null +++ b/python_test_code/devlib/util.py @@ -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(\*|\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))