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
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.
20 --------------------------------------------------------------------------------
22 require 'metalua.bytecode'
26 setmetatable(mlc, mlc)
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' }
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 --------------------------------------------------------------------------------
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'
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 ---------------------------------------------------
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
66 mlp.stat:add{ 'POINT', mlp.string, ',', mlp.string, builder = point_builder }
67 } -- end of meta-block
69 function mlc.convert (x, src_fmt, dst_fmt, name)
70 -- printf(" *** Convert a %s into a %s", src_fmt, dst_fmt)
74 error "Can't perform this conversion (bad src name)"
76 POINT 'luafile', 'string' -- x is the src file's name
78 if not name then name = '@'..x end
79 local f, msg = io.open(x, "rb")
80 if not f then error(msg) end
84 POINT 'luastring', 'string' -- x is the source
86 x = mlp.lexer:newstream(x, name)
88 POINT 'lexstream', 'table' -- x is the lexeme stream
90 local status -- status = compilation success
93 -- If SHOW_METABUGS is true, errors should be attributed to a parser bug.
94 then status, x = true, mlp.chunk (lx)
95 -- If SHOW_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("Parsing error in %s line %s, column %i, char %s: \n%s",
107 name or "<nofilename>", li[1], li[2], li[3], x))
111 if x then x.source = name end -- TODO [EVE] store debug info in the special part of ast
113 POINT 'ast', 'table' -- x is the AST
114 x = bytecode.metalua_compile(x, name or x.source)
115 POINT 'proto', 'table'
116 x = bytecode.dump_string (x)
117 POINT 'luacstring', 'string' -- normally x is a bytecode dump
118 x = string.undump(x, name)
119 POINT 'function', 'function'
120 error "Can't perform this conversion (bad dst name)"
123 --------------------------------------------------------------------------------
124 -- Dynamically compose a conversion function from a function name
125 -- xxx_of_yyy() or yyy_to_xxx().
126 --------------------------------------------------------------------------------
127 function mlc.__index(_, name)
128 local dst, src = name:strmatch '^([a-z]+)_of_([a-z]+)$'
129 if not dst then src, dst = name:strmatch '^([a-z]+)_to_([a-z]+)$' end
130 if not (src and dst) then return nil end -- not a converter
131 local osrc, odst = mlc.order[src], mlc.order[dst] -- check existence of formats
132 if not osrc then error ("unknown source format "..src) end
133 if not odst then error ("unknown destination format "..src) end
134 if osrc > odst then error "Can't convert in this direction" end
135 return |x, name| mlc.convert(x, src, dst, name)
138 --------------------------------------------------------------------------------
139 -- This case isn't handled by the __index method, as it goes "in the wrong direction"
140 --------------------------------------------------------------------------------
141 mlc.function_to_luacstring = string.dump
142 mlc.luacstring_of_function = string.dump
144 --------------------------------------------------------------------------------
145 -- These are drop-in replacement for loadfile() and loadstring(). The
146 -- C functions will call them instead of the original versions if
147 -- they're referenced in the registry.
148 --------------------------------------------------------------------------------
150 lua_loadstring = loadstring
151 local lua_loadstring = loadstring
153 function loadstring(str, name)
154 if type(str) ~= 'string' then error 'string expected' end
155 if str:match '^\027LuaQ' then return lua_loadstring(str) end
156 local n = str:match '^#![^\n]*\n()'
157 if n then str=str:sub(n, -1) end
158 -- FIXME: handle erroneous returns (return nil + error msg)
159 local success, f = pcall (mlc.function_of_luastring, str, name)
160 if success then return f else return nil, f end
163 function loadfile(filename)
164 local f, err_msg = io.open(filename, 'rb')
165 if not f then return nil, err_msg end
166 local success, src = pcall( f.read, f, '*a')
168 if success then return loadstring (src, '@'..filename)
169 else return nil, src end
172 function load(f, name)
175 if not x then break end
176 assert(type(x)=='string', "function passed to load() must return strings")
179 return loadstring(table.concat(x))
182 function dostring(src)
183 local f, msg = loadstring(src)
184 if not f then error(msg) end
188 function dofile(name)
189 local f, msg = loadfile(name)
190 if not f then error(msg) end