Skip to main content

vihaco_parser_core/
lib.rs

1// SPDX-FileCopyrightText: 2026 The vihaco Authors
2// SPDX-License-Identifier: MIT
3
4pub mod container;
5pub mod impls;
6
7pub use impls::ident;
8
9use chumsky::error::Simple;
10use chumsky::extra;
11
12/// A parser whose input is `&'src str` (char stream) and whose error type is `Simple<char>`.
13///
14/// The lifetime `'src` is the input lifetime. Output type `Self` is owned and does not borrow
15/// from the input.
16pub trait Parse<'src>: Sized {
17    fn parser() -> impl chumsky::Parser<'src, &'src str, Self, extra::Err<Simple<'src, char>>>;
18}
19
20#[cfg(test)]
21mod tests {
22    use super::*;
23    use chumsky::Parser;
24
25    fn parses<'src, T: Parse<'src>>(input: &'src str) -> T {
26        T::parser().parse(input).into_result().unwrap()
27    }
28
29    #[test]
30    fn i64_basic() {
31        assert_eq!(parses::<i64>("42"), 42);
32    }
33    #[test]
34    fn i32_basic() {
35        assert_eq!(parses::<i32>("7"), 7);
36    }
37    #[test]
38    fn u64_basic() {
39        assert_eq!(parses::<u64>("100"), 100);
40    }
41    #[test]
42    fn u32_basic() {
43        assert_eq!(parses::<u32>("0"), 0);
44    }
45    #[test]
46    fn usize_basic() {
47        assert_eq!(parses::<usize>("9"), 9);
48    }
49    #[test]
50    fn f64_int() {
51        assert_eq!(parses::<f64>("3"), 3.0);
52    }
53    #[test]
54    #[allow(clippy::approx_constant)]
55    fn f64_float() {
56        assert_eq!(parses::<f64>("3.14"), 3.14);
57    }
58    #[test]
59    fn f32_float() {
60        assert!((parses::<f32>("1.5") - 1.5f32).abs() < 1e-6);
61    }
62    #[test]
63    fn i64_negative() {
64        assert_eq!(parses::<i64>("-42"), -42);
65    }
66    #[test]
67    fn i32_negative() {
68        assert_eq!(parses::<i32>("-7"), -7);
69    }
70    #[test]
71    fn f64_negative() {
72        assert_eq!(parses::<f64>("-0.5"), -0.5);
73    }
74    #[test]
75    fn f64_negative_scientific() {
76        assert_eq!(parses::<f64>("-1.0e-3"), -1.0e-3);
77    }
78    #[test]
79    fn u64_rejects_negative() {
80        assert!(u64::parser().parse("-1").into_result().is_err());
81    }
82    #[test]
83    fn bool_true() {
84        assert!(parses::<bool>("true"));
85    }
86    #[test]
87    fn bool_false() {
88        assert!(!parses::<bool>("false"));
89    }
90    #[test]
91    fn string_word() {
92        assert_eq!(parses::<String>("hello"), "hello");
93    }
94
95    #[test]
96    fn string_stops_at_ws() {
97        // Without a trailing end(), Parser::parse() requires consuming all input — so a
98        // String parser given "hello world" fails because " world" is left unconsumed.
99        // Use lazy() / nested combinators for composition; that's not this test's job.
100        let result = String::parser().parse("hello world").into_result();
101        assert!(result.is_err());
102    }
103
104    #[test]
105    fn ident_operand_with_colons() {
106        assert_eq!(
107            ident().parse("AOD0:T1:A").into_result().unwrap(),
108            "AOD0:T1:A"
109        );
110    }
111
112    #[test]
113    fn ident_stops_at_comma() {
114        let result = ident()
115            .then_ignore(chumsky::primitive::just(','))
116            .parse("foo,")
117            .into_result();
118        assert_eq!(result.unwrap(), "foo");
119    }
120
121    #[test]
122    fn ident_allows_dots() {
123        assert_eq!(ident().parse("a.b.c").into_result().unwrap(), "a.b.c");
124    }
125
126    #[test]
127    fn ident_digi_target() {
128        assert_eq!(ident().parse("DIGI:0").into_result().unwrap(), "DIGI:0");
129    }
130
131    #[test]
132    fn ident_rejects_empty() {
133        assert!(ident().parse("").into_result().is_err());
134    }
135
136    #[test]
137    fn ident_rejects_leading_ws() {
138        assert!(ident().parse("  hello").into_result().is_err());
139    }
140
141    #[test]
142    fn ident_stops_at_open_paren() {
143        let result = ident()
144            .then_ignore(chumsky::primitive::just('('))
145            .parse("foo(")
146            .into_result();
147        assert_eq!(result.unwrap(), "foo");
148    }
149
150    #[test]
151    fn ident_stops_at_brace() {
152        let result = ident()
153            .then_ignore(chumsky::primitive::just('{'))
154            .parse("device{")
155            .into_result();
156        assert_eq!(result.unwrap(), "device");
157    }
158}