{$V-} program FileIndex; const IndexMax = 1000; RecCountErr = -2; NewFileCreated = -1; NoError = 0; RecordNotFound = 1; NoMoreRoom = 2; AlreadyExists = 3; OutOfRange = 4; type Keytype = string[40]; FileStr = string[80]; Whatever = string[12]; DataRec = record case Boolean of True : (NumRecs : Integer); False : (Key : Keytype; theRest : Whatever); end; IndexRec = record Key : Keytype; Num : Integer end; IndexList = array[1..IndexMax] of IndexRec; var KList : IndexList; DFile : file of DataRec; MaxRec : Integer; { compiler-specific file I/O routines } { these procedures are specific to TURBO Pascal. If you are using another Pascal compiler, you will need to modify them appropriately. Note that TURBO Pascal does not support the standard routines GET and PUT, but instead uses READ and WRITE. } {$I-} { turn off I/O error checking } procedure FRead(RNum : Integer; var Rec : DataRec; var Error : Integer); { reads record #RNum into Rec } begin if (RNum < 0) or (RNum > MaxRec) then Error := OutOfRange else begin Seek(DFile,RNum); Read(DFile,Rec); Error := IOResult; if Error > 0 then Error := 100 + Error end end; { of proc FRead } procedure FWrite(RNum : Integer; Rec : DataRec; var Error : Integer); { writes record #RNum into Rec } begin if (RNum < 0) or (RNum > MaxRec) then Error := OutOfRange else begin Seek(DFile,RNum); Write(DFile,Rec); Error := IOResult; if Error > 0 then Error := 100 + Error end end; { of proc FRead } procedure FOpen(FileName : FileStr; var Error : Integer); { tries to open FileName; if it doesn't exist, creates it with the appropriate header record } const TurboNoFile = 1; { "no file" error code for TURBO Pascal } NoIOError = 0; var IOCode : Integer; TRec : DataRec; begin Assign(DFile,FileName); Reset(DFile); IOCode := IOResult; if IOCode = TurboNoFile then begin { file doesn't exist } FillChar(TRec,SizeOf(TRec),0); Rewrite(DFile); TRec.NumRecs := 0; Write(DFile,TRec); Close(DFile); Assign(DFile,Filename); Reset(DFile); IOCode := IOResult; if IOCode = NoIOError then Error := NewFileCreated end; if IOCode <> NoIOError then Error := 100 + IOCode; end; { of proc FOpen } procedure FClose(var Error : Integer); { closes file } begin Close(DFile); Error := IOResult; if Error > 0 then Error := Error + 100 end; { of proc FClose } {$I+} { turn on I/O error checking } { initialization and cleanup routines } procedure SortIndexList; { sorts the array KList using a selection sort technique } var I,J,Min : Integer; Temp : IndexRec; begin for I := 1 to MaxRec-1 do begin Min := I; for J := I+1 to MaxRec do if KList[J].Key < KList[Min].Key then Min := J; Temp := KList[I]; KList[I] := KList[Min]; KList[Min] := Temp end end; { of proc SortIndexList } procedure InitStuff(FileName : FileStr; var Error : Integer); { sets everything up for indexing system. This assumes that there are no more than IndexMax (=1000) records, and that the records are numbered 1..IndexMax. Record #0 is the header record and is used to store the current number of records actively being used in the file } var Indx,TErr : Integer; TRec : DataRec; begin Error := NoError; FOpen(FileName,Error); if Error <= NoError then begin MaxRec := 0; FRead(0,TRec,TErr); Error := TErr; MaxRec := TRec.NumRecs; for Indx := 1 to MaxRec do begin FRead(Indx,TRec,TErr); if TErr > 0 then Error := TErr; KList[Indx].Key := TRec.Key; KList[Indx].Num := Indx end; SortIndexList end end; { of proc InitStuff } procedure CleanUpStuff(var Error : Integer); { this just does an orderly shutdown and should be called before you leave your program (or open another data file) } var TRec : DataRec; begin TRec.NumRecs := MaxRec; { save out # of records } FWrite(0,TRec,Error); FClose(Error) end; { of proc CleanUpStuff } function FindKey(Key : Keytype) : Integer; { looks for Key in KList; returns location in KList if found; otherwise returns - 1 } var L,R,Mid : Integer; begin L := 1; R := MaxRec; repeat Mid := (L+R) div 2; if Key < KList[Mid].Key then R := Mid-1 else L := Mid+1 until (Key = KList[Mid].Key) or (L > R); if Key = KList[Mid].Key then FindKey := Mid else FindKey := -1 end; { of proc FindKey } procedure GetRecord(Key : Keytype; var Rec : DataRec; var Error : Integer); { looks through KList for Key; if found, returns in Rec. It and the routines that follow assume the procedure Seek for random access of the file of records. } var Item : Integer; begin Error := NoError; Item := FindKey(Key); if Item > 0 then FRead(KList[Item].Num,Rec,Error) else Error := RecordNotFound end; { of proc GetRecord } procedure PutRecord(Rec : DataRec; var Error : Integer); { writes Rec out to the file. If a record with that key already exists, then overwrites that record; otherwise, adds the record to the end of the file. If there's no more room for records, exits with an error code } var Item : Integer; begin Error := NoError; Item := FindKey(Rec.Key); if Item >= 0 then FWrite(KList[Item].Num,Rec,Error) else if MaxRec < IndexMax then begin MaxRec := MaxRec + 1; FWrite(MaxRec,Rec,Error); KList[MaxRec].Key := Rec.Key; KList[MaxRec].Num := MaxRec; SortIndexList end else Error := NoMoreRoom end; { of proc PutRecord } procedure AddRecord(Rec : DataRec; var Error : Integer); { adds a record to the file. If a record with the same key already exists, then exits with an error code } var Item : Integer; begin Error := NoError; Item := FindKey(Rec.Key); if Item > 0 then Error := AlreadyExists else PutRecord(Rec,Error) end; { of proc AddRecord } procedure DeleteRecord(Key : Keytype; var Error : Integer); { deletes the record with 'Key' by copying the last record in the file to that slot, then modifies KList by shuffling all the key entries up } var Item,Last,Max,MVal : Integer; TRec : DataRec; begin Error := NoError; Item := FindKey(Key); if Item = -1 then Error := RecordNotFound else begin Max := 1; MVal := KList[Max].Num; for Last := 2 to MaxRec do if KList[Last].Num > MVal then begin Max := Last; MVal := KList[Last].Num end; if Max <> Item then begin FRead(MVal,TRec,Error); { get last record in file } FWrite(KList[Item].Num,TRec,Error); { write over it } KList[Max].Num := KList[Item].Num end; for Last := Item to MaxRec-1 do { delete KList[Item] } KList[Last] := KList[Last+1]; MaxRec := MaxRec - 1 { adjust # of records } end end; { of proc DeleteRecord } { USERIO.LIB procedure and functions in this library WriteStr write message out at (Col,Line) Error writes message out at (1,1), waits for character GetChar prompt user for one of a set of characters Yes gets Y/N answer from user GetString prompt user for a string IOCheck checks for I/O error; prints message if necessary } type MsgStr = string[80]; CharSet = set of Char; var IOErr : Boolean; IOCode : Integer; procedure WriteStr(Col,Line : Integer; TStr : MsgStr); { purpose writes message out at spot indicated last update 23 Jun 85 } begin GoToXY(Col,Line); ClrEol; Write(TStr) end; { of proc WriteStr } procedure Error(Msg : MsgStr); { purpose writes error message out at (1,1); waits for character last update 05 Jul 85 } const Bell = ^G; var Ch : Char; begin WriteStr(1,1,Msg+Bell+' (hit any key) '); Read(Kbd,Ch) end; { of proc Error } procedure GetChar(var Ch : Char; Prompt : MsgStr; OKSet : CharSet); { purpose let user enter command last update 23 Jun 85 } begin WriteStr(1,1,Prompt); repeat Read(Kbd,Ch); Ch := UpCase(Ch) until Ch in OKSet; WriteLn(Ch) end; { of proc GetChar } function Yes(Question : MsgStr) : Boolean; { purpose asks user Y/N question last update 03 Jul 85 } var Ch : Char; begin GetChar(Ch,Question+' (Y/N) ',['Y','N']); Yes := (Ch = 'Y') end; { of func Yes } procedure GetString(var NStr : MsgStr; Prompt : MsgStr; MaxLen : Integer; OKSet : CharSet); { purpose get string from user last update 09 Jul 85 } const BS = ^H; CR = ^M; ConSet : CharSet = [BS,CR]; var TStr : MsgStr; TLen,X : Integer; Ch : Char; begin {$I-} { turn off I/O checking } TStr := ''; TLen := 0; WriteStr(1,1,Prompt); X := 1 + Length(Prompt); OKSet := OKSet + ConSet; repeat GoToXY(X,1); repeat Read(Kbd,Ch) until Ch in OKSet; if Ch = BS then begin if TLen > 0 then begin TLen := TLen - 1; X := X - 1; GoToXY(X,1); Write(' '); end end else if (Ch <> CR) and (TLen < MaxLen) then begin Write(Ch); TLen := TLen + 1; TStr[TLen] := Ch; X := X + 1; end until Ch = CR; if TLen > 0 then begin TStr[0] := Chr(TLen); NStr := TStr end else Write(NStr) {$I+} end; { of proc GetString } procedure IOCheck(IOCode : Integer); { purpose check for IO error; print message if needed last update 19 Feb 86 } var TStr : string[4]; begin IOErr := (IOCode <> 0); if IOErr then case IOCode of $01 : Error('IOERROR> File does not exist'); $02 : Error('IOERROR> File not open for input'); $03 : Error('IOERROR> File not open for output'); $04 : Error('IOERROR> File not open'); $10 : Error('IOERROR> Error in numeric format'); $20 : Error('IOERROR> Operation not allowed on logical device'); $21 : Error('IOERROR> Not allowed in direct mode'); $22 : Error('IOERROR> Assign to standard files not allowed'); $90 : Error('IOERROR> Record length mismatch'); $91 : Error('IOERROR> Seek beyond end of file'); $99 : Error('IOERROR> Unexpected end of file'); $F0 : Error('IOERROR> Disk write error'); $F1 : Error('IOERROR> Directory is full'); $F2 : Error('IOERROR> File size overflow'); $FF : Error('IOERROR> File disappeared') else Str(IOCode:3,TStr); Error('IOERROR> Unknown I/O error: '+TStr) end end; { of proc IOCheck } { declarations and code for test program } const CmdPrompt : MsgStr = 'TEST> A)dd, D)elete, F)ind, L)ist, I)ndex, C)lose, Q(uit: '; FilePrompt : MsgStr = 'TEST> Enter file name: '; DonePrompt : MsgStr = 'TEST> Another file?'; CmdSet : CharSet = ['A','D','F','L','I','C','Q']; NameSet : CharSet = [' '..'~']; PhoneSet : CharSet = ['0'..'9','-','/','(',')']; var Cmd : Char; ErrVal : Integer; FileName : FileStr; Done : Boolean; procedure FileError(ErrVal : Integer); begin if ErrVal < 100 then case ErrVal of RecCountErr : Error('Record count mismatch'); NewFileCreated : Error('Creating new file'); RecordNotFound : Error('Record not found'); NoMoreRoom : Error('No more room'); AlreadyExists : Error('Record already exists') end else begin IOCheck(ErrVal-100) end end; { of proc FileError } procedure DoAdd; { purpose add a record to the file last update 19 Feb 86 } var TStr : MsgStr; TRec : DataRec; begin FillChar(TRec,SizeOf(TRec),0); with TRec do begin TStr := ''; GetString(TStr,'ADD> Enter name: ',40,NameSet); if TStr <> '' then begin Key := TStr; TStr := ''; GetString(TStr,'ADD> Enter phone #: ',12,PhoneSet); theRest := TStr; AddRecord(TRec,ErrVal); Flush(DFile); FileError(ErrVal) end end; end; { of proc DoAdd } procedure DoDelete; { purpose delete a record from the file last update 19 Feb 86 } var Key : Keytype; begin GetString(Key,'DELETE> Enter name: ',40,NameSet); DeleteRecord(Key,ErrVal); FileError(ErrVal) end; { of proc DoDelete } procedure DoFind; { purpose find a record in the file last update 19 Feb 86 } var Key : Keytype; TRec : DataRec; begin GetString(Key,'FIND> Enter name: ',40,NameSet); GetRecord(Key,TRec,ErrVal); if ErrVal = NoError then begin WriteStr(1,2,'The phone number is '); Writeln(TRec.theRest) end else FileError(ErrVal) end; { of proc DoDelete } procedure DoList; { purpose list out contents of the file last update 19 Feb 86 } var TRec : DataRec; Indx : Integer; begin ClrScr; Writeln; for Indx := 1 to MaxRec do with KList[Indx] do begin WriteStr(1,Indx+1,Key); Write(' ':(45-Length(Key))); GetRecord(Key,TRec,ErrVal); if ErrVal = NoError then with TRec do Writeln(theRest) else FileError(ErrVal) end end; { of proc DoList } procedure ShowIndex; { purpose list out contents of the key list last update 19 Feb 86 } var Indx : Integer; begin ClrScr; Writeln; for Indx := 1 to MaxRec do with KList[Indx] do Writeln(Key,' ':(45-Length(Key)),Num:5) end; { of proc DoList } begin repeat Done := False; ClrScr; GetString(FileName,FilePrompt,80,NameSet); InitStuff(FileName,ErrVal); FileError(ErrVal); repeat GetChar(Cmd,CmdPrompt,CmdSet); case Cmd of 'A' : DoAdd; 'D' : DoDelete; 'F' : DoFind; 'L' : DoList; 'I' : ShowIndex; 'Q' : Done := True end until (Cmd = 'C') or Done; CleanUpStuff(ErrVal); FileError(ErrVal); ClrScr; if not Done then Done := not Yes(DonePrompt) until Done end. { of program TestIndex }