Added the MPExpressionTree Classes
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
|
||||
@import Foundation;
|
||||
#import "NSString+MPExpressionElement.h"
|
||||
#import "MPExpressionTree.h"
|
||||
#import "MPToken.h"
|
||||
|
||||
typedef NS_ENUM(NSUInteger, MPReferenceFrame) {
|
||||
@@ -77,6 +78,8 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) {
|
||||
*/
|
||||
- (instancetype)init;
|
||||
|
||||
- (instancetype)initWithExpressionTree:(MPExpressionTree *)expressionTree;
|
||||
|
||||
|
||||
/*!
|
||||
@method initWithElement:
|
||||
@@ -358,6 +361,7 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) {
|
||||
*/
|
||||
- (NSDecimalNumber *)evaluateWithError:(MPParseError *__autoreleasing *)error;
|
||||
|
||||
- (MPExpressionTree *)parse;
|
||||
|
||||
#pragma mark Notifications
|
||||
// All notification methods should create a new rangePath with the receiver's index added to the beginning of the path and then ascend the message to it's parent
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#import "MPException.h"
|
||||
|
||||
#import "MPExpressionTokenizer.h"
|
||||
#import "MPExpressionEvaluator.h"
|
||||
#import "MPToken.h"
|
||||
|
||||
|
||||
@@ -55,6 +54,11 @@
|
||||
return [self initWithElements:@[]];
|
||||
}
|
||||
|
||||
- (instancetype)initWithExpressionTree:(MPExpressionTree *)expressionTree
|
||||
{
|
||||
return [self initWithElements:expressionTree.expressionElements];
|
||||
}
|
||||
|
||||
- (instancetype)initWithElement:(id<MPExpressionElement>)element
|
||||
{
|
||||
return [self initWithElements:@[element]];
|
||||
@@ -518,13 +522,22 @@
|
||||
|
||||
#pragma mark Evaluating Expressions
|
||||
|
||||
|
||||
- (NSDecimalNumber *)evaluateWithError:(MPParseError *__autoreleasing *)error
|
||||
{
|
||||
MPExpressionEvaluator *evaluator = [[MPExpressionEvaluator alloc] initWithExpression:self];
|
||||
MPTerm *term = [evaluator parseExpectingVariable:NO
|
||||
error:error];
|
||||
return [term evaluate];
|
||||
MPExpressionTree *tree = [self parse];
|
||||
if ([tree validate:error]) {
|
||||
return [tree evaluate];
|
||||
}
|
||||
if (error) {
|
||||
(*error).pathToExpression = self.indexPath;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (MPExpressionTree *)parse
|
||||
{
|
||||
MPTokenStream *tokenStream = [[MPTokenStream alloc] initWithTokens:self.tokens];
|
||||
return [[MPExpressionTree alloc] initWithTokenStream:tokenStream];
|
||||
}
|
||||
|
||||
#pragma mark Notifications
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
//
|
||||
// MPExpressionEvaluator.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 31.08.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPExpression.h"
|
||||
#import "MPExpressionTokenizer.h"
|
||||
#import "MPTerm.h"
|
||||
|
||||
@class MPExpressionEvaluator, MPExpression, MPParsedElementOld;
|
||||
|
||||
@interface MPExpressionEvaluator : NSObject
|
||||
|
||||
- (instancetype)initWithExpression:(MPExpression *)expression;
|
||||
@property (readonly, nonatomic, weak) MPExpression *expression;
|
||||
|
||||
#pragma mark Evaluating Expressions
|
||||
@property (readonly, nonatomic, copy) NSString *definedVariable;
|
||||
|
||||
- (MPTerm *)parseExpectingVariable:(BOOL)flag error:(MPParseError *__autoreleasing *)error;
|
||||
|
||||
@end
|
||||
@@ -1,261 +0,0 @@
|
||||
//
|
||||
// MPExpressionEvaluator.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 31.08.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPExpressionEvaluator.h"
|
||||
#import "MPExpression.h"
|
||||
#import "MPFunction.h"
|
||||
#import "MPMathRules.h"
|
||||
#import "MPToken.h"
|
||||
#import "MPFunction+MPToken.h"
|
||||
#import "MPTokenStream.h"
|
||||
|
||||
#import "MPEvaluationContext.h"
|
||||
#import "MPPowerFunction.h"
|
||||
|
||||
|
||||
|
||||
@interface MPExpressionEvaluator ()
|
||||
@property (readwrite, nonatomic, copy) NSString *definedVariable;
|
||||
|
||||
|
||||
- (void)setError:(MPParseError *)error;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPExpressionEvaluator {
|
||||
MPParseError *__autoreleasing *_error;
|
||||
MPTokenStream *tokenStream;
|
||||
}
|
||||
|
||||
- (id)initWithExpression:(MPExpression *)expression
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_expression = expression;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark Evaluating Expressions
|
||||
- (void)setError:(MPParseError *)error
|
||||
{
|
||||
if (_error) {
|
||||
error.pathToExpression = self.expression.indexPath;
|
||||
*_error = error;
|
||||
}
|
||||
}
|
||||
|
||||
- (MPTerm *)parseExpectingVariable:(BOOL)flag
|
||||
error:(MPParseError *__autoreleasing *)error
|
||||
{
|
||||
_error = error;
|
||||
tokenStream = [[MPTokenStream alloc] initWithTokens:[self.expression allItemsInReferenceFrame:MPTokenReferenceFrame]];
|
||||
if (!tokenStream.hasMoreTokens) {
|
||||
self.error = MPParseError(NSMakeRange(0, 0), @"Empty Expression");
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Variable Definition
|
||||
if (flag) {
|
||||
MPToken *variableToken = [tokenStream nextToken];
|
||||
MPToken *equalsToken = [tokenStream nextTokenOfType:MPEqualsToken];
|
||||
|
||||
if (variableToken.tokenType != MPVariableToken) {
|
||||
self.error = MPParseError(variableToken.range, @"Expected Variable Definition.");
|
||||
return nil;
|
||||
}
|
||||
if (equalsToken.tokenType != MPEqualsToken) {
|
||||
self.error = MPParseError(equalsToken.range, @"Expected \"=\".");
|
||||
return nil;
|
||||
}
|
||||
self.definedVariable = variableToken.stringValue;
|
||||
}
|
||||
|
||||
BOOL isAtBeginning = YES;
|
||||
NSMutableArray *summands = [[NSMutableArray alloc] init];
|
||||
NSMutableArray *currentSummand = [[NSMutableArray alloc] init];
|
||||
|
||||
while (tokenStream.hasMoreTokens) {
|
||||
MPToken *multiplicationToken = [tokenStream nextTokenOfType:MPMultiplicationSymbolToken];
|
||||
MPToken *operatorToken = [tokenStream nextTokenOfType:MPOperatorListToken];
|
||||
NSRange valueRange;
|
||||
MPTerm *value = [self nextValue:&valueRange];
|
||||
|
||||
if (multiplicationToken) {
|
||||
if (isAtBeginning) {
|
||||
self.error = MPParseError(multiplicationToken.range, @"Unexpected Symbol. Expected Number.");
|
||||
return nil;
|
||||
}
|
||||
if (operatorToken) {
|
||||
if (operatorToken.numberOfOperators > [[MPMathRules sharedRules] maximumOperatorChainLengthInMultiplication]) {
|
||||
self.error = MPParseError(operatorToken.range, @"Too many operators in multiplication");
|
||||
return nil;
|
||||
}
|
||||
[currentSummand addObject:[[MPTerm alloc] initWithNumber:operatorToken.operatorValue]];
|
||||
}
|
||||
if (value) {
|
||||
[currentSummand addObject:value];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
} else {
|
||||
if (operatorToken) {
|
||||
if (operatorToken.numberOfOperators > [[MPMathRules sharedRules] maximumOperatorChainLength]) {
|
||||
self.error = MPParseError(operatorToken.range, @"Too many operators.");
|
||||
return nil;
|
||||
}
|
||||
[summands addObject:[[MPTerm alloc] initWithFactors:currentSummand.copy]];
|
||||
currentSummand = [[NSMutableArray alloc] init];
|
||||
[currentSummand addObject:[[MPTerm alloc] initWithNumber:operatorToken.operatorValue]];
|
||||
if (value) {
|
||||
[currentSummand addObject:value];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
} else if (!value) {
|
||||
return nil;
|
||||
} else if (isAtBeginning || [[MPMathRules sharedRules] allowsImplicitMultiplication]) {
|
||||
[currentSummand addObject:value];
|
||||
} else {
|
||||
self.error = MPParseError(NSMakeRange(valueRange.location, 0), @"Implicit Multiplication not allowed here");
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
isAtBeginning = NO;
|
||||
}
|
||||
[summands addObject:[[MPTerm alloc] initWithFactors:currentSummand.copy]];
|
||||
return [[MPTerm alloc] initWithSummands:summands.copy];
|
||||
}
|
||||
|
||||
- (MPTerm *)nextValue:(NSRangePointer)range
|
||||
{
|
||||
MPToken *token = [tokenStream nextToken];
|
||||
if (range) {
|
||||
*range = token.range;
|
||||
}
|
||||
switch (token.tokenType) {
|
||||
case MPNumberToken:
|
||||
{
|
||||
return [self decoratedTerm:[[MPTerm alloc] initWithNumber:token.number]];
|
||||
}
|
||||
|
||||
case MPVariableToken:
|
||||
{
|
||||
if(![[MPEvaluationContext sharedContext] isVariableDefined:token.variable]) {
|
||||
self.error = MPParseError(token.range, @"Undefined Variable");
|
||||
return nil;
|
||||
}
|
||||
return [self decoratedTerm:[[MPTerm alloc] initWithVariable:token.variable]];
|
||||
}
|
||||
|
||||
case MPGenericFunctionToken:
|
||||
{
|
||||
if ([token isKindOfClass:[MPPowerFunction class]]) {
|
||||
self.error = MPParseError(NSMakeRange(token.range.location, 0), @"No Base for Power");
|
||||
return nil;
|
||||
}
|
||||
return [self decoratedTerm:[((MPFunction *)token) parseWithError:_error]];
|
||||
}
|
||||
|
||||
case MPSinToken:
|
||||
{
|
||||
BOOL inverse = [self inverseFunction];
|
||||
NSRange sinTermRange;
|
||||
MPTerm *sinTerm = [self nextValue:&sinTermRange];
|
||||
if (!sinTerm) {
|
||||
if (range) {
|
||||
*range = NSUnionRange(token.range, sinTermRange);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
return inverse ? [[MPTerm alloc] initWithInverseSinOfTerm:sinTerm] : [[MPTerm alloc] initWithSinOfTerm:sinTerm];
|
||||
}
|
||||
|
||||
case MPCosToken:
|
||||
{
|
||||
BOOL inverse = [self inverseFunction];
|
||||
NSRange cosTermRange;
|
||||
MPTerm *cosTerm = [self nextValue:&cosTermRange];
|
||||
if (!cosTerm) {
|
||||
if (range) {
|
||||
*range = NSUnionRange(token.range, cosTermRange);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
return inverse ? [[MPTerm alloc] initWithInverseCosOfTerm:cosTerm] : [[MPTerm alloc] initWithCosOfTerm:cosTerm];
|
||||
}
|
||||
|
||||
case MPTanToken:
|
||||
{
|
||||
BOOL inverse = [self inverseFunction];
|
||||
NSRange tanTermRange;
|
||||
MPTerm *tanTerm = [self nextValue:&tanTermRange];
|
||||
if (!tanTerm) {
|
||||
if (range) {
|
||||
*range = NSUnionRange(token.range, tanTermRange);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
return inverse ? [[MPTerm alloc] initWithInverseTanOfTerm:tanTerm] : [[MPTerm alloc] initWithTanOfTerm:tanTerm];
|
||||
}
|
||||
|
||||
case MPEOFToken:
|
||||
{
|
||||
self.error = MPParseError(token.range, @"Unexpected End. Expected Number.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
self.error = MPParseError(token.range, @"Unexpected Symbol. Expected Number.");
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (MPTerm *)decoratedTerm:(MPTerm *)term
|
||||
{
|
||||
MPTerm *decoratedTerm = term;
|
||||
MPToken *facultyToken = [tokenStream nextTokenOfType:MPFactorialToken];
|
||||
if (facultyToken) {
|
||||
decoratedTerm = [[MPTerm alloc] initWithFactorialOfTerm:term];
|
||||
}
|
||||
MPToken *powerToken = [tokenStream nextTokenOfType:MPGenericFunctionToken];
|
||||
if ([powerToken isKindOfClass:[MPPowerFunction class]]) {
|
||||
MPPowerFunction *powerFunction = (MPPowerFunction *)powerToken;
|
||||
powerFunction.baseTerm = decoratedTerm;
|
||||
return [powerFunction parseWithError:_error];
|
||||
} else if (powerToken) {
|
||||
tokenStream.currentTokenIndex--;
|
||||
}
|
||||
return decoratedTerm;
|
||||
}
|
||||
|
||||
- (BOOL)inverseFunction
|
||||
{
|
||||
MPToken *powerToken = [tokenStream nextTokenOfType:MPGenericFunctionToken];
|
||||
if ([powerToken isKindOfClass:[MPPowerFunction class]]) {
|
||||
MPPowerFunction *powerFunction = (MPPowerFunction *)powerToken;
|
||||
if (powerFunction.exponentExpression.countElements == 1) {
|
||||
id<MPExpressionElement> element = [powerFunction.exponentExpression elementAtIndex:0];
|
||||
if ([element isString]) {
|
||||
NSString *exponent = [[((NSString *)element) componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] componentsJoinedByString:@""];
|
||||
if ([exponent isEqualToString:@"-1"]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(powerToken) {
|
||||
tokenStream.currentTokenIndex--;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -11,6 +11,10 @@
|
||||
#import "MPExpression.h"
|
||||
#import "MPFunctionLayout.h"
|
||||
|
||||
#define kMPEmptyBoxWidth (self.usesSmallSize ? 2.0 : 3.0)
|
||||
#define kMPEmptyBoxHeight (CTFontGetDescent((CTFontRef)self.font) + CTFontGetAscent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font))
|
||||
#define kMPEmptyBoxYOrigin (-(CTFontGetDescent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font)))
|
||||
|
||||
@interface MPExpressionLayout : MPLayout
|
||||
|
||||
- (instancetype)initWithExpression:(MPExpression *)expression
|
||||
|
||||
@@ -14,10 +14,6 @@
|
||||
#import "NSString+MPExpressionElement.h"
|
||||
#import "NSIndexPath+MPAdditions.h"
|
||||
|
||||
#define kMPEmptyBoxWidth (self.usesSmallSize ? 2.0 : 3.0)
|
||||
#define kMPEmptyBoxHeight (CTFontGetDescent((CTFontRef)self.font) + CTFontGetAscent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font))
|
||||
#define kMPEmptyBoxYOrigin (-(CTFontGetDescent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font)))
|
||||
|
||||
#define kMPEmptyBoxDrawingWidth kMPEmptyBoxWidth
|
||||
#define kMPEmptyBoxDrawingHeight (CTFontGetDescent((CTFontRef)self.font) + CTFontGetAscent((CTFontRef)self.font))
|
||||
#define kMPEmptyBoxDrawingYOrigin (-(CTFontGetDescent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font)/2))
|
||||
@@ -60,6 +56,11 @@
|
||||
}
|
||||
|
||||
#pragma mark Cache Methods
|
||||
- (NSUInteger)numberOfChildren
|
||||
{
|
||||
return self.expression.countElements;
|
||||
}
|
||||
|
||||
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index
|
||||
{
|
||||
id cachedObject = [self cachableObjectForIndex:index generator:^id{
|
||||
@@ -216,6 +217,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)drawsChildrenManually
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)draw
|
||||
{
|
||||
// Get the current context
|
||||
|
||||
@@ -42,16 +42,19 @@
|
||||
|
||||
NSString *decimalSeparator = [NSRegularExpression escapedPatternForString:[[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator]];
|
||||
NSString *regexStringFormat = @"\\A(?:"
|
||||
@"(\\*)|"
|
||||
@"([\\*∙⋅])|"
|
||||
@"([+-](?:\\s*[+-])*)|"
|
||||
@"((?:\\d+(?:%@\\d+)?)|(?:%@\\d+))|"
|
||||
@"(sin)|"
|
||||
@"(cos)|"
|
||||
@"(tan)|"
|
||||
@"(asin)|"
|
||||
@"(acos)|"
|
||||
@"(atan)|"
|
||||
@"([A-Za-z])|"
|
||||
@"(!)|"
|
||||
@"(=)|"
|
||||
@"(\\s)"
|
||||
@"(\\s+)"
|
||||
@")";
|
||||
NSString *regexString = [NSString stringWithFormat:regexStringFormat, decimalSeparator, decimalSeparator];
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexString
|
||||
@@ -70,10 +73,13 @@
|
||||
NSRange sinRange = [match rangeAtIndex:4];
|
||||
NSRange cosRange = [match rangeAtIndex:5];
|
||||
NSRange tanRange = [match rangeAtIndex:6];
|
||||
NSRange variableRange = [match rangeAtIndex:7];
|
||||
NSRange factorialRange = [match rangeAtIndex:8];
|
||||
NSRange equalsRange = [match rangeAtIndex:9];
|
||||
NSRange whitespaceRange = [match rangeAtIndex:10];
|
||||
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];
|
||||
|
||||
if (MPRangeExists(multiplicationSymbolRange)) {
|
||||
range = multiplicationSymbolRange;
|
||||
@@ -93,6 +99,15 @@
|
||||
} else if (MPRangeExists(tanRange)) {
|
||||
range = tanRange;
|
||||
tokenType = MPTanToken;
|
||||
} else if (MPRangeExists(asinRange)) {
|
||||
range = asinRange;
|
||||
tokenType = MPASinToken;
|
||||
} else if (MPRangeExists(acosRange)) {
|
||||
range = acosRange;
|
||||
tokenType = MPACosToken;
|
||||
} else if (MPRangeExists(atanRange)) {
|
||||
range = atanRange;
|
||||
tokenType = MPATanToken;
|
||||
} else if (MPRangeExists(variableRange)) {
|
||||
range = variableRange;
|
||||
tokenType = MPVariableToken;
|
||||
|
||||
28
MathPad/MPExpressionTree.h
Normal file
28
MathPad/MPExpressionTree.h
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// MPExpressionTree.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPSummand.h"
|
||||
#import "MPExpressionTreeElement.h"
|
||||
|
||||
@interface MPExpressionTree : NSObject <MPExpressionTreeElement>
|
||||
|
||||
@property (nonatomic, copy) NSString *definedVariable;
|
||||
|
||||
- (NSArray *)summands;
|
||||
- (void)appendSummand:(MPSummand *)summand;
|
||||
- (void)insertSummand:(MPSummand *)summand
|
||||
atIndex:(NSUInteger)index;
|
||||
- (void)removeSummand:(MPSummand *)summand;
|
||||
- (void)removeSummandAtIndex:(NSUInteger)index;
|
||||
- (MPSummand *)summandAtIndex:(NSUInteger)index;
|
||||
|
||||
- (BOOL)validateExpectingVariableDefinition:(BOOL)flag
|
||||
error:(MPParseError *__autoreleasing *)error;
|
||||
|
||||
@end
|
||||
158
MathPad/MPExpressionTree.m
Normal file
158
MathPad/MPExpressionTree.m
Normal file
@@ -0,0 +1,158 @@
|
||||
//
|
||||
// MPExpressionTree.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPExpressionTree.h"
|
||||
|
||||
@implementation MPExpressionTree {
|
||||
NSRange _variableDefinitionRange;
|
||||
NSMutableArray *_summands;
|
||||
NSRange _range;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_summands = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream
|
||||
{
|
||||
self = [self init];
|
||||
if (self) {
|
||||
MPTokenStreamRecordCurrentLocation(tokenStream);
|
||||
|
||||
[tokenStream beginIgnoringWhitespaceTokens];
|
||||
|
||||
_variableDefinitionRange = NSMakeRange(NSNotFound, 0);
|
||||
self.definedVariable = nil;
|
||||
if (tokenStream.currentToken.tokenType == MPVariableToken) {
|
||||
if (tokenStream.peekNextToken.tokenType == MPEqualsToken) {
|
||||
self.definedVariable = tokenStream.currentToken.stringValue;
|
||||
NSRange variableRange = tokenStream.currentToken.range;
|
||||
[tokenStream currentTokenConsumed];
|
||||
NSRange equalsRange = tokenStream.currentToken.range;
|
||||
[tokenStream currentTokenConsumed];
|
||||
_variableDefinitionRange = NSUnionRange(variableRange, equalsRange);
|
||||
}
|
||||
}
|
||||
while ([tokenStream hasMoreTokens]) {
|
||||
[_summands addObject:[[MPSummand alloc] initWithTokenStream:tokenStream]];
|
||||
}
|
||||
|
||||
[tokenStream endIgnoringOrAcceptingWhitespaceTokens];
|
||||
_range = MPTokenStreamRecordedRange(tokenStream);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSRange)range
|
||||
{
|
||||
return _range;
|
||||
}
|
||||
|
||||
- (BOOL)validate:(MPParseError *__autoreleasing *)error
|
||||
{
|
||||
return [self validateExpectingVariableDefinition:NO
|
||||
error:error];
|
||||
}
|
||||
|
||||
- (BOOL)validateExpectingVariableDefinition:(BOOL)flag
|
||||
error:(MPParseError *__autoreleasing *)error
|
||||
{
|
||||
if (flag) {
|
||||
if (!self.definedVariable) {
|
||||
if (error) {
|
||||
*error = MPParseError(NSMakeRange(self.range.location, 0), @"Expected Variable Definition.");
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
} else {
|
||||
if (self.definedVariable) {
|
||||
if (error) {
|
||||
*error = MPParseError(_variableDefinitionRange, @"Unexpected Variable Definition");
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
if (_summands.count == 0) {
|
||||
if (error) {
|
||||
*error = MPParseError(self.range, @"Empty Expression.");
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
for (MPSummand *summand in _summands) {
|
||||
if (![summand validate:error]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)evaluate
|
||||
{
|
||||
NSDecimalNumber *value = [NSDecimalNumber zero];
|
||||
for (MPSummand *summand in _summands) {
|
||||
NSDecimalNumber *currentValue = [summand evaluate];
|
||||
if ([currentValue isEqualToNumber:[NSDecimalNumber notANumber]]) {
|
||||
value = currentValue;
|
||||
break;
|
||||
}
|
||||
value = [value decimalNumberByAdding:currentValue];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
- (NSArray *)expressionElements
|
||||
{
|
||||
NSMutableArray *elements = [[NSMutableArray alloc] init];
|
||||
if (self.definedVariable) {
|
||||
[elements addObject:[NSString stringWithFormat:@"%@=", self.definedVariable]];
|
||||
}
|
||||
for (MPSummand *summand in _summands) {
|
||||
[elements addObjectsFromArray:summand.expressionElements];
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
|
||||
- (NSArray *)summands
|
||||
{
|
||||
return _summands;
|
||||
}
|
||||
|
||||
- (void)appendSummand:(MPSummand *)summand
|
||||
{
|
||||
[_summands addObject:summand];
|
||||
}
|
||||
|
||||
- (void)insertSummand:(MPSummand *)summand
|
||||
atIndex:(NSUInteger)index
|
||||
{
|
||||
[_summands insertObject:summand
|
||||
atIndex:index];
|
||||
}
|
||||
|
||||
- (void)removeSummand:(MPSummand *)summand
|
||||
{
|
||||
[_summands removeObject:summand];
|
||||
}
|
||||
|
||||
- (void)removeSummandAtIndex:(NSUInteger)index
|
||||
{
|
||||
[_summands removeObjectAtIndex:index];
|
||||
}
|
||||
|
||||
- (MPSummand *)summandAtIndex:(NSUInteger)index
|
||||
{
|
||||
return [_summands objectAtIndex:index];
|
||||
}
|
||||
|
||||
@end
|
||||
30
MathPad/MPExpressionTreeElement.h
Normal file
30
MathPad/MPExpressionTreeElement.h
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// MPExpressionTreeElement.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPTokenStream.h"
|
||||
#import "MPParseError.h"
|
||||
#import "MPTokenStream.h"
|
||||
|
||||
#define MPTokenStreamRecordCurrentLocation(tokenStream) NSUInteger __tokenStreamStart = tokenStream.currentToken.range.location
|
||||
#define MPTokenStreamRecordedRange(tokenStream) NSMakeRange(__tokenStreamStart, tokenStream.currentToken.range.location - __tokenStreamStart)
|
||||
|
||||
@protocol MPExpressionTreeElement <NSObject>
|
||||
@required
|
||||
|
||||
- (instancetype)init;
|
||||
- (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream;
|
||||
|
||||
- (NSRange)range;
|
||||
|
||||
- (BOOL)validate:(MPParseError *__autoreleasing *)error;
|
||||
- (NSDecimalNumber *)evaluate;
|
||||
|
||||
- (NSArray *)expressionElements; // Converts
|
||||
|
||||
@end
|
||||
@@ -582,6 +582,9 @@
|
||||
[allowedCharacters addCharactersInString:[NSString stringWithFormat:@"+-*= !%@", decimalSeparator]];
|
||||
|
||||
if (characters.length == 1 && [characters stringByTrimmingCharactersInSet:allowedCharacters].length == 0) {
|
||||
if ([characters isEqualToString:@"*"]) {
|
||||
characters = @"⋅";
|
||||
}
|
||||
[self.expressionStorage replaceItemsInRangePath:self.selection
|
||||
referenceFrame:MPSymbolReferenceFrame
|
||||
withElements:@[characters]];
|
||||
|
||||
22
MathPad/MPFactor.h
Normal file
22
MathPad/MPFactor.h
Normal file
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// MPFactor.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPExpressionTreeElement.h"
|
||||
#import "MPValueGroup.h"
|
||||
#import "MPOperatorChain.h"
|
||||
|
||||
@interface MPFactor : NSObject <MPExpressionTreeElement>
|
||||
|
||||
@property (nonatomic) BOOL hasMultiplicationSymbol;
|
||||
@property (nonatomic, strong) MPOperatorChain *operatorChain;
|
||||
@property (nonatomic, strong) id<MPValue> value;
|
||||
|
||||
- (NSRange)multiplicationSymbolRange;
|
||||
|
||||
@end
|
||||
95
MathPad/MPFactor.m
Normal file
95
MathPad/MPFactor.m
Normal file
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// MPFactor.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFactor.h"
|
||||
#import "MPMathRules.h"
|
||||
|
||||
@implementation MPFactor {
|
||||
NSRange _multiplicationSymbolRange;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream
|
||||
{
|
||||
self = [self init];
|
||||
if (self) {
|
||||
[tokenStream beginIgnoringWhitespaceTokens];
|
||||
MPToken *token = tokenStream.currentToken;
|
||||
if (token.tokenType == MPMultiplicationSymbolToken) {
|
||||
_multiplicationSymbolRange = token.range;
|
||||
[tokenStream currentTokenConsumed];
|
||||
} else {
|
||||
_multiplicationSymbolRange = NSMakeRange(NSNotFound, 0);
|
||||
}
|
||||
if (tokenStream.currentToken.tokenType == MPOperatorListToken) {
|
||||
_operatorChain = [[MPOperatorChain alloc] initWithTokenStream:tokenStream];
|
||||
}
|
||||
MPValueGroup *valueGroup = [[MPValueGroup alloc] initWithTokenStream:tokenStream];
|
||||
_value = [tokenStream consumeSuffixesForValue:valueGroup];
|
||||
[tokenStream endIgnoringOrAcceptingWhitespaceTokens];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSRange)range
|
||||
{
|
||||
if (_multiplicationSymbolRange.location == NSNotFound) {
|
||||
return self.value.range;
|
||||
}
|
||||
return NSUnionRange(_multiplicationSymbolRange, self.value.range);
|
||||
}
|
||||
|
||||
- (NSRange)multiplicationSymbolRange
|
||||
{
|
||||
return _multiplicationSymbolRange;
|
||||
}
|
||||
|
||||
- (BOOL)hasMultiplicationSymbol
|
||||
{
|
||||
return _multiplicationSymbolRange.location != NSNotFound;
|
||||
}
|
||||
|
||||
- (BOOL)validate:(MPParseError *__autoreleasing *)error
|
||||
{
|
||||
if (self.operatorChain.numberOfOperators > [[MPMathRules sharedRules] maximumOperatorChainLengthInMultiplication]) {
|
||||
if (error) {
|
||||
*error = MPParseError(self.operatorChain.range, @"Too many operators in Multiplication");
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
return [self.value validate:error];
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)evaluate
|
||||
{
|
||||
NSDecimalNumber *value = [self.value evaluate];
|
||||
if ([value isNotEqualTo:[NSDecimalNumber notANumber]] && self.operatorChain) {
|
||||
value = [value decimalNumberByMultiplyingBy:[self.operatorChain evaluate]];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
- (NSArray *)expressionElements
|
||||
{
|
||||
NSMutableArray *elements = [[NSMutableArray alloc] init];
|
||||
if (self.hasMultiplicationSymbol) {
|
||||
[elements addObject:@"*"];
|
||||
}
|
||||
[elements addObjectsFromArray:self.value.expressionElements];
|
||||
return elements;
|
||||
}
|
||||
|
||||
@end
|
||||
16
MathPad/MPFactorial.h
Normal file
16
MathPad/MPFactorial.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// MPFactorial.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 11.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "MPValueGroup.h"
|
||||
|
||||
@interface MPFactorial : NSObject <MPValue>
|
||||
|
||||
@property (nonatomic, strong) id<MPValue> value;
|
||||
|
||||
@end
|
||||
58
MathPad/MPFactorial.m
Normal file
58
MathPad/MPFactorial.m
Normal file
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// MPFactorial.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 11.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFactorial.h"
|
||||
|
||||
@implementation MPFactorial
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream
|
||||
{
|
||||
self = [self init];
|
||||
if (self) {
|
||||
[tokenStream beginIgnoringWhitespaceTokens];
|
||||
if (tokenStream.currentToken.tokenType != MPFactorialToken) {
|
||||
[tokenStream endIgnoringOrAcceptingWhitespaceTokens];
|
||||
return nil;
|
||||
} else {
|
||||
[tokenStream currentTokenConsumed];
|
||||
}
|
||||
[tokenStream endIgnoringOrAcceptingWhitespaceTokens];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSRange)range
|
||||
{
|
||||
return NSMakeRange(0, 0);
|
||||
}
|
||||
|
||||
- (BOOL)validate:(MPParseError *__autoreleasing *)error
|
||||
{
|
||||
return [self.value validate:error];
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)evaluate
|
||||
{
|
||||
double value = [self.value evaluate].doubleValue;
|
||||
return [[NSDecimalNumber alloc] initWithDouble:tgamma(value+1)];
|
||||
}
|
||||
|
||||
- (NSArray *)expressionElements
|
||||
{
|
||||
return @[@"!"];
|
||||
}
|
||||
|
||||
@end
|
||||
16
MathPad/MPFractionFunction.h
Normal file
16
MathPad/MPFractionFunction.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// MPFractionFunction.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 07.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <MathKit/MathKit.h>
|
||||
|
||||
@interface MPFractionFunction : MPFunction
|
||||
|
||||
@property (nonatomic, strong) MPExpression *nominatorExpression;
|
||||
@property (nonatomic, strong) MPExpression *denominatorExpression;
|
||||
|
||||
@end
|
||||
36
MathPad/MPFractionFunction.m
Normal file
36
MathPad/MPFractionFunction.m
Normal file
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// MPFractionFunction.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 07.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFractionFunction.h"
|
||||
|
||||
@implementation MPFractionFunction
|
||||
|
||||
MPFunctionAccessorImplementation(NominatorExpression, _nominatorExpression)
|
||||
MPFunctionAccessorImplementation(DenominatorExpression, _denominatorExpression)
|
||||
|
||||
- (NSArray *)childrenAccessors
|
||||
{
|
||||
return @[@"nominatorExpression", @"denominatorExpression"];
|
||||
}
|
||||
|
||||
- (BOOL)validate:(MPParseError *__autoreleasing *)error
|
||||
{
|
||||
return [[self.nominatorExpression parse] validate:error] && [[self.denominatorExpression parse] validate:error];
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)evaluate
|
||||
{
|
||||
return [[[self.nominatorExpression parse] evaluate] decimalNumberByDividingBy:[[self.denominatorExpression parse] evaluate]];
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@ / %@", self.nominatorExpression, self.denominatorExpression];
|
||||
}
|
||||
|
||||
@end
|
||||
16
MathPad/MPFractionFunctionLayout.h
Normal file
16
MathPad/MPFractionFunctionLayout.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// MPFractionFunctionLayout.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 07.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFunctionLayout.h"
|
||||
#import "MPFractionFunction.h"
|
||||
|
||||
@interface MPFractionFunctionLayout : MPFunctionLayout
|
||||
|
||||
- (MPFractionFunction *)fractionFunction;
|
||||
|
||||
@end
|
||||
87
MathPad/MPFractionFunctionLayout.m
Normal file
87
MathPad/MPFractionFunctionLayout.m
Normal file
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// MPFractionFunctionLayout.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 07.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFractionFunctionLayout.h"
|
||||
#import "MPExpressionLayout.h"
|
||||
|
||||
#define kFractionFunctionLineWidth 2
|
||||
#define kFractionFunctionHorizontalInset 2
|
||||
#define kFractionFunctionNominatorOffset 0
|
||||
#define kFractionFunctionDenominatorOffset 0
|
||||
|
||||
#define MPFractionMiddle (CTFontGetAscent((CTFontRef)self.font) / 2)
|
||||
|
||||
@implementation MPFractionFunctionLayout
|
||||
|
||||
- (MPFractionFunction *)fractionFunction
|
||||
{
|
||||
return (MPFractionFunction *)self.function;
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfChildBelowChildAtIndex:(NSUInteger)index
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfChildAboveChildAtIndex:(NSUInteger)index
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSIndexSet *)indexesOfRemainingChildren
|
||||
{
|
||||
return [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 1)];
|
||||
}
|
||||
|
||||
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index
|
||||
{
|
||||
if (index == 0) {
|
||||
CGFloat y = MPFractionMiddle + kFractionFunctionLineWidth / 2;
|
||||
return NSMakePoint(0, y);
|
||||
} else {
|
||||
NSRect denominatorBounds = [self childLayoutAtIndex:1].bounds;
|
||||
CGFloat y = CTFontGetAscent((CTFontRef)self.font) / 2 - kFractionFunctionLineWidth / 2;
|
||||
y -= denominatorBounds.size.height - denominatorBounds.origin.y;
|
||||
return NSMakePoint(0, y);
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)childAtIndexUsesSmallSize:(NSUInteger)index
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPathForLocalMousePoint:(NSPoint)point
|
||||
{
|
||||
if (point.x < self.bounds.size.width / 2) {
|
||||
return [NSIndexPath indexPathWithIndex:0];
|
||||
} else {
|
||||
return [NSIndexPath indexPathWithIndex:1];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSRect)generateBounds
|
||||
{
|
||||
NSRect nominatorBounds = [self childLayoutAtIndex:0].bounds;
|
||||
NSRect denominatorBounds = [self childLayoutAtIndex:1].bounds;
|
||||
NSRect bounds;
|
||||
bounds.origin.x = 0;
|
||||
CGFloat y = CTFontGetAscent((CTFontRef)self.font) / 2 - kFractionFunctionLineWidth / 2;
|
||||
y -= denominatorBounds.size.height;
|
||||
bounds.size.width = MAX(nominatorBounds.size.width, denominatorBounds.size.width);
|
||||
bounds.size.height = nominatorBounds.size.height + denominatorBounds.size.height + kFractionFunctionLineWidth;
|
||||
return bounds;
|
||||
}
|
||||
|
||||
- (void)draw
|
||||
{
|
||||
CGFloat y = CTFontGetAscent((CTFontRef)self.font) / 2 - kFractionFunctionLineWidth / 2;
|
||||
NSRectFill(NSMakeRect(0, y, self.bounds.size.width, kFractionFunctionLineWidth));
|
||||
}
|
||||
|
||||
@end
|
||||
15
MathPad/MPFunction+MPValue.h
Normal file
15
MathPad/MPFunction+MPValue.h
Normal file
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// MPFunction+MPValue.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <MathKit/MathKit.h>
|
||||
#import "MPValueGroup.h"
|
||||
#import "MPFunction+MPToken.h"
|
||||
|
||||
@interface MPFunction (MPValue) <MPValue>
|
||||
|
||||
@end
|
||||
28
MathPad/MPFunction+MPValue.m
Normal file
28
MathPad/MPFunction+MPValue.m
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// MPFunction+MPValue.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFunction+MPValue.h"
|
||||
|
||||
@implementation MPFunction (MPValue)
|
||||
|
||||
- (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream
|
||||
{
|
||||
[tokenStream beginIgnoringWhitespaceTokens];
|
||||
if (tokenStream.currentToken.tokenType != MPGenericFunctionToken) {
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Expected Function" userInfo:nil];
|
||||
}
|
||||
[tokenStream endIgnoringOrAcceptingWhitespaceTokens];
|
||||
return [self init];
|
||||
}
|
||||
|
||||
- (NSArray *)expressionElements
|
||||
{
|
||||
return @[self];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -9,7 +9,6 @@
|
||||
@import Foundation;
|
||||
#import "MPExpressionElement.h"
|
||||
#import "MPParseError.h"
|
||||
#import "MPTerm.h"
|
||||
|
||||
#define MPFunctionAccessorImplementation(Accessor, variableName) \
|
||||
- (void)set##Accessor:(MPExpression *)value \
|
||||
@@ -50,7 +49,8 @@
|
||||
|
||||
#pragma mark Evaluating Functions
|
||||
|
||||
- (MPTerm *)parseWithError:(MPParseError *__autoreleasing *)error; // Override
|
||||
- (BOOL)validate:(MPParseError *__autoreleasing *)error; // Override
|
||||
- (NSDecimalNumber *)evaluate; // Override
|
||||
|
||||
#pragma mark Messages
|
||||
- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#import "MPParenthesisFunction.h"
|
||||
#import "MPParenthesisFunctionLayout.h"
|
||||
#import "MPPowerFunctionLayout.h"
|
||||
#import "MPFractionFunctionLayout.h"
|
||||
#import "MPFractionFunction.h"
|
||||
|
||||
#import "NSIndexPath+MPAdditions.h"
|
||||
|
||||
@@ -31,6 +33,8 @@
|
||||
return [[MPParenthesisFunctionLayout alloc] initWithFunction:function parent:parent];
|
||||
} else if (class == [MPPowerFunction class]) {
|
||||
return [[MPPowerFunctionLayout alloc] initWithFunction:function parent:parent];
|
||||
} else if (class == [MPFractionFunction class]) {
|
||||
return [[MPFractionFunctionLayout alloc] initWithFunction:function parent:parent];
|
||||
}
|
||||
return [[self alloc] initWithFunction:function
|
||||
parent:parent];
|
||||
@@ -67,6 +71,11 @@
|
||||
return [self cachableObjectForIndex:actualIndex generator:generator];
|
||||
}
|
||||
|
||||
- (NSUInteger)numberOfChildren
|
||||
{
|
||||
return self.function.numberOfChildren;
|
||||
}
|
||||
|
||||
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index
|
||||
{
|
||||
return [self cachableObjectForIndex:index generator:^id{
|
||||
|
||||
19
MathPad/MPFunctionValue.h
Normal file
19
MathPad/MPFunctionValue.h
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// MPFunctionValue.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 11.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "MPValueGroup.h"
|
||||
|
||||
@interface MPFunctionValue : NSObject <MPValue>
|
||||
|
||||
- (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream; // May return an MPFunctionValue or a powered function
|
||||
|
||||
@property (nonatomic, copy) NSString *functionName;
|
||||
@property (nonatomic, strong) MPValueGroup *valueGroup;
|
||||
|
||||
@end
|
||||
151
MathPad/MPFunctionValue.m
Normal file
151
MathPad/MPFunctionValue.m
Normal file
@@ -0,0 +1,151 @@
|
||||
//
|
||||
// MPFunctionValue.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 11.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFunctionValue.h"
|
||||
#import "MPMathRules.h"
|
||||
|
||||
double defaultFunction(double value) {return value;};
|
||||
|
||||
@implementation MPFunctionValue {
|
||||
NSDecimalNumber *(^_function)(NSDecimalNumber *);
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream
|
||||
{
|
||||
switch (tokenStream.currentToken.tokenType) {
|
||||
case MPSinToken:
|
||||
[tokenStream currentTokenConsumed];
|
||||
self.functionName = @"sin";
|
||||
return [self initWithTokenStream:tokenStream
|
||||
function:&sin
|
||||
takesArcValue:YES
|
||||
returnsArcValue:NO];
|
||||
|
||||
case MPCosToken:
|
||||
[tokenStream currentTokenConsumed];
|
||||
self.functionName = @"cos";
|
||||
return [self initWithTokenStream:tokenStream
|
||||
function:&cos
|
||||
takesArcValue:YES
|
||||
returnsArcValue:NO];
|
||||
|
||||
case MPTanToken:
|
||||
[tokenStream currentTokenConsumed];
|
||||
self.functionName = @"tan";
|
||||
return [self initWithTokenStream:tokenStream
|
||||
function:&tan
|
||||
takesArcValue:YES
|
||||
returnsArcValue:NO];
|
||||
|
||||
case MPASinToken:
|
||||
[tokenStream currentTokenConsumed];
|
||||
self.functionName = @"asin";
|
||||
return [self initWithTokenStream:tokenStream
|
||||
function:&asin
|
||||
takesArcValue:YES
|
||||
returnsArcValue:NO];
|
||||
|
||||
case MPACosToken:
|
||||
[tokenStream currentTokenConsumed];
|
||||
self.functionName = @"acos";
|
||||
return [self initWithTokenStream:tokenStream
|
||||
function:&acos
|
||||
takesArcValue:YES
|
||||
returnsArcValue:NO];
|
||||
|
||||
case MPATanToken:
|
||||
[tokenStream currentTokenConsumed];
|
||||
self.functionName = @"atan";
|
||||
return [self initWithTokenStream:tokenStream
|
||||
function:&atan
|
||||
takesArcValue:YES
|
||||
returnsArcValue:NO];
|
||||
|
||||
default:
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Expected Function" userInfo:nil];
|
||||
break;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream
|
||||
function:(double (*)(double))function
|
||||
takesArcValue:(BOOL)takesArc
|
||||
returnsArcValue:(BOOL)returnsArc
|
||||
{
|
||||
self = [self init];
|
||||
if (self) {
|
||||
_valueGroup = [[MPValueGroup alloc] initWithTokenStream:tokenStream];
|
||||
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;
|
||||
};
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (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;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSRange)range
|
||||
{
|
||||
return NSMakeRange(0, 0);
|
||||
}
|
||||
|
||||
- (BOOL)validate:(MPParseError *__autoreleasing *)error
|
||||
{
|
||||
return [self.valueGroup validate:error];
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)evaluate
|
||||
{
|
||||
return _function([self.valueGroup evaluate]);
|
||||
}
|
||||
|
||||
- (NSArray *)expressionElements
|
||||
{
|
||||
NSMutableArray *elements = [[NSMutableArray alloc] init];
|
||||
[elements addObject:self.functionName];
|
||||
[elements addObjectsFromArray:self.valueGroup.expressionElements];
|
||||
return elements;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -13,6 +13,7 @@
|
||||
#import "MPSumFunction.h"
|
||||
#import "MPParenthesisFunction.h"
|
||||
#import "MPPowerFunction.h"
|
||||
#import "MPFractionFunction.h"
|
||||
|
||||
|
||||
@class MPFunctionTemplateItem;
|
||||
@@ -205,6 +206,7 @@ static void *MPFunctionTemplateViewMouseOverContext = @"MPFunctionTemplateViewMo
|
||||
squareFunction.exponentExpression = [[MPExpression alloc] initWithElement:@"2"];
|
||||
MPPowerFunction *cubicFunction = [[MPPowerFunction alloc] init];
|
||||
cubicFunction.exponentExpression = [[MPExpression alloc] initWithElement:@"3"];
|
||||
MPFractionFunction *fractionFunction = [[MPFractionFunction alloc] init];
|
||||
self.functionPrototypes = @[
|
||||
@{@"function": sumFunction,
|
||||
@"name": NSLocalizedString(@"Sum", @"Sum Function Name")},
|
||||
@@ -215,7 +217,9 @@ static void *MPFunctionTemplateViewMouseOverContext = @"MPFunctionTemplateViewMo
|
||||
@{@"function": cubicFunction,
|
||||
@"name": NSLocalizedString(@"Cubic", @"Cubic Function Name")},
|
||||
@{@"function": powerFunction,
|
||||
@"name": NSLocalizedString(@"Power", @"Power Function Name")}
|
||||
@"name": NSLocalizedString(@"Power", @"Power Function Name")},
|
||||
@{@"function": fractionFunction,
|
||||
@"name": NSLocalizedString(@"Fraction", @"Fraction Function Name")}
|
||||
];
|
||||
[self.collectionView addObserver:self
|
||||
forKeyPath:@"hoverItem"
|
||||
|
||||
@@ -55,6 +55,8 @@
|
||||
@end
|
||||
|
||||
@interface MPLayout (MPSubclassImplement)
|
||||
- (NSUInteger)numberOfChildren;
|
||||
- (BOOL)drawsChildrenManually;
|
||||
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index; // To be implemented
|
||||
- (NSRect)generateBounds; // To be implemented
|
||||
- (NSRect)boundingRectForRange:(NSRange)range; // To be implemented, use rangePath instead, this one has wrong origin
|
||||
|
||||
@@ -174,6 +174,11 @@
|
||||
return bounds;
|
||||
}
|
||||
|
||||
- (BOOL)drawsChildrenManually
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)drawAtPoint:(NSPoint)point
|
||||
{
|
||||
NSAffineTransform *transform = [NSAffineTransform transform];
|
||||
@@ -181,6 +186,13 @@
|
||||
yBy:point.y];
|
||||
[transform concat];
|
||||
[self draw];
|
||||
if (!self.drawsChildrenManually) {
|
||||
for (NSUInteger index = 0; index < [self numberOfChildren]; index++) {
|
||||
MPLayout *childLayout = [self childLayoutAtIndex:index];
|
||||
NSPoint offset = [self offsetOfChildLayoutAtIndex:index];
|
||||
[childLayout drawAtPoint:offset];
|
||||
}
|
||||
}
|
||||
[transform invert];
|
||||
[transform concat];
|
||||
}
|
||||
|
||||
@@ -8,10 +8,8 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
FOUNDATION_EXPORT NSString *MPMathRulesAllowsImplicitMultiplicationKey;
|
||||
FOUNDATION_EXPORT NSString *MPMathRulesMaximumOperatorChainLengthKey;
|
||||
FOUNDATION_EXPORT NSString *MPMathRulesMaximumOperatorChainLengthInMultiplicationKey;
|
||||
FOUNDATION_EXPORT NSString *MPMathRulesMaximumOperatorChainLengthInFunctionKey;
|
||||
FOUNDATION_EXPORT NSString *MPMathRulesIsUsingDegreesKey;
|
||||
|
||||
@interface MPMathRules : NSObject
|
||||
@@ -20,11 +18,9 @@ FOUNDATION_EXPORT NSString *MPMathRulesIsUsingDegreesKey;
|
||||
|
||||
@property (nonatomic, getter = isUsingUserDefaultValues) BOOL usingUserDefaultValues;
|
||||
|
||||
@property (nonatomic) BOOL allowsImplicitMultiplication; // wether 2 3 is equal to 6 or error
|
||||
// Default value uses the key "..." in NSUserDefaults or ... if the key does not exist.
|
||||
@property (nonatomic) NSUInteger maximumOperatorChainLength; // +--++-5 -> Chain length: 6, 2 default (sign of number and operator) (0 is invalid?)
|
||||
@property (nonatomic) NSUInteger maximumOperatorChainLengthInMultiplication; // Default: 1, 0 means actually 0
|
||||
@property (nonatomic) NSUInteger maximumOperatorChainLengthInFunction; // For sin, cos, tan. Default: 1
|
||||
|
||||
@property (nonatomic) BOOL isUsingDegrees;
|
||||
|
||||
|
||||
@@ -8,10 +8,8 @@
|
||||
|
||||
#import "MPMathRules.h"
|
||||
|
||||
NSString *MPMathRulesAllowsImplicitMultiplicationKey = @"MPMathRulesAllowsImplicitMultiplicationKey";
|
||||
NSString *MPMathRulesMaximumOperatorChainLengthKey = @"MPMathRulesMaximumOperatorChainLengthKey";
|
||||
NSString *MPMathRulesMaximumOperatorChainLengthInMultiplicationKey = @"MPMathRulesMaximumOperatorChainLengthInMultiplicationKey";
|
||||
NSString *MPMathRulesMaximumOperatorChainLengthInFunctionKey = @"MPMathRulesMaximumOperatorChainLengthInFunctionKey";
|
||||
NSString *MPMathRulesIsUsingDegreesKey = @"MPMathRulesIsUsingDegreesKey";
|
||||
|
||||
@implementation MPMathRules
|
||||
@@ -35,16 +33,12 @@ static MPMathRules *sharedRules;
|
||||
if (self) {
|
||||
_usingUserDefaultValues = YES;
|
||||
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
|
||||
NSNumber *userDefaultsAllowImplicitMultiplication = [userDefaults objectForKey:MPMathRulesAllowsImplicitMultiplicationKey];
|
||||
NSNumber *userDefaultsMaximumOperatorChainLength = [userDefaults objectForKey:MPMathRulesMaximumOperatorChainLengthKey];
|
||||
NSNumber *userDefaultsMaximumOperatorChainLengthInMultiplication = [userDefaults objectForKey:MPMathRulesMaximumOperatorChainLengthInMultiplicationKey];
|
||||
NSNumber *userDefaultsMaximumOperatorChainLengthInFunction = [userDefaults objectForKey:MPMathRulesMaximumOperatorChainLengthInFunctionKey];
|
||||
NSNumber *userDefaultsIsUsingDegrees = [userDefaults objectForKey:MPMathRulesIsUsingDegreesKey];
|
||||
|
||||
_allowsImplicitMultiplication = userDefaultsAllowImplicitMultiplication.boolValue;
|
||||
_maximumOperatorChainLength = userDefaultsMaximumOperatorChainLength != nil ? userDefaultsMaximumOperatorChainLength.unsignedIntegerValue : 2;
|
||||
_maximumOperatorChainLengthInMultiplication = userDefaultsMaximumOperatorChainLengthInMultiplication != nil ? userDefaultsMaximumOperatorChainLengthInMultiplication.unsignedIntegerValue : 1;
|
||||
_maximumOperatorChainLengthInFunction = userDefaultsMaximumOperatorChainLengthInFunction != nil ? userDefaultsMaximumOperatorChainLengthInFunction.unsignedIntegerValue : 1;
|
||||
_isUsingDegrees = userDefaultsIsUsingDegrees.boolValue;
|
||||
}
|
||||
return self;
|
||||
@@ -54,22 +48,11 @@ static MPMathRules *sharedRules;
|
||||
{
|
||||
_usingUserDefaultValues = usingUserDefaultValues;
|
||||
// Save the current values
|
||||
self.allowsImplicitMultiplication = self.allowsImplicitMultiplication;
|
||||
self.maximumOperatorChainLength = self.maximumOperatorChainLength;
|
||||
self.maximumOperatorChainLengthInMultiplication = self.maximumOperatorChainLengthInMultiplication;
|
||||
self.maximumOperatorChainLengthInFunction = self.maximumOperatorChainLengthInFunction;
|
||||
self.isUsingDegrees = self.isUsingDegrees;
|
||||
}
|
||||
|
||||
- (void)setAllowsImplicitMultiplication:(BOOL)allowsImplicitMultiplication
|
||||
{
|
||||
_allowsImplicitMultiplication = allowsImplicitMultiplication;
|
||||
if (self.isUsingUserDefaultValues) {
|
||||
[[NSUserDefaults standardUserDefaults] setBool:allowsImplicitMultiplication
|
||||
forKey:MPMathRulesAllowsImplicitMultiplicationKey];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setMaximumOperatorChainLength:(NSUInteger)maximumOperatorChainLength
|
||||
{
|
||||
_maximumOperatorChainLength = maximumOperatorChainLength;
|
||||
@@ -88,15 +71,6 @@ static MPMathRules *sharedRules;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setMaximumOperatorChainLengthInFunction:(NSUInteger)maximumOperatorChainLengthInFunction
|
||||
{
|
||||
_maximumOperatorChainLengthInFunction = maximumOperatorChainLengthInFunction;
|
||||
if (self.isUsingUserDefaultValues) {
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@(maximumOperatorChainLengthInFunction)
|
||||
forKey:MPMathRulesMaximumOperatorChainLengthInFunctionKey];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setIsUsingDegrees:(BOOL)isUsingDegrees
|
||||
{
|
||||
_isUsingDegrees = isUsingDegrees;
|
||||
|
||||
16
MathPad/MPNumber.h
Normal file
16
MathPad/MPNumber.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// MPNumber.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPValueGroup.h"
|
||||
|
||||
@interface MPNumber : NSObject <MPValue>
|
||||
|
||||
@property (nonatomic, strong) NSDecimalNumber *number;
|
||||
|
||||
@end
|
||||
61
MathPad/MPNumber.m
Normal file
61
MathPad/MPNumber.m
Normal file
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// MPNumber.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPNumber.h"
|
||||
|
||||
@implementation MPNumber {
|
||||
NSRange _range;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream
|
||||
{
|
||||
self = [self init];
|
||||
if (self) {
|
||||
[tokenStream beginIgnoringWhitespaceTokens];
|
||||
MPToken *token = tokenStream.currentToken;
|
||||
if (token.tokenType == MPNumberToken) {
|
||||
self.number = [NSDecimalNumber decimalNumberWithString:token.stringValue locale:[NSLocale currentLocale]];
|
||||
_range = token.range;
|
||||
} else {
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Expected Number" userInfo:nil];
|
||||
}
|
||||
[tokenStream currentTokenConsumed];
|
||||
[tokenStream endIgnoringOrAcceptingWhitespaceTokens];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSRange)range
|
||||
{
|
||||
return _range;
|
||||
}
|
||||
|
||||
- (BOOL)validate:(MPParseError *__autoreleasing *)error
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)evaluate
|
||||
{
|
||||
return self.number;
|
||||
}
|
||||
|
||||
- (NSArray *)expressionElements
|
||||
{
|
||||
return @[[self.number descriptionWithLocale:[NSLocale currentLocale]]];
|
||||
}
|
||||
|
||||
@end
|
||||
17
MathPad/MPOperatorChain.h
Normal file
17
MathPad/MPOperatorChain.h
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// MPOperatorChain.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 11.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "MPExpressionTreeElement.h"
|
||||
|
||||
@interface MPOperatorChain : NSObject <MPExpressionTreeElement>
|
||||
|
||||
@property (nonatomic) BOOL negating;
|
||||
@property (nonatomic) NSUInteger numberOfOperators;
|
||||
|
||||
@end
|
||||
75
MathPad/MPOperatorChain.m
Normal file
75
MathPad/MPOperatorChain.m
Normal file
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// MPOperatorChain.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 11.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPOperatorChain.h"
|
||||
|
||||
@implementation MPOperatorChain {
|
||||
NSRange _range;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream
|
||||
{
|
||||
self = [self init];
|
||||
if (self) {
|
||||
[tokenStream beginIgnoringWhitespaceTokens];
|
||||
MPToken *currentToken = tokenStream.currentToken;
|
||||
if (currentToken.tokenType != MPOperatorListToken) {
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Expected Operators" userInfo:nil];
|
||||
}
|
||||
NSString *operatorString = currentToken.stringValue;
|
||||
_range = currentToken.range;
|
||||
operatorString = [[operatorString componentsSeparatedByString:@" "] componentsJoinedByString:@""];
|
||||
_negating = NO;
|
||||
_numberOfOperators = operatorString.length;
|
||||
for (NSUInteger index = 0; index < _numberOfOperators; index++) {
|
||||
if ([[operatorString substringWithRange:NSMakeRange(index, 1)] isEqualToString:@"-"]) {
|
||||
_negating = !_negating;
|
||||
}
|
||||
}
|
||||
[tokenStream currentTokenConsumed];
|
||||
[tokenStream endIgnoringOrAcceptingWhitespaceTokens];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSRange)range
|
||||
{
|
||||
return _range;
|
||||
}
|
||||
|
||||
- (BOOL)validate:(MPParseError *__autoreleasing *)error
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)evaluate
|
||||
{
|
||||
return self.negating ? [[NSDecimalNumber alloc] initWithInteger:-1] : [NSDecimalNumber one];
|
||||
}
|
||||
|
||||
- (NSArray *)expressionElements
|
||||
{
|
||||
NSMutableString *text = [[NSMutableString alloc] init];
|
||||
for (NSUInteger index = 1; index < self.numberOfOperators; index++) {
|
||||
[text appendString:@"+"];
|
||||
}
|
||||
if (self.negating) {
|
||||
[text appendString:@"-"];
|
||||
}
|
||||
return @[text.copy];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -8,7 +8,6 @@
|
||||
//
|
||||
|
||||
#import "MPParenthesisFunction.h"
|
||||
#import "MPExpressionEvaluator.h"
|
||||
|
||||
@implementation MPParenthesisFunction
|
||||
|
||||
@@ -19,11 +18,14 @@ MPFunctionAccessorImplementation(Expression, _expression)
|
||||
return @[@"expression"];
|
||||
}
|
||||
|
||||
- (MPTerm *)parseWithError:(MPParseError *__autoreleasing *)error
|
||||
- (BOOL)validate:(MPParseError *__autoreleasing *)error
|
||||
{
|
||||
MPExpressionEvaluator *evaluator = [[MPExpressionEvaluator alloc] initWithExpression:self.expression];
|
||||
return [evaluator parseExpectingVariable:NO
|
||||
error:error];
|
||||
return [[self.expression parse] validate:error];
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)evaluate
|
||||
{
|
||||
return [[self.expression parse] evaluate];
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
|
||||
@@ -154,6 +154,12 @@
|
||||
return bounds;
|
||||
}
|
||||
|
||||
#warning Let the children be drawn automatically
|
||||
- (BOOL)drawsChildrenManually
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)draw
|
||||
{
|
||||
NSBezierPath *openingParens = self.transformedOpeningParens;
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
//
|
||||
|
||||
#import <MathKit/MathKit.h>
|
||||
#import "MPSuffixFunction.h"
|
||||
|
||||
@interface MPPowerFunction : MPFunction
|
||||
@interface MPPowerFunction : MPSuffixFunction
|
||||
|
||||
@property (nonatomic, strong) MPTerm *baseTerm;
|
||||
@property (nonatomic, strong) MPExpression *exponentExpression;
|
||||
|
||||
@end
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
#import "MPPowerFunction.h"
|
||||
#import "MPExpressionEvaluator.h"
|
||||
#import "MPExpression.h"
|
||||
|
||||
@implementation MPPowerFunction
|
||||
|
||||
@@ -18,19 +18,24 @@ MPFunctionAccessorImplementation(ExponentExpression, _exponentExpression)
|
||||
return @[@"exponentExpression"];
|
||||
}
|
||||
|
||||
- (MPTerm *)parseWithError:(MPParseError *__autoreleasing *)error
|
||||
- (BOOL)validate:(MPParseError *__autoreleasing *)error
|
||||
{
|
||||
MPExpressionEvaluator *exponent = [[MPExpressionEvaluator alloc] initWithExpression:self.exponentExpression];
|
||||
MPTerm *exponentTerm = [exponent parseExpectingVariable:NO
|
||||
error:error];
|
||||
if (exponentTerm == nil) {
|
||||
return nil;
|
||||
if (!self.baseValue) {
|
||||
if (error) {
|
||||
*error = MPParseError(NSMakeRange([self.parent convertIndex:[self.parent indexOfElement:self]
|
||||
fromReferenceFrame:MPElementReferenceFrame
|
||||
toReferenceFrame:MPSymbolReferenceFrame], 0), @"No Base for Power");
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [[MPTerm alloc] initWithBlock:^NSDecimalNumber *{
|
||||
double power = pow([self.baseTerm evaluate].doubleValue, [exponentTerm evaluate].doubleValue);
|
||||
return [[NSDecimalNumber alloc] initWithDouble:power];
|
||||
}];
|
||||
return [self.baseValue validate:error] && [[self.exponentExpression parse] validate:error];
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)evaluate
|
||||
{
|
||||
NSDecimalNumber *base = [self.baseValue evaluate];
|
||||
NSDecimalNumber *exponent = [[self.exponentExpression parse] evaluate];
|
||||
return [[NSDecimalNumber alloc] initWithDouble:pow(base.doubleValue, exponent.doubleValue)];
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
//
|
||||
|
||||
#import "MPPowerFunctionLayout.h"
|
||||
|
||||
#define kMPEmptyBoxHeight (CTFontGetDescent((CTFontRef)self.font) + CTFontGetAscent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font))
|
||||
#define kMPEmptyBoxYOrigin (-(CTFontGetDescent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font)))
|
||||
#import "MPExpressionLayout.h"
|
||||
|
||||
#define kPowerFunctionExponentXOffset 1
|
||||
#define kPowerFunctionTrailingOffset 2
|
||||
@@ -36,7 +34,7 @@
|
||||
|
||||
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index
|
||||
{
|
||||
CGFloat y = self.baseBounds.size.height / 2;
|
||||
CGFloat y = (self.baseBounds.size.height + self.baseBounds.origin.y) / 2;
|
||||
return NSMakePoint(kPowerFunctionExponentXOffset, y);
|
||||
}
|
||||
|
||||
@@ -60,9 +58,6 @@
|
||||
}
|
||||
|
||||
- (void)draw
|
||||
{
|
||||
MPLayout *exponentLayout = [self childLayoutAtIndex:0];
|
||||
[exponentLayout drawAtPoint:[self offsetOfChildLayoutAtIndex:0]];
|
||||
}
|
||||
{}
|
||||
|
||||
@end
|
||||
|
||||
23
MathPad/MPProduct.h
Normal file
23
MathPad/MPProduct.h
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// MPProduct.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPExpressionTreeElement.h"
|
||||
#import "MPFactor.h"
|
||||
|
||||
@interface MPProduct : NSObject <MPExpressionTreeElement>
|
||||
|
||||
- (NSArray *)factors;
|
||||
- (void)appendFactor:(MPFactor *)factor;
|
||||
- (void)insertFactor:(MPFactor *)factor
|
||||
atIndex:(NSUInteger)index;
|
||||
- (void)removeFactor:(MPFactor *)factor;
|
||||
- (void)removeFactorAtIndex:(NSUInteger)index;
|
||||
- (MPFactor *)factorAtIndex:(NSUInteger)index;
|
||||
|
||||
@end
|
||||
125
MathPad/MPProduct.m
Normal file
125
MathPad/MPProduct.m
Normal file
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// MPProduct.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPProduct.h"
|
||||
#import "MPValueGroup.h"
|
||||
|
||||
@implementation MPProduct {
|
||||
NSMutableArray *_factors;
|
||||
NSRange _range;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_factors = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream
|
||||
{
|
||||
self = [self init];
|
||||
if (self) {
|
||||
[tokenStream beginIgnoringWhitespaceTokens];
|
||||
|
||||
MPTokenStreamRecordCurrentLocation(tokenStream);
|
||||
while (tokenStream.currentToken.tokenType != MPOperatorListToken && tokenStream.currentToken.tokenType != MPEOFToken) {
|
||||
[_factors addObject:[[MPFactor alloc] initWithTokenStream:tokenStream]];
|
||||
}
|
||||
_range = MPTokenStreamRecordedRange(tokenStream);
|
||||
[tokenStream endIgnoringOrAcceptingWhitespaceTokens];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSRange)range
|
||||
{
|
||||
return _range;
|
||||
}
|
||||
|
||||
- (BOOL)validate:(MPParseError *__autoreleasing *)error
|
||||
{
|
||||
MPFactor *factor = _factors[0];
|
||||
if (factor.hasMultiplicationSymbol) {
|
||||
if (error) {
|
||||
*error = MPParseError(factor.multiplicationSymbolRange, @"Unexpected Symbol.");
|
||||
}
|
||||
}
|
||||
if (![factor.value validate:error]) {
|
||||
return NO;
|
||||
}
|
||||
for (NSUInteger index = 1; index < _factors.count; index++) {
|
||||
factor = _factors[index];
|
||||
if (![factor validate:error]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)evaluate
|
||||
{
|
||||
NSDecimalNumber *value = [NSDecimalNumber one];
|
||||
for (MPFactor *factor in _factors) {
|
||||
NSDecimalNumber *currentValue = [factor evaluate];
|
||||
if ([currentValue isEqualToNumber:[NSDecimalNumber notANumber]]) {
|
||||
value = currentValue;
|
||||
break;
|
||||
}
|
||||
value = [value decimalNumberByMultiplyingBy:currentValue];
|
||||
if ([value compare:@(0)] == NSOrderedSame) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
- (NSArray *)expressionElements
|
||||
{
|
||||
NSMutableArray *elements = [[NSMutableArray alloc] init];
|
||||
for (MPFactor *factor in _factors) {
|
||||
[elements addObjectsFromArray:factor.expressionElements];
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
|
||||
- (NSArray *)factors
|
||||
{
|
||||
return _factors;
|
||||
}
|
||||
|
||||
- (void)appendFactor:(MPFactor *)factor
|
||||
{
|
||||
[_factors addObject:factor];
|
||||
}
|
||||
|
||||
- (void)insertFactor:(MPFactor *)factor
|
||||
atIndex:(NSUInteger)index
|
||||
{
|
||||
[_factors insertObject:factor
|
||||
atIndex:index];
|
||||
}
|
||||
|
||||
- (void)removeFactor:(MPFactor *)factor
|
||||
{
|
||||
[_factors removeObject:factor];
|
||||
}
|
||||
|
||||
- (void)removeFactorAtIndex:(NSUInteger)index
|
||||
{
|
||||
[_factors removeObjectAtIndex:index];
|
||||
}
|
||||
|
||||
- (MPFactor *)factorAtIndex:(NSUInteger)index
|
||||
{
|
||||
return [_factors objectAtIndex:index];
|
||||
}
|
||||
|
||||
@end
|
||||
15
MathPad/MPSuffixFunction.h
Normal file
15
MathPad/MPSuffixFunction.h
Normal file
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// MPSuffixFunction.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 11.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <MathKit/MathKit.h>
|
||||
|
||||
@interface MPSuffixFunction : MPFunction
|
||||
|
||||
@property (nonatomic, strong) id<MPValue> baseValue;
|
||||
|
||||
@end
|
||||
13
MathPad/MPSuffixFunction.m
Normal file
13
MathPad/MPSuffixFunction.m
Normal file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// MPSuffixFunction.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 11.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPSuffixFunction.h"
|
||||
|
||||
@implementation MPSuffixFunction
|
||||
|
||||
@end
|
||||
@@ -9,12 +9,8 @@
|
||||
#import "MPSumFunction.h"
|
||||
#import "MPExpression.h"
|
||||
|
||||
#import "MPExpressionEvaluator.h"
|
||||
#import "MPEvaluationContext.h"
|
||||
|
||||
// TODO: Use this macro elsewhere
|
||||
#define ReturnIfNil(test) if(test == nil) return nil
|
||||
|
||||
@implementation MPSumFunction
|
||||
|
||||
MPFunctionAccessorImplementation(StartExpression, _startExpression)
|
||||
@@ -27,42 +23,47 @@ MPFunctionAccessorImplementation(SumExpression, _sumExpression)
|
||||
}
|
||||
|
||||
#pragma mark Evaluating Functions
|
||||
- (MPTerm *)parseWithError:(MPParseError *__autoreleasing *)error
|
||||
{
|
||||
MPExpressionEvaluator *startEvaluator = [[MPExpressionEvaluator alloc] initWithExpression:self.startExpression];
|
||||
MPTerm *start = [startEvaluator parseExpectingVariable:YES
|
||||
error:error];
|
||||
ReturnIfNil(start);
|
||||
|
||||
MPExpressionEvaluator *targetEvaluator = [[MPExpressionEvaluator alloc] initWithExpression:self.targetExpression];
|
||||
MPTerm *target = [targetEvaluator parseExpectingVariable:NO
|
||||
error:error];
|
||||
ReturnIfNil(target);
|
||||
- (BOOL)validate:(MPParseError *__autoreleasing *)error
|
||||
{
|
||||
MPExpressionTree *startTree = [self.startExpression parse];
|
||||
if (![startTree validateExpectingVariableDefinition:YES error:error]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSString *variable = startEvaluator.definedVariable.copy;
|
||||
MPExpressionTree *targetTree = [self.targetExpression parse];
|
||||
if (![targetTree validate:error]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
[[MPEvaluationContext sharedContext] push];
|
||||
[[MPEvaluationContext sharedContext] defineVariable:variable withValue:[NSNull null]];
|
||||
MPExpressionEvaluator *sumEvaluator = [[MPExpressionEvaluator alloc] initWithExpression:self.sumExpression];
|
||||
MPTerm *sum = [sumEvaluator parseExpectingVariable:NO
|
||||
error:error];
|
||||
ReturnIfNil(sum);
|
||||
[[MPEvaluationContext sharedContext] defineVariable:startTree.definedVariable withValue:[NSDecimalNumber notANumber]];
|
||||
MPExpressionTree *sumTree = [self.sumExpression parse];
|
||||
if (![sumTree validate:error]) {
|
||||
return NO;
|
||||
}
|
||||
[[MPEvaluationContext sharedContext] pop];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)evaluate
|
||||
{
|
||||
MPExpressionTree *startTree = [self.startExpression parse];
|
||||
NSDecimalNumber *start = [startTree evaluate];
|
||||
NSDecimalNumber *target = [[self.targetExpression parse] evaluate];
|
||||
MPExpressionTree *sumTree = [self.sumExpression parse];
|
||||
|
||||
return [[MPTerm alloc] initWithBlock:^NSDecimalNumber *{
|
||||
[[MPEvaluationContext sharedContext] push];
|
||||
NSDecimalNumber *value = [NSDecimalNumber zero];
|
||||
NSDecimalNumber *startValue = [start evaluate];
|
||||
NSDecimalNumber *targetValue = [target evaluate];
|
||||
for (NSDecimalNumber *iterator = startValue;
|
||||
[iterator compare:targetValue] <= 0 ;
|
||||
iterator = [iterator decimalNumberByAdding:[[NSDecimalNumber alloc] initWithInteger:1]]) {
|
||||
[[MPEvaluationContext sharedContext] defineVariable:variable withValue:iterator];
|
||||
value = [value decimalNumberByAdding:[sum evaluate]];
|
||||
}
|
||||
[[MPEvaluationContext sharedContext] pop];
|
||||
return value;
|
||||
}];
|
||||
NSDecimalNumber *value = [NSDecimalNumber zero];
|
||||
|
||||
[[MPEvaluationContext sharedContext] push];
|
||||
for (NSDecimalNumber *current = start;
|
||||
[current compare:target] <= 0;
|
||||
current = [current decimalNumberByAdding:[[NSDecimalNumber alloc] initWithInteger:1]]) {
|
||||
[[MPEvaluationContext sharedContext] defineVariable:startTree.definedVariable withValue:current];
|
||||
value = [value decimalNumberByAdding:[sumTree evaluate]];
|
||||
}
|
||||
[[MPEvaluationContext sharedContext] pop];
|
||||
return value;
|
||||
}
|
||||
|
||||
#pragma mark Working With Functions
|
||||
|
||||
@@ -154,20 +154,7 @@
|
||||
NSRect localLineBounds = [self localLineBounds];
|
||||
CGContextSetTextPosition(context, localLineBounds.origin.x, 0);
|
||||
CTLineDraw(line, context);
|
||||
|
||||
// Draw the start function
|
||||
MPLayout *startExpressionLayout = [self childLayoutAtIndex:0];
|
||||
NSPoint startExpressionLocation = [self offsetOfChildLayoutAtIndex:0];
|
||||
[startExpressionLayout drawAtPoint:startExpressionLocation];
|
||||
|
||||
// Draw the target function
|
||||
MPLayout *targetExpressionLayout = [self childLayoutAtIndex:1];
|
||||
[targetExpressionLayout drawAtPoint:[self offsetOfChildLayoutAtIndex:1]];
|
||||
|
||||
// Draw the sum function
|
||||
MPLayout *sumExpressionLayout = [self childLayoutAtIndex:2];
|
||||
[sumExpressionLayout drawAtPoint:[self offsetOfChildLayoutAtIndex:2]];
|
||||
|
||||
|
||||
CFRelease(line);
|
||||
}
|
||||
|
||||
|
||||
19
MathPad/MPSummand.h
Normal file
19
MathPad/MPSummand.h
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// MPSummand.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPExpressionTreeElement.h"
|
||||
#import "MPProduct.h"
|
||||
#import "MPOperatorChain.h"
|
||||
|
||||
@interface MPSummand : NSObject <MPExpressionTreeElement>
|
||||
|
||||
@property (nonatomic, strong) MPOperatorChain *operatorChain;
|
||||
@property (nonatomic, strong) MPProduct *product;
|
||||
|
||||
@end
|
||||
71
MathPad/MPSummand.m
Normal file
71
MathPad/MPSummand.m
Normal file
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// MPSummand.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPSummand.h"
|
||||
#import "MPMathRules.h"
|
||||
|
||||
@implementation MPSummand
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream
|
||||
{
|
||||
self = [self init];
|
||||
if (self) {
|
||||
[tokenStream beginIgnoringWhitespaceTokens];
|
||||
if (tokenStream.currentToken.tokenType == MPOperatorListToken) {
|
||||
_operatorChain = [[MPOperatorChain alloc] initWithTokenStream:tokenStream];
|
||||
}
|
||||
_product = [[MPProduct alloc] initWithTokenStream:tokenStream];
|
||||
[tokenStream endIgnoringOrAcceptingWhitespaceTokens];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSRange)range
|
||||
{
|
||||
return NSUnionRange(self.operatorChain.range, self.product.range);
|
||||
}
|
||||
|
||||
- (BOOL)validate:(MPParseError *__autoreleasing *)error
|
||||
{
|
||||
if (self.operatorChain.numberOfOperators > [MPMathRules sharedRules].maximumOperatorChainLength) {
|
||||
if (error) {
|
||||
*error = MPParseError(self.operatorChain.range, @"Too many operators.");
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
return [self.product validate:error];
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)evaluate
|
||||
{
|
||||
NSDecimalNumber *value = [self.product evaluate];
|
||||
if ([value isNotEqualTo:[NSDecimalNumber notANumber]] && self.operatorChain) {
|
||||
value = [value decimalNumberByMultiplyingBy:[self.operatorChain evaluate]];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
-(NSArray *)expressionElements
|
||||
{
|
||||
NSMutableArray *elements = [[NSMutableArray alloc] init];
|
||||
if (self.operatorChain) {
|
||||
[elements addObjectsFromArray:self.operatorChain.expressionElements];
|
||||
}
|
||||
[elements addObjectsFromArray:self.product.expressionElements];
|
||||
return @[self.operatorChain.expressionElements,];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// MPTerm.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 27.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
@import Foundation;
|
||||
|
||||
@interface MPTerm : NSObject
|
||||
|
||||
- (instancetype)initWithBlock:(NSDecimalNumber *(^)())block; // Designated Initializer
|
||||
|
||||
- (instancetype)initWithNumber:(NSDecimalNumber *)number;
|
||||
- (instancetype)initWithSummands:(NSArray *)summands; // array of MPTerms
|
||||
- (instancetype)initWithFactors:(NSArray *)factors; // array of MPTerms
|
||||
- (instancetype)initWithVariable:(NSString *)variable;
|
||||
|
||||
- (instancetype)initWithFactorialOfTerm:(MPTerm *)term;
|
||||
|
||||
- (instancetype)initWithSinOfTerm:(MPTerm *)term;
|
||||
- (instancetype)initWithCosOfTerm:(MPTerm *)term;
|
||||
- (instancetype)initWithTanOfTerm:(MPTerm *)term;
|
||||
|
||||
- (instancetype)initWithInverseSinOfTerm:(MPTerm *)term;
|
||||
- (instancetype)initWithInverseCosOfTerm:(MPTerm *)term;
|
||||
- (instancetype)initWithInverseTanOfTerm:(MPTerm *)term;
|
||||
|
||||
- (NSDecimalNumber *)evaluate;
|
||||
|
||||
@end
|
||||
149
MathPad/MPTerm.m
149
MathPad/MPTerm.m
@@ -1,149 +0,0 @@
|
||||
//
|
||||
// MPTerm.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 27.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPTerm.h"
|
||||
#import "MPEvaluationContext.h"
|
||||
#import "MPMathRules.h"
|
||||
|
||||
@interface MPTerm ()
|
||||
|
||||
@property (nonatomic, copy) NSDecimalNumber *(^block)();
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPTerm
|
||||
|
||||
- (instancetype)initWithBlock:(NSDecimalNumber *(^)())block
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.block = block;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithNumber:(NSDecimalNumber *)number
|
||||
{
|
||||
return [self initWithBlock:^NSDecimalNumber *{
|
||||
return number;
|
||||
}];
|
||||
}
|
||||
|
||||
- (instancetype)initWithSummands:(NSArray *)summands
|
||||
{
|
||||
return [self initWithBlock:^NSDecimalNumber *{
|
||||
NSDecimalNumber *value = [NSDecimalNumber zero];
|
||||
for (MPTerm *term in summands) {
|
||||
value = [value decimalNumberByAdding:[term evaluate]];
|
||||
}
|
||||
return value;
|
||||
}];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFactors:(NSArray *)factors
|
||||
{
|
||||
return [self initWithBlock:^NSDecimalNumber *{
|
||||
NSDecimalNumber *value = [NSDecimalNumber one];
|
||||
for (MPTerm *term in factors) {
|
||||
value = [value decimalNumberByMultiplyingBy:[term evaluate]];
|
||||
}
|
||||
return value;
|
||||
}];
|
||||
}
|
||||
|
||||
- (instancetype)initWithVariable:(NSString *)variable
|
||||
{
|
||||
return [self initWithBlock:^NSDecimalNumber *{
|
||||
return [[MPEvaluationContext sharedContext] valueForVariable:variable];
|
||||
}];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFactorialOfTerm:(MPTerm *)term
|
||||
{
|
||||
return [self initWithBlock:^NSDecimalNumber *{
|
||||
NSDecimalNumber *termValue = [term evaluate];
|
||||
return [[NSDecimalNumber alloc] initWithDouble:tgamma(termValue.doubleValue + 1)];
|
||||
}];
|
||||
}
|
||||
|
||||
- (instancetype)initWithSinOfTerm:(MPTerm *)term
|
||||
{
|
||||
return [self initWithBlock:^NSDecimalNumber *{
|
||||
NSDecimalNumber *termValue = [term evaluate];
|
||||
if ([MPMathRules sharedRules].isUsingDegrees) {
|
||||
termValue = [[termValue decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithInteger:180]];
|
||||
}
|
||||
return [[NSDecimalNumber alloc] initWithDouble:sin(termValue.doubleValue)];
|
||||
}];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCosOfTerm:(MPTerm *)term
|
||||
{
|
||||
return [self initWithBlock:^NSDecimalNumber *{
|
||||
NSDecimalNumber *termValue = [term evaluate];
|
||||
if ([MPMathRules sharedRules].isUsingDegrees) {
|
||||
termValue = [[termValue decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithInteger:180]]; }
|
||||
return [[NSDecimalNumber alloc] initWithDouble:cos(termValue.doubleValue)];
|
||||
}];
|
||||
}
|
||||
|
||||
- (instancetype)initWithTanOfTerm:(MPTerm *)term
|
||||
{
|
||||
return [self initWithBlock:^NSDecimalNumber *{
|
||||
NSDecimalNumber *termValue = [term evaluate];
|
||||
if ([MPMathRules sharedRules].isUsingDegrees) {
|
||||
termValue = [[termValue decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithInteger:180]]; }
|
||||
return [[NSDecimalNumber alloc] initWithDouble:tan(termValue.doubleValue)];
|
||||
}];
|
||||
}
|
||||
|
||||
- (instancetype)initWithInverseSinOfTerm:(MPTerm *)term
|
||||
{
|
||||
return [self initWithBlock:^NSDecimalNumber *{
|
||||
NSDecimalNumber *termValue = [term evaluate];
|
||||
NSDecimalNumber *result = [[NSDecimalNumber alloc] initWithDouble:asin(termValue.doubleValue)];
|
||||
if ([MPMathRules sharedRules].isUsingDegrees) {
|
||||
result = [[result decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:180]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]];
|
||||
}
|
||||
return result;
|
||||
}];
|
||||
}
|
||||
|
||||
- (instancetype)initWithInverseCosOfTerm:(MPTerm *)term
|
||||
{
|
||||
return [self initWithBlock:^NSDecimalNumber *{
|
||||
NSDecimalNumber *termValue = [term evaluate];
|
||||
NSDecimalNumber *result = [[NSDecimalNumber alloc] initWithDouble:acos(termValue.doubleValue)];
|
||||
if ([MPMathRules sharedRules].isUsingDegrees) {
|
||||
result = [[result decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:180]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]];
|
||||
}
|
||||
return result; }];
|
||||
}
|
||||
|
||||
- (instancetype)initWithInverseTanOfTerm:(MPTerm *)term
|
||||
{
|
||||
return [self initWithBlock:^NSDecimalNumber *{
|
||||
NSDecimalNumber *termValue = [term evaluate];
|
||||
NSDecimalNumber *result = [[NSDecimalNumber alloc] initWithDouble:atan(termValue.doubleValue)];
|
||||
if ([MPMathRules sharedRules].isUsingDegrees) {
|
||||
result = [[result decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:180]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]];
|
||||
}
|
||||
return result; }];
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)evaluate
|
||||
{
|
||||
@try {
|
||||
return self.block();
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
return [NSDecimalNumber notANumber];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -15,6 +15,9 @@ typedef NS_ENUM(NSUInteger, MPTokenType) {
|
||||
MPSinToken,
|
||||
MPCosToken,
|
||||
MPTanToken,
|
||||
MPASinToken,
|
||||
MPACosToken,
|
||||
MPATanToken,
|
||||
MPNumberToken,
|
||||
MPVariableToken,
|
||||
MPFactorialToken,
|
||||
@@ -23,11 +26,8 @@ typedef NS_ENUM(NSUInteger, MPTokenType) {
|
||||
|
||||
MPWhitespaceToken,
|
||||
MPUnidentifiedToken,
|
||||
|
||||
MPCompoundToken
|
||||
};
|
||||
|
||||
|
||||
@protocol MPToken <NSObject>
|
||||
|
||||
- (MPTokenType)tokenType;
|
||||
@@ -41,22 +41,8 @@ typedef NS_ENUM(NSUInteger, MPTokenType) {
|
||||
@interface MPToken : NSObject <MPToken>
|
||||
|
||||
- (instancetype)initEOFTokenAtLocation:(NSUInteger)eofLocation;
|
||||
- (instancetype)initWithRange:(NSRange)range
|
||||
stringValue:(NSString *)input;
|
||||
- (instancetype)initWithTokenType:(MPTokenType)tokenType
|
||||
range:(NSRange)range
|
||||
stringValue:(NSString *)input;
|
||||
|
||||
@end
|
||||
|
||||
@interface MPToken (MPTokenExtension)
|
||||
// Methods are only available for respective token type
|
||||
|
||||
- (NSUInteger)numberOfOperators;
|
||||
- (NSDecimalNumber *)operatorValue;
|
||||
|
||||
- (NSDecimalNumber *)number;
|
||||
|
||||
- (NSString *)variable;
|
||||
|
||||
@end
|
||||
@@ -23,23 +23,14 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithRange:(NSRange)range stringValue:(NSString *)input
|
||||
- (instancetype)initWithTokenType:(MPTokenType)tokenType
|
||||
range:(NSRange)range
|
||||
stringValue:(NSString *)input
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_range = range;
|
||||
_stringValue = input.copy;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTokenType:(MPTokenType)tokenType
|
||||
range:(NSRange)range
|
||||
stringValue:(NSString *)input
|
||||
{
|
||||
self = [self initWithRange:range
|
||||
stringValue:input];
|
||||
if (self) {
|
||||
_tokenType = tokenType;
|
||||
}
|
||||
return self;
|
||||
@@ -65,40 +56,4 @@
|
||||
return self.stringValue;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation MPToken (MPTokenExtension)
|
||||
|
||||
- (NSUInteger)numberOfOperators
|
||||
{
|
||||
NSString *operatorString = [[self.stringValue componentsSeparatedByString:@" "] componentsJoinedByString:@""];
|
||||
return operatorString.length;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)operatorValue
|
||||
{
|
||||
NSString *operatorString = [[self.stringValue componentsSeparatedByString:@" "] componentsJoinedByString:@""];
|
||||
NSDecimalNumber *value = [NSDecimalNumber one];
|
||||
for (NSUInteger charIndex = 0; charIndex < operatorString.length; charIndex++) {
|
||||
NSString *operator = [operatorString substringWithRange:NSMakeRange(charIndex, 1)];
|
||||
if ([operator isEqualToString:@"-"]) {
|
||||
value = [value decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:-1]];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)number
|
||||
{
|
||||
return [NSDecimalNumber decimalNumberWithString:self.stringValue
|
||||
locale:[NSLocale currentLocale]];
|
||||
}
|
||||
|
||||
- (NSString *)variable
|
||||
{
|
||||
return self.stringValue;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@end
|
||||
@@ -14,14 +14,15 @@
|
||||
- (instancetype)initWithTokens:(NSArray *)tokens;
|
||||
|
||||
@property (nonatomic, copy) NSArray *tokens;
|
||||
@property (nonatomic, getter=isIgnoringWhitespaceTokens) BOOL ignoringWhitespaceTokens; // Default: YES
|
||||
|
||||
@property (nonatomic) NSUInteger currentTokenIndex;
|
||||
- (void)beginIgnoringWhitespaceTokens;
|
||||
- (void)beginAcceptingWhitespaceTokens;
|
||||
- (void)endIgnoringOrAcceptingWhitespaceTokens;
|
||||
|
||||
- (void)reset;
|
||||
- (BOOL)hasMoreTokens;
|
||||
|
||||
- (MPToken *)nextToken;
|
||||
- (MPToken *)nextTokenOfType:(MPTokenType)type;
|
||||
- (MPToken *)currentToken;
|
||||
- (MPToken *)peekNextToken;
|
||||
- (void)currentTokenConsumed;
|
||||
|
||||
@end
|
||||
|
||||
@@ -9,7 +9,12 @@
|
||||
#import "MPTokenStream.h"
|
||||
|
||||
@implementation MPTokenStream {
|
||||
NSUInteger _currentTokenIndex;
|
||||
NSUInteger eofLocation;
|
||||
|
||||
BOOL *whitespaceIgnores;
|
||||
NSUInteger maxWhitespaceIgnores;
|
||||
NSUInteger currentWhitespaceState;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTokens:(NSArray *)tokens
|
||||
@@ -17,65 +22,96 @@
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.tokens = tokens;
|
||||
self.ignoringWhitespaceTokens = YES;
|
||||
whitespaceIgnores = malloc(10 * sizeof(BOOL));
|
||||
maxWhitespaceIgnores = 10;
|
||||
currentWhitespaceState = 0;
|
||||
[self beginAcceptingWhitespaceTokens];
|
||||
_currentTokenIndex = 0;
|
||||
if (tokens.count > 0) {
|
||||
eofLocation = NSMaxRange([tokens.lastObject range]);
|
||||
} else {
|
||||
eofLocation = 0;
|
||||
}
|
||||
[self reset];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)beginAcceptingWhitespaceTokens
|
||||
{
|
||||
[self pushWhitespaceState:YES];
|
||||
}
|
||||
|
||||
- (void)beginIgnoringWhitespaceTokens
|
||||
{
|
||||
[self pushWhitespaceState:NO];
|
||||
}
|
||||
|
||||
- (void)pushWhitespaceState:(BOOL)state
|
||||
{
|
||||
currentWhitespaceState++;
|
||||
if (currentWhitespaceState >= maxWhitespaceIgnores) {
|
||||
maxWhitespaceIgnores += 10;
|
||||
BOOL *reallocatedWhitespaceIgnores = realloc(whitespaceIgnores, maxWhitespaceIgnores);
|
||||
if (reallocatedWhitespaceIgnores) {
|
||||
whitespaceIgnores = reallocatedWhitespaceIgnores;
|
||||
} else {
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Realloc Failed" userInfo:nil];
|
||||
}
|
||||
}
|
||||
whitespaceIgnores[currentWhitespaceState] = state;
|
||||
}
|
||||
|
||||
- (void)endIgnoringOrAcceptingWhitespaceTokens
|
||||
{
|
||||
currentWhitespaceState--;
|
||||
}
|
||||
|
||||
- (void)skipWhitespaces
|
||||
{
|
||||
if (!self.isIgnoringWhitespaceTokens) {
|
||||
if (whitespaceIgnores[currentWhitespaceState]) {
|
||||
return;
|
||||
}
|
||||
while (self.currentTokenIndex < self.tokens.count) {
|
||||
MPToken *token = self.tokens[self.currentTokenIndex];
|
||||
while (_currentTokenIndex < self.tokens.count) {
|
||||
MPToken *token = self.tokens[_currentTokenIndex];
|
||||
if (token.tokenType != MPWhitespaceToken) {
|
||||
return;
|
||||
}
|
||||
++self.currentTokenIndex;
|
||||
[self currentTokenConsumed];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reset
|
||||
{
|
||||
self.currentTokenIndex = 0;
|
||||
[self skipWhitespaces];
|
||||
}
|
||||
|
||||
- (BOOL)hasMoreTokens
|
||||
{
|
||||
[self skipWhitespaces];
|
||||
return self.currentTokenIndex < self.tokens.count;
|
||||
return _currentTokenIndex < self.tokens.count;
|
||||
}
|
||||
|
||||
- (MPToken *)nextToken
|
||||
- (MPToken *)currentToken
|
||||
{
|
||||
[self skipWhitespaces];
|
||||
if (self.currentTokenIndex >= self.tokens.count) {
|
||||
if (_currentTokenIndex >= _tokens.count) {
|
||||
return [[MPToken alloc] initEOFTokenAtLocation:eofLocation];
|
||||
} else {
|
||||
MPToken *token = self.tokens[self.currentTokenIndex++];
|
||||
return token;
|
||||
}
|
||||
return _tokens[_currentTokenIndex];
|
||||
}
|
||||
|
||||
- (MPToken *)nextTokenOfType:(MPTokenType)type
|
||||
- (MPToken *)peekNextToken
|
||||
{
|
||||
[self skipWhitespaces];
|
||||
if (self.currentTokenIndex >= self.tokens.count) {
|
||||
return nil;
|
||||
} else {
|
||||
MPToken *token = self.tokens[self.currentTokenIndex];
|
||||
if (token.tokenType != type) {
|
||||
return nil;
|
||||
}
|
||||
++self.currentTokenIndex;
|
||||
return token;
|
||||
}
|
||||
NSUInteger currentTokenIndex = _currentTokenIndex;
|
||||
[self currentTokenConsumed]; // Pretend the current token has been consumed
|
||||
MPToken *token = [self currentToken];
|
||||
_currentTokenIndex = currentTokenIndex; // Undo the lookahead
|
||||
return token;
|
||||
}
|
||||
|
||||
- (void)currentTokenConsumed
|
||||
{
|
||||
++_currentTokenIndex;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
free(whitespaceIgnores);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
16
MathPad/MPUnexpectedSymbolValue.h
Normal file
16
MathPad/MPUnexpectedSymbolValue.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// MPUnidentifiedSymbolValue.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 11.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "MPValueGroup.h"
|
||||
|
||||
@interface MPUnexpectedSymbolValue : NSObject <MPValue>
|
||||
|
||||
@property (nonatomic, copy) NSString *symbol;
|
||||
|
||||
@end
|
||||
59
MathPad/MPUnexpectedSymbolValue.m
Normal file
59
MathPad/MPUnexpectedSymbolValue.m
Normal file
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// MPUnidentifiedSymbolValue.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 11.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPUnexpectedSymbolValue.h"
|
||||
|
||||
@implementation MPUnexpectedSymbolValue {
|
||||
NSRange _range;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream
|
||||
{
|
||||
self = [self init];
|
||||
if (self) {
|
||||
[tokenStream beginIgnoringWhitespaceTokens];
|
||||
_range = tokenStream.currentToken.range;
|
||||
_symbol = tokenStream.currentToken.stringValue;
|
||||
[tokenStream currentTokenConsumed];
|
||||
[tokenStream endIgnoringOrAcceptingWhitespaceTokens];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSRange)range
|
||||
{
|
||||
return _range;
|
||||
}
|
||||
|
||||
- (BOOL)validate:(MPParseError *__autoreleasing *)error
|
||||
{
|
||||
if (error) {
|
||||
*error = MPParseError(self.range, @"Unknown Symbol.");
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)evaluate
|
||||
{
|
||||
return [NSDecimalNumber notANumber];
|
||||
}
|
||||
|
||||
- (NSArray *)expressionElements
|
||||
{
|
||||
return @[self.symbol];
|
||||
}
|
||||
|
||||
@end
|
||||
34
MathPad/MPValueGroup.h
Normal file
34
MathPad/MPValueGroup.h
Normal file
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// MPValueGroup.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPExpressionTreeElement.h"
|
||||
|
||||
@protocol MPValue <MPExpressionTreeElement>
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface MPValueGroup : NSObject <MPValue>
|
||||
|
||||
@property (nonatomic, strong) NSArray *values;
|
||||
- (void)appendValue:(id<MPValue>)value;
|
||||
- (void)insertValue:(id<MPValue>)value
|
||||
atIndex:(NSUInteger)index;
|
||||
- (void)removeValue:(id<MPValue>)value;
|
||||
- (void)removeValueAtIndex:(NSUInteger)index;
|
||||
- (id<MPValue>)valueAtIndex:(NSUInteger)index;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface MPTokenStream (MPValueSuffixes)
|
||||
|
||||
- (id<MPValue>)consumeSuffixesForValue:(id<MPValue>)value;
|
||||
|
||||
@end
|
||||
197
MathPad/MPValueGroup.m
Normal file
197
MathPad/MPValueGroup.m
Normal file
@@ -0,0 +1,197 @@
|
||||
//
|
||||
// MPValueGroup.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPValueGroup.h"
|
||||
#import "MPNumber.h"
|
||||
#import "MPVariable.h"
|
||||
#import "MPFunction+MPValue.h"
|
||||
#import "MPUnexpectedSymbolValue.h"
|
||||
#import "MPFunctionValue.h"
|
||||
#import "MPFactorial.h"
|
||||
#import "MPSuffixFunction.h"
|
||||
|
||||
@implementation MPValueGroup {
|
||||
NSMutableArray *_values;
|
||||
NSRange _range;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_values = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream
|
||||
{
|
||||
self = [self init];
|
||||
if (self) {
|
||||
[tokenStream beginIgnoringWhitespaceTokens];
|
||||
MPTokenStreamRecordCurrentLocation(tokenStream); // This will also skip leading whitespaces
|
||||
[tokenStream endIgnoringOrAcceptingWhitespaceTokens];
|
||||
[tokenStream beginAcceptingWhitespaceTokens];
|
||||
|
||||
BOOL checkMoreTokens = YES;
|
||||
while (tokenStream.currentToken.tokenType != MPWhitespaceToken && checkMoreTokens) {
|
||||
id<MPValue> currentValue;
|
||||
switch (tokenStream.currentToken.tokenType) {
|
||||
case MPNumberToken:
|
||||
{
|
||||
currentValue = [[MPNumber alloc] initWithTokenStream:tokenStream];
|
||||
}
|
||||
break;
|
||||
|
||||
case MPVariableToken:
|
||||
{
|
||||
currentValue = [[MPVariable alloc] initWithTokenStream:tokenStream];
|
||||
}
|
||||
break;
|
||||
|
||||
case MPGenericFunctionToken:
|
||||
{
|
||||
currentValue = (id<MPValue>)tokenStream.currentToken;
|
||||
[tokenStream currentTokenConsumed];
|
||||
}
|
||||
break;
|
||||
|
||||
case MPSinToken:
|
||||
case MPCosToken:
|
||||
case MPTanToken:
|
||||
case MPASinToken:
|
||||
case MPACosToken:
|
||||
case MPATanToken:
|
||||
{
|
||||
[tokenStream currentTokenConsumed];
|
||||
currentValue = [[MPFunctionValue alloc] initWithTokenStream:tokenStream];
|
||||
}
|
||||
break;
|
||||
|
||||
case MPUnidentifiedToken:
|
||||
case MPEqualsToken:
|
||||
currentValue = [[MPUnexpectedSymbolValue alloc] initWithTokenStream:tokenStream];
|
||||
break;
|
||||
|
||||
default:
|
||||
checkMoreTokens = NO;
|
||||
break;
|
||||
}
|
||||
if (checkMoreTokens) {
|
||||
[_values addObject:[tokenStream consumeSuffixesForValue:currentValue]];
|
||||
}
|
||||
}
|
||||
|
||||
_range = MPTokenStreamRecordedRange(tokenStream);
|
||||
|
||||
[tokenStream endIgnoringOrAcceptingWhitespaceTokens];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSRange)range
|
||||
{
|
||||
return _range;
|
||||
}
|
||||
|
||||
- (BOOL)validate:(MPParseError *__autoreleasing *)error
|
||||
{
|
||||
if (_values.count == 0) {
|
||||
if (error) {
|
||||
*error = MPParseError(self.range, @"Expected Value");
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
for (id<MPValue> value in _values) {
|
||||
if (![value validate:error]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)evaluate
|
||||
{
|
||||
NSDecimalNumber *result = [NSDecimalNumber one];
|
||||
for (id<MPValue> value in _values) {
|
||||
NSDecimalNumber *currentValue = [value evaluate];
|
||||
if ([currentValue isEqualToNumber:[NSDecimalNumber notANumber]]) {
|
||||
result = currentValue;
|
||||
break;
|
||||
}
|
||||
result = [result decimalNumberByMultiplyingBy:currentValue];
|
||||
if ([result compare:@(0)] == NSOrderedSame) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSArray *)expressionElements
|
||||
{
|
||||
NSMutableArray *elements = [[NSMutableArray alloc] init];
|
||||
for (id<MPValue> value in _values) {
|
||||
[elements addObjectsFromArray:value.expressionElements];
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
|
||||
- (void)appendValue:(id<MPValue>)value
|
||||
{
|
||||
[_values addObject:value];
|
||||
}
|
||||
|
||||
- (void)insertValue:(id<MPValue>)value
|
||||
atIndex:(NSUInteger)index
|
||||
{
|
||||
[_values insertObject:value
|
||||
atIndex:index];
|
||||
}
|
||||
|
||||
- (void)removeValue:(id<MPValue>)value
|
||||
{
|
||||
[_values removeObject:value];
|
||||
}
|
||||
|
||||
- (void)removeValueAtIndex:(NSUInteger)index
|
||||
{
|
||||
[_values removeObjectAtIndex:index];
|
||||
}
|
||||
|
||||
- (id<MPValue>)valueAtIndex:(NSUInteger)index
|
||||
{
|
||||
return [_values objectAtIndex:index];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation MPTokenStream (MPValueSuffixes)
|
||||
|
||||
- (id<MPValue>)consumeSuffixesForValue:(id<MPValue>)value
|
||||
{
|
||||
BOOL repeat = YES;
|
||||
while (repeat) {
|
||||
if (self.currentToken.tokenType == MPFactorialToken) {
|
||||
MPFactorial *factorial = [[MPFactorial alloc] init];
|
||||
factorial.value = value;
|
||||
value = factorial;
|
||||
[self currentTokenConsumed];
|
||||
} else if (self.currentToken.tokenType == MPGenericFunctionToken && [self.currentToken isKindOfClass:[MPSuffixFunction class]]) {
|
||||
MPSuffixFunction *token = (MPSuffixFunction *)self.currentToken;
|
||||
token.baseValue = value;
|
||||
value = token;
|
||||
[self currentTokenConsumed];
|
||||
} else {
|
||||
repeat = NO;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@end
|
||||
16
MathPad/MPVariable.h
Normal file
16
MathPad/MPVariable.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// MPVariable.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPValueGroup.h"
|
||||
|
||||
@interface MPVariable : NSObject <MPValue>
|
||||
|
||||
@property (nonatomic, strong) NSString *variableName;
|
||||
|
||||
@end
|
||||
68
MathPad/MPVariable.m
Normal file
68
MathPad/MPVariable.m
Normal file
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// MPVariable.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPVariable.h"
|
||||
#import "MPEvaluationContext.h"
|
||||
|
||||
@implementation MPVariable {
|
||||
NSRange _range;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream
|
||||
{
|
||||
self = [self init];
|
||||
if (self) {
|
||||
[tokenStream beginIgnoringWhitespaceTokens];
|
||||
MPToken *token = tokenStream.currentToken;
|
||||
if (token.tokenType == MPVariableToken) {
|
||||
self.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:(MPParseError *__autoreleasing *)error
|
||||
{
|
||||
if (![[MPEvaluationContext sharedContext] isVariableDefined:self.variableName]) {
|
||||
if (error) {
|
||||
*error = MPParseError(self.range, @"Undefined Variable");
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)evaluate
|
||||
{
|
||||
return [[MPEvaluationContext sharedContext] valueForVariable:self.variableName];
|
||||
}
|
||||
|
||||
- (NSArray *)expressionElements
|
||||
{
|
||||
return @[self.variableName];
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user