この初版はLua 5.0向けに記述されました。後続バージョンでも大部分は関連性がありますが、いくつかの違いがあります。
第4版はLua 5.3を対象としており、Amazonおよびその他の書店で購入できます。
本書を購入することで、Luaプロジェクトの支援にもなります。


25.1 – テーブル操作

このような姿勢を採用してみましょう。今度は、ウィンドウの背景色も設定したいとします。最終的な色の指定は3つの数値で構成され、各数値はRGBのカラーコンポーネントであると仮定します。通常、Cでは、これらの数値は`[0,255]`のような範囲の整数です。Luaでは、すべての数値が実数であるため、より自然な範囲`[0,1]`を使用できます。

ここで単純なアプローチは、ユーザーに各コンポーネントを異なるグローバル変数に設定するように求めることです。

    -- configuration file for program `pp'
    width = 200
    height = 300
    background_red = 0.30
    background_green = 0.10
    background_blue = 0
このアプローチには2つの欠点があります。冗長すぎる(実際のプログラムでは、ウィンドウの背景、ウィンドウのフォアグラウンド、メニューの背景など、数十種類の異なる色が必要になる場合があります)。また、共通の色を事前に定義する方法がないため、後でユーザーが`background = WHITE`のように書くことができません。これらの欠点を回避するために、テーブルを使用して色を表します。
    background = {r=0.30, g=0.10, b=0}
テーブルを使用することで、スクリプトにより多くの構造を与えることができます。これで、ユーザー(またはアプリケーション)が後で設定ファイルで使用するために色を簡単に事前に定義できるようになりました。
    BLUE = {r=0, g=0, b=1}
    ...
    background = BLUE
これらの値をCで取得するには、次のようにします。
    lua_getglobal(L, "background");
    if (!lua_istable(L, -1))
      error(L, "`background' is not a valid color table");
    
    red = getfield("r");
    green = getfield("g");
    blue = getfield("b");
いつものように、まずグローバル変数`background`の値を取得し、それがテーブルであることを確認します。次に、`getfield`を使用して各カラーコンポーネントを取得します。この関数はAPIの一部ではありません。次のように定義する必要があります。
    #define MAX_COLOR       255
    
    /* assume that table is on the stack top */
    int getfield (const char *key) {
      int result;
      lua_pushstring(L, key);
      lua_gettable(L, -2);  /* get background[key] */
      if (!lua_isnumber(L, -1))
        error(L, "invalid component in background color");
      result = (int)lua_tonumber(L, -1) * MAX_COLOR;
      lua_pop(L, 1);  /* remove number */
      return result;
    }
再び、ポリモーフィズムの問題に直面します。`getfield`関数は、キーの種類、値の種類、エラー処理などが異なるため、潜在的に多くのバージョンがあります。Lua APIは単一の関数`lua_gettable`を提供します。スタック内のテーブルの位置を受け取り、キーをスタックからポップし、対応する値をプッシュします。私たちのプライベート`getfield`は、テーブルがスタックの一番上にあります。そのため、キー(`lua_pushstring`)をプッシュした後、テーブルはインデックス`-2`になります。戻値の前に、`getfield`は取得した値をスタックからポップして、スタックを呼び出し前と同じレベルにします。

例をもう少し拡張し、ユーザーにカラー名を導入します。ユーザーは引き続きカラテーブルを使用できますが、より一般的な色のために事前に定義された名前を使用することもできます。

    struct ColorTable {
      char *name;
      unsigned char red, green, blue;
    } colortable[] = {
      {"WHITE",   MAX_COLOR, MAX_COLOR, MAX_COLOR},
      {"RED",     MAX_COLOR,   0,   0},
      {"GREEN",     0, MAX_COLOR,   0},
      {"BLUE",      0,   0, MAX_COLOR},
      {"BLACK",     0, 0, 0},
      ...
      {NULL,        0, 0, 0}  /* sentinel */
    };

私たちのインプリメンテーションは、カラー名を持つグローバル変数を作成し、カラテーブルを使用してこれらの変数を初期化します。結果は、ユーザーがスクリプトに次の行を含めた場合と同じです。

    WHITE = {r=1, g=1, b=1}
    RED   = {r=1, g=0, b=0}
    ...
これらのユーザー定義の色との唯一の違いは、アプリケーションがユーザーのスクリプトを実行する前にCでこれらの色を定義することです。

