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