1pub mod impls;
5
6pub use impls::{bare_token, ident, BareToken, Ident, QuotedString};
7
8use chumsky::error::Simple;
9use chumsky::extra;
10
11pub trait SurfaceInstruction {}
17
18pub trait Parse<'src>: Sized {
23 fn parser() -> impl chumsky::Parser<'src, &'src str, Self, extra::Err<Simple<'src, char>>>;
24}
25
26#[cfg(test)]
27mod tests {
28 use super::*;
29 use chumsky::Parser;
30
31 fn parses<'src, T: Parse<'src>>(input: &'src str) -> T {
32 T::parser().parse(input).into_result().unwrap()
33 }
34
35 #[test]
36 fn i64_basic() {
37 assert_eq!(parses::<i64>("42"), 42);
38 }
39 #[test]
40 fn i32_basic() {
41 assert_eq!(parses::<i32>("7"), 7);
42 }
43 #[test]
44 fn u64_basic() {
45 assert_eq!(parses::<u64>("100"), 100);
46 }
47 #[test]
48 fn u32_basic() {
49 assert_eq!(parses::<u32>("0"), 0);
50 }
51 #[test]
52 fn usize_basic() {
53 assert_eq!(parses::<usize>("9"), 9);
54 }
55 #[test]
56 fn f64_int() {
57 assert_eq!(parses::<f64>("3"), 3.0);
58 }
59 #[test]
60 #[allow(clippy::approx_constant)]
61 fn f64_float() {
62 assert_eq!(parses::<f64>("3.14"), 3.14);
63 }
64 #[test]
65 fn f32_float() {
66 assert!((parses::<f32>("1.5") - 1.5f32).abs() < 1e-6);
67 }
68 #[test]
69 fn i64_negative() {
70 assert_eq!(parses::<i64>("-42"), -42);
71 }
72 #[test]
73 fn i32_negative() {
74 assert_eq!(parses::<i32>("-7"), -7);
75 }
76 #[test]
77 fn f64_negative() {
78 assert_eq!(parses::<f64>("-0.5"), -0.5);
79 }
80 #[test]
81 fn f64_negative_scientific() {
82 assert_eq!(parses::<f64>("-1.0e-3"), -1.0e-3);
83 }
84 #[test]
85 fn u64_rejects_negative() {
86 assert!(u64::parser().parse("-1").into_result().is_err());
87 }
88 #[test]
89 fn bool_true() {
90 assert!(parses::<bool>("true"));
91 }
92 #[test]
93 fn bool_false() {
94 assert!(!parses::<bool>("false"));
95 }
96 #[test]
97 fn ident_word() {
98 assert_eq!(parses::<Ident>("hello"), Ident("hello".to_owned()));
99 }
100
101 #[test]
102 fn ident_stops_at_whitespace() {
103 let result = Ident::parser().parse("hello world").into_result();
107 assert!(result.is_err());
108 }
109
110 #[test]
111 fn ident_operand_with_colons() {
112 assert_eq!(
113 ident().parse("AOD0:T1:A").into_result().unwrap(),
114 "AOD0:T1:A"
115 );
116 }
117
118 #[test]
119 fn ident_stops_at_comma() {
120 let result = ident()
121 .then_ignore(chumsky::primitive::just(','))
122 .parse("foo,")
123 .into_result();
124 assert_eq!(result.unwrap(), "foo");
125 }
126
127 #[test]
128 fn ident_allows_dots() {
129 assert_eq!(ident().parse("a.b.c").into_result().unwrap(), "a.b.c");
130 }
131
132 #[test]
133 fn ident_digi_target() {
134 assert_eq!(ident().parse("DIGI:0").into_result().unwrap(), "DIGI:0");
135 }
136
137 #[test]
138 fn ident_rejects_symbol_sigil_and_quote_characters() {
139 assert!(Ident::parser().parse("@target").has_errors());
140 assert!(Ident::parser().parse("\"target\"").has_errors());
141 assert!(Ident::parser().parse("'target").has_errors());
142 assert!(Ident::parser().parse("`target`").has_errors());
143 }
144
145 #[test]
146 fn bare_token_accepts_symbol_sigil_but_rejects_quotes() {
147 assert_eq!(
148 parses::<BareToken>("@target"),
149 BareToken("@target".to_owned())
150 );
151 assert!(BareToken::parser().parse("\"target\"").has_errors());
152 }
153
154 #[test]
155 fn ident_rejects_empty() {
156 assert!(ident().parse("").into_result().is_err());
157 }
158
159 #[test]
160 fn ident_rejects_leading_ws() {
161 assert!(ident().parse(" hello").into_result().is_err());
162 }
163
164 #[test]
165 fn ident_stops_at_open_paren() {
166 let result = ident()
167 .then_ignore(chumsky::primitive::just('('))
168 .parse("foo(")
169 .into_result();
170 assert_eq!(result.unwrap(), "foo");
171 }
172
173 #[test]
174 fn ident_stops_at_brace() {
175 let result = ident()
176 .then_ignore(chumsky::primitive::just('{'))
177 .parse("device{")
178 .into_result();
179 assert_eq!(result.unwrap(), "device");
180 }
181
182 #[test]
183 fn quoted_string_supports_spaces_and_escapes() {
184 assert_eq!(
185 parses::<QuotedString>("\"hello\\nworld\""),
186 QuotedString("hello\nworld".to_owned())
187 );
188 }
189
190 #[test]
191 fn lexical_newtypes_expose_owned_and_borrowed_text() {
192 let ident = Ident("target".to_owned());
193 assert_eq!(ident.as_str(), "target");
194 assert_eq!(ident.to_string(), "target");
195 assert_eq!(String::from(ident), "target");
196 }
197
198 #[test]
199 fn vec_uses_square_brackets_and_commas() {
200 assert_eq!(parses::<Vec<f64>>("[1.0, 2.5]"), vec![1.0, 2.5]);
201 assert!(parses::<Vec<f64>>("[]").is_empty());
202 }
203
204 #[test]
205 fn tuple_uses_parentheses_and_a_comma() {
206 assert_eq!(parses::<(i64, f64)>("(1, 2.5)"), (1, 2.5));
207 }
208
209 #[test]
210 fn vec_supports_nested_tuple_items() {
211 assert_eq!(
212 parses::<Vec<(f64, f64)>>("[(1.0, 2.0), (3.0, 4.0)]"),
213 vec![(1.0, 2.0), (3.0, 4.0)]
214 );
215 }
216}