tensorlogic_adapters/
compiler_integration.rs

1//! Integration utilities for tensorlogic-compiler.
2//!
3//! This module provides utilities for exporting SymbolTable data to
4//! tensorlogic-compiler's CompilerContext and for bidirectional synchronization.
5
6use anyhow::Result;
7use std::collections::HashMap;
8
9use crate::{DomainInfo, PredicateInfo, SymbolTable};
10
11/// Export utilities for compiler integration.
12pub struct CompilerExport;
13
14impl CompilerExport {
15    /// Export domain information as a simple map for compiler consumption.
16    ///
17    /// This returns a HashMap that maps domain names to their cardinalities,
18    /// suitable for direct use in compiler contexts.
19    ///
20    /// # Example
21    ///
22    /// ```rust
23    /// use tensorlogic_adapters::{SymbolTable, DomainInfo, CompilerExport};
24    ///
25    /// let mut table = SymbolTable::new();
26    /// table.add_domain(DomainInfo::new("Person", 100)).unwrap();
27    /// table.add_domain(DomainInfo::new("Location", 50)).unwrap();
28    ///
29    /// let domain_map = CompilerExport::export_domains(&table);
30    /// assert_eq!(domain_map.get("Person"), Some(&100));
31    /// assert_eq!(domain_map.get("Location"), Some(&50));
32    /// ```
33    pub fn export_domains(table: &SymbolTable) -> HashMap<String, usize> {
34        table
35            .domains
36            .iter()
37            .map(|(name, info)| (name.clone(), info.cardinality))
38            .collect()
39    }
40
41    /// Export predicate signatures for type checking.
42    ///
43    /// Returns a HashMap mapping predicate names to their argument domain lists.
44    ///
45    /// # Example
46    ///
47    /// ```rust
48    /// use tensorlogic_adapters::{SymbolTable, DomainInfo, PredicateInfo, CompilerExport};
49    ///
50    /// let mut table = SymbolTable::new();
51    /// table.add_domain(DomainInfo::new("Person", 100)).unwrap();
52    /// table.add_predicate(PredicateInfo::new(
53    ///     "knows",
54    ///     vec!["Person".to_string(), "Person".to_string()]
55    /// )).unwrap();
56    ///
57    /// let signatures = CompilerExport::export_predicate_signatures(&table);
58    /// assert_eq!(signatures.get("knows"), Some(&vec!["Person".to_string(), "Person".to_string()]));
59    /// ```
60    pub fn export_predicate_signatures(table: &SymbolTable) -> HashMap<String, Vec<String>> {
61        table
62            .predicates
63            .iter()
64            .map(|(name, info)| (name.clone(), info.arg_domains.clone()))
65            .collect()
66    }
67
68    /// Export variable bindings for scope analysis.
69    ///
70    /// Returns a HashMap mapping variable names to their domain types.
71    ///
72    /// # Example
73    ///
74    /// ```rust
75    /// use tensorlogic_adapters::{SymbolTable, DomainInfo, CompilerExport};
76    ///
77    /// let mut table = SymbolTable::new();
78    /// table.add_domain(DomainInfo::new("Person", 100)).unwrap();
79    /// table.bind_variable("x", "Person").unwrap();
80    /// table.bind_variable("y", "Person").unwrap();
81    ///
82    /// let bindings = CompilerExport::export_variable_bindings(&table);
83    /// assert_eq!(bindings.get("x"), Some(&"Person".to_string()));
84    /// assert_eq!(bindings.get("y"), Some(&"Person".to_string()));
85    /// ```
86    pub fn export_variable_bindings(table: &SymbolTable) -> HashMap<String, String> {
87        table
88            .variables
89            .iter()
90            .map(|(var, domain)| (var.clone(), domain.clone()))
91            .collect()
92    }
93
94    /// Create a complete export bundle for compiler initialization.
95    ///
96    /// Returns all three maps (domains, signatures, bindings) in a single structure.
97    pub fn export_all(table: &SymbolTable) -> CompilerExportBundle {
98        CompilerExportBundle {
99            domains: Self::export_domains(table),
100            predicate_signatures: Self::export_predicate_signatures(table),
101            variable_bindings: Self::export_variable_bindings(table),
102        }
103    }
104}
105
106/// Complete export bundle for compiler integration.
107///
108/// This structure contains all information needed to initialize a compiler context
109/// from a symbol table.
110#[derive(Clone, Debug)]
111pub struct CompilerExportBundle {
112    /// Domain names mapped to cardinalities.
113    pub domains: HashMap<String, usize>,
114    /// Predicate names mapped to argument domain lists.
115    pub predicate_signatures: HashMap<String, Vec<String>>,
116    /// Variable names mapped to domain types.
117    pub variable_bindings: HashMap<String, String>,
118}
119
120impl CompilerExportBundle {
121    /// Create an empty export bundle.
122    pub fn new() -> Self {
123        Self {
124            domains: HashMap::new(),
125            predicate_signatures: HashMap::new(),
126            variable_bindings: HashMap::new(),
127        }
128    }
129
130    /// Check if the bundle is empty.
131    pub fn is_empty(&self) -> bool {
132        self.domains.is_empty()
133            && self.predicate_signatures.is_empty()
134            && self.variable_bindings.is_empty()
135    }
136}
137
138impl Default for CompilerExportBundle {
139    fn default() -> Self {
140        Self::new()
141    }
142}
143
144/// Import utilities for reverse synchronization.
145pub struct CompilerImport;
146
147impl CompilerImport {
148    /// Import domain information from a compiler context back into a symbol table.
149    ///
150    /// This is useful for synchronizing state after compilation.
151    ///
152    /// # Example
153    ///
154    /// ```rust
155    /// use tensorlogic_adapters::{SymbolTable, CompilerImport};
156    /// use std::collections::HashMap;
157    ///
158    /// let mut domains = HashMap::new();
159    /// domains.insert("Person".to_string(), 100);
160    /// domains.insert("Location".to_string(), 50);
161    ///
162    /// let mut table = SymbolTable::new();
163    /// CompilerImport::import_domains(&mut table, &domains).unwrap();
164    ///
165    /// assert!(table.get_domain("Person").is_some());
166    /// assert!(table.get_domain("Location").is_some());
167    /// ```
168    pub fn import_domains(table: &mut SymbolTable, domains: &HashMap<String, usize>) -> Result<()> {
169        for (name, cardinality) in domains {
170            table.add_domain(DomainInfo::new(name.clone(), *cardinality))?;
171        }
172        Ok(())
173    }
174
175    /// Import predicate signatures from a compiler context.
176    ///
177    /// # Example
178    ///
179    /// ```rust
180    /// use tensorlogic_adapters::{SymbolTable, DomainInfo, CompilerImport};
181    /// use std::collections::HashMap;
182    ///
183    /// let mut table = SymbolTable::new();
184    /// table.add_domain(DomainInfo::new("Person", 100)).unwrap();
185    ///
186    /// let mut signatures = HashMap::new();
187    /// signatures.insert("knows".to_string(), vec!["Person".to_string(), "Person".to_string()]);
188    ///
189    /// CompilerImport::import_predicates(&mut table, &signatures).unwrap();
190    ///
191    /// assert!(table.get_predicate("knows").is_some());
192    /// ```
193    pub fn import_predicates(
194        table: &mut SymbolTable,
195        signatures: &HashMap<String, Vec<String>>,
196    ) -> Result<()> {
197        for (name, arg_domains) in signatures {
198            table.add_predicate(PredicateInfo::new(name.clone(), arg_domains.clone()))?;
199        }
200        Ok(())
201    }
202
203    /// Import variable bindings from a compiler context.
204    ///
205    /// # Example
206    ///
207    /// ```rust
208    /// use tensorlogic_adapters::{SymbolTable, DomainInfo, CompilerImport};
209    /// use std::collections::HashMap;
210    ///
211    /// let mut table = SymbolTable::new();
212    /// table.add_domain(DomainInfo::new("Person", 100)).unwrap();
213    ///
214    /// let mut bindings = HashMap::new();
215    /// bindings.insert("x".to_string(), "Person".to_string());
216    /// bindings.insert("y".to_string(), "Person".to_string());
217    ///
218    /// CompilerImport::import_variables(&mut table, &bindings).unwrap();
219    ///
220    /// assert_eq!(table.get_variable_domain("x"), Some("Person"));
221    /// assert_eq!(table.get_variable_domain("y"), Some("Person"));
222    /// ```
223    pub fn import_variables(
224        table: &mut SymbolTable,
225        bindings: &HashMap<String, String>,
226    ) -> Result<()> {
227        for (var, domain) in bindings {
228            table.bind_variable(var, domain)?;
229        }
230        Ok(())
231    }
232
233    /// Import a complete bundle into a symbol table.
234    pub fn import_all(table: &mut SymbolTable, bundle: &CompilerExportBundle) -> Result<()> {
235        Self::import_domains(table, &bundle.domains)?;
236        Self::import_predicates(table, &bundle.predicate_signatures)?;
237        Self::import_variables(table, &bundle.variable_bindings)?;
238        Ok(())
239    }
240}
241
242/// Bidirectional synchronization utilities.
243pub struct SymbolTableSync;
244
245impl SymbolTableSync {
246    /// Synchronize a symbol table with compiler data, merging information.
247    ///
248    /// This performs a two-way sync:
249    /// 1. Exports current symbol table state
250    /// 2. Imports compiler context data
251    /// 3. Returns the merged export bundle
252    pub fn sync_with_compiler(
253        table: &mut SymbolTable,
254        compiler_bundle: &CompilerExportBundle,
255    ) -> Result<CompilerExportBundle> {
256        // First, import compiler data into the table
257        CompilerImport::import_all(table, compiler_bundle)?;
258
259        // Then, export the merged state
260        Ok(CompilerExport::export_all(table))
261    }
262
263    /// Validate that a compiler bundle is compatible with a symbol table.
264    ///
265    /// Checks that all referenced domains exist.
266    pub fn validate_bundle(
267        table: &SymbolTable,
268        bundle: &CompilerExportBundle,
269    ) -> Result<ValidationResult> {
270        let mut errors = Vec::new();
271        let mut warnings = Vec::new();
272
273        // Check predicate signatures reference existing domains
274        for (pred_name, arg_domains) in &bundle.predicate_signatures {
275            for domain in arg_domains {
276                if !table.domains.contains_key(domain) && !bundle.domains.contains_key(domain) {
277                    errors.push(format!(
278                        "Predicate '{}' references unknown domain '{}'",
279                        pred_name, domain
280                    ));
281                }
282            }
283        }
284
285        // Check variable bindings reference existing domains
286        for (var_name, domain) in &bundle.variable_bindings {
287            if !table.domains.contains_key(domain) && !bundle.domains.contains_key(domain) {
288                errors.push(format!(
289                    "Variable '{}' references unknown domain '{}'",
290                    var_name, domain
291                ));
292            }
293        }
294
295        // Warn about unused domains
296        for domain_name in bundle.domains.keys() {
297            let used_in_predicates = bundle
298                .predicate_signatures
299                .values()
300                .any(|args| args.contains(domain_name));
301            let used_in_variables = bundle.variable_bindings.values().any(|d| d == domain_name);
302
303            if !used_in_predicates && !used_in_variables {
304                warnings.push(format!(
305                    "Domain '{}' is defined but never used",
306                    domain_name
307                ));
308            }
309        }
310
311        Ok(ValidationResult { errors, warnings })
312    }
313}
314
315/// Result of bundle validation.
316#[derive(Clone, Debug)]
317pub struct ValidationResult {
318    /// Validation errors (must be empty for valid bundles).
319    pub errors: Vec<String>,
320    /// Validation warnings (non-critical issues).
321    pub warnings: Vec<String>,
322}
323
324impl ValidationResult {
325    /// Check if validation passed (no errors).
326    pub fn is_valid(&self) -> bool {
327        self.errors.is_empty()
328    }
329}
330
331#[cfg(test)]
332mod tests {
333    use super::*;
334
335    #[test]
336    fn test_export_domains() {
337        let mut table = SymbolTable::new();
338        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
339        table.add_domain(DomainInfo::new("Location", 50)).unwrap();
340
341        let domains = CompilerExport::export_domains(&table);
342        assert_eq!(domains.get("Person"), Some(&100));
343        assert_eq!(domains.get("Location"), Some(&50));
344    }
345
346    #[test]
347    fn test_export_predicate_signatures() {
348        let mut table = SymbolTable::new();
349        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
350        table
351            .add_predicate(PredicateInfo::new(
352                "knows",
353                vec!["Person".to_string(), "Person".to_string()],
354            ))
355            .unwrap();
356
357        let signatures = CompilerExport::export_predicate_signatures(&table);
358        assert_eq!(
359            signatures.get("knows"),
360            Some(&vec!["Person".to_string(), "Person".to_string()])
361        );
362    }
363
364    #[test]
365    fn test_export_all() {
366        let mut table = SymbolTable::new();
367        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
368        table
369            .add_predicate(PredicateInfo::new("knows", vec!["Person".to_string()]))
370            .unwrap();
371        table.bind_variable("x", "Person").unwrap();
372
373        let bundle = CompilerExport::export_all(&table);
374        assert_eq!(bundle.domains.len(), 1);
375        assert_eq!(bundle.predicate_signatures.len(), 1);
376        assert_eq!(bundle.variable_bindings.len(), 1);
377    }
378
379    #[test]
380    fn test_import_domains() {
381        let mut domains = HashMap::new();
382        domains.insert("Person".to_string(), 100);
383
384        let mut table = SymbolTable::new();
385        CompilerImport::import_domains(&mut table, &domains).unwrap();
386
387        assert!(table.get_domain("Person").is_some());
388    }
389
390    #[test]
391    fn test_import_predicates() {
392        let mut table = SymbolTable::new();
393        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
394
395        let mut signatures = HashMap::new();
396        signatures.insert("knows".to_string(), vec!["Person".to_string()]);
397
398        CompilerImport::import_predicates(&mut table, &signatures).unwrap();
399        assert!(table.get_predicate("knows").is_some());
400    }
401
402    #[test]
403    fn test_validation_invalid_domain_reference() {
404        let table = SymbolTable::new();
405        let mut bundle = CompilerExportBundle::new();
406        bundle
407            .predicate_signatures
408            .insert("knows".to_string(), vec!["UnknownDomain".to_string()]);
409
410        let result = SymbolTableSync::validate_bundle(&table, &bundle).unwrap();
411        assert!(!result.is_valid());
412        assert!(!result.errors.is_empty());
413    }
414
415    #[test]
416    fn test_validation_unused_domain_warning() {
417        let table = SymbolTable::new();
418        let mut bundle = CompilerExportBundle::new();
419        bundle.domains.insert("UnusedDomain".to_string(), 100);
420
421        let result = SymbolTableSync::validate_bundle(&table, &bundle).unwrap();
422        assert!(result.is_valid()); // Still valid, just has warnings
423        assert!(!result.warnings.is_empty());
424    }
425
426    #[test]
427    fn test_sync_with_compiler() {
428        let mut table = SymbolTable::new();
429        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
430
431        let mut bundle = CompilerExportBundle::new();
432        bundle.domains.insert("Location".to_string(), 50);
433
434        let result = SymbolTableSync::sync_with_compiler(&mut table, &bundle).unwrap();
435
436        // Table should now have both domains
437        assert!(table.get_domain("Person").is_some());
438        assert!(table.get_domain("Location").is_some());
439
440        // Result should include both
441        assert_eq!(result.domains.len(), 2);
442    }
443}