<commit_before>//
// RemotePhotoTool - remote camera control software
// Copyright (C) 2008-2015 <NAME>
//
/// \file Lua.cpp Lua wrapper classes
//

// includes
#include "StdAfx.h"
#include "Lua.hpp"

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

using namespace Lua;

/// list of libraries that can be loaded using Lua::State::RequireLib()
static const luaL_Reg loadedlibs[] = {
   { "_G", luaopen_base },
   { LUA_LOADLIBNAME, luaopen_package },
   { LUA_COLIBNAME, luaopen_coroutine },
   { LUA_TABLIBNAME, luaopen_table },
   { LUA_IOLIBNAME, luaopen_io },
   { LUA_OSLIBNAME, luaopen_os },
   { LUA_STRLIBNAME, luaopen_string },
   { LUA_MATHLIBNAME, luaopen_math },
   { LUA_UTF8LIBNAME, luaopen_utf8 },
   { LUA_DBLIBNAME, luaopen_debug },
   { NULL, NULL }
};

//
// Lua::Exception
//

CString Lua::Exception::MessageFromState(const CString& cszMessage, lua_State* L) throw()
{
   CString cszStackTrace = ReadStackTrace(L);

   try
   {
      CString cszText;
      cszText.Format(_T("Lua error: %s, in:\n%s"), cszMessage.GetString(), cszStackTrace.GetString());
      return cszText;
   }
   catch (...)
   {
      return CString();
   }
}

CString Lua::Exception::ReadStackTrace(lua_State* L)
{
   CString cszInfo;

   for (int iStack = 0;; iStack++)
   {
      lua_Debug debug = { 0 };

      int iRet = lua_getstack(L, iStack, &debug);
      if (iRet == 0)
         break;

      try
      {
         lua_getinfo(L, "Sl", &debug);
      }
      catch (...)
      {
         // may throw exception of type  struct lua_longjmp
         ATLTRACE(_T("exception during lua_getinfo()\n"));
      }

      try
      {
         cszInfo.AppendFormat(_T("%hs:%u\n"),
            debug.short_src,
            debug.currentline);
      }
      catch (...)
      {
         ATLTRACE(_T("exception during formatting debug info()\n"));
      }
   }

   cszInfo.Trim();

   return cszInfo;
}

void Lua::Exception::ParseLuaError(LPCSTR pszaText)
{
   CString cszText(pszaText);
   int iPos1 = cszText.Find(_T(':'), 3);
   int iPos2 = cszText.Find(_T(':'), iPos1 + 1);

   if (iPos1 != -1 && iPos2 != -1)
   {
      m_cszLuaSourceFile = cszText.Left(iPos1);
      m_cszLuaErrorMessage = cszText.Mid(iPos2 + 1);
      m_cszLuaErrorMessage.Trim();

      m_uiLuaLineNumber = static_cast<UINT>(_tcstoul(cszText.Mid(iPos1 + 1, iPos2 - iPos1 - 1), NULL, 10));
   }
}

//
// Lua::StackChecker
//

StackChecker::StackChecker(lua_State* L)
   :m_L(L),
   m_iStackDepth(lua_absindex(L, -1))
{
}

StackChecker::~StackChecker()
{
   int iStackDepth = lua_absindex(m_L, -1);
   ATLASSERT(m_iStackDepth == iStackDepth);
   iStackDepth;
}

//
// Lua::Ref
//

/// \param[in] state Lua state that the reference points to
/// \param[in] iStackIndex absolute or relative stack index of value to be
/// referenced. The value is converted to an absolute stack index.
Ref::Ref(State& state, int iStackIndex)
   :m_state(state),
   m_iStackIndex(lua_absindex(state.GetState(), iStackIndex))
{
}

Ref::~Ref() throw()
{
   if (m_iStackIndex != -1)
      m_state.RemoveRef(*this);
}

void Ref::Detach()
{
   m_iStackIndex = -1;
}

