Archived
1

Model Redesign: Added Reference Frames

Added Inverse Functions
UI Redesign
Cleaned Code
This commit is contained in:
Kim Wittenburg
2014-10-07 20:25:54 +02:00
parent 8f2f773909
commit 82259f87e2
40 changed files with 1124 additions and 998 deletions

View File

@@ -14,6 +14,10 @@
3B52CEDD19BEE63000CEDCFC /* NSRegularExpression+MPParsingAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B52CEDB19BEE63000CEDCFC /* NSRegularExpression+MPParsingAdditions.m */; };
3B591BBB19C58D000061D86B /* MPMathRules.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B591BB919C58D000061D86B /* MPMathRules.h */; };
3B591BBC19C58D000061D86B /* MPMathRules.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B591BBA19C58D000061D86B /* MPMathRules.m */; };
3B5FF73B19DB2FF500C8348A /* MPPowerFunction.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B5FF73919DB2FF500C8348A /* MPPowerFunction.h */; };
3B5FF73C19DB2FF500C8348A /* MPPowerFunction.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B5FF73A19DB2FF500C8348A /* MPPowerFunction.m */; };
3B69B66C19DB41B90028E608 /* MPPowerFunctionLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B69B66A19DB41B90028E608 /* MPPowerFunctionLayout.h */; };
3B69B66D19DB41B90028E608 /* MPPowerFunctionLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B69B66B19DB41B90028E608 /* MPPowerFunctionLayout.m */; };
3B7172EA19C7147000FEAA5B /* FunctionsButtonDisclosure@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3B7172E819C7147000FEAA5B /* FunctionsButtonDisclosure@2x.png */; };
3B7172EB19C7147000FEAA5B /* FunctionsButtonDisclosure.png in Resources */ = {isa = PBXBuildFile; fileRef = 3B7172E919C7147000FEAA5B /* FunctionsButtonDisclosure.png */; };
3B7172EE19C9FA8E00FEAA5B /* MPParenthesisFunction.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B7172EC19C9FA8E00FEAA5B /* MPParenthesisFunction.h */; };
@@ -143,7 +147,11 @@
3B52CEDB19BEE63000CEDCFC /* NSRegularExpression+MPParsingAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSRegularExpression+MPParsingAdditions.m"; sourceTree = "<group>"; };
3B591BB919C58D000061D86B /* MPMathRules.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMathRules.h; sourceTree = "<group>"; };
3B591BBA19C58D000061D86B /* MPMathRules.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMathRules.m; sourceTree = "<group>"; };
3B5FF73919DB2FF500C8348A /* MPPowerFunction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPowerFunction.h; sourceTree = "<group>"; };
3B5FF73A19DB2FF500C8348A /* MPPowerFunction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPowerFunction.m; sourceTree = "<group>"; };
3B688D9819982DF50006B4AB /* MPLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPLayout.m; sourceTree = "<group>"; };
3B69B66A19DB41B90028E608 /* MPPowerFunctionLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPowerFunctionLayout.h; sourceTree = "<group>"; };
3B69B66B19DB41B90028E608 /* MPPowerFunctionLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPowerFunctionLayout.m; sourceTree = "<group>"; };
3B7172E819C7147000FEAA5B /* FunctionsButtonDisclosure@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "FunctionsButtonDisclosure@2x.png"; sourceTree = "<group>"; };
3B7172E919C7147000FEAA5B /* FunctionsButtonDisclosure.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = FunctionsButtonDisclosure.png; sourceTree = "<group>"; };
3B7172EC19C9FA8E00FEAA5B /* MPParenthesisFunction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPParenthesisFunction.h; sourceTree = "<group>"; };
@@ -370,6 +378,8 @@
3BB09EC81906FD830080A5ED /* MPSumFunction.m */,
3B7172EC19C9FA8E00FEAA5B /* MPParenthesisFunction.h */,
3B7172ED19C9FA8E00FEAA5B /* MPParenthesisFunction.m */,
3B5FF73919DB2FF500C8348A /* MPPowerFunction.h */,
3B5FF73A19DB2FF500C8348A /* MPPowerFunction.m */,
);
name = Functions;
sourceTree = "<group>";
@@ -401,6 +411,8 @@
3BB09EE0190736160080A5ED /* MPSumFunctionLayout.m */,
3B7172F019C9FC6700FEAA5B /* MPParenthesisFunctionLayout.h */,
3B7172F119C9FC6700FEAA5B /* MPParenthesisFunctionLayout.m */,
3B69B66A19DB41B90028E608 /* MPPowerFunctionLayout.h */,
3B69B66B19DB41B90028E608 /* MPPowerFunctionLayout.m */,
);
name = "Function Layouts";
sourceTree = "<group>";
@@ -571,10 +583,12 @@
3B7172F219C9FC6700FEAA5B /* MPParenthesisFunctionLayout.h in Headers */,
3B52CEDC19BEE63000CEDCFC /* NSRegularExpression+MPParsingAdditions.h in Headers */,
3B85833A19BB63D400D76A8D /* MPFunction.h in Headers */,
3B69B66C19DB41B90028E608 /* MPPowerFunctionLayout.h in Headers */,
3B85834319BB653700D76A8D /* MPSumFunction.h in Headers */,
3B85834119BB651E00D76A8D /* MPRangePath.h in Headers */,
3B85834219BB652900D76A8D /* MPException.h in Headers */,
3B85834519BB655200D76A8D /* NSIndexPath+MPAdditions.h in Headers */,
3B5FF73B19DB2FF500C8348A /* MPPowerFunction.h in Headers */,
3B52CED019BE509C00CEDCFC /* MPParseError.h in Headers */,
3B7172EE19C9FA8E00FEAA5B /* MPParenthesisFunction.h in Headers */,
3B85834619BB655C00D76A8D /* MPExpressionView.h in Headers */,
@@ -758,6 +772,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3B5FF73C19DB2FF500C8348A /* MPPowerFunction.m in Sources */,
3BBEA94219BB79A700133766 /* MPExpressionLayout.m in Sources */,
3B7B3A1919CC44E4005849E5 /* MPExpressionTokenizer.m in Sources */,
3B591BBC19C58D000061D86B /* MPMathRules.m in Sources */,
@@ -781,6 +796,7 @@
3BF59AFF19D80ECC00E54292 /* MPFunctionsViewController.m in Sources */,
3B52CED119BE509C00CEDCFC /* MPParseError.m in Sources */,
3BB18AA619CDB3A900986DA0 /* MPTokenStream.m in Sources */,
3B69B66D19DB41B90028E608 /* MPPowerFunctionLayout.m in Sources */,
3BBEA93619BB79A700133766 /* MPFunction.m in Sources */,
3BBEA93519BB79A700133766 /* MPExpression.m in Sources */,
3BBEA93B19BB79A700133766 /* MPRangePath.m in Sources */,

View File

@@ -6,9 +6,8 @@
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MPDocument">
<connections>
<outlet property="errorLabel" destination="fw3-bj-cPR" id="vaC-tD-zlq"/>
<outlet property="expressionView" destination="lcd-Ip-jjR" id="Vww-eh-hP7"/>
<outlet property="resultLabel" destination="B5H-rE-1e9" id="Z5D-Co-tV1"/>
<outlet property="resultLabel" destination="Cdb-3b-4iC" id="7O2-I6-gqP"/>
<outlet property="window" destination="xOd-HO-29H" id="JIz-fz-R2o"/>
</connections>
</customObject>
@@ -16,75 +15,41 @@
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="MathPad" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="xOd-HO-29H" userLabel="Window">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<rect key="contentRect" x="525" y="411" width="507" height="249"/>
<rect key="contentRect" x="525" y="411" width="432" height="181"/>
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1178"/>
<view key="contentView" id="gIp-Ho-8D9">
<rect key="frame" x="0.0" y="0.0" width="507" height="249"/>
<rect key="frame" x="0.0" y="0.0" width="432" height="181"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="lcd-Ip-jjR" customClass="MPExpressionView">
<rect key="frame" x="20" y="70" width="467" height="159"/>
<rect key="frame" x="0.0" y="0.0" width="432" height="181"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Cdb-3b-4iC">
<rect key="frame" x="410" y="20" width="4" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" id="I9X-Yv-EiR">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="Cdb-3b-4iC" secondAttribute="bottom" constant="20" id="3T9-HB-OrZ"/>
<constraint firstAttribute="trailing" secondItem="Cdb-3b-4iC" secondAttribute="trailing" constant="20" id="toz-uq-kuq"/>
</constraints>
</customView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ar2-1O-Kl1">
<rect key="frame" x="18" y="45" width="47" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="43" id="EI3-gZ-BdS"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Result:" id="lYc-e4-5j0">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jQo-M8-to6">
<rect key="frame" x="18" y="20" width="40" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="36" id="v3s-bP-5SY"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Error:" id="0q9-PK-glz">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="B5H-rE-1e9">
<rect key="frame" x="71" y="45" width="418" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" id="KBm-kx-spX">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fw3-bj-cPR">
<rect key="frame" x="75" y="20" width="414" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" id="uaX-CN-Uoz">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="lcd-Ip-jjR" firstAttribute="top" secondItem="gIp-Ho-8D9" secondAttribute="top" constant="20" symbolic="YES" id="3g8-dx-OX8"/>
<constraint firstItem="B5H-rE-1e9" firstAttribute="trailing" secondItem="fw3-bj-cPR" secondAttribute="trailing" id="61b-ou-IhC"/>
<constraint firstItem="fw3-bj-cPR" firstAttribute="baseline" secondItem="jQo-M8-to6" secondAttribute="baseline" id="85V-7u-zhv"/>
<constraint firstItem="fw3-bj-cPR" firstAttribute="leading" secondItem="jQo-M8-to6" secondAttribute="trailing" constant="21" id="C53-c9-INb"/>
<constraint firstItem="ar2-1O-Kl1" firstAttribute="baseline" secondItem="B5H-rE-1e9" secondAttribute="baseline" id="K1d-om-K23"/>
<constraint firstAttribute="bottom" secondItem="jQo-M8-to6" secondAttribute="bottom" constant="20" symbolic="YES" id="Oet-xR-WPz"/>
<constraint firstItem="jQo-M8-to6" firstAttribute="leading" secondItem="ar2-1O-Kl1" secondAttribute="leading" id="UyW-dW-kNf"/>
<constraint firstItem="B5H-rE-1e9" firstAttribute="trailing" secondItem="lcd-Ip-jjR" secondAttribute="trailing" id="VN8-ni-28x"/>
<constraint firstItem="ar2-1O-Kl1" firstAttribute="leading" secondItem="lcd-Ip-jjR" secondAttribute="leading" id="X5m-hl-tOG"/>
<constraint firstItem="jQo-M8-to6" firstAttribute="top" secondItem="ar2-1O-Kl1" secondAttribute="bottom" constant="8" symbolic="YES" id="Zv5-DC-xhH"/>
<constraint firstItem="B5H-rE-1e9" firstAttribute="leading" secondItem="ar2-1O-Kl1" secondAttribute="trailing" constant="10" id="a1q-H8-Mfg"/>
<constraint firstItem="ar2-1O-Kl1" firstAttribute="top" secondItem="lcd-Ip-jjR" secondAttribute="bottom" constant="8" symbolic="YES" id="bDG-CZ-mMi"/>
<constraint firstItem="lcd-Ip-jjR" firstAttribute="leading" secondItem="gIp-Ho-8D9" secondAttribute="leading" constant="20" symbolic="YES" id="kTW-r9-ulq"/>
<constraint firstAttribute="trailing" secondItem="lcd-Ip-jjR" secondAttribute="trailing" constant="20" symbolic="YES" id="kl8-mR-t1l"/>
<constraint firstItem="lcd-Ip-jjR" firstAttribute="leading" secondItem="gIp-Ho-8D9" secondAttribute="leading" id="5G8-rw-mds"/>
<constraint firstAttribute="bottom" secondItem="lcd-Ip-jjR" secondAttribute="bottom" id="JAt-GL-atf"/>
<constraint firstAttribute="trailing" secondItem="lcd-Ip-jjR" secondAttribute="trailing" id="Wi2-6P-2Li"/>
<constraint firstItem="lcd-Ip-jjR" firstAttribute="top" secondItem="gIp-Ho-8D9" secondAttribute="top" id="hvP-0r-1OZ"/>
</constraints>
</view>
<connections>
<outlet property="delegate" destination="-2" id="0bl-1N-x8E"/>
</connections>
<point key="canvasLocation" x="139.5" y="146.5"/>
<point key="canvasLocation" x="102" y="112.5"/>
</window>
<collectionViewItem id="J9S-PW-LCL">
<connections>

View File

@@ -12,7 +12,6 @@
@property (weak) IBOutlet MPExpressionView *expressionView;
@property (weak) IBOutlet NSTextField *resultLabel;
@property (weak) IBOutlet NSTextField *errorLabel;
- (IBAction)evaluateExpression:(id)sender;

View File

@@ -62,11 +62,8 @@
- (IBAction)evaluateExpression:(id)sender {
MPParseError *error;
NSDecimalNumber *result = [self.expressionView.expressionStorage evaluateWithError:&error];
self.resultLabel.stringValue = result != nil ? result.description : @"Error!";
self.errorLabel.stringValue = error != nil ? [NSString stringWithFormat:@"%@, %@", error, error.pathToExpression] : @"No Error";
if (error) {
self.expressionView.error = error;
}
self.expressionView.error = error;
self.resultLabel.stringValue = result != nil ? [result descriptionWithLocale:[NSLocale currentLocale]] : @"";
}
@end

View File

