]> git.lizzy.rs Git - metalua.git/blob - metalua.lua
ast_to_src: format function calls and unary operators without space
[metalua.git] / metalua.lua
1 -------------------------------------------------------------------------------
2 -- Copyright (c) 2006-2013 Fabien Fleutot and others.
3 --
4 -- All rights reserved.
5 --
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
10 --
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
14 --
15 -- Contributors:
16 --     Fabien Fleutot - API and implementation
17 --
18 -------------------------------------------------------------------------------
19
20 -- Survive lack of checks
21 if not pcall(require, 'checks') then function package.preload.checks() function checks() end end end
22
23 -- Main file for the metalua executable
24 require 'metalua.loader' -- load *.mlue files
25 require 'metalua.compiler.globals' -- metalua-aware loadstring, dofile etc.
26
27 local alt_getopt = require 'alt_getopt'
28 local pp  = require 'metalua.pprint'
29 local mlc = require 'metalua.compiler'
30
31 local M = { }
32
33 local AST_COMPILE_ERROR_NUMBER        = -1
34 local RUNTIME_ERROR_NUMBER            = -3
35
36 local alt_getopt_options = "f:l:e:o:xivaASbs"
37
38 local long_opts = {
39     file='f',
40     library='l',
41     literal='e',
42     output='o',
43     run='x',
44     interactive='i',
45     verbose='v',
46     ['print-ast']='a',
47     ['print-ast-lineinfo']='A',
48     ['print-src']='S',
49     ['meta-bugs']='b',
50     ['sharp-bang']='s',
51 }
52
53 local chunk_options = {
54     library=1,
55     file=1,
56     literal=1
57 }
58
59 local usage=[[
60
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:
66
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
69   load.
70
71 - if no code and no parameter is passed, an interactive loop is
72   started.
73
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
77   forbids it.
78 ]]
79
80 function M.cmdline_parser(...)
81     local argv = {...}
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
93     end
94     cfg.params = { select(optind, ...) }
95     return cfg
96 end
97
98 function M.main (...)
99
100    local cfg = M.cmdline_parser(...)
101
102    -------------------------------------------------------------------
103    -- Print messages if in verbose mode
104    -------------------------------------------------------------------
105    local function verb_print (fmt, ...)
106       if cfg.verbose then
107          return pp.printf ("[ "..fmt.." ]", ...)
108       end
109    end
110
111    if cfg.verbose then
112       verb_print("raw options: %s", cfg)
113    end
114
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 }
122    end
123
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"
128       cfg.interactive=true
129    end
130
131
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")
137       cfg.run = true
138    end
139
140    local code = { }
141
142    -------------------------------------------------------------------
143    -- Get ASTs from sources
144
145    local last_file_idx
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)
150       local st, ast
151       if tag=='library' then
152           ast = { tag='Call',
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
159          ast = { tag='Call',
160                  { tag='Function', { { tag='Dots'} }, ast },
161                  { tag='Dots' } }
162          ast.source  = '@'..val
163          code.source = '@'..val
164          last_file_idx = i
165       else
166           error ("Bad option "..tag)
167       end
168       local valid = true -- TODO: check AST's correctness
169       if not valid then
170          pp.printf ("Cannot compile %s:\n%s", x, ast or "no msg")
171          os.exit (AST_COMPILE_ERROR_NUMBER)
172       end
173       ast.origin = x
174       table.insert(code, ast)
175    end
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
185    end
186
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()
191
192    -------------------------------------------------------------------
193    -- AST printing
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  }
202          pp.print(x, pp_cfg)
203       end
204    end
205
206    -------------------------------------------------------------------
207    -- Source printing
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))
214       end
215    end
216
217    -- TODO: canonize/check AST
218
219    local bytecode = compiler :ast_to_bytecode (code)
220    code = nil
221
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
230    end
231
232    -------------------------------------------------------------------
233    -- Save to file
234    if cfg.output then
235       -- FIXME: handle '-'
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
239       file:write(bytecode)
240       file:close()
241       if cfg.sharpbang and os.getenv "OS" ~= "Windows_NT" then
242          pcall(os.execute, 'chmod a+x "'..cfg.output..'"')
243       end
244    end
245
246    -------------------------------------------------------------------
247    -- Run compiled code
248    if cfg.run then
249       verb_print "Running"
250       local f = compiler :bytecode_to_function (bytecode)
251       bytecode = nil
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'
256       end
257       local function g() return f(unpack (cfg.params)) end
258       local st, msg = xpcall(g, print_traceback)
259       if not st then
260          io.stderr:write(msg)
261          os.exit(RUNTIME_ERROR_NUMBER)
262       end
263    end
264
265    -------------------------------------------------------------------
266    -- Run REPL loop
267    if cfg.interactive then
268       verb_print "Starting REPL loop"
269       require 'metalua.repl' .run()
270    end
271
272    verb_print "Done"
273
274 end
275
276 return M.main(...)