Skip to main content

ocas_parse/
lib.rs

1//! Parser and printer for oCAS expressions.
2//!
3//! This crate provides a [`logos`]-based lexer and a small recursive-descent
4//! parser that turns text into [`ocas_atom::Atom`] expression trees.
5
6pub mod lexer;
7pub mod parser;
8
9pub use parser::{ParseError, parse};
10
11#[cfg(test)]
12mod proptests {
13    use ocas_atom::AtomArena;
14    use ocas_core::arena::Arena;
15    use proptest::prelude::*;
16
17    use super::*;
18
19    fn valid_expr_str() -> impl Strategy<Value = String> {
20        let leaf = prop_oneof![
21            Just("x".to_string()),
22            Just("y".to_string()),
23            Just("z".to_string()),
24            (-100..100i64).prop_map(|n| n.to_string()),
25        ];
26        leaf.prop_recursive(4, 64, 4, |inner| {
27            prop_oneof![
28                inner.clone().prop_map(|e| format!("sin({})", e)),
29                inner.clone().prop_map(|e| format!("cos({})", e)),
30                (inner.clone(), inner.clone()).prop_map(|(a, b)| format!("({}) + ({})", a, b)),
31                (inner.clone(), inner.clone()).prop_map(|(a, b)| format!("({}) * ({})", a, b)),
32                (inner.clone(), 0..5u32).prop_map(|(a, n)| format!("({})^{}", a, n)),
33            ]
34        })
35    }
36
37    proptest! {
38        #[test]
39        fn parse_succeeds_on_valid_expr(s in valid_expr_str()) {
40            let arena = Arena::new();
41            let ctx = AtomArena::new(&arena);
42            let result = parse(&ctx, &s);
43            prop_assert!(result.is_ok(), "parse failed for {}: {:?}", s, result);
44        }
45
46        #[test]
47        fn parse_print_is_deterministic(s in valid_expr_str()) {
48            let arena = Arena::new();
49            let ctx = AtomArena::new(&arena);
50            let atom = parse(&ctx, &s).unwrap();
51            let printed = atom.to_string();
52            let reparsed = parse(&ctx, &printed).unwrap();
53            prop_assert_eq!(atom.to_string(), reparsed.to_string());
54        }
55
56        #[test]
57        fn parse_rejects_invalid(s in "[^0-9a-zA-Z()+*^\t\n\r]*") {
58            let arena = Arena::new();
59            let ctx = AtomArena::new(&arena);
60            let result = parse(&ctx, &s);
61            // Some strings may coincidentally be valid (e.g. empty), but most should fail.
62            // We only assert that parsing does not panic.
63            let _ = result;
64        }
65    }
66}