edward

An extensible POSIX-compatible implementation of the ed(1) text editor

git clone https://git.8pit.net/edward.git

  1;;>| Read–Eval–Print Loop
  2;;>
  3;;> REPL abstraction which provides a read-eval-print loop that
  4;;> continously reads data from standard input and parses this
  5;;> input data using provided parser combinators. The REPL
  6;;> operates on a [parse stream](#section-parse-streams) internally.
  7
  8;;> Create an new REPL instance with the given input `prompt` string.
  9
 10(define (make-repl prompt)
 11  (let ((prompt? (not (empty-string? prompt))))
 12    (%make-repl
 13      (if prompt? prompt "*")
 14      prompt?
 15      (make-parse-stream "stdin" fileno/stdin)
 16      0)))
 17
 18(define (repl-state-set! repl source index)
 19  (repl-stream-set! repl source)
 20  (repl-index-set! repl index))
 21
 22;;> Record type representing the REPL.
 23(define-record-type Read-Eval-Print-Loop
 24  (%make-repl prompt-str prompt? stream index)
 25  ;;> Predicate which returns true if the given object was created using [make-repl](#make-repl).
 26  repl?
 27  ;; Prompt string used for input prompt.
 28  (prompt-str repl-prompt-str)
 29  ;; Whether the prompt should be shown or hidden.
 30  (prompt?
 31    ;;> Predicate which returns true if the prompt should be shown.
 32    repl-prompt?
 33
 34    ;;> Change prompt visibility, a truth value means the prompt is shown.
 35    repl-set-prompt!)
 36  ;; Parse stream used for the parser combinator.
 37  (stream repl-stream repl-stream-set!)
 38  ;; Last index in parse stream.
 39  (index repl-index repl-index-set!))
 40
 41;; Skip all buffered chunks, i.e. next read will block.
 42
 43(define (repl-skip-chunks! repl)
 44  (define (%repl-skip-chunks! source i)
 45    (if (>= (+ i 1) (vector-length (parse-stream-buffer source)))
 46      (%repl-skip-chunks! (parse-stream-tail source) i) ;; go to last chunck
 47      (values
 48        source
 49        ;; inc to go beyond last char.
 50        (inc (parse-stream-max-char source)))))
 51
 52  (let-values (((source i)
 53                (%repl-skip-chunks!
 54                  (repl-stream repl)
 55                  (repl-index repl))))
 56    (repl-state-set! repl source i)))
 57
 58(define (repl-parse repl f sk fk)
 59  (define (stream-next-line source idx)
 60    (let* ((next-index  (parse-stream-next-index source idx))
 61           (next-source (parse-stream-next-source source idx))
 62           (char        (parse-stream-ref source idx)))
 63      (if (or (eof-object? char) (char=? char #\newline))
 64        (cons next-source next-index) ;; first index after newline/eof
 65        (stream-next-line
 66          next-source
 67          next-index))))
 68
 69  (call-with-parse f
 70    (repl-stream repl)
 71    (repl-index repl)
 72    (lambda (r s i fk)
 73      (repl-state-set! repl s i)
 74      (sk (repl-line repl i) r))
 75    (lambda (s i reason)
 76      (let ((line (repl-line repl i))
 77            (next (stream-next-line (repl-stream repl) i)))
 78        (repl-state-set! repl (car next) (cdr next))
 79        (fk line reason)))))
 80
 81(define (repl-line repl index)
 82  (let ((s (repl-stream repl)))
 83    (inc ;; XXX: For some reason line start at zero.
 84      (+
 85        (parse-stream-line s)
 86        (car (parse-stream-count-lines s (parse-stream-max-char s)))))))
 87
 88;;> Start the REPL given by `repl`, and continuously parse input using
 89;;> the provided parser `f`. Successfully parsed input is passed to
 90;;> the success continuation `sk`, which receives the line number and
 91;;> parser result as procedure arguments. If the parser failed for the
 92;;> current input, the failure continuation `fk` is invoked. This
 93;;> continuation receives the line number and failure reason as
 94;;> procedure arguments. Lastly, an interrupt continuation must
 95;;> also be provided which is invoked on `SIGINT`. This continuation
 96;;> is not passed any arguments.
 97
 98(define (repl-run repl f sk fk ik)
 99  (when (repl-prompt? repl)
100    (display (repl-prompt-str repl))
101    (flush-output-port))
102
103  ;; Allow parsing itself (especially of input mode commands) to be
104  ;; interrupted by SIGINT signals. See "Asynchronous Events" in ed(1).
105  (call-with-current-continuation
106    (lambda (k)
107      (set-signal-handler!
108        signal/int
109        (lambda (signum)
110          (ik)
111          (repl-skip-chunks! repl)
112          (k #f)))
113
114        (begin
115          (repl-parse repl f sk fk)
116          (k #f))))
117
118  (repl-run repl f sk fk ik))
119
120;;> Run a parser interactively within the REPL. That is, deviate from
121;;> the standard REPL parser and instead parse the next input line
122;;> with the given parser `f`. On success, returns the result of `f`
123;;> otherwise, invokes the provided failure continuation `fk`.
124
125(define (repl-interactive repl f fk)
126  (repl-parse repl f (lambda (line value) value) fk))