A set of classes for parsing, evaluating, and formatting die roll strings.
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

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] > 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. 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