----------------------------------------------------------------------
-- Metalua:  $Id$
--
-- Summary: 
--
-- This set of functions offer transformations between the different
-- stages of compilation, namely:
--
-- * luafile:  source file (generally named *.lua)
-- * string:   chunk source, as a string
-- * lexstream:    the stream of lexemes in the sources
-- * ast:      abstract syntax tree
-- * bin:      binary representation of the chunk, stored as a string
-- * luacfile: compiled file (generally named *.luac)
-- * function: evaluable from of the chunk
--
-- When we look at how these formats are related to each other, we
-- distinguish:
-- * a group (A) = { luafile, string } in which both represenations are
--   interchangable;
-- * a group (B) = { luacfile, bin, function } in which all three
--   representations are interchangable;
-- * a sequence (A) -> lexstream -> ast -> (B) which is one-way.
--
-- Moreover, a last pseudo-stage is handled, called "file": it
-- represents a file which might either contain sources or a binary
-- chunk. In transformations, it only makes sense as an original
-- format (not a target); given uncertainty about its content, the only
-- safe target for a "file" transformation are binary chunk, luacfile
-- and function.
--
-- This module implements all the relevant format transformations, in
-- both directions, inside groups (A) and (B); it also implement the
-- one-way transformations; finally, it provides aliases for all
-- transitive closures of the transformations above.
--
-- Functions reading or writing files take the file's name as parameter.
--
----------------------------------------------------------------------
--
-- Copyright (c) 2006-2007, Fabien Fleutot <metalua@gmail.com>.
--
-- This software is released under the MIT Licence, see licence.txt
-- for details.
--
----------------------------------------------------------------------
-- History:
--
----------------------------------------------------------------------


--------------------------------------------------------------------------------
--
-- Exported API:
-- * [mlc.ast_of_lexstream      (lexstream)]
-- * [mlc.ast_of_luafile        (luafile)]
-- * [mlc.ast_of_string         (string)]
-- * [mlc.bin_of_ast            (ast, srcname)]
-- * [mlc.bin_of_function       (f)]
-- * [mlc.bin_of_file           (filename)]
-- * [mlc.bin_of_lexstream      (lexstream, srcname)]
-- * [mlc.bin_of_luacfile       (luacfile)]
-- * [mlc.bin_of_luafile        (luafile)]
-- * [mlc.bin_of_string         (string, srcname)]
-- * [mlc.function_of_ast       (ast)]
-- * [mlc.function_of_bin       (bin)]
-- * [mlc.function_of_file      (file)]
-- * [mlc.function_of_lexstream (lexstream)]
-- * [mlc.function_of_luacfile  (luacfile)]
-- * [mlc.function_of_luafile   (luafile)]
-- * [mlc.function_of_string    (string)]
-- * [mlc.lexstream_of_luafile  (luafile)]
-- * [mlc.lexstream_of_string   (string)]
-- * [mlc.luacfile_of_ast       (ast,       luacfile, srcname)]
-- * [mlc.luacfile_of_bin       (bin,       luacfile)]
-- * [mlc.luacfile_of_function  (f,         luacfile)]
-- * [mlc.luacfile_of_file      (file,      luacfile, srcname)]
-- * [mlc.luacfile_of_lexstream (lexstream, luacfile, srcname)]
-- * [mlc.luacfile_of_luafile   (luafile,   luacfile, srcname)]
-- * [mlc.luacfile_of_string    (string,    luacfile, srcname)]
-- * [mlc.luafile_of_string     (string,    luafile)]
-- * [mlc.string_of_luafile     (filename)]
-- * [mlc.is_bin (str)]
--
--------------------------------------------------------------------------------

module ("mlc", package.seeall)

SHOW_METABUGS = false

function is_bin (str)
   if type(str) ~= "string" then return false end
   if str:sub (1, 4) == '\027Lua' then return true
   elseif str:sub(1, 2) == '#!' then
      local _, x = str:find ("[\n\r]+")
      if x and str:sub (x+1, x+4) == '\027Lua' then return true
      end
   end
   return false
end

function string_of_luafile (filename)
   if not filename then return nil end
   local file = io.open (filename, "r")
   if not file then return nil end
   local string = file:read "*a"
   file:close()
   return string
end

