Files @ 686166c7d5bc
Branch filter:

Location: Diana/src/diana/sgfParser/propValues.py

Laman
added more whitespace
import re

from . import ParserError, skipWhitespace


class Regexp:
	number = re.compile(r"(\+|-|)\d+")
	real = re.compile(r"(\+|-|)\d+(\.\d+)?")
	point = re.compile(r"[a-zA-Z]{2}|")
	text = re.compile(r"(?:.*?[^\\])??(?:\\\\)*(?=])", re.DOTALL)
	composedText = re.compile(r"(?:.*?[^\\])??(?:\\\\)*(?=]|:)", re.DOTALL)

	class Text:
		softBreaks = re.compile(r"(^|[^\\])((\\\\)*)\\((\n\r)|(\r\n)|\r|\n)")
		whitespace = re.compile(r"[\t\f\v]")
		simpleWhitespace = re.compile(r"[\t\f\v\n\r]")
		removeSlashes = re.compile(r"(^|[^\\])((\\\\)*)\\($|[^\\])")
		unescapeSlashes = re.compile(r"\\\\")


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):
	m = Regexp.number.match(s, start)
	if m is None:
		raise ParserError("expected a number matching '{0}'".format(Regexp.number.pattern), s, start)
	res = int(m.group(0))
	return (m.end(), res)


def real(s, start):
	m = Regexp.real.match(s, start)
	if m is None:
		raise ParserError("expected a real number matching '{0}'".format(Regexp.real.pattern), s, start)
	res = float(m.group(0))
	return (m.end(), res)


def double(s, start):
	c = s[start]
	if c not in ("1", "2"):
		raise ParserError("expected a double value, either '1' or '2'", s, start)
	return (start+1, c)


def color(s,start):
	c = s[start]
	if c not in ("B", "W"):
		raise ParserError("expected a color value, either 'B' or 'W'", s, start)
	return (start+1, c)


def text(simple=True, composed=False):
	def f(s, start):
		regexps = Regexp.Text
		m = Regexp.composedText.match(s, start) if composed else Regexp.text.match(s, start)
		res = m.group(0)
		res = regexps.softBreaks.sub(r"\1\2", res)  # remove soft line breaks
		if simple:
			res = regexps.simpleWhitespace.sub(" ", res)  # convert whitespace to spaces, no escapes
		else:
			res = regexps.whitespace.sub(" ", res)  # convert whitespace to spaces, no escapes
		res = regexps.removeSlashes.sub(r"\1\2\4", res)
		res = regexps.unescapeSlashes.sub(r"\\", res)  # unescape slashes

		return (m.end(), res)
	return f


def empty(s, start):
	return (start, "")


def anything(s, start):
	esc = False
	i = start
	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):
	m = Regexp.point.match(s, start)  # !! limit to board size
	if m is None:
		raise ParserError("expected a point value matching '{0}'".format(Regexp.point.pattern), 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