/* FILES.PL */ :- module files. :- public open_input/2, open_output/2, open_and_reconsult/2, exists_file/2, exists_file/1, find_file/4, find_file/5, add_file_defaults/2, print_file/2, print_and_delete_file/2. /* SPECIFICATION ------------- This module exports file-handling predicates. Briefly, open_input and open_output try opening files for input or output, but instead of crashing if an error (such as illegal filename) occurs, they return an atom saying what the error was. This makes it possible to trap and recover from errors. open_and_reconsult acts the same way, but reconsults the file if it's readable. exists_file checks whether a file exists and returns its full name. find_file acts as a wildcarded search, looking for a file amongst a range of directories or extensions. add_file_defaults generates a full file name from a partial name plus defaults. print_file prints a file to a specified printer. print_and_delete_file does the same but deletes it afterwards. You must consult BUG.PL before using this module. PUBLIC open_input( FileName+, Status? ): ---------------------------------------- "Status indicates the result of trying to open FileName for input and redirecting the CIS to it". Try opening the file named FileName for input. FileName should be an atom. If it opens, unify Status with 'ok', and make the file the CIS. Otherwise leave the CIS as it is and unify Status with one of the atoms {file_not_found, directory_not_found, access_forbidden, invalid_filename}. An error which isn't one of these may have undefined effects (such as causing an 'abort'). PUBLIC open_and_reconsult( FileName+, Status? ): ------------------------------------------------ "Status indicates the result of trying to open and reconsult FileName". FileName should be as for open_input. Try opening FileName for input. If it opens, unify Status with 'ok' and reconsult the file. Otherwise unify Status with one of the same error atoms as for open_input, and don't consult the file. The same caution about unexpected errors holds. This predicate does not change the CIS. PUBLIC open_output( FileName+, Status? ): ----------------------------------------- "Status indicates the result of trying to open FileName for output and redirect the COS to it". Try opening the file for output. If it opens, unify Status with 'ok', and make the file the COS. Otherwise leave the COS as it is and unify Status with one of the atoms {no_space_for_file, directory_not_found, access_forbidden, invalid_filename}. This predicate works in the same way as open_read, and the same caution about unexpected errors applies. PUBLIC exists_file( FileName+, FullName? ): ------------------------------------------- "The file named FileName exists, and its full name is FullName". FileName can be either an atom, or a structure of the form Name+defaults(Directory,Extension) where Name, Directory, and Extension are atoms. This predicate checks to see whether the file exists. If so, FullName is unified with an atom which is the full name (complete with disc, directory, extension, and version number on my system, VAX/VMS). If not, the predicate fails. If FileName is a +structure, its second argument is assumed to contain defaults for the directory name and file extension. If the name doesn't contain a component, and the default value given is not '', exists_file adds the default before testing for existence. Examples: exists_file( 'fred.pl', F ) - fail if no FRED.PL exists in the current directory. - else set F to (e.g.) 'fs12:[me]fred.pl;12'. exists_file( 'fred'+defaults('[me]','.pl') - if [ME]FRED.PL exists, set F to the full name as above. exists_file( 'fred.lsp'+defaults('[me]','.pl') - if [ME]FRED.LSP exists, set F to the full name. exists_file( '[you]fred.lsp'+defaults('[me]','.pl') - if [YOU]FRED.LSP exists, set F to the full name. Note: As exists_file is implemented here, it deems a file to be found even if it can't be read. So finding a file is no guarantee that it can be read (the file might, for example, have read access denied). PUBLIC exists_file( File+ ): ---------------------------- "The file named File exists". As for exists_file/2, ignoring the second argument. PUBLIC find_file( File+, DefaultList+, FullName?, Status? ): ------------------------------------------------------------ "Status indicates whether there exists a file whose name is File plus one of the defaults in DefaultList. Its full name is FullName." File is an atom, naming a file. DefaultList is a list of structures, each of the form defaults(Directory,Extension) as for exists_file/2. The idea is that DefaultList specifies a number of directory/extension defaults under which to search for File. This list must not be empty. These defaults work in the same way as for exists_file. find_file tries each in turn, until either it runs out of defaults, or it finds the file. If it does find the file, it unifies FullName with the full name (as for exists_file) and Status with 'ok'. Otherwise it leaves FullName uninstantiated and unifies Status with the atom file_not_found. Note that (like exists_file), find_file may say that a file exists even if it can't be read. Examples: find_file( fred, [defaults('','.pl'),defaults('','.lsp')], F, Status ) - if FRED.PL exists in the current directory, sets F to (e.g.) 'fs12:[me]fred.pl;12'. - if FRED.PL doesn't exist, but FRED.LSP does, sets F to (e.g.) 'fs12:[me]fred.lsp;5'. - else sets Status to file_not_found. find_file( 'fred.lib', [defaults('',''),defaults('[here]',''), defaults('[there]','')], F, Status ) - searches the current directory, [HERE], and [THERE] in that order for FRED.LIB. PUBLIC find_file( File+, DirectoryList+, Extension+, FullName?, Status? ): ---------------------------------------------------- "Status indicates whether there exists, in one of the directories in DirectoryList, a file whose name is File and whose extension defaults to Extension. Its full name is FullName." This is a different arrangement of arguments, more convenient for some purposes; it searches a list of directories for a file with specified extension. Once again, the list must not be empty. Arguments File, FullName and Status are as for find_file/4. DirectoryList is a list of directory names (each of which must be an atom). Extension is a default extension, also an atom. Example: find_file( 'fred', [ '', '[MATTHEWS]', '[LATNER]' ], '.PL', F, Status ) - looks for FRED.PL, first in the current directory, then in [MATTHEWS], and finally in [LATNER]. If found, F becomes a full name, otherwise S becomes file_not_found. PUBLIC add_file_defaults( FileName+, FullName? ): ------------------------------------------------- "FullName is the full name of FileName after adding any defaults it specifies". FileName can be either an atom, or a structure of the form Name+defaults(Directory,Extension) as for exists_file. If either default is not '', and the file name lacks that component, then add_file_defaults adds it to make FullName. Examples: add_file_defaults( fred+defaults('[me]','.pl'), F ) - sets F to '[me]fred.pl'. add_file_defaults( 'fred.lsp'+defaults('','.pl'), F ) - sets F to 'fred.lsp'. add_file_defaults( fred, F ) or add_file_defaults( fred+defaults('','') ) - set F to fred. PUBLIC print_file( FileName+, Printer+ ): ----------------------------------------- "FileName has been printed to Printer". Prints the file on the specified printer. FileName must be an atom. Printer is any atom naming one of the printers { USERAREA, PSYZOO, CTC, ... } defined in HELP PRINTERS at Oxford. Note there is no status returned. Under VMS, it is not possible to pick one up (even though the file has been queued for printing, the printer might be off-line, out of paper, etc). */ /* IMPLEMENTATION -------------- Below are notes on my implementation, and suggestions for porting. Of course, your Prolog implementor may be nice and provide 'print_file', 'exists_file' etc. built-in. In that case, you're lucky... 1) Safe open routines. ---------------------- These rely on being able to trap and recover from errors. If you can't do this, you won't be able to implement them. The way it's done here is this: when an error occurs (e.g. trying to see a non-existent file, trying to call an undefined predicate, trying to do arithmetic on a variable, trying to square-root a negative number), Poplog calls the system predicate prolog_error( ErrorMessage, Culprits ) with an error-message-atom (which may come in part from VMS) and the item or items which caused the error (the "culprits"). There are initially two clauses for prolog_error. The first is activated whenever ErrorMessage indicates that an undefined predicate was called. The second is activated if the first fails (i.e. when the error is _not_ an undefined predicate). It calls another system predicate which reports the error and aborts. You can change this action by asserting extra clauses for prolog_error. Obviously, to be useful, they must be asserta'd, to come before the clauses I've just mentioned. Part of the code below does this: there is one clause asserted for trapping errors caused by open_input and open_and_reconsult; and another for trapping errors caused by open_output. Consider input first. The clause for prolog_error that deals with input translates the system message it receives into one of the atoms I use for the Tutor's error codes. This is done by calling input_error( SystemMessage, MyMessage ) It then asserts '$open_input_error_was'( MyMessage ) So open_input can now be made safe. It calls 'see'. This will either work normally; or cause an error. In the latter case, the error will cause a call of prolog_error, which will assert a clause for '$open_input_error_was'(...). open_input can then test for the presence of this clause, retract it if it exists, and return the status value. open_output works in the same way. There is one complication. I sometimes want the same system message to be translated differently for input and output errors. So I need two different error-handlers. This is achieved by asserting another clause saying which the calling routine was: one of '$calling_open_input' '$calling_open_output' My clauses for prolog_error can then check this. If your Prolog has error-trap predicates, you may be able to adapt this mechanism. If not, but you have something akin to '$nofilerrors': i.e. some way of making 'see' fail if it detects an error, you can use that. You won't be able to pick up an error code, but you can at least detect whether 'see' failed, and return status value not_ok or something. 2) Exists file. --------------- This calls a Pop-11 routine to test for file existence. If you don't have a built-in existence tester, or one reachable from a foreign language, you may be able to use error traps, if you have them. To test whether a file exists: try 'see'ing it. If this causes an error, it probably doesn't exist. It might of course exist but be unreadable: in that case, you will lose a bit of specificity in the message (it's possible that the teacher could have copied the scripts to a library directory, and forgotten to give public access). 3) Find file. ------------- This just calls exists_file for each possible full name. 4) Add file defaults. --------------------- This calls a Pop-11 routine for parsing filenames into their components. If you don't have something like this, you could convert the filename to a list of characters and use a DCG grammar to parse it. 5) Print file. -------------- This calls the Pop-11 command issuer. If you don't have one, you might be able to do something like this: print_file( File, Printer ) :- seeing( CIS ), telling( COS ), see( File ), tell( Printer ), repeat, ( get0(C), C = /* your end-of-file character */ ; put(C), fail ), seen, told, see( CIS ), tell( COS ). in systems where the printer can be treated as a file. */ :- needs bug. :- dynamic '$open_input_error_was'/1, '$open_output_error_was'/1, '$calling_open_input'/0, '$calling_open_output'/0. /* The Pop-11 parts of these predicates live in FILES.P */ :- pop_compile( 'files.p' ). open_input( FileName, StatusOut ) :- retractall( '$open_input_error_was'(_) ), assert( '$calling_open_input' ), seeing( CIS ), see( FileName ), ( '$open_input_error_was'(Status) -> see( CIS ), retractall( '$open_input_error_was'(_) ) ; Status = ok ), retractall( '$calling_open_input' ), StatusOut = Status. open_and_reconsult( FileName, StatusOut ) :- retractall( '$open_input_error_was'(_) ), assert( '$calling_open_input' ), reconsult( FileName ), ( '$open_input_error_was'(Status) -> retractall('$open_input_error_was'(_)) ; Status = ok ), retractall( '$calling_open_input' ), StatusOut = Status. input_error('CAN\'T OPEN FILE (file not found)', file_not_found). input_error('CAN\'T OPEN FILE (directory not found)', directory_not_found). input_error( 'CAN\'T OPEN FILE (insufficient privilege or file protection violation)', access_forbidden). input_error('CAN\'T OPEN FILE (error in file name)', invalid_filename). input_error('Wrong value for argument1 of see', invalid_filename). :- assert(( prolog_error(SystemMessage,File) :- '$calling_open_input', !, input_error(SystemMessage,MyMessage), asserta( '$open_input_error_was'(MyMessage) ) )). open_output( FileName, StatusOut ) :- assert( '$calling_open_output' ), retractall( '$open_output_error_was'(_) ), telling( COS ), tell( FileName ), ( '$open_output_error_was'(Status) -> tell( COS ), retractall('$open_output_error_was'(_)) ; Status = ok ), retractall( '$calling_open_output' ), StatusOut = Status. output_error('CAN\'T OPEN FILE (directory not found)', directory_not_found). output_error('CAN\'T CREATE FILE (error in file name)', invalid_filename). output_error('Wrong value for argument1 of tell', invalid_filename). output_error('CAN\'T CREATE FILE (disk quota exceeded)', no_space_for_file). output_error( 'CAN\'T OPEN FILE (insufficient privilege or file protection violation)', access_forbidden). :- assert(( prolog_error(SystemMessage,_) :- '$calling_open_output', !, output_error( SystemMessage, MyMessage ), asserta( '$open_output_error_was'(MyMessage) ) )). exists_file( File+defaults(Directory,Extension), FullName ) :- !, prolog_eval( exists_file(Directory,File,Extension), FullName ), /* Returns 0 if the file doesn't exist. */ FullName \= 0. exists_file( File, FullName ) :- exists_file( File+defaults('',''), FullName ). exists_file( File ) :- exists_file( File, _ ). find_file( _, [], _, _ ) :- bug( 'find_file: default list is empty' ). find_file( File, DefaultList, FullNameOut, StatusOut ) :- find_file1( File, DefaultList, FullName, Status ), FullNameOut = FullName, StatusOut = Status. find_file1( _, [], _, file_not_found ) :- !. find_file1( File, [Defaults|OtherDefaults], FullName, ok ) :- exists_file( File+Defaults, FullName ), !. find_file1( File, [_|OtherDefaults], FullName, Status ) :- find_file( File, OtherDefaults, FullName, Status ). find_file( _, [], _, _, _ ) :- bug( 'find_file: default list is empty' ). find_file( File, DefaultList, Extension, FullNameOut, StatusOut ) :- find_file1( File, DefaultList, Extension, FullName, Status ), FullNameOut = FullName, StatusOut = Status. find_file1( _, [], _, _, file_not_found ) :- !. find_file1( File, [Dir|OtherDirs], Ext, FullName, ok ) :- exists_file( File+defaults(Dir,Ext), FullName ), !. find_file1( File, [_|OtherDirs], Ext, FullName, Status ) :- find_file1( File, OtherDirs, Ext, FullName, Status ). add_file_defaults( File+defaults(Directory,Extension), FullName ) :- !, prolog_eval( add_file_defaults(Directory,File,Extension), FullName ). add_file_defaults( FileName, FileName ). print_file( File, Printer ) :- prolog_eval( print_file(File,Printer,nodelete) ). print_and_delete_file( File, Printer ) :- prolog_eval( print_file(File,Printer,delete) ). :- endmodule.