MikoScript3 言語仕様

 構造体と構造化リスト

 「構造体」とは、あるまとまったデータ構造(或いはデータ構成)を表わすものです。
本言語では、構造体は、「複合箱」になるので、その複合箱の構造が、その構造体の構造
に対応します。そのため、構造体の設定は、構造体の雛型を定義するのではなく、構造体
を構成する実体、つまり、構造体の複合箱の中身を、実行文によって、構成していくこと
になります。かくて、構造体の構造は、プログラムの実行時に動的に自由に変化できます。
ちなみに、コンパイラ言語等では、通常、構造体の構造は、プログラムのコンパイル時に
確定されてしまい、実行時には変化できません。
 「構造化リスト」とは、HTML や XML などの構造化データ、あるいは、LISP 言語等の
リストのようなデータを、簡便な表記で、設定できるようにした構造体です。

●構造体の設定
 構造体の複合箱の中にある箱を、その構造体の「メンバー」と言います。つまり、構造
体は、メンバーの集まりで構成されます。構造体の設定では、そのメンバーを決めること
が主体になります。

 構造体は、通常、「構造体設定文」の実行によって、設定します。この構文の書式は、
以下のようになります。
    構造体名 ::=
    {
        メンバー設定部
    }
 ここで、「構造体名」には、設定対象の構造体の名前を規定します。この名前は、その
構造体の複合箱を代表する最外の箱の名前です。これは、単一の識別名だけでなく、一般
に「箱の経路」を規定できます。この箱の経路の基点は、スコープ規定演算子によって、
規定できます。それは通常の箱名の場合と同じです。構造体名に連想名を含む場合、その
インデックスには変数が使えます。

 ::= という記号が、「構造体名」の直後にあります。これは、一見、演算子のようです
が、あくまで構文構成上の記号で、文法的には、演算子ではありません。本構文は、この
ような記号を使わないで、struct という予約語を使って、C 言語風の構文にすることも
可能でしたが、諸般の混同を避けるために、敢えてこのような書式にしています。

 「メンバー設定部」は、::= 記号に続く波括弧 { } で囲われたブロック内にあります。
ここには、その構造体を構成するメンバーを設定するための一連の実行文を記述します。
「メンバー設定部」のブロック内では、メンバースコープが、「構造体名」で規定された
箱内直結のスコープに変わります。そのため、箱名の先頭に「.」のスコープ規定演算子
を付ければ、そのクラスのメンバーになります。箱名の先頭にスコープ規定演算子が何も
付いていなければ、それは、ブロックの内外で共通のデフォールトスコープになるので、
注意が必要です。なお、ブロックの内側と外側で変わるのは、メンバースコープだけで、
それ以外は何も変わりません。

 「構造体設定文」は、その末尾(波括弧のブロックの後)に、セミコロンは不要です。
但し、付けても、文法エラーにはなりません。

 以下に、構造体を設定する例を示します。
    S ::=
    {
        .A = 1;
        .B = 2;
    }
この実行によって、デフォールトスコープ(この場合、現実行関数のローカルスコープ)
内に、S という名前の構造体が生成され、そのメンバーに、箱 A と箱 B が登録されます。
これらのメンバーの初期値は、それぞれ、整数値 1, 2 に設定されています。このように、
構造体のメンバーは、代入によって生成します。その際、初期値が設定できます。なお、
代入以外でも、構造体のメンバーが生成できますが、これについては、次節で説明します。

 上例の構造体 S と同じ構造の複合箱は、「構造体設定文」を使わずに、以下のように、
生成することもできます。
    S.A = 1;
    S.B = 2;
しかしながら、「構造体設定文」を使わずに生成した複合箱には、構造体としての属性が
設定されません。そのため、次節で説明する各種の入出力に使えません。また、構造体は、
次章で説明する「クラス」の機能を担えますが、構造体の属性が設定されていない複合箱
には、その機能はありません。複合箱を構造体として構築するには通常、「構造体設定文」
を実行する必要があります。

 「構造体設定文」のメンバーは、既に設定されている構造体を使って、設定することが
できます。例えば、上例の構造体 S を使って、新規の構造体 T を設定するには、
    T ::=
    {
        .X = S;
        .Y = 0;
    }
とします。この実行で、デフォールトスコープ内に、T という名前の構造体が生成され、
そのメンバーとして、箱 X と箱 Y が登録されます。箱 X には、構造体 S のコピーが、
代入されているので、T.X.A には、整数値 1 が入っていて、T.X.B には、整数値 2 が
入っています。

 「構造体設定文」は、入れ子にできます。つまり、「構造体設定文」の中で、さらに、
「構造体設定文」を記述できます。以下に、この例を示します。
    R ::=
    {
        .P ::=
        {
            .A = 1;
            .B = 2;
        }
        .Q = 0;
    }
