コンテンツにスキップ

関数

概要

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

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

処理の集まりである。

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

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

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

次の「基本構造」は、汎用言語 DLL としての YAYA にとっての「関数」である。よく分からない場合は読み飛ばしてよい。

基本構造

request 実行時に "Hello World" という文字列を返すプログラムコードの全体を示す。

request
{
    "Hello World"
}

YAYA をロードしたモジュールが 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"
}

このように列挙すると、これらは平等な「出力候補」として扱われ、出力はこれらのうちのいずれかひとつになる。選択方式は次のいずれかを指定できる。ただし void と array / pool_array は特殊な用途のみで使用する。

方式 説明
指定なし / random 無作為に選択する(デフォルト)
nonoverlap できるだけ直前と同じ候補を選択しない
sequential 記述された順に出力し、最後まで出力したら先頭に戻る
array 出力候補をすべて結合した汎用配列を返す
void 何も出力しない
all(Tc567-1) すべての候補を 1 つの文字列に結合する
last(Tc567-1) 最後の候補を選択する

選択方式は、関数名の後ろに : nonoverlap のように付け加えて指定する。

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

関数によっては、実行のたびに出力候補の数が変動する。たとえば次の関数は、変数 i の値によって候補数が 2 個または 4 個に変化する。

request : sequential
{
    if i {
        "1"
        "2"
    }
    "3"
    "4"
}

候補数が変化した場合、nonoverlap と sequential の巡回順序は初期化され、最初からやり直しになる。このときに限って、前回と同じ値が重複して出力されることがある。

array

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

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

出力は汎用配列 ("This is a pen.", "A", "B", "C", 3.14) になる。

void

何も出力しなくなる。次の例の場合、request は 3 つの候補のどれも出力しない。

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

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

increment_i : void
{
    i++
    i
}

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

all

Tc567-1 で実装。選択候補すべてを 1 つの文字列に結合する。

last

Tc567-1 で実装。選択候補の中から、一番最後の候補を出力として選択する。

修飾子

_pool: _pool を付けたブロックの内側にあるブロックの候補を、外側の候補と同じ階層に並べて選ぶ。関数そのもの(OnTest : _pool)にも、ブロック(_pool : {)にも付けられる。

ふつうは、ネストしたブロックは全体で1つの候補として数えられる。下の例では "x" が 1/2、"y" と "z" が 1/4 ずつになる:

OnTest
{
  "x"
  {
    "y"
    "z"
  }
}

_pool を付けると、内側のブロックの候補がばらされて、"x" "y" "z" がどれも 1/3 になる:

OnTest : _pool
{
  "x"
  {
    "y"
    "z"
  }
}
  • 何段ネストしていてもばらされる(3段目の "z" "w" を加えれば 4 つが 1/4 ずつ)
  • if elseif else の中身もばらされる。条件が偽で実行されないブロックの候補は加わらない。if 条件 { "b" "c" } と書くと、条件が真のときだけ "b" "c" が外側の候補と同じ確率で選ばれる
  • while for foreach の中の候補は、繰り返すたびに1つずつ加わる(2回まわる for の中の "y" は2つ分になる)
  • nonoverlap : { のように選択方式を書いたブロックは、ばらされずに1つの候補のままになる
  • 選択方式と組み合わせるときは _pool_nonoverlap _pool_sequential のように書く(_ は省略できる)。方式を書かない場合は random_pool(pool と書くだけでもよい)で、ランダムに選ぶ。nonoverlap_pool、sequential_pool、array_pool のようにも書ける。ばらした後の候補全体に対して、重複回避や順番の選択が働く
  • ブロックに付けたときは、そのブロックの内側だけが対象になる。外側のブロックの候補は、そのブロックを1つの候補として数える
  • _pool と melt_ は、同じブロックや関数には両方を指定できない
OnTest : _pool_nonoverlap
{
  "通常のセリフ"
  if is_rainy {
    "雨の日のセリフ1"
    "雨の日のセリフ2"
  }
}

is_rainy が真のときは、3つのセリフが同じ確率で、連続しないように選ばれる。偽のときは "通常のセリフ" だけになる。

melt_: 汎用配列を使って並列出力効果を生成する。ブロックに付けると、そのブロックで選んだ出力が汎用配列の場合に要素をばらし、すぐ外側のブロックの出力候補に加える(parallel と同じ効果)。melt_nonoverlap のように選択方式と組み合わせられる。選択方式を書いていない内側のブロックにも引き継がれる。

OnTest
{
    "test"
    "test2"
    melt_nonoverlap : {
        ("red","green","blue")
    }
}

結果: "test"、"test2"、"red"、"green"、"blue" のいずれか。

関数そのものに付けた場合は外側のブロックが無いので、汎用配列の候補をばらしてから選ぶ(Tc574-9 / Tc603-2以降)。return で返した汎用配列はばらさない。

OnTest : melt_nonoverlap
{
    "test"
    "test2"
    ("red","green","blue")
}

結果: 上と同じ5つの候補から、重複しないように選ぶ。

Tc574-8 / Tc603-1 までは、関数そのものに付けた melt_ は効かず、汎用配列がそのまま1つの候補になっていた。また、2段以上内側のブロックに付けた melt_ は、すぐ外側ではなく関数の一番外側のブロックの候補に加わっていた。

入れ子

request
{
    {
        "Hello World"
        "こんにちは世界"
    }

    "Hallo Welt"
}

{ } は階層的にいくつでも重ねて書ける。動きは最上層の { } と同じで、包含される候補からひとつを選択して出力する。ただし選択方式は無作為抽出のみで、最上層のように nonoverlap や sequential の指定はできない(Tc564-1以降は、下記の「入れ子内の選択方式」で指定できる)。

上の例では、{ } の有無にかかわらず結果が同じように思えるかもしれないが、そうではない。{ } が無い場合、それぞれが出力される確率は平等に 1/3 である。しかし上の例の場合、まず "Hello World" と "こんにちは世界" からひとつが選ばれ、次いでその結果と残った "Hallo Welt" から出力が抽出される。すなわち、出現率は "Hello World" と "こんにちは世界" が 1/4、"Hallo Welt" は 1/2 になる。

入れ子内の選択方式

Tc564-1以降、入れ子それぞれに前項の選択方式を追加できる。

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

    "Hallo Welt"
}

if 文などの直後に続く { } に指定するには、; で区切って次のようにするか、改行を追加する。

request
{
    if 1 ; nonoverlap : {
        "Hello World"
        "こんにちは世界"
    }

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

    "Hallo Welt"
}

出力確定子

request
{
    "Hello"
    "Perfect"
    "Peaceful"
    --
    " Wor"
    --
    "ld"
    "th"
}

-- は出力確定子というもので、選択候補はここを区切りとしてグループ分けされる。そして、グループごとに選ばれた結果がひとつの文字列に結合される。したがって上の request を実行すると、次のいずれかが出力される。

"Hello World"
"Perfect World"
"Peaceful World"
"Hello Worth"
"Perfect Worth"
"Peaceful Worth"

-- は単独の行に書く。"Hello" / "Perfect" -- … のように 1 行に書くことはできず、/ は割り算になる。

nonoverlap、sequential、array と組み合わせて使用した場合は、(グループ単位ではなく)関数が取り得るすべての組み合わせに対して動作する。

たとえば sequential は、

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

次の順序で出力を発生する。

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

array は、

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

("1A","1B","2A","2B","3A","3B") という汎用配列を出力する。

YAYA は文字列のほかに数値なども扱えるが、出力確定子は結合する際にそれらをすべて文字列に変換し、文字列として結合する。