Feature: Web REPL using Emscripten (#138)

Currently doesn't include the new page into navigation since we aren't building the .js files anywhere.
This commit is contained in:
Pelanyo Kamara 2021-11-10 16:40:46 +00:00 committed by GitHub
parent d6b3346f58
commit aec8fbfd0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 133 additions and 10 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@
^default.prof*
^fuzz-*
^luau$
/.vs

View File

@ -198,6 +198,11 @@ 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());
}
@ -205,6 +210,44 @@ static std::string runCode(lua_State* L, const std::string& source)
return std::string();
}
#ifdef __EMSCRIPTEN__
extern "C"
{
const char* executeScript(const char* source)
{
// static string for caching result (prevents dangling ptr on function exit)
static std::string result;
// 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);
// run code + collect error
std::string error = runCode(L, source);
result = error;
if (error.length())
{
return result.c_str();
}
return NULL;
}
}
#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;
@ -547,3 +590,4 @@ int main(int argc, char** argv)
return failed;
}
}
#endif

View File

@ -17,17 +17,26 @@ add_library(Luau.VM STATIC)
if(LUAU_BUILD_CLI)
add_executable(Luau.Repl.CLI)
add_executable(Luau.Analyze.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)
set_target_properties(Luau.Analyze.CLI PROPERTIES OUTPUT_NAME luau-analyze)
if(NOT EMSCRIPTEN)
set_target_properties(Luau.Analyze.CLI PROPERTIES OUTPUT_NAME luau-analyze)
endif()
endif()
if(LUAU_BUILD_TESTS)
if(LUAU_BUILD_TESTS AND NOT EMSCRIPTEN)
add_executable(Luau.UnitTest)
add_executable(Luau.Conformance)
endif()
include(Sources.cmake)
target_compile_features(Luau.Ast PUBLIC cxx_std_17)
@ -53,10 +62,6 @@ if(MSVC)
else()
list(APPEND LUAU_OPTIONS -Wall) # All warnings
list(APPEND LUAU_OPTIONS -Werror) # Warnings are errors
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
list(APPEND LUAU_OPTIONS -Wno-unused) # GCC considers variables declared/checked in if() as unused
endif()
endif()
target_compile_options(Luau.Ast PRIVATE ${LUAU_OPTIONS})
@ -65,7 +70,10 @@ target_compile_options(Luau.VM PRIVATE ${LUAU_OPTIONS})
if(LUAU_BUILD_CLI)
target_compile_options(Luau.Repl.CLI PRIVATE ${LUAU_OPTIONS})
target_compile_options(Luau.Analyze.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)
@ -74,10 +82,20 @@ if(LUAU_BUILD_CLI)
target_link_libraries(Luau.Repl.CLI PRIVATE pthread)
endif()
target_link_libraries(Luau.Analyze.CLI PRIVATE Luau.Analysis)
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)
if(LUAU_BUILD_TESTS AND NOT EMSCRIPTEN)
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)

View File

@ -27,3 +27,7 @@ pages:
url: /profile
- title: Library
url: /library
# Remove demo pages until solution is found
# - title: Demo
# url: /demo

50
docs/_includes/repl.html Normal file
View File

@ -0,0 +1,50 @@
<form>
<div>
<label>Script:</label>
<br>
<textarea rows="10" cols="70" id="script">print("Hello World!")</textarea>
<br><br>
<button onclick="clearInput(); return false;">
Clear Input
</button>
<button onclick="executeScript(); return false;">
Run
</button>
</div>
<br><br>
<div>
<label>Output:</label>
<br>
<textarea readonly rows="10" cols="70" id="output"></textarea>
<br><br>
<button onclick="clearOutput(); return false;">
Clear Output
</button>
</div>
</form>
<script>
function output(text) {
document.getElementById("output").value += "[" + new Date().toLocaleTimeString() + "] " + text.replace('stdin:', '') + "\n";
}
var Module = {
'print': function (msg) { output(msg) }
};
function clearInput() {
document.getElementById("script").value = "";
}
function clearOutput() {
document.getElementById("output").value = "";
}
function executeScript() {
var err = Module.ccall('executeScript', 'string', ['string'], [document.getElementById("script").value]);
if (err) {
output('Error:' + err.replace('stdin:', ''));
}
}
</script>
<script async src="assets/luau/luau.js"></script>

6
docs/_pages/demo.md Normal file
View File

@ -0,0 +1,6 @@
---
permalink: /demo
title: Demo
---
{% include repl.html %}