1 / index / 3

2. 華和梨言語仕様

以下にKIS言語の仕様を示す。 始めに言語の動作を述べ、次章に辞書記述上の文法を詳細に解説する。 不備があれば、是非配布サイトまで一報願いたい。

用語解説

評価
おおよそ、次のような意味:要素を実行すること。動作させること。
オリジン
序列をその数字から数え始めることを表す。 0オリジンならば0から、1オリジンならば1から数え始める。
空文字列
内容のない文字列を指す。 「空文字列が出力される」とは、実質的には「何も出力されない」と同義である。
真偽値
if文などで用いられる値。 ある条件が満たされる(もしくは正しい)ことを「真である」、 満たされない(もしくは間違っている)ことを「偽である」と言う。 「"何か。"=="伺か。"」は偽であり、 「100>=10」は真である。

華和梨が扱う値について

華和梨は文字列モノタイプ言語である。 即ち、全ての値を文字列表現で保持している (少なくとも、そう見えることを要求される)。

そのため、ある値がどう解釈されるかは、 それを解釈する側(オペレータ)の特性に依存する。 "true"は、 if文の条件節に用いられた場合は真偽値であるが、 単純に文字列として加工し、出力することもできる。 "10"などの数値はさらに複雑で、 演算式の中では数値として扱うことができ、 条件節では真偽値として扱われ、 また文字列として出力することもできる。

なお、華和梨での真偽値は以下のように判定される。

"false"(論理としての偽), "0"(数値としての偽), ""(文字列としての偽) 以外は、全てを真とする。

2.1. 辞書とエントリ

華和梨は一つの「辞書」。 辞書は「エントリ」の集合。 エントリは「文」の順序付きリスト。

全てのエントリは、'.'で区切られた文字列で構成された名前 (エントリ名)を持つ。 エントリ名に使える文字は、半角英数文字, 多バイト文字(いわゆる全角文字), '.', '_', '?', '@'である。 ただし、数字のみのエントリ名はアクセス不可能なので有効でない。 '@'で始まるエントリ名は一時エントリ(後述)である。 エントリ名は、例えば以下のようなものである。

System.Callback.OnEvent
event.OnSecondChange.handler
@arg
反応.さくら

2.2. 文

文は「単語」の連なりであり、 評価(実行)されることによって文字列を出力する。 単語は「文字列」「置換子」のいずれかの連なりである。 華和梨の全てのデータは文表現で保持され、 エントリにグループ分けされて格納されている。

文字列は実行されると、そのままの値を出力するものであり、 その動作は実行時には変更されない(静的)。 文字列はさらに、「ベア(プレーン)文字列」、「クォート文字列」に分けられるが、 この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

2.3. エントリ呼び出し

通常のエントリ呼び出しは、呼び出すべきエントリ名を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]

2.4. インラインスクリプト(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.<関数名>"エントリの内容を削除する。

その他
return

文法:

return [ <戻り値> ]

関数評価を終了(中止)する。 戻り値が指定された場合、 それまでの関数評価結果を削除し、指定された戻り値で置き換える。 関数以外の場所で実行された場合、文全体の評価を終了(中止)する。

コマンド呼び出し

構文でないスクリプト文は、全てコマンド呼び出し文として扱われる。 全ての単語を評価して文字列配列とした後、 最初の文字列をコマンド名とし、配列全体を引数としてコマンドを呼び出し、 コマンド実行結果を出力する。

コマンドには「組み込みコマンド」と「ユーザ定義関数」がある。

組み込みコマンドは言語仕様に含まれる標準ライブラリであり、 多くはネイティブコードで実装され、変更不可能である。

ユーザ定義関数はfunction構文などにより動的に定義されるコマンドであり、 KISで記述され、変更可能である。 ユーザ定義関数が呼び出された場合、 全ての引数(n個)は@arg[0]〜@arg[n-1]に格納されて渡される。 @arg[0]はコマンド名である。

2.5. 演算式

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: マッチ演算)

2.6. コンテキスト、履歴参照、一時エントリ

以前から存在したが明記されなかったフィーチャに、 「コンテキスト」がある。 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|」

2.7. 空文字列

やはり以前から概念上は存在しながら、 余り明確でなかったフィーチャとして「空文字列」がある。

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}