mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-13 21:40:43 +00:00
3b0e93bec9
# What's changed? Add program argument passing to scripts run using the Luau REPL! You can now pass `--program-args` (or shorthand `-a`) to the REPL which will treat all remaining arguments as arguments to pass to executed scripts. These values can be accessed through variadic argument expansion. You can read these values like so: ``` local args = {...} -- gets you an array of all the arguments ``` For example if we run the following script like `luau test.lua -a test1 test2 test3`: ``` -- test.lua print(...) ``` you should get the output: ``` test1 test2 test3 ``` ### Native Code Generation * Improve A64 lowering for vector operations by using vector instructions * Fix lowering issue in IR value location tracking! - A developer reported a divergence between code run in the VM and Native Code Generation which we have now fixed ### New Type Solver * Apply substitution to type families, and emit new constraints to reduce those further * More progress on reducing comparison (`lt/le`)type families * Resolve two major sources of cyclic types in the new solver ### Miscellaneous * Turned internal compiler errors (ICE's) into warnings and errors ------- Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
899 lines
25 KiB
C++
899 lines
25 KiB
C++
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
#include "Repl.h"
|
|
|
|
#include "Luau/Common.h"
|
|
#include "lua.h"
|
|
#include "lualib.h"
|
|
|
|
#include "Luau/CodeGen.h"
|
|
#include "Luau/Compiler.h"
|
|
#include "Luau/Parser.h"
|
|
#include "Luau/TimeTrace.h"
|
|
|
|
#include "Coverage.h"
|
|
#include "FileUtils.h"
|
|
#include "Flags.h"
|
|
#include "Profiler.h"
|
|
#include "Require.h"
|
|
|
|
#include "isocline.h"
|
|
|
|
#include <memory>
|
|
|
|
#ifdef _WIN32
|
|
#include <io.h>
|
|
#include <fcntl.h>
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#ifdef __linux__
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef CALLGRIND
|
|
#include <valgrind/callgrind.h>
|
|
#endif
|
|
|
|
#include <locale.h>
|
|
#include <signal.h>
|
|
|
|
LUAU_FASTFLAG(DebugLuauTimeTracing)
|
|
|
|
LUAU_FASTFLAGVARIABLE(LuauUpdatedRequireByStringSemantics, false)
|
|
|
|
constexpr int MaxTraversalLimit = 50;
|
|
|
|
static bool codegen = false;
|
|
static int program_argc = 0;
|
|
char** program_argv = nullptr;
|
|
|
|
// Ctrl-C handling
|
|
static void sigintCallback(lua_State* L, int gc)
|
|
{
|
|
if (gc >= 0)
|
|
return;
|
|
|
|
lua_callbacks(L)->interrupt = NULL;
|
|
|
|
lua_rawcheckstack(L, 1); // reserve space for error string
|
|
luaL_error(L, "Execution interrupted");
|
|
}
|
|
|
|
static lua_State* replState = NULL;
|
|
|
|
#ifdef _WIN32
|
|
BOOL WINAPI sigintHandler(DWORD signal)
|
|
{
|
|
if (signal == CTRL_C_EVENT && replState)
|
|
lua_callbacks(replState)->interrupt = &sigintCallback;
|
|
return TRUE;
|
|
}
|
|
#else
|
|
static void sigintHandler(int signum)
|
|
{
|
|
if (signum == SIGINT && replState)
|
|
lua_callbacks(replState)->interrupt = &sigintCallback;
|
|
}
|
|
#endif
|
|
|
|
struct GlobalOptions
|
|
{
|
|
int optimizationLevel = 1;
|
|
int debugLevel = 1;
|
|
} globalOptions;
|
|
|
|
static Luau::CompileOptions copts()
|
|
{
|
|
Luau::CompileOptions result = {};
|
|
result.optimizationLevel = globalOptions.optimizationLevel;
|
|
result.debugLevel = globalOptions.debugLevel;
|
|
result.coverageLevel = coverageActive() ? 2 : 0;
|
|
|
|
return result;
|
|
}
|
|
|
|
static int lua_loadstring(lua_State* L)
|
|
{
|
|
size_t l = 0;
|
|
const char* s = luaL_checklstring(L, 1, &l);
|
|
const char* chunkname = luaL_optstring(L, 2, s);
|
|
|
|
lua_setsafeenv(L, LUA_ENVIRONINDEX, false);
|
|
|
|
std::string bytecode = Luau::compile(std::string(s, l), copts());
|
|
if (luau_load(L, chunkname, bytecode.data(), bytecode.size(), 0) == 0)
|
|
return 1;
|
|
|
|
lua_pushnil(L);
|
|
lua_insert(L, -2); // put before error message
|
|
return 2; // return nil plus error message
|
|
}
|
|
|
|
static int finishrequire(lua_State* L)
|
|
{
|
|
if (lua_isstring(L, -1))
|
|
lua_error(L);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int lua_require(lua_State* L)
|
|
{
|
|
if (FFlag::LuauUpdatedRequireByStringSemantics)
|
|
{
|
|
std::string name = luaL_checkstring(L, 1);
|
|
|
|
RequireResolver::ResolvedRequire resolvedRequire = RequireResolver::resolveRequire(L, std::move(name));
|
|
|
|
if (resolvedRequire.status == RequireResolver::ModuleStatus::Cached)
|
|
return finishrequire(L);
|
|
else if (resolvedRequire.status == RequireResolver::ModuleStatus::NotFound)
|
|
luaL_errorL(L, "error requiring module");
|
|
|
|
// module needs to run in a new thread, isolated from the rest
|
|
// note: we create ML on main thread so that it doesn't inherit environment of L
|
|
lua_State* GL = lua_mainthread(L);
|
|
lua_State* ML = lua_newthread(GL);
|
|
lua_xmove(GL, L, 1);
|
|
|
|
// new thread needs to have the globals sandboxed
|
|
luaL_sandboxthread(ML);
|
|
|
|
// now we can compile & run module on the new thread
|
|
std::string bytecode = Luau::compile(resolvedRequire.sourceCode, copts());
|
|
if (luau_load(ML, resolvedRequire.chunkName.c_str(), bytecode.data(), bytecode.size(), 0) == 0)
|
|
{
|
|
if (codegen)
|
|
Luau::CodeGen::compile(ML, -1);
|
|
|
|
if (coverageActive())
|
|
coverageTrack(ML, -1);
|
|
|
|
int status = lua_resume(ML, L, 0);
|
|
|
|
if (status == 0)
|
|
{
|
|
if (lua_gettop(ML) == 0)
|
|
lua_pushstring(ML, "module must return a value");
|
|
else if (!lua_istable(ML, -1) && !lua_isfunction(ML, -1))
|
|
lua_pushstring(ML, "module must return a table or function");
|
|
}
|
|
else if (status == LUA_YIELD)
|
|
{
|
|
lua_pushstring(ML, "module can not yield");
|
|
}
|
|
else if (!lua_isstring(ML, -1))
|
|
{
|
|
lua_pushstring(ML, "unknown error while running module");
|
|
}
|
|
}
|
|
|
|
// there's now a return value on top of ML; L stack: _MODULES ML
|
|
lua_xmove(ML, L, 1);
|
|
lua_pushvalue(L, -1);
|
|
lua_setfield(L, -4, resolvedRequire.absolutePath.c_str());
|
|
|
|
// L stack: _MODULES ML result
|
|
return finishrequire(L);
|
|
}
|
|
else
|
|
{
|
|
std::string name = luaL_checkstring(L, 1);
|
|
std::string chunkname = "=" + name;
|
|
|
|
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
|
|
|
|
// return the module from the cache
|
|
lua_getfield(L, -1, name.c_str());
|
|
if (!lua_isnil(L, -1))
|
|
{
|
|
// L stack: _MODULES result
|
|
return finishrequire(L);
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
std::optional<std::string> source = readFile(name + ".luau");
|
|
if (!source)
|
|
{
|
|
source = readFile(name + ".lua"); // try .lua if .luau doesn't exist
|
|
if (!source)
|
|
luaL_argerrorL(L, 1, ("error loading " + name).c_str()); // if neither .luau nor .lua exist, we have an error
|
|
}
|
|
|
|
// module needs to run in a new thread, isolated from the rest
|
|
// note: we create ML on main thread so that it doesn't inherit environment of L
|
|
lua_State* GL = lua_mainthread(L);
|
|
lua_State* ML = lua_newthread(GL);
|
|
lua_xmove(GL, L, 1);
|
|
// new thread needs to have the globals sandboxed
|
|
luaL_sandboxthread(ML);
|
|
|
|
// now we can compile & run module on the new thread
|
|
std::string bytecode = Luau::compile(*source, copts());
|
|
if (luau_load(ML, chunkname.c_str(), bytecode.data(), bytecode.size(), 0) == 0)
|
|
{
|
|
if (codegen)
|
|
Luau::CodeGen::compile(ML, -1);
|
|
if (coverageActive())
|
|
coverageTrack(ML, -1);
|
|
int status = lua_resume(ML, L, 0);
|
|
if (status == 0)
|
|
{
|
|
if (lua_gettop(ML) == 0)
|
|
lua_pushstring(ML, "module must return a value");
|
|
else if (!lua_istable(ML, -1) && !lua_isfunction(ML, -1))
|
|
lua_pushstring(ML, "module must return a table or function");
|
|
}
|
|
else if (status == LUA_YIELD)
|
|
{
|
|
lua_pushstring(ML, "module can not yield");
|
|
}
|
|
else if (!lua_isstring(ML, -1))
|
|
{
|
|
lua_pushstring(ML, "unknown error while running module");
|
|
}
|
|
}
|
|
// there's now a return value on top of ML; L stack: _MODULES ML
|
|
lua_xmove(ML, L, 1);
|
|
lua_pushvalue(L, -1);
|
|
lua_setfield(L, -4, name.c_str());
|
|
|
|
// L stack: _MODULES ML result
|
|
return finishrequire(L);
|
|
}
|
|
}
|
|
|
|
static int lua_collectgarbage(lua_State* L)
|
|
{
|
|
const char* option = luaL_optstring(L, 1, "collect");
|
|
|
|
if (strcmp(option, "collect") == 0)
|
|
{
|
|
lua_gc(L, LUA_GCCOLLECT, 0);
|
|
return 0;
|
|
}
|
|
|
|
if (strcmp(option, "count") == 0)
|
|
{
|
|
int c = lua_gc(L, LUA_GCCOUNT, 0);
|
|
lua_pushnumber(L, c);
|
|
return 1;
|
|
}
|
|
|
|
luaL_error(L, "collectgarbage must be called with 'count' or 'collect'");
|
|
}
|
|
|
|
#ifdef CALLGRIND
|
|
static int lua_callgrind(lua_State* L)
|
|
{
|
|
const char* option = luaL_checkstring(L, 1);
|
|
|
|
if (strcmp(option, "running") == 0)
|
|
{
|
|
int r = RUNNING_ON_VALGRIND;
|
|
lua_pushboolean(L, r);
|
|
return 1;
|
|
}
|
|
|
|
if (strcmp(option, "zero") == 0)
|
|
{
|
|
CALLGRIND_ZERO_STATS;
|
|
return 0;
|
|
}
|
|
|
|
if (strcmp(option, "dump") == 0)
|
|
{
|
|
const char* name = luaL_checkstring(L, 2);
|
|
|
|
CALLGRIND_DUMP_STATS_AT(name);
|
|
return 0;
|
|
}
|
|
|
|
luaL_error(L, "callgrind must be called with one of 'running', 'zero', 'dump'");
|
|
}
|
|
#endif
|
|
|
|
void setupState(lua_State* L)
|
|
{
|
|
if (codegen)
|
|
Luau::CodeGen::create(L);
|
|
|
|
luaL_openlibs(L);
|
|
|
|
static const luaL_Reg funcs[] = {
|
|
{"loadstring", lua_loadstring},
|
|
{"require", lua_require},
|
|
{"collectgarbage", lua_collectgarbage},
|
|
#ifdef CALLGRIND
|
|
{"callgrind", lua_callgrind},
|
|
#endif
|
|
{NULL, NULL},
|
|
};
|
|
|
|
lua_pushvalue(L, LUA_GLOBALSINDEX);
|
|
luaL_register(L, NULL, funcs);
|
|
lua_pop(L, 1);
|
|
|
|
luaL_sandbox(L);
|
|
}
|
|
|
|
void setupArguments(lua_State* L, int argc, char** argv)
|
|
{
|
|
for (int i = 0; i < argc; ++i)
|
|
lua_pushstring(L, argv[i]);
|
|
}
|
|
|
|
std::string runCode(lua_State* L, const std::string& source)
|
|
{
|
|
std::string bytecode = Luau::compile(source, copts());
|
|
|
|
if (luau_load(L, "=stdin", bytecode.data(), bytecode.size(), 0) != 0)
|
|
{
|
|
size_t len;
|
|
const char* msg = lua_tolstring(L, -1, &len);
|
|
|
|
std::string error(msg, len);
|
|
lua_pop(L, 1);
|
|
|
|
return error;
|
|
}
|
|
|
|
lua_State* T = lua_newthread(L);
|
|
|
|
lua_pushvalue(L, -2);
|
|
lua_remove(L, -3);
|
|
lua_xmove(L, T, 1);
|
|
|
|
int status = lua_resume(T, NULL, 0);
|
|
|
|
if (status == 0)
|
|
{
|
|
int n = lua_gettop(T);
|
|
|
|
if (n)
|
|
{
|
|
luaL_checkstack(T, LUA_MINSTACK, "too many results to print");
|
|
lua_getglobal(T, "_PRETTYPRINT");
|
|
// If _PRETTYPRINT is nil, then use the standard print function instead
|
|
if (lua_isnil(T, -1))
|
|
{
|
|
lua_pop(T, 1);
|
|
lua_getglobal(T, "print");
|
|
}
|
|
lua_insert(T, 1);
|
|
lua_pcall(T, n, 0, 0);
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
return std::string();
|
|
}
|
|
else
|
|
{
|
|
std::string error;
|
|
|
|
if (status == LUA_YIELD)
|
|
{
|
|
error = "thread yielded unexpectedly";
|
|
}
|
|
else if (const char* str = lua_tostring(T, -1))
|
|
{
|
|
error = str;
|
|
}
|
|
|
|
error += "\nstack backtrace:\n";
|
|
error += lua_debugtrace(T);
|
|
|
|
lua_pop(L, 1);
|
|
return error;
|
|
}
|
|
}
|
|
|
|
// Replaces the top of the lua stack with the metatable __index for the value
|
|
// if it exists. Returns true iff __index exists.
|
|
static bool tryReplaceTopWithIndex(lua_State* L)
|
|
{
|
|
if (luaL_getmetafield(L, -1, "__index"))
|
|
{
|
|
// Remove the table leaving __index on the top of stack
|
|
lua_remove(L, -2);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// This function is similar to lua_gettable, but it avoids calling any
|
|
// lua callback functions (e.g. __index) which might modify the Lua VM state.
|
|
static void safeGetTable(lua_State* L, int tableIndex)
|
|
{
|
|
lua_pushvalue(L, tableIndex); // Duplicate the table
|
|
|
|
// The loop invariant is that the table to search is at -1
|
|
// and the key is at -2.
|
|
for (int loopCount = 0;; loopCount++)
|
|
{
|
|
lua_pushvalue(L, -2); // Duplicate the key
|
|
lua_rawget(L, -2); // Try to find the key
|
|
if (!lua_isnil(L, -1) || loopCount >= MaxTraversalLimit)
|
|
{
|
|
// Either the key has been found, and/or we have reached the max traversal limit
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
lua_pop(L, 1); // Pop the nil result
|
|
if (!luaL_getmetafield(L, -1, "__index"))
|
|
{
|
|
lua_pushnil(L);
|
|
break;
|
|
}
|
|
else if (lua_istable(L, -1))
|
|
{
|
|
// Replace the current table being searched with __index table
|
|
lua_replace(L, -2);
|
|
}
|
|
else
|
|
{
|
|
lua_pop(L, 1); // Pop the value
|
|
lua_pushnil(L);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
lua_remove(L, -2); // Remove the table
|
|
lua_remove(L, -2); // Remove the original key
|
|
}
|
|
|
|
// completePartialMatches finds keys that match the specified 'prefix'
|
|
// Note: the table/object to be searched must be on the top of the Lua stack
|
|
static void completePartialMatches(lua_State* L, bool completeOnlyFunctions, const std::string& editBuffer, std::string_view prefix,
|
|
const AddCompletionCallback& addCompletionCallback)
|
|
{
|
|
for (int i = 0; i < MaxTraversalLimit && lua_istable(L, -1); i++)
|
|
{
|
|
// table, key
|
|
lua_pushnil(L);
|
|
|
|
// Loop over all the keys in the current table
|
|
while (lua_next(L, -2) != 0)
|
|
{
|
|
if (lua_type(L, -2) == LUA_TSTRING)
|
|
{
|
|
// table, key, value
|
|
std::string_view key = lua_tostring(L, -2);
|
|
int valueType = lua_type(L, -1);
|
|
|
|
// If the last separator was a ':' (i.e. a method call) then only functions should be completed.
|
|
bool requiredValueType = (!completeOnlyFunctions || valueType == LUA_TFUNCTION);
|
|
|
|
if (!key.empty() && requiredValueType && Luau::startsWith(key, prefix))
|
|
{
|
|
std::string completedComponent(key.substr(prefix.size()));
|
|
std::string completion(editBuffer + completedComponent);
|
|
if (valueType == LUA_TFUNCTION)
|
|
{
|
|
// Add an opening paren for function calls by default.
|
|
completion += "(";
|
|
}
|
|
addCompletionCallback(completion, std::string(key));
|
|
}
|
|
}
|
|
lua_pop(L, 1);
|
|
}
|
|
|
|
// Replace the current table being searched with an __index table if one exists
|
|
if (!tryReplaceTopWithIndex(L))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void completeIndexer(lua_State* L, const std::string& editBuffer, const AddCompletionCallback& addCompletionCallback)
|
|
{
|
|
std::string_view lookup = editBuffer;
|
|
bool completeOnlyFunctions = false;
|
|
|
|
// Push the global variable table to begin the search
|
|
lua_pushvalue(L, LUA_GLOBALSINDEX);
|
|
|
|
for (;;)
|
|
{
|
|
size_t sep = lookup.find_first_of(".:");
|
|
std::string_view prefix = lookup.substr(0, sep);
|
|
|
|
if (sep == std::string_view::npos)
|
|
{
|
|
completePartialMatches(L, completeOnlyFunctions, editBuffer, prefix, addCompletionCallback);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// find the key in the table
|
|
lua_pushlstring(L, prefix.data(), prefix.size());
|
|
safeGetTable(L, -2);
|
|
lua_remove(L, -2);
|
|
|
|
if (lua_istable(L, -1) || tryReplaceTopWithIndex(L))
|
|
{
|
|
completeOnlyFunctions = lookup[sep] == ':';
|
|
lookup.remove_prefix(sep + 1);
|
|
}
|
|
else
|
|
{
|
|
// Unable to search for keys, so stop searching
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
}
|
|
|
|
void getCompletions(lua_State* L, const std::string& editBuffer, const AddCompletionCallback& addCompletionCallback)
|
|
{
|
|
completeIndexer(L, editBuffer, addCompletionCallback);
|
|
}
|
|
|
|
static void icGetCompletions(ic_completion_env_t* cenv, const char* editBuffer)
|
|
{
|
|
auto* L = reinterpret_cast<lua_State*>(ic_completion_arg(cenv));
|
|
|
|
getCompletions(L, std::string(editBuffer), [cenv](const std::string& completion, const std::string& display) {
|
|
ic_add_completion_ex(cenv, completion.data(), display.data(), nullptr);
|
|
});
|
|
}
|
|
|
|
static bool isMethodOrFunctionChar(const char* s, long len)
|
|
{
|
|
char c = *s;
|
|
return len == 1 && (isalnum(c) || c == '.' || c == ':' || c == '_');
|
|
}
|
|
|
|
static void completeRepl(ic_completion_env_t* cenv, const char* editBuffer)
|
|
{
|
|
ic_complete_word(cenv, editBuffer, icGetCompletions, isMethodOrFunctionChar);
|
|
}
|
|
|
|
static void loadHistory(const char* name)
|
|
{
|
|
std::string path;
|
|
|
|
if (const char* home = getenv("HOME"))
|
|
{
|
|
path = joinPaths(home, name);
|
|
}
|
|
else if (const char* userProfile = getenv("USERPROFILE"))
|
|
{
|
|
path = joinPaths(userProfile, name);
|
|
}
|
|
|
|
if (!path.empty())
|
|
ic_set_history(path.c_str(), -1 /* default entries (= 200) */);
|
|
}
|
|
|
|
static void runReplImpl(lua_State* L)
|
|
{
|
|
ic_set_default_completer(completeRepl, L);
|
|
|
|
// Reset the locale to C
|
|
setlocale(LC_ALL, "C");
|
|
|
|
// Make brace matching easier to see
|
|
ic_style_def("ic-bracematch", "teal");
|
|
|
|
// Prevent auto insertion of braces
|
|
ic_enable_brace_insertion(false);
|
|
|
|
// Loads history from the given file; isocline automatically saves the history on process exit
|
|
loadHistory(".luau_history");
|
|
|
|
std::string buffer;
|
|
|
|
for (;;)
|
|
{
|
|
const char* prompt = buffer.empty() ? "" : ">";
|
|
std::unique_ptr<char, void (*)(void*)> line(ic_readline(prompt), free);
|
|
if (!line)
|
|
break;
|
|
|
|
if (buffer.empty() && runCode(L, std::string("return ") + line.get()) == std::string())
|
|
{
|
|
ic_history_add(line.get());
|
|
continue;
|
|
}
|
|
|
|
if (!buffer.empty())
|
|
buffer += "\n";
|
|
buffer += line.get();
|
|
|
|
std::string error = runCode(L, buffer);
|
|
|
|
if (error.length() >= 5 && error.compare(error.length() - 5, 5, "<eof>") == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (error.length())
|
|
{
|
|
fprintf(stdout, "%s\n", error.c_str());
|
|
}
|
|
|
|
ic_history_add(buffer.c_str());
|
|
buffer.clear();
|
|
}
|
|
}
|
|
|
|
static void runRepl()
|
|
{
|
|
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
|
|
lua_State* L = globalState.get();
|
|
|
|
setupState(L);
|
|
|
|
// setup Ctrl+C handling
|
|
replState = L;
|
|
#ifdef _WIN32
|
|
SetConsoleCtrlHandler(sigintHandler, TRUE);
|
|
#else
|
|
signal(SIGINT, sigintHandler);
|
|
#endif
|
|
|
|
luaL_sandboxthread(L);
|
|
runReplImpl(L);
|
|
}
|
|
|
|
// `repl` is used it indicate if a repl should be started after executing the file.
|
|
static bool runFile(const char* name, lua_State* GL, bool repl)
|
|
{
|
|
std::optional<std::string> source = readFile(name);
|
|
if (!source)
|
|
{
|
|
fprintf(stderr, "Error opening %s\n", name);
|
|
return false;
|
|
}
|
|
|
|
// module needs to run in a new thread, isolated from the rest
|
|
lua_State* L = lua_newthread(GL);
|
|
|
|
// new thread needs to have the globals sandboxed
|
|
luaL_sandboxthread(L);
|
|
|
|
std::string chunkname = "=" + std::string(name);
|
|
|
|
std::string bytecode = Luau::compile(*source, copts());
|
|
int status = 0;
|
|
|
|
if (luau_load(L, chunkname.c_str(), bytecode.data(), bytecode.size(), 0) == 0)
|
|
{
|
|
if (codegen)
|
|
Luau::CodeGen::compile(L, -1);
|
|
|
|
if (coverageActive())
|
|
coverageTrack(L, -1);
|
|
|
|
setupArguments(L, program_argc, program_argv);
|
|
status = lua_resume(L, NULL, program_argc);
|
|
}
|
|
else
|
|
{
|
|
status = LUA_ERRSYNTAX;
|
|
}
|
|
|
|
if (status != 0)
|
|
{
|
|
std::string error;
|
|
|
|
if (status == LUA_YIELD)
|
|
{
|
|
error = "thread yielded unexpectedly";
|
|
}
|
|
else if (const char* str = lua_tostring(L, -1))
|
|
{
|
|
error = str;
|
|
}
|
|
|
|
error += "\nstacktrace:\n";
|
|
error += lua_debugtrace(L);
|
|
|
|
fprintf(stderr, "%s", error.c_str());
|
|
}
|
|
|
|
if (repl)
|
|
{
|
|
runReplImpl(L);
|
|
}
|
|
lua_pop(GL, 1);
|
|
return status == 0;
|
|
}
|
|
|
|
static void displayHelp(const char* argv0)
|
|
{
|
|
printf("Usage: %s [options] [file list] [-a] [arg list]\n", argv0);
|
|
printf("\n");
|
|
printf("When file list is omitted, an interactive REPL is started instead.\n");
|
|
printf("\n");
|
|
printf("Available options:\n");
|
|
printf(" --coverage: collect code coverage while running the code and output results to coverage.out\n");
|
|
printf(" -h, --help: Display this usage message.\n");
|
|
printf(" -i, --interactive: Run an interactive REPL after executing the last script specified.\n");
|
|
printf(" -O<n>: compile with optimization level n (default 1, n should be between 0 and 2).\n");
|
|
printf(" -g<n>: compile with debug level n (default 1, n should be between 0 and 2).\n");
|
|
printf(" --profile[=N]: profile the code using N Hz sampling (default 10000) and output results to profile.out\n");
|
|
printf(" --timetrace: record compiler time tracing information into trace.json\n");
|
|
printf(" --codegen: execute code using native code generation\n");
|
|
printf(" --program-args,-a: declare start of arguments to be passed to the Luau program");
|
|
}
|
|
|
|
static int assertionHandler(const char* expr, const char* file, int line, const char* function)
|
|
{
|
|
printf("%s(%d): ASSERTION FAILED: %s\n", file, line, expr);
|
|
return 1;
|
|
}
|
|
|
|
int replMain(int argc, char** argv)
|
|
{
|
|
Luau::assertHandler() = assertionHandler;
|
|
|
|
setLuauFlagsDefault();
|
|
|
|
#ifdef _WIN32
|
|
SetConsoleOutputCP(CP_UTF8);
|
|
#endif
|
|
|
|
int profile = 0;
|
|
bool coverage = false;
|
|
bool interactive = false;
|
|
bool codegenPerf = false;
|
|
int program_args = argc;
|
|
|
|
for (int i = 1; i < argc; i++)
|
|
{
|
|
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0)
|
|
{
|
|
displayHelp(argv[0]);
|
|
return 0;
|
|
}
|
|
else if (strcmp(argv[i], "-i") == 0 || strcmp(argv[i], "--interactive") == 0)
|
|
{
|
|
interactive = true;
|
|
}
|
|
else if (strncmp(argv[i], "-O", 2) == 0)
|
|
{
|
|
int level = atoi(argv[i] + 2);
|
|
if (level < 0 || level > 2)
|
|
{
|
|
fprintf(stderr, "Error: Optimization level must be between 0 and 2 inclusive.\n");
|
|
return 1;
|
|
}
|
|
globalOptions.optimizationLevel = level;
|
|
}
|
|
else if (strncmp(argv[i], "-g", 2) == 0)
|
|
{
|
|
int level = atoi(argv[i] + 2);
|
|
if (level < 0 || level > 2)
|
|
{
|
|
fprintf(stderr, "Error: Debug level must be between 0 and 2 inclusive.\n");
|
|
return 1;
|
|
}
|
|
globalOptions.debugLevel = level;
|
|
}
|
|
else if (strcmp(argv[i], "--profile") == 0)
|
|
{
|
|
profile = 10000; // default to 10 KHz
|
|
}
|
|
else if (strncmp(argv[i], "--profile=", 10) == 0)
|
|
{
|
|
profile = atoi(argv[i] + 10);
|
|
}
|
|
else if (strcmp(argv[i], "--codegen") == 0)
|
|
{
|
|
codegen = true;
|
|
}
|
|
else if (strcmp(argv[i], "--codegen-perf") == 0)
|
|
{
|
|
codegen = true;
|
|
codegenPerf = true;
|
|
}
|
|
else if (strcmp(argv[i], "--coverage") == 0)
|
|
{
|
|
coverage = true;
|
|
}
|
|
else if (strcmp(argv[i], "--timetrace") == 0)
|
|
{
|
|
FFlag::DebugLuauTimeTracing.value = true;
|
|
}
|
|
else if (strncmp(argv[i], "--fflags=", 9) == 0)
|
|
{
|
|
setLuauFlags(argv[i] + 9);
|
|
}
|
|
else if (strcmp(argv[i], "--program-args") == 0 || strcmp(argv[i], "-a") == 0)
|
|
{
|
|
program_args = i + 1;
|
|
break;
|
|
}
|
|
else if (argv[i][0] == '-')
|
|
{
|
|
fprintf(stderr, "Error: Unrecognized option '%s'.\n\n", argv[i]);
|
|
displayHelp(argv[0]);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
program_argc = argc - program_args;
|
|
program_argv = &argv[program_args];
|
|
|
|
|
|
#if !defined(LUAU_ENABLE_TIME_TRACE)
|
|
if (FFlag::DebugLuauTimeTracing)
|
|
{
|
|
fprintf(stderr, "To run with --timetrace, Luau has to be built with LUAU_ENABLE_TIME_TRACE enabled\n");
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
if (codegenPerf)
|
|
{
|
|
#if __linux__
|
|
char path[128];
|
|
snprintf(path, sizeof(path), "/tmp/perf-%d.map", getpid());
|
|
|
|
// note, there's no need to close the log explicitly as it will be closed when the process exits
|
|
FILE* codegenPerfLog = fopen(path, "w");
|
|
|
|
Luau::CodeGen::setPerfLog(codegenPerfLog, [](void* context, uintptr_t addr, unsigned size, const char* symbol) {
|
|
fprintf(static_cast<FILE*>(context), "%016lx %08x %s\n", long(addr), size, symbol);
|
|
});
|
|
#else
|
|
fprintf(stderr, "--codegen-perf option is only supported on Linux\n");
|
|
return 1;
|
|
#endif
|
|
}
|
|
|
|
if (codegen && !Luau::CodeGen::isSupported())
|
|
fprintf(stderr, "Warning: Native code generation is not supported in current configuration\n");
|
|
|
|
const std::vector<std::string> files = getSourceFiles(argc, argv);
|
|
|
|
if (files.empty())
|
|
{
|
|
runRepl();
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
|
|
lua_State* L = globalState.get();
|
|
|
|
setupState(L);
|
|
|
|
if (profile)
|
|
profilerStart(L, profile);
|
|
|
|
if (coverage)
|
|
coverageInit(L);
|
|
|
|
int failed = 0;
|
|
|
|
for (size_t i = 0; i < files.size(); ++i)
|
|
{
|
|
bool isLastFile = i == files.size() - 1;
|
|
failed += !runFile(files[i].c_str(), L, interactive && isLastFile);
|
|
}
|
|
|
|
if (profile)
|
|
{
|
|
profilerStop();
|
|
profilerDump("profile.out");
|
|
}
|
|
|
|
if (coverage)
|
|
coverageDump("coverage.out");
|
|
|
|
return failed ? 1 : 0;
|
|
}
|
|
}
|