1 --*-lua-*- Set as a metalua file because it requires some metalua libs
3 --require 'verbose_require'
5 require 'metalua.compiler'
6 require 'metalua.clopts'
7 require 'metalua.mlc_xcall'
9 AST_COMPILE_ERROR_NUMBER = -1
10 RUNTIME_ERROR_NUMBER = -3
11 BYTECODE_SYNTHESE_ERROR_NUMBER = -100
13 -{ extension 'match' }
18 local acc_chunk = |kind| |arg| table.insert (chunks, { tag=kind, arg })
22 { short = 'f', long = 'file', type = 'string', action = acc_chunk 'File',
23 usage = 'load a file to compile and/or run'
25 { short = 'l', long = 'library', type = 'string', action = acc_chunk 'Library',
26 usage = 'load a libary from the standard paths'
28 { short = 'e', long = 'literal', type = 'string', action = acc_chunk 'Literal',
29 usage = 'load a literal piece of source code'
31 -- What to do with chunks
32 { short = 'o', long = 'output', type = 'string',
33 usage = 'set the target name of the next compiled file'
35 { short = 'x', long = 'run', type = 'boolean',
36 usage = 'execute the compiled file instead of saving it (unless -o is also used)'
38 { short = 'i', long = 'interactive', type = 'boolean',
39 usage = 'run an interactive loop after having run other files'
42 { short = 'v', long = 'verbose', type = 'boolean',
43 usage = 'verbose mode'
45 { short = 'a', long = 'print-ast', type = 'boolean',
46 usage = 'print the AST resulting from file compilation'
48 { short = 'A', long = 'print-ast-lineinfo', type = 'boolean',
49 usage = 'print the AST resulting from file compilation, including lineinfo data'
51 { short = 'S', long = 'print-src', type = 'boolean',
52 usage = 'print the AST resulting from file compilation, as re-gerenerated sources'
54 { short = 'b', long = 'metabugs', type = 'boolean',
55 usage = 'show syntax errors as compile-time execution errors'
57 { short = 's', long = 'sharpbang', type = 'string',
58 usage = 'set a first line to add to compiled file, typically "#!/bin/env mlr"'
60 { long = 'no-runtime', type = 'boolean',
61 usage = "prevent the automatic requirement of metalua runtime"
63 { long = '', short = 'p', type = '*',
64 action= function (newargs) runargs=table.icat(runargs, newargs) end,
65 usage = "pass all remaining arguments to the program"
69 Compile and/or execute metalua programs. Parameters passed to the
70 compiler should be prefixed with an option flag, hinting what must be
71 done with them: take tham as file names to compile, as library names
72 to load, as parameters passed to the running program... When option
73 flags are absent, metalua tries to adopt a "Do What I Mean" approach:
75 - if no code (no library, no literal expression and no file) is
76 specified, the first flag-less parameter is taken as a file name to
79 - if no code and no parameter is passed, an interactive loop is
82 - if a target file is specified with --output, the program is not
83 executed by default, unless a --run flag forces it to. Conversely,
84 if no --output target is specified, the code is run unless ++run
88 local function main (...)
90 local cfg = parser(...)
92 -------------------------------------------------------------------
93 -- Print messages if in verbose mode
94 -------------------------------------------------------------------
95 local function verb_print (fmt, ...)
97 return printf ("[ "..fmt.." ]", ...)
102 verb_print("raw options: %s", table.tostring(cfg))
105 -------------------------------------------------------------------
106 -- If there's no chunk but there are params, interpret the first
107 -- param as a file name.
108 if #chunks==0 and cfg.params then
109 local the_file = table.remove(cfg.params, 1)
110 verb_print("Param %q considered as a source file", the_file)
111 chunks = { `File{ the_file } }
114 -------------------------------------------------------------------
115 -- If nothing to do, run REPL loop
116 if #chunks==0 and cfg.interactive==nil then
117 verb_print "Nothing to compile nor run, force interactive loop"
122 -------------------------------------------------------------------
123 -- Run if asked to, or if no --output has been given
124 -- if cfg.run==false it's been *forced* to false, don't override.
125 if cfg.run==nil and not cfg.output then
126 verb_print("No output file specified; I'll run the program")
132 -------------------------------------------------------------------
133 -- Get ASTs from sources
134 mlc.metabugs = cfg.metabugs
136 for x in values(chunks) do
137 verb_print("Compiling %s", table.tostring(x))
140 | `Library{ l } -> st, ast = true, `Call{ `Id 'require', `String{ l } }
141 | `Literal{ e } -> st, ast = mlc_xcall.client_literal (e)
143 st, ast = mlc_xcall.client_file (f)
144 -- Isolate each file in a separate fenv
146 ast = +{ function (...) -{ast} end (...) }
147 ast.source = '@'..f -- TODO [EVE]
148 code.source = '@'..f -- TODO [EVE]
153 printf ("Cannot compile %s:\n%s", table.tostring(x), ast or "no msg")
154 os.exit (AST_COMPILE_ERROR_NUMBER)
157 table.insert(code, ast)
159 -- The last file returns the whole chunk's result
161 local c = table.shallow_copy(last_file)
162 last_file <- `Return{ source = c.source, c }
165 -------------------------------------------------------------------
167 if cfg['print-ast'] or cfg['print-ast-lineinfo'] then
168 verb_print "Resulting AST:"
169 for x in ivalues(code) do
170 printf("--- AST From %s: ---", table.tostring(x.source, 'nohash'))
171 if x.origin and x.origin.tag=='File' then x=x[1][1][2][1] end
172 if cfg['print-ast-lineinfo'] then table.print(x, 80, "indent1")
173 else table.print(x, 80, 'nohash') end
177 -------------------------------------------------------------------
179 if cfg['print-src'] then
180 verb_print "Resulting sources:"
181 require 'metalua.ast_to_string'
182 for x in ivalues(code) do
183 printf("--- Source From %s: ---", table.tostring(x.source, 'nohash'))
184 if x.origin and x.origin.tag=='File' then x=x[1][1][2][1] end
185 print (ast_to_string (x))
189 -- FIXME: canonize/check AST
191 -------------------------------------------------------------------
192 -- Insert runtime loader
193 if cfg['no-runtime'] then
194 verb_print "Prevent insertion of command \"require 'metalua.runtime'\""
196 table.insert(code, 1, +{require'metalua.runtime'})
199 local bytecode = mlc.luacstring_of_ast (code)
202 -------------------------------------------------------------------
203 -- Insert #!... command
204 if cfg.sharpbang then
205 local shbang = cfg.sharpbang
206 verb_print ("Adding sharp-bang directive %q", shbang)
207 if not shbang :strmatch'^#!' then shbang = '#!' .. shbang end
208 if not shbang :strmatch'\n$' then shbang = shbang .. '\n' end
209 bytecode = shbang .. bytecode
212 -------------------------------------------------------------------
216 verb_print ("Saving to file %q", cfg.output)
217 local file, err_msg = io.open(cfg.output, 'wb')
218 if not file then error("can't open output file: "..err_msg) end
221 if cfg.sharpbang and os.getenv "OS" ~= "Windows_NT" then
222 pcall(os.execute, 'chmod a+x "'..cfg.output..'"')
226 -------------------------------------------------------------------
230 local f = mlc.function_of_luacstring (bytecode)
232 -- FIXME: isolate execution in a ring
233 -- FIXME: check for failures
235 runargs = table.icat(cfg.params or { }, runargs)
236 local function print_traceback (errmsg)
237 return errmsg .. '\n' .. debug.traceback ('',2) .. '\n'
239 local st, msg = xpcall(|| f(unpack (runargs)), print_traceback)
242 os.exit(RUNTIME_ERROR_NUMBER)
246 -------------------------------------------------------------------
248 if cfg.interactive then
249 verb_print "Starting REPL loop"
250 require 'metalua.metaloop'