import re from . import ParserError, skip_whitespace class Regexp: number = re.compile(r"(\+|-|)\d+") real = re.compile(r"(\+|-|)\d+(\.\d+)?") point = re.compile(r"[a-zA-Z]{2}|") text = re.compile(r"(?:.*?[^\\])??(?:\\\\)*(?=])", re.DOTALL) composed_text = re.compile(r"(?:.*?[^\\])??(?:\\\\)*(?=]|:)", re.DOTALL) class Text: soft_breaks = re.compile(r"(^|[^\\])((\\\\)*)\\((\n\r)|(\r\n)|\r|\n)") whitespace = re.compile(r"[\t\f\v]") simple_whitespace = re.compile(r"[\t\f\v\n\r]") remove_slashes = re.compile(r"(^|[^\\])((\\\\)*)\\($|[^\\])") unescape_slashes = re.compile(r"\\\\") class Composed: def __init__(self, a=None, b=None): self.a = a self.b = b def __str__(self): return "{0}:{1}".format(self.a, self.b) class Point: def __init__(self, c, r): self.r = r self.c = c def __iter__(self): yield self.c yield self.r def __str__(self): a = ord("a") return chr(a+self.c) + chr(a+self.r) ## Metatype matching one of the provided types. # # Returns the first match, so the order is important. def choose(*v_types): def f(s, start): for vType in v_types: try: (i, x) = vType(s, start) return (i, x) except ParserError: pass raise ParserError("no variant of a 'choose' property value matched", s, start) return f def singleton_fits(s, i): return i < len(s) and s[i] == "[" def singleton_ends(s, i): return i < len(s) and s[i] == "]" def singleton(v_type): def f(s, start): if not singleton_fits(s, start): raise ParserError("expected a property value starting with '['", s, start) (i, x) = v_type(s, start + 1) if not singleton_ends(s, i): raise ParserError("expected a property value ending with ']'", s, i) i = skip_whitespace(s, i + 1) return (i, x) return f def list_of(v_type, allow_empty=False): def f(s, start): i = start if not singleton_fits(s, i): raise ParserError("expected a property value starting with '['", s, i) if singleton_ends(s, i + 1) and allow_empty: i = skip_whitespace(s, i + 2) return (i, []) single = singleton(v_type) (i, x) = single(s, i) res = [x] while singleton_fits(s, i): (i, x) = single(s, i) res.append(x) return (i, res) return f def compose(v_type_a, v_type_b): def f(s, start): (i, a) = v_type_a(s, start) if i >= len(s) or s[i] != ":": raise ParserError("expected a composed property value separated by ':'", s, i) (i, b) = v_type_b(s, i + 1) return (i, Composed(a, b)) return f def number(s, start): m = Regexp.number.match(s, start) if m is None: raise ParserError("expected a number matching '{0}'".format(Regexp.number.pattern), s, start) res = int(m.group(0)) return (m.end(), res) def real(s, start): m = Regexp.real.match(s, start) if m is None: raise ParserError("expected a real number matching '{0}'".format(Regexp.real.pattern), s, start) res = float(m.group(0)) return (m.end(), res) def double(s, start): c = s[start] if c not in ("1", "2"): raise ParserError("expected a double value, either '1' or '2'", s, start) return (start+1, c) def color(s,start): c = s[start] if c not in ("B", "W"): raise ParserError("expected a color value, either 'B' or 'W'", s, start) return (start+1, c) def text(simple=True, composed=False): def f(s, start): regexps = Regexp.Text m = Regexp.composed_text.match(s, start) if composed else Regexp.text.match(s, start) res = m.group(0) res = regexps.soft_breaks.sub(r"\1\2", res) # remove soft line breaks if simple: res = regexps.simple_whitespace.sub(" ", res) # convert whitespace to spaces, no escapes else: res = regexps.whitespace.sub(" ", res) # convert whitespace to spaces, no escapes res = regexps.remove_slashes.sub(r"\1\2\4", res) res = regexps.unescape_slashes.sub(r"\\", res) # unescape slashes return (m.end(), res) return f def empty(s, start): return (start, "") def anything(s, start): esc = False i = start for (i, c) in enumerate(s[start:], start): if esc: esc = False elif c == "\\": esc = True elif c == "]": break return (i, s[start:i]) # go specific def point(s, start): m = Regexp.point.match(s, start) # !! limit to board size if m is None: raise ParserError("expected a point value matching '{0}'".format(Regexp.point.pattern), s, start) if m.group(0) == "": # pass, !! tt return (m.end(), tuple()) col = m.group(0)[0] row = m.group(0)[1] col = ord(col) - (ord("a") if "a" <= col <= "z" else ord("A")-26) row = ord(row) - (ord("a") if "a" <= row <= "z" else ord("A")-26) return (m.end(), Point(col, row)) move = point stone = point