-- RopeReader.mesa
-- written by Bill Paxton, January 1981
-- last edit by Bill Paxton, March 23, 1981 3:17 PM

-- RopeReader provides fast inline access to characters in ropes
-- designed for speed in reading a sequence of characters
-- either forwards or backwards
-- from packed arrays such as created by mapping pages from a file

-- create a reader by RopeReader.Create
-- reader: RopeReader.Ref ← RopeReader.Create[];

-- position it where you wanto to read by SetPosition
-- RopeReader.SetPosition[reader, rope, index];
-- where rope is Rope.Ref to read from
-- and index is the initial position for the reader
-- you can reposition a reader at any time

-- read its position in the following ways:
-- [rope, index] ← RopeReader.Position[reader];
-- rope ← RopeReader.GetRope[reader];
-- index ← RopeReader.GetIndex[reader];

-- to read in ascending order (left to right)
-- initialize position before first character to be read
-- for example, use 0 to start at beginning of rope
-- then call Get which reads to the right and increments the position
-- char ← RopeReader.Get[reader];

-- to read in descending order (right to left)
-- initialize position after first character to be read
-- for example, use size of rope to start at end
-- then call Backwards which reads to the left and decrements the position
-- char ← RopeReader.Backwards[reader];

-- can also get a character without changing the reader position
-- to look at the next character that Get would return, call Peek
-- char ← RopeReader.Peek[reader];
-- to look at what Backwards would return, call PeekBackwards
-- char ← RopeReader.PeekBackwards[reader];

-- can intermix reading or peeking to left and right
-- don’t need to reinitialize the reader to change direction

-- if read off either end of rope, you can get an error
-- or a client-specified characters

-- operations are also provided to read a block of characters
-- destination given by either a REF TEXT or a REF chars array

DIRECTORY
Rope USING [Ref];

RopeReader: DEFINITIONS =
BEGIN OPEN r:Rope;

-- ***** RopeReader Declarations

Ref: TYPE = REF Body;
Body: TYPE = PRIVATE RECORD [
current: NAT ← 0, -- index of current character
first: NAT ← 0, -- index of first character to read
after: NAT ← 0, -- index beyond last character to read
chars: Chars, -- characters that we are currently reading
rope: Rope, -- rope that we are reading
index: Card ← 0, -- index in rope of first character
charForEndOfRope: BOOLEAN ← FALSE,
-- if true, return endChar; else give ERROR ReadOffEnd
endChar: Char ← 0C -- char to return when reach end of rope
];

Rope: TYPE = r.Ref;
Char: TYPE = CHARACTER;
Card: TYPE = LONG CARDINAL;

ReadOffEnd: ERROR;

charsPerPage: NAT = 512;
pagesPerArray: NAT = 32;
charsPerArray: NAT = charsPerPage*pagesPerArray; -- 16384
CharsArray: TYPE = PACKED ARRAY [0..charsPerArray) OF Char;
Chars: TYPE = REF READONLY CharsArray;

-- ***** RopeReader Operations

Create: PROC RETURNS [Ref] = INLINE { RETURN[NEW[Body]] };

SetPosition: PROC [reader: Ref, rope: Rope, index: Card ← 0] = INLINE {
IF GetRope[reader]#rope OR GetIndex[reader]#index THEN {
reader.current ← reader.first ← reader.after ← 0;
reader.chars ← NIL; reader.rope ← rope; reader.index ← index}};

SetIndex: PROC [reader: Ref, index: Card ← 0] = INLINE {
IF GetIndex[reader]#index THEN {
reader.current ← reader.first ← reader.after ← 0;
reader.chars ← NIL; reader.index ← index}};

BackupIndex: PROC [reader: Ref, amount: Card] = INLINE {
SetIndex[reader, GetIndex[reader]-amount] };

BumpIndex: PROC [reader: Ref, amount: Card] = INLINE {
SetIndex[reader, GetIndex[reader]+amount] };

Position: PROC [reader: Ref]
RETURNS [rope: Rope, index: Card] = INLINE {
RETURN [reader.rope, reader.index+reader.current-reader.first] };

GetRope: PROC [reader: Ref]
RETURNS [rope: Rope] = INLINE { RETURN [reader.rope] };

GetIndex: PROC [reader: Ref]
RETURNS [index: Card] = INLINE
{ RETURN [reader.index+reader.current-reader.first] };

GetEndChar: PROC [reader: Ref]
RETURNS [endChar: Char] = INLINE { RETURN [reader.endChar] };

CharForEndOfRope: PROC [reader: Ref] RETURNS [BOOLEAN] =
INLINE { RETURN [reader.charForEndOfRope] };

SetCharForEndOfRope: PROC [reader: Ref, char: Char] = INLINE {
reader.endChar ← char; reader.charForEndOfRope ← TRUE };

ClearCharForEndOfRope: PROC [reader: Ref] = INLINE {
reader.charForEndOfRope ← FALSE };

ReaderProc: TYPE = PROC [reader: Ref] RETURNS [Char];

Get: ReaderProc = INLINE {
-- get character, then increment reader location
current: NAT;
IF (current←reader.current) >= reader.after THEN
RETURN[ReadChar[reader, get]];
reader.current ← current+1;
RETURN[reader.chars[current]]};

Backwards: ReaderProc = INLINE {
-- decrement reader location, then get character
current: NAT;
IF (current←reader.current) <= reader.first THEN
RETURN[ReadChar[reader, backwards]];
RETURN[reader.chars[reader.current ← current-1]]};

Peek: ReaderProc = INLINE {
-- get character without incrementing reader location
current: NAT;
IF (current←reader.current) >= reader.after THEN
RETURN[ReadChar[reader, peek]];
RETURN[reader.chars[current]]};

PeekBackwards: ReaderProc = INLINE {
-- like Backwards, but doesn’t change position
current: NAT;
IF (current←reader.current) <= reader.first THEN
RETURN[ReadChar[reader, peekbackwards]];
RETURN[reader.chars[current-1]]};

Mode: PRIVATE TYPE = {get, backwards, peek, peekbackwards};
ReadChar: PRIVATE PROC [reader: Ref, mode: Mode] RETURNS [Char];

-- the following operations provide for reading blocks of characters

GetText: PROC
[reader: Ref, b: REF TEXT, length: NAT ← LAST[NAT]]
RETURNS [count: NAT];
-- appends characters to TEXT from rope; returns number of characters

BackwardsGetText: PROC
[reader: Ref, b: REF TEXT, length: NAT ← LAST[NAT]]
RETURNS [count: NAT];
-- appends characters to TEXT from rope; returns number of characters

GetChars: PROC [
reader: Ref,
b: REF CharsArray,
length: NAT ← LAST[NAT],
offset: NAT ← 0]
RETURNS [count: NAT];
-- appends characters to CharsArray from rope; returns number of characters

BackwardsGetChars: PROC [
reader: Ref, b: REF CharsArray,
length: NAT ← LAST[NAT],
offset: NAT ← 0]
RETURNS [count: NAT];

-- The following is available for creating ropes using Chars arrays

CharsRope: PROC [Chars] RETURNS [Rope];

END.