Context aware Lua Macros

Will automatically detect locals as well as global variables.

Usage: Macro “text ${Lua Code} more text”

test = Macro "Hello ${jack} ${'Bauer'}"

function test1()
	local jack = "Jack"
	print(test)
end

test1()

function test2()
	local jack = "No One"
	print(test)
end

test2()

function test3()
	local test = Macro "Hello ${world}"
	local world = "World"
	print(test())
end

test3()

function test4()
	local test = Macro "Hello ${print('asdf'); return 'World'}"
	local world = "World"
	print(test)
end

test4()

local _mtg = getmetatable(_G) or {}
function _mtg.__index(self, k)
	return rawget(self,k.."___macro")
end
setmetatable(_G,_mtg)

function string.escape(text)
	local str = ""
	for i=1, #text do
		local chr = text:sub(i,i)
		local escape = ""
		if isIn(chr, {"(", ")"}) then escape = "%" end
		str = str .. escape..chr
	end
	return str
end

function isIn(obj, table)
	for _,v in ipairs(table) do
		if obj == v then return true end
	end
end

function get_locals(func)
     local n = 1
     local locals = {}
     func = (type(func) == "number") and func + 1 or func
     while true do
          local lname, lvalue = debug.getlocal(func, n)
          if lname == nil then break end
          if lvalue == nil then lvalue = mynil end
          locals[lname] = lvalue
          n = n + 1
     end
     return locals
end

Macro = setmetatable({}, {__call=function(self, ...) return self:init(...) end})
function Macro:init(...)
	--We want to retain processing capability even after declaration
	local args = {...}
	local __string = ""
	for _,obj in ipairs(args) do
		__string = __string .. tostring(obj)
	end
	local members = {
		__string = __string,
		type="Macro"
	}
	local methods = {
		__tostring__ = function(self, n)
			--Actual Processing Logic goes here
			local str = self.__string
			local match = "${.-[^\\]}"
			--Check if str has ${statement}
			local indices = {str:find(match)}
			while #indices > 0 do
				local sub = str:sub(indices[1]+2, indices[2]-1)
				local repl = str:sub(unpack(indices))
				local final = ""
				local locals = get_locals(3+n)
				for k,_ in pairs(locals) do
					rawset(_G,k.."___macro",_)
				end
				--Evaluate
				local _fn, e = loadstring(sub)
				if not _fn then
					_fn, e = loadstring("return "..sub)
				end
				if not _fn then
					final = " "..e.." "
				else
					final = tostring(_fn() or " ")
				end
				for k,_ in pairs(locals) do
					rawset(_G,k.."___macro",nil)
				end
				str = str:gsub(repl:escape(), final)
				indices = {str:find(match)}
			end
			return str
		end,
		__tostring = function(self)
			return getmetatable(self).__tostring__(self, 2)
		end,
		__call = function(self)
			return getmetatable(self).__tostring__(self, 0)
		end
	}
	return setmetatable(members, methods)
end

test = Macro "Hello ${jack} ${'Bauer'}"

function test1()
	local jack = "Jack"
	print(test)
end

test1()

function test2()
	local jack = "No One"
	print(test)
end

test2()

function test3()
	local test = Macro "Hello ${world}"
	local world = "World"
	print(test())
end

test3()

function test4()
	local test = Macro "Hello ${print('asdf'); return 'World'}"
	local world = "World"
	print(test)
end

test4()
This entry was posted in Blog, Lua, Programming, Snippets. Bookmark the permalink.

This website uses IntenseDebate comments, but they are not currently loaded because either your browser doesn't support JavaScript, or they didn't load fast enough.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>