封裝系統

lua-users home
wiki

VersionNotice:此頁面已稍為過時。這是對 Lua 5.0 中實作的模組系統所作的最初提議,當時 Lua 5.0 缺少標準模組系統。標準模組系統已正式併入 Lua 5.1,而且這個模組系統也透過 [LuaCompat] 回傳給 Lua 5.0。我不確定此頁面是否具有任何剩餘的教育價值。如果它還有剩餘價值,它可能可以基於 5.1 模組工作來加以說明或重新修訂。

這是 Lua 5 初始的封裝系統。它主要的功用是

use "packagename" {options}
(其中 {options} 是選用的 ;-) 那個呼叫類似 require,除了

目前,目前的選項函數僅處理 import 選項。import="*" 表示宣告封裝的所有全域名稱至匯入封裝的全域空間;而 import={"name1", "name2", ...} 則僅匯入所選的名稱。

它也定義 declare ("name1", "name2", ...) 函數,用以啟用名稱宣告的強制執行,並宣告給定的名稱。對未定義/未宣告全域變數的任何存取都會引發錯誤。

--
-- auxiliar error function
--

local function error (level, fmt, ...)
  _G.error(string.format(fmt, unpack(arg)), level+1)
end


--
-- this package cannot use the package system (itself!), so use old
-- package tricks (but most of its functions are global anyway...)
--

_G.Package = {}

local function loadfrompath (packname)
  LUA_PATH = LUA_PATH or os.getenv"LUA_PATH" or "?.lua;?"
  for k in string.gfind(LUA_PATH, "[^;]+") do
    local fname = string.gsub(k, "?", packname)
    local f, err = loadfile(fname)
    if f then return f end
    if not string.find(err, "^cannot read") then
      error(err)
    end
  end
  error(3, "cannot find package `%s' in path `%s'", packname, LUA_PATH)
end


--
-- Metatable for Global tables
-- Inherit absent fields from main global
--

local global_mt = {
  __index = function (t,n)
              local val = _G[n]   -- get value from main global
              rawset(t, n, val)   -- save it for next time
              return val
            end,
}


--
-- Alternative metatable, that enforces declarations
--

local Predefined = {}      -- table for predefined variables
setmode(Predefined, "k")

local req_global_mt = {
  __index = function (t,n)
               local val = global_mt.__index(t, n)
               if val then return val end
               if not Predefined[t][n] then
                 error(2, "attempt to read undeclared variable `%s'", n)
               end
               return nil
             end,

   __newindex = function (t,n, val)
                 if not Predefined[t][n] then
                   error(2, "attempt to write to undeclared variable `%s'", n)
                 end
                 rawset(t, n, val)
               end,
}


--
-- Declare variables (and turn on declaration enforcing)
--

function _G.declare (...)
  local predec = Predefined[getglobals(2)]
  if predec == nil then   -- package didn't enforce declarations
    local g = getglobals(2)  -- get package global table
    setmetatable(g, req_global_mt)
    predec = {}
    Predefined[g] = predec
  end
  for _, name in ipairs(arg) do
    predec[name] = true
  end
end


--
-- Default function to handle `use' options
-- (where `oldpack' is using `newpack')
--

function Package.defaultoptions (oldpack, newpack, options)
  for k, v in pairs(options) do
    if k == "version" then
      -- ???
    elseif k == "import" then
      if v == "*" then   -- import all?
        for k,v in pairs(newpack) do
          -- do not import names starting with `_'
          if not string.find(k, "^_") then oldpack[k] = v end
        end
      elseif type(v) == "table" then  -- import list?
        for _,n in ipairs(v) do oldpack[n] = newpack[n] end
      else error(3, "invalid value for `import' option")
      end
    else error(3, "invalid option `"..k.."'")
    end
  end
end


--
-- Import a package, initialize it, and install it in current package
--

function _G.use (packname)
  local g = _G[packname]
  if not g then
    local f = loadfrompath(packname)
    g = {_name = packname}   -- new global table
    g._self = g
    _G[packname] = g
    setmetatable(g, global_mt)
    setglobals(f, g)  -- change global table of calling function
    f()   -- run main
  end
  local init = rawget(g, "_init")
  if init then init(getglobals(2)) end
  return function (options)
    (rawget(g, "_options") or Package.defaultoptions)(getglobals(2), g, options)
  end
end


RecentChanges · 喜好設定
編輯 · 歷史記錄
上次編輯時間為:2007 年 1 月 2 日 上午 5:36 GMT (差異)