import re GAME_INFO=1 UNKNOWN=99 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) def choose(*vTypes): def f(s,start): for vType in vTypes: i,x=vType(s,start) if x is not None: return (i,x) return (start,None) return f def singleton(vType): def f(s,start): if s[start]!="[": return (start,None) i,x=vType(s,start+1) if x is None: return (start,None) if s[i]!="]": return (start,None) return (i+1,x) return f def listOf(vType,allowEmpty=False): def f(s,start): res=[] single=singleton(vType) i,x=single(s,start) while x!=None: res.append(x) i,x=single(s,i) if len(res)==0 and not allowEmpty: return (start,None) return (i,res) return f def compose(vTypeA,vTypeB): def f(s,start): i,a=vTypeA(s,start) if a==None or s[i]!=":": return (start,None) i,b=vTypeB(s,i+1) if b==None: return start,None return (i,Composed(a,b)) return f def number(s,start): r=re.compile(r"(\+|-|)\d+") m=r.match(s,start) if m is None: return (start,None) res=int(m.group(0)) return (m.end(),res) def real(s,start): r=re.compile(r"(\+|-|)\d+(\.\d+)?") m=r.match(s,start) if m is None: return (start,None) res=float(m.group(0)) return (m.end(),res) def double(s,start): r=re.compile(r"1|2") m=r.match(s,start) if m is None: return (start,None) res=int(m.group(0)) return (m.end(),res) def color(s,start): r=re.compile(r"B|W") m=r.match(s,start) if m is None: return (start,None) return (m.end(),m.group(0)) def text(simple=True,composed=False): def f(s,start): res="" esc=False lastC="" for i,c in enumerate(s[start:],start): if esc: if c!="\n" and c!="\r": res+=c esc=False elif (c=="\n" and lastC=="\r") or (c=="\r" and lastC=="\n"): pass elif c=="\r" or c=="\n" and not simple: res+="\n" elif c.isspace(): res+=" " elif c=="\\": esc=True elif c=="]" or (c==":" and composed): break else: res+=c lastC=c return (i,res) return f def empty(s,start): return (start,"") def anything(s,start): esc=False 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): r=re.compile(r"[a-zA-Z]{2}|") # !! limit to board size m=r.match(s,start) if m is None: return (start,None) 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 class Property: def __init__(self): self.name="" self.value="" @staticmethod def create(s,start): res=Property() i,x=Property.ident(s,start) if x is None: return (start,None) res.name=x i,x=Property.createValue(s,i,res.name) if x is None: print('error when parsing property "{0}" at position {1}'.format(res.name,i)) return (start,None) res.value=x return (i,res) @staticmethod def ident(s,start): r=re.compile(r"[A-Z]+") m=r.match(s,start) if m is None: return (start,None) return (m.end(),m.group()) @staticmethod def createValue(s,start,name): if name in Property.patterns: return Property.patterns[name](s,start) else: print('warning, unknown property "{0}" at position {1}'.format(name,start)) return singleton(anything)(s,start) @property def type(self): gameInfo={"AN","BR","BT","CP","DT","EV","GN","GC","ON","OT","PB","PC","PW","RE","RO","RU","SO","TM","US","WR","WT"} if self.name in gameInfo: return GAME_INFO else: return UNKNOWN def copy(self): res=Property() res.name=self.name res.value=self.value if not isinstance(self.value,list) else self.value[:] return res def __str__(self): val="[{0}]".format(self.value) if not isinstance(self.value,list) else "".join("[{0}]".format(x) for x in self.value) return "{0}{1}".format(self.name,val) patterns={ "B":singleton(move), "KO":singleton(empty), "MN":singleton(number), "W":singleton(move), "AB":listOf(stone), # "AE":listOf(point), # "AW":listOf(stone), # "PL":singleton(color), "C":singleton(text(simple=False)), "DM":singleton(double), "GB":singleton(double), "GW":singleton(double), "HO":singleton(double), "N":singleton(text()), "UC":singleton(double), "V":singleton(real), "BM":singleton(double), "DO":singleton(empty), "IT":singleton(empty), "TE":singleton(double), "AR":listOf(compose(point,point)), # "CR":listOf(point), # "DD":listOf(point,allowEmpty=True), # "LB":listOf(compose(point,text())), # "LN":listOf(compose(point,point)), # "MA":listOf(point), # "SL":listOf(point), # "SQ":listOf(point), # "TR":listOf(point), # "AP":singleton(compose(text(composed=True),text())), # "CA":singleton(text()), "FF":singleton(number), "GM":singleton(number), "ST":singleton(number), "SZ":choose(singleton(number),singleton(compose(number,number))), # "AN":singleton(text()), "BR":singleton(text()), "BT":singleton(text()), "CP":singleton(text()), "DT":singleton(text()), "EV":singleton(text()), "GN":singleton(text()), "GC":singleton(text(simple=False)), "ON":singleton(text()), "OT":singleton(text()), "PB":singleton(text()), "PC":singleton(text()), "PW":singleton(text()), "RE":singleton(text()), "RO":singleton(text()), "RU":singleton(text()), "SO":singleton(text()), "TM":singleton(real), "US":singleton(text()), "WR":singleton(text()), "WT":singleton(text()), "BL":singleton(real), "OB":singleton(number), "OW":singleton(number), "WL":singleton(real), "FG":choose(singleton(empty),singleton(compose(number,text()))), # "PM":singleton(number), "VW":listOf(point,allowEmpty=True), # # go specific "HA":singleton(number), "KM":singleton(real), "TB":listOf(point,allowEmpty=True), "TW":listOf(point,allowEmpty=True) } # !! TODO: date