Skip to content

Commit

Permalink
Merge pull request #21 from eric-s-s/parser_fix
Browse files Browse the repository at this point in the history
Parser fix
  • Loading branch information
eric-s-s committed Aug 28, 2017
2 parents 18459af + a0e90da commit 46c2708
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 211 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@


#################
dicetables v2.4.0
dicetables v2.4.1
#################

Calculate the Combinations For Any Set of Dice
Expand Down
2 changes: 1 addition & 1 deletion dicetables/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from dicetables.dieevents import Die, ModDie, WeightedDie, ModWeightedDie, StrongDie, Modifier, ExplodingOn, Exploding
from dicetables.dicerecord import DiceRecord
from dicetables.eventsbases.eventerrors import DiceRecordError, InvalidEventsError
from dicetables.parser import ParseError, Parser
from dicetables.parser import Parser, ParseError, LimitsError
from dicetables.eventsinfo import (EventsCalculations, EventsInformation,
events_range, mean, stddev, percentage_points, percentage_axes, stats,
full_table_string)
74 changes: 42 additions & 32 deletions dicetables/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ def __init__(self, *args):
super(ParseError, self).__init__(*args)


class LimitsError(ValueError):
def __init__(self, *args):
super(LimitsError, self).__init__(*args)


class Parser(object):
def __init__(self, ignore_case=False, disable_kwargs=False, max_size=500, max_explosions=10, max_nested_dice=5):
"""
Expand All @@ -32,8 +37,8 @@ def __init__(self, ignore_case=False, disable_kwargs=False, max_size=500, max_ex
StrongDie: ('input_die', 'multiplier'), Exploding: ('input_die', 'explosions'),
ExplodingOn: ('input_die', 'explodes_on', 'explosions')}

self._size_limit_kwargs = ['die_size', 'dictionary_input']
self._explosions_limit_kwargs = ['explosions', 'explodes_on']
self._limits_values = {'size': [('die_size', None), ('dictionary_input', None)],
'explosions': [('explosions', 2), ('explodes_on', None)]}

self.ignore_case = ignore_case
self.disable_kwargs = disable_kwargs
Expand All @@ -58,8 +63,7 @@ def kwargs(self):

@property
def limits_kwargs(self):
return {'explosions_limits': self._explosions_limit_kwargs[:],
'die_size_limits': self._size_limit_kwargs[:]}
return self._limits_values.copy()

def parse_die(self, die_string):
ast_call_node = ast.parse(die_string).body[0].value
Expand All @@ -76,7 +80,8 @@ def parse_die_within_limits(self, die_string):
- explodes_on
If your die classes use different kwargs to describe size or number of explosions, they will
be parsed as if there were no limits.
be parsed as if there were no limits. You may register those kwargs (and any default value) with
:code:`add_die_size_limit_kwarg` and :code:`add_explosions_limit_kwarg`.
"""
ast_call_node = ast.parse(die_string).body[0].value

Expand Down Expand Up @@ -170,22 +175,34 @@ def _get_kwarg_value(self, die_class, kwarg_node):
def _check_limits(self, die_class, die_params, die_kwargs):
self._check_nested_calls()

class_kwargs = self._kwargs[die_class]
size_params = [find_value(key_word, class_kwargs, die_params, die_kwargs)
for key_word in self._size_limit_kwargs]
explosions_params = [find_value(key_word, class_kwargs, die_params, die_kwargs)
for key_word in self._explosions_limit_kwargs]
size_params = self._get_limits_params('size', die_class, die_params, die_kwargs)
explosions_params = self._get_limits_params('explosions', die_class, die_params, die_kwargs)

self._check_die_size(size_params)
self._check_explosions(explosions_params)

def _check_nested_calls(self):
if self._nested_dice_counter > self.max_nested_dice:
msg = 'LIMITS EXCEEDED. Max number of nested dice: {}'.format(self.max_nested_dice)
raise ParseError(msg)
msg = 'Max number of nested dice: {}'.format(self.max_nested_dice)
raise LimitsError(msg)

def _get_limits_params(self, param_types, die_class, die_params, die_kwargs):
limits_kw_default = self._limits_values[param_types]
class_kwargs = self._kwargs[die_class]
answer = []
for kwarg_name, default in limits_kw_default:
if kwarg_name not in class_kwargs:
continue

index = class_kwargs.index(kwarg_name)
if len(die_params) > index:
answer.append(die_params[index])
else:
answer.append(die_kwargs.get(kwarg_name, default))
return answer

def _check_die_size(self, die_size_params):
msg = 'LIMITS EXCEEDED. Max die_size: {}'.format(self.max_size)
msg = 'Max die_size: {}'.format(self.max_size)
for param in die_size_params:
if param is None:
continue
Expand All @@ -199,10 +216,10 @@ def _check_die_size(self, die_size_params):
raise ValueError(msg)

if size > self.max_size:
raise ParseError(msg)
raise LimitsError(msg)

def _check_explosions(self, explosions_params):
msg = 'LIMITS EXCEEDED. Max number of explosions + len(explodes_on): {}'.format(self.max_explosions)
msg = 'Max number of explosions + len(explodes_on): {}'.format(self.max_explosions)
explosions = 0
for param in explosions_params:
if param is None:
Expand All @@ -216,8 +233,8 @@ def _check_explosions(self, explosions_params):
msg = 'A kwarg declared as an "explosions limit" is neither an int nor a tuple/list of ints.'
raise ValueError(msg)