void Ref::OnRemoveStackValue(int iStackIndex)
{
   ATLASSERT(iStackIndex != m_iStackIndex); // must not be own ref that is removed

   if (m_iStackIndex >= iStackIndex)
      m_iStackIndex--; // it's below us, so reduce index
}


//
// Lua::Value
//

/// default ctor; constructs nil value
Value::Value()
   :m_enType(typeNil)
{
}

Value::Value(bool bValue)
   :m_value(bValue),
   m_enType(typeBoolean)
{
}

Value::Value(double dValue)
   :m_value(dValue),
   m_enType(typeNumber)
{
}

Value::Value(int iValue)
   :m_value(iValue),
   m_enType(typeInteger)
{
}

Value::Value(const CString& cszText)
   :m_value(CStringA(cszText)),
   m_enType(typeString)
{
}

Value::Value(LPCSTR pszText)
   :m_value(CStringA(pszText)),
   m_enType(typeString)
{
}

Value::Value(Table table)
   :m_enType(typeTable),
   m_value(table),
   m_spRef(table.GetRef())
{
}

Value::Value(Userdata userdata)
   :m_enType(typeUserdata),
   m_value(userdata),
   m_spRef(userdata.GetRef())
{
}

Value::Value(Function func)
   :m_enType(typeFunction),
   m_value(func),
   m_spRef(func.GetRef())
{
}

Value::Value(Thread thread)
   :m_enType(typeThread),
   m_value(thread),
   m_spRef(thread.GetRef())
{
}

Value::Value(const Value& val)
   :m_value(val.m_value),
   m_enType(val.m_enType),
   m_spRef(val.GetRef())
{
}

Value& Value::operator=(const Value& val)
{
   m_value = val.m_value;
   m_enType = val.m_enType;
   m_spRef = val.GetRef();

   return *this;
}

Value::~Value() throw()
{
   std::weak_ptr<Ref> wpRef = m_spRef;
   m_spRef.reset();

   if (wpRef.use_count() == 1)
   {
      std::shared_ptr<Ref> spRef = wpRef.lock();
      spRef->GetState().RemoveFromStack(*spRef);
   }
}

void Value::Attach(std::shared_ptr<Ref> spRef) const
{
   m_spRef = spRef;
}

void Value::Push(State& state) const
{
   lua_State* L = state.GetState();

   switch (m_enType)
   {
   case typeNil:
      lua_pushnil(L);
      Attach(std::make_shared<Ref>(state, -1));
      break;

   case typeBoolean:
      lua_pushboolean(L, boost::any_cast<bool>(m_value) ? 1 : 0);
      Attach(std::make_shared<Ref>(state, -1));
      break;

   case typeNumber:
      lua_pushnumber(L, boost::any_cast<double>(m_value));
      Attach(std::make_shared<Ref>(state, -1));
      break;

   case typeInteger:
      lua_pushinteger(L, boost::any_cast<int>(m_value));
      Attach(std::make_shared<Ref>(state, -1));
      break;

   case typeString:
      lua_pushstring(L, boost::any_cast<CStringA>(m_value));
      Attach(std::make_shared<Ref>(state, -1));
      break;

   case typeTable:
   {
      Table table = Get<Table>();
      table.Push();
   }
   break;

   case typeFunction:
   {
      Function func = Get<Function>();
      func.Push();
   }
   break;

   case typeUserdata:
   {
      Userdata userdata = Get<Userdata>();
      userdata.Push();
   }
   break;

   case typeThread:
   {
      Thread thread = Get<Thread>();
      thread.Push();
   }
   break;

   default:
      ATLASSERT(false);
      break;
   }
}

void Value::Detach() const
{
   m_spRef->Detach();
   m_spRef.reset();

   switch (m_enType)
   {
   case typeTable:
   {
      Table table = Get<Table>();
      table.Detach();
   }
   break;

   case typeFunction:
   {
      Function func = Get<Function>();
      func.Detach();
   }
   break;

   case typeUserdata:
   {
      Userdata userdata = Get<Userdata>();
      userdata.Detach();
   }
   break;

   case typeThread:
   {
      Thread thread = Get<Thread>();
      thread.Detach();
   }
   break;
   }
}

