A set of classes for parsing, evaluating, and formatting die roll strings.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

SA_DiceFormatter.m 15KB

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