]> git.lizzy.rs Git - metalua.git/blob - src/compiler/metalua.mlua
minor variable renamings
[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 'serialize'
8
9 AST_COMPILE_ERROR_NUMBER        = -1
10 RUNTIME_ERROR_NUMBER            = -3
11 BYTECODE_SYNTHESE_ERROR_NUMBER  = -100
12
13 -{ extension 'match' }
14
15 function spring_pcall (f, arg, name)
16    local pattern = 
17       [=[lua -l metalua.compiler -l serialize -e ]=]..
18       [=["print (serialize (%s ([[%s]], [[%s]])))"]=]
19    local cmd = string.format (pattern, f, arg, name)
20    --print ("Running the following process: " .. cmd)
21    local fd = io.popen (cmd)
22    local ast_src = fd:read '*a'
23    fd:close()
24    --print (data)
25    local ast_builder, msg = lua_loadstring(ast_src)
26    if not ast_builder then 
27       error ("can't compile data: "..msg) 
28       print (ast_src)
29    end
30    local ast = ast_builder()
31    return true, ast
32 end
33
34 local chunks  = { }
35 local runargs = { }
36
37 local acc_chunk = |kind| |arg| table.insert (chunks, { tag=kind, arg })
38
39 parser = clopts {
40    -- Chunk loading
41    {  short = 'f', long = 'file', type = 'string', action = acc_chunk 'File',
42       usage = 'load a file to compile and/or run'
43    },
44    {  short = 'l', long = 'library', type = 'string', action = acc_chunk 'Library',
45       usage = 'load a libary from the standard paths'
46    },
47    {  short = 'e', long = 'literal', type = 'string', action = acc_chunk 'Literal',
48       usage = 'load a literal piece of source code'
49    },
50    -- What to do with chunks
51    {  short = 'o', long = 'output', type = 'string',
52       usage = 'set the target name of the next compiled file'
53    },
54    {  short = 'x', long = 'run', type = 'boolean',
55       usage = 'execute the compiled file instead of saving it (unless -o is also used)'
56    },
57    {  short = 'i', long = 'interactive', type = 'boolean',
58       usage = 'run an interactive loop after having run other files'
59    },
60    -- Advanced stuff
61    {  short = 'v', long = 'verbose', type = 'boolean',
62       usage = 'verbose mode'
63    },
64    {  short = 'a', long = 'print-ast',  type = 'boolean',
65       usage = 'print the AST resulting from file compilation'
66    },
67    {  short = 'A', long = 'print-ast-lineinfo',  type = 'boolean',
68       usage = 'print the AST resulting from file compilation, including lineinfo data'
69    },
70    {  short = 'b', long = 'metabugs', type = 'boolean',
71       usage = 'show syntax errors as compile-time execution errors'
72    },
73    {  short = 's', long = 'sharpbang', type = 'string',
74       usage = 'set a first line to add to compiled file, typically "#!/bin/env mlr"'
75    },
76    {  long  = 'no-runtime', type = 'boolean',
77       usage = "prevent the automatic requirement of metalua runtime"
78    },
79    {  long  = '', short = 'p', type = '*',
80       action= function (newargs) runargs=table.icat(runargs, newargs) end,
81       usage = "pass all remaining arguments to the program"
82    },
83 usage=[[
84
85 Compile and/or execute metalua programs. Parameters passed to the
86 compiler should be prefixed with an option flag, hinting what must be
87 done with them: take tham as file names to compile, as library names
88 to load, as parameters passed to the running program... When option
89 flags lack, metalua tries to adopt a "Do What I Mean" approach:
90
91 - if no code (no library, no literal expression and no file) is
92   specified, the first flag-less parameter is taken as a file name to
93   load.
94
95 - if no code and no parameter is passed, an interactive loop is
96   started.
97
98 - if a target file is specified with --output, the program is not
99   executed by default, unless a --run flag forces it to. Conversely,
100   if no --output target is specified, the code is run unless ++run
101   forbids it.
102 ]]}
103
104 local function main (...)
105
106    local cfg = parser(...)
107
108    -------------------------------------------------------------------
109    -- Print messages if in verbose mode
110    -------------------------------------------------------------------
111    local function verb_print (fmt, ...)
112       if cfg.verbose then
113          return printf ("[ "..fmt.." ]", ...)
114       end
115    end
116
117    if cfg.verbose then
118       verb_print("raw options: %s", table.tostring(cfg))
119    end
120
121    -------------------------------------------------------------------
122    -- If there's no chunk but there are params, interpret the first
123    -- param as a file name.
124    if #chunks==0 and cfg.params then
125       local the_file = table.remove(cfg.params, 1)
126       verb_print("Param %q considered as a source file", the_file)
127       chunks = { `File{ the_file } }
128    end
129
130    -------------------------------------------------------------------
131    -- If nothing to do, run REPL loop
132    if #chunks==0 and cfg.interactive==nil then
133       verb_print "Nothing to compile nor run, force interactive loop"
134       cfg.interactive=true
135    end
136
137
138    -------------------------------------------------------------------
139    -- Run if asked to, or if no --output has been given
140    -- if cfg.run==false it's been *forced* to false, don't override.
141    if cfg.run==nil and not cfg.output then
142       verb_print("No output file specified; I'll run the program")
143       cfg.run = true
144    end
145
146    local code = { }
147
148    -------------------------------------------------------------------
149    -- Get ASTs from sources
150    local last_file
151    for x in values(chunks) do
152       verb_print("Compiling %s", table.tostring(x))
153       local st, ast
154       match x with
155       | `Library{ l } -> st, ast = true, `Call{ `Id 'require', `String{ l } }
156       | `Literal{ e } -> st, ast = spring_pcall('mlc.ast_of_luastring', e, 'literal')
157       | `File{ f } ->
158          st, ast = spring_pcall('mlc.ast_of_luafile', f, '@'..f)
159          -- Isolate each file in a separate fenv
160          if st then
161             ast = +{ function (...) -{ast} end (...)  }
162             ast.source  = '@'..f -- TODO [EVE]
163             code.source = '@'..f -- TODO [EVE]
164             last_file = ast
165          end
166       end
167       if not st then
168          printf ("Cannot compile %s: %s", table.tostring(x), ast or "no msg")
169          os.exit (AST_COMPILE_ERROR_NUMBER)
170       end
171       ast.origin = x
172       table.insert(code, ast)
173    end
174    -- The last file returns the whole chunk's result
175    if last_file then
176       local c = table.shallow_copy(last_file)
177       last_file <- `Return{ source = c.source, c }
178    end
179
180    -------------------------------------------------------------------
181    -- AST printing
182    if cfg['print-ast'] or cfg['print-ast-lineinfo'] then
183       verb_print "Resulting AST:"
184       for x in ivalues(code) do
185          printf("--- AST From %s: ---", table.tostring(x.source, 'nohash'))
186          if x.origin and x.origin.tag=='File' then x=x[1][1][2][1] end
187          if cfg['print-ast-lineinfo'] then table.print(x, 80, "indent1")
188          else table.print(x, 80, 'nohash') end
189       end
190    end
191
192    -------------------------------------------------------------------
193    -- Insert runtime loader
194    if cfg['no-runtime'] then
195       verb_print "Prevent insertion of command \"require 'metalua.runtime'\""
196    else
197       table.insert(code, 1, +{require'metalua.runtime'})
198    end
199
200    -- FIXME: check for failures
201    mlc.metabugs = cfg.metabugs
202    local bytecode = mlc.luacstring_of_ast (code)
203    code = nil
204
205    -------------------------------------------------------------------
206    -- Insert #!... command
207    if cfg.sharpbang then
208       verb_print ("Adding sharp-bang directive %q", cfg.sharpbang)
209       if not cfg.sharpbang:strmatch'^#!' then cfg.sharpbang='#!'..cfg.sharpbang end
210       if not cfg.sharpbang:strmatch'\n$' then cfg.sharpbang=cfg.sharpbang..'\n' end
211       bytecode = cfg.sharpbang..bytecode
212    end
213
214    -------------------------------------------------------------------
215    -- Save to file
216    if cfg.output then
217       -- FIXME: handle '-'
218       verb_print ("Saving to file %q", cfg.output)
219       local file, err_msg = io.open(cfg.output, 'wb')
220       if not file then error("can't open output file: "..err_msg) end
221       file:write(bytecode)
222       file:close()
223       if cfg.sharpbang and os.getenv "OS" ~= "Windows_NT" then
224          pcall(os.execute, 'chmod a+x "'..cfg.output..'"')
225       end
226    end
227
228    -------------------------------------------------------------------
229    -- Run compiled code
230    if cfg.run then
231       verb_print "Running"
232       local f = mlc.function_of_luacstring (bytecode)
233       bytecode = nil
234       -- FIXME: isolate execution in a ring
235       -- FIXME: check for failures
236
237       runargs = table.icat(cfg.params or { }, runargs)
238       local function print_traceback (errmsg)
239          return errmsg .. '\n' .. debug.traceback ('',2) .. '\n'
240       end
241       local st, msg = xpcall(|| f(unpack (runargs)), print_traceback)
242       if not st then
243          io.stderr:write(msg)
244          os.exit(RUNTIME_ERROR_NUMBER)
245       end
246    end
247
248    -------------------------------------------------------------------
249    -- Run REPL loop
250    if cfg.interactive then
251       verb_print "Starting REPL loop"
252       require 'metalua.metaloop'
253       metaloop.run()
254    end
255
256    verb_print "Done"
257
258 end
259
260 main(...)