That is a clever idea, but to my understanding it incurs quite an overhead. Give me some idea if I'm doing overly complex.C read/write works recursively, so by the time Scheme handlers are called, the state is implicitly represented in multiple C stack frames, which can't be portably saved and recovered.(Theoretically we can rewrite everything to use trampoline or keep track of state by ourselves to allow save/restore, butif we do so, we can handle conitnuations as well.)
What I can think of for read is that we save every characters read since the outermost call of C read, and save them alongthe continuation; when the continuation is invoked after outermost C read returns, we re-read input from the saved inputso that we can reach at the point of continuation with the same internal state. Writer is a bit easier, for we can just rememberthe number of characters, and when restarted, we just redirect that number of characters into void.
On Sun, Apr 19, 2020 at 8:45 PM Marc Nieper-Wißkirchen <xxxxxx@nieper-wisskirchen.de> wrote:After some thinking I have come to the conclusion that a C implementation of `write' or `read' that cannot handle arbitrary control flow is still possible:(1) The C implementation of `write' has an internal counter, which keeps track of the number of bytes already written. When control leaves the `write' procedure, the internal counter and the datum to be written is stored in the continuation. This counter can be used to restore the internal state of `write' when the C stack frames have been thrown away by just outputting the datum anew and throwing away the first characters corresponding to the value of the counter.(2) The C implementation of `read' keeps track of the characters read so far. These are stored in the continuation. When the stack frames of the C procedure have been thrown away, the internal state can be restored by reading the first characters from the characters stored.Not restricting non-local control flow is a good thing, but what should be added is that it is an error to modify a datum that is being written (which the custom port code easily could do). There may be other situations where mutating datums should be an error and which should be mentioned.MarcAm Sa., 18. Apr. 2020 um 00:11 Uhr schrieb Shiro Kawai <xxxxxx@gmail.com>:On Fri, Apr 17, 2020 at 11:34 AM Marc Nieper-Wißkirchen <xxxxxx@nieper-wisskirchen.de> wrote:Would Gauche tolerate the use of `raise-continuable' by the custom port code (and re-entering) without explicit use of `call/cc'?Yes. And the control can come back to its continuation and the handler can resume the operation. However, the C stack frame has been popped, then an error is signaled when the read/write returns after the handler finishes the operation.Would it be possible to replace the C versions of read/write with versions written in Scheme? It wouldn't pose a problem if they are a bit slower than their C counterparts because the latter can still be used for non-custom ports.It can be; we can dispatch by port type in the Scheme read/write, and calls internal C read/write only for built-in port types, for example. However, C read/write routines can be called from various other parts of C, so it is generally unavoidable that C stack frame is mixed into somewhere (e.g. extended type-specific writers can be written in C which recursively called generic C write routine).-- MarcOn Fri, Apr 17, 2020 at 10:33 AM Marc Nieper-Wißkirchen <xxxxxx@nieper-wisskirchen.de> wrote:Am Fr., 17. Apr. 2020 um 21:47 Uhr schrieb John Cowan <xxxxxx@ccil.org>:On Fri, Apr 17, 2020 at 9:17 AM Marc Nieper-Wißkirchen <xxxxxx@nieper-wisskirchen.de> wrote:I am not sure but have the implications of SRFI 181 for procedures like `read' and `write' ever been mentioned? Before SRFI 181 (or the similar R6RS library), `read' and `write' do not call user-supplied code. Now, they will when they are invoked with custom ports. This means that user code can capture and reinstall continuations that cover part of the Scheme reader and writer.That was already possible in the presence of readtables and SRFI-10, though neither has been standardized.I have to admit that I have never taken a closer look at SRFI 10.Forbidding escape continuations would be too severe in my opinion (they could be used to jump out of code blocks when the custom port notices certain error conditions). But even continuations that can jump back and forth are meaningful in custom port code (think of `raise-continuable').Note that raising an exception does not in itself alter the dynamic scope (except for the small part of it consisting of the hidden current-exceptioni-handler parameter), unlike throwing an exception in most languages. The continuation must be explicitly captured and then called; this is implicitly done by using `guard`.My wording may have been a bit misleading. I didn't mean any specific implementation but just a control flow realized with first-class continuations that can do what `raise-continuable' and the machinery to jump back to the point where the error was raised can do.The best resolution and the one without any rough edges for the user would be to allow unlimited call/cc use by custom port code. It has to be checked how much this would cost for implementations of `read' and `write'. Usual implementations of `read' and `write' have some mutable state in the form of hash tables (to support cyclic data structures), whose mutations would have to be guarded with, say `dynamic-wind'. (Another, more elegant solution would probably be to use functional persistent hash tables as defined by `(srfi 146 hash)'.)Indeed. However, I am content to leave this case unpredictable. REPLs, for example, do not always defend against parts of themselves being captured, as by invoking call/cc at the top level:#;> (define *escape* #f)#;> (call/cc (lambda (k) (set! *escape) k)))#:> (*escape*)produces unpredictable results.When arbitrary jumps are not allowed, it is another matter whether the implementation defends itself against it. (As a side note, it would be nice if R7RS-large will get continuation barriers, which can them be used to implement such defenses.)In the case of custom ports, though, it is not at all only academic question whether not to forbid non-local control flow. An example is the Emscripten port of Chibi-Scheme I once did. As JavaScript events are asynchronous, a Scheme (custom) port running in compiled form in JavaScript cannot just wait for the next character to become ready, say. One has to install an event handler and jump out of the JavaScript code. This can only work flawlessly when I can save and restore the current Scheme continuation. In fact, Scheme would have a great advantage here. Porting arbitrary C programs that do not have a central event loop to Emscripten is much more difficult.Thus, I would suggest checking whether unrestricted use of call/cc (which should be the default; are there any higher-order procedures in R7RS that do not work with call/cc?) can be realized through a clever implementation or read and write. If that is the case, no restrictions need to be mentioned in this SRFI.Marc