Archived
1

Reorganized File Structure

Added Documentation
This commit is contained in:
Kim Wittenburg
2014-12-12 00:39:30 +01:00
parent c367b1dbe8
commit 8f1f730358
90 changed files with 1270 additions and 1216 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,23 +0,0 @@
//
// MPFunctionValue.h
// MathPad
//
// Created by Kim Wittenburg on 11.10.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPTerm.h"
@class MPElementaryFunctionTerm;
@interface MPElementaryFunctionTerm : MPTerm
- (instancetype)initWithFunctionIdentifier:(NSString *)function
term:(MPTerm *)term; /* designated initializer */
@property (readonly, nonatomic, strong) MPTerm *term;
@end

View File

@@ -1,112 +0,0 @@
//
// MPFunctionValue.m
// MathPad
//
// Created by Kim Wittenburg on 11.10.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPElementaryFunctionTerm.h"
#import "MPParsedExpression.h"
#import "MPPowerFunction.h"
#import "MPProductTerm.h"
#import "MPToken.h"
#import "MPExpression.h"
#import "MPMathRules.h"
@implementation MPElementaryFunctionTerm {
NSDecimalNumber *(^_function)(NSDecimalNumber *);
}
- (instancetype)initWithFunctionIdentifier:(NSString *)function
term:(MPTerm *)term
{
self = [super init];
if (self) {
NSAssert(function != nil, @"function must not be nil.");
NSAssert(term != nil, @"term must not be nil.");
double (*func)(double);
BOOL takesArc = NO;
BOOL returnsArc = NO;
if ([function isEqualToString:@"sin"]) {
func = &sin;
takesArc = YES;
} else if ([function isEqualToString:@"cos"]) {
func = &cos;
takesArc = YES;
} else if ([function isEqualToString:@"tan"]) {
func = &tan;
takesArc = YES;
} else if ([function isEqualToString:@"asin"] || [function isEqualToString:@"arcsin"]) {
func = &asin;
returnsArc = YES;
} else if ([function isEqualToString:@"acos"] || [function isEqualToString:@"arccos"]) {
func = &acos;
returnsArc = YES;
} else if ([function isEqualToString:@"atan"] || [function isEqualToString:@"arctan"]) {
func = &atan;
returnsArc = YES;
} else if ([function isEqualToString:@"lg"] || [function isEqualToString:@"log"]) {
func = &log10;
} else if ([function isEqualToString:@"ln"]) {
func = &log;
} else {
NSAssert(true, @"function must be one of (sin, cos, tan, asin, acos, atan, lg, log, ln).");
}
[self setFunction:func
takesArcValue:takesArc
returnsArcValue:returnsArc];
_term = term;
}
return self;
}
- (void)setFunction:(double (*)(double))function
takesArcValue:(BOOL)takesArc
returnsArcValue:(BOOL)returnsArc
{
id __weak weakSelf = self;
_function = ^(NSDecimalNumber *value){
if (takesArc) {
value = [weakSelf convertToRadiantsIfNecessary:value];
}
NSDecimalNumber *returnValue = [[NSDecimalNumber alloc] initWithDouble:function(value.doubleValue)];
if (returnsArc) {
returnValue = [weakSelf convertToDegreesIfNecessary:returnValue];
}
return returnValue;
};
}
- (NSDecimalNumber *)convertToRadiantsIfNecessary:(NSDecimalNumber *)degrees
{
if ([MPMathRules sharedRules].isUsingDegrees) {
// * M_PI / 180
return [[degrees decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithInteger:180]];
} else {
return degrees;
}
}
- (NSDecimalNumber *)convertToDegreesIfNecessary:(NSDecimalNumber *)radiants
{
if ([MPMathRules sharedRules].isUsingDegrees) {
// * 180 / M_PI
return [[radiants decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:180]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]];
} else {
return radiants;
}
}
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
{
NSDecimalNumber *value = [self.term evaluate:error];
if (!value) {
return nil;
}
return _function(value);
}
@end

View File

@@ -1,27 +0,0 @@
//
// MPEvaluationContext.h
// MathPad
//
// Created by Kim Wittenburg on 12.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
@class MPEvaluationContext;
@interface MPEvaluationContext : NSObject
+ (MPEvaluationContext *)sharedContext;
- (void)push;
- (void)pop;
- (BOOL)defineVariable:(NSString *)variable
value:(NSDecimalNumber *)value;
- (void)undefineVariable:(NSString *)variable;
- (NSDecimalNumber *)valueForVariable:(NSString *)variable;
@end

View File

@@ -1,88 +0,0 @@
//
// MPEvaluationContext.m
// MathPad
//
// Created by Kim Wittenburg on 12.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPEvaluationContext.h"
@interface MPEvaluationContext ()
@property (nonatomic, strong) NSMutableArray *stack;
@end
@implementation MPEvaluationContext
static MPEvaluationContext *sharedContext;
+ (MPEvaluationContext *)sharedContext
{
if (!sharedContext) {
sharedContext = [[MPEvaluationContext alloc] init];
}
return sharedContext;
}
- (instancetype)init
{
if (sharedContext) {
return sharedContext;
}
self = [super init];
if (self) {
_stack = [[NSMutableArray alloc] init];
[self push];
[self defineVariable:@"e"
value:[[NSDecimalNumber alloc] initWithDouble:M_E]];
[self defineVariable:@"π"
value:[[NSDecimalNumber alloc] initWithDouble:M_PI]];
}
return self;
}
- (void)push
{
[self.stack addObject:[[NSMutableDictionary alloc] init]];
}
- (void)pop
{
[self.stack removeLastObject];
}
- (BOOL)defineVariable:(NSString *)variable
value:(NSDecimalNumber *)value
{
[self undefineVariable:variable];
if ([self isVariableDefined:variable]) {
return NO;
}
NSMutableDictionary *currentBindings = self.stack.lastObject;
currentBindings[variable] = value;
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;
NSDictionary *currentBindings;
NSDecimalNumber *value = nil;
while (!value && currentIndex > 0) {
currentBindings = self.stack[--currentIndex];
value = currentBindings[variable];
}
return value;
}
@end

View File

@@ -1,840 +0,0 @@
//
// MPExpression.h
// MathPad
//
// Created by Kim Wittenburg on 10.08.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
/*!
@const MPIllegalElementException
@brief 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
always contains the @c MPIllegalElementExceptionElementKey key in
its @c userInfo dictionary.
*/
FOUNDATION_EXPORT NSString *const MPIllegalElementException;
/*!
@const MPIllegalElementExceptionElementKey
@brief Predefined key for the invalid element that caused an exception
to be raised.
@discussion The invalid element can be of any type.
*/
FOUNDATION_EXPORT NSString *const MPIllegalElementExceptionElementKey;
/*!
@typedef MPReferenceFrame
@brief A reference frame describes what an @em item in an expression is.
@discussion Reference frames have to be passed in multiple methods of the @c
MPExpression class. You can convert between reference frames
using the following methods:
<pre>
@textblock
- convertIndexFromReferenceFrame:toReferenceFrame:
- convertIndexFromReferenceFrame:toReferenceFrame:offset:
@/textblock
</pre>
There are three different types of items (reference frames):
symbols, tokens and elements. A symbol is the smalles possible
unit in an expression. Thus a token consists of one or more
symbols. Similarly an element consists of one or more tokens.
@constant MPElementReferenceFrame
Specifies that items should be interpreted as elements. An
element is either a @c NSString object or a @c MPFunction object.
@constant MPSymbolReferenceFrame
Specifies that items should be interpreted as symbols. A symbol
can be a single character (represented by the @c NSString class)
or a @c MPFunction object.
@constant MPTokenReferenceFrame
Specifies that items should be interpreted as tokens. A token is
a logical unit of content in an expression. This can be a single
character (e.g. '+'), an arbitrary number of characters (e.g. a
number) or a @c MPFunction object.
All tokens conform to the @c MPToken protocol.
*/
typedef NS_ENUM(NSUInteger, MPReferenceFrame) {
MPElementReferenceFrame,
MPSymbolReferenceFrame,
MPTokenReferenceFrame
};
@class MPExpression, MPFunction, MPRangePath, MPParsedExpression;
@protocol MPExpressionElement, MPToken;
/*!
@class MPExpression
@brief An @c MPExpression object is the base object for any mathematical
expression.
@discussion Every expression consists of string elements (represented by the
@c NSString class) and function elements (represented by the @c
MPFunction class). Functions likewise can have expressions as
elements (also called <i>children</i> in this context). Both
expressions and functions are mutable. Through this organization
expression are organized in a tree-like structure called the
'expression tree'.
An expression can evaluate itself giving you either a
result or possibly an error if the expression was not constructed
correctly or could not be evaluated.
*/
@interface MPExpression : NSObject <NSCopying, NSCoding>
#pragma mark Creation Methods
/*!
@methodgroup Creation Methods
*/
/*!
@method init
@brief Initlializes a newly created expression.
@discussion This method is a convenience initializer to initialize an
expression with @c 0 elements.
@return An expression.
*/
- (instancetype)init;
/*!
@method initWithElement:
@brief Initializes a newly created expression with @c element.
@discussion This method is a convenience initializer to initialize an
expression with a single element.
@param element
The element to be added to the expression. The @c element will be
copied.
@return An expression initialized with @c element.
*/
- (instancetype)initWithElement:(id<MPExpressionElement>)element;
/*!
@method initWithElements:
@brief Initializes a newly created expression with the specified
elements.
@discussion All elements must conform to the @c MPExpressionElement protocol.
If one or more objects do not conform to that protocol an @c
MPIllegalElementException is raised.
This method is the designated initializer for the @c MPExpression
class.
@param elements
The elements that should be added to the expression. Every
element must conform to the @c MPExpressionElement protocol. Each
element is copied and the copy is then added to the expression.
The object in the @c elements array is not modified.
@return An expression containing @c elements.
*/
- (instancetype)initWithElements:(NSArray *)elements; /* designated initializer */
#pragma mark Querying Expressions
/*!
@methodgroup Querying Expressions
*/
/*!
@property parent
@brief The receiver's parent.
@discussion Expressions are organized in a tree-like structure. Through this
property an expression's containing function can be accessed.
@warning You should never set this property manually. If you are
implementing a custom subclass of @c MPFunction use the @c
MPFunctionAccessorImplementation macro.
@return The parent of the receiver or @c nil if the receiver is the root
expression.
*/
@property (nonatomic, weak) MPFunction *parent;
/*!
@method rootExpression
@brief Returns the root expression from the receiver's expression tree.
@discussion The root expression is the ultimate parent of all expressions and
functions in the expression tree. A root expression does not have
a parent.
@return The root expression from the receiver's expression tree.
*/
- (MPExpression *)rootExpression;
/*!
@method indexPath
@brief Returns the index path of the receiver in the expression tree.
@discussion The index path is calculated by going up the expression tree
collecting the respective index of the receiver. The indexes are
expressed in the element reference frame. If any of the indexes
exceed the respective receiver's bounds a @c NSRangeException is
raised.
@return The index path of the receiver in the expression tree.
*/
- (NSIndexPath *)indexPath;
/*!
@method countItemsInReferenceFrame:
@brief Returns the number of items in the receiver in the respective
reference frame.
@param referenceFrame
The reference frame that should be used to count the items.
@return The current number of items in the receiver counted in the
specified reference frame.
*/
- (NSUInteger)countItemsInReferenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@method itemAtIndex:referenceFrame:
@brief Returns the item at @c anIndex.
@discussion The item is not copied before it is returned. So be aware that
if you mutate a @c MPFunction object returned from this function
the changes will be reflected in the receiver.
@param anIndex
The index of the item. If the index is greater than the number of
items for the respective reference frame an @c NSRangeException
is raised.
@param referenceFrame
The reference frame that should be used to identify the item at
@c anIndex.
@return The item at @c anIndex.
*/
- (id)itemAtIndex:(NSUInteger)anIndex
referenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@method elementAtIndex:referenceFrame:
@brief Returns the element at the specified index.
@discussion An element is either a string or a function. If @c anIndex
specifies a position inside a string the complete string element
is returned.
This method does not return any item in any reference frame but
only complete elements. Use @c -itemAtIndex:referenceFrame: for
that purpose.
@param anIndex
The index of the element.
@param referenceFrame
The reference frame @c anIndex is specified in.
@return The item at @c anIndex.
*/
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)anIndex
referenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@method indexOfElement:
@brief Returns the index of @c element or @c NSNotFound if it was not
found.
@param element
The element to find.
@return The index of @c element expressed in the element reference frame.
*/
- (NSUInteger)indexOfElement:(id<MPExpressionElement>)element;
/*!
@method itemsInRange:referenceFrame:
@brief Returns an array of the items that are located in the specified
range.
@discussion The objects in the returned array are not copied before they are
returned. You should be aware of the fact that mutations to any
returned objects will be reflected in the receiver.
If the @c range exceeds the receiver's bounds an @c
NSRangeException is raised.
@param aRange
The requested range within the receiver's bounds.
@param referenceFrame
The referenceFrame @c aRange is expressed in.
@return An array of objects that conform to the @c MPExpressionElement
protocol (that is @c NSString objects and @c MPFunction objects).
The length of the returned array is equal to the length of the
specified range.
*/
- (NSArray *)itemsInRange:(NSRange)aRange
referenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@method allItemsInReferenceFrame:
@brief Returns an array of all items in the receiver for the specified
reference frame.
@discussion The elements in the returned array are not copied before they are
returned so be aware that mutations to any of the returned
objects will be reflected in the receiver.
@return An array of all items in the receiver.
*/
- (NSArray *)allItemsInReferenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@method elementAtIndexPath:
@brief Returns the element at the specified index path.
@discussion This method @em walks down the expression tree (including
functions) using the specified index path and finds the
corresponding element. The returned object can be an @c NSString,
a @c MPFunction or an @c MPExpression depending on the element @c
indexPath points to. If any of the indexes exceed the bounds of
the respective receiver an @c NSRangeException is raised.
If the index path does not contain any indexes the receiver
itself is returned.
@param indexPath
The index path the required object is located at. The indexes are
expressed in the element reference frame.
@return The element located at @c indexPath. The element is not copied
before it is returned. Be aware of the fact that any mutations
made to the returned object are reflected in the receiver.
*/
- (id)elementAtIndexPath:(NSIndexPath *)indexPath;
/*!
@method convertIndex:fromReferenceFrame:toReferenceFrame:
@brief Converts an index from one reference frame to another.
@discussion This method ignores any offsets into items the conversion between
reference frames can cause. If you are interested in the offset
use @c -convertIndex:fromReferenceFrame:toReferenceFrame:offset:
instead.
@param anIndex
The index to be converted.
@param fromReferenceFrame
The reference frame @c anIndex is specified in.
@param toReferenceFrame
The reference frame @c anIndex should be converted to.
@return An index specified in the @c toReferenceFrame. If @c anIndex
specifies a location inside an item in the @c toReferenceFrame
the index of the corresponding item is returned.
*/
- (NSUInteger)convertIndex:(NSUInteger)anIndex
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
toReferenceFrame:(MPReferenceFrame)toReferenceFrame;
/*!
@method convertIndex:fromReferenceFrame:toReferenceFrame:offset:
@brief Converts an index from one reference frame to another.
@discussion If @c anIndex specifies a location inside an item in the @c
toReferenceFrame the index of the corresponding item is returned.
@param anIndex
The index to be converted.
@param fromReferenceFrame
The reference frame @c anIndex is specified in.
@param toReferenceFrame
The reference frame @c anIndex should be converted to.
@param offset
This output parameter will be set to the number of symbols that
are between the location specified by @c anIndex and the index
that is actually returned. The offset is specified in the symbol
reference frame. If you are not interested in the offset pass
@c NULL.
@return An index specified in the @c toReferenceFrame corresponding to
@c anIndex.
*/
- (NSUInteger)convertIndex:(NSUInteger)anIndex
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
offset:(out NSUInteger *)offset;
/*!
@method convertRange:fromReferenceFrame:toReferenceFrame:
@brief Converts a range from one reference frame to another.
@discussion This method just converts the location and target of the range
separately using @c
-convertIndex:fromReferenceFrame:toReferenceFrame:. This method
ensures that the returned range definitely includes all items
that were specified by @c aRange.
This method ignores the possibility that the returned range may
include more than @a aRange. If you need that information use
@c -convertRange:fromReferenceFrame:toReferenceFrame:leadingOffset:trailingOffset:.
@param aRange
The range to be converted.
@param fromReferenceFrame
The reference frame @c aRange is specified in.
@param toReferenceFrame
The reference frame @c aRange is to be converted to.
@return A range in the @c toReferenceFrame that includes the the items
specified by @c aRange.
*/
- (NSRange)convertRange:(NSRange)aRange
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
toReferenceFrame:(MPReferenceFrame)toReferenceFrame;
/*!
@method convertRange:fromReferenceFrame:toReferenceFrame:leadingOffset:trailingOffset:
@brief Converts a range from one reference frame to another.
@discussion This method just converts the location and target of the range
separately using @c
-convertIndex:fromReferenceFrame:toReferenceFrame:. This method
ensures that the returned range definitely includes all items
that were specified by @c aRange.
@param aRange
The range to be converted.
@param fromReferenceFrame
The reference frame @c aRange is specified in.
@param toReferenceFrame
The reference frame @c aRange is to be converted to.
@param leadingOffset
The offset of the location of the returned range in respect to
the location of @c aRange.
@param trailingOffset
The offset of the last index in the range in respect to the last
index of @c aRange.
@return A range in the @c toReferenceFrame that includes the the items
specified by @c aRange.
*/
- (NSRange)convertRange:(NSRange)aRange
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
leadingOffset:(NSUInteger *)leadingOffset
trailingOffset:(NSUInteger *)trailingOffset;
#pragma mark Mutating Expressions
/*!
@methodgroup Mutating Expressions
*/
/*!
@method replaceItemsInRange:referenceFrame:withElements:
@brief Replaces the elements in the specified range with the contents of
the @c elements array.
@discussion This is the most primitive mutation method of @c MPExpression.
Every other mutating method utlimately must call this method.
After the receiver has been mutated the integrety of the receiver
is restored. That basically means that subsequent strings are
joined and empty strings removed. After restoring integrity the
receiver sends a @c
-changedElementsInIndexedRangePath:replacementLength: to
itself. For more information see the documentation on that
method.
@param aRange
The @c range of symbols (including functions) to replace.
@param referenceFrame
The reference frame @c aRange is specified in.
@param elements
The elements that should replace the symbols specified by @c
range.
*/
- (void)replaceItemsInRange:(NSRange)aRange
referenceFrame:(MPReferenceFrame)referenceFrame
withElements:(NSArray *)elements;
/*!
@method changedElementsInRangePath:replacementLength:
@brief Called after the receiver has been mutated.
@discussion This method does nothing more than notify it's parent that it has
been mutated at the receiver's index. If you need to know about
changes in an expression you should override this method instead
of @c -replaceSymbolsInRange:withElements: because this method
gives you information about the number of elements changed during
the mutation.
@param rangePath
The range path at which the receiver was changed starting at the
receiver. The range addressed by @c rangePath is expressed in the
element reference frame.
@param replacementLength
The number of elements replacing the elements specified by @c
rangePath (also specified in the element reference frame).
*/
- (void)changedElementsInRangePath:(MPRangePath *)rangePath
replacementLength:(NSUInteger)replacementLength;
/*!
@method subexpressionFromIndex:referenceFrame:
@brief Creates an expression from the items in the receiver from the
specified index to the end.
@discussion The items from the receiver are copied. Mutations to the returned
expression will not change the receiver.
@param from
The index from which to start constructing the subexpression.
@param referenceFrame
The reference frame @c from is specified in.
@return An expression containing the items from the specified index to
the end of the receiver.
*/
- (MPExpression *)subexpressionFromIndex:(NSUInteger)from
referenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@method subexpressionToIndex:referenceFrame:
@brief Creates an expression from the items in the receiver from the
first item to the end.
@discussion The items from the receiver are copied. Mutations to the returned
expression will not change the receiver.
@param to
The index of the first element not to include in the newly
constructed subexpression.
@param referenceFrame
The reference frame @c to is specified in.
@return An expression containing the items from the first item to the one
at the specified index.
*/
- (MPExpression *)subexpressionToIndex:(NSUInteger)to
referenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@method subexpressionWithRange:referenceFrame:
@brief Creates an expression from the items in the receiver within the
specified range.
@discussion The items from the receiver are copied. Mutations to the returned
expression will not change the receiver.
@param aRange
Specifies the items to be included in the newly created
subexpression.
@param referenceFrame
The reference frame @c aRange is specified in.
@return An expression containing the items in @c aRange.
*/
- (MPExpression *)subexpressionWithRange:(NSRange)aRange
referenceFrame:(MPReferenceFrame)referenceFrame;
#pragma mark Evaluating Expressions
/*!
@methodgroup Evaluating Expressions
*/
/*!
@method evaluateWitErrors:
@brief Evaluates the receiver.
@discussion This is a convenience method for evaluating an expression. If you
want more control over the evaluation process use @c -parse
instead.
@param errors
If the receiver (or any of its elements) contain syntax errors or
can not be evaluated this parameter is set to an appropriate
value. All items in the array are @c NSError instances. This
parameter is never set to an empty array.
Pass @c NULL if you are not interested in any errors that might
occur.
@return The result of the evaluation or @c nil of the receiver could not
be evaluated. In that case the @c error parameter is set to an
appropriate value.
*/
- (NSDecimalNumber *)evaluateWithErrors:(NSArray *__autoreleasing *)errors;
/*!
@method parse:
@brief Parses the receiver.
@discussion This is a convenience method that calls @c
-parseExpectingVariable:errors: with @c NO as the first argument.
@param errors
If the receiver (or any of its elements) contain syntax errors
this parameter is set to an appropriate value. All items in the
array are @c NSError instances. This parameter is never set to an
empty array.
Pass @c NULL if you are not interested in any errors that might
occur.
@return A @c MPParsedExpression object that represents the receiver and
can be evaluated or @c nil if an error occurs. In that case the
@c errors parameter is set to an array containing at least one
@c NSError instance.
*/
- (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors;
/*!
@method parseExpectingVariable:errors:
@brief Parses the receiver.
@param flag
If @c YES the receiver must (exept for whitespaces) begin with a
single letter followed by an equals sign. If it doesn't the
expression is considered invalid. Specify @c NO If the expression
should be evaluatable by itself.
@param errors
If the receiver (or any of its elements) contain syntax errors
this parameter is set to an appropriate value. All items in the
array are @c NSError instances. This parameter is never set to an
empty array.
Pass @c NULL if you are not interested in any errors that might
occur.
@return A @c MPParsedExpression object that represents the receiver and
can be evaluated or @c nil if an error occurs. In that case the
@c errors parameter is set to an array containing at least one
@c NSError instance.
*/
- (MPParsedExpression *)parseExpectingVariable:(BOOL)flag
errors:(NSArray *__autoreleasing *)errors;
@end
/*!
@category MPExpression (MPExpressionConvenience)
@brief This category defines convenience methods for the @c MPExpression
class.
@discussion All convenience methods are completely defined in terms of other
methods of the @c MPExpression class.
*/
@interface MPExpression (MPExpressionConvenience)
#pragma mark Querying Expressions
/*!
@methodgroup Querying Expressions
*/
/*!
@method countElements
@brief Returns the number of elements in the receiver.
@return The number of elements in the receiver expressed in the element
reference frame.
*/
- (NSUInteger)countElements;
/*!
@method countSymbols
@brief Returns the number of symbols in the receiver.
@return The number of symbols in the receiver expressed in the symbol
reference frame.
*/
- (NSUInteger)countSymbols;
/*!
@method countTokens
@brief Returns the number of tokens in the receiver.
@return The number of tokens in the receiver expressed in the token
reference frame.
*/
- (NSUInteger)countTokens;
/*!
@method elementAtIndex:
@brief Returns the element at the specified index.
@param anIndex
The index of the element specified in the element reference
frame.
@return The element at @c anIndex.
*/
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)anIndex;
/*!
@method symbolAtIndex:
@brief Returns the symbol at the specified index.
@param anIndex
The index of the symbol specified in the symbol reference frame.
@return The symbol at @c anIndex.
*/
- (id<MPExpressionElement>)symbolAtIndex:(NSUInteger)anIndex;
/*!
@method tokenAtIndex:
@brief Returns the token at the specified index.
@param anIndex
The index of the token specified in the token reference frame.
@return The token at @c anIndex.
*/
- (id<MPToken>)tokenAtIndex:(NSUInteger)anIndex;
#pragma mark Mutating Expressions
/*!
@methodgroup Mutating Expressions
*/
/*!
@method appendElement:
@brief Appends @c anElement to the receiver.
@param anElement
The element to append to the receiver.
*/
- (void)appendElement:(id<MPExpressionElement>)anElement;
/*!
@method appendElements:
@brief Appends the objects from @c elements to the receiver.
@param elements
The elements to append to the receiver.
*/
- (void)appendElements:(NSArray *)elements;
/*!
@method insertElement:atIndex:referenceFrame:
@brief Inserts @c anElement at the specified index.
@param anElement
The element to be inserted.
@param index
The index where to insert @c anElement.
@param referenceFrame
The reference frame @c index is specified in.
*/
- (void)insertElement:(id<MPExpressionElement>)anElement
atIndex:(NSUInteger)index
referenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@method insertElements:atIndex:referenceFrame:
@brief Inserts @c elements at the specified index.
@param elements
The elements to be inserted.
@param index
The index where to insert @c elements.
@param referenceFrame
The reference frame @c index is specified in.
*/
- (void)insertElements:(NSArray *)elements
atIndex:(NSUInteger)index
referenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@method deleteElementsInRange:
@brief Removes the elements specified by @c range from the receiver.
@param range
The range of items to remove from the receiver.
@param referenceFrame
The reference frame @c range is specified in.
*/
- (void)deleteElementsInRange:(NSRange)range
referenceFrame:(MPReferenceFrame)referenceFrame;
@end