Value Value::FromStack(State& state, int iStackIndex)
{
   lua_State* L = state.GetState();

   switch (lua_type(L, iStackIndex))
   {
   case LUA_TNONE:
   case LUA_TNIL:
   {
      Value val;
      val.Attach(std::make_shared<Ref>(state, iStackIndex));
      state.AddRef(val.GetRef());
      return val;
   }

   case LUA_TNUMBER:
   {
      Value val(lua_tonumber(L, iStackIndex));
      val.Attach(std::make_shared<Ref>(state, iStackIndex));
      state.AddRef(val.GetRef());
      return val;
   }

   case LUA_TBOOLEAN:
   {
      Value val(lua_toboolean(L, iStackIndex) != 0);
      val.Attach(std::make_shared<Ref>(state, iStackIndex));
      state.AddRef(val.GetRef());
      return val;
   }

   case LUA_TSTRING:
   {
      Value val(lua_tostring(L, iStackIndex));
      val.Attach(std::make_shared<Ref>(state, iStackIndex));
      state.AddRef(val.GetRef());
      return val;
   }

   case LUA_TTABLE:
   {
      auto spRef = std::make_shared<Ref>(state, iStackIndex);
      state.AddRef(spRef);

      return Value(Table(spRef, _T("")));
   }

   case LUA_TFUNCTION:
   {
      auto spRef = std::make_shared<Ref>(state, iStackIndex);
      state.AddRef(spRef);

      return Value(Function(spRef));
   }

   case LUA_TUSERDATA:
   {
      auto spRef = std::make_shared<Ref>(state, iStackIndex);
      state.AddRef(spRef);

      return Value(Userdata(spRef));
   }

   case LUA_TTHREAD:
   {
      auto spRef = std::make_shared<Ref>(state, iStackIndex);
      state.AddRef(spRef);

      return Value(Thread(spRef));
   }

   default:
      ATLASSERT(false); // type can't be converted into Value
      break;
   }

   return Value();
}

//
// FuncData
//

/// C++ function data (stored as user data in State)
struct FuncData
{
   /// ctor
   FuncData(T_fnCFunction fn)
      :m_fn(fn)
   {
   }

   /// pushes C closure
   static void PushCClosure(State& state, T_fnCFunction fn);

   /// returns ref to self from upvalue
   static FuncData& GetSelf(lua_State* L);

   /// function call
   static int OnFunctionCall(lua_State* L);

   /// garbage collect call
   static int OnFunctionGarbageCollect(lua_State* L);

   /// bound C++ function
   T_fnCFunction m_fn;
};

void FuncData::PushCClosure(State& state, T_fnCFunction fn)
{
   lua_State* L = state.GetState();

   // add userdata
   void* pUserdata = lua_newuserdata(L, sizeof(FuncData*));
   FuncData** pFunction = reinterpret_cast<FuncData**>(pUserdata);
   *pFunction = new FuncData(fn);

   ATLASSERT(lua_isuserdata(L, -1) != 0);

   // set up metatable
   lua_newtable(L);

   lua_pushcfunction(L, &FuncData::OnFunctionGarbageCollect);
   lua_setfield(L, -2, "__gc");

   lua_setmetatable(L, -2);

   /// push the C closure
   lua_pushcclosure(L, &FuncData::OnFunctionCall, 1);
}

FuncData& FuncData::GetSelf(lua_State* L)
{
   void* p = lua_touserdata(L, lua_upvalueindex(1));

   FuncData** ppFuncData = reinterpret_cast<FuncData**>(p);
   if (ppFuncData == nullptr)
      throw Lua::Exception(_T("function call without allocated FuncData"), L, __FILE__, __LINE__);

   FuncData* pFuncData = *ppFuncData;
   if (pFuncData == nullptr)
      throw Lua::Exception(_T("function call without set FuncData"), L, __FILE__, __LINE__);

   return *pFuncData;
}

