Files @ c6e20613189d
Branch filter:

Location: Diana/src/sgfParser/property.py - annotation

Laman
added location of the ParserError in the parsed file
b66f5379b832
0ee71f3564f4
b66f5379b832
b66f5379b832
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
a362783e3bec
a362783e3bec
a362783e3bec
a362783e3bec
a362783e3bec
a362783e3bec
a362783e3bec
a362783e3bec
a362783e3bec
a362783e3bec
a362783e3bec
a362783e3bec
a362783e3bec
a362783e3bec
a362783e3bec
a362783e3bec
a362783e3bec
a362783e3bec
a362783e3bec
a362783e3bec
a362783e3bec
a362783e3bec
a362783e3bec
0ee71f3564f4
0ee71f3564f4
0ee71f3564f4
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
0ee71f3564f4
0ee71f3564f4
0ee71f3564f4
0ee71f3564f4
0ee71f3564f4
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
0ee71f3564f4
0ee71f3564f4
0ee71f3564f4
0ee71f3564f4
0ee71f3564f4
0ee71f3564f4
0ee71f3564f4
0ee71f3564f4
cfcff53c74e6
cfcff53c74e6
0ee71f3564f4
0ee71f3564f4
cfcff53c74e6
0ee71f3564f4
0ee71f3564f4
0ee71f3564f4
0ee71f3564f4
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
0ee71f3564f4
0ee71f3564f4
0ee71f3564f4
0ee71f3564f4
0ee71f3564f4
0ee71f3564f4
cfcff53c74e6
0ee71f3564f4
0ee71f3564f4
0ee71f3564f4
0ee71f3564f4
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
0ee71f3564f4
c6e20613189d
cfcff53c74e6
a362783e3bec
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
0ee71f3564f4
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
0ee71f3564f4
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
0ee71f3564f4
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
0ee71f3564f4
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
0ee71f3564f4
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
2ed7f0dab5ef
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
0ee71f3564f4
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
a362783e3bec
a362783e3bec
a362783e3bec
cfcff53c74e6
cfcff53c74e6
cfcff53c74e6
b66f5379b832
b66f5379b832
2ed7f0dab5ef
0ee71f3564f4
0ee71f3564f4
b66f5379b832
2ed7f0dab5ef
2ed7f0dab5ef
b66f5379b832
b66f5379b832
0ee71f3564f4
0ee71f3564f4
0ee71f3564f4
0ee71f3564f4
2ed7f0dab5ef
0ee71f3564f4
2ed7f0dab5ef
0ee71f3564f4
0ee71f3564f4
2ed7f0dab5ef
2ed7f0dab5ef
0ee71f3564f4
2ed7f0dab5ef
2ed7f0dab5ef
2ed7f0dab5ef
2ed7f0dab5ef
0ee71f3564f4
0ee71f3564f4
2ed7f0dab5ef
2ed7f0dab5ef
2ed7f0dab5ef
2ed7f0dab5ef
2ed7f0dab5ef
2ed7f0dab5ef
b66f5379b832
0ee71f3564f4
0ee71f3564f4
0ee71f3564f4
b66f5379b832
2ed7f0dab5ef
2ed7f0dab5ef
2ed7f0dab5ef
2ed7f0dab5ef
2ed7f0dab5ef
2ed7f0dab5ef
2ed7f0dab5ef
2ed7f0dab5ef
2ed7f0dab5ef
2ed7f0dab5ef
2ed7f0dab5ef
2ed7f0dab5ef
a362783e3bec
a362783e3bec
a362783e3bec
a362783e3bec
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
b66f5379b832
cfcff53c74e6
2ed7f0dab5ef
import re
from . import skipWhitespace, ParserError


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)


## Metatype matching one of the provided types.
#
# Returns the first match, so the order is important.
def choose(*vTypes):
	def f(s,start):
		for vType in vTypes:
			try:
				i,x=vType(s,start)
				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("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:
				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: raise ParserError("expected a point value matching '[a-zA-Z]{2}|'",s,start)
	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:
	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)
		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)(s,start), 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