View File

@@ -1,835 +0,0 @@
//
// MPExpression.m
// MathPad
//
// Created by Kim Wittenburg on 10.08.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPExpression.h"
#import "MPExpressionElement.h"
#import "MPFunction.h"
#import "MPRangePath.h"
#import "MPExpressionTokenizer.h"
#import "MPToken.h"
#import "MPExpressionParser.h"
#import "MPParsedExpression.h"
#import "NSIndexPath+MPAdditions.h"
NSString *const MPIllegalElementException = @"Illegal Element Exception";
NSString *const MPIllegalElementExceptionElementKey = @"MPIllegalElementExceptionElementKey";
@interface MPExpression () {
NSMutableArray * _elements;
}
/*!
@method tokens
@brief Private method. Returns an array containing all tokens from the
receiver.
@return All items in the @c MPTokenReferenceFrame.
*/
- (NSArray *)tokens;
/*!
@method validateElements:
@brief Private method. Checks whether all objects in the specified array
are valid expression elements.
@discussion If an object is not valid a @c MPIllegalElementException is
raised.
@param elements
The array of objects to be validated.
*/
- (void)validateElements:(NSArray *)elements;
/*!
@method fixElements
@brief Private method. Restores consistency in the receiver after a
change was made.
*/
- (void)fixElements;
/*!
@method _replaceSymbolsInRange:withElements:
@brief Private method. Replaces the symbols in the specified range with
the elements from the @c elements array.
@discussion This is the most primitive mutation method of the @c MPExpression
class.
@param range
The range of symbols to be replaced. The range is specified in
the symbol reference frame. If the range exceeds the receiver's
bounds a @c NSRangeException is raised.
@param elements
The elements that should replace the symbols in the specified
range.
*/
- (void)_replaceSymbolsInRange:(NSRange)range
withElements:(NSArray *)elements;
/*!
@method _splitElementsAtLocation:insertionIndex:
@brief Splits the receiver's elements at the specified @c location.
@discussion The split location can be inside a string element. In that case
the string is replaced with two other smaller strings.
Splitting the elements destroys the receiver's integrity. The
receiver of this message must also receive a @c -fixElements
message soon afterwards.
@param location
The index where to split the elements. Specified in the symbol
reference frame.
@param insertionIndex
Splitting elements may shift the indexes of the following
elements. This parameter is set to the index (specified in the
element reference frame) that should be used to insert a new
element at the location where the elements were previously
splitted.
@return @c YES if a string element was split into two smaller strings, @c
NO if the split @c location corresponds to a location between two
elements.
*/
- (BOOL)_splitElementsAtLocation:(NSUInteger)location
insertionIndex:(NSUInteger *)insertionIndex;
@end
@implementation MPExpression {
NSArray *_tokenCache;
NSRange _editedRange;
BOOL _didSplitStartOnEditing;
BOOL _didSplitEndOnEditing;
NSUInteger _replacementLength;
}
#pragma mark Creation Methods
- (instancetype)init
{
return [self initWithElements:@[]];
}
- (instancetype)initWithElement:(id<MPExpressionElement>)element
{
return [self initWithElements:@[element]];
}
- (instancetype)initWithElements:(NSArray *)elements
{
self = [super init];
if (self) {
[self validateElements:elements];
_elements = [[NSMutableArray alloc] initWithArray:elements
copyItems:YES];
[self fixElements];
}
return self;
}
#pragma mark Private Methods
- (NSArray *)tokens
{
if (!_tokenCache) {
_tokenCache = [MPExpressionTokenizer tokenizeExpression:self];
}
return _tokenCache;
}
- (void)validateElements:(NSArray *)elements
{
for (id element in elements) {
if (![element conformsToProtocol:@protocol(MPExpressionElement)]) {
@throw [NSException exceptionWithName:MPIllegalElementException
reason:@"Elements must conform to the MPExpressionElement protocol."
userInfo:@{MPIllegalElementExceptionElementKey: element}];
}
}
}
- (void)fixElements
{
for (NSUInteger index = 0; index < _elements.count; index++) {
id<MPExpressionElement> next = index+1 < _elements.count ? _elements[index+1] : nil;
id<MPExpressionElement> current = _elements[index];
if ([current isString]) {
if (current.length == 0) {
[_elements removeObjectAtIndex:index];
if (index >= _editedRange.location && index < NSMaxRange(_editedRange)) {
--_replacementLength;
}
--index;
} else if ([next isString]) {
NSString *new = [NSString stringWithFormat:@"%@%@", current, next];
[_elements replaceObjectsInRange:NSMakeRange(index, 2)
withObjectsFromArray:@[new]];
NSUInteger maxReplacementIndex = _editedRange.location + _replacementLength;
if (index == _editedRange.location-1 && !_didSplitStartOnEditing) {
--_editedRange.location;
++_editedRange.length;
if (index == maxReplacementIndex-1 && !_didSplitEndOnEditing) {
++_editedRange.length;
++_replacementLength;
}
} else if (index >= _editedRange.location && index < maxReplacementIndex - 1) {
--_replacementLength;
} else if (index == maxReplacementIndex-1 && !_didSplitEndOnEditing) {
++_editedRange.length;
}
--index;
}
} else {
[(MPFunction *)current setParent:self];
}
}
}
#pragma mark Querying Expressions
- (MPExpression *)rootExpression
{
if (self.parent == nil) {
return self;
}
return [self.parent rootExpression];
}
- (NSIndexPath *)indexPath
{
if (self.parent) {
NSUInteger selfIndex = [self.parent indexOfChild:self];
return [[self.parent indexPath] indexPathByAddingIndex:selfIndex];
} else {
return [[NSIndexPath alloc] init];
}
}
- (NSUInteger)countItemsInReferenceFrame:(MPReferenceFrame)referenceFrame
{
switch (referenceFrame) {
case MPElementReferenceFrame:
return _elements.count;
case MPSymbolReferenceFrame:
{
NSUInteger count = 0;
for (id<MPExpressionElement> element in _elements) {
count += element.length;
}
return count;
}
case MPTokenReferenceFrame:
return self.tokens.count;
}
}
- (id)itemAtIndex:(NSUInteger)anIndex
referenceFrame:(MPReferenceFrame)referenceFrame
{
switch (referenceFrame) {
case MPElementReferenceFrame:
return _elements[anIndex];
case MPSymbolReferenceFrame:
{
NSUInteger offsetInElement;
NSUInteger elementIndex = [self convertIndex:anIndex
fromReferenceFrame:MPSymbolReferenceFrame
toReferenceFrame:MPElementReferenceFrame
offset:&offsetInElement];
id<MPExpressionElement> element = [self elementAtIndex:elementIndex];
if ([element isFunction]) {
return element;
} else {
return [((NSString *)element) substringWithRange:NSMakeRange(offsetInElement, 1)];
}
}
case MPTokenReferenceFrame:
return self.tokens[anIndex];
}
}
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)anIndex
referenceFrame:(MPReferenceFrame)referenceFrame
{
NSUInteger elementIndex = [self convertIndex:anIndex
fromReferenceFrame:referenceFrame
toReferenceFrame:MPElementReferenceFrame];
return _elements[elementIndex];
}
#warning If multiple equal expressions exist errors may occur...
- (NSUInteger)indexOfElement:(id<MPExpressionElement>)element
{
return [_elements indexOfObject:element];
}
- (NSArray *)itemsInRange:(NSRange)aRange
referenceFrame:(MPReferenceFrame)referenceFrame
{
MPExpression *subexpression = [self subexpressionWithRange:aRange
referenceFrame:referenceFrame];
return [subexpression allItemsInReferenceFrame:referenceFrame];
}
- (NSArray *)allItemsInReferenceFrame:(MPReferenceFrame)referenceFrame
{
switch (referenceFrame) {
case MPElementReferenceFrame:
return _elements;
case MPSymbolReferenceFrame:
{
NSMutableArray *symbols = [[NSMutableArray alloc] init];
for (id<MPExpressionElement> element in _elements) {
if ([element isString]) {
for (NSUInteger i = 0; i < [element length]; i++) {
NSString *ichar = [NSString stringWithFormat:@"%c", [((NSString *)element) characterAtIndex:i]];
[symbols addObject:ichar];
}
} else {
[symbols addObject:element];
}
}
}
case MPTokenReferenceFrame:
return self.tokens;
}
}
- (id)elementAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.length == 0) {
return self;
}
id<MPExpressionElement> element = _elements[[indexPath indexAtPosition:0]];
if (indexPath.length == 1) {
return element;
}
if ([element isFunction]) {
return [(MPFunction *)element elementAtIndexPath:[indexPath indexPathByRemovingFirstIndex]];
}
// TODO: Raise appropriate exeption.
return nil;
}
- (NSUInteger)convertIndex:(NSUInteger)anIndex
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
{
return [self convertIndex:anIndex
fromReferenceFrame:fromReferenceFrame
toReferenceFrame:toReferenceFrame
offset:NULL];
}
- (NSUInteger)convertIndex:(NSUInteger)anIndex
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
offset:(NSUInteger *)offset
{
if (fromReferenceFrame == toReferenceFrame || anIndex == 0) {
if (offset) {
*offset = 0;
}
return anIndex;
}
NSUInteger symbolIndex __block = 0;
switch (fromReferenceFrame) {
case MPElementReferenceFrame:
[_elements enumerateObjectsUsingBlock:^(id<MPExpressionElement> obj, NSUInteger idx, BOOL *stop) {
symbolIndex += obj.length;
*stop = idx >= anIndex - 1;
}];
break;
case MPSymbolReferenceFrame:
symbolIndex = anIndex;
break;
case MPTokenReferenceFrame:
[self.tokens enumerateObjectsUsingBlock:^(id<MPToken> obj, NSUInteger idx, BOOL *stop) {
symbolIndex += obj.range.length;
*stop = idx >= anIndex - 1;
}];
break;
}
switch (toReferenceFrame) {
case MPElementReferenceFrame:
{
NSUInteger totalLength = 0;
NSUInteger elementIndex = 0;
id<MPExpressionElement> element;
while (totalLength < symbolIndex) {
element = _elements[elementIndex++];
totalLength += element.length;
}
--elementIndex;
NSUInteger offsetInElement = element.length - totalLength + symbolIndex;
if (offsetInElement == element.length) {
offsetInElement = 0;
elementIndex++;
}
if (offset) {
*offset = offsetInElement;
}
return elementIndex;
}
case MPSymbolReferenceFrame:
if (offset) {
*offset = 0;
}
return symbolIndex;
case MPTokenReferenceFrame:
{
NSUInteger totalLength = 0;
NSUInteger tokenIndex = 0;
id<MPToken> token;
while (totalLength < symbolIndex) {
token = self.tokens[tokenIndex++];
totalLength += token.range.length;
}
--tokenIndex;
NSUInteger offsetInToken = token.range.length - totalLength + symbolIndex;
if (offsetInToken == token.range.length) {
offsetInToken = 0;
tokenIndex++;
}
if (offset) {
*offset = offsetInToken;
}
return tokenIndex;
}
}
}
- (NSRange)convertRange:(NSRange)aRange
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
{
return [self convertRange:aRange
fromReferenceFrame:fromReferenceFrame
toReferenceFrame:toReferenceFrame
leadingOffset:NULL
trailingOffset:NULL];
}
- (NSRange)convertRange:(NSRange)aRange
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
leadingOffset:(NSUInteger *)leadingOffset
trailingOffset:(NSUInteger *)trailingOffset
{
NSUInteger start = [self convertIndex:aRange.location
fromReferenceFrame:fromReferenceFrame
toReferenceFrame:toReferenceFrame
offset:leadingOffset];
NSUInteger end = [self convertIndex:NSMaxRange(aRange)
fromReferenceFrame:fromReferenceFrame
toReferenceFrame:toReferenceFrame
offset:trailingOffset];
return NSMakeRange(start, end - start);
}
#pragma mark Mutating Expressions
- (void)replaceItemsInRange:(NSRange)aRange
referenceFrame:(MPReferenceFrame)referenceFrame
withElements:(NSArray *)elements
{
NSUInteger start = [self convertIndex:aRange.location
fromReferenceFrame:referenceFrame
toReferenceFrame:MPSymbolReferenceFrame];
NSUInteger end = [self convertIndex:NSMaxRange(aRange)
fromReferenceFrame:referenceFrame
toReferenceFrame:MPSymbolReferenceFrame];
[self _replaceSymbolsInRange:NSMakeRange(start, end - start)
withElements:elements];
}
- (void)_replaceSymbolsInRange:(NSRange)range
withElements:(NSArray *)elements
{
if (NSMaxRange(range) > [self countItemsInReferenceFrame:MPSymbolReferenceFrame]) {
@throw [NSException exceptionWithName:NSRangeException
reason:@"Range out of bounds of expression"
userInfo:nil];
}
[self validateElements:elements];
// Locate the position, split the elements
NSUInteger startIndex; // startIndex is inclusive
BOOL didSplitStart = NO;
if (_elements.count == 0) {
startIndex = 0;
} else {
didSplitStart = [self _splitElementsAtLocation:range.location
insertionIndex:&startIndex];
}
NSUInteger endIndex; // endIndex is exclusive
BOOL didSplitEnd = [self _splitElementsAtLocation:NSMaxRange(range)
insertionIndex:&endIndex];
// Perform the replacement
NSMutableArray *newElements = [[NSMutableArray alloc] initWithArray:elements
copyItems:YES];
NSArray *removedElements = [_elements subarrayWithRange:NSMakeRange(startIndex, endIndex-startIndex)];
[_elements replaceObjectsInRange:NSMakeRange(startIndex, endIndex-startIndex)
withObjectsFromArray:newElements];
for (id<MPExpressionElement> element in removedElements) {
if ([element isFunction]) {
((MPFunction *)element).parent = nil;
}
}
_tokenCache = nil;
NSUInteger editLocation = startIndex - (didSplitStart ? 1 : 0);
NSUInteger editLength = endIndex - startIndex;
if (range.length == 0 && didSplitStart) {
editLength = 1;
}
_editedRange = NSMakeRange(editLocation, editLength);
_didSplitStartOnEditing = didSplitStart;
_didSplitEndOnEditing = didSplitEnd;
_replacementLength = elements.count;
if (didSplitStart) {
_replacementLength++;
if (range.length == 0) {
_replacementLength++;
}
}
if (didSplitEnd) {
_replacementLength++;
}
[self fixElements];
[self changedElementsInRangePath:[[MPRangePath alloc] initWithRange:_editedRange]
replacementLength:_replacementLength];
}
- (BOOL)_splitElementsAtLocation:(NSUInteger)location
insertionIndex:(NSUInteger *)insertionIndex
{
if (location == 0) {
*insertionIndex = 0;
return NO;
}
NSUInteger splitOffset;
NSUInteger splitElementIndex = [self convertIndex:location
fromReferenceFrame:MPSymbolReferenceFrame
toReferenceFrame:MPElementReferenceFrame
offset:&splitOffset];
if (splitOffset != 0) {
NSString *splitElement = (NSString *)_elements[splitElementIndex];
NSString *leftPart = [splitElement substringToIndex:splitOffset];
NSString *rightPart = [splitElement substringFromIndex:splitOffset];
[_elements replaceObjectsInRange:NSMakeRange(splitElementIndex, 1)
withObjectsFromArray:@[leftPart, rightPart]];
++splitElementIndex;
}
*insertionIndex = splitElementIndex;
return splitOffset != 0;
}
- (void)changedElementsInRangePath:(MPRangePath *)rangePath
replacementLength:(NSUInteger)replacementLength
{
NSUInteger selfIndex = [self.parent indexOfChild:self];
MPRangePath *newPath = MPMakeRangePath([rangePath.location indexPathByPreceedingIndex:selfIndex], rangePath.length);
[self.parent didChangeElementsInRangePath:newPath
replacementLength:replacementLength];
}
- (MPExpression *)subexpressionFromIndex:(NSUInteger)from
referenceFrame:(MPReferenceFrame)referenceFrame
{
return [self subexpressionWithRange:NSMakeRange(from, [self countItemsInReferenceFrame:referenceFrame] - from)
referenceFrame:referenceFrame];
}
- (MPExpression *)subexpressionToIndex:(NSUInteger)to
referenceFrame:(MPReferenceFrame)referenceFrame
{
return [self subexpressionWithRange:NSMakeRange(0, to)
referenceFrame:referenceFrame];
}
- (MPExpression *)subexpressionWithRange:(NSRange)aRange
referenceFrame:(MPReferenceFrame)referenceFrame
{
MPExpression *subexpression = [self copy];
NSRange preceedingRange = NSMakeRange(0, aRange.location);
NSUInteger firstOut = NSMaxRange(aRange);
NSRange exceedingRange = NSMakeRange(firstOut, [self countItemsInReferenceFrame:referenceFrame] - firstOut);
[subexpression deleteElementsInRange:exceedingRange
referenceFrame:referenceFrame];
[subexpression deleteElementsInRange:preceedingRange
referenceFrame:referenceFrame];
return subexpression;
}
#pragma mark Evaluating Expressions
- (NSDecimalNumber *)evaluateWithErrors:(NSArray *__autoreleasing *)errors
{
NSArray *parsingErrors;
MPParsedExpression *parsedExpression = [self parse:&parsingErrors];
NSError *evaluationError;
NSDecimalNumber *result = parsedExpression ? [parsedExpression evaluate:&evaluationError] : nil;
if (errors && (parsedExpression || evaluationError)) {
NSMutableArray *localErrors = [[NSMutableArray alloc] initWithCapacity:parsingErrors.count+1];
if (parsingErrors) {
[localErrors addObjectsFromArray:parsingErrors];
}
if (evaluationError) {
[localErrors addObject:evaluationError];
}
*errors = localErrors;
}
return result;
}
- (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors
{
return [self parseExpectingVariable:NO
errors:errors];
}
- (MPParsedExpression *)parseExpectingVariable:(BOOL)flag
errors:(NSArray *__autoreleasing *)errors
{
return [[[MPExpressionParser alloc] initWithExpression:self] parseExpectingVariableDefinition:flag
errors:errors];
}
#pragma mark Basic NSObject Methods
- (NSString *)description
{
#warning Bad Implementation
NSMutableString *description = [[NSMutableString alloc] init];
NSUInteger index = 0;
for (id element in _elements) {
if ([element isString]) {
NSMutableString *correctedSymbol = [[element stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] mutableCopy];
// Prefix operator
if (element != _elements[0]) {
unichar prefix = [correctedSymbol characterAtIndex:0];
if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:prefix]) {
[correctedSymbol insertString:@"*"
atIndex:0];
}
}
// Suffix operator
if (element != [_elements lastObject]) {
unichar suffix = [correctedSymbol characterAtIndex:correctedSymbol.length-1];
if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:suffix]) {
[correctedSymbol appendString:@"*"];
}
}
[description appendString:correctedSymbol];
} else if (index > 0 && [_elements[index-1] isKindOfClass:[MPFunction class]]) {
[description appendFormat:@"*%@", [element description]];
} else {
[description appendString:[element description]];
}
index++;
}
return description;
}
- (NSUInteger)hash
{
return [_elements hash];
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone
{
MPExpression *copy = [[MPExpression allocWithZone:zone] initWithElements:_elements];
return copy;
}
#pragma mark - NSCoding
- (id)initWithCoder:(NSCoder *)aDecoder
{
// TODO: Test Coding
return [self initWithElements:[aDecoder decodeObject]];
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:_elements];
}
@end
@implementation MPExpression (MPExpressionConvenience)
#pragma mark Querying Expressions
- (NSUInteger)countElements
{
return [self countItemsInReferenceFrame:MPElementReferenceFrame];
}
- (NSUInteger)countSymbols
{
return [self countItemsInReferenceFrame:MPSymbolReferenceFrame];
}
- (NSUInteger)countTokens
{
return [self countItemsInReferenceFrame:MPTokenReferenceFrame];
}
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)anIndex
{
return [self itemAtIndex:anIndex
referenceFrame:MPElementReferenceFrame];
}
- (id<MPExpressionElement>)symbolAtIndex:(NSUInteger)anIndex
{
return [self itemAtIndex:anIndex
referenceFrame:MPSymbolReferenceFrame];
}
- (id<MPToken>)tokenAtIndex:(NSUInteger)anIndex
{
return [self itemAtIndex:anIndex
referenceFrame:MPTokenReferenceFrame];
}
#pragma mark Mutating Expressions
- (void)appendElement:(id<MPExpressionElement>)anElement
{
[self appendElements:@[anElement]];
}
- (void)appendElements:(NSArray *)elements
{
[self replaceItemsInRange:NSMakeRange([self countItemsInReferenceFrame:MPSymbolReferenceFrame], 0)
referenceFrame:MPSymbolReferenceFrame
withElements:elements];
}
- (void)insertElement:(id<MPExpressionElement>)anElement
atIndex:(NSUInteger)index
referenceFrame:(MPReferenceFrame)referenceFrame
{
[self insertElements:@[anElement]
atIndex:index
referenceFrame:referenceFrame];
}
- (void)insertElements:(NSArray *)elements
atIndex:(NSUInteger)index
referenceFrame:(MPReferenceFrame)referenceFrame
{
[self replaceItemsInRange:NSMakeRange(index, 0)
referenceFrame:referenceFrame
withElements:elements];
}
- (void)deleteElementsInRange:(NSRange)range
referenceFrame:(MPReferenceFrame)referenceFrame
{
[self replaceItemsInRange:range
referenceFrame:referenceFrame
withElements:@[]];
}
@end

