Archived
1
This repository has been archived on 2022-08-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
mathpad/MathPad/MPExpression.m
2014-08-22 01:14:52 +02:00

444 lines
14 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)
- (NSUInteger)lengthOfElements:(NSArray *)elements;
- (NSUInteger)indexOfElementAtLocation:(NSUInteger)location;
- (void)validateElements:(NSArray *)elements;
- (BOOL)splitElementsAtLocation:(NSUInteger)location
insertionIndex:(out NSUInteger *)insertionIndex;
- (NSUInteger)calculateSplitOffsetForSplitLocation:(NSUInteger)location
inElementAtIndex:(out NSUInteger *)elementIndex;
@end
@implementation MPExpression (MPExpressionPrivate)
- (NSUInteger)lengthOfElements:(NSArray *)elements
{
NSUInteger length = 0;
for (id<MPExpressionElement> element in elements) {
length += element.length;
}
return length;
}
- (NSUInteger)indexOfElementAtLocation:(NSUInteger)location
{
NSUInteger index = 0;
[self calculateSplitOffsetForSplitLocation:location inElementAtIndex:&index];
return index;
}
- (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;
BOOL _didSplitEndOnEditing;
NSUInteger _replacementLength;
}
@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] 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 >= _editedRange.location && index < NSMaxRange(_editedRange)) {
--_replacementLength;
}
--index;
} else if ([next isString]) {
NSString *new = [NSString stringWithFormat:@"%@%@", current, next];
[self.elements replaceObjectsInRange:NSMakeRange(index, 2)
withObjectsFromArray:@[new]];
NSUInteger maxReplacementIndex = _editedRange.location + _replacementLength;
if (index == _editedRange.location - 1) {
--_editedRange.location;
++_editedRange.length;
} else if (index >= _editedRange.location && index < maxReplacementIndex - 1) {
--_replacementLength;
} else if (index == maxReplacementIndex - 1) {
if (!_didSplitEndOnEditing) {
++_editedRange.length;
}
}
--index;
}
} else {
[(MPFunction *)current setParent:self];
}
}
}
#pragma mark Primitive Methods
- (NSUInteger)length
{
if (_cachedLength == 0) {
_cachedLength = [self lengthOfElements:self.elements];
}
return _cachedLength;
}
- (NSUInteger)numberOfElements
{
return self.elements.count;
}
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx
{
[self replaceSymbolsInRange:NSMakeRange(idx, 1)
withElements:@[obj]];
}
- (id)objectAtIndexedSubscript:(NSUInteger)idx
{
return [self elementAtIndex:idx];
}
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)anIndex
{
return self.elements[anIndex];
}
- (NSArray *)elementsInRange:(NSRange)range
{
return [self.elements subarrayWithRange:range];
}
#warning If multiple equal expressions exist errors may occur...
- (NSUInteger)indexOfElement:(id<MPExpressionElement>)element
{
return [self.elements indexOfObject:element];
}
- (void)replaceSymbolsInRange:(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; // startIndex is inclusive
BOOL didSplitStart = NO;
if ([self numberOfElements] == 0) {
startIndex = 0;
} else {
didSplitStart = [self splitElementsAtLocation:range.location
insertionIndex:&startIndex];
}
NSUInteger endIndex; // endIndex is exclusive
BOOL didSplitEnd = [self splitElementsAtLocation:NSMaxRange(range)
insertionIndex:&endIndex];
// Perform the replacement
NSMutableArray *newElements = [[NSMutableArray alloc] initWithArray:elements
copyItems:YES];
[self.elements replaceObjectsInRange:NSMakeRange(startIndex, endIndex-startIndex)
withObjectsFromArray:newElements];
_cachedLength = 0;
NSUInteger editLocation = startIndex - (didSplitStart ? 1 : 0);
NSUInteger editLength = range.length > 0 ? (endIndex - startIndex) : 0;
_editedRange = NSMakeRange(editLocation, editLength);
_didSplitEndOnEditing = didSplitEnd;
_replacementLength = elements.count + (didSplitStart ? 1 : 0) + (didSplitEnd ? 1 : 0);
[self fixElements];
[self didChangeElementsInRangePath:[[MPRangePath alloc] initWithRange:_editedRange]
replacementLength:_replacementLength];
}
- (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
{
#warning Bad Implementation
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];
}
- (NSIndexPath *)indexPath
{
if (self.parent) {
NSUInteger selfIndex = [self.parent indexOfChild:self];
return [[self.parent indexPath] indexPathByAddingIndex:selfIndex];
} else {
return [[NSIndexPath alloc] init];
}
}
#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 replaceSymbolsInRange:NSMakeRange(self.length, 0) withElements:elements];
}
- (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)deleteElementsInRange:(NSRange)range
{
[self replaceSymbolsInRange:range withElements:@[]];
}
@end