diff --git a/CMakeLists.txt b/CMakeLists.txt index cb3340b699e..dd0bc9d4af9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -878,6 +878,8 @@ if( BUILD_SURGE_XT ) JUCE_WASAPI=1 JUCE_DIRECTSOUND=1 + JUCE_CATCH_UNHANDLED_EXCEPTIONS=$,1,0> + TARGET_JUCE_SYNTH=1 TARGET_HEADLESS=1 TARGET_JUCE_UI=1 diff --git a/src/common/LuaSupport.cpp b/src/common/LuaSupport.cpp index fa7b2825736..4c67d2171d9 100644 --- a/src/common/LuaSupport.cpp +++ b/src/common/LuaSupport.cpp @@ -17,8 +17,69 @@ #include #include #include +#include +#include #include "basic_dsp.h" +bool Surge::LuaSupport::parseStringDefiningFunction(lua_State *L, const std::string &definition, + const std::string &functionName, + std::string &errorMessage) +{ + const char *lua_script = definition.c_str(); + auto lerr = luaL_loadbuffer(L, lua_script, strlen(lua_script), "lua-script"); + if (lerr != LUA_OK) + { + if (lerr == LUA_ERRSYNTAX) + { + std::ostringstream oss; + oss << "Lua Syntax Error: " << lua_tostring(L, -1); + errorMessage = oss.str(); + } + else + { + std::ostringstream oss; + oss << "Lua Unknown Error: " << lua_tostring(L, -1); + errorMessage = oss.str(); + } + lua_pop(L, 1); + lua_pushnil(L); + return false; + } + + lerr = lua_pcall(L, 0, 0, 0); + if (lerr != LUA_OK) + { + // FIXME obviously + std::ostringstream oss; + oss << "Lua Evaluation Error: " << lua_tostring(L, -1); + errorMessage = oss.str(); + lua_pop(L, 1); + lua_pushnil(L); + return false; + } + + lua_getglobal(L, functionName.c_str()); + if (lua_isfunction(L, -1)) + return true; + + if (lua_isnil(L, -1)) + { + std::ostringstream oss; + oss << "Resolving global name '" << functionName << "' after parse returned nil." + << " Did you define the function?"; + errorMessage = oss.str(); + return false; + } + + std::ostringstream oss; + oss << "After trying to find function '" << functionName << "' found non-function type '" + << lua_typename(L, lua_type(L, -1)) << "'"; + errorMessage = oss.str(); + lua_pop(L, 1); + lua_pushnil(L); + return false; +} + int lua_limitRange(lua_State *L) { auto x = luaL_checknumber(L, -3); diff --git a/src/common/LuaSupport.h b/src/common/LuaSupport.h index 832495a215f..9e9b0db893a 100644 --- a/src/common/LuaSupport.h +++ b/src/common/LuaSupport.h @@ -43,6 +43,17 @@ namespace LuaSupport { #if HAS_LUAJIT +/* + * Given a string which is supposed to be valid lua defining a function + * with a name, parse the string, look for the function, and if the parse + * and stuff has no errors, return true with the function on the top of the + * stack, oterhwise return false with nil on top of the stack. So increases + * stack by 1. + */ + +bool parseStringDefiningFunction(lua_State *s, const std::string &definition, + const std::string &functionName, std::string &errorMessage); + /* * Call this function with the top of your stack being a * lua_function and the function will get wrapped in the standard diff --git a/src/common/dsp/WavetableScriptEvaluator.cpp b/src/common/dsp/WavetableScriptEvaluator.cpp index ab7767d2934..ca9c5ae786d 100644 --- a/src/common/dsp/WavetableScriptEvaluator.cpp +++ b/src/common/dsp/WavetableScriptEvaluator.cpp @@ -38,61 +38,49 @@ std::vector evaluateScriptAtFrame(const std::string &eqn, int resolution, auto values = std::vector(); auto wg = Surge::LuaSupport::SGLD("WavetableScript::evaluate", L); - int load_stat = luaL_loadbuffer(L, eqn.c_str(), eqn.length(), "wtScript"); - if (load_stat != LUA_OK) + std::string emsg; + auto res = Surge::LuaSupport::parseStringDefiningFunction(L, eqn.c_str(), "generate", emsg); + if (res) { - std::cout << "FIXME: Error Handling " << lua_error(L) << std::endl; - return values; - } - auto res = lua_pcall(L, 0, 0, 0); - if (res == LUA_OK) - { - lua_getglobal(L, "generate"); - if (lua_isfunction(L, -1)) + Surge::LuaSupport::setSurgeFunctionEnvironment(L); + /* + * Alright so we want the stack to be an array of 0...1 and a frame + * Right now the stack is just our generation function so + */ + lua_createtable(L, resolution, 0); + double dp = 1.0 / (resolution - 1); + for (auto i = 0; i < resolution; ++i) { - Surge::LuaSupport::setSurgeFunctionEnvironment(L); - /* - * Alright so we want the stack to be an array of 0...1 and a frame - * Right now the stack is just our generation function so - */ - lua_createtable(L, resolution, 0); - double dp = 1.0 / (resolution - 1); - for (auto i = 0; i < resolution; ++i) - { - lua_pushinteger(L, i + 1); // lua has a 1 based convention - lua_pushnumber(L, i * dp); - lua_settable(L, -3); - } - lua_pushinteger(L, frame); - auto pcr = lua_pcall(L, 2, 1, 0); - if (pcr == LUA_OK) + lua_pushinteger(L, i + 1); // lua has a 1 based convention + lua_pushnumber(L, i * dp); + lua_settable(L, -3); + } + lua_pushinteger(L, frame); + auto pcr = lua_pcall(L, 2, 1, 0); + if (pcr == LUA_OK) + { + if (lua_istable(L, -1)) { - if (lua_istable(L, -1)) + for (auto i = 0; i < resolution; ++i) { - for (auto i = 0; i < resolution; ++i) + lua_pushinteger(L, i + 1); + lua_gettable(L, -2); + if (lua_isnumber(L, -1)) + { + values.push_back(lua_tonumber(L, -1)); + } + else { - lua_pushinteger(L, i + 1); - lua_gettable(L, -2); - if (lua_isnumber(L, -1)) - { - values.push_back(lua_tonumber(L, -1)); - } - else - { - values.push_back(0.f); - } - lua_pop(L, 1); + values.push_back(0.f); } + lua_pop(L, 1); } } - else - { - std::cout << "FIXME : Error " << std::endl; - } - } - else - { } + } + else + { + std::cout << emsg << std::endl; lua_pop(L, 1); } return values; diff --git a/src/common/dsp/modulators/FormulaModulationHelper.cpp b/src/common/dsp/modulators/FormulaModulationHelper.cpp index 6e0ae035a5e..3dd24cb14c5 100644 --- a/src/common/dsp/modulators/FormulaModulationHelper.cpp +++ b/src/common/dsp/modulators/FormulaModulationHelper.cpp @@ -84,59 +84,39 @@ bool prepareForEvaluation(FormulaModulatorStorage *fs, EvaluatorState &s, bool i if (hasString) { snprintf(s.funcName, TXT_SIZE, "%s", pvf.c_str()); - s.isvalid = true; + // CHECK that I can actually get the function here + lua_getglobal(s.L, s.funcName); + s.isvalid = lua_isfunction(s.L, -1); + lua_pop(s.L, 1); } else { - const char *lua_script = fs->formulaString.c_str(); - int load_stat = luaL_loadbuffer(s.L, lua_script, strlen(lua_script), "processScript"); - if (load_stat != LUA_OK) - { - std::ostringstream oss; - oss << "Error parsing user script: " << lua_error(s.L); - s.adderror(oss.str()); - std::cout << "FIXME : Do I need to pop here?" << std::endl; - return false; - } - auto res = lua_pcall(s.L, 0, 0, 0); // FIXME error + std::string emsg; + bool res = + Surge::LuaSupport::parseStringDefiningFunction(s.L, fs->formulaString, "process", emsg); - if (res == LUA_OK) + if (res) { - // great now get the modfunc - lua_getglobal(s.L, "process"); - if (lua_isfunction(s.L, -1)) - { - // Great - rename it and nuke process - lua_setglobal(s.L, s.funcName); - lua_pushnil(s.L); - lua_setglobal(s.L, "process"); + // Great - rename it and nuke process + lua_setglobal(s.L, s.funcName); + lua_pushnil(s.L); + lua_setglobal(s.L, "process"); - // Then get it and set its env - lua_getglobal(s.L, s.funcName); - Surge::LuaSupport::setSurgeFunctionEnvironment(s.L); - lua_pop(s.L, 1); + // Then get it and set its env + lua_getglobal(s.L, s.funcName); + Surge::LuaSupport::setSurgeFunctionEnvironment(s.L); + lua_pop(s.L, 1); - s.isvalid = true; - } - else - { - // FIXME error - s.adderror("After parsing formula, no function 'process' present. You must define " - "a function called 'process' in your LUA."); - // Pop whatever I got - lua_pop(s.L, 1); - s.isvalid = false; - } + s.isvalid = true; } else { - std::ostringstream oss; - oss << "LUA Raised an error parsing formula: " << lua_tostring(s.L, -1); - s.adderror(oss.str()); + s.adderror(emsg); + lua_pop(s.L, 1); } // this happens here because we did parse it at least. Don't parse again until it is changed - lua_pushstring(s.L, lua_script); + lua_pushstring(s.L, fs->formulaString.c_str()); lua_setglobal(s.L, pvn.c_str()); } @@ -230,7 +210,7 @@ float valueAt(int phaseIntPart, float phaseFracPart, FormulaModulatorStorage *fs * values; then call my function; then update my global */ lua_getglobal(s->L, s->funcName); - if (lua_isnil(s->L, -1)) + if (!lua_isfunction(s->L, -1)) { s->isvalid = false; lua_pop(s->L, 1); diff --git a/src/headless/UnitTestsLUA.cpp b/src/headless/UnitTestsLUA.cpp index 5f6338e218e..675ac63a0c4 100644 --- a/src/headless/UnitTestsLUA.cpp +++ b/src/headless/UnitTestsLUA.cpp @@ -12,6 +12,7 @@ #include "FormulaModulationHelper.h" #include "WavetableScriptEvaluator.h" +#include "LuaSupport.h" #if HAS_LUAJIT extern "C" @@ -186,6 +187,126 @@ end } } +TEST_CASE("Parse to Function", "[lua]") +{ + SECTION("Simple Success") + { + auto fn = R"FN( +function plus_one(x) + return x + 1 +end +)FN"; + lua_State *L = lua_open(); + REQUIRE(L); + REQUIRE(lua_gettop(L) == 0); + luaL_openlibs(L); + + std::string err; + bool res = Surge::LuaSupport::parseStringDefiningFunction(L, fn, "plus_one", err); + REQUIRE(res); + REQUIRE(lua_gettop(L) == 1); + + // OK so the top of the stack should be my function. Lets call it + lua_pushnumber(L, 13); + lua_pcall(L, 1, 1, 0); + REQUIRE(lua_isnumber(L, -1)); + REQUIRE(lua_tonumber(L, -1) == 14); + REQUIRE(lua_gettop(L) == 1); + lua_pop(L, 1); + REQUIRE(lua_gettop(L) == 0); + lua_close(L); + } + SECTION("Isnt a Function") + { + auto fn = R"FN( +shazbot = 13 +)FN"; + lua_State *L = lua_open(); + REQUIRE(L); + REQUIRE(lua_gettop(L) == 0); + luaL_openlibs(L); + + std::string err; + bool res = Surge::LuaSupport::parseStringDefiningFunction(L, fn, "shazbot", err); + REQUIRE(!res); + REQUIRE(lua_gettop(L) == 1); + REQUIRE(lua_isnil(L, -1)); + REQUIRE(err == "After trying to find function 'shazbot' found non-function type 'number'"); + lua_pop(L, 1); + lua_close(L); + } + + SECTION("Isnt Found") + { + auto fn = R"FN( +function double(x) + return x * 2 +end +)FN"; + lua_State *L = lua_open(); + REQUIRE(L); + REQUIRE(lua_gettop(L) == 0); + luaL_openlibs(L); + + std::string err; + bool res = Surge::LuaSupport::parseStringDefiningFunction(L, fn, "triple", err); + REQUIRE(!res); + REQUIRE(lua_gettop(L) == 1); + REQUIRE(lua_isnil(L, -1)); + REQUIRE(err == "Resolving global name 'triple' after parse returned nil. Did you define " + "the function?"); + lua_pop(L, 1); + lua_close(L); + } + + SECTION("Doesnt Parse") + { + auto fn = R"FN( +function double(x) + if x > 0 then + return 2 + else +end +)FN"; + lua_State *L = lua_open(); + REQUIRE(L); + REQUIRE(lua_gettop(L) == 0); + luaL_openlibs(L); + + std::string err; + bool res = Surge::LuaSupport::parseStringDefiningFunction(L, fn, "triple", err); + REQUIRE(!res); + REQUIRE(lua_gettop(L) == 1); + REQUIRE(lua_isnil(L, -1)); + REQUIRE(err == "Lua Syntax Error: [string \"lua-script\"]:7: 'end' expected (to close " + "'function' at line 2) near ''"); + lua_pop(L, 1); + lua_close(L); + } + + SECTION("Doesnt Evaluate") + { + auto fn = R"FN( +-- A bit of a contrived case but that's OK +error("I will parse but will not run") +)FN"; + lua_State *L = lua_open(); + REQUIRE(L); + REQUIRE(lua_gettop(L) == 0); + luaL_openlibs(L); + + std::string err; + bool res = Surge::LuaSupport::parseStringDefiningFunction(L, fn, "triple", err); + REQUIRE(!res); + REQUIRE(lua_gettop(L) == 1); + REQUIRE(lua_isnil(L, -1)); + REQUIRE(err == + "Lua Evaluation Error: [string \"lua-script\"]:3: I will parse but will not run"); + lua_pop(L, 1); + lua_close(L); + } +} + struct formulaObservation { formulaObservation(int ip, float fp, float va)