Model Redesign: Added Reference Frames
Added Inverse Functions UI Redesign Cleaned Code
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
@property (weak) IBOutlet MPExpressionView *expressionView;
|
||||
@property (weak) IBOutlet NSTextField *resultLabel;
|
||||
@property (weak) IBOutlet NSTextField *errorLabel;
|
||||
|
||||
- (IBAction)evaluateExpression:(id)sender;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
|
||||
@interface MPExpressionTokenizer : NSObject
|
||||
|
||||
- (NSArray *)tokenizeExpression:(MPExpression *)expression; // Returns MPToken's
|
||||
+ (NSArray *)tokenizeExpression:(MPExpression *)expression; // Returns MPToken's
|
||||
|
||||
@end
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -128,16 +128,6 @@
|
||||
return [NSIndexPath indexPathWithIndex:0];
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfLeadingChild
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfTrailingChild
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSIndexSet *)indexesOfRemainingChildren
|
||||
{
|
||||
return [NSIndexSet indexSetWithIndex:0];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
|
||||
@interface MPPowerFunction : MPFunction
|
||||
|
||||
@property (nonatomic, strong) MPTerm *baseTerm;
|
||||
@property (nonatomic, strong) MPExpression *exponentExpression;
|
||||
|
||||
@property (nonatomic, strong) MPTerm *baseTerm;
|
||||
|
||||
@end
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
19
MathPad/MPPowerFunctionLayout.h
Normal file
19
MathPad/MPPowerFunctionLayout.h
Normal 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
|
||||
68
MathPad/MPPowerFunctionLayout.m
Normal file
68
MathPad/MPPowerFunctionLayout.m
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user