| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371 |
- //
- // SA_DiceEvaluator.m
- //
- // Copyright (c) 2016 Said Achmiz.
- //
- // This software is licensed under the MIT license.
- // See the file "LICENSE" for more information.
-
- #import "SA_DiceEvaluator.h"
-
- #import "SA_DiceBag.h"
- #import "SA_DiceParser.h"
- #import "SA_DiceExpressionStringConstants.h"
- #import "SA_DiceErrorHandling.h"
-
- /**************************/
- #pragma mark Defined values
- /**************************/
-
- #define DEFAULT_MAX_DIE_COUNT 1000 // One thousand
- #define DEFAULT_MAX_DIE_SIZE 10000 // Ten thousand
-
- /***************************************************/
- #pragma mark - SA_DiceEvaluator class implementation
- /***************************************************/
-
- @implementation SA_DiceEvaluator
- {
- NSInteger _maxDieCount;
- NSInteger _maxDieSize;
- }
-
- /************************/
- #pragma mark - Properties
- /************************/
-
- - (NSInteger)maxDieCount
- {
- return _maxDieCount;
- }
- - (void)setMaxDieCount:(NSInteger)maxDieCount
- {
- if(maxDieCount < 1)
- {
- _maxDieCount = 1;
- }
- else if(maxDieCount > NSIntegerMax / _maxDieSize)
- {
- _maxDieCount = NSIntegerMax / _maxDieSize;
- }
- else
- {
- _maxDieCount = maxDieCount;
- }
- }
-
- - (NSInteger)maxDieSize
- {
- return _maxDieSize;
- }
- - (void)setMaxDieSize:(NSInteger)maxDieSize
- {
- if(maxDieSize < 1)
- {
- _maxDieSize = 1;
- }
- else if(maxDieSize > [self.diceBag biggestPossibleDieSize] || maxDieSize > NSIntegerMax / _maxDieCount)
- {
- _maxDieSize = ([self.diceBag biggestPossibleDieSize] < (NSIntegerMax / _maxDieCount)) ? [_diceBag biggestPossibleDieSize] : NSIntegerMax / _maxDieCount;
- }
- else
- {
- _maxDieSize = maxDieSize;
- }
- }
-
- /**************************/
- #pragma mark - Initializers
- /**************************/
-
- - (instancetype)init
- {
- if(self = [super init])
- {
- _maxDieCount = DEFAULT_MAX_DIE_COUNT;
- _maxDieSize = DEFAULT_MAX_DIE_SIZE;
-
- self.diceBag = [[SA_DiceBag alloc] init];
- }
- return self;
- }
-
- /****************************/
- #pragma mark - Public methods
- /****************************/
-
- - (NSMutableDictionary *)resultOfExpression:(NSDictionary *)expression
- {
- // Check to see if the expression is erroneous (i.e. the parser has judged
- // that it is malformed, etc.). If so, decline to evaluate the expression;
- // return (a mutable copy of) it, unchanged.
- if(getErrorsForExpression(expression) != nil)
- {
- return [expression mutableCopy];
- }
-
- /*
- Even if an expression is not erroneous (i.e. if it has no syntax errors),
- it may still not be possible to evaluate it. For example, '5d0' is a
- perfectly well-formed die string, and will yield an expression tree as follows:
-
- @{SA_DB_TERM_TYPE : SA_DB_TERM_TYPE_ROLL_COMMAND,
- SA_DB_ROLL_COMMAND : SA_DB_ROLL_COMMAND_SUM,
- SA_DB_ROLL_DIE_COUNT : @{SA_DB_TERM_TYPE : SA_DB_TERM_TYPE_VALUE,
- SA_DB_VALUE : @(5)
- },
- SA_DB_ROLL_DIE_SIZE : @{SA_DB_TERM_TYPE : SA_DB_TERM_TYPE_VALUE,
- SA_DB_VALUE : @(0)
- }
- }
-
- This is, of course, an illegal expression; we can't roll a die of size 0
- (a die with zero sides?).
-
- If we encounter such an illegal expression, we add an appropriate error to
- the term. We are not required to set a value for the SA_DB_RESULT key in
- such a case.
- */
-
- // Check to see if the current term is an operation.
- if([expression[SA_DB_TERM_TYPE] isEqualToString:SA_DB_TERM_TYPE_OPERATION])
- {
- return [self resultOfExpressionDescribingOperation:expression];
- }
- // Check to see if the current term is a roll command.
- else if([expression[SA_DB_TERM_TYPE] isEqualToString:SA_DB_TERM_TYPE_ROLL_COMMAND])
- {
- return [self resultOfExpressionDescribingRollCommand:expression];
- }
- // If not an operation or a roll command, the current term can only be a
- // simple value expression (term type of SA_DB_TERM_TYPE_VALUE).
- else
- {
- return [self resultOfExpressionDescribingValue:expression];
- }
- }
-
- /****************************/
- #pragma mark - Helper methods
- /****************************/
-
- - (NSMutableDictionary *)resultOfExpressionDescribingValue:(NSDictionary *)expression
- {
- NSMutableDictionary *result = [expression mutableCopy];
-
- result[SA_DB_RESULT] = result[SA_DB_VALUE];
-
- return result;
- }
-
- - (NSMutableDictionary *)resultOfExpressionDescribingRollCommand:(NSDictionary *)expression
- {
- NSMutableDictionary *result = [expression mutableCopy];
-
- // For now, only a simple sum is supported. Other sorts of roll commands
- // may be added later.
- if([result[SA_DB_ROLL_COMMAND] isEqualToString:SA_DB_ROLL_COMMAND_SUM])
- {
- // First, recursively evaluate the expressions that represent the
- // die size and die count.
- result[SA_DB_ROLL_DIE_COUNT] = [self resultOfExpression:result[SA_DB_ROLL_DIE_COUNT]];
- result[SA_DB_ROLL_DIE_SIZE] = [self resultOfExpression:result[SA_DB_ROLL_DIE_SIZE]];
-
- // Evaluating those expressions may have generated an error(s);
- // propagate any errors up to the current term.
- addErrorsFromExpressionToExpression(result[SA_DB_ROLL_DIE_COUNT], result);
- addErrorsFromExpressionToExpression(result[SA_DB_ROLL_DIE_SIZE], result);
-
- // If indeed we've turned up errors, return.
- if(getErrorsForExpression(result) != nil)
- {
- return result;
- }
-
- // Evaluating the two sub-expressions didn't generate errors; this means
- // that we have successfully generated values for both the die count and
- // the die size...
- NSInteger dieCount = [result[SA_DB_ROLL_DIE_COUNT][SA_DB_RESULT] integerValue];
- NSInteger dieSize = [result[SA_DB_ROLL_DIE_SIZE][SA_DB_RESULT] integerValue];
-
- // ... but, the resulting values of the expressions may make it
- // impossible to evaluate the roll command. Check to see whether the die
- // count and die size have legal values.
- if(dieCount < 0)
- {
- addErrorToExpression(SA_DB_ERROR_DIE_COUNT_NEGATIVE, result);
- }
- else if(dieCount > _maxDieCount)
- {
- addErrorToExpression(SA_DB_ERROR_DIE_COUNT_EXCESSIVE, result);
- }
-
- if(dieSize < 1)
- {
- addErrorToExpression(SA_DB_ERROR_DIE_SIZE_INVALID, result);
- }
- else if(dieSize > _maxDieSize)
- {
- addErrorToExpression(SA_DB_ERROR_DIE_SIZE_EXCESSIVE, result);
- }
-
- // If indeed the die count or die size fall outside of their allowed
- // ranges, return.
- if(getErrorsForExpression(result) != nil)
- {
- return result;
- }
-
- // The die count and die size have legal values. We can safely roll the
- // requisite number of dice, and take the sum of the rolls (if needed).
- // NOTE: _maxDieSize is gauranteed to be no greater than the largest die
- // size that the SA_DiceBag can roll (this is enforced by the setter
- // method for the maxDieSize property), so we need not check to see
- // if the return value of rollDie: or rollNumber:ofDice: is valid.
- // We are also gauranteed that the product of _maxDieCount and
- // _maxDieSize is no greater than the largest unsigned value that can be
- // stored by whatever numeric type we specify simple value terms (terms
- // of type SA_DB_TERM_TYPE_VALUE) to contain (likewise enforced by the
- // setters for both maxDieSize and maxDieCount), therefore we need not
- // worry about overflow here.
- if(dieCount == 0)
- {
- result[SA_DB_RESULT] = @(0);
- result[SA_DB_ROLLS] = @[];
- }
- else if(dieCount == 1)
- {
- NSNumber *roll = @([self.diceBag rollDie:dieSize]);
-
- result[SA_DB_RESULT] = roll;
- result[SA_DB_ROLLS] = @[roll];
- }
- else
- {
- NSArray *rolls = [self.diceBag rollNumber:@(dieCount) ofDice:dieSize];
-
- result[SA_DB_RESULT] = [rolls valueForKeyPath:@"@sum.self"];
- result[SA_DB_ROLLS] = rolls;
- }
-
- // Return the successfully evaluated roll command expression.
- return result;
- }
- else
- {
- addErrorToExpression(SA_DB_ERROR_UNKNOWN_ROLL_COMMAND, result);
-
- return result;
- }
- }
-
- - (NSMutableDictionary *)resultOfExpressionDescribingOperation:(NSDictionary *)expression
- {
- NSMutableDictionary *result = [expression mutableCopy];
-
- // First, recursively evaluate the expressions that represent the
- // left-hand-side and right-hand-side operands.
- result[SA_DB_OPERAND_LEFT] = [self resultOfExpression:result[SA_DB_OPERAND_LEFT]];
- result[SA_DB_OPERAND_RIGHT] = [self resultOfExpression:result[SA_DB_OPERAND_RIGHT]];
-
- // Evaluating the operand may have generated an error(s); propagate any
- // errors up to the current term.
- addErrorsFromExpressionToExpression(result[SA_DB_OPERAND_LEFT], result);
- addErrorsFromExpressionToExpression(result[SA_DB_OPERAND_RIGHT], result);
-
- // If indeed we've turned up errors, return.
- if(getErrorsForExpression(result) != nil)
- {
- return result;
- }
-
- // Evaluating the operands didn't generate any errors. We have valid
- // operands.
- NSInteger leftOperand = [result[SA_DB_OPERAND_LEFT][SA_DB_RESULT] integerValue];
- NSInteger rightOperand = [result[SA_DB_OPERAND_RIGHT][SA_DB_RESULT] integerValue];
-
- // Check to see if the operation is subtraction.
- if([result[SA_DB_OPERATOR] isEqualToString:SA_DB_OPERATOR_MINUS])
- {
- // First, we check for possible overflow...
- if(leftOperand > 0 && rightOperand < 0 && NSIntegerMax + rightOperand < leftOperand)
- {
- addErrorToExpression(SA_DB_ERROR_INTEGER_OVERFLOW_SUBTRACTION, result);
-
- return result;
- }
- else if(leftOperand < 0 && rightOperand > 0 && NSIntegerMin + rightOperand > leftOperand)
- {
- addErrorToExpression(SA_DB_ERROR_INTEGER_UNDERFLOW_SUBTRACTION, result);
-
- return result;
- }
-
- // No overflow will occur. We can perform the subtraction operation.
- result[SA_DB_RESULT] = @(leftOperand - rightOperand);
-
- // Return the successfully evaluated subtraction expression.
- return result;
- }
- // Check to see if the operation is addition.
- else if([result[SA_DB_OPERATOR] isEqualToString:SA_DB_OPERATOR_PLUS])
- {
- // First, we check for possible overflow...
- if(rightOperand > 0 && leftOperand > 0 && NSIntegerMax - rightOperand < leftOperand)
- {
- addErrorToExpression(SA_DB_ERROR_INTEGER_OVERFLOW_ADDITION, result);
-
- return result;
- }
- else if(rightOperand < 0 && leftOperand < 0 && NSIntegerMin - rightOperand > leftOperand)
- {
- addErrorToExpression(SA_DB_ERROR_INTEGER_UNDERFLOW_ADDITION, result);
-
- return result;
- }
-
- // No overflow will occur. We can perform the addition operation.
- result[SA_DB_RESULT] = @(leftOperand + rightOperand);
-
- // Return the successfully evaluated addition expression.
- return result;
- }
- // Check to see if the operation is multiplication.
- else if([result[SA_DB_OPERATOR] isEqualToString:SA_DB_OPERATOR_TIMES])
- {
- // First, we check for possible overflow...
- if( ( leftOperand == NSIntegerMin && ( rightOperand != 0 || rightOperand != 1 ) ) ||
- ( rightOperand == NSIntegerMin && ( leftOperand != 0 || leftOperand != 1 ) ) ||
- ( leftOperand != 0 && ( (NSIntegerMax / ABS(leftOperand)) < rightOperand ) ) )
- {
- if((leftOperand > 0 && rightOperand > 0) || (leftOperand < 0 && rightOperand < 0))
- {
- addErrorToExpression(SA_DB_ERROR_INTEGER_OVERFLOW_MULTIPLICATION, result);
- }
- else
- {
- addErrorToExpression(SA_DB_ERROR_INTEGER_UNDERFLOW_MULTIPLICATION, result);
- }
-
- return result;
- }
-
- // No overflow will occur. We can perform the multiplication operation.
- result[SA_DB_RESULT] = @(leftOperand * rightOperand);
-
- // Return the successfully evaluated multiplication expression.
- return result;
- }
- // The operation is not one of the supported operators.
- else
- {
- // We add the appropriate error. We do not set a value for the
- // SA_DB_RESULT key.
-
- addErrorToExpression(SA_DB_ERROR_UNKNOWN_OPERATOR, result);
-
- return result;
- }
- }
-
- @end
|