From 8d29bfab786a6299a36a6bd1ef8c45fd232828a8 Mon Sep 17 00:00:00 2001 From: Humza Shahid Date: Mon, 29 Sep 2025 10:28:03 +0100 Subject: [PATCH] adjust nfa to return all matches in string, instead of just testing for one match and then returning true --- fcore/search-list/nfa | Bin 0 -> 151080 bytes fcore/search-list/nfa.sml | 19 +- shf.mlb | 1 + temp.txt | 3401 ++++++++++++++++++++++++++++++++++++- 4 files changed, 3410 insertions(+), 11 deletions(-) create mode 100755 fcore/search-list/nfa diff --git a/fcore/search-list/nfa b/fcore/search-list/nfa new file mode 100755 index 0000000000000000000000000000000000000000..54120a603585c2598a8c6be30d99dd9095353e65 GIT binary patch literal 151080 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 sSUWiMtHTM~v(muRgEc*9H#C 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 VbMVf(dih`NG38qW1p<%ie*wfh+8+P_ literal 0 HcmV?d00001 diff --git a/fcore/search-list/nfa.sml b/fcore/search-list/nfa.sml index 082be16..d313602 100644 --- a/fcore/search-list/nfa.sml +++ b/fcore/search-list/nfa.sml @@ -135,25 +135,28 @@ struct | _ => raise Fail "nfa.sml 69: not char literal or concat or alternation" - fun loop (pos, str, nfa, origNfa, startPos) = + fun loop (pos, str, nfa, origNfa, startPos, acc) = if pos = String.size str then - false + PersistentVector.toVector acc else let val chr = String.sub (str, pos) val (nfa, state) = rebuild (nfa, chr, pos) in case state of - VALID _ => true + VALID finishIdx => + let val acc = PersistentVector.append (pos, acc) + in loop (finishIdx, str, origNfa, origNfa, finishIdx, acc) + end | INVALID => (* backtrack to another position in the string * to see if the NFA matches that portion of the string *) - loop (startPos + 1, str, origNfa, origNfa, startPos + 1) - | UNTESTED => loop (pos + 1, str, nfa, origNfa, startPos) + loop (startPos + 1, str, origNfa, origNfa, startPos + 1, acc) + | UNTESTED => loop (pos + 1, str, nfa, origNfa, startPos, acc) end in - fun hasAnyMatch (str, nfa) = - loop (0, str, nfa, nfa, 0) + fun getMatches (str, nfa) = + loop (0, str, nfa, nfa, 0, PersistentVector.empty) end end @@ -275,5 +278,5 @@ struct end val parse = ParseNfa.parse - val hasAnyMatch = NfaMatch.hasAnyMatch + val getMatches = NfaMatch.getMatches end diff --git a/shf.mlb b/shf.mlb index ae83ab3..8bc8529 100644 --- a/shf.mlb +++ b/shf.mlb @@ -19,6 +19,7 @@ end fcore/escape-string.sml fcore/bin-search.sml +fcore/search-list/nfa.sml fcore/search-list.sml fcore/app-type.sml diff --git a/temp.txt b/temp.txt index 0056b4a..f323960 100644 --- a/temp.txt +++ b/temp.txt @@ -1,3 +1,3398 @@ -hello -world -again +signature LINE_GAP = +sig + type t = + { idx: int + , textLength: int + , leftStrings: string list + , rightStrings: string list + + , line: int + , lineLength: int + , leftLines: int vector list + , rightLines: int vector list + } + + val empty: t + + val fromString: string -> t + val toString: t -> string + + val substring: int * int * t -> string + val nullSubstring: int * int * t -> string + val substringWithEnd: int * int * t * string -> string + + val delete: int * int * t -> t + val insert: int * string * t -> t + val append: string * t -> t + + val goToStart: t -> t + val goToEnd: t -> t + val goToIdx: int * t -> t + val goToLine: int * t -> t + + val idxToLineNumber: int * t -> int + val lineNumberToIdx: int * t -> int + + (* for testing *) + val verifyIndex: t -> unit + val verifyLines: t -> unit +end + +structure LineGap :> LINE_GAP = +struct + local + fun helpCountLineBreaks (pos, acc, str) = + if pos < 0 then + Vector.fromList acc + else + let + val chr = String.sub (str, pos) + in + if chr = #"\n" then + (* Is this a \r\n pair? Then the position of \r should be consed. *) + if pos = 0 then + Vector.fromList (0 :: acc) + else + let + val prevChar = String.sub (str, pos - 1) + in + if prevChar = #"\r" then + helpCountLineBreaks (pos - 2, (pos - 1) :: acc, str) + else + helpCountLineBreaks (pos - 1, pos :: acc, str) + end + else if chr = #"\r" then + helpCountLineBreaks (pos - 1, pos :: acc, str) + else + helpCountLineBreaks (pos - 1, acc, str) + end + in + fun countLineBreaks str = + helpCountLineBreaks (String.size str - 1, [], str) + end + + type t = + { idx: int + , textLength: int + , leftStrings: string list + , rightStrings: string list + + , line: int + , lineLength: int + , leftLines: int vector list + , rightLines: int vector list + } + + val stringLimit = 1024 + val vecLimit = 32 + + val empty = + { idx = 0 + , textLength = 0 + , leftStrings = [] + , rightStrings = [] + , line = 0 + , lineLength = 0 + , leftLines = [] + , rightLines = [] + } + + fun fromString str = + let + val linebreaks = countLineBreaks str + in + { idx = 0 + , textLength = String.size str + , leftStrings = [] + , rightStrings = [str] + , line = 0 + , lineLength = Vector.length linebreaks + , leftLines = [] + , rightLines = [linebreaks] + } + end + + local + fun helpToString (acc, input) = + case input of + hd :: tl => helpToString (hd :: acc, tl) + | [] => String.concat acc + in + fun toString ({leftStrings, rightStrings, ...}: t) = + helpToString (rightStrings, leftStrings) + end + + fun isInLimit (s1, s2, v1, v2) = + String.size s1 + String.size s2 <= stringLimit + andalso Vector.length v1 + Vector.length v2 <= vecLimit + + fun isThreeInLimit (s1, s2, s3, v1, v2) = + String.size s1 + String.size s2 + String.size s3 <= stringLimit + andalso Vector.length v1 + Vector.length v2 <= vecLimit + + (* Binary search. If value isn't found, returns the value before it. *) + local + fun reverseLinearSearch (findNum, idx, lines) = + if idx < 0 then + idx + else + let + val curVal = Vector.sub (lines, idx) + in + if curVal < findNum then idx + else reverseLinearSearch (findNum, idx, lines) + end + + fun helpBinSearch (findNum, lines, low, high) = + let + val mid = low + ((high - low) div 2) + in + if high >= low then + let + val midVal = Vector.sub (lines, mid) + in + if midVal = findNum then + mid + else if midVal < findNum then + helpBinSearch (findNum, lines, mid + 1, high) + else + helpBinSearch (findNum, lines, low, mid - 1) + end + else + reverseLinearSearch (findNum, mid, lines) + end + in + fun binSearch (findNum, lines) = + if Vector.length lines = 0 then 0 + else helpBinSearch (findNum, lines, 0, Vector.length lines - 1) + end + + (* Binary search. If value isn't found, returns the value after it. *) + local + fun forwardLinearSearch (findNum, idx, lines) = + if idx = Vector.length lines then + idx + else + let + val curVal = Vector.sub (lines, idx) + in + if curVal > findNum then idx + else forwardLinearSearch (findNum, idx + 1, lines) + end + + fun helpBinSearch (findNum, lines, low, high) = + let + val mid = low + ((high - low) div 2) + in + if high >= low then + let + val midVal = Vector.sub (lines, mid) + in + if midVal = findNum then + mid + else if midVal < findNum then + helpBinSearch (findNum, lines, mid + 1, high) + else + helpBinSearch (findNum, lines, low, mid - 1) + end + else if mid >= 0 then + forwardLinearSearch (findNum, mid, lines) + else + 0 + end + in + fun forwardBinSearch (findNum, lines) = + if Vector.length lines = 0 then 0 + else helpBinSearch (findNum, lines, 0, Vector.length lines - 1) + end + + (* Insert function and helper functions for it. *) + local + fun insWhenIdxAndCurIdxAreEqual + ( newString + , newLines + , curIdx + , curLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) : t = + case (leftStrings, leftLines) of + (strHd :: strTl, lineHd :: lineTl) => + if isInLimit (strHd, newString, lineHd, newLines) then + (* Fits in limit, so we can add to existing string/line vector.*) + let + val newIdx = curIdx + String.size newString + val newStrHd = strHd ^ newString + val newLeftString = newStrHd :: strTl + val newLine = curLine + Vector.length newLines + + val newLinesHd = + Vector.tabulate + ( Vector.length lineHd + Vector.length newLines + , fn idx => + if idx < Vector.length lineHd then + Vector.sub (lineHd, idx) + else + Vector.sub (newLines, idx - Vector.length lineHd) + + String.size strHd + ) + val newLeftLines = newLinesHd :: lineTl + in + { idx = newIdx + , textLength = textLength + , line = newLine + , lineLength = lineLength + , leftStrings = newLeftString + , leftLines = newLeftLines + , rightStrings = rightStrings + , rightLines = rightLines + } + end + else + (* Does not fit in limit, so cons instead.*) + { idx = curIdx + String.size newString + , textLength = textLength + , line = curLine + Vector.length newLines + , lineLength = lineLength + , leftStrings = newString :: leftStrings + , leftLines = newLines :: leftLines + , rightStrings = rightStrings + , rightLines = rightLines + } + | (_, _) => + (* + * Because movements between string/line lists in the gap buffer + * always move together, we know that either list being empty + * also means that the other one is empty. + * So we don't need to perform addition or consing. + *) + { idx = String.size newString + , textLength = textLength + , line = Vector.length newLines + , lineLength = lineLength + , leftStrings = [newString] + , leftLines = [newLines] + , rightStrings = rightStrings + , rightLines = rightLines + } + + fun insInLeftList + ( idx + , newString + , newLines + , curIdx + , curLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , prevIdx + , leftStringsHd + , leftStringsTl + , leftLinesHd + , leftLinesTl + , textLength + , lineLength + ) : t = + if idx = prevIdx then + (* Need to insert at the start of the left list. *) + if isInLimit (newString, leftStringsHd, newLines, leftLinesHd) then + let + (* Create new vector, adjusting indices as needed. *) + val joinedLines = + Vector.tabulate + ( Vector.length newLines + Vector.length leftLinesHd + , fn idx => + if idx < Vector.length newLines then + Vector.sub (newLines, idx) + else + Vector.sub (leftLinesHd, idx - Vector.length newLines) + + String.size newString + ) + in + { idx = curIdx + String.size newString + , textLength = textLength + , line = curLine + Vector.length newLines + , lineLength = lineLength + , leftStrings = (newString ^ leftStringsHd) :: leftStringsTl + , leftLines = joinedLines :: leftLinesTl + , rightStrings = rightStrings + , rightLines = rightLines + } + end + else + (* Just cons everything; no way we can join while staying in limit. *) + { idx = curIdx + String.size newString + , textLength = textLength + , line = curLine + Vector.length newLines + , lineLength = lineLength + , leftStrings = leftStringsHd :: newString :: leftStringsTl + , leftLines = leftLinesHd :: newLines :: leftLinesTl + , rightStrings = rightStrings + , rightLines = rightLines + } + else + (* Need to insert in the middle of the left list. *) + let + (* Get string slices on both sides. *) + val strLength = idx - prevIdx + val strSub1 = String.substring (leftStringsHd, 0, strLength) + val strSub2 = String.substring + (leftStringsHd, strLength, String.size leftStringsHd - strLength) + val midpoint = binSearch (String.size strSub1 - 1, leftLinesHd) + in + if + isThreeInLimit (strSub1, newString, strSub2, leftLinesHd, newLines) + then + (* Join three strings together. *) + let + val joinedString = String.concat [strSub1, newString, strSub2] + val joinedLines = + if Vector.length leftLinesHd > 0 then + Vector.tabulate + ( Vector.length leftLinesHd + Vector.length newLines + , fn idx => + if idx <= midpoint then + Vector.sub (leftLinesHd, idx) + else if idx <= midpoint + Vector.length newLines then + Vector.sub (newLines, (idx - midpoint) - 1) + + String.size strSub1 + else + Vector.sub + (leftLinesHd, (idx - Vector.length newLines)) + + String.size newString + ) + else + Vector.map (fn el => el + String.size strSub1) newLines + in + { idx = curIdx + String.size newString + , textLength = textLength + , line = curLine + Vector.length newLines + , lineLength = lineLength + , leftStrings = joinedString :: leftStringsTl + , leftLines = joinedLines :: leftLinesTl + , rightStrings = rightStrings + , rightLines = rightLines + } + end + else if + String.size strSub1 + String.size newString <= stringLimit + andalso midpoint + Vector.length newLines <= vecLimit + then + (* If we can join newString/lines with sub1 while + * staying in limit. *) + if midpoint >= 0 then + (* Implicit: a binSearch match was found. *) + let + val newLeftLinesLength = midpoint + 1 + Vector.length newLines + val newLeftLines = + Vector.tabulate (newLeftLinesLength, fn idx => + if idx <= midpoint then + Vector.sub (leftLinesHd, idx) + else + Vector.sub (newLines, idx - (midpoint + 1)) + + String.size strSub1) + + val newRightLines = + Vector.tabulate + ( (Vector.length leftLinesHd - midpoint) - 1 + , fn idx => + Vector.sub (leftLinesHd, idx + midpoint + 1) + - String.size strSub1 + ) + in + { idx = prevIdx + String.size strSub1 + String.size newString + , textLength = textLength + , line = + (curLine - Vector.length leftLinesHd) + + Vector.length newLeftLines + , lineLength = lineLength + , leftStrings = (strSub1 ^ newString) :: leftStringsTl + , leftLines = newLeftLines :: leftLinesTl + , rightStrings = strSub2 :: rightStrings + , rightLines = newRightLines :: rightLines + } + end + else + let + (* No binSearch result found. *) + val newLeftLines = + Vector.map (fn el => el + String.size strSub1) newLines + val newRightLines = + Vector.map (fn idx => idx - String.size strSub1) leftLinesHd + in + { idx = prevIdx + String.size strSub1 + String.size newString + , textLength = textLength + , line = + (curLine - Vector.length leftLinesHd) + + Vector.length newLeftLines + , lineLength = lineLength + , leftStrings = (strSub1 ^ newString) :: leftStringsTl + , leftLines = newLeftLines :: leftLinesTl + , rightStrings = strSub2 :: rightStrings + , rightLines = newRightLines :: rightLines + } + end + else if + String.size newString + String.size strSub2 <= stringLimit + andalso + (Vector.length leftLinesHd - midpoint) + Vector.length newLines + <= vecLimit + then + (* If we can join newString/line with sub2 while staying + * in limit. *) + let + val newLeftLines = + if midpoint >= 0 andalso Vector.length leftLinesHd > 0 then + let + val newLeftLines = VectorSlice.slice + (leftLinesHd, 0, SOME (midpoint + 1)) + in + VectorSlice.vector newLeftLines + end + else + Vector.fromList [] + + val newRightLines = + Vector.tabulate + ( (Vector.length leftLinesHd - Vector.length newLeftLines) + + Vector.length newLines + , fn idx => + if idx < Vector.length newLines then + Vector.sub (newLines, idx) + else + Vector.sub + ( leftLinesHd + , (idx - Vector.length newLines) + + Vector.length newLeftLines + ) - String.size strSub1 + String.size newString + ) + in + { idx = prevIdx + String.size strSub1 + , textLength = textLength + , line = (curLine - Vector.length leftLinesHd) + midpoint + , lineLength = lineLength + , leftStrings = strSub1 :: leftStringsTl + , leftLines = newLeftLines :: leftLinesTl + , rightStrings = (newString ^ strSub2) :: rightStrings + , rightLines = newRightLines :: rightLines + } + end + else + (* Can't join on either side while staying in limit. *) + let + val lineSub1 = + if midpoint >= 0 andalso Vector.length leftLinesHd > 0 then + let + val lineSub1 = VectorSlice.slice + (leftLinesHd, 0, SOME (midpoint + 1)) + in + VectorSlice.vector lineSub1 + end + else + Vector.fromList [] + + val lineSub2Length = + Vector.length leftLinesHd - Vector.length lineSub1 + val lineSub2 = Vector.tabulate (lineSub2Length, fn idx => + Vector.sub (leftLinesHd, idx + Vector.length lineSub1) + - String.size strSub1) + in + { idx = prevIdx + String.size strSub1 + String.size newString + , textLength = textLength + , line = + (curLine - String.size leftStringsHd) + midpoint + + Vector.length newLines + , lineLength = lineLength + , leftStrings = newString :: strSub1 :: leftStringsTl + , leftLines = newLines :: lineSub1 :: leftLinesTl + , rightStrings = strSub2 :: rightStrings + , rightLines = lineSub2 :: rightLines + } + end + end + + fun moveLeftAndIns + ( idx + , newString + , newLines: int vector + , curIdx + , curLine + , leftStrings: string list + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) = + case (leftStrings, leftLines) of + (leftStringsHd :: leftStringsTl, leftLinesHd :: leftLinesTl) => + let + val prevIdx = curIdx - String.size leftStringsHd + in + if idx < prevIdx then + (* + * Need to move leftward. + * The rather complicated code below is an optimisation checking + * if we can minimise the number of lists in the gap buffer + * by concatenating lines/strings together while staying + * under the limit. + * *) + (case (rightStrings, rightLines) of + ( rightStringsHd :: rightStringsTl + , rightLinesHd :: rightLinesTl + ) => + if + isInLimit + ( leftStringsHd + , rightStringsHd + , leftLinesHd + , rightLinesHd + ) + then + let + val prevLine = curLine - Vector.length leftLinesHd + val newRightStringsHd = leftStringsHd ^ rightStringsHd + + val newRightLinesHd = + Vector.tabulate + ( Vector.length leftLinesHd + + Vector.length rightLinesHd + , fn idx => + if idx < Vector.length leftLinesHd then + Vector.sub (leftLinesHd, idx) + else + Vector.sub + ( rightLinesHd + , idx - Vector.length leftLinesHd + ) + String.size leftStringsHd + ) + in + moveLeftAndIns + ( idx + , newString + , newLines + , prevIdx + , prevLine + , leftStringsTl + , leftLinesTl + , newRightStringsHd :: rightStringsTl + , newRightLinesHd :: rightLinesTl + , textLength + , lineLength + ) + end + else + moveLeftAndIns + ( idx + , newString + , newLines + , prevIdx + , curLine - Vector.length leftLinesHd + , leftStringsTl + , leftLinesTl + , leftStringsHd :: rightStrings + , leftLinesHd :: rightLines + , textLength + , lineLength + ) + | (_, _) => + moveLeftAndIns + ( idx + , newString + , newLines + , prevIdx + , curLine - Vector.length newLines + , leftStringsTl + , leftLinesTl + , leftStringsHd :: rightStrings + , leftLinesHd :: rightLines + , textLength + , lineLength + )) + else + (* Insertion is somewhere between the head of the left list, + * and the tail of the left list. *) + insInLeftList + ( idx + , newString + , newLines + , curIdx + , curLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , prevIdx + , leftStringsHd + , leftStringsTl + , leftLinesHd + , leftLinesTl + , textLength + , lineLength + ) + end + | (_, _) => + (* Left list is empty, so need to cons or join. + * Just set left string/list as newString/newLines. *) + { idx = String.size newString + , textLength = textLength + , line = Vector.length newLines + , lineLength = lineLength + , leftStrings = [newString] + , leftLines = [newLines] + , rightStrings = rightStrings + , rightLines = rightLines + } + + fun insInRightList + ( idx + , newString + , newLines + , curIdx + , curLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , nextIdx + , rightStringsHd + , rightStringsTl + , rightLinesHd: int vector + , rightLinesTl + , textLength + , lineLength + ) : t = + if idx = nextIdx then + (* Need to put newString/newLines at the end of the right list's hd. *) + if isInLimit (newString, rightStringsHd, newLines, rightLinesHd) then + (* Allocate new string because we can do so while staying in limit. *) + let + val newRightStringsHd = rightStringsHd ^ newString + val newRightLinesHd = + Vector.tabulate + ( Vector.length rightLinesHd + Vector.length newLines + , fn idx => + if idx < Vector.length rightLinesHd then + Vector.sub (rightLinesHd, idx) + else + Vector.sub (newLines, idx - Vector.length rightLinesHd) + + String.size rightStringsHd + ) + in + { idx = curIdx + , textLength = textLength + , line = curLine + , lineLength = lineLength + , leftStrings = leftStrings + , leftLines = leftLines + , rightStrings = newRightStringsHd :: rightStringsTl + , rightLines = newRightLinesHd :: rightLinesTl + } + end + else + (* Cons newString and newLines to after-the-head, + * because we can't join while staying in the limit.*) + { idx = curIdx + , textLength = textLength + , line = curLine + , lineLength = lineLength + , leftStrings = leftStrings + , leftLines = leftLines + , rightStrings = rightStringsHd :: newString :: rightStringsTl + , rightLines = rightLinesHd :: newLines :: rightLinesTl + } + else + (* Have to split rightStringsHd and rightLinesHd in the middle. *) + let + val strLength = idx - curIdx + val strSub1 = String.substring (rightStringsHd, 0, strLength) + val strSub2 = String.substring + (rightStringsHd, strLength, String.size rightStringsHd - strLength) + val midpoint = binSearch (String.size strSub1 - 1, rightLinesHd) + in + if + isThreeInLimit (strSub1, newString, strSub2, rightLinesHd, newLines) + then + (* Join three strings together. *) + let + val newRightStringsHd = + String.concat [strSub1, newString, strSub2] + val newRightLinesHd = + if Vector.length rightLinesHd > 0 then + Vector.tabulate + ( Vector.length rightLinesHd + Vector.length newLines + , fn idx => + if idx <= midpoint then + Vector.sub (rightLinesHd, idx) + else if idx <= midpoint + Vector.length newLines then + Vector.sub (newLines, (idx - midpoint) - 1) + + String.size strSub1 + else + Vector.sub + (rightLinesHd, (idx - Vector.length newLines)) + + String.size newString + ) + else + Vector.map (fn el => el + String.size strSub1) newLines + in + { idx = curIdx + , textLength = textLength + , line = curLine + , lineLength = lineLength + , leftStrings = leftStrings + , leftLines = leftLines + , rightStrings = newRightStringsHd :: rightStringsTl + , rightLines = newRightLinesHd :: rightLinesTl + } + end + else if + String.size strSub1 + String.size newString <= stringLimit + andalso midpoint + Vector.length newLines <= vecLimit + then + (* If we can join newString/lines with sub1 while + * staying in limit. *) + if midpoint >= 0 then + let + (* Implicit: a binSearch match was found. *) + val newLeftStringsHd = strSub1 ^ newString + val newLeftLinesLength = midpoint + 1 + Vector.length newLines + val newLeftLinesHd = + Vector.tabulate (newLeftLinesLength, fn idx => + if idx <= midpoint then + Vector.sub (rightLinesHd, idx) + else + Vector.sub (newLines, idx - (midpoint + 1)) + + String.size strSub1) + + val newRightLinesHd = + Vector.tabulate + ( (Vector.length rightLinesHd - midpoint) - 1 + , fn idx => + Vector.sub (rightLinesHd, idx + midpoint + 1) + - String.size strSub1 + ) + in + { idx = curIdx + String.size newLeftStringsHd + , textLength = textLength + , line = curLine + Vector.length newLeftLinesHd + , lineLength = lineLength + , leftStrings = newLeftStringsHd :: leftStrings + , leftLines = newLeftLinesHd :: leftLines + , rightStrings = strSub2 :: rightStringsTl + , rightLines = newRightLinesHd :: rightLinesTl + } + end + else + let + (* No binSearch match found. *) + val newLeftStringsHd = strSub1 ^ newString + val newLeftLinesHd = + Vector.map (fn el => el + String.size strSub1) newLines + val newRightLinesHd = + Vector.map (fn idx => idx - String.size strSub1) rightLinesHd + in + { idx = curIdx + String.size newLeftStringsHd + , textLength = textLength + , line = curLine + Vector.length newLeftLinesHd + , lineLength = lineLength + , leftStrings = newLeftStringsHd :: leftStrings + , leftLines = newLeftLinesHd :: leftLines + , rightStrings = strSub2 :: rightStringsTl + , rightLines = newRightLinesHd :: rightLinesTl + } + end + else if + String.size newString + String.size strSub2 <= stringLimit + andalso + (Vector.length rightLinesHd - midpoint) + Vector.length newLines + <= vecLimit + then + (* If we can join newString/line with sub2 while staying + * in limit. *) + let + val newLeftLinesHd = + if midpoint >= 0 then + let + val newLeftLinesHd = VectorSlice.slice + (rightLinesHd, 0, SOME (midpoint + 1)) + in + VectorSlice.vector newLeftLinesHd + end + else + Vector.fromList [] + + val newRightStringsHd = newString ^ strSub2 + val newRightLinesHd = + Vector.tabulate + ( (Vector.length newLines + Vector.length rightLinesHd) + - Vector.length newLeftLinesHd + , fn idx => + if idx < Vector.length newLines then + Vector.sub (newLines, idx) + else + (Vector.sub + ( rightLinesHd + , (idx - Vector.length newLines) + + Vector.length newLeftLinesHd + ) - String.size strSub1) + String.size newString + ) + in + { idx = curIdx + String.size strSub1 + , textLength = textLength + , line = curLine + Vector.length newLeftLinesHd + , lineLength = lineLength + , leftStrings = strSub1 :: leftStrings + , leftLines = newLeftLinesHd :: leftLines + , rightStrings = newRightStringsHd :: rightStringsTl + , rightLines = newRightLinesHd :: rightLinesTl + } + end + else + (* Can't join on either side while staying in limit. *) + let + val lineSub1 = + if midpoint >= 0 andalso Vector.length rightLinesHd > 0 then + let + val lineSub1 = VectorSlice.slice + (rightLinesHd, 0, SOME (midpoint + 1)) + in + VectorSlice.vector lineSub1 + end + else + Vector.fromList [] + + val lineSub2Length = + Vector.length rightLinesHd - Vector.length lineSub1 + val lineSub2 = Vector.tabulate (lineSub2Length, fn idx => + Vector.sub (rightLinesHd, idx + Vector.length lineSub1) + - String.size strSub1) + in + { idx = curIdx + String.size strSub1 + String.size newString + , textLength = textLength + , line = curLine + Vector.length newLines + Vector.length lineSub1 + , lineLength = lineLength + , leftStrings = newString :: strSub1 :: leftStrings + , leftLines = newLines :: lineSub1 :: leftLines + , rightStrings = strSub2 :: rightStringsTl + , rightLines = lineSub2 :: rightLinesTl + } + end + end + + fun moveRightAndIns + ( idx + , newString + , newLines + , curIdx + , curLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) = + case (rightStrings, rightLines) of + (rightStringsHd :: rightStringsTl, rightLinesHd :: rightLinesTl) => + let + val nextIdx = curIdx + String.size rightStringsHd + in + if idx > nextIdx then + (* Need to move rightward. *) + (case (leftStrings, leftLines) of + (leftStringsHd :: leftStringsTl, leftLinesHd :: leftLinesTl) => + if + isInLimit + ( leftStringsHd + , rightStringsHd + , leftLinesHd + , rightLinesHd + ) + then + let + val nextLine = curLine + Vector.length rightLinesHd + val newLeftStringsHd = leftStringsHd ^ rightStringsHd + val newLeftLinesHd = + Vector.tabulate + ( Vector.length leftLinesHd + + Vector.length rightLinesHd + , fn idx => + if idx < Vector.length leftLinesHd then + Vector.sub (leftLinesHd, idx) + else + Vector.sub + ( rightLinesHd + , idx - Vector.length leftLinesHd + ) + String.size leftStringsHd + ) + in + moveRightAndIns + ( idx + , newString + , newLines + , nextIdx + , nextLine + , newLeftStringsHd :: leftStringsTl + , newLeftLinesHd :: leftLinesTl + , rightStringsTl + , rightLinesTl + , textLength + , lineLength + ) + end + else + moveRightAndIns + ( idx + , newString + , newLines + , nextIdx + , curLine + Vector.length rightLinesHd + , rightStringsHd :: leftStrings + , rightLinesHd :: leftLines + , rightStringsTl + , rightLinesTl + , textLength + , lineLength + ) + | (_, _) => + moveRightAndIns + ( idx + , newString + , newLines + , nextIdx + , curLine + Vector.length rightLinesHd + , rightStringsHd :: leftStrings + , rightLinesHd :: leftLines + , rightStringsTl + , rightLinesTl + , textLength + , lineLength + )) + else + (* Need to insert in the middle of the right string's hd. *) + insInRightList + ( idx + , newString + , newLines + , curIdx + , curLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , nextIdx + , rightStringsHd + , rightStringsTl + , rightLinesHd + , rightLinesTl + , textLength + , lineLength + ) + end + | (_, _) => + (* Right string/line is empty. *) + { idx = curIdx + , textLength = textLength + , line = curLine + , lineLength = lineLength + , leftStrings = leftStrings + , leftLines = leftLines + , rightStrings = [newString] + , rightLines = [newLines] + } + + fun ins + ( idx + , newString + , newLines + , curIdx + , curLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) : t = + if curIdx = idx then + insWhenIdxAndCurIdxAreEqual + ( newString + , newLines + , curIdx + , curLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) + else if idx < curIdx then + moveLeftAndIns + ( idx + , newString + , newLines + , curIdx + , curLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) + else + (* idx > curIdx. *) + moveRightAndIns + ( idx + , newString + , newLines + , curIdx + , curLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) + in + fun insert (idx, newString, buffer: t) = + let + val newLines = countLineBreaks newString + val newTextLength = #textLength buffer + String.size newString + val newLineLength = #lineLength buffer + Vector.length newLines + in + ins + ( idx + , newString + , newLines + , #idx buffer + , #line buffer + , #leftStrings buffer + , #leftLines buffer + , #rightStrings buffer + , #rightLines buffer + , newTextLength + , newLineLength + ) + end + end + + fun helpGoToEndAndAppend + ( newString + , newLines + , idx + , leftStrings + , rightStrings + , line + , leftLines + , rightLines + , textLength + , lineLength + ) = + case (rightStrings, rightLines) of + (rStrHd :: rStrTl, rLnHd :: rLnTl) => + (* move gap rightwards one node, + * and join with right head with left if possible *) + (case (leftStrings, leftLines) of + (lStrHd :: lStrTl, lLnHd :: lLnTl) => + if isInLimit (lStrHd, rStrHd, lLnHd, rLnHd) then + let + val newLstrHd = lStrHd ^ rStrHd + val newLlnHd = + Vector.tabulate + ( Vector.length lLnHd + Vector.length rLnHd + , fn lnIdx => + if lnIdx < Vector.length lLnHd then + Vector.sub (lLnHd, lnIdx) + else + Vector.sub (rLnHd, lnIdx - Vector.length lLnHd) + + String.size lStrHd + ) + in + helpGoToEndAndAppend + ( newString + , newLines + , idx + String.size rStrHd + , newLstrHd :: lStrTl + , rStrTl + , line + Vector.length rLnHd + , newLlnHd :: lLnTl + , rLnTl + , textLength + , lineLength + ) + end + else + helpGoToEndAndAppend + ( newString + , newLines + , idx + String.size rStrHd + , rStrHd :: leftStrings + , rStrTl + , line + Vector.length rLnHd + , rLnHd :: leftLines + , rLnTl + , textLength + , lineLength + ) + | (_, _) => + (* left side is empty; we are at start *) + helpGoToEndAndAppend + ( newString + , newLines + , String.size rStrHd + , [rStrHd] + , rStrTl + , Vector.length rLnHd + , [rLnHd] + , rLnTl + , textLength + , lineLength + )) + | (_, _) => + (* we have reached the end, and right side is empty *) + (case (leftStrings, leftLines) of + (lStrHd :: lStrTl, lLnHd :: lLnTl) => + if isInLimit (lStrHd, newString, lLnHd, newLines) then + (* join new string and line with left *) + let + val newLstrHd = lStrHd ^ newString + val newLlnHd = + Vector.tabulate + ( Vector.length lLnHd + Vector.length newLines + , fn lnIdx => + if lnIdx < Vector.length lLnHd then + Vector.sub (lLnHd, lnIdx) + else + Vector.sub (newLines, lnIdx - Vector.length lLnHd) + + String.size lStrHd + ) + in + { idx = idx + String.size newString + , textLength = textLength + , line = line + Vector.length newLines + , lineLength = lineLength + , leftStrings = newLstrHd :: lStrTl + , leftLines = newLlnHd :: lLnTl + , rightStrings = [] + , rightLines = [] + } + end + else + { idx = idx + String.size newString + , textLength = textLength + , line = line + Vector.length newLines + , lineLength = lineLength + , leftStrings = newString :: leftStrings + , leftLines = newLines :: leftLines + , rightStrings = [] + , rightLines = [] + } + | (_, _) => + { idx = idx + String.size newString + , textLength = textLength + , line = line + Vector.length newLines + , lineLength = lineLength + , leftStrings = newString :: leftStrings + , leftLines = newLines :: leftLines + , rightStrings = [] + , rightLines = [] + }) + + fun append (newString, buffer) = + let + val + { idx + , line + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + } = buffer + val newTextLength = textLength + String.size newString + val newLines = countLineBreaks newString + val newLineLength = lineLength + Vector.length newLines + in + helpGoToEndAndAppend + ( newString + , newLines + , idx + , leftStrings + , rightStrings + , line + , leftLines + , rightLines + , newTextLength + , newLineLength + ) + end + + (* Delete function and helper functions for it. *) + local + fun deleteRightFromHere + ( origIdx + , origLine + , moveIdx + , finish + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) = + case (rightStrings, rightLines) of + (rightStringsHd :: rightStringsTl, rightLinesHd :: rightLinesTl) => + let + val nextIdx = moveIdx + String.size rightStringsHd + in + if nextIdx < finish then + (* Remove string/line head and keep moving right. *) + deleteRightFromHere + ( origIdx + , origLine + , nextIdx + , finish + , leftStrings + , leftLines + , rightStringsTl + , rightLinesTl + , textLength - String.size rightStringsHd + , lineLength - Vector.length rightLinesHd + ) + else if nextIdx > finish then + (* Base case: delete from the start of this string and stop moving. *) + let + val oldNodeTextLength = String.size rightStringsHd + val oldNodeLineLength = Vector.length rightLinesHd + + (* Delete part of string. *) + val newStrStart = finish - moveIdx + val newStr = String.substring + ( rightStringsHd + , newStrStart + , String.size rightStringsHd - newStrStart + ) + + (* Delete from line vector if we need to. *) + val newLines = + if Vector.length rightLinesHd > 0 then + let + val lineDeleteStart = + forwardBinSearch (newStrStart, rightLinesHd) + in + if lineDeleteStart < Vector.length rightLinesHd then + let + val lineDeleteLength = + Vector.length rightLinesHd - lineDeleteStart + in + Vector.tabulate (lineDeleteLength, fn idx => + Vector.sub (rightLinesHd, idx + lineDeleteStart) + - newStrStart) + end + else + Vector.fromList [] + end + else + rightLinesHd (* empty vector *) + + val newNodeTextLength = String.size newStr + val textLengthDifference = oldNodeTextLength - newNodeTextLength + val textLength = textLength - textLengthDifference + + val newNodeLineLength = Vector.length newLines + val lineLengthDifference = oldNodeLineLength - newNodeLineLength + val lineLength = lineLength - lineLengthDifference + in + { idx = origIdx + , textLength = textLength + , line = origLine + , lineLength = lineLength + , leftStrings = leftStrings + , leftLines = leftLines + , rightStrings = newStr :: rightStringsTl + , rightLines = newLines :: rightLinesTl + } + end + else + (* Delete this node fully, but delete no further. *) + { idx = origIdx + , textLength = textLength - String.size rightStringsHd + , line = origLine + , lineLength = lineLength - Vector.length rightLinesHd + , leftStrings = leftStrings + , leftLines = leftLines + , rightStrings = rightStringsTl + , rightLines = rightLinesTl + } + end + | (_, _) => + { idx = origIdx + , textLength = textLength + , line = origLine + , lineLength = lineLength + , leftStrings = leftStrings + , leftLines = leftLines + , rightStrings = [] + , rightLines = [] + } + + fun moveRightAndDelete + ( start + , finish + , curIdx + , curLine + , leftStrings: string list + , leftLines: int vector list + , rightStrings: string list + , rightLines: int vector list + , textLength + , lineLength + ) = + case (rightStrings, rightLines) of + (rightStringsHd :: rightStringsTl, rightLinesHd :: rightLinesTl) => + let + val nextIdx = curIdx + String.size rightStringsHd + in + if nextIdx < start then + (* Keep moving right. + * Complicated code below is an optimsation to reduce number of + * elements in the gap buffer. + * If we can join left head with right head while staying in limit, then + * do so; else, just cons as we move. *) + (case (leftStrings, leftLines) of + (leftStringsHd :: leftStringsTl, leftLinesHd :: leftLinesTl) => + if + isInLimit + ( leftStringsHd + , rightStringsHd + , leftLinesHd + , rightLinesHd + ) + then + (* We can join the heads while staying in limit, so do so. *) + let + val newLeftStringsHd = leftStringsHd ^ rightStringsHd + val newLeftLinesHd: int vector = + Vector.tabulate + ( Vector.length leftLinesHd + + Vector.length rightLinesHd + , fn idx => + if idx < Vector.length leftLinesHd then + Vector.sub (leftLinesHd, idx) + else + Vector.sub + ( rightLinesHd + , idx - Vector.length leftLinesHd + ) + String.size leftStringsHd + ) + val newLeftStrings = newLeftStringsHd :: leftStringsTl + val newLeftLines = newLeftLinesHd :: leftLinesTl + in + moveRightAndDelete + ( start + , finish + , nextIdx + , curLine + Vector.length rightLinesHd + , newLeftStrings + , newLeftLines + , rightStringsTl + , rightLinesTl + , textLength + , lineLength + ) + end + else + (* Can't join heads while staying in limit, so just cons. *) + moveRightAndDelete + ( start + , finish + , nextIdx + , curLine + Vector.length rightLinesHd + , rightStringsHd :: leftStrings + , rightLinesHd :: leftLines + , rightStringsTl + , rightLinesTl + , textLength + , lineLength + ) + | (_, _) => + (* Can't join heads while staying in limit, so just cons. *) + moveRightAndDelete + ( start + , finish + , nextIdx + , curLine + Vector.length rightLinesHd + , rightStringsHd :: leftStrings + , rightLinesHd :: leftLines + , rightStringsTl + , rightLinesTl + , textLength + , lineLength + )) + else if nextIdx > start then + if nextIdx < finish then + (* Start deleting from the end of this string, + * and then continue deleting rightwards. *) + let + val oldNodeTextLength = String.size rightStringsHd + val oldNodeLineLength = Vector.length rightLinesHd + + val length = start - curIdx + val newString = String.substring (rightStringsHd, 0, length) + + val lineDeleteEnd = binSearch + (String.size newString - 1, rightLinesHd) + val newLines = + if Vector.length rightLinesHd = 0 orelse lineDeleteEnd < 0 then + Vector.fromList [] + else + let + val slice = VectorSlice.slice + (rightLinesHd, 0, SOME (lineDeleteEnd + 1)) + in + VectorSlice.vector slice + end + + val newNodeTextLength = String.size newString + val textLengthDifference = + oldNodeTextLength - newNodeTextLength + val textLength = textLength - textLengthDifference + + val newNodeLineLength = Vector.length newLines + val lineLengthDifference = + oldNodeLineLength - newNodeLineLength + val lineLength = lineLength - lineLengthDifference + in + (* Try joining new string with left head if possible. *) + (case (leftStrings, leftLines) of + ( leftStringsHd :: leftStringsTl + , leftLinesHd :: leftLinesTl + ) => + if + isInLimit + (newString, leftStringsHd, newLines, leftLinesHd) + then + (* Join new string with left head. *) + let + val newLeftStringsHd = leftStringsHd ^ newString + val newLeftLinesHd = + Vector.tabulate + ( Vector.length leftLinesHd + + Vector.length newLines + , fn idx => + if idx < Vector.length leftLinesHd then + Vector.sub (leftLinesHd, idx) + else + Vector.sub + ( newLines + , idx - Vector.length leftLinesHd + ) + String.size leftStringsHd + ) + in + (* moveIdx passed as arameter should be + * different from origIdx, + * because moveIdx considers range to delete from + * while origIdx considers index to return + * once buffer is done deleting. *) + deleteRightFromHere + ( curIdx + String.size newString + , curLine + Vector.length newLines + , nextIdx + , finish + , newLeftStringsHd :: leftStringsTl + , newLeftLinesHd :: leftLinesTl + , rightStringsTl + , rightLinesTl + , textLength + , lineLength + ) + end + else + (* Can't join new string with left head + * while staying in limit, so just cons. *) + deleteRightFromHere + ( curIdx + String.size newString + , curLine + Vector.length newLines + , nextIdx + , finish + , newString :: leftStrings + , newLines :: leftLines + , rightStringsTl + , rightLinesTl + , textLength + , lineLength + ) + | (_, _) => + deleteRightFromHere + ( curIdx + String.size newString + , curLine + Vector.length newLines + , nextIdx + , finish + , newString :: leftStrings + , newLines :: leftLines + , rightStringsTl + , rightLinesTl + , textLength + , lineLength + )) + end + else if nextIdx > finish then + (* Base case: delete from the middle part of this string. *) + let + val oldNodeTextLength = String.size rightStringsHd + val oldNodeLineLength = Vector.length rightLinesHd + + val sub1Length = start - curIdx + val sub1 = String.substring (rightStringsHd, 0, sub1Length) + val sub1LineEnd = binSearch + (String.size sub1 - 1, rightLinesHd) + val sub1Lines = + if sub1LineEnd < 0 orelse Vector.length rightLinesHd = 0 then + Vector.fromList [] + else + let + val slice = VectorSlice.slice + (rightLinesHd, 0, SOME (sub1LineEnd + 1)) + in + VectorSlice.vector slice + end + + val sub2Start = finish - curIdx + val sub2 = String.substring + ( rightStringsHd + , sub2Start + , String.size rightStringsHd - sub2Start + ) + val sub2LineStart = forwardBinSearch (sub2Start, rightLinesHd) + val sub2Lines = + if sub2LineStart < Vector.length rightLinesHd then + Vector.tabulate + ( Vector.length rightLinesHd - sub2LineStart + , fn idx => + Vector.sub (rightLinesHd, idx + sub2LineStart) + - (String.size rightStringsHd - String.size sub2) + ) + else + Vector.fromList [] + + val newNodeTextLength = String.size sub1 + String.size sub2 + val textLengthDifference = + oldNodeTextLength - newNodeTextLength + val newTextLength = textLength - textLengthDifference + + val newNodeLineLength = + Vector.length sub1Lines + Vector.length sub2Lines + val lineLengthDifference = + oldNodeLineLength - newNodeLineLength + val newLineLength = lineLength - lineLengthDifference + in + { idx = curIdx + String.size sub1 + , textLength = newTextLength + , line = curLine + Vector.length sub1Lines + , lineLength = newLineLength + , leftStrings = sub1 :: leftStrings + , leftLines = sub1Lines :: leftLines + , rightStrings = sub2 :: rightStringsTl + , rightLines = sub2Lines :: rightLinesTl + } + end + else + (* nextIdx = finish + * Base case: delete from middle to end of this string, keeping start. *) + let + val oldNodeTextLength = String.size rightStringsHd + val oldNodeLineLength = Vector.length rightLinesHd + + val strLength = start - curIdx + val str = String.substring (rightStringsHd, 0, strLength) + val midpoint = binSearch (String.size str - 1, rightLinesHd) + val newLeftLines = + if midpoint < 0 orelse Vector.length rightLinesHd = 0 then + Vector.fromList [] + else + let + val slice = VectorSlice.slice + (rightLinesHd, 0, SOME (midpoint + 1)) + in + VectorSlice.vector slice + end + + val newNodeTextLength = String.size str + val textLengthDifference = + oldNodeTextLength - newNodeTextLength + val newTextLength = textLength - textLengthDifference + + val newNodeLineLength = Vector.length newLeftLines + val lineLengthDifference = + oldNodeLineLength - newNodeLineLength + val newLineLength = lineLength - lineLengthDifference + in + { idx = curIdx + String.size str + , textLength = newTextLength + , line = curLine + Vector.length newLeftLines + , lineLength = newLineLength + , leftStrings = str :: leftStrings + , leftLines = newLeftLines :: leftLines + , rightStrings = rightStringsTl + , rightLines = rightLinesTl + } + end + else + (* nextIdx = start + * Another base case of this function. + * The start of the deletion range contains the rightStrings/LinesHd, + * and it may extend beyond the current head. + * So pass the rightStringsTl and rightLinesTl to a function that + * will delete rightwards if it needs to, or else terminates. *) + deleteRightFromHere + ( curIdx + String.size rightStringsHd + , curLine + Vector.length rightLinesHd + , nextIdx + , finish + , rightStringsHd :: leftStrings + , rightLinesHd :: leftLines + , rightStringsTl + , rightLinesTl + , textLength + , lineLength + ) + end + | (_, _) => + { idx = curIdx + , textLength = textLength + , line = curLine + , lineLength = lineLength + , leftStrings = leftStrings + , leftLines = leftLines + , rightStrings = rightStrings + , rightLines = rightLines + } + + fun deleteLeftFromHere + ( start + , curIdx + , curLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) = + case (leftStrings, leftLines) of + (leftStringsHd :: leftStringsTl, leftLinesHd :: leftLinesTl) => + let + val prevIdx = curIdx - String.size leftStringsHd + val prevLine = curLine - Vector.length leftLinesHd + in + if start < prevIdx then + (* Continue deleting leftward. *) + deleteLeftFromHere + ( start + , prevIdx + , prevLine + , leftStringsTl + , leftLinesTl + , rightStrings + , rightLines + , textLength - String.size leftStringsHd + , lineLength - Vector.length leftLinesHd + ) + else if start > prevIdx then + (* Base case: delete end part of this string and return. *) + let + val oldNodeTextLength = String.size leftStringsHd + val oldNodeLineLength = Vector.length leftLinesHd + + val length = start - prevIdx + val newStr = String.substring (leftStringsHd, 0, length) + val newLines = + if Vector.length leftLinesHd > 0 then + let + val midpoint = binSearch + (String.size newStr - 1, leftLinesHd) + val slice = VectorSlice.slice + (leftLinesHd, 0, SOME (midpoint + 1)) + in + VectorSlice.vector slice + end + else + Vector.fromList [] + + val newNodeTextLength = String.size newStr + val textLengthDifference = oldNodeTextLength - newNodeTextLength + val newTextLength = textLength - textLengthDifference + + val newNodeLineLength = Vector.length newLines + val lineLengthDifference = oldNodeLineLength - newNodeLineLength + val newLineLength = lineLength - lineLengthDifference + in + { idx = prevIdx + String.size newStr + , textLength = newTextLength + , line = prevLine + Vector.length newLines + , lineLength = newLineLength + , leftStrings = newStr :: leftStringsTl + , leftLines = newLines :: leftLinesTl + , rightStrings = rightStrings + , rightLines = rightLines + } + end + else + (* start = prevIdx + * Base case: Remove leftStrings/LinesHd without removing any further. *) + { idx = prevIdx + , line = prevLine + , leftStrings = leftStringsTl + , leftLines = leftLinesTl + , rightStrings = rightStrings + , rightLines = rightLines + , textLength = textLength - String.size leftStringsHd + , lineLength = lineLength - Vector.length leftLinesHd + } + end + | (_, _) => + { idx = curIdx + , line = curLine + , leftStrings = leftStrings + , leftLines = leftLines + , rightStrings = rightStrings + , rightLines = rightLines + , textLength = textLength + , lineLength = lineLength + } + + fun deleteFromLetAndRight + ( start + , finish + , curIdx + , curLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) = + let + val + { idx = curIdx + , line = curLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + } = deleteRightFromHere + ( curIdx + , curLine + , curIdx + , finish + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) + in + deleteLeftFromHere + ( start + , curIdx + , curLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) + end + + fun moveLeftAndDelete + ( start + , finish + , curIdx + , curLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) = + case (leftStrings, leftLines) of + (leftStringsHd :: leftStringsTl, leftLinesHd :: leftLinesTl) => + let + val prevIdx = curIdx - String.size leftStringsHd + in + if prevIdx > finish then + (* Have to continue moving leftwards. + * Case statement below is an optimisation attempt: + * We are trying to join strings and line-vectors while staying in + * limit if this is possible while staying in limit. + * If this is not possible, we just cons instead. *) + (case (rightStrings, rightLines) of + ( rightStringsHd :: rightStringsTl + , rightLinesHd :: rightLinesTl + ) => + if + isInLimit + ( leftStringsHd + , rightStringsHd + , leftLinesHd + , rightLinesHd + ) + then + (* Can join while staying in limit, so do join. *) + let + val newRightStringsHd = leftStringsHd ^ rightStringsHd + val newRightLinesHd = + Vector.tabulate + ( Vector.length leftLinesHd + + Vector.length rightLinesHd + , fn idx => + if idx < Vector.length leftLinesHd then + Vector.sub (leftLinesHd, idx) + else + Vector.sub + ( rightLinesHd + , idx - Vector.length leftLinesHd + ) + String.size leftStringsHd + ) + val newRightStrings = newRightStringsHd :: rightStringsTl + val newRightLines = newRightLinesHd :: rightLinesTl + in + moveLeftAndDelete + ( start + , finish + , prevIdx + , curLine - Vector.length leftLinesHd + , leftStringsTl + , leftLinesTl + , newRightStrings + , newRightLines + , textLength + , lineLength + ) + end + else + (* Cannot join while staying in limit, so don't. *) + moveLeftAndDelete + ( start + , finish + , prevIdx + , curLine - Vector.length leftLinesHd + , leftStringsTl + , leftLinesTl + , leftStringsHd :: rightStrings + , leftLinesHd :: rightLines + , textLength + , lineLength + ) + | (_, _) => + (* Base case: reached empty list while trying to move leftwards. + * Cannot do anything so just return. *) + moveLeftAndDelete + ( start + , finish + , prevIdx + , curLine - Vector.length leftLinesHd + , leftStringsTl + , leftLinesTl + , [leftStringsHd] + , [leftLinesHd] + , textLength + , lineLength + )) + else if prevIdx < finish then + if prevIdx > start then + (* Delete from start point of this string, + * and then call function to continue deleting leftwards. *) + let + val oldNodeTextLength = String.size leftStringsHd + val oldNodeLineLength = Vector.length leftLinesHd + + val stringStart = finish - prevIdx + val newString = String.substring + ( leftStringsHd + , stringStart + , String.size leftStringsHd - stringStart + ) + val newLines = + let + val midpoint = forwardBinSearch (stringStart, leftLinesHd) + in + if midpoint >= 0 then + Vector.tabulate + ( Vector.length leftLinesHd - midpoint + , fn idx => + Vector.sub (leftLinesHd, idx + midpoint) + - stringStart + ) + else + Vector.fromList [] + end + val newRightStrings = newString :: rightStrings + val newRightLines = newLines :: rightLines + val prevLine = curLine - Vector.length leftLinesHd + + val newNodeTextLength = String.size newString + val textLengthDifference = + oldNodeTextLength - newNodeTextLength + val textLength = textLength - textLengthDifference + + val newNodeLineLength = Vector.length newLines + val lineLengthDifference = + oldNodeLineLength - newNodeLineLength + val lineLength = lineLength - lineLengthDifference + in + deleteLeftFromHere + ( start + , prevIdx + , prevLine + , leftStringsTl + , leftLinesTl + , newRightStrings + , newRightLines + , textLength + , lineLength + ) + end + else if prevIdx < start then + (* We want to delete in the middle of leftStringsHd. + * We also have to delete in the middle of leftLinesHd in order to + * do this. *) + let + val oldNodeTextLength = String.size leftStringsHd + val oldNodeLineLength = Vector.length leftLinesHd + + val sub1Length = start - prevIdx + val sub1 = String.substring (leftStringsHd, 0, sub1Length) + val sub2Start = finish - prevIdx + val sub2 = String.substring + ( leftStringsHd + , sub2Start + , String.size leftStringsHd - sub2Start + ) + + val sub1Lines = + if Vector.length leftLinesHd > 0 then + let + val midpoint = binSearch + (String.size sub1 - 1, leftLinesHd) + in + if midpoint >= 0 then + let + val slice = VectorSlice.slice + (leftLinesHd, 0, SOME (midpoint + 1)) + in + VectorSlice.vector slice + end + else + Vector.fromList [] + end + else + leftLinesHd + + val sub2Lines = + let + val midpoint = forwardBinSearch (sub2Start, leftLinesHd) + in + if midpoint < Vector.length leftLinesHd then + Vector.tabulate + ( Vector.length leftLinesHd - midpoint + , fn idx => + Vector.sub (leftLinesHd, idx + midpoint) + - sub2Start + ) + else + Vector.fromList [] + end + + val newNodeTextLength = String.size sub1 + String.size sub2 + val textLengthDifference = + oldNodeTextLength - newNodeTextLength + val textLength = textLength - textLengthDifference + + val newNodeLineLength = + Vector.length sub1Lines + Vector.length sub2Lines + val lineLengthDifference = + oldNodeLineLength - newNodeLineLength + val lineLength = lineLength - lineLengthDifference + in + { idx = prevIdx + String.size sub1 + , line = + (curLine - Vector.length leftLinesHd) + + Vector.length sub1Lines + , leftStrings = sub1 :: leftStringsTl + , leftLines = sub1Lines :: leftLinesTl + , rightStrings = sub2 :: rightStrings + , rightLines = sub2Lines :: rightLines + , textLength = textLength + , lineLength = lineLength + } + end + else + (* prevIdx = start + * We want to delete from the start of this string and stop. *) + let + val oldNodeTextLength = String.size leftStringsHd + val oldNodeLineLength = Vector.length leftLinesHd + + val strStart = finish - prevIdx + val str = String.substring + ( leftStringsHd + , strStart + , String.size leftStringsHd - strStart + ) + val lines = + let + val lineStart = forwardBinSearch (strStart, leftLinesHd) + in + if lineStart < Vector.length leftLinesHd then + Vector.tabulate + ( Vector.length leftLinesHd - lineStart + , fn idx => + Vector.sub (leftLinesHd, idx + lineStart) + - strStart + ) + else + Vector.fromList [] + end + + val newNodeTextLength = String.size str + val textLengthDifference = + oldNodeTextLength - newNodeTextLength + val textLength = textLength - textLengthDifference + + val newNodeLineLength = Vector.length lines + val lineLengthDifference = + oldNodeLineLength - newNodeLineLength + val lineLength = lineLength - lineLengthDifference + in + { idx = prevIdx + String.size str + , textLength = textLength + , line = + (curLine - Vector.length leftLinesHd) + String.size str + , lineLength = lineLength + , leftStrings = str :: leftStringsTl + , leftLines = lines :: leftLinesTl + , rightStrings = rightStrings + , rightLines = rightLines + } + end + else + (* prevIdx = finish + * We need to call a function that will start deleting from prevIdx. + * Optimsation: Try joining leftStrings/LinesHd with + * rightStrings/LinesHd if possible while staying in limit. *) + (case (rightStrings, rightLines) of + ( rightStringsHd :: rightStringsTl + , rightLinesHd :: rightLinesTl + ) => + if + isInLimit + ( leftStringsHd + , rightStringsHd + , leftLinesHd + , rightLinesHd + ) + then + (* Can join while staying in limit. *) + let + val newRightStringsHd = leftStringsHd ^ rightStringsHd + val newRightLinesHd = + Vector.tabulate + ( Vector.length leftLinesHd + + Vector.length rightLinesHd + , fn idx => + if idx < Vector.length leftLinesHd then + Vector.sub (leftLinesHd, idx) + else + Vector.sub + ( rightLinesHd + , idx - Vector.length leftLinesHd + ) + String.size leftStringsHd + ) + in + deleteLeftFromHere + ( start + , prevIdx + , curLine - Vector.length leftLinesHd + , leftStringsTl + , leftLinesTl + , newRightStringsHd :: rightStringsTl + , newRightLinesHd :: rightLinesTl + , textLength + , lineLength + ) + end + else + (* Cannot join while staying in limit. *) + deleteLeftFromHere + ( start + , prevIdx + , curLine - Vector.length leftLinesHd + , leftStringsTl + , leftLinesTl + , leftStringsHd :: rightStrings + , leftLinesHd :: rightLines + , textLength + , lineLength + ) + | (_, _) => + (* Right strings and lines are empty, so can't join. *) + deleteLeftFromHere + ( start + , prevIdx + , curLine - Vector.length leftLinesHd + , leftStringsTl + , leftLinesTl + , [leftStringsHd] + , [leftLinesHd] + , textLength + , lineLength + )) + end + | (_, _) => + (* Can't move further leftward so just return. *) + { idx = 0 + , textLength = textLength + , line = 0 + , lineLength = lineLength + , leftStrings = [] + , leftLines = [] + , rightStrings = rightStrings + , rightLines = rightLines + } + + fun del + ( start + , finish + , curIdx + , curLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) = + if start > curIdx then + moveRightAndDelete + ( start + , finish + , curIdx + , curLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) + else if start < curIdx then + if finish <= curIdx then + moveLeftAndDelete + ( start + , finish + , curIdx + , curLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) + else + deleteFromLetAndRight + ( start + , finish + , curIdx + , curLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) + else + deleteRightFromHere + ( curIdx + , curLine + , curIdx + , finish + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) + in + fun delete (start, length, buffer: t) = + if length > 0 then + del + ( start + , start + length + , #idx buffer + , #line buffer + , #leftStrings buffer + , #leftLines buffer + , #rightStrings buffer + , #rightLines buffer + , #textLength buffer + , #lineLength buffer + ) + else + buffer + end + + local + fun consIfNotEmpty (s, acc) = + if String.size s > 0 then s :: acc else acc + + (* We build up the string list and, at the end, + * we always make sure to reverse the list too + * because the order of the list matters for String.concat *) + fun subRightFromHere (curIdx, finish, right, acc, endWith) = + case right of + hd :: tl => + let + val nextIdx = curIdx + String.size hd + in + if nextIdx < finish then + subRightFromHere (curIdx, finish, tl, hd :: acc, endWith) + else if nextIdx > finish then + let + val length = finish - curIdx + val accHd = String.substring (hd, 0, length) + val acc = consIfNotEmpty (endWith, accHd :: acc) + in + List.rev acc + end + else + (* nextIdx = finish + * so add current hd to vec and then concat *) + let + val acc = hd :: acc + val acc = consIfNotEmpty (endWith, acc) + in + List.rev acc + end + end + | [] => let val acc = consIfNotEmpty (endWith, acc) in List.rev acc end + + fun moveRightAndSub (start, finish, curIdx, right, endWith) = + case right of + hd :: tl => + let + val nextIdx = curIdx + String.size hd + in + if nextIdx < start then + (* continue moving rightwards *) + moveRightAndSub (start, finish, nextIdx, tl, endWith) + else if nextIdx > start then + if nextIdx < finish then + (* get starting acc, + * and then call subRightFromHere *) + let + val substart = start - curIdx + val length = String.size hd - substart + val acc = [String.substring (hd, substart, length)] + val acc = subRightFromHere (nextIdx, finish, tl, acc, endWith) + in + String.concat acc + end + else if nextIdx > finish then + (* have to get susbstring from middle of this string *) + let + val substart = start - curIdx + val subfinish = finish - curIdx + val length = subfinish - substart + val str = String.substring (hd, substart, length) + in + if String.size endWith > 0 then str ^ endWith else str + end + else + (* have to get substring from middle to end *) + let + val substart = start - curIdx + val length = String.size hd - substart + val str = String.substring (hd, substart, length) + in + if String.size endWith > 0 then str ^ endWith else str + end + else + (* nextIdx = start + * so we have to ignore this string + * and start building acc from tl *) + let val acc = subRightFromHere (nextIdx, finish, tl, [], endWith) + in String.concat acc + end + end + | [] => + (* if there are no strings to the right, + * return empty string, + * as we cannot do much else. *) + endWith + + fun subLeftFromHere (start, curIdx, left, acc) = + case left of + hd :: tl => + let + val prevIdx = curIdx - String.size hd + in + if start < prevIdx then + (* continue *) + subLeftFromHere (start, prevIdx, tl, hd :: acc) + else if start > prevIdx then + (* need to add some part of this string to acc + * and return *) + let + val substart = start - prevIdx + val length = String.size hd - substart + val accHd = String.substring (hd, substart, length) + val acc = accHd :: acc + in + String.concat acc + end + else + (* start = prevIdx + * add hd to acc and return *) + let val acc = hd :: acc + in String.concat acc + end + end + | [] => String.concat acc + + fun subFromLeftAndRight (start, finish, curIdx, left, right, endWith) = + let val acc = subRightFromHere (curIdx, finish, right, [], endWith) + in subLeftFromHere (start, curIdx, left, acc) + end + + fun moveLeftAndSub (start, finish, curIdx, left, endWith) = + case left of + hd :: tl => + let + val prevIdx = curIdx - String.size hd + in + if prevIdx > finish then + (* continue *) + moveLeftAndSub (start, finish, prevIdx, tl, endWith) + else if prevIdx < finish then + if prevIdx > start then + (* get initial acc + * and continue substring leftwards *) + let + val length = finish - prevIdx + val str = String.substring (hd, 0, length) + val acc = [str, endWith] + in + subLeftFromHere (start, prevIdx, tl, acc) + end + else if prevIdx < start then + (* we want to return a substring + * extracted from the middle of hd *) + let + val substart = start - prevIdx + val subfinish = finish - prevIdx + val length = subfinish - substart + val str = String.substring (hd, substart, length) + in + if String.size endWith > 0 then str ^ endWith else str + end + else + (* prevIdx = start + * we want to return a substring starting from 0 *) + let + val subfinish = finish - prevIdx + val length = String.size hd - subfinish + val str = String.substring (hd, 0, length) + in + if String.size endWith > 0 then str ^ endWith else str + end + else + (* prevIdx = finish + * so we want to ignore hd and start + * subLeftFromHere with an empty list *) + subLeftFromHere (start, prevIdx, tl, [endWith]) + end + | [] => endWith + + fun sub (start, finish, curIdx, left, right, endWith) = + if start > curIdx then + (* move rightwards to begin getting substring *) + moveRightAndSub (start, finish, curIdx, right, endWith) + else if start < curIdx then + if finish <= curIdx then + moveLeftAndSub (start, finish, curIdx, left, endWith) + else + (* in middle of buffer we want to get substring from *) + subFromLeftAndRight (start, finish, curIdx, left, right, endWith) + else + let + (* start = curIdx so only need to traverse right *) + val acc = subRightFromHere (curIdx, finish, right, [], endWith) + in + String.concat acc + end + in + fun substringWithEnd (start, length, buffer: t, endWith) = + let + val finish = start + length + val {idx, leftStrings, rightStrings, ...} = buffer + in + sub (start, finish, idx, leftStrings, rightStrings, endWith) + end + + fun nullSubstring (start, length, buffer: t) = + let + val finish = start + length + val {idx, leftStrings, rightStrings, ...} = buffer + in + sub (start, finish, idx, leftStrings, rightStrings, "\u0000") + end + + fun substring (start, length, buffer: t) = + let + val finish = start + length + val {idx, leftStrings, rightStrings, ...} = buffer + in + sub (start, finish, idx, leftStrings, rightStrings, "") + end + end + + fun helpGoToStart + ( idx + , line + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) = + case (leftStrings, leftLines) of + (lStrHd :: lStrTl, lLnHd :: lLnTl) => + (case (rightStrings, rightLines) of + (rStrHd :: rStrTl, rLnHd :: rLnTl) => + if isInLimit (lStrHd, rStrHd, lLnHd, rLnHd) then + (* join if possible *) + let + val newRstrHd = lStrHd ^ rStrHd + val newRlnHd = + Vector.tabulate + ( Vector.length lLnHd + Vector.length rLnHd + , fn lnIdx => + if lnIdx < Vector.length lLnHd then + Vector.sub (lLnHd, lnIdx) + else + Vector.sub (rLnHd, lnIdx - Vector.length lLnHd) + + String.size lStrHd + ) + in + helpGoToStart + ( idx - String.size lStrHd + , line - Vector.length lLnHd + , lStrTl + , lLnTl + , newRstrHd :: rStrTl + , newRlnHd :: rLnTl + , textLength + , lineLength + ) + end + else + helpGoToStart + ( idx - String.size lStrHd + , line - Vector.length lLnHd + , lStrTl + , lLnTl + , lStrHd :: rightStrings + , lLnHd :: rightLines + , textLength + , lineLength + ) + | (_, _) => + (* rightStrings and rightLines are both empty *) + helpGoToStart + ( idx - String.size lStrHd + , line - Vector.length lLnHd + , lStrTl + , lLnTl + , [lStrHd] + , [lLnHd] + , textLength + , lineLength + )) + | (_, _) => + (* left strings are empty, meaning we are at start and can return *) + { idx = idx + , textLength = textLength + , line = line + , lineLength = lineLength + , leftStrings = [] + , leftLines = [] + , rightStrings = rightStrings + , rightLines = rightLines + } + + fun goToStart (buffer: t) = + let + val + { idx + , line + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + } = buffer + in + helpGoToStart + ( idx + , line + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) + end + + fun helpGoToEnd + ( idx + , line + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) = + case (rightStrings, rightLines) of + (rStrHd :: rStrTl, rLnHd :: rLnTl) => + (case (leftStrings, leftLines) of + (lStrHd :: lStrTl, lLnHd :: lLnTl) => + if isInLimit (lStrHd, rStrHd, lLnHd, rLnHd) then + (* join if possible *) + let + val newLstrHd = lStrHd ^ rStrHd + val newLlnHd = + Vector.tabulate + ( Vector.length lLnHd + Vector.length rLnHd + , fn lnIdx => + if lnIdx < Vector.length lLnHd then + Vector.sub (lLnHd, lnIdx) + else + Vector.sub (rLnHd, lnIdx - Vector.length lLnHd) + + String.size lStrHd + ) + in + helpGoToEnd + ( idx + String.size rStrHd + , line + Vector.length rLnHd + , newLstrHd :: lStrTl + , newLlnHd :: lLnTl + , rStrTl + , rLnTl + , textLength + , lineLength + ) + end + else + helpGoToEnd + ( idx + String.size rStrHd + , line + Vector.length rLnHd + , rStrHd :: leftStrings + , rLnHd :: leftLines + , rStrTl + , rLnTl + , textLength + , lineLength + ) + | (_, _) => + (* rightStrings and rightLines are both empty *) + helpGoToEnd + ( idx + String.size rStrHd + , line + Vector.length rLnHd + , rStrHd :: leftStrings + , rLnHd :: leftLines + , rStrTl + , rLnTl + , textLength + , lineLength + )) + | (_, _) => + (* rightStrings strings are empty, meaning we are at end and can return *) + { idx = idx + , textLength = textLength + , line = line + , lineLength = lineLength + , leftStrings = leftStrings + , leftLines = leftLines + , rightStrings = [] + , rightLines = [] + } + + fun goToEnd (buffer: t) = + let + val + { idx + , line + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + } = buffer + in + helpGoToEnd + ( idx + , line + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) + end + + (* function to abstract leftwards movement. + * if the left hd and the right hd can be joined in one node + * during movement, while staying in limit, then join and move. + * Else, move without joining. + * The code to do this is a bit boiler-plate heavy + * so it has been abstracted to a reusable function. + * + * The last parameter, fGoLeft, is the function to return to + * after the leftwards movement. + * + * The third paremeter, searchTo, is the line number or UTF-8 + * index to search. Since moveLeft is meant to abstract over + * the search number, this parameter is just passed to fGoLeft. + * *) + fun moveLeft + ( idx + , line + , searchTo + , rightStrings + , rightLines + , lStrHd + , lStrTl + , lLnHd + , lLnTl + , textLength + , lineLength + , fGoLeft + ) = + case (rightStrings, rightLines) of + (rStrHd :: rStrTl, rLnHd :: rLnTl) => + if isInLimit (lStrHd, rStrHd, lLnHd, rLnHd) then + (* join into a single node before moving *) + let + val newRstrHd = lStrHd ^ rStrHd + val newRlnHd = + Vector.tabulate + ( Vector.length lLnHd + Vector.length rLnHd + , fn lnIdx => + if lnIdx < Vector.length lLnHd then + Vector.sub (lLnHd, lnIdx) + else + Vector.sub (rLnHd, lnIdx - Vector.length lLnHd) + + String.size lStrHd + ) + in + fGoLeft + ( idx - String.size lStrHd + , line - Vector.length lLnHd + , searchTo + , lStrTl + , lLnTl + , newRstrHd :: rStrTl + , newRlnHd :: rLnTl + , textLength + , lineLength + ) + end + else + (* move without joining *) + fGoLeft + ( idx - String.size lStrHd + , line - Vector.length lLnHd + , searchTo + , lStrTl + , lLnTl + , lStrHd :: rightStrings + , lLnHd :: rightLines + , textLength + , lineLength + ) + | (_, _) => + (* right side is empty, so just move left without joining *) + fGoLeft + ( idx - String.size lStrHd + , line - Vector.length lLnHd + , searchTo + , lStrTl + , lLnTl + , [lStrHd] + , [lLnHd] + , textLength + , lineLength + ) + + (* same as moveLeft function, except it move rightwards instead *) + fun moveRight + ( idx + , line + , searchTo + , leftStrings + , leftLines + , rStrHd + , rStrTl + , rLnHd + , rLnTl + , textLength + , lineLength + , fGoRight + ) = + case (leftStrings, leftLines) of + (lStrHd :: lStrTl, lLnHd :: lLnTl) => + if isInLimit (lStrHd, rStrHd, lLnHd, rLnHd) then + (* can join while staying in limit, so join and move right *) + let + val newLstrHd = lStrHd ^ rStrHd + val newLlnHd = + Vector.tabulate + ( Vector.length lLnHd + Vector.length rLnHd + , fn lnIdx => + if lnIdx < Vector.length lLnHd then + Vector.sub (lLnHd, lnIdx) + else + Vector.sub (rLnHd, lnIdx - Vector.length lLnHd) + + String.size lStrHd + ) + in + fGoRight + ( idx + String.size rStrHd + , line + Vector.length rLnHd + , searchTo + , newLstrHd :: lStrTl + , newLlnHd :: lLnTl + , rStrTl + , rLnTl + , textLength + , lineLength + ) + end + else + (* cannot join while staying in limit, so just move right *) + fGoRight + ( idx + String.size rStrHd + , line + Vector.length rLnHd + , searchTo + , rStrHd :: leftStrings + , rLnHd :: leftLines + , rStrTl + , rLnTl + , textLength + , lineLength + ) + | (_, _) => + (* left side is empty, so just move rightwards without joining *) + fGoRight + ( String.size rStrHd + , Vector.length rLnHd + , searchTo + , [rStrHd] + , [rLnHd] + , rStrTl + , rLnTl + , textLength + , lineLength + ) + + fun helpGoToLineLeft + ( idx + , line + , searchLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) = + case (leftStrings, leftLines) of + (lStrHd :: lStrTl, lLnHd :: lLnTl) => + if searchLine >= line - Vector.length lLnHd then + (* line is at left head, so place it to the right and return *) + { idx = idx - String.size lStrHd + , textLength = textLength + , line = line - Vector.length lLnHd + , lineLength = lineLength + , leftStrings = lStrTl + , leftLines = lLnTl + , rightStrings = lStrHd :: rightStrings + , rightLines = lLnHd :: rightLines + } + else + (* move leftwards, joining if possible *) + moveLeft + ( idx + , line + , searchLine + , rightStrings + , rightLines + , lStrHd + , lStrTl + , lLnHd + , lLnTl + , textLength + , lineLength + , helpGoToLineLeft + ) + | (_, _) => + (* left side is empty, so just return *) + { idx = idx + , textLength = textLength + , line = line + , lineLength = lineLength + , leftStrings = [] + , leftLines = [] + , rightStrings = rightStrings + , rightLines = rightLines + } + + fun helpGoToLineRight + ( idx + , line + , searchLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) = + case (rightStrings, rightLines) of + (rStrHd :: rStrTl, rLnHd :: rLnTl) => + if line + Vector.length rLnHd >= searchLine then + (* searchLine is in rStrHd/rLnHd, so return *) + { idx = idx + , textLength = textLength + , line = line + , lineLength = lineLength + , leftStrings = leftStrings + , leftLines = leftLines + , rightStrings = rightStrings + , rightLines = rightLines + } + else + (* have to move rightwards *) + moveRight + ( idx + , line + , searchLine + , leftStrings + , leftLines + , rStrHd + , rStrTl + , rLnHd + , rLnTl + , textLength + , lineLength + , helpGoToLineRight + ) + | (_, _) => + (* right side is empty, so just return *) + { idx = idx + , textLength = textLength + , line = line + , lineLength = lineLength + , leftStrings = leftStrings + , leftLines = leftLines + , rightStrings = [] + , rightLines = [] + } + + fun goToLine (searchLine, buffer: t) = + let + val + { idx + , line + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + } = buffer + in + (* we compare current line with searchLine - 1 + * because if searchLine - 1 is here, + * that means we can access the linebreak + * that starts searchLine *) + if searchLine - 1 < line then + helpGoToLineLeft + ( idx + , line + , searchLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) + else if searchLine - 1 > line then + helpGoToLineRight + ( idx + , line + , searchLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) + else + buffer + end + + fun helpGoToIdxLeft + ( idx + , line + , searchIdx + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) = + case (leftStrings, leftLines) of + (lStrHd :: lStrTl, lLnHd :: lLnTl) => + if searchIdx < idx - String.size lStrHd then + (* move leftwards, joining if possible *) + moveLeft + ( idx + , line + , searchIdx + , rightStrings + , rightLines + , lStrHd + , lStrTl + , lLnHd + , lLnTl + , textLength + , lineLength + , helpGoToIdxLeft + ) + else + (* line is at left head, so place it to the right and return *) + { idx = idx - String.size lStrHd + , textLength = textLength + , line = line - Vector.length lLnHd + , lineLength = lineLength + , leftStrings = lStrTl + , leftLines = lLnTl + , rightStrings = lStrHd :: rightStrings + , rightLines = lLnHd :: rightLines + } + | (_, _) => + (* left side is empty, so just return *) + { idx = idx + , textLength = textLength + , line = line + , lineLength = lineLength + , leftStrings = [] + , leftLines = [] + , rightStrings = rightStrings + , rightLines = rightLines + } + + fun helpGoToIdxRight + ( idx + , line + , searchIdx + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) = + case (rightStrings, rightLines) of + (rStrHd :: rStrTl, rLnHd :: rLnTl) => + if searchIdx > idx + String.size rStrHd then + (* have to move rightwards *) + moveRight + ( idx + , line + , searchIdx + , leftStrings + , leftLines + , rStrHd + , rStrTl + , rLnHd + , rLnTl + , textLength + , lineLength + , helpGoToIdxRight + ) + else + (* searchLine is in rStrHd/rLnHd, so return *) + { idx = idx + , textLength = textLength + , line = line + , lineLength = lineLength + , leftStrings = leftStrings + , leftLines = leftLines + , rightStrings = rightStrings + , rightLines = rightLines + } + | (_, _) => + (* right side is empty, so just return *) + { idx = idx + , textLength = textLength + , line = line + , lineLength = lineLength + , leftStrings = leftStrings + , leftLines = leftLines + , rightStrings = [] + , rightLines = [] + } + + fun goToIdx (searchIdx, buffer: t) = + let + val + { idx + , line + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + } = buffer + in + if searchIdx < idx then + helpGoToIdxLeft + ( idx + , line + , searchIdx + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) + else if searchIdx > idx then + helpGoToIdxRight + ( idx + , line + , searchIdx + , leftStrings + , leftLines + , rightStrings + , rightLines + , textLength + , lineLength + ) + else + buffer + end + + fun idxToLineNumberLeft (findIdx, curIdx, curLine, leftStrings, leftLines) = + case (leftStrings, leftLines) of + (shd :: stl, lhd :: ltl) => + let + val prevIdx = curIdx - String.size shd + in + if findIdx = prevIdx then + curLine - Vector.length lhd + else if findIdx > prevIdx then + (* bin search vector *) + if Vector.length lhd = 0 then + curLine + else + let + val prevLine = curLine - Vector.length lhd + val relativeIdx = findIdx - prevIdx - 1 + val relativeLine = binSearch (relativeIdx, lhd) + 1 + in + prevLine + relativeLine + end + else + let val prevLine = curLine - Vector.length lhd + in idxToLineNumberLeft (findIdx, prevIdx, prevLine, stl, ltl) + end + end + | (_, _) => 0 + + fun idxToLineNumberRight (findIdx, curIdx, curLine, rightStrings, rightLines) = + case (rightStrings, rightLines) of + (shd :: stl, lhd :: ltl) => + let + val nextIdx = curIdx + String.size shd + in + if findIdx = nextIdx then + curLine + Vector.length lhd + else if findIdx < nextIdx then + if Vector.length lhd = 0 then + curLine + else + let + val relativeIdx = findIdx - curIdx - 1 + val relativeLine = binSearch (relativeIdx, lhd) + 1 + in + curLine + relativeLine + end + else + let val nextLine = curLine + Vector.length lhd + in idxToLineNumberRight (findIdx, nextIdx, nextLine, stl, ltl) + end + end + | (_, _) => curLine + + fun idxToLineNumber (findIdx, buffer: t) = + let + val + { idx = curIdx + , leftStrings + , leftLines + , rightStrings + , rightLines + , line = curLine + , ... + } = buffer + in + if findIdx < curIdx then + idxToLineNumberLeft (findIdx, curIdx, curLine, leftStrings, leftLines) + else if findIdx > curIdx then + idxToLineNumberRight + (findIdx, curIdx, curLine, rightStrings, rightLines) + else + curLine + end + + fun lineNumberToIdxLeft (findLine, curIdx, curLine, leftStrings, leftLines) = + case (leftStrings, leftLines) of + (shd :: stl, lhd :: ltl) => + let + val prevLine = curLine - Vector.length lhd + val prevIdx = curIdx - String.size shd + in + if findLine >= prevLine then + let val relativeLine = findLine - prevLine - 1 + in Vector.sub (lhd, relativeLine) + prevIdx + end + else + lineNumberToIdxLeft (findLine, prevIdx, prevLine, stl, ltl) + end + | (_, _) => 0 + + fun lineNumberToIdxRight (findLine, curIdx, curLine, rightStrings, rightLines) = + case (rightStrings, rightLines) of + (shd :: stl, lhd :: ltl) => + let + val nextLine = curLine + Vector.length lhd + in + if findLine <= nextLine then + let val relativeLine = findLine - curLine - 1 + in Vector.sub (lhd, relativeLine) + curIdx + end + else + lineNumberToIdxRight + (findLine, curIdx + String.size shd, nextLine, stl, ltl) + end + | (_, _) => curIdx + + fun lineNumberToIdx (findLine, buffer: t) = + let + val + { idx = curIdx + , line = curLine + , leftStrings + , leftLines + , rightStrings + , rightLines + , ... + } = buffer + in + if findLine - 1 < curLine then + lineNumberToIdxLeft (findLine, curIdx, curLine, leftStrings, leftLines) + else + lineNumberToIdxRight + (findLine, curIdx, curLine, rightStrings, rightLines) + end + + (* TEST CODE *) + local + fun lineBreaksToString vec = + (Vector.foldr (fn (el, acc) => Int.toString el ^ ", " ^ acc) "" vec) + ^ "\n" + + fun checkLineBreaks (v1, v2) = + if v1 = v2 then + () + else + let + val _ = print ("broken: " ^ (lineBreaksToString v1)) + val _ = print ("fixed: " ^ (lineBreaksToString v2)) + in + () + end + + fun goToStart (leftStrings, leftLines, accStrings, accLines) = + case (leftStrings, leftLines) of + (lsHd :: lsTl, llHd :: llTl) => + goToStart (lsTl, llTl, lsHd :: accStrings, llHd :: accLines) + | (_, _) => (accStrings, accLines) + + fun isLineListCorrect (strings, lines) = + case (strings, lines) of + (strHd :: strTl, lHd :: lTl) => + let + val checkLines = countLineBreaks strHd + in + if checkLines = lHd then + isLineListCorrect (strTl, lTl) + else + let + val _ = print "line metadata is incorrect\n" + val _ = checkLineBreaks (lHd, checkLines) + in + false + end + end + | (_, _) => (print "verified lines; no problems\n"; true) + in + fun verifyLines (buffer: t) = + let + val (strings, lines) = + goToStart + ( #leftStrings buffer + , #leftLines buffer + , #rightStrings buffer + , #rightLines buffer + ) + + val lineListIsCorrect = isLineListCorrect (strings, lines) + val lineLengthIsCorrect = let val lines = Vector.concat lines + in Vector.length lines = #lineLength buffer + end + val () = + if lineLengthIsCorrect then () else print "line length is incorrect\n" + in + if lineLengthIsCorrect andalso lineListIsCorrect then () + else raise Fail "" + end + end + + local + fun calcIndexList (accIdx, lst) = + case lst of + [] => accIdx + | hd :: tl => calcIndexList (String.size hd + accIdx, tl) + + fun calcIndexStart lst = calcIndexList (0, lst) + in + fun verifyIndex (buffer: t) = + let + val bufferIdx = #idx buffer + val correctIdx = calcIndexStart (#leftStrings buffer) + val idxIsCorrect = bufferIdx = correctIdx + + val {rightLines, rightStrings, ...} = goToStart buffer + + val textLength = #textLength buffer + val correctTextLength = String.size (String.concat rightStrings) + val textLengthIsCorrect = textLength = correctTextLength + + val lineLength = #lineLength buffer + val correctLineLength = Vector.length (Vector.concat rightLines) + val lineLengthIsCorrect = lineLength = correctLineLength + + val _ = + if idxIsCorrect then + print "idx is correct\n" + else + let + val msg = String.concat + [ "idx is incorrect;" + , "bufferIdx: " + , Int.toString bufferIdx + , "; correctIdx: " + , Int.toString correctIdx + , "\n" + ] + in + print msg + end + + val _ = + if textLengthIsCorrect then + print "textLength is correct\n" + else + let + val msg = String.concat + [ "text length is incorrect;" + , "text length: " + , Int.toString textLength + , "; correct length: " + , Int.toString correctTextLength + , "\n" + ] + in + print msg + end + + val _ = + if lineLengthIsCorrect then + print "lineLength is correct\n" + else + let + val msg = String.concat + [ "line length is incorrect;" + , "line length: " + , Int.toString lineLength + , "; correct length: " + , Int.toString correctLineLength + , "\n" + ] + in + print msg + end + + val () = print "\n" + in + if textLengthIsCorrect andalso idxIsCorrect andalso lineLengthIsCorrect then + () + else + raise Fail "either index or idx metadata or text length is incorrect" + end + end +end