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.
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)
	lookup		(completely rewritten)

procedures deleted or transferred out of class STORE:-
	upcase (transferred)
	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;
  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);
		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;
	Location:= Location+1;
  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;
    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;
    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;
    IF storefile.Endfile THEN storefile.Image:= "/*"
     ELSE storefile.Inimage;
    virtual_location[least_recently_used]:= Location;
    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
    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");
	freeline:= initial_memory+1;
     ELSE BEGIN COMMENT old file;
	initial_memory:= storefile.Image.Sub(17,5).Getint;
	freeline:= storefile.Image.Sub(22,5).Getint;
	Location:= freeline;
	WHILE NOT storefile.Endfile DO BEGIN
	    Location:= Location+1; storefile_inimage;
	    IF NOT emptyline THEN freeline:= Location+1
	Location:= 1; storefile_inimage;
    END of old file
  END of open;

  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;
    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.Image:- virtual_memory[virtloop];

  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;
  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
	longmessage:= storeunit.Length > 630;
	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 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,
  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
      Location:= placelocation; locval:= placelocval				!assigned a value by lookup;

    storefile_inimage; storeimage:- storefile.Image.Sub(9,630);			!get message part of block;
    IF emptyline THEN storefile.Image.Sub(1,8):= "   0   0"
	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;
    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(1,8+keylength);				!overwrite part of message with header;
	    storeunit:- storeunit.Sub(630-keylength-7,
			storeunit.Length-630+keylength+8);			!move up the pointer;
	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);
	Location:= newlocation;
	GO TO try_another_line;							!try inserting the message in the next block;
     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;
    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;

    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***;
			    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;
				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,
	     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;
	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;

    IF NOT removing THEN transfer_storeunit:- storeunit;
  END of lookup;

  carriagereturn:= Char(13); tab:= Char(9); altmode:= Char(27);

END of class store;