文法†

YAYA の文法を 1 ページにまとめています。章ごとの内容はページ先頭の目次から飛べます。

基礎設定†

概要†

YAYA のデフォルトの DLL ファイル名は yaya.dll で、主ファイル名 yaya は他の名前に自由に変更できる。

YAYA を動作させるには「基礎設定ファイル」と呼ばれるファイルが必要となる。基礎設定ファイルのファイル名は「主ファイル名.txt」で、デフォルトでは yaya.txt になる。DLL のファイル名を hoge.dll に変更したなら、基礎設定ファイルは hoge.txt になる。

基礎設定ファイルはテキストファイルで、実行される OS のデフォルトの文字コードで解読される。国際化を考慮する必要があるなら、マルチバイト文字コードに関する問題を避けるため、基礎設定ファイルは ASCII コードのみで記述するべきである。

設定ファイルが無い場合や、読み込む辞書がひとつも無い場合は、シェルモードで動作する(Tc574-7 / Tc602-12以降。それまでは設定ファイルが無いとエラー E0005 で動作を止めていた)。

設定例†

// dics
dic, basis.dic
dic, control./*doc*/ayc

// option parameters
charset, UTF-8
charset.dic, Shift_JIS
charset.setting, Shift_JIS
charset.output, UTF-8
charset.file, Shift_JIS
charset.save, UTF-8
charset.extension, Shift_JIS

msglang, english
log, executelog.txt
iolog, off
fncdepth, 16

設定はコマンドとパラメータをカンマで区切って指定する。空行(改行のみの行)、// 以降、および /* と */ で囲まれた領域はコメントと見なされる。

読み込み時に辞書エラーが起きた際の挙動†

Tc555-1以降、読み込み時に辞書エラーが起きたときは、まず緊急時用の基礎設定ファイルで再度読み込みを試す。

緊急時用の基礎設定ファイルのファイル名は「主ファイル名_emerg.txt」で、デフォルトでは yaya_emerg.txt になる。DLL 名を変更したときの挙動も通常の基礎設定ファイルと同様。

それでもエラーになった場合は、request 関数から何も返さない(NULL)状態になる。

問題が起きたときのエラーログは GETERRORLOG で取得できる。

yaya_emerg.txt による緊急モードはシェルモードにはならない。yaya_emerg.txt が無ければ、これまでどおり動作を止める。

設定項目†

charset, name†

標準の文字コードを設定する。次の予約値からいずれかを指定する。指定が無い場合は Shift_JIS として扱われる。

以下の charset. で始まるものは個別設定。必ず charset での一括指定より後に書くこと。

charset.dic†

辞書の文字コード。

charset.setting†

基礎設定ファイルそのものの文字コード。

charset.output†

request 関数での入出力の文字コード。

charset.log†

ログファイルや玉の出力の文字コード(Tc569-15以降)。

charset.file†

ファイル入出力の文字コード。

charset.save†

グローバル変数セーブファイル入出力の文字コード。

charset.save.old†

文字コード指定のない旧形式のセーブファイルを読み取るときの文字コード。

charset.extension†

外部 DLL(SAORI 等)入出力の文字コード。

dic, filename ( , charset )†

辞書ファイル filename をロードする。辞書ファイルは YAYA のスクリプトが記述されたプログラムソースファイルで、YAYA はここにプログラムされた内容を元に動作する。辞書ファイルはいくつでも指定して読み込める。

filename は load で指定されたパス位置からの相対パスで指定する。

標準の辞書ファイルは charset で指定した文字コードで記述されたプレーンテキストファイル。このほかに、一定の法則でスクランブルをかけた暗号化ファイルを読み込める。

ファイル名の後に文字コードをカンマ区切りで追加すると、指定したファイルはその文字コードで読み込まれる。

dicif, filename ( , charset )†

dic と同じだが、指定したファイルが存在しないときは無視する(Tc571-2以降)。

dicdir, dirname ( , charset )†

指定したディレクトリ以下のすべてのファイルを辞書として読み込む(Tc556-1以降)。ディレクトリ指定であること以外は dic と同じ。

読み込み順序

ファイルを読む順序は決まっていない。そのため、globaldefine などを定義するときに、読み込み順序によって問題が発生する可能性がある。globaldefine を定義するときは、必ず定義した辞書を最初のほうに読み込むこと。

dicdir で指定したディレクトリの中に _loading_order_override.txt か _loading_order.txt という名前のファイルがあるときは、includeEX,(ディレクトリ)/_loading_order.txt と書いたのと同じ挙動になる。_loading_order_override.txt → _loading_order.txt の順にファイルの存在をチェックし、先に見つかったほうのみ includeEX する(Tc571-2以降)。

dic の指定が無い場合や、dicif の対象がすべて存在しない、dicdir のディレクトリが空、などで読み込む辞書がひとつも無い場合は、シェルモードで動作する(Tc574-7 / Tc602-12以降)。

msglang, language†

ふつうは指定不要

ふつうは特に指定しなくても、エラーメッセージの言語は自動で切り替わる(Tc573-5以降)。

ログに記録されるエラーメッセージ類の言語を選択する。次の予約値からいずれかを指定する。指定が無い場合は japanese として扱われる。

本設定は過去互換のために残されている。次の messagetxt を使うこと。

messagetxt, filename ( , charset )†

ふつうは指定不要

ふつうは特に指定しなくても、エラーメッセージの言語は自動で切り替わる(Tc573-5以降)。

ログに記録されるエラーメッセージ類の言語ファイルを読み込む。たとえば次のように書く。charset を省略した場合は UTF-8(BOM 付)になる(Tc557以降)。

messagetxt,messagetxt/japanese.txt
messagetxt,messagetxt/english.txt
messagetxt,messagetxt/chinese-simplified.txt
messagetxt,messagetxt/chinese-traditional.txt
messagetxt,messagetxt/german.txt
messagetxt,messagetxt/korean.txt

言語ファイル名は yaya-shiori の messagetxt で確認できる。

Tc570-1以降、Windows 上では言語ファイルを DLL に埋め込むようになったので、言語ファイル本体を配布する必要はない。messagetxt にファイル名を書く仕様は、Windows 以外の環境向けにそのまま残されている。

basepath, directory†

辞書等を読み込む基準点となるディレクトリを任意の場所に再設定する。設定しない場合、通常は yaya.dll と同じディレクトリになる(Tc557以降)。

include, filename†

基礎設定ファイルの内容を別ファイルから読み込む。指定したファイルの中身も有効な基礎設定として扱われる。

includeEX, filename†

include と基本的には同じ処理だが、filename に指定したファイル名がディレクトリ名を含む文字列だった場合に、そのディレクトリ内にカレントディレクトリを一時的に切り替えてから include 処理を行う(Tc559以降)。

log, logfilename†

実行ログをファイル logfilename に記録する。charset.log で指定した文字コードで書き込まれる。logfilename は load で指定されたパス位置からの相対パスで指定する。

iolog, [on|off]†

load、unload、request 実行時の入出力文字列と処理時間をログに記録するかを設定する。on で記録する。デフォルトでは on なので、不要な場合に off とすること。off 指定としても、玉にドラッグ&ドロップして起動する使い方をした場合は強制的に on になる。

iolog.filter.keyword, [無視判定文字列]†

request の文字列中に指定した無視判定文字列が含まれていた場合、その request の I/O をログに記録しない(玉への出力もしない)ようにする。複数指定できる。旧定義名は ignoreiolog。

例: OnSecondChange と OnMouse 系のイベントを記録しない

ignoreiolog, ID: OnSecondChange //OnSecondChange無視
iolog.filter.keyword, OnMouse        //OnMouse系無視

基礎設定ファイルの書式上、無視判定文字列の前後の空白、およびコメント // 以降は無視される。

iolog.filter.keyword.delete, [文字列]†

上記を削除する。SETSETTING 向けの書き方で、定義ファイルに直接書いてもあまり意味はない。

iolog.filter.keyword.regex, [無視判定文字列:正規表現]†

iolog.filter.keyword の正規表現版。

iolog.filter.keyword.regex.delete, [文字列]†

上記を削除する。SETSETTING 向けの書き方で、定義ファイルに直接書いてもあまり意味はない。

iolog.filter.mode, [モード]†

上記の iolog.filter.keyword 以下の判定条件について、「条件に合致したものを出力しない」か「条件に合致したものだけを出力する」かを切り替える。定義しない場合は「条件に合致したものを出力しない」モードになる。

iolog.filter.mode,allowlist
iolog.filter.mode,whitelist

条件に合致したものだけを出力するモードに切り替える。

iolog.filter.mode,denylist
iolog.filter.mode,blacklist

条件に合致したものを出力しないモードに切り替える。

fncdepth, [depth]†

関数呼び出しの深さ上限を数値で指定する。デフォルト値は 32。最低値は 2 で、これより小さな値や不正な値を指定した場合も 2 として扱われる。0 にすると深さの上限チェックをしない(Tc556-1以降)。

上限に達すると shiori.OnCallLimit が呼ばれる。

深さの上限とは別に、Tc607-4以降は、YAYA が動いているスレッドのスタックの残りが少なくなった(おおよそ 128KB を切った)ときも、上限に達したときと同じように呼び出しを止めて shiori.OnCallLimit を呼ぶ。止められた呼び出しの値は空になる。fncdepth を 0 や大きな値にしても、スタックを使い切ってベースウェアごと落ちることはなくなる。止まる深さはベースウェアのスレッドのスタックの大きさで変わる(1MB のスタックで数百階層)。Tc607-3 までは、0 にして呼び出しが深すぎるとベースウェアごと落ちていた。

