Archived
1

Fundamental Redesign of Evaluation

This commit is contained in:
Kim Wittenburg
2014-11-24 22:42:44 +01:00
parent 10f0e73ad3
commit 7a32e3b0b6
45 changed files with 1398 additions and 350 deletions

View 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

View 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

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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];
}

View 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

View 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

View File

@@ -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
View 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
View 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

View File

@@ -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
View 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
View 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

View File

@@ -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];

View File

@@ -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

View File

@@ -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
View 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
View 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
View 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
View 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

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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

View 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

View 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

View 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

View 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

View File

@@ -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

View File

@@ -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
View 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
View 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
View 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
View 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

View File

@@ -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

View 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

View 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
View 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
View 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
View 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
View 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

View File

@@ -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,

View File

@@ -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

View File

@@ -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;

View File

@@ -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