Skip to main content

pattern_core/
lib.rs

1//! pattern-core - Core pattern data structures
2//!
3//! This crate provides the core pattern data structures for the pattern-rs library.
4//! It is a faithful port of the gram-hs reference implementation.
5//!
6//! # Overview
7//!
8//! The `pattern-core` crate defines these main types:
9//!
10//! - **[`StandardGraph`]**: An ergonomic, zero-configuration graph
11//!   for building and querying graph structures. This is the recommended starting point
12//!   for most users.
13//!
14//! - **[`Subject`]**: A self-descriptive value type with identity, labels,
15//!   and properties. Use [`Subject::build`] for fluent construction.
16//!
17//! - **[`Pattern<V>`](pattern::Pattern)**: A recursive, nested structure (s-expression-like)
18//!   that is generic over value type `V`. This is the foundational data structure for
19//!   representing nested, hierarchical data that may be interpreted as graphs.
20//!
21//! # Quick Start
22//!
23//! Build a graph with [`StandardGraph`]:
24//!
25//! ```rust
26//! use pattern_core::graph::StandardGraph;
27//! use pattern_core::subject::Subject;
28//!
29//! let mut g = StandardGraph::new();
30//!
31//! // Add nodes using the fluent SubjectBuilder
32//! let alice = Subject::build("alice").label("Person").property("name", "Alice").done();
33//! let bob   = Subject::build("bob").label("Person").property("name", "Bob").done();
34//! g.add_node(alice.clone());
35//! g.add_node(bob.clone());
36//!
37//! // Add a relationship — pass the Subject objects directly
38//! g.add_relationship(Subject::build("r1").label("KNOWS").done(), &alice, &bob);
39//!
40//! assert_eq!(g.node_count(), 2);
41//! assert_eq!(g.relationship_count(), 1);
42//!
43//! // Query the graph
44//! let source = g.source(&"r1".into()).unwrap();
45//! assert_eq!(source.value.identity.0, "alice");
46//! let neighbors = g.neighbors(&"bob".into());
47//! assert_eq!(neighbors.len(), 1);
48//! ```
49//!
50//! # Low-Level Pattern Construction
51//!
52//! For direct pattern manipulation without the graph layer:
53//!
54//! ```rust
55//! use pattern_core::{Pattern, Subject, Symbol, Value};
56//! use std::collections::{HashSet, HashMap};
57//!
58//! // Create an atomic pattern (special case)
59//! let atomic = Pattern::point("hello".to_string());
60//!
61//! // Create a pattern with elements (primary constructor)
62//! let pattern = Pattern::pattern("parent".to_string(), vec![
63//!     Pattern::point("child1".to_string()),
64//!     Pattern::point("child2".to_string()),
65//! ]);
66//!
67//! // Access pattern components
68//! assert_eq!(atomic.value(), "hello");
69//! assert_eq!(pattern.length(), 2);
70//! assert_eq!(pattern.depth(), 1);
71//!
72//! // Transform pattern values (Functor)
73//! let upper = pattern.clone().map(|s| s.to_uppercase());
74//! assert_eq!(upper.value(), "PARENT");
75//!
76//! // Validate pattern structure
77//! use pattern_core::ValidationRules;
78//! let rules = ValidationRules {
79//!     max_depth: Some(10),
80//!     ..Default::default()
81//! };
82//! assert!(pattern.validate(&rules).is_ok());
83//!
84//! // Analyze pattern structure
85//! let analysis = pattern.analyze_structure();
86//! println!("Structure: {}", analysis.summary);
87//!
88//! // Create a pattern with Subject value
89//! let subject = Subject {
90//!     identity: Symbol("n".to_string()),
91//!     labels: {
92//!         let mut s = HashSet::new();
93//!         s.insert("Person".to_string());
94//!         s
95//!     },
96//!     properties: {
97//!         let mut m = HashMap::new();
98//!         m.insert("name".to_string(), Value::VString("Alice".to_string()));
99//!         m
100//!     },
101//! };
102//!
103//! let pattern_with_subject: Pattern<Subject> = Pattern::point(subject);
104//! ```
105//!
106//! # Pattern Combination
107//!
108//! Patterns can be combined associatively using the `combine()` method when the value type
109//! implements the `Combinable` trait. Combination merges two patterns by combining their values
110//! and concatenating their elements.
111//!
112//! ```rust
113//! use pattern_core::{Pattern, Combinable};
114//!
115//! // Combine atomic patterns (no elements)
116//! let p1 = Pattern::point("hello".to_string());
117//! let p2 = Pattern::point(" world".to_string());
118//! let combined = p1.combine(p2);
119//! assert_eq!(combined.value(), "hello world");
120//! assert_eq!(combined.length(), 0);
121//!
122//! // Combine patterns with elements
123//! let p3 = Pattern::pattern("a".to_string(), vec![
124//!     Pattern::point("b".to_string()),
125//!     Pattern::point("c".to_string()),
126//! ]);
127//! let p4 = Pattern::pattern("d".to_string(), vec![
128//!     Pattern::point("e".to_string()),
129//! ]);
130//! let result = p3.combine(p4);
131//! assert_eq!(result.value(), "ad");
132//! assert_eq!(result.length(), 3); // [b, c, e]
133//!
134//! // Associativity: (a ⊕ b) ⊕ c = a ⊕ (b ⊕ c)
135//! let a = Pattern::point("a".to_string());
136//! let b = Pattern::point("b".to_string());
137//! let c = Pattern::point("c".to_string());
138//! let left = a.clone().combine(b.clone()).combine(c.clone());
139//! let right = a.combine(b.combine(c));
140//! assert_eq!(left, right);
141//! ```
142//!
143//! # Pattern Ordering
144//!
145//! Patterns implement `Ord` and `PartialOrd` for types that support ordering,
146//! enabling sorting, comparison, and use in ordered data structures.
147//!
148//! ```rust
149//! use pattern_core::Pattern;
150//! use std::collections::{BTreeSet, BTreeMap};
151//!
152//! // Compare patterns
153//! let p1 = Pattern::point(1);
154//! let p2 = Pattern::point(2);
155//! assert!(p1 < p2);
156//!
157//! // Value-first ordering: values compared before elements
158//! let p3 = Pattern::pattern(3, vec![Pattern::point(100)]);
159//! let p4 = Pattern::pattern(4, vec![Pattern::point(1)]);
160//! assert!(p3 < p4); // 3 < 4, elements not compared
161//!
162//! // Sort patterns
163//! let mut patterns = vec![
164//!     Pattern::point(5),
165//!     Pattern::point(2),
166//!     Pattern::point(8),
167//! ];
168//! patterns.sort();
169//! assert_eq!(patterns[0], Pattern::point(2));
170//!
171//! // Find min/max
172//! let min = patterns.iter().min().unwrap();
173//! let max = patterns.iter().max().unwrap();
174//! assert_eq!(min, &Pattern::point(2));
175//! assert_eq!(max, &Pattern::point(8));
176//!
177//! // Use in BTreeSet (maintains sorted order)
178//! let mut set = BTreeSet::new();
179//! set.insert(Pattern::point(5));
180//! set.insert(Pattern::point(2));
181//! set.insert(Pattern::point(8));
182//! let sorted: Vec<_> = set.iter().map(|p| p.value).collect();
183//! assert_eq!(sorted, vec![2, 5, 8]);
184//!
185//! // Use as BTreeMap keys
186//! let mut map = BTreeMap::new();
187//! map.insert(Pattern::point(1), "first");
188//! map.insert(Pattern::point(2), "second");
189//! assert_eq!(map.get(&Pattern::point(1)), Some(&"first"));
190//! ```
191//!
192//! # WASM Compatibility
193//!
194//! All types in this crate are fully compatible with WebAssembly targets. Compile for WASM with:
195//!
196//! ```bash
197//! cargo build --package pattern-core --target wasm32-unknown-unknown
198//! ```
199//!
200//! # Reference Implementation
201//!
202//! This crate is ported from the gram-hs reference implementation:
203//! - Pattern: `../pattern-hs/libs/pattern/src/Pattern.hs`
204//! - Subject: `../pattern-hs/libs/subject/src/Subject/Core.hs`
205//! - Feature Spec: `../pattern-hs/specs/001-pattern-data-structure/`
206
207pub mod graph;
208pub mod pattern;
209pub mod pattern_graph;
210pub mod reconcile;
211pub mod subject;
212pub mod test_utils;
213
214#[cfg(feature = "python")]
215pub mod python;
216
217#[cfg(feature = "wasm")]
218pub mod wasm;
219
220pub use graph::{
221    all_paths, betweenness_centrality, bfs, canonical_classifier, classify_by_shape,
222    connected_components, degree_centrality, dfs, directed, directed_reverse, filter_graph,
223    fold_graph, frame_query, from_graph_lens, from_pattern_graph, from_test_node, has_cycle,
224    has_path, is_connected, is_neighbor, map_all_graph, map_graph, map_with_context, materialize,
225    memoize_incident_rels, minimum_spanning_tree, para_graph, para_graph_fixed,
226    query_annotations_of, query_co_members, query_walks_containing, shortest_path,
227    topological_sort, undirected, unfold_graph, CategoryMappers, GraphClass, GraphClassifier,
228    GraphQuery, GraphValue, GraphView, StandardGraph, Substitution, TraversalDirection,
229    TraversalWeight,
230};
231pub use pattern::{unfold, Pattern, StructureAnalysis, ValidationError, ValidationRules};
232pub use pattern_graph::{
233    from_pattern_graph as graph_query_from_pattern_graph, from_patterns, from_patterns_with_policy,
234    merge as pg_merge, merge_with_policy as pg_merge_with_policy, PatternGraph,
235};
236pub use reconcile::{
237    ElementMergeStrategy, HasIdentity, LabelMerge, Mergeable, PropertyMerge, ReconciliationPolicy,
238    Refinable, SubjectMergeStrategy,
239};
240pub use subject::{PropertyRecord, RangeValue, Subject, SubjectBuilder, Symbol, Value};
241
242// Re-export comonad operations for convenient access
243// These are defined in pattern::comonad and pattern::comonad_helpers modules
244// All operations are methods on Pattern<V>, so no additional re-exports needed beyond Pattern itself
245
246// ============================================================================
247// Combinable Trait
248// ============================================================================
249
250/// Types that support associative combination.
251///
252/// Implementors must ensure that combination is associative:
253/// `(a.combine(b)).combine(c)` must equal `a.combine(b.combine(c))` for all values.
254///
255/// This trait is used to enable pattern combination for `Pattern<V>` where `V: Combinable`.
256///
257/// # Laws
258///
259/// **Associativity**: For all values a, b, c of type Self:
260/// ```text
261/// (a.combine(b)).combine(c) == a.combine(b.combine(c))
262/// ```
263///
264/// # Examples
265///
266/// ```rust
267/// use pattern_core::Combinable;
268///
269/// let s1 = String::from("hello");
270/// let s2 = String::from(" world");
271/// let result = s1.combine(s2);
272/// assert_eq!(result, "hello world");
273/// ```
274pub trait Combinable {
275    /// Combines two values associatively.
276    ///
277    /// # Parameters
278    ///
279    /// * `self` - The first value (consumed)
280    /// * `other` - The second value to combine with (consumed)
281    ///
282    /// # Returns
283    ///
284    /// A new value representing the combination of `self` and `other`.
285    ///
286    /// # Laws
287    ///
288    /// Must be associative: `(a.combine(b)).combine(c) == a.combine(b.combine(c))`
289    fn combine(self, other: Self) -> Self;
290}
291
292// ============================================================================
293// Standard Implementations
294// ============================================================================
295
296/// Combines two strings by concatenation.
297///
298/// String concatenation is associative: `(a + b) + c = a + (b + c)`
299///
300/// # Examples
301///
302/// ```rust
303/// use pattern_core::Combinable;
304///
305/// let s1 = String::from("hello");
306/// let s2 = String::from(" world");
307/// let result = s1.combine(s2);
308/// assert_eq!(result, "hello world");
309/// ```
310impl Combinable for String {
311    fn combine(mut self, other: Self) -> Self {
312        self.push_str(&other);
313        self
314    }
315}
316
317/// Combines two vectors by concatenation.
318///
319/// Vector concatenation is associative: `(a ++ b) ++ c = a ++ (b ++ c)`
320///
321/// # Examples
322///
323/// ```rust
324/// use pattern_core::Combinable;
325///
326/// let v1 = vec![1, 2, 3];
327/// let v2 = vec![4, 5];
328/// let result = v1.combine(v2);
329/// assert_eq!(result, vec![1, 2, 3, 4, 5]);
330/// ```
331impl<T> Combinable for Vec<T> {
332    fn combine(mut self, other: Self) -> Self {
333        self.extend(other);
334        self
335    }
336}
337
338/// Combines two unit values (trivial).
339///
340/// Unit combination is trivially associative.
341///
342/// # Examples
343///
344/// ```rust
345/// use pattern_core::Combinable;
346///
347/// let u1 = ();
348/// let u2 = ();
349/// let result = u1.combine(u2);
350/// assert_eq!(result, ());
351/// ```
352impl Combinable for () {
353    fn combine(self, _other: Self) -> Self {}
354}
355
356// ============================================================================
357// Subject Combination Strategies
358// ============================================================================
359
360/// Combination strategy for Subject that merges labels and properties.
361///
362/// This strategy combines two subjects by:
363/// - Using the identity from the first subject
364/// - Taking the union of labels from both subjects
365/// - Merging properties (values from the second subject overwrite the first)
366///
367/// # Semigroup Laws
368///
369/// This implementation satisfies associativity:
370/// - Identity choice is associative (always picks leftmost)
371/// - Label union is associative (set union is associative)
372/// - Property merge is associative with right-bias (latter values win)
373///
374/// # Examples
375///
376/// ```rust
377/// use pattern_core::{Subject, Symbol, Combinable};
378/// use std::collections::{HashMap, HashSet};
379///
380/// let s1 = Subject {
381///     identity: Symbol("n1".to_string()),
382///     labels: {
383///         let mut s = HashSet::new();
384///         s.insert("Person".to_string());
385///         s
386///     },
387///     properties: HashMap::new(),
388/// };
389///
390/// let s2 = Subject {
391///     identity: Symbol("n2".to_string()),
392///     labels: {
393///         let mut s = HashSet::new();
394///         s.insert("Employee".to_string());
395///         s
396///     },
397///     properties: HashMap::new(),
398/// };
399///
400/// // Merge combines labels and uses first identity
401/// let merged = s1.combine(s2);
402/// assert_eq!(merged.identity.0, "n1");
403/// assert!(merged.labels.contains("Person"));
404/// assert!(merged.labels.contains("Employee"));
405/// ```
406impl Combinable for Subject {
407    fn combine(self, other: Self) -> Self {
408        // Keep first identity (leftmost in associative chain)
409        let identity = self.identity;
410
411        // Union of labels (set union is associative)
412        let labels = self.labels.union(&other.labels).cloned().collect();
413
414        // Merge properties (right overwrites left)
415        let mut properties = self.properties;
416        properties.extend(other.properties);
417
418        Subject {
419            identity,
420            labels,
421            properties,
422        }
423    }
424}
425
426/// Newtype wrapper for "first wins" combination strategy.
427///
428/// When combining two FirstSubject instances, the first subject is returned
429/// and the second is discarded. This is useful for scenarios where you want
430/// to keep the initial subject and ignore subsequent ones.
431///
432/// # Semigroup Laws
433///
434/// This satisfies associativity trivially: first(first(a, b), c) = first(a, first(b, c)) = a
435///
436/// # Examples
437///
438/// ```rust
439/// use pattern_core::{Subject, Symbol, Combinable};
440/// use std::collections::HashSet;
441///
442/// let s1 = Subject {
443///     identity: Symbol("alice".to_string()),
444///     labels: HashSet::new(),
445///     properties: Default::default(),
446/// };
447///
448/// let s2 = Subject {
449///     identity: Symbol("bob".to_string()),
450///     labels: HashSet::new(),
451///     properties: Default::default(),
452/// };
453///
454/// // First wins - s2 is discarded
455/// let result = s1.combine(s2);
456/// assert_eq!(result.identity.0, "alice");
457/// ```
458#[derive(Clone, PartialEq)]
459pub struct FirstSubject(pub Subject);
460
461impl Combinable for FirstSubject {
462    fn combine(self, _other: Self) -> Self {
463        self // Always return first, discard second
464    }
465}
466
467/// Newtype wrapper for "last wins" combination strategy.
468///
469/// When combining two LastSubject instances, the second subject is returned
470/// and the first is discarded. This is useful for scenarios where you want
471/// the most recent subject to take precedence.
472///
473/// # Semigroup Laws
474///
475/// This satisfies associativity trivially: last(last(a, b), c) = last(a, last(b, c)) = c
476///
477/// # Examples
478///
479/// ```rust
480/// use pattern_core::{Subject, Symbol, Combinable, LastSubject};
481/// use std::collections::HashSet;
482///
483/// let s1 = LastSubject(Subject {
484///     identity: Symbol("alice".to_string()),
485///     labels: HashSet::new(),
486///     properties: Default::default(),
487/// });
488///
489/// let s2 = LastSubject(Subject {
490///     identity: Symbol("bob".to_string()),
491///     labels: HashSet::new(),
492///     properties: Default::default(),
493/// });
494///
495/// // Last wins - s1 is the last argument, so it wins
496/// let result = s2.combine(s1);
497/// assert_eq!(result.0.identity.0, "alice");
498/// ```
499#[derive(Clone, PartialEq)]
500pub struct LastSubject(pub Subject);
501
502impl Combinable for LastSubject {
503    fn combine(self, other: Self) -> Self {
504        other // Always return second, discard first
505    }
506}
507
508/// Newtype wrapper for "empty" combination strategy that creates anonymous subjects.
509///
510/// When combining two EmptySubject instances, the result is always an anonymous
511/// subject with no labels or properties. This serves as the identity element for
512/// a Monoid-like structure.
513///
514/// # Semigroup Laws
515///
516/// This satisfies associativity trivially: empty(empty(a, b), c) = empty(a, empty(b, c)) = empty
517///
518/// # Monoid Laws
519///
520/// When used with Default, this provides monoid identity:
521/// - Left identity: empty.combine(s) = empty
522/// - Right identity: s.combine(empty) = empty
523///
524/// # Examples
525///
526/// ```rust
527/// use pattern_core::{Subject, Symbol, Combinable, EmptySubject};
528/// use std::collections::HashSet;
529///
530/// let s1 = EmptySubject(Subject {
531///     identity: Symbol("alice".to_string()),
532///     labels: {
533///         let mut s = HashSet::new();
534///         s.insert("Person".to_string());
535///         s
536///     },
537///     properties: Default::default(),
538/// });
539///
540/// let empty = EmptySubject(Subject {
541///     identity: Symbol("_".to_string()),
542///     labels: HashSet::new(),
543///     properties: Default::default(),
544/// });
545///
546/// // Always returns empty (anonymous)
547/// let result = s1.combine(empty);
548/// assert_eq!(result.0.identity.0, "_");
549/// assert!(result.0.labels.is_empty());
550/// ```
551#[derive(Clone, PartialEq)]
552pub struct EmptySubject(pub Subject);
553
554impl Combinable for EmptySubject {
555    fn combine(self, _other: Self) -> Self {
556        // Always return anonymous empty subject
557        EmptySubject(Subject {
558            identity: Symbol("_".to_string()),
559            labels: Default::default(),
560            properties: Default::default(),
561        })
562    }
563}
564
565impl Default for EmptySubject {
566    fn default() -> Self {
567        EmptySubject(Subject {
568            identity: Symbol("_".to_string()),
569            labels: Default::default(),
570            properties: Default::default(),
571        })
572    }
573}
574
575#[cfg(test)]
576mod tests {
577    use super::*;
578    use std::collections::{HashMap, HashSet};
579
580    #[test]
581    fn subject_merge_combines_labels_and_properties() {
582        let s1 = Subject {
583            identity: Symbol("n1".to_string()),
584            labels: {
585                let mut s = HashSet::new();
586                s.insert("Person".to_string());
587                s
588            },
589            properties: {
590                let mut m = HashMap::new();
591                m.insert("name".to_string(), Value::VString("Alice".to_string()));
592                m
593            },
594        };
595
596        let s2 = Subject {
597            identity: Symbol("n2".to_string()),
598            labels: {
599                let mut s = HashSet::new();
600                s.insert("Employee".to_string());
601                s
602            },
603            properties: {
604                let mut m = HashMap::new();
605                m.insert("role".to_string(), Value::VString("Engineer".to_string()));
606                m
607            },
608        };
609
610        let merged = s1.combine(s2);
611
612        assert_eq!(merged.identity.0, "n1");
613        assert_eq!(merged.labels.len(), 2);
614        assert!(merged.labels.contains("Person"));
615        assert!(merged.labels.contains("Employee"));
616        assert_eq!(merged.properties.len(), 2);
617    }
618
619    #[test]
620    fn subject_merge_is_associative() {
621        let s1 = Subject {
622            identity: Symbol("a".to_string()),
623            labels: {
624                let mut s = HashSet::new();
625                s.insert("L1".to_string());
626                s
627            },
628            properties: HashMap::new(),
629        };
630
631        let s2 = Subject {
632            identity: Symbol("b".to_string()),
633            labels: {
634                let mut s = HashSet::new();
635                s.insert("L2".to_string());
636                s
637            },
638            properties: HashMap::new(),
639        };
640
641        let s3 = Subject {
642            identity: Symbol("c".to_string()),
643            labels: {
644                let mut s = HashSet::new();
645                s.insert("L3".to_string());
646                s
647            },
648            properties: HashMap::new(),
649        };
650
651        // (s1 + s2) + s3
652        let left = s1.clone().combine(s2.clone()).combine(s3.clone());
653
654        // s1 + (s2 + s3)
655        let right = s1.combine(s2.combine(s3));
656
657        assert_eq!(left.identity, right.identity);
658        assert_eq!(left.labels, right.labels);
659    }
660
661    #[test]
662    fn first_subject_keeps_first() {
663        let s1 = FirstSubject(Subject {
664            identity: Symbol("alice".to_string()),
665            labels: HashSet::new(),
666            properties: HashMap::new(),
667        });
668
669        let s2 = FirstSubject(Subject {
670            identity: Symbol("bob".to_string()),
671            labels: HashSet::new(),
672            properties: HashMap::new(),
673        });
674
675        let result = s1.clone().combine(s2);
676        assert_eq!(result.0.identity.0, "alice");
677    }
678
679    #[test]
680    fn last_subject_keeps_last() {
681        let s1 = LastSubject(Subject {
682            identity: Symbol("alice".to_string()),
683            labels: HashSet::new(),
684            properties: HashMap::new(),
685        });
686
687        let s2 = LastSubject(Subject {
688            identity: Symbol("bob".to_string()),
689            labels: HashSet::new(),
690            properties: HashMap::new(),
691        });
692
693        let result = s1.combine(s2.clone());
694        assert_eq!(result.0.identity.0, "bob");
695    }
696
697    #[test]
698    fn empty_subject_returns_anonymous() {
699        let s1 = EmptySubject(Subject {
700            identity: Symbol("alice".to_string()),
701            labels: {
702                let mut s = HashSet::new();
703                s.insert("Person".to_string());
704                s
705            },
706            properties: HashMap::new(),
707        });
708
709        let s2 = EmptySubject(Subject {
710            identity: Symbol("bob".to_string()),
711            labels: HashSet::new(),
712            properties: HashMap::new(),
713        });
714
715        let result = s1.combine(s2);
716        assert_eq!(result.0.identity.0, "_");
717        assert!(result.0.labels.is_empty());
718        assert!(result.0.properties.is_empty());
719    }
720
721    #[test]
722    fn empty_subject_is_identity() {
723        let empty = EmptySubject::default();
724        let result = empty.clone().combine(empty);
725        assert_eq!(result.0.identity.0, "_");
726    }
727}