コンテンツにスキップ

文法

華和梨の文法を 1 ページにまとめています。章ごとの内容は右の目次から飛べます。

ユーザーズマニュアルの読み方

ユーザーズマニュアルでは、華和梨のほぼ全ての文法と機能を紹介します。これとKISリファレンスで、一通りの機能が使えるようになります。

初めて華和梨を使う方は、ユーザーズマニュアルより先にGetting Startedを読み、イメージを掴んでください。

何もないところからゴーストを作るのは大変手間がかかります。簡単に華和梨の動作を確認していくためには、コンソールUI版アプリケーション『幸水』を使ってください。幸水なしの華和梨修得は、まず考えられません。


辞書ファイル

辞書ファイルとは、華和梨に対して、文章や語彙や動作パターンを指示するためのファイルです。辞書ファイルが、あなたのゴーストを記述する全てです。

華和梨が最初に読む辞書ファイルは"kawarirc.kis"です。

辞書ファイルは、'='で始まる行によって、辞書定義ゾーンと、スクリプト記述ゾーンの二つのゾーンに分かれます。

辞書定義ゾーンは辞書定義を書く領域であり、スクリプト記述ゾーンはKISを直接書き、その場で実行する領域です。

初期状態(辞書ファイルを読み始めた状態)では辞書定義ゾーンで、=kisで始まる行から=endで始まる行までがスクリプト記述ゾーンです。

# ここは辞書記述ゾーン
sentence : \0\s[7]${地名}よ、私は帰ってきた!\1\s[10]……。\e
sentence : \0\s[7]${坊や}は死んだ! 何故か!?\e
=kis
# ここはスクリプト記述ゾーン
load dict-gundam.txt;
logprint ガンダム辞書のろーど。;
=end
# 再び辞書記述ゾーン
地名 : サンシャイン60,晴海,幕張,有明

スクリプト記述ゾーンにおける文法は、'$(' ~ ')'の間の文法と完全に同一ですので、ここでは省略します。スクリプト記述ゾーンで書いたスクリプトは、そのスクリプトが読み込まれた瞬間に実行されるということだけ注意してください。

辞書定義ゾーンにおける文法は3章以下で詳しく述べます。

コメント

行頭、空白記号以外の最初の文字がシャープ'#'だった場合、その行全体はコメントと見なされ、無視されます。

また、':rem'のみの行から':endrem'のみの行までの領域全体もコメントと見なされ、無視されます。

両者とも、後述の全ての文法に優先して処理されます。複数行に分けた記述の場合、行の先頭の文字に'#'が来ないように注意してください。その場合はクォート文字列にするなどして逃げることができます。

:rem
=======================================================
何行もコメントを書くときは
こんな風にすることもできますが
むしろ、あるエリアを一括してコメントアウトしたいときの方が
便利でしょう
=======================================================
:endrem
function checker $(
        if $[$@arg[0]=~"OK"] $(
                # OKの場合
                echo オッケー;
        ) else $(
                # 失敗(?)の場合
                echo シテオク;
        )
)

セキュリティレベルの指定

「何か。」から送られてくるイベントの中には、ローカルマシン(ゴーストの動作しているコンピュータ)以外の場所からSSTPなどを通じて送られてくるものがあります。このようなイベントを排除するために以下の記述によってセキュリティレベルを設定することが出来ます。

何も書かなかった場合、自動的に安全な設定(high)になるので、普通は記述する必要はありません。

記述する際には必ず初期化時に読み込まれるファイルに書いて下さい。kawarirc.kisに書くのが確実です。ゴーストが動作し始めてからはセキュリティレベルを変更することはできません。

  • securitylevel level ;

    セキュリティレベルをlevelに設定する。levelに書けるのは以下。

    値 意味
    0 / low 全てのイベントを許可する。
    1 / middle 外部からやってきたイベントを禁止。
    2 / high 当面は1(middle)と同じ。
    3 / ultrahigh 明確にローカルマシン発行と記されたイベントのみ許可。
