ropes.sml (2894B)
1 (* Editor's note: transmuting an assignment from one CMU class to another... 2 * Sometimes things are easier when the language isn't fighting you :) 3 * Comments are lightly edited. Written around the spring of 2024. *) 4 5 datatype rope = Leaf of string | Cat of rope ref * rope ref 6 7 structure Rope = 8 struct 9 fun size (ref (Leaf s)) = String.size s 10 | size (ref (Cat(l, r))) = size l + size r 11 12 fun charat (ref (Leaf s)) i = String.sub (s, i) 13 | charat (ref (Cat(l, r))) i = let 14 val leftSize = size l 15 in 16 if leftSize > i then charat l i else charat r (i - leftSize) 17 end 18 19 fun toString (ref (Leaf s)) = s 20 | toString (ref (Cat(l, r))) = (toString l) ^ (toString r) 21 22 fun join (L, R) = ref (Cat(L, R)) 23 24 (* Nasty casework: the use of refcells and my lack of knowledge of their semantics also 25 * means I had to punt the casework to a case expr so that I could return the original 26 * reference if needed. *) 27 fun sub R lo hi = 28 if (lo = 0 andalso hi = (size R)) then R else 29 (case R of 30 (ref (Leaf s)) => ref (Leaf(String.substring(s, lo, hi))) 31 | (ref (Cat(l, r))) => let 32 val leftSize = size l 33 in 34 (case (hi < size l, size l <= lo) of 35 (* is the substring all on the left? then recur on left *) 36 (true, _) => sub l lo hi 37 (* vice versa *) 38 | (_, true) => sub r 0 (hi - size l) 39 (* otherwise take what we want from either side *) 40 | _ => join (sub l lo leftSize, sub r 0 (hi-leftSize))) 41 end) 42 43 (* Takes a list of ropes and modifies them to reuse subtrees from earlier elements in 44 * the list (thus 'reducing' the overall size of the collection of ropes). *) 45 fun reduce ropes = let 46 (* Gross and imperative code both in the hashtable usage and in the overuse of let 47 * blocks to do things in sequence. In my defense it was 2AM when I had this idea. *) 48 val ropeEqual = fn (R1, R2) => (toString R1) = (toString R2) 49 val ropeHash = fn R => HashString.hashString (toString R) 50 exception NotFound 51 val H = HashTable.mkTable (ropeHash, ropeEqual) (100, NotFound) 52 53 fun reduceHelp H R = 54 HashTable.lookup H R 55 handle NotFound => 56 let val () = (HashTable.insert H (R,R)) in 57 (case R of (ref (Leaf s)) => R 58 | (ref (Cat (l, r))) => join (reduceHelp H l, reduceHelp H r)) 59 end 60 in 61 map (fn x => let val () = x := !(reduceHelp H x) in x end) ropes 62 end 63 end 64 65 val r1 = ref (Cat (ref (Leaf "a"), ref (Leaf "br"))) 66 val r2 = ref (Cat (ref (Leaf "abr"), ref (Leaf "a"))) 67 val r3 = ref (Cat (ref (Leaf "a"), ref (Cat (ref (Leaf "br"), ref (Leaf "a"))))) 68 69 (* seems to work and like... it typechecks™ 70 * (but seriously, not rigorously tested) *) 71 val out = Rope.reduce [r1, r2, r3]