1use anyhow::{Context, Result};
4use std::fs;
5use tensorlogic_ir::TLExpr;
6
7use crate::cli::ConvertFormat;
8use crate::parser;
9
10pub fn convert(
12 input: &str,
13 from: ConvertFormat,
14 to: ConvertFormat,
15 pretty: bool,
16) -> Result<String> {
17 let expr = read_input(input, from)?;
19
20 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 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 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
70pub 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), }
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 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}