summaryrefslogtreecommitdiff
path: root/Tools/LuaMacro/luam
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/LuaMacro/luam')
-rw-r--r--Tools/LuaMacro/luam280
1 files changed, 280 insertions, 0 deletions
diff --git a/Tools/LuaMacro/luam b/Tools/LuaMacro/luam
new file mode 100644
index 0000000..bd02269
--- /dev/null
+++ b/Tools/LuaMacro/luam
@@ -0,0 +1,280 @@
+#!/usr/bin/env lua
+--[[--
+Front end for LuaMacro, a Lua macro preprocessor.
+
+The default action is to preprocess and run a Lua file. To just dump
+the preprocessed output, use the `-d` flag. Like `lua`, the `-l` flag can
+be used to load a library first, but you need to explicitly say `-i` to
+get an interactive prompt.
+
+The package loader is modified so that `require 'mod'` will preprocess `mod` if it is found as `mod.m.lua`.
+
+Dumping is the only action available when preprocessing C code with `-C`.
+
+@script luam
+]]
+
+-- adjust the path so that this script can see the macro package
+local path = arg[0]:gsub('[^/\\]+$','')
+package.path = package.path .. ';' .. path .. '?.lua;'..path .. 'macro/?.lua'
+local macro = require 'macro'
+require 'macro.builtin'
+
+--- Using luam.
+-- @usage follows
+local usage = [[
+LuaMacro 2.5.0, a Lua macro preprocessor and runner
+ -l require a library
+ -e statement to be executed
+ -V set a variable (-VX or -VY=1)
+ -c error context to be shown (default 2)
+ -d dump preprocessed output to stdout
+ -o write to this file
+ -C C lexer
+ -N No #line directives when generating C
+ -i interactive prompt
+ -v verbose error trace
+ <input> Lua source file
+]]
+
+-- parsing the args, the hard way:
+local takes_value = {l = '', e = '', c = 2, o = '',V = ';'}
+
+local args = {}
+local idx,i = 1,1
+while i <= #arg do
+ local a = arg[i]
+ local flag = a:match '^%-(.+)'
+ local val
+ if flag then
+ if #flag > 1 then -- allow for -lmod, like Lua
+ val = flag:sub(2)
+ flag = flag:sub(1,1)
+ end
+ -- grab the next argument if we need a value
+ if takes_value[flag] and not val then
+ i = i + 1
+ val = arg[i]
+ end
+ -- convert the argument, if required
+ local def = takes_value[flag]
+ if type(def) == 'number' then
+ val = tonumber(val)
+ elseif def == ';' and args[flag] then
+ val = args[flag]..';'..val
+ end
+ args[flag] = val or true
+ else
+ args[idx] = a
+ idx = idx + 1
+ end
+ i = i + 1
+end
+
+if not args[1] and not args.i then
+ print(usage)
+ os.exit()
+elseif args[1] then
+ args.input_name = args[1]
+ args.input,err = io.open(args[1],'r')
+ if err then return print(err) end
+ table.remove(args,1)
+end
+-- set defaults, if flags not specified
+for k,v in pairs(takes_value) do
+ if not args[k] then
+ args[k] = v
+ end
+end
+
+---------- compiling and running the output ------
+-- the tricky bit here is presenting the errors so that they refer to the
+-- original line numbers. In addition, we also present a few lines of context
+-- in the output.
+
+local function lookup_line (lno,li)
+ for i = 1,#li-1 do
+ --print(li[i].il,li[i].ol,lno,'match')
+ if lno < li[i+1].ol then
+ return li[i].il + (lno - li[i].ol) - 1
+ end
+ end
+ return li[#li].il + (lno - li[#li].ol) - 1
+end
+
+-- iterating over all lines in a string can be awkward;
+-- gmatch doesn't handle the empty-line cases properly.
+local function split_nl (t)
+ local k1 = 1
+ local k2 = t:find ('[\r\n]',k1)
+ return function()
+ if not k2 then return nil end
+ local res = t:sub(k1,k2-1)
+ k1 = k2+1
+ k2 = t:find('[\r\n]',k1)
+ return res
+ end
+end
+
+local function fix_error_trace (err,li)
+ local strname,lno = err:match '%[string "(%S+)"%]:(%d+)'
+ local ino
+ if strname then
+ lno = tonumber(lno)
+ if li then
+ ino = lookup_line(lno,li)
+ err = err:gsub('%[string "%S+"%]:'..(lno or '?')..':',strname..':'..(ino or '?'))
+ end
+ end
+ return err,lno,ino
+end
+
+local function runstring (code,name,li,...)
+ local res,err = loadstring(code,name)
+ local lno,ok
+ if not res then
+ err,lno,ino = fix_error_trace(err,li)
+ if ino then
+ print 'preprocessed context of error:'
+ local l1,l2 = lno-args.c,lno+args.c
+ local l = 1
+ for line in split_nl(code) do
+ if l >= l1 and l <= l2 then
+ if l == lno then io.write('*') else io.write(' ') end
+ print(l,line)
+ end
+ l = l + 1
+ end
+ end
+ io.stderr:write(err,'\n')
+ os.exit(1)
+ end
+ ok,err = xpcall(function(...) return res(...) end, debug.traceback)
+ if not ok then
+ err = err:gsub("%[C%]: in function 'xpcall'.+",'')
+ if li then
+ repeat
+ err,lno = fix_error_trace(err,li)
+ until not lno
+ end
+ io.stderr:write(err,'\n')
+ end
+ return ok
+end
+
+local function subst (ins,name)
+ local C
+ if args.C then
+ C = args.N and true or 'line'
+ end
+ return macro.substitute_tostring(ins,name,C,args.v)
+end
+
+local function subst_runstring (ins,name,...)
+ local buf,li = subst(ins,name)
+ if not buf then
+ io.stderr:write(li,'\n')
+ os.exit(1)
+ end
+ if args.d or args.C or args.o ~= '' then
+ if args.o == '' then
+ print(buf)
+ else
+ local f = io.open(args.o,'w')
+ f:write(buf)
+ f:close()
+ end
+ else
+ return runstring(buf,name,li,...)
+ end
+end
+
+-- Lua 5.1/5.2 compatibility
+local pack = table.pack
+if not pack then
+ function pack(...)
+ return {n=select('#',...),...}
+ end
+end
+if not unpack then unpack = table.unpack end
+
+local function eval(code)
+ local status,val,f,err,rcnt
+ code,rcnt = code:gsub('^%s*=','return ')
+ f,err = loadstring(code,'TMP')
+ if f then
+ res = pack(pcall(f))
+ if not res[1] then err = res[2]
+ else
+ return res
+ end
+ end
+ if err then
+ err = tostring(err):gsub('^%[string "TMP"%]:1:','')
+ return {nil,err}
+ end
+end
+
+local function interactive_loop ()
+ os.execute(arg[-1]..' -v') -- for the Lua copyright
+ print 'Lua Macro 2.5.0 Copyright (C) 2007-2011 Steve Donovan'
+
+ local function readline()
+ io.write(_PROMPT or '> ')
+ return io.read()
+ end
+
+ require 'macro.all'
+ _G.macro = macro
+ macro.define 'quit os.exit()'
+ macro._interactive = true
+
+ local line = readline()
+ while line do
+ local s,err = subst(line..'\n')
+ if not s then
+ err = err:gsub('.-:%d+:','')
+ print('macro error: '..err)
+ elseif not s:match '^%s*$' then
+ if args.d then print(s) end
+ local res = eval(s)
+ if not res[1] then
+ print('expanded: '..s)
+ print('error: '..res[2])
+ elseif res[2] ~= nil then
+ print(unpack(res,2))
+ end
+ end
+ line = readline()
+ end
+end
+
+macro.set_package_loader()
+
+if args.l ~= '' then require(args.l) end
+
+if args.V ~= ';' then
+ for varset in args.V:gmatch '([^;]+)' do
+ local sym,val = varset:match '([^=]+)=(.+)'
+ if not sym then
+ sym = varset
+ val = true
+ end
+ _G[sym] = val
+ end
+end
+
+require 'macro.ifelse'
+
+if args.e ~= '' then
+ subst_runstring(args.e,"<temp>")
+else
+ if args.input then
+ arg = args
+ arg[0] = args.input_name
+ arg[-1] = 'luam'
+ subst_runstring(args.input,args.input_name,unpack(args))
+ elseif args.i then
+ interactive_loop()
+ end
+end