/*
** $Id: lundump.c,v 2.22.1.1 2013/04/12 18:48:47 roberto Exp $
** load precompiled Lua chunks
** See Copyright Notice in lua.h
*/

#include <string.h>

#define lundump_c
#define LUA_CORE

#include "lua.h"

#include "ldebug.h"
#include "ldo.h"
#include "lfunc.h"
#include "lmem.h"
#include "lobject.h"
#include "lstring.h"
#include "lundump.h"
#include "lzio.h"

typedef struct
{
	lua_State* L;
	ZIO* Z;
	Mbuffer* b;
	const char* name;
} LoadState;

static l_noret error(LoadState* S, const char* why)
{
	luaO_pushfstring(S->L, "%s: %s precompiled chunk", S->name, why);
	luaD_throw(S->L, LUA_ERRSYNTAX);
}

#define LoadMem(S, b, n, size) LoadBlock(S, b, (n) * (size))
#define LoadByte(S) (lu_byte) LoadChar(S)
#define LoadVar(S, x) LoadMem(S, &x, 1, sizeof(x))
#define LoadVector(S, b, n, size) LoadMem(S, b, n, size)

#if !defined(luai_verifycode)
#define luai_verifycode(L, b, f) /* empty */
#endif

static void LoadBlock(LoadState* S, void* b, size_t size)
{
	if (luaZ_read(S->Z, b, size) != 0) error(S, "truncated");
}

static int LoadChar(LoadState* S)
{
	char x;
	LoadVar(S, x);
	return x;
}

static int LoadInt(LoadState* S)
{
	int x;
	LoadVar(S, x);
	if (x < 0) error(S, "corrupted");
	return x;
}

static lua_Number LoadNumber(LoadState* S)
{
	lua_Number x;
	LoadVar(S, x);
	return x;
}

static TString* LoadString(LoadState* S)
{
	size_t size;
	LoadVar(S, size);
	if (size == 0)
		return NULL;
	else
	{
		char* s = luaZ_openspace(S->L, S->b, size);
		LoadBlock(S, s, size * sizeof(char));
		return luaS_newlstr(S->L, s, size - 1); /* remove trailing '\0' */
	}
}

static void LoadCode(LoadState* S, Proto* f)
{
	int n = LoadInt(S);
	f->code = luaM_newvector(S->L, n, Instruction);
	f->sizecode = n;
	LoadVector(S, f->code, n, sizeof(Instruction));
}

static void LoadFunction(LoadState* S, Proto* f);

static void LoadConstants(LoadState* S, Proto* f)
{
	int i, n;
	n = LoadInt(S);
	f->k = luaM_newvector(S->L, n, TValue);
	f->sizek = n;
	for (i = 0; i < n; i++) setnilvalue(&f->k[i]);
	for (i = 0; i < n; i++)
	{
		TValue* o = &f->k[i];
		int t = LoadChar(S);
		switch (t)
		{
			case LUA_TNIL:
				setnilvalue(o);
				break;
			case LUA_TBOOLEAN:
				setbvalue(o, LoadChar(S));
				break;
			case LUA_TNUMBER:
				setnvalue(o, LoadNumber(S));
				break;
			case LUA_TSTRING:
				setsvalue2n(S->L, o, LoadString(S));
				break;
			default:
				lua_assert(0);
		}
	}
	n = LoadInt(S);
	f->p = luaM_newvector(S->L, n, Proto*);
	f->sizep = n;
	for (i = 0; i < n; i++) f->p[i] = NULL;
	for (i = 0; i < n; i++)
	{
		f->p[i] = luaF_newproto(S->L);
		LoadFunction(S, f->p[i]);
	}
}

static void LoadUpvalues(LoadState* S, Proto* f)
{
	int i, n;
	n = LoadInt(S);
	f->upvalues = luaM_newvector(S->L, n, Upvaldesc);
	f->sizeupvalues = n;
	for (i = 0; i < n; i++) f->upvalues[i].name = NULL;
	for (i = 0; i < n; i++)
	{
		f->upvalues[i].instack = LoadByte(S);
		f->upvalues[i].idx = LoadByte(S);
	}
}