@@ -15,7 +15,7 @@
- (void)push;
- (void)pop;
- (void)defineVariable:(NSString *)variable withValue:(id)value;
- (void)defineVariable:(NSString *)variable withValue:(NSDecimalNumber *)value;
- (void)undefineVariable:(NSString *)variable;
- (BOOL)isVariableDefined:(NSString *)variable;

View File

@@ -31,6 +31,9 @@ static MPEvaluationContext *sharedContext;
self = [super init];
if (self) {
_stack = [[NSMutableArray alloc] init];
[self push];
[self defineVariable:@"e" withValue:[[NSDecimalNumber alloc] initWithDouble:M_E]];
[self defineVariable:@"π" withValue:[[NSDecimalNumber alloc] initWithDouble:M_PI]];
}
return self;
}
@@ -45,7 +48,7 @@ static MPEvaluationContext *sharedContext;
[self.stack removeLastObject];
}
- (void)defineVariable:(NSString *)variable withValue:(id)value
- (void)defineVariable:(NSString *)variable withValue:(NSDecimalNumber *)value
{
NSMutableDictionary *currentBindings = self.stack.lastObject;
currentBindings[variable] = value;
@@ -59,8 +62,14 @@ static MPEvaluationContext *sharedContext;
- (NSDecimalNumber *)valueForVariable:(NSString *)variable
{
NSMutableDictionary *currentBindings = self.stack.lastObject;
return currentBindings[variable];
NSUInteger currentIndex = self.stack.count;
NSDictionary *currentBindings;
NSDecimalNumber *value = nil;
while (!value && currentIndex > 0) {
currentBindings = self.stack[--currentIndex];
value = currentBindings[variable];
}
return value;
}
- (BOOL)isVariableDefined:(NSString *)variable

View File

@@ -8,6 +8,13 @@
@import Foundation;
#import "NSString+MPExpressionElement.h"
#import "MPToken.h"
typedef NS_ENUM(NSUInteger, MPReferenceFrame) {
MPElementReferenceFrame,
MPSymbolReferenceFrame,
MPTokenReferenceFrame
};
@class MPExpression, MPFunction, MPRangePath, MPExpressionEvaluator, MPParseError;
@protocol MPExpressionElement;
@@ -127,6 +134,34 @@
@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 indexed 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 numberOfElements
@brief Returns the number of elements in the receiver.
@@ -139,7 +174,7 @@
@return The current number of elements in the receiver.
*/
- (NSUInteger)numberOfElements;
- (NSUInteger)countItemsInReferenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@@ -160,7 +195,23 @@
@return The element at @c anIndex.
*/
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)anIndex;
- (id)itemAtIndex:(NSUInteger)anIndex
referenceFrame:(MPReferenceFrame)referenceFrame;
- (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 indexed reference frame.
*/
- (NSUInteger)indexOfElement:(id<MPExpressionElement>)element;
/*!
@@ -185,7 +236,8 @@
The length of the returned array is equal to the length of the
specified range.
*/
- (NSArray *)elementsInIndexedRange:(NSRange)range;
- (NSArray *)itemsInRange:(NSRange)range
referenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@@ -197,7 +249,51 @@
@return An array of all elements from the receiver.
*/
- (NSArray *)elements;
- (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 indexed 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;
- (NSUInteger)convertIndex:(NSUInteger)index
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
toReferenceFrame:(MPReferenceFrame)toReferenceFrame;
- (NSUInteger)convertIndex:(NSUInteger)index
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
offset:(NSUInteger *)offset;
- (NSRange)convertRange:(NSRange)aRange
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
toReferenceFrame:(MPReferenceFrame)toReferenceFrame;
- (NSRange)convertRange:(NSRange)aRange
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
leadingOffset:(NSUInteger *)leadingOffset
trailingOffset:(NSUInteger *)trailingOffset;
#pragma mark Mutating Expressions
@@ -228,9 +324,16 @@
The elements that should replace the symbols specified by @c
range.
*/
- (void)replaceSymbolsInRange:(NSRange)range
withElements:(NSArray *)elements;
- (void)replaceItemsInRange:(NSRange)range
referenceFrame:(MPReferenceFrame)referenceFrame
withElements:(NSArray *)elements;
- (MPExpression *)subexpressionFromIndex:(NSUInteger)from
referenceFrame:(MPReferenceFrame)referenceFrame;
- (MPExpression *)subexpressionToIndex:(NSUInteger)to
referenceFrame:(MPReferenceFrame)referenceFrame;
- (MPExpression *)subexpressionWithRange:(NSRange)range
referenceFrame:(MPReferenceFrame)referenceFrame;
#pragma mark Evaluating Expressions
@@ -256,17 +359,6 @@
- (NSDecimalNumber *)evaluateWithError:(MPParseError *__autoreleasing *)error;
/*!
@property evaluator
@brief Returns an object that can evaluate the receiver.
@discussion To just evaluate an expression it is recommended to send it an
@c evaluateWithError: message. You can however use this property
instead if you need more control over the evaluation process.
*/
@property (readonly, nonatomic, strong) MPExpressionEvaluator *evaluator;
#pragma mark Notifications
// All notification methods should create a new rangePath with the receiver's index added to the beginning of the path and then ascend the message to it's parent
@@ -291,8 +383,8 @@
The number of elements replacing the elements specified by @c
rangePath.
*/
- (void)didChangeElementsInIndexedRangePath:(MPRangePath *)rangePath
replacementLength:(NSUInteger)replacementLength;
- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath
replacementLength:(NSUInteger)replacementLength;
#pragma mark Basic NSObject Methods
@@ -301,412 +393,63 @@
@end
/* --------------------------------------------------------------------------- */
/* Extension Methods */
/* --------------------------------------------------------------------------- */
@interface MPExpression (MPExpressionExtension)
@interface MPExpression (MPExpressionConvenience)
#pragma mark Querying Expressions
- (NSUInteger)countElements;
- (NSUInteger)countSymbols;
- (NSUInteger)countTokens;
/*!
@method length
@brief Returns the length of the receiver.
@discussion The length of an expression is calculated by going over each
element in the receiver and sending it a @c -length message. This
method should be used to determine the number of digits or
symbols in an expression.
The result of this method is expressed in the located reference
frame. The respective method for the indexed reference frame is
@c -numberOfSymbols.
@return The length of the receiver. This is the number of symbols in all
elements in the receiver where a function element is counted as a
single symbol.
*/
- (NSUInteger)length;
/*!
@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 indexed 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;
// Subscripting is supported in the indexed reference frame
- (id)objectAtIndexedSubscript:(NSUInteger)idx;
#pragma mark Working With Expressions
/*!
@method elementAtLocation:
@brief Returns the element that is located at @c location.
@discussion This method finds an element in the located reference frame. If
@c location is greater or equal to the @c length of the receiver
a @c NSRangeException is raised.
@param location
The location of the element to find expressed in the located
reference frame.
@return The element located at @c location.
*/
- (id<MPExpressionElement>)elementAtLocation:(NSUInteger)location;
/*!
@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 indexed 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 elementsInIndexedRangePath:
@brief Returns the elements in the specified range path.
@discussion This method works similar to @c elementAtIndexPath: except that
it queries multiple elements at once.
@param rangePath
The range path the requested objects are located at. The complete
range path is expressed in the indexed reference frame.
@return An array of objects specified by the range path. The returned
elements are not copied before they are returned. Be aware that
any mutations made to the returned objects are reflected in the
receiver.
*/
- (NSArray *)elementsInIndexedRangePath:(MPRangePath *)rangePath;
/*!
@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 indexed reference frame.
*/
- (NSUInteger)indexOfElement:(id<MPExpressionElement>)element;
#pragma mark Converting Between Indexes and Locations
/*!
@method indexOfElementAtLocation:offset:
@brief Calculates the index of the element the specified location points
to.
@discussion The @c location is in the located reference frame whereas the
returned value is an index. This method converts from the former
to the latter.
This method prefers higher indexes. This means that if the
returned @c offset would be equal to the length of the element at
the calculated index, insead index+1 is returned and the @c
offset is set to @c 0.
If the @c location exceeds the receiver's bounds a @c
NSRangeException will be raised.
@param location
The location of which you want the corresponding element index.
@param offset
An output parameter that gets set to the offst into the symbol
whose index is returned. If location for example points to the
symbol @c '2' in the string element @c '123' the offset @c would
be set to @c 1.
@return The index of the element the location points to.
*/
- (NSUInteger)indexOfElementAtLocation:(NSUInteger)location offset:(out NSUInteger *)offset;
/*!
@method locationOfElementAtIndex:
@brief Calculates the location of the element at @c index.
@discussion @c index is expressed in the indexed reference frame. Use this
method to convert an index into the located reference frame.
If the index exceeds the receiver's number of elements a @c
NSRangeException will be raised.
@param index
The index of the element that is to be converted into the length
reference frame.
@return The number of symbols (in the length reference frame) before the
element at @c index.
*/
- (NSUInteger)locationOfElementAtIndex:(NSUInteger)index;
/*!
@method indexedRangeForRange:
@brief Converts @c aRange from the located reference frame into the
indexed reference frame.
@discussion If the range exceeds the receiver's bounds a @c NSRangeException
is raised.
@param aRange
The range to be converted. Expressed in the located reference
frame.
@return @c aRange converted into the indexed reference frame.
*/
- (NSRange)indexedRangeForRange:(NSRange)aRange;
/*!
@method rangeForIndexedRange:
@brief Converts @c aRange from the indexed reference frame into the
located reference frame.
@discussion In the range exceeds the receiver's bounds a @c NSRangeException
is raised.
@param aRange
The range to be converted. Expressed in the indexed reference
frame.
@return @c aRange converted into the located reference frame.
*/
- (NSRange)rangeForIndexedRange:(NSRange)aRange;
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)index;
- (id<MPExpressionElement>)symbolAtIndex:(NSUInteger)index;
- (id<MPToken>)tokenAtIndex:(NSUInteger)index;
#pragma mark Mutating Expressions
- (MPExpression *)subexpressionFromIndex:(NSUInteger)from;
- (MPExpression *)subexpressionToIndex:(NSUInteger)to;
- (MPExpression *)subexpressionWithIndexedRange:(NSRange)range;
/*!
@method subexpressionFromLocation:
@brief Creates a new expression from the specified location (inclusive)
to the end of the receiver.
@method appendElement:
@brief Appends @c anElement to the receiver.
@discussion The elements in the newly created expression are copied to the
new expression. The location is specified in the located
reference frame.
If the given location exceeds the receiver's bounds a @c
NSRangeException is raised.
@param from
The first location to be included in the new expression.
@return A new expression from the given location to the end of the
receiver.
*/
- (MPExpression *)subexpressionFromLocation:(NSUInteger)from;
/*!
@method subexpressionToLocation:
@brief Creates a new expression from the beginning to the specified
location (exclusive).
@discussion The elements in the newly created expression are copied to the
new expression. The location is specified in the located
reference frame.
If the given location exceeds the receiver's bounds a @c
NSRangeException is raised.
@param to
The first location not to be included in the new expression (or
the length of the new expression).
@return A new expression with the first @c to symbols of the receiver.
*/
- (MPExpression *)subexpressionToLocation:(NSUInteger)to;
/*!
@method subexpressionWithRange:
@brief Creates a new expression with the symbols in the specified range.
@discussion The elements in the newly created expression are copied to the
new exoression. The range is specified in the located reference
frame.
If the given range exceeds the receiver's bounds a @c
NSRangeException is raised.
@param range
The range from which to create the new expression.
@return A new expression with the symbols in the specified range.
*/
- (MPExpression *)subexpressionWithRange:(NSRange)range;
- (void)replaceElementsInIndexedRange:(NSRange)range
withElements:(NSArray *)elements;
- (void)replaceSymbolsInRangePath:(MPRangePath *)rangePath
withElements:(NSArray *)elements;
/*!
@method appendElement:
@brief Appends @c anElement to the receiver.
@param anElement
The element to append 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.
@method appendElements:
@brief Appends the objects from @c elements to the receiver.
@param elements
The elements to append to the receiver.
@param elements
The elements to append to the receiver.
*/
- (void)appendElements:(NSArray *)elements;
- (void)insertElement:(id<MPExpressionElement>)anElement
atIndex:(NSUInteger)index;
atIndex:(NSUInteger)index
referenceFrame:(MPReferenceFrame)referenceFrame;
- (void)insertElements:(NSArray *)elements
atIndex:(NSUInteger)index;
/*!
@method insertElement:atLocation:
@brief Inserts @c anElement at @c location.
@discussion The location is specified in the length reference frame.
If the given location exceeds the receiver's bounds a @c
NSRangeException is raised.
@param anElement
The element to be inserted into the receiver.
@param location
The location @c anElement should be inserted at.
*/
- (void)insertElement:(id<MPExpressionElement>)anElement
atLocation:(NSUInteger)location;
atIndex:(NSUInteger)index
referenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@method insertElements:atLocation:
@brief Inserts the elements from @c elements at @c location.
@method deleteElementsInRange:
@brief Removes the elements specified by @c range from the receiver.
@discussion The location is specified in the length reference frame.
@discussion The range is specified in the length reference frame.
If the given location exceeds the receiver's bounds a @c
NSRangeException is raised.
If @c range exceeds the receiver's bounds a @c NSRangeException
is raised.
@param elements
The elements to be inserted into the receiver.
@param location
The location the elements in @c elements should be inserted into
the receiver.
@param range
The range to remove from the receiver.
*/
- (void)insertElements:(NSArray *)elements
atLocation:(NSUInteger)location;
- (void)deleteElementsInIndexedRange:(NSRange)range;
/*!
@method deleteElementsInRange:
@brief Removes the elements specified by @c range from the receiver.
@discussion The range is specified in the length reference frame.
If @c range exceeds the receiver's bounds a @c NSRangeException
is raised.
@param range
The range to remove from the receiver.
*/
- (void)deleteElementsInRange:(NSRange)range;
/*!
@method mutableElements
@brief Returns a proxy mutable array object that responds to all methods
defined by @c NSMutableArray.
@discussion Mutations on the proxy object also change the receiver. The proxy
object does not respond to coding methods. Copying the proxy
object will not duplicate it.
@return A proxy object that responds to all methods defined by @c
NSMutableArray.
*/
// - (NSMutableArray *)mutableElements;
/* Subscripting is supported for elements in the indexed reference frame */
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx;
- (void)deleteElementsInRange:(NSRange)range
referenceFrame:(MPReferenceFrame)referenceFrame;
@end

View File

@@ -13,84 +13,32 @@
#import "NSIndexPath+MPAdditions.h"
#import "MPException.h"
#import "MPExpressionTokenizer.h"
#import "MPExpressionEvaluator.h"
#import "MPToken.h"
@interface MPExpression () {
NSMutableArray *__strong _elements;
NSMutableArray * _elements;
}
- (void)fixElements;
@end
@interface MPExpression (MPExpressionPrivate)
- (NSArray *)tokens;
- (void)validateElements:(NSArray *)elements;
- (void)fixElements;
- (NSUInteger)lengthOfElements:(NSArray *)elements;
- (BOOL)splitElementsAtLocation:(NSUInteger)location
insertionIndex:(out NSUInteger *)insertionIndex;
@end
@implementation MPExpression (MPExpressionPrivate)
- (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}];
}
}
}
- (NSUInteger)lengthOfElements:(NSArray *)elements
{
NSUInteger length = 0;
for (id<MPExpressionElement> element in elements) {
length += element.length;
}
return length;
}
- (BOOL)splitElementsAtLocation:(NSUInteger)location
insertionIndex:(out NSUInteger *)insertionIndex
{
if (location == 0) {
*insertionIndex = 0;
return NO;
}
NSUInteger splitOffset;
NSUInteger splitElementIndex = [self indexOfElementAtLocation:location
offset:&splitOffset];
if (splitOffset != 0) {
NSString *splitElement = (NSString *)self.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)_replaceSymbolsInRange:(NSRange)range
withElements:(NSArray *)elements;
- (BOOL)_splitElementsAtLocation:(NSUInteger)location
insertionIndex:(out NSUInteger *)insertionIndex;
@end
@implementation MPExpression {
NSUInteger _cachedLength;
NSArray *_tokenCache;
NSRange _editedRange;
BOOL _didSplitStartOnEditing;
@@ -117,7 +65,6 @@
self = [super init];
if (self) {
[self validateElements:elements];
_cachedLength = 0;
_elements = [[NSMutableArray alloc] initWithArray:elements
copyItems:YES];
[self fixElements];
@@ -129,11 +76,32 @@
#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 < self.elements.count; index++) {
id<MPExpressionElement> next = index+1 < self.elements.count ? self.elements[index+1] : nil;
id<MPExpressionElement> current = self.elements[index];
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];
@@ -166,38 +134,283 @@
}
}
#pragma mark Querying Expressions
- (NSUInteger)numberOfElements
- (MPExpression *)rootExpression
{
return self.elements.count;
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 location = 0;
NSUInteger elementIndex = 0;
id <MPExpressionElement> element = nil;
while (location < anIndex) {
element = _elements[elementIndex++];
location += element.length;
}
if (location == anIndex && element.isFunction) {
return element;
}
NSUInteger indexInString = location - element.length + anIndex;
return [((NSString *)element) substringWithRange:NSMakeRange(indexInString, 1)];
}
case MPTokenReferenceFrame:
return self.tokens[anIndex];
}
}
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)anIndex
referenceFrame:(MPReferenceFrame)referenceFrame
{
return self.elements[anIndex];
NSUInteger elementIndex = [self convertIndex:anIndex
fromReferenceFrame:referenceFrame
toReferenceFrame:MPElementReferenceFrame];
return _elements[elementIndex];
}
- (NSArray *)elementsInIndexedRange:(NSRange)range
#warning If multiple equal expressions exist errors may occur...
- (NSUInteger)indexOfElement:(id<MPExpressionElement>)element
{
return [self.elements subarrayWithRange:range];
return [_elements indexOfObject:element];
}
- (NSArray *)elements
- (NSArray *)itemsInRange:(NSRange)range
referenceFrame:(MPReferenceFrame)referenceFrame
{
return _elements;
MPExpression *subexpression = [self subexpressionWithRange:range
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)index
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
{
return [self convertIndex:index
fromReferenceFrame:fromReferenceFrame
toReferenceFrame:toReferenceFrame
offset:NULL];
}
- (NSUInteger)convertIndex:(NSUInteger)index
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
offset:(NSUInteger *)offset
{
if (fromReferenceFrame == toReferenceFrame || index == 0) {
if (offset) {
*offset = 0;
}
return index;
}
NSUInteger symbolIndex __block = 0;
switch (fromReferenceFrame) {
case MPElementReferenceFrame:
[_elements enumerateObjectsUsingBlock:^(id<MPExpressionElement> obj, NSUInteger idx, BOOL *stop) {
symbolIndex += obj.length;
*stop = idx >= index - 1;
}];
break;
case MPSymbolReferenceFrame:
symbolIndex = index;
break;
case MPTokenReferenceFrame:
[_elements enumerateObjectsUsingBlock:^(id<MPToken> obj, NSUInteger idx, BOOL *stop) {
symbolIndex = NSMaxRange(obj.range);
*stop = idx >= index - 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 = NSMaxRange(token.range);
}
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)replaceSymbolsInRange:(NSRange)range
- (void)replaceItemsInRange:(NSRange)range
referenceFrame:(MPReferenceFrame)referenceFrame
withElements:(NSArray *)elements
{
NSUInteger start = [self convertIndex:range.location
fromReferenceFrame:referenceFrame
toReferenceFrame:MPSymbolReferenceFrame];
NSUInteger end = [self convertIndex:NSMaxRange(range)
fromReferenceFrame:referenceFrame
toReferenceFrame:MPSymbolReferenceFrame];
[self _replaceSymbolsInRange:NSMakeRange(start, end - start)
withElements:elements];
}
- (void)_replaceSymbolsInRange:(NSRange)range
withElements:(NSArray *)elements
{
if (NSMaxRange(range) > self.length) {
if (NSMaxRange(range) > [self countItemsInReferenceFrame:MPSymbolReferenceFrame]) {
@throw [NSException exceptionWithName:NSRangeException
reason:@"Range out of bounds of expression"
userInfo:nil];
@@ -207,15 +420,15 @@
// Locate the position, split the elements
NSUInteger startIndex; // startIndex is inclusive
BOOL didSplitStart = NO;
if ([self numberOfElements] == 0) {
if (_elements.count == 0) {
startIndex = 0;
} else {
didSplitStart = [self splitElementsAtLocation:range.location
insertionIndex:&startIndex];
didSplitStart = [self _splitElementsAtLocation:range.location
insertionIndex:&startIndex];
}
NSUInteger endIndex; // endIndex is exclusive
BOOL didSplitEnd = [self splitElementsAtLocation:NSMaxRange(range)
insertionIndex:&endIndex];
BOOL didSplitEnd = [self _splitElementsAtLocation:NSMaxRange(range)
insertionIndex:&endIndex];
// Perform the replacement
NSMutableArray *newElements = [[NSMutableArray alloc] initWithArray:elements
@@ -223,7 +436,7 @@
[_elements replaceObjectsInRange:NSMakeRange(startIndex, endIndex-startIndex)
withObjectsFromArray:newElements];
_cachedLength = 0;
_tokenCache = nil;
NSUInteger editLocation = startIndex - (didSplitStart ? 1 : 0);
NSUInteger editLength = endIndex - startIndex;
@@ -246,37 +459,78 @@
}
[self fixElements];
[self.evaluator expressionDidChangeInRange:_editedRange
replacementLength:_replacementLength];
[self didChangeElementsInIndexedRangePath:[[MPRangePath alloc] initWithRange:_editedRange]
[self didChangeElementsInRangePath:[[MPRangePath alloc] initWithRange:_editedRange]
replacementLength:_replacementLength];
}
- (BOOL)_splitElementsAtLocation:(NSUInteger)location
insertionIndex:(out 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;
}
- (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)range
referenceFrame:(MPReferenceFrame)referenceFrame
{
MPExpression *subexpression = [self copy];
NSRange preceedingRange = NSMakeRange(0, range.location);
NSUInteger firstOut = NSMaxRange(range);
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 *)evaluateWithError:(MPParseError *__autoreleasing *)error
{
MPTerm *term = [self.evaluator parseExpectingVariable:NO
error:error];
MPExpressionEvaluator *evaluator = [[MPExpressionEvaluator alloc] initWithExpression:self];
MPTerm *term = [evaluator parseExpectingVariable:NO
error:error];
return [term evaluate];
}
@synthesize evaluator = _evaluator;
- (MPExpressionEvaluator *)evaluator
{
if (!_evaluator) {
_evaluator = [[MPExpressionEvaluator alloc] initWithExpression:self];
}
return _evaluator;
}
#pragma mark Notifications
- (void)didChangeElementsInIndexedRangePath:(MPRangePath *)rangePath
- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath
replacementLength:(NSUInteger)replacementLength
{
NSUInteger selfIndex = [self.parent indexOfChild:self];
@@ -315,11 +569,11 @@
#warning Bad Implementation
NSMutableString *description = [[NSMutableString alloc] init];
NSUInteger index = 0;
for (id element in self.elements) {
for (id element in _elements) {
if ([element isString]) {
NSMutableString *correctedSymbol = [[element stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] mutableCopy];
// Prefix operator
if (element != self.elements[0]) {
if (element != _elements[0]) {
unichar prefix = [correctedSymbol characterAtIndex:0];
if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:prefix]) {
[correctedSymbol insertString:@"*"
@@ -327,14 +581,14 @@
}
}
// Suffix operator
if (element != [self.elements lastObject]) {
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 && [self.elements[index-1] isKindOfClass:[MPFunction class]]) {
} else if (index > 0 && [_elements[index-1] isKindOfClass:[MPFunction class]]) {
[description appendFormat:@"*%@", [element description]];
} else {
[description appendString:[element description]];
@@ -346,7 +600,7 @@
- (NSUInteger)hash
{
return [self.elements hash];
return [_elements hash];
}
@@ -355,7 +609,7 @@
- (id)copyWithZone:(NSZone *)zone
{
MPExpression *copy = [[MPExpression allocWithZone:zone] initWithElements:self.elements];
MPExpression *copy = [[MPExpression allocWithZone:zone] initWithElements:_elements];
return copy;
}
@@ -371,211 +625,52 @@
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.elements];
[aCoder encodeObject:_elements];
}
@end
@implementation MPExpression (MPExpressionExtension)
@implementation MPExpression (MPExpressionConvenience)
#pragma mark Querying Expressions
- (NSUInteger)length
- (NSUInteger)countElements
{
if (_cachedLength == 0) {
_cachedLength = [self lengthOfElements:self.elements];
}
return _cachedLength;
return [self countItemsInReferenceFrame:MPElementReferenceFrame];
}
- (MPExpression *)rootExpression
- (NSUInteger)countSymbols
{
if (self.parent == nil) {
return self;
}
return [self.parent rootExpression];
return [self countItemsInReferenceFrame:MPSymbolReferenceFrame];
}
- (NSIndexPath *)indexPath
- (NSUInteger)countTokens
{
if (self.parent) {
NSUInteger selfIndex = [self.parent indexOfChild:self];
return [[self.parent indexPath] indexPathByAddingIndex:selfIndex];
} else {
return [[NSIndexPath alloc] init];
}
return [self countItemsInReferenceFrame:MPTokenReferenceFrame];
}
- (id)objectAtIndexedSubscript:(NSUInteger)idx
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)index
{
return [self elementAtIndex:idx];
return [self itemAtIndex:index
referenceFrame:MPElementReferenceFrame];
}
#pragma mark Working With Expressions
- (id<MPExpressionElement>)elementAtLocation:(NSUInteger)location
- (id<MPExpressionElement>)symbolAtIndex:(NSUInteger)index
{
NSUInteger index = [self indexOfElementAtLocation:location offset:NULL];
return [self elementAtIndex:index];
return [self itemAtIndex:index
referenceFrame:MPSymbolReferenceFrame];
}
- (id)elementAtIndexPath:(NSIndexPath *)indexPath
- (id<MPToken>)tokenAtIndex:(NSUInteger)index
{
if (indexPath.length == 0) {
return self;
}
id<MPExpressionElement> element = [self elementAtIndex:[indexPath indexAtPosition:0]];
if (indexPath.length == 1) {
return element;
}
if ([element isFunction]) {
return [(MPFunction *)element elementAtIndexPath:[indexPath indexPathByRemovingFirstIndex]];
}
return nil;
}
- (NSArray *)elementsInIndexedRangePath:(MPRangePath *)rangePath
{
MPExpression *targetExpression = [self elementAtIndexPath:[rangePath.location indexPathByRemovingLastIndex]];
return [targetExpression elementsInIndexedRange:rangePath.rangeAtLastIndex];
}
#warning If multiple equal expressions exist errors may occur...
- (NSUInteger)indexOfElement:(id<MPExpressionElement>)element
{
return [self.elements indexOfObject:element];
}
#pragma mark Converting Between Indexes and Locations
- (NSUInteger)indexOfElementAtLocation:(NSUInteger)location
offset:(out NSUInteger *)offset
{
if (location == 0) {
if (offset != NULL) {
*offset = 0;
}
return 0;
}
// Calculating elementIndex and splitOffset
NSUInteger totalLength = 0;
NSUInteger elementIndex = 0;
NSUInteger elementLength = 0;
for (id<MPExpressionElement> element in self.elements) {
elementLength = element.length;
totalLength += elementLength;
if (totalLength >= location) {
break;
}
++elementIndex;
}
NSUInteger elementOffset = elementLength - (totalLength - location);
id<MPExpressionElement> element = self.elements[elementIndex];
if (elementOffset == element.length) {
elementOffset = 0;
elementIndex++;
}
if (offset != NULL) {
*offset = elementOffset;
}
return elementIndex;
}
- (NSUInteger)locationOfElementAtIndex:(NSUInteger)index
{
NSUInteger location = 0;
for (NSUInteger i = 0; i < index; i++) {
location += [self elementAtIndex:i].length;
}
return location;
}
- (NSRange)indexedRangeForRange:(NSRange)aRange
{
NSUInteger startLocation = aRange.location;
NSUInteger endLocation = NSMaxRange(aRange);
NSUInteger startIndex = [self indexOfElementAtLocation:startLocation offset:NULL];
NSUInteger endIndex = [self indexOfElementAtLocation:endLocation offset:NULL];
return NSMakeRange(startIndex, endIndex-startIndex);
}
- (NSRange)rangeForIndexedRange:(NSRange)aRange
{
NSUInteger startIndex = aRange.location;
NSUInteger endIndex = NSMaxRange(aRange);
NSUInteger startLocation = [self locationOfElementAtIndex:startIndex];
NSUInteger endLocation = [self locationOfElementAtIndex:endIndex];
return NSMakeRange(startLocation, endLocation-startLocation);
return [self itemAtIndex:index
referenceFrame:MPTokenReferenceFrame];
}
#pragma mark Mutating Expressions
- (MPExpression *)subexpressionFromIndex:(NSUInteger)from
{
NSUInteger fromLocation = [self locationOfElementAtIndex:from];
return [self subexpressionFromLocation:fromLocation];
}
- (MPExpression *)subexpressionToIndex:(NSUInteger)to
{
NSUInteger toLocation = [self locationOfElementAtIndex:to];
return [self subexpressionToLocation:toLocation];
}
- (MPExpression *)subexpressionWithIndexedRange:(NSRange)range
{
NSRange locationRange = [self rangeForIndexedRange:range];
return [self subexpressionWithRange:locationRange];
}
- (MPExpression *)subexpressionFromLocation:(NSUInteger)from
{
return [self subexpressionWithRange:NSMakeRange(from, self.length - from)];
}
- (MPExpression *)subexpressionToLocation:(NSUInteger)to
{
return [self subexpressionWithRange:NSMakeRange(0, to)];
}
- (MPExpression *)subexpressionWithRange:(NSRange)range
{
MPExpression *subexpression = [self copy];
NSRange preceedingRange = NSMakeRange(0, range.location);
NSUInteger firstOut = NSMaxRange(range);
NSRange exceedingRange = NSMakeRange(firstOut, self.length-firstOut);
[subexpression deleteElementsInRange:exceedingRange];
[subexpression deleteElementsInRange:preceedingRange];
return subexpression;
}
- (void)replaceElementsInIndexedRange:(NSRange)range
withElements:(NSArray *)elements
{
[self replaceSymbolsInRange:[self rangeForIndexedRange:range]
withElements:elements];
}
- (void)replaceSymbolsInRangePath:(MPRangePath *)rangePath
withElements:(NSArray *)elements
{
MPExpression *targetExpression = [self elementAtIndexPath:[rangePath.location indexPathByRemovingLastIndex]];
[targetExpression replaceSymbolsInRange:rangePath.rangeAtLastIndex
withElements:elements];
}
- (void)appendElement:(id<MPExpressionElement>)anElement
{
[self appendElements:@[anElement]];
@@ -583,49 +678,35 @@
- (void)appendElements:(NSArray *)elements
{
[self replaceSymbolsInRange:NSMakeRange(self.length, 0) withElements:elements];
[self replaceItemsInRange:NSMakeRange([self countItemsInReferenceFrame:MPSymbolReferenceFrame], 0)
referenceFrame:MPSymbolReferenceFrame
withElements:elements];
}
- (void)insertElement:(id<MPExpressionElement>)anElement
atIndex:(NSUInteger)index
referenceFrame:(MPReferenceFrame)referenceFrame
{
[self insertElement:anElement
atLocation:[self locationOfElementAtIndex:index]];
[self insertElements:@[anElement]
atIndex:index
referenceFrame:referenceFrame];
}
- (void)insertElements:(NSArray *)elements
atIndex:(NSUInteger)index
referenceFrame:(MPReferenceFrame)referenceFrame
{
[self insertElements:elements
atLocation:[self locationOfElementAtIndex:index]];
}
- (void)insertElement:(id<MPExpressionElement>)anElement
atLocation:(NSUInteger)location
{
[self insertElements:@[anElement] atLocation:location];
}
- (void)insertElements:(NSArray *)elements
atLocation:(NSUInteger)location
{
[self replaceSymbolsInRange:NSMakeRange(location, 0) withElements:elements];
}
- (void)deleteElementsInIndexedRange:(NSRange)range
{
[self deleteElementsInIndexedRange:[self rangeForIndexedRange:range]];
[self replaceItemsInRange:NSMakeRange(index, 0)
referenceFrame:referenceFrame
withElements:elements];
}
- (void)deleteElementsInRange:(NSRange)range
referenceFrame:(MPReferenceFrame)referenceFrame
{
[self replaceSymbolsInRange:range withElements:@[]];
}
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx
{
[self replaceSymbolsInRange:NSMakeRange(idx, 1)
withElements:@[obj]];
[self replaceItemsInRange:range
referenceFrame:referenceFrame
withElements:@[]];
}
@end

View File

@@ -15,15 +15,9 @@
@interface MPExpressionEvaluator : NSObject
// Do not instanciate yourself, use evaluator property of MPExpression instead
- (instancetype)initWithExpression:(MPExpression *)expression;
@property (readonly, nonatomic, weak) MPExpression *expression;
@property (nonatomic, strong) MPExpressionTokenizer *lexer;
- (void)expressionDidChangeInRange:(NSRange)range
replacementLength:(NSUInteger)replacementLength;
#pragma mark Evaluating Expressions
@property (readonly, nonatomic, copy) NSString *definedVariable;

View File

@@ -21,7 +21,7 @@
@interface MPExpressionEvaluator ()
@property (readwrite, nonatomic, copy) NSString *definedVariable;
@property (nonatomic, strong) NSArray *tokens;
- (void)setError:(MPParseError *)error;
@@ -41,36 +41,6 @@
return self;
}
@synthesize lexer = _lexer;
- (void)setLexer:(MPExpressionTokenizer *)lexer
{
_lexer = lexer;
self.tokens = nil;
}
- (MPExpressionTokenizer *)lexer
{
if (!_lexer) {
_lexer = [[MPExpressionTokenizer alloc] init];
}
return _lexer;
}
- (NSArray *)tokens
{
if (!_tokens) {
_tokens = [self.lexer tokenizeExpression:self.expression];
}
return _tokens;
}
- (void)expressionDidChangeInRange:(NSRange)range
replacementLength:(NSUInteger)replacementLength
{
self.tokens = nil;
}
#pragma mark Evaluating Expressions
- (void)setError:(MPParseError *)error
{
@@ -84,7 +54,7 @@
error:(MPParseError *__autoreleasing *)error
{
_error = error;
tokenStream = [[MPTokenStream alloc] initWithTokens:self.tokens];
tokenStream = [[MPTokenStream alloc] initWithTokens:[self.expression allItemsInReferenceFrame:MPTokenReferenceFrame]];
if (!tokenStream.hasMoreTokens) {
self.error = MPParseError(NSMakeRange(0, 0), @"Empty Expression");
return nil;
@@ -186,11 +156,16 @@
case MPGenericFunctionToken:
{
if ([token isKindOfClass:[MPPowerFunction class]]) {
self.error = MPParseError(NSMakeRange(token.range.location, 0), @"No Base for Power");
return nil;
}
return [self decoratedTerm:[((MPFunction *)token) parseWithError:_error]];
}
case MPSinToken:
{
BOOL inverse = [self inverseFunction];
NSRange sinTermRange;
MPTerm *sinTerm = [self nextValue:&sinTermRange];
if (!sinTerm) {
@@ -199,11 +174,12 @@
}
return nil;
}
return [[MPTerm alloc] initWithSinOfTerm:sinTerm];
return inverse ? [[MPTerm alloc] initWithInverseSinOfTerm:sinTerm] : [[MPTerm alloc] initWithSinOfTerm:sinTerm];
}
case MPCosToken:
{
BOOL inverse = [self inverseFunction];
NSRange cosTermRange;
MPTerm *cosTerm = [self nextValue:&cosTermRange];
if (!cosTerm) {
@@ -212,11 +188,12 @@
}
return nil;
}
return [[MPTerm alloc] initWithSinOfTerm:cosTerm];
return inverse ? [[MPTerm alloc] initWithInverseCosOfTerm:cosTerm] : [[MPTerm alloc] initWithCosOfTerm:cosTerm];
}
case MPTanToken:
{
BOOL inverse = [self inverseFunction];
NSRange tanTermRange;
MPTerm *tanTerm = [self nextValue:&tanTermRange];
if (!tanTerm) {
@@ -225,7 +202,7 @@
}
return nil;
}
return [[MPTerm alloc] initWithTanOfTerm:tanTerm];
return inverse ? [[MPTerm alloc] initWithInverseTanOfTerm:tanTerm] : [[MPTerm alloc] initWithTanOfTerm:tanTerm];
}
case MPEOFToken:
@@ -254,10 +231,31 @@
MPPowerFunction *powerFunction = (MPPowerFunction *)powerToken;
powerFunction.baseTerm = decoratedTerm;
return [powerFunction parseWithError:_error];
} else {
tokenStream.currentLocation--;
} else if (powerToken) {
tokenStream.currentTokenIndex--;
}
return decoratedTerm;
}
- (BOOL)inverseFunction
{
MPToken *powerToken = [tokenStream nextTokenOfType:MPGenericFunctionToken];
if ([powerToken isKindOfClass:[MPPowerFunction class]]) {
MPPowerFunction *powerFunction = (MPPowerFunction *)powerToken;
if (powerFunction.exponentExpression.countElements == 1) {
id<MPExpressionElement> element = [powerFunction.exponentExpression elementAtIndex:0];
if ([element isString]) {
NSString *exponent = [[((NSString *)element) componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] componentsJoinedByString:@""];
if ([exponent isEqualToString:@"-1"]) {
return YES;
}
}
}
}
if(powerToken) {
tokenStream.currentTokenIndex--;
}
return NO;
}
@end

View File

@@ -8,6 +8,8 @@
#import "MPExpressionLayout.h"
#import "MPFunctionLayout.h"
#import "MPPowerFunction.h"
#import "MPPowerFunctionLayout.h"
#import "NSString+MPExpressionElement.h"
#import "NSIndexPath+MPAdditions.h"
@@ -91,11 +93,11 @@
#pragma mark Drawing Methods
- (NSRect)generateBounds
{
if (self.expression.numberOfElements == 0) {
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.numberOfElements; index++) {
for (NSUInteger index = 0; index < self.expression.countElements; index++) {
NSRect elementBounds = [self boundsOfElementAtIndex:index];
width += elementBounds.size.width;
height = MAX(height, elementBounds.size.height);
@@ -107,8 +109,10 @@
- (NSRect)boundingRectForRange:(NSRange)range
{
NSUInteger startOffset;
NSUInteger startElementIndex = [self.expression indexOfElementAtLocation:range.location
offset:&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++) {
@@ -122,7 +126,7 @@
x += xOffset;
width += CTLineGetBoundsWithOptions(line, 0).size.width - xOffset;
CFRelease(line);
} else if (startElementIndex < self.expression.numberOfElements) { // Otherwise the selection is after the last symbol
} else if (startElementIndex < self.expression.countElements) { // Otherwise the selection is after the last symbol
width += [self boundsOfElementAtIndex:startElementIndex].size.width;
}
@@ -132,8 +136,10 @@
}
NSUInteger endOffset;
NSUInteger endElementIndex = [self.expression indexOfElementAtLocation:NSMaxRange(range)
offset:&endOffset];
NSUInteger endElementIndex = [self.expression convertIndex:NSMaxRange(range)
fromReferenceFrame:MPSymbolReferenceFrame
toReferenceFrame:MPElementReferenceFrame
offset:&endOffset];
// Selection is inside of one string element
if (startElementIndex == endElementIndex) {
@@ -171,7 +177,7 @@
- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point
{
NSUInteger currentPosition = 0;
for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) {
for (NSUInteger index = 0; index < self.expression.countElements; index++) {
NSRect elementBounds = [self boundsOfElementAtIndex:index];
NSPoint elementOffset = [self offsetOfChildLayoutAtIndex:index];
elementBounds.origin.x += elementOffset.x;
@@ -206,7 +212,7 @@
if (point.x < self.bounds.size.width / 2) {
return [NSIndexPath indexPathWithIndex:0];
} else {
return [NSIndexPath indexPathWithIndex:self.expression.length];
return [NSIndexPath indexPathWithIndex:self.expression.countSymbols];
}
}
@@ -217,21 +223,8 @@
(CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
CGContextSaveGState(context);
#ifdef MPDEBUG_DRAW_ORIGIN
[[NSColor blueColor] set];
[[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(-2, -2, 4, 4)] fill];
#endif
[[NSColor textColor] set];
#ifdef MPDEBUG_DRAW_BOUNDS
[[NSColor greenColor] set];
[[NSBezierPath bezierPathWithRect:self.bounds] stroke];
[[NSColor textColor] set];
#endif
if (self.expression.numberOfElements == 0) {
if (self.expression.countElements == 0) {
CGContextRestoreGState(context);
NSBezierPath *path = [NSBezierPath bezierPathWithRect:NSMakeRect(0, kMPEmptyBoxDrawingYOrigin, kMPEmptyBoxDrawingWidth, kMPEmptyBoxDrawingHeight)];
path.lineWidth = 0.5;
@@ -245,7 +238,7 @@
// Track the x position
CGFloat x = 0;
for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) {
for (NSUInteger index = 0; index < self.expression.countElements; index++) {
// The current element
id element = [self.expression elementAtIndex:index];
NSRect elementBounds = [self boundsOfElementAtIndex:index];
@@ -258,16 +251,15 @@
// Move to the appropriate position
CGContextSetTextPosition(context, x, 0);
#ifdef MPDEBUG_DRAW_BASELINE
[[NSColor redColor] set];
NSRectFill(NSMakeRect(x, -1, elementBounds.size.width, 1));
[[NSColor textColor] set];
#endif
// Perform the drawing
CTLineDraw(line, context);
CFRelease(line);
if (index < self.expression.countElements-1 && [[self.expression elementAtIndex:index+1] isKindOfClass:[MPPowerFunction class]]) {
MPPowerFunctionLayout *layout = (MPPowerFunctionLayout *)[self childLayoutAtIndex:index+1];
layout.baseBounds = elementBounds;
}
} else {
// Let the child layout draw itself
MPLayout *layout = [self childLayoutAtIndex:index];

View File

@@ -36,7 +36,7 @@
self.rootLayout.flipped = expressionView.isFlipped;
}
- (void)didChangeElementsInIndexedRangePath:(MPRangePath *)rangePath
- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath
replacementLength:(NSUInteger)replacementLength
{
if (rangePath.location.length == 0) {

View File

@@ -11,6 +11,6 @@
@interface MPExpressionTokenizer : NSObject
- (NSArray *)tokenizeExpression:(MPExpression *)expression; // Returns MPToken's
+ (NSArray *)tokenizeExpression:(MPExpression *)expression; // Returns MPToken's
@end

View File

@@ -18,21 +18,25 @@
@implementation MPExpressionTokenizer
- (NSArray *)tokenizeExpression:(MPExpression *)expression
+ (NSArray *)tokenizeExpression:(MPExpression *)expression
{
NSMutableArray *tokens = [[NSMutableArray alloc] init];
for (NSUInteger index = 0; index < expression.numberOfElements; index++) {
id <MPExpressionElement> element = [expression elementAtIndex:index];
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]];
[tokens addObjectsFromArray:[self tokenizeElement:(NSString *)element
elementSymbolIndex:symbolIndex]];
}
symbolIndex += element.length;
}
return tokens;
}
- (NSArray *)tokenizeElement:(NSString *)element
+ (NSArray *)tokenizeElement:(NSString *)element
elementSymbolIndex:(NSUInteger)symbolIndex
{
NSUInteger lexLocation = 0;
@@ -40,7 +44,7 @@
NSString *regexStringFormat = @"\\A(?:"
@"(\\*)|"
@"([+-](?:\\s*[+-])*)|"
@"((?:\\d+(?:%@\\d+)?)|(?:\\s%@\\d+))|"
@"((?:\\d+(?:%@\\d+)?)|(?:%@\\d+))|"
@"(sin)|"
@"(cos)|"
@"(tan)|"
@@ -109,9 +113,11 @@
}
lexLocation = NSMaxRange(range);
NSString *tokenStringValue = [element substringWithRange:range];
range.location += symbolIndex;
[tokens addObject:[[MPToken alloc] initWithTokenType:tokenType
range:range
inString:element]];
stringValue:tokenStringValue]];
}
return tokens;

View File

@@ -24,9 +24,6 @@
@property (readonly, nonatomic, strong) MPExpressionStorage *expressionStorage;
// @property (nonatomic, getter = isEditable) BOOL editable;
@property (nonatomic) BOOL allowsIntelligentReplacements;
@property (nonatomic, strong) MPRangePath *selection;
@property (nonatomic, strong) MPParseError *error;
@@ -34,6 +31,7 @@
@property (nonatomic) SEL action;
#pragma mark Actions
- (IBAction)switchRadiansDegrees:(id)sender;
- (IBAction)showFunctions:(id)sender;
@end

View File

@@ -11,8 +11,11 @@
#import "MPExpressionLayout.h"
#import "MPFunctionLayout.h"
#import "MPPowerFunction.h"
#import "MPRangePath.h"
#import "MPMathRules.h"
#import "NSIndexPath+MPAdditions.h"
#import "MPSumFunction.h"
@@ -24,10 +27,14 @@
@interface MPExpressionView ()
@property (nonatomic, strong) NSButton *radiansDegreesButton;
@property (nonatomic, strong) NSButton *functionsButton;
@property (nonatomic, strong) NSPopover *functionsPopover;
@property (nonatomic, strong) MPFunctionsViewController *functionsViewController;
@property (nonatomic, strong) NSTextField *errorLabel;
@property (nonatomic, strong) NSTimer *caretTimer;
@property (nonatomic) NSTimeInterval caretBlinkRate;
@property (nonatomic) BOOL caretVisible;
@@ -117,20 +124,22 @@
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath];
NSUInteger locationInTarget = selectionPath.lastIndex;
NSUInteger locationInElement;
NSUInteger targetElementIndex = [targetExpression indexOfElementAtLocation:locationInTarget
offset:&locationInElement];
NSUInteger targetElementIndex = [targetExpression convertIndex:locationInTarget
fromReferenceFrame:MPSymbolReferenceFrame
toReferenceFrame:MPElementReferenceFrame
offset:&locationInElement];
id<MPExpressionElement> targetElement;
// There is only a target element if the selection is not the last location in an expression
if (targetElementIndex < targetExpression.numberOfElements) {
if (targetElementIndex < targetExpression.countElements) {
targetElement = [targetExpression elementAtIndex:targetElementIndex];
}
if (!selectWords && !extendingSelection && (locationInElement == 0 || locationInTarget == targetExpression.length)) {
if (!selectWords && !extendingSelection && (locationInElement == 0 || locationInTarget == targetExpression.countSymbols)) {
// First or last index in an element or expression
// Last element in the expression
if (locationInTarget == targetExpression.length) {
if (locationInTarget == targetExpression.countSymbols) {
// The selection is inside a function and should proceed
if (selectionPath.length > 1) {
NSIndexPath *functionPath = [[selectionPath indexPathByRemovingLastIndex] indexPathByRemovingLastIndex];
@@ -141,7 +150,9 @@
// The function is to be exited
if (newChildIndex == NSNotFound) {
targetExpression = [self.expressionStorage elementAtIndexPath:[functionPath indexPathByRemovingLastIndex]];
NSUInteger functionLocationInExpression = [targetExpression locationOfElementAtIndex:functionPath.lastIndex];
NSUInteger functionLocationInExpression = [targetExpression convertIndex:functionPath.lastIndex
fromReferenceFrame:MPElementReferenceFrame
toReferenceFrame:MPSymbolReferenceFrame];
return [functionPath indexPathByReplacingLastIndexWithIndex:functionLocationInExpression+1];
} else {
return [[targetExpressionPath indexPathByReplacingLastIndexWithIndex:newChildIndex] indexPathByAddingIndex:0];
@@ -159,9 +170,11 @@
return [[targetFunctionPath indexPathByAddingIndex:leadingChildIndex] indexPathByAddingIndex:0];
}
}
} else if (locationInTarget < targetExpression.length) {
} else if (locationInTarget < targetExpression.countSymbols) {
if (selectWords) {
locationInTarget = [targetExpression locationOfElementAtIndex:targetElementIndex+1];
locationInTarget = [targetExpression convertIndex:targetElementIndex+1
fromReferenceFrame:MPElementReferenceFrame
toReferenceFrame:MPSymbolReferenceFrame];
} else {
locationInTarget++;
}
@@ -177,8 +190,10 @@
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath];
NSUInteger locationInTarget = selectionPath.lastIndex;
NSUInteger locationInElement;
NSUInteger targetElementIndex = [targetExpression indexOfElementAtLocation:locationInTarget
offset:&locationInElement];
NSUInteger targetElementIndex = [targetExpression convertIndex:locationInTarget
fromReferenceFrame:MPSymbolReferenceFrame
toReferenceFrame:MPElementReferenceFrame
offset:&locationInElement];
NSUInteger previousElementIndex = targetElementIndex - (locationInElement == 0 ? 1 : 0);
id<MPExpressionElement> previousElement;
@@ -198,12 +213,14 @@
// The function is to be exited
if (newChildIndex == NSNotFound) {
targetExpression = [self.expressionStorage elementAtIndexPath:[functionPath indexPathByRemovingLastIndex]];
NSUInteger functionLocationInExpression = [targetExpression locationOfElementAtIndex:functionPath.lastIndex];
NSUInteger functionLocationInExpression = [targetExpression convertIndex:functionPath.lastIndex
fromReferenceFrame:MPElementReferenceFrame
toReferenceFrame:MPSymbolReferenceFrame];
return [functionPath indexPathByReplacingLastIndexWithIndex:functionLocationInExpression];
} else {
targetExpressionPath = [targetExpressionPath indexPathByReplacingLastIndexWithIndex:newChildIndex];
targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath];
return [targetExpressionPath indexPathByAddingIndex:targetExpression.length];
return [targetExpressionPath indexPathByAddingIndex:targetExpression.countSymbols];
}
} // else the selection does not change
@@ -218,7 +235,7 @@
targetExpressionPath = [targetFunctionPath indexPathByAddingIndex:trailingChildIndex];
targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath];
return [targetExpressionPath indexPathByAddingIndex:targetExpression.length];
return [targetExpressionPath indexPathByAddingIndex:targetExpression.countSymbols];
}
}
} else if (locationInTarget > 0) {
@@ -226,7 +243,9 @@
if (locationInElement == 0) {
targetElementIndex--;
}
locationInTarget = [targetExpression locationOfElementAtIndex:targetElementIndex];
locationInTarget = [targetExpression convertIndex:targetElementIndex
fromReferenceFrame:MPElementReferenceFrame
toReferenceFrame:MPSymbolReferenceFrame];
} else {
locationInTarget--;
}
@@ -258,10 +277,14 @@
NSUInteger newIndex = [newSelectionPath indexAtPosition:commonPath.length];
if (commonPath.length < anchorPath.length-1) {
anchorIndex = [closestCommonAncestor locationOfElementAtIndex:anchorIndex];
anchorIndex = [closestCommonAncestor convertIndex:anchorIndex
fromReferenceFrame:MPElementReferenceFrame
toReferenceFrame:MPSymbolReferenceFrame];
}
if (commonPath.length < newSelectionPath.length-1) {
newIndex = [closestCommonAncestor locationOfElementAtIndex:newIndex];
newIndex = [closestCommonAncestor convertIndex:newIndex
fromReferenceFrame:MPElementReferenceFrame
toReferenceFrame:MPSymbolReferenceFrame];
}
NSUInteger minIndex = MIN(anchorIndex, newIndex);
@@ -313,6 +336,7 @@
_expressionStorage = expressionStorage;
[self initializeButtons];
[self initializeErrorLabel];
self.selection = [[MPRangePath alloc] initWithRange:NSMakeRange(0, 0)];
self.caretBlinkRate = 1.0;
@@ -321,10 +345,23 @@
- (void)initializeButtons
{
NSButton *radiansDegreesButton = self.radiansDegreesButton;
[self addSubview:radiansDegreesButton];
NSButton *functionsButton = self.functionsButton;
[self addSubview:functionsButton];
NSDictionary *variableBindings = NSDictionaryOfVariableBindings(functionsButton);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"[functionsButton]-10-|"
NSDictionary *variableBindings = NSDictionaryOfVariableBindings(radiansDegreesButton, functionsButton);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[radiansDegreesButton]"
options:0
metrics:nil
views:variableBindings]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-10-[radiansDegreesButton]"
options:0
metrics:nil
views:variableBindings]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[functionsButton]-10-|"
options:0
metrics:nil
views:variableBindings]];
@@ -337,6 +374,21 @@
constant:0]];
}
- (void)initializeErrorLabel
{
NSTextField *errorLabel = self.errorLabel;
[self addSubview:errorLabel];
NSDictionary *variableBindings = NSDictionaryOfVariableBindings(errorLabel);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[errorLabel]"
options:0
metrics:nil
views:variableBindings]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[errorLabel]-10-|"
options:0
metrics:nil
views:variableBindings]];
}
#pragma mark Properties
- (void)setExpressionStorage:(MPExpressionStorage *)expressionStorage
{
@@ -356,16 +408,36 @@
- (void)setError:(MPParseError *)error
{
_error = error;
self.errorLabel.stringValue = error.localizedErrorMessage != nil ? error.localizedErrorMessage : @"";
self.needsDisplay = YES;
}
- (NSButton *)radiansDegreesButton
{
if (!_radiansDegreesButton) {
NSButton *radiansDegreesButton = [[NSButton alloc] initWithFrame:NSZeroRect];
radiansDegreesButton.translatesAutoresizingMaskIntoConstraints = NO;
radiansDegreesButton.buttonType = NSMomentaryPushInButton;
radiansDegreesButton.bordered = YES;
radiansDegreesButton.bezelStyle = NSRoundedBezelStyle;
radiansDegreesButton.imagePosition = NSNoImage;
radiansDegreesButton.alignment = NSCenterTextAlignment;
radiansDegreesButton.title = NSLocalizedString([MPMathRules sharedRules].isUsingDegrees ? @"Deg" : @"Rad", nil);
radiansDegreesButton.font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]];
radiansDegreesButton.target = self;
radiansDegreesButton.action = @selector(switchRadiansDegrees:);
_radiansDegreesButton = radiansDegreesButton;
}
return _radiansDegreesButton;
}
- (NSButton *)functionsButton
{
if (!_functionsButton) {
NSBundle *frameworkBundle = [NSBundle bundleForClass:[self class]];
NSImage *image = [frameworkBundle imageForResource:@"FunctionsButtonDisclosure"];
[image setName:@"FunctionsButtonDisclosure"];
NSButton *button = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)];
NSButton *button = [[NSButton alloc] initWithFrame:NSZeroRect];
button.translatesAutoresizingMaskIntoConstraints = NO;
button.target = self;
button.action = @selector(showFunctions:);
@@ -382,7 +454,28 @@
return _functionsButton;
}
- (NSTextField *)errorLabel
{
if (!_errorLabel) {
NSTextField *label = [[NSTextField alloc] initWithFrame:NSZeroRect];
label.textColor = [NSColor redColor];
label.translatesAutoresizingMaskIntoConstraints = NO;
label.bezeled = NO;
label.drawsBackground = NO;
label.editable = NO;
label.selectable = NO;
_errorLabel = label;
}
return _errorLabel;
}
#pragma mark Actions
- (void)switchRadiansDegrees:(id)sender
{
[MPMathRules sharedRules].isUsingDegrees = ![MPMathRules sharedRules].isUsingDegrees;
self.radiansDegreesButton.title = NSLocalizedString([MPMathRules sharedRules].isUsingDegrees ? @"Deg" : @"Rad", nil);
}
- (void)showFunctions:(id)sender
{
if (self.functionsPopover == nil || self.functionsViewController == nil) {
@@ -404,10 +497,14 @@
- (void)insertFunction:(MPFunction *)function
{
[self.functionsPopover close];
[self.expressionStorage replaceSymbolsInRangePath:self.selection withElements:@[function]];
[self.expressionStorage replaceItemsInRangePath:self.selection
referenceFrame:MPSymbolReferenceFrame
withElements:@[function]];
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:[targetExpression indexOfElementAtLocation:self.selection.location.lastIndex
offset:NULL]];
NSUInteger functionElementIndex = [targetExpression convertIndex:self.selection.location.lastIndex
fromReferenceFrame:MPSymbolReferenceFrame
toReferenceFrame:MPElementReferenceFrame];
NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:functionElementIndex];
MPFunctionLayout *functionLayout = (MPFunctionLayout *)[self.expressionStorage.rootLayout childLayoutAtIndexPath:functionPath];
self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:functionLayout.indexOfLeadingChild] indexPathByAddingIndex:0], 0);
}
@@ -475,15 +572,19 @@
[self insertParenthesisFunction:nil];
return;
}
if (theEvent.keyCode == 10) {
[self insertPowerFunction];
return;
}
NSString *decimalSeparator = [NSRegularExpression escapedPatternForString:[[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator]];
NSMutableCharacterSet *allowedCharacters = [NSMutableCharacterSet alphanumericCharacterSet];
[allowedCharacters addCharactersInString:[NSString stringWithFormat:@"+-*= !%@", decimalSeparator]];
if (characters.length == 1 && [characters stringByTrimmingCharactersInSet:allowedCharacters].length == 0) {
[self.expressionStorage replaceSymbolsInRangePath:self.selection withElements:@[characters]];
[self.expressionStorage replaceItemsInRangePath:self.selection
referenceFrame:MPSymbolReferenceFrame
withElements:@[characters]];
self.selection = MPMakeRangePath([self.selection.location indexPathByIncrementingLastIndex], 0);
} else {
[self interpretKeyEvents:@[theEvent]];
@@ -492,15 +593,37 @@
- (void)insertParenthesisFunction:(id)sender
{
MPExpression *selectedElementsExpression = [self.expressionStorage subexpressionWithRangePath:self.selection];
MPExpression *selectedElementsExpression = [self.expressionStorage subexpressionWithRangePath:self.selection
referenceFrame:MPSymbolReferenceFrame];
MPParenthesisFunction *function = [[MPParenthesisFunction alloc] init];
function.expression = selectedElementsExpression;
[self.expressionStorage replaceSymbolsInRangePath:self.selection
withElements:@[function]];
[self.expressionStorage replaceItemsInRangePath:self.selection
referenceFrame:MPSymbolReferenceFrame
withElements:@[function]];
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:[targetExpression indexOfElementAtLocation:self.selection.location.lastIndex
offset:NULL]];
NSUInteger functionElementIndex = [targetExpression convertIndex:self.selection.location.lastIndex
fromReferenceFrame:MPSymbolReferenceFrame
toReferenceFrame:MPElementReferenceFrame];
NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:functionElementIndex];
self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:0] indexPathByAddingIndex:0], self.selection.length);
}
- (void)insertPowerFunction
{
MPExpression *selectedElementsExpression = [self.expressionStorage subexpressionWithRangePath:self.selection
referenceFrame:MPSymbolReferenceFrame];
MPPowerFunction *function = [[MPPowerFunction alloc] init];
function.exponentExpression = selectedElementsExpression;
[self.expressionStorage replaceItemsInRangePath:self.selection
referenceFrame:MPSymbolReferenceFrame
withElements:@[function]];
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
NSUInteger functionElementIndex = [targetExpression convertIndex:self.selection.location.lastIndex
fromReferenceFrame:MPSymbolReferenceFrame
toReferenceFrame:MPElementReferenceFrame];
NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:functionElementIndex];
self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:0] indexPathByAddingIndex:0], self.selection.length);
}
@@ -563,7 +686,7 @@
- (void)moveToEndOfLine:(id)sender
{
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
self.selection = MPMakeRangePath([self.selection.location indexPathByReplacingLastIndexWithIndex:targetExpression.length], 0);
self.selection = MPMakeRangePath([self.selection.location indexPathByReplacingLastIndexWithIndex:targetExpression.countSymbols], 0);
}
- (void)moveLeftAndModifySelection:(id)sender
@@ -676,22 +799,27 @@
- (void)selectAll:(id)sender
{
NSIndexPath *location = [NSIndexPath indexPathWithIndex:0];
self.selection = MPMakeRangePath(location, self.expressionStorage.length);
self.selection = MPMakeRangePath(location, self.expressionStorage.countSymbols);
}
- (void)deleteBackward:(id)sender
{
if (self.selection.length > 0) {
[self.expressionStorage replaceSymbolsInRangePath:self.selection withElements:@[]];
[self.expressionStorage replaceItemsInRangePath:self.selection
referenceFrame:MPSymbolReferenceFrame
withElements:@[]];
self.selection = MPMakeRangePath(self.selection.location, 0);
} else if (self.selection.location.lastIndex > 0) {
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
id <MPExpressionElement> elementToDelete = [targetExpression elementAtIndex:[targetExpression indexOfElementAtLocation:self.selection.location.lastIndex-1
offset:NULL]];
id<MPExpressionElement> elementToDelete = [targetExpression elementAtIndex:[targetExpression convertIndex:self.selection.location.lastIndex-1
fromReferenceFrame:MPSymbolReferenceFrame
toReferenceFrame:MPElementReferenceFrame]];
if ([elementToDelete isFunction]) {
self.selection = MPMakeRangePath(self.selection.location.indexPathByDecrementingLastIndex, 1);
} else {
[targetExpression replaceSymbolsInRange:NSMakeRange(self.selection.location.lastIndex-1, 1) withElements:@[]];
[targetExpression replaceItemsInRange:NSMakeRange(self.selection.location.lastIndex-1, 1)
referenceFrame:MPSymbolReferenceFrame
withElements:@[]];
self.selection = MPMakeRangePath([self.selection.location indexPathByDecrementingLastIndex], 0);
}
} else {
@@ -707,14 +835,20 @@
for (NSUInteger index = remainingIndexes.firstIndex;
index <= remainingIndexes.lastIndex;
index = [remainingIndexes indexGreaterThanIndex:index]) {
[remainder addObjectsFromArray:[function childAtIndex:index].elements];
MPExpression *expression = [function childAtIndex:index];
[remainder addObjectsFromArray:[expression allItemsInReferenceFrame:MPElementReferenceFrame]];
}
NSIndexPath *newTargetExpressionPath = [functionPath indexPathByRemovingLastIndex];
MPExpression *newTargetExpression = [self.expressionStorage elementAtIndexPath:newTargetExpressionPath];
NSIndexPath *newSelectionLocation = [functionPath indexPathByReplacingLastIndexWithIndex:[newTargetExpression locationOfElementAtIndex:functionPath.lastIndex]];
NSUInteger newSelectionElementIndex = [newTargetExpression convertIndex:functionPath.lastIndex
fromReferenceFrame:MPElementReferenceFrame
toReferenceFrame:MPSymbolReferenceFrame];
NSIndexPath *newSelectionLocation = [functionPath indexPathByReplacingLastIndexWithIndex:newSelectionElementIndex];
[self.expressionStorage replaceSymbolsInRangePath:MPMakeRangePath(newSelectionLocation, 1) withElements:remainder];
[self.expressionStorage replaceItemsInRangePath:MPMakeRangePath(newSelectionLocation, 1)
referenceFrame:MPSymbolReferenceFrame
withElements:remainder];
self.selection = MPMakeRangePath(newSelectionLocation, 0);
}
}
@@ -762,6 +896,24 @@
yBy:expressionOrigin.y];
[transform concat];
// Draw the error
if (self.error) {
[[NSColor redColor] set];
NSRect rect = [self.expressionStorage.rootLayout boundingRectForRangePath:self.error.rangePath];
if (self.error.rangePath.length == 0) {
NSBezierPath *bezierPath = [NSBezierPath bezierPath];
[bezierPath moveToPoint:rect.origin];
[bezierPath lineToPoint:NSMakePoint(rect.origin.x - 5, rect.origin.y - 5)];
[bezierPath moveToPoint:rect.origin];
[bezierPath lineToPoint:NSMakePoint(rect.origin.x + 5, rect.origin.y - 5)];
bezierPath.lineWidth = 2.0;
[bezierPath stroke];
} else {
NSRect underlineRect = NSMakeRect(rect.origin.x, rect.origin.y + 2, rect.size.width, 2);
NSRectFill(underlineRect);
}
}
// Draw the selection
if (self.caretVisible || self.selection.length > 0) {
if (self.selection.length == 0) {

View File

@@ -18,7 +18,10 @@
- (NSRange)range
{
NSUInteger selfIndex = [self.parent indexOfElement:self];
return NSMakeRange([self.parent locationOfElementAtIndex:selfIndex], 1);
return NSMakeRange([self.parent convertIndex:selfIndex
fromReferenceFrame:MPElementReferenceFrame
toReferenceFrame:MPSymbolReferenceFrame],
1);
}
- (BOOL)exists

View File

@@ -99,7 +99,7 @@
{
NSUInteger selfIndex = [self.parent indexOfElement:self];
MPRangePath *newPath = MPMakeRangePath([rangePath.location indexPathByPreceedingIndex:selfIndex], rangePath.length);
[self.parent didChangeElementsInIndexedRangePath:newPath
[self.parent didChangeElementsInRangePath:newPath
replacementLength:replacementLength];
}

View File

@@ -14,6 +14,7 @@
#import "MPSumFunctionLayout.h"
#import "MPParenthesisFunction.h"
#import "MPParenthesisFunctionLayout.h"
#import "MPPowerFunctionLayout.h"
#import "NSIndexPath+MPAdditions.h"
@@ -28,6 +29,8 @@
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];
}
return [[self alloc] initWithFunction:function
parent:parent];
@@ -101,6 +104,16 @@
return [self indexPathForLocalMousePoint:point];
}
- (NSUInteger)indexOfLeadingChild
{
return 0;
}
- (NSUInteger)indexOfTrailingChild
{
return 0;
}
- (NSUInteger)indexOfChildBeforeChildAtIndex:(NSUInteger)index
{
return NSNotFound;

View File

@@ -12,6 +12,7 @@
#import "MPSumFunction.h"
#import "MPParenthesisFunction.h"
#import "MPPowerFunction.h"
@class MPFunctionTemplateItem;
@@ -197,11 +198,24 @@ static void *MPFunctionTemplateViewMouseOverContext = @"MPFunctionTemplateViewMo
- (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"];
self.functionPrototypes = @[
@{@"function": [[MPSumFunction alloc] init],
@{@"function": sumFunction,
@"name": NSLocalizedString(@"Sum", @"Sum Function Name")},
@{@"function": [[MPParenthesisFunction alloc] init],
@"name": NSLocalizedString(@"Parenthesis", @"Parenthesis 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")}
];
[self.collectionView addObserver:self
forKeyPath:@"hoverItem"

View File

@@ -12,6 +12,7 @@ FOUNDATION_EXPORT NSString *MPMathRulesAllowsImplicitMultiplicationKey;
FOUNDATION_EXPORT NSString *MPMathRulesMaximumOperatorChainLengthKey;
FOUNDATION_EXPORT NSString *MPMathRulesMaximumOperatorChainLengthInMultiplicationKey;
FOUNDATION_EXPORT NSString *MPMathRulesMaximumOperatorChainLengthInFunctionKey;
FOUNDATION_EXPORT NSString *MPMathRulesIsUsingDegreesKey;
@interface MPMathRules : NSObject
@@ -25,4 +26,6 @@ FOUNDATION_EXPORT NSString *MPMathRulesMaximumOperatorChainLengthInFunctionKey;
@property (nonatomic) NSUInteger maximumOperatorChainLengthInMultiplication; // Default: 1, 0 means actually 0
@property (nonatomic) NSUInteger maximumOperatorChainLengthInFunction; // For sin, cos, tan. Default: 1
@property (nonatomic) BOOL isUsingDegrees;
@end

View File

@@ -12,6 +12,7 @@ NSString *MPMathRulesAllowsImplicitMultiplicationKey = @"MPMathRulesAllowsImplic
NSString *MPMathRulesMaximumOperatorChainLengthKey = @"MPMathRulesMaximumOperatorChainLengthKey";
NSString *MPMathRulesMaximumOperatorChainLengthInMultiplicationKey = @"MPMathRulesMaximumOperatorChainLengthInMultiplicationKey";
NSString *MPMathRulesMaximumOperatorChainLengthInFunctionKey = @"MPMathRulesMaximumOperatorChainLengthInFunctionKey";
NSString *MPMathRulesIsUsingDegreesKey = @"MPMathRulesIsUsingDegreesKey";
@implementation MPMathRules
@@ -33,15 +34,18 @@ static MPMathRules *sharedRules;
self = [super init];
if (self) {
_usingUserDefaultValues = YES;
NSNumber *userDefaultsAllowImplicitMultiplication = [[NSUserDefaults standardUserDefaults] objectForKey:MPMathRulesAllowsImplicitMultiplicationKey];
NSNumber *userDefaultsMaximumOperatorChainLength = [[NSUserDefaults standardUserDefaults] objectForKey:MPMathRulesMaximumOperatorChainLengthKey];
NSNumber *userDefaultsMaximumOperatorChainLengthInMultiplication = [[NSUserDefaults standardUserDefaults] objectForKey:MPMathRulesMaximumOperatorChainLengthInMultiplicationKey];
NSNumber *userDefaultsMaximumOperatorChainLengthInFunction = [[NSUserDefaults standardUserDefaults] objectForKey:MPMathRulesMaximumOperatorChainLengthInFunctionKey];
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSNumber *userDefaultsAllowImplicitMultiplication = [userDefaults objectForKey:MPMathRulesAllowsImplicitMultiplicationKey];
NSNumber *userDefaultsMaximumOperatorChainLength = [userDefaults objectForKey:MPMathRulesMaximumOperatorChainLengthKey];
NSNumber *userDefaultsMaximumOperatorChainLengthInMultiplication = [userDefaults objectForKey:MPMathRulesMaximumOperatorChainLengthInMultiplicationKey];
NSNumber *userDefaultsMaximumOperatorChainLengthInFunction = [userDefaults objectForKey:MPMathRulesMaximumOperatorChainLengthInFunctionKey];
NSNumber *userDefaultsIsUsingDegrees = [userDefaults objectForKey:MPMathRulesIsUsingDegreesKey];
_allowsImplicitMultiplication = userDefaultsAllowImplicitMultiplication != nil ? userDefaultsAllowImplicitMultiplication.boolValue : NO;
_allowsImplicitMultiplication = userDefaultsAllowImplicitMultiplication.boolValue;
_maximumOperatorChainLength = userDefaultsMaximumOperatorChainLength != nil ? userDefaultsMaximumOperatorChainLength.unsignedIntegerValue : 2;
_maximumOperatorChainLengthInMultiplication = userDefaultsMaximumOperatorChainLengthInMultiplication != nil ? userDefaultsMaximumOperatorChainLengthInMultiplication.unsignedIntegerValue : 1;
_maximumOperatorChainLengthInFunction = userDefaultsMaximumOperatorChainLengthInFunction != nil ? userDefaultsMaximumOperatorChainLengthInFunction.unsignedIntegerValue : 1;
_isUsingDegrees = userDefaultsIsUsingDegrees.boolValue;
}
return self;
}
@@ -54,6 +58,7 @@ static MPMathRules *sharedRules;
self.maximumOperatorChainLength = self.maximumOperatorChainLength;
self.maximumOperatorChainLengthInMultiplication = self.maximumOperatorChainLengthInMultiplication;
self.maximumOperatorChainLengthInFunction = self.maximumOperatorChainLengthInFunction;
self.isUsingDegrees = self.isUsingDegrees;
}
- (void)setAllowsImplicitMultiplication:(BOOL)allowsImplicitMultiplication
@@ -69,7 +74,7 @@ static MPMathRules *sharedRules;
{
_maximumOperatorChainLength = maximumOperatorChainLength;
if (self.isUsingUserDefaultValues) {
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithUnsignedInteger:maximumOperatorChainLength]
[[NSUserDefaults standardUserDefaults] setObject:@(maximumOperatorChainLength)
forKey:MPMathRulesMaximumOperatorChainLengthKey];
}
}
@@ -78,7 +83,7 @@ static MPMathRules *sharedRules;
{
_maximumOperatorChainLengthInMultiplication = maximumOperatorChainLengthInMultiplication;
if (self.isUsingUserDefaultValues) {
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithUnsignedInteger:maximumOperatorChainLengthInMultiplication]
[[NSUserDefaults standardUserDefaults] setObject:@(maximumOperatorChainLengthInMultiplication)
forKey:MPMathRulesMaximumOperatorChainLengthInMultiplicationKey];
}
}
@@ -87,9 +92,18 @@ static MPMathRules *sharedRules;
{
_maximumOperatorChainLengthInFunction = maximumOperatorChainLengthInFunction;
if (self.isUsingUserDefaultValues) {
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithUnsignedInteger:maximumOperatorChainLengthInFunction]
[[NSUserDefaults standardUserDefaults] setObject:@(maximumOperatorChainLengthInFunction)
forKey:MPMathRulesMaximumOperatorChainLengthInFunctionKey];
}
}
- (void)setIsUsingDegrees:(BOOL)isUsingDegrees
{
_isUsingDegrees = isUsingDegrees;
if (self.isUsingUserDefaultValues) {
[[NSUserDefaults standardUserDefaults] setBool:isUsingDegrees
forKey:MPMathRulesIsUsingDegreesKey];
}
}
@end

