tensorlogic_compiler/import/
prolog.rs1use anyhow::{anyhow, Result};
30use tensorlogic_ir::{TLExpr, Term};
31
32pub fn parse_prolog(input: &str) -> Result<TLExpr> {
42 let input = input.trim().trim_end_matches('.');
43
44 if let Some(pos) = input.find(":-") {
46 let head = input[..pos].trim();
47 let body = input[pos + 2..].trim();
48
49 let head_expr = parse_prolog_term(head)?;
50 let body_expr = parse_prolog_term(body)?;
51
52 Ok(TLExpr::imply(body_expr, head_expr))
53 } else {
54 parse_prolog_term(input)
55 }
56}
57
58fn parse_prolog_term(input: &str) -> Result<TLExpr> {
59 let input = input.trim();
60
61 if let Some(pos) = find_operator(input, ';') {
63 let left = parse_prolog_term(input[..pos].trim())?;
64 let right = parse_prolog_term(input[pos + 1..].trim())?;
65 return Ok(TLExpr::or(left, right));
66 }
67
68 if let Some(pos) = find_operator(input, ',') {
70 let left = parse_prolog_term(input[..pos].trim())?;
71 let right = parse_prolog_term(input[pos + 1..].trim())?;
72 return Ok(TLExpr::and(left, right));
73 }
74
75 if let Some(stripped) = input.strip_prefix("\\+") {
77 let inner = stripped.trim();
78 return Ok(TLExpr::negate(parse_prolog_term(inner)?));
79 }
80
81 if input.starts_with("not(") && input.ends_with(')') {
83 let inner = &input[4..input.len() - 1];
84 return Ok(TLExpr::negate(parse_prolog_term(inner)?));
85 }
86
87 if input.starts_with('(') && input.ends_with(')') {
89 return parse_prolog_term(&input[1..input.len() - 1]);
90 }
91
92 if let Some(open_paren) = input.find('(') {
94 if !input.ends_with(')') {
95 return Err(anyhow!("Unmatched parentheses in: {}", input));
96 }
97
98 let pred_name = input[..open_paren].trim();
99 let args_str = &input[open_paren + 1..input.len() - 1];
100
101 let args = parse_arguments(args_str)?;
102
103 Ok(TLExpr::pred(pred_name, args))
104 } else {
105 Ok(TLExpr::pred(input, vec![]))
107 }
108}
109
110fn parse_arguments(args_str: &str) -> Result<Vec<Term>> {
111 if args_str.trim().is_empty() {
112 return Ok(vec![]);
113 }
114
115 let parts = split_arguments(args_str);
116 parts
117 .into_iter()
118 .map(|arg| parse_term(arg.trim()))
119 .collect()
120}
121
122fn parse_term(term_str: &str) -> Result<Term> {
123 if term_str.chars().next().is_some_and(|c| c.is_uppercase()) {
125 Ok(Term::var(term_str))
126 } else {
127 Ok(Term::constant(term_str))
129 }
130}
131
132fn split_arguments(args: &str) -> Vec<String> {
133 let mut result = Vec::new();
134 let mut current = String::new();
135 let mut depth = 0;
136
137 for ch in args.chars() {
138 match ch {
139 '(' => {
140 depth += 1;
141 current.push(ch);
142 }
143 ')' => {
144 depth -= 1;
145 current.push(ch);
146 }
147 ',' if depth == 0 => {
148 result.push(current.clone());
149 current.clear();
150 }
151 _ => current.push(ch),
152 }
153 }
154
155 if !current.is_empty() {
156 result.push(current);
157 }
158
159 result
160}
161
162fn find_operator(input: &str, op: char) -> Option<usize> {
163 let mut depth = 0;
164
165 for (i, ch) in input.chars().enumerate() {
166 match ch {
167 '(' => depth += 1,
168 ')' => depth -= 1,
169 c if c == op && depth == 0 => return Some(i),
170 _ => {}
171 }
172 }
173
174 None
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn test_simple_fact() {
183 let expr = parse_prolog("mortal(socrates).").unwrap();
184 assert!(matches!(expr, TLExpr::Pred { .. }));
185 }
186
187 #[test]
188 fn test_rule() {
189 let expr = parse_prolog("mortal(X) :- human(X).").unwrap();
190 assert!(matches!(expr, TLExpr::Imply { .. }));
191 }
192
193 #[test]
194 fn test_conjunction() {
195 let expr = parse_prolog("human(X), mortal(X).").unwrap();
196 assert!(matches!(expr, TLExpr::And(_, _)));
197 }
198
199 #[test]
200 fn test_disjunction() {
201 let expr = parse_prolog("human(X) ; god(X).").unwrap();
202 assert!(matches!(expr, TLExpr::Or(_, _)));
203 }
204
205 #[test]
206 fn test_negation_prefix() {
207 let expr = parse_prolog("\\+ mortal(X).").unwrap();
208 assert!(matches!(expr, TLExpr::Not(_)));
209 }
210
211 #[test]
212 fn test_negation_function() {
213 let expr = parse_prolog("not(mortal(X)).").unwrap();
214 assert!(matches!(expr, TLExpr::Not(_)));
215 }
216
217 #[test]
218 fn test_complex_rule() {
219 let expr = parse_prolog("mortal(X) :- human(X), \\+ god(X).").unwrap();
220 assert!(matches!(expr, TLExpr::Imply { .. }));
221 }
222
223 #[test]
224 fn test_predicate_no_args() {
225 let expr = parse_prolog("alive.").unwrap();
226 assert!(matches!(expr, TLExpr::Pred { .. }));
227 }
228
229 #[test]
230 fn test_multiple_args() {
231 let expr = parse_prolog("knows(alice, bob, charlie).").unwrap();
232 if let TLExpr::Pred { name, args } = expr {
233 assert_eq!(name, "knows");
234 assert_eq!(args.len(), 3);
235 } else {
236 panic!("Expected Pred");
237 }
238 }
239
240 #[test]
241 fn test_variables_and_constants() {
242 let expr = parse_prolog("age(Person, 25).").unwrap();
243 if let TLExpr::Pred { name: _, args } = expr {
244 assert!(matches!(args[0], Term::Var(_)));
245 assert!(matches!(args[1], Term::Const(_)));
246 } else {
247 panic!("Expected Pred");
248 }
249 }
250}