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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ build
dist
node_modules
coverage
compile_commands.json
compile_commands.json
_codeql_build_dir
_codeql_detected_source_root
7 changes: 7 additions & 0 deletions include/ds/rule.hh
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ namespace ds {
/// @param check_tail 可选的尾指针检查。
/// @return 自身,如果匹配失败则返回nullptr,如果尾指针检查失败则返回nullptr,在尾指针检查正常时,匹配失败会将本对象设置为null。
rule_t* match(rule_t* rule_1, rule_t* rule_2, std::byte* check_tail = nullptr);

/// @brief 将rule中的所有variable添加prefix和suffix, 结果更新至本对象。
/// @param rule 待被重命名的rule。
/// @param prefix_and_suffix 只有一个conclusion的rule,conclusion是含有两个list的list,每个内部list包含0或1个item,分别表示prefix和suffix。
/// @param check_tail 可选的尾指针检查。
/// @return 自身,是一个rule_t对象的指针,如果尾指针检查失败则返回nullptr。
rule_t* rename(rule_t* rule, rule_t* prefix_and_suffix, std::byte* check_tail = nullptr);
};
} // namespace ds

Expand Down
7 changes: 7 additions & 0 deletions include/ds/term.hh
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@ namespace ds {
/// @param check_tail 可选的尾指针检查。
/// @return 自身,如果匹配失败则返回nullptr,如果尾指针检查失败则返回nullptr,在尾指针检查正常时,匹配失败会将本对象设置为null。
term_t* match(term_t* term_1, term_t* term_2, const char* scope_1, const char* scope_2, std::byte* check_tail = nullptr);

/// @brief 将term中的所有variable添加prefix和suffix, 结果更新至本对象。
/// @param term 待被重命名的term。
/// @param prefix_and_suffix 含有两个list的list,每个内部list包含0或1个item,分别表示prefix和suffix。
/// @param check_tail 可选的尾指针检查。
/// @return 自身,是一个term_t对象的指针,如果尾指针检查失败则返回nullptr。
term_t* rename(term_t* term, term_t* prefix_and_suffix, std::byte* check_tail = nullptr);
};
} // namespace ds

Expand Down
158 changes: 158 additions & 0 deletions src/rename.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
#include <cstring>

#include <ds/helper.hh>
#include <ds/item.hh>
#include <ds/list.hh>
#include <ds/rule.hh>
#include <ds/term.hh>
#include <ds/variable.hh>

