error って。(3)

そもそも、なんで例外なんてものがあるかというと、実行時におかしなことが起きても継続してプログラムを実行させたいからだ。
たとえば、ユーザーの入力が原因で「予期せぬエラーが発生しました」とか出て強制終了したりするプログラムは、誰も使いたがらないだろう。
ユーザーは、自分の入力が原因でおかしなことになっても強制終了せずに、最後まで処理を続けて欲しいと思うに違いない。

じゃあちょっと書いてみる。数当てゲームとかどうだろう。

(defun kazuate ()
  (interactive)
  (let ((qas '(("10 ÷ □ = 2" . 5) ("10 ÷ □ = 5" . 2) ("10 ÷ □ = 10" . 1)))
        (correct 0))
    (insert (format nil "\n~D問出します。\n" (length qas)))
    (dolist (e qas)
      (insert (concat "  "(first e) "\t□はいくつ?\t" ))
      (let ((ans (read-string "")))
        (when (= (parse-integer ans) (cdr e))
          (incf correct))
        (insert (concat ans "\n"))))
    (insert (format nil "正解は ~D問中 ~D問でした。\n" (length qas) correct)))
  (values))

; test
(kazuate)

3問出します。
  10 ÷ □ = 2	□はいくつ?	5
  10 ÷ □ = 5	□はいくつ?	2
  10 ÷ □ = 10	□はいくつ?	8
正解は 3問中 2問でした。

ここで、ユーザが間違えて数字以外を入力すると、

(kazuate)

3問出します。
  10 ÷ □ = 2	□はいくつ?	; q を入力した
不正な数値の形式です: "q"       ; エラーが出て中断してしまう

エラーを吐いて中断してしまう。
これは、答えを判断している部分の parse-integer の引数に数字以外が与えられたので、整数に型変換できなきないよというエラーだ。
じゃあ、このエラーを捕まえることで中断しないようにしてみる。

(defun kazuate ()
  (interactive)
  (let ((qas '(("10 ÷ □ = 2" . 5) ("10 ÷ □ = 5" . 2) ("10 ÷ □ = 10" . 1)))
        (correct 0))
    (insert (format nil "\n~D問出します。\n" (length qas)))
    (dolist (e qas)
      (insert (concat "  "(first e) "\t□はいくつ?\t" ))
      (let ((ans (read-string "")))
        (handler-case                                   ; これを追加
            (when (= (parse-integer ans) (cdr e))
              (incf correct))
          (error (c) (msgbox "数字じゃないです")))      ; これを追加
        (insert (concat ans "\n"))))
    (insert (format nil "正解は ~D問中 ~D問でした。\n" (length qas) correct)))
  (values))

; test
(kazuate)

3問出します。
  10 ÷ □ = 2	□はいくつ?	q    ; 数字以外を入力したけど
  10 ÷ □ = 5	□はいくつ?	3    ; 中断せずに、最後まで実行した
  10 ÷ □ = 10	□はいくつ?	1
正解は 3問中 1問でした。

これで中断せずに、最後まで実行できた。
でも、「数字以外を入力したときは、次の問題に行かずにもう一度キー入力を待つ」というのをやるには、どう書いたらいいんだろうか。
なぞが深まる。