1#![allow(missing_docs)] use pest::iterators::Pair;
8use pest::Parser;
9use pest_derive::Parser;
10use xlog_core::{symbol, Result, ScalarType, XlogError};
11
12use crate::ast::{
13 AggExpr, AggOp, AnnotatedDisjunction, ArithExpr, Atom, BodyLiteral, CompOp, Comparison,
14 CondExpr, Constraint, DomainDecl, EpistemicLiteral, EpistemicMode, EpistemicOp, Evidence,
15 FuncBody, FuncDef, FuncParam, IsExpr, LearnableRule, MagicSetsMode, NeuralLabel,
16 NeuralPredDecl, PredColumn, PredDecl, ProbCache, ProbEngine, ProbFact, ProbMethod, ProbQuery,
17 Program, Query, Rule as AstRule, Term, TypeRef, Univ, UseDecl,
18};
19
20#[derive(Parser)]
22#[grammar = "grammar.pest"]
23pub struct XlogParser;
24
25pub type ParseResult<'a> = pest::iterators::Pairs<'a, Rule>;
27
28pub fn parse_program(input: &str) -> Result<Program> {
30 let pairs =
31 XlogParser::parse(Rule::program, input).map_err(|e| XlogError::Parse(e.to_string()))?;
32
33 build_program(pairs)
34}
35
36pub fn parse_statement(input: &str) -> Result<ParseResult<'_>> {
38 XlogParser::parse(Rule::statement, input).map_err(|e| XlogError::Parse(e.to_string()))
39}
40
41pub fn parse_atom(input: &str) -> Result<ParseResult<'_>> {
43 XlogParser::parse(Rule::atom, input).map_err(|e| XlogError::Parse(e.to_string()))
44}
45
46fn build_program(pairs: ParseResult<'_>) -> Result<Program> {
52 let mut program = Program::new();
53
54 for pair in pairs {
55 if pair.as_rule() == Rule::program {
56 for inner in pair.into_inner() {
57 match inner.as_rule() {
58 Rule::statement => {
59 build_statement(inner, &mut program)?;
60 }
61 Rule::EOI => {}
62 _ => {}
63 }
64 }
65 }
66 }
67
68 Ok(program)
69}
70
71fn build_statement(pair: Pair<'_, Rule>, program: &mut Program) -> Result<()> {
73 for inner in pair.into_inner() {
74 match inner.as_rule() {
75 Rule::use_stmt => {
76 program.imports.push(build_use_stmt(inner));
77 }
78 Rule::domain_decl => {
79 program.domains.push(build_domain_decl(inner)?);
80 }
81 Rule::pred_decl => {
82 program.predicates.push(build_pred_decl(inner)?);
83 }
84 Rule::pragma => {
85 apply_pragma(inner, program)?;
86 }
87 Rule::rule_def => {
88 program.rules.push(build_rule(inner)?);
89 }
90 Rule::fact => {
91 program.rules.push(build_fact(inner)?);
92 }
93 Rule::prob_fact => {
94 program.prob_facts.push(build_prob_fact(inner)?);
95 }
96 Rule::annotated_disjunction => {
97 program
98 .annotated_disjunctions
99 .push(build_annotated_disjunction(inner)?);
100 }
101 Rule::evidence_stmt => {
102 program.evidence.push(build_evidence(inner)?);
103 }
104 Rule::prob_query => {
105 program.prob_queries.push(build_prob_query(inner)?);
106 }
107 Rule::constraint => {
108 program.constraints.push(build_constraint(inner)?);
109 }
110 Rule::query => {
111 program.queries.push(build_query(inner)?);
112 }
113 Rule::func_def => {
114 program.functions.push(parse_func_def(inner)?);
115 }
116 Rule::neural_pred_decl => {
117 program
118 .neural_predicates
119 .push(build_neural_pred_decl(inner)?);
120 }
121 Rule::learnable_rule => {
122 program.learnable_rules.push(build_learnable_rule(inner)?);
123 }
124 _ => {}
125 }
126 }
127 Ok(())
128}
129
130fn apply_pragma(pair: Pair<'_, Rule>, program: &mut Program) -> Result<()> {
131 let pragma = pair
132 .into_inner()
133 .next()
134 .ok_or_else(|| XlogError::Parse("Empty pragma".to_string()))?;
135
136 match pragma.as_rule() {
137 Rule::pragma_prob_engine => {
138 let value = pragma
139 .into_inner()
140 .next()
141 .ok_or_else(|| XlogError::Parse("Missing prob_engine value".to_string()))?;
142 let engine = match value.as_str() {
143 "exact_ddnnf" => ProbEngine::ExactDdnnf,
144 "mc" => ProbEngine::Mc,
145 other => {
146 return Err(XlogError::Parse(format!(
147 "Unknown prob_engine value: {}",
148 other
149 )))
150 }
151 };
152 program.directives.prob_engine = Some(engine);
153 }
154 Rule::pragma_prob_cache => {
155 let value = pragma
156 .into_inner()
157 .next()
158 .ok_or_else(|| XlogError::Parse("Missing prob_cache value".to_string()))?;
159 let cache = match value.as_str() {
160 "on" => ProbCache::On,
161 "off" => ProbCache::Off,
162 other => {
163 return Err(XlogError::Parse(format!(
164 "Unknown prob_cache value: {}",
165 other
166 )))
167 }
168 };
169 program.directives.prob_cache = Some(cache);
170 }
171 Rule::pragma_epistemic_mode => {
172 let value = pragma
173 .into_inner()
174 .next()
175 .ok_or_else(|| XlogError::Parse("Missing epistemic_mode value".to_string()))?;
176 let mode = match value.as_str() {
177 "g91" => EpistemicMode::G91,
178 "faeel" => EpistemicMode::Faeel,
179 other => {
180 return Err(XlogError::Parse(format!(
181 "Unknown epistemic_mode value: {}",
182 other
183 )))
184 }
185 };
186 program.directives.epistemic_mode = Some(mode);
187 }
188 Rule::pragma_prob_samples => {
189 let value = pragma
190 .into_inner()
191 .next()
192 .ok_or_else(|| XlogError::Parse("Missing prob_samples value".to_string()))?;
193 let samples: usize = value.as_str().parse().map_err(|_| {
194 XlogError::Parse(format!("Invalid prob_samples value: {}", value.as_str()))
195 })?;
196 if samples == 0 {
197 return Err(XlogError::Parse(
198 "Invalid prob_samples value: expected > 0".to_string(),
199 ));
200 }
201 program.directives.prob_samples = Some(samples);
202 }
203 Rule::pragma_prob_seed => {
204 let value = pragma
205 .into_inner()
206 .next()
207 .ok_or_else(|| XlogError::Parse("Missing prob_seed value".to_string()))?;
208 let seed: u64 = value.as_str().parse().map_err(|_| {
209 XlogError::Parse(format!("Invalid prob_seed value: {}", value.as_str()))
210 })?;
211 program.directives.prob_seed = Some(seed);
212 }
213 Rule::pragma_prob_confidence => {
214 let value = pragma
215 .into_inner()
216 .next()
217 .ok_or_else(|| XlogError::Parse("Missing prob_confidence value".to_string()))?;
218 let confidence: f64 = value.as_str().parse().map_err(|_| {
219 XlogError::Parse(format!("Invalid prob_confidence value: {}", value.as_str()))
220 })?;
221 if !(0.0 < confidence && confidence < 1.0) || confidence.is_nan() {
222 return Err(XlogError::Parse(format!(
223 "Invalid prob_confidence value: {}; expected 0 < confidence < 1",
224 value.as_str()
225 )));
226 }
227 program.directives.prob_confidence = Some(confidence);
228 }
229 Rule::pragma_prob_method => {
230 let value = pragma
231 .into_inner()
232 .next()
233 .ok_or_else(|| XlogError::Parse("Missing prob_method value".to_string()))?;
234 let method = match value.as_str() {
235 "rejection" => ProbMethod::Rejection,
236 "evidence_clamping" => ProbMethod::EvidenceClamping,
237 other => {
238 return Err(XlogError::Parse(format!(
239 "Unknown prob_method value: {}",
240 other
241 )))
242 }
243 };
244 program.directives.prob_method = Some(method);
245 }
246 Rule::pragma_prob_max_nonmonotone_iterations => {
247 let value = pragma.into_inner().next().ok_or_else(|| {
248 XlogError::Parse("Missing prob_max_nonmonotone_iterations value".to_string())
249 })?;
250 let iterations: usize = value.as_str().parse().map_err(|_| {
251 XlogError::Parse(format!(
252 "Invalid prob_max_nonmonotone_iterations value: {}",
253 value.as_str()
254 ))
255 })?;
256 if iterations == 0 {
257 return Err(XlogError::Parse(
258 "Invalid prob_max_nonmonotone_iterations value: expected > 0".to_string(),
259 ));
260 }
261 program.directives.prob_max_nonmonotone_iterations = Some(iterations);
262 }
263 Rule::pragma_max_recursion => {
264 let value = pragma
265 .into_inner()
266 .next()
267 .ok_or_else(|| XlogError::Parse("Missing max_recursion_depth value".to_string()))?;
268 let depth: u32 = value.as_str().parse().map_err(|_| {
269 XlogError::Parse(format!(
270 "Invalid max_recursion_depth value: {}",
271 value.as_str()
272 ))
273 })?;
274 program.directives.max_recursion_depth = Some(depth);
275 }
276 Rule::pragma_magic_sets => {
277 let value = pragma
278 .into_inner()
279 .next()
280 .ok_or_else(|| XlogError::Parse("Missing magic_sets value".to_string()))?;
281 let mode = match value.as_str() {
282 "auto" => MagicSetsMode::Auto,
283 "on" => MagicSetsMode::On,
284 "off" => MagicSetsMode::Off,
285 other => {
286 return Err(XlogError::Parse(format!(
287 "Unknown magic_sets value: {}",
288 other
289 )))
290 }
291 };
292 program.directives.magic_sets = Some(mode);
293 }
294 _ => {}
295 }
296
297 Ok(())
298}
299
300fn build_domain_decl(pair: Pair<'_, Rule>) -> Result<DomainDecl> {
302 let mut inner = pair.into_inner();
303 let name = inner
304 .next()
305 .ok_or_else(|| XlogError::Parse("Missing domain name".to_string()))?
306 .as_str()
307 .to_string();
308 let type_pair = inner
309 .next()
310 .ok_or_else(|| XlogError::Parse("Missing domain type".to_string()))?;
311 let typ = build_scalar_type_spec(type_pair, "domain alias")?;
312
313 Ok(DomainDecl { name, typ })
314}
315
316fn parse_module_path(pair: Pair<Rule>) -> Vec<String> {
318 pair.as_str().split('/').map(|s| s.to_string()).collect()
319}
320
321fn build_use_stmt(pair: Pair<Rule>) -> UseDecl {
323 let mut inner = pair.into_inner();
324
325 let path_pair = inner.next().unwrap();
327 let module_path = parse_module_path(path_pair);
328
329 let imports = inner.next().map(|import_list| {
331 import_list
332 .into_inner()
333 .map(|p| p.as_str().to_string())
334 .collect()
335 });
336
337 UseDecl {
338 module_path,
339 imports,
340 }
341}
342
343fn build_pred_decl(pair: Pair<'_, Rule>) -> Result<PredDecl> {
345 let mut inner = pair.into_inner();
346 let mut is_private = false;
347
348 let first = inner
350 .next()
351 .ok_or_else(|| XlogError::Parse("Missing predicate name".to_string()))?;
352
353 let name_pair = if first.as_rule() == Rule::private_mod {
354 is_private = true;
355 inner
356 .next()
357 .ok_or_else(|| XlogError::Parse("Missing predicate name after private".to_string()))?
358 } else {
359 first
360 };
361
362 let name = name_pair.as_str().to_string();
363
364 let mut columns = Vec::new();
365 for type_pair in inner {
366 if type_pair.as_rule() == Rule::type_list {
367 for col in type_pair.into_inner() {
368 columns.push(build_pred_column(col)?);
369 }
370 }
371 }
372 let types = columns.iter().map(|c| c.typ.clone()).collect();
373
374 Ok(PredDecl {
375 name,
376 types,
377 columns,
378 is_private,
379 })
380}
381
382fn build_pred_column(pair: Pair<'_, Rule>) -> Result<PredColumn> {
383 let mut inner = pair.into_inner();
384 let first = inner
385 .next()
386 .ok_or_else(|| XlogError::Parse("Empty predicate column".to_string()))?;
387
388 if first.as_rule() == Rule::ident {
389 let typ_pair = inner
390 .next()
391 .ok_or_else(|| XlogError::Parse("Missing named column type".to_string()))?;
392 Ok(PredColumn {
393 name: Some(first.as_str().to_string()),
394 typ: build_type_ref(typ_pair)?,
395 })
396 } else {
397 Ok(PredColumn {
398 name: None,
399 typ: build_type_ref(first)?,
400 })
401 }
402}
403
404fn build_type_ref(pair: Pair<'_, Rule>) -> Result<TypeRef> {
406 if pair.as_rule() == Rule::type_spec {
407 let raw = pair.as_str().to_string();
408 let mut inner = pair.into_inner();
409 if let Some(child) = inner.next() {
410 return build_type_ref(child);
411 }
412 return match raw.as_str() {
413 "term" => Ok(TypeRef::Term),
414 "compound" => Ok(TypeRef::Compound),
415 "predref" => Ok(TypeRef::PredRef),
416 other => Err(XlogError::Parse(format!("Unknown type: {}", other))),
417 };
418 }
419
420 match pair.as_rule() {
421 Rule::list_type => {
422 let item = pair
423 .into_inner()
424 .next()
425 .ok_or_else(|| XlogError::Parse("Missing list element type".to_string()))?;
426 Ok(TypeRef::List(Box::new(build_type_ref(item)?)))
427 }
428 Rule::scalar_type => build_scalar_type_name(pair.as_str()).map(TypeRef::Scalar),
429 Rule::ident => Ok(TypeRef::Domain(pair.as_str().to_string())),
430 _ => match pair.as_str() {
431 "term" => Ok(TypeRef::Term),
432 "compound" => Ok(TypeRef::Compound),
433 "predref" => Ok(TypeRef::PredRef),
434 other => Err(XlogError::Parse(format!("Unknown type: {}", other))),
435 },
436 }
437}
438
439fn build_scalar_type_spec(pair: Pair<'_, Rule>, context: &str) -> Result<ScalarType> {
440 match build_type_ref(pair)? {
441 TypeRef::Scalar(ty) => Ok(ty),
442 other => Err(XlogError::Parse(format!(
443 "v0.8.5 {} must use a scalar type, got {:?}",
444 context, other
445 ))),
446 }
447}
448
449fn build_scalar_type_name(type_str: &str) -> Result<ScalarType> {
450 match type_str {
451 "u32" => Ok(ScalarType::U32),
452 "u64" => Ok(ScalarType::U64),
453 "i32" => Ok(ScalarType::I32),
454 "i64" => Ok(ScalarType::I64),
455 "f32" => Ok(ScalarType::F32),
456 "f64" => Ok(ScalarType::F64),
457 "bool" => Ok(ScalarType::Bool),
458 "symbol" => Ok(ScalarType::Symbol),
459 _ => Err(XlogError::Parse(format!(
460 "Unknown scalar type: {}",
461 type_str
462 ))),
463 }
464}
465
466fn parse_func_param(pair: Pair<'_, Rule>) -> Result<FuncParam> {
468 let mut inner = pair.into_inner();
469 let name = inner
470 .next()
471 .ok_or_else(|| XlogError::Parse("Missing parameter name".to_string()))?
472 .as_str()
473 .to_string();
474 let typ = inner
475 .next()
476 .map(|ta| {
477 build_scalar_type_spec(
479 ta.into_inner()
480 .next()
481 .expect("type_annotation must contain type_spec"),
482 "function parameter type annotation",
483 )
484 })
485 .transpose()?;
486 Ok(FuncParam { name, typ })
487}
488
489fn parse_cmp_op(pair: Pair<'_, Rule>) -> CompOp {
491 match pair.as_str() {
492 "==" | "=" => CompOp::Eq,
493 "!=" => CompOp::Ne,
494 "<" => CompOp::Lt,
495 "<=" => CompOp::Le,
496 ">" => CompOp::Gt,
497 ">=" => CompOp::Ge,
498 _ => unreachable!("unexpected comparison operator: {}", pair.as_str()),
499 }
500}
501
502fn parse_cond_expr(pair: Pair<'_, Rule>) -> Result<CondExpr> {
504 let mut inner = pair.into_inner();
505
506 let cond_test = inner
508 .next()
509 .ok_or_else(|| XlogError::Parse("Missing condition test".to_string()))?;
510 let mut test_inner = cond_test.into_inner();
511 let cond_left = build_arith_expr(
512 test_inner
513 .next()
514 .ok_or_else(|| XlogError::Parse("Missing left side of condition".to_string()))?,
515 )?;
516 let cond_op = parse_cmp_op(
517 test_inner
518 .next()
519 .ok_or_else(|| XlogError::Parse("Missing condition operator".to_string()))?,
520 );
521 let cond_right = build_arith_expr(
522 test_inner
523 .next()
524 .ok_or_else(|| XlogError::Parse("Missing right side of condition".to_string()))?,
525 )?;
526
527 let then_branch =
529 Box::new(parse_func_body(inner.next().ok_or_else(|| {
530 XlogError::Parse("Missing then branch".to_string())
531 })?)?);
532
533 let else_branch =
535 Box::new(parse_func_body(inner.next().ok_or_else(|| {
536 XlogError::Parse("Missing else branch".to_string())
537 })?)?);
538
539 Ok(CondExpr {
540 cond_left,
541 cond_op,
542 cond_right,
543 then_branch,
544 else_branch,
545 })
546}
547
548fn parse_func_body(pair: Pair<'_, Rule>) -> Result<FuncBody> {
550 let inner = pair
551 .into_inner()
552 .next()
553 .ok_or_else(|| XlogError::Parse("Empty function body".to_string()))?;
554 match inner.as_rule() {
555 Rule::func_body_pred => {
556 let mut parts = inner.into_inner();
557 let result = parts
558 .next()
559 .ok_or_else(|| XlogError::Parse("Missing result variable".to_string()))?
560 .as_str()
561 .to_string();
562 let body = build_body(
563 parts
564 .next()
565 .ok_or_else(|| XlogError::Parse("Missing predicate body".to_string()))?,
566 )?;
567 Ok(FuncBody::Predicate { result, body })
568 }
569 Rule::func_body_arith => {
570 let arith_inner = inner
571 .into_inner()
572 .next()
573 .ok_or_else(|| XlogError::Parse("Empty arithmetic body".to_string()))?;
574 match arith_inner.as_rule() {
575 Rule::cond_expr => Ok(FuncBody::Conditional(parse_cond_expr(arith_inner)?)),
576 _ => Ok(FuncBody::Arithmetic(build_arith_expr(arith_inner)?)),
577 }
578 }
579 _ => Err(XlogError::Parse(format!(
580 "Unexpected rule in func_body: {:?}",
581 inner.as_rule()
582 ))),
583 }
584}
585
586fn parse_func_def(pair: Pair<'_, Rule>) -> Result<FuncDef> {
588 let mut inner = pair.into_inner();
589 let mut is_private = false;
590
591 let first = inner
593 .next()
594 .ok_or_else(|| XlogError::Parse("Empty function definition".to_string()))?;
595 let name_pair = if first.as_rule() == Rule::private_mod {
596 is_private = true;
597 inner
598 .next()
599 .ok_or_else(|| XlogError::Parse("Missing function name after private".to_string()))?
600 } else {
601 first
602 };
603
604 let name = name_pair.as_str().to_string();
605
606 let mut params = Vec::new();
607 let mut return_type = None;
608 let mut body = None;
609
610 for p in inner {
611 match p.as_rule() {
612 Rule::func_params => {
613 params = p
614 .into_inner()
615 .map(parse_func_param)
616 .collect::<Result<Vec<_>>>()?;
617 }
618 Rule::return_type => {
619 return_type = Some(build_scalar_type_spec(
620 p.into_inner()
621 .next()
622 .ok_or_else(|| XlogError::Parse("Missing return type".to_string()))?,
623 "function return type annotation",
624 )?);
625 }
626 Rule::func_body => {
627 body = Some(parse_func_body(p)?);
628 }
629 _ => {}
630 }
631 }
632
633 Ok(FuncDef {
634 name,
635 params,
636 return_type,
637 body: body.ok_or_else(|| XlogError::Parse("Function must have a body".to_string()))?,
638 is_private,
639 })
640}
641
642fn build_rule(pair: Pair<'_, Rule>) -> Result<AstRule> {
644 let mut inner = pair.into_inner();
645 let head_pair = inner
646 .next()
647 .ok_or_else(|| XlogError::Parse("Missing rule head".to_string()))?;
648 let head = build_head(head_pair)?;
649
650 let body_pair = inner
651 .next()
652 .ok_or_else(|| XlogError::Parse("Missing rule body".to_string()))?;
653 let body = build_body(body_pair)?;
654
655 Ok(AstRule { head, body })
656}
657
658fn build_fact(pair: Pair<'_, Rule>) -> Result<AstRule> {
660 let mut inner = pair.into_inner();
661 let atom_pair = inner
662 .next()
663 .ok_or_else(|| XlogError::Parse("Missing fact atom".to_string()))?;
664 let head = build_atom(atom_pair)?;
665
666 Ok(AstRule { head, body: vec![] })
667}
668
669fn build_constraint(pair: Pair<'_, Rule>) -> Result<Constraint> {
671 let mut inner = pair.into_inner();
672 let body_pair = inner
673 .next()
674 .ok_or_else(|| XlogError::Parse("Missing constraint body".to_string()))?;
675 let body = build_body(body_pair)?;
676
677 Ok(Constraint { body })
678}
679
680fn build_query(pair: Pair<'_, Rule>) -> Result<Query> {
682 let mut inner = pair.into_inner();
683 let atom_pair = inner
684 .next()
685 .ok_or_else(|| XlogError::Parse("Missing query atom".to_string()))?;
686 let atom = build_atom(atom_pair)?;
687
688 Ok(Query { atom })
689}
690
691fn build_prob_fact(pair: Pair<'_, Rule>) -> Result<ProbFact> {
692 let choice = pair
693 .into_inner()
694 .next()
695 .ok_or_else(|| XlogError::Parse("Missing probabilistic fact".to_string()))?;
696 build_prob_choice(choice)
697}
698
699fn build_annotated_disjunction(pair: Pair<'_, Rule>) -> Result<AnnotatedDisjunction> {
700 let mut choices = Vec::new();
701 for inner in pair.into_inner() {
702 if inner.as_rule() == Rule::prob_choice {
703 choices.push(build_prob_choice(inner)?);
704 }
705 }
706 if choices.is_empty() {
707 return Err(XlogError::Parse(
708 "Annotated disjunction must have at least one choice".to_string(),
709 ));
710 }
711 Ok(AnnotatedDisjunction { choices })
712}
713
714fn build_prob_choice(pair: Pair<'_, Rule>) -> Result<ProbFact> {
715 let mut inner = pair.into_inner();
716 let prob_pair = inner
717 .next()
718 .ok_or_else(|| XlogError::Parse("Missing probability".to_string()))?;
719 let prob: f64 = prob_pair
720 .as_str()
721 .parse()
722 .map_err(|_| XlogError::Parse(format!("Invalid probability: {}", prob_pair.as_str())))?;
723
724 let atom_pair = inner
725 .next()
726 .ok_or_else(|| XlogError::Parse("Missing probabilistic atom".to_string()))?;
727 let atom = build_atom(atom_pair)?;
728
729 Ok(ProbFact { prob, atom })
730}
731
732fn build_evidence(pair: Pair<'_, Rule>) -> Result<Evidence> {
733 let mut inner = pair.into_inner();
734 let atom_pair = inner
735 .next()
736 .ok_or_else(|| XlogError::Parse("Missing evidence atom".to_string()))?;
737 let atom = build_atom(atom_pair)?;
738
739 let value_pair = inner
740 .next()
741 .ok_or_else(|| XlogError::Parse("Missing evidence value".to_string()))?;
742 let value = match value_pair.as_str() {
743 "true" => true,
744 "false" => false,
745 other => {
746 return Err(XlogError::Parse(format!(
747 "Invalid evidence value (expected true/false): {}",
748 other
749 )))
750 }
751 };
752
753 Ok(Evidence { atom, value })
754}
755
756fn build_prob_query(pair: Pair<'_, Rule>) -> Result<ProbQuery> {
757 let atom_pair = pair
758 .into_inner()
759 .next()
760 .ok_or_else(|| XlogError::Parse("Missing query atom".to_string()))?;
761 let atom = build_atom(atom_pair)?;
762 Ok(ProbQuery { atom })
763}
764
765fn build_neural_pred_decl(pair: Pair<'_, Rule>) -> Result<NeuralPredDecl> {
768 let mut inner = pair.into_inner();
769
770 let network = inner
772 .next()
773 .ok_or_else(|| XlogError::Parse("Missing network name in neural predicate".to_string()))?
774 .as_str()
775 .to_string();
776
777 let input_list = inner
779 .next()
780 .ok_or_else(|| XlogError::Parse("Missing input list in neural predicate".to_string()))?;
781 let inputs: Vec<String> = input_list
782 .into_inner()
783 .map(|p| p.as_str().to_string())
784 .collect();
785
786 let output = inner
788 .next()
789 .ok_or_else(|| XlogError::Parse("Missing output variable in neural predicate".to_string()))?
790 .as_str()
791 .to_string();
792
793 let next = inner
795 .next()
796 .ok_or_else(|| XlogError::Parse("Missing predicate in neural predicate".to_string()))?;
797
798 let (labels, predicate) = if next.as_rule() == Rule::neural_label_list {
799 let label_vec: Vec<NeuralLabel> = next
801 .into_inner()
802 .map(|label_pair| {
803 let label_inner = label_pair
804 .into_inner()
805 .next()
806 .expect("neural_label must have inner");
807 match label_inner.as_rule() {
808 Rule::integer => {
809 let val: i64 = label_inner.as_str().parse().expect("valid integer");
810 NeuralLabel::Integer(val)
811 }
812 Rule::ident => NeuralLabel::Symbol(label_inner.as_str().to_string()),
813 _ => unreachable!("neural_label should be integer or ident"),
814 }
815 })
816 .collect();
817
818 let atom_pair = inner.next().ok_or_else(|| {
820 XlogError::Parse("Missing predicate after labels in neural predicate".to_string())
821 })?;
822 let predicate = build_atom(atom_pair)?;
823
824 (Some(label_vec), predicate)
825 } else {
826 let predicate = build_atom(next)?;
828 (None, predicate)
829 };
830
831 Ok(NeuralPredDecl {
832 network,
833 inputs,
834 output,
835 labels,
836 predicate,
837 })
838}
839
840fn build_learnable_rule(pair: Pair<'_, Rule>) -> Result<LearnableRule> {
844 let mut inner = pair.into_inner();
845 let mask_name = inner
846 .next()
847 .ok_or_else(|| XlogError::Parse("Missing learnable mask name".into()))?
848 .as_str()
849 .to_string();
850 let head = build_head(
851 inner
852 .next()
853 .ok_or_else(|| XlogError::Parse("Missing learnable head".into()))?,
854 )?;
855 let body = build_body(
856 inner
857 .next()
858 .ok_or_else(|| XlogError::Parse("Missing learnable body".into()))?,
859 )?;
860 Ok(LearnableRule {
861 mask_name,
862 head,
863 body,
864 })
865}
866
867fn build_head(pair: Pair<'_, Rule>) -> Result<Atom> {
869 let mut inner = pair.into_inner();
870 let predicate = inner
871 .next()
872 .ok_or_else(|| XlogError::Parse("Missing head predicate".to_string()))?
873 .as_str()
874 .to_string();
875
876 let mut terms = Vec::new();
877 for term_list in inner {
878 if term_list.as_rule() == Rule::head_term_list {
879 for head_term in term_list.into_inner() {
880 terms.push(build_head_term(head_term)?);
881 }
882 }
883 }
884
885 Ok(Atom { predicate, terms })
886}
887
888fn build_head_term(pair: Pair<'_, Rule>) -> Result<Term> {
890 let inner = pair
891 .into_inner()
892 .next()
893 .ok_or_else(|| XlogError::Parse("Empty head term".to_string()))?;
894
895 match inner.as_rule() {
896 Rule::aggregate => build_aggregate(inner),
897 Rule::agg_term => {
898 let agg_inner = inner
900 .into_inner()
901 .next()
902 .ok_or_else(|| XlogError::Parse("Empty agg_term".to_string()))?;
903 match agg_inner.as_rule() {
904 Rule::aggregate => build_aggregate(agg_inner),
905 Rule::term => build_term(agg_inner),
906 _ => build_term(agg_inner),
907 }
908 }
909 Rule::term => build_term(inner),
910 _ => build_term(inner),
911 }
912}
913
914fn build_aggregate(pair: Pair<'_, Rule>) -> Result<Term> {
916 let mut inner = pair.into_inner();
917 let op_pair = inner
918 .next()
919 .ok_or_else(|| XlogError::Parse("Missing aggregate operator".to_string()))?;
920 let op = match op_pair.as_str() {
921 "count" => AggOp::Count,
922 "sum" => AggOp::Sum,
923 "min" => AggOp::Min,
924 "max" => AggOp::Max,
925 "logsumexp" => AggOp::LogSumExp,
926 _ => {
927 return Err(XlogError::Parse(format!(
928 "Unknown aggregate: {}",
929 op_pair.as_str()
930 )))
931 }
932 };
933
934 let var_pair = inner
935 .next()
936 .ok_or_else(|| XlogError::Parse("Missing aggregate variable".to_string()))?;
937 let variable = var_pair.as_str().to_string();
938
939 Ok(Term::Aggregate(AggExpr { op, variable }))
940}
941
942fn build_atom(pair: Pair<'_, Rule>) -> Result<Atom> {
944 let mut inner = pair.into_inner();
945 let predicate = inner
946 .next()
947 .ok_or_else(|| XlogError::Parse("Missing atom predicate".to_string()))?
948 .as_str()
949 .to_string();
950
951 let mut terms = Vec::new();
952 for term_list in inner {
953 if term_list.as_rule() == Rule::term_list {
954 for term in term_list.into_inner() {
955 terms.push(build_term(term)?);
956 }
957 }
958 }
959
960 Ok(Atom { predicate, terms })
961}
962
963fn build_body(pair: Pair<'_, Rule>) -> Result<Vec<BodyLiteral>> {
965 let mut literals = Vec::new();
966
967 for lit in pair.into_inner() {
968 literals.push(build_body_literal(lit)?);
969 }
970
971 Ok(literals)
972}
973
974fn build_body_literal(pair: Pair<'_, Rule>) -> Result<BodyLiteral> {
976 let inner = pair
977 .into_inner()
978 .next()
979 .ok_or_else(|| XlogError::Parse("Empty body literal".to_string()))?;
980
981 match inner.as_rule() {
982 Rule::nested_modal_chain => build_nested_modal_chain(inner),
983 Rule::negated_epistemic_atom => build_epistemic_literal(inner, true),
984 Rule::epistemic_atom => build_epistemic_literal(inner, false),
985 Rule::negated_atom => {
986 let atom_pair = inner
987 .into_inner()
988 .next()
989 .ok_or_else(|| XlogError::Parse("Missing negated atom".to_string()))?;
990 Ok(BodyLiteral::Negated(build_atom(atom_pair)?))
991 }
992 Rule::atom => Ok(BodyLiteral::Positive(build_atom(inner)?)),
993 Rule::comparison => Ok(BodyLiteral::Comparison(build_comparison(inner)?)),
994 Rule::is_expr => Ok(BodyLiteral::IsExpr(build_is_expr(inner)?)),
995 Rule::univ => Ok(BodyLiteral::Univ(build_univ(inner)?)),
996 _ => Err(XlogError::Parse(format!(
997 "Unknown body literal: {:?}",
998 inner.as_rule()
999 ))),
1000 }
1001}
1002
1003fn build_epistemic_literal(pair: Pair<'_, Rule>, negated: bool) -> Result<BodyLiteral> {
1004 let epistemic_pair = if pair.as_rule() == Rule::negated_epistemic_atom {
1005 pair.into_inner()
1006 .next()
1007 .ok_or_else(|| XlogError::Parse("Missing negated epistemic literal".to_string()))?
1008 } else {
1009 pair
1010 };
1011
1012 let mut inner = epistemic_pair.into_inner();
1013 let op_pair = inner
1014 .next()
1015 .ok_or_else(|| XlogError::Parse("Missing epistemic operator".to_string()))?;
1016 let op = match op_pair.as_str() {
1017 "know" => EpistemicOp::Know,
1018 "possible" => EpistemicOp::Possible,
1019 other => {
1020 return Err(XlogError::Parse(format!(
1021 "Unknown epistemic operator: {}",
1022 other
1023 )))
1024 }
1025 };
1026 let atom_pair = inner
1027 .next()
1028 .ok_or_else(|| XlogError::Parse("Missing epistemic atom".to_string()))?;
1029
1030 Ok(BodyLiteral::Epistemic(EpistemicLiteral {
1031 op,
1032 negated,
1033 atom: build_atom(atom_pair)?,
1034 }))
1035}
1036
1037fn build_nested_modal_chain(pair: Pair<'_, Rule>) -> Result<BodyLiteral> {
1066 let mut ops: Vec<EpistemicOp> = Vec::new();
1071 let mut neg_before_op: Vec<bool> = Vec::new();
1072 let mut atom_pair: Option<Pair<'_, Rule>> = None;
1073 let mut pending_not = false;
1074 let mut neg_before_atom = false;
1075
1076 for token in pair.into_inner() {
1077 match token.as_rule() {
1078 Rule::not_kw => {
1079 pending_not = true;
1080 }
1081 Rule::epistemic_op => {
1082 let op = match token.as_str() {
1083 "know" => EpistemicOp::Know,
1084 "possible" => EpistemicOp::Possible,
1085 other => {
1086 return Err(XlogError::Parse(format!(
1087 "Unknown epistemic operator: {}",
1088 other
1089 )))
1090 }
1091 };
1092 ops.push(op);
1093 neg_before_op.push(pending_not);
1094 pending_not = false;
1095 }
1096 Rule::atom => {
1097 neg_before_atom = pending_not;
1098 pending_not = false;
1099 atom_pair = Some(token);
1100 }
1101 other => {
1102 return Err(XlogError::Parse(format!(
1103 "Unexpected token in nested modal chain: {:?}",
1104 other
1105 )));
1106 }
1107 }
1108 }
1109
1110 let atom_pair = atom_pair
1111 .ok_or_else(|| XlogError::Parse("Missing atom in nested modal chain".to_string()))?;
1112 if ops.len() < 2 {
1113 return Err(XlogError::Parse(
1114 "Nested modal chain must contain at least two epistemic operators".to_string(),
1115 ));
1116 }
1117
1118 let innermost = *ops
1119 .last()
1120 .ok_or_else(|| XlogError::Parse("Empty modal chain".to_string()))?;
1121 let op = if neg_before_atom {
1122 match innermost {
1123 EpistemicOp::Know => EpistemicOp::Possible,
1124 EpistemicOp::Possible => EpistemicOp::Know,
1125 }
1126 } else {
1127 innermost
1128 };
1129 let negated = neg_before_op
1130 .iter()
1131 .copied()
1132 .fold(neg_before_atom, |acc, neg| acc ^ neg);
1133
1134 Ok(BodyLiteral::Epistemic(EpistemicLiteral {
1135 op,
1136 negated,
1137 atom: build_atom(atom_pair)?,
1138 }))
1139}
1140
1141fn build_univ(pair: Pair<'_, Rule>) -> Result<Univ> {
1143 let mut inner = pair.into_inner();
1144 let term = build_term(
1145 inner
1146 .next()
1147 .ok_or_else(|| XlogError::Parse("Missing univ term".to_string()))?,
1148 )?;
1149 let parts = build_term(
1150 inner
1151 .next()
1152 .ok_or_else(|| XlogError::Parse("Missing univ parts".to_string()))?,
1153 )?;
1154 Ok(Univ { term, parts })
1155}
1156
1157fn build_comparison(pair: Pair<'_, Rule>) -> Result<Comparison> {
1159 let mut inner = pair.into_inner();
1160
1161 let left_pair = inner
1162 .next()
1163 .ok_or_else(|| XlogError::Parse("Missing comparison left operand".to_string()))?;
1164 let left = build_term(left_pair)?;
1165
1166 let op_pair = inner
1167 .next()
1168 .ok_or_else(|| XlogError::Parse("Missing comparison operator".to_string()))?;
1169 let op = match op_pair.as_str() {
1170 "==" | "=" => CompOp::Eq,
1171 "!=" => CompOp::Ne,
1172 "<" => CompOp::Lt,
1173 "<=" => CompOp::Le,
1174 ">" => CompOp::Gt,
1175 ">=" => CompOp::Ge,
1176 _ => {
1177 return Err(XlogError::Parse(format!(
1178 "Unknown comparison operator: {}",
1179 op_pair.as_str()
1180 )))
1181 }
1182 };
1183
1184 let right_pair = inner
1185 .next()
1186 .ok_or_else(|| XlogError::Parse("Missing comparison right operand".to_string()))?;
1187 let right = build_term(right_pair)?;
1188
1189 Ok(Comparison { left, op, right })
1190}
1191
1192fn build_term(pair: Pair<'_, Rule>) -> Result<Term> {
1194 let inner = if pair.as_rule() == Rule::term {
1196 pair.into_inner()
1197 .next()
1198 .ok_or_else(|| XlogError::Parse("Empty term".to_string()))?
1199 } else {
1200 pair
1201 };
1202
1203 match inner.as_rule() {
1204 Rule::var_or_anon => {
1205 let var_inner = inner
1207 .into_inner()
1208 .next()
1209 .ok_or_else(|| XlogError::Parse("Empty var_or_anon".to_string()))?;
1210 match var_inner.as_rule() {
1211 Rule::anonymous => Ok(Term::Anonymous),
1212 Rule::variable => Ok(Term::Variable(var_inner.as_str().to_string())),
1213 _ => Err(XlogError::Parse(format!(
1214 "Expected variable or anonymous, got: {:?}",
1215 var_inner.as_rule()
1216 ))),
1217 }
1218 }
1219 Rule::variable => Ok(Term::Variable(inner.as_str().to_string())),
1220 Rule::anonymous => Ok(Term::Anonymous),
1221 Rule::integer => {
1222 let val: i64 = inner
1223 .as_str()
1224 .parse()
1225 .map_err(|_| XlogError::Parse(format!("Invalid integer: {}", inner.as_str())))?;
1226 Ok(Term::Integer(val))
1227 }
1228 Rule::float_num => {
1229 let val: f64 = inner
1230 .as_str()
1231 .parse()
1232 .map_err(|_| XlogError::Parse(format!("Invalid float: {}", inner.as_str())))?;
1233 Ok(Term::Float(val))
1234 }
1235 Rule::string_lit => {
1236 let s = inner.as_str();
1237 let unquoted = &s[1..s.len() - 1];
1239 Ok(Term::String(unquoted.to_string()))
1240 }
1241 Rule::list_literal => {
1242 let items = inner
1243 .into_inner()
1244 .map(build_term)
1245 .collect::<Result<Vec<_>>>()?;
1246 Ok(Term::List(items))
1247 }
1248 Rule::cons_pattern => {
1249 let mut parts = inner.into_inner();
1250 let head = parts
1251 .next()
1252 .ok_or_else(|| XlogError::Parse("Missing cons head".to_string()))?;
1253 let tail = parts
1254 .next()
1255 .ok_or_else(|| XlogError::Parse("Missing cons tail".to_string()))?;
1256 Ok(Term::Cons {
1257 head: Box::new(build_term(head)?),
1258 tail: Box::new(build_term(tail)?),
1259 })
1260 }
1261 Rule::compound_term => {
1262 let mut parts = inner.into_inner();
1263 let functor = parts
1264 .next()
1265 .ok_or_else(|| XlogError::Parse("Missing compound functor".to_string()))?
1266 .as_str()
1267 .to_string();
1268 let mut args = Vec::new();
1269 if let Some(term_list) = parts.next() {
1270 for term in term_list.into_inner() {
1271 args.push(build_term(term)?);
1272 }
1273 }
1274 Ok(Term::Compound { functor, args })
1275 }
1276 Rule::ident => Ok(Term::Symbol(symbol::intern(inner.as_str()))),
1277 _ => Err(XlogError::Parse(format!(
1278 "Unknown term type: {:?}",
1279 inner.as_rule()
1280 ))),
1281 }
1282}
1283
1284fn build_arith_expr(pair: Pair<'_, Rule>) -> Result<ArithExpr> {
1287 let mut inner = pair.into_inner();
1288
1289 let first = inner
1291 .next()
1292 .ok_or_else(|| XlogError::Parse("Empty arithmetic expression".to_string()))?;
1293 let mut result = build_arith_term(first)?;
1294
1295 while let Some(op_pair) = inner.next() {
1297 let op_str = op_pair.as_str();
1298 let right_pair = inner.next().ok_or_else(|| {
1299 XlogError::Parse("Missing right operand in arith expression".to_string())
1300 })?;
1301 let right = build_arith_term(right_pair)?;
1302
1303 result = match op_str {
1304 "+" => ArithExpr::Add(Box::new(result), Box::new(right)),
1305 "-" => ArithExpr::Sub(Box::new(result), Box::new(right)),
1306 _ => {
1307 return Err(XlogError::Parse(format!(
1308 "Unknown additive operator: {}",
1309 op_str
1310 )))
1311 }
1312 };
1313 }
1314
1315 Ok(result)
1316}
1317
1318fn build_arith_term(pair: Pair<'_, Rule>) -> Result<ArithExpr> {
1321 let mut inner = pair.into_inner();
1322
1323 let first = inner
1325 .next()
1326 .ok_or_else(|| XlogError::Parse("Empty arithmetic term".to_string()))?;
1327 let mut result = build_arith_primary(first)?;
1328
1329 while let Some(op_pair) = inner.next() {
1331 let op_str = op_pair.as_str();
1332 let right_pair = inner
1333 .next()
1334 .ok_or_else(|| XlogError::Parse("Missing right operand in arith term".to_string()))?;
1335 let right = build_arith_primary(right_pair)?;
1336
1337 result = match op_str {
1338 "*" => ArithExpr::Mul(Box::new(result), Box::new(right)),
1339 "/" => ArithExpr::Div(Box::new(result), Box::new(right)),
1340 "%" => ArithExpr::Mod(Box::new(result), Box::new(right)),
1341 _ => {
1342 return Err(XlogError::Parse(format!(
1343 "Unknown multiplicative operator: {}",
1344 op_str
1345 )))
1346 }
1347 };
1348 }
1349
1350 Ok(result)
1351}
1352
1353fn build_arith_primary(pair: Pair<'_, Rule>) -> Result<ArithExpr> {
1362 let mut inner = pair.into_inner();
1363 let first = inner
1364 .next()
1365 .ok_or_else(|| XlogError::Parse("Empty arithmetic primary".to_string()))?;
1366
1367 match first.as_rule() {
1368 Rule::builtin_fn => {
1369 let fn_name = first.as_str();
1370 let args: Vec<Pair<'_, Rule>> = inner.collect();
1372
1373 match fn_name {
1374 "abs" => {
1375 if args.len() != 1 {
1376 return Err(XlogError::Parse(
1377 "abs() takes exactly 1 argument".to_string(),
1378 ));
1379 }
1380 let arg = build_arith_expr(args.into_iter().next().unwrap())?;
1381 Ok(ArithExpr::Abs(Box::new(arg)))
1382 }
1383 "min" => {
1384 if args.len() != 2 {
1385 return Err(XlogError::Parse(
1386 "min() takes exactly 2 arguments".to_string(),
1387 ));
1388 }
1389 let mut args_iter = args.into_iter();
1390 let arg1 = build_arith_expr(args_iter.next().unwrap())?;
1391 let arg2 = build_arith_expr(args_iter.next().unwrap())?;
1392 Ok(ArithExpr::Min(Box::new(arg1), Box::new(arg2)))
1393 }
1394 "max" => {
1395 if args.len() != 2 {
1396 return Err(XlogError::Parse(
1397 "max() takes exactly 2 arguments".to_string(),
1398 ));
1399 }
1400 let mut args_iter = args.into_iter();
1401 let arg1 = build_arith_expr(args_iter.next().unwrap())?;
1402 let arg2 = build_arith_expr(args_iter.next().unwrap())?;
1403 Ok(ArithExpr::Max(Box::new(arg1), Box::new(arg2)))
1404 }
1405 "pow" => {
1406 if args.len() != 2 {
1407 return Err(XlogError::Parse(
1408 "pow() takes exactly 2 arguments".to_string(),
1409 ));
1410 }
1411 let mut args_iter = args.into_iter();
1412 let arg1 = build_arith_expr(args_iter.next().unwrap())?;
1413 let arg2 = build_arith_expr(args_iter.next().unwrap())?;
1414 Ok(ArithExpr::Pow(Box::new(arg1), Box::new(arg2)))
1415 }
1416 "cast" => {
1417 if args.len() != 2 {
1418 return Err(XlogError::Parse(
1419 "cast() takes exactly 2 arguments".to_string(),
1420 ));
1421 }
1422 let mut args_iter = args.into_iter();
1423 let arg1 = build_arith_expr(args_iter.next().unwrap())?;
1424 let type_pair = args_iter.next().unwrap();
1425 let target_type = build_scalar_type_spec(type_pair, "cast target")?;
1426 Ok(ArithExpr::Cast(Box::new(arg1), target_type))
1427 }
1428 _ => Err(XlogError::Parse(format!(
1429 "Unknown builtin function: {}",
1430 fn_name
1431 ))),
1432 }
1433 }
1434 Rule::arith_expr => {
1435 build_arith_expr(first)
1437 }
1438 Rule::variable => Ok(ArithExpr::Variable(first.as_str().to_string())),
1439 Rule::integer => {
1440 let val: i64 = first
1441 .as_str()
1442 .parse()
1443 .map_err(|_| XlogError::Parse(format!("Invalid integer: {}", first.as_str())))?;
1444 Ok(ArithExpr::Integer(val))
1445 }
1446 Rule::float_num => {
1447 let val: f64 = first
1448 .as_str()
1449 .parse()
1450 .map_err(|_| XlogError::Parse(format!("Invalid float: {}", first.as_str())))?;
1451 Ok(ArithExpr::Float(val))
1452 }
1453 Rule::func_call => {
1454 let mut call_inner = first.into_inner();
1455 let name = call_inner
1456 .next()
1457 .ok_or_else(|| XlogError::Parse("Missing function name".to_string()))?
1458 .as_str()
1459 .to_string();
1460 let args: Vec<ArithExpr> = call_inner
1461 .map(build_arith_expr)
1462 .collect::<Result<Vec<_>>>()?;
1463 Ok(ArithExpr::FuncCall { name, args })
1464 }
1465 _ => Err(XlogError::Parse(format!(
1466 "Unexpected token in arith_primary: {:?}",
1467 first.as_rule()
1468 ))),
1469 }
1470}
1471
1472fn build_is_expr(pair: Pair<'_, Rule>) -> Result<IsExpr> {
1474 let mut inner = pair.into_inner();
1475 let target = inner
1476 .next()
1477 .ok_or_else(|| XlogError::Parse("Missing target variable in is expression".to_string()))?
1478 .as_str()
1479 .to_string();
1480 let expr = build_arith_expr(
1481 inner
1482 .next()
1483 .ok_or_else(|| XlogError::Parse("Missing expression in is expression".to_string()))?,
1484 )?;
1485 Ok(IsExpr { target, expr })
1486}
1487
1488#[cfg(test)]
1489mod tests {
1490 use super::*;
1491
1492 #[test]
1493 fn test_parse_fact() {
1494 let input = "edge(1, 2).";
1495 let result = parse_program(input);
1496 assert!(result.is_ok(), "Failed to parse fact: {:?}", result.err());
1497
1498 let program = result.unwrap();
1499 assert_eq!(program.rules.len(), 1);
1500 assert!(program.rules[0].is_fact());
1501 assert_eq!(program.rules[0].head.predicate, "edge");
1502 assert_eq!(program.rules[0].head.terms.len(), 2);
1503 assert_eq!(program.rules[0].head.terms[0], Term::Integer(1));
1504 assert_eq!(program.rules[0].head.terms[1], Term::Integer(2));
1505 }
1506
1507 #[test]
1508 fn test_parse_rule() {
1509 let input = "reach(X, Y) :- edge(X, Y).";
1510 let result = parse_program(input);
1511 assert!(result.is_ok(), "Failed to parse rule: {:?}", result.err());
1512
1513 let program = result.unwrap();
1514 assert_eq!(program.rules.len(), 1);
1515 assert!(!program.rules[0].is_fact());
1516 assert_eq!(program.rules[0].head.predicate, "reach");
1517 assert_eq!(program.rules[0].body.len(), 1);
1518 }
1519
1520 #[test]
1521 fn test_parse_recursive_rule() {
1522 let input = "reach(X, Z) :- reach(X, Y), edge(Y, Z).";
1523 let result = parse_program(input);
1524 assert!(
1525 result.is_ok(),
1526 "Failed to parse recursive rule: {:?}",
1527 result.err()
1528 );
1529
1530 let program = result.unwrap();
1531 assert_eq!(program.rules.len(), 1);
1532 assert_eq!(program.rules[0].body.len(), 2);
1533 }
1534
1535 #[test]
1536 fn test_parse_negation() {
1537 let input = "isolated(X) :- node(X), not edge(X, Y).";
1538 let result = parse_program(input);
1539 assert!(
1540 result.is_ok(),
1541 "Failed to parse negation: {:?}",
1542 result.err()
1543 );
1544
1545 let program = result.unwrap();
1546 assert!(program.rules[0].has_negation());
1547 assert!(matches!(&program.rules[0].body[1], BodyLiteral::Negated(_)));
1548 }
1549
1550 #[test]
1551 fn test_parse_epistemic_literal_syntax() {
1552 let input = "believed(X) :- node(X), know edge(X).";
1553 let result = parse_program(input);
1554 assert!(
1555 result.is_ok(),
1556 "Failed to parse epistemic literal: {:?}",
1557 result.err()
1558 );
1559
1560 let program = result.unwrap();
1561 assert_eq!(program.rules.len(), 1);
1562 assert_eq!(program.rules[0].body.len(), 2);
1563 }
1564
1565 #[test]
1566 fn test_parse_predicate_with_epistemic_keyword_prefix_as_atom() {
1567 let input = "friend_of_friend(A, C) :- knows(A, B), knows(B, C), A != C.";
1568 let program = parse_program(input).expect("parse ordinary predicate named knows");
1569
1570 assert_eq!(program.rules.len(), 1);
1571 assert_eq!(program.rules[0].body.len(), 3);
1572 assert!(
1573 matches!(&program.rules[0].body[0], BodyLiteral::Positive(atom) if atom.predicate == "knows")
1574 );
1575 assert!(
1576 matches!(&program.rules[0].body[1], BodyLiteral::Positive(atom) if atom.predicate == "knows")
1577 );
1578 }
1579
1580 #[test]
1581 fn test_parse_aggregate() {
1582 let input = "out_degree(X, count(Y)) :- edge(X, Y).";
1583 let result = parse_program(input);
1584 assert!(
1585 result.is_ok(),
1586 "Failed to parse aggregate: {:?}",
1587 result.err()
1588 );
1589
1590 let program = result.unwrap();
1591 assert!(program.rules[0].has_aggregation());
1592 if let Term::Aggregate(agg) = &program.rules[0].head.terms[1] {
1593 assert_eq!(agg.op, AggOp::Count);
1594 assert_eq!(agg.variable, "Y");
1595 } else {
1596 panic!("Expected aggregate term");
1597 }
1598 }
1599
1600 #[test]
1601 fn test_parse_logsumexp_aggregate() {
1602 let input = "score(X, logsumexp(Y)) :- obs(X, Y).";
1603 let result = parse_program(input);
1604 assert!(
1605 result.is_ok(),
1606 "Failed to parse logsumexp aggregate: {:?}",
1607 result.err()
1608 );
1609
1610 let program = result.unwrap();
1611 assert!(program.rules[0].has_aggregation());
1612 if let Term::Aggregate(agg) = &program.rules[0].head.terms[1] {
1613 assert_eq!(agg.op, AggOp::LogSumExp);
1614 assert_eq!(agg.variable, "Y");
1615 } else {
1616 panic!("Expected aggregate term");
1617 }
1618 }
1619
1620 #[test]
1621 fn test_parse_constraint() {
1622 let input = ":- reach(X, X).";
1623 let result = parse_program(input);
1624 assert!(
1625 result.is_ok(),
1626 "Failed to parse constraint: {:?}",
1627 result.err()
1628 );
1629
1630 let program = result.unwrap();
1631 assert_eq!(program.constraints.len(), 1);
1632 assert_eq!(program.constraints[0].body.len(), 1);
1633 }
1634
1635 #[test]
1636 fn test_parse_query() {
1637 let input = "?- reach(1, N).";
1638 let result = parse_program(input);
1639 assert!(result.is_ok(), "Failed to parse query: {:?}", result.err());
1640
1641 let program = result.unwrap();
1642 assert_eq!(program.queries.len(), 1);
1643 assert_eq!(program.queries[0].atom.predicate, "reach");
1644 }
1645
1646 #[test]
1647 fn test_parse_full_program() {
1648 let input = r#"
1649 edge(1, 2).
1650 edge(2, 3).
1651 edge(3, 4).
1652 reach(X, Y) :- edge(X, Y).
1653 reach(X, Z) :- reach(X, Y), edge(Y, Z).
1654 ?- reach(1, N).
1655 "#;
1656 let result = parse_program(input);
1657 assert!(
1658 result.is_ok(),
1659 "Failed to parse full program: {:?}",
1660 result.err()
1661 );
1662
1663 let program = result.unwrap();
1664 assert_eq!(program.rules.len(), 5); assert_eq!(program.queries.len(), 1);
1666 assert_eq!(program.facts().count(), 3);
1667 assert_eq!(program.proper_rules().count(), 2);
1668 }
1669
1670 #[test]
1671 fn test_parse_comparison() {
1672 let input = "small(X) :- value(X), X < 10.";
1673 let result = parse_program(input);
1674 assert!(
1675 result.is_ok(),
1676 "Failed to parse comparison: {:?}",
1677 result.err()
1678 );
1679
1680 let program = result.unwrap();
1681 assert_eq!(program.rules[0].body.len(), 2);
1682 if let BodyLiteral::Comparison(cmp) = &program.rules[0].body[1] {
1683 assert_eq!(cmp.op, CompOp::Lt);
1684 assert_eq!(cmp.left, Term::Variable("X".to_string()));
1685 assert_eq!(cmp.right, Term::Integer(10));
1686 } else {
1687 panic!("Expected comparison");
1688 }
1689 }
1690
1691 #[test]
1692 fn test_parse_pred_decl() {
1693 let input = "pred edge(u32, u32).";
1694 let result = parse_program(input);
1695 assert!(
1696 result.is_ok(),
1697 "Failed to parse pred decl: {:?}",
1698 result.err()
1699 );
1700
1701 let program = result.unwrap();
1702 assert_eq!(program.predicates.len(), 1);
1703 assert_eq!(program.predicates[0].name, "edge");
1704 assert_eq!(program.predicates[0].types.len(), 2);
1705 assert_eq!(
1706 program.predicates[0].types[0],
1707 TypeRef::Scalar(ScalarType::U32)
1708 );
1709 assert_eq!(
1710 program.predicates[0].types[1],
1711 TypeRef::Scalar(ScalarType::U32)
1712 );
1713 }
1714
1715 #[test]
1716 fn test_parse_anonymous_wildcard() {
1717 let input = "has_child(X) :- parent(X, _).";
1719 let result = parse_program(input);
1720 assert!(
1721 result.is_ok(),
1722 "Failed to parse anonymous wildcard: {:?}",
1723 result.err()
1724 );
1725
1726 let program = result.unwrap();
1727 assert_eq!(program.rules.len(), 1);
1728 let rule = &program.rules[0];
1729 assert_eq!(rule.head.predicate, "has_child");
1730
1731 if let BodyLiteral::Positive(atom) = &rule.body[0] {
1733 assert_eq!(atom.predicate, "parent");
1734 assert_eq!(atom.terms.len(), 2);
1735 assert_eq!(atom.terms[0], Term::Variable("X".to_string()));
1736 assert_eq!(atom.terms[1], Term::Anonymous);
1737 } else {
1738 panic!("Expected positive atom");
1739 }
1740 }
1741
1742 #[test]
1743 fn test_parse_multiple_wildcards() {
1744 let input = "exists(X) :- rel(X, _, _).";
1746 let result = parse_program(input);
1747 assert!(
1748 result.is_ok(),
1749 "Failed to parse multiple wildcards: {:?}",
1750 result.err()
1751 );
1752
1753 let program = result.unwrap();
1754 if let BodyLiteral::Positive(atom) = &program.rules[0].body[0] {
1755 assert_eq!(atom.terms.len(), 3);
1756 assert_eq!(atom.terms[0], Term::Variable("X".to_string()));
1757 assert_eq!(atom.terms[1], Term::Anonymous);
1758 assert_eq!(atom.terms[2], Term::Anonymous);
1759 }
1760 }
1761
1762 #[test]
1763 fn test_parse_is_expr() {
1764 let input = "result(X, Z) :- input(X, Y), Z is Y + 1.";
1766 let result = XlogParser::parse(Rule::program, input);
1767 assert!(
1768 result.is_ok(),
1769 "Failed to parse is expression: {:?}",
1770 result.err()
1771 );
1772 }
1773
1774 #[test]
1775 fn test_parse_arithmetic_precedence() {
1776 let input = "r(X, Z) :- p(X, A, B), Z is A + B * 2.";
1778 let result = parse_program(input).unwrap();
1779 let rule = &result.rules[0];
1780 assert_eq!(rule.body.len(), 2);
1781 assert!(matches!(&rule.body[1], BodyLiteral::IsExpr(_)));
1782 }
1783
1784 #[test]
1785 fn test_parse_arithmetic_parentheses() {
1786 let input = "r(X, Z) :- p(X, A, B), Z is (A + B) * 2.";
1787 assert!(parse_program(input).is_ok());
1788 }
1789
1790 #[test]
1791 fn test_parse_probabilistic_fact_syntax() {
1792 let input = "0.7::rain().";
1793 let result = parse_program(input);
1794 assert!(
1795 result.is_ok(),
1796 "Failed to parse probabilistic fact: {:?}",
1797 result.err()
1798 );
1799 }
1800
1801 #[test]
1802 fn test_parse_annotated_disjunction_syntax() {
1803 let input = "0.6::coin(heads); 0.4::coin(tails).";
1804 let result = parse_program(input);
1805 assert!(
1806 result.is_ok(),
1807 "Failed to parse annotated disjunction: {:?}",
1808 result.err()
1809 );
1810 }
1811
1812 #[test]
1813 fn test_parse_evidence_is_not_a_fact() {
1814 let input = "evidence(rain(), true).";
1815 let program = parse_program(input).unwrap();
1816 assert_eq!(
1817 program.rules.len(),
1818 0,
1819 "evidence/2 should not be parsed as a regular fact"
1820 );
1821 }
1822
1823 #[test]
1824 fn test_parse_query_directive_is_not_a_fact() {
1825 let input = "query(reach(1,3)).";
1826 let program = parse_program(input).unwrap();
1827 assert_eq!(
1828 program.rules.len(),
1829 0,
1830 "query/1 should not be parsed as a regular fact"
1831 );
1832 }
1833
1834 #[test]
1835 fn test_parse_prob_engine_pragma_syntax() {
1836 let input = "#pragma prob_engine = mc";
1837 let result = parse_program(input);
1838 assert!(
1839 result.is_ok(),
1840 "Failed to parse prob_engine pragma: {:?}",
1841 result.err()
1842 );
1843 }
1844
1845 #[test]
1846 fn test_parse_epistemic_mode_pragma_syntax() {
1847 let input = "#pragma epistemic_mode = faeel";
1848 let result = parse_program(input);
1849 assert!(
1850 result.is_ok(),
1851 "Failed to parse epistemic_mode pragma: {:?}",
1852 result.err()
1853 );
1854 }
1855
1856 #[test]
1857 fn test_parse_probabilistic_fact_ast() {
1858 let program = parse_program("0.7::rain().").unwrap();
1859 assert_eq!(program.prob_facts.len(), 1);
1860 assert!((program.prob_facts[0].prob - 0.7).abs() < 1e-9);
1861 assert_eq!(program.prob_facts[0].atom.predicate, "rain");
1862 assert!(program.prob_facts[0].atom.terms.is_empty());
1863 }
1864
1865 #[test]
1866 fn test_parse_annotated_disjunction_ast() {
1867 let program = parse_program("0.6::coin(heads); 0.4::coin(tails).").unwrap();
1868 assert_eq!(program.annotated_disjunctions.len(), 1);
1869 let ad = &program.annotated_disjunctions[0];
1870 assert_eq!(ad.choices.len(), 2);
1871 assert!((ad.choices[0].prob - 0.6).abs() < 1e-9);
1872 assert_eq!(ad.choices[0].atom.predicate, "coin");
1873 assert_eq!(ad.choices[0].atom.terms.len(), 1);
1874 assert_eq!(
1875 ad.choices[0].atom.terms[0],
1876 Term::Symbol(symbol::intern("heads"))
1877 );
1878 assert!((ad.choices[1].prob - 0.4).abs() < 1e-9);
1879 assert_eq!(
1880 ad.choices[1].atom.terms[0],
1881 Term::Symbol(symbol::intern("tails"))
1882 );
1883 }
1884
1885 #[test]
1886 fn test_parse_evidence_ast() {
1887 let program = parse_program("evidence(rain(), true).").unwrap();
1888 assert_eq!(program.evidence.len(), 1);
1889 assert_eq!(program.evidence[0].atom.predicate, "rain");
1890 assert!(program.evidence[0].value);
1891 }
1892
1893 #[test]
1894 fn test_parse_prob_query_ast() {
1895 let program = parse_program("query(reach(1,3)).").unwrap();
1896 assert_eq!(program.prob_queries.len(), 1);
1897 assert_eq!(program.prob_queries[0].atom.predicate, "reach");
1898 assert_eq!(program.prob_queries[0].atom.terms.len(), 2);
1899 assert_eq!(program.prob_queries[0].atom.terms[0], Term::Integer(1));
1900 assert_eq!(program.prob_queries[0].atom.terms[1], Term::Integer(3));
1901 }
1902
1903 #[test]
1904 fn test_parse_prob_engine_pragma_ast() {
1905 let program = parse_program("#pragma prob_engine = mc").unwrap();
1906 assert_eq!(
1907 program.directives.prob_engine,
1908 Some(crate::ast::ProbEngine::Mc)
1909 );
1910 assert_eq!(program.prob_engine(), crate::ast::ProbEngine::Mc);
1911 }
1912
1913 #[test]
1914 fn test_parse_arithmetic_builtins() {
1915 let inputs = [
1916 "r(X, Z) :- p(X, Y), Z is abs(Y).",
1917 "r(X, Z) :- p(X, A, B), Z is min(A, B).",
1918 "r(X, Z) :- p(X, A, B), Z is max(A, B).",
1919 "r(X, Z) :- p(X, A, B), Z is pow(A, B).",
1920 "r(X, Z) :- p(X, Y), Z is cast(Y, f64).",
1921 ];
1922 for input in inputs {
1923 assert!(parse_program(input).is_ok(), "Failed to parse: {}", input);
1924 }
1925 }
1926
1927 #[test]
1928 fn test_parse_arithmetic_nested() {
1929 let input = "r(X, Z) :- p(X, A, B, C), Z is abs(A - B) + min(B, C) * 2.";
1930 assert!(parse_program(input).is_ok());
1931 }
1932
1933 fn first_epistemic_literal(src: &str) -> EpistemicLiteral {
1936 let program = parse_program(src).unwrap_or_else(|e| panic!("parse failed: {e:?}"));
1937 let rule = program.rules.first().expect("one rule");
1938 match rule.body.first().expect("one body literal") {
1939 BodyLiteral::Epistemic(lit) => lit.clone(),
1940 other => panic!("expected epistemic literal, got {other:?}"),
1941 }
1942 }
1943
1944 #[test]
1945 fn test_nested_modal_chain_collapses_to_innermost_operator() {
1946 let lit = first_epistemic_literal("q() :- know possible p().");
1948 assert_eq!(lit.op, EpistemicOp::Possible);
1949 assert!(!lit.negated);
1950 assert_eq!(lit.atom.predicate, "p");
1951
1952 let lit = first_epistemic_literal("q() :- possible know p().");
1954 assert_eq!(lit.op, EpistemicOp::Know);
1955 assert!(!lit.negated);
1956
1957 let lit = first_epistemic_literal("q() :- know know p().");
1959 assert_eq!(lit.op, EpistemicOp::Know);
1960
1961 let lit = first_epistemic_literal("q() :- possible possible p().");
1963 assert_eq!(lit.op, EpistemicOp::Possible);
1964
1965 let lit = first_epistemic_literal("q() :- know possible know p().");
1967 assert_eq!(lit.op, EpistemicOp::Know);
1968 }
1969
1970 #[test]
1971 fn test_nested_modal_chain_leading_negation_distributes() {
1972 let lit = first_epistemic_literal("q() :- not know possible p().");
1974 assert_eq!(lit.op, EpistemicOp::Possible);
1975 assert!(lit.negated);
1976
1977 let lit = first_epistemic_literal("q() :- not possible know p().");
1979 assert_eq!(lit.op, EpistemicOp::Know);
1980 assert!(lit.negated);
1981 }
1982
1983 #[test]
1984 fn test_nested_modal_chain_interior_negation_dualizes() {
1985 let lit = first_epistemic_literal("q() :- know not possible p().");
1987 assert_eq!(lit.op, EpistemicOp::Possible);
1988 assert!(lit.negated);
1989
1990 let lit = first_epistemic_literal("q() :- possible not know p().");
1992 assert_eq!(lit.op, EpistemicOp::Know);
1993 assert!(lit.negated);
1994
1995 let lit = first_epistemic_literal("q() :- not know not possible p().");
1997 assert_eq!(lit.op, EpistemicOp::Possible);
1998 assert!(!lit.negated);
1999 }
2000
2001 #[test]
2002 fn test_nested_modal_chain_atom_adjacent_negation_dualizes() {
2003 let lit = first_epistemic_literal("q() :- know possible not p().");
2005 assert_eq!(lit.op, EpistemicOp::Know);
2006 assert!(lit.negated);
2007
2008 let lit = first_epistemic_literal("q() :- possible know not p().");
2010 assert_eq!(lit.op, EpistemicOp::Possible);
2011 assert!(lit.negated);
2012 }
2013}