A set of classes for parsing, evaluating, and formatting die roll strings.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. //
  2. // SA_DiceFormatter.m
  3. //
  4. // Copyright (c) 2016 Said Achmiz.
  5. //
  6. // This software is licensed under the MIT license.
  7. // See the file "LICENSE" for more information.
  8. #import "SA_DiceFormatter.h"
  9. #import "SA_DiceExpressionStringConstants.h"
  10. #import "SA_DiceErrorHandling.h"
  11. /********************************/
  12. #pragma mark File-scope variables
  13. /********************************/
  14. static SA_DiceFormatterBehavior _defaultFormatterBehavior = SA_DiceFormatterBehaviorLegacy;
  15. static NSDictionary *_errorDescriptions;
  16. static NSDictionary *_stringFormatRules;
  17. /**********************************************************/
  18. #pragma mark - SA_DiceFormatter class implementation
  19. /**********************************************************/
  20. @implementation SA_DiceFormatter
  21. {
  22. SA_DiceFormatterBehavior _formatterBehavior;
  23. }
  24. /**********************************/
  25. #pragma mark - Properties (general)
  26. /**********************************/
  27. - (void)setFormatterBehavior:(SA_DiceFormatterBehavior)newFormatterBehavior
  28. {
  29. _formatterBehavior = newFormatterBehavior;
  30. switch (_formatterBehavior)
  31. {
  32. case SA_DiceFormatterBehaviorLegacy:
  33. self.legacyModeErrorReportingEnabled = YES;
  34. break;
  35. case SA_DiceFormatterBehaviorSimple:
  36. case SA_DiceFormatterBehaviorModern:
  37. case SA_DiceFormatterBehaviorFeepbot:
  38. break;
  39. case SA_DiceFormatterBehaviorDefault:
  40. default:
  41. [self setFormatterBehavior:[SA_DiceFormatter defaultFormatterBehavior]];
  42. break;
  43. }
  44. }
  45. - (SA_DiceFormatterBehavior)formatterBehavior
  46. {
  47. return _formatterBehavior;
  48. }
  49. /****************************************/
  50. #pragma mark - "Class property" accessors
  51. /****************************************/
  52. + (void)setDefaultFormatterBehavior:(SA_DiceFormatterBehavior)newDefaultFormatterBehavior
  53. {
  54. if(newDefaultFormatterBehavior == SA_DiceFormatterBehaviorDefault)
  55. {
  56. _defaultFormatterBehavior = SA_DiceFormatterBehaviorLegacy;
  57. }
  58. else
  59. {
  60. _defaultFormatterBehavior = newDefaultFormatterBehavior;
  61. }
  62. }
  63. + (SA_DiceFormatterBehavior)defaultFormatterBehavior
  64. {
  65. return _defaultFormatterBehavior;
  66. }
  67. + (NSDictionary *)stringFormatRules
  68. {
  69. if(_stringFormatRules == nil)
  70. {
  71. [SA_DiceFormatter loadStringFormatRules];
  72. }
  73. return _stringFormatRules;
  74. }
  75. /********************************************/
  76. #pragma mark - Initializers & factory methods
  77. /********************************************/
  78. - (instancetype)init
  79. {
  80. return [self initWithBehavior:SA_DiceFormatterBehaviorDefault];
  81. }
  82. - (instancetype)initWithBehavior:(SA_DiceFormatterBehavior)formatterBehavior
  83. {
  84. if(self = [super init])
  85. {
  86. self.formatterBehavior = formatterBehavior;
  87. if(_errorDescriptions == nil)
  88. {
  89. [SA_DiceFormatter loadErrorDescriptions];
  90. }
  91. if(_stringFormatRules == nil)
  92. {
  93. [SA_DiceFormatter loadStringFormatRules];
  94. }
  95. }
  96. return self;
  97. }
  98. + (instancetype)defaultFormatter
  99. {
  100. return [[SA_DiceFormatter alloc] initWithBehavior:SA_DiceFormatterBehaviorDefault];
  101. }
  102. + (instancetype)formatterWithBehavior:(SA_DiceFormatterBehavior)formatterBehavior
  103. {
  104. return [[SA_DiceFormatter alloc] initWithBehavior:formatterBehavior];
  105. }
  106. /****************************/
  107. #pragma mark - Public methods
  108. /****************************/
  109. - (NSString *)stringFromExpression:(NSDictionary *)expression
  110. {
  111. if(_formatterBehavior == SA_DiceFormatterBehaviorSimple)
  112. {
  113. return [self simpleStringFromExpression:expression];
  114. }
  115. else // if(_formatterBehavior == SA_DiceFormatterBehaviorLegacy)
  116. {
  117. return [self legacyStringFromExpression:expression];
  118. }
  119. }
  120. // NOT YET IMPLEMENTED
  121. - (NSAttributedString *)attributedStringFromExpression:(NSDictionary *)expression
  122. {
  123. return [[NSAttributedString alloc] initWithString:[self stringFromExpression:expression]];
  124. }
  125. /**********************************************/
  126. #pragma mark - "Legacy" behavior implementation
  127. /**********************************************/
  128. // PROPERTIES
  129. @synthesize legacyModeErrorReportingEnabled = _legacyModeErrorReportingEnabled;
  130. // METHODS
  131. - (NSString *)legacyStringFromExpression:(NSDictionary *)expression
  132. {
  133. __block NSMutableString *formattedString = [NSMutableString string];
  134. // Attach the formatted string representation of the expression itself.
  135. [formattedString appendString:[self legacyStringFromIntermediaryExpression:expression]];
  136. // An expression may contain either a result, or one or more errors.
  137. // If a result is present, attach it. If errors are present, attach them
  138. // only if error reporting is enabled.
  139. if(expression[SA_DB_RESULT] != nil)
  140. {
  141. [formattedString appendFormat:@" = %@", expression[SA_DB_RESULT]];
  142. }
  143. else if(_legacyModeErrorReportingEnabled == YES && [getErrorsForExpression(expression) count] > 0)
  144. {
  145. if([getErrorsForExpression(expression) count] == 1)
  146. {
  147. [formattedString appendFormat:@" [ERROR: %@]", [SA_DiceFormatter descriptionForError:[getErrorsForExpression(expression) firstObject]]];
  148. }
  149. else
  150. {
  151. [formattedString appendFormat:@" [ERRORS: "];
  152. [getErrorsForExpression(expression) enumerateObjectsUsingBlock:^(NSString *error, NSUInteger idx, BOOL *stop)
  153. {
  154. [formattedString appendString:[SA_DiceFormatter descriptionForError:error]];
  155. if(idx != [getErrorsForExpression(expression) count] - 1)
  156. {
  157. [formattedString appendFormat:@" / "];
  158. }
  159. else
  160. {
  161. [formattedString appendFormat:@"]"];
  162. }
  163. }];
  164. }
  165. }
  166. // Make all instances of the minus sign be represented with the proper,
  167. // canonical minus sign.
  168. return [self rectifyMinusSignInString:formattedString];
  169. }
  170. - (NSString *)legacyStringFromIntermediaryExpression:(NSDictionary *)expression
  171. {
  172. /*
  173. In legacy behavior, we do not print the results of intermediate terms in
  174. the expression tree (since the legacy output format was designed for
  175. expressions generated by a parser that does not support parentheses,
  176. doing so would not make sense anyway).
  177. The exception is roll commands, where the result of a roll-and-sum command
  178. is printed along with the rolls.
  179. For this reasons, when we recursively retrieve the string representations
  180. of sub-expressions, we call this method, not legacyStringFromExpression:.
  181. */
  182. if([expression[SA_DB_TERM_TYPE] isEqualToString:SA_DB_TERM_TYPE_OPERATION])
  183. {
  184. return [self legacyStringFromOperationExpression:expression];
  185. }
  186. else if([expression[SA_DB_TERM_TYPE] isEqualToString:SA_DB_TERM_TYPE_ROLL_COMMAND])
  187. {
  188. return [self legacyStringFromRollCommandExpression:expression];
  189. }
  190. else if([expression[SA_DB_TERM_TYPE] isEqualToString:SA_DB_TERM_TYPE_VALUE])
  191. {
  192. return [self legacyStringFromValueExpression:expression];
  193. }
  194. else // if([expression[SA_DB_TERM_TYPE] isEqualToString:SA_DB_TERM_TYPE_NONE]), probably
  195. {
  196. return expression[SA_DB_INPUT_STRING];
  197. }
  198. }
  199. - (NSString *)legacyStringFromOperationExpression:(NSDictionary *)expression
  200. {
  201. NSMutableString *formattedString = [NSMutableString string];
  202. // Check to see if the term is a negation or subtraction operation.
  203. if([expression[SA_DB_OPERATOR] isEqualToString:SA_DB_OPERATOR_MINUS])
  204. {
  205. // Get the canonical representation for the operator.
  206. NSString *operatorString = [SA_DiceFormatter canonicalRepresentationForOperator:SA_DB_OPERATOR_MINUS];
  207. // If we have a left operand, it's subtraction. If we do not, it's
  208. // negation.
  209. if(expression[SA_DB_OPERAND_LEFT] == nil)
  210. {
  211. // Get the operand.
  212. NSDictionary *rightOperandExpression = expression[SA_DB_OPERAND_RIGHT];
  213. // Write out the string representations of operator and the
  214. // right-hand-side expression.
  215. [formattedString appendString:operatorString];
  216. [formattedString appendString:[self legacyStringFromIntermediaryExpression:rightOperandExpression]];
  217. return formattedString;
  218. }
  219. else
  220. {
  221. // Get the operands.
  222. NSDictionary *leftOperandExpression = expression[SA_DB_OPERAND_LEFT];
  223. NSDictionary *rightOperandExpression = expression[SA_DB_OPERAND_RIGHT];
  224. // Write out the string representations of the left-hand-side
  225. // expression, the operator, and the right-hand-side expression.
  226. [formattedString appendString:[self legacyStringFromIntermediaryExpression:leftOperandExpression]];
  227. [formattedString appendFormat:@" %@ ", operatorString];
  228. [formattedString appendString:[self legacyStringFromIntermediaryExpression:rightOperandExpression]];
  229. return formattedString;
  230. }
  231. }
  232. // Check to see if the term is an addition or subtraction operation.
  233. else if([expression[SA_DB_OPERATOR] isEqualToString:SA_DB_OPERATOR_PLUS])
  234. {
  235. NSString *operatorString = [SA_DiceFormatter canonicalRepresentationForOperator:SA_DB_OPERATOR_PLUS];
  236. // Get the operands.
  237. NSDictionary *leftOperandExpression = expression[SA_DB_OPERAND_LEFT];
  238. NSDictionary *rightOperandExpression = expression[SA_DB_OPERAND_RIGHT];
  239. // Write out the string representations of the left-hand-side
  240. // expression, the operator, and the right-hand-side expression.
  241. [formattedString appendString:[self legacyStringFromIntermediaryExpression:leftOperandExpression]];
  242. [formattedString appendFormat:@" %@ ", operatorString];
  243. [formattedString appendString:[self legacyStringFromIntermediaryExpression:rightOperandExpression]];
  244. return formattedString;
  245. }
  246. // Check to see if the term is a multiplication operation.
  247. else if([expression[SA_DB_OPERATOR] isEqualToString:SA_DB_OPERATOR_TIMES])
  248. {
  249. // Get the canonical representation for the operator.
  250. NSString *operatorString = [SA_DiceFormatter canonicalRepresentationForOperator:SA_DB_OPERATOR_TIMES];
  251. // Get the operands.
  252. NSDictionary *leftOperandExpression = expression[SA_DB_OPERAND_LEFT];
  253. NSDictionary *rightOperandExpression = expression[SA_DB_OPERAND_RIGHT];
  254. // Write out the string representations of the left-hand-side
  255. // expression, the operator, and the right-hand-side expression.
  256. [formattedString appendString:[self legacyStringFromIntermediaryExpression:leftOperandExpression]];
  257. [formattedString appendFormat:@" %@ ", operatorString];
  258. [formattedString appendString:[self legacyStringFromIntermediaryExpression:rightOperandExpression]];
  259. return formattedString;
  260. }
  261. else
  262. {
  263. // If the operator is not one of the supported operators, default to
  264. // outputting the input string.
  265. return expression[SA_DB_INPUT_STRING];
  266. }
  267. }
  268. - (NSString *)legacyStringFromRollCommandExpression:(NSDictionary *)expression
  269. {
  270. /*
  271. In legacy behavior, we print the result of roll commands with the rolls
  272. generated by the roll command. If a roll command generates a roll-related
  273. error (any of the errors that begin with SA_DB_DIE_), we print "ERROR"
  274. in place of a result.
  275. Legacy behavior assumes support for roll-and-sum only, so we do not need
  276. to adjust the output format for different roll commands.
  277. */
  278. __block NSMutableString *formattedString = [NSMutableString string];
  279. // Append the die roll expression itself.
  280. [formattedString appendString:[self legacyStringFromIntermediaryExpression:expression[SA_DB_ROLL_DIE_COUNT]]];
  281. [formattedString appendString:[SA_DiceFormatter canonicalRollCommandDelimiterRepresentation]];
  282. [formattedString appendString:[self legacyStringFromIntermediaryExpression:expression[SA_DB_ROLL_DIE_SIZE]]];
  283. [formattedString appendFormat:@" < "];
  284. // Append a list of the rolled values, if any.
  285. if(expression[SA_DB_ROLLS] != nil && [expression[SA_DB_ROLLS] count] > 0)
  286. {
  287. [expression[SA_DB_ROLLS] enumerateObjectsUsingBlock:^(NSNumber *roll, NSUInteger idx, BOOL *stop) {
  288. [formattedString appendFormat:@"%@ ", roll];
  289. }];
  290. [formattedString appendFormat:@"= "];
  291. }
  292. // Append either the result, or the word 'ERROR'.
  293. [formattedString appendFormat:@"%@ >", ((expression[SA_DB_RESULT] != nil) ? expression[SA_DB_RESULT] : @"ERROR")];
  294. return formattedString;
  295. }
  296. - (NSString *)legacyStringFromValueExpression:(NSDictionary *)expression
  297. {
  298. // We use the value for the SA_DB_VALUE key and not the SA_DB_RESULT key
  299. // because they should be the same, and the SA_DB_RESULT key might not
  300. // have a value (if the expression was not evaluated); this saves us
  301. // having to compare it against nil, and saves code.
  302. return [expression[SA_DB_VALUE] stringValue];
  303. }
  304. /**********************************************/
  305. #pragma mark - "Simple" behavior implementation
  306. /**********************************************/
  307. - (NSString *)simpleStringFromExpression:(NSDictionary *)expression
  308. {
  309. NSMutableString *formattedString = [NSMutableString string];
  310. if(expression[SA_DB_RESULT] != nil)
  311. {
  312. [formattedString appendFormat:@"%@", expression[SA_DB_RESULT]];
  313. }
  314. else
  315. {
  316. [formattedString appendFormat:@"ERROR"];
  317. }
  318. // Make all instances of the minus sign be represented with the proper,
  319. // canonical minus sign.
  320. return [self rectifyMinusSignInString:formattedString];
  321. }
  322. /****************************/
  323. #pragma mark - Helper methods
  324. /****************************/
  325. - (NSString *)rectifyMinusSignInString:(NSString *)aString
  326. {
  327. __block NSMutableString* sameStringButMutable = aString.mutableCopy;
  328. NSLog(@"%@", sameStringButMutable);
  329. NSString *validMinusSignCharacters = [SA_DiceFormatter stringFormatRules][SA_DB_VALID_CHARACTERS][SA_DB_VALID_OPERATOR_CHARACTERS][SA_DB_OPERATOR_MINUS];
  330. NSString *theRealMinusSign = [SA_DiceFormatter canonicalRepresentationForOperator:SA_DB_OPERATOR_MINUS];
  331. [validMinusSignCharacters enumerateSubstringsInRange:NSMakeRange(0, validMinusSignCharacters.length)
  332. options:NSStringEnumerationByComposedCharacterSequences
  333. usingBlock:^(NSString *character, NSRange characterRange, NSRange enclosingRange, BOOL *stop)
  334. {
  335. [sameStringButMutable replaceOccurrencesOfString:character withString:theRealMinusSign options:NSLiteralSearch range:NSMakeRange(0, sameStringButMutable.length)];
  336. }];
  337. NSLog(@"%@", sameStringButMutable);
  338. return sameStringButMutable.copy;
  339. }
  340. + (void)loadErrorDescriptions
  341. {
  342. NSString* errorDescriptionsPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"SA_DB_ErrorDescriptions" ofType:@"plist"];
  343. _errorDescriptions = [NSDictionary dictionaryWithContentsOfFile:errorDescriptionsPath];
  344. if(!_errorDescriptions)
  345. {
  346. NSLog(@"Could not load error descriptions!");
  347. }
  348. }
  349. + (NSString *)descriptionForError:(NSString *)error
  350. {
  351. if(_errorDescriptions == nil)
  352. {
  353. [SA_DiceFormatter loadErrorDescriptions];
  354. }
  355. if(_errorDescriptions[error] != nil)
  356. {
  357. return _errorDescriptions[error];
  358. }
  359. else
  360. {
  361. return error;
  362. }
  363. }
  364. + (void)loadStringFormatRules
  365. {
  366. NSString *stringFormatRulesPath = [[NSBundle bundleForClass:[self class]] pathForResource:SA_DB_STRING_FORMAT_RULES_PLIST_NAME ofType:@"plist"];
  367. _stringFormatRules = [NSDictionary dictionaryWithContentsOfFile:stringFormatRulesPath];
  368. if(!_stringFormatRules)
  369. {
  370. NSLog(@"Could not load string format rules!");
  371. }
  372. }
  373. + (NSString *)canonicalRepresentationForOperator:(NSString *)operatorName
  374. {
  375. return [SA_DiceFormatter canonicalOperatorRepresentations][operatorName];
  376. }
  377. + (NSDictionary *)canonicalOperatorRepresentations
  378. {
  379. return [SA_DiceFormatter stringFormatRules][SA_DB_CANONICAL_REPRESENTATIONS][SA_DB_CANONICAL_OPERATOR_REPRESENTATIONS];
  380. }
  381. + (NSString *)canonicalRollCommandDelimiterRepresentation
  382. {
  383. return [SA_DiceFormatter stringFormatRules][SA_DB_CANONICAL_REPRESENTATIONS][SA_DB_CANONICAL_ROLL_COMMAND_DELIMITER_REPRESENTATION];
  384. }
  385. @end