1mod parsers;
2
3use nom::branch::alt;
4use nom::bytes::complete::tag;
5use nom::character::complete::{alpha1, alphanumeric1, char, digit1, multispace0};
6use nom::combinator::{eof, map, opt, recognize};
7use nom::error::ParseError;
8use nom::multi::{many0, many1};
9use nom::sequence::{delimited, pair, preceded, terminated};
10use nom::IResult;
11
12use crate::ast::{
13 BlockExpression,
14 ConnectionExpression,
15 ConnectionTargetExpression,
16 FlowExpression,
17 FlowProgram,
18 InstancePort,
19 InstanceTarget,
20};
21use crate::Error;
22
23pub static CONNECTION_SEPARATOR: &str = "->";
25
26const DEFAULT_ID: &str = "<>";
28
29#[derive(Debug, Clone, Copy)]
30#[non_exhaustive]
31pub enum ParserError {
33 Fail,
35 UnexpectedToken,
37}
38
39impl std::error::Error for ParserError {}
40impl std::fmt::Display for ParserError {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 match self {
43 ParserError::Fail => write!(f, "Parse failed"),
44 ParserError::UnexpectedToken => write!(f, "Unexpected token"),
45 }
46 }
47}
48
49pub fn parse(input: &str) -> Result<FlowProgram, ParserError> {
51 _parse(input).map(|(_, t)| t).map_err(|_| ParserError::Fail)
52}
53
54fn component_id(input: &str) -> IResult<&str, InstanceTarget> {
55 let (i, t) = recognize(alt((reserved_component_id, identifier)))(input)?;
56 let t = match t {
57 super::SCHEMATIC_INPUT => InstanceTarget::Input,
58 super::SCHEMATIC_OUTPUT => InstanceTarget::Output,
59 super::SCHEMATIC_NULL | "drop" => InstanceTarget::Null(None),
60 super::CORE_ID => InstanceTarget::Core,
61 DEFAULT_ID => InstanceTarget::Default,
62 super::NS_LINK => InstanceTarget::Link,
63 name => InstanceTarget::Named(name.to_owned()),
64 };
65 Ok((i, t))
66}
67
68pub(crate) fn identifier(input: &str) -> IResult<&str, &str> {
69 recognize(pair(alt((alpha1, tag("_"))), many0(alt((alphanumeric1, tag("_"))))))(input)
70}
71
72fn reserved_component_id(input: &str) -> IResult<&str, &str> {
73 alt((
74 tag(super::SCHEMATIC_INPUT),
75 tag(super::SCHEMATIC_OUTPUT),
76 tag(super::SCHEMATIC_NULL),
77 tag("drop"),
78 tag(super::CORE_ID),
79 tag(super::NS_LINK),
80 tag(DEFAULT_ID),
81 ))(input)
82}
83
84fn operation_path(input: &str) -> IResult<&str, &str> {
85 recognize(pair(many1(terminated(identifier, tag("::"))), identifier))(input)
86}
87
88fn inline_id(input: &str) -> IResult<&str, &str> {
89 delimited(char('['), identifier, char(']'))(input)
90}
91
92fn component_path(input: &str) -> IResult<&str, InstanceTarget> {
93 let (i, (path_parts, id)) = pair(operation_path, opt(inline_id))(input)?;
94 Ok((
95 i,
96 id.map_or_else(
97 || InstanceTarget::anonymous_path(path_parts),
98 |id| InstanceTarget::path(path_parts, id),
99 ),
100 ))
101}
102
103fn instance(input: &str) -> IResult<&str, InstanceTarget> {
104 alt((component_path, component_id))(input)
105}
106
107pub(crate) fn instance_port(input: &str) -> IResult<&str, InstancePort> {
108 let (i, (name, parts)) = pair(
109 identifier,
110 many0(preceded(
111 char('.'),
112 alt((
113 map(identifier, |r: &str| r.to_owned()),
114 map(digit1, |r: &str| r.to_owned()),
115 parsers::parse_string,
116 )),
117 )),
118 )(input)?;
119 if parts.is_empty() {
120 Ok((i, InstancePort::Named(name.to_owned())))
121 } else {
122 Ok((i, InstancePort::Path(name.to_owned(), parts.into_iter().collect())))
123 }
124}
125
126fn connection_target_expression(input: &str) -> IResult<&str, (InstanceTarget, InstancePort)> {
127 pair(terminated(instance, char('.')), instance_port)(input).map(|(i, v)| (i, (v.0, v.1)))
128}
129
130fn portless_target_expression(input: &str) -> IResult<&str, (InstanceTarget, InstancePort)> {
131 instance(input).map(|(i, v)| (i, (v, InstancePort::None)))
132}
133
134fn connection_expression_sequence(input: &str) -> IResult<&str, FlowExpression> {
135 let (i, (from, hops)) = pair(
136 alt((connection_target_expression, portless_target_expression)),
137 many1(preceded(
138 ws(tag(CONNECTION_SEPARATOR)),
139 ws(alt((connection_target_expression, portless_target_expression))),
140 )),
141 )(input)?;
142
143 let mut connections = Vec::new();
144
145 let mut last_hop = from;
146 last_hop.0.ensure_id();
147 for mut hop in hops {
148 hop.0.ensure_id();
149 connections.push(FlowExpression::ConnectionExpression(Box::new(connect(
150 last_hop,
151 hop.clone(),
152 ))));
153 last_hop = hop;
154 }
155
156 if connections.len() == 1 {
157 Ok((i, connections.remove(0)))
158 } else {
159 Ok((i, FlowExpression::BlockExpression(BlockExpression::new(connections))))
160 }
161}
162
163fn connect(from: (InstanceTarget, InstancePort), to: (InstanceTarget, InstancePort)) -> ConnectionExpression {
164 let (from, to) = match (from, to) {
165 ((from, InstancePort::None), (to, to_port))
167 if matches!(from, InstanceTarget::Input | InstanceTarget::Default)
168 && matches!(to_port, InstancePort::Named(_) | InstancePort::Path(_, _)) =>
169 {
170 ((from, InstancePort::named(to_port.name().unwrap())), (to, to_port))
171 }
172 ((from, from_port), (to, InstancePort::None))
174 if matches!(to, InstanceTarget::Output | InstanceTarget::Default)
175 && matches!(from_port, InstancePort::Named(_) | InstancePort::Path(_, _)) =>
176 {
177 let to_port = InstancePort::named(from_port.name().unwrap());
178 ((from, from_port), (to, to_port))
179 }
180 x => x,
182 };
183 ConnectionExpression::new(
184 ConnectionTargetExpression::new(from.0, from.1),
185 ConnectionTargetExpression::new(to.0, to.1),
186 )
187}
188
189pub(crate) fn flow_expression(input: &str) -> IResult<&str, FlowExpression> {
190 let (i, expr) = terminated(connection_expression_sequence, alt((eof, ws(tag(";")))))(input)?;
191 Ok((i, expr))
192}
193
194fn _parse(input: &str) -> IResult<&str, FlowProgram> {
195 let (i, expressions) = many0(flow_expression)(input)?;
196
197 Ok((i, FlowProgram::new(expressions)))
198}
199
200fn ws<'a, F, O, E: ParseError<&'a str>>(inner: F) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
201where
202 F: FnMut(&'a str) -> IResult<&'a str, O, E>,
203{
204 delimited(multispace0, inner, multispace0)
205}
206
207pub(crate) fn parse_instance(s: &str) -> Result<InstanceTarget, Error> {
209 let (_, c) = instance(s).map_err(|_e| Error::ComponentIdError(s.to_owned()))?;
210 Ok(c)
211}
212
213#[cfg(test)]
214mod tests {
215
216 use anyhow::Result;
217 use pretty_assertions::assert_eq;
218 use rstest::rstest;
219
220 use super::{
221 ConnectionExpression as CE,
222 ConnectionTargetExpression as CTE,
223 FlowExpression as FE,
224 InstancePort as InstPort,
225 InstanceTarget as InstTgt,
226 *,
227 };
228 use crate::ast::{set_seed, BlockExpression};
229 #[rstest]
232 #[case("<>", InstTgt::Default)]
233 #[case("<input>", InstTgt::Input)]
234 #[case("<output>", InstTgt::Output)]
235 #[case("core", InstTgt::Core)]
236 #[case("heya", InstTgt::named("heya"))]
237 #[case("this::that[A]", InstTgt::path("this::that", "A"))]
238 fn test_component_id(#[case] input: &'static str, #[case] expected: InstTgt) -> Result<()> {
239 let (i, t) = instance(input)?;
240 assert_eq!(t, expected);
241 assert_eq!(i, "");
242 Ok(())
243 }
244
245 #[rstest]
246 #[case("single")]
247 #[case("<input>")]
248 fn test_path_negative(#[case] input: &str) -> Result<()> {
249 assert_err(&operation_path(input));
250 Ok(())
251 }
252
253 #[rstest]
254 #[case("comp::op", "comp::op")]
255 #[case("THIS::That", "THIS::That")]
256 #[allow(clippy::needless_pass_by_value)]
257 fn path_tester(#[case] input: &'static str, #[case] expected: &str) -> Result<()> {
258 let (i, t) = operation_path(input)?;
259 assert_eq!(t, expected);
260 assert_eq!(i, "");
261 Ok(())
262 }
263
264 #[rstest]
265 #[case("comp::op[FOO].foo", (InstTgt::path("comp::op","FOO"), InstPort::named("foo")))]
266 #[case("That.bar", (InstTgt::Named("That".to_owned()), InstPort::named("bar")))]
267 #[case("<>.input", (InstTgt::Default, InstPort::named("input")))]
268 #[case("input.foo", (InstTgt::named("input"), InstPort::named("foo")))]
269 #[case("ref.foo", (InstTgt::named("ref"), InstPort::named("foo")))]
270 #[case("<>.foo", (InstTgt::Default, InstPort::named("foo")))]
271 fn connection_target_expression_tester(
272 #[case] input: &'static str,
273 #[case] expected: (InstTgt, InstancePort),
274 ) -> Result<()> {
275 let (i, t) = connection_target_expression(input)?;
276 assert_eq!(t, expected);
277 assert_eq!(i, "");
278 Ok(())
279 }
280
281 #[rstest]
282 #[case("foo", InstancePort::named("foo"))]
283 #[case("foo.hey", InstancePort::path("foo", vec!["hey".to_owned()]))]
284 #[case("foo.hey.0.this", InstancePort::path("foo", vec!["hey".to_owned(),"0".to_owned(),"this".to_owned()]))]
285 #[case("input.\"Raw String Field #\"", InstancePort::path("input", vec!["Raw String Field #".to_owned()]))]
286 fn test_instance_port(#[case] input: &'static str, #[case] expected: InstancePort) -> Result<()> {
287 let (i, actual) = instance_port(input)?;
288
289 assert_eq!(expected, actual);
290 assert_eq!(i, "");
291 Ok(())
292 }
293
294 #[rstest]
295 #[case("comp::op[INLINE].foo -> <>.output", ((InstTgt::path("comp::op","INLINE"), "foo"),(InstTgt::Default, "output")))]
296 #[case("<> -> ref1.port", ((InstTgt::Default, "port"),(InstTgt::named("ref1"), "port")))]
297 #[case("ref1.in -> ref2.out", ((InstTgt::named("ref1"), "in"),(InstTgt::named("ref2"), "out")))]
298 #[case("<>.in->ref2.out", ((InstTgt::Default, "in"),(InstTgt::named("ref2"), "out")))]
299 #[case("ref1.in-><>.out", ((InstTgt::named("ref1"), "in"),(InstTgt::Default, "out")))]
300 #[case("ref1.port-><>", ((InstTgt::named("ref1"), "port"),(InstTgt::Default, "port")))]
301 #[case("<> -> ref1.port", ((InstTgt::Default, "port"),(InstTgt::named("ref1"), "port")))]
302 #[case("<> -> test::reverse[A].input",((InstTgt::Default, "input"),(InstTgt::path("test::reverse","A"), "input")))]
303 #[case("<>.anything -> drop",((InstTgt::Default, "anything"),(InstTgt::Null(None), InstancePort::None)))]
304 fn connection_parts(
305 #[case] input: &'static str,
306 #[case] expected: ((InstTgt, impl Into<InstancePort>), (InstTgt, impl Into<InstancePort>)),
307 ) -> Result<()> {
308 let (i, t) = connection_expression_sequence(input)?;
309 let expected = FlowExpression::ConnectionExpression(Box::new(CE::new(
310 CTE::new(expected.0 .0, expected.0 .1.into()),
311 CTE::new(expected.1 .0, expected.1 .1.into()),
312 )));
313
314 assert_eq!(t, expected);
315 assert_eq!(i, "");
316 Ok(())
317 }
318
319 fn assert_err<O, E>(item: &Result<O, E>)
320 where
321 E: std::fmt::Debug,
322 O: std::fmt::Debug,
323 {
324 if !item.is_err() {
325 panic!("Expected error, got {:?}", item);
326 } else {
327 }
328 }
329
330 fn flow_expr_conn(from_tgt: InstTgt, from_port: InstPort, to_tgt: InstTgt, to_port: InstPort) -> FE {
331 FE::connection(CE::new(CTE::new(from_tgt, from_port), CTE::new(to_tgt, to_port)))
332 }
333
334 fn flow_block<const K: usize>(seed: u64, hops: impl FnOnce() -> [(InstTgt, InstPort); K]) -> FE {
335 set_seed(seed);
336 let mut connections = Vec::new();
337 let mut last_hop: Option<(InstanceTarget, InstancePort)> = None;
338 let hops = hops();
339 for hop in hops {
340 if let Some(last) = last_hop.take() {
341 last_hop = Some(hop.clone());
342 connections.push(FE::connection(CE::new(
343 CTE::new(last.0, last.1),
344 CTE::new(hop.0, hop.1),
345 )));
346 } else {
347 last_hop = Some(hop);
348 }
349 }
350 set_seed(seed);
351 FE::block(BlockExpression::new(connections))
352 }
353
354 mod rng_limited {
355 use pretty_assertions::assert_eq;
356
357 use super::*;
358
359 #[rstest]
360 #[case(
361 "comp::op[INLINE].foo -> <>.output",
362 flow_expr_conn(
363 InstTgt::path("comp::op", "INLINE"),
364 InstPort::named("foo"),
365 InstTgt::Default,
366 InstPort::named("output")
367 )
368 )]
369 #[case(
370 "this.output.field -> <>.output",
371 flow_expr_conn(InstTgt::named("this"), InstPort::path("output",vec!["field".to_owned()]), InstTgt::Default, InstPort::named("output"))
372 )]
373 #[case(
374 "this.output.field.other.0 -> <>.output",
375 flow_expr_conn(InstTgt::named("this"), InstPort::path("output",vec!["field".to_owned(),"other".to_owned(), "0".to_owned()]), InstTgt::Default, InstPort::named("output"))
376 )]
377 #[case(
378 "this.output.\"field\" -> <>.output",
379 flow_expr_conn(InstTgt::named("this"), InstPort::path("output",vec!["field".to_owned()]), InstTgt::Default, InstPort::named("output"))
380 )]
381 #[case(
382 "this.output.\"field with spaces and \\\" quotes and symbols ,*|#\" -> <>.output",
383 flow_expr_conn(InstTgt::named("this"), InstPort::path("output",vec!["field with spaces and \" quotes and symbols ,*|#".to_owned()]), InstTgt::Default, InstPort::named("output"))
384 )]
385 #[case(
386 "this -> that",
387 flow_expr_conn(InstTgt::named("this"), InstPort::None, InstTgt::named("that"), InstPort::None)
388 )]
389 #[case(
390 "this -> that -> another",
391 flow_block(0,||[
392 (InstTgt::named("this"), InstPort::None),
393 (InstTgt::named("that"), InstPort::None),
394 (InstTgt::named("another"), InstPort::None)
395 ])
396 )]
397 #[case(
398 "<> -> test::reverse -> test::uppercase -> <>",
399 flow_block(0,||[
400 (InstTgt::Input, InstPort::None),
401 (InstTgt::generated_path("test::reverse"), InstPort::None),
402 (InstTgt::generated_path("test::uppercase"), InstPort::None),
403 (InstTgt::Output, InstPort::None)
404 ])
405 )]
406 #[case(
407 "test::in -> test::middle -> test::out",
408 flow_block(0,||[
409 (InstTgt::generated_path("test::in"), InstPort::None),
410 (InstTgt::generated_path("test::middle"), InstPort::None),
411 (InstTgt::generated_path("test::out"), InstPort::None),
412 ])
413 )]
414
415 fn test_flow_expression(#[case] input: &'static str, #[case] expected: FE) -> Result<()> {
416 set_seed(0);
417 let (t, actual) = flow_expression(input)?;
418 println!("expected: {:?}", expected);
419 println!("actual: {:?}", actual);
420 assert_eq!(actual, expected);
421 match actual {
422 FlowExpression::ConnectionExpression(_) => {
423 }
425 FlowExpression::BlockExpression(block) => {
426 let mut last: Option<&ConnectionExpression> = None;
428 let inner = block.inner();
429
430 for expr in inner {
431 let this_con = expr.as_connection().unwrap();
432 if let Some(last) = last {
433 assert_eq!(last.to(), this_con.from());
435 assert_ne!(last.to().instance(), this_con.to().instance());
437 }
438 last = Some(this_con);
439 }
440 }
441 }
442
443 assert_eq!(t, "");
444 Ok(())
445 }
446 }
447}