int FuncData::OnFunctionCall(lua_State* L)
{
   FuncData& data = GetSelf(L);

   if (data.m_fn == nullptr)
      return 0; // no function bound

   // collect params
   int iStackDepth = lua_absindex(L, -1);

   State paramState(L, false);

   std::vector<Value> vecParams;
   for (int i = 1; i <= iStackDepth; i++)
      vecParams.push_back(Value::FromStack(paramState, i));

   State::TraceUpvalues(L);

   // call bound function
   std::vector<Value> vecRetVals;
   try
   {
      vecRetVals = data.m_fn(paramState, vecParams);
   }
   catch (...)
   {
      // in case of exceptions, detach all values from parameter state
      paramState.DetachAll();
      throw;
   }

   // put results on stack
   std::for_each(vecRetVals.begin(), vecRetVals.end(),
      [&](const Value& value) { value.Push(paramState); value.Detach(); });

   return vecRetVals.size();
}

int FuncData::OnFunctionGarbageCollect(lua_State* L)
{
   void* p = lua_touserdata(L, -1);

   FuncData** ppFuncData = reinterpret_cast<FuncData**>(p);
   if (ppFuncData == nullptr)
      throw Lua::Exception(_T("garbage collecting c closure without allocated FuncData"), L, __FILE__, __LINE__);

   delete *ppFuncData;
   *ppFuncData = nullptr;

   return 0;
}

//
// Lua::Function
//

Function::Function(std::shared_ptr<Ref> spRef)
   :m_spRef(spRef)
{
}

Function::Function(const Function& func)
   :m_spRef(func.GetRef())
{
}

Function& Function::operator=(const Function& func)
{
   m_spRef = func.GetRef();

   return *this;
}

Function::~Function() throw()
{
   ATLASSERT(m_spRef == nullptr || m_spRef->GetStackIndex() == -1 ||
      lua_isfunction(m_spRef->GetState().GetState(), m_spRef->GetStackIndex()) == true);

   std::weak_ptr<Ref> wpRef = m_spRef;
   m_spRef.reset();

   if (wpRef.use_count() == 1)
   {
      std::shared_ptr<Ref> spRef = wpRef.lock();
      spRef->GetState().RemoveFromStack(*spRef);
   }
}

std::vector<Value> Function::Call(int iResults, const std::vector<Value>& vecParam)
{
   State& state = m_spRef->GetState();
   lua_State* L = state.GetState();

   lua_pushvalue(L, m_spRef->GetStackIndex());

   std::for_each(vecParam.begin(), vecParam.end(),
      [&](const Value& value) { value.Push(state); value.Detach(); });

   lua_call(L, vecParam.size(), iResults);

   // collect return values
   std::vector<Value> vecResults;
   for (int i = -iResults; i <= -1; i++)
      vecResults.push_back(Value::FromStack(state, i));

   return vecResults;
}

void Function::Push()
{
   State& state = m_spRef->GetState();
   lua_State* L = state.GetState();

   lua_pushvalue(L, m_spRef->GetStackIndex());
}

void Function::Detach() const
{
   m_spRef->Detach();
   m_spRef.reset();
}

//
// Lua::Table
//

