mirror of
https://github.com/g012/l65.git
synced 2024-06-02 12:41:28 +00:00
278 lines
9.0 KiB
Lua
278 lines
9.0 KiB
Lua
#! /usr/bin/env lua51
|
|
-------------------------------------------------------------------------------
|
|
-- 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 }
|
|
new_ast.source, new_ast.origin, prv_ast.source, prv_ast.origin =
|
|
prv_ast.source, prv_ast.origin, nil, nil
|
|
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, 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(...)
|