1use crate::error::{AltiumError, Result};
18
19use super::pattern::Pattern;
20use super::selector::{
21 Combinator, FilterOperator, FilterValue, NetConnectedTarget, PropertyFilter, PseudoSelector,
22 RecordMatcher, RecordType, Selector, SelectorChain, SelectorSegment,
23};
24
25pub struct SelectorParser<'a> {
27 input: &'a str,
28 pos: usize,
29}
30
31impl<'a> SelectorParser<'a> {
32 pub fn new(input: &'a str) -> Self {
34 Self {
35 input: input.trim(),
36 pos: 0,
37 }
38 }
39
40 pub fn parse(mut self) -> Result<Selector> {
42 if self.input.is_empty() {
43 return Ok(Selector::any());
44 }
45
46 let mut alternatives = vec![];
47
48 loop {
49 self.skip_whitespace();
50 if self.is_at_end() {
51 break;
52 }
53
54 let chain = self.parse_chain()?;
55 alternatives.push(chain);
56
57 self.skip_whitespace();
58 if self.peek() == Some(',') {
59 self.advance();
60 self.skip_whitespace();
61 } else {
62 break;
63 }
64 }
65
66 if alternatives.is_empty() {
67 Ok(Selector::any())
68 } else {
69 Ok(Selector { alternatives })
70 }
71 }
72
73 fn parse_chain(&mut self) -> Result<SelectorChain> {
75 let mut segments = vec![];
76
77 loop {
78 self.skip_whitespace();
79 if self.is_at_end() || self.peek() == Some(',') {
80 break;
81 }
82
83 let mut segment = self.parse_segment()?;
84
85 self.skip_whitespace();
87 if let Some(combinator) = self.try_parse_combinator() {
88 segment.combinator = Some(combinator);
89 self.skip_whitespace();
90 }
91
92 segments.push(segment);
93
94 if segments.last().unwrap().combinator.is_none() {
96 let next = self.peek();
98 let could_continue = next
99 .map(|c| {
100 c.is_alphanumeric()
101 || c == '$'
102 || c == '~'
103 || c == '@'
104 || c == '#'
105 || c == '*'
106 || c == '['
107 || c == ':'
108 })
109 .unwrap_or(false);
110
111 if could_continue {
112 segments.last_mut().unwrap().combinator = Some(Combinator::Descendant);
114 } else {
115 break;
116 }
117 }
118 }
119
120 Ok(SelectorChain { segments })
121 }
122
123 fn parse_segment(&mut self) -> Result<SelectorSegment> {
125 self.skip_whitespace();
126
127 let matcher = self.parse_matcher()?;
129
130 let mut filters = vec![];
132 while self.peek() == Some('[') {
133 filters.push(self.parse_property_filter()?);
134 }
135
136 let mut pseudo = vec![];
138 while self.peek() == Some(':') {
139 let saved_pos = self.pos;
141 self.advance(); if let Some(c) = self.peek() {
145 if c.is_alphabetic() {
146 let name = self.parse_identifier_chars();
147 if let Some(ps) = PseudoSelector::from_name(&name) {
148 pseudo.push(ps);
149 continue;
150 } else if name == "not" || name == "has" || name == "is" {
151 self.expect('(')?;
153 let inner = self.parse_until_char(')')?;
154 self.expect(')')?;
155 let inner_selector = SelectorParser::new(&inner).parse()?;
156 let ps = match name.as_str() {
157 "not" => PseudoSelector::Not(Box::new(inner_selector)),
158 "has" => PseudoSelector::Has(Box::new(inner_selector)),
159 "is" => PseudoSelector::Is(Box::new(inner_selector)),
160 _ => unreachable!(),
161 };
162 pseudo.push(ps);
163 continue;
164 } else if name == "nth-child" || name == "nthchild" {
165 self.expect('(')?;
166 let n_str = self.parse_until_char(')')?;
167 self.expect(')')?;
168 let n: usize = n_str.trim().parse().map_err(|_| {
169 AltiumError::Parse(format!("Invalid nth-child value: {}", n_str))
170 })?;
171 pseudo.push(PseudoSelector::NthChild(n));
172 continue;
173 }
174 }
175 }
176
177 self.pos = saved_pos;
179 break;
180 }
181
182 Ok(SelectorSegment {
183 matcher,
184 filters,
185 pseudo,
186 combinator: None,
187 })
188 }
189
190 fn parse_matcher(&mut self) -> Result<RecordMatcher> {
192 match self.peek() {
193 Some('$') => {
194 self.advance();
196 let pattern = self.parse_pattern()?;
197 Ok(RecordMatcher::PartNumber(pattern))
198 }
199 Some('~') => {
200 self.advance();
202 let net_pattern = self.parse_pattern()?;
203
204 if self.peek() == Some(':') {
206 let saved_pos = self.pos;
207 self.advance();
208 let target_name = self.parse_identifier_chars();
209
210 match target_name.to_lowercase().as_str() {
211 "pins" => Ok(RecordMatcher::NetConnected {
212 net: net_pattern,
213 target: NetConnectedTarget::Pins,
214 }),
215 "components" => Ok(RecordMatcher::NetConnected {
216 net: net_pattern,
217 target: NetConnectedTarget::Components,
218 }),
219 _ => {
220 self.pos = saved_pos;
222 Ok(RecordMatcher::Net(net_pattern))
223 }
224 }
225 } else {
226 Ok(RecordMatcher::Net(net_pattern))
227 }
228 }
229 Some('@') => {
230 self.advance();
232 let pattern = self.parse_pattern()?;
233 Ok(RecordMatcher::Value(pattern))
234 }
235 Some('#') => {
236 self.advance();
238 let pattern = self.parse_pattern()?;
239 Ok(RecordMatcher::Sheet(pattern))
240 }
241 Some('*') => {
242 self.advance();
244 Ok(RecordMatcher::Any)
245 }
246 Some('[') | Some(':') => {
247 Ok(RecordMatcher::Any)
249 }
250 _ => {
251 let ident = self.parse_identifier()?;
253
254 if let Some(record_type) = RecordType::try_parse(&ident) {
256 return Ok(RecordMatcher::Type(record_type));
257 }
258
259 let designator_pattern = Pattern::new(&ident)?;
261
262 if self.peek() == Some(':') {
264 let saved_pos = self.pos;
265 self.advance();
266
267 if let Some(c) = self.peek() {
269 if c.is_alphanumeric() || c == '*' || c == '?' || c == '[' {
270 let pin_ident = self.parse_identifier()?;
271
272 if PseudoSelector::from_name(&pin_ident).is_some()
274 || pin_ident == "not"
275 || pin_ident == "has"
276 || pin_ident == "is"
277 || pin_ident.starts_with("nth")
278 {
279 self.pos = saved_pos;
281 return Ok(RecordMatcher::Designator(designator_pattern));
282 }
283
284 let pin_pattern = Pattern::new(&pin_ident)?;
285 return Ok(RecordMatcher::Pin {
286 component: designator_pattern,
287 pin: pin_pattern,
288 });
289 }
290 }
291 self.pos = saved_pos;
293 }
294
295 if self.peek() == Some('@') {
297 self.advance();
298 let value_pattern = self.parse_pattern()?;
299 return Ok(RecordMatcher::DesignatorWithValue {
300 designator: designator_pattern,
301 value: value_pattern,
302 });
303 }
304
305 Ok(RecordMatcher::Designator(designator_pattern))
306 }
307 }
308 }
309
310 fn parse_property_filter(&mut self) -> Result<PropertyFilter> {
312 self.expect('[')?;
313 self.skip_whitespace();
314
315 let property = self.parse_identifier()?;
317 self.skip_whitespace();
318
319 let operator = self.parse_operator()?;
321 self.skip_whitespace();
322
323 let value = self.parse_filter_value()?;
325 self.skip_whitespace();
326
327 self.expect(']')?;
328
329 Ok(PropertyFilter {
330 property,
331 operator,
332 value,
333 })
334 }
335
336 fn parse_operator(&mut self) -> Result<FilterOperator> {
338 let two_char = self.peek_n(2);
340 if let Some(op) = two_char.and_then(|s| FilterOperator::try_parse(&s)) {
341 self.advance();
342 self.advance();
343 return Ok(op);
344 }
345
346 if let Some(c) = self.peek() {
348 let s = c.to_string();
349 if let Some(op) = FilterOperator::try_parse(&s) {
350 self.advance();
351 return Ok(op);
352 }
353 }
354
355 Err(AltiumError::Parse(format!(
356 "Expected operator at position {}",
357 self.pos
358 )))
359 }
360
361 fn parse_filter_value(&mut self) -> Result<FilterValue> {
363 match self.peek() {
364 Some('"') | Some('\'') => {
365 let quote = self.advance().unwrap();
367 let value = self.parse_until_char(quote)?;
368 self.expect(quote)?;
369 Ok(FilterValue::String(value))
370 }
371 Some(c) if c.is_ascii_digit() || c == '-' || c == '.' => {
372 let num_str = self.parse_number_str();
374 let num: f64 = num_str
375 .parse()
376 .map_err(|_| AltiumError::Parse(format!("Invalid number: {}", num_str)))?;
377 Ok(FilterValue::Number(num))
378 }
379 _ => {
380 let value = self.parse_until_chars(&[']', ' ', '\t']);
382 if value.contains(['*', '?', '[']) {
383 Ok(FilterValue::Pattern(Pattern::new(&value)?))
384 } else if value.eq_ignore_ascii_case("true") {
385 Ok(FilterValue::Bool(true))
386 } else if value.eq_ignore_ascii_case("false") {
387 Ok(FilterValue::Bool(false))
388 } else {
389 Ok(FilterValue::String(value))
390 }
391 }
392 }
393 }
394
395 fn try_parse_combinator(&mut self) -> Option<Combinator> {
397 match self.peek() {
398 Some('>') => {
399 self.advance();
400 Some(Combinator::DirectChild)
401 }
402 Some('/') => {
403 self.advance();
404 Some(Combinator::DirectChild)
405 }
406 _ => None,
407 }
408 }
409
410 fn parse_pattern(&mut self) -> Result<Pattern> {
412 let ident = self.parse_identifier()?;
413 Pattern::new(&ident)
414 }
415
416 fn parse_identifier(&mut self) -> Result<String> {
418 let ident = self.parse_identifier_chars();
419 if ident.is_empty() {
420 Err(AltiumError::Parse(format!(
421 "Expected identifier at position {}",
422 self.pos
423 )))
424 } else {
425 Ok(ident)
426 }
427 }
428
429 fn parse_identifier_chars(&mut self) -> String {
432 let start = self.pos;
433 while let Some(c) = self.peek() {
434 if c.is_alphanumeric() || c == '_' || c == '-' || c == '*' || c == '?' || c == '.' {
435 self.advance();
436 } else if c == '[' {
437 let saved_pos = self.pos;
440 self.advance(); let mut found_close = false;
443 let mut looks_like_filter = false;
444
445 while let Some(inner_c) = self.peek() {
446 if inner_c == ']' {
447 self.advance();
448 found_close = true;
449 break;
450 } else if inner_c == '=' || inner_c == '>' || inner_c == '<' {
451 looks_like_filter = true;
453 break;
454 } else if inner_c == '[' || inner_c == ' ' || inner_c == '\t' {
455 looks_like_filter = true;
457 break;
458 }
459 self.advance();
460 }
461
462 if !found_close || looks_like_filter {
463 self.pos = saved_pos;
465 break;
466 }
467 } else {
469 break;
470 }
471 }
472 self.input[start..self.pos].to_string()
473 }
474
475 fn parse_number_str(&mut self) -> String {
477 let start = self.pos;
478 while let Some(c) = self.peek() {
479 if c.is_ascii_digit() || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E' {
480 self.advance();
481 } else {
482 break;
483 }
484 }
485 self.input[start..self.pos].to_string()
486 }
487
488 fn parse_until_char(&mut self, end: char) -> Result<String> {
490 let start = self.pos;
491 let mut depth = 0;
492 while let Some(c) = self.peek() {
493 if c == '(' {
494 depth += 1;
495 } else if c == ')' {
496 if depth == 0 && end == ')' {
497 break;
498 }
499 depth -= 1;
500 } else if c == end && depth == 0 {
501 break;
502 }
503 self.advance();
504 }
505 Ok(self.input[start..self.pos].to_string())
506 }
507
508 fn parse_until_chars(&mut self, ends: &[char]) -> String {
510 let start = self.pos;
511 while let Some(c) = self.peek() {
512 if ends.contains(&c) {
513 break;
514 }
515 self.advance();
516 }
517 self.input[start..self.pos].to_string()
518 }
519
520 fn expect(&mut self, expected: char) -> Result<()> {
522 match self.peek() {
523 Some(c) if c == expected => {
524 self.advance();
525 Ok(())
526 }
527 Some(c) => Err(AltiumError::Parse(format!(
528 "Expected '{}' but found '{}' at position {}",
529 expected, c, self.pos
530 ))),
531 None => Err(AltiumError::Parse(format!(
532 "Expected '{}' but reached end of input",
533 expected
534 ))),
535 }
536 }
537
538 fn skip_whitespace(&mut self) {
540 while let Some(c) = self.peek() {
541 if c.is_whitespace() {
542 self.advance();
543 } else {
544 break;
545 }
546 }
547 }
548
549 fn peek(&self) -> Option<char> {
551 self.input[self.pos..].chars().next()
552 }
553
554 fn peek_n(&self, n: usize) -> Option<String> {
556 let remaining = &self.input[self.pos..];
557 if remaining.len() >= n {
558 Some(remaining[..n].to_string())
559 } else {
560 None
561 }
562 }
563
564 fn advance(&mut self) -> Option<char> {
566 let c = self.peek();
567 if let Some(ch) = c {
568 self.pos += ch.len_utf8();
569 }
570 c
571 }
572
573 fn is_at_end(&self) -> bool {
575 self.pos >= self.input.len()
576 }
577}
578
579pub fn parse(input: &str) -> Result<Selector> {
581 SelectorParser::new(input).parse()
582}
583
584#[cfg(test)]
585mod tests {
586 use super::*;
587
588 #[test]
589 fn test_parse_designator() {
590 let sel = parse("U1").unwrap();
591 assert_eq!(sel.alternatives.len(), 1);
592 let chain = &sel.alternatives[0];
593 assert_eq!(chain.segments.len(), 1);
594 match &chain.segments[0].matcher {
595 RecordMatcher::Designator(p) => assert_eq!(p.as_str(), "U1"),
596 _ => panic!("Expected Designator"),
597 }
598 }
599
600 #[test]
601 fn test_parse_designator_wildcard() {
602 let sel = parse("R*").unwrap();
603 match &sel.alternatives[0].segments[0].matcher {
604 RecordMatcher::Designator(p) => {
605 assert_eq!(p.as_str(), "R*");
606 assert!(!p.is_literal());
607 }
608 _ => panic!("Expected Designator"),
609 }
610 }
611
612 #[test]
613 fn test_parse_pin() {
614 let sel = parse("U1:3").unwrap();
615 match &sel.alternatives[0].segments[0].matcher {
616 RecordMatcher::Pin { component, pin } => {
617 assert_eq!(component.as_str(), "U1");
618 assert_eq!(pin.as_str(), "3");
619 }
620 _ => panic!("Expected Pin"),
621 }
622 }
623
624 #[test]
625 fn test_parse_pin_by_name() {
626 let sel = parse("U1:VCC").unwrap();
627 match &sel.alternatives[0].segments[0].matcher {
628 RecordMatcher::Pin { component, pin } => {
629 assert_eq!(component.as_str(), "U1");
630 assert_eq!(pin.as_str(), "VCC");
631 }
632 _ => panic!("Expected Pin"),
633 }
634 }
635
636 #[test]
637 fn test_parse_part_number() {
638 let sel = parse("$LM358").unwrap();
639 match &sel.alternatives[0].segments[0].matcher {
640 RecordMatcher::PartNumber(p) => assert_eq!(p.as_str(), "LM358"),
641 _ => panic!("Expected PartNumber"),
642 }
643 }
644
645 #[test]
646 fn test_parse_net() {
647 let sel = parse("~VCC").unwrap();
648 match &sel.alternatives[0].segments[0].matcher {
649 RecordMatcher::Net(p) => assert_eq!(p.as_str(), "VCC"),
650 _ => panic!("Expected Net"),
651 }
652 }
653
654 #[test]
655 fn test_parse_net_pins() {
656 let sel = parse("~VCC:pins").unwrap();
657 match &sel.alternatives[0].segments[0].matcher {
658 RecordMatcher::NetConnected { net, target } => {
659 assert_eq!(net.as_str(), "VCC");
660 assert_eq!(*target, NetConnectedTarget::Pins);
661 }
662 _ => panic!("Expected NetConnected"),
663 }
664 }
665
666 #[test]
667 fn test_parse_value() {
668 let sel = parse("@10K").unwrap();
669 match &sel.alternatives[0].segments[0].matcher {
670 RecordMatcher::Value(p) => assert_eq!(p.as_str(), "10K"),
671 _ => panic!("Expected Value"),
672 }
673 }
674
675 #[test]
676 fn test_parse_sheet() {
677 let sel = parse("#Power").unwrap();
678 match &sel.alternatives[0].segments[0].matcher {
679 RecordMatcher::Sheet(p) => assert_eq!(p.as_str(), "Power"),
680 _ => panic!("Expected Sheet"),
681 }
682 }
683
684 #[test]
685 fn test_parse_designator_with_value() {
686 let sel = parse("R*@10K").unwrap();
687 match &sel.alternatives[0].segments[0].matcher {
688 RecordMatcher::DesignatorWithValue { designator, value } => {
689 assert_eq!(designator.as_str(), "R*");
690 assert_eq!(value.as_str(), "10K");
691 }
692 _ => panic!("Expected DesignatorWithValue"),
693 }
694 }
695
696 #[test]
697 fn test_parse_type() {
698 let sel = parse("component").unwrap();
699 match &sel.alternatives[0].segments[0].matcher {
700 RecordMatcher::Type(t) => assert_eq!(*t, RecordType::Component),
701 _ => panic!("Expected Type"),
702 }
703 }
704
705 #[test]
706 fn test_parse_property_filter() {
707 let sel = parse("U*[rotation=90]").unwrap();
708 let segment = &sel.alternatives[0].segments[0];
709 assert_eq!(segment.filters.len(), 1);
710 assert_eq!(segment.filters[0].property, "rotation");
711 assert!(matches!(segment.filters[0].operator, FilterOperator::Equal));
712 }
713
714 #[test]
715 fn test_parse_pseudo_selector() {
716 let sel = parse("U*:unconnected").unwrap();
717 let segment = &sel.alternatives[0].segments[0];
718 assert_eq!(segment.pseudo.len(), 1);
719 assert!(matches!(segment.pseudo[0], PseudoSelector::Unconnected));
720 }
721
722 #[test]
723 fn test_parse_union() {
724 let sel = parse("R*, C*").unwrap();
725 assert_eq!(sel.alternatives.len(), 2);
726 }
727
728 #[test]
729 fn test_parse_child_combinator() {
730 let sel = parse("U1 > pin").unwrap();
731 let chain = &sel.alternatives[0];
732 assert_eq!(chain.segments.len(), 2);
733 assert!(matches!(
734 chain.segments[0].combinator,
735 Some(Combinator::DirectChild)
736 ));
737 }
738
739 #[test]
740 fn test_parse_slash_combinator() {
741 let sel = parse("U1/pin").unwrap();
742 let chain = &sel.alternatives[0];
743 assert_eq!(chain.segments.len(), 2);
744 assert!(matches!(
745 chain.segments[0].combinator,
746 Some(Combinator::DirectChild)
747 ));
748 }
749
750 #[test]
751 fn test_parse_descendant_combinator() {
752 let sel = parse("U1 pin").unwrap();
753 let chain = &sel.alternatives[0];
754 assert_eq!(chain.segments.len(), 2);
755 assert!(matches!(
756 chain.segments[0].combinator,
757 Some(Combinator::Descendant)
758 ));
759 }
760
761 #[test]
762 fn test_parse_complex() {
763 let sel = parse("U*:output:unconnected").unwrap();
764 let segment = &sel.alternatives[0].segments[0];
765 match &segment.matcher {
766 RecordMatcher::Designator(p) => assert_eq!(p.as_str(), "U*"),
767 _ => panic!("Expected Designator"),
768 }
769 assert_eq!(segment.pseudo.len(), 2);
770 }
771}