2004/02/01
Phase 8.2.0
華和梨開発チーム :
NAKAUE.T (Meister), 偽Meister (夢乃), さとー, 酔狂, さくらのにえ
1. はじめに
2. 辞書ファイル
2.1. コメント
2.2. セキュリティレベル設定
2.3. 暗号化辞書
3. エントリ定義
3.1. 文と文字列
3.2. クォート文字列
3.3. ブロック
3.4. 複数行対応
4. 置換とエントリ呼び出しと実行
4.1. エントリ呼び出し
4.2. 実行
4.3. 置換の入れ子
5. エントリ呼び出しのバリエーション
5.1. 集合演算式
5.2. エントリ配列呼び出し
5.3. 履歴参照(中級)
5.4. 一時エントリ(上級)
6. 演算式
6.1. 比較演算
6.2. 論理演算
6.3. ビット演算
6.4. 数値と文字列の扱い
7. インラインスクリプト(中級)
7.1. 構文コマンドと関数コマンド
7.2. if
7.3. 組み込みコマンドとユーザ定義コマンド
8. SHIORI/SAORIインターフェース
8.1. コールバック
8.2. その他のシステムエントリ
8.3. 栞としての華和梨
8.4. SAORIモジュールとしての華和梨
8.5. SAORIモジュールの使用宣言
8.6 SAORIモジュール呼び出し
9. 応用編
9.1. イベント反応
9.2. コミュニケート
9.3. 自発トーク
ユーザーズマニュアルでは、華和梨のほぼ全ての文法と機能を紹介します。 これとKISリファレンスで、 一通りの機能が使えるようになります。
初めて華和梨を使う方は、 ユーザーズマニュアルより先にGetting Started を読み、イメージを掴んでください。
何もないところからゴーストを作るのは大変手間がかかります。 簡単に華和梨の動作を確認していくためには、 コンソールUI版アプリケーション『幸水』 を使ってください。幸水なしの華和梨修得は、まず考えられません。
辞書ファイルとは、 華和梨に対して、文章や語彙や動作パターンを指示するためのファイルです。 辞書ファイルが、あなたのゴーストを記述する全てです。
華和梨が最初に読む辞書ファイルは"kawarirc.kis"です。
辞書ファイルは、'='で始まる行によって、 辞書定義ゾーンと、スクリプト記述ゾーンの二つのゾーンに分かれます。
辞書定義ゾーンは辞書定義を書く領域であり、 スクリプト記述ゾーンはKISを直接書き、その場で実行する領域です。
初期状態(辞書ファイルを読み始めた状態)では辞書定義ゾーンで、 =kisで始まる行から =endで始まる行までがスクリプト記述ゾーンです。
スクリプト記述ゾーンにおける文法は、 '$(' 〜 ')' の間の文法と完全に同一ですので、ここでは省略します。 スクリプト記述ゾーンで書いたスクリプトは、 そのスクリプトが読み込まれた瞬間に実行される ということだけ注意してください。
辞書定義ゾーンにおける文法は3章以下で詳しく述べます。
行頭、空白記号以外の最初の文字がシャープ'#' だった場合、その行全体はコメントと見なされ、無視されます。
また、':rem'のみの行から ':endrem'のみの行までの領域全体もコメントと見なされ、 無視されます。
両者とも、後述の全ての文法に優先して処理されます。 複数行に分けた記述の場合、行の先頭の文字に'#' が来ないように注意してください。 その場合はクォート文字列にするなどして逃げることができます。
「何か。」から送られてくるイベントの中には、 ローカルマシン(ゴーストの動作しているコンピュータ)以外の場所から SSTPなどを通じて送られてくるものがあります。 このようなイベントを排除するために 以下の記述によってセキュリティレベルを設定することが出来ます。
何も書かなかった場合、自動的に安全な設定(high)になるので、 普通は記述する必要はありません。
記述する際には必ず初期化時に読み込まれるファイルに書いて下さい。 kawarirc.kisに書くのが確実です。 ゴーストが動作し始めてからはセキュリティレベルを変更することはできません。
0 / low | 全てのイベントを許可する。 |
1 / middle | 外部からやってきたイベントを禁止。 |
2 / high | 当面は1(middle)と同じ。 |
3 / ultrahigh | 明確にローカルマシン発行と記されたイベントのみ許可。 |
なお、華和梨の動作中、 セキュリティレベルは数値表現(0〜3)で "System.SecurityLevel"に格納されています。 書き換えることは出来ません。
ネタをよりばれにくくするために、辞書ファイルに簡単な暗号化を施すことができます。 暗号化には付属のkawari_encode2.exeを用います。 使用方法は、DOSプロンプトで以下のように入力します。
そうすると、キーワードを聞かれるので、適当なキーワードを入れてください。 暗号化されたファイルが作成されます。
辞書ファイルのファイル名の拡張子を「.kaw」に変えたファイルが、 暗号化された辞書ファイルです。 辞書ファイル中、 ":crypt"と書かれた行から、 ":endcrypt"と書かれた行までが暗号化の対象となります。 それ以外の行は元のまま残されます。
このファイルを暗号化すると、次のようなファイルが生成されます。 (見やすさのために一部行を折り返してあります)
生成された暗号化辞書ファイル「dict-*.kaw」を、kawarirc.kisで指定して下さい。 暗号化ファイルの拡張子は、.kaw以外に変更しても大丈夫です。
暗号化したファイルを元に戻したい場合は、 kawari_decode2.exeを使います。
やはりキーワードを聞かれるので、 暗号化の時に入力したキーワードを入れてください。
すると、拡張子が「.txt」の、復号化された辞書ファイルが作成されます。
注意:すでに同名のファイルがあっても上書きされてしまいます!
華和梨は、全てのデータを「エントリ」に分類して保持しています。 辞書定義とは、エントリ定義行を沢山並べたものです。 エントリ定義は以下の書式('['〜']'は、無くても良い部分)で書いてください。
Phase 8では、以前「単語」と呼んでいたものを、 「文」と「単語」に区別していますが、 主に細かい文法定義上の問題ですので気にしなくて結構です。
コロン":"やカンマ","の 前後には自由に空白を入れられます。
エントリを複数並べる形式は、同じ文を複数のエントリに登録する場合に使って下さい。 '('〜')'で囲う後者の形式は、 文を何行にも分けて並べたい場合に使ってください。 '('〜')'の間は自由に改行できます。 これはその中に幾つもの文を並べられる点で、 後述の「ブロック」とは異なるので注意してください。 分かりづらければ使わなくても構いません。
エントリ名に使える文字は以下です。 強調してあるものは、新たに使えるようになったものです。
ただし、アットマーク「@」 は一時エントリに用いるので、普段は使わないでください。 さらに、エントリ名の先頭に「.」は使えません。 また、エントリ名中に「.」が連続した場合、 一つにまとめられます(npw.....special → npw.special)。 'System.'で始まるエントリは、 華和梨が特別に使うエントリ名ですので勝手に使ってしまわないように注意してください。
以上の決まり事を守る限り、エントリ名は自由に決められます。 自分にとって分かりやすい名前にしてください。 いわゆる「全角文字」が自由に使えるので、積極的に使うと良いでしょう (タイプは少し面倒かもしれませんが……)。
文は、「文字列」「置換」「ブロック」を好きなだけ並べたものです。 置換とブロックは後述します。
文字列は、例えば以下のようなものです。
ほとんどの文章は、このように地の文としてそのまま書くことができます。 しかし、ごく少数(以前よりも減っています)ながら、使えない文字があります。
まず、以下の記号は、必ず使えません。 後述のブロックの中では、これ以外の制限はありません。
エントリ定義文にベタに書いている場合、 さらにカンマ','が使えません。 また、インラインスクリプト中にベタに書いた場合、 セミコロン';'が使えません。 それぞれ、その場所で文を区切るために使われるため、使えないことは感覚的に分かります。 実質的に気を付けなければならないのは最初に挙げたものだけです。
また、文の前後、及び行の先頭末尾にあるベタの空白文字 (半角スペース、タブ、改行可能な場所では改行)は無視されます。 こうした場所に空白を書くには、次のクォートを使ってください。
前述の使えない文字や文頭文末の空白を文字列として華和梨の文に含めるには、 クォート文字列を使います。
上のように、 ダブルクォート(")もしくは、 (シングル)クォート(') で囲まれた文字列を「クォート文字列」と呼びます。 クォート文字列では、ほぼ全ての文字を、「書いたまま」の形で出力できます。
クォート文字列では出力できない文字はありません。 が、記述上、ちょっと変わった書き方をしないといけないことが2つだけあります。
さて、では「ダブルクォートを出力する」にはどうすればいいでしょうか。
このようにするのが一つの答えです。もう一つは:
いずれも、色が変わっている部分がクォート文字列です。
もう一つ。閉じクォート直前に\マークを出力するには:
文中、'(' 〜 ')'で囲まれた部分を 「ブロック」と呼びます。 '$( ' 〜 ')'は、 後述のインラインスクリプトですので、間違えないでください。
ブロックの効果は2つだけです。
空白文字は通常通り無視されます。 置換のルールなども全て他の場所と同じです。 また、ブロック用の括弧'(', ')'は、 もちろん出力には現れません。
ブロックは、主に複数行に分けて文を書きたいときに使われます。
ブロックを使って複数行記述をしていても、 エントリ定義の最後には必ず改行が必要なことに注意してください。
上記は、こうも書けます。
しかし、これは間違いです。
なぜなら、「呪文 : 」のところで、 エントリ定義文が終わってしまっているからです。
「開き括弧に対応する閉じ括弧が来ない間は改行がいくらあってもよい」 というのが、Phase 8の複数行対応の考え方です。 ブロック、エントリ呼び出し、エントリ配列呼び出しの添え字部、 演算式、インラインスクリプトなど、 括弧に囲まれた場所すべてにおいて、 文法的に空白が許される場所ならばどこにでも改行が入れられるようになりました。 そうした場所では改行は空白文字(スペースやタブと同じ)と見なされます。
また、括弧書きのエントリ定義(「エントリ名 '(' ')'」形式)により、 エントリ定義中に全く自由に複数行記述ができるようになりました。
最も重要な章です。 疑問が湧くたびにここに立ち戻り、繰り返し読んで理解してください。
上記までで、エントリが呼び出されたとき、 どんな文字列でも出力できるようになりました。 しかし、華和梨が真に華和梨たる知的動作を行うためには、 次に述べる置換機能を欠かすことができません。
華和梨は4つの置換機能を持っています。 そのいずれもが、'$'で始まり、 括弧記号などで決められる特定の範囲を持っています。
これらの詳細については順番に詳しく解説します。 ただ、いずれの機能についても、 置換部('$'で始まる記述)を、その実行結果で置き換える という一点は共通です。 中には実行結果が無いものもあります。 そうしたものは、単に置換記述が見えなくなるだけ(空文字列で置き換えた)となります。
また、置換はその文が実行されるたびに起きます。 ですから、特にランダムな動作をするエントリ呼び出しなどは、 文を実行するたびに違った結果を返します。
エントリに登録した文を呼び出すことを「エントリ呼び出し」と言います。 例えば、「人名」という名前のエントリを呼び出すには、以下のように書きます。
エントリ呼び出しのすることは2つです。
2番目の「実行する」ことが重要なので、覚えてください。 場合によって「評価する」「評価される」などとも呼ばれます。
次節に続きます。
華和梨辞書でエントリに登録するときの文は、 決してそのまま「何か。」などに送られる形ではありません。
上の例は、${なまえ}、${しょくぎょう}、${ともだち} のそれぞれが正しい文字列に置き換わることを意図しています。
このような辞書があった場合、先ほどの例を実行すると正しい文字列になるでしょう。
つまり、
「実行」=「置換を実際に行うこと」
と覚えてください。 華和梨を使い込んでいくと、 置換が行われるタイミングを知ることがどうしても必要となります。 込み入った文を書いて混乱してしまったとき、 「実行(あるいは評価)」が行われるのがいつなのか、 それに注意するようにしてください。
このルールを知れば、クォート文字列の扱いと置換が異なることも理解できると思います。 クォート文字列における「エスケープ」(\"など)は、 実行されるたびに結果が変わる必要はありません。 そこで、読み込まれた段階で既にエスケープの処理は行われています。
さて、それ自体が置換機能であるエントリ呼び出しが、 選び出した文に対してさらに実行を行うため、 必然的にエントリ呼び出しは何度も行われることになります。
ここで、${sentence}を実行します。
このように、エントリ呼び出しは、置換子が無くなるまで全ての置換を実行します。
置換は、お互いに入れ子状にすることができます。 置換の種類は問いません。入れ子の深さにも制限はありません。
入れ子になった置換は必ず内側から置換されます。 例えば:
あるいは
という具合です。
Phase 7ユーザは、 以前はできなかった(entry/evalコマンドで実現していた) ${ ${ } } という入れ子ができるようになっていることに注意してください。
なお、以下の部分だけは例外的に置換にできません。注意してください。
例えば以下はエラーです。
エントリ呼び出しの中身('${'〜'}'の間)には、 以下のようなことも書けます。
一行目は「「作家」エントリと「女性」エントリの両方に入っている文の中から一つを選ぶ」 (もちろん選んだ後に実行します)、 二行目は「「ゴースト」エントリと「植物」エントリの両方に入っている文の中から一つを選ぶ」、 同様に、 三行目は「「男性」エントリと「女性」エントリのどちらか」、 四行目は「「ゴースト」エントリに入っていて「友達」エントリに入っていない」 文から一つを選びます。
これらは幾らでも並べられます。
優先度について:
数式では'*'や'/'が、 '+'や'-'よりも 「優先」されます。 例えば
など。
同様に華和梨の集合演算式では'&'が、 他の2つの演算子よりも「優先」されます。
と書くと、「『男性』、もしくは『女性かつ作家』」が選ばれます。 これを「男性か女性、かつ作家」に変えるには、数式と同様、こう書きます。
なお、ジャンル分けをやりやすくするために、 ${エントリ名}のみの文は、 その先のエントリの持つ文まで候補に入れます。
エントリに登録された文の内、ある特定の位置の文を選ぶ時に使います。
上記のように「'$' + エントリ名 + '[' + 演算式 + ']'」 という形式になります。 演算式については後述します。
エントリ配列呼び出しを実行すると、 指定エントリの、指定番目(これをインデックスと言います)の文を選択し、実行します。 インデックスは0から数え始めます。 普通「一番目」と呼ぶものは、「0」番目ですので気を付けてください。 また、インデックスに負の値を入れた場合は、後ろから数え始めます。
指定番目の文が存在しなかった場合の結果は空文字列("")です。
以下、幸水の表記で動作を示します。
「${数値}」 という特殊なエントリ呼び出しを履歴参照と呼びます。 履歴参照は、同じ文脈で行われた置換結果を再度参照するときに使います。
上記の例ですと、sentenceの実行結果は「石のような梨、梨のような石。」になります。
この数値もやはり0から数え始めます。 また、負の数値を入れると後ろから数え始めます。
履歴参照は少し特別扱いなので、 集合演算に使う(${0 & entry}など)ことはできません。 ${ ${エントリ } }のような書き方で、 内部のエントリ呼び出し結果が数値でも、履歴参照にはなりません。
ここからはPhase 7.3.1以前との違いです。
まず、全ての置換記述が履歴参照によって参照可能になりました。 つまり、エントリ呼び出し、エントリ配列呼び出し、演算式、インラインスクリプトのことです。 分かりやすく言えば、「全部の'$'を参照可能」です。
では、次の場合はどうでしょうか。
${1}は、 $(echo 「${n}」)の中の${n} を参照してしまわないのでしょうか? もしくは、実行順序としては${n} の方が先のように思えますから、 ${n}が0番目で、 $(echo 「${n}」)が1番目でしょうか。
答えはどちらでもありません。 履歴参照においては、'$'の中は関知しません。 よって、 $(echo 「${n}」)が0番目で、 $(echo 「${food}」)が1番目です。
かと言って、 '$(' 〜 ')'の中(あるいはエントリ集合演算式などの中)では、 履歴参照は使えないという意味ではありません。 もちろん使えます。そして従来通り、括弧の外の以前の置換履歴も参照可能です。
しかし、スクリプトの外から参照するときは、スクリプト全体しか見えません。
見かけは全く異なりますが、これは履歴参照とほぼ同じ機能です。
一時エントリとは、華和梨が普段持っている辞書とは別に、 あるエントリ中の一つの文を実行している間のみ存在する一時的な辞書 に登録されるエントリです。 エントリ名の先頭にアットマーク'@'が付くのが特徴です。
この辞書は、文の実行(置換作業ですね)が始まると同時に、その文専用に一つ作成され、 終わると同時に削除されます。
一時エントリは辞書定義時には存在しない(何も実行されていないのですから当然です)ため、 通常のエントリ定義で文を登録することができません。 従って、一時エントリは常にスクリプトによって作られることになります。
上記では、文の実行が始まった瞬間に一時辞書(中身無し)が設定され、 最初のスクリプトで@名前エントリに (通常辞書の)名前エントリを呼び出した結果の文字列が入ります。 それ以降、@名前エントリは、 この文の実行が終了するまで残ります (もちろん、それ以前にスクリプトによって消去することは可能です)。
履歴参照が、自動的に設定される過去の置換履歴を参照するという単機能であるのに比べ、 一時エントリは自由に設定・変更・呼び出しができ、 集合演算にも使えます。
他のエントリに対して履歴参照できないのと同様、 他のエントリの一時辞書を参照することはできません。
まず「@名前」一時エントリに、 「うなぎ」という文字列をセットしてから、 さきほどの「質問」エントリを呼び出していますが、 これも無意味です。 呼び出し元に対して履歴参照できないのと同様、 呼び出し元の一時辞書を参照することはできません。
履歴参照と違う点は、 同じ文の中でありさえすれば、スクリプトや演算式の中だろうと外だろうと、 場所に関係なく同じ一時辞書にアクセスできるところです。 スクリプト中で操作を行った結果をスクリプト外で受け取ることも勿論できます。 実際、上に挙げた例でも既に行っています。
一時エントリがもっとも活用されるのは、 KISのユーザ定義関数においてでしょう。 以前は「関数的機能を持つエントリ」を呼び出す場合、 そのエントリに渡すべき値(引数)を特別に用意した(しかし通常辞書の一部である)エントリにセットしてから呼び出すのが通例でした。 しかし、この形式では問題があります:
Phase 8のユーザ定義関数では、 華和梨システムによって、引数は自動的に呼び出された関数側の一時エントリ@argにセットされますので、 安心して引数を使うことができます。 複数のユーザ定義関数が互いを何度も呼び合っても、 その引数のエントリが上書きされたり、過去の引数が残っていたりする危険はありません。
なお、この機能は無理に使わなくても構いません。 全てのエントリ名が互いにぶつからないように自分で管理できていて、 なおかつfunctionによる関数定義を使わない場合は、一時エントリを使う必要はありません。 エントリ呼び出しを関数代わりに使う従来の手法を踏襲する場合などです。
$[ 〜 ]で囲まれた領域を「演算式」と呼びます。 演算式では、整数演算、ビット単位演算、論理演算、整数比較、文字列比較が行えます。
使用できる演算子は以下になります。 結合優先度が高いもの順です。 優先度の定義は集合演算の章のものと同じです。
記号 | 数値 | 文字列 | 動作 | 例 |
** | ○ | 累乗 | $[10**2] => 100 | |
- | ○ | 単項マイナス | $[-10] => -10 | |
+ | ○ | 単項プラス | $[+10] => 10 | |
! | ○ | ○ | (単項)NOT | $[!1] => false, $[!"hoge"] => false, $[!""] => true |
~ | ○ | (単項)補数 | $[~-10] => 9 | |
* | ○ | 乗算 | $[10*"2"] => 20, $["string"*10] => 0 | |
/ | ○ | 除算 | $[10/2] => 5, $[10/0] => "" (エラーログ:"devided by 0") | |
% | ○ | 剰余算 | $[10%3] => 1 | |
+ | ○ | 加算 | $[-10+2] => -8, $[""+1] => 1 | |
- | ○ | 減算 | $[10-3] => 7 | |
& | ○ | ビットAND | $[1&2] => 0 | |
| | ○ | ビットOR | $[1|2] => 3 | |
^ | ○ | ビットXOR | $[1^2] => 3 | |
> | ○ | より大きい | $[10>10] => false | |
>= | ○ | 以上 | $[10>=10] => true | |
< | ○ | 未満 | $[10<10] => false | |
<= | ○ | 以下 | $[10<=10] => true | |
== | ○ | ○ | 等しい | $["string"=="string"] => true, $[10==8] => false |
!= | ○ | ○ | 等しくない | $["mac"!="mcdonalds"] => true |
=~ | ○ | マッチ | $["substring"=~"string"] => true | |
!~ | ○ | 非マッチ | $["substring"!~"string"] => false | |
&& | ○ | ○ | 論理AND | $["str"&&10] => "str", $["false"&&10] => false, $[0&&10] => false |
|| | ○ | ○ | 論理OR | $["str"||0] => "str", $["false"||10] => 10 |
幾つかの、四則演算以外の演算について、非常にいい加減な説明をします。 ここにある演算形式は全て既存の(プログラミング言語としては)一般的な概念ですので、 正しい説明はその手の教科書をご覧下さい。
比較演算('>', '>=', '<', '<=', '==', '!=', '=~', '!~')は、 「その記述が正しいか否か」を確認するものだと思えばよいでしょう。 結果として、必ず真偽値を返します。 例えば:
これは明らかに間違っています。 間違っていることを専門用語で「偽」と言います。 反対に正しい状態であることは「真」と言います。 偽の場合は文字列"false"が返ります。 真の場合は文字列"true"が返ります。
少し先走りますが、 この「正しいか間違っているか」を利用して、 スクリプトで条件分岐することができます。 華和梨の真偽判断の基準は、論理演算子、および、 if, while, untilなどの構文コマンド全てにおいて統一されています。
では、ディスプレイの幅(screen.widthエントリに格納されているとします) が1200を越えていたら「広いディスプレイ」 エントリを呼ぶようにしてみます。
論理演算('!', '&&', '||')は、 真偽値を使った演算です。
'!'は、「ではない」とでも言えるもので、 右側の値の逆の値を返します。 右側の値が真であれば偽、偽であれば真を返します。
'&&'は、「且つ」つまり、 「〜〜〜 且つ 〜〜〜」です。 右側の値と左側の値が真の時のみ、真、 そうでなければ偽を返します。 必ず、すべての要素を評価します。 ただし真を返す場合は、"true"ではなく、 並列に並べられた'&&'の、一番左側の値をそのまま返します。
'||'は、「または」つまり、 「〜〜〜 または 〜〜〜」です。 両側の値のどちらかが真の時、真、そうでなければ偽を返します。 最初に真の値が出現した時点で評価を終了します。 ただし真を返す場合は、"true"ではなく、 並列に並べられた'||'の左側から順にテストして、 最初に真となった値をそのまま返します。
ビット演算('&', '|', '^')は、 数値を32bit値として扱う演算です。 通常はまず使わないでしょう。
演算式では数値として扱えるものは必ず数値として扱う という規則があります。 この結果、次のような事態が起きます。
このような現象を避けたい(必ず文字列として比較したい)場合は、 KISのcompareコマンドを用いてください。
エントリ呼び出しとは別に、 '$(' 〜 ')'でいくつかの文を囲った部分を、 「インラインスクリプト」と言います。 また、=kisのみの行と、 =endのみの行で囲った部分も、 同様に「インラインスクリプト」と言います。 例えば、日付情報を返すdateコマンドを使うには、次のように書きます。
インラインスクリプトは、 エントリ呼び出しの「実行する」機能を、 より強化したものと考えて下さい。 エントリ呼び出しと同様、インラインスクリプトを呼ぶと、 インラインスクリプトは実行結果に置き換わります。
エントリ呼び出しと違うのは、 上の例で言うと「date」等のコマンド名の後に、 空白を挟んで「%n」などの文がある点です。 コマンド名はエントリ呼び出しのエントリ名に相当し、 どの機能を呼ぶかを決めます。この機能を「コマンド」と呼びます。
一方、空白以降の文は、 コマンドを呼ぶ際に、補助的情報としてコマンドに渡されます。 この補助的情報を「引数」と言います。 引数はコマンドの許す限り、空白で区切って幾つでも並べることが出来ます。
引数の中に、エントリ呼び出しやインラインスクリプトがあった場合を考えます。
コマンドが実行される時、エントリ呼び出しやインラインスクリプトは、 それぞれの実行結果に置き換わったものが引数となり、コマンドに渡ります。 上の例では、$(date %m%d)はその日の日付、 例えば「0522」に置き換わり、setコマンドは、
と書いたのと同じ状態で実行されます。 引数の中のインラインスクリプトの引数も、 さらにエントリ呼び出し、インラインスクリプトを含む場合も有り得ます。 この場合、考え方はエントリ呼び出しと同じです。 一番内側の括弧から順番に、置換子がなくなるまで置換を実行します。 そして、その結果が引数としてコマンドに渡ります。
しかし、幾つかのコマンドでは、この引数の置換タイミングが違います。 具体的には、if、while、foreachなど、 プログラムの流れを司るコマンドと、 function、returnコマンド等です。
これらのコマンドは「構文コマンド」又は単に「構文」と呼びます。 構文コマンドは、その引数を使うときにエントリ呼び出し等を置換し、 使わない引数は置換しないという性質があります。 具体的な例を挙げます。
この例の場合、$[ ${a} == "Y" ]の結果に応じて、 $(set answer Yes)、 もしくは$(set answer No)のどちらか一方だけ、 実行(=置換)されます。
構文コマンド以外のコマンドは、 「関数コマンド」又は単に「コマンド」と呼びます。
構文コマンドのうち、ifはPhase 7.3.1と比べ、 特に文法が変わりました。 elseとelse ifの、 2つのキーワードを新たに導入しました。
従来のifは、 連続した条件分岐で入れ子のifを使う必要がありました。 これは括弧の対応を間違いやすいだけではなく、 間違いを発見しにくいものでした。 多くの場合、 入れ子のifを、 別のエントリに記述する等の対策が必要でした。 ただし、こうした入れ子をエントリに分解する方法は、 条件を追加・削除する際に厄介です。
新しいifでは、こうした問題が起きにくくなっています。 複数行記述と併せ、一つの処理は一つのエントリの中で完結します。 メンテナンスが容易になるでしょう。
エントリと違い、いくつかのコマンドは、ユーザが定義しなくても実行できます。 このようなコマンドを、「組み込みコマンド」と言います。 一方、functionコマンドを使いユーザが定義したコマンドを、 「ユーザ定義コマンド」と言います。 ユーザ定義コマンドは、一度定義すれば、華和梨が起動している間有効です。 なお、ユーザ定義コマンドは必ず関数コマンドになります。
コマンド定義中では、 @arg一時エントリを引数として使います。 第1引数は$@arg[1]、 第2引数は$@arg[2]、 以降第N引数は$@arg[N]で参照できます。 $@arg[0]は定義中のコマンド名となります。 以下にコマンド定義の例を示します。
次に、既存の組み込みコマンドと同じ名前で、 ユーザ定義コマンドを定義した場合を考えます。
この例の場合、sizeコマンドは、 ユーザ定義コマンド版sizeに上書きされます。 必ず組み込みコマンドを呼びたい場合、 $(.size entry)のように、 コマンド名の先頭に「.」を付けて呼んで下さい。
また、ユーザ定義コマンドをもう一度定義すると、 後に定義した方が呼ばれます。
上の例では、defalutコマンドを3回呼んでいます。 しかし、毎回直前で定義しなおしているため、3回とも違う結果になります。
ここまでに、ゴーストの動作記述方法については、ほぼ全て解説しました。 が、肝心の 「ダブルクリックイベントに対応するには?」 「おすすめURLを表示するには?」 などの説明を一切しませんでした。
栞としての機能については、この章でまとめて説明します。 また、華和梨はSAORIとしても機能しますので、それについても併せて説明します。
華和梨が情報のやり取りのため、 特別扱いするエントリを「システムエントリ」と呼びます。 このうち、幾つかのエントリは呼び出す際の仕組みが、 他のエントリとまったく違います。 この節では、このシステムエントリの中でも特異な、 「コールバックエントリ」を説明します。
コールバックエントリとは、本体がイベント、NOTIFYを通知してきた際、 最初に評価するエントリです。 通常のエントリを評価する場合、エントリ中の文を一つランダムに選び、 その文の評価結果をエントリの評価結果とします。 一方、コールバックエントリが本体から呼ばれた場合、 コールバックエントリ中のすべての文を添え字順に評価し、 すべての評価結果を結合したものを本体に返します。 仮に、System.Callback.OnGETエントリが、 次のような内容だったとします。
もしこのコールバックエントリが本体から呼ばれたとすると、 本体に返すスクリプトは次のようになります。
この結果は、 KISコマンドのgetを使い、 次のように書いた結果と等価です。 コールバックエントリは、 コールバックエントリをgetで評価した結果を本体に返すと考えて下さい。
このコールバックエントリの動作は、 主にミドルウェアの記述を簡素化する際に有効でしょう。 ミドルウェアはOnSecondChangeイベント等で、 多数の独立した機能を動作させることがあります。 従来、この独立動作する機能を追加したい場合、 必然的にミドルウェアを書き換える必要がありました。 しかし、今回からはSystem.Callback.*エントリに、 追加機能を呼ぶ文を追加するだけで大丈夫です。
次に、華和梨の使う全コールバックエントリを示します。
SHIORI/3.0 | |
System.Callback.OnGET | GET |
System.Callback.OnNOTIFY | NOTIFY |
SHIORI/2.x | |
System.Callback.OnEvent | SHIORI/2.2 イベント応答、GET Sentenceのみ |
System.Callback.OnGetSentence | SHIORI/2.3b コミュニケート |
System.Callback.OnGetStatus | ステータス取得 |
System.Callback.OnResource | SHIORI/2.5リソース取得 |
SAORI/1.0 | |
System.Callback.OnSaoriExecute | SAORIモジュールとして呼ばれた |
共通 | |
System.Callback.OnUnload | 切り離しイベント(華和梨が発行) |
System.Callback.OnRequest | その他全てのリクエスト(NOTIFY SHIORI/2.x、TRANSLATE SHIORI/2.x等) |
このうちSystem.Callback.OnRequestは、 少し変わっているので解説します。 このコールバックエントリは、他のコールバックエントリに該当しなかった、 全ての本体からのコールで呼ばれます。 具体的にはNOTIFY SHIORI/2.x、TRANSLATE SHIORI/2.6、 TEACH SHIORI/2.4等が該当します。 あまり使用しない呼び出しや、将来の本体仕様変更に備えたエントリです。
どのような呼び出しが来たかを知るためには、 System.Requestエントリを参照します。 このエントリに、本体のコール種類を示す文字列である、 「TEACH」や「NOTIFY OtherGhostName」等が格納されます。 また、詳しくは次の節で解説しますが、 本体が渡したヘッダは、 System.Request.*エントリ群に格納されます。 また、本体への応答ヘッダは、 System.Response.*エントリ群に書き込みます。 処理状態を示すステータスコードは、 System.Responseエントリに書き込みます。 どのようなヘッダが来るか、どのようなヘッダを返すか、 どのようなステータスコードを返すかについては、 本体仕様書を参照して下さい。
一例として、TEACH SHIORI/2.4を簡易的に処理するスクリプトを示します。 TEACH SHIORI/2.4は、Wordヘッダに教えた単語が来ます。 応答の際は、Sentenceヘッダに書きます。 処理が成功したら、ステータスコードとして200を発行します。 これをスクリプトにすると、次のようになります。
コールバックエントリ以外にも、幾つかシステムエントリが存在します。 次に一覧を示します。
本体からの通知情報 | |
System.Request.* | リクエストヘッダ |
本体への応答 | |
System.Response.* | レスポンスヘッダ |
System.Response.To | SHIORI/2.3b 話しかけたいゴースト名。"stop"で打ち切り。 |
System.Response | SHIORI/2.0 ステータスコード |
その他(リードオンリー) | |
System.DataPath | shiori.dllの存在するディレクトリ |
System.SecurityLevel | セキュリティレベル |
特に重要なのは、 System.Request.*のリクエストヘッダエントリ群です。 このエントリ群は、SHIORI/2.x、SHIORI/3.0、SAORI/1.0において、 本体が送ってきたヘッダに対応します。 具体例で説明すると、 例えば「Reference0: まゆら」というヘッダが来た場合、 System.Request.Reference0エントリに、 「まゆら」という単語をセットすることになります。
これとは反対に、 System.Response.*エントリ群は、 本体に返すレスポンスヘッダに対応します。 具体的には、 例えばSystem.Response.Reference0エントリに、 「さくら」という単語をセットしたと考えます。 すると本体に返すヘッダに、 「Reference0: さくら」というヘッダが追加されます。 また、System.Responseエントリにセットされた単語は、 本体に返すステータスコードになります。
リクエストヘッダエントリ、レスポンスヘッダエントリ群は、 本体から呼ばれてコールバックエントリを評価する直前に、 一度完全に内容を消します。
栞サブシステムの仕事は一見複雑ですが、要約すれば、 「本体の通知してきたIDから、相応しい応答を割り出して本体に返す」ことです。 華和梨Phase 8はPhase 7までと比べると、 こうした栞の仕事を、使用者により「生のまま」見せています。
代表的な例はイベント応答です。 華和梨Phase 8はイベント応答、リソース文字列の要求、 NOTIFY処理の呼び分けを、KISを使って書く必要があります。 本体が通知してきたID(=イベント名、リソース名)は、 System.Request.IDエントリに入っています。 これを使って呼び分けます。
華和梨Phase 7.3.1と同じ名前でイベントエントリ、 リソース文字列エントリを使いたい場合、 System.Callback.OnGETエントリに次のように書きます。
この例では、例えばマウスのダブルクリックイベントが来た時、 event.OnMouseDoubleClickエントリを呼びます。 また、例えばさくら側の「おすすめURL」の要求があった場合、 ${resource.sakura.recommendsites}の評価結果を返します。
しかし、SHIORI/3.0ではイベントとリソース文字列要求は、 本体からの通知形式に差がありません。 そこで、エントリ名を従来から変更する代わりに、 記述を簡素化することが出来ます。 この場合、次のように書きます。
この例では、例えばマウスのダブルクリックイベントが来た時、 reply.OnMouseDoubleClickエントリを呼びます。 また、例えばさくら側の「おすすめURL」の要求があった場合、 ${reply.sakura.recommendsites}の評価結果を返します。
最後に、NOTIFYの処理の呼び分け触れます。 NOTIFYの形式はイベント・リソース文字列要求のGETの場合と、 ほとんど差がありません。
例えば他に起動中のゴーストの名前がNOTIFYされた場合、 notify.otherghostnameエントリを呼びます。
ミドルウェアを使わずに華和梨を使う場合、こうした記述が必ず必要です。 しかし、多くのミドルウェアでは、 こうした低レベル(よりプログラムに密着した)の記述が、 既にパッケージ化されています。 何らかの事情が無い限り、こうしたミドルウェアの使用をおすすめします。
華和梨はSHIORI規格の準AIモジュールですが、 同時にSAORI規格モジュールでもあります。 SAORIモジュールとして使うと、
といったメリットがあります。
華和梨をSAORIとして使うとき、次の3つのことに注意します。
華和梨がSAORIとして呼ばれた時、 System.Callback.OnSaoriExecuteエントリを評価します。 他のコールバックエントリと同様、所属する全ての単語を評価します。 この時、SAORIモジュールに与えられた引数は、 System.Request.*以下、 System.Request.Argument0、 System.Request.Argument1 等のエントリに存在します。
引数が何個あるか等を知りたい場合、listtreeコマンドを使い、
として、argumentsエントリを調べると知ることが出来ます。
戻り値を返したい時はコミュニケートと同様、 System.Response.*エントリ群を使います。 SAORI規格に従って、戻り値は、 System.Response.Resultに書き込みます。 以下、 System.Response.Value0、 System.Response.Value1、 System.Response.Value2 等のエントリに補助情報を書き込みます。
戻り値を返すには、実はこれだけでは不十分です。 System.Resoposeエントリに、 ステータスを書き込む必要があります。 このステータスコードを見て、 SAORIモジュールを呼び出した側は処理の成功/失敗を判断する為、 必須情報です。 主なステータスコードは次の通りです。
200 | 引数を正しく理解し、戻り値を書き込んだ |
204 | 引数は正しく理解したが、戻り値がない |
400(省略時デフォルト) | 理解できない引数が来た |
なお、 System.Callback.OnSaoriExecuteの評価結果は、 ステータスと無関係です。 この点が他のコールバックエントリと違いますので、注意が必要です
SAORIモジュールを使用する際は、 「これからこのSAORIを使う」という使用宣言の記述が必要です。 この記述を受けて、 華和梨はSAORIモジュールを読み込んだり、読み込む準備をします。
華和梨Phase 8以前では、 このSAORI使用宣言はkawari.iniに記述しました。 しかし、Phase 8からkawari.iniは廃止になり、 使用宣言はKISで記述することになりました。
SAORIモジュール使用宣言は、 saoriregistコマンドを使います。
こうして使用宣言をすると、華和梨からSAORIモジュールを呼び出すことが出来ます。
SAORIモジュールを呼び出す時は、 callsaoriコマンド、 callsaorixコマンドを使います。 callsaoriコマンド、 callsaorixコマンドは、 先に定義した「エイリアス」でSAORIモジュールを呼びます。 仮に、music.dllというSAORIモジュールを、 「音楽」というエイリアスで使用宣言したとします。
このSAORIモジュールの機能が指定した音楽ファイルの再生だとしたら、 再生する音楽ファイル名を指定するのが普通でしょう。 この場合、SAORIモジュールに再生するファイル名を伝える必要があります。 話の都合上、music.dllは
という仕様だとします。
このmusic.dllで、 「technopolis.mid」という音楽ファイルを再生したい場合、
と書きます。 callsaoriコマンドのエイリアスより後ろの引数は、 SAORIモジュールに引数として渡します。
callsaorixコマンドは、 SAORIモジュールが戻り値以外に、 様々な情報を送ってくるタイプの場合に使用します。 仮に、先ほどのmusic.dllが「play」を指示した場合、 次のような情報を送ってくるとします。
では、callsaorixを使って、 音楽ファイル「Truth21c.mid」を再生します。 エイリアスはcallsaoriコマンド、 callsaorixコマンドに共通で使えます。
ここで、エイリアスのすぐ後ろの「music.info」は、 SAORIモジュールが送ってきた情報を記録する際の、 基準となるエントリの名前です。このエントリの名前は自由に決められます。 上の例の場合、次のようなエントリに情報を書き込みます。
sizeはValueが何個あるかを示します。
callsaori、 callsaorixはともに戻り値を返す関数コマンドです。 戻り値はSAORIモジュールの返したResultヘッダです。
上記を踏まえ、ゴーストの基本動作を実装する際、 華和梨Phase 8ではどのように記述するのか、 幾つか例を交えて紹介します。
イベント通知の際、本体からのReferenceヘッダを華和梨から参照するには、 ${System.Request.Reference0}など、 非常に長いエントリ名を記述する必要があります。 使ってみると、これは不便です。 そこで、コマンドを作って記述を簡単にする場合を考えます。
コマンドを定義する際の注意ですが、 スクリプト記述ゾーンで定義して下さい。 辞書記述ゾーンのエントリ定義中でコマンドを定義した場合、 コマンドの定義は「そのエントリが呼び出されて」初めて機能します。 多く場合、コマンドは未定義も同然となります。
では、実際に定義してみます。
この例では、同じReference番号のReferenceヘッダが複数来る場合を想定し、 getで0番目のRefernce?を参照しています。
他のゴーストに話し掛ける時は、 Reference0に話し掛けたいゴースト名をセットして、 トークを返します。 ReferenceN(Nは0以上の整数)を返すには、 System.Response.ReferenceNエントリに単語をセットします。 なお、System.Responseで始まるエントリは、 コールバックごとに毎回内容が消去されます。
こうして話し掛けられたゴーストには、 OnCommunicateがやってきます。 SHIORI/3.0から、コミュニケートはイベントの一種になりました。
自分から話し掛けるときと同様、 話し掛けるゴースト名をReference0に設定して下さい。
なお、コミュニケート用コマンドは、 ユーザ定義コマンドで使いやすいようラッピングするのが得策ですが、 ここでは一番素直に書いた場合を例示します。
コミュニケートの基本的書き方は、次のようになります。
以上をまとめた例を挙げます。
自発的にトークをするには、 OnSecondChangeイベントを使うのが一般的です。 ほぼ毎秒来るこのイベントが発生するたびに内部でカウンタを1ずつ増やし、 カウンタが指定の値になったら、 OnSecondChangeでトークを返す、という方法です。
このとき注意が必要なのは、 OnSecondChangeが最小化しているときも発生する、 という点です。 OnSecondChangeでトークが捨てられるか否かは、 Reference3の1/0で判定できます。
以上を踏まえて、簡単な自発トークをするスクリプトを組んでみます。 伝統的理由から、 トークはsentenceエントリにあるものとします。