blob: 86a64dfcd113f2f9d53e73f8c7ac5932c9467d7f [file] [log] [blame]
-------------------------------------------------------------------------------
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
--
-- All rights reserved.
--
-- This program and the accompanying materials are made available
-- under the terms of the Eclipse Public License v1.0 which
-- accompanies this distribution, and is available at
-- http://www.eclipse.org/legal/epl-v10.html
--
-- This program and the accompanying materials are also made available
-- under the terms of the MIT public license which accompanies this
-- distribution, and is available at http://www.lua.org/license.html
--
-- Contributors:
-- Fabien Fleutot - API and implementation
--
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- This module is written in a more hackish way than necessary, just
-- because I can. Its core feature is to dynamically generate a
-- function that converts from a source format to a destination
-- format; these formats are the various ways to represent a piece of
-- program, from the source file to the executable function. Legal
-- formats are:
--
-- * luafile: the name of a file containing sources.
-- * luastring: these sources as a single string.
-- * lexstream: a stream of lexemes.
-- * ast: an abstract syntax tree.
-- * proto: a (Yueliang) struture containing a high level
-- representation of bytecode. Largely based on the
-- Proto structure in Lua's VM.
-- * luacstring: a string dump of the function, as taken by
-- loadstring() and produced by string.dump().
-- * function: an executable lua function in RAM.
--
-------------------------------------------------------------------------------
require 'metalua.bytecode'
require 'metalua.mlp'
mlc = { }
setmetatable(mlc, mlc)
mlc.metabugs = false
--------------------------------------------------------------------------------
-- Order of the transformations. if 'a' is on the left of 'b', then a 'a' can
-- be transformed into a 'b' (but not the other way around).
-- mlc.sequence goes for numbers to format names, mlc.order goes from format
-- names to numbers.
--------------------------------------------------------------------------------
mlc.sequence = {
'luafile', 'luastring', 'lexstream', 'ast', 'proto',
'luacstring', 'function' }
mlc.order = table.transpose(mlc.sequence)
-- Check whether a structure of nested tables is a valid AST.
-- Currently thows an error if it isn't.
-- @return true When no error is found in given ast
-- @return false, error string
-- @return false, error string, positions Provide position of error in
-- given ast as a table. The position contains the following keys
-- * column: number
-- * line : number
-- * offset: number
-- TODO: build a detailed error location, with the lineinfo of every nested node.
local function check_ast(kind, ast)
if not ast then return check_ast('block', kind) end
assert(type(ast)=='table', "wrong AST type")
local cfg = {}
local function error2ast(error_node, ...)
if not error_node.stuffing then
if error_node.tag=='Error' then
cfg.errorfound = true
cfg.errormsg = error_node[1]
-- Try to extract error position in source
local li = error_node.lineinfo and error_node.lineinfo.first
-- Fill positions if undefined or not narrow enough
if li and ( not cfg.positions or cfg.positions.offset < li.offset ) then
cfg.positions = {
column = li.column,
line = li.line,
offset = li.offset
}
end
else
-- This block is for dealing with errors which are not error
-- nodes. It would be soooo nice to get rid of it.
-- TODO: Try to remove this bug when issue #20 is fixed
local li
for _, n in ipairs{ error_node, ... } do
if n.lineinfo then
li = n.lineinfo
cfg.errorfound = true
break
end
end
local posmsg
if li then
local column = li.first.column
local line = li.first.line
local offset = li.first.offset
posmsg = string.format("line %d, char %d, offset %d",
line, column, offset)
cfg.positions = {
column = column,
line = line,
offset = offset
}
else
posmsg = "unknown source position"
end
local msg = "Invalid node "..
(error_node.tag and "tag "..tostring(error_node.tag) or "without tag")..
(posmsg and " at "..posmsg or "")
cfg.errormsg = msg
end
end
end
local f = require 'metalua.treequery.walk' [kind]
cfg.malformed=error2ast
cfg.unknown= error2ast
cfg.error= error2ast
f(cfg, ast)
return cfg.errorfound == nil, cfg.errormsg, cfg.positions
end
mlc.check_ast = check_ast
function mlc.luafile_to_luastring(x, name)
name = name or '@'..x
local f, msg = io.open (x, 'rb')
if not f then return f, msg end
local r = f :read '*a'
f :close()
return r, name
end
function mlc.luastring_to_lexstream(src, name)
local r = mlp.lexer :newstream (src, name)
return r, name
end
function mlc.lexstream_to_ast(lx, name)
if PRINT_PARSED_STAT then
print("About to parse a lexstream, starting with "..tostring(lx:peek()))
end
local r = mlp.chunk(lx)
r.source = name
return r, name
end
function mlc.ast_to_proto(ast, name)
name = name or ast.source
return bytecode.metalua_compile(ast, name), name
end
function mlc.proto_to_luacstring(proto, name)
return bytecode.dump_string(proto), name
end
function mlc.luacstring_to_function(bc, name)
return string.undump(bc, name)
end
-- Create all sensible combinations
for i=1,#mlc.sequence do
for j=i+2, #mlc.sequence do
local dst_name = mlc.sequence[i].."_to_"..mlc.sequence[j]
local functions = { }
--local n = { }
for k=i, j-1 do
local name = mlc.sequence[k].."_to_"..mlc.sequence[k+1]
local f = assert(mlc[name])
table.insert (functions, f)
--table.insert(n, name)
end
mlc[dst_name] = function(a, b)
for _, f in ipairs(functions) do
a, b = f(a, b)
end
return a, b
end
--printf("Created mlc.%s out of %s", dst_name, table.concat(n, ', '))
end
end
--------------------------------------------------------------------------------
-- This case isn't handled by the __index method, as it goes "in the wrong direction"
--------------------------------------------------------------------------------
mlc.function_to_luacstring = string.dump
--------------------------------------------------------------------------------
-- These are drop-in replacement for loadfile() and loadstring(). The
-- C functions will call them instead of the original versions if
-- they're referenced in the registry.
--------------------------------------------------------------------------------
lua_loadstring = loadstring
local lua_loadstring = loadstring
lua_loadfile = loadfile
local lua_loadfile = loadfile
function loadstring(str, name)
if type(str) ~= 'string' then error 'string expected' end
if str:match '^\027LuaQ' then return lua_loadstring(str) end
local n = str:match '^#![^\n]*\n()'
if n then str=str:sub(n, -1) end
-- FIXME: handle erroneous returns (return nil + error msg)
local success, f = pcall (mlc.luastring_to_function, str, name)
if success then return f else return nil, f end
end
function loadfile(filename)
local f, err_msg = io.open(filename, 'rb')
if not f then return nil, err_msg end
local success, src = pcall( f.read, f, '*a')
pcall(f.close, f)
if success then return loadstring (src, '@'..filename)
else return nil, src end
end
function load(f, name)
while true do
local x = f()
if not x then break end
assert(type(x)=='string', "function passed to load() must return strings")
table.insert(acc, x)
end
return loadstring(table.concat(x))
end
function dostring(src)
local f, msg = loadstring(src)
if not f then error(msg) end
return f()
end
function dofile(name)
local f, msg = loadfile(name)
if not f then error(msg) end
return f()
end