]> git.lizzy.rs Git - metalua.git/blob - src/compiler/metalua.mlua
pushed in a hurry, alimentation issue on laptop
[metalua.git] / src / compiler / metalua.mlua
1 --*-lua-*- Set as a metalua file because it requires some metalua libs
2
3 --require 'verbose_require'
4
5 require 'metalua.compiler'
6 require 'metalua.clopts'
7 require 'metalua.mlc_xcall'
8
9 AST_COMPILE_ERROR_NUMBER        = -1
10 RUNTIME_ERROR_NUMBER            = -3
11 BYTECODE_SYNTHESE_ERROR_NUMBER  = -100
12
13 -{ extension 'match' }
14
15 local chunks  = { }
16 local runargs = { }
17
18 local acc_chunk = |kind| |arg| table.insert (chunks, { tag=kind, arg })
19
20 parser = clopts {
21    -- Chunk loading
22    {  short = 'f', long = 'file', type = 'string', action = acc_chunk 'File',
23       usage = 'load a file to compile and/or run'
24    },
25    {  short = 'l', long = 'library', type = 'string', action = acc_chunk 'Library',
26       usage = 'load a libary from the standard paths'
27    },
28    {  short = 'e', long = 'literal', type = 'string', action = acc_chunk 'Literal',
29       usage = 'load a literal piece of source code'
30    },
31    -- What to do with chunks
32    {  short = 'o', long = 'output', type = 'string',
33       usage = 'set the target name of the next compiled file'
34    },
35    {  short = 'x', long = 'run', type = 'boolean',
36       usage = 'execute the compiled file instead of saving it (unless -o is also used)'
37    },
38    {  short = 'i', long = 'interactive', type = 'boolean',
39       usage = 'run an interactive loop after having run other files'
40    },
41    -- Advanced stuff
42    {  short = 'v', long = 'verbose', type = 'boolean',
43       usage = 'verbose mode'
44    },
45    {  short = 'a', long = 'print-ast',  type = 'boolean',
46       usage = 'print the AST resulting from file compilation'
47    },
48    {  short = 'A', long = 'print-ast-lineinfo',  type = 'boolean',
49       usage = 'print the AST resulting from file compilation, including lineinfo data'
50    },
51    {  short = 'S', long = 'print-src',  type = 'boolean',
52       usage = 'print the AST resulting from file compilation, as re-gerenerated sources'
53    },
54    {  short = 'b', long = 'metabugs', type = 'boolean',
55       usage = 'show syntax errors as compile-time execution errors'
56    },
57    {  short = 's', long = 'sharpbang', type = 'string',
58       usage = 'set a first line to add to compiled file, typically "#!/bin/env mlr"'
59    },
60    {  long  = 'no-runtime', type = 'boolean',
61       usage = "prevent the automatic requirement of metalua runtime"
62    },
63    {  long  = '', short = 'p', type = '*',
64       action= function (newargs) runargs=table.icat(runargs, newargs) end,
65       usage = "pass all remaining arguments to the program"
66    },
67 usage=[[
68
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 lack, metalua tries to adopt a "Do What I Mean" approach:
74
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
77   load.
78
79 - if no code and no parameter is passed, an interactive loop is
80   started.
81
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
85   forbids it.
86 ]]}
87
88 local function main (...)
89
90    local cfg = parser(...)
91
92    -------------------------------------------------------------------
93    -- Print messages if in verbose mode
94    -------------------------------------------------------------------
95    local function verb_print (fmt, ...)
96       if cfg.verbose then
97          return printf ("[ "..fmt.." ]", ...)
98       end
99    end
100
101    if cfg.verbose then
102       verb_print("raw options: %s", table.tostring(cfg))
103    end
104
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 } }
112    end
113
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"
118       cfg.interactive=true
119    end
120
121
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")
127       cfg.run = true
128    end
129
130    local code = { }
131
132    -------------------------------------------------------------------
133    -- Get ASTs from sources
134    local last_file
135    for x in values(chunks) do
136       verb_print("Compiling %s", table.tostring(x))
137       local st, ast
138       match x with
139       | `Library{ l } -> st, ast = true, `Call{ `Id 'require', `String{ l } }
140       | `Literal{ e } -> st, ast = mlc_xcall.client_literal (e)
141       | `File{ f } ->
142          st, ast = mlc_xcall.client_file (f)
143           -- Isolate each file in a separate fenv
144          if st then
145             ast = +{ function (...) -{ast} end (...)  }
146             ast.source  = '@'..f -- TODO [EVE]
147             code.source = '@'..f -- TODO [EVE]
148             last_file = ast
149          end
150       end
151       if not st then
152          printf ("Cannot compile %s:\n%s", table.tostring(x), ast or "no msg")
153          os.exit (AST_COMPILE_ERROR_NUMBER)
154       end
155       ast.origin = x
156       table.insert(code, ast)
157    end
158    -- The last file returns the whole chunk's result
159    if last_file then
160       local c = table.shallow_copy(last_file)
161       last_file <- `Return{ source = c.source, c }
162    end
163
164    -------------------------------------------------------------------
165    -- AST printing
166    if cfg['print-ast'] or cfg['print-ast-lineinfo'] then
167       verb_print "Resulting AST:"
168       for x in ivalues(code) do
169          printf("--- AST From %s: ---", table.tostring(x.source, 'nohash'))
170          if x.origin and x.origin.tag=='File' then x=x[1][1][2][1] end
171          if cfg['print-ast-lineinfo'] then table.print(x, 80, "indent1")
172          else table.print(x, 80, 'nohash') end
173       end
174    end
175
176    -------------------------------------------------------------------
177    -- Source printing
178    if cfg['print-src'] then
179       verb_print "Resulting sources:"
180       require 'metalua.ast_to_string'
181       for x in ivalues(code) do
182          printf("--- Source 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          print (ast_to_string (x))
185       end
186    end
187
188    -------------------------------------------------------------------
189    -- Insert runtime loader
190    if cfg['no-runtime'] then
191       verb_print "Prevent insertion of command \"require 'metalua.runtime'\""
192    else
193       table.insert(code, 1, +{require'metalua.runtime'})
194    end
195
196    -- FIXME: check for failures
197    mlc.metabugs = cfg.metabugs
198    local bytecode = mlc.luacstring_of_ast (code)
199    code = nil
200
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'^#!' then cfg.sharpbang='#!'..cfg.sharpbang end
206       if not cfg.sharpbang:strmatch'\n$' then cfg.sharpbang=cfg.sharpbang..'\n' end
207       bytecode = cfg.sharpbang..bytecode
208    end
209
210    -------------------------------------------------------------------
211    -- Save to file
212    if cfg.output then
213       -- FIXME: handle '-'
214       verb_print ("Saving to file %q", cfg.output)
215       local file, err_msg = io.open(cfg.output, 'wb')
216       if not file then error("can't open output file: "..err_msg) end
217       file:write(bytecode)
218       file:close()
219       if cfg.sharpbang and os.getenv "OS" ~= "Windows_NT" then
220          pcall(os.execute, 'chmod a+x "'..cfg.output..'"')
221       end
222    end
223
224    -------------------------------------------------------------------
225    -- Run compiled code
226    if cfg.run then
227       verb_print "Running"
228       local f = mlc.function_of_luacstring (bytecode)
229       bytecode = nil
230       -- FIXME: isolate execution in a ring
231       -- FIXME: check for failures
232
233       runargs = table.icat(cfg.params or { }, runargs)
234       local function print_traceback (errmsg)
235          return errmsg .. '\n' .. debug.traceback ('',2) .. '\n'
236       end
237       local st, msg = xpcall(|| f(unpack (runargs)), print_traceback)
238       if not st then
239          io.stderr:write(msg)
240          os.exit(RUNTIME_ERROR_NUMBER)
241       end
242    end
243
244    -------------------------------------------------------------------
245    -- Run REPL loop
246    if cfg.interactive then
247       verb_print "Starting REPL loop"
248       require 'metalua.metaloop'
249       metaloop.run()
250    end
251
252    verb_print "Done"
253
254 end
255
256 main(...)