Trailing-Edge
-
PDP-10 Archives
-
decuslib20-03
-
decus/20-0078/libsim/store.sim
There is 1 other file named store.sim in the archive. Click here to see a list.
OPTIONS(/E);
COMMENT this program was originally written by Jacob Palme,
Swedish National Defense Research Institute, Stockholm, Sweden,
in 1975;
COMMENT this file was made by Mike Soul, Institut fuehr Informatik
Hamburg, Western Germany, Oct 1976.
This file has been altered from the original STORE as supplied as follows:-
Identifier changes: "recently_used" to "last_use_of"
"length_of_new_storeunit" to "nextlength"
"keylength_of_newstoreunit" to "nextkeylength"
"storebase" deleted (not used)
"lastvirtual" "
layout and logic tidied up in procedures:-
direct (side effect on global storeimage removed)
storefile_inimage
open
close
putmessage
getmessage
putinstore
lookup (completely rewritten)
procedures deleted or transferred out of class STORE:-
upcase (transferred)
putsafe
getsafe
sixlettersordigits
letterordigit (transferred)
My layout conventions are:-
1) indent steps of 4 spaces,
2) in general the key words IF, ELSE, FOR, WHILE introduce indented
blocks, BEGINs are ignored. ELSE has an extra indent of 1 space over
the corresponding initial IF. END is aligned with the key word starting
the block (IF, FOR, WHILE) or ignored with ELSE.
;
comment ***** MACHINE DEPENDENCIES *****
This program assumes that
i) a disk file block holds 640 characters, and therefore 638
significant characters plus cr-lf. The first 8 characters are
used to store tree pointers, leaving 630 for messages. A
computer with a different block size may well require all these
constants to be changed.
ii) In lookup there is a text value assignment between overlapping
texts, whose result is undefined. If the instruction is compiled
as copying from the front of the source text it will work.
;
comment variables and their use:
INTEGER virtual_size = size of virtual_memory in core blocks of 128 words, parameter of Store
INTEGER initial_memory = start size of direct_access file in core blocks,
should not be too small or runs may become costly, parameter of Store
TEXT ARRAY virtual_memory = each element points to a disk block kept
in core to minimise disk accesses.
INTEGER ARRAY virtual_location = disk address (location) of block in
corresponding virtual_memory.
INTEGER ARRAY last_use_of = no of last access of this disk block, used
to remove least recently used from virtual memory when new block
is to be entered there.
TEXT oldfilename = previous file name when opening and closing several times.
TEXT storeimage = part of input image from DA file not yet scanned.
INTEGER hashvalue = computed from key by procedure lookup.
INTEGER location = disk address of block to be written or read to disk.
INTEGER keylength = length of key, computed in Putmessage, Getmessage and Delete.
INTEGER nextlength = length of storeunit being scanned while reading
disk DA file.
INTEGER placelocation = location (disk address) of disk block where
there is space enough to start to insert new message.
INTEGER placelocval = locval for the placelocation location.
INTEGER locval = hashvalue divided to get left-right decision in binary
tree on disk.
INTEGER freeline = last written disk block in DA file plus 1.
INTEGER newlocation = next location in binary tree on disk.
INTEGER iocount = index number of virtual disk accesses.
INTEGER least_recently_used = index of least recently used virtual memory block.
BOOLEAN longmessage = true if a message too long to fit into one disk block
is being stored.
BOOLEAN longmessfound = true when retrieving a message longer than
one disk block.
BOOLEAN emptyline = nothing was written in last block read from disk.
BOOLEAN remove = if true previous messages with same key are removed
to put in new messages.
BOOLEAN removing = in the act of such removing.
BOOLEAN getting = in the act of getting message through getmessage.
BOOLEAN left = go to left in binary tree on disk.
end of variable name comment;
CLASS store(virtual_size, initial_memory);
INTEGER virtual_size, initial_memory;
BEGIN
REF(Directfile) storefile;
TEXT ARRAY virtual_memory[0:virtual_size];
INTEGER ARRAY virtual_location[0:virtual_size], last_use_of[0:virtual_size];
TEXT oldfilename, storeimage;
INTEGER hashvalue, location, keylength, nextlength, placelocation,
placelocval, locval, freeline, newlocation, iocount, least_recently_used;
BOOLEAN longmessage, longmessfound, emptyline, remove, removing,
getting, left;
CHARACTER altmode, carriagereturn, tab;
PROCEDURE direct(keyprocessor);
COMMENT to list all keys in the direct access file and apply the
procedure keyprocessor to each key. Keys are listed through linear
search of DA file. If multi-block long message, key is listed when the
last block with message is read. *** WARNING keyprocessor must not
change the values of global variables used by direct i.e. location
(for example with keyprocessor = getmessage);
PROCEDURE keyprocessor;
BEGIN TEXT key, storeimage;
INTEGER nextkeylength, nextlength;
Location:= 1; storefile.Locate(Location); !first block;
WHILE NOT storefile.Endfile DO BEGIN
storefile_inimage; !get a block;
IF NOT emptyline THEN BEGIN
storeimage:- Copy(storefile.Image.Sub(9,630)); !message part of block;
WHILE (IF storeimage.Length <= 8 THEN FALSE
ELSE storeimage.Sub(1,5) NE " ") DO BEGIN
nextkeylength:= storeimage.Sub(6,3).Getint; !get lengths of unit;
nextlength:= storeimage.Sub(1,5).Getint;
IF nextkeylength > 0
AND storeimage.Length >= nextlength THEN BEGIN !if unit doesn't overflow block;
key:- storeimage.Sub(9,nextkeylength);
keyprocessor(key,Location)
END;
IF storeimage.Length > nextlength + 8 THEN !if room for another unit;
storeimage:- storeimage.Sub(nextlength+1,storeimage.Length-nextlength) !move pointer;
ELSE storeimage:- NOTEXT; !otherwise exit;
END
END;
Location:= Location+1;
END;
END of direct;
PROCEDURE storefile_inimage;
COMMENT will input the (location) disk block from disk. If however,
the image is in the virtual_memory, it is input from this and not
from disk. When a disk block is not in the virtual_memory,
a least_recently_used block in the virtual memory is first output
on disk to get space in the virtual memory for the new input block.
Note that storefile_image and virtual_memory[n] are both references
to the same object (Blanks(638)) and side-effect each other;
BEGIN INTEGER virtloop;
iocount:= iocount+1;
storefile.Locate(Location);
FOR virtloop:= 0 STEP 1 UNTIL virtual_size DO
IF Location = virtual_location[virtloop] THEN BEGIN
storefile.Image:- virtual_memory[virtloop];
last_use_of[virtloop]:= iocount;
GOTO located;
END;
COMMENT not in virtual memory, so get from file;
least_recently_used:= 0;
FOR virtloop:= 1 STEP 1 UNTIL virtual_size DO
IF last_use_of[virtloop] < last_use_of[least_recently_used] THEN
least_recently_used:= virtloop;
last_use_of[least_recently_used]:= iocount;
storefile.Image:- virtual_memory[least_recently_used];
IF virtual_location[least_recently_used] > 0 AND
storefile.Image.Sub(1,2) NE "/*" THEN BEGIN
COMMENT something stored in this block;
storefile.Locate(virtual_location[least_recently_used]);
storefile.Outimage;
storefile.Locate(Location)
END;
IF storefile.Endfile THEN storefile.Image:= "/*"
ELSE storefile.Inimage;
virtual_location[least_recently_used]:= Location;
located:
emptyline:= storefile.Image.Sub(1,2) = "/*";
END of storefile_inimage;
PROCEDURE Open(filename);
COMMENT will open the direct access file. The size of the hash table
(initial_store_size) is read from the file if the file is old. The size
of the file (freeline) is also read from the file, but this is checked
to see that there really is nothing more in the file past freeline.
This data is found in a special empty message with no key always in
block 1 of the DA file;
VALUE filename; TEXT filename;
BEGIN TEXT extendedfilename; INTEGER virtloop;
extendedfilename:- Blanks(filename.Length+4);
extendedfilename:= filename;
extendedfilename.Sub(filename.Length+1,4):= ".DAF";
IF filename NE oldfilename THEN BEGIN
storefile:- NEW Directfile(extendedfilename);
oldfilename:- Copy(filename);
IF oldfilename NE NOTEXT THEN
FOR virtloop:= 0 STEP 1 UNTIL virtual_size DO BEGIN
virtual_memory[virtloop]:- Blanks(638);
last_use_of[virtloop] := virtual_location[virtloop] := 0
END
END;
storefile.Open(virtual_memory[0]);
Location:= 1; storefile_inimage;
IF emptyline THEN BEGIN COMMENT new file;
IF initial_memory < 1 THEN initial_memory:= 1
ELSE IF initial_memory > 1400 THEN initial_memory:= 1400;
storefile.Outtext(" 0 0 18 0");
storefile.Image.Sub(17,5).Putint(initial_memory);
freeline:= initial_memory+1;
storefile.Image.Sub(22,5).Putint(freeline)
END
ELSE BEGIN COMMENT old file;
initial_memory:= storefile.Image.Sub(17,5).Getint;
freeline:= storefile.Image.Sub(22,5).Getint;
Location:= freeline;
storefile_inimage;
WHILE NOT storefile.Endfile DO BEGIN
Location:= Location+1; storefile_inimage;
IF NOT emptyline THEN freeline:= Location+1
END;
Location:= 1; storefile_inimage;
storefile.Image.Sub(22,5).Putint(freeline)
END of old file
END of open;
PROCEDURE Close;
COMMENT the DA file is closed. Important is to output all virtual
storage disk blocks, since new info in them would otherwise be lost;
BEGIN INTEGER virtloop;
Location:= 1; storefile_inimage;
storefile.Image.Sub(22,5).Putint(freeline);
COMMENT if the above action is not done because the file is closed
by the operating system, nothing will go wrong and the next open
will scan for the true freeline;
FOR virtloop:= 0 STEP 1 UNTIL virtual_size DO
IF virtual_location[virtloop] > 0
AND virtual_memory[virtloop].Sub(1,2) NE "/*" THEN BEGIN
storefile.Locate(virtual_location[virtloop]);
storefile.Image:- virtual_memory[virtloop];
storefile.Outimage
END;
storefile.Close
END;
PROCEDURE delete(key);
TEXT key;
BEGIN TEXT empty;
COMMENT deletes a key and associated message from the directfile;
removing := TRUE; getting := FALSE;
placelocation := 0;
keylength := key.Length;
lookup(key,empty)
END of delete;
BOOLEAN PROCEDURE putmessage(key,message);
COMMENT the user routine to put messages into the DA file;
TEXT key, message;
BEGIN TEXT storeunit;
removing:= remove; getting:= FALSE; keylength:= key.Length;
putmessage:= TRUE;
storeunit:- Blanks(keylength+message.Length+8);
IF storeunit.Length > 99999 OR keylength > 400 OR keylength = 0 THEN
putmessage:= FALSE
ELSE BEGIN
longmessage:= storeunit.Length > 630;
storeunit.Sub(1,5).Putint(storeunit.Length);
storeunit.Sub(6,3).Putint(keylength);
storeunit.Sub(9,keylength):= key;
storeunit.Sub(9+keylength,message.Length):= message;
placelocation:= 0;
IF lookup(key, storeunit) AND NOT removing THEN putmessage:= FALSE
ELSE putinstore(storeunit)
END
END of putmessage;
TEXT PROCEDURE getmessage(key);
COMMENT the user routine to get messages from the DA file;
TEXT key;
BEGIN TEXT storeunit;
removing:= FALSE; getting:= TRUE;
keylength:= key.Length;
placelocation:= -1;
IF keylength > 0 AND keylength <= 400 THEN BEGIN
IF lookup(key, storeunit) THEN
getmessage:- Copy(storeunit.Sub(keylength+9,
storeunit.Length-keylength-8))
END
END of getmessage;
PROCEDURE putinstore(storeunit);
COMMENT internal routine to put a whole storeunit, that is lengths plus key plus message, into the da file. MUST first call
LOOKUP to see if the message is already there, and calculate hash value. Finds an empty place to put it in if LOOKUP has not
already found such a place. Long messages are put into several blocks succeeding each other in the binary tree on disk, possibly
with the beginning in the hash table. Note all long messages MUST start at the beginning of a block;
TEXT storeunit;
BEGIN COMMENT find block in table or tree;
IF placelocation <= 0 THEN BEGIN !entered direct from put;
Location:= Mod(hashvalue,initial_memory)+1;
locval:= hashvalue//initial_memory END
ELSE BEGIN
Location:= placelocation; locval:= placelocval !assigned a value by lookup;
END;
try_another_line:
storefile_inimage; storeimage:- storefile.Image.Sub(9,630); !get message part of block;
IF emptyline THEN storefile.Image.Sub(1,8):= " 0 0"
ELSE
WHILE (IF storeimage.Length <= 8 THEN FALSE
ELSE storeimage.Sub(1,5) NE " ") DO BEGIN
nextlength:= storeimage.Sub(1,5).Getint;
storeimage:- IF nextlength >= storeimage.Length-8 THEN NOTEXT !no room for more in this block;
ELSE storeimage.Sub(nextlength+1,
storeimage.Length-nextlength); !step on one more storeunit;
END;
IF storeimage.Length < storeunit.Length THEN BEGIN !no room for unit in this block;
IF longmessage AND storeimage.Length = 630 THEN BEGIN !only split long messages;
storeimage:= storeunit.Sub(1,630); !copy first part;
storeunit.Sub(1,5).Putint(storeunit.Length-630+8+keylength); !change total length;
storeunit.Sub(630-keylength-7,8+keylength):=
storeunit.Sub(1,8+keylength); !overwrite part of message with header;
storeunit:- storeunit.Sub(630-keylength-7,
storeunit.Length-630+keylength+8); !move up the pointer;
END;
left:= Mod(locval,2)=0; locval:= locval//2; !either way have no room in current block;
newlocation:=storefile.Image.Sub((IF left THEN 1 ELSE 5),4).Getint; !find location of next block in tree;
IF newlocation = 0 THEN BEGIN !sprout a branch;
newlocation:= freeline; freeline:= freeline+1;
storefile.Image.Sub((IF left THEN 1 ELSE 5),4).Putint(newlocation);
END;
Location:= newlocation;
GO TO try_another_line; !try inserting the message in the next block;
END
ELSE storeimage.Sub(1,storeunit.Length):= storeunit; !the unit fits in the current block;
END of putinstore;
BOOLEAN PROCEDURE lookup(key, transfer_storeunit);
COMMENT this procedure looks in the direct access data base to find a certain key given as argument. Search order is first
hashing, if not there then binary tree as described in main documentation of this program. This procedure requires keylength to
have been assigned to, and must be used before putinstore to calculate the hashvalue. WARNING this procedure deletes any message
under key when removing is true. Split messages MUST start at the beginning of a block, otherwise a preceding (short) message
could be deleted and the long message would get moved up the block and the end of the block filled with blanks behind it. These
blanks would be taken as part of the message(without changing the length) and so destroy the whole format;
NAME transfer_storeunit;
TEXT key, transfer_storeunit;
BEGIN INTEGER nextkeylength, newpiecelength;
TEXT storeunit, tailstoreunit;
longmessfound:= FALSE;
storeunit:- transfer_storeunit;
key.Setpos(1);
hashvalue:= 1 + 7 * keylength + 11 * Rank(key.Getchar) + ! 1 + 7*length + 11*firstch + lastch + 5*middlech;
Rank(key.Sub(keylength,1).Getchar) +
5 * (IF keylength > 2 THEN Rank(key.Sub(keylength//2+1,1).Getchar) ELSE 0);
Location:= 1 + Mod(hashvalue,initial_memory);
locval:= hashvalue//initial_memory;
storefile_inimage;
storeimage:- storefile.Image.Sub(9,630); !get the relevant block;
WHILE storeimage.Sub(1,5) NE NOTEXT AND NOT emptyline DO BEGIN
WHILE (IF storeimage.Length <= 8 THEN FALSE
ELSE storeimage.Sub(1,5) NE " ") DO BEGIN !whilst still data in this block;
nextlength:= storeimage.Sub(1,5).Getint;
nextkeylength:= storeimage.Sub(6,3).Getint;
IF nextkeylength = keylength THEN BEGIN !key lengths match;
IF storeimage.Sub(9,keylength)=key THEN BEGIN !keys match;
lookup:= TRUE;
IF NOT (getting OR removing) THEN GOTO return_from_lookup; !cannot put if key already there;
IF nextlength <= storeimage.Length THEN BEGIN !message is all in this block;
IF removing THEN BEGIN !overwrite the unit with the rest;
storeimage:= storeimage.Sub(1+nextlength, !***according to handbook this is unpredictable***;
storeimage.Length-nextlength);
nextlength:= 0 !force to continue search past rest of block;
END of removing
ELSE BEGIN !getting short unit or end of long;
IF longmessfound THEN !copy end of message into tailstoreunit;
tailstoreunit:= storeimage.Sub(9+keylength, tailstoreunit.Length)
ELSE storeunit:- storeimage.Sub(1,nextlength); !return short unit;
GOTO return_from_lookup
END of getting
END of non overflow
ELSE BEGIN !unit continues in another block;
IF removing THEN storeimage:= NOTEXT !overwrite with blanks;
ELSE IF longmessfound THEN BEGIN !middle piece of long unit;
newpiecelength:= storeimage.Length-8-keylength;
tailstoreunit.Sub(1,newpiecelength):=
storeimage.Sub(9+keylength,newpiecelength); !copy into tailstoreunit;
tailstoreunit :- tailstoreunit.sub(newpiecelength+1,
tailstoreunit.length-newpiecelength) !move pointer up;
END of middle piece
ELSE BEGIN !start of a long unit;
longmessfound:= TRUE;
storeunit:- Blanks(nextlength); !make text to hold unit;
storeunit.Sub(1,storeimage.Length):= storeimage; !copy in first part;
tailstoreunit :- storeunit.sub(storeimage.Length+1,
nextlength-storeimage.Length) !remember pointer to rest;
END of long message
END of overflow
END of right key
END of right keylength;
IF storeimage.Length > nextlength + 8 THEN !if room for another unit, move up pointer;
storeimage:- storeimage.Sub(nextlength+1,
storeimage.Length-nextlength)
ELSE IF storeimage.sub(1,5) NE " " THEN storeimage:- NOTEXT; !otherwise exit from loop;
!if putting, storeimage is now blanks anyway;
END of loop through storeimage for one location;
IF placelocation = 0 THEN BEGIN !putting or removing and just entered;
IF storeimage.Length >= storeunit.Length OR !message fits in remainder of block;
(longmessage AND storeimage.Length = 630) THEN BEGIN !starting a long message in an empty block;
placelocation:= Location; placelocval:= locval !remember starting block;
END
END;
left:= Mod(locval,2)=0;
newlocation:= storefile.Image.Sub((IF left THEN 1 ELSE 5),4).Getint; !get next block in tree;
IF newlocation = 0 THEN GOTO return_from_lookup; !since there's no branch, can't be more message;
locval:= locval//2;
Location:= newlocation;
storefile_inimage; storeimage:- storefile.Image.Sub(9,630); !get next block and repeat;
END of loop through locations;
return_from_lookup:
IF NOT removing THEN transfer_storeunit:- storeunit;
END of lookup;
carriagereturn:= Char(13); tab:= Char(9); altmode:= Char(27);
END of class store;