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) 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