diff options
Diffstat (limited to 'Tools/LuaMacro/luam')
-rw-r--r-- | Tools/LuaMacro/luam | 280 |
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 |