# 指定する場合は、kawarirc.kisのできるだけ
# 先頭に近い部分を強く推奨します。
=kis
securitylevel ultrahigh;

=end

なお、華和梨の動作中、セキュリティレベルは数値表現(0~3)で"System.SecurityLevel"に格納されています。書き換えることは出来ません。

暗号化辞書

ネタをよりばれにくくするために、辞書ファイルに簡単な暗号化を施すことができます。暗号化には付属のkawari_encode2.exeを用います。使用方法は、DOSプロンプトで以下のように入力します。

kawari_encode2 辞書ファイル名1 辞書ファイル名2 ...

そうすると、キーワードを聞かれるので、適当なキーワードを入れてください。暗号化されたファイルが作成されます。

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

辞書ファイルのファイル名の拡張子を「.kaw」に変えたファイルが、暗号化された辞書ファイルです。辞書ファイル中、":crypt"と書かれた行から、":endcrypt"と書かれた行までが暗号化の対象となります。それ以外の行は元のまま残されます。

例 dict-sample.txt
#############################
# 著作権表示など
#############################

# ここから暗号化
:crypt
# 会話データ
sentence : \h昨日${npw}を${npp}で偶然見かけたよ。\e
sentence : \h${npp}良いとこ一度はおいで、はぁーじょいなじょいな。\e
# 名詞-固有名詞-人名 ( npw )
npw : 鈴木一郎 , 山本太郎
# 名詞-固有名詞-地名 ( npp )
npp : 首相官邸 , ホワイトハウス
npp : ${npw}の家
:endcrypt
# ここまで暗号化

このファイルを暗号化すると、次のようなファイルが生成されます。(見やすさのために一部行を折り返してあります)

例 dict-sample.kaw
#############################
# 著作権表示など
#############################

# ここから暗号化
!KAWA0001uJuYMVcg2jveOeM75g==
!KAWA0001uMvd1szd1tvdmIKY5NA1SCtCnMPWyM/FOkicw9bIyMU6f
TNMKeo0EToROhc6BTpeOfrk3Q==
!KAWA0001uMvd1szd1tvdmIKY5NCcw9bIyMUvfzoaOn46CTBSK8A6d
ToQOho6fTn5OnU6JznjOg46XToaOnA6DjpdOho6cDn65N0=
!KAWA0001uJuYLgQ2NJU0fS/0LgQ2NJUo1C4EmJCY1sjPmJE=
!KAWA0001uNbIz5iCmC9RLmAwUiDhmJSYNuouwykGIOE=
!KAWA0001uJuYLgQ2NJU0fS/0LgQ2NJUq1i4EmJCY1sjIm JE=
!KAWA0001uNbIyJiCmDZJKTIyFyv4mJSYO8I7Nzv7O9871 jv9O+A=
!KAWA0001uNbIyJiCmJzD1sjPxTp0MX4=
# ここまで暗号化

生成された暗号化辞書ファイル「dict-*.kaw」を、kawarirc.kisで指定して下さい。暗号化ファイルの拡張子は、.kaw以外に変更しても大丈夫です。

Phase 9.0.0から、辞書ファイルの文字コードはUTF-8とShift_JIS(CP932)の両方に対応しました。辞書ファイルの先頭にUTF-8のBOMがあるか、ファイル全体がUTF-8として正当ならUTF-8、そうでなければShift_JISとして読み込まれます(従来のShift_JIS辞書はそのまま使えます)。kawari_encode2は、入力がUTF-8(BOMあり、またはUTF-8として正当で非ASCII文字を含む)なら、暗号化ファイルの先頭にもBOMを付けます。暗号化辞書の読み込み時は、ファイルにBOMがあれば復号結果をUTF-8、なければShift_JISとして扱います。kawari_decode2は、入力にBOMがあれば出力にもBOMを付けます。なお、savecryptコマンドはUTF-8(BOMあり)で保存するため、その辞書はPhase 8.2.9以前の華和梨では正しく読めません。

暗号化したファイルを元に戻したい場合は、kawari_decode2.exeを使います。

