Changeset - c6e20613189d
[Not reviewed]
default
0 4 0
Laman - 8 years ago 2017-01-29 18:39:34

added location of the ParserError in the parsed file
4 files changed with 33 insertions and 14 deletions:
0 comments (0 inline, 0 general)
src/sgfParser/__init__.py
Show inline comments
 
def skipWhitespace(s,start):
 
	i=start
 
	while i<len(s) and s[i].isspace(): i+=1
 
	return i
 

	
 

	
 
def lineNumber(s,i):
 
def strRowCol(s, i):
 
	k=0
 
	r=0
 
	r,c=0,0
 
	for (r,line) in enumerate(s.splitlines(True)):
 
		k+=len(line)
 
		if k>=i: break
 
	return r+1
 
		c=i-k
 
		if k+len(line)>i:
 
			break
 
		else:
 
			k+=len(line)
 
	return (r+1, c+1)
 

	
 

	
 
class ParserError(Exception):
 
	def __init__(self,message,s,i):
 
		# self.line=line
 
		# self.col=col
 
		# !! check for i<len(s)
 
		print(message)
 
		print(s[i:])
 
		self.message=message
 
	def __init__(self,msg,s,i):
 
		self.msg=msg
 
		self.row,self.col=strRowCol(s, i)
 

	
 
	def __str__(self):
 
		return "{0} at row {1}, col {2}".format(self.msg,self.row,self.col)
 

	
 

	
 
class ParserWarning(ParserError):
 
	pass
src/sgfParser/collection.py
Show inline comments
 
@@ -10,84 +10,84 @@ class Collection:
 
		elif not GameTree.fits(s,i):
 
			raise ParserError("expected a GameTree starting with '('",s,i)
 
		while GameTree.fits(s,i):
 
			i,x=GameTree.create(s,i)
 
			self.gameTrees.append(x)
 
		if i<len(s):
 
			raise ParserError("expected EOF",s,i)
 
 
	def listGames(self):
 
		for tree in self.gameTrees:
 
			for game in tree.listGames(): yield game
 
 
 
class GameTree:
 
	def __init__(self):
 
		self.nodes=[]
 
		self.branches=[]
 
 
	@staticmethod
 
	def fits(s,i):
 
		return i<len(s) and s[i]=="("
 
 
	@staticmethod
 
	def create(s,start):
 
		assert GameTree.fits(s,start)
 
		res=GameTree()
 
 
		i=skipWhitespace(s,start+1)
 
		if not Node.fits(s,i):
 
			raise ParserError("expected a Node starting with ';'",s,i)
 
 
		y=None
 
		while Node.fits(s,i):
 
			i,x=Node.create(s,i)
 
			res.nodes.append(x)
 
			if y: y.addChild(x)
 
			x.setParent(y)
 
			y=x
 
			i=skipWhitespace(s,i)
 
 
		while GameTree.fits(s,i):
 
			i,x=GameTree.create(s,i)
 
			res.branches.append(x)
 
			subroot=x.getNode(0)
 
			subroot.setParent(y)
 
			if y: y.addChild(subroot)
 
			i=skipWhitespace(s,i)
 
		if i>=len(s) or s[i]!=")":
 
			raise ParserError("expected end of the GameTree marked by ')'",s,i)
 
			raise ParserError("expected end of a GameTree marked by ')'",s,i)
 
		i=skipWhitespace(s,i+1)
 
		return (i,res)
 
 
	## Expand multiple games into distinct GameTrees and yield each.
 
	def listGames(self):
 
		for node in self._listGINodes():
 
			yield self._buildSubtree(node)
 
 
	def getNode(self,i):
 
		if 0<=i<len(self.nodes):
 
			return self.nodes[i]
 
		return None
 
 
	## Create and return a new game tree containing the provided Node.
 
	#
 
	# Ancestor nodes are copied, descendants are shared.
 
	def _buildSubtree(self,seedNode):
 
		node=seedNode.copy()
 
 
		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):
 
		for node in self.nodes:
 
			if node.isGINode():
 
				yield node
 
		for tree in self.branches:
 
			for node in tree._listGINodes():
 
				yield node
src/sgfParser/property.py
Show inline comments
 
@@ -40,97 +40,97 @@ def choose(*vTypes):
 
				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) and s[i]=="["
 

	
 

	
 
