Skip to main content

altium_format/query/
ast.rs

1//! Abstract Syntax Tree types for the query language.
2
3use super::common::{FilterOp, FilterValue, compare_filter};
4
5/// Parsed query AST node
6#[derive(Debug, Clone, PartialEq)]
7pub enum Selector {
8    /// Element type selector: `component`, `pin`, `net`, etc.
9    Element(ElementType),
10
11    /// ID selector: `#U1`, `#VCC`
12    Id(String),
13
14    /// Universal selector: `*`
15    Universal,
16
17    /// Attribute selector: `[attr=value]`
18    Attribute(AttributeSelector),
19
20    /// Pseudo-selector: `:connected`, `:power`
21    Pseudo(PseudoSelector),
22
23    /// Compound selector (multiple conditions on same element)
24    Compound(Vec<Selector>),
25
26    /// Combinator: `A B`, `A > B`, `A >> B`
27    Combinator {
28        left: Box<Selector>,
29        combinator: CombinatorType,
30        right: Box<Selector>,
31    },
32
33    /// Union: `A, B`
34    Union(Vec<Selector>),
35
36    /// Negation: `:not(A)`
37    Not(Box<Selector>),
38
39    /// Has child: `:has(A)`
40    Has(Box<Selector>),
41}
42
43impl Selector {
44    /// Check if this selector has any result modifiers (count, limit, etc.)
45    pub fn has_result_modifier(&self) -> bool {
46        match self {
47            Selector::Pseudo(p) => p.is_result_modifier(),
48            Selector::Compound(parts) => parts.iter().any(|s| s.has_result_modifier()),
49            Selector::Combinator { right, .. } => right.has_result_modifier(),
50            Selector::Union(selectors) => selectors.iter().any(|s| s.has_result_modifier()),
51            _ => false,
52        }
53    }
54
55    /// Extract result modifiers from this selector
56    pub fn get_result_modifiers(&self) -> Vec<&PseudoSelector> {
57        let mut modifiers = Vec::new();
58        self.collect_result_modifiers(&mut modifiers);
59        modifiers
60    }
61
62    fn collect_result_modifiers<'a>(&'a self, modifiers: &mut Vec<&'a PseudoSelector>) {
63        match self {
64            Selector::Pseudo(p) if p.is_result_modifier() => {
65                modifiers.push(p);
66            }
67            Selector::Compound(parts) => {
68                for part in parts {
69                    part.collect_result_modifiers(modifiers);
70                }
71            }
72            Selector::Combinator { right, .. } => {
73                right.collect_result_modifiers(modifiers);
74            }
75            Selector::Union(selectors) => {
76                for sel in selectors {
77                    sel.collect_result_modifiers(modifiers);
78                }
79            }
80            _ => {}
81        }
82    }
83}
84
85/// Element types that can be queried
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
87pub enum ElementType {
88    /// Component instance (U1, R1, C1, etc.)
89    Component,
90    /// Pin on a component
91    Pin,
92    /// Electrical net (computed from connectivity)
93    Net,
94    /// Inter-sheet port
95    Port,
96    /// Wire segment
97    Wire,
98    /// Power symbol (VCC, +5V, etc.)
99    Power,
100    /// Ground symbol (subset of power with ground style)
101    Ground,
102    /// Net label
103    Label,
104    /// Junction point
105    Junction,
106    /// Component parameter (value, footprint, etc.)
107    Parameter,
108    /// Designator record
109    Designator,
110    /// Sheet/document level
111    Sheet,
112}
113
114impl ElementType {
115    /// Parse element type from string
116    pub fn try_parse(s: &str) -> Option<Self> {
117        match s.to_lowercase().as_str() {
118            "component" | "comp" | "c" => Some(ElementType::Component),
119            "pin" | "p" => Some(ElementType::Pin),
120            "net" | "n" => Some(ElementType::Net),
121            "port" => Some(ElementType::Port),
122            "wire" | "w" => Some(ElementType::Wire),
123            "power" | "pwr" => Some(ElementType::Power),
124            "ground" | "gnd" => Some(ElementType::Ground),
125            "label" | "netlabel" => Some(ElementType::Label),
126            "junction" | "junc" => Some(ElementType::Junction),
127            "parameter" | "param" => Some(ElementType::Parameter),
128            "designator" | "des" => Some(ElementType::Designator),
129            "sheet" | "document" | "doc" => Some(ElementType::Sheet),
130            _ => None,
131        }
132    }
133
134    /// Get canonical string representation
135    pub fn as_str(&self) -> &'static str {
136        match self {
137            ElementType::Component => "component",
138            ElementType::Pin => "pin",
139            ElementType::Net => "net",
140            ElementType::Port => "port",
141            ElementType::Wire => "wire",
142            ElementType::Power => "power",
143            ElementType::Ground => "ground",
144            ElementType::Label => "label",
145            ElementType::Junction => "junction",
146            ElementType::Parameter => "parameter",
147            ElementType::Designator => "designator",
148            ElementType::Sheet => "sheet",
149        }
150    }
151}
152
153/// Attribute comparison operators
154#[derive(Debug, Clone, Copy, PartialEq, Eq)]
155pub enum AttributeOp {
156    /// `[attr]` - Has attribute
157    Exists,
158    /// `[attr=value]` - Exact match
159    Equals,
160    /// `[attr!=value]` - Not equal
161    NotEquals,
162    /// `[attr~=value]` - Word match (space-separated list)
163    WordMatch,
164    /// `[attr^=value]` - Starts with
165    StartsWith,
166    /// `[attr$=value]` - Ends with
167    EndsWith,
168    /// `[attr*=value]` - Contains
169    Contains,
170    /// `[attr>value]` - Greater than (numeric)
171    GreaterThan,
172    /// `[attr<value]` - Less than (numeric)
173    LessThan,
174    /// `[attr>=value]` - Greater or equal
175    GreaterOrEqual,
176    /// `[attr<=value]` - Less or equal
177    LessOrEqual,
178}
179
180/// Attribute selector
181#[derive(Debug, Clone, PartialEq)]
182pub struct AttributeSelector {
183    /// Attribute name (normalized to lowercase)
184    pub name: String,
185    /// Comparison operator
186    pub op: AttributeOp,
187    /// Value to compare (None for Exists)
188    pub value: Option<String>,
189    /// Case insensitive flag
190    pub case_insensitive: bool,
191}
192
193impl AttributeSelector {
194    /// Create a new attribute selector
195    pub fn new(name: impl Into<String>, op: AttributeOp, value: Option<String>) -> Self {
196        Self {
197            name: name.into().to_lowercase(),
198            op,
199            value,
200            case_insensitive: false,
201        }
202    }
203
204    /// Test if an attribute value matches this selector
205    pub fn matches(&self, attr_value: Option<&str>) -> bool {
206        // Convert to shared types and use common comparison logic
207        let filter_op = self.op.to_filter_op();
208        let filter_value = self
209            .value
210            .as_ref()
211            .map(|v| FilterValue::String(v.clone()))
212            .unwrap_or_else(|| FilterValue::String(String::new()));
213
214        compare_filter(attr_value, filter_op, &filter_value, self.case_insensitive)
215    }
216}
217
218impl AttributeOp {
219    /// Convert to shared FilterOp type.
220    pub fn to_filter_op(&self) -> FilterOp {
221        match self {
222            AttributeOp::Exists => FilterOp::Exists,
223            AttributeOp::Equals => FilterOp::Equals,
224            AttributeOp::NotEquals => FilterOp::NotEquals,
225            AttributeOp::WordMatch => FilterOp::WordMatch,
226            AttributeOp::StartsWith => FilterOp::StartsWith,
227            AttributeOp::EndsWith => FilterOp::EndsWith,
228            AttributeOp::Contains => FilterOp::Contains,
229            AttributeOp::GreaterThan => FilterOp::GreaterThan,
230            AttributeOp::LessThan => FilterOp::LessThan,
231            AttributeOp::GreaterOrEqual => FilterOp::GreaterOrEqual,
232            AttributeOp::LessOrEqual => FilterOp::LessOrEqual,
233        }
234    }
235
236    /// Create from shared FilterOp type.
237    pub fn from_filter_op(op: FilterOp) -> Self {
238        match op {
239            FilterOp::Exists => AttributeOp::Exists,
240            FilterOp::Equals => AttributeOp::Equals,
241            FilterOp::NotEquals => AttributeOp::NotEquals,
242            FilterOp::WordMatch => AttributeOp::WordMatch,
243            FilterOp::StartsWith => AttributeOp::StartsWith,
244            FilterOp::EndsWith => AttributeOp::EndsWith,
245            FilterOp::Contains => AttributeOp::Contains,
246            FilterOp::GreaterThan => AttributeOp::GreaterThan,
247            FilterOp::LessThan => AttributeOp::LessThan,
248            FilterOp::GreaterOrEqual => AttributeOp::GreaterOrEqual,
249            FilterOp::LessOrEqual => AttributeOp::LessOrEqual,
250        }
251    }
252}
253
254/// Standard attribute names with aliases
255#[derive(Debug, Clone, Copy, PartialEq, Eq)]
256pub enum StandardAttribute {
257    // Component attributes
258    Designator,
259    Part,
260    Value,
261    Footprint,
262    Description,
263
264    // Pin attributes
265    Name,
266    Number,
267    Type,
268    Hidden,
269
270    // Net attributes
271    NetName,
272
273    // Port attributes
274    IoType,
275
276    // Power attributes
277    Style,
278
279    // Location attributes
280    X,
281    Y,
282}
283
284impl StandardAttribute {
285    /// Parse attribute name from string (with aliases)
286    pub fn try_parse(s: &str) -> Option<Self> {
287        match s.to_lowercase().as_str() {
288            "designator" | "des" | "ref" | "refdes" => Some(Self::Designator),
289            "part" | "partname" | "libref" | "libreference" => Some(Self::Part),
290            "value" | "val" => Some(Self::Value),
291            "footprint" | "fp" | "package" | "pcbfootprint" => Some(Self::Footprint),
292            "description" | "desc" => Some(Self::Description),
293            "name" | "pinname" => Some(Self::Name),
294            "number" | "num" | "pin" | "pinnum" => Some(Self::Number),
295            "type" | "electrical" | "elec" => Some(Self::Type),
296            "hidden" => Some(Self::Hidden),
297            "netname" | "net" => Some(Self::NetName),
298            "io" | "iotype" | "direction" | "dir" => Some(Self::IoType),
299            "style" => Some(Self::Style),
300            "x" | "locx" | "locationx" => Some(Self::X),
301            "y" | "locy" | "locationy" => Some(Self::Y),
302            _ => None,
303        }
304    }
305
306    /// Get canonical attribute name
307    pub fn canonical_name(&self) -> &'static str {
308        match self {
309            Self::Designator => "designator",
310            Self::Part => "part",
311            Self::Value => "value",
312            Self::Footprint => "footprint",
313            Self::Description => "description",
314            Self::Name => "name",
315            Self::Number => "number",
316            Self::Type => "type",
317            Self::Hidden => "hidden",
318            Self::NetName => "net",
319            Self::IoType => "io",
320            Self::Style => "style",
321            Self::X => "x",
322            Self::Y => "y",
323        }
324    }
325}
326
327/// Pseudo-selector types
328#[derive(Debug, Clone, PartialEq)]
329pub enum PseudoSelector {
330    // Connectivity
331    Connected,
332    Unconnected,
333
334    // Electrical types
335    Power,
336    Ground,
337    Input,
338    Output,
339    Bidirectional,
340    Passive,
341    OpenCollector,
342    OpenEmitter,
343    HiZ,
344
345    // Visibility
346    Hidden,
347    Visible,
348
349    // Position selectors
350    First,
351    Last,
352    Nth(usize),
353    NthLast(usize),
354    Even,
355    Odd,
356
357    // Result modifiers
358    Count,
359    Limit(usize),
360    Offset(usize),
361}
362
363impl PseudoSelector {
364    /// Parse pseudo-selector from string
365    pub fn try_parse(s: &str, arg: Option<&str>) -> Option<Self> {
366        match s.to_lowercase().as_str() {
367            "connected" | "conn" => Some(Self::Connected),
368            "unconnected" | "unconn" | "floating" | "nc" => Some(Self::Unconnected),
369            "power" | "pwr" => Some(Self::Power),
370            "ground" | "gnd" => Some(Self::Ground),
371            "input" | "in" => Some(Self::Input),
372            "output" | "out" => Some(Self::Output),
373            "bidirectional" | "bidir" | "inout" => Some(Self::Bidirectional),
374            "passive" | "pass" => Some(Self::Passive),
375            "opencollector" | "oc" => Some(Self::OpenCollector),
376            "openemitter" | "oe" => Some(Self::OpenEmitter),
377            "hiz" | "tristate" => Some(Self::HiZ),
378            "hidden" => Some(Self::Hidden),
379            "visible" => Some(Self::Visible),
380            "first" => Some(Self::First),
381            "last" => Some(Self::Last),
382            "even" => Some(Self::Even),
383            "odd" => Some(Self::Odd),
384            "count" => Some(Self::Count),
385            "nth" => arg.and_then(|a| a.parse().ok()).map(Self::Nth),
386            "nth-last" | "nthlast" => arg.and_then(|a| a.parse().ok()).map(Self::NthLast),
387            "limit" => arg.and_then(|a| a.parse().ok()).map(Self::Limit),
388            "offset" | "skip" => arg.and_then(|a| a.parse().ok()).map(Self::Offset),
389            _ => None,
390        }
391    }
392
393    /// Check if this is a result modifier (affects output, not filtering)
394    pub fn is_result_modifier(&self) -> bool {
395        matches!(
396            self,
397            PseudoSelector::Count
398                | PseudoSelector::Limit(_)
399                | PseudoSelector::Offset(_)
400                | PseudoSelector::First
401                | PseudoSelector::Last
402                | PseudoSelector::Nth(_)
403                | PseudoSelector::NthLast(_)
404                | PseudoSelector::Even
405                | PseudoSelector::Odd
406        )
407    }
408
409    /// Check if this is a filter (affects element selection)
410    pub fn is_filter(&self) -> bool {
411        !self.is_result_modifier()
412    }
413}
414
415/// Combinator types for relationship queries
416#[derive(Debug, Clone, Copy, PartialEq, Eq)]
417pub enum CombinatorType {
418    /// `A B` - B is descendant of A (owned by, directly or indirectly)
419    Descendant,
420    /// `A > B` - B is direct child of A
421    Child,
422    /// `A >> B` - B is electrically connected to A (through nets)
423    Connected,
424    /// `A ~ B` - B is sibling of A (same owner)
425    Sibling,
426    /// `A + B` - B immediately follows A in document order
427    Adjacent,
428    /// `A :: B` - B is on net A (for net queries)
429    OnNet,
430}
431
432impl CombinatorType {
433    /// Get the syntax representation
434    pub fn syntax(&self) -> &'static str {
435        match self {
436            CombinatorType::Descendant => " ",
437            CombinatorType::Child => " > ",
438            CombinatorType::Connected => " >> ",
439            CombinatorType::Sibling => " ~ ",
440            CombinatorType::Adjacent => " + ",
441            CombinatorType::OnNet => " :: ",
442        }
443    }
444}