kawari_encode2 辞書ファイル名1 辞書ファイル名2 ...

やはりキーワードを聞かれるので、暗号化の時に入力したキーワードを入れてください。

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

すると、拡張子が「.txt」の、復号化された辞書ファイルが作成されます。

注意:すでに同名のファイルがあっても上書きされてしまいます!


エントリ定義

華和梨は、全てのデータを「エントリ」に分類して保持しています。辞書定義とは、エントリ定義行を沢山並べたものです。エントリ定義は以下の書式('['~']'は、無くても良い部分)で書いてください。

エントリ名 [ , エントリ名, エントリ名 ...  ] : 文 , 文 , 文 ... <改行>
エントリ名 [ , エントリ名, エントリ名 ... ] ( 文 , 文 , 文 ... ,<改行>
文 , 文 , 文 ... <改行>
... )

Phase 8では、以前「単語」と呼んでいたものを、「文」と「単語」に区別していますが、主に細かい文法定義上の問題ですので気にしなくて結構です。

コロン":"やカンマ","の前後には自由に空白を入れられます。

エントリを複数並べる形式は、同じ文を複数のエントリに登録する場合に使って下さい。'('~')'で囲う後者の形式は、文を何行にも分けて並べたい場合に使ってください。'('~')'の間は自由に改行できます。これはその中に幾つもの文を並べられる点で、後述の「ブロック」とは異なるので注意してください。分かりづらければ使わなくても構いません。

# 単語の登録
お昼の挨拶 : こんちは。, ちーっす。, にょース。,やっ, ゴルァ
萌え, コスプレ : メイド, 眼鏡っ娘, チャイナ服

# 文章の登録も同じです
ランダム会話.夜更かし : \0\s[0]やっぱり緑茶でしょ。\1\s[10]…\w8…\w8は?\w8\0\s[3]コーヒーより緑茶の方がカフェインは多いよね。\1\s[10]そやな。\w8\0今夜も仕事は続きそうなので、緑茶を飲もうかと――――\1\s[11]スコッチ\0\s[2]……\w8\w8え?\1\s[11]スコッチ\e

# どこいつ的文章の例
ランダム会話.電波系 : 私たちは、たぶん、${地名}と${地名}にひきさかれる${人間関係}の、最初の世代だ。

# イベント処理スクリプトなど(後述)も同じ
イベント.OnCommunicate : $(if $[$(Reference 0)=="まゆら"] ……これは夢? それともまぼろし?)

エントリ名に使える文字は以下です。強調してあるものは、新たに使えるようになったものです。

英数字(A~Za~z0~9) / アットマーク('@') / クエスチョンマーク('?') / ピリオド('.') / アンダーバー('_') / いわゆる全角文字

ただし、アットマーク「@」は一時エントリに用いるので、普段は使わないでください。さらに、エントリ名の先頭に「.」は使えません。また、エントリ名中に「.」が連続した場合、一つにまとめられます(npw.....special→npw.special)。'System.'で始まるエントリは、華和梨が特別に使うエントリ名ですので勝手に使ってしまわないように注意してください。

以上の決まり事を守る限り、エントリ名は自由に決められます。自分にとって分かりやすい名前にしてください。いわゆる「全角文字」が自由に使えるので、積極的に使うと良いでしょう(タイプは少し面倒かもしれませんが……)。

文と文字列

文は、「文字列」「置換」「ブロック」を好きなだけ並べたものです。置換とブロックは後述します。

文字列は、例えば以下のようなものです。

\0\s[3]死んだゴーストにしてやれることなんて無いさ。\1\s[10]……心にもないことを\e

ほとんどの文章は、このように地の文としてそのまま書くことができます。しかし、ごく少数(以前よりも減っています)ながら、使えない文字があります。

まず、以下の記号は、必ず使えません。後述のブロックの中では、これ以外の制限はありません。

