| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439 |
- //
- // SA_DiceFormatter.m
- //
- // Copyright (c) 2016 Said Achmiz.
- //
- // This software is licensed under the MIT license.
- // See the file "LICENSE" for more information.
-
- #import "SA_DiceFormatter.h"
-
- #import "SA_DiceExpressionStringConstants.h"
- #import "SA_DiceErrorHandling.h"
-
- /********************************/
- #pragma mark File-scope variables
- /********************************/
-
- static SA_DiceFormatterBehavior _defaultFormatterBehavior = SA_DiceFormatterBehaviorLegacy;
- static NSDictionary *_errorDescriptions;
- static NSDictionary *_stringFormatRules;
-
- /**********************************************************/
- #pragma mark - SA_DiceFormatter class implementation
- /**********************************************************/
-
- @implementation SA_DiceFormatter
- {
- SA_DiceFormatterBehavior _formatterBehavior;
- }
-
- /**********************************/
- #pragma mark - Properties (general)
- /**********************************/
-
- - (void)setFormatterBehavior:(SA_DiceFormatterBehavior)newFormatterBehavior
- {
- _formatterBehavior = newFormatterBehavior;
-
- switch (_formatterBehavior)
- {
- case SA_DiceFormatterBehaviorLegacy:
- self.legacyModeErrorReportingEnabled = YES;
- break;
-
- case SA_DiceFormatterBehaviorSimple:
- case SA_DiceFormatterBehaviorModern:
- case SA_DiceFormatterBehaviorFeepbot:
- break;
-
- case SA_DiceFormatterBehaviorDefault:
- default:
- [self setFormatterBehavior:[SA_DiceFormatter defaultFormatterBehavior]];
- break;
- }
- }
-
- - (SA_DiceFormatterBehavior)formatterBehavior
- {
- return _formatterBehavior;
- }
-
- /****************************************/
- #pragma mark - "Class property" accessors
- /****************************************/
-
- + (void)setDefaultFormatterBehavior:(SA_DiceFormatterBehavior)newDefaultFormatterBehavior
- {
- if(newDefaultFormatterBehavior == SA_DiceFormatterBehaviorDefault)
- {
- _defaultFormatterBehavior = SA_DiceFormatterBehaviorLegacy;
- }
- else
- {
- _defaultFormatterBehavior = newDefaultFormatterBehavior;
- }
- }
-
- + (SA_DiceFormatterBehavior)defaultFormatterBehavior
- {
- return _defaultFormatterBehavior;
- }
-
- + (NSDictionary *)stringFormatRules
- {
- if(_stringFormatRules == nil)
- {
- [SA_DiceFormatter loadStringFormatRules];
- }
-
- return _stringFormatRules;
- }
-
- /********************************************/
- #pragma mark - Initializers & factory methods
- /********************************************/
-
- - (instancetype)init
- {
- return [self initWithBehavior:SA_DiceFormatterBehaviorDefault];
- }
-
- - (instancetype)initWithBehavior:(SA_DiceFormatterBehavior)formatterBehavior
- {
- if(self = [super init])
- {
- self.formatterBehavior = formatterBehavior;
-
- if(_errorDescriptions == nil)
- {
- [SA_DiceFormatter loadErrorDescriptions];
- }
- if(_stringFormatRules == nil)
- {
- [SA_DiceFormatter loadStringFormatRules];
- }
- }
- return self;
- }
-
- + (instancetype)defaultFormatter
- {
- return [[SA_DiceFormatter alloc] initWithBehavior:SA_DiceFormatterBehaviorDefault];
- }
-
- + (instancetype)formatterWithBehavior:(SA_DiceFormatterBehavior)formatterBehavior
- {
- return [[SA_DiceFormatter alloc] initWithBehavior:formatterBehavior];
- }
-
- /****************************/
- #pragma mark - Public methods
- /****************************/
-
- - (NSString *)stringFromExpression:(NSDictionary *)expression
- {
- if(_formatterBehavior == SA_DiceFormatterBehaviorSimple)
- {
- return [self simpleStringFromExpression:expression];
- }
- else // if(_formatterBehavior == SA_DiceFormatterBehaviorLegacy)
- {
- return [self legacyStringFromExpression:expression];
- }
- }
-
- // NOT YET IMPLEMENTED
- - (NSAttributedString *)attributedStringFromExpression:(NSDictionary *)expression
- {
- return [[NSAttributedString alloc] initWithString:[self stringFromExpression:expression]];
- }
-
- /**********************************************/
- #pragma mark - "Legacy" behavior implementation
- /**********************************************/
-
- // PROPERTIES
-
- @synthesize legacyModeErrorReportingEnabled = _legacyModeErrorReportingEnabled;
-
- // METHODS
-
- - (NSString *)legacyStringFromExpression:(NSDictionary *)expression
- {
- __block NSMutableString *formattedString = [NSMutableString string];
-
- // Attach the formatted string representation of the expression itself.
- [formattedString appendString:[self legacyStringFromIntermediaryExpression:expression]];
-
- // An expression may contain either a result, or one or more errors.
- // If a result is present, attach it. If errors are present, attach them
- // only if error reporting is enabled.
- if(expression[SA_DB_RESULT] != nil)
- {
- [formattedString appendFormat:@" = %@", expression[SA_DB_RESULT]];
- }
- else if(_legacyModeErrorReportingEnabled == YES && [getErrorsForExpression(expression) count] > 0)
- {
- if([getErrorsForExpression(expression) count] == 1)
- {
- [formattedString appendFormat:@" [ERROR: %@]", [SA_DiceFormatter descriptionForError:[getErrorsForExpression(expression) firstObject]]];
- }
- else
- {
- [formattedString appendFormat:@" [ERRORS: "];
- [getErrorsForExpression(expression) enumerateObjectsUsingBlock:^(NSString *error, NSUInteger idx, BOOL *stop)
- {
- [formattedString appendString:[SA_DiceFormatter descriptionForError:error]];
- if(idx != [getErrorsForExpression(expression) count] - 1)
- {
- [formattedString appendFormat:@" / "];
- }
- else
- {
- [formattedString appendFormat:@"]"];
- }
- }];
- }
- }
-
- return formattedString;
- }
-
- - (NSString *)legacyStringFromIntermediaryExpression:(NSDictionary *)expression
- {
- /*
- In legacy behavior, we do not print the results of intermediate terms in
- the expression tree (since the legacy output format was designed for
- expressions generated by a parser that does not support parentheses,
- doing so would not make sense anyway).
-
- The exception is roll commands, where the result of a roll-and-sum command
- is printed along with the rolls.
-
- For this reasons, when we recursively retrieve the string representations
- of sub-expressions, we call this method, not legacyStringFromExpression:.
- */
-
- if([expression[SA_DB_TERM_TYPE] isEqualToString:SA_DB_TERM_TYPE_OPERATION])
- {
- return [self legacyStringFromOperationExpression:expression];
- }
- else if([expression[SA_DB_TERM_TYPE] isEqualToString:SA_DB_TERM_TYPE_ROLL_COMMAND])
- {
- return [self legacyStringFromRollCommandExpression:expression];
- }
- else if([expression[SA_DB_TERM_TYPE] isEqualToString:SA_DB_TERM_TYPE_VALUE])
- {
- return [self legacyStringFromValueExpression:expression];
- }
- else // if([expression[SA_DB_TERM_TYPE] isEqualToString:SA_DB_TERM_TYPE_NONE]), probably
- {
- return expression[SA_DB_INPUT_STRING];
- }
- }
-
- - (NSString *)legacyStringFromOperationExpression:(NSDictionary *)expression
- {
- NSMutableString *formattedString = [NSMutableString string];
-
- // Check to see if the term is a negation or subtraction operation.
- if([expression[SA_DB_OPERATOR] isEqualToString:SA_DB_OPERATOR_MINUS])
- {
- // Get the canonical representation for the operator.
- NSString *operatorString = [SA_DiceFormatter canonicalRepresentationForOperator:SA_DB_OPERATOR_MINUS];
-
- // If we have a left operand, it's subtraction. If we do not, it's
- // negation.
- if(expression[SA_DB_OPERAND_LEFT] == nil)
- {
- // Get the operand.
- NSDictionary *rightOperandExpression = expression[SA_DB_OPERAND_RIGHT];
-
- // Write out the string representations of operator and the
- // right-hand-side expression.
- [formattedString appendString:operatorString];
- [formattedString appendString:[self legacyStringFromIntermediaryExpression:rightOperandExpression]];
-
- return formattedString;
- }
- else
- {
- // Get the operands.
- NSDictionary *leftOperandExpression = expression[SA_DB_OPERAND_LEFT];
- NSDictionary *rightOperandExpression = expression[SA_DB_OPERAND_RIGHT];
-
- // Write out the string representations of the left-hand-side
- // expression, the operator, and the right-hand-side expression.
- [formattedString appendString:[self legacyStringFromIntermediaryExpression:leftOperandExpression]];
- [formattedString appendFormat:@" %@ ", operatorString];
- [formattedString appendString:[self legacyStringFromIntermediaryExpression:rightOperandExpression]];
-
- return formattedString;
- }
- }
- // Check to see if the term is an addition or subtraction operation.
- else if([expression[SA_DB_OPERATOR] isEqualToString:SA_DB_OPERATOR_PLUS])
- {
- NSString *operatorString = [SA_DiceFormatter canonicalRepresentationForOperator:SA_DB_OPERATOR_PLUS];
-
- // Get the operands.
- NSDictionary *leftOperandExpression = expression[SA_DB_OPERAND_LEFT];
- NSDictionary *rightOperandExpression = expression[SA_DB_OPERAND_RIGHT];
-
- // Write out the string representations of the left-hand-side
- // expression, the operator, and the right-hand-side expression.
- [formattedString appendString:[self legacyStringFromIntermediaryExpression:leftOperandExpression]];
- [formattedString appendFormat:@" %@ ", operatorString];
- [formattedString appendString:[self legacyStringFromIntermediaryExpression:rightOperandExpression]];
-
- return formattedString;
- }
- // Check to see if the term is a multiplication operation.
- else if([expression[SA_DB_OPERATOR] isEqualToString:SA_DB_OPERATOR_TIMES])
- {
- // Get the canonical representation for the operator.
- NSString *operatorString = [SA_DiceFormatter canonicalRepresentationForOperator:SA_DB_OPERATOR_TIMES];
-
- // Get the operands.
- NSDictionary *leftOperandExpression = expression[SA_DB_OPERAND_LEFT];
- NSDictionary *rightOperandExpression = expression[SA_DB_OPERAND_RIGHT];
-
- // Write out the string representations of the left-hand-side
- // expression, the operator, and the right-hand-side expression.
- [formattedString appendString:[self legacyStringFromIntermediaryExpression:leftOperandExpression]];
- [formattedString appendFormat:@" %@ ", operatorString];
- [formattedString appendString:[self legacyStringFromIntermediaryExpression:rightOperandExpression]];
-
- return formattedString;
- }
- else
- {
- // If the operator is not one of the supported operators, default to
- // outputting the input string.
- return expression[SA_DB_INPUT_STRING];
- }
- }
-
- - (NSString *)legacyStringFromRollCommandExpression:(NSDictionary *)expression
- {
- /*
- In legacy behavior, we print the result of roll commands with the rolls
- generated by the roll command. If a roll command generates a roll-related
- error (any of the errors that begin with SA_DB_DIE_), we print "ERROR"
- in place of a result.
-
- Legacy behavior assumes support for roll-and-sum only, so we do not need
- to adjust the output format for different roll commands.
- */
- __block NSMutableString *formattedString = [NSMutableString string];
-
- // Append the die roll expression itself.
- [formattedString appendString:[self legacyStringFromIntermediaryExpression:expression[SA_DB_ROLL_DIE_COUNT]]];
- [formattedString appendString:[SA_DiceFormatter canonicalRollCommandDelimiterRepresentation]];
- [formattedString appendString:[self legacyStringFromIntermediaryExpression:expression[SA_DB_ROLL_DIE_SIZE]]];
-
- [formattedString appendFormat:@" < "];
-
- // Append a list of the rolled values, if any.
- if(expression[SA_DB_ROLLS] != nil && [expression[SA_DB_ROLLS] count] > 0)
- {
- [expression[SA_DB_ROLLS] enumerateObjectsUsingBlock:^(NSNumber *roll, NSUInteger idx, BOOL *stop) {
- [formattedString appendFormat:@"%@ ", roll];
- }];
-
- [formattedString appendFormat:@"= "];
- }
-
- // Append either the result, or the word 'ERROR'.
- [formattedString appendFormat:@"%@ >", ((expression[SA_DB_RESULT] != nil) ? expression[SA_DB_RESULT] : @"ERROR")];
-
- return formattedString;
- }
-
- - (NSString *)legacyStringFromValueExpression:(NSDictionary *)expression
- {
- // We use the value for the SA_DB_VALUE key and not the SA_DB_RESULT key
- // because they should be the same, and the SA_DB_RESULT key might not
- // have a value (if the expression was not evaluated); this saves us
- // having to compare it against nil, and saves code.
- return [expression[SA_DB_VALUE] stringValue];
- }
-
- /**********************************************/
- #pragma mark - "Simple" behavior implementation
- /**********************************************/
-
- - (NSString *)simpleStringFromExpression:(NSDictionary *)expression
- {
- NSMutableString *formattedString = [NSMutableString string];
-
- if(expression[SA_DB_RESULT] != nil)
- {
- [formattedString appendFormat:@"%@", expression[SA_DB_RESULT]];
- }
- else
- {
- [formattedString appendFormat:@"ERROR"];
- }
-
- return formattedString;
- }
-
- /****************************/
- #pragma mark - Helper methods
- /****************************/
-
- + (void)loadErrorDescriptions
- {
- NSString* errorDescriptionsPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"SA_DB_ErrorDescriptions" ofType:@"plist"];
- _errorDescriptions = [NSDictionary dictionaryWithContentsOfFile:errorDescriptionsPath];
- if(!_errorDescriptions)
- {
- NSLog(@"Could not load error descriptions!");
- }
- }
-
- + (NSString *)descriptionForError:(NSString *)error
- {
- if(_errorDescriptions == nil)
- {
- [SA_DiceFormatter loadErrorDescriptions];
- }
-
- if(_errorDescriptions[error] != nil)
- {
- return _errorDescriptions[error];
- }
- else
- {
- return error;
- }
- }
-
- + (void)loadStringFormatRules
- {
- NSString *stringFormatRulesPath = [[NSBundle bundleForClass:[self class]] pathForResource:SA_DB_STRING_FORMAT_RULES_PLIST_NAME ofType:@"plist"];
- _stringFormatRules = [NSDictionary dictionaryWithContentsOfFile:stringFormatRulesPath];
- if(!_stringFormatRules)
- {
- NSLog(@"Could not load string format rules!");
- }
- }
-
- + (NSString *)canonicalRepresentationForOperator:(NSString *)operatorName
- {
- return [SA_DiceFormatter canonicalOperatorRepresentations][operatorName];
- }
-
- + (NSDictionary *)canonicalOperatorRepresentations
- {
- return [SA_DiceFormatter stringFormatRules][SA_DB_CANONICAL_REPRESENTATIONS][SA_DB_CANONICAL_OPERATOR_REPRESENTATIONS];
- }
-
- + (NSString *)canonicalRollCommandDelimiterRepresentation
- {
- return [SA_DiceFormatter stringFormatRules][SA_DB_CANONICAL_REPRESENTATIONS][SA_DB_CANONICAL_ROLL_COMMAND_DELIMITER_REPRESENTATION];
- }
-
- @end
|