namespace ds {
namespace {
/// @brief 存储prefix和suffix字符串信息的结构体。
struct prefix_suffix_t {
char* prefix_str; // prefix字符串指针,如果prefix为空则为nullptr
length_t prefix_len; // prefix字符串长度
char* suffix_str; // suffix字符串指针,如果suffix为空则为nullptr
length_t suffix_len; // suffix字符串长度
};

/// @brief 从prefix_and_suffix中提取prefix和suffix字符串。
/// @param prefix_and_suffix 格式为((prefix) (suffix))的term,每个内部list包含0或1个item。
/// @param ps 输出参数,存储提取的prefix和suffix信息。
/// @return 成功返回true,格式错误返回false。
bool extract_prefix_suffix(term_t* prefix_and_suffix, prefix_suffix_t* ps) {
list_t* ps_list = prefix_and_suffix->list();
if (ps_list == nullptr || ps_list->get_list_size() != 2) [[unlikely]] {
return false;
}
// prefix_and_suffix格式为((prefix) (suffix)),每个元素是包含0或1个item的list
list_t* prefix_list = ps_list->term(0)->list();
list_t* suffix_list = ps_list->term(1)->list();
if (prefix_list == nullptr || suffix_list == nullptr) [[unlikely]] {
return false;
}
// 获取prefix字符串(如果list为空则为nullptr)
ps->prefix_str = nullptr;
ps->prefix_len = 0;
if (prefix_list->get_list_size() == 1) {
item_t* prefix_item = prefix_list->term(0)->item();
if (prefix_item == nullptr) [[unlikely]] {
return false;
}
ps->prefix_str = prefix_item->name()->get_string();
// get_length()返回的是包含末尾\0的长度,所以需要减1
ps->prefix_len = prefix_item->name()->get_length() - 1;
} else if (prefix_list->get_list_size() != 0) [[unlikely]] {
return false;
}
// 获取suffix字符串(如果list为空则为nullptr)
ps->suffix_str = nullptr;
ps->suffix_len = 0;
if (suffix_list->get_list_size() == 1) {
item_t* suffix_item = suffix_list->term(0)->item();
if (suffix_item == nullptr) [[unlikely]] {
return false;
}
ps->suffix_str = suffix_item->name()->get_string();
// get_length()返回的是包含末尾\0的长度,所以需要减1
ps->suffix_len = suffix_item->name()->get_length() - 1;
} else if (suffix_list->get_list_size() != 0) [[unlikely]] {
return false;
}
return true;
}

/// @brief 内部递归函数,使用已提取的prefix和suffix字符串对term进行重命名。
/// @param result 存放结果的term指针。
/// @param term 待被重命名的term。
/// @param ps 包含prefix和suffix信息的结构体指针。
/// @param check_tail 可选的尾指针检查。
/// @return 成功返回result,失败返回nullptr。
term_t* rename_with_strings(term_t* result, term_t* term, prefix_suffix_t* ps, std::byte* check_tail) {
switch (term->get_type()) {
case term_type_t::variable: {
// get_length()返回的是包含末尾\0的长度,所以需要减1
length_t name_len = term->variable()->name()->get_length() - 1;
length_t new_len = ps->prefix_len + name_len + ps->suffix_len + 1;
if (result->set_variable(check_tail) == nullptr) [[unlikely]] {
return nullptr;
}
if (result->variable()->name()->set_length(new_len, check_tail) == nullptr) [[unlikely]] {
return nullptr;
}
char* name_str = term->variable()->name()->get_string();
char* dst = result->variable()->name()->get_string();
if (ps->prefix_len > 0) {
memcpy(dst, ps->prefix_str, ps->prefix_len);
}
memcpy(dst + ps->prefix_len, name_str, name_len);
if (ps->suffix_len > 0) {
memcpy(dst + ps->prefix_len + name_len, ps->suffix_str, ps->suffix_len);
}
dst[new_len - 1] = 0;
return result;
}
case term_type_t::item: {
if (check_before_fail(check_tail, result, term->data_size())) [[unlikely]] {
return nullptr;
}
memcpy(result, term, term->data_size());
return result;
}
case term_type_t::list: {
list_t* src = term->list();
if (result->set_list(check_tail) == nullptr) [[unlikely]] {
return nullptr;
}
list_t* dst = result->list();
if (dst->set_list_size(src->get_list_size(), check_tail) == nullptr) [[unlikely]] {
return nullptr;
}
for (length_t index = 0; index < dst->get_list_size(); ++index) {
if (rename_with_strings(dst->term(index), src->term(index), ps, check_tail) == nullptr) [[unlikely]] {
return nullptr;
}
dst->update_term_size(index);
}
return result;
}
default:
return nullptr;
}
}
} // namespace

term_t* term_t::rename(term_t* term, term_t* prefix_and_suffix, std::byte* check_tail) {
// 在开头提取prefix和suffix字符串,避免每次递归时重复解析
prefix_suffix_t ps;
if (!extract_prefix_suffix(prefix_and_suffix, &ps)) [[unlikely]] {
return nullptr;
}
return rename_with_strings(this, term, &ps, check_tail);
}

rule_t* rule_t::rename(rule_t* rule, rule_t* prefix_and_suffix, std::byte* check_tail) {
term_t* ps_term = prefix_and_suffix->only_conclusion();
if (ps_term == nullptr) [[unlikely]] {
return nullptr;
}
// 在开头提取prefix和suffix字符串,避免每次递归时重复解析
prefix_suffix_t ps;
if (!extract_prefix_suffix(ps_term, &ps)) [[unlikely]] {
return nullptr;
}
list_t* dst = this;
list_t* src = rule;
if (dst->set_list_size(src->get_list_size(), check_tail) == nullptr) [[unlikely]] {
return nullptr;
}
for (length_t index = 0; index < dst->get_list_size(); ++index) {
if (rename_with_strings(dst->term(index), src->term(index), &ps, check_tail) == nullptr) [[unlikely]] {
return nullptr;
}
dst->update_term_size(index);
}
return this;
}
} // namespace ds
148 changes: 148 additions & 0 deletions tests/test_rename.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#include <ds/term.hh>
#include <ds/utility.hh>
#include <gtest/gtest.h>