View File

@@ -1,57 +0,0 @@
//
// MPExpressionElement.h
// MathPad
//
// Created by Kim Wittenburg on 10.08.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
/*!
@protocol MPExpressionElement
@brief This protocol defines the functionality an element in an
expression must have.
*/
@protocol MPExpressionElement <NSObject, NSCopying, NSCoding>
/*!
@method isString
@brief Returns wether the receiver is a string.
@discussion A string is defined by being an instance of @c NSString. If this
method returns @c YES you can be sure it is an @c NSString
instance.
@return @c YES if the receiver is a string, @c NO otherwise.
*/
- (BOOL)isString;
/*!
@method isFunction
@brief Returns wether the receiver is a function.
@discussion A function is defined by being an instance of @c MPFunction. If
this method returns @c YES you can be sure it is a @c MPFunction
instance.
@return @c YES if the receiver is a function, @c NO otherwise.
*/
- (BOOL)isFunction;
/*!
@method length
@brief Calculates the length of the receiver.
@discussion The length of a @c MPExpressionElement is the number of symbols
it consists of. For strings this is the number of characters in
it. Functions have a length of @c 1.
@return The receiver's length.
*/
- (NSUInteger)length;
@end

View File

@@ -1,22 +0,0 @@
//
// MPExpressionLayout.h
// MathPad
//
// Created by Kim Wittenburg on 07.08.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPLayout.h"
@class MPExpressionLayout, MPExpression, MPFunctionLayout;
@interface MPExpressionLayout : MPLayout
- (instancetype)initWithExpression:(MPExpression *)expression
parent:(MPFunctionLayout *)parent;
@property (readonly, nonatomic, weak) MPExpression *expression;
@end

View File

@@ -1,297 +0,0 @@
//
// MPExpressionLayout.m
// MathPad
//
// Created by Kim Wittenburg on 07.08.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPExpressionLayout.h"
#import "MPExpression.h"
#import "MPExpressionElement.h"
#import "MPPowerFunction.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
{
id element = [self.expression elementAtIndex:index];
if (![element isString]) {
return NULL;
}
NSRange tokensRange = [self.expression convertRange:NSMakeRange(index, 1)
fromReferenceFrame:MPElementReferenceFrame
toReferenceFrame:MPTokenReferenceFrame];
NSArray *tokens = [self.expression itemsInRange:tokensRange
referenceFrame:MPTokenReferenceFrame];
id lineObject = [self cachableObjectForIndex:index generator:^id{
NSMutableAttributedString *text = [[NSMutableAttributedString alloc] init];
for (id<MPToken> token in tokens) {
NSFont *font;
if (token.tokenType == MPElementaryFunctionToken) {
font = [self specialFontWithSize:self.contextInferredFontSize];
} else {
font = [self normalFontWithSize:self.contextInferredFontSize];
}
NSAttributedString *tokenText = [[NSAttributedString alloc] initWithString:token.stringValue
attributes:@{NSFontAttributeName: font}];
[text appendAttributedString:tokenText];
}
CFAttributedStringRef attributedString = CFBridgingRetain(text);
CTLineRef line = CTLineCreateWithAttributedString(attributedString);
CFRelease(attributedString);
return CFBridgingRelease(line);
}];
return (__bridge CTLineRef)lineObject;
}
@end
@implementation MPExpressionLayout
# pragma mark Creation Methods
- (instancetype)initWithExpression:(MPExpression *)expression
parent:(MPFunctionLayout *)parent
{
self = [super initWithParent:parent];
if (self) {
_expression = expression;
}
return self;
}
#pragma mark Cache Methods
- (NSUInteger)numberOfChildren
{
return self.expression.countElements;
}
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index
{
id cachedObject = [self cachableObjectForIndex:index generator:^id{
MPFunction *function = (MPFunction *)[self.expression elementAtIndex:index];
MPFunctionLayout *layout = [MPFunctionLayout functionLayoutForFunction:function
parent:self];
layout.flipped = self.flipped;
layout.usesSmallSize = self.usesSmallSize;
return layout;
}];
if ([cachedObject isKindOfClass:[MPLayout class]]) {
return cachedObject;
}
return nil;
}
- (NSRect)boundsOfElementAtIndex:(NSUInteger)index
{
id symbol = [self.expression elementAtIndex:index];
if ([symbol isString]) {
CTLineRef line = [self lineForElementAtIndex:index];
CFRetain(line);
CGRect bounds = CTLineGetBoundsWithOptions(line, 0);
CFRelease(line);
return bounds;
} else {
return [self childLayoutAtIndex:index].bounds;
}
}
#pragma mark Drawing Methods
- (NSRect)generateBounds
{
if (self.expression.countElements == 0) {
return NSMakeRect(0, kMPEmptyBoxYOrigin, kMPEmptyBoxWidth, kMPEmptyBoxHeight);
}
CGFloat x = 0, y = 0, width = 0, height = 0;
for (NSUInteger index = 0; index < self.expression.countElements; index++) {
NSRect elementBounds = [self boundsOfElementAtIndex:index];
width += elementBounds.size.width;
height = MAX(height, elementBounds.size.height);
y = MIN(y, elementBounds.origin.y);
}
return NSMakeRect(x, y, width, height);
}
- (NSRect)boundingRectForRange:(NSRange)range
{
NSUInteger startOffset;
NSUInteger startElementIndex = [self.expression convertIndex:range.location
fromReferenceFrame:MPSymbolReferenceFrame
toReferenceFrame:MPElementReferenceFrame
offset:&startOffset];
// Calculate x position
CGFloat x = 0, width = 0;
for (NSUInteger index = 0; index < startElementIndex; index++) {
x += [self boundsOfElementAtIndex:index].size.width;
}
if (startOffset > 0) {
CTLineRef line = [self lineForElementAtIndex:startElementIndex];
CFRetain(line);
CGFloat xOffset = CTLineGetOffsetForStringIndex(line, startOffset, NULL);
x += xOffset;
width += CTLineGetBoundsWithOptions(line, 0).size.width - xOffset;
CFRelease(line);
} else if (startElementIndex < self.expression.countElements) { // Otherwise the selection is after the last symbol
width += [self boundsOfElementAtIndex:startElementIndex].size.width;
}
// If we search the caret position we are done
if (range.length == 0) {
return NSMakeRect(x, self.bounds.origin.y, 0, self.bounds.size.height);
}
NSUInteger endOffset;
NSUInteger endElementIndex = [self.expression convertIndex:NSMaxRange(range)
fromReferenceFrame:MPSymbolReferenceFrame
toReferenceFrame:MPElementReferenceFrame
offset:&endOffset];
// Selection is inside of one string element
if (startElementIndex == endElementIndex) {
CTLineRef line = [self lineForElementAtIndex:endElementIndex];
CFRetain(line);
CGFloat xStart = CTLineGetOffsetForStringIndex(line, startOffset, NULL);
CGFloat xEnd = CTLineGetOffsetForStringIndex(line, endOffset, NULL);
width = xEnd - xStart;
CFRelease(line);
return NSMakeRect(x, self.bounds.origin.y, width, self.bounds.size.height);
}
// Calculate width
for (NSUInteger index = startElementIndex + 1; index < endElementIndex; index++) {
width += [self boundsOfElementAtIndex:index].size.width;
}
if (endOffset > 0) {
CTLineRef line = [self lineForElementAtIndex:endElementIndex];
CFRetain(line);
width += CTLineGetOffsetForStringIndex(line, endOffset, NULL);
CFRelease(line);
}
return NSMakeRect(x, self.bounds.origin.y, width, self.bounds.size.height);
}
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index
{
CGFloat x = 0;
for (NSUInteger i = 0; i < index; i++) {
x += [self boundsOfElementAtIndex:i].size.width;
}
return NSMakePoint(x, 0);
}
- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point
{
NSUInteger currentPosition = 0;
for (NSUInteger index = 0; index < self.expression.countElements; index++) {
NSRect elementBounds = [self boundsOfElementAtIndex:index];
NSPoint elementOffset = [self offsetOfChildLayoutAtIndex:index];
elementBounds.origin.x += elementOffset.x;
elementBounds.origin.y += elementOffset.y;
// Only the horizontal location is to consider for hit testing
elementBounds.size.height = CGFLOAT_MAX;
id<MPExpressionElement> element = [self.expression elementAtIndex:index];
if (NSMouseInRect(point, elementBounds, self.flipped)) {
NSPoint pointInElement = NSMakePoint(point.x - elementOffset.x, point.y + elementOffset.y);
if ([element isString]) {
CTLineRef line = [self lineForElementAtIndex:index];
CFRetain(line);
CFIndex localIndex = CTLineGetStringIndexForPosition(line, pointInElement);
CFRelease(line);
return [NSIndexPath indexPathWithIndex:currentPosition+localIndex];
} else {
NSIndexPath *subPath = [[self childLayoutAtIndex:index] indexPathForMousePoint:pointInElement];
if (subPath.length == 1) {
// A single index is used to communicate back wether the
// selection should be before or after the function.
// A 0 means before, a 1 means after.
return [NSIndexPath indexPathWithIndex:currentPosition + [subPath indexAtPosition:0]];
} else {
return [subPath indexPathByPreceedingIndex:index];
}
}
}
currentPosition += element.length;
}
if (point.x < self.bounds.size.width / 2) {
return [NSIndexPath indexPathWithIndex:0];
} else {
return [NSIndexPath indexPathWithIndex:self.expression.countSymbols];
}
}
- (BOOL)drawsChildrenManually
{
return YES;
}
- (void)draw
{
// Get the current context
CGContextRef context =
(CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
CGContextSaveGState(context);
[[NSColor textColor] set];
if (self.expression.countElements == 0) {
CGContextRestoreGState(context);
NSBezierPath *path = [NSBezierPath bezierPathWithRect:NSMakeRect(0, kMPEmptyBoxDrawingYOrigin, kMPEmptyBoxDrawingWidth, kMPEmptyBoxDrawingHeight)];
path.lineWidth = 0.5;
[path stroke];
return;
}
// Set the text matrix
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
// Track the x position and the bounds of the last element
CGFloat x = 0;
NSRect lastElementBounds = NSMakeRect(0, kMPEmptyBoxYOrigin, kMPEmptyBoxWidth, kMPEmptyBoxHeight);
for (NSUInteger index = 0; index < self.expression.countElements; index++) {
// The current element
id element = [self.expression elementAtIndex:index];
NSRect elementBounds = [self boundsOfElementAtIndex:index];
if ([element isString]) {
// Get the line to draw
CTLineRef line = [self lineForElementAtIndex:index];
CFRetain(line);
// Move to the appropriate position
CGContextSetTextPosition(context, x, 0);
// Perform the drawing
CTLineDraw(line, context);
CFRelease(line);
} else {
// Let the child layout draw itself
MPLayout *layout = [self childLayoutAtIndex:index];
if ([layout isKindOfClass:[MPPowerFunctionLayout class]]) {
((MPPowerFunctionLayout *)layout).baseBounds = lastElementBounds;
}
[layout drawAtPoint:NSMakePoint(x, 0)];
}
x += elementBounds.size.width;
lastElementBounds = elementBounds;
}
CGContextRestoreGState(context);
}
@end

View File

@@ -1,24 +0,0 @@
//
// MPExpressionParser.h
// MathPad
//
// Created by Kim Wittenburg on 16.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
@class MPExpressionParser, MPExpression, MPParsedExpression;
@interface MPExpressionParser : NSObject
@property (readonly, nonatomic, strong) MPExpression *expression;
- (instancetype)initWithExpression:(MPExpression *)expression;
- (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors;
- (MPParsedExpression *)parseExpectingVariableDefinition:(BOOL)flag
errors:(NSArray *__autoreleasing *)errors;
@end

View File

@@ -1,431 +0,0 @@
//
// MPExpressionParser.m
// MathPad
//
// Created by Kim Wittenburg on 16.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPExpressionParser.h"
#import "MPExpression.h"
#import "MPTerm.h"
#import "MPToken.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 "MPNumber.h"
#import "MPVariable.h"
#import "MPFactorialTerm.h"
#define success() state = 0
#define fail() self.errorTokenIndex = self.currentTokenIndex; state = -1
@interface MPExpressionParser ()
@property (nonatomic, strong) NSArray *tokens;
@property (nonatomic) NSUInteger currentTokenIndex;
@property (nonatomic, strong) NSMutableArray *errors;
@property (nonatomic, strong) NSMutableArray *summands;
@property (nonatomic) BOOL summandNegative;
@property (nonatomic, strong) NSMutableArray *factors;
@property (nonatomic) BOOL factorNegative;
@property (nonatomic, strong) NSMutableArray *functionStack;
@property (nonatomic, strong) NSMutableArray *valueGroup;
@property (nonatomic, strong) MPTerm *value;
@property (nonatomic) NSUInteger errorTokenIndex;
@end
@implementation MPExpressionParser
- (instancetype)initWithExpression:(MPExpression *)expression
{
self = [super init];
if (self) {
_tokens = [expression allItemsInReferenceFrame:MPTokenReferenceFrame];
_expression = expression;
}
return self;
}
- (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors
{
return [self parseExpectingVariableDefinition:NO
errors:errors];
}
- (MPParsedExpression *)parseExpectingVariableDefinition:(BOOL)flag
errors:(NSArray *__autoreleasing *)errors
{
MPParsedExpression *result = [[MPParsedExpression alloc] init];
self.currentTokenIndex = 0;
BOOL hasVariableDefinition = NO;
NSString *variableName = nil;
[self skipWhitespaces];
if (self.currentToken.tokenType == MPVariableToken) {
variableName = self.currentToken.stringValue;
[self nextToken];
[self skipWhitespaces];
if (self.currentToken.tokenType == MPEqualsToken) {
[self nextToken];
hasVariableDefinition = YES;
}
}
if (flag && hasVariableDefinition) {
result.definedVariable = variableName;
} else if (flag) {
[self addErrorWithCode:3 localizedDescription:NSLocalizedString(@"Expected Variable Definition.", nil) useRange:NO];
if (errors) {
*errors = self.errors;
}
return nil;
} else if (hasVariableDefinition) {
[self addErrorWithCode:4 localizedDescription:NSLocalizedString(@"Unexpected Variable Definition.", nil) useRange:NO];
if (errors) {
*errors = self.errors;
}
return nil;
} else {
self.currentTokenIndex = 0;
}
[self skipWhitespaces];
if (self.currentTokenIndex >= self.tokens.count) {
[self addErrorWithCode:5 localizedDescription:NSLocalizedString(@"Empty Expression.", nil) useRange:NO];
if (errors) {
*errors = self.errors;
}
return nil;
}
[self runParserMachine];
if ([self errorOccured]) {
if (errors) {
*errors = self.errors;
}
return nil;
}
result.term = [[MPSumTerm alloc] initWithSummands:self.summands];
return result;
}
- (void)runParserMachine
{
NSInteger state = 1;
NSInteger backtraceState = 0;
NSUInteger backtraceTokenIndex = self.currentTokenIndex;
BOOL alternateRoute = NO;
while (state != 0) {
switch (state) {
case -1:
state = backtraceState;
self.currentTokenIndex = backtraceTokenIndex;
alternateRoute = YES;
break;
case 1:
[self skipWhitespaces];
if (self.currentToken.tokenType == MPOperatorListToken) {
self.factorNegative = [self parseOperatorList:self.currentToken];
[self nextToken];
state = 2;
} else {
self.factorNegative = NO;
state = 2;
}
break;
case 2:
[self skipWhitespaces];
if (self.currentToken.tokenType == MPElementaryFunctionToken) {
[self.functionStack addObject:self.currentToken.stringValue];
[self nextToken];
state = 2;
} else {
state = 3;
}
break;
case 3:
[self skipWhitespaces];
if (self.currentToken.tokenType == MPGenericFunctionToken) {
NSArray *errors;
self.value = [[MPFunctionTerm alloc] initWithFunction:(MPFunction *)self.currentToken
errors:&errors];
if (!self.value) {
[self.errors addObjectsFromArray:errors];
}
[self nextToken];
[self runSuffixMachine];
if (self.value) {
[self.valueGroup addObject:self.value];
}
state = 6;
} else {
self.value = nil;
state = 4;
}
break;
case 4:
if (self.currentToken.tokenType == MPNumberToken) {
self.value = [[MPNumber alloc] initWithNumber:[self parseNumber:self.currentToken]];
[self nextToken];
[self runSuffixMachine];
[self.valueGroup addObject:self.value];
state = 5;
} else if (self.currentToken.tokenType == MPVariableToken) {
self.value = [[MPVariable alloc] initWithVariableName:[self parseVariable:self.currentToken]];
[self nextToken];
[self runSuffixMachine];
[self.valueGroup addObject:self.value];
state = 5;
} else {
fail();
}
break;
case 5:
if (alternateRoute) {
alternateRoute = NO;
state = 6;
} else {
backtraceState = 5;
backtraceTokenIndex = self.currentTokenIndex;
state = 4;
}
break;
case 6:
[self collapseFactor];
[self skipWhitespaces];
backtraceState = 6;
backtraceTokenIndex = self.currentTokenIndex;
if (!alternateRoute && self.currentToken.tokenType == MPOperatorListToken) {
[self collapseSummand];
self.summandNegative = [self parseOperatorList:self.currentToken];
[self nextToken];
state = 1;
} else {
state = 7;
}
break;
case 7:
if (alternateRoute) {
[self collapseSummand];
alternateRoute = NO;
success();
} else {
backtraceState = 7;
backtraceTokenIndex = self.currentTokenIndex;
state = 8;
}
break;
case 8:
[self skipWhitespaces];
if (self.currentToken.tokenType == MPMultiplicationSymbolToken) {
[self nextToken];
state = 1;
} else {
state = 1;
}
break;
default:
// Should never get here
break;
}
}
}
- (void)skipWhitespaces
{
while ([self currentToken] != nil && [self currentToken].tokenType == MPWhitespaceToken) {
[self nextToken];
}
}
- (id<MPToken>)currentToken
{
if (self.currentTokenIndex >= self.tokens.count) {
return nil;
}
return self.tokens[self.currentTokenIndex];
}
- (void)nextToken
{
self.currentTokenIndex++;
}
- (NSMutableArray *)errors
{
if (!_errors) {
_errors = [[NSMutableArray alloc] init];
}
return _errors;
}
- (NSMutableArray *)summands
{
if (!_summands) {
_summands = [[NSMutableArray alloc] init];
}
return _summands;
}
- (NSMutableArray *)factors
{
if (!_factors) {
_factors = [[NSMutableArray alloc] init];
}
return _factors;
}
- (NSMutableArray *)functionStack
{
if (!_functionStack) {
_functionStack = [[NSMutableArray alloc] init];
}
return _functionStack;
}
- (NSMutableArray *)valueGroup
{
if (!_valueGroup) {
_valueGroup = [[NSMutableArray alloc] init];
}
return _valueGroup;
}
- (BOOL)parseOperatorList:(id<MPToken>)token // Returns YES if list is overall negative
{
NSString *operatorString = [[token.stringValue stringByReplacingOccurrencesOfString:@" " withString:@""]
stringByReplacingOccurrencesOfString:@"+" withString:@""];
return (operatorString.length & 1) == 1;
}
- (NSDecimalNumber *)parseNumber:(id<MPToken>)token
{
return [NSDecimalNumber decimalNumberWithString:token.stringValue
locale:[NSLocale currentLocale]];
}
- (NSString *)parseVariable:(id<MPToken>)token
{
return token.stringValue;
}
- (void)collapseSummand
{
if (self.factors.count > 0) {
MPTerm *summand = [[MPProductTerm alloc] initWithFactors:self.factors];
if (self.summandNegative) {
summand = [[MPNegatedTerm alloc] initWithTerm:summand];
}
[self.summands addObject:summand];
}
self.factors = nil;
self.summandNegative = NO;
}
- (void)collapseFactor
{
if (self.valueGroup.count > 0) {
MPTerm *factor = [[MPProductTerm alloc] initWithFactors:self.valueGroup];
for (NSInteger index = self.functionStack.count - 1; index >= 0; index--) {
factor = [[MPElementaryFunctionTerm alloc] initWithFunctionIdentifier:self.functionStack[index]
term:factor];
}
if (self.factorNegative) {
factor = [[MPNegatedTerm alloc] initWithTerm:factor];
}
[self.factors addObject:factor];
}
self.valueGroup = nil;
self.functionStack = nil;
self.factorNegative = NO;
}
- (void)runSuffixMachine
{
BOOL checkMore = YES;
while (checkMore) {
if (self.currentToken.tokenType == MPFactorialToken) {
[self nextToken];
if (self.value) {
MPFactorialTerm *term = [[MPFactorialTerm alloc] initWithTerm:self.value];
self.value = term;
}
} else if (self.currentToken.tokenType == MPPowerToken) {
if (self.value) {
NSArray *errors;
MPPowerTerm *term = [[MPPowerTerm alloc] initWithFunction:(MPFunction *)self.currentToken errors:&errors];
if (!term) {
[self.errors addObjectsFromArray:errors];
} else {
term.baseTerm = self.value;
self.value = term;
}
}
[self nextToken];
} else {
checkMore = NO;
}
}
}
- (void)addErrorWithCode:(NSInteger)code
localizedDescription:(NSString *)description
useRange:(BOOL)flag
{
NSRange errorRange;
if (flag) {
errorRange = [self.tokens[self.errorTokenIndex] range];
} else {
NSInteger errorIndex = [self.expression convertIndex:self.errorTokenIndex
fromReferenceFrame:MPTokenReferenceFrame
toReferenceFrame:MPSymbolReferenceFrame];
errorRange = NSMakeRange(errorIndex, 0);
}
[self.errors addObject:[NSError errorWithDomain:MPMathKitErrorDomain
code:code
userInfo:@{NSLocalizedDescriptionKey: description,
MPPathToExpressionKey: self.expression.indexPath,
MPErrorRangeKey: [NSValue valueWithRange:errorRange]}]];
}
- (BOOL)errorOccured
{
[self skipWhitespaces];
if (self.currentTokenIndex < self.tokens.count) {
if (self.errorTokenIndex >= self.tokens.count) {
[self addErrorWithCode:0 localizedDescription:NSLocalizedString(@"Unexpected end. Expected Value", nil) useRange:NO];
} else {
id<MPToken> unexpectedToken = self.tokens[self.errorTokenIndex];
if (unexpectedToken.tokenType == MPPowerToken) {
[self addErrorWithCode:34 localizedDescription:NSLocalizedString(@"No base for Power", nil) useRange:NO];
} else if (unexpectedToken.tokenType == MPDeformedNumberToken) {
[self addErrorWithCode:42 localizedDescription:NSLocalizedString(@"Not a Number", nil) useRange:YES];
} else {
[self addErrorWithCode:1 localizedDescription:NSLocalizedString(@"Unexpected Symbol. Expected Value", nil) useRange:NO];
}
}
}
return self.errors.count > 0;
}
@end

View File

@@ -1,21 +0,0 @@
//
// MPExpressionStorage.h
// MathPad
//
// Created by Kim Wittenburg on 22.04.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPExpression.h"
@class MPExpressionStorage, MPExpressionView, MPExpressionLayout;
@interface MPExpressionStorage : MPExpression
@property (nonatomic, weak) MPExpressionView *expressionView; // Do not set
@property (nonatomic, strong) MPExpressionLayout *rootLayout;
@end

View File

@@ -1,58 +0,0 @@
//
// MPExpressionStorage.m
// MathPad
//
// Created by Kim Wittenburg on 22.04.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#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
{
self = [super initWithElements:elements];
if (self) {
_rootLayout = [[MPExpressionLayout alloc] initWithExpression:self parent:nil];
}
return self;
}
- (void)setExpressionView:(MPExpressionView *)expressionView
{
_expressionView = expressionView;
self.rootLayout = [[MPExpressionLayout alloc] initWithExpression:self parent:nil];
self.rootLayout.flipped = expressionView.flipped;
}
- (void)changedElementsInRangePath:(MPRangePath *)rangePath
replacementLength:(NSUInteger)replacementLength
{
if (rangePath.location.length == 0) {
return;
}
MPLayout *current = self.rootLayout;
for (NSUInteger position = 0; position < rangePath.location.length-1; position++) {
NSUInteger index = [rangePath.location indexAtPosition:position];
current = [current childLayoutAtIndex:index];
}
[current clearCacheInRange:rangePath.rangeAtLastIndex
replacementLength:replacementLength];
[self.expressionView invalidateIntrinsicContentSize];
self.expressionView.needsDisplay = YES;
}
@end

View File

@@ -1,37 +0,0 @@
//
// MPExpressionTokenizer.h
// MathPad
//
// Created by Kim Wittenburg on 19.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
@class MPExpressionTokenizer, MPExpression;
/*!
@class MPExpressionTokenizer
@brief The expression tokenizer class convers an @c MPExpression
instance into an array of tokens.
*/
@interface MPExpressionTokenizer : NSObject
/*!
@method tokenizeExpression:
@brief Converts an @c MPExpression instance into an array of tokens.
@discussion The objects in the returned array all conform to the @c MPToken
protocol. Function tokens are not copied from the @c expression
so they can still be mutated.
@param expression
The expression to be tokenized.
@return An array of objects that conform to the @c MPToken protocol.
*/
+ (NSArray *)tokenizeExpression:(MPExpression *)expression;
@end

View File

@@ -1,126 +0,0 @@
//
// MPExpressionTokenizer.m
// MathPad
//
// Created by Kim Wittenburg on 19.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPExpressionTokenizer.h"
#import "MPExpression.h"
#import "MPExpressionElement.h"
#import "MPToken.h"
#import "NSRegularExpression+MPParsingAdditions.h"
#define MPRangeExists(range) (range.location != NSNotFound)
@implementation MPExpressionTokenizer
+ (NSArray *)tokenizeExpression:(MPExpression *)expression
{
NSMutableArray *tokens = [[NSMutableArray alloc] init];
NSUInteger symbolIndex = 0;
for (NSUInteger index = 0; index < [expression countItemsInReferenceFrame:MPElementReferenceFrame]; index++) {
id <MPExpressionElement> element = [expression itemAtIndex:index referenceFrame:MPElementReferenceFrame];
if ([element isFunction]) {
[tokens addObject:element];
} else {
[tokens addObjectsFromArray:[self tokenizeElement:(NSString *)element
elementSymbolIndex:symbolIndex]];
}
symbolIndex += element.length;
}
return tokens;
}
+ (NSArray *)tokenizeElement:(NSString *)element
elementSymbolIndex:(NSUInteger)symbolIndex
{
NSUInteger lexLocation = 0;
NSString *decimalSeparator = [NSRegularExpression escapedPatternForString:[[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator]];
NSString *regexStringFormat = @"\\A(?:"
@"([\\*∙⋅])|"
@"([+-](?:\\s*[+-])*)|"
@"((?:\\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])|"
@"(!)|"
@"(=)|"
@"(\\s+)"
@")";
NSString *regexString = [NSString stringWithFormat:regexStringFormat, decimalSeparator, decimalSeparator, decimalSeparator, decimalSeparator, decimalSeparator];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexString
options:0
error:NULL];
NSMutableArray *tokens = [[NSMutableArray alloc] init];
while (lexLocation < element.length) {
NSTextCheckingResult *match = [regex firstMatchInString:element fromIndex:lexLocation];
NSRange range = NSMakeRange(lexLocation, 1);
MPTokenType tokenType = MPUnidentifiedToken;
if (match) {
NSRange multiplicationSymbolRange = [match rangeAtIndex:1];
NSRange operatorRange = [match rangeAtIndex:2];
NSRange deformedNumberRange = [match rangeAtIndex:3];
NSRange numberRange = [match rangeAtIndex:4];
NSRange elementaryFunctionRange = [match rangeAtIndex:5];
NSRange variableRange = [match rangeAtIndex:6];
NSRange factorialRange = [match rangeAtIndex:7];
NSRange equalsRange = [match rangeAtIndex:8];
NSRange whitespaceRange = [match rangeAtIndex:9];
if (MPRangeExists(multiplicationSymbolRange)) {
range = multiplicationSymbolRange;
tokenType = MPMultiplicationSymbolToken;
} else if (MPRangeExists(operatorRange)) {
range = operatorRange;
tokenType = MPOperatorListToken;
} else if (MPRangeExists(deformedNumberRange)) {
range = deformedNumberRange;
tokenType = MPDeformedNumberToken;
} else if (MPRangeExists(numberRange)) {
range = numberRange;
tokenType = MPNumberToken;
} else if (MPRangeExists(elementaryFunctionRange)) {
range = elementaryFunctionRange;
tokenType = MPElementaryFunctionToken;
} else if (MPRangeExists(variableRange)) {
range = variableRange;
tokenType = MPVariableToken;
} else if (MPRangeExists(factorialRange)) {
range = factorialRange;
tokenType = MPFactorialToken;
} else if (MPRangeExists(equalsRange)) {
range = equalsRange;
tokenType = MPEqualsToken;
} else if (MPRangeExists(whitespaceRange)) {
range = whitespaceRange;
tokenType = MPWhitespaceToken;
} else {
// Should not get here
range = NSMakeRange(lexLocation, 1);
tokenType = MPUnidentifiedToken;
}
}
lexLocation = NSMaxRange(range);
NSString *tokenStringValue = [element substringWithRange:range];
range.location += symbolIndex;
[tokens addObject:[[MPToken alloc] initWithTokenType:tokenType
range:range
stringValue:tokenStringValue]];
}
return tokens;
}
@end

View File

@@ -1,38 +0,0 @@
//
// MPExpressionView.h
// MathPad
//
// Created by Kim Wittenburg on 17.04.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
// TODO: Undo/Redo + Delegate (evaluateExpressionView:evaluate...)
@class MPExpressionView, MPExpressionStorage, MPRangePath;
@interface MPExpressionView : NSView
#pragma mark Creation Methods
- (id)initWithFrame:(NSRect)frameRect;
#pragma mark Properties
@property (readonly, nonatomic, strong) MPExpressionStorage *expressionStorage;
@property (nonatomic, strong) MPRangePath *selection;
@property (nonatomic, strong) NSError *mathError;
@property (nonatomic, strong) NSArray *syntaxErrors;
@property (nonatomic, weak) id target;
@property (nonatomic) SEL action;
#pragma mark Actions
- (IBAction)switchRadiansDegrees:(id)sender;
- (IBAction)showFunctions:(id)sender;
@end

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +0,0 @@
//
// MPFactorialTerm.h
// MathPad
//
// Created by Kim Wittenburg on 15.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPTerm.h"
@class MPFactorialTerm;
@interface MPFactorialTerm : MPTerm
- (instancetype)initWithTerm:(MPTerm *)term; /* designated initializer */
@property (readonly, nonatomic, strong) MPTerm *term;
@end

View File

@@ -1,32 +0,0 @@
//
// MPFactorialTerm.m
// MathPad
//
// Created by Kim Wittenburg on 15.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFactorialTerm.h"
@implementation MPFactorialTerm
- (instancetype)initWithTerm:(MPTerm *)term
{
self = [super init];
if (self) {
NSAssert(term != nil, @"term must not be nil.");
_term = term;
}
return self;
}
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
{
NSDecimalNumber *value = [self.term evaluate:error];
if (!value) {
return nil;
}
return [[NSDecimalNumber alloc] initWithDouble:tgamma(value.doubleValue + 1)];
}
@end

View File

@@ -1,42 +0,0 @@
//
// MPFractionFunction.h
// MathPad
//
// Created by Kim Wittenburg on 07.10.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFunction.h"
@class MPFractionFunction, MPExpression;
/*!
@class MPFractionFunction
@brief This class represents a fraction.
@discussion When a fraction is evaluated the nominator is divided by the
denominator.
*/
@interface MPFractionFunction : MPFunction
/*!
@property nominatorExpression
@brief The receiver's nominator.
@discussion The nominator must not define a variable.
*/
@property (nonatomic, strong) MPExpression *nominatorExpression;
/*!
@property denominatorExpression
@brief The receiver's denominator.
@discussion The denominator must not define a variable.
*/
@property (nonatomic, strong) MPExpression *denominatorExpression;
@end

View File

@@ -1,34 +0,0 @@
//
// MPFractionFunction.m
// MathPad
//
// Created by Kim Wittenburg on 07.10.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFractionFunction.h"
#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];
}
@end

View File

@@ -1,20 +0,0 @@
//
// MPFractionFunctionLayout.h
// MathPad
//
// Created by Kim Wittenburg on 07.10.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFunctionLayout.h"
@class MPFractionFunctionLayout, MPFractionFunction;
@interface MPFractionFunctionLayout : MPFunctionLayout
- (MPFractionFunction *)fractionFunction;
@end

View File

@@ -1,89 +0,0 @@
//
// MPFractionFunctionLayout.m
// MathPad
//
// Created by Kim Wittenburg on 07.10.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFractionFunctionLayout.h"
#import "MPFractionFunction.h"
#define kFractionFunctionLineWidth 1.0
#define kFractionFunctionHorizontalInset 2.0
#define kFractionFunctionNominatorOffset 0
#define kFractionFunctionDenominatorOffset 0
#define MPFractionMiddle (CTFontGetCapHeight((CTFontRef)self.font) / 2)
@implementation MPFractionFunctionLayout
- (MPFractionFunction *)fractionFunction
{
return (MPFractionFunction *)self.function;
}
- (NSUInteger)indexOfChildBelowChildAtIndex:(NSUInteger)index
{
return 1;
}
- (NSUInteger)indexOfChildAboveChildAtIndex:(NSUInteger)index
{
return 0;
}
- (NSIndexSet *)indexesOfRemainingChildren
{
return [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)];
}
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index
{
NSRect bounds = self.bounds;
if (index == 0) {
NSRect nominatorBounds = [self childLayoutAtIndex:0].bounds;
CGFloat y = MPFractionMiddle + kFractionFunctionLineWidth / 2 - nominatorBounds.origin.y;
return NSMakePoint((bounds.size.width - nominatorBounds.size.width) / 2, y);
} else {
NSRect denominatorBounds = [self childLayoutAtIndex:1].bounds;
CGFloat y = MPFractionMiddle - kFractionFunctionLineWidth / 2;
y -= denominatorBounds.size.height + denominatorBounds.origin.y;
return NSMakePoint((bounds.size.width - denominatorBounds.size.width) / 2, y);
}
}
- (BOOL)childAtIndexUsesSmallSize:(NSUInteger)index
{
return YES;
}
- (NSIndexPath *)indexPathForLocalMousePoint:(NSPoint)point
{
if (point.x < self.bounds.size.width / 2) {
return [NSIndexPath indexPathWithIndex:0];
} else {
return [NSIndexPath indexPathWithIndex:1];
}
}
- (NSRect)generateBounds
{
NSRect nominatorBounds = [self childLayoutAtIndex:0].bounds;
NSRect denominatorBounds = [self childLayoutAtIndex:1].bounds;
NSRect bounds;
bounds.origin.x = 0;
bounds.origin.y = MPFractionMiddle - kFractionFunctionLineWidth / 2 - denominatorBounds.size.height;
bounds.size.width = MAX(nominatorBounds.size.width, denominatorBounds.size.width) + 2 * kFractionFunctionHorizontalInset;
bounds.size.height = nominatorBounds.size.height + denominatorBounds.size.height + kFractionFunctionLineWidth;
return bounds;
}
- (void)draw
{
CGFloat y = MPFractionMiddle - kFractionFunctionLineWidth / 2;
NSRectFill(NSMakeRect(0, y, self.bounds.size.width, kFractionFunctionLineWidth));
}
@end

View File

@@ -1,18 +0,0 @@
//
// MPFractionTerm.h
// MathPad
//
// Created by Kim Wittenburg on 15.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFunctionTerm.h"
@class MPFractionTerm;
@interface MPFractionTerm : MPFunctionTerm
@end

View File

@@ -1,31 +0,0 @@
//
// MPFractionTerm.m
// MathPad
//
// Created by Kim Wittenburg on 15.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFractionTerm.h"
#import "MPParsedExpression.h"
@implementation MPFractionTerm
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
{
MPEvaluateExpression(nominator, 0);
MPEvaluateExpression(denominator, 1);
if ([denominator isEqualToNumber:@0]) {
if (error) {
*error = [NSError errorWithDomain:MPMathKitErrorDomain
code:100
userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"Division by zero.", nil)}];
}
return nil;
} else {
return [nominator decimalNumberByDividingBy:denominator];
}
}
@end