テーブルフィールドを設定するには、補助関数`setfield`を定義します。インデックスとフィールドの値をスタックにプッシュし、`lua_settable`を呼び出します。

    /* assume that table is at the top */
    void setfield (const char *index, int value) {
      lua_pushstring(L, index);
      lua_pushnumber(L, (double)value/MAX_COLOR);
      lua_settable(L, -3);
    }
他のAPI関数と同様に、`lua_settable`は多くの異なる型で動作するため、スタックからすべてのオペランドを取得します。引数としてテーブルインデックスを受け取り、キーと値をポップします。`setfield`関数は、呼び出しの前にテーブルがスタックの一番上(インデックス`-1`)にあると仮定します。インデックスと値をプッシュした後、テーブルはインデックス`-3`になります。

`setcolor`関数は単一の色を定義します。テーブルを作成し、適切なフィールドを設定し、このテーブルを対応するグローバル変数に割り当てる必要があります。

    void setcolor (struct ColorTable *ct) {
      lua_newtable(L);               /* creates a table */
      setfield("r", ct->red);        /* table.r = ct->r */
      setfield("g", ct->green);      /* table.g = ct->g */
      setfield("b", ct->blue);       /* table.b = ct->b */
      lua_setglobal(L, ct->name);    /* `name' = table */
    }
`lua_newtable`関数は空のテーブルを作成し、スタックにプッシュします。`setfield`呼び出しはテーブルフィールドを設定します。最後に、`lua_setglobal`はテーブルをポップし、指定された名前のグローバルの値として設定します。

これらの関数を使用すると、次のループはアプリケーションのグローバル環境にすべての色を登録します。

    int i = 0;
    while (colortable[i].name != NULL)
      setcolor(&colortable[i++]);
アプリケーションは、ユーザーのスクリプトを実行する前にこのループを実行する必要があることを忘れないでください。

名前付きの色を実装するための別のオプションがあります。グローバル変数の代わりに、ユーザーは文字列でカラー名を指定し、`background = "BLUE"`のように設定できます。したがって、`background`はテーブルまたは文字列のいずれかになります。この実装では、アプリケーションはユーザーのスクリプトを実行する前に何もする必要がありません。代わりに、色を取得するためにより多くの作業が必要です。変数`background`の値を取得すると、値の型が文字列かどうかをテストしてから、カラーテーブルで文字列を検索する必要があります。

    lua_getglobal(L, "background");
    if (lua_isstring(L, -1)) {
      const char *name = lua_tostring(L, -1);
      int i = 0;
      while (colortable[i].name != NULL &&
             strcmp(colorname, colortable[i].name) != 0)
        i++;
      if (colortable[i].name == NULL)  /* string not found? */
        error(L, "invalid color name (%s)", colorname);
      else {  /* use colortable[i] */
        red = colortable[i].red;
        green = colortable[i].green;
        blue = colortable[i].blue;
      }
    } else if (lua_istable(L, -1)) {
      red = getfield("r");
      green = getfield("g");
      blue = getfield("b");
    } else
        error(L, "invalid value for `background'");

最適なオプションは何でしょうか?Cプログラムでは、コンパイラがスペルミスを検出できないため、オプションを表すために文字列を使用することは良い方法ではありません。しかし、Luaでは、グローバル変数は宣言する必要がないため、ユーザーがカラー名のスペルを間違えた場合でも、Luaはエラーを信号しません。ユーザーが`WITE`と`WHITE`の代わりに記述した場合、`background`変数は`nil`(初期化されていない変数`WITE`の値)を受け取り、アプリケーションが知っているのは`background`が`nil`であるということだけです。何が間違っているかについての他の情報は何もありません。一方、文字列を使用すると、`background`の値はスペルミスのある文字列になります。そのため、アプリケーションはエラーメッセージにその情報を追加できます。アプリケーションは大文字と小文字を区別せずに文字列を比較することもできます。そのため、ユーザーは`「white」`、`「WHITE」`、または`「White」`と書くことができます。さらに、ユーザーのスクリプトが小さく、多くの色がある場合、ユーザーがいくつか選択するだけで何百もの色を登録すること(そして何百ものテーブルとグローバル変数を作成すること)は奇妙かもしれません。文字列を使用すると、このオーバーヘッドを回避できます。