if explosions > self.max_explosions:
raise ParseError(msg)
if explosions > self.max_explosions:
raise LimitsError(msg)

def add_class(self, class_, param_identifiers, auto_detect_kwargs=True, kwargs=()):
"""
Expand All @@ -235,11 +252,15 @@ def add_class(self, class_, param_identifiers, auto_detect_kwargs=True, kwargs=(
def add_param_type(self, param_type, creation_method):
self._param_types[param_type] = creation_method

def add_die_size_limit_kwarg(self, new_key_word):
self._size_limit_kwargs.append(new_key_word)
def add_die_size_limit_kwarg(self, new_key_word, default=None):
"""If there is a default value and you do not add it or you add the incorrect one,
`parse_within_limits` will fail."""
self._limits_values['size'].append((new_key_word, default))

def add_explosions_limit_kwarg(self, new_key_word):
self._explosions_limit_kwargs.append(new_key_word)
def add_explosions_limit_kwarg(self, new_key_word, default=None):
"""If there is a default value and you do not add it or you add the incorrect one,
`parse_within_limits` will fail."""
self._limits_values['explosions'].append((new_key_word, default))


def make_int_dict(dict_node):
Expand Down Expand Up @@ -268,14 +289,3 @@ def _get_kwargs_from_init(class_):
return kwargs[1:]
except AttributeError:
raise AttributeError('could not find the code for __init__ function at class_.__init__.__code__')


def find_value(key_word, class_kwargs, die_params, die_kwargs):
if key_word not in class_kwargs:
return None

index = list(class_kwargs).index(key_word)
if len(die_params) > index:
return die_params[index]
else:
return die_kwargs[key_word]
33 changes: 23 additions & 10 deletions docs/implementation_details/parser.rst
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,15 @@ Die(500)
>>> dt.Parser().parse_die_within_limits('Die(501)')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ParseError: LIMITS EXCEEDED. Max die_size: 500
LimitsError: Max die_size: 500
>>> stupid = 'StrongDie(' * 20 + 'Die(5)' + ', 2)' * 20
>>> dt.Parser().parse_die_within_limits(stupid)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
LimitsError: Max number of nested dice: 5

>>> class NewDie(dt.Die):
... def __init__(self, funky_new_die_size):
... def __init__(self, funky_new_die_size=6):
... super(NewDie, self).__init__(funky_new_die_size)
...
... def __repr__(self):
Expand All @@ -209,23 +214,31 @@ NewDie(5000)
>>> parser.parse_die_within_limits('Die(5000)')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ParseError: LIMITS EXCEEDED. Max die_size: 500
LimitsError: Max die_size: 500

You can add your new and exciting key-words to the parser with :meth:`Parser.add_die_size_limit_kwarg` and
:meth:`Parser.add_explosions_limit_kwarg`.
:meth:`Parser.add_explosions_limit_kwarg`. If this has a default value, you can add that too.

>>> new_parser = dt.Parser()
>>> new_parser.add_class(NewDie, ('int',))
>>> new_parser.add_die_size_limit_kwarg('funky_new_die_size')
>>> new_parser.add_die_size_limit_kwarg('funky_new_die_size', default=6)

>>> new_parser.parse_die_within_limits('NewDie(5000)')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ParseError: LIMITS EXCEEDED. Max die_size: 500
LimitsError: Max die_size: 500
>>> new_parser.parse_die_within_limits('Die(5000)')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ParseError: LIMITS EXCEEDED. Max die_size: 500
LimitsError: Max die_size: 500

>>> new_parser.parse_die_within_limits('NewDie()')
NewDie(6)
>>> new_parser.max_size = 5
>>> new_parser.parse_die_within_limits('NewDie()')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
LimitsError: Max die_size: 5

The parser only knows how to evaluate size based on a parameter that represents size as an `int` or dictionary of
`{int: int}` where the size is the highest key value. Similarly, the parser assumes that it can count the explosions
Expand Down Expand Up @@ -259,7 +272,7 @@ and **a** solution
... other_params = [param for param in die_size_params if not isinstance(param, str)]
... for number_str in string_params:
... if int(number_str) > self.max_size:
... raise dt.ParseError('Dude! NOT cool!')
... raise dt.LimitsError('Dude! NOT cool!')
... super(NewParser, self)._check_die_size(other_params)
...
>>> parser = NewParser()
Expand All @@ -270,8 +283,8 @@ and **a** solution
>>> parser.parse_die_within_limits('NewDie("5000")')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ParseError: LIMITS EXCEEDED. Max die_size: 500
LimitsError: Dude! NOT cool!
>>> parser.parse_die_within_limits('Die(5000)')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ParseError: LIMITS EXCEEDED. Max die_size: 500
LimitsError: Max die_size: 500
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def readme():
return f.read()

setup(name='dicetables',
version='2.4.0',
version='2.4.1',
description='get all combinations for any set of dice',
long_description=readme(),
keywords='dice, die, statistics, table, probability, combinations',
Expand Down

0 comments on commit 46c2708

Please sign in to comment.