View File

@@ -1,21 +0,0 @@
//
// MPFunction+MPToken.h
// MathPad
//
// Created by Kim Wittenburg on 19.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFunction.h"
#import "MPToken.h"
/*!
@category MPFunction (MPToken)
@brief This category adds @c MPToken protocol conformance to the @c
MPFunction class.
*/
@interface MPFunction (MPToken) <MPToken>
@end

View File

@@ -1,38 +0,0 @@
//
// MPFunction+MPToken.m
// MathPad
//
// Created by Kim Wittenburg on 19.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFunction+MPToken.h"
#import "MPExpression.h"
#import "MPPowerFunction.h"
@implementation MPFunction (MPToken)
- (MPTokenType)tokenType
{
return [self isMemberOfClass:[MPPowerFunction class]] ? MPPowerToken : MPGenericFunctionToken;
}
- (NSRange)range
{
return NSMakeRange([self.parent convertIndex:[self.parent indexOfElement:self]
fromReferenceFrame:MPElementReferenceFrame
toReferenceFrame:MPSymbolReferenceFrame],
1);
}
- (NSString *)stringValue
{
return [self description];
}
@end

View File

@@ -1,330 +0,0 @@
//
// MPFunction.h
// MathPad
//
// Created by Kim Wittenburg on 18.04.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPExpressionElement.h"
/* Use this macro to implement a custom setter for children of custom MPFunction
* subclasses. This is necessary to maintain the consistency of the expression
* tree. Not implementing a custom setter using this macro or a similar custom
* method will lead to unexpected behaviour.
*/
#define MPFunctionAccessorImplementation(Accessor, variableName) \
- (void)set##Accessor:(MPExpression *)value \
{ \
variableName.parent = nil; \
variableName = value; \
variableName.parent = self; \
[self didChangeChildAtIndex:[self indexOfChild:variableName]]; \
}
@class MPFunction, MPExpression, MPRangePath;
@protocol MPExpressionElement;
/*!
@class MPFunction
@brief The @c MPFunction class represents a mathematical function.
@discussion A mathematical function can be anything that exceeds the
graphical capability of text.
*/
@interface MPFunction : NSObject <NSCoding, NSCopying, MPExpressionElement>
#pragma mark Properties
/* Subclasses should define readwrite properties for all child expressions they
* can have. In the implementation file the property setter should be overridden
* using the @c MPFunctionAccessorImplementation macro. The macro takes two
* parameters: The capitalized name of the property and the name of the
* property's backing variable (usually the property's name prefixed with an
* underscore.
*
* Note that none of the children may be nil at any time.
*/
#pragma mark Working With the Expression Tree
/*!
@methodgroup Working With the Expression Tree
*/
/*!
@property parent
@brief The receiver's parent.
@discussion Expressions and functions are organized in a tree-like structure.
Through this property a function's containing expression can be
accessed.
@warning Do not set this property manually. It will automatically be set
when the function is added to an expression.
@note If you need to know when a function is added to or removed from
an expression you can observe this property.
@return The parent of the receiver or @c nil if the receiver does not
have a parent.
*/
@property (nonatomic, weak) MPExpression *parent;
/*!
@method rootExpression
@brief Returns the root expression from the receiver's expression tree.
@discussion The root expression is the ultimate parent of all expressions and
functions in the expression tree. A root expression does not have
a parent.
@return The root expression from the receiver's expression tree or @c nil
if this function does not have a parent.
*/
- (MPExpression *)rootExpression;
/*!
@method indexPath
@brief Returns the index path of the receiver in the expression tree.
@discussion The index path is calculated by going up the expression tree
collecting the respective index of the receiver. The indexes are
expressed in the element reference frame. If any of the indexes
exceed the respective receiver's bounds a @c NSRangeException is
raised.
@return The index path of the receiver in the expression tree or @c nil
if this function does not have a parent.
*/
- (NSIndexPath *)indexPath;
/*!
@method numberOfChildren
@brief Returns the number of children the receiver has.
@discussion The number of children must be equal to the number of child
accessors as determined by the @c -childrenAccessors method.
@return The number of children of the receiving function.
*/
- (NSUInteger)numberOfChildren;
/*!
@method childAtIndex:
@brief Returns the child at the specified index.
@discussion The ordering of children is determined by the order of the
children returned from the @c -childrenAccessors method.
@param index
The index of the requested child.
@return The expression that corresponds to the child at @c index.
*/
- (MPExpression *)childAtIndex:(NSUInteger)index;
/*!
@method setChild:atIndex:
@brief Sets the child at the specified index.
@discussion Although this method does @b not call the respective accessor
methods it behaves exactly as the setter method implemented using
the @c MPFunctionAccessorImplementation macro.
@param child
The child that should replace the previous one.
@param index
The index of the child to be replaced.
*/
- (void)setChild:(MPExpression *)child
atIndex:(NSUInteger)index;
/*!
@method children
@brief Returns the receiver's children.
@discussion The ordering of the children is the same as in the array returned
from @c -childrenAccessors.
@return An array containing all the function's children.
*/
- (NSArray *)children;
/*!
@method indexOfChild:
@brief Returns the index of @c child in the receiver.
@param child
The child to be searched.
@return The index of @c child or @c NSNotFound if none of the receiver's
children is equal to @c child.
*/
- (NSUInteger)indexOfChild:(MPExpression *)child;
/*!
@method elementAtIndexPath:
@brief Returns the element at the specified index path.
@discussion This method @em walks down the expression tree (including other
functions) using the specified index path and finds the
corresponding element. The returned object can be an @c NSString,
a @c MPFunction or an @c MPExpression depending on the element @c
indexPath points to. If any of the indexes exceed the bounds of
the respective receiver an @c NSRangeException is raised.
If the index path does not contain any indexes the receiver
itself is returned.
@param indexPath
The index path the required object is located at. The indexes are
expressed in the element reference frame.
@return The element located at @c indexPath. The element is not copied
before it is returned. Be aware of the fact that any mutations
made to the returned object are reflected in the receiver.
*/
- (id)elementAtIndexPath:(NSIndexPath *)indexPath;
#pragma mark Notifications
/*!
@methodgroup Notifications
*/
/*!
@method didChangeElementsInRangePath:replacementLength:
@brief Notification method that is called if one of the children was
mutated.
@discussion This method is also called when one of the children is changed
via one of the accessor methods.
@param rangePath
The location of the change that happened. Because it may have
happened somewhere deep down in the expression tree a range path
is used.
@param replacementLength
The number of elements that replaced the elements addressed by
the range path.
*/
- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath
replacementLength:(NSUInteger)replacementLength;
/*!
@method didChangeChildAtIndex:
@brief Convenience method that calls @c
-didChangeElementsInRangePath:replacementLength:
@discussion This method is automatically called when one of the children is
changed using one of the accessor methods or @c
-setChild:atIndex:.
@param index
The index of the child that was changed.
*/
- (void)didChangeChildAtIndex:(NSUInteger)index;
#pragma mark Evaluating Functions
/*!
@methodgroup Evaluating Functions
*/
/*!
@method expectsVariableDefinitionInChildAtIndex:
@brief Returns whether the child at @c anIndex is supposed to contain a
variable definition.
@discussion This method is automatically called during expression parsing.
You can override this method to opt into the default behaviour.
If you override this method you do not need to call super.
The default implementation just returns @c NO for every index.
@param anIndex
The index of the child to check.
@return @c YES if the child at @c anIndex is supposed to contain a
variable definition, @c NO otherwise.
*/
- (BOOL)expectsVariableDefinitionInChildAtIndex:(NSUInteger)anIndex;
@end
/*!
@category MPFunction (MPSubcalssOverride)
@brief The methods in this category must be implemented by any concrete
subclasses of @c MPFunction.
@discussion @c MPFunction does not implement the functions itself but calls
them at various points. If a subclass does not provide
implementations your app will most likely crash.
*/
@interface MPFunction (MPSubclassOverride)
/* In Addition to the methods listed below MPFunction subclasses should also
* override the NSObject method -description.
* Also -expectsVariabledefinitionInChildAtIndex: can be overridden if one of the
* children should contain a variable definition.
*/
#pragma mark Working With the Expression Tree
/*!
@methodgroup Working With the Expression Tree
*/
/*!
@method childrenAccessors
@brief Returns the names of the children properties.
@discussion A @c MPFunction subclass should declare a public propertiy for
every child it can have. This method should return the names as
they would be used for key-value-coding.
@return An array of strings describing the names of children a function
has.
*/
- (NSArray *)childrenAccessors;
#pragma mark Evaluating Functions
/*!
@methodgroup Evaluating Functions
*/
/*!
@method functionTermClass
@brief Returns the class that represents the receiver's evaluation
behaviour.
@discussion The class returned must be a subclass of @c MPFunctionTerm. That
class must not implement a custom initializer.
@return The @c MPFunctionTerm subclass that can evaluate instances of the
receiver's class.
*/
- (Class)functionTermClass;
@end

