第一版是為 Lua 5.0 所寫。儘管在很大程度上仍然適用於後來的版本,但仍有一些差異。
第四版針對 Lua 5.3,可在 Amazon 和其他書店購買。
購買書籍,您也可以協助 支援 Lua 專案


29.1 – 目錄迭代器

先前,我們實作了一個 `dir` 函式,它會傳回一個包含指定目錄中所有檔案的表格。我們的新實作會傳回一個迭代器,每次呼叫時會傳回一個新項目。有了這個新實作,我們將能夠使用像這樣的一個迴圈來遍歷目錄

    for fname in dir(".") do  print(fname)  end

要在 C 中遍歷目錄,我們需要一個 `DIR` 結構。`DIR` 的執行個體是由 `opendir` 建立的,並且必須明確地呼叫 `closedir` 來釋放。我們先前實作的 `dir` 會將其 `DIR` 執行個體保留為一個區域變數,並在擷取最後一個檔案名稱後關閉該執行個體。我們的新實作無法將這個 `DIR` 執行個體保留在區域變數中,因為它必須在多個呼叫中查詢這個值。此外,它無法只在擷取最後一個名稱後才關閉目錄;如果程式中斷迴圈,迭代器將永遠無法擷取這個最後一個名稱。因此,為了確保 `DIR` 執行個體始終會被釋放,我們將其位址儲存在使用者資料中,並使用這個使用者資料的 `__gc` 元方法來釋放目錄結構。

儘管這個代表目錄的使用者資料在我們的實作中扮演著核心角色,但 Lua 不需要看到它。`dir` 函式會傳回一個迭代器函式;這就是 Lua 所看到的。目錄可能是迭代器函式的上層值。因此,迭代器函式可以直接存取這個結構,但 Lua 程式碼沒有(也不需要)。

總而言之,我們需要三個 C 函數。首先,我們需要 dir 函數,一個 Lua 用來建立迭代器的工廠;它必須開啟一個 DIR 結構,並將其作為迭代器函數的上值。其次,我們需要迭代器函數。第三,我們需要 __gc 元方法,用來關閉 DIR 結構。照例,我們也需要一個額外的函數來進行初始安排,例如建立目錄的元表並初始化此元表。

讓我們從 dir 函數開始我們的程式碼

    #include <dirent.h>
    #include <errno.h>
    
    /* forward declaration for the iterator function */
    static int dir_iter (lua_State *L);
    
    static int l_dir (lua_State *L) {
      const char *path = luaL_checkstring(L, 1);
    
      /* create a userdatum to store a DIR address */
      DIR **d = (DIR **)lua_newuserdata(L, sizeof(DIR *));
    
      /* set its metatable */
      luaL_getmetatable(L, "LuaBook.dir");
      lua_setmetatable(L, -2);
    
      /* try to open the given directory */
      *d = opendir(path);
      if (*d == NULL)  /* error opening the directory? */
        luaL_error(L, "cannot open %s: %s", path,
                                            strerror(errno));
    
      /* creates and returns the iterator function
         (its sole upvalue, the directory userdatum,
         is already on the stack top */
      lua_pushcclosure(L, dir_iter, 1);
      return 1;
    }
這裡有一個微妙之處,我們必須在開啟目錄之前建立使用者資料。如果我們先開啟目錄,然後 lua_newuserdata 的呼叫引發錯誤,我們就會失去 DIR 結構。按照正確的順序,一旦建立 DIR 結構,就會立即與使用者資料關聯;無論之後發生什麼事,__gc 元方法最終都會釋放結構。

下一個函數是迭代器本身

    static int dir_iter (lua_State *L) {
      DIR *d = *(DIR **)lua_touserdata(L, lua_upvalueindex(1));
      struct dirent *entry;
      if ((entry = readdir(d)) != NULL) {
        lua_pushstring(L, entry->d_name);
        return 1;
      }
      else return 0;  /* no more values to return */
    }

__gc 元方法會關閉目錄,但它必須採取一項預防措施:因為我們在開啟目錄之前建立使用者資料,所以無論 opendir 的結果如何,這個使用者資料都會被收集。如果 opendir 失敗,就沒有什麼可以關閉的了。

    static int dir_gc (lua_State *L) {
      DIR *d = *(DIR **)lua_touserdata(L, 1);
      if (d) closedir(d);
      return 0;
    }

最後,有一個函數開啟這個單一函數函式庫

    int luaopen_dir (lua_State *L) {
      luaL_newmetatable(L, "LuaBook.dir");
    
      /* set its __gc field */
      lua_pushstring(L, "__gc");
      lua_pushcfunction(L, dir_gc);
      lua_settable(L, -3);
    
      /* register the `dir' function */
      lua_pushcfunction(L, l_dir);
      lua_setglobal(L, "dir");
    
      return 0;
    }

整個範例有一個有趣的微妙之處。起初,dir_gc 似乎應該檢查其引數是否為目錄。否則,惡意的使用者可能會用另一種使用者資料(例如檔案)呼叫它,造成災難性的後果。然而,Lua 程式無法存取這個函數:它只儲存在目錄的元表中,而 Lua 程式永遠不會存取那些目錄。