ian@unix.computer-science.manchester.ac.uk (Ian Cottam) (03/15/88)
Submitted-By: "Ian Cottam" <ian@unix.computer-science.manchester.ac.uk> Archive-Name: pstrings comp.sources.misc: Volume 2, Issue 75 Submitted-By: "Ian Cottam" <ian@unix.computer-science.manchester.ac.uk> Archive-Name: pstrings #! /bin/sh # This is a shell archive, meaning: # 1. Remove everything above the #! /bin/sh line. # 2. Save the resulting text in a file. # 3. Execute the file with /bin/sh (not csh) to create the files: # README # strings.h # CtoS.p # assignS.p # compare.p # concatS.p # disposeS.p # emptyS.p # eqS.p # finalS.p # first.p # geS.p # getsubS.p # gtS.p # indexS.p # initS.p # initvalparamS.p # leS.p # lengthS.p # ltS.p # matchS.p # mk.p # mkS.p # mkStaticS.p # neS.p # newS.p # next.p # readS.p # readtS.p # repS.p # updateS.p # writeS.p # writelnS.p # Makefile # This archive created: Tue Mar 15 10:11:49 1988 export PATH; PATH=/bin:$PATH if test -f 'README' then echo shar: will not over-write existing file "'README'" else cat << \SHAR_EOF > 'README' This is an Unbounded-length Strings package I wrote for our first year undergrads to use some years ago. It is written in and assumes you are using an ISO level 1 conforming Pascal compiler. (If they come pretty close e.g. SUN Pascal then you will be alright. N.B. Berkeley pc is NOT close enough -- at least the version I have which is that with 4.3BSD.) I have used the package with: SUN Pascal, VAX-VMS-Pascal, and York Pascal (a UN*X/portable Pascal compiler) on VAX-UN*X. See the strings.h header file for some implementation comments. Where you put things like #include is, of course, compiler specific. The distributed version should work on SUNs; other systems will require you to make trivial (hopefully) mods. Even on SUNs you may have trouble with erroneous complaints from /usr/lib/pc3 -- the separate compilation checker -- about redefinitions. Personally, I don't bother with /usr/lib/pc3. Good luck -Ian Cottam Univ of Manchester, Dept of Comp Sci, Oxford Rd, Manchester M13 9PL, UK, ian@ux.cs.man.ac.uk SHAR_EOF fi # end of overwriting check if test -f 'strings.h' then echo shar: will not over-write existing file "'strings.h'" else cat << \SHAR_EOF > 'strings.h' { * * String handling package in Pascal (ISO Level 1). * * This package of procedures and functions implements unbounded * Strings of Characters. * * N.B. All string variables MUST be initialised via initS(s). * Assignment MUST be via assignS(dest, src). * If desired, storage may be reclaimed via finalS(s). * i.e. * var s,t: String; * . . . * initS(s); initS(t); * . . . * assignS(t, concatS(mkS('Join this string '), mkS('to this'))); * assignS(s, t); * . . . * finalS(s); finalS(t); * * Additionally, string by-value parameters must be initialised by calling * initvalparamS(s). * * e.g. * * procedure p(s:String); * begin writelnS(output, concatS(s, concatS(s,s))) * end; * * MUST be written as: * * procedure p(s:String); * begin initvalparamS(s); * writelnS(output, concatS(s, concatS(s,s))) * end; * (This is because the package performs incremental garbage collection * on unassigned strings, but extant by-value references cannot be * detected.) * * * * Implementation Issues: * * The representation is a header record containing a * length field, a reference count, and a packed array [1..slength] * of Char, followed by zero or more `tail' chunks - also * containing a packed array [1..slength] of Char. * The empty string is represented by nil. Beware of * s1 := s2 this copies pointers (!) not the strings themselves. * `:=' between strings should not be used; it cannot be banned * because types inherit assignment in Pascal. * The procedure assignS(dest, source) * should be used to copy strings, it uses the reference count to * avoid copying. Only if updateS is used will the string * actually be copied (if the ref count is > 1). * * All the routines end with a capital S. * * Ian Cottam, University of Manchester, NOV.85. revised MAR.86 and DEC.86. * revised MAR.88 - better names, * plus use of initvalparamS. } { -- string chunk length - any length > 0 will work } const slength = 16; type String = ^ stringrec; Nat0 = 0 .. maxint; Nat1 = 1 .. maxint; stringtail = ^ tailrec; stringrec = record LEN: Nat1; { -- Note: no 0 as nil represents '' } REFS: Nat0; { -- How many refs are there to this string } { -- N.B. only = 0 when string generated by a function } HEAD: packed array [1..slength] of Char; TAIL: stringtail end; tailrec = record MORE: packed array [1..slength] of Char; REST: stringtail end; { -- Result of compare - internal function to ADT } StrCmpResult = (lt, eq, gt); { -- type for sequencing thru strings - internal to ADT at the moment} CharOfString = record POS: 1..slength; case KIND: Boolean of true: (HD: String); false: (TL: stringtail) end; {************ function and procedure headings **************} { -- ... in Alphabetical order ... } procedure assignS(var lhs: String; rhs: String); { * lhs := rhs } external; { ***** AUXILIARY FUNCTION ***** } function compare(left, right:String):StrCmpResult; { * String comparison - used in the impl. of eqS, neS, ltS, etc. } external; function concatS(s1, s2: String):String; { * Returns s1 + s2 * Concatenates s1 and s2. } external; function CtoS(c: Char):String; { * Converts a character into a string of length 1 } external; procedure disposeS(var s: String); { * reclaims the storage associated with the string s } external; function emptyS: String; { * Returns the empty or null string '' } external; function eqS(left,right: String):Boolean; { * left = right } external; procedure finalS(var s: String); { * same as disposeS but possibly better name * reclaims the storage associated with the string s } external; { ***** AUXILIARY FUNCTION ***** } procedure first(var c:CharOfString; var s: String); { * c initialised to point to the first char of s * * precondition * s <> '' } external; function geS(left,right: String):Boolean; { * left >= right } external; function getsubS(s: String; frompos, topos: Nat0):String; { * Returns s[frompos..topos] * Extracts a substring of s. * returns '' if frompos..topos not in range. } external; function gtS(left,right: String):Boolean; { * left > right } external; function indexS(s: String; i: Nat1):Char; { * Returns s[i] * * precondition: * i <= lengthS(s) } external; procedure initS(var s: String); { * Initialises s to be the empty or null string '' * Same as newS, but possibly less confusing name. } external; procedure initvalparamS(var s: String); { * Initialises s, which should be a value parameter, to be * safely useable within the current procedure. } external; function leS(left,right: String):Boolean; { * left <= right } external; function lengthS(s: String):Nat0; { * Returns the dynamic length of a string } external; function ltS(left,right: String):Boolean; { * left < right } external; function matchS(s, pat: String):Nat0; { * Returns position of pat in s or 0 if not present. * Empty strings are not considered present! } external; { ***** AUXILIARY FUNCTION ***** } function mk(var static: packed array [lo..hi:Integer] of Char; limit: Integer):String; { * Converts a static Pascal string into a (dynamic) String. * From lo to limit rather than hi. * This internal procedure may be made generally available * should there be a demand. } external; function mkS(static: packed array [lo..hi:Integer] of Char):String; { * Converts a static Pascal string into a (dynamic) String. } external; procedure mkStaticS(s: String; var p: packed array[lo..hi:Integer] of Char); { * Converts a dynamic string into a static string. * p is null padded if necessary. * Info will be lost if lengthS(s) > hi-lo+1. } external; function neS(left,right: String):Boolean; { * left <> right } external; procedure newS(var s: String); { * Initialises s to be the empty or null string '' } external; { ***** AUXILIARY FUNCTION ***** } procedure next(var c: CharOfString; var ch: Char); { * c is advanced to point to next char in its string and current char * returned in ch * * precondition * c initialised by call to first and not at end of string } external; procedure readS(var f: Text; var s: String); { * Reads a string from text file f; eoln terminating. The input is * left pointing to the beginning of the next line, if any. * * precondition: * f open for reading & not eof(f) } external; procedure readtS(var f: Text; var s: String; function stop(c:Char):Boolean); { * Reads a string from text file f; eoln or stop(c) returning true * (whichever occurs first) terminating. In either case, * input is left positioned at the terminator. * * precondition: * f open for reading & not eof(f) } external; function repS(s: String; n: Nat0):String; { * Returns s * n * Replicates s, n times. } external; procedure updateS(var s: String; i: Nat1; c:Char); { * Updates the string s at position i with the char c. * if i > lengthS(s), s is first space filled upto i-1. } external; procedure writeS(var f: Text; s: String); { * Write the dynamic string s to file f * * precondition: * f open for writing } external; procedure writelnS(var f: Text; s: String); { * Write the dynamic string s to file f followed by an eoln marker * * precondition: * f open for writing } external; SHAR_EOF fi # end of overwriting check if test -f 'CtoS.p' then echo shar: will not over-write existing file "'CtoS.p'" else cat << \SHAR_EOF > 'CtoS.p' # include "strings.h" function CtoS{(c: Char):String}; { * Converts a character into a string of length 1 } var ss: packed array [1 .. 1] of Char; begin ss[1] := c; CtoS := mkS(ss) end{ -- CtoS}; SHAR_EOF fi # end of overwriting check if test -f 'assignS.p' then echo shar: will not over-write existing file "'assignS.p'" else cat << \SHAR_EOF > 'assignS.p' # include "strings.h" procedure assignS{(var lhs: String; rhs: String)}; { * lhs := rhs } begin if lhs <> rhs then begin { -- Care with case, e.g., assignS(x,x) } disposeS(lhs); if rhs = nil then { -- Empty string } lhs := nil else begin lhs := rhs; { -- Ref. copy } with rhs^ do REFS := REFS+1 end end end{ -- assignS}; SHAR_EOF fi # end of overwriting check if test -f 'compare.p' then echo shar: will not over-write existing file "'compare.p'" else cat << \SHAR_EOF > 'compare.p' # include "strings.h" function compare{(left, right: String):StrCmpResult}; var lenl, lenr: Nat0; ltail, rtail: stringtail; state: (GoOn, Less, Greater, Stop); begin lenl := lengthS(left); lenr := lengthS(right); { -- Do trivial cases first } if lenl = 0 then if lenr = 0 then compare := eq else compare := lt else if lenr = 0 then compare := gt else begin { -- Non-trivial cases - both left and right are non empty } ltail := left^.TAIL; rtail := right^.TAIL; if left^.HEAD < right^.HEAD then state := Less else if left^.HEAD > right^.HEAD then state := Greater else if (ltail = nil) or (rtail = nil) then state := Stop else state := GoOn; { -- Check tails if necessary } while state = GoOn do if ltail^.MORE < rtail^.MORE then state := Less else if ltail^.MORE > rtail^.MORE then state := Greater else if (ltail^.REST = nil) or (rtail^.REST = nil) then state := Stop else begin ltail := ltail^.REST; rtail := rtail^.REST end; { -- Final check for differing lengths (etc.) } case state of Less: compare := lt; Greater: compare := gt; Stop: if lenl < lenr then compare := lt else if lenl > lenr then compare := gt else compare := eq end end; { -- comparison may have involved constant strings } if left <> nil then if left^.REFS = 0 then disposeS(left); if right <> nil then if right^.REFS = 0 then disposeS(right) end{ -- compare}; SHAR_EOF fi # end of overwriting check if test -f 'concatS.p' then echo shar: will not over-write existing file "'concatS.p'" else cat << \SHAR_EOF > 'concatS.p' # include "strings.h" function concatS{(s1, s2: String):String}; { * Returns s1 + s2 * Concatenates s1 and s2. } var t: String; { -- Result is built in t } l, r, End1: stringtail; StillInHeadOfT, InTailOfT, InTailOfS2: Boolean; i, j: Nat1; tindx, rindx: 1..slength; null: Char; begin t := nil; null := chr(0); { -- Deal with trivial cases first } if s1 = nil then concatS := s2 else if s2 = nil then concatS := s1 else { -- Both s1 and s2 are non-empty } begin new(t); with t^ do begin LEN := s1^.LEN + s2^.LEN; { -- Copy head of s1 } HEAD := s1^.HEAD; TAIL := nil; { -- Allocate and link in any extra string chunks needed } for i := 1 to (LEN-1) div slength do begin new(l); { -- pad with nulls if chunk is last one } if i=1 then for j:=1 to slength do l^.MORE[j] := null; l^.REST := TAIL; TAIL := l end; { -- Loop through copying string tail of s1, if required } l := TAIL; End1 := TAIL; r := s1^.TAIL; for i := 1 to (s1^.LEN-1) div slength do begin l^.MORE := r^.MORE; End1 := l; l := l^.REST; r := r^.REST end; { -- End1 points to the last tail entry (partially) filled} if s1^.LEN mod slength <> 0 then l := End1; r := s2^.TAIL; { -- Loop thru copying s2 to end of t char by char! } tindx := s1^.LEN mod slength + 1; rindx := 1; StillInHeadOfT := s1^.LEN < slength; InTailOfT := false; InTailOfS2 := false; for i := 1 to s2^.LEN do begin if StillInHeadOfT then begin HEAD[tindx] := s2^.HEAD[rindx]; StillInHeadOfT := tindx < slength end else if i <= slength then begin InTailOfT := true; l^.MORE[tindx] := s2^.HEAD[rindx] end else begin InTailOfS2 := true; l^.MORE[tindx] := r^.MORE[rindx] end; { -- Always inc indices and step down lists if req. } tindx := tindx mod slength + 1; if (tindx = 1) and InTailOfT then l := l^.REST; rindx := rindx mod slength + 1; if (rindx = 1) and InTailOfS2 then r := r^.REST end end{ -- with}; { -- Make 0 ref count } t^.REFS := 0; { -- Tidy up any intermediate storage } if s1 <> nil then if s1^.REFS = 0 then disposeS(s1); if s2 <> nil then if s2^.REFS = 0 then disposeS(s2); concatS := t end end{ -- concatS}; SHAR_EOF fi # end of overwriting check if test -f 'disposeS.p' then echo shar: will not over-write existing file "'disposeS.p'" else cat << \SHAR_EOF > 'disposeS.p' # include "strings.h" procedure disposeS{(var s: String)}; { * reclaims the storage associated with the string s } var t, next: stringtail; begin if s = nil then { -- Do nothing } else if s^.REFS < 2 then begin { -- Only ref. to this string } t := s^.TAIL; dispose(s); s := nil; { -- emptyS } while t <> nil do begin next := t^.REST; dispose(t); t := next end end else begin { -- Decrement the references count, and make s = the empty string } with s^ do REFS := REFS-1; s := nil end end{ -- disposeS}; SHAR_EOF fi # end of overwriting check if test -f 'emptyS.p' then echo shar: will not over-write existing file "'emptyS.p'" else cat << \SHAR_EOF > 'emptyS.p' # include "strings.h" function emptyS{: String}; { * Returns the empty or null string '' } begin emptyS := nil end{ -- emptyS}; SHAR_EOF fi # end of overwriting check if test -f 'eqS.p' then echo shar: will not over-write existing file "'eqS.p'" else cat << \SHAR_EOF > 'eqS.p' # include "strings.h" function eqS{(left,right: String):Boolean}; { * left = right } begin eqS := compare(left, right) = eq end{ -- eqS}; SHAR_EOF fi # end of overwriting check if test -f 'finalS.p' then echo shar: will not over-write existing file "'finalS.p'" else cat << \SHAR_EOF > 'finalS.p' # include "strings.h" procedure finalS{(var s: String)}; { * reclaims the storage associated with the string s } begin disposeS(s) end{ -- finalS}; SHAR_EOF fi # end of overwriting check if test -f 'first.p' then echo shar: will not over-write existing file "'first.p'" else cat << \SHAR_EOF > 'first.p' # include "strings.h" procedure first{(var c:CharOfString; var s: String)}; { * c initialised to point to the first char of s * * precondition * s <> '' } begin with c do begin KIND := true; { -- head record } HD := s; POS := 1 end end{ -- first}; SHAR_EOF fi # end of overwriting check if test -f 'geS.p' then echo shar: will not over-write existing file "'geS.p'" else cat << \SHAR_EOF > 'geS.p' # include "strings.h" function geS{(left,right: String):Boolean}; { * left >= right } begin geS := compare(left, right) <> lt end{ -- geS}; SHAR_EOF fi # end of overwriting check if test -f 'getsubS.p' then echo shar: will not over-write existing file "'getsubS.p'" else cat << \SHAR_EOF > 'getsubS.p' # include "strings.h" function getsubS{(s: String; frompos, topos: Nat0):String}; { * Returns s[frompos..topos] * Extracts a substring of s. * returns '' if frompos..topos not in range. } const BufferLength = 512; var t: String; j,i, stoppos: Nat1; ch: Char; sp: CharOfString; buf: packed array [1..BufferLength] of Char; begin t := nil; { -- empty string } if topos <= lengthS(s) then begin { -- convert max(BufferLength) chars to fixed string } if topos-frompos+1 > BufferLength then stoppos := frompos+BufferLength-1 else stoppos := topos; j := 1; first(sp, s); for i := 1 to frompos-1 do next(sp, ch); for i := frompos to stoppos do begin next(sp, ch); buf[j] := ch; j := j+1 end{ -- for}; { -- convert to String } if j <> 1 then { -- positive slice } t := mk(buf, j-1); { -- check any more left } if topos <> stoppos then t := concatS(t, getsubS(s, stoppos+1, topos)) end; if s <> nil then if s^.REFS = 0 then disposeS(s); getsubS := t end{ -- getsubS}; SHAR_EOF fi # end of overwriting check if test -f 'gtS.p' then echo shar: will not over-write existing file "'gtS.p'" else cat << \SHAR_EOF > 'gtS.p' # include "strings.h" function gtS{(left,right: String):Boolean}; { * left > right } begin gtS := compare(left, right) = gt end{ -- gtS}; SHAR_EOF fi # end of overwriting check if test -f 'indexS.p' then echo shar: will not over-write existing file "'indexS.p'" else cat << \SHAR_EOF > 'indexS.p' # include "strings.h" function indexS{(s: String; i: Nat1):Char}; { * Returns s[i] * * precondition: * i <= lengthS(s) } var j: 2..maxint; chunk: stringtail; begin with s^ do if i <= slength then indexS := HEAD[i] else begin chunk := TAIL; for j := 2 to (i-1) div slength do chunk := chunk^.REST; indexS := chunk^.MORE[ (i-1) mod slength + 1 ] end end{ -- indexS}; SHAR_EOF fi # end of overwriting check if test -f 'initS.p' then echo shar: will not over-write existing file "'initS.p'" else cat << \SHAR_EOF > 'initS.p' # include "strings.h" procedure initS{(var s: String)}; { * Initialises s to be the empty or null string '' * This is a copy of newS for those people that prefer the name initS! } begin s := nil end{ -- initS}; SHAR_EOF fi # end of overwriting check if test -f 'initvalparamS.p' then echo shar: will not over-write existing file "'initvalparamS.p'" else cat << \SHAR_EOF > 'initvalparamS.p' # include "strings.h" procedure initvalparamS{(var s: String)}; { * Initialises s, which should be a value parameter, to be * safely useable within the current procedure. * * increase ref count for a by-value param } begin s^.REFS := s^.REFS + 1 end{ -- initvalparamS}; SHAR_EOF fi # end of overwriting check if test -f 'leS.p' then echo shar: will not over-write existing file "'leS.p'" else cat << \SHAR_EOF > 'leS.p' # include "strings.h" function leS{(left,right: String):Boolean}; { * left <= right } begin leS := compare(left, right) <> gt end{ -- leS}; SHAR_EOF fi # end of overwriting check if test -f 'lengthS.p' then echo shar: will not over-write existing file "'lengthS.p'" else cat << \SHAR_EOF > 'lengthS.p' # include "strings.h" function lengthS{(s: String):Nat0}; { * Returns the dynamic length of a string } begin if s = nil then lengthS := 0 else lengthS := s^.LEN end{ -- lengthS}; SHAR_EOF fi # end of overwriting check if test -f 'ltS.p' then echo shar: will not over-write existing file "'ltS.p'" else cat << \SHAR_EOF > 'ltS.p' # include "strings.h" function ltS{(left,right: String):Boolean}; { * left < right } begin ltS := compare(left, right) = lt end{ -- ltS}; SHAR_EOF fi # end of overwriting check if test -f 'matchS.p' then echo shar: will not over-write existing file "'matchS.p'" else cat << \SHAR_EOF > 'matchS.p' # include "strings.h" function matchS{(s, pat: String):Nat0}; { * Returns position of pat in s or 0 if not present. * Empty strings are not considered present! } var diff, lens, lenp, start, next: Nat0; nomatch: Boolean; begin lens := lengthS(s); lenp := lengthS(pat); if (lens = 0) or (lenp = 0) or (lenp > lens) then matchS := 0 else begin start := 0; diff := lens - lenp; repeat start := start+1; next := 0; repeat next := next+1; nomatch := indexS(pat, next) <> indexS(s, start+next-1) until nomatch or (next = lenp); until not nomatch or (start > diff); if nomatch then matchS := 0 else matchS := start end; { -- possible that function called with constant string for pat } if pat <> nil then if pat^.REFS = 0 then disposeS(pat) end{ -- matchS}; SHAR_EOF fi # end of overwriting check if test -f 'mk.p' then echo shar: will not over-write existing file "'mk.p'" else cat << \SHAR_EOF > 'mk.p' # include "strings.h" function mk{(var static: packed array [lo..hi:Integer] of Char; limit: Integer):String}; { * Converts a static Pascal string into a (dynamic) String. * From lo to limit rather than hi. * This internal procedure may be made generally available * should there be a demand. } var null: Char; StaticLength: Nat1; i, ExtraChunks, CurrentLength: Nat0; StringHead: String; temp: stringtail; k: Integer; j: 1..slength; begin null := chr(0); StaticLength := limit-lo+1; ExtraChunks := (StaticLength-1) div slength; { -- Copy into String head } new(StringHead); with StringHead^ do begin LEN := StaticLength; REFS := 0; TAIL := nil; k := lo; { -- Copy string, null padding if necessary } for j := 1 to slength do if j > StaticLength then HEAD[j] := null else begin HEAD[j] := static[k]; k := k+1 end; { -- Allocate and link in any extra string chunks needed} for i := 1 to ExtraChunks do begin new(temp); temp^.REST := TAIL; TAIL := temp end; { -- Loop through copying string tail if required } temp := TAIL; CurrentLength := 0; while temp <> nil do begin with temp^ do begin CurrentLength := CurrentLength+slength; { -- Copy string, null padding if necessary } for j := 1 to slength do if j+CurrentLength > StaticLength then MORE[j] := null else begin MORE[j] := static[k]; k := k+1 end end; temp := temp^.REST end{ -- while} end{ -- with}; { -- Return the newly created dynamic string } mk := StringHead end{ -- mk}; SHAR_EOF fi # end of overwriting check if test -f 'mkS.p' then echo shar: will not over-write existing file "'mkS.p'" else cat << \SHAR_EOF > 'mkS.p' # include "strings.h" function mkS{(static: packed array[lo..hi:Integer]of Char):String}; { * Converts a static Pascal string into a (dynamic) String. } begin mkS := mk(static, hi) end{ -- mkS}; SHAR_EOF fi # end of overwriting check if test -f 'mkStaticS.p' then echo shar: will not over-write existing file "'mkStaticS.p'" else cat << \SHAR_EOF > 'mkStaticS.p' # include "strings.h" procedure mkStaticS{(s: String; var p: packed array[lo..hi:Integer] of Char)}; { * Converts a dynamic string into a static string. * p is null padded if necessary. * Info will be lost if lengthS(s) > hi-lo+1. } var i: Integer; j: Nat1; lens: Nat0; ch,null: Char; sp: CharOfString; begin j := 1; lens := lengthS(s); null := chr(0); if lens <> 0 then first(sp, s); for i := lo to hi do if j <= lens then begin next(sp, ch); p[i] := ch; j := j+1 end else p[i] := null end{ -- mkStaticS}; SHAR_EOF fi # end of overwriting check if test -f 'neS.p' then echo shar: will not over-write existing file "'neS.p'" else cat << \SHAR_EOF > 'neS.p' # include "strings.h" function neS{(left,right: String):Boolean}; { * left <> right } begin neS := compare(left, right) <> eq end{ -- neS}; SHAR_EOF fi # end of overwriting check if test -f 'newS.p' then echo shar: will not over-write existing file "'newS.p'" else cat << \SHAR_EOF > 'newS.p' # include "strings.h" procedure newS{(var s: String)}; { * Initialises s to be the empty or null string '' } begin s := nil end{ -- newS}; SHAR_EOF fi # end of overwriting check if test -f 'next.p' then echo shar: will not over-write existing file "'next.p'" else cat << \SHAR_EOF > 'next.p' # include "strings.h" procedure next{(var c: CharOfString; var ch: Char)}; { * c is advanced to point to next char in its string and current char * returned in ch * * precondition * c initialised by call to first and not at end of string } var nxtchunk: stringtail; begin with c do case KIND of true: begin { -- header record } ch := HD^.HEAD[POS]; if POS <> slength then POS := POS+1 else begin POS := 1; nxtchunk := HD^.TAIL; { -- change variant } KIND := false; TL := nxtchunk end end; false: begin { -- tail record } ch := TL^.MORE[POS]; if POS <> slength then POS := POS+1 else begin POS := 1; TL := TL^.REST end end end{ -- case} end; SHAR_EOF fi # end of overwriting check if test -f 'readS.p' then echo shar: will not over-write existing file "'readS.p'" else cat << \SHAR_EOF > 'readS.p' # include "strings.h" procedure readS{(var f: Text; var s: String)}; { * Reads a string from text file f; eoln terminating. The input is * left pointing to the beginning of the next line, if any. * * precondition: * f open for reading & not eof(f) } const BufferLength = 120; var t : String; i : Nat0; line : packed array [1..BufferLength] of Char; begin i := 0; while not eoln(f) and (i <> BufferLength) do begin i := i+1; read(f, line[i]) end; if i = 0 then assignS(s, nil) else assignS(s, mk(line, i)); { -- Check for more characters on the input line } if (i = BufferLength) and not eoln(f) then begin { -- Get the rest } t := nil; readS(f, t); assignS(s, concatS(s, t)) end; if eoln(f) then get(f) end{ -- readS}; SHAR_EOF fi # end of overwriting check if test -f 'readtS.p' then echo shar: will not over-write existing file "'readtS.p'" else cat << \SHAR_EOF > 'readtS.p' # include "strings.h" procedure readtS{(var f: Text; var s: String; function stop(c:Char):Boolean)}; { * Reads a string from text file f; eoln or stop(c) returning true * (whichever occurs first) terminating. In either case, * input is left positioned at the terminator. * * precondition: * f open for reading & not eof(f) } const BufferLength = 120; var t : String; i : Nat0; line : packed array [1..BufferLength] of Char; begin i := 0; while not eoln(f) and (i <> BufferLength) and not stop(f^) do begin i := i+1; read(f, line[i]) end; if i = 0 then assignS(s, nil) else assignS(s, mk(line, i)); { -- Check for more characters on the input line } if (i = BufferLength) and not stop(f^) and not eoln(f) then begin { -- Get the rest } t := nil; readS(f, t); assignS(s, concatS(s, t)) end end{ -- readtS}; SHAR_EOF fi # end of overwriting check if test -f 'repS.p' then echo shar: will not over-write existing file "'repS.p'" else cat << \SHAR_EOF > 'repS.p' # include "strings.h" function repS{(s: String; n: Nat0):String}; { * [[ Returns s * n ]] * Replicates s, n times. } var null, ChFromS: Char; lens, StaticLength: Nat0; i, ExtraChunks, CurrentLength: Nat0; StringHead: String; temp: stringtail; k: Integer; j: 1..slength; sp: CharOfString; begin null := chr(0); lens := lengthS(s); StaticLength := lens*n; if StaticLength = 0 then repS := nil { -- emptyS} else begin ExtraChunks := (StaticLength-1) div slength; { -- Copy into String head } new(StringHead); with StringHead^ do begin LEN := StaticLength; REFS := 0; TAIL := nil; first(sp, s); k := 1; { -- Copy string, null padding if necessary } for j := 1 to slength do if j > StaticLength then HEAD[j] := null else begin next(sp, ChFromS); if k = lens then begin k := 1; first(sp, s) end else k := k+1; HEAD[j] := ChFromS end; { -- Allocate and link in any extra string chunks needed} for i := 1 to ExtraChunks do begin new(temp); temp^.REST := TAIL; TAIL := temp end; { -- Loop through copying string tail if required } temp := TAIL; CurrentLength := 0; while temp <> nil do begin with temp^ do begin CurrentLength := CurrentLength+slength; { -- Copy string, null padding if necessary } for j := 1 to slength do if j+CurrentLength > StaticLength then MORE[j] := null else begin next(sp, ChFromS); if k = lens then begin k := 1; first(sp, s) end else k := k+1; MORE[j] := ChFromS end end; temp := temp^.REST end{ -- while}; end{ -- with}; { -- Return the newly created dynamic string } repS := StringHead end; if s <> nil then if s^.REFS = 0 then disposeS(s); end{ -- repS}; SHAR_EOF fi # end of overwriting check if test -f 'updateS.p' then echo shar: will not over-write existing file "'updateS.p'" else cat << \SHAR_EOF > 'updateS.p' # include "strings.h" procedure updateS{(var s: String; i: Nat1; c:Char)}; { * Updates the string s at position i with the char c. * if i > lengthS(s), s is first space filled upto i-1. } var j: 2..maxint; chunk: stringtail; procedure copy(var lhs: String; rhs: String); { * lhs := rhs (forces a string copy) } var ExtraChunks: Nat0; i: Nat1; temp, l, r: stringtail; begin new(lhs); { -- Copy string head } lhs^ := rhs^; with lhs^ do begin REFS := 1; ExtraChunks := (rhs^.LEN-1) div slength; TAIL := nil; { -- Allocate and link in any extra string chunks needed } for i := 1 to ExtraChunks do begin new(temp); temp^.REST := TAIL; TAIL := temp end end; { -- Loop through copying string tail if required } l := lhs^.TAIL; r := rhs^.TAIL; for i := 1 to ExtraChunks do begin l^.MORE := r^.MORE; l := l^.REST; r := r^.REST end end{ -- copy}; begin { -- of updateS } if s <> nil then with s^ do if REFS > 1 then begin { -- Make a unique copy before update } REFS := REFS-1; copy(s, s) { -- N.B. careful (!) use of var and value params. } end; if i <= lengthS(s) then with s^ do if i <= slength then { -- pos is in string head } HEAD[i] := c else begin { -- find tail chunk containing pos. i } chunk := TAIL; for j := 2 to (i-1) div slength do chunk := chunk^.REST; chunk^.MORE[ (i-1) mod slength + 1 ] := c end else { -- Inefficient but rare case } assignS(s, concatS(s,concatS(repS(CtoS(' '),i-lengthS(s)-1),CtoS(c)))) end{ -- updateS}; SHAR_EOF fi # end of overwriting check if test -f 'writeS.p' then echo shar: will not over-write existing file "'writeS.p'" else cat << \SHAR_EOF > 'writeS.p' # include "strings.h" procedure writeS{var f: Text; s: String)}; { * Write the dynamic string s to file f * * precondition: * f open for writing } var temp: stringtail; i, Currentlength: Nat1; ExtraChunks: Nat0; begin if s = nil then { -- Do nothing if string = '' } else begin with s^ do begin ExtraChunks := (LEN-1) div slength; if LEN > slength then CurrentLength := slength else CurrentLength := LEN; write(f, HEAD:CurrentLength); temp := TAIL; { -- Output any tail chunks } for i := 1 to ExtraChunks do with temp^ do if i <> ExtraChunks then begin write(f, MORE); temp := REST end else if LEN mod slength <> 0 then write(f, MORE:(LEN mod slength)) else write(f, MORE) end; { -- may have been asked to output a constant string } if s^.REFS = 0 then disposeS(s) end end{ -- writeS}; SHAR_EOF fi # end of overwriting check if test -f 'writelnS.p' then echo shar: will not over-write existing file "'writelnS.p'" else cat << \SHAR_EOF > 'writelnS.p' # include "strings.h" procedure writelnS{(var f: Text; s: String)}; { * Write the dynamic string s to file f followed by an eoln marker * * precondition: * f open for writing } begin writeS(f, s); writeln(f) end{ -- writelnS}; SHAR_EOF fi # end of overwriting check if test -f 'Makefile' then echo shar: will not over-write existing file "'Makefile'" else cat << \SHAR_EOF > 'Makefile' PFLAGS=-O -L OBJ= initvalparamS.o finalS.o initS.o mk.o mkS.o CtoS.o writeS.o emptyS.o lengthS.o writelnS.o assignS.o repS.o concatS.o disposeS.o readtS.o readS.o indexS.o getsubS.o mkStaticS.o matchS.o updateS.o compare.o eqS.o\ neS.o ltS.o \ first.o next.o gtS.o leS.o geS.o newS.o strings.a: strings.h ${OBJ} ar ruv strings.a ${OBJ} ranlib strings.a ${OBJ}: strings.h SHAR_EOF fi # end of overwriting check # End of shell archive exit 0 ----- End Forwarded Message -----