--- /dev/null
+-------------------------------------------------------------------------------
+-- Copyright (c) 2006-2013 Fabien Fleutot and others.
+--
+-- All rights reserved.
+--
+-- This program and the accompanying materials are made available
+-- under the terms of the Eclipse Public License v1.0 which
+-- accompanies this distribution, and is available at
+-- http://www.eclipse.org/legal/epl-v10.html
+--
+-- This program and the accompanying materials are also made available
+-- under the terms of the MIT public license which accompanies this
+-- distribution, and is available at http://www.lua.org/license.html
+--
+-- Contributors:
+-- Fabien Fleutot - API and implementation
+--
+-------------------------------------------------------------------------------
+
+-- Survive lack of checks
+if not pcall(require, 'checks') then function package.preload.checks() function checks() end end end
+
+-- Main file for the metalua executable
+require 'metalua.loader' -- load *.mlue files
+require 'metalua.compiler.globals' -- metalua-aware loadstring, dofile etc.
+
+local alt_getopt = require 'alt_getopt'
+local pp = require 'metalua.pprint'
+local mlc = require 'metalua.compiler'
+
+local M = { }
+
+local AST_COMPILE_ERROR_NUMBER = -1
+local RUNTIME_ERROR_NUMBER = -3
+
+local alt_getopt_options = "f:l:e:o:xivaASbs"
+
+local long_opts = {
+ file='f',
+ library='l',
+ literal='e',
+ output='o',
+ run='x',
+ interactive='i',
+ verbose='v',
+ ['print-ast']='a',
+ ['print-ast-lineinfo']='A',
+ ['print-src']='S',
+ ['meta-bugs']='b',
+ ['sharp-bang']='s',
+}
+
+local chunk_options = {
+ library=1,
+ file=1,
+ literal=1
+}
+
+local usage=[[
+
+Compile and/or execute metalua programs. Parameters passed to the
+compiler should be prefixed with an option flag, hinting what must be
+done with them: take tham as file names to compile, as library names
+to load, as parameters passed to the running program... When option
+flags are absent, metalua tries to adopt a "Do What I Mean" approach:
+
+- if no code (no library, no literal expression and no file) is
+ specified, the first flag-less parameter is taken as a file name to
+ load.
+
+- if no code and no parameter is passed, an interactive loop is
+ started.
+
+- if a target file is specified with --output, the program is not
+ executed by default, unless a --run flag forces it to. Conversely,
+ if no --output target is specified, the code is run unless ++run
+ forbids it.
+]]
+
+function M.cmdline_parser(...)
+ local argv = {...}
+ local opts, optind, optarg =
+ alt_getopt.get_ordered_opts({...}, alt_getopt_options, long_opts)
+ --pp.printf("argv=%s; opts=%s, ending at %i, with optarg=%s",
+ -- argv, opts, optind, optarg)
+ local s2l = { } -- short to long option names conversion table
+ for long, short in pairs(long_opts) do s2l[short]=long end
+ local cfg = { chunks = { } }
+ for i, short in pairs(opts) do
+ local long = s2l[short]
+ if chunk_options[long] then table.insert(cfg.chunks, { tag=long, optarg[i] })
+ else cfg[long] = optarg[i] or true end
+ end
+ cfg.params = { select(optind, ...) }
+ return cfg
+end
+
+function M.main (...)
+
+ local cfg = M.cmdline_parser(...)
+
+ -------------------------------------------------------------------
+ -- Print messages if in verbose mode
+ -------------------------------------------------------------------
+ local function verb_print (fmt, ...)
+ if cfg.verbose then
+ return pp.printf ("[ "..fmt.." ]", ...)
+ end
+ end
+
+ if cfg.verbose then
+ verb_print("raw options: %s", cfg)
+ end
+
+ -------------------------------------------------------------------
+ -- If there's no chunk but there are params, interpret the first
+ -- param as a file name.
+ if not next(cfg.chunks) and next(cfg.params) then
+ local the_file = table.remove(cfg.params, 1)
+ verb_print("Param %q considered as a source file", the_file)
+ cfg.file={ the_file }
+ end
+
+ -------------------------------------------------------------------
+ -- If nothing to do, run REPL loop
+ if not next(cfg.chunks) and not cfg.interactive then
+ verb_print "Nothing to compile nor run, force interactive loop"
+ cfg.interactive=true
+ end
+
+
+ -------------------------------------------------------------------
+ -- Run if asked to, or if no --output has been given
+ -- if cfg.run==false it's been *forced* to false, don't override.
+ if not cfg.run and not cfg.output then
+ verb_print("No output file specified; I'll run the program")
+ cfg.run = true
+ end
+
+ local code = { }
+
+ -------------------------------------------------------------------
+ -- Get ASTs from sources
+
+ local last_file_idx
+ for i, x in ipairs(cfg.chunks) do
+ local compiler = mlc.new()
+ local tag, val = x.tag, x[1]
+ verb_print("Compiling %s", x)
+ local st, ast
+ if tag=='library' then
+ ast = { tag='Call',
+ {tag='Id', "require" },
+ {tag='String', val } }
+ elseif tag=='literal' then ast = compiler :src_to_ast(val)
+ elseif tag=='file' then
+ ast = compiler :srcfile_to_ast(val)
+ -- Isolate each file in a separate fenv
+ ast = { tag='Call',
+ { tag='Function', { { tag='Dots'} }, ast },
+ { tag='Dots' } }
+ ast.source = '@'..val
+ code.source = '@'..val
+ last_file_idx = i
+ else
+ error ("Bad option "..tag)
+ end
+ local valid = true -- TODO: check AST's correctness
+ if not valid then
+ pp.printf ("Cannot compile %s:\n%s", x, ast or "no msg")
+ os.exit (AST_COMPILE_ERROR_NUMBER)
+ end
+ ast.origin = x
+ table.insert(code, ast)
+ end
+ -- The last file returns the whole chunk's result
+ if last_file_idx then
+ -- transform +{ (function(...) -{ast} end)(...) }
+ -- into +{ return (function(...) -{ast} end)(...) }
+ local prv_ast = code[last_file_idx]
+ local new_ast = { tag='Return', prv_ast }
+ code[last_file_idx] = new_ast
+ end
+
+ -- Further uses of compiler won't involve AST transformations:
+ -- they can share the same instance.
+ -- TODO: reuse last instance if possible.
+ local compiler = mlc.new()
+
+ -------------------------------------------------------------------
+ -- AST printing
+ if cfg['print-ast'] or cfg['print-ast-lineinfo'] then
+ verb_print "Resulting AST:"
+ for _, x in ipairs(code) do
+ pp.printf("--- AST From %s: ---", x.source)
+ if x.origin and x.origin.tag=='File' then x=x[1][1][2][1] end
+ local pp_cfg = cfg['print-ast-lineinfo']
+ and { line_max=1, fix_indent=1, metalua_tag=1 }
+ or { line_max=1, metalua_tag=1, hide_hash=1 }
+ pp.print(x, 80, pp_cfg)
+ end
+ end
+
+ -------------------------------------------------------------------
+ -- Source printing
+ if cfg['print-src'] then
+ verb_print "Resulting sources:"
+ for _, x in ipairs(code) do
+ printf("--- Source From %s: ---", table.tostring(x.source, 'nohash'))
+ if x.origin and x.origin.tag=='File' then x=x[1][1][2] end
+ print (compiler :ast2string (x))
+ end
+ end
+
+ -- TODO: canonize/check AST
+
+ local bytecode = compiler :ast_to_bytecode (code)
+ code = nil
+
+ -------------------------------------------------------------------
+ -- Insert #!... command
+ if cfg.sharpbang then
+ local shbang = cfg.sharpbang
+ verb_print ("Adding sharp-bang directive %q", shbang)
+ if not shbang :match'^#!' then shbang = '#!' .. shbang end
+ if not shbang :match'\n$' then shbang = shbang .. '\n' end
+ bytecode = shbang .. bytecode
+ end
+
+ -------------------------------------------------------------------
+ -- Save to file
+ if cfg.output then
+ -- FIXME: handle '-'
+ verb_print ("Saving to file %q", cfg.output)
+ local file, err_msg = io.open(cfg.output, 'wb')
+ if not file then error("can't open output file: "..err_msg) end
+ file:write(bytecode)
+ file:close()
+ if cfg.sharpbang and os.getenv "OS" ~= "Windows_NT" then
+ pcall(os.execute, 'chmod a+x "'..cfg.output..'"')
+ end
+ end
+
+ -------------------------------------------------------------------
+ -- Run compiled code
+ if cfg.run then
+ verb_print "Running"
+ local f = compiler :bytecode_to_function (bytecode)
+ bytecode = nil
+ -- FIXME: isolate execution in a ring
+ -- FIXME: check for failures
+ local function print_traceback (errmsg)
+ return errmsg .. '\n' .. debug.traceback ('',2) .. '\n'
+ end
+ local function g() return f(unpack (cfg.params)) end
+ local st, msg = xpcall(g, print_traceback)
+ if not st then
+ io.stderr:write(msg)
+ os.exit(RUNTIME_ERROR_NUMBER)
+ end
+ end
+
+ -------------------------------------------------------------------
+ -- Run REPL loop
+ if cfg.interactive then
+ verb_print "Starting REPL loop"
+ require 'metalua.repl' .run()
+ end
+
+ verb_print "Done"
+
+end
+
+return M.main(...)