Table::Table(State& state, const CString& cszName)
   :m_cszName(cszName)
{
   lua_State* L = state.GetState();

   lua_newtable(L);

   if (!cszName.IsEmpty())
   {
      // no temporary (empty name) table? then set and get again
      lua_setglobal(L, CStringA(m_cszName));

      lua_getglobal(L, CStringA(m_cszName).GetString());
   }

   m_spRef = std::make_shared<Ref>(state, -1);<commit_msg>using GetString() when passing strings to Lua<commit_after>//
// RemotePhotoTool - remote camera control software
// Copyright (C) 2008-2015 <NAME>
//
/// \file Lua.cpp Lua wrapper classes
//

// includes
#include "StdAfx.h"
#include "Lua.hpp"

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

using namespace Lua;

/// list of libraries that can be loaded using Lua::State::RequireLib()
static const luaL_Reg loadedlibs[] = {
   { "_G", luaopen_base },
   { LUA_LOADLIBNAME, luaopen_package },
   { LUA_COLIBNAME, luaopen_coroutine },
   { LUA_TABLIBNAME, luaopen_table },
   { LUA_IOLIBNAME, luaopen_io },
   { LUA_OSLIBNAME, luaopen_os },
   { LUA_STRLIBNAME, luaopen_string },
   { LUA_MATHLIBNAME, luaopen_math },
   { LUA_UTF8LIBNAME, luaopen_utf8 },
   { LUA_DBLIBNAME, luaopen_debug },
   { NULL, NULL }
};

//
// Lua::Exception
//

CString Lua::Exception::MessageFromState(const CString& cszMessage, lua_State* L) throw()
{
   CString cszStackTrace = ReadStackTrace(L);

   try
   {
      CString cszText;
      cszText.Format(_T("Lua error: %s, in:\n%s"), cszMessage.GetString(), cszStackTrace.GetString());
      return cszText;
   }
   catch (...)
   {
      return CString();
   }
}

CString Lua::Exception::ReadStackTrace(lua_State* L)
{
   CString cszInfo;

   for (int iStack = 0;; iStack++)
   {
      lua_Debug debug = { 0 };

      int iRet = lua_getstack(L, iStack, &debug);
      if (iRet == 0)
         break;

      try
      {
         lua_getinfo(L, "Sl", &debug);
      }
      catch (...)
      {
         // may throw exception of type  struct lua_longjmp
         ATLTRACE(_T("exception during lua_getinfo()\n"));
      }

      try
      {
         cszInfo.AppendFormat(_T("%hs:%u\n"),
            debug.short_src,
            debug.currentline);
      }
      catch (...)
      {
         ATLTRACE(_T("exception during formatting debug info()\n"));
      }
   }

   cszInfo.Trim();

   return cszInfo;
}

void Lua::Exception::ParseLuaError(LPCSTR pszaText)
{
   CString cszText(pszaText);
   int iPos1 = cszText.Find(_T(':'), 3);
   int iPos2 = cszText.Find(_T(':'), iPos1 + 1);

   if (iPos1 != -1 && iPos2 != -1)
   {
      m_cszLuaSourceFile = cszText.Left(iPos1);
      m_cszLuaErrorMessage = cszText.Mid(iPos2 + 1);
      m_cszLuaErrorMessage.Trim();

      m_uiLuaLineNumber = static_cast<UINT>(_tcstoul(cszText.Mid(iPos1 + 1, iPos2 - iPos1 - 1), NULL, 10));
   }
}

//
// Lua::StackChecker
//

StackChecker::StackChecker(lua_State* L)
   :m_L(L),
   m_iStackDepth(lua_absindex(L, -1))
{
}

StackChecker::~StackChecker()
{
   int iStackDepth = lua_absindex(m_L, -1);
   ATLASSERT(m_iStackDepth == iStackDepth);
   iStackDepth;
}

//
// Lua::Ref
//

/// \param[in] state Lua state that the reference points to
/// \param[in] iStackIndex absolute or relative stack index of value to be
/// referenced. The value is converted to an absolute stack index.
Ref::Ref(State& state, int iStackIndex)
   :m_state(state),
   m_iStackIndex(lua_absindex(state.GetState(), iStackIndex))
{
}

Ref::~Ref() throw()
{
   if (m_iStackIndex != -1)
      m_state.RemoveRef(*this);
}

void Ref::Detach()
{
   m_iStackIndex = -1;
}

void Ref::OnRemoveStackValue(int iStackIndex)
{
   ATLASSERT(iStackIndex != m_iStackIndex); // must not be own ref that is removed

   if (m_iStackIndex >= iStackIndex)
      m_iStackIndex--; // it's below us, so reduce index
}


//
// Lua::Value
//

/// default ctor; constructs nil value
Value::Value()
   :m_enType(typeNil)
{
}

Value::Value(bool bValue)
   :m_value(bValue),
   m_enType(typeBoolean)
{
}

Value::Value(double dValue)
   :m_value(dValue),
   m_enType(typeNumber)
{
}

Value::Value(int iValue)
   :m_value(iValue),
   m_enType(typeInteger)
{
}

Value::Value(const CString& cszText)
   :m_value(CStringA(cszText)),
   m_enType(typeString)
{
}

Value::Value(LPCSTR pszText)
   :m_value(CStringA(pszText)),
   m_enType(typeString)
{
}

Value::Value(Table table)
   :m_enType(typeTable),
   m_value(table),
   m_spRef(table.GetRef())
{
}

Value::Value(Userdata userdata)
   :m_enType(typeUserdata),
   m_value(userdata),
   m_spRef(userdata.GetRef())
{
}

Value::Value(Function func)
   :m_enType(typeFunction),
   m_value(func),
   m_spRef(func.GetRef())
{
}

Value::Value(Thread thread)
   :m_enType(typeThread),
   m_value(thread),
   m_spRef(thread.GetRef())
{
}

Value::Value(const Value& val)
   :m_value(val.m_value),
   m_enType(val.m_enType),
   m_spRef(val.GetRef())
{
}

Value& Value::operator=(const Value& val)
{
   m_value = val.m_value;
   m_enType = val.m_enType;
   m_spRef = val.GetRef();

   return *this;
}

Value::~Value() throw()
{
   std::weak_ptr<Ref> wpRef = m_spRef;
   m_spRef.reset();

   if (wpRef.use_count() == 1)
   {
      std::shared_ptr<Ref> spRef = wpRef.lock();
      spRef->GetState().RemoveFromStack(*spRef);
   }
}

void Value::Attach(std::shared_ptr<Ref> spRef) const
{
   m_spRef = spRef;
}

void Value::Push(State& state) const
{
   lua_State* L = state.GetState();

   switch (m_enType)
   {
   case typeNil:
      lua_pushnil(L);
      Attach(std::make_shared<Ref>(state, -1));
      break;

   case typeBoolean:
      lua_pushboolean(L, boost::any_cast<bool>(m_value) ? 1 : 0);
      Attach(std::make_shared<Ref>(state, -1));
      break;

   case typeNumber:
      lua_pushnumber(L, boost::any_cast<double>(m_value));
      Attach(std::make_shared<Ref>(state, -1));
      break;

   case typeInteger:
      lua_pushinteger(L, boost::any_cast<int>(m_value));
      Attach(std::make_shared<Ref>(state, -1));
      break;

   case typeString:
      lua_pushstring(L, boost::any_cast<CStringA>(m_value));
      Attach(std::make_shared<Ref>(state, -1));
      break;

   case typeTable:
   {
      Table table = Get<Table>();
      table.Push();
   }
   break;

   case typeFunction:
   {
      Function func = Get<Function>();
      func.Push();
   }
   break;

   case typeUserdata:
   {
      Userdata userdata = Get<Userdata>();
      userdata.Push();
   }
   break;

   case typeThread:
   {
      Thread thread = Get<Thread>();
      thread.Push();
   }
   break;

   default:
      ATLASSERT(false);
      break;
   }
}

void Value::Detach() const
{
   m_spRef->Detach();
   m_spRef.reset();

   switch (m_enType)
   {
   case typeTable:
   {
      Table table = Get<Table>();
      table.Detach();
   }
   break;

   case typeFunction:
   {
      Function func = Get<Function>();
      func.Detach();
   }
   break;

   case typeUserdata:
   {
      Userdata userdata = Get<Userdata>();
      userdata.Detach();
   }
   break;

   case typeThread:
   {
      Thread thread = Get<Thread>();
      thread.Detach();
   }
   break;
   }
}

Value Value::FromStack(State& state, int iStackIndex)
{
   lua_State* L = state.GetState();

   switch (lua_type(L, iStackIndex))
   {
   case LUA_TNONE:
   case LUA_TNIL:
   {
      Value val;
      val.Attach(std::make_shared<Ref>(state, iStackIndex));
      state.AddRef(val.GetRef());
      return val;
   }

   case LUA_TNUMBER:
   {
      Value val(lua_tonumber(L, iStackIndex));
      val.Attach(std::make_shared<Ref>(state, iStackIndex));
      state.AddRef(val.GetRef());
      return val;
   }

   case LUA_TBOOLEAN:
   {
      Value val(lua_toboolean(L, iStackIndex) != 0);
      val.Attach(std::make_shared<Ref>(state, iStackIndex));
      state.AddRef(val.GetRef());
      return val;
   }

   case LUA_TSTRING:
   {
      Value val(lua_tostring(L, iStackIndex));
      val.Attach(std::make_shared<Ref>(state, iStackIndex));
      state.AddRef(val.GetRef());
      return val;
   }

   case LUA_TTABLE:
   {
      auto spRef = std::make_shared<Ref>(state, iStackIndex);
      state.AddRef(spRef);

      return Value(Table(spRef, _T("")));
   }

   case LUA_TFUNCTION:
   {
      auto spRef = std::make_shared<Ref>(state, iStackIndex);
      state.AddRef(spRef);

      return Value(Function(spRef));
   }

   case LUA_TUSERDATA:
   {
      auto spRef = std::make_shared<Ref>(state, iStackIndex);
      state.AddRef(spRef);

      return Value(Userdata(spRef));
   }

   case LUA_TTHREAD:
   {
      auto spRef = std::make_shared<Ref>(state, iStackIndex);
      state.AddRef(spRef);

      return Value(Thread(spRef));
   }

   default:
      ATLASSERT(false); // type can't be converted into Value
      break;
   }

   return Value();
}

//
// FuncData
//

/// C++ function data (stored as user data in State)
struct FuncData
{
   /// ctor
   FuncData(T_fnCFunction fn)
      :m_fn(fn)
   {
   }

   /// pushes C closure
   static void PushCClosure(State& state, T_fnCFunction fn);

   /// returns ref to self from upvalue
   static FuncData& GetSelf(lua_State* L);

   /// function call
   static int OnFunctionCall(lua_State* L);

   /// garbage collect call
   static int OnFunctionGarbageCollect(lua_State* L);

   /// bound C++ function
   T_fnCFunction m_fn;
};

void FuncData::PushCClosure(State& state, T_fnCFunction fn)
{
   lua_State* L = state.GetState();

   // add userdata
   void* pUserdata = lua_newuserdata(L, sizeof(FuncData*));
   FuncData** pFunction = reinterpret_cast<FuncData**>(pUserdata);
   *pFunction = new FuncData(fn);

   ATLASSERT(lua_isuserdata(L, -1) != 0);

   // set up metatable
   lua_newtable(L);

   lua_pushcfunction(L, &FuncData::OnFunctionGarbageCollect);
   lua_setfield(L, -2, "__gc");

   lua_setmetatable(L, -2);

   /// push the C closure
   lua_pushcclosure(L, &FuncData::OnFunctionCall, 1);
}

FuncData& FuncData::GetSelf(lua_State* L)
{
   void* p = lua_touserdata(L, lua_upvalueindex(1));

   FuncData** ppFuncData = reinterpret_cast<FuncData**>(p);
   if (ppFuncData == nullptr)
      throw Lua::Exception(_T("function call without allocated FuncData"), L, __FILE__, __LINE__);

   FuncData* pFuncData = *ppFuncData;
   if (pFuncData == nullptr)
      throw Lua::Exception(_T("function call without set FuncData"), L, __FILE__, __LINE__);

   return *pFuncData;
}

int FuncData::OnFunctionCall(lua_State* L)
{
   FuncData& data = GetSelf(L);

   if (data.m_fn == nullptr)
      return 0; // no function bound

   // collect params
   int iStackDepth = lua_absindex(L, -1);

   State paramState(L, false);

   std::vector<Value> vecParams;
   for (int i = 1; i <= iStackDepth; i++)
      vecParams.push_back(Value::FromStack(paramState, i));

   State::TraceUpvalues(L);

   // call bound function
   std::vector<Value> vecRetVals;
   try
   {
      vecRetVals = data.m_fn(paramState, vecParams);
   }
   catch (...)
   {
      // in case of exceptions, detach all values from parameter state
      paramState.DetachAll();
      throw;
   }

   // put results on stack
   std::for_each(vecRetVals.begin(), vecRetVals.end(),
      [&](const Value& value) { value.Push(paramState); value.Detach(); });

   return vecRetVals.size();
}

int FuncData::OnFunctionGarbageCollect(lua_State* L)
{
   void* p = lua_touserdata(L, -1);

   FuncData** ppFuncData = reinterpret_cast<FuncData**>(p);
   if (ppFuncData == nullptr)
      throw Lua::Exception(_T("garbage collecting c closure without allocated FuncData"), L, __FILE__, __LINE__);

   delete *ppFuncData;
   *ppFuncData = nullptr;

   return 0;
}

//
// Lua::Function
//

Function::Function(std::shared_ptr<Ref> spRef)
   :m_spRef(spRef)
{
}

Function::Function(const Function& func)
   :m_spRef(func.GetRef())
{
}

Function& Function::operator=(const Function& func)
{
   m_spRef = func.GetRef();

   return *this;
}

Function::~Function() throw()
{
   ATLASSERT(m_spRef == nullptr || m_spRef->GetStackIndex() == -1 ||
      lua_isfunction(m_spRef->GetState().GetState(), m_spRef->GetStackIndex()) == true);

   std::weak_ptr<Ref> wpRef = m_spRef;
   m_spRef.reset();

   if (wpRef.use_count() == 1)
   {
      std::shared_ptr<Ref> spRef = wpRef.lock();
      spRef->GetState().RemoveFromStack(*spRef);
   }
}

std::vector<Value> Function::Call(int iResults, const std::vector<Value>& vecParam)
{
   State& state = m_spRef->GetState();
   lua_State* L = state.GetState();

   lua_pushvalue(L, m_spRef->GetStackIndex());

   std::for_each(vecParam.begin(), vecParam.end(),
      [&](const Value& value) { value.Push(state); value.Detach(); });

   lua_call(L, vecParam.size(), iResults);

   // collect return values
   std::vector<Value> vecResults;
   for (int i = -iResults; i <= -1; i++)
      vecResults.push_back(Value::FromStack(state, i));

   return vecResults;
}

void Function::Push()
{
   State& state = m_spRef->GetState();
   lua_State* L = state.GetState();

   lua_pushvalue(L, m_spRef->GetStackIndex());
}

void Function::Detach() const
{
   m_spRef->Detach();
   m_spRef.reset();
}

//
// Lua::Table
//

Table::Table(State& state, const CString& cszName)
   :m_cszName(cszName)
{
   lua_State* L = state.GetState();

   lua_newtable(L);

   if (!cszName.IsEmpty())
   {
      // no temporary (empty name) table? then set and get again
      lua_setglobal(L, CStringA(m_cszName).GetString());

      lua_getglobal(L, CStringA(m_cszName).GetString());
   }

   m_spRef = std::make_shared<Ref>(state, -1);
{'fraction_non_alphanumeric': 0.15163081193615544, 'fraction_numerical': 0.004626416840157298, 'mean_word_length': 3.191822882999354, 'pattern_counts': {'":': 0, '<': 97, '<?xml version=': 0, '>': 117, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7116294', 'n_tokens_mistral': 9838, 'n_tokens_neox': 9528, 'n_words': 1823}