]> git.lizzy.rs Git - metalua.git/blob - src/compiler/mlc.mlua
Merge remote branch 'origin/master'
[metalua.git] / src / compiler / mlc.mlua
1 --*-lua-*-----------------------------------------------------------------------
2 -- This module is written in a more hackish way than necessary, just
3 -- because I can.  Its core feature is to dynamically generate a
4 -- function that converts from a source format to a destination
5 -- format; these formats are the various ways to represent a piece of
6 -- program, from the source file to the executable function. Legal
7 -- formats are:
8 --
9 -- * luafile:    the name of a file containing sources.
10 -- * luastring:  these sources as a single string.
11 -- * lexstream:  a stream of lexemes.
12 -- * ast:        an abstract syntax tree.
13 -- * proto:      a (Yueliang) struture containing a high level 
14 --               representation of bytecode. Largely based on the 
15 --               Proto structure in Lua's VM.
16 -- * luacstring: a string dump of the function, as taken by 
17 --               loadstring() and produced by string.dump().
18 -- * function:   an executable lua function in RAM.
19 --
20 --------------------------------------------------------------------------------
21
22 require 'metalua.bytecode'
23 require 'metalua.mlp'
24
25 mlc = { }
26 setmetatable(mlc, mlc)
27 mlc.metabugs = false
28
29 --------------------------------------------------------------------------------
30 -- Order of the transformations. if 'a' is on the left of 'b', then a 'a' can
31 -- be transformed into a 'b' (but not the other way around). Since the table
32 -- is transposed, the test is 'if mlc.order.a > mlc.order.b then error(...) end'
33 --------------------------------------------------------------------------------
34 mlc.order = table.transpose{ 
35    'luafile',  'luastring', 'lexstream', 'ast', 'proto', 
36    'luacstring', 'function' }
37
38 --------------------------------------------------------------------------------
39 -- The macro 'POINT(point_name, expected_type)' creates an entry point in the
40 -- 'mlc.convert' function. When we convert a 'a' into a 'b', FIXME
41 --------------------------------------------------------------------------------
42 -{ block:
43    jump_to_point = `If{ }
44    function point_builder(args)
45       local name, point_type, code = unpack(args)
46       table.insert(jump_to_point, +{src_fmt == -{name}}) -- if source format is 'name'
47       table.insert(jump_to_point, { `Goto{name} })       -- then jump to label  'name'
48       return {
49          ---------------------------------------------------
50          -- Stop if this is the destination format
51          ---------------------------------------------------
52          +{stat: if dst_fmt == -{name} then return x end },
53          ---------------------------------------------------
54          -- Start here if the source format matches
55          ---------------------------------------------------
56          `Label{ name },
57          -- +{print(" *** point "..-{name})}, -- debug trace
58          ---------------------------------------------------
59          -- Check that the type matches
60          ---------------------------------------------------
61          +{stat: assert (-{point_type} == type(x), "Invalid source type") },
62          -- perform transformation operations to the next type
63          }
64    end
65    mlp.lexer:add 'POINT'
66    mlp.stat:add{ 'POINT', mlp.string, ',', mlp.string, builder = point_builder }
67 } -- end of meta-block
68
69 function mlc.convert (x, src_fmt, dst_fmt, name)
70    -- printf(" *** Convert a %s into a %s", src_fmt, dst_fmt)
71
72    -{ jump_to_point }
73
74    error "Can't perform this conversion (bad src name)"
75
76    POINT 'luafile', 'string' -- x is the src file's name
77
78    if not name then name = '@'..x end
79    local f, msg = io.open(x, "rb")
80    if not f then error("While trying to open file '"..x.."': "..msg) end
81    x = f:read'*a'
82    f:close()
83    
84    POINT 'luastring', 'string' -- x is the source
85
86    x = mlp.lexer:newstream(x, name)      
87    
88    POINT 'lexstream', 'table' -- x is the lexeme stream
89
90    local status -- status = compilation success
91    local lx=x
92    if mlc.metabugs
93    -- If metabugs is true, errors should be attributed to a parser bug.
94    then status, x = true, mlp.chunk (lx)
95    -- If metabugs is false, errors should be attributed to an invalid entry.
96    else status, x = pcall (mlp.chunk, lx) end
97    -- FIXME: this test seems wrong ??? Or is it the message?
98    if status and lx:peek().tag ~= "Eof"
99    then status, x = false, "Premature Eof" 
100    elseif status and lx:peek().tag == "End"
101    then status, x = false, "Unexpected 'end' keyword" end
102    if not status and x then 
103       -- x = error msg; get rid of ???
104       x = x:strmatch "[^:]+:[0-9]+: (.*)" or x
105       local li = lx:lineinfo_left()
106       error (string.format (
107          "Parsing error in %s line %s, column %i, char %s: \n%s",
108          name or "<nofilename>", li[1], li[2], li[3], x), 2)
109       return nil
110    end
111    
112    if x then x.source = name end -- TODO [EVE] store debug info in the special part of ast
113
114    POINT 'ast', 'table' -- x is the AST
115    x = bytecode.metalua_compile(x, name or x.source) 
116    POINT 'proto', 'table' 
117    x = bytecode.dump_string (x)
118    POINT 'luacstring', 'string' -- normally x is a bytecode dump
119    x = string.undump(x, name)
120    POINT 'function', 'function' 
121    error "Can't perform this conversion (bad dst name)"
122 end
123
124 --------------------------------------------------------------------------------
125 -- Dynamically compose a conversion function from a function name
126 -- xxx_of_yyy() or yyy_to_xxx().
127 --------------------------------------------------------------------------------
128 function mlc.__index(_, name)   
129    local  dst, src = name:strmatch '^([a-z]+)_of_([a-z]+)$'
130    if not dst then src, dst = name:strmatch '^([a-z]+)_to_([a-z]+)$' end
131    if not (src and dst) then return nil end -- not a converter
132    local  osrc, odst = mlc.order[src], mlc.order[dst] -- check existence of formats
133    if not osrc then error ("unknown source format "..src) end
134    if not odst then error ("unknown destination format "..src) end
135    if osrc > odst then error "Can't convert in this direction" end
136    return |x, name| mlc.convert(x, src, dst, name) 
137 end
138
139 --------------------------------------------------------------------------------
140 -- This case isn't handled by the __index method, as it goes "in the wrong direction"
141 --------------------------------------------------------------------------------
142 mlc.function_to_luacstring = string.dump
143 mlc.luacstring_of_function = string.dump
144
145 --------------------------------------------------------------------------------
146 -- These are drop-in replacement for loadfile() and loadstring(). The
147 -- C functions will call them instead of the original versions if
148 -- they're referenced in the registry.
149 --------------------------------------------------------------------------------
150
151 lua_loadstring = loadstring
152 local lua_loadstring = loadstring
153 lua_loadfile = loadfile
154 local lua_loadfile = loadfile
155
156 function loadstring(str, name)
157    if type(str) ~= 'string' then error 'string expected' end
158    if str:match '^\027LuaQ' then return lua_loadstring(str) end
159    local n = str:match '^#![^\n]*\n()'
160    if n then str=str:sub(n, -1) end
161    -- FIXME: handle erroneous returns (return nil + error msg)
162    local success, f = pcall (mlc.function_of_luastring, str, name)
163    if success then return f else return nil, f end
164 end
165
166 function loadfile(filename)
167    local f, err_msg = io.open(filename, 'rb')
168    if not f then return nil, err_msg end
169    local success, src = pcall( f.read, f, '*a')
170    pcall(f.close, f)
171    if success then return loadstring (src, '@'..filename)
172    else return nil, src end
173 end
174
175 function load(f, name)
176    while true do
177       local x = f()
178       if not x then break end
179       assert(type(x)=='string', "function passed to load() must return strings")
180       table.insert(acc, x)
181    end
182    return loadstring(table.concat(x))
183 end
184
185 function dostring(src)
186    local f, msg = loadstring(src)
187    if not f then error(msg) end
188    return f()
189 end
190
191 function dofile(name)
192    local f, msg = loadfile(name)
193    if not f then error(msg) end
194    return f()
195 end