looplimit, [count]†

ループの回数上限を数値で指定する。デフォルト値は 1000000(Tc574-10 / Tc606-4以降。それまでは 10000)。0 にすると上限チェックをしないが、延々と実行を続けてフリーズする。foreach には効果がない(Tc564-1以降)。

上限に達すると shiori.OnLoopLimit が呼ばれる。

save.encode, [on|off]†

セーブファイルを暗号化する。「暗号化」といってもたいしたものではないので、せいぜい多少わかりにくくする程度と考えること。一度 on にするとセーブファイルを削除するまで暗号化されたままになる。設定を変えるときは十分注意すること。

save.auto, [on|off]†

セーブファイル自動保存機能の ON/OFF を切り替える。ON の場合、一定時間ごとのバックアップと終了時の変数自動保存が行われる。OFF の場合は保存関数を明示的に呼ばない限り保存されない。標準は ON。

embed.lazy, [on|off]†

文字列のカッコなし % 埋め込みの名前を実行時に探す(デフォルト: on)。off で辞書の読み込み時に確定する(Tc604-1以降。詳細)。

maxlognum, [count]†

GETERRORLOG で取得できるログの最大値を設定する。デフォルト値は 256 要素。

シェルモード†

Tc574-7 / Tc602-12以降。設定ファイルが無いか、読み込む辞書がひとつも無い場合、YAYAは次の辞書だけを読み込んだものとして動作する。

load { }
unload { }
request { EVAL(_argv[0]) }

requestに渡された文字列をそのまま EVAL し、その結果を返す。式ひとつのほか、; や改行で区切った文の並びや制御文も書ける。ゴースト一式や辞書を用意しなくても、YAYAの式や関数の動作を手早く確かめられる。

request の入力 返値
1+2 3
_a = 3; _a * 5 15
'hello' + ' world' hello world
X = 10 空文字列(グローバル変数 X に 10 が入る)
X 10

関数†

概要†

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

処理の集まりである。

引数の数は変数 _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!" が得られる。

書式†

次の規則がある。

つまり先に挙げた 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" になる。

関数名に使えない名前†

関数名は自由につけられるが、次に抵触する名前はエラーになる。

再帰呼び出し†

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

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"
  }
}

_pool の有無で選択確率がどう変わるかの例を示す。次の関数では、_pool を付けないと "Hello World" が 50%、"こんにちは世界" と "Hallo Welt" が 25% ずつになる。: random_pool(_pool だけでもよい)と書くと、3つの候補がそれぞれ 33.3% ずつになる。if 1 は常に真。

request : random
{
    "Hello World"
    if 1 {
        "こんにちは世界"
        "Hallo Welt"
    }
}
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 は文字列のほかに数値なども扱えるが、出力確定子は結合する際にそれらをすべて文字列に変換し、文字列として結合する。


値と変数†

即値†

YAYA が扱える値は整数、実数、文字列の 3 種類。

整数 (int)†

符号付き 64bit 整数(Tc566-1で拡張。それ以前は 32bit)。10 進数値はそのまま記述する。先頭に 0b を付加すると 2 進数値、先頭に 0x を付加すると 16 進数値を記述できる。

次の関数 int10 は整数 10 を返す。3 つの記述はいずれも 10 進数では 10 だから。

int10
{
    10
    0b1010
    0xa
}

実数 (double)†

符号付き 64bit 浮動小数点数。小数点以下の桁がある場合や、精度が落ちてもいいので非常に巨大な数値を扱いたい場合に使う。

整数との区別は小数点の有無で、YAYA は数値に小数点を見つけると、これを実数として取り扱う。たとえば

Test
{
    3/4*100
}

とすると、結果は 75 ではなく 0 になる。これは 3/4 を評価したときに int として解釈し、小数点以下が切り捨てられるため(演算子は左から順に評価される)。意図した動作にするには

Test
{
    3.0/4*100
}

とする必要がある。3.0 を評価した時点で double 型として解釈するため、結果は実数の 75.0 になる。実数を文字列にすると小数点以下 6 桁になり、75.000000 と出力される。整数にしたい場合は TOINT 関数を使う。

変数の演算でこれを行いたいときは、宣言時に実数を代入するか、TOREAL 関数を用いる必要がある。

Test
{
    _A = 3
    _B = 4
    TOREAL(_A)/_B*100
}

小数点は OS のロケールによらず常に . で、文字列から実数への変換や辞書に書いた実数も . で読む(ドイツ語などの , が小数点のロケールでも同じ)。

文字列†

