aya5 - manual |
文 version 5 マニュアル |
概要 動作環境 利用規定 「伺か」ゴーストテンプレートについて(別ページ) 動作速度(別ページ) 主な変更点 エクスポートしている関数 基礎設定 文スクリプトリファレンス - 関数 - 基礎 - 書式 - ユーザー関数の定義と実行 - 択一 - 出力確定子 - 値と変数 - 即値 - 変数 - 変数のスコープと寿命 - 演算 - 基本 - ブラケット( )による演算順序制御 - フィードバック演算子& - 配列 - 簡易配列 - 汎用配列 - 範囲指定 - 汎用配列のパラレル出力 - 文字列内埋め込み要素の展開 - 範囲付き展開 - 名称最長一致展開 - フロー制御 - ifによる分岐 - caseによる分岐 - switchによる分岐 - ループ - return - プリプロセス - 予約語 - システム関数リファレンス(別ページ) 謝辞 履歴(別ページ) 各項の終わりにある"↑"をクリックするとこの目次へ戻れます。
概要 「あや」と読みます。文字列処理を行なうためのDLLです。 C言語に似た文法を使用して、
文はWindows DLLですので、直接利用するためには、プログラミングに関する知識がある程度必要です。 環境 Windows用です。 開発のごく初期に98SE、2000、xpで動作確認しましたが、最終版はxpでしか確認を取っていません。 報告をいただけると助かります。 利用規定
主な変更点 version 4系列の文をお使いの方は、この項を読むことでversion 5の性能のあらましを簡単に理解できます。 新機能/改善された機能
変更された機能 version 5の開発においては、version 4が抱える不具合や仕様の不整合を矯正することが重視されました。互換性の確保にも配慮が無かったわけでは ありませんが、それが足を引っ張るようならきっぱり切り捨てる方針で臨みました。 以下の点で互換性が崩れています。移植の際には注意が必要です。
version 4が持つシステム関数のうち、完全に同一機能のまま引き継がれたものは半数程度です。多くが新規追加、廃止、あるいは機能変更されています。詳細はシステム関数リファレンスで確認して下さい。 最も注意を要するのは文字列処理系の関数です。 version 4 STR*系(バイト数単位)、MSTR*系(文字数単位)の二系統が存在 version 5 STR*系(文字数単位) STR*系関数が残されましたが、動作はMSTR*系のものになっています。これは内部処理がUNICODE化されたためです。 _argv、_argc以外のシステム変数は廃止されました。ただし同等機能を持つシステム関数が追加されています。 version 4では空の文字列と値無しの区別が不明確でした。version 5ではこれが厳格に区別されます。 version 4では使用可能でした(そもそも12hourというシステム変数があったほどです)。これはversion 5では禁止となりました。 他にも変数名/関数名として使用不可能になった文字が存在します。 考えた結果、とりあえず未実装としました。 version 4のパーサが弱かったおかげで見逃されていたエラーが顕在化する可能性があります。 エクスポートしている関数 文は以下の公開された関数を持っています。 文を利用するプログラムは、文をLoadLibraryした後にこれらの関数を実行して所望の処理を行ないます。
文に初期化を指示します。 文をLoadLibraryして使用を開始する直前に、この関数を一度だけ必ず実行してください。 hには「文がカレントとして認識するディレクトリ絶対パス」を、lenはhの長さを渡してください。 hの領域開放は文側で行ないますので、呼び出し側では使い放しでかまいません。
文に終了を指示します。 文をFreeLibraryする直前に一度だけ実行してください。
文に処理を指示し、結果を得ます。 hには処理対象の文字列を、*lenにhの長さを渡してください。 渡したhの領域開放は文側で行ないますので、呼び出し側では使い放しでかまいません。 処理結果は戻り値で得られます。処理結果の長さは*lenに格納されています(つまりこの値は書き換えられます)。 戻り値を取得した後、領域を*lenで示されるサイズで開放(GlobalFree)してください。 なお、これはデスクトップマスコットソフトウェア「伺か」で使用される擬似AI用DLL「SHIORI」 のインタフェース規格と完全に同一のものです。 基礎設定 文 ver.5のデフォルトのDLLファイル名は「aya5.dll」です。 主ファイル名「aya5」は他の名前に自由に変更することができます。 文を動作させるためには「基礎設定ファイル」と呼ばれるファイルが必ず必要となります。 基礎設定ファイルのファイル名は「主ファイル名.txt」です。すなわちデフォルトでは「aya5.txt」となります。もしあなたがDLLのファイル名を 「hoge.dll」に変更したなら、基礎設定ファイルは「hoge.txt」です。 基礎設定ファイルはテキストファイルで、実行されるOSのデフォルトの文字コードで解読されます。 もし国際化に関して考慮しなければならないなら、マルチバイト文字コードに関する問題を回避するため、基礎設定ファイルはASCIIコードのみで記述するべきです。 以下に例を示します。 // dics dic, basis.dic dic, control./*doc*/ayc // option parameters charset, UTF-8 msglang, english log, executelog.txt iolog, off fncdepth, 16 設定はコマンドとパラメータをカンマで区切って指定します。 空行(改行のみの行)、"//"以降、および"/*"と"*/"で囲まれた領域はコメントと見なされます。 コマンドとその意味は以下の通りです。
標準の文字コードを設定します。 以下の予約値からいずれかを指定します。指定が無い場合はShift_JISとして扱われます。
辞書ファイルfilenameをロードします。 辞書ファイルは文スクリプトが記述されたプログラムソースファイルで、文はここのプログラムされた内容を元に動作します。 辞書ファイルはいくつでも指定し、読み込むことができます。 filenameはloadで指定されたパス位置からの相対バスで指定します。 標準の辞書ファイルはcharsetで指定した文字コードで記述されたプレーンテキストファイルです。この他に、一定の法則でスクランブルをかけた 暗号化ファイルを読み込むことができます。
ログに記録されるエラーメッセージ類の言語を選択します。 以下の予約値からいずれかを指定します。指定が無い場合はjapaneseとして扱われます。
実行ログをファイルlogfilenameに記録します。 charsetで指定した文字コードで書き込まれます。 logfilenameはloadで指定されたパス位置からの相対バスで指定します。
load、unload、request実行時の入出力文字列と処理時間をログに記録するかを設定します。 onで記録します。デフォルトではonになっていますので、不要の場合にoffとしてください。
関数呼び出しの深さ上限を数値で指定します。 デフォルト値は32です。最低値は2で、これより小さな値や不正な値を指定した場合も2として扱われます。
辞書の構文解析結果レポートをログに記録するかを設定します。 onで記録します。デフォルトではoffになっています。 文スクリプトの書式はC言語のそれを踏襲しています。 したがってあらかじめC言語、およびC言語に類似する言語を習得していると理解が早いです。 関数 基礎 request実行時に"Hello World"という文字列を返すプログラムコードの全体を以下に示します。 request { "Hello World" } 文をロードしたモジュールがHGLOBAL request(HGLOBAL h, long *len)を実行すると、このスクリプトが実行され、"Hello World"が 返されます。 loadとunloadも同様です。 ただしloadとunloadは値を返さないので、出力文字列を書いても意味がありません。loadには変数に初期値を入れるといった初期化処理を、 unloadには逆にその後始末をするコードを書きます。 必要な関数だけ書けばよいです。不要なものは省けます。 たとえば上の例ではloadとunloadは書いていませんが、これはエラーとはなりません。何もしないだけです。 極端な例を挙げるなら、辞書ファイルがまったく無くてもエラーにはなりません。この場合loadとunloadは何もせず、 requestは空の文字列を返します。 * loadとrequestはひとつの引数を持っています。これは変数で取り出せます。 変数の名前は_argcと_argvで、これはC言語のmain関数のインタフェースと類似しています。
これまでの説明を踏まえて読んでみてください。 load { str = "Hello" } request { str + " " + _argv[0] + "!" } 処理対象文字列を"World"としてrequestを実行すると、結果として"Hello World!"が得られます。 書式 以下の則があります。
request{"Hello World"} では以下のように書いてもいいのか? もちろん。問題なく動作します。(ただしこんな風に書くのは感心できない!) req/ uest { "/ Hello World" } /の次の行(新たに結合される行)先頭にある空白文字はインデント文字と見なされ、消されます。 したがって以下の文字列の結合結果は "ABCDEFG" であり、決して "ABCD EFG" ではありません。 "ABCD EFG" 第4項を説明します。 以下のrequestは1+2の答えを求め、日本語の文章にして返しています。 request { answer = 1 + 2 "答えは" + answer + "です。" } セミコロンを使用して以下のように書けます。 request { answer = 1 + 2; "答えは" + answer + "です。" } セミコロンは過剰に書いても問題ありません。それらは無視され、動作に影響は与えません。 したがって、あなたがC言語の書式に慣れているなら、各行の終端に必ずセミコロンを書くことができます。 ユーザー関数の定義と実行 load、unload、request以外に好きな名前の関数を作成できます。 作成した関数は、その名前を書くだけで実行できます。 request { hello } hello { "Hello World" } 上に示したのはもっとも単純な例です。requestは結果として"Hello World"を返します。 同じ結果が得られるもう少し複雑な例を、以下に示します。 request { combine("Hello", "World") } combine { _argv[0] + " " + _argv[1] } 関数名の後ろに( )をつけて、その中にカンマで値を並べて書くと、これらは該関数に渡される引数として扱われます。 すなわち変数_argvと_argcに値が格納されて、該関数内で参照できるようになります。 ここで挙げた例では、combine関数内において_argcは2、_argv[0]は"Hello"、_argv[1]は"World"となるわけです。 関数名は自由につけられますが、以下に抵触する名前はエラーとなります。
* * * 関数は再帰呼び出しが可能です。 もっともありふれた例として階乗計算を行った例を挙げます。 request { factorial(5) } factorial { if !_argv[0] 1 else factorial(_argv[0] - 1)*_argv[0] } requestは120を返します。 択一 request { "Hello World" "こんにちは世界" "Hallo Welt" } このように列挙すると、これらは平等な「出力候補」として扱われ、出力はこれらのうちのいずれかひとつになります。 5種類の選択方法が用意されており、いずれかを選ぶことができます。 ただし、voidとarrayは特殊な用途のみで使用します。
記述された順に出力します。最後まで出力したらまた先頭に戻ります。 何も出力しなくなります。 以下の例の場合、requestは3つの候補のどれも出力しません。 内部に書かれた関数や数式は処理されます。 increment_i : void { i++ i } 上の関数increment_iはiに1を加算します。voidが無い場合、この関数は加算結果を返しますが、voidを指定すると値を返さず、 ただ加算を行うのみとなります。 出力候補をすべて結合した汎用配列を関数の返値とします。 nonoverlapとsequentialは、出力確定子が存在する場合でも取り得るすべての組み合わせに対して正常に機能します。 たとえばsequentialを例に挙げると、 request : sequential { "1" "2" "3" -- "A" "B" } requestは以下の順序で出力を発生します。 "1A" "2A" "3A" "1B" "2B" "3B" "1A" "2A" … * * * 関数によっては実行毎に出力候補の数が変動します。 たとえば以下の関数は、変数iの値によって候補数は2個もしくは4個に変化します。 request : sequential { if i { "1" "2" } "3" "4" } 候補数が変化した場合、nonoverlapとsequentialの巡回順序は初期化され、最初からやり直しになります。この時に限って、前回と同じ値が重複して 出力されることはあり得ます。 入れ子 request { { "Hello World" "こんにちは世界" } "Hallo Welt" } { } は階層的にいくつでも重ねて書くことが出来ます。動きは最上層の{ }と同じです。すなわち、包含される候補からひとつを選択して出力します。 ただし選択方法は無作為抽出のみで、最上層のようにnonoverlapやsequentialの指定はできません。 上の例では、{ }の有無にかかわらず結果は同じであるように思われるかもしれませんが、実はそうではありません。 { }が無い場合、それぞれが出力される確率は平等に1/3です。しかし上の例の場合、まず"Hello World"と"こんにちは世界"からひとつが選ばれ、次いでその結果と残った"Hallo Welt"から出力が抽出されます。すなわち、出現率は"Hello World"と"こんにちは世界"が1/4、"Hallo Welt"は1/2となるのです。 出力確定子 request { "Hello" "Perfect" "Peaceful" -- " Wor" -- "ld" "th" } --は出力確定子というもので、選択候補はここを区切りとしてグループ分けされます。そして、グループごとに選ばれた結果がひとつの文字列に結合されます。 したがって上のrequestを実行すると、以下のいずれかが出力されることになります。 "Hello World" "Perfect World" "Peaceful World" "Hello Worth" "Perfect Worth" "Peaceful Worth" nonoverlap、sequentialと組み合わせて使用した場合、(グループ単位ではなく)関数が取り得る全ての組み合わせに対して動作します。 出力確定子はどこでも問題なく使用できます。{ }入れ子の深い位置でも使えます。 文は文字列のほかに数値なども扱えますが、出力確定子は結合する際にそれらをすべて文字列に変換し、文字列として結合します。 値と変数 即値 文が扱える値は整数、実数、文字列の3種類です。
符号付き64bit浮動小数点数です。小数点以下の桁がある場合や、精度が落ちてもいいので非常に巨大な数値を扱いたい場合はこれを使用します。 ダブルクォート(")で囲まれた値は文字列です。 文字列の中にダブルクォートが含まれてはいけません。 シングルクォート(')で囲まれた値は、 文では文字列中に変数や関数を埋め込むことが出来ますが、これが展開されるのはダブルクォートで囲まれた文字列のみです。 文字列の中にシングルクォートが含まれてはいけません。 変数 変数は値を格納するための領域です。 ひとつの変数には、
名前は以下の禁止条項に抵触しない限りは自由につけることができます。
request { str = "こんにちは" str } 上は単純な例で、変数strに文字列を格納し、そのまま出力しています。 * * * まだ値が代入されていない変数は、空の文字列を出力します。 「何も出力しない」ではないことに注意してください。 request { "Hello World" i } i は存在しません。これは空の文字列になりますから、結局上の例は下のコードと等価であり、"Hello World"もしくは空の文字列を1/2の確率で出力することになります。 request { "Hello World" "" } 変数のスコープと寿命 変数はスコープ(有効範囲)の違いによって2種類存在します。
「必要な範囲だけで有効な変数」であるローカル変数をうまく利用することで、プログラムの見通しが良くなります。 request { _i = "3*2は" _j = multi(3) _i +_j + "です" } multi { _i = _argv[0] _i * 2 } requestとmultiが同じ名前の変数 _i を使っていますが、それらはまったく別物として扱われます。お互いの値を破壊することはありません。 関数の引数を扱う変数_argcと_argvも実はローカル変数であることに気付かれたかと思います。 これらもまた関数ごとに別の値を格納しますので、 ローカル変数になっているわけです。 * * * ローカル変数が「現在の関数内で使用可能な変数」ではなく、「現在の{ }内、およびそれより深い入れ子階層のみで使用できる変数」であることには注意してください。 request { { _str = "Hello World" } _str } このプログラムは期待通りには動作しません。 _strは{ }内のみで有効ですから、出力を取り出そうとしている位置では消えてしまっています。 したがって、この関数requestは空の文字列を出力します。 グローバル変数とローカル変数の違いはもうひとつあります。寿命です。 ローカル変数は上で見たとおり、変数を使用している{ }から外れると消えてしまいます。 対してグローバル変数はどこでも使用可能、さらにunloadでその値はファイルへ自動的に保存され、loadで復元されるようになっています。すなわちグローバル変数の内容は、(特殊な操作によって意図的に消去しない限りは)永遠に保持されるのです。 演算 基本 C言語と同様の書式で四則演算、比較演算、代入、その他が可能です。 演算の順序は演算子毎に重み付けされた演算優先度に基づいて決定されます。また、ブラケット( )で囲まれた部分は最優先で演算されます。 演算子の種類と演算優先度は以下の通りです。 演算子 意味 優先度 ( ) [ ] ブラケット 高い ! 否定* ++ -- ポストインクリ*/デクリ* * / % 乗除算、剰余 + - 加減算 & フィードバック* == != >= <= > < _in_ !_in_ 比較 && 論理積 || 論理和 = := 代入 += -= *= /= %= +:= -:= *:= /:= %:= ,= 演算して代入 , 汎用配列要素の列挙 低い
コロン(":")付きの代入演算子は過去互換性を保持するために残されているもので、機能的にはコロン無しのものと同等です。
ブラケット( )については次項で詳説します。*は単項演算子です。 カンマ演算子(",")、およびスクウェアブラケット[ ]については配列の項で詳説しています。 フィードバック演算子&については別項で詳説しています。 _in_と!_in_は文字列の包含チェックに使用する演算子です。 foo { "or" _in_ "World" } _in_は左辺の文字列が右辺の文字列に含まれていれば1、含まれていなければ0を返します。!_in_はその逆です。 上の関数fooは1を返します。 比較演算子は結果が真であれば整数1を、偽であれば0を返します。 これらの演算子は文字列に対しても正しく機能します。値の大小は辞書順比較で決定されます。 論理の真偽は以下で判断されます。 偽 整数0、実数0.0、空の文字列、空の汎用配列 真 上記以外のすべて * * * 代入でない演算は結果がそのまま出力となります。 foo { (3+2)*4 } この関数fooは20を出力します。 * * * 同じ優先度の演算子が連続している場合は、常に左から結合されます。 1+2-3 たとえば上の式は (1) 1+2 (2) 3-3 の順に計算されます。 i = j = 10 これはどうなるでしょう。C言語では代入演算子は右から結合されますから、iとjには10が代入されます。 しかし文では結合は常に左からです。すなわち (1) i = j (2) j = 10 の順に計算されます。したがって、iには10は入らないのです。 i = (j = 10) これで i にも10が入るようになります。 * * * 演算対象の項の型が一致していない場合、結果の型は以下のようになります。
必要に応じて、上記の法則により型変換が起こります。 "10+2は" + (10+2) + "です。" 最初に10+2が整数として計算され、結果12が得られます。残りはすべて文字列との加算ですから、12は文字列に変換され、文字列として結合されます。 ブラケット( )による演算順序制御 ブラケット( )で囲んだ部分は演算順序が最優先扱いになります。 ( ) はいくつでも重ねて指定可能で、深くなるほど優先度が上がります。 平たく言えば「かっこで囲んだところを先に計算する」ということです。これは当たり前の規則なのでいまさら大きく扱うまでもないのですが、 数式の書き方によってはなかなかややこしいこともあります。 以下の例を見てください。 answer = (_i = 10) + (2*(_i + 10)) 文の演算則をよく理解している人でないと、answerに何が代入されるかを正確に答えることは出来ないでしょう。 answerには文字列の"10"が入ります。決して整数50ではありません。 最初に計算されるのはどこでしょう。ブラケットが一番深いところ、すなわち _i + 10 です。さて変数 _i はまだありません。したがってこれは空の文字列になります。となると、_i + 10 は文字列と整数の加算です。10は文字列に変換され、"10"となります。しかし次が整数との乗算なので、結局また空文字列になってしまいます。 ここまで説明すれば結果が文字列の"10"になる理由は明白でしょう。 さて、こちらの意図ではまず_i = 10の代入を先にしてもらいたいわけです。 このような場合、文では以下のようにブラケットを過剰に付与することで演算順序をコントロールします。 answer = (((_i = 10))) + (2*(_i + 10)) これで代入部分の優先度が一気に引き上げられました。今度は正しい結果が入るはずです。 なお、代入部分を囲むブラケットは2段で十分ではないかと思われるかも知れませんが、それも誤りです。ブラケット2段は_i + 10と同じ深さとなりますが、=と+では優先度が+のほうが高いのです。 フィードバック演算子& フィードバック演算子&は、他の演算子とは使い方がまったく異なる独特の演算子です。 request { _i = 1 foo(&_i) } foo { _argv[0] = 100 } 関数呼び出しの際に引数として変数を与える場合、その先頭に&を書くことができます。 先頭に&を付けられた変数は、呼び出し先関数の_argvの対応する要素へ関連付けられます。すなわち、_argvを書き換えると、対応する呼び出し元の変数の値も書き換わるようになります。 上の例では、fooを実行すると _i の値が100に書き換わります。 フィードバック演算子は好きな位置にいくつでも使用可能です。 request { foo(1, 2, &_value, "Hello", &_value2) _value + _value2 } foo { _argv[2] = _argv[0] + _argv[1] _argv[4] = _argv[3] + " World" } 関数requestは "3Hello World" を出力します。 当然のことですが、フィードバック演算子は変数にしか付けられません。 配列 演算子[ ]は配列要素にアクセスするための演算子です。 配列には文字列スプリットを擬似的に配列と見なす「簡易配列」と、カンマ区切りで列挙された要素を扱う「汎用配列」の2種類があります。 簡易配列 文字列に含まれるカンマをデリミタ(区切り文字)と解釈して、配列のように扱う機能です。 request { _a = "this,is,a,pen" _a[1] } requestは"is"を出力します。 [ ]演算子が処理する対象は変数である必要はありません。即値でも関数の返値でもいいです。 上の例は以下のように書いても同じです。 request { "this,is,a,pen"[1] } 序数の後ろにカンマ区切りでデリミタを指定することにより、カンマでなく他の文字列で簡易配列要素を区切ることができます。 request { "This is a island."[2,"is"] } "is"で区切るわけですから、文字列は以下のように分解されます。 [0] "Th"したがってrequestは" a "を返します。 デリミタ指定をうまく使うと、多次元配列風に値を取り出すことが出来ます。 request { _ar = "taro|male,ayame|female,hotaru|female" _ar[2][1,"|"] } _ar[2]は"hotaru|female"です。さらに"|"を区切り文字として解釈して[1]を取り出しますので、結果は"female"となります。 このように、階層毎にユニークなデリミタを適用することによって、任意の位置の値を簡単に抜き出すことができるようになります。 範囲外の序数を指定した場合の出力は、存在しない変数の値を取り出そうとした場合と同様、空の文字列となります。 * * * ここからは変数限定の機能を解説します。 要素に代入が可能です。 request { _a = "this,is,a,pen" _a[3] = "eraser" _a } "pen"を"eraser"に書き換えています。requestの実行結果は"this,is,a,eraser"となります。 デリミタ指定しても正しく機能します。 request { _s = "This is a island." _s[2,"is"] = " beautiful " _s } requestは"This is beautiful island."を出力します。 多次元配列風に[ ]演算子を連結して使用している場合、代入はできません。 request { _ar = "taro|male,ayame|female,hotaru|female" _ar[1][1,"|"] = "male" } ayameの性別をmaleに書き換えようとしていますが、この操作はエラーとなります。代入が可能なのは一次元の場合のみです。 現在の要素数を越える位置へも問題なく代入できます。デリミタが自動的に追加され、要素数が拡張されます。 request { _m = "fuji/asama/tanigawa" _m[5,"/"] = "daisen" _m } requestは"fuji/asama/tanigawa///daisen"を出力します。 SETDELIMという関数を使用すると、「デフォルトのデリミタ」をカンマから別の文字列へ変更できます。 先に示した例をSETDELIMを使用して書き換えたものを以下に示します。 request { _m = "fuji/asama/tanigawa" SETDELIM(_m, "/") _m[5] = "daisen" _m } SETDELIMすることにより、単純に_m[5]と書くことができるようになります。 多次元配列風に[ ]演算子を連結して使用している場合、SETDELIMは最初の(一次元目の)[ ]のみで有効です。 汎用配列 汎用配列はさまざまな型の値を混在して格納できる配列構造です。 一般的なアクセスでは簡易配列より高速に動作します。 代入する場合は上のように要素の集合を( )で囲んでください。カンマの演算優先度は代入よりも低いため、ブラケットが無いと (i = 100),"test",-1.5 このように解釈されてしまいます。 配列を空の状態で初期化するには、IARRAYという関数を使用します。IARRAYは「空の汎用配列」を返す関数です。 i = IARRAY 初期化時に要素をひとつだけ代入したい場合は工夫が要ります。たとえば単に i = 100 としたのでは、配列ではなくただの数値の代入になってしまうからです。 以下のように記述します。 i = (IARRAY,100) 配列を追加することもできます。 i = (i,("add",123,0.0)) a = a + 1 を a += 1と略せるように、上の例は下のようにも書くことができます。 i ,= ("add",123,0.0) 先頭への挿入も同じ書式で可能です。 i = ("first",i) 中途への挿入もできます。 i = (100,200,300,400,500,600) i[2] ,= "insertion" iは(100,200,300,"insertion",400,500,600)となります。 挿入対象は[2]でなく[3]に入ることに注意してください。 i[2]へ挿入したい場合は i[2] = ("insertion",i[2]) とします。 削除したい要素へIARRAYを代入します。 単純に要素へ代入できます。 現在の要素数を越える位置へも問題なく代入できます。必要な数だけ要素数が拡張されます。 通常の変数と同様、序数を指定して記述すればそのまま出力されます。 範囲外の序数を指定した場合の出力は、存在しない変数の値を取り出そうとした場合と同様、空の文字列となります。 [ ]演算子が処理する対象は変数である必要はありません。即値でも関数の返値でもいいです。 (100,200,300,400,500,600)[4] 500が出力されます。 汎用配列をそのまま関数の出力にできます。 request { river[2] } river { "tenryu","bandou-tarou","ishikari","shimanto" } requestは"ishikari"を出力します。 汎用配列は多次元配列を構成できません。 結局以下のように単純に結合されてしまいます。 (100,200,300,400,500,600) 要素単位の演算は通常に行うことが出来ます。 汎用配列と単項値との演算は、 pref = "gunnma","ohsaka","hokkaido" pref += "-ken" answer = (2*(1,2,3))[1] prefは "gunnma-ken","ohsaka-ken","hokkaido-ken" となります。 answerには4が代入されます。 2*(1,2,3)の計算結果が(2,4,6)となるからです。 * * * 文において つまり func(1, 2, "test") という関数の呼び出しは、 _i = (1,2,"test") func(_i) とも書けます。非常にトリッキーな表記ですが、これで意図どおりに動作します。 注意してください。書き直したスクリプトにおいても、引数の数は決して1個ではありません。3個です! この構造をうまく利用すると、可変長の引数を他の関数へ簡単に渡すことが出来ます。 request { total(1,2,3,4,5,6) } total { calc_total(_argv) } calc_total { _answer = 0; foreach _argv; _val { _answer += _val } _answer } totalは自分では何もせず、すべての引数をそのままcalc_totalへ引き渡しています。 この例では単純に引き渡していますが、もちろん必要に応じて加工してから渡すことも可能です。 引数の指定方法が複雑になっている場合は注意すべきです。 _i = (1,2,"test") func("sky", _i, "sun") 上の呼び出しは以下と等価です。汎用配列は多次元化できないことを思い出してください。 func("sky", 1, 2, "test", "sun") "This is a island."[2,"is"] 範囲指定 簡易配列/汎用配列とも、序数を範囲で指定できます。取得、代入とも可能です。 範囲は汎用配列で指定します。たとえば i[a,b] は「iの要素a〜b」を表します。 name = ("さくら","せりこ","奈留","まゆら","毒子","美耳") i = name[1,3] name[3,4] = "奎子" j = name name[0,2] = IARRAY k = name iには ("せりこ","奈留","まゆら") が格納されます。 jは ("さくら","せりこ","奈留","奎子","美耳") 、 kは ("奎子","美耳") となります。 範囲外は無視されます。 n = (1,2,3,4) n[-2,1] *= 5 nは (5,10,3,4) です。 対象が簡易配列の場合でも書式は同じです。 name = "さくら,せりこ,奈留,まゆら,毒子,美耳" i = name[1,3] name[3,4] = "奎子" j = name iは "せりこ,奈留,まゆら" 、jは "さくら,せりこ,奈留,奎子,美耳" です。 デリミタ指定も可能です。範囲指定のすぐ後ろに続けて指定します。 animal = "くま!うさぎ!ねこ!いぬ!わに" i = animal[0,2,"!"] animal[2,4,"!"] = "ぶた" j = animal iは "くま!うさぎ!ねこ" 、jは "くま!うさぎ!ぶた" です。 汎用配列のパラレル出力 すべての式/値の前に「parallel」を書くことができます。 parallelは出力候補値が汎用配列だった場合、 要素のそれぞれを出力候補値にする機能を持っています。 foo0 { ("A","B","C") "地球" } foo1 { parallel ("A","B","C") "地球" } foo0の出力は、("A", "B", "C") もしくは "地球"。 foo1の出力は、"A"、"B"、"A"、"地球" のいずれかとなります。 汎用配列以外にparallelを使っても意味がなく、書かないのと同じです。たとえば下の2つの記述は等価です。 parallel STRLEN("earth") STRLEN("earth") 択一メソッドarrayとparallelを利用することにより、関数の出力候補と汎用配列を相互に変換することができます。 さまざまな応用が考えられます。たとえば以下の非常に簡潔な関数cyclicは、汎用配列から記述順に要素値を取り出します。 cyclic : sequential { parallel _argv } 文字列内埋め込み要素の展開 文字列の中に変数や関数を埋め込んで、これらの実行結果を当該位置へ挿入することができます。 範囲付き展開 要素を%( )で囲んで埋め込みます。 request { _i = "pen" "This is a %(_i)." } requestを実行すると、"This is a pen."が出力されます。 %( )はいわゆるeval(文字列をスクリプトコードと解釈して実行する)のような動作を行います。 単一の関数や変数だけでなく、数式を埋め込むことができます。 request { "1+2+3は%(1+2+3)です。" } requestは"1+2+3は6です。"を出力します。 文の文字列は内部にダブルクォートを含むことが出来ない点に注意してください。したがって、文字列を含む数式を埋め込むことは出来ません。以下はエラーです。 request { "This is a %(_i = "pen")." } このような場合は、埋め込みを使用せず通常の数式として結合します。 request { "This is a " + (_i = "pen") + "." } ブラケット( )による演算順序制御は範囲付き展開でも同様に働きます。 以下の例を見てください。 request { "遊星「%(_i = planet)」は遠い。この遊星は%(color(_i))色をしている。" } planet { "mars" "saturn" "pluto" } color { case _argv[0] { when "mars"; "red" when "saturn"; "yerrow" when "pluto"; "blue" others; "unknown" } } 最も( )が深いのはcolor(_i)の引数ですので、_i = planetの前にcolorが呼び出されてしまいます。 ブラケットを過剰付与して演算順序を調整してください。 "遊星「%((_i = planet))」は遠い。この遊星は%(color(_i))色をしている。" これで矛盾の無い文字列が得られるようになります。 名称最長一致展開 ( )を付与せず、単に%のみでも埋め込み展開は機能します。 request { o = "pen" obj = "eraser" object = "world" "This is a %object." } obje { "television" } 展開対象は%以降の文字列に一致する最も長い名前を持った変数/関数となります。上の例の場合、もっともよく一致するのは変数objectですから、これが採用されます。 結果は"This is a world."となります。 変数は刻々と作成されたり消えたりしますから、展開対象が状況によって変化することになります。 これは範囲付き展開には見られない特徴です。 request { val = "red" trans -- value = "blue" trans } trans { "%value" } transは二度実行されますが、最初と二度目では"%value"の動きが変わります。すなわち、最初は変数val+"ue"、二度目は変数valueと解釈されます。 requestの出力は"redueblue"です。 %[ ]という書式で過去の展開結果を再利用できます。 request { "「%planet」は遠い。「%city」も遠い。もっとも%[1]になら行けなくもない。" } planet { "mars" "saturn" "pluto" } city { "newyork" "moscow" "madrid" } %[i]は0オリジンでi番目の展開結果を得ます。 つまり上の例では、%[0]が%planetの、%[1]が%cityの展開結果を示しています。 %[ ]は範囲付き展開では使えません。範囲付き展開で過去の結果を再利用したい場合は、それを変数へ入れてください。 * * * 名称最長一致による展開は実行する度に展開対象を検索しなおしますので、範囲付き展開と比べて動作速度が劇的に遅いです。 本当に必要な場合は別ですが、通常は範囲付き展開を利用すべきでしょう。 フロー制御 ifによる分岐 式の結果が真であれば以降に続く{ }内を処理します。 request { if !i { "iは0である。" } } すぐ後にelseif節を付加することができます。これはifの判定が偽であった場合のみ処理されます。 いくつでも連結できます。 また、if〜elseifの最終端にはelse節を置くことができます。これは先行するifおよびelseifの判定がすべて偽であった場合に 処理されます。 request { if !i { "iは0である。" } elseif i == 5 { "iは5である。" } elseif "A" _in_ TOUPPER(i) { "iは文字列で、aもしくはAを含んでいる。" } else { "iは0でも5でもaを含む文字列でもない何物かである。" } } if、elseif、elseが処理する{ }内にスクリプトが1つしか存在しない場合は、{ }を略せます。したがって上の例は 下のように書きなおすことが出来ます。 request { if !i "iは0である。" elseif i == 5 "iは5である。" elseif "A" _in_ TOUPPER(i) "iは文字列で、aもしくはAを含んでいる。" else "iは0でも5でもaを含む文字列でもない何物かである。" } ただしifが重なっている場合は{ }の省略は出来ません。以下はC言語では正しいですが、文では誤りです。 if i == 0 if j == 0 "iとjはともに0である。" 以下のように{ }が必要です。 if i == 0 { if j == 0 "iとjはともに0である。" } * * * C言語と同様に、ifの判定式は全体をブラケット( )で囲むことができます。 動作は囲まない場合と変わりません。 if、elseif、case、while、for、switchの判定式で使用できます。 caseによる分岐 caseはラベル分岐構造を実現します。 request { case i { when 0 { "iは0である。" } when "A" "iは文字列Aである。" others { "iは0でもAでもない。" } } } caseに与えられた判定式の結果に一致するラベル値を持ったwhen節が実行されます。 others節は、すべてのラベルに合致しなかった場合に実行される部分です。othersは省略できます。省略した場合は何もしません。 ヒットさせるラベル値はカンマで列挙可能です。またマイナス符号でヒットさせる範囲を指定することもできます。 request { case name+(i+1) { when "Pentium3","Pentium4" "Pen!!!は1999年、Pen4は2000年発売発売。" when "Pentium5"-"PentiumX" { "まだ無い。" } others "分からない。" } } whenに記述するラベル値は必ず即値でなければなりません。変数や関数、演算子を含む数式は記述できません。 when、othersが処理する{ }内にスクリプトが1つしか存在しない場合は、ifと同様に{ }を省略できます。 switchによる分岐 { }内の出力候補から出力は無作為に選ばれますが、switchを使用すると選択する候補をを位置で指定できます。 request { switch id { "idは0である。" "idは1である。" { "idは2である。" "idはtwoである。" } "idは"3である。" } } 変数idの値によって出力される文字列が指定されます。指定は0オリジンです。 idが2の時は、"idは2である。"もしくは"idはtwoである。"が出力されます。この内包された{ }部分では、選択は無作為です。 switchが評価する値に対応する候補が{ }内に存在しない場合は空の文字列が出力されます。たとえば、上の例においてidが100だった場合は空の文字列が出力されます。 switch内に出力確定子がある場合は、各ブロックの該位置にある候補が選択されます。 request { switch 1 { "かわいい" "天才" "サル" -- "とは言い難い" -- "ですね。" "かもしれません。" } } requestの出力は"天才かもしれません。"となります。 中間のブロックには指定位置に候補が無いので、出力が空の文字列となっていることに注意してください。 ループ while、for、foreachの3種類のループ構造があります。 whileが評価する式が真である間は{ }内を繰り返し処理します。 下の例では異なる10個の文字列を発生しています。requestの出力は、1〜10のうちいずれかの平方根を報告する文字列です。 request { _i = 1 while _i < 11 { "%(_i)の平方根は%(SQRT(_i))である。" _i++ } } forはwhileと同様の先判定ループ構造ですが、初期化式、脱出判定式、ループ毎に実行する式を一箇所にまとめて書ける点が優れています。 以下は、whileで挙げた平方根を報告する例をforで書き直したものです。 C言語では for ( ; ; ) とすることで無限ループとできますが、文では各式を省略できません。 無限ループを作る場合は for 1;1;1 などとしてください。ただ、whileなら while 1 で済むため、文で無限ループを作る際は、可読性の点からも、動作速度の点からも、whileを使用すべきであると言えます。 簡易配列、もしくは汎用配列の各要素値を順番に取り出します。 以下では簡易配列の要素値を取り出して数値へ変換し、すべての合計を計算しています。 処理対象の変数のデリミタがSETDELIMによって変更されていても、foreachはそのデリミタを認識して正常に動作します。 foreachは汎用配列も処理できます。 request { _sent = ("I", "am", 31, "years", "old.") _t = "" foreach _sent; _i { _t += (_i + " ") } _t } requestは"I am 31 years old. "を出力します。 * * * foreachループ内において要素取り出し対象の簡易配列、汎用配列を書き換えてもかまいません。 変更は正常に反映されます。 ループ中にbreakが現れると、現在実行中の最も深いループから脱出します。 requestは_iから1を減じた価を返しています。つまりrequestは、二乗した結果が100を越えない最大の整数を求める関数です。 ループ中にcontinueが現れると、その位置からすぐにループ先頭へ戻ります。 したがってrequestの出力は"go ahead go go "となります。 return returnが現れると、その関数の実行はそこで終わります。 関数の出力はそれまでに蓄積された候補から選ばれます。 to_rad { if GETTYPE(_argv[0]) == 3 { -1 return } _argv[0]*2.0*3.14/360.0 } 関数to_radはdegreeをradianへ変換します。 引数に文字列が与えられた場合は、if判定でそれを発見して-1を返すようにプログラムされています。returnの時点で出力の候補は-1しか ありませんから、これが出力されることになります。 プリプロセス プリプロセスは辞書ファイルを読み込んでいる段階で実行される命令です。 辞書ファイルから読み込んだ(パース前の)生の文字列に対して、直接文字列置換を実行します。 #define の有効範囲は、宣言した次の行から、そのファイルの終端までです。 置換は記述順に行われますので、先に変換しておきたいものを先に書いてください。 #globaldefine を宣言すると、次の行以降の全ての範囲(その後に読み込まれる辞書ファイルも含む)で有効になります。 たとえば最初に読み込む辞書ファイルの先頭に #globaldefine を記述すると、その効果はすべての辞書ファイルに及ぶことになります。 * * * #defineが先に処理されます。。#globaldefine は、#define 置換のあとで実行されます。 #globaldefine tea green #define tea milk "teacup" 置換結果は"milkcup"となります。 予約語 以下の単語はシステム関数名、及び制御命令名です。 これらの名前は文システムで予約されています。これらと完全に一致する名称の変数や関数をユーザー側で作成、利用することは出来ません。 TOINT TOREAL TOSTR GETTYPE ISFUNC ISVAR LOGGING GETLASTERROR LOADLIB UNLOADLIB REQUESTLIB CHARSETLIB RAND FLOOR CEIL ROUND SIN COS TAN LOG LOG10 POW SQRT STRSTR STRLEN REPLACE SUBSTR ERASE INSERT TOUPPER TOLOWER CUTSPACE TOBINSTR TOHEXSTR BINSTRTOI HEXSTRTOI CHR FOPEN FCLOSE FREAD FWRITE FWRITE2 FCOPY FMOVE MKDIR RMDIR FDEL FRENAME FSIZE FENUM FCHARSET ARRAYSIZE SETDELIM EVAL ERASEVAR GETTIME GETTICKCOUNT GETMEMINFO RE_SEARCH RE_MATCH RE_GREP SETLASTERROR RE_REPLACE RE_SPLIT RE_GETSTR RE_GETPOS RE_GETLEN CHRCODE ISINTSTR ISREALSTR IARRAY SPLITPATH CVINT CVSTR CVREAL LETTONAME LSO STRFORM ANY SAVEVAR GETSTRBYTES以下の単語/文字は演算子です。 これらと完全に一致する名称の変数や関数をユーザー側で作成、利用することは出来ません。また、変数や関数名の一部にこれらの単語/文字を含むことも出来ません。 ( ) [ ] ! ++ -- * / % + - & == != <= >= < > _in_ !_in_ && || = := += -= *= /= %= +:= -:= *:= /:= %:= ,= 謝辞 以下のライブラリを利用もしくは参考にさせていただきました。感謝致します。 http://kamoland.com/comp/unicode.html 本家 http://www.boost.org/libs/regex/doc/index.html 日本語訳 http://boost.cppll.jp/HEAD/libs/regex/index.htm VC6へのインストール方法 http://village.infoweb.ne.jp/~fwhk9290/behind/regex.htm 基本的な使用方法 http://www.s34.co.jp/cpptechdoc/article/regexpp/ |