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 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  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] > 1)
  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. return formattedString;
  167. }
  168. - (NSString *)legacyStringFromIntermediaryExpression:(NSDictionary *)expression
  169. {
  170. /*
  171. In legacy behavior, we do not print the results of intermediate terms in
  172. the expression tree (since the legacy output format was designed for
  173. expressions generated by a parser that does not support parentheses,
  174. doing so would not make sense anyway).
  175. The exception is roll commands, where the result of a roll-and-sum command
  176. is printed along with the rolls.
  177. For this reasons, when we recursively retrieve the string representations
  178. of sub-expressions, we call this method, not legacyStringFromExpression:.
  179. */
  180. if([expression[SA_DB_TERM_TYPE] isEqualToString:SA_DB_TERM_TYPE_OPERATION])
  181. {
  182. return [self legacyStringFromOperationExpression:expression];
  183. }
  184. else if([expression[SA_DB_TERM_TYPE] isEqualToString:SA_DB_TERM_TYPE_ROLL_COMMAND])
  185. {
  186. return [self legacyStringFromRollCommandExpression:expression];
  187. }
  188. else if([expression[SA_DB_TERM_TYPE] isEqualToString:SA_DB_TERM_TYPE_VALUE])
  189. {
  190. return [self legacyStringFromValueExpression:expression];
  191. }
  192. else // if([expression[SA_DB_TERM_TYPE] isEqualToString:SA_DB_TERM_TYPE_NONE]), probably
  193. {
  194. return expression[SA_DB_INPUT_STRING];
  195. }
  196. }
  197. - (NSString *)legacyStringFromOperationExpression:(NSDictionary *)expression
  198. {
  199. NSMutableString *formattedString = [NSMutableString string];
  200. // Check to see if the term is a negation or subtraction operation.
  201. if([expression[SA_DB_OPERATOR] isEqualToString:SA_DB_OPERATOR_MINUS])
  202. {
  203. // Get the canonical representation for the operator.
  204. NSString *operatorString = [SA_DiceFormatter canonicalRepresentationForOperator:SA_DB_OPERATOR_MINUS];
  205. // If we have a left operand, it's subtraction. If we do not, it's
  206. // negation.
  207. if(expression[SA_DB_OPERAND_LEFT] == nil)
  208. {
  209. // Get the operand.
  210. NSDictionary *rightOperandExpression = expression[SA_DB_OPERAND_RIGHT];
  211. // Write out the string representations of operator and the
  212. // right-hand-side expression.
  213. [formattedString appendString:operatorString];
  214. [formattedString appendString:[self legacyStringFromIntermediaryExpression:rightOperandExpression]];
  215. return formattedString;
  216. }
  217. else
  218. {
  219. // Get the operands.
  220. NSDictionary *leftOperandExpression = expression[SA_DB_OPERAND_LEFT];
  221. NSDictionary *rightOperandExpression = expression[SA_DB_OPERAND_RIGHT];
  222. // Write out the string representations of the left-hand-side
  223. // expression, the operator, and the right-hand-side expression.
  224. [formattedString appendString:[self legacyStringFromIntermediaryExpression:leftOperandExpression]];
  225. [formattedString appendFormat:@" %@ ", operatorString];
  226. [formattedString appendString:[self legacyStringFromIntermediaryExpression:rightOperandExpression]];
  227. return formattedString;
  228. }
  229. }
  230. // Check to see if the term is an addition or subtraction operation.
  231. else if([expression[SA_DB_OPERATOR] isEqualToString:SA_DB_OPERATOR_PLUS])
  232. {
  233. NSString *operatorString = [SA_DiceFormatter canonicalRepresentationForOperator:SA_DB_OPERATOR_PLUS];
  234. // Get the operands.
  235. NSDictionary *leftOperandExpression = expression[SA_DB_OPERAND_LEFT];
  236. NSDictionary *rightOperandExpression = expression[SA_DB_OPERAND_RIGHT];
  237. // Write out the string representations of the left-hand-side
  238. // expression, the operator, and the right-hand-side expression.
  239. [formattedString appendString:[self legacyStringFromIntermediaryExpression:leftOperandExpression]];
  240. [formattedString appendFormat:@" %@ ", operatorString];
  241. [formattedString appendString:[self legacyStringFromIntermediaryExpression:rightOperandExpression]];
  242. return formattedString;
  243. }
  244. // Check to see if the term is a multiplication operation.
  245. else if([expression[SA_DB_OPERATOR] isEqualToString:SA_DB_OPERATOR_TIMES])
  246. {
  247. // Get the canonical representation for the operator.
  248. NSString *operatorString = [SA_DiceFormatter canonicalRepresentationForOperator:SA_DB_OPERATOR_TIMES];
  249. // Get the operands.
  250. NSDictionary *leftOperandExpression = expression[SA_DB_OPERAND_LEFT];
  251. NSDictionary *rightOperandExpression = expression[SA_DB_OPERAND_RIGHT];
  252. // Write out the string representations of the left-hand-side
  253. // expression, the operator, and the right-hand-side expression.
  254. [formattedString appendString:[self legacyStringFromIntermediaryExpression:leftOperandExpression]];
  255. [formattedString appendFormat:@" %@ ", operatorString];
  256. [formattedString appendString:[self legacyStringFromIntermediaryExpression:rightOperandExpression]];
  257. return formattedString;
  258. }
  259. else
  260. {
  261. // If the operator is not one of the supported operators, default to
  262. // outputting the input string.
  263. return expression[SA_DB_INPUT_STRING];
  264. }
  265. }
  266. - (NSString *)legacyStringFromRollCommandExpression:(NSDictionary *)expression
  267. {
  268. /*
  269. In legacy behavior, we print the result of roll commands with the rolls
  270. generated by the roll command. If a roll command generates a roll-related
  271. error (any of the errors that begin with SA_DB_DIE_), we print "ERROR"
  272. in place of a result.
  273. Legacy behavior assumes support for roll-and-sum only, so we do not need
  274. to adjust the output format for different roll commands.
  275. */
  276. __block NSMutableString *formattedString = [NSMutableString string];
  277. // Append the die roll expression itself.
  278. [formattedString appendString:[self legacyStringFromIntermediaryExpression:expression[SA_DB_ROLL_DIE_COUNT]]];
  279. [formattedString appendString:[SA_DiceFormatter canonicalRollCommandDelimiterRepresentation]];
  280. [formattedString appendString:[self legacyStringFromIntermediaryExpression:expression[SA_DB_ROLL_DIE_SIZE]]];
  281. [formattedString appendFormat:@" < "];
  282. // Append a list of the rolled values, if any.
  283. if(expression[SA_DB_ROLLS] != nil && [expression[SA_DB_ROLLS] count] > 0)
  284. {
  285. [expression[SA_DB_ROLLS] enumerateObjectsUsingBlock:^(NSNumber *roll, NSUInteger idx, BOOL *stop) {
  286. [formattedString appendFormat:@"%@ ", roll];
  287. }];
  288. [formattedString appendFormat:@"= "];
  289. }
  290. // Append either the result, or the word 'ERROR'.
  291. [formattedString appendFormat:@"%@ >", ((expression[SA_DB_RESULT] != nil) ? expression[SA_DB_RESULT] : @"ERROR")];
  292. return formattedString;
  293. }
  294. - (NSString *)legacyStringFromValueExpression:(NSDictionary *)expression
  295. {
  296. // We use the value for the SA_DB_VALUE key and not the SA_DB_RESULT key
  297. // because they should be the same, and the SA_DB_RESULT key might not
  298. // have a value (if the expression was not evaluated); this saves us
  299. // having to compare it against nil, and saves code.
  300. return [expression[SA_DB_VALUE] stringValue];
  301. }
  302. /**********************************************/
  303. #pragma mark - "Simple" behavior implementation
  304. /**********************************************/
  305. - (NSString *)simpleStringFromExpression:(NSDictionary *)expression
  306. {
  307. NSMutableString *formattedString = [NSMutableString string];
  308. if(expression[SA_DB_RESULT] != nil)
  309. {
  310. [formattedString appendFormat:@"%@", expression[SA_DB_RESULT]];
  311. }
  312. else
  313. {
  314. [formattedString appendFormat:@"ERROR"];
  315. }
  316. return formattedString;
  317. }
  318. /****************************/
  319. #pragma mark - Helper methods
  320. /****************************/
  321. + (void)loadErrorDescriptions
  322. {
  323. NSString* errorDescriptionsPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"SA_DB_ErrorDescriptions" ofType:@"plist"];
  324. _errorDescriptions = [NSDictionary dictionaryWithContentsOfFile:errorDescriptionsPath];
  325. if(_errorDescriptions)
  326. {
  327. NSLog(@"Error descriptions loaded successfully.");
  328. }
  329. else
  330. {
  331. NSLog(@"Could not load error descriptions!");
  332. }
  333. }
  334. + (NSString *)descriptionForError:(NSString *)error
  335. {
  336. if(_errorDescriptions == nil)
  337. {
  338. [SA_DiceFormatter loadErrorDescriptions];
  339. }
  340. if(_errorDescriptions[error] != nil)
  341. {
  342. return _errorDescriptions[error];
  343. }
  344. else
  345. {
  346. return error;
  347. }
  348. }
  349. + (void)loadStringFormatRules
  350. {
  351. NSString *stringFormatRulesPath = [[NSBundle bundleForClass:[self class]] pathForResource:SA_DB_STRING_FORMAT_RULES_PLIST_NAME ofType:@"plist"];
  352. _stringFormatRules = [NSDictionary dictionaryWithContentsOfFile:stringFormatRulesPath];
  353. if(_stringFormatRules)
  354. {
  355. NSLog(@"String format rules loaded successfully.");
  356. }
  357. else
  358. {
  359. NSLog(@"Could not load string format rules!");
  360. }
  361. }
  362. + (NSString *)canonicalRepresentationForOperator:(NSString *)operatorName
  363. {
  364. return [SA_DiceFormatter canonicalOperatorRepresentations][operatorName];
  365. }
  366. + (NSDictionary *)canonicalOperatorRepresentations
  367. {
  368. return [SA_DiceFormatter stringFormatRules][SA_DB_CANONICAL_REPRESENTATIONS][SA_DB_CANONICAL_OPERATOR_REPRESENTATIONS];
  369. }
  370. + (NSString *)canonicalRollCommandDelimiterRepresentation
  371. {
  372. return [SA_DiceFormatter stringFormatRules][SA_DB_CANONICAL_REPRESENTATIONS][SA_DB_CANONICAL_ROLL_COMMAND_DELIMITER_REPRESENTATION];
  373. }
  374. @end