tensorlogic_cli/
conversion.rs

1//! Format conversion utilities
2
3use anyhow::{Context, Result};
4use std::fs;
5use tensorlogic_ir::TLExpr;
6
7use crate::cli::ConvertFormat;
8use crate::parser;
9
10/// Convert between different formats
11pub fn convert(
12    input: &str,
13    from: ConvertFormat,
14    to: ConvertFormat,
15    pretty: bool,
16) -> Result<String> {
17    // Parse input
18    let expr = read_input(input, from)?;
19
20    // Convert to output format
21    write_output(&expr, to, pretty)
22}
23
24fn read_input(input: &str, format: ConvertFormat) -> Result<TLExpr> {
25    match format {
26        ConvertFormat::Expr => parser::parse_expression(input),
27        ConvertFormat::Json => {
28            let content = if input == "-" {
29                use std::io::Read;
30                let mut buffer = String::new();
31                std::io::stdin().read_to_string(&mut buffer)?;
32                buffer
33            } else if std::path::Path::new(input).exists() {
34                fs::read_to_string(input).context("Failed to read input file")?
35            } else {
36                // Try to parse as JSON string directly
37                input.to_string()
38            };
39            serde_json::from_str(&content).context("Failed to parse JSON")
40        }
41        ConvertFormat::Yaml => {
42            let content = if std::path::Path::new(input).exists() {
43                fs::read_to_string(input).context("Failed to read input file")?
44            } else {
45                // Try to parse as YAML string directly
46                input.to_string()
47            };
48            serde_yaml::from_str(&content).context("Failed to parse YAML")
49        }
50    }
51}
52
53fn write_output(expr: &TLExpr, format: ConvertFormat, pretty: bool) -> Result<String> {
54    match format {
55        ConvertFormat::Expr => {
56            let s = format_expression(expr, pretty);
57            Ok(s)
58        }
59        ConvertFormat::Json => {
60            if pretty {
61                serde_json::to_string_pretty(expr).context("Failed to serialize to JSON")
62            } else {
63                serde_json::to_string(expr).context("Failed to serialize to JSON")
64            }
65        }
66        ConvertFormat::Yaml => serde_yaml::to_string(expr).context("Failed to serialize to YAML"),
67    }
68}
69
70/// Format an expression as a human-readable string
71pub fn format_expression(expr: &TLExpr, pretty: bool) -> String {
72    if pretty {
73        format_expression_pretty(expr, 0)
74    } else {
75        format_expression_compact(expr)
76    }
77}
78
79fn format_expression_compact(expr: &TLExpr) -> String {
80    match expr {
81        TLExpr::Pred { name, args } => {
82            if args.is_empty() {
83                name.clone()
84            } else {
85                let arg_strs: Vec<String> = args.iter().map(format_term).collect();
86                format!("{}({})", name, arg_strs.join(", "))
87            }
88        }
89        TLExpr::And(left, right) => {
90            format!(
91                "({} AND {})",
92                format_expression_compact(left),
93                format_expression_compact(right)
94            )
95        }
96        TLExpr::Or(left, right) => {
97            format!(
98                "({} OR {})",
99                format_expression_compact(left),
100                format_expression_compact(right)
101            )
102        }
103        TLExpr::Not(inner) => {
104            format!("NOT {}", format_expression_compact(inner))
105        }
106        TLExpr::Imply(left, right) => {
107            format!(
108                "({} -> {})",
109                format_expression_compact(left),
110                format_expression_compact(right)
111            )
112        }
113        TLExpr::Exists { var, domain, body } => {
114            format!(
115                "EXISTS {} IN {}. {}",
116                var,
117                domain,
118                format_expression_compact(body)
119            )
120        }
121        TLExpr::ForAll { var, domain, body } => {
122            format!(
123                "FORALL {} IN {}. {}",
124                var,
125                domain,
126                format_expression_compact(body)
127            )
128        }
129        TLExpr::Eq(left, right) => {
130            format!(
131                "{} = {}",
132                format_expression_compact(left),
133                format_expression_compact(right)
134            )
135        }
136        TLExpr::Lt(left, right) => {
137            format!(
138                "{} < {}",
139                format_expression_compact(left),
140                format_expression_compact(right)
141            )
142        }
143        TLExpr::Gt(left, right) => {
144            format!(
145                "{} > {}",
146                format_expression_compact(left),
147                format_expression_compact(right)
148            )
149        }
150        TLExpr::Lte(left, right) => {
151            format!(
152                "{} <= {}",
153                format_expression_compact(left),
154                format_expression_compact(right)
155            )
156        }
157        TLExpr::Gte(left, right) => {
158            format!(
159                "{} >= {}",
160                format_expression_compact(left),
161                format_expression_compact(right)
162            )
163        }
164        TLExpr::Add(left, right) => {
165            format!(
166                "({} + {})",
167                format_expression_compact(left),
168                format_expression_compact(right)
169            )
170        }
171        TLExpr::Sub(left, right) => {
172            format!(
173                "({} - {})",
174                format_expression_compact(left),
175                format_expression_compact(right)
176            )
177        }
178        TLExpr::Mul(left, right) => {
179            format!(
180                "({} * {})",
181                format_expression_compact(left),
182                format_expression_compact(right)
183            )
184        }
185        TLExpr::Div(left, right) => {
186            format!(
187                "({} / {})",
188                format_expression_compact(left),
189                format_expression_compact(right)
190            )
191        }
192        TLExpr::IfThenElse {
193            condition,
194            then_branch,
195            else_branch,
196        } => {
197            format!(
198                "IF {} THEN {} ELSE {}",
199                format_expression_compact(condition),
200                format_expression_compact(then_branch),
201                format_expression_compact(else_branch)
202            )
203        }
204        TLExpr::Constant(val) => format!("{}", val),
205        TLExpr::Score(inner) => format!("SCORE({})", format_expression_compact(inner)),
206        _ => format!("{:?}", expr), // Fallback for other variants
207    }
208}
209
210fn format_expression_pretty(expr: &TLExpr, indent: usize) -> String {
211    let indent_str = "  ".repeat(indent);
212
213    match expr {
214        TLExpr::Pred { name, args } => {
215            if args.is_empty() {
216                format!("{}{}", indent_str, name)
217            } else {
218                let arg_strs: Vec<String> = args.iter().map(format_term).collect();
219                format!("{}{}({})", indent_str, name, arg_strs.join(", "))
220            }
221        }
222        TLExpr::And(left, right) | TLExpr::Or(left, right) => {
223            let op = match expr {
224                TLExpr::And(_, _) => "AND",
225                TLExpr::Or(_, _) => "OR",
226                _ => unreachable!(),
227            };
228
229            // Check if subexpressions are simple
230            if is_simple(left) && is_simple(right) {
231                format!(
232                    "{}{} {} {}",
233                    indent_str,
234                    format_expression_compact(left),
235                    op,
236                    format_expression_compact(right)
237                )
238            } else {
239                format!(
240                    "{}{}(\n{},\n{}\n{})",
241                    indent_str,
242                    op,
243                    format_expression_pretty(left, indent + 1),
244                    format_expression_pretty(right, indent + 1),
245                    indent_str
246                )
247            }
248        }
249        TLExpr::Not(inner) => {
250            if is_simple(inner) {
251                format!("{}NOT {}", indent_str, format_expression_compact(inner))
252            } else {
253                format!(
254                    "{}NOT(\n{}\n{})",
255                    indent_str,
256                    format_expression_pretty(inner, indent + 1),
257                    indent_str
258                )
259            }
260        }
261        TLExpr::Imply(left, right) => {
262            if is_simple(left) && is_simple(right) {
263                format!(
264                    "{}{} -> {}",
265                    indent_str,
266                    format_expression_compact(left),
267                    format_expression_compact(right)
268                )
269            } else {
270                format!(
271                    "{}IMPLIES(\n{},\n{}\n{})",
272                    indent_str,
273                    format_expression_pretty(left, indent + 1),
274                    format_expression_pretty(right, indent + 1),
275                    indent_str
276                )
277            }
278        }
279        TLExpr::Exists { var, domain, body } | TLExpr::ForAll { var, domain, body } => {
280            let quantifier = match expr {
281                TLExpr::Exists { .. } => "EXISTS",
282                TLExpr::ForAll { .. } => "FORALL",
283                _ => unreachable!(),
284            };
285
286            let domain_str = format!(" IN {}", domain);
287
288            if is_simple(body) {
289                format!(
290                    "{}{} {}{}. {}",
291                    indent_str,
292                    quantifier,
293                    var,
294                    domain_str,
295                    format_expression_compact(body)
296                )
297            } else {
298                format!(
299                    "{}{} {}{}.\n{}",
300                    indent_str,
301                    quantifier,
302                    var,
303                    domain_str,
304                    format_expression_pretty(body, indent + 1)
305                )
306            }
307        }
308        _ => format!("{}{}", indent_str, format_expression_compact(expr)),
309    }
310}
311
312fn format_term(term: &tensorlogic_ir::Term) -> String {
313    match term {
314        tensorlogic_ir::Term::Var(name) => name.clone(),
315        tensorlogic_ir::Term::Const(name) => name.clone(),
316        tensorlogic_ir::Term::Typed { value, .. } => format_term(value),
317    }
318}
319
320fn is_simple(expr: &TLExpr) -> bool {
321    matches!(
322        expr,
323        TLExpr::Pred { .. }
324            | TLExpr::Eq(_, _)
325            | TLExpr::Lt(_, _)
326            | TLExpr::Gt(_, _)
327            | TLExpr::Lte(_, _)
328            | TLExpr::Gte(_, _)
329            | TLExpr::Constant(_)
330    )
331}
332
333#[cfg(test)]
334mod tests {
335    use super::*;
336    use tensorlogic_ir::Term;
337
338    #[test]
339    fn test_format_simple_predicate() {
340        let expr = TLExpr::Pred {
341            name: "knows".to_string(),
342            args: vec![Term::Var("x".to_string()), Term::Var("y".to_string())],
343        };
344
345        let formatted = format_expression(&expr, false);
346        assert_eq!(formatted, "knows(x, y)");
347    }
348
349    #[test]
350    fn test_format_and() {
351        let expr = TLExpr::And(
352            Box::new(TLExpr::Pred {
353                name: "p".to_string(),
354                args: vec![],
355            }),
356            Box::new(TLExpr::Pred {
357                name: "q".to_string(),
358                args: vec![],
359            }),
360        );
361
362        let formatted = format_expression(&expr, false);
363        assert_eq!(formatted, "(p AND q)");
364    }
365
366    #[test]
367    fn test_format_exists() {
368        let expr = TLExpr::Exists {
369            var: "x".to_string(),
370            domain: "Person".to_string(),
371            body: Box::new(TLExpr::Pred {
372                name: "knows".to_string(),
373                args: vec![Term::Var("x".to_string()), Term::Const("alice".to_string())],
374            }),
375        };
376
377        let formatted = format_expression(&expr, false);
378        assert_eq!(formatted, "EXISTS x IN Person. knows(x, alice)");
379    }
380
381    #[test]
382    fn test_convert_json_to_yaml() {
383        let json_input = r#"{"Pred":{"name":"test","args":[{"Var":"x"}]}}"#;
384        let result = convert(json_input, ConvertFormat::Json, ConvertFormat::Yaml, false);
385        assert!(result.is_ok());
386    }
387
388    #[test]
389    fn test_convert_expr_to_json() {
390        let expr_input = "knows(x, y)";
391        let result = convert(expr_input, ConvertFormat::Expr, ConvertFormat::Json, false);
392        assert!(result.is_ok());
393    }
394}