View File

@@ -1,213 +0,0 @@
//
// MPFunction.m
// MathPad
//
// Created by Kim Wittenburg on 18.04.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFunction.h"
#import "MPExpression.h"
#import "MPRangePath.h"
#import "NSIndexPath+MPAdditions.h"
#import "MPFunctionTerm.h"
@implementation MPFunction
#pragma mark Creation Methods
- (instancetype)init
{
self = [super init];
if (self) {
for (NSString *key in self.childrenAccessors) {
MPExpression *emptyExpression = [[MPExpression alloc] init];
emptyExpression.parent = self;
[self setValue:emptyExpression
forKey:key];
}
}
return self;
}
#pragma mark Working With the Expression Tree
- (MPExpression *)rootExpression
{
return [self.parent rootExpression];
}
- (NSIndexPath *)indexPath
{
if (!self.parent) {
return nil;
}
NSUInteger selfIndex = [self.parent indexOfElement:self];
return [[self.parent indexPath] indexPathByAddingIndex:selfIndex];
}
- (NSUInteger)numberOfChildren
{
return self.childrenAccessors.count;
}
- (MPExpression *)childAtIndex:(NSUInteger)index
{
return [self valueForKey:self.childrenAccessors[index]];
}
- (void)setChild:(MPExpression *)child
atIndex:(NSUInteger)index
{
[[self valueForKey:self.childrenAccessors[index]] setParent:nil];
[self setValue:child
forKey:self.childrenAccessors[index]];
child.parent = self;
[self didChangeChildAtIndex:index];
}
- (NSArray *)children
{
NSUInteger childCount = [self numberOfChildren];
NSMutableArray *children = [[NSMutableArray alloc] initWithCapacity:childCount];
for (NSInteger i = 0; i < childCount; i++) {
[children addObject:[self childAtIndex:i]];
}
return [children copy];
}
- (NSUInteger)indexOfChild:(MPExpression *)child
{
NSUInteger index = 0;
for (; index < [self numberOfChildren]; index++) {
if ([self childAtIndex:index] == child) {
return index;
}
}
return NSNotFound;
}
- (id)elementAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.length == 0) {
return self;
}
MPExpression *child = [self childAtIndex:[indexPath indexAtPosition:0]];
return [child elementAtIndexPath:[indexPath indexPathByRemovingFirstIndex]];
}
#pragma mark Notifications
- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath
replacementLength:(NSUInteger)replacementLength
{
NSUInteger selfIndex = [self.parent indexOfElement:self];
MPRangePath *newPath = MPMakeRangePath([rangePath.location indexPathByPreceedingIndex:selfIndex], rangePath.length);
[self.parent changedElementsInRangePath:newPath
replacementLength:replacementLength];
}
- (void)didChangeChildAtIndex:(NSUInteger)index
{
MPRangePath *path = [[MPRangePath alloc] initWithRange:NSMakeRange(index, 1)];
[self didChangeElementsInRangePath:path
replacementLength:1];
}
#pragma mark Evaluating Functions
- (BOOL)expectsVariableDefinitionInChildAtIndex:(NSUInteger)index
{
return NO;
}
#pragma mark Working With Functions
- (NSString *)description
{
return @"[]";
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone
{
id copy = [[self.class allocWithZone:zone] init];
for (NSString *key in self.childrenAccessors) {
MPExpression *child = [[self valueForKey:key] copy];
[copy setValue:child forKey:key];
}
return copy;
}
#pragma mark - NSCoding
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
NSArray *children = [aDecoder decodeObject];
NSInteger index = 0;
for (MPExpression *child in children) {
[self setChild:child atIndex:index];
++index;
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.children];
}
#pragma mark - MPExpressionElement
- (BOOL)isString
{
return NO;
}
- (BOOL)isFunction
{
return YES;
}
- (NSUInteger)length
{
return 1;
}
@end

View File

@@ -1,61 +0,0 @@
//
// MPFunctionLayout.h
// MathPad
//
// Created by Kim Wittenburg on 07.08.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPLayout.h"
@class MPFunctionLayout, MPFunction, MPExpressionLayout;
@interface MPFunctionLayout : MPLayout
+ (MPFunctionLayout *)functionLayoutForFunction:(MPFunction *)function
parent:(MPExpressionLayout *)parent;
@property (readonly, nonatomic, weak) MPFunction *function;
@end
@interface MPFunctionLayout (MPSubclassOverride)
- (instancetype)initWithFunction:(MPFunction *)function
parent:(MPExpressionLayout *)parent;
#pragma mark Cache Methods
- (CTLineRef)lineForPrivateCacheIndex:(NSUInteger)index
generator:(CTLineRef (^)())generator;
- (id)objectForPrivateCacheIndex:(NSUInteger)index
generator:(id (^)())generator;
// Should also implement accessor method for special function type:
// - (MPCustomFunction *)customFunction
// {
// return (MPCustomFunction *)self.function;
// }
#pragma mark Size and Drawing Methods
- (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
@end

View File

@@ -1,154 +0,0 @@
//
// MPFunctionLayout.m
// MathPad
//
// Created by Kim Wittenburg on 07.08.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFunctionLayout.h"
#import "MPExpression.h"
#import "MPFunction.h"
#import "MPSumFunction.h"
#import "MPParenthesisFunction.h"
#import "MPPowerFunction.h"
#import "MPFractionFunction.h"
#import "MPExpressionLayout.h"
#import "MPSumFunctionLayout.h"
#import "MPParenthesisFunctionLayout.h"
#import "MPPowerFunctionLayout.h"
#import "MPFractionFunctionLayout.h"
#import "NSIndexPath+MPAdditions.h"
@implementation MPFunctionLayout
#pragma mark Creation Methods
+ (MPFunctionLayout *)functionLayoutForFunction:(MPFunction *)function
parent:(MPExpressionLayout *)parent
{
Class class = [function class];
if (class == [MPSumFunction class]) {
return [[MPSumFunctionLayout alloc] initWithFunction:function parent:parent];
} else if (class == [MPParenthesisFunction class]) {
return [[MPParenthesisFunctionLayout alloc] initWithFunction:function parent:parent];
} else if (class == [MPPowerFunction class]) {
return [[MPPowerFunctionLayout alloc] initWithFunction:function parent:parent];
} else if (class == [MPFractionFunction class]) {
return [[MPFractionFunctionLayout alloc] initWithFunction:function parent:parent];
}
return [[self alloc] initWithFunction:function
parent:parent];
}
- (instancetype)initWithFunction:(MPFunction *)function
parent:(MPExpressionLayout *)parent
{
self = [super initWithParent:parent];
if (self) {
_function = function;
}
return self;
}
#pragma mark Cache Methods
- (CTLineRef)lineForPrivateCacheIndex:(NSUInteger)index
generator:(CTLineRef (^)())generator
{
NSUInteger actualIndex = self.function.numberOfChildren + index;
id (^actualGenerator)() = ^{
CTLineRef line = generator();
return CFBridgingRelease(line);
};
id lineObject = [self cachableObjectForIndex:actualIndex
generator:actualGenerator];
return (__bridge CTLineRef)(lineObject);
}
- (id)objectForPrivateCacheIndex:(NSUInteger)index
generator:(id (^)())generator
{
NSUInteger actualIndex = self.function.numberOfChildren + index;
return [self cachableObjectForIndex:actualIndex generator:generator];
}
- (NSUInteger)numberOfChildren
{
return self.function.numberOfChildren;
}
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index
{
return [self cachableObjectForIndex:index generator:^id{
MPExpression *child = [self.function childAtIndex:index];
MPExpressionLayout *layout = [[MPExpressionLayout alloc] initWithExpression:child
parent:self];
layout.flipped = self.flipped;
layout.usesSmallSize = self.usesSmallSize ? YES : [self childAtIndexUsesSmallSize:index];
return layout;
}];
}
- (BOOL)childAtIndexUsesSmallSize:(NSUInteger)index
{
return NO;
}
- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point
{
// A single index is used to communicate back wether the
// selection should be before or after the function.
// A 0 means before, a 1 means after.
for (NSUInteger index = 0; index < self.function.numberOfChildren; index++) {
MPLayout *childLayout = [self childLayoutAtIndex:index];
NSRect childBounds = childLayout.bounds;
NSPoint childOffset = [self offsetOfChildLayoutAtIndex:index];
childBounds.origin.x += childOffset.x;
childBounds.origin.y += childOffset.y;
if (NSMouseInRect(point, childBounds, self.flipped)) {
NSPoint pointInChild = NSMakePoint(point.x - childOffset.x, point.y - childOffset.y);
NSIndexPath *subPath = [childLayout indexPathForMousePoint:pointInChild];
return [subPath indexPathByPreceedingIndex:index];
}
}
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];
}
@end

View File

@@ -1,25 +0,0 @@
//
// MPFunctionTerm.h
// MathPad
//
// Created by Kim Wittenburg on 12.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPTerm.h"
#define MPEvaluateExpression(var, index) NSDecimalNumber *var = [[self expressionAtIndex:index] evaluate:error]; if (var == nil) return nil
@class MPFunctionTerm, MPFunction, MPParsedExpression;
@interface MPFunctionTerm : MPTerm
- (instancetype)initWithFunction:(MPFunction *)function
errors:(NSArray *__autoreleasing *)errors; /* designated initializer */
- (MPParsedExpression *)expressionAtIndex:(NSUInteger)anIndex;
@end

View File

@@ -1,72 +0,0 @@
//
// MPFunctionTerm.m
// MathPad
//
// Created by Kim Wittenburg on 12.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFunctionTerm.h"
#import "MPFunction.h"
#import "MPExpression.h"
#import "MPParsedExpression.h"
#import "MPToken.h"
@interface MPFunctionTerm ()
@property (nonatomic, strong) NSArray *parsedExpressions;
@end
@implementation MPFunctionTerm
- (instancetype)initWithFunction:(MPFunction *)function
errors:(NSArray *__autoreleasing *)errors
{
NSAssert(function != nil, @"function must not be nil.");
if ([self isMemberOfClass:[MPFunctionTerm class]]) {
return [[function.functionTermClass alloc] initWithFunction:function
errors:errors];
}
self = [super init];
if (self) {
NSMutableArray *parsedExpressions = [[NSMutableArray alloc] initWithCapacity:function.childrenAccessors.count];
NSUInteger childIndex = 0;
BOOL errorOccured = NO;
NSMutableArray *mutableErrors = [[NSMutableArray alloc] init];
for (NSString *accessor in function.childrenAccessors) {
MPExpression *expression = [function valueForKey:accessor];
NSArray *localErrors;
MPParsedExpression *parsedExpression = [expression parseExpectingVariable:[function expectsVariableDefinitionInChildAtIndex:childIndex]
errors:&localErrors];
if (parsedExpression) {
[parsedExpressions addObject:parsedExpression];
} else {
[mutableErrors addObjectsFromArray:localErrors];
errorOccured = YES;
}
childIndex++;
}
if (errorOccured) {
if (errors) {
*errors = mutableErrors;
}
return nil;
}
self.parsedExpressions = parsedExpressions;
}
return self;
}
- (MPParsedExpression *)expressionAtIndex:(NSUInteger)anIndex
{
return [self.parsedExpressions objectAtIndex:anIndex];
}
@end

View File

@@ -1,25 +0,0 @@
//
// MPFunctionsViewController.h
// MathPad
//
// Created by Kim Wittenburg on 28.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
@class MPFunctionsViewController, MPFunctionsCollectionView;
@interface MPFunctionsViewController : NSViewController
- (id)init;
@property (weak) IBOutlet MPFunctionsCollectionView *collectionView;
@property (nonatomic, strong) NSArray *functionPrototypes;
@property (nonatomic, strong) NSString *currentDescription;
@property (nonatomic, weak) id target;
@property (nonatomic) SEL action; // 1 argument: The function to insert
@end

View File

