Files @ afabea7d0e61
Branch filter:

Location: Diana/src/diana/sgfparser/property.py

Laman
renamed sgfparser, game_record, prop_values
import re
from datetime import date
import logging as log

from .prop_values import choose, singleton, list_of, compose, number, real, double, color, text, empty, anything, point, move, stone
from . import skip_whitespace, ParserError

GAME_INFO = 1
UNKNOWN = 99


class DateException(Exception):
	pass


class Property:
	ident_regexp = 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 = skip_whitespace(s, i)
		try:
			(i, x) = Property.create_value(s, i, res.name)
		except ParserError as e:  # a malformed value
			log.warning(e)
			(i, x) = choose(list_of(anything), singleton(anything))(s, i)
			res.name = "_"+res.name
		res.value = x
		if res.name == "DT":
			res = DateProperty(x)
		i = skip_whitespace(s, i)
		return (i, res)

	@staticmethod
	def ident(s, start):
		m = Property.ident_regexp.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 create_value(s, start, name):
		if name in Property.patterns:
			return Property.patterns[name](s, start)
		else:
			log.info("unknown property %s at position %d", name, start)
			return choose(list_of(anything), singleton(anything))(s, start)

	@property
	def type(self):
		game_info = {"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 game_info:
			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):
		name = self.name.lstrip("_")
		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(name, val)

	patterns = {
		"B": singleton(move),
		"KO": singleton(empty),
		"MN": singleton(number),
		"W": singleton(move),
		"AB": list_of(stone),  #
		"AE": list_of(point),  #
		"AW": list_of(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": list_of(compose(point, point)),  #
		"CR": list_of(point),  #
		"DD": list_of(point, allow_empty=True),  #
		"LB": list_of(compose(point, text())),  #
		"LN": list_of(compose(point, point)),  #
		"MA": list_of(point),  #
		"SL": list_of(point),  #
		"SQ": list_of(point),  #
		"TR": list_of(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": list_of(point, allow_empty=True),  #

		# go specific
		"HA": singleton(number),
		"KM": singleton(real),
		"TB": list_of(point, allow_empty=True),
		"TW": list_of(point, allow_empty=True)
	}


class DateProperty(Property):
	def __init__(self, value):
		super().__init__()
		self.name = "DT"
		self.value = []
		self.raw_value = value
		self.parse(value)

	def parse(self, s):
		regexp = re.compile(r"\d{4}(-\d\d){0,2}(,(\d{4}(-\d\d){0,2}|\d\d(-\d\d)?))*")
		match = re.search(regexp, s)
		if not match:
			raise DateException('Could not parse a DT value: "{0}"'.format(s))
		substr = match.group(0)
		date_strs = substr.split(",")
		dates = []
		prev_format = None

		for s in date_strs:
			try:
				(prev_format, d) = DateProperty.parse_single(s, prev_format, dates[-1] if dates else None)
			except ValueError:
				raise DateException('Could not parse a DT value: "{0}"'.format(s))
			dates.append(d)
		self.value = dates

	@staticmethod
	def parse_single(date_str, prev_format, prev=None):
		tokens = date_str.split("-")
		num_tokens = list(map(int, tokens))
		if len(tokens) == 3:
			return ("YMD", date(*num_tokens))
		elif len(tokens) == 2:
			if len(tokens[0]) == 4:
				return ("YM", date(*num_tokens, 1))
			else:
				return ("MD", date(prev.year, *num_tokens))
		else:
			if len(tokens[0]) == 4:
				return ("Y", date(*num_tokens, 1, 1))
			elif prev_format in ("YM", "M"):
				return ("M", date(prev.year, *num_tokens, 1))
			else:
				return ("D", date(prev.year, prev.month, *num_tokens))