Top > マニュアル > 文法 > 2.関数
  トップページへ   [ 一覧 | 検索 | 最終更新 ]   [ 差分 | バックアップ | 凍結 ]

関数

基礎の基礎

YAYAにおける「関数」とは、

  • 0個以上の引数を取り
  • 0個以上の「出力候補」から1つの出力を選んで返す

処理の集まりです。

引数の数は、変数 _argc、各引数は、変数 _argv[n] に格納されています。
nは0から始まる引数の番号です。

出力候補とは、関数内に直接書かれた文字列や数値、変数名など、左辺値(「=」の左側)が無いものの集まりです。
複数の出力候補がある場合、YAYAはそのうちどれか一つを選んで関数の出力(返り値、戻り値)とします。

また、同名の関数が複数存在するとエラーになります。(特に既存のゴーストをベースに改編する場合に注意)

次の「基礎」の章は、汎用言語DLLとしてのYAYAにとっての「関数」です。良く分からない場合は読み飛ばしてかまいません。

基礎

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関数のインタフェースと類似しています。

_argc

引数の数。loadとrequestでは引数はひとつの文字列ですから1となります。unloadは引数を持たないので、この値は0となります。

_argv

引数の実体が格納されています。これは_argc個の要素を持つ配列です。各要素へのアクセスは演算子[i]で行います。
序数iは0オリジン指定です。たとえば_argcが1の場合は_argv[0]が使用可能で、ここに引数が格納されていることになります。

基礎編のまとめとして、loadでは変数strに"Hello"を格納し、requestで引数として渡された文字列とstrを結合して返すプログラムコードを 示します。~これまでの説明を踏まえて読んでみてください。

load
{
    str = "Hello"
}

request
{
    str + " " + _argv[0] + "!"
}

処理対象文字列を"World"としてrequestを実行すると、結果として"Hello World!"が得られます。

書式

以下の則があります。

  • 空行(改行のみの行)、"//"以降、および"/*"と"*/"で囲まれた領域はコメントと見なされます。
  • 1行の終わりがスラッシュ("/")の場合は、次の行がこの行の後ろに結合されます。
  • 行頭、および単語間には自由に空白文字を入れることが出来ます。空白文字は空白、およびタブ文字です。
  • 複数のステートメントを1行に詰めて書きたい場合は、セミコロン(";")で区切ることが出来ます。

つまり先に挙げたHello Worldコードは以下のように1行に詰めて書くことが出来ます。

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"となるわけです。 関数名は自由につけられますが、以下に抵触する名前はエラーとなります。

  • 数字0~9で始まる。
    • 定数の数字として解釈される可能性があります。
  • アンダースコア("_")で始まる。
    • アンダースコアで始まる名前はローカル変数で使われています。
  • 以下の文字を含む。
    空白 ! " # $ % & ( ) * + , - / : ; < = > ? @ [ ] ` { | } 
    • アンダースコア以外の記号は使わないほうが良いでしょう。
  • 予約語と完全に一致する。
    • アルファベット大文字+アンダースコアはシステム関数で使われます。
    • forやwhile、ifなど、制御構造に使われるものも避けなければいけません。

関数は再帰呼び出しが可能です。
もっともありふれた例として階乗計算を行った例を挙げます。

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は特殊な用途のみで使用します。

なにもなし

デフォルトでは無作為に選択します。

nonoverlap

すべての候補が出力されるまでは、同じ候補を選択しなくなります。

request : nonoverlap
{
    "Hello World"
    "こんにちは世界"
    "Hallo Welt"
}

上のように、関数名の後ろに": nonoverlap"を付け加えます。

sequential

記述された順に出力します。最後まで出力したらまた先頭に戻ります。

request : sequential
{
    "Hello World"
    "こんにちは世界"
    "Hallo Welt"
}

上のように、関数名の後ろに": sequential"を付け加えます。

void

何も出力しなくなります。
以下の例の場合、requestは3つの候補のどれも出力しません。

request : void
{
    "Hello World"
    "こんにちは世界"
    "Hallo Welt"
}

「何も実行しない」ではなく、「何も出力しない」であることに注意してください。
内部に書かれた関数や数式は処理されます。

increment_i : void
{
    i++
    i
}

上の関数increment_iはiに1を加算します。voidが無い場合、この関数は加算結果を返しますが、voidを指定すると値を返さず、 ただ加算を行うのみとなります。

array

出力候補をすべて結合した汎用配列を関数の返値とします。

request : array
{
  "This is a pen."
  ("A","B","C")
  3.14
}

出力は汎用配列 ("This is a pen.", "A", "B", "C", 3.14) となります。

nonoverlap・sequential・arrayは、出力確定子が存在する場合でも取り得るすべての組み合わせに対して正常に機能します。

たとえばsequentialは、

request : sequential
{
    "1"
    "2"
    "3"
    --
    "A"
    "B"
}

requestは以下の順序で出力を発生します。

"1A" "2A" "3A" "1B" "2B" "3B" "1A" "2A"

たとえばarrayは、

request : array
{
    "1"
    "2"
    "3"
    --
    "A"
    "B"
}

上記関数は、 ("1A","1B","1C","2A","2B","2C") という汎用配列を出力します。

関数によっては実行毎に出力候補の数が変動します。~たとえば以下の関数は、変数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、arrayと組み合わせて使用した場合、(グループ単位ではなく)関数が取り得る全ての組み合わせに対して動作します。 出力確定子はどこでも問題なく使用できます。{ }入れ子の深い位置でも使えます。 文は文字列のほかに数値なども扱えますが、出力確定子は結合する際にそれらをすべて文字列に変換し、文字列として結合します。