View File

@@ -14,13 +14,6 @@
MPFunctionAccessorImplementation(Expression, _expression)
//- (void)setExpression:(MPExpression *)expression
//{
// _expression.parent = nil;
// _expression = expression;
// _expression.parent = self;
//}
- (NSArray *)childrenAccessors
{
return @[@"expression"];
@@ -28,7 +21,7 @@ MPFunctionAccessorImplementation(Expression, _expression)
- (MPTerm *)parseWithError:(MPParseError *__autoreleasing *)error
{
MPExpressionEvaluator *evaluator = self.expression.evaluator;
MPExpressionEvaluator *evaluator = [[MPExpressionEvaluator alloc] initWithExpression:self.expression];
return [evaluator parseExpectingVariable:NO
error:error];
}

View File

@@ -128,16 +128,6 @@
return [NSIndexPath indexPathWithIndex:0];
}
- (NSUInteger)indexOfLeadingChild
{
return 0;
}
- (NSUInteger)indexOfTrailingChild
{
return 0;
}
- (NSIndexSet *)indexesOfRemainingChildren
{
return [NSIndexSet indexSetWithIndex:0];

View File

@@ -36,7 +36,7 @@
- (MPRangePath *)rangePath
{
NSIndexPath *location = self.pathToExpression;
location = [location indexPathByPreceedingIndex:self.errorRange.location];
location = [location indexPathByAddingIndex:self.errorRange.location];
MPRangePath *rangePath = [MPRangePath rangePathWithLocation:location length:self.errorRange.length];
return rangePath;
}

View File

@@ -10,8 +10,7 @@
@interface MPPowerFunction : MPFunction
@property (nonatomic, strong) MPTerm *baseTerm;
@property (nonatomic, strong) MPExpression *exponentExpression;
@property (nonatomic, strong) MPTerm *baseTerm;
@end

View File

@@ -20,8 +20,9 @@ MPFunctionAccessorImplementation(ExponentExpression, _exponentExpression)
- (MPTerm *)parseWithError:(MPParseError *__autoreleasing *)error
{
MPTerm *exponentTerm = [self.exponentExpression.evaluator parseExpectingVariable:NO
error:error];
MPExpressionEvaluator *exponent = [[MPExpressionEvaluator alloc] initWithExpression:self.exponentExpression];
MPTerm *exponentTerm = [exponent parseExpectingVariable:NO
error:error];
if (exponentTerm == nil) {
return nil;
}

View File

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

View File

@@ -0,0 +1,68 @@
//
// MPPowerFunctionLayout.m
// MathPad
//
// Created by Kim Wittenburg on 30.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPPowerFunctionLayout.h"
#define kMPEmptyBoxHeight (CTFontGetDescent((CTFontRef)self.font) + CTFontGetAscent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font))
#define kMPEmptyBoxYOrigin (-(CTFontGetDescent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font)))
#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 / 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
{
MPLayout *exponentLayout = [self childLayoutAtIndex:0];
[exponentLayout drawAtPoint:[self offsetOfChildLayoutAtIndex:0]];
}
@end

View File

@@ -251,6 +251,11 @@
@return A new expression.
*/
- (MPExpression *)subexpressionWithRangePath:(MPRangePath *)aRangePath; // If location points to expression (in function) nil is returned
- (MPExpression *)subexpressionWithRangePath:(MPRangePath *)aRangePath
referenceFrame:(MPReferenceFrame)referenceFrame;
- (void)replaceItemsInRangePath:(MPRangePath *)rangePath
referenceFrame:(MPReferenceFrame)referenceFrame
withElements:(NSArray *)elements;
@end

View File

@@ -156,13 +156,33 @@
@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];
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

@@ -29,12 +29,12 @@ MPFunctionAccessorImplementation(SumExpression, _sumExpression)
#pragma mark Evaluating Functions
- (MPTerm *)parseWithError:(MPParseError *__autoreleasing *)error
{
MPExpressionEvaluator *startEvaluator = self.startExpression.evaluator;
MPExpressionEvaluator *startEvaluator = [[MPExpressionEvaluator alloc] initWithExpression:self.startExpression];
MPTerm *start = [startEvaluator parseExpectingVariable:YES
error:error];
ReturnIfNil(start);
MPExpressionEvaluator *targetEvaluator = self.targetExpression.evaluator;
MPExpressionEvaluator *targetEvaluator = [[MPExpressionEvaluator alloc] initWithExpression:self.targetExpression];
MPTerm *target = [targetEvaluator parseExpectingVariable:NO
error:error];
ReturnIfNil(target);
@@ -43,7 +43,7 @@ MPFunctionAccessorImplementation(SumExpression, _sumExpression)
[[MPEvaluationContext sharedContext] push];
[[MPEvaluationContext sharedContext] defineVariable:variable withValue:[NSNull null]];
MPExpressionEvaluator *sumEvaluator = self.sumExpression.evaluator;
MPExpressionEvaluator *sumEvaluator = [[MPExpressionEvaluator alloc] initWithExpression:self.sumExpression];
MPTerm *sum = [sumEvaluator parseExpectingVariable:NO
error:error];
ReturnIfNil(sum);

View File

@@ -142,12 +142,6 @@
- (void)draw
{
#ifdef MPDEBUG_DRAW_BASELINE
[[NSColor redColor] set];
NSRectFill(NSMakeRect(0, -1, self.bounds.size.width, 1));
[[NSColor textColor] set];
#endif
// Get the current context
CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
@@ -161,12 +155,6 @@
CGContextSetTextPosition(context, localLineBounds.origin.x, 0);
CTLineDraw(line, context);
#ifdef MPDEBUG_DRAW_FUNCTION_BOUNDS
[[NSColor blueColor] set];
[[NSBezierPath bezierPathWithRect:localLineBounds] stroke];
[[NSColor textColor] set];
#endif
// Draw the start function
MPLayout *startExpressionLayout = [self childLayoutAtIndex:0];
NSPoint startExpressionLocation = [self offsetOfChildLayoutAtIndex:0];

View File

@@ -23,6 +23,10 @@
- (instancetype)initWithCosOfTerm:(MPTerm *)term;
- (instancetype)initWithTanOfTerm:(MPTerm *)term;
- (instancetype)initWithInverseSinOfTerm:(MPTerm *)term;
- (instancetype)initWithInverseCosOfTerm:(MPTerm *)term;
- (instancetype)initWithInverseTanOfTerm:(MPTerm *)term;
- (NSDecimalNumber *)evaluate;
@end

View File

@@ -8,6 +8,7 @@
#import "MPTerm.h"
#import "MPEvaluationContext.h"
#import "MPMathRules.h"
@interface MPTerm ()
@@ -66,7 +67,6 @@
{
return [self initWithBlock:^NSDecimalNumber *{
NSDecimalNumber *termValue = [term evaluate];
NSLog(@"Factorial of %@ = %f", termValue, tgamma(termValue.doubleValue + 1));
return [[NSDecimalNumber alloc] initWithDouble:tgamma(termValue.doubleValue + 1)];
}];
}
@@ -75,6 +75,9 @@
{
return [self initWithBlock:^NSDecimalNumber *{
NSDecimalNumber *termValue = [term evaluate];
if ([MPMathRules sharedRules].isUsingDegrees) {
termValue = [[termValue decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithInteger:180]];
}
return [[NSDecimalNumber alloc] initWithDouble:sin(termValue.doubleValue)];
}];
}
@@ -83,6 +86,8 @@
{
return [self initWithBlock:^NSDecimalNumber *{
NSDecimalNumber *termValue = [term evaluate];
if ([MPMathRules sharedRules].isUsingDegrees) {
termValue = [[termValue decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithInteger:180]]; }
return [[NSDecimalNumber alloc] initWithDouble:cos(termValue.doubleValue)];
}];
}
@@ -91,13 +96,54 @@
{
return [self initWithBlock:^NSDecimalNumber *{
NSDecimalNumber *termValue = [term evaluate];
if ([MPMathRules sharedRules].isUsingDegrees) {
termValue = [[termValue decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithInteger:180]]; }
return [[NSDecimalNumber alloc] initWithDouble:tan(termValue.doubleValue)];
}];
}
- (instancetype)initWithInverseSinOfTerm:(MPTerm *)term
{
return [self initWithBlock:^NSDecimalNumber *{
NSDecimalNumber *termValue = [term evaluate];
NSDecimalNumber *result = [[NSDecimalNumber alloc] initWithDouble:asin(termValue.doubleValue)];
if ([MPMathRules sharedRules].isUsingDegrees) {
result = [[result decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:180]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]];
}
return result;
}];
}
- (instancetype)initWithInverseCosOfTerm:(MPTerm *)term
{
return [self initWithBlock:^NSDecimalNumber *{
NSDecimalNumber *termValue = [term evaluate];
NSDecimalNumber *result = [[NSDecimalNumber alloc] initWithDouble:acos(termValue.doubleValue)];
if ([MPMathRules sharedRules].isUsingDegrees) {
result = [[result decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:180]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]];
}
return result; }];
}
- (instancetype)initWithInverseTanOfTerm:(MPTerm *)term
{
return [self initWithBlock:^NSDecimalNumber *{
NSDecimalNumber *termValue = [term evaluate];
NSDecimalNumber *result = [[NSDecimalNumber alloc] initWithDouble:atan(termValue.doubleValue)];
if ([MPMathRules sharedRules].isUsingDegrees) {
result = [[result decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:180]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]];
}
return result; }];
}
- (NSDecimalNumber *)evaluate
{
return self.block();
@try {
return self.block();
}
@catch (NSException *exception) {
return [NSDecimalNumber notANumber];
}
}
@end