static void LoadDebug(LoadState* S, Proto* f)
{
	int i, n;
	f->source = LoadString(S);
	n = LoadInt(S);
	f->lineinfo = luaM_newvector(S->L, n, int);
	f->sizelineinfo = n;
	LoadVector(S, f->lineinfo, n, sizeof(int));
	n = LoadInt(S);
	f->locvars = luaM_newvector(S->L, n, LocVar);
	f->sizelocvars = n;
	for (i = 0; i < n; i++) f->locvars[i].varname = NULL;
	for (i = 0; i < n; i++)
	{
		f->locvars[i].varname = LoadString(S);
		f->locvars[i].startpc = LoadInt(S);
		f->locvars[i].endpc = LoadInt(S);
	}
	n = LoadInt(S);
	for (i = 0; i < n; i++) f->upvalues[i].name = LoadString(S);
}

static void LoadFunction(LoadState* S, Proto* f)
{
	f->linedefined = LoadInt(S);
	f->lastlinedefined = LoadInt(S);
	f->numparams = LoadByte(S);
	f->is_vararg = LoadByte(S);
	f->maxstacksize = LoadByte(S);
	LoadCode(S, f);
	LoadConstants(S, f);
	LoadUpvalues(S, f);
	LoadDebug(S, f);
}

/* the code below must be consistent with the code in luaU_header */
#define N0 LUAC_HEADERSIZE
#define N1 (sizeof(LUA_SIGNATURE) - sizeof(char))
#define N2 N1 + 2
#define N3 N2 + 6

static void LoadHeader(LoadState* S)
{
	lu_byte h[LUAC_HEADERSIZE];
	lu_byte s[LUAC_HEADERSIZE];
	luaU_header(h);
	memcpy(s, h, sizeof(char)); /* first char already read */
	LoadBlock(S, s + sizeof(char), LUAC_HEADERSIZE - sizeof(char));
	if (memcmp(h, s, N0) == 0) return;
	if (memcmp(h, s, N1) != 0) error(S, "not a");
	if (memcmp(h, s, N2) != 0) error(S, "version mismatch in");
	if (memcmp(h, s, N3) != 0)
		error(S, "incompatible");
	else
		error(S, "corrupted");
}

/*
** load precompiled chunk
*/
Closure* luaU_undump(lua_State* L, ZIO* Z, Mbuffer* buff, const char* name)
{
	LoadState S;
	Closure* cl;
	if (*name == '@' || *name == '=')
		S.name = name + 1;
	else if (*name == LUA_SIGNATURE[0])
		S.name = "binary string";
	else
		S.name = name;
	S.L = L;
	S.Z = Z;
	S.b = buff;
	LoadHeader(&S);
	cl = luaF_newLclosure(L, 1);
	setclLvalue(L, L->top, cl);
	incr_top(L);
	cl->l.p = luaF_newproto(L);
	LoadFunction(&S, cl->l.p);
	if (cl->l.p->sizeupvalues != 1)
	{
		Proto* p = cl->l.p;
		cl = luaF_newLclosure(L, cl->l.p->sizeupvalues);
		cl->l.p = p;
		setclLvalue(L, L->top - 1, cl);
	}
	luai_verifycode(L, buff, cl->l.p);
	return cl;
}

#define MYINT(s) (s[0] - '0')
#define VERSION MYINT(LUA_VERSION_MAJOR) * 16 + MYINT(LUA_VERSION_MINOR)
#define FORMAT 0 /* this is the official format */

/*
* make header for precompiled chunks
* if you change the code below be sure to update LoadHeader and FORMAT above
* and LUAC_HEADERSIZE in lundump.h
*/
void luaU_header(lu_byte* h)
{
	int x = 1;
	memcpy(h, LUA_SIGNATURE, sizeof(LUA_SIGNATURE) - sizeof(char));
	h += sizeof(LUA_SIGNATURE) - sizeof(char);
	*h++ = cast_byte(VERSION);
	*h++ = cast_byte(FORMAT);
	*h++ = cast_byte(*(char*)&x); /* endianness */
	*h++ = cast_byte(sizeof(int));
	*h++ = cast_byte(sizeof(size_t));
	*h++ = cast_byte(sizeof(Instruction));
	*h++ = cast_byte(sizeof(lua_Number));
	*h++ = cast_byte(((lua_Number)0.5) == 0); /* is lua_Number integral? */
	memcpy(h, LUAC_TAIL, sizeof(LUAC_TAIL) - sizeof(char));
}
