仕様書¶
華和梨の仕様書を 1 ページにまとめています。章ごとの内容は右の目次から飛べます。
KIS プログラミングリファレンス¶
本ドキュメントは、華和梨Phase8上でのプログラミングリファレンスであり、ミドルウェア制作者、華和梨の移植を行うデベロッパをターゲットとしている。ゴーストベンダ向け簡易ガイドとしてはユーザーズマニュアル、華和梨初心者向けガイドとしてはGetting Startedをそれぞれ用意しているので、そちらを参考にされたい。また、華和梨を使用したゴースト制作初心者向けの導入については、「華和梨を使い倒す」「私的「華和梨」応援団」などの解説サイトを参考にされたい。
はじめに¶
半年ぶりの華和梨メジャーバージョンアップとなるPhase8(開発コードネーム"KIU")は、従来、華和梨の長所であり、また弱点とされてきたKIS(Kawari Inline Script)について、その記述性、可読性を大幅に向上させることを目的として作成された。既にKISは辞書定義全体、ひいては華和梨利用全体を支えるシステムとなっており、その文法を厳密化し、機能を整理、強化することは大変重要である。これによってより高度なゴースト制作、ミドルウェア制作が可能となるほか、華和梨本体に埋め込まれていたシステム依存部分をスクリプト化することが可能となり、変更の多い「何か。」に対して柔軟に対応できる。
Phase7系最新バージョンであるKAWARI.kpcg/7.3.1に対して行った変更の、主なポイントは次の通り。なお、"(7.9.0)"と付いているものは、KAWARI.meister/7.9.0から継承されたもの、"(ebi)"と付いているものは、えびさわさんバージョンから継承されたものである。7.9.0と"KIU"のコンセプトはほぼ完全に直交しており、そのマージは驚くほどスムーズだった。
- 複数行対応
- 新構文
$[ ] - 新構文()
- 新構文
$...[] - 旧ifを廃止。新構文コマンドif~else if~else, break, return
- KIS関数
- 一時エントリ(自動変数)
- 履歴参照ルールの厳密化
- エントリ集合演算の強化
- (7.9.0)kawari.iniの廃止
- (7.9.0)従来型マッチエントリの廃止
- (7.9.0)内蔵piro廃止
- (7.9.0)辞書ファイル中のKIS直接実行
- (7.9.0)KISコマンドの大幅増強
- (7.9.0)辞書インターフェースの整理
- (7.9.0)エントリツリーの導入
- (7.9.0)各種外部インターフェース(SHIORI/SAORI-Server)の刷新
- (ebi)SAORI-Clientの導入
- 全文法の厳密化
- 記述エラー検出機能の導入
華和梨とは何か?¶
華和梨は、生まれた当初は互換栞、現在は栞サブシステムと呼ばれる存在である。しかし、華和梨が真に目指している目標は、栞として優れていることだけではなく、DA(Desktop Agent)記述方式として優れていること、もしくは、優れた方式を見いだすためのステップとしてあることである。
基本概念¶
華和梨という概念の基本は、「何か。」用SHIORI(もしくは偽AI)としての機能には直接依存しない、極めて簡素な、一種の思想である。その骨子は以下のようなルールで説明することができる。
-
幾つかの文字列(単語、ネタ、KISプログラムなど)をグループ化してグループ名を付ける。グループのことを「エントリ」、グループ名のことを「エントリ名」と呼ぶ。
エントリ1 : 単語1, 単語2 エントリ2 : 華和梨の梨はgoogleで引くと3番目に引っかかる梨……"(鬱)"
-
文字列の中に、エントリ呼び出し機能を含めることが出来る。「
${対象エントリ}」が、基本的なエントリ呼び出しの書き方である。<${エントリ1}>は、単語1か単語2
-
エントリ呼び出しが行われると、エントリに含まれている文字列のうちのどれか一つがランダムに選ばれ、呼び出し結果として返る。
「<${エントリ1}>は、単語1か単語2」を評価(=実行) → <単語1>は、単語1か単語2 or <単語2>は、単語1か単語2
-
選ばれた文字列のなかに、再び「
${対象エントリ}」などのエントリ呼び出しがあった場合、さらにもう一度エントリ呼び出しが行われ、その結果がエントリ呼び出し記述と置き換えられる。エントリ3:<${エントリ1}>は、単語1か単語2 「『${エントリ3}』は常に正しい。」を評価 → (1) 「<${エントリ1}>は、単語1か単語2」は常に正しい。 さらにエントリ1を呼び出し、「${エントリ1}」の部分と置き換える。 (2) 「<単語1>は、単語1か単語2」は常に正しい。
- それ以上置き換えが行われなくなったところで、最終的な結果となる。
- 華和梨の機能は、任意の名前のエントリを呼び出すことでキックされる。例えば「何か。」本体からイベントリクエストが行われた場合、System.Callback.OnEventエントリが呼び出され、その結果が本体へのレスポンスとなる。
何度も辞書を引き直す(ここではエントリ名に対応する文を探すことを、辞書を引く、と呼んでいる)ことで実現されるこの多段連想配列的機能は、最も初期の華和梨関係者の一人によって「逆yacc」と命名された。yaccを初めとするパーサジェネレータは、ある言語で記述された文章(大抵はプログラムである)を、その言語の文法に従って分解するモジュールを、特定の(大抵はBNFライクな)表記法によって表された文法自体から生成する。言い換えれば、「様々な文章を一つの文法に落とし込むもの(を作るもの)」である。
華和梨は逆に「一つの文法から様々な文章を生成するもの」である。「文法的に正しい(この場合、文法は日本語文法を指すとは限らない)」文章を簡単に、ランダムに、大量に作り出すために逆yacc機能は存在する。
下記の例のとおり、「文法」はエントリ呼び出しによって定義される。
食べ物 : ${固有人名}が作ったアレ, きのこ, ワッフル, カレー, アイス, ジャム
地名 : 東京 , 秋葉原 , 浅草
固有人名 : ぐにゅう、うにゅう、うにゅん、ほねゅう
人名 : ${地名}に住んでる${固有人名}
# 簡易うわさ話1
sentence : \0${人名}ってさ、${食べ物}が好きなんだって。\n\1ふーん。\n\0もう少し、なんか言えよう…\e
もっとも量的な側面は現状は決して満足できるものではない。仮にDAを1日3時間起動し、1分に1度喋らせ、その文章が1ヶ月の間重複を起こさないためには、最低でも5400本もの異なる文章を生成しなければならない(一ヶ月間に!)。また文章生成ルールの単純さは語るまでもなく、これが一種の「面白さ」を演出してはいるものの、DAをそのスタイルに画一化してしまう働きがある。質・量の不足(=飽き)に対して、ユーザは、ゴーストを切り替えるなどの方法で対応しているが、単体のゴーストについても逆yacc以上のものが求められている。
何が華和梨を難しくさせているか¶
こうしたシンプルな概念を基礎とするにも関わらず、華和梨が難しいSHIORIであるのは何故か。それは、「歴史的経緯による整理不足」と「中途半端な記述力」のためである。
華和梨は元々、魅力的なプラットフォームとなりうる素質を持ちながら、クローズドな状態のまま開発停止してしまった「あれ以外の何かwith偽春菜」(後に「あれ以外の何かwith "任意"」、「何か。」、「伺か。」)を生き延びさせること(だけ)を目的に開発された。華和梨のソースが、コア350行、SHIORIインターフェース250行であった古き良き時代(ちなみに現在は総計8000行に達している)、SHIORIに求められる機能は事実上ただ一つ、「自発トークの際の文章を生成すること」のみであった。華和梨は「sentence」エントリを呼び出すだけで良かったのである。そのためMeisterは以下のような意図をもって華和梨を作成した。
華和梨の当初の目標は「最小コストで最大効果のランダムトークエンジンを最短期間で実装する」というものでした。
Meister :KIS Programming Language Chapter-0
見通しが良くコンパクトな設計を持った華和梨は各種のプラットフォームに移植され、最も移植性の高いSHIORIともなった。
しかし、DAに当然求められる機能を追及したとき、(偽)AI部が担うべき作業がランダムトークだけで済むはずがなく、また、済むべきではないと、本体サイドも華和梨サイドも承知していた。華和梨にとっては、イベント反応とランダムトークを、まったく同じ枠組みで扱えることが一つの強みだった。つまり、「どんなイベントが来ようとも、最初に呼び出すエントリの名前が変わるだけ」ということである。
その後、後から振り返れば史上最も混乱した一ヶ月半が訪れた。本体は、dllのメソッドを直接呼び出すSHIORI/1.x系から、HTTPを擬したプロトコルによる粗結合RPCモデルであるSHIORI/2.0系への移行(2001/04/22~04/24)、SHIORI COMMUNICATE/1.1(2001/04/17)、SHIORI/2.1 COMMUNICATE(2001/04/26)、SHIORI/2.2イベント系リクエスト(2001/04/30)など、現在の本体の基本となる大幅な仕様変更を立て続けに行った。
一方、華和梨サイドでは、全イベント反応を統括していたpiroモジュールをSHIORIに統合すべく、"Piroject-X"がひそかに進行を始め(2001/03/17スタート)ていたが、本体の(正しい)仕様変更が先行したために、その動きは結果としては潰れた。それでも、華和梨内部での動作は当初構想された通りのものとなった。すなわち、呼び出しエントリ名によって動作を制御する方式である。その他の変更は全て本体とのインターフェース部に集中し、華和梨の核となる機能(=辞書と逆yacc)に変更はなかった。
ただし、イベント処理を行うことは、即ちスクリプトレベルのプログラマビリティの必要性に繋がる。特にコミュニケーション部においては内部状態変数の操作は必須と思われたし、ごくごく一部の開発者(つまりC++とSTLと華和梨の構造を理解する人間)に負担が集中しないように、誰でも修正が可能となるような簡易なスクリプトとすることも必須条件である。これは、ペルソナウェアをいじった経験があり、なおかつ本体の強烈な更新ラッシュに疲れ果てていたMeisterには初めから認識されており、この対策として2段階のスクリプト言語導入が計画された。その第1段がKISである。この辺りの経緯とKISの設計については、彼の出した文書「KIS Programming Language Chapter-0」を参考にされたい。ここではKISの特徴の一つとして、できる限り通常の辞書記述に制約を加えないために幾つかの工夫が施された点を指摘しておく。KISは、汎用スクリプトプログラミング言語ではなく、華和梨の辞書記述と共存することを前提とした言語である。
KISは一旦は成功を収めた。ランダムトーク記述のしやすさとプログラマビリティとのバランスの良さが、ゴーストベンダに受け入れられたと考えられる。その後、インタプリタ設計の見直しと共に華和梨はPhase0.5αからPhase5.x系へ移行し、「辞書+逆yacc+KIS」の構図は完全に安定した。「プログラマブル準AI華和梨」の誕生である。心配されていたテンプレーティング問題(誰がどう作るべきか、開発チーム内でも戸惑いがあった)も、こやま☆あきら氏による「KEEPS」の登場により解決し、華和梨本体に対するパッチも少しずつ供出されるようになり、限定的ながらバザールモデルが機能し始めたかに思えた。
だが、ここで二つ想定外の事態が起きた。
一つは第2段として計画していたFIS (Fabricated Intelligence Script:いんちき知能スクリプト。仮称である)言語が、いつまで経っても導入できる目処が立たなかったことである(モジュール自体は完成したが、その頃には華和梨が普及しすぎてしまったため、推論DB(DataBase)付き超重量級AIエンジンであるFISに華和梨の名前を名乗らせるのは無理があった、というのが実情に近い)。FISが持つ推論DBには要素の順序が保証されないという特性があり、KISにも『将来を睨んで』その制約を課した結果、不自由なコマンドセットを長く引きずることとなった。また、開発チーム内では、FISが待っている以上KISを拡張しても無駄である、あるいは、移植性を落とすことになる、との意識が強く、KISの充実と強化に対しては一貫して否定的であった(実際にはFISを他言語に移植することの方が遙かに非現実的である)。これらの結果、KISは長い間、「クリーンな華和梨コア」に寄生する不格好な害虫という不遇の扱いを受け続け、さらなる洗練を受けないまま放置されることとなる。複雑さを増す要求に対し、KISは次第に使いにくいものとなっていった。
もう一つは、KISがイベント処理以外の場所で使えてしまうこと、あるいは、イベント処理以外の場所に「滲み出して」いること、もしくは、イベント処理という概念の実装が明確でないことである。ペルソナウェアでは、AI記述の全てを綾織と呼ばれるC言語ライクなプログラムで行っていたことが、ペルソナ制作の敷居を著しく引き上げていた。ゴースト制作者にとって、通常のトークを記述する上ではプログラム的詳細を可能な限り排し、演出面に集中できることが望ましいのは、優れたSHIORIシステムである里々などを見ても明らかである。そもそも"さくらスクリプト"(タグによる抽象化)が一つの例であるし、逆yacc構造はプログラム性を排したトーク記述力強化を目指していた。トーク記述とそれ以下のミドルウェアは可能な限り分離されなければならない。にもかかわらず、現状ではゴーストベンダがKISに全く手を触れずにいることは難しい。KISの設計目標の一つである「辞書記述との親和性」は、この点では裏目に出たと言える。KISの記述力の貧弱さ自体がミドルウェア層とトーク記述層の完全分離を困難にし、あげく、ミドルウェアを解析しなければならなくなったゴーストベンダに対して、読みにくいスクリプトを見せつける結果になっているのである。
Phase 8における変革¶
Phase 8は、過去、および、未来との訣別である。FISの華和梨搭載予定はキャンセルされた。華和梨は、Meisterを始め開発チーム全員の予想を遙かに超えた所へ行き、もはや修正不可能な軌道に乗ってしまった。華和梨については、後はこの道を出来る限り先へ進む以外にない、と開発チームは判断した。面白いことに、その結果、「未来ある」華和梨に課せられていたあらゆる制約が消滅し、華和梨は自由に拡張可能となった。これを押し進めることで、次世代華和梨像が徐々に明らかとなった。Phase 8の精神は以下である。
-
バイナリから余計な要素を全て削除
雑然としていた華和梨の機能を整理し、凝縮した。SHIORIインターフェース部は、「SHIORIプロトコルをエントリ呼び出しに置き換える」単機能に集約。主にPhase5.0以降に積み重ねられた不透明な機能は、全て削除した。辞書に課せられていた「インテリジェントさ」の制約を排除した。kawari.ini相当機能はKIS構文に一本化し、削除した。イベント処理のインターフェースを明確にし、ほぼ全ての権限をKISに与えるためである。
-
全てはKISが行う
KISはもはや「おまけ」ではない。従来、華和梨のバイナリが行っていた作業も含め、SHIORIリクエストを受けてトークを呼び出すまでの動作は、全てKISミドルウェアが引き受ける。可読性が向上した文法、強化された構文、エントリ操作コマンド群が、これを助ける。華和梨Phase 8はKISなしではSHIORIとして動作しない。
-
レイヤリング
Phase 8はレイヤリングエンジンである。既にその流れになっているが、改めて明言しなければならない。KEEPS, OpenKEEPS, FUDS, KLAFTなどのミドルウェアは華和梨本体と同程度に重要なパッケージであり、華和梨に匹敵するブランドとなるべきである。ミドルウェアは必ずしも「汎用」である必要はない。ゴーストベンダはゴーストの性格に合わせてミドルウェアを自由に選択することができ、その上で、華和梨システムの逆yacc機能による高い記述性と、ミドルウェアによる抽象化の恩恵を得る。理想的なケースにおいては、ベンダはゴースト記述時には生のKISを一切触らずに、トークの演出に集中できる。「華和梨の難しさ=ゴースト制作の難しさ」は、レイヤリングによって分散される。
1年以上(ウォッチしていた期間を含めれば、ほぼ2年間)「何か。」について研究し、ゴーストとは何か、ゴースト制作とは何か、ゴースト制作をなるべく楽に行い、しかも多様性を出すためにはどうすれば良いかを考えてきた華和梨開発チームの、現時点の結論がPhase 8である。これは、最初の実用版であるPhase 0.42、始めてKISを搭載したPhase 0.5α4以来のインパクトを持つバージョンアップとなる。もはや自由に研究できなくなってしまった本体の今後がどうなるかは不明だが、現状の疎結合モデルを用いている限り、この華和梨はもはやバイナリ更新を必要としない(もちろん、それは甘い考えだろう)。ネイティブコードを必要とする機能追加は、最新のSHIORI拡張仕様であるSAORIを通じて行うことができる。本体の仕様が変更された場合でも、KISで書かれたミドルウェアは充分な可読性を持つため、ある程度の技術力があれば誰でもそれを修正し、ネットワーク更新に乗せることができる。非常に近い将来、複数のネットワーク更新先が選べるようになると思われるため、ミドルウェアの自動バージョンアップが可能となることも十分考えられる。ゴーストベンダは、より容易にゴーストを制作することができ、ゴーストの(本体やSAORIのバージョンアップによる)「事故死」を、より長い間免れることができるだろう。また、必要に応じてミドルウェアまで踏み込んだカスタマイズを行う際にも、以前のような大幅なギャップを生むことなく自然な形で拡張が行えるようになるだろうし、ミドルウェアにはある程度それが要求されるだろう。
注記:
上記結論は、ゴーストベンダによるKIS記述を否定するものでは全くない。また、ゴースト独自の「複雑な」知性を実現する健全な試みを否定するものでもない。そのような不安を抱かれた方がおられたら陳謝する次第である。
この節を書いた動機を、より現実に即して言うならば、古くから開発チーム内で言われてきた言葉:「華和梨を使うな。KEEPSを使え」である。
閑馬氏は「実装記録」中で
「汎用」は使う側にとって簡単なぶんだけ作る側にとっては難しい
と書いたが、これは真実である。だが、完全に汎用なシステムから完全に個性的なゴーストに至るまでのギャップは、若干の個性を持ったミドルウェアを挟む、レイヤリングによって吸収することができると考えられる。
使い始めの状態において、ベンダはミドルウェアの中身を理解しなくてよい。ミドルウェアはブラックボックスであり、使う側からすれば、れっきとした一つのSHIORIである。
ベンダが使用中のミドルウェアに不満を感じた場合、初めてその構造と動作を解析し、変更を加えるなり、フルスクラッチで書き直すなりすればよい。KEEPSなどのミドルウェアが他のSHIORIと唯一異なる点は、それがKISで書かれており、適度な抽象度の上にあるために、一般のゴーストベンダにも手が出せるということである。ただそのためには、KEEPS/OpenKEEPSを初めとしたミドルウェアが「読みやすく」、また、「書きやすく」なければならない。その目的のためにPhase 8が用意された、と言っても過言ではない。
重要なことは、このようなミドルウェアを使うためのドキュメントが今現在全く不足していることである。これは現状、ミドルウェアの社会的地位がSHIORIに比べて不当に低いことと、決して無関係ではないだろう。
華和梨言語仕様¶
以下にKIS言語の仕様を示す。始めに言語の動作を述べ、次章に辞書記述上の文法を詳細に解説する。不備があれば、是非配布サイトまで一報願いたい。
用語解説¶
-
評価
おおよそ、次のような意味:要素を実行すること。動作させること。
-
オリジン
序列をその数字から数え始めることを表す。0オリジンならば0から、1オリジンならば1から数え始める。
-
空文字列
内容のない文字列を指す。「空文字列が出力される」とは、実質的には「何も出力されない」と同義である。
-
真偽値
if文などで用いられる値。ある条件が満たされる(もしくは正しい)ことを「真である」、満たされない(もしくは間違っている)ことを「偽である」と言う。「
"何か。"=="伺か。"」は偽であり、「100>=10」は真である。
華和梨が扱う値について¶
華和梨は文字列モノタイプ言語である。即ち、全ての値を文字列表現で保持している (少なくとも、そう見えることを要求される)。
そのため、ある値がどう解釈されるかは、それを解釈する側(オペレータ)の特性に依存する。"true"は、if文の条件節に用いられた場合は真偽値であるが、単純に文字列として加工し、出力することもできる。"10"などの数値はさらに複雑で、演算式の中では数値として扱うことができ、条件節では真偽値として扱われ、また文字列として出力することもできる。
なお、華和梨での真偽値は以下のように判定される。
"false"(論理としての偽), "0"(数値としての偽), ""(文字列としての偽) 以外は、全てを真とする。
辞書とエントリ¶
華和梨は一つの「辞書」。辞書は「エントリ」の集合。エントリは「文」の順序付きリスト。
全てのエントリは、'.'で区切られた文字列で構成された名前(エントリ名)を持つ。エントリ名に使える文字は、半角英数文字,多バイト文字(いわゆる全角文字),'.', '_', '?', '@'である。ただし、数字のみのエントリ名はアクセス不可能なので有効でない。'@'で始まるエントリ名は一時エントリ(後述)である。エントリ名は、例えば以下のようなものである。
System.Callback.OnEvent
event.OnSecondChange.handler
@arg
反応.さくら
文¶
文は「単語」の連なりであり、評価(実行)されることによって文字列を出力する。単語は「文字列」「置換子」のいずれかの連なりである。華和梨の全てのデータは文表現で保持され、エントリにグループ分けされて格納されている。
文字列は実行されると、そのままの値を出力するものであり、その動作は実行時には変更されない(静的)。文字列はさらに、「ベア(プレーン)文字列」、「クォート文字列」に分けられるが、この2つの差異はソース表記上のものである。以下は、文字列の例である。
\0\s[0]これはベア文字列です。\1\s[10]普通はさくらスクリプトやな。\e
"\0\s[0]これがクォート文字列。 '空白'を入れたり、 ${\"普通\"は使えない文字を使ったり}できます。\e"
置換子は、記述上'$'で始まる、「エントリ呼び出し」「エントリ配列呼び出し」「インラインスクリプト」「演算式(以前のexpr)」のいずれかであり、文の実行時に置換内容を評価し、その値を出力する(記述上、置換子と置き換えているように見える)。また置換の際、置換内容が履歴参照リストに追加される。
${エントリ呼び出し}
$エントリ配列呼び出し[10]
$(echo インラインスクリプト)
$["演算式" != "expr"]
文の同値関係¶
華和梨において、内部的な同値性は重要である。構文解釈的に同じものと解釈できるものは、内部で同一と見なされる。また特別な場合として、同一単語内で連続して記述された文字列は、内部的に単一の文字列に置き換えられる。ただし、ブロックなどで分割されている場合は除く。
文の同値関係はエントリ集合演算時に問題となる。
cycm : "Catch"" ""You"" ""Catch"" ""Me"
cycm2 : "Catch You Catch Me"
echo-mode > ${cycm&cycm2}
Catch You Catch Me
エントリ呼び出し¶
通常のエントリ呼び出しは、呼び出すべきエントリ名を1つだけ持つ。実行されると、指定されたエントリに属する全ての文の中から1つをランダムで選び、その文を評価した結果を出力する。以下にその例を示すが、評価結果はランダムである。
a : foo, bar
echo-mode > ${a}
foo
echo-mode > ${a}
bar
echo-mode > ${a}
bar
なお、エントリ名1つを対象としたエントリ呼び出し1つだけの文がエントリに属した場合、次で述べるエントリ集合演算時に特殊な扱いを受ける「純粋仮想単語(本来は文。歴史的経緯により単語)」となる。エントリ呼び出し時の機能は変わらない。
エントリ集合演算呼び出し¶
エントリ集合演算呼び出しでは、エントリの持つ文の集合を使って集合演算を行い、結果として残った文からランダムで1つを選び、その文を評価した結果を出力する。なお、エントリの持つ文に前述の「純粋仮想単語」が存在した場合、そのエントリ呼び出しが指定する先のエントリも合わせて展開する。
集合演算に使える演算子を以下に示す。優先度は積が高く、和と差は同等である。また、'('~')'による優先度操作が可能である。
| 演算子 | 意味: 選ばれる要素 |
|---|---|
& |
積集合: 2つの集合の双方に含まれる要素 |
+ |
和集合: 2つの集合のどちらかに含まれる要素 |
- |
差集合: 左辺の集合に含まれ、右辺の集合に含まれない要素 |
以下に例を示す。
a : 1, 2
b : 2, ${b.sub}
b.sub : 3
c : 3
echo-mode > ${a&b}
2
echo-mode > ${a-b}
1
echo-mode > ${a+c}
3 # あるいは 1, 2
echo-mode > ${a-b&c}
2
echo-mode > ${(a-b)&c}
履歴参照¶
履歴参照は数字のみのエントリ名によるエントリ名呼び出しの形で記述され、そのコンテキスト(後述)内における、指定数(0オリジン)回目の置換結果を参照し、出力する。指定数値が負の場合、最後尾から数える。履歴参照結果も履歴に入る(仕様変更)ことに注意。
地名 : エロマンガ島、サンドイッチ島
人名 : マキコ, ムネオ
echo-mode > \0\s[0]${人名}がね、今度${地名}に${0}ハウスを建てるんだって。\1\s[10]なんだって${-2}なんかに……\e
マキコがね、今度エロマンガ島にマキコハウスを建てるんだって。\1\s[10]なんだってエロマンガ島なんかに……\e
エントリ配列呼び出し¶
Phase 8からエントリ内容の順序が保証されるようになり、それを受けて、エントリへの配列的アクセスが強化された。その一環として、エントリへの配列呼び出し構文が用意される。
エントリ配列呼び出しは、エントリ名とインデックス値の指定を伴う。インデックス部は演算式であり、呼び出し時に式を評価した結果がインデックス値となる。評価時には、指定エントリに属する文のリストの内、インデックス番目(0オリジン)の文を評価し、出力する。指定数値が負の数であった場合は、指定エントリに属する文の数と数値を足し、その数をインデックスとする。インデックス番目の文が存在しない場合、空文字列を出力する。
a : 零、壱、弐、参
base : 1
echo-mode > $a[0]
零
echo-mode > $a[3]
参
echo-mode > $a[${base}+1]
弐
echo-mode > $a[-1]
参
echo-mode > $a[5]
echo-mode > $a[-5]
インラインスクリプト(KIS)¶
インラインスクリプトはスクリプト文の連なりであり、それらを全て評価した結果を連結して出力する。ただし、break, return構文が実行された場合はこの限りでなく、インラインスクリプトや、場合によっては文全体の評価を途中で終了する。
スクリプト文¶
スクリプト文は「構文コマンド(一般的なプログラミング言語で言う制御構造)」「関数コマンド」のどちらかを実行するものである。
構文コマンドであるかどうかの判定は、静的に(通常は読み込み時に)行われる。スクリプト文の最初の単語全体がベア文字列(次節参照)であり、かつ存在する構文コマンドである場合に限り、スクリプト文は構文として扱われる。最初の単語がエントリ呼び出しなどの置換を含み、その評価結果が"if"や"while"などであっても、それはコマンド呼び出しのコマンド名として扱われる。
構文¶
Phase 8で用意される構文は、以下の通りである。
-
分岐
-
if文法:
if <条件単語> <真の時に評価される単語> [else if <条件単語> <真の時に評価される単語> [else if ... ] ] [else <全ての条件が偽の時に評価される単語>]条件を評価し、真であれば対応する単語を評価する。条件に合わない単語は評価されない。すべての条件が偽の時、else節があれば、その単語を評価する。
条件単語、条件に従って評価される単語は、KIS中に書ける単語であれば何でも良い。
各評価節の評価中は、条件単語の評価結果は最新の履歴に入り、
${-1}で取得可能。if文全体を終了後、履歴はif文実行直前の状態に戻り、if文評価結果が追加される。
-
?文法:
? <単語> [<単語> ...]引数の中からランダムで1つを選んで評価し、その値を出力する。選ばれなかった引数は評価されない。
?文全体を終了後、履歴は?文実行直前の状態に戻り、?文評価結果が追加される。
-
-
繰り返し
-
while文法:
while <条件単語> <実行単語>条件単語の評価結果が真である間、繰り返し実行単語を実行する。条件単語の評価が偽であれば、繰り返しを終了し、それまでに評価した実行単語の出力を連結して出力する。毎ループごとに条件単語が実行される。
実行単語を評価中、条件単語の評価結果は最新の履歴に入り、
${-1}で取得可能。while文全体が終了後、履歴はwhile文実行直前の状態に戻り、while文評価結果が追加される。
-
until文法:
until <条件単語> <実行単語>whileの条件判断を逆転させたもの。条件単語の評価結果が偽である間、繰り返し実行単語を実行する。条件単語の評価が真であれば、繰り返しを終了し、それまでに評価した実行単語の出力を連結して出力する。毎ループごとに条件単語が実行される。
実行単語を評価中、条件単語の評価結果は最新の履歴に入り、
${-1}で取得可能。until文全体が終了後、履歴はuntil文実行直前の状態に戻り、until文評価結果が追加される。
-
loop文法:
loop <回数> <実行単語>一度だけ回数単語を評価し、その出力を整数として扱い、ループ回数とする。回数分だけ実行単語を評価し、その結果を連結して出力とする。
実行単語を評価中、ループ実行回数(0オリジン)が最新の履歴に入り、
${-1}で取得可能。loop文全体が終了後、履歴はloop文実行直前の状態に戻り、loop文評価結果が追加される。
-
foreach文法:
foreach <代入エントリ名> <列挙対象エントリ名> <実行単語>代入エントリ名単語と列挙対象エントリ名単語を一度だけ評価し、その出力を代入用エントリと列挙対象エントリとする。列挙対象エントリに属する文全てについて、文の評価結果を代入用エントリの唯一の要素とし、実行単語を評価する。
foreachでは、実行単語の評価中の履歴操作は特に行われない。foreach文全体が終了後、履歴はforeach文実行直前の状態に戻り、foreach文評価結果が追加される。
-
break文法:
breakループ構文の実行単語を評価中にbreak構文が実行されることにより、全ての単語評価が中止され、最内側のループから脱出する。脱出した際も途中まで評価されたループの実行結果は出力される。この構文は、ループ構文中でのみ機能する。
例:
echo-mode > $(loop 10 $(if $[${-1}<=5] ${-2}"," else 脱出します。$(break))) 0,1,2,3,4,5,脱出します。 echo-mode > 12345$(break)6789 123456789
-
continue文法:
continueループ構文の実行単語を評価中にcontinue構文が実行されることにより、現在のループの単語評価を中止し、次のループ処理に移る。中止した際も途中まで評価されたループの実行結果は出力される。この構文は、ループ構文中でのみ機能する。
例:
echo-mode > 開始$(loop 10 $(if $[${-1}<=5] $(continue) else ","${-1})) 開始,5,6,7,8,9
-
-
関数
-
function文法:
function <関数名> <関数> function <関数名>関数名単語を評価し、その出力を関数名、第二引数(未評価)を内容としてユーザ定義関数を定義する。第二引数がない場合、関数名単語を評価し、その出力を関数名とするユーザ定義関数の内容を、評価前形式で返す。
例:
command-mode > function サンプル関数 $(echo "これは関数のサンプルです") echo-mode > $(サンプル関数) これは関数のサンプルです echo-mode > $(function サンプル関数) $("echo" "これは関数のサンプルです")リファレンス実装となる"KIU"では、関数の実体は"
System.Function.<関数名>"エントリの第一要素として登録されている。
-
rmfunc文法:
rmfunc <関数名>関数名単語を評価し、その出力結果の関数を削除する。
リファレンス実装となる"KIU"では、"
System.Function.<関数名>"エントリの内容を削除する。
-
lambda文法:
lambda <関数>重ならないように自動で生成された関数名に対し、第一引数を内容として、関数を定義。関数名を返す。
リファレンス実装となる"KIU"では、関数名は"
System.Lambda.<連番>"で、実体は"System.Function.<関数名>"エントリの第一要素として登録されている。定義した場所のローカルエントリは引き継がない(クロージャではない)。
-
-
その他
-
return文法:
return [ <戻り値> ]関数評価を終了(中止)する。戻り値が指定された場合、それまでの関数評価結果を削除し、指定された戻り値で置き換える。関数以外の場所で実行された場合、文全体の評価を終了(中止)する。
-
コマンド呼び出し¶
構文でないスクリプト文は、全てコマンド呼び出し文として扱われる。全ての単語を評価して文字列配列とした後、最初の文字列をコマンド名とし、配列全体を引数としてコマンドを呼び出し、コマンド実行結果を出力する。
コマンドには「組み込みコマンド」と「ユーザ定義関数」がある。
組み込みコマンドは言語仕様に含まれる標準ライブラリであり、多くはネイティブコードで実装され、変更不可能である。
ユーザ定義関数はfunction構文などにより動的に定義されるコマンドであり、KISで記述され、変更可能である。ユーザ定義関数が呼び出された場合、全ての引数(n個)は@arg[0]~@arg[n-1]に格納されて渡される。@arg[0]はコマンド名である。
演算式¶
Phase 8では、以前のexprとtestコマンドを代替するものとして、演算式構文を導入する。
演算子は静的に(平の文字列で)記述されなければならない。
真偽値は"true"(真)、"false"(偽)で出力される。数値としての0は"0"が出力される。
旧exprが持っていた幾つかのコマンド(substrなど)は、単独のKISコマンドとして標準ライブラリに再定義された。
演算子は結合優先度の高い順に以下である。背景色が同じグループは同じ優先度を持つ。同じ優先度の演算子は左側優先で結合される。また、()によるグルーピングは全ての演算子よりも高い優先度を持つ。
| 記号 | 数値 | 文字列 | 動作 | 例 |
|---|---|---|---|---|
** |
○ | 累乗 | $[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 |
== |
○ | ○ | 等しい(同上) | $["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 |
ビット演算は、一般のプログラミング言語と同じ動作である。
幾つかの一般的でない結果を返す演算について補足する。
(TODO:論理演算)
(TODO:マッチ演算)
コンテキスト、履歴参照、一時エントリ¶
以前から存在したが明記されなかったフィーチャに、「コンテキスト」がある。Phase 8はコンテキストを更に活用しているため、この概念を正式に仕様とする。ただし、プログラミング言語に慣れていない者には少々難しい概念であるので、そのようなユーザは特に理解しなくても良い。
「コンテキスト」とは、ある一つのエントリ呼び出しの評価中につき一つ設定される、内部的で一時的な「一時辞書」である。この辞書は、華和梨が常に持っている、「エントリ名→エントリ内容(文の集合)」の辞書と同じ機能を持つ。ただし、同一のコンテキストを利用可能な領域はきわめて狭く、「あるエントリ内容一つ」である。
代表的な使用例が、履歴参照である。履歴参照は、同一のエントリ内容を評価中、過去に起きた置換の履歴を参照することが出来る。呼び出した他のエントリにおける置換や、自分を呼び出した元の文における置換などを参照することはできない。コンテキストが違うのである。
npw : ムネオ, マキコ, ジュンイチロウ
event : ${npw}パパと${0}ママの、血で血を洗う抗争
sentence : \0\s[30]${npw}が${event}のあおりでお星様になったって。\1\s[10]なんまんだぶ……${0}よ、成仏してくれ。\e
echo-mode > ${sentence}
マキコがムネオパパとムネオママの、血で血を洗う抗争のあおりでお星様になったって。\1\s[10]なんまんだぶ……マキコよ、成仏してくれ。\e
上記の例において、sentenceから呼び出されたeventの評価中も、sentenceのコンテキストが消滅しているわけではないことに注意してほしい。eventの評価が終了した後、${0}によってevent呼び出し以前の履歴を参照している。しかし、eventを評価中は、sentence評価中の履歴を参照できない。sentenceからeventの履歴を参照することも、当然できない。
華和梨内である単語が評価されているときは、必ず何らかのコンテキストが存在する。通常、エントリ呼び出しが行われると、新しいコンテキストが作成され、設定される。その呼び出しが終了した時点で作成したコンテキストは破棄され、以前のコンテキストに戻される。コンテキストはエントリ呼び出しの連鎖に伴ってスタック状に積み重ねられ、エントリ呼び出しが終了するつどに一つずつ取り除かれる。あるコンテキストから別のコンテキストへのアクセスは不可能である。
なお、ここで言う「エントリ呼び出し」には、華和梨外部からのアクセスによって生じる「最初の」エントリ呼び出しも含む(SHIORIリクエストに伴う、System.Callback.OnEvent呼び出しなど)。
また、コンテキストは、エントリ呼び出しが行われない限り切り替わらない。辞書記述上、もっとも判りやすい表現としては「ぱっとエントリ定義を見て見える記述は、全て同一のコンテキスト上で実行される」である。
履歴参照の他、一時エントリの機能が、コンテキストによって実現される。一時エントリは、「@」で始まる名を持ち、コンテキストと同じ寿命(即ち、エントリ呼び出しによって定められた境界内)を持つ。
少々難解だが、以下のように、例えばエントリ集合演算時の集合演算式の評価中は元のコンテキストである。集合演算式の評価が終了した後、ランダムに選択された単語の評価は、新しいコンテキスト上となる。
$(addstr @候補 entry1; addstr @候補 entry2)${$@候補[1]+$@候補[2]}
一方、コンテキスト上の履歴は、「置換子領域(${~}, $[~], $..[~], $(~))を出ると置換子の評価以前の履歴まで巻き戻り、置換子の評価結果が追加される」という動作をする。分かりやすく言うと、以下のルールで表せる。
$マークの数だけ、履歴参照が追加される。$領域の中(スクリプトやエントリ呼び出し)では、$領域の外の履歴を参照することも出来る。- 実行時の動作によらず、記述段階で履歴参照の番号と参照先は一対一対応となる。
最後のルールを実現するために、インラインスクリプト中の構文については、各構文独自の法則が適用される。以前の華和梨では、分岐(やループ)の際、場合によって追加される履歴の数が変化していたが、Phase 8では履歴の数が分岐によって変化することはなく、辞書記述時に確実に正しい履歴を参照することが出来る。
a : A
b : B
echo-mode > ${a}|$(echo ${0}|; echo ${b}|; echo ${2}|)${3}|「${1}」
A|A|B|B||「A|B|B|」
空文字列¶
やはり以前から概念上は存在しながら、余り明確でなかったフィーチャとして「空文字列」がある。
Phase 8以前には、実装上の都合により、空文字列の記述には$(NULL)という表記を用いた。Phase 8では、どの場所でも""によって、長さ0の文字列――空文字列を宣言することが出来る。
一部のコマンドにおいては空文字列は特別な意味を持つ。例えば置換を行うgsubコマンドにおいて、検索パターンに空文字列を指定した場合、空文字列は「全ての文字間」を表す。これは文頭と最初の文字の間、最後の文字と文末の間も含む。
echo-mode > $(gsub abcde "" |)
|a|b|c|d|e|
マッチ演算子では、空文字列はあらゆる単語にマッチする。
echo-mode > $["hoge"=~""]
true
また、エントリ呼び出しにおいては、「選択した文の評価結果が空文字列」であるケースと、「エントリが存在しない(エントリが文を1つも含まない)ため、評価結果が空文字列」であるケースを判別できない。従って、エントリ定義に空文字列のみの文を含めておくことにより、エントリの評価結果を意図的に空にすることができる。
entry : "", 当たり
echo-mode > ${entry}
華和梨文法¶
字句構造¶
KISはネイティブ言語キャラクタセットで動作する。アルファベットの大文字と小文字は区別される。
空白文字(半角スペースとタブ)、改行文字は、場所によって扱いが異なる。いわゆる「辞書」記述とスクリプト言語を共存させるため、KISの字句構造は複雑であり、多数のモードを持つ。
改行可能かどうかの目安は、「対応する閉じ括弧を必要とする場面かどうか」である。ブロック、インラインスクリプトブロック、エントリ呼び出し、演算式などの中身が、これに相当する。
エントリ名¶
エントリ名には、以下の文字を使用できる。
- 1バイト英数字
- 1バイト記号の「
._@?」 - 日本語文字など、(マルチバイトの)非ASCII文字
文字列¶
華和梨辞書にデータとして書くことのできる文字列には、ベア文字列(裸の、あるいは生の文字列)と、クォート文字列がある。華和梨はほとんどの文章を生に書けるようにモード分けしてあるが、時としてクォート文字列を使う必要がある。
ベア文字列に使える文字は、日本語などのマルチバイト文字と、文字コード0x09(水平タブ)、および0x20~0x7Eの間(改行は入らないことに注意)の以下の文字である。その場所で意味をなす文字以外は全て使えるようになっている。
-
通常
「
" ' $ ( ) ,」以外の文字
-
インラインスクリプト文中
「
" ' $ ( ) ;」以外の文字
-
ブロック中
「
" ' $ ( )」以外の文字
なお、スクリプト実行中などに動的に文をパースする場合は、ブロック中扱いとなる。
クォート文字列は'"', "'"のどちらかで囲まれた文字列であり、この中では、基本的に、クォートに用いた文字以外の全ての文字(空白を含む)が使える。ただし、「\"」あるいは「\'」(実際にクォートに用いた文字による)は、クォート文字そのもの、および「\\」は、「\」そのものとして扱われる。
echo-mode > "これは\"サンプル\"です"
これは"サンプル"です
echo-mode > 'これは"サンプル"です'
これは"サンプル"です
echo-mode > '"でクォートした文字列の中で「\\"」と書くと、"が出力できます。'
"でクォートした文字列の中で「\"」と書くと、"が出力できます。
echo-mode > 以前の華和梨には「"クォート文字列の最後で\\を出力できない\\"」という仕様バグがありました
以前の華和梨には「クォート文字列の最後で\を出力できない\」という仕様バグがありました
コメント、プラグマ¶
ある行において、空白文字を除いた最初の文字が「#」である場合、その行はコメントとして無視される。これは、辞書記述中のあらゆる場所において有効である。
また、「:rem」だけの行から「:endrem」だけの行の間も、コメントとして無視する。このように行頭が「:」で始まる場合、その行はプラグマとして扱う。現在、「:crypt」、「:endcrypt」等が存在する。
予約語¶
華和梨に特定の予約語は存在しない。ただし、KISコマンドとして、構文コマンドと同名のコマンドをユーザが新たに定義することはできない。そのほかの組み込みコマンドをオーバーライドすることは可能である。詳細はKISリファレンスに記載。
辞書ファイル形式、ゾーン切り替え¶
Phase 8の辞書ファイルは、「辞書記述ゾーン」と「スクリプト記述ゾーン」の二つから成る。辞書記述ゾーンはPhase 8以前の辞書と同様、エントリに単語を登録する場所であり、単語の評価は行わない。従って、エントリの定義のファイル内における順序に意味が無い。一方、スクリプト記述ゾーンはKISを直ちに評価する。評価する順番は読み込んだ順であり、辞書記述ゾーンと違い記述の順序に意味がある。
辞書記述ゾーンは、「=dict」のみの行と「=end」のみの行の間である。また、スクリプト記述ゾーンは「=kis」のみの行と「=end」のみの行の間である。今のところあまり意味はないものの、辞書記述ゾーンとスクリプト記述ゾーンはお互いに入れ子(ネスト)が可能であり、この場合「=end」は直前の「=kis」または「=dict」に対応すると解釈する。辞書ファイル読み込み時、デフォルトは辞書記述ゾーンである。
スクリプト記述ゾーンにおける基本文法は、後述する「インラインスクリプトリスト」と同じである。
=dict
# ここは辞書記述ゾーン
プログラム , 栞 : 華和梨 , 里々 , 翡翠 , "ese-shiori" , 美坂 , 文
=kis
# ここはスクリプト記述ゾーン
load dict-keeps.txt;
load dict-standard.txt;
setstr Flags "ジギル";
=dict
# ネストした辞書記述ゾーン
植物 : さくら , 双葉 , みかん
妖怪 : 白子 , 毒子 , 薬子
人間 : 陽子 , 名無子
不明 : 美耳 , サンバーレイン
=end
#ここはスクリプト記述ゾーン
function tset $(echo $@arg[2] ; set $@arg[1] $(getcode @arg[2]));
=end
#ここは辞書記述ゾーン
sentence : \0\s[0]私は${人間}です。\e
=end
辞書記述¶
辞書記述は、エントリ定義を並べたものである。一つのエントリ定義は以下で表され、改行で終了する。
<エントリ名リスト> : <文リスト(改行不可)>
もしくは、
<エントリ名リスト> ( <文リスト(改行可)> )
-
エントリ名リスト
エントリ名一つか、もしくは複数のエントリ名をコンマ(
",")で区切って並べたもの。エントリ名とコンマの間に0個以上の空白文字が入ってもよい。エントリ名リストの最後がコンマであってはならない。
-
文リスト
「文(後述)」一つか、もしくは、複数の文をコンマ(
",")で区切って並べたもの。文とコンマの間に、0個以上の空白文字が入ってもよく、その空白文字は文の解釈に優先して削除される。文リストの最後がコンマであってはならない。
文と単語¶
文は(1個以上の)単語のリストである。
エントリ定義中は文=単語であるが、スクリプト文では、単語は1個以上の空白文字(改行可能な場所では改行も含む)で区切られている。
単語は、「文字列」「置換子」「ブロック」のリストである。
# deprecated
# 以下の文は単語1つ。
# ただし、単語の中身は要素を3つ(文字列、エントリ呼び出し、文字列)持っている。
sentence : ある日パパと二人で${場所}へ行ったさ~
# 以下の文は単語が1つ。ただし、ブロックの中に単語が3つ。
sentence : (
ある日パパと二人で
${場所}
へ行ったさ~
)
文と単語の区別が重要になるのは、主にスクリプト文の場合である。
=kis
function DAI_OH_JOU $(
if $[${弾数}>=1000] ${大往生}します。
)
=end
上記で、ユーザ定義コマンドに登録されたスクリプト文は、3つの単語のリストであり、3つめの単語は要素を2つ(エントリ呼び出し、文字列)持っている。
ブロック¶
「文(改行可)」を「(」と「)」ではさんだものをブロックと呼ぶ。ブロックの小括弧の内部では自由に改行できる。改行前後で単語が分割されるが、記述上特に気にする必要はない。また、インラインスクリプトの引数としてブロックを使った場合も、ブロック全体が一つの単語要素として扱われるため、内部で自由に改行可能である。
単語はブロックを含むため、ブロックの入れ子は可能である(あまり意味はない)。またベア文字列の定義から、ブロック内では、「,」をクォートしなくても文字として使用できる。
# ブロックを使用したエントリ定義のサンプル
Question : \0\s[0]それでは質問です。私の名前は\n(
" "\q[さくら,NameIsさくら]\n
" "\q[双葉,NameIs双葉]\n
" "\q[まゆら,NameIsまゆら]\n
)のどれでしょう?\e
Speech : \0\s[7](
'"'さくら'"'は正しいコンセプトを持っていながら実装不足で
デスクトップに立ち続けられなかった。\w8単なる他人の下らない
プログラムではなく、\w4あと一歩惜しいところで意味のあるもの
にならなかったこと、\w4それが腹立たしく悔しかった。\w8\n
実装の不足が生きた素材を殺してしまった。\w8
'"'さくら'"' は本当は生きて行くはずだった。\w8
遺志を継がなくてはならない。
)\e
置換子¶
置換子とは、「エントリ呼び出し」、「エントリ配列呼び出し」、「インラインスクリプトブロック」、「式呼び出し」のいずれかである。これら全てが「$」で始まり、実行時にはその出力が置換履歴に加えられる。それぞれの文法は次の通りである。
エントリ呼び出し¶
${ <エントリ名> }
${ <集合演算式> }
文法的には後者で統一されるが、単独のエントリ名を書いた場合、後述の集合演算時に展開される可能性がある。また、単独のエントリ名が整数値であった場合、履歴参照という特別な動作を行う。
「集合演算式」はエントリを単語の集合と見なし、和集合や積集合、差集合を記述する。詳細は「集合演算式」の項で説明する。
括弧の中は空白文字、改行文字を(エントリ名が分割されない限り)自由に入れてよい。
エントリ配列呼び出し¶
$<エントリ指定単語>[ <演算式> ]
エントリ指定単語部には、置換子、およびエントリ名に使える文字を使って作られた任意の単語が使える。ただし、途中、前後での空白文字は許されない。「演算式」は後述。
括弧の中は空白文字、改行文字を(演算子や演算要素が分割されない限り)自由に入れてよい。
$User.Callback.${System.Request.ID}[0]
$User.Callback.$System.Request.ID[0][0]
インラインスクリプトブロック¶
$( <インラインスクリプト文リスト> )
「インラインスクリプト文リスト」とは、インラインスクリプト文をセミコロン(";")で区切って並べたものである。
インラインスクリプトは、空白文字もしくは改行文字で区切られた複数の「単語」のリストである。「単語」は、前述の通り、また、インラインスクリプト中の単語で直に書ける文字も前述の通りである。
式呼び出し¶
$[ <演算式> ]
詳細は「演算式」の項で説明する。
# エントリ呼び出しのサンプル
${npw}、${人間&男性}、${猫-三毛猫}、${${Character}.Head.limithalf}
${(作家&SF作家)-女性}、${犬+猫}、${$(get entries)}
${
非常に長いエントリ名を使用する場合はこういうことも出来る
}
# エントリ配列呼び出しのサンプル
$wordlist[5]、$${エントリ名}[${@インデックス}]、$@arg[2]
$$インデックスエントリ[5][3]
$event[
$(date %y) * 365 + $(date %m) *31 + $(date %e)
]
# インラインスクリプトブロックのサンプル
$(if ${a} $(print Yes) else $(print No))、$(date %m)、$(Reference 0)
# 式呼び出しのサンプル
$[ (${収入} - ${支出}) * 3 / 100 ]
$[ "String" != "string" ]
$[
1+2+3+4+5+6+7+8
+9+10+11
+10*5
]
3.7. 演算式¶
演算式とは、通常の数式・論理式そのものである。項としては数値、クォート文字列、置換ブロックが使える。また、小括弧によるグルーピングも使える。文法は次の通りである。
<単項式> ( <二項演算子> <単項式> )の0個以上の繰り返し : 各要素間に0個以上の空白文字・改行文字を許す
単項式¶
単項式とは、演算式の「要素」、もしくは要素の前に「単項演算子」をつけたものである。単項演算子とは、'+'、'-'、'!'、'~'の4つの記号のことである。
要素、式単語¶
演算式の要素とは「式単語」、または小括弧で括った演算式そのものである。式単語とは、十進整数字の文字列、クォート文字列、置換ブロックのいずれかを並べたものである。
一つ注意しなければならないのは、文字列比較などの際に、演算式内に直接文字列を書くときは、必ずクォート文字列にしなければならない点である。
二項演算子¶
二項演算子とは、'|'、'&'、'^'、'||'、'&&'、'<'、'<='、 '='、'=='、'!='、'=~'、'!~'、'>='、'>'、'+'、'-'、'*'、 '/' 、'%'、'**'のいずれかの記号、もしくは記号列である。
集合演算式¶
集合演算式とは、エントリを単語の集合と見なし、積集合、和集合、差集合を表現するものである。文法は次の通りである。
<エントリ要素> ( <エントリ二項演算子> <エントリ要素> )の0個以上の繰り返し : 各要素間に0個以上の空白文字・改行文字を許す
「エントリ要素」とは、エントリ名、または小括弧で括った集合演算式そのものである。
エントリ二項演算子とは、'+'、'-'、'&'のいずれかの記号である。
外部インターフェース¶
華和梨は単独では何もできず、外部からエントリ呼び出しを受けて初めて機能する。以下に、華和梨の外部インターフェースについて述べる。
SHIORI-Serverインターフェース¶
SHIORIはゴーストの中核的要素であり、この部分の挙動が人格のほぼ全てとなる。
SHIORI プロトコルの仕様は、SHIORI/3.0 仕様書を参照。
「何か。」、及びその互換系のためのSHIORIサブシステムとしての動作が、現在の華和梨の最大かつほぼ唯一の存在目的である。
SHIORIとはゴーストの挙動を決定するブラックボックスである。「何か。」本体は、環境の情報(マウスクリックや時間経過など)を吸い上げてSHIORIへと送り(リクエスト)、それに対するSHIORIの反応(レスポンス)を得て、描画や通信を行う。逆に、SHIORIは、本体からの情報を元にゴーストの挙動を決定し、必要があれば会話文などの反応を返す。このようにSHIORIと本体は役割を分担しており、その間を「SHIORIプロトコル(通信規約)」と呼ばれる通信方式が結んでいる。SHIORIにとってはSHIORIプロトコルを守ることが唯一かつ最大の制約である。(もっとも、この図式はSAORIによってさらに変革されつつある)
SHIORIプロトコルはHTTPを強く意識したアーキテクチャを持つ。リクエスト/レスポンス型プロトコルであり、そのメッセージはほぼテキストベースであり、行指向であり、先行するスタートライン(SHIORIプロトコルでは明確な名称はない)と、それに続くヘッダ行という形式であり、ヘッダ行は最左の':'(コロン)によって、ヘッダ名と値に分割される。
SHIORIプロトコルのメッセージはこのようにフラットなフォーマットであるため、その内容によらず、一定のルールによって華和梨の辞書構造に変換することができる。ただし、そのままでは余りにもミドルウェアの負担が大きくなるため、最小限の処理は華和梨システムが事前に行う。
以下に、Phase 8.0.0時点で、華和梨が内部的に行っている、SHIORIプロトコルから華和梨辞書への変換手続きを示す。
なお、SHIORIプロトコルについてのある程度の知識を前提とする。具体的なプロトコルの定義については、SHIORI/3.0 仕様書を参考にされたい。
「エントリにセット」とは、そのエントリに属する唯一の文とすることを指す。これは華和梨においてエントリを変数的に使用する場合の常套句である。また、「リクエストライン」はリクエストメッセージのスタートライン、「ステータスライン(「何か。」正式にはステータスコード)」はレスポンスメッセージのスタートラインを指す。
-
セキュリティチェック(後述)を行い、許可されないリクエストについては、"400 Bad Request"を返す。
-
リクエストライン(リクエストメッセージのスタートライン)から、プロトコルバージョン表記とそれ以降(あれば)を取り除き、"System.Request"エントリにセット。
-
全てのヘッダ行について、ヘッダ名と値を分離し、"
System.Request.<ヘッダ名>"エントリに値をセット。 -
SHIORI規格は、現在2.x系と3.0に明示的に対応している。
-
3.x系GETイベント
version, craftman, nameの三種のリソース問い合わせには自動応答する。
その他の問い合わせについては、"System.Callback.OnGET"エントリに属する全ての文を評価し、その結果を連結し、レスポンスのValueヘッダの内容とする。
"System.Response."で始まる全てのエントリを、"
System.Response.<ヘッダ名>"として扱い、そのエントリの最初の文を評価して、レスポンスヘッダの値とする。
-
3.x系NOTIFYイベント
"System.Callback.OnNOTIFY"エントリに属する全ての文を評価する。
-
2.x系通常イベント
"System.Callback.OnEvent"エントリに属する全ての文を評価し、その結果を連結し、レスポンスのSentenceヘッダの内容とする。
"System.Response."で始まる全てのエントリを、"
System.Response.<ヘッダ名>"として扱い、そのエントリの最初の文を評価して、レスポンスヘッダの値とする。ただし、SentenceとAgeヘッダは自動作成される。
-
2.x系コミュニケートイベント
"System.Callback.OnGetSentence"エントリに属する全ての文を評価し、その結果を連結し、レスポンスのSentenceヘッダの内容とする。
"System.Response."で始まる全てのエントリを、"
System.Response.<ヘッダ名>"として扱い、そのエントリの最初の文を評価して、レスポンスヘッダの値とする。ただし、Sentenceヘッダは自動作成される。Toヘッダの値が"User"、あるいは"stop"であった場合、To、Ageヘッダは削除される(コミュニケート終了)。Toが、リクエストのSenderと同値であった場合、Ageヘッダの値は、リクエストのAgeの値に+1したものとなる。それ以外の場合、Ageヘッダは"0"となる。
-
2.x系、GET Status(GET Status SHIORI/2.0)
"System.Callback.OnGetStatus"エントリに文があれば、最初の文を評価して出力。なければ、「単語数,エントリ数,100,華和梨バージョン,100,100」をStatusヘッダの内容とする。
-
2.x系 リソースイベント(GET String SHIORI/2.5)
"System.Callback.OnResource"エントリに属する全ての文を評価し、その結果を連結し、レスポンスのStringヘッダ内容とする。
"System.Request.ID"に、要求するリソースIDが登録されるため、これに対応するリソースを返せばよい。
-
その他のイベント全て
"System.Callback.OnRequest"エントリに属する全ての文を評価する。
"System.Response."で始まる全てのエントリを、"
System.Response.<ヘッダ名>"として扱い、そのエントリの最初の文を評価して、レスポンスヘッダの値とする。"System.Response"エントリの最初の文を、レスポンスステータスコード(200など)とする。
-
セキュリティチェック(TODO)¶
SAORI-Serverインターフェース¶
華和梨はSAORIとしても動作する。内部動作は、ほぼSHIORIサーバ動作と同様である。
クエリに対しては、以下のように動作する。
-
EXECUTEコマンド
"System.Callback.OnSaoriExecute"エントリに属する全ての文を評価する。
"System.Response."で始まる全てのエントリを、"
System.Response.<ヘッダ名>"として扱い、そのエントリの最初の文を評価して、レスポンスヘッダの値とする。"System.Response"エントリの最初の文を、レスポンスステータスコード(200など)とする。
-
その他のイベント全て
"System.Callback.OnRequest"エントリに属する全ての文を評価する。
"System.Response."で始まる全てのエントリを、"
System.Response.<ヘッダ名>"として扱い、そのエントリの最初の文を評価して、レスポンスヘッダの値とする。"System.Response"エントリの最初の文を、レスポンスステータスコード(200など)とする。
ロードマップ¶
Phase 8はしばらく安定化プロセスを必要とすると思われ、場合によってはPhase 9にまで発展する可能性もある。その一方で採用が望まれるフィーチャとして、以下のようなものを認識している。
-
えびさわさんの華和梨Z
ミドルウェアが肥大化することを考えるとzlib採用は妥当。
-
sanoriさんによる国際化パッチ
近々に取り込み予定。
-
正規表現への対応
正規表現ライブラリ「鬼車」はライセンス的に華和梨と整合性があり、検討中。
文法仕様 (EBNF)¶
===========================================================================
■ コードネーム "KIU" ~雨ニモ負ケズ、風ニモ負ケズ~ Version 0.4.6
===========================================================================
>●喜雨 悔いあらためてください( ̄▽ ̄)
///////////////////////////////////////////////////////////////////////////
文法仕様です。いい感じに肥大化しています。
なお、単語のデリミタで若干嘘が混じっています。
空白が1つ以上要求される場所にWSを使っているケースです。
良きに計らってください。
///////////////////////////////////////////////////////////////////////////
EBNF簡略解説
■0回以上の繰り返し
'*'で表す。
ex)
Sentence ::= Token *
■1回以上の繰り返し
'+'で表す。
ex)
TokenGroup ::= Token +
■文字、文字列
文字や文字列は''で囲む。また、文字は#xで始まるWideCharacter表記でも良い。
ex)
Group ::= '(' Part-word * ')'
S ::= (#x20 |#x9 |#xd |#xa)+
■文字範囲
('a'|'b'|'c'|'d'|'e') は '[abcde]'と書け、'[a-e]'とも書ける。
[#x20-#x07FF]などという表記も可。
基本的に2byte文字を想定しているが、多バイト文字でもよい。
その場合は適時文字範囲を拡張して考えること。
■例外
'-'で表す。(Char - '/')は、'/'を除くCharを表す。
■文字範囲の除外
[]内の先頭の'^'は、この文字範囲が除外されることを表す。
[^a-d]は、[a-d]以外の文字を表す。
■コメント
'#'以降はコメントとする。#xXX表記と紛らわしい。
///////////////////////////////////////////////////////////////////////////
"KIU" プリプロセッサ
[0] 行が'!'で始まる場合、暗号化された行として、復号処理を行う。
[1] 行頭に':'がある場合、その行はプラグマとして扱われる。
・':rem' : ':endrem'までの間の行をコメントとして扱う。
・認識できないプラグマは素通りする
[2] 行頭から空白文字以外の最初の文字が'#'であった場合、その行は
コメントとして破棄される。
現実的には、ゾーン切替の処理もこの段階で行うことになる。
///////////////////////////////////////////////////////////////////////////
"KIU" 辞書文法 / 恐らくフルセット
■辞書
[0] <辞書> [GOAL]
Dictionary ::=
DictionaryDecl
[1] <辞書記述ゾーン>
DictionaryDeclZone ::=
'=dict' R DictionaryDecl '=end' R
[2] <スクリプト記述ゾーン>
ScriptDeclZone ::=
'=kis' R ScriptDecl '=end' R
[3] <辞書記述>
DictionaryDecl ::=
( EntryDef | ScriptDeclZone ) *
[4] <スクリプト記述>
ScriptDecl ::=
( ScriptStatementSeq | DictionaryDeclZone ) *
[5] <エントリ定義>
EntryDef ::=
WS EntryIdList ':' S NRStatementList R
| WS EntryIdList '(' WS StatementList ')'
[6] <エントリ識別子並び>
EntryIdList ::=
IdLiteral S | IdLiteral S ',' S EntryIdList
[7] <改行不可文並び>
NRStatementList ::=
NRStatement S | NRStatement S ',' S NRStatementList
# No Return Statement List
[8] <文並び>
StatementList ::=
Statement WS | Statement WS ',' WS StatementList
■文
[9] <改行不可文>
NRStatement ::=
S ( Word.mode1 S ) *
# No Return Statement
[10] <ブロック>
Block ::=
'(' WS Statement ')'
[11] <文>
Statement ::=
WS ( Word.mode3 WS ) *
[12] <単語>
Word.(mode) ::=
( Literal.(mode) | Block | Subst ) +
# NO Whitespace!
[13] <置換ブロック>
Subst ::=
EntryCallSubst | EntryIndexSubst | InlineScriptSubst | ExprSubst
[14] <エントリ呼び出し>
EntryCallSubst ::=
'${' WS EntryExpr '}'
[15] <エントリ配列呼び出し>
EntryIndexSubst ::=
'$' EntryWord '[' WS Expression WS ']'
# NO Whitespace
[16] <インラインスクリプトブロック>
InlineScriptSubst ::=
'$(' WS ScriptStatementSeq WS ')'
[17] <インラインスクリプト文並び>
ScriptStatementSeq ::=
Statement WS ( ';' WS ScriptStatement ) *
[18] <インラインスクリプト文>
ScriptStatement ::=
WS ( Word.mode2 WS ) *
[19] <式呼び出し>
ExprSubst ::=
'$[' WS Expression ']'
■エントリ演算式
[20] <エントリ演算式>
EntryExpr ::=
EntryFactor ( WS EntryBinaryOp WS EntryFactor ) *
[21] <エントリ要素>
EntryFactor ::=
EntryWord WS
| '(' WS EntryExpr ')' WS
[22] <エントリ名>
EntryWord ::=
( IdLiteral | Subst ) + WS
■式
[23] <式>
Expression ::=
UnaryExpr ( WS BinaryOp WS UnaryExpr ) *
[24] <単項式>
UnaryExpr ::=
ExprFactor
| UnaryOp WS ExprFactor
[25] <要素>
ExprFactor ::=
ExprWord
| '(' Expression ')' WS
[26] <Expr単語>
ExprWord ::=
( DecimalLiteral | QuotedLiteral | Subst ) + WS
■終端記号群
# <空白文字>
SC ::=
#x20 | #x09
# ' ' と '\t'
S ::=
SC*
# <改行>
R ::=
#x0D | #x0A | #x0d#x0A
# <空白>
WS ::=
( SC | R ) *
# <識別子文字列>
IdLiteral ::=
( [0-9A-Za-z._@?] | NAChar ) +
# <文字列>
Literal.(mode) ::=
BareLiteral.(mode) | QuotedLiteral
# <ベア文字列>
BareLiteral.mode1 ::=
( [#x21-#x7E] - ["$'(),] | NAChar ) +
# = [A-Za-z0-9_!<>{}[]\%#&@?~:;./*+-=|^`] | NAChar
BareLiteral.mode2 ::=
( [#x21-#x7E] - ["$'();] | NAChar ) +
# = [A-Za-z0-9_!<>{}[]\%#&@?~:.,/*+-=|^`] | NAChar
BareLiteral.mode3 ::=
( [#x21-#x7E] - ["$'()] | NAChar ) +
# = [A-Za-z0-9_!<>{}[]\%#&@?~:;.,/*+-=|^`] | NAChar
# * No Whitesapce!! *
# <クォート文字列>
QuotedLiteral ::=
"'" ([^'#x0D#x0A] | '\\' | "\'")* "'"
| '"' ([^"#x0D#x0A] | '\\' | '\"')* '"'
# <10進整数文字列>
DecimalLiteral ::=
'0'
| [1-9] [0-9]*
# <非ASCII文字>
NAChar ::=
[#x0080-#xFFFF]
# Non ASCII Character
# <エントリ二項演算子>
EntryBinaryOp ::=
'+' | '-' | '&'
# <二項演算子>
BinaryOp ::=
'|' | '&' | '^'
| '||' | '&&'
| '<' | '<=' | '=' | '==' | '!=' | '=~' | '!~' | '>=' | '>'
| '+' | '-' | '*' | '/' | '%' | '**'
# <単項演算子>
UnaryOp ::=
'-' | '+' | '!' | '~'
///////////////////////////////////////////////////////////////////////////
ChangeLog :
0.4.5 -> 0.4.6
完全複数行エントリ定義を追加。
0.4.4 -> 0.4.5
この期に及んで、スクリプトの単語中に';'が使えるようなバグを発見。
この際ということでベア文字列をモード分け。
0.4.3 -> 0.4.4
式中のQuotedLiteralは必須。エンバグしました。すみません。
0.4.2 -> 0.4.3
Entry名の扱いを厳密化。
ついでに式中のQuotedLiteralも禁止。
QuotedLiteralの使える場所が分かりやすくなった。
0.4.1b -> 0.4.2
EntryNameをEntryWordに統一
EntryNameListをEntryDeclListに統一
エントリ名にQuotedLiteralが入っていたバグを修正
0.4.0b -> 0.4.1b
式中の単語についてbug fix
Word/WeakWord導入
0.3.0b -> 0.4.0b
マッチエントリ削除。
=dict, =kisゾーン導入。
lexerに関するメモを削除。
0.2.5b -> 0.3.0b
エントリ定義を、通常のものとマッチエントリとで統一。
各演算式構文表記の簡略化し、動作については別記とした。
Expressionの定義の追加。
演算式の単語にBlockが入っていたバグを修正。
0.2.4b -> 0.2.5b
マッチ演算子の内、','を「弱論理積演算子」とした。WSの位置を最適化。
EntryIndexSubstを正式に分離した。
0.2.3b -> 0.2.4b
Lexerの動作を修正。置換の特別扱いを廃止。
0.2.2b -> 0.2.3b
Exprについての記述を若干追加。空白を見逃していた。
0.2.1b -> 0.2.2b
添え字の扱いがlexerレベルに落ちていた("[0-9]+"などとふざけた記述に
なっていた)のを、Expr相当としたので、簡易版ながらExprの定義を追加。
0.2.0b -> 0.2.1b
添え字付きエントリ呼び出しについての記述が抜けていた。
0.1.1b -> 0.2.0b
preprocessorとlexerについての記述を追加。
0.1.0b -> 0.1.1b
QuotedLiteralのbug fix.