@@ -1,272 +0,0 @@
//
// MPFunctionsViewController.m
// MathPad
//
// Created by Kim Wittenburg on 28.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFunctionsViewController.h"
#import "MPExpression.h"
#import "NSString+MPExpressionElement.h"
#import "MPFunction.h"
#import "MPSumFunction.h"
#import "MPParenthesisFunction.h"
#import "MPPowerFunction.h"
#import "MPFractionFunction.h"
#import "MPFunctionLayout.h"
@class MPFunctionsCollectionView, MPFunctionTemplateView, MPFunctionTemplateItem;
@interface MPFunctionsCollectionView : NSCollectionView
@property (nonatomic, weak) MPFunctionTemplateItem *hoverItem;
@property (nonatomic, weak) id target;
@property (nonatomic) SEL action;
@end
@interface MPFunctionTemplateView : NSView
@property (nonatomic, strong) MPFunction *functionTemplate;
@property (readonly, nonatomic, strong) MPFunctionLayout *functionTemplateLayout;
@property (nonatomic, strong) NSTrackingArea *trackingArea;
@property (nonatomic) BOOL mouseOver;
@end
@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
fromView:nil];
for (NSUInteger index = 0; index < self.content.count; index++) {
NSCollectionViewItem *viewItem = [self itemAtIndex:index];
if (NSMouseInRect(pointInView, viewItem.view.frame, self.isFlipped)) {
if (self.target && self.action) {
[self.target performSelector:self.action
withObject:[[viewItem.representedObject objectForKey:@"function" ] copy]
afterDelay:0.0];
}
break;
}
}
}
@end
@implementation MPFunctionTemplateView
- (void)updateTrackingAreas
{
if (!self.trackingArea) {
self.trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds
options:NSTrackingMouseEnteredAndExited|NSTrackingActiveInKeyWindow|NSTrackingInVisibleRect
owner:self
userInfo:nil];
[self addTrackingArea:self.trackingArea];
}
[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
{
if (!_functionTemplateLayout) {
_functionTemplateLayout = [MPFunctionLayout functionLayoutForFunction:self.functionTemplate
parent:nil];
_functionTemplateLayout.usesSmallSize = YES;
}
return _functionTemplateLayout;
}
- (NSSize)intrinsicContentSize
{
return [self.functionTemplateLayout bounds].size;
}
- (BOOL)isOpaque
{
return NO;
}
- (void)drawRect:(NSRect)dirtyRect
{
if (self.mouseOver) {
[[NSColor selectedTextBackgroundColor] set];
NSBezierPath *background = [NSBezierPath bezierPathWithRoundedRect:self.bounds
xRadius:10
yRadius:10];
[background fill];
}
[[NSColor textColor] set];
NSPoint origin = NSMakePoint((self.bounds.size.width - self.functionTemplateLayout.bounds.size.width) / 2, (self.bounds.size.height - self.functionTemplateLayout.bounds.size.height) / 2);
origin.y -= self.functionTemplateLayout.bounds.origin.y;
[self.functionTemplateLayout drawAtPoint:origin];
}
@end
@implementation MPFunctionTemplateItem
static void *MPFunctionTemplateViewMouseOverContext = @"MPFunctionTemplateViewMouseOverContext";
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder
{
return [super awakeAfterUsingCoder:aDecoder];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context == MPFunctionTemplateViewMouseOverContext) {
MPFunctionTemplateView *view = (MPFunctionTemplateView *)self.view;
((MPFunctionsCollectionView *)self.collectionView).hoverItem = view.mouseOver ? self : nil;
} else {
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
- (void)setRepresentedObject:(id)representedObject
{
MPFunctionTemplateView *view = (MPFunctionTemplateView *)self.view;
view.functionTemplate = [representedObject objectForKey:@"function"];
self.templateName = [representedObject objectForKey:@"name"];
[view addObserver:self
forKeyPath:@"mouseOver"
options:0
context:MPFunctionTemplateViewMouseOverContext];
[super setRepresentedObject:representedObject];
}
@end
@interface MPFunctionsViewController ()
@end
@implementation MPFunctionsViewController
- (id)init
{
return [self initWithNibName:@"MPFunctionsViewController"
bundle:[NSBundle bundleForClass:[self class]]];
}
- (void)awakeFromNib
{
MPFunction *sumFunction = [[MPSumFunction alloc] init];
MPFunction *parenthesisFunction = [[MPParenthesisFunction alloc] init];
MPFunction *powerFunction = [[MPPowerFunction alloc] init];
MPPowerFunction *squareFunction = [[MPPowerFunction alloc] init];
squareFunction.exponentExpression = [[MPExpression alloc] initWithElement:@"2"];
MPPowerFunction *cubicFunction = [[MPPowerFunction alloc] init];
cubicFunction.exponentExpression = [[MPExpression alloc] initWithElement:@"3"];
MPFractionFunction *fractionFunction = [[MPFractionFunction alloc] init];
self.functionPrototypes = @[
@{@"function": sumFunction,
@"name": NSLocalizedString(@"Sum", @"Sum Function Name")},
@{@"function": parenthesisFunction,
@"name": NSLocalizedString(@"Parenthesis", @"Parenthesis Function Name")},
@{@"function": squareFunction,
@"name": NSLocalizedString(@"Square", @"Square Function Name")},
@{@"function": cubicFunction,
@"name": NSLocalizedString(@"Cubic", @"Cubic Function Name")},
@{@"function": powerFunction,
@"name": NSLocalizedString(@"Power", @"Power Function Name")},
@{@"function": fractionFunction,
@"name": NSLocalizedString(@"Fraction", @"Fraction Function Name")}
];
[self.collectionView addObserver:self
forKeyPath:@"hoverItem"
options:0
context:MPCollectionViewHoverItemChangeContext];
self.collectionView.target = self.target;
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;
}
}
- (void)setAction:(SEL)action
{
_action = action;
if (self.collectionView) {
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;
} else {
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
@end

View File

@@ -1,84 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6250" systemVersion="14B25" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6250"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MPFunctionsViewController">
<connections>
<outlet property="collectionView" destination="QmA-Ge-oq3" id="mYG-bs-5TG"/>
<outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="Hz6-mo-xeY" customClass="MPWhiteView">
<rect key="frame" x="0.0" y="0.0" width="320" height="185"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="D89-Gi-YJm">
<rect key="frame" x="10" y="10" width="300" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Description" drawsBackground="YES" id="xrL-Xy-aRm">
<font key="font" metaFont="system"/>
<color key="textColor" name="disabledControlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="currentDescription" id="0Vx-OA-1Zy">
<dictionary key="options">
<string key="NSNullPlaceholder">Choose an Element</string>
</dictionary>
</binding>
</connections>
</textField>
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9w0-UQ-Fqx">
<rect key="frame" x="10" y="27" width="300" height="148"/>
<clipView key="contentView" copiesOnScroll="NO" id="Z8C-m4-8Mb">
<rect key="frame" x="1" y="1" width="248" height="158"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView selectable="YES" id="QmA-Ge-oq3" customClass="MPFunctionsCollectionView">
<rect key="frame" x="0.0" y="0.0" width="248" height="158"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="primaryBackgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<connections>
<binding destination="-2" name="content" keyPath="functionPrototypes" id="sPD-4B-zL5"/>
<outlet property="itemPrototype" destination="4Wf-e2-Nmy" id="v9O-tD-Y7U"/>
</connections>
</collectionView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="umr-K7-88h">
<rect key="frame" x="1" y="144" width="233" height="15"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="MEs-Qt-Wze">
<rect key="frame" x="234" y="1" width="15" height="143"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
</subviews>
<constraints>
<constraint firstItem="D89-Gi-YJm" firstAttribute="centerX" secondItem="Hz6-mo-xeY" secondAttribute="centerX" id="9WK-zU-g2j"/>
<constraint firstItem="D89-Gi-YJm" firstAttribute="leading" secondItem="9w0-UQ-Fqx" secondAttribute="leading" id="FSW-Jj-9RW"/>
<constraint firstItem="9w0-UQ-Fqx" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="10" id="OFp-Pr-zZT"/>
<constraint firstItem="9w0-UQ-Fqx" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="10" id="fPs-mT-ads"/>
<constraint firstAttribute="bottom" secondItem="D89-Gi-YJm" secondAttribute="bottom" constant="10" id="mk2-Ph-Kh5"/>
<constraint firstItem="D89-Gi-YJm" firstAttribute="top" secondItem="9w0-UQ-Fqx" secondAttribute="bottom" id="nYD-XL-3y1"/>
<constraint firstItem="9w0-UQ-Fqx" firstAttribute="trailing" secondItem="D89-Gi-YJm" secondAttribute="trailing" id="w9y-K1-mce"/>
</constraints>
<point key="canvasLocation" x="-101" y="140.5"/>
</customView>
<collectionViewItem id="4Wf-e2-Nmy" customClass="MPFunctionTemplateItem">
<connections>
<outlet property="view" destination="dE9-Vu-psk" id="hX8-fz-7FR"/>
</connections>
</collectionViewItem>
<view id="dE9-Vu-psk" customClass="MPFunctionTemplateView">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<point key="canvasLocation" x="119" y="-114.5"/>
</view>
</objects>
</document>

View File

@@ -1,76 +0,0 @@
//
// MPLayout.h
// MathPad
//
// Created by Kim Wittenburg on 07.08.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#define kMPEmptyBoxWidth (self.usesSmallSize ? 2.0 : 3.0)
#define kMPEmptyBoxHeight (CTFontGetDescent((CTFontRef)self.font) + CTFontGetAscent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font))
#define kMPEmptyBoxYOrigin (-(CTFontGetDescent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font)))
#define kMPEmptyBoxDrawingWidth kMPEmptyBoxWidth
#define kMPEmptyBoxDrawingHeight (CTFontGetDescent((CTFontRef)self.font) + CTFontGetAscent((CTFontRef)self.font))
#define kMPEmptyBoxDrawingYOrigin (-(CTFontGetDescent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font)/2))
@class MPLayout, MPRangePath;
@interface MPLayout : NSObject
#pragma mark Creation Methods
- (instancetype)init;
- (instancetype)initWithParent:(MPLayout *)parent;
#pragma mark Properties
- (NSFont *)normalFontWithSize:(CGFloat)size;
- (NSFont *)specialFontWithSize:(CGFloat)size;
- (CGFloat)contextInferredFontSize;
- (CGFloat)normalFontSize;
- (CGFloat)smallFontSize;
- (NSFont *)font;
#pragma mark Cache Tree
@property (readonly, nonatomic, weak) MPLayout *parent;
#pragma mark Cache Methods
// Querying Caches
- (id)cachableObjectForIndex:(NSUInteger)index
generator:(id(^)())generator;
// Clearing Caches
- (void)clearCacheInRange:(NSRange)range
replacementLength:(NSUInteger)replacementLength;
- (void)invalidate;
- (MPLayout *)childLayoutAtIndexPath:(NSIndexPath *)indexPath;
#pragma mark Calculation and Drawing Methods
- (CTLineRef)createLineForString:(NSString *)aString;
- (CTLineRef)createLineForString:(NSString *)aString emphasize:(BOOL)emphasize;
- (CTLineRef)createLineForString:(NSString *)aString usingFont:(NSFont *)font;
@property (nonatomic, getter = isFlipped) BOOL flipped;
@property (nonatomic) BOOL usesSmallSize;
- (NSRect)bounds;
- (NSRect)boundingRectForRangePath:(MPRangePath *)rangePath; /* if rangePath.length is 0 the returned rect will have a width of 0 */
- (void)drawAtPoint:(NSPoint)point;
@end
@interface MPLayout (MPSubclassImplement)
- (NSUInteger)numberOfChildren;
- (BOOL)drawsChildrenManually;
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index; // To be implemented
- (NSRect)generateBounds; // To be implemented
- (NSRect)boundingRectForRange:(NSRange)range; // To be implemented, use rangePath instead, this one has wrong origin
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index;
- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point;
- (void)draw; // To be implemented
@end

View File

@@ -1,206 +0,0 @@
//
// MPLayout.m
// MathPad
//
// Created by Kim Wittenburg on 11.08.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPLayout.h"
#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];
if (self) {
_cache = [[NSMutableArray alloc] init];
_cachedBounds = NSZeroRect;
}
return self;
}
- (instancetype)initWithParent:(MPLayout *)parent
{
self = [self init];
if (self) {
_parent = parent;
}
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) {
return NO;
}
return _cache[index] != [NSNull null];
}
- (id)cachableObjectForIndex:(NSUInteger)index
generator:(id (^)())generator
{
if ([self hasCacheForElementAtIndex:index]) {
return _cache[index];
}
id object = generator();
[self ensureCacheSizeForIndex:index];
_cache[index] = object;
return object;
}
- (void)ensureCacheSizeForIndex:(NSUInteger)index
{
while (index >= _cache.count) {
[_cache addObject:[NSNull null]];
}
}
// Clearing Caches
- (void)clearCacheInRange:(NSRange)range
replacementLength:(NSUInteger)replacementLength
{
NSMutableArray *placeholders = [[NSMutableArray alloc] initWithCapacity:replacementLength];
while (placeholders.count < replacementLength) {
[placeholders addObject:[NSNull null]];
}
[_cache replaceObjectsInRange:range
withObjectsFromArray:placeholders];
[self invalidate];
}
- (void)invalidate
{
_cachedBounds = NSZeroRect;
[self.parent invalidate];
}
- (MPLayout *)childLayoutAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.length == 0) {
return self;
}
MPLayout *child = [self childLayoutAtIndex:indexPath.firstIndex];
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
{
NSAttributedString *text = [[NSAttributedString alloc] initWithString:aString
attributes:@{NSFontAttributeName: font}];
CFAttributedStringRef attributedString = CFBridgingRetain(text);
CTLineRef line = CTLineCreateWithAttributedString(attributedString);
CFRelease(attributedString);
return line;
}
- (NSRect)bounds
{
if (NSEqualRects(_cachedBounds, NSZeroRect)) {
_cachedBounds = [self generateBounds];
}
return _cachedBounds;
}
- (NSRect)boundingRectForRangePath:(MPRangePath *)rangePath
{
if (rangePath.location.length == 1) {
return [self boundingRectForRange:rangePath.rangeAtLastIndex];
}
NSUInteger nextIndex = [rangePath.location indexAtPosition:0];
NSIndexPath *newLocation = [rangePath.location indexPathByRemovingFirstIndex];
MPRangePath *newRangePath = [[MPRangePath alloc] initWithLocation:newLocation length:rangePath.length];
NSRect bounds = [[self childLayoutAtIndex:nextIndex] boundingRectForRangePath:newRangePath];
NSPoint offset = [self offsetOfChildLayoutAtIndex:nextIndex];
bounds.origin = NSMakePoint(bounds.origin.x + offset.x, bounds.origin.y + offset.y);
return bounds;
}
- (BOOL)drawsChildrenManually
{
return NO;
}
- (void)drawAtPoint:(NSPoint)point
{
NSAffineTransform *transform = [NSAffineTransform transform];
[transform translateXBy:point.x
yBy:point.y];
[transform concat];
[self draw];
if (!self.drawsChildrenManually) {
for (NSUInteger index = 0; index < [self numberOfChildren]; index++) {
MPLayout *childLayout = [self childLayoutAtIndex:index];
NSPoint offset = [self offsetOfChildLayoutAtIndex:index];
[childLayout drawAtPoint:offset];
}
}
[transform invert];
[transform concat];
}
@end

View File

@@ -1,31 +0,0 @@
//
// MPMathRules.h
// MathPad
//
// Created by Kim Wittenburg on 14.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
FOUNDATION_EXPORT NSString *MPMathRulesMaximumOperatorChainLengthKey;
FOUNDATION_EXPORT NSString *MPMathRulesMaximumOperatorChainLengthInMultiplicationKey;
FOUNDATION_EXPORT NSString *MPMathRulesIsUsingDegreesKey;
@class MPMathRules;
@interface MPMathRules : NSObject
+ (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 (nonatomic) BOOL isUsingDegrees;
@end

View File

@@ -1,83 +0,0 @@
//
// MPMathRules.m
// MathPad
//
// Created by Kim Wittenburg on 14.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPMathRules.h"
NSString *MPMathRulesMaximumOperatorChainLengthKey = @"MPMathRulesMaximumOperatorChainLengthKey";
NSString *MPMathRulesMaximumOperatorChainLengthInMultiplicationKey = @"MPMathRulesMaximumOperatorChainLengthInMultiplicationKey";
NSString *MPMathRulesIsUsingDegreesKey = @"MPMathRulesIsUsingDegreesKey";
@implementation MPMathRules
static MPMathRules *sharedRules;
+ (MPMathRules *)sharedRules
{
if (!sharedRules) {
sharedRules = [[MPMathRules alloc] init];
}
return sharedRules;
}
- (instancetype)init
{
if (sharedRules) {
return 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
{
_usingUserDefaultValues = usingUserDefaultValues;
// Save the current values
self.maximumOperatorChainLength = self.maximumOperatorChainLength;
self.maximumOperatorChainLengthInMultiplication = self.maximumOperatorChainLengthInMultiplication;
self.isUsingDegrees = self.isUsingDegrees;
}
- (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
forKey:MPMathRulesIsUsingDegreesKey];
}
}
@end

View File

@@ -1,22 +0,0 @@
//
// MPNegatedTerm.h
// MathPad
//
// Created by Kim Wittenburg on 22.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPTerm.h"
@class MPNegatedTerm;
@interface MPNegatedTerm : MPTerm
- (instancetype)initWithTerm:(MPTerm *)term; /* designated initializer */
@property (readonly, nonatomic, strong) MPTerm *term;
@end

View File

@@ -1,32 +0,0 @@
//
// MPNegatedTerm.m
// MathPad
//
// Created by Kim Wittenburg on 22.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPNegatedTerm.h"
@implementation MPNegatedTerm
- (instancetype)initWithTerm:(MPTerm *)term
{
self = [super init];
if (self) {
NSAssert(term != nil, @"term must not be nil.");
_term = term;
}
return self;
}
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
{
NSDecimalNumber *value = [self.term evaluate:error];
if (value) {
value = [value decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:-1]];
}
return value;
}
@end

View File

@@ -1,22 +0,0 @@
//
// MPNumber.h
// MathPad
//
// Created by Kim Wittenburg on 09.10.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPTerm.h"
@class MPNumber;
@interface MPNumber : MPTerm
- (instancetype)initWithNumber:(NSDecimalNumber *)number; /* designated initializer */
@property (readonly, nonatomic, strong) NSDecimalNumber *number;
@end

View File

@@ -1,32 +0,0 @@
//
// MPNumber.m
// MathPad
//
// Created by Kim Wittenburg on 09.10.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPNumber.h"
#import "MPParsedExpression.h"
#import "MPToken.h"
@implementation MPNumber
- (instancetype)initWithNumber:(NSDecimalNumber *)number
{
self = [super init];
if (self) {
NSAssert(number != nil, @"number must not be nil.");
_number = number;
}
return self;
}
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
{
return self.number;
}
@end

View File

@@ -1,35 +0,0 @@
//
// MPParenthesisFunction.h
// MathPad
//
// Created by Kim Wittenburg on 17.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFunction.h"
@class MPParenthesisFunction, MPExpression;
/*!
@class MPParenthesisFunction
@brief A parenthesis function encapsulates a single expression and thus
prioritizes it in evaluation.
@discussion Prioritizing the expression means that the complete expression
must be evaluated 'as is'. No part of it can be 'used' elsewhere
before the parenthesis function has been evaluated.
*/
@interface MPParenthesisFunction : MPFunction
/*!
@property expression
@brief The expression encapsulated by the parenthesis.
@discussion The expression must not define a variable.
*/
@property (nonatomic, strong) MPExpression *expression;
@end

View File

@@ -1,36 +0,0 @@
//
// MPParenthesisFunction.m
// MathPad
//
// Created by Kim Wittenburg on 17.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPParenthesisFunction.h"
#import "MPExpression.h"
#import "MPParenthesisTerm.h"
@implementation MPParenthesisFunction
MPFunctionAccessorImplementation(Expression, _expression)
- (NSArray *)childrenAccessors
{
return @[@"expression"];
}
- (Class)functionTermClass
{
return [MPParenthesisTerm class];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"(%@)", self.expression.description];
}
@end

View File

@@ -1,20 +0,0 @@
//
// MPParenthesisFunctionLayout.h
// MathPad
//
// Created by Kim Wittenburg on 17.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFunctionLayout.h"
@class MPParenthesisFunctionLayout, MPParenthesisFunction;
@interface MPParenthesisFunctionLayout : MPFunctionLayout
- (MPParenthesisFunction *)parenthesisFunction;
@end

View File

@@ -1,187 +0,0 @@
//
// MPParenthesisFunctionLayout.m
// MathPad
//
// Created by Kim Wittenburg on 17.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPParenthesisFunctionLayout.h"
#import "MPParenthesisFunction.h"
#define MPParenthesisFunctionOpeningParensOffset 2
#define MPParenthesisFunctionClosingParensOffset 0
@interface MPParenthesisFunctionLayout ()
- (NSBezierPath *)openingParens;
- (NSBezierPath *)closingParens;
- (NSBezierPath *)transformedOpeningParens;
- (NSBezierPath *)transformedClosingParens;
- (NSAffineTransform *)parenthesisTransform;
@end
@implementation MPParenthesisFunctionLayout
- (MPParenthesisFunction *)parenthesisFunction
{
return (MPParenthesisFunction *)self.function;
}
- (NSBezierPath *)openingParens
{
NSBezierPath *parens = [self objectForPrivateCacheIndex:0 generator:^id{
CTLineRef line = [self createLineForString:@"("];
CFArrayRef glyphRuns = CTLineGetGlyphRuns(line);
CFRetain(glyphRuns);
CTRunRef glyphRun = CFArrayGetValueAtIndex(glyphRuns, 0);
CFRetain(glyphRun);
CGGlyph *glyphs = malloc(1 * sizeof(CGGlyph));
CTRunGetGlyphs(glyphRun, CFRangeMake(0, 1), glyphs);
NSGlyph glyph = glyphs[0];
NSBezierPath *path = [[NSBezierPath alloc] init];
[path moveToPoint:NSZeroPoint];
[path appendBezierPathWithGlyph:glyph
inFont:self.font];
CFRelease(line);
CFRelease(glyphRuns);
CFRelease(glyphRun);
free(glyphs);
return path;
}];
return parens;
}
- (NSBezierPath *)closingParens
{
NSBezierPath *parens = [self objectForPrivateCacheIndex:1 generator:^id{
CTLineRef line = [self createLineForString:@")"];
CFArrayRef glyphRuns = CTLineGetGlyphRuns(line);
CFRetain(glyphRuns);
CTRunRef glyphRun = CFArrayGetValueAtIndex(glyphRuns, 0);
CFRetain(glyphRun);
CGGlyph *glyphs = malloc(1 * sizeof(CGGlyph));
CTRunGetGlyphs(glyphRun, CFRangeMake(0, 1), glyphs);
NSGlyph glyph = glyphs[0];
NSBezierPath *path = [[NSBezierPath alloc] init];
[path moveToPoint:NSZeroPoint];
[path appendBezierPathWithGlyph:glyph
inFont:self.font];
CFRelease(line);
CFRelease(glyphRuns);
CFRelease(glyphRun);
free(glyphs);
return path;
}];
return parens;
}
- (NSBezierPath *)transformedOpeningParens
{
NSBezierPath *parens = self.openingParens.copy;
[parens transformUsingAffineTransform:self.parenthesisTransform];
return parens;
}
- (NSBezierPath *)transformedClosingParens
{
NSBezierPath *parens = self.closingParens.copy;
[parens transformUsingAffineTransform:self.parenthesisTransform];
return parens;
}
- (NSAffineTransform *)parenthesisTransform
{
NSAffineTransform *transform = [NSAffineTransform transform];
[transform scaleXBy:1
yBy:self.scaleFactor];
return transform;
}
- (CGFloat)scaleFactor
{
NSRect parensBounds = self.openingParens.bounds;
NSRect expressionBounds = [self childLayoutAtIndex:0].bounds;
CGFloat parensHeight = parensBounds.size.height;
CGFloat expressionHeight = expressionBounds.size.height;
if (expressionHeight <= parensHeight) {
return 1.0;
}
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;
NSRect closingParensBounds = self.transformedClosingParens.bounds;
NSRect bounds = [self childLayoutAtIndex:0].bounds;
if (openingParensBounds.size.height > bounds.size.height) {
bounds.size.height = openingParensBounds.size.height;
}
if (closingParensBounds.size.height > bounds.size.height) {
bounds.size.height = closingParensBounds.size.height;
}
bounds.size.width += MPParenthesisFunctionOpeningParensOffset;
bounds.size.width += openingParensBounds.size.width;
bounds.size.width += MPParenthesisFunctionClosingParensOffset;
bounds.size.width += closingParensBounds.size.width;
return bounds;
}
- (void)draw
{
NSBezierPath *openingParens = self.transformedOpeningParens;
MPLayout *expressionLayout = [self childLayoutAtIndex:0];
NSBezierPath *closingParens = self.transformedClosingParens;
CGFloat x = 0;
CGFloat y = -openingParens.bounds.origin.y + expressionLayout.bounds.origin.y;
NSAffineTransform *transform = [NSAffineTransform transform];
[transform translateXBy:0
yBy:y];
[openingParens transformUsingAffineTransform:transform];
[openingParens fill];
x += openingParens.bounds.size.width;
x += MPParenthesisFunctionOpeningParensOffset;
x += expressionLayout.bounds.size.width;
x += MPParenthesisFunctionClosingParensOffset;
[transform translateXBy:x
yBy:0];
[closingParens transformUsingAffineTransform:transform];
[closingParens fill];
}
@end

View File

@@ -1,18 +0,0 @@
//
// MPParenthesisTerm.h
// MathPad
//
// Created by Kim Wittenburg on 15.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFunctionTerm.h"
@class MPParenthesisTerm;
@interface MPParenthesisTerm : MPFunctionTerm
@end

View File

@@ -1,20 +0,0 @@
//
// MPParenthesisTerm.m
// MathPad
//
// Created by Kim Wittenburg on 15.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPParenthesisTerm.h"
#import "MPParsedExpression.h"
@implementation MPParenthesisTerm
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
{
return [[self expressionAtIndex:0] evaluate:error];
}
@end

View File

@@ -1,36 +0,0 @@
//
// MPParsedExpression.h
// MathPad
//
// Created by Kim Wittenburg on 14.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
@class MPParsedExpression, MPTerm;
/*!
@const MPMathKitErrorDomain
@brief Predefined error domain for errors from the MathKit framework.
@discussion Errors in MathKit can occur during parsing of expressions or
during evaluation of expressions. These two can be distinguished
by the error code. Parsing errors have lower error codes.
Evaluation errors (math errors) have error codes from @c 100 upwards.
*/
FOUNDATION_EXPORT NSString *const MPMathKitErrorDomain;
FOUNDATION_EXPORT NSString *const MPPathToExpressionKey;
FOUNDATION_EXPORT NSString *const MPErrorRangeKey;
@interface MPParsedExpression : NSObject
@property (nonatomic, strong) NSString *definedVariable;
@property (nonatomic, strong) MPTerm *term;
- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error;
@end

View File

@@ -1,26 +0,0 @@
//
// MPParsedExpression.m
// MathPad
//
// Created by Kim Wittenburg on 14.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#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
{
return [self.term evaluate:error];
}
@end

View File

@@ -1,32 +0,0 @@
//
// MPPowerFunction.h
// MathPad
//
// Created by Kim Wittenburg on 30.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFunction.h"
@class MPPowerFunction, MPExpression;
/*!
@class MPPowerFunction
@brief This class represents a power.
*/
@interface MPPowerFunction : MPFunction
/*!
@property exponentExpression
@brief The receiver's exponent.
@discussion The base of the power can only be set during evaluation because
it is not actually part of the function element.
The exponent must not define a variable.
*/
@property (nonatomic, strong) MPExpression *exponentExpression;
@end

View File

@@ -1,27 +0,0 @@
//
// MPPowerFunction.m
// MathPad
//
// Created by Kim Wittenburg on 30.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPPowerFunction.h"
#import "MPExpression.h"
@implementation MPPowerFunction
MPFunctionAccessorImplementation(ExponentExpression, _exponentExpression)
- (NSArray *)childrenAccessors
{
return @[@"exponentExpression"];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"^%@", self.exponentExpression.description];
}
@end

View File

@@ -1,22 +0,0 @@
//
// MPPowerFunctionLayout.h
// MathPad
//
// Created by Kim Wittenburg on 30.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFunctionLayout.h"
@class MPPowerFunctionLayout, MPPowerFunction;
@interface MPPowerFunctionLayout : MPFunctionLayout
@property (nonatomic) NSRect baseBounds;
- (MPPowerFunction *)powerFunction;
@end

View File

@@ -1,64 +0,0 @@
//
// MPPowerFunctionLayout.m
// MathPad
//
// Created by Kim Wittenburg on 30.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPPowerFunctionLayout.h"
#import "MPPowerFunction.h"
#define kPowerFunctionExponentXOffset 1
#define kPowerFunctionTrailingOffset 2
@implementation MPPowerFunctionLayout
- (NSRect)baseBounds
{
if (NSEqualRects(_baseBounds, NSZeroRect)) {
return NSMakeRect(0, kMPEmptyBoxYOrigin, 0, kMPEmptyBoxHeight);
}
return _baseBounds;
}
- (MPPowerFunction *)powerFunction
{
return (MPPowerFunction *)self.function;
}
- (NSIndexSet *)indexesOfRemainingChildren
{
return [NSIndexSet indexSetWithIndex:0];
}
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index
{
CGFloat y = (self.baseBounds.size.height + self.baseBounds.origin.y) / 2;
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;
CGFloat y = self.baseBounds.origin.y;
CGFloat height = -y + [self offsetOfChildLayoutAtIndex:0].y + exponentBounds.size.height + exponentBounds.origin.y;
CGFloat width = kPowerFunctionExponentXOffset + exponentBounds.size.width + kPowerFunctionTrailingOffset;
return NSMakeRect(0, y, width, height);
}
- (void)draw
{}
@end

