import re try: from scripts.dumpunet.report import message as E except: # for debug def E(msg: str, title=""): return msg class BadPromptError(ValueError): prompt: str pos: int # 0-origin line: int # 0-origin linepos: int # 0-origin msg: str|None def __init__(self, prompt: str, pos: int, msg: str|None) -> None: self.prompt = prompt self.pos = pos self.msg = msg title = E("Syntax Error at line={0}, pos={1}") before = prompt[:pos] line = before.count("\n") if 0 < line: linepos = pos - before.rindex("\n") - 1 else: linepos = pos self.line = line self.linepos = linepos cur_line = prompt.splitlines()[line] show_range = (linepos - 20, linepos + 20) leading = " ... " trailing = " ... " if show_range[0] < 0: show_range = (0, 40) leading = "" if len(cur_line) <= show_range[1]: show_range = (show_range[0], len(cur_line)) trailing = "" title = title.format(line + 1, pos) self._message = f"""\ {title} {leading}{cur_line[show_range[0]:show_range[1]]}{trailing} {" " * (len(leading) + linepos)}^~~~ {msg} """ super().__init__(self._message) def message(self): return self._message # Scanner re_layer2 = re.compile("|".join( [ f"IN{n:02}" for n in range(12) ] + [ "M00" ] + [ f"OUT{n:02}" for n in range(12) ] )) re_space = re.compile(r"\s+") re_comma = re.compile(r"\s*,\s*") re_hyphen = re.compile(r"\s*-\s*") re_under_colon = re.compile(r"\s*_\s*:\s*") #re_colon = re.compile(r"\s*(? tuple[int,str]|None: nonlocal pos match = re.match(s, pos=pos) if match: pos = match.end() return match.start(), match.group(group) else: return None def skip(re, group=0) -> bool: return False if M(re) is None else True def need(re, group=0, ex:str|None=None) -> tuple[int,str]: m = M(re, group) if m is None: fail(f"expecting {ex}, but not." if ex else None) return m def parse_layer2() -> tuple[int,str]: # IN00 | IN01 | ... return need(re_layer2, ex="a layer name (IN00, IN01, ...)") def parse_layer1() -> tuple[tuple[int,str],tuple[int,str]]|tuple[int,str]: # layer2 (- layer2)? left = parse_layer2() if left is None: fail("a layer name (IN00, IN01, ...)") if not skip(re_hyphen): return left right = parse_layer2() if right is None: fail("a layer name (IN00, IN01, ...)") return (left, right) def parse_layer() -> list[tuple[tuple[int,str],tuple[int,str]]|tuple[int,str]]: # layer1 (, layer)* left = parse_layer1() if left is None: fail("a layer name (IN00, IN01, ...)") if not skip(re_comma): return [left] right = parse_layer() if right: right.append(left) return right else: fail("a layer name (IN00, IN01, ...)") def parse_content() -> tuple[int,str]: return need(re_eoc, 1, ex="semi-colon (;) or EOS ($)") def parse_pair() -> tuple[list[tuple[tuple[int,str],tuple[int,str]]|tuple[int,str]], tuple[int,str]]: skip(re_space) any = M(re_under_colon) if any is not None: layers: list[tuple[tuple[int,str],tuple[int,str]]|tuple[int,str]] = [(any[0],"_")] else: layers = parse_layer() layers.reverse() need(re_colon, ex="colon (:)") content = parse_content() skip(re_space) return layers, content result: list[tuple[list[tuple[tuple[int,str],tuple[int,str]]|tuple[int,str]], tuple[int,str]]] result = [] while pos < len(s): layers, content = parse_pair() result.append((layers, content)) return result def bad_prompt(s: str, pos: int, msg: str|None = None): raise BadPromptError(s, pos, msg)