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("While trying to open file '"..x.."': "..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 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)
112 if x then x.source = name end -- TODO [EVE] store debug info in the special part of ast
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)"
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)
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
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 --------------------------------------------------------------------------------
151 lua_loadstring = loadstring
152 local lua_loadstring = loadstring
153 lua_loadfile = loadfile
154 local lua_loadfile = loadfile
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
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')
171 if success then return loadstring (src, '@'..filename)
172 else return nil, src end
175 function load(f, name)
178 if not x then break end
179 assert(type(x)=='string', "function passed to load() must return strings")
182 return loadstring(table.concat(x))
185 function dostring(src)
186 local f, msg = loadstring(src)
187 if not f then error(msg) end
191 function dofile(name)
192 local f, msg = loadfile(name)
193 if not f then error(msg) end