Skip to content

feat: Add chain_t class for multi-premise matching with language bindings - #260

Merged
hzhangxyz merged 11 commits into
mainfrom
dev/chain
Mar 12, 2026
Merged

feat: Add chain_t class for multi-premise matching with language bindings#260
hzhangxyz merged 11 commits into
mainfrom
dev/chain

Conversation

@hzhangxyz

@hzhangxyz hzhangxyz commented Mar 11, 2026

Copy link
Copy Markdown
Member

Summary

This PR introduces the chain_t class for multi-premise rule matching in a single cycle, along with Python and TypeScript bindings and comprehensive documentation.

Changes

Core Implementation

  • New chain_t class (include/ds/chain.hh, src/chain.cc) - Matches all premises of a rule in a single cycle, compared to search_t which matches one premise per cycle
  • Bug fixes:
    • Fixed incorrect .get() call on raw pointer in chain.cc
    • Set default max_depth to 8 in chain_t
    • Refactored execute logic to use recursive matching for premises

Language Bindings

  • Python bindings (apyds/chain_t.py, apyds/_ds.pyi, tests/test_chain.py)
  • TypeScript bindings (atsds/index.mts, tests/test_chain.mjs)

Documentation

  • Added API documentation for C++, Python, and TypeScript in both English and Chinese
  • Updated README with Chain class features

Tests

  • Added comprehensive test coverage (tests/test_chain.cc, tests/test_chain.py, tests/test_chain.mjs)

Key Features

  • Single-cycle multi-premise matching
  • Configurable limit_size and buffer_size parameters
  • Depth-limited recursion with max_depth control
  • Full language bindings for Python and TypeScript

Copilot AI review requested due to automatic review settings March 11, 2026 15:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new “Chain” inference engine to the deductive system, intended to match all premises of a rule within a single execute cycle, and exposes it consistently across the C++ core, Python bindings, and WASM/TypeScript wrapper—with corresponding multi-language tests.

Changes:

  • Added ds::chain_t implementation and public header (include/ds/chain.hh, src/chain.cc).
  • Exposed Chain via pybind11 (apyds/ds.cc, apyds/_ds.pyi, apyds/chain_t.py, export wiring) and embind/TypeScript wrapper (atsds/ds.cc, atsds/index.mts).
  • Added new tests validating Chain behavior in C++/gtest, Python/pytest, and JS/jest.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/test_chain.py Adds pytest coverage for the new apyds.Chain wrapper behavior.
tests/test_chain.mjs Adds jest coverage for the new Chain TS wrapper over WASM bindings.
tests/test_chain.cc Adds gtest coverage for ds::chain_t.
src/chain.cc Implements the new chain-based inference algorithm.
include/ds/chain.hh Declares the ds::chain_t public API and internal state.
atsds/index.mts Adds a TS Chain wrapper consistent with existing Search wrapper.
atsds/ds.cc Adds embind bindings for ds::chain_t (Chain) and its methods.
apyds/ds.py Exposes the bound Chain type from the extension module.
apyds/ds.cc Adds pybind11 bindings for ds::chain_t (Chain).
apyds/chain_t.py Adds a Python ergonomic wrapper Chain mirroring Search.
apyds/_ds.pyi Adds typing stubs for the bound Chain class.
apyds/init.py Exports the Python wrapper Chain at the package top-level.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/chain.cc
@@ -0,0 +1,167 @@
#include <cstring>
#include <set>

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chain_t::execute() uses std::vector but this translation unit doesn’t include <vector>, which will fail to compile on toolchains that don’t pull it in transitively. Add the missing header include (and avoid relying on indirect includes).

Suggested change
#include <set>
#include <set>
#include <vector>