クォート2種 '"' , ''' / ドルマーク '$' / 丸括弧 '(' , ')'

エントリ定義文にベタに書いている場合、さらにカンマ','が使えません。また、インラインスクリプト中にベタに書いた場合、セミコロン';'が使えません。それぞれ、その場所で文を区切るために使われるため、使えないことは感覚的に分かります。実質的に気を付けなければならないのは最初に挙げたものだけです。

また、文の前後、及び行の先頭末尾にあるベタの空白文字(半角スペース、タブ、改行可能な場所では改行)は無視されます。こうした場所に空白を書くには、次のクォートを使ってください。

テスト文字列 : We are the champions ","my friends

=kis
echo ${テスト文字列}
# 「We are the champions ,my friends」が返る
=end

クォート文字列

前述の使えない文字や文頭文末の空白を文字列として華和梨の文に含めるには、クォート文字列を使います。

"    クォート文字列の中では空白も ${エントリ}呼び出し形式も、カンマ(,)や括弧や、セミコロン(;)も書けます"

上のように、ダブルクォート(")もしくは、(シングル)クォート(')で囲まれた文字列を「クォート文字列」と呼びます。クォート文字列では、ほぼ全ての文字を、「書いたまま」の形で出力できます。

クォート文字列では出力できない文字はありません。が、記述上、ちょっと変わった書き方をしないといけないことが2つだけあります。

  • 「\"」もしくは「\'」 (クォートに使っている文字の方)

    「"」もしくは「'」が出力されます。クォート文字列内で、クォートに使っている文字を出力するために使います。

  • 「\\」

    「\」が出力されます。「\」マークを出力するために必ず「\\」を書かなければならないわけではないことに注意してください。通常は、そのまま"\c"や"\s"などと書いてください。

さて、では「ダブルクォートを出力する」にはどうすればいいでしょうか。

"\""${偽名}"\""って呼ぶなーっ!

このようにするのが一つの答えです。もう一つは:

'"'${偽名}'"'って呼ぶなーっ!

いずれも、色が変わっている部分がクォート文字列です。

もう一つ。閉じクォート直前に\マークを出力するには:

echo-mode > 華和梨では、"こんな風に→「\\\"」"書くと、クォート直前に\が書けます。
華和梨では、こんな風に→「\"」書くと、クォート直前に\が書けます。

echo-mode > 華和梨では、'こんな風に→「\"」'書くと、クォート直前に\が書けます。
華和梨では、こんな風に→「\"」書くと、クォート直前に\が書けます。

ブロック

文中、'(' ~ ')'で囲まれた部分を「ブロック」と呼びます。'$( ' ~ ')'は、後述のインラインスクリプトですので、間違えないでください。

ブロックの効果は2つだけです。

  • ブロックの中を強制的に一つの単語要素とみなさせる(そのため、','や';'などの、文を区切るための文字が単語の一部としてみなされます)
  • ブロックの中では改行が許される

空白文字は通常通り無視されます。置換のルールなども全て他の場所と同じです。また、ブロック用の括弧'(', ')'は、もちろん出力には現れません。

ブロックは、主に複数行に分けて文を書きたいときに使われます。

# 従来
sentence.メインメニュー : \t\0\s[0]メニューだよん\n\n\q0[RandomTalk][トーク]\q1[TestCommand][テスト]\q2[Communicate][コミュニケート]\q3[PrefTalk][トーク設定]\q4[Cancel][キャンセル]

# Phase 8
sentence.メインメニュー : (
        \t\0\s[0]メニューだよん\n
        \n
        \q0[RandomTalk][トーク]
        \q1[TestCommand][テスト]
        \q2[Communicate][コミュニケート]
        \q3[PrefTalk][トーク設定]
        \q4[Cancel][キャンセル]
)

ブロックを使って複数行記述をしていても、エントリ定義の最後には必ず改行が必要なことに注意してください。

呪文 : (修行するぞ修行するぞ
        修行するぞ修行するぞ
        修行するぞ修行するぞ)

上記は、こうも書けます。

呪文 : (
        修行するぞ修行するぞ
        修行するぞ修行するぞ
        修行するぞ修行するぞ
)

しかし、これは間違いです。

呪文 :
(
        修行するぞ修行するぞ
        修行するぞ修行するぞ
        修行するぞ修行するぞ
)

なぜなら、「呪文 : 」のところで、エントリ定義文が終わってしまっているからです。

複数行対応

「開き括弧に対応する閉じ括弧が来ない間は改行がいくらあってもよい」というのが、Phase 8の複数行対応の考え方です。ブロック、エントリ呼び出し、エントリ配列呼び出しの添え字部、演算式、インラインスクリプトなど、括弧に囲まれた場所すべてにおいて、文法的に空白が許される場所ならばどこにでも改行が入れられるようになりました。そうした場所では改行は空白文字(スペースやタブと同じ)と見なされます。

また、括弧書きのエントリ定義(「エントリ名 '(' ')'」形式)により、エントリ定義中に全く自由に複数行記述ができるようになりました。


置換とエントリ呼び出しと実行

最も重要な章です。疑問が湧くたびにここに立ち戻り、繰り返し読んで理解してください。

上記までで、エントリが呼び出されたとき、どんな文字列でも出力できるようになりました。しかし、華和梨が真に華和梨たる知的動作を行うためには、次に述べる置換機能を欠かすことができません。

華和梨は4つの置換機能を持っています。そのいずれもが、'$'で始まり、括弧記号などで決められる特定の範囲を持っています。

# [1] エントリ呼び出し
${エントリ名}

# [2] エントリ配列呼び出し
$エントリ名[数字]

# [3] 演算式
$[式 (1+2*3とか)]

# [4] インラインスクリプト
$(スクリプト文; スクリプト文; スクリプト文 ... )

これらの詳細については順番に詳しく解説します。ただ、いずれの機能についても、置換部('$'で始まる記述)を、その実行結果で置き換えるという一点は共通です。中には実行結果が無いものもあります。そうしたものは、単に置換記述が見えなくなるだけ(空文字列で置き換えた)となります。

また、置換はその文が実行されるたびに起きます。ですから、特にランダムな動作をするエントリ呼び出しなどは、文を実行するたびに違った結果を返します。

エントリ呼び出し

エントリに登録した文を呼び出すことを「エントリ呼び出し」と言います。例えば、「人名」という名前のエントリを呼び出すには、以下のように書きます。

# 人名エントリに幾つかの人名を登録
人名 : サザエ, カツオ, ワカメ, タラ, フグタ

# 人名エントリを呼び出すときは、このように書く
${人名}

エントリ呼び出しのすることは2つです。

  1. エントリに登録されている文の中から一つをランダムで選ぶ
  2. 選んだ文を実行する

2番目の「実行する」ことが重要なので、覚えてください。場合によって「評価する」「評価される」などとも呼ばれます。

次節に続きます。

実行

華和梨辞書でエントリに登録するときの文は、決してそのまま「何か。」などに送られる形ではありません。

\0\s[0]私は${なまえ}。${しょくぎょう}やってます。相方は${ともだち}だよ。\e

上の例は、${なまえ}、${しょくぎょう}、${ともだち}のそれぞれが正しい文字列に置き換わることを意図しています。

# エントリ定義
なまえ : さくら
しょくぎょう : ゴースト
ともだち : うにゅう

このような辞書があった場合、先ほどの例を実行すると正しい文字列になるでしょう。

\0\s[0]私はさくら。ゴーストやってます。相方はうにゅうだよ。\e

つまり、

「実行」=「置換を実際に行うこと」

と覚えてください。華和梨を使い込んでいくと、置換が行われるタイミングを知ることがどうしても必要となります。込み入った文を書いて混乱してしまったとき、「実行(あるいは評価)」が行われるのがいつなのか、それに注意するようにしてください。

このルールを知れば、クォート文字列の扱いと置換が異なることも理解できると思います。クォート文字列における「エスケープ」(\"など)は、実行されるたびに結果が変わる必要はありません。そこで、読み込まれた段階で既にエスケープの処理は行われています。

さて、それ自体が置換機能であるエントリ呼び出しが、選び出した文に対してさらに実行を行うため、必然的にエントリ呼び出しは何度も行われることになります。

なまえ : さくら , ふたば
しょくぎょう : ゴースト , デスクトップマスコット
ともだち : うにゅう , ただきちさん
批評 : 私から言わせれば${なまえ}って、${しょくぎょう}やってる場合じゃないと思うの。
批評 : 私、${なまえ}は嫌いだけど${ともだち}って好きだな。

# 入り口。sentenceというエントリ名は歴史的理由による。
sentence : ${批評}

ここで、${sentence}を実行します。

  1. 登録された唯一の文である「${批評}」が選択される。

  2. 実行する。「批評」を指定したエントリ呼び出しが起きる。

    • (例えば)「私から言わせれば${なまえ}って、${しょくぎょう}やってる場合じゃないと思うの。」が選択される。
    • これも実行する。まず、「なまえ」のエントリ呼び出し。

      • 「さくら」が選択される。
      • 実行する。置換子が無いので、変化無し

      「${なまえ}」が「さくら」と置き換えられる。

      「しょくぎょう」のエントリ呼び出し。

      • 「デスクトップマスコット」が選択される。
      • 実行する。置換子が無いので、変化無し

      「${しょくぎょう}」が「デスクトップマスコット」と置き換えられる。

    「${批評}」が「私から言わせればさくらって、デスクトップマスコットやってる場合じゃないと思うの。」と置き換えられる。

このように、エントリ呼び出しは、置換子が無くなるまで全ての置換を実行します。

置換の入れ子

置換は、お互いに入れ子状にすることができます。置換の種類は問いません。入れ子の深さにも制限はありません。

$(set あの話題 ${この話題})
$[ ${幅} * ${高さ} ]
${ 駄文のリスト.${カウンタ} }
$駄文のエントリ[ ${カウンタ} ]
今から10年後って言うと、西暦$[ $(date %Y) + 10 ]年だね。

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

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

  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}}

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

集合演算式

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

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

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

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

優先度について:

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

100 - 10 * 2 = 80

など。

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

${ 男性 + 女性 & 作家 }

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

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

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

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

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

エントリ配列呼び出し

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

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

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

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

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

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

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

echo-mode > $a[0]
零
echo-mode > $a[2]
弐
echo-mode > $a[-1]
参

履歴参照(中級)

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

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}」)。

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

一時エントリ(上級)

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

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

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

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

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

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

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

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

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

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

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

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

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

再入(その関数の実行中に再び同じ関数が呼び出されること)不能

関数型エントリ実行終了時に引数エントリをいちいちクリアしない限り、以前の値が残っている可能性がある

呼び出す際以外に、偶然から引数エントリの値が書き変わる可能性がある

Phase 8のユーザ定義関数では、華和梨システムによって、引数は自動的に呼び出された関数側の一時エントリ@argにセットされますので、安心して引数を使うことができます。複数のユーザ定義関数が互いを何度も呼び合っても、その引数のエントリが上書きされたり、過去の引数が残っていたりする危険はありません。

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


演算式

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

$[ 演算式 ]
# 一年は何分?
$[ 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

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

比較演算

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

$[ 1 == 10 ]

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

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

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

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

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

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

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

論理演算

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

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

$[ ! "ほげ" ]
# "ほげ"は、上述の偽の条件に当てはまらないから真。
# 従って、その逆である「偽」を返します。
# 偽を返す場合は"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以外が返ったところでその値を返す。

ビット演算

ビット演算('&', '|', '^')は、数値を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"

数値と文字列の扱い

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

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

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

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

インラインスクリプト

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

$(date %H)

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

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

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

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

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

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

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

$(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)のどちらか一方だけ、実行(=置換)されます。

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

if

構文コマンドのうち、ifはPhase 7.3.1と比べ、特に文法が変わりました。elseとelse 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 <すべての条件が偽の時の文>)

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

エントリと違い、いくつかのコマンドは、ユーザが定義しなくても実行できます。このようなコマンドを、「組み込みコマンド」と言います。一方、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回とも違う結果になります。