| return _maxDieCount; | return _maxDieCount; | ||||
| } | } | ||||
| -(void) setMaxDieCount:(NSUInteger)maxDieCount { | -(void) setMaxDieCount:(NSUInteger)maxDieCount { | ||||
| if (maxDieCount > NSUIntegerMax / _maxDieSize) { | |||||
| if (maxDieCount > (NSUIntegerMax / _maxDieSize)) { | |||||
| _maxDieCount = NSUIntegerMax / _maxDieSize; | _maxDieCount = NSUIntegerMax / _maxDieSize; | ||||
| } else { | } else { | ||||
| _maxDieCount = maxDieCount; | _maxDieCount = maxDieCount; | ||||
| return _maxDieSize; | return _maxDieSize; | ||||
| } | } | ||||
| -(void) setMaxDieSize:(NSUInteger)maxDieSize { | -(void) setMaxDieSize:(NSUInteger)maxDieSize { | ||||
| if (maxDieSize > [_diceBag biggestPossibleDieSize] || | |||||
| maxDieSize > NSIntegerMax / _maxDieCount) { | |||||
| _maxDieSize = ([_diceBag biggestPossibleDieSize] < (NSIntegerMax / _maxDieCount)) ? [_diceBag biggestPossibleDieSize] : NSIntegerMax / _maxDieCount; | |||||
| if ( maxDieSize > [_diceBag biggestPossibleDieSize] | |||||
| || maxDieSize > (NSIntegerMax / _maxDieCount)) { | |||||
| _maxDieSize = (([_diceBag biggestPossibleDieSize] < (NSIntegerMax / _maxDieCount)) | |||||
| ? [_diceBag biggestPossibleDieSize] | |||||
| : (NSIntegerMax / _maxDieCount)); | |||||
| } else { | } else { | ||||
| _maxDieSize = maxDieSize; | _maxDieSize = maxDieSize; | ||||
| } | } | ||||
| /**************************/ | /**************************/ | ||||
| -(instancetype) init { | -(instancetype) init { | ||||
| if (self = [super init]) { | |||||
| _maxDieCount = DEFAULT_MAX_DIE_COUNT; | |||||
| _maxDieSize = DEFAULT_MAX_DIE_SIZE; | |||||
| _diceBag = [SA_DiceBag new]; | |||||
| } | |||||
| if (!(self = [super init])) | |||||
| return nil; | |||||
| _maxDieCount = DEFAULT_MAX_DIE_COUNT; | |||||
| _maxDieSize = DEFAULT_MAX_DIE_SIZE; | |||||
| _diceBag = [SA_DiceBag new]; | |||||
| return self; | return self; | ||||
| } | } | ||||
| #pragma mark - Public methods | #pragma mark - Public methods | ||||
| /****************************/ | /****************************/ | ||||
| // TODO: Possibly refuse to evaluate an expression that’s already evaluated? | |||||
| // (i.e., it has a ... .result?? .value??) | |||||
| -(SA_DiceExpression *) resultOfExpression:(SA_DiceExpression *)expression { | -(SA_DiceExpression *) resultOfExpression:(SA_DiceExpression *)expression { | ||||
| // Check to see if the expression is erroneous (i.e. the parser has judged | // 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; | // that it is malformed, etc.). If so, decline to evaluate the expression; | ||||
| } | } | ||||
| /* | /* | ||||
| 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: | |||||
| NOTE: 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) | |||||
| } | |||||
| } | |||||
| [ type : SA_DiceExpressionTerm_ROLL_COMMAND, | |||||
| rollCommand : SA_DiceExpressionRollCommand_SUM, | |||||
| dieCount : [ type : SA_DiceExpressionTerm_VALUE, | |||||
| value : @(5) | |||||
| ], | |||||
| dieSize : [ type : SA_DiceExpressionTerm_VALUE, | |||||
| value : @(0) | |||||
| ] | |||||
| ] | |||||
| This is, of course, an illegal expression; we can’t roll a die of size 0 | This is, of course, an illegal expression; we can’t roll a die of size 0 | ||||
| (a die with zero sides?). | (a die with zero sides?). | ||||
| If we encounter such an illegal expression, we add an appropriate error to | 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. | |||||
| the -[errorBitMask]. We are not required to set a value (-[value] property) | |||||
| in such a case. | |||||
| */ | */ | ||||
| switch (expression.type) { | switch (expression.type) { | ||||
| SA_DiceExpression *result = [expression copy]; | SA_DiceExpression *result = [expression copy]; | ||||
| // For now, only sum and exploding sum (i.e., sum but with exploding dice) | // For now, only sum and exploding sum (i.e., sum but with exploding dice) | ||||
| // are supported Other sorts of roll commands may be added later. | |||||
| // are supported. Other sorts of roll commands may be added later. | |||||
| switch (result.rollCommand) { | switch (result.rollCommand) { | ||||
| case SA_DiceExpressionRollCommand_SUM: | case SA_DiceExpressionRollCommand_SUM: | ||||
| case SA_DiceExpressionRollCommand_SUM_EXPLODING: { | case SA_DiceExpressionRollCommand_SUM_EXPLODING: { | ||||
| // If the left-hand operand is not a roll-and-sum, then the KEEP | // If the left-hand operand is not a roll-and-sum, then the KEEP | ||||
| // modifier cannot be applied to it. In that case, we add an error | // modifier cannot be applied to it. In that case, we add an error | ||||
| // and return the result without evaluating. | // and return the result without evaluating. | ||||
| if (result.leftOperand.type != SA_DiceExpressionTerm_ROLL_COMMAND || | |||||
| (result.leftOperand.rollCommand != SA_DiceExpressionRollCommand_SUM && | |||||
| result.leftOperand.rollCommand != SA_DiceExpressionRollCommand_SUM_EXPLODING)) { | |||||
| if ( result.leftOperand.type != SA_DiceExpressionTerm_ROLL_COMMAND | |||||
| || ( result.leftOperand.rollCommand != SA_DiceExpressionRollCommand_SUM | |||||
| && result.leftOperand.rollCommand != SA_DiceExpressionRollCommand_SUM_EXPLODING) | |||||
| ) { | |||||
| result.errorBitMask |= SA_DiceExpressionError_ROLL_MODIFIER_INAPPLICABLE; | result.errorBitMask |= SA_DiceExpressionError_ROLL_MODIFIER_INAPPLICABLE; | ||||
| return result; | return result; | ||||
| } | } | ||||
| switch (result.operator) { | switch (result.operator) { | ||||
| case SA_DiceExpressionOperator_MINUS: { | case SA_DiceExpressionOperator_MINUS: { | ||||
| // First, we check for possible overflow... | // First, we check for possible overflow... | ||||
| if (leftOperand > 0 && rightOperand < 0 && | |||||
| NSIntegerMax + rightOperand < leftOperand) { | |||||
| if ( leftOperand > 0 | |||||
| && rightOperand < 0 | |||||
| && (NSIntegerMax + rightOperand) < leftOperand) { | |||||
| result.errorBitMask |= SA_DiceExpressionError_INTEGER_OVERFLOW_SUBTRACTION; | result.errorBitMask |= SA_DiceExpressionError_INTEGER_OVERFLOW_SUBTRACTION; | ||||
| break; | break; | ||||
| } else if (leftOperand < 0 && rightOperand > 0 && | |||||
| NSIntegerMin + rightOperand > leftOperand) { | |||||
| } else if ( leftOperand < 0 | |||||
| && rightOperand > 0 | |||||
| && (NSIntegerMin + rightOperand) > leftOperand) { | |||||
| result.errorBitMask |= SA_DiceExpressionError_INTEGER_UNDERFLOW_SUBTRACTION; | result.errorBitMask |= SA_DiceExpressionError_INTEGER_UNDERFLOW_SUBTRACTION; | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| case SA_DiceExpressionOperator_PLUS: { | case SA_DiceExpressionOperator_PLUS: { | ||||
| // First, we check for possible overflow... | // First, we check for possible overflow... | ||||
| if (rightOperand > 0 && leftOperand > 0 && | |||||
| NSIntegerMax - rightOperand < leftOperand) { | |||||
| if ( rightOperand > 0 | |||||
| && leftOperand > 0 | |||||
| && (NSIntegerMax - rightOperand) < leftOperand) { | |||||
| result.errorBitMask |= SA_DiceExpressionError_INTEGER_OVERFLOW_ADDITION; | result.errorBitMask |= SA_DiceExpressionError_INTEGER_OVERFLOW_ADDITION; | ||||
| break; | break; | ||||
| } else if(rightOperand < 0 && leftOperand < 0 && NSIntegerMin - rightOperand > leftOperand) { | |||||
| } else if ( rightOperand < 0 | |||||
| && leftOperand < 0 | |||||
| && (NSIntegerMin - rightOperand) > leftOperand) { | |||||
| result.errorBitMask |= SA_DiceExpressionError_INTEGER_UNDERFLOW_ADDITION; | result.errorBitMask |= SA_DiceExpressionError_INTEGER_UNDERFLOW_ADDITION; | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| case SA_DiceExpressionOperator_TIMES: { | case SA_DiceExpressionOperator_TIMES: { | ||||
| // First, we check for possible overflow... | // 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)) { | |||||
| 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)) { | |||||
| result.errorBitMask |= SA_DiceExpressionError_INTEGER_OVERFLOW_MULTIPLICATION; | result.errorBitMask |= SA_DiceExpressionError_INTEGER_OVERFLOW_MULTIPLICATION; | ||||
| } else { | } else { | ||||
| result.errorBitMask |= SA_DiceExpressionError_INTEGER_UNDERFLOW_MULTIPLICATION; | result.errorBitMask |= SA_DiceExpressionError_INTEGER_UNDERFLOW_MULTIPLICATION; |