View File

@@ -1,20 +0,0 @@
//
// MPPowerTerm.h
// MathPad
//
// Created by Kim Wittenburg on 15.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFunctionTerm.h"
@class MPPowerTerm, MPTerm;
@interface MPPowerTerm : MPFunctionTerm
@property (nonatomic, strong) MPTerm *baseTerm;
@end

View File

@@ -1,31 +0,0 @@
//
// MPPowerTerm.m
// MathPad
//
// Created by Kim Wittenburg on 15.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPPowerTerm.h"
#import "MPParsedExpression.h"
@implementation MPPowerTerm
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
{
MPEvaluateExpression(exponent, 0);
NSDecimalNumber *base = [self.baseTerm evaluate:error];
ReturnNilIfNil(base);
if ([base isEqualToNumber:@(0)] && [exponent isEqualToNumber:@(0)]) {
// The C pow function returns 1 for pow(0, 0). Mathematically this should be undefined.
if (error) {
*error = [NSError errorWithDomain:MPMathKitErrorDomain
code:101
userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"0 to the power of 0 is undefined.", nil)}];
}
return nil;
}
return [[NSDecimalNumber alloc] initWithDouble:pow(base.doubleValue, exponent.doubleValue)];
}
@end

View File

@@ -1,22 +0,0 @@
//
// MPProductTerm.h
// MathPad
//
// Created by Kim Wittenburg on 15.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPTerm.h"
@class MPProductTerm;
@interface MPProductTerm : MPTerm
- (instancetype)initWithFactors:(NSArray *)factors; /* designated initializer */
@property (readonly, nonatomic, strong) NSArray *factors;
@end

View File

@@ -1,49 +0,0 @@
//
// MPProductTerm.m
// MathPad
//
// Created by Kim Wittenburg on 15.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPProductTerm.h"
#import "MPParsedExpression.h"
#import "MPPowerFunction.h"
#import "MPFunctionTerm.h"
#import "MPElementaryFunctionTerm.h"
#import "MPNumber.h"
#import "MPFactorialTerm.h"
#import "MPPowerTerm.h"
#import "MPVariable.h"
#import "MPToken.h"
@implementation MPProductTerm
- (instancetype)initWithFactors:(NSArray *)factors
{
self = [super init];
if (self) {
NSAssert(factors != nil, @"factors must not be nil.");
NSAssert(factors.count > 0, @"factors must not be empty.");
_factors = factors;
}
return self;
}
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
{
NSDecimalNumber *value = [NSDecimalNumber one];
for (MPTerm *factor in self.factors) {
NSDecimalNumber *currentValue = [factor evaluate:error];
if (!currentValue) {
return nil;
}
value = [value decimalNumberByMultiplyingBy:currentValue];
}
return value;
}
@end

View File

@@ -1,295 +0,0 @@
//
// MPRange.h
// MathPad
//
// Created by Kim Wittenburg on 18.04.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPExpression.h"
/// Convenience Constructor Macro
#define MPMakeRangePath(loc, len) [MPRangePath rangePathWithLocation:(loc) length:(len)]
@class MPRangePath, MPExpression;
/*!
@class MPRangePath
@brief @c MPRangePath is like @c NSRange but with an @c NSIndexPath as
location.
@discussion Range paths are intended to address a range (as defined by @c
NSRange) that resides in a tree structure. Thus the @c location
property of a range path is a @c NSIndexPath.
@c MPRangePath instances are immutable.
*/
@interface MPRangePath : NSObject <NSCopying, NSCoding>
#pragma mark Creation Methods
/*!
@methodgroup Creation Methods
*/
/*!
@method init
@brief Initializes an empty range path.
@discussion This method is a convenience initializer to create a range path with
an empty location and a length of @c 0.
@return A @c MPRangePath instance.
*/
- (instancetype)init;
/*!
@method initWithRange:
@brief Initializes a newly created range path with the specified range.
@discussion This method is a convenience initializer to convert from a @c
NSRange to a @c MPRangePath. The location of @c aRange is
translated into a location for the range path.
@param aRange
The range to be converted into a @c MPRangePath.
@return A @c MPRangePath instance.
*/
- (instancetype)initWithRange:(NSRange)aRange;
/*!
@method initWithLocation:lenght:
@brief Initializes a newly created range path with the specified location
and length.
@discussion The last index in the @c location should address the first item
that is actually selected.
@param location
The location of the first element that should be addressed by
this range path.
@param length
The number of elements to be addressed by this range path.
@return A @c MPRangePath instance.
*/
- (instancetype)initWithLocation:(NSIndexPath *)location
length:(NSUInteger)length; /* designated initializer */
/*!
@method rangePath
@brief Allocates and initializes an empty @c NSRangePath instance.
@discussion An empty range path's location has @c 0 indexes and a length of
@c 0.
@return A newly created @c MPRangePath instance.
*/
+ (instancetype)rangePath;
/*!
@method rangePathWithRange:
@brief Allocates a new @c MPRangePath instance and initializes it with
the specified location and length.
@discussion The location of @c aRange is translated into an index path for
the range path.
@param aRange
The range to be converted into a range path.
@return A newly created @c MPRangePath instance.
*/
+ (instancetype)rangePathWithRange:(NSRange)aRange;
/*!
@method rangePathWithLocation:length:
@brief Allocates a new @c MPRangePath instance and initializes it with
the specified location and length.
@param location
The location of the first element that should be addressed by the
range path.
@param length
The number of elements that should be addressed by the range
path.
@return A newly created @c MPRangePath instance.
*/
+ (instancetype)rangePathWithLocation:(NSIndexPath *)location
length:(NSUInteger)length;
#pragma mark Properties
/*!
@methodgroup Properties
*/
/*!
@property location
@brief The location of the first element addressed by the receiver.
*/
@property (readonly, nonatomic, strong) NSIndexPath *location;
/*!
@property length
@brief The number of elements addressed by the receiver. The element
addressed by the last index of the location property plus the
length of the receiver is NOT considered inside of the range
path.
*/
@property (readonly, nonatomic) NSUInteger length;
/*!
@method maxRangePath
@brief Similar to the @c NSRange method @c NSMaxRange this method
returns the first index after the receiving @c NSRangePath.
@return The first index after the receiving @c NSRangePath.
*/
- (NSIndexPath *)maxRangePath;
/*!
@method rangeAtLastIndex
@brief Returns a @c NSRange constructed from the last index of the
receiver's location and the receiver's length.
@return The range identifying the elements at the last index in the
receiver's index path.
*/
- (NSRange)rangeAtLastIndex;
#pragma mark Working With Ranges
/*!
@methodgroup Working With Ranges
*/
/*!
@method containsLocation:
@brief Checks wether the range path addressed by the receiver includes
@c location.
@discussion The index path is considered inside the receiver if the indexes
in the receiver's @c location property are equal to the
respective indexes index in @c location. For the last position of
the last index of the receiver's @c location it is also valid if
the respective index is inside the range at the last index.
@param location
The index path to check wether it is contained in the receiver.
@return @c YES if the range path addressed by the receiver also contains
@c location, @c NO otherwise.
*/
- (BOOL)containsLocation:(NSIndexPath *)location;
/*!
@method containsRangePath:
@brief Checks wether the receiver completely contains @c aRangePath.
@discussion A range path is contained by another range path if either the
receiver's range at its last index contains the index at the
respective location of the location path of @c aRangePath or (if
the locations are of the same length) the receiver's range at its
last index contains the last index's range of @c aRangePath.
@param aRangePath
The range path to check wether it is contained in the receiver.
@return @c YES if the range path addressed by the receiver also includes
@c aRangePath, @c NO otherwise.
*/
- (BOOL)containsRangePath:(MPRangePath *)aRangePath;
/*!
@method isEqualToRangePath:
@brief Checks wether the receiver and @c aRangePath are equal.
@discussion If you know that you are comparing two range paths (that are not
@c nil) you should use this method over @c -isEqual: because it
is more performant.
@param aRangePath
The range path to check for equality. If this parameter is @c nil
this method may raise an exception.
@return @c YES if the receiver is equal to @c aRangePath, @c NO
otherwise.
*/
- (BOOL)isEqualToRangePath:(MPRangePath *)aRangePath;
@end
@interface MPExpression (MPRangeExtension)
/*!
@method subexpressionWithRangePath:
@brief Creates a new expression with the symbols in the specified range
path.
@discussion The elements in the newly created expression are copied to the
new exoression. The range path is specified in the length
reference frame.
If the specified range exceeds the receiver's bounds a @c
NSRangeException is raised.
@param aRangePath
The range path from which to create the new expression.
@return A new expression.
*/
- (MPExpression *)subexpressionWithRangePath:(MPRangePath *)aRangePath
referenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@method replaceItemsInRangePath:referenceFrame:withElements:
@brief Replaces the items in the specified range path with the contents
of @c elements.
@discussion All objects in the @c elements array must conform to the @c
MPExpressionElement protocol. If one or more objects do not
conform to the protocol a @c MPIllegalElementException will be
raised.
@param rangePath
The range path to be replaced. The path of the range path is
expressed in the element reference frame. The range of the range
path is expressed in the specified @c referenceFrame.
@param referenceFrame
The reference frame to use. This only applies to the range at the
last index of the range path. The path of the range path is
always expressed in the element reference frame.
*/
- (void)replaceItemsInRangePath:(MPRangePath *)rangePath
referenceFrame:(MPReferenceFrame)referenceFrame
withElements:(NSArray *)elements;
@end

View File

@@ -1,215 +0,0 @@
//
// MPRange.m
// MathPad
//
// Created by Kim Wittenburg on 18.04.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPRangePath.h"
@implementation MPRangePath
#pragma mark Creation Methods
- (instancetype)init
{
return [self initWithLocation:[[NSIndexPath alloc] init]
length:0];
}
- (instancetype)initWithLocation:(NSIndexPath *)location
length:(NSUInteger)length
{
self = [super init];
if (self) {
_location = location;
_length = length;
}
return self;
}
- (instancetype)initWithRange:(NSRange)aRange
{
return [self initWithLocation:[[NSIndexPath alloc] initWithIndex:aRange.location]
length:aRange.length];
}
+ (instancetype)rangePath
{
return [[self alloc] init];
}
+ (instancetype)rangePathWithRange:(NSRange)aRange
{
return [[self alloc] initWithRange:aRange];
}
+ (instancetype)rangePathWithLocation:(NSIndexPath *)location
length:(NSUInteger)length
{
return [[self alloc] initWithLocation:location
length:length];
}
#pragma mark Properties
- (NSIndexPath *)maxRangePath
{
NSUInteger lastIndex = [self.location indexAtPosition:self.location.length-1];
NSUInteger newLastIndex = lastIndex + self.length;
return [[self.location indexPathByRemovingLastIndex] indexPathByAddingIndex:newLastIndex];
}
- (NSRange)rangeAtLastIndex
{
return NSMakeRange([self.location indexAtPosition:self.location.length-1], self.length);
}
#pragma mark Working With Ranges
- (BOOL)containsLocation:(NSIndexPath *)location
{
return [self containsRangePath:[[MPRangePath alloc] initWithLocation:location length:0]];
}
- (BOOL)containsRangePath:(MPRangePath *)aRangePath
{
if (aRangePath.location.length < self.location.length) {
return NO;
}
// Compare indices (except the last one)
for (NSUInteger pos = 0; pos < self.location.length-1; pos++) {
NSUInteger selfIndex = [self.location indexAtPosition:pos];
NSUInteger otherIndex = [aRangePath.location indexAtPosition:pos];
if (selfIndex != otherIndex) {
return NO;
}
}
// Compare range at last index
NSUInteger selfIndex = [self.location indexAtPosition:self.location.length-1];
NSUInteger otherIndex = [aRangePath.location indexAtPosition:self.location.length-1];
if (aRangePath.location.length > self.location.length) {
return NSLocationInRange(otherIndex, self.rangeAtLastIndex);
} else {
return otherIndex >= selfIndex && NSMaxRange(aRangePath.rangeAtLastIndex) <= NSMaxRange(self.rangeAtLastIndex);
}
}
- (BOOL)isEqual:(id)object
{
if (self == object) {
return YES;
}
if (object == nil) {
return NO;
}
if (![object isKindOfClass:[MPRangePath class]]) {
return NO;
}
return [self isEqualToRangePath:(MPRangePath *)object];
}
- (BOOL)isEqualToRangePath:(MPRangePath *)aRangePath
{
return [self.location isEqual:aRangePath.location] && self.length == aRangePath.length;
}
- (NSString *)description
{
NSMutableString *description = [[NSMutableString alloc] initWithString:@"MPRangePath<location="];
if (self.location.length > 0) {
[description appendFormat:@"%ld", [self.location indexAtPosition:0]];
}
for (NSUInteger position = 1; position < self.location.length; position ++) {
[description appendFormat:@",%ld", [self.location indexAtPosition:position]];
}
[description appendFormat:@" length=%ld", self.length];
[description appendString:@">"];
return description.copy;
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone
{
MPRangePath *copy = [[MPRangePath allocWithZone:zone] initWithLocation:self.location.copy length:self.length];
return copy;
}
#pragma mark - NSCoding
- (id)initWithCoder:(NSCoder *)aDecoder
{
return [self initWithLocation:[aDecoder decodeObjectForKey:@"location"]
length:[aDecoder decodeIntegerForKey:@"length"]];
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.location forKey:@"location"];
[aCoder encodeInteger:self.length forKey:@"length"];
}
@end
@implementation MPExpression (MPRangeExtension)
- (NSArray *)itemsInRangePath:(MPRangePath *)rangePath
referenceFrame:(MPReferenceFrame)referenceFrame
{
MPExpression *targetExpression = [self elementAtIndexPath:[rangePath.location indexPathByRemovingLastIndex]];
return [targetExpression itemsInRange:rangePath.rangeAtLastIndex referenceFrame:referenceFrame];
}
- (MPExpression *)subexpressionWithRangePath:(MPRangePath *)aRangePath
referenceFrame:(MPReferenceFrame)referenceFrame
{
MPExpression *targetExpression = [self elementAtIndexPath:[aRangePath.location indexPathByRemovingLastIndex]];
if (![targetExpression isKindOfClass:[MPExpression class]]) {
// TODO: Raise appropriate exception
return nil;
}
return [targetExpression subexpressionWithRange:aRangePath.rangeAtLastIndex
referenceFrame:referenceFrame];
}
- (void)replaceItemsInRangePath:(MPRangePath *)rangePath
referenceFrame:(MPReferenceFrame)referenceFrame
withElements:(NSArray *)elements
{
MPExpression *targetExpression = [self elementAtIndexPath:[rangePath.location indexPathByRemovingLastIndex]];
[targetExpression replaceItemsInRange:rangePath.rangeAtLastIndex
referenceFrame:referenceFrame
withElements:elements];
}
@end

View File

@@ -1,73 +0,0 @@
//
// MPSumFunction.h
// MathPad
//
// Created by Kim Wittenburg on 22.04.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
/*!
@header
This is a description of the MPFunction class.
It has multiple lines.
*/
#import "MPFunction.h"
@class MPSumFunction, MPExpression;
/*!
@class MPSumFunction
@brief This class represents a sum function (generally noted using a
capital sigma).
@discussion A sum function has a start and a target expression indicating how
often the sum expression should be evaluated. Both the value of
the start expression and the target expressions are included in
the iterations.
Each iteration the sum value is incremented by @c 1. If the start
and target expression evaluate to the same value the sum is
evaluated once.
*/
@interface MPSumFunction : MPFunction
/*!
@property startExpression
@brief The value of the first iteration.
@discussion The start expression must define a variable that may be used in
the sum expression. If the start expression does not define a
variable the sum function will fail on validation.
@code Some samples
*/
@property (nonatomic, strong) MPExpression *startExpression; /* Index 0 */
/*!
@property targetExpression
@brief The value if the last iteration.
@discussion The target expression must not define a variable.
*/
@property (nonatomic, strong) MPExpression *targetExpression; /* Index 1 */
/*!
@property sumExpression
@brief The sum expression evaluated multiple times.
@discussion During evaluation of the sum expression the variable defined in
the start expression is available. That variable always contains
the value of the current iteration.
The sum expression itself must not define a variable.
*/
@property (nonatomic, strong) MPExpression *sumExpression; /* Index 2 */
@end

View File

@@ -1,45 +0,0 @@
//
// MPSumFunction.m
// MathPad
//
// Created by Kim Wittenburg on 22.04.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPSumFunction.h"
#import "MPSumFunctionTerm.h"
#import "MPExpression.h"
@implementation MPSumFunction
MPFunctionAccessorImplementation(StartExpression, _startExpression)
MPFunctionAccessorImplementation(TargetExpression, _targetExpression)
MPFunctionAccessorImplementation(SumExpression, _sumExpression)
- (NSArray *)childrenAccessors
{
return @[@"startExpression", @"targetExpression", @"sumExpression"];
}
- (BOOL)expectsVariableDefinitionInChildAtIndex:(NSUInteger)index
{
return index == 0;
}
- (Class)functionTermClass
{
return [MPSumFunctionTerm class];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"Sum(From: %@; To: %@; Using: %@)", self.startExpression, self.targetExpression, self.sumExpression];
}
@end

View File

@@ -1,20 +0,0 @@
//
// MPSumFunctionLayout.h
// MathPad
//
// Created by Kim Wittenburg on 23.04.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFunctionLayout.h"
@class MPSumFunctionLayout, MPSumFunction;
@interface MPSumFunctionLayout : MPFunctionLayout
- (MPSumFunction *)sumFunction;
@end

View File

@@ -1,160 +0,0 @@
//
// MPSumFunctionLayout.m
// MathPad
//
// Created by Kim Wittenburg on 23.04.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#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
{
return (MPSumFunction *)self.function;
}
- (NSUInteger)indexOfLeadingChild
{
return 2;
}
- (NSUInteger)indexOfTrailingChild
{
return 2;
}
- (NSUInteger)indexOfChildAfterChildAtIndex:(NSUInteger)index
{
if (index != 2) {
return 2;
}
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{
return [self createLineForString:@"∑"
usingFont:[NSFont fontWithName:@"Times New Roman"
size:self.contextInferredFontSize]];
}];
return line;
}
- (NSRect)localLineBounds
{
NSRect lineBounds = CTLineGetBoundsWithOptions(self.line, 0);
NSRect startExpressionBounds = [self childLayoutAtIndex:0].bounds;
NSRect targetExpressionBounds = [self childLayoutAtIndex:1].bounds;
CGFloat width = MAX(MAX(startExpressionBounds.size.width, targetExpressionBounds.size.width), lineBounds.size.width);
CGFloat xPosition = (width - lineBounds.size.width) / 2;
return NSMakeRect(xPosition, lineBounds.origin.y, lineBounds.size.width, lineBounds.size.height);
}
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index
{
NSRect childBounds = [self childLayoutAtIndex:index].bounds;
NSRect localLineBounds = [self localLineBounds];
NSPoint offset;
if (index == 0) {
// Start Expression
offset.x = localLineBounds.origin.x + localLineBounds.size.width / 2 - childBounds.size.width / 2;
offset.y = localLineBounds.origin.y - kSumFunctionStartExpressionOffset - childBounds.size.height - childBounds.origin.y;
} else if (index == 1) {
// Target Expression
offset.x = localLineBounds.origin.x + localLineBounds.size.width / 2 - childBounds.size.width / 2;
offset.y = kSumFunctionTargetExpressionOffset + localLineBounds.size.height + localLineBounds.origin.y - childBounds.origin.y;
} else {
// Sum Expression
MPLayout *startExpressionLayout = [self childLayoutAtIndex:0];
MPLayout *targetExpressionLayout = [self childLayoutAtIndex:1];
CGFloat sumWidth = MAX(MAX(localLineBounds.origin.x + localLineBounds.size.width, startExpressionLayout.bounds.size.width), targetExpressionLayout.bounds.size.width);
offset.x = sumWidth + kSumFunctionSumExpressionOffset;
offset.y = 0;
}
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) {
return [NSIndexPath indexPathWithIndex:0];
} else {
return [NSIndexPath indexPathWithIndex:1];
}
}
- (NSRect)generateBounds
{
NSRect lineBounds = CTLineGetBoundsWithOptions(self.line, 0);
NSRect startExpressionBounds = [self childLayoutAtIndex:0].bounds;
NSRect targetExpressionBounds = [self childLayoutAtIndex:1].bounds;
NSRect sumExpressionBounds = [self childLayoutAtIndex:2].bounds;
NSRect bounds = lineBounds;
bounds.size.width = MAX(lineBounds.size.width, startExpressionBounds.size.width);
bounds.size.height += startExpressionBounds.size.height + kSumFunctionStartExpressionOffset;
bounds.origin.y -= startExpressionBounds.size.height + kSumFunctionStartExpressionOffset;
bounds.size.width = MAX(bounds.size.width, targetExpressionBounds.size.width);
bounds.size.height += targetExpressionBounds.size.height + kSumFunctionTargetExpressionOffset;
bounds.size.width += kSumFunctionSumExpressionOffset + sumExpressionBounds.size.width + kSumFunctionTrailingOffset;
CGFloat yOffsetBottom = bounds.origin.y - sumExpressionBounds.origin.y;
CGFloat yOffsetTop = sumExpressionBounds.size.height - (yOffsetBottom + bounds.size.height);
bounds.size.height = MAX(yOffsetBottom, 0) + bounds.size.height + MAX(yOffsetTop, 0);
bounds.origin.y = MIN(sumExpressionBounds.origin.y, bounds.origin.y);
return bounds;
}
- (void)draw
{
// Get the current context
CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
// Set the text matrix
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
// Draw the sum symbol
CTLineRef line = [self line];
CFRetain(line);
NSRect localLineBounds = [self localLineBounds];
CGContextSetTextPosition(context, localLineBounds.origin.x, 0);
CTLineDraw(line, context);
CFRelease(line);
}
@end

View File

@@ -1,18 +0,0 @@
//
// MPSumFunctionTerm.h
// MathPad
//
// Created by Kim Wittenburg on 15.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFunctionTerm.h"
@class MPSumFunctionTerm;
@interface MPSumFunctionTerm : MPFunctionTerm
@end

