achmizs 6 роки тому
джерело
коміт
7052f3adf1

+ 1
- 1
LICENSE Переглянути файл

@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2016 Said Achmiz.
Copyright (c) 2019 Said Achmiz.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

+ 13
- 13
README.md Переглянути файл

@@ -1,30 +1,30 @@
# SA_Dice
A set of classes for parsing, evaluating, and formatting die roll strings.

A "die roll string" is a string that contains an expression that defines operations that (usually) involve the rolling of dice, and possibly various operations on the results of those die rolls. (See "What Are Dice?" and "What's This For?", below, for more info. See the header files for each of the classes for more detailed documentation.)
A “die roll string” is a string that contains an expression that defines operations that (usually) involve the rolling of dice, and possibly various operations on the results of those die rolls. (See “What Are Dice?” and “What’s This For?”, below, for more info. See the header files for each of the classes for more detailed documentation.)

Examples
========

* "1d6"
* Roll a single six-sided die. This expression evaluates to the result of the roll.
* "1d20+5"
* Roll a single twenty-sided die; add 5 to the rolled number and return.
* "4d6"
* Roll four six-sided dice; add up the individual rolls and return the total.
* "5d6-2d20+5"
* Roll five six-sided dice and add up the rolls; roll two twenty-sided dice, add up the two rolls, and subtract from the previous total; add 5 to the result; return.
* “1d6”
* Roll a single six-sided die. This expression evaluates to the result of the roll.
* “1d20+5”
* Roll a single twenty-sided die; add 5 to the rolled number and return.
* “4d6”
* Roll four six-sided dice; add up the individual rolls and return the total.
* “5d6-2d20+5”
* Roll five six-sided dice and add up the rolls; roll two twenty-sided dice, add up the two rolls, and subtract from the previous total; add 5 to the result; return.

What Are Dice?
==============

