-- RopeEditImpl.mesa -- written by Bill Paxton, February 1981 -- last edit by Bill Paxton, 17-Jan-82 15:47:25 DIRECTORY RopeEdit, RopeEditingAlloc, Rope, RopeFrom, RopeReader, RopeInline, Directory, IOStream; RopeEditImpl: PROGRAM IMPORTS RopeEdit, RopeReader, RopeInline, RopeFrom, Rope, Directory, IOStream, RopeEditingAlloc EXPORTS RopeEdit SHARES Rope = BEGIN OPEN RopeEdit, RopeInline; FlatMax: Offset = 30; -- if shorter, copy to flat rope Depth: PUBLIC PROC [rope: ROPE] RETURNS [depth: INTEGER] = { IF rope=NIL THEN RETURN [0]; WITH x:rope SELECT FROM text => depth ← 0; node => WITH x:x SELECT FROM substr => depth ← x.depth; concat => depth ← x.depth; replace => depth ← x.depth; object => depth ← 0; ENDCASE => ERROR Rope.NoRope; ENDCASE => ERROR Rope.NoRope }; -- Edit Substr: PUBLIC PROC [base: ROPE, start: Offset ← 0, len: Offset ← MaxLen] RETURNS [ROPE] = { IF start < 0 THEN ERROR; DO IF base=NIL OR len<=0 THEN RETURN[NIL]; WITH x:base SELECT FROM text => { rem: Offset; IF (rem ← NonNeg[x.length-start]) <= len THEN IF start = 0 THEN RETURN [base] ELSE len ← rem}; node => WITH x:x SELECT FROM substr => { rem: Offset; IF (rem ← NonNeg[x.size-start]) <= len THEN IF start = 0 THEN RETURN [base] ELSE len ← rem; start ← start + x.start; base ← x.base; LOOP}; concat => { xpos, rem: Offset; IF (rem ← NonNeg[x.size-start]) <= len THEN IF start = 0 THEN RETURN [base] ELSE len ← rem; IF start >= (xpos ← x.pos) THEN { start ← start - xpos; base ← x.rest; LOOP}; IF xpos >= start+len THEN {base ← x.base; LOOP}}; replace => { xstart, xnew, rem: Offset; IF (rem ← NonNeg[x.size-start]) <= len THEN IF start = 0 THEN RETURN [base] ELSE len ← rem; IF start >= (xnew ← x.newPos) THEN { start ← start - xnew + x.oldPos; base ← x.base; LOOP}; IF (rem ← start+len) <= (xstart ← x.start) THEN { base ← x.base; LOOP}; IF start >= xstart AND rem <= xnew THEN { start ← start - xstart; base ← x.replace; LOOP}}; object => { rem: Offset; IF (rem ← x.size-start) <= len THEN IF start = 0 THEN RETURN [base] ELSE len ← rem}; ENDCASE => ERROR Rope.NoRope; ENDCASE => ERROR Rope.NoRope; EXIT; ENDLOOP; RETURN [ IF len <= FlatMax THEN RopeFrom.FlatSubstr[base,start,len] ELSE RopeFrom.qZone.NEW[Tsubstr ← [node[substr[len,base,start,Depth[base]+1]]]]] }; Concat: PUBLIC PROC [base, rest: ROPE, baseLen, restLen: Offset ← MaxLen] RETURNS [new: ROPE] = { size: Offset; IF baseLen=MaxLen THEN baseLen ← RopeInline.InlineSize[base]; IF restLen=MaxLen THEN restLen ← RopeInline.InlineSize[rest]; IF restLen <= FlatMax AND (new ← RopeFrom.TryAppendConcat[base,rest,baseLen,restLen]) # NIL THEN RETURN [new]; IF (size ← baseLen+restLen) <= FlatMax THEN RETURN [RopeFrom.FlatConcat[base,rest,baseLen,restLen,FALSE]]; IF rest=NIL THEN RETURN [base]; IF base=NIL THEN RETURN [rest]; IF baseLen < FlatMax THEN WITH x:rest SELECT FROM node => WITH x:x SELECT FROM concat => -- try ((base & rest.base) & rest.rest) IF x.pos <= FlatMax AND (new ← RopeFrom.TryAppendConcat[base, x.base, baseLen, x.pos]) # NIL THEN RETURN [RopeFrom.qZone.NEW[Tconcat ← [node[concat[size, new, x.rest, baseLen+x.pos, 1+MAX[Depth[new],Depth[x.rest]]]]]]] ELSE IF baseLen+x.pos <= FlatMax THEN { new ← RopeFrom.FlatConcat[base, x.base, baseLen, x.pos, FALSE]; RETURN [RopeFrom.qZone.NEW[Tconcat ← [node[concat[size, new, x.rest, baseLen+x.pos, 1+MAX[Depth[new],Depth[x.rest]]]]]]] }; ENDCASE; ENDCASE; IF restLen <= FlatMax THEN WITH x:base SELECT FROM node => WITH x:x SELECT FROM concat => -- try (base.base & (base.rest & rest)) IF (new ← RopeFrom.TryAppendConcat[x.rest, rest, x.size-x.pos, restLen]) # NIL THEN RETURN [RopeFrom.qZone.NEW[Tconcat ← [node[concat[size,x.base,new,x.pos, 1+MAX[Depth[new],Depth[x.base]]]]]]] ELSE IF restLen+x.size-x.pos <= FlatMax THEN { new ← RopeFrom.FlatConcat[x.rest, rest, x.size-x.pos, restLen, FALSE]; RETURN [RopeFrom.qZone.NEW[Tconcat ← [node[concat[size,x.base,new,x.pos, 1+MAX[Depth[new],Depth[x.base]]]]]]]}; ENDCASE; ENDCASE; RETURN [RopeFrom.qZone.NEW[Tconcat ← [node[concat[size,base,rest,baseLen, 1+MAX[Depth[base],Depth[rest]]]]]]] }; Copy: PUBLIC PROC [ dest: ROPE, destLoc: Offset ← 0, source: ROPE, start: Offset ← 0, len: Offset ← MaxLen, destSize: Offset ← MaxLen] RETURNS [ROPE] = { size: Offset; IF len=MaxLen THEN { sourceSize: Offset ← RopeInline.InlineSize[source]; IF start >= sourceSize THEN RETURN [dest]; len ← MIN[len,sourceSize-start] }; IF destSize=MaxLen THEN destSize ← RopeInline.InlineSize[dest]; IF destLoc > destSize THEN destLoc ← destSize; IF (size←destSize+len) <= FlatMax THEN RETURN [ RopeFrom.FlatCopy[dest,destLoc,source,start,len,destSize]]; RETURN [Insert[dest,destLoc,Substr[source,start,len],destSize,len]]}; Move: PUBLIC PROC [ base: ROPE, dest, start: Offset ← 0, len, baseSize: Offset ← MaxLen] RETURNS [ROPE] = { moving, newbase: ROPE; IF baseSize=MaxLen THEN baseSize ← RopeInline.InlineSize[base]; IF start >= baseSize THEN RETURN [base]; len ← MIN[len,baseSize-start]; IF len=0 THEN RETURN [base]; IF dest > baseSize THEN dest ← baseSize; IF dest IN [start..start+len] THEN RETURN[base]; IF baseSize <= FlatMax THEN RETURN [RopeFrom.FlatMove[base,dest,start,len,baseSize]]; moving ← Substr[base,start,len]; newbase ← Delete[base,start,len,baseSize]; IF dest > start THEN dest ← dest-len; RETURN [Insert[newbase,dest,moving,baseSize-len,len]]}; BadTranspose: PUBLIC ERROR = CODE; Transpose: PUBLIC PROC [ base: ROPE, astart, alen, bstart, blen: Offset, baseSize: Offset ← MaxLen] RETURNS [ROPE] = { -- [astart..astart+alen) must not intersect [bstart..bstart+blen) newbase: ROPE; IF astart > bstart THEN { -- switch them so a-part before b-part start: Offset ← astart; len: Offset ← alen; astart ← bstart; bstart ← start; alen ← blen; blen ← len }; SELECT astart+alen FROM = bstart => { -- the pieces are adjacent RETURN [Move[base, astart, bstart, blen, baseSize]]}; > bstart => ERROR BadTranspose; ENDCASE; IF baseSize=MaxLen THEN baseSize ← RopeInline.InlineSize[base]; IF baseSize <= FlatMax THEN RETURN [ RopeFrom.FlatTranspose[base, astart, alen, bstart, blen, baseSize]]; newbase ← Move[base, bstart+blen, astart, alen, baseSize]; RETURN [Move[newbase, astart, bstart-alen, blen, baseSize]]}; RopeStats: PUBLIC PROC [base: ROPE, start: Offset ← 0, len: Offset ← MaxLen] RETURNS [size, pieces, depth: Offset] = { rem, altDepth, subDepth, subPieces: Offset; size ← RopeInline.InlineSize[base]; rem ← NonNeg[size - NonNeg[start]]; altDepth ← 0; IF len > rem THEN len ← rem; pieces ← depth ← 0; WHILE len > 0 DO x: ROPE ← base; WITH x: x SELECT FROM text => RETURN [size,pieces+1,MAX[depth,altDepth]]; node => { depth ← depth+1; WITH x: x SELECT FROM substr => {base ← x.base; start ← start + x.start; LOOP}; concat => {subLen: Offset ← x.pos - start; IF subLen > 0 THEN {IF len <= subLen THEN {base ← x.base; LOOP}; [,subPieces,subDepth] ← RopeStats[x.base, start, subLen]; pieces ← pieces+subPieces; altDepth ← MAX[altDepth,depth+subDepth]; len ← len - subLen; start ← 0} ELSE start ← -subLen; base ← x.rest; LOOP}; replace => -- three pieces to consider (first, middle, last) {xstart: Offset ← x.start; len1: Offset ← xstart - start; base ← x.base; IF len1 > 0 THEN {-- a piece in first section of rope IF len1 >= len THEN LOOP; -- only in first section [,subPieces,subDepth] ← RopeStats[base, start, len1]; pieces ← pieces+subPieces; altDepth ← MAX[altDepth,depth+subDepth]; start ← xstart; len ← len - len1; len1 ← 0}; {xpos: Offset ← x.newPos; len2: Offset ← xpos - start; IF len2 <= 0 THEN {-- no piece in middle section start ← x.oldPos - len2; LOOP}; -- a piece in middle section of replace node base ← x.replace; start ← -len1; IF len2 >= len THEN LOOP; -- only in middle section [,subPieces,subDepth] ← RopeStats[base, start, len2]; pieces ← pieces+subPieces; altDepth ← MAX[altDepth,depth+subDepth]; base ← x.base; start ← x.oldPos; len ← len - len2; }}; object => RETURN [size,pieces+1,MAX[depth,altDepth]]; ENDCASE => ERROR Rope.NoRope }; ENDCASE => ERROR Rope.NoRope; ENDLOOP; RETURN [0,0,0]; }; -- **** IOStream Operations **** PutRope: PUBLIC PROC [stream: IOStream.Handle, rope: ROPE] = { reader: RopeReader.Ref ← RopeReader.GetRopeReader[]; size: Offset ← RopeInline.InlineSize[rope]; cnt: NAT; blockSize: NAT = 512; block: REF TEXT ← RopeEditingAlloc.GetBlock[]; RopeReader.SetPosition[reader,rope]; block.length ← 0; UNTIL size=0 DO IF block.length >= blockSize THEN { -- buffer is full IOStream.PutBlock[stream,block]; block.length ← 0 }; cnt ← RopeReader.GetString[reader:reader, str:block]; size ← size-cnt; ENDLOOP; IF block.length > 0 THEN IOStream.PutBlock[stream,block]; RopeEditingAlloc.FreeBlock[block]; RopeReader.FreeRopeReader[reader] }; GetRope: PUBLIC PROC [stream: IOStream.Handle, len: Offset ← MaxLen] RETURNS [ROPE] = { RETURN [RopeFrom.Stream[stream,len]] }; -- **** Filing Operations **** ToFile: PUBLIC PROC [fileName, rope: ROPE, start: Offset ← 0] = { stream: IOStream.Handle ← IOStream.CreateFileStream[Rope.Flatten[fileName],write]; IOStream.SetLength[stream,start]; IOStream.SetIndex[stream,start]; PutRope[stream,rope]; IOStream.Close[stream] }; FromFile: PUBLIC PROC [fileName: ROPE, start: Offset ← 0, len: Offset ← MaxLen, okToMapFile: BOOLEAN ← FALSE] RETURNS [ROPE] = { RETURN [RopeFrom.File[ Directory.Lookup[fileName: LOOPHOLE[Rope.Flatten[fileName]]], start, len, FALSE, MaxLen, okToMapFile]] }; -- ***** Initialization charPropertyTable: PUBLIC REF READONLY CharPropertyTable; LegalCharacters: TYPE = CHAR[0C..177C]; Start: PUBLIC PROC = { cpt: REF CharPropertyTable ← NEW[CharPropertyTable]; ch: CHAR; i: CARDINAL; punctuationString: STRING = "!@#$%~&*()-`=+[{]};:'"",.<>/?\\|←↑"L; charPropertyTable ← cpt; FOR ch IN CHAR DO cpt[ch] ← illegal; ENDLOOP; FOR ch IN LegalCharacters DO cpt[ch] ← alphaNumeric; ENDLOOP; cpt[TAB] ← white; cpt[SP] ← white; cpt[CR] ← white; cpt[140C] ← punctuation; FOR i IN [0 .. punctuationString.length) DO cpt[punctuationString[i]] ← punctuation; ENDLOOP; }; END.