xyzzy や Emacs は、いろいろなモード (mode) を定義することができます。たとえば lisp-mode とか c-mode など、モードを使ったことがないユーザーはいないはずです。モードにはメジャーモードとマイナーモードの 2 種類があり、編集中にオン/オフすることができます。
メジャーモードは、特定のテキストを編集するために xyzzy をカスタマイズするものです。ベースとなるモードが基本 (Fundamental) モードです。このモードが xyzzy のデフォルトの動作となります。この基本モードにキーやオプションなどを再定義することで、カスタマイズすることができます。一時的にカスタマイズすることもできますが、ふつうはカスタマイズした環境を繰り返し利用することが多いでしょう。このとき、メージャーモードとして定義しておくと便利です。
メジャーモードの作成は、それほど難しい話ではありません。ただし、守らなければならない規則がいくつかあります。コマンド calc を例題に、どのような規則があるか調べてみましょう。calc は新しいバッファを作成して、そこにメジャーモードを設定するので、ふつうのメジャーモードとは異なる部分がありますが、モードの設定は同じです。
新しいモードに切り替えるコマンドを定義します。コマンドは引数無しで呼び出され、名前は -mode で終わるようにつけます。calc の場合は calc-mode です。
コマンドの最初で kill-all-local-variables を呼び出し、バッファローカルな変数を無効にします。Emacs Lisp リファレンスマニュアルによると、『それ以前に有効であったメジャーモードのバッファローカルな変数に対処するため』 とのことです。
変数 mode-name にモードの愛称を文字列でセットします。この名前がモード行に表示されます。calc の場合は "Calc" です。
変数 buffer-mode にモード名をセットします。これはカレントバッファのモードを格納する変数です。calc の場合はコマンド名 calc-mode をセットしています。
キーマップは *modename-mode-map* という名前のスペシャル変数に保持します。設定は use-keymap で行います。calc の場合は *calc-mode-map* です。
*modename-mode-hook* という名前のモードフックを用意し、コマンドの最後に run-hooks を用いてフックを実行します。calc の場合は *calc-mode-hook* です。
スペシャル変数は Common Lisp の用語で、ほかのプログラミング言語ではグローバル変数、大域変数、外部変数などと呼ばれています。Common Lisp の場合、スペシャル変数は * で囲んで表す習慣があります。
このほかにも、バッファローカルな変数を定義するなど必要な処理がありますが、大筋ではこんなところでしょう。この中で、あまりなじみがないのがフックだと思います。まずフックから説明します。
フックとは、既存のプログラムから特定の場面で呼び出される関数(ひとつでも複数でもよい)を格納した変数です。通常、多くのフックはノーマルフック (normal hook) と呼ばれ、これらの変数には引数無しで呼び出される関数のリストが格納されています。
各メジャーモードにはモードフックというノーマルフックがあり、モード関数は最後に run-hooks を用いてフックを実行するように作られています。ユーザーはフックに関数を登録することで、メジャーモードをカスタマイズすることができます。たとえば、デフォルトで設定したバッファローカルな変数を上書きし、モードの動作を変更することが可能です。
フックを登録する関数と実行する関数を表に示します。
| 関数名 | 機能 |
|---|---|
| add-hook hook func &optional append | フック変数 hook に関数 func を追加 |
| run-hooks &rest hook | 複数個のフック変数を受け取り、各フックを順番に実行する |
calc の場合、*calc-mode-hook* は nil に初期化されています。
それではモードを作ってみましょう。とりあえず、バッファを切り替えてモード Test を設定します。カスタマイズはまったく行っていません。プログラムは次のようになります。
| List 1 : モードのテスト |
|---|
; フック (defvar *test-mode-hook* nil) ; コマンド (defun test-mode () (interactive) (switch-to-buffer "*Test*") (kill-all-local-variables) (setq buffer-mode 'test-mode) (setq mode-name "Test") (use-keymap *test-mode-map*) (make-local-variable 'need-not-save) (setq need-not-save t) (make-local-variable 'auto-save) (setq auto-save nil) (run-hooks '*test-mode-hook*)) ; キーマップ (defvar *test-mode-map* nil) (unless *test-mode-map* (setq *test-mode-map* (make-sparse-keymap))) |
コマンド calc を参考にしました。switch-to-buffer を使ってバッファを *Test* に切り替えます。このバッファのモードが Test になります。ここで switch-to-buffer を呼び出さないと、新しいバッファは作成されません。この場合、カレントバッファがモード Test に切り替わります。
xyzzy ではキーワードファイルを使って、指定したキーワードに色をつけることができます。このとき、シンタックステーブル(構文テーブル)も設定する必要があります。シンタックステーブルは、Lisp のリーダ (read) が使用するものではなく、編集するテキストの構文を扱うためのものです。
たとえば、プログラミング言語によってコメントは異なりますね。C言語では /* から */ をコメントとして扱いますが、Perl では # から行末までです。また、単語の扱いにしても、プログラミング言語で異なります。これらの違いは、シンタックステーブルの設定を変更することで対応できます。
シンタックステーブルはバッファローカルな変数なので、メジャーモード、たとえば c-mode と perl-mode で別々のシンタックステーブルを使用することができます。また、シンタックステーブルによってコマンドの動作も変化します。簡単なコマンド ESC f (forward-word) さえも、シンタックステーブルに従って動作しています。
キーワードファイルは、xyzzy のサブディレクトリ etc に格納されています。テキスト形式のファイルなので、簡単に作成や修正を行うことができます。それでは簡単な例題として、Test モードを作成してキーワードを設定してみましょう。まず、グローバル変数を定義します。
| List 2 : グローバル変数の定義 |
|---|
; フック (defvar *test-mode-hook* nil) ; キーマップ (defvar *test-mode-map* nil) (unless *test-mode-map* (setq *test-mode-map* (make-sparse-keymap))) ; キーワード (defvar *test-keyword-hash-table* nil) (defvar *test-keyword-file* "Test") ; シンタックステーブル (defvar *test-mode-syntax-table* nil) (unless *test-mode-syntax-table* (setq *test-mode-syntax-table* (make-syntax-table))) |
キーワードファイルの名前は、*test-keyword-file* にセットします。キーワードファイルのリードは Test モードを設定するときに行い、その結果を *test-keyword-hash-table* に格納します。シンタックステーブルは、あとで詳しく説明します。Test モードのプログラムは次のようになります。
| List 3 : Test モード |
|---|
(defun test-mode ()
(interactive)
(kill-all-local-variables)
(setq buffer-mode 'test-mode)
(setq mode-name "Test")
(use-keymap *test-mode-map*)
(use-syntax-table *test-mode-syntax-table*)
; キーワードのロード
(and *test-keyword-file*
(null *test-keyword-hash-table*)
(setq *test-keyword-hash-table*
(load-keyword-file *test-keyword-file* t)))
(when *test-keyword-hash-table*
(make-local-variable 'keyword-hash-table)
(setq keyword-hash-table *test-keyword-hash-table*))
; フックの実行
(run-hooks '*test-mode-hook*))
|
シンタックステーブルの設定は関数 use-syntax-table を使います。これで、このバッファのシンタックステーブルが設定されます。キーワードファイルは関数 load-keyword-file で読み込みます。この関数はキーワードファイルを読み込み、ハッシュテーブルを作成して返します。キーワードを有効にするには、バッファローカルな変数 keyword-hash-table に、このハッシュテーブルをセットします。
シンタックステーブルにしてもキーワード用のハッシュテーブルにしても、Test モードを設定するたびに作成するのは無駄ですね。一度作成したらグローバル変数に格納しておいて、あとはそれを使って設定します。
これで、ファイル Test に書かれているキーワードが青色に変化します。ファイル名は test.l とします。このファイルを xyzzy のサブディレクトリsite-lisp に格納しておくと、コマンド load-library でロードすることができます。このとき、load-library には test と入力するだけでOKです。
モードの設定は M-x test-mode です。たとえば、キーワードが test だとすると、次のようになります。
test
test01
test()
test01 はひとつの単語として認識されるので、キーワードになりません。test() は、 ( の前まででひとつの単語として認識されるので、test はキーワードになります。これは、英大小文字と数字が単語(ワード)を構成する文字、( ) は区切り文字として認識されているからです。この設定を行うのがシンタックステーブルです。
シンタックステーブルを操作する主な関数を示します。
| 関数名 | 機能 |
|---|---|
| make-syntax-table | シンタックステーブルの作成 |
| make-syntax-symbol テーブル 文字 | シンボルに設定 |
| set-syntax-whitespace テーブル 文字 | 空白文字に設定 |
| set-syntax-junk テーブル 文字 | ジャンクに設定 |
| set-syntax-word テーブル 文字 | ワードに設定 |
| set-syntax-punctuation テーブル 文字 | 区切り文字に設定 |
make-syntax-table で作成されたシンタックステーブルは、次のように初期化されます。
| 文字 | 種別 |
|---|---|
| スペース、タブ、\r,\n,\f | 空白文字 |
| '0'-'9','A'-'Z','a'-'z' | ワード |
| ワード以外の 0x20 - 0x7e | 区切り文字 |
| 0x81 - 0x9f, 0x0e - 0xfc | 漢字 |
| 0xa1 - 0xdf | カナ |
| それ以外 | ジャンク |
これは、Test モードの動作結果と一致しますね。このほかにも、次のような構文を設定する関数が用意されています。
| 関数名 | 概要 |
|---|---|
| set-syntax-start-comment | コメントの開始文字 |
| set-syntax-end-comment | コメントの終了文字 |
| set-syntax-start-multi-comment | コメントの開始文字(複数文字) |
| set-syntax-end-multi-comment | コメントの終了文字(複数文字) |
| set-syntax-start-c++-comment | C++のコメント開始文字 |
| set-syntax-end-c++-comment | C++のコメント終了文字 |
| set-syntax-start-tag | タグの開始文字 |
| set-syntax-end-tag | タグの終了文字 |
| set-syntax-string | 文字列を表す文字 |
| set-syntax-escape | エスケープ文字 |
| set-syntax-match | ( ),{ },[ ] など数をマッチさせる文字 |
M.Hiroi は、これら関数の使い方をよく知りません。ほかのソース、たとえば c-mode.l や perl.l などを読んで、使い方を勉強してください。簡単な例として、文字 / から行末までをコメントとしてみましょう。シンタックステーブルの初期化で、次の関数を追加します。
| List 4 : コメントの設定 |
|---|
; シンタックステーブル (defvar *test-mode-syntax-table* nil) (unless *test-mode-syntax-table* (setq *test-mode-syntax-table* (make-syntax-table)) (set-syntax-start-comment *test-mode-syntax-table* #\/ t) (set-syntax-end-comment *test-mode-syntax-table* #\LFD t t)) |
これで / から行末までが緑色で表示されます。
キーワードの色は、キーワードファイル内で定義することができます。次のキーワードファイルを見てください。
| キーワードファイル |
|---|
;*0 test ;*1 test1 ;*2 test2 |
test はキーワード色 0 に、test1 は 1 に、test2 は 2 になります。キーワード色は、次のような意味があります。
| 番号 | 内容 |
|---|---|
| 0 | キーワード1 |
| 1 | キーワード2 |
| 2 | キーワード3 |
| 3 | キーワード1:キーワードだけ背景色つけ |
| 4 | キーワード2:キーワードだけ背景色つけ |
| 5 | キーワード3:キーワードだけ背景色つけ |
| 6 | キーワード1:行全体を背景色つけ |
| 7 | キーワード2:行全体を背景色つけ |
| 8 | キーワード3:行全体を背景色つけ |
| 9 | ボールド表示 |
| a-i | 0 - 8 と同じ色でボールド表示 |
なお、キーワードの色は xyzzy のメニュー「共通設定」で変更することができます。
メジャーモードを作ったならば、ファイルを読み込んだときに自動でモードを設定したいですね。これは、グローバル変数 *auto-mode-alist* に、拡張子とメジャーモードの対応をセットすることで実現できます。たとえば、拡張子 .test のファイルは、test-mode を設定するとしましょう。このような場合、ファイル .xyzzy に次のプログラムを追加します。
(load-library "test")
(push '("\\.test$" . test-mode) *auto-mode-alist*)
これで、xyzzy を立ち上げるとプログラム test がロードされ、拡張子とメジャーモードの対応づけが行われます。そして、拡張子が .test のファイルを読み込むと、自動的に test-mode が設定されます。
xyzzy Lisp で簡単なゲームを作ってみましょう。ゲームは定番であるマスターマインドにします。コンピュータ側は 0 から 9 までの中から重複しないように数字を 4 つ選びます。私たちは数字だけではなく、その位置も当てなくてはいけません。数字は合っているが位置が間違っている個数を cows で表し、数字も位置も合っている個数を bulls で表します。bulls が 4 になると正解というわけです。言葉で説明するとわかりにくいので、ゲームの進行状況を図にしてみました。
| 図 1 : マスターマインドの動作例 |
|---|
(6 2 8 1)
--------------------------------
1. (0 1 2 3) : cows 2 : bulls 0
2. (1 0 4 5) : cows 1 : bulls 0
3. (2 3 5 6) : cows 2 : bulls 0
4. (3 2 7 4) : cows 0 : bulls 1
5. (3 6 0 8) : cows 2 : bulls 0
6. (6 2 8 1) : cows 0 : bulls 4
|
数字の入力ですが、標準入力からではなくミニバッファを使うことにします。コマンドを実行するときに M-x を入力すると、コマンド名を聞いてきますね。このときのバッファがミニバッファです。コマンドを作成するときに interactive で指定した引数は、このミニバッファから入力することができます。また、ミニバッファはコマンドからだけではなく、プログラムの中からも使うことができます。ミニバッファからデータを受け取る主な関数を示します。
| 関数名 | 機能概要 |
|---|---|
| read-integer prompt | ミニバッファに prompt を表示してユーザーの入力を数値として返す |
| read-string prompt | ミニバッファに prompt を表示してユーザーの入力を文字列として返す |
| y-or-n-p message | ミニバッファに message を表示してユーザーが y か n を入力するのを待つ y なら t を、n なら nil を返す |
4 つの数字を入力するのに 4 回ミニバッファを開くのも面倒なので、数字を 4 つまとめて入力してもらいましょう。数字は空白で区切ることにします。4 つの数字は read-string を使って文字列として受け取ります。そうすると、文字列から 4 つの数字を取り出す処理が必要になります。データの読み取りには関数 read を使うと簡単です。Lisp の文法に従って、データを変換してくれます。
ただし、read には入力ストリームを与えなければいけません。文字列から読み取ることはできないのです。そこで、Common Lisp に用意されている、文字列を文字列ストリームに変換する関数を使います。
make-string-input-stream string &optional start end
この関数は入力ストリームを返します。入力ストリームは start と end によって区切られる部分文字列の中の文字を順番に与えます。省略した場合は文字列全体がストリーム内のデータとなります。
この関数を使えば文字列から数字を簡単に取り出すことができます。入力データから数字を取り出してチェックするプログラムは次のようになります。
| List 5 : 4 つの数字をチェック |
|---|
(defun check-four-numbers (numbers)
(let ((in (make-string-input-stream numbers))
number-list num)
; 文字列ストリームから数値をリード
(while (setq num (read in nil))
(cond
((or (not (integerp num)) (not (<= 0 num 9)))
(print "0 - 9 までの数値を入力してください")
(setq number-list nil)
(return))
((member num number-list)
(print "同じ数字がありますよ")
(setq number-list nil)
(return))
(t
(push num number-list))))
(close in)
(when (and number-list (/= (length number-list) 4))
(print "数字は4つ入力してください")
(setq number-list nil))
; 結果を返す
number-list))
|
文字列から数字を取り出してリストに格納します。データに間違いがある場合はリストを空にして、while ループから脱出します。このとき check-four-numbers の返り値は nil となります。注意点は関数 read の引数です。
read &optional input-stream eof-error-p eof-value recursive-p
read はファイルの終わりに達するとエラーを発生させます。ところが、eof-error-p に nil を指定すると、エラーとはせずに eof-value の評価結果を返します。eof-value を省略した場合は nil を返します。
このプログラムでは、文字列ストリームが空になったら read が nil を返すことで、while ループを終了するようになっています。そのあとで数字の個数をチェックします。
ミニバッファから数字を入力するプログラムは次のようになります。
| List 6 : 4 つの数字を入力 |
|---|
(defun input-four-numbers ()
(let (input)
(loop
; ミニバッファから4つの数字を入力
(setq input (check-four-numbers (read-string "Input 4 Numbers > ")))
(if input
(return input)))))
|
read-string を実行するとミニバッファが開かれ、ユーザーからのデータ入力を待ちます。read-string は文字列を返すので、それを check-four-numbers へ渡してチェックします。返り値が nil でなければデータは正常に入力されたので、return で loop から脱出します。
大きな修正はこんなところでしょう。あとはリストを見てください。ダウンロードファイルは用意していないので、プログラムはコピーアンドペーストでファイルに格納してください。あとは、load-file でファイルを読み込み、*scratch* で (master) を実行してください。とりあえずゲームをすることはできますが、使い勝手はまだまだでしょう。ぼちぼちと改良していく予定です。
;
; master.vl : マスターマインド for xyzzy Lisp
;
; Copyright (C) 2000 Makoto Hiroi
;
;
; 数字を決める
;
(defun make-collect ()
(let (collect num)
(loop
(setq num (random 10))
(if (and (not (member num collect))
(= 4 (length (push num collect))))
(return collect)))))
;
; 数字のチェック
;
(defun check-four-numbers (numbers)
(let ((in (make-string-input-stream numbers))
number-list num)
; 文字列ストリームから数値をリード
(while (setq num (read in nil))
(cond
((or (not (integerp num)) (not (<= 0 num 9)))
(print "0 - 9 までの数値を入力してください")
(setq number-list nil)
(return))
((member num number-list)
(print "同じ数字がありますよ")
(setq number-list nil)
(return))
(t
(push num number-list))))
(close in)
(when (and number-list (/= (length number-list) 4))
(print "数字は4つ入力してください")
(setq number-list nil))
; 結果を返す
number-list))
;
; 4つの数字を入力
;
(defun input-four-numbers ()
(let (input)
(loop
; ミニバッファから4つの数字を入力
(setq input (check-four-numbers (read-string "Input 4 Numbers > ")))
(if input
(return input)))))
;
; bulls を求める(再帰版)
;
(defun count-bulls (collect input)
(cond ((atom collect) 0)
((= (car collect) (car input))
(1+ (count-bulls (cdr collect) (cdr input))))
(t (count-bulls (cdr collect) (cdr input)))))
;
; 同じ数字を数える(再帰版)
;
(defun count-same-number (collect input)
(cond ((atom collect) 0)
((member (car collect) input)
(1+ (count-same-number (cdr collect) input)))
(t (count-same-number (cdr collect) input))))
;
; ゲームオーバー
;
(defun game-over (collect)
(format t "残念無念、正解は ~A でした~%" (reverse collect)))
;
; ゲーム本体
;
(defun master ()
(print "***** MasterMind *****")
(print "0 から 9 までの異なる4つの数字を当ててください")
(terpri)
(let ((collect (make-collect)) data bulls)
(dotimes (count 10 (game-over collect))
(setq data (input-four-numbers)
bulls (count-bulls collect data))
(when (= bulls 4)
(format t "~A ***** おめでとう!正解です *****~%" (reverse collect))
(return))
; 表示する
(format t "~A 回数 ~D : cows ~D : bulls ~D~%"
(reverse data)
(1+ count)
(- (count-same-number collect data) bulls)
bulls))))
今度は、メジャーモードを使ってマスターマインドを改良してみましょう。少しは使いやすくなるかもしれません。
今まで、マスターマインドは *scratch* で実行しましたが、M-x master-mind で実行できるようにします。最初にモードを設定しましょう。これは難しいことではありません。
| List 7 : モードの設定 |
|---|
; フック (defvar *master-mode-hook* nil) ; キーマップの設定 (defvar *master-mode-map* nil) (unless *master-mode-map* (setq *master-mode-map* (make-sparse-keymap))) ; モードの設定 (defun master-mode () (interactive) (switch-to-buffer "*Master*") (kill-all-local-variables) (setq buffer-mode 'maste-mode) (setq mode-name "Master") (use-keymap *master-mode-map*) (make-local-variable 'need-not-save) (setq need-not-save t) (make-local-variable 'auto-save) (setq auto-save nil) (run-hooks '*master-mode-hook*)) ; ゲームの実行 (defun master-mind () (interactive) (master-mode) (master)) |
ゲームは master-mind で実行します。ここから master-mode を呼び出します。master-mode では、switch-to-buffer でバッファ *Master* を作成し、モード Master を設定します。やっていることは、モードの説明で作成したテストプログラムと同じです。
マスターマインド用のバッファ *Master* を作成したので、出力は標準出力ではなく *Master* へ行う必要があります。今までのプログラムは format と print を使っていましたが、出力用に新しい関数 print-message を作成することにします。
format は結果を文字列として出力することできます。出力結果を print-message へ渡すこともできますが、ここは print-message を可変個の引数を受け取る関数として定義し、print-message から format を呼び出すことにします。プログラムは次のようになります。
| List 8 : メッセージを出力 |
|---|
(defun print-message (message &rest args) (with-output-to-selected-buffer (apply #'format 't message args) (terpri))) |
このプログラムのポイントは関数 apply です。
apply func &rest more-args
apply は、最初の引数 func を残りの引数に適用して、その結果を返します。最後の引数はリストでなければいけません。簡単な使用例を示しましょう。
(apply #'+ '(1 2 3)) 6 (apply #'+ 4 5 6 '(1 2 3)) 21
#'+ は (function +) の省略形です。function は特殊形式で、シンボルに格納されている関数を取り出す働きをします。#'+ であれば、シンボル + から加算を行う機能を取り出します。つまり、apply にはシンボル + に格納されている値ではなく、関数を渡しているのです。
正確にいうと、function は関数だけではなく、それを実行する環境もまとめて取り出します。これをクロージャ (closure) というのですが、少々難しい話になるので、ここでは説明しません。興味のある方は Common Lisp 入門 クロージャ をお読みください。
関数をデータと同じように扱うことができるのは、Lisp などの関数型言語では当然の機能です。apply のように、関数を引数として受け取る関数を「汎関数 (functional) 」と呼ぶことがあります。Lisp の世界では、汎関数は特別なものではなく、便利な汎関数がいろいろと用意されています。詳しい説明は Common Lisp 入門 汎関数とラムダ式 をお読みください。
あとは、format や print でメッセージを出力している個所を、print-message に変更するだけです。これで完成です。ファイル名は master.l です。M-x load-file で master.l をロードし、M-x master-mind で実行します。ゲームはコマンドとして実装しているため、ゲームの最中にほかの操作を行うことはできません。まあ、短時間で終わるゲームなので、とりあえずこのまま公開します。中断するときは、ほかのコマンドと同様に CTRL-g を入力してください。
ダウンロード(1,383 byte)
またまた、マスターマインドの改造です。今回は当てる数字の桁数を 3, 4, 5 の中から選べるようにします。また、ギブアップの機能を追加しましょう。
桁数はゲームを実行するときに入力してもらいましょう。これは interactive で引数を指定すればいいですね。次のプログラムを見てください。
| List 9 : ゲームの起動 |
|---|
(defun master-mind (digit)
(interactive "n桁数(3 - 5) > ")
(if (or (< digit 3) (< 5 digit))
(error "桁数の範囲に誤りがあります"))
(master-mode)
(master digit))
|
interactive の引数には n を指定したので、数値以外のデータは入力できません。入力されたデータは、数値に変換されて引数 digit に格納されます。あとは数値の範囲をチェックします。範囲外の場合は関数 error を呼び出します。この関数が実行されるとエラー用のダイアログが表示されて、コマンドの実行は中断されます。
関数 master は、入力された桁数を受け取ってゲームを実行します。
| List 10 : ゲームの実行 |
|---|
(defun master (digit)
(goto-char (point-max))
(print-message "***** MasterMind *****")
(print-message "0 から 9 までの異なる ~D つの数字を当ててください" digit)
(let ((collect (make-collect digit)))
(if (catch 'giveup (play digit collect))
(print-message
"ほほほ、修行が足りませんね。正解は ~A でした"
(reverse collect)))))
|
make-collect は、入力された桁数の問題を作成します。この修正は簡単なので説明は省略します。ゲームの本体は関数 play に移しました。
ギブアップの処理は、catch と throw を使って大域脱出で実現します。詳しい説明は Common Lisp 入門 ちょっと特殊な制御構造 をお読みください。catch で大域脱出先のラベル giveup を定義します。そして、入力されたデータのチェック処理で throw を使います。次のプログラムを見てください。
| List 11 : 数字のチェック |
|---|
(defun check-numbers (numbers digit)
(let ((in (make-string-input-stream numbers))
number-list num)
; 文字列ストリームから数値をリード
(while (setq num (read in nil))
(cond
((eq 'giveup num) (throw 'giveup t)) ; 大域脱出
((or (not (integerp num)) (not (<= 0 num 9)))
(print-message "0 - 9 までの数値を入力してください")
(setq number-list nil)
(return))
((member num number-list)
(print-message "同じ数字がありますよ")
(setq number-list nil)
(return))
(t (push num number-list))))
(close in)
(when (and number-list (/= (length number-list) digit))
(print-message "数字は ~D つ入力してください" digit)
(setq number-list nil))
; 結果を返す
number-list))
|
入力されたデータが giveup ならば、throw を呼び出して catch へジャンプします。この場合、引数 t の評価結果が t になるので catch の評価結果が t となり、関数 master ではギブアップしたときのメッセージが表示されます。
あと細かな修正がありますが、ソースファイルを見てください。ファイル名は master.l です。M-x load-file で master.l をロードし、M-x master-mind で実行します。
ダウンロード(1,627 byte)