これを実行すると、デフォールトスコープ内に、構造体 R が生成され、そのメンバーが、
箱 P と箱 Q になります。箱 P は、構造体で、そのメンバーが、箱 A と箱 B です。

 「メンバー設定部」には、任意の実行文が記述できますが、ラベルは設定できません。
また、ここには、関数定義も記述できません。以下に、「メンバー設定部」に、for文を
使った例を示します。
    ::G ::=
    {
        for( i = 0 ; i < 10 ; i++ )
            .A[i] = 0;
    }
これを実行すると、グローバルスコープ内に、構造体 G が生成され、そのメンバーには、
連想配列 A[0], A[1] ... A[9] が、登録されます。なお、ここで、使用している変数 i 
には、スコープ規定演算子が付いていないので、変数 i は、デフォールトスコープ内の
変数になります。決して、構造体のメンバーではありません。

 今までの例では、「構造体設定文」の実行時点で、「構造体名」の対象となる箱が存在
していませんでした。その場合、既に述べた通り、その構造体箱は、新規に生成されます。
「構造体設定文」の実行時点で、「構造体名」の対象となる箱が存在している場合、事情
が若干異なります。まず、「構造体名」の対象の箱が「箱を入れる箱」でない場合、その
箱は、無条件に、空の「箱を入れる箱」に変更されてから、新規生成された場合と同様の
構造体設定が行なわれます。一方、その対象の箱が「箱を入れる箱」の場合、その箱には
無条件に構造体としての属性が設定され、構造体のメンバー設定は、現状への追加になり
ます。例えば、前述の構造体 S が、前述の通り設定されている状態で、
    S ::=
    {
        .C = 3;
        .D = 4;
    }
を実行すると、構造体 S のメンバーに、箱 C と箱 D が追加されます。もともとあった
箱 A と箱 B は、そのまま存続しています。

 構造体内の各メンバーは、生成された順に登録されていきます。これは、構造体の箱に
限ったことではなく、一般の「箱を入れる箱」の場合でも同様です。この原則によって、
例えば、上述の構造体 S 内の各メンバーの登録順は、箱 A, B, C, D となります。この
登録順は、次節で説明する構造体のバイナリー構成を扱う時に、考慮する必要があります。

●入出力時のバイナリー構成
 構造体内の各メンバーには、「入出力形式」を設定できます。これによって、構造体内
のデータを、ファイルやメモリーバッファに入出力する際のバイナリー構成(バイト構成)
を、厳密に規定できます。そうすれば、構造体内の各メンバーを逐一入出力しなくても、
構造体内の全データをまとめて、希望のバイナリー構成で、入出力することができます。

 「入出力形式」の設定は、システム組み込みの後置型(リレー型)関数で行ないます。
これに関しては、「入出力形式」の章で詳しく説明します。ここでは、これを使った例を
示します。

 まず、簡単な例として、画面上の点の位置( X, Y ) を表わす構造体を示します。
    POINT ::=
    {
        .X  'LONG;      // 点の X 座標
        .Y  'LONG;      // 点の Y 座標
    }
ここで、'LONG は、対象の変数の入出力形式を「符号有 32-bit 整数」に設定します。
対象の変数がなければ、新規に生成します。これによって、構造体 POINT 内のメンバー 
X, Y が生成され、その入出力形式が「符号有 32-bit 整数」になります。

この構造体 POINT を、ファイルやメモリーバッファへ出力する際には、
    .X が 4 バイト( 32-bit )
    .Y が 4 バイト( 32-bit )
の順で、合計 8 バイト( 64-bit )のバイナリーデータが書き込まれます。
また、入力する際には、この順で、このサイズのバイナリーデータが取り込まれます。

 次に、Windows プログラミングではお馴染みの MSG 構造体を例に使います。この構造
体は、C++ 言語で、以下のように表わされます。
    struct MSG      // スレッドのメッセージ キューからのメッセージ情報
    {
        HWND    hwnd;       // メッセージを受け取るウィンドウのハンドル
        UINT    message;    // メッセージ番号
        WPARAM  wParam;     // 付加情報(1)
        LPARAM  lParam;     // 付加情報(2)
        DWORD   time;       // メッセージがポストされた時間
        POINT   pt;         // カーソル位置(スクリーン座標系)
    };
ここで、HWND, UINT, WPARAM, DWORD は、どれも「符号無 32-bit 整数」で、LPARAM は、
「符号有 32-bit 整数」です。また、 POINT は、上例と同じ構成の構造体です。
この MSG 構造体に相当する本言語の構造体の表記は、以下のようになります。
    MSG ::=
    {
        .hwnd       'ULONG;
        .message    'ULONG;
        .wParam     'ULONG;
        .lParam     'LONG;
        .time       'ULONG;
        .pt         = POINT;
    }
ここで、'ULONG は、'LONG と殆ど同じですが、入出力形式が「符号無 32-bit 整数」に
なります。また、.pt は、上例の構造体 POINT のコピーが代入されています。

