diff --git a/MathKit/MPElementaryFunctionTerm.h b/MathKit/MPElementaryFunctionTerm.h index 8921f78..bd9825c 100644 --- a/MathKit/MPElementaryFunctionTerm.h +++ b/MathKit/MPElementaryFunctionTerm.h @@ -9,15 +9,63 @@ #import "MPTerm.h" +/*! + @header + This file contains the MPElementaryFunction class. + */ + + @class MPElementaryFunctionTerm; +/*! + @class MPElementaryFunctionTerm + @abstract A MPElementaryFunction implements various + mathematical functions that have a valid text representation. + */ @interface MPElementaryFunctionTerm : MPTerm +/*! + @method initWithFunctionIdentifier:term: + @abstract Initializes a function term using the specified textual + representation of the function and a term as its value. + + @param function + The textual representation of the function to use. Valid values + are "sin", "cos", "tan", + "asin", "arcsin", "acos", + "arccos", "atan", + "arctan", "log", "lg" and + "ln". + + @param term + The term that should be evaluated using function. + + @return A new MPElementaryFunctionTerm instance. + */ - (instancetype)initWithFunctionIdentifier:(NSString *)function term:(MPTerm *)term; /* designated initializer */ + +/*! + @property functionIdentifier + @abstract The identifier of the receiver. + + @discussion The identifier of a function is the string of letters it is + mathematically defined of (e.g. "sin" for the sine + function). + */ +@property (readonly, nonatomic, copy) NSString *functionIdentifier; + + +/*! + @property term + @abstract The receiver's term. + + @discussion Depending on the actual function the value of the + term may be converted from radians into degrees. + */ @property (readonly, nonatomic, strong) MPTerm *term; @end \ No newline at end of file diff --git a/MathKit/MPElementaryFunctionTerm.m b/MathKit/MPElementaryFunctionTerm.m index d95bdf4..b492228 100644 --- a/MathKit/MPElementaryFunctionTerm.m +++ b/MathKit/MPElementaryFunctionTerm.m @@ -9,13 +9,10 @@ #import "MPElementaryFunctionTerm.h" #import "MPParsedExpression.h" -#import "MPPowerFunction.h" -#import "MPProductTerm.h" -#import "MPToken.h" - -#import "MPExpression.h" #import "MPMathRules.h" + + @implementation MPElementaryFunctionTerm { NSDecimalNumber *(^_function)(NSDecimalNumber *); } @@ -53,16 +50,18 @@ } else if ([function isEqualToString:@"ln"]) { func = &log; } else { - NSAssert(true, @"function must be one of (sin, cos, tan, asin, acos, atan, lg, log, ln)."); + NSAssert(true, @"function must be one of (sin, cos, tan, asin, arcsin, acos, arccos, atan, arctan, lg, log, ln)."); } [self setFunction:func takesArcValue:takesArc returnsArcValue:returnsArc]; _term = term; + _functionIdentifier = function.copy; } return self; } + - (void)setFunction:(double (*)(double))function takesArcValue:(BOOL)takesArc returnsArcValue:(BOOL)returnsArc @@ -80,6 +79,7 @@ }; } + - (NSDecimalNumber *)convertToRadiantsIfNecessary:(NSDecimalNumber *)degrees { if ([MPMathRules sharedRules].isUsingDegrees) { @@ -90,6 +90,7 @@ } } + - (NSDecimalNumber *)convertToDegreesIfNecessary:(NSDecimalNumber *)radiants { if ([MPMathRules sharedRules].isUsingDegrees) { @@ -100,13 +101,24 @@ } } + - (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error { NSDecimalNumber *value = [self.term evaluate:error]; if (!value) { return nil; } - return _function(value); + NSDecimalNumber *result = _function(value); + if ([result isEqualToNumber:[NSDecimalNumber notANumber]]) { + result = nil; + if (error) { + NSString *errorDescription = [NSString stringWithFormat:NSLocalizedString(@"%@ is not defined for the value %@.", nil), self.functionIdentifier, [value descriptionWithLocale:[NSLocale currentLocale]]]; + *error = [NSError errorWithDomain:MPMathKitErrorDomain + code:102 + userInfo:@{NSLocalizedDescriptionKey: errorDescription}]; + } + } + return result; } @end diff --git a/MathKit/MPEvaluationContext.h b/MathKit/MPEvaluationContext.h index 27c409b..d2f2bd4 100644 --- a/MathKit/MPEvaluationContext.h +++ b/MathKit/MPEvaluationContext.h @@ -7,21 +7,117 @@ // +/*! + @header + This file contains the MPEvaluationContext class. + + The evaluation context is organized in so called levels. Every level of a + context contains a unique set of variables. Only the highest level is + accessible. Before any lower level can be accessed the higher ones have to be + discarded. A higher level can not define variables that are already defined in + a lower level. Similarly variables can only be undefined if they are defined at + the current level. Discarding (popping) a level always undefines all variables + defined on that level. + */ + + @class MPEvaluationContext; +/*! + @class MPEvaluationContext + @abstract The evaluation context maintains a map of variables and + associates them with a certain level. + + @discussion Different levels are nested using @link + //apple_ref/occ/instm/MPEvaluationContext/push@/link and + @link + //apple_ref/occ/instm/MPEvaluationContext/pop@/link + messages. + + MPEvaluationContext is a singletone class. There can + only exist one instance (the shared instance) at any time. + */ @interface MPEvaluationContext : NSObject +/*! + @method sharedContext + @abstract Returns the shared evaluation context. + */ + (MPEvaluationContext *)sharedContext; + +/*! + @method push + @abstract Pushes a new level on context. + */ - (void)push; + + +/*! + @method pop + @abstract Pops a level off the context. + + @discussion Popping a level also undefines all variables that were defined in + that level. + */ - (void)pop; + +/*! + @method defineVariable:value: + @abstract Defines a variable with the specified value at the current level. + + @discussion The variable can only be defined if it has not been defined + previously in a lower level. If the variable has been defined at + the current level its value will be overwritten to the specified + one. + + @param variable + The name of the variable. Typically this is a one-letter string. + + @param value + The value of the variable. + + @return YES if the variable was defined successfully, + NO if it is already defined in a lower level. + */ - (BOOL)defineVariable:(NSString *)variable value:(NSDecimalNumber *)value; + + +/*! + @method undefineVariable: + @abstract Undefines a variable that has previously been defined. + + @discussion Variables can only be undefined if they have been defined at the + same level. If the variable to be undefined was defined at a + different level this method does nothing. + + Normally there is very little reason to call this method since + all variables defined on one level are automatically undefined + when the level is popped. + + @param variable + The variable to be undefined. + */ - (void)undefineVariable:(NSString *)variable; + +/*! + @method valueForVariable: + @abstract Returns the value for the specified variable. + + @discussion If the variable has not been defined previously this + method returns nil. + + @param variable + The variable whose value is to be retrieved. + + @return The value variable was defined with or + nil if it was not defined. + */ - (NSDecimalNumber *)valueForVariable:(NSString *)variable; @end diff --git a/MathKit/MPEvaluationContext.m b/MathKit/MPEvaluationContext.m index 134cebf..21fac5c 100644 --- a/MathKit/MPEvaluationContext.m +++ b/MathKit/MPEvaluationContext.m @@ -8,13 +8,21 @@ #import "MPEvaluationContext.h" + + @interface MPEvaluationContext () + @property (nonatomic, strong) NSMutableArray *stack; + @end + + @implementation MPEvaluationContext static MPEvaluationContext *sharedContext; + + + (MPEvaluationContext *)sharedContext { if (!sharedContext) { @@ -23,6 +31,7 @@ static MPEvaluationContext *sharedContext; return sharedContext; } + - (instancetype)init { if (sharedContext) { @@ -40,16 +49,19 @@ static MPEvaluationContext *sharedContext; return self; } + - (void)push { [self.stack addObject:[[NSMutableDictionary alloc] init]]; } + - (void)pop { [self.stack removeLastObject]; } + - (BOOL)defineVariable:(NSString *)variable value:(NSDecimalNumber *)value { @@ -62,17 +74,20 @@ static MPEvaluationContext *sharedContext; return YES; } + - (void)undefineVariable:(NSString *)variable { NSMutableDictionary *currentBindings = self.stack.lastObject; [currentBindings removeObjectForKey:variable]; } + - (BOOL)isVariableDefined:(NSString *)variable { return [self valueForVariable:variable] != nil; } + - (NSDecimalNumber *)valueForVariable:(NSString *)variable { NSUInteger currentIndex = self.stack.count; diff --git a/MathKit/MPExpression.h b/MathKit/MPExpression.h index a32718f..11772b6 100644 --- a/MathKit/MPExpression.h +++ b/MathKit/MPExpression.h @@ -10,6 +10,9 @@ /*! @header + This file contains the MPExpression class and the + MPExpressionElement protocol. + The MPExpression class is used to represent a mathematical expression. It is used in the storage layer of the MathKit Expression System. An instance of the MPExpression class stores all contents related to @@ -108,6 +111,58 @@ */ +/*! + @const MPIllegalElementException + @abstract Name for an exception that is raised if an invalid element is + added to an expression. + + @discussion This exception may be raised during initialization of an + expression or when the expression is mutated. This exception + contains the invalid element in its userInfo + dictionary. You can query it using the + MPIllegalElementExceptionElementKey key. + */ +FOUNDATION_EXPORT NSString *const MPIllegalElementException; + + +/*! + @const MPIllegalElementExceptionElementKey + @abstract Predefined key for an invalid element that caused a + MPIllegalElementException to be raised. + + @discussion The invalid element can be of any type. Numbers and structs are + wrapped in an NSNumber or NSValue + instance respectively. + */ +FOUNDATION_EXPORT NSString *const MPIllegalElementExceptionElementKey; + + +/*! + @typedef MPReferenceFrame + @abstract A reference frame specifies the way an item is to be + interpreted. + + @constant MPElementReferenceFrame + Specifies that items should be interpreted as elements. + + @constant MPSymbolReferenceFrame + Specifies that items should be interpreted as symbols. + + @constant MPTokenReferenceFrame + Specifies that items should be interpreted as tokens. + */ +typedef enum { + MPElementReferenceFrame, + MPSymbolReferenceFrame, + MPTokenReferenceFrame +} MPReferenceFrame; + + + +@class MPExpression, MPFunction, MPRangePath, MPParsedExpression; +@protocol MPExpressionElement, MPToken; + + /*! @protocol MPExpressionElement @abstract This protocol defines the functionality an element in an @@ -161,58 +216,6 @@ -/*! - @const MPIllegalElementException - @abstract Name for an exception that is raised if an invalid element is - added to an expression. - - @discussion This exception may be raised during initialization of an - expression or when the expression is mutated. This exception - contains the invalid element in its userInfo - dictionary. You can query it using the - MPIllegalElementExceptionElementKey key. - */ -FOUNDATION_EXPORT NSString *const MPIllegalElementException; - - -/*! - @const MPIllegalElementExceptionElementKey - @abstract Predefined key for an invalid element that caused a - MPIllegalElementException to be raised. - - @discussion The invalid element can be of any type. Numbers and structs are - wrapped in an NSNumber or NSValue - instance respectively. - */ -FOUNDATION_EXPORT NSString *const MPIllegalElementExceptionElementKey; - - -/*! - @typedef MPReferenceFrame - @abstract A reference frame specifies the way an item is to be - interpreted. - - @constant MPElementReferenceFrame - Specifies that items should be interpreted as elements. - - @constant MPSymbolReferenceFrame - Specifies that items should be interpreted as symbols. - - @constant MPTokenReferenceFrame - Specifies that items should be interpreted as tokens. - */ -typedef enum { - MPElementReferenceFrame, - MPSymbolReferenceFrame, - MPTokenReferenceFrame -} MPReferenceFrame; - - - -@class MPExpression, MPFunction, MPRangePath, MPParsedExpression; -@protocol MPExpressionElement, MPToken; - - /*! @class MPExpression @abstract A MPExpression instance represents a mathematical diff --git a/MathKit/MPExpression.m b/MathKit/MPExpression.m index 6d201a5..da26d60 100644 --- a/MathKit/MPExpression.m +++ b/MathKit/MPExpression.m @@ -21,11 +21,11 @@ #import "NSIndexPath+MPAdditions.h" - NSString *const MPIllegalElementException = @"Illegal Element Exception"; NSString *const MPIllegalElementExceptionElementKey = @"MPIllegalElementExceptionElementKey"; + @interface MPExpression () { NSMutableArray * _elements; } @@ -299,7 +299,6 @@ NSString *const MPIllegalElementExceptionElementKey = @"MPIllegalElementExceptio } -#warning If multiple equal expressions exist errors may occur... - (NSUInteger)indexOfElement:(id)element { return [_elements indexOfObject:element]; @@ -656,7 +655,7 @@ NSString *const MPIllegalElementExceptionElementKey = @"MPIllegalElementExceptio - (NSString *)description { -#warning Bad Implementation +#warning Incomplete Implementation NSMutableString *description = [[NSMutableString alloc] init]; NSUInteger index = 0; for (id element in _elements) { diff --git a/MathKit/MPExpressionLayout.h b/MathKit/MPExpressionLayout.h index cc8d52c..d44e723 100644 --- a/MathKit/MPExpressionLayout.h +++ b/MathKit/MPExpressionLayout.h @@ -9,14 +9,53 @@ #import "MPLayout.h" +/*! + @header + This file contains the MPExpressionLayout class. + */ + + @class MPExpressionLayout, MPExpression, MPFunctionLayout; + +/*! + @class MPExpressionLayout + @abstract An expression layout draws an @link + //apple_ref/occ/cl/MPExpression@/link. + + @discussion For more information on the layout system see the documentation + on the @link //apple_ref/occ/cl/MPLayout@/link + class. + */ @interface MPExpressionLayout : MPLayout +/*! + @method initWithExpression:parent: + @abstract Initializes an expression layout with the specified expression + and parent layout. + + @discussion To initialize a layout for the root expression of an expression + tree specify nil as the parent. + + @param expression + The expression that sould be represented by the receiver. Must + not be ni. + + @param parent + The parent layout of the receiver. Specify nil to + initalize a root expression layout. + + @return A newly initialized expression layout. + */ - (instancetype)initWithExpression:(MPExpression *)expression parent:(MPFunctionLayout *)parent; + +/*! + @property expression + @abstract The expression represented by the receiver. + */ @property (readonly, nonatomic, weak) MPExpression *expression; @end diff --git a/MathKit/MPExpressionLayout.m b/MathKit/MPExpressionLayout.m index 30333fa..2ffcbeb 100644 --- a/MathKit/MPExpressionLayout.m +++ b/MathKit/MPExpressionLayout.m @@ -9,21 +9,23 @@ #import "MPExpressionLayout.h" #import "MPExpression.h" -#import "MPExpression.h" -#import "MPPowerFunction.h" - +#import "MPFunction.h" #import "MPFunctionLayout.h" #import "MPPowerFunctionLayout.h" #import "MPToken.h" #import "NSIndexPath+MPAdditions.h" + + @interface MPExpressionLayout (MPLineGeneration) - (CTLineRef)lineForElementAtIndex:(NSUInteger)index; @end + + @implementation MPExpressionLayout (MPLineGeneration) - (CTLineRef)lineForElementAtIndex:(NSUInteger)index @@ -60,9 +62,13 @@ @end + + @implementation MPExpressionLayout # pragma mark Creation Methods + + - (instancetype)initWithExpression:(MPExpression *)expression parent:(MPFunctionLayout *)parent { @@ -73,12 +79,16 @@ return self; } + #pragma mark Cache Methods + + - (NSUInteger)numberOfChildren { return self.expression.countElements; } + - (MPLayout *)childLayoutAtIndex:(NSUInteger)index { id cachedObject = [self cachableObjectForIndex:index generator:^id{ @@ -95,6 +105,7 @@ return nil; } + - (NSRect)boundsOfElementAtIndex:(NSUInteger)index { id symbol = [self.expression elementAtIndex:index]; @@ -109,7 +120,10 @@ } } + #pragma mark Drawing Methods + + - (NSRect)generateBounds { if (self.expression.countElements == 0) { @@ -125,6 +139,7 @@ return NSMakeRect(x, y, width, height); } + - (NSRect)boundingRectForRange:(NSRange)range { NSUInteger startOffset; @@ -184,6 +199,7 @@ return NSMakeRect(x, self.bounds.origin.y, width, self.bounds.size.height); } + - (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index { CGFloat x = 0; @@ -193,6 +209,7 @@ return NSMakePoint(x, 0); } + - (NSIndexPath *)indexPathForMousePoint:(NSPoint)point { NSUInteger currentPosition = 0; @@ -235,11 +252,13 @@ } } + - (BOOL)drawsChildrenManually { return YES; } + - (void)draw { // Get the current context diff --git a/MathKit/MPExpressionParser.h b/MathKit/MPExpressionParser.h index 217f5eb..1d6a63b 100644 --- a/MathKit/MPExpressionParser.h +++ b/MathKit/MPExpressionParser.h @@ -9,6 +9,8 @@ /*! @header + This file contains the MPExpressionParser class. + The MPExpressionParser converts a @link MPExpression@/link instance into a @link //apple_ref/occ/cl/MPParsedExpression@/link instance. The rules that are diff --git a/MathKit/MPExpressionParser.m b/MathKit/MPExpressionParser.m index 89e7d34..80140db 100644 --- a/MathKit/MPExpressionParser.m +++ b/MathKit/MPExpressionParser.m @@ -8,25 +8,27 @@ #import "MPExpressionParser.h" -#import "MPExpression.h" -#import "MPTerm.h" #import "MPToken.h" +#import "MPExpression.h" +#import "MPParsedExpression.h" + +#import "MPTerm.h" #import "MPSumTerm.h" #import "MPProductTerm.h" -#import "MPFactorialTerm.h" -#import "MPElementaryFunctionTerm.h" -#import "MPFunctionTerm.h" -#import "MPPowerTerm.h" -#import "MPParsedExpression.h" #import "MPNegatedTerm.h" - +#import "MPFunctionTerm.h" +#import "MPElementaryFunctionTerm.h" #import "MPNumber.h" #import "MPVariable.h" #import "MPFactorialTerm.h" +#import "MPPowerTerm.h" + #define success() state = 0 #define fail() self.errorTokenIndex = self.currentTokenIndex; state = -1 + + @interface MPExpressionParser () @property (nonatomic, strong) NSArray *tokens; @@ -46,6 +48,8 @@ @end + + @implementation MPExpressionParser - (instancetype)initWithExpression:(MPExpression *)expression @@ -58,12 +62,14 @@ return self; } + - (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors { return [self parseExpectingVariableDefinition:NO errors:errors]; } + - (MPParsedExpression *)parseExpectingVariableDefinition:(BOOL)flag errors:(NSArray *__autoreleasing *)errors { @@ -120,6 +126,7 @@ return result; } + - (void)runParserMachine { NSInteger state = 1; @@ -251,6 +258,7 @@ } } + - (void)skipWhitespaces { while ([self currentToken] != nil && [self currentToken].tokenType == MPWhitespaceToken) { @@ -258,6 +266,7 @@ } } + - (id)currentToken { if (self.currentTokenIndex >= self.tokens.count) { @@ -266,11 +275,13 @@ return self.tokens[self.currentTokenIndex]; } + - (void)nextToken { self.currentTokenIndex++; } + - (NSMutableArray *)errors { if (!_errors) { @@ -279,6 +290,7 @@ return _errors; } + - (NSMutableArray *)summands { if (!_summands) { @@ -287,6 +299,7 @@ return _summands; } + - (NSMutableArray *)factors { if (!_factors) { @@ -295,6 +308,7 @@ return _factors; } + - (NSMutableArray *)functionStack { if (!_functionStack) { @@ -303,6 +317,7 @@ return _functionStack; } + - (NSMutableArray *)valueGroup { if (!_valueGroup) { @@ -311,6 +326,7 @@ return _valueGroup; } + - (BOOL)parseOperatorList:(id)token // Returns YES if list is overall negative { NSString *operatorString = [[token.stringValue stringByReplacingOccurrencesOfString:@" " withString:@""] @@ -318,17 +334,20 @@ return (operatorString.length & 1) == 1; } + - (NSDecimalNumber *)parseNumber:(id)token { return [NSDecimalNumber decimalNumberWithString:token.stringValue locale:[NSLocale currentLocale]]; } + - (NSString *)parseVariable:(id)token { return token.stringValue; } + - (void)collapseSummand { if (self.factors.count > 0) { @@ -342,6 +361,7 @@ self.summandNegative = NO; } + - (void)collapseFactor { if (self.valueGroup.count > 0) { @@ -360,6 +380,7 @@ self.factorNegative = NO; } + - (void)runSuffixMachine { BOOL checkMore = YES; @@ -388,6 +409,7 @@ } } + - (void)addErrorWithCode:(NSInteger)code localizedDescription:(NSString *)description useRange:(BOOL)flag @@ -408,6 +430,7 @@ MPErrorRangeKey: [NSValue valueWithRange:errorRange]}]]; } + - (BOOL)errorOccured { [self skipWhitespaces]; diff --git a/MathKit/MPExpressionStorage.h b/MathKit/MPExpressionStorage.h index ea80169..fdb4d7d 100644 --- a/MathKit/MPExpressionStorage.h +++ b/MathKit/MPExpressionStorage.h @@ -9,13 +9,51 @@ #import "MPExpression.h" +/*! + @header + This file contains the MPExpressionStorage class. + */ + + @class MPExpressionStorage, MPExpressionView, MPExpressionLayout; +/*! + @class MPExpressionStorage + @abstract An expression storage manages the contents of an expression view + and notifies it when the underlying expression changes. + */ @interface MPExpressionStorage : MPExpression -@property (nonatomic, weak) MPExpressionView *expressionView; // Do not set +/*! + @property expressionView + @abstract The receiver's expression view. + + @discussion The expression view is the view that displays the contents of the + expression storage. Normally you should not call the setter of + this property. It is automatically assigned when the @link + //apple_ref/occ/instp/MPExpressionView/expressionStorage@/link + property of the @link + //apple_ref/occ/cl/MPExpressionView@/link class is set. + + When this property is set the receiver assumes that the + displaying expression view has changed. It then adapts to the + properties of the new expression view and discards the root + layout. + */ +@property (nonatomic, weak) MPExpressionView *expressionView; + + +/*! + @property rootLayout + @abstract The receiver's root layout. + + @discussion The root layout is the root node of the layout tree that draws + the receiver's contents. For more information see the + documentation on the @link + //apple_ref/occ/cl/MPLayout@/link class. + */ @property (nonatomic, strong) MPExpressionLayout *rootLayout; @end diff --git a/MathKit/MPExpressionStorage.m b/MathKit/MPExpressionStorage.m index 1432afc..83d7e3a 100644 --- a/MathKit/MPExpressionStorage.m +++ b/MathKit/MPExpressionStorage.m @@ -9,17 +9,22 @@ #import "MPExpressionStorage.h" #import "MPExpressionView.h" -#import "MPLayout.h" #import "MPExpressionLayout.h" #import "MPRangePath.h" + + @interface MPExpressionStorage () + @property (nonatomic, strong) NSLayoutManager *layoutManager; @property (nonatomic, strong) NSTextContainer *textContainer; @property (nonatomic, strong) NSTextStorage *textStorage; + @end + + @implementation MPExpressionStorage - (instancetype)initWithElements:(NSArray *)elements @@ -31,6 +36,7 @@ return self; } + - (void)setExpressionView:(MPExpressionView *)expressionView { _expressionView = expressionView; @@ -38,6 +44,7 @@ self.rootLayout.flipped = expressionView.flipped; } + - (void)changedElementsInRangePath:(MPRangePath *)rangePath replacementLength:(NSUInteger)replacementLength { diff --git a/MathKit/MPExpressionTokenizer.h b/MathKit/MPExpressionTokenizer.h index 1c6d7ae..0a36a21 100644 --- a/MathKit/MPExpressionTokenizer.h +++ b/MathKit/MPExpressionTokenizer.h @@ -7,6 +7,12 @@ // +/*! + @header + This file contains the MPExpressionTokenizer class. + */ + + @class MPExpressionTokenizer, MPExpression; diff --git a/MathKit/MPExpressionTokenizer.m b/MathKit/MPExpressionTokenizer.m index 09914bd..5dd4cce 100644 --- a/MathKit/MPExpressionTokenizer.m +++ b/MathKit/MPExpressionTokenizer.m @@ -52,7 +52,7 @@ @"((?:\\d+%@(?!\\d+))|(?:(?:\\d*%@){2,}\\d*)|%@(?!\\d+))|" // Substitute with decimal separator 3 times @"((?:\\d+(?:%@\\d+)?)|(?:%@\\d+))|" // Substitute with decimal separator 2 times @"(sin|cos|tan|asin|arcsin|acos|arccos|atan|arctan|lg|log|ln)|" - @"([A-Za-z])|" + @"([A-Za-z΀])|" @"(!)|" @"(=)|" @"(\\s+)" diff --git a/MathKit/MPExpressionView.h b/MathKit/MPExpressionView.h index d978e60..49b941b 100644 --- a/MathKit/MPExpressionView.h +++ b/MathKit/MPExpressionView.h @@ -6,68 +6,195 @@ // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // -// TODO: Undo/Redo + Delegate (evaluateExpressionView:evaluate...) + +/*! + @header + This file contains the MPExpressionView class. + +

