Initial commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
zig-out
|
||||
.zig-cache
|
||||
zig-pkg
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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",
|
||||
},
|
||||
}
|
||||
Vendored
+21
@@ -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.
|
||||
Vendored
+1042
File diff suppressed because it is too large
Load Diff
Vendored
+18
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
Vendored
+466
@@ -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;
|
||||
}
|
||||
Vendored
+30
@@ -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);
|
||||
Vendored
+182
@@ -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;
|
||||
}
|
||||
Vendored
+383
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Vendored
+15
@@ -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;
|
||||
}
|
||||
Vendored
+46
@@ -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
|
||||
Vendored
+2454
File diff suppressed because it is too large
Load Diff
Vendored
+211
@@ -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);
|
||||
Vendored
+648
@@ -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
|
||||
Vendored
+15
@@ -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;
|
||||
}
|
||||
Vendored
+130
@@ -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
|
||||
Vendored
+60
@@ -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
|
||||
Vendored
+78
@@ -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;
|
||||
}
|
||||
Vendored
+156
@@ -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
|
||||
Vendored
+15
@@ -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);
|
||||
}
|
||||
Vendored
+731
@@ -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
@@ -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
|
||||
Vendored
+191
@@ -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
@@ -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
|
||||
Vendored
+66
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
Vendored
+133
@@ -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);
|
||||
}
|
||||
Vendored
+157
@@ -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
|
||||
Vendored
+49
@@ -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
@@ -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);
|
||||
}
|
||||
Vendored
+25
@@ -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);
|
||||
Vendored
+11
@@ -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";
|
||||
Vendored
+1650
File diff suppressed because it is too large
Load Diff
Vendored
+278
@@ -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;
|
||||
}
|
||||
Vendored
+7
@@ -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));
|
||||
}
|
||||
Vendored
+7
@@ -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));
|
||||
}
|
||||
Vendored
+33
@@ -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);
|
||||
}
|
||||
Vendored
+7
@@ -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));
|
||||
}
|
||||
Vendored
+90
@@ -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);
|
||||
}
|
||||
Vendored
+7
@@ -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;
|
||||
Vendored
+7
@@ -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));
|
||||
}
|
||||
Vendored
+7
@@ -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));
|
||||
}
|
||||
Vendored
+146
@@ -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)));
|
||||
}
|
||||
Vendored
+138
@@ -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;
|
||||
}
|
||||
Vendored
+164
@@ -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);
|
||||
}
|
||||
Vendored
+1129
File diff suppressed because it is too large
Load Diff
Vendored
+223
@@ -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()));
|
||||
}
|
||||
}
|
||||
Vendored
+16
@@ -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));
|
||||
}
|
||||
Vendored
+7
@@ -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));
|
||||
}
|
||||
Vendored
+720
@@ -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;
|
||||
}
|
||||
}
|
||||
Vendored
+58
@@ -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;
|
||||
}
|
||||
Vendored
+943
@@ -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"));
|
||||
}
|
||||
Vendored
+7
@@ -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));
|
||||
}
|
||||
Vendored
+9
@@ -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();
|
||||
}
|
||||
Vendored
+1011
File diff suppressed because it is too large
Load Diff
Vendored
+213
@@ -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);
|
||||
}
|
||||
Vendored
+7
@@ -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));
|
||||
}
|
||||
Vendored
+700
@@ -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);
|
||||
}
|
||||
Vendored
+7
@@ -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));
|
||||
}
|
||||
Vendored
+132
@@ -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;
|
||||
}
|
||||
Vendored
+268
@@ -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,
|
||||
};
|
||||
Vendored
+57
@@ -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);
|
||||
Vendored
+57
@@ -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);
|
||||
}
|
||||
Vendored
+7
@@ -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));
|
||||
}
|
||||
Vendored
+7
@@ -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));
|
||||
}
|
||||
Vendored
+7
@@ -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);
|
||||
}
|
||||
Vendored
+9
@@ -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;
|
||||
}
|
||||
Vendored
+128
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+1591
File diff suppressed because it is too large
Load Diff
Vendored
+125
@@ -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);
|
||||
}
|
||||
Vendored
+15
@@ -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
|
||||
Vendored
+173
@@ -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,
|
||||
};
|
||||
}
|
||||
Vendored
+346
@@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Vendored
+1949
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn execute(running: *bool) void {
|
||||
running.* = false;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void xsh_set_luau_flags(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user