fixed incorrect behavior for loadstring() and loadfile() when metalua.compiler is...
[metalua.git] / src / compiler / mlc.mlua
blob90cdc9a12d38856f913e10f8ef71f8daca847710
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.
20 --------------------------------------------------------------------------------
22 require 'bytecode'
23 require 'mlp'
25 mlc = { }
26 setmetatable(mlc, mlc)
27 mlc.metabugs = false
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 --------------------------------------------------------------------------------
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
69 function mlc.convert (x, src_fmt, dst_fmt, name)
70    -- printf(" *** Convert a %s into a %s", src_fmt, dst_fmt)
72    -{ jump_to_point }
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
81    x = f:read'*a'
82    f:close()
83    
84    POINT 'luastring', 'string' -- x is the source
86    x = mlp.lexer:newstream(x, name)      
87    
88    POINT 'lexstream', 'table' -- x is the lexeme stream
90    local status -- status = compilation success
91    local lx=x
92    if mlc.metabugs
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))
108       return nil
109    end
110    
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')
167    pcall(f.close, f)
168    if success then return loadstring (src, '@'..filename)
169    else return nil, src end
172 function load(f, name)
173    while true do
174       local x = f()
175       if not x then break end
176       assert(type(x)=='string', "function passed to load() must return strings")
177       table.insert(acc, x)
178    end
179    return loadstring(table.concat(x))
182 function dostring(src)
183    local f, msg = loadstring(src)
184    if not f then error(msg) end
185    return f()
188 function dofile(name)
189    local f, msg = loadfile(name)
190    if not f then error(msg) end
191    return f()