Skip to main content

pick/selector/
parser.rs

1use super::types::*;
2use crate::error::PickError;
3
4// ──────────────────────────────────────────────
5// Expression / Pipeline / Stage parsing
6// ──────────────────────────────────────────────
7
8impl Expression {
9    /// Parse a full expression: `pipeline (',' pipeline)*`
10    pub fn parse(input: &str) -> Result<Self, PickError> {
11        if input.is_empty() {
12            return Ok(Expression {
13                pipelines: vec![Pipeline {
14                    stages: vec![PipeStage::Path(Selector { segments: vec![] })],
15                }],
16            });
17        }
18
19        let parts = split_top_level(input, ',');
20        let mut pipelines = Vec::new();
21        for part in parts {
22            let trimmed = part.trim();
23            if trimmed.is_empty() {
24                return Err(PickError::InvalidSelector(
25                    "empty selector in comma-separated list".into(),
26                ));
27            }
28            pipelines.push(Pipeline::parse(trimmed)?);
29        }
30
31        Ok(Expression { pipelines })
32    }
33}
34
35impl Pipeline {
36    /// Parse a pipeline: `stage ('|' stage)*`
37    pub fn parse(input: &str) -> Result<Self, PickError> {
38        let parts = split_top_level(input, '|');
39        let mut stages = Vec::new();
40        for part in parts {
41            let trimmed = part.trim();
42            if trimmed.is_empty() {
43                return Err(PickError::InvalidSelector("empty stage in pipeline".into()));
44            }
45            stages.push(parse_pipe_stage(trimmed)?);
46        }
47        Ok(Pipeline { stages })
48    }
49}
50
51/// Parse a single pipe stage.
52fn parse_pipe_stage(input: &str) -> Result<PipeStage, PickError> {
53    // select(...)
54    if let Some(rest) = input.strip_prefix("select(") {
55        let inner = rest
56            .strip_suffix(')')
57            .ok_or_else(|| PickError::InvalidSelector("unterminated select()".into()))?;
58        let expr = parse_filter_expr(inner.trim())?;
59        return Ok(PipeStage::Select(expr));
60    }
61
62    // set(.path, value)
63    if let Some(rest) = input.strip_prefix("set(") {
64        let inner = rest
65            .strip_suffix(')')
66            .ok_or_else(|| PickError::InvalidSelector("unterminated set()".into()))?;
67        let (path, value) = parse_set_args(inner.trim())?;
68        return Ok(PipeStage::Set { path, value });
69    }
70
71    // del(.path)
72    if let Some(rest) = input.strip_prefix("del(") {
73        let inner = rest
74            .strip_suffix(')')
75            .ok_or_else(|| PickError::InvalidSelector("unterminated del()".into()))?;
76        let path = parse_filter_path(inner.trim())?;
77        return Ok(PipeStage::Del(path));
78    }
79
80    // Standalone builtin: keys(), values(), length()
81    if let Some(builtin) = try_parse_standalone_builtin(input) {
82        return Ok(PipeStage::Builtin(builtin));
83    }
84
85    // Default: path expression
86    Ok(PipeStage::Path(Selector::parse(input)?))
87}
88
89fn try_parse_standalone_builtin(input: &str) -> Option<Builtin> {
90    match input {
91        "keys()" => Some(Builtin::Keys),
92        "values()" => Some(Builtin::Values),
93        "length()" => Some(Builtin::Length),
94        _ => None,
95    }
96}
97
98// ──────────────────────────────────────────────
99// Selector / Segment / Key / Index parsing
100// ──────────────────────────────────────────────
101
102impl Selector {
103    /// Parse a dot-separated path: `segment ('.' segment)*`
104    ///
105    /// Supports recursive descent via `..`: `foo..bar` finds `bar` anywhere
106    /// under `foo`.
107    pub fn parse(input: &str) -> Result<Self, PickError> {
108        if input.is_empty() {
109            return Ok(Selector { segments: vec![] });
110        }
111
112        let mut segments = Vec::new();
113        let mut remaining = input;
114        let mut next_recursive = false;
115
116        // Leading `..` means recursive from root
117        if remaining.starts_with("..") {
118            next_recursive = true;
119            remaining = &remaining[2..];
120            if remaining.is_empty() {
121                return Err(PickError::InvalidSelector(
122                    "trailing '..' in selector".into(),
123                ));
124            }
125        }
126
127        while !remaining.is_empty() {
128            let (mut segment, rest) = parse_segment(remaining)?;
129            segment.recursive = next_recursive;
130            next_recursive = false;
131            segments.push(segment);
132            remaining = rest;
133
134            if remaining.starts_with("..") {
135                next_recursive = true;
136                remaining = &remaining[2..];
137                if remaining.is_empty() {
138                    return Err(PickError::InvalidSelector(
139                        "trailing '..' in selector".into(),
140                    ));
141                }
142            } else if remaining.starts_with('.') {
143                remaining = &remaining[1..];
144                if remaining.is_empty() {
145                    return Err(PickError::InvalidSelector(
146                        "trailing dot in selector".into(),
147                    ));
148                }
149            }
150        }
151
152        Ok(Selector { segments })
153    }
154}
155
156fn parse_segment(input: &str) -> Result<(Segment, &str), PickError> {
157    let (key, remaining) = parse_key(input)?;
158
159    // Check for builtin syntax: keys(), values(), length()
160    if let Some(ref k) = key
161        && let Some(builtin) = recognize_builtin(k)
162        && let Some(after_parens) = remaining.strip_prefix("()")
163    {
164        return Ok((
165            Segment {
166                key: None,
167                indices: vec![],
168                recursive: false,
169                builtin: Some(builtin),
170            },
171            after_parens,
172        ));
173    }
174
175    let (indices, remaining) = parse_indices(remaining)?;
176
177    if key.is_none() && indices.is_empty() {
178        return Err(PickError::InvalidSelector(format!(
179            "unexpected character: '{}'",
180            input.chars().next().unwrap_or('?')
181        )));
182    }
183
184    Ok((
185        Segment {
186            key,
187            indices,
188            recursive: false,
189            builtin: None,
190        },
191        remaining,
192    ))
193}
194
195fn parse_key(input: &str) -> Result<(Option<String>, &str), PickError> {
196    if input.is_empty() {
197        return Ok((None, input));
198    }
199
200    let first = input.as_bytes()[0];
201
202    if first == b'"' {
203        let rest = &input[1..];
204        let mut key = String::new();
205        let mut chars = rest.chars();
206        let mut consumed = 0;
207        loop {
208            match chars.next() {
209                None => {
210                    return Err(PickError::InvalidSelector("unterminated quoted key".into()));
211                }
212                Some('"') => {
213                    consumed += 1;
214                    break;
215                }
216                Some('\\') => {
217                    consumed += 1;
218                    match chars.next() {
219                        Some('"') => {
220                            key.push('"');
221                            consumed += 1;
222                        }
223                        Some('\\') => {
224                            key.push('\\');
225                            consumed += 1;
226                        }
227                        Some(c) => {
228                            key.push('\\');
229                            key.push(c);
230                            consumed += c.len_utf8();
231                        }
232                        None => {
233                            return Err(PickError::InvalidSelector(
234                                "unterminated quoted key".into(),
235                            ));
236                        }
237                    }
238                }
239                Some(c) => {
240                    key.push(c);
241                    consumed += c.len_utf8();
242                }
243            }
244        }
245        Ok((Some(key), &rest[consumed..]))
246    } else if first == b'[' {
247        Ok((None, input))
248    } else if first.is_ascii_alphanumeric() || first == b'_' {
249        let end = input
250            .find(|c: char| !c.is_ascii_alphanumeric() && c != '_' && c != '-')
251            .unwrap_or(input.len());
252        let key = &input[..end];
253        Ok((Some(key.to_string()), &input[end..]))
254    } else {
255        Err(PickError::InvalidSelector(format!(
256            "unexpected character: '{}'",
257            first as char
258        )))
259    }
260}
261
262fn parse_indices(input: &str) -> Result<(Vec<Index>, &str), PickError> {
263    let mut indices = Vec::new();
264    let mut remaining = input;
265
266    while remaining.starts_with('[') {
267        remaining = &remaining[1..]; // consume [
268
269        let bracket_end = remaining
270            .find(']')
271            .ok_or_else(|| PickError::InvalidSelector("unterminated index bracket".into()))?;
272        let content = &remaining[..bracket_end];
273
274        if content == "*" {
275            indices.push(Index::Wildcard);
276        } else if content.is_empty() {
277            return Err(PickError::InvalidSelector("empty index bracket".into()));
278        } else if let Some(colon_pos) = content.find(':') {
279            let start_str = content[..colon_pos].trim();
280            let end_str = content[colon_pos + 1..].trim();
281
282            let start = if start_str.is_empty() {
283                None
284            } else {
285                Some(start_str.parse::<i64>().map_err(|_| {
286                    PickError::InvalidSelector(format!("invalid slice start: '{start_str}'"))
287                })?)
288            };
289
290            let end = if end_str.is_empty() {
291                None
292            } else {
293                Some(end_str.parse::<i64>().map_err(|_| {
294                    PickError::InvalidSelector(format!("invalid slice end: '{end_str}'"))
295                })?)
296            };
297
298            indices.push(Index::Slice { start, end });
299        } else {
300            let n: i64 = content
301                .parse()
302                .map_err(|_| PickError::InvalidSelector(format!("invalid index: '{content}'")))?;
303            indices.push(Index::Number(n));
304        }
305
306        remaining = &remaining[bracket_end + 1..]; // skip past ]
307    }
308
309    Ok((indices, remaining))
310}
311
312fn recognize_builtin(name: &str) -> Option<Builtin> {
313    match name {
314        "keys" => Some(Builtin::Keys),
315        "values" => Some(Builtin::Values),
316        "length" => Some(Builtin::Length),
317        _ => None,
318    }
319}
320
321// ──────────────────────────────────────────────
322// Filter expression parsing
323// ──────────────────────────────────────────────
324
325/// Parse a complete filter expression with `and`/`or` logic.
326fn parse_filter_expr(input: &str) -> Result<FilterExpr, PickError> {
327    let (expr, remaining) = parse_or_expr(input)?;
328    let remaining = remaining.trim();
329    if !remaining.is_empty() {
330        return Err(PickError::InvalidSelector(format!(
331            "unexpected trailing content in filter: '{remaining}'"
332        )));
333    }
334    Ok(expr)
335}
336
337fn parse_or_expr(input: &str) -> Result<(FilterExpr, &str), PickError> {
338    let (mut left, mut remaining) = parse_and_expr(input)?;
339    loop {
340        let trimmed = remaining.trim_start();
341        if let Some(rest) = trimmed.strip_prefix("or ") {
342            let (right, rest) = parse_and_expr(rest)?;
343            left = FilterExpr::Or(Box::new(left), Box::new(right));
344            remaining = rest;
345        } else {
346            break;
347        }
348    }
349    Ok((left, remaining))
350}
351
352fn parse_and_expr(input: &str) -> Result<(FilterExpr, &str), PickError> {
353    let (mut left, mut remaining) = parse_atom(input)?;
354    loop {
355        let trimmed = remaining.trim_start();
356        if let Some(rest) = trimmed.strip_prefix("and ") {
357            let (right, rest) = parse_atom(rest)?;
358            left = FilterExpr::And(Box::new(left), Box::new(right));
359            remaining = rest;
360        } else {
361            break;
362        }
363    }
364    Ok((left, remaining))
365}
366
367fn parse_atom(input: &str) -> Result<(FilterExpr, &str), PickError> {
368    let input = input.trim_start();
369
370    // not <atom>
371    if let Some(rest) = input.strip_prefix("not ") {
372        let (inner, remaining) = parse_atom(rest)?;
373        return Ok((FilterExpr::Not(Box::new(inner)), remaining));
374    }
375
376    // Must start with a path (.)
377    if !input.starts_with('.') {
378        return Err(PickError::InvalidSelector(
379            "filter condition must start with '.' (path)".into(),
380        ));
381    }
382
383    let (path, after_path) = parse_filter_path_with_remaining(input)?;
384    let after_path_trimmed = after_path.trim_start();
385
386    // Try to parse a comparison operator
387    if let Some((op, after_op)) = try_parse_compare_op(after_path_trimmed) {
388        let (value, remaining) = parse_literal(after_op.trim_start())?;
389        Ok((
390            FilterExpr::Condition(Condition { path, op, value }),
391            remaining,
392        ))
393    } else {
394        // Truthiness test: select(.active)
395        Ok((FilterExpr::Truthy(path), after_path))
396    }
397}
398
399/// Parse a filter path starting with `.` and return remaining input.
400fn parse_filter_path_with_remaining(input: &str) -> Result<(Selector, &str), PickError> {
401    debug_assert!(input.starts_with('.'));
402    let after_dot = &input[1..];
403
404    if after_dot.is_empty()
405        || after_dot.starts_with(' ')
406        || after_dot.starts_with(')')
407        || after_dot.starts_with('=')
408        || after_dot.starts_with('!')
409        || after_dot.starts_with('>')
410        || after_dot.starts_with('<')
411        || after_dot.starts_with('~')
412    {
413        // Identity path (just `.`)
414        return Ok((Selector { segments: vec![] }, after_dot));
415    }
416
417    // Find where the path ends: at whitespace, operator, or closing paren
418    let mut bracket_depth: i32 = 0;
419    let mut in_quotes = false;
420    let bytes = after_dot.as_bytes();
421    let mut i = 0;
422
423    while i < bytes.len() {
424        if in_quotes {
425            if bytes[i] == b'\\' && i + 1 < bytes.len() {
426                i += 2;
427                continue;
428            }
429            if bytes[i] == b'"' {
430                in_quotes = false;
431            }
432            i += 1;
433            continue;
434        }
435
436        match bytes[i] {
437            b'"' => in_quotes = true,
438            b'[' => bracket_depth += 1,
439            b']' => bracket_depth -= 1,
440            b' ' | b')' if bracket_depth == 0 => break,
441            b'=' | b'!' | b'>' | b'<' | b'~' if bracket_depth == 0 => break,
442            _ => {}
443        }
444        i += 1;
445    }
446    let end = i;
447
448    let path_str = &after_dot[..end];
449    let remaining = &after_dot[end..];
450    let selector = Selector::parse(path_str)?;
451    Ok((selector, remaining))
452}
453
454/// Parse a filter path (no remaining). Used for del() and set() paths.
455fn parse_filter_path(input: &str) -> Result<Selector, PickError> {
456    if !input.starts_with('.') {
457        return Err(PickError::InvalidSelector(
458            "path must start with '.'".into(),
459        ));
460    }
461    let after_dot = &input[1..];
462    if after_dot.is_empty() {
463        return Ok(Selector { segments: vec![] });
464    }
465    Selector::parse(after_dot)
466}
467
468fn try_parse_compare_op(input: &str) -> Option<(CompareOp, &str)> {
469    // Two-char operators first
470    if let Some(rest) = input.strip_prefix("==") {
471        return Some((CompareOp::Eq, rest));
472    }
473    if let Some(rest) = input.strip_prefix("!=") {
474        return Some((CompareOp::Ne, rest));
475    }
476    if let Some(rest) = input.strip_prefix(">=") {
477        return Some((CompareOp::Gte, rest));
478    }
479    if let Some(rest) = input.strip_prefix("<=") {
480        return Some((CompareOp::Lte, rest));
481    }
482    // Single-char operators
483    if let Some(rest) = input.strip_prefix('>') {
484        return Some((CompareOp::Gt, rest));
485    }
486    if let Some(rest) = input.strip_prefix('<') {
487        return Some((CompareOp::Lt, rest));
488    }
489    if let Some(rest) = input.strip_prefix('~') {
490        return Some((CompareOp::Match, rest));
491    }
492    None
493}
494
495fn parse_literal(input: &str) -> Result<(LiteralValue, &str), PickError> {
496    let input = input.trim_start();
497
498    // null
499    if let Some(rest) = input.strip_prefix("null")
500        && (rest.is_empty() || !rest.as_bytes()[0].is_ascii_alphanumeric())
501    {
502        return Ok((LiteralValue::Null, rest));
503    }
504
505    // booleans
506    if let Some(rest) = input.strip_prefix("true")
507        && (rest.is_empty() || !rest.as_bytes()[0].is_ascii_alphanumeric())
508    {
509        return Ok((LiteralValue::Bool(true), rest));
510    }
511    if let Some(rest) = input.strip_prefix("false")
512        && (rest.is_empty() || !rest.as_bytes()[0].is_ascii_alphanumeric())
513    {
514        return Ok((LiteralValue::Bool(false), rest));
515    }
516
517    // quoted string
518    if let Some(rest) = input.strip_prefix('"') {
519        let mut value = String::new();
520        let mut chars = rest.chars();
521        let mut consumed = 0;
522        loop {
523            match chars.next() {
524                None => {
525                    return Err(PickError::InvalidSelector(
526                        "unterminated string literal".into(),
527                    ));
528                }
529                Some('"') => {
530                    consumed += 1;
531                    break;
532                }
533                Some('\\') => {
534                    consumed += 1;
535                    match chars.next() {
536                        Some('"') => {
537                            value.push('"');
538                            consumed += 1;
539                        }
540                        Some('\\') => {
541                            value.push('\\');
542                            consumed += 1;
543                        }
544                        Some('n') => {
545                            value.push('\n');
546                            consumed += 1;
547                        }
548                        Some('t') => {
549                            value.push('\t');
550                            consumed += 1;
551                        }
552                        Some(c) => {
553                            value.push(c);
554                            consumed += c.len_utf8();
555                        }
556                        None => {
557                            return Err(PickError::InvalidSelector(
558                                "unterminated string literal".into(),
559                            ));
560                        }
561                    }
562                }
563                Some(c) => {
564                    value.push(c);
565                    consumed += c.len_utf8();
566                }
567            }
568        }
569        return Ok((LiteralValue::String(value), &rest[consumed..]));
570    }
571
572    // number (integer or float, possibly negative)
573    let num_end = input
574        .find(|c: char| {
575            !c.is_ascii_digit() && c != '.' && c != '-' && c != '+' && c != 'e' && c != 'E'
576        })
577        .unwrap_or(input.len());
578
579    if num_end == 0 {
580        return Err(PickError::InvalidSelector(format!(
581            "expected literal value, got: '{}'",
582            &input[..input.len().min(20)]
583        )));
584    }
585
586    let num_str = &input[..num_end];
587    let n: f64 = num_str
588        .parse()
589        .map_err(|_| PickError::InvalidSelector(format!("invalid number: '{num_str}'")))?;
590    Ok((LiteralValue::Number(n), &input[num_end..]))
591}
592
593/// Parse set() arguments: `.path, value`
594fn parse_set_args(input: &str) -> Result<(Selector, LiteralValue), PickError> {
595    let comma_pos = find_top_level_comma(input).ok_or_else(|| {
596        PickError::InvalidSelector("set() requires two arguments: set(.path, value)".into())
597    })?;
598
599    let path_str = input[..comma_pos].trim();
600    let value_str = input[comma_pos + 1..].trim();
601
602    let path = parse_filter_path(path_str)?;
603    let (value, remaining) = parse_literal(value_str)?;
604
605    if !remaining.trim().is_empty() {
606        return Err(PickError::InvalidSelector(format!(
607            "unexpected content after set() value: '{}'",
608            remaining.trim()
609        )));
610    }
611
612    Ok((path, value))
613}
614
615fn find_top_level_comma(input: &str) -> Option<usize> {
616    let mut depth = 0;
617    let mut in_quotes = false;
618    for (i, b) in input.bytes().enumerate() {
619        if in_quotes {
620            if b == b'\\' {
621                continue; // next char is escaped
622            }
623            if b == b'"' {
624                in_quotes = false;
625            }
626            continue;
627        }
628        match b {
629            b'"' => in_quotes = true,
630            b'(' | b'[' => depth += 1,
631            b')' | b']' => depth -= 1,
632            b',' if depth == 0 => return Some(i),
633            _ => {}
634        }
635    }
636    None
637}
638
639// ──────────────────────────────────────────────
640// Top-level splitting utilities
641// ──────────────────────────────────────────────
642
643/// Split `input` on `delimiter` at the top level, respecting brackets,
644/// parentheses, and quoted strings.
645fn split_top_level(input: &str, delimiter: char) -> Vec<&str> {
646    let delim_byte = delimiter as u8;
647    let mut parts = Vec::new();
648    let mut depth: i32 = 0;
649    let mut in_quotes = false;
650    let mut start = 0;
651    let bytes = input.as_bytes();
652    let mut i = 0;
653
654    while i < bytes.len() {
655        if in_quotes {
656            if bytes[i] == b'\\' && i + 1 < bytes.len() {
657                i += 2;
658                continue;
659            }
660            if bytes[i] == b'"' {
661                in_quotes = false;
662            }
663            i += 1;
664            continue;
665        }
666
667        match bytes[i] {
668            b'"' => in_quotes = true,
669            b'[' | b'(' => depth += 1,
670            b']' | b')' => depth = (depth - 1).max(0),
671            b if b == delim_byte && depth == 0 => {
672                parts.push(&input[start..i]);
673                start = i + 1;
674            }
675            _ => {}
676        }
677        i += 1;
678    }
679    parts.push(&input[start..]);
680    parts
681}
682
683// ──────────────────────────────────────────────
684// Tests
685// ──────────────────────────────────────────────
686
687#[cfg(test)]
688mod tests {
689    use super::*;
690
691    // ── Selector parsing (existing behavior preserved) ──
692
693    #[test]
694    fn parse_empty_selector() {
695        let sel = Selector::parse("").unwrap();
696        assert!(sel.segments.is_empty());
697    }
698
699    #[test]
700    fn parse_simple_key() {
701        let sel = Selector::parse("foo").unwrap();
702        assert_eq!(sel.segments.len(), 1);
703        assert_eq!(sel.segments[0].key, Some("foo".into()));
704        assert!(sel.segments[0].indices.is_empty());
705    }
706
707    #[test]
708    fn parse_nested_keys() {
709        let sel = Selector::parse("foo.bar.baz").unwrap();
710        assert_eq!(sel.segments.len(), 3);
711        assert_eq!(sel.segments[0].key, Some("foo".into()));
712        assert_eq!(sel.segments[1].key, Some("bar".into()));
713        assert_eq!(sel.segments[2].key, Some("baz".into()));
714    }
715
716    #[test]
717    fn parse_array_index() {
718        let sel = Selector::parse("items[0]").unwrap();
719        assert_eq!(sel.segments.len(), 1);
720        assert_eq!(sel.segments[0].key, Some("items".into()));
721        assert_eq!(sel.segments[0].indices, vec![Index::Number(0)]);
722    }
723
724    #[test]
725    fn parse_nested_with_index() {
726        let sel = Selector::parse("foo.bar[0].baz").unwrap();
727        assert_eq!(sel.segments.len(), 3);
728        assert_eq!(sel.segments[1].key, Some("bar".into()));
729        assert_eq!(sel.segments[1].indices, vec![Index::Number(0)]);
730    }
731
732    #[test]
733    fn parse_wildcard() {
734        let sel = Selector::parse("items[*]").unwrap();
735        assert_eq!(sel.segments[0].indices, vec![Index::Wildcard]);
736    }
737
738    #[test]
739    fn parse_multiple_indices() {
740        let sel = Selector::parse("matrix[0][1]").unwrap();
741        assert_eq!(
742            sel.segments[0].indices,
743            vec![Index::Number(0), Index::Number(1)]
744        );
745    }
746
747    #[test]
748    fn parse_negative_index() {
749        let sel = Selector::parse("items[-1]").unwrap();
750        assert_eq!(sel.segments[0].indices, vec![Index::Number(-1)]);
751    }
752
753    #[test]
754    fn parse_quoted_key() {
755        let sel = Selector::parse("\"foo.bar\".baz").unwrap();
756        assert_eq!(sel.segments.len(), 2);
757        assert_eq!(sel.segments[0].key, Some("foo.bar".into()));
758        assert_eq!(sel.segments[1].key, Some("baz".into()));
759    }
760
761    #[test]
762    fn parse_key_with_hyphens() {
763        let sel = Selector::parse("content-type").unwrap();
764        assert_eq!(sel.segments[0].key, Some("content-type".into()));
765    }
766
767    #[test]
768    fn parse_key_with_numbers() {
769        let sel = Selector::parse("item1.value2").unwrap();
770        assert_eq!(sel.segments[0].key, Some("item1".into()));
771        assert_eq!(sel.segments[1].key, Some("value2".into()));
772    }
773
774    #[test]
775    fn parse_leading_index() {
776        let sel = Selector::parse("[0].name").unwrap();
777        assert_eq!(sel.segments.len(), 2);
778        assert_eq!(sel.segments[0].key, None);
779        assert_eq!(sel.segments[0].indices, vec![Index::Number(0)]);
780        assert_eq!(sel.segments[1].key, Some("name".into()));
781    }
782
783    #[test]
784    fn parse_only_index() {
785        let sel = Selector::parse("[0]").unwrap();
786        assert_eq!(sel.segments.len(), 1);
787        assert_eq!(sel.segments[0].key, None);
788        assert_eq!(sel.segments[0].indices, vec![Index::Number(0)]);
789    }
790
791    #[test]
792    fn parse_only_wildcard() {
793        let sel = Selector::parse("[*]").unwrap();
794        assert_eq!(sel.segments.len(), 1);
795        assert_eq!(sel.segments[0].indices, vec![Index::Wildcard]);
796    }
797
798    #[test]
799    fn parse_trailing_dot_error() {
800        assert!(Selector::parse("foo.").is_err());
801    }
802
803    #[test]
804    fn parse_double_dot_error_old_style() {
805        // `foo..bar` is now valid: recursive descent
806        let sel = Selector::parse("foo..bar").unwrap();
807        assert_eq!(sel.segments.len(), 2);
808        assert!(!sel.segments[0].recursive);
809        assert!(sel.segments[1].recursive);
810    }
811
812    #[test]
813    fn parse_unterminated_bracket_error() {
814        assert!(Selector::parse("foo[0").is_err());
815    }
816
817    #[test]
818    fn parse_empty_bracket_error() {
819        assert!(Selector::parse("foo[]").is_err());
820    }
821
822    #[test]
823    fn parse_invalid_index_error() {
824        assert!(Selector::parse("foo[abc]").is_err());
825    }
826
827    #[test]
828    fn parse_unterminated_quote_error() {
829        assert!(Selector::parse("\"foo").is_err());
830    }
831
832    #[test]
833    fn parse_wildcard_then_index() {
834        let sel = Selector::parse("[*][0]").unwrap();
835        assert_eq!(
836            sel.segments[0].indices,
837            vec![Index::Wildcard, Index::Number(0)]
838        );
839    }
840
841    // ── Phase 1: Slice parsing ──
842
843    #[test]
844    fn parse_slice_full() {
845        let sel = Selector::parse("items[1:3]").unwrap();
846        assert_eq!(
847            sel.segments[0].indices,
848            vec![Index::Slice {
849                start: Some(1),
850                end: Some(3)
851            }]
852        );
853    }
854
855    #[test]
856    fn parse_slice_open_end() {
857        let sel = Selector::parse("items[2:]").unwrap();
858        assert_eq!(
859            sel.segments[0].indices,
860            vec![Index::Slice {
861                start: Some(2),
862                end: None
863            }]
864        );
865    }
866
867    #[test]
868    fn parse_slice_open_start() {
869        let sel = Selector::parse("items[:3]").unwrap();
870        assert_eq!(
871            sel.segments[0].indices,
872            vec![Index::Slice {
873                start: None,
874                end: Some(3)
875            }]
876        );
877    }
878
879    #[test]
880    fn parse_slice_both_open() {
881        let sel = Selector::parse("items[:]").unwrap();
882        assert_eq!(
883            sel.segments[0].indices,
884            vec![Index::Slice {
885                start: None,
886                end: None
887            }]
888        );
889    }
890
891    #[test]
892    fn parse_slice_negative_start() {
893        let sel = Selector::parse("items[-2:]").unwrap();
894        assert_eq!(
895            sel.segments[0].indices,
896            vec![Index::Slice {
897                start: Some(-2),
898                end: None
899            }]
900        );
901    }
902
903    #[test]
904    fn parse_slice_negative_end() {
905        let sel = Selector::parse("items[:-1]").unwrap();
906        assert_eq!(
907            sel.segments[0].indices,
908            vec![Index::Slice {
909                start: None,
910                end: Some(-1)
911            }]
912        );
913    }
914
915    #[test]
916    fn parse_slice_both_negative() {
917        let sel = Selector::parse("items[-3:-1]").unwrap();
918        assert_eq!(
919            sel.segments[0].indices,
920            vec![Index::Slice {
921                start: Some(-3),
922                end: Some(-1)
923            }]
924        );
925    }
926
927    #[test]
928    fn parse_slice_invalid_start() {
929        assert!(Selector::parse("items[abc:]").is_err());
930    }
931
932    #[test]
933    fn parse_slice_invalid_end() {
934        assert!(Selector::parse("items[:abc]").is_err());
935    }
936
937    #[test]
938    fn parse_slice_then_index() {
939        let sel = Selector::parse("matrix[0:2][0]").unwrap();
940        assert_eq!(sel.segments[0].indices.len(), 2);
941        assert_eq!(
942            sel.segments[0].indices[0],
943            Index::Slice {
944                start: Some(0),
945                end: Some(2)
946            }
947        );
948        assert_eq!(sel.segments[0].indices[1], Index::Number(0));
949    }
950
951    // ── Phase 1: Builtin parsing ──
952
953    #[test]
954    fn parse_builtin_keys() {
955        let sel = Selector::parse("keys()").unwrap();
956        assert_eq!(sel.segments.len(), 1);
957        assert_eq!(sel.segments[0].key, None);
958        assert_eq!(sel.segments[0].builtin, Some(Builtin::Keys));
959    }
960
961    #[test]
962    fn parse_builtin_values() {
963        let sel = Selector::parse("values()").unwrap();
964        assert_eq!(sel.segments[0].builtin, Some(Builtin::Values));
965    }
966
967    #[test]
968    fn parse_builtin_length() {
969        let sel = Selector::parse("length()").unwrap();
970        assert_eq!(sel.segments[0].builtin, Some(Builtin::Length));
971    }
972
973    #[test]
974    fn parse_builtin_after_key() {
975        let sel = Selector::parse("foo.keys()").unwrap();
976        assert_eq!(sel.segments.len(), 2);
977        assert_eq!(sel.segments[0].key, Some("foo".into()));
978        assert_eq!(sel.segments[1].builtin, Some(Builtin::Keys));
979    }
980
981    #[test]
982    fn parse_keys_as_key_without_parens() {
983        // "keys" without () is a key lookup, not a builtin
984        let sel = Selector::parse("keys").unwrap();
985        assert_eq!(sel.segments[0].key, Some("keys".into()));
986        assert_eq!(sel.segments[0].builtin, None);
987    }
988
989    #[test]
990    fn parse_builtin_after_index() {
991        let sel = Selector::parse("items[0].keys()").unwrap();
992        assert_eq!(sel.segments.len(), 2);
993        assert_eq!(sel.segments[0].key, Some("items".into()));
994        assert_eq!(sel.segments[1].builtin, Some(Builtin::Keys));
995    }
996
997    // ── Phase 1: Recursive descent parsing ──
998
999    #[test]
1000    fn parse_recursive_simple() {
1001        let sel = Selector::parse("..name").unwrap();
1002        assert_eq!(sel.segments.len(), 1);
1003        assert!(sel.segments[0].recursive);
1004        assert_eq!(sel.segments[0].key, Some("name".into()));
1005    }
1006
1007    #[test]
1008    fn parse_recursive_after_key() {
1009        let sel = Selector::parse("foo..bar").unwrap();
1010        assert_eq!(sel.segments.len(), 2);
1011        assert!(!sel.segments[0].recursive);
1012        assert_eq!(sel.segments[0].key, Some("foo".into()));
1013        assert!(sel.segments[1].recursive);
1014        assert_eq!(sel.segments[1].key, Some("bar".into()));
1015    }
1016
1017    #[test]
1018    fn parse_recursive_with_index() {
1019        let sel = Selector::parse("..items[0]").unwrap();
1020        assert_eq!(sel.segments.len(), 1);
1021        assert!(sel.segments[0].recursive);
1022        assert_eq!(sel.segments[0].key, Some("items".into()));
1023        assert_eq!(sel.segments[0].indices, vec![Index::Number(0)]);
1024    }
1025
1026    #[test]
1027    fn parse_recursive_chained() {
1028        let sel = Selector::parse("a..b..c").unwrap();
1029        assert_eq!(sel.segments.len(), 3);
1030        assert!(!sel.segments[0].recursive);
1031        assert!(sel.segments[1].recursive);
1032        assert!(sel.segments[2].recursive);
1033    }
1034
1035    #[test]
1036    fn parse_trailing_double_dot_error() {
1037        assert!(Selector::parse("foo..").is_err());
1038    }
1039
1040    #[test]
1041    fn parse_only_double_dot_error() {
1042        assert!(Selector::parse("..").is_err());
1043    }
1044
1045    // ── Phase 1: Expression (multiple selectors) ──
1046
1047    #[test]
1048    fn parse_expression_single() {
1049        let expr = Expression::parse("name").unwrap();
1050        assert_eq!(expr.pipelines.len(), 1);
1051        assert_eq!(expr.pipelines[0].stages.len(), 1);
1052    }
1053
1054    #[test]
1055    fn parse_expression_multiple() {
1056        let expr = Expression::parse("name, age").unwrap();
1057        assert_eq!(expr.pipelines.len(), 2);
1058    }
1059
1060    #[test]
1061    fn parse_expression_three() {
1062        let expr = Expression::parse("name, age, email").unwrap();
1063        assert_eq!(expr.pipelines.len(), 3);
1064    }
1065
1066    #[test]
1067    fn parse_expression_empty_part_error() {
1068        assert!(Expression::parse("name,").is_err());
1069    }
1070
1071    #[test]
1072    fn parse_expression_empty() {
1073        let expr = Expression::parse("").unwrap();
1074        assert_eq!(expr.pipelines.len(), 1);
1075    }
1076
1077    // ── Phase 2: Pipeline parsing ──
1078
1079    #[test]
1080    fn parse_pipeline_single_stage() {
1081        let p = Pipeline::parse("name").unwrap();
1082        assert_eq!(p.stages.len(), 1);
1083        assert!(matches!(p.stages[0], PipeStage::Path(_)));
1084    }
1085
1086    #[test]
1087    fn parse_pipeline_two_stages() {
1088        let p = Pipeline::parse("items[*] | name").unwrap();
1089        assert_eq!(p.stages.len(), 2);
1090    }
1091
1092    #[test]
1093    fn parse_pipeline_builtin_stage() {
1094        let p = Pipeline::parse("foo | keys()").unwrap();
1095        assert_eq!(p.stages.len(), 2);
1096        assert!(matches!(p.stages[1], PipeStage::Builtin(Builtin::Keys)));
1097    }
1098
1099    #[test]
1100    fn parse_pipeline_select_stage() {
1101        let p = Pipeline::parse("items[*] | select(.price > 100)").unwrap();
1102        assert_eq!(p.stages.len(), 2);
1103        assert!(matches!(p.stages[1], PipeStage::Select(_)));
1104    }
1105
1106    #[test]
1107    fn parse_pipeline_empty_stage_error() {
1108        assert!(Pipeline::parse("foo | ").is_err());
1109    }
1110
1111    // ── Phase 2: Filter expression parsing ──
1112
1113    #[test]
1114    fn parse_filter_eq_string() {
1115        let expr = parse_filter_expr(".name == \"Alice\"").unwrap();
1116        match expr {
1117            FilterExpr::Condition(c) => {
1118                assert_eq!(c.op, CompareOp::Eq);
1119                assert_eq!(c.value, LiteralValue::String("Alice".into()));
1120            }
1121            _ => panic!("expected Condition"),
1122        }
1123    }
1124
1125    #[test]
1126    fn parse_filter_gt_number() {
1127        let expr = parse_filter_expr(".price > 100").unwrap();
1128        match expr {
1129            FilterExpr::Condition(c) => {
1130                assert_eq!(c.op, CompareOp::Gt);
1131                assert_eq!(c.value, LiteralValue::Number(100.0));
1132            }
1133            _ => panic!("expected Condition"),
1134        }
1135    }
1136
1137    #[test]
1138    fn parse_filter_ne_bool() {
1139        let expr = parse_filter_expr(".active != false").unwrap();
1140        match expr {
1141            FilterExpr::Condition(c) => {
1142                assert_eq!(c.op, CompareOp::Ne);
1143                assert_eq!(c.value, LiteralValue::Bool(false));
1144            }
1145            _ => panic!("expected Condition"),
1146        }
1147    }
1148
1149    #[test]
1150    fn parse_filter_eq_null() {
1151        let expr = parse_filter_expr(".deleted == null").unwrap();
1152        match expr {
1153            FilterExpr::Condition(c) => {
1154                assert_eq!(c.op, CompareOp::Eq);
1155                assert_eq!(c.value, LiteralValue::Null);
1156            }
1157            _ => panic!("expected Condition"),
1158        }
1159    }
1160
1161    #[test]
1162    fn parse_filter_regex() {
1163        let expr = parse_filter_expr(".name ~ \"^A\"").unwrap();
1164        match expr {
1165            FilterExpr::Condition(c) => {
1166                assert_eq!(c.op, CompareOp::Match);
1167                assert_eq!(c.value, LiteralValue::String("^A".into()));
1168            }
1169            _ => panic!("expected Condition"),
1170        }
1171    }
1172
1173    #[test]
1174    fn parse_filter_and() {
1175        let expr = parse_filter_expr(".price > 10 and .stock > 0").unwrap();
1176        assert!(matches!(expr, FilterExpr::And(_, _)));
1177    }
1178
1179    #[test]
1180    fn parse_filter_or() {
1181        let expr = parse_filter_expr(".price < 10 or .sale == true").unwrap();
1182        assert!(matches!(expr, FilterExpr::Or(_, _)));
1183    }
1184
1185    #[test]
1186    fn parse_filter_not() {
1187        let expr = parse_filter_expr("not .deleted == true").unwrap();
1188        assert!(matches!(expr, FilterExpr::Not(_)));
1189    }
1190
1191    #[test]
1192    fn parse_filter_truthy() {
1193        let expr = parse_filter_expr(".active").unwrap();
1194        assert!(matches!(expr, FilterExpr::Truthy(_)));
1195    }
1196
1197    #[test]
1198    fn parse_filter_truthy_not() {
1199        let expr = parse_filter_expr("not .hidden").unwrap();
1200        match expr {
1201            FilterExpr::Not(inner) => assert!(matches!(*inner, FilterExpr::Truthy(_))),
1202            _ => panic!("expected Not(Truthy)"),
1203        }
1204    }
1205
1206    #[test]
1207    fn parse_filter_nested_path() {
1208        let expr = parse_filter_expr(".user.age >= 18").unwrap();
1209        match expr {
1210            FilterExpr::Condition(c) => {
1211                assert_eq!(c.path.segments.len(), 2);
1212                assert_eq!(c.op, CompareOp::Gte);
1213            }
1214            _ => panic!("expected Condition"),
1215        }
1216    }
1217
1218    #[test]
1219    fn parse_filter_identity_comparison() {
1220        let expr = parse_filter_expr(". > 5").unwrap();
1221        match expr {
1222            FilterExpr::Condition(c) => {
1223                assert!(c.path.segments.is_empty()); // identity
1224                assert_eq!(c.op, CompareOp::Gt);
1225            }
1226            _ => panic!("expected Condition"),
1227        }
1228    }
1229
1230    #[test]
1231    fn parse_filter_lte() {
1232        let expr = parse_filter_expr(".count <= 50").unwrap();
1233        match expr {
1234            FilterExpr::Condition(c) => assert_eq!(c.op, CompareOp::Lte),
1235            _ => panic!("expected Condition"),
1236        }
1237    }
1238
1239    #[test]
1240    fn parse_filter_lt() {
1241        let expr = parse_filter_expr(".count < 50").unwrap();
1242        match expr {
1243            FilterExpr::Condition(c) => assert_eq!(c.op, CompareOp::Lt),
1244            _ => panic!("expected Condition"),
1245        }
1246    }
1247
1248    #[test]
1249    fn parse_filter_float_literal() {
1250        let expr = parse_filter_expr(".score > 3.14").unwrap();
1251        match expr {
1252            FilterExpr::Condition(c) => assert_eq!(c.value, LiteralValue::Number(3.14)),
1253            _ => panic!("expected Condition"),
1254        }
1255    }
1256
1257    #[test]
1258    fn parse_filter_negative_number() {
1259        let expr = parse_filter_expr(".temp > -10").unwrap();
1260        match expr {
1261            FilterExpr::Condition(c) => assert_eq!(c.value, LiteralValue::Number(-10.0)),
1262            _ => panic!("expected Condition"),
1263        }
1264    }
1265
1266    #[test]
1267    fn parse_filter_and_or_precedence() {
1268        // `a or b and c` should parse as `a or (b and c)`
1269        let expr = parse_filter_expr(".a == 1 or .b == 2 and .c == 3").unwrap();
1270        match expr {
1271            FilterExpr::Or(left, right) => {
1272                assert!(matches!(*left, FilterExpr::Condition(_)));
1273                assert!(matches!(*right, FilterExpr::And(_, _)));
1274            }
1275            _ => panic!("expected Or at top level"),
1276        }
1277    }
1278
1279    #[test]
1280    fn parse_filter_escaped_string() {
1281        let expr = parse_filter_expr(".name == \"hello\\\"world\"").unwrap();
1282        match expr {
1283            FilterExpr::Condition(c) => {
1284                assert_eq!(c.value, LiteralValue::String("hello\"world".into()));
1285            }
1286            _ => panic!("expected Condition"),
1287        }
1288    }
1289
1290    // ── Phase 3: set / del parsing ──
1291
1292    #[test]
1293    fn parse_set_string() {
1294        let p = Pipeline::parse("set(.name, \"Alice\")").unwrap();
1295        match &p.stages[0] {
1296            PipeStage::Set { path, value } => {
1297                assert_eq!(path.segments[0].key, Some("name".into()));
1298                assert_eq!(*value, LiteralValue::String("Alice".into()));
1299            }
1300            _ => panic!("expected Set"),
1301        }
1302    }
1303
1304    #[test]
1305    fn parse_set_number() {
1306        let p = Pipeline::parse("set(.count, 42)").unwrap();
1307        match &p.stages[0] {
1308            PipeStage::Set { path, value } => {
1309                assert_eq!(*value, LiteralValue::Number(42.0));
1310            }
1311            _ => panic!("expected Set"),
1312        }
1313    }
1314
1315    #[test]
1316    fn parse_set_bool() {
1317        let p = Pipeline::parse("set(.active, true)").unwrap();
1318        match &p.stages[0] {
1319            PipeStage::Set { path, value } => {
1320                assert_eq!(*value, LiteralValue::Bool(true));
1321            }
1322            _ => panic!("expected Set"),
1323        }
1324    }
1325
1326    #[test]
1327    fn parse_set_null() {
1328        let p = Pipeline::parse("set(.deleted, null)").unwrap();
1329        match &p.stages[0] {
1330            PipeStage::Set { path, value } => {
1331                assert_eq!(*value, LiteralValue::Null);
1332            }
1333            _ => panic!("expected Set"),
1334        }
1335    }
1336
1337    #[test]
1338    fn parse_set_nested_path() {
1339        let p = Pipeline::parse("set(.user.name, \"Bob\")").unwrap();
1340        match &p.stages[0] {
1341            PipeStage::Set { path, .. } => {
1342                assert_eq!(path.segments.len(), 2);
1343            }
1344            _ => panic!("expected Set"),
1345        }
1346    }
1347
1348    #[test]
1349    fn parse_del_simple() {
1350        let p = Pipeline::parse("del(.temp)").unwrap();
1351        match &p.stages[0] {
1352            PipeStage::Del(path) => {
1353                assert_eq!(path.segments[0].key, Some("temp".into()));
1354            }
1355            _ => panic!("expected Del"),
1356        }
1357    }
1358
1359    #[test]
1360    fn parse_del_nested() {
1361        let p = Pipeline::parse("del(.metadata.temp)").unwrap();
1362        match &p.stages[0] {
1363            PipeStage::Del(path) => {
1364                assert_eq!(path.segments.len(), 2);
1365            }
1366            _ => panic!("expected Del"),
1367        }
1368    }
1369
1370    #[test]
1371    fn parse_set_unterminated_error() {
1372        assert!(Pipeline::parse("set(.name, \"Alice\"").is_err());
1373    }
1374
1375    #[test]
1376    fn parse_del_unterminated_error() {
1377        assert!(Pipeline::parse("del(.name").is_err());
1378    }
1379
1380    // ── Utility: split_top_level ──
1381
1382    #[test]
1383    fn split_simple_comma() {
1384        let parts = split_top_level("a, b, c", ',');
1385        assert_eq!(parts, vec!["a", " b", " c"]);
1386    }
1387
1388    #[test]
1389    fn split_respects_brackets() {
1390        let parts = split_top_level("items[0,1], name", ',');
1391        assert_eq!(parts, vec!["items[0,1]", " name"]);
1392    }
1393
1394    #[test]
1395    fn split_respects_parens() {
1396        let parts = split_top_level("select(.a, .b) | name", '|');
1397        assert_eq!(parts, vec!["select(.a, .b) ", " name"]);
1398    }
1399
1400    #[test]
1401    fn split_respects_quotes() {
1402        let parts = split_top_level("\"a,b\", c", ',');
1403        assert_eq!(parts, vec!["\"a,b\"", " c"]);
1404    }
1405
1406    #[test]
1407    fn split_pipe() {
1408        let parts = split_top_level("items[*] | select(.x > 1)", '|');
1409        assert_eq!(parts, vec!["items[*] ", " select(.x > 1)"]);
1410    }
1411
1412    // ── Literal parsing ──
1413
1414    #[test]
1415    fn parse_literal_string() {
1416        let (v, r) = parse_literal("\"hello\"").unwrap();
1417        assert_eq!(v, LiteralValue::String("hello".into()));
1418        assert_eq!(r, "");
1419    }
1420
1421    #[test]
1422    fn parse_literal_number() {
1423        let (v, r) = parse_literal("42").unwrap();
1424        assert_eq!(v, LiteralValue::Number(42.0));
1425        assert_eq!(r, "");
1426    }
1427
1428    #[test]
1429    fn parse_literal_negative() {
1430        let (v, r) = parse_literal("-3.5").unwrap();
1431        assert_eq!(v, LiteralValue::Number(-3.5));
1432        assert_eq!(r, "");
1433    }
1434
1435    #[test]
1436    fn parse_literal_true() {
1437        let (v, _) = parse_literal("true").unwrap();
1438        assert_eq!(v, LiteralValue::Bool(true));
1439    }
1440
1441    #[test]
1442    fn parse_literal_false() {
1443        let (v, _) = parse_literal("false").unwrap();
1444        assert_eq!(v, LiteralValue::Bool(false));
1445    }
1446
1447    #[test]
1448    fn parse_literal_null() {
1449        let (v, _) = parse_literal("null").unwrap();
1450        assert_eq!(v, LiteralValue::Null);
1451    }
1452
1453    #[test]
1454    fn parse_literal_escaped_string() {
1455        let (v, _) = parse_literal("\"a\\\"b\"").unwrap();
1456        assert_eq!(v, LiteralValue::String("a\"b".into()));
1457    }
1458
1459    #[test]
1460    fn parse_literal_number_with_remainder() {
1461        let (v, r) = parse_literal("42 and").unwrap();
1462        assert_eq!(v, LiteralValue::Number(42.0));
1463        assert_eq!(r, " and");
1464    }
1465
1466    // ══════════════════════════════════════════════
1467    // Additional coverage tests
1468    // ══════════════════════════════════════════════
1469
1470    // ── Slice parsing edge cases ──
1471
1472    #[test]
1473    fn parse_slice_in_nested_path() {
1474        let sel = Selector::parse("data[0].items[1:3]").unwrap();
1475        assert_eq!(sel.segments.len(), 2);
1476        assert_eq!(sel.segments[0].key, Some("data".into()));
1477        assert_eq!(sel.segments[0].indices, vec![Index::Number(0)]);
1478        assert_eq!(sel.segments[1].key, Some("items".into()));
1479        assert_eq!(
1480            sel.segments[1].indices,
1481            vec![Index::Slice {
1482                start: Some(1),
1483                end: Some(3)
1484            }]
1485        );
1486    }
1487
1488    #[test]
1489    fn parse_slice_wildcard_then_slice() {
1490        let sel = Selector::parse("items[*][0:2]").unwrap();
1491        assert_eq!(sel.segments[0].indices.len(), 2);
1492        assert_eq!(sel.segments[0].indices[0], Index::Wildcard);
1493        assert_eq!(
1494            sel.segments[0].indices[1],
1495            Index::Slice {
1496                start: Some(0),
1497                end: Some(2)
1498            }
1499        );
1500    }
1501
1502    #[test]
1503    fn parse_slice_then_wildcard() {
1504        let sel = Selector::parse("items[0:3][*]").unwrap();
1505        assert_eq!(sel.segments[0].indices.len(), 2);
1506        assert_eq!(
1507            sel.segments[0].indices[0],
1508            Index::Slice {
1509                start: Some(0),
1510                end: Some(3)
1511            }
1512        );
1513        assert_eq!(sel.segments[0].indices[1], Index::Wildcard);
1514    }
1515
1516    #[test]
1517    fn parse_triple_index_chain() {
1518        let sel = Selector::parse("a[0][1][2]").unwrap();
1519        assert_eq!(sel.segments[0].indices.len(), 3);
1520        assert_eq!(sel.segments[0].indices[0], Index::Number(0));
1521        assert_eq!(sel.segments[0].indices[1], Index::Number(1));
1522        assert_eq!(sel.segments[0].indices[2], Index::Number(2));
1523    }
1524
1525    #[test]
1526    fn parse_slice_zero_to_zero() {
1527        let sel = Selector::parse("items[0:0]").unwrap();
1528        assert_eq!(
1529            sel.segments[0].indices,
1530            vec![Index::Slice {
1531                start: Some(0),
1532                end: Some(0)
1533            }]
1534        );
1535    }
1536
1537    // ── Builtin parsing edge cases ──
1538
1539    #[test]
1540    fn parse_builtin_length_after_nested() {
1541        let sel = Selector::parse("a.b.length()").unwrap();
1542        assert_eq!(sel.segments.len(), 3);
1543        assert_eq!(sel.segments[2].builtin, Some(Builtin::Length));
1544    }
1545
1546    #[test]
1547    fn parse_builtin_values_after_key() {
1548        let sel = Selector::parse("data.values()").unwrap();
1549        assert_eq!(sel.segments.len(), 2);
1550        assert_eq!(sel.segments[1].builtin, Some(Builtin::Values));
1551    }
1552
1553    // ── Recursive descent parsing edge cases ──
1554
1555    #[test]
1556    fn parse_recursive_with_slice() {
1557        let sel = Selector::parse("..items[1:3]").unwrap();
1558        assert!(sel.segments[0].recursive);
1559        assert_eq!(sel.segments[0].key, Some("items".into()));
1560        assert_eq!(
1561            sel.segments[0].indices,
1562            vec![Index::Slice {
1563                start: Some(1),
1564                end: Some(3)
1565            }]
1566        );
1567    }
1568
1569    #[test]
1570    fn parse_recursive_with_wildcard() {
1571        let sel = Selector::parse("..items[*]").unwrap();
1572        assert!(sel.segments[0].recursive);
1573        assert_eq!(sel.segments[0].indices, vec![Index::Wildcard]);
1574    }
1575
1576    #[test]
1577    fn parse_recursive_then_builtin() {
1578        // ..name should be a recursive selector; builtin after is separate
1579        let p = Pipeline::parse("..items | length()").unwrap();
1580        assert_eq!(p.stages.len(), 2);
1581    }
1582
1583    // ── Pipeline parsing edge cases ──
1584
1585    #[test]
1586    fn parse_pipeline_three_stages() {
1587        let p = Pipeline::parse("items[*] | select(.price > 100) | name").unwrap();
1588        assert_eq!(p.stages.len(), 3);
1589        assert!(matches!(p.stages[0], PipeStage::Path(_)));
1590        assert!(matches!(p.stages[1], PipeStage::Select(_)));
1591        assert!(matches!(p.stages[2], PipeStage::Path(_)));
1592    }
1593
1594    #[test]
1595    fn parse_pipeline_four_stages() {
1596        let p = Pipeline::parse("items[*] | select(.x > 0) | name | length()").unwrap();
1597        assert_eq!(p.stages.len(), 4);
1598        assert!(matches!(p.stages[3], PipeStage::Builtin(Builtin::Length)));
1599    }
1600
1601    #[test]
1602    fn parse_pipeline_set_then_path() {
1603        let p = Pipeline::parse("set(.name, \"Bob\") | name").unwrap();
1604        assert_eq!(p.stages.len(), 2);
1605        assert!(matches!(p.stages[0], PipeStage::Set { .. }));
1606        assert!(matches!(p.stages[1], PipeStage::Path(_)));
1607    }
1608
1609    #[test]
1610    fn parse_pipeline_del_then_builtin() {
1611        let p = Pipeline::parse("del(.x) | keys()").unwrap();
1612        assert_eq!(p.stages.len(), 2);
1613        assert!(matches!(p.stages[0], PipeStage::Del(_)));
1614        assert!(matches!(p.stages[1], PipeStage::Builtin(Builtin::Keys)));
1615    }
1616
1617    #[test]
1618    fn parse_pipeline_set_then_set() {
1619        let p = Pipeline::parse("set(.a, 1) | set(.b, 2)").unwrap();
1620        assert_eq!(p.stages.len(), 2);
1621        assert!(matches!(p.stages[0], PipeStage::Set { .. }));
1622        assert!(matches!(p.stages[1], PipeStage::Set { .. }));
1623    }
1624
1625    #[test]
1626    fn parse_pipeline_del_then_del() {
1627        let p = Pipeline::parse("del(.a) | del(.b)").unwrap();
1628        assert_eq!(p.stages.len(), 2);
1629        assert!(matches!(p.stages[0], PipeStage::Del(_)));
1630        assert!(matches!(p.stages[1], PipeStage::Del(_)));
1631    }
1632
1633    // ── Filter parsing edge cases ──
1634
1635    #[test]
1636    fn parse_filter_triple_and() {
1637        let expr = parse_filter_expr(".a > 1 and .b > 2 and .c > 3").unwrap();
1638        // Should nest as ((.a > 1) and (.b > 2)) and (.c > 3) — left-associative
1639        assert!(matches!(expr, FilterExpr::And(_, _)));
1640    }
1641
1642    #[test]
1643    fn parse_filter_triple_or() {
1644        let expr = parse_filter_expr(".a > 1 or .b > 2 or .c > 3").unwrap();
1645        assert!(matches!(expr, FilterExpr::Or(_, _)));
1646    }
1647
1648    #[test]
1649    fn parse_filter_not_condition() {
1650        let expr = parse_filter_expr("not .x == 0").unwrap();
1651        match expr {
1652            FilterExpr::Not(inner) => assert!(matches!(*inner, FilterExpr::Condition(_))),
1653            _ => panic!("expected Not(Condition)"),
1654        }
1655    }
1656
1657    #[test]
1658    fn parse_filter_deep_nested_path() {
1659        let expr = parse_filter_expr(".a.b.c.d > 0").unwrap();
1660        match expr {
1661            FilterExpr::Condition(c) => assert_eq!(c.path.segments.len(), 4),
1662            _ => panic!("expected Condition"),
1663        }
1664    }
1665
1666    #[test]
1667    fn parse_filter_empty_string_literal() {
1668        let expr = parse_filter_expr(".name == \"\"").unwrap();
1669        match expr {
1670            FilterExpr::Condition(c) => assert_eq!(c.value, LiteralValue::String("".into())),
1671            _ => panic!("expected Condition"),
1672        }
1673    }
1674
1675    #[test]
1676    fn parse_filter_zero_literal() {
1677        let expr = parse_filter_expr(".count == 0").unwrap();
1678        match expr {
1679            FilterExpr::Condition(c) => assert_eq!(c.value, LiteralValue::Number(0.0)),
1680            _ => panic!("expected Condition"),
1681        }
1682    }
1683
1684    #[test]
1685    fn parse_filter_with_index_in_path() {
1686        let expr = parse_filter_expr(".items[0].name == \"first\"").unwrap();
1687        match expr {
1688            FilterExpr::Condition(c) => {
1689                assert_eq!(c.path.segments.len(), 2);
1690                assert_eq!(c.path.segments[0].key, Some("items".into()));
1691                assert_eq!(c.path.segments[0].indices, vec![Index::Number(0)]);
1692            }
1693            _ => panic!("expected Condition"),
1694        }
1695    }
1696
1697    // ── set/del parsing edge cases ──
1698
1699    #[test]
1700    fn parse_set_with_array_index() {
1701        let p = Pipeline::parse("set(.items[0], 99)").unwrap();
1702        match &p.stages[0] {
1703            PipeStage::Set { path, value } => {
1704                assert_eq!(path.segments[0].key, Some("items".into()));
1705                assert_eq!(path.segments[0].indices, vec![Index::Number(0)]);
1706                assert_eq!(*value, LiteralValue::Number(99.0));
1707            }
1708            _ => panic!("expected Set"),
1709        }
1710    }
1711
1712    #[test]
1713    fn parse_set_float_value() {
1714        let p = Pipeline::parse("set(.score, 3.14)").unwrap();
1715        match &p.stages[0] {
1716            PipeStage::Set { value, .. } => assert_eq!(*value, LiteralValue::Number(3.14)),
1717            _ => panic!("expected Set"),
1718        }
1719    }
1720
1721    #[test]
1722    fn parse_set_negative_value() {
1723        let p = Pipeline::parse("set(.temp, -5)").unwrap();
1724        match &p.stages[0] {
1725            PipeStage::Set { value, .. } => assert_eq!(*value, LiteralValue::Number(-5.0)),
1726            _ => panic!("expected Set"),
1727        }
1728    }
1729
1730    #[test]
1731    fn parse_set_false_value() {
1732        let p = Pipeline::parse("set(.active, false)").unwrap();
1733        match &p.stages[0] {
1734            PipeStage::Set { value, .. } => assert_eq!(*value, LiteralValue::Bool(false)),
1735            _ => panic!("expected Set"),
1736        }
1737    }
1738
1739    #[test]
1740    fn parse_del_with_array_index() {
1741        let p = Pipeline::parse("del(.items[0])").unwrap();
1742        match &p.stages[0] {
1743            PipeStage::Del(path) => {
1744                assert_eq!(path.segments[0].key, Some("items".into()));
1745                assert_eq!(path.segments[0].indices, vec![Index::Number(0)]);
1746            }
1747            _ => panic!("expected Del"),
1748        }
1749    }
1750
1751    #[test]
1752    fn parse_del_deeply_nested() {
1753        let p = Pipeline::parse("del(.a.b.c)").unwrap();
1754        match &p.stages[0] {
1755            PipeStage::Del(path) => assert_eq!(path.segments.len(), 3),
1756            _ => panic!("expected Del"),
1757        }
1758    }
1759
1760    // ── Expression/multi-selector edge cases ──
1761
1762    #[test]
1763    fn parse_expression_with_pipelines() {
1764        let expr = Expression::parse("name, items | length()").unwrap();
1765        assert_eq!(expr.pipelines.len(), 2);
1766        assert_eq!(expr.pipelines[1].stages.len(), 2);
1767    }
1768
1769    #[test]
1770    fn parse_expression_with_builtins() {
1771        let expr = Expression::parse("keys(), values()").unwrap();
1772        assert_eq!(expr.pipelines.len(), 2);
1773    }
1774
1775    #[test]
1776    fn parse_expression_with_recursive() {
1777        let expr = Expression::parse("..name, ..id").unwrap();
1778        assert_eq!(expr.pipelines.len(), 2);
1779    }
1780
1781    // ── Whitespace handling ──
1782
1783    #[test]
1784    fn parse_pipeline_extra_whitespace() {
1785        let p = Pipeline::parse("  items[*]  |  select(.price > 100)  |  name  ").unwrap();
1786        assert_eq!(p.stages.len(), 3);
1787    }
1788
1789    #[test]
1790    fn parse_filter_whitespace_around_ops() {
1791        let expr = parse_filter_expr(".price  >  100").unwrap();
1792        assert!(matches!(expr, FilterExpr::Condition(_)));
1793    }
1794
1795    // ── Literal parsing edge cases ──
1796
1797    #[test]
1798    fn parse_literal_large_number() {
1799        let (v, _) = parse_literal("999999999").unwrap();
1800        assert_eq!(v, LiteralValue::Number(999999999.0));
1801    }
1802
1803    #[test]
1804    fn parse_literal_zero() {
1805        let (v, _) = parse_literal("0").unwrap();
1806        assert_eq!(v, LiteralValue::Number(0.0));
1807    }
1808
1809    #[test]
1810    fn parse_literal_negative_zero() {
1811        let (v, _) = parse_literal("-0").unwrap();
1812        assert_eq!(v, LiteralValue::Number(0.0)); // -0.0 == 0.0
1813    }
1814
1815    #[test]
1816    fn parse_literal_string_with_spaces() {
1817        let (v, _) = parse_literal("\"hello world\"").unwrap();
1818        assert_eq!(v, LiteralValue::String("hello world".into()));
1819    }
1820
1821    #[test]
1822    fn parse_literal_string_with_special_chars() {
1823        let (v, _) = parse_literal("\"foo@bar.com\"").unwrap();
1824        assert_eq!(v, LiteralValue::String("foo@bar.com".into()));
1825    }
1826}