From 60760b8b3dc61fc22cd982f43ed9c11692071e8f Mon Sep 17 00:00:00 2001 From: Kim Wittenburg Date: Mon, 11 Aug 2014 13:57:48 +0200 Subject: [PATCH] Internal Redesign: - Combined MPExpression and MPMutableExpression - Abstracted children of MPExpression into MPExpressionElement protocol - Abstracted most of MPExpressionLayout and MPFunctionLayout into common superclass MPLayout --- MathPad.xcodeproj/project.pbxproj | 94 ++- MathPad/Base.lproj/MPDocument.xib | 29 +- MathPad/MPDocument.m | 4 +- MathPad/MPException.h | 4 +- MathPad/MPException.m | 4 +- MathPad/MPExpression.h | 132 ++-- MathPad/MPExpression.m | 855 +++++++++---------------- MathPad/MPExpressionElement.h | 24 + MathPad/MPExpressionLayout.h | 60 +- MathPad/MPExpressionLayout.m | 246 +++---- MathPad/MPExpressionStorage.h | 4 +- MathPad/MPExpressionStorage.m | 25 +- MathPad/MPExpressionView.h | 1 - MathPad/MPExpressionView.m | 26 +- MathPad/MPFunction.h | 55 +- MathPad/MPFunction.m | 184 +++--- MathPad/MPFunctionLayout.h | 64 +- MathPad/MPFunctionLayout.m | 112 +--- MathPad/MPLayout.h | 56 ++ MathPad/MPLayout.m | 159 +++++ MathPad/MPModel.h | 4 +- MathPad/MPRangePath.h | 4 + MathPad/MPRangePath.m | 3 +- MathPad/MPSumFunction.h | 1 + MathPad/MPSumFunction.m | 51 +- MathPad/MPSumFunctionLayout.m | 25 +- MathPad/NSIndexPath+MPAdditions.h | 30 + MathPad/NSIndexPath+MPAdditions.m | 37 ++ MathPad/NSString+MPExpressionElement.h | 14 + MathPad/NSString+MPExpressionElement.m | 49 ++ MathPadTests/MPExpressionTests.m | 209 +++--- 31 files changed, 1222 insertions(+), 1343 deletions(-) create mode 100644 MathPad/MPExpressionElement.h create mode 100644 MathPad/MPLayout.h create mode 100644 MathPad/MPLayout.m create mode 100644 MathPad/NSIndexPath+MPAdditions.h create mode 100644 MathPad/NSIndexPath+MPAdditions.m create mode 100644 MathPad/NSString+MPExpressionElement.h create mode 100644 MathPad/NSString+MPExpressionElement.m diff --git a/MathPad.xcodeproj/project.pbxproj b/MathPad.xcodeproj/project.pbxproj index 16ccf9f..432c096 100644 --- a/MathPad.xcodeproj/project.pbxproj +++ b/MathPad.xcodeproj/project.pbxproj @@ -9,20 +9,20 @@ /* Begin PBXBuildFile section */ 3B0F69A919028C6000817707 /* MPException.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B0F69A819028C6000817707 /* MPException.m */; }; 3B0F69AC1902A82C00817707 /* MPExpressionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B0F69AB1902A82C00817707 /* MPExpressionTests.m */; }; - 3B0F69AD1902AD2200817707 /* MPExpression.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B87E3581900857C00259938 /* MPExpression.m */; }; 3B0F69AE1902AD2800817707 /* MPException.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B0F69A819028C6000817707 /* MPException.m */; }; 3B0F69B01902AD2E00817707 /* MPFunction.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B87E35F19009D5F00259938 /* MPFunction.m */; }; + 3B528D10199417E10054DB5F /* MPExpressionLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B528D0F199417E10054DB5F /* MPExpressionLayout.m */; }; + 3B528D13199417E90054DB5F /* MPFunctionLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B528D12199417E90054DB5F /* MPFunctionLayout.m */; }; + 3B528D1619941F5B0054DB5F /* MPExpressionLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B528D0F199417E10054DB5F /* MPExpressionLayout.m */; }; + 3B528D1819941F5B0054DB5F /* MPFunctionLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B528D12199417E90054DB5F /* MPFunctionLayout.m */; }; + 3B53AD5F1997E0FB00C925C4 /* MPExpression.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BFAC38E1997B61300B3EF67 /* MPExpression.m */; }; + 3B688D9919982DF50006B4AB /* MPLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B688D9819982DF50006B4AB /* MPLayout.m */; }; 3B87E3561900856F00259938 /* MPExpressionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B87E3551900856F00259938 /* MPExpressionView.m */; }; - 3B87E3591900857C00259938 /* MPExpression.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B87E3581900857C00259938 /* MPExpression.m */; }; 3B87E36019009D5F00259938 /* MPFunction.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B87E35F19009D5F00259938 /* MPFunction.m */; }; 3BB09EB21905DE500080A5ED /* MPExpressionStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BB09EB11905DE500080A5ED /* MPExpressionStorage.m */; }; 3BB09EC91906FD830080A5ED /* MPSumFunction.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BB09EC81906FD830080A5ED /* MPSumFunction.m */; }; - 3BB09ED0190713F00080A5ED /* MPExpressionLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BB09ECF190713F00080A5ED /* MPExpressionLayout.m */; }; - 3BB09ED3190713FC0080A5ED /* MPFunctionLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BB09ED2190713FC0080A5ED /* MPFunctionLayout.m */; }; - 3BB09EDE190728220080A5ED /* NSIndexPath+MPReverseIndexPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BB09EDD190728220080A5ED /* NSIndexPath+MPReverseIndexPath.m */; }; + 3BB09EDE190728220080A5ED /* NSIndexPath+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BB09EDD190728220080A5ED /* NSIndexPath+MPAdditions.m */; }; 3BB09EE1190736160080A5ED /* MPSumFunctionLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BB09EE0190736160080A5ED /* MPSumFunctionLayout.m */; }; - 3BBBA358190327B700824E74 /* MPMutableExpressionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BBBA357190327B700824E74 /* MPMutableExpressionTests.m */; }; - 3BBBA35D1903F8A700824E74 /* NSObject+MPStringTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BBBA35C1903F8A700824E74 /* NSObject+MPStringTest.m */; }; 3BBBA35E1903FD3600824E74 /* MPRangePath.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B87E35C1900933200259938 /* MPRangePath.m */; }; 3BBBA35F1903FD3600824E74 /* MPRangePath.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B87E35C1900933200259938 /* MPRangePath.m */; }; 3BBBA3951905704200824E74 /* MPRangeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BBBA3941905704200824E74 /* MPRangeTests.m */; }; @@ -37,7 +37,8 @@ 3BF9979118DE623E009CF6C4 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BF9979018DE623E009CF6C4 /* XCTest.framework */; }; 3BF9979218DE623E009CF6C4 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BF9976E18DE623E009CF6C4 /* Cocoa.framework */; }; 3BF9979A18DE623E009CF6C4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3BF9979818DE623E009CF6C4 /* InfoPlist.strings */; }; - 3BFCFF491905AAF30001FE33 /* NSTextStorage+MPSetContents.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BFCFF481905AAF30001FE33 /* NSTextStorage+MPSetContents.m */; }; + 3BFAC38F1997B61300B3EF67 /* MPExpression.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BFAC38E1997B61300B3EF67 /* MPExpression.m */; }; + 3BFAC39C1997BC7600B3EF67 /* NSString+MPExpressionElement.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BFAC39B1997BC7600B3EF67 /* NSString+MPExpressionElement.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -54,10 +55,14 @@ 3B0F69A719028BC600817707 /* MPException.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPException.h; sourceTree = ""; }; 3B0F69A819028C6000817707 /* MPException.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPException.m; sourceTree = ""; }; 3B0F69AB1902A82C00817707 /* MPExpressionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPExpressionTests.m; sourceTree = ""; }; + 3B528D0D199417740054DB5F /* MPLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLayout.h; sourceTree = ""; }; + 3B528D0E199417E10054DB5F /* MPExpressionLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPExpressionLayout.h; sourceTree = ""; }; + 3B528D0F199417E10054DB5F /* MPExpressionLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPExpressionLayout.m; sourceTree = ""; }; + 3B528D11199417E90054DB5F /* MPFunctionLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPFunctionLayout.h; sourceTree = ""; }; + 3B528D12199417E90054DB5F /* MPFunctionLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPFunctionLayout.m; sourceTree = ""; }; + 3B688D9819982DF50006B4AB /* MPLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPLayout.m; sourceTree = ""; }; 3B87E3541900856F00259938 /* MPExpressionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPExpressionView.h; sourceTree = ""; }; 3B87E3551900856F00259938 /* MPExpressionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPExpressionView.m; sourceTree = ""; }; - 3B87E3571900857C00259938 /* MPExpression.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPExpression.h; sourceTree = ""; }; - 3B87E3581900857C00259938 /* MPExpression.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPExpression.m; sourceTree = ""; }; 3B87E35B1900933200259938 /* MPRangePath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPRangePath.h; sourceTree = ""; }; 3B87E35C1900933200259938 /* MPRangePath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRangePath.m; sourceTree = ""; }; 3B87E35E19009D5F00259938 /* MPFunction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPFunction.h; sourceTree = ""; }; @@ -66,18 +71,11 @@ 3BB09EB11905DE500080A5ED /* MPExpressionStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPExpressionStorage.m; sourceTree = ""; }; 3BB09EC71906FD830080A5ED /* MPSumFunction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSumFunction.h; sourceTree = ""; }; 3BB09EC81906FD830080A5ED /* MPSumFunction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSumFunction.m; sourceTree = ""; }; - 3BB09ECE190713F00080A5ED /* MPExpressionLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPExpressionLayout.h; sourceTree = ""; }; - 3BB09ECF190713F00080A5ED /* MPExpressionLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPExpressionLayout.m; sourceTree = ""; }; - 3BB09ED1190713FC0080A5ED /* MPFunctionLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPFunctionLayout.h; sourceTree = ""; }; - 3BB09ED2190713FC0080A5ED /* MPFunctionLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPFunctionLayout.m; sourceTree = ""; }; - 3BB09EDC190728220080A5ED /* NSIndexPath+MPReverseIndexPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSIndexPath+MPReverseIndexPath.h"; sourceTree = ""; }; - 3BB09EDD190728220080A5ED /* NSIndexPath+MPReverseIndexPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSIndexPath+MPReverseIndexPath.m"; sourceTree = ""; }; + 3BB09EDC190728220080A5ED /* NSIndexPath+MPAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSIndexPath+MPAdditions.h"; sourceTree = ""; }; + 3BB09EDD190728220080A5ED /* NSIndexPath+MPAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSIndexPath+MPAdditions.m"; sourceTree = ""; }; 3BB09EDF190736160080A5ED /* MPSumFunctionLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSumFunctionLayout.h; sourceTree = ""; }; 3BB09EE0190736160080A5ED /* MPSumFunctionLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSumFunctionLayout.m; sourceTree = ""; }; - 3BBBA357190327B700824E74 /* MPMutableExpressionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMutableExpressionTests.m; sourceTree = ""; }; 3BBBA3591903EA9B00824E74 /* MPModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPModel.h; sourceTree = ""; }; - 3BBBA35B1903F8A700824E74 /* NSObject+MPStringTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+MPStringTest.h"; sourceTree = ""; }; - 3BBBA35C1903F8A700824E74 /* NSObject+MPStringTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+MPStringTest.m"; sourceTree = ""; }; 3BBBA38419047FC900824E74 /* MPView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPView.h; sourceTree = ""; }; 3BBBA3941905704200824E74 /* MPRangeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRangeTests.m; sourceTree = ""; }; 3BF9976B18DE623E009CF6C4 /* MathPad.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MathPad.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -99,8 +97,11 @@ 3BF9979018DE623E009CF6C4 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 3BF9979718DE623E009CF6C4 /* MathPadTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "MathPadTests-Info.plist"; sourceTree = ""; }; 3BF9979918DE623E009CF6C4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; - 3BFCFF471905AAF30001FE33 /* NSTextStorage+MPSetContents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSTextStorage+MPSetContents.h"; sourceTree = ""; }; - 3BFCFF481905AAF30001FE33 /* NSTextStorage+MPSetContents.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSTextStorage+MPSetContents.m"; sourceTree = ""; }; + 3BFAC38D1997B61300B3EF67 /* MPExpression.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPExpression.h; sourceTree = ""; }; + 3BFAC38E1997B61300B3EF67 /* MPExpression.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPExpression.m; sourceTree = ""; }; + 3BFAC3961997B67400B3EF67 /* MPExpressionElement.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPExpressionElement.h; sourceTree = ""; }; + 3BFAC39A1997BC7600B3EF67 /* NSString+MPExpressionElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+MPExpressionElement.h"; sourceTree = ""; }; + 3BFAC39B1997BC7600B3EF67 /* NSString+MPExpressionElement.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+MPExpressionElement.m"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -127,14 +128,18 @@ 3B87E350190082B300259938 /* Model */ = { isa = PBXGroup; children = ( - 3BBBA35A1903F89100824E74 /* Categories */, 3BBBA3591903EA9B00824E74 /* MPModel.h */, - 3B87E3571900857C00259938 /* MPExpression.h */, - 3B87E3581900857C00259938 /* MPExpression.m */, + 3BFAC38D1997B61300B3EF67 /* MPExpression.h */, + 3BFAC38E1997B61300B3EF67 /* MPExpression.m */, + 3BFAC3961997B67400B3EF67 /* MPExpressionElement.h */, 3B87E35E19009D5F00259938 /* MPFunction.h */, 3B87E35F19009D5F00259938 /* MPFunction.m */, + 3BFAC39A1997BC7600B3EF67 /* NSString+MPExpressionElement.h */, + 3BFAC39B1997BC7600B3EF67 /* NSString+MPExpressionElement.m */, 3B87E35B1900933200259938 /* MPRangePath.h */, 3B87E35C1900933200259938 /* MPRangePath.m */, + 3BB09EDC190728220080A5ED /* NSIndexPath+MPAdditions.h */, + 3BB09EDD190728220080A5ED /* NSIndexPath+MPAdditions.m */, 3B0F69A719028BC600817707 /* MPException.h */, 3B0F69A819028C6000817707 /* MPException.m */, 3BB09EBC1905EF210080A5ED /* Functions */, @@ -194,10 +199,12 @@ 3BB09ED819071C270080A5ED /* Private */ = { isa = PBXGroup; children = ( - 3BB09ECE190713F00080A5ED /* MPExpressionLayout.h */, - 3BB09ECF190713F00080A5ED /* MPExpressionLayout.m */, - 3BB09ED1190713FC0080A5ED /* MPFunctionLayout.h */, - 3BB09ED2190713FC0080A5ED /* MPFunctionLayout.m */, + 3B528D0D199417740054DB5F /* MPLayout.h */, + 3B688D9819982DF50006B4AB /* MPLayout.m */, + 3B528D0E199417E10054DB5F /* MPExpressionLayout.h */, + 3B528D0F199417E10054DB5F /* MPExpressionLayout.m */, + 3B528D11199417E90054DB5F /* MPFunctionLayout.h */, + 3B528D12199417E90054DB5F /* MPFunctionLayout.m */, ); name = Private; sourceTree = ""; @@ -211,19 +218,6 @@ name = "Function Layouts"; sourceTree = ""; }; - 3BBBA35A1903F89100824E74 /* Categories */ = { - isa = PBXGroup; - children = ( - 3BBBA35B1903F8A700824E74 /* NSObject+MPStringTest.h */, - 3BBBA35C1903F8A700824E74 /* NSObject+MPStringTest.m */, - 3BFCFF471905AAF30001FE33 /* NSTextStorage+MPSetContents.h */, - 3BFCFF481905AAF30001FE33 /* NSTextStorage+MPSetContents.m */, - 3BB09EDC190728220080A5ED /* NSIndexPath+MPReverseIndexPath.h */, - 3BB09EDD190728220080A5ED /* NSIndexPath+MPReverseIndexPath.m */, - ); - name = Categories; - sourceTree = ""; - }; 3BF9976218DE623E009CF6C4 = { isa = PBXGroup; children = ( @@ -292,7 +286,6 @@ isa = PBXGroup; children = ( 3B0F69AB1902A82C00817707 /* MPExpressionTests.m */, - 3BBBA357190327B700824E74 /* MPMutableExpressionTests.m */, 3BBBA3941905704200824E74 /* MPRangeTests.m */, 3BF9979618DE623E009CF6C4 /* Supporting Files */, ); @@ -408,17 +401,17 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3B688D9919982DF50006B4AB /* MPLayout.m in Sources */, 3BB09EE1190736160080A5ED /* MPSumFunctionLayout.m in Sources */, 3B87E3561900856F00259938 /* MPExpressionView.m in Sources */, - 3BFCFF491905AAF30001FE33 /* NSTextStorage+MPSetContents.m in Sources */, 3BB09EC91906FD830080A5ED /* MPSumFunction.m in Sources */, 3BB09EB21905DE500080A5ED /* MPExpressionStorage.m in Sources */, 3BF9977B18DE623E009CF6C4 /* main.m in Sources */, - 3B87E3591900857C00259938 /* MPExpression.m in Sources */, - 3BB09ED3190713FC0080A5ED /* MPFunctionLayout.m in Sources */, - 3BB09ED0190713F00080A5ED /* MPExpressionLayout.m in Sources */, - 3BBBA35D1903F8A700824E74 /* NSObject+MPStringTest.m in Sources */, - 3BB09EDE190728220080A5ED /* NSIndexPath+MPReverseIndexPath.m in Sources */, + 3BFAC38F1997B61300B3EF67 /* MPExpression.m in Sources */, + 3BFAC39C1997BC7600B3EF67 /* NSString+MPExpressionElement.m in Sources */, + 3B528D10199417E10054DB5F /* MPExpressionLayout.m in Sources */, + 3B528D13199417E90054DB5F /* MPFunctionLayout.m in Sources */, + 3BB09EDE190728220080A5ED /* NSIndexPath+MPAdditions.m in Sources */, 3B87E36019009D5F00259938 /* MPFunction.m in Sources */, 3B0F69A919028C6000817707 /* MPException.m in Sources */, 3BBBA35E1903FD3600824E74 /* MPRangePath.m in Sources */, @@ -430,13 +423,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3BBBA358190327B700824E74 /* MPMutableExpressionTests.m in Sources */, + 3B528D1619941F5B0054DB5F /* MPExpressionLayout.m in Sources */, + 3B528D1819941F5B0054DB5F /* MPFunctionLayout.m in Sources */, 3B0F69AE1902AD2800817707 /* MPException.m in Sources */, 3B0F69AC1902A82C00817707 /* MPExpressionTests.m in Sources */, 3BBBA3951905704200824E74 /* MPRangeTests.m in Sources */, 3B0F69B01902AD2E00817707 /* MPFunction.m in Sources */, + 3B53AD5F1997E0FB00C925C4 /* MPExpression.m in Sources */, 3BBBA35F1903FD3600824E74 /* MPRangePath.m in Sources */, - 3B0F69AD1902AD2200817707 /* MPExpression.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/MathPad/Base.lproj/MPDocument.xib b/MathPad/Base.lproj/MPDocument.xib index 1600811..0359d18 100644 --- a/MathPad/Base.lproj/MPDocument.xib +++ b/MathPad/Base.lproj/MPDocument.xib @@ -1,5 +1,5 @@ - + @@ -7,7 +7,6 @@ - @@ -17,22 +16,18 @@ - + - + - - - - - - - - - - - - - + + + + + + diff --git a/MathPad/MPDocument.m b/MathPad/MPDocument.m index 6e86a3c..9ed8e20 100644 --- a/MathPad/MPDocument.m +++ b/MathPad/MPDocument.m @@ -59,9 +59,9 @@ } #pragma mark Actions - - (IBAction)changeExpression:(id)sender { - [self.resultExpressionView.expressionStorage insertString:@"abc" atIndex:6]; + [self.resultExpressionView.expressionStorage insertElement:@"abc" atLocation:6]; + self.resultExpressionView.needsDisplay = YES; } @end diff --git a/MathPad/MPException.h b/MathPad/MPException.h index eee7df0..5f23aca 100644 --- a/MathPad/MPException.h +++ b/MathPad/MPException.h @@ -9,7 +9,7 @@ #ifndef MathPad_MPException_h #define MathPad_MPException_h -extern NSString *MPIllegalSymbolException; -extern NSString *MPIllegalSymbolExceptionSymbolKey; +extern NSString *MPIllegalElementException; +extern NSString *MPIllegalElementExceptionElementKey; #endif diff --git a/MathPad/MPException.m b/MathPad/MPException.m index 8ce8ea9..8c71821 100644 --- a/MathPad/MPException.m +++ b/MathPad/MPException.m @@ -8,5 +8,5 @@ #import "MPException.h" -NSString *MPIllegalSymbolException = @"MPIllegalSymbolException"; -NSString *MPIllegalSymbolExceptionSymbolKey = @"MPIllegalSymbolExceptionSymbolKey"; \ No newline at end of file +NSString *MPIllegalElementException = @"MPIllegalSymbolException"; +NSString *MPIllegalElementExceptionElementKey = @"MPIllegalSymbolExceptionSymbolKey"; \ No newline at end of file diff --git a/MathPad/MPExpression.h b/MathPad/MPExpression.h index 7134cb6..f0433c4 100644 --- a/MathPad/MPExpression.h +++ b/MathPad/MPExpression.h @@ -2,124 +2,82 @@ // MPExpression.h // MathPad // -// Created by Kim Wittenburg on 17.04.14. +// Created by Kim Wittenburg on 10.08.14. // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // +@import Foundation; +#import "NSString+MPExpressionElement.h" + @class MPExpression, MPFunction, MPRangePath; +@protocol MPExpressionElement; -extern NSString *MPAdditionOperator; -extern NSString *MPSubtractionOperator; -extern NSString *MPMultiplicationOperator; -extern NSString *MPDivisionOperator; - -@interface MPExpression : NSObject +@interface MPExpression : NSObject #pragma mark Creation Methods - -- (instancetype)initWithSymbols:(NSArray *)symbols; +- (instancetype)init; // Convenience +- (instancetype)initWithElement:(id)element; // Convenience +- (instancetype)initWithElements:(NSArray *)elements; // Designated Initializer #pragma mark Working With the Expression Tree +@property (nonatomic, weak) MPFunction *parent; // Set automatically, nil for root expression -@property (nonatomic, weak) MPFunction *parent; // Documentation: Do not set, may be nil - -- (void)fixSymbols; +- (void)fixElements; // Called automatically, removes empty elements, joins subsequent strings #pragma mark Primitive Methods - -- (NSUInteger)numberOfSymbols; -- (id)symbolAtIndex:(NSUInteger)index; // Either an NSString or a MPFunction (which can be mutated) +- (NSUInteger)length; +- (NSUInteger)numberOfElements; +- (id)elementAtIndex:(NSUInteger)index; +- (NSArray *)elementsInRange:(NSRange)range; +- (NSUInteger)indexOfElement:(id)element; +- (void)replaceElementsInRange:(NSRange)range withElements:(NSArray *)elements; +// TODO: - (NSUInteger)indexOfElementAtLocation:(NSUInteger)location; - (double)doubleValue; // Evaluates Expression +#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 +// TODO: More notifications +- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath + replacementLength:(NSUInteger)replacementLength; + +#pragma mark Basic NSObject Methods +- (BOOL)isEqualToExpression:(MPExpression *)anExpression; + +- (NSString *)description; +- (NSUInteger)hash; + @end -@interface MPExpression (MPExpressionExtensionMethods) - -+ (NSArray *)operators; - -#pragma mark Creation Methods - -- (id)init; -- (instancetype)initWithString:(NSString *)aString; -- (instancetype)initWithFunction:(MPFunction *)aFunction; - -+ (instancetype)expression; -+ (instancetype)expressionWithString:(NSString *)aString; -+ (instancetype)expressionWithFunction:(MPFunction *)aFunction; -+ (instancetype)expressionWithSymbols:(NSArray *)symbols; +@interface MPExpression (MPExpressionExtension) #pragma mark Working With the Expression Tree - -- (NSUInteger)indexOfSymbol:(id)symbol; -- (id)symbolAtIndexPath:(NSIndexPath *)indexPath; // May also return MPExpression +- (id)elementAtIndexPath:(NSIndexPath *)indexPath; // Returns an MPExpression or id +- (NSArray *)elementsInRangePath:(MPRangePath *)rangePath; #pragma mark Working With Expressions -- (NSUInteger)length; - -- (MPExpression *)subexpressionFromIndex:(NSUInteger)from; -- (MPExpression *)subexpressionToIndex:(NSUInteger)to; +- (MPExpression *)subexpressionFromLocation:(NSUInteger)from; +- (MPExpression *)subexpressionToLocation:(NSUInteger)to; - (MPExpression *)subexpressionWithRange:(NSRange)range; -- (BOOL)isEqualToExpression:(MPExpression *)anExpression; -- (MPExpression *)expressionByAppendingString:(NSString *)aString; -- (MPExpression *)expressionByAppendingFunction:(MPFunction *)aFunction; -- (MPExpression *)expressionByAppendingExpression:(MPExpression *)anExpression; -- (MPExpression *)expressionByAppendingSymbols:(NSArray *)symbols; +#pragma mark Mutating Expressions +- (void)appendElement:(id)anElement; +- (void)appendElements:(NSArray *)elements; + +- (void)insertElement:(id)anElement atLocation:(NSUInteger)index; +- (void)insertElements:(NSArray *)elements atLocation:(NSUInteger)index; + +- (void)deleteElementsInRange:(NSRange)range; #pragma mark Evaluating Expressions - - (float)floatValue; - (int)intValue; - (NSInteger)integerValue; - (long long)longLongValue; -#pragma mark Querying an Expression's Contents - -- (NSArray *)symbols; - -- (NSString *)description; - -- (NSUInteger)hash; - -@end - -@interface MPExpression (MPChangeNotificationExtension) - -- (void)symbolsChangedInRangePath:(MPRangePath *)rangePath replacementLength:(NSUInteger)length; - -@end - -@interface MPMutableExpression : MPExpression - -- (void)replaceSymbolsInRange:(NSRange)range - withSymbols:(NSArray *)symbols; - -@end - -@interface MPMutableExpression (MPMutableExpressionExtensionMethods) - -- (void)insertString:(NSString *)aString - atIndex:(NSUInteger)loc; -- (void)insertFunction:(MPFunction *)aFunction - atIndex:(NSUInteger)loc; -- (void)insertExpression:(MPExpression *)anExpression - atIndex:(NSUInteger)loc; -- (void)insertSymbols:(NSArray *)symbols - atIndex:(NSUInteger)loc; - -- (void)deleteSymbolsInRange:(NSRange)range; - -- (void)appendString:(NSString *)aString; -- (void)appendFunction:(MPFunction *)aFunction; -- (void)appendExpression:(MPExpression *)anExpression; -- (void)appendSymbols:(NSArray *)symbols; - -- (void)setString:(NSString *)aString; -- (void)setFunction:(MPFunction *)aFunction; -- (void)setExpression:(MPExpression *)anExpression; -- (void)setSymbols:(NSArray *)symbols; +#pragma mark Querying Expressions +- (NSArray *)elements; @end diff --git a/MathPad/MPExpression.m b/MathPad/MPExpression.m index 9045d25..7476c66 100644 --- a/MathPad/MPExpression.m +++ b/MathPad/MPExpression.m @@ -2,88 +2,228 @@ // MPExpression.m // MathPad // -// Created by Kim Wittenburg on 17.04.14. +// Created by Kim Wittenburg on 10.08.14. // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // #import "MPExpression.h" #import "MPFunction.h" -#import "MPException.h" #import "MPRangePath.h" -#import "NSObject+MPStringTest.h" -#import "NSIndexPath+MPReverseIndexPath.h" +#import "NSIndexPath+MPAdditions.h" +#import "MPException.h" -NSString *MPAdditionOperator = @"+"; -NSString *MPSubtractionOperator = @"-"; -NSString *MPMultiplicationOperator = @"*"; -NSString *MPDivisionOperator = @"/"; +@interface MPExpression () +@property (readonly, nonatomic, strong) NSMutableArray *elements; +@end @interface MPExpression (MPExpressionPrivate) -- (NSInteger)lengthOfSymbol:(id)symbol; -- (void)validateSymbols:(NSArray *)symbols; -- (void)getSplitOffset:(out NSUInteger *)offset - inSymbolAtIndex:(out NSUInteger *)symbolIndex - forSplitLocation:(NSUInteger)loc; +- (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 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 element in self.elements) { + elementLength = element.length; + length += elementLength; + if (length >= location) { + break; + } + ++index; + } + *elementIndex = index; + return elementLength - (length - location); +} @end @implementation MPExpression { - @package - __strong NSArray *_symbols; - NSInteger _length; + NSUInteger _cachedLength; + NSRange editedRange; + NSRange replacementRange; } -#pragma mark Creation Methods +@synthesize elements = _elements; -- (instancetype)initWithSymbols:(NSArray *)symbols +#pragma mark Creation Methods +- (instancetype)init +{ + return [self initWithElements:@[]]; +} + +- (instancetype)initWithElement:(id)element +{ + return [self initWithElements:@[element]]; +} + +- (instancetype)initWithElements:(NSArray *)elements { - [self validateSymbols:symbols]; self = [super init]; if (self) { - _symbols = [[NSArray alloc] initWithArray:symbols - copyItems:YES]; - [self fixSymbols]; + _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)fixSymbols +- (void)fixElements { - NSMutableArray *mutableSymbols = [_symbols mutableCopy]; - for (NSInteger index = 0; index < mutableSymbols.count; index++) { - id next = index+1 < mutableSymbols.count ? mutableSymbols[index+1] : nil; - id current = mutableSymbols[index]; + for (NSUInteger index = 0; index < self.elements.count; index++) { + id next = index+1 < self.elements.count ? self.elements[index+1] :nil; + id current = self.elements[index]; if ([current isString]) { - if ([current length] == 0) { - [mutableSymbols removeObjectAtIndex:index]; - index--; + 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]; - [mutableSymbols replaceObjectAtIndex:index withObject:new]; - [mutableSymbols removeObjectAtIndex:index+1]; - index--; + [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]; } } - _symbols = [mutableSymbols copy]; } #pragma mark Primitive Methods - -- (NSUInteger)numberOfSymbols +- (NSUInteger)length { - return [_symbols count]; + if (_cachedLength == 0) { + for (id element in self.elements) { + _cachedLength += element.length; + } + } + return _cachedLength; } -- (id)symbolAtIndex:(NSUInteger)index +- (NSUInteger)numberOfElements { - return _symbols[index]; + return self.elements.count; +} + +- (id)elementAtIndex:(NSUInteger)index +{ + return self.elements[index]; +} + +- (NSArray *)elementsInRange:(NSRange)range +{ + return [self.elements subarrayWithRange:range]; +} + +- (NSUInteger)indexOfElement:(id)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 @@ -92,218 +232,18 @@ NSString *MPDivisionOperator = @"/"; return 0; } -#pragma mark - NSCopying - -- (id)copyWithZone:(NSZone *)zone +#pragma mark Notifications +- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath + replacementLength:(NSUInteger)replacementLength { - MPExpression *copy = [[MPExpression allocWithZone:zone] initWithSymbols:_symbols]; - return copy; + NSUInteger selfIndex = [self.parent indexOfChild:self]; + MPRangePath *newPath = rangePath.copy; + newPath.location = [newPath.location indexPathByPreceedingIndex:selfIndex]; + [self.parent didChangeElementsInRangePath:newPath + replacementLength:replacementLength]; } -#pragma mark - NSMutableCopying - -- (id)mutableCopyWithZone:(NSZone *)zone -{ - MPMutableExpression *mutableCopy = [[MPMutableExpression allocWithZone:zone] initWithSymbols:_symbols]; - return mutableCopy; -} - -#pragma mark - NSCoding - -- (id)initWithCoder:(NSCoder *)aDecoder -{ - // TODO: Test Coding - return [self initWithSymbols:[aDecoder decodeObject]]; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder -{ - [aCoder encodeObject:_symbols]; -} - -@end - -@implementation MPExpression (MPExpressionPrivate) - -- (NSInteger)lengthOfSymbol:(id)symbol -{ - if ([symbol isString]) { - return [symbol length]; - } - return 1; -} - -- (void)validateSymbols:(NSArray *)symbols -{ - for (id symbol in symbols) { - if (!([symbol isString] - || [symbol isKindOfClass:[MPFunction class]])) { - @throw [NSException exceptionWithName:MPIllegalSymbolException - reason:@"Only NSString and MPFunction objects are valid symbols." - userInfo:@{MPIllegalSymbolExceptionSymbolKey: symbol}]; - } - } -} - -- (void)getSplitOffset:(out NSUInteger *)offset - inSymbolAtIndex:(out NSUInteger *)symbolIndex - forSplitLocation:(NSUInteger)loc -{ - NSUInteger length = 0; - NSUInteger index = 0; - NSUInteger symbolLength = 0; - for (id symbol in _symbols) { - symbolLength = [self lengthOfSymbol:symbol]; - length += symbolLength; - if (length >= loc) { - break; - } - index++; - } - *offset = symbolLength - (length - loc); - *symbolIndex = index; -} - -@end - -@implementation MPExpression (MPExpressionExtensionMethods) - -+ (NSArray *)operators -{ - return @[MPAdditionOperator, MPSubtractionOperator, MPMultiplicationOperator, MPDivisionOperator]; -} - -#pragma mark Creation Methods - -- (instancetype)init -{ - return [self initWithSymbols:@[]]; -} - -- (instancetype)initWithString:(NSString *)aString -{ - return [self initWithSymbols:@[aString]]; -} - -- (instancetype)initWithFunction:(MPFunction *)aFunction -{ - return [self initWithSymbols:@[aFunction]]; -} - -+ (instancetype)expression -{ - return [[self alloc] init]; -} - -+ (instancetype)expressionWithString:(NSString *)aString -{ - return [[self alloc] initWithString:aString]; -} - -+ (instancetype)expressionWithFunction:(MPFunction *)aFunction -{ - return [[self alloc] initWithFunction:aFunction]; -} - -+ (instancetype)expressionWithSymbols:(NSArray *)symbols -{ - return [[self alloc] initWithSymbols:symbols]; -} - -#pragma mark Working With the Expression Tree - -- (NSUInteger)indexOfSymbol:(id)symbol -{ - return [_symbols indexOfObject:symbol]; -} - -- (id)symbolAtIndexPath:(NSIndexPath *)indexPath -{ - if (indexPath.length == 0) { - return self; - } - id symbol = [self symbolAtIndex:[indexPath indexAtPosition:0]]; - if (indexPath.length == 1) { - return symbol; - } - if ([symbol isKindOfClass:[MPFunction class]]) { - return [symbol symbolAtIndexPath:[indexPath indexPathByRemovingFirstIndex]]; - } - return nil; -} - -#pragma mark Working With Expressions - -- (NSUInteger)length -{ - if (_length == 0) { - for (id symbol in _symbols) { - _length += [self lengthOfSymbol:symbol]; - } - } - return _length; -} - -- (MPExpression *)subexpressionFromIndex:(NSUInteger)from -{ - return [self subexpressionWithRange:NSMakeRange(from, [self length] - from)]; -} - -- (MPExpression *)subexpressionToIndex:(NSUInteger)to -{ - return [self subexpressionWithRange:NSMakeRange(0, to)]; -} - -- (MPExpression *)subexpressionWithRange:(NSRange)range -{ - if (NSMaxRange(range) > self.length) { - @throw [NSException exceptionWithName:NSRangeException - reason:@"Range outside bounds of expression." - userInfo:nil]; - } - if (range.location == self.length || NSMaxRange(range) == 0 || range.length == 0) { - // Speed this up - return [[MPExpression alloc] init]; - } - NSUInteger startOffset; - NSUInteger startSymbolIndex; - [self getSplitOffset:&startOffset - inSymbolAtIndex:&startSymbolIndex - forSplitLocation:range.location]; - id startSymbol = _symbols[startSymbolIndex]; - if (startOffset == [self lengthOfSymbol:startSymbol]) { - startOffset = 0; - startSymbolIndex++; - startSymbol = _symbols[startSymbolIndex]; - } else if ([startSymbol isString]) { - startSymbol = [startSymbol substringFromIndex:startOffset]; - } - NSUInteger endOffset; - NSUInteger endSymbolIndex; - [self getSplitOffset:&endOffset - inSymbolAtIndex:&endSymbolIndex - forSplitLocation:NSMaxRange(range)]; - id endSymbol = _symbols[endSymbolIndex]; - if ([endSymbol isString]) { - endSymbol = [endSymbol substringToIndex:endOffset]; - - } - - NSMutableArray *symbols = [[NSMutableArray alloc] initWithCapacity:endSymbolIndex-startSymbolIndex+1]; - [symbols addObject:startSymbol]; - if (endSymbolIndex > startSymbolIndex + 1) { - NSInteger restLength = endSymbolIndex - startSymbolIndex - 1; - [symbols addObjectsFromArray:[_symbols subarrayWithRange:NSMakeRange(startSymbolIndex+1, restLength)]]; - } - if (endSymbolIndex > startSymbolIndex) { - [symbols addObject:endSymbol]; - } else if (endSymbolIndex == startSymbolIndex && [startSymbol isString]) { - NSString *result = [_symbols[startSymbolIndex] substringWithRange:NSMakeRange(startOffset, endOffset-startOffset)]; - [symbols replaceObjectAtIndex:0 - withObject:result]; - } - return [[MPExpression alloc] initWithSymbols:symbols]; -} +#pragma mark Basic NSObject Methods - (BOOL)isEqual:(id)object { @@ -321,32 +261,141 @@ NSString *MPDivisionOperator = @"/"; - (BOOL)isEqualToExpression:(MPExpression *)anExpression { - // TODO: Use ->_symbols or .symbols - return [_symbols isEqualToArray:anExpression->_symbols]; + return [self.elements isEqualToArray:anExpression.elements]; } -- (MPExpression *)expressionByAppendingString:(NSString *)aString +- (NSString *)description { - return [self expressionByAppendingSymbols:@[aString]]; + 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; } -- (MPExpression *)expressionByAppendingFunction:(MPFunction *)aFunction +- (NSUInteger)hash { - return [self expressionByAppendingSymbols:@[aFunction]]; + return [self.elements hash]; } -- (MPExpression *)expressionByAppendingExpression:(MPExpression *)anExpression +#pragma mark - NSCopying +- (id)copyWithZone:(NSZone *)zone { - return [self expressionByAppendingSymbols:anExpression.symbols]; + MPExpression *copy = [[MPExpression allocWithZone:zone] initWithElements:self.elements]; + return copy; } -- (MPExpression *)expressionByAppendingSymbols:(NSArray *)symbols +#pragma mark - NSCoding +- (id)initWithCoder:(NSCoder *)aDecoder { - return [[MPExpression alloc] initWithSymbols:[_symbols arrayByAddingObjectsFromArray:symbols]]; + // 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 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)anElement +{ + [self appendElements:@[anElement]]; +} + +- (void)appendElements:(NSArray *)elements +{ + [self replaceElementsInRange:NSMakeRange(self.length, 0) withElements:elements]; +} + +- (void)insertElement:(id)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]; @@ -367,290 +416,4 @@ NSString *MPDivisionOperator = @"/"; return (long long)[self doubleValue]; } -#pragma mark Querying an Expression's Contents - -- (NSArray *)symbols -{ - // _symbols is immutable so it is ok to just return it instead of making a copy - return _symbols; -} - -- (NSString *)description -{ - NSMutableString *description = [[NSMutableString alloc] init]; - NSUInteger index = 0; - for (id symbol in _symbols) { - if ([symbol isString]) { - NSMutableString *correctedSymbol = [[symbol stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] mutableCopy]; - // Prefix operator - if (symbol != _symbols[0]) { - unichar prefix = [correctedSymbol characterAtIndex:0]; - if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:prefix]) { - [correctedSymbol insertString:@"*" - atIndex:0]; - } - } - // Suffix operator - if (symbol != [_symbols lastObject]) { - unichar suffix = [correctedSymbol characterAtIndex:correctedSymbol.length-1]; - if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:suffix]) { - [correctedSymbol appendString:@"*"]; - } - } - [description appendString:correctedSymbol]; - } else if (index > 0 && [_symbols[index-1] isKindOfClass:[MPFunction class]]) { - [description appendFormat:@"*%@", [symbol description]]; - } else { - [description appendString:[symbol description]]; - } - index++; - } - return description; -} - -- (NSUInteger)hash -{ - return [_symbols hash]; -} - @end - -@implementation MPExpression (MPChangeNotificationExtension) - -- (void)symbolsChangedInRangePath:(MPRangePath *)rangePath replacementLength:(NSUInteger)length -{ - if (!self.parent) { - return; - } - NSUInteger selfIndex = [self.parent indexOfChild:self]; - NSIndexPath *newLocation = [rangePath.location indexPathByPrecedingIndex:selfIndex]; - MPRangePath *newPath = [[MPRangePath alloc] initWithLocation:newLocation length:rangePath.length]; - [self.parent symbolsChangedInRangePath:newPath replacementLength:length]; -} - -@end - -@implementation MPMutableExpression { - NSRange editedRange; - NSRange replacementRange; -} - -- (instancetype)initWithSymbols:(NSArray *)symbols -{ - [self validateSymbols:symbols]; - self = [super initWithSymbols:nil]; - if (self) { - editedRange = NSMakeRange(0, 0); - replacementRange = NSMakeRange(0, 0); - _symbols = [[NSMutableArray alloc] initWithArray:symbols - copyItems:YES]; - [self fixSymbols]; - } - return self; -} - -- (void)fixSymbols -{ - NSMutableArray *mutableSymbols = (NSMutableArray *)_symbols; - for (NSInteger index = 0; index < mutableSymbols.count; index++) { - id next = index+1 < mutableSymbols.count ? mutableSymbols[index+1] : nil; - id current = mutableSymbols[index]; - if ([current isString]) { - if ([current length] == 0) { - [mutableSymbols 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]; - [mutableSymbols replaceObjectAtIndex:index withObject:new]; - [mutableSymbols removeObjectAtIndex:index+1]; - 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]; - } - } -} - -- (NSArray *)symbols -{ - // Return an immutable array: - return [_symbols copy]; -} - -- (void)replaceSymbolsInRange:(NSRange)range - withSymbols:(NSArray *)symbols -{ - if (NSMaxRange(range) > self.length) { - @throw [NSException exceptionWithName:NSRangeException - reason:@"Range out of bounds of expression." - userInfo:nil]; - } - [self validateSymbols:symbols]; - - // Locate the position, split the symbols - NSUInteger startIndex; - BOOL didSplitStart = NO; - if ([self numberOfSymbols] == 0) { - startIndex = 0; - } else { - [self splitSymbolsAtLocation:range.location - insertionIndex:&startIndex - didSplit:&didSplitStart]; - } - - // Perform the deletion - NSUInteger endIndex; - BOOL didSplitEnd = NO; - [self splitSymbolsAtLocation:NSMaxRange(range) - insertionIndex:&endIndex - didSplit:&didSplitEnd]; - if (range.length > 0) { - NSMutableIndexSet *indexes = [[NSMutableIndexSet alloc] init]; - for (NSUInteger index = startIndex; index < endIndex; index++) { - [indexes addIndex:index]; - } - // TODO: Replace with removeObjectsInRange: - [(NSMutableArray *)_symbols removeObjectsAtIndexes:indexes]; - } - - // Perform the insertion - if (symbols.count > 0) { - NSArray *newSymbols = [[NSArray alloc] initWithArray:symbols copyItems:YES]; - [(NSMutableArray *)_symbols replaceObjectsInRange:NSMakeRange(startIndex, 0) - withObjectsFromArray:newSymbols]; - } - - // Invalidate length and revalidate structure - _length = 0; - NSUInteger editingStart = startIndex - (didSplitStart?1:0); - NSUInteger editingLength = endIndex - startIndex + (didSplitStart?1:0) + (didSplitEnd?1:0); - editedRange = NSMakeRange(editingStart, editingLength); - replacementRange = NSMakeRange(startIndex, symbols.count); - [self fixSymbols]; - MPRangePath *changePath = [[MPRangePath alloc] initWithRange:editedRange]; - [self symbolsChangedInRangePath:changePath replacementLength:replacementRange.length]; -} - -- (void)splitSymbolsAtLocation:(NSUInteger)loc - insertionIndex:(out NSUInteger *)insertionIndex - didSplit:(out BOOL *)flag; -{ - NSUInteger splitSymbolIndex; - NSUInteger splitOffset; - [self getSplitOffset:&splitOffset - inSymbolAtIndex:&splitSymbolIndex - forSplitLocation:loc]; - id splitSymbol = _symbols[splitSymbolIndex]; - NSInteger splitSymbolLength = [self lengthOfSymbol:splitSymbol]; - if (splitOffset == splitSymbolLength) { - splitOffset = 0; - splitSymbolIndex++; - } - if (splitOffset != 0) { - NSString *leftPart = [splitSymbol substringToIndex:splitOffset]; - NSString *rightPart = [splitSymbol substringFromIndex:splitOffset]; - [(NSMutableArray *)_symbols replaceObjectAtIndex:splitSymbolIndex - withObject:leftPart]; - splitSymbolIndex++; - [(NSMutableArray *)_symbols insertObject:rightPart - atIndex:splitSymbolIndex]; - } - *flag = splitOffset != 0; - *insertionIndex = splitSymbolIndex; -} - -@end - -@implementation MPMutableExpression (MPMutableExpressionExtensionMethods) - -- (void)insertString:(NSString *)aString - atIndex:(NSUInteger)loc -{ - [self insertSymbols:@[aString] - atIndex:loc]; -} - -- (void)insertFunction:(MPFunction *)aFunction - atIndex:(NSUInteger)loc -{ - [self insertSymbols:@[aFunction] - atIndex:loc]; -} - -- (void)insertExpression:(MPExpression *)anExpression - atIndex:(NSUInteger)loc -{ - [self insertSymbols:anExpression.symbols - atIndex:loc]; -} - -- (void)insertSymbols:(NSArray *)symbols - atIndex:(NSUInteger)loc -{ - [self replaceSymbolsInRange:NSMakeRange(loc, 0) - withSymbols:symbols]; -} - -- (void)deleteSymbolsInRange:(NSRange)range -{ - [self replaceSymbolsInRange:range - withSymbols:@[]]; -} - -- (void)appendString:(NSString *)aString -{ - [self appendSymbols:@[aString]]; -} - -- (void)appendFunction:(MPFunction *)aFunction -{ - [self appendSymbols:@[aFunction]]; -} - -- (void)appendExpression:(MPExpression *)anExpression -{ - [self appendSymbols:anExpression.symbols]; -} - -- (void)appendSymbols:(NSArray *)symbols -{ - [self replaceSymbolsInRange:NSMakeRange(self.length, 0) - withSymbols:symbols]; -} - -- (void)setString:(NSString *)aString -{ - [self setSymbols:@[aString]]; -} - -- (void)setFunction:(MPFunction *)aFunction -{ - [self setSymbols:@[aFunction]]; -} - -- (void)setExpression:(MPExpression *)anExpression -{ - [self setSymbols:anExpression.symbols]; -} - -- (void)setSymbols:(NSArray *)symbols -{ - [self replaceSymbolsInRange:NSMakeRange(0, self.length) - withSymbols:symbols]; -} - -@end \ No newline at end of file diff --git a/MathPad/MPExpressionElement.h b/MathPad/MPExpressionElement.h new file mode 100644 index 0000000..3198f85 --- /dev/null +++ b/MathPad/MPExpressionElement.h @@ -0,0 +1,24 @@ +// +// MPExpressionElement.h +// MathPad +// +// Created by Kim Wittenburg on 10.08.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +@import Foundation; + +@protocol MPExpressionElement + +- (BOOL)isString; +- (BOOL)isFunction; + +- (NSUInteger)length; + +- (double)doubleValue; +- (float)floatValue; +- (int)intValue; +- (NSInteger)integerValue; +- (long long)longLongValue; + +@end diff --git a/MathPad/MPExpressionLayout.h b/MathPad/MPExpressionLayout.h index 7c05565..95d5cee 100644 --- a/MathPad/MPExpressionLayout.h +++ b/MathPad/MPExpressionLayout.h @@ -2,64 +2,18 @@ // MPExpressionLayout.h // MathPad // -// Created by Kim Wittenburg on 22.04.14. +// Created by Kim Wittenburg on 07.08.14. // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // -#import +@import Cocoa; +#import "MPLayout.h" +#import "MPExpression.h" -@class MPExpressionLayout, MPFunctionLayout, MPExpressionStorage, MPExpressionView, MPExpression; +@interface MPExpressionLayout : MPLayout -@interface MPExpressionLayout : NSObject { - BOOL _valid; - NSSize _cachedSize; - NSMutableArray *_symbolCache; -} +- (instancetype)initRootLayoutWithExpressionStorage:(MPExpressionStorage *)expressionStorage; -#pragma mark Creation Methods - -// -init not supported -- (id)initRootLayoutWithExpressionStorage:(MPExpressionStorage *)expressionStorage; -- (id)initWithExpressionPath:(NSIndexPath *)expressionPath - parent:(MPFunctionLayout *)parent; - -#pragma mark Properties - -@property (readonly, nonatomic, weak) MPFunctionLayout *parent; - -@property (readonly, nonatomic, weak) MPExpressionStorage *expressionStorage; -@property (readonly, nonatomic, strong) NSIndexPath *expressionPath; - -@property (nonatomic, weak) MPExpressionView *expressionView; - -- (MPExpression *)expression; // Convenience -- (NSLayoutManager *)layoutManager; -- (NSTextContainer *)textContainer; -- (NSTextStorage *)textStorage; - -#pragma mark Cache Methods - -- (void)invalidate; -- (void)editedExpressionInRange:(NSRange)range - replacementLength:(NSUInteger)length; - -- (BOOL)hasCacheForSymbolAtIndex:(NSUInteger)index; -- (MPFunctionLayout *)functionLayoutForFunctionAtIndex:(NSUInteger)index; -- (NSSize)cachedSizeForSymbolAtIndex:(NSUInteger)index; -- (void)cacheSize:(NSSize)size forSymbolAtIndex:(NSUInteger)index; - -#pragma mark Sizes Calculation Methods - -- (NSSize)sizeForAllSymbols; -- (NSSize)sizeForSymbolAtIndex:(NSUInteger)index; -- (NSSize)sizeForSymbolsInRange:(NSRange)range; - -#pragma mark Drawing Methods - -- (void)drawSymbolAtIndex:(NSUInteger)index - atPoint:(NSPoint)point; -- (void)drawSymbolsInRange:(NSRange)range - atPoint:(NSPoint)point; -- (void)drawAllSymbolsAtPoint:(NSPoint)point; +@property (readonly, nonatomic, weak) MPExpression *expression; // Convenience @end diff --git a/MathPad/MPExpressionLayout.m b/MathPad/MPExpressionLayout.m index 6b4da0a..ed1aae4 100644 --- a/MathPad/MPExpressionLayout.m +++ b/MathPad/MPExpressionLayout.m @@ -2,45 +2,69 @@ // MPExpressionLayout.m // MathPad // -// Created by Kim Wittenburg on 22.04.14. +// Created by Kim Wittenburg on 07.08.14. // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // #import "MPExpressionLayout.h" -#import "MPExpressionStorage.h" #import "MPFunctionLayout.h" -#import "MPModel.h" -#import "MPExpressionView.h" + +@interface MPExpressionLayout (MPPathGeneration) + +- (NSBezierPath *)bezierPathForChildAtIndex:(NSUInteger)index; +- (NSBezierPath *)generateBezierPathForString:(NSString *)aString; + + +@end + +@implementation MPExpressionLayout (MPPathGeneration) + +- (NSBezierPath *)bezierPathForChildAtIndex:(NSUInteger)index +{ + id symbol = [self.expression elementAtIndex:index]; + if ([symbol isString]) { + return [self cachableObjectForIndex:index + generator:^id{ + return [self generateBezierPathForString:symbol]; + }]; + } else { + MPLayout *layout = [self childLayoutAtIndex:index]; + return layout.bezierPath; + } +} + +- (NSBezierPath *)generateBezierPathForString:(NSString *)aString +{ + NSAttributedString *text = [[NSAttributedString alloc] initWithString:aString + attributes:@{NSFontAttributeName: [NSFont fontWithName:@"Lucida Grande" size:18.0]}]; + self.textStorage.attributedString = text; + NSRange glyphRange = [self.layoutManager glyphRangeForTextContainer:self.textContainer]; + NSGlyph glyphs[glyphRange.length+1]; + NSUInteger actualGlyphCount = [self.layoutManager getGlyphs:glyphs + range:glyphRange]; + NSBezierPath *path = [NSBezierPath bezierPath]; + [path moveToPoint:NSZeroPoint]; + [path appendBezierPathWithGlyphs:glyphs + count:actualGlyphCount + inFont:[NSFont fontWithName:@"Lucida Grande" size:18.0]]; + return path; +} + +@end @implementation MPExpressionLayout -#pragma mark Creation Methods - +# pragma mark Creation Methods - (instancetype)initRootLayoutWithExpressionStorage:(MPExpressionStorage *)expressionStorage { self = [super init]; if (self) { - _symbolCache = [[NSMutableArray alloc] init]; _expressionStorage = expressionStorage; - _expressionPath = [[NSIndexPath alloc] init]; - } - return self; -} - -- (instancetype)initWithExpressionPath:(NSIndexPath *)expressionPath - parent:(MPFunctionLayout *)parent -{ - self = [super init]; - if (self) { - _symbolCache = [[NSMutableArray alloc] init]; - _expressionPath = expressionPath; - _parent = parent; } return self; } #pragma mark Properties - @synthesize expressionStorage = _expressionStorage; - (MPExpressionStorage *)expressionStorage { @@ -52,172 +76,50 @@ - (MPExpression *)expression { - return [self.expressionStorage symbolAtIndexPath:self.expressionPath]; -} - -- (NSLayoutManager *)layoutManager -{ - return self.expressionStorage.layoutManager; -} - -- (NSTextContainer *)textContainer -{ - return self.expressionStorage.textContainer; -} - -- (NSTextStorage *)textStorage -{ - return self.expressionStorage.textStorage; + return [self.expressionStorage elementAtIndexPath:self.path]; } #pragma mark Cache Methods - -// TODO: Return nil from caching with illegal index - -- (void)invalidate +- (MPLayout *)childLayoutAtIndex:(NSUInteger)index { - _valid = NO; - [self.parent invalidate]; - [self.expressionView setNeedsDisplay:YES]; + id cachedObject = [self cachableObjectForIndex:index generator:^id{ + NSIndexPath *indexPath = [self.path indexPathByAddingIndex:index]; + MPFunctionLayout *layout = [MPFunctionLayout functionLayoutForFunctionAtIndexPath:indexPath + parent:self]; + return layout; + }]; + if ([cachedObject isKindOfClass:[NSBezierPath class]]) { + return nil; + } + return cachedObject; } -- (void)editedExpressionInRange:(NSRange)range replacementLength:(NSUInteger)length +- (NSSize)sizeForChildAtIndex:(NSUInteger)index { - // TODO: New symbols may also be inserted in the middle or at the beginning - NSInteger changeInLength = length - range.length; - while (_symbolCache.count < (self.expression.numberOfSymbols + changeInLength)) { - [_symbolCache addObject:[NSNull null]]; - } - NSMutableArray *newPlaceholders = [[NSMutableArray alloc] initWithCapacity:length]; - while (newPlaceholders.count < length) { - [newPlaceholders addObject:[NSNull null]]; - } - [_symbolCache replaceObjectsInRange:range withObjectsFromArray:newPlaceholders]; - [self invalidate]; -} - -- (BOOL)hasCacheForSymbolAtIndex:(NSUInteger)index -{ - if (index >= _symbolCache.count) { - return NO; - } - return _symbolCache[index] != [NSNull null]; -} - -- (MPFunctionLayout *)functionLayoutForFunctionAtIndex:(NSUInteger)index; -{ - if ([self hasCacheForSymbolAtIndex:index]) { - id cacheObject = _symbolCache[index]; - if ([cacheObject isKindOfClass:[NSValue class]]) { - return nil; - } - return cacheObject; - } - MPFunctionLayout *layout = [MPFunctionLayout functionLayoutForFunctionAtIndexPath:[self.expressionPath indexPathByAddingIndex:index] parent:self]; - while (index >= _symbolCache.count) { - [_symbolCache addObject:[NSNull null]]; - } - _symbolCache[index] = layout; - return layout; -} - -- (NSSize)cachedSizeForSymbolAtIndex:(NSUInteger)index -{ - id cachedSymbol = _symbolCache[index]; - if ([cachedSymbol isKindOfClass:[NSValue class]]) { - return [cachedSymbol sizeValue]; - } - return [(MPFunctionLayout *)cachedSymbol sizeOfFunction]; -} - -- (void)cacheSize:(NSSize)size forSymbolAtIndex:(NSUInteger)index -{ - while (index >= _symbolCache.count) { - [_symbolCache addObject:[NSNull null]]; - } - _symbolCache[index] = [NSValue valueWithSize:size]; -} - -#pragma mark Size Calculation Methods - -- (NSSize)sizeForAllSymbols -{ - if (!_valid) { - _cachedSize = [self sizeForSymbolsInRange:NSMakeRange(0, self.expression.numberOfSymbols)]; - _valid = YES; - } - return _cachedSize; -} - -- (NSSize)sizeForSymbolsInRange:(NSRange)range -{ - NSSize size = NSMakeSize(0, 0); - for (NSUInteger index = range.location; index < NSMaxRange(range); index++) { - NSSize symbolSize = [self sizeForSymbolAtIndex:index]; - size.width += symbolSize.width; - size.height = MAX(size.height, symbolSize.height); - } - return size; -} - -- (NSSize)sizeForSymbolAtIndex:(NSUInteger)index -{ - if ([self hasCacheForSymbolAtIndex:index]) { - return [self cachedSizeForSymbolAtIndex:index]; - } - id symbol = [self.expression symbolAtIndex:index]; + id symbol = [self.expression elementAtIndex:index]; if ([symbol isString]) { - [self.textStorage setString:symbol]; - NSRange glyphRange = [self.layoutManager glyphRangeForTextContainer:self.textContainer]; - NSSize symbolSize = [self.layoutManager boundingRectForGlyphRange:glyphRange - inTextContainer:self.textContainer].size; - [self cacheSize:symbolSize - forSymbolAtIndex:index]; - - return symbolSize; + return [self bezierPathForChildAtIndex:index].bounds.size; } else { - MPFunctionLayout *layout = [self functionLayoutForFunctionAtIndex:index]; - return [layout sizeOfFunction]; + return [self childLayoutAtIndex:index].size; } } #pragma mark Drawing Methods - -- (void)drawSymbolAtIndex:(NSUInteger)index - atPoint:(NSPoint)point +- (NSBezierPath *)generateBezierPath { - id symbol = [self.expression symbolAtIndex:index]; - // point.x = point.y = 0; - NSLog(@"draw Symbol: %@ at Point: (x: %f, y: %f)", symbol, point.x, point.y); - if ([symbol isString]) { - [self.textStorage setString:symbol]; - NSRange glyphRange = [self.layoutManager glyphRangeForTextContainer:self.textContainer]; - [self.layoutManager drawGlyphsForGlyphRange:glyphRange - atPoint:point]; - } else { - MPFunctionLayout *layout = [self functionLayoutForFunctionAtIndex:index]; - NSLog(@"layout: %@, index: %ld", layout, index); - [layout drawFunctionAtPoint:point]; + NSBezierPath *fullPath = [NSBezierPath bezierPath]; + [fullPath moveToPoint:NSZeroPoint]; + NSUInteger x = 0; + for (NSInteger index = 0; index < self.expression.numberOfElements; ++index) { + NSAffineTransform *transform = [NSAffineTransform transform]; + // TODO: Translate by the right amount + [transform translateXBy:x yBy:0]; + NSBezierPath *path = [self bezierPathForChildAtIndex:index].copy; + [path transformUsingAffineTransform:transform]; + [fullPath appendBezierPath:path]; + x += path.bounds.size.width; } -} - -- (void)drawSymbolsInRange:(NSRange)range - atPoint:(NSPoint)point -{ - NSSize overallSize = [self sizeForSymbolsInRange:range]; - CGFloat x = point.x; - for (NSUInteger index = range.location; index < NSMaxRange(range); index++) { - NSSize symbolSize = [self sizeForSymbolAtIndex:index]; - CGFloat dy = (overallSize.height - symbolSize.height) / 2; - [self drawSymbolAtIndex:index - atPoint:NSMakePoint(x, point.y + dy)]; - x += symbolSize.width; - } -} - -- (void)drawAllSymbolsAtPoint:(NSPoint)point -{ - [self drawSymbolsInRange:NSMakeRange(0, [self.expression numberOfSymbols]) atPoint:point]; + return fullPath; } @end diff --git a/MathPad/MPExpressionStorage.h b/MathPad/MPExpressionStorage.h index 9042766..e54d725 100644 --- a/MathPad/MPExpressionStorage.h +++ b/MathPad/MPExpressionStorage.h @@ -10,9 +10,9 @@ @class MPExpressionStorage, MPExpressionLayout; -@interface MPExpressionStorage : MPMutableExpression +@interface MPExpressionStorage : MPExpression -@property (nonatomic, strong) MPExpressionLayout *expressionLayout; +@property (nonatomic, strong) MPExpressionLayout *rootLayout; - (NSLayoutManager *)layoutManager; - (NSTextContainer *)textContainer; diff --git a/MathPad/MPExpressionStorage.m b/MathPad/MPExpressionStorage.m index ae665e8..5a4cf91 100644 --- a/MathPad/MPExpressionStorage.m +++ b/MathPad/MPExpressionStorage.m @@ -19,11 +19,11 @@ @implementation MPExpressionStorage -- (instancetype)initWithSymbols:(NSArray *)symbols +- (instancetype)initWithElements:(NSArray *)elements { - self = [super initWithSymbols:symbols]; + self = [super initWithElements:elements]; if (self) { - _expressionLayout = [[MPExpressionLayout alloc] initRootLayoutWithExpressionStorage:self]; + _rootLayout = [[MPExpressionLayout alloc] initRootLayoutWithExpressionStorage:self]; } return self; } @@ -59,24 +59,17 @@ } } -- (void)symbolsChangedInRangePath:(MPRangePath *)rangePath replacementLength:(NSUInteger)length +- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath + replacementLength:(NSUInteger)replacementLength { if (rangePath.location.length == 0) { return; } - id current = self.expressionLayout; - for (NSUInteger position = 1; position < rangePath.location.length-1; position++) { - if ([current isKindOfClass:[MPExpressionLayout class]]) { - current = [(MPExpressionLayout *)current functionLayoutForFunctionAtIndex:position]; - } else { - current = [(MPFunctionLayout *)current expressionLayoutForChildAtIndex:position]; - } - } - if ([current isKindOfClass:[MPExpressionLayout class]]) { - [(MPExpressionLayout *)current editedExpressionInRange:rangePath.rangeAtLastIndex replacementLength:length]; - } else { - [(MPFunctionLayout *)current editedChildAtIndex:[rangePath.location indexAtPosition:rangePath.location.length-1]]; + MPLayout *current = self.rootLayout; + for (NSUInteger index = 1; index < rangePath.location.length-1; index++) { + current = [current childLayoutAtIndex:index]; } + [current clearCacheInRange:rangePath.rangeAtLastIndex replacementLength:replacementLength]; } @end diff --git a/MathPad/MPExpressionView.h b/MathPad/MPExpressionView.h index 1cc8b70..23060d5 100644 --- a/MathPad/MPExpressionView.h +++ b/MathPad/MPExpressionView.h @@ -21,7 +21,6 @@ #pragma mark Properties @property (readonly, nonatomic, strong) MPExpressionStorage *expressionStorage; -- (MPExpressionLayout *)expressionLayout; // Convenience Method @property (nonatomic, getter = isEditable) BOOL editable; @property (nonatomic, strong) MPRangePath *selection; diff --git a/MathPad/MPExpressionView.m b/MathPad/MPExpressionView.m index 4a37a62..4b270c9 100644 --- a/MathPad/MPExpressionView.m +++ b/MathPad/MPExpressionView.m @@ -7,13 +7,15 @@ // #import "MPExpressionView.h" -#import "MPExpressionLayout.h" #import "MPExpressionStorage.h" - -#import "NSObject+MPStringTest.h" +#import "MPExpressionLayout.h" #import "MPSumFunction.h" +@interface MPExpressionView (MPCursor) +@property (nonatomic, strong) NSTimer *cursorTimer; +@end + @implementation MPExpressionView #pragma mark Creation Methods @@ -47,23 +49,20 @@ - (void)initializeObjects { - MPExpressionStorage *expressionStorage = [[MPExpressionStorage alloc] initWithSymbols:@[@"12 345", [[MPSumFunction alloc] init], [[MPSumFunction alloc] init]]]; + MPExpressionStorage *expressionStorage = [[MPExpressionStorage alloc] initWithElements:@[@"12345", [[MPSumFunction alloc] init], [[MPSumFunction alloc] init]]]; _expressionStorage = expressionStorage; - [self.expressionLayout setExpressionView:self]; } #pragma mark Properties -- (void)setExpressionStorage:(MPExpressionStorage *)expressionStorage +- (BOOL)isFlipped { - [_expressionStorage.expressionLayout setExpressionView:nil]; - _expressionStorage = expressionStorage; - [_expressionStorage.expressionLayout setExpressionView:self]; + return NO; } -- (MPExpressionLayout *)expressionLayout +- (void)setExpressionStorage:(MPExpressionStorage *)expressionStorage { - return self.expressionStorage.expressionLayout; + _expressionStorage = expressionStorage; } #pragma mark Drawing Methods @@ -74,11 +73,10 @@ [[NSColor whiteColor] set]; NSRectFill(self.bounds); [[NSColor blackColor] set]; - NSSize expressionSize = [self.expressionLayout sizeForAllSymbols]; + NSSize expressionSize = [self.expressionStorage.rootLayout size]; CGFloat y = (self.bounds.size.height - expressionSize.height) / 2; - NSLog(@"%f", self.bounds.origin.y); NSPoint point = NSMakePoint(self.bounds.origin.x, self.bounds.origin.y + y); - [self.expressionLayout drawAllSymbolsAtPoint:point]; + [self.expressionStorage.rootLayout drawAtPoint:point]; } @end diff --git a/MathPad/MPFunction.h b/MathPad/MPFunction.h index 53864e8..13a6215 100644 --- a/MathPad/MPFunction.h +++ b/MathPad/MPFunction.h @@ -6,58 +6,47 @@ // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // +@import Foundation; +#import "MPExpressionElement.h" + @class MPFunction, MPExpression, MPRangePath; -@interface MPFunction : NSObject +@interface MPFunction : NSObject #pragma mark Creation Methods - (instancetype)init; -#pragma mark Working With the Expression Tree +#pragma mark Properties +// Subclasses should define accessor properties for all sub expressions, which, in the setter, should send didChangeElementAtRangePath:replacementLength: to self with a replacement length of 1. +#pragma mark Working With the Expression Tree @property (nonatomic, weak) MPExpression *parent; // Documentation: Do not set -- (NSUInteger)numberOfChildren; -- (MPExpression *)childAtIndex:(NSUInteger)index; +- (NSUInteger)numberOfChildren; // Override +- (MPExpression *)childAtIndex:(NSUInteger)index; // Override - (void)setChild:(MPExpression *)child - atIndex:(NSUInteger)index; - -#pragma mark Evaluating Functions - -- (double)doubleValue; - -#pragma mark Working With Functions - -- (BOOL)isEqualToFunction:(MPFunction *)aFunction; - -@end - -@interface MPFunction (MPFunctionExtensionMethods) - -#pragma mark Working With the Expression Tree + atIndex:(NSUInteger)index; // Override // May be overridden for performance improvements -- (NSArray *)children; +- (NSArray *)children; // Indexes must equal the ones from the native methods - (NSUInteger)indexOfChild:(MPExpression *)child; -- (id)symbolAtIndexPath:(NSIndexPath *)indexPath; +- (id)elementAtIndexPath:(NSIndexPath *)indexPath; #pragma mark Evaluating Functions +- (double)doubleValue; // Override -- (float)floatValue; -- (int)intValue; -- (NSInteger)integerValue; -- (long long)longLongValue; +#pragma mark Messages +- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath + replacementLength:(NSUInteger)replacementLength; +- (void)didChangeChild:(MPExpression *)child; +- (void)didChangeChildAtIndex:(NSUInteger)index; -- (NSString *)description; +#pragma mark Working With Functions +- (BOOL)isEqualToFunction:(MPFunction *)aFunction; // Override -- (NSUInteger)hash; - -@end - -@interface MPFunction (MPDisplayExtension) - -- (void)symbolsChangedInRangePath:(MPRangePath *)rangePath replacementLength:(NSUInteger)length; +- (NSString *)description; // Should be overridden +- (NSUInteger)hash;// Override @end diff --git a/MathPad/MPFunction.m b/MathPad/MPFunction.m index 55d8afb..1d99321 100644 --- a/MathPad/MPFunction.m +++ b/MathPad/MPFunction.m @@ -10,12 +10,11 @@ #import "MPExpression.h" #import "MPRangePath.h" -#import "NSIndexPath+MPReverseIndexPath.h" +#import "NSIndexPath+MPAdditions.h" @implementation MPFunction #pragma mark Creation Methods - - (instancetype)init { self = [super init]; @@ -25,7 +24,6 @@ } #pragma mark Working With the Expression Tree - - (NSUInteger)numberOfChildren { return 0; @@ -38,63 +36,10 @@ - (void)setChild:(MPExpression *)child atIndex:(NSUInteger)index -{} - -#pragma mark Evaluating Functions - -- (double)doubleValue { - return 0; + [self didChangeChildAtIndex:index]; } -#pragma mark Working With Functions - -- (BOOL)isEqual:(id)object -{ - if (self == object) { - return YES; - } - if (object == nil) { - return NO; - } - if (![object isKindOfClass:[MPFunction class]]) { - return NO; - } - return [self isEqualToFunction:(MPFunction *)object]; -} - -- (BOOL)isEqualToFunction:(MPFunction *)aFunction -{ - return [aFunction isMemberOfClass:[MPFunction class]] && [self isMemberOfClass:[MPFunction class]]; -} - -#pragma mark - NSCopying - -- (id)copyWithZone:(NSZone *)zone -{ - return [[MPFunction allocWithZone:zone] init]; -} - -#pragma mark - NSCoding - -- (id)initWithCoder:(NSCoder *)aDecoder -{ - self = [super init]; - if (self) { - - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder -{} - -@end - -@implementation MPFunction (MPFunctionExtensionMethods) - -#pragma mark Working With the Expression Tree - - (NSArray *)children { NSUInteger childCount = [self numberOfChildren]; @@ -116,16 +61,115 @@ return NSNotFound; } -- (id)symbolAtIndexPath:(NSIndexPath *)indexPath +- (id)elementAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.length == 0) { return self; } MPExpression *child = [self childAtIndex:[indexPath indexAtPosition:0]]; - return [child symbolAtIndexPath:[indexPath indexPathByRemovingFirstIndex]]; + return [child elementAtIndexPath:[indexPath indexPathByRemovingFirstIndex]]; } #pragma mark Evaluating Functions +- (double)doubleValue +{ + return 0; +} + +#pragma mark Notifications +- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath + replacementLength:(NSUInteger)replacementLength +{ + NSUInteger selfIndex = [self.parent indexOfElement:self]; + MPRangePath *newPath = rangePath.copy; + newPath.location = [newPath.location indexPathByPreceedingIndex:selfIndex]; + [self.parent didChangeElementsInRangePath:newPath + replacementLength:replacementLength]; +} + +- (void)didChangeChild:(MPExpression *)child +{ + [self didChangeChildAtIndex:[self indexOfChild:child]]; +} + +- (void)didChangeChildAtIndex:(NSUInteger)index +{ + MPRangePath *path = [[MPRangePath alloc] initWithRange:NSMakeRange(index, 1)]; + [self didChangeElementsInRangePath:path + replacementLength:1]; +} + +#pragma mark Working With Functions +- (BOOL)isEqual:(id)object +{ + if (self == object) { + return YES; + } + if (object == nil) { + return NO; + } + if (![object isKindOfClass:[MPFunction class]]) { + return NO; + } + return [self isEqualToFunction:(MPFunction *)object]; +} + +- (BOOL)isEqualToFunction:(MPFunction *)aFunction +{ + return [aFunction isMemberOfClass:[MPFunction class]] && [self isMemberOfClass:[MPFunction class]]; +} + +- (NSString *)description +{ + return @"[]"; +} + +- (NSUInteger)hash +{ + return 0; +} + +#pragma mark - NSCopying +- (id)copyWithZone:(NSZone *)zone +{ + return [[MPFunction allocWithZone:zone] init]; +} + +#pragma mark - NSCoding +- (id)initWithCoder:(NSCoder *)aDecoder +{ + self = [super init]; + if (self) { + NSArray *children = [aDecoder decodeObject]; + NSInteger index = 0; + for (MPExpression *child in children) { + [self setChild:child atIndex:index]; + ++index; + } + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder +{ + [aCoder encodeObject:self.children]; +} + +#pragma mark - MPExpressionElement +- (BOOL)isString +{ + return NO; +} + +- (BOOL)isFunction +{ + return YES; +} + +- (NSUInteger)length +{ + return 1; +} - (float)floatValue { @@ -147,26 +191,4 @@ return (long long)[self doubleValue]; } -- (NSString *)description -{ - return @"[]"; -} - -- (NSUInteger)hash -{ - return 0; -} - @end - -@implementation MPFunction (MPDisplayExtension) - -- (void)symbolsChangedInRangePath:(MPRangePath *)rangePath replacementLength:(NSUInteger)length -{ - NSUInteger index = [self.parent indexOfSymbol:self]; - NSIndexPath *newLocation = [rangePath.location indexPathByPrecedingIndex:index]; - MPRangePath *newRangePath = [[MPRangePath alloc] initWithLocation:newLocation length:rangePath.length]; - [self.parent symbolsChangedInRangePath:newRangePath replacementLength:length]; -} - -@end \ No newline at end of file diff --git a/MathPad/MPFunctionLayout.h b/MathPad/MPFunctionLayout.h index 5810e71..7fb8a61 100644 --- a/MathPad/MPFunctionLayout.h +++ b/MathPad/MPFunctionLayout.h @@ -2,56 +2,30 @@ // MPFunctionLayout.h // MathPad // -// Created by Kim Wittenburg on 22.04.14. +// Created by Kim Wittenburg on 07.08.14. // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // -#import +@import Cocoa; +#import "MPLayout.h" -@class MPFunctionLayout, MPExpressionLayout, MPExpressionStorage, MPFunction; +@interface MPFunctionLayout : MPLayout -@interface MPFunctionLayout : NSObject { - @protected - BOOL _valid; - NSSize _cachedSize; - NSMutableArray *_childCache; -} ++ (MPFunctionLayout *)functionLayoutForFunctionAtIndexPath:(NSIndexPath *)path + parent:(MPExpressionLayout *)parent; -#pragma mark Creation Methods - -+ (instancetype)functionLayoutForFunctionAtIndexPath:(NSIndexPath *)functionPath - parent:(MPExpressionLayout *)parent; - -- (id)initWithFunctionPath:(NSIndexPath *)functionPath - parent:(MPExpressionLayout *)parent; - -#pragma mark Properties - -@property (readonly, nonatomic, weak) MPExpressionLayout *parent; - -@property (readonly, nonatomic, strong) NSIndexPath *functionPath; - -- (MPExpressionStorage *)expressionStorage; -- (MPFunction *)function; // Convenience -- (NSLayoutManager *)layoutManager; -- (NSTextContainer *)textContainer; -- (NSTextStorage *)textStorage; - -#pragma mark Cache Methods - -- (void)invalidate; -- (void)editedChildAtIndex:(NSUInteger)index; - -- (BOOL)hasCacheForChildAtIndex:(NSUInteger)index; -- (MPExpressionLayout *)expressionLayoutForChildAtIndex:(NSUInteger)index; - -#pragma mark Size Calculation Methods - -- (NSSize)sizeOfFunction; -- (NSSize)calculateSize; - -#pragma mark Drawing Methods - -- (void)drawFunctionAtPoint:(NSPoint)point; +@property (readonly, nonatomic, weak) MPFunction *function; // Convenience @end + +@interface MPFunctionLayout (MPSubclassOverride) + +// Should also implement accessor method for special function type: +// - (MPCustomFunction *)customFunction +// { +// return (MPCustomFunction *)self.function; +// } + +- (NSBezierPath *)generateBezierPath; + +@end \ No newline at end of file diff --git a/MathPad/MPFunctionLayout.m b/MathPad/MPFunctionLayout.m index b85a2f5..8fdfa82 100644 --- a/MathPad/MPFunctionLayout.m +++ b/MathPad/MPFunctionLayout.m @@ -2,14 +2,13 @@ // MPFunctionLayout.m // MathPad // -// Created by Kim Wittenburg on 22.04.14. +// Created by Kim Wittenburg on 07.08.14. // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // #import "MPFunctionLayout.h" -#import "MPExpressionLayout.h" -#import "MPExpressionStorage.h" #import "MPFunction.h" +#import "MPExpressionLayout.h" #import "MPSumFunction.h" #import "MPSumFunctionLayout.h" @@ -17,114 +16,43 @@ @implementation MPFunctionLayout #pragma mark Creation Methods - -+ (instancetype)functionLayoutForFunctionAtIndexPath:(NSIndexPath *)functionPath - parent:(MPExpressionLayout *)parent ++ (MPFunctionLayout *)functionLayoutForFunctionAtIndexPath:(NSIndexPath *)path + parent:(MPExpressionLayout *)parent { - MPFunction *function = [parent.expressionStorage symbolAtIndexPath:functionPath]; + MPFunction *function = [parent.expressionStorage elementAtIndexPath:path]; Class class = [function class]; if (class == [MPSumFunction class]) { - return [[MPSumFunctionLayout alloc] initWithFunctionPath:functionPath parent:parent]; + return [[MPSumFunctionLayout alloc] initWithPath:path parent:parent]; } - return nil; -} - -- (id)initWithFunctionPath:(NSIndexPath *)functionPath - parent:(MPExpressionLayout *)parent -{ - self = [super init]; - if (self) { - _functionPath = functionPath; - _parent = parent; - _childCache = [[NSMutableArray alloc] init]; - } - return self; + return [[self alloc] initWithPath:path parent:parent]; } #pragma mark Properties - -- (MPExpressionStorage *)expressionStorage -{ - return self.parent.expressionStorage; -} - - (MPFunction *)function { - return [self.expressionStorage symbolAtIndexPath:self.functionPath]; -} - -- (NSLayoutManager *)layoutManager -{ - return self.expressionStorage.layoutManager; -} - -- (NSTextContainer *)textContainer -{ - return self.expressionStorage.textContainer; -} - -- (NSTextStorage *)textStorage -{ - return self.expressionStorage.textStorage; + return [self.expressionStorage elementAtIndexPath:self.path]; } #pragma mark Cache Methods - -- (void)invalidate +- (MPLayout *)childLayoutAtIndex:(NSUInteger)index { - _valid = NO; - [self.parent invalidate]; + return [self cachableObjectForIndex:index generator:^id{ + NSIndexPath *childPath = [self.path indexPathByAddingIndex:index]; + MPExpressionLayout *layout = [[MPExpressionLayout alloc] initWithPath:childPath + parent:self]; + return layout; + }]; } -- (void)editedChildAtIndex:(NSUInteger)index +- (NSSize)sizeForChildAtIndex:(NSUInteger)index { - if ([self hasCacheForChildAtIndex:index]) { - _childCache[index] = [NSNull null]; - } - [self invalidate]; + MPLayout *childLayout = [self childLayoutAtIndex:index]; + return [childLayout size]; } -- (BOOL)hasCacheForChildAtIndex:(NSUInteger)index -{ - if (index >= _childCache.count) { - return NO; - } - return _childCache[index] != [NSNull null]; -} - -- (MPExpressionLayout *)expressionLayoutForChildAtIndex:(NSUInteger)index -{ - if ([self hasCacheForChildAtIndex:index]) { - return _childCache[index]; - } - while (index >= _childCache.count) { - [_childCache addObject:[NSNull null]]; - } - MPExpressionLayout *expressionLayout = [[MPExpressionLayout alloc] initWithExpressionPath:[self.functionPath indexPathByAddingIndex:index] parent:self]; - _childCache[index] = expressionLayout; - return expressionLayout; -} - -#pragma mark Size Calculation Methods - -- (NSSize)sizeOfFunction -{ - if (!_valid) { - _cachedSize = [self calculateSize]; - _valid = YES; - } - return _cachedSize; -} - -- (NSSize)calculateSize -{ - return NSMakeSize(0, 0); -} - -#pragma mark Drawing Methods - -- (void)drawFunctionAtPoint:(NSPoint)point +- (NSBezierPath *)generateBezierPath { + return [NSBezierPath bezierPath]; } @end diff --git a/MathPad/MPLayout.h b/MathPad/MPLayout.h new file mode 100644 index 0000000..812598a --- /dev/null +++ b/MathPad/MPLayout.h @@ -0,0 +1,56 @@ +// +// MPDrawable.h +// MathPad +// +// Created by Kim Wittenburg on 07.08.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +@import Cocoa; +#import "MPExpressionStorage.h" + +#define MPNull [NSNull null] + +@interface MPLayout : NSObject + +#pragma mark Creation Methods +- (instancetype)init; +- (instancetype)initWithPath:(NSIndexPath *)path + parent:(MPLayout *)parent; + +#pragma mark Text System Objects +@property (readonly, nonatomic, weak) MPExpressionStorage *expressionStorage; +@property (readonly, nonatomic, weak) NSLayoutManager *layoutManager; +@property (readonly, nonatomic, weak) NSTextContainer *textContainer; +@property (readonly, nonatomic, weak) NSTextStorage *textStorage; + +#pragma mark Cache Tree +@property (readonly, nonatomic, weak) MPLayout *parent; +@property (readonly, nonatomic, strong) NSIndexPath *path; + +#pragma mark Cache Methods +// Querying Caches +- (id)cachableObjectForIndex:(NSUInteger)index + generator:(id(^)())generator; + +// Clearing Caches +- (void)clearCacheInRange:(NSRange)range + replacementLength:(NSUInteger)replacementLength; +- (void)invalidate; + +#pragma mark Calculation and Drawing Methods +// @property (nonatomic) BOOL usesSmallSize; +- (NSSize)size; + +- (NSBezierPath *)bezierPath; +- (NSBezierPath *)bezierPathAtOrigin:(NSPoint)point; + +- (void)drawAtPoint:(NSPoint)point; + +@end + +@interface MPLayout (MPSubclassImplement) +- (MPLayout *)childLayoutAtIndex:(NSUInteger)index; // To be implemented +- (NSSize)sizeForChildAtIndex:(NSUInteger)index; // To be implemented +- (NSBezierPath *)generateBezierPath; // To be implemented +@end \ No newline at end of file diff --git a/MathPad/MPLayout.m b/MathPad/MPLayout.m new file mode 100644 index 0000000..515c45f --- /dev/null +++ b/MathPad/MPLayout.m @@ -0,0 +1,159 @@ +// +// MPLayout.m +// MathPad +// +// Created by Kim Wittenburg on 11.08.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPLayout.h" + +@interface MPLayout () + +// Querying Caches +- (BOOL)hasCacheForElementAtIndex:(NSUInteger)index; + +// Storing Caches +- (void)cacheObject:(id)anObject + forElementAtIndex:(NSUInteger)index; +- (void)ensureCacheSizeForIndex:(NSUInteger)index; + +@end + +@implementation MPLayout { + NSMutableArray *_cache; + NSBezierPath *_cachedPath; +} + +#pragma mark Creation Methods +- (id)init +{ + self = [super init]; + if (self) { + _cache = [[NSMutableArray alloc] init]; + _cachedPath = nil; + _path = [[NSIndexPath alloc] init]; + } + return self; +} + +- (id)initWithPath:(NSIndexPath *)path + parent:(MPLayout *)parent +{ + self = [self init]; + if (self) { + _path = path; + _parent = parent; + } + return self; +} + +#pragma Text System Objects +- (MPExpressionStorage *)expressionStorage +{ + return self.parent.expressionStorage; +} + +- (NSLayoutManager *)layoutManager +{ + return self.expressionStorage.layoutManager; +} + +- (NSTextContainer *)textContainer +{ + return self.expressionStorage.textContainer; +} + +- (NSTextStorage *)textStorage +{ + return self.expressionStorage.textStorage; +} + +#pragma mark Cache Tree +// Querying Caches +- (BOOL)hasCacheForElementAtIndex:(NSUInteger)index +{ + if (index >= _cache.count) { + return NO; + } + return _cache[index] != MPNull; +} + +- (id)cachableObjectForIndex:(NSUInteger)index + generator:(id (^)())generator +{ + if ([self hasCacheForElementAtIndex:index]) { + return _cache[index]; + } + id object = generator(); + [self cacheObject:object + forElementAtIndex:index]; + return object; +} + +// Storing Caches +- (void)cacheObject:(id)anObject + forElementAtIndex:(NSUInteger)index +{ + [self ensureCacheSizeForIndex:index]; + _cache[index] = anObject; +} + +- (void)ensureCacheSizeForIndex:(NSUInteger)index +{ + while (index >= _cache.count) { + [_cache addObject:MPNull]; + } +} + +// Clearing Caches +- (void)clearCacheInRange:(NSRange)range + replacementLength:(NSUInteger)replacementLength +{ + NSMutableArray *placeholders = [[NSMutableArray alloc] initWithCapacity:replacementLength]; + while (placeholders.count < replacementLength) { + [placeholders addObject:MPNull]; + } + [_cache replaceObjectsInRange:range + withObjectsFromArray:placeholders]; + [self invalidate]; +} + +- (void)invalidate +{ + _cachedPath = nil; + [self.parent invalidate]; +} + +#pragma mark Calculation Methods +- (NSSize)size +{ + return self.bezierPath.bounds.size; +} + +- (NSBezierPath *)bezierPath +{ + if (!_cachedPath) { + _cachedPath = [self generateBezierPath]; + } + return _cachedPath; +} + +- (NSBezierPath *)bezierPathAtOrigin:(NSPoint)point +{ + NSAffineTransform *transform = [NSAffineTransform transform]; + [transform translateXBy:point.x + yBy:point.y]; + NSBezierPath *path = [NSBezierPath bezierPath]; + [path appendBezierPath:self.bezierPath]; + [path transformUsingAffineTransform:transform]; + return path; +} + +- (void)drawAtPoint:(NSPoint)point +{ + NSBezierPath *path = [self bezierPathAtOrigin:point]; + [path fill]; +} + +@end diff --git a/MathPad/MPModel.h b/MathPad/MPModel.h index df2e393..1f7f7f6 100644 --- a/MathPad/MPModel.h +++ b/MathPad/MPModel.h @@ -18,8 +18,6 @@ #import "MPFunction.h" #import "MPRangePath.h" -#import "NSObject+MPStringTest.h" -#import "NSTextStorage+MPSetContents.h" -#import "NSIndexPath+MPReverseIndexPath.h" +#import "NSIndexPath+MPAdditions.h" #endif diff --git a/MathPad/MPRangePath.h b/MathPad/MPRangePath.h index cdc101d..be32207 100644 --- a/MathPad/MPRangePath.h +++ b/MathPad/MPRangePath.h @@ -6,8 +6,12 @@ // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // +@import Foundation; + #import "MPExpression.h" +@class MPRangePath, MPExpression; + @interface MPRangePath : NSObject #pragma mark Creation Methods diff --git a/MathPad/MPRangePath.m b/MathPad/MPRangePath.m index e498e01..801c6b7 100644 --- a/MathPad/MPRangePath.m +++ b/MathPad/MPRangePath.m @@ -7,6 +7,7 @@ // #import "MPRangePath.h" +#import "MPExpression.h" @implementation MPRangePath @@ -143,7 +144,7 @@ - (MPExpression *)subexpressionWithRangePath:(MPRangePath *)aRangePath { - MPExpression *targetExpression = [self symbolAtIndexPath:[aRangePath.location indexPathByRemovingLastIndex]]; + MPExpression *targetExpression = [self elementAtIndexPath:[aRangePath.location indexPathByRemovingLastIndex]]; if (![targetExpression isKindOfClass:[MPExpression class]]) { return nil; } diff --git a/MathPad/MPSumFunction.h b/MathPad/MPSumFunction.h index 4676883..2f4e964 100644 --- a/MathPad/MPSumFunction.h +++ b/MathPad/MPSumFunction.h @@ -6,6 +6,7 @@ // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // +@import Foundation; #import "MPFunction.h" @class MPSumFunction, MPExpression; diff --git a/MathPad/MPSumFunction.m b/MathPad/MPSumFunction.m index 6608bde..2240e4c 100644 --- a/MathPad/MPSumFunction.m +++ b/MathPad/MPSumFunction.m @@ -12,7 +12,6 @@ @implementation MPSumFunction #pragma mark Creation Methods - - (instancetype)init { self = [super init]; @@ -27,8 +26,32 @@ return self; } -#pragma mark Working With the Expression Tree +#pragma mark Properties +- (void)setStartExpression:(MPExpression *)startExpression +{ + _startExpression.parent = nil; + _startExpression = startExpression; + _startExpression.parent = self; + [self didChangeChildAtIndex:0]; +} +- (void)setTargetExpression:(MPExpression *)targetExpression +{ + _targetExpression.parent = nil; + _targetExpression = targetExpression; + _targetExpression.parent = self; + [self didChangeChildAtIndex:1]; +} + +- (void)setSumExpression:(MPExpression *)sumExpression +{ + _sumExpression.parent = nil; + _sumExpression = sumExpression; + _sumExpression.parent = self; + [self didChangeChildAtIndex:2]; +} + +#pragma mark Working With the Expression Tree - (NSUInteger)numberOfChildren { return 3; @@ -48,36 +71,32 @@ } } -- (NSArray *)children -{ - return @[self.startExpression, self.targetExpression, self.sumExpression]; -} - - (void)setChild:(MPExpression *)child atIndex:(NSUInteger)index { switch (index) { // TODO: Copy child? case 0: - self.startExpression.parent = nil; self.startExpression = child; break; case 1: - self.targetExpression.parent = nil; self.targetExpression = child; break; case 2: - self.sumExpression.parent = nil; self.sumExpression = child; break; default: return; } - child.parent = self; + [self didChangeChildAtIndex:index]; +} + +- (NSArray *)children +{ + return @[self.startExpression, self.targetExpression, self.sumExpression]; } #pragma mark Evaluating Functions - - (double)doubleValue { #warning Implementation @@ -85,7 +104,6 @@ } #pragma mark Working With Functions - - (BOOL)isEqualToFunction:(MPFunction *)aFunction { if (![aFunction isKindOfClass:[MPSumFunction class]]) { @@ -106,8 +124,13 @@ return [NSString stringWithFormat:@"Sum(From: %@; To: %@; Using: %@)", self.startExpression, self.targetExpression, self.sumExpression]; } -#pragma mark - NSCopying +- (NSUInteger)hash +{ +#warning Unimplemented Method + return [super hash]; +} +#pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { MPSumFunction *copy = [[MPSumFunction allocWithZone:zone] init]; diff --git a/MathPad/MPSumFunctionLayout.m b/MathPad/MPSumFunctionLayout.m index d001fe5..7a0c2d2 100644 --- a/MathPad/MPSumFunctionLayout.m +++ b/MathPad/MPSumFunctionLayout.m @@ -16,20 +16,21 @@ return (MPSumFunction *)self.function; } -- (NSSize)calculateSize +- (NSBezierPath *)generateBezierPath { - NSAttributedString *text = [[NSAttributedString alloc] initWithString:@"∑" attributes:@{NSFontAttributeName: [NSFont fontWithName:@"HelveticaNeue" size:50.0]}]; - [self.textStorage setAttributedString:text]; + NSAttributedString *text = [[NSAttributedString alloc] initWithString:@"∑" + attributes:@{NSFontAttributeName: [NSFont fontWithName:@"Lucida Grande" size:18.0]}]; + self.textStorage.attributedString = text; NSRange glyphRange = [self.layoutManager glyphRangeForTextContainer:self.textContainer]; - return [self.layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:self.textContainer].size; -} - -- (void)drawFunctionAtPoint:(NSPoint)point -{ - NSAttributedString *text = [[NSAttributedString alloc] initWithString:@"∑" attributes:@{NSFontAttributeName: [NSFont fontWithName:@"HelveticaNeue" size:50.0]}]; - [self.textStorage setAttributedString:text]; - NSRange glyphRange = [self.layoutManager glyphRangeForTextContainer:self.textContainer]; - [self.layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:point]; + NSGlyph glyphs[glyphRange.length+1]; + NSUInteger actualGylphCount = [self.layoutManager getGlyphs:glyphs + range:glyphRange]; + NSBezierPath *path = [NSBezierPath bezierPath]; + [path moveToPoint:NSZeroPoint]; + [path appendBezierPathWithGlyphs:glyphs + count:actualGylphCount + inFont:[NSFont fontWithName:@"Lucida Grande" size:18.0]]; + return path; } @end diff --git a/MathPad/NSIndexPath+MPAdditions.h b/MathPad/NSIndexPath+MPAdditions.h new file mode 100644 index 0000000..0d6979c --- /dev/null +++ b/MathPad/NSIndexPath+MPAdditions.h @@ -0,0 +1,30 @@ +// +// NSIndexPath+MPRemoveFirstIndex.h +// MathPad +// +// Created by Kim Wittenburg on 23.04.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +@import Foundation; + +@interface NSIndexPath (MPAdditions) + +/*! + @method indexPathByRemovingFirstIndex + @brief Provides an index path with the indexes in the receiving index path, excluding the first one. + @discussion Returns an empty NSIndexPath instance if the receiving index path’s length is 1 or less. + @return A new index path with the receiving index path’s indexes, excluding the first one. + */ +- (NSIndexPath *)indexPathByRemovingFirstIndex; + +/*! + @method indexPathByPreceedingIndex: + @brief Provides an index path with the given index followed by the indexes of the receiver. + @discussion If the receiver does not contain any indexes the given index is the only index contained in the returned index path. + @param index The index new index preceeding all others + @return A new index path with all the receiver's indexes preceeded by @c index. + */ +- (NSIndexPath *)indexPathByPreceedingIndex:(NSUInteger)index; + +@end diff --git a/MathPad/NSIndexPath+MPAdditions.m b/MathPad/NSIndexPath+MPAdditions.m new file mode 100644 index 0000000..8bf2767 --- /dev/null +++ b/MathPad/NSIndexPath+MPAdditions.m @@ -0,0 +1,37 @@ +// +// NSIndexPath+MPRemoveFirstIndex.m +// MathPad +// +// Created by Kim Wittenburg on 23.04.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "NSIndexPath+MPAdditions.h" + +@implementation NSIndexPath (MPAdditions) + +- (NSIndexPath *)indexPathByRemovingFirstIndex +{ + if (self.length <= 1) { + return [[NSIndexPath alloc] init]; + } + NSUInteger indexes[self.length]; + [self getIndexes:indexes]; + NSUInteger newIndexes[self.length-1]; + for (NSUInteger i = 0; i < self.length-1; i++) { + newIndexes[i] = indexes[i+1]; + } + return [[NSIndexPath alloc] initWithIndexes:newIndexes length:self.length-1]; +} + +- (NSIndexPath *)indexPathByPreceedingIndex:(NSUInteger)index +{ + NSUInteger newIndexes[self.length+1]; + newIndexes[0] = index; + for (NSUInteger i = 0; i < self.length; i++) { + newIndexes[i+1] = [self indexAtPosition:i]; + } + return [[NSIndexPath alloc] initWithIndexes:newIndexes length:self.length+1]; +} + +@end diff --git a/MathPad/NSString+MPExpressionElement.h b/MathPad/NSString+MPExpressionElement.h new file mode 100644 index 0000000..59a0cdf --- /dev/null +++ b/MathPad/NSString+MPExpressionElement.h @@ -0,0 +1,14 @@ +// +// NSString+MPExpressionElement.h +// MathPad +// +// Created by Kim Wittenburg on 10.08.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +@import Foundation; +#import "MPExpressionElement.h" + +@interface NSString (MPExpressionElement) + +@end diff --git a/MathPad/NSString+MPExpressionElement.m b/MathPad/NSString+MPExpressionElement.m new file mode 100644 index 0000000..380108a --- /dev/null +++ b/MathPad/NSString+MPExpressionElement.m @@ -0,0 +1,49 @@ +// +// NSString+MPExpressionElement.m +// MathPad +// +// Created by Kim Wittenburg on 10.08.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "NSString+MPExpressionElement.h" + +@implementation NSString (MPExpressionElement) + +- (BOOL)isString +{ + return YES; +} + +- (BOOL)isFunction +{ + return NO; +} + +- (double)doubleValue +{ +#warning Unimplemented Method + return 0; +} + +- (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 diff --git a/MathPadTests/MPExpressionTests.m b/MathPadTests/MPExpressionTests.m index 35458e0..7902fb4 100644 --- a/MathPadTests/MPExpressionTests.m +++ b/MathPadTests/MPExpressionTests.m @@ -20,119 +20,119 @@ - (void)testInitialization { // Test empty expression MPExpression *testExpression = [[MPExpression alloc] init]; - XCTAssertEqual([testExpression numberOfSymbols], 0); + XCTAssertEqual(testExpression.numberOfElements, 0); // Test expression with string - testExpression = [[MPExpression alloc] initWithString:@"1234+5678"]; - XCTAssertEqual([testExpression numberOfSymbols], 1); - XCTAssertEqualObjects([testExpression symbolAtIndex:0], @"1234+5678"); + testExpression = [[MPExpression alloc] initWithElement:@"1234+5678"]; + XCTAssertEqual(testExpression.numberOfElements, 1); + XCTAssertEqualObjects([testExpression elementAtIndex:0], @"1234+5678"); // Test expression with function - testExpression = [[MPExpression alloc] initWithFunction:[[MPFunction alloc] init]]; - XCTAssertEqual([testExpression numberOfSymbols], 1); - XCTAssertEqualObjects([testExpression symbolAtIndex:0], [[MPFunction alloc] init]); + testExpression = [[MPExpression alloc] initWithElement:[[MPFunction alloc] init]]; + XCTAssertEqual([testExpression numberOfElements], 1); + XCTAssertEqualObjects([testExpression elementAtIndex:0], [[MPFunction alloc] init]); - testExpression = [[MPExpression alloc] initWithSymbols:@[@"1234", [[MPFunction alloc] init], @"17", [[MPFunction alloc] init]]]; - XCTAssertEqual([testExpression numberOfSymbols], 4); - XCTAssertEqualObjects([testExpression symbolAtIndex:2], @"17"); + testExpression = [[MPExpression alloc] initWithElements:@[@"1234", [[MPFunction alloc] init], @"17", [[MPFunction alloc] init]]]; + XCTAssertEqual([testExpression numberOfElements], 4); + XCTAssertEqualObjects([testExpression elementAtIndex:2], @"17"); // Test expression with subsequent strings - testExpression = [[MPExpression alloc] initWithSymbols:@[@"1234", @"5678"]]; - XCTAssertEqual([testExpression numberOfSymbols], 1); - XCTAssertEqualObjects([testExpression symbolAtIndex:0], @"12345678"); + testExpression = [[MPExpression alloc] initWithElements:@[@"1234", @"5678"]]; + XCTAssertEqual([testExpression numberOfElements], 1); + XCTAssertEqualObjects([testExpression elementAtIndex:0], @"12345678"); // Test expression with only empty string - testExpression = [[MPExpression alloc] initWithString:@""]; - XCTAssertEqual([testExpression numberOfSymbols], 0); + testExpression = [[MPExpression alloc] initWithElement:@""]; + XCTAssertEqual([testExpression numberOfElements], 0); } - (void)testSubexpressions { - MPExpression *testExpression = [MPExpression expressionWithSymbols:@[@"1234", [[MPFunction alloc] init], @"17", [[MPFunction alloc] init]]]; + MPExpression *testExpression = [[MPExpression alloc] initWithElements:@[@"1234", [[MPFunction alloc] init], @"17", [[MPFunction alloc] init]]]; /********** subexpressionFromIndex: **********/ // Test with start index at front - MPExpression *subexpression = [testExpression subexpressionFromIndex:0]; - XCTAssertEqual([subexpression numberOfSymbols], 4); - XCTAssertEqualObjects([subexpression symbolAtIndex:0], @"1234"); + MPExpression *subexpression = [testExpression subexpressionFromLocation:0]; + XCTAssertEqual([subexpression numberOfElements], 4); + XCTAssertEqualObjects([subexpression elementAtIndex:0], @"1234"); - // Test with start index in first symbol - subexpression = [testExpression subexpressionFromIndex:2]; - XCTAssertEqual([subexpression numberOfSymbols], 4); - XCTAssertEqualObjects([subexpression symbolAtIndex:0], @"34"); + // Test with start index in first element + subexpression = [testExpression subexpressionFromLocation:2]; + XCTAssertEqual([subexpression numberOfElements], 4); + XCTAssertEqualObjects([subexpression elementAtIndex:0], @"34"); - // Test with start index in middle symbol starting with a literal - subexpression = [testExpression subexpressionFromIndex:6]; - XCTAssertEqual([subexpression numberOfSymbols], 2); - XCTAssertEqualObjects([subexpression symbolAtIndex:0], @"7"); + // Test with start index in middle element starting with a literal + subexpression = [testExpression subexpressionFromLocation:6]; + XCTAssertEqual([subexpression numberOfElements], 2); + XCTAssertEqualObjects([subexpression elementAtIndex:0], @"7"); - // Test with start index in middle symbol starting with a function - subexpression = [testExpression subexpressionFromIndex:4]; - XCTAssertEqual([subexpression numberOfSymbols], 3); - XCTAssertEqualObjects([subexpression symbolAtIndex:0], [[MPFunction alloc] init]); + // Test with start index in middle element starting with a function + subexpression = [testExpression subexpressionFromLocation:4]; + XCTAssertEqual([subexpression numberOfElements], 3); + XCTAssertEqualObjects([subexpression elementAtIndex:0], [[MPFunction alloc] init]); - // Test with start index in last symbol - subexpression = [testExpression subexpressionFromIndex:7]; - XCTAssertEqual([subexpression numberOfSymbols], 1); - XCTAssertEqualObjects([subexpression symbolAtIndex:0], [[MPFunction alloc] init]); + // Test with start index in last element + subexpression = [testExpression subexpressionFromLocation:7]; + XCTAssertEqual([subexpression numberOfElements], 1); + XCTAssertEqualObjects([subexpression elementAtIndex:0], [[MPFunction alloc] init]); // Test with start index at end - subexpression = [testExpression subexpressionFromIndex:8]; - XCTAssertEqual([subexpression numberOfSymbols], 0); + subexpression = [testExpression subexpressionFromLocation:8]; + XCTAssertEqual([subexpression numberOfElements], 0); /********** subexpressionToIndex: **********/ // Test with end index at front - subexpression = [testExpression subexpressionToIndex:0]; - XCTAssertEqual([subexpression numberOfSymbols], 0); + subexpression = [testExpression subexpressionToLocation:0]; + XCTAssertEqual([subexpression numberOfElements], 0); - // Test with end index in first symbol - subexpression = [testExpression subexpressionToIndex:2]; - XCTAssertEqual([subexpression numberOfSymbols], 1); - XCTAssertEqualObjects([subexpression symbolAtIndex:0], @"12"); + // Test with end index in first Element + subexpression = [testExpression subexpressionToLocation:2]; + XCTAssertEqual([subexpression numberOfElements], 1); + XCTAssertEqualObjects([subexpression elementAtIndex:0], @"12"); - // Test with end index in middle symbol ending with a literal - subexpression = [testExpression subexpressionToIndex:6]; - XCTAssertEqual([subexpression numberOfSymbols], 3); - XCTAssertEqualObjects([subexpression symbolAtIndex:2], @"1"); + // Test with end index in middle Element ending with a literal + subexpression = [testExpression subexpressionToLocation:6]; + XCTAssertEqual([subexpression numberOfElements], 3); + XCTAssertEqualObjects([subexpression elementAtIndex:2], @"1"); - // Test with end index in middle symbol ending with a function - subexpression = [testExpression subexpressionToIndex:5]; - XCTAssertEqual([subexpression numberOfSymbols], 2); - XCTAssertEqualObjects([subexpression symbolAtIndex:1], [[MPFunction alloc] init]); + // Test with end index in middle Element ending with a function + subexpression = [testExpression subexpressionToLocation:5]; + XCTAssertEqual([subexpression numberOfElements], 2); + XCTAssertEqualObjects([subexpression elementAtIndex:1], [[MPFunction alloc] init]); // Test with end index at end - subexpression = [testExpression subexpressionToIndex:8]; - XCTAssertEqual([subexpression numberOfSymbols], 4); - XCTAssertEqualObjects([subexpression symbolAtIndex:3], [[MPFunction alloc] init]); + subexpression = [testExpression subexpressionToLocation:8]; + XCTAssertEqual([subexpression numberOfElements], 4); + XCTAssertEqualObjects([subexpression elementAtIndex:3], [[MPFunction alloc] init]); /********** subexpressionWithRange: **********/ // Test with empty range subexpression = [testExpression subexpressionWithRange:NSMakeRange(4, 0)]; - XCTAssertEqual([subexpression numberOfSymbols], 0); + XCTAssertEqual([subexpression numberOfElements], 0); - // Test with start and end in first symbol + // Test with start and end in first element subexpression = [testExpression subexpressionWithRange:NSMakeRange(1, 2)]; - XCTAssertEqual([subexpression numberOfSymbols], 1); - XCTAssertEqualObjects([subexpression symbolAtIndex:0], @"23"); + XCTAssertEqual([subexpression numberOfElements], 1); + XCTAssertEqualObjects([subexpression elementAtIndex:0], @"23"); // Test with start in first and end in middle after function subexpression = [testExpression subexpressionWithRange:NSMakeRange(2, 3)]; - XCTAssertEqual([subexpression numberOfSymbols], 2); - XCTAssertEqualObjects([subexpression symbolAtIndex:0], @"34"); - XCTAssertEqualObjects([subexpression symbolAtIndex:1], [[MPFunction alloc] init]); + XCTAssertEqual([subexpression numberOfElements], 2); + XCTAssertEqualObjects([subexpression elementAtIndex:0], @"34"); + XCTAssertEqualObjects([subexpression elementAtIndex:1], [[MPFunction alloc] init]); // Test with start in first and end in middle after literal subexpression = [testExpression subexpressionWithRange:NSMakeRange(2, 4)]; - XCTAssertEqual([subexpression numberOfSymbols], 3); - XCTAssertEqualObjects([subexpression symbolAtIndex:0], @"34"); - XCTAssertEqualObjects([subexpression symbolAtIndex:2], @"1"); + XCTAssertEqual([subexpression numberOfElements], 3); + XCTAssertEqualObjects([subexpression elementAtIndex:0], @"34"); + XCTAssertEqualObjects([subexpression elementAtIndex:2], @"1"); } - (void)testSubexpressionsIllegalRange { - MPExpression *testExpression = [MPExpression expressionWithSymbols:@[@"1234", [[MPFunction alloc] init], @"17", [[MPFunction alloc] init]]]; + MPExpression *testExpression = [[MPExpression alloc] initWithElements:@[@"1234", [[MPFunction alloc] init], @"17", [[MPFunction alloc] init]]]; // Test with start index beyond end @try { - [testExpression subexpressionFromIndex:10]; + [testExpression subexpressionFromLocation:10]; XCTFail(@"Should have raised an exception."); } @catch (NSException *exception) {} @@ -146,11 +146,11 @@ } - (void)testEqualExpressions { - MPExpression *expression1 = [[MPExpression alloc] initWithString:@"123"]; - MPExpression *expression2 = [[MPExpression alloc] initWithString:@"1234"]; - MPExpression *expression3 = [[MPExpression alloc] initWithFunction:[[MPFunction alloc] init]]; - MPExpression *expression4 = [[MPExpression alloc] initWithSymbols:@[[[MPFunction alloc] init], @"123"]]; - MPExpression *expression5 = [[MPExpression alloc] initWithSymbols:@[[[MPFunction alloc] init], @"123"]]; + MPExpression *expression1 = [[MPExpression alloc] initWithElement:@"123"]; + MPExpression *expression2 = [[MPExpression alloc] initWithElement:@"1234"]; + MPExpression *expression3 = [[MPExpression alloc] initWithElement:[[MPFunction alloc] init]]; + MPExpression *expression4 = [[MPExpression alloc] initWithElements:@[[[MPFunction alloc] init], @"123"]]; + MPExpression *expression5 = [[MPExpression alloc] initWithElements:@[[[MPFunction alloc] init], @"123"]]; XCTAssertNotEqualObjects(expression1, expression2); XCTAssertNotEqualObjects(expression1, expression3); @@ -159,68 +159,83 @@ XCTAssertEqualObjects(expression4, expression5); } -- (void)testAppendSymbols { - MPExpression *expression1 = [[MPExpression alloc] initWithString:@"123"]; - MPExpression *expression2 = [expression1 expressionByAppendingFunction:[[MPFunction alloc] init]]; - MPExpression *expression3 = [[MPExpression alloc] initWithSymbols:@[@"123", [[MPFunction alloc] init]]]; - - XCTAssertEqualObjects(expression2, expression3); -} - - (void)testDescription { // Test Simple Expressions - MPExpression *testExpression = [[MPExpression alloc] initWithString:@"1234"]; + MPExpression *testExpression = [[MPExpression alloc] initWithElement:@"1234"]; XCTAssertEqualObjects([testExpression description], @"1234"); - testExpression = [[MPExpression alloc] initWithFunction:[[MPFunction alloc] init]]; + testExpression = [[MPExpression alloc] initWithElement:[[MPFunction alloc] init]]; XCTAssertEqualObjects([testExpression description], @"[]"); // Test function after literal without explicit operator - testExpression = [[MPExpression alloc] initWithSymbols:@[@"123", [[MPFunction alloc] init]]]; + testExpression = [[MPExpression alloc] initWithElements:@[@"123", [[MPFunction alloc] init]]]; XCTAssertEqualObjects([testExpression description], @"123*[]"); // Test function after literal with explicit operator - testExpression = [[MPExpression alloc] initWithSymbols:@[@"123+", [[MPFunction alloc] init]]]; + testExpression = [[MPExpression alloc] initWithElements:@[@"123+", [[MPFunction alloc] init]]]; XCTAssertEqualObjects([testExpression description], @"123+[]"); // Test literal after function without explicit operator - testExpression = [[MPExpression alloc] initWithSymbols:@[[[MPFunction alloc] init], @"123"]]; + testExpression = [[MPExpression alloc] initWithElements:@[[[MPFunction alloc] init], @"123"]]; XCTAssertEqualObjects([testExpression description], @"[]*123"); // Test literal after function with explicit operator - testExpression = [[MPExpression alloc] initWithSymbols:@[[[MPFunction alloc] init], @"-123"]]; + testExpression = [[MPExpression alloc] initWithElements:@[[[MPFunction alloc] init], @"-123"]]; XCTAssertEqualObjects([testExpression description], @"[]-123"); // Test function after function without explicit operator - testExpression = [[MPExpression alloc] initWithSymbols:@[[[MPFunction alloc] init], [[MPFunction alloc] init]]]; + testExpression = [[MPExpression alloc] initWithElements:@[[[MPFunction alloc] init], [[MPFunction alloc] init]]]; XCTAssertEqualObjects([testExpression description], @"[]*[]"); // Test function after function with explicit operator - testExpression = [[MPExpression alloc] initWithSymbols:@[[[MPFunction alloc] init], @"-", [[MPFunction alloc] init]]]; + testExpression = [[MPExpression alloc] initWithElements:@[[[MPFunction alloc] init], @"-", [[MPFunction alloc] init]]]; XCTAssertEqualObjects([testExpression description], @"[]-[]"); // Test whitespaces in literal - testExpression = [[MPExpression alloc] initWithSymbols:@[@" 123 + ", [[MPFunction alloc] init]]]; + testExpression = [[MPExpression alloc] initWithElements:@[@" 123 + ", [[MPFunction alloc] init]]]; XCTAssertEqualObjects([testExpression description], @"123 +[]"); } - (void)testCopying { - MPExpression *baseExpression = [[MPExpression alloc] initWithFunction:[[MPFunction alloc] init]]; + MPExpression *baseExpression = [[MPExpression alloc] initWithElement:[[MPFunction alloc] init]]; MPExpression *copy = [baseExpression copy]; + XCTAssertEqual(baseExpression.numberOfElements, copy.numberOfElements); XCTAssertNotEqual(copy, baseExpression); - XCTAssertNotEqual([baseExpression symbolAtIndex:0], [copy symbolAtIndex:0]); + XCTAssertNotEqual([baseExpression elementAtIndex:0], [copy elementAtIndex:0]); + XCTAssertEqualObjects(baseExpression, copy); } -- (void)testMutableCopying { - MPExpression *baseExpression = [[MPExpression alloc] initWithString:@"123"]; - MPMutableExpression *expression1 = [baseExpression mutableCopy]; - [expression1 appendFunction:[[MPFunction alloc] init]]; - MPExpression *expression2 = [[MPExpression alloc] initWithSymbols:@[@"123", [[MPFunction alloc] init]]]; +- (void)testMutating { + MPExpression *testExpression = [[MPExpression alloc] initWithElements:@[@"1234", [[MPFunction alloc] init], @"5678", [[MPFunction alloc] init]]]; - XCTAssertEqualObjects(expression1, expression2); - XCTAssertNotEqualObjects(baseExpression, expression1); + [testExpression appendElement:@"90"]; + XCTAssertEqual([testExpression numberOfElements], 5); + // 1234 [] 5678 [] 90 + + [testExpression deleteElementsInRange:NSMakeRange(2, 4)]; + XCTAssertEqual([testExpression numberOfElements], 3); + // 12678 [] 90 + + [testExpression insertElement:[[MPFunction alloc] init] + atLocation:2]; + XCTAssertEqual([testExpression numberOfElements], 5); + // 12 [] 678 [] 90 + + [testExpression replaceElementsInRange:NSMakeRange(2, 5) + withElements:@[[[MPFunction alloc] init]]]; + XCTAssertEqual([testExpression numberOfElements], 3); + // 12 [] 90 +} + +- (void)testInvalidMutatingRange { + @try { + MPExpression *testExpression = [[MPExpression alloc] initWithElements:@[@"1234", [[MPFunction alloc] init], @"5678", [[MPFunction alloc] init]]]; + [testExpression deleteElementsInRange:NSMakeRange(5, 17)]; + XCTFail(@"Should have raised an exception"); + } + @catch (NSException *exception) {} } // TODO: Test evaluating expressions