コンテンツにスキップ

演算

基本

C 言語と同様の書式で、四則演算、比較演算、代入、その他ができる。演算の順序は、演算子ごとに重み付けされた演算優先度に基づいて決定される。また、ブラケット ( ) で囲まれた部分は最優先で演算される。

演算子優先度(高い順)

優先度 演算子 説明
1 ( ) [ ] ブラケット
2 ! 否定
3 ++ -- 1 を足す / 1 を引く
4 * / % 乗除算、剰余
5 + - 加減算
6 == != >= <= > < _in_ !_in_ 比較
7 && 論理積
8 || 論理和
9 = := 代入
10 += -= *= /= %= +:= -:= *:= /:= %:= ,= 演算して代入
11 , 汎用配列要素の列挙(最低優先度)
  • コロン(:)付きの代入演算子は過去互換性を保つために残されているもので、機能的にはコロン無しのものと同等
  • ++ -- ! は単項演算子
  • & は関数の引数に付ける前置の記号で(フィードバック演算子)、二項演算子ではないため、この表には含まれない
  • カンマ演算子(,)とスクウェアブラケット [ ] については配列で詳しく説明している

比較と真偽

_in_ と !_in_ は文字列の包含チェックに使用する演算子。_in_ は左辺の文字列が右辺の文字列に含まれていれば 1、含まれていなければ 0 を返す。!_in_ はその逆。

foo
{
    "or" _in_ "World"
}

上の関数 foo は 1 を返す。

比較演算子は、結果が真であれば整数 1 を、偽であれば 0 を返す。これらの演算子は文字列に対しても正しく機能し、値の大小は辞書順比較で決定される。

論理の真偽は次で判断される。

  • 偽: 整数 0、実数 0.0、空の文字列、空の汎用配列
  • 真: 上記以外のすべて

代入でない演算は、結果がそのまま出力となる。

foo
{
    (3+2)*4
}

この関数 foo は 20 を出力する。

左結合

同じ優先度の演算子が連続している場合は、常に左から結合される。たとえば

1+2-3

は、1+2 → 3-3 の順に計算される。

i = j = 10

これはどうなるか。C 言語では代入演算子は右から結合されるので、i と j に 10 が代入される。しかし YAYA では結合は常に左からなので、i = j → j = 10 の順に計算される。したがって i には 10 は入らない。

i = (j = 10)

こう書けば i にも 10 が入る。

型強制(Type Coercion)

演算対象の項の型が一致していない場合、結果の型は次のようになる。

  • 整数と実数の演算: 結果は実数になる
  • 整数 / 実数と文字列の加算: 数値は文字列に変換された後、もう一方の文字列に結合される
  • 整数 / 実数と文字列の大小比較: 数値は文字列に変換された後、辞書順で大小比較される
  • 整数 / 実数と文字列の演算(加算・大小比較以外): 演算不能だがエラーにはならない。結果は空の文字列になる

ひとつの演算式内で型が混在していてもよい。必要に応じて、上の法則により型変換が起こる。

"10+2は" + (10+2) + "です。"

最初に 10+2 が整数として計算され、結果 12 が得られる。残りはすべて文字列との加算なので、12 は文字列に変換され、文字列として結合される。

ハッシュの演算(Tc600-3以降)

  • +: 相手がスカラーかハッシュなら、両方を文字列にして連結する(IHASH("a",1) + 1 は "a=11")。相手が配列なら、配列の要素ごとに連結する
  • - * / %(複合代入を含む): 警告 W0024 を出す。意味のある結果にはならない
  • ==: ハッシュ同士なら、要素数・キー・値がすべて一致すれば真。ハッシュとハッシュ以外は常に偽

詳細は ハッシュ を参照。

ブラケット( )による演算順序制御

ブラケット ( ) で囲んだ部分は、演算順序が最優先扱いになる。( ) はいくつでも重ねて指定でき、深くなるほど優先度が上がる。平たく言えば「かっこで囲んだところを先に計算する」ということ。当たり前の規則だが、数式の書き方によってはややこしいこともある。次の例を見てほしい。

answer = (_i = 10) + (2*(_i + 10))

演算則をよく理解していないと、answer に何が代入されるかを正確に答えるのは難しい。answer には、意図した 50 ではなく 30 が入る。

最初に計算されるのは、ブラケットが一番深いところ、すなわち _i + 10 である。この時点で変数 _i はまだ存在しない。未定義の変数は数値との演算では 0 として扱われるので、_i + 10 は 10、2*(_i + 10) は 20 になる。あとから (_i = 10) が評価されて 10 になり、合計の 30 が answer に入る。

こちらの意図では、まず _i = 10 の代入を先にしてほしい。このような場合、次のようにブラケットを過剰に付与することで演算順序をコントロールする。

answer = (((_i = 10))) + (2*(_i + 10))

これで代入部分の優先度が一気に引き上げられ、今度は正しい結果(50)が入る。

なお、代入部分を囲むブラケットは 2 段で十分ではないかと思うかもしれないが、それは誤り。ブラケット 2 段は _i + 10 と同じ深さになるが、= と + では + のほうが優先度が高いので、_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" を出力する。当然のことだが、フィードバック演算子は変数にしか付けられない。

配列の要素も渡せる。Tc600-4以降では、ハッシュや入れ子の要素の要素も渡せる(多次元代入)。

func(&_a[1])            // func の中で _argv[0] を書き換えると _a[1] が変わる
func(&_h["sub"]["x"])   // 同じく _h["sub"]["x"] が変わる(Tc600-4以降)

配列の変数を引数に渡すと、_argv では要素が展開されて並ぶ。書き戻しは展開した後の位置を基準に行われる。

_a = (1, 2)
func(_a, &_x)           // _argv は (1, 2, _x の値)。_argv[2] を書き換えると _x が変わる
func(&_a)               // _argv の _a の要素数ぶんが、配列として _a に戻る

Tc603-3 までは、書き戻しの位置が引数の位置(何番目の引数か)のままだったため、手前に配列を渡すと別の要素が書き戻されたり、_argv が範囲外になって実行が中断されたりした。呼ばれた関数が _argv を縮めていた場合、残っていない引数は書き戻さない。

第1引数が括弧で囲まれていても(func((1, 2), &_x) や func((_a), &_x))、囲まれていないときと同じに動く(Tc603-5以降)。Tc603-4 までは、このとき引数の区切りが正しく認識されず、& の書き戻しが効かなかった。あわせて、システム関数に渡る引数も、囲まれていないときと同じく1つずつ別の引数になる。たとえば ARRAYSIZE((_a), 1) は _a の要素数を返し、TOSTR((1), (2)) は "12" になる(それまでは第1引数と第2引数がひとつの配列にまとまり、ARRAYSIZE は要素数に 1 を足した値、TOSTR は "1,2" になっていた)。

後方互換性

コロン付き代入演算子(":=")は後方互換性のために残されているが、コロンなしのものと同一の動作をする。