1 --*-lua-*- Set as a metalua file because it requires some metalua libs
3 --require 'verbose_require'
5 require 'metalua.compiler'
9 local mfast = os.getenv 'LUA_NOSPRINGS'
10 if mfast=='yes' or mfast=='true' then
11 function spring_pcall(f, ...)
12 if type(f)=='string' then f = loadstring("return "..f)() end
17 function spring_pcall(...)
18 local ring = springs.new()
19 ring:dostring (INIT_COMPILATION_RING)
20 st, ast = ring:pcall(...)
27 AST_COMPILE_ERROR_NUMBER = -1
28 RUNTIME_ERROR_NUMBER = -3
29 BYTECODE_SYNTHESE_ERROR_NUMBER = -100
31 -{ extension 'match' }
36 local acc_chunk = |kind| function (arg)
37 table.insert (chunks, { tag=kind, arg })
42 { short = 'f', long = 'file', type = 'string', action = acc_chunk 'File',
43 usage = 'load a file to compile and/or run'
45 { short = 'l', long = 'library', type = 'string', action = acc_chunk 'Library',
46 usage = 'load a libary from the standard paths'
48 { short = 'e', long = 'literal', type = 'string', action = acc_chunk 'Literal',
49 usage = 'load a literal piece of source code'
51 -- What to do with chunks
52 { short = 'o', long = 'output', type = 'string',
53 usage = 'set the target name of the next compiled file'
55 { short = 'x', long = 'run', type = 'boolean',
56 usage = 'execute the compiled file instead of saving it (unless -o is also used)'
58 { short = 'i', long = 'interactive', type = 'boolean',
59 usage = 'run an interactive loop after having run other files'
62 { short = 'v', long = 'verbose', type = 'boolean',
63 usage = 'verbose mode'
65 { short = 'a', long = 'print-ast', type = 'boolean',
66 usage = 'print the AST resulting from file compilation'
68 { short = 'b', long = 'metabugs', type = 'boolean',
69 usage = 'show syntax errors as compile-time execution errors'
71 { short = 's', long = 'sharpbang', type = 'string',
72 usage = 'set a first line to add to compiled file, typically "#!/bin/env mlr"'
74 { long = 'no-runtime', type = 'boolean',
75 usage = "prevent the automatic requirement of metalua runtime"
77 { long = '', short = 'p', type = '*',
78 action= function (newargs) runargs=table.icat(runargs, newargs) end,
79 usage = "pass all remaining arguments to the program"
83 Compile and/or execute metalua programs. Parameters passed to the
84 compiler should be prefixed with an option flag, hinting what must be
85 done with them: take tham as file names to compile, as library names
86 to load, as parameters passed to the running program... When option
87 flags lack, metalua tries to adopt a "Do What I Mean" approach:
89 - if no code (no library, no literal expression and no file) is
90 specified, the first flag-less parameter is taken as a file name to
93 - if no code and no parameter is passed, an interactive loop is
96 - if a target file is specified with --output, the program is not
97 executed by default, unless a --run flag forces it to. Conversely,
98 if no --output target is specified, the code is run unless ++run
103 INIT_COMPILATION_RING = [[require 'metalua.compiler']]
105 local function main (...)
107 local cfg = parser(...)
109 -------------------------------------------------------------------
110 -- Print messages if in verbose mode
111 -------------------------------------------------------------------
112 local function verb_print (fmt, ...)
114 return printf ("[ "..fmt.." ]", ...)
118 -------------------------------------------------------------------
119 -- If there's no chunk but there are params, interpret the first
120 -- param as a file name.
121 if #chunks==0 and cfg.params then
122 local the_file = table.remove(cfg.params, 1)
123 verb_print("Param %q considered as a source file", the_file)
124 chunks = { `File{ the_file } }
127 -------------------------------------------------------------------
128 -- If nothing to do, run REPL loop
129 if #chunks==0 and cfg.interactive==nil then
130 verb_print "Nothing to compile nor run, force interactive loop"
135 -------------------------------------------------------------------
136 -- Run if asked to, or if no --output has been given
137 -- if cfg.run==false it's been *forced* to false, don't override.
138 if cfg.run==nil and not cfg.output then
139 verb_print("No output file specified; I'll run the program")
145 -------------------------------------------------------------------
146 -- Get ASTs from sources
148 for x in values(chunks) do
149 verb_print("Compiling %s", table.tostring(x))
152 | `Library{ l } -> st, ast = true, `Call{ `Id 'require', `String{ l } }
153 | `Literal{ e } -> st, ast = spring_pcall('mlc.ast_of_luastring', e, 'literal')
155 st, ast = spring_pcall('mlc.ast_of_luafile', f, '@'..f)
156 -- Isolate each file in a separate fenv
158 ast = +{ function (...) -{ast} end (...) }
159 ast.source = '@'..f -- TODO [EVE]
160 code.source = '@'..f -- TODO [EVE]
165 printf ("Cannot compile %s: %s", table.tostring(x), ast)
166 os.exit (AST_COMPILE_ERROR_NUMBER)
169 table.insert(code, ast)
171 -- The last file returns the whole chunk's result
173 local c = table.shallow_copy(last_file)
174 last_file <- `Return{ source = c.source, c }
177 -------------------------------------------------------------------
179 if cfg['print-ast'] then
180 verb_print "Resulting AST:"
181 for x in ivalues(code) do
182 printf("--- AST From %s: ---", table.tostring(x.source, 'nohash'))
183 if x.origin and x.origin.tag=='File' then x=x[1][1][2][1] end
184 table.print(x, 80, 'nohash')
188 -------------------------------------------------------------------
189 -- Insert runtime loader
190 if cfg['no-runtime'] then
191 verb_print "Prevent insertion of command \"require 'metalua.runtime'\""
193 table.insert(code, 1, +{require'metalua.runtime'})
196 -- FIXME: check for failures
197 -- FIXME: handle metabugs
198 local bytecode = mlc.luacstring_of_ast (code)
201 -------------------------------------------------------------------
202 -- Insert #!... command
203 if cfg.sharpbang then
204 verb_print ("Adding sharp-bang directive %q", cfg.sharpbang)
205 if not cfg.sharpbang:strmatch'\n$' then cfg.sharpbang=cfg.sharpbang..'\n' end
206 bytecode = cfg.sharpbang..bytecode
209 -------------------------------------------------------------------
213 verb_print ("Saving to file %q", cfg.output)
214 local file, err_msg = io.open(cfg.output, 'wb')
215 if not file then error("can't open output file: "..err_msg) end
218 if cfg.sharpbang and os.getenv "OS" ~= "Windows_NT" then
219 pcall(os.execute, 'chmod a+x "'..cfg.output..'"')
223 -------------------------------------------------------------------
227 local f = mlc.function_of_luacstring (bytecode)
229 -- FIXME: isolate execution in a ring
230 -- FIXME: check for failures
231 local st, msg = pcall(f, unpack (runargs))
234 os.exit(RUNTIME_ERROR_NUMBER)
238 -------------------------------------------------------------------
240 if cfg.interactive then
241 verb_print "Starting REPL loop"
244 -- print ("*":rep(70))
245 -- print "*** !!! Interactive loop not implemented !!!"
246 -- print ("*":rep(70))