150 lines
4.3 KiB
Python
150 lines
4.3 KiB
Python
import re
|
|
|
|
try:
|
|
from scripts.dumpunetlib.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*(?<!\\)(?:\\\\)*:")
|
|
re_colon = re.compile(r"\s*:")
|
|
re_eoc = re.compile(r"(.*?)(?:(?<!\\)(?:\\\\)*;|$)")
|
|
|
|
def parse(s: str):
|
|
pos = 0
|
|
|
|
def fail(msg: str|None):
|
|
bad_prompt(s, pos, msg)
|
|
|
|
def M(re: re.Pattern, group=0) -> 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)
|