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}