def singletonEnds(s,i):
 
	return i<len(s) and s[i]=="]"
 

	
 

	
 
def singleton(vType):
 
	def f(s,start):
 
		if not singletonFits(s,start):
 
			raise ParserError("expected a property value starting with '['",s,start)
 
		i,x=vType(s,start+1)
 
		if not singletonEnds(s,i):
 
			raise ParserError("expected a property value ending with ']'",s,i)
 
		i=skipWhitespace(s,i+1)
 
		return (i,x)
 
	return f
 

	
 

	
 
def listOf(vType,allowEmpty=False):
 
	def f(s,start):
 
		i=start
 
		if not singletonFits(s,i):
 
			raise ParserError("expected a property value starting with '['",s,i)
 
		if singletonEnds(s,i+1) and allowEmpty:
 
			i=skipWhitespace(s,i+2)
 
			return (i,[])
 
		single=singleton(vType)
 
		i,x=single(s,i)
 
		res=[x]
 
		while singletonFits(s,i):
 
			i,x=single(s,i)
 
			res.append(x)
 
		return (i,res)
 
	return f
 

	
 

	
 
def compose(vTypeA,vTypeB):
 
	def f(s,start):
 
		i,a=vTypeA(s,start)
 
		if i>=len(s) or s[i]!=":":
 
			raise ParserError("a composed property value separated by ':' expected",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):
 
	r=re.compile(r"(\+|-|)\d+")
 
	m=r.match(s,start)
 
	if m is None: raise ParserError("expected a number matching '(\+|-|)\d+'",s,start)
 
	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: raise ParserError("expected a real number matching '(\+|-|)\d+(\.\d+)?'",s,start)
 
	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: raise ParserError("expected a double value, either '1' or '2'",s,start)
 
	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: raise ParserError("expected a color value, either 'B' or 'W'",s,start)
 
	return (m.end(),m.group(0))
 

	
 

	
 
def text(simple=True,composed=False):
 
	def f(s,start):
 
		res=""
 
		esc=False
 
		lastC=""
 
		i=start
 
		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:
src/tests/testSgfParser.py
Show inline comments
 
from itertools import chain
 
import unittest
 
from unittest import TestCase
 
import os
 

	
 
from sgfParser import strRowCol
 
from sgfParser.collection import Collection
 
from sgfParser.property import Property
 

	
 

	
 
dataDir=os.path.join(os.path.dirname(__file__), "data")
 

	
 

	
 
class TestUtils(TestCase):
 
	def testTextPos(self):
 
		s="abc\ndef\rgh\r\nij\n\rklmn"
 
		rc=[
 
			[1,2,3,4],
 
			[1,2,3,4],
 
			[1,2,3,4],
 
			[1,2,3], [1], # don't care about LFCR, we unicode now
 
			[1,2,3,4]
 
		]
 
		res=chain((r+1,c) for (r,row) in enumerate(rc) for c in row)
 
		for (i,(r,c)) in zip(range(len(s)+1), res):
 
			self.assertEqual(strRowCol(s, i), (r, c))
 

	
 

	
 
class TestProperty(TestCase):
 
	def testName(self):
 
		with self.assertRaises(AssertionError):
 
			Property.create("[99]",0)
 
		with self.assertRaises(AssertionError):
 
			Property.create("99[99]",0)
 

	
 
		i,prop=Property.create("MN[99]",0)
 
		self.assertNotEqual((i,prop), (0,None))
 
		self.assertEqual((i,prop.name), (6,"MN"))
 

	
 

	
 
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)
 
		self.assertRegex(games[0].export(), r"^\(;B\[aa];(PB\[Some Black]|PW\[Some White]|W\[ab]){3};B\[ac]\)$")
 
		self.assertRegex(games[1].export(), r"^\(;B\[aa];(PB\[Other Black]|PW\[Other White]|W\[bb]){3}\)$")
 

	
 
	def testEmptySgf(self):
 
		Collection("(;)")
 

	
 
	def testSimpleSgf(self):
 
		with open(os.path.join(dataDir, "simple.sgf")) as f:
 
			Collection(f.read())
 

	
 
	def testComplexSgf(self):
 
		with open(os.path.join(dataDir, "kogos.sgf")) as f:
 
			Collection(f.read())
 

	
 
if __name__ == '__main__':
 
	unittest.main()
0 comments (0 inline, 0 general)