4 // Copyright 2016-2021 Said Achmiz.
5 // See LICENSE and README.md for more info.
7 #import "SA_DiceExpression.h"
9 #import "SA_DiceFormatter.h"
11 /*********************/
12 #pragma mark Functions
13 /*********************/
15 NSString *NSStringFromSA_DiceExpressionOperator(SA_DiceExpressionOperator operator) {
16 static NSDictionary <NSNumber *, NSString *> *SA_DiceExpressionOperatorStringValues;
17 static dispatch_once_t onceToken;
18 dispatch_once(&onceToken, ^{
19 SA_DiceExpressionOperatorStringValues = @{ @(SA_DiceExpressionOperator_NONE) : @"SA_DB_OPERATOR_NONE",
20 @(SA_DiceExpressionOperator_MINUS) : @"SA_DB_OPERATOR_MINUS",
21 @(SA_DiceExpressionOperator_PLUS) : @"SA_DB_OPERATOR_PLUS",
22 @(SA_DiceExpressionOperator_TIMES) : @"SA_DB_OPERATOR_TIMES"
26 return SA_DiceExpressionOperatorStringValues[@(operator)];
29 NSString *NSStringFromSA_DiceExpressionRollCommand(SA_DiceExpressionRollCommand command) {
30 static NSDictionary <NSNumber *, NSString *> *SA_DiceExpressionRollCommandStringValues;
31 static dispatch_once_t onceToken;
32 dispatch_once(&onceToken, ^{
33 SA_DiceExpressionRollCommandStringValues = @{ @(SA_DiceExpressionRollCommand_SUM) : @"SA_DB_ROLL_COMMAND_SUM",
34 @(SA_DiceExpressionRollCommand_SUM_EXPLODING) : @"SA_DB_ROLL_COMMAND_SUM_EXPLODING"
38 return SA_DiceExpressionRollCommandStringValues[@(command)];
41 NSString *NSStringFromSA_DiceExpressionRollModifier(SA_DiceExpressionRollModifier modifier) {
42 static NSDictionary <NSNumber *, NSString *> *SA_DiceExpressionRollModifierStringValues;
43 static dispatch_once_t onceToken;
44 dispatch_once(&onceToken, ^{
45 SA_DiceExpressionRollModifierStringValues = @{ @(SA_DiceExpressionRollModifier_KEEP_HIGHEST) : @"SA_DB_ROLL_MODIFIER_KEEP_HIGHEST",
46 @(SA_DiceExpressionRollModifier_KEEP_LOWEST) : @"SA_DB_ROLL_MODIFIER_KEEP_LOWEST"
50 return SA_DiceExpressionRollModifierStringValues[@(modifier)];
53 NSString *NSStringFromSA_DiceExpressionError(SA_DiceExpressionError error) {
54 static NSDictionary <NSNumber *, NSString *> *SA_DiceExpressionErrorStringValues;
55 static dispatch_once_t onceToken;
56 dispatch_once(&onceToken, ^{
57 SA_DiceExpressionErrorStringValues = @{ @(SA_DiceExpressionError_ROLL_STRING_EMPTY) : @"SA_DB_ERROR_ROLL_STRING_EMPTY",
58 @(SA_DiceExpressionError_ROLL_STRING_HAS_ILLEGAL_CHARACTERS) : @"SA_DB_ERROR_ROLL_STRING_HAS_ILLEGAL_CHARACTERS",
59 @(SA_DiceExpressionError_UNKNOWN_ROLL_COMMAND) : @"SA_DB_ERROR_UNKNOWN_ROLL_COMMAND",
60 @(SA_DiceExpressionError_ROLL_MODIFIER_INAPPLICABLE) : @"SA_DB_ERROR_ROLL_MODIFIER_INAPPLICABLE",
61 @(SA_DiceExpressionError_UNKNOWN_ROLL_MODIFIER) : @"SA_DB_ERROR_UNKNOWN_ROLL_MODIFIER",
62 @(SA_DiceExpressionError_DIE_COUNT_NEGATIVE) : @"SA_DB_ERROR_DIE_COUNT_NEGATIVE",
63 @(SA_DiceExpressionError_DIE_COUNT_EXCESSIVE) : @"SA_DB_ERROR_DIE_COUNT_EXCESSIVE",
64 @(SA_DiceExpressionError_DIE_SIZE_INVALID) : @"SA_DB_ERROR_DIE_SIZE_INVALID",
65 @(SA_DiceExpressionError_DIE_SIZE_EXCESSIVE) : @"SA_DB_ERROR_DIE_SIZE_EXCESSIVE",
66 @(SA_DiceExpressionError_UNKNOWN_OPERATOR) : @"SA_DB_ERROR_UNKNOWN_OPERATOR",
67 @(SA_DiceExpressionError_INVALID_EXPRESSION) : @"SA_DB_ERROR_INVALID_EXPRESSION",
68 @(SA_DiceExpressionError_INTEGER_OVERFLOW_NEGATION) : @"SA_DB_ERROR_INTEGER_OVERFLOW_NEGATION",
69 @(SA_DiceExpressionError_INTEGER_OVERFLOW_ADDITION) : @"SA_DB_ERROR_INTEGER_OVERFLOW_ADDITION",
70 @(SA_DiceExpressionError_INTEGER_UNDERFLOW_ADDITION) : @"SA_DB_ERROR_INTEGER_UNDERFLOW_ADDITION",
71 @(SA_DiceExpressionError_INTEGER_OVERFLOW_SUBTRACTION) : @"SA_DB_ERROR_INTEGER_OVERFLOW_SUBTRACTION",
72 @(SA_DiceExpressionError_INTEGER_UNDERFLOW_SUBTRACTION) : @"SA_DB_ERROR_INTEGER_UNDERFLOW_SUBTRACTION",
73 @(SA_DiceExpressionError_INTEGER_OVERFLOW_MULTIPLICATION) : @"SA_DB_ERROR_INTEGER_OVERFLOW_MULTIPLICATION",
74 @(SA_DiceExpressionError_INTEGER_UNDERFLOW_MULTIPLICATION) : @"SA_DB_ERROR_INTEGER_UNDERFLOW_MULTIPLICATION",
75 @(SA_DiceExpressionError_KEEP_COUNT_EXCEEDS_ROLL_COUNT) : @"SA_DB_ERROR_KEEP_COUNT_EXCEEDS_ROLL_COUNT",
76 @(SA_DiceExpressionError_KEEP_COUNT_NEGATIVE) : @"SA_DB_ERROR_KEEP_COUNT_NEGATIVE"
80 __block NSMutableArray *errorStrings = [NSMutableArray array];
81 [SA_DiceExpressionErrorStringValues enumerateKeysAndObjectsUsingBlock:^(NSNumber *errorKey,
82 NSString *errorString,
84 if (errorKey.unsignedIntegerValue & error)
85 [errorStrings addObject:errorString];
87 return [errorStrings componentsJoinedByString:@","];
89 // return SA_DiceExpressionErrorStringValues[@(error)];
92 NSComparisonResult compareEvaluatedExpressionsByResult(SA_DiceExpression *expression1,
93 SA_DiceExpression *expression2) {
94 if (expression1.result.integerValue < expression2.result.integerValue)
95 return NSOrderedAscending;
96 else if (expression1.result.integerValue > expression2.result.integerValue)
97 return NSOrderedDescending;
103 NSComparisonResult compareEvaluatedExpressionsByAttemptBonus(SA_DiceExpression *expression1,
104 SA_DiceExpression *expression2) {
105 if (expression1.rightOperand.result.integerValue < expression2.rightOperand.result.integerValue)
106 return NSOrderedAscending;
107 else if (expression1.rightOperand.result.integerValue > expression2.rightOperand.result.integerValue)
108 return NSOrderedDescending;
110 return NSOrderedSame;
114 /****************************************************/
115 #pragma mark - SA_DiceExpression class implementation
116 /****************************************************/
118 @implementation SA_DiceExpression
120 +(SA_DiceExpression *) expressionByJoiningExpression:(SA_DiceExpression *)leftHandExpression
121 toExpression:(SA_DiceExpression *)rightHandExpression
122 withOperator:(SA_DiceExpressionOperator)operator {
123 SA_DiceExpression *expression = [SA_DiceExpression new];
125 // First, we check that the operands and operator are not nil. If they are,
126 // then the expression is invalid...
127 if ( leftHandExpression == nil
128 || rightHandExpression == nil
129 || operator == SA_DiceExpressionOperator_NONE
131 expression.type = SA_DiceExpressionTerm_NONE;
132 expression.errorBitMask |= SA_DiceExpressionError_INVALID_EXPRESSION;
136 // If the operands and operator are present, then the expression is an
137 // operation expression...
138 expression.type = SA_DiceExpressionTerm_OPERATION;
140 // ... but does it have a valid operator?
141 if ( operator == SA_DiceExpressionOperator_MINUS
142 || operator == SA_DiceExpressionOperator_PLUS
143 || operator == SA_DiceExpressionOperator_TIMES
145 expression.operator = operator;
147 expression.errorBitMask |= SA_DiceExpressionError_UNKNOWN_OPERATOR;
151 // The operator is valid. Set the operands...
152 expression.leftOperand = leftHandExpression;
153 expression.rightOperand = rightHandExpression;
155 // And inherit any errors that they may have.
156 expression.errorBitMask |= expression.leftOperand.errorBitMask;
157 expression.errorBitMask |= expression.rightOperand.errorBitMask;
159 // Since this top-level expression was NOT generated by parsing an input
160 // string, for completeness and consistency, we have to generate a fake
161 // input string ourselves! We do this by wrapping each operand in
162 // parentheses and putting the canonical representation of the operator
164 // TODO: Shouldn't the canonical representation of an operator
165 // possibly vary by formatter behavior...?
166 // But on the other hand, how does a parser know what formatter behavior
168 // TODO: Parentheses aren't even supported by the legacy parser!
169 // This is total nonsense!
170 expression.inputString = [NSString stringWithFormat:@"(%@)%@(%@)",
171 expression.leftOperand.inputString,
172 [SA_DiceFormatter canonicalRepresentationForOperator:expression.operator],
173 expression.rightOperand.inputString];
175 // The joining is complete. (Power overwhelming.)
179 /*******************************/
180 #pragma mark - NSCopying methods
181 /*******************************/
183 -(instancetype) copyWithZone:(NSZone *)zone {
184 SA_DiceExpression *copy = [SA_DiceExpression new];
188 copy.errorBitMask = _errorBitMask;
190 copy.operator = _operator;
191 copy.leftOperand = [_leftOperand copy];
192 copy.rightOperand = [_rightOperand copy];
194 copy.rollCommand = _rollCommand;
195 copy.dieCount = [_dieCount copy];
196 copy.dieSize = [_dieSize copy];
197 copy.dieType = _dieType;
199 copy.rollModifier = _rollModifier;
203 copy.inputString = _inputString;
204 copy.attributedInputString = _attributedInputString;
206 copy.result = _result;