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