8000 Add basic compile command by Nicell · Pull Request #267 · luau-lang/lute · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add basic compile command #267

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Sources.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ target_sources(Lute.Process PRIVATE

target_sources(Lute.CLI PRIVATE
cli/main.cpp
cli/compile.h
cli/compile.cpp
cli/tc.h
cli/tc.cpp
)
Expand Down
121 changes: 121 additions & 0 deletions cli/compile.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#include "compile.h"

#include "lute/options.h"
#include "uv.h"

#include <fstream>

const char MAGIC_FLAG[] = "LUTEBYTE";
const size_t MAGIC_FLAG_SIZE = sizeof(MAGIC_FLAG) - 1;
const size_t BYTECODE_SIZE_FIELD_SIZE = sizeof(uint64_t);

AppendedBytecodeResult checkForAppendedBytecode(const std::string& executablePath)
{
AppendedBytecodeResult result;
std::ifstream exeFile(executablePath, std::ios::binary | std::ios::ate);
if (!exeFile)
{
return result;
}

std::streampos fileSize = exeFile.tellg();
if (fileSize < static_cast<std::streampos>(MAGIC_FLAG_SIZE + BYTECODE_SIZE_FIELD_SIZE))
{
exeFile.close();
return result;
}

std::vector<char> flagBuffer(MAGIC_FLAG_SIZE);
exeFile.seekg(fileSize - static_cast<std::streampos>(MAGIC_FLAG_SIZE));
exeFile.read(flagBuffer.data(), MAGIC_FLAG_SIZE);

if (memcmp(flagBuffer.data(), MAGIC_FLAG, MAGIC_FLAG_SIZE) != 0)
{
exeFile.close();
return result;
}

uint64_t BytecodeSize;
exeFile.seekg(fileSize - static_cast<std::streampos>(MAGIC_FLAG_SIZE + BYTECODE_SIZE_FIELD_SIZE));
exeFile.read(reinterpret_cast<char*>(&BytecodeSize), BYTECODE_SIZE_FIELD_SIZE);

if (fileSize < static_cast<std::streampos>(MAGIC_FLAG_SIZE + BYTECODE_SIZE_FIELD_SIZE + BytecodeSize)) {
fprintf(stderr, "Warning: Found magic flag but file size inconsistent.\n");
exeFile.close();
return result;
}

result.BytecodeData.resize(BytecodeSize);
exeFile.seekg(fileSize - static_cast<std::streampos>(MAGIC_FLAG_SIZE + BYTECODE_SIZE_FIELD_SIZE + BytecodeSize));
exeFile.read(&result.BytecodeData[0], BytecodeSize);

exeFile.close();
result.found = true;
return result;
}

int compileScript(const std::string& inputFilePath, const std::string& outputFilePath, const std::string& currentExecutablePath)
{
std::optional<std::string> source = readFile(inputFilePath);
10000 if (!source)
{
fprintf(stderr, "Error opening input file %s\n", inputFilePath.c_str());
return 1;
}

std::string bytecode = Luau::compile(*source, copts());
if (bytecode.empty())
{
fprintf(stderr, "Error compiling %s to bytecode.\n", inputFilePath.c_str());
return 1;
}

std::ifstream exeFile(currentExecutablePath, std::ios::binary | std::ios::ate);
if (!exeFile)
{
fprintf(stderr, "Error opening current executable %s\n", currentExecutablePath.c_str());
return 1;
}
std::streamsize exeSize = exeFile.tellg();
exeFile.seekg(0, std::ios::beg);
std::vector<char> exeBuffer(exeSize);
if (!exeFile.read(exeBuffer.data(), exeSize))
{
fprintf(stderr, "Error reading current executable %s\n", currentExecutablePath.c_str());
exeFile.close();
return 1;
}
exeFile.close();

std::ofstream outFile(outputFilePath, std::ios::binary | std::ios::trunc);
if (!outFile) {
fprintf(stderr, "Error creating output file %s\n", outputFilePath.c_str());
return 1;
}

outFile.write(exeBuffer.data(), exeSize);

uint64_t bytecodeSize = bytecode.size();
outFile.write(bytecode.data(), bytecodeSize);

outFile.write(reinterpret_cast<const char*>(&bytecodeSize), BYTECODE_SIZE_FIELD_SIZE);

outFile.write(MAGIC_FLAG, MAGIC_FLAG_SIZE);

if (!outFile.good())
{
fprintf(stderr, "Error writing to output file %s\n", outputFilePath.c_str());
outFile.close();
remove(outputFilePath.c_str());
return 1;
}

outFile.close();

printf("Successfully compiled %s to %s\n", inputFilePath.c_str(), outputFilePath.c_str());
#ifndef _WIN32
chmod(outputFilePath.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
#endif

return 0;
}
13 changes: 13 additions & 0 deletions cli/compile.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once

#include "Luau/FileUtils.h"

struct AppendedBytecodeResult
{
bool found = false;
std::string BytecodeData;
};

AppendedBytecodeResult checkForAppendedBytecode(const std::string& executablePath);

int compileScript(const std::string& inputFilePath, const std::string& outputFilePath, const std::string& currentExecutablePath);
155 changes: 130 additions & 25 deletions cli/main.cpp
6D47
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "lute/vm.h"
#include "lute/time.h"

#include "compile.h"
#include "tc.h"

#ifdef _WIN32
Expand Down Expand Up @@ -128,38 +129,14 @@ bool setupArguments(lua_State* L, int argc, char** argv)
return true;
}

static bool runFile(Runtime& runtime, const char* name, lua_State* GL)
static bool runBytecode(Runtime& runtime, const std::string& bytecode, const std::string& chunkname, lua_State* GL)
{
if (isDirectory(name))
{
fprintf(stderr, "Error: %s is a directory\n", name);
return false;
}

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);

// ignore file extension when storing module's chunkname
std::string chunkname = "@";
std::string_view nameView = name;
if (size_t dotPos = nameView.find_last_of('.'); dotPos != std::string_view::npos)
{
nameView.remove_suffix(nameView.size() - dotPos);
}
chunkname += nameView;

std::string bytecode = Luau::compile(*source, copts());

if (luau_load(L, chunkname.c_str(), bytecode.data(), bytecode.size(), 0) != 0)
{
if (const char* str = lua_tostring(L, -1))
Expand Down Expand Up @@ -192,13 +169,43 @@ static bool runFile(Runtime& runtime, const char* name, lua_State* GL)
return runtime.runToCompletion();
}

static bool runFile(Runtime& runtime, const char* name, lua_State* GL)
{
if (isDirectory(name))
{
fprintf(stderr, "Error: %s is a directory\n", name);
return false;
}

std::optional<std::string> source = readFile(name);
if (!source)
{
fprintf(stderr, "Error opening %s\n", name);
return false;
}

// ignore file extension when storing module's chunkname
std::string chunkname = "@";
std::string_view nameView = name;
if (size_t dotPos = nameView.find_last_of('.'); dotPos != std::string_view::npos)
{
nameView.remove_suffix(nameView.size() - dotPos);
}
chunkname += nameView;

std::string bytecode = Luau::compile(*source, copts());

return runBytecode(runtime, bytecode, chunkname, GL);
}

static void displayHelp(const char* argv0)
{
printf("Usage: %s <command> [options] [arguments...]\n", argv0);
printf("\n");
printf("Commands:\n");
printf(" run (default) Run a Luau script.\n");
printf(" check Type check Luau files.\n");
printf(" compile Compile a Luau script into the executable.\n");
printf("\n");
printf("Run Options (when using 'run' or no command):\n");
printf(" %s [run] <script.luau> [args...]\n", argv0);
Expand All @@ -208,6 +215,10 @@ static void displayHelp(const char* argv0)
printf(" %s check <file1.luau> [file2.luau...]\n", argv0);
printf(" Performs a type check on the specified files.\n");
printf("\n");
printf("Compile Options:\n");
printf(" %s compile <script.luau> [output_executable]\n", argv0);
printf(" Compiles the script, embedding it into a new executable.\n");
printf("\n");
printf("General Options:\n");
printf(" -h, --help Display this usage message.\n");
}
Expand All @@ -228,6 +239,16 @@ static void displayCheckHelp(const char* argv0)
printf(" -h, --help Display this usage message.\n");
}

