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 --------------------------------------------------------------------------------
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)
73 error "Can't perform this conversion (bad src name)"
75 POINT 'luafile', 'string' -- x is the src file's name
77 if not name then name = x end
78 local f, msg = io.open(x, "rb")
79 if not f then error(msg) end
83 POINT 'luastring', 'string' -- x is the source
85 x = mlp.lexer:newstream(x)
87 POINT 'lexstream', 'table' -- x is the lexeme stream
89 local status -- status = compilation success
92 -- If SHOW_METABUGS is true, errors should be attributed to a parser bug.
93 then status, x = true, mlp.chunk (lx)
94 -- If SHOW_METABUGS is false, errors should be attributed to an invalid entry.
95 else status, x = pcall (mlp.chunk, lx) end
96 -- FIXME: this test seems wrong ???
97 if status and lx:peek().tag ~= "Eof"
98 then status, x = false, "Premature Eof"
99 elseif status and lx:peek().tag == "End"
100 then status, x = false, "Unexpected 'end' keyword" end
101 if not status and x then
102 -- x = error msg; get rid of ???
103 x = x:match "[^:]+:[0-9]+: (.*)" or x
104 printf("Parsing error in %s line %s, char %s: \n%s",
105 filename or "?", lx.line, lx.i, x)
109 POINT 'ast', 'table' -- x is the AST
110 x = bytecode.metalua_compile(x)
112 POINT 'proto', 'table'
113 x = bytecode.dump_string (x)
114 POINT 'luacstring', 'string' -- normally x is a bytecode dump
115 x = string.undump(x, name)
116 POINT 'function', 'function'
117 error "Can't perform this conversion (bad dst name)"
120 -- Dynamically compose a conversion function from a function name
121 -- xxx_of_yyy() or yyy_to_xxx().
122 function mlc.__index(_, name)
123 local dst, src = name:strmatch '^([a-z]+)_of_([a-z]+)$'
124 if not dst then src, dst = name:strmatch '^([a-z]+)_to_([a-z]+)$' end
125 if not src and dst then error "Bad converter name" end
126 local osrc, odst = mlc.order[src], mlc.order[dst]
127 if not src or not dst then error ("malformed mlc function "..name) end
128 if not osrc then error ("unknown source format "..src) end
129 if not odst then error ("unknown destination format "..src) end
130 if osrc > odst then error "Can't convert in this direction" end
131 return |x, name| mlc.convert(x, src, dst, name)
134 -- This case isn't handled by the __index method, as it goes "in the wrong direction"
135 mlc.function_to_luacstring = string.dump
136 mlc.luacstring_of_function = string.dump
138 -- These are drop-in replacement for loadfile() and loadstring(). The
139 -- C functions will call them instead of the original versions if
140 -- they're referenced in the registry.
142 function loadstring(str, name)
143 if type(str) ~= 'string' then error 'string expected' end
144 if str:match '^\027LuaQ' then return string.undump(str) end
145 local n = str:match '^#![^\n]*\n()'
146 if n then str=str:sub(n, -1) end
147 -- FIXME: handle erroneous returns (return nil + error msg)
148 return mlc.function_of_luastring(str, name)
151 function loadfile(filename)
152 local f = io.open(filename, 'rb')
153 local src = f:read '*a'
155 return loadstring(src, '@'..filename)
158 function load(f, name)
161 if not x then break end
162 assert(type(x)=='string', "function passed to load() must return strings")
165 return loadstring(table.concat(x))
168 function dostring(src)
169 local f, msg = loadstring(src)
170 if not f then error(msg) end
174 function dofile(name)
175 local f, msg = loadfile(name)
176 if not f then error(msg) end
182 debug.getregistry().loadstring = metalua_loadstring
183 debug.getregistry().loadfile = metalua_loadfile