2004/02/01
Phase 8.2.0
Kawari Development Team :
NAKAUE.T (Meister), 偽Meister (夢乃), さとー, 酔狂, さくらのにえ
1. Introduction
2. Dictionary Files
2.1. Comments
2.2. Specifying Security Levels
2.3. Encrypting Dictionary Files
3. Entry Definitions
3.1. Definitions and Strings
3.2. Quoted Strings
3.3. Blocks
3.4. Multi-line Support
4. Substitution and Entry Calls and Execution
4.1. Entry Calls
4.2. Execution
4.3. Nested Substitutions
5. Entry Call Variations
5.1. Aggregate Expessions
5.2. Entry Array Calls
5.3. History References (Intermediate)
5.4. Temporary Entries (Advanced)
6. Computational Expressions
6.1. Comparison Expressions
6.2. Logical Expressions
6.3. Bit Expressions
6.4. Numerical and String Operations
7. Inline Script (Intermediate)
7.1. Syntax and Function Commands
7.2. if
7.3. Built-in and User-defined Commands
8. SHIORI/SAORI Interface
8.1. Callbacks
8.2. Other System Entries
8.3. Kawari as SHIORI
8.4. Kawari as SAORI
8.5. Declaring SAORI Modules
8.6 Calling SAORI Modules
9. Practical Application
9.1. Events
9.2. Communicate
9.3. Random Talk
The User's Manual introduces almost all the grammar and functions of Kawari. With this and the KIS Reference, you will be able to use all the functions.
If you are new to Kawari, please read Getting Started before the User's Manual to get an idea of what to expect.
It takes a lot of time and effort to create a ghost from scratch. To easily check the operation of Kawari, please use the console UI version of the application "Kosui". It is impossible to learn Kawari without Kosui.
A dictionary file is a file that directs sentences, vocabulary, and behavior patterns to Kawari. The dictionary file is all that describes your ghost.
The first dictionary file that Kawari reads is "kawarirc.kis".
The dictionary file is divided into two zones by lines starting with '=': the dictionary definition zone and the script description zone.
The dictionary definition zone is for writing dictionary definitions, and the script description zone is for writing KIS directly and executing it on the fly.
In the initial state (when the dictionary file is read), it's in the dictionary description zone, and the line starting with =kis to lines starting with =end are in the script description zone.
The syntax in the script description zone is identical to the syntax between '$(' ?` ')', so it is omitted here. Scripts written in the script description zone are executed the moment the script is loaded. Please be aware of that.
The grammar in the dictionary definition zone is described in detail in Chapter 3 and below.
If the first non-whitespace character at the beginning of a line is a '#' the entire line is considered a comment and is ignored.
Also, the entire region from a line starting with ':rem' to a line starting with ':endrem' is considered a comment and ignored.
Both of these grammars take precedence over all other grammars described below. In the case of a multi-line description, make sure that the '#' character does not appear at the beginning of a line. If this is the case, you can escape by using a quoted string.
Some of the events sent by Ukagaka may come from outside the local machine (the computer on which the ghost is running) via SSTP or other means. In order to eliminate such events, you can set the security level with the following statement.
If nothing is written, the setting is automatically set to high (secure), so it is usually not necessary to write anything.
When describing the security level, be sure to put it in the file that is loaded at initialization. It's best to put it in kawarirc.kis. You can't change the security level after the ghost starts running.
0 / low | Alllow all events. |
1 / middle | Prohibit external events. |
2 / high | Same as 1 (middle) for the time being. |
3 / ultrahigh | Only allow events marked as Local Machine Issued. |
During the operation of Kawari, the security level is stored as a numerical expression (0 to 3) in "System.SecurityLevel". It can't be rewritten while the Ghost is running.
To make your dialogue less obvious, you can apply simple encryption to dictionary files. To encrypt, use the included kawari_encode2.exe, and type the following in the DOS prompt.
You will then be asked for a password, so please enter appropriate passwords. An encrypted file will be created.
The encrypted dictionary file is the one whose file name extension is changed to ".kaw". In the dictionary file, the lines from ":crypt" to ":endcrypt" are subject to encryption. All other lines are left in their original state.
Encrypting this file will produce the following file. (Some lines are wrapped for readability.)
Remember to specify the generated encrypted dictionary file "dict-*.kaw" in kawarirc.kis. You can change the extension of the encrypted file to something other than .kaw.
If you want to restore the encrypted file, use kawari_decode2.exe.
You will still be asked for a password, so enter the password you entered during the initial encryption.
A decrypted dictionary file with a ".txt" extension will then be created.
Note: If a file with the same name already exists, it will be overwritten!
Kawari keeps all data classified into "entries". A dictionary definition is a lot of entry definition lines. Entry definitions should be written in the following format: ('['~']' is an optional part.)
In Phase 8, we now distinguish between "definitions" and "words," which we used to call just "words". But this is mainly a matter of fine grammatical definition and should not be a concern normally.
Spaces can be freely inserted before and after colons ":" and commas ",".
The multiple-entry format should be used when the same sentence is to be registered in multiple entries. The latter form, enclosed in '('〜')', should be used when you want to separate sentences into multiple lines. You can freely break lines between '('〜')'. Note that this differs from the "block" format described below in that it allows multiple sentences to be placed within it. You do not have to use this if it is confusing.
The following characters can be used in entry names. Those highlighted are newly available.
However, the at symbol, "@", is used for temporary entries and should not normally be used. In addition, "." is not allowed at the beginning of an entry name. If there are consecutive "." in the entry name, they will be combined into one (npw.....special → npw.special). Entries beginning with 'System.' are entry names specially used by Kawari, so be careful not to use them without permission.
As long as you follow the above rules, you are free to decide on an entry name. Please use a name that is easy for you to understand. You can freely use full-width characters so you should be proactive in using them. (Although typing them may be a bit cumbersome...).
A statement is any number of "strings", "substitutions", and "blocks". substitutions and blocks are discussed below.
A string could be, for example:
Most sentences can be written as they are in this way. However, there are a very small number (less than before) of characters that cannot be used.
First, the following symbols are always not allowed. There are no other restrictions other than these in the blocks described below.
If written solidly in an entry definition, an additional comma, ',', is not allowed. Also, a semicolon, ';'is not allowed in inline scripts. Each of these is used to delimit a sentence at that location, so it is sensible to know that they cannot be used. Practically the only ones you have to watch out for are the first ones listed.
Also, solid white space (spaces, tabs, and line breaks where possible) before and after a sentence, and at the beginning and end of a line, are ignored. To write whitespace in these places, use the following quotes:
To include the above-mentioned characters that cannot be used, or spaces at the beginning, or at the end of a sentence in a Kawari sentence, use a quoted string.
A string enclosed in double quotes (") or ('), as shown above, is called a "quoted string. A quoted string can output almost all characters in their "as written" form.
There is no character that cannot be output in a quoted string. However, there are two things that must be written a little differently. 。
Now, what should I do to "output double quotes"?
One solution is to do it this way. Another is:
In both cases, the quoted string is the part that changes color.
One more thing. To output the \ mark just before the closing quote:
The part of the sentence enclosed by '(' 〜 ')' is called a "block". The '$( ' 〜 ')' are the inline scripts described below, so do not make a mistake.
The block has only two effects.
Whitespace is ignored as usual. All substitution rules and so on are the same as elsewhere. Also, the blocking parentheses '(', ')' do not appear in the output, of course.
Blocks are used primarily when you want to write sentences on multiple lines.
Note that a line break is always required at the end of an entry definition, even if a block is used to describe multiple lines.
The above can also be written thus.
However, thisis a mistake.
The reason is that the entry definition statement ends at "incantation : ".
The concept behind Phase 8's multi-line support is "as long as there is no closing parenthesis corresponding to the opening parenthesis, there can be any number of newlines" . In blocks, entry calls, subscripts in entry array calls, arithmetic expressions, inline scripts, and all other places enclosed in parentheses, a line break may now be inserted wherever grammatical white space is permitted. In such places, a line break is considered whitespace (the same as a space or tab).
Also, encased entry definitions ("entryname '(' ')'" form) allow for completely free multi-line descriptions in entry definitions.
This is the most important chapter. Please return here every time a question arises and read it repeatedly to understand it.
Now, any string can be output when an entry is invoked. However, in order for Kawari to truly perform intelligently, the substitution function described below is indispensable.
Kawari has four replacement functions. Each of them starts with a '$' and has a specific range determined by parentheses, etc.
These details will be discussed in detail in turn. However, all of them have one thing in common: they all replace the substitution part (the description beginning with '$') with the result of the execution. Some of them do not have an execution result. For those that do, the replacement description will simply be invisible (replaced by an empty string).
Also, substitution occurs each time the statement is executed. Thus, entry calls, especially those with random behavior, return different results each time the statement is executed.
Calling a statement registered in an entry is called an "entry call. For example, to call an entry named "person name," write:
The entry call does two things.
Remember, the second "to execute" is important. It is also called "evaluate" or "be evaluated," depending on the case.
Continue to the next section.
When an entry is registered in the Kawari Dictionary, the sentence is never sent directly to the Ukagaka.
The above example is intended to replace each of ${name}, ${occupation}, and ${friend} with the correct string.
If you had such a dictionary, executing the previous example would result in the correct string.
In other words,
Execution = To perform the substitution
Remember that As you become more proficient with Kawari, you will inevitably need to know when substitutions are made. When you get confused with complicated statements, pay attention to when the "execution" (or "evaluation") is performed.
Knowing this rule will also help you understand the difference between quoted strings and replacements. Escaping (e.g \") in a quoted string need not change the result each time it is executed. So, the escaping process is already done when the file is read.
Now, because the entry call, which is itself a substitution function, performs further execution on the selected statement, the entry call will inevitably be performed many times.
Here, ${sentence} is executed.
Thus, the entry call performs all substitutions until there are no more substitutions to be made.
Substitutions can be nested within each other. Any type of substitution is allowed. There is no limit to the depth of nesting.
入れ子になった置換は必ず内側から置換されます。 例えば:
あるいは
という具合です。
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エントリにあるものとします。