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}