Skip to main content

shape_ast/parser/expressions/
literals.rs

1//! Literal expression parsing
2//!
3//! This module handles parsing of literal values:
4//! - Numbers, strings, booleans, None (Option)
5//! - Colors and timeframes
6//! - Array literals
7//! - Object literals
8
9use crate::ast::{Expr, Literal, Timeframe};
10use crate::error::{Result, ShapeError};
11use crate::int_width::IntWidth;
12use crate::parser::string_literals::parse_string_literal_with_kind;
13use crate::parser::{Rule, pair_location};
14use pest::iterators::Pair;
15
16use super::super::pair_span;
17
18/// Parse a literal value
19pub fn parse_literal(pair: Pair<Rule>) -> Result<Expr> {
20    let pair_loc = pair_location(&pair);
21    let span = pair_span(&pair);
22    let inner = pair
23        .into_inner()
24        .next()
25        .ok_or_else(|| ShapeError::ParseError {
26            message: "expected literal value".to_string(),
27            location: Some(pair_loc.clone()),
28        })?;
29
30    let literal = match inner.as_rule() {
31        Rule::decimal => {
32            let dec_str = inner.as_str();
33            // Remove the 'D' suffix and parse as Decimal
34            let num_part = dec_str.trim_end_matches('D');
35            use rust_decimal::Decimal;
36            Literal::Decimal(
37                num_part
38                    .parse::<Decimal>()
39                    .map_err(|e| ShapeError::ParseError {
40                        message: format!("Invalid decimal: {}", e),
41                        location: None,
42                    })?,
43            )
44        }
45        Rule::percent_literal => {
46            let pct_str = inner.as_str().trim_end_matches('%');
47            let value: f64 = pct_str.parse().map_err(|e| ShapeError::ParseError {
48                message: format!("Invalid percent literal: {}", e),
49                location: None,
50            })?;
51            Literal::Number(value / 100.0)
52        }
53        Rule::number => {
54            let num_str = inner.as_str();
55            // Check for hex/binary/octal prefixes
56            let stripped = num_str.trim_start_matches('-');
57            let is_negative = num_str.starts_with('-');
58            if stripped.starts_with("0x") || stripped.starts_with("0X") {
59                parse_prefixed_int(num_str, stripped, 16, 2, is_negative, &pair_loc)?
60            } else if stripped.starts_with("0b") || stripped.starts_with("0B") {
61                parse_prefixed_int(num_str, stripped, 2, 2, is_negative, &pair_loc)?
62            } else if stripped.starts_with("0o") || stripped.starts_with("0O") {
63                parse_prefixed_int(num_str, stripped, 8, 2, is_negative, &pair_loc)?
64            } else if let Some(lit) = try_parse_suffixed_int(num_str, &pair_loc)? {
65                // Check for integer width suffix
66                lit
67            } else if num_str.contains('.') || num_str.contains('e') || num_str.contains('E') {
68                // Fraction or exponent → f64
69                Literal::Number(num_str.parse().map_err(|e| ShapeError::ParseError {
70                    message: format!("Invalid number: {}", e),
71                    location: None,
72                })?)
73            } else {
74                // Plain integer (no suffix, no decimal).
75                //
76                // R5c-2-β-γ checkpoint (b) u64-carrier: an unsuffixed
77                // decimal literal in `0..2^64` must parse. Values that
78                // fit `i64` keep the default `int` carrier (`Literal::Int`);
79                // values above `i64::MAX` but `<= u64::MAX` are full-range
80                // unsigned literals and produce `Literal::UInt(u64)` — the
81                // type checker unifies them with a `u64` annotation
82                // (`type_system/inference/operators.rs` maps `Literal::UInt`
83                // to the `u64` type). Pre-fix the parser used `parse::<i64>()`
84                // unconditionally, so `let a: u64 = 18446744073709551615`
85                // failed with "Invalid integer: number too large to fit in
86                // target type" — a valid u64 literal rejected at the lexer.
87                match num_str.parse::<i64>() {
88                    Ok(i) => Literal::Int(i),
89                    Err(_) => Literal::UInt(num_str.parse::<u64>().map_err(|e| {
90                        ShapeError::ParseError {
91                            message: format!("Invalid integer: {}", e),
92                            location: None,
93                        }
94                    })?),
95                }
96            }
97        }
98        Rule::string => {
99            let parsed = parse_string_literal_with_kind(inner.as_str())?;
100            if let Some(mode) = parsed.interpolation_mode {
101                Literal::FormattedString {
102                    value: parsed.value,
103                    mode,
104                }
105            } else {
106                Literal::String(parsed.value)
107            }
108        }
109        Rule::boolean => Literal::Bool(inner.as_str() == "true"),
110        Rule::none_literal => Literal::None,
111
112        Rule::char_literal => {
113            let raw = inner.as_str();
114            // Strip surrounding quotes: 'x' -> x
115            let inner_str = &raw[1..raw.len() - 1];
116            let c = parse_char_literal_inner(inner_str).map_err(|msg| ShapeError::ParseError {
117                message: msg,
118                location: Some(pair_loc.clone()),
119            })?;
120            Literal::Char(c)
121        }
122        Rule::timeframe => {
123            let tf = Timeframe::parse(inner.as_str()).ok_or_else(|| ShapeError::ParseError {
124                message: format!("Invalid timeframe: {}", inner.as_str()),
125                location: None,
126            })?;
127            Literal::Timeframe(tf)
128        }
129        _ => {
130            return Err(ShapeError::ParseError {
131                message: format!("Unexpected literal: {:?}", inner.as_rule()),
132                location: None,
133            });
134        }
135    };
136
137    Ok(Expr::Literal(literal, span))
138}
139
140/// Parse the inner content of a char literal (after stripping quotes).
141fn parse_char_literal_inner(s: &str) -> std::result::Result<char, String> {
142    if s.is_empty() {
143        return Err("Empty char literal".to_string());
144    }
145    if s.starts_with('\\') {
146        if s.starts_with("\\u{") && s.ends_with('}') {
147            // Unicode escape: \u{XXXX}
148            let hex = &s[3..s.len() - 1];
149            let code = u32::from_str_radix(hex, 16)
150                .map_err(|_| format!("Invalid unicode escape: {}", s))?;
151            char::from_u32(code)
152                .ok_or_else(|| format!("Invalid unicode code point: U+{:04X}", code))
153        } else if s.len() == 2 {
154            // Simple escape: \n, \t, \r, \\, \', \0
155            match s.as_bytes()[1] {
156                b'n' => Ok('\n'),
157                b't' => Ok('\t'),
158                b'r' => Ok('\r'),
159                b'\\' => Ok('\\'),
160                b'\'' => Ok('\''),
161                b'0' => Ok('\0'),
162                other => Err(format!("Unknown escape sequence: \\{}", other as char)),
163            }
164        } else {
165            Err(format!("Invalid escape sequence: {}", s))
166        }
167    } else {
168        let mut chars = s.chars();
169        let c = chars
170            .next()
171            .ok_or_else(|| "Empty char literal".to_string())?;
172        if chars.next().is_some() {
173            return Err(format!(
174                "Char literal must be a single character, got: {}",
175                s
176            ));
177        }
178        Ok(c)
179    }
180}
181
182/// Parse an array literal
183pub fn parse_array_literal(pair: Pair<Rule>) -> Result<Expr> {
184    let mut elements = Vec::new();
185    let span = pair_span(&pair);
186
187    // Check if we have any inner pairs (empty array case)
188    let inner_pairs: Vec<_> = pair.into_inner().collect();
189
190    // Parse each element in the array
191    for inner_pair in inner_pairs {
192        match inner_pair.as_rule() {
193            Rule::array_elements => {
194                // Parse the array_elements node
195                for element_pair in inner_pair.into_inner() {
196                    match element_pair.as_rule() {
197                        Rule::array_element => {
198                            // Parse each array_element
199                            let elem_loc = pair_location(&element_pair);
200                            let mut elem_inner = element_pair.into_inner();
201                            let elem = elem_inner.next().ok_or_else(|| ShapeError::ParseError {
202                                message: "expected array element content".to_string(),
203                                location: Some(elem_loc.clone()),
204                            })?;
205                            match elem.as_rule() {
206                                Rule::spread_element => {
207                                    // Parse the expression inside the spread
208                                    let elem_span = pair_span(&elem);
209                                    let spread_inner =
210                                        elem.into_inner().next().ok_or_else(|| {
211                                            ShapeError::ParseError {
212                                                message:
213                                                    "expected expression after '...' in spread"
214                                                        .to_string(),
215                                                location: Some(elem_loc),
216                                            }
217                                        })?;
218                                    let spread_expr = super::parse_expression(spread_inner)?;
219                                    elements.push(Expr::Spread(Box::new(spread_expr), elem_span));
220                                }
221                                _ => {
222                                    elements.push(super::parse_expression(elem)?);
223                                }
224                            }
225                        }
226                        _ => {
227                            return Err(ShapeError::ParseError {
228                                message: format!(
229                                    "Unexpected rule in array_elements: {:?}",
230                                    element_pair.as_rule()
231                                ),
232                                location: None,
233                            });
234                        }
235                    }
236                }
237            }
238            Rule::list_comprehension => {
239                // List comprehensions are parsed as the array_literal alternative in grammar
240                // This branch handles when they appear inside an array literal context
241                return super::comprehensions::parse_list_comprehension(inner_pair);
242            }
243            _ => {
244                // This shouldn't happen for array literals
245                return Err(ShapeError::ParseError {
246                    message: format!(
247                        "Unexpected rule in array literal: {:?}",
248                        inner_pair.as_rule()
249                    ),
250                    location: None,
251                });
252            }
253        }
254    }
255
256    Ok(Expr::Array(elements, span))
257}
258
259/// Parse an object literal
260pub fn parse_object_literal(pair: Pair<Rule>) -> Result<Expr> {
261    use crate::ast::ObjectEntry;
262    use crate::parser::types::parse_type_annotation;
263
264    let mut entries = Vec::new();
265    let span = pair_span(&pair);
266
267    // Parse object fields if present
268    for inner_pair in pair.into_inner() {
269        match inner_pair.as_rule() {
270            Rule::object_fields => {
271                for field_item_pair in inner_pair.into_inner() {
272                    match field_item_pair.as_rule() {
273                        Rule::object_field_item => {
274                            let field_item_loc = pair_location(&field_item_pair);
275                            let field_item_inner =
276                                field_item_pair.into_inner().next().ok_or_else(|| {
277                                    ShapeError::ParseError {
278                                        message: "expected object field content".to_string(),
279                                        location: Some(field_item_loc.clone()),
280                                    }
281                                })?;
282                            match field_item_inner.as_rule() {
283                                Rule::object_field => {
284                                    let field_loc = pair_location(&field_item_inner);
285                                    let mut field_inner = field_item_inner.into_inner();
286                                    let field_kind = field_inner.next().ok_or_else(|| {
287                                        ShapeError::ParseError {
288                                            message: "expected object field content".to_string(),
289                                            location: Some(field_loc.clone()),
290                                        }
291                                    })?;
292
293                                    match field_kind.as_rule() {
294                                        Rule::object_typed_field => {
295                                            let mut typed_inner = field_kind.into_inner();
296                                            let key_pair = typed_inner.next().ok_or_else(|| {
297                                                ShapeError::ParseError {
298                                                    message: "expected object field key"
299                                                        .to_string(),
300                                                    location: Some(field_loc.clone()),
301                                                }
302                                            })?;
303                                            let key_pair =
304                                                if key_pair.as_rule() == Rule::object_field_name {
305                                                    key_pair.into_inner().next().ok_or_else(
306                                                        || ShapeError::ParseError {
307                                                            message: "expected object field key"
308                                                                .to_string(),
309                                                            location: Some(field_loc.clone()),
310                                                        },
311                                                    )?
312                                                } else {
313                                                    key_pair
314                                                };
315                                            let key = match key_pair.as_rule() {
316                                                Rule::ident | Rule::keyword => {
317                                                    key_pair.as_str().to_string()
318                                                }
319                                                _ => {
320                                                    return Err(ShapeError::ParseError {
321                                                        message: format!(
322                                                            "unexpected object key type: {:?}",
323                                                            key_pair.as_rule()
324                                                        ),
325                                                        location: Some(pair_location(&key_pair)),
326                                                    });
327                                                }
328                                            };
329
330                                            let type_pair = typed_inner.next().ok_or_else(|| ShapeError::ParseError {
331                                                message: format!("expected type annotation for object field '{}'", key),
332                                                location: Some(field_loc.clone()),
333                                            })?;
334                                            let type_annotation = parse_type_annotation(type_pair)?;
335
336                                            let value_pair =
337                                                typed_inner.next().ok_or_else(|| {
338                                                    ShapeError::ParseError {
339                                                        message: format!(
340                                                            "expected value for object field '{}'",
341                                                            key
342                                                        ),
343                                                        location: Some(field_loc),
344                                                    }
345                                                })?;
346                                            let value = super::parse_expression(value_pair)?;
347
348                                            entries.push(ObjectEntry::Field {
349                                                key,
350                                                value,
351                                                type_annotation: Some(type_annotation),
352                                            });
353                                        }
354                                        Rule::object_value_field => {
355                                            let mut value_inner = field_kind.into_inner();
356                                            let key_pair = value_inner.next().ok_or_else(|| {
357                                                ShapeError::ParseError {
358                                                    message: "expected object field key"
359                                                        .to_string(),
360                                                    location: Some(field_loc.clone()),
361                                                }
362                                            })?;
363                                            let key_pair =
364                                                if key_pair.as_rule() == Rule::object_field_name {
365                                                    key_pair.into_inner().next().ok_or_else(
366                                                        || ShapeError::ParseError {
367                                                            message: "expected object field key"
368                                                                .to_string(),
369                                                            location: Some(field_loc.clone()),
370                                                        },
371                                                    )?
372                                                } else {
373                                                    key_pair
374                                                };
375                                            let key = match key_pair.as_rule() {
376                                                Rule::ident | Rule::keyword => {
377                                                    key_pair.as_str().to_string()
378                                                }
379                                                _ => {
380                                                    return Err(ShapeError::ParseError {
381                                                        message: format!(
382                                                            "unexpected object key type: {:?}",
383                                                            key_pair.as_rule()
384                                                        ),
385                                                        location: Some(pair_location(&key_pair)),
386                                                    });
387                                                }
388                                            };
389
390                                            let value_pair =
391                                                value_inner.next().ok_or_else(|| {
392                                                    ShapeError::ParseError {
393                                                        message: format!(
394                                                            "expected value for object field '{}'",
395                                                            key
396                                                        ),
397                                                        location: Some(field_loc),
398                                                    }
399                                                })?;
400                                            let value = super::parse_expression(value_pair)?;
401
402                                            entries.push(ObjectEntry::Field {
403                                                key,
404                                                value,
405                                                type_annotation: None,
406                                            });
407                                        }
408                                        other => {
409                                            return Err(ShapeError::ParseError {
410                                                message: format!(
411                                                    "unexpected object field kind: {:?}",
412                                                    other
413                                                ),
414                                                location: Some(pair_location(&field_kind)),
415                                            });
416                                        }
417                                    }
418                                }
419                                Rule::object_spread => {
420                                    let spread_expr_pair = field_item_inner
421                                        .into_inner()
422                                        .next()
423                                        .ok_or_else(|| ShapeError::ParseError {
424                                            message: "expected expression after spread operator"
425                                                .to_string(),
426                                            location: Some(field_item_loc),
427                                        })?;
428                                    let spread_expr = super::parse_expression(spread_expr_pair)?;
429                                    entries.push(ObjectEntry::Spread(spread_expr));
430                                }
431                                _ => {
432                                    return Err(ShapeError::ParseError {
433                                        message: format!(
434                                            "Unexpected rule in object_field_item: {:?}",
435                                            field_item_inner.as_rule()
436                                        ),
437                                        location: None,
438                                    });
439                                }
440                            }
441                        }
442                        _ => {
443                            return Err(ShapeError::ParseError {
444                                message: format!(
445                                    "Unexpected rule in object_fields: {:?}",
446                                    field_item_pair.as_rule()
447                                ),
448                                location: None,
449                            });
450                        }
451                    }
452                }
453            }
454            _ => {} // Empty object case
455        }
456    }
457
458    Ok(Expr::Object(entries, span))
459}
460
461/// Parse a prefixed integer literal (hex 0x, binary 0b, octal 0o).
462/// `stripped` is the number string with leading '-' removed, `prefix_len` is the length of the base prefix (2 for "0x"/"0b"/"0o").
463fn parse_prefixed_int(
464    full_str: &str,
465    stripped: &str,
466    radix: u32,
467    prefix_len: usize,
468    is_negative: bool,
469    loc: &crate::error::SourceLocation,
470) -> Result<Literal> {
471    let after_prefix = &stripped[prefix_len..];
472    // Check for width suffix
473    let (digits, width) = try_strip_width_suffix(after_prefix);
474    let value = i64::from_str_radix(digits, radix).map_err(|e| ShapeError::ParseError {
475        message: format!("Invalid base-{} integer '{}': {}", radix, full_str, e),
476        location: Some(loc.clone()),
477    })?;
478    let value = if is_negative { -value } else { value };
479    if let Some(w) = width {
480        if !w.in_range_i64(value) {
481            return Err(ShapeError::ParseError {
482                message: format!(
483                    "Value {} out of range for {}: [{}, {}]",
484                    value,
485                    w.type_name(),
486                    w.min_value(),
487                    w.max_value(),
488                ),
489                location: Some(loc.clone()),
490            });
491        }
492        Ok(Literal::TypedInt(value, w))
493    } else {
494        Ok(Literal::Int(value))
495    }
496}
497
498/// Try to strip width suffix from digit string, returning (digits, optional width).
499fn try_strip_width_suffix(s: &str) -> (&str, Option<IntWidth>) {
500    const SUFFIXES: &[(&str, IntWidth)] = &[
501        ("i32", IntWidth::I32),
502        ("i16", IntWidth::I16),
503        ("i8", IntWidth::I8),
504        ("u64", IntWidth::U64),
505        ("u32", IntWidth::U32),
506        ("u16", IntWidth::U16),
507        ("u8", IntWidth::U8),
508    ];
509    for &(suffix, width) in SUFFIXES {
510        if let Some(digits) = s.strip_suffix(suffix) {
511            return (digits, Some(width));
512        }
513    }
514    (s, None)
515}
516
517/// Try to parse a suffixed integer literal (e.g., "42i8", "255u8", "18446744073709551615u64").
518/// Returns None if no suffix is found. Returns Err for invalid range.
519fn try_parse_suffixed_int(
520    num_str: &str,
521    loc: &crate::error::SourceLocation,
522) -> Result<Option<Literal>> {
523    // Check all suffixes (longer first to avoid prefix issues)
524    const SUFFIXES: &[(&str, IntWidth)] = &[
525        ("i32", IntWidth::I32),
526        ("i16", IntWidth::I16),
527        ("i8", IntWidth::I8),
528        ("u64", IntWidth::U64),
529        ("u32", IntWidth::U32),
530        ("u16", IntWidth::U16),
531        ("u8", IntWidth::U8),
532    ];
533
534    for &(suffix, width) in SUFFIXES {
535        if let Some(digits) = num_str.strip_suffix(suffix) {
536            if digits.is_empty() {
537                return Err(ShapeError::ParseError {
538                    message: format!("Missing digits before '{}'", suffix),
539                    location: Some(loc.clone()),
540                });
541            }
542
543            if width == IntWidth::U64 {
544                // u64: parse as u64 directly (handles values > i64::MAX)
545                let value: u64 = digits.parse().map_err(|e| ShapeError::ParseError {
546                    message: format!("Invalid u64 literal '{}': {}", num_str, e),
547                    location: Some(loc.clone()),
548                })?;
549                if value > i64::MAX as u64 {
550                    return Ok(Some(Literal::UInt(value)));
551                }
552                return Ok(Some(Literal::TypedInt(value as i64, width)));
553            }
554
555            // Signed/unsigned sub-64: parse as i64, then range-check
556            let value: i64 = digits.parse().map_err(|e| ShapeError::ParseError {
557                message: format!("Invalid {} literal '{}': {}", suffix, num_str, e),
558                location: Some(loc.clone()),
559            })?;
560
561            if !width.in_range_i64(value) {
562                // Allow the absolute value of the signed minimum (e.g. 128i8) so that
563                // unary negation can fold it into the valid minimum (-128i8).
564                // This value is only reachable from `-128i8` in source, where the
565                // parser splits `-` as a unary op and `128i8` as the literal.
566                let is_pending_negation =
567                    width.is_signed() && value > 0 && value == -(width.min_value());
568                if !is_pending_negation {
569                    return Err(ShapeError::ParseError {
570                        message: format!(
571                            "Value {} out of range for {}: [{}, {}]",
572                            value,
573                            width.type_name(),
574                            width.min_value(),
575                            width.max_value(),
576                        ),
577                        location: Some(loc.clone()),
578                    });
579                }
580            }
581
582            return Ok(Some(Literal::TypedInt(value, width)));
583        }
584    }
585
586    Ok(None)
587}
588
589#[cfg(test)]
590mod u64_literal_tests {
591    //! R5c-2-β-γ checkpoint (b) u64-carrier — parser/lexer layer.
592    //!
593    //! An unsuffixed decimal literal in `0..2^64` must parse: values
594    //! fitting `i64` keep the default `int` carrier (`Literal::Int`),
595    //! values above `i64::MAX` produce the full-range `Literal::UInt(u64)`.
596    //! Pre-checkpoint-(b) the parser used `parse::<i64>()` unconditionally,
597    //! rejecting any valid u64 literal above `i64::MAX` with
598    //! "Invalid integer: number too large to fit in target type".
599
600    use crate::ast::{Expr, Literal};
601    use crate::parser::parse_expression_str;
602
603    fn parse_int_literal(src: &str) -> Literal {
604        match parse_expression_str(src).expect("should parse") {
605            Expr::Literal(lit, _) => lit,
606            other => panic!("expected literal, got {:?}", other),
607        }
608    }
609
610    #[test]
611    fn unsuffixed_u64_max_parses() {
612        // u64::MAX, unsuffixed — pre-fix this was a hard parse error.
613        let lit = parse_int_literal("18446744073709551615");
614        assert_eq!(lit, Literal::UInt(u64::MAX));
615    }
616
617    #[test]
618    fn unsuffixed_above_i64_max_parses_as_uint() {
619        // i64::MAX + 1 — the first value that no longer fits i64.
620        let lit = parse_int_literal("9223372036854775808");
621        assert_eq!(lit, Literal::UInt(9_223_372_036_854_775_808));
622    }
623
624    #[test]
625    fn unsuffixed_at_i64_max_stays_int() {
626        // i64::MAX itself fits i64 — keeps the default `int` carrier.
627        let lit = parse_int_literal("9223372036854775807");
628        assert_eq!(lit, Literal::Int(i64::MAX));
629    }
630
631    #[test]
632    fn unsuffixed_small_stays_int() {
633        let lit = parse_int_literal("100");
634        assert_eq!(lit, Literal::Int(100));
635    }
636
637    #[test]
638    fn suffixed_u64_max_parses() {
639        // The `u64` suffix path (pre-existing) — still parses to UInt.
640        let lit = parse_int_literal("18446744073709551615u64");
641        assert_eq!(lit, Literal::UInt(u64::MAX));
642    }
643
644    #[test]
645    fn above_u64_max_is_parse_error() {
646        // 2^64 — out of range for u64; still a clean parse error.
647        assert!(parse_expression_str("18446744073709551616").is_err());
648    }
649}