| ------------------------------------------------------------------------------- |
| -- 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 |
| -- |
| ------------------------------------------------------------------------------- |
| |
| --require 'verbose_require' |
| |
| require 'metalua.compiler' |
| require 'metalua.clopts' |
| require 'metalua.mlc_xcall' |
| |
| AST_COMPILE_ERROR_NUMBER = -1 |
| RUNTIME_ERROR_NUMBER = -3 |
| BYTECODE_SYNTHESE_ERROR_NUMBER = -100 |
| |
| -{ extension 'match' } |
| |
| local chunks = { } |
| local runargs = { } |
| |
| local acc_chunk = |kind| |arg| table.insert (chunks, { tag=kind, arg }) |
| |
| parser = clopts { |
| -- Chunk loading |
| { short = 'f', long = 'file', type = 'string', action = acc_chunk 'File', |
| usage = 'load a file to compile and/or run' |
| }, |
| { short = 'l', long = 'library', type = 'string', action = acc_chunk 'Library', |
| usage = 'load a libary from the standard paths' |
| }, |
| { short = 'e', long = 'literal', type = 'string', action = acc_chunk 'Literal', |
| usage = 'load a literal piece of source code' |
| }, |
| -- What to do with chunks |
| { short = 'o', long = 'output', type = 'string', |
| usage = 'set the target name of the next compiled file' |
| }, |
| { short = 'x', long = 'run', type = 'boolean', |
| usage = 'execute the compiled file instead of saving it (unless -o is also used)' |
| }, |
| { short = 'i', long = 'interactive', type = 'boolean', |
| usage = 'run an interactive loop after having run other files' |
| }, |
| -- Advanced stuff |
| { short = 'v', long = 'verbose', type = 'boolean', |
| usage = 'verbose mode' |
| }, |
| { short = 'a', long = 'print-ast', type = 'boolean', |
| usage = 'print the AST resulting from file compilation' |
| }, |
| { short = 'A', long = 'print-ast-lineinfo', type = 'boolean', |
| usage = 'print the AST resulting from file compilation, including lineinfo data' |
| }, |
| { short = 'S', long = 'print-src', type = 'boolean', |
| usage = 'print the AST resulting from file compilation, as re-gerenerated sources' |
| }, |
| { short = 'b', long = 'metabugs', type = 'boolean', |
| usage = 'show syntax errors as compile-time execution errors' |
| }, |
| { short = 's', long = 'sharpbang', type = 'string', |
| usage = 'set a first line to add to compiled file, typically "#!/bin/env mlr"' |
| }, |
| { long = 'no-runtime', type = 'boolean', |
| usage = "prevent the automatic requirement of metalua runtime" |
| }, |
| { long = '', short = 'p', type = '*', |
| action= function (newargs) runargs=table.icat(runargs, newargs) end, |
| usage = "pass all remaining arguments to the program" |
| }, |
| 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. |
| ]]} |
| |
| local function main (...) |
| |
| local cfg = parser(...) |
| |
| ------------------------------------------------------------------- |
| -- Print messages if in verbose mode |
| ------------------------------------------------------------------- |
| local function verb_print (fmt, ...) |
| if cfg.verbose then |
| return printf ("[ "..fmt.." ]", ...) |
| end |
| end |
| |
| if cfg.verbose then |
| verb_print("raw options: %s", table.tostring(cfg)) |
| end |
| |
| ------------------------------------------------------------------- |
| -- If there's no chunk but there are params, interpret the first |
| -- param as a file name. |
| if #chunks==0 and cfg.params then |
| local the_file = table.remove(cfg.params, 1) |
| verb_print("Param %q considered as a source file", the_file) |
| chunks = { `File{ the_file } } |
| end |
| |
| ------------------------------------------------------------------- |
| -- If nothing to do, run REPL loop |
| if #chunks==0 and cfg.interactive==nil 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 cfg.run==nil 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 |
| mlc.metabugs = cfg.metabugs |
| local last_file |
| for x in values(chunks) do |
| verb_print("Compiling %s", table.tostring(x)) |
| local st, ast |
| match x with |
| | `Library{ l } -> st, ast = true, `Call{ `Id 'require', `String{ l } } |
| | `Literal{ e } -> st, ast = mlc_xcall.client_literal (e) |
| | `File{ f } -> |
| st, ast = mlc_xcall.client_file (f) |
| -- Isolate each file in a separate fenv |
| if st then |
| ast = +{ function (...) -{ast} end (...) } |
| ast.source = '@'..f -- TODO [EVE] |
| code.source = '@'..f -- TODO [EVE] |
| last_file = ast |
| end |
| end |
| if not st then |
| printf ("Cannot compile %s:\n%s", table.tostring(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 then |
| local c = table.shallow_copy(last_file) |
| last_file <- `Return{ source = c.source, c } |
| end |
| |
| ------------------------------------------------------------------- |
| -- AST printing |
| if cfg['print-ast'] or cfg['print-ast-lineinfo'] then |
| verb_print "Resulting AST:" |
| for x in ivalues(code) do |
| printf("--- AST From %s: ---", table.tostring(x.source, 'nohash')) |
| if x.origin and x.origin.tag=='File' then x=x[1][1][2][1] end |
| if cfg['print-ast-lineinfo'] then table.print(x, 80, "indent1") |
| else table.print(x, 80, 'nohash') end |
| end |
| end |
| |
| ------------------------------------------------------------------- |
| -- Source printing |
| if cfg['print-src'] then |
| verb_print "Resulting sources:" |
| require 'metalua.ast_to_string' |
| for x in ivalues(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][1] end |
| print (ast_to_string (x)) |
| end |
| end |
| |
| -- FIXME: canonize/check AST |
| |
| ------------------------------------------------------------------- |
| -- Insert runtime loader |
| if cfg['no-runtime'] then |
| verb_print "Prevent insertion of command \"require 'metalua.runtime'\"" |
| else |
| table.insert(code, 1, +{require'metalua.runtime'}) |
| end |
| |
| local bytecode = mlc.ast_to_luacstring (code) |
| code = nil |
| |
| ------------------------------------------------------------------- |
| -- Insert #!... command |
| if cfg.sharpbang then |
| local shbang = cfg.sharpbang |
| verb_print ("Adding sharp-bang directive %q", shbang) |
| if not shbang :strmatch'^#!' then shbang = '#!' .. shbang end |
| if not shbang :strmatch'\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 = mlc.luacstring_to_function (bytecode) |
| bytecode = nil |
| -- FIXME: isolate execution in a ring |
| -- FIXME: check for failures |
| |
| runargs = table.icat(cfg.params or { }, runargs) |
| local function print_traceback (errmsg) |
| return errmsg .. '\n' .. debug.traceback ('',2) .. '\n' |
| end |
| local st, msg = xpcall(|| f(unpack (runargs)), 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.metaloop' |
| metaloop.run() |
| end |
| |
| verb_print "Done" |
| |
| end |
| |
| main(...) |