Initial commit.
This commit is contained in:
commit
5fcd8bdbd3
38
SA_DB_ErrorDescriptions.plist
Normal file
38
SA_DB_ErrorDescriptions.plist
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>SA_DB_ERROR_ROLL_STRING_EMPTY</key>
|
||||||
|
<string>Empty expression</string>
|
||||||
|
<key>SA_DB_ERROR_ROLL_STRING_HAS_ILLEGAL_CHARACTERS</key>
|
||||||
|
<string>Illegal characters detected</string>
|
||||||
|
<key>SA_DB_ERROR_UNKNOWN_ROLL_COMMAND</key>
|
||||||
|
<string>Unknown roll command</string>
|
||||||
|
<key>SA_DB_ERROR_DIE_COUNT_NEGATIVE</key>
|
||||||
|
<string>Die count negative</string>
|
||||||
|
<key>SA_DB_ERROR_DIE_COUNT_EXCESSIVE</key>
|
||||||
|
<string>Too many dice</string>
|
||||||
|
<key>SA_DB_ERROR_DIE_SIZE_INVALID</key>
|
||||||
|
<string>Invalid die size (zero or negative)</string>
|
||||||
|
<key>SA_DB_ERROR_DIE_SIZE_EXCESSIVE</key>
|
||||||
|
<string>Die size too big</string>
|
||||||
|
<key>SA_DB_ERROR_UNKNOWN_OPERATOR</key>
|
||||||
|
<string>Unknown operator</string>
|
||||||
|
<key>SA_DB_ERROR_INVALID_EXPRESSION</key>
|
||||||
|
<string>Invalid expression</string>
|
||||||
|
<key>SA_DB_ERROR_INTEGER_OVERFLOW_NEGATION</key>
|
||||||
|
<string>Integer overflow during negation</string>
|
||||||
|
<key>SA_DB_ERROR_INTEGER_OVERFLOW_ADDITION</key>
|
||||||
|
<string>Integer overflow during addition</string>
|
||||||
|
<key>SA_DB_ERROR_INTEGER_UNDERFLOW_ADDITION</key>
|
||||||
|
<string>Integer underflow during addition</string>
|
||||||
|
<key>SA_DB_ERROR_INTEGER_OVERFLOW_SUBTRACTION</key>
|
||||||
|
<string>Integer overflow during subtraction</string>
|
||||||
|
<key>SA_DB_ERROR_INTEGER_UNDERFLOW_SUBTRACTION</key>
|
||||||
|
<string>Integer underflow during subtraction</string>
|
||||||
|
<key>SA_DB_ERROR_INTEGER_OVERFLOW_MULTIPLICATION</key>
|
||||||
|
<string>Integer overflow during multiplication</string>
|
||||||
|
<key>SA_DB_ERROR_INTEGER_UNDERFLOW_MULTIPLICATION</key>
|
||||||
|
<string>Integer underflow during multiplication</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
36
SA_DB_StringFormatRules.plist
Normal file
36
SA_DB_StringFormatRules.plist
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>SA_DB_VALID_CHARACTERS</key>
|
||||||
|
<dict>
|
||||||
|
<key>SA_DB_VALID_NUMERAL_CHARACTERS</key>
|
||||||
|
<string>0123456789</string>
|
||||||
|
<key>SA_DB_VALID_OPERATOR_CHARACTERS</key>
|
||||||
|
<dict>
|
||||||
|
<key>SA_DB_OPERATOR_MINUS</key>
|
||||||
|
<string>-–−</string>
|
||||||
|
<key>SA_DB_OPERATOR_PLUS</key>
|
||||||
|
<string>+</string>
|
||||||
|
<key>SA_DB_OPERATOR_TIMES</key>
|
||||||
|
<string>*×⋅·</string>
|
||||||
|
</dict>
|
||||||
|
<key>SA_DB_VALID_ROLL_COMMAND_DELIMITER_CHARACTERS</key>
|
||||||
|
<string>dD</string>
|
||||||
|
</dict>
|
||||||
|
<key>SA_DB_CANONICAL_REPRESENTATIONS</key>
|
||||||
|
<dict>
|
||||||
|
<key>SA_DB_CANONICAL_OPERATOR_REPRESENTATIONS</key>
|
||||||
|
<dict>
|
||||||
|
<key>SA_DB_OPERATOR_MINUS</key>
|
||||||
|
<string>-</string>
|
||||||
|
<key>SA_DB_OPERATOR_PLUS</key>
|
||||||
|
<string>+</string>
|
||||||
|
<key>SA_DB_OPERATOR_TIMES</key>
|
||||||
|
<string>*</string>
|
||||||
|
</dict>
|
||||||
|
<key>SA_DB_CANONICAL_ROLL_COMMAND_DELIMITER_REPRESENTATION</key>
|
||||||
|
<string>d</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
26
SA_DiceBag.h
Normal file
26
SA_DiceBag.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// SA_DiceBag.h
|
||||||
|
// RPGBot
|
||||||
|
//
|
||||||
|
// Created by Sandy Achmiz on 12/30/15.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
/****************************************/
|
||||||
|
#pragma mark SA_DiceBag class declaration
|
||||||
|
/****************************************/
|
||||||
|
|
||||||
|
@interface SA_DiceBag : NSObject
|
||||||
|
|
||||||
|
/****************************/
|
||||||
|
#pragma mark - Public methods
|
||||||
|
/****************************/
|
||||||
|
|
||||||
|
- (unsigned long long)biggestPossibleDieSize;
|
||||||
|
|
||||||
|
- (unsigned long long)rollDie:(unsigned long long)die;
|
||||||
|
- (NSArray *)rollNumber:(NSNumber *)number ofDice:(unsigned long long)die;
|
||||||
|
|
||||||
|
@end
|
||||||
54
SA_DiceBag.m
Normal file
54
SA_DiceBag.m
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
//
|
||||||
|
// SA_DiceBag.m
|
||||||
|
// RPGBot
|
||||||
|
//
|
||||||
|
// Created by Sandy Achmiz on 12/30/15.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SA_DiceBag.h"
|
||||||
|
|
||||||
|
/*******************************************/
|
||||||
|
#pragma mark SA_DiceBag class implementation
|
||||||
|
/*******************************************/
|
||||||
|
|
||||||
|
@implementation SA_DiceBag
|
||||||
|
|
||||||
|
/****************************/
|
||||||
|
#pragma mark - Public methods
|
||||||
|
/****************************/
|
||||||
|
|
||||||
|
- (unsigned long long)biggestPossibleDieSize
|
||||||
|
{
|
||||||
|
return UINT32_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (unsigned long long)rollDie:(unsigned long long)dieSize
|
||||||
|
{
|
||||||
|
if(dieSize > UINT32_MAX)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (unsigned long long) arc4random_uniform((u_int32_t) dieSize) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSArray *)rollNumber:(NSNumber *)number ofDice:(unsigned long long)dieSize
|
||||||
|
{
|
||||||
|
if(dieSize > UINT32_MAX)
|
||||||
|
{
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long long numRolls = number.unsignedLongLongValue;
|
||||||
|
|
||||||
|
NSMutableArray *rollsArray = [NSMutableArray arrayWithCapacity:numRolls];
|
||||||
|
for(unsigned long long i = 0; i < numRolls; i++)
|
||||||
|
{
|
||||||
|
rollsArray[i] = @((unsigned long long) arc4random_uniform((u_int32_t) dieSize) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rollsArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
183
SA_DiceBot.h
Normal file
183
SA_DiceBot.h
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
//
|
||||||
|
// SA_DiceBot.h
|
||||||
|
// RPGBot
|
||||||
|
//
|
||||||
|
// Created by Sandy Achmiz on 12/30/15.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
/*
|
||||||
|
The SA_DiceBot class hierarchy works like this:
|
||||||
|
|
||||||
|
SA_DiceBot is the root class. A user of the SA_DiceBot package usually only
|
||||||
|
needs to create and manage objects of this class. An SA_DiceBot creates and
|
||||||
|
manages all other objects and classes in the package (directly or through its
|
||||||
|
other members), as needed.
|
||||||
|
|
||||||
|
An SA_DiceBot owns a single SA_DiceRoller and a single SA_DiceFormatter.
|
||||||
|
|
||||||
|
The objects owned by an SA_DiceBot are for the SA_DiceBot's internal use
|
||||||
|
only, and not accessible to users of the package. (But see SA_DiceRoller.h
|
||||||
|
and SA_DiceFormatter.h for information on standalone use.)
|
||||||
|
|
||||||
|
An SA_DiceRoller owns an SA_DiceParser and an SA_DiceEvaluator. An
|
||||||
|
SA_DiceEvaluator owns an SA_DiceBag. (See Figure 1, below.)
|
||||||
|
|
||||||
|
*--------------------------------------------------------------------------*
|
||||||
|
| Fig. 1. SA_DiceBot class hierarchy diagram. |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| SA_DiceBot |
|
||||||
|
| / \ |
|
||||||
|
| SA_DiceFormatter SA_DiceRoller |
|
||||||
|
| / \ |
|
||||||
|
| SA_DiceParser SA_DiceEvaluator |
|
||||||
|
| | |
|
||||||
|
| SA_DiceBag |
|
||||||
|
| |
|
||||||
|
*--------------------------------------------------------------------------*
|
||||||
|
|
||||||
|
When the SA_DiceBot receives a recognized command that involves rolling some
|
||||||
|
dice or otherwise evaluating some sort of string of semantically meaningful
|
||||||
|
text (like just adding some numbers), it extracts that string (which may
|
||||||
|
optionally include some text label) from the rest of the message body, and
|
||||||
|
passes it to the SA_DiceRoller.
|
||||||
|
|
||||||
|
The SA_DiceRoller strips off the label (if any), and passes the 'pure' string
|
||||||
|
to the SA_DiceParser.
|
||||||
|
|
||||||
|
The SA_DiceParser parses the string and produces an 'expression tree'
|
||||||
|
representation of the string. An expression tree is an NSDictionary with a
|
||||||
|
certain structure (which is described in SA_DiceExpressionStringConstants.h).
|
||||||
|
|
||||||
|
The SA_DiceRoller then passes the expression tree to the SA_DiceEvaluator.
|
||||||
|
The evaluator recursively traverses the expression tree in a depth-first
|
||||||
|
manner, computing the results of each subtree, and storing those results in
|
||||||
|
(a mutable copy of) the expression tree itself. Once the entire tree has been
|
||||||
|
evaluated, (a mutable copy of) it is returned to the SA_DiceRoller. The
|
||||||
|
SA_DiceRoller reattaches the stripped-off label (inserting it into the top
|
||||||
|
level of the expresion tree) and returns the evaluated expression tree
|
||||||
|
(now called a result) to the SA_DiceBot.
|
||||||
|
|
||||||
|
The SA_DiceBot passes the evaluated result tree to its SA_DiceFormatter.
|
||||||
|
The formatter traverses the tree, constructing a human-readable string form of
|
||||||
|
the results, with whatever formatting it (the formatter) has been configured
|
||||||
|
to provide. The formatter then returns this formatted result string to the
|
||||||
|
SA_DiceBot.
|
||||||
|
|
||||||
|
The SA_DiceBot then incorporates the formatted result string into some
|
||||||
|
appropriate reply message or messages, and sends the reply(ies) back to its
|
||||||
|
delegate, for transmission to the appropriate endpoint.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import "SA_Bot.h"
|
||||||
|
|
||||||
|
@class SA_CommandResponder;
|
||||||
|
|
||||||
|
/*
|
||||||
|
######################################
|
||||||
|
#### SA_DiceBot Usage Information ####
|
||||||
|
######################################
|
||||||
|
|
||||||
|
I. SUPPORTED COMMANDS & PARSER MODES
|
||||||
|
|
||||||
|
The set of commands, and the syntax and behavior of those commands, that an
|
||||||
|
SA_DiceBot supports depends on its currently set parser behavior mode (and, in
|
||||||
|
some cases, also on the currently set formatter behavior mode). (Read more
|
||||||
|
about parser behavior modes in SA_DiceParser.h, and about formatter behavior
|
||||||
|
modes in SA_DiceFormatter.h.)
|
||||||
|
|
||||||
|
Below is a list of available parser modes, along with the commands supported in
|
||||||
|
each mode. (See section II for a list of commands that are supported in all
|
||||||
|
parser modes.)
|
||||||
|
|
||||||
|
NOTE: Commands are not case-sensitive; e.g., 'roll', 'ROLL', and 'rOLl' all
|
||||||
|
work equally well.
|
||||||
|
|
||||||
|
1. 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".)
|
||||||
|
|
||||||
|
2. LEGACY mode
|
||||||
|
|
||||||
|
"Legacy" mode (mostly) emulates DiceBot by Sabin (and Dawn by xthemage before
|
||||||
|
it). The following commands are available in legacy mode.
|
||||||
|
|
||||||
|
1. ROLL command.
|
||||||
|
|
||||||
|
Takes 1 or more whitespace-delimited roll strings as parameters. The roll
|
||||||
|
command is executed once for every parameter, and each execution generates
|
||||||
|
a separate result string. One reply message is sent for each result string
|
||||||
|
(or, if the simple formatter is being used, the results may optionally be
|
||||||
|
collapsed into a single reply message).
|
||||||
|
|
||||||
|
Each roll string may optionally be suffixed with with a (configurable)
|
||||||
|
delimiter character (such as ';'), which may be followed with an arbitrary
|
||||||
|
text label (which may not contain whitespace, however). That label may then
|
||||||
|
be included in the result string (depending on the selected formatter
|
||||||
|
behavior mode and other formatter settings).
|
||||||
|
|
||||||
|
The body of the roll string (up to the label delimiter, if any) is simply
|
||||||
|
parsed and evaluated.
|
||||||
|
|
||||||
|
EXAMPLES (assuming legacy formatter behavior):
|
||||||
|
|
||||||
|
Obormot: !roll 1d20
|
||||||
|
SA_DiceBot: 1d20 < 14 = 14 > = 14
|
||||||
|
Obormot: .roll 2+4-5
|
||||||
|
SA_DiceBot: 2 + 4 - 5 = 1
|
||||||
|
Obormot: /roll 2d4 1d20+19 4d10
|
||||||
|
SA_DiceBot: 2d4 < 3 1 = 4 > = 4
|
||||||
|
SA_DiceBot: 1d20 < 5 = 5 > + 19 = 24
|
||||||
|
SA_DiceBot: 4d10 < 2 2 1 2 = 7 > = 7
|
||||||
|
Obormot: !roll 1d20+4;fort_save
|
||||||
|
SA_DiceBot: (fort_save) 1d20 < 8 = 8 > + 4 = 12
|
||||||
|
Obormot: SA_DiceBot: 1d8+6;longsword 1d6+3;shortsword
|
||||||
|
SA_DiceBot: (longsword) 1d8 < 4 = 4 > + 6 = 10
|
||||||
|
SA_DiceBot: (shortsword) 1d6 < 2 = 2 > + 3 = 5
|
||||||
|
|
||||||
|
2. TRY command.
|
||||||
|
|
||||||
|
Takes 1 or more whitespace-delimited roll strings. Prepends "1d20+" to each
|
||||||
|
roll string, and otherwise behaves in the same way as the ROLL command.
|
||||||
|
|
||||||
|
3. CHAR command.
|
||||||
|
|
||||||
|
II. OTHER COMMANDS
|
||||||
|
|
||||||
|
III. CONFIGURATION
|
||||||
|
*/
|
||||||
|
/****************************************/
|
||||||
|
#pragma mark SA_DiceBot class declaration
|
||||||
|
/****************************************/
|
||||||
|
|
||||||
|
@interface SA_DiceBot : SA_Bot
|
||||||
|
|
||||||
|
/************************/
|
||||||
|
#pragma mark - Properties
|
||||||
|
/************************/
|
||||||
|
|
||||||
|
@property (strong) SA_CommandResponder *botCommandresponder;
|
||||||
|
@property (strong) SA_CommandResponder *legacyCommandResponder;
|
||||||
|
|
||||||
|
@property (strong) SA_CommandResponder *currentCommandResponder;
|
||||||
|
|
||||||
|
@property (copy) NSString *commandDesignatorCharacters;
|
||||||
|
|
||||||
|
/****************************/
|
||||||
|
#pragma mark - Public methods
|
||||||
|
/****************************/
|
||||||
|
|
||||||
|
- (void)message:(NSString *)messageBody withInfo:(NSDictionary *)messageInfo;
|
||||||
|
|
||||||
|
/****************************/
|
||||||
|
#pragma mark - Helper methods
|
||||||
|
/****************************/
|
||||||
|
|
||||||
|
- (NSArray <NSDictionary *> *)repliesForCommandString:(NSString *)commandString messageInfo:(NSDictionary *)messageInfo byName:(BOOL)byName;
|
||||||
|
|
||||||
|
- (void)loadDefaultCommandResponders;
|
||||||
|
|
||||||
|
@end
|
||||||
144
SA_DiceBot.m
Normal file
144
SA_DiceBot.m
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
//
|
||||||
|
// SA_DiceBot.m
|
||||||
|
// RPGBot
|
||||||
|
//
|
||||||
|
// Created by Sandy Achmiz on 12/30/15.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SA_DiceBot.h"
|
||||||
|
|
||||||
|
#import "SA_BotDelegate.h"
|
||||||
|
#import "SA_CommandResponder.h"
|
||||||
|
#import "SA_BotCommandResponder.h"
|
||||||
|
#import "SA_LegacyCommandResponder.h"
|
||||||
|
#import "SA_ErrorCatalog.h"
|
||||||
|
#import "NSString+SA_NSStringExtensions.h"
|
||||||
|
#import "NSRange-Conventional.h"
|
||||||
|
|
||||||
|
/*********************************************/
|
||||||
|
#pragma mark - SA_DiceBot class implementation
|
||||||
|
/*********************************************/
|
||||||
|
|
||||||
|
@implementation SA_DiceBot
|
||||||
|
|
||||||
|
/**************************/
|
||||||
|
#pragma mark - Initializers
|
||||||
|
/**************************/
|
||||||
|
|
||||||
|
- (instancetype)init
|
||||||
|
{
|
||||||
|
return [self initWithName:@"DIE_BOT"];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithName:(NSString *)name
|
||||||
|
{
|
||||||
|
if(self = [super initWithName:name])
|
||||||
|
{
|
||||||
|
NSLog(NSLocalizedString(@"Initializing SA_DiceBot with name \"%@\"", @"{display name of the bot}"), name);
|
||||||
|
|
||||||
|
self.commandDesignatorCharacters = @".!/";
|
||||||
|
|
||||||
|
[self loadDefaultCommandResponders];
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************/
|
||||||
|
#pragma mark - Public methods
|
||||||
|
/****************************/
|
||||||
|
|
||||||
|
- (void)message:(NSString *)messageBody withInfo:(NSDictionary *)messageInfo
|
||||||
|
{
|
||||||
|
if([messageBody isEqualToString:@""])
|
||||||
|
{
|
||||||
|
// Ignore empty messages.
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NSRange commandRange;
|
||||||
|
BOOL byName;
|
||||||
|
|
||||||
|
// Is the message a possible command? That is, does it start with any of
|
||||||
|
// the permitted initial characters that designate a command?
|
||||||
|
NSString *firstChar = [messageBody substringToIndex:1];
|
||||||
|
if([self.commandDesignatorCharacters containsCharactersInString:firstChar])
|
||||||
|
{
|
||||||
|
commandRange = NSMakeRange(1, messageBody.length - 1);
|
||||||
|
byName = NO;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We also recognize commands that come after mentions of the bot's
|
||||||
|
// name at the beginning of the message.
|
||||||
|
NSRange possibleNameRange = NSRangeMake(0, self.name.length);
|
||||||
|
if(messageBody.length > self.name.length &&
|
||||||
|
[[messageBody substringWithRange:possibleNameRange] isEqualToString:self.name])
|
||||||
|
{
|
||||||
|
commandRange = [messageBody rangeToEndFrom:[messageBody firstNonWhitespaceAfterRange:[messageBody firstWhitespaceAfterRange:possibleNameRange]]];
|
||||||
|
byName = YES;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Does not begin with a command. Ignore.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the part of the string that is the actual command.
|
||||||
|
NSString *commandString = [messageBody substringWithRange:commandRange];
|
||||||
|
|
||||||
|
// Get the replies for this command.
|
||||||
|
NSArray <NSDictionary *> *replies = [self repliesForCommandString:commandString messageInfo:messageInfo byName:byName];
|
||||||
|
|
||||||
|
// Send the replies.
|
||||||
|
[replies enumerateObjectsUsingBlock:^(NSDictionary *reply, NSUInteger idx, BOOL *stop) {
|
||||||
|
[self.delegate SA_botMessage:reply[SA_DB_MESSAGE_BODY]
|
||||||
|
from:self
|
||||||
|
withInfo:reply[SA_DB_MESSAGE_INFO]];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************/
|
||||||
|
#pragma mark - Helper methods
|
||||||
|
/****************************/
|
||||||
|
|
||||||
|
- (NSArray <NSDictionary *> *)repliesForCommandString:(NSString *)commandString messageInfo:(NSDictionary *)messageInfo byName:(BOOL)byName
|
||||||
|
{
|
||||||
|
NSError *error;
|
||||||
|
NSArray <NSDictionary *> *replies = [self.botCommandresponder repliesForCommandString:commandString messageInfo:messageInfo error:&error];
|
||||||
|
|
||||||
|
if(error && error.code == SA_DiceBotErrorUnknownCommand)
|
||||||
|
{
|
||||||
|
error = nil;
|
||||||
|
replies = [self.currentCommandResponder repliesForCommandString:commandString messageInfo:messageInfo error:&error];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(error)
|
||||||
|
{
|
||||||
|
// Is outputting the provided error the right way to do error handling
|
||||||
|
// here? I don't know. Maybe not. For now, that's what it is.
|
||||||
|
NSString *errorReply = [NSString stringWithFormat:NSLocalizedString(@"ERROR: %@ (%@ %@)", @"{description}, {failure reason}, {recovery suggestion}"),
|
||||||
|
error.localizedDescription,
|
||||||
|
error.localizedFailureReason,
|
||||||
|
error.localizedRecoverySuggestion];
|
||||||
|
|
||||||
|
replies = [replies arrayByAddingObject:@{ SA_DB_MESSAGE_BODY : errorReply,
|
||||||
|
SA_DB_MESSAGE_INFO : messageInfo }];
|
||||||
|
}
|
||||||
|
|
||||||
|
return replies;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)loadDefaultCommandResponders
|
||||||
|
{
|
||||||
|
self.legacyCommandResponder = [SA_LegacyCommandResponder new];
|
||||||
|
self.botCommandresponder = [SA_BotCommandResponder new];
|
||||||
|
|
||||||
|
// The default command responder, in the current implementation, is the
|
||||||
|
// legacy command responder.
|
||||||
|
self.currentCommandResponder = self.legacyCommandResponder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
18
SA_DiceErrorHandling.h
Normal file
18
SA_DiceErrorHandling.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// SA_DiceErrorHandling.h
|
||||||
|
// RPGBot
|
||||||
|
//
|
||||||
|
// Created by Sandy Achmiz on 1/11/16.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
#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);
|
||||||
|
|
||||||
|
#endif /* SA_DiceErrorHandling_h */
|
||||||
56
SA_DiceErrorHandling.m
Normal file
56
SA_DiceErrorHandling.m
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
//
|
||||||
|
// SA_DiceErrorHandling.m
|
||||||
|
// RPGBot
|
||||||
|
//
|
||||||
|
// Created by Sandy Achmiz on 1/11/16.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SA_DiceErrorHandling.h"
|
||||||
|
|
||||||
|
#import "SA_DiceExpressionStringConstants.h"
|
||||||
|
|
||||||
|
void addErrorToExpression (NSString *error, NSMutableDictionary *expression)
|
||||||
|
{
|
||||||
|
if(error == nil || expression == nil)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(expression[SA_DB_ERRORS] == nil)
|
||||||
|
{
|
||||||
|
expression[SA_DB_ERRORS] = [NSMutableArray <NSString *> arrayWithObjects:error, nil];
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
targetExpression[SA_DB_ERRORS] = [NSMutableArray <NSString *> arrayWithArray:sourceExpression[SA_DB_ERRORS]];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[targetExpression[SA_DB_ERRORS] addObjectsFromArray:sourceExpression[SA_DB_ERRORS]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NSArray <NSString *> *getErrorsForExpression (NSDictionary *expression)
|
||||||
|
{
|
||||||
|
return ([expression[SA_DB_ERRORS] count] > 0) ? expression[SA_DB_ERRORS] : nil;
|
||||||
|
}
|
||||||
34
SA_DiceEvaluator.h
Normal file
34
SA_DiceEvaluator.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// SA_DiceEvaluator.h
|
||||||
|
// RPGBot
|
||||||
|
//
|
||||||
|
// Created by Sandy Achmiz on 1/2/16.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
@class SA_DiceBag;
|
||||||
|
|
||||||
|
/************************************************/
|
||||||
|
#pragma mark SA_DiceEvaluator class declaration
|
||||||
|
/************************************************/
|
||||||
|
|
||||||
|
@interface SA_DiceEvaluator : NSObject
|
||||||
|
|
||||||
|
/************************/
|
||||||
|
#pragma mark - Properties
|
||||||
|
/************************/
|
||||||
|
|
||||||
|
@property NSInteger maxDieCount;
|
||||||
|
@property NSInteger maxDieSize;
|
||||||
|
|
||||||
|
@property (strong) SA_DiceBag *diceBag;
|
||||||
|
|
||||||
|
/****************************/
|
||||||
|
#pragma mark - Public methods
|
||||||
|
/****************************/
|
||||||
|
|
||||||
|
- (NSMutableDictionary *)resultOfExpression:(NSDictionary *)expression;
|
||||||
|
|
||||||
|
@end
|
||||||
371
SA_DiceEvaluator.m
Normal file
371
SA_DiceEvaluator.m
Normal file
@ -0,0 +1,371 @@
|
|||||||
|
//
|
||||||
|
// SA_DiceEvaluator.m
|
||||||
|
// RPGBot
|
||||||
|
//
|
||||||
|
// Created by Sandy Achmiz on 1/2/16.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SA_DiceEvaluator.h"
|
||||||
|
|
||||||
|
#import "SA_DiceBag.h"
|
||||||
|
#import "SA_DiceParser.h"
|
||||||
|
#import "SA_DiceExpressionStringConstants.h"
|
||||||
|
#import "SA_DiceErrorHandling.h"
|
||||||
|
|
||||||
|
/**************************/
|
||||||
|
#pragma mark Defined values
|
||||||
|
/**************************/
|
||||||
|
|
||||||
|
#define DEFAULT_MAX_DIE_COUNT 1000 // One thousand
|
||||||
|
#define DEFAULT_MAX_DIE_SIZE 10000 // Ten thousand
|
||||||
|
|
||||||
|
/***************************************************/
|
||||||
|
#pragma mark - SA_DiceEvaluator class implementation
|
||||||
|
/***************************************************/
|
||||||
|
|
||||||
|
@implementation SA_DiceEvaluator
|
||||||
|
{
|
||||||
|
NSInteger _maxDieCount;
|
||||||
|
NSInteger _maxDieSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************/
|
||||||
|
#pragma mark - Properties
|
||||||
|
/************************/
|
||||||
|
|
||||||
|
- (NSInteger)maxDieCount
|
||||||
|
{
|
||||||
|
return _maxDieCount;
|
||||||
|
}
|
||||||
|
- (void)setMaxDieCount:(NSInteger)maxDieCount
|
||||||
|
{
|
||||||
|
if(maxDieCount < 1)
|
||||||
|
{
|
||||||
|
_maxDieCount = 1;
|
||||||
|
}
|
||||||
|
else if(maxDieCount > NSIntegerMax / _maxDieSize)
|
||||||
|
{
|
||||||
|
_maxDieCount = NSIntegerMax / _maxDieSize;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_maxDieCount = maxDieCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSInteger)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
|
||||||
|
{
|
||||||
|
_maxDieSize = maxDieSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************/
|
||||||
|
#pragma mark - Initializers
|
||||||
|
/**************************/
|
||||||
|
|
||||||
|
- (instancetype)init
|
||||||
|
{
|
||||||
|
if(self = [super init])
|
||||||
|
{
|
||||||
|
_maxDieCount = DEFAULT_MAX_DIE_COUNT;
|
||||||
|
_maxDieSize = DEFAULT_MAX_DIE_SIZE;
|
||||||
|
|
||||||
|
self.diceBag = [[SA_DiceBag alloc] init];
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************/
|
||||||
|
#pragma mark - Public methods
|
||||||
|
/****************************/
|
||||||
|
|
||||||
|
- (NSMutableDictionary *)resultOfExpression:(NSDictionary *)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];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
perfectly well-formed die string, and will yield an expression tree as follows:
|
||||||
|
|
||||||
|
@{SA_DB_TERM_TYPE : SA_DB_TERM_TYPE_ROLL_COMMAND,
|
||||||
|
SA_DB_ROLL_COMMAND : SA_DB_ROLL_COMMAND_SUM,
|
||||||
|
SA_DB_ROLL_DIE_COUNT : @{SA_DB_TERM_TYPE : SA_DB_TERM_TYPE_VALUE,
|
||||||
|
SA_DB_VALUE : @(5)
|
||||||
|
},
|
||||||
|
SA_DB_ROLL_DIE_SIZE : @{SA_DB_TERM_TYPE : SA_DB_TERM_TYPE_VALUE,
|
||||||
|
SA_DB_VALUE : @(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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************/
|
||||||
|
#pragma mark - Helper methods
|
||||||
|
/****************************/
|
||||||
|
|
||||||
|
- (NSMutableDictionary *)resultOfExpressionDescribingValue:(NSDictionary *)expression
|
||||||
|
{
|
||||||
|
NSMutableDictionary *result = [expression mutableCopy];
|
||||||
|
|
||||||
|
result[SA_DB_RESULT] = result[SA_DB_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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] = @[];
|
||||||
|
}
|
||||||
|
else if(dieCount == 1)
|
||||||
|
{
|
||||||
|
NSNumber *roll = @([self.diceBag rollDie:dieSize]);
|
||||||
|
|
||||||
|
result[SA_DB_RESULT] = roll;
|
||||||
|
result[SA_DB_ROLLS] = @[roll];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NSArray *rolls = [self.diceBag rollNumber:@(dieCount) ofDice:dieSize];
|
||||||
|
|
||||||
|
result[SA_DB_RESULT] = [rolls valueForKeyPath:@"@sum.self"];
|
||||||
|
result[SA_DB_ROLLS] = rolls;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the successfully evaluated roll command expression.
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
addErrorToExpression(SA_DB_ERROR_UNKNOWN_ROLL_COMMAND, result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSMutableDictionary *)resultOfExpressionDescribingOperation:(NSDictionary *)expression
|
||||||
|
{
|
||||||
|
NSMutableDictionary *result = [expression mutableCopy];
|
||||||
|
|
||||||
|
// 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]];
|
||||||
|
|
||||||
|
// Evaluating the operand 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)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 negation 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
addErrorToExpression(SA_DB_ERROR_INTEGER_UNDERFLOW_MULTIPLICATION, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
167
SA_DiceExpressionStringConstants.h
Normal file
167
SA_DiceExpressionStringConstants.h
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
//
|
||||||
|
// SA_DiceExpressionStringConstants.h
|
||||||
|
// RPGBot
|
||||||
|
//
|
||||||
|
// Created by Sandy Achmiz on 1/2/16.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
#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.
|
||||||
|
// (An operation expression with an SA_DB_OPERATOR value of SA_DB_OPERATOR_MINUS
|
||||||
|
// that does not have an SA_DB_OPERAND_LEFT value is simply a negation of the
|
||||||
|
// SA_DB_OPERAND_RIGHT value.)
|
||||||
|
// 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.
|
||||||
|
// NOTE: Despite being an NSInteger, this numeric value may not be negative.
|
||||||
|
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
|
||||||
|
/***************************************************************/
|
||||||
|
|
||||||
|
extern NSString * const SA_DB_STRING_FORMAT_RULES_PLIST_NAME;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The string format rules file (whose filename - minus the .plist extension -
|
||||||
|
is given by the SA_DB_STRING_FORMAT_RULES_PLIST_NAME string) contains
|
||||||
|
values for variables that define the properties of legal die roll strings,
|
||||||
|
as well as certain variables that define the format of result strings.
|
||||||
|
|
||||||
|
The file is organized as a dictionary contaning several sub-dictionaries. The
|
||||||
|
valid keys (those for which values are present in the file), and the values
|
||||||
|
that those keys may be expected to have, are listed below.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// The value for the top-level key SA_DB_VALID_CHARACTERS is a dictionary that
|
||||||
|
// defines those characters that are recognized as valid representations of
|
||||||
|
// various components of a die roll string. This dictionary has values for the
|
||||||
|
// keys listed below whose names begin with SA_DB_VALID_.
|
||||||
|
extern NSString * const SA_DB_VALID_CHARACTERS;
|
||||||
|
|
||||||
|
// The value for the key SA_DB_VALID_NUMERAL_CHARACTERS is a string that
|
||||||
|
// contains all characters that are recognized as representing decimal numerals.
|
||||||
|
// (Usually, this will be the 10 characters representing the standard Arabic
|
||||||
|
// numerals.)
|
||||||
|
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).
|
||||||
|
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'.)
|
||||||
|
extern NSString * const SA_DB_VALID_ROLL_COMMAND_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_.
|
||||||
|
extern NSString * const SA_DB_CANONICAL_REPRESENTATIONS;
|
||||||
|
|
||||||
|
// The value for the key SA_DB_CANONICAL_OPERATOR_REPRESENTATIONS is a
|
||||||
|
// 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).
|
||||||
|
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;
|
||||||
84
SA_DiceExpressionStringConstants.m
Normal file
84
SA_DiceExpressionStringConstants.m
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
//
|
||||||
|
// SA_DiceExpressionStringConstants.m
|
||||||
|
// RPGBot
|
||||||
|
//
|
||||||
|
// Created by Sandy Achmiz on 1/2/16.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
#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_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_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";
|
||||||
156
SA_DiceFormatter.h
Normal file
156
SA_DiceFormatter.h
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
//
|
||||||
|
// SA_DiceFormatter.h
|
||||||
|
// RPGBot
|
||||||
|
//
|
||||||
|
// Created by Sandy Achmiz on 12/31/15.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
/*********************/
|
||||||
|
#pragma mark Constants
|
||||||
|
/*********************/
|
||||||
|
/*
|
||||||
|
These constants describe one of several behavior modes for die string results
|
||||||
|
formatting.
|
||||||
|
|
||||||
|
NOTE: Each of the modes have their own set of settings, which may be configured
|
||||||
|
via various of the SA_DiceFormatter properties. Be sure to use the
|
||||||
|
appropriate settings for the currently set behavior mode.
|
||||||
|
|
||||||
|
NOTE 2: SA_DiceParser also has a behavior mode property, and can operate in
|
||||||
|
one of several parser behavior modes. Each formatter behavior mode is
|
||||||
|
appropriate for formatting results generated by some parser behavior modes but
|
||||||
|
not others. Attempting to format a results tree in a formatter mode that does
|
||||||
|
not support the parser mode in which those results were generated causes
|
||||||
|
undefined behavior. Thus, when using any a formatter and a parser together, we
|
||||||
|
must always make sure that the two objects are set to operate in compatible
|
||||||
|
behavior modes.
|
||||||
|
|
||||||
|
Each formatter behavior mode is described below. Each description also lists
|
||||||
|
the parser modes which are supported by that formatter mode.
|
||||||
|
|
||||||
|
======================
|
||||||
|
==== 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".)
|
||||||
|
|
||||||
|
=====================
|
||||||
|
==== LEGACY mode ====
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Legacy mode mostly emulates the output format of DiceBot by Sabin (and Dawn by
|
||||||
|
xthemage before it). (It adds optional error reporting, and several minor
|
||||||
|
aesthetic enhancement options.)
|
||||||
|
|
||||||
|
The legacy mode output format reproduces the entire die roll expression, in
|
||||||
|
an expanded form (with whitespace inserted between operators and operands);
|
||||||
|
prints each individual die roll (grouped by roll command), along with the
|
||||||
|
sum of each die group. The final result is also printed, as is a label
|
||||||
|
(if any). Typical legacy mode output looks like this:
|
||||||
|
|
||||||
|
(Hasan_the_Great) 4d6 < 3 3 4 6 = 16 > + 2d4 < 2 4 = 6 > + 3 + 20 = 45
|
||||||
|
|
||||||
|
This output line would be generated by a roll string as follows:
|
||||||
|
|
||||||
|
4d6+2d4+3+20;Hasan_the_Great
|
||||||
|
|
||||||
|
Here are some typical output strings for erroneous or malformed input strings.
|
||||||
|
|
||||||
|
INPUT:
|
||||||
|
8d6+4d0+5
|
||||||
|
|
||||||
|
OUTPUT:
|
||||||
|
8d6 < 3 1 2 5 2 2 1 6 = 22 > + 4d0 < ERROR > [ERROR: Invalid die size (zero or negative)]
|
||||||
|
|
||||||
|
Legacy mode does not support attributed text of any kind.
|
||||||
|
|
||||||
|
SUPPORTED PARSER MODES: Legacy.
|
||||||
|
|
||||||
|
=====================
|
||||||
|
==== SIMPLE mode ====
|
||||||
|
=====================
|
||||||
|
|
||||||
|
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
|
||||||
|
prints the label (if any).
|
||||||
|
|
||||||
|
SUPPORTED PARSER MODES: Feepbot, Legacy.
|
||||||
|
|
||||||
|
=====================
|
||||||
|
==== MODERN mode ====
|
||||||
|
=====================
|
||||||
|
|
||||||
|
>>> NOT YET IMPLEMENTED <<<
|
||||||
|
|
||||||
|
Modern mode supports a comprehensive range of commands and capabilities, and
|
||||||
|
has many configurable options for extensive customizability of the output
|
||||||
|
format. It also supports attributed text.
|
||||||
|
|
||||||
|
SUPPORTED PARSER MODES: Feepbot, Legacy, Modern.
|
||||||
|
|
||||||
|
======================
|
||||||
|
==== FEEPBOT mode ====
|
||||||
|
======================
|
||||||
|
|
||||||
|
>>> NOT YET IMPLEMENTED <<<
|
||||||
|
|
||||||
|
Feepbot mode emulates the output format of feepbot by feep.
|
||||||
|
|
||||||
|
SUPPORTED PARSER MODES: Feepbot, Legacy.
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
SA_DiceFormatterBehaviorDefault = 0,
|
||||||
|
SA_DiceFormatterBehaviorSimple = 1,
|
||||||
|
SA_DiceFormatterBehaviorLegacy = 1337,
|
||||||
|
SA_DiceFormatterBehaviorModern = 2001,
|
||||||
|
SA_DiceFormatterBehaviorFeepbot = 65516
|
||||||
|
} SA_DiceFormatterBehavior;
|
||||||
|
|
||||||
|
/************************************************/
|
||||||
|
#pragma mark - SA_DiceFormatter class declaration
|
||||||
|
/************************************************/
|
||||||
|
|
||||||
|
@interface SA_DiceFormatter : NSObject
|
||||||
|
|
||||||
|
/**********************************/
|
||||||
|
#pragma mark - Properties (general)
|
||||||
|
/**********************************/
|
||||||
|
|
||||||
|
@property SA_DiceFormatterBehavior formatterBehavior;
|
||||||
|
|
||||||
|
/*************************************************/
|
||||||
|
#pragma mark - Properties ("legacy" behavior mode)
|
||||||
|
/*************************************************/
|
||||||
|
|
||||||
|
@property BOOL legacyModeErrorReportingEnabled;
|
||||||
|
|
||||||
|
/****************************************/
|
||||||
|
#pragma mark - "Class property" accessors
|
||||||
|
/****************************************/
|
||||||
|
|
||||||
|
+ (void)setDefaultFormatterBehavior:(SA_DiceFormatterBehavior)defaultFormatterBehavior;
|
||||||
|
+ (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;
|
||||||
|
|
||||||
|
/****************************/
|
||||||
|
#pragma mark - Public methods
|
||||||
|
/****************************/
|
||||||
|
|
||||||
|
- (NSString *)stringFromExpression:(NSDictionary *)expression;
|
||||||
|
- (NSAttributedString *)attributedStringFromExpression:(NSDictionary *)expression;
|
||||||
|
|
||||||
|
@end
|
||||||
447
SA_DiceFormatter.m
Normal file
447
SA_DiceFormatter.m
Normal file
@ -0,0 +1,447 @@
|
|||||||
|
//
|
||||||
|
// SA_DiceFormatter.m
|
||||||
|
// RPGBot
|
||||||
|
//
|
||||||
|
// Created by Sandy Achmiz on 12/31/15.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SA_DiceFormatter.h"
|
||||||
|
|
||||||
|
#import "SA_DiceExpressionStringConstants.h"
|
||||||
|
#import "SA_DiceErrorHandling.h"
|
||||||
|
|
||||||
|
/********************************/
|
||||||
|
#pragma mark File-scope variables
|
||||||
|
/********************************/
|
||||||
|
|
||||||
|
static SA_DiceFormatterBehavior _defaultFormatterBehavior = SA_DiceFormatterBehaviorLegacy;
|
||||||
|
static NSDictionary *_errorDescriptions;
|
||||||
|
static NSDictionary *_stringFormatRules;
|
||||||
|
|
||||||
|
/**********************************************************/
|
||||||
|
#pragma mark - SA_DiceFormatter class implementation
|
||||||
|
/**********************************************************/
|
||||||
|
|
||||||
|
@implementation SA_DiceFormatter
|
||||||
|
{
|
||||||
|
SA_DiceFormatterBehavior _formatterBehavior;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************/
|
||||||
|
#pragma mark - Properties (general)
|
||||||
|
/**********************************/
|
||||||
|
|
||||||
|
- (void)setFormatterBehavior:(SA_DiceFormatterBehavior)newFormatterBehavior
|
||||||
|
{
|
||||||
|
_formatterBehavior = newFormatterBehavior;
|
||||||
|
|
||||||
|
switch (_formatterBehavior)
|
||||||
|
{
|
||||||
|
case SA_DiceFormatterBehaviorLegacy:
|
||||||
|
self.legacyModeErrorReportingEnabled = YES;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SA_DiceFormatterBehaviorSimple:
|
||||||
|
case SA_DiceFormatterBehaviorModern:
|
||||||
|
case SA_DiceFormatterBehaviorFeepbot:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SA_DiceFormatterBehaviorDefault:
|
||||||
|
default:
|
||||||
|
[self setFormatterBehavior:[SA_DiceFormatter defaultFormatterBehavior]];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (SA_DiceFormatterBehavior)formatterBehavior
|
||||||
|
{
|
||||||
|
return _formatterBehavior;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************/
|
||||||
|
#pragma mark - "Class property" accessors
|
||||||
|
/****************************************/
|
||||||
|
|
||||||
|
+ (void)setDefaultFormatterBehavior:(SA_DiceFormatterBehavior)newDefaultFormatterBehavior
|
||||||
|
{
|
||||||
|
if(newDefaultFormatterBehavior == SA_DiceFormatterBehaviorDefault)
|
||||||
|
{
|
||||||
|
_defaultFormatterBehavior = SA_DiceFormatterBehaviorLegacy;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_defaultFormatterBehavior = newDefaultFormatterBehavior;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (SA_DiceFormatterBehavior)defaultFormatterBehavior
|
||||||
|
{
|
||||||
|
return _defaultFormatterBehavior;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSDictionary *)stringFormatRules
|
||||||
|
{
|
||||||
|
if(_stringFormatRules == nil)
|
||||||
|
{
|
||||||
|
[SA_DiceFormatter loadStringFormatRules];
|
||||||
|
}
|
||||||
|
|
||||||
|
return _stringFormatRules;
|
||||||
|
}
|
||||||
|
|
||||||
|
/********************************************/
|
||||||
|
#pragma mark - Initializers & factory methods
|
||||||
|
/********************************************/
|
||||||
|
|
||||||
|
- (instancetype)init
|
||||||
|
{
|
||||||
|
return [self initWithBehavior:SA_DiceFormatterBehaviorDefault];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithBehavior:(SA_DiceFormatterBehavior)formatterBehavior
|
||||||
|
{
|
||||||
|
if(self = [super init])
|
||||||
|
{
|
||||||
|
self.formatterBehavior = formatterBehavior;
|
||||||
|
|
||||||
|
if(_errorDescriptions == nil)
|
||||||
|
{
|
||||||
|
[SA_DiceFormatter loadErrorDescriptions];
|
||||||
|
}
|
||||||
|
if(_stringFormatRules == nil)
|
||||||
|
{
|
||||||
|
[SA_DiceFormatter loadStringFormatRules];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (instancetype)defaultFormatter
|
||||||
|
{
|
||||||
|
return [[SA_DiceFormatter alloc] initWithBehavior:SA_DiceFormatterBehaviorDefault];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (instancetype)formatterWithBehavior:(SA_DiceFormatterBehavior)formatterBehavior
|
||||||
|
{
|
||||||
|
return [[SA_DiceFormatter alloc] initWithBehavior:formatterBehavior];
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************/
|
||||||
|
#pragma mark - Public methods
|
||||||
|
/****************************/
|
||||||
|
|
||||||
|
- (NSString *)stringFromExpression:(NSDictionary *)expression
|
||||||
|
{
|
||||||
|
if(_formatterBehavior == SA_DiceFormatterBehaviorSimple)
|
||||||
|
{
|
||||||
|
return [self simpleStringFromExpression:expression];
|
||||||
|
}
|
||||||
|
else // if(_formatterBehavior == SA_DiceFormatterBehaviorLegacy)
|
||||||
|
{
|
||||||
|
return [self legacyStringFromExpression:expression];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOT YET IMPLEMENTED
|
||||||
|
- (NSAttributedString *)attributedStringFromExpression:(NSDictionary *)expression
|
||||||
|
{
|
||||||
|
return [[NSAttributedString alloc] initWithString:[self stringFromExpression:expression]];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************/
|
||||||
|
#pragma mark - "Legacy" behavior implementation
|
||||||
|
/**********************************************/
|
||||||
|
|
||||||
|
// PROPERTIES
|
||||||
|
|
||||||
|
@synthesize legacyModeErrorReportingEnabled = _legacyModeErrorReportingEnabled;
|
||||||
|
|
||||||
|
// METHODS
|
||||||
|
|
||||||
|
- (NSString *)legacyStringFromExpression:(NSDictionary *)expression
|
||||||
|
{
|
||||||
|
__block NSMutableString *formattedString = [NSMutableString string];
|
||||||
|
|
||||||
|
// Attach the formatted string representation of the expression itself.
|
||||||
|
[formattedString appendString:[self legacyStringFromIntermediaryExpression:expression]];
|
||||||
|
|
||||||
|
// 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] > 1)
|
||||||
|
{
|
||||||
|
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:@"]"];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedString;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)legacyStringFromIntermediaryExpression:(NSDictionary *)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
|
||||||
|
expressions generated by a parser that does not support parentheses,
|
||||||
|
doing so would not make sense anyway).
|
||||||
|
|
||||||
|
The exception is roll commands, where the result of a roll-and-sum command
|
||||||
|
is printed along with the rolls.
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
// If the operator is not one of the supported operators, default to
|
||||||
|
// outputting the input string.
|
||||||
|
return expression[SA_DB_INPUT_STRING];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)legacyStringFromRollCommandExpression:(NSDictionary *)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.
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (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];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************/
|
||||||
|
#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"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************/
|
||||||
|
#pragma mark - Helper methods
|
||||||
|
/****************************/
|
||||||
|
|
||||||
|
+ (void)loadErrorDescriptions
|
||||||
|
{
|
||||||
|
NSString* errorDescriptionsPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"SA_DB_ErrorDescriptions" ofType:@"plist"];
|
||||||
|
_errorDescriptions = [NSDictionary dictionaryWithContentsOfFile:errorDescriptionsPath];
|
||||||
|
if(_errorDescriptions)
|
||||||
|
{
|
||||||
|
NSLog(@"Error descriptions loaded successfully.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NSLog(@"Could not load error descriptions!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSString *)descriptionForError:(NSString *)error
|
||||||
|
{
|
||||||
|
if(_errorDescriptions == nil)
|
||||||
|
{
|
||||||
|
[SA_DiceFormatter loadErrorDescriptions];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_errorDescriptions[error] != nil)
|
||||||
|
{
|
||||||
|
return _errorDescriptions[error];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (void)loadStringFormatRules
|
||||||
|
{
|
||||||
|
NSString *stringFormatRulesPath = [[NSBundle bundleForClass:[self class]] pathForResource:SA_DB_STRING_FORMAT_RULES_PLIST_NAME ofType:@"plist"];
|
||||||
|
_stringFormatRules = [NSDictionary dictionaryWithContentsOfFile:stringFormatRulesPath];
|
||||||
|
if(_stringFormatRules)
|
||||||
|
{
|
||||||
|
NSLog(@"String format rules loaded successfully.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NSLog(@"Could not load string format rules!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSString *)canonicalRepresentationForOperator:(NSString *)operatorName
|
||||||
|
{
|
||||||
|
return [SA_DiceFormatter canonicalOperatorRepresentations][operatorName];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (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];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
155
SA_DiceParser.h
Normal file
155
SA_DiceParser.h
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
//
|
||||||
|
// SA_DiceParser.h
|
||||||
|
// RPGBot
|
||||||
|
//
|
||||||
|
// Created by Sandy Achmiz on 12/30/15.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
/*********************/
|
||||||
|
#pragma mark Constants
|
||||||
|
/*********************/
|
||||||
|
/*
|
||||||
|
These constants describe one of several behavior modes for roll string parsing.
|
||||||
|
|
||||||
|
Each behavior mode defines a set of capabilities - what sorts of expressions,
|
||||||
|
operators, roll commands, etc. the parser recognizes in that mode, and the
|
||||||
|
syntax for using those capabilities.
|
||||||
|
|
||||||
|
The structure and content of the expression tree generated by the parser from
|
||||||
|
a given die roll string also depends on the behavior mode that the parser is
|
||||||
|
set to.
|
||||||
|
|
||||||
|
NOTE: While SA_DiceEvaluator is modeless (it correctly evaluates all
|
||||||
|
expressions generated by the parser in any supported mode),
|
||||||
|
SA_DiceFormatter is modal. SA_DiceFormatter provides several
|
||||||
|
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
|
||||||
|
formatter behavior mode) results in undefined behavior.
|
||||||
|
|
||||||
|
See SA_DiceFormatter.h for a list of which formatter behavior modes are
|
||||||
|
appropriate for use with which parser modes. See also SA_DiceBot.h for a
|
||||||
|
discussion of how the currently set parser mode affects what die-roll-related
|
||||||
|
bot commands are available. (Together, the parser behavior mode and the results
|
||||||
|
formatter behavior mode define the behavior and capabilities of an SA_DiceBot.)
|
||||||
|
|
||||||
|
Each mode is described below.
|
||||||
|
|
||||||
|
======================
|
||||||
|
==== 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".)
|
||||||
|
|
||||||
|
=====================
|
||||||
|
==== LEGACY mode ====
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Legacy mode (mostly) emulates DiceBot by Sabin (and Dawn by xthemage before
|
||||||
|
it). It replicates the parsing and evaluation functions of those venerable
|
||||||
|
bots, providing the following capabilities:
|
||||||
|
|
||||||
|
1. Arithmetic operations: addition, subtraction, multiplication. Syntax is as
|
||||||
|
normal for such operations, e.g.:
|
||||||
|
|
||||||
|
2+3
|
||||||
|
25*10
|
||||||
|
5-3*2
|
||||||
|
|
||||||
|
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
|
||||||
|
is a positive integer, e.g.:
|
||||||
|
|
||||||
|
1d20
|
||||||
|
5d6
|
||||||
|
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
|
||||||
|
positive integer and X may be a nonnegative integer or a recursive roll-and-sum
|
||||||
|
expression, e.g.:
|
||||||
|
|
||||||
|
5d6d10
|
||||||
|
1d20d6
|
||||||
|
4d4d4d4d4d4d4
|
||||||
|
|
||||||
|
The above capabilities may be used indiscriminately in a single roll string:
|
||||||
|
|
||||||
|
1d20-5
|
||||||
|
5d6*10
|
||||||
|
1d20+2d4*10
|
||||||
|
5d6d10-2*3
|
||||||
|
5+3-2*4d6+2d10d3-20+5d4*2
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
=====================
|
||||||
|
==== MODERN mode ====
|
||||||
|
=====================
|
||||||
|
|
||||||
|
>>> NOT YET IMPLEMENTED <<<
|
||||||
|
|
||||||
|
Modern mode provides a comprehensive range of commands and capabilities.
|
||||||
|
|
||||||
|
======================
|
||||||
|
==== FEEPBOT mode ====
|
||||||
|
======================
|
||||||
|
|
||||||
|
>>> NOT YET IMPLEMENTED <<<
|
||||||
|
|
||||||
|
Feepbot mode emulates feepbot by feep.
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
SA_DiceParserBehaviorDefault = 0,
|
||||||
|
SA_DiceParserBehaviorLegacy = 1337,
|
||||||
|
SA_DiceParserBehaviorModern = 2001,
|
||||||
|
SA_DiceParserBehaviorFeepbot = 65516
|
||||||
|
} SA_DiceParserBehavior;
|
||||||
|
|
||||||
|
/*********************************************/
|
||||||
|
#pragma mark - SA_DiceParser class declaration
|
||||||
|
/*********************************************/
|
||||||
|
|
||||||
|
@interface SA_DiceParser : NSObject
|
||||||
|
|
||||||
|
/************************/
|
||||||
|
#pragma mark - Properties
|
||||||
|
/************************/
|
||||||
|
|
||||||
|
@property SA_DiceParserBehavior parserBehavior;
|
||||||
|
|
||||||
|
/****************************************/
|
||||||
|
#pragma mark - "Class property" accessors
|
||||||
|
/****************************************/
|
||||||
|
|
||||||
|
+ (void)setDefaultParserBehavior:(SA_DiceParserBehavior)defaultParserBehavior;
|
||||||
|
+ (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;
|
||||||
|
|
||||||
|
/****************************/
|
||||||
|
#pragma mark - Public methods
|
||||||
|
/****************************/
|
||||||
|
|
||||||
|
- (NSDictionary *)expressionForString:(NSString *)dieRollString;
|
||||||
|
|
||||||
|
@end
|
||||||
413
SA_DiceParser.m
Normal file
413
SA_DiceParser.m
Normal file
@ -0,0 +1,413 @@
|
|||||||
|
//
|
||||||
|
// SA_DiceParser.m
|
||||||
|
// RPGBot
|
||||||
|
//
|
||||||
|
// Created by Sandy Achmiz on 12/30/15.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SA_DiceParser.h"
|
||||||
|
|
||||||
|
#import "SA_DiceExpressionStringConstants.h"
|
||||||
|
#import "SA_DiceErrorHandling.h"
|
||||||
|
#import "NSString+SA_NSStringExtensions.h"
|
||||||
|
|
||||||
|
/********************************/
|
||||||
|
#pragma mark File-scope variables
|
||||||
|
/********************************/
|
||||||
|
|
||||||
|
static SA_DiceParserBehavior _defaultParserBehavior = SA_DiceParserBehaviorLegacy;
|
||||||
|
static NSDictionary *_validCharactersDict;
|
||||||
|
|
||||||
|
/************************************************/
|
||||||
|
#pragma mark - SA_DiceParser class implementation
|
||||||
|
/************************************************/
|
||||||
|
|
||||||
|
@implementation SA_DiceParser
|
||||||
|
{
|
||||||
|
SA_DiceParserBehavior _parserBehavior;
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************/
|
||||||
|
#pragma mark - Properties
|
||||||
|
/************************/
|
||||||
|
|
||||||
|
- (void)setParserBehavior:(SA_DiceParserBehavior)newParserBehavior
|
||||||
|
{
|
||||||
|
_parserBehavior = newParserBehavior;
|
||||||
|
|
||||||
|
switch (_parserBehavior)
|
||||||
|
{
|
||||||
|
case SA_DiceParserBehaviorLegacy:
|
||||||
|
case SA_DiceParserBehaviorModern:
|
||||||
|
case SA_DiceParserBehaviorFeepbot:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SA_DiceParserBehaviorDefault:
|
||||||
|
default:
|
||||||
|
_parserBehavior = [SA_DiceParser defaultParserBehavior];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (SA_DiceParserBehavior)parserBehavior
|
||||||
|
{
|
||||||
|
return _parserBehavior;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************/
|
||||||
|
#pragma mark - "Class property" accessors
|
||||||
|
/****************************************/
|
||||||
|
|
||||||
|
+ (void)setDefaultParserBehavior:(SA_DiceParserBehavior)newDefaultParserBehavior
|
||||||
|
{
|
||||||
|
if(newDefaultParserBehavior == SA_DiceParserBehaviorDefault)
|
||||||
|
{
|
||||||
|
_defaultParserBehavior = SA_DiceParserBehaviorLegacy;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_defaultParserBehavior = newDefaultParserBehavior;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (SA_DiceParserBehavior)defaultParserBehavior
|
||||||
|
{
|
||||||
|
return _defaultParserBehavior;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSDictionary *)validCharactersDict
|
||||||
|
{
|
||||||
|
if(_validCharactersDict == nil)
|
||||||
|
{
|
||||||
|
[SA_DiceParser loadValidCharactersDict];
|
||||||
|
}
|
||||||
|
|
||||||
|
return _validCharactersDict;
|
||||||
|
}
|
||||||
|
|
||||||
|
/********************************************/
|
||||||
|
#pragma mark - Initializers & factory methods
|
||||||
|
/********************************************/
|
||||||
|
|
||||||
|
- (instancetype)init
|
||||||
|
{
|
||||||
|
return [self initWithBehavior:SA_DiceParserBehaviorDefault];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithBehavior:(SA_DiceParserBehavior)parserBehavior
|
||||||
|
{
|
||||||
|
if(self = [super init])
|
||||||
|
{
|
||||||
|
self.parserBehavior = parserBehavior;
|
||||||
|
|
||||||
|
if(_validCharactersDict == nil)
|
||||||
|
{
|
||||||
|
[SA_DiceParser loadValidCharactersDict];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (instancetype)defaultParser
|
||||||
|
{
|
||||||
|
return [[SA_DiceParser alloc] initWithBehavior:SA_DiceParserBehaviorDefault];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (instancetype)parserWithBehavior:(SA_DiceParserBehavior)parserBehavior
|
||||||
|
{
|
||||||
|
return [[SA_DiceParser alloc] initWithBehavior:parserBehavior];
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************/
|
||||||
|
#pragma mark - Public methods
|
||||||
|
/****************************/
|
||||||
|
|
||||||
|
- (NSDictionary *)expressionForString:(NSString *)dieRollString
|
||||||
|
{
|
||||||
|
if(_parserBehavior == SA_DiceParserBehaviorLegacy)
|
||||||
|
{
|
||||||
|
return [self legacyExpressionForString:dieRollString];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return @{};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************/
|
||||||
|
#pragma mark - "Legacy" behavior implementation
|
||||||
|
/**********************************************/
|
||||||
|
|
||||||
|
- (NSDictionary *)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] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we have checked the entire string for forbidden characters, we can
|
||||||
|
// now begin parsing the string; there is no need to check substrings for
|
||||||
|
// illegal characters (which is why we do it only once, in this wrapper
|
||||||
|
// method). When constructing the expression tree, we call
|
||||||
|
// legacyExpressionForLegalString:, not legacyExpressionForString:, when
|
||||||
|
// recursively parsing substrings.
|
||||||
|
return [self legacyExpressionForLegalString:dieRollString];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSDictionary *)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] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// We now know the string describes one of the allowable expression types
|
||||||
|
// (probably; it could be malformed in some way other than being empty or
|
||||||
|
// containing forbidden characters, such as e.g. by starting with a + sign).
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
NSString *operator = [dieRollString substringWithRange:lastOperatorRange];
|
||||||
|
|
||||||
|
return [self legacyExpressionForStringDescribingOperation:dieRollString withOperator:operator atRange:lastOperatorRange];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not an operation nor a roll command, the top-level term can only be
|
||||||
|
// a simple numeric value.
|
||||||
|
return [self legacyExpressionForStringDescribingNumericValue:dieRollString];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSDictionary *)legacyExpressionForStringDescribingOperation:(NSString *)dieRollString withOperator:(NSString *)operator atRange:(NSRange)operatorRange
|
||||||
|
{
|
||||||
|
NSMutableDictionary *expression;
|
||||||
|
|
||||||
|
// If the operator is at the beginning, this may be negation; we handle that
|
||||||
|
// case later below. Otherwise, it's a binary operator.
|
||||||
|
if(operatorRange.location != 0)
|
||||||
|
{
|
||||||
|
expression = [NSMutableDictionary dictionary];
|
||||||
|
expression[SA_DB_TERM_TYPE] = SA_DB_TERM_TYPE_OPERATION;
|
||||||
|
expression[SA_DB_INPUT_STRING] = 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)]];
|
||||||
|
|
||||||
|
// Check to see if the term is a subtraction operation.
|
||||||
|
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 an addition operation.
|
||||||
|
else 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 multiplication operation.
|
||||||
|
else if([[SA_DiceParser validCharactersForOperator:SA_DB_OPERATOR_TIMES] containsCharactersInString:operator])
|
||||||
|
{
|
||||||
|
// 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
expression[SA_DB_OPERATOR] = SA_DB_OPERATOR_TIMES;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
addErrorToExpression(SA_DB_ERROR_UNKNOWN_OPERATOR, expression);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check to see if the term is a negation operation (we can only reach this
|
||||||
|
// case if the subtraction operator is at the beginning).
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(dieRollString.length == operatorRange.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([[SA_DiceParser validCharactersForOperator:SA_DB_OPERATOR_MINUS] containsCharactersInString:operator])
|
||||||
|
{
|
||||||
|
// It turns out this isn't actually an operation expression. It's a
|
||||||
|
// simple value expression with 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...
|
||||||
|
NSString *fixedRollString = [dieRollString stringByReplacingCharactersInRange:operatorRange withString:@"-"];
|
||||||
|
|
||||||
|
return [self legacyExpressionForStringDescribingNumericValue:fixedRollString];
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
|
||||||
|
return 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);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// 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]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)]];
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************/
|
||||||
|
#pragma mark - Helper methods
|
||||||
|
/****************************/
|
||||||
|
|
||||||
|
+ (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)
|
||||||
|
{
|
||||||
|
NSLog(@"Valid characters dictionary loaded successfully.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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 *)allValidOperatorCharacters
|
||||||
|
{
|
||||||
|
NSDictionary *validCharactersDict = [SA_DiceParser validCharactersDict];
|
||||||
|
|
||||||
|
__block NSMutableString *validOperatorCharactersString = [NSMutableString string];
|
||||||
|
|
||||||
|
[validCharactersDict[SA_DB_VALID_OPERATOR_CHARACTERS] enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) {
|
||||||
|
[validOperatorCharactersString appendString:value];
|
||||||
|
}];
|
||||||
|
|
||||||
|
return validOperatorCharactersString;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSString *)validCharactersForOperator:(NSString *)operatorName
|
||||||
|
{
|
||||||
|
return [SA_DiceParser validCharactersDict][SA_DB_VALID_OPERATOR_CHARACTERS][operatorName];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSString *)validNumeralCharacters
|
||||||
|
{
|
||||||
|
NSDictionary *validCharactersDict = [SA_DiceParser validCharactersDict];
|
||||||
|
|
||||||
|
return validCharactersDict[SA_DB_VALID_NUMERAL_CHARACTERS];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSString *)validRollCommandDelimiterCharacters
|
||||||
|
{
|
||||||
|
NSDictionary *validCharactersDict = [SA_DiceParser validCharactersDict];
|
||||||
|
|
||||||
|
return validCharactersDict[SA_DB_VALID_ROLL_COMMAND_DELIMITER_CHARACTERS];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
Loading…
Reference in New Issue
Block a user