Correct output bug and remove extraneous files
This commit is contained in:
parent
a8d4fdf24b
commit
9f45d5b5d3
183
SA_DiceBot.h
183
SA_DiceBot.h
@ -1,183 +0,0 @@
|
|||||||
//
|
|
||||||
// SA_DiceBot.h
|
|
||||||
//
|
|
||||||
// Copyright (c) 2016 Said Achmiz.
|
|
||||||
//
|
|
||||||
// This software is licensed under the MIT license.
|
|
||||||
// See the file "LICENSE" for more information.
|
|
||||||
/*
|
|
||||||
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
144
SA_DiceBot.m
@ -1,144 +0,0 @@
|
|||||||
//
|
|
||||||
// SA_DiceBot.m
|
|
||||||
//
|
|
||||||
// Copyright (c) 2016 Said Achmiz.
|
|
||||||
//
|
|
||||||
// This software is licensed under the MIT license.
|
|
||||||
// See the file "LICENSE" for more information.
|
|
||||||
|
|
||||||
#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
|
|
||||||
@ -173,7 +173,7 @@ static NSDictionary *_stringFormatRules;
|
|||||||
{
|
{
|
||||||
[formattedString appendFormat:@" = %@", expression[SA_DB_RESULT]];
|
[formattedString appendFormat:@" = %@", expression[SA_DB_RESULT]];
|
||||||
}
|
}
|
||||||
else if(_legacyModeErrorReportingEnabled == YES && [getErrorsForExpression(expression) count] > 1)
|
else if(_legacyModeErrorReportingEnabled == YES && [getErrorsForExpression(expression) count] > 0)
|
||||||
{
|
{
|
||||||
if([getErrorsForExpression(expression) count] == 1)
|
if([getErrorsForExpression(expression) count] == 1)
|
||||||
{
|
{
|
||||||
@ -187,7 +187,7 @@ static NSDictionary *_stringFormatRules;
|
|||||||
[formattedString appendString:[SA_DiceFormatter descriptionForError:error]];
|
[formattedString appendString:[SA_DiceFormatter descriptionForError:error]];
|
||||||
if(idx != [getErrorsForExpression(expression) count] - 1)
|
if(idx != [getErrorsForExpression(expression) count] - 1)
|
||||||
{
|
{
|
||||||
[formattedString appendFormat:@", "];
|
[formattedString appendFormat:@" / "];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user