View File

@@ -41,10 +41,11 @@ typedef NS_ENUM(NSUInteger, MPTokenType) {
@interface MPToken : NSObject <MPToken>
- (instancetype)initEOFTokenAtLocation:(NSUInteger)eofLocation;
- (instancetype)initWithRange:(NSRange)range inString:(NSString *)input;
- (instancetype)initWithRange:(NSRange)range
stringValue:(NSString *)input;
- (instancetype)initWithTokenType:(MPTokenType)tokenType
range:(NSRange)range
inString:(NSString *)input;
stringValue:(NSString *)input;
@end

View File

@@ -23,22 +23,22 @@
return self;
}
- (instancetype)initWithRange:(NSRange)range inString:(NSString *)input
- (instancetype)initWithRange:(NSRange)range stringValue:(NSString *)input
{
self = [super init];
if (self) {
_range = range;
_stringValue = [input substringWithRange:range].copy;
_stringValue = input.copy;
}
return self;
}
- (instancetype)initWithTokenType:(MPTokenType)tokenType
range:(NSRange)range
inString:(NSString *)input
stringValue:(NSString *)input
{
self = [self initWithRange:range
inString:input];
stringValue:input];
if (self) {
_tokenType = tokenType;
}

View File

@@ -16,7 +16,7 @@
@property (nonatomic, copy) NSArray *tokens;
@property (nonatomic, getter=isIgnoringWhitespaceTokens) BOOL ignoringWhitespaceTokens; // Default: YES
@property (nonatomic) NSUInteger currentLocation;
@property (nonatomic) NSUInteger currentTokenIndex;
- (void)reset;
- (BOOL)hasMoreTokens;