この構造体のデータを、メモリーバッファへ書き出せば、C++ 言語での構造体のデータと
同じバイト構成になります。また、その構成のデータが格納されているメモリーバッファ
から、この構造体へ読み込めば、各メンバーに、対応する値が設定されます。この機能は、
Windows のAPIを使ってプログラミングする時等に、必要になります。

 次に、ファイルのレコードの入出力用に構造体を使う例を示します。ここでは、以下の
ような構成のレコード( 48 バイト )があるとします。

個人情報
氏名生年月日性別血液型電話番号
文字10桁文字10桁数字4桁数字2桁数字2 桁文字2桁文字2桁文字16桁
このレコードの入出力を対象にした構造体は、以下のような設定になります。なお、今回 は、識別名に日本語を使っています。
    個人情報 ::= {
        .氏名 ::= {
            .姓         'C(10);
            .名         'C(10);
        }
        .生年月日 ::= {
            .年         'I(4);
            .月         'I(2);
            .日         'I(2);
        }
        .性別           'C(2);
        .血液型         'C(2);
        .電話番号       'C(16);
    }
ここで、'C(n) は、対象の変数の入出力形式を「文字 n 桁(半角単位)」に設定します。
'I(n) は、対象の変数の入出力形式を「数字 n 桁」に設定します。両者とも、対象変数
がなければ、新規に生成します。

この構造体にデータを設定するには、
    個人情報.氏名.姓 = "山田";
    個人情報.氏名.名 = "太郎";
    個人情報.生年月日.年 = 1964;
    ・・・・・
のように、各メンバーごとに逐一、その値を代入することもできますが、以下のように、
まとめて設定できます。これについては、次節で説明します。
    個人情報 =
      { { "山田", "太郎" }, { 1964, 5, 3 }, "男", "O", "03-4567-8888" };
この「個人情報」のデータを、上記のレコード構成で、ファイルに格納するには、例えば、
以下のようにします。なお、格納先のファイル名は「Persons.dat」としています。また、
ファイル関連の操作は、「ファイル操作」の章で詳しく説明しています。
    file = ::File.Open( "Persons.dat", "out" );
    file.Write( 個人情報 );
ここで、「個人情報」の各メンバーには、入出力形式が設定されているので、
    file.Write( 個人情報 );
とするだけで、構造体内の全データをまとめて、希望のバイナリー構成で、書き込むこと
ができます。続いて、もう3件、同様に格納して、ファイルを閉じます。
    個人情報 = { { "春川","花子" }, { 1978, 3,21 }, "女", "A", "012-345-6789" };
    file.Write( 個人情報 );
    個人情報 = { { "花岡","実太" }, { 1950,10,10 }, "男", "B", "098-765-4321" };
    file.Write( 個人情報 );
    個人情報 = { { "橘","カオル" }, { 1999, 9, 9 }, "女", "AB","077-888-9999" };
    file.Write( 個人情報 );
    file.Close();
この実行によって、4件の「個人情報」レコードが格納されたファイル「Persons.dat」
が作成されます。

今度は、このファイルを読み出して、各「個人情報」の内容をプリントするプログラムを
書いてみます。
    個人情報 ::= {
        〜〜 ここは前記と同じ内容になる 〜〜
    }
    size = 個人情報 'size;
    file = ::File.Open( "Persons.dat", "in" );
    while( file.Read( 個人情報 ) == size )
    {
        do 個人情報 'enum with v {  print v, -;  };
        print;
    }
    file.Close();
ここで、ファイル内の1レコードの読み出しは、単に、
    file.Read( 個人情報 )
としているだけです。これで、「個人情報」の各メンバーには、レコード構成内の各対応
するデータ部の値が設定されます。ちなみに、この式の値は、このファイル操作によって
読み出されたデータのバイト数になります。その値が、「個人情報」の構造体のサイズと
等しければ、正常に読み出せたということになります。ファイルの終端では、等しくなら
ないので、while ループから抜け出します。

なお、'size は、対象の箱( この場合「個人情報」の構造体の箱 )の総データサイズを
返すシステム組み込みのリレー型関数です。従って、個人情報 'size は 48 になります。
また、'enum は、対象の箱の階層構造を深さ優先で辿っていって、各要素を順に指定関数
(この場合、do-with 式のブロック関数)に渡すシステム組み込みのリレー型関数です。
ここでのブロック関数は、渡された要素の内容を、コンマ付き無改行でプリントするだけ
です。このプログラムを実行すると、結果は、以下の通りです。
    山田, 太郎, 1964, 5, 3, 男, O, 03-4567-8888, 
    春川, 花子, 1978, 3, 21, 女, A, 012-345-6789, 
    花岡, 実太, 1950, 10, 10, 男, B, 098-765-4321, 
    橘, カオル, 1999, 9, 9, 女, AB, 077-888-9999, 
 また、同ファイル( Persons.dat )から、ある条件に合致する個人情報を抽出して、
