import re from . import ParserError, skipWhitespace 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) composedText = re.compile(r"(?:.*?[^\\])??(?:\\\\)*(?=]|:)", re.DOTALL) class Text: softBreaks = re.compile(r"(^|[^\\])((\\\\)*)\\((\n\r)|(\r\n)|\r|\n)") whitespace = re.compile(r"[\t\f\v]") simpleWhitespace = re.compile(r"[\t\f\v\n\r]") removeSlashes = re.compile(r"(^|[^\\])((\\\\)*)\\($|[^\\])") unescapeSlashes = 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(*vTypes): def f(s, start): for vType in vTypes: 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 singletonFits(s, i): return i < len(s) and s[i] == "[" def singletonEnds(s, i): return i < len(s) and s[i] == "]" def singleton(vType): def f(s, start): if not singletonFits(s, start): raise ParserError("expected a property value starting with '['", s, start) (i, x) = vType(s, start+1) if not singletonEnds(s, i): raise ParserError("expected a property value ending with ']'", s, i) i = skipWhitespace(s, i+1) return (i, x) return f def listOf(vType, allowEmpty=False): def f(s, start): i = start if not singletonFits(s, i): raise ParserError("expected a property value starting with '['", s, i) if singletonEnds(s, i+1) and allowEmpty: i = skipWhitespace(s, i+2) return (i, []) single = singleton(vType) (i, x) = single(s, i) res = [x] while singletonFits(s, i): (i, x) = single(s, i) res.append(x) return (i, res) return f def compose(vTypeA, vTypeB): def f(s, start): (i, a) = vTypeA(s, start) if i >= len(s) or s[i] != ":": raise ParserError("expected a composed property value separated by ':'", s, i) (i, b) = vTypeB(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.composedText.match(s, start) if composed else Regexp.text.match(s, start) res = m.group(0) res = regexps.softBreaks.sub(r"\1\2", res) # remove soft line breaks if simple: res = regexps.simpleWhitespace.sub(" ", res) # convert whitespace to spaces, no escapes else: res = regexps.whitespace.sub(" ", res) # convert whitespace to spaces, no escapes res = regexps.removeSlashes.sub(r"\1\2\4", res) res = regexps.unescapeSlashes.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