View File

@@ -9,7 +9,6 @@
#import "MPTokenStream.h"
@implementation MPTokenStream {
NSUInteger currentTokenIndex;
NSUInteger eofLocation;
}
@@ -32,37 +31,34 @@
if (!self.isIgnoringWhitespaceTokens) {
return;
}
while (currentTokenIndex < self.tokens.count) {
MPToken *token = self.tokens[currentTokenIndex];
while (self.currentTokenIndex < self.tokens.count) {
MPToken *token = self.tokens[self.currentTokenIndex];
if (token.tokenType != MPWhitespaceToken) {
return;
}
self.currentLocation = NSMaxRange(token.range);
++currentTokenIndex;
++self.currentTokenIndex;
}
}
- (void)reset
{
currentTokenIndex = 0;
self.currentLocation = 0;
self.currentTokenIndex = 0;
[self skipWhitespaces];
}
- (BOOL)hasMoreTokens
{
[self skipWhitespaces];
return currentTokenIndex < self.tokens.count;
return self.currentTokenIndex < self.tokens.count;
}
- (MPToken *)nextToken
{
[self skipWhitespaces];
if (currentTokenIndex >= self.tokens.count) {
if (self.currentTokenIndex >= self.tokens.count) {
return [[MPToken alloc] initEOFTokenAtLocation:eofLocation];
} else {
MPToken *token = self.tokens[currentTokenIndex++];
self.currentLocation = NSMaxRange(token.range);
MPToken *token = self.tokens[self.currentTokenIndex++];
return token;
}
}
@@ -70,15 +66,14 @@
- (MPToken *)nextTokenOfType:(MPTokenType)type
{
[self skipWhitespaces];
if (currentTokenIndex >= self.tokens.count) {
if (self.currentTokenIndex >= self.tokens.count) {
return nil;
} else {
MPToken *token = self.tokens[currentTokenIndex];
MPToken *token = self.tokens[self.currentTokenIndex];
if (token.tokenType != type) {
return nil;
}
++currentTokenIndex;
self.currentLocation = NSMaxRange(token.range);
++self.currentTokenIndex;
return token;
}
}

View File

@@ -29,21 +29,21 @@
// Test expression with function
testExpression = [[MPExpression alloc] initWithElement:[[MPFunction alloc] init]];
XCTAssertEqual([testExpression numberOfElements], 1);
XCTAssertEqual([testExpression countElements], 1);
XCTAssertEqualObjects([testExpression elementAtIndex:0], [[MPFunction alloc] init]);
testExpression = [[MPExpression alloc] initWithElements:@[@"1234", [[MPFunction alloc] init], @"17", [[MPFunction alloc] init]]];
XCTAssertEqual([testExpression numberOfElements], 4);
XCTAssertEqual([testExpression countElements], 4);
XCTAssertEqualObjects([testExpression elementAtIndex:2], @"17");
// Test expression with subsequent strings
testExpression = [[MPExpression alloc] initWithElements:@[@"1234", @"5678"]];
XCTAssertEqual([testExpression numberOfElements], 1);
XCTAssertEqual([testExpression countElements], 1);
XCTAssertEqualObjects([testExpression elementAtIndex:0], @"12345678");
// Test expression with only empty string
testExpression = [[MPExpression alloc] initWithElement:@""];
XCTAssertEqual([testExpression numberOfElements], 0);
XCTAssertEqual([testExpression countElements], 0);
}
- (void)testSubexpressions {
@@ -52,77 +52,77 @@
/********** subexpressionFromIndex: **********/
// Test with start index at front
MPExpression *subexpression = [testExpression subexpressionFromLocation:0];
XCTAssertEqual([subexpression numberOfElements], 4);
XCTAssertEqual([subexpression countElements], 4);
XCTAssertEqualObjects([subexpression elementAtIndex:0], @"1234");
// Test with start index in first element
subexpression = [testExpression subexpressionFromLocation:2];
XCTAssertEqual([subexpression numberOfElements], 4);
XCTAssertEqual([subexpression countElements], 4);
XCTAssertEqualObjects([subexpression elementAtIndex:0], @"34");
// Test with start index in middle element starting with a literal
subexpression = [testExpression subexpressionFromLocation:6];
XCTAssertEqual([subexpression numberOfElements], 2);
XCTAssertEqual([subexpression countElements], 2);
XCTAssertEqualObjects([subexpression elementAtIndex:0], @"7");
// Test with start index in middle element starting with a function
subexpression = [testExpression subexpressionFromLocation:4];
XCTAssertEqual([subexpression numberOfElements], 3);
XCTAssertEqual([subexpression countElements], 3);
XCTAssertEqualObjects([subexpression elementAtIndex:0], [[MPFunction alloc] init]);
// Test with start index in last element
subexpression = [testExpression subexpressionFromLocation:7];
XCTAssertEqual([subexpression numberOfElements], 1);
XCTAssertEqual([subexpression countElements], 1);
XCTAssertEqualObjects([subexpression elementAtIndex:0], [[MPFunction alloc] init]);
// Test with start index at end
subexpression = [testExpression subexpressionFromLocation:8];
XCTAssertEqual([subexpression numberOfElements], 0);
XCTAssertEqual([subexpression countElements], 0);
/********** subexpressionToIndex: **********/
// Test with end index at front
subexpression = [testExpression subexpressionToLocation:0];
XCTAssertEqual([subexpression numberOfElements], 0);
XCTAssertEqual([subexpression countElements], 0);
// Test with end index in first Element
subexpression = [testExpression subexpressionToLocation:2];
XCTAssertEqual([subexpression numberOfElements], 1);
XCTAssertEqual([subexpression countElements], 1);
XCTAssertEqualObjects([subexpression elementAtIndex:0], @"12");
// Test with end index in middle Element ending with a literal
subexpression = [testExpression subexpressionToLocation:6];
XCTAssertEqual([subexpression numberOfElements], 3);
XCTAssertEqual([subexpression countElements], 3);
XCTAssertEqualObjects([subexpression elementAtIndex:2], @"1");
// Test with end index in middle Element ending with a function
subexpression = [testExpression subexpressionToLocation:5];
XCTAssertEqual([subexpression numberOfElements], 2);
XCTAssertEqual([subexpression countElements], 2);
XCTAssertEqualObjects([subexpression elementAtIndex:1], [[MPFunction alloc] init]);
// Test with end index at end
subexpression = [testExpression subexpressionToLocation:8];
XCTAssertEqual([subexpression numberOfElements], 4);
XCTAssertEqual([subexpression countElements], 4);
XCTAssertEqualObjects([subexpression elementAtIndex:3], [[MPFunction alloc] init]);
/********** subexpressionWithRange: **********/
// Test with empty range
subexpression = [testExpression subexpressionWithRange:NSMakeRange(4, 0)];
XCTAssertEqual([subexpression numberOfElements], 0);
XCTAssertEqual([subexpression countElements], 0);
// Test with start and end in first element
subexpression = [testExpression subexpressionWithRange:NSMakeRange(1, 2)];
XCTAssertEqual([subexpression numberOfElements], 1);
XCTAssertEqual([subexpression countElements], 1);
XCTAssertEqualObjects([subexpression elementAtIndex:0], @"23");
// Test with start in first and end in middle after function
subexpression = [testExpression subexpressionWithRange:NSMakeRange(2, 3)];
XCTAssertEqual([subexpression numberOfElements], 2);
XCTAssertEqual([subexpression countElements], 2);
XCTAssertEqualObjects([subexpression elementAtIndex:0], @"34");
XCTAssertEqualObjects([subexpression elementAtIndex:1], [[MPFunction alloc] init]);
// Test with start in first and end in middle after literal
subexpression = [testExpression subexpressionWithRange:NSMakeRange(2, 4)];
XCTAssertEqual([subexpression numberOfElements], 3);
XCTAssertEqual([subexpression countElements], 3);
XCTAssertEqualObjects([subexpression elementAtIndex:0], @"34");
XCTAssertEqualObjects([subexpression elementAtIndex:2], @"1");
}
@@ -211,25 +211,25 @@
MPExpression *testExpression = [[MPExpression alloc] initWithElements:@[@"1234", [[MPFunction alloc] init], @"5678", [[MPFunction alloc] init]]];
[testExpression appendElement:@"90"];
XCTAssertEqual([testExpression numberOfElements], 5);
XCTAssertEqual([testExpression countElements], 5);
// 1234 [] 5678 [] 90
[testExpression deleteElementsInRange:NSMakeRange(2, 4)];
XCTAssertEqual([testExpression numberOfElements], 3);
XCTAssertEqual([testExpression countElements], 3);
// 12678 [] 90
[testExpression deleteElementsInRange:NSMakeRange(0, 2)];
XCTAssertEqual([testExpression numberOfElements], 3);
XCTAssertEqual([testExpression countElements], 3);
XCTAssertEqualObjects([testExpression elementAtIndex:0], @"678");
[testExpression insertElement:[[MPFunction alloc] init]
atLocation:2];
XCTAssertEqual([testExpression numberOfElements], 5);
XCTAssertEqual([testExpression countElements], 5);
// 67 [] 8 [] 90
[testExpression replaceSymbolsInRange:NSMakeRange(2, 5)
withElements:@[[[MPFunction alloc] init]]];
XCTAssertEqual([testExpression numberOfElements], 2);
XCTAssertEqual([testExpression countElements], 2);
XCTAssertEqualObjects([testExpression elementAtIndex:0], @"67");
// 67 []
}