プリントするプログラムは、以下のようになります。なお、ここでは、抽出条件として、
3月生まれの女性にしています。また、ファイルオープン時のエラーチェックも付け加え
ています。
    個人情報 ::= {
        〜〜 ここは前記と同じ内容になる 〜〜
    }
    size = 個人情報 'size;
    if(( file = ::File.Open( "Persons.dat", "in" )) == null )
    {
        print "個人情報のファイルがありません!";
        return;
    }
    while( file.Read( 個人情報 ) == size )
    {
        if( 個人情報.生年月日.月 == 3  &&
            個人情報.性別 == "女" )
        {
            do 個人情報 'enum with v {  print v, -;  };
            print;
        }
    }
    file.Close();
    return;
このプログラムの実行結果は、以下の通りです。
    春川, 花子, 1978, 3, 21, 女, A, 012-345-6789, 
 最後に、注意事項を述べておきます。ファイルやメモリーバッファから入力する際には、
必ず、各箱に「入出力形式」が設定されている必要があります。もし、設定されていなけ
れば、実行時にエラーになります。一方、出力する際には、必ずしも、「入出力形式」が
設定されていなくても構いませんが、その場合、その箱の内容に応じて、デフォールトの
入出力形式が適用されます。なお、入力の際にデフォールトの入出力形式を設けていない
のは、それが無意味か不適切になる場合が多いからです。

 構造体の各メンバーが、ファイルやメモリーバッファに、入出力される順番は、基本的
に、登録順で、階層深化が優先されます。つまり、まず、最外箱内直下の各箱が登録順に
対象になります。その対象になった際、それが「値を入れる箱」であれば、それに対する
入出力が行なわれます。一方、それが「箱を入れる箱」であれば、その箱内直下の各箱が
登録順に対象になります。その対象になった際は、先程と同様です。各「箱を入れる箱」
内において、最後の対象の箱が終ると、その「箱を入れる箱」の次の箱に進みます。これ
が、対象の箱がなくなるまで繰り返されます。
 構造体の各メンバーが一旦決まった後に、追加/削除を行なって、入出力する場合は、
この順番を配慮しておく必要があります。

●構造体へのデータ設定
 構造体へまとめてデータを設定するのは、連想配列へ初期化データ列を設定するのと、
基本的に同じです。そのため、以下の説明では、連想配列への初期化データ列の設定が、
既に理解されていることを前提にしています。

 まず、階層がフラットな場合ですが、例えば、
    S ::=
    {
        .A  'LONG;
        .B  'LONG;
        .C  'LONG;
    }
の構造体に対して、
    S = { 1, 2, 3 };
とすると、S 内のメンバー A, B, C は、それぞれ、1, 2, 3 に設定されます。

 波括弧 { } のブロック内の各要素は、省略できます。その場合、その省略要素に対応
するメンバーは、設定されません。また、当ブロック内の各要素の個数の方が、構造体の
メンバーの個数よりも少ない場合、不足分の要素に対応するメンバーは、設定されません。
例えば、上記の構造体 S に対して、
    S = { , -2 };
とすると、S 内のメンバー B は、-2 に設定されますが、A, C は、設定されないので、
その値は、それぞれ、1, 3 のままです。

 一方、波括弧 { } のブロック内の要素の個数の方が、構造体のメンバーの個数よりも
多い場合、過剰分の要素に対応するメンバーは、新規に登録されます。その時の登録名は、
そのメンバーの登録順に対応する連想名になります。この登録順は 0 から 1 ずつ増えて
いく整数です。これを i とすると、それに対応する連想名は、[i] になります。例えば、
上記の構造体 S に対して、
    S = { 10, 11, 12, 13, 14 };
とすると、S 内のメンバー A, B, C は、それぞれ、10, 11, 12 に設定されますが、過剰
要素があるので、その分に対応するメンバー [3], [4] が新規に追加されて、それぞれの
値が、13, 14 に設定されます。つまり、S[3], S[4] の値は、それぞれ、13, 14 です。

 次に、構造体の中にさらに構造体がある場合のデータ設定について説明します。例えば、
    T ::=
    {
        .A  'LONG;
        .B  'LONG;
        .C ::=
        {
            .X  'LONG;
            .Y  'LONG;
        }
    }
の構造体に対して、
    T = { 1, 2, { 3, 4 } };
とすると、T 内のメンバー A, B, C は、それぞれ、1, 2, { 3, 4 } に対応しているので、
    T.A = 1;
    T.B = 2;
    T.C = { 3, 4 };
として、設定されます。ここで、T.C は、構造体でその中の構造はフラットなので、その
中のメンバーは、上述の通り設定されます。つまり、T.C.X, T.C,Y は、それぞれ、3, 4 
に設定されます。

 このように、構造体の中にさらに構造体がある場合には、波括弧 { } のブロックを、
