1 -------------------------------------------------------------------------------
2 -- Copyright (c) 2006-2013 Fabien Fleutot and others.
4 -- All rights reserved.
6 -- This program and the accompanying materials are made available
7 -- under the terms of the Eclipse Public License v1.0 which
8 -- accompanies this distribution, and is available at
9 -- http://www.eclipse.org/legal/epl-v10.html
11 -- This program and the accompanying materials are also made available
12 -- under the terms of the MIT public license which accompanies this
13 -- distribution, and is available at http://www.lua.org/license.html
16 -- Fabien Fleutot - API and implementation
18 -------------------------------------------------------------------------------
20 -- Survive lack of checks
21 if not pcall(require, 'checks') then function package.preload.checks() function checks() end end end
23 -- Main file for the metalua executable
24 require 'metalua.loader' -- load *.mlue files
25 require 'metalua.compiler.globals' -- metalua-aware loadstring, dofile etc.
27 local alt_getopt = require 'alt_getopt'
28 local pp = require 'metalua.pprint'
29 local mlc = require 'metalua.compiler'
33 local AST_COMPILE_ERROR_NUMBER = -1
34 local RUNTIME_ERROR_NUMBER = -3
36 local alt_getopt_options = "f:l:e:o:xivaASbs"
47 ['print-ast-lineinfo']='A',
53 local chunk_options = {
61 Compile and/or execute metalua programs. Parameters passed to the
62 compiler should be prefixed with an option flag, hinting what must be
63 done with them: take tham as file names to compile, as library names
64 to load, as parameters passed to the running program... When option
65 flags are absent, metalua tries to adopt a "Do What I Mean" approach:
67 - if no code (no library, no literal expression and no file) is
68 specified, the first flag-less parameter is taken as a file name to
71 - if no code and no parameter is passed, an interactive loop is
74 - if a target file is specified with --output, the program is not
75 executed by default, unless a --run flag forces it to. Conversely,
76 if no --output target is specified, the code is run unless ++run
80 function M.cmdline_parser(...)
82 local opts, optind, optarg =
83 alt_getopt.get_ordered_opts({...}, alt_getopt_options, long_opts)
84 --pp.printf("argv=%s; opts=%s, ending at %i, with optarg=%s",
85 -- argv, opts, optind, optarg)
86 local s2l = { } -- short to long option names conversion table
87 for long, short in pairs(long_opts) do s2l[short]=long end
88 local cfg = { chunks = { } }
89 for i, short in pairs(opts) do
90 local long = s2l[short]
91 if chunk_options[long] then table.insert(cfg.chunks, { tag=long, optarg[i] })
92 else cfg[long] = optarg[i] or true end
94 cfg.params = { select(optind, ...) }
100 local cfg = M.cmdline_parser(...)
102 -------------------------------------------------------------------
103 -- Print messages if in verbose mode
104 -------------------------------------------------------------------
105 local function verb_print (fmt, ...)
107 return pp.printf ("[ "..fmt.." ]", ...)
112 verb_print("raw options: %s", cfg)
115 -------------------------------------------------------------------
116 -- If there's no chunk but there are params, interpret the first
117 -- param as a file name.
118 if not next(cfg.chunks) and next(cfg.params) then
119 local the_file = table.remove(cfg.params, 1)
120 verb_print("Param %q considered as a source file", the_file)
121 cfg.file={ the_file }
124 -------------------------------------------------------------------
125 -- If nothing to do, run REPL loop
126 if not next(cfg.chunks) and not cfg.interactive then
127 verb_print "Nothing to compile nor run, force interactive loop"
132 -------------------------------------------------------------------
133 -- Run if asked to, or if no --output has been given
134 -- if cfg.run==false it's been *forced* to false, don't override.
135 if not cfg.run and not cfg.output then
136 verb_print("No output file specified; I'll run the program")
142 -------------------------------------------------------------------
143 -- Get ASTs from sources
146 for i, x in ipairs(cfg.chunks) do
147 local compiler = mlc.new()
148 local tag, val = x.tag, x[1]
149 verb_print("Compiling %s", x)
151 if tag=='library' then
153 {tag='Id', "require" },
154 {tag='String', val } }
155 elseif tag=='literal' then ast = compiler :src_to_ast(val)
156 elseif tag=='file' then
157 ast = compiler :srcfile_to_ast(val)
158 -- Isolate each file in a separate fenv
160 { tag='Function', { { tag='Dots'} }, ast },
162 ast.source = '@'..val
163 code.source = '@'..val
166 error ("Bad option "..tag)
168 local valid = true -- TODO: check AST's correctness
170 pp.printf ("Cannot compile %s:\n%s", x, ast or "no msg")
171 os.exit (AST_COMPILE_ERROR_NUMBER)
174 table.insert(code, ast)
176 -- The last file returns the whole chunk's result
177 if last_file_idx then
178 -- transform +{ (function(...) -{ast} end)(...) }
179 -- into +{ return (function(...) -{ast} end)(...) }
180 local prv_ast = code[last_file_idx]
181 local new_ast = { tag='Return', prv_ast }
182 new_ast.source, new_ast.origin, prv_ast.source, prv_ast.origin =
183 prv_ast.source, prv_ast.origin, nil, nil
184 code[last_file_idx] = new_ast
187 -- Further uses of compiler won't involve AST transformations:
188 -- they can share the same instance.
189 -- TODO: reuse last instance if possible.
190 local compiler = mlc.new()
192 -------------------------------------------------------------------
194 if cfg['print-ast'] or cfg['print-ast-lineinfo'] then
195 verb_print "Resulting AST:"
196 for _, x in ipairs(code) do
197 pp.printf("--- AST From %s: ---", x.source)
198 if x.origin and x.origin.tag=='File' then x=x[1][1][2][1] end
199 local pp_cfg = cfg['print-ast-lineinfo']
200 and { line_max=1, fix_indent=1, metalua_tag=1 }
201 or { line_max=1, metalua_tag=1, hide_hash=1 }
206 -------------------------------------------------------------------
208 if cfg['print-src'] then
209 verb_print "Resulting sources:"
210 for _, x in ipairs(code) do
211 printf("--- Source From %s: ---", table.tostring(x.source, 'nohash'))
212 if x.origin and x.origin.tag=='File' then x=x[1][1][2] end
213 print (compiler :ast2string (x))
217 -- TODO: canonize/check AST
219 local bytecode = compiler :ast_to_bytecode (code)
222 -------------------------------------------------------------------
223 -- Insert #!... command
224 if cfg.sharpbang then
225 local shbang = cfg.sharpbang
226 verb_print ("Adding sharp-bang directive %q", shbang)
227 if not shbang :match'^#!' then shbang = '#!' .. shbang end
228 if not shbang :match'\n$' then shbang = shbang .. '\n' end
229 bytecode = shbang .. bytecode
232 -------------------------------------------------------------------
236 verb_print ("Saving to file %q", cfg.output)
237 local file, err_msg = io.open(cfg.output, 'wb')
238 if not file then error("can't open output file: "..err_msg) end
241 if cfg.sharpbang and os.getenv "OS" ~= "Windows_NT" then
242 pcall(os.execute, 'chmod a+x "'..cfg.output..'"')
246 -------------------------------------------------------------------
250 local f = compiler :bytecode_to_function (bytecode)
252 -- FIXME: isolate execution in a ring
253 -- FIXME: check for failures
254 local function print_traceback (errmsg)
255 return errmsg .. '\n' .. debug.traceback ('',2) .. '\n'
257 local function g() return f(unpack (cfg.params)) end
258 local st, msg = xpcall(g, print_traceback)
261 os.exit(RUNTIME_ERROR_NUMBER)
265 -------------------------------------------------------------------
267 if cfg.interactive then
268 verb_print "Starting REPL loop"
269 require 'metalua.repl' .run()