# HG changeset patch # User Laman # Date 2017-01-22 22:40:14 # Node ID 2ed7f0dab5efdcf0255854535d9a9fc0ee145662 # Parent cfcff53c74e67597eb294de0ae2dcff5806f127a expanding compressed game trees diff --git a/src/sgfParser/collection.py b/src/sgfParser/collection.py --- a/src/sgfParser/collection.py +++ b/src/sgfParser/collection.py @@ -30,7 +30,7 @@ class GameTree: if i>=len(s) or s[i]!="(": # print("error when parsing GameTree") return (start,None) - i,x=Node.create(s,start+1) + i,x=Node.create(s,i+1) if x is None: # print("error when parsing GameTree") return (i,None) @@ -67,14 +67,19 @@ class GameTree: return self.nodes[i] return None - ## Create and return a new GameTree containing the provided Node. + ## Create and return a new game tree containing the provided Node. # - # The Node objects are shared between the trees, not copied. - def _buildSubtree(self,node): - res=GameTree() + # Ancestor nodes are copied, descendants are taken directly. + def _buildSubtree(self,seedNode): + node=seedNode.copy() - return res - # chci celou cestu ke kořeni a všechny podstromy nodu + while node.parent: + newNode=node.parent.copy() + node.parent=newNode + newNode.setChildren([node]) + node=newNode + + return node ## Find and yield Game Info nodes. def _listGINodes(self): diff --git a/src/sgfParser/node.py b/src/sgfParser/node.py --- a/src/sgfParser/node.py +++ b/src/sgfParser/node.py @@ -5,8 +5,8 @@ from .property import Property, GAME_INF class Node: def __init__(self): self.properties=dict() - self._parent=None - self._children=[] + self.parent=None + self.children=[] @staticmethod def create(s,start): @@ -34,26 +34,37 @@ class Node: # zkontrolovat typ value def setParent(self,node): - self._parent=node + self.parent=node + + def setChildren(self,children): + self.children=children def addChild(self,node): - if node in self._children: return node - self._children.append(node) + if node in self.children: return node + self.children.append(node) return node def removeChild(self,node): - if node not in self._children: + if node not in self.children: return None - del self._children[self._children.index(node)] + del self.children[self.children.index(node)] return node def removeChildAt(self,i): - if -len(self._children)<i<len(self._children): - res=self._children[i] - del self._children[i] + if -len(self.children)<i<len(self.children): + res=self.children[i] + del self.children[i] return res return None + ## Create a copy of the Node, with deep copied propeties and shallow copied parent and children. + def copy(self): + res=Node() + res.properties={k: v.copy() for (k,v) in self.properties.items()} + res.parent=self.parent + res.setChildren(self.children[:]) + return res + def getProperty(self,name): if name in self.properties: return self.properties[name] else: return None diff --git a/src/sgfParser/property.py b/src/sgfParser/property.py --- a/src/sgfParser/property.py +++ b/src/sgfParser/property.py @@ -5,39 +5,6 @@ GAME_INFO=1 UNKNOWN=99 -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=PropValue.create(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()) - - @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 choose(*vTypes): def f(s,start): for vType in vTypes: @@ -149,6 +116,7 @@ def anything(s,start): 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 @@ -166,19 +134,53 @@ move=point stone=point -class PropValue: +class Property: def __init__(self): - self.type="" - self.value=None + self.name="" + self.value="" @staticmethod - def create(s,start,name): - if name in PropValue.patterns: - return PropValue.patterns[name](s,start) + 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 + # !! wouldn't work for listOf(listOf()) + res.value=self.value if not isinstance(self.value,list) else self.value[:] + return res + patterns={ "B":singleton(move), "KO":singleton(empty), @@ -252,5 +254,4 @@ class PropValue: } -# TODO: -# date +# !! TODO: date diff --git a/src/tests/testSgfParser.py b/src/tests/testSgfParser.py --- a/src/tests/testSgfParser.py +++ b/src/tests/testSgfParser.py @@ -20,6 +20,16 @@ class TestProperty(TestCase): class TestCollection(TestCase): + def testSubtrees(self): + c=Collection(""" +(;B[aa] + (;W[ab]PB[Some Black]PW[Some White];B[ac]) + (;W[bb]PB[Other Black]PW[Other White]) +)""") + games=list(c.listGames()) + + self.assertEqual(len(games),2) + def testEmptySgf(self): Collection("(;)")