class TestRename : public ::testing::Test {
protected:
const ds::length_t buffer_size = 200;

TestRename() { }
~TestRename() override { }
void SetUp() override {
result_t = reinterpret_cast<ds::term_t*>(operator new(buffer_size));
result_r = reinterpret_cast<ds::rule_t*>(operator new(buffer_size));
}
void TearDown() override {
operator delete(result_t);
operator delete(result_r);
}

ds::term_t* result_t;
ds::rule_t* result_r;

void rename_term_check(const char* term_text, const char* prefix_suffix_text, const char* expect_text) {
auto term = ds::text_to_term(term_text, buffer_size);
auto prefix_suffix = ds::text_to_term(prefix_suffix_text, buffer_size);
EXPECT_NE(result_t->rename(term.get(), prefix_suffix.get(), nullptr), nullptr);
auto result = ds::term_to_text(result_t, buffer_size);
EXPECT_STREQ(result.get(), expect_text);
auto correct_length = result_t->data_size();
EXPECT_NE(result_t->rename(term.get(), prefix_suffix.get(), reinterpret_cast<std::byte*>(result_t) + correct_length), nullptr);
for (auto i = 0; i < correct_length; ++i) {
EXPECT_EQ(result_t->rename(term.get(), prefix_suffix.get(), reinterpret_cast<std::byte*>(result_t) + i), nullptr);
}
}

void rename_rule_check(const char* rule_text, const char* prefix_suffix_text, const char* expect_text) {
auto rule = ds::text_to_rule(rule_text, buffer_size);
auto prefix_suffix = ds::text_to_rule(prefix_suffix_text, buffer_size);
EXPECT_NE(result_r->rename(rule.get(), prefix_suffix.get(), nullptr), nullptr);
auto result = ds::rule_to_text(result_r, buffer_size);
EXPECT_STREQ(result.get(), expect_text);
auto correct_length = result_r->data_size();
EXPECT_NE(result_r->rename(rule.get(), prefix_suffix.get(), reinterpret_cast<std::byte*>(result_r) + correct_length), nullptr);
for (auto i = 0; i < correct_length; ++i) {
EXPECT_EQ(result_r->rename(rule.get(), prefix_suffix.get(), reinterpret_cast<std::byte*>(result_r) + i), nullptr);
}
}
};

TEST_F(TestRename, rename_term_variable) {
// Test basic variable renaming with new interface ((prefix) (suffix))
rename_term_check("`x", "((pre_) (_suf))", "`pre_x_suf");
rename_term_check("`abc", "((a_) (_z))", "`a_abc_z");
// Test with empty prefix (only suffix)
rename_term_check("`x", "(() (_suf))", "`x_suf");
rename_term_check("`var", "(() (suffix))", "`varsuffix");
// Test with empty suffix (only prefix)
rename_term_check("`x", "((pre_) ())", "`pre_x");
rename_term_check("`var", "((prefix) ())", "`prefixvar");
// Test with both empty (no change to variable name)
rename_term_check("`x", "(() ())", "`x");
}