入れ子にすることによって、設定できます。この場合、入れ子になったブロック内での各
要素の省略や過不足に関しては、上述と同様です。しかしながら、構造体でないメンバー
にブロックが対応していたり、逆に、構造体であるメンバーにブロックが対応していない
と、構造体の構造が変わってしまうので、注意が必要です。例えば、
    T = { 1, { 2, 3 }, 4 };
とすると、T 内のメンバー A, B, C は、
    T.A = 1;
    T.B = { 2, 3 };
    T.C = 4;
として、設定されてしまいます。この場合、T.B の箱の中に [0] と [1] の連想名の新規
メンバーが登録され、それぞれ、 2 と 3 の値に設定されます。つまり、T.B[0], T.B[1] 
は、それぞれ、2, 3 になります。一方、T.C は、元は構造体でしたが、この設定よって、
整数値 4 を格納する箱に変わってしまいます。その際、T.C.X と T.C,Y は、破棄されて
います。なお、この場合の T.B は、構造体としての属性を持っていません。


●任意関数のメンバー関数としての使用
 本言語では、任意の関数を、構造体のメンバー関数として使用できます。例えば、以下
のような関数があったとします。
    function ShowMember()
    {
        print .A, .B ;
    }
この関数は、メンバースコープ内にある変数 A と変数 B の内容をプリントします。一方、
以下のような構造体があったとします。
    S ::=
    {
        .A = 1;
        .B = 2;
    }
さて、この構造体のメンバー関数として、上の関数を適用するには、以下のようにします。
    S.[ ShowMember ]();
これを実行すると、以下の通りプリントされます。
    1, 2

また、以下のような別の構造体があったとします。
    T ::=
    {
        .A = -1;
        .B = -2;
        .C = -3;
    }
これは、前の構造体 S とは、特に依存関係はありませんが、この構造体 T に対しても、
同様に、同関数を適用できます。
    T.[ ShowMember ]();
これを実行すると、以下の通りプリントされます。
    -1, -2

 次に、以下のような関数があったとします。
    function MultMember( n )
    {
        .A *= n;
        .B *= n;
    }
これは、メンバースコープ内にある変数 A と変数 B の値を n 倍する関数です。これを、
先程の構造体 S と構造体 T に適用して、各メンバーの値ををプリントする例を、以下に
示します。
    S.[ MultMember ]( 10 );
    T.[ MultMember ]( 10 );

    S.[ ShowMember ]();
    T.[ ShowMember ]();
これを実行すると、以下の通りプリントされます。
    10, 20
    -10, -20

 関数への参照を使っても、構造体のメンバー関数として、適用できます。例えば、先程
の続きで、
    R := MultMember;
    S.[ R ]( 10 );
    T.[ R ]( 10 );

    R := ShowMember;
    S.[ R ]();
    T.[ R ]();
を実行すると、以下の通りプリントされます。
    100, 200
    -100, -200

 メンバー関数としてコールされた関数の中でも、任意の関数を同メンバーの関数として
コールできます。例えば、
    function MultAndShow( n )
    {
        .[ MultMember ]( n );
        .[ ShowMember ]();
    }
この関数は、実行時点でのメンバースコープに対するメンバー関数として、つまり、同じ
構造体に対するメンバー関数として、関数 MultMember と ShowMember をコールします。
この関数 MultAndShow を、先程の続きの構造体 S と構造体 T のメンバー関数として、
以下のように、コールします。
    S.[ MultAndShow ]( 100 );
    T.[ MultAndShow ]( 100 );
これを実行すると、以下の通りプリントされます。
    10000, 20000
    -10000, -20000


●構造化リスト
 HTML や XML などの構造化データ、あるいは、LISP 言語等のリストのようなデータは、
今までに説明した通常の構造体でも、充分扱えます。ただ、そのデータ構造の表記の段階
において、不便さと不自然さがあります。例えば、次のような HTML のテキストがあると
します。
  <html>
     <head>
       <title>構造化データ</title>
     </head>
     <body>
       <h1>表記言語</h1>
       <table>
         <tr><td>1</td><td>マークアップ言語</td></tr>
         <tr><td>2</td><td>LISP 言語</td></tr>
         <tr><td>3</td><td>MikoScript 言語</td></tr>
       </table>
     </body>
  </html>
通常の構造体で、これを表わすと以下のようになります。なお、1つのスコープ内で同じ
名前の箱は2コ以上登録できないので、td と tr は通し番号を付加して各々別名にして
います。また、数値や文字列は、とにかく箱に格納しないと保持されないので、ここでは、
._ という名前の箱に格納しています。
  html ::= {
    .head ::= {
      .title ::= { ._ = "構造化データ"; }
    }
    .body ::= {
      .h1 ::= { ._ = "表記言語"; }
      .table ::= {
        .tr_1 ::= { .td_1 ::= { ._ = 1; }; .td_2 ::= { ._ = "マークアップ言語"; } }
        .tr_2 ::= { .td_1 ::= { ._ = 2; }; .td_2 ::= { ._ = "LISP 言語";        } }
        .tr_3 ::= { .td_1 ::= { ._ = 3; }; .td_2 ::= { ._ = "MikoScript 言語";  } }
      }
    }
  }
