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)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;
|
||||
- (BOOL)isVariableDefined:(NSString *)variable;
|
||||
|
||||
- (NSDecimalNumber *)valueForVariable:(NSString *)variable;
|
||||
|
||||
|
||||
@@ -32,8 +32,10 @@ static MPEvaluationContext *sharedContext;
|
||||
if (self) {
|
||||
_stack = [[NSMutableArray alloc] init];
|
||||
[self push];
|
||||
[self defineVariable:@"e" withValue:[[NSDecimalNumber alloc] initWithDouble:M_E]];
|
||||
[self defineVariable:@"π" withValue:[[NSDecimalNumber alloc] initWithDouble:M_PI]];
|
||||
[self defineVariable:@"e"
|
||||
value:[[NSDecimalNumber alloc] initWithDouble:M_E]];
|
||||
[self defineVariable:@"π"
|
||||
value:[[NSDecimalNumber alloc] initWithDouble:M_PI]];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -48,10 +50,23 @@ static MPEvaluationContext *sharedContext;
|
||||
[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;
|
||||
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
|
||||
@@ -60,6 +75,11 @@ static MPEvaluationContext *sharedContext;
|
||||
[currentBindings removeObjectForKey:variable];
|
||||
}
|
||||
|
||||
- (BOOL)isVariableDefined:(NSString *)variable
|
||||
{
|
||||
return [self valueForVariable:variable] != nil;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)valueForVariable:(NSString *)variable
|
||||
{
|
||||
NSUInteger currentIndex = self.stack.count;
|
||||
@@ -72,9 +92,4 @@ static MPEvaluationContext *sharedContext;
|
||||
return value;
|
||||
}
|
||||
|
||||
- (BOOL)isVariableDefined:(NSString *)variable
|
||||
{
|
||||
return [self valueForVariable:variable] != nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -31,57 +31,6 @@ FOUNDATION_EXPORT NSString *const MPIllegalElementException;
|
||||
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
|
||||
@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;
|
||||
|
||||
/*!
|
||||
@@ -630,7 +579,7 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) {
|
||||
want more control over the evaluation process use @c -parse
|
||||
instead.
|
||||
|
||||
@param error
|
||||
@param errors
|
||||
If the receiver (or any of its elements) contains a syntax error
|
||||
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
|
||||
@@ -642,7 +591,7 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) {
|
||||
math error. In most cases the error parameter contains an
|
||||
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.
|
||||
*/
|
||||
- (MPExpressionTree *)parse;
|
||||
- (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors;
|
||||
|
||||
- (MPParsedExpression *)parseExpectingVariable:(BOOL)flag
|
||||
errors:(NSArray *__autoreleasing *)errors;
|
||||
|
||||
|
||||
#pragma mark Notifications
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
#import "MPRangePath.h"
|
||||
|
||||
#import "MPExpressionTokenizer.h"
|
||||
#import "MPTokenStream.h"
|
||||
#import "MPToken.h"
|
||||
#import "MPExpressionTree.h"
|
||||
#import "MPExpressionParser.h"
|
||||
#import "MPParsedExpression.h"
|
||||
|
||||
#import "NSIndexPath+MPAdditions.h"
|
||||
|
||||
@@ -25,10 +25,6 @@
|
||||
NSString *const MPIllegalElementException = @"MPIllegalElementException";
|
||||
NSString *const MPIllegalElementExceptionElementKey = @"MPIllegalElementExceptionElementKey";
|
||||
|
||||
NSString *const MPMathKitErrorDomain = @"MPMathKitErrorDomain";
|
||||
NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey";
|
||||
|
||||
|
||||
|
||||
@interface MPExpression () {
|
||||
NSMutableArray * _elements;
|
||||
@@ -328,7 +324,11 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey";
|
||||
break;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -556,17 +556,38 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey";
|
||||
#pragma mark Evaluating Expressions
|
||||
|
||||
|
||||
- (NSDecimalNumber *)evaluateWithError:(NSError *__autoreleasing *)error
|
||||
- (NSDecimalNumber *)evaluateWithErrors:(NSArray *__autoreleasing *)errors
|
||||
{
|
||||
MPExpressionTree *tree = [self parse];
|
||||
return [tree validate:error] ? [tree evaluate] : nil;
|
||||
NSArray *parsingErrors;
|
||||
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 [[MPExpressionTree alloc] initWithTokenStream:tokenStream];
|
||||
return [self parseExpectingVariable:NO
|
||||
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*[+-])*)|"
|
||||
@"((?:\\d+(?:%@\\d+)?)|(?:%@\\d+))|"
|
||||
@"(sin)|"
|
||||
@"(cos)|"
|
||||
@"(tan)|"
|
||||
@"(asin)|"
|
||||
@"(acos)|"
|
||||
@"(atan)|"
|
||||
@"(sin|cos|tan|asin|acos|atan)|"
|
||||
@"([A-Za-z])|"
|
||||
@"(!)|"
|
||||
@"(=)|"
|
||||
@@ -76,16 +71,11 @@
|
||||
NSRange multiplicationSymbolRange = [match rangeAtIndex:1];
|
||||
NSRange operatorRange = [match rangeAtIndex:2];
|
||||
NSRange numberRange = [match rangeAtIndex:3];
|
||||
NSRange sinRange = [match rangeAtIndex:4];
|
||||
NSRange cosRange = [match rangeAtIndex:5];
|
||||
NSRange tanRange = [match rangeAtIndex:6];
|
||||
NSRange asinRange = [match rangeAtIndex:7];
|
||||
NSRange acosRange = [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];
|
||||
NSRange elementaryFunctionRange = [match rangeAtIndex:4];
|
||||
NSRange variableRange = [match rangeAtIndex:5];
|
||||
NSRange factorialRange = [match rangeAtIndex:6];
|
||||
NSRange equalsRange = [match rangeAtIndex:7];
|
||||
NSRange whitespaceRange = [match rangeAtIndex:8];
|
||||
|
||||
if (MPRangeExists(multiplicationSymbolRange)) {
|
||||
range = multiplicationSymbolRange;
|
||||
@@ -96,24 +86,9 @@
|
||||
} else if (MPRangeExists(numberRange)) {
|
||||
range = numberRange;
|
||||
tokenType = MPNumberToken;
|
||||
} else if (MPRangeExists(sinRange)) {
|
||||
range = sinRange;
|
||||
tokenType = MPSinToken;
|
||||
} 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(elementaryFunctionRange)) {
|
||||
range = elementaryFunctionRange;
|
||||
tokenType = MPElementaryFunctionToken;
|
||||
} else if (MPRangeExists(variableRange)) {
|
||||
range = variableRange;
|
||||
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 "MPFractionTerm.h"
|
||||
#import "MPExpression.h"
|
||||
#import "MPExpressionTree.h"
|
||||
|
||||
@implementation MPFractionFunction
|
||||
|
||||
@@ -21,14 +21,9 @@ MPFunctionAccessorImplementation(DenominatorExpression, _denominatorExpression)
|
||||
return @[@"nominatorExpression", @"denominatorExpression"];
|
||||
}
|
||||
|
||||
- (BOOL)validate:(NSError *__autoreleasing *)error
|
||||
- (Class)functionTermClass
|
||||
{
|
||||
return [[self.nominatorExpression parse] validate:error] && [[self.denominatorExpression parse] validate:error];
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)evaluate
|
||||
{
|
||||
return [[[self.nominatorExpression parse] evaluate] decimalNumberByDividingBy:[[self.denominatorExpression parse] evaluate]];
|
||||
return [MPFractionTerm class];
|
||||
}
|
||||
|
||||
- (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 "MPExpression.h"
|
||||
#import "MPPowerFunction.h"
|
||||
|
||||
@implementation MPFunction (MPToken)
|
||||
|
||||
- (MPTokenType)tokenType
|
||||
{
|
||||
return MPGenericFunctionToken;
|
||||
return [self isMemberOfClass:[MPPowerFunction class]] ? MPPowerToken : MPGenericFunctionToken;
|
||||
}
|
||||
|
||||
|
||||
- (NSRange)range
|
||||
{
|
||||
NSUInteger selfIndex = [self.parent indexOfElement:self];
|
||||
return NSMakeRange([self.parent convertIndex:selfIndex
|
||||
return NSMakeRange([self.parent convertIndex:[self.parent indexOfElement:self]
|
||||
fromReferenceFrame:MPElementReferenceFrame
|
||||
toReferenceFrame:MPSymbolReferenceFrame],
|
||||
1);
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)exists
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)stringValue
|
||||
{
|
||||
return [self description];
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
|
||||
|
||||
@class MPFunction, MPExpression, MPRangePath;
|
||||
@class MPFunction, MPExpression, MPRangePath, MPFunctionTerm;
|
||||
@protocol MPExpressionElement;
|
||||
|
||||
|
||||
@@ -241,6 +241,12 @@
|
||||
*/
|
||||
- (void)didChangeChildAtIndex:(NSUInteger)index;
|
||||
|
||||
|
||||
#pragma mark Evaluating Functions
|
||||
|
||||
|
||||
- (BOOL)expectsVariableDefinitionInChildAtIndex:(NSUInteger)index;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -256,6 +262,10 @@
|
||||
*/
|
||||
@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
|
||||
/*!
|
||||
@methodgroup Working With the Expression Tree
|
||||
@@ -281,40 +291,6 @@
|
||||
@methodgroup Evaluating Functions
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
@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.
|
||||
*/
|
||||
- (Class)functionTermClass;
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#import "MPRangePath.h"
|
||||
#import "NSIndexPath+MPAdditions.h"
|
||||
|
||||
#import "MPFunctionTerm.h"
|
||||
|
||||
|
||||
|
||||
@implementation MPFunction
|
||||
@@ -131,6 +133,15 @@
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Evaluating Functions
|
||||
|
||||
|
||||
- (BOOL)expectsVariableDefinitionInChildAtIndex:(NSUInteger)index
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
#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.
|
||||
//
|
||||
|
||||
#import "MPValueGroup.h"
|
||||
#import "MPTerm.h"
|
||||
|
||||
@class MPNumber;
|
||||
|
||||
@interface MPNumber : NSObject <MPValue>
|
||||
@interface MPNumber : MPTerm
|
||||
|
||||
- (instancetype)initWithNumber:(NSDecimalNumber *)number; /* designated initializer */
|
||||
|
||||
@property (readonly, nonatomic, strong) NSDecimalNumber *number;
|
||||
|
||||
|
||||
@@ -8,44 +8,24 @@
|
||||
|
||||
#import "MPNumber.h"
|
||||
|
||||
#import "MPParsedExpression.h"
|
||||
|
||||
#import "MPTokenStream.h"
|
||||
#import "MPToken.h"
|
||||
|
||||
@implementation MPNumber
|
||||
|
||||
- (instancetype)init
|
||||
- (instancetype)initWithNumber:(NSDecimalNumber *)number
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
NSAssert(number != nil, @"number must not be nil.");
|
||||
_number = number;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream
|
||||
{
|
||||
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
|
||||
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return self.number;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#import "MPParenthesisFunction.h"
|
||||
|
||||
#import "MPExpression.h"
|
||||
#import "MPExpressionTree.h"
|
||||
#import "MPParenthesisTerm.h"
|
||||
|
||||
@implementation MPParenthesisFunction
|
||||
|
||||
@@ -21,14 +21,9 @@ MPFunctionAccessorImplementation(Expression, _expression)
|
||||
return @[@"expression"];
|
||||
}
|
||||
|
||||
- (BOOL)validate:(NSError *__autoreleasing *)error
|
||||
- (Class)functionTermClass
|
||||
{
|
||||
return [[self.expression parse] validate:error];
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)evaluate
|
||||
{
|
||||
return [[self.expression parse] evaluate];
|
||||
return [MPParenthesisTerm class];
|
||||
}
|
||||
|
||||
- (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.
|
||||
//
|
||||
|
||||
#import "MPSuffixFunction.h"
|
||||
#import "MPFunction.h"
|
||||
|
||||
@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;
|
||||
|
||||
@end
|
||||
|
||||
@@ -9,9 +9,6 @@
|
||||
#import "MPPowerFunction.h"
|
||||
|
||||
#import "MPExpression.h"
|
||||
#import "MPExpressionTree.h"
|
||||
|
||||
#import "MPValueGroup.h"
|
||||
|
||||
@implementation MPPowerFunction
|
||||
|
||||
@@ -22,30 +19,6 @@ MPFunctionAccessorImplementation(ExponentExpression, _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
|
||||
{
|
||||
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 "MPSumFunctionTerm.h"
|
||||
#import "MPExpression.h"
|
||||
#import "MPExpressionTree.h"
|
||||
#import "MPEvaluationContext.h"
|
||||
|
||||
|
||||
@@ -27,54 +27,16 @@ MPFunctionAccessorImplementation(SumExpression, _sumExpression)
|
||||
return @[@"startExpression", @"targetExpression", @"sumExpression"];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Evaluating Functions
|
||||
|
||||
|
||||
- (BOOL)validate:(NSError *__autoreleasing *)error
|
||||
- (BOOL)expectsVariableDefinitionInChildAtIndex:(NSUInteger)index
|
||||
{
|
||||
MPExpressionTree *startTree = [self.startExpression parse];
|
||||
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;
|
||||
return index == 0;
|
||||
}
|
||||
|
||||
|
||||
- (NSDecimalNumber *)evaluate
|
||||
- (Class)functionTermClass
|
||||
{
|
||||
MPExpressionTree *startTree = [self.startExpression parse];
|
||||
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;
|
||||
return [MPSumFunctionTerm class];
|
||||
}
|
||||
|
||||
|
||||
#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
|
||||
A list of operators (+ and - symbols). The token may be longer
|
||||
than the number of operators if there are spaces between them.
|
||||
|
||||
@constant MPSinToken
|
||||
The sin function.
|
||||
|
||||
@constant MPCosToken
|
||||
The cos function.
|
||||
|
||||
@constant MPTanToken
|
||||
The tan function.
|
||||
|
||||
@constant MPASinToken
|
||||
The asin function.
|
||||
|
||||
@constant MPACosToken
|
||||
The acos function.
|
||||
|
||||
@constant MPATanToken
|
||||
The atan function.
|
||||
|
||||
@constant MPElementaryFunction
|
||||
@em Most elementary functions are represented by this token type.
|
||||
Elementary functions not categorized as such are those that can
|
||||
not be represented as text (e.g. roots and powers).
|
||||
|
||||
@constant MPNumberToken
|
||||
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.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, MPTokenType) {
|
||||
MPEOFToken = 0,
|
||||
MPMultiplicationSymbolToken,
|
||||
MPOperatorListToken,
|
||||
MPSinToken,
|
||||
MPCosToken,
|
||||
MPTanToken,
|
||||
MPASinToken,
|
||||
MPACosToken,
|
||||
MPATanToken,
|
||||
MPElementaryFunctionToken,
|
||||
MPNumberToken,
|
||||
MPVariableToken,
|
||||
MPFactorialToken,
|
||||
MPEqualsToken,
|
||||
MPFactorialToken,
|
||||
MPPowerToken,
|
||||
MPGenericFunctionToken,
|
||||
|
||||
MPWhitespaceToken,
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
@property (nonatomic) NSUInteger currentTokenIndex;
|
||||
@property (nonatomic) NSUInteger eofLocation;
|
||||
|
||||
@property (nonatomic) BOOL *whitespaceIgnores;
|
||||
@property (nonatomic) BOOL *whitespaceStates;
|
||||
@property (nonatomic) NSUInteger maxWhitespaceIgnores;
|
||||
@property (nonatomic) NSUInteger currentWhitespaceIgnoreState;
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.tokens = tokens;
|
||||
self.whitespaceIgnores = malloc(10 * sizeof(BOOL));
|
||||
self.whitespaceStates = malloc(10 * sizeof(BOOL));
|
||||
self.maxWhitespaceIgnores = 10;
|
||||
self.currentWhitespaceIgnoreState = 0;
|
||||
[self beginAcceptingWhitespaceTokens];
|
||||
@@ -66,14 +66,14 @@
|
||||
self.currentWhitespaceIgnoreState++;
|
||||
if (self.currentWhitespaceIgnoreState >= self.maxWhitespaceIgnores) {
|
||||
self.maxWhitespaceIgnores += 10;
|
||||
BOOL *reallocatedWhitespaceIgnores = realloc(self.whitespaceIgnores, self.maxWhitespaceIgnores);
|
||||
BOOL *reallocatedWhitespaceIgnores = realloc(self.whitespaceStates, self.maxWhitespaceIgnores);
|
||||
if (reallocatedWhitespaceIgnores) {
|
||||
self.whitespaceIgnores = reallocatedWhitespaceIgnores;
|
||||
self.whitespaceStates = reallocatedWhitespaceIgnores;
|
||||
} else {
|
||||
@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
|
||||
{
|
||||
if (self.whitespaceIgnores[self.currentWhitespaceIgnoreState]) {
|
||||
if (self.whitespaceStates[self.currentWhitespaceIgnoreState]) {
|
||||
return;
|
||||
}
|
||||
while (_currentTokenIndex < self.tokens.count) {
|
||||
@@ -93,7 +93,7 @@
|
||||
if (token.tokenType != MPWhitespaceToken) {
|
||||
return;
|
||||
}
|
||||
[self currentTokenConsumed];
|
||||
_currentTokenIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
free(self.whitespaceIgnores);
|
||||
free(self.whitespaceStates);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPValueGroup.h"
|
||||
#import "MPTerm.h"
|
||||
|
||||
@class MPVariable;
|
||||
|
||||
@interface MPVariable : NSObject <MPValue>
|
||||
@interface MPVariable : MPTerm
|
||||
|
||||
- (instancetype)initWithVariableName:(NSString *)variableName; /* designated initializer */
|
||||
|
||||
@property (readonly, nonatomic, strong) NSString *variableName;
|
||||
|
||||
|
||||
@@ -8,63 +8,30 @@
|
||||
|
||||
#import "MPVariable.h"
|
||||
|
||||
#import "MPParsedExpression.h"
|
||||
|
||||
#import "MPTokenStream.h"
|
||||
#import "MPToken.h"
|
||||
|
||||
#import "MPExpression.h"
|
||||
#import "MPEvaluationContext.h"
|
||||
|
||||
@implementation MPVariable {
|
||||
NSRange _range;
|
||||
}
|
||||
@implementation MPVariable
|
||||
|
||||
- (instancetype)init
|
||||
- (instancetype)initWithVariableName:(NSString *)variableName
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
NSAssert(variableName != nil, @"variableName must not be nil.");
|
||||
_variableName = variableName;
|
||||
}
|
||||
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 == 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];
|
||||
return [self valueForVariable:self.variableName
|
||||
error:error];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Reference in New Issue
Block a user