In real life, a die (https://en.wikipedia.org/wiki/Dice) is a small object, usually shaped as a regular polyhedron, on each face of which is inscribed some sort of symbol. When tossed on a flat surface, a die lands in such a way as to have one face facing up, and therefore showing one of the symbols. Dice are used in gambling, tabletop gaming, etc.

The "digital" implementation of a die is a random number generator, configured (usually) to generate integers in a contiguous interval \[1, n\] (where n is the number of faces the die has), with a uniform distribution over the entire interval. (I say "usually" because some unusual sorts of dice exist, such as Fudge dice (https://en.wikipedia.org/wiki/Fudge_(role-playing_game_system)#Fudge_dice), but those are basically variations on the same theme.)
The “digital” implementation of a die is a random number generator, configured (usually) to generate integers in a contiguous interval \[1, n\] (where n is the number of faces the die has), with a uniform distribution over the entire interval. (I say “usually” because some unusual sorts of dice exist, such as Fudge dice (https://en.wikipedia.org/wiki/Fudge_(role-playing_game_system)#Fudge_dice), but those are basically variations on the same theme.)

What's This For?
Whats This For?
================

The most common use is "dice bots" and "dice rollers", programs designed to simulate the rolling of physical dice. Such programs are often used when playing certain sorts of games, such as tabletop roleplaying games (https://en.wikipedia.org/wiki/Tabletop_role-playing_game) on the internet. Other uses exist as well.
The most common use is “dice bots” and “dice rollers”, programs designed to simulate the rolling of physical dice. Such programs are often used when playing certain sorts of games, such as tabletop roleplaying games (https://en.wikipedia.org/wiki/Tabletop_role-playing_game) on the internet. Other uses exist as well.

SA_Dice is copyright (c) 2016 Said Achmiz. It is licensed under the MIT license. See the file "LICENSE" for more information.
SA_Dice is copyright (c) 2019 Said Achmiz. It is licensed under the MIT license. See the file “LICENSE” for more information.

+ 8
- 0
SA_DB_ErrorDescriptions.plist Переглянути файл

@@ -8,6 +8,10 @@
<string>Illegal characters detected</string>
<key>SA_DB_ERROR_UNKNOWN_ROLL_COMMAND</key>
<string>Unknown roll command</string>
<key>SA_DB_ERROR_ROLL_MODIFIER_INAPPLICABLE</key>
<string>Roll modifier cannot be applied</string>
<key>SA_DB_ERROR_UNKNOWN_ROLL_MODIFIER</key>
<string>Unknown roll modifier</string>
<key>SA_DB_ERROR_DIE_COUNT_NEGATIVE</key>
<string>Die count negative</string>
<key>SA_DB_ERROR_DIE_COUNT_EXCESSIVE</key>
@@ -34,5 +38,9 @@
<string>Integer overflow during multiplication</string>
<key>SA_DB_ERROR_INTEGER_UNDERFLOW_MULTIPLICATION</key>
<string>Integer underflow during multiplication</string>
<key>SA_DB_ERROR_KEEP_COUNT_EXCEEDS_ROLL_COUNT</key>
<string>Can’t keep more rolls than were made</string>
<key>SA_DB_ERROR_KEEP_COUNT_NEGATIVE</key>
<string>Can’t keep a negative number of rolls</string>
</dict>
</plist>

+ 30
- 6
SA_DB_StringFormatRules.plist Переглянути файл

@@ -5,7 +5,7 @@
<key>SA_DB_VALID_CHARACTERS</key>
<dict>
<key>SA_DB_VALID_NUMERAL_CHARACTERS</key>
<string>0123456789</string>
<string>0123456789fF</string>
<key>SA_DB_VALID_OPERATOR_CHARACTERS</key>
<dict>
<key>SA_DB_OPERATOR_MINUS</key>
@@ -16,21 +16,45 @@
<string>*×⋅·</string>
</dict>
<key>SA_DB_VALID_ROLL_COMMAND_DELIMITER_CHARACTERS</key>
<string>dD</string>
<dict>
<key>SA_DB_ROLL_COMMAND_SUM</key>
<string>dD</string>
<key>SA_DB_ROLL_COMMAND_SUM_EXPLODING</key>
<string>eE</string>
</dict>
<key>SA_DB_VALID_ROLL_MODIFIER_DELIMITER_CHARACTERS</key>
<dict>
<key>SA_DB_ROLL_MODIFIER_KEEP_HIGHEST</key>
<string>kK</string>
<key>SA_DB_ROLL_MODIFIER_KEEP_LOWEST</key>
<string>lL</string>
</dict>
</dict>
<key>SA_DB_CANONICAL_REPRESENTATIONS</key>
<dict>
<key>SA_DB_CANONICAL_OPERATOR_REPRESENTATIONS</key>
<dict>
<key>SA_DB_OPERATOR_MINUS</key>
<string>−</string>
<string>-</string>
<key>SA_DB_OPERATOR_PLUS</key>
<string>+</string>
<key>SA_DB_OPERATOR_TIMES</key>
<string>×</string>
<string>*</string>
</dict>
<key>SA_DB_CANONICAL_ROLL_COMMAND_DELIMITER_REPRESENTATIONS</key>
<dict>
<key>SA_DB_ROLL_COMMAND_SUM</key>
<string>d</string>
<key>SA_DB_ROLL_COMMAND_SUM_EXPLODING</key>
<string>e</string>
</dict>
<key>SA_DB_CANONICAL_ROLL_MODIFIER_DELIMITER_REPRESENTATIONS</key>
<dict>
<key>SA_DB_ROLL_MODIFIER_KEEP_HIGHEST</key>
<string>k</string>
<key>SA_DB_ROLL_MODIFIER_KEEP_LOWEST</key>
<string>l</string>
</dict>
<key>SA_DB_CANONICAL_ROLL_COMMAND_DELIMITER_REPRESENTATION</key>
<string>d</string>
</dict>
</dict>
</plist>

+ 25
- 3
SA_DiceBag.h Переглянути файл

@@ -8,6 +8,10 @@

#import <Foundation/Foundation.h>

typedef NS_OPTIONS(NSUInteger, SA_DiceRollingOptions) {
SA_DiceRollingExplodingDice = 1 << 1
};

/****************************************/
#pragma mark SA_DiceBag class declaration
/****************************************/
@@ -18,9 +22,27 @@
#pragma mark - Public methods
/****************************/

- (unsigned long long)biggestPossibleDieSize;
-(NSUInteger) biggestPossibleDieSize;

// -------------
// Regular dice.
// -------------

-(NSUInteger) rollDie:(NSUInteger)dieSize;

-(NSArray <NSNumber *> *) rollNumber:(NSUInteger)number
ofDice:(NSUInteger)dieSize;

-(NSArray <NSNumber *> *) rollNumber:(NSUInteger)number
ofDice:(NSUInteger)dieSize
withOptions:(SA_DiceRollingOptions)options;

// -----------
// Fudge dice.
// -----------

-(char) rollFudgeDie;

- (unsigned long long)rollDie:(unsigned long long)die;
- (NSArray *)rollNumber:(NSNumber *)number ofDice:(unsigned long long)die;
-(NSArray <NSNumber *> *) rollFudgeDice:(NSUInteger)number;

@end

+ 67
- 24
SA_DiceBag.m Переглянути файл

@@ -7,48 +7,91 @@
// See the file "LICENSE" for more information.

#import "SA_DiceBag.h"
#import <GameplayKit/GameplayKit.h>

/*******************************************/
#pragma mark SA_DiceBag class implementation
/*******************************************/

@implementation SA_DiceBag
@implementation SA_DiceBag {
GKRandomSource *_randomSource;

// NSMutableDictionary <NSNumber *, GKRandomDistribution *> *_dice;
}

-(instancetype) init {
self = [super init];
if (!self) return nil;

_randomSource = [GKMersenneTwisterRandomSource new];

// _dice = [NSMutableDictionary dictionary];

return self;
}

/****************************/
#pragma mark - Public methods
/****************************/

- (unsigned long long)biggestPossibleDieSize
{
return UINT32_MAX;
-(NSUInteger) biggestPossibleDieSize {
return NSUIntegerMax;
}

- (unsigned long long)rollDie:(unsigned long long)dieSize
{
if(dieSize > UINT32_MAX)
{
return -1;
}
-(NSUInteger) rollDie:(NSUInteger)dieSize {
return [_randomSource nextIntWithUpperBound:dieSize] + 1;
// return [[self dieOfSize:dieSize] nextInt];
}

return (unsigned long long) arc4random_uniform((u_int32_t) dieSize) + 1;
-(NSArray <NSNumber *> *) rollNumber:(NSUInteger)number
ofDice:(NSUInteger)dieSize {
return [self rollNumber:number
ofDice:dieSize
withOptions:0];
}

- (NSArray *)rollNumber:(NSNumber *)number ofDice:(unsigned long long)dieSize
{
if(dieSize > UINT32_MAX)
{
return nil;
-(NSArray <NSNumber *> *) rollNumber:(NSUInteger)number
ofDice:(NSUInteger)dieSize
withOptions:(SA_DiceRollingOptions)options {
NSMutableArray *rollsArray = [NSMutableArray arrayWithCapacity:number];

for (NSUInteger i = 0; i < number; i++) {
NSUInteger dieRoll;
do {
dieRoll = [self rollDie:dieSize];
[rollsArray addObject:@(dieRoll)];
} while ((options & SA_DiceRollingExplodingDice)
&& dieSize > 1
&& dieRoll == dieSize);
}
unsigned long long numRolls = number.unsignedLongLongValue;
NSMutableArray *rollsArray = [NSMutableArray arrayWithCapacity:numRolls];
for(unsigned long long i = 0; i < numRolls; i++)
{
[rollsArray addObject:@((unsigned long long) arc4random_uniform((u_int32_t) dieSize) + 1)];

return rollsArray;
}

-(char) rollFudgeDie {
NSInteger d3roll = [self rollDie:3];
return (char) (d3roll - 2);
}

-(NSArray <NSNumber *> *) rollFudgeDice:(NSUInteger)number {
NSMutableArray *rollsArray = [NSMutableArray arrayWithCapacity:number];

for (NSUInteger i = 0; i < number; i++) {
[rollsArray addObject:@([self rollFudgeDie])];
}
return rollsArray;
}

/****************************/
#pragma mark - Helper methods
/****************************/

//-(GKRandomDistribution *) dieOfSize:(NSUInteger) dieSize {
// if (_dice[@(dieSize)] == nil)
// _dice[@(dieSize)] = [GKRandomDistribution distributionForDieWithSideCount:dieSize];
//
// return _dice[@(dieSize)];
//}

@end

+ 5
- 3
SA_DiceComparators.h Переглянути файл

@@ -11,7 +11,9 @@
#ifndef SA_DiceComparators_h
#define SA_DiceComparators_h

NSComparisonResult compareEvaluatedExpressionsByResult(NSDictionary* expression1, NSDictionary *expression2);
NSComparisonResult compareEvaluatedExpressionsByAttemptBonus(NSDictionary* expression1, NSDictionary *expression2);
NSComparisonResult compareEvaluatedExpressionsByResult(NSDictionary* expression1,
NSDictionary *expression2);
NSComparisonResult compareEvaluatedExpressionsByAttemptBonus(NSDictionary* expression1,
NSDictionary *expression2);

#endif /* SA_DiceComparators_h */
#endif /* SA_DiceComparators_h */

+ 10
- 20
SA_DiceComparators.m Переглянути файл

@@ -10,34 +10,24 @@

#import "SA_DiceExpressionStringConstants.h"

NSComparisonResult compareEvaluatedExpressionsByResult(NSDictionary* expression1, NSDictionary *expression2)
{
if([expression1[SA_DB_RESULT] integerValue] < [expression2[SA_DB_RESULT] integerValue])
{
NSComparisonResult compareEvaluatedExpressionsByResult(NSDictionary* expression1,
NSDictionary *expression2) {
if ([expression1[SA_DB_RESULT] integerValue] < [expression2[SA_DB_RESULT] integerValue]) {
return NSOrderedAscending;
}
else if([expression1[SA_DB_RESULT] integerValue] > [expression2[SA_DB_RESULT] integerValue])
{
} else if ([expression1[SA_DB_RESULT] integerValue] > [expression2[SA_DB_RESULT] integerValue]) {
return NSOrderedDescending;
}
else
{
} else {
return NSOrderedSame;
}
}

NSComparisonResult compareEvaluatedExpressionsByAttemptBonus(NSDictionary* expression1, NSDictionary *expression2)
{
if([expression1[SA_DB_OPERAND_RIGHT][SA_DB_RESULT] integerValue] < [expression2[SA_DB_OPERAND_RIGHT][SA_DB_RESULT] integerValue])
{
NSComparisonResult compareEvaluatedExpressionsByAttemptBonus(NSDictionary* expression1,
NSDictionary *expression2) {
if ([expression1[SA_DB_OPERAND_RIGHT][SA_DB_RESULT] integerValue] < [expression2[SA_DB_OPERAND_RIGHT][SA_DB_RESULT] integerValue]) {
return NSOrderedAscending;
}
else if([expression1[SA_DB_OPERAND_RIGHT][SA_DB_RESULT] integerValue] > [expression2[SA_DB_OPERAND_RIGHT][SA_DB_RESULT] integerValue])
{
} else if ([expression1[SA_DB_OPERAND_RIGHT][SA_DB_RESULT] integerValue] > [expression2[SA_DB_OPERAND_RIGHT][SA_DB_RESULT] integerValue]) {
return NSOrderedDescending;
}
else
{
} else {
return NSOrderedSame;
}
}

+ 10
- 7
SA_DiceErrorHandling.h Переглянути файл

@@ -6,13 +6,16 @@
// This software is licensed under the MIT license.
// See the file "LICENSE" for more information.

#ifndef SA_DiceErrorHandling_h
#define SA_DiceErrorHandling_h

#import <Foundation/Foundation.h>

void addErrorToExpression (NSString *error, NSMutableDictionary *expression);
void addErrorsFromExpressionToExpression (NSDictionary *sourceExpression, NSMutableDictionary *targetExpression);
NSArray <NSString *> *getErrorsForExpression (NSDictionary *expression);
@interface SA_DiceErrorHandler : NSObject

+(void) addError:(NSString *)error
toExpression:(NSMutableDictionary *)expression;

+(void) addErrorsFromExpression:(NSDictionary *)sourceExpression
toExpression:(NSMutableDictionary *)targetExpression;

+(NSArray <NSString *> *) errorsForExpression:(NSDictionary *)expression;

#endif /* SA_DiceErrorHandling_h */
@end

+ 19
- 25
SA_DiceErrorHandling.m Переглянути файл

@@ -10,47 +10,41 @@

#import "SA_DiceExpressionStringConstants.h"

void addErrorToExpression (NSString *error, NSMutableDictionary *expression)
{
if(error == nil || expression == nil)
{
@implementation SA_DiceErrorHandler

+(void) addError:(NSString *)error
toExpression:(NSMutableDictionary *)expression {
if (error == nil || expression == nil) {
return;
}
if(expression[SA_DB_ERRORS] == nil)
{

if (expression[SA_DB_ERRORS] == nil) {
expression[SA_DB_ERRORS] = [NSMutableArray <NSString *> arrayWithObjects:error, nil];
}
else
{
} else {
[expression[SA_DB_ERRORS] addObject:error];
}
}

// Top-level errors only (i.e. the expression tree is not traversed in search
// of deeper errors).
void addErrorsFromExpressionToExpression (NSDictionary *sourceExpression, NSMutableDictionary *targetExpression)
{
if(sourceExpression == nil || targetExpression == nil)
{
+(void) addErrorsFromExpression:(NSDictionary *)sourceExpression
toExpression:(NSMutableDictionary *)targetExpression {
if (sourceExpression == nil || targetExpression == nil) {
return;
}
if(sourceExpression[SA_DB_ERRORS] == nil || [sourceExpression[SA_DB_ERRORS] count] == 0)
{

if (sourceExpression[SA_DB_ERRORS] == nil || [sourceExpression[SA_DB_ERRORS] count] == 0) {
// Do absolutely nothing; no errors to add.
}
else if(targetExpression[SA_DB_ERRORS] == nil)
{
} else if(targetExpression[SA_DB_ERRORS] == nil) {
targetExpression[SA_DB_ERRORS] = [NSMutableArray <NSString *> arrayWithArray:sourceExpression[SA_DB_ERRORS]];
}
else
{
} else {
[targetExpression[SA_DB_ERRORS] addObjectsFromArray:sourceExpression[SA_DB_ERRORS]];
}
}

NSArray <NSString *> *getErrorsForExpression (NSDictionary *expression)
{
+(NSArray <NSString *> *) errorsForExpression:(NSDictionary *)expression {
return ([expression[SA_DB_ERRORS] count] > 0) ? expression[SA_DB_ERRORS] : nil;
}


@end

+ 4
- 5
SA_DiceEvaluator.h Переглянути файл

@@ -9,6 +9,7 @@
#import <Foundation/Foundation.h>

@class SA_DiceBag;
@class SA_DiceExpression;

/************************************************/
#pragma mark SA_DiceEvaluator class declaration
@@ -20,15 +21,13 @@
#pragma mark - Properties
/************************/

@property NSInteger maxDieCount;
@property NSInteger maxDieSize;

@property (strong) SA_DiceBag *diceBag;
@property NSUInteger maxDieCount;
@property NSUInteger maxDieSize;

/****************************/
#pragma mark - Public methods
/****************************/

- (NSMutableDictionary *)resultOfExpression:(NSDictionary *)expression;
-(SA_DiceExpression *) resultOfExpression:(SA_DiceExpression *)expression;

@end

+ 292
- 255
SA_DiceEvaluator.m Переглянути файл

@@ -10,8 +10,10 @@

#import "SA_DiceBag.h"
#import "SA_DiceParser.h"
#import "SA_DiceExpression.h"
#import "SA_DiceExpressionStringConstants.h"
#import "SA_DiceErrorHandling.h"

#import "SA_Utility.h"

/**************************/
#pragma mark Defined values
@@ -24,52 +26,36 @@
#pragma mark - SA_DiceEvaluator class implementation
/***************************************************/

@implementation SA_DiceEvaluator
{
NSInteger _maxDieCount;
NSInteger _maxDieSize;
@implementation SA_DiceEvaluator {
SA_DiceBag *_diceBag;

NSUInteger _maxDieCount;
NSUInteger _maxDieSize;
}

/************************/
#pragma mark - Properties
/************************/

- (NSInteger)maxDieCount
{
-(NSUInteger) maxDieCount {
return _maxDieCount;
}
- (void)setMaxDieCount:(NSInteger)maxDieCount
{
if(maxDieCount < 1)
{
_maxDieCount = 1;
}
else if(maxDieCount > NSIntegerMax / _maxDieSize)
{
_maxDieCount = NSIntegerMax / _maxDieSize;
}
else
{
-(void) setMaxDieCount:(NSUInteger)maxDieCount {
if (maxDieCount > NSUIntegerMax / _maxDieSize) {
_maxDieCount = NSUIntegerMax / _maxDieSize;
} else {
_maxDieCount = maxDieCount;
}
}

- (NSInteger)maxDieSize
{
-(NSUInteger) maxDieSize {
return _maxDieSize;
}
- (void)setMaxDieSize:(NSInteger)maxDieSize
{
if(maxDieSize < 1)
{
_maxDieSize = 1;
}
else if(maxDieSize > [self.diceBag biggestPossibleDieSize] || maxDieSize > NSIntegerMax / _maxDieCount)
{
_maxDieSize = ([self.diceBag biggestPossibleDieSize] < (NSIntegerMax / _maxDieCount)) ? [_diceBag biggestPossibleDieSize] : NSIntegerMax / _maxDieCount;
}
else
{
-(void) setMaxDieSize:(NSUInteger)maxDieSize {
if (maxDieSize > [_diceBag biggestPossibleDieSize] ||
maxDieSize > NSIntegerMax / _maxDieCount) {
_maxDieSize = ([_diceBag biggestPossibleDieSize] < (NSIntegerMax / _maxDieCount)) ? [_diceBag biggestPossibleDieSize] : NSIntegerMax / _maxDieCount;
} else {
_maxDieSize = maxDieSize;
}
}
@@ -78,14 +64,12 @@
#pragma mark - Initializers
/**************************/

- (instancetype)init
{
if(self = [super init])
{
-(instancetype) init {
if (self = [super init]) {
_maxDieCount = DEFAULT_MAX_DIE_COUNT;
_maxDieSize = DEFAULT_MAX_DIE_SIZE;
self.diceBag = [[SA_DiceBag alloc] init];
_diceBag = [SA_DiceBag new];
}
return self;
}
@@ -94,19 +78,17 @@
#pragma mark - Public methods
/****************************/

- (NSMutableDictionary *)resultOfExpression:(NSDictionary *)expression
{
-(SA_DiceExpression *) resultOfExpression:(SA_DiceExpression *)expression {
// Check to see if the expression is erroneous (i.e. the parser has judged
// that it is malformed, etc.). If so, decline to evaluate the expression;
// return (a mutable copy of) it, unchanged.
if(getErrorsForExpression(expression) != nil)
{
return [expression mutableCopy];
// return (a copy of) it, unchanged.
if (expression.errorBitMask != 0) {
return [expression copy];
}
/*
Even if an expression is not erroneous (i.e. if it has no syntax errors),
it may still not be possible to evaluate it. For example, '5d0' is a
it may still not be possible to evaluate it. For example, ‘5d0’ is a
perfectly well-formed die string, and will yield an expression tree as follows:
@{SA_DB_TERM_TYPE : SA_DB_TERM_TYPE_ROLL_COMMAND,
@@ -119,29 +101,32 @@
}
}
This is, of course, an illegal expression; we can't roll a die of size 0
This is, of course, an illegal expression; we can’t roll a die of size 0
(a die with zero sides?).
If we encounter such an illegal expression, we add an appropriate error to
the term. We are not required to set a value for the SA_DB_RESULT key in
such a case.
*/
// Check to see if the current term is an operation.
if([expression[SA_DB_TERM_TYPE] isEqualToString:SA_DB_TERM_TYPE_OPERATION])
{
return [self resultOfExpressionDescribingOperation:expression];
}
// Check to see if the current term is a roll command.
else if([expression[SA_DB_TERM_TYPE] isEqualToString:SA_DB_TERM_TYPE_ROLL_COMMAND])
{
return [self resultOfExpressionDescribingRollCommand:expression];
}
// If not an operation or a roll command, the current term can only be a
// simple value expression (term type of SA_DB_TERM_TYPE_VALUE).
else
{
return [self resultOfExpressionDescribingValue:expression];

switch (expression.type) {
case SA_DiceExpressionTerm_OPERATION: {
return [self resultOfExpressionDescribingOperation:expression];
break;
}
case SA_DiceExpressionTerm_ROLL_COMMAND: {
return [self resultOfExpressionDescribingRollCommand:expression];
break;
}
case SA_DiceExpressionTerm_ROLL_MODIFIER: {
return [self resultOfExpressionDescribingRollModifier:expression];
break;
}
case SA_DiceExpressionTerm_VALUE:
default: {
return [self resultOfExpressionDescribingValue:expression];
break;
}
}
}

@@ -149,223 +134,275 @@
#pragma mark - Helper methods
/****************************/

- (NSMutableDictionary *)resultOfExpressionDescribingValue:(NSDictionary *)expression
{
NSMutableDictionary *result = [expression mutableCopy];
-(SA_DiceExpression *) resultOfExpressionDescribingValue:(SA_DiceExpression *)expression {
SA_DiceExpression *result = [expression copy];
result[SA_DB_RESULT] = result[SA_DB_VALUE];
result.result = result.value;
return result;
}

- (NSMutableDictionary *)resultOfExpressionDescribingRollCommand:(NSDictionary *)expression
{
NSMutableDictionary *result = [expression mutableCopy];
// For now, only a simple sum is supported. Other sorts of roll commands
// may be added later.
if([result[SA_DB_ROLL_COMMAND] isEqualToString:SA_DB_ROLL_COMMAND_SUM])
{
// First, recursively evaluate the expressions that represent the
// die size and die count.
result[SA_DB_ROLL_DIE_COUNT] = [self resultOfExpression:result[SA_DB_ROLL_DIE_COUNT]];
result[SA_DB_ROLL_DIE_SIZE] = [self resultOfExpression:result[SA_DB_ROLL_DIE_SIZE]];
// Evaluating those expressions may have generated an error(s);
// propagate any errors up to the current term.
addErrorsFromExpressionToExpression(result[SA_DB_ROLL_DIE_COUNT], result);
addErrorsFromExpressionToExpression(result[SA_DB_ROLL_DIE_SIZE], result);
// If indeed we've turned up errors, return.
if(getErrorsForExpression(result) != nil)
{
return result;
}
// Evaluating the two sub-expressions didn't generate errors; this means
// that we have successfully generated values for both the die count and
// the die size...
NSInteger dieCount = [result[SA_DB_ROLL_DIE_COUNT][SA_DB_RESULT] integerValue];
NSInteger dieSize = [result[SA_DB_ROLL_DIE_SIZE][SA_DB_RESULT] integerValue];
// ... but, the resulting values of the expressions may make it
// impossible to evaluate the roll command. Check to see whether the die
// count and die size have legal values.
if(dieCount < 0)
{
addErrorToExpression(SA_DB_ERROR_DIE_COUNT_NEGATIVE, result);
}
else if(dieCount > _maxDieCount)
{
addErrorToExpression(SA_DB_ERROR_DIE_COUNT_EXCESSIVE, result);
}
if(dieSize < 1)
{
addErrorToExpression(SA_DB_ERROR_DIE_SIZE_INVALID, result);
}
else if(dieSize > _maxDieSize)
{
addErrorToExpression(SA_DB_ERROR_DIE_SIZE_EXCESSIVE, result);
}
// If indeed the die count or die size fall outside of their allowed
// ranges, return.
if(getErrorsForExpression(result) != nil)
{
return result;
-(SA_DiceExpression *) resultOfExpressionDescribingRollCommand:(SA_DiceExpression *)expression {
SA_DiceExpression *result = [expression copy];

// For now, only sum and exploding sum (i.e., sum but with exploding dice)
// are supported Other sorts of roll commands may be added later.
switch (result.rollCommand) {
case SA_DiceExpressionRollCommand_SUM:
case SA_DiceExpressionRollCommand_SUM_EXPLODING: {
// First, recursively evaluate the expressions that represent the
// die count and (for standard dice) the die size.
result.dieCount = [self resultOfExpression:result.dieCount];
if (result.dieType == SA_DiceExpressionDice_STANDARD)
result.dieSize = [self resultOfExpression:result.dieSize];

// Evaluating those expressions may have generated an error(s);
// propagate any errors up to the current term.
result.errorBitMask |= result.dieCount.errorBitMask;
if (result.dieType == SA_DiceExpressionDice_STANDARD)
result.errorBitMask |= result.dieSize.errorBitMask;

// If indeed we’ve turned up errors, return.
if (result.errorBitMask != 0)
return result;

// Evaluating the two sub-expressions didn’t generate errors; this means
// that we have successfully generated values for both the die count and
// (for standard dice) the die size...
NSInteger dieCount = result.dieCount.result.integerValue;
NSInteger dieSize = 0;
if (result.dieType == SA_DiceExpressionDice_STANDARD)
dieSize = result.dieSize.result.integerValue;

// ... but, the resulting values of the expressions may make it
// impossible to evaluate the roll command. Check to see whether the die
// count and die size have legal values.
if (dieCount < 0) {
result.errorBitMask |= SA_DiceExpressionError_DIE_COUNT_NEGATIVE;
} else if (dieCount > _maxDieCount) {
result.errorBitMask |= SA_DiceExpressionError_DIE_COUNT_EXCESSIVE;
}

// Die type only matters for standard dice, not for Fudge dice.
if (result.dieType == SA_DiceExpressionDice_STANDARD) {
if (dieSize < 1) {
result.errorBitMask |= SA_DiceExpressionError_DIE_SIZE_INVALID;
} else if (dieSize > _maxDieSize) {
result.errorBitMask |= SA_DiceExpressionError_DIE_SIZE_EXCESSIVE;
}
}

// If indeed the die count or die size fall outside of their allowed
// ranges, return.
if (result.errorBitMask != 0)
return result;

// The die count and die size have legal values. We can safely roll the
// requisite number of dice, and take the sum of the rolls (if needed).
// NOTE: _maxDieSize is guaranteed to be no greater than the largest die
// size that the SA_DiceBag can roll (this is enforced by the setter
// method for the maxDieSize property), so we need not check to see
// if the return value of rollDie: or rollNumber:ofDice: is valid.
// We are also guaranteed that the product of _maxDieCount and
// _maxDieSize is no greater than the largest unsigned value that can be
// stored by whatever numeric type we specify simple value terms (terms
// of type SA_DiceExpressionTerm_VALUE) to contain (likewise enforced
// by the setters for both maxDieSize and maxDieCount), therefore we
// need not worry about overflow here.
if (dieCount == 0) {
result.result = @(0);
result.rolls = @[];
} else {
NSArray *rolls;
if (result.dieType == SA_DiceExpressionDice_STANDARD) {
SA_DiceRollingOptions options = (result.rollCommand == SA_DiceExpressionRollCommand_SUM_EXPLODING) ? SA_DiceRollingExplodingDice : 0;
rolls = [_diceBag rollNumber:dieCount
ofDice:dieSize
withOptions:options];
} else if (result.dieType == SA_DiceExpressionDice_FUDGE) {
rolls = [_diceBag rollFudgeDice:dieCount];
}

result.result = [rolls valueForKeyPath:@"@sum.self"];
result.rolls = rolls;
}

break;
}
// The die count and die size have legal values. We can safely roll the
// requisite number of dice, and take the sum of the rolls (if needed).
// NOTE: _maxDieSize is gauranteed to be no greater than the largest die
// size that the SA_DiceBag can roll (this is enforced by the setter
// method for the maxDieSize property), so we need not check to see
// if the return value of rollDie: or rollNumber:ofDice: is valid.
// We are also gauranteed that the product of _maxDieCount and
// _maxDieSize is no greater than the largest unsigned value that can be
// stored by whatever numeric type we specify simple value terms (terms
// of type SA_DB_TERM_TYPE_VALUE) to contain (likewise enforced by the
// setters for both maxDieSize and maxDieCount), therefore we need not
// worry about overflow here.
if(dieCount == 0)
{
result[SA_DB_RESULT] = @(0);
result[SA_DB_ROLLS] = @[];
default: {
result.errorBitMask |= SA_DiceExpressionError_UNKNOWN_ROLL_COMMAND;

break;
}
else if(dieCount == 1)
{
NSNumber *roll = @([self.diceBag rollDie:dieSize]);
result[SA_DB_RESULT] = roll;
result[SA_DB_ROLLS] = @[roll];
}

return result;
}

-(SA_DiceExpression *) resultOfExpressionDescribingRollModifier:(SA_DiceExpression *)expression {
SA_DiceExpression *result = [expression copy];

switch (result.rollModifier) {
case SA_DiceExpressionRollModifier_KEEP_HIGHEST:
case SA_DiceExpressionRollModifier_KEEP_LOWEST: {
// These roll modifiers takes the highest, or the lowest, N rolls
// out of all the rolls generated by a roll command, discarding the
// rest, and summing the kept ones.

// First, check if the left-hand operand is a roll command (and
// specifically, a simple sum; though this latter requirement may
// be dropped later).
// TODO: re-evaluate this ^
// If the left-hand operand is not a roll-and-sum, then the KEEP
// modifier cannot be applied to it. In that case, we add an error
// and return the result without evaluating.
if (result.leftOperand.type != SA_DiceExpressionTerm_ROLL_COMMAND ||
(result.leftOperand.rollCommand != SA_DiceExpressionRollCommand_SUM &&
result.leftOperand.rollCommand != SA_DiceExpressionRollCommand_SUM_EXPLODING)) {
result.errorBitMask |= SA_DiceExpressionError_ROLL_MODIFIER_INAPPLICABLE;
return result;
}

// We now know the left-hand operand is a roll command. Recursively
// evaluate the expressions that represent the roll command and the
// modifier value (the right-hand operand).
result.leftOperand = [self resultOfExpression:result.leftOperand];
result.rightOperand = [self resultOfExpression:result.rightOperand];

// Evaluating the operands may have generated an error(s); propagate any
// errors up to the current term.
result.errorBitMask |= result.leftOperand.errorBitMask;
result.errorBitMask |= result.rightOperand.errorBitMask;

// If indeed we’ve turned up errors, return.
if (result.errorBitMask != 0)
return result;

// Evaluating the operands didn’t generate any errors; this means
// that on the left hand we have a set of rolls (as well as a
// result, which we are ignoring), and on the right hand we have a
// result, which specifies how many rolls to keep.
NSArray <NSNumber *> *rolls = result.leftOperand.rolls;
NSNumber *keepHowMany = result.rightOperand.result;

// However, it is now possible that the “keep how many” value
// exceeds the number of rolls, which would make the expression
// incoherent. If so, add an error and return.
if (keepHowMany.unsignedIntegerValue > rolls.count) {
result.errorBitMask |= SA_DiceExpressionError_KEEP_COUNT_EXCEEDS_ROLL_COUNT;
return result;
}

// It is also possible that the “keep how many” value is negative.
// This, too, would make the expression incoherent. Likewise, add
// an error and return.
if (keepHowMany < 0) {
result.errorBitMask |= SA_DiceExpressionError_KEEP_COUNT_NEGATIVE;
return result;
}

// We sort the rolls array...
BOOL sortAscending = (result.rollModifier == SA_DiceExpressionRollModifier_KEEP_LOWEST);
result.rolls = [rolls sortedArrayUsingDescriptors:@[ [NSSortDescriptor sortDescriptorWithKey:@"integerValue"
ascending:sortAscending] ]];

// And the ‘result’ property of the result expression is the sum of
// the first <keepHowMany> elements of the sorted rolls array.
result.result = [[result.rolls subarrayWithRange:NSRangeMake(0, keepHowMany.unsignedIntegerValue)] valueForKeyPath:@"@sum.self"];

break;
}
else
{
NSArray *rolls = [self.diceBag rollNumber:@(dieCount) ofDice:dieSize];
result[SA_DB_RESULT] = [rolls valueForKeyPath:@"@sum.self"];
result[SA_DB_ROLLS] = rolls;
default: {
result.errorBitMask |= SA_DiceExpressionError_UNKNOWN_ROLL_MODIFIER;

break;
}
// Return the successfully evaluated roll command expression.
return result;
}
else
{
addErrorToExpression(SA_DB_ERROR_UNKNOWN_ROLL_COMMAND, result);
return result;
}
return result;
}

- (NSMutableDictionary *)resultOfExpressionDescribingOperation:(NSDictionary *)expression
{
NSMutableDictionary *result = [expression mutableCopy];
-(SA_DiceExpression *) resultOfExpressionDescribingOperation:(SA_DiceExpression *)expression {
SA_DiceExpression *result = [expression copy];
// First, recursively evaluate the expressions that represent the
// left-hand-side and right-hand-side operands.
result[SA_DB_OPERAND_LEFT] = [self resultOfExpression:result[SA_DB_OPERAND_LEFT]];
result[SA_DB_OPERAND_RIGHT] = [self resultOfExpression:result[SA_DB_OPERAND_RIGHT]];
result.leftOperand = [self resultOfExpression:result.leftOperand];
result.rightOperand = [self resultOfExpression:result.rightOperand];
// Evaluating the operand may have generated an error(s); propagate any
// Evaluating the operands may have generated an error(s); propagate any
// errors up to the current term.
addErrorsFromExpressionToExpression(result[SA_DB_OPERAND_LEFT], result);
addErrorsFromExpressionToExpression(result[SA_DB_OPERAND_RIGHT], result);
// If indeed we've turned up errors, return.
if(getErrorsForExpression(result) != nil)
{
result.errorBitMask |= result.leftOperand.errorBitMask;
result.errorBitMask |= result.rightOperand.errorBitMask;

// If indeed we’ve turned up errors, return.
if (result.errorBitMask != 0)
return result;
}
// Evaluating the operands didn't generate any errors. We have valid

// Evaluating the operands didn’t generate any errors. We have valid
// operands.
NSInteger leftOperand = [result[SA_DB_OPERAND_LEFT][SA_DB_RESULT] integerValue];
NSInteger rightOperand = [result[SA_DB_OPERAND_RIGHT][SA_DB_RESULT] integerValue];
// Check to see if the operation is subtraction.
if([result[SA_DB_OPERATOR] isEqualToString:SA_DB_OPERATOR_MINUS])
{
// First, we check for possible overflow...
if(leftOperand > 0 && rightOperand < 0 && NSIntegerMax + rightOperand < leftOperand)
{
addErrorToExpression(SA_DB_ERROR_INTEGER_OVERFLOW_SUBTRACTION, result);
return result;
}
else if(leftOperand < 0 && rightOperand > 0 && NSIntegerMin + rightOperand > leftOperand)
{
addErrorToExpression(SA_DB_ERROR_INTEGER_UNDERFLOW_SUBTRACTION, result);
return result;
}
// No overflow will occur. We can perform the subtraction operation.
result[SA_DB_RESULT] = @(leftOperand - rightOperand);
// Return the successfully evaluated subtraction expression.
return result;
}
// Check to see if the operation is addition.
else if([result[SA_DB_OPERATOR] isEqualToString:SA_DB_OPERATOR_PLUS])
{
// First, we check for possible overflow...
if(rightOperand > 0 && leftOperand > 0 && NSIntegerMax - rightOperand < leftOperand)
{
addErrorToExpression(SA_DB_ERROR_INTEGER_OVERFLOW_ADDITION, result);
return result;
}
else if(rightOperand < 0 && leftOperand < 0 && NSIntegerMin - rightOperand > leftOperand)
{
addErrorToExpression(SA_DB_ERROR_INTEGER_UNDERFLOW_ADDITION, result);
return result;
NSInteger leftOperand = result.leftOperand.result.integerValue;
NSInteger rightOperand = result.rightOperand.result.integerValue;

switch (result.operator) {
case SA_DiceExpressionOperator_MINUS: {
// First, we check for possible overflow...
if (leftOperand > 0 && rightOperand < 0 &&
NSIntegerMax + rightOperand < leftOperand) {
result.errorBitMask |= SA_DiceExpressionError_INTEGER_OVERFLOW_SUBTRACTION;
break;
} else if (leftOperand < 0 && rightOperand > 0 &&
NSIntegerMin + rightOperand > leftOperand) {
result.errorBitMask |= SA_DiceExpressionError_INTEGER_UNDERFLOW_SUBTRACTION;
break;
}

// No overflow will occur. We can perform the subtraction operation.
result.result = @(leftOperand - rightOperand);
break;
}
// No overflow will occur. We can perform the addition operation.
result[SA_DB_RESULT] = @(leftOperand + rightOperand);
// Return the successfully evaluated addition expression.
return result;
}
// Check to see if the operation is multiplication.
else if([result[SA_DB_OPERATOR] isEqualToString:SA_DB_OPERATOR_TIMES])
{
// First, we check for possible overflow...
if( ( leftOperand == NSIntegerMin && ( rightOperand != 0 || rightOperand != 1 ) ) ||
( rightOperand == NSIntegerMin && ( leftOperand != 0 || leftOperand != 1 ) ) ||
( leftOperand != 0 && ( (NSIntegerMax / ABS(leftOperand)) < rightOperand ) ) )
{
if((leftOperand > 0 && rightOperand > 0) || (leftOperand < 0 && rightOperand < 0))
{
addErrorToExpression(SA_DB_ERROR_INTEGER_OVERFLOW_MULTIPLICATION, result);
case SA_DiceExpressionOperator_PLUS: {
// First, we check for possible overflow...
if (rightOperand > 0 && leftOperand > 0 &&
NSIntegerMax - rightOperand < leftOperand) {
result.errorBitMask |= SA_DiceExpressionError_INTEGER_OVERFLOW_ADDITION;
break;
} else if(rightOperand < 0 && leftOperand < 0 && NSIntegerMin - rightOperand > leftOperand) {
result.errorBitMask |= SA_DiceExpressionError_INTEGER_UNDERFLOW_ADDITION;
break;
}
else
{
addErrorToExpression(SA_DB_ERROR_INTEGER_UNDERFLOW_MULTIPLICATION, result);

// No overflow will occur. We can perform the addition operation.
result.result = @(leftOperand + rightOperand);
break;
}
case SA_DiceExpressionOperator_TIMES: {
// First, we check for possible overflow...
if (( leftOperand == NSIntegerMin && ( rightOperand != 0 || rightOperand != 1 )) ||
( rightOperand == NSIntegerMin && ( leftOperand != 0 || leftOperand != 1 )) ||
( leftOperand != 0 && ( (NSIntegerMax / ABS(leftOperand)) < rightOperand ))) {
if ((leftOperand > 0 && rightOperand > 0) ||
(leftOperand < 0 && rightOperand < 0)) {
result.errorBitMask |= SA_DiceExpressionError_INTEGER_OVERFLOW_MULTIPLICATION;
} else {
result.errorBitMask |= SA_DiceExpressionError_INTEGER_UNDERFLOW_MULTIPLICATION;
}
break;
}
return result;

// No overflow will occur. We can perform the multiplication operation.
result.result = @(leftOperand * rightOperand);
break;
}
default: {
// We add the appropriate error. We do not set a value for the
// result property.
result.errorBitMask |= SA_DiceExpressionError_UNKNOWN_OPERATOR;
break;
}
// No overflow will occur. We can perform the multiplication operation.
result[SA_DB_RESULT] = @(leftOperand * rightOperand);
// Return the successfully evaluated multiplication expression.
return result;
}
// The operation is not one of the supported operators.
else
{
// We add the appropriate error. We do not set a value for the
// SA_DB_RESULT key.
addErrorToExpression(SA_DB_ERROR_UNKNOWN_OPERATOR, result);
return result;
}

return result;
}

@end

+ 28
- 106
SA_DiceExpressionStringConstants.h Переглянути файл

@@ -8,100 +8,6 @@

#import <Foundation/Foundation.h>

/***************************************************/
#pragma mark String constants for expression parsing
/***************************************************/

// Each term in an expression (i.e. each node in the expression tree) has a
// value for the SA_DB_TERM_TYPE key, which may be one of the term types listed
// below. SA_DB_TERM_TYPE_NONE is used for terms that fail to be parsed at all,
// because their input strings contain illegal characters or are empty.
extern NSString * const SA_DB_TERM_TYPE; // key
extern NSString * const SA_DB_TERM_TYPE_VALUE; // value
extern NSString * const SA_DB_TERM_TYPE_ROLL_COMMAND; // value
extern NSString * const SA_DB_TERM_TYPE_OPERATION; // value
extern NSString * const SA_DB_TERM_TYPE_NONE; // value

// Terms that are erroneous (malformed or illegal in some way) have a value for
// the SA_DB_ERRORS key. Whether they have values for any other keys is
// undefined. The value for this key is an NSArray of NSString objects, each
// having one of the listed values.
extern NSString * const SA_DB_ERRORS; // key
extern NSString * const SA_DB_ERROR_ROLL_STRING_EMPTY; // value
extern NSString * const SA_DB_ERROR_ROLL_STRING_HAS_ILLEGAL_CHARACTERS; // value

// Terms of type SA_DB_TERM_TYPE_OPERATION (a.k.a. operation expressions) have a
// value for the SA_DB_OPERATOR key, which may be any of the allowed operators
// listed below.
// Operation expressions also have a value for the SA_DB_OPERAND_RIGHT key,
// and, possibly, a value for the SA_DB_OPERAND_LEFT key as well.
// The values for the SA_DB_OPERAND_RIGHT (and, if present, SA_DB_OPERAND_LEFT)
// are themselves expressions of some type (i.e. NSDictionary objects), which
// must be recursively evaluated.
extern NSString * const SA_DB_OPERATOR; // key
extern NSString * const SA_DB_OPERATOR_MINUS; // value
extern NSString * const SA_DB_OPERATOR_PLUS; // value
extern NSString * const SA_DB_OPERATOR_TIMES; // value
extern NSString * const SA_DB_OPERAND_LEFT; // key
extern NSString * const SA_DB_OPERAND_RIGHT; // key

// Terms of type SA_DB_TERM_TYPE_ROLL_COMMAND (a.k.a. roll command expressions)
// have a value for the SA_DB_ROLL_COMMAND key, which may be any of the roll
// commands listed below.
// Roll command expressions also have values for the keys SA_DB_ROLL_DIE_COUNT
// and SA_DB_ROLL_DIE_SIZE, which are themselves expressions of some type
// (i.e. NSDictionary objects), which must be recursively evaluated.
extern NSString * const SA_DB_ROLL_COMMAND; // key
extern NSString * const SA_DB_ROLL_COMMAND_SUM; // value
extern NSString * const SA_DB_ROLL_DIE_COUNT; // key
extern NSString * const SA_DB_ROLL_DIE_SIZE; // key

// Terms of type SA_DB_TERM_TYPE_VALUE (a.k.a. simple value expressions) have a
// value for the SA_DB_VALUE key, which is an NSNumber that represents an
// NSInteger value.
extern NSString * const SA_DB_VALUE; // key

// All terms that were generated via parsing a string should have a value for
// the SA_DB_INPUT_STRING key. This is in order to be able to reconstruct the
// input, for useful output later (i.e., it's used by the results formatter).
// The value is, of course, an NSString.
// Optionally, there may also be a value for the SA_DB_ATTRIBUTED_INPUT_STRING
// key. The current implementation does not set a value for this key, nor is it
// used in any way, but it may be used in future versions or in alternate
// implementations.
extern NSString * const SA_DB_INPUT_STRING; // key
extern NSString * const SA_DB_ATTRIBUTED_INPUT_STRING; // key

/******************************************************/
#pragma mark String constants for expression evaluation
/******************************************************/

// Additional values for key SA_DB_ERROR
extern NSString * const SA_DB_ERROR_UNKNOWN_ROLL_COMMAND; // value
extern NSString * const SA_DB_ERROR_DIE_COUNT_NEGATIVE; // value
extern NSString * const SA_DB_ERROR_DIE_COUNT_EXCESSIVE; // value
extern NSString * const SA_DB_ERROR_DIE_SIZE_INVALID; // value
extern NSString * const SA_DB_ERROR_DIE_SIZE_EXCESSIVE; // value
extern NSString * const SA_DB_ERROR_UNKNOWN_OPERATOR; // value
extern NSString * const SA_DB_ERROR_INVALID_EXPRESSION; // value
extern NSString * const SA_DB_ERROR_INTEGER_OVERFLOW_NEGATION; // value
extern NSString * const SA_DB_ERROR_INTEGER_OVERFLOW_ADDITION; // value
extern NSString * const SA_DB_ERROR_INTEGER_UNDERFLOW_ADDITION; // value
extern NSString * const SA_DB_ERROR_INTEGER_OVERFLOW_SUBTRACTION; // value
extern NSString * const SA_DB_ERROR_INTEGER_UNDERFLOW_SUBTRACTION; // value
extern NSString * const SA_DB_ERROR_INTEGER_OVERFLOW_MULTIPLICATION; // value
extern NSString * const SA_DB_ERROR_INTEGER_UNDERFLOW_MULTIPLICATION; // value

// Successfully evaluated terms (i.e., those that have no errors) have a value
// for the SA_DB_RESULT key. This value is an NSNumber that represents an
// NSInteger value.
extern NSString * const SA_DB_RESULT; // key

// Successfully evaluated roll command terms (i.e. those that have no errors)
// have a value for the SA_DB_ROLLS key. This is an NSArray containing all of
// the individual die rolls that were generated by executing the roll command.
extern NSString * const SA_DB_ROLLS; // key

/***************************************************************/
#pragma mark String constants for retrieving string format rules
/***************************************************************/
@@ -134,17 +40,24 @@ extern NSString * const SA_DB_VALID_NUMERAL_CHARACTERS;
// The value for the key SA_DB_VALID_OPERATOR_CHARACTERS is a dictionary that
// defines those characters that are recognized as valid representations of
// the supported mathematical operators. This dictionary has values for keys
// corresponding to the names of each of the supported operators (see
// the "String constants for expression parsing" section, above).
// corresponding to the names of each of the supported operators.
extern NSString * const SA_DB_VALID_OPERATOR_CHARACTERS;

// The value for the key SA_DB_VALID_ROLL_COMMAND_DELIMITER_CHARACTERS is a
// string that contains all the characters that are recognized as representing
// die roll command delimiters. (Usually this is the lowercase and uppercase
// versions of the letter 'd', as in '1d20' or '4D6'.)
// The value for the key SA_DB_VALID_ROLL_COMMAND_DELIMITER_CHARACTERS is a
// dictionary that defines those characters that are recognized as valid
// representations of delimiters for each of the allowed roll commands. This
// dictionary has values for keys corresponding to the names of each of the
// supported roll modifiers.
extern NSString * const SA_DB_VALID_ROLL_COMMAND_DELIMITER_CHARACTERS;

// The value for the top-level key SA_DB_CANONICAL_REPRESENTATIONS is a
// The value for the key SA_DB_VALID_ROLL_MODIFIER_DELIMITER_CHARACTERS is a
// dictionary that defines those characters that are recognized as valid
// representations of delimiters for each of the allowed roll modifiers. This
// dictionary has values for keys corresponding to the names of each of the
// supported roll modifiers.
extern NSString * const SA_DB_VALID_ROLL_MODIFIER_DELIMITER_CHARACTERS;

// The value for the top-level key SA_DB_CANONICAL_REPRESENTATIONS is a
// dictionary that defines canonical representations of certain components
// of a formatted die roll string. This dictionary has values for the keys
// listed below whose names begin with SA_DB_CANONICAL_.
@@ -154,10 +67,19 @@ extern NSString * const SA_DB_CANONICAL_REPRESENTATIONS;
// dictionary that defines canonical representations of each of the
// supported mathematical operators (for use when formatting results for
// output). This dictionary has values for keys corresponding to the names of
// each of the supported operators (see the "String constants for expression
// parsing" section, above).
// each of the supported operators.
extern NSString * const SA_DB_CANONICAL_OPERATOR_REPRESENTATIONS;

// The value for the key SA_DB_CANONICAL_ROLL_COMMAND_DELIMITER_REPRESENTATION
// is the canonical representation of the die roll command delimiter.
extern NSString * const SA_DB_CANONICAL_ROLL_COMMAND_DELIMITER_REPRESENTATION;
// The value for the key SA_DB_CANONICAL_ROLL_COMMAND_DELIMITER_REPRESENTATIONS
// is a dictionary that defines canonical representations of each of the
// supported roll commands (for use when formatting results for
// output). This dictionary has values for keys corresponding to the names of
// each of the supported roll commands.
extern NSString * const SA_DB_CANONICAL_ROLL_COMMAND_DELIMITER_REPRESENTATIONS;

// The value for the key SA_DB_CANONICAL_ROLL_MODIFIER_REPRESENTATIONS is a
// dictionary that defines canonical representations of each of the
// supported roll modifiers (for use when formatting results for
// output). This dictionary has values for keys corresponding to the names of
// each of the supported roll modifiers.
extern NSString * const SA_DB_CANONICAL_ROLL_MODIFIER_DELIMITER_REPRESENTATIONS;

+ 10
- 68
SA_DiceExpressionStringConstants.m Переглянути файл

@@ -8,77 +8,19 @@

#import "SA_DiceExpressionStringConstants.h"

/***************************************************/
#pragma mark String constants for expression parsing
/***************************************************/

NSString * const SA_DB_TERM_TYPE = @"SA_DB_TERM_TYPE";
NSString * const SA_DB_TERM_TYPE_VALUE = @"SA_DB_TERM_TYPE_VALUE";
NSString * const SA_DB_TERM_TYPE_ROLL_COMMAND = @"SA_DB_TERM_TYPE_ROLL_COMMAND";
NSString * const SA_DB_TERM_TYPE_OPERATION = @"SA_DB_TERM_TYPE_OPERATION";
NSString * const SA_DB_TERM_TYPE_NONE = @"SA_DB_TERM_TYPE_NONE";

NSString * const SA_DB_ERRORS = @"SA_DB_ERRORS";
NSString * const SA_DB_ERROR_ROLL_STRING_EMPTY = @"SA_DB_ERROR_ROLL_STRING_EMPTY";
NSString * const SA_DB_ERROR_ROLL_STRING_HAS_ILLEGAL_CHARACTERS = @"SA_DB_ERROR_ROLL_STRING_HAS_ILLEGAL_CHARACTERS";

NSString * const SA_DB_OPERATOR = @"SA_DB_OPERATOR";
NSString * const SA_DB_OPERATOR_MINUS = @"SA_DB_OPERATOR_MINUS";
NSString * const SA_DB_OPERATOR_PLUS = @"SA_DB_OPERATOR_PLUS";
NSString * const SA_DB_OPERATOR_TIMES = @"SA_DB_OPERATOR_TIMES";
NSString * const SA_DB_OPERAND_LEFT = @"SA_DB_OPERAND_LEFT";
NSString * const SA_DB_OPERAND_RIGHT = @"SA_DB_OPERAND_RIGHT";

NSString * const SA_DB_ROLL_COMMAND = @"SA_DB_ROLL_COMMAND";
NSString * const SA_DB_ROLL_COMMAND_SUM = @"SA_DB_ROLL_COMMAND_SUM";
NSString * const SA_DB_ROLL_DIE_COUNT = @"SA_DB_ROLL_DIE_COUNT";
NSString * const SA_DB_ROLL_DIE_SIZE = @"SA_DB_ROLL_DIE_SIZE";

NSString * const SA_DB_VALUE = @"SA_DB_VALUE";

NSString * const SA_DB_INPUT_STRING = @"SA_DB_INPUT_STRING";
NSString * const SA_DB_ATTRIBUTED_INPUT_STRING = @"SA_DB_ATTRIBUTED_INPUT_STRING";

/******************************************************/
#pragma mark String constants for expression evaluation
/******************************************************/

NSString * const SA_DB_ERROR_UNKNOWN_ROLL_COMMAND = @"SA_DB_ERROR_UNKNOWN_ROLL_COMMAND";
NSString * const SA_DB_ERROR_DIE_COUNT_NEGATIVE = @"SA_DB_ERROR_DIE_COUNT_NEGATIVE";
NSString * const SA_DB_ERROR_DIE_COUNT_EXCESSIVE = @"SA_DB_ERROR_DIE_COUNT_EXCESSIVE";
NSString * const SA_DB_ERROR_DIE_SIZE_INVALID = @"SA_DB_ERROR_DIE_SIZE_INVALID";
NSString * const SA_DB_ERROR_DIE_SIZE_EXCESSIVE = @"SA_DB_ERROR_DIE_SIZE_EXCESSIVE";
NSString * const SA_DB_ERROR_UNKNOWN_OPERATOR = @"SA_DB_ERROR_UNKNOWN_OPERATOR";
NSString * const SA_DB_ERROR_INVALID_EXPRESSION = @"SA_DB_ERROR_INVALID_EXPRESSION";
NSString * const SA_DB_ERROR_INTEGER_OVERFLOW_NEGATION = @"SA_DB_ERROR_INTEGER_OVERFLOW_NEGATION";
NSString * const SA_DB_ERROR_INTEGER_OVERFLOW_ADDITION = @"SA_DB_ERROR_INTEGER_OVERFLOW_ADDITION";
NSString * const SA_DB_ERROR_INTEGER_UNDERFLOW_ADDITION = @"SA_DB_ERROR_INTEGER_UNDERFLOW_ADDITION";
NSString * const SA_DB_ERROR_INTEGER_OVERFLOW_SUBTRACTION = @"SA_DB_ERROR_INTEGER_OVERFLOW_SUBTRACTION";
NSString * const SA_DB_ERROR_INTEGER_UNDERFLOW_SUBTRACTION = @"SA_DB_ERROR_INTEGER_UNDERFLOW_SUBTRACTION";
NSString * const SA_DB_ERROR_INTEGER_OVERFLOW_MULTIPLICATION = @"SA_DB_ERROR_INTEGER_OVERFLOW_MULTIPLICATION";
NSString * const SA_DB_ERROR_INTEGER_UNDERFLOW_MULTIPLICATION = @"SA_DB_ERROR_INTEGER_UNDERFLOW_MULTIPLICATION";

NSString * const SA_DB_RESULT = @"SA_DB_RESULT";

NSString * const SA_DB_ROLLS = @"SA_DB_ROLLS";

/******************************************************/
#pragma mark String constants for expression formatting
/******************************************************/

NSString * const SA_DB_LABEL = @"SA_DB_LABEL";

/***************************************************************/
#pragma mark String constants for retrieving string format rules
/***************************************************************/

NSString * const SA_DB_STRING_FORMAT_RULES_PLIST_NAME = @"SA_DB_StringFormatRules";
NSString * const SA_DB_STRING_FORMAT_RULES_PLIST_NAME = @"SA_DB_StringFormatRules";

NSString * const SA_DB_VALID_CHARACTERS = @"SA_DB_VALID_CHARACTERS";
NSString * const SA_DB_VALID_NUMERAL_CHARACTERS = @"SA_DB_VALID_NUMERAL_CHARACTERS";
NSString * const SA_DB_VALID_OPERATOR_CHARACTERS = @"SA_DB_VALID_OPERATOR_CHARACTERS";
NSString * const SA_DB_VALID_ROLL_COMMAND_DELIMITER_CHARACTERS = @"SA_DB_VALID_ROLL_COMMAND_DELIMITER_CHARACTERS";
NSString * const SA_DB_VALID_CHARACTERS = @"SA_DB_VALID_CHARACTERS";
NSString * const SA_DB_VALID_NUMERAL_CHARACTERS = @"SA_DB_VALID_NUMERAL_CHARACTERS";
NSString * const SA_DB_VALID_OPERATOR_CHARACTERS = @"SA_DB_VALID_OPERATOR_CHARACTERS";
NSString * const SA_DB_VALID_ROLL_COMMAND_DELIMITER_CHARACTERS = @"SA_DB_VALID_ROLL_COMMAND_DELIMITER_CHARACTERS";
NSString * const SA_DB_VALID_ROLL_MODIFIER_DELIMITER_CHARACTERS = @"SA_DB_VALID_ROLL_MODIFIER_DELIMITER_CHARACTERS";

NSString * const SA_DB_CANONICAL_REPRESENTATIONS = @"SA_DB_CANONICAL_REPRESENTATIONS";
NSString * const SA_DB_CANONICAL_OPERATOR_REPRESENTATIONS = @"SA_DB_CANONICAL_OPERATOR_REPRESENTATIONS";
NSString * const SA_DB_CANONICAL_ROLL_COMMAND_DELIMITER_REPRESENTATION = @"SA_DB_CANONICAL_ROLL_COMMAND_DELIMITER_REPRESENTATION";
NSString * const SA_DB_CANONICAL_REPRESENTATIONS = @"SA_DB_CANONICAL_REPRESENTATIONS";
NSString * const SA_DB_CANONICAL_OPERATOR_REPRESENTATIONS = @"SA_DB_CANONICAL_OPERATOR_REPRESENTATIONS";
NSString * const SA_DB_CANONICAL_ROLL_COMMAND_DELIMITER_REPRESENTATIONS = @"SA_DB_CANONICAL_ROLL_COMMAND_DELIMITER_REPRESENTATIONS";
NSString * const SA_DB_CANONICAL_ROLL_MODIFIER_DELIMITER_REPRESENTATIONS = @"SA_DB_CANONICAL_ROLL_MODIFIER_DELIMITER_REPRESENTATIONS";

+ 22
- 21
SA_DiceFormatter.h Переглянути файл

@@ -8,6 +8,8 @@

#import <Foundation/Foundation.h>

#import "SA_DiceExpression.h"

/*********************/
#pragma mark Constants
/*********************/
@@ -35,9 +37,9 @@
==== DEFAULT mode ====
======================
"Default" mode is an alias for whatever default behavior is currently set for
new SA_DiceFormatter instances. (The "default default" behavior for the
current implementation is "legacy".)
“Default” mode is an alias for whatever default behavior is currently set for
new SA_DiceFormatter instances. (The “default default” behavior for the
current implementation is “legacy”.)
=====================
==== LEGACY mode ====
@@ -76,7 +78,7 @@
=====================
Simple mode generates a very minimal output format. It prints the final result
of a die roll expression, or the word 'ERROR' if any error occurred. It also
of a die roll expression, or the word ‘ERROR’ if any error occurred. It also
prints the label (if any).
SUPPORTED PARSER MODES: Feepbot, Legacy.
@@ -103,14 +105,13 @@
SUPPORTED PARSER MODES: Feepbot, Legacy.
*/
typedef enum
{
typedef NS_ENUM(unsigned int, SA_DiceFormatterBehavior) {
SA_DiceFormatterBehaviorDefault = 0,
SA_DiceFormatterBehaviorSimple = 1,
SA_DiceFormatterBehaviorLegacy = 1337,
SA_DiceFormatterBehaviorModern = 2001,
SA_DiceFormatterBehaviorFeepbot = 65516
} SA_DiceFormatterBehavior;
SA_DiceFormatterBehaviorFeepbot = 65536
};

/************************************************/
#pragma mark - SA_DiceFormatter class declaration
@@ -125,34 +126,34 @@ typedef enum
@property SA_DiceFormatterBehavior formatterBehavior;

/*************************************************/
#pragma mark - Properties ("legacy" behavior mode)
#pragma mark - Properties (“legacy” behavior mode)
/*************************************************/

@property BOOL legacyModeErrorReportingEnabled;

/****************************************/
#pragma mark - "Class property" accessors
/****************************************/
/******************************/
#pragma mark - Class properties
/******************************/

+ (void)setDefaultFormatterBehavior:(SA_DiceFormatterBehavior)defaultFormatterBehavior;
+ (SA_DiceFormatterBehavior)defaultFormatterBehavior;
@property (class) SA_DiceFormatterBehavior defaultFormatterBehavior;

/********************************************/
#pragma mark - Initializers & factory methods
/********************************************/

- (instancetype)init;
- (instancetype)initWithBehavior:(SA_DiceFormatterBehavior)formatterBehavior NS_DESIGNATED_INITIALIZER;
+ (instancetype)defaultFormatter;
+ (instancetype)formatterWithBehavior:(SA_DiceFormatterBehavior)formatterBehavior;
-(instancetype) init;
-(instancetype) initWithBehavior:(SA_DiceFormatterBehavior)formatterBehavior NS_DESIGNATED_INITIALIZER;
+(instancetype) defaultFormatter;
+(instancetype) formatterWithBehavior:(SA_DiceFormatterBehavior)formatterBehavior;

/****************************/
#pragma mark - Public methods
/****************************/

- (NSString *)stringFromExpression:(NSDictionary *)expression;
- (NSAttributedString *)attributedStringFromExpression:(NSDictionary *)expression;
-(NSString *) stringFromExpression:(SA_DiceExpression *)expression;
-(NSAttributedString *) attributedStringFromExpression:(SA_DiceExpression *)expression;

+ (NSString *)canonicalRepresentationForOperator:(NSString *)operatorName;
+(NSString *) rectifyMinusSignInString:(NSString *)aString;
+(NSString *) canonicalRepresentationForOperator:(SA_DiceExpressionOperator)operator;

@end

+ 206
- 271
SA_DiceFormatter.m Переглянути файл

@@ -9,7 +9,8 @@
#import "SA_DiceFormatter.h"

#import "SA_DiceExpressionStringConstants.h"
#import "SA_DiceErrorHandling.h"

#import "SA_Utility.h"

/********************************/
#pragma mark File-scope variables
@@ -19,12 +20,11 @@ static SA_DiceFormatterBehavior _defaultFormatterBehavior = SA_DiceFormatterBeha
static NSDictionary *_errorDescriptions;
static NSDictionary *_stringFormatRules;

/**********************************************************/
/***************************************************/
#pragma mark - SA_DiceFormatter class implementation
/**********************************************************/
/***************************************************/

@implementation SA_DiceFormatter
{
@implementation SA_DiceFormatter {
SA_DiceFormatterBehavior _formatterBehavior;
}

@@ -32,12 +32,10 @@ static NSDictionary *_stringFormatRules;
#pragma mark - Properties (general)
/**********************************/

- (void)setFormatterBehavior:(SA_DiceFormatterBehavior)newFormatterBehavior
{
-(void) setFormatterBehavior:(SA_DiceFormatterBehavior)newFormatterBehavior {
_formatterBehavior = newFormatterBehavior;
switch (_formatterBehavior)
{
switch (_formatterBehavior) {
case SA_DiceFormatterBehaviorLegacy:
self.legacyModeErrorReportingEnabled = YES;
break;
@@ -49,41 +47,33 @@ static NSDictionary *_stringFormatRules;
case SA_DiceFormatterBehaviorDefault:
default:
[self setFormatterBehavior:[SA_DiceFormatter defaultFormatterBehavior]];
self.formatterBehavior = SA_DiceFormatter.defaultFormatterBehavior;
break;
}
}

- (SA_DiceFormatterBehavior)formatterBehavior
{
-(SA_DiceFormatterBehavior) formatterBehavior {
return _formatterBehavior;
}

/****************************************/
#pragma mark - "Class property" accessors
/****************************************/
/******************************/
#pragma mark - Class properties
/******************************/

+ (void)setDefaultFormatterBehavior:(SA_DiceFormatterBehavior)newDefaultFormatterBehavior
{
if(newDefaultFormatterBehavior == SA_DiceFormatterBehaviorDefault)
{
+(void) setDefaultFormatterBehavior:(SA_DiceFormatterBehavior)newDefaultFormatterBehavior {
if (newDefaultFormatterBehavior == SA_DiceFormatterBehaviorDefault) {
_defaultFormatterBehavior = SA_DiceFormatterBehaviorLegacy;
}
else
{
} else {
_defaultFormatterBehavior = newDefaultFormatterBehavior;
}
}

+ (SA_DiceFormatterBehavior)defaultFormatterBehavior
{
+(SA_DiceFormatterBehavior) defaultFormatterBehavior {
return _defaultFormatterBehavior;
}

+ (NSDictionary *)stringFormatRules
{
if(_stringFormatRules == nil)
{
+(NSDictionary *) stringFormatRules {
if (_stringFormatRules == nil) {
[SA_DiceFormatter loadStringFormatRules];
}
@@ -94,36 +84,30 @@ static NSDictionary *_stringFormatRules;
#pragma mark - Initializers & factory methods
/********************************************/

- (instancetype)init
{
-(instancetype) init {
return [self initWithBehavior:SA_DiceFormatterBehaviorDefault];
}

- (instancetype)initWithBehavior:(SA_DiceFormatterBehavior)formatterBehavior
{
if(self = [super init])
{
-(instancetype) initWithBehavior:(SA_DiceFormatterBehavior)formatterBehavior {
if (self = [super init]) {
self.formatterBehavior = formatterBehavior;
if(_errorDescriptions == nil)
{
if (_errorDescriptions == nil) {
[SA_DiceFormatter loadErrorDescriptions];
}
if(_stringFormatRules == nil)
{
if (_stringFormatRules == nil) {
[SA_DiceFormatter loadStringFormatRules];
}
}
return self;
}

+ (instancetype)defaultFormatter
{
+(instancetype) defaultFormatter {
return [[SA_DiceFormatter alloc] initWithBehavior:SA_DiceFormatterBehaviorDefault];
}

+ (instancetype)formatterWithBehavior:(SA_DiceFormatterBehavior)formatterBehavior
{
+(instancetype) formatterWithBehavior:(SA_DiceFormatterBehavior)formatterBehavior {
return [[SA_DiceFormatter alloc] initWithBehavior:formatterBehavior];
}

@@ -131,37 +115,27 @@ static NSDictionary *_stringFormatRules;
#pragma mark - Public methods
/****************************/

- (NSString *)stringFromExpression:(NSDictionary *)expression
{
if(_formatterBehavior == SA_DiceFormatterBehaviorSimple)
{
-(NSString *) stringFromExpression:(SA_DiceExpression *)expression {
if (_formatterBehavior == SA_DiceFormatterBehaviorSimple) {
return [self simpleStringFromExpression:expression];
}
else // if(_formatterBehavior == SA_DiceFormatterBehaviorLegacy)
{
} else { // if(_formatterBehavior == SA_DiceFormatterBehaviorLegacy)
return [self legacyStringFromExpression:expression];
}
}

// NOT YET IMPLEMENTED
- (NSAttributedString *)attributedStringFromExpression:(NSDictionary *)expression
{
-(NSAttributedString *) attributedStringFromExpression:(SA_DiceExpression *)expression {
return [[NSAttributedString alloc] initWithString:[self stringFromExpression:expression]];
}

/**********************************************/
#pragma mark - "Legacy" behavior implementation
#pragma mark - “Legacy” behavior implementation
/**********************************************/

// PROPERTIES

@synthesize legacyModeErrorReportingEnabled = _legacyModeErrorReportingEnabled;

// METHODS

- (NSString *)legacyStringFromExpression:(NSDictionary *)expression
{
__block NSMutableString *formattedString = [NSMutableString string];
-(NSString *) legacyStringFromExpression:(SA_DiceExpression *)expression {
NSMutableString *formattedString = [NSMutableString string];
// Attach the formatted string representation of the expression itself.
[formattedString appendString:[self legacyStringFromIntermediaryExpression:expression]];
@@ -169,41 +143,20 @@ static NSDictionary *_stringFormatRules;
// 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:@"]"];
}
}];
}
if (expression.result != nil) {
[formattedString appendFormat:@" = %@", expression.result];
} else if (_legacyModeErrorReportingEnabled == YES &&
expression.errorBitMask != 0) {
[formattedString appendFormat:((__builtin_popcountl(expression.errorBitMask) == 1) ? @" [ERROR: %@]" : @" [ERRORS: %@]"),
[SA_DiceFormatter descriptionForErrors:expression.errorBitMask]];
}
// Make all instances of the minus sign be represented with the proper,
// canonical minus sign.
return [self rectifyMinusSignInString:formattedString];
return [SA_DiceFormatter rectifyMinusSignInString:formattedString];
}

- (NSString *)legacyStringFromIntermediaryExpression:(NSDictionary *)expression
{
-(NSString *) legacyStringFromIntermediaryExpression:(SA_DiceExpression *)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
@@ -216,247 +169,229 @@ static NSDictionary *_stringFormatRules;
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;
switch (expression.type) {
case SA_DiceExpressionTerm_OPERATION: {
return [self legacyStringFromOperationExpression:expression];
break;
}
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;
case SA_DiceExpressionTerm_ROLL_COMMAND: {
return [self legacyStringFromRollCommandExpression:expression];
break;
}
case SA_DiceExpressionTerm_ROLL_MODIFIER: {
return [self legacyStringFromRollModifierExpression:expression];
break;
}
case SA_DiceExpressionTerm_VALUE: {
return [self legacyStringFromValueExpression:expression];
break;
}
default: {
return expression.inputString;
break;
}
}
// 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
{
}

-(NSString *) legacyStringFromOperationExpression:(SA_DiceExpression *)expression {
if (expression.operator == SA_DiceExpressionOperator_MINUS &&
expression.leftOperand == nil) {
// Check to see if the term is a negation operation.
return [@[ [SA_DiceFormatter canonicalRepresentationForOperator:SA_DiceExpressionOperator_MINUS],
[self legacyStringFromIntermediaryExpression:expression.rightOperand]
] componentsJoinedByString:@""];
} else if (expression.operator == SA_DiceExpressionOperator_MINUS ||
expression.operator == SA_DiceExpressionOperator_PLUS ||
expression.operator == SA_DiceExpressionOperator_TIMES) {
// Check to see if the term is an addition, subtraction, or
// multiplication operation.
return [@[ [self legacyStringFromIntermediaryExpression:expression.leftOperand],
[SA_DiceFormatter canonicalRepresentationForOperator:expression.operator],
[self legacyStringFromIntermediaryExpression:expression.rightOperand]
] componentsJoinedByString:@" "];
} else {
// If the operator is not one of the supported operators, default to
// outputting the input string.
return expression[SA_DB_INPUT_STRING];
return expression.inputString;
}
}

- (NSString *)legacyStringFromRollCommandExpression:(NSDictionary *)expression
{
-(NSString *) legacyStringFromRollCommandExpression:(SA_DiceExpression *)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.
error (any of the errors that begin with 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;
return [NSString stringWithFormat:@"%@%@%@ < %@%@ >",
[self legacyStringFromIntermediaryExpression:expression.dieCount],
[SA_DiceFormatter canonicalRepresentationForRollCommandDelimiter:expression.rollCommand],
[self legacyStringFromIntermediaryExpression:expression.dieSize],
((expression.rolls != nil) ?
[NSString stringWithFormat:@"%@ = ",
[(expression.dieType == SA_DiceExpressionDice_FUDGE ?
[self formattedFudgeRolls:expression.rolls] :
expression.rolls
) componentsJoinedByString:@" "]] :
@""),
(expression.result ?: @"ERROR")];
}

-(NSArray *) formattedFudgeRolls:(NSArray <NSNumber *> *)rolls {
static NSDictionary *fudgeDieRollRepresentations;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
fudgeDieRollRepresentations = @{ @(-1): [SA_DiceFormatter canonicalRepresentationForOperator:SA_DiceExpressionOperator_MINUS],
@(0): @"0",
@(1): [SA_DiceFormatter canonicalRepresentationForOperator:SA_DiceExpressionOperator_PLUS]
};
});

return [rolls map:^NSString *(NSNumber *roll) {
return fudgeDieRollRepresentations[roll];
}];
}

-(NSString *) legacyStringFromRollModifierExpression:(SA_DiceExpression *)expression {
/*
In legacy behavior, we print the result of roll modifiers with the rolls
generated by the roll command, plus the modifications. If a roll modifier
generates an error, we print “ERROR” in place of any of the components.

Legacy behavior assumes support for the ‘keep’ modifier only, so we do not
need to adjust the output format for different roll modifiers.
*/
NSUInteger keptHowMany = expression.rightOperand.result.unsignedIntegerValue;
return [NSString stringWithFormat:@"%@%@%@%@%@ < %@ less %@ leaves %@ = %@ >",
[self legacyStringFromIntermediaryExpression:expression.leftOperand.dieCount],
[SA_DiceFormatter canonicalRepresentationForRollCommandDelimiter:expression.leftOperand.rollCommand],
[self legacyStringFromIntermediaryExpression:expression.leftOperand.dieSize],
[SA_DiceFormatter canonicalRepresentationForRollModifierDelimiter:expression.rollModifier],
expression.rightOperand.result,
((expression.leftOperand.rolls != nil) ?
[(expression.leftOperand.dieType == SA_DiceExpressionDice_FUDGE ?
[self formattedFudgeRolls:expression.rolls] :
expression.leftOperand.rolls
) componentsJoinedByString:@" "] :
@""),
[(expression.leftOperand.dieType == SA_DiceExpressionDice_FUDGE ?
[self formattedFudgeRolls:[expression.rolls subarrayWithRange:NSRangeMake(keptHowMany, expression.rolls.count - keptHowMany)]] :
[expression.rolls subarrayWithRange:NSRangeMake(keptHowMany, expression.rolls.count - keptHowMany)]
) componentsJoinedByString:@" "],
[(expression.leftOperand.dieType == SA_DiceExpressionDice_FUDGE ?
[self formattedFudgeRolls:[expression.rolls subarrayWithRange:NSRangeMake(0, keptHowMany)]] :
[expression.rolls subarrayWithRange:NSRangeMake(0, keptHowMany)]
) componentsJoinedByString:@" "],
(expression.result ?: @"ERROR")];
}

- (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];
-(NSString *) legacyStringFromValueExpression:(SA_DiceExpression *)expression {
if ([expression.inputString.lowercaseString isEqualToString:@"f"]) {
return @"F";
} else {
// We use the value for the ‘value’ property and not the ‘result’ property
// because they should be the same, and the ‘result’ property 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.value stringValue];
}
}

/**********************************************/
#pragma mark - "Simple" behavior implementation
#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"];
}
-(NSString *) simpleStringFromExpression:(SA_DiceExpression *)expression {
NSString *formattedString = [NSString stringWithFormat:@"%@",
(expression.result ?: @"ERROR")];
// Make all instances of the minus sign be represented with the proper,
// canonical minus sign.
return [self rectifyMinusSignInString:formattedString];
return [SA_DiceFormatter rectifyMinusSignInString:formattedString];
}

/****************************/
#pragma mark - Helper methods
/****************************/

- (NSString *)rectifyMinusSignInString:(NSString *)aString
{
__block NSMutableString* sameStringButMutable = aString.mutableCopy;
NSLog(@"%@", sameStringButMutable);
NSString *validMinusSignCharacters = [SA_DiceFormatter stringFormatRules][SA_DB_VALID_CHARACTERS][SA_DB_VALID_OPERATOR_CHARACTERS][SA_DB_OPERATOR_MINUS];
NSString *theRealMinusSign = [SA_DiceFormatter canonicalRepresentationForOperator:SA_DB_OPERATOR_MINUS];
+(NSString *) rectifyMinusSignInString:(NSString *)aString {
NSMutableString* sameStringButMutable = aString.mutableCopy;
[validMinusSignCharacters enumerateSubstringsInRange:NSMakeRange(0, validMinusSignCharacters.length)
NSString *validMinusSignCharacters = [SA_DiceFormatter stringFormatRules][SA_DB_VALID_CHARACTERS][SA_DB_VALID_OPERATOR_CHARACTERS][NSStringFromSA_DiceExpressionOperator(SA_DiceExpressionOperator_MINUS)];
[validMinusSignCharacters enumerateSubstringsInRange:NSRangeMake(0, validMinusSignCharacters.length)
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *character, NSRange characterRange, NSRange enclosingRange, BOOL *stop)
{
[sameStringButMutable replaceOccurrencesOfString:character withString:theRealMinusSign options:NSLiteralSearch range:NSMakeRange(0, sameStringButMutable.length)];
usingBlock:^(NSString *aValidMinusSignCharacter,
NSRange characterRange,
NSRange enclosingRange,
BOOL *stop) {
[sameStringButMutable replaceOccurrencesOfString:aValidMinusSignCharacter
withString:[SA_DiceFormatter canonicalRepresentationForOperator:SA_DiceExpressionOperator_MINUS]
options:NSLiteralSearch
range:NSRangeMake(0, sameStringButMutable.length)];
}];
NSLog(@"%@", sameStringButMutable);
return sameStringButMutable.copy;
return [sameStringButMutable copy];
}

+ (void)loadErrorDescriptions
{
NSString* errorDescriptionsPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"SA_DB_ErrorDescriptions" ofType:@"plist"];
+(void) loadErrorDescriptions {
NSString* errorDescriptionsPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"SA_DB_ErrorDescriptions"
ofType:@"plist"];
_errorDescriptions = [NSDictionary dictionaryWithContentsOfFile:errorDescriptionsPath];
if(!_errorDescriptions)
{
if (!_errorDescriptions) {
NSLog(@"Could not load error descriptions!");
}
}

+ (NSString *)descriptionForError:(NSString *)error
{
if(_errorDescriptions == nil)
{
+(NSString *) descriptionForErrors:(NSUInteger)errorBitMask {
if (_errorDescriptions == nil) {
[SA_DiceFormatter loadErrorDescriptions];
}
if(_errorDescriptions[error] != nil)
{
return _errorDescriptions[error];
}
else
{
return error;

NSMutableArray <NSString *> *errorDescriptions = [NSMutableArray array];
for (int i = 0; i <= 18; i++) {
if ((errorBitMask & 1 << i) == 0) continue;
NSString *errorName = NSStringFromSA_DiceExpressionError((SA_DiceExpressionError) 1 << i);
[errorDescriptions addObject:(_errorDescriptions[errorName] ?: errorName)];
}

return [errorDescriptions componentsJoinedByString:@" / "];
}

+ (void)loadStringFormatRules
{
NSString *stringFormatRulesPath = [[NSBundle bundleForClass:[self class]] pathForResource:SA_DB_STRING_FORMAT_RULES_PLIST_NAME ofType:@"plist"];
+(void) loadStringFormatRules {
NSString *stringFormatRulesPath = [[NSBundle bundleForClass:[self class]] pathForResource:SA_DB_STRING_FORMAT_RULES_PLIST_NAME
ofType:@"plist"];
_stringFormatRules = [NSDictionary dictionaryWithContentsOfFile:stringFormatRulesPath];
if(!_stringFormatRules)
{
if (!_stringFormatRules) {
NSLog(@"Could not load string format rules!");
}
}

+ (NSString *)canonicalRepresentationForOperator:(NSString *)operatorName
{
return [SA_DiceFormatter canonicalOperatorRepresentations][operatorName];
+(NSString *) canonicalRepresentationForOperator:(SA_DiceExpressionOperator)operator {
return [SA_DiceFormatter canonicalOperatorRepresentations][NSStringFromSA_DiceExpressionOperator(operator)];
}

+ (NSDictionary *)canonicalOperatorRepresentations
{
+(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];
+(NSString *) canonicalRepresentationForRollCommandDelimiter:(SA_DiceExpressionRollCommand)command {
return [SA_DiceFormatter canonicalRollCommandDelimiterRepresentations][NSStringFromSA_DiceExpressionRollCommand(command)];
}

+(NSDictionary *) canonicalRollCommandDelimiterRepresentations {
return [SA_DiceFormatter stringFormatRules][SA_DB_CANONICAL_REPRESENTATIONS][SA_DB_CANONICAL_ROLL_COMMAND_DELIMITER_REPRESENTATIONS];
}

+(NSString *) canonicalRepresentationForRollModifierDelimiter:(SA_DiceExpressionRollModifier)modifier {
return [SA_DiceFormatter canonicalRollModifierDelimiterRepresentations][NSStringFromSA_DiceExpressionRollModifier(modifier)];
}

+(NSDictionary *) canonicalRollModifierDelimiterRepresentations {
return [SA_DiceFormatter stringFormatRules][SA_DB_CANONICAL_REPRESENTATIONS][SA_DB_CANONICAL_ROLL_MODIFIER_DELIMITER_REPRESENTATIONS];
}

@end

+ 24
- 22
SA_DiceParser.h Переглянути файл

@@ -8,6 +8,8 @@

#import <Foundation/Foundation.h>

#import "SA_DiceExpression.h"

/*********************/
#pragma mark Constants
/*********************/
@@ -28,7 +30,7 @@
formatter behaviors, each of which supports one or more parser modes. Using the
wrong formatter behavior mode for an expression tree (that is, passing an
SA_DiceFormatter instance an expression tree that was was generated
by a parser mode that is not supported by the formatter's currently set
by a parser mode that is not supported by the formatter’s currently set
formatter behavior mode) results in undefined behavior.
See SA_DiceFormatter.h for a list of which formatter behavior modes are
@@ -43,9 +45,9 @@
==== DEFAULT mode ====
======================
"Default" mode is an alias for whatever default behavior is currently set for
new SA_DiceParser instances. (The "default default" behavior for the current
implementation is "legacy".)
“Default” mode is an alias for whatever default behavior is currently set for
new SA_DiceParser instances. (The “default default” behavior for the current
implementation is “legacy”.)
=====================
==== LEGACY mode ====
@@ -65,7 +67,7 @@
Normal operator precedence and behavior (commutativity, associativity) apply.

2. Simple roll-and-sum. Roll X dice, each with Y sides, and take the sum of
the rolled values, by inputting 'XdY' where X is a nonnegative integer and Y
the rolled values, by inputting ‘XdY’ where X is a nonnegative integer and Y
is a positive integer, e.g.:
1d20
@@ -73,7 +75,7 @@
8d27
3. Left-associative recursive roll-and-sum. Roll X dice, each with Y sides,
and take the sum of the rolled values, by inputting 'XdY', where Y is a
and take the sum of the rolled values, by inputting ‘XdY’, where Y is a
positive integer and X may be a nonnegative integer or a recursive roll-and-sum
expression, e.g.:
@@ -89,7 +91,7 @@
5d6d10-2*3
5+3-2*4d6+2d10d3-20+5d4*2
NOTE: The 'd' operator takes precedence over arithmetic operators. (Legacy
NOTE: The ‘d’ operator takes precedence over arithmetic operators. (Legacy
mode does not support parentheses.)
NOTE 2: Legacy mode does not support whitespace within roll strings.
@@ -110,13 +112,12 @@
Feepbot mode emulates feepbot by feep.
*/
typedef enum
{
typedef NS_ENUM(NSUInteger, SA_DiceParserBehavior) {
SA_DiceParserBehaviorDefault = 0,
SA_DiceParserBehaviorLegacy = 1337,
SA_DiceParserBehaviorModern = 2001,
SA_DiceParserBehaviorFeepbot = 65516
} SA_DiceParserBehavior;
SA_DiceParserBehaviorFeepbot = 65536
};

/*********************************************/
#pragma mark - SA_DiceParser class declaration
@@ -130,28 +131,29 @@ typedef enum

@property SA_DiceParserBehavior parserBehavior;

/****************************************/
#pragma mark - "Class property" accessors
/****************************************/
/******************************/
#pragma mark - Class properties
/******************************/

+ (void)setDefaultParserBehavior:(SA_DiceParserBehavior)defaultParserBehavior;
+ (SA_DiceParserBehavior)defaultParserBehavior;
@property (class) SA_DiceParserBehavior defaultParserBehavior;

/********************************************/
#pragma mark - Initializers & factory methods
/********************************************/

- (instancetype)init;
- (instancetype)initWithBehavior:(SA_DiceParserBehavior)parserBehavior NS_DESIGNATED_INITIALIZER;
+ (instancetype)defaultParser;
+ (instancetype)parserWithBehavior:(SA_DiceParserBehavior)parserBehavior;
-(instancetype) init;
-(instancetype) initWithBehavior:(SA_DiceParserBehavior)parserBehavior NS_DESIGNATED_INITIALIZER;
+(instancetype) defaultParser;
+(instancetype) parserWithBehavior:(SA_DiceParserBehavior)parserBehavior;

/****************************/
#pragma mark - Public methods
/****************************/

- (NSDictionary *)expressionForString:(NSString *)dieRollString;
-(SA_DiceExpression *) expressionForString:(NSString *)dieRollString;

- (NSDictionary *)expressionByJoiningExpression:(NSDictionary *)leftHandExpression toExpression:(NSDictionary *)rightHandExpression withOperator:(NSString *)operatorName;
-(SA_DiceExpression *) expressionByJoiningExpression:(SA_DiceExpression *)leftHandExpression
toExpression:(SA_DiceExpression *)rightHandExpression
withOperator:(SA_DiceExpressionOperator)operator;

@end

+ 280
- 267
SA_DiceParser.m Переглянути файл

@@ -4,15 +4,15 @@
// Copyright (c) 2016 Said Achmiz.
//
// This software is licensed under the MIT license.
// See the file "LICENSE" for more information.
// See the file “LICENSE” for more information.

#import "SA_DiceParser.h"

#import "SA_DiceExpressionStringConstants.h"
#import "SA_DiceErrorHandling.h"
#import "NSString+SA_NSStringExtensions.h"
#import "SA_DiceFormatter.h"

#import "SA_Utility.h"

/********************************/
#pragma mark File-scope variables
/********************************/
@@ -24,8 +24,7 @@ static NSDictionary *_validCharactersDict;
#pragma mark - SA_DiceParser class implementation
/************************************************/

@implementation SA_DiceParser
{
@implementation SA_DiceParser {
SA_DiceParserBehavior _parserBehavior;
}

@@ -33,12 +32,10 @@ static NSDictionary *_validCharactersDict;
#pragma mark - Properties
/************************/

- (void)setParserBehavior:(SA_DiceParserBehavior)newParserBehavior
{
-(void) setParserBehavior:(SA_DiceParserBehavior)newParserBehavior {
_parserBehavior = newParserBehavior;
switch (_parserBehavior)
{
switch (_parserBehavior) {
case SA_DiceParserBehaviorLegacy:
case SA_DiceParserBehaviorModern:
case SA_DiceParserBehaviorFeepbot:
@@ -46,41 +43,33 @@ static NSDictionary *_validCharactersDict;
case SA_DiceParserBehaviorDefault:
default:
_parserBehavior = [SA_DiceParser defaultParserBehavior];
_parserBehavior = SA_DiceParser.defaultParserBehavior;
break;
}
}

- (SA_DiceParserBehavior)parserBehavior
{
-(SA_DiceParserBehavior) parserBehavior {
return _parserBehavior;
}

/****************************************/
#pragma mark - "Class property" accessors
/****************************************/
/******************************/
#pragma mark - Class properties
/******************************/

+ (void)setDefaultParserBehavior:(SA_DiceParserBehavior)newDefaultParserBehavior
{
if(newDefaultParserBehavior == SA_DiceParserBehaviorDefault)
{
+(void) setDefaultParserBehavior:(SA_DiceParserBehavior)newDefaultParserBehavior {
if (newDefaultParserBehavior == SA_DiceParserBehaviorDefault) {
_defaultParserBehavior = SA_DiceParserBehaviorLegacy;
}
else
{
} else {
_defaultParserBehavior = newDefaultParserBehavior;
}
}

+ (SA_DiceParserBehavior)defaultParserBehavior
{
+(SA_DiceParserBehavior) defaultParserBehavior {
return _defaultParserBehavior;
}

+ (NSDictionary *)validCharactersDict
{
if(_validCharactersDict == nil)
{
+(NSDictionary *) validCharactersDict {
if (_validCharactersDict == nil) {
[SA_DiceParser loadValidCharactersDict];
}
@@ -91,32 +80,26 @@ static NSDictionary *_validCharactersDict;
#pragma mark - Initializers & factory methods
/********************************************/

- (instancetype)init
{
-(instancetype) init {
return [self initWithBehavior:SA_DiceParserBehaviorDefault];
}

- (instancetype)initWithBehavior:(SA_DiceParserBehavior)parserBehavior
{
if(self = [super init])
{
-(instancetype) initWithBehavior:(SA_DiceParserBehavior)parserBehavior {
if (self = [super init]) {
self.parserBehavior = parserBehavior;
if(_validCharactersDict == nil)
{
if (_validCharactersDict == nil) {
[SA_DiceParser loadValidCharactersDict];
}
}
return self;
}

+ (instancetype)defaultParser
{
+(instancetype) defaultParser {
return [[SA_DiceParser alloc] initWithBehavior:SA_DiceParserBehaviorDefault];
}

+ (instancetype)parserWithBehavior:(SA_DiceParserBehavior)parserBehavior
{
+(instancetype) parserWithBehavior:(SA_DiceParserBehavior)parserBehavior {
return [[SA_DiceParser alloc] initWithBehavior:parserBehavior];
}

@@ -124,88 +107,77 @@ static NSDictionary *_validCharactersDict;
#pragma mark - Public methods
/****************************/

- (NSDictionary *)expressionForString:(NSString *)dieRollString
{
if(_parserBehavior == SA_DiceParserBehaviorLegacy)
{
-(SA_DiceExpression *) expressionForString:(NSString *)dieRollString {
if (_parserBehavior == SA_DiceParserBehaviorLegacy) {
return [self legacyExpressionForString:dieRollString];
}
else
{
return @{};
} else {
return nil;
}
}

- (NSDictionary *)expressionByJoiningExpression:(NSDictionary *)leftHandExpression toExpression:(NSDictionary *)rightHandExpression withOperator:(NSString *)operatorName
{
NSMutableDictionary *expression = [NSMutableDictionary dictionary];
-(SA_DiceExpression *) expressionByJoiningExpression:(SA_DiceExpression *)leftHandExpression
toExpression:(SA_DiceExpression *)rightHandExpression
withOperator:(SA_DiceExpressionOperator)operator {
SA_DiceExpression *expression = [SA_DiceExpression new];
// First, we check that the operands and operator are not nil. If they are,
// then the expression is invalid...
if(leftHandExpression == nil || rightHandExpression == nil || operatorName == nil)
{
expression[SA_DB_TERM_TYPE] = SA_DB_TERM_TYPE_NONE;
addErrorToExpression(SA_DB_ERROR_INVALID_EXPRESSION, expression);
if (leftHandExpression == nil ||
rightHandExpression == nil ||
operator == SA_DiceExpressionOperator_NONE) {
expression.type = SA_DiceExpressionTerm_NONE;
expression.errorBitMask |= SA_DiceExpressionError_INVALID_EXPRESSION;
return expression;
}
// If the operands and operator are present, then the expression is an
// operation expression...
expression[SA_DB_TERM_TYPE] = SA_DB_TERM_TYPE_OPERATION;
expression.type = SA_DiceExpressionTerm_OPERATION;
// ... but does it have a valid operator?
if([operatorName isEqualToString:SA_DB_OPERATOR_PLUS] ||
[operatorName isEqualToString:SA_DB_OPERATOR_MINUS] ||
[operatorName isEqualToString:SA_DB_OPERATOR_TIMES]
)
{
expression[SA_DB_OPERATOR] = operatorName;
}
else
{
addErrorToExpression(SA_DB_ERROR_UNKNOWN_OPERATOR, expression);
if (operator == SA_DiceExpressionOperator_MINUS ||
operator == SA_DiceExpressionOperator_PLUS ||
operator == SA_DiceExpressionOperator_TIMES) {
expression.operator = operator;
} else {
expression.errorBitMask |= SA_DiceExpressionError_UNKNOWN_OPERATOR;
return expression;
}
// The operator is valid. Set the operands...
expression[SA_DB_OPERAND_LEFT] = leftHandExpression;
expression[SA_DB_OPERAND_RIGHT] = rightHandExpression;
expression.leftOperand = leftHandExpression;
expression.rightOperand = rightHandExpression;
// And inherit any errors that they may have.
addErrorsFromExpressionToExpression(expression[SA_DB_OPERAND_LEFT], expression);
addErrorsFromExpressionToExpression(expression[SA_DB_OPERAND_RIGHT], expression);
expression.errorBitMask |= expression.leftOperand.errorBitMask;
expression.errorBitMask |= expression.rightOperand.errorBitMask;
// Since this top-level expression was NOT generated by parsing an input
// string, for completeness and consistency, we have to generate a fake
// input string ourselves! We do this by wrapping each operand in
// parentheses and putting the canonical representation of the operator
// between them.
NSString *fakeInputString = [NSString stringWithFormat:@"(%@)%@(%@)",
expression[SA_DB_OPERAND_LEFT][SA_DB_INPUT_STRING],
[SA_DiceFormatter canonicalRepresentationForOperator:expression[SA_DB_OPERATOR]],
expression[SA_DB_OPERAND_RIGHT][SA_DB_INPUT_STRING]];
expression[SA_DB_INPUT_STRING] = fakeInputString;
expression.inputString = [NSString stringWithFormat:@"(%@)%@(%@)",
expression.leftOperand.inputString,
[SA_DiceFormatter canonicalRepresentationForOperator:expression.operator],
expression.rightOperand.inputString];

// The joining is complete. (Power overwhelming.)
return expression;
}

/**********************************************/
#pragma mark - "Legacy" behavior implementation
#pragma mark - “Legacy” behavior implementation
/**********************************************/

- (NSDictionary *)legacyExpressionForString:(NSString *)dieRollString
{
-(SA_DiceExpression *) legacyExpressionForString:(NSString *)dieRollString {
// Check for forbidden characters.
NSCharacterSet *forbiddenCharacterSet = [[NSCharacterSet characterSetWithCharactersInString:[SA_DiceParser allValidCharacters]] invertedSet];
if([dieRollString containsCharactersInSet:forbiddenCharacterSet])
{
return @{ SA_DB_TERM_TYPE : SA_DB_TERM_TYPE_NONE,
SA_DB_INPUT_STRING : dieRollString,
SA_DB_ERRORS : @[SA_DB_ERROR_ROLL_STRING_HAS_ILLEGAL_CHARACTERS] };
if ([dieRollString containsCharactersInSet:[[NSCharacterSet characterSetWithCharactersInString:[SA_DiceParser allValidCharacters]] invertedSet]]) {
SA_DiceExpression *errorExpression = [SA_DiceExpression new];
errorExpression.type = SA_DiceExpressionTerm_NONE;
errorExpression.inputString = dieRollString;
errorExpression.errorBitMask |= SA_DiceExpressionError_ROLL_STRING_HAS_ILLEGAL_CHARACTERS;
return errorExpression;
}
// Since we have checked the entire string for forbidden characters, we can
@@ -217,14 +189,14 @@ static NSDictionary *_validCharactersDict;
return [self legacyExpressionForLegalString:dieRollString];
}

- (NSDictionary *)legacyExpressionForLegalString:(NSString *)dieRollString
{
-(SA_DiceExpression *) legacyExpressionForLegalString:(NSString *)dieRollString {
// Make sure string is not empty.
if(dieRollString.length == 0)
{
return @{ SA_DB_TERM_TYPE : SA_DB_TERM_TYPE_NONE,
SA_DB_INPUT_STRING : dieRollString,
SA_DB_ERRORS : @[SA_DB_ERROR_ROLL_STRING_EMPTY] };
if (dieRollString.length == 0) {
SA_DiceExpression *errorExpression = [SA_DiceExpression new];
errorExpression.type = SA_DiceExpressionTerm_NONE;
errorExpression.inputString = dieRollString;
errorExpression.errorBitMask |= SA_DiceExpressionError_ROLL_STRING_EMPTY;
return errorExpression;
}
// We now know the string describes one of the allowable expression types
@@ -233,85 +205,82 @@ static NSDictionary *_validCharactersDict;

// Check to see if the top-level term is an operation. Note that we parse
// operator expressions left-associatively.
NSRange lastOperatorRange = [dieRollString rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:[SA_DiceParser allValidOperatorCharacters]] options:NSBackwardsSearch];
if(lastOperatorRange.location != NSNotFound)
{
NSRange lastOperatorRange = [dieRollString rangeOfCharacterFromSet:[NSCharacterSet
characterSetWithCharactersInString:[SA_DiceParser
allValidOperatorCharacters]]
options:NSBackwardsSearch];
if (lastOperatorRange.location != NSNotFound) {
NSString *operator = [dieRollString substringWithRange:lastOperatorRange];
// If the last (and thus only) operator is the leading character of
// the expression, then this is one of several possible special cases.
if(lastOperatorRange.location == 0)
{
NSMutableDictionary *expression;
if (lastOperatorRange.location != 0) {
return [self legacyExpressionForStringDescribingOperation:dieRollString
withOperatorString:operator
atRange:lastOperatorRange];
} else {
// If the last (and thus only) operator is the leading character of
// the expression, then this is one of several possible special cases.
// First, we check for whether there even is anything more to the
// roll string besides the operator. If not, then the string is
// malformed by definition...
if(dieRollString.length == lastOperatorRange.length)
{
expression = [NSMutableDictionary dictionary];
expression[SA_DB_TERM_TYPE] = SA_DB_TERM_TYPE_OPERATION;
expression[SA_DB_INPUT_STRING] = dieRollString;
addErrorToExpression(SA_DB_ERROR_INVALID_EXPRESSION, expression);
return expression;
}
// If the last operator is the leading character (i.e. there's just
// one operator in the expression, and it's at the beginning), and
// there's more to the expression than just the operator, then
// If the last operator is the leading character (i.e. there’s just
// one operator in the expression, and it’s at the beginning), and
// there’s more to the expression than just the operator, then
// this is either an expression whose first term (which may or may
// not be its only term) is a simple value expression which
// represents a negative number - or, it's a malformed expression
// represents a negative number - or, it’s a malformed expression
// (because operators other than negation cannot begin an
// expression).
// In the former case, we do nothing, letting the testing for
// expression type fall through to the remaining cases (roll command
// or simple value).
// In the latter case, we register an error and return.
if([[SA_DiceParser validCharactersForOperator:SA_DB_OPERATOR_MINUS] containsCharactersInString:operator])
{
// We've determined that this expression begins with a simple
// value expression that represents a negative number.
// This next line is a hack to account for the fact that Cocoa's
// Unicode compliance is incomplete. :( NSString's integerValue
// method only accepts the hyphen as a negation sign when reading a
// number - not any of the Unicode characters which officially
// symbolize negation! But we are more modern-minded, and accept
// arbitrary symbols as minus-sign. For proper parsing, though,
// we have to replace it like this...
dieRollString = [dieRollString stringByReplacingCharactersInRange:lastOperatorRange withString:@"-"];
// Now we skip the remainder of the "is it an operator?" code
// and fall through to "is it a roll command, or maybe a simple
// value?"...
}
else
{
expression = [NSMutableDictionary dictionary];
expression[SA_DB_TERM_TYPE] = SA_DB_TERM_TYPE_OPERATION;
expression[SA_DB_INPUT_STRING] = dieRollString;
addErrorToExpression(SA_DB_ERROR_INVALID_EXPRESSION, expression);
if (dieRollString.length == lastOperatorRange.length ||
![[SA_DiceParser validCharactersForOperator:SA_DiceExpressionOperator_MINUS] containsCharactersInString:operator]) {
SA_DiceExpression *expression = [SA_DiceExpression new];

expression.type = SA_DiceExpressionTerm_OPERATION;
expression.inputString = dieRollString;
expression.errorBitMask |= SA_DiceExpressionError_INVALID_EXPRESSION;
return expression;
}
}
else
{
return [self legacyExpressionForStringDescribingOperation:dieRollString withOperator:operator atRange:lastOperatorRange];

// We’ve determined that this expression begins with a simple
// value expression that represents a negative number.
// This next line is a hack to account for the fact that Cocoa’s
// Unicode compliance is incomplete. :( NSString’s integerValue
// method only accepts the hyphen as a negation sign when reading a
// number - not any of the Unicode characters which officially
// symbolize negation! But we are more modern-minded, and accept
// arbitrary symbols as minus-sign. For proper parsing, though,
// we have to replace it like this...
dieRollString = [dieRollString stringByReplacingCharactersInRange:lastOperatorRange
withString:@"-"];

// Now we fall through to “is it a roll command, or maybe a simple
// value?”...
}
}
// If not an operation, the top-level term might be a die roll command.
// Look for one of the characters recognized as valid die roll delimiters.
// If not an operation, the top-level term might be a die roll command
// or a die roll modifier.
// Look for one of the characters recognized as valid die roll or die roll
// modifier delimiters.
// Note that we parse roll commands left-associatively, therefore e.g.
// 5d6d10 parses as "roll N d10s, where N is the result of rolling 5d6".
NSRange lastRollCommandDelimiterRange = [dieRollString rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:[SA_DiceParser validRollCommandDelimiterCharacters]] options:NSBackwardsSearch];
if(lastRollCommandDelimiterRange.location != NSNotFound)
{
return [self legacyExpressionForStringDescribingRollCommand:dieRollString withDelimiterAtRange:lastRollCommandDelimiterRange];
// 5d6d10 parses as “roll N d10s, where N is the result of rolling 5d6”.
NSMutableCharacterSet *validDelimiterCharacters = [NSMutableCharacterSet characterSetWithCharactersInString:[SA_DiceParser allValidRollCommandDelimiterCharacters]];
[validDelimiterCharacters addCharactersInString:[SA_DiceParser allValidRollModifierDelimiterCharacters]];
NSRange lastDelimiterRange = [dieRollString rangeOfCharacterFromSet:validDelimiterCharacters
options:NSBackwardsSearch];
if (lastDelimiterRange.location != NSNotFound) {
if ([[SA_DiceParser allValidRollCommandDelimiterCharacters] containsString:[dieRollString substringWithRange:lastDelimiterRange]])
return [self legacyExpressionForStringDescribingRollCommand:dieRollString
withDelimiterAtRange:lastDelimiterRange];
else if ([[SA_DiceParser allValidRollModifierDelimiterCharacters] containsString:[dieRollString substringWithRange:lastDelimiterRange]])
return [self legacyExpressionForStringDescribingRollModifier:dieRollString
withDelimiterAtRange:lastDelimiterRange];
else
// This should be impossible.
NSLog(@"IMPOSSIBLE CONDITION ENCOUNTERED WHILE PARSING DIE ROLL STRING!");
}
// If not an operation nor a roll command, the top-level term can only be
@@ -319,108 +288,155 @@ static NSDictionary *_validCharactersDict;
return [self legacyExpressionForStringDescribingNumericValue:dieRollString];
}

- (NSDictionary *)legacyExpressionForStringDescribingOperation:(NSString *)dieRollString withOperator:(NSString *)operator atRange:(NSRange)operatorRange
{
NSMutableDictionary *expression;
expression = [NSMutableDictionary dictionary];
expression[SA_DB_TERM_TYPE] = SA_DB_TERM_TYPE_OPERATION;
expression[SA_DB_INPUT_STRING] = dieRollString;
-(SA_DiceExpression *) legacyExpressionForStringDescribingOperation:(NSString *)dieRollString
withOperatorString:(NSString *)operatorString
atRange:(NSRange)operatorRange {
SA_DiceExpression *expression = [SA_DiceExpression new];
expression.type = SA_DiceExpressionTerm_OPERATION;
expression.inputString = dieRollString;
// Operands of a binary operator are the expressions generated by
// parsing the strings before and after the addition operator.
expression[SA_DB_OPERAND_LEFT] = [self legacyExpressionForLegalString:[dieRollString substringToIndex:operatorRange.location]];
expression[SA_DB_OPERAND_RIGHT] = [self legacyExpressionForLegalString:[dieRollString substringFromIndex:(operatorRange.location + operatorRange.length)]];
expression.leftOperand = [self legacyExpressionForLegalString:[dieRollString substringToIndex:operatorRange.location]];
expression.rightOperand = [self legacyExpressionForLegalString:[dieRollString substringFromIndex:(operatorRange.location + operatorRange.length)]];
// Check to see if the term is an addition operation.
if([[SA_DiceParser validCharactersForOperator:SA_DB_OPERATOR_PLUS] containsCharactersInString:operator])
{
expression[SA_DB_OPERATOR] = SA_DB_OPERATOR_PLUS;
}
// Check to see if the term is a subtraction operation.
else if([[SA_DiceParser validCharactersForOperator:SA_DB_OPERATOR_MINUS] containsCharactersInString:operator])
{
expression[SA_DB_OPERATOR] = SA_DB_OPERATOR_MINUS;
}
// Check to see if the term is a multiplication operation.
else if([[SA_DiceParser validCharactersForOperator:SA_DB_OPERATOR_TIMES] containsCharactersInString:operator])
{
// Look for other, lower-precedence operators to the left of the
if ([[SA_DiceParser validCharactersForOperator:SA_DiceExpressionOperator_PLUS] containsCharactersInString:operatorString]) {
// Check to see if the term is an addition operation.
expression.operator = SA_DiceExpressionOperator_PLUS;
} else if([[SA_DiceParser validCharactersForOperator:SA_DiceExpressionOperator_MINUS] containsCharactersInString:operatorString]) {
// Check to see if the term is a subtraction operation.
expression.operator = SA_DiceExpressionOperator_MINUS;
} else if([[SA_DiceParser validCharactersForOperator:SA_DiceExpressionOperator_TIMES] containsCharactersInString:operatorString]) {
// Check to see if the term is a multiplication operation.
// Look for other, lower-precedence operators to the left of the
// multiplication operator. If found, split the string there
// instead of at the current operator.
NSString *allLowerPrecedenceOperators = [NSString stringWithFormat:@"%@%@", [SA_DiceParser validCharactersForOperator:SA_DB_OPERATOR_PLUS], [SA_DiceParser validCharactersForOperator:SA_DB_OPERATOR_MINUS]];
NSRange lastLowerPrecedenceOperatorRange = [dieRollString rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:allLowerPrecedenceOperators] options:NSBackwardsSearch range:NSMakeRange(1, operatorRange.location - 1)];
if(lastLowerPrecedenceOperatorRange.location != NSNotFound)
{
NSString *lowerPrecedenceOperator = [dieRollString substringWithRange:lastLowerPrecedenceOperatorRange];
return [self legacyExpressionForStringDescribingOperation:dieRollString withOperator:lowerPrecedenceOperator atRange:lastLowerPrecedenceOperatorRange];
NSString *allLowerPrecedenceOperators = [@[ [SA_DiceParser validCharactersForOperator:SA_DiceExpressionOperator_PLUS],
[SA_DiceParser validCharactersForOperator:SA_DiceExpressionOperator_MINUS] ]
componentsJoinedByString:@""];
NSRange lastLowerPrecedenceOperatorRange = [dieRollString rangeOfCharacterFromSet:[NSCharacterSet
characterSetWithCharactersInString:allLowerPrecedenceOperators]
options:NSBackwardsSearch
range:NSRangeMake(1, operatorRange.location - 1)];
if (lastLowerPrecedenceOperatorRange.location != NSNotFound) {
return [self legacyExpressionForStringDescribingOperation:dieRollString
withOperatorString:[dieRollString substringWithRange:lastLowerPrecedenceOperatorRange]
atRange:lastLowerPrecedenceOperatorRange];
}
expression[SA_DB_OPERATOR] = SA_DB_OPERATOR_TIMES;
expression.operator = SA_DiceExpressionOperator_TIMES;
} else {
expression.errorBitMask |= SA_DiceExpressionError_UNKNOWN_OPERATOR;
}
else
{
addErrorToExpression(SA_DB_ERROR_UNKNOWN_OPERATOR, expression);
}

// The operands have now been parsed recursively; this parsing may have
// generated one or more errors. Inherit any error(s) from the
// error-generating operand(s).
addErrorsFromExpressionToExpression(expression[SA_DB_OPERAND_RIGHT], expression);
addErrorsFromExpressionToExpression(expression[SA_DB_OPERAND_LEFT], expression);
expression.errorBitMask |= expression.leftOperand.errorBitMask;
expression.errorBitMask |= expression.rightOperand.errorBitMask;
return expression;
}

- (NSDictionary *)legacyExpressionForStringDescribingRollCommand:(NSString *)dieRollString withDelimiterAtRange:(NSRange)delimiterRange
{
NSMutableDictionary *expression = [NSMutableDictionary dictionary];
expression[SA_DB_TERM_TYPE] = SA_DB_TERM_TYPE_ROLL_COMMAND;
expression[SA_DB_INPUT_STRING] = dieRollString;
-(SA_DiceExpression *) legacyExpressionForStringDescribingRollCommand:(NSString *)dieRollString
withDelimiterAtRange:(NSRange)delimiterRange {
SA_DiceExpression *expression = [SA_DiceExpression new];

expression.type = SA_DiceExpressionTerm_ROLL_COMMAND;
expression.inputString = dieRollString;

// For now, only one kind of roll command is supported - roll-and-sum.
// This rolls one or more dice of a given sort, and determines the sum of
// their rolled values.
// For now, only two kinds of roll command is supported - roll-and-sum,
// and roll-and-sum with exploding dice.
// These roll one or more dice of a given sort, and determine the sum of
// their rolled values. (In the “exploding dice” version, each die can
// explode, of course.)
// In the future, support for other, more complex roll commands might be
// added, such as "roll several and return the highest", exploding dice,
// etc.
expression[SA_DB_ROLL_COMMAND] = SA_DB_ROLL_COMMAND_SUM;
// added, such as “roll several and return the highest”.
if ([[SA_DiceParser validCharactersForRollCommandDelimiter:SA_DiceExpressionRollCommand_SUM]
containsString:[dieRollString substringWithRange:delimiterRange]])
expression.rollCommand = SA_DiceExpressionRollCommand_SUM;
else if ([[SA_DiceParser validCharactersForRollCommandDelimiter:SA_DiceExpressionRollCommand_SUM_EXPLODING]
containsString:[dieRollString substringWithRange:delimiterRange]])
expression.rollCommand = SA_DiceExpressionRollCommand_SUM_EXPLODING;

// Check to see if the delimiter is the initial character of the roll
// string. If so (i.e. if the die count is omitted), we assume it to be 1
// (i.e. 'd6' is read as '1d6').
if(delimiterRange.location == 0)
{
expression[SA_DB_ROLL_DIE_COUNT] = [self legacyExpressionForStringDescribingNumericValue:@"1"];
}
else
{
// The die count is the expression generated by parsing the string
// before the delimiter.
expression[SA_DB_ROLL_DIE_COUNT] = [self legacyExpressionForLegalString:[dieRollString substringToIndex:delimiterRange.location]];
}
// (i.e. ‘d6’ is read as ‘1d6’).
// Otherwise, the die count is the expression generated by parsing the
// string before the delimiter.
expression.dieCount = ((delimiterRange.location == 0) ?
[self legacyExpressionForStringDescribingNumericValue:@"1"] :
[self legacyExpressionForLegalString:[dieRollString substringToIndex:delimiterRange.location]]);

// The die size is the expression generated by parsing the string after the
// delimiter.
expression[SA_DB_ROLL_DIE_SIZE] = [self legacyExpressionForLegalString:[dieRollString substringFromIndex:(delimiterRange.location + delimiterRange.length)]];
expression.dieSize = [self legacyExpressionForLegalString:[dieRollString substringFromIndex:(delimiterRange.location + delimiterRange.length)]];
if ([expression.dieSize.inputString.lowercaseString isEqualToString:@"f"])
expression.dieType = SA_DiceExpressionDice_FUDGE;

// The die count and die size have now been parsed recursively; this parsing
// may have generated one or more errors. Inherit any error(s) from the
// error-generating sub-terms.
addErrorsFromExpressionToExpression(expression[SA_DB_ROLL_DIE_COUNT], expression);
addErrorsFromExpressionToExpression(expression[SA_DB_ROLL_DIE_SIZE], expression);
expression.errorBitMask |= expression.dieCount.errorBitMask;
expression.errorBitMask |= expression.dieSize.errorBitMask;
return expression;
}

- (NSDictionary *)legacyExpressionForStringDescribingNumericValue:(NSString *)dieRollString
{
NSMutableDictionary *expression = [NSMutableDictionary dictionary];
expression[SA_DB_TERM_TYPE] = SA_DB_TERM_TYPE_VALUE;
expression[SA_DB_INPUT_STRING] = dieRollString;
expression[SA_DB_VALUE] = @(dieRollString.integerValue);
-(SA_DiceExpression *) legacyExpressionForStringDescribingRollModifier:(NSString *)dieRollString
withDelimiterAtRange:(NSRange)delimiterRange {
SA_DiceExpression *expression = [SA_DiceExpression new];

expression.type = SA_DiceExpressionTerm_ROLL_MODIFIER;
expression.inputString = dieRollString;

// The possible roll modifiers are KEEP HIGHEST and KEEP LOWEST.
// These take a roll command and a number, and keep that number of rolls
// generated by the roll command (either the highest or lowest rolls,
// respectively).
if ([[SA_DiceParser validCharactersForRollModifierDelimiter:SA_DiceExpressionRollModifier_KEEP_HIGHEST]
containsString:[dieRollString substringWithRange:delimiterRange]])
expression.rollModifier = SA_DiceExpressionRollModifier_KEEP_HIGHEST;
else if ([[SA_DiceParser validCharactersForRollModifierDelimiter:SA_DiceExpressionRollModifier_KEEP_LOWEST]
containsString:[dieRollString substringWithRange:delimiterRange]])
expression.rollModifier = SA_DiceExpressionRollModifier_KEEP_LOWEST;

// Check to see if the delimiter is the initial character of the roll
// string. If so, set an error, because a roll modifier requires a
// roll command to modify.
if (delimiterRange.location == 0) {
expression.errorBitMask |= SA_DiceExpressionError_ROLL_STRING_EMPTY;
return expression;
}

// Otherwise, the left operand is the expression generated by parsing the
// string before the delimiter.
expression.leftOperand = [self legacyExpressionForLegalString:[dieRollString substringToIndex:delimiterRange.location]];

// The right operand is the expression generated by parsing the string after
// the delimiter.
expression.rightOperand = [self legacyExpressionForLegalString:[dieRollString substringFromIndex:(delimiterRange.location + delimiterRange.length)]];

// The left and right operands have now been parsed recursively; this
// parsing may have generated one or more errors. Inherit any error(s) from
// the error-generating sub-terms.
expression.errorBitMask |= expression.leftOperand.errorBitMask;
expression.errorBitMask |= expression.rightOperand.errorBitMask;

return expression;
}

-(SA_DiceExpression *) legacyExpressionForStringDescribingNumericValue:(NSString *)dieRollString {
SA_DiceExpression *expression = [SA_DiceExpression new];

expression.type = SA_DiceExpressionTerm_VALUE;
expression.inputString = dieRollString;
if ([expression.inputString.lowercaseString isEqualToString:@"f"])
expression.value = @(-1);
else
expression.value = @(dieRollString.integerValue);

return expression;
}

@@ -428,57 +444,54 @@ static NSDictionary *_validCharactersDict;
#pragma mark - Helper methods
/****************************/

+ (void)loadValidCharactersDict
{
NSString *stringFormatRulesPath = [[NSBundle bundleForClass:[self class]] pathForResource:SA_DB_STRING_FORMAT_RULES_PLIST_NAME ofType:@"plist"];
+(void) loadValidCharactersDict {
NSString *stringFormatRulesPath = [[NSBundle bundleForClass:[self class]] pathForResource:SA_DB_STRING_FORMAT_RULES_PLIST_NAME
ofType:@"plist"];
_validCharactersDict = [NSDictionary dictionaryWithContentsOfFile:stringFormatRulesPath][SA_DB_VALID_CHARACTERS];
if(!_validCharactersDict)
{
if (!_validCharactersDict) {
NSLog(@"Could not load valid characters dictionary!");
}
}

+ (NSString *)allValidCharacters
{
NSMutableString *validCharactersString = [NSMutableString string];
[validCharactersString appendString:[SA_DiceParser validNumeralCharacters]];
[validCharactersString appendString:[SA_DiceParser validRollCommandDelimiterCharacters]];
[validCharactersString appendString:[SA_DiceParser allValidOperatorCharacters]];
return validCharactersString;
+(NSString *) allValidCharacters {
return [ @[ [SA_DiceParser validNumeralCharacters],
[SA_DiceParser allValidRollCommandDelimiterCharacters],
[SA_DiceParser allValidRollModifierDelimiterCharacters],
[SA_DiceParser allValidOperatorCharacters] ] componentsJoinedByString:@""];
}

+ (NSString *)allValidOperatorCharacters
{
NSDictionary *validCharactersDict = [SA_DiceParser validCharactersDict];
__block NSMutableString *validOperatorCharactersString = [NSMutableString string];
+(NSString *) allValidOperatorCharacters {
NSDictionary *validOperatorCharactersDict = [SA_DiceParser validCharactersDict][SA_DB_VALID_OPERATOR_CHARACTERS];
[validCharactersDict[SA_DB_VALID_OPERATOR_CHARACTERS] enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) {
[validOperatorCharactersString appendString:value];
}];
return validOperatorCharactersString;
return [validOperatorCharactersDict.allValues componentsJoinedByString:@""];
}

+ (NSString *)validCharactersForOperator:(NSString *)operatorName
{
return [SA_DiceParser validCharactersDict][SA_DB_VALID_OPERATOR_CHARACTERS][operatorName];
+(NSString *) validCharactersForOperator:(SA_DiceExpressionOperator)operator {
return [SA_DiceParser validCharactersDict][SA_DB_VALID_OPERATOR_CHARACTERS][NSStringFromSA_DiceExpressionOperator(operator)];
}

+ (NSString *)validNumeralCharacters
{
NSDictionary *validCharactersDict = [SA_DiceParser validCharactersDict];
return validCharactersDict[SA_DB_VALID_NUMERAL_CHARACTERS];
+(NSString *) validNumeralCharacters {
return [SA_DiceParser validCharactersDict][SA_DB_VALID_NUMERAL_CHARACTERS];
}

+(NSString *) validCharactersForRollCommandDelimiter:(SA_DiceExpressionRollCommand)command {
return [SA_DiceParser validCharactersDict][SA_DB_VALID_ROLL_COMMAND_DELIMITER_CHARACTERS][NSStringFromSA_DiceExpressionRollCommand(command)];
}

+(NSString *) allValidRollCommandDelimiterCharacters {
NSDictionary *validRollCommandDelimiterCharactersDict = [SA_DiceParser validCharactersDict][SA_DB_VALID_ROLL_COMMAND_DELIMITER_CHARACTERS];

return [validRollCommandDelimiterCharactersDict.allValues componentsJoinedByString:@""];
}

+(NSString *) validCharactersForRollModifierDelimiter:(SA_DiceExpressionRollModifier)modifier {
return [SA_DiceParser validCharactersDict][SA_DB_VALID_ROLL_MODIFIER_DELIMITER_CHARACTERS][NSStringFromSA_DiceExpressionRollModifier(modifier)];
}

+ (NSString *)validRollCommandDelimiterCharacters
{
NSDictionary *validCharactersDict = [SA_DiceParser validCharactersDict];
+(NSString *) allValidRollModifierDelimiterCharacters {
NSDictionary *validRollModifierDelimiterCharactersDict = [SA_DiceParser validCharactersDict][SA_DB_VALID_ROLL_MODIFIER_DELIMITER_CHARACTERS];

return validCharactersDict[SA_DB_VALID_ROLL_COMMAND_DELIMITER_CHARACTERS];
return [validRollModifierDelimiterCharactersDict.allValues componentsJoinedByString:@""];
}

@end

Завантаження…
Відмінити
Зберегти