From c2cca33c9552b5b6b2c5e5467ba036855c0eb68b Mon Sep 17 00:00:00 2001 From: Maurice Schmicking <17197791+mschmicking@users.noreply.github.com> Date: Sat, 8 Aug 2026 10:26:41 +0200 Subject: [PATCH] refactor: collapse repeated argument guards into CheckArgs Every method opened with the same 15-line preamble: an arity check, a type check per argument, then an EnsureOpen check, each throwing and returning by hand. That was 217 of 673 lines in luastate.cc, and SetGlobal was typical -- 22 lines of which 2 did the work. Adds CheckArgs(info, method, {Arg::String, ...}), which validates arity and types and throws in the established wording, so a method now opens with one line. The messages are generated rather than hardcoded, and were verified byte-identical for all fourteen distinct forms, including CollectGarbage's ', try nodelua.GC.[TYPE]' hint and the singular/plural split between 'Requires 1 Argument' and 'Requires 2 Arguments'. Also adds ThrowLuaError, which builds the message, pops the error and throws. Popping was previously written out at each of the six call sites and is easy to omit, which silently grows the stack. Replaces lua_error_message, whose callers all had to remember to pop afterwards. Extracts EnsureIndexable, the non-table check duplicated between SetField and GetField. Pop and SetTop keep their hand-written checks: their argument is optional, so CheckArgs does not apply, and the comments now say so. luastate.cc 672 -> 447 lines, guard boilerplate 217 -> 63, src/ down 152 lines overall. No behaviour change: 62/62 tests pass and all three examples run. Co-Authored-By: Claude Opus 5 --- src/luastate.cc | 315 +++++++----------------------------------------- src/luastate.h | 4 + src/utils.cc | 63 +++++++++- src/utils.h | 22 +++- 4 files changed, 126 insertions(+), 278 deletions(-) diff --git a/src/luastate.cc b/src/luastate.cc index a94b57c..ac33c52 100644 --- a/src/luastate.cc +++ b/src/luastate.cc @@ -84,6 +84,18 @@ bool LuaState::EnsureOpen(Napi::Env env) { return true; } +// Indexing a non-table raises an unprotected Lua error, which aborts the whole +// process rather than throwing. Reject it here instead. +bool LuaState::EnsureIndexable(Napi::Env env, int index, const char* method) { + if(!lua_istable(lua_, index) && lua_type(lua_, index) != LUA_TUSERDATA){ + std::string message = std::string("LuaState.") + method + + ": Value At The Given Index Is Not A Table"; + Napi::TypeError::New(env, message).ThrowAsJavaScriptException(); + return false; + } + return true; +} + int LuaState::CallFunction(lua_State* L){ const char* func_name = lua_tostring(L, lua_upvalueindex(1)); LuaState* self = static_cast(lua_touserdata(L, lua_upvalueindex(2))); @@ -124,23 +136,7 @@ int LuaState::CallFunction(lua_State* L){ Napi::Value LuaState::RegisterFunction(const Napi::CallbackInfo& info){ Napi::Env env = info.Env(); - - if(info.Length() < 2){ - Napi::TypeError::New(env, "LuaState.RegisterFunction Requires 2 Arguments").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!info[0].IsString()){ - Napi::TypeError::New(env, "LuaState.RegisterFunction Argument 1 Must Be A String").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!info[1].IsFunction()){ - Napi::TypeError::New(env, "LuaState.RegisterFunction Argument 2 Must Be A Function").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!EnsureOpen(env)){ + if(!CheckArgs(info, "RegisterFunction", {Arg::String, Arg::Function}) || !EnsureOpen(env)){ return env.Undefined(); } @@ -160,18 +156,7 @@ Napi::Value LuaState::RegisterFunction(const Napi::CallbackInfo& info){ Napi::Value LuaState::AddPackagePath(const Napi::CallbackInfo& info){ Napi::Env env = info.Env(); - - if(info.Length() < 1){ - Napi::TypeError::New(env, "LuaState.AddPackagePath Requires 1 Argument").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!info[0].IsString()){ - Napi::TypeError::New(env, "LuaState.AddPackagePath Argument 1 Must Be A String").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!EnsureOpen(env)){ + if(!CheckArgs(info, "AddPackagePath", {Arg::String}) || !EnsureOpen(env)){ return env.Undefined(); } @@ -208,28 +193,13 @@ Napi::Value LuaState::AddPackagePath(const Napi::CallbackInfo& info){ Napi::Value LuaState::LoadFile(const Napi::CallbackInfo& info){ Napi::Env env = info.Env(); - - if(info.Length() < 1){ - Napi::TypeError::New(env, "LuaState.LoadFile Requires 1 Argument").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!info[0].IsString()){ - Napi::TypeError::New(env, "LuaState.LoadFile Argument 1 Must Be A String").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!EnsureOpen(env)){ + if(!CheckArgs(info, "LoadFile", {Arg::String}) || !EnsureOpen(env)){ return env.Undefined(); } std::string file_name = info[0].As().Utf8Value(); - if(luaL_loadfile(lua_, file_name.c_str())){ - std::string message = lua_error_message(lua_, "LuaState.LoadFile: Parsing Of File " + file_name + " Has Failed:\n"); - lua_pop(lua_, 1); - Napi::Error::New(env, message).ThrowAsJavaScriptException(); - return env.Undefined(); + ThrowLuaError(env, lua_, "LuaState.LoadFile: Parsing Of File " + file_name + " Has Failed:\n"); } return env.Undefined(); @@ -237,28 +207,13 @@ Napi::Value LuaState::LoadFile(const Napi::CallbackInfo& info){ Napi::Value LuaState::LoadString(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - - if(info.Length() < 1){ - Napi::TypeError::New(env, "LuaState.LoadString Requires 1 Argument").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!info[0].IsString()){ - Napi::TypeError::New(env, "LuaState.LoadString Argument 1 Must Be A String").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!EnsureOpen(env)){ + if(!CheckArgs(info, "LoadString", {Arg::String}) || !EnsureOpen(env)){ return env.Undefined(); } std::string lua_code = info[0].As().Utf8Value(); - if(luaL_loadstring(lua_, lua_code.c_str())){ - std::string message = lua_error_message(lua_, "LuaState.LoadString: Parsing Of Lua Code Has Failed:\n"); - lua_pop(lua_, 1); - Napi::Error::New(env, message).ThrowAsJavaScriptException(); - return env.Undefined(); + ThrowLuaError(env, lua_, "LuaState.LoadString: Parsing Of Lua Code Has Failed:\n"); } return env.Undefined(); @@ -266,28 +221,13 @@ Napi::Value LuaState::LoadString(const Napi::CallbackInfo& info) { Napi::Value LuaState::DoFile(const Napi::CallbackInfo& info){ Napi::Env env = info.Env(); - - if(info.Length() < 1){ - Napi::TypeError::New(env, "LuaState.DoFile Requires 1 Argument").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!info[0].IsString()){ - Napi::TypeError::New(env, "LuaState.DoFile Argument 1 Must Be A String").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!EnsureOpen(env)){ + if(!CheckArgs(info, "DoFile", {Arg::String}) || !EnsureOpen(env)){ return env.Undefined(); } std::string file_name = info[0].As().Utf8Value(); - if(luaL_dofile(lua_, file_name.c_str())){ - std::string message = lua_error_message(lua_, "LuaState.DoFile: Execution Of File " + file_name + " Has Failed:\n"); - lua_pop(lua_, 1); - Napi::Error::New(env, message).ThrowAsJavaScriptException(); - return env.Undefined(); + ThrowLuaError(env, lua_, "LuaState.DoFile: Execution Of File " + file_name + " Has Failed:\n"); } return env.Undefined(); @@ -295,28 +235,13 @@ Napi::Value LuaState::DoFile(const Napi::CallbackInfo& info){ Napi::Value LuaState::DoString(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - - if(info.Length() < 1){ - Napi::TypeError::New(env, "LuaState.DoString Requires 1 Argument").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!info[0].IsString()){ - Napi::TypeError::New(env, "LuaState.DoString Argument 1 Must Be A String").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!EnsureOpen(env)){ + if(!CheckArgs(info, "DoString", {Arg::String}) || !EnsureOpen(env)){ return env.Undefined(); } std::string lua_code = info[0].As().Utf8Value(); - if(luaL_dostring(lua_, lua_code.c_str())){ - std::string message = lua_error_message(lua_, "LuaState.DoString: Execution Of Lua Code Has Failed:\n"); - lua_pop(lua_, 1); - Napi::Error::New(env, message).ThrowAsJavaScriptException(); - return env.Undefined(); + ThrowLuaError(env, lua_, "LuaState.DoString: Execution Of Lua Code Has Failed:\n"); } return env.Undefined(); @@ -324,18 +249,7 @@ Napi::Value LuaState::DoString(const Napi::CallbackInfo& info) { Napi::Value LuaState::SetGlobal(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - - if(info.Length() < 1){ - Napi::TypeError::New(env, "LuaState.SetGlobal Requires 1 Argument").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!info[0].IsString()){ - Napi::TypeError::New(env, "LuaState.SetGlobal Argument 1 Must Be A String").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!EnsureOpen(env)){ + if(!CheckArgs(info, "SetGlobal", {Arg::String}) || !EnsureOpen(env)){ return env.Undefined(); } @@ -347,18 +261,7 @@ Napi::Value LuaState::SetGlobal(const Napi::CallbackInfo& info) { Napi::Value LuaState::GetGlobal(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - - if(info.Length() < 1){ - Napi::TypeError::New(env, "LuaState.GetGlobal Requires 1 Argument").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!info[0].IsString()){ - Napi::TypeError::New(env, "LuaState.GetGlobal Argument 1 Must Be A String").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!EnsureOpen(env)){ + if(!CheckArgs(info, "GetGlobal", {Arg::String}) || !EnsureOpen(env)){ return env.Undefined(); } @@ -370,38 +273,19 @@ Napi::Value LuaState::GetGlobal(const Napi::CallbackInfo& info) { Napi::Value LuaState::SetField(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - - if(info.Length() < 3){ - Napi::TypeError::New(env, "LuaState.SetField Requires 3 Arguments").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!info[0].IsNumber()){ - Napi::TypeError::New(env, "LuaState.SetField Argument 1 Must Be A Number").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!info[1].IsString()){ - Napi::TypeError::New(env, "LuaState.SetField Argument 2 Must Be A String").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!EnsureOpen(env)){ + if(!CheckArgs(info, "SetField", {Arg::Number, Arg::String, Arg::Any}) || !EnsureOpen(env)){ return env.Undefined(); } // Resolve the index before pushing: pushing the value shifts every relative // index by one, which would otherwise leave us assigning into the value itself. int index = abs_index(lua_, info[0].As().Int32Value()); - std::string field_name = info[1].As().Utf8Value(); - - // Indexing a non-table raises an unprotected Lua error, which aborts the whole - // process rather than throwing. Reject it here instead. - if(!lua_istable(lua_, index) && lua_type(lua_, index) != LUA_TUSERDATA){ - Napi::TypeError::New(env, "LuaState.SetField: Value At The Given Index Is Not A Table").ThrowAsJavaScriptException(); + if(!EnsureIndexable(env, index, "SetField")){ return env.Undefined(); } + std::string field_name = info[1].As().Utf8Value(); + // Push the value, not the key: lua_setfield takes the key as a C string and // pops the value from the top of the stack. push_value_to_lua(lua_, info[2]); @@ -412,36 +296,16 @@ Napi::Value LuaState::SetField(const Napi::CallbackInfo& info) { Napi::Value LuaState::GetField(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - - if(info.Length() < 2){ - Napi::TypeError::New(env, "LuaState.GetField Requires 2 Arguments").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!info[0].IsNumber()){ - Napi::TypeError::New(env, "LuaState.GetField Argument 1 Must Be A Number").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!info[1].IsString()){ - Napi::TypeError::New(env, "LuaState.GetField Argument 2 Must Be A String").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!EnsureOpen(env)){ + if(!CheckArgs(info, "GetField", {Arg::Number, Arg::String}) || !EnsureOpen(env)){ return env.Undefined(); } int index = abs_index(lua_, info[0].As().Int32Value()); - std::string field_name = info[1].As().Utf8Value(); - - // Indexing a non-table raises an unprotected Lua error, which aborts the whole - // process rather than throwing. Reject it here instead. - if(!lua_istable(lua_, index) && lua_type(lua_, index) != LUA_TUSERDATA){ - Napi::TypeError::New(env, "LuaState.GetField: Value At The Given Index Is Not A Table").ThrowAsJavaScriptException(); + if(!EnsureIndexable(env, index, "GetField")){ return env.Undefined(); } + std::string field_name = info[1].As().Utf8Value(); lua_getfield(lua_, index, field_name.c_str()); return env.Undefined(); @@ -449,44 +313,16 @@ Napi::Value LuaState::GetField(const Napi::CallbackInfo& info) { Napi::Value LuaState::ToValue(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - - if(info.Length() < 1){ - Napi::TypeError::New(env, "LuaState.ToValue Requires 1 Argument").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!info[0].IsNumber()){ - Napi::TypeError::New(env, "LuaState.ToValue Argument 1 Must Be A Number").ThrowAsJavaScriptException(); + if(!CheckArgs(info, "ToValue", {Arg::Number}) || !EnsureOpen(env)){ return env.Undefined(); } - if(!EnsureOpen(env)){ - return env.Undefined(); - } - - int index = info[0].As().Int32Value(); - return lua_to_value(env, lua_, index); + return lua_to_value(env, lua_, info[0].As().Int32Value()); } Napi::Value LuaState::Call(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - - if(info.Length() < 2){ - Napi::TypeError::New(env, "LuaState.Call Requires 2 Arguments").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!info[0].IsNumber()){ - Napi::TypeError::New(env, "LuaState.Call Argument 1 Must Be A Number").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!info[1].IsNumber()){ - Napi::TypeError::New(env, "LuaState.Call Argument 2 Must Be A Number").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!EnsureOpen(env)){ + if(!CheckArgs(info, "Call", {Arg::Number, Arg::Number}) || !EnsureOpen(env)){ return env.Undefined(); } @@ -494,10 +330,7 @@ Napi::Value LuaState::Call(const Napi::CallbackInfo& info) { int results = info[1].As().Int32Value(); if(lua_pcall(lua_, args, results, 0)){ - std::string message = lua_error_message(lua_, "LuaState.Call: Execution Of Lua Function Has Failed:\n"); - lua_pop(lua_, 1); - Napi::Error::New(env, message).ThrowAsJavaScriptException(); - return env.Undefined(); + ThrowLuaError(env, lua_, "LuaState.Call: Execution Of Lua Function Has Failed:\n"); } return env.Undefined(); @@ -505,47 +338,21 @@ Napi::Value LuaState::Call(const Napi::CallbackInfo& info) { Napi::Value LuaState::Yield(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - - if(info.Length() < 1){ - Napi::TypeError::New(env, "LuaState.Yield Requires 1 Argument").ThrowAsJavaScriptException(); + if(!CheckArgs(info, "Yield", {Arg::Number}) || !EnsureOpen(env)){ return env.Undefined(); } - if(!info[0].IsNumber()){ - Napi::TypeError::New(env, "LuaState.Yield Argument 1 Must Be A Number").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!EnsureOpen(env)){ - return env.Undefined(); - } - - int args = info[0].As().Int32Value(); - lua_yield(lua_, args); - + lua_yield(lua_, info[0].As().Int32Value()); return env.Undefined(); } Napi::Value LuaState::Resume(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - - if(info.Length() < 1){ - Napi::TypeError::New(env, "LuaState.Resume Requires 1 Argument").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!info[0].IsNumber()){ - Napi::TypeError::New(env, "LuaState.Resume Argument 1 Must Be A Number").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!EnsureOpen(env)){ + if(!CheckArgs(info, "Resume", {Arg::Number}) || !EnsureOpen(env)){ return env.Undefined(); } - int args = info[0].As().Int32Value(); - int status = lua_resume(lua_, args); - + int status = lua_resume(lua_, info[0].As().Int32Value()); return Napi::Number::New(env, status); } @@ -561,7 +368,6 @@ Napi::Value LuaState::Close(const Napi::CallbackInfo& info) { Napi::Value LuaState::Status(const Napi::CallbackInfo& info){ Napi::Env env = info.Env(); - if(!EnsureOpen(env)){ return env.Undefined(); } @@ -571,18 +377,7 @@ Napi::Value LuaState::Status(const Napi::CallbackInfo& info){ Napi::Value LuaState::CollectGarbage(const Napi::CallbackInfo& info){ Napi::Env env = info.Env(); - - if(info.Length() < 1){ - Napi::TypeError::New(env, "LuaState.CollectGarbage Requires 1 Argument").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!info[0].IsNumber()){ - Napi::TypeError::New(env, "LuaState.CollectGarbage Argument 1 Must Be A Number, try nodelua.GC.[TYPE]").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!EnsureOpen(env)){ + if(!CheckArgs(info, "CollectGarbage", {Arg::Number}, ", try nodelua.GC.[TYPE]") || !EnsureOpen(env)){ return env.Undefined(); } @@ -592,13 +387,7 @@ Napi::Value LuaState::CollectGarbage(const Napi::CallbackInfo& info){ Napi::Value LuaState::Push(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - - if(info.Length() < 1){ - Napi::TypeError::New(env, "LuaState.Push Requires 1 Argument").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!EnsureOpen(env)){ + if(!CheckArgs(info, "Push", {Arg::Any}) || !EnsureOpen(env)){ return env.Undefined(); } @@ -608,11 +397,11 @@ Napi::Value LuaState::Push(const Napi::CallbackInfo& info) { Napi::Value LuaState::Pop(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - if(!EnsureOpen(env)){ return env.Undefined(); } + // The count is optional and defaults to 1, so this cannot use CheckArgs. int pop_n = 1; if(info.Length() > 0 && info[0].IsNumber()){ pop_n = info[0].As().Int32Value(); @@ -624,7 +413,6 @@ Napi::Value LuaState::Pop(const Napi::CallbackInfo& info) { Napi::Value LuaState::GetTop(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - if(!EnsureOpen(env)){ return env.Undefined(); } @@ -634,11 +422,11 @@ Napi::Value LuaState::GetTop(const Napi::CallbackInfo& info) { Napi::Value LuaState::SetTop(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - if(!EnsureOpen(env)){ return env.Undefined(); } + // The index is optional and defaults to 0, so this cannot use CheckArgs. int set_n = 0; if(info.Length() > 0 && info[0].IsNumber()){ set_n = info[0].As().Int32Value(); @@ -650,23 +438,10 @@ Napi::Value LuaState::SetTop(const Napi::CallbackInfo& info) { Napi::Value LuaState::Replace(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - - if(info.Length() < 1){ - Napi::TypeError::New(env, "LuaState.Replace Requires 1 Argument").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - if(!info[0].IsNumber()){ - Napi::TypeError::New(env, "LuaState.Replace Argument 1 Must Be A Number").ThrowAsJavaScriptException(); + if(!CheckArgs(info, "Replace", {Arg::Number}) || !EnsureOpen(env)){ return env.Undefined(); } - if(!EnsureOpen(env)){ - return env.Undefined(); - } - - int index = info[0].As().Int32Value(); - lua_replace(lua_, index); - + lua_replace(lua_, info[0].As().Int32Value()); return env.Undefined(); } diff --git a/src/luastate.h b/src/luastate.h index 0d08e91..1b64ea1 100644 --- a/src/luastate.h +++ b/src/luastate.h @@ -35,6 +35,10 @@ class LuaState : public Napi::ObjectWrap { // calling into a closed LuaState raises instead of dereferencing freed memory. bool EnsureOpen(Napi::Env env); + // Throws and returns false if the value at `index` cannot be indexed, which + // would otherwise raise an unprotected Lua error and abort the process. + bool EnsureIndexable(Napi::Env env, int index, const char* method); + Napi::Value Close(const Napi::CallbackInfo& info); Napi::Value CollectGarbage(const Napi::CallbackInfo& info); diff --git a/src/utils.cc b/src/utils.cc index c69f78a..d3bd225 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -1,5 +1,63 @@ #include "utils.h" +namespace { + +const char* ArgTypeName(Arg kind){ + switch(kind){ + case Arg::Number: return "A Number"; + case Arg::String: return "A String"; + case Arg::Function: return "A Function"; + default: return "A Value"; + } +} + +bool ArgMatches(Napi::Value value, Arg kind){ + switch(kind){ + case Arg::Number: return value.IsNumber(); + case Arg::String: return value.IsString(); + case Arg::Function: return value.IsFunction(); + default: return true; + } +} + +} // namespace + +bool CheckArgs(const Napi::CallbackInfo& info, const char* method, + std::initializer_list expected, const char* hint){ + Napi::Env env = info.Env(); + const size_t required = expected.size(); + + if(info.Length() < required){ + std::string message = std::string("LuaState.") + method + " Requires " + + std::to_string(required) + (required == 1 ? " Argument" : " Arguments"); + Napi::TypeError::New(env, message).ThrowAsJavaScriptException(); + return false; + } + + size_t i = 0; + for(Arg kind : expected){ + if(!ArgMatches(info[i], kind)){ + std::string message = std::string("LuaState.") + method + " Argument " + + std::to_string(i + 1) + " Must Be " + ArgTypeName(kind); + if(hint != NULL){ + message += hint; + } + Napi::TypeError::New(env, message).ThrowAsJavaScriptException(); + return false; + } + ++i; + } + + return true; +} + +void ThrowLuaError(Napi::Env env, lua_State* L, const std::string& prefix){ + const char* message = lua_tostring(L, -1); + std::string full = prefix + (message ? message : "unknown error"); + lua_pop(L, 1); + Napi::Error::New(env, full).ThrowAsJavaScriptException(); +} + int abs_index(lua_State* L, int index) { if(index > 0 || index <= LUA_REGISTRYINDEX){ return index; @@ -63,8 +121,3 @@ void push_value_to_lua(lua_State* L, Napi::Value value){ lua_pushnil(L); } } - -std::string lua_error_message(lua_State* L, const std::string& prefix){ - const char* message = lua_tostring(L, -1); - return prefix + (message ? message : "unknown error"); -} diff --git a/src/utils.h b/src/utils.h index 9fce934..f58f846 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,6 +1,7 @@ #ifndef UTILS_H #define UTILS_H +#include #include #include @@ -9,6 +10,24 @@ extern "C" { #include } +// Kinds of JavaScript argument the binding methods accept. `Any` only asserts that +// the argument was supplied. +enum class Arg { Number, String, Function, Any }; + +// Checks arity and argument types for a LuaState method, throwing a TypeError in +// this project's established wording if either is wrong. `hint` is appended to a +// type error when a method wants to point at the constants to use. +// +// Returns false when it threw, so callers read as: +// if(!CheckArgs(info, "SetField", {Arg::Number, Arg::String, Arg::Any})) return env.Undefined(); +bool CheckArgs(const Napi::CallbackInfo& info, const char* method, + std::initializer_list expected, const char* hint = NULL); + +// Throws the Lua error on top of the stack as a JavaScript Error prefixed with +// `prefix`, and pops it. Popping here rather than at each call site is the point: +// leaving the error behind silently grows the stack. +void ThrowLuaError(Napi::Env env, lua_State* L, const std::string& prefix); + // Resolves a relative stack index to an absolute one, leaving positive indices // and pseudo-indices (LUA_GLOBALSINDEX and friends) untouched. Needed wherever we // push onto the stack before consuming a caller-supplied index. @@ -22,7 +41,4 @@ Napi::Value lua_to_value(Napi::Env env, lua_State* L, int index); // are pushed as nil. void push_value_to_lua(lua_State* L, Napi::Value value); -// Builds "" from the error at the top of the stack. -std::string lua_error_message(lua_State* L, const std::string& prefix); - #endif