Kawari User's Manual

2004/02/01
Phase 8.2.0

Kawari Development Team :
NAKAUE.T (Meister), 偽Meister (夢乃), さとー, 酔狂, さくらのにえ

Index

← back
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

1. Introduction

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.

2. Dictionary Files

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.

# This is the dictionary description zone.
sentence : \0\s[7]${location}, I have returned!\1\s[10]......\e
sentence : \0\s[7]${boy} is dead! Why?!\e
=kis
# This is the script description zone.
load dict-gundam.txt;
logprint Gundam dictionary load.;
=end
# And this is the dictionary description zone again.
location : Sunshine 60,Harumi,Makuhari,Arake

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.

2.1. Comments

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.

:rem
============================================================
When you want to write multiple lines in comments...
you can do it like this. It is more convenient
for when you want to comment out a certain area all at once.
============================================================
:endrem

function checker $(
        if $[$@arg[0]=~"OK"] $(
                # if 'OK'
                echo OK;
        ) else $(
                # if failed (?)
                echo NG;
        )
)

2.2. Specifying Security Levels

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.

securitylevel level ;
Sets the security level to the specified level. The following levels can be written:
0 / low Alllow all events.
1 / middle Prohibit external events.
2 / high Same as 1 (middle) for the time being.
3 / ultrahighOnly allow events marked as Local Machine Issued.
# If you do specify a security level,
# it's highly recommended to set it near the beginning of kawarirc.kis.
=kis
securitylevel ultrahigh;

=end

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.

2.3. Encrypting Dictionary Files

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.

kawari_encode2 dictionary_file_name1 dictionary_file_name2 ...

You will then be asked for a password, so please enter appropriate passwords. An encrypted file will be created.

C:\home\suikyo\project\kawari\kiu\current> kawari_encode2 dict-sample.txt
Input Keyword : this is test

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.

example dict-sample.txt
#############################
# Copyright Notice, etc.
#############################

# Encryption begins
:crypt
# Conversation data
sentence : \h I happened to see ${npw} yesterday at ${npp}.\e
sentence : \h${npp} is a cool place! You should come here as soon as you can, haha.\e
# noun-proper noun-person's name ( npw )
npw : Ichiro Suzuki, Taro Yamamoto
# nouns-proper nouns-place names ( npp )
npp : Prime Minister's Residence, White House
npp : ${npw}'s house
:endcrypt
# Encryption ends

Encrypting this file will produce the following file. (Some lines are wrapped for readability.)

example dict-sample.kaw
#############################
# Copyright Notice, etc.
#############################

# Encryption begins
!KAWA0001uJuYMVcg2jveOeM75g==
!KAWA0001uMvd1szd1tvdmIKY5NA1SCtCnMPWyM/FOkicw9bIyMU6f
TNMKeo0EToROhc6BTpeOfrk3Q==
!KAWA0001uMvd1szd1tvdmIKY5NCcw9bIyMUvfzoaOn46CTBSK8A6d
ToQOho6fTn5OnU6JznjOg46XToaOnA6DjpdOho6cDn65N0=
!KAWA0001uJuYLgQ2NJU0fS/0LgQ2NJUo1C4EmJCY1sjPmJE=
!KAWA0001uNbIz5iCmC9RLmAwUiDhmJSYNuouwykGIOE=
!KAWA0001uJuYLgQ2NJU0fS/0LgQ2NJUq1i4EmJCY1sjIm JE=
!KAWA0001uNbIyJiCmDZJKTIyFyv4mJSYO8I7Nzv7O9871 jv9O+A=
!KAWA0001uNbIyJiCmJzD1sjPxTp0MX4=
# Encryption ends

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.

kawari_encode2 dictionary_file_name1 dictionary_file_name2 ...

You will still be asked for a password, so enter the password you entered during the initial encryption.

C:\home\suikyo\project\kawari\kiu\current> kawari_decode2 dict-sample.kaw
Input Keyword : this is test

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!

3. Entry Definitions

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.)

entry_name [ , entry_name , entry_name ... ] : definition , definition , definition ... <newline>
entry_name [ , entry_name , entry_name ... ] ( definition , definition , definition ... ,<newline>
definition , definition , definition ... <newline>
... )
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.

# Registering an entry.
lunch.greeting : Hello., Hey., Hey there.,Yo., Hey!
moe, cosplay : Maid, Cute Glasses Girl, Mandarin Dress

# It's the same for dialogue registration.
RandomTalk.LateNight : \0\s[0]It's green tea, after all.\1\s[10]...\w8...\w8What about-?\w8\0\s[3]Green tea has more caffeine than coffee, right?\1\s[10]Yes, that's right.\w8\0I'm going to continue working tonight, so I thought I'd have a cup of green tea---\1\s[11]Scotch.\0\s[2]...\w8\w8What?\1\s[11]Scotch.\e

# An example of a "who/what/when/where" type entry.
RandomTalk.RadioSystem : We are probably the first generation of ${location} and ${relationships} traced back to ${location}.

# Same for event processing scripts, etc. (see below)
event.OnCommunicate : $(if $[$(Reference 0)=="Mayura"] ...Is this a dream? Or an illusion?

The following characters can be used in entry names. Those highlighted are newly available.

Alphanumeric Characters (A〜Za〜z0〜9) / At Symbols('@') / Question Marks('?') / Periods('.') / Underscores('_') / Full-width Characters

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.....specialnpw.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...).

3.1. Definitions and Strings

A statement is any number of "strings", "substitutions", and "blocks". substitutions and blocks are discussed below.

A string could be, for example:

\0\s[3]There's nothing you can do with a dead ghost.\1\s[10]Unthinkable...\e

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.

