Changeset - 36b0271e7393
[Not reviewed]
default
0 2 0
Laman - 6 years ago 2019-05-29 16:30:43

more lenient and robust error handling
2 files changed with 9 insertions and 3 deletions:
0 comments (0 inline, 0 general)
src/diana/sgfParser/node.py
Show inline comments
 
from collections import deque
 
import logging as log
 

	
 
from . import skipWhitespace, ParserWarning
 
from .property import Property, GAME_INFO
 

	
 

	
 
class Node:
 
	def __init__(self):
 
		self.properties=dict()
 
		self.parent=None
 
		self.children=[]
 

	
 
	@staticmethod
 
	def fits(s,i):
 
		return i<len(s) and s[i]==";"
 

	
 
	@staticmethod
 
	def create(s,start):
 
		assert Node.fits(s,start)
 
		res=Node()
 

	
 
		i=skipWhitespace(s,start+1)
 
		while Property.fits(s,i):
 
			i,x=Property.create(s,i)
 
			if x.name in res.properties:
 
				# !! raise or log or ignore
 
				raise ParserWarning('duplicate "{0}" property in a node. second value ignored'.format(x.name),s,i)
 
				log.warning(ParserWarning('duplicate "{0}" property in a node. second value ignored'.format(x.name),s,i))
 
			else:
 
				res.properties[x.name]=x
 
			i=skipWhitespace(s,i)
 
		return (i,res)
 

	
 
	def listGINodes(self):
 
		if self.isGINode(): yield self
 
		empty=not self.isGINode()
 

	
 
		node=self
 
		while node.parent:
 
			node=node.parent
 
			if node.isGINode():
 
				empty=False
 
				yield node
 

	
 
		queue=deque(self.children)
 
		while len(queue)>0:
 
			node=queue.popleft()
 
			if node.isGINode():
 
				empty=False
 
				yield node
 
			queue.extend(node.children)
 
		if empty: yield self # always yield at least self, can work as GINode as well as any other
 

	
 
	def isGINode(self):
 
		return any(prop.type==GAME_INFO for prop in self.properties.values())
 

	
 
	def setProp(self,name,value):
 
		self.properties[name]=value
 
		# zkontrolovat typ value
 

	
 
	def setChildren(self,children):
 
		self.children=children
 
		for child in children: child.parent=self
 

	
 
	def addChild(self,node):
 
		if node in self.children: return node
 
		node.parent=self
 
		self.children.append(node)
 
		return node
 

	
 
	def removeChild(self,node):
 
		if node not in self.children:
 
			return None
 
		del self.children[self.children.index(node)]
 
		node.parent=None
 
		return node
 

	
 
	def removeChildAt(self,i):
 
		if -len(self.children)<i<len(self.children):
 
			res=self.children[i]
 
			del self.children[i]
 
			res.parent=None
 
			return res
 
		return None
 

	
 
	## Create a copy of the Node, with the same parent and deep copied properties, no copied children.
 
	def copy(self):
 
		res=Node()
 
		res.properties={k: v.copy() for (k,v) in self.properties.items()}
 
		res.parent=self.parent
 
		return res
 

	
 
	def getProp(self,name,default=None):
 
		if name in self.properties: return self.properties[name].value
 
		else: return default
 

	
 
	## Returns textual representation of the Node itself, but disregards its children.
 
	def __str__(self):
 
		return ";" + "".join(str(p) for p in self.properties.values())
 

	
 
	def export(self):
 
		# there is a beatiful recursive solution, which this stack is too narrow to contain
 
		stack=[(self,1,1)]
 
		output=[]
 

	
 
		while len(stack)>0:
 
			node,left,right=stack.pop()
 
			if left>0: output.append("("*left)
 
			output.append(str(node))
 

	
 
			childCount=len(node.children)
 
			if childCount==0: # a leaf
 
				output.append(")"*right)
 
			elif childCount==1: # a line
 
				stack.append((node.children[0],0,right))
 
			else: # a branching node
 
				# first child pops first, last child closes parent's parentheses
 
				children=zip(node.children,[1]*childCount,[1]*(childCount-1)+[1+right])
 
				stack.extend(reversed(children))
 

	
 
		return "".join(output)
src/diana/sgfParser/property.py
Show inline comments
 
import re
 
import logging as log
 

	
 
from .propValues import choose, singleton, listOf, compose, number, real, double, color, text, empty, anything, point, move, stone
 
from . import skipWhitespace, ParserError
 

	
 
GAME_INFO=1
 
UNKNOWN=99
 

	
 

	
 
class Property:
 
	identRegexp=re.compile(r"[A-Z]+")
 

	
 
	def __init__(self):
 
		self.name=""
 
		self.value=""
 

	
 
	@staticmethod
 
	def fits(s,i):
 
		return i<len(s) and s[i].isupper()
 

	
 
	@staticmethod
 
	def create(s,start):
 
		assert Property.fits(s,start)
 
		res=Property()
 
		i,res.name=Property.ident(s,start)
 
		i=skipWhitespace(s,i)
 
		i,x=Property.createValue(s,i,res.name)
 
		try:
 
			i,x=Property.createValue(s,i,res.name)
 
		except ParserError as e: # malformed value
 
			log.warning(e)
 
			i,x=choose(listOf(anything), singleton(anything))(s,i)
 
			res.name="_"+res.name
 
		res.value=x
 
		i=skipWhitespace(s,i)
 
		return (i,res)
 

	
 
	@staticmethod
 
	def ident(s,start):
 
		m=Property.identRegexp.match(s,start)
 
		if m is None: raise ParserError("expected a property identifier matching '[A-Z]+'",s,start)
 
		return (m.end(),m.group())
 

	
 
	@staticmethod
 
	def createValue(s,start,name):
 
		if name in Property.patterns:
 
			return Property.patterns[name](s,start)
 
		else:
 
			# !! raise or log or ignore
 
			# print('warning, unknown property "{0}" at position {1}'.format(name,start))
 
			return choose(listOf(anything), 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
0 comments (0 inline, 0 general)