-- functions for turning a format string into a callable function
-- they work by calling parse(), passing it the format string and
-- a table of code generators appropriate for whether we are reading
-- or writing.
-- The resulting code is then prefixed with some setup code and postfixed
-- with a return value and loadstring() is called on it to generate a function
-- Copyright ï¿½ 2008 Ben "ToxicFrog" Kelly; see COPYING

local require,loadstring,setfenv,type,select,unpack,setmetatable
	= require,loadstring,setfenv,type,select,unpack,setmetatable

local print,assert,error,xpcall,pairs,where
	= print,assert,error,xpcall,pairs,debug.traceback

module((...))

local parse = require(_PACKAGE.."parser")

local function nulsafe_error(s)
	return error(s:gsub('%z', '_'))
end

local function xpcall2(f, err, ...)  
	local args = {n=select('#', ...), ...}  
	return xpcall(function() return f(unpack(args, 1, args.n)) end, err)  
end

local function err_generate(message, format, trace)
	nulsafe_error([[
struct: internal error in code generator
This is an internal error in the struct library
Please report it as a bug and include the following information:
-- error message
]]..message.."\n\n"..[[
-- format string
]]..format.."\n\n"..[[
-- stack trace
]]..trace)
end

local function err_compile(message, format, source)
	nulsafe_error([[
struct: syntax error in emitted lua source
This is an internal error in the struct library
Please report it as a bug and include the following information:
-- loadstring error
]]..message.."\n\n"..[[
-- format string
]]..format.."\n\n"..[[
-- emitted source
]]..source.."\n\n"..[[
-- stack trace
]])
end

local function err_execute(message, format, source, trace)
	nulsafe_error([[
struct: runtime error in generated function
This is at some level an internal error in the struct library
It could be a genuine error in the emitted code (in which case this is a code
generation bug)
Alternately, it could be that you gave it a malformed format string, a bad
file descriptor, or data that does not match the given format (in which case
it is an argument validation bug and you should be getting an error anyways).
Please report this as a bug and include the following information:
-- execution error
]]..message.."\n\n"..[[
-- format string
]]..format.."\n\n"..[[
-- emitted source
]]..source.."\n\n"..[[
-- stack trace
]]..trace)
end

local function compile(format, gen, env)
	local status,source = xpcall(function()
		return parse(format, gen, true)
	end,
	function(message)
		return { message, where("",2) }
	end)

	if not status then
		if type(source[1]) == "function" then
			error(source[1]()..source[2])
		end
		err_generate(source[1], format, source[2])
	end
	
	local fn,err = loadstring(source)
	
	if not fn then
		err_compile(err, format, source)
	end
	
	setfenv(fn, env)
	
	local fn = function(...)
		local status,ret,len = xpcall2(fn, function(message)
			return { message, where("",2) }
		end, ...)
		
		-- call succeeded without errors
		if status then return ret,len end
		
		local message,where = ret[1],ret[2]
		
		-- call generated a deliberate error; call the provided closure
		-- it will either emit an error code or re-throw
		if type(message) == "function" then return nil,message() end
		
		-- call generated an internal error; re-throw with extra debug info
		err_execute(message, format, source, where)
	end
	
	return fn
end

local gen_unpack = require(_PACKAGE.."gen_unpack")
local io_unpack = require(_PACKAGE.."io_unpack")

function _M.unpack(format)
	return compile(format, gen_unpack, io_unpack)
end

local gen_pack = require(_PACKAGE.."gen_pack")
local io_pack = require(_PACKAGE.."io_pack")

function _M.pack(format)
	return compile(format, gen_pack, io_pack)
end

return _M