The MathKit Expression System

+ MathKit contains class es that make up the so called expression system. + The expression system is divided into three layers: the model, the view and the + controller layer. The MPExpressionView class represents the view + layer and interacts with the Cocoa NSView system. The model is + represented by the classes @link + //apple_ref/occ/cl/MPExpression@/link and @link + //apple_ref/occ/cl/MPFunction@/link. The controller layer between the two + consists of the class @link //apple_ref/occ/cl/MPLayout@/link and + its subclasses. The purpose of the expression system is presenting expressions + to the user and offering possibilities to manipulate and work with expressions. + */ @class MPExpressionView, MPExpressionStorage, MPFunction, MPRangePath; -//@protocol MPExpressionViewDelegate; +/*! + @class MPExpressionView + @abstract The MPExpressionView class is the front-end class of + the MathKit expression system. + + @discussion The class draws the expression managed by the the back-end class + @link //apple_ref/occ/cl/MPExpressionStorage@/link + and is the interface between Application Kit's view system and + the MathKit expression system. + */ @interface MPExpressionView : NSView #pragma mark Creation Methods +/*! + @methodgroup Creation Methods + */ + +/*! + @method initWithFrame: + @abstract Initializes a MPExpressionView instance. + + @discussion This method sets up all layers of the expression system including + an empty expression storage. + + This method is the designated initializer of the + MPExpressionView class. + + @param frameRect + The frame rectangle for the created view. + + @return An initialized MPExpressionView or nil + if the object could not be created. + */ - (id)initWithFrame:(NSRect)frameRect; + #pragma mark Properties +/*! + @methodgroup Properties + */ -@property (readonly, nonatomic, strong) MPExpressionStorage *expressionStorage; -//@property (nonatomic, weak) id delegate; +/*! + @property expressionStorage + @abstract The receiver's expression storage. + + @discussion The expression storage maintains the contents of the receiver. + User interactions change the underlying data. If the expression + storage or the underlying expression change the expression + storage updates the receiver. + */ +@property (nonatomic, strong) MPExpressionStorage *expressionStorage; + +/*! + @property selection + @abstract The receiver's selection. + */ @property (nonatomic, strong) MPRangePath *selection; + +/*! + @property mathError + @abstract If set the receiver will display the localized description of the + specified error. + + @discussion There can only be one math error at a time. This property should + not be set simutaneously with the @link + //apple_ref/occ/instp/MPExpressionView/syntaxErrors@/link. + */ @property (nonatomic, strong) NSError *mathError; + + +/*! + @property syntaxErrors + @abstract If set the receiver will display a dropdown list of syntax + errors. + + @discussion The array must only contain NSError instances. In + addition to that every instance must contain a valid + NSIndexPath for the @link + //apple_ref/c/data/MPPathToExpressionKey@/link in its + userDict acompanied by a NSRange + wrapped in a NSValue instance for the @link + //apple_ref/c/data/MPErrorRangeKey@/link. + + If the user selects an item from the dropdown list the part of + the expression displayed by the receiver will be selected that is + specified by the values in the corresponding NSError + instance. + */ @property (nonatomic, strong) NSArray *syntaxErrors; + +/*! + @property target + @abstract The target object to receive action messages from the receiver. + */ @property (nonatomic, weak) id target; + + +/*! + @property action + @abstract The receiver's action method to the specified selector. + + @discussion The action method is invoked when the user presses enter + in the receiver. Typically this is understood as evaluation + request. + */ @property (nonatomic) SEL action; -//@property (nonatomic) BOOL editable; -//@property (nonatomic) BOOL selectable; #pragma mark Actions -// Radians - Degrees +/*! + @methodgroup Actions + */ + + +/*! + @method switchToRadians: + @abstract Tells the receiver that it should switch the interpretation of + trigonometric function values to radians. + + @param sender + Typically the object that invoked the method. + */ - (IBAction)switchToRadians:(id)sender; + + +/*! + @method switchToDegrees: + @abstract Tells the receiver that it should switch the interpretation of + trigonometric function values to degrees. + + @param sender + Typically the object that invoked the method. + */ - (IBAction)switchToDegrees:(id)sender; + + +/*! + @method switchRadiansDegrees: + @abstract Tells the receiver that it should switch the interpretation of + trigonometric function values. + + @discussion If the interpretation is currently degrees it is changed to + radians or vice versa. + + @param sender + Typically the object that invoked the method. + */ - (IBAction)switchRadiansDegrees:(id)sender; -// Functions + + +/*! + @method toggleFunctionsPopover: + @abstract Tells the receiver to toggle the visibility of the functions + popover. + + @discussion From the functions popover the user can select and insert + functions into the displayed expression + + @param sender + Typically the object that invoked the method. + */ - (IBAction)toggleFunctionsPopover:(id)sender; @end - - -//@protocol MPExpressionViewDelegate -//@optional -// -//#pragma mark Editing -//- (MPRangePath *)expressionView:(MPExpressionView *)expressionView willChangeSelectionFromRangePath:(MPRangePath *)oldSelection toRangePath:(MPRangePath *)newSelection; -//- (void)expressionView:(MPExpressionView *)expressionView didChangeSelectionFromRangePath:(MPRangePath *)oldSelection toRangePath:(MPRangePath *)newSelection; -// -//- (BOOL)expressionView:(MPExpressionView *)expressionView shouldChangeSymbolsInRangePath:(MPRangePath *)rangePath replacementElements:(NSArray *)replacement; -//- (BOOL)expressionView:(MPExpressionView *)expressionView shouldInsertFunction:(MPFunction *)function replacingRangePath:(MPRangePath *)currentSelection; -//- (void)expressionViewDidChange:(MPExpressionView *)expressionView; -// -//- (BOOL)expressionViewShouldBeginEditing:(MPExpressionView *)expressionView; -//- (BOOL)expressionViewShouldEndEditing:(MPExpressionView *)expressionView; -//- (void)expressionViewDidBeginEditing:(MPExpressionView *)expressionView; -//- (void)expressionDidEndEditing:(MPExpressionView *)expressionView; -// -//// TODO: Errors... -//#pragma mark Evaluation -// -//- (BOOL)expressionViewShouldEvaluate:(MPExpressionView *)expressionView; -//- (void)expressionViewDidEvaluate:(MPExpressionView *)expressionView; -// -//@end \ No newline at end of file diff --git a/MathKit/MPExpressionView.m b/MathKit/MPExpressionView.m index a4317c4..f17463e 100644 --- a/MathKit/MPExpressionView.m +++ b/MathKit/MPExpressionView.m @@ -8,26 +8,25 @@ #import "MPExpressionView.h" -#import "MPExpression.h" -#import "MPExpression.h" -#import "MPParsedExpression.h" - -#import "MPPowerFunction.h" -#import "MPParenthesisFunction.h" -#import "MPFractionFunction.h" - -#import "MPToken.h" -#import "MPRangePath.h" -#import "MPMathRules.h" - #import "MPExpressionStorage.h" #import "MPExpressionLayout.h" -#import "MPFunctionLayout.h" #import "MPFunctionsViewController.h" +#import "MPFunctionLayout.h" +#import "MPFractionFunction.h" +#import "MPParenthesisFunction.h" +#import "MPPowerFunction.h" + +#import "MPParsedExpression.h" +#import "MPMathRules.h" +#import "MPToken.h" + +#import "MPRangePath.h" #import "NSIndexPath+MPAdditions.h" + + @interface MPExpressionView () @property (nonatomic, strong) NSButton *functionsButton; @@ -58,6 +57,7 @@ @interface MPExpressionView (MPSelectionHelper) + - (void)restartCaretTimer; - (void)updateCaret:(NSTimer *)timer; @@ -101,6 +101,7 @@ [self updateCaret:self.caretTimer]; } + - (void)updateCaret:(NSTimer *)timer { self.caretVisible = !self.caretVisible; @@ -111,6 +112,7 @@ [self setNeedsDisplayInRect:updatedRect]; } + - (NSRect)selectionRect { NSRect selectionRect = [self.expressionStorage.rootLayout boundingRectForRangePath:self.selection]; @@ -120,6 +122,7 @@ return selectionRect; } + - (NSIndexPath *)selectionToTheRightOf:(NSIndexPath *)selectionPath byExtendingSelection:(BOOL)extendingSelection selectWords:(BOOL)selectWords @@ -186,6 +189,7 @@ return [selectionPath indexPathByReplacingLastIndexWithIndex:locationInTarget]; } + - (NSIndexPath *)selectionToTheLeftOf:(NSIndexPath *)selectionPath byExtendingSelection:(BOOL)extendingSelection selectWords:(BOOL)selectWords @@ -257,6 +261,7 @@ return [selectionPath indexPathByReplacingLastIndexWithIndex:locationInTarget]; } + - (MPRangePath *)rangePathEnclosingAnchorPath:(NSIndexPath *)anchorPath newSelectionPath:(NSIndexPath *)newSelectionPath { @@ -312,9 +317,13 @@ #pragma mark - + + @implementation MPExpressionView #pragma mark Creation Methods + + - (id)initWithFrame:(NSRect)frame { self = [super initWithFrame:frame]; @@ -324,6 +333,7 @@ return self; } + - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; @@ -333,6 +343,7 @@ return self; } + - (void)initializeExpressionView { // Setup the Expression Storage @@ -348,6 +359,7 @@ [self restartCaretTimer]; } + - (void)initializeButtons { NSButton *functionsButton = self.functionsButton; @@ -368,6 +380,7 @@ constant:0]]; } + - (void)initializeErrorsViews { @@ -394,34 +407,42 @@ views:variableBindings]]; } + #pragma mark - NSView Configuration + + - (BOOL)acceptsFirstResponder { return YES; } + - (BOOL)canBecomeKeyView { return YES; } + - (BOOL)isOpaque { return YES; } + - (void)resetCursorRects { [self addCursorRect:self.bounds cursor:[NSCursor IBeamCursor]]; } + - (BOOL)becomeFirstResponder { [self restartCaretTimer]; return [super becomeFirstResponder]; } + - (BOOL)resignFirstResponder { [self.caretTimer invalidate]; @@ -430,12 +451,14 @@ return [super resignFirstResponder]; } + - (void)setFrame:(NSRect)frameRect { [self setNeedsLayout:YES]; [super setFrame:frameRect]; } + - (NSSize)intrinsicContentSize { NSSize size = self.expressionStorage.rootLayout.bounds.size; @@ -443,7 +466,10 @@ return size; } + #pragma mark - Properties + + - (void)setExpressionStorage:(MPExpressionStorage *)expressionStorage { _expressionStorage.expressionView = nil; @@ -452,6 +478,7 @@ [self invalidateIntrinsicContentSize]; } + - (void)setSelection:(MPRangePath *)selection { _selection = selection; @@ -459,6 +486,7 @@ self.needsDisplay = YES; } + - (void)setSyntaxErrors:(NSArray *)syntaxErrors { _syntaxErrors = syntaxErrors; @@ -491,12 +519,14 @@ } } + - (void)setMathError:(NSError *)mathError { _mathError = mathError; self.mathErrorTextField.stringValue = mathError != nil ? mathError.localizedDescription : @""; } + - (NSButton *)functionsButton { if (!_functionsButton) { @@ -520,6 +550,7 @@ return _functionsButton; } + - (NSPopUpButton *)syntaxErrorsPopUpButton { if (!_syntaxErrorsPopUpButton) { @@ -539,6 +570,7 @@ return _syntaxErrorsPopUpButton; } + - (NSTextField *)mathErrorTextField { if (!_mathErrorTextField) { @@ -555,6 +587,7 @@ return _mathErrorTextField; } + - (void)didSelectError:(NSPopUpButton *)sender { NSError *error = self.syntaxErrors[sender.indexOfSelectedItem-1]; @@ -564,7 +597,10 @@ self.selection = MPMakeRangePath(pathToExpression, errorRange.length); } + #pragma mark - Mouse Event Handling + + - (void)mouseDown:(NSEvent *)theEvent { NSPoint pointInView = [self convertPoint:theEvent.locationInWindow @@ -577,6 +613,7 @@ self.selection = MPMakeRangePath(selectionPath, 0); } + - (void)mouseDragged:(NSEvent *)theEvent { NSPoint pointInView = [self convertPoint:theEvent.locationInWindow @@ -590,7 +627,10 @@ newSelectionPath:mouseSelectionPath]; } + #pragma mark Key Event Handling + + - (void)keyDown:(NSEvent *)theEvent { NSString *characters = theEvent.characters; @@ -617,6 +657,15 @@ [allowedCharacters addCharactersInString:[NSString stringWithFormat:@"+-*= !%@", decimalSeparator]]; if (characters.length == 1 && [characters stringByTrimmingCharactersInSet:allowedCharacters].length == 0) { + if ([characters isEqualTo:@"i"]) { + if (self.selection.location.lastIndex > 0) { + id symbol = [self.expressionStorage symbolAtIndex:self.selection.location.lastIndex-1]; + if ([symbol isEqual:@"p"]) { + self.selection = MPMakeRangePath([self.selection.location indexPathByDecrementingLastIndex], self.selection.length+1); + characters = @"Ī€"; + } + } + } if ([characters isEqualToString:@"*"]) { characters = @"⋅"; } @@ -629,22 +678,28 @@ } } + #pragma mark - Actions + + - (void)switchToDegrees:(id)sender { [MPMathRules sharedRules].isUsingDegrees = YES; } + - (void)switchToRadians:(id)sender { [MPMathRules sharedRules].isUsingDegrees = NO; } + - (void)switchRadiansDegrees:(id)sender { [MPMathRules sharedRules].isUsingDegrees = ![MPMathRules sharedRules].isUsingDegrees; } + - (IBAction)toggleFunctionsPopover:(id)sender { if (self.functionsPopover == nil || self.functionsViewController == nil) { @@ -663,6 +718,7 @@ } } + - (void)insertFunction:(MPFunction *)function { [self.functionsPopover close]; @@ -678,8 +734,10 @@ self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:functionLayout.indexOfLeadingChild] indexPathByAddingIndex:0], 0); } + #pragma mark Editing Actions + - (void)insertParenthesisFunction:(id)sender { MPExpression *selectedElementsExpression = [self.expressionStorage subexpressionWithRangePath:self.selection @@ -698,6 +756,7 @@ self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:0] indexPathByAddingIndex:0], self.selection.length); } + - (void)insertClosingParenthesis { if (self.selection.length > 0) { @@ -721,6 +780,7 @@ } } + - (void)insertPowerFunction { MPExpression *selectedElementsExpression = [self.expressionStorage subexpressionWithRangePath:self.selection @@ -739,6 +799,7 @@ self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:0] indexPathByAddingIndex:0], self.selection.length); } + - (void)insertFractionFunction { if (self.selection.length == 0) { @@ -792,6 +853,7 @@ } } + - (void)insertNewline:(id)sender { if (self.target && self.action) { @@ -801,6 +863,7 @@ } } + - (void)deleteBackward:(id)sender { if (self.selection.length > 0) { @@ -853,6 +916,7 @@ } } + - (void)delete:(id)sender { [self.expressionStorage replaceItemsInRangePath:self.selection @@ -861,8 +925,10 @@ self.selection = MPMakeRangePath(self.selection.location, 0); } + #pragma mark Selection Actions + - (void)moveRight:(id)sender { if (self.selection.length > 0) { @@ -875,6 +941,7 @@ } } + - (void)moveLeft:(id)sender { if (self.selection.length > 0) { @@ -887,6 +954,7 @@ } } + - (void)moveWordRight:(id)sender { NSIndexPath *location = self.selection.maxRangePath; @@ -896,6 +964,7 @@ self.selection = MPMakeRangePath(newSelectionLocation, 0); } + - (void)moveWordLeft:(id)sender { NSIndexPath *location = self.selection.location; @@ -905,17 +974,20 @@ self.selection = MPMakeRangePath(newSelectionLocation, 0); } + - (void)moveToBeginningOfLine:(id)sender { self.selection = MPMakeRangePath([self.selection.location indexPathByReplacingLastIndexWithIndex:0], 0); } + - (void)moveToEndOfLine:(id)sender { MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]]; self.selection = MPMakeRangePath([self.selection.location indexPathByReplacingLastIndexWithIndex:targetExpression.countSymbols], 0); } + - (void)moveLeftAndModifySelection:(id)sender { if (self.selection.length == 0) { @@ -935,6 +1007,7 @@ self.selection = [self rangePathEnclosingAnchorPath:maxLocation newSelectionPath:location]; } + - (void)moveRightAndModifySelection:(id)sender { if (self.selection.length == 0) { @@ -955,6 +1028,7 @@ self.selection = MPMakeRangePath(location, maxLocation.lastIndex-location.lastIndex); } + - (void)moveWordRightAndModifySelection:(id)sender { if (self.selection.length == 0) { @@ -977,6 +1051,7 @@ self.selection = MPMakeRangePath(location, maxLocation.lastIndex-location.lastIndex); } + - (void)moveWordLeftAndModifySelection:(id)sender { if (self.selection.length == 0) { @@ -999,6 +1074,7 @@ self.selection = MPMakeRangePath(location, maxLocation.lastIndex-location.lastIndex); } + - (void)moveUp:(id)sender { NSIndexPath *targetExpressionPath = [self.selection.location indexPathByRemovingLastIndex]; @@ -1011,6 +1087,7 @@ } } + - (void)moveDown:(id)sender { NSIndexPath *targetExpressionPath = [self.selection.location indexPathByRemovingLastIndex]; @@ -1023,13 +1100,17 @@ } } + - (void)selectAll:(id)sender { NSIndexPath *location = [NSIndexPath indexPathWithIndex:0]; self.selection = MPMakeRangePath(location, self.expressionStorage.countSymbols); } + #pragma mark Drawing Methods + + - (void)drawRect:(NSRect)dirtyRect { // Draw the background @@ -1066,6 +1147,7 @@ #pragma mark - User Interface Validations + - (BOOL)validateUserInterfaceItem:(id)anItem { BOOL degrees = [MPMathRules sharedRules].isUsingDegrees; diff --git a/MathKit/MPFactorialTerm.h b/MathKit/MPFactorialTerm.h index ec2450c..aedc168 100644 --- a/MathKit/MPFactorialTerm.h +++ b/MathKit/MPFactorialTerm.h @@ -9,6 +9,12 @@ #import "MPTerm.h" +/*! + @header + This file contains the MPFactorialTerm class. + */ + + @class MPFactorialTerm; diff --git a/MathKit/MPFactorialTerm.m b/MathKit/MPFactorialTerm.m index 0c16c85..5b9c3f9 100644 --- a/MathKit/MPFactorialTerm.m +++ b/MathKit/MPFactorialTerm.m @@ -8,6 +8,8 @@ #import "MPFactorialTerm.h" + + @implementation MPFactorialTerm - (instancetype)initWithTerm:(MPTerm *)term @@ -20,6 +22,7 @@ return self; } + - (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error { NSDecimalNumber *value = [self.term evaluate:error]; diff --git a/MathKit/MPFractionFunction.h b/MathKit/MPFractionFunction.h index 83a71b8..e9cceac 100644 --- a/MathKit/MPFractionFunction.h +++ b/MathKit/MPFractionFunction.h @@ -9,6 +9,12 @@ #import "MPFunction.h" +/*! + @header + This file contains the MPFractionFunction class. + */ + + @class MPFractionFunction, MPExpression; diff --git a/MathKit/MPFractionFunction.m b/MathKit/MPFractionFunction.m index 76312e6..944c455 100644 --- a/MathKit/MPFractionFunction.m +++ b/MathKit/MPFractionFunction.m @@ -11,21 +11,26 @@ #import "MPFractionTerm.h" #import "MPExpression.h" + + @implementation MPFractionFunction MPFunctionAccessorImplementation(NominatorExpression, _nominatorExpression) MPFunctionAccessorImplementation(DenominatorExpression, _denominatorExpression) + - (NSArray *)childrenAccessors { return @[@"nominatorExpression", @"denominatorExpression"]; } + - (Class)functionTermClass { return [MPFractionTerm class]; } + - (NSString *)description { return [NSString stringWithFormat:@"%@ / %@", self.nominatorExpression, self.denominatorExpression]; diff --git a/MathKit/MPFractionFunctionLayout.h b/MathKit/MPFractionFunctionLayout.h index ec361b5..ce3e95b 100644 --- a/MathKit/MPFractionFunctionLayout.h +++ b/MathKit/MPFractionFunctionLayout.h @@ -9,12 +9,32 @@ #import "MPFunctionLayout.h" +/*! + @header + This file contains the MPFractionFunctionLayout class. + */ + + @class MPFractionFunctionLayout, MPFractionFunction; +/*! + @class MPFractionFunctionLayout + @abstract A fraction function layout displays a @link + //apple_ref/occ/cl/MPFractionFunction@/link. + + @discussion The nominator is displayed above the denominator. Between the two + children a horizontal bar is drawn. + */ @interface MPFractionFunctionLayout : MPFunctionLayout +/*! + @method fractionFunction + @abstract Returns the @link + //apple_ref/occ/cl/MPFractionFunction@/link represented by + the receiver. + */ - (MPFractionFunction *)fractionFunction; @end diff --git a/MathKit/MPFractionFunctionLayout.m b/MathKit/MPFractionFunctionLayout.m index 7683484..a0c3f4f 100644 --- a/MathKit/MPFractionFunctionLayout.m +++ b/MathKit/MPFractionFunctionLayout.m @@ -10,6 +10,7 @@ #import "MPFractionFunction.h" + #define kFractionFunctionLineWidth 1.0 #define kFractionFunctionHorizontalInset 2.0 #define kFractionFunctionNominatorOffset 0 @@ -17,6 +18,8 @@ #define MPFractionMiddle (CTFontGetCapHeight((CTFontRef)self.font) / 2) + + @implementation MPFractionFunctionLayout - (MPFractionFunction *)fractionFunction @@ -24,21 +27,25 @@ return (MPFractionFunction *)self.function; } + - (NSUInteger)indexOfChildBelowChildAtIndex:(NSUInteger)index { return 1; } + - (NSUInteger)indexOfChildAboveChildAtIndex:(NSUInteger)index { return 0; } + - (NSIndexSet *)indexesOfRemainingChildren { return [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]; } + - (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index { NSRect bounds = self.bounds; @@ -54,11 +61,13 @@ } } + - (BOOL)childAtIndexUsesSmallSize:(NSUInteger)index { return YES; } + - (NSIndexPath *)indexPathForLocalMousePoint:(NSPoint)point { if (point.x < self.bounds.size.width / 2) { @@ -68,6 +77,7 @@ } } + - (NSRect)generateBounds { NSRect nominatorBounds = [self childLayoutAtIndex:0].bounds; @@ -80,6 +90,7 @@ return bounds; } + - (void)draw { CGFloat y = MPFractionMiddle - kFractionFunctionLineWidth / 2; diff --git a/MathKit/MPFractionTerm.h b/MathKit/MPFractionTerm.h index 0c35590..f08c6ab 100644 --- a/MathKit/MPFractionTerm.h +++ b/MathKit/MPFractionTerm.h @@ -9,10 +9,24 @@ #import "MPFunctionTerm.h" +/*! + @header + This file contains the MPFractionTerm class. + */ + + @class MPFractionTerm; +/*! + @class MPFractionTerm + @abstract Represens a @link + //apple_ref/occ/cl/MPFractionFunction@/link. + + @discussion A fraction is evaluating by dividing the nominator by the + denominator. + */ @interface MPFractionTerm : MPFunctionTerm @end diff --git a/MathKit/MPFractionTerm.m b/MathKit/MPFractionTerm.m index a7c34b4..2e39fdb 100644 --- a/MathKit/MPFractionTerm.m +++ b/MathKit/MPFractionTerm.m @@ -10,6 +10,8 @@ #import "MPParsedExpression.h" + + @implementation MPFractionTerm - (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error diff --git a/MathKit/MPFunction+MPToken.h b/MathKit/MPFunction+MPToken.h index d567982..2d3339f 100644 --- a/MathKit/MPFunction+MPToken.h +++ b/MathKit/MPFunction+MPToken.h @@ -10,6 +10,12 @@ #import "MPToken.h" +/*! + @header + This file contains the MPFunction(MPToken) category. + */ + + /*! @category MPFunction (MPToken) diff --git a/MathKit/MPFunction.h b/MathKit/MPFunction.h index 3bd420f..f4bf663 100644 --- a/MathKit/MPFunction.h +++ b/MathKit/MPFunction.h @@ -11,6 +11,8 @@ /*! @header + This file contains the MPFunction class. + MPFunction is a half abstract class that is designed to be subclassed. Subclasses of the MPFunction class (subsequently called functions) need to regard the following guidelines: @@ -41,7 +43,6 @@ */ - /*! @define MPFunctionAccessorImplementation @abstract This macro implements the setter of a previously declared diff --git a/MathKit/MPFunction.m b/MathKit/MPFunction.m index a7a23f2..ba7f746 100644 --- a/MathKit/MPFunction.m +++ b/MathKit/MPFunction.m @@ -18,7 +18,6 @@ @implementation MPFunction - #pragma mark Creation Methods diff --git a/MathKit/MPFunctionLayout.h b/MathKit/MPFunctionLayout.h index e2b0933..4ff97c3 100644 --- a/MathKit/MPFunctionLayout.h +++ b/MathKit/MPFunctionLayout.h @@ -9,53 +9,334 @@ #import "MPLayout.h" +/*! + @header + This file contains the MPFunctionLayout class. + + The MPFunctionLayout class is an abstract class that implements + many of the methods from the layout system. Concrete subclasses implement the + actual layout of a specific function and its children. For that reason + MPFunctionLayout features a private cache. In that cache any + subclass can store objects so they do not need to be recreated everytime the + function is rendered. + */ + + @class MPFunctionLayout, MPFunction, MPExpressionLayout; +/*! + @class MPFunctionLayout + @abstract A function layout represents a @link + //apple_ref/occ/cl/MPFunction@/link. + + @discussion MPFunctionLayout is an abstract class that + implements many required methods of the layout system. For more + information about the layout system see the documentation on the + @link //apple_ref/occ/cl/MPLayout@/link class. + */ @interface MPFunctionLayout : MPLayout +/*! + @method functionLayoutForFunction:parent: + @abstract Creates a function layout that can draw the specified function. + + @discussion This method is the preferred whay to construct function layouts. + + @param function + The function to create a layout for. Must not be + nil. + + @param parent + The parent of the created function layout. May be + nil. + + @return A newly created function layout. + */ + (MPFunctionLayout *)functionLayoutForFunction:(MPFunction *)function parent:(MPExpressionLayout *)parent; -@property (readonly, nonatomic, weak) MPFunction *function; -@end - -@interface MPFunctionLayout (MPSubclassOverride) +/*! + @method initWithFunction:parent: + @abstract Initializes the receiver with the specified function and parent. + + @discussion This method should not be called direcly. It is however possible + do perform custom initialization in sublcasses by overriding this method. + + This method is the designated initializer of the + MPFunctionLayout class. + @param function + The function represented by the receiver. Must not be + nil. + + @param parent + The parent of the receiver or nil if it does not + have one. + + @return A newly initialized MPFunctionLayout instance. + */ - (instancetype)initWithFunction:(MPFunction *)function parent:(MPExpressionLayout *)parent; -#pragma mark Cache Methods + +/*! + @property function + @abstract The function represented by the receiver. + */ +@property (readonly, nonatomic, weak) MPFunction *function; + + +/*! + @method lineForPrivateCacheIndex:generator: + @abstract Returns the cached line for the specified private index. + + @discussion This is a convenience method that calls @link + //apple_ref/occ/instm/MPFunctionLayout/objectForPrivateCacheIndex:generator:@/link. + See the documentation on that method for details. + + @param index + The index of the line to retrieve. Private cache indexes start at 0. + + @param generator + A block that generates a line in case there is no cached one. + + @return The cached line or the newly generated one if there was no cache value. + */ - (CTLineRef)lineForPrivateCacheIndex:(NSUInteger)index generator:(CTLineRef (^)())generator; + + +/*! + @method objectForPrivateCacheIndex:generator: + @abstract Returns the cached value for the specified private index. + + @discussion If there is no object in the private cache for the specified + index the specified generator is invoked. The result + of the generator is then stored at the specified + index and returned. + + @param index + The index of the private cache item to retrieve. Private cache + indexes start at 0. + + @param generator + A block that generates an object in case there is no cached one. + + @return The cached object or the newly generated one if there was no + cache value. + */ - (id)objectForPrivateCacheIndex:(NSUInteger)index generator:(id (^)())generator; -// Should also implement accessor method for special function type: -// - (MPCustomFunction *)customFunction -// { -// return (MPCustomFunction *)self.function; -// } +@end -#pragma mark Size and Drawing Methods + + +/*! + @category MPFunctionLayout (MPSubclassOverride) + @abstract The methods defined in this category must be implemented by any + concrete subclass of MPFunctionLayout. + */ +@interface MPFunctionLayout (MPSubclassOverride) + +/* Apart from the methods below it is recommended (although not required) to + * implement a method in the following form: + * - (CustomFunctionType *)customFunction + * { + * return (CustomFunctionType *)self.function; + * } + */ + + +/*! + @method childAtIndexUsesSmallSize: + @abstract Determines whether the child expression at the specified index + should be drawn using the small font. + + @discussion This method may be overridden to change the default behaviour. + + @param index + The index of the child whose size is to be determined. + + @return YES if the receiver's child at the specified + index should use the small font, NO + otherwise. The default implementation returns NO. + */ - (BOOL)childAtIndexUsesSmallSize:(NSUInteger)index; // May be implemented -- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index; // To be implemented -- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point; -- (NSIndexPath *)indexPathForLocalMousePoint:(NSPoint)point; // To be implemented -- (NSRect)generateBounds; // To be implemented -- (void)draw; // To be implemented -// Specify the child that is used when the cursor enters the function from the left or right respectively -- (NSUInteger)indexOfLeadingChild; // To be implemented -- (NSUInteger)indexOfTrailingChild; // To be implemented -// The index of the child before (left) or after (right) the child at @c index. return NSNotFound if there is no child before or after @c index. -- (NSUInteger)indexOfChildBeforeChildAtIndex:(NSUInteger)index; // May be implemented -- (NSUInteger)indexOfChildAfterChildAtIndex:(NSUInteger)index; // May be implemented -- (NSUInteger)indexOfChildBelowChildAtIndex:(NSUInteger)index; // May be implemented -- (NSUInteger)indexOfChildAboveChildAtIndex:(NSUInteger)index; // May be implemented -- (NSIndexSet *)indexesOfRemainingChildren; // May be implemented +/*! + @method offsetOfChildLayoutAtIndex: + @abstract Implementation requirement from superclass. See @link + //apple_ref/occ/instm/MPLayout(MPSubclassImplement)/offsetOfChildLayoutAtIndex:@/link + for details. + */ +- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index; + + +/*! + @method indexPathForLocalMousePoint: + @abstract Performs hit testing. + + @discussion This method is called when the user selected a point in a + function that is not in any of its children. The return value of + this method identifies the position relative to the function + represented by the receiver where the caret is to be placed. + + This method must be implemented by subclasses. + + @param point + The location the user clicked at relative to the receiver. + + @return An index path identifying the new caret position relative to the + function represented by the receiver. Use an index path + containing a single index to place the caret before or after the + represented function (0 means before, 1 + means after). + */ +- (NSIndexPath *)indexPathForLocalMousePoint:(NSPoint)point; + + +/*! + @method generateBounds + @abstract Implementation requirement from superclass. See @link + //apple_ref/occ/instm/MPLayout(MPSubclassImplement)/generateBounds@/link + for details. + */ +- (NSRect)generateBounds; + + +/*! + @method draw + @abstract Implementation requirement from superclass. See @link + //apple_ref/occ/instm/MPLayout(MPSubclassImplement)/draw@/link + for details. + */ +- (void)draw; + + +/*! + @method indexOfLeadingChild + @abstract Identifies the index of the receiver's child that will get + selected when the cursor enters the function in the + direction of reading. + + @discussion You may implement this method do override the default behaviour. + + @return The index of the leading child. The default implementation + returns 0. + */ +- (NSUInteger)indexOfLeadingChild; + + +/*! + @method indexOfTrailingChild + @abstract Identifies the index of the receiver's child that will get + selected when the cursor enters the function in the + direction opposite to reading. + + @discussion You may implement this method do override the default behaviour. + + @return The index of the trailing child. The default implementation + returns 0. + */ +- (NSUInteger)indexOfTrailingChild; + + +/*! + @method indexOfChildBeforeChildAtIndex: + @abstract Identifies the index of the receiver's child that will get + selected when the cursor leaves the child at the specified + index in the direction opposite to reading. + + @discussion Return NSNotFound from this method if the function + should be left when the child at the specified index is + left. + + You may implement this method to override the default behaviour. + + @param index + The index of the child the caret left. + + @return The index of the child that is to be entered or + NSNotFound if there are no more children. The + default implementation returns NSNotFound. + */ +- (NSUInteger)indexOfChildBeforeChildAtIndex:(NSUInteger)index; + + +/*! + @method indexOfChildAfterChildAtIndex: + @abstract Identifies the index of the receiver's child that will get + selected when the cursor leaves the child at the specified + index in the direction of reading. + + @discussion Return NSNotFound from this method if the function + should be left when the child at the specified index is + left. + + You may implement this method to override the default behaviour. + + @param index + The index of the child the caret left. + + @return The index of the child that is to be entered or + NSNotFound if there are no more children. + */ +- (NSUInteger)indexOfChildAfterChildAtIndex:(NSUInteger)index; + + +/*! + @method indexOfChildBelowChildAtIndex: + @abstract Returns the index of the child that is graphically positioned + below the child at the specified index. + + @discussion If there are no other children below the one at the specified + index this method should return index. + + You may implement this method to override the default behaviour. + + @param index + The index of the child whose neighbor is to be determined. + + @return The index of the child directly below the one at the specified + index. The default implementation returns index. + */ +- (NSUInteger)indexOfChildBelowChildAtIndex:(NSUInteger)index; + + +/*! + @method indexOfChildAboveChildAtIndex: + @abstract Returns the index of the child that is graphically positioned + above the child at the specified index. + + @discussion If there are no other children above the one at the specified + index this method should return index. + + You may implement this method to override the default behaviour. + + @param index + The index of the child whose neighbor is to be determined. + + @return The index of the child directly above the one at the specified + index. The default implementation returns index. + */ +- (NSUInteger)indexOfChildAboveChildAtIndex:(NSUInteger)index; + + +/*! + @method indexesOfRemainingChildren + @abstract Returns the indexes of the receiver's children that should + replace the receiver if it is deleted. + + @discussion You may implement this method to override the default behaviour. + + @return The indexes of the children that replace the receiver when it is + deleted. The default implementation returns an empty index set. + */ +- (NSIndexSet *)indexesOfRemainingChildren; @end \ No newline at end of file diff --git a/MathKit/MPFunctionLayout.m b/MathKit/MPFunctionLayout.m index 5bac16d..822a34b 100644 --- a/MathKit/MPFunctionLayout.m +++ b/MathKit/MPFunctionLayout.m @@ -8,24 +8,28 @@ #import "MPFunctionLayout.h" -#import "MPExpression.h" +#import "MPExpressionLayout.h" + #import "MPFunction.h" -#import "MPSumFunction.h" +#import "MPFractionFunction.h" #import "MPParenthesisFunction.h" #import "MPPowerFunction.h" -#import "MPFractionFunction.h" +#import "MPSumFunction.h" -#import "MPExpressionLayout.h" -#import "MPSumFunctionLayout.h" +#import "MPFractionFunctionLayout.h" #import "MPParenthesisFunctionLayout.h" #import "MPPowerFunctionLayout.h" -#import "MPFractionFunctionLayout.h" +#import "MPSumFunctionLayout.h" #import "NSIndexPath+MPAdditions.h" + + @implementation MPFunctionLayout #pragma mark Creation Methods + + + (MPFunctionLayout *)functionLayoutForFunction:(MPFunction *)function parent:(MPExpressionLayout *)parent { @@ -43,6 +47,7 @@ parent:parent]; } + - (instancetype)initWithFunction:(MPFunction *)function parent:(MPExpressionLayout *)parent { @@ -53,7 +58,9 @@ return self; } + #pragma mark Cache Methods + - (CTLineRef)lineForPrivateCacheIndex:(NSUInteger)index generator:(CTLineRef (^)())generator { @@ -67,6 +74,7 @@ return (__bridge CTLineRef)(lineObject); } + - (id)objectForPrivateCacheIndex:(NSUInteger)index generator:(id (^)())generator { @@ -74,11 +82,13 @@ return [self cachableObjectForIndex:actualIndex generator:generator]; } + - (NSUInteger)numberOfChildren { return self.function.numberOfChildren; } + - (MPLayout *)childLayoutAtIndex:(NSUInteger)index { return [self cachableObjectForIndex:index generator:^id{ @@ -91,11 +101,13 @@ }]; } + - (BOOL)childAtIndexUsesSmallSize:(NSUInteger)index { return NO; } + - (NSIndexPath *)indexPathForMousePoint:(NSPoint)point { // A single index is used to communicate back wether the @@ -116,36 +128,43 @@ return [self indexPathForLocalMousePoint:point]; } + - (NSUInteger)indexOfLeadingChild { return 0; } + - (NSUInteger)indexOfTrailingChild { return 0; } + - (NSUInteger)indexOfChildBeforeChildAtIndex:(NSUInteger)index { return NSNotFound; } + - (NSUInteger)indexOfChildAfterChildAtIndex:(NSUInteger)index { return NSNotFound; } + - (NSUInteger)indexOfChildBelowChildAtIndex:(NSUInteger)index { return index; } + - (NSUInteger)indexOfChildAboveChildAtIndex:(NSUInteger)index { return index; } + - (NSIndexSet *)indexesOfRemainingChildren { return [NSIndexSet indexSet]; diff --git a/MathKit/MPFunctionTerm.h b/MathKit/MPFunctionTerm.h index 0d84429..20a1eee 100644 --- a/MathKit/MPFunctionTerm.h +++ b/MathKit/MPFunctionTerm.h @@ -8,6 +8,28 @@ #import "MPTerm.h" +/*! + @header + This file contains the MPFunctionTerm class. + */ + + +/*! + @define MPEvaluateExpression + @abstract Evaluates the receiver's child at the specified index and assigns + the evaluation result to a variable. + + @discussion If errors occur during evaluation this macro returns + nil. Also there has to exist a NSError *__autoreleasing * pointer called error. + + @param var + The name of the variable that will be declared in this macro. It + will be of the type NSDecimalNumber * and contain + the result of the evaluation. + + @param index + The index of the child to be evaluated. + */ #define MPEvaluateExpression(var, index) NSDecimalNumber *var = [[self expressionAtIndex:index] evaluate:error]; if (var == nil) return nil @@ -15,11 +37,62 @@ @class MPFunctionTerm, MPFunction, MPParsedExpression; +/*! + @class MPFunctionTerm + @abstract A function term represents the term of a generic function. + + @discussion This class is an abstract class that can not be evaluated. It is + the common superclass of all terms that represent @link + //apple_ref/occ/cl/MPFunction@/link instances. + */ @interface MPFunctionTerm : MPTerm +/*! + @method initWithFunction:errors: + @abstract Initializes a MPFunctionTerm from the specified + @link //apple_ref/occ/cl/MPFunction@/link instance. + + @discussion This method parses the children of the specified + function and stores the respective parsed + expressions. They are accessible using the @link + //apple_ref/occ/instm/MPFunctionTerm/expressionAtIndex:@/link + method. + + Whether the children should contain a variable definition is + determined using the @link + //apple_ref/occ/instm/MPFunction/expectsVariableDefinitionInChildAtIndex:@/link + + @param function + The function to initialize the receiver with. This method parses + the function's children. If any of them contain syntax errors + they are returned indirectly through the errors + parameter. + + @param errors + If any of the function's children contain syntax + errors they are returned indirectly through this parameter. In + that case the method returns nil. If there are no + errors the parameter is not modified. This parameter is never set + to an empty array. + + @return A new MPFunctionTerm instance. + */ - (instancetype)initWithFunction:(MPFunction *)function errors:(NSArray *__autoreleasing *)errors; /* designated initializer */ + +/*! + @method expressionAtIndex: + @abstract Returns a @link + //apple_ref/occ/cl/MPParsedExpression@/link instance that + represents the child at anIndex of the function that + is represented by the receiver. + + @param anIndex + The index of the child. + + @return A parsed expression that represents th + */ - (MPParsedExpression *)expressionAtIndex:(NSUInteger)anIndex; @end diff --git a/MathKit/MPFunctionTerm.m b/MathKit/MPFunctionTerm.m index bfc9249..050c702 100644 --- a/MathKit/MPFunctionTerm.m +++ b/MathKit/MPFunctionTerm.m @@ -9,10 +9,6 @@ #import "MPFunctionTerm.h" #import "MPFunction.h" -#import "MPExpression.h" -#import "MPParsedExpression.h" - -#import "MPToken.h" @@ -64,6 +60,7 @@ return self; } + - (MPParsedExpression *)expressionAtIndex:(NSUInteger)anIndex { return [self.parsedExpressions objectAtIndex:anIndex]; diff --git a/MathKit/MPFunctionsViewController.h b/MathKit/MPFunctionsViewController.h index 204bb57..0fc5c84 100644 --- a/MathKit/MPFunctionsViewController.h +++ b/MathKit/MPFunctionsViewController.h @@ -7,19 +7,74 @@ // +/*! + @header + This file contains the MPFunctionsViewController class. + */ + + @class MPFunctionsViewController, MPFunctionsCollectionView; +/*! + @class MPFunctionsViewController + @abstract Controls a view from which the user can select function to be + inserted into a @link + //apple_ref/occ/cl/MPExpressionView@/link. + + @discussion The view contain a NSCollectionView displaying the + prototypes an a NSTextField displaying a description + of the currently selected prototype. + */ @interface MPFunctionsViewController : NSViewController -- (id)init; +/*! + @property collectionView + @abstract The NSCollectionView that displays the function + prototypes. + */ +@property (weak) IBOutlet NSCollectionView *collectionView; -@property (weak) IBOutlet MPFunctionsCollectionView *collectionView; + +/*! + @property functionPrototypes + @abstract Contains the @link + //apple_ref/occ/cl/MPFunction@/link objects that get + inserted into an expression if selected. + + @discussion Every object in the array must be an @link + //apple_ref/occ/cl/MPFunction@/link instance. + */ @property (nonatomic, strong) NSArray *functionPrototypes; + + +/*! + @property currentDescription + @abstract The string that describes the function prototype that is + currently selected. + + @discussion If this string is empty a placeholder value will be displayed. + */ @property (nonatomic, strong) NSString *currentDescription; + +/*! + @property target + @abstract The target object to receive action messages from the receiver. + */ @property (nonatomic, weak) id target; -@property (nonatomic) SEL action; // 1 argument: The function to insert + + +/*! + @property action + @abstract The receiver's action method to the specified selector. + + @discussion The action method is invoked when the user selects one of the + function prototypes in the collection view. The prototype that + was selected is passed as the one and only argument to + the specified selector. + */ +@property (nonatomic) SEL action; @end diff --git a/MathKit/MPFunctionsViewController.m b/MathKit/MPFunctionsViewController.m index 0b21c93..a60a730 100644 --- a/MathKit/MPFunctionsViewController.m +++ b/MathKit/MPFunctionsViewController.m @@ -8,18 +8,21 @@ #import "MPFunctionsViewController.h" -#import "MPExpression.h" -#import "NSString+MPExpressionElement.h" #import "MPFunction.h" -#import "MPSumFunction.h" +#import "MPFractionFunction.h" #import "MPParenthesisFunction.h" #import "MPPowerFunction.h" -#import "MPFractionFunction.h" +#import "MPSumFunction.h" #import "MPFunctionLayout.h" +#import "NSString+MPExpressionElement.h" + + + @class MPFunctionsCollectionView, MPFunctionTemplateView, MPFunctionTemplateItem; + @interface MPFunctionsCollectionView : NSCollectionView @property (nonatomic, weak) MPFunctionTemplateItem *hoverItem; @@ -30,6 +33,7 @@ @end + @interface MPFunctionTemplateView : NSView @property (nonatomic, strong) MPFunction *functionTemplate; @@ -42,16 +46,20 @@ @interface MPFunctionTemplateItem : NSCollectionViewItem + @property (nonatomic, copy) NSString *templateName; + @end + @implementation MPFunctionsCollectionView - (void)mouseDown:(NSEvent *)theEvent { } + - (void)mouseUp:(NSEvent *)theEvent { NSPoint pointInView = [self convertPoint:theEvent.locationInWindow @@ -87,25 +95,30 @@ [super updateTrackingAreas]; } + - (void)mouseEntered:(NSEvent *)theEvent { self.mouseOver = YES; self.needsDisplay = YES; } + - (void)mouseExited:(NSEvent *)theEvent { self.mouseOver = NO; self.needsDisplay = YES; } + - (void)setFunctionTemplate:(MPFunction *)functionTemplate { _functionTemplate = functionTemplate; _functionTemplateLayout = nil; } + @synthesize functionTemplateLayout = _functionTemplateLayout; + - (MPFunctionLayout *)functionTemplateLayout { @@ -117,16 +130,19 @@ return _functionTemplateLayout; } + - (NSSize)intrinsicContentSize { return [self.functionTemplateLayout bounds].size; } + - (BOOL)isOpaque { return NO; } + - (void)drawRect:(NSRect)dirtyRect { if (self.mouseOver) { @@ -150,11 +166,13 @@ static void *MPFunctionTemplateViewMouseOverContext = @"MPFunctionTemplateViewMouseOverContext"; + - (id)awakeAfterUsingCoder:(NSCoder *)aDecoder { return [super awakeAfterUsingCoder:aDecoder]; } + - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change @@ -171,6 +189,7 @@ static void *MPFunctionTemplateViewMouseOverContext = @"MPFunctionTemplateViewMo } } + - (void)setRepresentedObject:(id)representedObject { MPFunctionTemplateView *view = (MPFunctionTemplateView *)self.view; @@ -191,6 +210,8 @@ static void *MPFunctionTemplateViewMouseOverContext = @"MPFunctionTemplateViewMo @end + + @implementation MPFunctionsViewController - (id)init @@ -199,6 +220,7 @@ static void *MPFunctionTemplateViewMouseOverContext = @"MPFunctionTemplateViewMo bundle:[NSBundle bundleForClass:[self class]]]; } + - (void)awakeFromNib { MPFunction *sumFunction = [[MPSumFunction alloc] init]; @@ -227,40 +249,45 @@ static void *MPFunctionTemplateViewMouseOverContext = @"MPFunctionTemplateViewMo forKeyPath:@"hoverItem" options:0 context:MPCollectionViewHoverItemChangeContext]; - self.collectionView.target = self.target; - self.collectionView.action = self.action; + ((MPFunctionsCollectionView *)self.collectionView).target = self.target; + ((MPFunctionsCollectionView *)self.collectionView).action = self.action; } + static void *MPCollectionViewHoverItemChangeContext = @"MPCollectionViewHoverItemChangeContext"; + - (void)setView:(NSView *)view { [super setView:view]; } + - (void)setTarget:(id)target { _target = target; if (self.collectionView) { - self.collectionView.target = target; + ((MPFunctionsCollectionView *)self.collectionView).target = target; } } + - (void)setAction:(SEL)action { _action = action; if (self.collectionView) { - self.collectionView.action = action; + ((MPFunctionsCollectionView *)self.collectionView).action = action; } } + - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context == MPCollectionViewHoverItemChangeContext) { - self.currentDescription = self.collectionView.hoverItem.templateName; + self.currentDescription = ((MPFunctionsCollectionView *)self.collectionView).hoverItem.templateName; } else { [super observeValueForKeyPath:keyPath ofObject:object diff --git a/MathKit/MPLayout.h b/MathKit/MPLayout.h index fff345e..8c85be6 100644 --- a/MathKit/MPLayout.h +++ b/MathKit/MPLayout.h @@ -6,12 +6,82 @@ // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // + +/*! + @header + This file contains the MPLayout class. + + The MPLayout class renders an expression to be displayed in an + @link //apple_ref/occ/cl/MPExpressionView@/link. There are multiple + subclasses of MPLayout the most important of which are @link + //apple_ref/occ/cl/MPExpressionLayout@/link and @link + //apple_ref/occ/cl/MPFunctionLayout@/link for rendering expressions and + functions respectively. + + Both types of layouts can occur in two different sizes: normal and small. The + root expression ueses the normal size. Depending on the concrete subclass of + @link //apple_ref/occ/cl/MPFunctionLayout@/link that is used a + function's children (and any subsequent children) may be rendered using the + small size. Also there are two different fonts available for layouts: a normal + and a special font. The special font should only be used to emphasize special + content that uses the normal font. + + If an expression is empty an empty box is rendered instead. That box (called the + empty box uses the font metrics of the empty expression it represents. + That means it uses different sizes depending on whether the expression uses a + small or normal sized font. Also it is drawn a little smaller than the actual + box's height is because it looks nicer. Depending on your needs you can query + the metrics of the empty box using the kMPEmptyBox... or + kMPEmptyBoxDrawing... macros. These macros must not be used outside + of the MPLayout class or any of it subclasses. + */ + + +/*! + @define kMPEmptyBoxWidth + @abstract The width of the empty box. + */ #define kMPEmptyBoxWidth (self.usesSmallSize ? 2.0 : 3.0) + + +/*! + @define kMPEmptyBoxHeight + @abstract The actual height of the empty box. + */ #define kMPEmptyBoxHeight (CTFontGetDescent((CTFontRef)self.font) + CTFontGetAscent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font)) + + +/*! + @define kMPEmptyBoxYOrigin + @abstract The actual vertical origin of the empty box. + + @discussion The vertical origin is a negative value to be compliant with the + metrics of strings. + */ #define kMPEmptyBoxYOrigin (-(CTFontGetDescent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font))) + +/*! + @define kMPEmptyBoxDrawingWidth + @abstract The with the empty box is rendered with. + */ #define kMPEmptyBoxDrawingWidth kMPEmptyBoxWidth + + +/*! + @define kMPEmptyBoxDrawingHeight + @abstract The height the empty box is drawn with. + */ #define kMPEmptyBoxDrawingHeight (CTFontGetDescent((CTFontRef)self.font) + CTFontGetAscent((CTFontRef)self.font)) + + +/*! + @define kMPEmptyBoxDrawingYOrigin + @abstract The vertical origin the empty box is drawn at. + + @discussion The vertical origin is a negative value to be compliant with the + metrics of strings. + */ #define kMPEmptyBoxDrawingYOrigin (-(CTFontGetDescent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font)/2)) @@ -19,58 +89,548 @@ @class MPLayout, MPRangePath; +/*! + @class MPLayout + @abstract This is an abstract class that defines the basic methods that + must be implemented by other layout classes. It also defines some + useful helper methods. + + @discussion The MPLayout class maintains a generic cache. It is + used to store child layouts and actual cache information. + + Every layout instance represents either an expression or a + function. The instance's children represent the children of the + represented object. + */ @interface MPLayout : NSObject #pragma mark Creation Methods +/*! + @methodgroup Creation Methods + */ + + +/*! + @method init + @abstract Initializes the receiver. + + @discussion This method should only be used to initialize the expression + layout that renders the root layout. + + This method is the designated initializer of the + MPLayout class. + + @return A newly initialized MPLayout instance. + */ - (instancetype)init; + + +/*! + @method initWithParent: + @abstract Initializes the receiver with the specified parent. + + @discussion This method should generally be used when initializing layout + instances. In most cases it should be called at some point in the + implementation of the @link + //apple_ref/occ/instm/MPLayout(MPSubclassImplement)/childLayoutAtIndex:@/link + method. + + @param parent + The receiver's parent. + + @return A newly initialized MPLayout instance. + */ - (instancetype)initWithParent:(MPLayout *)parent; -#pragma mark Properties -- (NSFont *)normalFontWithSize:(CGFloat)size; -- (NSFont *)specialFontWithSize:(CGFloat)size; -- (CGFloat)contextInferredFontSize; -- (CGFloat)normalFontSize; -- (CGFloat)smallFontSize; +#pragma mark Properties +/*! + @methodgroup Properties + */ + + +/*! + @method font + @abstract Returns the receiver's context inferred font. + + @discussion The context inferred font is the normal font using the context + inferred font size. Normally this method is used for any textual + content of a layout. + + @return The font the receiver should use for rendering textual content. + */ - (NSFont *)font; + +/*! + @method normalFontWithSize: + @abstract Returns a NSFont object that represents the normal + font in the specified size. + + @discussion Instead of this method you want to use @link + //apple_ref/occ/instm/MPLayout/font@/link instead in most + cases. + + @param size + The size of the font. + + @return A NSFont object that can be used to render textual + content of the receiver in the specified size. + */ +- (NSFont *)normalFontWithSize:(CGFloat)size; + + +/*! + @method specialFontWithSize: + @abstract Returns a NSFont object that represents the special + font in the specified size. + + @discussion Use fonts returned by this method only if you need to emphasize + text that is surrounded by other text that uses the normal font. + + @param size + The size of the font. + + @return A NSFont object that can be used to render special + textual content of the receiver in the specified + size. + */ +- (NSFont *)specialFontWithSize:(CGFloat)size; + + +/*! + @method contextInferredFontSize + @abstract Returns the appropriate font size for the receiver based on the + context it will be rendered in. + */ +- (CGFloat)contextInferredFontSize; + + +/*! + @method normalFontSize + @abstract Returns the font size that is used in the root expression. + */ +- (CGFloat)normalFontSize; + + +/*! + @method smallFontSize + @abstract Returns the font size that is used in expressions that are using + the small size. + */ +- (CGFloat)smallFontSize; + + #pragma mark Cache Tree +/*! + @methodgroup Cache Tree + */ + + +/*! + @property parent + @abstract The receiver's parent layout. + + @discussion The parent layout is most likely the one that created the + receiver and is responsible for setting its attributes + appropriately based on the rendering context. + */ @property (readonly, nonatomic, weak) MPLayout *parent; + #pragma mark Cache Methods -// Querying Caches +/*! + @methodgroup Cache Methods + */ + + +/*! + @method chacableObjectForIndex:generator: + @abstract Returns the cached object for the specified index or + creates one if it does not exist. + + @discussion This method only returns nil if the + generator returns nil. + + @param index + The index of the cached object to retrieve. + + @param generator + This block is executed if there is no cached object at + index. The result of this block is then cached at + the index. + + @return The cached object for index or the result of the + generator if there is no cached object. + */ - (id)cachableObjectForIndex:(NSUInteger)index generator:(id(^)())generator; -// Clearing Caches + +/*! + @method clearCacheInRange:replacementLength: + @abstract Removes the objects in the specified from the cache and moves all + objects at subsequent indexes. + + @discussion The objects at subsequent indexes are moved + replacementLength - range.length places. If + replacementLength is greater than + range.length they are moved to smaller indexes. + + @param range + The range of objects to be removed. + + @param replacementLength + The number of indexes replacing the ones specified by + range. + */ - (void)clearCacheInRange:(NSRange)range replacementLength:(NSUInteger)replacementLength; + + +/*! + @method invalidate + @abstract Invalidates the receiver. + + @discussion Invalidating the receiver causes him to discard all cached + information about itself and send a invalidate + message to its parent. The cached information about the + receiver's children is not discarded. Use @link + //apple_ref/occ/instm/MPLayout/clearCacheInRange:replacementLength:@/link + for that purpose. + */ - (void)invalidate; + +/*! + @method childLayoutAtIndexPath: + @abstract Returns the child layout at the specified index path. + + @discussion If the indexPath is empty the receiver is returned. + If the child layout the index path specifies is not yet created + this method should do so and add it to the cache of the + respective parent layout. + + @param indexPath + The index path of the child. + + @return The child at the specified index path. + */ - (MPLayout *)childLayoutAtIndexPath:(NSIndexPath *)indexPath; -#pragma mark Calculation and Drawing Methods + +#pragma mark Rendering Methods +/*! + @methodgroup Rendering Methods + */ + + +/*! + @method createLineForString: + @abstract Renders the specified string. + + @discussion This method uses the normal font and the context inferred font + size to render the string. In most cases this is the appropriate + method for rendering textual content in a layout. + + @param aString + The string to be rendered. + + @return A CTLineRef representing the rendered string. + */ - (CTLineRef)createLineForString:(NSString *)aString; + + +/*! + @method createLineForString:emphasize: + @abstract Renders the specified string. + + @discussion This method uses either the normal or the special font depending + on the emphasize flag. It uses the context inferred + font size. + + @param aString + The string to be rendered. + + @param emphasize + A flag indicating whether the special font should be used. + + @return A CTLineRef representing the rendered string. + */ - (CTLineRef)createLineForString:(NSString *)aString emphasize:(BOOL)emphasize; + + +/*! + @method createLineForString:usingFont: + @abstract Renders the specified string in the specified font. + + @discussion In most cases you do should prefer @link + //apple_ref/occ/instm/MPLayout/createLineForString:@/link + or @link + //apple_ref/occ/instm/MPLayout/createLineForString:emphasize:@/link + over this method. + + @param aString + The string to be rendered. + + @param font + The font to render aString in. + + @return A CTLineRef representing the rendered string. + */ - (CTLineRef)createLineForString:(NSString *)aString usingFont:(NSFont *)font; + +/*! + @property flipped + @abstract This property indicates whether the receiver uses a flipped + layout. + + @discussion The value of this property should be the same in the receiver, + its parent and all of its children. + */ @property (nonatomic, getter = isFlipped) BOOL flipped; + + +/*! + @property usesSmallSize + @abstract This property indicates whether the receiver uses the small font + size. + + @discussion This property is used to determine the @link + //apple_ref/occ/instm/MPLayout/contextInferredFontSize@/link. + If this value is YES all of the receiver's children + should also use the small size. If not the children may or may + not (depending on the actual layout implementation) use the small + size. + */ @property (nonatomic) BOOL usesSmallSize; + + +/*! + @method bounds + @abstract Returns the receiver's bounds. + + @discussion If the receiver has cached bounds it will return the cached + value. The cached value can be removed using the @link + //apple_ref/occ/instm/MPLayout/invalidate@/link method. If + there is no cached value this method will generate a new cached + value by calling the @link + //apple_ref/occ/instm/MPLayout(MPSubclassImplement)/generateBounds@/link + method. + + @return The receiver's bounds. + */ - (NSRect)bounds; + +/*! + @method boundingRectForRangePath: + @abstract Returns the rect relative to the origin of the receiver's bounds + that contains the symbols identified by the specified range path. + + @discussion Use this method to determine where the user's selection should be + drawn at. + + @param rangePath + The range path whose bounds are needed. + + @return The rectangle containing the symbols identified by + rangePath. + */ - (NSRect)boundingRectForRangePath:(MPRangePath *)rangePath; /* if rangePath.length is 0 the returned rect will have a width of 0 */ + +/*! + @method drawAtPoint: + @abstract Draws the receiver at the specified point. + + @discussion If the receiver returns NO from its @link + //apple_ref/occ/instm/MPLayout(MPSubclassImplement)/drawsChildrenManually@/link + method this method also draws every single child of the receiver. + The location the children are drawn at is determined by the + @link + //apple_ref/occ/instm/MPLayout(MPSubclassImplement)/offsetOfChildLayoutAtIndex:@/link + method. + + @param point + The point the receiver is to be drawn at. + */ - (void)drawAtPoint:(NSPoint)point; @end + + +/*! + @category MPLayout (MPSubclassImplement) + @abstract The methods in this category must be implemented by any concrete + subclass of MPLayout. + */ @interface MPLayout (MPSubclassImplement) + +/*! + @method numberOfChildren + @abstract Returns the number of sublayouts of the receiver. + + @discussion This method must be implemented by subclasses. + */ - (NSUInteger)numberOfChildren; + + +/*! + @method drawsChildrenManually + @abstract Returns whether the receiver wants to take over responsibility of + drawing its children. + + @discussion If you return YES from this method + MPLayout does not draw the receiver's children. The + receiver's //apple_ref/occ/intfm/MPLayout/draw + method has to implement that functionality. If you return + NO from this method the receiver must not draw its + children in the //apple_ref/occ/intfm/MPLayout/draw + method. + + This method may be implemented by subclasses to opt in and change + the default behaviour. + + @return YES if the receiver draws its children + automatically, NO otherwise. The default + implementation returns NO. + */ - (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 + + +/*! + @method childLayoutAtIndex: + @abstract Returns the receiver's child at the specified index. + + @discussion As long as the specified index does not exceed the + receiver's number of children, this method should always return a + value based on the following criteria: + 1. If the receiver represents a @link + //apple_ref/occ/cl/MPFunction@/link instance it should + always return a valid MPLayout instance. + 2. If the receiver represents a @link + //apple_ref/occ/cl/MPExpression@/link instance it + returns only a valid MPLayout instance if the + represented expression's element at index is a + function. + + This method should cache any generated child layout and use the + cache whenever possible in order to reduce resources needed for + rendering. + + This method must be implemented by subclasses. + + @param index + The index of the requested child layout. + + @return A MPLayout instance representing the respective + child of the object that is represented by the receiver, or + nil if the child represents a string element in an + expression. + */ +- (MPLayout *)childLayoutAtIndex:(NSUInteger)index; + + +/*! + @method generateBounds + @abstract Calculates the receiever's bounds. + + @discussion This method should not use any cached values. Caching of the + calculated bounds is done automatically by the + MPLayout class. This method is only called if the + receiving MPLayout instance has been invalidated and + the receiver's bounds have been requested. + + This method must be implemented by subclasses. + + @return The receiver's bounds. + */ +- (NSRect)generateBounds; + + +/*! + @method boundingRectForRange: + @abstract Returns the rectangle that encloses the symbols in the specified + range. + + @discussion This method is called to calculate the rectangle that encloses + the user's selection. If the specified range has a + length of 0 the returned rectangle should be the + bounds of the caret (which has a width of 0). The + height and y-origin of the returned rectangle should be equal to + the respective values of the receiver. + + This method must be implemented by subclasses. + + @param range + The range of the selection whose bounds should be calculated. + + @return A rectangle enclosing the user's selection. + */ +- (NSRect)boundingRectForRange:(NSRange)range; + + +/*! + @method offsetOfChildLayoutAtIndex: + @abstract Returns the location of the child layout at the specified index. + + @discussion This method must be implemented by subclasses even if the + subclass returns YES from the @link + //apple_ref/occ/instm/MPLayout(MPSubclassImplement)/drawsChildrenManually@/link + method. + + @param index + The index of the child whose location is to be calculated. + + @return The location of the child layout at the specified index relative + to the receiver. + */ - (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index; + + +/*! + @method indexPathForMousePoint: + @abstract Performs hit testing. + + @discussion This method tests the specified point against all of + its children and returns the index path (relative to the index + path of the receiver) that identifies the location inside the + expression tree the user selected. The specified point must be + inside of the receiver and must be specified relatively to the + receiver. If the specified point is not inside any of the + receiver's children but in the receiver itself the returned index + path must contain one index specifiying the location in the + receiver. + + This method must be implemented by subclasses. + + @param point + The point inside the receiver that is to be tested. + + @return The index path that points to the point the user selected. All + indexes in the path except for the last one are specified in the + element reference frame. The last index is specified in the + symbol reference frame. + */ - (NSIndexPath *)indexPathForMousePoint:(NSPoint)point; -- (void)draw; // To be implemented + + +/*! + @method draw + @abstract Draws the receiver. + + @discussion If the receiver returns YES from its @link + //apple_ref/occ/instm/MPLayout(MPSubclassImplement)/drawsChildrenManually@/link + method the implementation of this method must also draw the + receiver's children. + + This method must be implemented by subclasses. If the receiver + does not have anything to draw except for its children you should + implement this method with an empty method body. + */ +- (void)draw; + @end \ No newline at end of file diff --git a/MathKit/MPLayout.m b/MathKit/MPLayout.m index 16da726..e101604 100644 --- a/MathKit/MPLayout.m +++ b/MathKit/MPLayout.m @@ -11,20 +11,25 @@ #import "MPRangePath.h" #import "NSIndexPath+MPAdditions.h" + + @interface MPLayout () -// Querying and Storing Caches - (BOOL)hasCacheForElementAtIndex:(NSUInteger)index; - (void)ensureCacheSizeForIndex:(NSUInteger)index; @end + + @implementation MPLayout { NSMutableArray *_cache; NSRect _cachedBounds; } #pragma mark Creation Methods + + - (id)init { self = [super init]; @@ -35,6 +40,7 @@ return self; } + - (instancetype)initWithParent:(MPLayout *)parent { self = [self init]; @@ -44,41 +50,51 @@ return self; } + #pragma mark Properties + + - (NSFont *)normalFontWithSize:(CGFloat)size { return [NSFont fontWithName:@"CMU Serif" size:size]; } + - (NSFont *)specialFontWithSize:(CGFloat)size { return [NSFont fontWithName:@"CMU Serif Italic" size:size]; } + - (CGFloat)contextInferredFontSize { return self.usesSmallSize ? self.smallFontSize : self.normalFontSize; } + - (CGFloat)normalFontSize { return 18.0; } + - (CGFloat)smallFontSize { return 12.0; } + - (NSFont *)font { return [self normalFontWithSize:self.contextInferredFontSize]; } + #pragma mark Cache Tree -// Querying and Storing Caches + + - (BOOL)hasCacheForElementAtIndex:(NSUInteger)index { if (index >= _cache.count) { @@ -87,6 +103,7 @@ return _cache[index] != [NSNull null]; } + - (id)cachableObjectForIndex:(NSUInteger)index generator:(id (^)())generator { @@ -99,6 +116,7 @@ return object; } + - (void)ensureCacheSizeForIndex:(NSUInteger)index { while (index >= _cache.count) { @@ -106,7 +124,7 @@ } } -// Clearing Caches + - (void)clearCacheInRange:(NSRange)range replacementLength:(NSUInteger)replacementLength { @@ -119,12 +137,14 @@ [self invalidate]; } + - (void)invalidate { _cachedBounds = NSZeroRect; [self.parent invalidate]; } + - (MPLayout *)childLayoutAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.length == 0) { @@ -134,19 +154,24 @@ return [child childLayoutAtIndexPath:[indexPath indexPathByRemovingFirstIndex]]; } + #pragma mark Calculation and Drawing Methods + + - (CTLineRef)createLineForString:(NSString *)aString { return [self createLineForString:aString usingFont:self.font]; } + - (CTLineRef)createLineForString:(NSString *)aString emphasize:(BOOL)emphasize { return [self createLineForString:aString usingFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]]; } + - (CTLineRef)createLineForString:(NSString *)aString usingFont:(NSFont *)font { @@ -158,6 +183,7 @@ return line; } + - (NSRect)bounds { if (NSEqualRects(_cachedBounds, NSZeroRect)) { @@ -166,6 +192,7 @@ return _cachedBounds; } + - (NSRect)boundingRectForRangePath:(MPRangePath *)rangePath { if (rangePath.location.length == 1) { @@ -180,11 +207,13 @@ return bounds; } + - (BOOL)drawsChildrenManually { return NO; } + - (void)drawAtPoint:(NSPoint)point { NSAffineTransform *transform = [NSAffineTransform transform]; diff --git a/MathKit/MPMathRules.h b/MathKit/MPMathRules.h index 78afc0d..5bc2474 100644 --- a/MathKit/MPMathRules.h +++ b/MathKit/MPMathRules.h @@ -7,25 +7,49 @@ // +/*! + @header + This file contains the MPMathRules class. + */ -FOUNDATION_EXPORT NSString *MPMathRulesMaximumOperatorChainLengthKey; -FOUNDATION_EXPORT NSString *MPMathRulesMaximumOperatorChainLengthInMultiplicationKey; + +/*! + @const MPMathRulesIsUsingDegreesKey + @abstract Predefined key that is used to store the properties of the + MPMathRules class in the NSUserDefaults + database. + */ FOUNDATION_EXPORT NSString *MPMathRulesIsUsingDegreesKey; + @class MPMathRules; +/*! + @class MPMathRules + @abstract The math rules class stores global settings concerning + mathematical interpretations + + @discussion The MPMathRules class is a singletone class. There + can only exist one instance (the shared instance) at any time. + */ @interface MPMathRules : NSObject +/*! + @method sharedRules + @abstract Returnes the shared MPMathRules instance. + */ + (MPMathRules *)sharedRules; -@property (nonatomic, getter = isUsingUserDefaultValues) BOOL usingUserDefaultValues; - -// 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 isUsingDegrees + @abstract Specifies whether trigonometric functions use degree values. + + @discussion If set to NO radians are used instead. The default + value is NO. + */ @property (nonatomic) BOOL isUsingDegrees; @end diff --git a/MathKit/MPMathRules.m b/MathKit/MPMathRules.m index e8654a7..598152d 100644 --- a/MathKit/MPMathRules.m +++ b/MathKit/MPMathRules.m @@ -8,14 +8,16 @@ #import "MPMathRules.h" -NSString *MPMathRulesMaximumOperatorChainLengthKey = @"MPMathRulesMaximumOperatorChainLengthKey"; -NSString *MPMathRulesMaximumOperatorChainLengthInMultiplicationKey = @"MPMathRulesMaximumOperatorChainLengthInMultiplicationKey"; + NSString *MPMathRulesIsUsingDegreesKey = @"MPMathRulesIsUsingDegreesKey"; + + @implementation MPMathRules static MPMathRules *sharedRules; + + (MPMathRules *)sharedRules { if (!sharedRules) { @@ -24,6 +26,7 @@ static MPMathRules *sharedRules; return sharedRules; } + - (instancetype)init { if (sharedRules) { @@ -31,53 +34,21 @@ static MPMathRules *sharedRules; } self = [super init]; if (self) { - _usingUserDefaultValues = YES; - NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; - NSNumber *userDefaultsMaximumOperatorChainLength = [userDefaults objectForKey:MPMathRulesMaximumOperatorChainLengthKey]; - NSNumber *userDefaultsMaximumOperatorChainLengthInMultiplication = [userDefaults objectForKey:MPMathRulesMaximumOperatorChainLengthInMultiplicationKey]; - NSNumber *userDefaultsIsUsingDegrees = [userDefaults objectForKey:MPMathRulesIsUsingDegreesKey]; - - _maximumOperatorChainLength = userDefaultsMaximumOperatorChainLength != nil ? userDefaultsMaximumOperatorChainLength.unsignedIntegerValue : 2; - _maximumOperatorChainLengthInMultiplication = userDefaultsMaximumOperatorChainLengthInMultiplication != nil ? userDefaultsMaximumOperatorChainLengthInMultiplication.unsignedIntegerValue : 1; - _isUsingDegrees = userDefaultsIsUsingDegrees.boolValue; } return self; } -- (void)setUsingUserDefaultValues:(BOOL)usingUserDefaultValues + +- (BOOL)isUsingDegrees { - _usingUserDefaultValues = usingUserDefaultValues; - // Save the current values - self.maximumOperatorChainLength = self.maximumOperatorChainLength; - self.maximumOperatorChainLengthInMultiplication = self.maximumOperatorChainLengthInMultiplication; - self.isUsingDegrees = self.isUsingDegrees; + return [[NSUserDefaults standardUserDefaults] boolForKey:MPMathRulesIsUsingDegreesKey]; } -- (void)setMaximumOperatorChainLength:(NSUInteger)maximumOperatorChainLength -{ - _maximumOperatorChainLength = maximumOperatorChainLength; - if (self.isUsingUserDefaultValues) { - [[NSUserDefaults standardUserDefaults] setObject:@(maximumOperatorChainLength) - forKey:MPMathRulesMaximumOperatorChainLengthKey]; - } -} - -- (void)setMaximumOperatorChainLengthInMultiplication:(NSUInteger)maximumOperatorChainLengthInMultiplication -{ - _maximumOperatorChainLengthInMultiplication = maximumOperatorChainLengthInMultiplication; - if (self.isUsingUserDefaultValues) { - [[NSUserDefaults standardUserDefaults] setObject:@(maximumOperatorChainLengthInMultiplication) - forKey:MPMathRulesMaximumOperatorChainLengthInMultiplicationKey]; - } -} - (void)setIsUsingDegrees:(BOOL)isUsingDegrees { - _isUsingDegrees = isUsingDegrees; - if (self.isUsingUserDefaultValues) { - [[NSUserDefaults standardUserDefaults] setBool:isUsingDegrees + [[NSUserDefaults standardUserDefaults] setBool:isUsingDegrees forKey:MPMathRulesIsUsingDegreesKey]; - } } @end diff --git a/MathKit/MPNegatedTerm.h b/MathKit/MPNegatedTerm.h index 7c4b305..5c39556 100644 --- a/MathKit/MPNegatedTerm.h +++ b/MathKit/MPNegatedTerm.h @@ -9,6 +9,12 @@ #import "MPTerm.h" +/*! + @header + This file contains the MPNegatedTerm class. + */ + + @class MPNegatedTerm; @@ -20,7 +26,6 @@ */ @interface MPNegatedTerm : MPTerm - /*! @method initWithTerm: @abstract Initializes a new negated term. @@ -34,8 +39,8 @@ /*! - @property term - @abstract The receiver's term. + @property term + @abstract The receiver's term. */ @property (readonly, nonatomic, strong) MPTerm *term; diff --git a/MathKit/MPNegatedTerm.m b/MathKit/MPNegatedTerm.m index d7b1d27..1e6df48 100644 --- a/MathKit/MPNegatedTerm.m +++ b/MathKit/MPNegatedTerm.m @@ -8,6 +8,8 @@ #import "MPNegatedTerm.h" + + @implementation MPNegatedTerm - (instancetype)initWithTerm:(MPTerm *)term @@ -20,6 +22,7 @@ return self; } + - (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error { NSDecimalNumber *value = [self.term evaluate:error]; diff --git a/MathKit/MPNumber.h b/MathKit/MPNumber.h index e9d1f1e..feaf477 100644 --- a/MathKit/MPNumber.h +++ b/MathKit/MPNumber.h @@ -9,14 +9,44 @@ #import "MPTerm.h" +/*! + @header + This file contains the MPNumber class. + */ + + @class MPNumber; +/*! + @class MPNumber + @abstract This class represents a number that evaluates to itself. + + @discussion Numbers include integers as well as floating point numbers. They + have to be representable as a decimal number literal (e.g. + 3.4). Numbers that have periods or are irrational + are not implemented by this class. + */ @interface MPNumber : MPTerm +/*! + @method initWithNumber: + @abstract Initializes a number term with the specified number. + + @param number + The number that the term should evaluate to. Must not be + nil. + + @return A new MPNumberTerm instance. + */ - (instancetype)initWithNumber:(NSDecimalNumber *)number; /* designated initializer */ + +/*! + @property number + @abstract The receiver's number. + */ @property (readonly, nonatomic, strong) NSDecimalNumber *number; @end diff --git a/MathKit/MPNumber.m b/MathKit/MPNumber.m index 4d69957..4daa7d9 100644 --- a/MathKit/MPNumber.m +++ b/MathKit/MPNumber.m @@ -8,9 +8,7 @@ #import "MPNumber.h" -#import "MPParsedExpression.h" -#import "MPToken.h" @implementation MPNumber @@ -24,6 +22,7 @@ return self; } + - (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error { return self.number; diff --git a/MathKit/MPParenthesisFunction.h b/MathKit/MPParenthesisFunction.h index 813847b..42664b9 100644 --- a/MathKit/MPParenthesisFunction.h +++ b/MathKit/MPParenthesisFunction.h @@ -9,6 +9,12 @@ #import "MPFunction.h" +/*! + @header + This file contains the MPParenthesisFunction class. + */ + + @class MPParenthesisFunction, MPExpression; diff --git a/MathKit/MPParenthesisFunction.m b/MathKit/MPParenthesisFunction.m index 19bd785..fe33eba 100644 --- a/MathKit/MPParenthesisFunction.m +++ b/MathKit/MPParenthesisFunction.m @@ -18,16 +18,19 @@ MPFunctionAccessorImplementation(Expression, _expression) + - (NSArray *)childrenAccessors { return @[@"expression"]; } + - (Class)functionTermClass { return [MPParenthesisTerm class]; } + - (NSString *)description { return [NSString stringWithFormat:@"(%@)", self.expression.description]; diff --git a/MathKit/MPParenthesisFunctionLayout.h b/MathKit/MPParenthesisFunctionLayout.h index a83d78a..1e6a5de 100644 --- a/MathKit/MPParenthesisFunctionLayout.h +++ b/MathKit/MPParenthesisFunctionLayout.h @@ -9,12 +9,32 @@ #import "MPFunctionLayout.h" +/*! + @header + This file contains the MPParenthesisFunctionLayout class. + */ + + @class MPParenthesisFunctionLayout, MPParenthesisFunction; +/*! + @class MPParenthesisFunctionLayout + @abstract A parenthesis function layout displays a @link + //apple_ref/occ/cl/MPParenthesisFunction@/link. + + @discussion The child of the parenthesis function is encapsulated by standard + parenthesis that are scaled to the smae height as the child. + */ @interface MPParenthesisFunctionLayout : MPFunctionLayout +/*! + @method parenthesisFunction + @abstract Returns the @link + //apple_ref/occ/cl/MPParenthesisFunction@/link represented + by the receiver. + */ - (MPParenthesisFunction *)parenthesisFunction; @end diff --git a/MathKit/MPParenthesisFunctionLayout.m b/MathKit/MPParenthesisFunctionLayout.m index 00f83f7..fd400b6 100644 --- a/MathKit/MPParenthesisFunctionLayout.m +++ b/MathKit/MPParenthesisFunctionLayout.m @@ -10,9 +10,12 @@ #import "MPParenthesisFunction.h" + #define MPParenthesisFunctionOpeningParensOffset 2 #define MPParenthesisFunctionClosingParensOffset 0 + + @interface MPParenthesisFunctionLayout () - (NSBezierPath *)openingParens; @@ -25,6 +28,8 @@ @end + + @implementation MPParenthesisFunctionLayout - (MPParenthesisFunction *)parenthesisFunction @@ -32,6 +37,7 @@ return (MPParenthesisFunction *)self.function; } + - (NSBezierPath *)openingParens { NSBezierPath *parens = [self objectForPrivateCacheIndex:0 generator:^id{ @@ -60,6 +66,7 @@ return parens; } + - (NSBezierPath *)closingParens { NSBezierPath *parens = [self objectForPrivateCacheIndex:1 generator:^id{ @@ -88,6 +95,7 @@ return parens; } + - (NSBezierPath *)transformedOpeningParens { NSBezierPath *parens = self.openingParens.copy; @@ -95,6 +103,7 @@ return parens; } + - (NSBezierPath *)transformedClosingParens { NSBezierPath *parens = self.closingParens.copy; @@ -102,6 +111,7 @@ return parens; } + - (NSAffineTransform *)parenthesisTransform { NSAffineTransform *transform = [NSAffineTransform transform]; @@ -110,6 +120,7 @@ return transform; } + - (CGFloat)scaleFactor { NSRect parensBounds = self.openingParens.bounds; @@ -122,22 +133,25 @@ return expressionHeight / parensHeight; } + - (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index { return NSMakePoint(self.openingParens.bounds.size.width + MPParenthesisFunctionOpeningParensOffset, 0); } + - (NSIndexPath *)indexPathForLocalMousePoint:(NSPoint)point { -#warning Missing Implementation return [NSIndexPath indexPathWithIndex:0]; } + - (NSIndexSet *)indexesOfRemainingChildren { return [NSIndexSet indexSetWithIndex:0]; } + - (NSRect)generateBounds { NSRect openingParensBounds = self.transformedOpeningParens.bounds; @@ -159,6 +173,7 @@ return bounds; } + - (void)draw { NSBezierPath *openingParens = self.transformedOpeningParens; diff --git a/MathKit/MPParenthesisTerm.h b/MathKit/MPParenthesisTerm.h index 185ea47..098a3a1 100644 --- a/MathKit/MPParenthesisTerm.h +++ b/MathKit/MPParenthesisTerm.h @@ -9,10 +9,24 @@ #import "MPFunctionTerm.h" +/*! + @header + This file contains the MPParenthesisTerm class. + */ + + @class MPParenthesisTerm; +/*! + @class MPParenthesisTerm + @abstract Represents a @link + //apple_ref/occ/cl/MPParenthesisFunction@/link. + + @discussion A parenthesis function encapsulates a term and thus prioritizes + it in evaluation. + */ @interface MPParenthesisTerm : MPFunctionTerm @end diff --git a/MathKit/MPParenthesisTerm.m b/MathKit/MPParenthesisTerm.m index 8e100e1..42f7de6 100644 --- a/MathKit/MPParenthesisTerm.m +++ b/MathKit/MPParenthesisTerm.m @@ -10,6 +10,8 @@ #import "MPParsedExpression.h" + + @implementation MPParenthesisTerm - (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error diff --git a/MathKit/MPParsedExpression.h b/MathKit/MPParsedExpression.h index 273a4b0..7f7a6f4 100644 --- a/MathKit/MPParsedExpression.h +++ b/MathKit/MPParsedExpression.h @@ -7,8 +7,10 @@ // - -@class MPParsedExpression, MPTerm; +/*! + @header + This file contains the MPParsedExpression class. + */ /*! @@ -50,6 +52,10 @@ FOUNDATION_EXPORT NSString *const MPPathToExpressionKey; FOUNDATION_EXPORT NSString *const MPErrorRangeKey; + +@class MPParsedExpression, MPTerm; + + /*! @class MPParsedExpression @abstract A parsed expression represents an expression whose syntax is @@ -93,7 +99,7 @@ FOUNDATION_EXPORT NSString *const MPErrorRangeKey; @discussion This method is a convenience method for evaluating the receiver's @link - //apple_ref/occ/intfp/MPParsedExpression/term@/link. + //apple_ref/occ/instp/MPParsedExpression/term@/link. @param error If an error occured during evaluation it will be returned diff --git a/MathKit/MPParsedExpression.m b/MathKit/MPParsedExpression.m index 7e6793f..7e1ae91 100644 --- a/MathKit/MPParsedExpression.m +++ b/MathKit/MPParsedExpression.m @@ -9,13 +9,15 @@ #import "MPParsedExpression.h" #import "MPToken.h" - #import "MPTerm.h" + NSString *const MPMathKitErrorDomain = @"MPMathKitErrorDomain"; NSString *const MPPathToExpressionKey = @"MPPathToExpressionKey"; NSString *const MPErrorRangeKey = @"MPErrorRangeKey"; + + @implementation MPParsedExpression - (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error diff --git a/MathKit/MPPowerFunction.h b/MathKit/MPPowerFunction.h index 846269b..336e4ea 100644 --- a/MathKit/MPPowerFunction.h +++ b/MathKit/MPPowerFunction.h @@ -9,6 +9,12 @@ #import "MPFunction.h" +/*! + @header + This file contains the MPPowerFunction class. + */ + + @class MPPowerFunction, MPExpression; diff --git a/MathKit/MPPowerFunction.m b/MathKit/MPPowerFunction.m index 5953196..d5a373c 100644 --- a/MathKit/MPPowerFunction.m +++ b/MathKit/MPPowerFunction.m @@ -10,15 +10,19 @@ #import "MPExpression.h" + + @implementation MPPowerFunction MPFunctionAccessorImplementation(ExponentExpression, _exponentExpression) + - (NSArray *)childrenAccessors { return @[@"exponentExpression"]; } + - (NSString *)description { return [NSString stringWithFormat:@"^%@", self.exponentExpression.description]; diff --git a/MathKit/MPPowerFunctionLayout.h b/MathKit/MPPowerFunctionLayout.h index 1c9a2c0..81d3362 100644 --- a/MathKit/MPPowerFunctionLayout.h +++ b/MathKit/MPPowerFunctionLayout.h @@ -9,14 +9,46 @@ #import "MPFunctionLayout.h" +/*! + @header + This file contains the MPPowerFunctionLayout class. + */ + + @class MPPowerFunctionLayout, MPPowerFunction; +/*! + @class MPPowerFunctionLayout + @abstract A power function layout displays a @link + //apple_ref/occ/cl/MPPowerFunction@/link. + + @discussion A power function layout draws its child at the top right of the + base. Because of this the power function layout has a special + property that gets set during the drawing of an expression: + @link + //apple_ref/occ/instp/MPPowerFunctionLayout/baseBounds@/link. + */ @interface MPPowerFunctionLayout : MPFunctionLayout +/*! + @property baseBounds + @abstract The bounds of the expression that is the base of the receiving + power function layout. + + @discussion This value should be considered very volatile. It may change even + if the power function itself didn't. This value is guaranteed to + stay the same only during a single cycle of drawing. + */ @property (nonatomic) NSRect baseBounds; +/*! + @method powerFunction + @abstract Returns the @link + //apple_ref/occ/cl/MPPowerFunction@/link represented by + the receiver. + */ - (MPPowerFunction *)powerFunction; @end diff --git a/MathKit/MPPowerFunctionLayout.m b/MathKit/MPPowerFunctionLayout.m index e8c955d..ad1cda7 100644 --- a/MathKit/MPPowerFunctionLayout.m +++ b/MathKit/MPPowerFunctionLayout.m @@ -10,9 +10,12 @@ #import "MPPowerFunction.h" + #define kPowerFunctionExponentXOffset 1 #define kPowerFunctionTrailingOffset 2 + + @implementation MPPowerFunctionLayout - (NSRect)baseBounds @@ -23,16 +26,19 @@ return _baseBounds; } + - (MPPowerFunction *)powerFunction { return (MPPowerFunction *)self.function; } + - (NSIndexSet *)indexesOfRemainingChildren { return [NSIndexSet indexSetWithIndex:0]; } + #warning Broken Power Layout - (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index { @@ -41,16 +47,19 @@ return NSMakePoint(kPowerFunctionExponentXOffset, y); } + - (BOOL)childAtIndexUsesSmallSize:(NSUInteger)index { return YES; } + - (NSIndexPath *)indexPathForLocalMousePoint:(NSPoint)point { return [[NSIndexPath indexPathWithIndex:0] indexPathByAddingIndex:0]; } + - (NSRect)generateBounds { NSRect exponentBounds = [self childLayoutAtIndex:0].bounds; @@ -59,9 +68,8 @@ return NSMakeRect(0, 0, width, height); } + - (void)draw -{ - [[NSBezierPath bezierPathWithRect:self.bounds] stroke]; -} +{} @end diff --git a/MathKit/MPPowerTerm.h b/MathKit/MPPowerTerm.h index 045114b..93f4e66 100644 --- a/MathKit/MPPowerTerm.h +++ b/MathKit/MPPowerTerm.h @@ -9,12 +9,33 @@ #import "MPFunctionTerm.h" +/*! + @header + This file contains the MPPowerTerm class. + */ + + @class MPPowerTerm, MPTerm; +/*! + @class MPPowerTerm + @abstract Represents a @link + //apple_ref/occ/cl/MPPowerFunction@/link. + + @discussion A power function is evaluated using the C pow + function. + */ @interface MPPowerTerm : MPFunctionTerm +/*! + @property baseTerm + @abstract The base of the power. + + @discussion This value is set during the evaluation of the power term and + should be considered very volatile. + */ @property (nonatomic, strong) MPTerm *baseTerm; @end diff --git a/MathKit/MPPowerTerm.m b/MathKit/MPPowerTerm.m index e846775..fcada00 100644 --- a/MathKit/MPPowerTerm.m +++ b/MathKit/MPPowerTerm.m @@ -7,8 +7,11 @@ // #import "MPPowerTerm.h" + #import "MPParsedExpression.h" + + @implementation MPPowerTerm - (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error diff --git a/MathKit/MPProductTerm.h b/MathKit/MPProductTerm.h index 4445a9a..b695aad 100644 --- a/MathKit/MPProductTerm.h +++ b/MathKit/MPProductTerm.h @@ -9,6 +9,12 @@ #import "MPTerm.h" +/*! + @header + This file contains the MPProductTerm class. + */ + + @class MPProductTerm; @@ -22,7 +28,6 @@ */ @interface MPProductTerm : MPTerm - /*! @method initWithFactors: @abstract Initializes a new product term with the specified diff --git a/MathKit/MPProductTerm.m b/MathKit/MPProductTerm.m index c0002f7..a3a3f83 100644 --- a/MathKit/MPProductTerm.m +++ b/MathKit/MPProductTerm.m @@ -8,17 +8,7 @@ #import "MPProductTerm.h" -#import "MPParsedExpression.h" -#import "MPPowerFunction.h" -#import "MPFunctionTerm.h" -#import "MPElementaryFunctionTerm.h" -#import "MPNumber.h" -#import "MPFactorialTerm.h" -#import "MPPowerTerm.h" -#import "MPVariable.h" - -#import "MPToken.h" @implementation MPProductTerm @@ -33,6 +23,7 @@ return self; } + - (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error { NSDecimalNumber *value = [NSDecimalNumber one]; diff --git a/MathKit/MPRangePath.h b/MathKit/MPRangePath.h index 3ceecde..48b58fa 100644 --- a/MathKit/MPRangePath.h +++ b/MathKit/MPRangePath.h @@ -11,6 +11,8 @@ /*! @header + This file contains the MPRangePath class. + The MPRangePath combines an NSIndexPath with a NSRange. A range path is used in a tree structure to identify a number of subsequent nodes. This is achieved by combining a range path that @@ -36,6 +38,7 @@ #define MPMakeRangePath(loc, len) [MPRangePath rangePathWithLocation:(loc) length:(len)] + @class MPRangePath, MPExpression; @@ -268,7 +271,6 @@ @interface MPExpression (MPRangeExtension) - /*! @method subexpressionWithRangePath: @abstract Creates a new expression with the symbols in the specified range diff --git a/MathKit/MPRangePath.m b/MathKit/MPRangePath.m index 7c34ae7..c03844a 100644 --- a/MathKit/MPRangePath.m +++ b/MathKit/MPRangePath.m @@ -12,7 +12,6 @@ @implementation MPRangePath - #pragma mark Creation Methods diff --git a/MathKit/MPSumFunction.h b/MathKit/MPSumFunction.h index ef0b7f0..dfde2ae 100644 --- a/MathKit/MPSumFunction.h +++ b/MathKit/MPSumFunction.h @@ -9,6 +9,12 @@ #import "MPFunction.h" +/*! + @header + This file contains the MPSumFunction class. + */ + + @class MPSumFunction, MPExpression; diff --git a/MathKit/MPSumFunction.m b/MathKit/MPSumFunction.m index cedc69a..7bd6580 100644 --- a/MathKit/MPSumFunction.m +++ b/MathKit/MPSumFunction.m @@ -15,7 +15,6 @@ @implementation MPSumFunction - MPFunctionAccessorImplementation(StartExpression, _startExpression) MPFunctionAccessorImplementation(TargetExpression, _targetExpression) MPFunctionAccessorImplementation(SumExpression, _sumExpression) @@ -26,11 +25,13 @@ MPFunctionAccessorImplementation(SumExpression, _sumExpression) return @[@"startExpression", @"targetExpression", @"sumExpression"]; } + - (BOOL)expectsVariableDefinitionInChildAtIndex:(NSUInteger)index { return index == 0; } + - (Class)functionTermClass { return [MPSumFunctionTerm class]; diff --git a/MathKit/MPSumFunctionLayout.h b/MathKit/MPSumFunctionLayout.h index e530a19..e2d0b2e 100644 --- a/MathKit/MPSumFunctionLayout.h +++ b/MathKit/MPSumFunctionLayout.h @@ -9,12 +9,33 @@ #import "MPFunctionLayout.h" +/*! + @header + This file contains the MPSumFunctionLayout class. + */ + + @class MPSumFunctionLayout, MPSumFunction; +/*! + @class MPSumFunctionLayout + @abstract A sum function layout displays a @link + //apple_ref/occ/cl/MPSumFunction@/link. + + @discussion A sum is drawn using a capital greeg sigma (∑) The three children + are placed around it: The start expression below, target + expression above and sum expression to the right of it. + */ @interface MPSumFunctionLayout : MPFunctionLayout +/*! + @method sumFunction + @abstract Returns the @link + //apple_ref/occ/cl/MPSumFunction@/link represented by the + receiver. + */ - (MPSumFunction *)sumFunction; @end diff --git a/MathKit/MPSumFunctionLayout.m b/MathKit/MPSumFunctionLayout.m index ee195ab..525d727 100644 --- a/MathKit/MPSumFunctionLayout.m +++ b/MathKit/MPSumFunctionLayout.m @@ -9,14 +9,16 @@ #import "MPSumFunctionLayout.h" #import "MPSumFunction.h" - #import "NSIndexPath+MPAdditions.h" + #define kSumFunctionStartExpressionOffset 0 #define kSumFunctionTargetExpressionOffset 0 #define kSumFunctionSumExpressionOffset 3 #define kSumFunctionTrailingOffset 5 + + @implementation MPSumFunctionLayout - (MPSumFunction *)sumFunction @@ -24,16 +26,19 @@ return (MPSumFunction *)self.function; } + - (NSUInteger)indexOfLeadingChild { return 2; } + - (NSUInteger)indexOfTrailingChild { return 2; } + - (NSUInteger)indexOfChildAfterChildAtIndex:(NSUInteger)index { if (index != 2) { @@ -42,21 +47,25 @@ return [super indexOfChildAfterChildAtIndex:index]; } + - (NSUInteger)indexOfChildBelowChildAtIndex:(NSUInteger)index { return 0; } + - (NSUInteger)indexOfChildAboveChildAtIndex:(NSUInteger)index { return 1; } + - (NSIndexSet *)indexesOfRemainingChildren { return [NSIndexSet indexSetWithIndex:2]; } + - (CTLineRef)line { CTLineRef line = [self lineForPrivateCacheIndex:0 generator:^CTLineRef{ @@ -67,6 +76,7 @@ return line; } + - (NSRect)localLineBounds { NSRect lineBounds = CTLineGetBoundsWithOptions(self.line, 0); @@ -77,6 +87,7 @@ return NSMakeRect(xPosition, lineBounds.origin.y, lineBounds.size.width, lineBounds.size.height); } + - (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index { NSRect childBounds = [self childLayoutAtIndex:index].bounds; @@ -101,11 +112,13 @@ return offset; } + - (BOOL)childAtIndexUsesSmallSize:(NSUInteger)index { return (index == 0 || index == 1) ? YES : self.usesSmallSize; } + - (NSIndexPath *)indexPathForLocalMousePoint:(NSPoint)point { if (point.x < CTLineGetBoundsWithOptions(self.line, 0).size.width / 2) { @@ -115,6 +128,7 @@ } } + - (NSRect)generateBounds { NSRect lineBounds = CTLineGetBoundsWithOptions(self.line, 0); @@ -139,6 +153,7 @@ return bounds; } + - (void)draw { // Get the current context diff --git a/MathKit/MPSumFunctionTerm.h b/MathKit/MPSumFunctionTerm.h index 6768030..1890b01 100644 --- a/MathKit/MPSumFunctionTerm.h +++ b/MathKit/MPSumFunctionTerm.h @@ -8,11 +8,30 @@ #import "MPFunctionTerm.h" +/*! + @header + This file contains the MPSumFunctionTerm class. + */ + @class MPSumFunctionTerm; +/*! + @class MPSumFunctionTerm + @abstract Represents and evaluates a @link + //apple_ref/occ/cl/MPSumFunction@/link. + + @discussion A sum function evaluates its sum term n times. How + often it actually is evaluated depends on the boundary + expressions (start and target). Both are inclusive. + + A sum function also features a variable that contains the current + value of the iteration. The variable is defined in the start + expression and incremented by 1 after every + iteration. + */ @interface MPSumFunctionTerm : MPFunctionTerm @end diff --git a/MathKit/MPSumFunctionTerm.m b/MathKit/MPSumFunctionTerm.m index 23b09f1..8c985cd 100644 --- a/MathKit/MPSumFunctionTerm.m +++ b/MathKit/MPSumFunctionTerm.m @@ -10,6 +10,8 @@ #import "MPParsedExpression.h" + + @implementation MPSumFunctionTerm - (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error diff --git a/MathKit/MPSumTerm.h b/MathKit/MPSumTerm.h index d99737e..dd53de8 100644 --- a/MathKit/MPSumTerm.h +++ b/MathKit/MPSumTerm.h @@ -9,6 +9,12 @@ #import "MPTerm.h" +/*! + @header + This file contains the MPSumTerm class. + */ + + @class MPSumTerm; diff --git a/MathKit/MPSumTerm.m b/MathKit/MPSumTerm.m index 25e16b6..3ec8fc1 100644 --- a/MathKit/MPSumTerm.m +++ b/MathKit/MPSumTerm.m @@ -8,10 +8,7 @@ #import "MPSumTerm.h" -#import "MPParsedExpression.h" -#import "MPProductTerm.h" -#import "MPToken.h" @implementation MPSumTerm @@ -26,6 +23,7 @@ return self; } + - (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error { NSDecimalNumber *value = [NSDecimalNumber zero]; diff --git a/MathKit/MPTerm.h b/MathKit/MPTerm.h index 45accb8..a11b761 100644 --- a/MathKit/MPTerm.h +++ b/MathKit/MPTerm.h @@ -9,6 +9,8 @@ /*! @header + This file contains the MPTerm class. + The MPTerm class is used to evaluate a mathematical expression. A term is a purely mathematical representation of a part of an expression. A term can be defined with three rules: @@ -66,6 +68,7 @@ #define ReturnNilIfNil(test) if (test == nil) return nil + @class MPTerm; diff --git a/MathKit/MPTerm.m b/MathKit/MPTerm.m index af567fd..00ff9c6 100644 --- a/MathKit/MPTerm.m +++ b/MathKit/MPTerm.m @@ -9,9 +9,10 @@ #import "MPTerm.h" #import "MPParsedExpression.h" -#import "MPSumTerm.h" #import "MPEvaluationContext.h" + + @implementation MPTerm - (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error @@ -22,6 +23,7 @@ return result; } + - (BOOL)defineVariable:(NSString *)variableName value:(NSDecimalNumber *)value error:(NSError *__autoreleasing *)error @@ -39,6 +41,7 @@ return couldDefineVariable; } + - (NSDecimalNumber *)valueForVariable:(NSString *)variableName error:(NSError *__autoreleasing *)error { @@ -54,6 +57,7 @@ return value; } + - (void)undefineVariable:(NSString *)variableName { [[MPEvaluationContext sharedContext] undefineVariable:variableName]; diff --git a/MathKit/MPToken.h b/MathKit/MPToken.h index 6a8365b..de090c7 100644 --- a/MathKit/MPToken.h +++ b/MathKit/MPToken.h @@ -9,6 +9,8 @@ /*! @header + This file contains the MPToken class and protocol. + One way to represent a mathematical expression using the @link MPExpression@/link class is a sequence of tokens. A token is a logical unit of input. The different types of units are identified by a @link @@ -30,10 +32,6 @@ while ((token = [self nextToken]).tokenType != MPEOFToken) { */ -@class MPToken; -@protocol MPToken; - - /*! @typedef MPTokenType @abstract The type of a token identifies its behaviour in a mathematical @@ -103,6 +101,10 @@ typedef enum { +@class MPToken; +@protocol MPToken; + + /*! @protocol MPToken @abstract Tokens represent logical units in an expresion. diff --git a/MathKit/MPVariable.h b/MathKit/MPVariable.h index 9f884cc..882bddf 100644 --- a/MathKit/MPVariable.h +++ b/MathKit/MPVariable.h @@ -9,14 +9,44 @@ #import "MPTerm.h" +/*! + @header + This file contains the MPVariable class. + */ + + @class MPVariable; +/*! + @class MPVariable + @abstract This class represents a variable. + + @discussion Variables are evaluated in the @link + //apple_ref/occ/cl/MPEvaluationContext@/link and generate + errors if they are not defined. + */ @interface MPVariable : MPTerm +/*! + @method initWithVariableName: + @abstract Initializes a MPVariable with the specified + variableName + + @param variableName + The name of the variable. Must not be nil and must + be at least one character long. + + @return A new MPVariable instance. + */ - (instancetype)initWithVariableName:(NSString *)variableName; /* designated initializer */ + +/*! + @property variableName + @abstract The receiver's variable name. + */ @property (readonly, nonatomic, strong) NSString *variableName; @end diff --git a/MathKit/MPVariable.m b/MathKit/MPVariable.m index 3f8a2a1..c56382f 100644 --- a/MathKit/MPVariable.m +++ b/MathKit/MPVariable.m @@ -8,12 +8,7 @@ #import "MPVariable.h" -#import "MPParsedExpression.h" -#import "MPToken.h" - -#import "MPExpression.h" -#import "MPEvaluationContext.h" @implementation MPVariable @@ -22,11 +17,13 @@ self = [super init]; if (self) { NSAssert(variableName != nil, @"variableName must not be nil."); + NSAssert(variableName.length > 0, @"variableName must be at least one character long."); _variableName = variableName; } return self; } + - (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error { return [self valueForVariable:self.variableName diff --git a/MathKit/MPWhiteView.h b/MathKit/MPWhiteView.h index 5de222e..eea665d 100644 --- a/MathKit/MPWhiteView.h +++ b/MathKit/MPWhiteView.h @@ -7,10 +7,20 @@ // +/*! + @header + This file contains the MPWhiteView class. + */ + + @class MPWhiteView; +/*! + @class MPWhiteView + @abstract A NSView that draws a white square in its frame. + */ @interface MPWhiteView : NSView @end diff --git a/MathKit/MathKit.h b/MathKit/MathKit.h index b650f34..90d723e 100644 --- a/MathKit/MathKit.h +++ b/MathKit/MathKit.h @@ -6,20 +6,19 @@ // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // -#import "MPExpression.h" #import "MPExpression.h" #import "NSString+MPExpressionElement.h" #import "MPFunction.h" - -#import "MPSumFunction.h" -#import "MPParenthesisFunction.h" -#import "MPPowerFunction.h" -#import "MPFractionFunction.h" - #import "MPToken.h" #import "MPFunction+MPToken.h" +#import "MPFractionFunction.h" +#import "MPParenthesisFunction.h" +#import "MPPowerFunction.h" +#import "MPSumFunction.h" +#import "MPParsedExpression.h" +#import "MPTerm.h" #import "MPEvaluationContext.h" #import "MPMathRules.h" @@ -27,5 +26,13 @@ #import "NSIndexPath+MPAdditions.h" #import "NSRegularExpression+MPParsingAdditions.h" +#import "MPExpressionStorage.h" #import "MPExpressionView.h" -#import "MPExpressionStorage.h" \ No newline at end of file +#import "MPLayout.h" +#import "MPExpressionLayout.h" +#import "MPFunctionLayout.h" + +#import "MPFractionFunctionLayout.h" +#import "MPParenthesisFunctionLayout.h" +#import "MPPowerFunctionLayout.h" +#import "MPSumFunctionLayout.h" \ No newline at end of file diff --git a/MathKit/NSIndexPath+MPAdditions.h b/MathKit/NSIndexPath+MPAdditions.h index 93b9000..141e0e7 100644 --- a/MathKit/NSIndexPath+MPAdditions.h +++ b/MathKit/NSIndexPath+MPAdditions.h @@ -7,6 +7,12 @@ // +/*! + @header + This file contains the NSIndexPath(MPAdditions) category. + */ + + /*! @category NSIndexPath (MPAdditions) @@ -15,7 +21,6 @@ */ @interface NSIndexPath (MPAdditions) - /*! @property firstIndex @abstract The first index from the receiver. diff --git a/MathKit/NSIndexPath+MPAdditions.m b/MathKit/NSIndexPath+MPAdditions.m index 032f1ed..4a8751f 100644 --- a/MathKit/NSIndexPath+MPAdditions.m +++ b/MathKit/NSIndexPath+MPAdditions.m @@ -12,7 +12,6 @@ @implementation NSIndexPath (MPAdditions) - - (NSUInteger)firstIndex { return [self indexAtPosition:0]; diff --git a/MathKit/NSRegularExpression+MPParsingAdditions.h b/MathKit/NSRegularExpression+MPParsingAdditions.h index 351a127..5194dc0 100644 --- a/MathKit/NSRegularExpression+MPParsingAdditions.h +++ b/MathKit/NSRegularExpression+MPParsingAdditions.h @@ -7,6 +7,13 @@ // +/*! + @header + This file contains the NSRegularExpression(MPParsingAdditions) + category. + */ + + /*! @category NSRegularExpression (MPParsingAdditions) @@ -15,7 +22,6 @@ */ @interface NSRegularExpression (MPParsingAdditions) - /*! @method firstMathInString: @abstract Returns the first match of the regular expression within the diff --git a/MathKit/NSString+MPExpressionElement.h b/MathKit/NSString+MPExpressionElement.h index be63994..d4baab0 100644 --- a/MathKit/NSString+MPExpressionElement.h +++ b/MathKit/NSString+MPExpressionElement.h @@ -9,11 +9,18 @@ #import "MPExpression.h" +/*! + @header + This file contains the NSString(MPExpressionElement) category. + */ + + /*! @category NSString (MPExpressionElement) - @abstract This category adds @link MPExpressionElement@/link - protocol conformance to the NSString class. + @abstract This category adds @link + //apple_ref/occ/intf/MPExpressionElement@/link protocol + conformance to the NSString class. */ @interface NSString (MPExpressionElement) diff --git a/MathPad.xcodeproj/project.pbxproj b/MathPad.xcodeproj/project.pbxproj index f6f3823..e330a88 100644 --- a/MathPad.xcodeproj/project.pbxproj +++ b/MathPad.xcodeproj/project.pbxproj @@ -253,8 +253,6 @@ 3B85832719BB5E5500D76A8D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 3B85833219BB5F2D00D76A8D /* MathKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MathKit.h; sourceTree = ""; }; 3BBBA3941905704200824E74 /* MPRangeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MPRangeTests.m; path = ../MathPadTests/MPRangeTests.m; sourceTree = ""; }; - 3BC4661519B365070033F13A /* MPArrayCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPArrayCache.h; sourceTree = ""; }; - 3BC4661619B365070033F13A /* MPArrayCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPArrayCache.m; sourceTree = ""; }; 3BF9976B18DE623E009CF6C4 /* MathPad.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MathPad.app; sourceTree = BUILT_PRODUCTS_DIR; }; 3BF9976E18DE623E009CF6C4 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 3BF9977118DE623E009CF6C4 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; @@ -460,15 +458,6 @@ name = "Function Layouts"; sourceTree = ""; }; - 3BC4661819B3692A0033F13A /* Temp */ = { - isa = PBXGroup; - children = ( - 3BC4661519B365070033F13A /* MPArrayCache.h */, - 3BC4661619B365070033F13A /* MPArrayCache.m */, - ); - name = Temp; - sourceTree = ""; - }; 3BC46B4B19B38CB60033F13A /* Base Expression Classes */ = { isa = PBXGroup; children = ( @@ -510,22 +499,22 @@ 3B6338671A3A4C2B00698BFB /* MPParsedExpression.m */, 3B63387A1A3A4C7E00698BFB /* MPTerm.h */, 3B63387B1A3A4C7E00698BFB /* MPTerm.m */, - 3B6338601A3A4C2B00698BFB /* MPNegatedTerm.h */, - 3B6338611A3A4C2B00698BFB /* MPNegatedTerm.m */, 3B6338781A3A4C7E00698BFB /* MPSumTerm.h */, 3B6338791A3A4C7E00698BFB /* MPSumTerm.m */, 3B63386A1A3A4C2B00698BFB /* MPProductTerm.h */, 3B63386B1A3A4C2B00698BFB /* MPProductTerm.m */, - 3B6338841A3A4CA500698BFB /* MPFactorialTerm.h */, - 3B6338851A3A4CA500698BFB /* MPFactorialTerm.m */, + 3B6338601A3A4C2B00698BFB /* MPNegatedTerm.h */, + 3B6338611A3A4C2B00698BFB /* MPNegatedTerm.m */, + 3B63388C1A3A4CBE00698BFB /* MPFunctionTerm.h */, + 3B63388D1A3A4CBE00698BFB /* MPFunctionTerm.m */, 3B6338881A3A4CAF00698BFB /* MPElementaryFunctionTerm.h */, 3B6338891A3A4CAF00698BFB /* MPElementaryFunctionTerm.m */, 3B6338621A3A4C2B00698BFB /* MPNumber.h */, 3B6338631A3A4C2B00698BFB /* MPNumber.m */, 3B63387C1A3A4C7E00698BFB /* MPVariable.h */, 3B63387D1A3A4C7E00698BFB /* MPVariable.m */, - 3B63388C1A3A4CBE00698BFB /* MPFunctionTerm.h */, - 3B63388D1A3A4CBE00698BFB /* MPFunctionTerm.m */, + 3B6338841A3A4CA500698BFB /* MPFactorialTerm.h */, + 3B6338851A3A4CA500698BFB /* MPFactorialTerm.m */, 3B6338901A3A4CCA00698BFB /* MPEvaluationContext.h */, 3B6338911A3A4CCA00698BFB /* MPEvaluationContext.m */, 3B6338941A3A4CD100698BFB /* MPMathRules.h */, @@ -538,14 +527,14 @@ 3BEFF75F1A17FF5C00301C0C /* Functions */ = { isa = PBXGroup; children = ( - 3B6338981A3A4CE100698BFB /* MPSumFunctionTerm.h */, - 3B6338991A3A4CE100698BFB /* MPSumFunctionTerm.m */, + 3B63389C1A3A4CEF00698BFB /* MPFractionTerm.h */, + 3B63389D1A3A4CEF00698BFB /* MPFractionTerm.m */, 3B6338641A3A4C2B00698BFB /* MPParenthesisTerm.h */, 3B6338651A3A4C2B00698BFB /* MPParenthesisTerm.m */, 3B6338681A3A4C2B00698BFB /* MPPowerTerm.h */, 3B6338691A3A4C2B00698BFB /* MPPowerTerm.m */, - 3B63389C1A3A4CEF00698BFB /* MPFractionTerm.h */, - 3B63389D1A3A4CEF00698BFB /* MPFractionTerm.m */, + 3B6338981A3A4CE100698BFB /* MPSumFunctionTerm.h */, + 3B6338991A3A4CE100698BFB /* MPSumFunctionTerm.m */, ); name = Functions; sourceTree = ""; @@ -598,7 +587,6 @@ 3BF9977418DE623E009CF6C4 /* MathPad */ = { isa = PBXGroup; children = ( - 3BC4661819B3692A0033F13A /* Temp */, 3BF9978018DE623E009CF6C4 /* MPDocument.h */, 3BF9978118DE623E009CF6C4 /* MPDocument.m */, 3B87E353190082E200259938 /* Resources */, diff --git a/MathPad/MPArrayCache.h b/MathPad/MPArrayCache.h deleted file mode 100644 index f8fb8ef..0000000 --- a/MathPad/MPArrayCache.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// MPArrayCache.h -// MathPad -// -// Created by Kim Wittenburg on 31.08.14. -// Copyright (c) 2014 Kim Wittenburg. All rights reserved. -// - -#import - -// A wrapper around NSCache to support index-based caches. Indexes are -// automatically adjusted to represent array-like behaviours. -@interface MPArrayCache : NSObject { - @private - NSCache *_cache; -} - -- (id)init; /* designated initializer */ - -- (void)cacheObject:(id)object - forIndex:(NSUInteger)index; -- (id)cachedObjectForIndex:(NSUInteger)index; - -- (void)clearCacheAtIndex:(NSUInteger)index - replacementLength:(NSUInteger)replacementLength; -- (void)clearCacheInRange:(NSRange)range - replacementLength:(NSUInteger)replacementLength; - -- (void)replaceCachedObjectAtIndex:(NSUInteger)index - withObjects:(NSArray *)objects; -- (void)replaceCachedObjectsInRange:(NSRange)range - withObjects:(NSArray *)objects; - -@end diff --git a/MathPad/MPArrayCache.m b/MathPad/MPArrayCache.m deleted file mode 100644 index 50116c4..0000000 --- a/MathPad/MPArrayCache.m +++ /dev/null @@ -1,59 +0,0 @@ -// -// MPArrayCache.m -// MathPad -// -// Created by Kim Wittenburg on 31.08.14. -// Copyright (c) 2014 Kim Wittenburg. All rights reserved. -// - -#import "MPArrayCache.h" - -@implementation MPArrayCache { - NSUInteger lastIndex; -} - -- (instancetype)init -{ - self = [super init]; - if (self) { - lastIndex = 0; - _cache = [[NSCache alloc] init]; - } - return self; -} - -- (void)cacheObject:(id)object - forIndex:(NSUInteger)index -{ - [_cache setObject:object - forKey:@(index)]; - if (index > lastIndex) { - lastIndex = index; - } -} - -- (id)cachedObjectForIndex:(NSUInteger)index -{ - return [_cache objectForKey:@(index)]; -} - -- (void)clearCacheAtIndex:(NSUInteger)index replacementLength:(NSUInteger)replacementLength -{ - [_cache removeObjectForKey:@(index)]; - for (NSUInteger i = index+1; i < lastIndex; i++) { - id object = [_cache objectForKey:@(i)]; - if (object) { - [_cache removeObjectForKey:@(i)]; - } - } - lastIndex += replacementLength - 1; -} - -- (void)clearCacheInRange:(NSRange)range replacementLength:(NSUInteger)replacementLength; - -- (void)replaceCachedObjectAtIndex:(NSUInteger)index - withObjects:(NSArray *)objects; -- (void)replaceCachedObjectsInRange:(NSRange)range - withObjects:(NSArray *)objects; - -@end diff --git a/MathPad/MPDocument.h b/MathPad/MPDocument.h index 425a88a..8fef23c 100644 --- a/MathPad/MPDocument.h +++ b/MathPad/MPDocument.h @@ -8,11 +8,39 @@ #import + + +/*! + @class MPDocument + @abstract This class is the document class that displays the MathPad user + interface. + */ @interface MPDocument : NSDocument + +/*! + @property expressionView + @abstract The main expression view. + */ @property (weak) IBOutlet MPExpressionView *expressionView; + + +/*! + @property resultLabel + @abstract The label which displays the result of the calculation. + + @discussion The label is placed inside the expression view. + */ @property (weak) IBOutlet NSTextField *resultLabel; + +/*! + @method evaluateExpression: + @abstract Called by the expression view when it should be evaluated. + + @param sender + Typically the object that invoked the method. + */ - (IBAction)evaluateExpression:(id)sender; @end diff --git a/MathPad/MPDocument.m b/MathPad/MPDocument.m index 03d4d83..b7d3453 100644 --- a/MathPad/MPDocument.m +++ b/MathPad/MPDocument.m @@ -8,9 +8,11 @@ #import "MPDocument.h" -#import "MPParsedExpression.h" -@implementation MPDocument + +@implementation MPDocument { + MPExpression *loadedExpression; +} - (id)init { @@ -21,46 +23,47 @@ return self; } + - (NSString *)windowNibName { - // Override returning the nib file name of the document - // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead. return @"MPDocument"; } + - (void)windowControllerDidLoadNib:(NSWindowController *)aController { [super windowControllerDidLoadNib:aController]; self.expressionView.target = self; self.expressionView.action = @selector(evaluateExpression:); - // Add any code here that needs to be executed once the windowController has loaded the document's window. + if (loadedExpression) { + [self.expressionView.expressionStorage appendElements:[loadedExpression allItemsInReferenceFrame:MPElementReferenceFrame]]; + } } + + (BOOL)autosavesInPlace { return YES; } + - (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError { - // Insert code here to write your document to data of the specified type. If outError != NULL, ensure that you create and set an appropriate error when returning nil. - // You can also choose to override -fileWrapperOfType:error:, -writeToURL:ofType:error:, or -writeToURL:ofType:forSaveOperation:originalContentsURL:error: instead. - NSException *exception = [NSException exceptionWithName:@"UnimplementedMethod" reason:[NSString stringWithFormat:@"%@ is unimplemented", NSStringFromSelector(_cmd)] userInfo:nil]; - @throw exception; - return nil; + return [NSKeyedArchiver archivedDataWithRootObject:self.expressionView.expressionStorage]; } + - (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError { - // Insert code here to read your document from the given data of the specified type. If outError != NULL, ensure that you create and set an appropriate error when returning NO. - // You can also choose to override -readFromFileWrapper:ofType:error: or -readFromURL:ofType:error: instead. - // If you override either of these, you should also override -isEntireFileLoaded to return NO if the contents are lazily loaded. - NSException *exception = [NSException exceptionWithName:@"UnimplementedMethod" reason:[NSString stringWithFormat:@"%@ is unimplemented", NSStringFromSelector(_cmd)] userInfo:nil]; - @throw exception; + MPExpression *expression = [NSKeyedUnarchiver unarchiveObjectWithData:data]; + loadedExpression = expression; return YES; } + #pragma mark Actions + + - (IBAction)evaluateExpression:(id)sender { NSArray *errors; MPParsedExpression *parsedExpression = [self.expressionView.expressionStorage parse:&errors]; @@ -74,4 +77,5 @@ self.resultLabel.stringValue = result != nil ? [result descriptionWithLocale:[NSLocale currentLocale]] : @""; } + @end