diff --git a/.gitignore b/.gitignore index 5199a53..6e26988 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ build dist node_modules coverage -compile_commands.json \ No newline at end of file +compile_commands.json +_codeql_build_dir +_codeql_detected_source_root \ No newline at end of file diff --git a/include/ds/rule.hh b/include/ds/rule.hh index 81745cd..732a42c 100644 --- a/include/ds/rule.hh +++ b/include/ds/rule.hh @@ -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 diff --git a/include/ds/term.hh b/include/ds/term.hh index 886394f..b80120e 100644 --- a/include/ds/term.hh +++ b/include/ds/term.hh @@ -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 diff --git a/src/rename.cc b/src/rename.cc new file mode 100644 index 0000000..e6fedc9 --- /dev/null +++ b/src/rename.cc @@ -0,0 +1,158 @@ +#include + +#include +#include +#include +#include +#include +#include + +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 diff --git a/tests/test_rename.cc b/tests/test_rename.cc new file mode 100644 index 0000000..ac2aebd --- /dev/null +++ b/tests/test_rename.cc @@ -0,0 +1,148 @@ +#include +#include +#include + +class TestRename : public ::testing::Test { + protected: + const ds::length_t buffer_size = 200; + + TestRename() { } + ~TestRename() override { } + void SetUp() override { + result_t = reinterpret_cast(operator new(buffer_size)); + result_r = reinterpret_cast(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(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(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(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(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(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" + ); +}