From edac7560ed8c3981c64f9f6988961458a89da790 Mon Sep 17 00:00:00 2001 From: Hao Zhang Date: Sat, 26 Jul 2025 09:28:57 +0800 Subject: [PATCH] Add search class into the core library. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- examples/main.cc | 137 +++++++++---------------------------------- examples/main.mjs | 114 ++++++++++------------------------- examples/main.py | 96 ++++++++++-------------------- include/ds/search.hh | 77 ++++++++++++++++++++++++ package.json | 2 +- pyds/__init__.py | 1 + pyds/ds.cc | 11 ++++ pyds/search_t.py | 31 ++++++++++ src/search.cc | 134 ++++++++++++++++++++++++++++++++++++++++++ tsds/ds.cc | 23 ++++++++ tsds/tsds.mts | 35 +++++++++++ 11 files changed, 403 insertions(+), 258 deletions(-) create mode 100644 include/ds/search.hh create mode 100644 pyds/search_t.py create mode 100644 src/search.cc diff --git a/examples/main.cc b/examples/main.cc index 5a76e4a..761a6ea 100644 --- a/examples/main.cc +++ b/examples/main.cc @@ -2,134 +2,53 @@ #include #include #include -#include -#include #include +#include #include -struct PointerLess { - template - bool operator()(const T& lhs, const T& rhs) const { - if (lhs->data_size() < rhs->data_size()) { - return true; - } - if (lhs->data_size() > rhs->data_size()) { - return false; - } - ds::length_t data_size = lhs->data_size(); - const std::byte* lhs_data = reinterpret_cast(lhs.get()); - const std::byte* rhs_data = reinterpret_cast(rhs.get()); - for (ds::length_t index = 0; index < data_size; ++index) { - if (lhs_data[index] < rhs_data[index]) { - return true; - } - if (lhs_data[index] > rhs_data[index]) { - return false; - } - } - return false; - } -}; - void run() { int temp_data_size = 1000; int temp_text_size = 1000; int single_result_size = 10000; + auto search = ds::search_t(temp_data_size, single_result_size); + // P -> Q, P |- Q - auto mp = ds::text_to_rule( - "(`P -> `Q)\n" - "`P\n" - "----------\n" - "`Q", - temp_data_size - ); + search.add("(`P -> `Q) `P `Q"); // p -> (q -> p) - auto axiom1 = ds::text_to_rule( - "------------------\n" - "(`p -> (`q -> `p))\n", - temp_data_size - ); + search.add("(`p -> (`q -> `p))"); // (p -> (q -> r)) -> ((p -> q) -> (p -> r)) - auto axiom2 = ds::text_to_rule( - "--------------------------------------------------\n" - "((`p -> (`q -> `r)) -> ((`p -> `q) -> (`p -> `r)))\n", - temp_data_size - ); + search.add("((`p -> (`q -> `r)) -> ((`p -> `q) -> (`p -> `r)))"); // (!p -> !q) -> (q -> p) - auto axiom3 = ds::text_to_rule( - "----------------------------------\n" - "(((! `p) -> (! `q)) -> (`q -> `p))\n", - temp_data_size - ); + search.add("(((! `p) -> (! `q)) -> (`q -> `p))"); - auto premise = ds::text_to_rule("(! (! X))", temp_data_size); - auto target = ds::text_to_rule("X", temp_data_size); + // premise + search.add("(! (! X))"); - std::map, ds::length_t, PointerLess> rules; - std::map, ds::length_t, PointerLess> facts; - - std::set, PointerLess> temp_rules; - std::set, PointerLess> temp_facts; - - ds::length_t cycle = -1; - rules.emplace(std::move(mp), cycle); - facts.emplace(std::move(axiom1), cycle); - facts.emplace(std::move(axiom2), cycle); - facts.emplace(std::move(axiom3), cycle); - facts.emplace(std::move(premise), cycle); - - auto buffer = std::unique_ptr(reinterpret_cast(operator new(single_result_size))); - - auto less = PointerLess(); + auto target = ds::text_to_rule("X", temp_data_size); while (true) { - temp_rules.clear(); - temp_facts.clear(); + bool success = false; - for (auto& [rule, rules_cycle] : rules) { - for (auto& [fact, facts_cycle] : facts) { - if (rules_cycle != cycle && facts_cycle != cycle) { - continue; - } - buffer->match(rule.get(), fact.get(), reinterpret_cast(buffer.get()) + single_result_size); - if (!buffer->valid()) { - continue; - } - if (buffer->premises_count() != 0) { - // rule - if (rules.find(buffer) != rules.end() || temp_rules.find(buffer) != temp_rules.end()) { - continue; - } - auto new_rule = std::unique_ptr(reinterpret_cast(operator new(buffer->data_size()))); - memcpy(new_rule.get(), buffer.get(), buffer->data_size()); - temp_rules.emplace(std::move(new_rule)); - } else { - // fact - if (facts.find(buffer) != facts.end() || temp_facts.find(buffer) != temp_facts.end()) { - continue; - } - auto new_fact = std::unique_ptr(reinterpret_cast(operator new(buffer->data_size()))); - memcpy(new_fact.get(), buffer.get(), buffer->data_size()); - if ((!less(new_fact, target)) && (!less(target, new_fact))) { - printf("Found!\n"); - printf("%s", ds::rule_to_text(new_fact.get(), temp_text_size).get()); - return; - } - temp_facts.emplace(std::move(new_fact)); - } + auto callback = [&target, &success, &temp_text_size](ds::rule_t* candidate) { + if (candidate->data_size() != target->data_size()) { + return false; } - } + auto data_size = candidate->data_size(); + auto equal = memcmp(candidate->head(), target->head(), data_size) == 0; + if (equal) { + printf("Found!\n"); + printf("%s", ds::rule_to_text(candidate, temp_text_size).get()); + success = true; + return true; + } + return false; + }; - ++cycle; - for (auto& rule : temp_rules) { - auto& movable_rule = const_cast&>(rule); - rules.emplace(std::move(movable_rule), cycle); - } - for (auto& fact : temp_facts) { - auto& movable_fact = const_cast&>(fact); - facts.emplace(std::move(movable_fact), cycle); + search.execute(callback); + if (success) { + break; } } } @@ -139,7 +58,7 @@ void timer(std::function func) { func(); auto end = std::chrono::high_resolution_clock::now(); std::chrono::duration duration = end - start; - std::cout << "Execution time: " << duration.count() << " seconds\n" << std::flush; + std::cout << "Execution time: " << duration.count() << " seconds" << std::endl; } int main() { diff --git a/examples/main.mjs b/examples/main.mjs index 05c824d..ed96d7c 100644 --- a/examples/main.mjs +++ b/examples/main.mjs @@ -1,93 +1,43 @@ -import { buffer_size, rule_t } from "../tsds/tsds.mts"; +import { rule_t, search_t, buffer_size } from "../tsds/tsds.mts"; -buffer_size(1000); - -// biome-ignore format: 保持多行对齐 -// P -> Q, P |- Q -const mp = new rule_t( - "(`P -> `Q)\n" + - "`P\n" + - "----------\n" + - "`Q\n"); - -// biome-ignore format: 保持多行对齐 -// p -> (q -> p) -const axiom1 = new rule_t( - "(`p -> (`q -> `p))" -); - -// biome-ignore format: 保持多行对齐 -// (p -> (q -> r)) -> ((p -> q) -> (p -> r)) -const axiom2 = new rule_t( - "((`p -> (`q -> `r)) -> ((`p -> `q) -> (`p -> `r)))" -); +function main() { + const temp_data_size = 1000; + const temp_text_size = 1000; + const single_result_size = 10000; -// biome-ignore format: 保持多行对齐 -// (!p -> !q) -> (q -> p) -const axiom3 = new rule_t( - "(((! `p) -> (! `q)) -> (`q -> `p))" -); + buffer_size(temp_text_size); + const search = new search_t(temp_data_size, single_result_size); -const premise = new rule_t("(! (! X))"); -const target = new rule_t("X"); -const target_hash = target.key(); + // P -> Q, P |- Q + search.add("(`P -> `Q) `P `Q\n"); + // p -> (q -> p) + search.add("(`p -> (`q -> `p))"); + // (p -> (q -> r)) -> ((p -> q) -> (p -> r)) + search.add("((`p -> (`q -> `r)) -> ((`p -> `q) -> (`p -> `r)))"); + // (!p -> !q) -> (q -> p) + search.add("(((! `p) -> (! `q)) -> (`q -> `p))"); -function main() { - const rules = {}; - const facts = {}; + // premise + search.add("(! (! X))"); - let cycle = -1; - rules[mp.key()] = [mp, cycle]; - facts[axiom1.key()] = [axiom1, cycle]; - facts[axiom2.key()] = [axiom2, cycle]; - facts[axiom3.key()] = [axiom3, cycle]; - facts[premise.key()] = [premise, cycle]; + const target = new rule_t("X"); while (true) { - const temp_rules = {}; - const temp_facts = {}; - - for (const r_hash in rules) { - for (const f_hash in facts) { - const [rule, r_cycle] = rules[r_hash]; - const [fact, f_cycle] = facts[f_hash]; - if (r_cycle !== cycle && f_cycle !== cycle) { - continue; - } - const candidate = rule.match(fact); - if (candidate === null) { - continue; - } - const candidate_hash = candidate.key(); - if (candidate.length() !== 0) { - // rule - if (candidate_hash in rules || candidate_hash in temp_rules) { - continue; - } - temp_rules[candidate_hash] = candidate; - } else { - // fact - if (candidate_hash in facts || candidate_hash in temp_facts) { - continue; - } - if (candidate_hash === target_hash) { - console.log("Found!"); - console.log(candidate.toString()); - return; - } - temp_facts[candidate_hash] = candidate; - } + let success = false; + + const callback = (candidate) => { + if (candidate.key() === target.key()) { + console.log("Found!"); + console.log(candidate.toString()); + success = true; + return true; } - } + return false; + }; - cycle++; - for (const r_hash in temp_rules) { - const rule = temp_rules[r_hash]; - rules[rule.key()] = [rule, cycle]; - } - for (const f_hash in temp_facts) { - const fact = temp_facts[f_hash]; - facts[fact.key()] = [fact, cycle]; + search.execute(callback); + if (success) { + break; } } } @@ -96,5 +46,5 @@ for (let i = 0; i < 10; i++) { const begin = new Date(); main(); const end = new Date(); - console.log(`Time taken: ${(end - begin) / 1000}s`); + console.log(`Execution time: ${(end - begin) / 1000} seconds`); } diff --git a/examples/main.py b/examples/main.py index f336322..559b48c 100644 --- a/examples/main.py +++ b/examples/main.py @@ -1,84 +1,48 @@ import time import pyds -pyds.buffer_size(1000) -# P -> Q, P |- Q -mp = pyds.Rule(""" -(`P -> `Q) -`P ----------- -`Q -""") - -# p -> (q -> p) -axiom1 = pyds.Rule(""" ------------------- -(`p -> (`q -> `p)) -""") - -# (p -> (q -> r)) -> ((p -> q) -> (p -> r)) -axiom2 = pyds.Rule(""" --------------------------------------------------- -((`p -> (`q -> `r)) -> ((`p -> `q) -> (`p -> `r))) -""") - -# (!p -> !q) -> (q -> p) -axiom3 = pyds.Rule(""" ----------------------------------- -(((! `p) -> (! `q)) -> (`q -> `p)) -""") +def main(): + temp_data_size = 1000 + temp_text_size = 1000 + single_result_size = 10000 -premise = pyds.Rule("(! (! X))") -target = pyds.Rule("X") + pyds.buffer_size(temp_text_size) + search = pyds.Search(temp_data_size, single_result_size) + # P -> Q, P |- Q + search.add("(`P -> `Q) `P `Q\n") + # p -> (q -> p) + search.add("(`p -> (`q -> `p))") + # (p -> (q -> r)) -> ((p -> q) -> (p -> r)) + search.add("((`p -> (`q -> `r)) -> ((`p -> `q) -> (`p -> `r)))") + # (!p -> !q) -> (q -> p) + search.add("(((! `p) -> (! `q)) -> (`q -> `p))") -def main(): - rules: dict[pyds.Rule, int] = {} - facts: dict[pyds.Rule, int] = {} + # premise + search.add("(! (! X))") - cycle = -1 - rules[mp] = cycle - facts[axiom1] = cycle - facts[axiom2] = cycle - facts[axiom3] = cycle - facts[premise] = cycle + target = pyds.Rule("X") while True: - temp_rules: set[pyds.Rule] = set() - temp_facts: set[pyds.Rule] = set() + success = False - for rule, r_cycle in rules.items(): - for fact, f_cycle in facts.items(): - if r_cycle != cycle and f_cycle != cycle: - continue - candidate = rule @ fact - if candidate is None: - continue - if len(candidate) != 0: - # Rule - if candidate in rules or candidate in temp_rules: - continue - temp_rules.add(candidate) - else: - # Fact - if candidate in facts or candidate in temp_facts: - continue - if hash(candidate) == hash(target): - print("Found!") - print(candidate) - return - temp_facts.add(candidate) + def callback(candidate: pyds.Rule) -> bool: + if candidate == target: + print("Found!") + print(candidate) + nonlocal success + success = True + return True + return False - cycle += 1 - for rule in temp_rules: - rules[rule] = cycle - for fact in temp_facts: - facts[fact] = cycle + search.execute(callback) + if success: + break for i in range(10): begin = time.time() main() end = time.time() - print(end - begin) + print(f"Execution time: {end - begin:.8f} seconds") diff --git a/include/ds/search.hh b/include/ds/search.hh new file mode 100644 index 0000000..6389ef2 --- /dev/null +++ b/include/ds/search.hh @@ -0,0 +1,77 @@ +#ifndef DS_SEARCH_HH +#define DS_SEARCH_HH + +#include +#include +#include +#include + +#include + +namespace ds { + /// @brief 用于进行推理搜索的类。 + class search_t { + /// @brief 用于比较rule_t的智能指针大小的类型,用于将其存储在map中。 + /// @note 该类型比较的是rule_t对象的大小,而不是指针地址。 + struct less_t { + /// @brief 判断两个rule_t的智能指针的大小关系。 + /// @param lhs 第一个rule_t的智能指针。 + /// @param rhs 第二个rule_t的智能指针。 + /// @return 如果第一个rule_t的智能指针小于第二个,则返回true;否则返回false。 + /// @note 该比较函数比较的是rule_t对象的大小,而不是指针地址。 + bool operator()(const std::unique_ptr& lhs, const std::unique_ptr& rhs) const; + }; + + /// @brief 每个有效rule_t的最大长度。 + length_t limit_size; + /// @brief 在搜索过程中使用的缓冲区最大长度。 + length_t buffer_size; + + /// @brief 已经完成的cycle,表示在此与此之前的所有rules和facts都已经被处理过。 + length_t done_cycle; + /// @brief rules库和facts库中最大的cycle,此变量在更新rules和facts前设置。 + length_t current_cycle; + /// @brief 用于存储规则的map,键为rule_t的智能指针,值为其对应的cycle。 + std::map, length_t, less_t> rules; + /// @brief 用于存储事实的map,键为rule_t的智能指针,值为其对应的cycle。 + std::map, length_t, less_t> facts; + + /// @brief 用于存储搜索过程中使用的缓冲区。 + std::unique_ptr buffer; + public: + /// @brief 构造函数,用于初始化搜索对象 + /// @param _limit_size 每个有效rule_t的最大长度。 + /// @param _buffer_size 在搜索过程中使用的缓冲区最大长度。 + search_t(length_t _limit_size, length_t _buffer_size); + + /// @brief 设置每个有效rule_t的最大长度。 + /// @param _limit_size 每个有效rule_t的最大长度。 + void set_limit_size(length_t _limit_size); + + /// @brief 设置在搜索过程中使用的缓冲区最大长度。 + /// @param _buffer_size 在搜索过程中使用的缓冲区最大长度。 + void set_buffer_size(length_t _buffer_size); + + /// @brief 重置搜索过程中的所有状态。 + void reset(); + + /// @brief 向本搜索对象添加一个rule或fact。 + /// @param text 描述rule或fact的文本。 + /// @return 如果添加成功则返回true,否则返回false。 + bool add(std::string_view text); + + /// @brief 向本搜索对象添加一些rule或fact,使用指定的分隔符。 + /// @param text 描述rule或fact的文本,他们之间使用分隔符分隔。 + /// @param sep 在text中用于分隔不同rule或fact的分隔符。 + /// @return 成功添加的rule或fact的数量。 + length_t add(std::string_view text, std::string_view sep); + + /// @brief 执行一轮搜索操作,遍历所有规则和事实,并对每个匹配的规则执行回调函数。 + /// @param callback 回调函数,每个新中找到的结果都会调用此函数。 + /// @return 搜索到新的结果的数量。 + /// @note 如果回调函数返回false,则继续搜索;如果回调函数返回true,则停止搜索。 + length_t execute(const std::function& callback); + }; +} // namespace ds + +#endif diff --git a/package.json b/package.json index 4378c13..264e909 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dist/tsds.mjs.map" ], "scripts": { - "emcc": "emcc tsds/ds.cc src/*.cc -Iinclude -lembind -o tsds/ds.mjs --emit-tsd ds.d.mts -gsource-map=inline -O3 -ffast-math -flto -s ALLOW_MEMORY_GROWTH=1", + "emcc": "emcc -std=c++20 tsds/ds.cc src/*.cc -Iinclude -lembind -o tsds/ds.mjs --emit-tsd ds.d.mts -gsource-map=inline -O3 -ffast-math -flto -s ALLOW_MEMORY_GROWTH=1", "rollup": "rollup --config rollup.config.mjs", "build": "run-s emcc rollup", "test": "cross-env NODE_OPTIONS='$NODE_OPTIONS --experimental-vm-modules' jest --config=jest.config.mjs", diff --git a/pyds/__init__.py b/pyds/__init__.py index 44be182..79f686a 100644 --- a/pyds/__init__.py +++ b/pyds/__init__.py @@ -17,4 +17,5 @@ from .list_t import List from .term_t import Term from .rule_t import Rule +from .search_t import Search from .version import __version__ diff --git a/pyds/ds.cc b/pyds/ds.cc index 88ddcac..91c9319 100644 --- a/pyds/ds.cc +++ b/pyds/ds.cc @@ -1,4 +1,6 @@ #include +#include +#include #include namespace py = pybind11; @@ -128,4 +130,13 @@ PYBIND11_MODULE(ds, m) { term_t.def_static("ground", term_ground); rule_t.def_static("ground", rule_ground); rule_t.def_static("match", rule_match); + + auto search_t = py::class_(m, "Search"); + search_t.def(py::init()); + search_t.def("set_limit_size", &ds::search_t::set_limit_size); + search_t.def("set_buffer_size", &ds::search_t::set_buffer_size); + search_t.def("reset", &ds::search_t::reset); + search_t.def("add_single", py::overload_cast(&ds::search_t::add)); + search_t.def("add_multiple", py::overload_cast(&ds::search_t::add)); + search_t.def("execute", &ds::search_t::execute); } diff --git a/pyds/search_t.py b/pyds/search_t.py new file mode 100644 index 0000000..3b53e0f --- /dev/null +++ b/pyds/search_t.py @@ -0,0 +1,31 @@ +__all__ = [ + "Search", +] + +import typing +from . import ds +from .rule_t import Rule + + +class Search: + + def __init__(self, limit_size: int = 1000, buffer_size: int = 10000): + self._search: ds.Search = ds.Search(limit_size, buffer_size) + + def set_limit_size(self, limit_size: int) -> None: + self._search.set_limit_size(limit_size) + + def set_buffer_size(self, buffer_size: int) -> None: + self._search.set_buffer_size(buffer_size) + + def reset(self) -> None: + self._search.reset() + + def add(self, text: str, sep: str | None = None) -> int: + if sep is not None: + return self._search.add_multiple(text, sep) + else: + return int(self._search.add_single(text)) + + def execute(self, callback: typing.Callable[[Rule], bool]) -> int: + return self._search.execute(lambda candidate: callback(Rule(candidate.clone()))) \ No newline at end of file diff --git a/src/search.cc b/src/search.cc new file mode 100644 index 0000000..c900ca4 --- /dev/null +++ b/src/search.cc @@ -0,0 +1,134 @@ +#include +#include +#include + +#include +#include + +namespace ds { + bool search_t::less_t::operator()(const std::unique_ptr& lhs, const std::unique_ptr& rhs) const { + const length_t lhs_size = lhs->data_size(); + const length_t rhs_size = rhs->data_size(); + if (lhs_size < rhs_size) { + return true; + } + if (lhs_size > rhs_size) { + return false; + } + if (std::memcmp(lhs->head(), rhs->head(), lhs_size) < 0) { + return true; + } + return false; + } + + search_t::search_t(length_t _limit_size, length_t _buffer_size) { + set_limit_size(_limit_size); + set_buffer_size(_buffer_size); + reset(); + } + + void search_t::set_limit_size(length_t _limit_size) { + limit_size = _limit_size; + done_cycle = 0; + } + + void search_t::set_buffer_size(length_t _buffer_size) { + buffer_size = _buffer_size; + buffer = std::unique_ptr(reinterpret_cast(operator new(buffer_size))); + done_cycle = 0; + } + + void search_t::reset() { + done_cycle = 0; + current_cycle = 0; + rules.clear(); + facts.clear(); + } + + bool search_t::add(std::string_view text) { + auto candidate = text_to_rule(text.data(), limit_size); + if (candidate) { + if (done_cycle == current_cycle) { + ++current_cycle; + } + if (candidate->premises_count() != 0) { + rules.emplace(std::move(candidate), current_cycle); + } else { + facts.emplace(std::move(candidate), current_cycle); + } + return true; + } else { + return false; + } + } + + length_t search_t::add(std::string_view text, std::string_view sep) { + length_t success_count = 0; + for (const auto candidate : std::views::split(text, sep)) { + bool success = add(std::string_view(candidate.data(), candidate.size())); + if (success) { + ++success_count; + } + } + return success_count; + } + + length_t search_t::execute(const std::function& callback) { + std::set, less_t> temp_rules; + std::set, less_t> temp_facts; + + bool break_all = false; + for (auto& [rule, rules_cycle] : rules) { + for (auto& [fact, facts_cycle] : facts) { + if (rules_cycle <= done_cycle && facts_cycle <= done_cycle) { + continue; + } + buffer->match(rule.get(), fact.get(), reinterpret_cast(buffer.get()) + buffer_size); + if (!buffer->valid()) { + continue; + } + if (buffer->data_size() > limit_size) { + continue; + } + if (buffer->premises_count() != 0) { + // rule + if (rules.find(buffer) != rules.end() || temp_rules.find(buffer) != temp_rules.end()) { + continue; + } + auto new_rule = std::unique_ptr(reinterpret_cast(operator new(buffer->data_size()))); + memcpy(new_rule.get(), buffer.get(), buffer->data_size()); + temp_rules.emplace(std::move(new_rule)); + } else { + // fact + if (facts.find(buffer) != facts.end() || temp_facts.find(buffer) != temp_facts.end()) { + continue; + } + auto new_fact = std::unique_ptr(reinterpret_cast(operator new(buffer->data_size()))); + memcpy(new_fact.get(), buffer.get(), buffer->data_size()); + temp_facts.emplace(std::move(new_fact)); + } + if (callback(buffer.get())) { + break_all = true; + break; + } + } + if (break_all) { + break; + } + } + + if (!break_all) { + done_cycle = current_cycle; + } + ++current_cycle; + for (auto it = temp_rules.begin(); it != temp_rules.end();) { + auto node = temp_rules.extract(it++); + rules.emplace(std::move(node.value()), current_cycle); + } + for (auto it = temp_facts.begin(); it != temp_facts.end();) { + auto node = temp_facts.extract(it++); + facts.emplace(std::move(node.value()), current_cycle); + } + return temp_rules.size() + temp_facts.size(); + } +} // namespace ds diff --git a/tsds/ds.cc b/tsds/ds.cc index abf2403..f703518 100644 --- a/tsds/ds.cc +++ b/tsds/ds.cc @@ -1,4 +1,5 @@ #include +#include #include namespace em = emscripten; @@ -96,6 +97,18 @@ auto rule_match(ds::rule_t* rule_1, ds::rule_t* rule_2, int length) -> std::uniq return std::unique_ptr(result); } +auto search_add_single(ds::search_t* search, const std::string& text) -> bool { + return search->add(text); +} + +auto search_add_multiple(ds::search_t* search, const std::string& text, const std::string& sep) -> ds::length_t { + return search->add(text, sep); +} + +auto search_execute(ds::search_t* search, const em::val& callback) -> ds::length_t { + return search->execute([&callback](ds::rule_t* candidate) -> bool { return callback(clone(candidate)).as(); }); +} + EMSCRIPTEN_BINDINGS(ds) { em::register_vector("Buffer"); @@ -137,4 +150,14 @@ EMSCRIPTEN_BINDINGS(ds) { term_t.class_function("ground", term_ground, em::return_value_policy::take_ownership()); rule_t.class_function("ground", rule_ground, em::return_value_policy::take_ownership()); rule_t.class_function("match", rule_match, em::return_value_policy::take_ownership()); + + auto search_t = em::class_("Search"); + search_t.constructor(); + search_t.function("set_limit_size", &ds::search_t::set_limit_size); + search_t.function("set_buffer_size", &ds::search_t::set_buffer_size); + search_t.function("reset", &ds::search_t::reset); + // 因为embind的限制,这里无法使用string_view和function。 + search_t.function("add_single", &search_add_single, em::allow_raw_pointers()); + search_t.function("add_multiple", &search_add_multiple, em::allow_raw_pointers()); + search_t.function("execute", &search_execute, em::allow_raw_pointers()); } diff --git a/tsds/tsds.mts b/tsds/tsds.mts index 0512700..6973f63 100644 --- a/tsds/tsds.mts +++ b/tsds/tsds.mts @@ -189,3 +189,38 @@ export class rule_t extends _common_t { return new rule_t(rule, capacity); } } + +export class search_t { + _search: dst.Search; + + constructor(limit_size: number = 1000, buffer_size: number = 10000) { + this._search = new ds.Search(limit_size, buffer_size); + } + + set_limit_size(limit_size: number): void { + this._search.set_limit_size(limit_size); + } + + set_buffer_size(buffer_size: number): void { + this._search.set_buffer_size(buffer_size); + } + + reset(): void { + this._search.reset(); + } + + add(text: string, sep: string | null = null): number { + if (sep !== null) { + return this._search.add_multiple(text, sep); + } else { + return Number(this._search.add_single(text)); + } + } + + execute(callback: (candidate: rule_t) => boolean): number { + return this._search.execute((candidate: dst.Rule): boolean => { + // 由于embind的限制,这里的candidate已经在c++端被复制过一次,在此不需要再次复制。 + return callback(new rule_t(candidate)); + }); + } +}