Copilot uses AI. Check for mistakes.
Comment thread src/chain.cc Outdated
Comment on lines +77 to +79
if (rules_cycle <= done_cycle && facts_cycle <= done_cycle) {
continue;
}

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cycle-filtering logic for multi-premise chaining is incorrect: initial chain generation skips pairs where both rules_cycle and the first-matched facts_cycle are <= done_cycle. This causes missed inferences when an old rule’s first premise is satisfied by an old fact, but a later premise is satisfied by a newly-added fact (e.g., rule a b -> c, old fact a, new fact b won’t infer c). You’ll need to restructure the algorithm to allow old facts for earlier premises while still ensuring each emitted result depends on at least one ‘new’ rule/fact (e.g., propagate a has_new_input flag alongside each partial chain and gate results on it, rather than gating only on the first premise match).

Suggested change
if (rules_cycle <= done_cycle && facts_cycle <= done_cycle) {
continue;
}

Copilot uses AI. Check for mistakes.
Comment thread tests/test_chain.cc
Comment on lines +41 to +46
auto target = ds::text_to_rule("q", limit_size);
bool success = false;
auto count = search->execute([&success, &target](ds::rule_t* rule) {
if (memcmp(rule, target.get(), rule->data_size()) == 0) {
success = true;
return true;

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The callback comparison uses memcmp(rule, target.get(), rule->data_size()) without first checking that target->data_size() is at least rule->data_size(). If a candidate rule differs in size, this can read past the end of target (UB) and make the test flaky. Compare sizes first (or compare via a safe helper that checks size equality before memcmp).

Copilot uses AI. Check for mistakes.
Comment thread tests/test_chain.cc Outdated
Comment on lines +60 to +65
auto target = ds::text_to_rule("r", limit_size);
bool success = false;
auto count = search->execute([&success, &target](ds::rule_t* rule) {
if (memcmp(rule, target.get(), rule->data_size()) == 0) {
success = true;
return true;

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same potential out-of-bounds memcmp issue as above: the length passed is rule->data_size() but target may be smaller when the candidate doesn’t match, leading to UB. Check data_size() equality before calling memcmp (or use a safe equality helper).

Copilot uses AI. Check for mistakes.
Comment thread tests/test_chain.cc Outdated
Comment on lines +91 to +96
auto target = ds::text_to_rule("s", limit_size);
bool success = false;
auto count = search->execute([&success, &target](ds::rule_t* rule) {
if (memcmp(rule, target.get(), rule->data_size()) == 0) {
success = true;
return true;

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same potential out-of-bounds memcmp issue as above: the length passed is rule->data_size() but target may be smaller when the candidate doesn’t match, leading to UB. Check data_size() equality before calling memcmp (or use a safe equality helper).

Copilot uses AI. Check for mistakes.
- Add chain_t class (include/ds/chain.hh, src/chain.cc)
- Add tests for chain_t (tests/test_chain.cc)
- chain_t matches all premises of a rule in a single cycle
  vs search_t which only matches one premise per cycle
Add documentation for the Chain/chain_t class to README and all API documentation files:
- README.md: Add Chain to features and API overview sections
- docs/en/api/typescript.md: Add Chain class API reference
- docs/en/api/python.md: Add Chain class API reference
- docs/en/api/cpp.md: Add chain_t class API reference
- docs/zh/api/typescript.md: Add Chinese Chain class API reference
- docs/zh/api/python.md: Add Chinese Chain class API reference
- docs/zh/api/cpp.md: Add Chinese chain_t class API reference
- Refactor chain_t::execute to use recursive matching for premises
- Add max_depth parameter to control maximum recursion depth
- Add set_max_depth() method to chain_t class
- Reject rules with premises count exceeding max_depth in add()
- Remove existing rules exceeding new max_depth when set_max_depth is called
- Update cycle tracking: use last_fact_cycle instead of per-fact cycles
- Add bindings for Python (apyds) and TypeScript (atsds)
- Add tests for set_max_depth functionality
- Update documentation for C++, Python, and TypeScript APIs
@hzhangxyz
hzhangxyz force-pushed the dev/chain branch 6 times, most recently from eeb67fc to b883953 Compare March 12, 2026 07:12
@hzhangxyz hzhangxyz changed the title Add chain class. feat: Add chain_t class for multi-premise matching with language bindings Mar 12, 2026
@hzhangxyz
hzhangxyz merged commit ff767ee into main Mar 12, 2026
67 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants