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