diff --git a/src/sgfParser/node.py b/src/sgfParser/node.py --- a/src/sgfParser/node.py +++ b/src/sgfParser/node.py @@ -1,5 +1,5 @@ from . import skipWhitespace, ParserError -from .property import Property +from .property import Property, GAME_INFO class Node: @@ -27,7 +27,7 @@ class Node: return (i,res) def isGINode(self): - return any(prop.type==Property.GAME_INFO for prop in self.properties.values()) + return any(prop.type==GAME_INFO for prop in self.properties.values()) def setProperty(self,name,value): self.properties[name]=value diff --git a/src/sgfParser/property.py b/src/sgfParser/property.py --- a/src/sgfParser/property.py +++ b/src/sgfParser/property.py @@ -1,10 +1,11 @@ import re +GAME_INFO=1 +UNKNOWN=99 + + class Property: - GAME_INFO=1 - UNKNOWN=99 - def __init__(self): self.name="" self.value="" @@ -33,8 +34,136 @@ class Property: @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 Property.GAME_INFO - else: return Property.UNKNOWN + if self.name in gameInfo: return GAME_INFO + else: return UNKNOWN + + +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,(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(),(col,row)) + +move=point +stone=point class PropValue: @@ -42,134 +171,13 @@ class PropValue: self.type="" self.value=None - patterns=dict() - @staticmethod def create(s,start,name): if name in PropValue.patterns: return PropValue.patterns[name](s,start) else: print('warning, unknown property "{0}" at position {1}'.format(name,start)) - return PropValue.singleton(PropValue.anything)(s,start) - - 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=PropValue.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) - # print(">",i,a) - if a==None or s[i]!=":": return (start,None) - i,b=vTypeB(s,i+1) - # print(">",i,b) - if b==None: return start,None - return (i,(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(),(col,row)) - - move=point - stone=point + return singleton(anything)(s,start) patterns={ "B":singleton(move), @@ -243,89 +251,6 @@ class PropValue: "TW":listOf(point,allowEmpty=True) } + # TODO: # date - - -""" -# move -B move -KO none -MN number -W move - -# setup -AB list of stone -AE list of point -AW list of stone -PL color - -# node annotation -C text -DM double -GB double -GW double -HO double -N simpleText -UC double -V real - -# move annotation -BM double -DO none -IT none -TE double - -# markup -AR list of composed point:point -CR list of point -DD elist of point -LB list of composed point:simpleText -LN list of composed point:point -MA list of point -SL list of point -SQ list of point -TR list of point - -# root -AP composed simpleText:simpleText -CA simpleText -FF number -GM number -ST number -SZ number | composed number:number - -# game info -AN simpleText -BR simpleText -BT simpleText -CP simpleText -DT simpleText -EV simpleText -GN simpleText -GC text -ON simpleText -OT simpleText -PB simpleText -PC simpleText -PW simpleText -RE simpleText -RO simpleText -RU simpleText -SO simpleText -TM real -US simpleText -WR simpleText -WT simpleText - -# timing -BL real -OB number -OW number -WL real - -# misc -FG none | composition of number:simpleText -PM number -VW elist of point -""" diff --git a/src/tests/testSgfParser.py b/src/tests/testSgfParser.py --- a/src/tests/testSgfParser.py +++ b/src/tests/testSgfParser.py @@ -3,11 +3,22 @@ from unittest import TestCase import os from sgfParser.collection import Collection +from sgfParser.property import Property dataDir=os.path.join(os.path.dirname(__file__), "data") +class TestProperty(TestCase): + def testName(self): + self.assertEqual(Property.create("[99]",0), (0,None)) + self.assertEqual(Property.create("99[99]",0), (0,None)) + + i,prop=Property.create("MN[99]",0) + self.assertNotEqual((i,prop), (0,None)) + self.assertEqual((i,prop.name), (6,"MN")) + + class TestCollection(TestCase): def testEmptySgf(self): Collection("(;)")