function ast_of_lexstream (lexstream, filename)
   if not lexstream then return nil end
   local status, e
   if SHOW_METABUGS
   then status, e = true, mlp.block (lexstream)
   else status, e = pcall (mlp.block, lexstream) end
   if status and lexstream:peek().tag ~= "Eof"
   then status, e = false, "Premature Eof" 
   elseif status and lexstream:peek().tag == "End"
   then status, e = false, "Unexpected 'end' keyword" end
   if not status and e then 
      e = e:match "[^:]+:[0-9]+: (.*)" or e
      printf("Parsing error in %s line %s, char %s: \n%s",
             filename or "?", lexstream.line, lexstream.i, e)
      return nil
   else return e end
end

function luacfile_of_ast (ast, filename, srcname) 
   if not ast or not filename then return nil end
   local proto = compast.metalua_compile (ast)
   proto.filename = srcname or filename
   return compast.dump_file (compast.metalua_compile (ast), filename) 
end

function luacfile_of_luafile (luafile, luacfile)
   if not luafile or not luacfile then return nil end
   local ast = ast_of_luafile (luafile)
   luacfile =  luacfile or luafile .. 
      (luafile:match ".*%.lua$" and "c" or ".luac")
   return luacfile_of_ast (ast, luacfile, luafile)
end

function luacfile_of_string (string, luacfile, srcname)
   if not luacfile then return nil end
   local ast = ast_of_string (string)
   --luacfile =  luacfile or "out.luac"
   return luacfile_of_ast (ast, luacfile, srcname)
end

function luacfile_of_lexstream (lexstream, luacfile, srcname)
   if not lexstream or not luacfile then return nil end
   local ast = ast_of_lexstream (lexstream)
   --luacfile =  luacfile or "out.luac"
   return luacfile_of_ast (ast, luacfile, srcname)
end

function luacfile_of_bin (bin, filename)
   local file = io.open (filename, "w+")
   file:write(bin)
   file:close()
end

function bin_of_string (str, filename)
   if not str then return nil end
   if is_bin (str) then return str end
   local lx = lexstream_of_string (str)
   local ast = ast_of_lexstream (lx, filename)
   local bin = bin_of_ast (ast, filename)
   return bin
end

function bin_of_file (filename)
   local str = string_of_luafile (filename)
   return bin_of_string (str, filename)
end


function luacfile_of_file (file, luacfile)
   return luacfile_of_bin (bin_of_file (file), luacfile)
end

local function checknil(f)
   return function(...) if ... then return f(...) end end
end

function lexstream_of_string (str)
   if str then return mlp.lexer:newstream(str) end
end

function bin_of_ast (ast, srcname)
   if not ast then return nil end
   local proto = compast.metalua_compile (ast)
   if not proto then return nil end
   proto.source = srcname
   return compast.dump_string (proto)
end

-- Convenient aliasings:
bin_of_function      = checknil (string.dump)
bin_of_luacfile      = checknil (string_of_luafile)
luafile_of_string    = checknil (luacfile_of_string)
function_of_bin      = checknil (loadstring)

-- Functions obtained by composition:
ast_of_luafile       = o(ast_of_lexstream, lexstream_of_string, string_of_luafile)
ast_of_string        = o(ast_of_lexstream, lexstream_of_string)
bin_of_lexstream     = o(bin_of_ast, ast_of_lexstream)
bin_of_luafile       = o(bin_of_ast, ast_of_lexstream, lexstream_of_string, string_of_luafile)
function_of_ast      = o(function_of_bin, bin_of_ast)
function_of_lexstream= o(function_of_bin, bin_of_ast, ast_of_lexstream)
function_of_luacfile = o(function_of_bin, bin_of_luacfile)
function_of_luafile  = o(function_of_bin, bin_of_ast, ast_of_lexstream, lexstream_of_string, string_of_luafile)
function_of_string   = o(function_of_bin, bin_of_ast, ast_of_lexstream, lexstream_of_string)
lexstream_of_luafile = o(lexstream_of_string, string_of_luafile)
luacfile_of_function = o(luacfile_of_bin, bin_of_function)
function_of_file     = o(function_of_bin, bin_of_file)

function dofile(filename) 
   return mlc.function_of_file(filename)()
end

_G.loadfile   = function_of_file
_G.luadstring = function_of_string

function _G.load (f, name)
   local acc = { }
   while true do
      local x = f()
      if not x then break end
      table.insert (acc, x)
   end
   return function_of_string (table.concat (acc))
end