larcenists / larceny

Larceny Scheme implementation
Other
203 stars 32 forks source link

close file after failing to load it #62

Open larceny-trac-import opened 11 years ago

larceny-trac-import commented 11 years ago

Reported by: pnkfelix on Tue May 9 19:55:15 2006 Lets assume we have the file, death.scm, with the following contents:

(error 'death "Whoops")

If we do (load "death.scm") in Larceny, the load fails.
However, on Windows, attempts to write a new version of the file (perhaps with Emacs) do not go through, because Windows doesn't allow you to write to open files.

Right now, the work around is to call (close-open-files). (That, or exit Larceny. close-open-files is very useful.)

It would be better for load to try to close the file itself. I know this is hard, because "errors" are just continuations for us, and perhaps we will later re-enter and attempt to continue reading from the file. I know Jesse looked at some work on the right set of constructs for describing the different kinds of control flow that are involved here.

All I know is that the current semantics are annoying on Windows.

larceny-trac-import commented 11 years ago

Author: pnkfelix One way to deal with the continuation issue is to first read in the entirety of the file (as a list of s-exprs), then close the file, and '''then''' start evaluating the s-exprs.

larceny-trac-import commented 11 years ago

Author: pnkfelix punt

larceny-trac-import commented 11 years ago

Author: pnkfelix From comment above (circa 05/13/06): One way to deal with the continuation issue is to first read in the entirety of the file (as a list of s-exprs), then close the file, and then start evaluating the s-exprs.

Don't do this. If we were to do this:

  1. We would not longer be able to dynamically change the reader to affect how the remainder of the file is read
  2. We would not longer be able to replace the interaction-environment to affect how global references in the remainder of the file are resolved (because compiled references to global variables are resolved by the reader in Larceny).
larceny-trac-import commented 11 years ago

(manually splicing in comment:5 which trac import failed to import)

There are four different approaches I've looked into briefly for this:

  1. Wrap a dynamic-wind around the evaluation in the load procedure. The problem here comes if we end up jumping out and back in again for a non-error reason.
  2. Override the error-handler parameter within load and have it close the port before calling the original error-handler. The problem here is that if the calling context already overrode the error-handler, it might not necessarily exit the system anymore, and so it would be a mistake to close the port.
  3. Override the reset-handler parameter (which the standard error-handler invokes) within load to close the port. This does not fall victim to the same problems as (2), because the reset-handler is specified as something that will exit out to the host system. (I think of it like a declaration like let/ec).
    • The only problem I ran into with this approach is that if someone uses first captures a continuation during the load, and then wraps a dynamic-wind around the erroneous expression, and finally invokes the saved continuation in the after procedure of the dynamic-wind, then they will be able to observe the fact that the file was closed. (Specifically, load's call to read-char will fail as load will attempt to just load the next expression in the file.)
    • Strictly speaking, the code described above is not legal under R5RS -- you are not allowed to capture or invoke captured continuations within the before or after of a dynamic-wind. So only someone writing code targetting Larceny (or Scheme's that extend R5RS with support for this, which Larceny might not claim to do anyway) would do such a thing.
  4. But if we had to, there is a workaround to support even this sort of code. We could do the following:
Error: Failed to load processor sch

No macro or processor named 'sch' found

This keeps a state variable tracking whether reset was ever called, and if so, closes the port when the continuation exits the function. This should handle the majority of the cases that we would be worrying about in option 3.

Personally I want to go with Option 3 without worrying about dynamic-wind "support" a la option 4, since the latter seems complicated and error prone. Option 3 is simple, and if someone really expects an error and wants to recover from it, they should probably be using the Larceny specific extensions such as overriding the error-handler parameter.

Author: pnkfelix Replying to [comment:5 pnkfelix]:

There was a code snippet (in the comment I am replying to here) that apparently is not rendered because it refers to a code formatting plugin that does not exist. (I think this is an artifact of the move from our own hosted Trac to the CCIS hosted Trac...)

Here is a transcription of that snippet:

(let ((p (open-binary-input-file filename)))
 (let ((reset-invoked #f)) ;; START OF NEW CODE
   (dynamic-wind
       (lambda () 'do-nothing)
       (lambda () 
         (parameterize ((reset-handler (let ((old-rh (reset-handler)))
                                         (lambda args
                                           (set! reset-invoked #t)
                                           (apply old-rh args)))))
           ... ;; OLD-LOAD-CODE
           ))
       (lambda () ;; MORE NEW CODE
         (if reset-invoked (close-input-port p)))))
 (close-input-port p)
 (unspecified))