Gauche Devlog

< Curse of extended floating point arithmetic | Building a list with index---which way is faster? >

2013/01/29

New REPL

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 user. (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.

We use 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: gauche.interactive.

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.

  1. The core libgauche implements a built-in REPL, with the default reader, evaluator, printer and prompter as the minimal default. This is what we have in libeval.scm now, 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)))))
    
  2. The gauche.interactive module 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)))
    
  3. The main.c of gosh imports gauche.interactive into the user module when it is invoked in interactive mode, then it evaluates (read-eval-print-loop). If gauche.interactive is loaded successfully, the extended version is called because it shadows the built-in version. If gauche.interactive can't be loaded, however, the built-in version is called.

Tags: 0.9.4, repl, gauche.interactive

Post a comment

Name: