Upcoming 0.9.4 will have a slightly improved REPL---eval result history
is available via variables
*1 etc, and it also shows current module in the prompt if you switch the module from the default
(See Better REPL? for the history feature. Other features listed there haven't been ported.)
I admit it's not much. The point is that now we have a scaffold that can be easily extended later to try out new ideas. The new REPL is written in Scheme (in
gauche.interactive module) and loaded automatically when
gosh starts in the interactive mode.
Here I'd like to write about the implementation rather than the features.
Writing REPL in Scheme is trivial. There's a catch, though.
gosh as a script interpreter as well as an interactive shell. If we use it as a script interpreter, we don't need REPL and we don't want to carry the extra baggage. (Currently the extended REPL is pretty simple, but it'll get bigger as we throw in more features).
This leads to an idea that we implement REPL in a separate module that can only be loaded when
gosh is in interactive mode. We already have such module:
However, during development of Gauche itself, it is often a case that
gosh fails to load
gauche.interactive due to a bug. Writing REPL is like fixing a roof while you're on it; you can be stuck or fall if you make mistakes. Even in such a state we need some sort of basic REPL to investigate and fix the problem.
And we don't want to duplicate similar loop code. The difference between the basic REPL and the extended one is in evaluator, printer, etc., but the core loop construct should be shared.
So here's what I did.
- The core
libgaucheimplements a built-in REPL, with the default
prompteras the minimal default. This is what we have in
libeval.scmnow, which is compatible to the one we had until 0.9.3, except it was written in C.
(define-in-module gauche (read-eval-print-loop :optional (reader #f) (evaluator #f) (printer #f) (prompter #f)) (let ([reader (or reader read)] [evaluator (or evaluator eval)] [printer (or printer %repl-print)] [prompter (or prompter %repl-prompt)]) (let loop1 () (and (with-error-handler (^e (report-error e) #t) (^ (let loop2 () (prompter) (let1 exp (reader) (and (not (eof-object? exp)) (receive results (evaluator exp (vm-current-module)) (apply printer results) (loop2))))))) (loop1)))))
gauche.interactivemodule implements extended evaluator (
%evaluator) and prompter (
%prompter). In future, we'll also extend reader and printer. Then it defines its own
read-eval-print-loop, wrapping the built-in REPL with the extended handlers:
(define (read-eval-print-loop :optional (reader #f) (evaluator #f) (printer #f) (prompter #f)) (let ([evaluator (or evaluator %evaluator)] [prompter (or prompter %prompter)]) ((with-module gauche read-eval-print-loop) reader evaluator printer prompter)))
usermodule when it is invoked in interactive mode, then it evaluates
gauche.interactiveis loaded successfully, the extended version is called because it shadows the built-in version. If
gauche.interactivecan't be loaded, however, the built-in version is called.