IMPLEMENTATION MODULE NdxBones; (* * REPERTOIRE * Release 1.6 * By Charles Bradford and Cole Brecheen * (c) Copyright 1985-1992 PMI * Green Bay, Wisconsin * All rights reserved * (414) 468-6040 * * $Header: D:/logfiles/mods/ndxbones.mov 1.7 10 Mar 1991 15:30:02 coleb $ * *) (*EntryDiag: IMPORT Diagnostics; :EntryDiag*) IMPORT ErrorNames; IMPORT HandleIO; IMPORT GenLists; IMPORT ListUtils; IMPORT LowLevel; IMPORT M2Strings; IMPORT NdxTypes; IMPORT Numbers; IMPORT NumTypes; IMPORT PosUtils; IMPORT StrEdit; IMPORT StringIO; IMPORT SYSTEM; IMPORT VStorage; VAR Initialized : BOOLEAN; CONST StartChar = 'B'; EndChar = 'E'; EncodedChar = 'R'; NdxMarkChar = 'X'; (*This character marks the start of the ndx list. We use it to make sure we've got the right file offset when we back up and read the list.*) blank = ' '; null = ''; AfterLastElmt = 65535; PROCEDURE BiggestRecord( NdxFile: NdxTypes.NdxFileType ): CARDINAL; VAR TmpElmt: NdxTypes.NdxElement; TypeCode, LstLngth, TmpResult: CARDINAL; BEGIN IF GenLists.ListLength( NdxFile^.Ndx ) = 0 THEN RETURN 0; END; GenLists.GetElmt( NdxFile^.Ndx, 1, TmpElmt, TypeCode ); TmpResult := TmpElmt.UsedChars; LstLngth := GenLists.ListLength( NdxFile^.Ndx ); WHILE GenLists.ElmtNow( NdxFile^.Ndx ) < LstLngth DO GenLists.NextElmt( NdxFile^.Ndx, 1, TmpElmt, TypeCode ); IF TmpElmt.UsedChars > TmpResult THEN TmpResult := TmpElmt.UsedChars; END; END; RETURN TmpResult; END BiggestRecord; PROCEDURE FindRecord( NdxFile: NdxTypes.NdxFileType; VAR RecordName : ARRAY OF CHAR; VAR offset : LONGINT; VAR UsedRecSize, AllocSize: CARDINAL) : BOOLEAN; (* Move in the record list to this record. If can't find record, AllocSize is set to the collating position where record requested would be inserted. All other VAR results are meaningless *) VAR found, StillLooking: BOOLEAN; HiBound, LoBound, distance, ListPos, TypeCode: CARDINAL; CompResult: INTEGER; TmpElmt : NdxTypes.NdxElement; PROCEDURE AtCurrentPtr( VAR InsertionPoint: CARDINAL ): BOOLEAN; (*Because lots of routines call FindRecord, sometimes in sequence when they're all looking for the same record, it's usually a good bet that the record you want is either at the current pointer for the ndx list or at least needs to be inserted there. This routine determines whether that is true, and if it is, allows us to skip the binary search through the whole list.*) VAR CompResult: INTEGER; now: CARDINAL; BEGIN InsertionPoint := 0; (*If AtCurrentPtr comes back FALSE but with a non-zero value in its parameter, it means we didn't find the record but we know its insertion point.*) now := GenLists.ElmtNow(NdxFile^.Ndx); IF (now = 0) OR (now > GenLists.ListLength(NdxFile^.Ndx)) THEN (* list has not been used at all yet (or is empty) *) RETURN FALSE; END; GenLists.GetElmt( NdxFile^.Ndx, now, TmpElmt, TypeCode ); CompResult := CompareProc(RecordName, TmpElmt.RecName); CASE CompResult OF 0 : M2Strings.Assign( TmpElmt.RecName, RecordName ); RETURN TRUE; | 1 : IF GenLists.ElmtNow( NdxFile^.Ndx ) = 1 THEN (*We were at the first of the list and RecordName was less than TmpElmt.RecName, so we know the insertion point.*) InsertionPoint := 1; RETURN FALSE; END; GenLists.NextElmt( NdxFile^.Ndx, -1, TmpElmt, TypeCode ); CompResult := CompareProc( RecordName, TmpElmt.RecName ); IF CompResult = 0 THEN M2Strings.Assign( TmpElmt.RecName, RecordName ); RETURN TRUE; ELSIF CompResult = -1 THEN (*We know the insertion point is between this element and the last one we tried.*) InsertionPoint := GenLists.ElmtNow(NdxFile^.Ndx) + 1; END; RETURN FALSE; |-1 : IF now = GenLists.ListLength(NdxFile^.Ndx) THEN (*We were at the end of the list and RecordName was greater than TmpElmt.RecName, so we know the insertion point.*) InsertionPoint := now + 1; RETURN FALSE; END; GenLists.NextElmt( NdxFile^.Ndx, 1, TmpElmt, TypeCode ); CompResult := CompareProc( RecordName, TmpElmt.RecName ); IF CompResult = 0 THEN M2Strings.Assign( TmpElmt.RecName, RecordName ); RETURN TRUE; ELSIF CompResult = 1 THEN (*We know the insertion point is between this element and the last one we tried.*) InsertionPoint := GenLists.ElmtNow(NdxFile^.Ndx); END; RETURN FALSE; END; RETURN FALSE; END AtCurrentPtr; BEGIN IF AtCurrentPtr( AllocSize ) THEN found := TRUE; ELSE IF AllocSize # 0 THEN RETURN FALSE; END; found := FALSE; offset := Numbers.Lc( 0); UsedRecSize := 0; ListPos := 1; HiBound := GenLists.ListLength(NdxFile^.Ndx) + 1; IF HiBound = 1 THEN AllocSize := 1; RETURN FALSE; END; LoBound := 0; StillLooking := TRUE; GenLists.GetElmt( NdxFile^.Ndx, 1, TmpElmt, TypeCode ); END; WHILE (NOT found) AND StillLooking DO CompResult := CompareProc(RecordName, TmpElmt.RecName); IF CompResult = 0 THEN M2Strings.Assign( TmpElmt.RecName, RecordName ); found := TRUE; ELSIF CompResult < 0 THEN (* new name is higher than current spot in list; so try to move higher in list *) distance := (HiBound - ListPos) DIV 2; LoBound := ListPos; INC( ListPos, distance ); IF distance = 0 THEN StillLooking := FALSE; AllocSize := ListPos + 1; ELSE GenLists.NextElmt(NdxFile^.Ndx, distance, TmpElmt, TypeCode); END; ELSE distance := (ListPos - LoBound) DIV 2; HiBound := ListPos; DEC( ListPos, distance ); IF distance = 0 THEN StillLooking := FALSE; AllocSize := ListPos; ELSE GenLists.NextElmt(NdxFile^.Ndx, -INTEGER(distance), TmpElmt, TypeCode); END; END; END; IF NOT found THEN (*If we can't find RecordName in the list ... *) RETURN FALSE; ELSE (*Return file location of record.*) offset := TmpElmt.FilePos; UsedRecSize := TmpElmt.UsedChars; AllocSize := TmpElmt.Allocated; RETURN TRUE; END; END FindRecord; PROCEDURE RemoveCodes( Source: SYSTEM.ADDRESS; VAR TheSize: CARDINAL); (* Removes all chars that follow CodeChars, and changes TheSize to reflect number of chars removed. *) VAR tmpsize, toskip : CARDINAL; badspot : SYSTEM.ADDRESS; tmpchar : CHAR; BEGIN tmpsize := TheSize; tmpchar := CodeChar; LOOP toskip := PosUtils.PatternScan( SYSTEM.ADR(tmpchar), 1, Source, tmpsize ); IF toskip >= tmpsize THEN RETURN END; badspot := LowLevel.AddAddr( Source, toskip + 1 ); DEC( tmpsize, toskip + 1); LowLevel.ShiftArrayLeft( badspot, tmpsize, 1 ); Source := badspot; DEC( TheSize); END; END RemoveCodes; PROCEDURE StripList( VAR TheList: GenLists.GenList); (* Converts CodedStrs to CodeChars in TheList *) VAR OldSize, NewSize, ElmntType, cnt, leng: CARDINAL; SubList: GenLists.GenList; GenAddr: SYSTEM.ADDRESS; BEGIN leng := GenLists.ListLength( TheList ); FOR cnt := 1 TO leng DO GenLists.GetElmtAdr( TheList, cnt, GenAddr, OldSize, ElmntType); NewSize := OldSize; IF ElmntType # GenLists.ListCode THEN RemoveCodes( GenAddr, NewSize ); IF OldSize # NewSize THEN (* do not replace unless code chars found *) GenLists.ListReplaceAdr( GenAddr, NewSize, ElmntType, TheList, cnt); END; ELSE (* this element is a list *) GenLists.GetChildList( TheList, cnt, SubList); StripList( SubList ); (* strip this sub list *) END; END; END StripList; PROCEDURE ReadStructure( NdxFile: NdxTypes.NdxFileType; VAR HadToRebuild: BOOLEAN): BOOLEAN; (*Note that this is the first procedure that will have an opportunity to determine whether the user is trying to open a file that is not a valid NdxFile. It has to be very careful about relying on values it gets from the file.*) VAR tmpstr: ARRAY [0..80] OF CHAR; checkchar: CHAR; bufsize, dumcard: CARDINAL; ReadBuf : SYSTEM.ADDRESS; TmpHandle: VStorage.MemHandle; BEGIN (*ReadStructure*) HadToRebuild := FALSE; bufsize := 0; HandleIO.SetFilePtr( NdxFile^.handle, HandleIO.FromStart, NumTypes.L0 ); CASE HandleIO.BlockRead( NdxFile^.handle, SYSTEM.ADR(bufsize), 2) OF (*Read the file's first two bytes into bufsize.*) StringIO.NoError: (*fall through*); ELSE RETURN FALSE; END; HandleIO.SetFilePtr( NdxFile^.handle, HandleIO.FromStart, Numbers.Lc( 2)); (*Put the pointer on the third byte in the file.*) IF NOT VStorage.IsAvailable( bufsize ) THEN ErrorNames.WarningName('Mem'); RETURN FALSE; END; IF NOT VStorage.AllocMem( TmpHandle, bufsize ) THEN ErrorNames.WarningName('Mem'); RETURN FALSE; END; ReadBuf := VStorage.LockMem( TmpHandle ); CASE HandleIO.BlockRead( NdxFile^.handle, ReadBuf, bufsize) OF (*Read the structure record into the ReadBuf.*) StringIO.NoError, StringIO.EndOfFile: (*fall through*); ELSE VStorage.DeallocMem( TmpHandle, bufsize); RETURN FALSE; END; (* Diagnostics.diagBlock( NdxFile^.name, ReadBuf, bufsize ); *) LowLevel.Fill( ReadBuf, 4, 0C ); LowLevel.Fill( LowLevel.AddAddr(ReadBuf, bufsize - 2), 2, 0C ); (*Wipe out the startsep and endsep that brackets the whole structure.*) VStorage.UnLockMem( TmpHandle ); GenLists.NewList( NdxFile^.StructLst ); ListUtils.HandleToList( TmpHandle, bufsize, StartSep, EndSep, 0, 0, NdxFile^.StructLst); IF GenLists.ListLength( NdxFile^.StructLst ) = 0 THEN GenLists.DisposeList( NdxFile^.StructLst); RETURN FALSE; END; GenLists.GetElmt( NdxFile^.StructLst, 1, tmpstr, dumcard); checkchar := tmpstr[0]; M2Strings.Delete(tmpstr, 0, 1); IF NOT PosUtils.Equal( tmpstr, StructName) THEN GenLists.DisposeList( NdxFile^.StructLst); RETURN FALSE; ELSIF checkchar = IsGarbageByte THEN (* Diagnostics.diagS( 'STRUCTURE record is marked garbage', checkchar ); *) RebuildProc( NdxFile ); (*Structure record gets marked as garbage when you write a record but don't update the ndx. Gets marked okay when the ndx gets written.*) HadToRebuild := TRUE; END; GenLists.ListDelete( NdxFile^.StructLst, 1, 1); (* deletes the StructName from the Structure *) StripList( NdxFile^.StructLst ); (*Removes CodedStrs from StructLst.*) RETURN TRUE; END ReadStructure; PROCEDURE MakeEmptyBuf( VAR StruList, BufList: GenLists.GenList); VAR LastCode, TheType, cnt, StructLstLength : CARDINAL; SubStruList, EmptyList: GenLists.GenList; BEGIN StructLstLength := GenLists.ListLength( StruList); LastCode := GenLists.ListCode; FOR cnt := 1 TO StructLstLength DO (* write a ListBuf element for each structure element *) TheType := ListUtils.TypeCheck( StruList, cnt ); (* get next element from structure list *) IF TheType = GenLists.ListCode THEN IF LastCode = GenLists.ListCode THEN ErrorNames.WarningName( 'BadStruc' ); END; GenLists.NewList( EmptyList); GenLists.ListInsert( EmptyList, GenLists.ListCode, BufList, AfterLastElmt); GenLists.GetChildList( StruList, cnt, SubStruList); MakeEmptyBuf( SubStruList, EmptyList); ELSE IF LastCode # GenLists.ListCode THEN GenLists.ListInsert( 0C, GenLists.StrCode, BufList, AfterLastElmt); END; END; LastCode := TheType; END; IF LastCode # GenLists.ListCode THEN GenLists.ListInsert( 0C, GenLists.StrCode, BufList, AfterLastElmt); END; END MakeEmptyBuf; PROCEDURE OpenNdxFile( VAR NdxFile: NdxTypes.NdxFileType; FileName: ARRAY OF CHAR): BOOLEAN; (*Uses HandleIO FindFile routine to open NdxFile. Returns FALSE if not found. Otherwise initializes NdxFile and creates linked list of record names, record sizes, and file offsets from the ndx stored at the end of NdxFile.*) PROCEDURE ReadNdx( NdxFile: NdxTypes.NdxFileType ); VAR TmpAvail, len1, len2, FilePos, BytesToRead : LONGINT; CheckStr: ARRAY [0..1] OF CHAR; NdxAddr : SYSTEM.ADDRESS; TmpHandle : VStorage.MemHandle; TmpNdx: GenLists.GenList; ThisChunk: CARDINAL; BEGIN (*ReadNdx*) HandleIO.SetFilePtr(NdxFile^.handle, HandleIO.FromEnd, Numbers.Li( -4) ); (*Goto end of file and read Ndx offset*) IF StringIO.NoError <> HandleIO.BlockRead(NdxFile^.handle, SYSTEM.ADR(FilePos), 4) THEN ErrorNames.WarningName( 'FRead' ); RETURN; END; IF (FilePos < NumTypes.L0) OR ( FilePos > HandleIO.FileLength(NdxFile^.handle)) THEN (* Diagnostics.diagL( 'FilePos', FilePos ); *) RebuildProc( NdxFile ); RETURN; END; HandleIO.SetFilePtr(NdxFile^.handle, HandleIO.FromStart, FilePos); (*Now we should be sitting on the NdxMarker.*) NdxFile^.NdxFilePtr := FilePos; (*Set file pointer to start of the index.*) len1 := HandleIO.FileLength(NdxFile^.handle) - FilePos; (* len1 is the file size minus the data *) BytesToRead := len1 - Numbers.Lc( 6); (* the index size *) IF BytesToRead = NumTypes.L0 THEN (* return empty index if index area of file is empty *) GenLists.NewList( NdxFile^.Ndx ); RETURN; END; (*Now we check to make sure our offset numbers are valid by looking for the NdxMarker.*) IF HandleIO.BlockRead(NdxFile^.handle, SYSTEM.ADR(CheckStr), 2) # StringIO.NoError THEN (* Diagnostics.diagL( 'BlockRead failure at', FilePos ); *) RebuildProc( NdxFile ); RETURN; END; IF NOT PosUtils.Equal( CheckStr, NdxMarker) THEN (* Diagnostics.diagS( 'Invalid CheckStr:', CheckStr ); *) RebuildProc( NdxFile ); RETURN; END; IF (BytesToRead MOD Numbers.Lc( SYSTEM.TSIZE(NdxTypes.NdxElement))) # NumTypes.L0 THEN (*If the size of the index does not divide evenly by the size of an NdxElement, then the size of an NdxElement has been changed since the file was last used, and we have to rebuild.*) (* Diagnostics.diagL( 'BytesToRead', BytesToRead ); *) RebuildProc( NdxFile ); RETURN; END; (* Next, read in the index *) GenLists.NewList( NdxFile^.Ndx ); WHILE BytesToRead > NumTypes.L0 DO TmpAvail := VStorage.AvailMem(); (*If EMS is installed, more than 64K may be available, so we do this in two steps.*) IF TmpAvail > NumTypes.L65535 THEN ThisChunk := 65535; ELSE ThisChunk := Numbers.C( TmpAvail ); END; DEC( ThisChunk, ThisChunk MOD SYSTEM.TSIZE(NdxTypes.NdxElement) ); IF ThisChunk < SYSTEM.TSIZE(NdxTypes.NdxElement) THEN ErrorNames.WarningName('Mem'); RETURN; END; IF BytesToRead > Numbers.Lc( ThisChunk) THEN BytesToRead := BytesToRead - Numbers.Lc( ThisChunk ); ELSE ThisChunk := Numbers.C( BytesToRead); BytesToRead := NumTypes.L0; END; IF NOT VStorage.AllocMem( TmpHandle, ThisChunk ) THEN ErrorNames.WarningName('Mem'); RETURN; END; NdxAddr := VStorage.LockMem( TmpHandle ); IF HandleIO.BlockRead(NdxFile^.handle, NdxAddr, ThisChunk) # StringIO.NoError THEN VStorage.UnLockMem( TmpHandle ); VStorage.DeallocMem( TmpHandle, ThisChunk ); (* Diagnostics.diagC( 'Unable to read ThisChunk # of bytes:', ThisChunk ); *) RebuildProc( NdxFile ); RETURN; END; VStorage.UnLockMem( TmpHandle ); GenLists.NewList( TmpNdx ); ListUtils.HandleToList( TmpHandle, ThisChunk, '', '', SYSTEM.TSIZE(NdxTypes.NdxElement), NdxTypes.NdxTypeCode, TmpNdx ); (*The nulls mean "there are no delimiters in the block."*) len1 := Numbers.Lc( GenLists.ListLength(TmpNdx)); len2 := Numbers.Lc( GenLists.ListLength(NdxFile^.Ndx)); IF (len1 + len2) > NumTypes.L65535 THEN ErrorNames.WarningName('RecLim'); END; GenLists.JoinLists( TmpNdx, NdxFile^.Ndx, GenLists.ListLength(NdxFile^.Ndx) + 1 ); END; END ReadNdx; VAR NdxRebuilt : BOOLEAN; BEGIN (*OpenNdxFile*) NdxTypes.InitNdxFile( NdxFile ); M2Strings.Assign( FileName, NdxFile^.name ); IF HandleIO.FindFile( NdxFile^.handle, NdxFile^.name, "path" ) # StringIO.NoError THEN (*Find and open the ndx file if it exists.*) NdxFile^.name := ""; VStorage.DosDealloc( NdxFile, SYSTEM.TSIZE(NdxTypes.NdxRecord) ); NdxFile := NIL; (*Some Storage modules don't nil out the pointer after deallocating it, so we do it ourselves.*) RETURN FALSE; END; IF NOT ReadStructure( NdxFile, NdxRebuilt) THEN (*Reads the zero record and fills the StructLst in NdxFile^.*) VStorage.DosDealloc( NdxFile, SYSTEM.TSIZE(NdxTypes.NdxRecord) ); NdxFile := NIL; RETURN FALSE; END; IF NOT NdxRebuilt THEN ReadNdx( NdxFile); (*Load a linked list with the record index for this file.*) END; NdxFile^.FileBufPtr := NIL; GenLists.NewList( NdxFile^.ListBuf ); MakeEmptyBuf( NdxFile^.StructLst, NdxFile^.ListBuf ); RETURN TRUE; END OpenNdxFile; PROCEDURE InitBuffer( VAR NdxFile: NdxTypes.NdxFileType; RecName: ARRAY OF CHAR): BOOLEAN; (* called by PutField and GetField to load the record data into the list buffer. If RecordName is already in BufRecName RETURNs TRUE. If not, checks to see if RecName exists in index. If no, returns FALSE. If OK, retrieves record, then moves read buffer into GenList in ListBuf. IF all OK, returns TRUE.*) VAR NameFound: ARRAY [0..NdxTypes.RecNameLength] OF CHAR; NameAdr : SYSTEM.ADDRESS; NameSize : CARDINAL; checkchar: CHAR; OffSet: LONGINT; UsedSize, AllocatedSize, dumcard: CARDINAL; BEGIN NdxTypes.CheckInit( NdxFile); StrEdit.CrunchBlanks( RecName ); IF PosUtils.Equal(RecName, NdxFile^.BufRecName) THEN RETURN TRUE; ELSIF M2Strings.Length(RecName) = 0 THEN RETURN FALSE; END; IF GenLists.Initialized( NdxFile^.ListBuf ) THEN GenLists.DisposeList( NdxFile^.ListBuf ); END; IF NOT FindRecord( NdxFile, RecName, OffSet, UsedSize, AllocatedSize ) THEN GenLists.NewList( NdxFile^.ListBuf ); MakeEmptyBuf( NdxFile^.StructLst, NdxFile^.ListBuf ); M2Strings.Assign( RecName, NdxFile^.BufRecName ); RETURN FALSE; END; VStorage.DosAlloc( NdxFile^.FileBufPtr, UsedSize ); NdxFile^.BufSizeNow := UsedSize; HandleIO.SetFilePtr(NdxFile^.handle, HandleIO.FromStart, OffSet ); IF NOT (StringIO.NoError = HandleIO.BlockRead( NdxFile^.handle, NdxFile^.FileBufPtr, UsedSize)) THEN VStorage.DosDealloc( NdxFile^.FileBufPtr, UsedSize ); NdxFile^.BufSizeNow := 0; RETURN FALSE; END; LowLevel.Fill( NdxFile^.FileBufPtr, 4 (*SIZE(StartSep) + SIZE(TypeCode)*), 0C ); LowLevel.Fill( LowLevel.AddAddr( NdxFile^.FileBufPtr, UsedSize - 2 ), 2, 0C ); (*These two fills wipe out the startsep and endsep that begin and end each record.*) M2Strings.Assign( RecName, NdxFile^.BufRecName ); (* put current record name in BufRecName *) GenLists.NewList( NdxFile^.ListBuf ); GenLists.BlockToList( NdxFile^.FileBufPtr, NdxFile^.BufSizeNow, StartSep, EndSep, 0, 0, NdxFile^.ListBuf); IF GenLists.ErrorFlag # GenLists.NoListError THEN RETURN FALSE; END; IF GenLists.ListLength( NdxFile^.ListBuf ) > 0 THEN StripList( NdxFile^.ListBuf ); GenLists.GetElmtAdr( NdxFile^.ListBuf, 1, NameAdr, NameSize, dumcard ); (* We want to store the record name in NameFound, but first we have to check it so that we can warn intelligently in case of file damage. *) NameSize := LowLevel.ScanEQ( NameSize, 0C, NameAdr ); (* Reduce NameSize to length of string before the null. *) IF NameSize <= NdxTypes.RecNameLength THEN LowLevel.Move( NameAdr, SYSTEM.ADR(NameFound), NameSize + 1 ); (* It's NameSize + 1 because we have to make sure the trailing null gets included in the move. *) ELSE ErrorNames.WarningName( 'RecDam' ); END; GenLists.ListDelete( NdxFile^.ListBuf, 1, 1 ); (*Delete the record name from the ListBuf.*) ELSE MakeEmptyBuf( NdxFile^.StructLst, NdxFile^.ListBuf ); RETURN TRUE; (*Actually something has probably gone wrong here--a record has apparently been written to the file that doesn't even have the record-bracketing StartSep and EndSep. But maybe we're about to correct it, so we return TRUE.*) END; checkchar := NameFound[0]; M2Strings.Delete(NameFound, 0, 1); RETURN PosUtils.Equal( NameFound, RecName ) (* RecName stored in record read *) AND (checkchar # IsGarbageByte); (* Successful closing of file last time it was used *) END InitBuffer; PROCEDURE Retrieve( NdxFile: NdxTypes.NdxFileType; RecordName: ARRAY OF CHAR; BufAddr: SYSTEM.ADDRESS; VAR RecSize: CARDINAL): BOOLEAN; (* Reads record named into file buffer. Returns TRUE if found, FALSE if not. *) VAR offset: LONGINT; ListSpot: CARDINAL; BEGIN NdxTypes.CheckInit( NdxFile ); IF NOT FindRecord( NdxFile, RecordName, offset, RecSize, ListSpot ) THEN (* if RecordName not in index list, return FALSE *) RETURN FALSE; END; HandleIO.SetFilePtr(NdxFile^.handle, HandleIO.FromStart, offset ); RETURN StringIO.NoError = HandleIO.BlockRead( NdxFile^.handle, BufAddr, RecSize); END Retrieve; PROCEDURE NoRebuild( VAR NdxFile: NdxTypes.NdxFileType ); BEGIN ErrorNames.WarningName( 'NdxDam' ); END NoRebuild; PROCEDURE Init(); BEGIN IF Initialized THEN RETURN; ELSE Initialized := TRUE; END; (*EntryDiag: Diagnostics.Init(); :EntryDiag*) ErrorNames.Init(); HandleIO.Init(); GenLists.Init(); ListUtils.Init(); LowLevel.Init(); M2Strings.Init(); NdxTypes.Init(); Numbers.Init(); NumTypes.Init(); PosUtils.Init(); StrEdit.Init(); StringIO.Init(); VStorage.Init(); (*EntryDiag: Diagnostics.diagS( 'Entering NdxBones', '' ); :EntryDiag*) RebuildProc := NoRebuild; CompareProc := M2Strings.CompareStr; StartSep[0] := CodeChar; StartSep[1] := StartChar; EndSep[0] := CodeChar; EndSep[1] := EndChar; CodedStr[0] := CodeChar; CodedStr[1] := EncodedChar; NdxMarker[0] := CodeChar; NdxMarker[1] := NdxMarkChar; StrEdit.AssignStr( "STRUCTURE", StructName); (*EntryDiag: Diagnostics.diagS( 'Exiting NdxBones', '' ); :EntryDiag*) END Init; BEGIN Initialized := FALSE; Init(); END NdxBones.