static void displayCompileHelp(const char* argv0)
{
printf("Usage: %s compile <script.luau> [output_executable]\n", argv0);
printf("\n");
printf("Compile Options:\n");
printf(" output_executable Optional name for the compiled executable.\n");
printf(" Defaults to '<script_name>_compiled'.\n");
printf(" -h, --help Display this usage message.\n");
}

static int assertionHandler(const char* expr, const char* file, int line, const char* function)
{
printf("%s(%d): ASSERTION FAILED: %s\n", file, line, expr);
Expand Down Expand Up @@ -311,10 +332,90 @@ int handleCheckCommand(int argc, char** argv, int argOffset)
return typecheck(files);
}

int handleCompileCommand(int argc, char** argv, int argOffset)
{
std::string inputFilePath;
std::string outputFilePath;

for (int i = argOffset; i < argc; ++i)
{
const char* currentArg = argv[i];

if (strcmp(currentArg, "-h") == 0 || strcmp(currentArg, "--help") == 0)
{
displayCompileHelp(argv[0]);
return 0;
}
else if (inputFilePath.empty())
{
inputFilePath = currentArg;
}
else if (outputFilePath.empty())
{
outputFilePath = currentArg;
}
else
{
fprintf(stderr, "Error: Too many arguments for 'compile' command.\n\n");
displayCompileHelp(argv[0]);
return 1;
}
}

if (inputFilePath.empty())
{
fprintf(stderr, "Error: No input file specified for 'compile' command.\n\n");
displayCompileHelp(argv[0]);
return 1;
}

if (outputFilePath.empty())
{
std::string inputBase = inputFilePath;
size_t lastSlash = inputBase.find_last_of("/");
if (lastSlash != std::string::npos)
{
inputBase = inputBase.substr(lastSlash + 1);
}
#ifdef _WIN32
size_t lastBackslash = inputBase.find_last_of("\\");
if (lastBackslash != std::string::npos)
{
inputBase = inputBase.substr(lastBackslash + 1);
}
#endif
size_t lastDot = inputBase.find_last_of('.');
if (lastDot != std::string::npos)
{
inputBase = inputBase.substr(0, lastDot);
}
outputFilePath = inputBase;
#ifdef _WIN32
outputFilePath += ".exe";
#endif
}

return compileScript(inputFilePath, outputFilePath, argv[0]);
}

int main(int argc, char** argv)
{
Luau::assertHandler() = assertionHandler;

AppendedBytecodeResult embedded = checkForAppendedBytecode(argv[0]);
if (embedded.found)
{
Runtime runtime;
lua_State* GL = setupState(runtime);

program_argc = argc;
program_argv = argv;

bool success = runBytecode(runtime, embedded.BytecodeData, "=__EMBEDDED__", GL);

return success ? 0 : 1;
}

#ifdef _WIN32
SetConsoleOutputCP(CP_UTF8);
#endif
Expand All @@ -337,6 +438,10 @@ int main(int argc, char** argv)
{
return handleCheckCommand(argc, argv, argOffset);
}
else if (strcmp(command, "compile") == 0)
{
return handleCompileCommand(argc, argv, argOffset);
}
else if (strcmp(command, "-h") == 0 || strcmp(command, "--help") == 0)
{
displayHelp(argv[0]);
Expand Down
0