View File

@@ -1,38 +0,0 @@
//
// MPSumFunctionTerm.m
// MathPad
//
// Created by Kim Wittenburg on 15.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPSumFunctionTerm.h"
#import "MPParsedExpression.h"
@implementation MPSumFunctionTerm
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
{
MPParsedExpression *startExpression = [self expressionAtIndex:0];
MPParsedExpression *sumExpression = [self expressionAtIndex:2];
NSDecimalNumber *start = [startExpression evaluate:error];
ReturnNilIfNil(start);
MPEvaluateExpression(target, 1);
NSDecimalNumber *value = [NSDecimalNumber zero];
for (NSDecimalNumber *current = start;
[current compare:target] <= 0;
current = [current decimalNumberByAdding:[[NSDecimalNumber alloc] initWithInteger:1]]) {
if (![self defineVariable:startExpression.definedVariable value:current error:error]) {
return nil;
}
NSDecimalNumber *currentValue = [sumExpression evaluate:error];
ReturnNilIfNil(currentValue);
value = [value decimalNumberByAdding:currentValue];
}
return value;
}
@end

View File

@@ -1,23 +0,0 @@
//
// MPSumTerm.h
// MathPad
//
// Created by Kim Wittenburg on 14.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPTerm.h"
@class MPSumTerm;
@interface MPSumTerm : MPTerm
- (instancetype)initWithSummands:(NSArray *)summands; /* designated initializer */
@property (readonly, nonatomic, strong) NSArray *summands;
@end

View File

@@ -1,42 +0,0 @@
//
// MPSumTerm.m
// MathPad
//
// Created by Kim Wittenburg on 14.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPSumTerm.h"
#import "MPParsedExpression.h"
#import "MPProductTerm.h"
#import "MPToken.h"
@implementation MPSumTerm
- (instancetype)initWithSummands:(NSArray *)summands
{
self = [super init];
if (self) {
NSAssert(summands != nil, @"summands must not be nil.");
NSAssert(summands.count > 0, @"summands must not be empty.");
_summands = summands;
}
return self;
}
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
{
NSDecimalNumber *value = [NSDecimalNumber zero];
for (MPTerm *summand in self.summands) {
NSDecimalNumber *currentValue = [summand evaluate:error];
if (!currentValue) {
return nil;
}
value = [value decimalNumberByAdding:currentValue];
}
return value;
}
@end

View File

@@ -1,28 +0,0 @@
//
// MPTerm.h
// MathPad
//
// Created by Kim Wittenburg on 11.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#define ReturnIfNil(test, value) if (test == nil) return value
#define ReturnNilIfNil(test) if (test == nil) return nil
@class MPTerm;
@interface MPTerm : NSObject
- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error;
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error;
- (BOOL)defineVariable:(NSString *)variableName
value:(NSDecimalNumber *)value
error:(NSError *__autoreleasing *)error;
- (NSDecimalNumber *)valueForVariable:(NSString *)variableName
error:(NSError *__autoreleasing *)error;
- (void)undefineVariable:(NSString *)variableName; // Should rarely be needed, defined variables are automatically undefined at the end of evaluation.
@end

View File

@@ -1,68 +0,0 @@
//
// MPTerm.m
// MathPad
//
// Created by Kim Wittenburg on 11.11.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPTerm.h"
#import "MPParsedExpression.h"
#import "MPSumTerm.h"
#import "MPEvaluationContext.h"
@implementation MPTerm
- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error
{
[[MPEvaluationContext sharedContext] push];
NSDecimalNumber *result = [self doEvaluation:error];
[[MPEvaluationContext sharedContext] pop];
return result;
}
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
{
NSLog(@"%@", self.class);
return nil;
}
- (BOOL)defineVariable:(NSString *)variableName
value:(NSDecimalNumber *)value
error:(NSError *__autoreleasing *)error
{
BOOL couldDefineVariable = [[MPEvaluationContext sharedContext] defineVariable:variableName
value:value];
if (!couldDefineVariable) {
if (error) {
NSString *localizedDescription = [NSString stringWithFormat:NSLocalizedString(@"Redifinition of variable \"%@\".", nil), variableName];
*error = [NSError errorWithDomain:MPMathKitErrorDomain
code:100
userInfo:@{NSLocalizedDescriptionKey: localizedDescription}];
}
}
return couldDefineVariable;
}
- (NSDecimalNumber *)valueForVariable:(NSString *)variableName
error:(NSError *__autoreleasing *)error
{
NSDecimalNumber *value = [[MPEvaluationContext sharedContext] valueForVariable:variableName];
if (!value) {
if (error) {
NSString *localizedDescription = [NSString stringWithFormat:NSLocalizedString(@"Undefined variable \"%@\".", nil), variableName];
*error = [NSError errorWithDomain:MPMathKitErrorDomain
code:101
userInfo:@{NSLocalizedDescriptionKey: localizedDescription}];
}
}
return value;
}
- (void)undefineVariable:(NSString *)variableName
{
[[MPEvaluationContext sharedContext] undefineVariable:variableName];
}
@end

View File

@@ -1,160 +0,0 @@
//
// MPToken.h
// MathPad
//
// Created by Kim Wittenburg on 19.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
@class MPToken;
@protocol MPToken;
/*!
@typedef MPTokenType
@brief The type of a <code>token</code> identifies its behaviour in a mathematical
context.
@constant MPEOFToken
A token that represents the end of the input. This token should
not be used to create a @c MPToken instance. It exists so that
you can safely send a @c tokenType message to @c nil without
getting confusing results. A token with this token type should be
interpreted as 'no token'.
@constant MPMultiplicationSymbolToken
A multiplication symbol.
@constant MPOperatorListToken
A list of operators (+ and - symbols). The token may be longer
than the number of operators if there are spaces between them.
@constant MPElementaryFunction
@em Most elementary functions are represented by this token type.
Elementary functions not categorized as such are those that can
not be represented as text (e.g. roots and powers).
@constant MPNumberToken
A number. This may be an integer or a floating point number.
Floating point numbers contain a @c NSLocaleDecimalSeparator.
@constant MPVariableToken
A variable. A variable is exactly one character long.
@constant MPFactorialToken
The factorial symbol (!).
@constant MPEqualsToken
The equals sign.
@constant MPGenericFunctionToken
A function represented by the @c MPFunction class. A token with
this token type is guaranteed to be a @c MPFunction instance.
@constant MPWhitespaceToken
A whitespace. This token can typically be ignored.
@constant MPUnidentifiedToken
Any symbol that does not match any other token.
*/
typedef NS_ENUM(NSUInteger, MPTokenType) {
MPEOFToken = 0,
MPMultiplicationSymbolToken,
MPOperatorListToken,
MPElementaryFunctionToken,
MPNumberToken,
MPDeformedNumberToken,
MPVariableToken,
MPEqualsToken,
MPFactorialToken,
MPPowerToken,
MPGenericFunctionToken,
MPWhitespaceToken,
MPUnidentifiedToken,
};
/*!
@protocol MPToken
@brief Tokens represent logical units in an expresion.
*/
@protocol MPToken <NSObject>
/*!
@method tokenType
@brief Returns the receiver's token type.
@discussion The token type identifies what kind of token the receiver is. For
more information see the documentation on the <code>MPTokenType
</code> enum.
@return The receiver's token type.
*/
- (MPTokenType)tokenType;
/*!
@method range
@brief Returns the receiver's range.
@discussion The range identifies where the token is in the expression. It is
specified in the symbol reference frame.
@return The range the token occupies in the expression it was parsed
from specified in the symbol reference frame.
*/
- (NSRange)range;
/*!
@method stringValue
@brief The string that caused the token to be parsed.
@discussion Depending on the type of the token the string value can have a
fixed or variable length. For example the equals token always has
a length of @c 1 whereas a number or whitespace token can be much
longer.
@return The receiver's string value.
*/
- (NSString *)stringValue;
@end
/*!
@class MPToken
@brief The @c MPToken class implements the functionality of the @c
MPToken protocol. Most tokens are instances of the @c MPToken
class.
*/
@interface MPToken : NSObject <MPToken>
/*!
@method initWithTokenType:range:stringValue:
@brief Creates a new @c MPToken instance.
@param tokenType
The type of the token.
@param range
The range of the token in the expression. Specified in the symbol
reference frame.
@param input
The string value of the token.
@return A newly initialized token.
*/
- (instancetype)initWithTokenType:(MPTokenType)tokenType
range:(NSRange)range
stringValue:(NSString *)input; /* designated initializer */
@end

View File

@@ -1,44 +0,0 @@
//
// MPToken.m
// MathPad
//
// Created by Kim Wittenburg on 19.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPToken.h"
@interface MPToken ()
@property (readonly, nonatomic) NSRange range;
@property (readonly, nonatomic, strong) NSString *stringValue;
@property (readonly, nonatomic) MPTokenType tokenType;
@end
@implementation MPToken
- (instancetype)initWithTokenType:(MPTokenType)tokenType
range:(NSRange)range
stringValue:(NSString *)input
{
self = [super init];
if (self) {
_range = range;
_stringValue = input.copy;
_tokenType = tokenType;
}
return self;
}
- (NSString *)description
{
return self.stringValue;
}
@end

View File

@@ -1,22 +0,0 @@
//
// MPVariable.h
// MathPad
//
// Created by Kim Wittenburg on 09.10.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPTerm.h"
@class MPVariable;
@interface MPVariable : MPTerm
- (instancetype)initWithVariableName:(NSString *)variableName; /* designated initializer */
@property (readonly, nonatomic, strong) NSString *variableName;
@end

View File

@@ -1,36 +0,0 @@
//
// MPVariable.m
// MathPad
//
// Created by Kim Wittenburg on 09.10.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPVariable.h"
#import "MPParsedExpression.h"
#import "MPToken.h"
#import "MPExpression.h"
#import "MPEvaluationContext.h"
@implementation MPVariable
- (instancetype)initWithVariableName:(NSString *)variableName
{
self = [super init];
if (self) {
NSAssert(variableName != nil, @"variableName must not be nil.");
_variableName = variableName;
}
return self;
}
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
{
return [self valueForVariable:self.variableName
error:error];
}
@end

View File

@@ -1,16 +0,0 @@
//
// MPWhiteView.h
// MathPad
//
// Created by Kim Wittenburg on 28.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
@class MPWhiteView;
@interface MPWhiteView : NSView
@end

View File

@@ -1,22 +0,0 @@
//
// MPWhiteView.m
// MathPad
//
// Created by Kim Wittenburg on 28.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPWhiteView.h"
@implementation MPWhiteView
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
[[NSColor whiteColor] set];
NSRectFill(dirtyRect);
}
@end

View File

@@ -1,163 +0,0 @@
//
// NSIndexPath+MPRemoveFirstIndex.h
// MathPad
//
// Created by Kim Wittenburg on 23.04.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
@interface NSIndexPath (MPAdditions)
/*!
@property firstIndex
@brief The first index from the index path.
@discussion If the index path is empty @c NSNotFound is returned.
*/
@property (readonly, nonatomic) NSUInteger firstIndex;
/*!
@property lastIndex
@brief The last index from the index path.
@discussion If the index path is empty @c NSNotFound is returned.
*/
@property (readonly, nonatomic) NSUInteger lastIndex;
/*!
@method indexPathByReplacingLastIndexWithIndex:
@brief Provides an index path with the index in the receiving index path
where the last one is replaced by @c index.
@discussion If the receiving index path is empty an index path of length @c 1
is returned. The last index in the returned index path is @c
index.
@param index
The index with which to replace the last index in the receiving
index path.
@return A new index path with @c index as its last index.
*/
- (NSIndexPath *)indexPathByReplacingLastIndexWithIndex:(NSUInteger)index;
/*!
@method indexPathByRemovingFirstIndex
@brief Provides an index path with the indexes in the receiving index
path, excluding the first one.
@discussion Returns an empty NSIndexPath instance if the receiving index
paths length is 1 or less.
@return A new index path with the receiving index paths indexes,
excluding the first one.
*/
- (NSIndexPath *)indexPathByRemovingFirstIndex;
/*!
@method indexPathByPreceedingIndex:
@brief Provides an index path with the specified index followed by the
indexes of the receiver.
@discussion If the receiver does not contain any indexes the specified index
is the only index contained in the returned index path.
@param index
The index new index preceeding all others
@return A new index path with all the receiver's indexes preceeded by @c
index.
*/
- (NSIndexPath *)indexPathByPreceedingIndex:(NSUInteger)index;
/*!
@method indexPathByIncrementingLastIndex
@brief Provides an index path with the indexes in the receiving index
path where the last one is incremented by @c 1.
@discussion If the receiver does not contain any indexes an empty index path
is returned.
@return A new index path with all the receiver's indexes and the last one
incremented by @c 1.
*/
- (NSIndexPath *)indexPathByIncrementingLastIndex;
/*!
@method indexPathByDecrementingLastIndex
@brief Provides an index path with the indexes in the receiving index
path where the last one is decremented by @c 1.
@discussion If the receiver does not contain any indexes an empty index path
is returned.
@return A new index path with all the receiver's indexes and the last one
decremented by @c 1.
*/
- (NSIndexPath *)indexPathByDecrementingLastIndex;
/*!
@method indexPathByRemovingIndexesFrom:
@brief Provides an index path with the indexes in the recieving index
path up to the index at the specified position.
@discussion If @c from is greater or equal to the number of indexes in the
receiving index path only the indexes to the end of the receiver
are removed.
@param from
The position of the first index to be excluded in the returned
index path.
@return An index path with all indexes from the receiver up to position
@c from.
*/
- (NSIndexPath *)indexPathByRemovingIndexesFrom:(NSUInteger)from;
/*!
@method indexPathByRemovingIndexesTo:
@brief Provides an index path with the indexes in the receiving index
path where the first indexes are removed.
@discussion @c to specifies the number of indexes to be removed from the
front. Thus the index at position @c to will be included in the
returned index path.
@param to
The number of indexes to remove from the front.
@return A new index path with all the receiver's indexes exept the first
@c to ones.
*/
- (NSIndexPath *)indexPathByRemovingIndexesTo:(NSUInteger)to;
/*!
@method commonIndexPathWith:
@brief Provides an index path that contains the first indexes of the
receiver that are equal to the specified index path.
@discussion If one index path is completely included in the other a new index
path is returned that is equal to the contained index path.
@param indexPath
The index path to compare the receiver against.
@return A new index path with the first indexes of the receiver that are
also present in @c indexPath.
*/
- (NSIndexPath *)commonIndexPathWith:(NSIndexPath *)indexPath;
@end

View File

@@ -1,117 +0,0 @@
//
// NSIndexPath+MPRemoveFirstIndex.m
// MathPad
//
// Created by Kim Wittenburg on 23.04.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "NSIndexPath+MPAdditions.h"
@implementation NSIndexPath (MPAdditions)
- (NSUInteger)firstIndex
{
return [self indexAtPosition:0];
}
- (NSUInteger)lastIndex
{
return [self indexAtPosition:self.length-1];
}
- (NSIndexPath *)indexPathByReplacingLastIndexWithIndex:(NSUInteger)index
{
return [[self indexPathByRemovingLastIndex] indexPathByAddingIndex:index];
}
- (NSIndexPath *)indexPathByRemovingFirstIndex
{
if (self.length <= 1) {
return [[NSIndexPath alloc] init];
}
NSUInteger indexes[self.length];
[self getIndexes:indexes];
NSUInteger newIndexes[self.length-1];
for (NSUInteger i = 0; i < self.length-1; i++) {
newIndexes[i] = indexes[i+1];
}
return [[NSIndexPath alloc] initWithIndexes:newIndexes length:self.length-1];
}
- (NSIndexPath *)indexPathByPreceedingIndex:(NSUInteger)index
{
NSUInteger newIndexes[self.length+1];
newIndexes[0] = index;
for (NSUInteger i = 0; i < self.length; i++) {
newIndexes[i+1] = [self indexAtPosition:i];
}
return [[NSIndexPath alloc] initWithIndexes:newIndexes length:self.length+1];
}
- (NSIndexPath *)indexPathByIncrementingLastIndex
{
if (self.length < 1) {
return [[NSIndexPath alloc] init];
}
NSUInteger lastIndex = [self lastIndex];
lastIndex++;
return [[self indexPathByRemovingLastIndex] indexPathByAddingIndex:lastIndex];
}
- (NSIndexPath *)indexPathByDecrementingLastIndex
{
if (self.length < 1) {
return [[NSIndexPath alloc] init];
}
NSUInteger lastIndex = [self lastIndex];
lastIndex--;
return [[self indexPathByRemovingLastIndex] indexPathByAddingIndex:lastIndex];
}
- (NSIndexPath *)indexPathByRemovingIndexesFrom:(NSUInteger)from
{
NSIndexPath *indexPath = [[NSIndexPath alloc] init];
for (NSUInteger position = 0; position < MIN(from, self.length); position++) {
indexPath = [indexPath indexPathByAddingIndex:[self indexAtPosition:position]];
}
return indexPath;
}
- (NSIndexPath *)indexPathByRemovingIndexesTo:(NSUInteger)to
{
NSIndexPath *indexPath = [[NSIndexPath alloc] init];
for (NSUInteger position = to; position < self.length; position++) {
indexPath = [indexPath indexPathByAddingIndex:[self indexAtPosition:position]];
}
return indexPath;
}
- (NSIndexPath *)commonIndexPathWith:(NSIndexPath *)indexPath
{
NSIndexPath *commonPath = [[NSIndexPath alloc] init];
NSUInteger length = MIN(self.length, indexPath.length);
for (NSUInteger position = 0; position < length; position++) {
NSUInteger selfIndex = [self indexAtPosition:position];
NSUInteger otherIndex = [indexPath indexAtPosition:position];
if (selfIndex == otherIndex) {
commonPath = [commonPath indexPathByAddingIndex:selfIndex];
} else {
break;
}
}
return commonPath;
}
@end

View File

@@ -1,107 +0,0 @@
//
// NSRegularExpression+MPParsingAdditions.h
// MathPad
//
// Created by Kim Wittenburg on 09.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
@interface NSRegularExpression (MPParsingAdditions)
/*!
@method firstMathInString:
@brief Returns the first match of the regular expression within the
specified string.
@discussion This is a convenience method that calls @c
-firstMatchInString:options:range:
@param string
The string to search.
@return An NSTextCheckingResult object. This result gives the overall
matched range via its range property, and the range of each
individual capture group via its rangeAtIndex: method. The range
@c {NSNotFound, 0} is returned if one of the capture groups did
not participate in this particular match.
*/
- (NSTextCheckingResult *)firstMatchInString:(NSString *)string;
/*!
@method firstMathInString:fromIndex
@brief Returns the first match of the regular expression from the
specified index in the string.
@discussion This is a convenience method that calls @c
-firstMatchInString:options:range:
@param string
The string to search.
@param start
The index from which to start searching.
@return An NSTextCheckingResult object. This result gives the overall
matched range via its range property, and the range of each
individual capture group via its rangeAtIndex: method. The range
@c {NSNotFound, 0} is returned if one of the capture groups did
not participate in this particular match.
*/
- (NSTextCheckingResult *)firstMatchInString:(NSString *)string fromIndex:(NSUInteger)start;
/*!
@method firstMathInString:options:
@brief Returns the first match of the regular expression within the
specified string.
@discussion This is a convenience method that calls @c
-firstMatchInString:options:range:
@param string
The string to search.
@param options
The matching options to use. See @c NSMatchingOptions for
possible values.
@return An NSTextCheckingResult object. This result gives the overall
matched range via its range property, and the range of each
individual capture group via its rangeAtIndex: method. The range
@c {NSNotFound, 0} is returned if one of the capture groups did
not participate in this particular match.
*/
- (NSTextCheckingResult *)firstMatchInString:(NSString *)string options:(NSMatchingOptions)options;
/*!
@method firstMathInString:fromIndex
@brief Returns the first match of the regular expression from the
specified index in the string.
@discussion This is a convenience method that calls @c
-firstMatchInString:options:range:
@param string
The string to search.
@param options
The matching options to use. See @c NSMatchingOptions for
possible values.
@param start
The index from which to start searching.
@return An NSTextCheckingResult object. This result gives the overall
matched range via its range property, and the range of each
individual capture group via its rangeAtIndex: method. The range
@c {NSNotFound, 0} is returned if one of the capture groups did
not participate in this particular match.
*/
- (NSTextCheckingResult *)firstMatchInString:(NSString *)string options:(NSMatchingOptions)options fromIndex:(NSUInteger)start;
@end

View File

@@ -1,38 +0,0 @@
//
// NSRegularExpression+MPParsingAdditions.m
// MathPad
//
// Created by Kim Wittenburg on 09.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "NSRegularExpression+MPParsingAdditions.h"
@implementation NSRegularExpression (MPParsingAdditions)
- (NSTextCheckingResult *)firstMatchInString:(NSString *)string
{
return [self firstMatchInString:string options:0 range:NSMakeRange(0, string.length)];
}
- (NSTextCheckingResult *)firstMatchInString:(NSString *)string fromIndex:(NSUInteger)start
{
return [self firstMatchInString:string options:0 range:NSMakeRange(start, [string length]-start)];
}
- (NSTextCheckingResult *)firstMatchInString:(NSString *)string options:(NSMatchingOptions)options
{
return [self firstMatchInString:string options:options range:NSMakeRange(0, string.length)];
}
- (NSTextCheckingResult *)firstMatchInString:(NSString *)string options:(NSMatchingOptions)options fromIndex:(NSUInteger)start
{
return [self firstMatchInString:string options:options range:NSMakeRange(start, [string length]-start)];
}
@end

View File

@@ -1,20 +0,0 @@
//
// NSString+MPExpressionElement.h
// MathPad
//
// Created by Kim Wittenburg on 10.08.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPExpressionElement.h"
/*!
@category NSString (MPExpressionElement)
@brief This category adds @c MPExpressionElement protocol conformance to
the @c NSString class.
*/
@interface NSString (MPExpressionElement) <MPExpressionElement>
@end

View File

@@ -1,26 +0,0 @@
//
// NSString+MPExpressionElement.m
// MathPad
//
// Created by Kim Wittenburg on 10.08.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "NSString+MPExpressionElement.h"
@implementation NSString (MPExpressionElement)
- (BOOL)isString
{
return YES;
}
- (BOOL)isFunction
{
return NO;
}
@end