mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-04-30 11:50:28 +02:00
Modified-by: Mark H Weaver <mhw@netris.org> * module/system/repl/coop-server.scm: New module. * module/system/repl/repl.scm (start-repl): Extract body to start-repl*. (start-repl*): New procedure. (run-repl): Extract body to run-repl*. (run-repl*): New procedure. * module/system/repl/server.scm (run-server): Extract body to run-server*. (run-server*): New procedure. * doc/ref/api-evaluation.texi (Cooperative REPL Servers): New node. * module/Makefile.am (SYSTEM_SOURCES): Add system/repl/coop-server.scm.
233 lines
7.7 KiB
Scheme
233 lines
7.7 KiB
Scheme
;;; Read-Eval-Print Loop
|
||
|
||
;; Copyright (C) 2001, 2009, 2010, 2011, 2013,
|
||
;; 2014 Free Software Foundation, Inc.
|
||
|
||
;; This library is free software; you can redistribute it and/or
|
||
;; modify it under the terms of the GNU Lesser General Public
|
||
;; License as published by the Free Software Foundation; either
|
||
;; version 3 of the License, or (at your option) any later version.
|
||
;;
|
||
;; This library is distributed in the hope that it will be useful,
|
||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
;; Lesser General Public License for more details.
|
||
;;
|
||
;; You should have received a copy of the GNU Lesser General Public
|
||
;; License along with this library; if not, write to the Free Software
|
||
;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||
;; 02110-1301 USA
|
||
|
||
;;; Code:
|
||
|
||
(define-module (system repl repl)
|
||
#:use-module (system base syntax)
|
||
#:use-module (system base pmatch)
|
||
#:use-module (system base compile)
|
||
#:use-module (system base language)
|
||
#:use-module (system vm vm)
|
||
#:use-module (system repl error-handling)
|
||
#:use-module (system repl common)
|
||
#:use-module (system repl command)
|
||
#:use-module (ice-9 control)
|
||
#:export (start-repl run-repl))
|
||
|
||
|
||
;;;
|
||
;;; Comments
|
||
;;;
|
||
;;; (You don't want a comment to force a continuation line.)
|
||
;;;
|
||
|
||
(define (read-scheme-line-comment port)
|
||
(let lp ()
|
||
(let ((ch (read-char port)))
|
||
(or (eof-object? ch)
|
||
(eqv? ch #\newline)
|
||
(lp)))))
|
||
|
||
(define (read-scheme-datum-comment port)
|
||
(read port))
|
||
|
||
;; ch is a peeked char
|
||
(define (read-comment lang port ch)
|
||
(and (eq? (language-name lang) 'scheme)
|
||
(case ch
|
||
((#\;)
|
||
(read-char port)
|
||
(read-scheme-line-comment port)
|
||
#t)
|
||
((#\#)
|
||
(read-char port)
|
||
(case (peek-char port)
|
||
((#\;)
|
||
(read-char port)
|
||
(read-scheme-datum-comment port)
|
||
#t)
|
||
;; Not doing R6RS block comments because of the possibility
|
||
;; of read-hash extensions. Lame excuse. Not doing scsh
|
||
;; block comments either, because I don't feel like handling
|
||
;; #!r6rs.
|
||
(else
|
||
(unread-char #\# port)
|
||
#f)))
|
||
(else
|
||
#f))))
|
||
|
||
|
||
|
||
;;;
|
||
;;; Meta commands
|
||
;;;
|
||
|
||
(define meta-command-token (cons 'meta 'command))
|
||
|
||
(define (meta-reader lang env)
|
||
(lambda* (#:optional (port (current-input-port)))
|
||
(with-input-from-port port
|
||
(lambda ()
|
||
(let ((ch (flush-leading-whitespace)))
|
||
(cond ((eof-object? ch)
|
||
(read-char)) ; consume the EOF and return it
|
||
((eqv? ch #\,)
|
||
(read-char)
|
||
meta-command-token)
|
||
((read-comment lang port ch)
|
||
*unspecified*)
|
||
(else ((language-reader lang) port env))))))))
|
||
|
||
(define (flush-all-input)
|
||
(if (and (char-ready?)
|
||
(not (eof-object? (peek-char))))
|
||
(begin
|
||
(read-char)
|
||
(flush-all-input))))
|
||
|
||
;; repl-reader is a function defined in boot-9.scm, and is replaced by
|
||
;; something else if readline has been activated. much of this hoopla is
|
||
;; to be able to re-use the existing readline machinery.
|
||
;;
|
||
;; Catches read errors, returning *unspecified* in that case.
|
||
;;
|
||
;; Note: although not exported, this is used by (system repl coop-server)
|
||
(define (prompting-meta-read repl)
|
||
(catch #t
|
||
(lambda ()
|
||
(repl-reader (lambda () (repl-prompt repl))
|
||
(meta-reader (repl-language repl) (current-module))))
|
||
(lambda (key . args)
|
||
(case key
|
||
((quit)
|
||
(apply throw key args))
|
||
(else
|
||
(format (current-output-port) "While reading expression:\n")
|
||
(print-exception (current-output-port) #f key args)
|
||
(flush-all-input)
|
||
*unspecified*)))))
|
||
|
||
|
||
|
||
;;;
|
||
;;; The repl
|
||
;;;
|
||
|
||
(define* (start-repl #:optional (lang (current-language)) #:key debug)
|
||
(start-repl* lang debug prompting-meta-read))
|
||
|
||
;; Note: although not exported, this is used by (system repl coop-server)
|
||
(define (start-repl* lang debug prompting-meta-read)
|
||
;; ,language at the REPL will update the current-language. Make
|
||
;; sure that it does so in a new dynamic scope.
|
||
(parameterize ((current-language lang))
|
||
(run-repl* (make-repl lang debug) prompting-meta-read)))
|
||
|
||
;; (put 'abort-on-error 'scheme-indent-function 1)
|
||
(define-syntax-rule (abort-on-error string exp)
|
||
(catch #t
|
||
(lambda () exp)
|
||
(lambda (key . args)
|
||
(format #t "While ~A:~%" string)
|
||
(print-exception (current-output-port) #f key args)
|
||
(abort))))
|
||
|
||
(define (run-repl repl)
|
||
(run-repl* repl prompting-meta-read))
|
||
|
||
(define (run-repl* repl prompting-meta-read)
|
||
(define (with-stack-and-prompt thunk)
|
||
(call-with-prompt (default-prompt-tag)
|
||
(lambda () (start-stack #t (thunk)))
|
||
(lambda (k proc)
|
||
(with-stack-and-prompt (lambda () (proc k))))))
|
||
|
||
(% (with-fluids ((*repl-stack*
|
||
(cons repl (or (fluid-ref *repl-stack*) '()))))
|
||
(if (null? (cdr (fluid-ref *repl-stack*)))
|
||
(repl-welcome repl))
|
||
(let prompt-loop ()
|
||
(let ((exp (prompting-meta-read repl)))
|
||
(cond
|
||
((eqv? exp *unspecified*)) ; read error or comment, pass
|
||
((eq? exp meta-command-token)
|
||
(catch #t
|
||
(lambda ()
|
||
(meta-command repl))
|
||
(lambda (k . args)
|
||
(if (eq? k 'quit)
|
||
(abort args)
|
||
(begin
|
||
(format #t "While executing meta-command:~%")
|
||
(print-exception (current-output-port) #f k args))))))
|
||
((eof-object? exp)
|
||
(newline)
|
||
(abort '()))
|
||
(else
|
||
;; since the input port is line-buffered, consume up to the
|
||
;; newline
|
||
(flush-to-newline)
|
||
(call-with-error-handling
|
||
(lambda ()
|
||
(catch 'quit
|
||
(lambda ()
|
||
(call-with-values
|
||
(lambda ()
|
||
(% (let ((thunk
|
||
(abort-on-error "compiling expression"
|
||
(repl-prepare-eval-thunk
|
||
repl
|
||
(abort-on-error "parsing expression"
|
||
(repl-parse repl exp))))))
|
||
(run-hook before-eval-hook exp)
|
||
(call-with-error-handling
|
||
(lambda ()
|
||
(with-stack-and-prompt thunk))
|
||
#:on-error (repl-option-ref repl 'on-error)))
|
||
(lambda (k) (values))))
|
||
(lambda l
|
||
(for-each (lambda (v)
|
||
(repl-print repl v))
|
||
l))))
|
||
(lambda (k . args)
|
||
(abort args))))
|
||
#:on-error (repl-option-ref repl 'on-error)
|
||
#:trap-handler 'disabled)))
|
||
(flush-to-newline) ;; consume trailing whitespace
|
||
(prompt-loop))))
|
||
(lambda (k status)
|
||
status)))
|
||
|
||
;; Returns first non-whitespace char.
|
||
(define (flush-leading-whitespace)
|
||
(let ((ch (peek-char)))
|
||
(cond ((eof-object? ch) ch)
|
||
((char-whitespace? ch) (read-char) (flush-leading-whitespace))
|
||
(else ch))))
|
||
|
||
(define (flush-to-newline)
|
||
(if (char-ready?)
|
||
(let ((ch (peek-char)))
|
||
(if (and (not (eof-object? ch)) (char-whitespace? ch))
|
||
(begin
|
||
(read-char)
|
||
(if (not (char=? ch #\newline))
|
||
(flush-to-newline)))))))
|