Runtime Syntax

lua-users home
wiki

This utility module automates the run-time simulation of what resembles syntax extensions. It's somewhat of a hack, intended mostly as a proof of concept, and not well tested. For a truer implementation of this type of thing, see MetaLua.

Example:

-- Define syntax
local S = require "runtimesyntax"
S["insert (x) .into (t)"] = function(x,t)
  table.insert(t, x)
end
S["increment (t) .by (n)"] = function(t,n)
  for i in ipairs(t) do t[i] = t[i] + n end
end
S["check (a) '>' (b) '<' (c)"] = function(a,b,c)
  local ok = a > b and a < c
  print(string.format("checking %f < %f < %f", b, a, c),
    "...", ok and "pass" or "fail")
end

-- using it
local t = {}
insert (5) .into (t)
insert (6) .into (t)
increment (t) .by (10)
assert(t[1] == 15 and t[2] == 16)
check (#t) '>' (1) '<' (3)
print 'done'

-- This cases "syntax error: missing  (c) at @insert.lua:?:25"
check (#t) '>' (1) '<'

Implementation:

-- runtimesyntax.lua
-- runtime simulatation of syntax extensions in Lua.
--
-- WARNING: not well tested.
--
-- (c) 2009 David Manura
-- Licensed under the same terms as Lua (MIT license).


-- Detect incomplete syntax (optional)
local function wrap(o, syntax, pos)
  if syntax:match'^%s*$' then return o end
  -- http://lua-users.org/wiki/HiddenFeatures
  local ud = newproxy(true)
  local mt = getmetatable(ud)
  mt.__call = type(o) == 'function' and
                 function(_, ...) return o(...) end or nil
  mt.__index = type(o) == 'table' and getmetatable(o).__index or nil
  mt.__gc = function()
    io.stderr:write("syntax error: missing ", syntax, " at ", pos, "\n")
  end
  return ud
end
local function clear(o)
  if type(o) == 'userdata' then
    getmetatable(o).__gc = nil
  end
end


-- Build function for rest of syntax.
--   syntax - syntax string
--   vars - arguments passed to function
--          (with n count field to support nils)
--   f - function to call after building, passing
--        unpacked vars
--   oprev - previous function or userdata this syntax
--        is appended to
--   pos - string indicating position in source
local function make(syntax, vars, f, oprev, pos)
  local var, moresyntax = syntax:match("^%s*%(%s*(%w+)%s*%)(.*)")
  if var then
    local o
    o = wrap(function(vara)
      clear(oprev)
      vars[vars.n+1] = vara
      vars.n = vars.n + 1
      return make(moresyntax, vars, f, o, pos)
    end, moresyntax, pos)
    return o
  end
  local prep, moresyntax = syntax:match("^%s*%.(%w+)%s*(.*)")
  if prep then
    local o
    local mt = {}
    function mt:__index(prepa)
      clear(oprev)
      if prep ~= prepa then
        error(string.format("syntax error: expecting (%s) got (%s)",
          prep, tostring(prepa)))
      end
      return make(moresyntax, vars, f, o, pos)
    end
    o = wrap(setmetatable({}, mt), moresyntax, pos)
    return o
  end
  local prep, moresyntax = syntax:match("^%s*'([^']+)'(.*)")
  if prep then
    local o
    o = wrap(function(prepa)
      clear(oprev)
      if prep ~= prepa then
        error(string.format("syntax error: expecting (%s) got (%s)",
          prep, tostring(prepa)))
      end
      return make(moresyntax, vars, f, o, pos)
    end, moresyntax, pos)
    return o
  end
  if syntax:match'^%s*$' then
    return f(unpack(vars, 1, vars.n)), true
  end
  error("unrecognized:" .. syntax)
end

local M = {}
local M_mt = {}
function M_mt:__newindex(syntax, f)
  local w, moresyntax = syntax:match("^%s*(%w+)(.*)")
  if w then
    _G[w] = function(...)
      local info = debug.getinfo(2)
      local pos = (info.source or '?') .. ':' ..
                  (info.name or '?') .. ':' ..
                  (info.currentline or '?')
      return make(moresyntax, {n=0}, f, nil, pos)(...)
    end
  end
end
setmetatable(M, M_mt)

return M

--DavidManura

See Also


RecentChanges · preferences
edit · history
Last edited May 2, 2009 2:35 am GMT (diff)