Double or single quotes '"' , ''' / Dollar Sign '$' / Parenthesis '(' , ')'

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:

test_string : We are the champions ","my friends

=kis
echo ${test_string}
# 'We are the champions ,my friends' is written.
=end

3.2. Quoted Strings

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.

"    In a quoted string, you can write both whitespace and ${entry} call form, commas (,), parentheses, and semicolons (;)."

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. 。

" \" "or " \' " (for the character used in quotes)
" " "and " ' " will be outputted respectively. Used to output the characters used for quoting in a quoted string.
"\\"
"\" will be output. Note that "\" does not always have to be written in order to output the "\" mark Normally, you can write "\c" or "\s" as it is.

Now, what should I do to "output double quotes"?

Don't call me "\""${pseudonym}"\""

One solution is to do it this way. Another is:

Don't call me '"'${pseudonym}'"'

In both cases, the quoted string is the part that changes color.

One more thing. To output the \ mark just before the closing quote:

echo-mode > In Kawari, if you write just before the quote "like this → [\\\"]", you can write \.
In Kawari, if write just before the quote like this → [\"], you can write \.

echo-mode > In Kawari, if you write just before the quote 'like this → [\"]', you can write \.
In Kawari, if you write just before the quote like this → [\"], you can write \.

3.3. Blocks

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.

# traditionally
sentence.mainmenu : \t\0\s[0]Menu\n\n\q0[RandomTalk][talk]\q1[TestCommand][test]\q2[Communicate][communicate]\q3[PrefTalk][talk settings]\q4[Cancel][cancel]

# Phase 8
sentence.mainmenu : (
        \t\0\s[0]Menu\n
        \n
        \q0[RandomTalk][talk]
        \q1[TestCommand][test]
        \q2[Communicate][communicate]
        \q3[PrefTalk][talk settings]
        \q4[Cancel][cancel]
)

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.

incantation : (I'm going to train, I'm going to train,
        I'm going to train, I'm going to train,
        I'm going to train, I'm going to train)

The above can also be written thus.

incantation : (
        I'm going to train, I'm going to train,
        I'm going to train, I'm going to train,
        I'm going to train, I'm going to train
)

However, thisis a mistake.

incantation :
(
        I'm going to train, I'm going to train,
        I'm going to train, I'm going to train,
        I'm going to train, I'm going to train
)

The reason is that the entry definition statement ends at "incantation : ".

3.4. Multi-line Support

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.

4. Substitution and Entry Calls and Execution

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.

# [1] Entry Calls
${entryname}

# [2] Entry Array Calls
$entryname[num]

# [3] Computational Expressions
$[expression (1+2*3 etc)]

# [4] Inline Scripts
$(Script; Script; Script ... )

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.

4.1. Entry Calls

Calling a statement registered in an entry is called an "entry call. For example, to call an entry named "person name," write:

# Register several names in the person name entry
person_name : Conch, Bonito, Seaweed, Cod, Puffer Fish

# When calling the person name entry, write like this
${person_name}

The entry call does two things.

  1. Randomly select one of the sentences registered in the entry
  2. Execute the chosen statement

Remember, the second "to execute" is important. It is also called "evaluate" or "be evaluated," depending on the case.

Continue to the next section.

4.2. Execution

When an entry is registered in the Kawari Dictionary, the sentence is never sent directly to the Ukagaka.

\0\s[0]I'm ${name}. I'm a ${occupation}. My friend is ${friend}.\e

The above example is intended to replace each of ${name}, ${occupation}, and ${friend} with the correct string.

# Entry Definition
name : Sakura
ghost : ghost
friend : Unyuu

If you had such a dictionary, executing the previous example would result in the correct string.

\0\s[0]I'm Sakura. I'm a ghost. My friend is Unyuu.\e

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.

name : Sakura , Futaba
occupation : ghost , desktop mascot
friend : Unyuu , Tadakichi-san
criticism : If you ask me, I don't think ${name} should be a ${occupation} at this time.
criticism : I don't like ${name}, but I like ${friend}.

# The entry name of sentence is due to historical reasons.
sentence : ${criticism}

Here, ${sentence} is executed.

  1. The only registered sentence, ${criticism}, is selected.
  2. An entry call specifying "criticism" occurs.
    1. (e.g.) "If you ask me, I don't think ${name} should be a ${occupation} at this time." is selected.
    2. This is also executed. First, the "Name" entry call.
      1. "Sakura" is selected.
      2. Execution. No change because there is no further substitution to be made.
      The "${name}" is replaced by "Sakura".
      Entry call for "occupation".
      1. "desktop mascot" is selected.
      2. Execution. No change because there is no further substitution to be made.
      The "${occupation}" is replaced by "desktop mascot".
    "${criticism}" is replaced with "If you ask me, I don't think Sakura should be a desktop mascot at this time.".

Thus, the entry call performs all substitutions until there are no more substitutions to be made.

4.3. Nested Substitutions

Substitutions can be nested within each other. Any type of substitution is allowed. There is no limit to the depth of nesting.

$(set topic1 ${topic2})
$[ ${width} * ${height} ]
${ badlist.${counter} }
$badlist[ ${counter} ]
When you say 10 years from now, it's the year $[ $(date %Y) + 10 ] in Western calendar years.

# 分かりにくいですが、これでもちゃんと動きます
$${間接参照}[$[${ベース}+${ポインタ}]]

入れ子になった置換は必ず内側から置換されます。 例えば:

  1. 今から10年後って言うと、西暦$[ $(date %Y) + 10 ]年だね。
  2. 今から10年後って言うと、西暦$[ 2002 + 10 ]年だね。
  3. 今から10年後って言うと、西暦2012年だね。

あるいは

  1. $[ ${幅} * ${高さ} ]
  2. $[ 1600 * 1200 ]
  3. 1920000

という具合です。

Phase 7ユーザは、 以前はできなかった(entry/evalコマンドで実現していた) ${ ${ } } という入れ子ができるようになっていることに注意してください。

# 「何か。」からのGETリクエストに対し、「event.<イベント名>」を呼ぶ
System.Callback.OnGET : ${event.${System.Request.ID}}

なお、以下の部分だけは例外的に置換にできません。注意してください。

例えば以下はエラーです。

# 足したり引いたりしたい
$[ 100 ${plus_or_minus} 10 ]

# 場合によってuntilとwhileを使い分けたい
$(${until_or_while} ${条件} $(実行文 ) )

# 共通単語を呼びたい
${x${and}y}}

5. エントリ呼び出しのバリエーション

5.1. 集合演算式

エントリ呼び出しの中身('${'〜'}'の間)には、 以下のようなことも書けます。

${ 作家 & 女性 }
${ ゴースト & 植物 }
${ 男性 + 女性 }
${ ゴースト - 友達 }

一行目は「「作家」エントリと「女性」エントリの両方に入っている文の中から一つを選ぶ」 (もちろん選んだ後に実行します)、 二行目は「「ゴースト」エントリと「植物」エントリの両方に入っている文の中から一つを選ぶ」、 同様に、 三行目は「「男性」エントリと「女性」エントリのどちらか」、 四行目は「「ゴースト」エントリに入っていて「友達」エントリに入っていない」 文から一つを選びます。

これらは幾らでも並べられます。

優先度について:

数式では'*''/'が、 '+''-'よりも 「優先」されます。 例えば

100 - 10 * 2 = 80

など。

同様に華和梨の集合演算式では'&'が、 他の2つの演算子よりも「優先」されます。

${ 男性 + 女性 & 作家 }

と書くと、「『男性』、もしくは『女性かつ作家』」が選ばれます。 これを「男性か女性、かつ作家」に変えるには、数式と同様、こう書きます。

${ (男性 + 女性) & 作家 }

なお、ジャンル分けをやりやすくするために、 ${エントリ名}のみの文は、 その先のエントリの持つ文まで候補に入れます。

作家 : ${SF作家}, ${ファンタジー作家}, ${ミステリ作家}, ${良くわかんない作家}
SF作家 : ティプトリJr.
ファンタジー作家 : トールキン
ミステリ作家 : クリスティ
良くわかんない作家 : キング
男性 : アシモフ, トールキン, ポオ, キング

=kis
echo ${男性 & 作家}
# 「トールキン」もしくは「キング」
=end

5.2. エントリ配列呼び出し

エントリに登録された文の内、ある特定の位置の文を選ぶ時に使います。

# サンプル
$sentence[0]
$人名[2]
$演説[${最後に喋った演説}+1]

上記のように「'$' + エントリ名 + '[' + 演算式 + ']'」 という形式になります。 演算式については後述します。

エントリ配列呼び出しを実行すると、 指定エントリの、指定番目(これをインデックスと言います)の文を選択し、実行します。 インデックスは0から数え始めます。 普通「一番目」と呼ぶものは、「0」番目ですので気を付けてください。 また、インデックスに負の値を入れた場合は、後ろから数え始めます。

指定番目の文が存在しなかった場合の結果は空文字列("")です。

以下、幸水の表記で動作を示します。

# 辞書内容
a : 零, 壱, 弐, 参

echo-mode > $a[0]

echo-mode > $a[2]

echo-mode > $a[-1]

5.3. 履歴参照(中級)

${数値}」 という特殊なエントリ呼び出しを履歴参照と呼びます。 履歴参照は、同じ文脈で行われた置換結果を再度参照するときに使います。

n : 石
food : 梨
sentence : ${n}のような${food}、${1}のような${0}。

上記の例ですと、sentenceの実行結果は「石のような梨、梨のような石。」になります。

この数値もやはり0から数え始めます。 また、負の数値を入れると後ろから数え始めます。

履歴参照は少し特別扱いなので、 集合演算に使う(${0 & entry}など)ことはできません。 ${ ${エントリ } }のような書き方で、 内部のエントリ呼び出し結果が数値でも、履歴参照にはなりません。


ここからはPhase 7.3.1以前との違いです。

まず、全ての置換記述が履歴参照によって参照可能になりました。 つまり、エントリ呼び出し、エントリ配列呼び出し、演算式、インラインスクリプトのことです。 分かりやすく言えば、「全部の'$'を参照可能」です。

では、次の場合はどうでしょうか。

n : 石
food : 梨
sentence : $(echo 「${n}」)のような$(echo 「${food}」)、${1}のような${0}。

${1}は、 $(echo 「${n}」)の中の${n} を参照してしまわないのでしょうか? もしくは、実行順序としては${n} の方が先のように思えますから、 ${n}が0番目で、 $(echo 「${n}」)が1番目でしょうか。

答えはどちらでもありません。 履歴参照においては、'$'の中は関知しません。 よって、 $(echo 「${n}」)が0番目で、 $(echo 「${food}」)が1番目です。

かと言って、 '$(' 〜 ')'の中(あるいはエントリ集合演算式などの中)では、 履歴参照は使えないという意味ではありません。 もちろん使えます。そして従来通り、括弧の外の以前の置換履歴も参照可能です。

sentence : ${n}のような${food}、$(echo 「${1}」)のような$(echo 「${0}」)。

しかし、スクリプトの外から参照するときは、スクリプト全体しか見えません。

5.4. 一時エントリ(上級)

見かけは全く異なりますが、これは履歴参照とほぼ同じ機能です。

一時エントリとは、華和梨が普段持っている辞書とは別に、 あるエントリ中の一つの文を実行している間のみ存在する一時的な辞書 に登録されるエントリです。 エントリ名の先頭にアットマーク'@'が付くのが特徴です。

この辞書は、文の実行(置換作業ですね)が始まると同時に、その文専用に一つ作成され、 終わると同時に削除されます。

一時エントリは辞書定義時には存在しない(何も実行されていないのですから当然です)ため、 通常のエントリ定義で文を登録することができません。 従って、一時エントリは常にスクリプトによって作られることになります。

sentence : $(setstr @名前 ${名前})\0\s[0]名前エントリから${@名前}を選びました。\1\s[10]\w8何で${@名前}\0\s[0]\w2何でって言われても……

上記では、文の実行が始まった瞬間に一時辞書(中身無し)が設定され、 最初のスクリプトで@名前エントリに (通常辞書の)名前エントリを呼び出した結果の文字列が入ります。 それ以降、@名前エントリは、 この文の実行が終了するまで残ります (もちろん、それ以前にスクリプトによって消去することは可能です)。

履歴参照が、自動的に設定される過去の置換履歴を参照するという単機能であるのに比べ、 一時エントリは自由に設定・変更・呼び出しができ、 集合演算にも使えます。

質問 : ${@名前}って何?

他のエントリに対して履歴参照できないのと同様、 他のエントリの一時辞書を参照することはできません。

sentence : $(pushstr @名前 うなぎ)${質問}

まず「@名前」一時エントリに、 「うなぎ」という文字列をセットしてから、 さきほどの「質問」エントリを呼び出していますが、 これも無意味です。 呼び出し元に対して履歴参照できないのと同様、 呼び出し元の一時辞書を参照することはできません。


履歴参照と違う点は、 同じ文の中でありさえすれば、スクリプトや演算式の中だろうと外だろうと、 場所に関係なく同じ一時辞書にアクセスできるところです。 スクリプト中で操作を行った結果をスクリプト外で受け取ることも勿論できます。 実際、上に挙げた例でも既に行っています。

一時エントリがもっとも活用されるのは、 KISのユーザ定義関数においてでしょう。 以前は「関数的機能を持つエントリ」を呼び出す場合、 そのエントリに渡すべき値(引数)を特別に用意した(しかし通常辞書の一部である)エントリにセットしてから呼び出すのが通例でした。 しかし、この形式では問題があります:

  • 再入(その関数の実行中に再び同じ関数が呼び出されること)不能
  • 関数型エントリ実行終了時に引数エントリをいちいちクリアしない限り、以前の値が残っている可能性がある
  • 呼び出す際以外に、偶然から引数エントリの値が書き変わる可能性がある
  • Phase 8のユーザ定義関数では、 華和梨システムによって、引数は自動的に呼び出された関数側の一時エントリ@argにセットされますので、 安心して引数を使うことができます。 複数のユーザ定義関数が互いを何度も呼び合っても、 その引数のエントリが上書きされたり、過去の引数が残っていたりする危険はありません。

    なお、この機能は無理に使わなくても構いません。 全てのエントリ名が互いにぶつからないように自分で管理できていて、 なおかつfunctionによる関数定義を使わない場合は、一時エントリを使う必要はありません。 エントリ呼び出しを関数代わりに使う従来の手法を踏襲する場合などです。

    6. 演算式

    $[ 〜 ]で囲まれた領域を「演算式」と呼びます。 演算式では、整数演算、ビット単位演算、論理演算、整数比較、文字列比較が行えます。

    $[ 演算式 ]
    # 一年は何分?
    $[ 365 * 24 * 60 ]

    # 今年は2002年ですか?
    $[ $(date %y)==2002 ]

    # あなたの名前は「ほげ」ですか?
    $[ ${name}=="ほげ" ]

    # widthからxを引いて、10で割る
    $[(${width}-${x})/10]

    使用できる演算子は以下になります。 結合優先度が高いもの順です。 優先度の定義は集合演算の章のものと同じです。

    記号 数値 文字列 動作
    **累乗$[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

    幾つかの、四則演算以外の演算について、非常にいい加減な説明をします。 ここにある演算形式は全て既存の(プログラミング言語としては)一般的な概念ですので、 正しい説明はその手の教科書をご覧下さい。

    6.1. 比較演算

    比較演算('>', '>=', '<', '<=', '==', '!=', '=~', '!~')は、 「その記述が正しいか否か」を確認するものだと思えばよいでしょう。 結果として、必ず真偽値を返します。 例えば:

    $[ 1 == 10 ]

    これは明らかに間違っています。 間違っていることを専門用語で「偽」と言います。 反対に正しい状態であることは「真」と言います。 偽の場合は文字列"false"が返ります。 真の場合は文字列"true"が返ります。

    $[ "ばよえ〜ん" == "だいあきゅーと" ]
    # "false"

    $[ (100 / 2) < 100 ]
    # "true"

    少し先走りますが、 この「正しいか間違っているか」を利用して、 スクリプトで条件分岐することができます。 華和梨の真偽判断の基準は、論理演算子、および、 if, while, untilなどの構文コマンド全てにおいて統一されています。

    "", "0", "false"は「偽」、それ以外は全て「真」として扱う

    では、ディスプレイの幅(screen.widthエントリに格納されているとします) が1200を越えていたら「広いディスプレイ」 エントリを呼ぶようにしてみます。

    $(if $[ ${screen.width} > 1200 ] ${広いディスプレイ})

    6.2. 論理演算

    論理演算('!', '&&', '||')は、 真偽値を使った演算です。

    '!'は、「ではない」とでも言えるもので、 右側の値の逆の値を返します。 右側の値が真であれば偽、偽であれば真を返します。

    $[ ! "ほげ" ]
    # "ほげ"は、上述の偽の条件に当てはまらないから真。
    # 従って、その逆である「偽」を返します。
    # 偽を返す場合は"false"ですから、"false"が返ります。
    $[ ! 0 ]
    # 「0」は偽ですから、真が返ります。
    # "true"になります。

    '&&'は、「且つ」つまり、 「〜〜〜 且つ 〜〜〜」です。 右側の値と左側の値が真の時のみ、真、 そうでなければ偽を返します。 必ず、すべての要素を評価します。 ただし真を返す場合は、"true"ではなく、 並列に並べられた'&&'の、一番左側の値をそのまま返します。

    $[ 10 < 100 && "ほげほげ" == "ほげほげ" ]
    # 左側、右側の双方が"true"なので、真、つまり"true"を返します。
    $[ 10 <= ${数値} && ${数値} <= 100 ]
    # ${数値}が、10以上100以下の場合、"true"、そうでなければ"false"を返します。
    $[ ${他のゴースト} && $(暇?) ]
    # ${他のゴースト}エントリに名前が一つ以上あり、"暇?"コマンドがtrueを返した場合に限り、${他のゴースト}エントリから選ばれたランダムな名前が一つ返ります。

    '||'は、「または」つまり、 「〜〜〜 または 〜〜〜」です。 両側の値のどちらかが真の時、真、そうでなければ偽を返します。 最初に真の値が出現した時点で評価を終了します。 ただし真を返す場合は、"true"ではなく、 並列に並べられた'||'の左側から順にテストして、 最初に真となった値をそのまま返します。

    $[ 100 < 10 || "ほげほげ" == "ほげほげ" ]
    # 後者が正しいので、"true"
    $[ ${台詞}=~"胸" || ${台詞}=~"尻" ]
    # ${台詞}が、「胸」か「尻」を含んでいれば"true"
    $[ ${counter1} || ${counter2} || ${counter3} ]
    # counter1, counter2, counter3の内、最初に0以外が返ったところでその値を返す。

    6.3. ビット演算

    ビット演算('&', '|', '^')は、 数値を32bit値として扱う演算です。 通常はまず使わないでしょう。

    $[ 100 & 10 ]
    # 01100100 & 00001010 -> 00000000 = "0"
    $[ 64 & 96 ]
    # 01000000 & 01100000 -> 01000000 = "64"
    $[ 100 | 10 ]
    # 01100100 | 00001010 -> 01101110 = "110"
    $[ 64 | 96 ]
    # 01000000 | 01100000 -> 01100000 = "96"

    6.4. 数値と文字列の扱い

    演算式では数値として扱えるものは必ず数値として扱う という規則があります。 この結果、次のような事態が起きます。

    $[ "001" == "1" ]
    # "true"になる

    このような現象を避けたい(必ず文字列として比較したい)場合は、 KISのcompareコマンドを用いてください。

    $[$(compare "001" "1")==0]
    # "false"になる

    7. インラインスクリプト

    エントリ呼び出しとは別に、 '$(' 〜 ')'でいくつかの文を囲った部分を、 「インラインスクリプト」と言います。 また、=kisのみの行と、 =endのみの行で囲った部分も、 同様に「インラインスクリプト」と言います。 例えば、日付情報を返すdateコマンドを使うには、次のように書きます。

    $(date %H)

    インラインスクリプトは、 エントリ呼び出しの「実行する」機能を、 より強化したものと考えて下さい。 エントリ呼び出しと同様、インラインスクリプトを呼ぶと、 インラインスクリプトは実行結果に置き換わります。

    \0\s[0]今日は$(date %n)月$(date %e)日です。\e

    エントリ呼び出しと違うのは、 上の例で言うと「date」等のコマンド名の後に、 空白を挟んで「%n」などの文がある点です。 コマンド名はエントリ呼び出しのエントリ名に相当し、 どの機能を呼ぶかを決めます。この機能を「コマンド」と呼びます。

    一方、空白以降の文は、 コマンドを呼ぶ際に、補助的情報としてコマンドに渡されます。 この補助的情報を「引数」と言います。 引数はコマンドの許す限り、空白で区切って幾つでも並べることが出来ます。

    $(echo 引数を 幾つも 並べることが出来る 例です)
    $(matchall ${System.Request.Reference1} 胸 薄い)

    7.1. 構文コマンドと関数コマンド

    引数の中に、エントリ呼び出しやインラインスクリプトがあった場合を考えます。

    $(set Today $(date %m%d))

    コマンドが実行される時、エントリ呼び出しやインラインスクリプトは、 それぞれの実行結果に置き換わったものが引数となり、コマンドに渡ります。 上の例では、$(date %m%d)はその日の日付、 例えば「0522」に置き換わり、setコマンドは、

    $(set Today 0522)

    と書いたのと同じ状態で実行されます。 引数の中のインラインスクリプトの引数も、 さらにエントリ呼び出し、インラインスクリプトを含む場合も有り得ます。 この場合、考え方はエントリ呼び出しと同じです。 一番内側の括弧から順番に、置換子がなくなるまで置換を実行します。 そして、その結果が引数としてコマンドに渡ります。

    エントリ名 : entry
    内容1 : 内容2
    内容2 : あ , い , う , え , お

    # 模式的な引数の置換の様子
    $(set ${エントリ名} $(get ${内容1}))
    →$(set entry $(get 内容2))
    →$(set entry あいうえお)
    →entryエントリに「あいうえお」をセットする

    しかし、幾つかのコマンドでは、この引数の置換タイミングが違います。 具体的には、if、while、foreachなど、 プログラムの流れを司るコマンドと、 function、returnコマンド等です。

    これらのコマンドは「構文コマンド」又は単に「構文」と呼びます。 構文コマンドは、その引数を使うときにエントリ呼び出し等を置換し、 使わない引数は置換しないという性質があります。 具体的な例を挙げます。

    $(if $[ ${a} == "Y" ] $(set answer Yes) else $(set answer No))

    この例の場合、$[ ${a} == "Y" ]の結果に応じて、 $(set answer Yes)、 もしくは$(set answer No)のどちらか一方だけ、 実行(=置換)されます。

    構文コマンド以外のコマンドは、 「関数コマンド」又は単に「コマンド」と呼びます。

    7.2. if

    構文コマンドのうち、ifはPhase 7.3.1と比べ、 特に文法が変わりました。 elseelse ifの、 2つのキーワードを新たに導入しました。

    従来のifは、 連続した条件分岐で入れ子のifを使う必要がありました。 これは括弧の対応を間違いやすいだけではなく、 間違いを発見しにくいものでした。 多くの場合、 入れ子のifを、 別のエントリに記述する等の対策が必要でした。 ただし、こうした入れ子をエントリに分解する方法は、 条件を追加・削除する際に厄介です。

    # 従来のif
    $(if <条件> <条件が真の時の文> <条件が偽の時の文>)
    $(if <条件1> <条件1が真の時の文>
      $(if <条件2> <条件1が偽、条件2が真の時の文>
      $(if <条件3> <条件1、2が偽、条件3が真の時の文>
      $(…
       <すべての条件が偽の時の文> )…<ifの数に対応した小括弧の連続>…)

    # あるいは、次のようにエントリに分解する
    if1 : $(if <条件1> <条件1が真の時の文> ${if2})
    if2 : $(if <条件2> <条件1が偽、条件2が真の時の文> ${if3})
    if3 : $(if <条件3> <条件1、2が偽、条件3が真の時の文> ${if4})
     …
    ifn : $(if <条件n> <条件1…n-1が偽、条件nが真の時の文> <すべての条件が偽の時の文>)

    新しいifでは、こうした問題が起きにくくなっています。 複数行記述と併せ、一つの処理は一つのエントリの中で完結します。 メンテナンスが容易になるでしょう。

    # 新しいif
    $(if <条件> <条件が真の時の文> else <条件が偽の時の文>)
    $(if <条件1> <条件1が真の時の文>
      else if <条件2> <条件1が偽、条件2が真の時の文>
      else if <条件3> <条件1、2が偽、条件3が真の時の文>
      …
      else <すべての条件が偽の時の文>)

    7.3. 組み込みコマンドとユーザ定義コマンド

    エントリと違い、いくつかのコマンドは、ユーザが定義しなくても実行できます。 このようなコマンドを、「組み込みコマンド」と言います。 一方、functionコマンドを使いユーザが定義したコマンドを、 「ユーザ定義コマンド」と言います。 ユーザ定義コマンドは、一度定義すれば、華和梨が起動している間有効です。 なお、ユーザ定義コマンドは必ず関数コマンドになります。

    コマンド定義中では、 @arg一時エントリを引数として使います。 第1引数は$@arg[1]、 第2引数は$@arg[2]、 以降第N引数は$@arg[N]で参照できます。 $@arg[0]は定義中のコマンド名となります。 以下にコマンド定義の例を示します。

    # @argエントリに引数が入っているものとして記述する
    $(function 改行挿入 $(clear @arg[0] ; foreach i @arg $(echo ${i}\n)))

    # 使用例
    # 「カステラ1番\n電話は2番\n3時のおやつは文明堂\n」が返る
    $(改行挿入 カステラ1番 電話は2番 3時のおやつは文明堂)

    次に、既存の組み込みコマンドと同じ名前で、 ユーザ定義コマンドを定義した場合を考えます。

    $(function size $(length $(get $@arg[1])))

    この例の場合、sizeコマンドは、 ユーザ定義コマンド版sizeに上書きされます。 必ず組み込みコマンドを呼びたい場合、 $(.size entry)のように、 コマンド名の先頭に「.」を付けて呼んで下さい。

    また、ユーザ定義コマンドをもう一度定義すると、 後に定義した方が呼ばれます。

    $(function default $(echo さくら) ; default)
    # 「さくら」が返る

    $(function default $(echo まゆら) ; default)
    # 「まゆら」が返る

    $(function default $(echo 涼璃) ; default)
    # 「涼璃」が返る

    上の例では、defalutコマンドを3回呼んでいます。 しかし、毎回直前で定義しなおしているため、3回とも違う結果になります。

    8. SHIORI/SAORIインターフェース

    ここまでに、ゴーストの動作記述方法については、ほぼ全て解説しました。 が、肝心の 「ダブルクリックイベントに対応するには?」 「おすすめURLを表示するには?」 などの説明を一切しませんでした。

    栞としての機能については、この章でまとめて説明します。 また、華和梨はSAORIとしても機能しますので、それについても併せて説明します。

    8.1. コールバック

    華和梨が情報のやり取りのため、 特別扱いするエントリを「システムエントリ」と呼びます。 このうち、幾つかのエントリは呼び出す際の仕組みが、 他のエントリとまったく違います。 この節では、このシステムエントリの中でも特異な、 「コールバックエントリ」を説明します。

    コールバックエントリとは、本体がイベント、NOTIFYを通知してきた際、 最初に評価するエントリです。 通常のエントリを評価する場合、エントリ中の文を一つランダムに選び、 その文の評価結果をエントリの評価結果とします。 一方、コールバックエントリが本体から呼ばれた場合、 コールバックエントリ中のすべての文を添え字順に評価し、 すべての評価結果を結合したものを本体に返します。 仮に、System.Callback.OnGETエントリが、 次のような内容だったとします。

    System.Callback.OnGET : \0
    System.Callback.OnGET : \s[0]
    System.Callback.OnGET : 阿。
    System.Callback.OnGET : \1
    System.Callback.OnGET : \s[10]
    System.Callback.OnGET : 吽。
    System.Callback.OnGET : \e

    もしこのコールバックエントリが本体から呼ばれたとすると、 本体に返すスクリプトは次のようになります。

    \0\s[0]阿。\1\s[10]吽。\e

    この結果は、 KISコマンドのgetを使い、 次のように書いた結果と等価です。 コールバックエントリは、 コールバックエントリをgetで評価した結果を本体に返すと考えて下さい。

    get System.Callback.OnGET

    このコールバックエントリの動作は、 主にミドルウェアの記述を簡素化する際に有効でしょう。 ミドルウェアはOnSecondChangeイベント等で、 多数の独立した機能を動作させることがあります。 従来、この独立動作する機能を追加したい場合、 必然的にミドルウェアを書き換える必要がありました。 しかし、今回からはSystem.Callback.*エントリに、 追加機能を呼ぶ文を追加するだけで大丈夫です。

    次に、華和梨の使う全コールバックエントリを示します。

    SHIORI/3.0
    System.Callback.OnGETGET
    System.Callback.OnNOTIFYNOTIFY
    SHIORI/2.x
    System.Callback.OnEventSHIORI/2.2 イベント応答、GET Sentenceのみ
    System.Callback.OnGetSentenceSHIORI/2.3b コミュニケート
    System.Callback.OnGetStatusステータス取得
    System.Callback.OnResourceSHIORI/2.5リソース取得
    SAORI/1.0
    System.Callback.OnSaoriExecuteSAORIモジュールとして呼ばれた
    共通
    System.Callback.OnUnload切り離しイベント(華和梨が発行)
    System.Callback.OnRequestその他全てのリクエスト(NOTIFY SHIORI/2.x、TRANSLATE SHIORI/2.x等)

    System.Callback.OnRequest

    このうち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.Callback.OnRequest : $(
        if $[ ${System.Request} == "TEACH" ] $(
            setstr @Word ${System.Request.Word};
            pushstr TeachWord ${@Word};
            setstr System.Response.Sentence \0\s[0]${@Word}ね。\e;
            setstr System.Response 200;
        );
      )

    8.2. その他のシステムエントリ

    コールバックエントリ以外にも、幾つかシステムエントリが存在します。 次に一覧を示します。

    本体からの通知情報
    System.Request.*リクエストヘッダ
    本体への応答
    System.Response.*レスポンスヘッダ
    System.Response.ToSHIORI/2.3b 話しかけたいゴースト名。"stop"で打ち切り。
    System.ResponseSHIORI/2.0 ステータスコード
    その他(リードオンリー)
    System.DataPathshiori.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エントリにセットされた単語は、 本体に返すステータスコードになります。

    リクエストヘッダエントリ、レスポンスヘッダエントリ群は、 本体から呼ばれてコールバックエントリを評価する直前に、 一度完全に内容を消します。

    8.3. 栞としての華和梨

    栞サブシステムの仕事は一見複雑ですが、要約すれば、 「本体の通知してきたIDから、相応しい応答を割り出して本体に返す」ことです。 華和梨Phase 8はPhase 7までと比べると、 こうした栞の仕事を、使用者により「生のまま」見せています。

    代表的な例はイベント応答です。 華和梨Phase 8はイベント応答、リソース文字列の要求、 NOTIFY処理の呼び分けを、KISを使って書く必要があります。 本体が通知してきたID(=イベント名、リソース名)は、 System.Request.IDエントリに入っています。 これを使って呼び分けます。

    華和梨Phase 7.3.1と同じ名前でイベントエントリ、 リソース文字列エントリを使いたい場合、 System.Callback.OnGETエントリに次のように書きます。

    # GET SHIORI/3.0への応答
    # イベント、リソース文字列、コミュニケート、etc…
    # イベントは「event.<イベント名>」、
    # リソースは「Resourece.<リソース名>」というエントリを呼ぶ
    System.Callback.OnGET: $(
        if $[ $(match_at ${System.Request.ID} On) ]
            ${event.${System.Request.ID}}
        else $(
            ${resource.${System.Request.ID}}
      )

    この例では、例えばマウスのダブルクリックイベントが来た時、 event.OnMouseDoubleClickエントリを呼びます。 また、例えばさくら側の「おすすめURL」の要求があった場合、 ${resource.sakura.recommendsites}の評価結果を返します。

    しかし、SHIORI/3.0ではイベントとリソース文字列要求は、 本体からの通知形式に差がありません。 そこで、エントリ名を従来から変更する代わりに、 記述を簡素化することが出来ます。 この場合、次のように書きます。

    # GET SHIORI/3.0への応答
    # イベント、リソース文字列、コミュニケート、etc…
    # 「reply.<要求されている処理名>」というエントリを呼ぶ
    System.Callback.OnGET : ${reply.${System.Request.ID}}

    この例では、例えばマウスのダブルクリックイベントが来た時、 reply.OnMouseDoubleClickエントリを呼びます。 また、例えばさくら側の「おすすめURL」の要求があった場合、 ${reply.sakura.recommendsites}の評価結果を返します。

    最後に、NOTIFYの処理の呼び分け触れます。 NOTIFYの形式はイベント・リソース文字列要求のGETの場合と、 ほとんど差がありません。

    # NOTIFY SHIORI/3.0の処理
    # HWnd通知、他のゴースト通知、インストール済みゴースト通知、etc…
    # 「notify.<通知された情報名>」というエントリを呼ぶ
    System.Callback.OnNOTIFY : ${notify.${System.Request.ID}}

    例えば他に起動中のゴーストの名前がNOTIFYされた場合、 notify.otherghostnameエントリを呼びます。

    ミドルウェアを使わずに華和梨を使う場合、こうした記述が必ず必要です。 しかし、多くのミドルウェアでは、 こうした低レベル(よりプログラムに密着した)の記述が、 既にパッケージ化されています。 何らかの事情が無い限り、こうしたミドルウェアの使用をおすすめします。

    8.4. SAORIモジュールとしての華和梨

    華和梨はSHIORI規格の準AIモジュールですが、 同時にSAORI規格モジュールでもあります。 SAORIモジュールとして使うと、

    といったメリットがあります。

    華和梨をSAORIとして使うとき、次の3つのことに注意します。

    引数の受け取り

    華和梨がSAORIとして呼ばれた時、 System.Callback.OnSaoriExecuteエントリを評価します。 他のコールバックエントリと同様、所属する全ての単語を評価します。 この時、SAORIモジュールに与えられた引数は、 System.Request.*以下、 System.Request.Argument0System.Request.Argument1 等のエントリに存在します。

    引数が何個あるか等を知りたい場合、listtreeコマンドを使い、

    listtree arguments System.Request

    として、argumentsエントリを調べると知ることが出来ます。

    戻り値の引渡し

    戻り値を返したい時はコミュニケートと同様、 System.Response.*エントリ群を使います。 SAORI規格に従って、戻り値は、 System.Response.Resultに書き込みます。 以下、 System.Response.Value0System.Response.Value1System.Response.Value2 等のエントリに補助情報を書き込みます。

    ステータス

    戻り値を返すには、実はこれだけでは不十分です。 System.Resoposeエントリに、 ステータスを書き込む必要があります。 このステータスコードを見て、 SAORIモジュールを呼び出した側は処理の成功/失敗を判断する為、 必須情報です。 主なステータスコードは次の通りです。

    200引数を正しく理解し、戻り値を書き込んだ
    204引数は正しく理解したが、戻り値がない
    400(省略時デフォルト)理解できない引数が来た

    なお、 System.Callback.OnSaoriExecuteの評価結果は、 ステータスと無関係です。 この点が他のコールバックエントリと違いますので、注意が必要です

    System.Callback.OnSaoriExecute : $(
        if $[ ${System.Request.Argument0} == "処理1" ] $(
            SAORI処理1;
        ) else if $[ ${System.Request.Argument0} == "処理2" ] $(
            SAORI処理2;
      );

    # SAORI処理をわざわざ関数にする必然性はないのだが…
    # 新機軸に見慣れて貰うために、あえて関数で記述した。
    # 普通にエントリとして書いても何の問題もない。
    # 引数はSystem.Request.*に存在し、関数の引数として与える必要がないから。

    =kis
    function SAORI処理1 $(
        if $[ ${System.Request.Argument1} == "貢物" ] $(
            # Resultのみ返す例
            setstr System.Response.Result "うむ、よろしい!";
            setstr System.Response 200;
        ) else $(
            # ResultとValue*を併用する例
            setstr System.Response.Result "貢物はどうした!";
            setstr System.Response.Value0 "文句";
            setstr System.Response.Value1 "不満";
            setstr System.Response.Value2 $(date %y%m%d);
            setstr System.Response 200;
        );
    );

    function SAORI処理2 $(
        if $[ ${System.Request.Argument1} == "1" ] $(
            # Resultなしだが正常終了の例
            setstr System.Response 204;
        ) else $(
            # 異常終了の例
            setstr System.Response 400;
        );
    );
    =end

    8.5. SAORIモジュールの使用宣言

    SAORIモジュールを使用する際は、 「これからこのSAORIを使う」という使用宣言の記述が必要です。 この記述を受けて、 華和梨はSAORIモジュールを読み込んだり、読み込む準備をします。

    華和梨Phase 8以前では、 このSAORI使用宣言はkawari.iniに記述しました。 しかし、Phase 8からkawari.iniは廃止になり、 使用宣言はKISで記述することになりました。

    SAORIモジュール使用宣言は、 saoriregistコマンドを使います。

    saoriregist <SAORIモジュール名> <エイリアス> [<オプション>]
    SAORIモジュール名(必須)
    SAORIモジュールのファイル名を記述します。拡張子(「.dll」等)を含めます。 華和梨の存在するフォルダからの相対パスで記述します。
    エイリアス(必須)
    実際にSAORIモジュールを使用する際、 使用するモジュールを区別するためのラベルです。 複数のSAORIモジュールを使う場合、モジュールごとに別の名前を付けてください。 また、モジュールを一つだけ使う場合も、必ずエイリアスが必要です。
    オプション(省略可能)
    SAORIモジュールを読み込むタイミングを指定します。 「preload」で直ちにに読み込み、 「loadoncall」でモジュールを使用する直前に読み込み、 「noresident」でモジュールを使用する直前に読み込み、 使用後に切り離しです。
    省略した場合、「loadoncall」と等価です。 使用するSAORIモジュールのマニュアルに注意書きがない場合、 通常はオプションを省略して構いません。

    こうして使用宣言をすると、華和梨からSAORIモジュールを呼び出すことが出来ます。

    8.6. SAORIモジュール呼び出し

    SAORIモジュールを呼び出す時は、 callsaoriコマンド、 callsaorixコマンドを使います。 callsaoriコマンド、 callsaorixコマンドは、 先に定義した「エイリアス」でSAORIモジュールを呼びます。 仮に、music.dllというSAORIモジュールを、 「音楽」というエイリアスで使用宣言したとします。

    saoriregist music.dll 音楽

    このSAORIモジュールの機能が指定した音楽ファイルの再生だとしたら、 再生する音楽ファイル名を指定するのが普通でしょう。 この場合、SAORIモジュールに再生するファイル名を伝える必要があります。 話の都合上、music.dll

    という仕様だとします。

    このmusic.dllで、 「technopolis.mid」という音楽ファイルを再生したい場合、

    callsaori 音楽 play technopolis.mid

    と書きます。 callsaoriコマンドのエイリアスより後ろの引数は、 SAORIモジュールに引数として渡します。

    callsaorixコマンドは、 SAORIモジュールが戻り値以外に、 様々な情報を送ってくるタイプの場合に使用します。 仮に、先ほどのmusic.dllが「play」を指示した場合、 次のような情報を送ってくるとします。

    では、callsaorixを使って、 音楽ファイル「Truth21c.mid」を再生します。 エイリアスはcallsaoriコマンド、 callsaorixコマンドに共通で使えます。

    callsaorix 音楽 music.info play Truth21c.mid

    ここで、エイリアスのすぐ後ろの「music.info」は、 SAORIモジュールが送ってきた情報を記録する際の、 基準となるエントリの名前です。このエントリの名前は自由に決められます。 上の例の場合、次のようなエントリに情報を書き込みます。

    sizeはValueが何個あるかを示します。

    callsaoricallsaorixはともに戻り値を返す関数コマンドです。 戻り値はSAORIモジュールの返したResultヘッダです。

    9. 応用編

    上記を踏まえ、ゴーストの基本動作を実装する際、 華和梨Phase 8ではどのように記述するのか、 幾つか例を交えて紹介します。

    9.1. イベント

    イベント通知の際、本体からのReferenceヘッダを華和梨から参照するには、 ${System.Request.Reference0}など、 非常に長いエントリ名を記述する必要があります。 使ってみると、これは不便です。 そこで、コマンドを作って記述を簡単にする場合を考えます。

    コマンドを定義する際の注意ですが、 スクリプト記述ゾーンで定義して下さい。 辞書記述ゾーンのエントリ定義中でコマンドを定義した場合、 コマンドの定義は「そのエントリが呼び出されて」初めて機能します。 多く場合、コマンドは未定義も同然となります。

    では、実際に定義してみます。

    =kis
    # Referenceを返すコマンド "Reference"
    function Reference $(
        # 引数がなかった場合、何も返さない
        if $[ $(size @arg) <= 1 ] $(return);

        # System.Request.Reference?エントリの一つ目の単語を得る
        get System.Request.Reference$@arg[1][0];
    );
    =end

    この例では、同じReference番号のReferenceヘッダが複数来る場合を想定し、 getで0番目のRefernce?を参照しています。

    9.2. コミュニケート

    他のゴーストに話し掛ける時は、 Reference0に話し掛けたいゴースト名をセットして、 トークを返します。 ReferenceN(Nは0以上の整数)を返すには、 System.Response.ReferenceNエントリに単語をセットします。 なお、System.Responseで始まるエントリは、 コールバックごとに毎回内容が消去されます。

    sentence : (
        $(setstr System.Response.Reference0 毒子)
        \0\s[0]おーい、毒子さん、聞こえるかーい。
        \1\s[11]やめとけ、アレは偽毒子だ。変な注射されるぞ。\e
      )

    こうして話し掛けられたゴーストには、 OnCommunicateがやってきます。 SHIORI/3.0から、コミュニケートはイベントの一種になりました。

    # 「会話」エントリの全内容を評価し、戻り値の一つをエントリ名として
    # エントリ呼び出し
    # 戻り値がなければ、communicate.unknownエントリを呼ぶ
    reply.OnCommunicate : $(communicate 会話 ${communicate.unknown})

    自分から話し掛けるときと同様、 話し掛けるゴースト名をReference0に設定して下さい。

    なお、コミュニケート用コマンドは、 ユーザ定義コマンドで使いやすいようラッピングするのが得策ですが、 ここでは一番素直に書いた場合を例示します。

    コミュニケートの基本的書き方は、次のようになります。

    1. 反応するキーワードを書いたエントリを作る。 キーワード群1個につきエントリ一個とする。仮にkeyとする。
    2. キーワード群に対応する、返事を書いたエントリを作る。 これもキーワード群1個につきエントリ1個とする。仮にanswerとする。
    3. communicateコマンドで束ねるエントリを、 仮にcomとする。
    4. キーワード、返事をcomエントリに登録する時は、
      com : $(
          if $(xargs key matchall ${System.Request.Reference1})
              answer
        )
      と書く。
    5. OnCommunicateイベントで、
      $(communicate com)
      と書く。

    以上をまとめた例を挙げます。

    # 例えば「あなたの名前は何?」に反応する
    com.key.1 : あなた , 名前 , 何?
    com.answer.1 : (
        \0\s[0]私の名前は${myname}です。\s[5]あなたは?\e
        $(setstr System.Response.Reference0 $(Reference 0))
      )
    com.answer.1 : \0\s[4]うー。\1\s[10]すまん、こいつ今機嫌が悪くてな\e
    com.answer.1 : \0\s[8]…。\1\s[10]無視かよ。\e
    com : $(
        if $(xargs com.key.1 matchall $(Reference 1))
            com.answer.1
      )

    # 「胸が無い」と言われるとキれる
    com.key.2 : 胸 , 無い
    com.answer.2 : \0\s[25]…。\1\s[11]俺が時間を稼ぐ、早く逃げろ!\e
    com : $(
        if $(xargs com.key.2 matchall $(Reference 1))
            com.answer.2
      )

    # 複数のゴーストの呼びかけに応じる例
    # 「うにゅぼんで一緒に遊ぼう」等に反応する
    com.key.3 : うにゅぼん , 一緒 , 遊ぼう
    com.answer.3 : (
        \0\s[5]うん!じゃ、私は「${installedghost}」使うよ。\e
        $(setstr System.Response.Reference0 $(Reference 0))
      )
    # 反応するゴースト名一覧
    com.ghost.3 : 黒姉 , まゆら , さくら , せりこ
    com.ghost.3 : 毒子 , あると , 奈留 , 安子
    # findを使い、反応するべきゴーストかどうか判定
    com : $(
        if $[ $(find com.ghost.3 $(Reference 0)) >= 0 &&
         $(xargs com.key.3 matchall $(Reference 1)) ]
            com.answer.3
      )

    # あるゴーストに自分の名前を呼ばれたときに反応
    com.key.4 : ${myname}
    com.answer.4 : \0\s[5]……$(Reference 0)様!\e$(setstr FlagMode happy)
    com.ghost.4 : まゆら
    com : $(
        if $[ $(Reference 0) == ${com.ghost.4} &&
         $(xargs com.key.4 matchall $(Reference 1)) ]
            com.answer.4
      )

    # キーワード外の反応文
    com.unknown : \0\s[4]ごめん、よく分かんない。\e
    com.unknown : \0\s[8]なプー。\1\s[10]…。\e
    com.unknown : (
        \0\s[0]…それから?\e
        $(setstr System.Response.Reference0 $(Reference 0))
      )

    # コミュニケートイベントでcomエントリを登録
    reply.OnCommunicate : $(communicate com ${com.unknown})

    9.3. 自発トーク

    自発的にトークをするには、 OnSecondChangeイベントを使うのが一般的です。 ほぼ毎秒来るこのイベントが発生するたびに内部でカウンタを1ずつ増やし、 カウンタが指定の値になったら、 OnSecondChangeでトークを返す、という方法です。

    このとき注意が必要なのは、 OnSecondChangeが最小化しているときも発生する、 という点です。 OnSecondChangeでトークが捨てられるか否かは、 Reference3の1/0で判定できます。

    以上を踏まえて、簡単な自発トークをするスクリプトを組んでみます。 伝統的理由から、 トークはsentenceエントリにあるものとします。

    System.Callback.OnGET : ${reply.${System.Request.ID}}

    # トーク周期(秒)
    # このエントリに指定した時間間隔で発話する
    interval : 90

    # トークカウンタ
    # このカウンタがトーク周期と等しくなったら発話する
    talkcounter : 0

    # OnSecondChangeイベント
    # Reference3が1なら、トークを返しても大丈夫
    reply.OnSecondChange : $(
        if ${System.Request.Reference3} $(
            inc talkcounter;
            # カウンタがトーク周期以上になっていたら発話し、
            # カウンタをリセットする
            if $[ ${talkcounter} >= ${interval} ] $(
                entry sentence;
                setstr talkcounter 0;
            );
        );
      )