Fundamental Redesign of Evaluation
This commit is contained in:
20
MathPad/MPElementaryFunctionTerm.h
Normal file
20
MathPad/MPElementaryFunctionTerm.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// MPFunctionValue.h
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 11.10.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPTerm.h"
|
||||||
|
|
||||||
|
@class MPElementaryFunctionTerm, MPValueGroup;
|
||||||
|
|
||||||
|
@interface MPElementaryFunctionTerm : MPTerm
|
||||||
|
|
||||||
|
- (instancetype)initWithFunctionIdentifier:(NSString *)function
|
||||||
|
term:(MPTerm *)term; /* designated initializer */
|
||||||
|
|
||||||
|
@property (readonly, nonatomic, strong) MPTerm *term;
|
||||||
|
|
||||||
|
@end
|
||||||
109
MathPad/MPElementaryFunctionTerm.m
Normal file
109
MathPad/MPElementaryFunctionTerm.m
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
//
|
||||||
|
// MPFunctionValue.m
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 11.10.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPElementaryFunctionTerm.h"
|
||||||
|
|
||||||
|
#import "MPParsedExpression.h"
|
||||||
|
#import "MPPowerFunction.h"
|
||||||
|
#import "MPProductTerm.h"
|
||||||
|
#import "MPTokenStream.h"
|
||||||
|
#import "MPToken.h"
|
||||||
|
|
||||||
|
#import "MPExpression.h"
|
||||||
|
#import "MPMathRules.h"
|
||||||
|
|
||||||
|
@implementation MPElementaryFunctionTerm {
|
||||||
|
NSDecimalNumber *(^_function)(NSDecimalNumber *);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithFunctionIdentifier:(NSString *)function
|
||||||
|
term:(MPTerm *)term
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
if (self) {
|
||||||
|
NSAssert(function != nil, @"function must not be nil.");
|
||||||
|
NSAssert(term != nil, @"term must not be nil.");
|
||||||
|
double (*func)(double);
|
||||||
|
BOOL takesArc = NO;
|
||||||
|
BOOL returnsArc = NO;
|
||||||
|
if ([function isEqualToString:@"sin"]) {
|
||||||
|
func = &sin;
|
||||||
|
takesArc = YES;
|
||||||
|
} else if ([function isEqualToString:@"cos"]) {
|
||||||
|
func = &cos;
|
||||||
|
takesArc = YES;
|
||||||
|
} else if ([function isEqualToString:@"tan"]) {
|
||||||
|
func = &tan;
|
||||||
|
takesArc = YES;
|
||||||
|
} else if ([function isEqualToString:@"asin"]) {
|
||||||
|
func = &asin;
|
||||||
|
returnsArc = YES;
|
||||||
|
} else if ([function isEqualToString:@"acos"]) {
|
||||||
|
func = &acos;
|
||||||
|
returnsArc = YES;
|
||||||
|
} else if ([function isEqualToString:@"atan"]) {
|
||||||
|
func = &atan;
|
||||||
|
returnsArc = YES;
|
||||||
|
} else {
|
||||||
|
NSAssert(true, @"function must be one of (sin, cos, tan, asin, acos, atan).");
|
||||||
|
}
|
||||||
|
[self setFunction:func
|
||||||
|
takesArcValue:takesArc
|
||||||
|
returnsArcValue:returnsArc];
|
||||||
|
_term = term;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setFunction:(double (*)(double))function
|
||||||
|
takesArcValue:(BOOL)takesArc
|
||||||
|
returnsArcValue:(BOOL)returnsArc
|
||||||
|
{
|
||||||
|
id __weak weakSelf = self;
|
||||||
|
_function = ^(NSDecimalNumber *value){
|
||||||
|
if (takesArc) {
|
||||||
|
value = [weakSelf convertToRadiantsIfNecessary:value];
|
||||||
|
}
|
||||||
|
NSDecimalNumber *returnValue = [[NSDecimalNumber alloc] initWithDouble:function(value.doubleValue)];
|
||||||
|
if (returnsArc) {
|
||||||
|
returnValue = [weakSelf convertToDegreesIfNecessary:returnValue];
|
||||||
|
}
|
||||||
|
return returnValue;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSDecimalNumber *)convertToRadiantsIfNecessary:(NSDecimalNumber *)degrees
|
||||||
|
{
|
||||||
|
if ([MPMathRules sharedRules].isUsingDegrees) {
|
||||||
|
// * M_PI / 180
|
||||||
|
return [[degrees decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithInteger:180]];
|
||||||
|
} else {
|
||||||
|
return degrees;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSDecimalNumber *)convertToDegreesIfNecessary:(NSDecimalNumber *)radiants
|
||||||
|
{
|
||||||
|
if ([MPMathRules sharedRules].isUsingDegrees) {
|
||||||
|
// * 180 / M_PI
|
||||||
|
return [[radiants decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:180]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]];
|
||||||
|
} else {
|
||||||
|
return radiants;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||||
|
{
|
||||||
|
NSDecimalNumber *value = [self.term evaluate:error];
|
||||||
|
if (!value) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
return _function(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -15,9 +15,11 @@
|
|||||||
- (void)push;
|
- (void)push;
|
||||||
- (void)pop;
|
- (void)pop;
|
||||||
|
|
||||||
- (void)defineVariable:(NSString *)variable withValue:(NSDecimalNumber *)value;
|
- (BOOL)defineVariable:(NSString *)variable
|
||||||
|
value:(NSDecimalNumber *)value;
|
||||||
|
- (BOOL)redefineVariable:(NSString *)variable
|
||||||
|
value:(NSDecimalNumber *)value;
|
||||||
- (void)undefineVariable:(NSString *)variable;
|
- (void)undefineVariable:(NSString *)variable;
|
||||||
- (BOOL)isVariableDefined:(NSString *)variable;
|
|
||||||
|
|
||||||
- (NSDecimalNumber *)valueForVariable:(NSString *)variable;
|
- (NSDecimalNumber *)valueForVariable:(NSString *)variable;
|
||||||
|
|
||||||
|
|||||||
@@ -32,8 +32,10 @@ static MPEvaluationContext *sharedContext;
|
|||||||
if (self) {
|
if (self) {
|
||||||
_stack = [[NSMutableArray alloc] init];
|
_stack = [[NSMutableArray alloc] init];
|
||||||
[self push];
|
[self push];
|
||||||
[self defineVariable:@"e" withValue:[[NSDecimalNumber alloc] initWithDouble:M_E]];
|
[self defineVariable:@"e"
|
||||||
[self defineVariable:@"π" withValue:[[NSDecimalNumber alloc] initWithDouble:M_PI]];
|
value:[[NSDecimalNumber alloc] initWithDouble:M_E]];
|
||||||
|
[self defineVariable:@"π"
|
||||||
|
value:[[NSDecimalNumber alloc] initWithDouble:M_PI]];
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
@@ -48,10 +50,23 @@ static MPEvaluationContext *sharedContext;
|
|||||||
[self.stack removeLastObject];
|
[self.stack removeLastObject];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)defineVariable:(NSString *)variable withValue:(NSDecimalNumber *)value
|
- (BOOL)defineVariable:(NSString *)variable
|
||||||
|
value:(NSDecimalNumber *)value
|
||||||
{
|
{
|
||||||
|
if ([self isVariableDefined:variable]) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
NSMutableDictionary *currentBindings = self.stack.lastObject;
|
NSMutableDictionary *currentBindings = self.stack.lastObject;
|
||||||
currentBindings[variable] = value;
|
currentBindings[variable] = value;
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)redefineVariable:(NSString *)variable
|
||||||
|
value:(NSDecimalNumber *)value
|
||||||
|
{
|
||||||
|
[self undefineVariable:variable];
|
||||||
|
return [self defineVariable:variable
|
||||||
|
value:value];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)undefineVariable:(NSString *)variable
|
- (void)undefineVariable:(NSString *)variable
|
||||||
@@ -60,6 +75,11 @@ static MPEvaluationContext *sharedContext;
|
|||||||
[currentBindings removeObjectForKey:variable];
|
[currentBindings removeObjectForKey:variable];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL)isVariableDefined:(NSString *)variable
|
||||||
|
{
|
||||||
|
return [self valueForVariable:variable] != nil;
|
||||||
|
}
|
||||||
|
|
||||||
- (NSDecimalNumber *)valueForVariable:(NSString *)variable
|
- (NSDecimalNumber *)valueForVariable:(NSString *)variable
|
||||||
{
|
{
|
||||||
NSUInteger currentIndex = self.stack.count;
|
NSUInteger currentIndex = self.stack.count;
|
||||||
@@ -72,9 +92,4 @@ static MPEvaluationContext *sharedContext;
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isVariableDefined:(NSString *)variable
|
|
||||||
{
|
|
||||||
return [self valueForVariable:variable] != nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -31,57 +31,6 @@ FOUNDATION_EXPORT NSString *const MPIllegalElementException;
|
|||||||
FOUNDATION_EXPORT NSString *const MPIllegalElementExceptionElementKey;
|
FOUNDATION_EXPORT NSString *const MPIllegalElementExceptionElementKey;
|
||||||
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@const MPMathKitErrorDomain
|
|
||||||
@brief Predefined error domain for errors from the MathKit framework.
|
|
||||||
|
|
||||||
@discussion Errors in MathKit can occur during parsing of expressions or
|
|
||||||
during evaluation of expressions. These two can be distinguished
|
|
||||||
by the error code. Parsing errors have lower error codes.
|
|
||||||
Evaluation errors (math errors) have error codes from @c 100 upwards.
|
|
||||||
*/
|
|
||||||
FOUNDATION_EXPORT NSString *const MPMathKitErrorDomain;
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: Deal with this:
|
|
||||||
// FOUNDATION_EXPORT NSString *const MPPathToExpressionKey;
|
|
||||||
// FOUNDATION_EXPORT NSString *const MPRangeKey;
|
|
||||||
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@function MPParseError
|
|
||||||
@brief Creates an @c NSError object in the @c MPMathKitErrorDomain.
|
|
||||||
|
|
||||||
@discussion The created error is filled with the passed data if present.
|
|
||||||
|
|
||||||
@param errorCode
|
|
||||||
The error code of the created error.
|
|
||||||
|
|
||||||
@param errorDescription
|
|
||||||
The localized description of the created error. May be @c nil.
|
|
||||||
|
|
||||||
@param underlyingError
|
|
||||||
The underlying error of the created error. May be @c nil.
|
|
||||||
|
|
||||||
@return An @c NSError object initialized with the given data.
|
|
||||||
*/
|
|
||||||
NS_INLINE NSError * MPParseError(NSInteger errorCode,
|
|
||||||
NSString *errorDescription,
|
|
||||||
NSError *underlyingError) {
|
|
||||||
NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init];
|
|
||||||
if (errorDescription) {
|
|
||||||
userInfo[NSLocalizedDescriptionKey] = errorDescription;
|
|
||||||
}
|
|
||||||
if (underlyingError) {
|
|
||||||
userInfo[NSUnderlyingErrorKey] = underlyingError;
|
|
||||||
}
|
|
||||||
NSError *error = [NSError errorWithDomain:MPMathKitErrorDomain
|
|
||||||
code:errorCode
|
|
||||||
userInfo:userInfo.copy];
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@typedef MPReferenceFrame
|
@typedef MPReferenceFrame
|
||||||
@brief A reference frame describes what an @em item is.
|
@brief A reference frame describes what an @em item is.
|
||||||
@@ -116,7 +65,7 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@class MPExpression, MPFunction, MPRangePath, MPExpressionTree, MPExpressionEvaluator;
|
@class MPExpression, MPFunction, MPRangePath, MPParsedExpression;
|
||||||
@protocol MPExpressionElement, MPToken;
|
@protocol MPExpressionElement, MPToken;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -630,7 +579,7 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) {
|
|||||||
want more control over the evaluation process use @c -parse
|
want more control over the evaluation process use @c -parse
|
||||||
instead.
|
instead.
|
||||||
|
|
||||||
@param error
|
@param errors
|
||||||
If the receiver (or any of its elements) contains a syntax error
|
If the receiver (or any of its elements) contains a syntax error
|
||||||
or can not be evaluated this parameter is set to an appropriate
|
or can not be evaluated this parameter is set to an appropriate
|
||||||
value. Pass @c NULL if you are not interested in any errors that
|
value. Pass @c NULL if you are not interested in any errors that
|
||||||
@@ -642,7 +591,7 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) {
|
|||||||
math error. In most cases the error parameter contains an
|
math error. In most cases the error parameter contains an
|
||||||
appropriate description of the problem.
|
appropriate description of the problem.
|
||||||
*/
|
*/
|
||||||
- (NSDecimalNumber *)evaluateWithError:(NSError *__autoreleasing *)error;
|
- (NSDecimalNumber *)evaluateWithErrors:(NSArray *__autoreleasing *)errors;
|
||||||
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -657,7 +606,10 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) {
|
|||||||
|
|
||||||
@return A @c MPExpressionTree object that can evaluate the receiver.
|
@return A @c MPExpressionTree object that can evaluate the receiver.
|
||||||
*/
|
*/
|
||||||
- (MPExpressionTree *)parse;
|
- (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors;
|
||||||
|
|
||||||
|
- (MPParsedExpression *)parseExpectingVariable:(BOOL)flag
|
||||||
|
errors:(NSArray *__autoreleasing *)errors;
|
||||||
|
|
||||||
|
|
||||||
#pragma mark Notifications
|
#pragma mark Notifications
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
#import "MPRangePath.h"
|
#import "MPRangePath.h"
|
||||||
|
|
||||||
#import "MPExpressionTokenizer.h"
|
#import "MPExpressionTokenizer.h"
|
||||||
#import "MPTokenStream.h"
|
|
||||||
#import "MPToken.h"
|
#import "MPToken.h"
|
||||||
#import "MPExpressionTree.h"
|
#import "MPExpressionParser.h"
|
||||||
|
#import "MPParsedExpression.h"
|
||||||
|
|
||||||
#import "NSIndexPath+MPAdditions.h"
|
#import "NSIndexPath+MPAdditions.h"
|
||||||
|
|
||||||
@@ -25,10 +25,6 @@
|
|||||||
NSString *const MPIllegalElementException = @"MPIllegalElementException";
|
NSString *const MPIllegalElementException = @"MPIllegalElementException";
|
||||||
NSString *const MPIllegalElementExceptionElementKey = @"MPIllegalElementExceptionElementKey";
|
NSString *const MPIllegalElementExceptionElementKey = @"MPIllegalElementExceptionElementKey";
|
||||||
|
|
||||||
NSString *const MPMathKitErrorDomain = @"MPMathKitErrorDomain";
|
|
||||||
NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@interface MPExpression () {
|
@interface MPExpression () {
|
||||||
NSMutableArray * _elements;
|
NSMutableArray * _elements;
|
||||||
@@ -328,7 +324,11 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey";
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case MPTokenReferenceFrame:
|
case MPTokenReferenceFrame:
|
||||||
symbolIndex = [self.tokens[anIndex] range].location;
|
[self.tokens enumerateObjectsUsingBlock:^(id<MPToken> obj, NSUInteger idx, BOOL *stop) {
|
||||||
|
symbolIndex += obj.range.length;
|
||||||
|
*stop = idx >= anIndex - 1;
|
||||||
|
}];
|
||||||
|
// symbolIndex = [self.tokens[anIndex] range].location;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,17 +556,38 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey";
|
|||||||
#pragma mark Evaluating Expressions
|
#pragma mark Evaluating Expressions
|
||||||
|
|
||||||
|
|
||||||
- (NSDecimalNumber *)evaluateWithError:(NSError *__autoreleasing *)error
|
- (NSDecimalNumber *)evaluateWithErrors:(NSArray *__autoreleasing *)errors
|
||||||
{
|
{
|
||||||
MPExpressionTree *tree = [self parse];
|
NSArray *parsingErrors;
|
||||||
return [tree validate:error] ? [tree evaluate] : nil;
|
MPParsedExpression *parsedExpression = [self parse:&parsingErrors];
|
||||||
|
NSError *evaluationError;
|
||||||
|
NSDecimalNumber *result = parsedExpression ? [parsedExpression evaluate:&evaluationError] : nil;
|
||||||
|
if (errors) {
|
||||||
|
NSMutableArray *localErrors = [[NSMutableArray alloc] initWithCapacity:parsingErrors.count+1];
|
||||||
|
if (parsingErrors) {
|
||||||
|
[localErrors addObjectsFromArray:parsingErrors];
|
||||||
|
}
|
||||||
|
if (evaluationError) {
|
||||||
|
[localErrors addObject:evaluationError];
|
||||||
|
}
|
||||||
|
*errors = localErrors;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
- (MPExpressionTree *)parse
|
- (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors
|
||||||
{
|
{
|
||||||
MPTokenStream *tokenStream = [[MPTokenStream alloc] initWithTokens:self.tokens];
|
return [self parseExpectingVariable:NO
|
||||||
return [[MPExpressionTree alloc] initWithTokenStream:tokenStream];
|
errors:errors];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
- (MPParsedExpression *)parseExpectingVariable:(BOOL)flag
|
||||||
|
errors:(NSArray *__autoreleasing *)errors
|
||||||
|
{
|
||||||
|
return [[[MPExpressionParser alloc] initWithExpression:self] parseExpectingVariableDefinition:flag
|
||||||
|
errors:errors];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
22
MathPad/MPExpressionParser.h
Normal file
22
MathPad/MPExpressionParser.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// MPExpressionParser.h
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 16.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPExpression.h"
|
||||||
|
#import "MPParsedExpression.h"
|
||||||
|
|
||||||
|
@interface MPExpressionParser : NSObject
|
||||||
|
|
||||||
|
@property (readonly, nonatomic, strong) MPExpression *expression;
|
||||||
|
|
||||||
|
- (instancetype)initWithExpression:(MPExpression *)expression;
|
||||||
|
|
||||||
|
- (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors;
|
||||||
|
- (MPParsedExpression *)parseExpectingVariableDefinition:(BOOL)flag
|
||||||
|
errors:(NSArray *__autoreleasing *)errors;
|
||||||
|
|
||||||
|
@end
|
||||||
416
MathPad/MPExpressionParser.m
Normal file
416
MathPad/MPExpressionParser.m
Normal file
@@ -0,0 +1,416 @@
|
|||||||
|
//
|
||||||
|
// MPExpressionParser.m
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 16.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPExpressionParser.h"
|
||||||
|
|
||||||
|
#import "MPExpression.h"
|
||||||
|
#import "MPTokenStream.h"
|
||||||
|
#import "MPTerm.h"
|
||||||
|
#import "MPToken.h"
|
||||||
|
#import "MPSumTerm.h"
|
||||||
|
#import "MPProductTerm.h"
|
||||||
|
#import "MPFactorialTerm.h"
|
||||||
|
#import "MPElementaryFunctionTerm.h"
|
||||||
|
#import "MPFunctionTerm.h"
|
||||||
|
#import "MPPowerTerm.h"
|
||||||
|
#import "MPNegatedTerm.h"
|
||||||
|
|
||||||
|
#import "MPNumber.h"
|
||||||
|
#import "MPVariable.h"
|
||||||
|
#import "MPFactorialTerm.h"
|
||||||
|
|
||||||
|
#define success() state = 0
|
||||||
|
#define fail() state = -1
|
||||||
|
|
||||||
|
@interface MPExpressionParser ()
|
||||||
|
|
||||||
|
@property (nonatomic, strong) NSArray *tokens;
|
||||||
|
@property (nonatomic) NSUInteger currentTokenIndex;
|
||||||
|
|
||||||
|
@property (nonatomic, strong) NSMutableArray *errors;
|
||||||
|
|
||||||
|
@property (nonatomic, strong) NSMutableArray *summands;
|
||||||
|
@property (nonatomic) BOOL summandNegative;
|
||||||
|
@property (nonatomic, strong) NSMutableArray *factors;
|
||||||
|
@property (nonatomic) BOOL factorNegative;
|
||||||
|
@property (nonatomic, strong) NSMutableArray *functionStack;
|
||||||
|
@property (nonatomic, strong) NSMutableArray *valueGroup;
|
||||||
|
@property (nonatomic, strong) MPTerm *value;
|
||||||
|
|
||||||
|
@property (nonatomic) NSUInteger errorTokenIndex;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation MPExpressionParser
|
||||||
|
|
||||||
|
- (instancetype)initWithExpression:(MPExpression *)expression
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
if (self) {
|
||||||
|
_tokens = [expression allItemsInReferenceFrame:MPTokenReferenceFrame];
|
||||||
|
_expression = expression;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors
|
||||||
|
{
|
||||||
|
return [self parseExpectingVariableDefinition:NO
|
||||||
|
errors:errors];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (MPParsedExpression *)parseExpectingVariableDefinition:(BOOL)flag
|
||||||
|
errors:(NSArray *__autoreleasing *)errors
|
||||||
|
{
|
||||||
|
MPParsedExpression *result = [[MPParsedExpression alloc] init];
|
||||||
|
self.currentTokenIndex = 0;
|
||||||
|
|
||||||
|
BOOL hasVariableDefinition = NO;
|
||||||
|
NSString *variableName = nil;
|
||||||
|
[self skipWhitespaces];
|
||||||
|
if (self.currentToken.tokenType == MPVariableToken) {
|
||||||
|
variableName = self.currentToken.stringValue;
|
||||||
|
[self nextToken];
|
||||||
|
[self skipWhitespaces];
|
||||||
|
if (self.currentToken.tokenType == MPEqualsToken) {
|
||||||
|
[self nextToken];
|
||||||
|
hasVariableDefinition = YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (flag && hasVariableDefinition) {
|
||||||
|
result.definedVariable = variableName;
|
||||||
|
} else if (flag) {
|
||||||
|
[self addErrorWithCode:3 localizedDescription:NSLocalizedString(@"Expected Variable Definition.", nil)];
|
||||||
|
if (errors) {
|
||||||
|
*errors = self.errors;
|
||||||
|
}
|
||||||
|
return nil;
|
||||||
|
} else if (hasVariableDefinition) {
|
||||||
|
[self addErrorWithCode:4 localizedDescription:NSLocalizedString(@"Unexpected Variable Definition.", nil)];
|
||||||
|
if (errors) {
|
||||||
|
*errors = self.errors;
|
||||||
|
}
|
||||||
|
return nil;
|
||||||
|
} else {
|
||||||
|
self.currentTokenIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[self skipWhitespaces];
|
||||||
|
if (self.currentTokenIndex >= self.tokens.count) {
|
||||||
|
[self addErrorWithCode:5 localizedDescription:NSLocalizedString(@"Empty Expression.", nil)];
|
||||||
|
if (errors) {
|
||||||
|
*errors = self.errors;
|
||||||
|
}
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
[self runParserMachine];
|
||||||
|
if ([self errorOccured]) {
|
||||||
|
if (errors) {
|
||||||
|
*errors = self.errors;
|
||||||
|
}
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
result.term = [[MPSumTerm alloc] initWithSummands:self.summands];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)runParserMachine
|
||||||
|
{
|
||||||
|
NSInteger state = 1;
|
||||||
|
NSInteger backtraceState = 0;
|
||||||
|
NSUInteger backtraceTokenIndex = self.currentTokenIndex;
|
||||||
|
BOOL alternateRoute = NO;
|
||||||
|
while (state != 0) {
|
||||||
|
switch (state) {
|
||||||
|
case -1:
|
||||||
|
state = backtraceState;
|
||||||
|
self.currentTokenIndex = backtraceTokenIndex;
|
||||||
|
alternateRoute = YES;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
[self skipWhitespaces];
|
||||||
|
if (self.currentToken.tokenType == MPOperatorListToken) {
|
||||||
|
self.factorNegative = [self parseOperatorList:self.currentToken];
|
||||||
|
[self nextToken];
|
||||||
|
state = 2;
|
||||||
|
} else {
|
||||||
|
self.factorNegative = NO;
|
||||||
|
state = 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
[self skipWhitespaces];
|
||||||
|
if (self.currentToken.tokenType == MPElementaryFunctionToken) {
|
||||||
|
[self.functionStack addObject:self.currentToken.stringValue];
|
||||||
|
[self nextToken];
|
||||||
|
state = 2;
|
||||||
|
} else {
|
||||||
|
state = 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
[self skipWhitespaces];
|
||||||
|
if (self.currentToken.tokenType == MPGenericFunctionToken) {
|
||||||
|
NSArray *errors;
|
||||||
|
self.value = [[MPFunctionTerm alloc] initWithFunction:(MPFunction *)self.currentToken
|
||||||
|
errors:&errors];
|
||||||
|
if (!self.value) {
|
||||||
|
[self.errors addObjectsFromArray:errors];
|
||||||
|
}
|
||||||
|
[self nextToken];
|
||||||
|
[self runSuffixMachine];
|
||||||
|
if (self.value) {
|
||||||
|
[self.valueGroup addObject:self.value];
|
||||||
|
}
|
||||||
|
state = 6;
|
||||||
|
} else {
|
||||||
|
state = 4;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
if (self.currentToken.tokenType == MPNumberToken) {
|
||||||
|
self.value = [[MPNumber alloc] initWithNumber:[self parseNumber:self.currentToken]];
|
||||||
|
[self nextToken];
|
||||||
|
[self runSuffixMachine];
|
||||||
|
[self.valueGroup addObject:self.value];
|
||||||
|
state = 5;
|
||||||
|
} else if (self.currentToken.tokenType == MPVariableToken) {
|
||||||
|
self.value = [[MPVariable alloc] initWithVariableName:[self parseVariable:self.currentToken]];
|
||||||
|
[self nextToken];
|
||||||
|
[self runSuffixMachine];
|
||||||
|
[self.valueGroup addObject:self.value];
|
||||||
|
state = 5;
|
||||||
|
} else {
|
||||||
|
self.errorTokenIndex = self.currentTokenIndex;
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
if (alternateRoute) {
|
||||||
|
alternateRoute = NO;
|
||||||
|
state = 6;
|
||||||
|
} else {
|
||||||
|
backtraceState = 5;
|
||||||
|
backtraceTokenIndex = self.currentTokenIndex;
|
||||||
|
state = 4;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 6:
|
||||||
|
[self collapseFactor];
|
||||||
|
[self skipWhitespaces];
|
||||||
|
backtraceState = 6;
|
||||||
|
backtraceTokenIndex = self.currentTokenIndex;
|
||||||
|
if (!alternateRoute && self.currentToken.tokenType == MPOperatorListToken) {
|
||||||
|
[self collapseSummand];
|
||||||
|
self.summandNegative = [self parseOperatorList:self.currentToken];
|
||||||
|
[self nextToken];
|
||||||
|
state = 1;
|
||||||
|
} else {
|
||||||
|
state = 7;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 7:
|
||||||
|
if (alternateRoute) {
|
||||||
|
[self collapseSummand];
|
||||||
|
alternateRoute = NO;
|
||||||
|
success();
|
||||||
|
} else {
|
||||||
|
backtraceState = 7;
|
||||||
|
backtraceTokenIndex = self.currentTokenIndex;
|
||||||
|
state = 8;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 8:
|
||||||
|
[self skipWhitespaces];
|
||||||
|
if (self.currentToken.tokenType == MPMultiplicationSymbolToken) {
|
||||||
|
[self nextToken];
|
||||||
|
state = 1;
|
||||||
|
} else {
|
||||||
|
state = 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Should never get here
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)skipWhitespaces
|
||||||
|
{
|
||||||
|
while ([self currentToken] != nil && [self currentToken].tokenType == MPWhitespaceToken) {
|
||||||
|
[self nextToken];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id<MPToken>)currentToken
|
||||||
|
{
|
||||||
|
if (self.currentTokenIndex >= self.tokens.count) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
return self.tokens[self.currentTokenIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)nextToken
|
||||||
|
{
|
||||||
|
self.currentTokenIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSMutableArray *)errors
|
||||||
|
{
|
||||||
|
if (!_errors) {
|
||||||
|
_errors = [[NSMutableArray alloc] init];
|
||||||
|
}
|
||||||
|
return _errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSMutableArray *)summands
|
||||||
|
{
|
||||||
|
if (!_summands) {
|
||||||
|
_summands = [[NSMutableArray alloc] init];
|
||||||
|
}
|
||||||
|
return _summands;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSMutableArray *)factors
|
||||||
|
{
|
||||||
|
if (!_factors) {
|
||||||
|
_factors = [[NSMutableArray alloc] init];
|
||||||
|
}
|
||||||
|
return _factors;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSMutableArray *)functionStack
|
||||||
|
{
|
||||||
|
if (!_functionStack) {
|
||||||
|
_functionStack = [[NSMutableArray alloc] init];
|
||||||
|
}
|
||||||
|
return _functionStack;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSMutableArray *)valueGroup
|
||||||
|
{
|
||||||
|
if (!_valueGroup) {
|
||||||
|
_valueGroup = [[NSMutableArray alloc] init];
|
||||||
|
}
|
||||||
|
return _valueGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)parseOperatorList:(id<MPToken>)token // Returns YES if list is overall negative
|
||||||
|
{
|
||||||
|
NSString *operatorString = [[token.stringValue stringByReplacingOccurrencesOfString:@" " withString:@""]
|
||||||
|
stringByReplacingOccurrencesOfString:@"+" withString:@""];
|
||||||
|
return (operatorString.length & 1) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSDecimalNumber *)parseNumber:(id<MPToken>)token
|
||||||
|
{
|
||||||
|
return [NSDecimalNumber decimalNumberWithString:token.stringValue
|
||||||
|
locale:[NSLocale currentLocale]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)parseVariable:(id<MPToken>)token
|
||||||
|
{
|
||||||
|
return token.stringValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)collapseSummand
|
||||||
|
{
|
||||||
|
if (self.factors.count > 0) {
|
||||||
|
MPTerm *summand = [[MPProductTerm alloc] initWithFactors:self.factors];
|
||||||
|
if (self.summandNegative) {
|
||||||
|
summand = [[MPNegatedTerm alloc] initWithTerm:summand];
|
||||||
|
}
|
||||||
|
[self.summands addObject:summand];
|
||||||
|
}
|
||||||
|
self.factors = nil;
|
||||||
|
self.summandNegative = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)collapseFactor
|
||||||
|
{
|
||||||
|
if (self.valueGroup.count > 0) {
|
||||||
|
MPTerm *factor = [[MPProductTerm alloc] initWithFactors:self.valueGroup];
|
||||||
|
for (NSInteger index = self.functionStack.count - 1; index >= 0; index--) {
|
||||||
|
factor = [[MPElementaryFunctionTerm alloc] initWithFunctionIdentifier:self.functionStack[index]
|
||||||
|
term:factor];
|
||||||
|
}
|
||||||
|
if (self.factorNegative) {
|
||||||
|
factor = [[MPNegatedTerm alloc] initWithTerm:factor];
|
||||||
|
}
|
||||||
|
[self.factors addObject:factor];
|
||||||
|
}
|
||||||
|
self.valueGroup = nil;
|
||||||
|
self.functionStack = nil;
|
||||||
|
self.factorNegative = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)runSuffixMachine
|
||||||
|
{
|
||||||
|
BOOL checkMore = YES;
|
||||||
|
while (checkMore) {
|
||||||
|
if (self.currentToken.tokenType == MPFactorialToken) {
|
||||||
|
[self nextToken];
|
||||||
|
if (self.value) {
|
||||||
|
MPFactorialTerm *term = [[MPFactorialTerm alloc] initWithTerm:self.value];
|
||||||
|
self.value = term;
|
||||||
|
}
|
||||||
|
} else if (self.currentToken.tokenType == MPPowerToken) {
|
||||||
|
[self nextToken];
|
||||||
|
if (self.value) {
|
||||||
|
NSArray *errors;
|
||||||
|
MPPowerTerm *term = [[MPPowerTerm alloc] initWithFunction:(MPFunction *)self.currentToken errors:&errors];
|
||||||
|
if (!term) {
|
||||||
|
[self.errors addObjectsFromArray:errors];
|
||||||
|
}
|
||||||
|
term.baseTerm = self.value;
|
||||||
|
self.value = term;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
checkMore = NO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)addErrorWithCode:(NSInteger)code
|
||||||
|
localizedDescription:(NSString *)description
|
||||||
|
{
|
||||||
|
NSInteger errorIndex = [self.expression convertIndex:self.errorTokenIndex
|
||||||
|
fromReferenceFrame:MPTokenReferenceFrame
|
||||||
|
toReferenceFrame:MPSymbolReferenceFrame];
|
||||||
|
[self.errors addObject:[NSError errorWithDomain:MPMathKitErrorDomain
|
||||||
|
code:code
|
||||||
|
userInfo:@{NSLocalizedDescriptionKey: description,
|
||||||
|
MPPathToExpressionKey: self.expression.indexPath,
|
||||||
|
MPErrorIndexKey: @(errorIndex)}]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)errorOccured
|
||||||
|
{
|
||||||
|
[self skipWhitespaces];
|
||||||
|
if (self.currentTokenIndex < self.tokens.count) {
|
||||||
|
if (self.errorTokenIndex >= self.tokens.count) {
|
||||||
|
[self addErrorWithCode:0 localizedDescription:NSLocalizedString(@"Unexpected end. Expected Value.", nil)];
|
||||||
|
} else {
|
||||||
|
[self addErrorWithCode:1 localizedDescription:NSLocalizedString(@"Unexpected Symbol. Expected Value", nil)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return self.errors.count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -51,12 +51,7 @@
|
|||||||
@"([\\*∙⋅])|"
|
@"([\\*∙⋅])|"
|
||||||
@"([+-](?:\\s*[+-])*)|"
|
@"([+-](?:\\s*[+-])*)|"
|
||||||
@"((?:\\d+(?:%@\\d+)?)|(?:%@\\d+))|"
|
@"((?:\\d+(?:%@\\d+)?)|(?:%@\\d+))|"
|
||||||
@"(sin)|"
|
@"(sin|cos|tan|asin|acos|atan)|"
|
||||||
@"(cos)|"
|
|
||||||
@"(tan)|"
|
|
||||||
@"(asin)|"
|
|
||||||
@"(acos)|"
|
|
||||||
@"(atan)|"
|
|
||||||
@"([A-Za-z])|"
|
@"([A-Za-z])|"
|
||||||
@"(!)|"
|
@"(!)|"
|
||||||
@"(=)|"
|
@"(=)|"
|
||||||
@@ -76,16 +71,11 @@
|
|||||||
NSRange multiplicationSymbolRange = [match rangeAtIndex:1];
|
NSRange multiplicationSymbolRange = [match rangeAtIndex:1];
|
||||||
NSRange operatorRange = [match rangeAtIndex:2];
|
NSRange operatorRange = [match rangeAtIndex:2];
|
||||||
NSRange numberRange = [match rangeAtIndex:3];
|
NSRange numberRange = [match rangeAtIndex:3];
|
||||||
NSRange sinRange = [match rangeAtIndex:4];
|
NSRange elementaryFunctionRange = [match rangeAtIndex:4];
|
||||||
NSRange cosRange = [match rangeAtIndex:5];
|
NSRange variableRange = [match rangeAtIndex:5];
|
||||||
NSRange tanRange = [match rangeAtIndex:6];
|
NSRange factorialRange = [match rangeAtIndex:6];
|
||||||
NSRange asinRange = [match rangeAtIndex:7];
|
NSRange equalsRange = [match rangeAtIndex:7];
|
||||||
NSRange acosRange = [match rangeAtIndex:8];
|
NSRange whitespaceRange = [match rangeAtIndex:8];
|
||||||
NSRange atanRange = [match rangeAtIndex:9];
|
|
||||||
NSRange variableRange = [match rangeAtIndex:10];
|
|
||||||
NSRange factorialRange = [match rangeAtIndex:11];
|
|
||||||
NSRange equalsRange = [match rangeAtIndex:12];
|
|
||||||
NSRange whitespaceRange = [match rangeAtIndex:13];
|
|
||||||
|
|
||||||
if (MPRangeExists(multiplicationSymbolRange)) {
|
if (MPRangeExists(multiplicationSymbolRange)) {
|
||||||
range = multiplicationSymbolRange;
|
range = multiplicationSymbolRange;
|
||||||
@@ -96,24 +86,9 @@
|
|||||||
} else if (MPRangeExists(numberRange)) {
|
} else if (MPRangeExists(numberRange)) {
|
||||||
range = numberRange;
|
range = numberRange;
|
||||||
tokenType = MPNumberToken;
|
tokenType = MPNumberToken;
|
||||||
} else if (MPRangeExists(sinRange)) {
|
} else if (MPRangeExists(elementaryFunctionRange)) {
|
||||||
range = sinRange;
|
range = elementaryFunctionRange;
|
||||||
tokenType = MPSinToken;
|
tokenType = MPElementaryFunctionToken;
|
||||||
} else if (MPRangeExists(cosRange)) {
|
|
||||||
range = cosRange;
|
|
||||||
tokenType = MPCosToken;
|
|
||||||
} else if (MPRangeExists(tanRange)) {
|
|
||||||
range = tanRange;
|
|
||||||
tokenType = MPTanToken;
|
|
||||||
} else if (MPRangeExists(asinRange)) {
|
|
||||||
range = asinRange;
|
|
||||||
tokenType = MPASinToken;
|
|
||||||
} else if (MPRangeExists(acosRange)) {
|
|
||||||
range = acosRange;
|
|
||||||
tokenType = MPACosToken;
|
|
||||||
} else if (MPRangeExists(atanRange)) {
|
|
||||||
range = atanRange;
|
|
||||||
tokenType = MPATanToken;
|
|
||||||
} else if (MPRangeExists(variableRange)) {
|
} else if (MPRangeExists(variableRange)) {
|
||||||
range = variableRange;
|
range = variableRange;
|
||||||
tokenType = MPVariableToken;
|
tokenType = MPVariableToken;
|
||||||
|
|||||||
17
MathPad/MPFactorialTerm.h
Normal file
17
MathPad/MPFactorialTerm.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// MPFactorialTerm.h
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 15.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPTerm.h"
|
||||||
|
|
||||||
|
@interface MPFactorialTerm : MPTerm
|
||||||
|
|
||||||
|
- (instancetype)initWithTerm:(MPTerm *)term; /* designated initializer */
|
||||||
|
|
||||||
|
@property (readonly, nonatomic, strong) MPTerm *term;
|
||||||
|
|
||||||
|
@end
|
||||||
32
MathPad/MPFactorialTerm.m
Normal file
32
MathPad/MPFactorialTerm.m
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// MPFactorialTerm.m
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 15.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPFactorialTerm.h"
|
||||||
|
|
||||||
|
@implementation MPFactorialTerm
|
||||||
|
|
||||||
|
- (instancetype)initWithTerm:(MPTerm *)term
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
if (self) {
|
||||||
|
NSAssert(term != nil, @"term must not be nil.");
|
||||||
|
_term = term;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||||
|
{
|
||||||
|
NSDecimalNumber *value = [self.value evaluate:error];
|
||||||
|
if (!value) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
return [[NSDecimalNumber alloc] initWithDouble:tgamma(value.doubleValue + 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
#import "MPFractionFunction.h"
|
#import "MPFractionFunction.h"
|
||||||
|
|
||||||
|
#import "MPFractionTerm.h"
|
||||||
#import "MPExpression.h"
|
#import "MPExpression.h"
|
||||||
#import "MPExpressionTree.h"
|
|
||||||
|
|
||||||
@implementation MPFractionFunction
|
@implementation MPFractionFunction
|
||||||
|
|
||||||
@@ -21,14 +21,9 @@ MPFunctionAccessorImplementation(DenominatorExpression, _denominatorExpression)
|
|||||||
return @[@"nominatorExpression", @"denominatorExpression"];
|
return @[@"nominatorExpression", @"denominatorExpression"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)validate:(NSError *__autoreleasing *)error
|
- (Class)functionTermClass
|
||||||
{
|
{
|
||||||
return [[self.nominatorExpression parse] validate:error] && [[self.denominatorExpression parse] validate:error];
|
return [MPFractionTerm class];
|
||||||
}
|
|
||||||
|
|
||||||
- (NSDecimalNumber *)evaluate
|
|
||||||
{
|
|
||||||
return [[[self.nominatorExpression parse] evaluate] decimalNumberByDividingBy:[[self.denominatorExpression parse] evaluate]];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)description
|
- (NSString *)description
|
||||||
|
|||||||
13
MathPad/MPFractionTerm.h
Normal file
13
MathPad/MPFractionTerm.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
//
|
||||||
|
// MPFractionTerm.h
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 15.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPFunctionTerm.h"
|
||||||
|
|
||||||
|
@interface MPFractionTerm : MPFunctionTerm
|
||||||
|
|
||||||
|
@end
|
||||||
27
MathPad/MPFractionTerm.m
Normal file
27
MathPad/MPFractionTerm.m
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// MPFractionTerm.m
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 15.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPFractionTerm.h"
|
||||||
|
|
||||||
|
@implementation MPFractionTerm
|
||||||
|
|
||||||
|
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||||
|
{
|
||||||
|
NSDecimalNumber *nominator = [[self expressionAtIndex:0] evaluate:error];
|
||||||
|
if (!nominator) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
NSDecimalNumber *denominator = [[self expressionAtIndex:1] evaluate:error];
|
||||||
|
if (!denominator) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
#warning Division by zero ahead
|
||||||
|
return [nominator decimalNumberByDividingBy:denominator];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -9,31 +9,25 @@
|
|||||||
#import "MPFunction+MPToken.h"
|
#import "MPFunction+MPToken.h"
|
||||||
|
|
||||||
#import "MPExpression.h"
|
#import "MPExpression.h"
|
||||||
|
#import "MPPowerFunction.h"
|
||||||
|
|
||||||
@implementation MPFunction (MPToken)
|
@implementation MPFunction (MPToken)
|
||||||
|
|
||||||
- (MPTokenType)tokenType
|
- (MPTokenType)tokenType
|
||||||
{
|
{
|
||||||
return MPGenericFunctionToken;
|
return [self isMemberOfClass:[MPPowerFunction class]] ? MPPowerToken : MPGenericFunctionToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
- (NSRange)range
|
- (NSRange)range
|
||||||
{
|
{
|
||||||
NSUInteger selfIndex = [self.parent indexOfElement:self];
|
return NSMakeRange([self.parent convertIndex:[self.parent indexOfElement:self]
|
||||||
return NSMakeRange([self.parent convertIndex:selfIndex
|
|
||||||
fromReferenceFrame:MPElementReferenceFrame
|
fromReferenceFrame:MPElementReferenceFrame
|
||||||
toReferenceFrame:MPSymbolReferenceFrame],
|
toReferenceFrame:MPSymbolReferenceFrame],
|
||||||
1);
|
1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
- (BOOL)exists
|
|
||||||
{
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
- (NSString *)stringValue
|
- (NSString *)stringValue
|
||||||
{
|
{
|
||||||
return [self description];
|
return [self description];
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@class MPFunction, MPExpression, MPRangePath;
|
@class MPFunction, MPExpression, MPRangePath, MPFunctionTerm;
|
||||||
@protocol MPExpressionElement;
|
@protocol MPExpressionElement;
|
||||||
|
|
||||||
|
|
||||||
@@ -241,6 +241,12 @@
|
|||||||
*/
|
*/
|
||||||
- (void)didChangeChildAtIndex:(NSUInteger)index;
|
- (void)didChangeChildAtIndex:(NSUInteger)index;
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark Evaluating Functions
|
||||||
|
|
||||||
|
|
||||||
|
- (BOOL)expectsVariableDefinitionInChildAtIndex:(NSUInteger)index;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
@@ -256,6 +262,10 @@
|
|||||||
*/
|
*/
|
||||||
@interface MPFunction (MPSubclassOverride)
|
@interface MPFunction (MPSubclassOverride)
|
||||||
|
|
||||||
|
/* In Addition to the methods listed below MPFunction subclasses should also
|
||||||
|
* override the NSObject methods -description and -hash.
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma mark Working With the Expression Tree
|
#pragma mark Working With the Expression Tree
|
||||||
/*!
|
/*!
|
||||||
@methodgroup Working With the Expression Tree
|
@methodgroup Working With the Expression Tree
|
||||||
@@ -281,40 +291,6 @@
|
|||||||
@methodgroup Evaluating Functions
|
@methodgroup Evaluating Functions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
- (Class)functionTermClass;
|
||||||
/*!
|
|
||||||
@method validate:
|
|
||||||
@brief Validates the function and all its children for syntax errors.
|
|
||||||
|
|
||||||
@param error
|
|
||||||
If the validation fails this parameter should be set to an
|
|
||||||
appropriate value.
|
|
||||||
|
|
||||||
@return @c YES if the validation was successful, @c NO otherwise.
|
|
||||||
*/
|
|
||||||
- (BOOL)validate:(NSError *__autoreleasing *)error;
|
|
||||||
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@method evaluate:
|
|
||||||
@brief Evaluates the function.
|
|
||||||
|
|
||||||
@discussion Function evaluation should actually perform math. Also the
|
|
||||||
implementation of this method assumes that the function and all
|
|
||||||
children are valid (as determined by the @c -validate: method).
|
|
||||||
|
|
||||||
@param error
|
|
||||||
If a mathematical error occurs it is reflected in this parameter.
|
|
||||||
Depending on the error this method then either returns @c nil or
|
|
||||||
@c NaN.
|
|
||||||
|
|
||||||
@return The result of the evaluation or @c nil or @c NaN if an evaluation
|
|
||||||
error occured (such as a division by @c 0).
|
|
||||||
*/
|
|
||||||
- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error;
|
|
||||||
|
|
||||||
/* In Addition to the above methods MPFunction subclasses should also override
|
|
||||||
* the NSObject methods -description and -hash.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
#import "MPRangePath.h"
|
#import "MPRangePath.h"
|
||||||
#import "NSIndexPath+MPAdditions.h"
|
#import "NSIndexPath+MPAdditions.h"
|
||||||
|
|
||||||
|
#import "MPFunctionTerm.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@implementation MPFunction
|
@implementation MPFunction
|
||||||
@@ -131,6 +133,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark Evaluating Functions
|
||||||
|
|
||||||
|
|
||||||
|
- (BOOL)expectsVariableDefinitionInChildAtIndex:(NSUInteger)index
|
||||||
|
{
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#pragma mark Working With Functions
|
#pragma mark Working With Functions
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
23
MathPad/MPFunctionTerm.h
Normal file
23
MathPad/MPFunctionTerm.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// MPFunctionTerm.h
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 12.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPTerm.h"
|
||||||
|
|
||||||
|
#import "MPParsedExpression.h"
|
||||||
|
#import "MPFunction.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@interface MPFunctionTerm : MPTerm
|
||||||
|
|
||||||
|
- (instancetype)initWithFunction:(MPFunction *)function
|
||||||
|
errors:(NSArray *__autoreleasing *)errors; /* designated initializer */
|
||||||
|
|
||||||
|
- (MPParsedExpression *)expressionAtIndex:(NSUInteger)anIndex;
|
||||||
|
|
||||||
|
@end
|
||||||
72
MathPad/MPFunctionTerm.m
Normal file
72
MathPad/MPFunctionTerm.m
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
//
|
||||||
|
// MPFunctionTerm.m
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 12.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPFunctionTerm.h"
|
||||||
|
|
||||||
|
#import "MPParsedExpression.h"
|
||||||
|
|
||||||
|
#import "MPToken.h"
|
||||||
|
#import "MPTokenStream.h"
|
||||||
|
#import "MPExpression.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@interface MPFunctionTerm ()
|
||||||
|
|
||||||
|
@property (nonatomic, strong) NSArray *parsedExpressions;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@implementation MPFunctionTerm
|
||||||
|
|
||||||
|
- (instancetype)initWithFunction:(MPFunction *)function
|
||||||
|
errors:(NSArray *__autoreleasing *)errors
|
||||||
|
{
|
||||||
|
NSAssert(function != nil, @"function must not be nil.");
|
||||||
|
if ([self isMemberOfClass:[MPFunctionTerm class]]) {
|
||||||
|
return [[function.functionTermClass alloc] initWithFunction:function
|
||||||
|
errors:errors];
|
||||||
|
}
|
||||||
|
self = [super init];
|
||||||
|
if (self) {
|
||||||
|
NSMutableArray *parsedExpressions = [[NSMutableArray alloc] initWithCapacity:function.childrenAccessors.count];
|
||||||
|
NSUInteger childIndex = 0;
|
||||||
|
BOOL errorOccured = NO;
|
||||||
|
NSMutableArray *mutableErrors = [[NSMutableArray alloc] init];
|
||||||
|
for (NSString *accessor in function.childrenAccessors) {
|
||||||
|
MPExpression *expression = [function valueForKey:accessor];
|
||||||
|
NSArray *localErrors;
|
||||||
|
MPParsedExpression *parsedExpression = [expression parseExpectingVariable:[function expectsVariableDefinitionInChildAtIndex:childIndex]
|
||||||
|
errors:&localErrors];
|
||||||
|
if (parsedExpression) {
|
||||||
|
[parsedExpressions addObject:parsedExpression];
|
||||||
|
} else {
|
||||||
|
[mutableErrors addObjectsFromArray:localErrors];
|
||||||
|
errorOccured = YES;
|
||||||
|
}
|
||||||
|
childIndex++;
|
||||||
|
}
|
||||||
|
if (errorOccured) {
|
||||||
|
if (errors) {
|
||||||
|
*errors = mutableErrors;
|
||||||
|
}
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
self.parsedExpressions = parsedExpressions;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (MPParsedExpression *)expressionAtIndex:(NSUInteger)anIndex
|
||||||
|
{
|
||||||
|
return [self.parsedExpressions objectAtIndex:anIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
17
MathPad/MPNegatedTerm.h
Normal file
17
MathPad/MPNegatedTerm.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// MPNegatedTerm.h
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 22.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPTerm.h"
|
||||||
|
|
||||||
|
@interface MPNegatedTerm : MPTerm
|
||||||
|
|
||||||
|
- (instancetype)initWithTerm:(MPTerm *)term; /* designated initializer */
|
||||||
|
|
||||||
|
@property (readonly, nonatomic, strong) MPTerm *term;
|
||||||
|
|
||||||
|
@end
|
||||||
32
MathPad/MPNegatedTerm.m
Normal file
32
MathPad/MPNegatedTerm.m
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// MPNegatedTerm.m
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 22.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPNegatedTerm.h"
|
||||||
|
|
||||||
|
@implementation MPNegatedTerm
|
||||||
|
|
||||||
|
- (instancetype)initWithTerm:(MPTerm *)term
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
if (self) {
|
||||||
|
NSAssert(term != nil, @"term must not be nil.");
|
||||||
|
_term = term;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||||
|
{
|
||||||
|
NSDecimalNumber *value = [self.term evaluate:error];
|
||||||
|
if (value) {
|
||||||
|
value = [value decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:-1]];
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -6,11 +6,13 @@
|
|||||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import "MPValueGroup.h"
|
#import "MPTerm.h"
|
||||||
|
|
||||||
@class MPNumber;
|
@class MPNumber;
|
||||||
|
|
||||||
@interface MPNumber : NSObject <MPValue>
|
@interface MPNumber : MPTerm
|
||||||
|
|
||||||
|
- (instancetype)initWithNumber:(NSDecimalNumber *)number; /* designated initializer */
|
||||||
|
|
||||||
@property (readonly, nonatomic, strong) NSDecimalNumber *number;
|
@property (readonly, nonatomic, strong) NSDecimalNumber *number;
|
||||||
|
|
||||||
|
|||||||
@@ -8,44 +8,24 @@
|
|||||||
|
|
||||||
#import "MPNumber.h"
|
#import "MPNumber.h"
|
||||||
|
|
||||||
|
#import "MPParsedExpression.h"
|
||||||
|
|
||||||
#import "MPTokenStream.h"
|
#import "MPTokenStream.h"
|
||||||
#import "MPToken.h"
|
#import "MPToken.h"
|
||||||
|
|
||||||
@implementation MPNumber
|
@implementation MPNumber
|
||||||
|
|
||||||
- (instancetype)init
|
- (instancetype)initWithNumber:(NSDecimalNumber *)number
|
||||||
{
|
{
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (self) {
|
if (self) {
|
||||||
|
NSAssert(number != nil, @"number must not be nil.");
|
||||||
|
_number = number;
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream
|
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||||
{
|
|
||||||
self = [self init];
|
|
||||||
if (self) {
|
|
||||||
[tokenStream beginIgnoringWhitespaceTokens];
|
|
||||||
MPToken *token = tokenStream.currentToken;
|
|
||||||
if (token.tokenType == MPNumberToken) {
|
|
||||||
_number = [NSDecimalNumber decimalNumberWithString:token.stringValue locale:[NSLocale currentLocale]];
|
|
||||||
} else {
|
|
||||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException
|
|
||||||
reason:@"Expected Number"
|
|
||||||
userInfo:nil];
|
|
||||||
}
|
|
||||||
[tokenStream currentTokenConsumed];
|
|
||||||
[tokenStream endIgnoringOrAcceptingWhitespaceTokens];
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)validate:(NSError *__autoreleasing *)error
|
|
||||||
{
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSDecimalNumber *)evaluate
|
|
||||||
{
|
{
|
||||||
return self.number;
|
return self.number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
#import "MPParenthesisFunction.h"
|
#import "MPParenthesisFunction.h"
|
||||||
|
|
||||||
#import "MPExpression.h"
|
#import "MPExpression.h"
|
||||||
#import "MPExpressionTree.h"
|
#import "MPParenthesisTerm.h"
|
||||||
|
|
||||||
@implementation MPParenthesisFunction
|
@implementation MPParenthesisFunction
|
||||||
|
|
||||||
@@ -21,14 +21,9 @@ MPFunctionAccessorImplementation(Expression, _expression)
|
|||||||
return @[@"expression"];
|
return @[@"expression"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)validate:(NSError *__autoreleasing *)error
|
- (Class)functionTermClass
|
||||||
{
|
{
|
||||||
return [[self.expression parse] validate:error];
|
return [MPParenthesisTerm class];
|
||||||
}
|
|
||||||
|
|
||||||
- (NSDecimalNumber *)evaluate
|
|
||||||
{
|
|
||||||
return [[self.expression parse] evaluate];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)description
|
- (NSString *)description
|
||||||
|
|||||||
13
MathPad/MPParenthesisTerm.h
Normal file
13
MathPad/MPParenthesisTerm.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
//
|
||||||
|
// MPParenthesisTerm.h
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 15.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPFunctionTerm.h"
|
||||||
|
|
||||||
|
@interface MPParenthesisTerm : MPFunctionTerm
|
||||||
|
|
||||||
|
@end
|
||||||
18
MathPad/MPParenthesisTerm.m
Normal file
18
MathPad/MPParenthesisTerm.m
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// MPParenthesisTerm.m
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 15.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPParenthesisTerm.h"
|
||||||
|
|
||||||
|
@implementation MPParenthesisTerm
|
||||||
|
|
||||||
|
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||||
|
{
|
||||||
|
return [[self expressionAtIndex:0] evaluate:error];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
34
MathPad/MPParsedExpression.h
Normal file
34
MathPad/MPParsedExpression.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// MPParsedExpression.h
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 14.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
@class MPParsedExpression, MPTokenStream, MPTerm;
|
||||||
|
@protocol MPToken;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@const MPMathKitErrorDomain
|
||||||
|
@brief Predefined error domain for errors from the MathKit framework.
|
||||||
|
|
||||||
|
@discussion Errors in MathKit can occur during parsing of expressions or
|
||||||
|
during evaluation of expressions. These two can be distinguished
|
||||||
|
by the error code. Parsing errors have lower error codes.
|
||||||
|
Evaluation errors (math errors) have error codes from @c 100 upwards.
|
||||||
|
*/
|
||||||
|
FOUNDATION_EXPORT NSString *const MPMathKitErrorDomain;
|
||||||
|
|
||||||
|
FOUNDATION_EXPORT NSString *const MPPathToExpressionKey;
|
||||||
|
FOUNDATION_EXPORT NSString *const MPErrorIndexKey;
|
||||||
|
|
||||||
|
|
||||||
|
@interface MPParsedExpression : NSObject
|
||||||
|
|
||||||
|
@property (nonatomic, strong) NSString *definedVariable;
|
||||||
|
@property (nonatomic, strong) MPTerm *term;
|
||||||
|
|
||||||
|
- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error;
|
||||||
|
|
||||||
|
@end
|
||||||
27
MathPad/MPParsedExpression.m
Normal file
27
MathPad/MPParsedExpression.m
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// MPParsedExpression.m
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 14.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPParsedExpression.h"
|
||||||
|
|
||||||
|
#import "MPTokenStream.h"
|
||||||
|
#import "MPToken.h"
|
||||||
|
|
||||||
|
#import "MPTerm.h"
|
||||||
|
|
||||||
|
NSString *const MPMathKitErrorDomain = @"MPMathKitErrorDomain";
|
||||||
|
NSString *const MPPathToExpressionKey = @"MPPathToExpressionKey";
|
||||||
|
NSString *const MPErrorIndexKey = @"MPErrorIndexKey";
|
||||||
|
|
||||||
|
@implementation MPParsedExpression
|
||||||
|
|
||||||
|
- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error
|
||||||
|
{
|
||||||
|
return [self.term evaluate:error];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -6,12 +6,19 @@
|
|||||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import "MPSuffixFunction.h"
|
#import "MPFunction.h"
|
||||||
|
|
||||||
@class MPPowerFunction, MPExpression;
|
@class MPPowerFunction, MPExpression;
|
||||||
|
|
||||||
@interface MPPowerFunction : MPSuffixFunction
|
@interface MPPowerFunction : MPFunction
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@property baseValue
|
||||||
|
@brief The receiver's base value.
|
||||||
|
|
||||||
|
@discussion The base value is the thing a suffix function applies to (e.g.
|
||||||
|
a power's base).
|
||||||
|
*/
|
||||||
@property (nonatomic, strong) MPExpression *exponentExpression;
|
@property (nonatomic, strong) MPExpression *exponentExpression;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -9,9 +9,6 @@
|
|||||||
#import "MPPowerFunction.h"
|
#import "MPPowerFunction.h"
|
||||||
|
|
||||||
#import "MPExpression.h"
|
#import "MPExpression.h"
|
||||||
#import "MPExpressionTree.h"
|
|
||||||
|
|
||||||
#import "MPValueGroup.h"
|
|
||||||
|
|
||||||
@implementation MPPowerFunction
|
@implementation MPPowerFunction
|
||||||
|
|
||||||
@@ -22,30 +19,6 @@ MPFunctionAccessorImplementation(ExponentExpression, _exponentExpression)
|
|||||||
return @[@"exponentExpression"];
|
return @[@"exponentExpression"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)validate:(NSError *__autoreleasing *)error
|
|
||||||
{
|
|
||||||
if (!self.baseValue) {
|
|
||||||
if (error) {
|
|
||||||
*error = MPParseError(11,
|
|
||||||
NSLocalizedString(@"No Base For Power.", @"Error message. This is displayed when a power does not have a base value."),
|
|
||||||
nil);
|
|
||||||
}
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
return [self.baseValue validate:error] && [[self.exponentExpression parse] validate:error];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSDecimalNumber *)evaluate
|
|
||||||
{
|
|
||||||
NSDecimalNumber *base = [self.baseValue evaluate];
|
|
||||||
NSDecimalNumber *exponent = [[self.exponentExpression parse] evaluate];
|
|
||||||
if ([base isEqualToNumber:@(0)] && [exponent isEqualToNumber:@(0)]) {
|
|
||||||
// The C pow function returns 1 for pow(0, 0). Mathematically this should be undefined.
|
|
||||||
return [NSDecimalNumber notANumber];
|
|
||||||
}
|
|
||||||
return [[NSDecimalNumber alloc] initWithDouble:pow(base.doubleValue, exponent.doubleValue)];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)description
|
- (NSString *)description
|
||||||
{
|
{
|
||||||
return [NSString stringWithFormat:@"^%@", self.exponentExpression.description];
|
return [NSString stringWithFormat:@"^%@", self.exponentExpression.description];
|
||||||
|
|||||||
15
MathPad/MPPowerTerm.h
Normal file
15
MathPad/MPPowerTerm.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
//
|
||||||
|
// MPPowerTerm.h
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 15.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPFunctionTerm.h"
|
||||||
|
|
||||||
|
@interface MPPowerTerm : MPFunctionTerm
|
||||||
|
|
||||||
|
@property (nonatomic, strong) MPTerm *baseTerm;
|
||||||
|
|
||||||
|
@end
|
||||||
36
MathPad/MPPowerTerm.m
Normal file
36
MathPad/MPPowerTerm.m
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// MPPowerTerm.m
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 15.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPPowerTerm.h"
|
||||||
|
#import "MPParsedExpression.h"
|
||||||
|
|
||||||
|
@implementation MPPowerTerm
|
||||||
|
|
||||||
|
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||||
|
{
|
||||||
|
NSDecimalNumber *exponent = [[self expressionAtIndex:0] evaluate:error];
|
||||||
|
if (!exponent) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
NSDecimalNumber *base = [self.baseTerm evaluate:error];
|
||||||
|
if (!base) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
if ([base isEqualToNumber:@(0)] && [exponent isEqualToNumber:@(0)]) {
|
||||||
|
// The C pow function returns 1 for pow(0, 0). Mathematically this should be undefined.
|
||||||
|
if (error) {
|
||||||
|
*error = [NSError errorWithDomain:MPMathKitErrorDomain
|
||||||
|
code:101
|
||||||
|
userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"0 to the power of 0 is undefined.", nil)}];
|
||||||
|
}
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
return [[NSDecimalNumber alloc] initWithDouble:pow(base.doubleValue, exponent.doubleValue)];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
17
MathPad/MPProductTerm.h
Normal file
17
MathPad/MPProductTerm.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// MPProductTerm.h
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 15.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPTerm.h"
|
||||||
|
|
||||||
|
@interface MPProductTerm : MPTerm
|
||||||
|
|
||||||
|
- (instancetype)initWithFactors:(NSArray *)factors; /* designated initializer */
|
||||||
|
|
||||||
|
@property (readonly, nonatomic, strong) NSArray *factors;
|
||||||
|
|
||||||
|
@end
|
||||||
50
MathPad/MPProductTerm.m
Normal file
50
MathPad/MPProductTerm.m
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
//
|
||||||
|
// MPProductTerm.m
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 15.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPProductTerm.h"
|
||||||
|
|
||||||
|
#import "MPParsedExpression.h"
|
||||||
|
#import "MPPowerFunction.h"
|
||||||
|
|
||||||
|
#import "MPFunctionTerm.h"
|
||||||
|
#import "MPElementaryFunctionTerm.h"
|
||||||
|
#import "MPNumber.h"
|
||||||
|
#import "MPFactorialTerm.h"
|
||||||
|
#import "MPPowerTerm.h"
|
||||||
|
#import "MPVariable.h"
|
||||||
|
|
||||||
|
#import "MPTokenStream.h"
|
||||||
|
#import "MPToken.h"
|
||||||
|
|
||||||
|
@implementation MPProductTerm
|
||||||
|
|
||||||
|
- (instancetype)initWithFactors:(NSArray *)factors
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
if (self) {
|
||||||
|
NSAssert(factors != nil, @"factors must not be nil.");
|
||||||
|
NSAssert(factors.count > 0, @"factors must not be empty.");
|
||||||
|
_factors = factors;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||||
|
{
|
||||||
|
NSDecimalNumber *value = [NSDecimalNumber one];
|
||||||
|
for (MPTerm *factor in self.factors) {
|
||||||
|
NSDecimalNumber *currentValue = [factor evaluate:error];
|
||||||
|
if (!currentValue) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
value = [value decimalNumberByMultiplyingBy:currentValue];
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
#import "MPSumFunction.h"
|
#import "MPSumFunction.h"
|
||||||
|
|
||||||
|
#import "MPSumFunctionTerm.h"
|
||||||
#import "MPExpression.h"
|
#import "MPExpression.h"
|
||||||
#import "MPExpressionTree.h"
|
|
||||||
#import "MPEvaluationContext.h"
|
#import "MPEvaluationContext.h"
|
||||||
|
|
||||||
|
|
||||||
@@ -27,54 +27,16 @@ MPFunctionAccessorImplementation(SumExpression, _sumExpression)
|
|||||||
return @[@"startExpression", @"targetExpression", @"sumExpression"];
|
return @[@"startExpression", @"targetExpression", @"sumExpression"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL)expectsVariableDefinitionInChildAtIndex:(NSUInteger)index
|
||||||
#pragma mark Evaluating Functions
|
|
||||||
|
|
||||||
|
|
||||||
- (BOOL)validate:(NSError *__autoreleasing *)error
|
|
||||||
{
|
{
|
||||||
MPExpressionTree *startTree = [self.startExpression parse];
|
return index == 0;
|
||||||
if (![startTree validateExpectingVariableDefinition:YES error:error]) {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
MPExpressionTree *targetTree = [self.targetExpression parse];
|
|
||||||
if (![targetTree validate:error]) {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[MPEvaluationContext sharedContext] push];
|
|
||||||
[[MPEvaluationContext sharedContext] defineVariable:startTree.definedVariable withValue:[NSDecimalNumber notANumber]];
|
|
||||||
MPExpressionTree *sumTree = [self.sumExpression parse];
|
|
||||||
if (![sumTree validate:error]) {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
[[MPEvaluationContext sharedContext] pop];
|
|
||||||
return YES;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (Class)functionTermClass
|
||||||
- (NSDecimalNumber *)evaluate
|
|
||||||
{
|
{
|
||||||
MPExpressionTree *startTree = [self.startExpression parse];
|
return [MPSumFunctionTerm class];
|
||||||
NSDecimalNumber *start = [startTree evaluate];
|
|
||||||
NSDecimalNumber *target = [[self.targetExpression parse] evaluate];
|
|
||||||
MPExpressionTree *sumTree = [self.sumExpression parse];
|
|
||||||
|
|
||||||
NSDecimalNumber *value = [NSDecimalNumber zero];
|
|
||||||
|
|
||||||
[[MPEvaluationContext sharedContext] push];
|
|
||||||
for (NSDecimalNumber *current = start;
|
|
||||||
[current compare:target] <= 0;
|
|
||||||
current = [current decimalNumberByAdding:[[NSDecimalNumber alloc] initWithInteger:1]]) {
|
|
||||||
[[MPEvaluationContext sharedContext] defineVariable:startTree.definedVariable withValue:current];
|
|
||||||
value = [value decimalNumberByAdding:[sumTree evaluate]];
|
|
||||||
}
|
|
||||||
[[MPEvaluationContext sharedContext] pop];
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#pragma mark Working With Functions
|
#pragma mark Working With Functions
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
13
MathPad/MPSumFunctionTerm.h
Normal file
13
MathPad/MPSumFunctionTerm.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
//
|
||||||
|
// MPSumFunctionTerm.h
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 15.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPFunctionTerm.h"
|
||||||
|
|
||||||
|
@interface MPSumFunctionTerm : MPFunctionTerm
|
||||||
|
|
||||||
|
@end
|
||||||
38
MathPad/MPSumFunctionTerm.m
Normal file
38
MathPad/MPSumFunctionTerm.m
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// MPSumFunctionTerm.m
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 15.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPSumFunctionTerm.h"
|
||||||
|
|
||||||
|
@implementation MPSumFunctionTerm
|
||||||
|
|
||||||
|
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||||
|
{
|
||||||
|
MPParsedExpression *startExpression = [self expressionAtIndex:0];
|
||||||
|
MPParsedExpression *targetExpression = [self expressionAtIndex:1];
|
||||||
|
MPParsedExpression *sumExpression = [self expressionAtIndex:2];
|
||||||
|
|
||||||
|
NSDecimalNumber *start = [startExpression evaluate:error];
|
||||||
|
NSDecimalNumber *target = [targetExpression evaluate:error];
|
||||||
|
NSDecimalNumber *value = [NSDecimalNumber zero];
|
||||||
|
|
||||||
|
for (NSDecimalNumber *current = start;
|
||||||
|
[current compare:target] <= 0;
|
||||||
|
current = [current decimalNumberByAdding:[[NSDecimalNumber alloc] initWithInteger:1]]) {
|
||||||
|
if (![self redefineVariable:startExpression.definedVariable value:current error:error]) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
NSDecimalNumber *currentValue = [sumExpression evaluate:error];
|
||||||
|
if (!currentValue) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
value = [value decimalNumberByAdding:currentValue];
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
18
MathPad/MPSumTerm.h
Normal file
18
MathPad/MPSumTerm.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// MPSumTerm.h
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 14.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPTerm.h"
|
||||||
|
|
||||||
|
@interface MPSumTerm : MPTerm
|
||||||
|
|
||||||
|
- (instancetype)initWithSummands:(NSArray *)summands; /* designated initializer */
|
||||||
|
|
||||||
|
@property (readonly, nonatomic, strong) NSArray *summands;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
43
MathPad/MPSumTerm.m
Normal file
43
MathPad/MPSumTerm.m
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// MPSumTerm.m
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 14.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPSumTerm.h"
|
||||||
|
|
||||||
|
#import "MPParsedExpression.h"
|
||||||
|
#import "MPProductTerm.h"
|
||||||
|
|
||||||
|
#import "MPTokenStream.h"
|
||||||
|
#import "MPToken.h"
|
||||||
|
|
||||||
|
@implementation MPSumTerm
|
||||||
|
|
||||||
|
- (instancetype)initWithSummands:(NSArray *)summands
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
if (self) {
|
||||||
|
NSAssert(summands != nil, @"summands must not be nil.");
|
||||||
|
NSAssert(summands.count > 0, @"summands must not be empty.");
|
||||||
|
_summands = summands;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||||
|
{
|
||||||
|
NSDecimalNumber *value = [NSDecimalNumber zero];
|
||||||
|
for (MPTerm *summand in self.summands) {
|
||||||
|
NSDecimalNumber *currentValue = [summand evaluate:error];
|
||||||
|
if (!currentValue) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
value = [value decimalNumberByAdding:currentValue];
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
28
MathPad/MPTerm.h
Normal file
28
MathPad/MPTerm.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
//
|
||||||
|
// MPTerm.h
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 11.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
@class MPTerm, MPTokenStream;
|
||||||
|
@protocol MPToken;
|
||||||
|
|
||||||
|
@interface MPTerm : NSObject
|
||||||
|
|
||||||
|
- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error;
|
||||||
|
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error;
|
||||||
|
|
||||||
|
- (BOOL)defineVariable:(NSString *)variableName
|
||||||
|
value:(NSDecimalNumber *)value
|
||||||
|
error:(NSError *__autoreleasing *)error;
|
||||||
|
- (BOOL)redefineVariable:(NSString *)variableName
|
||||||
|
value:(NSDecimalNumber *)value
|
||||||
|
error:(NSError *__autoreleasing *)error;
|
||||||
|
- (NSDecimalNumber *)valueForVariable:(NSString *)variableName
|
||||||
|
error:(NSError *__autoreleasing *)error;
|
||||||
|
- (void)undefineVariable:(NSString *)variableName; // Should rarely be needed, defined variables are automatically undefined at the end of evaluation.
|
||||||
|
|
||||||
|
@end
|
||||||
85
MathPad/MPTerm.m
Normal file
85
MathPad/MPTerm.m
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
//
|
||||||
|
// MPTerm.m
|
||||||
|
// MathPad
|
||||||
|
//
|
||||||
|
// Created by Kim Wittenburg on 11.11.14.
|
||||||
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPTerm.h"
|
||||||
|
|
||||||
|
#import "MPParsedExpression.h"
|
||||||
|
#import "MPSumTerm.h"
|
||||||
|
#import "MPEvaluationContext.h"
|
||||||
|
|
||||||
|
@implementation MPTerm
|
||||||
|
|
||||||
|
- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error
|
||||||
|
{
|
||||||
|
[[MPEvaluationContext sharedContext] push];
|
||||||
|
NSDecimalNumber *result = [self doEvaluation:error];
|
||||||
|
[[MPEvaluationContext sharedContext] pop];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||||
|
{
|
||||||
|
NSLog(@"%@", self.class);
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)defineVariable:(NSString *)variableName
|
||||||
|
value:(NSDecimalNumber *)value
|
||||||
|
error:(NSError *__autoreleasing *)error
|
||||||
|
{
|
||||||
|
BOOL couldDefineVariable = [[MPEvaluationContext sharedContext] defineVariable:variableName
|
||||||
|
value:value];
|
||||||
|
if (!couldDefineVariable) {
|
||||||
|
if (error) {
|
||||||
|
NSString *localizedDescription = [NSString stringWithFormat:NSLocalizedString(@"Redifinition of variable \"%@\".", nil), variableName];
|
||||||
|
*error = [NSError errorWithDomain:MPMathKitErrorDomain
|
||||||
|
code:100
|
||||||
|
userInfo:@{NSLocalizedDescriptionKey: localizedDescription}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return couldDefineVariable;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)redefineVariable:(NSString *)variableName
|
||||||
|
value:(NSDecimalNumber *)value
|
||||||
|
error:(NSError *__autoreleasing *)error
|
||||||
|
{
|
||||||
|
BOOL couldDefineVariable = [[MPEvaluationContext sharedContext] redefineVariable:variableName
|
||||||
|
value:value];
|
||||||
|
if (!couldDefineVariable) {
|
||||||
|
if (error) {
|
||||||
|
NSString *localizedDescription = [NSString stringWithFormat:NSLocalizedString(@"Redifinition of variable \"%@\".", nil), variableName];
|
||||||
|
*error = [NSError errorWithDomain:MPMathKitErrorDomain
|
||||||
|
code:100
|
||||||
|
userInfo:@{NSLocalizedDescriptionKey: localizedDescription}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return couldDefineVariable;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSDecimalNumber *)valueForVariable:(NSString *)variableName
|
||||||
|
error:(NSError *__autoreleasing *)error
|
||||||
|
{
|
||||||
|
NSDecimalNumber *value = [[MPEvaluationContext sharedContext] valueForVariable:variableName];
|
||||||
|
if (!value) {
|
||||||
|
if (error) {
|
||||||
|
NSString *localizedDescription = [NSString stringWithFormat:NSLocalizedString(@"Undefined variable \"%@\".", nil), variableName];
|
||||||
|
*error = [NSError errorWithDomain:MPMathKitErrorDomain
|
||||||
|
code:101
|
||||||
|
userInfo:@{NSLocalizedDescriptionKey: localizedDescription}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)undefineVariable:(NSString *)variableName
|
||||||
|
{
|
||||||
|
[[MPEvaluationContext sharedContext] undefineVariable:variableName];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -23,24 +23,11 @@
|
|||||||
@constant MPOperatorListToken
|
@constant MPOperatorListToken
|
||||||
A list of operators (+ and - symbols). The token may be longer
|
A list of operators (+ and - symbols). The token may be longer
|
||||||
than the number of operators if there are spaces between them.
|
than the number of operators if there are spaces between them.
|
||||||
|
|
||||||
@constant MPSinToken
|
@constant MPElementaryFunction
|
||||||
The sin function.
|
@em Most elementary functions are represented by this token type.
|
||||||
|
Elementary functions not categorized as such are those that can
|
||||||
@constant MPCosToken
|
not be represented as text (e.g. roots and powers).
|
||||||
The cos function.
|
|
||||||
|
|
||||||
@constant MPTanToken
|
|
||||||
The tan function.
|
|
||||||
|
|
||||||
@constant MPASinToken
|
|
||||||
The asin function.
|
|
||||||
|
|
||||||
@constant MPACosToken
|
|
||||||
The acos function.
|
|
||||||
|
|
||||||
@constant MPATanToken
|
|
||||||
The atan function.
|
|
||||||
|
|
||||||
@constant MPNumberToken
|
@constant MPNumberToken
|
||||||
A number. This may be an integer or a floating point number.
|
A number. This may be an integer or a floating point number.
|
||||||
@@ -66,18 +53,15 @@
|
|||||||
Any symbol that does not match any other token.
|
Any symbol that does not match any other token.
|
||||||
*/
|
*/
|
||||||
typedef NS_ENUM(NSUInteger, MPTokenType) {
|
typedef NS_ENUM(NSUInteger, MPTokenType) {
|
||||||
|
MPEOFToken = 0,
|
||||||
MPMultiplicationSymbolToken,
|
MPMultiplicationSymbolToken,
|
||||||
MPOperatorListToken,
|
MPOperatorListToken,
|
||||||
MPSinToken,
|
MPElementaryFunctionToken,
|
||||||
MPCosToken,
|
|
||||||
MPTanToken,
|
|
||||||
MPASinToken,
|
|
||||||
MPACosToken,
|
|
||||||
MPATanToken,
|
|
||||||
MPNumberToken,
|
MPNumberToken,
|
||||||
MPVariableToken,
|
MPVariableToken,
|
||||||
MPFactorialToken,
|
|
||||||
MPEqualsToken,
|
MPEqualsToken,
|
||||||
|
MPFactorialToken,
|
||||||
|
MPPowerToken,
|
||||||
MPGenericFunctionToken,
|
MPGenericFunctionToken,
|
||||||
|
|
||||||
MPWhitespaceToken,
|
MPWhitespaceToken,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
@property (nonatomic) NSUInteger currentTokenIndex;
|
@property (nonatomic) NSUInteger currentTokenIndex;
|
||||||
@property (nonatomic) NSUInteger eofLocation;
|
@property (nonatomic) NSUInteger eofLocation;
|
||||||
|
|
||||||
@property (nonatomic) BOOL *whitespaceIgnores;
|
@property (nonatomic) BOOL *whitespaceStates;
|
||||||
@property (nonatomic) NSUInteger maxWhitespaceIgnores;
|
@property (nonatomic) NSUInteger maxWhitespaceIgnores;
|
||||||
@property (nonatomic) NSUInteger currentWhitespaceIgnoreState;
|
@property (nonatomic) NSUInteger currentWhitespaceIgnoreState;
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
self = [super init];
|
self = [super init];
|
||||||
if (self) {
|
if (self) {
|
||||||
self.tokens = tokens;
|
self.tokens = tokens;
|
||||||
self.whitespaceIgnores = malloc(10 * sizeof(BOOL));
|
self.whitespaceStates = malloc(10 * sizeof(BOOL));
|
||||||
self.maxWhitespaceIgnores = 10;
|
self.maxWhitespaceIgnores = 10;
|
||||||
self.currentWhitespaceIgnoreState = 0;
|
self.currentWhitespaceIgnoreState = 0;
|
||||||
[self beginAcceptingWhitespaceTokens];
|
[self beginAcceptingWhitespaceTokens];
|
||||||
@@ -66,14 +66,14 @@
|
|||||||
self.currentWhitespaceIgnoreState++;
|
self.currentWhitespaceIgnoreState++;
|
||||||
if (self.currentWhitespaceIgnoreState >= self.maxWhitespaceIgnores) {
|
if (self.currentWhitespaceIgnoreState >= self.maxWhitespaceIgnores) {
|
||||||
self.maxWhitespaceIgnores += 10;
|
self.maxWhitespaceIgnores += 10;
|
||||||
BOOL *reallocatedWhitespaceIgnores = realloc(self.whitespaceIgnores, self.maxWhitespaceIgnores);
|
BOOL *reallocatedWhitespaceIgnores = realloc(self.whitespaceStates, self.maxWhitespaceIgnores);
|
||||||
if (reallocatedWhitespaceIgnores) {
|
if (reallocatedWhitespaceIgnores) {
|
||||||
self.whitespaceIgnores = reallocatedWhitespaceIgnores;
|
self.whitespaceStates = reallocatedWhitespaceIgnores;
|
||||||
} else {
|
} else {
|
||||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Realloc Failed" userInfo:nil];
|
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Realloc Failed" userInfo:nil];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.whitespaceIgnores[self.currentWhitespaceIgnoreState] = state;
|
self.whitespaceStates[self.currentWhitespaceIgnoreState] = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
|
|
||||||
- (void)skipWhitespaces
|
- (void)skipWhitespaces
|
||||||
{
|
{
|
||||||
if (self.whitespaceIgnores[self.currentWhitespaceIgnoreState]) {
|
if (self.whitespaceStates[self.currentWhitespaceIgnoreState]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
while (_currentTokenIndex < self.tokens.count) {
|
while (_currentTokenIndex < self.tokens.count) {
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
if (token.tokenType != MPWhitespaceToken) {
|
if (token.tokenType != MPWhitespaceToken) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
[self currentTokenConsumed];
|
_currentTokenIndex++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
|
|
||||||
- (void)dealloc
|
- (void)dealloc
|
||||||
{
|
{
|
||||||
free(self.whitespaceIgnores);
|
free(self.whitespaceStates);
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -6,11 +6,13 @@
|
|||||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import "MPValueGroup.h"
|
#import "MPTerm.h"
|
||||||
|
|
||||||
@class MPVariable;
|
@class MPVariable;
|
||||||
|
|
||||||
@interface MPVariable : NSObject <MPValue>
|
@interface MPVariable : MPTerm
|
||||||
|
|
||||||
|
- (instancetype)initWithVariableName:(NSString *)variableName; /* designated initializer */
|
||||||
|
|
||||||
@property (readonly, nonatomic, strong) NSString *variableName;
|
@property (readonly, nonatomic, strong) NSString *variableName;
|
||||||
|
|
||||||
|
|||||||
@@ -8,63 +8,30 @@
|
|||||||
|
|
||||||
#import "MPVariable.h"
|
#import "MPVariable.h"
|
||||||
|
|
||||||
|
#import "MPParsedExpression.h"
|
||||||
|
|
||||||
#import "MPTokenStream.h"
|
#import "MPTokenStream.h"
|
||||||
#import "MPToken.h"
|
#import "MPToken.h"
|
||||||
|
|
||||||
#import "MPExpression.h"
|
#import "MPExpression.h"
|
||||||
#import "MPEvaluationContext.h"
|
#import "MPEvaluationContext.h"
|
||||||
|
|
||||||
@implementation MPVariable {
|
@implementation MPVariable
|
||||||
NSRange _range;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)init
|
- (instancetype)initWithVariableName:(NSString *)variableName
|
||||||
{
|
{
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (self) {
|
if (self) {
|
||||||
|
NSAssert(variableName != nil, @"variableName must not be nil.");
|
||||||
|
_variableName = variableName;
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream
|
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||||
{
|
{
|
||||||
self = [self init];
|
return [self valueForVariable:self.variableName
|
||||||
if (self) {
|
error:error];
|
||||||
[tokenStream beginIgnoringWhitespaceTokens];
|
|
||||||
MPToken *token = tokenStream.currentToken;
|
|
||||||
if (token.tokenType == MPVariableToken) {
|
|
||||||
_variableName = token.stringValue;
|
|
||||||
_range = token.range;
|
|
||||||
} else {
|
|
||||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Expected Variable" userInfo:nil];
|
|
||||||
}
|
|
||||||
[tokenStream currentTokenConsumed];
|
|
||||||
[tokenStream endIgnoringOrAcceptingWhitespaceTokens];
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSRange)range
|
|
||||||
{
|
|
||||||
return _range;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)validate:(NSError *__autoreleasing *)error
|
|
||||||
{
|
|
||||||
if (![[MPEvaluationContext sharedContext] isVariableDefined:self.variableName]) {
|
|
||||||
if (error) {
|
|
||||||
*error = MPParseError(9,
|
|
||||||
NSLocalizedString(@"Undefined Variable.", @"Error message. This is displayed when an expression contains an undefined variable."),
|
|
||||||
nil);
|
|
||||||
}
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSDecimalNumber *)evaluate
|
|
||||||
{
|
|
||||||
return [[MPEvaluationContext sharedContext] valueForVariable:self.variableName];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
Reference in New Issue
Block a user