Initial commit

This commit is contained in:
2026-08-23 11:11:36 +03:00
commit 0548f8de26
97 changed files with 20841 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
zig-out
.zig-cache
zig-pkg
+39
View File
@@ -0,0 +1,39 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const luau_dep = b.dependency("luau", .{ .target = target, .optimize = optimize, .use_zig_backend = false });
const exe = b.addExecutable(.{
.name = "xsh",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
}),
});
exe.root_module.addIncludePath(b.path("src/include"));
exe.root_module.addImport(
"luau",
luau_dep.module("root"),
);
b.installArtifact(exe);
const run = b.addRunArtifact(exe);
if (b.args) |args| {
run.addArgs(args);
}
const run_step = b.step(
"run",
"Run xsh",
);
run_step.dependOn(&run.step);
}
+19
View File
@@ -0,0 +1,19 @@
.{
.name = .xsh,
.version = "0.0.0",
.fingerprint = 0xe2d823a53ce2c535,
.minimum_zig_version = "0.16.0",
.dependencies = .{
.luau = .{
.path = "deps/luau",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Scythe Technology
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+1042
View File
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
.{
.name = .luau,
.fingerprint = 0x5e07e249a70630b8,
.version = "0.0.0+730",
.minimum_zig_version = "0.16.0",
.dependencies = .{
.luau = .{
.url = "git+https://github.com/luau-lang/luau#0.730",
.hash = "N-V-__8AANL5zQDipKKyPgqjHf_oS7MBYpGH4D7DmYQ3GKRv",
},
},
.paths = .{
"src",
"LICENSE",
"build.zig",
"build.zig.zon",
},
}
+23
View File
@@ -0,0 +1,23 @@
#include <bridge.h>
#include "Luau/AstJsonEncoder.h"
#define ZIG_LUAU_ANALYSIS(name) ZIG_FN(Luau_Analysis_##name)
ZIG_EXPORT const char* ZIG_LUAU_ANALYSIS(AstJsonEncoder_toJson)(Luau::AstNode* node, size_t* len)
{
std::string res = Luau::toJson(node);
char* copy = static_cast<char*>(malloc(res.size()));
if (!copy)
return nullptr;
memcpy(copy, res.data(), res.size());
*len = res.size();
return copy;
}
ZIG_EXPORT void ZIG_LUAU_ANALYSIS(AstJsonEncoder_free)(const char* json)
{
free((void*)json);
}
+46
View File
@@ -0,0 +1,46 @@
const std = @import("std");
const Ast = @import("../Ast/Ast.zig");
extern "c" fn zig_Luau_Analysis_AstJsonEncoder_toJson(*Ast.Node, *usize) [*c]const u8;
extern "c" fn zig_Luau_Analysis_AstJsonEncoder_free([*c]const u8) void;
pub fn toJson(allocator: std.mem.Allocator, node: *Ast.Node) ![]const u8 {
var size: usize = 0;
const json = zig_Luau_Analysis_AstJsonEncoder_toJson(node, &size);
defer zig_Luau_Analysis_AstJsonEncoder_free(json);
if (json == null)
return error.OutOfMemory;
const result = try allocator.dupe(u8, json[0..size]);
return result;
}
test toJson {
const Lexer = @import("../Ast/Lexer.zig");
const Parser = @import("../Ast/Parser.zig");
const Allocator = @import("../Ast/Allocator.zig");
{
const allocator = Allocator.init();
defer allocator.deinit();
const table = Lexer.AstNameTable.init(allocator);
defer table.deinit();
const source =
\\local x = 1
\\
;
var parse_result = Parser.parse(source, table, allocator, .{});
defer parse_result.deinit();
const root = parse_result.root;
const data = try toJson(std.testing.allocator, @ptrCast(@alignCast(root)));
defer std.testing.allocator.free(data);
try std.testing.expectEqualStrings(
\\{"type":"AstStatBlock","location":"0,0 - 1,0","hasEnd":true,"body":[{"type":"AstStatLocal","location":"0,0 - 0,11","vars":[{"luauType":null,"name":"x","isConst":false,"type":"AstLocal","location":"0,6 - 0,7"}],"values":[{"type":"AstExprConstantNumber","location":"0,10 - 0,11","value":1}]}]}
, data);
}
}
+113
View File
@@ -0,0 +1,113 @@
#include <bridge.h>
#include "Luau/Ast.h"
#include "Luau/Frontend.h"
#include "Luau/ModuleResolver.h"
#define ZIG_LUAU_ANALYSIS(name) ZIG_FN(Luau_Analysis_##name)
ZIG_EXPORT using FileResolver_readSource = const char* (*)(void* ctx, const char* name, size_t len, size_t* outLen, unsigned char* outType);
ZIG_EXPORT using FileResolver_resolveModule = const char* (*)(void* ctx, const char* name, size_t len, const char* node, size_t nodeLen, size_t* outLen);
ZIG_EXPORT using FileResolver_getHumanReadableModuleName = const char* (*)(void* ctx, const char* name, size_t len, size_t* outLen);
ZIG_EXPORT using FileResolver_freeString = void (*)(void* ctx, const char* str, size_t len);
struct zig_FileResolver : Luau::FileResolver
{
void* ctx = nullptr;
FileResolver_readSource c_readSource;
FileResolver_resolveModule c_resolveModule;
FileResolver_getHumanReadableModuleName c_getHumanReadableModuleName;
FileResolver_freeString c_freeString;
zig_FileResolver(
void* ctx,
FileResolver_readSource fn_readSource,
FileResolver_resolveModule fn_resolveModule,
FileResolver_getHumanReadableModuleName fn_getHumanReadableModuleName,
FileResolver_freeString fn_freeString
)
: ctx(ctx),
c_readSource(fn_readSource),
c_resolveModule(fn_resolveModule),
c_getHumanReadableModuleName(fn_getHumanReadableModuleName),
c_freeString(fn_freeString)
{
}
std::optional<Luau::SourceCode> readSource(const Luau::ModuleName& name) override
{
size_t len = 0;
unsigned char type = 0;
const char* source = c_readSource(ctx, name.data(), name.size(), &len, &type);
if (!source)
return std::nullopt;
Luau::SourceCode::Type sourceType;
if (type == 0)
{
sourceType = Luau::SourceCode::Script;
}
else if (type == 1)
{
sourceType = Luau::SourceCode::Module;
}
else
{
sourceType = Luau::SourceCode::None;
}
std::string sourceStr(source, len);
c_freeString(ctx, source, len);
return Luau::SourceCode{sourceStr, sourceType};
}
std::optional<Luau::ModuleInfo> resolveModule(const Luau::ModuleInfo* context, Luau::AstExpr* node, const Luau::TypeCheckLimits& limits) override
{
if (Luau::AstExprConstantString* expr = node->as<Luau::AstExprConstantString>())
{
std::string path{expr->value.data, expr->value.size};
size_t len = 0;
const char* result = c_resolveModule(ctx, context->name.c_str(), context->name.size(), path.c_str(), path.size(), &len);
if (result)
{
std::string resolvedPath(result, len);
c_freeString(ctx, result, len);
return {{resolvedPath}};
}
}
return std::nullopt;
}
std::string getHumanReadableModuleName(const Luau::ModuleName& name) const override
{
size_t len = 0;
const char* nameStr = c_getHumanReadableModuleName(ctx, name.data(), name.size(), &len);
std::string result(nameStr, len);
c_freeString(ctx, nameStr, len);
return result;
}
};
ZIG_EXPORT zig_FileResolver* ZIG_LUAU_ANALYSIS(FileResolver_init)(
void* ctx,
FileResolver_readSource fn_readSource,
FileResolver_resolveModule fn_resolveModule,
FileResolver_getHumanReadableModuleName fn_getHumanReadableModuleName,
FileResolver_freeString fn_freeString
)
{
return new zig_FileResolver(
ctx,
fn_readSource,
fn_resolveModule,
fn_getHumanReadableModuleName,
fn_freeString
);
}
ZIG_EXPORT void* ZIG_LUAU_ANALYSIS(FileResolver_dtor)(zig_FileResolver* resolver)
{
void* ctx = resolver->ctx;
delete resolver;
return ctx;
}
+92
View File
@@ -0,0 +1,92 @@
const std = @import("std");
const FileResolver_readSource = fn (ud: *anyopaque, name: [*c]const u8, len: usize, outLen: *usize, outType: *u8) callconv(.c) ?[*]const u8;
const FileResolver_resolveModule = fn (ud: *anyopaque, name: [*c]const u8, len: usize, node: [*c]const u8, nodeLen: usize, outLen: *usize) callconv(.c) ?[*]const u8;
const FileResolver_getHumanReadableModuleName = fn (ud: *anyopaque, name: [*c]const u8, len: usize, outLen: *usize) callconv(.c) [*]const u8;
const FileResolver_freeString = fn (ud: *anyopaque, str: [*c]const u8, len: usize) callconv(.c) void;
extern "c" fn zig_Luau_Analysis_FileResolver_init(
*anyopaque,
*const FileResolver_readSource,
*const FileResolver_resolveModule,
*const FileResolver_getHumanReadableModuleName,
*const FileResolver_freeString,
) *anyopaque;
extern "c" fn zig_Luau_Analysis_FileResolver_dtor(*anyopaque) *anyopaque;
pub const SourceCodeType = enum {
Script,
Module,
None,
};
pub fn FileResolver(comptime T: type) type {
return opaque {
const Self = @This();
pub fn init(ctx: *T) *Self {
return @ptrCast(zig_Luau_Analysis_FileResolver_init(
@ptrCast(@alignCast(ctx)),
struct {
fn inner(ud: *anyopaque, name: [*c]const u8, len: usize, outLen: *usize, outType: *u8) callconv(.c) ?[*]const u8 {
const res: struct { []const u8, SourceCodeType } = @call(.always_inline, T.readSource, .{ @as(*T, @ptrCast(@alignCast(ud))), name[0..len] }) orelse return null;
const buf, const t = res;
outLen.* = buf.len;
outType.* = @intFromEnum(t);
return buf.ptr;
}
}.inner,
struct {
fn inner(ud: *anyopaque, name: [*c]const u8, len: usize, node: [*c]const u8, nodeLen: usize, outLen: *usize) callconv(.c) ?[*]const u8 {
const res: []const u8 = @call(.always_inline, T.resolveModule, .{ @as(*T, @ptrCast(@alignCast(ud))), name[0..len], node[0..nodeLen] }) orelse return null;
outLen.* = res.len;
return res.ptr;
}
}.inner,
struct {
fn inner(ud: *anyopaque, name: [*c]const u8, len: usize, outLen: *usize) callconv(.c) [*]const u8 {
const res: []const u8 = @call(.always_inline, T.getHumanReadableModuleName, .{ @as(*T, @ptrCast(@alignCast(ud))), name[0..len] });
outLen.* = res.len;
return res.ptr;
}
}.inner,
struct {
fn inner(ud: *anyopaque, str: [*c]const u8, len: usize) callconv(.c) void {
@call(.always_inline, T.freeString, .{ @as(*T, @ptrCast(@alignCast(ud))), str[0..len] });
}
}.inner,
));
}
pub fn deinit(self: *Self) void {
const ctx = zig_Luau_Analysis_FileResolver_dtor(self);
if (@hasDecl(T, "deinit")) {
@call(.always_inline, T.deinit, .{@as(*T, @ptrCast(@alignCast(ctx)))});
}
}
};
}
test "FileResolver" {
const Sample = struct {
const Self = @This();
pub fn readSource(_: *Self, _: []const u8) ?struct { []const u8, SourceCodeType } {
return null;
}
pub fn resolveModule(_: *Self, _: []const u8, _: []const u8) ?[]const u8 {
return null;
}
pub fn getHumanReadableModuleName(_: *Self, name: []const u8) []const u8 {
return name;
}
pub fn freeString(_: *Self, _: []const u8) void {}
};
const SampleResolver = FileResolver(Sample);
var sample: Sample = .{};
const resolver = SampleResolver.init(&sample);
defer resolver.deinit();
}
+466
View File
@@ -0,0 +1,466 @@
// This code is based on https://github.com/luau-lang/luau/blob/68cdcc4a3a5f3ed23186c4f7f6b8a5aacf835bee/CLI/src/FileUtils.cpp
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "./FileUtils.h"
#include "Luau/Common.h"
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <direct.h>
#include <windows.h>
#else
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#endif
#include <string.h>
#include <string_view>
#ifdef _WIN32
static std::wstring fromUtf8(const std::string& path)
{
size_t result = MultiByteToWideChar(CP_UTF8, 0, path.data(), int(path.size()), nullptr, 0);
LUAU_ASSERT(result);
std::wstring buf(result, L'\0');
MultiByteToWideChar(CP_UTF8, 0, path.data(), int(path.size()), &buf[0], int(buf.size()));
return buf;
}
static std::string toUtf8(const std::wstring& path)
{
size_t result = WideCharToMultiByte(CP_UTF8, 0, path.data(), int(path.size()), nullptr, 0, nullptr, nullptr);
LUAU_ASSERT(result);
std::string buf(result, '\0');
WideCharToMultiByte(CP_UTF8, 0, path.data(), int(path.size()), &buf[0], int(buf.size()), nullptr, nullptr);
return buf;
}
#endif
bool isAbsolutePath(std::string_view path)
{
#ifdef _WIN32
// Must either begin with "X:/", "X:\", "/", or "\", where X is a drive letter
return (path.size() >= 3 && isalpha(path[0]) && path[1] == ':' && (path[2] == '/' || path[2] == '\\')) ||
(path.size() >= 1 && (path[0] == '/' || path[0] == '\\'));
#else
// Must begin with '/'
return path.size() >= 1 && path[0] == '/';
#endif
}
std::optional<std::string> getCurrentWorkingDirectory()
{
// 2^17 - derived from the Windows path length limit
constexpr size_t maxPathLength = 131072;
constexpr size_t initialPathLength = 260;
std::string directory(initialPathLength, '\0');
char* cstr = nullptr;
while (!cstr && directory.size() <= maxPathLength)
{
#ifdef _WIN32
cstr = _getcwd(directory.data(), static_cast<int>(directory.size()));
#else
cstr = getcwd(directory.data(), directory.size());
#endif
if (cstr)
{
directory.resize(strlen(cstr));
return directory;
}
else if (errno != ERANGE || directory.size() * 2 > maxPathLength)
{
return std::nullopt;
}
else
{
directory.resize(directory.size() * 2);
}
}
return std::nullopt;
}
std::string normalizePath(std::string_view path)
{
const std::vector<std::string_view> components = splitPath(path);
std::vector<std::string_view> normalizedComponents;
const bool isAbsolute = isAbsolutePath(path);
// 1. Normalize path components
const size_t startIndex = isAbsolute ? 1 : 0;
for (size_t i = startIndex; i < components.size(); i++)
{
std::string_view component = components[i];
if (component == "..")
{
if (normalizedComponents.empty())
{
if (!isAbsolute)
{
normalizedComponents.emplace_back("..");
}
}
else if (normalizedComponents.back() == "..")
{
normalizedComponents.emplace_back("..");
}
else
{
normalizedComponents.pop_back();
}
}
else if (!component.empty() && component != ".")
{
normalizedComponents.emplace_back(component);
}
}
std::string normalizedPath;
// 2. Add correct prefix to formatted path
if (isAbsolute)
{
normalizedPath += components[0];
normalizedPath += "/";
}
else if (normalizedComponents.empty() || normalizedComponents[0] != "..")
{
normalizedPath += "./";
}
// 3. Join path components to form the normalized path
for (auto iter = normalizedComponents.begin(); iter != normalizedComponents.end(); ++iter)
{
if (iter != normalizedComponents.begin())
normalizedPath += "/";
normalizedPath += *iter;
}
if (normalizedPath.size() >= 2 && normalizedPath[normalizedPath.size() - 1] == '.' && normalizedPath[normalizedPath.size() - 2] == '.')
normalizedPath += "/";
return normalizedPath;
}
std::optional<std::string> resolvePath(std::string_view path, std::string_view baseFilePath)
{
std::optional<std::string> baseFilePathParent = getParentPath(baseFilePath);
if (!baseFilePathParent)
return std::nullopt;
return normalizePath(joinPaths(*baseFilePathParent, path));
}
bool hasFileExtension(std::string_view name, const std::vector<std::string>& extensions)
{
for (const std::string& extension : extensions)
{
if (name.size() >= extension.size() && name.substr(name.size() - extension.size()) == extension)
return true;
}
return false;
}
std::optional<std::string> readFile(const std::string& name)
{
#ifdef _WIN32
FILE* file = _wfopen(fromUtf8(name).c_str(), L"rb");
#else
FILE* file = fopen(name.c_str(), "rb");
#endif
if (!file)
return std::nullopt;
fseek(file, 0, SEEK_END);
long length = ftell(file);
if (length < 0)
{
fclose(file);
return std::nullopt;
}
fseek(file, 0, SEEK_SET);
std::string result(length, 0);
size_t read = fread(result.data(), 1, length, file);
fclose(file);
if (read != size_t(length))
return std::nullopt;
// Skip first line if it's a shebang
if (length > 2 && result[0] == '#' && result[1] == '!')
result.erase(0, result.find('\n'));
return result;
}
std::optional<std::string> readStdin()
{
std::string result;
char buffer[4096] = {};
while (fgets(buffer, sizeof(buffer), stdin) != nullptr)
result.append(buffer);
// If eof was not reached for stdin, then a read error occurred
if (!feof(stdin))
return std::nullopt;
return result;
}
template<typename Ch>
static void joinPaths(std::basic_string<Ch>& str, const Ch* lhs, const Ch* rhs)
{
str = lhs;
if (!str.empty() && str.back() != '/' && str.back() != '\\' && *rhs != '/' && *rhs != '\\')
str += '/';
str += rhs;
}
#ifdef _WIN32
static bool traverseDirectoryRec(const std::wstring& path, const std::function<void(const std::string& name)>& callback)
{
std::wstring query = path + std::wstring(L"/*");
WIN32_FIND_DATAW data;
HANDLE h = FindFirstFileW(query.c_str(), &data);
if (h == INVALID_HANDLE_VALUE)
return false;
std::wstring buf;
do
{
if (wcscmp(data.cFileName, L".") != 0 && wcscmp(data.cFileName, L"..") != 0)
{
joinPaths(buf, path.c_str(), data.cFileName);
if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
{
// Skip reparse points to avoid handling cycles
}
else if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
traverseDirectoryRec(buf, callback);
}
else
{
callback(toUtf8(buf));
}
}
} while (FindNextFileW(h, &data));
FindClose(h);
return true;
}
bool traverseDirectory(const std::string& path, const std::function<void(const std::string& name)>& callback)
{
return traverseDirectoryRec(fromUtf8(path), callback);
}
#else
static bool traverseDirectoryRec(const std::string& path, const std::function<void(const std::string& name)>& callback)
{
int fd = open(path.c_str(), O_DIRECTORY);
DIR* dir = fdopendir(fd);
if (!dir)
return false;
std::string buf;
while (dirent* entry = readdir(dir))
{
const dirent& data = *entry;
if (strcmp(data.d_name, ".") != 0 && strcmp(data.d_name, "..") != 0)
{
joinPaths(buf, path.c_str(), data.d_name);
#if defined(DTTOIF)
mode_t mode = DTTOIF(data.d_type);
#else
mode_t mode = 0;
#endif
// we need to stat an UNKNOWN to be able to tell the type
if ((mode & S_IFMT) == 0)
{
struct stat st = {};
#ifdef _ATFILE_SOURCE
fstatat(fd, data.d_name, &st, 0);
#else
lstat(buf.c_str(), &st);
#endif
mode = st.st_mode;
}
if (mode == S_IFDIR)
{
traverseDirectoryRec(buf, callback);
}
else if (mode == S_IFREG)
{
callback(buf);
}
else if (mode == S_IFLNK)
{
// Skip symbolic links to avoid handling cycles
}
}
}
closedir(dir);
return true;
}
bool traverseDirectory(const std::string& path, const std::function<void(const std::string& name)>& callback)
{
return traverseDirectoryRec(path, callback);
}
#endif
bool isFile(const std::string& path)
{
#ifdef _WIN32
DWORD fileAttributes = GetFileAttributesW(fromUtf8(path).c_str());
if (fileAttributes == INVALID_FILE_ATTRIBUTES)
return false;
return (fileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
#else
struct stat st = {};
lstat(path.c_str(), &st);
return (st.st_mode & S_IFMT) == S_IFREG;
#endif
}
bool isDirectory(const std::string& path)
{
#ifdef _WIN32
DWORD fileAttributes = GetFileAttributesW(fromUtf8(path).c_str());
if (fileAttributes == INVALID_FILE_ATTRIBUTES)
return false;
return (fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
#else
struct stat st = {};
lstat(path.c_str(), &st);
return (st.st_mode & S_IFMT) == S_IFDIR;
#endif
}
std::vector<std::string_view> splitPath(std::string_view path)
{
std::vector<std::string_view> components;
size_t pos = 0;
size_t nextPos = path.find_first_of("\\/", pos);
while (nextPos != std::string::npos)
{
components.push_back(path.substr(pos, nextPos - pos));
pos = nextPos + 1;
nextPos = path.find_first_of("\\/", pos);
}
components.push_back(path.substr(pos));
return components;
}
std::string joinPaths(std::string_view lhs, std::string_view rhs)
{
std::string result = std::string(lhs);
if (!result.empty() && result.back() != '/' && result.back() != '\\')
result += '/';
result += rhs;
return result;
}
std::optional<std::string> getParentPath(std::string_view path)
{
if (path == "" || path == "." || path == "/")
return std::nullopt;
#ifdef _WIN32
if (path.size() == 2 && path.back() == ':')
return std::nullopt;
#endif
size_t slash = path.find_last_of("\\/", path.size() - 1);
if (slash == 0)
return "/";
if (slash != std::string::npos)
return std::string(path.substr(0, slash));
return "";
}
static std::string getExtension(const std::string& path)
{
size_t dot = path.find_last_of(".\\/");
if (dot == std::string::npos || path[dot] != '.')
return "";
return path.substr(dot);
}
std::vector<std::string> getSourceFiles(int argc, char** argv)
{
std::vector<std::string> files;
for (int i = 1; i < argc; ++i)
{
// Early out once we reach --program-args,-a since the remaining args are passed to lua
if (strcmp(argv[i], "--program-args") == 0 || strcmp(argv[i], "-a") == 0)
return files;
// Treat '-' as a special file whose source is read from stdin
// All other arguments that start with '-' are skipped
if (argv[i][0] == '-' && argv[i][1] != '\0')
continue;
std::string normalized = normalizePath(argv[i]);
if (isDirectory(normalized))
{
traverseDirectory(
normalized,
[&](const std::string& name)
{
std::string ext = getExtension(name);
if (ext == ".lua" || ext == ".luau")
files.push_back(name);
}
);
}
else
{
files.push_back(normalized);
}
}
return files;
}
+30
View File
@@ -0,0 +1,30 @@
// This code is based on https://github.com/luau-lang/luau/blob/68cdcc4a3a5f3ed23186c4f7f6b8a5aacf835bee/CLI/include/Luau/FileUtils.h
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <optional>
#include <string>
#include <string_view>
#include <functional>
#include <vector>
std::optional<std::string> getCurrentWorkingDirectory();
std::string normalizePath(std::string_view path);
std::optional<std::string> resolvePath(std::string_view relativePath, std::string_view baseFilePath);
std::optional<std::string> readFile(const std::string& name);
std::optional<std::string> readStdin();
bool hasFileExtension(std::string_view name, const std::vector<std::string>& extensions);
bool isAbsolutePath(std::string_view path);
bool isFile(const std::string& path);
bool isDirectory(const std::string& path);
bool traverseDirectory(const std::string& path, const std::function<void(const std::string& name)>& callback);
std::vector<std::string_view> splitPath(std::string_view path);
std::string joinPaths(std::string_view lhs, std::string_view rhs);
std::optional<std::string> getParentPath(std::string_view path);
std::vector<std::string> getSourceFiles(int argc, char** argv);
+182
View File
@@ -0,0 +1,182 @@
#include <bridge.h>
#include "Luau/Frontend.h"
#include "Luau/BuiltinDefinitions.h"
#define ZIG_LUAU_ANALYSIS(name) ZIG_FN(Luau_Analysis_##name)
ZIG_EXPORT struct luau_FrontendOptions
{
// When true, we retain full type information about every term in the AST.
// Setting this to false cuts back on RAM and is a good idea for batch
// jobs where the type graph is not deeply inspected after typechecking
// is complete.
bool retainFullTypeGraphs = false;
// Run typechecking only in mode required for autocomplete (strict mode in
// order to get more precise type information)
bool forAutocomplete = false;
bool runLintChecks = false;
// When true, some internal complexity limits will be scaled down for modules that miss the limit set by moduleTimeLimitSec
bool applyInternalLimitScaling = false;
};
ZIG_EXPORT Luau::Frontend* ZIG_LUAU_ANALYSIS(Frontend_init)(Luau::FileResolver* fileResolver, Luau::ConfigResolver* configResolver, luau_FrontendOptions options)
{
Luau::FrontendOptions frontendOptions;
frontendOptions.retainFullTypeGraphs = options.retainFullTypeGraphs;
frontendOptions.runLintChecks = options.runLintChecks;
frontendOptions.forAutocomplete = options.forAutocomplete;
frontendOptions.applyInternalLimitScaling = options.applyInternalLimitScaling;
return new Luau::Frontend(fileResolver, configResolver, frontendOptions);
}
ZIG_EXPORT using Frontend_loadDefinitionError = bool (*)(void* ctx, const char* str, size_t len, Luau::Location location);
ZIG_EXPORT bool ZIG_LUAU_ANALYSIS(Frontend_loadDefinitionFile)(
Luau::Frontend* frontend,
const char* src,
size_t srcLen,
const char* packagename,
bool captureComments,
bool typeCheckForAutocomplete,
void* ctx,
Frontend_loadDefinitionError fn_loadDefinitionError
)
{
std::string source(src, srcLen);
std::string packageName(packagename);
Luau::LoadDefinitionFileResult result = frontend->loadDefinitionFile(frontend->globals, frontend->globals.globalScope, source, packageName, captureComments, typeCheckForAutocomplete);
if (!result.success)
{
if (fn_loadDefinitionError)
{
if (result.parseResult.errors.size() > 0){
Luau::ParseError error = result.parseResult.errors.front();
std::string msg = error.getMessage();
Luau::Location location = error.getLocation();
fn_loadDefinitionError(ctx, msg.c_str(), msg.size(), location);
} else if (result.module->errors.size() > 0) {
Luau::TypeError error = result.module->errors.front();
std::string msg = "<type error>";
Luau::Location location = error.location;
fn_loadDefinitionError(ctx, msg.c_str(), msg.size(), location);
}
}
}
return result.success;
}
ZIG_EXPORT void ZIG_LUAU_ANALYSIS(Frontend_registerBuiltinGlobals)(Luau::Frontend& frontend)
{
Luau::registerBuiltinGlobals(frontend, frontend.globals);
}
ZIG_EXPORT void ZIG_LUAU_ANALYSIS(Frontend_freeze)(Luau::Frontend* frontend)
{
Luau::freeze(frontend->globals.globalTypes);
}
ZIG_EXPORT void ZIG_LUAU_ANALYSIS(Frontend_queueModuleCheck)(Luau::Frontend* frontend, const char* path, size_t pathLen)
{
std::string modulePath(path, pathLen);
frontend->queueModuleCheck(modulePath);
}
ZIG_EXPORT using Frontend_checkedModule = bool (*)(void* ctx, const char* str, size_t len);
ZIG_EXPORT using Frontend_checkedModuleError = void (*)(
void* ctx,
const char* moduleName,
size_t moduleNameLen,
const char* errorMessage,
size_t errorMessageLen,
Luau::Location location
);
ZIG_EXPORT bool ZIG_LUAU_ANALYSIS(Frontend_checkQueuedModules)(
Luau::Frontend* frontend,
void* ctx,
Frontend_checkedModule fn_checkedModule,
Frontend_checkedModuleError fn_checkedModuleError
)
{
std::vector<Luau::ModuleName> checkedModules;
try
{
checkedModules = frontend->checkQueuedModules(std::nullopt);
}
catch (const Luau::InternalCompilerError& ice)
{
Luau::Location location = ice.location ? *ice.location : Luau::Location();
std::string moduleName = ice.moduleName ? *ice.moduleName : "<unknown module>";
std::string readableName = frontend->fileResolver->getHumanReadableModuleName(moduleName);
if (fn_checkedModuleError)
fn_checkedModuleError(ctx, readableName.c_str(), readableName.size(), ice.message.c_str(), ice.message.size(), location);
return false;
}
for (const auto& module : checkedModules)
{
if (!fn_checkedModule(ctx, module.c_str(), module.size()))
return false;
}
return true;
}
ZIG_EXPORT using Frontend_checkedResult = void (*)(
void* ctx,
unsigned char kind,
const char* moduleName,
size_t moduleNameLen,
const char* errorMessage,
size_t errorMessageLen,
const char* typeName,
Luau::Location location
);
ZIG_EXPORT unsigned char ZIG_LUAU_ANALYSIS(Frontend_getCheckResult)(
Luau::Frontend* frontend,
const char* moduleName,
size_t moduleNameLen,
bool accumulateNested,
bool forAutocomplete,
void* ctx,
Frontend_checkedResult fn_checkedResult
)
{
std::string name(moduleName, moduleNameLen);
std::optional<Luau::CheckResult> cr = frontend->getCheckResult(name, false);
if (!cr)
{
return 0;
}
for (auto& error : cr->errors)
{
std::string readableName = frontend->fileResolver->getHumanReadableModuleName(error.moduleName);
if (const Luau::SyntaxError* syntaxError = Luau::get_if<Luau::SyntaxError>(&error.data))
fn_checkedResult(ctx, 0, readableName.c_str(), readableName.size(), syntaxError->message.c_str(), syntaxError->message.size(), "SyntaxError", error.location);
else
{
std::string msg = Luau::toString(error, Luau::TypeErrorToStringOptions{frontend->fileResolver});
fn_checkedResult(ctx, 0, readableName.c_str(), readableName.size(), msg.c_str(), msg.size(), "TypeError", error.location);
}
}
std::string readableName = frontend->fileResolver->getHumanReadableModuleName(name);
for (auto& error : cr->lintResult.errors)
fn_checkedResult(ctx, 1, readableName.c_str(), readableName.size(), error.text.c_str(), error.text.size(), Luau::LintWarning::getName(error.code), error.location);
for (auto& warning : cr->lintResult.warnings)
fn_checkedResult(ctx, 2, readableName.c_str(), readableName.size(), warning.text.c_str(), warning.text.size(), Luau::LintWarning::getName(warning.code), warning.location);
return cr->errors.empty() && cr->lintResult.errors.empty() ? 1 : 2;
}
ZIG_EXPORT void ZIG_LUAU_ANALYSIS(Frontend_dtor)(Luau::Frontend* frontend)
{
delete frontend;
}
+383
View File
@@ -0,0 +1,383 @@
const std = @import("std");
const Location = @import("../Ast/Location.zig").Location;
const GenericConfigResolver = @import("GenericConfigResolver.zig");
pub const LoadDefinitionResult = extern struct {
success: bool,
};
pub const Options = extern struct {
/// When true, we retain full type information about every term in the AST.
/// Setting this to false cuts back on RAM and is a good idea for batch
/// jobs where the type graph is not deeply inspected after typechecking
/// is complete.
retainFullTypeGraphs: bool = false,
/// Run typechecking only in mode required for autocomplete (strict mode in
/// order to get more precise type information)
forAutocomplete: bool = false,
runLintChecks: bool = false,
/// When true, some internal complexity limits will be scaled down for modules that miss the limit set by moduleTimeLimitSec
applyInternalLimitScaling: bool = false,
};
pub const CheckResultStatus = enum(u8) {
None,
Success,
Error,
};
pub const CheckResultErrorKind = enum(u8) {
Error,
LintError,
LintWarning,
};
const loadDefinitionFileErrorFn = fn (?*anyopaque, [*c]const u8, usize, Location) callconv(.c) void;
const CheckedModuleFn = fn (?*anyopaque, [*c]const u8, usize) callconv(.c) bool;
const CheckedModuleErrorFn = fn (?*anyopaque, [*c]const u8, usize, [*c]const u8, usize, Location) callconv(.c) void;
const CheckedResultFn = fn (?*anyopaque, u8, [*c]const u8, usize, [*c]const u8, usize, [*c]const u8, Location) callconv(.c) void;
extern "c" fn zig_luau_free(ptr: *anyopaque) void;
extern "c" fn zig_Luau_Analysis_Frontend_init(*anyopaque, *GenericConfigResolver.GenericConfigResolver, Options) *Frontend;
extern "c" fn zig_Luau_Analysis_Frontend_registerBuiltinGlobals(*Frontend) void;
extern "c" fn zig_Luau_Analysis_Frontend_freeze(*Frontend) void;
extern "c" fn zig_Luau_Analysis_Frontend_loadDefinitionFile(*Frontend, [*c]const u8, usize, [*c]const u8, bool, bool, ?*anyopaque, ?*const loadDefinitionFileErrorFn) bool;
extern "c" fn zig_Luau_Analysis_Frontend_queueModuleCheck(*Frontend, [*c]const u8, usize) void;
extern "c" fn zig_Luau_Analysis_Frontend_checkQueuedModules(*Frontend, ?*anyopaque, *const CheckedModuleFn, ?*const CheckedModuleErrorFn) bool;
extern "c" fn zig_Luau_Analysis_Frontend_getCheckResult(*Frontend, [*c]const u8, usize, bool, bool, ?*anyopaque, *const CheckedResultFn) u8;
extern "c" fn zig_Luau_Analysis_Frontend_dtor(*Frontend) void;
pub const Frontend = opaque {
pub fn registerBuiltinGlobals(self: *Frontend) void {
zig_Luau_Analysis_Frontend_registerBuiltinGlobals(self);
}
pub fn freeze(self: *Frontend) void {
zig_Luau_Analysis_Frontend_freeze(self);
}
pub fn queueModuleCheck(self: *Frontend, path: []const u8) void {
return zig_Luau_Analysis_Frontend_queueModuleCheck(self, path.ptr, path.len);
}
pub fn checkQueuedModules(
self: *Frontend,
context: anytype,
comptime checkedModule: *const fn (@TypeOf(context), [:0]const u8) bool,
comptime checkedModuleError: *const fn (@TypeOf(context), moduleName: [:0]const u8, errMsg: [:0]const u8, Location) void,
) bool {
const T = @TypeOf(context);
if (@typeInfo(T) != .pointer and T != void)
@compileError("context must be a pointer type or void");
if (T != void and @typeInfo(T).pointer.is_const)
@compileError("context must be a mutable pointer type or void");
return zig_Luau_Analysis_Frontend_checkQueuedModules(
self,
if (T == void) null else @ptrCast(@alignCast(context)),
struct {
fn inner(
ud: ?*anyopaque,
name: [*c]const u8,
len: usize,
) callconv(.c) bool {
return @call(.always_inline, checkedModule, .{
if (T == void) undefined else @as(T, @ptrCast(@alignCast(ud.?))),
name[0..len :0],
});
}
}.inner,
struct {
fn inner(
ud: ?*anyopaque,
name: [*c]const u8,
len: usize,
msg: [*c]const u8,
msgLen: usize,
loc: Location,
) callconv(.c) void {
@call(.always_inline, checkedModuleError, .{
if (T == void) undefined else @as(T, @ptrCast(@alignCast(ud.?))),
name[0..len :0],
msg[0..msgLen :0],
loc,
});
}
}.inner,
);
}
pub fn getCheckResult(
self: *Frontend,
moduleName: []const u8,
captureComments: bool,
typeCheckForAutocomplete: bool,
context: anytype,
comptime checkFn: *const fn (@TypeOf(context), CheckResultErrorKind, [:0]const u8, [:0]const u8, [:0]const u8, Location) void,
) CheckResultStatus {
const T = @TypeOf(context);
if (@typeInfo(T) != .pointer and T != void)
@compileError("context must be a pointer type or void");
if (T != void and @typeInfo(T).pointer.is_const)
@compileError("context must be a mutable pointer type or void");
const result = zig_Luau_Analysis_Frontend_getCheckResult(
self,
moduleName.ptr,
moduleName.len,
captureComments,
typeCheckForAutocomplete,
if (T == void) null else @as(*anyopaque, @ptrCast(@alignCast(context))),
struct {
fn inner(
ud: ?*anyopaque,
kind: u8,
readableModuleName: [*c]const u8,
readableModuleNameLen: usize,
errorMessage: [*c]const u8,
errorMessageLen: usize,
contextName: [*c]const u8,
loc: Location,
) callconv(.c) void {
@call(.always_inline, checkFn, .{
if (T == void) undefined else @as(T, @ptrCast(@alignCast(ud.?))),
@as(CheckResultErrorKind, @enumFromInt(kind)),
readableModuleName[0..readableModuleNameLen :0],
errorMessage[0..errorMessageLen :0],
std.mem.span(contextName),
loc,
});
}
}.inner,
);
return @enumFromInt(result);
}
pub fn loadDefinitionFile(
self: *Frontend,
src: []const u8,
packageName: [:0]const u8,
captureComments: bool,
typeCheckForAutocomplete: ?bool,
) bool {
return zig_Luau_Analysis_Frontend_loadDefinitionFile(self, src.ptr, src.len, packageName, captureComments, typeCheckForAutocomplete orelse false, null, null);
}
const LoadDefintionResult = struct {
message: []const u8,
location: Location,
allocator: std.mem.Allocator,
pub fn deinit(self: *LoadDefintionResult) void {
self.allocator.free(self.message);
}
};
pub fn loadDefinitionFileWithAlloc(
self: *Frontend,
allocator: std.mem.Allocator,
src: []const u8,
packageName: [:0]const u8,
captureComments: bool,
typeCheckForAutocomplete: ?bool,
) !?LoadDefintionResult {
var result: struct { anyerror, LoadDefintionResult } = .{ error.None, .{
.allocator = allocator,
.message = undefined,
.location = undefined,
} };
const success = zig_Luau_Analysis_Frontend_loadDefinitionFile(self, src.ptr, src.len, packageName, captureComments, typeCheckForAutocomplete orelse false, &result, struct {
fn inner(
ud: ?*anyopaque,
msg: [*c]const u8,
len: usize,
loc: Location,
) callconv(.c) void {
const res: *struct { anyerror, LoadDefintionResult } = @ptrCast(@alignCast(ud.?));
res.@"1".location = loc;
res.@"1".message = res.@"1".allocator.dupe(u8, msg[0..len]) catch |err| {
res.@"0" = err;
return;
};
}
}.inner);
if (success) {
return null;
}
if (result.@"0" != error.None) {
return result.@"0";
}
return result.@"1";
}
pub fn deinit(self: *Frontend) void {
zig_Luau_Analysis_Frontend_dtor(self);
}
};
pub fn init(fileResolver: anytype, configResolver: *GenericConfigResolver.GenericConfigResolver, opts: Options) *Frontend {
return zig_Luau_Analysis_Frontend_init(@ptrCast(@alignCast(fileResolver)), configResolver, opts);
}
test Frontend {
const FileResolver = @import("FileResolver.zig");
{
const FileImpl = struct {
const Self = @This();
pub fn readSource(_: *Self, _: []const u8) ?struct { []const u8, FileResolver.SourceCodeType } {
return null;
}
pub fn resolveModule(_: *Self, _: []const u8, _: []const u8) ?[]const u8 {
return null;
}
pub fn getHumanReadableModuleName(_: *Self, name: []const u8) []const u8 {
return name;
}
pub fn freeString(_: *Self, _: []const u8) void {}
};
const FileImplResolver = FileResolver.FileResolver(FileImpl);
var file_impl: FileImpl = .{};
const file_resolver = FileImplResolver.init(&file_impl);
defer file_resolver.deinit();
const config_resolver = GenericConfigResolver.init(.Strict);
defer config_resolver.deinit();
const frontend = init(file_resolver, config_resolver, .{});
defer frontend.deinit();
frontend.registerBuiltinGlobals();
var load_result = try frontend.loadDefinitionFileWithAlloc(
std.testing.allocator,
\\ - This is a test
,
"@test",
false,
null,
) orelse @panic("no fail");
defer load_result.deinit();
try std.testing.expectEqualStrings("Expected identifier when parsing expression, got '-'", load_result.message);
try std.testing.expectEqual(0, load_result.location.begin.line);
try std.testing.expectEqual(1, load_result.location.begin.column);
try std.testing.expectEqual(0, load_result.location.end.line);
try std.testing.expectEqual(2, load_result.location.end.column);
}
{
const StaticFileTree = std.StaticStringMap([]const u8).initComptime(.{
.{
"./main.luau",
\\local module = require("./module.luau")
,
},
.{
"./module.luau",
\\print("module");
\\return {};
,
},
.{
"./sub/test.luau",
\\local test = global.foo;
\\local test2 = g.foo;
\\
,
},
});
const FileImpl = struct {
const Self = @This();
pub fn readSource(_: *Self, path: []const u8) ?struct { []const u8, FileResolver.SourceCodeType } {
const source = StaticFileTree.get(path) orelse @panic("failed to find source");
return .{ source, .Module };
}
pub fn resolveModule(_: *Self, _: []const u8, to: []const u8) ?[]const u8 {
return to;
}
pub fn getHumanReadableModuleName(_: *Self, name: []const u8) []const u8 {
return name;
}
pub fn freeString(_: *Self, _: []const u8) void {}
};
const FileImplResolver = FileResolver.FileResolver(FileImpl);
var file_impl: FileImpl = .{};
const file_resolver = FileImplResolver.init(&file_impl);
defer file_resolver.deinit();
const config_resolver = GenericConfigResolver.init(.Strict);
defer config_resolver.deinit();
const frontend = init(file_resolver, config_resolver, .{});
defer frontend.deinit();
frontend.registerBuiltinGlobals();
try std.testing.expectEqual(null, try frontend.loadDefinitionFileWithAlloc(
std.testing.allocator,
\\declare global: {
\\ foo: string,
\\}
,
"@main",
false,
null,
));
frontend.queueModuleCheck("./main.luau");
frontend.queueModuleCheck("./sub/test.luau");
const success = frontend.checkQueuedModules(
frontend,
struct {
fn checkedModule(f: *Frontend, name: [:0]const u8) bool {
switch (f.getCheckResult(name, false, false, @as(void, undefined), struct {
fn inner(_: void, kind: CheckResultErrorKind, readableModuleName: [:0]const u8, errorMessage: [:0]const u8, typeName: [:0]const u8, loc: Location) void {
if (!std.mem.eql(u8, readableModuleName, "./sub/test.luau"))
@panic("Expected no errors in main module");
std.testing.expectEqual(.Error, kind) catch @panic("failed");
std.testing.expectEqualStrings("Unknown global 'g'; consider assigning to it first", errorMessage) catch @panic("failed");
std.testing.expectEqualStrings("TypeError", typeName) catch @panic("failed");
std.testing.expectEqual(1, loc.begin.line) catch @panic("failed");
std.testing.expectEqual(14, loc.begin.column) catch @panic("failed");
std.testing.expectEqual(1, loc.end.line) catch @panic("failed");
std.testing.expectEqual(15, loc.end.column) catch @panic("failed");
}
}.inner)) {
.None => unreachable,
.Success => {},
.Error => if (!std.mem.eql(u8, name, "./sub/test.luau")) @panic("Expected no errors in main module"),
}
return true;
}
}.checkedModule,
struct {
fn checkedModuleError(_: *Frontend, name: [:0]const u8, errMsg: [:0]const u8, loc: Location) void {
std.debug.print("Error in module {s}: {s} at {d}:{d}-{d}:{d}\n", .{
name,
errMsg,
loc.begin.line,
loc.begin.column,
loc.end.line,
loc.end.column,
});
@panic("Module check failed");
}
}.checkedModuleError,
);
try std.testing.expect(success);
}
}
+84
View File
@@ -0,0 +1,84 @@
#include <bridge.h>
#include "Luau/ConfigResolver.h"
#include "./FileUtils.h"
#define ZIG_LUAU_ANALYSIS(name) ZIG_FN(Luau_Analysis_##name)
// This code is based on https://github.com/luau-lang/luau/blob/68cdcc4a3a5f3ed23186c4f7f6b8a5aacf835bee/CLI/src/Analyze.cpp#L202
struct GenericConfigResolver : Luau::ConfigResolver
{
mutable std::vector<std::pair<std::string, std::string>> configErrors;
mutable std::unordered_map<std::string, Luau::Config> configCache;
Luau::Config defaultConfig;
GenericConfigResolver(Luau::Mode mode)
{
defaultConfig.mode = mode;
}
const Luau::Config& getConfig(const Luau::ModuleName& name, const Luau::TypeCheckLimits& limits) const override
{
std::optional<std::string> path = getParentPath(name);
if (!path)
return defaultConfig;
return readConfigRec(*path, limits);
}
const Luau::Config& readConfigRec(const std::string& path, const Luau::TypeCheckLimits& limits) const
{
auto it = configCache.find(path);
if (it != configCache.end())
return it->second;
std::optional<std::string> parent = getParentPath(path);
Luau::Config result = parent ? readConfigRec(*parent, limits) : defaultConfig;
std::string configPath = joinPaths(path, Luau::kConfigName);
if (std::optional<std::string> contents = readFile(configPath))
{
Luau::ConfigOptions::AliasOptions aliasOpts;
aliasOpts.configLocation = configPath;
aliasOpts.overwriteAliases = true;
Luau::ConfigOptions opts;
opts.aliasOptions = std::move(aliasOpts);
std::optional<std::string> error = Luau::parseConfig(*contents, result, opts);
if (error)
configErrors.push_back({configPath, *error});
}
return configCache[path] = result;
}
};
ZIG_EXPORT GenericConfigResolver* ZIG_LUAU_ANALYSIS(GenericConfigResolver_init)(unsigned char mode)
{
Luau::Mode luauMode = Luau::Mode::NoCheck;
if (mode == 0)
luauMode = Luau::Mode::NoCheck;
else if (mode == 1)
luauMode = Luau::Mode::Nonstrict;
else if (mode == 2)
luauMode = Luau::Mode::Strict;
else if (mode == 3)
luauMode = Luau::Mode::Definition;
return new GenericConfigResolver(luauMode);
}
ZIG_EXPORT struct ErrorGroup
{
const char **paths;
const char **messages;
size_t size;
};
ZIG_EXPORT void ZIG_LUAU_ANALYSIS(GenericConfigResolver_dtor)(GenericConfigResolver* resolver)
{
delete resolver;
}
+59
View File
@@ -0,0 +1,59 @@
const std = @import("std");
const cpp_std = @import("../cpp_std.zig");
const ErrorGroup = extern struct {
paths: [*][*c]const u8,
messages: [*][*c]const u8,
size: usize,
};
const Mode = enum(u8) {
NoCheck = 0,
Nonstrict = 1,
Strict = 2,
Definition = 3,
};
const ConfigErrors = cpp_std.Vector(cpp_std.Pair(cpp_std.String, cpp_std.String));
const ResolverInterface = extern struct {
vtable: *const anyopaque,
errors: ConfigErrors,
};
extern "c" fn zig_Luau_Analysis_GenericConfigResolver_init(u8) *GenericConfigResolver;
extern "c" fn zig_Luau_Analysis_GenericConfigResolver_dtor(*GenericConfigResolver) void;
pub const GenericConfigResolver = opaque {
pub const AnyErrorGroup = struct {
group: ErrorGroup,
pub fn paths(self: AnyErrorGroup) []const [*c]const u8 {
return self.group.paths[0..self.group.size];
}
pub fn messages(self: AnyErrorGroup) []const [*c]const u8 {
return self.group.messages[0..self.group.size];
}
};
pub fn getErrors(self: *GenericConfigResolver) ConfigErrors {
return @as(*ResolverInterface, @ptrCast(@alignCast(self))).errors;
}
pub fn deinit(self: *GenericConfigResolver) void {
zig_Luau_Analysis_GenericConfigResolver_dtor(self);
}
};
pub fn init(mode: Mode) *GenericConfigResolver {
return zig_Luau_Analysis_GenericConfigResolver_init(@intFromEnum(mode));
}
test GenericConfigResolver {
const resolver = init(.Strict);
defer resolver.deinit();
const errors = resolver.getErrors();
try std.testing.expect(errors.size() == 0);
}
+15
View File
@@ -0,0 +1,15 @@
#include <bridge.h>
#include "Luau/Allocator.h"
#define ZIG_LUAU_AST(name) ZIG_FN(Luau_Ast_##name)
ZIG_EXPORT Luau::Allocator* ZIG_LUAU_AST(Allocator_init)()
{
return new Luau::Allocator();
}
ZIG_EXPORT void ZIG_LUAU_AST(Allocator_dtor)(Luau::Allocator* allocator)
{
delete allocator;
}
+46
View File
@@ -0,0 +1,46 @@
const std = @import("std");
// extern fn zig_delete_any(*Page) callconv(.c) void;
extern "c" fn zig_Luau_Ast_Allocator_init() *This;
extern "c" fn zig_Luau_Ast_Allocator_dtor(*This) void;
const This = @This();
root: [*c]Page,
offset: usize = 0,
pub const Page = extern struct {
next: [*c]Page = null,
data: [8192]u8 align(8),
};
// /// cleans up the luau allocator
// /// frees all pages created by C++
// pub fn destroy(self: This) void {
// var page = self.root;
// while (page != null) {
// const next = page.*.next;
// // pages are C++ allocated, so we need to use the C++ deallocator
// zig_delete_any(page);
// std.debug.print("clean page\n", .{});
// page = next;
// }
// }
pub fn init() *This {
return zig_Luau_Ast_Allocator_init();
}
pub fn deinit(self: *This) void {
zig_Luau_Ast_Allocator_dtor(self);
}
test This {
const allocator = This.init();
defer allocator.deinit();
}
// sources:
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Ast/include/Luau/Allocator.h
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Ast/src/Allocator.cpp
+2454
View File
File diff suppressed because it is too large Load Diff
+211
View File
@@ -0,0 +1,211 @@
#include <bridge.h>
#include "Luau/Ast.h"
#include "Luau/Cst.h"
using namespace Luau;
// AST
ZIG_EXPORT const unsigned char AstAttrIndex = AstAttr::ClassIndex();
ZIG_EXPORT const unsigned char AstGenericTypeIndex = AstGenericType::ClassIndex();
ZIG_EXPORT const unsigned char AstGenericTypePackIndex = AstGenericTypePack::ClassIndex();
ZIG_EXPORT const unsigned char AstExprGroupIndex = AstExprGroup::ClassIndex();
ZIG_EXPORT const unsigned char AstExprConstantNilIndex = AstExprConstantNil::ClassIndex();
ZIG_EXPORT const unsigned char AstExprConstantBoolIndex = AstExprConstantBool::ClassIndex();
ZIG_EXPORT const unsigned char AstExprConstantNumberIndex = AstExprConstantNumber::ClassIndex();
ZIG_EXPORT const unsigned char AstExprConstantIntegerIndex = AstExprConstantInteger::ClassIndex();
ZIG_EXPORT const unsigned char AstExprConstantStringIndex = AstExprConstantString::ClassIndex();
ZIG_EXPORT const unsigned char AstExprLocalIndex = AstExprLocal::ClassIndex();
ZIG_EXPORT const unsigned char AstExprGlobalIndex = AstExprGlobal::ClassIndex();
ZIG_EXPORT const unsigned char AstExprVarargsIndex = AstExprVarargs::ClassIndex();
ZIG_EXPORT const unsigned char AstExprCallIndex = AstExprCall::ClassIndex();
ZIG_EXPORT const unsigned char AstExprIndexNameIndex = AstExprIndexName::ClassIndex();
ZIG_EXPORT const unsigned char AstExprIndexExprIndex = AstExprIndexExpr::ClassIndex();
ZIG_EXPORT const unsigned char AstExprFunctionIndex = AstExprFunction::ClassIndex();
ZIG_EXPORT const unsigned char AstExprTableIndex = AstExprTable::ClassIndex();
ZIG_EXPORT const unsigned char AstExprUnaryIndex = AstExprUnary::ClassIndex();
ZIG_EXPORT const unsigned char AstExprBinaryIndex = AstExprBinary::ClassIndex();
ZIG_EXPORT const unsigned char AstExprTypeAssertionIndex = AstExprTypeAssertion::ClassIndex();
ZIG_EXPORT const unsigned char AstExprIfElseIndex = AstExprIfElse::ClassIndex();
ZIG_EXPORT const unsigned char AstExprInterpStringIndex = AstExprInterpString::ClassIndex();
ZIG_EXPORT const unsigned char AstExprInstantiateIndex = AstExprInstantiate::ClassIndex();
ZIG_EXPORT const unsigned char AstStatBlockIndex = AstStatBlock::ClassIndex();
ZIG_EXPORT const unsigned char AstStatIfIndex = AstStatIf::ClassIndex();
ZIG_EXPORT const unsigned char AstStatWhileIndex = AstStatWhile::ClassIndex();
ZIG_EXPORT const unsigned char AstStatRepeatIndex = AstStatRepeat::ClassIndex();
ZIG_EXPORT const unsigned char AstStatBreakIndex = AstStatBreak::ClassIndex();
ZIG_EXPORT const unsigned char AstStatContinueIndex = AstStatContinue::ClassIndex();
ZIG_EXPORT const unsigned char AstStatReturnIndex = AstStatReturn::ClassIndex();
ZIG_EXPORT const unsigned char AstStatExprIndex = AstStatExpr::ClassIndex();
ZIG_EXPORT const unsigned char AstStatLocalIndex = AstStatLocal::ClassIndex();
ZIG_EXPORT const unsigned char AstStatForIndex = AstStatFor::ClassIndex();
ZIG_EXPORT const unsigned char AstStatForInIndex = AstStatForIn::ClassIndex();
ZIG_EXPORT const unsigned char AstStatAssignIndex = AstStatAssign::ClassIndex();
ZIG_EXPORT const unsigned char AstStatCompoundAssignIndex = AstStatCompoundAssign::ClassIndex();
ZIG_EXPORT const unsigned char AstStatFunctionIndex = AstStatFunction::ClassIndex();
ZIG_EXPORT const unsigned char AstStatLocalFunctionIndex = AstStatLocalFunction::ClassIndex();
ZIG_EXPORT const unsigned char AstStatTypeAliasIndex = AstStatTypeAlias::ClassIndex();
ZIG_EXPORT const unsigned char AstStatTypeFunctionIndex = AstStatTypeFunction::ClassIndex();
ZIG_EXPORT const unsigned char AstStatDeclareFunctionIndex = AstStatDeclareFunction::ClassIndex();
ZIG_EXPORT const unsigned char AstStatDeclareGlobalIndex = AstStatDeclareGlobal::ClassIndex();
ZIG_EXPORT const unsigned char AstStatClassIndex = AstStatClass::ClassIndex();
ZIG_EXPORT const unsigned char AstStatDeclareExternTypeIndex = AstStatDeclareExternType::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeReferenceIndex = AstTypeReference::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeTableIndex = AstTypeTable::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeFunctionIndex = AstTypeFunction::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeTypeofIndex = AstTypeTypeof::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeOptionalIndex = AstTypeOptional::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeUnionIndex = AstTypeUnion::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeIntersectionIndex = AstTypeIntersection::ClassIndex();
ZIG_EXPORT const unsigned char AstExprErrorIndex = AstExprError::ClassIndex();
ZIG_EXPORT const unsigned char AstStatErrorIndex = AstStatError::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeErrorIndex = AstTypeError::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeSingletonBoolIndex = AstTypeSingletonBool::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeSingletonStringIndex = AstTypeSingletonString::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeGroupIndex = AstTypeGroup::ClassIndex();
ZIG_EXPORT const unsigned char AstTypePackExplicitIndex = AstTypePackExplicit::ClassIndex();
ZIG_EXPORT const unsigned char AstTypePackVariadicIndex = AstTypePackVariadic::ClassIndex();
ZIG_EXPORT const unsigned char AstTypePackGenericIndex = AstTypePackGeneric::ClassIndex();
ZIG_EXPORT const unsigned long AstAttrSize = sizeof(AstAttr);
ZIG_EXPORT const unsigned long AstGenericTypeSize = sizeof(AstGenericType);
ZIG_EXPORT const unsigned long AstGenericTypePackSize = sizeof(AstGenericTypePack);
ZIG_EXPORT const unsigned long AstExprGroupSize = sizeof(AstExprGroup);
ZIG_EXPORT const unsigned long AstExprConstantNilSize = sizeof(AstExprConstantNil);
ZIG_EXPORT const unsigned long AstExprConstantBoolSize = sizeof(AstExprConstantBool);
ZIG_EXPORT const unsigned long AstExprConstantNumberSize = sizeof(AstExprConstantNumber);
ZIG_EXPORT const unsigned long AstExprConstantIntegerSize = sizeof(AstExprConstantInteger);
ZIG_EXPORT const unsigned long AstExprConstantStringSize = sizeof(AstExprConstantString);
ZIG_EXPORT const unsigned long AstExprLocalSize = sizeof(AstExprLocal);
ZIG_EXPORT const unsigned long AstExprGlobalSize = sizeof(AstExprGlobal);
ZIG_EXPORT const unsigned long AstExprVarargsSize = sizeof(AstExprVarargs);
ZIG_EXPORT const unsigned long AstExprCallSize = sizeof(AstExprCall);
ZIG_EXPORT const unsigned long AstExprIndexNameSize = sizeof(AstExprIndexName);
ZIG_EXPORT const unsigned long AstExprIndexExprSize = sizeof(AstExprIndexExpr);
ZIG_EXPORT const unsigned long AstExprFunctionSize = sizeof(AstExprFunction);
ZIG_EXPORT const unsigned long AstExprTableSize = sizeof(AstExprTable);
ZIG_EXPORT const unsigned long AstExprUnarySize = sizeof(AstExprUnary);
ZIG_EXPORT const unsigned long AstExprBinarySize = sizeof(AstExprBinary);
ZIG_EXPORT const unsigned long AstExprTypeAssertionSize = sizeof(AstExprTypeAssertion);
ZIG_EXPORT const unsigned long AstExprIfElseSize = sizeof(AstExprIfElse);
ZIG_EXPORT const unsigned long AstExprInterpStringSize = sizeof(AstExprInterpString);
ZIG_EXPORT const unsigned long AstExprInstantiateSize = sizeof(AstExprInstantiate);
ZIG_EXPORT const unsigned long AstStatBlockSize = sizeof(AstStatBlock);
ZIG_EXPORT const unsigned long AstStatIfSize = sizeof(AstStatIf);
ZIG_EXPORT const unsigned long AstStatWhileSize = sizeof(AstStatWhile);
ZIG_EXPORT const unsigned long AstStatRepeatSize = sizeof(AstStatRepeat);
ZIG_EXPORT const unsigned long AstStatBreakSize = sizeof(AstStatBreak);
ZIG_EXPORT const unsigned long AstStatContinueSize = sizeof(AstStatContinue);
ZIG_EXPORT const unsigned long AstStatReturnSize = sizeof(AstStatReturn);
ZIG_EXPORT const unsigned long AstStatExprSize = sizeof(AstStatExpr);
ZIG_EXPORT const unsigned long AstStatLocalSize = sizeof(AstStatLocal);
ZIG_EXPORT const unsigned long AstStatForSize = sizeof(AstStatFor);
ZIG_EXPORT const unsigned long AstStatForInSize = sizeof(AstStatForIn);
ZIG_EXPORT const unsigned long AstStatAssignSize = sizeof(AstStatAssign);
ZIG_EXPORT const unsigned long AstStatCompoundAssignSize = sizeof(AstStatCompoundAssign);
ZIG_EXPORT const unsigned long AstStatFunctionSize = sizeof(AstStatFunction);
ZIG_EXPORT const unsigned long AstStatLocalFunctionSize = sizeof(AstStatLocalFunction);
ZIG_EXPORT const unsigned long AstStatTypeAliasSize = sizeof(AstStatTypeAlias);
ZIG_EXPORT const unsigned long AstStatTypeFunctionSize = sizeof(AstStatTypeFunction);
ZIG_EXPORT const unsigned long AstStatDeclareFunctionSize = sizeof(AstStatDeclareFunction);
ZIG_EXPORT const unsigned long AstStatDeclareGlobalSize = sizeof(AstStatDeclareGlobal);
ZIG_EXPORT const unsigned long AstStatClassSize = sizeof(AstStatClass);
ZIG_EXPORT const unsigned long AstStatDeclareExternTypeSize = sizeof(AstStatDeclareExternType);
ZIG_EXPORT const unsigned long AstTypeReferenceSize = sizeof(AstTypeReference);
ZIG_EXPORT const unsigned long AstTypeTableSize = sizeof(AstTypeTable);
ZIG_EXPORT const unsigned long AstTypeFunctionSize = sizeof(AstTypeFunction);
ZIG_EXPORT const unsigned long AstTypeTypeofSize = sizeof(AstTypeTypeof);
ZIG_EXPORT const unsigned long AstTypeOptionalSize = sizeof(AstTypeOptional);
ZIG_EXPORT const unsigned long AstTypeUnionSize = sizeof(AstTypeUnion);
ZIG_EXPORT const unsigned long AstTypeIntersectionSize = sizeof(AstTypeIntersection);
ZIG_EXPORT const unsigned long AstExprErrorSize = sizeof(AstExprError);
ZIG_EXPORT const unsigned long AstStatErrorSize = sizeof(AstStatError);
ZIG_EXPORT const unsigned long AstTypeErrorSize = sizeof(AstTypeError);
ZIG_EXPORT const unsigned long AstTypeSingletonBoolSize = sizeof(AstTypeSingletonBool);
ZIG_EXPORT const unsigned long AstTypeSingletonStringSize = sizeof(AstTypeSingletonString);
ZIG_EXPORT const unsigned long AstTypeGroupSize = sizeof(AstTypeGroup);
ZIG_EXPORT const unsigned long AstTypePackExplicitSize = sizeof(AstTypePackExplicit);
ZIG_EXPORT const unsigned long AstTypePackVariadicSize = sizeof(AstTypePackVariadic);
ZIG_EXPORT const unsigned long AstTypePackGenericSize = sizeof(AstTypePackGeneric);
// CST
ZIG_EXPORT const unsigned char CstAttrIndex = CstAttr::CstClassIndex();
ZIG_EXPORT const unsigned char CstParametrizedAttrIndex = CstParametrizedAttr::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprGroupIndex = CstExprGroup::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprConstantNumberIndex = CstExprConstantNumber::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprConstantIntegerIndex = CstExprConstantInteger::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprConstantStringIndex = CstExprConstantString::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprCallIndex = CstExprCall::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprIndexExprIndex = CstExprIndexExpr::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprFunctionIndex = CstExprFunction::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprTableIndex = CstExprTable::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprOpIndex = CstExprOp::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprTypeAssertionIndex = CstExprTypeAssertion::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprIfElseIndex = CstExprIfElse::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprInterpStringIndex = CstExprInterpString::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprExplicitTypeInstantiationIndex = CstExprExplicitTypeInstantiation::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatDoIndex = CstStatDo::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatRepeatIndex = CstStatRepeat::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatReturnIndex = CstStatReturn::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatLocalIndex = CstStatLocal::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatForIndex = CstStatFor::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatForInIndex = CstStatForIn::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatAssignIndex = CstStatAssign::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatCompoundAssignIndex = CstStatCompoundAssign::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatFunctionIndex = CstStatFunction::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatLocalFunctionIndex = CstStatLocalFunction::CstClassIndex();
ZIG_EXPORT const unsigned char CstGenericTypeIndex = CstGenericType::CstClassIndex();
ZIG_EXPORT const unsigned char CstGenericTypePackIndex = CstGenericTypePack::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatTypeAliasIndex = CstStatTypeAlias::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatTypeFunctionIndex = CstStatTypeFunction::CstClassIndex();
ZIG_EXPORT const unsigned char CstTypeReferenceIndex = CstTypeReference::CstClassIndex();
ZIG_EXPORT const unsigned char CstTypeTableIndex = CstTypeTable::CstClassIndex();
ZIG_EXPORT const int CstTypeTableItemKindIndexer = static_cast<int>(CstTypeTable::Item::Kind::Indexer);
ZIG_EXPORT const int CstTypeTableItemKindProperty = static_cast<int>(CstTypeTable::Item::Kind::Property);
ZIG_EXPORT const int CstTypeTableItemKindStringProperty = static_cast<int>(CstTypeTable::Item::Kind::StringProperty);
ZIG_EXPORT const unsigned char CstTypeFunctionIndex = CstTypeFunction::CstClassIndex();
ZIG_EXPORT const unsigned char CstTypeTypeofIndex = CstTypeTypeof::CstClassIndex();
ZIG_EXPORT const unsigned char CstTypeUnionIndex = CstTypeUnion::CstClassIndex();
ZIG_EXPORT const unsigned char CstTypeIntersectionIndex = CstTypeIntersection::CstClassIndex();
ZIG_EXPORT const unsigned char CstTypeSingletonStringIndex = CstTypeSingletonString::CstClassIndex();
ZIG_EXPORT const unsigned char CstTypeGroupIndex = CstTypeGroup::CstClassIndex();
ZIG_EXPORT const unsigned char CstTypePackExplicitIndex = CstTypePackExplicit::CstClassIndex();
ZIG_EXPORT const unsigned char CstTypePackGenericIndex = CstTypePackGeneric::CstClassIndex();
ZIG_EXPORT const unsigned long CstExprGroupSize = sizeof(CstExprGroup);
ZIG_EXPORT const unsigned long CstExprConstantNumberSize = sizeof(CstExprConstantNumber);
ZIG_EXPORT const unsigned long CstExprConstantIntegerSize = sizeof(CstExprConstantInteger);
ZIG_EXPORT const unsigned long CstExprConstantStringSize = sizeof(CstExprConstantString);
ZIG_EXPORT const unsigned long CstExprCallSize = sizeof(CstExprCall);
ZIG_EXPORT const unsigned long CstExprIndexExprSize = sizeof(CstExprIndexExpr);
ZIG_EXPORT const unsigned long CstExprFunctionSize = sizeof(CstExprFunction);
ZIG_EXPORT const unsigned long CstExprTableSize = sizeof(CstExprTable);
ZIG_EXPORT const unsigned long CstExprOpSize = sizeof(CstExprOp);
ZIG_EXPORT const unsigned long CstExprTypeAssertionSize = sizeof(CstExprTypeAssertion);
ZIG_EXPORT const unsigned long CstExprIfElseSize = sizeof(CstExprIfElse);
ZIG_EXPORT const unsigned long CstExprInterpStringSize = sizeof(CstExprInterpString);
ZIG_EXPORT const unsigned long CstExprExplicitTypeInstantiationSize = sizeof(CstExprExplicitTypeInstantiation);
ZIG_EXPORT const unsigned long CstStatDoSize = sizeof(CstStatDo);
ZIG_EXPORT const unsigned long CstStatRepeatSize = sizeof(CstStatRepeat);
ZIG_EXPORT const unsigned long CstStatReturnSize = sizeof(CstStatReturn);
ZIG_EXPORT const unsigned long CstStatLocalSize = sizeof(CstStatLocal);
ZIG_EXPORT const unsigned long CstStatForSize = sizeof(CstStatFor);
ZIG_EXPORT const unsigned long CstStatForInSize = sizeof(CstStatForIn);
ZIG_EXPORT const unsigned long CstStatAssignSize = sizeof(CstStatAssign);
ZIG_EXPORT const unsigned long CstStatCompoundAssignSize = sizeof(CstStatCompoundAssign);
ZIG_EXPORT const unsigned long CstStatFunctionSize = sizeof(CstStatFunction);
ZIG_EXPORT const unsigned long CstStatLocalFunctionSize = sizeof(CstStatLocalFunction);
ZIG_EXPORT const unsigned long CstGenericTypeSize = sizeof(CstGenericType);
ZIG_EXPORT const unsigned long CstGenericTypePackSize = sizeof(CstGenericTypePack);
ZIG_EXPORT const unsigned long CstStatTypeAliasSize = sizeof(CstStatTypeAlias);
ZIG_EXPORT const unsigned long CstStatTypeFunctionSize = sizeof(CstStatTypeFunction);
ZIG_EXPORT const unsigned long CstTypeReferenceSize = sizeof(CstTypeReference);
ZIG_EXPORT const unsigned long CstTypeTableSize = sizeof(CstTypeTable);
ZIG_EXPORT const unsigned long CstTypeFunctionSize = sizeof(CstTypeFunction);
ZIG_EXPORT const unsigned long CstTypeTypeofSize = sizeof(CstTypeTypeof);
ZIG_EXPORT const unsigned long CstTypeUnionSize = sizeof(CstTypeUnion);
ZIG_EXPORT const unsigned long CstTypeIntersectionSize = sizeof(CstTypeIntersection);
ZIG_EXPORT const unsigned long CstTypeSingletonStringSize = sizeof(CstTypeSingletonString);
ZIG_EXPORT const unsigned long CstTypeGroupSize = sizeof(CstTypeGroup);
ZIG_EXPORT const unsigned long CstTypePackExplicitSize = sizeof(CstTypePackExplicit);
ZIG_EXPORT const unsigned long CstTypePackGenericSize = sizeof(CstTypePackGeneric);
+648
View File
@@ -0,0 +1,648 @@
const std = @import("std");
const Ast = @import("Ast.zig");
const Location = @import("Location.zig").Location;
const cpp_std = @import("../cpp_std.zig");
const Cst = @This();
pub const Node = extern struct {
classIndex: Kind,
pub const Kind = enum(i32) {
unknown,
attr,
parametrized_attr,
expr_group,
expr_constant_number,
expr_constant_integer,
expr_constant_string,
expr_call,
expr_index_expr,
expr_function,
expr_table,
expr_op,
expr_type_assertion,
expr_if_else,
expr_interp_string,
expr_explicit_type_instantiation,
stat_do,
stat_repeat,
stat_return,
stat_local,
stat_for,
stat_for_in,
stat_assign,
stat_compound_assign,
stat_function,
stat_local_function,
generic_type,
generic_type_pack,
stat_type_alias,
stat_type_function,
type_reference,
type_table,
type_function,
type_typeof,
type_union,
type_intersection,
type_singleton_string,
type_group,
type_pack_explicit,
type_pack_generic,
pub fn Type(comptime self: Kind) type {
return switch (self) {
.unknown => Node,
.attr => Attr,
.parametrized_attr => ParametrizedAttr,
.expr_group => ExprGroup,
.expr_constant_number => ExprConstantNumber,
.expr_constant_integer => ExprConstantInteger,
.expr_constant_string => ExprConstantString,
.expr_call => ExprCall,
.expr_index_expr => ExprIndexExpr,
.expr_function => ExprFunction,
.expr_table => ExprTable,
.expr_op => ExprOp,
.expr_type_assertion => ExprTypeAssertion,
.expr_if_else => ExprIfElse,
.expr_interp_string => ExprInterpString,
.expr_explicit_type_instantiation => ExprExplicitTypeInstantiation,
.stat_do => StatDo,
.stat_repeat => StatRepeat,
.stat_return => StatReturn,
.stat_local => StatLocal,
.stat_for => StatFor,
.stat_for_in => StatForIn,
.stat_assign => StatAssign,
.stat_compound_assign => StatCompoundAssign,
.stat_function => StatFunction,
.stat_local_function => StatLocalFunction,
.generic_type => GenericType,
.generic_type_pack => GenericTypePack,
.stat_type_alias => StatTypeAlias,
.stat_type_function => StatTypeFunction,
.type_reference => TypeReference,
.type_table => TypeTable,
.type_function => TypeFunction,
.type_typeof => TypeTypeof,
.type_union => TypeUnion,
.type_intersection => TypeIntersection,
.type_singleton_string => TypeSingletonString,
.type_group => TypeGroup,
.type_pack_explicit => TypePackExplicit,
.type_pack_generic => TypePackGeneric,
};
}
};
pub const is = IsFn;
pub const as = AsCastFn;
};
pub fn IsFn(base: anytype, comptime to: Node.Kind) bool {
return base.classIndex == to;
}
pub fn AsCastFn(base: anytype, comptime to: Node.Kind) ?*to.Type() {
return if (base.classIndex == to) @ptrCast(@alignCast(base)) else null;
}
pub const Attr = extern struct {
classIndex: Node.Kind = .attr,
/// false when inside an attribute list, ie @[native checked]
hasAt: bool,
};
pub const ParametrizedAttr = extern struct {
classIndex: Node.Kind = .parametrized_attr,
/// for `@x(args)` form
openParenPosition: Location.Position,
closeParenPosition: Location.Position,
/// Commas inside the `(a, b, c)` arg list
argsCommaPositions: Ast.Array(Location.Position),
};
pub const AttrList = extern struct {
atBracketPosition: Location.Position,
closeBracketPosition: Location.Position,
commaPositions: Ast.Array(Location.Position),
};
pub const ExprGroup = extern struct {
classIndex: Node.Kind = .expr_group,
closePosition: Location.Position,
};
pub const ExprConstantNumber = extern struct {
classIndex: Node.Kind = .expr_constant_number,
value: Ast.Array(u8),
};
pub const ExprConstantInteger = extern struct {
classIndex: Node.Kind = .expr_constant_integer,
value: Ast.Array(u8),
};
pub const ExprConstantString = extern struct {
classIndex: Node.Kind = .expr_constant_string,
sourceString: Ast.Array(u8),
quoteStyle: QuoteStyle,
blockDepth: u32,
pub const QuoteStyle = enum(u32) {
quoted_single,
quoted_double,
quoted_raw,
quoted_interp,
};
};
pub const TypeInstantiation = extern struct {
leftArrow1Position: Location.Position = .missing,
leftArrow2Position: Location.Position = .missing,
commaPositions: Ast.Array(Location.Position),
rightArrow1Position: Location.Position = .missing,
rightArrow2Position: Location.Position = .missing,
};
pub const ExprCall = extern struct {
classIndex: Node.Kind = .expr_call,
openParens: Location.Position,
closeParens: Location.Position,
commaPositions: Ast.Array(Location.Position),
explicitTypes: ?*TypeInstantiation = null,
};
pub const ExprIndexExpr = extern struct {
classIndex: Node.Kind = .expr_index_expr,
openBracketPosition: Location.Position,
closeBracketPosition: Location.Position,
};
pub const ExprFunction = extern struct {
classIndex: Node.Kind = .expr_function,
attrLists: Ast.Array(*AttrList) = .{},
functionKeywordPosition: Location.Position = .missing,
openGenericsPosition: Location.Position = .missing,
genericsCommaPositions: Ast.Array(Location.Position),
closeGenericsPosition: Location.Position = .missing,
argsAnnotationColonPositions: Ast.Array(Location.Position),
argsCommaPositions: Ast.Array(Location.Position),
varargAnnotationColonPosition: Location.Position = .missing,
returnSpecifierPosition: Location.Position = .missing,
};
pub const ExprTable = extern struct {
classIndex: Node.Kind = .expr_table,
items: Ast.Array(Item),
pub const Separator = enum(u32) {
comma,
semicolon,
missing,
};
pub const Item = extern struct {
/// '[', only if Kind == General
indexerOpenPosition: Location.Position,
/// ']', only if Kind == General
indexerClosePosition: Location.Position,
/// only if Kind != List
equalsPosition: Location.Position,
/// may be missing for last Item
separator: Separator,
/// may be missing for last Item
separatorPosition: Location.Position,
};
};
pub const ExprOp = extern struct {
classIndex: Node.Kind = .expr_op,
opPosition: Location.Position,
};
pub const ExprTypeAssertion = extern struct {
classIndex: Node.Kind = .expr_type_assertion,
opPosition: Location.Position,
};
pub const ExprIfElse = extern struct {
classIndex: Node.Kind = .expr_if_else,
thenPosition: Location.Position,
elsePosition: Location.Position,
isElseIf: bool,
};
pub const ExprInterpString = extern struct {
classIndex: Node.Kind = .expr_interp_string,
sourceStrings: Ast.Array(Ast.Array(u8)),
stringPositions: Ast.Array(Location.Position),
};
pub const ExprExplicitTypeInstantiation = extern struct {
classIndex: Node.Kind = .expr_explicit_type_instantiation,
instantiation: TypeInstantiation,
};
pub const StatDo = extern struct {
classIndex: Node.Kind = .stat_do,
statsStartPosition: Location.Position,
endPosition: Location.Position,
};
pub const StatRepeat = extern struct {
classIndex: Node.Kind = .stat_repeat,
untilPosition: Location.Position,
};
pub const StatReturn = extern struct {
classIndex: Node.Kind = .stat_return,
commaPositions: Ast.Array(Location.Position),
};
pub const StatLocal = extern struct {
classIndex: Node.Kind = .stat_local,
varsAnnotationColonPositions: Ast.Array(Location.Position),
varsCommaPositions: Ast.Array(Location.Position),
valuesCommaPositions: Ast.Array(Location.Position),
};
pub const StatFor = extern struct {
classIndex: Node.Kind = .stat_for,
annotationColonPosition: Location.Position,
equalsPosition: Location.Position,
endCommaPosition: Location.Position,
stepCommaPosition: Location.Position,
};
pub const StatForIn = extern struct {
classIndex: Node.Kind = .stat_for_in,
varsAnnotationColonPositions: Ast.Array(Location.Position),
varsCommaPositions: Ast.Array(Location.Position),
valuesCommaPositions: Ast.Array(Location.Position),
};
pub const StatAssign = extern struct {
classIndex: Node.Kind = .stat_assign,
varsCommaPositions: Ast.Array(Location.Position),
equalsPosition: Location.Position,
valuesCommaPositions: Ast.Array(Location.Position),
};
pub const StatCompoundAssign = extern struct {
classIndex: Node.Kind = .stat_compound_assign,
opPosition: Location.Position,
};
pub const StatFunction = extern struct {
classIndex: Node.Kind = .stat_function,
attrLists: Ast.Array(*AttrList),
functionKeywordPosition: Location.Position,
};
pub const StatLocalFunction = extern struct {
classIndex: Node.Kind = .stat_local_function,
attrLists: Ast.Array(*AttrList),
localKeywordPosition: Location.Position,
functionKeywordPosition: Location.Position,
};
pub const GenericType = extern struct {
classIndex: Node.Kind = .generic_type,
defaultEqualsPosition: Location.Position,
};
pub const GenericTypePack = extern struct {
classIndex: Node.Kind = .generic_type_pack,
ellipsisPosition: Location.Position,
defaultEqualsPosition: Location.Position,
};
pub const StatTypeAlias = extern struct {
classIndex: Node.Kind = .stat_type_alias,
typeKeywordPosition: Location.Position,
genericsOpenPosition: Location.Position,
genericsCommaPositions: Ast.Array(Location.Position),
genericsClosePosition: Location.Position,
equalsPosition: Location.Position,
};
pub const StatTypeFunction = extern struct {
classIndex: Node.Kind = .stat_type_function,
typeKeywordPosition: Location.Position,
functionKeywordPosition: Location.Position,
};
pub const TypeReference = extern struct {
classIndex: Node.Kind = .type_reference,
prefixPointPosition: Location.Position,
openParametersPosition: Location.Position,
parametersCommaPositions: Ast.Array(Location.Position),
closeParametersPosition: Location.Position,
};
pub const TypeTable = extern struct {
classIndex: Node.Kind = .type_table,
items: Ast.Array(Item),
isArray: bool,
pub const Item = extern struct {
kind: Kind,
indexerOpenPosition: Location.Position, // '[', only if Kind != Property
indexerClosePosition: Location.Position, // ']' only if Kind != Property
colonPosition: Location.Position,
separator: ExprTable.Separator, // may be missing for last Item
separatorPosition: Location.Position,
stringInfo: ?*ExprConstantString, // only if Kind == StringProperty
stringPosition: Location.Position, // only if Kind == StringProperty
pub const Kind = enum(u32) {
indexer,
property,
string_property,
};
};
};
pub const TypeFunction = extern struct {
classIndex: Node.Kind = .type_function,
openGenericsPosition: Location.Position,
genericsCommaPositions: Ast.Array(Location.Position),
closeGenericsPosition: Location.Position,
openArgsPosition: Location.Position,
argumentNameColonPositions: Ast.Array(Location.Position),
argumentsCommaPositions: Ast.Array(Location.Position),
closeArgsPosition: Location.Position,
returnArrowPosition: Location.Position,
};
pub const TypeTypeof = extern struct {
classIndex: Node.Kind = .type_typeof,
openPosition: Location.Position,
closePosition: Location.Position,
};
pub const TypeUnion = extern struct {
classIndex: Node.Kind = .type_union,
leadingPosition: Location.Position,
separatorPositions: Ast.Array(Location.Position),
};
pub const TypeIntersection = extern struct {
classIndex: Node.Kind = .type_intersection,
leadingPosition: Location.Position,
separatorPositions: Ast.Array(Location.Position),
};
pub const TypeSingletonString = extern struct {
classIndex: Node.Kind = .type_singleton_string,
sourceString: Ast.Array(u8),
quoteStyle: ExprConstantString.QuoteStyle,
blockDepth: u32,
};
pub const TypeGroup = extern struct {
classIndex: Node.Kind = .type_group,
closePosition: Location.Position,
};
pub const TypePackExplicit = extern struct {
classIndex: Node.Kind = .type_pack_explicit,
openParenthesesPosition: Location.Position,
closeParenthesesPosition: Location.Position,
commaPositions: Ast.Array(Location.Position),
};
pub const TypePackGeneric = extern struct {
classIndex: Node.Kind = .type_pack_generic,
ellipsisPosition: Location.Position,
};
test Node {
const Lexer = @import("Lexer.zig");
const Parser = @import("Parser.zig");
const Allocator = @import("Allocator.zig");
{
const allocator = Allocator.init();
defer allocator.deinit();
const table = Lexer.AstNameTable.init(allocator);
defer table.deinit();
const source =
\\local x: number = 1;
\\local x = 2
\\local x = 3
\\
;
var parse_result = Parser.parse(source, table, allocator, .{
.storeCstData = true,
});
defer parse_result.deinit();
const root = parse_result.root;
try std.testing.expectEqual(Ast.Node.Kind.stat_block, root.classIndex);
const stats = root.body.slice();
try std.testing.expectEqual(3, stats.len);
try std.testing.expectEqual(7, parse_result.cstNodeMap.count);
try std.testing.expect(parse_result.cstNodeMap.find(@ptrCast(@alignCast(root))) == null);
for (stats, 1..) |node, order| {
switch (node.classIndex) {
.stat_local => {
const local: *Ast.StatLocal = node.as(.stat_local).?;
const cst_node = (parse_result.cstNodeMap.find(@ptrCast(@alignCast(node))) orelse @panic("Not found")).second;
const cst_local: *StatLocal = cst_node.as(.stat_local).?;
try std.testing.expect(cst_local.varsCommaPositions.size == 0);
try std.testing.expect(cst_local.valuesCommaPositions.size == 0);
try std.testing.expect(cst_local.varsAnnotationColonPositions.size == 1);
try std.testing.expectEqualStrings("x", std.mem.span(local.vars.slice()[0].name.value));
try std.testing.expect(@as(f64, @floatFromInt(order)) == local.values.slice()[0].as(.expr_constant_number).?.value);
},
else => {},
}
}
}
}
test "CstValuesCheck" {
if (@import("builtin").cpu.arch.isWasm() or @import("builtin").os.tag == .windows)
return error.SkipZigTest;
const CstValues = struct {
pub extern "c" const CstAttrIndex: u8;
pub extern "c" const CstParametrizedAttrIndex: u8;
pub extern "c" const CstExprGroupIndex: u8;
pub extern "c" const CstExprConstantNumberIndex: u8;
pub extern "c" const CstExprConstantIntegerIndex: u8;
pub extern "c" const CstExprConstantStringIndex: u8;
pub extern "c" const CstExprCallIndex: u8;
pub extern "c" const CstExprIndexExprIndex: u8;
pub extern "c" const CstExprFunctionIndex: u8;
pub extern "c" const CstExprTableIndex: u8;
pub extern "c" const CstExprOpIndex: u8;
pub extern "c" const CstExprTypeAssertionIndex: u8;
pub extern "c" const CstExprIfElseIndex: u8;
pub extern "c" const CstExprInterpStringIndex: u8;
pub extern "c" const CstExprExplicitTypeInstantiationIndex: u8;
pub extern "c" const CstStatDoIndex: u8;
pub extern "c" const CstStatRepeatIndex: u8;
pub extern "c" const CstStatReturnIndex: u8;
pub extern "c" const CstStatLocalIndex: u8;
pub extern "c" const CstStatForIndex: u8;
pub extern "c" const CstStatForInIndex: u8;
pub extern "c" const CstStatAssignIndex: u8;
pub extern "c" const CstStatCompoundAssignIndex: u8;
pub extern "c" const CstStatFunctionIndex: u8;
pub extern "c" const CstStatLocalFunctionIndex: u8;
pub extern "c" const CstGenericTypeIndex: u8;
pub extern "c" const CstGenericTypePackIndex: u8;
pub extern "c" const CstStatTypeAliasIndex: u8;
pub extern "c" const CstStatTypeFunctionIndex: u8;
pub extern "c" const CstTypeReferenceIndex: u8;
pub extern "c" const CstTypeTableIndex: u8;
pub extern "c" const CstTypeTableItemKindIndexer: u8;
pub extern "c" const CstTypeTableItemKindProperty: u8;
pub extern "c" const CstTypeTableItemKindStringProperty: u8;
pub extern "c" const CstTypeFunctionIndex: u8;
pub extern "c" const CstTypeTypeofIndex: u8;
pub extern "c" const CstTypeUnionIndex: u8;
pub extern "c" const CstTypeIntersectionIndex: u8;
pub extern "c" const CstTypeSingletonStringIndex: u8;
pub extern "c" const CstTypeGroupIndex: u8;
pub extern "c" const CstTypePackExplicitIndex: u8;
pub extern "c" const CstTypePackGenericIndex: u8;
pub extern "c" const CstExprGroupSize: usize;
pub extern "c" const CstExprConstantNumberSize: usize;
pub extern "c" const CstExprConstantIntegerSize: usize;
pub extern "c" const CstExprConstantStringSize: usize;
pub extern "c" const CstExprCallSize: usize;
pub extern "c" const CstExprIndexExprSize: usize;
pub extern "c" const CstExprFunctionSize: usize;
pub extern "c" const CstExprTableSize: usize;
pub extern "c" const CstExprOpSize: usize;
pub extern "c" const CstExprTypeAssertionSize: usize;
pub extern "c" const CstExprIfElseSize: usize;
pub extern "c" const CstExprInterpStringSize: usize;
pub extern "c" const CstExprExplicitTypeInstantiationSize: usize;
pub extern "c" const CstStatDoSize: usize;
pub extern "c" const CstStatRepeatSize: usize;
pub extern "c" const CstStatReturnSize: usize;
pub extern "c" const CstStatLocalSize: usize;
pub extern "c" const CstStatForSize: usize;
pub extern "c" const CstStatForInSize: usize;
pub extern "c" const CstStatAssignSize: usize;
pub extern "c" const CstStatCompoundAssignSize: usize;
pub extern "c" const CstStatFunctionSize: usize;
pub extern "c" const CstStatLocalFunctionSize: usize;
pub extern "c" const CstGenericTypeSize: usize;
pub extern "c" const CstGenericTypePackSize: usize;
pub extern "c" const CstStatTypeAliasSize: usize;
pub extern "c" const CstStatTypeFunctionSize: usize;
pub extern "c" const CstTypeReferenceSize: usize;
pub extern "c" const CstTypeTableSize: usize;
pub extern "c" const CstTypeFunctionSize: usize;
pub extern "c" const CstTypeTypeofSize: usize;
pub extern "c" const CstTypeUnionSize: usize;
pub extern "c" const CstTypeIntersectionSize: usize;
pub extern "c" const CstTypeSingletonStringSize: usize;
pub extern "c" const CstTypeGroupSize: usize;
pub extern "c" const CstTypePackExplicitSize: usize;
pub extern "c" const CstTypePackGenericSize: usize;
};
try std.testing.expect(CstValues.CstTypeTableItemKindIndexer == @intFromEnum(TypeTable.Item.Kind.indexer));
try std.testing.expect(CstValues.CstTypeTableItemKindProperty == @intFromEnum(TypeTable.Item.Kind.property));
try std.testing.expect(CstValues.CstTypeTableItemKindStringProperty == @intFromEnum(TypeTable.Item.Kind.string_property));
@setEvalBranchQuota(2000);
inline for (@typeInfo(CstValues).@"struct".decls) |decl| {
if (comptime std.mem.endsWith(u8, decl.name, "Index")) {
const name = decl.name[3 .. decl.name.len - 5];
const cst_node_type = @field(Cst, name);
const info = @typeInfo(cst_node_type).@"struct";
comptime var field: ?std.builtin.Type.StructField = null;
inline for (info.fields) |f| {
if (comptime std.mem.eql(u8, f.name, "classIndex")) {
field = f;
break;
}
}
if (field == null)
@compileError("classIndex field not found");
const default_value_ptr = field.?.default_value_ptr orelse @compileError("classIndex field does not have a default value");
const enum_value = @as(*const Cst.Node.Kind, @ptrCast(@alignCast(default_value_ptr))).*;
std.testing.expectEqual(@field(CstValues, decl.name), @intFromEnum(enum_value)) catch |err| {
std.debug.print("index error for {s}\n", .{name});
return err;
};
} else if (comptime std.mem.endsWith(u8, decl.name, "Size")) {
const name = decl.name[3 .. decl.name.len - 4];
const cst_node_type = @field(Cst, name);
std.testing.expectEqual(@field(CstValues, decl.name), @sizeOf(cst_node_type)) catch |err| {
std.debug.print("size error for {s}\n", .{name});
return err;
};
}
}
}
// sources:
// https://github.com/luau-lang/luau/blob/40d4815888f63362a6cb79b3e74c4aafa0b2cbf4/Ast/include/Luau/Cst.h
// https://github.com/luau-lang/luau/blob/40d4815888f63362a6cb79b3e74c4aafa0b2cbf4/Ast/src/Cst.cpp
+15
View File
@@ -0,0 +1,15 @@
#include <bridge.h>
#include "Luau/Lexer.h"
#define ZIG_LUAU_AST(name) ZIG_FN(Luau_Ast_##name)
ZIG_EXPORT Luau::AstNameTable* ZIG_LUAU_AST(Lexer_AstNameTable_init)(Luau::Allocator* allocator)
{
return new Luau::AstNameTable(*allocator);
}
ZIG_EXPORT void ZIG_LUAU_AST(Lexer_AstNameTable_dtor)(Luau::AstNameTable* names)
{
delete names;
}
+130
View File
@@ -0,0 +1,130 @@
const std = @import("std");
const Ast = @import("Ast.zig");
const Allocator = @import("Allocator.zig");
const DenseHash = @import("../Common/DenseHash.zig");
extern "c" fn zig_Luau_Ast_Lexer_AstNameTable_init(*Allocator) *AstNameTable;
extern "c" fn zig_Luau_Ast_Lexer_AstNameTable_dtor(*AstNameTable) void;
pub const Lexeme = struct {
pub const Type = enum(c_int) {
Eof = 0,
// 1..255 means actual character values
Char_END = 256,
Equal,
LessEqual,
GreaterEqual,
NotEqual,
Dot2,
Dot3,
SkinnyArrow,
DoubleColon,
FloorDiv,
InterpStringBegin,
InterpStringMid,
InterpStringEnd,
// An interpolated string with no expressions (like `x`)
InterpStringSimple,
AddAssign,
SubAssign,
MulAssign,
DivAssign,
FloorDivAssign,
ModAssign,
PowAssign,
ConcatAssign,
RawString,
QuotedString,
Number,
Name,
Comment,
BlockComment,
Attribute,
AttributeOpen,
BrokenString,
BrokenComment,
BrokenUnicode,
BrokenInterpDoubleBrace,
Error,
// Reserved_BEGIN,
ReservedAnd,
ReservedBreak,
ReservedDo,
ReservedElse,
ReservedElseif,
ReservedEnd,
ReservedFalse,
ReservedFor,
ReservedFunction,
ReservedIf,
ReservedIn,
ReservedLocal,
ReservedNil,
ReservedNot,
ReservedOr,
ReservedRepeat,
ReservedReturn,
ReservedThen,
ReservedTrue,
ReservedUntil,
ReservedWhile,
Reserved_END,
pub const Reserved_BEGIN = Type.ReservedAnd;
};
};
pub const AstNameTable = extern struct {
data: DenseHash.DenseHashSet(Entry, EntryHash),
allocator: *Allocator,
const Entry = extern struct {
value: Ast.Name,
length: u32,
type: Lexeme.Type,
};
const EntryHash = extern struct {
pub fn hash(e: *const Entry) usize {
var h: u32 = 2166136261;
for (0..e.length) |i| {
h ^= @as(u8, e.value[i]);
h *= 16777619;
}
return h;
}
pub fn eq(_: *const Entry, _: *const Entry) bool {
@compileError("not implemented");
}
};
pub fn init(allocator: *Allocator) *AstNameTable {
return zig_Luau_Ast_Lexer_AstNameTable_init(allocator);
}
pub fn deinit(self: *AstNameTable) void {
zig_Luau_Ast_Lexer_AstNameTable_dtor(self);
}
};
test AstNameTable {
const allocator = Allocator.init();
defer allocator.deinit();
const astNameTable = AstNameTable.init(allocator);
defer astNameTable.deinit();
}
// sources:
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Ast/include/Luau/Lexer.h
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Ast/src/Lexer.cpp
+60
View File
@@ -0,0 +1,60 @@
const std = @import("std");
pub const Location = extern struct {
begin: Position = .zeros,
end: Position = .zeros,
pub fn eq(self: Location, other: Location) bool {
return self.begin.eq(other.begin) and self.end.eq(other.end);
}
pub fn encloses(self: Location, other: Location) bool {
return self.begin.lessThanOrEq(other.begin) and self.end.greaterThanOrEq(other.end);
}
pub fn overlaps(self: Location, other: Location) bool {
return (self.begin.lessThanOrEq(other.begin) and self.end.greaterThanOrEq(other.begin)) or (self.begin.lessThanOrEq(other.end) and self.end.greaterThanOrEq(other.end)) or (other.begin.greaterThanOrEq(self.begin) and other.end.lessThanOrEq(self.end));
}
pub fn contains(self: Location, position: Position) bool {
return self.begin.lessThanOrEq(position) and position.lessThan(self.end);
}
pub fn containsClosed(self: Location, position: Position) bool {
return self.begin.lessThanOrEq(position) and position.lessThanOrEq(self.end);
}
pub const Position = extern struct {
line: c_uint,
column: c_uint,
pub const missing: Position = .{ .line = std.math.maxInt(u32), .column = std.math.maxInt(u32) };
pub const zeros: Position = .{ .line = 0, .column = 0 };
pub fn eq(self: Position, other: Position) bool {
return self.line == other.line and self.column == other.column;
}
pub fn lessThan(self: Position, other: Position) bool {
if (self.line == other.line)
return self.column < other.column;
return self.line < other.line;
}
pub inline fn lessThanOrEq(self: Position, other: Position) bool {
return self.eq(other) or self.lessThan(other);
}
pub inline fn greaterThan(self: Position, other: Position) bool {
return !self.lessThanOrEq(other);
}
pub inline fn greaterThanOrEq(self: Position, other: Position) bool {
return !self.lessThan(other);
}
pub fn hasValue(self: Position) bool {
return self.line != std.math.maxInt(u32) and self.column != std.math.maxInt(u32);
}
};
};
// sources:
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Ast/include/Luau/Location.h
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Ast/src/Location.cpp
+78
View File
@@ -0,0 +1,78 @@
#include <bridge.h>
#include "Luau/Ast.h"
#include "Luau/Parser.h"
#define ZIG_LUAU_AST(name) ZIG_FN(Luau_Ast_##name)
ZIG_EXPORT struct luau_ParseOptions
{
unsigned char data[sizeof(Luau::ParseOptions)];
};
ZIG_EXPORT Luau::ParseResult* ZIG_LUAU_AST(Parser_parse)(
const char* source, size_t sourceLen,
Luau::AstNameTable* names,
Luau::Allocator* allocator,
const luau_ParseOptions* options
)
{
Luau::ParseOptions parseOptions;
if (options)
{
static_assert(sizeof(luau_ParseOptions) == sizeof(Luau::ParseOptions), "C and C++ interface must match");
memcpy(static_cast<void*>(&parseOptions), options, sizeof(parseOptions));
}
Luau::ParseResult result = Luau::Parser::parse(source, sourceLen, *names, *allocator, parseOptions);
return new Luau::ParseResult(std::move(result));
}
ZIG_EXPORT void ZIG_LUAU_AST(ParseResult_dtor)(Luau::ParseResult* result)
{
delete result;
}
ZIG_EXPORT Luau::ParseNodeResult<Luau::AstExpr>* ZIG_LUAU_AST(Parser_parseExpr)(
const char* source, size_t sourceLen,
Luau::AstNameTable* names,
Luau::Allocator* allocator,
const luau_ParseOptions* options
)
{
Luau::ParseOptions parseOptions;
if (options)
{
static_assert(sizeof(luau_ParseOptions) == sizeof(Luau::ParseOptions), "C and C++ interface must match");
memcpy(static_cast<void*>(&parseOptions), options, sizeof(parseOptions));
}
Luau::ParseNodeResult<Luau::AstExpr> result = Luau::Parser::parseExpr(source, sourceLen, *names, *allocator, parseOptions);
return new Luau::ParseNodeResult<Luau::AstExpr>(std::move(result));
}
ZIG_EXPORT void ZIG_LUAU_AST(ParseNodeResult_AstExpr_dtor)(Luau::ParseNodeResult<Luau::AstExpr>* result)
{
delete result;
}
ZIG_EXPORT Luau::ParseNodeResult<Luau::AstType>* ZIG_LUAU_AST(Parser_parseType)(
const char* source, size_t sourceLen,
Luau::AstNameTable* names,
Luau::Allocator* allocator,
const luau_ParseOptions* options
)
{
Luau::ParseOptions parseOptions;
if (options)
{
static_assert(sizeof(luau_ParseOptions) == sizeof(Luau::ParseOptions), "C and C++ interface must match");
memcpy(static_cast<void*>(&parseOptions), options, sizeof(parseOptions));
}
Luau::ParseNodeResult<Luau::AstType> result = Luau::Parser::parseType(source, sourceLen, *names, *allocator, parseOptions);
return new Luau::ParseNodeResult<Luau::AstType>(std::move(result));
}
ZIG_EXPORT void ZIG_LUAU_AST(ParseNodeResult_AstType_dtor)(Luau::ParseNodeResult<Luau::AstType>* result)
{
delete result;
}
+156
View File
@@ -0,0 +1,156 @@
const std = @import("std");
const cpp_std = @import("../cpp_std.zig");
const Ast = @import("Ast.zig");
const Cst = @import("Cst.zig");
const Lexer = @import("Lexer.zig");
const Location = @import("Location.zig").Location;
const Allocator = @import("Allocator.zig");
const DenseHash = @import("../Common/DenseHash.zig");
pub const ParseError = cpp_std.Exception(extern struct {
location: Location,
message: cpp_std.String,
});
pub const ParseErrors = cpp_std.Exception(extern struct {
errors: cpp_std.Vector(ParseError),
message: cpp_std.String,
});
pub const HotComment = extern struct {
header: bool,
location: Location,
content: cpp_std.String,
};
pub const Comment = extern struct {
type: Lexer.Lexeme.Type, // Comment, BlockComment, or BrokenComment
location: Location,
};
pub const ParseOptions = extern struct {
allowDeclarationSyntax: bool = false,
captureComments: bool = false,
parseFragment: cpp_std.Optional(FragmentParseResumeSettings) = .nullopt,
storeCstData: bool = false,
noErrorLimit: bool = false,
pub const FragmentParseResumeSettings = extern struct {
localMap: DenseHash.DenseHashMap(Ast.Name, *Ast.Local, struct {}) = .init(.{ .value = "" }, 0),
localStack: cpp_std.Vector(*Ast.Local) = undefined,
resumePosition: Location.Position,
};
};
extern "c" fn zig_Luau_Ast_Parser_parse([*]const u8, usize, *Lexer.AstNameTable, *Allocator, *const ParseOptions) *ParseResult;
extern "c" fn zig_Luau_Ast_Parser_parseExpr([*]const u8, usize, *Lexer.AstNameTable, *Allocator, *const ParseOptions) *ParseNodeResult(Ast.Expr);
extern "c" fn zig_Luau_Ast_Parser_parseType([*]const u8, usize, *Lexer.AstNameTable, *Allocator, *const ParseOptions) *ParseNodeResult(Ast.Type);
extern "c" fn zig_Luau_Ast_ParseResult_dtor(*ParseResult) void;
extern "c" fn zig_Luau_Ast_ParseNodeResult_AstExpr_dtor(*ParseNodeResult(Ast.Expr)) void;
extern "c" fn zig_Luau_Ast_ParseNodeResult_AstType_dtor(*ParseNodeResult(Ast.Type)) void;
pub fn parse(source: []const u8, nameTable: *Lexer.AstNameTable, allocator: *Allocator, options: ParseOptions) *ParseResult {
return zig_Luau_Ast_Parser_parse(source.ptr, source.len, nameTable, allocator, &options);
}
pub fn parseExpr(source: []const u8, nameTable: *Lexer.AstNameTable, allocator: *Allocator, options: ParseOptions) *ParseNodeResult(Ast.Expr) {
return zig_Luau_Ast_Parser_parseExpr(source.ptr, source.len, nameTable, allocator, &options);
}
pub fn parseType(source: []const u8, nameTable: *Lexer.AstNameTable, allocator: *Allocator, options: ParseOptions) *ParseNodeResult(Ast.Type) {
return zig_Luau_Ast_Parser_parseType(source.ptr, source.len, nameTable, allocator, &options);
}
pub const CstNodeMap = DenseHash.DenseHashMap(*Ast.Node, *Cst.Node, struct {});
pub const ParseResult = extern struct {
root: *Ast.StatBlock,
lines: usize = 0,
hotcomments: cpp_std.Vector(HotComment),
errors: cpp_std.Vector(ParseError),
commentLocations: cpp_std.Vector(Comment),
cstNodeMap: CstNodeMap,
pub inline fn deinit(self: *ParseResult) void {
zig_Luau_Ast_ParseResult_dtor(self);
}
};
pub fn ParseNodeResult(comptime T: type) type {
return extern struct {
expr: *T,
lines: usize = 0,
hotcomments: cpp_std.Vector(HotComment),
errors: cpp_std.Vector(ParseError),
commentLocations: cpp_std.Vector(Comment),
cstNodeMap: CstNodeMap,
pub const Self = @This();
pub inline fn deinit(self: *Self) void {
comptime std.debug.assert(T == Ast.Type or T == Ast.Expr);
if (T == Ast.Type) {
zig_Luau_Ast_ParseNodeResult_AstType_dtor(self);
} else {
zig_Luau_Ast_ParseNodeResult_AstExpr_dtor(self);
}
}
};
}
test ParseResult {
{
const allocator = Allocator.init();
defer allocator.deinit();
const astNameTable = Lexer.AstNameTable.init(allocator);
defer astNameTable.deinit();
const source =
\\--!test
\\-- This is a test comment
\\local x =
\\
;
var parseResult = parse(source, astNameTable, allocator, .{});
defer parseResult.deinit();
{
var iter = parseResult.hotcomments.iterator();
var count: usize = 0;
while (iter.next()) |comment| : (count += 1) {
const string = comment.content.slice();
try std.testing.expectEqualStrings("test", string);
try std.testing.expectEqual(true, comment.header);
try std.testing.expectEqual(0, comment.location.begin.line);
try std.testing.expectEqual(0, comment.location.begin.column);
try std.testing.expectEqual(0, comment.location.end.line);
try std.testing.expectEqual(7, comment.location.end.column);
}
try std.testing.expectEqual(1, count);
}
{
try std.testing.expectEqual(1, parseResult.errors.size());
const first = parseResult.errors.at(0).value;
try std.testing.expectEqualStrings("Expected identifier when parsing expression, got <eof>", first.message.slice());
try std.testing.expectEqual(3, first.location.begin.line);
try std.testing.expectEqual(0, first.location.begin.column);
try std.testing.expectEqual(3, first.location.end.line);
try std.testing.expectEqual(0, first.location.end.column);
}
}
}
// sources:
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Ast/include/Luau/Parser.h
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Ast/src/Parser.cpp
+15
View File
@@ -0,0 +1,15 @@
const c = @import("c");
const lua = @import("../VM/lua.zig");
pub inline fn supported() bool {
return c.luau_codegen_supported() != 0;
}
pub inline fn create(L: *lua.State) void {
c.luau_codegen_create(@ptrCast(L));
}
pub inline fn compile(L: *lua.State, idx: i32) void {
c.luau_codegen_compile(@ptrCast(L), idx);
}
+731
View File
@@ -0,0 +1,731 @@
// This file contains the bytecode definition for Luau interpreter
// Creating the bytecode is outside the scope of this file and is handled by bytecode builder (BytecodeBuilder.h) and bytecode compiler (Compiler.h)
// Note that ALL enums declared in this file are order-sensitive since the values are baked into bytecode that needs to be processed by legacy clients.
// # Bytecode definitions
// Bytecode instructions are using "word code" - each instruction is one or many 32-bit words.
// The first word in the instruction is always the instruction header, and *must* contain the opcode (enum below) in the least significant byte.
//
// Instruction word can be encoded using one of the following encodings:
// ABC - least-significant byte for the opcode, followed by three bytes, A, B and C; each byte declares a register index, small index into some other table or an unsigned integral value
// AD - least-significant byte for the opcode, followed by A byte, followed by D half-word (16-bit integer). D is a signed integer that commonly specifies constant table index or jump offset
// E - least-significant byte for the opcode, followed by E (24-bit integer). E is a signed integer that commonly specifies a jump offset
//
// Instruction word is sometimes followed by one extra word, indicated as AUX - this is just a 32-bit word and is decoded according to the specification for each opcode.
// For each opcode the encoding is *static* - that is, based on the opcode you know a-priory how large the instruction is, with the exception of NEWCLOSURE
// # Bytecode indices
// Bytecode instructions commonly refer to integer values that define offsets or indices for various entities. For each type, there's a maximum encodable value.
// Note that in some cases, the compiler will set a lower limit than the maximum encodable value is to prevent fragile code into bumping against the limits whenever we change the compilation details.
// Additionally, in some specific instructions such as ANDK, the limit on the encoded value is smaller; this means that if a value is larger, a different instruction must be selected.
//
// Registers: 0-254. Registers refer to the values on the function's stack frame, including arguments.
// Upvalues: 0-199. Upvalues refer to the values stored in the closure object.
// Constants: 0-2^23-1. Constants are stored in a table allocated with each proto; to allow for future bytecode tweaks the encodable value is limited to 23 bits.
// Closures: 0-2^15-1. Closures are created from child protos via a child index; the limit is for the number of closures immediately referenced in each function.
// Jumps: -2^23..2^23. Jump offsets are specified in word increments, so jumping over an instruction may sometimes require an offset of 2 or more. Note that for jump instructions with AUX, the AUX word is included as part of the jump offset.
// # Bytecode versions
// Bytecode serialized format embeds a version number, that dictates both the serialized form as well as the allowed instructions. As long as the bytecode version falls into supported
// range (indicated by BYTECODE_MIN / BYTECODE_MAX) and was produced by Luau compiler, it should load and execute correctly.
//
// Note that Luau runtime doesn't provide indefinite bytecode compatibility: support for older versions gets removed over time. As such, bytecode isn't a durable storage format and it's expected
// that Luau users can recompile bytecode from source on Luau version upgrades if necessary.
// # Bytecode version history
//
// Note: due to limitations of the versioning scheme, some bytecode blobs that carry version 2 are using features from version 3. Starting from version 3, version should be sufficient to indicate bytecode compatibility.
//
// Version 1: Baseline version for the open-source release. Supported until 0.521.
// Version 2: Adds Proto::linedefined. Supported until 0.544.
// Version 3: Adds FORGPREP/JUMPXEQK* and enhances AUX encoding for FORGLOOP. Removes FORGLOOP_NEXT/INEXT and JUMPIFEQK/JUMPIFNOTEQK. Currently supported.
// Version 4: Adds Proto::flags, typeinfo, and floor division opcodes IDIV/IDIVK. Currently supported.
// Version 5: Adds SUBRK/DIVRK and vector constants. Currently supported.
// Version 6: Adds FASTCALL3. Currently supported.
// # Bytecode type information history
// Version 1: (from bytecode version 4) Type information for function signature. Currently supported.
// Version 2: (from bytecode version 4) Type information for arguments, upvalues, locals and some temporaries. Currently supported.
// Bytecode opcode, part of the instruction header
pub const Opcode = enum(u32) {
// NOP: noop
NOP,
// BREAK: debugger break
BREAK,
// LOADNIL: sets register to nil
// A: target register
LOADNIL,
// LOADB: sets register to boolean and jumps to a given short offset (used to compile comparison results into a boolean)
// A: target register
// B: value (0/1)
// C: jump offset
LOADB,
// LOADN: sets register to a number literal
// A: target register
// D: value (-32768..32767)
LOADN,
// LOADK: sets register to an entry from the constant table from the proto (number/vector/string)
// A: target register
// D: constant table index (0..32767)
LOADK,
// MOVE: move (copy) value from one register to another
// A: target register
// B: source register
MOVE,
// GETGLOBAL: load value from global table using constant string as a key
// A: target register
// C: predicted slot index (based on hash)
// AUX: constant table index
GETGLOBAL,
// SETGLOBAL: set value in global table using constant string as a key
// A: source register
// C: predicted slot index (based on hash)
// AUX: constant table index
SETGLOBAL,
// GETUPVAL: load upvalue from the upvalue table for the current function
// A: target register
// B: upvalue index
GETUPVAL,
// SETUPVAL: store value into the upvalue table for the current function
// A: target register
// B: upvalue index
SETUPVAL,
// CLOSEUPVALS: close (migrate to heap) all upvalues that were captured for registers >= target
// A: target register
CLOSEUPVALS,
// GETIMPORT: load imported global table global from the constant table
// A: target register
// D: constant table index (0..32767); we assume that imports are loaded into the constant table
// AUX: 3 10-bit indices of constant strings that, combined, constitute an import path; length of the path is set by the top 2 bits (1,2,3)
GETIMPORT,
// GETTABLE: load value from table into target register using key from register
// A: target register
// B: table register
// C: index register
GETTABLE,
// SETTABLE: store source register into table using key from register
// A: source register
// B: table register
// C: index register
SETTABLE,
// GETTABLEKS: load value from table into target register using constant string as a key
// A: target register
// B: table register
// C: predicted slot index (based on hash)
// AUX: constant table index
GETTABLEKS,
// SETTABLEKS: store source register into table using constant string as a key
// A: source register
// B: table register
// C: predicted slot index (based on hash)
// AUX: constant table index
SETTABLEKS,
// GETTABLEN: load value from table into target register using small integer index as a key
// A: target register
// B: table register
// C: index-1 (index is 1..256)
GETTABLEN,
// SETTABLEN: store source register into table using small integer index as a key
// A: source register
// B: table register
// C: index-1 (index is 1..256)
SETTABLEN,
// NEWCLOSURE: create closure from a child proto; followed by a CAPTURE instruction for each upvalue
// A: target register
// D: child proto index (0..32767)
NEWCLOSURE,
// NAMECALL: prepare to call specified method by name by loading function from source register using constant index into target register and copying source register into target register + 1
// A: target register
// B: source register
// C: predicted slot index (based on hash)
// AUX: constant table index
// Note that this instruction must be followed directly by CALL; it prepares the arguments
// This instruction is roughly equivalent to GETTABLEKS + MOVE pair, but we need a special instruction to support custom __namecall metamethod
NAMECALL,
// CALL: call specified function
// A: register where the function object lives, followed by arguments; results are placed starting from the same register
// B: argument count + 1, or 0 to preserve all arguments up to top (MULTRET)
// C: result count + 1, or 0 to preserve all values and adjust top (MULTRET)
CALL,
// RETURN: returns specified values from the function
// A: register where the returned values start
// B: number of returned values + 1, or 0 to return all values up to top (MULTRET)
RETURN,
// JUMP: jumps to target offset
// D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
JUMP,
// JUMPBACK: jumps to target offset; this is equivalent to JUMP but is used as a safepoint to be able to interrupt while/repeat loops
// D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
JUMPBACK,
// JUMPIF: jumps to target offset if register is not nil/false
// A: source register
// D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
JUMPIF,
// JUMPIFNOT: jumps to target offset if register is nil/false
// A: source register
// D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
JUMPIFNOT,
// JUMPIFEQ, JUMPIFLE, JUMPIFLT, JUMPIFNOTEQ, JUMPIFNOTLE, JUMPIFNOTLT: jumps to target offset if the comparison is true (or false, for NOT variants)
// A: source register 1
// D: jump offset (-32768..32767; 1 means "next instruction" aka "don't jump")
// AUX: source register 2
JUMPIFEQ,
JUMPIFLE,
JUMPIFLT,
JUMPIFNOTEQ,
JUMPIFNOTLE,
JUMPIFNOTLT,
// ADD, SUB, MUL, DIV, MOD, POW: compute arithmetic operation between two source registers and put the result into target register
// A: target register
// B: source register 1
// C: source register 2
ADD,
SUB,
MUL,
DIV,
MOD,
POW,
// ADDK, SUBK, MULK, DIVK, MODK, POWK: compute arithmetic operation between the source register and a constant and put the result into target register
// A: target register
// B: source register
// C: constant table index (0..255); must refer to a number
ADDK,
SUBK,
MULK,
DIVK,
MODK,
POWK,
// AND, OR: perform `and` or `or` operation (selecting first or second register based on whether the first one is truthy) and put the result into target register
// A: target register
// B: source register 1
// C: source register 2
AND,
OR,
// ANDK, ORK: perform `and` or `or` operation (selecting source register or constant based on whether the source register is truthy) and put the result into target register
// A: target register
// B: source register
// C: constant table index (0..255)
ANDK,
ORK,
// CONCAT: concatenate all strings between B and C (inclusive) and put the result into A
// A: target register
// B: source register start
// C: source register end
CONCAT,
// NOT, MINUS, LENGTH: compute unary operation for source register and put the result into target register
// A: target register
// B: source register
NOT,
MINUS,
LENGTH,
// NEWTABLE: create table in target register
// A: target register
// B: table size, stored as 0 for v=0 and ceil(log2(v))+1 for v!=0
// AUX: array size
NEWTABLE,
// DUPTABLE: duplicate table using the constant table template to target register
// A: target register
// D: constant table index (0..32767)
DUPTABLE,
// SETLIST: set a list of values to table in target register
// A: target register
// B: source register start
// C: value count + 1, or 0 to use all values up to top (MULTRET)
// AUX: table index to start from
SETLIST,
// FORNPREP: prepare a numeric for loop, jump over the loop if first iteration doesn't need to run
// A: target register; numeric for loops assume a register layout [limit, step, index, variable]
// D: jump offset (-32768..32767)
// limit/step are immutable, index isn't visible to user code since it's copied into variable
FORNPREP,
// FORNLOOP: adjust loop variables for one iteration, jump back to the loop header if loop needs to continue
// A: target register; see FORNPREP for register layout
// D: jump offset (-32768..32767)
FORNLOOP,
// FORGLOOP: adjust loop variables for one iteration of a generic for loop, jump back to the loop header if loop needs to continue
// A: target register; generic for loops assume a register layout [generator, state, index, variables...]
// D: jump offset (-32768..32767)
// AUX: variable count (1..255) in the low 8 bits, high bit indicates whether to use ipairs-style traversal in the fast path
// loop variables are adjusted by calling generator(state, index) and expecting it to return a tuple that's copied to the user variables
// the first variable is then copied into index; generator/state are immutable, index isn't visible to user code
FORGLOOP,
// FORGPREP_INEXT: prepare FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_inext, and jump to FORGLOOP
// A: target register (see FORGLOOP for register layout)
FORGPREP_INEXT,
// FASTCALL3: perform a fast call of a built-in function using 3 register arguments
// A: builtin function id (see LuauBuiltinFunction)
// B: source argument register
// C: jump offset to get to following CALL
// AUX: source register 2 in least-significant byte
// AUX: source register 3 in second least-significant byte
FASTCALL3,
// FORGPREP_NEXT: prepare FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_next, and jump to FORGLOOP
// A: target register (see FORGLOOP for register layout)
FORGPREP_NEXT,
// NATIVECALL: start executing new function in native code
// this is a pseudo-instruction that is never emitted by bytecode compiler, but can be constructed at runtime to accelerate native code dispatch
NATIVECALL,
// GETVARARGS: copy variables into the target register from vararg storage for current function
// A: target register
// B: variable count + 1, or 0 to copy all variables and adjust top (MULTRET)
GETVARARGS,
// DUPCLOSURE: create closure from a pre-created function object (reusing it unless environments diverge)
// A: target register
// D: constant table index (0..32767)
DUPCLOSURE,
// PREPVARARGS: prepare stack for variadic functions so that GETVARARGS works correctly
// A: number of fixed arguments
PREPVARARGS,
// LOADKX: sets register to an entry from the constant table from the proto (number/string)
// A: target register
// AUX: constant table index
LOADKX,
// JUMPX: jumps to the target offset; like JUMPBACK, supports interruption
// E: jump offset (-2^23..2^23; 0 means "next instruction" aka "don't jump")
JUMPX,
// FASTCALL: perform a fast call of a built-in function
// A: builtin function id (see LuauBuiltinFunction)
// C: jump offset to get to following CALL
// FASTCALL is followed by one of (GETIMPORT, MOVE, GETUPVAL) instructions and by CALL instruction
// This is necessary so that if FASTCALL can't perform the call inline, it can continue normal execution
// If FASTCALL *can* perform the call, it jumps over the instructions *and* over the next CALL
// Note that FASTCALL will read the actual call arguments, such as argument/result registers and counts, from the CALL instruction
FASTCALL,
// COVERAGE: update coverage information stored in the instruction
// E: hit count for the instruction (0..2^23-1)
// The hit count is incremented by VM every time the instruction is executed, and saturates at 2^23-1
COVERAGE,
// CAPTURE: capture a local or an upvalue as an upvalue into a newly created closure; only valid after NEWCLOSURE
// A: capture type, see LuauCaptureType
// B: source register (for VAL/REF) or upvalue index (for UPVAL/UPREF)
CAPTURE,
// SUBRK, DIVRK: compute arithmetic operation between the constant and a source register and put the result into target register
// A: target register
// B: constant table index (0..255); must refer to a number
// C: source register
SUBRK,
DIVRK,
// FASTCALL1: perform a fast call of a built-in function using 1 register argument
// A: builtin function id (see LuauBuiltinFunction)
// B: source argument register
// C: jump offset to get to following CALL
FASTCALL1,
// FASTCALL2: perform a fast call of a built-in function using 2 register arguments
// A: builtin function id (see LuauBuiltinFunction)
// B: source argument register
// C: jump offset to get to following CALL
// AUX: source register 2 in least-significant byte
FASTCALL2,
// FASTCALL2K: perform a fast call of a built-in function using 1 register argument and 1 constant argument
// A: builtin function id (see LuauBuiltinFunction)
// B: source argument register
// C: jump offset to get to following CALL
// AUX: constant index
FASTCALL2K,
// FORGPREP: prepare loop variables for a generic for loop, jump to the loop backedge unconditionally
// A: target register; generic for loops assume a register layout [generator, state, index, variables...]
// D: jump offset (-32768..32767)
FORGPREP,
// JUMPXEQKNIL, JUMPXEQKB: jumps to target offset if the comparison with constant is true (or false, see AUX)
// A: source register 1
// D: jump offset (-32768..32767; 1 means "next instruction" aka "don't jump")
// AUX: constant value (for boolean) in low bit, NOT flag (that flips comparison result) in high bit
JUMPXEQKNIL,
JUMPXEQKB,
// JUMPXEQKN, JUMPXEQKS: jumps to target offset if the comparison with constant is true (or false, see AUX)
// A: source register 1
// D: jump offset (-32768..32767; 1 means "next instruction" aka "don't jump")
// AUX: constant table index in low 24 bits, NOT flag (that flips comparison result) in high bit
JUMPXEQKN,
JUMPXEQKS,
// IDIV: compute floor division between two source registers and put the result into target register
// A: target register
// B: source register 1
// C: source register 2
IDIV,
// IDIVK compute floor division between the source register and a constant and put the result into target register
// A: target register
// B: source register
// C: constant table index (0..255)
IDIVK,
// Atom-based userdata field access acceleration
// These are equivalent to their GETTABLEKS/SETTABLEKS/NAMECALL counterparts, except tailored towards userdata field accesses
// If the user has registered metamethods for a userdata tag, callbacks will be called by these instructions
GETUDATAKS,
SETUDATAKS,
NAMECALLUDATA,
// NEWCLASSMEMBER: register this method on a class object.
// A: target register of class
// B: reserved
// C: initial value of this member. currently must be a function.
// AUX: The name of this member as a constant string
NEWCLASSMEMBER,
// CALLFB: call specified function with collecting runtime stats in a feedback slot
// A: register where the function object lives, followed by arguments; results are placed starting from the same register
// B: argument count + 1, or 0 to preserve all arguments up to top (MULTRET)
// C: result count + 1, or 0 to preserve all values and adjust top (MULTRET)
// AUX: feedback slot id. 0xFFFFFFFF - sealed
CALLFB,
// CMPPROTO: check if a register contains a closure with a specified Luau function proto id
// A: closure register
// D: jump offset if proto doesn't match
// AUX: proto id
CMPPROTO,
// Enum entry for number of opcodes, not a valid opcode by itself!
_COUNT,
};
// Bytecode instruction header: it's always a 32-bit integer, with low byte (first byte in little endian) containing the opcode
// Some instruction types require more data and have more 32-bit integers following the header
pub inline fn INSN_OP(insn: u32) u8 {
return (insn) & 0xff;
}
// ABC encoding: three 8-bit values, containing registers or small numbers
pub inline fn INSN_A(insn: u32) u8 {
return (((insn) >> 8) & 0xff);
}
pub inline fn INSN_B(insn: u32) u8 {
return (((insn) >> 16) & 0xff);
}
pub inline fn INSN_C(insn: u32) u8 {
return (((insn) >> 24) & 0xff);
}
// AD encoding: one 8-bit value, one signed 16-bit value
pub inline fn INSN_D(insn: u32) i16 {
return (@as(i32, @bitCast(insn)) >> 16);
}
// E encoding: one signed 24-bit value
pub inline fn INSN_E(insn: u32) i32 {
return (@as(i32, @bitCast(insn)) >> 8);
}
// Bytecode tags, used internally for bytecode encoded as a string
pub const BytecodeTag = enum(u32) {
CONSTANT_NIL = 0,
CONSTANT_BOOLEAN,
CONSTANT_NUMBER,
CONSTANT_STRING,
CONSTANT_IMPORT,
CONSTANT_TABLE,
CONSTANT_CLOSURE,
CONSTANT_VECTOR,
CONSTANT_TABLE_WITH_CONSTANTS,
CONSTANT_INTEGER,
CONSTANT_CLASS_SHAPE,
// WARNING: This must always be last.
CONSTANT__COUNT,
// Bytecode version; runtime supports [MIN, MAX], compiler emits TARGET by default but may emit a higher version when flags are enabled
// Type encoding version
// Types of constant table entries
pub const VERSION_MIN = 3;
pub const VERSION_MAX = 6;
pub const VERSION_TARGET = 6;
pub const TYPE_VERSION_MIN = 1;
pub const TYPE_VERSION_MAX = 3;
pub const TYPE_VERSION_TARGET = 3;
};
// Type table tags
pub const BytecodeType = enum(u32) {
TYPE_NIL = 0,
TYPE_BOOLEAN,
TYPE_NUMBER,
TYPE_STRING,
TYPE_TABLE,
TYPE_FUNCTION,
TYPE_THREAD,
TYPE_USERDATA,
TYPE_VECTOR,
TYPE_BUFFER,
TYPE_ANY = 15,
TYPE_TAGGED_USERDATA_BASE = 64,
TYPE_TAGGED_USERDATA_END = 64 + 32,
TYPE_OPTIONAL_BIT = 1 << 7,
TYPE_INVALID = 256,
};
// Builtin function ids, used in FASTCALL
pub const BuiltinFunction = enum(u32) {
LBF_NONE = 0,
// assert()
LBF_ASSERT,
// math.
LBF_MATH_ABS,
LBF_MATH_ACOS,
LBF_MATH_ASIN,
LBF_MATH_ATAN2,
LBF_MATH_ATAN,
LBF_MATH_CEIL,
LBF_MATH_COSH,
LBF_MATH_COS,
LBF_MATH_DEG,
LBF_MATH_EXP,
LBF_MATH_FLOOR,
LBF_MATH_FMOD,
LBF_MATH_FREXP,
LBF_MATH_LDEXP,
LBF_MATH_LOG10,
LBF_MATH_LOG,
LBF_MATH_MAX,
LBF_MATH_MIN,
LBF_MATH_MODF,
LBF_MATH_POW,
LBF_MATH_RAD,
LBF_MATH_SINH,
LBF_MATH_SIN,
LBF_MATH_SQRT,
LBF_MATH_TANH,
LBF_MATH_TAN,
// bit32.
LBF_BIT32_ARSHIFT,
LBF_BIT32_BAND,
LBF_BIT32_BNOT,
LBF_BIT32_BOR,
LBF_BIT32_BXOR,
LBF_BIT32_BTEST,
LBF_BIT32_EXTRACT,
LBF_BIT32_LROTATE,
LBF_BIT32_LSHIFT,
LBF_BIT32_REPLACE,
LBF_BIT32_RROTATE,
LBF_BIT32_RSHIFT,
// type()
LBF_TYPE,
// string.
LBF_STRING_BYTE,
LBF_STRING_CHAR,
LBF_STRING_LEN,
// typeof()
LBF_TYPEOF,
// string.
LBF_STRING_SUB,
// math.
LBF_MATH_CLAMP,
LBF_MATH_SIGN,
LBF_MATH_ROUND,
// raw*
LBF_RAWSET,
LBF_RAWGET,
LBF_RAWEQUAL,
// table.
LBF_TABLE_INSERT,
LBF_TABLE_UNPACK,
// vector ctor
LBF_VECTOR,
// bit32.count
LBF_BIT32_COUNTLZ,
LBF_BIT32_COUNTRZ,
// select(_, ...)
LBF_SELECT_VARARG,
// rawlen
LBF_RAWLEN,
// bit32.extract(_, k, k)
LBF_BIT32_EXTRACTK,
// get/setmetatable
LBF_GETMETATABLE,
LBF_SETMETATABLE,
// tonumber/tostring
LBF_TONUMBER,
LBF_TOSTRING,
// bit32.byteswap(n)
LBF_BIT32_BYTESWAP,
// buffer.
LBF_BUFFER_READI8,
LBF_BUFFER_READU8,
LBF_BUFFER_WRITEU8,
LBF_BUFFER_READI16,
LBF_BUFFER_READU16,
LBF_BUFFER_WRITEU16,
LBF_BUFFER_READI32,
LBF_BUFFER_READU32,
LBF_BUFFER_WRITEU32,
LBF_BUFFER_READF32,
LBF_BUFFER_WRITEF32,
LBF_BUFFER_READF64,
LBF_BUFFER_WRITEF64,
// vector.
LBF_VECTOR_MAGNITUDE,
LBF_VECTOR_NORMALIZE,
LBF_VECTOR_CROSS,
LBF_VECTOR_DOT,
LBF_VECTOR_FLOOR,
LBF_VECTOR_CEIL,
LBF_VECTOR_ABS,
LBF_VECTOR_SIGN,
LBF_VECTOR_CLAMP,
LBF_VECTOR_MIN,
LBF_VECTOR_MAX,
// math.lerp
LBF_MATH_LERP,
// vector.lerp
LBF_VECTOR_LERP,
// math.
LBF_MATH_ISNAN,
LBF_MATH_ISINF,
LBF_MATH_ISFINITE,
// integer
LBF_INTEGER_CREATE,
LBF_INTEGER_TONUMBER,
LBF_INTEGER_NEG,
LBF_INTEGER_ADD,
LBF_INTEGER_SUB,
LBF_INTEGER_MUL,
LBF_INTEGER_DIV,
LBF_INTEGER_MIN,
LBF_INTEGER_MAX,
LBF_INTEGER_REM,
LBF_INTEGER_IDIV,
LBF_INTEGER_UDIV,
LBF_INTEGER_UREM,
LBF_INTEGER_MOD,
LBF_INTEGER_CLAMP,
LBF_INTEGER_BAND,
LBF_INTEGER_BOR,
LBF_INTEGER_BNOT,
LBF_INTEGER_BXOR,
LBF_INTEGER_LT,
LBF_INTEGER_LE,
LBF_INTEGER_ULT,
LBF_INTEGER_ULE,
LBF_INTEGER_GT,
LBF_INTEGER_GE,
LBF_INTEGER_UGT,
LBF_INTEGER_UGE,
LBF_INTEGER_LSHIFT,
LBF_INTEGER_RSHIFT,
LBF_INTEGER_ARSHIFT,
LBF_INTEGER_LROTATE,
LBF_INTEGER_RROTATE,
LBF_INTEGER_EXTRACT,
LBF_INTEGER_BTEST,
LBF_INTEGER_COUNTRZ,
LBF_INTEGER_COUNTLZ,
LBF_INTEGER_BSWAP,
// buffer.readinteger / buffer.writeinteger (int64_t)
LBF_BUFFER_READINTEGER,
LBF_BUFFER_WRITEINTEGER,
};
// Capture type, used in CAPTURE
pub const CaptureType = enum(u32) {
LCT_VAL = 0,
LCT_REF,
LCT_UPVAL,
};
// Proto flag bitmask, stored in Proto::flags
pub const ProtoFlag = enum(u32) {
/// used to tag main proto for modules with --!native
LPF_NATIVE_MODULE = 1 << 0,
/// used to tag individual protos as not profitable to compile natively
LPF_NATIVE_COLD = 1 << 1,
/// used to tag main proto for modules that have at least one function with native attribute
LPF_NATIVE_FUNCTION = 1 << 2,
/// function can be inlined
LPF_INLINABLE = 1 << 3,
};
pub const LuauFeedbackType = enum(u32) { LFT_CALLTARGET = 0 };
// sources:
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Common/include/Luau/Bytecode.h
+40
View File
@@ -0,0 +1,40 @@
const Bytecode = @import("Bytecode.zig");
pub inline fn getOpLength(op: Bytecode.Opcode) usize {
return switch (op) {
.GETGLOBAL,
.SETGLOBAL,
.GETIMPORT,
.GETTABLEKS,
.SETTABLEKS,
.NAMECALL,
.JUMPIFEQ,
.JUMPIFLE,
.JUMPIFLT,
.JUMPIFNOTEQ,
.JUMPIFNOTLE,
.JUMPIFNOTLT,
.NEWTABLE,
.SETLIST,
.FORGLOOP,
.LOADKX,
.FASTCALL2,
.FASTCALL2K,
.FASTCALL3,
.JUMPXEQKNIL,
.JUMPXEQKB,
.JUMPXEQKN,
.JUMPXEQKS,
.GETUDATAKS,
.SETUDATAKS,
.NAMECALLUDATA,
.NEWCLASSMEMBER,
.CALLFB,
.CMPPROTO,
=> 2,
else => 1,
};
}
// sources:
// https://github.com/luau-lang/luau/blob/32d52d1b2ceef46fc25d87094a2d7f201c3ea5b8/Common/include/Luau/BytecodeUtils.h
+191
View File
@@ -0,0 +1,191 @@
const std = @import("std");
const cpp_std = @import("../cpp_std.zig");
extern fn zig_new_any(size: usize) callconv(.c) *anyopaque;
extern fn zig_delete_any(*anyopaque) callconv(.c) void;
pub fn DenseHashPointer(key: *const anyopaque) usize {
// return (@intFromPtr(key) >> 4) ^ (@intFromPtr(key) >> 9);
// The idea to use this hash function was suggested here originally: https://maskray.me/blog/2026-06-07-recent-llvm-hash-table-improvements
// Hash function implementation is detailed here: https://github.com/MaskRay/llvm-project/blob/main/llvm/include/llvm/ADT/DenseMapInfo.h
// This hash produces better scattering for arena allocated types, because the pointers usually share the higher order bits.
// When inserting lots of keys, quadratic probing is not enough to save DenseHash, although it usually takes many more elements,
// before it becomes a problem
var u: u64 = @intFromPtr(key);
u *%= 0xbf58476d1ce4e5b9;
u ^= u >> 31;
// On 32-bit platforms uint64_t to size_t is a narrowing, so we need
// to static cast here.
return @truncate(u);
}
pub const detail = struct {
pub fn DenseHashTable(
comptime Key: type,
comptime Item: type,
comptime MutableItem: type,
comptime ItemInterface: type,
comptime Hasher: type,
) type {
const hash = if (@hasDecl(Hasher, "hash")) Hasher.hash else struct {
pub fn hash(e: Key) usize {
if (comptime @typeInfo(Key) == .pointer) {
return DenseHashPointer(@ptrCast(@alignCast(e)));
} else {
@compileError("Hasher must implement 'hash' function");
}
}
}.hash;
const eq = if (@hasDecl(ItemInterface, "eq")) ItemInterface.eq else struct {
pub fn eq(a: Key, b: Key) bool {
return a == b;
}
}.eq;
_ = MutableItem;
return extern struct {
data: ?[*]Item = null,
capacity: usize = 0,
count: usize = 0,
empty_key: Key,
hasher: u8 = 0,
eq: u8 = 0,
const This = @This();
pub fn init(empty_key: Key, buckets: usize) This {
var data: ?[*]Item = null;
var capacity: usize = 0;
if (buckets > 0) {
data = @ptrCast(@alignCast(zig_new_any(@sizeOf(Item) * buckets)));
capacity = buckets;
ItemInterface.fill(data.?, buckets, empty_key);
}
return .{
.data = data,
.capacity = capacity,
.count = 0,
.empty_key = empty_key,
};
}
pub fn find(self: *This, key: Key) ?*const Item {
if (self.count == 0)
return null;
if (eq(key, self.empty_key))
return null;
const hashmod = self.capacity - 1;
var bucket = hash(key) & hashmod;
for (0..hashmod) |probe| {
const probe_item = &self.data.?[bucket];
// Element exists
if (eq(ItemInterface.getKey(probe_item), key))
return probe_item;
// Element does not exist
if (eq(ItemInterface.getKey(probe_item), self.empty_key))
return null;
// Hash collision, quadratic probing
bucket = (bucket + probe + 1) & hashmod;
}
// Hash table is full - this should not happen
std.debug.assert(false);
return null;
}
pub fn size(self: This) usize {
return self.count;
}
pub fn deinit(self: *This) void {
if (self.data) |data| {
ItemInterface.destroy(data, self.capacity);
zig_delete_any(@ptrCast(@alignCast(data)));
self.data = null;
self.capacity = 0;
}
}
};
}
};
pub fn ItemInterfaceSet(comptime Key: type) type {
return struct {
pub fn getKey(item: *const Key) Key {
return item.*;
}
pub fn setKey(item: *Key, key: Key) void {
item.* = key;
}
pub fn fill(data: [*]Key, count: usize, key: Key) void {
for (0..count) |i|
data[i] = key;
}
pub fn destroy(data: [*]Key, count: usize) void {
if (@hasDecl(Key, "deinit"))
for (0..count) |i| {
Key.deinit(data[i]);
};
}
};
}
pub fn ItemInterfaceMap(comptime Key: type, comptime Value: type) type {
return struct {
pub fn getKey(item: *const cpp_std.Pair(Key, Value)) Key {
return item.first;
}
pub fn setKey(item: *cpp_std.Pair(Key, Value), key: Key) void {
item.first = key;
}
pub fn fill(data: [*]cpp_std.Pair(Key, Value), count: usize, key: Key) void {
for (0..count) |i| {
data[i].first = key;
data[i].second = .{};
}
}
pub fn destroy(data: [*]cpp_std.Pair(Key, Value), count: usize) void {
for (0..count) |i| {
const ptr = data[i];
if (@hasDecl(Key, "deinit"))
Key.deinit(&ptr.first);
if (@hasDecl(Value, "deinit"))
Value.deinit(&ptr.second);
}
}
};
}
pub fn DenseHashSet(
comptime Key: type,
comptime Hasher: type,
) type {
return detail.DenseHashTable(Key, Key, Key, ItemInterfaceSet(Key), Hasher);
}
pub fn DenseHashMap(
comptime Key: type,
comptime Value: type,
comptime Hasher: type,
) type {
return detail.DenseHashTable(Key, cpp_std.Pair(Key, Value), cpp_std.Pair(Key, Value), ItemInterfaceMap(Key, Value), Hasher);
}
// sources:
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Common/include/Luau/DenseHash.h
+28
View File
@@ -0,0 +1,28 @@
const std = @import("std");
pub inline fn isAnalysisFlagExperimental(flag: []const u8) bool {
// Flags in this list are disabled by default in various command-line tools. They may have behavior that is not fully final,
// or critical bugs that are found after the code has been submitted. This list is intended _only_ for flags that affect
// Luau's type checking. Flags that may change runtime behavior (e.g.: parser or VM flags) are not appropriate for this list.
const kList = [_][]const u8{
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
"StudioReportLuauAny2", // takes telemetry data for usage of any types
"LuauTableCloneClonesType3", // requires fixes in lua-apps code, terrifyingly
"LuauSolverV2",
"UseNewLuauTypeSolverDefaultEnabled", // This can change the default solver used in cli applications, so it also needs to be disabled. Will require fixes in lua-apps code
};
for (comptime kList) |item|
if (std.mem.eql(u8, flag, item))
return true;
return false;
}
test {
std.testing.refAllDecls(@This());
}
// sources:
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Common/include/Luau/ExperimentalFlags.h
+66
View File
@@ -0,0 +1,66 @@
const std = @import("std");
pub fn Variant(comptime Ts: []const type) type {
comptime {
if (Ts.len == 0) @compileError("variant must have at least 1 type");
}
const storage_size = comptime blk: {
var max: usize = 0;
for (Ts) |T|
max = @max(max, @sizeOf(T));
break :blk max;
};
const storage_align = comptime blk: {
var max: usize = 1;
for (Ts) |T|
max = @max(max, @alignOf(T));
break :blk max;
};
const TaggedUnion = blk: {
var names: [Ts.len][]const u8 = undefined;
var field_types: [Ts.len]type = undefined;
var field_attributes: [Ts.len]std.builtin.Type.UnionField.Attributes = undefined;
inline for (Ts, 0..) |T, i| {
names[i] = std.fmt.comptimePrint("{d}", .{i}); // becomes @"0", @"1", etc.
field_types[i] = T;
field_attributes[i] = .{
.@"align" = @alignOf(T),
};
}
break :blk @Union(
.auto,
null,
&names,
&field_types,
&field_attributes,
);
};
return extern struct {
typeId: c_int,
storage: [storage_size]u8 align(storage_align),
pub const Union = TaggedUnion;
pub fn @"union"(self: *const @This()) Union {
inline for (Ts, 0..) |T, i| {
if (self.typeId == @as(c_int, @intCast(i))) {
const active_ptr: *const T = @ptrCast(@alignCast(&self.storage));
return @unionInit(
Union,
std.fmt.comptimePrint("{d}", .{i}),
active_ptr.*,
);
}
}
unreachable;
}
pub fn index(self: *const @This()) c_int {
return self.typeId;
}
};
}
+133
View File
@@ -0,0 +1,133 @@
#include <bridge.h>
#include "luacode.h"
#include "Luau/Common.h"
#include "Luau/Parser.h"
#include "Luau/BytecodeBuilder.h"
#include "Luau/Compiler.h"
#include "Luau/TimeTrace.h"
#define ZIG_LUAU_COMPILER(name) ZIG_FN(Luau_Compiler_##name)
const char* outputBytes(const std::string& result, size_t* len)
{
char* copy = static_cast<char*>(malloc(result.size()));
if (!copy)
return nullptr;
memcpy(copy, result.data(), result.size());
*len = result.size();
return copy;
}
ZIG_EXPORT const char* ZIG_LUAU_COMPILER(compile_ParseResult)(
const Luau::ParseResult* result,
Luau::AstNameTable* names,
size_t* len,
lua_CompileOptions* options,
Luau::BytecodeEncoder* encoder = nullptr
) {
Luau::CompileOptions opts;
if (options)
{
static_assert(sizeof(lua_CompileOptions) == sizeof(Luau::CompileOptions), "C and C++ interface must match");
memcpy(static_cast<void*>(&opts), options, sizeof(opts));
}
LUAU_TIMETRACE_SCOPE("compile", "Compiler");
if (!result->errors.empty())
{
// Users of this function expect only a single error message
const Luau::ParseError& parseError = result->errors.front();
std::string error = Luau::format(":%d: %s", parseError.getLocation().begin.line + 1, parseError.what());
return outputBytes(Luau::BytecodeBuilder::getError(error), len);
}
try
{
Luau::BytecodeBuilder bcb(encoder);
Luau::compileOrThrow(bcb, *result, *names, opts);
return outputBytes(bcb.getBytecode(), len);
}
catch (Luau::CompileError& e)
{
std::string error = Luau::format(":%d: %s", e.getLocation().begin.line + 1, e.what());
return outputBytes(Luau::BytecodeBuilder::getError(error), len);
}
}
ZIG_EXPORT int ZIG_LUAU_COMPILER(compileLoad_ParseResult)(
const Luau::ParseResult* result,
Luau::AstNameTable* names,
lua_State* L,
const char* moduleName,
const lua_CompileOptions* options,
int env,
Luau::BytecodeEncoder* encoder = nullptr
) {
Luau::CompileOptions opts;
if (options)
{
static_assert(sizeof(lua_CompileOptions) == sizeof(Luau::CompileOptions), "C and C++ interface must match");
memcpy(static_cast<void*>(&opts), options, sizeof(opts));
}
LUAU_TIMETRACE_SCOPE("compile", "Compiler");
if (!result->errors.empty())
{
// Users of this function expect only a single error message
const Luau::ParseError& parseError = result->errors.front();
std::string error = Luau::format(":%d: %s", parseError.getLocation().begin.line + 1, parseError.what());
std::string bytecode = Luau::BytecodeBuilder::getError(error);
return luau_load(L, moduleName, bytecode.data(), bytecode.size(), env);
}
try
{
Luau::BytecodeBuilder bcb(encoder);
Luau::compileOrThrow(bcb, *result, *names, opts);
std::string bytecode = bcb.getBytecode();
return luau_load(L, moduleName, bytecode.data(), bytecode.size(), env);
}
catch (Luau::CompileError& e)
{
std::string error = Luau::format(":%d: %s", e.getLocation().begin.line + 1, e.what());
std::string bytecode = Luau::BytecodeBuilder::getError(error);
return luau_load(L, moduleName, bytecode.data(), bytecode.size(), env);
}
}
ZIG_EXPORT int ZIG_LUAU_COMPILER(compileLoad)(
lua_State* L,
const char* moduleName,
const char* contents,
size_t len,
const lua_CompileOptions* options,
int env
) {
Luau::CompileOptions opts;
if (options)
{
static_assert(sizeof(lua_CompileOptions) == sizeof(Luau::CompileOptions), "C and C++ interface must match");
memcpy(static_cast<void*>(&opts), options, sizeof(opts));
}
std::string bytecode = Luau::compile(std::string(contents, len), opts);
return luau_load(L, moduleName, bytecode.data(), bytecode.size(), env);
}
ZIG_EXPORT void ZIG_LUAU_COMPILER(compile_free)(void *ptr)
{
free(ptr);
}
+157
View File
@@ -0,0 +1,157 @@
const std = @import("std");
const lua = @import("../VM/lua.zig");
const Parser = @import("../Ast/Parser.zig");
const Lexer = @import("../Ast/Lexer.zig");
const Location = @import("../Ast/Location.zig").Location;
const cpp_std = @import("../cpp_std.zig");
pub const CompileConstant = *anyopaque;
/// return a type identifier for a global library member
/// values are defined by 'enum LuauBytecodeType' in Bytecode.h
pub const LibraryMemberTypeCallback = *const fn (library: [*c]const u8, member: [*c]const u8) callconv(.c) c_int;
/// setup a value of a constant for a global library member
/// use setCompileConstant*** set of functions for values
pub const LibraryMemberConstantCallback = *const fn (library: [*c]const u8, member: [*c]const u8, constant: *CompileConstant) callconv(.c) c_int;
pub const CompileOptions = extern struct {
/// 0 - no optimization
/// 1 - baseline optimization level that doesn't prevent debuggability
/// 2 - includes optimizations that harm debuggability such as inlining
optimizationLevel: c_int = 1,
/// 0 - no debugging support
/// 1 - line info & function names only; sufficient for backtraces
/// 2 - full debug info with local & upvalue names; necessary for debugger
debugLevel: c_int = 1,
/// type information is used to guide native code generation decisions
/// information includes testable typeArguments for function arguments, locals, upvalues and some temporaries
/// 0 - generate for native modules
/// 1 - generate for all modules
typeInfoLevel: c_int = 0,
/// 0 - no code coverage support
/// 1 - statement coverage
/// 2 - statement and expression coverage (verbose)
coverageLevel: c_int = 0,
/// alternative global builtin to construct vectors, in addition to default builtin 'vector.create'
vectorLib: [*c]const u8 = null,
vectorCtor: [*c]const u8 = null,
/// alternative vector type name for type tables, in addition to default type 'vector'
vectorType: [*c]const u8 = null,
/// null-terminated array of globals that are mutable; disables the import optimization for fields accessed through these
mutableGlobals: [*c]const [*c]const u8 = null,
/// null-terminated array of userdata typeArguments that will be included in the type information
userdataTypes: [*c]const [*c]const u8 = null,
/// null-terminated array of globals which act as libraries and have members with known type and/or constant value
/// when an import of one of these libraries is accessed, callbacks below will be called to receive that information
librariesWithKnownMembers: [*c]const [*c]const u8 = null,
libraryMemberTypeCb: ?LibraryMemberTypeCallback = null,
libraryMemberConstantCb: ?LibraryMemberConstantCallback = null,
// null-terminated array of library functions that should not be compiled into a built-in fastcall ("name" "lib.name")
disabledBuiltins: [*c]const [*c]const u8 = null,
};
pub const CompilerError = cpp_std.Exception(extern struct {
location: Location,
message: cpp_std.String,
});
extern "c" fn zig_Luau_Compiler_compile_ParseResult(
*const Parser.ParseResult,
*const Lexer.AstNameTable,
*usize,
?*const CompileOptions,
?*anyopaque,
) ?[*]const u8;
extern "c" fn zig_Luau_Compiler_compileLoad_ParseResult(
*const Parser.ParseResult,
*const Lexer.AstNameTable,
*lua.State,
[*c]const u8,
?*const CompileOptions,
c_int,
?*anyopaque,
) c_int;
extern "c" fn zig_Luau_Compiler_compileLoad(
*lua.State,
[*c]const u8,
[*c]const u8,
usize,
?*const CompileOptions,
c_int,
) c_int;
extern "c" fn zig_Luau_Compiler_compile_free(*anyopaque) void;
pub fn compileParseResult(
allocator: std.mem.Allocator,
parseResult: *Parser.ParseResult,
namesTable: *Lexer.AstNameTable,
options: ?CompileOptions,
) error{OutOfMemory}![]const u8 {
var size: usize = 0;
const bytes = zig_Luau_Compiler_compile_ParseResult(parseResult, namesTable, &size, if (options) |*o| o else null, null) orelse return error.OutOfMemory;
defer zig_Luau_Compiler_compile_free(@ptrCast(@constCast(bytes)));
return try allocator.dupe(u8, bytes[0..size]);
}
pub fn compileLoadParseResult(
L: *lua.State,
moduleName: [:0]const u8,
parseResult: *Parser.ParseResult,
namesTable: *Lexer.AstNameTable,
options: ?CompileOptions,
env: i32,
) error{Fail}!void {
if (zig_Luau_Compiler_compileLoad_ParseResult(parseResult, namesTable, L, moduleName, if (options) |*o| o else null, env, null) != 0)
return error.Fail;
}
pub fn compileLoad(
L: *lua.State,
moduleName: [:0]const u8,
source: []const u8,
options: ?CompileOptions,
env: i32,
) error{Fail}!void {
if (zig_Luau_Compiler_compileLoad(L, moduleName, source.ptr, source.len, if (options) |*o| o else null, env) != 0)
return error.Fail;
}
test compileParseResult {
const Allocator = @import("../Ast/Allocator.zig");
const allocator = Allocator.init();
defer allocator.deinit();
const astNameTable = Lexer.AstNameTable.init(allocator);
defer astNameTable.deinit();
const source =
\\--!test
\\-- This is a test comment
\\local x =
\\
;
const parseResult = Parser.parse(source, astNameTable, allocator, .{});
defer parseResult.deinit();
const zig_allocator = std.testing.allocator;
const bytes = try compileParseResult(zig_allocator, parseResult, astNameTable, null);
defer zig_allocator.free(bytes);
try std.testing.expect(bytes[0] == 0);
try std.testing.expectEqualStrings(":4: Expected identifier when parsing expression, got <eof>", bytes[1..]);
}
// sources:
// https://github.com/luau-lang/luau/blob/8fe64db609ccbffb0abb7507c7ecef8c88327ef3/Compiler/include/Luau/Compiler.h
// https://github.com/luau-lang/luau/blob/8fe64db609ccbffb0abb7507c7ecef8c88327ef3/Compiler/src/Compiler.cpp
+49
View File
@@ -0,0 +1,49 @@
const c = @import("c");
const std = @import("std");
const Compiler = @import("Compiler.zig");
extern "c" fn zig_luau_free(ptr: *anyopaque) void;
extern "c" fn luau_compile(source: [*c]const u8, size: usize, options: ?*const Compiler.CompileOptions, outsize: [*c]usize) [*c]u8;
extern "c" fn luau_set_compile_constant_nil(constant: *Compiler.CompileConstant) void;
extern "c" fn luau_set_compile_constant_boolean(constant: *Compiler.CompileConstant, b: c_int) void;
extern "c" fn luau_set_compile_constant_number(constant: *Compiler.CompileConstant, n: f64) void;
extern "c" fn luau_set_compile_constant_vector(constant: *Compiler.CompileConstant, x: f32, y: f32, z: f32, w: f32) void;
extern "c" fn luau_set_compile_constant_string(constant: *Compiler.CompileConstant, s: [*c]const u8, l: usize) void;
/// Compile luau source into bytecode, return callee owned buffer allocated through the given allocator.
pub fn compile(allocator: std.mem.Allocator, source: []const u8, options: ?Compiler.CompileOptions) ![]const u8 {
var size: usize = 0;
const bytecode = luau_compile(source.ptr, source.len, if (options) |*o| o else null, &size);
if (bytecode == null)
return error.OutOfMemory;
defer zig_luau_free(bytecode);
return try allocator.dupe(u8, bytecode[0..size]);
}
pub fn set_compile_constant_nil(constant: *Compiler.CompileConstant) void {
luau_set_compile_constant_nil(constant);
}
pub fn set_compile_constant_boolean(constant: *Compiler.CompileConstant, b: bool) void {
luau_set_compile_constant_boolean(constant, if (b) 1 else 0);
}
pub fn set_compile_constant_number(constant: *Compiler.CompileConstant, n: f64) void {
luau_set_compile_constant_number(constant, n);
}
pub fn set_compile_constant_vector(constant: *Compiler.CompileConstant, x: f32, y: f32, z: f32, w: f32) void {
luau_set_compile_constant_vector(constant, x, y, z, w);
}
pub fn set_compile_constant_string(constant: *Compiler.CompileConstant, s: []const u8) void {
luau_set_compile_constant_string(constant, s.ptr, s.len);
}
// sources:
// https://github.com/luau-lang/luau/blob/8fe64db609ccbffb0abb7507c7ecef8c88327ef3/Compiler/include/luacode.h
// https://github.com/luau-lang/luau/blob/8fe64db609ccbffb0abb7507c7ecef8c88327ef3/Compiler/src/lcode.cpp
+12
View File
@@ -0,0 +1,12 @@
const lua = @import("../VM/lua.zig");
extern "c" fn luau_enable_jit_inliner(lua_State: *lua.State) void;
extern "c" fn luau_disable_jit_inliner(lua_State: *lua.State) void;
pub fn enable(L: *lua.State) void {
luau_enable_jit_inliner(L);
}
pub fn disable(L: *lua.State) void {
luau_disable_jit_inliner(L);
}
+25
View File
@@ -0,0 +1,25 @@
#include <bridge.h>
#include "lobject.h"
#include "lstate.h"
ZIG_EXPORT const unsigned char GCObject_size = sizeof(GCObject);
ZIG_EXPORT const unsigned char GCheader_size = sizeof(GCheader);
ZIG_EXPORT const unsigned char Value_size = sizeof(Value);
ZIG_EXPORT const unsigned char TValue_size = sizeof(TValue);
ZIG_EXPORT const unsigned char TString_size = sizeof(TString);
ZIG_EXPORT const unsigned char Udata_size = sizeof(Udata);
ZIG_EXPORT const unsigned char LuauBuffer_size = sizeof(LuauBuffer);
ZIG_EXPORT const unsigned char Proto_size = sizeof(Proto);
ZIG_EXPORT const unsigned char LocVar_size = sizeof(LocVar);
ZIG_EXPORT const unsigned char UpVal_size = sizeof(UpVal);
ZIG_EXPORT const unsigned char Closure_size = sizeof(Closure);
ZIG_EXPORT const unsigned char TKey_size = sizeof(TKey);
ZIG_EXPORT const unsigned char LuaNode_size = sizeof(LuaNode);
ZIG_EXPORT const unsigned char LuaTable_size = sizeof(LuaTable);
ZIG_EXPORT const unsigned char LuauClass_size = sizeof(LuauClass);
ZIG_EXPORT const unsigned char LuauObject_size = sizeof(LuauObject);
ZIG_EXPORT const unsigned char TString_data_offset = offsetof(TString, data);
ZIG_EXPORT const unsigned char Udata_data_offset = offsetof(Udata, data);
ZIG_EXPORT const unsigned char LuauBuffer_data_offset = offsetof(LuauBuffer, data);
+11
View File
@@ -0,0 +1,11 @@
pub const Memory = error{ BlockTooBig, OutOfMemory };
pub const Table = Memory || error{
@"table overflow",
@"attempt to modify a readonly table",
@"table index is nil",
@"table index is nan",
@"table index contains nan",
};
pub const TableReadonly = Table.@"attempt to modify a readonly table";
+1650
View File
File diff suppressed because it is too large Load Diff
+278
View File
@@ -0,0 +1,278 @@
const c = @import("c");
const std = @import("std");
const lua = @import("lua.zig");
const ltm = @import("ltm.zig");
const lapi = @import("lapi.zig");
const lobject = @import("lobject.zig");
const Errorset = @import("errorset.zig");
pub const Reg = struct {
name: [:0]const u8,
func: ?lua.CFunction,
};
pub fn OptionalValue(comptime T: type, L: *lua.State, check: anytype, narg: i32, d: T) T {
if (L.isnoneornil(narg))
return d
else
return check(L, narg);
}
pub fn currfuncname(L: *lua.State) ?[:0]const u8 {
const cl: ?*lobject.Closure = if (@intFromPtr(L.ci) > @intFromPtr(L.base_ci))
L.curr_func()
else
null;
const debugname: ?[:0]const u8 = if (cl != null and cl.?.isC != 0)
std.mem.span(cl.?.d.c.debugname)
else
null;
if (debugname != null and std.mem.eql(u8, debugname.?, "__namecall")) {
return if (L.namecall) |namecall|
std.mem.span(namecall.getstr())
else
null;
} else return debugname;
}
pub inline fn LargerrorL(L: *lua.State, narg: i32, extramsg: [:0]const u8) Errorset.Table!noreturn {
const fname = currfuncname(L);
if (fname) |name|
try LerrorL(L, "invalid argument #{d} to '{s}' ({s})", .{ narg, name, extramsg })
else
try LerrorL(L, "invalid argument #{d} ({s})", .{ narg, extramsg });
}
pub inline fn Largerror(L: *lua.State, narg: i32, extramsg: [:0]const u8) Errorset.Table!noreturn {
try LargerrorL(L, narg, extramsg);
}
pub inline fn Largcheck(L: *lua.State, cond: bool, narg: i32, extramsg: [:0]const u8) Errorset.Table!noreturn {
if (!cond) try LargerrorL(L, narg, extramsg);
}
pub inline fn LtypeerrorL(L: *lua.State, narg: i32, tname: [:0]const u8) noreturn {
c.luaL_typeerror(@as(*c.lua_State, @ptrCast(L)), narg, tname);
}
inline fn tag_error(L: *lua.State, narg: i32, tag: lua.Type) void {
LtypeerrorL(L, narg, lapi.typename(tag));
}
pub fn Lwhere(L: *lua.State, level: i32) Errorset.Table!void {
var info: lua.Debug = .{ .ssbuf = undefined };
if (L.getinfo(level, "sl", &info)) {
if (info.currentline) |line| {
try L.pushfstring("{s}:{d}: ", .{ info.short_src.?, line });
return;
}
}
try L.rawcheckstack(1);
try L.pushstring("");
}
pub fn LerrorL(L: *lua.State, comptime fmt: []const u8, args: anytype) Errorset.Table!noreturn {
try Lwhere(L, 1);
try L.pushvfstring(fmt, args);
try L.concat(2);
L.raiseerror();
}
pub inline fn Lcheckoption(L: *lua.State, comptime T: type, narg: i32, def: ?T) T {
const name = blk: {
if (def) |d|
break :blk Loptstring(narg, @tagName(d))
else
break :blk L.checkstring(narg);
};
inline for (std.meta.fields(T)) |field| {
if (std.mem.eql(u8, field.name, name))
return @enumFromInt(field.value);
}
var buf: [128]u8 = undefined;
return LargerrorL(L, narg, std.fmt.bufPrintZ(&buf, "invalid option '{s}'", .{name}) catch "");
}
/// Returns true if metatable was created, false if it already exists.
pub fn Lnewmetatable(L: *lua.State, tname: [:0]const u8) !bool {
if (try L.getfield(lua.REGISTRYINDEX, tname) != .Nil) // get registry.name, name already in use?
return false; // leave previous value on top, but return false
L.pop(1);
try L.newtable(); // create metatable
L.pushvalue(-1);
try L.setfield(lua.REGISTRYINDEX, tname); // registry.name = metatable
return true;
}
pub inline fn Lgetmetatable(L: *lua.State, tname: [:0]const u8) !lua.Type {
return L.getfield(lua.REGISTRYINDEX, tname);
}
pub inline fn Lcheckudata(L: *lua.State, comptime T: type, ud: i32, tname: [:0]const u8) ?*T {
return @ptrCast(c.luaL_checkudata(@ptrCast(L), ud, tname).?);
}
pub fn Lcheckbuffer(L: *lua.State, idx: i32) []u8 {
if (L.tobuffer(idx)) |b|
return b
else
return tag_error(L, idx, .Buffer);
}
pub fn Lcheckstack(L: *lua.State, space: usize, msg: ?[]const u8) Errorset.Table!void {
if (!try L.checkstack(space))
if (msg) |m|
try LerrorL(L, "stack overflow ({s})", .{m})
else
try LerrorL(L, "stack overflow", .{});
}
pub fn Lchecktype(L: *lua.State, narg: i32, t: lua.Type) void {
if (L.typeOf(narg) != t)
tag_error(L, narg, t);
}
pub fn Lcheckany(L: *lua.State, narg: i32) !void {
if (L.typeOf(narg) == .None)
try LerrorL(L, "missing argument #{d}", .{narg});
}
pub fn Lchecklstring(L: *lua.State, narg: i32) []const u8 {
if (L.tolstring(narg)) |s|
return s
else
tag_error(L, narg, .String);
}
pub fn Lcheckstring(L: *lua.State, narg: i32) [:0]const u8 {
if (L.tolstring(narg)) |s|
return s
else
tag_error(L, narg, .String);
}
pub fn Loptlstring(L: *lua.State, narg: i32, d: []const u8) []const u8 {
return OptionalValue([]const u8, L, Lchecklstring, narg, d);
}
pub fn Loptstring(L: *lua.State, narg: i32, d: [:0]const u8) [:0]const u8 {
return OptionalValue([:0]const u8, L, Lcheckstring, narg, d);
}
pub fn Lchecknumber(L: *lua.State, narg: i32) f64 {
return L.tonumberx(narg) orelse tag_error(L, narg, .Number);
}
pub fn Loptnumber(L: *lua.State, narg: i32, d: f64) f64 {
return OptionalValue(f64, L, Lchecknumber, narg, d);
}
pub fn Lcheckboolean(L: *lua.State, narg: i32) bool {
if (L.isboolean(narg))
return L.toboolean(narg)
else
tag_error(L, narg, .Boolean);
}
pub fn Loptboolean(L: *lua.State, narg: i32, d: bool) bool {
return OptionalValue(bool, L, Lcheckboolean, narg, d);
}
pub fn Lcheckinteger(L: *lua.State, narg: i32) i32 {
return L.tointegerx(narg) orelse tag_error(L, narg, .Number);
}
pub fn Lcheckinteger64(L: *lua.State, narg: i32) i64 {
return L.tointeger64(narg) orelse tag_error(L, narg, .Number);
}
pub fn Loptinteger(L: *lua.State, narg: i32, d: i32) i32 {
return OptionalValue(i32, L, Lcheckinteger, narg, d);
}
pub fn Loptinteger64(L: *lua.State, narg: i32, d: i64) i64 {
return OptionalValue(i64, L, Lcheckinteger64, narg, d);
}
pub fn Lcheckunsigned(L: *lua.State, narg: i32) u32 {
return L.tounsignedx(narg) orelse tag_error(L, narg, .Number);
}
pub fn Loptunsigned(L: *lua.State, narg: i32, d: u32) u32 {
return OptionalValue(u32, L, Lcheckunsigned, narg, d);
}
pub fn Lcheckvector(L: *lua.State, narg: i32) []const f32 {
return L.tovector(narg) orelse tag_error(L, narg, .Vector);
}
pub fn Loptvector(L: *lua.State, narg: i32, d: []const f32) []const f32 {
return OptionalValue([]const f32, L, Lcheckvector, narg, d);
}
pub fn Lgetmetafield(L: *lua.State, obj: i32, event: [:0]const u8) Errorset.Table!bool {
if (!L.getmetatable(obj)) // no metatable?
return false;
try L.pushstring(event);
_ = L.rawget(-2);
if (L.isnil(-1)) {
L.pop(2); // remove metatable and metafield
return false;
}
L.remove(-2); // remove only metatable
return true;
}
/// `unsafe` throws exceptions. Use `Zcallmeta`.
pub fn Lcallmeta(L: *lua.State, obj: i32, event: [:0]const u8) bool {
return c.luaL_callmeta(@ptrCast(L), obj, event) != 0;
}
pub fn Lregister(L: *lua.State, libname: ?[:0]const u8, funcs: []const Reg) Errorset.Table!void {
if (libname) |name| {
_ = try Lfindtable(L, lua.REGISTRYINDEX, "_LOADED", 1);
_ = try L.getfield(-1, name);
if (!L.istable(-1)) {
L.pop(1);
if (try Lfindtable(L, lua.GLOBALSINDEX, name, funcs.len)) |_|
try LerrorL(L, "name conflict for module '{s}'", .{name});
L.pushvalue(-1);
try L.setfield(-3, name);
}
L.remove(-2);
L.insert(-1);
}
for (funcs) |f| {
if (f.func) |func| {
try L.pushcfunction(func, f.name);
try L.setfield(-2, f.name);
}
}
}
pub inline fn Lfindtable(L: *lua.State, idx: i32, fname: [:0]const u8, szhint: usize) !?[]const u8 {
const p = c.luaL_findtable(@ptrCast(L), idx, fname, @truncate(@as(isize, @intCast(szhint))));
if (p != null)
return std.mem.span(p)
else
return null;
}
pub inline fn Ltypename(L: *lua.State, idx: i32) [:0]const u8 {
return std.mem.span(c.luaL_typename(@ptrCast(L), idx));
}
pub inline fn Lcallyieldable(L: *lua.State, nargs: i32, nresults: i32) i32 {
return c.luaL_callyieldable(@ptrCast(L), nargs, nresults);
}
/// `unsafe` throws exceptions. Use `Ztolstring`.
pub inline fn Ltolstring(L: *lua.State, idx: i32) [:0]const u8 {
var len: usize = undefined;
if (c.luaL_tolstring(@ptrCast(L), idx, &len)) |str|
return str[0..len :0]
else
unreachable;
}
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_base(@ptrCast(L));
}
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_bit32(@ptrCast(L));
}
+33
View File
@@ -0,0 +1,33 @@
const std = @import("std");
const lua = @import("lua.zig");
const lobject = @import("lobject.zig");
const lgc = @import("lgc.zig");
const lmem = @import("lmem.zig");
const Errorset = @import("errorset.zig");
// buffer size limit
pub const MAX_BUFFER_SIZE = 1 << 30;
// GCObject size has to be at least 16 bytes, so a minimum of 8 bytes is always reserved
pub inline fn sizebuffer(len: usize) usize {
return @offsetOf(lobject.Buffer, "data") + (if (len < 8) 8 else len);
}
pub fn Bnewbuffer(L: *lua.State, s: usize) Errorset.Memory!*lobject.Buffer {
if (s > MAX_BUFFER_SIZE)
return error.BlockTooBig;
const b = try lmem.Mnewgco(L, lobject.Buffer, sizebuffer(s), L.activememcat);
lgc.Cinit(L, @ptrCast(@alignCast(b)), @intFromEnum(lua.Type.Buffer));
b.len = @intCast(s);
@memset(@as([*]u8, @ptrCast(@alignCast(&b.data)))[0..s], 0);
return b;
}
pub fn Bfreebuffer(L: *lua.State, b: *lobject.Buffer, page: *lmem.lua_Page) void {
lmem.Mfreegco(L, b.obj2gco(), sizebuffer(b.len), b.header.memcat, page);
}
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_buffer(@ptrCast(L));
}
+90
View File
@@ -0,0 +1,90 @@
const std = @import("std");
const lua = @import("lua.zig");
const lobject = @import("lobject.zig");
const lgc = @import("lgc.zig");
const ltm = @import("ltm.zig");
const lmem = @import("lmem.zig");
const ltable = @import("ltable.zig");
const lstring = @import("lstring.zig");
const lfunc = @import("lfunc.zig");
const Errorset = @import("errorset.zig");
pub fn Rnewclass(
L: *lua.State,
name: *lobject.TString,
memberstooffset: *lobject.LuaTable,
offsettomember: [*]*lobject.TString,
numberofinstancemembers: u32,
numberofstaticmembers: u32,
) !*lobject.LuauClass {
std.debug.assert(L.global.GCthreshold == std.math.maxInt(usize)); // GC must be paused
const classobject = try lmem.Mnewgco(L, lobject.LuauClass, @sizeOf(lobject.LuauClass), L.activememcat);
lgc.Cinit(L, @ptrCast(@alignCast(classobject)), @intFromEnum(lua.Type.Class));
classobject.name = name;
classobject.staticmembers = try lmem.Mnewarray(L, lobject.TValue, numberofstaticmembers, classobject.header.memcat);
for (0..numberofstaticmembers) |i|
classobject.staticmembers[i].setnilvalue();
classobject.memberstooffset = memberstooffset;
classobject.offsettomember = offsettomember;
classobject.metatable = try ltable.Hnew(L, 0, 1);
const constructor = try lfunc.FnewCclosure(L, 0, L.gt.?);
constructor.d.c.f = zig_luaR_createobject;
constructor.d.c.debugname = "luaR_createobject";
constructor.d.c.cont = null;
const dest = try ltable.Hsetstr(L, classobject.metatable, L.global.tmname[@intFromEnum(ltm.TMS.TM_CALL)]);
std.debug.assert(dest.ttisnil());
dest.setclvalue(L, constructor);
classobject.metatable.readonly = 1;
classobject.instancemetatable = null;
classobject.numberofinstancemembers = numberofinstancemembers;
classobject.numberofallmembers = numberofinstancemembers + numberofstaticmembers;
return classobject;
}
pub fn Raddclassmember(L: *lua.State, classobject: *lobject.LuauClass, name: *lobject.TString, value: *const lobject.TValue) !void {
std.debug.assert(@as(?*anyopaque, @ptrCast(@alignCast(classobject.staticmembers))) != null);
const offset = ltable.Hgetstr(classobject.memberstooffset, name);
const offsetint: u32 = @intFromFloat(offset.nvalue());
std.debug.assert(offsetint >= classobject.numberofinstancemembers and offsetint < classobject.numberofallmembers);
std.debug.assert(value.ttisfunction() and value.value.gc.?.gch.ttype() == @intFromEnum(lua.Type.Function));
classobject.staticmembers[offsetint - classobject.numberofinstancemembers].setobj(L, value);
lgc.Cbarrier(L, @ptrCast(@alignCast(classobject)), value);
var isMetamethod: bool = name == lstring.Sassumelstr(L, "__tostring");
var i: u32 = 0;
while (!isMetamethod and i < ltm.N) : (i += 1)
isMetamethod = name == L.global.tmname[i];
if (isMetamethod) {
if (classobject.instancemetatable == null) {
classobject.instancemetatable = try ltable.Hnew(L, 0, 1);
lgc.Cobjbarrier(L, @ptrCast(@alignCast(classobject)), @ptrCast(@alignCast(classobject.instancemetatable.?)));
}
const dest = try ltable.Hsetstr(L, classobject.instancemetatable.?, name);
dest.setobj(L, value);
lgc.Cbarrier(L, @ptrCast(@alignCast(classobject.instancemetatable.?)), value);
}
}
extern "c" fn zig_luaR_createobject(L: *lua.State) c_int;
pub fn Rfreeclass(L: *lua.State, classobject: *lobject.LuauClass, page: *lmem.lua_Page) void {
lmem.Mfreearray(L, lobject.TValue, classobject.staticmembers, classobject.numberofallmembers - classobject.numberofinstancemembers, classobject.header.memcat);
lmem.Mfreearray(L, *lobject.TString, classobject.offsettomember, classobject.numberofallmembers, classobject.header.memcat);
lmem.Mfreegco(L, @ptrCast(@alignCast(classobject)), @sizeOf(lobject.LuauClass), classobject.header.memcat, page);
}
pub fn Rfreeobject(L: *lua.State, classinstance: *lobject.LuauObject, page: *lmem.lua_Page) void {
lmem.Mfreearray(L, lobject.TValue, classinstance.members, classinstance.numberofmembers, classinstance.header.memcat);
lmem.Mfreegco(L, @ptrCast(@alignCast(classinstance)), @sizeOf(lobject.LuauObject), classinstance.header.memcat, page);
}
+7
View File
@@ -0,0 +1,7 @@
const config = @import("luaconf.zig");
///
/// type for virtual-machine instructions
/// must be an unsigned with (at least) 4 bytes (see details in lopcodes.h)
///
pub const Instruction = u32;
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_coroutine(@ptrCast(L));
}
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_debug(@ptrCast(L));
}
+146
View File
@@ -0,0 +1,146 @@
const c = @import("c");
const std = @import("std");
const build_config = @import("config");
const lua = @import("lua.zig");
const ltm = @import("ltm.zig");
const lstate = @import("lstate.zig");
const lobject = @import("lobject.zig");
const Errorset = @import("errorset.zig");
pub const MEMERRMSG = "not enough memory";
pub const ERRERRMSG = "error in error handling";
pub fn currentpc(ci: *lstate.CallInfo) usize {
if (ci.savedpc.inst) |pc| {
return (@divExact(@intFromPtr(pc) - @intFromPtr(ci.ci_func().d.l.p.code), @sizeOf(u32))) - 1;
} else return 0;
}
pub fn currentline(ci: *lstate.CallInfo) i32 {
std.debug.assert(ci.isLua());
return Ggetline(ci.ci_func().d.l.p, currentpc(ci));
}
pub fn getluaproto(ci: *lstate.CallInfo) ?*lobject.Proto {
return if (ci.isLua())
ci.ci_func().d.l.p
else
null;
}
pub inline fn getargument(L: *lua.State, level: i32, n: i32) bool {
return c.lua_getargument(@ptrCast(L), level, n) != 0;
}
pub inline fn getlocal(L: *lua.State, level: i32, n: i32) ?[:0]const u8 {
const name = c.lua_getlocal(@ptrCast(L), level, n);
if (name != null)
return std.mem.span(name);
return null;
}
pub inline fn setlocal(L: *lua.State, level: i32, n: i32) ?[:0]const u8 {
const name = c.lua_setlocal(@ptrCast(L), level, n);
if (name != null)
return std.mem.span(name);
return null;
}
pub inline fn stackdepth(L: *lua.State) usize {
return L.ci.? - L.base_ci.?;
}
pub fn getinfo(L: *lua.State, level: i32, what: [:0]const u8, ar: *lua.Debug) bool {
var info: c.lua_Debug = undefined;
if (c.lua_getinfo(@ptrCast(L), level, what.ptr, &info) == 0)
return false;
ar.fromLua(info, what);
return true;
}
fn pusherror(L: *lua.State, msg: [:0]const u8) Errorset.Memory!void {
const ci = L.ci.?;
if (ci.isLua()) {
const source = getluaproto(ci).?.source;
// var chunkbuf: [lua.config.IDSIZE]u8 = undefined;
const line = currentline(ci);
if (source) |src| {
try L.pushfstring("{s}:{d}: {s}", .{ std.mem.span(src.getstr()), line, msg });
} else {
try L.pushfstring(":{d}: {s}", .{ line, msg });
}
} else {
try L.pushstring(msg);
}
}
pub fn GrunerrorL(L: *lua.State, comptime fmt: []const u8, args: anytype) Errorset.Table!noreturn {
try L.pushvfstring(fmt, args);
try L.rawcheckstack(1);
L.raiseerror();
}
pub fn Ggetline(p: *lobject.Proto, pc: usize) i32 {
std.debug.assert(pc >= 0 and pc < p.sizecode);
if (p.lineinfo) |lineinfo| {
return p.abslineinfo.?[pc >> @intCast(p.linegaplog2)] + lineinfo[pc];
} else return 0;
}
pub fn Gisnative(L: *lua.State, level: usize) bool {
if (level >= L.ci.?[0].sub(@ptrCast(L.base_ci.?)))
return false;
const ci = L.ci.? - level;
return (ci[0].flags & lstate.CALLINFO_NATIVE) != 0;
}
pub fn singlestep(L: *lua.State, enabled: bool) void {
if (comptime !build_config.use_zig_backend) {
return c.lua_singlestep(@ptrCast(L), if (enabled) 1 else 0);
}
L.singlestep_on = enabled;
}
pub inline fn breakpoint(L: *lua.State, funcindex: i32, line: i32, enabled: bool) i32 {
return c.lua_breakpoint(@ptrCast(L), funcindex, line, if (enabled) 1 else 0);
}
pub fn getcoverage(
L: *lua.State,
comptime T: type,
context: *T,
funcindex: i32,
comptime callback: *const fn (
ctx: *T,
func: ?[:0]const u8,
line: i32,
depth: i32,
hits: []const i32,
) void,
) void {
c.lua_getcoverage(@ptrCast(L), funcindex, context, struct {
fn inner(
ctx: ?*anyopaque,
func: [*c]const u8,
line: c_int,
depth: c_int,
hits: [*c]const c_int,
size: usize,
) callconv(.c) void {
@call(.always_inline, callback, .{
@as(*T, @ptrCast(@alignCast(ctx.?))),
if (func != null) std.mem.span(func) else null,
line,
depth,
hits[0..size],
});
}
}.inner);
}
pub inline fn debugtrace(L: *lua.State) [:0]const u8 {
return std.mem.span(c.lua_debugtrace(@ptrCast(L)));
}
+138
View File
@@ -0,0 +1,138 @@
const c = @import("c");
const std = @import("std");
const lmem = @import("lmem.zig");
const lstate = @import("lstate.zig");
const lua = @import("lua.zig");
const ldebug = @import("ldebug.zig");
const lobject = @import("lobject.zig");
const Errorset = @import("errorset.zig");
extern "c" fn zig_luau_luaD_throw(L: *lua.State, errcode: i32) noreturn;
pub const MAX_STACK_SIZE = (1024 / @sizeOf(lobject.TValue)) * 1024 * 1024;
pub inline fn throw(L: *lua.State, errcode: lua.Status) noreturn {
zig_luau_luaD_throw(L, @intFromEnum(errcode));
}
pub inline fn getgrownstacksize(L: *lua.State, n: usize) usize {
return if (n <= L.stacksize) 2 * @as(u32, @intCast(L.stacksize)) else @as(u32, @intCast(L.stacksize)) + n;
}
pub inline fn stacklimitreached(L: *lua.State, n: usize) bool {
return @intFromPtr(L.stack_last) - @intFromPtr(L.top) <= n * @sizeOf(lobject.TValue);
}
pub inline fn Dcheckstackfornewci(L: *lua.State, n: usize) Errorset.Memory!void {
if (@intFromPtr(L.stack_last) - @intFromPtr(L.top) < n * @sizeOf(lobject.TValue))
try Dreallocstack(L, getgrownstacksize(L, n), true)
else
try Dreallocstack(L, @as(u32, @intCast(L.stacksize - lstate.EXTRA_STACK)), true);
}
pub inline fn Dcheckstack(L: *lua.State, n: usize) Errorset.Memory!void {
if (@intFromPtr(L.stack_last) - @intFromPtr(L.top) < n * @sizeOf(lobject.TValue))
try Dgrowstack(L, n)
else
try Dreallocstack(L, @as(u32, @intCast(L.stacksize - lstate.EXTRA_STACK)), false);
}
pub inline fn incr_top(L: *lua.State) Errorset.Memory!void {
try Dcheckstack(L, 1);
L.top += 1;
}
pub inline fn expandstacklimit(L: *lua.State, p: *lobject.TValue) void {
std.debug.assert(@intFromPtr(p) <= @intFromPtr(L.stack_last));
if (@intFromPtr(L.ci.?[0].top) < @intFromPtr(p))
L.ci.?[0].top = @ptrCast(p);
}
fn correctstack(L: *lua.State, oldstack: [*]lobject.TValue) void {
L.top = L.stack + (L.top - oldstack);
var up: ?*lobject.UpVal = L.openupval;
while (up) |uv| : (up = uv.u.open.threadnext)
uv.v = @ptrCast(L.stack + (@as([*]lobject.TValue, @ptrCast(uv.v)) - oldstack));
var ci = L.base_ci.?;
const top_bound = @intFromPtr(L.ci);
while (@intFromPtr(ci) <= top_bound) : (ci += 1) {
ci[0].top = L.stack + (ci[0].top - oldstack);
ci[0].base = L.stack + (ci[0].base - oldstack);
ci[0].func = L.stack + (ci[0].func - oldstack);
}
L.base = L.stack + (L.base - oldstack);
}
pub fn Dreallocstack(L: *lua.State, newsize: usize, fornewci: bool) Errorset.Memory!void {
// throw 'out of memory' error because space for a custom error message cannot be guaranteed here
if (newsize > MAX_STACK_SIZE) {
// reallocation was performed to setup a new CallInfo frame, which we have to remove
if (fornewci) {
const cip = L.ci.? - 1;
L.ci = cip;
L.base = cip[0].base;
L.top = cip[0].top;
}
return error.OutOfMemory;
}
const realsize = newsize + lstate.EXTRA_STACK;
if (L.stacksize == realsize) {
// fast path: skip reallocation
return;
}
const oldstack = L.stack;
std.debug.assert(L.stack_last - L.stack == L.stacksize - lstate.EXTRA_STACK);
L.stack = try lmem.Mreallocarray(L, lobject.TValue, L.stack, @intCast(L.stacksize), realsize, L.header.memcat) orelse unreachable;
const newstack = L.stack;
var i: usize = @intCast(L.stacksize);
while (i < realsize) : (i += 1)
newstack[i].setnilvalue();
L.stacksize = @intCast(realsize);
L.stack_last = newstack + newsize;
correctstack(L, oldstack);
}
pub fn DreallocCI(L: *lua.State, newsize: usize) Errorset.Memory!void {
const oldci = L.base_ci;
L.base_ci = try lmem.Mreallocarray(L, lstate.CallInfo, L.base_ci, @intCast(L.size_ci), newsize, L.header.memcat);
L.size_ci = @intCast(newsize);
L.ci = @ptrFromInt((@intFromPtr(L.ci) - @intFromPtr(oldci)) + @intFromPtr(L.base_ci));
L.end_ci = L.base_ci.? + newsize - 1;
}
pub fn Dgrowstack(L: *lua.State, n: usize) Errorset.Memory!void {
try Dreallocstack(L, getgrownstacksize(L, n), false);
}
pub inline fn @"resume"(L: *lua.State, from: ?*lua.State, narg: i32) lua.Status {
return @enumFromInt(c.lua_resume(@ptrCast(L), @ptrCast(from), narg));
}
pub inline fn resumeerror(L: *lua.State, from: ?*lua.State) lua.Status {
return @enumFromInt(c.lua_resumeerror(@ptrCast(L), @ptrCast(from)));
}
pub fn yield(L: *lua.State, nresults: u32) !i32 {
if (L.nCcalls > L.baseCcalls)
try ldebug.GrunerrorL(L, "attempt to yield across metamethod/C-call boundary", .{});
L.base = L.top - nresults;
L.curr_status = @intFromEnum(lua.Status.Yield);
return -1;
}
pub fn @"break"(L: *lua.State) !i32 {
if (L.nCcalls > L.baseCcalls)
try ldebug.GrunerrorL(L, "attempt to yield across metamethod/C-call boundary", .{});
L.curr_status = @intFromEnum(lua.Status.Break);
return -1;
}
pub fn isyieldable(L: *lua.State) bool {
return L.nCcalls <= L.baseCcalls;
}
+164
View File
@@ -0,0 +1,164 @@
const std = @import("std");
const lua = @import("lua.zig");
const lgc = @import("lgc.zig");
const lmem = @import("lmem.zig");
const lstate = @import("lstate.zig");
const lcommon = @import("lcommon.zig");
const lobject = @import("lobject.zig");
pub inline fn sizeCclosure(n: u8) usize {
return @offsetOf(lobject.Closure, "d") + @offsetOf(lobject.Closure.ValueUnion.C, "upvals") + (@sizeOf(lobject.TValue) * @as(usize, @intCast(n)));
}
pub inline fn sizeLclosure(n: u8) usize {
return @offsetOf(lobject.Closure, "d") + @offsetOf(lobject.Closure.ValueUnion.L, "uprefs") + (@sizeOf(lobject.TValue) * @as(usize, @intCast(n)));
}
pub inline fn getproto(cl: *lobject.Closure) *lobject.Proto {
return cl.d.l.p;
}
pub fn Fnewproto(L: *lua.State) !*lobject.Proto {
const f = try lmem.Mnewgco(L, lobject.Proto, @sizeOf(lobject.Proto), L.activememcat);
lgc.Cinit(L, @ptrCast(@alignCast(f)), @intFromEnum(lua.Type.Proto));
f.nups = 0;
f.numparams = 0;
f.is_vararg = 0;
f.maxstacksize = 0;
f.flags = 0;
f.k = null;
f.code = null;
f.p = null;
f.codeentry = null;
f.execdata = null;
f.exectarget = 0;
f.lineinfo = null;
f.abslineinfo = null;
f.locvars = null;
f.upvalues = null;
f.source = null;
f.debugname = null;
f.debuginsn = null;
f.typeinfo = null;
f.userdata = null;
f.gclist = null;
f.sizecode = 0;
f.sizep = 0;
f.sizelocvars = 0;
f.sizeupvalues = 0;
f.sizek = 0;
f.sizelineinfo = 0;
f.linegaplog2 = 0;
f.linedefined = 0;
f.bytecodeid = 0;
f.sizetypeinfo = 0;
f.feedbackvec = null;
f.feedbackvecsize = 0;
f.funid = 0;
f.optimized = null;
f.deoptimized = null;
f.cost = 0;
return f;
}
pub fn FnewLclosure(L: *lua.State, nelems: u8, e: *lobject.LuaTable, p: *lobject.Proto) !*lobject.Closure {
const c = try lmem.Mnewgco(L, lobject.Closure, sizeCclosure(nelems), L.activememcat);
lgc.Cinit(L, @ptrCast(@alignCast(c)), @intFromEnum(lua.Type.Function));
c.isC = 0;
c.env = e;
c.nupvalues = nelems;
c.stacksize = p.maxstacksize;
c.preload = 0;
c.d.l.p = p;
for (0..nelems) |i|
c.d.l.upreferences()[i].setnilvalue();
return c;
}
pub fn FnewCclosure(L: *lua.State, nelems: u8, e: *lobject.LuaTable) !*lobject.Closure {
const c = try lmem.Mnewgco(L, lobject.Closure, sizeCclosure(nelems), L.activememcat);
lgc.Cinit(L, @ptrCast(@alignCast(c)), @intFromEnum(lua.Type.Function));
c.isC = 1;
c.env = e;
c.nupvalues = nelems;
c.stacksize = lua.config.MINSTACK;
c.preload = 0;
c.d.c.f = null;
c.d.c.cont = null;
c.d.c.debugname = null;
return c;
}
pub fn Ffreeupval(L: *lua.State, uv: *lobject.UpVal, page: *lmem.lua_Page) void {
lmem.Mfreegco(L, uv.obj2gco(), @sizeOf(lobject.UpVal), uv.header.memcat, page); // free upvalue
}
pub fn Fclose(L: *lua.State, level: *lobject.TValue) void {
const g = L.global;
var uv: ?*lobject.UpVal = L.openupval;
const lvl_num = @intFromPtr(level);
while (uv != null and @intFromPtr(uv.?.v) >= lvl_num) : (uv = L.openupval) {
const u = uv.?;
const o: *lstate.GCObject = u.obj2gco();
std.debug.assert(!lgc.isblack(o) and u.upisopen());
std.debug.assert(!lgc.isdead(g, o));
// unlink value *before* closing it since value storage overlaps
L.openupval = u.u.open.threadnext;
Fcloseupval(L, u, false);
}
}
pub fn Fcloseupval(L: *lua.State, uv: *lobject.UpVal, dead: bool) void {
// unlink value from all lists *before* closing it since value storage overlaps
std.debug.assert(uv.u.open.next.?.u.open.prev == uv and uv.u.open.prev.?.u.open.next == uv);
uv.u.open.next.?.u.open.prev = uv.u.open.prev;
uv.u.open.prev.?.u.open.next = uv.u.open.next;
if (dead)
return;
uv.u.value.setobj(L, uv.v);
uv.v = &uv.u.value;
lgc.Cupvalclosed(L, uv);
}
pub fn Ffreeproto(L: *lua.State, f: *lobject.Proto, page: *lmem.lua_Page) void {
lmem.Mfreearray(L, lcommon.Instruction, f.code, @intCast(f.sizecode), f.header.memcat);
lmem.Mfreearray(L, ?*lobject.Proto, f.p, @intCast(f.sizep), f.header.memcat);
lmem.Mfreearray(L, lobject.TValue, f.k, @intCast(f.sizek), f.header.memcat);
if (f.lineinfo) |li|
lmem.Mfreearray(L, u8, li, @intCast(f.sizelineinfo), f.header.memcat);
lmem.Mfreearray(L, lobject.LocVar, f.locvars, @intCast(f.sizelocvars), f.header.memcat);
lmem.Mfreearray(L, ?*lobject.TString, f.upvalues, @intCast(f.sizeupvalues), f.header.memcat);
if (f.debuginsn) |di|
lmem.Mfreearray(L, u8, di, @intCast(f.sizecode), f.header.memcat);
if (f.execdata) |_|
L.global.ecb.destroy.?(L, @ptrCast(f));
if (f.typeinfo) |ti|
lmem.Mfreearray(L, u8, ti, @intCast(f.sizetypeinfo), f.header.memcat);
if (f.feedbackvec) |fv|
lmem.Mfreearray(L, lobject.FeedbackVectorSlot, fv, f.feedbackvecsize, f.header.memcat);
lmem.Mfreegco(L, f.obj2gco(), @sizeOf(lobject.Proto), f.header.memcat, page);
}
pub fn Ffreeclosure(L: *lua.State, c: *lobject.Closure, page: *lmem.lua_Page) void {
const size = if (c.isC != 0) sizeCclosure(c.nupvalues) else sizeLclosure(c.nupvalues);
lmem.Mfreegco(L, c.obj2gco(), size, c.header.memcat, page);
}
+1129
View File
File diff suppressed because it is too large Load Diff
+223
View File
@@ -0,0 +1,223 @@
const std = @import("std");
const lgc = @import("lgc.zig");
const lua = @import("lua.zig");
const lmem = @import("lmem.zig");
const lstate = @import("lstate.zig");
const ludata = @import("ludata.zig");
const lobject = @import("lobject.zig");
fn validateobjref(g: *const lstate.global_State, f: *lstate.GCObject, t: *lstate.GCObject) void {
std.debug.assert(!lgc.isdead(g, t));
if (lgc.keepinvariant(g)) {
// basic incremental invariant: black can't point to white
std.debug.assert(!(lgc.isblack(f) and lgc.iswhite(t)));
}
}
fn validateref(g: *const lstate.global_State, f: *lstate.GCObject, v: *lobject.TValue) void {
if (v.iscollectable()) {
std.debug.assert(v.ttype() == v.gcvalue().gch.header.tt);
validateobjref(g, f, v.gcvalue());
}
}
fn validatetable(g: *const lstate.global_State, h: *lobject.LuaTable) void {
const sizenode = @as(u32, 1) << @intCast(h.lsizenode);
std.debug.assert(h.bound.lastfree <= sizenode);
if (h.metatable) |mt|
validateobjref(g, h.obj2gco(), mt.obj2gco());
for (0..@intCast(h.sizearray)) |i|
validateref(g, h.obj2gco(), &h.array.?[i]);
for (0..sizenode) |i| {
const n = &h.node[i];
std.debug.assert(n.gkey().ttype() != @intFromEnum(lua.Type.Deadkey) or n.gval().ttisnil());
std.debug.assert(@as(isize, @intCast(i)) + n.gnext() >= 0 and @as(isize, @intCast(i)) + n.gnext() < sizenode);
if (!n.gval().ttisnil()) {
var k: lobject.TValue = undefined;
k.tt = n.gkey().ttype();
k.value = n.gkey().value;
validateref(g, h.obj2gco(), &k);
validateref(g, h.obj2gco(), n.gval());
}
}
}
fn validateclosure(g: *const lstate.global_State, cl: *lobject.Closure) void {
validateobjref(g, cl.obj2gco(), cl.env.obj2gco());
if (cl.isC != 0) {
for (cl.d.c.upvalues()[0..cl.nupvalues]) |*upval|
validateref(g, cl.obj2gco(), upval);
} else {
std.debug.assert(cl.nupvalues == cl.d.l.p.nups);
validateobjref(g, cl.obj2gco(), cl.d.l.p.obj2gco());
for (cl.d.l.upreferences()[0..cl.nupvalues]) |*upref|
validateref(g, cl.obj2gco(), upref);
}
}
fn validatestack(g: *const lstate.global_State, l: *lua.State) void {
validateobjref(g, @ptrCast(@alignCast(l)), l.gt.?.obj2gco());
for (l.base_ci.?[0 .. (l.ci.? - l.base_ci.?) + 1]) |*ci| {
std.debug.assert(@intFromPtr(l.stack) <= @intFromPtr(ci.base));
std.debug.assert(@intFromPtr(ci.func) <= @intFromPtr(ci.base) and @intFromPtr(ci.base) <= @intFromPtr(ci.top));
std.debug.assert(@intFromPtr(ci.top) <= @intFromPtr(l.stack_last));
}
// note: stack refs can violate gc invariant so we only check for liveness
for (l.stack[0..(l.top - l.stack)]) |*o|
o.checkliveness(g);
if (l.namecall) |nc|
validateobjref(g, @ptrCast(@alignCast(l)), nc.obj2gco());
var upval: ?*lobject.UpVal = l.openupval;
while (upval) |uv| : (upval = uv.u.open.threadnext) {
std.debug.assert(uv.header.tt == @intFromEnum(lua.Type.UpVal));
std.debug.assert(uv.upisopen());
std.debug.assert(uv.u.open.next.?.u.open.prev == uv and uv.u.open.prev.?.u.open.next == uv);
std.debug.assert(!lgc.isblack(uv.obj2gco()));
}
}
fn validateproto(g: *const lstate.global_State, f: *lobject.Proto) void {
if (f.source) |src|
validateobjref(g, f.obj2gco(), src.obj2gco());
if (f.debugname) |name|
validateobjref(g, f.obj2gco(), name.obj2gco());
for (0..@intCast(f.sizek)) |i|
validateref(g, f.obj2gco(), &f.k.?[i]);
for (0..@intCast(f.sizeupvalues)) |i|
if (f.upvalues.?[i]) |uv|
validateobjref(g, f.obj2gco(), uv.obj2gco());
for (0..@intCast(f.sizep)) |i|
if (f.p.?[i]) |proto|
validateobjref(g, f.obj2gco(), proto.obj2gco());
for (0..@intCast(f.sizelocvars)) |i|
if (f.locvars.?[i].varname) |varname|
validateobjref(g, f.obj2gco(), varname.obj2gco());
}
fn validateclass(g: *const lstate.global_State, lco: *lobject.LuauClass) void {
const obj = lco.obj2gco();
validateobjref(g, obj, lco.name.obj2gco());
validateobjref(g, obj, lco.memberstooffset.obj2gco());
for (0..lco.numberofallmembers) |i| {
validateobjref(g, obj, lco.offsettomember[i].obj2gco());
if (i >= lco.numberofinstancemembers)
validateref(g, obj, &lco.staticmembers[i - @as(u32, @intCast(lco.numberofinstancemembers))]);
}
validateobjref(g, obj, lco.metatable.obj2gco());
if (lco.instancemetatable) |mt|
validateobjref(g, obj, mt.obj2gco());
}
fn validateobject(g: *const lstate.global_State, inst: *lobject.LuauObject) void {
const obj = inst.obj2gco();
validateobjref(g, obj, inst.lclass.obj2gco());
for (0..inst.numberofmembers) |i|
validateref(g, obj, &inst.members[i]);
}
fn validateobj(g: *const lstate.global_State, o: *lstate.GCObject) void {
if (lgc.isdead(g, o)) {
std.debug.assert(g.gcstate == lgc.GCSsweep);
return;
}
switch (o.gch.header.tt) {
@intFromEnum(lua.Type.String), @intFromEnum(lua.Type.Buffer) => {},
@intFromEnum(lua.Type.Table) => validatetable(g, o.toh()),
@intFromEnum(lua.Type.Function) => validateclosure(g, o.tocl()),
@intFromEnum(lua.Type.Userdata) => if (o.tou().metatable) |mt|
validateobjref(g, o, mt.obj2gco()),
@intFromEnum(lua.Type.Thread) => validatestack(g, o.toth()),
@intFromEnum(lua.Type.Proto) => validateproto(g, o.top()),
@intFromEnum(lua.Type.UpVal) => validateref(g, o, o.touv().v),
@intFromEnum(lua.Type.Class) => validateclass(g, o.toclass()),
@intFromEnum(lua.Type.Object) => validateobject(g, o.toobject()),
else => unreachable,
}
}
fn validategraylist(g: *const lstate.global_State, obj: ?*lstate.GCObject) void {
if (!lgc.keepinvariant(g))
return;
var so: ?*lstate.GCObject = obj;
while (so) |o| {
std.debug.assert(lgc.isgray(o));
switch (o.gch.header.tt) {
@intFromEnum(lua.Type.Table) => so = o.toh().gclist,
@intFromEnum(lua.Type.Function) => so = o.tocl().gclist,
@intFromEnum(lua.Type.Thread) => so = o.toth().gclist,
@intFromEnum(lua.Type.Class) => so = o.toclass().gclist,
@intFromEnum(lua.Type.Object) => so = o.toobject().gclist,
@intFromEnum(lua.Type.Proto) => so = o.top().gclist,
else => unreachable,
}
}
}
fn validategco(L: *lua.State, _: ?*lmem.lua_Page, gco: *lstate.GCObject) bool {
const g = L.global;
validateobj(g, gco);
return false;
}
pub fn Cvalidate(L: *lua.State) void {
const g = L.global;
std.debug.assert(!lgc.isdead(g, @ptrCast(@alignCast(g.mainthread))));
g.registry.checkliveness(g);
for (0..lua.Type.T_COUNT) |i|
if (g.mt[i]) |mt|
std.debug.assert(!lgc.isdead(g, mt.obj2gco()));
for (0..lua.config.UTAG_LIMIT) |i|
if (g.udatamt[i]) |mt|
std.debug.assert(!lgc.isdead(g, mt.obj2gco()));
for (0..ludata.UTAG_INTERNAL_LIMIT) |i| {
g.udatadirect[i].indextm.checkliveness(g);
g.udatadirect[i].newindextm.checkliveness(g);
g.udatadirect[i].namecalltm.checkliveness(g);
if (g.udatadirectfields[i]) |f|
std.debug.assert(!lgc.isdead(g, f.obj2gco()));
}
validategraylist(g, g.weak);
validategraylist(g, g.gray);
validategraylist(g, g.grayagain);
_ = validategco(@ptrCast(L), null, @ptrCast(@alignCast(g.mainthread)));
lmem.Mvisitgco(L, *lua.State, L, validategco);
var upval: ?*lobject.UpVal = g.uvhead.u.open.next.?;
while (upval != &g.uvhead) : (upval = upval.?.u.open.next) {
std.debug.assert(upval.?.header.tt == @intFromEnum(lua.Type.UpVal));
std.debug.assert(upval.?.upisopen());
std.debug.assert(upval.?.u.open.next.?.u.open.prev == upval and upval.?.u.open.prev.?.u.open.next == upval);
std.debug.assert(!lgc.isblack(upval.?.obj2gco()));
}
}
+16
View File
@@ -0,0 +1,16 @@
const c = @import("c");
const std = @import("std");
const lua = @import("lua.zig");
pub fn Lopenlibs(L: *lua.State) !void {
c.luaL_openlibs(@ptrCast(L));
}
pub fn Lsandbox(L: *lua.State) !void {
c.luaL_sandbox(@ptrCast(L));
}
pub fn Lsandboxthread(L: *lua.State) !void {
c.luaL_sandboxthread(@ptrCast(L));
}
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_math(@ptrCast(L));
}
+720
View File
@@ -0,0 +1,720 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
const std = @import("std");
const builtin = @import("builtin");
const lua = @import("lua.zig");
const lobject = @import("lobject.zig");
const lstate = @import("lstate.zig");
const ldo = @import("ldo.zig");
const ldebug = @import("ldebug.zig");
const Errorset = @import("errorset.zig");
const Error = Errorset.Memory;
//
// Luau heap uses a size-segregated page structure, with individual pages and large allocations
// allocated using system heap (via frealloc callback).
//
// frealloc callback serves as a general, if slow, allocation callback that can allocate, free or
// resize allocations:
//
// void* frealloc(void* ud, void* ptr, size_t oldsize, size_t newsize);
//
// frealloc(ud, NULL, 0, x) creates a new block of size x
// frealloc(ud, p, x, 0) frees the block p (must return NULL)
// frealloc(ud, NULL, 0, 0) does nothing, equivalent to free(NULL)
//
// frealloc returns NULL if it cannot create or reallocate the area
// (any reallocation to an equal or smaller size cannot fail!)
//
// On top of this, Luau implements heap storage which is split into two types of allocations:
//
// - GCO, short for "garbage collected objects"
// - other objects (for example, arrays stored inside table objects)
//
// The heap layout for these two allocation types is a bit different.
//
// All GCO are allocated in pages, which is a block of memory of ~16K in size that has a page header
// (lua_Page). Each page contains 1..N blocks of the same size, where N is selected to fill the page
// completely. This amortizes the allocation cost and increases locality. Each GCO block starts with
// the GC header (GCheader) which contains the object type, mark bits and other GC metadata. If the
// GCO block is free (not used), then it must have the type set to TNIL; in this case the block can
// be part of the per-page free list, the link for that list is stored after the header (freegcolink).
//
// Importantly, the GCO block doesn't have any back references to the page it's allocated in, so it's
// impossible to free it in isolation - GCO blocks are freed by sweeping the pages they belong to,
// using luaM_freegco which must specify the page; this is called by page sweeper that traverses the
// entire page's worth of objects. For this reason it's also important that freed GCO blocks keep the
// GC header intact and accessible (with type = NIL) so that the sweeper can access it.
//
// Some GCOs are too large to fit in a 16K page without excessive fragmentation (the size threshold is
// currently 512 bytes); in this case, we allocate a dedicated small page with just a single block's worth
// storage space, but that requires allocating an extra page header. In effect large GCOs are a little bit
// less memory efficient, but this allows us to uniformly sweep small and large GCOs using page lists.
//
// All GCO pages are linked in a large intrusive linked list (global_State::allgcopages). Additionally,
// for each block size there's a page free list that contains pages that have at least one free block
// (global_State::freegcopages). This free list is used to make sure object allocation is O(1).
//
// When LUAU_ASSERTENABLED is enabled, all non-GCO pages are also linked in a list (global_State::allpages).
// Because this list is not strictly required for runtime operations, it is only tracked for the purposes of
// debugging. While overhead of linking those pages together is very small, unnecessary operations are avoided.
//
// Compared to GCOs, regular allocations have two important differences: they can be freed in isolation,
// and they don't start with a GC header. Because of this, each allocation is prefixed with block metadata,
// which contains the pointer to the page for allocated blocks, and the pointer to the next free block
// inside the page for freed blocks.
// For regular allocations that are too large to fit in a page (using the same threshold of 512 bytes),
// we don't allocate a separate page, instead simply using frealloc to allocate a vanilla block of memory.
//
// Just like GCO pages, we store a page free list (global_State::freepages) that allows O(1) allocation;
// there is no global list for non-GCO pages since we never need to traverse them directly.
//
// In both cases, we pick the page by computing the size class from the block size which rounds the block
// size up to reduce the chance that we'll allocate pages that have very few allocated blocks. The size
// class strategy is determined by SizeClassConfig constructor.
//
// Note that when the last block in a page is freed, we immediately free the page with frealloc - the
// memory manager doesn't currently attempt to keep unused memory around. This can result in excessive
// allocation traffic and can be mitigated by adding a page cache in the future.
//
// For both GCO and non-GCO pages, the per-page block allocation combines bump pointer style allocation
// (lua_Page::freeNext) and per-page free list (lua_Page::freeList). We use the bump allocator to allocate
// the contents of the page, and the free list for further reuse; this allows shorter page setup times
// which results in less variance between allocation cost, as well as tighter sweep bounds for newly
// allocated pages.
//
//
// The sizes of most Luau objects aren't crucial for code correctness, but they are crucial for memory efficiency
// To prevent some of them accidentally growing and us losing memory without realizing it, we're going to lock
// the sizes of all critical structures down.
//
fn ABISWITCH(b64: comptime_int, b32: comptime_int) comptime_int {
if (@sizeOf(*anyopaque) == 8)
return b64
else
return b32;
}
comptime {
if (lua.config.VECTOR_SIZE == 4) {
std.debug.assert(@sizeOf(lobject.TValue) == ABISWITCH(24, 24)); // size mismatch for value
std.debug.assert(@sizeOf(lobject.LuaNode) == ABISWITCH(48, 48)); // size mismatch for table entry
} else {
std.debug.assert(@sizeOf(lobject.TValue) == ABISWITCH(16, 16)); // size mismatch for value
std.debug.assert(@sizeOf(lobject.LuaNode) == ABISWITCH(32, 32)); // size mismatch for table entry
}
std.debug.assert(@offsetOf(lobject.TString, "data") == ABISWITCH(24, 20)); // size mismatch for string header
std.debug.assert(@sizeOf(lobject.LuaTable) == ABISWITCH(48, 32)); // size mismatch for table header
std.debug.assert(@offsetOf(lobject.Buffer, "data") == ABISWITCH(8, 8)); // size mismatch for buffer header
// The userdata is designed to provide 16 byte alignment for 16 byte and larger userdata sizes
std.debug.assert(@offsetOf(lobject.Udata, "data") == 16); // data must be at precise offset provide proper alignment
}
const kSizeClasses = lua.config.SIZECLASSES;
// Controls the number of entries in SizeClassConfig and define the maximum possible paged allocation size
// Modifications require updates the SizeClassConfig initialization
const kMaxSmallSize = 1024;
// Effective limit on object size to use paged allocation
// Can be modified without additional changes to code, provided it is smaller or equal to kMaxSmallSize
const kMaxSmallSizeUsed = 1024;
const kLargePageThreshold = 512; // larger pages are used for objects larger than this size to fit more of them into a page
// constant factor to reduce our page sizes by, to increase the chances that pages we allocate will
// allow external allocators to allocate them without wasting space due to rounding introduced by their heap meta data
const kExternalAllocatorMetaDataReduction = 24;
const kSmallPageSize = 16 * 1024 - kExternalAllocatorMetaDataReduction;
const kLargePageSize = 32 * 1024 - kExternalAllocatorMetaDataReduction;
const kBlockHeader = if (@sizeOf(f64) > @sizeOf(*anyopaque)) @sizeOf(f64) else @sizeOf(*anyopaque); // suitable for aligning double & void* on all platforms
const kGCOLinkOffset = (@sizeOf(lobject.GCheader) + @sizeOf(*anyopaque) - 1) & ~@as(usize, @sizeOf(*anyopaque) - 1); // GCO pages contain freelist links after the GC header
const SizeClassConfig = extern struct {
const values = generateSizeOfClass();
sizeOfClass: [kSizeClasses]c_int = values[0],
classForSize: [kMaxSmallSize + 1]i8 = values[1],
classCount: c_int = values[2],
fn generateSizeOfClass() struct { [kSizeClasses]c_int, [kMaxSmallSize + 1]i8, comptime_int } {
var classCount: comptime_int = 0;
var sizeOfClass: [kSizeClasses]c_int = [_]c_int{0} ** kSizeClasses;
var classForSize: [kMaxSmallSize + 1]i8 = [_]i8{-1} ** (kMaxSmallSize + 1);
// we use a progressive size class scheme:
// - all size classes are aligned by 8b to satisfy pointer alignment requirements
// - we first allocate sizes classes in multiples of 8
// - after the first cutoff we allocate size classes in multiples of 16
// - after the second cutoff we allocate size classes in multiples of 32
// - after the third cutoff we allocate size classes in multiples of 64
// this balances internal fragmentation vs external fragmentation
for ((8 / 8)..(64 / 8)) |i| {
sizeOfClass[classCount] = i * 8;
classCount += 1;
}
for ((64 / 16)..(256 / 16)) |i| {
sizeOfClass[classCount] = i * 16;
classCount += 1;
}
for ((256 / 32)..(512 / 32)) |i| {
sizeOfClass[classCount] = i * 32;
classCount += 1;
}
for ((512 / 64)..(1024 / 64) + 1) |i| {
sizeOfClass[classCount] = i * 64;
classCount += 1;
}
std.debug.assert(classCount <= kSizeClasses);
// fill the lookup table for all classes
for (0..classCount) |klass|
classForSize[sizeOfClass[klass]] = @as(i8, @intCast(klass));
// fill the gaps in lookup table
{
var size = kMaxSmallSize - 1;
@setEvalBranchQuota(kMaxSmallSize + 128);
while (size >= 0) : (size -= 1) {
if (classForSize[size] < 0)
classForSize[size] = classForSize[size + 1];
}
}
return .{ sizeOfClass, classForSize, classCount };
}
};
const kSizeClassConfig: SizeClassConfig = .{};
// size class for a block of size sz; returns -1 for size=0 because empty allocations take no space
inline fn sizeclass(sz: usize) i8 {
return if (sz -% 1 < kMaxSmallSizeUsed) kSizeClassConfig.classForSize[sz] else -1;
}
inline fn debugpageset(set: *?*lua_Page) ?*?*lua_Page {
switch (comptime builtin.mode) {
.ReleaseFast, .ReleaseSmall => return null, // ReleaseFast and ReleaseSmall defines NDEBUG
else => return set, // ReleaseSafe and Debug does not define NDEBUG
}
}
// metadata for a block is stored in the first pointer of the block
inline fn metadata(block: *anyopaque) *?*anyopaque {
return @as(*?*anyopaque, @ptrCast(@alignCast(block)));
}
inline fn freegcolink(block: *anyopaque) *?*anyopaque {
return @as(*?*anyopaque, @ptrFromInt(@intFromPtr(block) + kGCOLinkOffset));
}
pub const lua_Page = extern struct {
// list of pages with free blocks
prev: ?*lua_Page = null,
next: ?*lua_Page = null,
// list of all pages
listprev: ?*lua_Page = null,
listnext: ?*lua_Page = null,
pageSize: c_int = 0, // page size in bytes, including page header
blockSize: c_int = 0, // block size in bytes, including block header
freeList: ?*anyopaque = null, // next free block in this page; linked with metadata()/freegcolink()
freeNext: c_int = 0, // next free block offset in this page
busyBlocks: c_int = 0, // number of blocks allocated out of this page
// provide additional padding based on current object size to provide 16 byte alignment of data
// later static_assert checks that this requirement is held
padding: [if (@sizeOf(*anyopaque) == 8) 8 else 12]u8 = undefined,
data: [1]u8,
};
comptime {
std.debug.assert(@offsetOf(lua_Page, "data") % 16 == 0); // data must be 16 byte aligned to provide properly aligned allocation of userdata objects
}
fn Mtoobig(L: *lua.State) noreturn {
ldebug.GrunerrorL(L, "memory allocation error: block too big");
}
fn newpage(L: *lua.State, pageset: ?*?*lua_Page, pageSize: usize, blockSize: c_int, blockCount: c_int) Error!*lua_Page {
const g = L.global;
std.debug.assert(pageSize - @offsetOf(lua_Page, "data") >= blockSize * blockCount);
const page = @as(*lua_Page, @ptrCast(@alignCast(
(g.frealloc.?)(g.ud, null, 0, pageSize) orelse return Error.OutOfMemory,
)));
// ASAN_POISON_MEMORY_REGION(...); // TODO: ASAN support
// setup page header
page.* = .{
.prev = null,
.next = null,
.listprev = null,
.listnext = null,
.pageSize = @intCast(pageSize),
.blockSize = blockSize,
// note: we start with the last block in the page and move downward
// either order would work, but that way we don't need to store the block count in the page
// additionally, GC stores objects in singly linked lists, and this way the GC lists end up in increasing pointer order
.freeList = null,
.freeNext = (blockCount - 1) * blockSize,
.busyBlocks = 0,
.data = undefined,
};
if (pageset) |set| {
page.listnext = set.*;
if (page.listnext) |next|
next.listprev = page;
set.* = page;
}
return page;
}
// this is part of a cold path in newblock and newgcoblock
// it is marked as noinline to prevent it from being inlined into those functions
// if it is inlined, then the compiler may determine those functions are "too big" to be profitably inlined, which results in reduced performance
noinline fn newclasspage(L: *lua.State, freepageset: [*]?*lua_Page, pageset: ?*?*lua_Page, sizeClass: u8, storeMetadata: bool) Error!*lua_Page {
const sizeOfClass = kSizeClassConfig.sizeOfClass[sizeClass];
const pageSize: usize = if (sizeOfClass > @as(c_int, kLargePageThreshold)) kLargePageSize else kSmallPageSize;
const blockSize = sizeOfClass + @as(c_int, if (storeMetadata) kBlockHeader else 0);
const blockCount = @divTrunc(pageSize - @offsetOf(lua_Page, "data"), @as(usize, @intCast(blockSize)));
const page = try newpage(L, pageset, pageSize, blockSize, @intCast(blockCount));
// prepend a page to page freelist (which is empty because we only ever allocate a new page when it is!)
std.debug.assert(freepageset[sizeClass] == null);
freepageset[sizeClass] = page;
return page;
}
fn freepage(L: *lua.State, pageset: ?*?*lua_Page, page: *lua_Page) void {
const g = L.global;
if (pageset) |set| {
// remove page from alllist
if (page.listnext) |next|
next.listprev = page.listprev;
if (page.listprev) |prev|
prev.listnext = page.listnext
else if (set.* == page)
set.* = page.listnext;
}
// so long
_ = (g.frealloc.?)(g.ud, @ptrCast(page), @intCast(page.pageSize), 0);
}
fn freeclasspage(L: *lua.State, freepageset: [*]?*lua_Page, pageset: ?*?*lua_Page, page: *lua_Page, sizeClass: u8) void {
// remove page from freelist
if (page.next) |next|
next.prev = page.prev;
if (page.prev) |prev|
prev.next = page.next
else if (freepageset[sizeClass] == page)
freepageset[sizeClass] = page.next;
freepage(L, pageset, page);
}
fn newblock(L: *lua.State, sizeClass: u8) Error!*anyopaque {
const g = L.global;
const page = g.freepages[sizeClass] orelse blk: {
// slow path: no page in the freelist, allocate a new one
break :blk try newclasspage(L, &g.freepages, debugpageset(&g.allpages), sizeClass, true);
};
std.debug.assert(page.prev == null);
std.debug.assert(page.freeList != null or page.freeNext >= 0);
std.debug.assert(page.blockSize == kSizeClassConfig.sizeOfClass[sizeClass] + kBlockHeader);
var block: *anyopaque = undefined;
if (page.freeNext >= 0) {
block = @ptrFromInt(@intFromPtr(&page.data) + @as(usize, @intCast(page.freeNext)));
// ASAN_UNPOISON_MEMORY_REGION(...); // TODO: ASAN support
page.freeNext -= page.blockSize;
page.busyBlocks += 1;
} else {
block = page.freeList.?;
// ASAN_UNPOISON_MEMORY_REGION(...); // TODO: ASAN support
page.freeList = metadata(block).*;
page.busyBlocks += 1;
}
// the first word in a block point back to the page
metadata(block).* = @ptrCast(@alignCast(page));
// if we allocate the last block out of a page, we need to remove it from free list
if (page.freeList == null and page.freeNext < 0) {
g.freepages[sizeClass] = page.next;
if (page.next) |next|
next.prev = null;
page.next = null;
}
// the user data is right after the metadata
return @ptrFromInt(@intFromPtr(block) + kBlockHeader);
}
fn newgcoblock(L: *lua.State, sizeClass: u8) Error!*anyopaque {
const g = L.global;
const page = g.freegcopages[sizeClass] orelse blk: {
// slow path: no page in the freelist, allocate a new one
break :blk try newclasspage(L, &g.freegcopages, &g.allgcopages, sizeClass, false);
};
std.debug.assert(page.prev == null);
std.debug.assert(page.freeList != null or page.freeNext >= 0);
std.debug.assert(page.blockSize == kSizeClassConfig.sizeOfClass[sizeClass]);
var block: *anyopaque = undefined;
if (page.freeNext >= 0) {
block = @ptrFromInt(@intFromPtr(&page.data) + @as(usize, @intCast(page.freeNext)));
// ASAN_UNPOISON_MEMORY_REGION(...); // TODO: ASAN support
page.freeNext -= page.blockSize;
page.busyBlocks += 1;
} else {
block = page.freeList.?;
// ASAN_UNPOISON_MEMORY_REGION(...); // TODO: ASAN support
page.freeList = freegcolink(block).*;
page.busyBlocks += 1;
}
// if we allocate the last block out of a page, we need to remove it from free list
if (page.freeList == null and page.freeNext < 0) {
g.freegcopages[sizeClass] = page.next;
if (page.next) |next|
next.prev = null;
page.next = null;
}
return block;
}
fn freeblock(L: *lua.State, sizeClass: u8, iblock: *anyopaque) void {
const g = L.global;
// the user data is right after the metadata
const block: *anyopaque = @ptrFromInt(@intFromPtr(iblock) - kBlockHeader);
const page = @as(*lua_Page, @ptrCast(@alignCast(metadata(block).*)));
std.debug.assert(page.busyBlocks > 0);
std.debug.assert(page.blockSize == kSizeClassConfig.sizeOfClass[sizeClass] + kBlockHeader);
std.debug.assert(@intFromPtr(block) >= @intFromPtr(&page.data) and @intFromPtr(block) < @intFromPtr(page) + @as(usize, @intCast(page.pageSize)));
// if the page wasn't in the page free list, it should be now since it got a block!
if (page.freeList == null and page.freeNext < 0) {
std.debug.assert(page.prev == null);
std.debug.assert(page.next == null);
page.next = g.freepages[sizeClass];
if (page.next) |next|
next.prev = page;
g.freepages[sizeClass] = page;
}
// add the block to the free list inside the page
metadata(block).* = page.freeList;
page.freeList = block;
// ASAN_POISON_MEMORY_REGION(...); // TODO: ASAN support
page.busyBlocks -= 1;
// if it's the last block in the page, we don't need the page
if (page.busyBlocks == 0)
freeclasspage(L, &g.freepages, debugpageset(&g.allpages), page, sizeClass);
}
fn freegcoblock(L: *lua.State, sizeClass: u8, block: *anyopaque, page: *lua_Page) void {
std.debug.assert(page.busyBlocks > 0);
std.debug.assert(page.blockSize == kSizeClassConfig.sizeOfClass[sizeClass]);
std.debug.assert(@intFromPtr(block) >= @intFromPtr(&page.data) and @intFromPtr(block) < @intFromPtr(page) + @as(usize, @intCast(page.pageSize)));
const g = L.global;
// if the page wasn't in the page free list, it should be now since it got a block!
if (page.freeList == null and page.freeNext < 0) {
std.debug.assert(page.prev == null);
std.debug.assert(page.next == null);
page.next = g.freegcopages[sizeClass];
if (page.next) |next|
next.prev = page;
g.freegcopages[sizeClass] = page;
}
// when separate block metadata is not used, free list link is stored inside the block data itself
freegcolink(block).* = page.freeList;
page.freeList = block;
// ASAN_POISON_MEMORY_REGION(...); // TODO: ASAN support
page.busyBlocks -= 1;
// if it's the last block in the page, we don't need the page
if (page.busyBlocks == 0)
freeclasspage(L, &g.freegcopages, &g.allgcopages, page, sizeClass);
}
pub fn Mnew_(L: *lua.State, nsize: usize, memcat: u8) Error!*anyopaque {
const g = L.global;
const nclass = sizeclass(nsize);
const block = if (nclass >= 0)
newblock(L, @intCast(nclass))
else
(g.frealloc.?)(g.ud, null, 0, nsize) orelse return Error.OutOfMemory;
g.totalbytes += nsize;
g.memcatbytes[memcat] += nsize;
if (g.cb.onallocate) |onallocate| {
@branchHint(.unlikely);
onallocate(L, 0, nsize);
}
return block;
}
pub fn Mnewgco_(L: *lua.State, nsize: usize, memcat: u8) Error!*lstate.GCObject {
// we need to accommodate space for link for free blocks (freegcolink)
std.debug.assert(nsize >= kGCOLinkOffset + @sizeOf(*anyopaque));
const g = L.global;
const nclass = sizeclass(nsize);
var block: *anyopaque = undefined;
if (nclass >= 0) {
block = try newgcoblock(L, @intCast(nclass));
} else {
const page = try newpage(L, &g.allgcopages, @offsetOf(lua_Page, "data") + nsize, @intCast(nsize), 1);
block = @ptrCast(@alignCast(&page.data));
// ASAN_UNPOISON_MEMORY_REGION(...); // TODO: ASAN support
page.freeNext -= page.blockSize;
page.busyBlocks += 1;
}
g.totalbytes += nsize;
g.memcatbytes[memcat] += nsize;
if (g.cb.onallocate) |onallocate| {
@branchHint(.unlikely);
onallocate(L, 0, nsize);
}
return @ptrCast(@alignCast(block));
}
pub inline fn Mnewgco(L: *lua.State, comptime T: type, nsize: usize, memcat: u8) !*T {
return @ptrCast(@alignCast(try Mnewgco_(L, nsize, memcat)));
}
pub fn Mfree_(L: *lua.State, block: ?*anyopaque, osize: usize, memcat: u8) void {
const g = L.global;
std.debug.assert((osize == 0) == (block == null));
const oclass = sizeclass(osize);
if (oclass >= 0)
freeblock(L, @intCast(oclass), block.?)
else
_ = (g.frealloc.?)(g.ud, @ptrCast(block), osize, 0);
g.totalbytes -= osize;
g.memcatbytes[memcat] -= osize;
}
pub fn Mfreegco_(L: *lua.State, block: ?*lstate.GCObject, osize: usize, memcat: u8, page: *lua_Page) void {
const g = L.global;
std.debug.assert((osize == 0) == (block == null));
const oclass = sizeclass(osize);
if (oclass >= 0) {
block.?.gch.header.tt = @intFromEnum(lua.Type.Nil);
freegcoblock(L, @intCast(oclass), @ptrCast(@alignCast(block.?)), page);
} else {
std.debug.assert(page.busyBlocks == 1);
std.debug.assert(page.blockSize == osize);
std.debug.assert(@intFromPtr(block.?) == @intFromPtr(&page.data));
freepage(L, &g.allgcopages, page);
}
g.totalbytes -= osize;
g.memcatbytes[memcat] -= osize;
}
pub inline fn Mfreegco(L: *lua.State, p: *lstate.GCObject, size: usize, memcat: u8, page: *lua_Page) void {
std.debug.assert(p.gch.header.tt >= @intFromEnum(lua.Type.String));
Mfreegco_(L, p, size, memcat, page);
}
pub fn Mrealloc_(L: *lua.State, block: ?*anyopaque, osize: usize, nsize: usize, memcat: u8) Error!?*anyopaque {
const g = L.global;
std.debug.assert((osize == 0) == (block == null));
const nclass = sizeclass(nsize);
const oclass = sizeclass(osize);
var result: ?*anyopaque = undefined;
// if either block needs to be allocated using a block allocator, we can't use realloc directly
if (nclass >= 0 or oclass >= 0) {
result = if (nclass >= 0)
try newblock(L, @intCast(nclass))
else
(g.frealloc.?)(g.ud, null, 0, nsize) orelse if (nsize > 0) return Error.OutOfMemory else null;
if (osize > 0 and nsize > 0) {
const tsize = @min(osize, nsize);
@memcpy(
@as([*]u8, @ptrCast(@alignCast(result)))[0..tsize],
@as([*]u8, @ptrCast(@alignCast(block.?)))[0..tsize],
);
}
if (oclass >= 0)
freeblock(L, @intCast(oclass), block.?)
else
_ = (g.frealloc.?)(g.ud, block, osize, 0);
} else {
result = (g.frealloc.?)(g.ud, block, osize, nsize) orelse if (nsize > 0) return Error.OutOfMemory else null;
}
std.debug.assert((nsize == 0) == (result == null));
g.totalbytes = (g.totalbytes - osize) + nsize;
if (nsize < osize)
g.memcatbytes[memcat] -= osize - nsize
else
g.memcatbytes[memcat] += nsize - osize;
if (g.cb.onallocate) |onallocate| {
@branchHint(.unlikely);
onallocate(L, osize, nsize);
}
return result;
}
pub inline fn Marraysize_(n: usize, e: usize) Error!usize {
if (n <= @divTrunc(std.math.maxInt(usize), e)) return n * e else return Error.BlockTooBig;
}
pub inline fn Mnewarray(L: *lua.State, comptime T: type, n: usize, memcat: u8) Error![*]T {
return @ptrCast(@alignCast(try Mnew_(L, try Marraysize_(n, @sizeOf(T)), memcat)));
}
pub inline fn Mfreearray(L: *lua.State, comptime T: type, b: ?[*]T, n: usize, memcat: u8) void {
Mfree_(L, @ptrCast(@alignCast(b)), n * @sizeOf(T), memcat);
}
pub inline fn Mreallocarray(L: *lua.State, comptime T: type, v: ?[*]T, oldn: usize, n: usize, memcat: u8) Error!?[*]T {
return @ptrCast(@alignCast((try Mrealloc_(L, @ptrCast(@alignCast(v)), oldn * @sizeOf(T), try Marraysize_(n, @sizeOf(T)), memcat))));
}
pub fn Mgetpagewalkinfo(page: *lua_Page, start: *[*]u8, end: *[*]u8, busyBlocks: *c_int, blockSize: *c_int) void {
const blockCount = @divTrunc(page.pageSize - @offsetOf(lua_Page, "data"), page.blockSize);
std.debug.assert(page.freeNext >= -page.blockSize and page.freeNext <= (blockCount - 1) * page.blockSize);
const data = @as([*]u8, @ptrCast(@alignCast(&page.data))); // silences ubsan when indexing page->data
start.* = data[@intCast(page.freeNext + page.blockSize)..];
end.* = data[@intCast(blockCount * page.blockSize)..];
busyBlocks.* = page.busyBlocks;
blockSize.* = page.blockSize;
}
pub fn Mgetpageinfo(page: *lua_Page, pageBlocks: *c_int, busyBlocks: *c_int, blockSize: *c_int, pageSize: *c_int) void {
pageBlocks.* = @divTrunc(page.pageSize - @offsetOf(lua_Page, "data"), page.blockSize);
busyBlocks.* = page.busyBlocks;
blockSize.* = page.blockSize;
pageSize.* = page.pageSize;
}
pub fn Mgetnextpage(page: *lua_Page) ?*lua_Page {
return page.listnext;
}
pub fn Mvisitpage(
page: *lua_Page,
comptime T: type,
context: T,
comptime visitor: fn (context: T, page: *lua_Page, gco: *lstate.GCObject) bool,
) void {
var start: [*]u8 = undefined;
var end: [*]u8 = undefined;
var busyBlocks: c_int = 0;
var blockSize: c_int = 0;
Mgetpagewalkinfo(page, &start, &end, &busyBlocks, &blockSize);
var pos: [*]u8 = start;
while (pos != end) : (pos += @as(u32, @intCast(blockSize))) {
const gco: *lstate.GCObject = @ptrCast(@alignCast(pos));
// skip memory blocks that are already freed
if (gco.gch.header.tt == @intFromEnum(lua.Type.Nil))
continue;
// when true is returned it means that the element was deleted
if (visitor(context, page, gco)) {
std.debug.assert(busyBlocks > 0);
busyBlocks -= 1;
// if the last block was removed, page would be removed as well
if (busyBlocks == 0)
break;
}
}
}
pub fn Mvisitgco(
L: *lua.State,
comptime T: type,
context: T,
comptime visitor: fn (context: T, page: *lua_Page, gco: *lstate.GCObject) bool,
) void {
const g = L.global;
var curr: ?*lua_Page = g.allgcopages;
while (curr) |page| {
const next = page.listnext; // block visit might destroy the page
Mvisitpage(page, T, context, visitor);
curr = next;
}
}
+58
View File
@@ -0,0 +1,58 @@
const std = @import("std");
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn inumisnan(x: anytype) bool {
comptime switch (@typeInfo(@TypeOf(x))) {
.comptime_float, .float => {},
.comptime_int, .int => {},
else => @compileError("Unsupported type"),
};
return x != x;
}
pub inline fn inumeq(a: anytype, b: f64) bool {
comptime switch (@typeInfo(@TypeOf(a))) {
.comptime_float, .comptime_int => return @as(f64, a) == b,
.float => return @as(f64, @floatCast(a)) == b,
.int => return @as(f64, @floatFromInt(a)) == b,
else => @compileError("Unsupported type"),
};
}
pub inline fn iveceq(a: []const f32, b: []const f32) bool {
if (comptime lua.config.VECTOR_SIZE == 4)
return a[0] == b[0] and a[1] == b[1] and a[2] == b[2] and a[3] == b[3]
else
return a[0] == b[0] and a[1] == b[1] and a[2] == b[2];
}
pub inline fn ivecisnan(x: []const f32) bool {
if (comptime lua.config.VECTOR_SIZE == 4)
return x[0] != x[0] or x[1] != x[1] or x[2] != x[2] or x[3] != x[3]
else
return x[0] != x[0] or x[1] != x[1] or x[2] != x[2];
}
pub inline fn inum2int(x: f64) i32 {
return @truncate(@as(i53, @intFromFloat(x)));
}
pub const I_MAXNUM2STR = 48;
pub fn printspecial(buf: []u8, sign: u1, fraction: u64) []u8 {
if (fraction == 0) {
const _inf = "-inf";
const len = _inf.len - (1 - sign);
@memcpy(buf[0..len], _inf[1 - sign ..]);
return buf[0..len];
} else {
@memcpy(buf[0..3], "nan");
return buf[0..3];
}
}
pub fn inum2str(buf: []u8, x: f64) []u8 {
return std.fmt.bufPrint(buf, "{d}", .{x}) catch unreachable;
}
+943
View File
@@ -0,0 +1,943 @@
const c = @import("c");
const std = @import("std");
const lgc = @import("lgc.zig");
const lua = @import("lua.zig");
const lstate = @import("lstate.zig");
const lcommon = @import("lcommon.zig");
const lnumutils = @import("lnumutils.zig");
const Errorset = @import("errorset.zig");
pub const CommonHeader = extern struct {
tt: u8,
marked: u8,
memcat: u8,
};
pub const GCheader = extern struct {
header: CommonHeader,
pub inline fn ttype(this: *const GCheader) c_int {
return this.header.tt;
}
};
pub const Value = extern union {
gc: ?*lstate.GCObject,
p: ?*anyopaque,
n: f64,
b: c_int,
l: i64,
/// v[0], v[1] live here; v[2] lives in TValue::extra
v: [2]f32,
};
///
/// Tagged Values
///
pub const TValue = extern struct {
value: Value,
extra: [lua.config.EXTRA_SIZE]c_int = undefined,
tt: c_int,
pub inline fn ttype(this: *const TValue) c_int {
return this.tt;
}
pub inline fn typeOf(obj: *const TValue) lua.Type {
return @enumFromInt(obj.ttype());
}
pub inline fn ttisnil(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Nil);
}
pub inline fn ttisnumber(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Number);
}
pub inline fn ttisinteger(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Integer);
}
pub inline fn ttisstring(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.String);
}
pub inline fn ttistable(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Table);
}
pub inline fn ttisfunction(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Function);
}
pub inline fn ttisboolean(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Boolean);
}
pub inline fn ttisuserdata(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Userdata);
}
pub inline fn ttisthread(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Thread);
}
pub inline fn ttisbuffer(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Buffer);
}
pub inline fn ttislightuserdata(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.LightUserdata);
}
pub inline fn ttisvector(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Vector);
}
pub inline fn ttisupval(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.UpVal);
}
pub inline fn ttisclass(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Class);
}
pub inline fn ttisobject(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Object);
}
pub inline fn obj2gco(obj: *TValue) *lstate.GCObject {
std.debug.assert(obj.iscollectable());
return @ptrCast(@alignCast(obj));
}
pub inline fn gcvalue(obj: *const TValue) *lstate.GCObject {
std.debug.assert(obj.iscollectable());
return obj.value.gc.?;
}
pub inline fn pvalue(obj: *const TValue) ?*anyopaque {
std.debug.assert(obj.ttislightuserdata());
return obj.value.p;
}
pub inline fn nvalue(obj: *const TValue) f64 {
std.debug.assert(obj.ttisnumber());
return obj.value.n;
}
pub inline fn lvalue(obj: *const TValue) i64 {
std.debug.assert(obj.ttisinteger());
return obj.value.l;
}
pub inline fn vvalue(obj: *const TValue) []const f32 {
std.debug.assert(obj.ttisvector());
return @as([*]const f32, @ptrCast(&obj.value.v))[0..lua.config.VECTOR_SIZE];
}
pub inline fn tsvalue(obj: *const TValue) *TString {
std.debug.assert(obj.ttisstring());
return &obj.value.gc.?.ts;
}
pub inline fn uvalue(obj: *const TValue) *Udata {
std.debug.assert(obj.ttisuserdata());
return &obj.value.gc.?.u;
}
pub inline fn clvalue(obj: *const TValue) *Closure {
std.debug.assert(obj.ttisfunction());
return &obj.value.gc.?.cl;
}
pub inline fn hvalue(obj: *const TValue) *LuaTable {
std.debug.assert(obj.ttistable());
return &obj.value.gc.?.h;
}
pub inline fn bvalue(obj: *const TValue) bool {
std.debug.assert(obj.ttisboolean());
return obj.value.b != 0;
}
pub inline fn thvalue(obj: *const TValue) *lstate.lua_State {
std.debug.assert(obj.ttisthread());
return &obj.value.gc.?.th;
}
pub inline fn bufvalue(obj: *const TValue) *Buffer {
std.debug.assert(obj.ttisbuffer());
return &obj.value.gc.?.buf;
}
pub inline fn upvalue(obj: *TValue) *UpVal {
std.debug.assert(obj.ttisupval());
return &obj.value.gc.?.uv;
}
pub inline fn classvalue(obj: *const TValue) *LuauClass {
std.debug.assert(obj.ttisclass());
return &obj.value.gc.?.lclass;
}
pub inline fn objectvalue(obj: *const TValue) *LuauObject {
std.debug.assert(obj.ttisobject());
return &obj.value.gc.?.lobject;
}
pub inline fn svalue(obj: *const TValue) [*c]const u8 {
return obj.tsvalue().getstr();
}
pub inline fn l_isfalse(obj: *const TValue) bool {
return obj.ttisnil() or (obj.ttisboolean() and !obj.bvalue());
}
pub inline fn lightuserdatatag(obj: *const TValue) u8 {
std.debug.assert(obj.ttislightuserdata());
return @intCast(obj.extra[0]);
}
pub inline fn checkliveness(obj: *const TValue, g: *const lstate.global_State) void {
std.debug.assert(!obj.iscollectable() or ((obj.ttype() == obj.value.gc.?.gch.header.tt) and !lgc.isdead(g, obj.value.gc.?)));
}
pub inline fn setnilvalue(obj: *TValue) void {
obj.settype(.Nil);
}
pub inline fn setnvalue(obj: *TValue, x: f64) void {
obj.value.n = x;
obj.settype(.Number);
}
pub inline fn setlvalue(obj: *TValue, x: i64) void {
obj.value.l = x;
obj.settype(.Integer);
}
pub inline fn setvvalue(obj: *TValue, x: f32, y: f32, z: f32, w: ?f32) void {
const i_v: [*]f32 = @ptrCast(&obj.value.v);
i_v[0] = x;
i_v[1] = y;
i_v[2] = z;
if (comptime lua.config.VECTOR_SIZE == 4)
i_v[3] = w orelse 0;
obj.settype(.Vector);
}
pub inline fn setpvalue(obj: *TValue, x: ?*anyopaque, tag: u32) void {
obj.value.p = x;
obj.extra[0] = @intCast(tag);
obj.settype(.LightUserdata);
}
pub inline fn setbvalue(obj: *TValue, x: bool) void {
obj.value.b = if (x) 1 else 0;
obj.settype(.Boolean);
}
pub inline fn setsvalue(obj: *TValue, L: *lstate.lua_State, x: *TString) void {
obj.value.gc = @ptrCast(@alignCast(x));
obj.settype(.String);
obj.checkliveness(L.global);
}
pub inline fn setuvalue(obj: *TValue, L: *lstate.lua_State, x: *Udata) void {
obj.value.gc = @ptrCast(@alignCast(x));
obj.settype(.Userdata);
obj.checkliveness(L.global);
}
pub inline fn setthvalue(obj: *TValue, L: *lstate.lua_State, x: *lstate.lua_State) void {
obj.value.gc = @ptrCast(@alignCast(x));
obj.settype(.Thread);
obj.checkliveness(L.global);
}
pub inline fn setbufvalue(obj: *TValue, L: *lstate.lua_State, x: *Buffer) void {
obj.value.gc = @ptrCast(@alignCast(x));
obj.settype(.Buffer);
obj.checkliveness(L.global);
}
pub inline fn setclvalue(obj: *TValue, L: *lstate.lua_State, x: *Closure) void {
obj.value.gc = @ptrCast(@alignCast(x));
obj.settype(.Function);
obj.checkliveness(L.global);
}
pub inline fn sethvalue(obj: *TValue, L: *lstate.lua_State, x: *LuaTable) void {
obj.value.gc = @ptrCast(@alignCast(x));
obj.settype(.Table);
obj.checkliveness(L.global);
}
pub inline fn setptvalue(obj: *TValue, L: *lstate.lua_State, x: *Proto) void {
obj.value.gc = @ptrCast(@alignCast(x));
obj.settype(.Proto);
obj.checkliveness(L.global);
}
pub inline fn setupvalue(obj: *TValue, L: *lstate.lua_State, x: *UpVal) void {
obj.value.gc = @ptrCast(@alignCast(x));
obj.settype(.UpVal);
obj.checkliveness(L.global);
}
pub inline fn setobj(obj: *TValue, L: *lstate.lua_State, o2: *const TValue) void {
obj.* = o2.*;
obj.checkliveness(L.global);
}
pub inline fn setclassvalue(obj: *TValue, L: *lstate.lua_State, x: *LuauClass) void {
obj.value.gc = @ptrCast(@alignCast(x));
obj.settype(.Class);
obj.checkliveness(L.global);
}
pub inline fn setobjectvalue(obj: *TValue, L: *lstate.lua_State, x: *LuauObject) void {
obj.value.gc = @ptrCast(@alignCast(x));
obj.settype(.Object);
obj.checkliveness(L.global);
}
pub inline fn settype(obj: *TValue, t: lua.Type) void {
obj.tt = @intFromEnum(t);
}
pub inline fn iscollectable(o: *const TValue) bool {
return o.ttype() >= @intFromEnum(lua.Type.String);
}
pub inline fn iscfunction(o: *const TValue) bool {
return o.ttype() == @intFromEnum(lua.Type.Function) and o.clvalue().isC != 0;
}
pub inline fn isLfunction(o: *const TValue) bool {
return o.ttype() == @intFromEnum(lua.Type.Function) and o.clvalue().isC == 0;
}
};
pub const LU_TAG_ITERATOR = lua.config.UTAG_LIMIT;
pub inline fn checkliveness() void {}
pub const StkId = [*]TValue;
pub const TString = extern struct {
header: CommonHeader,
// 1 byte padding
atom: i16,
// 2 byte padding
/// next string in the hash table bucket
next: ?*TString,
hash: c_uint,
len: c_uint,
/// string data is allocated right after the header
data: [1]u8,
pub inline fn obj2gco(obj: *TString) *lstate.GCObject {
return @ptrCast(@alignCast(obj));
}
pub inline fn gdata(s: *TString) [*]u8 {
return @ptrCast(@alignCast(&s.data));
}
pub inline fn getstr(s: *const TString) [*c]const u8 {
return @ptrCast(@alignCast(&s.data));
}
pub inline fn toSlice(s: *const TString) [:0]const u8 {
return s.getstr()[0..s.len :0];
}
};
pub const Udata = extern struct {
header: CommonHeader,
tag: u8,
len: c_int,
metatable: ?*LuaTable,
data: [1]u8 align(8),
pub inline fn obj2gco(obj: *Udata) *lstate.GCObject {
return @ptrCast(@alignCast(obj));
}
};
pub const Buffer = extern struct {
header: CommonHeader,
len: c_uint,
data: [1]u8 align(8),
pub inline fn obj2gco(obj: *Buffer) *lstate.GCObject {
return @ptrCast(@alignCast(obj));
}
};
pub const FeedbackVectorSlotKind = enum(u8) {
CallTarget,
};
pub const FeedbackVectorSlot = extern struct {
kind: FeedbackVectorSlotKind,
data: extern union {
call_target: extern struct {
pc: u32,
proto: u32,
hits: u32,
},
},
};
///
/// Function Prototypes
///
pub const Proto = extern struct {
header: CommonHeader,
/// number of upvalues
nups: u8,
numparams: u8,
is_vararg: u8,
maxstacksize: u8,
flags: u8,
/// constants used by the function
k: ?[*]TValue,
/// function bytecode
code: ?[*]lcommon.Instruction,
/// functions defined inside the function
p: ?[*]?*Proto,
codeentry: ?*const lcommon.Instruction,
execdata: ?*anyopaque,
exectarget: usize,
lineinfo: ?[*]u8, // for each instruction, line number as a delta from baseline
abslineinfo: ?[*]u8, // baseline line info, one entry for each 1<<linegaplog2 instructions; allocated after lineinfo
locvars: ?[*]LocVar, // information about local variables
upvalues: ?[*]?*TString, // upvalue names
source: ?*TString,
debugname: ?*TString,
debuginsn: ?[*]u8, // a copy of code[] array with just opcodes
typeinfo: ?[*]u8,
userdata: ?*anyopaque,
gclist: ?*lstate.GCObject,
sizecode: c_int,
sizep: c_int,
sizelocvars: c_int,
sizeupvalues: c_int,
sizek: c_int,
sizelineinfo: c_int,
linegaplog2: c_int,
linedefined: c_int,
bytecodeid: c_int,
sizetypeinfo: c_int,
feedbackvec: ?[*]FeedbackVectorSlot,
feedbackvecsize: u32,
funid: u32,
optimized: ?*Proto,
deoptimized: ?*Proto,
cost: u64,
pub inline fn obj2gco(obj: *Proto) *lstate.GCObject {
return @ptrCast(@alignCast(obj));
}
};
pub const LocVar = extern struct {
varname: ?*TString,
/// first point where variable is active
startpc: c_int,
/// first point where variable is dead
endpc: c_int,
/// register slot, relative to base, where variable is stored
reg: u8,
};
///
/// Upvalues
///
pub const UpVal = extern struct {
header: CommonHeader,
/// set if reachable from an alive thread (only valid during atomic)
markedopen: u8,
// 4 byte padding (x64)
/// points to stack or to its own value
v: *TValue,
u: extern union {
/// the value (when closed)
value: TValue,
open: extern struct {
// global double linked list (when open)
prev: ?*UpVal,
next: ?*UpVal,
// thread linked list (when open)
threadnext: ?*UpVal,
},
},
pub inline fn obj2gco(obj: *UpVal) *lstate.GCObject {
return @ptrCast(@alignCast(obj));
}
pub inline fn upisopen(up: *const UpVal) bool {
return up.v != &up.u.value;
}
};
///
/// Closures
///
pub const Closure = extern struct {
header: CommonHeader,
isC: u8,
nupvalues: u8,
stacksize: u8,
preload: u8,
gclist: ?*lstate.GCObject,
env: *LuaTable,
d: ValueUnion,
pub const ValueUnion = extern union {
c: C,
l: L,
pub const C = extern struct {
f: ?lua.CFunction,
cont: ?lua.Continuation,
debugname: [*c]const u8,
upvals: [1]TValue,
pub inline fn upvalues(cc: *C) [*]TValue {
return @as([*]TValue, @ptrCast(&cc.upvals));
}
};
pub const L = extern struct {
p: *Proto,
uprefs: [1]TValue,
pub inline fn upreferences(ll: *L) [*]TValue {
return @as([*]TValue, @ptrCast(&ll.uprefs));
}
};
};
pub inline fn obj2gco(obj: *Closure) *lstate.GCObject {
return @ptrCast(@alignCast(obj));
}
};
pub const TKey = extern struct {
value: Value,
extra: [lua.config.EXTRA_SIZE]c_int,
pi: Packed = undefined,
pub const Packed = packed struct(u32) {
tt: u4, // type
next: i28, // next in the chain
pub fn withtt(tt: u4) [4]u8 {
return @bitCast(Packed{ .tt = tt, .next = 0 });
}
};
pub inline fn ttype(this: *const TKey) u4 {
return this.pi.tt;
}
pub inline fn typeOf(obj: *const TKey) lua.Type {
return @enumFromInt(obj.ttype());
}
pub inline fn setttype(this: *TKey, t: lua.Type) void {
this.pi.tt = @intFromEnum(t);
}
pub inline fn ttisnil(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.Nil);
}
pub inline fn ttisnumber(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.Number);
}
pub inline fn ttisinteger(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.Integer);
}
pub inline fn ttisstring(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.String);
}
pub inline fn ttistable(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.Table);
}
pub inline fn ttisfunction(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.Function);
}
pub inline fn ttisboolean(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.Boolean);
}
pub inline fn ttisuserdata(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.Userdata);
}
pub inline fn ttisthread(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.Thread);
}
pub inline fn ttisbuffer(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.Buffer);
}
pub inline fn ttislightuserdata(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.LightUserdata);
}
pub inline fn ttisvector(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.Vector);
}
pub inline fn ttisupval(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.UpVal);
}
pub inline fn gcvalue(obj: *const TKey) *lstate.GCObject {
std.debug.assert(obj.iscollectable());
return obj.value.gc.?;
}
pub inline fn pvalue(obj: *const TKey) ?*anyopaque {
std.debug.assert(obj.ttislightuserdata());
return obj.value.p;
}
pub inline fn nvalue(obj: *const TKey) f64 {
std.debug.assert(obj.ttisnumber());
return obj.value.n;
}
pub inline fn lvalue(obj: *const TKey) i64 {
std.debug.assert(obj.ttisinteger());
return obj.value.l;
}
pub inline fn vvalue(obj: *const TKey) []const f32 {
std.debug.assert(obj.ttisvector());
return @as([*]const f32, @ptrCast(&obj.value.v))[0..lua.config.VECTOR_SIZE];
}
pub inline fn tsvalue(obj: *const TKey) *TString {
std.debug.assert(obj.ttisstring());
return &obj.value.gc.?.ts;
}
pub inline fn uvalue(obj: *const TKey) *Udata {
std.debug.assert(obj.ttisuserdata());
return &obj.value.gc.?.u;
}
pub inline fn clvalue(obj: *const TKey) *Closure {
std.debug.assert(obj.ttisfunction());
return &obj.value.gc.?.cl;
}
pub inline fn hvalue(obj: *const TKey) *LuaTable {
std.debug.assert(obj.ttistable());
return &obj.value.gc.?.h;
}
pub inline fn bvalue(obj: *const TKey) bool {
std.debug.assert(obj.ttisboolean());
return obj.value.b != 0;
}
pub inline fn thvalue(obj: *const TKey) *lstate.lua_State {
std.debug.assert(obj.ttisthread());
return &obj.value.gc.?.th;
}
pub inline fn bufvalue(obj: *const TKey) *Buffer {
std.debug.assert(obj.ttisbuffer());
return &obj.value.gc.?.buf;
}
pub inline fn upvalue(obj: *TKey) *UpVal {
std.debug.assert(obj.ttisupval());
return &obj.value.gc.?.uv;
}
pub inline fn svalue(obj: *const TKey) [*c]const u8 {
return obj.tsvalue().getstr();
}
pub inline fn lightuserdatatag(obj: *const TKey) c_int {
std.debug.assert(obj.ttislightuserdata());
return obj.extra[0];
}
pub inline fn iscollectable(o: *const TKey) bool {
return o.ttype() >= @intFromEnum(lua.Type.String);
}
pub inline fn setnilvalue(obj: *TKey) void {
obj.pi.tt = @intFromEnum(lua.Type.Nil);
}
pub inline fn next(this: *TKey) i28 {
return this.pi.next;
}
};
pub const LuaNode = extern struct {
val: TValue,
key: TKey,
pub inline fn gkey(this: *LuaNode) *TKey {
return &this.key;
}
pub inline fn gval(this: *LuaNode) *TValue {
return &this.val;
}
pub inline fn gnext(this: *LuaNode) i28 {
return this.key.pi.next;
}
pub inline fn add_num(this: *LuaNode, n: anytype) *LuaNode {
switch (@typeInfo(@TypeOf(n))) {
.comptime_int => return @ptrCast(@as([*]LuaNode, @ptrCast(this)) + @as(usize, @intCast(n))),
.int => |i| {
if (i.signedness == .unsigned)
return @ptrCast(@as([*]LuaNode, @ptrCast(this)) + @as(usize, @intCast(n)))
else {
if (n < 0)
return @ptrCast(@as([*]LuaNode, @ptrCast(this)) - @as(usize, @intCast(-n)))
else
return @ptrCast(@as([*]LuaNode, @ptrCast(this)) + @as(usize, @intCast(n)));
}
},
else => @compileError("n must be an integer"),
}
}
pub fn sub(this: *LuaNode, ptr: *LuaNode) isize {
if (@intFromPtr(ptr) > @intFromPtr(this))
return -@as(isize, @intCast(@as([*]LuaNode, @ptrCast(ptr)) - @as([*]LuaNode, @ptrCast(this))));
return @intCast(@as([*]LuaNode, @ptrCast(this)) - @as([*]LuaNode, @ptrCast(ptr)));
}
};
pub inline fn setnodekey(L: *lstate.lua_State, node: *LuaNode, obj: *const TValue) void {
node.key.value = obj.value;
@memcpy(node.key.extra[0..lua.config.EXTRA_SIZE], obj.extra[0..lua.config.EXTRA_SIZE]);
node.key.pi.tt = @intCast(obj.tt);
obj.checkliveness(L.global);
}
pub inline fn getnodekey(L: *lstate.lua_State, obj: *TValue, node: *const LuaNode) void {
obj.value = node.key.value;
@memcpy(obj.extra[0..lua.config.EXTRA_SIZE], node.key.extra[0..lua.config.EXTRA_SIZE]);
obj.tt = @intCast(node.key.pi.tt);
obj.checkliveness(L.global);
}
pub const LuaTable = extern struct {
header: CommonHeader,
/// 1<<p means tagmethod(p) is not present
tmcache: u8,
/// sandboxing feature to prohibit writes to table
readonly: u8,
/// environment doesn't share globals with other scripts
safeenv: u8,
/// log2 of size of `node' array
lsizenode: u8,
/// (1<<lsizenode)-1, truncated to 8 bits
nodemask8: u8,
/// size of `array' array
sizearray: c_int,
bound: extern union {
/// any free position is before this position
lastfree: c_int,
/// negated 'boundary' of `array' array; iff aboundary < 0
aboundary: c_int,
},
metatable: ?*LuaTable,
array: ?[*]TValue, // array part
node: [*]LuaNode,
gclist: ?*lstate.GCObject,
pub inline fn obj2gco(obj: *LuaTable) *lstate.GCObject {
return @ptrCast(@alignCast(obj));
}
pub inline fn gnode(t: *const LuaTable, i: usize) [*]LuaNode {
return t.node + i;
}
};
pub const LuauClass = extern struct {
header: CommonHeader,
gclist: ?*lstate.GCObject,
name: *TString,
/// Mapping from offset to static members (only methods for now).
staticmembers: [*]TValue,
/// Mapping from member name to offset.
memberstooffset: *LuaTable,
/// Mapping from offset to member name.
offsettomember: [*]*TString,
/// Metatable for this *class object*. At time of writing this only contains
/// __call, but we may add more metamethods to class objects in the future.
metatable: *LuaTable,
/// Metatable for instances of this class. NULL until the first metamethod
/// is added via luaR_addclassmember.
instancemetatable: ?*LuaTable,
/// Number of instance members that we expect instances of this class object
/// to have.
numberofinstancemembers: u32,
// Total number of members that we expect this class object to have between
// instance and static members.
//
// We store this number as an optimization. It's pretty rare that we need
// to reference the specific number of static members, but it's very common
// to reference the total number of members (for validating hot paths in
// the interpreter) and the number of instance members (branching on
// instance or static members, creating class instances).
numberofallmembers: u32,
pub inline fn obj2gco(obj: *LuauClass) *lstate.GCObject {
return @ptrCast(@alignCast(obj));
}
};
pub const LuauObject = extern struct {
header: CommonHeader,
gclist: ?*lstate.GCObject,
/// The class object that this value is an instance of.
lclass: *LuauClass,
/// The number of members that this instance contains. We need this in order
/// to free ourselves if we got swept in the same GC cycle as our class
/// pointer.
numberofmembers: u32,
/// The fields of this instance.
members: [*]TValue,
pub inline fn obj2gco(obj: *LuauObject) *lstate.GCObject {
return @ptrCast(@alignCast(obj));
}
};
pub inline fn lmod(comptime T: type, s: u32, size: T) T {
std.debug.assert(size & (size - 1) == 0);
return s & (size - 1);
}
pub inline fn twoto(x: if (@sizeOf(usize) == 8) u6 else u5) usize {
return @as(usize, 1) << x;
}
pub inline fn sizenode(t: *const LuaTable) usize {
return @intCast(@as(usize, 1) << @truncate(t.lsizenode));
}
extern "c" const luaO_nilobject_: TValue;
pub const Onilobject = &luaO_nilobject_;
pub inline fn ceillog2(x: u32) i32 {
return Olog2(x - 1) + 1;
}
pub fn Olog2(i: u32) i32 {
// zig fmt: off
const log_2: [256]u8 = [_]u8{0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8};
// zig fmt: on
var x: u32 = i;
var l: i32 = -1;
while (x >= 256) {
l += 8;
x >>= 8;
}
return l + log_2[x];
}
pub fn OrawequalObj(t1: *const TValue, t2: *const TValue) bool {
if (t1.ttype() != t2.ttype())
return false;
switch (t1.typeOf()) {
.None => unreachable,
.Nil => return true,
.Number => return t1.nvalue() == t2.nvalue(),
.Integer => return t1.lvalue() == t2.lvalue(),
.Vector => return lnumutils.iveceq(t1.vvalue(), t2.vvalue()),
.Boolean => return t1.bvalue() == t2.bvalue(),
.LightUserdata => return t1.pvalue() == t2.pvalue() and t1.lightuserdatatag() == t2.lightuserdatatag(),
inline else => |t| {
comptime std.debug.assert(t.istypecollectable());
return t1.gcvalue() == t2.gcvalue();
},
}
}
pub fn OrawequalKey(t1: *const TKey, t2: *const TValue) bool {
if (t1.ttype() != t2.ttype())
return false;
switch (t1.typeOf()) {
.None => unreachable,
.Nil => return true,
.Number => return t1.nvalue() == t2.nvalue(),
.Integer => return t1.lvalue() == t2.lvalue(),
.Vector => return lnumutils.iveceq(t1.vvalue(), t2.vvalue()),
.Boolean => return t1.bvalue() == t2.bvalue(),
.LightUserdata => return t1.pvalue() == t2.pvalue() and t1.lightuserdatatag() == t2.lightuserdatatag(),
inline else => |t| {
comptime std.debug.assert(t.istypecollectable());
return t1.gcvalue() == t2.gcvalue();
},
}
}
pub fn Opushvfstring(L: *lua.State, comptime fmt: []const u8, args: anytype) Errorset.Table!void {
var buf: [lua.config.BUFFERSIZE]u8 = undefined;
const fstr = std.fmt.bufPrint(&buf, fmt, args) catch |err| @panic(@errorName(err));
try L.pushlstring(fstr);
}
pub inline fn Opushfstring(L: *lua.State, comptime fmt: []const u8, args: anytype) Errorset.Table!void {
try Opushvfstring(L, fmt, args);
}
// pub fn Ochunkid(out: []u8, comptime source: []const u8) []u8 {
// c.luaO
// }
test "size match" {
const Sizes = struct {
extern "c" const GCObject_size: u8;
extern "c" const GCheader_size: u8;
extern "c" const Value_size: u8;
extern "c" const TValue_size: u8;
extern "c" const TString_size: u8;
extern "c" const Udata_size: u8;
extern "c" const LuauBuffer_size: u8;
extern "c" const Proto_size: u8;
extern "c" const LocVar_size: u8;
extern "c" const UpVal_size: u8;
extern "c" const Closure_size: u8;
extern "c" const TKey_size: u8;
extern "c" const LuaNode_size: u8;
extern "c" const LuaTable_size: u8;
extern "c" const LuauClass_size: u8;
extern "c" const LuauObject_size: u8;
extern "c" const TString_data_offset: u8;
extern "c" const Udata_data_offset: u8;
extern "c" const LuauBuffer_data_offset: u8;
};
try std.testing.expect(Sizes.GCObject_size == @sizeOf(lstate.GCObject));
try std.testing.expect(Sizes.GCheader_size == @sizeOf(GCheader));
try std.testing.expect(Sizes.Value_size == @sizeOf(Value));
try std.testing.expect(Sizes.TValue_size == @sizeOf(TValue));
try std.testing.expect(Sizes.TString_size == @sizeOf(TString));
try std.testing.expect(Sizes.Udata_size == @sizeOf(Udata));
try std.testing.expect(Sizes.LuauBuffer_size == @sizeOf(Buffer));
try std.testing.expect(Sizes.Proto_size == @sizeOf(Proto));
try std.testing.expect(Sizes.LocVar_size == @sizeOf(LocVar));
try std.testing.expect(Sizes.UpVal_size == @sizeOf(UpVal));
try std.testing.expect(Sizes.Closure_size == @sizeOf(Closure));
try std.testing.expect(Sizes.TKey_size == @sizeOf(TKey));
try std.testing.expect(Sizes.LuaNode_size == @sizeOf(LuaNode));
try std.testing.expect(Sizes.LuaTable_size == @sizeOf(LuaTable));
try std.testing.expect(Sizes.LuauClass_size == @sizeOf(LuauClass));
try std.testing.expect(Sizes.LuauObject_size == @sizeOf(LuauObject));
try std.testing.expect(Sizes.TString_data_offset == @offsetOf(TString, "data"));
try std.testing.expect(Sizes.Udata_data_offset == @offsetOf(Udata, "data"));
try std.testing.expect(Sizes.LuauBuffer_data_offset == @offsetOf(Buffer, "data"));
}
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_os(@ptrCast(L));
}
+9
View File
@@ -0,0 +1,9 @@
const c = @import("c");
const lua = @import("lua.zig");
extern "c" fn lua_clock() f64;
pub fn clock() f64 {
return lua_clock();
}
+1011
View File
File diff suppressed because it is too large Load Diff
+213
View File
@@ -0,0 +1,213 @@
const std = @import("std");
const builtin = @import("builtin");
const lua = @import("lua.zig");
const lobject = @import("lobject.zig");
const lgc = @import("lgc.zig");
const lmem = @import("lmem.zig");
const Errorset = @import("errorset.zig");
/// string size limit
pub const MAXSSIZE = (1 << 30);
/// string atoms are not defined by default; the storage is 16-bit integer
pub const ATOM_UNDEF = -32768;
inline fn sizestring(len: usize) usize {
return @offsetOf(lobject.TString, "data") + len + 1;
}
pub inline fn Snew(L: *lua.State, s: []const u8) Errorset.Memory!*lobject.TString {
return Snewlstr(L, s);
}
pub inline fn Sfix(s: *lobject.TString) void {
s.header.marked |= lgc.bitmask(lgc.FIXEDBIT);
}
pub inline fn Supdateatom(L: *lua.State, ts: *lobject.TString) void {
if (ts.atom == ATOM_UNDEF)
ts.atom = if (L.global.cb.useratom) |useratom| useratom(L, @ptrCast(@alignCast(&ts.data)), @intCast(ts.len)) else -1;
}
pub fn Shash(str: []const u8) u32 {
// Note that this hashing algorithm is replicated in BytecodeBuilder.cpp, BytecodeBuilder::getStringHash
var src = str;
var len: usize = str.len;
var a: u32 = 0;
var b: u32 = 0;
var h: u32 = @truncate(len);
// hash prefix in 12b chunks (using aligned reads) with ARX based hash (LuaJIT v2.1, lookup3)
// note that we stop at length<32 to maintain compatibility with Lua 5.1
while (len >= 32) : (len -= 12) {
a +%= std.mem.readInt(u32, src[0..4], builtin.cpu.arch.endian());
b +%= std.mem.readInt(u32, src[4..8], builtin.cpu.arch.endian());
h +%= std.mem.readInt(u32, src[8..12], builtin.cpu.arch.endian());
// mix
a ^= h;
a -%= ((h >> 14) | (h << (32 - 14)));
b ^= a;
b -%= ((a >> 11) | (a << (32 - 11)));
h ^= b;
h -%= ((b >> 25) | (b << (32 - 25)));
src = src[12..];
}
// original Lua 5.1 hash for compatibility (exact match when len<32)
var i: usize = len;
while (i > 0) : (i -= 1)
h ^= (h << 5) +% (h >> 2) +% src[i - 1];
return h;
}
pub fn Sresize(L: *lua.State, newsize: usize) Errorset.Memory!void {
const newhash = try lmem.Mnewarray(L, ?*lobject.TString, newsize, 0);
const tb = &L.global.strt;
for (0..newsize) |i|
newhash[i] = null;
// rehash
for (0..@intCast(tb.size)) |i| {
var p: ?*lobject.TString = tb.hash.?[i];
while (p) |node| { // for each node in the list
const next = node.next; // save next
const h = node.hash;
const h1 = lobject.lmod(usize, h, newsize); // new position
std.debug.assert(h % newsize == h1);
node.next = newhash[h1]; // chain it
newhash[h1] = node;
p = next;
}
}
lmem.Mfreearray(L, ?*lobject.TString, tb.hash, @intCast(tb.size), 0);
tb.size = @intCast(newsize);
tb.hash = newhash;
}
fn newlstr(L: *lua.State, str: []const u8, hash: u32) Errorset.Memory!*lobject.TString {
const l = str.len;
if (l > MAXSSIZE)
return error.BlockTooBig;
const ts = try lmem.Mnewgco(L, lobject.TString, sizestring(l), L.activememcat);
lgc.Cinit(L, @ptrCast(@alignCast(ts)), @intFromEnum(lua.Type.String));
ts.atom = ATOM_UNDEF;
ts.hash = hash;
ts.len = @intCast(l);
@memcpy(ts.gdata()[0..l], str[0..l]);
ts.gdata()[l] = 0; // ending 0
const tb = &L.global.strt;
const h: u32 = lobject.lmod(u32, hash, @intCast(tb.size));
ts.next = tb.hash.?[h]; // chain new entry
tb.hash.?[h] = ts;
tb.nuse += 1;
if (tb.nuse > tb.size and tb.size <= @divTrunc(std.math.maxInt(i32), 2))
try Sresize(L, @intCast(tb.size * 2)); // too crowded
return ts;
}
pub fn Sbufstart(L: *lua.State, size: usize) Errorset.Memory!*lobject.TString {
if (size > MAXSSIZE)
return error.BlockTooBig;
const ts = try lmem.Mnewgco(L, lobject.TString, sizestring(size), L.activememcat);
lgc.Cinit(L, @ptrCast(@alignCast(ts)), @intFromEnum(lua.Type.String));
ts.atom = ATOM_UNDEF;
ts.hash = 0; // computed in Sbuffinish
ts.len = @intCast(size);
ts.next = null;
return ts;
}
pub fn Sbuffinish(L: *lua.State, ts: *lobject.TString) Errorset.Memory!*lobject.TString {
const h = Shash(ts.gdata()[0..ts.len]);
const tb = &L.global.strt;
const bucket: u32 = lobject.lmod(u32, h, @intCast(tb.size));
// search if we already have this string in the hash table
var el: ?*lobject.TString = tb.hash.?[bucket];
while (el) |node| : (el = node.next) {
if (node.len == ts.len and std.mem.eql(u8, node.gdata()[0..ts.len], ts.gdata()[0..ts.len])) {
// string may be dead
if (lgc.isdead(L.global, @ptrCast(@alignCast(node))))
lgc.changewhite(@ptrCast(@alignCast(node)));
return node;
}
}
std.debug.assert(ts.next == null);
ts.hash = h;
ts.gdata()[ts.len] = 0; // ending 0
ts.next = tb.hash.?[bucket]; // chain new entry
tb.hash.?[bucket] = ts;
tb.nuse += 1;
if (tb.nuse > tb.size and tb.size <= @divTrunc(std.math.maxInt(i32), 2))
try Sresize(L, @intCast(tb.size * 2)); // too crowded
return ts;
}
fn findstrnode(L: *lua.State, str: []const u8, h: u32) ?*lobject.TString {
var el = L.global.strt.hash.?[lobject.lmod(u32, h, @intCast(L.global.strt.size))];
while (el) |node| : (el = node.next) {
if (node.len == str.len and std.mem.eql(u8, node.gdata()[0..node.len], str[0..str.len])) {
// string may be dead
if (lgc.isdead(L.global, @ptrCast(@alignCast(node))))
lgc.changewhite(@ptrCast(@alignCast(node)));
return node;
}
}
return null; // not found
}
pub fn Snewlstr(L: *lua.State, str: []const u8) Errorset.Memory!*lobject.TString {
const h = Shash(str);
if (findstrnode(L, str, h)) |el|
return el;
return newlstr(L, str, h); // not found
}
pub fn Sassumelstr(L: *lua.State, str: []const u8) ?*lobject.TString {
const h = Shash(str);
return findstrnode(L, str, h);
}
fn unlinkstr(L: *lua.State, ts: *lobject.TString) bool {
const g = L.global;
var p = &g.strt.hash.?[lobject.lmod(u32, ts.hash, @intCast(g.strt.size))];
while (p.*) |node| {
if (node == ts) {
p.* = node.next;
return true;
} else {
p = &node.next;
}
}
return false;
}
pub fn Sfree(L: *lua.State, ts: *lobject.TString, page: *lmem.lua_Page) void {
if (unlinkstr(L, ts))
L.global.strt.nuse -= 1
else
std.debug.assert(ts.next == null); // orphaned string buffer
lmem.Mfreegco(L, ts.obj2gco(), sizestring(ts.len), ts.header.memcat, page);
}
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_string(@ptrCast(L));
}
+700
View File
@@ -0,0 +1,700 @@
const c = @import("c");
const std = @import("std");
const lua = @import("lua.zig");
const lobject = @import("lobject.zig");
const lstate = @import("lstate.zig");
const ldebug = @import("ldebug.zig");
const lgc = @import("lgc.zig");
const lmem = @import("lmem.zig");
const lnumutils = @import("lnumutils.zig");
const Errorset = @import("errorset.zig");
const Error = Errorset.Table;
const MAXBITS = 26;
const MAXSIZE = 1 << MAXBITS;
const TValue = lobject.TValue;
const LuaNode = lobject.LuaNode;
const LuaTable = lobject.LuaTable;
const LUA_VECTOR_SIZE = lua.config.VECTOR_SIZE;
// const Hdummynode: LuaNode = .{
// .val = .{ .extra = undefined, .tt = @intFromEnum(lua.Type.Nil), .value = undefined },
// .key = .{ .extra = undefined, .pi = .{ .tt = @intFromEnum(lua.Type.Nil), .next = 0 }, .value = undefined },
// };
extern "c" const luaH_dummynode: LuaNode;
pub const dummynode = &luaH_dummynode;
pub inline fn invalidateTMcache(t: *LuaTable) void {
t.tmcache = 0;
}
pub inline fn hashpow2(t: *const LuaTable, n: u32) [*]LuaNode {
return t.gnode(lobject.lmod(usize, n, lobject.sizenode(t)));
}
pub inline fn hashstr(t: *const LuaTable, str: *const lobject.TString) [*]LuaNode {
return hashpow2(t, str.hash);
}
pub inline fn hashboolean(t: *const LuaTable, b: bool) [*]LuaNode {
return hashpow2(t, if (b) 1 else 0);
}
pub fn hashpointer(t: *const LuaTable, p: ?*const anyopaque) [*]LuaNode {
// we discard the high 32-bit portion of the pointer on 64-bit platforms as it doesn't carry much entropy anyway
var h: u32 = if (p) |ptr| @truncate(@intFromPtr(ptr)) else 0;
// MurmurHash3 32-bit finalizer
h ^= h >> 16;
h *%= 0x85ebca6b;
h ^= h >> 13;
h *%= 0xc2b2ae35;
h ^= h >> 16;
return hashpow2(t, h);
}
fn hashnum(t: *const LuaTable, n: f64) [*]LuaNode {
comptime std.debug.assert(@sizeOf(f64) == @sizeOf(u32) * 2); // expected a 8-byte double;
var i: [2]u32 = undefined;
@memcpy(i[0..], &@as([2]u32, @bitCast(n)));
// mask out sign bit to make sure -0 and 0 hash to the same value
var h1: u32 = i[0];
var h2: u32 = i[1] & 0x7fffffff;
// finalizer from MurmurHash64B
const m: u32 = 0x5bd1e995;
h1 ^= h2 >> 18;
h1 *%= m;
h2 ^= h1 >> 22;
h2 *%= m;
h1 ^= h2 >> 17;
h1 *%= m;
h2 ^= h1 >> 19;
h2 *%= m;
// ... truncated to 32-bit output (normally hash is equal to (uint64_t(h1) << 32) | h2, but we only really need the lower 32-bit half)
return hashpow2(t, h2);
}
fn hashint(t: *const LuaTable, n: i64) [*]LuaNode {
comptime std.debug.assert(@sizeOf(i64) == @sizeOf(u32) * 2); // expected a 8-byte integer;
var i: [2]u32 = undefined;
@memcpy(i[0..], &@as([2]u32, @bitCast(n)));
var h1: u32 = i[0];
var h2: u32 = i[1];
// finalizer from MurmurHash64B
const m: u32 = 0x5bd1e995;
h1 ^= h2 >> 18;
h1 *%= m;
h2 ^= h1 >> 22;
h2 *%= m;
h1 ^= h2 >> 17;
h1 *%= m;
h2 ^= h1 >> 19;
h2 *%= m;
// ... truncated to 32-bit output (normally hash is equal to (uint64_t(h1) << 32) | h2, but we only really need the lower 32-bit half)
return hashpow2(t, h2);
}
fn hashvec(t: *const LuaTable, v: []const f32) [*]LuaNode {
var i: [LUA_VECTOR_SIZE]u32 = undefined;
@memcpy(i[0..], (@as([*]const u32, @ptrCast(@alignCast(v.ptr))))[0..LUA_VECTOR_SIZE]);
// convert -0 to 0 to make sure they hash to the same value
i[0] = if (i[0] == 0x80000000) 0 else i[0];
i[1] = if (i[1] == 0x80000000) 0 else i[1];
i[2] = if (i[2] == 0x80000000) 0 else i[2];
// scramble bits to make sure that integer coordinates have entropy in lower bits
i[0] ^= i[0] >> 17;
i[1] ^= i[1] >> 17;
i[2] ^= i[2] >> 17;
// Optimized Spatial Hashing for Collision Detection of Deformable Objects
var h: u32 = (i[0] * 73856093) ^ (i[1] * 19349663) ^ (i[2] * 83492791);
if (comptime LUA_VECTOR_SIZE == 4) {
i[3] = if (i[3] == 0x80000000) 0 else i[3];
i[3] ^= i[3] >> 17;
h ^= i[3] * 39916801;
}
return hashpow2(t, h);
}
fn mainposition(t: *const LuaTable, key: *const TValue) [*]LuaNode {
comptime std.debug.assert(@sizeOf(LuaNode) == @sizeOf(TValue) * 2);
comptime std.debug.assert(@alignOf(LuaNode) == @alignOf(TValue));
return switch (key.typeOf()) {
.Number => hashnum(t, key.nvalue()),
.Integer => hashint(t, key.lvalue()),
.Vector => hashvec(t, key.vvalue()),
.String => hashstr(t, key.tsvalue()),
.Boolean => hashboolean(t, key.bvalue()),
.LightUserdata => hashpointer(t, key.pvalue()),
else => hashpointer(t, @ptrCast(@alignCast(key.gcvalue()))),
};
}
///
/// returns the index for `key` if `key` is an appropriate key to live in
/// the array part of the table, -1 otherwise.
///
fn arrayindex(key: f64) i32 {
const i: i32 = lnumutils.inum2int(key);
return if (@as(f64, @floatFromInt(i)) == key) i else -1;
}
// {=============================================================
// Rehash
// ==============================================================
pub inline fn maybesetaboundary(t: *LuaTable, boundary: i32) void {
if (t.bound.aboundary <= 0)
t.bound.aboundary = -boundary;
}
pub inline fn getaboundary(t: *LuaTable) c_int {
return if (t.bound.aboundary < 0) -t.bound.aboundary else t.sizearray;
}
fn computesizes(nums: []const u32, narray: *usize) usize {
var i: usize = 0;
var twotoi: usize = 1; // 2^i
var a: usize = 0; // number of elements smaller than 2^i
var na: usize = 0; // number of elements to go to array part
var n: usize = 0; // optimal size for array part
while (@divTrunc(twotoi, 2) < narray.*) : (i += 1) {
defer twotoi *= 2;
if (nums[i] > 0) {
a += nums[i];
if (a > @divTrunc(twotoi, 2)) { // more than half elements present?
n = twotoi; // optimal size (till now)
na = a; // all elements smaller than n will go to array part
}
}
if (a == narray.*)
break; // all elements already counted
}
narray.* = n;
std.debug.assert(@divTrunc(narray.*, 2) <= na and na <= narray.*);
return na;
}
fn countint(key: f64, nums: []u32) u1 {
const k = arrayindex(key);
if (0 < k and k <= MAXSIZE) {
// is `key' an appropriate array index?
nums[@intCast(lobject.ceillog2(@intCast(k)))] += 1; // count as such
return 1;
}
return 0;
}
fn numusearray(t: *const LuaTable, nums: []u32) usize {
var lg: u8 = 0;
var ttlg: i32 = 1; // 2^lg
var ause: usize = 0; // summation of `nums'
var i: u32 = 1; // count to traverse all array keys
while (lg <= MAXBITS) : (lg += 1) { // for each slice
defer ttlg *= 2;
var lc: u32 = 0; // counter
var lim: i32 = ttlg;
if (lim > t.sizearray) {
lim = t.sizearray; // adjust upper limit
if (i > lim)
break; // no more elements to count
}
while (i <= lim) : (i += 1) {
if (!t.array.?[i - 1].ttisnil())
lc += 1;
}
nums[lg] = lc;
ause += lc;
}
return ause;
}
fn numusehash(t: *const LuaTable, nums: []u32, pnasize: *usize) u32 {
var totaluse: u32 = 0; // total number of elements
var ause: usize = 0; // summation of `nums'
var i: usize = lobject.sizenode(t);
while (i > 0) : (i -= 1) {
const n: *LuaNode = @ptrCast(t.gnode(i - 1));
if (!n.gval().ttisnil()) {
if (n.gkey().ttisnumber())
ause += countint(n.gkey().nvalue(), nums);
totaluse += 1;
}
}
pnasize.* += ause;
return totaluse;
}
fn setarrayvector(L: *lua.State, t: *LuaTable, size: usize) Error!void {
if (size > MAXSIZE)
return error.@"table overflow";
t.array = try lmem.Mreallocarray(L, TValue, t.array, @intCast(t.sizearray), size, t.header.memcat);
var i: usize = @intCast(t.sizearray);
while (i < size) : (i += 1)
t.array.?[i].setnilvalue();
t.sizearray = @intCast(size);
}
fn setnodevector(L: *lua.State, t: *LuaTable, newsize: usize) Error!void {
var size: usize = newsize;
var lsize: u8 = 0;
if (size == 0) { // no elements to hash part?
t.node = @ptrCast(@alignCast(@constCast(dummynode))); // use common `dummynode'
} else {
lsize = @intCast(lobject.ceillog2(@truncate(newsize)));
if (lsize > MAXBITS)
return error.@"table overflow";
size = lobject.twoto(@intCast(lsize));
t.node = try lmem.Mnewarray(L, LuaNode, size, t.header.memcat);
for (0..size) |i| {
const n: *LuaNode = @ptrCast(t.gnode(i));
n.key.pi.next = 0;
n.gkey().setnilvalue();
n.gval().setnilvalue();
}
}
t.lsizenode = lsize;
t.nodemask8 = @truncate((@as(usize, 1) << @truncate(lsize)) - 1);
t.bound.lastfree = @intCast(size); // all positions are free
}
fn arrayornewkey(L: *lua.State, t: *LuaTable, key: *const TValue) Error!*TValue {
if (key.ttisnumber()) {
const n = key.nvalue();
const k = lnumutils.inum2int(n);
if (@as(f64, @floatFromInt(k)) == n and k - 1 < t.sizearray)
return &t.array.?[@intCast(k - 1)];
}
return newkey(L, t, key);
}
fn resize(L: *lua.State, t: *LuaTable, nasize: usize, nhsize: usize) Error!void {
if (nasize > MAXSIZE or nhsize > MAXSIZE)
return error.@"table overflow";
const oldasize: i32 = t.sizearray;
const oldhsize: u8 = t.lsizenode;
const nold = t.node; // save old hash ...
if (nasize > oldasize) // array part must grow?
try setarrayvector(L, t, nasize);
// create new hash part with appropriate size
try setnodevector(L, t, nhsize);
// used for the migration check at the end
const nnew = t.node;
if (nasize < oldasize) { // array part must shrink?
t.sizearray = @intCast(nasize);
// re-insert elements from vanishing slice
var i: usize = nasize;
while (i < oldasize) : (i += 1) {
if (!t.array.?[i].ttisnil()) {
var ok: TValue = undefined;
ok.setnvalue(@floatFromInt(i + 1));
(try newkey(L, t, &ok)).setobj(L, &t.array.?[i]);
}
}
// shrink array
t.array = try lmem.Mreallocarray(L, TValue, t.array, @intCast(oldasize), nasize, t.header.memcat);
}
// used for the migration check at the end
const anew = t.array;
// re-insert elements from hash part
var i: usize = lobject.twoto(@truncate(oldhsize));
while (i > 0) : (i -= 1) {
const old: *LuaNode = @ptrCast(nold + i - 1);
if (!old.gval().ttisnil()) {
var ok: TValue = undefined;
lobject.getnodekey(L, &ok, old);
(try arrayornewkey(L, t, &ok)).setobj(L, old.gval());
}
}
// make sure we haven't recursively rehashed during element migration
std.debug.assert(nnew == t.node);
std.debug.assert(anew == t.array);
if (@as(*LuaNode, @ptrCast(nold)) != dummynode)
lmem.Mfreearray(L, LuaNode, nold, lobject.twoto(@truncate(oldhsize)), t.header.memcat); // free old array
}
fn adjustasize(t: *LuaTable, size: usize, ek: ?*const TValue) usize {
const tbound: bool = @as(*LuaNode, @ptrCast(t.node)) != dummynode or size < t.sizearray;
const ekindex: i32 = if (ek != null and ek.?.ttisnumber()) arrayindex(ek.?.nvalue()) else -1;
// move the array size up until the boundary is guaranteed to be inside the array part
var adjusted_size = size;
while (adjusted_size + 1 == ekindex or (tbound and !Hgetnum(t, @intCast(adjusted_size + 1)).ttisnil()))
adjusted_size += 1;
return adjusted_size;
}
pub fn Hresizearray(L: *lua.State, t: *LuaTable, nasize: usize) Error!void {
const nsize = if (@as(*LuaNode, @ptrCast(t.node)) == dummynode) 0 else lobject.sizenode(t);
const asize = adjustasize(t, nasize, null);
try resize(L, t, asize, nsize);
}
pub fn Hresizehash(L: *lua.State, t: *LuaTable, nhsize: usize) Error!void {
try resize(L, t, @intCast(t.sizearray), nhsize);
}
fn rehash(L: *lua.State, t: *LuaTable, ek: *const TValue) Error!void {
var nums: [MAXBITS + 1]u32 = [_]u32{0} ** (MAXBITS + 1);
var nasize = numusearray(t, nums[0..]); // count keys in array part
var totaluse: usize = nasize; // all those keys are integer keys
totaluse += numusehash(t, nums[0..], &nasize); // count keys in hash part
// count extra key
if (ek.ttisnumber())
nasize += countint(ek.nvalue(), nums[0..]);
totaluse += 1;
// compute new size for array part
const na = computesizes(nums[0..], &nasize);
var nh = totaluse - na;
// enforce the boundary invariant; for performance, only do hash lookups if we must
const nadjusted = adjustasize(t, nasize, ek);
// count how many extra elements belong to array part instead of hash part
const aextra = nadjusted - nasize;
if (aextra != 0) {
// we no longer need to store those extra array elements in hash part
nh -= aextra;
// because hash nodes are twice as large as array nodes, the memory we saved for hash parts can be used by array part
// this follows the general sparse array part optimization where array is allocated when 50% occupation is reached
nasize = nadjusted + aextra;
// since the size was changed, it's again important to enforce the boundary invariant at the new size
nasize = adjustasize(t, nasize, ek);
}
// resize the table to new computed sizes
try resize(L, t, nasize, nh);
}
pub fn Hnew(L: *lua.State, narray: u32, nhash: u32) Error!*LuaTable {
const t = try lmem.Mnewgco(L, LuaTable, @sizeOf(LuaTable), L.header.memcat);
lgc.Cinit(L, @ptrCast(@alignCast(t)), @intFromEnum(lua.Type.Table));
t.metatable = null;
t.tmcache = ~(@as(u8, 0));
t.array = null;
t.sizearray = 0;
t.bound.lastfree = 0;
t.lsizenode = 0;
t.readonly = 0;
t.safeenv = 0;
t.nodemask8 = 0;
t.node = @ptrCast(@alignCast(@constCast(dummynode)));
if (narray > 0)
try setarrayvector(L, t, narray);
if (nhash > 0)
try setnodevector(L, t, nhash);
return t;
}
pub fn Hfree(L: *lua.State, t: *LuaTable, page: *lmem.lua_Page) void {
if (@as(*LuaNode, @ptrCast(t.node)) != dummynode)
lmem.Mfreearray(L, LuaNode, t.node, lobject.sizenode(t), t.header.memcat);
if (t.array) |arr|
lmem.Mfreearray(L, TValue, arr, @intCast(t.sizearray), t.header.memcat);
lmem.Mfreegco(L, t.obj2gco(), @sizeOf(LuaTable), t.header.memcat, page);
}
fn getfreepos(t: *LuaTable) ?*LuaNode {
while (t.bound.lastfree > 0) {
t.bound.lastfree -= 1;
const n: *LuaNode = @ptrCast(t.gnode(@intCast(t.bound.lastfree)));
if (n.gkey().ttisnil())
return n;
}
return null; // could not find a free place
}
//
// inserts a new key into a hash table; first, check whether key's main
// position is free. If not, check whether colliding node is in its main
// position or not: if it is not, move colliding node to an empty place and
// put new key in its main position; otherwise (colliding node is in its main
// position), new key goes to an empty position.
//
fn newkey(L: *lua.State, t: *LuaTable, key: *const TValue) Error!*TValue {
// enforce boundary invariant
if (key.ttisnumber() and key.nvalue() == @as(f64, @floatFromInt(t.sizearray + 1))) {
try rehash(L, t, key); // grow table
// after rehash, numeric keys might be located in the new array part, but won't be found in the node part
return arrayornewkey(L, t, key);
}
var mp: *LuaNode = @ptrCast(mainposition(t, key));
if (!mp.gval().ttisnil() or mp == dummynode) {
const n = getfreepos(t) orelse {
// cannot find a free place?
try rehash(L, t, key); // grow table
// after rehash, numeric keys might be located in the new array part, but won't be found in the node part
return arrayornewkey(L, t, key);
}; // get a free place
std.debug.assert(@as(*LuaNode, @ptrCast(n)) != dummynode);
var mk: TValue = undefined;
lobject.getnodekey(L, &mk, mp);
var othern: *LuaNode = @ptrCast(mainposition(t, &mk));
if (othern != mp) { // is colliding node out of its main position?
// yes; move colliding node into free position
while (othern.add_num(othern.gnext()) != mp)
othern = othern.add_num(othern.gnext()); // find previous
othern.key.pi.next = @truncate(n.sub(othern)); // redo the chain with `n' in place of `mp'
n.* = mp.*; // copy colliding node into free pos. (mp->next also goes)
if (mp.gnext() != 0) {
n.key.pi.next += @truncate(mp.sub(n)); // correct 'next'
mp.key.pi.next = 0; // now 'mp' is free
}
mp.gval().setnilvalue();
} else { // colliding node is in its own main position
// new node will go into free position
if (mp.gnext() != 0)
n.key.pi.next = @truncate((mp.add_num(mp.gnext())).sub(n)) // chain new position
else
std.debug.assert(n.gnext() == 0);
mp.key.pi.next = @truncate(n.sub(mp));
mp = n;
}
}
lobject.setnodekey(L, mp, key);
lgc.Cbarriert(L, t, key);
std.debug.assert(mp.gval().ttisnil());
return mp.gval();
}
//
// search function for integers
//
pub fn Hgetnum(t: *LuaTable, key: i32) *const TValue {
// (1 <= key && key <= t->sizearray)
if (@as(u32, @intCast(key - 1)) < @as(u32, @intCast(t.sizearray)))
return &t.array.?[@as(u32, @intCast(key - 1))]
else if (@as(*LuaNode, @ptrCast(t.node)) != dummynode) {
// hash fallback
const nk: f64 = @floatFromInt(key);
var n = hashnum(t, nk);
while (true) { // check whether `key' is somewhere in the chain
if (n[0].gkey().ttisnumber() and n[0].gkey().nvalue() == nk)
return n[0].gval(); // that's it
if (n[0].gnext() == 0)
break;
n = @ptrCast(n[0].add_num(n[0].gnext()));
}
}
return lobject.Onilobject;
}
pub fn Hgetstr(t: *LuaTable, key: *lobject.TString) *const TValue {
var n: *LuaNode = @ptrCast(hashstr(t, key));
while (true) { // check whether `key' is somewhere in the chain
if (n.gkey().ttisstring() and n.gkey().tsvalue() == key)
return n.gval(); // that's it
if (n.gnext() == 0)
break;
n = n.add_num(n.gnext());
}
return lobject.Onilobject;
}
pub fn Hget(t: *LuaTable, key: *const TValue) *const TValue {
switch (key.typeOf()) {
.Nil => return lobject.Onilobject,
.String => return Hgetstr(t, key.tsvalue()),
.Number => {
const k = lnumutils.inum2int(key.nvalue());
if (@as(f64, @floatFromInt(k)) == key.nvalue()) // index is int?
return Hgetnum(t, k); // use specialized version
// else go through
},
else => {},
}
var n = mainposition(t, key);
while (true) { // check whether `key' is somewhere in the chain
if (lobject.OrawequalKey(n[0].gkey(), key))
return n[0].gval(); // that's it
if (n[0].gnext() == 0)
break;
n = @ptrCast(n[0].add_num(n[0].gnext()));
}
return lobject.Onilobject; // not found
}
pub fn Hset(L: *lua.State, t: *LuaTable, key: *const TValue) Error!*TValue {
const p = Hget(t, key);
invalidateTMcache(t);
if (p != lobject.Onilobject)
return @constCast(p)
else
return try Hnewkey(L, t, key);
}
pub fn Hnewkey(L: *lua.State, t: *LuaTable, key: *const TValue) Error!*TValue {
if (key.ttisnil())
return error.@"table index is nil";
if (key.ttisnumber() and lnumutils.inumisnan(key.nvalue()))
return error.@"table index is nan";
if (key.ttisvector() and lnumutils.ivecisnan(key.vvalue()))
return error.@"table index contains nan";
return newkey(L, t, key);
}
pub fn Hsetnum(L: *lua.State, t: *LuaTable, key: i32) Error!*TValue {
// (1 <= key && key <= t->sizearray)
if (key - 1 < t.sizearray)
return &t.array.?[@intCast(key - 1)];
// hash fallback
const p = Hgetnum(t, key);
if (p != lobject.Onilobject)
return @constCast(p)
else {
var k: TValue = undefined;
k.setnvalue(@floatFromInt(key));
return newkey(L, t, &k);
}
}
pub fn Hsetstr(L: *lua.State, t: *LuaTable, key: *lobject.TString) Error!*TValue {
const p = Hgetstr(t, key);
invalidateTMcache(t);
if (p != lobject.Onilobject)
return @constCast(p)
else {
var k: TValue = undefined;
k.setsvalue(L, key);
return newkey(L, t, &k);
}
}
fn updateaboundary(t: *LuaTable, boundary: u32) u32 {
if (boundary < t.sizearray and t.array.?[boundary - 1].ttisnil()) {
if (boundary >= 2 and !t.array.?[boundary - 2].ttisnil()) {
maybesetaboundary(t, @intCast(boundary - 1));
return boundary - 1;
}
} else if (boundary + 1 < t.sizearray and !t.array.?[boundary].ttisnil() and t.array.?[boundary + 1].ttisnil()) {
maybesetaboundary(t, @intCast(boundary + 1));
return boundary + 1;
}
return 0;
}
/// Try to find a boundary in table `t'. A `boundary' is an integer index
/// such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil).
pub fn Hgetn(t: *LuaTable) usize {
const boundary = getaboundary(t);
const array_size: usize = @intCast(t.sizearray);
if (boundary > 0) {
if (!t.array.?[array_size - 1].ttisnil() and @as(*lobject.LuaNode, @ptrCast(t.node)) == dummynode)
return @intCast(array_size); // fast-path: the end of the array in `t' already refers to a boundary
if (boundary < array_size and !t.array.?[@as(u32, @intCast(boundary)) - 1].ttisnil() and t.array.?[@intCast(boundary)].ttisnil())
return @intCast(boundary); // fast-path: boundary already refers to a boundary in `t'
const foundboundary = updateaboundary(t, @intCast(boundary));
if (foundboundary > 0)
return @intCast(foundboundary);
}
if (array_size > 0 and t.array.?[array_size - 1].ttisnil()) {
// "branchless" binary search from Array Layouts for Comparison-Based Searching, Paul Khuong, Pat Morin, 2017.
// note that clang is cmov-shy on cmovs around memory operands, so it will compile this to a branchy loop.
var base = t.array.?;
var rest = array_size;
var half = rest >> 1;
while (half > 0) : (half = rest >> 1) {
base = if (base[half].ttisnil()) base else base[half..];
rest -= half;
}
const _boundary = @as(usize, if (!base[0].ttisnil()) 1 else 0) + (base - t.array.?);
maybesetaboundary(t, @intCast(_boundary));
return _boundary;
} else {
// validate boundary invariant
std.debug.assert(@as(*lobject.LuaNode, @ptrCast(t.node)) == dummynode or Hgetnum(t, @intCast(array_size + 1)).ttisnil());
return array_size;
}
}
pub fn Hclone(L: *lua.State, tt: *LuaTable) Error!*LuaTable {
const t = try lmem.Mnewgco(L, LuaTable, @sizeOf(LuaTable), L.activememcat);
lgc.Cinit(L, @ptrCast(@alignCast(t)), @intFromEnum(lua.Type.Table));
t.metatable = tt.metatable;
t.tmcache = tt.tmcache;
t.array = null;
t.sizearray = 0;
t.lsizenode = 0;
t.nodemask8 = 0;
t.readonly = 0;
t.safeenv = 0;
t.node = @ptrCast(@constCast(dummynode));
t.bound.lastfree = 0;
if (tt.sizearray > 0) {
t.array = try lmem.Mnewarray(L, TValue, @intCast(tt.sizearray), tt.header.memcat);
maybesetaboundary(t, getaboundary(tt));
t.sizearray = tt.sizearray;
@memcpy(t.array.?[0..@intCast(tt.sizearray)], tt.array.?[0..@intCast(tt.sizearray)]);
}
if (@as(*LuaNode, @ptrCast(tt.node)) != dummynode) {
const size = @as(usize, 1) << @as(if (@sizeOf(usize) == 8) u6 else u5, @truncate(tt.lsizenode));
t.node = try lmem.Mnewarray(L, LuaNode, size, tt.header.memcat);
t.lsizenode = tt.lsizenode;
t.nodemask8 = tt.nodemask8;
@memcpy(t.node[0..@intCast(size)], tt.node[0..@intCast(size)]);
t.bound.lastfree = tt.bound.lastfree;
}
return t;
}
pub fn Hclear(tt: *LuaTable) void {
// clear array part
for (0..@intCast(tt.sizearray)) |i|
tt.array.?[i].setnilvalue();
maybesetaboundary(tt, 0);
// clear hash part
if (@as(*LuaNode, @ptrCast(tt.node)) != dummynode) {
const size = lobject.sizenode(tt);
tt.bound.lastfree = @intCast(size);
for (0..@intCast(size)) |i| {
const n = tt.gnode(i);
n[0].gkey().setttype(.Nil);
n[0].gval().setnilvalue();
n[0].key.pi.next = 0;
}
}
// back to empty -> no tag methods present
tt.tmcache = ~@as(u8, 0);
}
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_table(@ptrCast(L));
}
+132
View File
@@ -0,0 +1,132 @@
const std = @import("std");
const lua = @import("lua.zig");
const lobject = @import("lobject.zig");
const lstate = @import("lstate.zig");
const ltable = @import("ltable.zig");
const lstring = @import("lstring.zig");
const Errorset = @import("errorset.zig");
pub const TMS = enum {
TM_INDEX,
TM_NEWINDEX,
TM_MODE,
TM_NAMECALL,
TM_CALL,
TM_ITER,
TM_LEN,
TM_EQ, // last tag method with `fast' access
TM_ADD,
TM_SUB,
TM_MUL,
TM_DIV,
TM_IDIV,
TM_MOD,
TM_POW,
TM_UNM,
TM_LT,
TM_LE,
TM_CONCAT,
TM_TYPE,
TM_METATABLE,
TM_N, // number of elements in the enum
};
pub const N: comptime_int = @intFromEnum(TMS.TM_N);
pub const typenames = [_][:0]const u8{
// ORDER TYPE
"nil",
"boolean",
"userdata",
"number",
"integer",
"vector",
"string",
"table",
"function",
"userdata",
"thread",
"buffer",
"class",
"object",
};
pub const eventname = [_][:0]const u8{
// ORDER TM
"__index",
"__newindex",
"__mode",
"__namecall",
"__call",
"__iter",
"__len",
"__eq",
"__add",
"__sub",
"__mul",
"__div",
"__idiv",
"__mod",
"__pow",
"__unm",
"__lt",
"__le",
"__concat",
"__type",
"__metatable",
};
comptime {
if (typenames.len != lua.Type.T_COUNT)
@compileError("typenames size mismatch");
if (eventname.len != N)
@compileError("eventname size mismatch");
if (@intFromEnum(TMS.TM_EQ) >= 8)
@compileError("fasttm optimization stores a bitfield with metamethods in a byte");
}
pub fn Tinit(L: *lua.State) Errorset.Memory!void {
for (0..@intCast(lua.Type.T_COUNT)) |i| {
L.global.ttname[i] = try lstring.Snew(L, typenames[i]);
lstring.Sfix(L.global.ttname[i]); // never collect these names
}
for (0..N) |i| {
L.global.tmname[i] = try lstring.Snew(L, eventname[i]);
lstring.Sfix(L.global.tmname[i]); // never collect these names
}
}
pub const LONGEST_TYPENAME_SIZE = res: {
var large = 0;
for (typenames) |name|
large = @max(large, name.len);
break :res large;
};
pub fn gfasttm(g: *lstate.global_State, et: ?*lobject.LuaTable, event: TMS) ?*const lobject.TValue {
const mt = et orelse return null;
if (mt.tmcache & (@as(usize, 1) << @intFromEnum(event)) > 0)
return null;
return Tgettm(mt, event, g.tmname[@intFromEnum(event)]);
}
pub fn Tgettm(events: *lobject.LuaTable, e: TMS, ename: *lobject.TString) ?*const lobject.TValue {
const tm = ltable.Hgetstr(events, ename);
if (tm.ttisnil()) {
// no tag method?
events.tmcache |= @truncate(@as(usize, 1) << @intFromEnum(e)); // cache this fact
return null;
}
return tm;
}
+268
View File
@@ -0,0 +1,268 @@
const std = @import("std");
pub const c = @import("c");
const lstate = @import("lstate.zig");
pub const config = @import("luaconf.zig");
pub const MULTRET = c.LUA_MULTRET;
// pseudo-indices
pub const REGISTRYINDEX = c.LUA_REGISTRYINDEX;
pub const GLOBALSINDEX = c.LUA_GLOBALSINDEX;
pub const ENVIRONINDEX = c.LUA_ENVIRONINDEX;
pub fn upvalueindex(i: i32) i32 {
return GLOBALSINDEX - i;
}
pub fn ispseudo(i: i32) bool {
return i <= REGISTRYINDEX;
}
// thread status; 0 is OK
pub const Status = enum(u3) {
Ok = 0,
Yield,
ErrRun,
/// legacy error code, preserved for compatibility
ErrSyntax,
ErrMem,
ErrErr,
/// yielded for a debug breakpoint
Break,
pub fn check(s: Status) !Status {
switch (s) {
.ErrErr, .ErrRun => return error.Runtime,
.ErrMem => return error.OutOfMemory,
.ErrSyntax => return error.BadSyntax,
else => return s,
}
}
};
pub const CoStatus = enum(u3) {
/// running
Running = 0,
/// suspended
Suspended,
/// 'normal' (it resumed another coroutine)
Normal,
/// finished
Finished,
/// finished with error
FinishedErr,
};
pub const State = lstate.lua_State;
pub const CFunction = *const fn (L: *State) callconv(.c) c_int;
pub const Continuation = *const fn (L: *State, status: c_int) callconv(.c) c_int;
pub const Destructor = *const fn (L: *State, ?*anyopaque) callconv(.c) void;
pub const Coverage = *const fn (?*anyopaque, [*c]const u8, c_int, c_int, [*c]const c_int, usize) callconv(.c) void;
///
/// prototype for memory-allocation functions
///
pub const Alloc = *const fn (ud: ?*anyopaque, ptr: ?*anyopaque, osize: usize, nsize: usize) callconv(.c) ?*anyopaque;
///
/// basic types
///
pub const TNONE = c.LUA_TNONE;
/// Must be a signed integer because LuaType.none is -1
pub const Type = enum(i6) {
None = TNONE,
Nil = c.LUA_TNIL, // must be 0 due to lua_isnoneornil
Boolean = c.LUA_TBOOLEAN, // must be 1 due to l_isfalse
LightUserdata = c.LUA_TLIGHTUSERDATA,
Number = c.LUA_TNUMBER,
Integer = c.LUA_TINTEGER,
Vector = c.LUA_TVECTOR,
String = c.LUA_TSTRING, // all types above this must be value types, all types below this must be GC types - see iscollectable
Table = c.LUA_TTABLE,
Function = c.LUA_TFUNCTION,
Userdata = c.LUA_TUSERDATA,
Thread = c.LUA_TTHREAD,
Buffer = c.LUA_TBUFFER,
Class = c.LUA_TCLASS,
Object = c.LUA_TOBJECT,
// values below this line are used in GCObject tags but may never show up in TValue type tags
/// LUA_TDEADKEY is used in TKey to identify Luau table entries that have the value set to nil,
/// so that we can remove the strong reference to the key.
Deadkey = c.LUA_TDEADKEY,
// These values should never show up in TValue tag types.
Proto = c.LUA_TPROTO,
UpVal = c.LUA_TUPVAL,
// the count of TValue type tags
pub const T_COUNT = c.LUA_T_COUNT;
pub inline fn isnoneornil(t: Type) bool {
return t == .None or t == .Nil;
}
pub inline fn istypecollectable(comptime t: Type) bool {
return @intFromEnum(t) >= @intFromEnum(Type.String);
}
};
// type of numbers in Luau
pub const Number = c.lua_Number;
// type for integer functions
pub const Integer = c.lua_Integer;
// unsigned integer type
pub const Unsigned = c.lua_Unsigned;
///
/// garbage-collection function and options
///
pub const GCOp = enum(u4) {
// stop and resume incremental garbage collection
Stop = c.LUA_GCSTOP,
Restart = c.LUA_GCRESTART,
// run a full GC cycle; not recommended for latency sensitive applications
Collect = c.LUA_GCCOLLECT,
// return the heap size in KB and the remainder in bytes
Count = c.LUA_GCCOUNT,
CountB = c.LUA_GCCOUNTB,
// return 1 if GC is active (not stopped); note that GC may not be actively collecting even if it's running
IsRunning = c.LUA_GCISRUNNING,
///
/// perform an explicit GC step, with the step size specified in KB
///
/// garbage collection is handled by 'assists' that perform some amount of GC work matching pace of allocation
/// explicit GC steps allow to perform some amount of work at custom points to offset the need for GC assists
/// note that GC might also be paused for some duration (until bytes allocated meet the threshold)
/// if an explicit step is performed during this pause, it will trigger the start of the next collection cycle
///
Step = c.LUA_GCSTEP,
///
/// tune GC parameters G (goal), S (step multiplier) and step size (usually best left ignored)
///
/// garbage collection is incremental and tries to maintain the heap size to balance memory and performance overhead
/// this overhead is determined by G (goal) which is the ratio between total heap size and the amount of live data in it
/// G is specified in percentages; by default G=200% which means that the heap is allowed to grow to ~2x the size of live data.
///
/// collector tries to collect S% of allocated bytes by interrupting the application after step size bytes were allocated.
/// when S is too small, collector may not be able to catch up and the effective goal that can be reached will be larger.
/// S is specified in percentages; by default S=200% which means that collector will run at ~2x the pace of allocations.
///
/// it is recommended to set S in the interval [100 / (G - 100), 100 + 100 / (G - 100))] with a minimum value of 150%; for example:
/// - for G=200%, S should be in the interval [150%, 200%]
/// - for G=150%, S should be in the interval [200%, 300%]
/// - for G=125%, S should be in the interval [400%, 500%]
///
SetGoal = c.LUA_GCSETGOAL,
SetStepMul = c.LUA_GCSETSTEPMUL,
SetStepSize = c.LUA_GCSETSTEPSIZE,
};
///
/// reference system, can be used to pin objects
///
pub const NOREF = c.LUA_NOREF;
pub const REFNIL = c.LUA_REFNIL;
pub const Hook = *const fn (?*State, [*c]c.lua_Debug) callconv(.c) void;
pub const Debug = struct {
what: Context = .lua,
name: ?[:0]const u8 = null,
source: ?[:0]const u8 = null,
short_src: ?[]u8 = null,
linedefined: ?u32 = null,
currentline: ?u32 = null,
nupvals: u8 = 0,
nparams: u8 = 0,
isvararg: u8 = 0,
ssbuf: [config.IDSIZE:0]u8,
pub const Context = enum {
lua,
c,
main,
tail,
};
pub fn fromLua(self: *Debug, ar: c.lua_Debug, options: []const u8) void {
if (std.mem.indexOf(u8, options, "n")) |_| {
if (ar.name != null)
self.name = std.mem.span(ar.name);
}
if (std.mem.indexOf(u8, options, "s")) |_| {
self.source = std.mem.span(ar.source);
const short_src: [:0]const u8 = std.mem.span(ar.short_src);
@memcpy(self.ssbuf[0..short_src.len], short_src[0.. :0]);
self.short_src = self.ssbuf[0..short_src.len];
if (ar.linedefined >= 0)
self.linedefined = @intCast(ar.linedefined);
self.what = blk: {
const what = std.mem.span(ar.what);
if (std.mem.eql(u8, "Lua", what)) break :blk .lua;
if (std.mem.eql(u8, "C", what)) break :blk .c;
if (std.mem.eql(u8, "main", what)) break :blk .main;
if (std.mem.eql(u8, "tail", what)) break :blk .tail;
unreachable;
};
}
if (std.mem.indexOf(u8, options, "l")) |_| {
if (ar.currentline >= 0)
self.currentline = @intCast(ar.currentline);
}
if (std.mem.indexOf(u8, options, "u")) |_|
self.nupvals = ar.nupvals;
if (std.mem.indexOf(u8, options, "a")) |_| {
self.nparams = ar.nparams;
self.isvararg = ar.isvararg;
}
}
};
/// Callbacks that can be used to reconfigure behavior of the VM dynamically.
/// These are shared between all coroutines.
///
/// Note: interrupt is safe to set from an arbitrary thread but all other callbacks
/// can only be changed when the VM is not running any code
pub const Callbacks = extern struct {
/// arbitrary userdata pointer that is never overwritten by Luau
userdata: ?*anyopaque = null,
/// gets called at safepoints (loop back edges, call/ret, gc) if set
interrupt: ?*const fn (L: *State, gc: c_int) callconv(.c) void = null,
/// gets called when an unprotected error is raised (if longjmp is used)
panic: ?*const fn (L: *State, errcode: c_int) callconv(.c) void = null,
/// gets called when L is created (LP == parent) or destroyed (LP == NULL)
userthread: ?*const fn (LP: ?*State, L: *State) callconv(.c) void = null,
/// gets called when a string is created; returned atom can be retrieved via tostringatom
useratom: ?*const fn (L: *State, s: [*c]const u8, l: usize) callconv(.c) i16 = null,
/// gets called when BREAK instruction is encountered
debugbreak: ?*const fn (L: *State, ar: *c.lua_Debug) callconv(.c) void = null,
/// gets called after each instruction in single step mode
debugstep: ?*const fn (L: *State, ar: *c.lua_Debug) callconv(.c) void = null,
/// gets called when thread execution is interrupted by break in another thread
debuginterrupt: ?*const fn (L: *State, ar: *c.lua_Debug) callconv(.c) void = null,
/// gets called when protected call results in an error
debugprotectederror: ?*const fn (L: *State) callconv(.c) void = null,
/// gets called when memory is allocated
onallocate: ?*const fn (L: *State, osize: usize, nsize: usize) callconv(.c) void = null,
};
+57
View File
@@ -0,0 +1,57 @@
const c = @import("c");
const config = @import("config");
pub const LUAU_VERSION = config.luau_version;
/// Can be used to reconfigure internal error handling to use longjmp instead of C++ EH
pub const USE_LONGJMP = c.LUA_USE_LONGJMP;
/// LUA_IDSIZE gives the maximum size for the description of the source
pub const IDSIZE = c.LUA_IDSIZE;
/// LUA_MINSTACK is the guaranteed number of Lua stack slots available to a C function
pub const MINSTACK = c.LUA_MINSTACK;
/// LUAI_MAXCSTACK limits the number of Lua stack slots that a C function can use
pub const I_MAXCSTACK = c.LUAI_MAXCSTACK;
/// LUAI_MAXCALLS limits the number of nested calls
pub const I_MAXCALLS = c.LUAI_MAXCALLS;
/// LUAI_MAXCCALLS is the maximum depth for nested C calls; this limit depends on native stack size
pub const I_MAXCCALLS = c.LUAI_MAXCCALLS;
/// buffer size used for on-stack string operations; this limit depends on native stack size
pub const BUFFERSIZE = c.LUA_BUFFERSIZE;
/// number of valid Lua userdata tags
pub const UTAG_LIMIT = c.LUA_UTAG_LIMIT;
/// number of valid Lua lightuserdata tags
pub const LUTAG_LIMIT = c.LUA_LUTAG_LIMIT;
/// upper bound for number of size classes used by page allocator
pub const SIZECLASSES = c.LUA_SIZECLASSES;
/// available number of separate memory categories
pub const MEMORY_CATEGORIES = c.LUA_MEMORY_CATEGORIES;
/// extra storage for execution callbacks in global state
pub const EXECUTION_CALLBACK_STORAGE = c.LUA_EXECUTION_CALLBACK_STORAGE;
/// minimum size for the string table (must be power of 2)
pub const MINSTRTABSIZE = c.LUA_MINSTRTABSIZE;
/// maximum number of captures supported by pattern matching
pub const MAXCAPTURES = c.LUA_MAXCAPTURES;
pub const I_USER_ALIGNMENT_T = extern union {
u: f64,
s: *anyopaque,
l: c_long,
};
/// The length of Luau vector values, either 3 or 4.
pub const VECTOR_SIZE = if (config.use_4_vector) 4 else 3;
pub const EXTRA_SIZE = (VECTOR_SIZE - 2);
+57
View File
@@ -0,0 +1,57 @@
const std = @import("std");
const builtin = @import("builtin");
const lua = @import("lua.zig");
const lobject = @import("lobject.zig");
const lgc = @import("lgc.zig");
const lmem = @import("lmem.zig");
const Errorset = @import("errorset.zig");
/// special tag value is used for user data with inline dtors
pub const UTAG_IDTOR = lua.config.UTAG_LIMIT;
/// special tag value is used for newproxy-created user data (all other user data objects are host-exposed)
pub const UTAG_PROXY = (lua.config.UTAG_LIMIT + 1);
/// must be updated if more internal tags are added
pub const UTAG_INTERNAL_LIMIT = UTAG_PROXY + 1;
pub inline fn sizeudata(len: usize) usize {
return @offsetOf(lobject.Udata, "data") + (if (len > 16) ((len + 15) & ~@as(usize, 15)) else len);
}
pub fn Unewudata(L: *lua.State, s: usize, tag: u8) Errorset.Memory!*lobject.Udata {
if (s > std.math.maxInt(i32) - @sizeOf(lobject.Udata))
return error.BlockTooBig;
const u = try lmem.Mnewgco(L, lobject.Udata, sizeudata(s), L.activememcat);
lgc.Cinit(L, @ptrCast(@alignCast(u)), @intFromEnum(lua.Type.Userdata));
u.metatable = null;
u.len = @intCast(s);
u.tag = tag;
return u;
}
pub fn Ufreeudata(L: *lua.State, u: *lobject.Udata, page: *lmem.lua_Page) void {
if (u.tag < lua.config.UTAG_LIMIT) {
// TODO: access to L here is highly unsafe since this is called during internal GC traversal
// certain operations such as lua_getthreaddata are okay, but by and large this risks crashes on improper use
if (L.global.udatagc[u.tag]) |dtor|
dtor(L, @ptrCast(@alignCast(&u.data)));
} else if (u.tag == UTAG_IDTOR) {
const InlineDtor = *const fn (data: ?*anyopaque) callconv(.c) void;
var dtor: ?InlineDtor = null;
dtor = @ptrFromInt(std.mem.readVarInt(
usize,
(@as([*]u8, @ptrCast(&u.data)) + @as(u32, @intCast(u.len)) - @sizeOf(InlineDtor))[0..@sizeOf(InlineDtor)],
builtin.cpu.arch.endian(),
));
if (dtor) |d|
d(@ptrCast(@alignCast(&u.data)));
}
lmem.Mfreegco(L, u.obj2gco(), sizeudata(@intCast(u.len)), u.header.memcat, page);
}
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_utf8(@ptrCast(L));
}
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_vector(@ptrCast(L));
}
+7
View File
@@ -0,0 +1,7 @@
const lua = @import("lua.zig");
const lobject = @import("lobject.zig");
const lvmutils = @import("lvmutils.zig");
pub inline fn equalobj(L: *lua.State, o1: *const lobject.TValue, o2: *const lobject.TValue) bool {
return o1.ttype() == o2.ttype() and lvmutils.Vequalval(L, o1, o2);
}
+9
View File
@@ -0,0 +1,9 @@
const c = @import("c");
const std = @import("std");
const lua = @import("lua.zig");
pub inline fn load(L: *lua.State, chunkname: [:0]const u8, bytecode: []const u8, env: i32) !void {
if (c.luau_load(@ptrCast(L), chunkname.ptr, bytecode.ptr, bytecode.len, env) != 0)
return error.Fail;
}
+128
View File
@@ -0,0 +1,128 @@
const std = @import("std");
const lua = @import("lua.zig");
const ltm = @import("ltm.zig");
const ldebug = @import("ldebug.zig");
const lobject = @import("lobject.zig");
const lstring = @import("lstring.zig");
const lnumutils = @import("lnumutils.zig");
pub fn Vtonumber(obj: *const lobject.TValue, n: *lobject.TValue) ?*const lobject.TValue {
if (obj.ttisnumber())
return obj;
if (obj.ttisstring()) {
const num = std.fmt.parseFloat(f64, std.mem.span(obj.svalue())) catch return null;
n.setnvalue(num);
return n;
}
return null;
}
pub fn Vtostring(L: *lua.State, obj: *lobject.TValue) bool {
if (!obj.ttisnumber())
return false
else {
var s: [lnumutils.I_MAXNUM2STR]u8 = undefined;
const n = obj.nvalue();
const e = lnumutils.inum2str(&s, n);
obj.setsvalue(L, lstring.Snewlstr(L, e) catch return false);
return true;
}
}
// pub fn Vlessthan(L: *lua.State, l: *const lobject.TValue, r: *const lobject.TValue) bool {
// if (l.ttype() != r.ttype()) {
// ldebug.Gordererror();
// }
// }
fn get_compTM(L: *lua.State, mt1: *lobject.LuaTable, mt2: *lobject.LuaTable, event: ltm.TMS) ?*const lobject.TValue {
const tm1 = ltm.gfasttm(L.global, mt1, event) orelse return null;
if (mt1 == mt2)
return tm1;
const tm2 = ltm.gfasttm(L.global, mt2, event) orelse return null;
if (lobject.OrawequalObj(tm1, tm2))
return tm1;
return null;
}
// pub fn Vequalval(L: *lua.State, t1: *const lobject.TValue, t2: *const lobject.TValue) bool {
// std.debug.assert(t1.ttype() == t2.ttype());
// switch (t1.ttype()) {
// .Nil => return true,
// .Number => return t1.nvalue() == t2.nvalue(),
// .Vector => lnumutils.iveceq(t1.vvalue(), t2.vvalue()),
// .Boolean => return t1.bvalue() == t2.bvalue(),
// .LightUserdata => return t1.pvalue() == t2.pvalue() and t1.lightuserdatatag() == t2.lightuserdatatag(),
// .Userdata => {
// const tm = get_compTM(L, t1.u.table, t2.u.table, ltm.TMS.TM_EQ) orelse return t1.hvalue() == t2.hvalue();
// callTMres(L, L.top, tm, t1, t2);
// return !(L.top.ttisnil() or L.top.ttisboolean() and !L.top.bvalue());
// },
// }
// }
test Vtonumber {
const allocator = std.testing.allocator;
{
var n = lobject.TValue{ .tt = @intFromEnum(lua.Type.None), .value = undefined };
const obj = lobject.TValue{ .tt = @intFromEnum(lua.Type.Number), .value = .{ .n = 1.0 } };
try std.testing.expect(Vtonumber(&obj, &n) == &obj);
}
{
var n = lobject.TValue{ .tt = @intFromEnum(lua.Type.None), .value = undefined };
const obj = lobject.TValue{ .tt = @intFromEnum(lua.Type.Boolean), .value = .{ .b = 1 } };
try std.testing.expect(Vtonumber(&obj, &n) == null);
}
{
const GCObject = @import("lstate.zig").GCObject;
const gc_buf = try allocator.alloc(u8, @sizeOf(GCObject) + 4);
defer allocator.free(gc_buf);
const gc: *GCObject = @ptrCast(@alignCast(gc_buf[0..@sizeOf(GCObject)]));
gc.* = .{
.gch = .{
.header = .{
.tt = @intFromEnum(lua.Type.String),
.marked = 0,
.memcat = 0,
},
},
};
const data = gc_buf[@offsetOf(lobject.TString, "data")..];
data[0] = '1';
data[1] = '2';
data[2] = '3';
data[3] = 0;
var n = lobject.TValue{ .tt = @intFromEnum(lua.Type.None), .value = undefined };
const obj = lobject.TValue{ .tt = @intFromEnum(lua.Type.String), .value = .{ .gc = gc } };
try std.testing.expect(Vtonumber(&obj, &n) == &n);
try std.testing.expect(n.ttisnumber());
try std.testing.expect(n.nvalue() == 123.0);
}
{
const GCObject = @import("lstate.zig").GCObject;
const gc_buf = try allocator.alloc(u8, @sizeOf(GCObject) + 2);
defer allocator.free(gc_buf);
const gc: *GCObject = @ptrCast(@alignCast(gc_buf[0..@sizeOf(GCObject)]));
gc.* = .{
.gch = .{
.header = .{
.tt = @intFromEnum(lua.Type.String),
.marked = 0,
.memcat = 0,
},
},
};
const data = gc_buf[@offsetOf(lobject.TString, "data")..];
data[0] = 'b';
data[1] = 0;
var n = lobject.TValue{ .tt = @intFromEnum(lua.Type.None), .value = undefined };
const obj = lobject.TValue{ .tt = @intFromEnum(lua.Type.String), .value = .{ .gc = gc } };
try std.testing.expect(Vtonumber(&obj, &n) == null);
}
}
+1591
View File
File diff suppressed because it is too large Load Diff
+125
View File
@@ -0,0 +1,125 @@
#include <bridge.h>
#include "Luau/Common.h"
#include "ldo.h"
#include "lclass.h"
#include <cstdio>
#include <cstdlib>
#include <string>
#include <vector>
LUAU_FASTFLAG(DebugLuauUserDefinedClasses);
LUAU_FASTFLAG(DebugLuauUserDefinedClassesRuntime);
LUAU_FASTFLAG(LuauAllowGlobalDeclarationToBeCalledClass);
LUAU_FASTFLAG(LuauIntegerType2);
ZIG_EXPORT void xsh_set_luau_flags()
{
std::printf("SETTING LUAU FLAGS\n");
FFlag::DebugLuauUserDefinedClasses.value = true;
FFlag::DebugLuauUserDefinedClassesRuntime.value = true;
FFlag::LuauAllowGlobalDeclarationToBeCalledClass.value = true;
FFlag::LuauIntegerType2.value = true;
}
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;
}
ZIG_EXPORT void zig_registerAssertionHandler()
{
Luau::assertHandler() = assertionHandler;
}
ZIG_EXPORT void ZIG_FN(luau_free)(void *ptr)
{
free(ptr);
}
ZIG_EXPORT void ZIG_FN(delete_any)(void* value)
{
operator delete(value);
}
ZIG_EXPORT void* ZIG_FN(new_any)(size_t size)
{
return operator new(size);
}
ZIG_EXPORT size_t ZIG_FN(string_size)(std::string *str)
{
return str->size();
}
ZIG_EXPORT const char* ZIG_FN(string_c_str)(std::string *str)
{
return str->c_str();
}
ZIG_EXPORT Luau::FValue<bool>* zig_luau_getFValueList_bool()
{
return Luau::FValue<bool>::list;
}
ZIG_EXPORT Luau::FValue<int>* zig_luau_getFValueList_int()
{
return Luau::FValue<int>::list;
}
ZIG_EXPORT l_noret zig_luau_luaD_throw(lua_State *L, int errcode)
{
luaD_throw(L, errcode);
}
#if defined(__wasm__)
#include <functional>
#define LUAU_TRY_CATCH(trying, catching) zig_luau_try_catch_js(trying, catching)
#define LUAU_THROW(e) zig_luau_throw_js(e)
#define LUAU_EXTERNAL_TRY_CATCH
#if not defined(LUAU_WASM_ENV_NAME)
#define LUAU_WASM_ENV_NAME "env"
#endif
struct TryCatchContext
{
std::function<void()> trying;
std::function<void(const std::exception &)> catching;
};
// only clang compilers support C/C++ -> wasm so it's safe to use the attribute here
__attribute__((import_module(LUAU_WASM_ENV_NAME), import_name("try_catch"))) void zig_luau_try_catch_js_impl(TryCatchContext *context);
__attribute__((import_module(LUAU_WASM_ENV_NAME), import_name("throw"))) void zig_luau_throw_js_impl(const std::exception *e);
void zig_luau_try_catch_js(std::function<void()> trying, std::function<void(const std::exception &)> catching)
{
auto context = TryCatchContext{trying, catching};
zig_luau_try_catch_js_impl(&context);
}
void zig_luau_throw_js(const std::exception &e)
{
zig_luau_throw_js_impl(&e);
}
ZIG_EXPORT void zig_luau_try_impl(TryCatchContext *context)
{
context->trying();
}
ZIG_EXPORT void zig_luau_catch_impl(TryCatchContext *context, const std::exception &e)
{
context->catching(e);
}
#endif
ZIG_EXPORT int ZIG_FN(luaR_createobject)(lua_State *L)
{
return luaR_createobject(L);
}
+15
View File
@@ -0,0 +1,15 @@
#ifndef LUAU_HEADERS
#define LUAU_HEADERS
#include "lua.h"
#include "lualib.h"
#include "luacode.h"
#if (defined(__x86_64__) || defined(__amd64__) || defined(__aarch64__) || defined(__arm64__) || defined(__ARM64__)) && !defined(__BIG_ENDIAN__)
#include "luacodegen.h"
#endif
#define ZIG_EXPORT extern "C"
#define ZIG_FN(name) zig_##name
#endif // LUAU_HEADERS
+173
View File
@@ -0,0 +1,173 @@
const std = @import("std");
const builtin = @import("builtin");
extern "c" fn zig_string_size(self: *const String) usize;
extern "c" fn zig_string_c_str(self: *const String) [*c]const u8;
pub fn BasicString(comptime value_type: type) type {
return extern struct {
const __min_cap = if ((@sizeOf(__long) - 1) / @sizeOf(value_type) > 2)
(@sizeOf(__long) - 1) / @sizeOf(value_type)
else
2;
const __long = extern struct {
capacity: usize,
size: usize,
ptr: [*c]const value_type,
};
const __short = extern struct {
len: u8,
buffer: [__min_cap - 1:0]value_type,
};
data: extern union {
short: __short,
long: __long,
},
pub inline fn isShort(self: *const String) bool {
return self.data.short.len & 1 == 0;
}
pub fn size(self: *const String) usize {
// if (self.isShort())
// return self.data.short.len >> 1;
// return self.data.long.size;
return zig_string_size(self);
}
pub fn c_str(self: *const String) [*c]const u8 {
// if (self.isShort())
// return self.data.short.buffer;
// return self.data.long.ptr;
return zig_string_c_str(self);
}
pub fn slice(self: *const String) []const u8 {
// if (self.isShort()) {
// const len = self.data.short.len >> 1;
// return self.data.short.buffer[0..len];
// }
// const len = self.data.long.size;
// return self.data.long.ptr[0..len];
const len = zig_string_size(self);
return zig_string_c_str(self)[0..len];
}
};
}
// LLVM: 24
// GCC/MSVC: 32
// data: [24]u8 align(8),
pub const String = BasicString(u8);
comptime {
switch (@sizeOf(usize)) {
4 => {
std.testing.expectEqual(12, @sizeOf(String)) catch @panic("String must be 12 bytes");
std.testing.expectEqual(4, @alignOf(String)) catch @panic("String must be 4-byte aligned");
},
8 => {
std.testing.expectEqual(24, @sizeOf(String)) catch @panic("String must be 24 bytes");
std.testing.expectEqual(8, @alignOf(String)) catch @panic("String must be 8-byte aligned");
},
else => @compileError("Unsupported pointer size"),
}
}
pub fn Exception(comptime T: type) type {
return extern struct {
vtable: *const anyopaque,
value: T,
};
}
pub fn Optional(comptime T: type) type {
return extern struct {
value: T = undefined,
has: bool,
pub fn to(self: @This()) ?T {
if (self.has) {
return self.value;
} else {
return null;
}
}
pub const nullopt = @This(){ .has = false };
};
}
pub fn Vector(comptime T: type) type {
return extern struct {
begin: [*]T,
end: [*]T,
capacity_end: [*]T,
const This = @This();
pub fn iterator(self: This) Iterator {
return .{
.current = self.begin,
.end = self.end,
};
}
pub fn size(self: This) usize {
// divExact would not work if the sizeOf(T) doesn't match the C++ std::vector<T>
return @divExact(@intFromPtr(self.end) - @intFromPtr(self.begin), @sizeOf(T));
}
pub fn empty(self: This) bool {
return self.size() == 0;
}
pub fn front(self: This) ?T {
if (self.begin == self.end)
return null
else
return self.begin[0];
}
pub fn back(self: This) ?T {
const i = self.size();
if (i == 0)
return null;
return self.begin[i - 1];
}
pub fn capacity(self: This) usize {
// divExact would not work if the sizeOf(T) doesn't match the C++ std::vector<T>
return @divExact(@intFromPtr(self.capacity_end) - @intFromPtr(self.begin), @sizeOf(T));
}
pub fn at(self: This, pos: usize) T {
std.debug.assert(!self.empty());
std.debug.assert(pos < self.size());
return self.begin[pos];
}
pub const Iterator = struct {
current: [*]T,
end: [*]T,
pub fn next(self: *Iterator) ?T {
if (self.current == self.end)
return null
else {
const value = self.current[0];
self.current = self.current[1..];
return value;
}
}
};
};
}
pub fn Pair(comptime First: type, comptime Second: type) type {
return extern struct {
first: First,
second: Second,
};
}
+346
View File
@@ -0,0 +1,346 @@
const std = @import("std");
const builtin = @import("builtin");
const build_config = @import("config");
pub const codegen = @import("CodeGen/lcodegen.zig");
pub const Analysis = if (build_config.buildAnalysis) struct {
pub const Frontend = @import("Analysis/Frontend.zig");
pub const FileResolver = @import("Analysis/FileResolver.zig");
pub const AstJsonEncoder = @import("Analysis/AstJsonEncoder.zig");
pub const GenericConfigResolver = @import("Analysis/GenericConfigResolver.zig");
test {
inline for (@typeInfo(@This()).@"struct".decls) |decl|
std.testing.refAllDecls(@field(@This(), decl.name));
}
} else void;
pub const Ast = if (build_config.buildAst) struct {
pub const Ast = @import("Ast/Ast.zig");
pub const Cst = @import("Ast/Cst.zig");
pub const Allocator = @import("Ast/Allocator.zig");
pub const Lexer = @import("Ast/Lexer.zig");
pub const Parser = @import("Ast/Parser.zig");
pub const Location = @import("Ast/Location.zig");
test {
inline for (@typeInfo(@This()).@"struct".decls) |decl|
std.testing.refAllDecls(@field(@This(), decl.name));
}
} else void;
pub const Inliner = if (build_config.buildInliner) struct {
pub const luajitinliner = @import("Inliner/luajitinliner.zig");
test {
inline for (@typeInfo(@This()).@"struct".decls) |decl|
std.testing.refAllDecls(@field(@This(), decl.name));
}
} else void;
pub const Common = struct {
pub const DenseHash = @import("Common/DenseHash.zig");
pub const Bytecode = @import("Common/Bytecode.zig");
pub const BytecodeUtils = @import("Common/BytecodeUtils.zig");
pub const ExperimentalFlags = @import("Common/ExperimentalFlags.zig");
pub const Variant = @import("Common/Variant.zig");
test {
inline for (@typeInfo(@This()).@"struct".decls) |decl|
std.testing.refAllDecls(@field(@This(), decl.name));
}
};
pub const Compiler = if (build_config.buildCompiler) struct {
pub const luacode = @import("Compiler/luacode.zig");
pub const Compiler = @import("Compiler/Compiler.zig");
test {
inline for (@typeInfo(@This()).@"struct".decls) |decl|
std.testing.refAllDecls(@field(@This(), decl.name));
}
} else void;
pub const VM = if (build_config.buildVM) struct {
pub const lua = @import("VM/lua.zig");
pub const ldo = @import("VM/ldo.zig");
pub const lgc = @import("VM/lgc.zig");
pub const ltm = @import("VM/ltm.zig");
pub const zapi = @import("VM/zapi.zig");
pub const lapi = @import("VM/lapi.zig");
pub const laux = @import("VM/laux.zig");
pub const lperf = @import("VM/lperf.zig");
pub const linit = @import("VM/linit.zig");
pub const lmem = @import("VM/lmem.zig");
pub const lstate = @import("VM/lstate.zig");
pub const lstring = @import("VM/lstring.zig");
pub const ltable = @import("VM/ltable.zig");
pub const ludata = @import("VM/ludata.zig");
pub const lbuffer = @import("VM/lbuffer.zig");
pub const lclass = @import("VM/lclass.zig");
pub const lfunc = @import("VM/lfunc.zig");
pub const ldebug = @import("VM/ldebug.zig");
pub const lobject = @import("VM/lobject.zig");
pub const lvmload = @import("VM/lvmload.zig");
pub const lcommon = @import("VM/lcommon.zig");
pub const lvm = @import("VM/lvm.zig");
pub const lvmutils = @import("VM/lvmutils.zig");
pub const lgcdebug = @import("VM/lgcdebug.zig");
// libraries
pub const lbitlib = @import("VM/lbitlib.zig");
pub const lbaselib = @import("VM/lbaselib.zig");
pub const lcorolib = @import("VM/lcorolib.zig");
pub const ldblib = @import("VM/ldblib.zig");
pub const lmathlib = @import("VM/lmathlib.zig");
pub const loslib = @import("VM/loslib.zig");
pub const lstrlib = @import("VM/lstrlib.zig");
pub const ltablib = @import("VM/ltablib.zig");
pub const lutf8lib = @import("VM/lutf8lib.zig");
pub const lveclib = @import("VM/lveclib.zig");
// extra
pub const Errorset = @import("VM/errorset.zig");
test {
inline for (@typeInfo(@This()).@"struct".decls) |decl|
std.testing.refAllDecls(@field(@This(), decl.name));
}
} else void;
pub const cpp_std = @import("cpp_std.zig");
test {
_ = Analysis;
_ = Ast;
_ = Common;
_ = Compiler;
_ = VM;
_ = Inliner;
_ = cpp_std;
}
//
// VM
//
pub const LUAU_VERSION = VM.lua.config.LUAU_VERSION;
pub const VECTOR_SIZE = VM.lua.config.VECTOR_SIZE;
pub const State = VM.lua.State;
//
// Compiler
//
pub const compile = Compiler.luacode.compile;
pub const CompileOptions = Compiler.Compiler.CompileOptions;
const c_FlagGroup = extern struct {
names: [*c][*c]const u8,
types: [*c]c_int,
size: usize,
};
fn FValue(comptime T: type) type {
return extern struct {
const Self = @This();
value: T,
dynamic: bool,
name: [*c]const u8,
next: ?*Self,
pub const Iterator = struct {
state: *Self,
consumed: bool = false,
pub fn next(self: *Iterator) ?*Self {
const state = self.state;
if (!self.consumed) {
self.consumed = true;
return state;
}
const n = state.next orelse return null;
self.state = n;
return n;
}
};
pub fn iterator(self: *Self) Iterator {
return .{
.state = self,
};
}
};
}
/// This function is defined in luau.cpp and must be called to define the assertion printer
extern "c" fn zig_registerAssertionHandler() void;
extern "c" fn zig_luau_getFValueList_bool() *FValue(bool);
extern "c" fn zig_luau_getFValueList_int() *FValue(c_int);
// NCG Workarounds - Minimal Debug Support for NCG
/// Luau.CodeGen mock __register_frame for a workaround Luau NCG
export fn __register_frame(frame: *const u8) void {
_ = frame;
}
/// Luau.CodeGen mock __deregister_frame for a workaround Luau NCG
export fn __deregister_frame(frame: *const u8) void {
_ = frame;
}
pub const FFlags = struct {
pub fn Get(comptime T: type) *FValue(if (T == i32) c_int else T) {
if (T == bool)
return zig_luau_getFValueList_bool()
else if (T == c_int or T == i32)
return zig_luau_getFValueList_int()
else
@compileError("Unsupported type");
}
pub fn SetByName(comptime T: type, name: []const u8, value: T) !void {
var iter = Get(T).iterator();
while (iter.next()) |flag| {
if (std.mem.eql(u8, std.mem.span(flag.name), name)) {
flag.value = value;
return;
}
}
return error.UnknownFlag;
}
pub fn GetByName(comptime T: type, name: []const u8) ?*FValue(if (T == i32) c_int else T) {
var iter = Get(T).iterator();
while (iter.next()) |flag| {
if (std.mem.eql(u8, std.mem.span(flag.name), name))
return flag;
}
return null;
}
};
pub const Metamethods = struct {
pub const index = "__index";
pub const newindex = "__newindex";
pub const call = "__call";
pub const concat = "__concat";
pub const unm = "__unm";
pub const add = "__add";
pub const sub = "__sub";
pub const mul = "__mul";
pub const div = "__div";
pub const idiv = "__idiv";
pub const mod = "__mod";
pub const pow = "__pow";
pub const tostring = "__tostring";
pub const metatable = "__metatable";
pub const eq = "__eq";
pub const lt = "__lt";
pub const le = "__le";
pub const mode = "__mode";
pub const len = "__len";
pub const iter = "__iter";
pub const typename = "__type";
pub const namecall = "__namecall";
};
pub const CodeGen = if (build_config.buildCodeGen) struct {
pub fn Supported() bool {
return codegen.supported();
}
pub fn Create(luau: *VM.lua.State) void {
codegen.create(luau);
}
pub fn Compile(luau: *VM.lua.State, idx: i32) void {
codegen.compile(luau, @intCast(idx));
}
} else struct {
pub fn Supported() bool {
return false;
}
pub fn Create(_: *VM.lua.State) void {
@panic("CodeGen is not supported on " ++ @tagName(builtin.target.cpu.arch));
}
pub fn Compile(_: *VM.lua.State, _: i32) void {
@panic("CodeGen is not supported on " ++ @tagName(builtin.target.cpu.arch));
}
};
const alignment = @alignOf(std.c.max_align_t);
/// Allows Luau to allocate memory using a Zig allocator passed in via data.
fn alloc(data: ?*anyopaque, ptr: ?*anyopaque, osize: usize, nsize: usize) callconv(.c) ?*align(alignment) anyopaque {
// just like malloc() returns a pointer "which is suitably aligned for any built-in type",
// the memory allocated by this function should also be aligned for any type that Lua may
// desire to allocate. use the largest alignment for the target
const allocator_ptr: *std.mem.Allocator = @ptrCast(@alignCast(data.?));
if (@as(?[*]align(alignment) u8, @ptrCast(@alignCast(ptr)))) |prev_ptr| {
const prev_slice = prev_ptr[0..osize];
// when nsize is zero the allocator must behave like free and return null
if (nsize == 0) {
allocator_ptr.free(prev_slice);
return null;
}
// when nsize is not zero the allocator must behave like realloc
const new_ptr = allocator_ptr.realloc(prev_slice, nsize) catch return null;
return new_ptr.ptr;
} else if (nsize == 0) {
return null;
} else {
// ptr is null, allocate a new block of memory
const new_ptr = allocator_ptr.alignedAlloc(u8, .fromByteUnits(alignment), nsize) catch return null;
return new_ptr.ptr;
}
}
pub fn getallocator(luau: *VM.lua.State) std.mem.Allocator {
var data: ?*std.mem.Allocator = undefined;
_ = luau.getallocf(@ptrCast(&data));
if (data) |allocator_ptr| {
// Although the Allocator is passed to Lua as a pointer, return a
// copy to make use more convenient.
return allocator_ptr.*;
}
@panic("Lua.allocator() invalid on Lua states created without a Zig allocator");
}
/// Initialize a Luau state with the given allocator
pub fn init(allocator_ptr: *const std.mem.Allocator) !*VM.lua.State {
zig_registerAssertionHandler();
return try VM.lstate.newstate(alloc, @constCast(allocator_ptr));
}
comptime {
if (builtin.target.cpu.arch.isWasm() and build_config.wasm_cxa_exceptions) {
_ = struct {
var exception_buf: [4096]u8 = undefined;
var exception_fba = std.heap.FixedBufferAllocator.init(exception_buf[0..]);
export fn __cxa_allocate_exception(size: usize) callconv(.c) [*]u8 {
const data = exception_fba.allocator().alloc(u8, size + 4) catch unreachable;
std.mem.writeInt(u32, data[0..4], size, .little);
return data[4..].ptr;
}
export fn __cxa_free_exception(data: [*]const u8) callconv(.c) void {
const size = std.mem.readInt(u32, (data - 4)[0..4], .little);
exception_fba.allocator().free(data[0 .. size + 4]);
}
// should NEVER be called as we override in the luau upstream dependency
export fn __cxa_throw(thrown_exception: *u8, cpp_type_info: *anyopaque, dest: *const fn () callconv(.c) void) callconv(.c) noreturn {
_ = thrown_exception;
_ = cpp_type_info;
_ = dest;
unreachable;
}
var threaded: ?std.Io.Threaded = null;
export fn clock() callconv(.c) i64 {
if (threaded == null) {
threaded = std.Io.Threaded.init(std.heap.c_allocator, .{});
}
return std.Io.Timestamp.now(threaded.?.io(), .cpu_process).toMilliseconds();
}
};
}
}
+1949
View File
File diff suppressed because it is too large Load Diff
+54
View File
@@ -0,0 +1,54 @@
const std = @import("std");
const parser = @import("../shell/parser.zig");
const cd = @import("cmds/cd.zig");
const echo = @import("cmds/echo.zig");
const exit = @import("cmds/exit.zig");
const pwd = @import("cmds/pwd.zig");
const run = @import("cmds/run.zig");
pub const Shell = struct {
running: bool = true,
interrupted: bool = false,
environ: *const std.process.Environ.Map,
};
pub fn execute(
io: std.Io,
allocator: std.mem.Allocator,
shell: *Shell,
command: parser.Command,
) !bool {
if (command.argv.len == 0)
return true;
const name = command.argv[0];
if (std.mem.eql(u8, name, "cd")) {
try cd.execute(io, shell.environ, command.argv);
return true;
}
if (std.mem.eql(u8, name, "pwd")) {
try pwd.execute(io, allocator);
return true;
}
if (std.mem.eql(u8, name, "run")) {
try run.execute(io, allocator, command.argv);
return true;
}
if (std.mem.eql(u8, name, "echo")) {
try echo.execute(io, command.argv);
return true;
}
if (std.mem.eql(u8, name, "exit")) {
exit.execute(&shell.running);
return true;
}
return false;
}
+20
View File
@@ -0,0 +1,20 @@
const std = @import("std");
pub fn execute(
io: std.Io,
environ: *const std.process.Environ.Map,
argv: []const []const u8,
) !void {
const path = if (argv.len >= 2)
argv[1]
else
environ.get("USERPROFILE") orelse environ.get("HOME") orelse {
std.debug.print("xsh: cd: cannot find home directory\n", .{});
return;
};
var dir = try std.Io.Dir.cwd().openDir(io, path, .{});
defer dir.close(io);
try std.process.setCurrentDir(io, dir);
}
+16
View File
@@ -0,0 +1,16 @@
const std = @import("std");
pub fn execute(
io: std.Io,
argv: []const []const u8,
) !void {
for (argv[1..], 0..) |argument, index| {
if (index != 0) {
try std.Io.File.stdout().writeStreamingAll(io, " ");
}
try std.Io.File.stdout().writeStreamingAll(io, argument);
}
try std.Io.File.stdout().writeStreamingAll(io, "\n");
}
+3
View File
@@ -0,0 +1,3 @@
pub fn execute(running: *bool) void {
running.* = false;
}
+12
View File
@@ -0,0 +1,12 @@
const std = @import("std");
pub fn execute(
io: std.Io,
allocator: std.mem.Allocator,
) !void {
const path = try std.process.currentPathAlloc(io, allocator);
defer allocator.free(path);
try std.Io.File.stdout().writeStreamingAll(io, path);
try std.Io.File.stdout().writeStreamingAll(io, "\n");
}
+33
View File
@@ -0,0 +1,33 @@
const std = @import("std");
const script = @import("../../script/script.zig");
pub fn execute(
io: std.Io,
allocator: std.mem.Allocator,
argv: []const []const u8,
) !void {
if (argv.len < 2) {
std.debug.print("xsh: run: missing script path\n", .{});
return;
}
var file = try std.Io.Dir.cwd().openFile(io, argv[1], .{});
defer file.close(io);
var file_reader = file.reader(io, &.{});
const source = try file_reader.interface.allocRemaining(
allocator,
.limited(16 * 1024 * 1024),
);
const name_z = try allocator.dupeZ(u8, argv[1]);
try script.run(
allocator,
io,
name_z,
source,
);
}
View File
+12
View File
@@ -0,0 +1,12 @@
pub const prompt = "xsh> ";
pub const max_input_size = 4096;
pub const max_script_size = 16 * 1024 * 1024;
pub const max_exec_args = 64;
pub const features = .{
.luau = true,
.aliases = false,
.history = false,
.autocomplete = false
};
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void xsh_set_luau_flags(void);
#ifdef __cplusplus
}
#endif
+93
View File
@@ -0,0 +1,93 @@
const std = @import("std");
const lexer = @import("shell/lexer.zig");
const parser = @import("shell/parser.zig");
const execute = @import("shell/execute.zig");
const builtin = @import("builtin/builtin.zig");
const script = @import("script/script.zig");
const config = @import("config.zig");
pub fn main(init: std.process.Init) !void {
const allocator = init.gpa;
const io = init.io;
var shell = builtin.Shell{
.environ = init.environ_map,
};
var stderr_buffer: [1024]u8 = undefined;
var stderr_file_writer = std.Io.File.stderr().writer(io, &stderr_buffer);
const stderr = &stderr_file_writer.interface;
while (shell.running) {
try std.Io.File.stdout().writeStreamingAll(io, config.prompt);
var input: [4096]u8 = undefined;
const line = readLine(io, &input) catch |err| {
try stderr.print("xsh: error reading input: {}\n", .{err});
try stderr_file_writer.flush();
continue;
} orelse break;
if (shell.interrupted) {
try std.Io.File.stdout().writeStreamingAll(io, "^C\n");
continue;
}
const source = std.mem.trim(u8, line, " \t\r\n");
if (source.len == 0)
continue;
var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit();
const arena_allocator = arena.allocator();
runPipeline(arena_allocator, io, &shell, source) catch |err| {
try stderr.print("xsh error: {}\n", .{err});
try stderr_file_writer.flush();
};
}
}
fn runPipeline(allocator: std.mem.Allocator, io: std.Io, shell: *builtin.Shell, source: []const u8) !void {
const tokens = try lexer.lex(allocator, source);
const command = try parser.parse(allocator, tokens.items);
try execute.run(
io,
allocator,
shell,
command,
);
}
fn readLine(
io: std.Io,
buffer: []u8,
) !?[]u8 {
var reader_buffer: [1024]u8 = undefined;
var file_reader = std.Io.File.stdin().reader(
io,
&reader_buffer,
);
const reader = &file_reader.interface;
const result = try reader.takeDelimiterInclusive('\n');
if (result.len == 0)
return null;
const len = @min(result.len, buffer.len);
@memcpy(buffer[0..len], result[0..len]);
return buffer[0..len];
}
+63
View File
@@ -0,0 +1,63 @@
const std = @import("std");
const luau = @import("luau");
const c = @cImport({
@cInclude("../include/bridge.h");
});
const State = luau.State;
var shell_io: std.Io = undefined;
pub fn init(L: *State, io: std.Io) !void {
shell_io = io;
try L.newtable();
try L.Zpushfunction(exec, "exec");
try L.setfield(-2, "exec");
try L.setglobal("xsh");
}
fn exec(L: *State) i32 {
const argc = L.gettop();
if (argc == 0) {
L.pushinteger(1);
return 1;
}
var argv: [64][]const u8 = undefined;
if (argc > argv.len) {
L.pushinteger(1);
return 1;
}
for (0..@intCast(argc)) |index| {
const arg = L.tostring(@intCast(index + 1)) orelse {
L.pushinteger(1);
return 1;
};
argv[index] = arg;
}
var child = std.process.spawn(shell_io, .{
.argv = argv[0..@intCast(argc)],
}) catch {
L.pushinteger(1);
return 1;
};
const result = child.wait(shell_io) catch {
L.pushinteger(1);
return 1;
};
_ = result;
L.pushinteger(0);
return 1;
}
+69
View File
@@ -0,0 +1,69 @@
const std = @import("std");
const zluau = @import("luau");
const luau = @import("luau.zig");
const c = @cImport({
@cInclude("../include/bridge.h");
});
pub fn run(
allocator: std.mem.Allocator,
io: std.Io,
name: [:0]const u8,
source: []const u8,
) !void {
c.xsh_set_luau_flags();
var L = try zluau.init(&allocator);
defer L.deinit();
try L.Lopenlibs();
try luau.init(L, io);
const bytecode = zluau.compile(
allocator,
source,
.{},
) catch |err| {
std.debug.print(
"xsh: failed to compile '{s}': {}\n",
.{ name, err },
);
return err;
};
defer allocator.free(bytecode);
// try L.load(name, bytecode, 0);
L.load(name, bytecode, 0) catch |err| {
const message = L.tostring(-1) orelse "unknown Luau load error";
std.debug.print(
"xsh: failed to load '{s}': {s} ({})\n",
.{ name, message, err },
);
L.pop(1);
return err;
};
const call = L.pcall(0, 0, 0);
if (call.check()) |_| {
return;
} else |err| {
const message = L.tostring(-1) orelse "unknown Luau runtime error";
std.debug.print(
"xsh: {s}\n",
.{message},
);
L.pop(1);
return err;
}
}
+24
View File
@@ -0,0 +1,24 @@
const std = @import("std");
const builtin = @import("../builtin/builtin.zig");
const parser = @import("parser.zig");
pub fn run(
io: std.Io,
allocator: std.mem.Allocator,
shell: *builtin.Shell,
command: parser.Command,
) !void {
if (try builtin.execute(io, allocator, shell, command))
return;
var child = try std.process.spawn(
io,
.{
.argv = command.argv,
},
);
_ = try child.wait(io);
}
+29
View File
@@ -0,0 +1,29 @@
const std = @import("std");
pub const Token = struct {
text: []const u8,
};
pub fn lex(
allocator: std.mem.Allocator,
source: []const u8,
) !std.ArrayList(Token) {
var tokens: std.ArrayList(Token) = .empty;
var iterator = std.mem.tokenizeAny(
u8,
source,
" \t\r\n",
);
while (iterator.next()) |word| {
try tokens.append(
allocator,
.{
.text = word,
},
);
}
return tokens;
}
+29
View File
@@ -0,0 +1,29 @@
const std = @import("std");
const lexer = @import("lexer.zig");
pub const Command = struct {
argv: []const []const u8,
};
pub fn parse(
allocator: std.mem.Allocator,
tokens: []const lexer.Token,
) !Command {
if (tokens.len == 0) {
return error.EmptyCommand;
}
const argv = try allocator.alloc(
[]const u8,
tokens.len,
);
for (tokens, 0..) |token, index| {
argv[index] = token.text;
}
return .{
.argv = argv,
};
}
View File
+20
View File
@@ -0,0 +1,20 @@
const X = 1000
const Y = 10
const Res = X + Y
const Str = `is {Res} == 1010?`
class Foo
public Bar: string
function new(Bar: string)
return Test {Foo = Bar}
end
end
print(Str)
print(if Res == 1010 then "Yes it is!" else "No it isn't.")
print(Foo, Foo.new("Classes work!").Bar)