1pub 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 let _ = result;
64 }
65 }
66}