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