KMRCLを眺める(234) repl.lisp — #:g1

Posted 2011-01-08 12:39:00 GMT

今回はKMRCLのrepl.lispまるごとです。
一つ一つの関数をばらして解説というのがちょっと難しそうなのと、それ程長くもない、ということでファイル全体を眺めます。
まず、名前からしてREPLを実現するファイルだろうなということは分かります。
とりあえず上からつらつらと眺めつつ実際に動かしてみます。

定数などの定義

(in-package #:kmrcl)

(defconstant +default-repl-server-port+ 4000)

デフォルトの接続ポートを4000番にしていますが、どうやら外部と通信できるようです。

REPLクラス

(defclass repl ()
  ((listener :initarg :listener :accessor listener
             :initform nil)))
REPLクラスを定義しています。

MAKE-REPL

(defun make-repl (&key (port +default-repl-server-port+)
                       announce user-checker remote-host-checker)
  (make-instance 'listener
    :port port
    :base-name "repl"
    :function 'repl-worker
    :function-args (list user-checker announce)
    :format :text
    :wait nil
    :remote-host-checker remote-host-checker
    :catch-errors nil))
MAKE-REPLというLISTNERのインスタンスを生成するユーティリティを定義していますが、LISTERクラスは、KMRCLのlistener.lispで定義されています。こちらもいつか眺めます。
LISTNERはどうやら通信できることを前提に設定されている様子。

INIT/REPL

(defun init/repl (repl state)
  (init/listener repl state))
INIT/REPLは名前の通りREPLを初期化するものだろうと思われます。
INIT/LISTENERもlistener.lispで定義されています。state引数が謎ですが、定義を辿ってみると、:start、:stop、:restartという引数を取り状態を遷移させるもののようです。

REPL-WORKER

(defun repl-worker (conn user-checker announce)
  (when announce
    (format conn "~A~%" announce)
    (force-output conn))
  (when user-checker
    (let (login password)
      (format conn "login: ")
      (finish-output conn)
      (setq login (read-socket-line conn))
      (format conn "password: ")
      (finish-output conn)
      (setq password (read-socket-line conn))
      (unless (funcall user-checker login password)
        (format conn "Invalid login~%")
        (finish-output conn)
        (return-from repl-worker))))
  #+allegro
  (tpl::start-interactive-top-level
   conn
   #'tpl::top-level-read-eval-print-loop
   nil)
  #-allegro
  (repl-on-stream conn)
  )
REPL-WORKERはLISTENERのFUNCTIONに登録されるものです。接続と、ユーザーチェック(パスワードの確認)の有無、接続時に表示させるアナウンスの内容を取り、一連の処理をした後、REPL-ON-STREAMを呼びます。

READ-SOCKET-LINE

(defun read-socket-line (stream)
  (string-right-trim-one-char #\return
                              (read-line stream nil nil)))
READ-SOCKET-LINEは、REPL-WORKERの中でユーザー名とパスワードを読み取るのに使われています。
STRING-RIGHT-TRIM-ONE-CHARはKMRCLのユーティリティ関数です。

PRINT-PROMPT

(defun print-prompt (stream)
  (format stream "~&~A> " (package-name *package*))
  (force-output stream))
名前の通りプロンプトを表示させるもの。パッケージも表示されるようです。

REPL-ON-STREAM

(defun repl-on-stream (stream)
  (let ((*standard-input* stream)
        (*standard-output* stream)
        (*terminal-io* stream)
        (*debug-io* stream))
    #|
    #+sbcl
    (if (and (find-package 'sb-aclrepl)
             (fboundp (intern "REPL-FUN" "SB-ACLREPL")))
        (sb-aclrepl::repl-fun)
        (%repl))
    #-sbcl
    |#
    (%repl)))
REPL-ON-STREAMは、*standard-input/output*等をストリームに束縛して%REPLを呼ぶというもの。
SBCLの場合は、SB-ACLREPL(SBCLで、Allegro CL風のREPLを実現するもの)を使おうとしたりしているようですが、コメントアウトされています。

%REPL

(defun %repl ()
  (loop
    (print-prompt *standard-output*)
    (let ((form (read *standard-input*)))
      (format *standard-output* "~&~S~%" (eval form)))))
%REPLが実質の本体で、Read-Eval-Print-Loopそのままに、read->eval->format->loopとなっています。

内容は以上ですが、実際に使ってみます。
(require :kmrcl)

(defvar *repl*)

;; REPLインスタンスを生成 (setq *repl* (kl:make-repl :announce "hello!" :port 4001 :user-checker (lambda (user pass) (find (cons user pass) '(("g000001" . "g000001")) :test #'equal)))) ;; 起動 (kl:init/repl *repl* :start)

telnetで接続
setq% rlwrap telnet localhost 4001
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
hello!
login: g000001
password: g000001

COMMON-LISP-USER> (+ 3 3 3 3 )

12 COMMON-LISP-USER>

という感じになります。

comments powered by Disqus