Land of Lisp 6章メモ

6章メモ

5章のゲームにコマンドラインインターフェースを追加する。

コンソールへの表示

  • print : 表示位置が行頭でなければ改行して値を出力して空白を出力
  • prin1 : 単に表示

入出力いろいろ

printreadLispの値をそのまま入出力できる。 すなわち文字列なら"で囲われて、文字なら#\がついて出力され、入力するときにはこれらをつける。

読みやすい形で出力するにはprincを使う。 また、文字列として入力するにはread-lineを使う。 読みやすい形への整形は単射ではない

> (princ "10")
10
> (princ 10)
10

ので、読みやすい形で入力された値をいつも適切な型で受け取るprinc逆関数は原理的に無理。

eval

データとしてのLispコードを実行する関数。

REPL

Read Eval Print Loop

(defun repl ()
  (loop (print (eval (read)))))

なんというそのまま。素敵。

Read,Eval,PrintをカスタマイズすることでオリジナルのREPLを作れる。

ゲーム用REPL

  • Read : 括弧と引数のクオートをつけることで入力を楽に
  • Eval : ゲームのコマンド以外を弾く事で安全に
  • Print : 内部データを整形して表示する

ここでのPrintは基本的な変換しかしていないが、内部データを扱いやすい形で持ち、 適切に変換して表示する構造は多くのプログラムで使える。
要はView。

作成したREPLの安全性

Readで引数をクオートして、Evalでゲームのコマンド以外の関数を弾いてるので基本的には任意のコードは実行できないが、 Readで組み込みのreadを使っているのでリーダマクロを使われると安全ではないらしい。

Source

5章で出力文を1文ごとのリストのリストとしているのでそれに合わせてgame-printも変えている。

;ゲーム用のREPLを作成する
(defun game-repl ()
  (let ((cmd (game-read)))
    (unless (eq (car cmd) 'quit)
      (game-print (game-eval cmd))
      (game-repl))))

;括弧とクオートを付けなくてもコマンドを呼べるようにする
(defun game-read ()
  (let ((cmd (read-from-string 
               (concatenate 'string "(" (read-line) ")"))))
    (flet ((quote-it (x)
             (list 'quote x)))
      (cons (car cmd) (mapcar #'quote-it (cdr cmd))))))

;ゲームのコマンド以外を受け付けない
(defparameter *allowed-commands* '(look walk pickup inventory))

(defun game-eval (sexp)
  (if (member (car sexp) *allowed-commands*)
      (eval sexp)
      '((i do not know that command.))))

;1文毎のリストに構造を改変してるのでgame-printも改変
(defun game-statement-print (statement)
  (labels ((proper (fn elem)
             (if (stringp elem)
                 elem
                 (funcall fn (prin1-to-string elem))))
           (capitalize (str)
             (coerce (let ((lst (coerce str 'list)))
                       (cons (char-upcase (car lst)) (mapcar #'char-downcase (cdr lst))))
                     'string))
           (proper-capitalize (elem)
             (proper #'capitalize elem))
           (proper-downcase (elem)
             (proper #'string-downcase elem)))
    (string-trim "()"
                 (princ-to-string (cons (proper-capitalize (car statement))
                                        (mapcar #'proper-downcase (cdr statement)))))))

(defun game-print (statements)
  (princ (string-trim "()" (princ-to-string (mapcar #'game-statement-print statements))))
  (fresh-line))