mirror of
https://github.com/g012/l65.git
synced 2025-01-06 08:31:22 +00:00
714 lines
22 KiB
Lua
714 lines
22 KiB
Lua
----------------------------------------------
|
|
-- LuaMacro 2, a macro-preprocessor for Lua.
|
|
-- Unlike LuaMacro 1.x, it does not depend on the token-filter patch and generates
|
|
-- Lua code which can be printed out or compiled directly. C-style macros are easy, but LM2
|
|
-- allows for macros that can read their own input and generate output using Lua code.
|
|
-- New in this release are lexically-scoped macros.
|
|
-- The Lua Lpeg Lexer is by Peter Odding.
|
|
--
|
|
-- Examples:
|
|
--
|
|
-- macro.define 'sqr(x) ((x)*(x))'
|
|
-- macro.define 'assert_(expr) assert(expr,_STR_(expr))'
|
|
-- macro.define('R(n)',function(n)
|
|
-- n = n:get_number()
|
|
-- return ('-'):rep(n)
|
|
-- end
|
|
-- macro.define('lazy',function(get)
|
|
-- get() -- skip space
|
|
-- local expr,endt = get:upto(function(t,v)
|
|
-- return t == ',' or t == ')' or t == ';'
|
|
-- or (t=='space' and v:match '\n')
|
|
-- end)
|
|
-- return 'function(_) return '..tostring(expr)..' end'..tostring(endt)
|
|
-- end)
|
|
--
|
|
--
|
|
-- @author Steve Donovan
|
|
-- @copyright 2011
|
|
-- @license MIT/X11
|
|
-- @module macro
|
|
-- @alias M
|
|
|
|
local macro = {}
|
|
local M = macro
|
|
local lexer = require 'macro.lexer'
|
|
local Getter = require 'macro.Getter'
|
|
local TokenList = require 'macro.TokenList'
|
|
local scan_code = lexer.scan_lua
|
|
local append = table.insert
|
|
local setmetatable = setmetatable
|
|
|
|
--local tdump = require 'pl.pretty'.dump
|
|
|
|
local scan_iter, tnext = Getter.scan_iter, Getter.next
|
|
|
|
|
|
M.upto_keywords = Getter.upto_keywords
|
|
M.Putter = TokenList.new
|
|
|
|
-- given a token list, a set of formal arguments and the actual arguments,
|
|
-- return a new token list where the formal arguments have been replaced
|
|
-- by the actual arguments
|
|
local function substitute_tokenlist (tl,parms,args)
|
|
local append,put_tokens = table.insert,TokenList.tokens
|
|
local parm_map = {}
|
|
for i,name in ipairs(parms) do
|
|
parm_map[name] = args[i]
|
|
end
|
|
local res = {}
|
|
for _,tv in ipairs(tl) do
|
|
local t,v = tv[1],tv[2]
|
|
if t == 'iden' then
|
|
local pval = parm_map[v]
|
|
if pval then
|
|
put_tokens(res,pval)
|
|
else
|
|
append(res,tv)
|
|
end
|
|
else
|
|
append(res,tv)
|
|
end
|
|
end
|
|
return res
|
|
end
|
|
|
|
----------------
|
|
-- Defining and Working with Macros.
|
|
-- @section macros
|
|
|
|
--- make a copy of a list of tokens.
|
|
-- @param tok the list
|
|
-- @param pred copy up to this condition; if defined, must be a function
|
|
-- of two arguments, the token type and the token value.
|
|
-- @return the copy
|
|
-- @return the token that matched the predicate
|
|
function M.copy_tokens(tok,pred)
|
|
local res = {}
|
|
local t,v = tok()
|
|
while t and not (pred and pred(t,v)) do
|
|
append(res,{t,v})
|
|
t,v = tok()
|
|
end
|
|
return res,{t,v}
|
|
end
|
|
|
|
---- define new lexical tokens.
|
|
-- @param extra a list of strings defining the new tokens
|
|
-- @usage macro.define_tokens{'{|'}
|
|
function M.define_tokens(extra)
|
|
lexer.add_extra_tokens(extra)
|
|
end
|
|
|
|
local imacros,smacros = {},{}
|
|
|
|
M.macro_table = imacros
|
|
|
|
--- define a macro using a specification string and optional function.
|
|
-- The specification looks very much like a C preprocessor macro: the name,
|
|
-- followed by an optional formal argument list (_no_ space after name!) and
|
|
-- the substitution. e.g `answer 42` or `sqr(x) ((x)*(x))`
|
|
--
|
|
-- If there is no substitution, then the second argument must be a function which
|
|
-- will be evaluated for the actual substitution. If there are explicit parameters, then they will be passed as token lists. Otherwise, the function is passed a `get` and a `put` argument, which are `Getter` and `TokenList` objects.
|
|
--
|
|
-- The substitution function may return a `TokenList` object, or a string.
|
|
-- @param macstr
|
|
-- @param subst_fn the optional substitution function
|
|
-- @see macro.Getter, macro.TokenList
|
|
function M.define(macstr,subst_fn)
|
|
local tok,t,macname,parms,parm_map
|
|
local mtbl
|
|
tok = scan_code(macstr)
|
|
t,macname = tok()
|
|
if t == 'iden' then mtbl = imacros
|
|
elseif t ~= 'string' and t ~= 'number' and t ~= 'keyword' then
|
|
mtbl = smacros
|
|
else
|
|
error("a macro cannot be of type "..t)
|
|
end
|
|
t = tok()
|
|
if t == '(' then
|
|
parms = Getter.new(tok):idens()
|
|
end
|
|
mtbl[macname] = {
|
|
name = macname,
|
|
subst = subst_fn or M.copy_tokens(tok),
|
|
parms = parms
|
|
}
|
|
end
|
|
|
|
--- define a macro using a function and a parameter list.
|
|
-- @param name either an identifier or an operator.
|
|
-- @param subst a function
|
|
-- @param parms a list of parameter names
|
|
-- @return the existing value of this macro, if any
|
|
function M.set_macro(name,subst,parms)
|
|
local macros
|
|
if name:match '^[_%a][_%w]*$' then
|
|
macros = imacros
|
|
else
|
|
macros = smacros
|
|
end
|
|
if subst == nil then
|
|
macros[name] = nil
|
|
return
|
|
end
|
|
local last = macros[name]
|
|
if type(subst) ~= 'table' or not subst.name then
|
|
subst = {
|
|
name = name,
|
|
subst = subst,
|
|
parms = parms
|
|
}
|
|
end
|
|
macros[name] = subst
|
|
return last
|
|
end
|
|
|
|
--- defined a scoped macro. Like define except this macro will not
|
|
-- be visible outside the current scope.
|
|
-- @param name either an identifier or an operator.
|
|
-- @param subst a function
|
|
-- @param parms a list of parameter names
|
|
-- @see set_macro
|
|
function M.define_scoped (name,subst,parms)
|
|
local old_value = M.set_macro(name,subst,parms)
|
|
M.block_handler(-1,function()
|
|
M.set_macro(name,old_value)
|
|
end)
|
|
end
|
|
|
|
--- get the value of a macro. The macro substitution must either be a
|
|
-- a string or a single token.
|
|
-- @param name existing macro name
|
|
-- @return a string value, or nil if the macro does not exist.
|
|
function M.get_macro_value(name)
|
|
local mac = imacros[name]
|
|
if not mac then return nil end
|
|
if type(mac.subst) == 'table' then
|
|
return mac.subst[1][2]
|
|
else
|
|
return mac.subst
|
|
end
|
|
end
|
|
|
|
local function get_macro (mac, no_error)
|
|
local macro = imacros[mac]
|
|
if not macro and not no_error then
|
|
M.error("macro "..mac.." is not defined")
|
|
end
|
|
return macro
|
|
end
|
|
|
|
local push,pop = table.insert,table.remove
|
|
|
|
--- push a value on the stack associated with a macro.
|
|
-- @param name macro name
|
|
-- @param value any value
|
|
function M.push_macro_stack (name,value)
|
|
local macro = get_macro(name)
|
|
macro.stack = macro.stack or {}
|
|
push(macro.stack,value)
|
|
end
|
|
|
|
--- pop a value from a macro stack.
|
|
-- @param name macro name
|
|
-- @return any value, if defined
|
|
function M.pop_macro_stack (name)
|
|
local macro = get_macro(name)
|
|
if macro.stack and #macro.stack > 0 then
|
|
return pop(macro.stack)
|
|
end
|
|
end
|
|
|
|
--- value of top of macro stack.
|
|
-- @param name macro name
|
|
-- @return any value, if defined
|
|
function M.value_of_macro_stack (name)
|
|
local macro = get_macro(name,true)
|
|
if not macro then return nil end
|
|
if macro.stack and #macro.stack > 0 then
|
|
return macro.stack[#macro.stack]
|
|
end
|
|
end
|
|
|
|
local lua_keywords = {
|
|
['do'] = 'open', ['then'] = 'open', ['else'] = 'open', ['function'] = 'open',
|
|
['repeat'] = 'open';
|
|
['end'] = 'close', ['until'] = 'close',['elseif'] = 'close'
|
|
}
|
|
|
|
local c_keywords = {}
|
|
local keywords = lua_keywords
|
|
|
|
local block_handlers,keyword_handlers = {},{}
|
|
local level = 1
|
|
|
|
--- specify a block handler at a given level.
|
|
-- a block handler may indicate with an extra true return
|
|
-- that it wants to persist; it is passed the getter and the keyword
|
|
-- so we can get more specific end-of-block handlers.
|
|
-- @param lev relative block level
|
|
-- @param action will be called when the block reaches the level
|
|
function M.block_handler (lev,action)
|
|
lev = lev + level
|
|
if not block_handlers[lev] then
|
|
block_handlers[lev] = {}
|
|
end
|
|
append(block_handlers[lev],action)
|
|
end
|
|
|
|
local function process_block_handlers(level,get,v)
|
|
local persist,result
|
|
for _,bh in pairs(block_handlers[level]) do
|
|
local res,keep = bh(get,v)
|
|
if not keep then
|
|
if res then result = res end
|
|
else
|
|
persist = persist or {}
|
|
append(persist,bh)
|
|
end
|
|
end
|
|
block_handlers[level] = persist
|
|
return result
|
|
end
|
|
|
|
|
|
--- set a keyword handler. Unlike macros, the keyword itself is always
|
|
-- passed through, but the handler may add some output afterwards.
|
|
-- If the action is nil, then the handler for that keyword is removed.
|
|
-- @param word keyword
|
|
-- @param action function to be called when keyword is encountered
|
|
-- @return previous handler associated with this keyword
|
|
function M.keyword_handler (word,action)
|
|
if word == 'BEGIN' or word == 'END' then
|
|
keyword_handlers[word] = action
|
|
return
|
|
end
|
|
if action then
|
|
local last = keyword_handlers[word]
|
|
keyword_handlers[word] = action
|
|
return last
|
|
else
|
|
keyword_handlers[word] = nil
|
|
end
|
|
end
|
|
|
|
--- set a scoped keyword handler. Like keyword_handler, except
|
|
-- it restores the original keyword handler (if any) at the end
|
|
-- of the current block.
|
|
-- @param word keyword
|
|
-- @param action to be called when keyword is encountered
|
|
-- @see keyword_handler
|
|
function M.scoped_keyword_handler (keyword, action)
|
|
local last = M.keyword_handler(keyword,action)
|
|
M.block_handler(-1,function()
|
|
M.keyword_handler(keyword,last)
|
|
end)
|
|
end
|
|
|
|
-- a convenient way to use keyword handlers. This sets a handler and restores
|
|
-- the old handler at the end of the current block.
|
|
-- @param word keyword
|
|
-- @param action to be called when keyword is encountered
|
|
-- @return a function that creates a scoped keyword handler
|
|
function M.make_scoped_handler(keyword,handler)
|
|
return function() M.scoped_keyword_handler(keyword, action) end
|
|
end
|
|
|
|
M.please_throw = false
|
|
|
|
--- macro error messages.
|
|
-- @param msg the message: will also have file:line.
|
|
function M.error(msg)
|
|
msg = M.filename..':'..lexer.line..': '..msg
|
|
if M.please_throw then
|
|
error(msg,2)
|
|
else
|
|
io.stderr:write(msg,'\n')
|
|
os.exit(1)
|
|
end
|
|
end
|
|
|
|
M.define ('debug_',function()
|
|
M.DEBUG = true
|
|
end)
|
|
|
|
--- macro error assert.
|
|
-- @param expr an expression.
|
|
-- @param msg a message
|
|
function M.assert(expr,msg)
|
|
if not expr then M.error(msg or 'internal error') end
|
|
return expr
|
|
end
|
|
|
|
Getter.error = M.error
|
|
Getter.assert = M.assert
|
|
TokenList.assert = M.assert
|
|
|
|
local line_updater, line_table, last_name, last_lang
|
|
|
|
local function lua_line_updater (iline,oline)
|
|
if not line_table then line_table = {} end
|
|
append(line_table,{il=iline,ol=oline})
|
|
end
|
|
|
|
local function c_line_updater (iline,oline,last_t,last_v)
|
|
local endt = last_t == 'space' and last_v or '\n'
|
|
return '#line '..iline..' "'..M.filename..'"'..endt
|
|
end
|
|
|
|
local make_putter = TokenList.new
|
|
|
|
--- do a macro substitution on Lua source.
|
|
-- @param src Lua source (either string or file-like reader)
|
|
-- @param out output (a file-like writer)
|
|
-- @param name input file name
|
|
-- @param use_c nil for Lua; if 'line', then output #line directives; if true, then don't
|
|
-- @return the result as table of strings
|
|
-- @return line number information
|
|
function M.substitute(src,name, use_c)
|
|
local out, ii = {}, 1
|
|
local subparse
|
|
if name then
|
|
last_name = name
|
|
last_lang = use_c
|
|
else
|
|
name = last_name
|
|
use_c = last_lang and true
|
|
subparse = true
|
|
end
|
|
M.filename = name
|
|
if use_c then
|
|
lexer = require 'macro.clexer'
|
|
scan_code = lexer.scan_c
|
|
keywords = c_keywords
|
|
if use_c == 'line' then
|
|
line_updater = c_line_updater
|
|
else
|
|
line_updater = function() end
|
|
end
|
|
else
|
|
lexer = require 'macro.lexer'
|
|
scan_code = lexer.scan_lua
|
|
keywords = lua_keywords
|
|
line_updater = lua_line_updater
|
|
end
|
|
local tok = scan_code(src,name)
|
|
local iline,iline_changed = 0
|
|
local last_t,last_v = 'space','\n'
|
|
local do_action
|
|
|
|
|
|
local t,v = tok()
|
|
|
|
-- this function get() is always used, so that we can handle end-of-stream properly.
|
|
-- The substitution mechanism pushes a new stream on the tstack, which is popped
|
|
-- when empty.
|
|
local tstack = {}
|
|
local push,pop = table.insert,table.remove
|
|
|
|
local function get ()
|
|
last_t,last_v = t,v
|
|
local t,v = tok()
|
|
while not t do
|
|
tok = pop(tstack)
|
|
if tok == nil then
|
|
if not subparse and keyword_handlers.END then
|
|
do_action(keyword_handlers.END)
|
|
keyword_handlers.END = nil
|
|
end
|
|
if tok == nil then -- END action might have inserted some tokens
|
|
return nil
|
|
end
|
|
end -- finally finished
|
|
t,v = tok()
|
|
end
|
|
if name == lexer.name and iline ~= lexer.line then
|
|
iline = lexer.line -- input line has changed
|
|
iline_changed = last_v
|
|
end
|
|
return t,v
|
|
end
|
|
|
|
local getter = Getter.new(get)
|
|
|
|
--- get a list of consecutive matching tokens.
|
|
-- @param get token fetching function
|
|
-- @param accept set of token types (default: `{space=true,comment=true}`)
|
|
function getter.matching (get, accept)
|
|
accept = accept or {space=true, comment=true}
|
|
local tl = TokenList.new()
|
|
local t,v = get:peek(1, true)
|
|
while accept[t] do
|
|
t,v = get ()
|
|
append(tl, {t, v})
|
|
t,v = get:peek(1, true)
|
|
end
|
|
return tl
|
|
end
|
|
|
|
function getter:peek (offset,dont_skip)
|
|
local step = offset < 0 and -1 or 1 -- passing offset 0 is undefined
|
|
local k = 0
|
|
local token, t, v
|
|
repeat
|
|
while true do
|
|
token = tok (k)
|
|
if not token then return nil, 'EOS' end
|
|
t,v = token[1], token[2]
|
|
if dont_skip or (t ~= 'space' and t ~= 'comment') then break end
|
|
k = k + 1
|
|
end
|
|
offset = offset - step
|
|
k = k + step
|
|
until offset == 0
|
|
return t,v,k+1
|
|
end
|
|
|
|
function getter:peek2 ()
|
|
local t1,v1,k1 = self:peek(1)
|
|
local t2,v2 = self:peek(k1+1)
|
|
return t1,v1,t2,v2
|
|
end
|
|
|
|
function getter:patch (idx,text)
|
|
out[idx] = text
|
|
end
|
|
|
|
function getter:placeholder (put)
|
|
put:iden '/MARK?/'
|
|
return ii
|
|
end
|
|
|
|
function getter:copy_from (pos,clear)
|
|
local res = {}
|
|
for i = pos, ii do
|
|
if out[i] and not out[i]:match '^#line' then
|
|
append(res,out[i])
|
|
end
|
|
end
|
|
if clear then
|
|
for i = pos, ii do
|
|
table.remove(out,pos)
|
|
ii = ii - 1
|
|
end
|
|
end
|
|
return table.concat(res)
|
|
end
|
|
|
|
-- this feeds the results of a substitution into the token stream.
|
|
-- substitutions may be token lists, Lua strings or nil, in which case
|
|
-- the substitution is ignored. The result is to push a new token stream
|
|
-- onto the tstack, so it can be fetched using get() above
|
|
local function push_substitution (subst)
|
|
if subst == nil then return end
|
|
local st = type(subst)
|
|
push(tstack,tok)
|
|
if st == 'table' then
|
|
subst = scan_iter(subst)
|
|
elseif st == 'string' then
|
|
subst = scan_code(subst)
|
|
end
|
|
tok = subst
|
|
end
|
|
M.push_substitution = push_substitution
|
|
|
|
-- a macro object consists of a subst object and (optional) parameters.
|
|
-- If there are parms, then a macro argument list must follow.
|
|
-- The subst object is either a token list or a function; if a token list we
|
|
-- substitute the actual parameters for the formal parameters; if a function
|
|
-- then we call it with the actual parameters.
|
|
-- Without parameters, it may be a simple substitution (TL or Lua string) or
|
|
-- may be a function. In the latter case we call it passing the token getter,
|
|
-- assuming that it will grab anything it needs from the token stream.
|
|
local function expand_macro(get,mac)
|
|
local pass_through
|
|
local subst = mac.subst
|
|
local fun = type(subst)=='function'
|
|
if mac.parms then
|
|
t = tnext(get);
|
|
if t ~= '(' then
|
|
M.error('macro '..mac.name..' expects parameters')
|
|
end
|
|
local args,err = Getter.list(get)
|
|
M.assert(args,'no end of argument list')
|
|
if fun then
|
|
subst = subst(unpack(args))
|
|
else
|
|
if #mac.parms ~= #args then
|
|
M.error(mac.name.." takes "..#mac.parms.." arguments")
|
|
end
|
|
subst = substitute_tokenlist(subst,mac.parms,args)
|
|
end
|
|
elseif fun then
|
|
subst,pass_through = subst(getter,make_putter())
|
|
end
|
|
push_substitution(subst)
|
|
return pass_through
|
|
end
|
|
|
|
local multiline_tokens,sync = lexer.multiline_tokens,lexer.sync
|
|
local line,last_diff = 0,0
|
|
|
|
function do_action (action)
|
|
push_substitution(action(getter,make_putter()))
|
|
end
|
|
|
|
if not subparse and keyword_handlers.BEGIN then
|
|
do_action(keyword_handlers.BEGIN)
|
|
end
|
|
|
|
while t do
|
|
--print('tv',t,v)
|
|
local dump = true
|
|
if t == 'iden' then -- classic name macro
|
|
local mac = imacros[v]
|
|
if mac then
|
|
dump = expand_macro(get,mac)
|
|
end
|
|
elseif t == 'keyword' then
|
|
-- important to track block level for lexical scoping and block handlers
|
|
local class = keywords[v]
|
|
if class == 'open' then
|
|
if v ~= 'else' then level = level + 1 end
|
|
elseif class == 'close' then
|
|
level = level - 1
|
|
if block_handlers[level] then
|
|
local res = process_block_handlers(level,get,v)
|
|
if res then push_substitution(res) end
|
|
end
|
|
--* elseif class == 'hook' then
|
|
end
|
|
local action = keyword_handlers[v]
|
|
if action then do_action(action) end
|
|
else -- any unused 'operator' token (like @, \, #) can be used as a macro
|
|
if use_c then
|
|
if v == '{' then
|
|
level = level + 1
|
|
elseif v == '}' then
|
|
level = level - 1
|
|
if block_handlers[level] then
|
|
local res = process_block_handlers(level,get,v)
|
|
if res then push_substitution(res) end
|
|
end
|
|
end
|
|
end
|
|
local mac = smacros[v]
|
|
if mac then
|
|
dump = expand_macro(get,mac)
|
|
end
|
|
end
|
|
if dump then
|
|
if multiline_tokens[t] then -- track output line
|
|
line = sync(line, v)
|
|
end
|
|
if iline_changed then
|
|
local diff = line - iline
|
|
if diff ~= last_diff then
|
|
local ldir = line_updater(iline,line,last_t,last_v)
|
|
if ldir then out[ii] = ldir; ii=ii+1 end
|
|
last_diff = diff
|
|
end
|
|
iline_changed = nil
|
|
end
|
|
out[ii] = v
|
|
ii = ii + 1
|
|
end
|
|
t,v = get()
|
|
end
|
|
|
|
return out,line_table
|
|
end
|
|
|
|
--- take some Lua source and return the result of the substitution.
|
|
-- Does not raise any errors.
|
|
-- @param src either a string or a readable file object
|
|
-- @param name optional name for the chunk
|
|
-- @return the result or nil
|
|
-- @return the error, if error
|
|
function M.substitute_tostring(src,name,use_c,throw)
|
|
M.please_throw = true
|
|
local ok,out,li
|
|
if throw then
|
|
out,li = M.substitute(src,name,use_c)
|
|
else
|
|
ok,out,li = pcall(M.substitute,src,name,use_c)
|
|
end
|
|
if type(src) ~= 'string' and src.close then src:close() end
|
|
if not ok then return nil, out
|
|
else
|
|
return table.concat(out), li
|
|
end
|
|
end
|
|
|
|
local lua52 = _VERSION:match '5.2'
|
|
local load, searchpath = load, package.searchpath
|
|
|
|
if not lua52 then -- Lua 5.1
|
|
function load (env,src,name)
|
|
local chunk,err = loadstring(src,name)
|
|
if chunk and env then
|
|
setfenv(chunk,env)
|
|
end
|
|
return chunk,err
|
|
end
|
|
end
|
|
|
|
if not searchpath then
|
|
local sep = package.config:sub(1,1)
|
|
searchpath = function (mod,path)
|
|
mod = mod:gsub('%.',sep)
|
|
for m in path:gmatch('[^;]+') do
|
|
local nm = m:gsub('?',mod)
|
|
local f = io.open(nm,'r')
|
|
if f then f:close(); return nm end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- load Lua code in a given envrionment after passing
|
|
-- through the macro preprocessor.
|
|
-- @param src either a string or a readable file object
|
|
-- @param name optional name for the chunk
|
|
-- @param env the environment (may be nil)
|
|
-- @return the cnunk, or nil
|
|
-- @return the error, if no chunk
|
|
function M.load(src,name,env)
|
|
local res,err = M.substitute_tostring(src,'tmp')
|
|
if not res then return nil,err end
|
|
return loadin(env,res,name)
|
|
end
|
|
|
|
--- evaluate Lua macro code in a given environment.
|
|
-- @param src either a string or a readable file object
|
|
-- @param env the environment (can be nil)
|
|
-- @return true if succeeded
|
|
-- @return result(s)
|
|
function M.eval(src,env)
|
|
local chunk,err = M.loadin(src,'(tmp)',env)
|
|
if not chunk then return nil,err end
|
|
return pcall(chunk)
|
|
end
|
|
|
|
package.mpath = './?.m.lua'
|
|
|
|
--- Make `require` use macro expansion.
|
|
-- This is controlled by package.mpath, which is initially './?.m.lua'
|
|
function M.set_package_loader()
|
|
-- directly inspired by https://github.com/bartbes/Meta/blob/master/meta.lua#L32,
|
|
-- after a suggestion by Alexander Gladysh
|
|
table.insert(package.searchers, function(name)
|
|
local fname = searchpath(name,package.mpath)
|
|
if not fname then return nil,"cannot find "..name end
|
|
local res,err = M.load(io.open(fname),lname)
|
|
if not res then
|
|
error (err)
|
|
end
|
|
return res
|
|
end)
|
|
end
|
|
|
|
return macro
|