mirror of
https://github.com/luau-lang/luau.git
synced 2025-05-04 10:33:46 +01:00
merge latest from roblox/master
This commit is contained in:
commit
42863021a8
23 changed files with 493 additions and 111 deletions
19
.github/workflows/build.yml
vendored
19
.github/workflows/build.yml
vendored
|
@ -83,3 +83,22 @@ jobs:
|
|||
with:
|
||||
name: coverage
|
||||
path: coverage
|
||||
|
||||
web:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: emscripten-core/emsdk
|
||||
path: emsdk
|
||||
- name: emsdk install
|
||||
run: |
|
||||
cd emsdk
|
||||
./emsdk install latest
|
||||
./emsdk activate latest
|
||||
- name: make
|
||||
run: |
|
||||
source emsdk/emsdk_env.sh
|
||||
emcmake cmake . -DLUAU_BUILD_WEB=ON -DCMAKE_BUILD_TYPE=Release
|
||||
make -j2 Luau.Web
|
||||
|
|
23
.github/workflows/release.yml
vendored
23
.github/workflows/release.yml
vendored
|
@ -33,3 +33,26 @@ jobs:
|
|||
with:
|
||||
name: luau-${{matrix.os}}
|
||||
path: Release\luau*.exe
|
||||
|
||||
web:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: emscripten-core/emsdk
|
||||
path: emsdk
|
||||
- name: emsdk install
|
||||
run: |
|
||||
cd emsdk
|
||||
./emsdk install latest
|
||||
./emsdk activate latest
|
||||
- name: make
|
||||
run: |
|
||||
source emsdk/emsdk_env.sh
|
||||
emcmake cmake . -DLUAU_BUILD_WEB=ON -DCMAKE_BUILD_TYPE=Release
|
||||
make -j2 Luau.Web
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Luau.Web.js
|
||||
path: Luau.Web.js
|
||||
|
|
39
CLI/Repl.cpp
39
CLI/Repl.cpp
|
@ -198,11 +198,6 @@ static std::string runCode(lua_State* L, const std::string& source)
|
|||
error += "\nstack backtrace:\n";
|
||||
error += lua_debugtrace(T);
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// nicer formatting for errors in web repl
|
||||
error = "Error:" + error;
|
||||
#endif
|
||||
|
||||
fprintf(stdout, "%s", error.c_str());
|
||||
}
|
||||
|
||||
|
@ -210,39 +205,6 @@ static std::string runCode(lua_State* L, const std::string& source)
|
|||
return std::string();
|
||||
}
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
extern "C"
|
||||
{
|
||||
const char* executeScript(const char* source)
|
||||
{
|
||||
// setup flags
|
||||
for (Luau::FValue<bool>* flag = Luau::FValue<bool>::list; flag; flag = flag->next)
|
||||
if (strncmp(flag->name, "Luau", 4) == 0)
|
||||
flag->value = true;
|
||||
|
||||
// create new state
|
||||
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
|
||||
lua_State* L = globalState.get();
|
||||
|
||||
// setup state
|
||||
setupState(L);
|
||||
|
||||
// sandbox thread
|
||||
luaL_sandboxthread(L);
|
||||
|
||||
// static string for caching result (prevents dangling ptr on function exit)
|
||||
static std::string result;
|
||||
|
||||
// run code + collect error
|
||||
result = runCode(L, source);
|
||||
|
||||
return result.empty() ? NULL : result.c_str();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Excluded from emscripten compilation to avoid -Wunused-function errors.
|
||||
#ifndef __EMSCRIPTEN__
|
||||
static void completeIndexer(lua_State* L, const char* editBuffer, size_t start, std::vector<std::string>& completions)
|
||||
{
|
||||
std::string_view lookup = editBuffer + start;
|
||||
|
@ -564,5 +526,4 @@ int main(int argc, char** argv)
|
|||
return failed;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
106
CLI/Web.cpp
Normal file
106
CLI/Web.cpp
Normal file
|
@ -0,0 +1,106 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "lua.h"
|
||||
#include "lualib.h"
|
||||
#include "luacode.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static void setupState(lua_State* L)
|
||||
{
|
||||
luaL_openlibs(L);
|
||||
|
||||
luaL_sandbox(L);
|
||||
}
|
||||
|
||||
static std::string runCode(lua_State* L, const std::string& source)
|
||||
{
|
||||
size_t bytecodeSize = 0;
|
||||
char* bytecode = luau_compile(source.data(), source.length(), nullptr, &bytecodeSize);
|
||||
int result = luau_load(L, "=stdin", bytecode, bytecodeSize, 0);
|
||||
free(bytecode);
|
||||
|
||||
if (result != 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, "print");
|
||||
lua_insert(T, 1);
|
||||
lua_pcall(T, n, 0, 0);
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
error = "Error:" + error;
|
||||
|
||||
fprintf(stdout, "%s", error.c_str());
|
||||
}
|
||||
|
||||
lua_pop(L, 1);
|
||||
return std::string();
|
||||
}
|
||||
|
||||
extern "C" const char* executeScript(const char* source)
|
||||
{
|
||||
// setup flags
|
||||
for (Luau::FValue<bool>* flag = Luau::FValue<bool>::list; flag; flag = flag->next)
|
||||
if (strncmp(flag->name, "Luau", 4) == 0)
|
||||
flag->value = true;
|
||||
|
||||
// create new state
|
||||
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
|
||||
lua_State* L = globalState.get();
|
||||
|
||||
// setup state
|
||||
setupState(L);
|
||||
|
||||
// sandbox thread
|
||||
luaL_sandboxthread(L);
|
||||
|
||||
// static string for caching result (prevents dangling ptr on function exit)
|
||||
static std::string result;
|
||||
|
||||
// run code + collect error
|
||||
result = runCode(L, source);
|
||||
|
||||
return result.empty() ? NULL : result.c_str();
|
||||
}
|
|
@ -9,6 +9,7 @@ project(Luau LANGUAGES CXX)
|
|||
|
||||
option(LUAU_BUILD_CLI "Build CLI" ON)
|
||||
option(LUAU_BUILD_TESTS "Build tests" ON)
|
||||
option(LUAU_BUILD_WEB "Build Web module" OFF)
|
||||
option(LUAU_WERROR "Warnings as errors" OFF)
|
||||
|
||||
add_library(Luau.Ast STATIC)
|
||||
|
@ -18,26 +19,22 @@ add_library(Luau.VM STATIC)
|
|||
|
||||
if(LUAU_BUILD_CLI)
|
||||
add_executable(Luau.Repl.CLI)
|
||||
if(NOT EMSCRIPTEN)
|
||||
add_executable(Luau.Analyze.CLI)
|
||||
else()
|
||||
# add -fexceptions for emscripten to allow exceptions to be caught in C++
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions")
|
||||
endif()
|
||||
|
||||
# This also adds target `name` on Linux/macOS and `name.exe` on Windows
|
||||
set_target_properties(Luau.Repl.CLI PROPERTIES OUTPUT_NAME luau)
|
||||
|
||||
if(NOT EMSCRIPTEN)
|
||||
set_target_properties(Luau.Analyze.CLI PROPERTIES OUTPUT_NAME luau-analyze)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(LUAU_BUILD_TESTS AND NOT EMSCRIPTEN)
|
||||
if(LUAU_BUILD_TESTS)
|
||||
add_executable(Luau.UnitTest)
|
||||
add_executable(Luau.Conformance)
|
||||
endif()
|
||||
|
||||
if(LUAU_BUILD_WEB)
|
||||
add_executable(Luau.Web)
|
||||
endif()
|
||||
|
||||
include(Sources.cmake)
|
||||
|
||||
target_compile_features(Luau.Ast PUBLIC cxx_std_17)
|
||||
|
@ -72,16 +69,18 @@ if(LUAU_WERROR)
|
|||
endif()
|
||||
endif()
|
||||
|
||||
if(LUAU_BUILD_WEB)
|
||||
# add -fexceptions for emscripten to allow exceptions to be caught in C++
|
||||
list(APPEND LUAU_OPTIONS -fexceptions)
|
||||
endif()
|
||||
|
||||
target_compile_options(Luau.Ast PRIVATE ${LUAU_OPTIONS})
|
||||
target_compile_options(Luau.Analysis PRIVATE ${LUAU_OPTIONS})
|
||||
target_compile_options(Luau.VM PRIVATE ${LUAU_OPTIONS})
|
||||
|
||||
if(LUAU_BUILD_CLI)
|
||||
target_compile_options(Luau.Repl.CLI PRIVATE ${LUAU_OPTIONS})
|
||||
|
||||
if(NOT EMSCRIPTEN)
|
||||
target_compile_options(Luau.Analyze.CLI PRIVATE ${LUAU_OPTIONS})
|
||||
endif()
|
||||
|
||||
target_include_directories(Luau.Repl.CLI PRIVATE extern)
|
||||
target_link_libraries(Luau.Repl.CLI PRIVATE Luau.Compiler Luau.VM)
|
||||
|
@ -93,20 +92,10 @@ if(LUAU_BUILD_CLI)
|
|||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT EMSCRIPTEN)
|
||||
target_link_libraries(Luau.Analyze.CLI PRIVATE Luau.Analysis)
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
# declare exported functions to emscripten
|
||||
target_link_options(Luau.Repl.CLI PRIVATE -sEXPORTED_FUNCTIONS=['_executeScript'] -sEXPORTED_RUNTIME_METHODS=['ccall','cwrap'] -fexceptions)
|
||||
|
||||
# custom output directory for wasm + js file
|
||||
set_target_properties(Luau.Repl.CLI PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/docs/assets/luau)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(LUAU_BUILD_TESTS AND NOT EMSCRIPTEN)
|
||||
if(LUAU_BUILD_TESTS)
|
||||
target_compile_options(Luau.UnitTest PRIVATE ${LUAU_OPTIONS})
|
||||
target_include_directories(Luau.UnitTest PRIVATE extern)
|
||||
target_link_libraries(Luau.UnitTest PRIVATE Luau.Analysis Luau.Compiler)
|
||||
|
@ -115,3 +104,17 @@ if(LUAU_BUILD_TESTS AND NOT EMSCRIPTEN)
|
|||
target_include_directories(Luau.Conformance PRIVATE extern)
|
||||
target_link_libraries(Luau.Conformance PRIVATE Luau.Analysis Luau.Compiler Luau.VM)
|
||||
endif()
|
||||
|
||||
if(LUAU_BUILD_WEB)
|
||||
target_compile_options(Luau.Web PRIVATE ${LUAU_OPTIONS})
|
||||
target_link_libraries(Luau.Web PRIVATE Luau.Compiler Luau.VM)
|
||||
|
||||
# declare exported functions to emscripten
|
||||
target_link_options(Luau.Web PRIVATE -sEXPORTED_FUNCTIONS=['_executeScript'] -sEXPORTED_RUNTIME_METHODS=['ccall','cwrap'])
|
||||
|
||||
# add -fexceptions for emscripten to allow exceptions to be caught in C++
|
||||
target_link_options(Luau.Web PRIVATE -fexceptions)
|
||||
|
||||
# the output is a single .js file with an embedded wasm blob
|
||||
target_link_options(Luau.Web PRIVATE -sSINGLE_FILE=1)
|
||||
endif()
|
||||
|
|
|
@ -224,3 +224,9 @@ if(TARGET Luau.Conformance)
|
|||
tests/Conformance.test.cpp
|
||||
tests/main.cpp)
|
||||
endif()
|
||||
|
||||
if(TARGET Luau.Web)
|
||||
# Luau.Web Sources
|
||||
target_sources(Luau.Web PRIVATE
|
||||
CLI/Web.cpp)
|
||||
endif()
|
||||
|
|
|
@ -159,7 +159,11 @@ LUA_API void lua_pushnil(lua_State* L);
|
|||
LUA_API void lua_pushnumber(lua_State* L, double n);
|
||||
LUA_API void lua_pushinteger(lua_State* L, int n);
|
||||
LUA_API void lua_pushunsigned(lua_State* L, unsigned n);
|
||||
#if LUA_VECTOR_SIZE == 4
|
||||
LUA_API void lua_pushvector(lua_State* L, float x, float y, float z, float w);
|
||||
#else
|
||||
LUA_API void lua_pushvector(lua_State* L, float x, float y, float z);
|
||||
#endif
|
||||
LUA_API void lua_pushlstring(lua_State* L, const char* s, size_t l);
|
||||
LUA_API void lua_pushstring(lua_State* L, const char* s);
|
||||
LUA_API const char* lua_pushvfstring(lua_State* L, const char* fmt, va_list argp);
|
||||
|
|
|
@ -122,3 +122,7 @@
|
|||
void* s; \
|
||||
long l; \
|
||||
}
|
||||
|
||||
#define LUA_VECTOR_SIZE 3 /* must be 3 or 4 */
|
||||
|
||||
#define LUA_EXTRA_SIZE LUA_VECTOR_SIZE - 2
|
||||
|
|
|
@ -25,6 +25,9 @@ LUALIB_API const char* luaL_optlstring(lua_State* L, int numArg, const char* def
|
|||
LUALIB_API double luaL_checknumber(lua_State* L, int numArg);
|
||||
LUALIB_API double luaL_optnumber(lua_State* L, int nArg, double def);
|
||||
|
||||
LUALIB_API int luaL_checkboolean(lua_State* L, int narg);
|
||||
LUALIB_API int luaL_optboolean(lua_State* L, int narg, int def);
|
||||
|
||||
LUALIB_API int luaL_checkinteger(lua_State* L, int numArg);
|
||||
LUALIB_API int luaL_optinteger(lua_State* L, int nArg, int def);
|
||||
LUALIB_API unsigned luaL_checkunsigned(lua_State* L, int numArg);
|
||||
|
|
|
@ -550,12 +550,21 @@ void lua_pushunsigned(lua_State* L, unsigned u)
|
|||
return;
|
||||
}
|
||||
|
||||
void lua_pushvector(lua_State* L, float x, float y, float z)
|
||||
#if LUA_VECTOR_SIZE == 4
|
||||
void lua_pushvector(lua_State* L, float x, float y, float z, float w)
|
||||
{
|
||||
setvvalue(L->top, x, y, z);
|
||||
setvvalue(L->top, x, y, z, w);
|
||||
api_incr_top(L);
|
||||
return;
|
||||
}
|
||||
#else
|
||||
void lua_pushvector(lua_State* L, float x, float y, float z)
|
||||
{
|
||||
setvvalue(L->top, x, y, z, 0.0f);
|
||||
api_incr_top(L);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
void lua_pushlstring(lua_State* L, const char* s, size_t len)
|
||||
{
|
||||
|
|
|
@ -183,6 +183,22 @@ LUALIB_API double luaL_optnumber(lua_State* L, int narg, double def)
|
|||
return luaL_opt(L, luaL_checknumber, narg, def);
|
||||
}
|
||||
|
||||
LUALIB_API int luaL_checkboolean(lua_State* L, int narg)
|
||||
{
|
||||
// This checks specifically for boolean values, ignoring
|
||||
// all other truthy/falsy values. If the desired result
|
||||
// is true if value is present then lua_toboolean should
|
||||
// directly be used instead.
|
||||
if (!lua_isboolean(L, narg))
|
||||
tag_error(L, narg, LUA_TBOOLEAN);
|
||||
return lua_toboolean(L, narg);
|
||||
}
|
||||
|
||||
LUALIB_API int luaL_optboolean(lua_State* L, int narg, int def)
|
||||
{
|
||||
return luaL_opt(L, luaL_checkboolean, narg, def);
|
||||
}
|
||||
|
||||
LUALIB_API int luaL_checkinteger(lua_State* L, int narg)
|
||||
{
|
||||
int isnum;
|
||||
|
@ -462,7 +478,11 @@ LUALIB_API const char* luaL_tolstring(lua_State* L, int idx, size_t* len)
|
|||
case LUA_TVECTOR:
|
||||
{
|
||||
const float* v = lua_tovector(L, idx);
|
||||
#if LUA_VECTOR_SIZE == 4
|
||||
lua_pushfstring(L, LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT, v[0], v[1], v[2], v[3]);
|
||||
#else
|
||||
lua_pushfstring(L, LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT, v[0], v[1], v[2]);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
|
|
@ -1018,13 +1018,23 @@ static int luauF_tunpack(lua_State* L, StkId res, TValue* arg0, int nresults, St
|
|||
|
||||
static int luauF_vector(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
#if LUA_VECTOR_SIZE == 4
|
||||
if (nparams >= 4 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1) && ttisnumber(args + 2))
|
||||
#else
|
||||
if (nparams >= 3 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1))
|
||||
#endif
|
||||
{
|
||||
double x = nvalue(arg0);
|
||||
double y = nvalue(args);
|
||||
double z = nvalue(args + 1);
|
||||
|
||||
setvvalue(res, float(x), float(y), float(z));
|
||||
#if LUA_VECTOR_SIZE == 4
|
||||
double w = nvalue(args + 2);
|
||||
setvvalue(res, float(x), float(y), float(z), float(w));
|
||||
#else
|
||||
setvvalue(res, float(x), float(y), float(z), 0.0f);
|
||||
#endif
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,11 +33,17 @@
|
|||
#define ABISWITCH(x64, ms32, gcc32) (sizeof(void*) == 8 ? x64 : ms32)
|
||||
#endif
|
||||
|
||||
#if LUA_VECTOR_SIZE == 4
|
||||
static_assert(sizeof(TValue) == ABISWITCH(24, 24, 24), "size mismatch for value");
|
||||
static_assert(sizeof(LuaNode) == ABISWITCH(48, 48, 48), "size mismatch for table entry");
|
||||
#else
|
||||
static_assert(sizeof(TValue) == ABISWITCH(16, 16, 16), "size mismatch for value");
|
||||
static_assert(sizeof(LuaNode) == ABISWITCH(32, 32, 32), "size mismatch for table entry");
|
||||
#endif
|
||||
|
||||
static_assert(offsetof(TString, data) == ABISWITCH(24, 20, 20), "size mismatch for string header");
|
||||
static_assert(offsetof(Udata, data) == ABISWITCH(24, 16, 16), "size mismatch for userdata header");
|
||||
static_assert(sizeof(Table) == ABISWITCH(56, 36, 36), "size mismatch for table header");
|
||||
static_assert(sizeof(LuaNode) == ABISWITCH(32, 32, 32), "size mismatch for table entry");
|
||||
|
||||
const size_t kSizeClasses = LUA_SIZECLASSES;
|
||||
const size_t kMaxSmallSize = 512;
|
||||
|
|
|
@ -18,12 +18,20 @@
|
|||
|
||||
inline bool luai_veceq(const float* a, const float* b)
|
||||
{
|
||||
#if LUA_VECTOR_SIZE == 4
|
||||
return a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3];
|
||||
#else
|
||||
return a[0] == b[0] && a[1] == b[1] && a[2] == b[2];
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool luai_vecisnan(const float* a)
|
||||
{
|
||||
#if LUA_VECTOR_SIZE == 4
|
||||
return a[0] != a[0] || a[1] != a[1] || a[2] != a[2] || a[3] != a[3];
|
||||
#else
|
||||
return a[0] != a[0] || a[1] != a[1] || a[2] != a[2];
|
||||
#endif
|
||||
}
|
||||
|
||||
LUAU_FASTMATH_BEGIN
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
|
||||
|
||||
const TValue luaO_nilobject_ = {{NULL}, LUA_TNIL};
|
||||
const TValue luaO_nilobject_ = {{NULL}, {0}, LUA_TNIL};
|
||||
|
||||
int luaO_log2(unsigned int x)
|
||||
{
|
||||
|
|
|
@ -47,7 +47,7 @@ typedef union
|
|||
typedef struct lua_TValue
|
||||
{
|
||||
Value value;
|
||||
int extra;
|
||||
int extra[LUA_EXTRA_SIZE];
|
||||
int tt;
|
||||
} TValue;
|
||||
|
||||
|
@ -105,7 +105,19 @@ typedef struct lua_TValue
|
|||
i_o->tt = LUA_TNUMBER; \
|
||||
}
|
||||
|
||||
#define setvvalue(obj, x, y, z) \
|
||||
#if LUA_VECTOR_SIZE == 4
|
||||
#define setvvalue(obj, x, y, z, w) \
|
||||
{ \
|
||||
TValue* i_o = (obj); \
|
||||
float* i_v = i_o->value.v; \
|
||||
i_v[0] = (x); \
|
||||
i_v[1] = (y); \
|
||||
i_v[2] = (z); \
|
||||
i_v[3] = (w); \
|
||||
i_o->tt = LUA_TVECTOR; \
|
||||
}
|
||||
#else
|
||||
#define setvvalue(obj, x, y, z, w) \
|
||||
{ \
|
||||
TValue* i_o = (obj); \
|
||||
float* i_v = i_o->value.v; \
|
||||
|
@ -114,6 +126,7 @@ typedef struct lua_TValue
|
|||
i_v[2] = (z); \
|
||||
i_o->tt = LUA_TVECTOR; \
|
||||
}
|
||||
#endif
|
||||
|
||||
#define setpvalue(obj, x) \
|
||||
{ \
|
||||
|
@ -364,7 +377,7 @@ typedef struct Closure
|
|||
typedef struct TKey
|
||||
{
|
||||
::Value value;
|
||||
int extra;
|
||||
int extra[LUA_EXTRA_SIZE];
|
||||
unsigned tt : 4;
|
||||
int next : 28; /* for chaining */
|
||||
} TKey;
|
||||
|
@ -381,7 +394,7 @@ typedef struct LuaNode
|
|||
LuaNode* n_ = (node); \
|
||||
const TValue* i_o = (obj); \
|
||||
n_->key.value = i_o->value; \
|
||||
n_->key.extra = i_o->extra; \
|
||||
memcpy(n_->key.extra, i_o->extra, sizeof(n_->key.extra)); \
|
||||
n_->key.tt = i_o->tt; \
|
||||
checkliveness(L->global, i_o); \
|
||||
}
|
||||
|
@ -392,7 +405,7 @@ typedef struct LuaNode
|
|||
TValue* i_o = (obj); \
|
||||
const LuaNode* n_ = (node); \
|
||||
i_o->value = n_->key.value; \
|
||||
i_o->extra = n_->key.extra; \
|
||||
memcpy(i_o->extra, n_->key.extra, sizeof(i_o->extra)); \
|
||||
i_o->tt = n_->key.tt; \
|
||||
checkliveness(L->global, i_o); \
|
||||
}
|
||||
|
|
|
@ -31,18 +31,19 @@ LUAU_FASTFLAGVARIABLE(LuauArrayBoundary, false)
|
|||
#define MAXSIZE (1 << MAXBITS)
|
||||
|
||||
static_assert(offsetof(LuaNode, val) == 0, "Unexpected Node memory layout, pointer cast in gval2slot is incorrect");
|
||||
|
||||
// TKey is bitpacked for memory efficiency so we need to validate bit counts for worst case
|
||||
static_assert(TKey{{NULL}, 0, LUA_TDEADKEY, 0}.tt == LUA_TDEADKEY, "not enough bits for tt");
|
||||
static_assert(TKey{{NULL}, 0, LUA_TNIL, MAXSIZE - 1}.next == MAXSIZE - 1, "not enough bits for next");
|
||||
static_assert(TKey{{NULL}, 0, LUA_TNIL, -(MAXSIZE - 1)}.next == -(MAXSIZE - 1), "not enough bits for next");
|
||||
static_assert(TKey{{NULL}, {0}, LUA_TDEADKEY, 0}.tt == LUA_TDEADKEY, "not enough bits for tt");
|
||||
static_assert(TKey{{NULL}, {0}, LUA_TNIL, MAXSIZE - 1}.next == MAXSIZE - 1, "not enough bits for next");
|
||||
static_assert(TKey{{NULL}, {0}, LUA_TNIL, -(MAXSIZE - 1)}.next == -(MAXSIZE - 1), "not enough bits for next");
|
||||
|
||||
// reset cache of absent metamethods, cache is updated in luaT_gettm
|
||||
#define invalidateTMcache(t) t->flags = 0
|
||||
|
||||
// empty hash data points to dummynode so that we can always dereference it
|
||||
const LuaNode luaH_dummynode = {
|
||||
{{NULL}, 0, LUA_TNIL}, /* value */
|
||||
{{NULL}, 0, LUA_TNIL, 0} /* key */
|
||||
{{NULL}, {0}, LUA_TNIL}, /* value */
|
||||
{{NULL}, {0}, LUA_TNIL, 0} /* key */
|
||||
};
|
||||
|
||||
#define dummynode (&luaH_dummynode)
|
||||
|
@ -96,7 +97,7 @@ static LuaNode* hashnum(const Table* t, double n)
|
|||
|
||||
static LuaNode* hashvec(const Table* t, const float* v)
|
||||
{
|
||||
unsigned int i[3];
|
||||
unsigned int i[LUA_VECTOR_SIZE];
|
||||
memcpy(i, v, sizeof(i));
|
||||
|
||||
// convert -0 to 0 to make sure they hash to the same value
|
||||
|
@ -112,6 +113,12 @@ static LuaNode* hashvec(const Table* t, const float* v)
|
|||
// Optimized Spatial Hashing for Collision Detection of Deformable Objects
|
||||
unsigned int h = (i[0] * 73856093) ^ (i[1] * 19349663) ^ (i[2] * 83492791);
|
||||
|
||||
#if LUA_VECTOR_SIZE == 4
|
||||
i[3] = (i[3] == 0x8000000) ? 0 : i[3];
|
||||
i[3] ^= i[3] >> 17;
|
||||
h ^= i[3] * 39916801;
|
||||
#endif
|
||||
|
||||
return hashpow2(t, h);
|
||||
}
|
||||
|
||||
|
|
|
@ -601,7 +601,13 @@ static void luau_execute(lua_State* L)
|
|||
const char* name = getstr(tsvalue(kv));
|
||||
int ic = (name[0] | ' ') - 'x';
|
||||
|
||||
if (unsigned(ic) < 3 && name[1] == '\0')
|
||||
#if LUA_VECTOR_SIZE == 4
|
||||
// 'w' is before 'x' in ascii, so ic is -1 when indexing with 'w'
|
||||
if (ic == -1)
|
||||
ic = 3;
|
||||
#endif
|
||||
|
||||
if (unsigned(ic) < LUA_VECTOR_SIZE && name[1] == '\0')
|
||||
{
|
||||
setnvalue(ra, rb->value.v[ic]);
|
||||
VM_NEXT();
|
||||
|
@ -1526,7 +1532,7 @@ static void luau_execute(lua_State* L)
|
|||
{
|
||||
const float* vb = rb->value.v;
|
||||
const float* vc = rc->value.v;
|
||||
setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2]);
|
||||
setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2], vb[3] + vc[3]);
|
||||
VM_NEXT();
|
||||
}
|
||||
else
|
||||
|
@ -1572,7 +1578,7 @@ static void luau_execute(lua_State* L)
|
|||
{
|
||||
const float* vb = rb->value.v;
|
||||
const float* vc = rc->value.v;
|
||||
setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2]);
|
||||
setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2], vb[3] - vc[3]);
|
||||
VM_NEXT();
|
||||
}
|
||||
else
|
||||
|
@ -1618,21 +1624,21 @@ static void luau_execute(lua_State* L)
|
|||
{
|
||||
const float* vb = rb->value.v;
|
||||
float vc = cast_to(float, nvalue(rc));
|
||||
setvvalue(ra, vb[0] * vc, vb[1] * vc, vb[2] * vc);
|
||||
setvvalue(ra, vb[0] * vc, vb[1] * vc, vb[2] * vc, vb[3] * vc);
|
||||
VM_NEXT();
|
||||
}
|
||||
else if (ttisvector(rb) && ttisvector(rc))
|
||||
{
|
||||
const float* vb = rb->value.v;
|
||||
const float* vc = rc->value.v;
|
||||
setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2]);
|
||||
setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2], vb[3] * vc[3]);
|
||||
VM_NEXT();
|
||||
}
|
||||
else if (ttisnumber(rb) && ttisvector(rc))
|
||||
{
|
||||
float vb = cast_to(float, nvalue(rb));
|
||||
const float* vc = rc->value.v;
|
||||
setvvalue(ra, vb * vc[0], vb * vc[1], vb * vc[2]);
|
||||
setvvalue(ra, vb * vc[0], vb * vc[1], vb * vc[2], vb * vc[3]);
|
||||
VM_NEXT();
|
||||
}
|
||||
else
|
||||
|
@ -1679,21 +1685,21 @@ static void luau_execute(lua_State* L)
|
|||
{
|
||||
const float* vb = rb->value.v;
|
||||
float vc = cast_to(float, nvalue(rc));
|
||||
setvvalue(ra, vb[0] / vc, vb[1] / vc, vb[2] / vc);
|
||||
setvvalue(ra, vb[0] / vc, vb[1] / vc, vb[2] / vc, vb[3] / vc);
|
||||
VM_NEXT();
|
||||
}
|
||||
else if (ttisvector(rb) && ttisvector(rc))
|
||||
{
|
||||
const float* vb = rb->value.v;
|
||||
const float* vc = rc->value.v;
|
||||
setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2]);
|
||||
setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2], vb[3] / vc[3]);
|
||||
VM_NEXT();
|
||||
}
|
||||
else if (ttisnumber(rb) && ttisvector(rc))
|
||||
{
|
||||
float vb = cast_to(float, nvalue(rb));
|
||||
const float* vc = rc->value.v;
|
||||
setvvalue(ra, vb / vc[0], vb / vc[1], vb / vc[2]);
|
||||
setvvalue(ra, vb / vc[0], vb / vc[1], vb / vc[2], vb / vc[3]);
|
||||
VM_NEXT();
|
||||
}
|
||||
else
|
||||
|
@ -1826,7 +1832,7 @@ static void luau_execute(lua_State* L)
|
|||
{
|
||||
const float* vb = rb->value.v;
|
||||
float vc = cast_to(float, nvalue(kv));
|
||||
setvvalue(ra, vb[0] * vc, vb[1] * vc, vb[2] * vc);
|
||||
setvvalue(ra, vb[0] * vc, vb[1] * vc, vb[2] * vc, vb[3] * vc);
|
||||
VM_NEXT();
|
||||
}
|
||||
else
|
||||
|
@ -1872,7 +1878,7 @@ static void luau_execute(lua_State* L)
|
|||
{
|
||||
const float* vb = rb->value.v;
|
||||
float vc = cast_to(float, nvalue(kv));
|
||||
setvvalue(ra, vb[0] / vc, vb[1] / vc, vb[2] / vc);
|
||||
setvvalue(ra, vb[0] / vc, vb[1] / vc, vb[2] / vc, vb[3] / vc);
|
||||
VM_NEXT();
|
||||
}
|
||||
else
|
||||
|
@ -2037,7 +2043,7 @@ static void luau_execute(lua_State* L)
|
|||
else if (ttisvector(rb))
|
||||
{
|
||||
const float* vb = rb->value.v;
|
||||
setvvalue(ra, -vb[0], -vb[1], -vb[2]);
|
||||
setvvalue(ra, -vb[0], -vb[1], -vb[2], -vb[3]);
|
||||
VM_NEXT();
|
||||
}
|
||||
else
|
||||
|
|
|
@ -401,19 +401,19 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM
|
|||
switch (op)
|
||||
{
|
||||
case TM_ADD:
|
||||
setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2]);
|
||||
setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2], vb[3] + vc[3]);
|
||||
return;
|
||||
case TM_SUB:
|
||||
setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2]);
|
||||
setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2], vb[3] - vc[3]);
|
||||
return;
|
||||
case TM_MUL:
|
||||
setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2]);
|
||||
setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2], vb[3] * vc[3]);
|
||||
return;
|
||||
case TM_DIV:
|
||||
setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2]);
|
||||
setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2], vb[3] / vc[3]);
|
||||
return;
|
||||
case TM_UNM:
|
||||
setvvalue(ra, -vb[0], -vb[1], -vb[2]);
|
||||
setvvalue(ra, -vb[0], -vb[1], -vb[2], -vb[3]);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
|
@ -430,10 +430,10 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM
|
|||
switch (op)
|
||||
{
|
||||
case TM_MUL:
|
||||
setvvalue(ra, vb[0] * nc, vb[1] * nc, vb[2] * nc);
|
||||
setvvalue(ra, vb[0] * nc, vb[1] * nc, vb[2] * nc, vb[3] * nc);
|
||||
return;
|
||||
case TM_DIV:
|
||||
setvvalue(ra, vb[0] / nc, vb[1] / nc, vb[2] / nc);
|
||||
setvvalue(ra, vb[0] / nc, vb[1] / nc, vb[2] / nc, vb[3] / nc);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
|
@ -451,10 +451,10 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM
|
|||
switch (op)
|
||||
{
|
||||
case TM_MUL:
|
||||
setvvalue(ra, nb * vc[0], nb * vc[1], nb * vc[2]);
|
||||
setvvalue(ra, nb * vc[0], nb * vc[1], nb * vc[2], nb * vc[3]);
|
||||
return;
|
||||
case TM_DIV:
|
||||
setvvalue(ra, nb / vc[0], nb / vc[1], nb / vc[2]);
|
||||
setvvalue(ra, nb / vc[0], nb / vc[1], nb / vc[2], nb / vc[3]);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
|
|
|
@ -36,5 +36,5 @@
|
|||
}
|
||||
</style>
|
||||
<script src="assets/js/vs/loader.js"></script>
|
||||
<script async src="https://github.com/Roblox/luau/releases/download/0.505/Luau.Web.js"></script>
|
||||
<script async src="https://github.com/Roblox/luau/releases/latest/download/Luau.Web.js"></script>
|
||||
<script type="text/javascript" src="/assets/js/repl.js"></script>
|
142
rfcs/syntax-string-interpolation.md
Normal file
142
rfcs/syntax-string-interpolation.md
Normal file
|
@ -0,0 +1,142 @@
|
|||
# String interpolation
|
||||
|
||||
## Summary
|
||||
|
||||
New string interpolation syntax.
|
||||
|
||||
## Motivation
|
||||
|
||||
The problems with `string.format` are many.
|
||||
|
||||
1. Must be exact about the types and its corresponding value.
|
||||
2. Using `%d` is the idiomatic default for most people, but this loses precision.
|
||||
* `%d` casts the number into `long long`, which has a lower max value than `double` and does not support decimals.
|
||||
* `%f` by default will format to the millionths, e.g. `5.5` is `5.500000`.
|
||||
* `%g` by default will format up to the hundred thousandths, e.g. `5.5` is `5.5` and `5.5312389` is `5.53123`. It will also convert the number to scientific notation when it encounters a number equal to or greater than 10^6.
|
||||
* To not lose too much precision, you need to use `%s`, but even so the type checker assumes you actually wanted strings.
|
||||
3. No support for `boolean`. You must use `%s` **and** call `tostring`.
|
||||
4. No support for values implementing the `__tostring` metamethod.
|
||||
5. Using `%` is in itself a dangerous operation within `string.format`.
|
||||
* `"Your health is %d% so you need to heal up."` causes a runtime error because `% so` is actually parsed as `(%s)o` and now requires a corresponding string.
|
||||
6. Having to use parentheses around string literals just to call a method of it.
|
||||
|
||||
## Design
|
||||
|
||||
To fix all of those issues, we need to do a few things.
|
||||
|
||||
1. A new string interpolation expression (fixes #5, #6)
|
||||
2. Extend `string.format` to accept values of arbitrary types (fixes #1, #2, #3, #4)
|
||||
|
||||
Because we care about backward compatibility, we need some new syntax in order to not change the meaning of existing strings. There are a few components of this new expression:
|
||||
|
||||
1. A string chunk (`` `...{ ``, `}...{`, and `` }...` ``) where `...` is a range of 0 to many characters.
|
||||
* `\` escapes `` ` ``, `{`, and itself `\`.
|
||||
* Restriction: the string interpolation literal must have at least one value to interpolate. We do not need 3 ways to express a single line string literal.
|
||||
* The pairs must be on the same line (unless a `\` escapes the newline) but expressions needn't be on the same line.
|
||||
2. An expression between the braces. This is the value that will be interpolated into the string.
|
||||
3. Formatting specification may follow after the expression, delimited by an unambiguous character.
|
||||
* Restriction: the formatting specification must be constant at parse time.
|
||||
* In the absence of an explicit formatting specification, the `%*` token will be used.
|
||||
* For now, we explicitly reject any formatting specification syntax. A future extension may be introduced to extend the syntax with an optional specification.
|
||||
|
||||
To put the above into formal EBNF grammar:
|
||||
|
||||
```
|
||||
stringinterp ::= <INTERP_BEGIN> exp {<INTERP_MID> exp} <INTERP_END>
|
||||
```
|
||||
|
||||
Which, in actual Luau code, will look like the following:
|
||||
|
||||
```
|
||||
local world = "world"
|
||||
print(`Hello {world}!`)
|
||||
--> Hello world!
|
||||
|
||||
local combo = {5, 2, 8, 9}
|
||||
print(`The lock combinations are: {table.concat(combo, ", ")}`)
|
||||
--> The lock combinations are: 5, 2, 8, 9
|
||||
|
||||
local set1 = Set.new({0, 1, 3})
|
||||
local set2 = Set.new({0, 5, 4})
|
||||
print(`{set1} ∪ {set2} = {Set.union(set1, set2)}`)
|
||||
--> {0, 1, 3} ∪ {0, 5, 4} = {0, 1, 3, 4, 5}
|
||||
|
||||
-- For illustrative purposes. These are illegal specifically because they don't interpolate anything.
|
||||
print(`Some example escaping the braces \{like so}`)
|
||||
print(`backslash \ that escapes the space is not a part of the string...`)
|
||||
print(`backslash \\ will escape the second backslash...`)
|
||||
print(`Some text that also includes \`...`)
|
||||
--> Some example escaping the braces {like so}
|
||||
--> backslash that escapes the space is not a part of the string...
|
||||
--> backslash \ will escape the second backslash...
|
||||
--> Some text that also includes `...
|
||||
```
|
||||
|
||||
As for how newlines are handled, they are handled the same as other string literals. Any text between the `{}` delimiters are not considered part of the string, hence newlines are OK. The main thing is that one opening pair will scan until either a closing pair is encountered, or an unescaped newline.
|
||||
|
||||
```
|
||||
local name = "Luau"
|
||||
|
||||
print(`Welcome to {
|
||||
name
|
||||
}!`)
|
||||
--> Welcome to Luau!
|
||||
|
||||
print(`Welcome to \
|
||||
{name}!`)
|
||||
--> Welcome to
|
||||
-- Luau!
|
||||
```
|
||||
|
||||
This expression will not be allowed to come after a `prefixexp`. I believe this is fully additive, so a future RFC may allow this. So for now, we explicitly reject the following:
|
||||
|
||||
```
|
||||
local name = "world"
|
||||
print`Hello {name}`
|
||||
```
|
||||
|
||||
Since the string interpolation expression is going to be lowered into a `string.format` call, we'll also need to extend `string.format`. The bare minimum to support the lowering is to add a new token whose definition is to perform a `tostring` call. `%*` is currently an invalid token, so this is a backward compatible extension. This RFC shall define `%*` to have the same behavior as if `tostring` was called.
|
||||
|
||||
```lua
|
||||
print(string.format("%* %*", 1, 2))
|
||||
--> 1 2
|
||||
```
|
||||
|
||||
The offset must always be within bound of the numbers of values passed to `string.format`.
|
||||
|
||||
```lua
|
||||
local function return_one_thing() return "hi" end
|
||||
local function return_two_nils() return nil, nil end
|
||||
|
||||
print(string.format("%*", return_one_thing()))
|
||||
--> "hi"
|
||||
|
||||
print(string.format("%*", Set.new({1, 2, 3})))
|
||||
--> {1, 2, 3}
|
||||
|
||||
print(string.format("%* %*", return_two_nils()))
|
||||
--> nil nil
|
||||
|
||||
print(string.format("%* %* %*", return_two_nils()))
|
||||
--> error: value #3 is missing, got 2
|
||||
```
|
||||
|
||||
## Drawbacks
|
||||
|
||||
If we want to use backticks for other purposes, it may introduce some potential ambiguity. One option to solve that is to only ever produce string interpolation tokens from the context of an expression. This is messy but doable because the parser and the lexer are already implemented to work in tandem. The other option is to pick a different delimiter syntax to keep backticks available for use in the future.
|
||||
|
||||
If we were to naively compile the expression into a `string.format` call, then implementation details would be observable if you write `` `Your health is {hp}% so you need to heal up.` ``. When lowering the expression, we would need to implicitly insert a `%` character anytime one shows up in a string interpolation token. Otherwise attempting to run this will produce a runtime error where the `%s` token is missing its corresponding string value.
|
||||
|
||||
## Alternatives
|
||||
|
||||
Rather than coming up with a new syntax (which doesn't help issue #5 and #6) and extending `string.format` to accept an extra token, we could just make `%s` call `tostring` and be done. However, doing so would cause programs to be more lenient and the type checker would have no way to infer strings from a `string.format` call. To preserve that, we would need a different token anyway.
|
||||
|
||||
Language | Syntax | Conclusion
|
||||
----------:|:----------------------|:-----------
|
||||
Python | `f'Hello {name}'` | Rejected because it's ambiguous with function call syntax.
|
||||
Swift | `"Hello \(name)"` | Rejected because it changes the meaning of existing strings.
|
||||
Ruby | `"Hello #{name}"` | Rejected because it changes the meaning of existing strings.
|
||||
JavaScript | `` `Hello ${name}` `` | Viable option as long as we don't intend to use backticks for other purposes.
|
||||
C# | `$"Hello {name}"` | Viable option and guarantees no ambiguities with future syntax.
|
||||
|
||||
This leaves us with only two syntax that already exists in other programming languages. The current proposal are for backticks, so the only backward compatible alternative are `$""` literals. We don't necessarily need to use `$` symbol here, but if we were to choose a different symbol, `#` cannot be used. I picked backticks because it doesn't require us to add a stack of closing delimiters in the lexer to make sure each nested string interpolation literals are correctly closed with its opening pair. You only have to count them.
|
|
@ -67,7 +67,12 @@ static int lua_vector(lua_State* L)
|
|||
double y = luaL_checknumber(L, 2);
|
||||
double z = luaL_checknumber(L, 3);
|
||||
|
||||
#if LUA_VECTOR_SIZE == 4
|
||||
double w = luaL_optnumber(L, 4, 0.0);
|
||||
lua_pushvector(L, float(x), float(y), float(z), float(w));
|
||||
#else
|
||||
lua_pushvector(L, float(x), float(y), float(z));
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -373,11 +378,17 @@ TEST_CASE("Pack")
|
|||
|
||||
TEST_CASE("Vector")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIfElseExpressionBaseSupport", true};
|
||||
|
||||
runConformance("vector.lua", [](lua_State* L) {
|
||||
lua_pushcfunction(L, lua_vector, "vector");
|
||||
lua_setglobal(L, "vector");
|
||||
|
||||
#if LUA_VECTOR_SIZE == 4
|
||||
lua_pushvector(L, 0.0f, 0.0f, 0.0f, 0.0f);
|
||||
#else
|
||||
lua_pushvector(L, 0.0f, 0.0f, 0.0f);
|
||||
#endif
|
||||
luaL_newmetatable(L, "vector");
|
||||
|
||||
lua_pushstring(L, "__index");
|
||||
|
@ -524,7 +535,7 @@ TEST_CASE("Debugger")
|
|||
L,
|
||||
[](lua_State* L) -> int {
|
||||
int line = luaL_checkinteger(L, 1);
|
||||
bool enabled = lua_isboolean(L, 2) ? lua_toboolean(L, 2) : true;
|
||||
bool enabled = luaL_optboolean(L, 2, true);
|
||||
|
||||
lua_Debug ar = {};
|
||||
lua_getinfo(L, 1, "f", &ar);
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
-- This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
print('testing vectors')
|
||||
|
||||
-- detect vector size
|
||||
local vector_size = if pcall(function() return vector(0, 0, 0).w end) then 4 else 3
|
||||
|
||||
-- equality
|
||||
assert(vector(1, 2, 3) == vector(1, 2, 3))
|
||||
assert(vector(0, 1, 2) == vector(-0, 1, 2))
|
||||
|
@ -13,8 +16,14 @@ assert(not rawequal(vector(1, 2, 3), vector(1, 2, 4)))
|
|||
|
||||
-- type & tostring
|
||||
assert(type(vector(1, 2, 3)) == "vector")
|
||||
assert(tostring(vector(1, 2, 3)) == "1, 2, 3")
|
||||
assert(tostring(vector(-1, 2, 0.5)) == "-1, 2, 0.5")
|
||||
|
||||
if vector_size == 4 then
|
||||
assert(tostring(vector(1, 2, 3, 4)) == "1, 2, 3, 4")
|
||||
assert(tostring(vector(-1, 2, 0.5, 0)) == "-1, 2, 0.5, 0")
|
||||
else
|
||||
assert(tostring(vector(1, 2, 3)) == "1, 2, 3")
|
||||
assert(tostring(vector(-1, 2, 0.5)) == "-1, 2, 0.5")
|
||||
end
|
||||
|
||||
local t = {}
|
||||
|
||||
|
@ -42,12 +51,19 @@ assert(8 * vector(8, 16, 24) == vector(64, 128, 192));
|
|||
assert(vector(1, 2, 4) * '8' == vector(8, 16, 32));
|
||||
assert('8' * vector(8, 16, 24) == vector(64, 128, 192));
|
||||
|
||||
assert(vector(1, 2, 4) / vector(8, 16, 24) == vector(1/8, 2/16, 4/24));
|
||||
if vector_size == 4 then
|
||||
assert(vector(1, 2, 4, 8) / vector(8, 16, 24, 32) == vector(1/8, 2/16, 4/24, 8/32));
|
||||
assert(8 / vector(8, 16, 24, 32) == vector(1, 1/2, 1/3, 1/4));
|
||||
assert('8' / vector(8, 16, 24, 32) == vector(1, 1/2, 1/3, 1/4));
|
||||
else
|
||||
assert(vector(1, 2, 4) / vector(8, 16, 24, 1) == vector(1/8, 2/16, 4/24));
|
||||
assert(8 / vector(8, 16, 24) == vector(1, 1/2, 1/3));
|
||||
assert('8' / vector(8, 16, 24) == vector(1, 1/2, 1/3));
|
||||
end
|
||||
|
||||
assert(vector(1, 2, 4) / 8 == vector(1/8, 1/4, 1/2));
|
||||
assert(vector(1, 2, 4) / (1 / val) == vector(1/8, 2/8, 4/8));
|
||||
assert(8 / vector(8, 16, 24) == vector(1, 1/2, 1/3));
|
||||
assert(vector(1, 2, 4) / '8' == vector(1/8, 1/4, 1/2));
|
||||
assert('8' / vector(8, 16, 24) == vector(1, 1/2, 1/3));
|
||||
|
||||
assert(-vector(1, 2, 4) == vector(-1, -2, -4));
|
||||
|
||||
|
@ -71,4 +87,9 @@ assert(pcall(function() local t = {} rawset(t, vector(0/0, 2, 3), 1) end) == fal
|
|||
-- make sure we cover both builtin and C impl
|
||||
assert(vector(1, 2, 4) == vector("1", "2", "4"))
|
||||
|
||||
-- additional checks for 4-component vectors
|
||||
if vector_size == 4 then
|
||||
assert(vector(1, 2, 3, 4).w == 4)
|
||||
end
|
||||
|
||||
return 'OK'
|
||||
|
|
Loading…
Add table
Reference in a new issue