Updated LICENSE and info comments
[SA_Dice.git] / SA_DiceExpression.m
blobca23447975502f6ed4a9b5a46f290c7dc9ecf721
1 //
2 //  SA_DiceExpression.m
3 //
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"
23                                                                                                    };
24         });
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"
35                                                                                                                 };
36         });
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"
47                                                                                                            };
48         });
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"
77                                                                                                 };
78         });
80         __block NSMutableArray *errorStrings = [NSMutableArray array];
81         [SA_DiceExpressionErrorStringValues enumerateKeysAndObjectsUsingBlock:^(NSNumber *errorKey,
82                                                                                                                                                         NSString *errorString,
83                                                                                                                                                         BOOL *stop) {
84                 if (errorKey.unsignedIntegerValue & error)
85                         [errorStrings addObject:errorString];
86         }];
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;
98         else
99                 return NSOrderedSame;
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;
109         else
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
130                 ) {
131                 expression.type = SA_DiceExpressionTerm_NONE;
132                 expression.errorBitMask |= SA_DiceExpressionError_INVALID_EXPRESSION;
133                 return expression;
134         }
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
144                 ) {
145                 expression.operator = operator;
146         } else {
147                 expression.errorBitMask |= SA_DiceExpressionError_UNKNOWN_OPERATOR;
148                 return expression;
149         }
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
163         // between them.
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
167         // is in use...?
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.)
176         return expression;
179 /*******************************/
180 #pragma mark - NSCopying methods
181 /*******************************/
183 -(instancetype) copyWithZone:(NSZone *)zone {
184         SA_DiceExpression *copy = [SA_DiceExpression new];
186         copy.type = _type;
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;
201         copy.value = _value;
203         copy.inputString = _inputString;
204         copy.attributedInputString = _attributedInputString;
206         copy.result = _result;
208         copy.rolls = _rolls;
210         return copy;
213 @end