ダブルクォート(")で囲まれた値は文字列で、変数や関数の展開(文字列内埋め込み要素の展開)が行われる。

行末に / を書くと、改行を含む文字列を記述できる。

"こん\n/
なかんじに\n/
書いてもOK\n/"

後述するヒアドキュメントを用いても、改行を含む文字列を書ける。さくらスクリプトは文字列として記述しないと動作しない。

Tc569-8 以前のバージョンでは、文字列の中にダブルクォートを含められない。Tc569-8 以降のバージョンでは、ダブルクォートを 2 回続けて書くことで、文字列内にダブルクォートを追加できる。

"1""2"

と書くと、1"2 という文字列を定義したことになる。

文字列(展開なし)†

シングルクォート(')で囲まれた値は、展開されない単純な文字列。YAYA では文字列中に変数や関数を埋め込めるが、これが展開されるのはダブルクォートで囲まれた文字列のみ。

Tc569-8 以前のバージョンでは、文字列の中にシングルクォートを含められない。Tc569-8 以降のバージョンでは、シングルクォートを 2 回続けて書くことで、文字列内にシングルクォートを追加できる。

'1''2'

と書くと、1'2 という文字列を定義したことになる。

あまり複雑な使い方をすると後で見にくくなるのでおすすめしないが、同時に使う例として、

'1''2"3'
"1'2""3"

はいずれも、1'2"3 という文字列を定義したのと同じになる。

ヒアドキュメント†

複数行書ける文字列即値。Tc535-1 から使用できる。

<<"
行1
行2
">>

で展開あり文字列、

<<'
行1
行2
'>>

で展開なし文字列になる。ヒアドキュメントの前や後にいろいろな式を書けるが、辞書ファイル内のみのサポートで、EVAL してもエラーになる。

配列とハッシュ†

即値のほかに、値をまとめて扱う型として汎用配列(配列)がある。Tc600-3以降ではハッシュ(連想配列、ハッシュ)も使える。

Tc600-3以降では、配列やハッシュの要素に配列やハッシュを入れる(入れ子にする)こともできる(値の入れ子と多次元代入)。

変数†

変数は値を格納するための領域。ひとつの変数には、

を格納できる。名前は次の禁止条項に抵触しない限り自由につけられる。

値の格納(代入)は代入演算子 = で行う。内容を出力するには、関数と同様にその名前を書く。

request
{
    str = "こんにちは"

    str
}

上は単純な例で、変数 str に文字列を格納し、そのまま出力している。

まだ値が代入されていない変数は、「未定義型」という特殊な型になる。「何も出力しない」ではないことに注意。

request
{
    "Hello World"
    i
}

この場合、i は存在しないため「未定義型」になる。「未定義型」は、数値との演算では 0、文字列との演算では ''(空っぽの文字列)として扱われる。上記の例では、この「未定義型」と "Hello World" という文字列のどちらかが 1/2 の確率で返される。

スコープとライフタイム†

変数はスコープ(有効範囲)の違いによって 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 で復元される。

グローバル変数を削除するには ERASEVAR 関数を使う必要がある。また、その性質上、グローバル変数の初期化は起動時に最初に実行される関数(OnBoot など)で行うことを推奨する。


演算†

基本†

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 を返す。これらの演算子は文字列に対しても正しく機能し、値の大小は辞書順比較で決定される。

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

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

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以降)†

詳細は ハッシュ を参照。

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

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

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" になっていた)。

後方互換性†

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


配列†

概要†

演算子 [ ] は配列要素にアクセスするための演算子。配列には、文字列を分割して擬似的に配列と見なす簡易配列と、コンマ区切りで列挙された要素を扱う汎用配列の 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"
[1] " "
[2] " a "
[3] "land."

したがって request は " a " を返す。

多次元配列風のアクセス†

デリミタ指定をうまく使うと、多次元配列風に値を取り出せる。

request
{
    _ar = "taro|male,ayame|female,hotaru|female"

    _ar[2][1,"|"]
}

_ar[2] は "hotaru|female" になる。さらに | を区切り文字として解釈して [1] を取り出すので、結果は "female" になる。このように、階層ごとにユニークなデリミタを適用することで、任意の位置の値を簡単に抜き出せる。

範囲外の序数を指定した場合の出力については、範囲外の要素を参照。

簡易配列と汎用配列を利用した多次元配列†

もっと直接的に多次元配列を扱いたいなら、

Food = "うどん,ラーメン,おにぎり"
Color = "赤,青,黄"

と簡易配列を宣言し、汎用配列で

Word = (Food, Color)

とすると、Word[0][0] で「うどん」、Word[1][2] で「黄」が得られる。

代入†

ここからは変数限定の機能。要素に代入できる。

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." を出力する。

多次元配列風に [ ] 演算子を連結して使っている場合の代入は、Tc600-3以降で使える(値の入れ子と多次元代入)。それ以前のバージョンでは、代入が可能なのは一次元の場合のみで、次の操作はエラーになった。

request
{
    _ar = "taro|male,ayame|female,hotaru|female"

    _ar[1][1,"|"] = "male"
}

現在の要素数を超える位置へも問題なく代入できる。デリミタが自動的に追加され、要素数が拡張される。

request
{
    _m = "fuji/asama/tanigawa"
    _m[5,"/"] = "daisen"
    _m
}

request は "fuji/asama/tanigawa///daisen" を出力する。

SETDELIM†

SETDELIM 関数を使うと、「デフォルトのデリミタ」をコンマから別の文字列へ変更できる。先に示した例を SETDELIM を使って書き換えたものを示す。

request
{
    _m = "fuji/asama/tanigawa"
    SETDELIM(_m, "/")
    _m[5] = "daisen"
    _m
}

SETDELIM することにより、単純に _m[5] と書けるようになる。多次元配列風に [ ] 演算子を連結して使っている場合、SETDELIM は最初の(一次元目の)[ ] のみで有効。

汎用配列†

汎用配列は、さまざまな型の値を混在して格納できる配列構造。一般的なアクセスでは簡易配列より高速に動作する。

初期化†

i = (100,"test",-1.5)

要素をコンマで列挙して記述する。代入する場合は、上のように要素の集合を ( ) で囲むこと。コンマの演算優先度は代入よりも低いため、ブラケットが無いと、

i = 100,"test",-1.5

は (i = 100),"test",-1.5 と解釈され、i には 100 だけが入る。

配列を空の状態で初期化するには、IARRAY という関数を使う。IARRAY は「空の汎用配列」を返す関数。

i = IARRAY

初期化時に要素をひとつだけ代入したい場合は工夫が要る。たとえば単に i = 100 としたのでは、配列ではなくただの数値の代入になってしまうから。次のように記述する。

i = (IARRAY,100)

要素追加†

i = (i,"add")

とすると、配列の後端に "add" が追加される。配列を追加することもできる。

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 を代入する。

i = (100,200,300,400,500,600)
i[2] = IARRAY

300 が削除され、i は (100,200,400,500,600) になる。

値の更新†

単純に要素へ代入できる。現在の要素数を超える位置へも問題なく代入でき、必要な数だけ要素数が拡張される。

値の取り出し†

通常の変数と同様、序数を指定して記述すればそのまま出力される。範囲外の序数を指定した場合の出力については、範囲外の要素を参照。

i = (100,200,300,400,500,600)
i[4]

500 が出力される。[ ] 演算子が処理する対象は変数である必要はなく、即値でも関数の返値でもよい。

(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)

と書いても、結局次のように結合される。

(100,200,300,400,500,600)

要素に配列やハッシュを入れたいときは、次の「入れ子と多次元代入」を参照。

演算†

要素単位の演算は通常に行える。

汎用配列と単項値との演算は、「全要素へ単項値が演算される」という独特の動作になる。

pref = ("gunnma","ohsaka","hokkaido")
pref += "-ken"

pref は "gunnma-ken","ohsaka-ken","hokkaido-ken" になる。

answer = (2*(1,2,3))[1]

answer には 4 が代入される。2*(1,2,3) の計算結果が (2,4,6) になるから。

汎用配列どうしの演算は、演算可能なすべての組み合わせを出力する。

pref1 = ("gunma","osaka","hokkai")
pref2 = ("-ken","-fu","-do")
pref3 = pref1 + pref2

pref3 は "gunma-ken","gunma-fu","gunma-do","osaka-ken","osaka-fu","osaka-do","hokkai-ken","hokkai-fu","hokkai-do" になる。配列に数値(整数、実数)が入っている場合は、引き算・割り算・掛け算など他の演算も有効。

要素の比較†

配列同士を == で比べると、要素ごとに比べる。Tc600-3以降では、要素が両方とも VOID のときも等しいとみなす(それまでは偽だった)。

関数の引数†

YAYA において関数の引数は汎用配列。_argv の内容は、呼び出し側の引数がそのまま代入された汎用配列になっている。つまり

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")

デリミタ/取得数指定†

_i = (2,"is")
"This is a island."[_i]

簡易配列のデリミタ指定の部分も汎用配列なので、上のようなこともできる。これは次の記述と等価。

"This is a island."[2,"is"]

入れ子と多次元代入(Tc600-3以降)†

配列の要素にハッシュを入れたり、ハッシュの値に配列を入れたりできる。読み出しは _a[1]["x"]、代入は _a[1]["x"] = 5 のように [ ] を続けて書く。

_a = (0, IHASH("x", 1), 2)
_a[1]["x"]        // 1
_a[1]["x"] = 5    // 多次元代入

多次元代入では、途中の要素が文字列なら簡易配列として書き換える:

_a = ("p,q", "r")
_a[0][1] = "Q"    // _a[0] は "p,Q"

ただし、汎用配列の中に新しく汎用配列を入れる手段は無い。, 演算子も _a[1] = (8,9) も、配列を展開して差し込む。代入先の要素がすでに配列やハッシュのときだけは、展開せずに丸ごと置き換える(Tc603-1以降。内側の配列やハッシュを丸ごと書き換える)。

詳細は 値の入れ子と多次元代入 を参照。

範囲指定†

簡易配列・汎用配列とも、序数を範囲で指定できる。取得、代入ともに可能。範囲は汎用配列で指定する。たとえば i[a,b] は「i の要素 a〜b」を表す。

name = ("さくら","せりこ","奈留","まゆら","毒子","美耳")
i = name[1,3]
name[3,4] = "奎子"
j = name
name[0,2] = IARRAY
k = name

対象が簡易配列の場合でも書式は同じ。

name = "さくら,せりこ,奈留,まゆら,毒子,美耳"
i = name[1,3]
name[3,4] = "奎子"
j = name

i は "せりこ,奈留,まゆら"、j は "さくら,せりこ,奈留,奎子,美耳" になる。

デリミタ指定もできる。範囲指定のすぐ後ろに続けて指定する。

animal = "くま!うさぎ!ねこ!いぬ!わに"
i = animal[0,2,"!"]
animal[2,4,"!"] = "ぶた"
j = animal

i は "くま!うさぎ!ねこ"、j は "くま!うさぎ!ぶた" になる。

範囲の端の扱い†

n = (1,2,3,4)
i = n[-2,1]
n[-2,1] *= 5

i は (2,3,4)、n は (1,10,15,20) になる。先頭から数えたいときは n[0,1] のように 0 以上で書く。

範囲外の要素†

範囲外の要素を読むと、簡易配列でも汎用配列でも、未定義の変数と同じく VOID になる(Tc574-8 / Tc603-1以降)。範囲指定の読み出しが範囲外のときは、空の範囲(簡易配列なら空文字列、汎用配列なら空の配列)になる。

_a = (1, 2)
_a[9]            // VOID
_a[9] == ""      // 1
_a[9] == 0       // 1
_a[9] + 1        // 1 (整数)
GETTYPE(_a[9])   // 0
_a[5, 6]         // 空の配列
"a,b"[5, 6]      // ""

並列出力†

すべての式 / 値の前に parallel を書ける。parallel は、出力候補の値が汎用配列だった場合に、要素のそれぞれを出力候補の値にする機能を持つ。

foo0
{
    ("A","B","C")
    "地球"
}

foo1
{
    parallel ("A","B","C")
    "地球"
}

foo0 の出力は、("A", "B", "C") もしくは "地球"。foo1 の出力は、"A"、"B"、"C"、"地球" のいずれかになる。

汎用配列以外に parallel を使っても意味がなく、書かないのと同じ。たとえば次の 2 つの記述は等価。

parallel STRLEN("earth")
STRLEN("earth")

選択方式の array と parallel を利用すると、関数の出力候補と汎用配列を相互に変換できる。さまざまな応用が考えられる。たとえば次の非常に簡潔な関数 cyclic は、汎用配列から記述順に要素値を取り出す。

cyclic : sequential
{
    parallel _argv
}

逆に、すべての式 / 値の前に void と書くことで、計算結果を破棄し、出力候補にしないこともできる。


文字列内埋め込み要素の展開†

概要†

文字列の中に変数や関数を埋め込んで、これらの実行結果を当該位置へ挿入できる。埋め込み方は「範囲付き展開」と「名称最長一致展開」の 2 種類。

範囲付き展開†

要素を %( ) で囲んで埋め込む。

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 がまだ空のため "unknown" になる。ブラケットを過剰に付与して演算順序を調整する。

"遊星「%((_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 は 2 度実行されるが、最初と 2 度目では "%value" の動きが変わる。最初は変数 val + "ue"、2 度目は変数 value と解釈される。request の出力は "redueblue" になる。

展開結果の再利用†

%[ ] という書式で、過去の展開結果を再利用できる。

request
{
    "「%planet」は遠い。「%city」も遠い。もっとも%[1]になら行けなくもない。"
}

planet
{
    "mars"
    "saturn"
    "pluto"
}

city
{
    "newyork"
    "moscow"
    "madrid"
}

%[i] は 0 オリジンで i 番目の展開結果を得る。上の例では、%[0] が %planet の、%[1] が %city の展開結果を示している。

%[ ] は範囲付き展開では使えない。範囲付き展開で過去の結果を再利用したい場合は、それを変数へ入れる。

速度†

名称最長一致による展開は、実行するたびに展開対象を検索しなおすので、範囲付き展開と比べて動作速度が劇的に遅い。本当に必要な場合は別として、通常は範囲付き展開を利用すること。

名前を読み込み時に確定する†

名称最長一致展開は、実行した時点で存在する変数で名前を探す。そのため、URL などの % を含む文字列が、ある時点から急に置き換わることがある。

"https://example.com/?p=100%off"  // 変数 o や of が作られると、その値に置き換わる

設定ファイルに embed.lazy, off と書くと(Tc604-1以降)、カッコなしの % の後の名前を辞書の読み込み時に確定する。


フロー制御†

条件分岐†

if / elseif / else 構文†

式の結果が真であれば、以降に続く { } 内を処理する。条件式は ( ) で括ってもよい。

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 言語では正しいが、YAYA では誤り(読み込み時にエラーになる)。

if i == 0
    if j == 0
        "iとjはともに0である。"

次のように { } が必要。

if i == 0 {
    if j == 0
        "iとjはともに0である。"
}

case / when 構文†

ラベルベースの分岐。case 文が式を評価し、一致するラベル値を持った when 節が実行される。

request
{
    case i {
    when 0 {
        "iは0である。"
    }
    when "A"
        "iは文字列Aである。"
    others {
        "iは0でもAでもない。"
    }
    }
}

others 節は、すべてのラベルに合致しなかった場合に実行される部分。others は省略でき、省略した場合は何もしない。

ヒットさせるラベル値はコンマで列挙できる。また、マイナス符号でヒットさせる範囲を指定することもできる(例:"value1"-"value5")。

request
{
    case name+(i+1) {
    when "Pentium3","Pentium4"
        "Pen!!!は1999年、Pen4は2000年発売発売。"
    when "Pentium5"-"PentiumX" {
        "まだ無い。"
    }
    others
        "分からない。"
    }
}

when に記述するラベル値は必ず即値でなければならない。変数や関数、演算子を含む数式は書けない。when、others が処理する { } 内にスクリプトが 1 つしか存在しない場合は、if と同様に { } を省略できる。

一般形は次のとおり。

case 式
{
  when 値1
  {
    // 値1の場合
  }
  when 値2, 値3
  {
    // 値2または値3の場合
  }
  others
  {
    // どれにもマッチしない場合
  }
}

when と others は、case 式 に続く { } の中に書く。case 式 の直後に { がなく when が続く書き方は、読み込み時にエラー(E0051 caseに続く{がありません)になる。case 式 { のように { を同じ行に書いてもよい。

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 だった場合は、空の文字列が出力される。

{ } の中の候補は 1 行に 1 つ書く。switch 1 { "a" "b" "c" }(各候補を行に分けて書く)は "b" になる。

{ } を候補の数だけ並べて、ブロックごとに位置を対応させる書き方もできる。この場合、位置に対応するブロックの中の候補からランダムに選ばれる。

switch _n
{
  "ゼロ番目"
}
{
  "一番目"
}

switch 内に出力確定子(--)がある場合は、各ブロックの該当する位置にある候補が選択される。

request
{
    switch 1 {
        "かわいい"
        "天才"
        "サル"
        --
        "とは言い難い"
        --
        "ですね。"
        "かもしれません。"
    }
}

request の出力は "天才かもしれません。" になる。中間のブロックには指定位置に候補が無いので、出力が空の文字列になっていることに注意(関数)。

ループ構造†

while、for、foreach の 3 種類のループ構造がある。

基礎設定ファイルの looplimit でループ回数の上限を設定できる。足りない場合は変更すること。標準は 1000000 回(Tc574-10 / Tc606-4以降。それまでは 10000 回)。限界に到達したときは shiori.OnLoopLimit(関数名, 行) が呼ばれる。

while ループ†

while が評価する式が真である間は、{ } 内を繰り返し処理する。条件式はカッコで囲んでも囲まなくてもよい(while (_i < 3) と while _i < 3 は同じ動作になる)。

while 条件式
{
  // 処理
}
request
{
    _i = 1
    _j = 0
    while _i < 11 {
        _j += _i
        _i++
    }

    "1から10をすべて足すと%(_j)である。"
}

上の例は while の機能を簡単に説明している。次の例では、異なる 10 個の文字列を発生している。request の出力は、1〜10 のうちいずれかの平方根を報告する文字列になる。

request
{
    _i = 1
    while _i < 11 {
        "%(_i)の平方根は%(SQRT(_i))である。"
        _i++
    }
}

for ループ†

for は while と同様の先判定ループ構造だが、初期化式、脱出判定式、ループごとに実行する式を一箇所にまとめて書ける点が優れている。C 言語と異なり、すべてのパートが必須。for ( ; ; ) のような無限ループは使えない。

for 初期化 ; 条件 ; インクリメント
{
  // 処理
}

次は、while で挙げた平方根を報告する例を for で書き直したもの。

request
{
    for _i = 1; _i < 11; _i++ {
        "%(_i)の平方根は%(SQRT(_i))である。"
    }
}

_i = 1 はループを始める直前に実行される。_i < 11 はループ判定式で、これが真である間はループが続く。_i++ はループの一単位が完了して先頭へ戻るときに実行される式。

無限ループを作る場合は for 1;1;1 などとする。ただし while 1 で済むため、無限ループを作るときは、可読性の点からも動作速度の点からも while を使うべきである。

Tc603-6以降では、ヘッダ全体を括弧で囲んでもよい。foreach も同じ。

for (_i = 0; _i < 3; _i++) { ... }
foreach (_a; _k, _v) { ... }

Tc603-5 までは、ヘッダ全体を囲む括弧が別々の文に分かれて、E0019 などのエラーになっていた。for と foreach の条件式を ( ) でくくるとエラーになるのは、このバージョンまでの仕様。

foreach ループ†

簡易配列、もしくは汎用配列の各要素値を順番に取り出す(SETDELIM で設定したカスタムデリミタも認識される)。

foreach 配列 ; 変数
{
  // 変数に要素が格納されて実行
}

次の例では、簡易配列の要素値を取り出して数値へ変換し、すべての合計を計算している。

request
{
    _str = "1,3,5,7,9"
    _t   = 0
    foreach _str; _i {
        _t += TOINT(_i)
    }
    _t
}

foreach に続けて処理対象を記述する。上の例では簡易配列 _str を指定している。次に書かれた _i は、取り出された要素値を格納する変数で、必ず変数でなければならない。処理対象の変数のデリミタが SETDELIM によって変更されていても、foreach はそのデリミタを認識して正常に動作する。

foreach は汎用配列も処理できる。

request
{
    _sent = ("I", "am", 31, "years", "old.")
    _t   = ""
    foreach _sent; _i {
        _t += (_i + " ")
    }
    _t
}

request は "I am 31 years old. " を出力する。

foreach ループ内で、要素取り出し対象の簡易配列・汎用配列を書き換えてもかまわない。その場合でも、列挙は書き換え前のデータを対象とする。

Tc600-3以降では、ハッシュも foreach で回せる。変数には値がキーの順に入る。値が配列やハッシュなら、変数も配列やハッシュになる。

2変数の foreach(Tc600-3以降)†

変数をコンマで区切って 2 つ書くと、1 つ目にキー、2 つ目に値が入る。配列と簡易配列でも使え、その場合の 1 つ目には 0 から始まるインデックス(整数)が入る。

foreach 配列またはハッシュ ; キー変数, 値変数
{
  // 処理
}
_h = IHASH("a", 1, "b", 2)
foreach _h ; _k, _v { _s += _k + '=' + _v + ' ' }   // "a=1 b=2 "
foreach ("p", "q") ; _i, _v { ... }                  // _i = 0, 1
foreach "x,y" ; _i, _v { ... }                       // 簡易配列も同じ

変数にはローカル変数とグローバル変数のどちらも書ける。変数が 3 つ以上のとき(_a, _b, _c)や、変数でないものを書いたとき(_k, 1、_a + _b)はエラー E0043 になる。

break†

ループ中に break が現れると、現在実行中の最も深いループから脱出する。

request
{
    _j = 0
    for _i = 0; _i < 100; _i++ {
        _j = _i*_i
        if  _j >= 100
            break
    }
    _i - 1
}

上の例では、for は初期値 0 の _i が 100 に達するまでループを実行しようとする。しかし、ループ内には「_i を二乗した結果が 100 以上になったらループから抜ける」ように break が仕込まれている。したがって、実際には _i = 10 の時点でループが終了する。request は _i から 1 を減じた値を返すので、二乗した結果が 100 を超えない最大の整数(9)を求める関数になっている。

continue†

ループ中に continue が現れると、その位置からすぐにループ先頭へ戻り、次のループ反復に進む。

request
{
    _j = ""
    for _i = 0; _i < 3; _i++
    {
        _j += "go "
        if  _i > 0
            continue
        _j += "ahead "
    }
    _j
}

_i は 0、1、2 と変化するが、1 と 2 では continue が働くので、_j に "ahead " を追加する式が実行されない。したがって 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 しかないので、これが出力される。

引数つき return(Tc573-1以降)†

return 式 の形で戻り値を明示できる。引数には定数だけでなく数式も書ける。

to_rad
{
    if GETTYPE(_argv[0]) == 3 {
        return -1
    }
    return _argv[0]*2.0*3.14/360.0
}

引数つきの return は、それまでに蓄積した候補をすべて破棄して指定した値に置き換える。次の例では返り値は必ず "D" になり、"A" "B" "C" は破棄される。

test_func
{
    "A"
    "B"
    "C"
    return "D"
}

プリプロセス†

概要†

辞書ファイルの読み込み時に実行されるプリプロセッサコマンド。

#define†

辞書ファイルの生テキストに対して直接文字列置換を行う:

#define before after

宣言以降のすべてのテキストに対して、そのファイルの末尾まで適用される。置換は宣言された順に実行されるため、先に変換すべきものを先に書く。

#globaldefine†

#define と似ているが、スコープが広い:

#globaldefine before after

以降に読み込まれるすべての辞書ファイルにわたって有効。最初の辞書ファイルの先頭に置くと、プロジェクト全体に効果が及ぶ。

処理順序: #define の置換が先に実行され、次に #globaldefine の置換が行われる:

#globaldefine tea green
#define tea milk
"teacup"

結果: "milkcup" (#define が優先される)

__AYA_SYSTEM_FILE__ {#08-preprocessor-aya_system_file}†

YAYA Tc543-1 以降で使用可能。DLLからの現在ファイルの相対パスを返す:

"__AYA_SYSTEM_FILE__"

例:"aya_aitalk.txt" を出力

__AYA_SYSTEM_LINE__ {#08-preprocessor-aya_system_line}†

Tc543-1 以降で使用可能。__AYA_SYSTEM_FILE__ と同じ仕組みで、実行中のファイルの現在行番号を返す。

__AYA_SYSTEM_FUNC__ {#08-preprocessor-aya_system_func}†

Tc574-2 以降(600 系は Tc602-2 以降)で使用可能。関数の中に書くと、その関数の名前に置き換わる:

OnTest
{
    LOGGING("__AYA_SYSTEM_FUNC__: called")
}

例:"OnTest: called" を出力

#ifdef / #ifndef / #elifdef / #elifndef / #else / #endif†

Tc574-2 以降(600 系は Tc602-2 以降)で使用可能。名前が定義されているかどうかで、辞書の一部を読み込むかどうかを切り替える:

#ifdef 名前
(名前が定義されていれば読み込む)
#elifdef 別の名前
(最初の名前が未定義で、別の名前が定義されていれば読み込む)
#else
(どれにも当てはまらなければ読み込む)
#endif

読み込まれない区間の行は、構文を解析しない。存在しないシステム関数の呼び出しや、その YAYA が知らない構文を書いてもエラーにならない。区間内の #define / #globaldefine も登録されない。

ただし、コメント(/* */)・行末の / による行の結合・ヒアドキュメントは先に処理される。ヒアドキュメントの中に書いた #endif などは、プリプロセッサとして扱われない。

判定の対象になる名前†

名前 定義されている条件
#define の名前 そのファイルの、その行より前で定義されている
#globaldefine の名前 それより前に読み込まれた辞書ファイル(同じファイルならその行より前)で定義されている
__AYA_SYSTEM_YAYA5__ 500 系の YAYA
__AYA_SYSTEM_YAYA6__ 600 系の YAYA
__AYA_SYSTEM_SYSFUNC_関数名__ そのシステム関数がある(例:__AYA_SYSTEM_SYSFUNC_FREADJSON__)
__AYA_SYSTEM_FILE__ / __AYA_SYSTEM_LINE__ / __AYA_SYSTEM_FUNC__ 常に定義されている

__AYA_SYSTEM_YAYA5__ / __AYA_SYSTEM_YAYA6__ / __AYA_SYSTEM_SYSFUNC_関数名__ は #ifdef 系の判定にだけ使われる名前で、辞書の中の文字列は置き換えない。

#globaldefine は読み込み順の影響を受ける。後で読み込まれる辞書ファイルの #globaldefine は見えない。

500 系と 600 系で共用する辞書†

600 系で増えた構文や関数を使うと、500 系では読み込み時にエラーになる(未知の関数の呼び出しは E0071 など)。#ifdef で書き分ければ、どちらでもエラーにならない:

#ifdef __AYA_SYSTEM_YAYA6__
LoadConfig
{
    FREADJSON('config.json')
}
#else
LoadConfig
{
    // 500 系向けの代わりの処理
}
#endif

特定のシステム関数があるかどうかで書き分けるときは __AYA_SYSTEM_SYSFUNC_関数名__ を使う。こちらは今後の版で関数が追加されたときにも、そのまま使える:

#ifdef __AYA_SYSTEM_SYSFUNC_DUMPJSON__
    _s = DUMPJSON(_data)
#else
    _s = ''
#endif

#ifdef を知らない古い YAYA(Tc574-1 以前、Tc602-1 以前)では、#ifdef などの行がエラー E0076 になり、区間の中身もすべて解析される。共用の辞書を配布するときは、対応する YAYA の版を明記すること。

#error / #warning†

Tc574-2 以降(600 系は Tc602-2 以降)で使用可能。読み込み時にエラー・警告を出す:

#ifndef __AYA_SYSTEM_YAYA6__
#error この辞書は YAYA 600 系(Tc602-2 以降)が必要です
#endif

エラー†

番号 内容
E0074 #ifdef 系に名前が無い、名前が2つ以上ある、#else / #endif の後ろに文字列がある
E0101 対応する #ifdef / #ifndef が無い #elifdef / #elifndef / #else / #endif
E0102 #else の後に #elifdef / #elifndef / #else がある
E0103 #ifdef / #ifndef が #endif で閉じられていない(いちばん内側の #ifdef の行を表示する)
E0104 #error
W0028 #warning

予約語†

概要†

YAYAシステムで予約されている名前。ユーザー定義の変数名や関数名に使用できない。

システム関数†

システム関数一覧 に掲載されているすべての関数名は予約済み。

大文字+アンダースコアの名前

大文字+アンダースコアのみで構成される名前は将来の互換性のためにも使わないほうが良い。

制御構造キーワード†

以下の制御フロー用語は予約済み:

if  elseif  else  case  when  others  switch
while  for  break  continue  return  foreach
void  parallel

void と parallel は関数の出力を制御するキーワード(配列)。

演算子†

変数名・関数名に使用できない予約済み演算子記号:

( ) [ ] ! ++ -- * / % + - & == != <= >= < > _in_ !_in_ && ||
= := += -= *= /= %= +:= -:= *:= /:= %:= ,=

演算子の単語・記号は、変数名や関数名の一部に含めることもできない。


WindowsDLLとしてのYAYA†

概要†

YAYA(読み: やや)は、文字列処理を行うための DLL。C 言語に似た文法を使用して、

といった処理ができる。YAYA は Windows DLL なので、直接利用するためには、プログラミングに関する知識がある程度必要。

動作環境†

Windows 用。SSP と同じ開発環境、つまり Windows 95 でも動くと思われる設定で構築されている。

エクスポート関数†

YAYA は次の公開された関数を持つ。YAYA を利用するプログラムは、YAYA を LoadLibrary した後にこれらの関数を実行して、所望の処理を行う。

load†

extern "C" __declspec(dllexport) BOOL __cdecl load(HGLOBAL h, long len)

YAYA に初期化を指示する。YAYA を LoadLibrary して使用を開始する直前に、この関数を一度だけ必ず実行すること。h には「YAYA がカレントとして認識するディレクトリの絶対パス」を、len には h の長さを渡す。h の領域の解放は YAYA 側で行うので、呼び出し側では使いっぱなしでかまわない。

unload†

extern "C" __declspec(dllexport) BOOL __cdecl unload()

YAYA に終了を指示する。YAYA を FreeLibrary する直前に一度だけ実行すること。

request†

extern "C" __declspec(dllexport) HGLOBAL __cdecl request(HGLOBAL h, long *len)

YAYA に処理を指示し、結果を得る。h には処理対象の文字列を、*len には h の長さを渡す。渡した h の領域の解放は YAYA 側で行うので、呼び出し側では使いっぱなしでかまわない。

処理結果は戻り値で得られる。処理結果の長さは *len に格納される(つまりこの値は書き換えられる)。戻り値を取得した後、領域を *len で示されるサイズで解放(GlobalFree)すること。

技術的な注記†

これは、デスクトップマスコットソフトウェア「伺か」で使用される擬似 AI 用 DLL「SHIORI」のインタフェース規格と完全に同一のもの。

利用規定†


文字コード関連†

内部文字コード†

YAYA は内部文字コードとして UCS-2 を使用する。辞書操作・ファイルI/O・外部ライブラリとのやり取りを行う際は、すべての文字列を自動的に UCS-2 との間で変換する。

文字コードIDシステム†

一部の関数では、文字コードを数値IDで参照する。名前で指定できる関数では、テキスト表現の名前を使う(文字コードの名前):

エンコーディング テキスト表現 ID
Shift JIS Shift_JIS 0
UTF-8 UTF-8 1
EUC-JP EUC-JP 2
Big5 Big5 3
GB2312 GB2312 4
EUC-KR EUC-KR 5
ISO-2022-JP (JIS) ISO-2022-JP 6
バイナリ binary 126
OSデフォルト default(OSNative も同じ) 127

文字コードの名前†

文字コードを名前で指定するときは、上の表のテキスト表現を使う。大文字小文字は区別しない。

使えない名前(UTF-16、ISO-8859-1 など)をシステム関数に渡すと、警告 W0012 を出して失敗する(GETLASTERROR は 12)。どう失敗するかは関数ごとのページを参照。Tc574-8 / Tc603-1 より前は、警告を出さずに OS デフォルトとして扱っていた。

設定ファイル(charset など)に使えない名前を書いたときは、これまでどおり警告を出さずに OS デフォルトになる。

バイナリエンコーディングの特殊ケース†

バイナリ形式は「UCS-2文字列の下位バイトをそのままバイト列にした特殊な文字コード」。変換なしにデータをやり取りするために FWRITEBIN や FREADBIN などの関数向けに設計されている。

重要な制限: バイナリモードであっても YAYA は文字列内のヌルバイト(0x00)を処理できない。また FUNCTIONEX などの既存システム辞書はバイナリ文字列値を想定していない。

関連†


ハッシュ(Tc600-3以降)†

Tc600-3 以降では、値の型にハッシュ(連想配列)が加わった。

ハッシュの値やキーには、配列やハッシュを入れられる(入れ子)。入れ子の読み書きや多次元代入は 値の入れ子と多次元代入 を参照。

500 系からの互換性に関わる変更は 600 での変更点 を参照。

生成†

ハッシュは IHASH で作る。キーと値を交互に並べる。IHASH() は空のハッシュになる。

_h = IHASH("name", "さくら", "age", 16)
_e = IHASH()

文字列から作るときは HASH_SPLIT を使う。

_h = HASH_SPLIT("a=1,b=2", ",", "=")

ハッシュの変数は、IHASH などで最初にハッシュとして作っておく必要がある。未定義の変数に _x["a"] = 1 と書いても、ハッシュではなく汎用配列の扱いになる("a" は数値として 0 番目と解釈される)。

要素の参照・代入†

h[キー] で値を読み、h[キー] = 値 で追加・上書きする。

_h = IHASH()
_h["name"] = "さくら"
_h["name"]       // "さくら"
_h["none"]       // VOID (キーが無いときは未定義の変数と同じ)
_h["age"] += 1   // 複合代入も使える

キーの一致†

スカラーのキーは「数値」と「文字列」に分けて比べる。

このため、h[1]、h["1"]、h[1.0] はすべて同じキーになる。一方、h[1.5] と h["1.5"]、h[0] と h["-0"] は別のキーになる。

同じキーとみなされる値を複数回 IHASH に渡したときは、最初のキーと値が残る。

IHASH(9, "i9", "9", "s9")   // 9=i9 だけ

並び順†

ハッシュの要素はキーの順に並ぶ。挿入した順ではない。HASH_KEYS、HASH_VALUES、文字列化、foreach、セーブファイルの並びはすべてこの順になる。

_h = IHASH(10,1, 9,1, 100,1, -1,1, "b",1, "B",1, 2.5,1)
HASH_KEYS(_h)   // -1,2.5,9,10,100,B,b

値としての振る舞い†

操作 結果
真偽 要素が1つ以上あれば真、空なら偽
GETTYPE / GETTYPEEX 5
文字列化(TOSTR や文字列との連結) キー=値 を , でつないだもの。例 a=1,b=x
数値化 0
h1 == h2 要素数、キー、値がすべて一致すれば真。値が入れ子なら中身まで比べる
ハッシュとハッシュ以外の == 常に偽(文字列化した結果が同じでも偽)
+(相手がスカラーかハッシュ) 両方を文字列にして連結する
+(相手が配列) 配列の要素ごとに、ハッシュと文字列として連結する
- * / % 警告 W0024 を出す(下記)
_h = IHASH("a", 1)
_h + 1        // "a=11"
'z' + _h      // "za=1"
_h + _h       // "a=1a=1"
(1,2) + _h    // ("1a=1","2a=1")

- * / %(複合代入を含む)の両辺のどちらかがハッシュのときは、警告 W0024 を出す。結果は、相手がスカラーなら右辺がそのまま返り(h - 1 は 1、1 * h は h)、相手が配列なら要素ごとに計算される。意味のある結果にはならないので使わないこと。なお、配列の要素がハッシュの場合((h, 1) - 1)は W0024 は出ない。

関数の引数と , 演算子†

汎用配列は引数や , 演算子で展開されるが、ハッシュは展開されず、そのまま1つの要素になる。

_a = (0, _h, 2)   // 要素数 3。_a[1] はハッシュ
func(_h)          // func の中で _argv[0] がハッシュになる

foreach†

ハッシュも foreach で回せる。1変数の形では、値がキーの順に入る。

_h = IHASH("a", 1, "b", 2)
foreach _h ; _v
{
  // _v = 1, 2
}

変数を2つ書くと、1つ目にキー、2つ目に値が入る。この形は配列と簡易配列でも使え、1つ目には 0 から始まるインデックスが入る。

foreach _h ; _k, _v { _s += _k + '=' + _v + ' ' }   // "a=1 b=2 "
foreach ("p", "q") ; _i, _v { ... }                  // _i = 0, 1
foreach "x,y" ; _i, _v { ... }                       // 簡易配列も同じ

詳細は フロー制御 を参照。

関連†


値の入れ子と多次元代入(Tc600-3以降)†

Tc600-3 以降では、配列の要素、ハッシュの値、ハッシュのキーに、配列やハッシュを入れられる(入れ子)。入れ子の内側は [ ] を続けて書いて読み書きする。要素の要素へ直接代入することを多次元代入と呼ぶ。

500 系では配列の要素はスカラーだけで、a[x][y] = v は読み込み時にエラー E0029 だった。500 系からの変更点は 600 での変更点 を参照。

ハッシュそのものについては ハッシュ を参照。

入れ子の作り方†

ハッシュの値に入れる†

ハッシュの要素に代入すれば、配列もハッシュもそのまま値になる。

_h = IHASH()
_h["list"] = (1, 2, 3)        // 値に配列
_h["sub"] = IHASH("x", 1)     // 値にハッシュ

IHASH の引数に配列を書くと、ほかの関数と同じく展開される。IHASH("list", (1, 2, 3)) は IHASH("list", 1, 2, 3) と同じになり、値に配列は入らない。配列を値にしたいときは、上のようにあとから代入する。ハッシュは展開されないので、IHASH("sub", IHASH("x", 1)) のように書ける。

配列の要素にハッシュを入れる†

ハッシュは , 演算子でも要素への代入でも展開されず、1つの要素になる。

_h = IHASH("x", 1)
_a = (0, _h, 2)     // 要素数 3。_a[1] はハッシュ
_a[2] = _h          // 同上
_a ,= _h            // 末尾に追加

配列の要素に配列を入れる†

配列の中に新しく配列を入れる構文は無い。, 演算子、,=、要素への代入(_a[1] = (8, 9))のどれも、右辺の配列を展開して差し込む(500 系と同じ)。

_a = (1, 2)
_a[1] = (8, 9)      // 1,8,9 (要素数 3)
_a[1] = IARRAY()    // 1,9 (空の配列を差し込むと要素が消える)

ただし、代入先の要素がすでに配列やハッシュのときは、展開せずに丸ごと置き換える(Tc603-1以降。内側の配列やハッシュを丸ごと書き換える)。

配列の中に配列を持つ値は、次のような関数が返す。

_v = PARSEJSON('[[1,2,3],[4]]')   // 要素数 2。_v[0] は配列 (1,2,3)

こうして得た配列の中の配列も、多次元代入で書き換えられる(Tc602-7以降。途中の値ごとの動作)。

段階的に組み立てる†

内側のハッシュは自動では作られない。途中のハッシュは IHASH で作ってから、その中へ代入する。

_m = IHASH()
_m["a"] = IHASH()
_m["a"]["b"] = 1
_m["a"]["list"] = IARRAY()
_m["a"]["list"] ,= "p"
_m["a"]["list"] ,= "q"
DUMPJSON(_m)   // {"a":{"b":1,"list":["p","q"]}}

途中のハッシュを作らずに _m["c"]["d"] = 1 と書くと、_m["c"] はハッシュではなく配列 (1) になる(途中の値ごとの動作)。

読み出し†

[ ] を続けて書くと、1つ目の [ ] の結果に2つ目の [ ] を、その結果に3つ目を……と順に適用する。

_h = IHASH()
_h["list"] = (1, 2, 3)
_h["sub"] = IHASH("x", 1)
_a = (0, _h, 2)

_h["sub"]["x"]      // 1
_h["list"][1]       // 2
_a[1]["sub"]["x"]   // 1
_a[1]["list"]       // (1,2,3) そのものが返る

それぞれの [ ] は、その時点の値の型に応じて次のように動く。どの段でも、該当する要素が無いときは未定義の変数と同じく VOID になり、そこから先の [ ] も VOID になる(エラーにはならない。範囲外の要素)。

値 [x] の結果
配列 x 番目の要素。要素が配列やハッシュならそのまま返る
ハッシュ キー x の値(キーの一致)
文字列 簡易配列として x 番目の要素
数値 x が 0 ならその値、それ以外は VOID
VOID(未定義の変数など) VOID

Tc602-13 までは、配列の範囲外、ハッシュの無いキー、VOID への [ ] は空文字列だった(文字列と数値の範囲外はもとから VOID)。

範囲指定も使える。配列の範囲指定 [a, b] の結果は、入れ子の要素をそのまま含む配列になる。範囲外なら空の配列になる。ハッシュの範囲指定は空文字列になる。

_v = PARSEJSON('[[1,2],[3,4],[5,6]]')
_v[0, 1]     // 要素数 2 の配列。どちらの要素も配列のまま

簡易配列のデリミタ†

SETDELIM で変数に設定したデリミタは、その変数に直接付けた1段目の [ ] にだけ効く。入れ子の中の文字列を簡易配列として読むときは、2段目以降の [ ] でデリミタを指定する。

_s = "a|b"
SETDELIM(_s, "|")
_t = (_s, "c")
_t[0][1]         // VOID (2段目は "," で区切られるので範囲外)
_t[0][1, "|"]    // "b"

文字列への埋め込み†

文字列の中でも [ ] を続けて書ける。

"値は %_h['sub']['x'] です"      // 値は 1 です
"値は %(_h['list'][1]) です"     // 値は 2 です

入れ子の配列やハッシュそのものを埋め込むと、文字列化した結果になる。

関数の引数と , 演算子†

関数の引数や , 演算子で展開されるのはいちばん外側の配列だけで、その要素にある配列やハッシュは展開されずに1つの要素のまま残る。ハッシュはいちばん外側でも展開されない。

_v = PARSEJSON('[[1,2,3],[4]]')

func(_v)        // _argc は 2。_argv[0] は配列 (1,2,3)
func(_v[0])     // _argc は 3。_argv は 1,2,3
func(_v, 9)     // _argc は 3。_argv[0] は配列 (1,2,3)
_c = (_v, 9)    // 要素数 3。_c[0] は配列 (1,2,3)

ARRAYSIZE(_v)      // 2
ARRAYSIZE(_v[0])   // 3

関数に入れ子の配列を渡すと、いちばん外側の配列は _argv そのものになる。受け取った側で「引数が1つの配列」と「引数が複数」を区別する手段は無いので、入れ子の配列を関数に渡すときは、ハッシュに包むか、配列の要素を1つずつ渡すとよい。

GETTYPE に要素が1つだけの配列を渡すと、配列ではなくその要素の型を返す(500 系と同じ)。上の例では GETTYPE(_v[1]) は 4 ではなく 1 になる。

foreach†

入れ子の配列やハッシュも foreach で回せる。ループ変数には要素がそのまま入るので、要素が配列やハッシュなら、さらに foreach で回したり [ ] で読んだりできる。

_h = PARSEJSON('{"a":{"x":1,"y":2},"b":{"x":3}}')
foreach _h ; _k, _sub {
  foreach _sub ; _k2, _v {
    // _k/_k2 = _v の順に a/x=1, a/y=2, b/x=3
  }
}

foreach _h["a"] ; _k, _v { ... }   // 内側だけを回す

2変数の foreach は ハッシュ と フロー制御 を参照。

値のコピー†

代入や引数渡しは値のコピーになる。入れ子の内側も含めて、コピー元とコピー先は互いに影響しない。

_h = IHASH("a", 1)
_h["self"] = _h
_h["self"]["a"] = 2
_h["a"]             // 1 のまま

内側を取り出して書き換えても、元の値は変わらない。元の値を変えたいときは、書き換えたものを代入し直すか、多次元代入を使う。

_sub = _h["self"]
_sub["a"] = 3        // _h["self"]["a"] は 2 のまま
_h["self"] = _sub    // これで _h["self"]["a"] が 3 になる

比較†

配列同士、ハッシュ同士の == は中身まで比べる。入れ子の内側も同じ規則で再帰的に比べる。

PARSEJSON('{"a":[1,2]}') == PARSEJSON('{"a":[1,2]}')   // 1

ASEARCH で要素がハッシュの配列を探すときも同じ規則で比べる。ただし、探す値に配列を渡すと引数として展開されるので、配列そのものを探すことはできない。

ASORT は、要素の配列やハッシュを文字列にして(int / double では 0 として)並べる(要素が配列やハッシュのとき)。

多次元代入†

要素の要素へ直接代入できる。= のほか += ,= などすべての代入演算子で使える。

_h["sub"]["x"] = 5
_h["list"][0] += 10
_h["list"] ,= 4               // 内側の配列の末尾に追加
_a[1]["sub"]["y"] = "new"

右辺の値は、1次元の代入と同じく最後の [ ] の位置に入る。そのため、その位置の要素がスカラーなら、右辺の配列は展開して差し込まれる。内側の配列を丸ごと置き換えるときは、1つ手前の段に代入する。

_h["list"] = (1, 2, 3)
_h["list"][0] = (7, 8)    // _h["list"] は 7,8,2,3
_h["list"] = (7, 8)       // _h["list"] を (7,8) に置き換える

範囲指定の代入(_h["list"][0, 1] = "R")も1次元と同じように使える。

内側の配列やハッシュを丸ごと書き換える†

範囲指定でない代入で、代入先の要素が今配列かハッシュのときは、右辺を展開せずに要素を丸ごと置き換える(Tc603-1以降)。配列の中の配列にも、1つ手前の段への代入と同じ書き方で、置き換え・追加・要素ごとの計算ができる。

_v = PARSEJSON('[[1,2],[3,4]]')
_v[0] = (7, 8, 9)     // _v は (7,8,9),(3,4)   丸ごと置き換える
_v[0] ,= 5            // _v は (7,8,9,5),(3,4) 内側の末尾に追加する
_v[0] += 1            // _v は (8,9,10,6),(3,4) 内側の要素ごとに計算する
_v[0] = _v[0]         // 変わらない
_v[0] = IARRAY        // _v は (),(3,4)        空の配列に置き換える(要素は消えない)
_v[0, 0] = IARRAY     // _v は (3,4)           範囲指定なら展開して差し込む(要素を消す)

Tc602-13 までは、根が配列のときに内側の配列が展開されて、入れ子が崩れていた(_v[0] = _v[0] で _v が 1、2、(3,4) の3要素、_v[0] ,= 5 で 1、2、5、(3,4) の4要素になる)。

途中の値ごとの動作†

多次元代入は、最後の段から根元の変数に向かって、それぞれの段の値を書き換えては1つ手前の段へ入れ直す。それぞれの段の値の型によって、次のように動く。

途中の値 動作
ハッシュ キーの値を追加・上書きする
配列 要素を書き換える。範囲外なら、足りない分を VOID で埋めて末尾に追加する
文字列 簡易配列として書き換える
数値 配列に変えてから書き換える。0 番目は VOID になり、元の数値は失われる
無い(範囲外・キーが無い) 未定義の変数と同じく、汎用配列を作って書き換える
_a = ("p,q", 1)
_a[0][1] = "Q"        // _a[0] は "p,Q"

_h = IHASH("l", (1, 2))
_h["l"][4] = 9        // _h["l"] は 1,2,VOID,VOID,9

_n = (1, 2.5)
_n[1][2] = 9          // _n[1] は配列 (VOID,VOID,9)

_e = IHASH()
_e["n"]["x"] = 5      // _e["n"] は配列 (5) (ハッシュは作られない)
_e["m"][2] = 5        // _e["m"] は配列 (VOID,VOID,5)

_e["n"]["x"] = 5 で _e["n"] が (5) になるのは、キー "x" を配列の序数として読むと 0 番目になるため。未定義の変数への _x["x"] = 5 と同じ結果になる。

Tc602-13 までは、無い段を空文字列の簡易配列として書き換えていたので、_e["n"] は文字列 "5"、_e["m"] は文字列 ",,5" になっていた。

書き換えた値を1つ手前の段へ入れ直すときは、その値が配列でも展開せず、1つの要素として置き換える。右辺の配列が展開されるのは最後の段だけなので、配列の中の配列も入れ子のまま書き換えられる。

_v = PARSEJSON('[[1,2],[3,4]]')
_v[0][0] = "A"        // _v は ("A",2),(3,4)
_v[1][0] = (7, 8)     // _v は ("A",2),(7,8,4)

ただし、途中の段が範囲指定のとき(_v[0, 1][0] = 9)は、取り出した範囲の配列を書き換えてから、展開して元の範囲へ差し戻す。

Tc602-6 までは、配列の中の配列に多次元代入すると、書き換えた内側の配列が外側の配列へ展開されて入れ子が崩れていた(上の _v[0][0] = "A" で _v が "A"、2、(3,4) の3要素になる)。途中の値が数値の場合も、作った配列が外側へ展開されていた。参照渡し(&_v[0][0])の書き戻しも同じ。

代入の根元†

代入の根元は変数でなければならない。(式)[0] = 1 や func()[0] = 1 などはエラー E0029 になる。

参照渡し†

フィードバック演算子(参照渡し)でも要素の要素を渡せる(Tc600-4以降)。func(&_h["sub"]["x"]) の中で _argv[0] を書き換えると _h["sub"]["x"] が変わる。Tc600-3 では手前の次元が無視され、_h["x"] に書き込まれていた。

文字列化と保存†

入れ子の値を文字列にすると(TOSTR、文字列との連結、埋め込みなど)、内側もそのまま , や = でつながれるので、どこまでが内側なのかわからなくなる。

_h = IHASH()
_h["l"] = (1, 2)
_h["m"] = IHASH("x", 1)
TOSTR(_h)    // "l=1,2,m=x=1"

入れ子の形を保ったまま扱いたいときは次の方法を使う。

目的 方法
変数をファイルに保存して復元する SAVEVAR / RESTOREVAR。入れ子は IARRAY{...} / IHASH{...} の形で保存される(保存形式)
中身を確認する DUMPVAR(Tc600-4以降)。入れ子は (array)[ ... ] / (hash){ ... } で表示される。LOGGING(Tc602-8以降)も、入れ子の配列を ( )、ハッシュを { } で囲んで書き出す
文字列にして受け渡す DUMPJSON と PARSEJSON。ただし JSON で表せない区別(整数のキーと文字列のキー、VOID と空文字列など)は失われる。区別を保つなら SERIALIZEVAR と DESERIALIZEVAR(Tc607-3以降)。セーブファイルと同じ書式の文字列になる

制限†

関連†


正規表現†

RE_ で始まるシステム関数は、正規表現で文字列を検索・分割・置換する。このページでは、YAYA から正規表現を使うときの基本と、Tc606-6 以降で使えるサブルーチン呼び出しをまとめる。

正規表現エンジンは、Tc606-6 以降は伺か共用の正規表現エンジン uka_regexp を使う。構文と意味は PCRE2(Perl 互換)に近い。構文の細かい部分(先読み・後読み、Unicode プロパティ、再帰、制御動詞など)は uka_regexp の README を参照。 このページに書いてある基本的な書き方は、従来どおり使える。

関数の使い分け†

知りたいこと 関数
文字列のどこかにマッチする部分があるか RE_SEARCH
文字列全体がマッチするか RE_MATCH
マッチする部分をすべて探す RE_GREP
マッチした部分で文字列を分割する RE_SPLIT
マッチした部分を置換する RE_REPLACE($1 などは使えない)、RE_REPLACEEX(使える)
配列の中からマッチする要素を探す RE_ASEARCH(最初の 1 つ)、RE_ASEARCHEX(すべて)
マッチした内容・位置・長さを調べる RE_GETSTR、RE_GETPOS、RE_GETLEN
マッチの条件を変える RE_OPTION
if RE_SEARCH(reference[0], 'こんに?ち[はわ]') {
  "こんにちは"
}

パターンの書き方†

パターンは文字列で渡す。シングルクォートで囲むのがおすすめ。ダブルクォートの文字列では %(...) などが埋め込みとして展開されるが、シングルクォートなら展開されない。\ はどちらでもそのまま正規表現に渡るので、\d と書ける。

RE_SEARCH("abc123", '\d+')    // 1

基本の構文†

書き方 意味
abc、あ その文字そのもの。特別な意味の記号(. * + ? ( ) [ ] { } ^ $ | \)は、前に \ を付けると記号そのものになる
. 改行以外の任意の 1 文字
[abc]、[a-z]、[^abc] 文字の集まり、範囲、それ以外
\d \w \s 数字(0-9)、単語の文字(A-Za-z0-9_)、空白(スペース・タブ・改行など)
\D \W \S \d \w \s 以外
^、$ 文字列の先頭、末尾(m オプションなら行頭、行末)
\b 単語の境界
*、+、? 0 回以上、1 回以上、0 回か 1 回の繰り返し
{3}、{2,4}、{2,} 3 回、2〜4 回、2 回以上の繰り返し
*?、+?、?? できるだけ短くマッチする繰り返し(最短一致)
a|b a または b
(...) グループ。マッチした内容を取り出したり、置換文字列で使ったりできる
(?:...) 取り出さないグループ
(?<name>...) 名前付きグループ

\d \w \s \b は ASCII だけ

\d は全角の数字に、\w は日本語の文字にマッチしない。日本語を判定するときは、Unicode のプロパティを使う。ひらがなは \p{Hiragana}、カタカナは \p{Katakana}、漢字は \p{Han}。

RE_SEARCH("あ", '\p{Hiragana}')            // 1
RE_SEARCH("2024", '^\d+$')            // 0(全角の数字)
RE_REPLACEEX("ひらがなカタカナ", '\p{Katakana}+', '[$0]')   // ひらがな[カタカナ]

使える構文はこれだけではない。先読み (?=...)・後読み (?<=...)、所有量指定子、アトミックグループ、\Q...\E などの詳細は uka_regexp の README を参照。

オプション†

RE_OPTION で、以降の正規表現関数に効くオプションを設定する。既定ではオプションなし。

文字 意味
i 大文字と小文字を区別しない
m ^ $ を行頭・行末にマッチさせる(複数行として扱う)
s . を改行にもマッチさせる
x パターン中の空白を無視し、# から行末までをコメントにする
RE_OPTION("im")

オプションはパターンの中でも指定できる。(?i) はそこから先を、(?i:...) はかっこの中だけを、大文字小文字を区別しない指定にする。(?-i) で解除する。

RE_SEARCH("ABC", '(?i)abc')       // 1
RE_SEARCH("ABC", '(?i:a)BC')      // 1

改行として扱うのは既定では LF(\n)だけ。CR LF も改行として扱いたいときは、パターンの先頭に (*ANYCRLF) を書く。

マッチした内容を調べる†

マッチしたら、RE_GETSTR・RE_GETPOS・RE_GETLEN で内容・位置(0 始まり)・長さを配列で取得できる。次に正規表現の関数を呼ぶと上書きされる。

_r = RE_SEARCH("abc123def", '(\d+)')
_s = RE_GETSTR()      // ("123", "123")
_p = RE_GETPOS()      // (3, 3)
_l = RE_GETLEN()      // (3, 3)
_n = RE_GREP("a1b22c333", '\d+')   // 3
_s = RE_GETSTR()                   // ("1", "22", "333")

戻り値を捨てると出力候補になる

RE_SEARCH(...) のように戻り値を使わずに書くと、その値が関数の出力候補に混ざる。結果を調べるだけのときは _r = RE_SEARCH(...) のように変数に代入する。

置換†

RE_REPLACE は、置換文字列をそのまま使う。RE_REPLACEEX の置換文字列では、次の書き方が使える。

書き方 意味
$0、$& マッチした部分全体
$1、$2、… 1 番目、2 番目、…のグループ。$10 以上は、その数のグループがあるときだけ 2 桁として読む
${1}、${name} 番号・名前で指定する(後ろに数字が続くときに便利)
$`、$' マッチした部分より前、後ろの文字列
$+ マッチしたグループのうち、番号が最も大きいもの
$_ 元の文字列全体
$$ $ という文字
\1〜\9 $1〜$9 と同じ
\\ \n \r \t \a \e \f \v バックスラッシュ、改行、復帰、タブなどの制御文字

マッチに参加しなかったグループは、空文字列になる。

RE_REPLACEEX("2024-05-17", '(\d+)-(\d+)-(\d+)', '$3/$2/$1')    // 17/05/2024
RE_REPLACEEX("abc", '(?<first>a)', '${first}-')               // a-bc
RE_REPLACEEX("aaaa", 'a', 'b', 2)                             // bbaa(2 回まで置換)

サブルーチン呼び出し(Tc606-6以降)†

正規表現の中の部品を名前で呼び出せる。パターンの中に (?<date>...) のような定義がなくても、その名前を YAYA の変数や関数から探して、パターンとして取り込む。

呼び出しの書き方は、次のどれでもよい(意味は同じ)。

' を含む書き方は、シングルクォートの文字列の中では '' と 2 回続けて書く('^\g''name''$')か、ダブルクォートの文字列に書く。

_date = '\d{4}-\d{2}-\d{2}'
_r = RE_SEARCH("今日は2024-05-17です", '(?&date)')     // 1

名前の探し方†

パターンの中に定義のない名前 name は、次の順で探す。最初に見つかった、使える値が部品になる。

  1. ローカル変数 _name(名前が _ で始まるときは、その名前のローカル変数)
  2. グローバル変数 name
  3. 関数 name(引数なしで実行した戻り値)
// 部品を関数で用意する
Digits4 { '\d{4}' }
Digits2 { '\d{2}' }

IsDate
{
  RE_MATCH(reference[0], '(?&Digits4)-(?&Digits2)-(?&Digits2)')
}

部品を組み立てて、長いパターンを読みやすくできる。

_y = '\d{4}'
_m = '\d{2}'
_date = '(?&y)-(?&m)-(?&m)'
_r = RE_MATCH("2024-05-17", '(?&date)')     // 1

再帰を使えば、入れ子の構造にもマッチする。次は、かっこの対応が取れている文字列にマッチする例。

_paren = '\((?:[^()]++|(?&paren))*\)'
_r = RE_MATCH("(a(b)c)", '(?&paren)')     // 1

部品の中のグループ

部品の中に書いた (...) は、呼び出しから戻ると結果から消える。RE_GETSTR などで取り出せるのは、呼び出し元のパターンのグループだけ。また、部品の中では、呼び出し元のパターンの (?i) などのオプションは引き継がれない。RE_OPTION で設定したオプションから始まる。

エラー†

状況 結果
パターンの構文が不正、サブルーチンの名前を解決できない 警告 W0016。マッチしなかったものとして扱う(GETLASTERROR は 16)
繰り返しが入れ子になっているなどでバックトラックの回数が上限を超えた、メモリが足りない 警告 W0017。マッチしなかったものとして扱う(GETLASTERROR は 17)

(a+)+$ のようにバックトラックが指数的に増えるパターンは、長い文字列に当てると上限に達する。上限に達したときは、W0017 でマッチしなかったことになる。