1use std::fmt;
67
68use unicode_normalization::UnicodeNormalization;
69
70use crate::rst::digits::decimal_digit_value;
71
72#[derive(Debug, Clone, PartialEq, Eq)]
76pub enum PyExpr {
77 Name(String),
79 Attribute(Box<PyExpr>, String),
81 Subscript {
84 value: Box<PyExpr>,
85 slice: Box<PyExpr>,
86 },
87 BinOp {
89 left: Box<PyExpr>,
90 op: PyOp,
91 right: Box<PyExpr>,
92 },
93 UnaryOp { op: PyUnaryOp, operand: Box<PyExpr> },
95 Constant(PyConst),
97 Tuple(Vec<PyExpr>),
99 List(Vec<PyExpr>),
101 Set(Vec<PyExpr>),
103 Dict(Vec<(Option<PyExpr>, PyExpr)>),
105 Call {
108 func: Box<PyExpr>,
109 args: Vec<PyExpr>,
110 kwargs: Vec<(String, PyExpr)>,
111 },
112 Starred(Box<PyExpr>),
116 BoolOp { op: PyBoolOp, values: Vec<PyExpr> },
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub enum PyBoolOp {
125 And,
126 Or,
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133pub enum PyOp {
134 Add,
135 Sub,
136 Mult,
137 MatMult,
138 Div,
139 Mod,
140 Pow,
141 LShift,
142 RShift,
143 BitOr,
144 BitXor,
145 BitAnd,
146 FloorDiv,
147}
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
151pub enum PyUnaryOp {
152 Invert,
153 Not,
154 UAdd,
155 USub,
156}
157
158#[derive(Debug, Clone, PartialEq, Eq)]
162pub enum PyConst {
163 None,
164 True,
165 False,
166 Ellipsis,
167 Int(String),
169 Float(String),
171 Str {
177 value: String,
178 quote: char,
179 u_prefix: bool,
180 },
181 Bytes(Vec<u8>),
183}
184
185#[derive(Debug, Clone, PartialEq, Eq)]
188pub struct PyExprError {
189 msg: String,
190}
191
192impl PyExprError {
193 fn new(msg: impl Into<String>) -> Self {
194 Self { msg: msg.into() }
195 }
196}
197
198impl fmt::Display for PyExprError {
199 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200 write!(f, "invalid python expression: {}", self.msg)
201 }
202}
203
204impl std::error::Error for PyExprError {}
205
206pub fn parse_py_expr(s: &str) -> Result<PyExpr, PyExprError> {
210 let mut parser = Parser::new(tokenize(s)?, TopLevel::Eval);
211 if parser.peek().is_none() {
212 return Err(PyExprError::new("empty expression"));
213 }
214 parser.parse_top()
215}
216
217pub fn parse_py_expr_stmt(s: &str) -> Result<Option<PyExpr>, PyExprError> {
228 if has_leading_indent(s) {
229 return Err(PyExprError::new("unexpected indent"));
233 }
234 let mut parser = Parser::new(tokenize(s)?, TopLevel::Exec);
235 if parser.peek().is_none() {
236 return Ok(None);
237 }
238 parser.parse_top().map(Some)
239}
240
241pub fn parse_py_star_annotation(s: &str) -> Result<PyExpr, PyExprError> {
245 let mut parser = Parser::new(tokenize(s)?, TopLevel::StarAnnotation);
246 if parser.peek().is_none() {
247 return Err(PyExprError::new("empty expression"));
248 }
249 parser.parse_top()
250}
251
252fn has_leading_indent(s: &str) -> bool {
258 for line in s.split('\n') {
259 if line
260 .chars()
261 .all(|c| matches!(c, ' ' | '\t' | '\x0c' | '\r' | '\x0b'))
262 {
263 continue;
264 }
265 return line.starts_with([' ', '\t']);
266 }
267 false
268}
269
270pub fn unparse(e: &PyExpr) -> String {
273 let mut out = String::new();
274 write_expr(&mut out, e, Prec::Test);
277 out
278}
279
280const MAX_DEPTH: u32 = 200;
293
294const KEYWORDS: &[&str] = &[
297 "False", "None", "True", "and", "as", "assert", "async", "await", "break", "class", "continue",
298 "def", "del", "elif", "else", "except", "finally", "for", "from", "global", "if", "import",
299 "in", "is", "lambda", "nonlocal", "not", "or", "pass", "raise", "return", "try", "while",
300 "with", "yield",
301];
302
303#[derive(Debug, Clone, PartialEq, Eq)]
304enum Tok {
305 Name(String),
306 Int(String),
308 Float(String),
310 Str {
312 value: String,
313 u_prefix: bool,
314 },
315 Bytes(Vec<u8>),
317 LParen,
318 RParen,
319 LBracket,
320 RBracket,
321 LBrace,
322 RBrace,
323 Comma,
324 Colon,
325 Dot,
326 Ellipsis,
327 Eq,
328 Plus,
329 Minus,
330 Star,
331 DoubleStar,
332 Slash,
333 DoubleSlash,
334 Percent,
335 At,
336 Pipe,
337 Caret,
338 Amp,
339 Tilde,
340 LShift,
341 RShift,
342}
343
344fn is_ident_start(c: char) -> bool {
347 c == '_' || c.is_alphabetic()
348}
349
350fn is_ident_continue(c: char) -> bool {
354 is_ident_start(c) || decimal_digit_value(c).is_some()
355}
356
357struct Tokenizer {
358 chars: Vec<char>,
359 pos: usize,
360}
361
362impl Tokenizer {
363 fn cur(&self) -> Option<char> {
364 self.chars.get(self.pos).copied()
365 }
366
367 fn at(&self, i: usize) -> Option<char> {
368 self.chars.get(i).copied()
369 }
370
371 fn take_while(&mut self, pred: impl Fn(char) -> bool) -> String {
372 let mut out = String::new();
373 while let Some(c) = self.cur() {
374 if !pred(c) {
375 break;
376 }
377 out.push(c);
378 self.pos += 1;
379 }
380 out
381 }
382
383 fn skip_trivia(&mut self) {
387 loop {
388 match self.cur() {
389 Some(c) if c.is_whitespace() => self.pos += 1,
390 Some('#') => {
391 while !matches!(self.cur(), None | Some('\n')) {
392 self.pos += 1;
393 }
394 }
395 Some('\\') if matches!(self.at(self.pos + 1), Some('\n' | '\r')) => {
396 self.pos += 2;
397 }
398 _ => break,
399 }
400 }
401 }
402
403 fn scan_name_or_prefixed_string(&mut self) -> Result<Tok, PyExprError> {
404 let name = self.take_while(is_ident_continue);
405 if matches!(self.cur(), Some('\'' | '"')) {
406 match name.to_ascii_lowercase().as_str() {
407 "r" => return self.scan_string(true, false, false),
408 "b" => return self.scan_string(false, true, false),
409 "u" => return self.scan_string(false, false, true),
410 "rb" | "br" => return self.scan_string(true, true, false),
411 "f" | "rf" | "fr" => {
412 return Err(PyExprError::new(
413 "f-strings are not part of the supported expression subset",
414 ));
415 }
416 _ => {}
419 }
420 }
421 let name = if name.is_ascii() {
423 name
424 } else {
425 name.nfkc().collect()
426 };
427 Ok(Tok::Name(name))
428 }
429
430 fn scan_string(&mut self, raw: bool, bytes: bool, u_prefix: bool) -> Result<Tok, PyExprError> {
431 let Some(quote) = self.cur() else {
432 return Err(PyExprError::new("expected string quote"));
433 };
434 self.pos += 1;
435 let triple = self.cur() == Some(quote) && self.at(self.pos + 1) == Some(quote);
436 if triple {
437 self.pos += 2;
438 }
439 let mut body = String::new();
440 loop {
441 let Some(c) = self.cur() else {
442 return Err(PyExprError::new("unterminated string literal"));
443 };
444 if c == '\\' {
445 let Some(next) = self.at(self.pos + 1) else {
449 return Err(PyExprError::new("unterminated string literal"));
450 };
451 body.push('\\');
452 body.push(next);
453 self.pos += 2;
454 continue;
455 }
456 if c == quote {
457 if !triple {
458 self.pos += 1;
459 break;
460 }
461 if self.at(self.pos + 1) == Some(quote) && self.at(self.pos + 2) == Some(quote) {
462 self.pos += 3;
463 break;
464 }
465 } else if c == '\n' && !triple {
466 return Err(PyExprError::new("EOL inside string literal"));
467 }
468 body.push(c);
469 self.pos += 1;
470 }
471 if bytes {
472 let value = if raw {
473 raw_bytes(&body)?
474 } else {
475 decode_bytes_escapes(&body)?
476 };
477 Ok(Tok::Bytes(value))
478 } else {
479 let value = if raw {
480 body
481 } else {
482 decode_str_escapes(&body)?
483 };
484 Ok(Tok::Str { value, u_prefix })
485 }
486 }
487
488 fn scan_number(&mut self) -> Result<Tok, PyExprError> {
489 if self.cur() == Some('0') {
490 let base = match self.at(self.pos + 1) {
491 Some('x' | 'X') => Some(16),
492 Some('o' | 'O') => Some(8),
493 Some('b' | 'B') => Some(2),
494 _ => None,
495 };
496 if let Some(base) = base {
497 self.pos += 2;
498 let run = self.take_while(|c| c == '_' || c.is_digit(base));
499 let digits = strip_underscores(&run, true)?;
500 if digits.is_empty() || self.cur().is_some_and(|c| c.is_ascii_digit()) {
501 return Err(PyExprError::new("invalid digit in numeric literal"));
502 }
503 return Ok(Tok::Int(based_digits_to_decimal(&digits, base)));
504 }
505 }
506 let int_run = self.take_while(|c| c.is_ascii_digit() || c == '_');
507 let int_digits = strip_underscores(&int_run, false)?;
508 let mut is_float = false;
509 let mut frac_digits = String::new();
510 if self.cur() == Some('.') {
511 is_float = true;
512 self.pos += 1;
513 let frac_run = self.take_while(|c| c.is_ascii_digit() || c == '_');
514 frac_digits = strip_underscores(&frac_run, false)?;
515 }
516 let mut exp_part: Option<String> = None;
517 if matches!(self.cur(), Some('e' | 'E')) {
518 let mut look = self.pos + 1;
519 let mut negative = false;
520 if let Some(sign @ ('+' | '-')) = self.at(look) {
521 negative = sign == '-';
522 look += 1;
523 }
524 if self.at(look).is_some_and(|c| c.is_ascii_digit()) {
527 self.pos = look;
528 let run = self.take_while(|c| c.is_ascii_digit() || c == '_');
529 let digits = strip_underscores(&run, false)?;
530 is_float = true;
531 exp_part = Some(if negative {
532 format!("-{digits}")
533 } else {
534 digits
535 });
536 }
537 }
538 if matches!(self.cur(), Some('j' | 'J')) {
539 return Err(PyExprError::new(
540 "complex literals are not part of the supported expression subset",
541 ));
542 }
543 if is_float {
544 let mut text = String::new();
545 text.push_str(if int_digits.is_empty() {
546 "0"
547 } else {
548 &int_digits
549 });
550 text.push('.');
551 text.push_str(if frac_digits.is_empty() {
552 "0"
553 } else {
554 &frac_digits
555 });
556 if let Some(exp) = &exp_part {
557 text.push('e');
558 text.push_str(exp);
559 }
560 let value: f64 = text
561 .parse()
562 .map_err(|_| PyExprError::new("invalid float literal"))?;
563 return Ok(Tok::Float(py_float_repr(value)));
564 }
565 if int_digits.is_empty() {
566 return Err(PyExprError::new("invalid numeric literal"));
567 }
568 if int_digits.len() > 1
569 && int_digits.starts_with('0')
570 && int_digits.bytes().any(|b| b != b'0')
571 {
572 return Err(PyExprError::new(
573 "leading zeros in decimal integer literals are not permitted",
574 ));
575 }
576 let trimmed = int_digits.trim_start_matches('0');
577 Ok(Tok::Int(if trimmed.is_empty() {
578 "0".to_string()
579 } else {
580 trimmed.to_string()
581 }))
582 }
583
584 fn scan_operator(&mut self) -> Result<Tok, PyExprError> {
585 let Some(c) = self.cur() else {
586 return Err(PyExprError::new("unexpected end of input"));
587 };
588 let next = self.at(self.pos + 1);
589 let (tok, len) = match (c, next) {
590 ('*', Some('*')) => (Tok::DoubleStar, 2),
591 ('/', Some('/')) => (Tok::DoubleSlash, 2),
592 ('<', Some('<')) => (Tok::LShift, 2),
593 ('>', Some('>')) => (Tok::RShift, 2),
594 ('<' | '>', _) | ('=', Some('=')) | ('!', Some('=')) => {
595 return Err(PyExprError::new(
596 "comparison operators are not part of the supported expression subset",
597 ));
598 }
599 ('(', _) => (Tok::LParen, 1),
600 (')', _) => (Tok::RParen, 1),
601 ('[', _) => (Tok::LBracket, 1),
602 (']', _) => (Tok::RBracket, 1),
603 ('{', _) => (Tok::LBrace, 1),
604 ('}', _) => (Tok::RBrace, 1),
605 (',', _) => (Tok::Comma, 1),
606 (':', _) => (Tok::Colon, 1),
607 ('=', _) => (Tok::Eq, 1),
608 ('+', _) => (Tok::Plus, 1),
609 ('-', _) => (Tok::Minus, 1),
610 ('*', _) => (Tok::Star, 1),
611 ('/', _) => (Tok::Slash, 1),
612 ('%', _) => (Tok::Percent, 1),
613 ('@', _) => (Tok::At, 1),
614 ('|', _) => (Tok::Pipe, 1),
615 ('^', _) => (Tok::Caret, 1),
616 ('&', _) => (Tok::Amp, 1),
617 ('~', _) => (Tok::Tilde, 1),
618 _ => {
619 return Err(PyExprError::new(format!(
620 "unsupported character {c:?} in expression"
621 )));
622 }
623 };
624 self.pos += len;
625 Ok(tok)
626 }
627}
628
629fn tokenize(src: &str) -> Result<Vec<Tok>, PyExprError> {
630 let mut t = Tokenizer {
631 chars: src.chars().collect(),
632 pos: 0,
633 };
634 let mut toks = Vec::new();
635 loop {
636 t.skip_trivia();
637 let Some(c) = t.cur() else { break };
638 let tok = if is_ident_start(c) {
639 t.scan_name_or_prefixed_string()?
640 } else if c.is_ascii_digit() {
641 t.scan_number()?
642 } else if c == '\'' || c == '"' {
643 t.scan_string(false, false, false)?
644 } else if c == '.' {
645 if t.at(t.pos + 1) == Some('.') && t.at(t.pos + 2) == Some('.') {
646 t.pos += 3;
647 Tok::Ellipsis
648 } else if t.at(t.pos + 1).is_some_and(|d| d.is_ascii_digit()) {
649 t.scan_number()?
650 } else {
651 t.pos += 1;
652 Tok::Dot
653 }
654 } else {
655 t.scan_operator()?
656 };
657 toks.push(tok);
658 }
659 Ok(toks)
660}
661
662fn strip_underscores(run: &str, allow_leading: bool) -> Result<String, PyExprError> {
665 let mut out = String::with_capacity(run.len());
666 let mut last_was_underscore = false;
667 for (i, c) in run.chars().enumerate() {
668 if c == '_' {
669 let legal = if i == 0 {
670 allow_leading
671 } else {
672 !last_was_underscore
673 };
674 if !legal {
675 return Err(PyExprError::new("invalid underscore in numeric literal"));
676 }
677 last_was_underscore = true;
678 } else {
679 out.push(c);
680 last_was_underscore = false;
681 }
682 }
683 if last_was_underscore {
684 return Err(PyExprError::new("invalid underscore in numeric literal"));
685 }
686 Ok(out)
687}
688
689fn based_digits_to_decimal(digits: &str, base: u32) -> String {
693 let mut dec: Vec<u8> = vec![0];
694 for c in digits.chars() {
695 let mut carry = c.to_digit(base).unwrap_or(0);
696 for slot in dec.iter_mut() {
697 let v = u32::from(*slot) * base + carry;
698 *slot = (v % 10) as u8;
699 carry = v / 10;
700 }
701 while carry > 0 {
702 dec.push((carry % 10) as u8);
703 carry /= 10;
704 }
705 }
706 dec.iter().rev().map(|d| char::from(b'0' + d)).collect()
707}
708
709fn py_float_repr(v: f64) -> String {
717 debug_assert!(
718 v >= 0.0 || v.is_nan(),
719 "py_float_repr takes magnitudes only"
720 );
721 if v.is_infinite() {
722 return "1e309".to_string();
723 }
724 let sci = format!("{v:e}");
726 let (mantissa, exp_text) = sci.split_once('e').unwrap_or((sci.as_str(), "0"));
727 let digits: String = mantissa.chars().filter(|c| *c != '.').collect();
728 let exp10: i32 = exp_text.parse().unwrap_or(0);
729 if (-4..16).contains(&exp10) {
730 if exp10 >= 0 {
731 let int_len = exp10.unsigned_abs() as usize + 1;
732 if digits.len() > int_len {
733 format!("{}.{}", &digits[..int_len], &digits[int_len..])
734 } else {
735 let zeros = "0".repeat(int_len - digits.len());
736 format!("{digits}{zeros}.0")
737 }
738 } else {
739 let zeros = "0".repeat(exp10.unsigned_abs() as usize - 1);
740 format!("0.{zeros}{digits}")
741 }
742 } else {
743 let mantissa_out = if digits.len() == 1 {
744 digits
745 } else {
746 format!("{}.{}", &digits[..1], &digits[1..])
747 };
748 let sign = if exp10 < 0 { '-' } else { '+' };
749 format!("{mantissa_out}e{sign}{:02}", exp10.unsigned_abs())
750 }
751}
752
753fn take_hex(chars: &[char], i: &mut usize, n: usize, kind: &str) -> Result<u32, PyExprError> {
754 let mut val: u32 = 0;
755 for _ in 0..n {
756 let digit = chars.get(*i).and_then(|c| c.to_digit(16));
757 let Some(digit) = digit else {
758 return Err(PyExprError::new(format!("truncated {kind} escape")));
759 };
760 val = val * 16 + digit;
761 *i += 1;
762 }
763 Ok(val)
764}
765
766fn take_octal(chars: &[char], i: &mut usize, first: char) -> u32 {
767 let mut val = first.to_digit(8).unwrap_or(0);
768 for _ in 0..2 {
769 let Some(digit) = chars.get(*i).and_then(|c| c.to_digit(8)) else {
770 break;
771 };
772 val = val * 8 + digit;
773 *i += 1;
774 }
775 val
776}
777
778fn decode_str_escapes(body: &str) -> Result<String, PyExprError> {
783 let chars: Vec<char> = body.chars().collect();
784 let mut out = String::with_capacity(body.len());
785 let mut i = 0;
786 while i < chars.len() {
787 let Some(&c) = chars.get(i) else { break };
788 if c != '\\' {
789 out.push(c);
790 i += 1;
791 continue;
792 }
793 let Some(&esc) = chars.get(i + 1) else {
794 return Err(PyExprError::new("trailing backslash in string literal"));
795 };
796 i += 2;
797 match esc {
798 '\n' => {}
799 '\\' => out.push('\\'),
800 '\'' => out.push('\''),
801 '"' => out.push('"'),
802 'a' => out.push('\u{7}'),
803 'b' => out.push('\u{8}'),
804 'f' => out.push('\u{c}'),
805 'n' => out.push('\n'),
806 'r' => out.push('\r'),
807 't' => out.push('\t'),
808 'v' => out.push('\u{b}'),
809 '0'..='7' => {
810 let val = take_octal(&chars, &mut i, esc);
811 out.push(char::from_u32(val).unwrap_or('\u{fffd}'));
813 }
814 'x' => {
815 let val = take_hex(&chars, &mut i, 2, "\\xXX")?;
816 out.push(char::from_u32(val).unwrap_or('\u{fffd}'));
817 }
818 'u' => {
819 let val = take_hex(&chars, &mut i, 4, "\\uXXXX")?;
820 let Some(decoded) = char::from_u32(val) else {
821 return Err(PyExprError::new("surrogate escapes are not supported"));
822 };
823 out.push(decoded);
824 }
825 'U' => {
826 let val = take_hex(&chars, &mut i, 8, "\\UXXXXXXXX")?;
827 let Some(decoded) = char::from_u32(val) else {
828 return Err(PyExprError::new("invalid \\U escape value"));
829 };
830 out.push(decoded);
831 }
832 'N' => {
833 return Err(PyExprError::new(
834 "\\N{...} escapes are not supported (no unicodedata)",
835 ));
836 }
837 other => {
838 out.push('\\');
839 out.push(other);
840 }
841 }
842 }
843 Ok(out)
844}
845
846fn decode_bytes_escapes(body: &str) -> Result<Vec<u8>, PyExprError> {
850 let chars: Vec<char> = body.chars().collect();
851 let mut out = Vec::with_capacity(body.len());
852 let mut i = 0;
853 while i < chars.len() {
854 let Some(&c) = chars.get(i) else { break };
855 if c != '\\' {
856 if !c.is_ascii() {
857 return Err(PyExprError::new(
858 "bytes can only contain ASCII literal characters",
859 ));
860 }
861 out.push(c as u8);
862 i += 1;
863 continue;
864 }
865 let Some(&esc) = chars.get(i + 1) else {
866 return Err(PyExprError::new("trailing backslash in bytes literal"));
867 };
868 i += 2;
869 match esc {
870 '\n' => {}
871 '\\' => out.push(b'\\'),
872 '\'' => out.push(b'\''),
873 '"' => out.push(b'"'),
874 'a' => out.push(0x07),
875 'b' => out.push(0x08),
876 'f' => out.push(0x0c),
877 'n' => out.push(b'\n'),
878 'r' => out.push(b'\r'),
879 't' => out.push(b'\t'),
880 'v' => out.push(0x0b),
881 '0'..='7' => out.push(take_octal(&chars, &mut i, esc) as u8),
882 'x' => out.push(take_hex(&chars, &mut i, 2, "\\xXX")? as u8),
883 other => {
886 if !other.is_ascii() {
887 return Err(PyExprError::new(
888 "bytes can only contain ASCII literal characters",
889 ));
890 }
891 out.push(b'\\');
892 out.push(other as u8);
893 }
894 }
895 }
896 Ok(out)
897}
898
899fn raw_bytes(body: &str) -> Result<Vec<u8>, PyExprError> {
900 if !body.is_ascii() {
901 return Err(PyExprError::new(
902 "bytes can only contain ASCII literal characters",
903 ));
904 }
905 Ok(body.bytes().collect())
906}
907
908#[derive(Debug, Clone, Copy, PartialEq, Eq)]
934enum TopLevel {
935 Eval,
938 Exec,
941 StarAnnotation,
944}
945
946struct Parser {
947 toks: Vec<Tok>,
948 pos: usize,
949 depth: u32,
950 top: TopLevel,
951}
952
953fn level_op(level: usize, tok: &Tok) -> Option<PyOp> {
955 match (level, tok) {
956 (0, Tok::Pipe) => Some(PyOp::BitOr),
957 (1, Tok::Caret) => Some(PyOp::BitXor),
958 (2, Tok::Amp) => Some(PyOp::BitAnd),
959 (3, Tok::LShift) => Some(PyOp::LShift),
960 (3, Tok::RShift) => Some(PyOp::RShift),
961 (4, Tok::Plus) => Some(PyOp::Add),
962 (4, Tok::Minus) => Some(PyOp::Sub),
963 (5, Tok::Star) => Some(PyOp::Mult),
964 (5, Tok::Slash) => Some(PyOp::Div),
965 (5, Tok::DoubleSlash) => Some(PyOp::FloorDiv),
966 (5, Tok::Percent) => Some(PyOp::Mod),
967 (5, Tok::At) => Some(PyOp::MatMult),
968 _ => None,
969 }
970}
971
972impl Parser {
973 fn new(toks: Vec<Tok>, top: TopLevel) -> Self {
974 Self {
975 toks,
976 pos: 0,
977 depth: 0,
978 top,
979 }
980 }
981
982 fn peek(&self) -> Option<&Tok> {
983 self.toks.get(self.pos)
984 }
985
986 fn peek_keyword(&self, kw: &str) -> bool {
989 matches!(self.peek(), Some(Tok::Name(n)) if n == kw)
990 }
991
992 fn eat_keyword(&mut self, kw: &str) -> bool {
993 if self.peek_keyword(kw) {
994 self.pos += 1;
995 true
996 } else {
997 false
998 }
999 }
1000
1001 fn eat(&mut self, t: &Tok) -> bool {
1002 if self.peek() == Some(t) {
1003 self.pos += 1;
1004 true
1005 } else {
1006 false
1007 }
1008 }
1009
1010 fn expect(&mut self, t: &Tok, what: &str) -> Result<(), PyExprError> {
1011 if self.eat(t) {
1012 Ok(())
1013 } else {
1014 Err(PyExprError::new(format!("expected {what}")))
1015 }
1016 }
1017
1018 fn charge_node(&mut self) -> Result<(), PyExprError> {
1027 self.depth += 1;
1028 if self.depth > MAX_DEPTH {
1029 return Err(PyExprError::new("expression is too deeply nested"));
1030 }
1031 Ok(())
1032 }
1033
1034 fn parse_top(&mut self) -> Result<PyExpr, PyExprError> {
1035 let first = self.parse_star_or_expr()?;
1036 let mut elts = vec![first];
1037 let mut tuple = false;
1038 while self.eat(&Tok::Comma) {
1039 tuple = true;
1040 if self.peek().is_none() {
1041 break;
1042 }
1043 elts.push(self.parse_star_or_expr()?);
1044 }
1045 if self.peek().is_some() {
1046 return Err(PyExprError::new("unexpected trailing input"));
1047 }
1048 let starred = elts.iter().any(|e| matches!(e, PyExpr::Starred(_)));
1054 let star_ok = match self.top {
1055 TopLevel::Eval => false,
1056 TopLevel::Exec => true,
1057 TopLevel::StarAnnotation => !tuple,
1058 };
1059 if starred && !star_ok {
1060 return Err(PyExprError::new("cannot use starred expression here"));
1061 }
1062 if tuple {
1063 return Ok(PyExpr::Tuple(elts));
1064 }
1065 match elts.into_iter().next() {
1066 Some(e) => Ok(e),
1067 None => Err(PyExprError::new("empty expression")),
1068 }
1069 }
1070
1071 fn parse_star_or_expr(&mut self) -> Result<PyExpr, PyExprError> {
1074 if self.eat(&Tok::Star) {
1075 Ok(PyExpr::Starred(Box::new(self.parse_binop(0)?)))
1076 } else {
1077 self.parse_expr()
1078 }
1079 }
1080
1081 fn parse_expr(&mut self) -> Result<PyExpr, PyExprError> {
1082 self.depth += 1;
1083 if self.depth > MAX_DEPTH {
1084 self.depth -= 1;
1085 return Err(PyExprError::new("expression is too deeply nested"));
1086 }
1087 let result = self.parse_expr_inner();
1088 self.depth -= 1;
1089 result
1090 }
1091
1092 fn parse_expr_inner(&mut self) -> Result<PyExpr, PyExprError> {
1093 self.parse_bool_op(PyBoolOp::Or)
1094 }
1095
1096 fn parse_bool_op(&mut self, op: PyBoolOp) -> Result<PyExpr, PyExprError> {
1100 let kw = match op {
1101 PyBoolOp::Or => "or",
1102 PyBoolOp::And => "and",
1103 };
1104 let first = self.parse_bool_operand(op)?;
1105 if !self.peek_keyword(kw) {
1106 return Ok(first);
1107 }
1108 let mut values = vec![first];
1109 while self.eat_keyword(kw) {
1110 self.charge_node()?;
1111 values.push(self.parse_bool_operand(op)?);
1112 }
1113 Ok(PyExpr::BoolOp { op, values })
1114 }
1115
1116 fn parse_bool_operand(&mut self, op: PyBoolOp) -> Result<PyExpr, PyExprError> {
1117 match op {
1118 PyBoolOp::Or => self.parse_bool_op(PyBoolOp::And),
1119 PyBoolOp::And => self.parse_not(),
1120 }
1121 }
1122
1123 fn parse_not(&mut self) -> Result<PyExpr, PyExprError> {
1126 if !self.peek_keyword("not") {
1127 return self.parse_binop(0);
1128 }
1129 self.depth += 1;
1130 if self.depth > MAX_DEPTH {
1131 self.depth -= 1;
1132 return Err(PyExprError::new("expression is too deeply nested"));
1133 }
1134 self.pos += 1;
1135 let operand = self.parse_not();
1136 self.depth -= 1;
1137 Ok(PyExpr::UnaryOp {
1138 op: PyUnaryOp::Not,
1139 operand: Box::new(operand?),
1140 })
1141 }
1142
1143 fn parse_binop(&mut self, level: usize) -> Result<PyExpr, PyExprError> {
1144 if level == 6 {
1145 return self.parse_factor();
1146 }
1147 let mut left = self.parse_binop(level + 1)?;
1148 while let Some(op) = self.peek().and_then(|t| level_op(level, t)) {
1149 self.pos += 1;
1150 self.charge_node()?;
1151 let right = self.parse_binop(level + 1)?;
1152 left = PyExpr::BinOp {
1153 left: Box::new(left),
1154 op,
1155 right: Box::new(right),
1156 };
1157 }
1158 Ok(left)
1159 }
1160
1161 fn parse_factor(&mut self) -> Result<PyExpr, PyExprError> {
1162 self.depth += 1;
1163 if self.depth > MAX_DEPTH {
1164 self.depth -= 1;
1165 return Err(PyExprError::new("expression is too deeply nested"));
1166 }
1167 let result = self.parse_factor_inner();
1168 self.depth -= 1;
1169 result
1170 }
1171
1172 fn parse_factor_inner(&mut self) -> Result<PyExpr, PyExprError> {
1173 let op = match self.peek() {
1174 Some(Tok::Plus) => Some(PyUnaryOp::UAdd),
1175 Some(Tok::Minus) => Some(PyUnaryOp::USub),
1176 Some(Tok::Tilde) => Some(PyUnaryOp::Invert),
1177 _ => None,
1178 };
1179 if let Some(op) = op {
1180 self.pos += 1;
1181 let operand = self.parse_factor()?;
1182 return Ok(PyExpr::UnaryOp {
1183 op,
1184 operand: Box::new(operand),
1185 });
1186 }
1187 self.parse_power()
1188 }
1189
1190 fn parse_power(&mut self) -> Result<PyExpr, PyExprError> {
1191 let base = self.parse_postfix()?;
1192 if self.eat(&Tok::DoubleStar) {
1193 let right = self.parse_factor()?;
1195 return Ok(PyExpr::BinOp {
1196 left: Box::new(base),
1197 op: PyOp::Pow,
1198 right: Box::new(right),
1199 });
1200 }
1201 Ok(base)
1202 }
1203
1204 fn parse_postfix(&mut self) -> Result<PyExpr, PyExprError> {
1205 let mut e = self.parse_atom()?;
1206 loop {
1207 if !matches!(self.peek(), Some(Tok::Dot | Tok::LParen | Tok::LBracket)) {
1208 return Ok(e);
1209 }
1210 self.charge_node()?;
1211 if self.eat(&Tok::Dot) {
1212 let attr = match self.peek() {
1213 Some(Tok::Name(n)) => n.clone(),
1214 _ => return Err(PyExprError::new("expected attribute name after '.'")),
1215 };
1216 if KEYWORDS.contains(&attr.as_str()) {
1217 return Err(PyExprError::new("keyword cannot be an attribute name"));
1218 }
1219 self.pos += 1;
1220 e = PyExpr::Attribute(Box::new(e), attr);
1221 } else if self.eat(&Tok::LParen) {
1222 e = self.parse_call(e)?;
1223 } else if self.eat(&Tok::LBracket) {
1224 e = self.parse_subscript(e)?;
1225 } else {
1226 return Ok(e);
1227 }
1228 }
1229 }
1230
1231 fn parse_atom(&mut self) -> Result<PyExpr, PyExprError> {
1232 let Some(tok) = self.peek() else {
1233 return Err(PyExprError::new("unexpected end of expression"));
1234 };
1235 match tok {
1236 Tok::Name(n) => {
1237 let name = n.clone();
1238 self.pos += 1;
1239 match name.as_str() {
1240 "True" => Ok(PyExpr::Constant(PyConst::True)),
1241 "False" => Ok(PyExpr::Constant(PyConst::False)),
1242 "None" => Ok(PyExpr::Constant(PyConst::None)),
1243 _ if KEYWORDS.contains(&name.as_str()) => Err(PyExprError::new(format!(
1244 "keyword {name:?} is not part of the supported expression subset"
1245 ))),
1246 _ => Ok(PyExpr::Name(name)),
1247 }
1248 }
1249 Tok::Int(digits) => {
1250 let digits = digits.clone();
1251 self.pos += 1;
1252 Ok(PyExpr::Constant(PyConst::Int(digits)))
1253 }
1254 Tok::Float(text) => {
1255 let text = text.clone();
1256 self.pos += 1;
1257 Ok(PyExpr::Constant(PyConst::Float(text)))
1258 }
1259 Tok::Str { .. } | Tok::Bytes(_) => self.parse_string_concat(),
1260 Tok::Ellipsis => {
1261 self.pos += 1;
1262 Ok(PyExpr::Constant(PyConst::Ellipsis))
1263 }
1264 Tok::LParen => {
1265 self.pos += 1;
1266 self.parse_paren()
1267 }
1268 Tok::LBracket => {
1269 self.pos += 1;
1270 self.parse_list()
1271 }
1272 Tok::LBrace => {
1273 self.pos += 1;
1274 self.parse_brace()
1275 }
1276 other => Err(PyExprError::new(format!("unexpected token {other:?}"))),
1277 }
1278 }
1279
1280 fn parse_string_concat(&mut self) -> Result<PyExpr, PyExprError> {
1284 enum Acc {
1285 Str { value: String, u_prefix: bool },
1286 Bytes(Vec<u8>),
1287 }
1288 let mut acc = match self.peek() {
1289 Some(Tok::Str { value, u_prefix }) => Acc::Str {
1290 value: value.clone(),
1291 u_prefix: *u_prefix,
1292 },
1293 Some(Tok::Bytes(b)) => Acc::Bytes(b.clone()),
1294 _ => return Err(PyExprError::new("expected string literal")),
1295 };
1296 self.pos += 1;
1297 loop {
1298 match (self.peek(), &mut acc) {
1299 (Some(Tok::Str { value, .. }), Acc::Str { value: v, .. }) => {
1300 v.push_str(value);
1301 self.pos += 1;
1302 }
1303 (Some(Tok::Bytes(b)), Acc::Bytes(v)) => {
1304 v.extend_from_slice(b);
1305 self.pos += 1;
1306 }
1307 (Some(Tok::Str { .. }), Acc::Bytes(_)) | (Some(Tok::Bytes(_)), Acc::Str { .. }) => {
1308 return Err(PyExprError::new("cannot mix bytes and nonbytes literals"));
1309 }
1310 _ => break,
1311 }
1312 }
1313 Ok(match acc {
1314 Acc::Str { value, u_prefix } => {
1315 let quote = repr_quote(&value);
1316 PyExpr::Constant(PyConst::Str {
1317 value,
1318 quote,
1319 u_prefix,
1320 })
1321 }
1322 Acc::Bytes(b) => PyExpr::Constant(PyConst::Bytes(b)),
1323 })
1324 }
1325
1326 fn parse_paren(&mut self) -> Result<PyExpr, PyExprError> {
1327 if self.eat(&Tok::RParen) {
1328 return Ok(PyExpr::Tuple(Vec::new()));
1329 }
1330 let first = self.parse_star_or_expr()?;
1331 let mut elts = vec![first];
1332 let mut tuple = false;
1333 while self.eat(&Tok::Comma) {
1334 tuple = true;
1335 if self.peek() == Some(&Tok::RParen) {
1336 break;
1337 }
1338 elts.push(self.parse_star_or_expr()?);
1339 }
1340 self.expect(&Tok::RParen, "')'")?;
1341 if tuple {
1342 return Ok(PyExpr::Tuple(elts));
1343 }
1344 match elts.into_iter().next() {
1345 Some(PyExpr::Starred(_)) => Err(PyExprError::new("cannot use starred expression here")),
1348 Some(e) => Ok(e),
1350 None => Err(PyExprError::new("empty parentheses")),
1351 }
1352 }
1353
1354 fn parse_list(&mut self) -> Result<PyExpr, PyExprError> {
1355 let mut elts = Vec::new();
1356 if !self.eat(&Tok::RBracket) {
1357 loop {
1358 elts.push(self.parse_star_or_expr()?);
1359 if self.eat(&Tok::Comma) {
1360 if self.eat(&Tok::RBracket) {
1361 break;
1362 }
1363 continue;
1364 }
1365 self.expect(&Tok::RBracket, "']'")?;
1366 break;
1367 }
1368 }
1369 Ok(PyExpr::List(elts))
1370 }
1371
1372 fn parse_brace(&mut self) -> Result<PyExpr, PyExprError> {
1373 if self.eat(&Tok::RBrace) {
1374 return Ok(PyExpr::Dict(Vec::new()));
1375 }
1376 if self.peek() == Some(&Tok::DoubleStar) {
1377 return self.parse_dict_items(None);
1378 }
1379 let first = self.parse_star_or_expr()?;
1380 if !matches!(first, PyExpr::Starred(_)) && self.peek() == Some(&Tok::Colon) {
1381 return self.parse_dict_items(Some(first));
1382 }
1383 self.parse_set_items(first)
1384 }
1385
1386 fn parse_dict_items(&mut self, first_key: Option<PyExpr>) -> Result<PyExpr, PyExprError> {
1387 let mut items = Vec::new();
1388 if let Some(key) = first_key {
1389 self.expect(&Tok::Colon, "':'")?;
1390 items.push((Some(key), self.parse_expr()?));
1391 } else {
1392 self.expect(&Tok::DoubleStar, "'**'")?;
1393 items.push((None, self.parse_binop(0)?));
1395 }
1396 loop {
1397 if self.eat(&Tok::Comma) {
1398 if self.eat(&Tok::RBrace) {
1399 break;
1400 }
1401 if self.eat(&Tok::DoubleStar) {
1402 items.push((None, self.parse_binop(0)?));
1403 } else {
1404 let key = self.parse_expr()?;
1405 self.expect(&Tok::Colon, "':'")?;
1406 items.push((Some(key), self.parse_expr()?));
1407 }
1408 continue;
1409 }
1410 self.expect(&Tok::RBrace, "'}'")?;
1411 break;
1412 }
1413 Ok(PyExpr::Dict(items))
1414 }
1415
1416 fn parse_set_items(&mut self, first: PyExpr) -> Result<PyExpr, PyExprError> {
1417 let mut elts = vec![first];
1418 loop {
1419 if self.eat(&Tok::Comma) {
1420 if self.eat(&Tok::RBrace) {
1421 break;
1422 }
1423 elts.push(self.parse_star_or_expr()?);
1424 continue;
1425 }
1426 self.expect(&Tok::RBrace, "'}'")?;
1427 break;
1428 }
1429 Ok(PyExpr::Set(elts))
1430 }
1431
1432 fn parse_call(&mut self, func: PyExpr) -> Result<PyExpr, PyExprError> {
1438 let mut args = Vec::new();
1439 let mut kwargs: Vec<(String, PyExpr)> = Vec::new();
1440 if !self.eat(&Tok::RParen) {
1441 loop {
1442 if self.peek() == Some(&Tok::DoubleStar) {
1443 return Err(PyExprError::new(
1444 "** unpacking in a call is not representable",
1445 ));
1446 }
1447 if self.eat(&Tok::Star) {
1448 args.push(PyExpr::Starred(Box::new(self.parse_binop(0)?)));
1449 } else {
1450 let e = self.parse_expr()?;
1451 if self.peek() == Some(&Tok::Eq) {
1452 let PyExpr::Name(name) = e else {
1453 return Err(PyExprError::new(
1454 "keyword argument name must be an identifier",
1455 ));
1456 };
1457 self.pos += 1;
1458 kwargs.push((name, self.parse_expr()?));
1459 } else {
1460 if !kwargs.is_empty() {
1461 return Err(PyExprError::new(
1462 "positional argument follows keyword argument",
1463 ));
1464 }
1465 args.push(e);
1466 }
1467 }
1468 if self.eat(&Tok::Comma) {
1469 if self.eat(&Tok::RParen) {
1470 break;
1471 }
1472 continue;
1473 }
1474 self.expect(&Tok::RParen, "')'")?;
1475 break;
1476 }
1477 }
1478 Ok(PyExpr::Call {
1479 func: Box::new(func),
1480 args,
1481 kwargs,
1482 })
1483 }
1484
1485 fn parse_subscript(&mut self, value: PyExpr) -> Result<PyExpr, PyExprError> {
1490 if self.eat(&Tok::RBracket) {
1491 return Err(PyExprError::new("empty subscript"));
1492 }
1493 let first = self.parse_star_or_expr()?;
1494 let mut elts = vec![first];
1495 let mut tuple = false;
1496 while self.eat(&Tok::Comma) {
1497 tuple = true;
1498 if self.peek() == Some(&Tok::RBracket) {
1499 break;
1500 }
1501 elts.push(self.parse_star_or_expr()?);
1502 }
1503 self.expect(&Tok::RBracket, "']'")?;
1504 let slice = if tuple {
1505 PyExpr::Tuple(elts)
1506 } else {
1507 match elts.into_iter().next() {
1508 Some(starred @ PyExpr::Starred(_)) => PyExpr::Tuple(vec![starred]),
1509 Some(e) => e,
1510 None => return Err(PyExprError::new("empty subscript")),
1511 }
1512 };
1513 Ok(PyExpr::Subscript {
1514 value: Box::new(value),
1515 slice: Box::new(slice),
1516 })
1517 }
1518}
1519
1520#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
1528#[allow(dead_code)] enum Prec {
1530 NamedExpr,
1531 Tuple,
1532 Yield,
1533 Test,
1534 Or,
1535 And,
1536 Not,
1537 Cmp,
1538 Expr,
1539 BXor,
1540 BAnd,
1541 Shift,
1542 Arith,
1543 Term,
1544 Factor,
1545 Power,
1546 Await,
1547 Atom,
1548}
1549
1550impl Prec {
1551 fn next(self) -> Prec {
1554 match self {
1555 Prec::NamedExpr => Prec::Tuple,
1556 Prec::Tuple => Prec::Yield,
1557 Prec::Yield => Prec::Test,
1558 Prec::Test => Prec::Or,
1559 Prec::Or => Prec::And,
1560 Prec::And => Prec::Not,
1561 Prec::Not => Prec::Cmp,
1562 Prec::Cmp => Prec::Expr,
1563 Prec::Expr => Prec::BXor,
1564 Prec::BXor => Prec::BAnd,
1565 Prec::BAnd => Prec::Shift,
1566 Prec::Shift => Prec::Arith,
1567 Prec::Arith => Prec::Term,
1568 Prec::Term => Prec::Factor,
1569 Prec::Factor => Prec::Power,
1570 Prec::Power => Prec::Await,
1571 Prec::Await | Prec::Atom => Prec::Atom,
1572 }
1573 }
1574}
1575
1576impl PyOp {
1577 fn symbol(self) -> &'static str {
1578 match self {
1579 PyOp::Add => "+",
1580 PyOp::Sub => "-",
1581 PyOp::Mult => "*",
1582 PyOp::MatMult => "@",
1583 PyOp::Div => "/",
1584 PyOp::Mod => "%",
1585 PyOp::Pow => "**",
1586 PyOp::LShift => "<<",
1587 PyOp::RShift => ">>",
1588 PyOp::BitOr => "|",
1589 PyOp::BitXor => "^",
1590 PyOp::BitAnd => "&",
1591 PyOp::FloorDiv => "//",
1592 }
1593 }
1594
1595 fn prec(self) -> Prec {
1597 match self {
1598 PyOp::Add | PyOp::Sub => Prec::Arith,
1599 PyOp::Mult | PyOp::MatMult | PyOp::Div | PyOp::Mod | PyOp::FloorDiv => Prec::Term,
1600 PyOp::LShift | PyOp::RShift => Prec::Shift,
1601 PyOp::BitOr => Prec::Expr,
1602 PyOp::BitXor => Prec::BXor,
1603 PyOp::BitAnd => Prec::BAnd,
1604 PyOp::Pow => Prec::Power,
1605 }
1606 }
1607}
1608
1609fn items_view(out: &mut String, elts: &[PyExpr]) {
1612 if let [single] = elts {
1613 write_expr(out, single, Prec::Test);
1614 out.push(',');
1615 } else {
1616 write_joined(out, elts);
1617 }
1618}
1619
1620fn write_joined(out: &mut String, elts: &[PyExpr]) {
1621 for (i, e) in elts.iter().enumerate() {
1622 if i > 0 {
1623 out.push_str(", ");
1624 }
1625 write_expr(out, e, Prec::Test);
1626 }
1627}
1628
1629fn write_expr(out: &mut String, e: &PyExpr, ctx: Prec) {
1633 match e {
1634 PyExpr::Name(n) => out.push_str(n),
1635 PyExpr::Constant(c) => write_const(out, c),
1636 PyExpr::Attribute(value, attr) => {
1637 write_expr(out, value, Prec::Atom);
1638 if matches!(
1642 value.as_ref(),
1643 PyExpr::Constant(PyConst::Int(_) | PyConst::True | PyConst::False)
1644 ) {
1645 out.push(' ');
1646 }
1647 out.push('.');
1648 out.push_str(attr);
1649 }
1650 PyExpr::Subscript { value, slice } => {
1651 write_expr(out, value, Prec::Atom);
1652 out.push('[');
1653 match slice.as_ref() {
1654 PyExpr::Tuple(elts) if !elts.is_empty() => items_view(out, elts),
1657 other => write_expr(out, other, Prec::Test),
1658 }
1659 out.push(']');
1660 }
1661 PyExpr::Call { func, args, kwargs } => {
1662 write_expr(out, func, Prec::Atom);
1663 out.push('(');
1664 let mut comma = false;
1665 for arg in args {
1666 if comma {
1667 out.push_str(", ");
1668 }
1669 comma = true;
1670 write_expr(out, arg, Prec::Test);
1671 }
1672 for (name, value) in kwargs {
1673 if comma {
1674 out.push_str(", ");
1675 }
1676 comma = true;
1677 out.push_str(name);
1678 out.push('=');
1679 write_expr(out, value, Prec::Test);
1680 }
1681 out.push(')');
1682 }
1683 PyExpr::Tuple(elts) => {
1684 let parens = elts.is_empty() || ctx > Prec::Tuple;
1685 if parens {
1686 out.push('(');
1687 }
1688 items_view(out, elts);
1689 if parens {
1690 out.push(')');
1691 }
1692 }
1693 PyExpr::List(elts) => {
1694 out.push('[');
1695 write_joined(out, elts);
1696 out.push(']');
1697 }
1698 PyExpr::Set(elts) => {
1699 if elts.is_empty() {
1700 out.push_str("{*()}");
1703 } else {
1704 out.push('{');
1705 write_joined(out, elts);
1706 out.push('}');
1707 }
1708 }
1709 PyExpr::Dict(items) => {
1710 out.push('{');
1711 for (i, (key, value)) in items.iter().enumerate() {
1712 if i > 0 {
1713 out.push_str(", ");
1714 }
1715 match key {
1716 Some(k) => {
1717 write_expr(out, k, Prec::Test);
1718 out.push_str(": ");
1719 write_expr(out, value, Prec::Test);
1720 }
1721 None => {
1722 out.push_str("**");
1723 write_expr(out, value, Prec::Expr);
1724 }
1725 }
1726 }
1727 out.push('}');
1728 }
1729 PyExpr::Starred(value) => {
1730 out.push('*');
1731 write_expr(out, value, Prec::Expr);
1732 }
1733 PyExpr::BoolOp { op, values } => {
1734 let (own, sep) = match op {
1741 PyBoolOp::Or => (Prec::Or, " or "),
1742 PyBoolOp::And => (Prec::And, " and "),
1743 };
1744 let parens = ctx > own;
1745 if parens {
1746 out.push('(');
1747 }
1748 let mut child = own;
1749 for (i, value) in values.iter().enumerate() {
1750 if i > 0 {
1751 out.push_str(sep);
1752 }
1753 child = child.next();
1754 write_expr(out, value, child);
1755 }
1756 if parens {
1757 out.push(')');
1758 }
1759 }
1760 PyExpr::BinOp { left, op, right } => {
1761 let op_prec = op.prec();
1762 let parens = ctx > op_prec;
1763 if parens {
1764 out.push('(');
1765 }
1766 let (left_prec, right_prec) = if matches!(op, PyOp::Pow) {
1769 (op_prec.next(), op_prec)
1770 } else {
1771 (op_prec, op_prec.next())
1772 };
1773 write_expr(out, left, left_prec);
1774 out.push(' ');
1775 out.push_str(op.symbol());
1776 out.push(' ');
1777 write_expr(out, right, right_prec);
1778 if parens {
1779 out.push(')');
1780 }
1781 }
1782 PyExpr::UnaryOp { op, operand } => {
1783 let (symbol, op_prec) = match op {
1784 PyUnaryOp::Invert => ("~", Prec::Factor),
1785 PyUnaryOp::Not => ("not", Prec::Not),
1786 PyUnaryOp::UAdd => ("+", Prec::Factor),
1787 PyUnaryOp::USub => ("-", Prec::Factor),
1788 };
1789 let parens = ctx > op_prec;
1790 if parens {
1791 out.push('(');
1792 }
1793 out.push_str(symbol);
1794 if op_prec != Prec::Factor {
1796 out.push(' ');
1797 }
1798 write_expr(out, operand, op_prec);
1799 if parens {
1800 out.push(')');
1801 }
1802 }
1803 }
1804}
1805
1806fn write_const(out: &mut String, c: &PyConst) {
1807 match c {
1808 PyConst::None => out.push_str("None"),
1809 PyConst::True => out.push_str("True"),
1810 PyConst::False => out.push_str("False"),
1811 PyConst::Ellipsis => out.push_str("..."),
1812 PyConst::Int(digits) => out.push_str(digits),
1813 PyConst::Float(text) => out.push_str(text),
1814 PyConst::Str {
1815 value, u_prefix, ..
1816 } => {
1817 if *u_prefix {
1818 out.push('u');
1819 }
1820 write_str_repr(out, value, repr_quote(value));
1823 }
1824 PyConst::Bytes(bytes) => write_bytes_repr(out, bytes),
1825 }
1826}
1827
1828fn repr_quote(value: &str) -> char {
1834 if value.contains('\'') && !value.contains('"') {
1835 '"'
1836 } else {
1837 '\''
1838 }
1839}
1840
1841fn is_nonprintable_nonascii(c: char) -> bool {
1846 matches!(u32::from(c),
1847 0xa0 | 0xad
1848 | 0x600..=0x605 | 0x61c | 0x6dd | 0x70f | 0x890..=0x891 | 0x8e2
1849 | 0x1680 | 0x180e
1850 | 0x2000..=0x200f | 0x2028..=0x202f | 0x205f..=0x2064 | 0x2066..=0x206f
1851 | 0x3000 | 0xfeff | 0xfff9..=0xfffb
1852 | 0xe000..=0xf8ff
1853 | 0x110bd | 0x110cd | 0x13430..=0x1343f | 0x1bca0..=0x1bca3
1854 | 0x1d173..=0x1d17a | 0xe0001 | 0xe0020..=0xe007f
1855 | 0xf0000..=0xffffd | 0x100000..=0x10fffd)
1856}
1857
1858fn write_str_repr(out: &mut String, value: &str, quote: char) {
1862 use fmt::Write as _;
1863 out.push(quote);
1864 for c in value.chars() {
1865 if c == quote || c == '\\' {
1866 out.push('\\');
1867 out.push(c);
1868 } else if c == '\n' {
1869 out.push_str("\\n");
1870 } else if c == '\r' {
1871 out.push_str("\\r");
1872 } else if c == '\t' {
1873 out.push_str("\\t");
1874 } else if c.is_control() || (!c.is_ascii() && is_nonprintable_nonascii(c)) {
1875 let u = u32::from(c);
1876 let _ = if u < 0x100 {
1877 write!(out, "\\x{u:02x}")
1878 } else if u < 0x10000 {
1879 write!(out, "\\u{u:04x}")
1880 } else {
1881 write!(out, "\\U{u:08x}")
1882 };
1883 } else {
1884 out.push(c);
1885 }
1886 }
1887 out.push(quote);
1888}
1889
1890fn write_bytes_repr(out: &mut String, bytes: &[u8]) {
1892 use fmt::Write as _;
1893 let quote = if bytes.contains(&b'\'') && !bytes.contains(&b'"') {
1894 '"'
1895 } else {
1896 '\''
1897 };
1898 out.push('b');
1899 out.push(quote);
1900 for &b in bytes {
1901 if b == quote as u8 || b == b'\\' {
1902 out.push('\\');
1903 out.push(char::from(b));
1904 } else if b == b'\t' {
1905 out.push_str("\\t");
1906 } else if b == b'\n' {
1907 out.push_str("\\n");
1908 } else if b == b'\r' {
1909 out.push_str("\\r");
1910 } else if (0x20..0x7f).contains(&b) {
1911 out.push(char::from(b));
1912 } else {
1913 let _ = write!(out, "\\x{b:02x}");
1914 }
1915 }
1916 out.push(quote);
1917}
1918
1919#[cfg(test)]
1920mod tests {
1921 use super::{parse_py_expr, parse_py_expr_stmt, parse_py_star_annotation, unparse};
1922
1923 const ORACLE: &[(&str, &str)] = &[
1930 ("1+2", "1 + 2"),
1932 ("dict[str,int]", "dict[str, int]"),
1933 ("\"x\"", "'x'"),
1934 ("[1 ,2]", "[1, 2]"),
1935 ("( 1, )", "(1,)"),
1936 ("{'a':1}", "{'a': 1}"),
1937 ("-1", "-1"),
1938 ("x [ 1 ]", "x[1]"),
1939 ("dict[str, list[int]]", "dict[str, list[int]]"),
1941 ("tuple[int, ...]", "tuple[int, ...]"),
1942 ("Callable[[int], str]", "Callable[[int], str]"),
1943 ("int | None", "int | None"),
1944 ("Optional[int] | str", "Optional[int] | str"),
1945 ("a.b.c.d", "a.b.c.d"),
1946 ("a . b . c", "a.b.c"),
1947 ("0xFF", "255"),
1949 ("1_000", "1000"),
1950 ("0o777", "511"),
1951 ("0b1010", "10"),
1952 ("0x_FF", "255"),
1953 ("000", "0"),
1954 ("0_0", "0"),
1955 ("0x0", "0"),
1956 ("999999999999999999999999", "999999999999999999999999"),
1957 ("0xFFFFFFFFFFFFFFFFFFFF", "1208925819614629174706175"),
1958 ("1e-3", "0.001"),
1960 ("1E3", "1000.0"),
1961 ("1e+3", "1000.0"),
1962 (".5", "0.5"),
1963 ("5.", "5.0"),
1964 ("5.0", "5.0"),
1965 ("0.1", "0.1"),
1966 ("2.675", "2.675"),
1967 ("10_000_000.0", "10000000.0"),
1968 ("1e15", "1000000000000000.0"),
1969 ("1e16", "1e+16"),
1970 ("1e300", "1e+300"),
1971 ("1e-4", "0.0001"),
1972 ("0.0001", "0.0001"),
1973 ("0.00001", "1e-05"),
1974 ("1e-5", "1e-05"),
1975 ("1.5e10", "15000000000.0"),
1976 ("1e400", "1e309"),
1977 ("0e0", "0.0"),
1978 ("00.0", "0.0"),
1979 ("9007199254740993.0", "9007199254740992.0"),
1980 ("123456789123456789.0", "1.2345678912345678e+17"),
1981 ("1.7976931348623157e308", "1.7976931348623157e+308"),
1982 ("5e-324", "5e-324"),
1983 ("-1.0", "-1.0"),
1984 ("-0.0", "-0.0"),
1985 ("\"a'b\"", "\"a'b\""),
1987 ("'a\"b'", "'a\"b'"),
1988 ("'both\\'\\\"'", "'both\\'\"'"),
1989 ("'don\\'t \"quote\"'", "'don\\'t \"quote\"'"),
1990 ("'ab' 'cd'", "'abcd'"),
1991 ("'x' 'y' 'z'", "'xyz'"),
1992 ("b'x'", "b'x'"),
1993 ("B\"y\"", "b'y'"),
1994 ("u'x'", "u'x'"),
1995 ("u''", "u''"),
1996 ("b''", "b''"),
1997 ("''", "''"),
1998 ("u'a' 'b'", "u'ab'"),
1999 ("'a' u'b'", "'ab'"),
2000 ("b'a' b'b'", "b'ab'"),
2001 ("r'\\d'", "'\\\\d'"),
2002 ("rb'\\x00'", "b'\\\\x00'"),
2003 ("'\\n'", "'\\n'"),
2004 ("'\\x41'", "'A'"),
2005 ("'\\t\\\\'", "'\\t\\\\'"),
2006 ("'\\''", "\"'\""),
2007 ("\"\\\"\"", "'\"'"),
2008 ("'\\u00e9'", "'é'"),
2009 ("'é'", "'é'"),
2010 ("'\\x85'", "'\\x85'"),
2011 ("'\\xa0'", "'\\xa0'"),
2012 ("'\\u2028'", "'\\u2028'"),
2013 ("'\u{202f}'", "'\\u202f'"),
2014 ("'\\x7f'", "'\\x7f'"),
2015 ("'\\x00'", "'\\x00'"),
2016 ("'\\v'", "'\\x0b'"),
2017 ("'\\a'", "'\\x07'"),
2018 ("'\\401'", "'ā'"),
2019 ("'\\x9F'", "'\\x9f'"),
2020 ("b'\\x00\\xff'", "b'\\x00\\xff'"),
2021 ("b'a\\'b'", "b\"a'b\""),
2022 ("b'\\''", "b\"'\""),
2023 ("b'\"'", "b'\"'"),
2024 ("b'both\\'\\\"'", "b'both\\'\"'"),
2025 ("b'\\401'", "b'\\x01'"),
2026 ("'''x'''", "'x'"),
2028 ("\"\"\"a'b\"\"\"", "\"a'b\""),
2029 ("'''a\nb'''", "'a\\nb'"),
2030 ("'''it's'''", "\"it's\""),
2031 ("u'''k'''", "u'k'"),
2032 ("b'''q'''", "b'q'"),
2033 ("'''trip''' 'le'", "'triple'"),
2034 ("[]", "[]"),
2036 ("{}", "{}"),
2037 ("()", "()"),
2038 ("(1,)", "(1,)"),
2039 ("(1, 2)", "(1, 2)"),
2040 ("1, 2", "(1, 2)"),
2041 ("1,", "(1,)"),
2042 ("{1, 2}", "{1, 2}"),
2043 ("{'a': 1, 'b': 2}", "{'a': 1, 'b': 2}"),
2044 ("{**base, 'a': 1}", "{**base, 'a': 1}"),
2045 ("{**a}", "{**a}"),
2046 ("{**a | b}", "{**a | b}"),
2047 ("{1: (2, 3)}", "{1: (2, 3)}"),
2048 ("[*a, 1]", "[*a, 1]"),
2049 ("(*a, 1)", "(*a, 1)"),
2050 ("{*a, 1}", "{*a, 1}"),
2051 ("[[1, 2], [3]]", "[[1, 2], [3]]"),
2052 ("[(1, 2)]", "[(1, 2)]"),
2053 ("((1, 2),)", "((1, 2),)"),
2054 ("x[1,]", "x[1,]"),
2056 ("x[()]", "x[()]"),
2057 ("x[1, 2]", "x[1, 2]"),
2058 ("x[(1, 2)]", "x[1, 2]"),
2059 ("x[*a]", "x[*a,]"),
2060 ("x[a, *b]", "x[a, *b]"),
2061 ("x[a, *b, c]", "x[a, *b, c]"),
2062 ("x[a][b]", "x[a][b]"),
2063 ("x[-1]", "x[-1]"),
2064 ("x[...]", "x[...]"),
2065 ("f()", "f()"),
2067 ("f(1, x=2)", "f(1, x=2)"),
2068 ("f(*args)", "f(*args)"),
2069 ("f(x=1)", "f(x=1)"),
2070 ("f(1, *a, x=2)", "f(1, *a, x=2)"),
2071 ("f(1, x=2, *a, y=3)", "f(1, *a, x=2, y=3)"),
2072 ("f(a)(b)[c].d", "f(a)(b)[c].d"),
2073 ("f(a, *b)(c)", "f(a, *b)(c)"),
2074 ("(a + b).method(x)", "(a + b).method(x)"),
2075 ("(1).bit_length()", "1 .bit_length()"),
2076 ("f(g(x), y=h(z))", "f(g(x), y=h(z))"),
2077 ("f((1, 2))", "f((1, 2))"),
2078 ("f(-1)", "f(-1)"),
2079 ("...", "..."),
2081 ("True", "True"),
2082 ("False", "False"),
2083 ("None", "None"),
2084 ("True | False", "True | False"),
2085 ("2**8", "2 ** 8"),
2087 ("2 ** -1", "2 ** (-1)"),
2088 ("-2 ** 2", "-2 ** 2"),
2089 ("(-2) ** 2", "(-2) ** 2"),
2090 ("-(2 ** 2)", "-2 ** 2"),
2091 ("-x ** 2", "-x ** 2"),
2092 ("2 ** -x ** 3", "2 ** (-x ** 3)"),
2093 ("a ** b ** c", "a ** b ** c"),
2094 ("(a ** b) ** c", "(a ** b) ** c"),
2095 ("(1 + 2) * 3", "(1 + 2) * 3"),
2096 ("1 + (2 * 3)", "1 + 2 * 3"),
2097 ("1 + 2 * 3", "1 + 2 * 3"),
2098 ("a | b | c", "a | b | c"),
2099 ("a | (b | c)", "a | (b | c)"),
2100 ("a + b | c & d", "a + b | c & d"),
2101 ("a << 2 >> 1", "a << 2 >> 1"),
2102 ("x & y ^ z", "x & y ^ z"),
2103 ("x @ y", "x @ y"),
2104 ("(a @ b) @ c", "a @ b @ c"),
2105 ("a @ (b @ c)", "a @ (b @ c)"),
2106 ("x // y % z", "x // y % z"),
2107 ("a / b * c", "a / b * c"),
2108 ("a - (b - c)", "a - (b - c)"),
2109 ("(a - b) - c", "a - b - c"),
2110 ("a % (b % c)", "a % (b % c)"),
2111 ("~x", "~x"),
2112 ("+x", "+x"),
2113 ("not x", "not x"),
2114 ("not not x", "not not x"),
2115 ("- -x", "--x"),
2116 ("-(-x)", "--x"),
2117 ("-(1)", "-1"),
2118 ("-(a + b)", "-(a + b)"),
2119 ("~x | +y", "~x | +y"),
2120 ("not a | b", "not a | b"),
2121 ("(a | b)[c]", "(a | b)[c]"),
2122 ("-x[0]", "-x[0]"),
2123 ("(-x)[0]", "(-x)[0]"),
2124 ("a or b", "a or b"),
2128 ("a and b or c", "a and b or c"),
2129 ("a or b and c", "a or (b and c)"),
2130 ("(a or b) and c", "(a or b) and c"),
2131 ("not a or b", "not a or b"),
2132 ("a or not b", "a or not b"),
2133 ("a | b or c", "a | b or c"),
2134 ("a or b or c", "a or b or c"),
2135 ("a and b and c", "a and b and c"),
2136 ("(a or b) or c", "(a or b) or c"),
2137 ("a or (b or c)", "a or (b or c)"),
2138 ("-a or b", "-a or b"),
2139 ("f(a or b)", "f(a or b)"),
2140 ("[a or b]", "[a or b]"),
2141 ("a or b, c", "(a or b, c)"),
2142 ("x[a or b]", "x[a or b]"),
2143 ("(a or b).c", "(a or b).c"),
2144 ("(a and b)(c)", "(a and b)(c)"),
2145 ("not (a or b)", "not (a or b)"),
2146 ("a or b ** c", "a or b ** c"),
2147 ("(a or b)[0]", "(a or b)[0]"),
2148 ("{a or b}", "{a or b}"),
2149 ("{(a or b): 1}", "{a or b: 1}"),
2150 ("{'k': a or b}", "{'k': a or b}"),
2151 ("a or b | c", "a or b | c"),
2152 ("(a and b) or (c and d)", "a and b or (c and d)"),
2153 ("a and (b or c)", "a and (b or c)"),
2154 ("a or b or c or d", "a or b or c or d"),
2155 ("a and b or c and d", "a and b or (c and d)"),
2156 ("f(*(a or b))", "f(*(a or b))"),
2157 ("x[a or b, c]", "x[a or b, c]"),
2158 ("a or b", "a or b"),
2159 ("not not a or b", "not not a or b"),
2160 ("True or False", "True or False"),
2161 ("a or 1 or 'x'", "a or 1 or 'x'"),
2162 ];
2163
2164 const ERR_CASES: &[&str] = &[
2169 "lambda: 1",
2171 "x if y else z",
2172 "(x := 1)",
2173 "",
2174 "f(a",
2175 "1 +",
2176 "[1,, 2]",
2177 "await x",
2178 "f'{x}'",
2179 "x for x in y",
2180 "(x for x in y)",
2181 "[x for x in y]",
2182 "x < y",
2184 "x == y",
2185 "x[1:2]",
2186 "x[:]",
2187 "f(**kw)",
2188 "1j",
2189 "'\\N{DASH}'",
2190 "yield x",
2191 "1 if True else 2",
2192 "1if True else 2",
2193 "*x",
2195 "(*x)",
2196 "*a, b",
2197 "b, *a",
2198 "*a,",
2199 "x = 1",
2200 "01",
2201 "09",
2202 "1_",
2203 "1__0",
2204 "0x",
2205 "0b2",
2206 "def f(): pass",
2207 "x²",
2208 "'unterminated",
2209 "b'unterminated",
2210 "f(x=1, 2)",
2211 "1 2",
2212 "x[]",
2213 "{*a: 1}",
2214 "'a' b'b'",
2215 "b'é'",
2216 "x.if",
2217 "f(if=1)",
2218 "x + not y",
2219 ")",
2220 "((((",
2221 "\\",
2222 "..",
2223 "@x",
2224 "x;",
2225 "x, = y",
2226 "'\\ud800'",
2227 ];
2228
2229 #[test]
2230 fn oracle_battery_round_trips_ast_unparse() {
2231 for (src, want) in ORACLE {
2232 let parsed = parse_py_expr(src)
2233 .unwrap_or_else(|e| panic!("parse_py_expr({src:?}) unexpectedly failed: {e}"));
2234 let got = unparse(&parsed);
2235 assert_eq!(&got, want, "unparse mismatch for source {src:?}");
2236 }
2237 }
2238
2239 #[test]
2242 fn unparse_is_idempotent_over_reparse() {
2243 for (src, _) in ORACLE {
2244 let first = unparse(&parse_py_expr(src).unwrap());
2245 let reparsed = parse_py_expr(&first)
2246 .unwrap_or_else(|e| panic!("unparse output {first:?} must reparse: {e}"));
2247 assert_eq!(unparse(&reparsed), first, "not idempotent for {src:?}");
2248 }
2249 }
2250
2251 #[test]
2262 fn exec_mode_accepts_top_level_starred_and_empty_sources() {
2263 for (src, want) in [
2264 ("*Ts", "*Ts"),
2265 ("* Ts", "*Ts"),
2266 ("*tuple[int, ...]", "*tuple[int, ...]"),
2267 ("*a.b", "*a.b"),
2268 ("*a | b", "*a | b"),
2269 ("*-a", "*-a"),
2270 ("*a, b", "(*a, b)"),
2271 ("a, *b", "(a, *b)"),
2272 ("*a,", "(*a,)"),
2273 ("int", "int"),
2274 ("\nint", "int"),
2275 (" \nint", "int"),
2276 ("\u{c}int", "int"),
2277 ("int ", "int"),
2278 ] {
2279 let parsed = parse_py_expr_stmt(src)
2280 .unwrap_or_else(|e| panic!("parse_py_expr_stmt({src:?}) failed: {e}"))
2281 .unwrap_or_else(|| panic!("parse_py_expr_stmt({src:?}) yielded no statement"));
2282 assert_eq!(unparse(&parsed), want, "exec-mode unparse for {src:?}");
2283 }
2284 for src in ["", " ", " ", "\n", "\u{c}", "\t\n \n"] {
2285 assert_eq!(
2286 parse_py_expr_stmt(src),
2287 Ok(None),
2288 "{src:?} must be Module(body=[])"
2289 );
2290 }
2291 for src in [" int", " int", "\tint", " \u{c} int", "int\n str"] {
2294 assert!(
2295 parse_py_expr_stmt(src).is_err(),
2296 "{src:?} must raise IndentationError"
2297 );
2298 }
2299 for src in ["*Ts", "*a, b", "*a,", "a, *b"] {
2301 assert!(parse_py_expr(src).is_err(), "eval mode must reject {src:?}");
2302 }
2303 }
2304
2305 #[test]
2313 fn star_annotation_mode_takes_one_leading_star_only() {
2314 for (src, want) in [
2315 ("*Ts", "*Ts"),
2316 ("*tuple[int, ...]", "*tuple[int, ...]"),
2317 ("int", "int"),
2318 ("int | None", "int | None"),
2319 ] {
2320 let parsed = parse_py_star_annotation(src)
2321 .unwrap_or_else(|e| panic!("parse_py_star_annotation({src:?}) failed: {e}"));
2322 assert_eq!(unparse(&parsed), want);
2323 }
2324 for src in ["*a, b", "a, *b", "*a,", ""] {
2325 assert!(
2326 parse_py_star_annotation(src).is_err(),
2327 "star_annotation must reject {src:?}"
2328 );
2329 }
2330 }
2331
2332 #[test]
2333 fn unsupported_and_invalid_inputs_are_errors_not_panics() {
2334 for src in ERR_CASES {
2335 assert!(
2336 parse_py_expr(src).is_err(),
2337 "expected Err for source {src:?}"
2338 );
2339 }
2340 }
2341
2342 #[test]
2348 fn deep_nesting_is_an_error_not_a_stack_overflow() {
2349 for (open, close) in [("(", ")"), ("[", "]"), ("-", "")] {
2350 let src = format!("{}1{}", open.repeat(5000), close.repeat(5000));
2351 assert!(parse_py_expr(&src).is_err(), "expected Err for deep {open}");
2352 }
2353 for src in [
2354 format!("a{}", ".b".repeat(5000)),
2355 format!("x{}", "[1]".repeat(5000)),
2356 format!("f{}", "()".repeat(5000)),
2357 format!("1{}", "+1".repeat(5000)),
2358 ] {
2359 assert!(
2360 parse_py_expr(&src).is_err(),
2361 "expected Err for left-deep input starting {:?}",
2362 &src[..8]
2363 );
2364 }
2365 }
2366
2367 #[test]
2370 fn stored_quote_matches_rendered_quote() {
2371 for (src, quote) in [("'x'", '\''), ("\"a'b\"", '"'), ("'a\"b'", '\'')] {
2372 let parsed = parse_py_expr(src).unwrap();
2373 match parsed {
2374 super::PyExpr::Constant(super::PyConst::Str { quote: q, .. }) => {
2375 assert_eq!(q, quote, "quote for {src:?}");
2376 }
2377 other => panic!("expected Str constant for {src:?}, got {other:?}"),
2378 }
2379 }
2380 }
2381
2382 mod proptests {
2383 use super::super::parse_py_expr;
2384 use proptest::prelude::*;
2385
2386 proptest! {
2387 #![proptest_config(ProptestConfig { cases: 512, ..ProptestConfig::default() })]
2388
2389 #[test]
2391 fn parse_never_panics_on_arbitrary_input(s in "\\PC*") {
2392 let _ = parse_py_expr(&s);
2393 }
2394
2395 #[test]
2398 fn parse_never_panics_on_expr_shaped_input(
2399 s in proptest::collection::vec(
2400 prop_oneof![
2401 Just("x".to_string()),
2402 Just("1".to_string()),
2403 Just("1.5".to_string()),
2404 Just("'s'".to_string()),
2405 Just("b'q'".to_string()),
2406 Just("(".to_string()),
2407 Just(")".to_string()),
2408 Just("[".to_string()),
2409 Just("]".to_string()),
2410 Just("{".to_string()),
2411 Just("}".to_string()),
2412 Just(",".to_string()),
2413 Just(":".to_string()),
2414 Just(".".to_string()),
2415 Just("...".to_string()),
2416 Just("**".to_string()),
2417 Just("*".to_string()),
2418 Just("|".to_string()),
2419 Just("-".to_string()),
2420 Just("=".to_string()),
2421 Just("not ".to_string()),
2422 Just(" ".to_string()),
2423 ],
2424 0..24,
2425 ).prop_map(|v| v.concat())
2426 ) {
2427 if let Ok(parsed) = parse_py_expr(&s) {
2428 let out = super::super::unparse(&parsed);
2429 let reparsed = parse_py_expr(&out).expect("unparse output reparses");
2430 prop_assert_eq!(super::super::unparse(&reparsed), out);
2431 }
2432 }
2433 }
2434 }
2435}