- Combined MPExpression and MPMutableExpression - Abstracted children of MPExpression into MPExpressionElement protocol - Abstracted most of MPExpressionLayout and MPFunctionLayout into common superclass MPLayout
420 lines
13 KiB
Objective-C
420 lines
13 KiB
Objective-C
//
|
|
// MPExpression.m
|
|
// MathPad
|
|
//
|
|
// Created by Kim Wittenburg on 10.08.14.
|
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
|
//
|
|
|
|
#import "MPExpression.h"
|
|
#import "MPFunction.h"
|
|
#import "MPRangePath.h"
|
|
|
|
#import "NSIndexPath+MPAdditions.h"
|
|
#import "MPException.h"
|
|
|
|
@interface MPExpression ()
|
|
@property (readonly, nonatomic, strong) NSMutableArray *elements;
|
|
@end
|
|
|
|
@interface MPExpression (MPExpressionPrivate)
|
|
|
|
- (void)validateElements:(NSArray *)elements;
|
|
- (BOOL)splitElementsAtLocation:(NSUInteger)location
|
|
insertionIndex:(out NSUInteger *)insertionIndex;
|
|
- (NSUInteger)calculateSplitOffsetForSplitLocation:(NSUInteger)location
|
|
inElementAtIndex:(out NSUInteger *)elementIndex;
|
|
|
|
@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}];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (BOOL)splitElementsAtLocation:(NSUInteger)location
|
|
insertionIndex:(out NSUInteger *)insertionIndex
|
|
{
|
|
if (location == 0) {
|
|
*insertionIndex = 0;
|
|
return NO;
|
|
}
|
|
NSUInteger splitElementIndex;
|
|
NSUInteger splitOffset = [self calculateSplitOffsetForSplitLocation:location
|
|
inElementAtIndex:&splitElementIndex];
|
|
id<MPExpressionElement> splitElement = self.elements[splitElementIndex];
|
|
if (splitOffset == splitElement.length) {
|
|
splitOffset = 0;
|
|
splitElementIndex++;
|
|
}
|
|
if (splitOffset != 0) {
|
|
NSString *stringElement = (NSString *)splitElement;
|
|
NSString *leftPart = [stringElement substringToIndex:splitOffset];
|
|
NSString *rightPart = [stringElement substringFromIndex:splitOffset];
|
|
[self.elements replaceObjectsInRange:NSMakeRange(splitElementIndex, 1)
|
|
withObjectsFromArray:@[leftPart, rightPart]];
|
|
++splitElementIndex;
|
|
}
|
|
*insertionIndex = splitElementIndex;
|
|
return splitOffset != 0;
|
|
}
|
|
|
|
- (NSUInteger)calculateSplitOffsetForSplitLocation:(NSUInteger)location
|
|
inElementAtIndex:(out NSUInteger *)elementIndex
|
|
{
|
|
NSUInteger length = 0;
|
|
NSUInteger index = 0;
|
|
NSUInteger elementLength = 0;
|
|
for (id<MPExpressionElement> element in self.elements) {
|
|
elementLength = element.length;
|
|
length += elementLength;
|
|
if (length >= location) {
|
|
break;
|
|
}
|
|
++index;
|
|
}
|
|
*elementIndex = index;
|
|
return elementLength - (length - location);
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation MPExpression {
|
|
NSUInteger _cachedLength;
|
|
NSRange editedRange;
|
|
NSRange replacementRange;
|
|
}
|
|
|
|
@synthesize elements = _elements;
|
|
|
|
#pragma mark Creation Methods
|
|
- (instancetype)init
|
|
{
|
|
return [self initWithElements:@[]];
|
|
}
|
|
|
|
- (instancetype)initWithElement:(id<MPExpressionElement>)element
|
|
{
|
|
return [self initWithElements:@[element]];
|
|
}
|
|
|
|
- (instancetype)initWithElements:(NSArray *)elements
|
|
{
|
|
self = [super init];
|
|
if (self) {
|
|
_cachedLength = 0;
|
|
_elements = [[NSMutableArray alloc] initWithCapacity:elements.count];
|
|
_elements = [[NSMutableArray alloc] initWithArray:elements
|
|
copyItems:YES];
|
|
[self fixElements];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
#pragma mark Working With the Expression Tree
|
|
- (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];
|
|
if ([current isString]) {
|
|
if (current.length == 0) {
|
|
[self.elements removeObjectAtIndex:index];
|
|
if (index < replacementRange.location) {
|
|
replacementRange.location--;
|
|
} else if (index < NSMaxRange(replacementRange)-1) {
|
|
replacementRange.length--;
|
|
} else if (index == NSMaxRange(replacementRange)) {
|
|
editedRange.length++;
|
|
}
|
|
--index;
|
|
} else if ([next isString]) {
|
|
NSString *new = [NSString stringWithFormat:@"%@%@", current, next];
|
|
[self.elements replaceObjectsInRange:NSMakeRange(index, 2)
|
|
withObjectsFromArray:@[new]];
|
|
if (index < replacementRange.location) {
|
|
replacementRange.location--;
|
|
} else if (index < NSMaxRange(replacementRange)-1) {
|
|
replacementRange.length--;
|
|
} else if (index == NSMaxRange(replacementRange)-1) {
|
|
editedRange.length++;
|
|
}
|
|
--index;
|
|
}
|
|
} else {
|
|
[(MPFunction *)current setParent:self];
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma mark Primitive Methods
|
|
- (NSUInteger)length
|
|
{
|
|
if (_cachedLength == 0) {
|
|
for (id<MPExpressionElement> element in self.elements) {
|
|
_cachedLength += element.length;
|
|
}
|
|
}
|
|
return _cachedLength;
|
|
}
|
|
|
|
- (NSUInteger)numberOfElements
|
|
{
|
|
return self.elements.count;
|
|
}
|
|
|
|
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)index
|
|
{
|
|
return self.elements[index];
|
|
}
|
|
|
|
- (NSArray *)elementsInRange:(NSRange)range
|
|
{
|
|
return [self.elements subarrayWithRange:range];
|
|
}
|
|
|
|
- (NSUInteger)indexOfElement:(id<MPExpressionElement>)element
|
|
{
|
|
return [self.elements indexOfObject:element];
|
|
}
|
|
|
|
- (void)replaceElementsInRange:(NSRange)range
|
|
withElements:(NSArray *)elements
|
|
{
|
|
if (NSMaxRange(range) > self.length) {
|
|
@throw [NSException exceptionWithName:NSRangeException
|
|
reason:@"Range out of bounds of expression"
|
|
userInfo:nil];
|
|
}
|
|
[self validateElements:elements];
|
|
|
|
// Locate the position, split the elements
|
|
NSUInteger startIndex;
|
|
BOOL didSplitStart = NO;
|
|
if ([self numberOfElements] == 0) {
|
|
startIndex = 0;
|
|
} else {
|
|
didSplitStart = [self splitElementsAtLocation:range.location
|
|
insertionIndex:&startIndex];
|
|
}
|
|
NSUInteger endIndex;
|
|
BOOL didSplitEnd = [self splitElementsAtLocation:NSMaxRange(range)
|
|
insertionIndex:&endIndex];
|
|
|
|
// Perform the replacement
|
|
NSArray *newElements = [[NSArray alloc] initWithArray:elements
|
|
copyItems:YES];
|
|
[self.elements replaceObjectsInRange:NSMakeRange(startIndex, endIndex-startIndex)
|
|
withObjectsFromArray:newElements];
|
|
|
|
|
|
_cachedLength = 0;
|
|
NSUInteger editingStart = startIndex - (didSplitStart?1:0);
|
|
NSUInteger editingLength = endIndex - startIndex + (didSplitStart?1:0) + (didSplitEnd?1:0);
|
|
editedRange = NSMakeRange(editingStart, editingLength);
|
|
replacementRange = NSMakeRange(startIndex, elements.count);
|
|
[self fixElements];
|
|
MPRangePath *changePath = [[MPRangePath alloc] initWithRange:editedRange];
|
|
[self didChangeElementsInRangePath:changePath replacementLength:replacementRange.length];
|
|
}
|
|
|
|
- (double)doubleValue
|
|
{
|
|
#warning Unimplemented Method
|
|
return 0;
|
|
}
|
|
|
|
#pragma mark Notifications
|
|
- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath
|
|
replacementLength:(NSUInteger)replacementLength
|
|
{
|
|
NSUInteger selfIndex = [self.parent indexOfChild:self];
|
|
MPRangePath *newPath = rangePath.copy;
|
|
newPath.location = [newPath.location indexPathByPreceedingIndex:selfIndex];
|
|
[self.parent didChangeElementsInRangePath:newPath
|
|
replacementLength:replacementLength];
|
|
}
|
|
|
|
#pragma mark Basic NSObject Methods
|
|
|
|
- (BOOL)isEqual:(id)object
|
|
{
|
|
if (self == object) {
|
|
return YES;
|
|
}
|
|
if (object == nil) {
|
|
return NO;
|
|
}
|
|
if (![object isKindOfClass:[MPExpression class]]) {
|
|
return NO;
|
|
}
|
|
return [self isEqualToExpression:(MPExpression *)object];
|
|
}
|
|
|
|
- (BOOL)isEqualToExpression:(MPExpression *)anExpression
|
|
{
|
|
return [self.elements isEqualToArray:anExpression.elements];
|
|
}
|
|
|
|
- (NSString *)description
|
|
{
|
|
NSMutableString *description = [[NSMutableString alloc] init];
|
|
NSUInteger index = 0;
|
|
for (id element in self.elements) {
|
|
if ([element isString]) {
|
|
NSMutableString *correctedSymbol = [[element stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] mutableCopy];
|
|
// Prefix operator
|
|
if (element != self.elements[0]) {
|
|
unichar prefix = [correctedSymbol characterAtIndex:0];
|
|
if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:prefix]) {
|
|
[correctedSymbol insertString:@"*"
|
|
atIndex:0];
|
|
}
|
|
}
|
|
// Suffix operator
|
|
if (element != [self.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]]) {
|
|
[description appendFormat:@"*%@", [element description]];
|
|
} else {
|
|
[description appendString:[element description]];
|
|
}
|
|
index++;
|
|
}
|
|
return description;
|
|
}
|
|
|
|
- (NSUInteger)hash
|
|
{
|
|
return [self.elements hash];
|
|
}
|
|
|
|
#pragma mark - NSCopying
|
|
- (id)copyWithZone:(NSZone *)zone
|
|
{
|
|
MPExpression *copy = [[MPExpression allocWithZone:zone] initWithElements:self.elements];
|
|
return copy;
|
|
}
|
|
|
|
#pragma mark - NSCoding
|
|
- (id)initWithCoder:(NSCoder *)aDecoder
|
|
{
|
|
// TODO: Test Coding
|
|
return [self initWithElements:[aDecoder decodeObject]];
|
|
}
|
|
|
|
- (void)encodeWithCoder:(NSCoder *)aCoder
|
|
{
|
|
[aCoder encodeObject:self.elements];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation MPExpression (MPExpressionExtension)
|
|
|
|
#pragma mark Working With the Expression Tree
|
|
- (id)elementAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
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 indexPathByRemovingLastIndex]];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (NSArray *)elementsInRangePath:(MPRangePath *)rangePath
|
|
{
|
|
MPExpression *targetExpression = [self elementAtIndexPath:[rangePath.location indexPathByRemovingLastIndex]];
|
|
return [targetExpression elementsInRange:rangePath.rangeAtLastIndex];
|
|
}
|
|
|
|
#pragma mark Working With Expressions
|
|
- (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;
|
|
}
|
|
|
|
#pragma mark Mutating Expressions
|
|
- (void)appendElement:(id<MPExpressionElement>)anElement
|
|
{
|
|
[self appendElements:@[anElement]];
|
|
}
|
|
|
|
- (void)appendElements:(NSArray *)elements
|
|
{
|
|
[self replaceElementsInRange:NSMakeRange(self.length, 0) withElements:elements];
|
|
}
|
|
|
|
- (void)insertElement:(id<MPExpressionElement>)anElement atLocation:(NSUInteger)index
|
|
{
|
|
[self insertElements:@[anElement] atLocation:index];
|
|
}
|
|
|
|
- (void)insertElements:(NSArray *)elements atLocation:(NSUInteger)index
|
|
{
|
|
[self replaceElementsInRange:NSMakeRange(index, 0) withElements:elements];
|
|
}
|
|
|
|
- (void)deleteElementsInRange:(NSRange)range
|
|
{
|
|
[self replaceElementsInRange:range withElements:@[]];
|
|
}
|
|
|
|
#pragma mark Evaluating Expressions
|
|
- (float)floatValue
|
|
{
|
|
return (float)[self doubleValue];
|
|
}
|
|
|
|
- (int)intValue
|
|
{
|
|
return (int)[self doubleValue];
|
|
}
|
|
|
|
- (NSInteger)integerValue
|
|
{
|
|
return (NSInteger)[self doubleValue];
|
|
}
|
|
|
|
- (long long)longLongValue
|
|
{
|
|
return (long long)[self doubleValue];
|
|
}
|
|
|
|
@end
|