Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 14 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,36 +247,25 @@ Call(s, JIT_GCC, "calc_multi", std::tie(x, y, msg), 10, 20, 30); // x=10, y=20,

FakeLua 提供完整的核心标准库(`math`、`table`、`string`),完全按照独立 C++ 模块解耦设计(`native_math` / `native_table` / `native_string`),既支持在 Lua 脚本中直接使用,也支持由 CGen 编译器生成的 C 代码进行 Fast-path 直连调用:

- **Basic 全局函数**:
- **类型与转换**:`type`、`tostring`、`tonumber`
- **输入输出**:`print`、`select`
- **错误处理**:`error`、`assert`、`pcall`、`xpcall`
- **表迭代**:`next`、`pairs`、`ipairs`
- **Math 数学库 (`math.*`)**:
- **基础与三角函数**:`math.abs`, `math.floor`, `math.ceil`, `math.min`, `math.max`, `math.sqrt`, `math.sin`, `math.cos`, `math.tan`, `math.asin`, `math.acos`, `math.atan`, `math.sinh`, `math.cosh`, `math.tanh`
- **指数、对数与分解**:`math.exp`, `math.log`, `math.log10`, `math.deg`, `math.rad`, `math.modf`, `math.frexp`
- **指数、对数与分解**:`math.exp`, `math.log`, `math.log10`, `math.deg`, `math.rad`, `math.modf`, `math.frexp`, `math.atan2`, `math.copysign`
- **随机数与数值常量**:`math.random`, `math.randomseed`, 以及数值常量 `math.pi`, `math.huge`, `math.maxinteger`, `math.mininteger`
- **Table 表操作库 (`table.*`)**:
- `table.concat(list [, sep [, i [, j]]])`:列表元素格式化拼接
- `table.insert(list [, pos], value)` & `table.remove(list [, pos])`:快速数组插入与删除
- `table.pack(...)` & `table.unpack(list [, i [, j]])`:变长参数打包与数组元素解包
- `table.sort(list [, comp])`:列表高效排序(支持默认比较及自定义 Lua 比较闭包函数)
- `table.create(seq_size [, hash_size])`:预分配容量构造优化 Table 结构
- **数组操作**:`table.insert(list [, pos], value)`、`table.remove(list [, pos])`、`table.concat(list [, sep [, i [, j]]])`、`table.sort(list [, comp])`
- **打包与解包**:`table.pack(...)`、`table.unpack(list [, i [, j]])`
- **预分配构造**:`table.create(seq_size [, hash_size])`
- **String 字符串处理库 (`string.*`)**:
- `string.len(s)`:计算字符串长度
- `string.sub(s, i [, j])`:子串提取(支持 Lua 1-indexed 正负索引切片)
- `string.rep(s, n [, sep])`:重复生成并拼接字符串
- `string.reverse(s)`:反转字符序列
- `string.lower(s)` & `string.upper(s)`:ASCII 字符大小写转换
- `string.byte(s [, i [, j]])` & `string.char(...)`:ASCII 字符编码解包与字符串构建
- `string.format(fmt, ...)`:C 语言 `sprintf` 风格格式化(支持 `%d`, `%f`, `%s`, `%q` 转义等)
- `string.find`, `string.match`, `string.gmatch`, `string.gsub`:模式匹配(类 Lua 5.3,基于 ECMAScript 正则)
- `string.dump(func)` 与全局 `load` / `loadstring`:运行时闭包字节/代码序列化与动态编译加载
- `string.pack(fmt, ...)` / `string.packsize(fmt)` / `string.unpack(fmt, s [, pos])`:Lua 5.3 二进制序列化
- **Basic 全局函数**:
- `type(v)`:返回值类型名字符串(`"nil"`, `"boolean"`, `"number"`, `"string"`, `"table"`, `"function"`)
- `tostring(v)`:值转字符串
- `tonumber(e [, base])`:字符串转数字(支持 2-36 进制)
- `print(...)`:输出到 stdout,tab 分隔
- `select(n, ...)`:选择从第 n 个开始的参数,`select("#", ...)` 返回总数
- `error(msg)` / `assert(v [, msg])`:错误处理与断言
- `pcall(f, ...)` / `xpcall(f, err, ...)`:保护调用
- `next(t [, index])` / `pairs(t)` / `ipairs(t)`:表迭代
- **基础操作**:`string.len`、`string.sub`、`string.rep`、`string.reverse`、`string.lower`、`string.upper`
- **编码转换**:`string.byte`、`string.char`、`string.charpattern`
- **格式化**:`string.format`
- **模式匹配**:`string.find`、`string.match`、`string.gmatch`、`string.gsub`
- **序列化与加载**:`string.pack`、`string.packsize`、`string.unpack`、`string.dump`、`load`、`loadstring`、`loadfile`(直接编译文件,顶层函数注册为全局,无需调用闭包)