「構造化リスト」では、これをもっと簡便で自然に表記できます。それを以下に示します。
  html {
    head {
      title { "構造化データ" }
    },
    body {
      h1 { "表記言語" },
      table {
        tr {  td { 1 },  td { "マークアップ言語" } },
        tr {  td { 2 },  td { "LISP 言語"        } },
        tr {  td { 3 },  td { "MikoScript 言語"  } },
      }
    }
  }
さて、このように表記したデータが、実際にどのような構造になっているかですが、それを見る
ために、次のスクリプトを実行してみます。
  do html'enum with p { print "  "'rep( p'level ) : ( p'cbox? ? p'name : p ); };
すると、以下のようにプリントされます。なお、ここで、tr と td に番号が付いている
ところがありますが、これに関しては、後で説明します。
    head
      title
        構造化データ
    body
      h1
        表記言語
      table
        tr
          td
            1
          td$1
            マークアップ言語
        tr$1
          td
            2
          td$1
            LISP 言語
        tr$2
          td
            3
          td$1
            MikoScript 言語

 さて、構造化リストは、「構造化リスト文」の実行によって設定します。この構文の
書式は、以下のようになります。
    構造箱  {  項目指定  }
ここで、「構造箱」は、その構造化リストを格納する箱を指定します。これは、単一の
識別名だけでなく、「箱の経路」や「箱を特定する式」が規定できます。この「箱の経路」
の基点は、スコープ規定演算子で規定できます。例えば、グローバル・スコープなら、
    ::箱名 {  項目指定  }
のようにできます。また、「箱を特定する式」には参照や関数などを使っても構いません。
これらは通常の箱名の場合と同じです。

「項目指定」には、0個以上の「項目」を指定します。「項目」が複数個ある場合は、
各項目を「,(コンマ)」で区切ります。つまり、
    構造箱  {  項目, 項目, 項目, ・・・  }
のようになります。これで、その構造箱の中に、これらの各項目の箱が設定されます。

この「項目」には、次の (1) 〜 (8) のどれかを指定します。
   定数           ・・・・・ (1)
   ( 式 )          ・・・・・ (2)
   {  項目指定  }      ・・・・・ (3)
   識別名          ・・・・・ (4)
   識別名 = 式        ・・・・・ (5)
   識別名 := 式       ・・・・・ (6)
   識別名 <- 式       ・・・・・ (7)
   識別名 {  項目指定  }   ・・・・・ (8)

(1) では、その項目の箱に、その定数値(整数、実数、文字列、null)が設定されます。
(2) では、その項目の箱に、その式の評価値(または参照)が設定されます。
(3) では、その項目の箱に、そのサブ構造化リストが設定されます。
(4) では、その識別名の項目箱が、「空箱」に設定されます。
(5) では、その識別名の項目箱に、その式に対する複製代入が行なわれます。
(6) では、その識別名の項目箱に、その式に対する参照代入が行なわれます。
(7) では、その識別名の項目箱に、その式に対する移動代入が行なわれます。
(8) では、その識別名の項目箱に、その「項目指定」のサブ構造化リストが設定されます。

(1) 〜 (3) の場合、その項目の箱は、「$i」( i = 0,1,2,... ) という特殊な名前に
  なります。つまり、(1) 〜 (3) の項目の箱は、最初は、$0 という名前になり、
  次は $1、それ以降は、$2, $3, $4 ... となります。

(2) の場合、丸括弧は省略できません。その項目の箱へ代入されるのは、
   ・式の評価値が、整数値、実数値、定文字列、null の場合、その値
   ・式が、複合箱、関数箱(匿名関数定義も含む)等の場合、その箱への参照
  となります。
   また、式が関数コールで、その返値が多値の場合、その個数分の箱へ、各値が
  代入されます。例えば、$2 の箱から3値を代入する場合、$2, $3, $4 の3箱へ
  代入されます。

(4) 〜 (8) の場合、同じ「識別名」の項目が既に設定されていると、今回新たに追加
  される項目の箱名は「その識別名 $ 通番」に変更されます。例えば、abc という
  名前の項目箱が既にある時に、同じ「識別名」の項目箱が追加されると、その項目
  の箱名は、abc$1 になります。さらに同名の項目箱が追加されると、その箱名は、
  abc$2 になります。以降同様に、abc$3, abc$4, ... となります。
   ちなみに、先ほどの tr と td に番号が付いていたのは、このためです。

(2),(5),(6),(7) の「式」内の箱名のスコープは、次のようになります。
  ・スコープ規定演算子のない箱名は、デフォールトスコープ、つまり、現実行中の
   関数のローカルスコープになります。ちなみにその構造箱内のローカルスコープ
   になるわけではありません。
  ・メンバースコープの箱名( .箱名 )の場合、その構造箱内のローカルスコープに
   なります。例えば、X1 と X2 という名前の項目が既に設定されていて、それらの
   和を今回の項目の値にするには、( .X1 + .X2 ) のようにします。また、$0, $1 
   等の特殊な名前の箱に対しても同様に、( .$0 - .$1 * 10 ) のようにします。
  ・それ以外のスコープ規定演算子の付く箱名は、通常通りです。

 次に、構造化リストの実用時の参考になりそうな例を示します。

≪ 例1 ≫ SVG (Scalable Vector Graphics) の表記に対応した例
    svg { { width="128mm", height="96mm", viewBox { 0, 0, 640, 480 } },
        rect { x=2, y=2, width=637, height=478, fill=0, stroke="green" },
        circle { cx=300, cy=100, r=160, fill="red" },
        text { x=320, y=180, font_family="Georgia", font_size=18,
            "This is a test to draw SVG." }
    }
    do svg'enum with p { print "  "'rep( p'level ) : p'name : " = " : p ; };
これを実行すると、以下のようにプリントされます。
    $0 = <structure: $0>
      width = 128mm
      height = 96mm
      viewBox = <structure: viewBox>
        $0 = 0
        $1 = 0
        $2 = 640
        $3 = 480
    rect = <structure: rect>
      x = 2
      y = 2
      width = 637
      height = 478
      fill = 0
      stroke = green
    circle = <structure: circle>
      cx = 300
      cy = 100
      r = 160
      fill = red
    text = <structure: text>
      x = 320
      y = 180
      font_family = Georgia
      font_size = 18
      $0 = This is a test to draw SVG.
≪ 例2 ≫ 識別名に日本語を使った例
    レシピ {
        料理 = "ゆでたまごのエコな作り方",
        調理時間 = "16分",
        材料 { "たまご", "3個" },
        材料 { "塩", "少々" },
        作り方 {
            "小鍋に水を入れる(深さが 1cm になる程度でよい)",
            "たまご3個と塩を少々入れる",
            "強火にかけて水が沸騰するまで待つ",
            "弱火にして、鍋にふたをしてから、5分間待つ",
            "火を消して、鍋にふたをしたまま、10分間待つ",
            "たまごを水で冷やして、出来上がり",
        },
        補足 = function( s )  { print s: "塩は特に入れなくても構いません!"; }
    }
    do レシピ'enum with p { print "  "'rep( p'level ) : p'name : " = " : p ; };
    レシピ.補足( "  ※ " );
これを実行すると、以下のようにプリントされます。
    料理 = ゆでたまごのエコな作り方
    調理時間 = 16分
    材料 = <structure: 材料>
      $0 = たまご
      $1 = 3個
    材料$1 = <structure: 材料$1>
      $0 = 塩
      $1 = 少々
    作り方 = <structure: 作り方>
      $0 = 小鍋に水を入れる(深さが 1cm になる程度でよい)
      $1 = たまご3個と塩を少々入れる
      $2 = 強火にかけて水が沸騰するまで待つ
      $3 = 弱火にして、鍋にふたをしてから、5分間待つ
      $4 = 火を消して、鍋にふたをしたまま、10分間待つ
      $5 = たまごを水で冷やして、出来上がり
  *3 = <function: *3>
  ※ 塩は特に入れなくても構いません!

 次に、「構造化リスト」内に、項目を追加する方法を説明します。例えば、
    Q { abc { 1 }, abc { 2 }, abc { 3 } };
のようにして、構造化リスト Q を生成したとします。このあと、
    Q { abc { 4 }, abc { xyz { 5 } }, abc { xyz { uvw{ 6 } } }, };
を実行すると、Q 内には、これらの項目が追加されます。さらに、
    Q.abc$0 { 11, 111 };
    Q.abc$1 { 22, 222 };
を実行すると、結局、構造化リスト Q 内は、以下のようになります。
    abc
      1
      11
      111
    abc$1
      2
      22
      222
    abc$2
      3
    abc$3
      4
    abc$4
      xyz
        5
    abc$5
      xyz
        uvw
          6

●同属名
 同じ名前の箱を、同じスコープ内に、複数個入れることは、原理的にできませんが、
上記の「構造化リスト」では、「識別名 $ 通番」によって、それに近いことができる
ようになっているということは、既に述べた通りです。

 「同属名」は、その「識別名 $ 通番」を若干拡張したもので、それ以外の用途にも
使えるようになっています。

 同属名の表記は、簡易的には、次のようになります。
    A$i
ここで、A は、任意の識別名、i は、0以上の整数定数です。例えば、
   name$1  hobby$2  robot$8
などが、同属名の簡易表記です。

 同属名のもう少し汎用的な表記は、次のようになります。
    A$(i)
ここで、A は、任意の識別名、i は、評価値が、整数、実数、あるいは、文字列になる
任意の式です。例えば、
  title$(2)  size$( N++ )  home( "new" )  重量$( x + y )  色彩$( 4.5 )
などは、同属名の表記です。

 なお、上記のどちらの表記でも、i が 0(整数値)の時は、元の識別名そのものと同じ
になります。例えば、X$0 と X$(0) は、X と同じです。

 同属名の表記の $ の左側の識別名を連想配列名にすることはできません。例えば、
    X[1]$2  Y[i]$(n)
などは、文法エラーになります。

 $ の直後の丸括弧内の数値が同じでも、データ型が異なれば別の箱になります。例えば、
    Z$( 1 )  Z$( 1.0 )  Z$( "1" )
は、それぞれ、データ型が、整数、実数、文字列なので、別の箱です。

 同属名と連想配列は、似ているところもありますが、根本的な違いもあります。以下に
両者の比較表を示します。
同属名連想配列形 態
 A$1  不可  簡易表記
 A$(i)  A[i]  1次元インデックスでのアクセス
 不可  A[i][j]  多次元インデックスでのアクセス
  1   2  要素の深さ( 'level で確認可 )
 A'qs  A'data  インデックスなしでの要素の追加
 同属名が、実際に内部的にはどのような箱名になっているのかは、リレー型関数 'name を使えば分かります。例えば、
        P$1 = 0;
        Q$5'new!;
        i = 123;
        R$( i ) = 1.23;
        S$( "abc" ) = null;
        print P$1'name, Q$5'name, R$(i)'name, S$("abc")'name;
を、実行すると、
    P$1, Q$5, R$123, S$abc
と、プリントされます。

 同属名関連のリレー型関数があります。これらを次に説明します。

'qs 関数
書式: <対象箱>'qs
説明: <対象箱> の同属名の空箱を新規に生成します。その際に、
    <対象箱> が不在なら、<対象箱> 自身を生成します。
    <対象箱> が既存なら、その識別値が当スコープ内で最小の正値となる同属名
    の箱を生成します。( 同属名の識別値に関しては、'qval を参照。)
    なお、<対象箱>には、連想配列名は使えません。
返値: 新規生成した同属名の箱の参照を返します。
    当箱を新規生成できない時は、null を返します。
用例: 次のスクリプトを実行すると、「<null>, 3, xyz」とプリントされます。
          A'qs;          // 空箱の A を新規生成
          A'qs = 3;      // 空箱の A$1 を新規生成して、3 を代入
          A'qs = "xyz";  // 空箱の A$2 を新規生成して、"xyz" を代入
          print A, A$1, A$2;
'qtop 関数 書式: <対象箱>'qtop 説明: <対象箱> の同属名の箱群のうち、先頭の箱の参照を返します。 補説: <対象箱> の同属名とは、その箱名自身、または、その箱名の後に $ と任意の     文字列が付加された箱名のことです。例えば、<対象箱> が A の場合、A$...     に一致する箱名です。(ここで、... は、任意の文字列)     なお、<対象箱> は既存でも不在でも構いませんが、連想配列名は使えません。     先頭の箱というのは、最初に生成された(生成時期が最も古い)箱のことです。 返値: 当箱の参照を返します。     当箱が見つからない(実在しない)時は、null を返します。 用例: 次のスクリプトを実行すると、「B = 0」,「B$1 = 1」,「B$2 = 2」の3行が     プリントされます。
          ( B'qs, B'qs, B'qs ) = ( 0, 1, 2 );
          for( p := B'qtop ; p'ref? ; p := p'qnxt )
              print p'name : " = " : p;
'qnxt 関数 書式: <対象箱>'qnxt 説明: <対象箱> の次の同属名の箱の参照を返します。     当箱がない時は、null を返します。     なお、<対象箱> は、実在する箱でないといけません(連想配列名は不可)。 用例: 次のスクリプトを実行すると、「C = 0」,「C$1 = -1」,「C$2 = -2」の3行が     プリントされます。
          ( X.C, X.C$1, X.C$2 ) = ( 0, -1, -2 );
          for( p := X.C'qtop ; p'ref? ; p := p'qnxt )
              print p'name : " = " : p;
'qval 関数 書式: <対象箱>'qval 説明: <対象箱> の同属名の識別値を返します。     同属名の識別値というのは、同属名の一般的な表記「A$(i)」(上記参照)での     i の値になります。これには、整数、実数、文字列の場合があります。     なお、<対象箱> がない時は、null を返します。     <対象箱> には、連想配列名は使えません。 用例: 次のスクリプトを実行すると、「D, 10, 0」,「D$1, 11, 1」,「D$akb, 12, akb」     の3行がプリントされます。
          D = 10;
          D$5 = 11;
          D$( "akb" ) = 12;
          for( p := D'qtop ; p'ref? ; p := p'qnxt )
              print p'name, p, p'qval;