TEST_F(TestRename, rename_term_item) {
// Items should not be renamed
rename_term_check("item", "((pre_) (_suf))", "item");
rename_term_check("abc", "((a_) (_z))", "abc");
}

TEST_F(TestRename, rename_term_list) {
// List with variables should have all variables renamed
rename_term_check("(`x `y)", "((p_) (_s))", "(`p_x_s `p_y_s)");
rename_term_check("(a `x b `y)", "((pre_) (_suf))", "(a `pre_x_suf b `pre_y_suf)");
// Nested lists
rename_term_check("((`x))", "((p_) (_s))", "((`p_x_s))");
rename_term_check("((`x `y) `z)", "((a_) (_b))", "((`a_x_b `a_y_b) `a_z_b)");
}

TEST_F(TestRename, rename_term_mixed) {
// Mixed term with item and variable
rename_term_check("(item `var)", "((pre) (_suf))", "(item `prevar_suf)");
rename_term_check("(f `a `b c)", "((x) (y))", "(f `xay `xby c)");
}

TEST_F(TestRename, rename_term_invalid) {
// Invalid prefix_and_suffix (not a list)
auto term = ds::text_to_term("`x", buffer_size);
auto invalid_ps = ds::text_to_term("item", buffer_size);
EXPECT_EQ(result_t->rename(term.get(), invalid_ps.get(), nullptr), nullptr);

// Invalid prefix_and_suffix (list size != 2)
auto invalid_ps_size = ds::text_to_term("((a))", buffer_size);
EXPECT_EQ(result_t->rename(term.get(), invalid_ps_size.get(), nullptr), nullptr);

auto invalid_ps_size3 = ds::text_to_term("((a) (b) (c))", buffer_size);
EXPECT_EQ(result_t->rename(term.get(), invalid_ps_size3.get(), nullptr), nullptr);

// Invalid prefix_and_suffix (elements are not lists)
auto invalid_ps_elem = ds::text_to_term("(a b)", buffer_size);
EXPECT_EQ(result_t->rename(term.get(), invalid_ps_elem.get(), nullptr), nullptr);

// Invalid prefix_and_suffix (inner list element is variable, not item)
auto invalid_ps_var = ds::text_to_term("((`a) (b))", buffer_size);
EXPECT_EQ(result_t->rename(term.get(), invalid_ps_var.get(), nullptr), nullptr);

// Invalid prefix_and_suffix (inner list has more than 1 element)
auto invalid_ps_multi = ds::text_to_term("((a b) (c))", buffer_size);
EXPECT_EQ(result_t->rename(term.get(), invalid_ps_multi.get(), nullptr), nullptr);

// Null term
ds::term_t* null_term = reinterpret_cast<ds::term_t*>(operator new(buffer_size));
null_term->set_null(nullptr);
auto valid_ps = ds::text_to_term("((a) (b))", buffer_size);
EXPECT_EQ(result_t->rename(null_term, valid_ps.get(), nullptr), nullptr);
operator delete(null_term);
}

TEST_F(TestRename, rename_rule_basic) {
rename_rule_check("`x", "((pre_) (_suf))", "----\n`pre_x_suf\n");
rename_rule_check("item", "((pre_) (_suf))", "----\nitem\n");
rename_rule_check("(`x `y)", "((a) (b))", "----\n(`axb `ayb)\n");
}

TEST_F(TestRename, rename_rule_with_premises) {
rename_rule_check(
"`p\n"
"`q\n"
"----------\n"
"`r\n",
"((pre_) (_suf))",
"`pre_p_suf\n"
"`pre_q_suf\n"
"----------\n"
"`pre_r_suf\n"
);

rename_rule_check(
"(`p -> `q)\n"
"`p\n"
"----------\n"
"`q\n",
"((x) (y))",
"(`xpy -> `xqy)\n"
"`xpy\n"
"--------------\n"
"`xqy\n"
);
}