-- EditTestImpl.mesa -- written by Bill Paxton, April 1981 -- last edit by Bill Paxton, 2-Nov-81 10:29:03 -- This package provides random testing for editing Text nodes DIRECTORY EditTest, EditSpan, TextEdit, TextFind, TextLooks, TextLooksSupport, UndoEvent, RopeEdit, GetTree, TreeCheck, TextNode, EditNotify, RunReader, RopeReader, Rope, RopeInline, CheckNode, RandomCard, RandomLongInt, Inline; EditTestImpl: PROGRAM IMPORTS EditTest, EditSpan, TextEdit, RopeEdit, EditNotify, TextLooks, TextLooksSupport, TextFind, RunReader, RopeReader, Rope, TextNode, UndoEvent, GetTree, TreeCheck, CheckNode, RandomCard, RandomLongInt, Inline EXPORTS EditTest = BEGIN OPEN rI:Rope, EditTest, spanI:EditSpan, editI:TextEdit, findI:TextFind, looksI:TextLooks, looksSI:TextLooksSupport, undoI:UndoEvent, ropeI:RopeEdit, nodeI:TextNode, runrdrI:RunReader, roperdrI:RopeReader, checkI:CheckNode, notify:EditNotify, randC:RandomCard, randLI:RandomLongInt; alpha, beta: PUBLIC Node; alphaTree, betaTree: PUBLIC Ref; runrdr1, runrdr2: PUBLIC runrdrI.Ref; roperdr1, roperdr2: PUBLIC roperdrI.Ref; numTests: NAT = 28; testCountArray: ARRAY [0..numTests) OF NAT; -- number of times each test is executed by Run testProcArray: ARRAY [0..numTests) OF PROC = [ ReplaceText, DeleteText, CopyText, MoveText, MoveTextOnto, TransposeText, ReplaceByChar, InsertChar, AppendChar, ReplaceByString, InsertString, AppendString, ReplaceByRope, InsertRope, AppendRope, Find, BackwardsFind, ChangeLooks, AddLooks, RemoveLooks, SetLooks, ClearLooks, PutGet, PutGetRope, GetBigFile, PutGetTree, PutGetToRope, PutGetToStream ]; numNodeTests: NAT = 11; nodeTestCountArray: ARRAY [0..numNodeTests) OF NAT; nodeTestProcArray: ARRAY [0..numNodeTests) OF PROC = [ ReplaceNodes, DeleteNodes, CopyNodes, MoveNodes, MoveNodesOnto, TransposeNodes, SplitNode, MergeNodes, NestNodes, UnNestNodes, InsertNode ]; shortest: Offset = 20; -- don't let nodes get shorter than this longest: Offset = 2000; -- don't let nodes get longer than this -- ***** Looks Edit operations noLooks: Looks = looksI.noLooks; allLooks: Looks = looksI.allLooks; ChangeLooks: PUBLIC PROC = { node: Node ← PickNode[]; start, end: Offset; rem, add: Looks; rem ← PickLooks[]; add ← PickLooks[]; [start,end] ← PickTwo[node]; IF start=end THEN RETURN; BeforeUndo[node]; spanI.ChangeLooks[TextSpan[node,start,end-start],rem,add,event]; CheckLooks[node,rem,add,start,end-start] }; AddLooks: PUBLIC PROC = { node: Node ← PickNode[]; start, end: Offset; add: Looks ← PickLooks[]; [start,end] ← PickTwo[node]; IF start=end THEN RETURN; BeforeUndo[node]; spanI.AddLooks[TextSpan[node,start,end-start],add,event]; CheckLooks[node,noLooks,add,start,end-start] }; RemoveLooks: PUBLIC PROC = { node: Node ← PickNode[]; start, end: Offset; rem: Looks ← PickLooks[]; [start,end] ← PickTwo[node]; IF start=end THEN RETURN; BeforeUndo[node]; spanI.RemoveLooks[TextSpan[node,start,end-start],rem,event]; CheckLooks[node,rem,noLooks,start,end-start] }; SetLooks: PUBLIC PROC = { node: Node ← PickNode[]; start, end: Offset; new: Looks ← PickLooks[]; [start,end] ← PickTwo[node]; IF start=end THEN RETURN; BeforeUndo[node]; spanI.SetLooks[TextSpan[node,start,end-start],new,event]; CheckLooks[node,allLooks,new,start,end-start] }; ClearLooks: PUBLIC PROC = { node: Node ← PickNode[]; start, end: Offset; [start,end] ← PickTwo[node]; IF start=end THEN RETURN; BeforeUndo[node]; spanI.ClearLooks[TextSpan[node,start,end-start],event]; CheckLooks[node,allLooks,noLooks,start,end-start] }; CheckLooks: PROC [ node: Node, remove, add: Looks, start, len: Offset] = { out: Looks ← looksSI.LooksAND[remove,looksSI.LooksNOT[add]]; -- out contains looks that we are removing and not adding loc, runLen: Offset ← start; end: Offset ← start+len; runs: Runs ← editI.GetRuns[node]; IF runs=NIL THEN { IF add # noLooks THEN ERROR; RETURN }; runrdrI.SetPosition[runrdr1,runs,start]; UNTIL loc >= end DO -- check the runs in the changed section looks: Looks; [runLen,looks] ← runrdrI.Get[runrdr1]; IF looksSI.LooksAND[looks,add] # add THEN ERROR; IF looksSI.LooksAND[looks,out] # noLooks THEN ERROR; loc ← loc+runLen; ENDLOOP; TestUndo[] }; -- ***** Display text node as string nodeText: STRING ← [2000]; ShowNode: PROC [n: Node] RETURNS [s: STRING] = { -- for debugging s ← nodeText; s.length ← 0; AppendNode[n,s,0] }; AppendNode: PROC [n: Node, s: STRING, indent: NAT] = { THROUGH [0..indent) DO s[s.length] ← ' ; s.length ← s.length+1; ENDLOOP; roperdrI.SetPosition[roperdr1,n.rope]; FOR i:Offset ← editI.Size[n], i-1 UNTIL i=0 DO s[s.length] ← roperdrI.Get[roperdr1]; s.length ← s.length+1; ENDLOOP; s[s.length] ← 'M-100B; s.length ← s.length+1; FOR c:editI.Ref ← n.child, nodeI.Next[c] UNTIL c=NIL DO WITH cc:c SELECT FROM text => AppendNode[@cc,s,indent+4]; ENDCASE; ENDLOOP }; -- ***** Find and BackwardsFind backFindFlag: BOOLEAN ← FALSE; BackwardsFind: PUBLIC PROC = { DoFind: PROC = { [found,at,atEnd,before,after] ← findI.BackwardsFind[pattern: pattern, text: text, literal: FALSE, word: FALSE, ignoreLooks: FALSE, ignoreCase: TRUE, looksExact: FALSE] }; pattern, text: Node; found: BOOLEAN; patternLooks, textLooks: Looks; at, atEnd, before, after: Offset; IF backFindFlag THEN RETURN; backFindFlag ← TRUE; -- only do this once text ← editI.FromString["abc DEF .ghi. xjkly lmn ghi"]; -- 0 5 0 5 0 5 -- first just simple test, no special characters pattern ← editI.FromString["DEF"]; DoFind; IF ~found OR at#4 OR atEnd#7 OR before#at OR after#atEnd THEN ERROR; -- test for only lower case pattern ← editI.FromString["'D'E'F"]; DoFind; IF ~found OR at#4 OR atEnd#7 OR before#at OR after#atEnd THEN ERROR; -- pattern in lowercase, target in upper case pattern ← editI.FromString["def"]; DoFind; IF ~found OR at#4 OR atEnd#7 OR before#at OR after#atEnd THEN ERROR; -- use # inside pattern pattern ← editI.FromString["D#F"]; DoFind; IF ~found OR at#4 OR atEnd#7 OR before#at OR after#atEnd THEN ERROR; -- use * inside pattern to match 0 characters pattern ← editI.FromString["D*EF"]; DoFind; IF ~found OR at#4 OR atEnd#7 OR before#at OR after#atEnd THEN ERROR; -- use * inside pattern to match 1 character pattern ← editI.FromString["D*F"]; DoFind; IF ~found OR at#4 OR atEnd#7 OR before#at OR after#atEnd THEN ERROR; -- use @ inside pattern pattern ← editI.FromString["D@F"]; DoFind; IF ~found OR at#4 OR atEnd#7 OR before#at OR after#atEnd THEN ERROR; -- use & inside pattern to match 0 characters pattern ← editI.FromString["D&EF"]; DoFind; IF ~found OR at#4 OR atEnd#7 OR before#at OR after#atEnd THEN ERROR; -- use & inside pattern to match 1 character pattern ← editI.FromString["D&F"]; DoFind; IF ~found OR at#4 OR atEnd#7 OR before#at OR after#atEnd THEN ERROR; -- use ! and {}'s pattern ← editI.FromString["!{DEF}!"]; DoFind; IF ~found OR at#4 OR atEnd#7 OR before#at-1 OR after#atEnd+1 THEN ERROR; -- use word search pattern ← editI.FromString["DEF"]; [found,at,atEnd,before,after] ← findI.BackwardsFind[pattern: pattern, text: text, literal: FALSE, word: TRUE, ignoreLooks: FALSE, ignoreCase: TRUE, looksExact: FALSE]; IF ~found OR at#4 OR atEnd#7 OR before#at-1 OR after#atEnd+1 THEN ERROR; -- use ~ and {}'s pattern ← editI.FromString["DEF~{GHI}~x"]; DoFind; IF ~found OR at#9 OR atEnd#12 THEN ERROR; -- use | at start pattern ← editI.FromString["|abc"]; DoFind; IF ~found OR at#0 OR atEnd#3 THEN ERROR; -- use | at end pattern ← editI.FromString["ghi|"]; DoFind; IF ~found OR at#24 OR atEnd#27 THEN ERROR; -- use | at start and end pattern ← editI.FromString["|abc*{ghi}|"]; DoFind; IF ~found OR at#24 OR atEnd#27 THEN ERROR; -- requires unwinding wildcard stack pattern ← editI.FromString["b{YX}~YX&."]; text ← editI.FromString["bYX-YXaYXa."]; DoFind; IF ~found OR at#1 OR atEnd#3 THEN ERROR; -- test looks in patterns patternLooks ← PickLooks[]; textLooks ← looksSI.LooksOR[PickLooks[],patternLooks]; text ← editI.FromString["abc DEF .ghi. xjkly lmn ghi"]; pattern ← editI.FromString["DEF"]; editI.AddLooks[pattern,patternLooks]; editI.AddLooks[text,textLooks,4,3]; DoFind; IF ~found OR at#4 OR atEnd#7 THEN ERROR; pattern ← editI.FromString["D#@"]; editI.AddLooks[pattern,patternLooks]; DoFind; IF ~found OR at#4 OR atEnd#7 THEN ERROR; pattern ← editI.FromString["D*@"]; editI.AddLooks[pattern,patternLooks]; DoFind; IF ~found OR at#4 OR atEnd#7 THEN ERROR; pattern ← editI.FromString["D&@"]; editI.AddLooks[pattern,patternLooks]; DoFind; IF ~found OR at#4 OR atEnd#7 THEN ERROR; pattern ← editI.FromString["D@@"]; editI.AddLooks[pattern,patternLooks]; DoFind; IF ~found OR at#4 OR atEnd#7 THEN ERROR; }; findFlag: BOOLEAN ← FALSE; Find: PUBLIC PROC = { DoFind: PROC = { [found,at,atEnd,before,after] ← findI.Find[pattern: pattern, text: text, literal: FALSE, word: FALSE, ignoreLooks: FALSE, ignoreCase: TRUE, looksExact: FALSE] }; pattern, text: Node; found: BOOLEAN; patternLooks, textLooks: Looks; at, atEnd, before, after: Offset; IF findFlag THEN RETURN; findFlag ← TRUE; -- only do this once text ← editI.FromString["abc DEF .ghi. xjkly lmn ghi"]; -- 0 5 0 5 0 5 -- first just simple test, no special characters pattern ← editI.FromString["ghi"]; DoFind; IF ~found OR at#9 OR atEnd#12 OR before#at OR after#atEnd THEN ERROR; -- test for only lower case pattern ← editI.FromString["'g'h'i"]; DoFind; IF ~found OR at#9 OR atEnd#12 OR before#at OR after#atEnd THEN ERROR; -- pattern in uppercase, target in lower case pattern ← editI.FromString["GHI"]; DoFind; IF ~found OR at#9 OR atEnd#12 OR before#at OR after#atEnd THEN ERROR; -- use # inside pattern pattern ← editI.FromString["G#I"]; DoFind; IF ~found OR at#9 OR atEnd#12 OR before#at OR after#atEnd THEN ERROR; -- use * inside pattern to match 0 characters pattern ← editI.FromString["G*HI"]; DoFind; IF ~found OR at#9 OR atEnd#12 OR before#at OR after#atEnd THEN ERROR; -- use * inside pattern to match 1 character pattern ← editI.FromString["G*I"]; DoFind; IF ~found OR at#9 OR atEnd#12 OR before#at OR after#atEnd THEN ERROR; -- use @ inside pattern pattern ← editI.FromString["G@I"]; DoFind; IF ~found OR at#9 OR atEnd#12 OR before#at OR after#atEnd THEN ERROR; -- use & inside pattern to match 0 characters pattern ← editI.FromString["G&HI"]; DoFind; IF ~found OR at#9 OR atEnd#12 OR before#at OR after#atEnd THEN ERROR; -- use & inside pattern to match 1 character pattern ← editI.FromString["G&I"]; DoFind; IF ~found OR at#9 OR atEnd#12 OR before#at OR after#atEnd THEN ERROR; -- use ! and {}'s pattern ← editI.FromString["!{GHI}!"]; DoFind; IF ~found OR at#9 OR atEnd#12 OR before#at-1 OR after#atEnd+1 THEN ERROR; -- use word search pattern ← editI.FromString["GHI"]; [found,at,atEnd,before,after] ← findI.Find[pattern: pattern, text: text, literal: FALSE, word: TRUE, ignoreLooks: FALSE, ignoreCase: TRUE, looksExact: FALSE]; IF ~found OR at#9 OR atEnd#12 OR before#at-1 OR after#atEnd+1 THEN ERROR; -- use ~ and {}'s pattern ← editI.FromString["DEF~{GHI}~x"]; DoFind; IF ~found OR at#9 OR atEnd#12 THEN ERROR; -- use | at start pattern ← editI.FromString["|abc"]; DoFind; IF ~found OR at#0 OR atEnd#3 THEN ERROR; -- use | at end pattern ← editI.FromString["ghi|"]; DoFind; IF ~found OR at#24 OR atEnd#27 THEN ERROR; -- use | at start and end pattern ← editI.FromString["|abc*{ghi}|"]; DoFind; IF ~found OR at#24 OR atEnd#27 THEN ERROR; -- requires unwinding wildcard stack pattern ← editI.FromString[".&XY~{XY}b"]; text ← editI.FromString[".aXYaXY-XYb"]; DoFind; IF ~found OR at#8 OR atEnd#10 THEN ERROR; -- test looks in patterns patternLooks ← PickLooks[]; textLooks ← looksSI.LooksOR[PickLooks[],patternLooks]; text ← editI.FromString["abc DEF .ghi. xjkly lmn ghi"]; pattern ← editI.FromString["ghi"]; editI.AddLooks[pattern,patternLooks]; editI.AddLooks[text,textLooks,24,3]; DoFind; IF ~found OR at#24 OR atEnd#27 THEN ERROR; pattern ← editI.FromString["@#i"]; editI.AddLooks[pattern,patternLooks]; DoFind; IF ~found OR at#24 OR atEnd#27 THEN ERROR; pattern ← editI.FromString["@*i"]; editI.AddLooks[pattern,patternLooks]; DoFind; IF ~found OR at#24 OR atEnd#27 THEN ERROR; pattern ← editI.FromString["@&i"]; editI.AddLooks[pattern,patternLooks]; DoFind; IF ~found OR at#24 OR atEnd#27 THEN ERROR; pattern ← editI.FromString["@@i"]; editI.AddLooks[pattern,patternLooks]; DoFind; IF ~found OR at#24 OR atEnd#27 THEN ERROR; }; -- ***** Support routines for node edits LocateSpan: PUBLIC PROC [span: Span] RETURNS [start, len, size: Offset] = { first: Ref ← span.start.node; last: Ref ← span.end.node; root: Ref ← nodeI.Root[first]; size ← 0; start ← len ← -1; FOR n: Ref ← nodeI.FirstChild[root], nodeI.StepForward[n] DO SELECT n FROM NIL => RETURN; first => { start ← size; IF n=last THEN len ← 1 }; last => len ← size-start+1; ENDCASE; size ← size+1; ENDLOOP }; LocateNode: PUBLIC PROC [node: Ref] RETURNS [loc, size: Offset] = { root: Ref ← nodeI.Root[node]; size ← 0; loc ← -1; FOR n: Ref ← nodeI.FirstChild[root], nodeI.StepForward[n] DO SELECT n FROM NIL => RETURN; node => loc ← size; ENDCASE; size ← size+1; ENDLOOP }; PickTrees: PUBLIC PROC RETURNS [tree1, tree2: Ref] = { RETURN [PickTree[], PickTree[]] }; PickTree: PUBLIC PROC RETURNS [tree: Ref] = { RETURN [IF RandomBoolean[] THEN alphaTree ELSE betaTree] }; PickPlace: PUBLIC PROC RETURNS [Place] = { RETURN [SELECT randLI.Choose[1,3] FROM 1 => before, 2 => after, 3 => child, ENDCASE => ERROR] }; PickNodeSpan: PUBLIC PROC [tree: Ref] RETURNS [span: Span, start, len, size: Offset] = { end: Offset; first, last: Ref; IF (size ← TreeSize[tree]) = 0 THEN RETURN [nodeI.nullSpan, 0, 0, 0]; DO [start, end] ← ChooseTwo[0,size]; IF (len ← end-start) > 0 THEN EXIT; ENDLOOP; first ← FindFirstNode[tree, start]; last ← FindLastNode[first, len]; span ← nodeI.MakeNodeSpan[first, last] }; PickNodeLoc: PUBLIC PROC [tree: Ref] RETURNS [dest: Location, loc, size: Offset] = { node: Ref; IF (size ← TreeSize[tree]) = 0 THEN RETURN [nodeI.nullLocation, 0, 0]; loc ← Choose[0,size-1]; node ← FindTreeNode[tree, loc]; dest ← nodeI.MakeNodeLoc[node] }; FindTreeNode, FindFirstNode: PUBLIC PROC [tree: Ref, loc: Offset] RETURNS [node: Ref] = { place: Offset ← 0; FOR n: Ref ← nodeI.FirstChild[tree], nodeI.StepForward[n] DO IF place=loc OR n=NIL THEN RETURN [n]; place ← place+1; ENDLOOP }; FindLastNode: PUBLIC PROC [first: Ref, len: Offset] RETURNS [last: Ref] = { IF len <= 0 THEN RETURN [NIL]; FOR n: Ref ← first, nodeI.StepForward[n] DO IF (len←len-1)=0 OR n=NIL THEN RETURN [n]; ENDLOOP }; TreeSize: PUBLIC PROC [tree: Ref] RETURNS [size: Offset] = { size ← 0; FOR n: Ref ← nodeI.FirstChild[tree], nodeI.StepForward[n] UNTIL n=NIL DO size ← size+1; ENDLOOP }; biggestTree: Offset = 200; smallestTree: Offset = 50; AdjustTree: PUBLIC PROC [tree: Ref, size: Offset] = { DO SELECT size FROM > biggestTree => size ← DeleteFromTree[tree, size, size-biggestTree]; < smallestTree => size ← InsertInTree[tree, size, smallestTree-size]; ENDCASE => RETURN; ENDLOOP }; -- ***** Support routines Choose: PUBLIC PROC [min,max: Offset] RETURNS [Offset] = { IF min >= max THEN RETURN [max]; RETURN [SELECT randLI.Choose[1,8] FROM 1 => min, 2 => max, ENDCASE => LOOPHOLE[randLI.Choose[min,max]]] }; ChooseTwo: PUBLIC PROC [min,max: Offset] RETURNS [first,second: Offset] = { temp: Offset; first ← Choose[min,max]; second ← Choose[min,max]; IF first > second THEN { temp←first; first←second; second←temp }}; ChooseNAT: PUBLIC PROC [min,max: NAT] RETURNS [NAT] = { maxNAT: NAT = LAST[NAT]; IF min >= max THEN RETURN [max]; RETURN [SELECT randLI.Choose[1,8] FROM 1 => min, 2 => max, ENDCASE => LOOPHOLE[Inline.LowHalf[randLI.Choose[min,max]]]] }; ChooseTwoNATs: PUBLIC PROC [min,max: NAT] RETURNS [first,second: NAT] = { temp: NAT; first ← ChooseNAT[min,max]; second ← ChooseNAT[min,max]; IF first > second THEN { temp←first; first←second; second←temp }}; RandomBoolean: PUBLIC PROC RETURNS [BOOLEAN] = { RETURN [randC.Choose[0,1]=0] }; OneInN: PUBLIC PROC [n: NAT] RETURNS [BOOLEAN] = { RETURN [randC.Choose[1,n]=1] }; PickNode: PUBLIC PROC RETURNS [Node] = { RETURN [ IF RandomBoolean[] THEN alpha ELSE beta] }; PickNodes: PUBLIC PROC RETURNS [source,dest: Node] = { RETURN [PickNode[], PickNode[]] }; PickOne: PUBLIC PROC [node: Node] RETURNS [loc: Offset] = { -- picks a random places within node RETURN [Choose[0,editI.Size[node]]] }; PickTwo: PUBLIC PROC [node: Node] RETURNS [start, end: Offset] = { -- picks two random places within node -- orders them such that start <= end size, temp: Offset; IF (size ← editI.Size[node]) = 0 THEN RETURN; start ← Choose[0,size]; end ← Choose[0,size]; IF start > end THEN { temp←start; start←end; end←temp }}; PickLooks: PUBLIC PROC RETURNS [Looks] = { RETURN [LOOPHOLE[randLI.Random[]]] }; PickRope: PUBLIC PROC RETURNS [Rope] = { source: Node ← PickNode[]; sourceStart, sourceEnd: Offset; [sourceStart,sourceEnd] ← PickTwo[source]; RETURN [ropeI.Substr[editI.GetRope[source],sourceStart,sourceEnd-sourceStart]] }; AdjustLengths: PUBLIC PROC = { AdjustLength[alpha]; AdjustLength[beta] }; AdjustLength: PUBLIC PROC [node: Node] = { DO SELECT editI.Size[node] FROM > longest => DeleteFromNode[node]; < shortest => InsertStringInNode[node]; ENDCASE => RETURN; ENDLOOP }; event: PUBLIC Event ← undoI.Create[]; undoEvent: PUBLIC Event ← undoI.Create[]; txt1, txt2: Node; t1Rope, t2Rope: rI.Ref; t1Runs, t2Runs: looksI.Runs; BeforeUndo: PUBLIC PROC [n1, n2: Node ← NIL] = { txt1 ← n1; t1Rope ← txt1.rope; t1Runs ← txt1.runs; IF n2 = NIL OR n2 = n1 THEN txt2 ← NIL ELSE { txt2 ← n2; t2Rope ← txt2.rope; t2Runs ← txt2.runs }; undoI.Reset[event] }; TestUndo: PUBLIC PROC = { -- t1RopeBefore, t2RopeBefore, t1RopeAfter, t2RopeAfter: rI.Ref; t1RunsBefore, t2RunsBefore, t1RunsAfter, t2RunsAfter: looksI.Runs; oldSize1, oldSize2, newSize1, newSize2: Offset; node1, node2: nodeI.RefTextNode; t1RopeBefore ← t1Rope; t2RopeBefore ← t2Rope; -- set by PreEdit t1RunsBefore ← t1Runs; t2RunsBefore ← t2Runs; -- set by PreEdit IF (node1 ← txt1) # NIL THEN { t1RopeAfter ← node1.rope; t1RunsAfter ← node1.runs }; IF (node2 ← txt2) # NIL THEN { t2RopeAfter ← node2.rope; t2RunsAfter ← node2.runs }; undoI.Reset[undoEvent]; undoI.Undo[event,undoEvent]; IF node1 # NIL THEN { oldSize1 ← ropeI.Size[t1RopeBefore]; IF ropeI.Size[node1.rope] # oldSize1 THEN ERROR; CheckRopes[node1.rope,0,t1RopeBefore,0,oldSize1]; CheckRuns[node1.runs,0,t1RunsBefore,0,oldSize1] }; IF node2 # NIL THEN { oldSize2 ← ropeI.Size[t2RopeBefore]; IF ropeI.Size[node2.rope] # oldSize2 THEN ERROR; CheckRopes[node2.rope,0,t2RopeBefore,0,oldSize2]; CheckRuns[node2.runs,0,t2RunsBefore,0,oldSize2] }; undoI.Undo[undoEvent]; -- undo the previous undo IF node1 # NIL THEN { newSize1 ← ropeI.Size[t1RopeAfter]; IF ropeI.Size[node1.rope] # newSize1 THEN ERROR; CheckRopes[node1.rope,0,t1RopeAfter,0,newSize1]; CheckRuns[node1.runs,0,t1RunsAfter,0,newSize1] }; IF node2 # NIL THEN { newSize2 ← ropeI.Size[t2RopeAfter]; IF ropeI.Size[node2.rope] # newSize2 THEN ERROR; CheckRopes[node2.rope,0,t2RopeAfter,0,newSize2]; CheckRuns[node2.runs,0,t2RunsAfter,0,newSize2] }}; -- ***** Initialization numEdits: PUBLIC NAT ← 0; PreEdit: PROC [t1, t2: nodeI.Ref, change: REF READONLY notify.Change] = { IF t1 = NIL THEN ERROR; IF t1.kind # text THEN ERROR; IF t2 # NIL AND t2.kind # text THEN ERROR }; PostEdit: PROC [t1, t2: nodeI.Ref, change: REF READONLY notify.Change] = { OPEN checkI; IF t1 # NIL THEN WITH x:t1 SELECT FROM text => CheckTextNode[@x]; ENDCASE => ERROR; IF t2 # NIL THEN WITH x:t2 SELECT FROM text => CheckTextNode[@x]; ENDCASE => ERROR; }; Flatten: PROC [text: Node] = { -- flatten if more than pieceMax pieces pieceMax: Offset = 250; oldRope: Rope ← text.rope; oldRuns: Runs ← text.runs; newRope: Rope; newRuns: Runs; oldSize: Offset ← ropeI.Size[oldRope]; ropePieces: Offset ← ropeI.CountPieces[oldRope]; IF ropePieces > pieceMax THEN { text.rope ← newRope ← ropeI.Flatten[oldRope]; text.runs ← newRuns ← looksI.Flatten[oldRuns]; checkI.CheckTextNode[text]; CheckRopes[newRope,0,oldRope,0,oldSize]; CheckRuns[newRuns,0,oldRuns,0,oldSize]; text.count ← 0 } ELSE { text.count ← text.count/2 }}; InitReaders: PROC = { runrdr1 ← runrdrI.Create[]; runrdr2 ← runrdrI.Create[]; roperdr1 ← roperdrI.Create[]; roperdr2 ← roperdrI.Create[] }; InitNodes: PROC = { alpha ← editI.FromRope[rI.FromString[ "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"]]; beta ← editI.FromRope[rI.FromString[ "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ"]]; AdjustLengths[]; }; InitTrees: PROC = { treeFileName: Rope ← rI.FromString["TestTree.Text"]; alphaTree ← GetTree.ReadIndent[treeFileName]; TreeCheck.Verify[alphaTree]; betaTree ← GetTree.ReadIndent[treeFileName]; TreeCheck.Verify[betaTree]; }; brkAt: NAT ← LAST[NAT]; testNum: NAT; Run: PUBLIC PROC [shuffle: NAT ← 0, numberOfTests: NAT ← LAST[NAT], mode: RunMode ← both] = { [] ← randC.InitRandom[]; [] ← randLI.InitRandom[]; notify.AddNotifyProc[PreEdit,before]; notify.AddNotifyProc[PostEdit,after]; InitReaders[]; InitNodes[]; InitTrees[]; FOR i:CARDINAL IN [0..numTests) DO testCountArray[i] ← 0; ENDLOOP; FOR i:CARDINAL IN [0..numNodeTests) DO nodeTestCountArray[i] ← 0; ENDLOOP; THROUGH [0..shuffle) DO [] ← randC.Choose[0,numTests-1]; ENDLOOP; UNTIL numEdits >= numberOfTests DO IF mode=nodesOnly OR (mode=both AND RandomBoolean[]) THEN { -- do a node test this time testNum ← randC.Choose[0,numNodeTests-1]; nodeTestProcArray[testNum][]; nodeTestCountArray[testNum] ← nodeTestCountArray[testNum]+1 } ELSE { -- do a text test testNum ← randC.Choose[0,numTests-1]; testProcArray[testNum][]; testCountArray[testNum] ← testCountArray[testNum]+1; IF alpha.count >= 30 THEN Flatten[alpha]; IF beta.count >= 30 THEN Flatten[beta] }; IF (numEdits ← numEdits+1) >= brkAt THEN { -- break here foo: NAT ← 0; foo ← foo+1 }; ENDLOOP }; END.