Package System

lua-users home
wiki

VersionNotice: This page is a bit outdated. It's a initial proposal for a module system implemented on top of Lua 5.0, which lacked a standard module system. A standard module system was formally incorporated in Lua 5.1, and that module system was also backported to Lua 5.0 via [LuaCompat]. I'm not sure if this page has any remaining educational value. If it does have remaining value, it probably could be described or reworked in light of the 5.1 module work.

This is an embryonic package system for Lua 5. Its main function is

use "packagename" {options}

(where {options} is optional ;-) That call is similar to a require, except that

Currently, the current option function handles only the import option. import="*" means to declare all global names of the package into the global space of the importing package; import={"name1", "name2", ...} imports only the selected names.

It also defines a declare ("name1", "name2", ...) function, that turns on enforcing declaration of names, and also declares the given names. Any access to an undefined/undeclared global raises an error.

--

-- 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 · preferences
edit · history
Last edited January 2, 2007 4:36 am GMT (diff)