```lua
-- 示例:使用标准库完成排序、格式化与数学计算
Expand Down
32 changes: 21 additions & 11 deletions src/compile/c_gen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2494,18 +2494,28 @@ std::string CGen::CompileVar(const SyntaxTreeInterfacePtr &v) {
// 拦截 math 静态库常量访问 (math.pi / math.huge)
if (pe && pe->Type() == SyntaxTreeType::PrefixExp) {
if (const auto pe_ptr = std::dynamic_pointer_cast<SyntaxTreePrefixexp>(pe); pe_ptr && pe_ptr->GetPrefixKind() == PrefixExpKind::kVar) {
if (const auto base_var = std::dynamic_pointer_cast<SyntaxTreeVar>(pe_ptr->GetValue()); base_var && base_var->GetVarKind() == VarKind::kSimple && base_var->GetName() == "math") {
if (name == "pi") {
return "(CVar){.type_ = VAR_FLOAT, .data_.f = 3.14159265358979323846}";
}
if (name == "huge") {
return "(CVar){.type_ = VAR_FLOAT, .data_.f = HUGE_VAL}";
}
if (name == "maxinteger") {
return "(CVar){.type_ = VAR_INT, .data_.i = 9223372036854775807LL}";
const auto base_var = std::dynamic_pointer_cast<SyntaxTreeVar>(pe_ptr->GetValue());
if (base_var && base_var->GetVarKind() == VarKind::kSimple) {
// 拦截 math 静态库常量访问 (math.pi / math.huge / math.maxinteger / math.mininteger)
if (base_var->GetName() == "math") {
if (name == "pi") {
return "(CVar){.type_ = VAR_FLOAT, .data_.f = 3.14159265358979323846}";
}
if (name == "huge") {
return "(CVar){.type_ = VAR_FLOAT, .data_.f = HUGE_VAL}";
}
if (name == "maxinteger") {
return "(CVar){.type_ = VAR_INT, .data_.i = 9223372036854775807LL}";
}
if (name == "mininteger") {
return "(CVar){.type_ = VAR_INT, .data_.i = (-9223372036854775807LL - 1LL)}";
}
}
if (name == "mininteger") {
return "(CVar){.type_ = VAR_INT, .data_.i = (-9223372036854775807LL - 1LL)}";
// 拦截 string 静态库常量访问 (string.charpattern)
if (base_var->GetName() == "string") {
if (name == "charpattern") {
return std::format("(CVar){{.type_ = VAR_STRINGID, .data_.i = {}}}", s_->GetConstString().Alloc("[^%z]"));
}
}
}
}
Expand Down
17 changes: 17 additions & 0 deletions src/native/native_math.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,23 @@ void RegisterMathLibraryApi(State *s) {
return inter::NativeToFakeluaFloat(state, std::atan(v0));
});

RegisterNativeFunction(s, "math.atan2", 2, false, [](State *state, CVar *args, int n) -> CVar {
CVar a0 = inter::GetNativeArg(state, args, n, 0);
CVar a1 = inter::GetNativeArg(state, args, n, 1);
double v0 = (a0.type_ == static_cast<int>(VarType::Int)) ? a0.data_.i : (a0.type_ == static_cast<int>(VarType::Float) ? a0.data_.f : 0.0);
double v1 = (a1.type_ == static_cast<int>(VarType::Int)) ? a1.data_.i : (a1.type_ == static_cast<int>(VarType::Float) ? a1.data_.f : 0.0);
return inter::NativeToFakeluaFloat(state, std::atan2(v0, v1));
});

RegisterNativeFunction(s, "math.copysign", 2, false, [](State *state, CVar *args, int n) -> CVar {
CVar a0 = inter::GetNativeArg(state, args, n, 0);
CVar a1 = inter::GetNativeArg(state, args, n, 1);
double v0 = (a0.type_ == static_cast<int>(VarType::Int)) ? a0.data_.i : (a0.type_ == static_cast<int>(VarType::Float) ? a0.data_.f : 0.0);
double v1 = (a1.type_ == static_cast<int>(VarType::Int)) ? a1.data_.i : (a1.type_ == static_cast<int>(VarType::Float) ? a1.data_.f : 0.0);
return inter::NativeToFakeluaFloat(state, std::copysign(v0, v1));
});


RegisterNativeFunction(s, "math.exp", 1, false, [](State *state, CVar *args, int n) -> CVar {
CVar a0 = inter::GetNativeArg(state, args, n, 0);
double v0 = (a0.type_ == static_cast<int>(VarType::Int)) ? a0.data_.i : (a0.type_ == static_cast<int>(VarType::Float) ? a0.data_.f : 0.0);
Expand Down
28 changes: 28 additions & 0 deletions src/native/native_string.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "native/native_string.h"
#include <fstream>
#include "native/native_object.h"
#include "compile/c_runtime_header.h"
#include "state/state.h"
Expand Down Expand Up @@ -1050,6 +1051,32 @@ void RegisterStringLibraryApi(State *s) {
RegisterNativeFunction(s, "load", 1, true, load_impl);
RegisterNativeFunction(s, "loadstring", 1, true, load_impl);

// ─── loadfile([filename [, mode [, env]]]) ───
// 从文件加载 Lua 源码并编译。mode/env 参数被忽略(fakelua 无环境概念)。
// 编译后文件中定义的顶层函数直接注册为全局函数,编译器的 __fakelua_init
// 会自动执行文件级常量/变量初始化。成功返回 nil,失败返回 nil。
RegisterNativeFunction(s, "loadfile", 0, true, [](State *state, CVar *args, int n) -> CVar {
if (n < 1) return inter::NativeToFakeluaNil(state);
CVar filename_var = inter::GetNativeArg(state, args, n, 0);
std::string_view filename_sv = KeyToStringView(filename_var);
if (filename_sv.empty()) return inter::NativeToFakeluaNil(state);

// 读取文件内容
std::ifstream ifs(std::string(filename_sv), std::ios::in | std::ios::binary);
if (!ifs.is_open()) return inter::NativeToFakeluaNil(state);
std::string source((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
ifs.close();

// 编译文件内容,顶层函数注册为全局,编译器自动执行 __fakelua_init
try {
CompileConfig config;
CompileString(state, source, config);
} catch (...) {
return inter::NativeToFakeluaNil(state);
}
return inter::NativeToFakeluaNil(state);
});

// ─── string.pack (Lua 5.3 binary serialization) ───
// 注册的签名是 (fmt, ...) 即 arg_count=1, is_vararg=true
// 调用时:args[0]=fmt, args[1]=Multi(剩余参数)
Expand Down Expand Up @@ -1526,6 +1553,7 @@ extern "C" CVar FlEvalLoadClosure(State *state, VarClosure *cl, int arg_num, con
return inter::NativeToFakeluaNil(state);
}
std::string code = cl->code_str;

if (code.size() >= 4 && code.substr(0, 4) == "\x1bLua") {
code = (code.size() >= 5) ? code.substr(5) : code.substr(4);
}
Expand Down
23 changes: 23 additions & 0 deletions test/lua/math/test_math_atan2.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
function test_math_atan2()
-- atan2(0, 1) = 0
local a1 = math.atan2(0, 1)
if math.abs(a1 - 0.0) > 1e-6 then return 0 end

-- atan2(1, 0) = pi/2
local a2 = math.atan2(1, 0)
if math.abs(a2 - math.pi / 2) > 1e-6 then return 0 end

-- atan2(1, 1) = pi/4
local a3 = math.atan2(1, 1)
if math.abs(a3 - math.pi / 4) > 1e-6 then return 0 end

-- atan2(-1, -1) = -3*pi/4
local a4 = math.atan2(-1, -1)
if math.abs(a4 - (-3 * math.pi / 4)) > 1e-6 then return 0 end

-- 整数参数也应工作
local a5 = math.atan2(0.0, -1.0)
if math.abs(a5 - math.pi) > 1e-6 then return 0 end

return 6000
end
23 changes: 23 additions & 0 deletions test/lua/math/test_math_copysign.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
function test_math_copysign()
-- copysign(1.0, -2.0) = -1.0
local c1 = math.copysign(1.0, -2.0)
if c1 >= 0 then return 0 end

-- copysign(-1.0, 2.0) = 1.0
local c2 = math.copysign(-1.0, 2.0)
if c2 < 0 then return 0 end

-- copysign(3.14, -1) = -3.14
local c3 = math.copysign(3.14, -1)
if math.abs(c3 - (-3.14)) > 1e-6 then return 0 end

-- copysign(-5.5, 0.0) = 5.5 (zero is positive)
local c4 = math.copysign(-5.5, 0.0)
if c4 < 0 then return 0 end

-- 整数参数也应工作
local c5 = math.copysign(10, -20)
if c5 >= 0 then return 0 end

return 7000
end
27 changes: 27 additions & 0 deletions test/lua/string/test_string_charpattern.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
function test_string_charpattern()
-- string.charpattern 应该是匹配任意单个字符的模式
local pat = string.charpattern
if type(pat) ~= "string" then return 0 end

-- 用 string.match + charpattern 匹配单个字符
local matched = string.match("hello", pat)
if matched ~= "h" then return 0 end

-- charpattern 应该匹配任意非空字符
local digits = "12345"
local count = 0
for c in string.gmatch(digits, pat) do
count = count + 1
end
if count ~= 5 then return 0 end

-- 空字符串不匹配
local m2 = string.match("", pat)
if m2 ~= nil then return 0 end

-- charpattern 应该能匹配特殊字符
local m3 = string.match("!@#", pat)
if m3 ~= "!" then return 0 end

return 6000
end
19 changes: 19 additions & 0 deletions test/lua/string/test_string_loadfile.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
function test_string_loadfile()
-- 加载外部文件,编译后文件中定义的函数直接变为全局
local ret = loadfile("./string/test_string_loadfile_helper.lua")
-- fakelua 的 loadfile 返回 nil(成功或失败),不返回闭包
if ret ~= nil then return 0 end

-- 文件中定义的函数已注册为全局,可直接调用
local sum = helper_add(10, 20)
if sum ~= 30 then return 0 end

local msg = helper_greet("world")
if msg ~= "hello world" then return 0 end

-- 加载不存在的文件应返回 nil
local ret2 = loadfile("./string/nonexistent_file_xyz.lua")
if ret2 ~= nil then return 0 end

return 7000
end
7 changes: 7 additions & 0 deletions test/lua/string/test_string_loadfile_helper.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
function helper_add(a, b)
return a + b
end

function helper_greet(name)
return "hello " .. name
end
31 changes: 31 additions & 0 deletions test/test_math.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,34 @@ TEST(test_math, test_math_modf_frexp) {

FakeluaDeleteState(s);
}


TEST(test_math, test_math_atan2) {
State *s = FakeluaNewState();
ASSERT_NE(s, nullptr);
CompileConfig config;

for (auto jit_type: {JIT_TCC, JIT_GCC}) {
CompileFile(s, "./math/test_math_atan2.lua", config);
double res = 0;
Call(s, jit_type, "test_math_atan2", res);
EXPECT_NEAR(res, 6000, 0.5);
}

FakeluaDeleteState(s);
}

TEST(test_math, test_math_copysign) {
State *s = FakeluaNewState();
ASSERT_NE(s, nullptr);
CompileConfig config;

for (auto jit_type: {JIT_TCC, JIT_GCC}) {
CompileFile(s, "./math/test_math_copysign.lua", config);
double res = 0;
Call(s, jit_type, "test_math_copysign", res);
EXPECT_NEAR(res, 7000, 0.5);
}

FakeluaDeleteState(s);
}
31 changes: 31 additions & 0 deletions test/test_string.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,34 @@ TEST(test_string, test_string_pack_unpack) {

FakeluaDeleteState(s);
}


TEST(test_string, test_string_charpattern) {
State *s = FakeluaNewState();
ASSERT_NE(s, nullptr);
CompileConfig config;

for (auto jit_type: {JIT_TCC, JIT_GCC}) {
CompileFile(s, "./string/test_string_charpattern.lua", config);
double res = 0;
Call(s, jit_type, "test_string_charpattern", res);
EXPECT_NEAR(res, 6000, 0.5);
}

FakeluaDeleteState(s);
}

TEST(test_string, test_string_loadfile) {
State *s = FakeluaNewState();
ASSERT_NE(s, nullptr);
CompileConfig config;

for (auto jit_type: {JIT_TCC, JIT_GCC}) {
CompileFile(s, "./string/test_string_loadfile.lua", config);
double res = 0;
Call(s, jit_type, "test_string_loadfile", res);
EXPECT_NEAR(res, 7000, 0.5);
}

FakeluaDeleteState(s);
}
Loading