ruff_python_parser/lib.rs
1//! This crate can be used to parse Python source code into an Abstract
2//! Syntax Tree.
3//!
4//! ## Overview
5//!
6//! The process by which source code is parsed into an AST can be broken down
7//! into two general stages: [lexical analysis] and [parsing].
8//!
9//! During lexical analysis, the source code is converted into a stream of lexical
10//! tokens that represent the smallest meaningful units of the language. For example,
11//! the source code `print("Hello world")` would _roughly_ be converted into the following
12//! stream of tokens:
13//!
14//! ```text
15//! Name("print"), LeftParen, String("Hello world"), RightParen
16//! ```
17//!
18//! These tokens are then consumed by the `ruff_python_parser`, which matches them against a set of
19//! grammar rules to verify that the source code is syntactically valid and to construct
20//! an AST that represents the source code.
21//!
22//! During parsing, the `ruff_python_parser` consumes the tokens generated by the lexer and constructs
23//! a tree representation of the source code. The tree is made up of nodes that represent
24//! the different syntactic constructs of the language. If the source code is syntactically
25//! invalid, parsing fails and an error is returned. After a successful parse, the AST can
26//! be used to perform further analysis on the source code. Continuing with the example
27//! above, the AST generated by the `ruff_python_parser` would _roughly_ look something like this:
28//!
29//! ```text
30//! node: Expr {
31//! value: {
32//! node: Call {
33//! func: {
34//! node: Name {
35//! id: "print",
36//! ctx: Load,
37//! },
38//! },
39//! args: [
40//! node: Constant {
41//! value: Str("Hello World"),
42//! kind: None,
43//! },
44//! ],
45//! keywords: [],
46//! },
47//! },
48//! },
49//!```
50//!
51//! **Note:** The Tokens/ASTs shown above are not the exact tokens/ASTs generated by the `ruff_python_parser`.
52//! Refer to the [playground](https://play.ruff.rs) for the correct representation.
53//!
54//! ## Source code layout
55//!
56//! The functionality of this crate is split into several modules:
57//!
58//! - [lexer]: This module contains the lexer and is responsible for generating the tokens.
59//! - parser: This module contains an interface to the [Parsed] and is responsible for generating the AST.
60//! - mode: This module contains the definition of the different modes that the `ruff_python_parser` can be in.
61//!
62//! [lexical analysis]: https://en.wikipedia.org/wiki/Lexical_analysis
63//! [parsing]: https://en.wikipedia.org/wiki/Parsing
64//! [lexer]: crate::lexer
65
66pub use crate::error::{
67 InterpolatedStringErrorType, LexicalErrorType, ParseError, ParseErrorType,
68 UnsupportedSyntaxError, UnsupportedSyntaxErrorKind,
69};
70pub use crate::parser::ParseOptions;
71
72use crate::parser::Parser;
73
74use ruff_python_ast::token::{Token, TokenFlags, TokenKind, Tokens};
75use ruff_python_ast::{
76 AtomicNodeIndex, Expr, Mod, ModExpression, ModModule, PySourceType, StringFlags, StringLiteral,
77 Suite,
78};
79use ruff_text_size::{Ranged, TextRange};
80
81mod error;
82pub mod lexer;
83mod parser;
84pub mod semantic_errors;
85mod string;
86mod token_set;
87mod token_source;
88pub mod typing;
89
90/// Parse a full Python module usually consisting of multiple lines.
91///
92/// This is a convenience function that can be used to parse a full Python program without having to
93/// specify the [`Mode`] or the location. It is probably what you want to use most of the time.
94///
95/// # Example
96///
97/// For example, parsing a simple function definition and a call to that function:
98///
99/// ```
100/// use ruff_python_parser::parse_module;
101///
102/// let source = r#"
103/// def foo():
104/// return 42
105///
106/// print(foo())
107/// "#;
108///
109/// let module = parse_module(source);
110/// assert!(module.is_ok());
111/// ```
112pub fn parse_module(source: &str) -> Result<Parsed<ModModule>, ParseError> {
113 Parser::new(source, ParseOptions::from(Mode::Module))
114 .parse()
115 .try_into_module()
116 .unwrap()
117 .into_result()
118}
119
120/// Parses a single Python expression.
121///
122/// This convenience function can be used to parse a single expression without having to
123/// specify the Mode or the location.
124///
125/// # Example
126///
127/// For example, parsing a single expression denoting the addition of two numbers:
128///
129/// ```
130/// use ruff_python_parser::parse_expression;
131///
132/// let expr = parse_expression("1 + 2");
133/// assert!(expr.is_ok());
134/// ```
135pub fn parse_expression(source: &str) -> Result<Parsed<ModExpression>, ParseError> {
136 Parser::new(source, ParseOptions::from(Mode::Expression))
137 .parse()
138 .try_into_expression()
139 .unwrap()
140 .into_result()
141}
142
143/// Parses a Python expression for the given range in the source.
144///
145/// This function allows to specify the range of the expression in the source code, other than
146/// that, it behaves exactly like [`parse_expression`].
147///
148/// # Example
149///
150/// Parsing one of the numeric literal which is part of an addition expression:
151///
152/// ```
153/// use ruff_python_parser::parse_expression_range;
154/// # use ruff_text_size::{TextRange, TextSize};
155///
156/// let parsed = parse_expression_range("11 + 22 + 33", TextRange::new(TextSize::new(5), TextSize::new(7)));
157/// assert!(parsed.is_ok());
158/// ```
159pub fn parse_expression_range(
160 source: &str,
161 range: TextRange,
162) -> Result<Parsed<ModExpression>, ParseError> {
163 let source = &source[..range.end().to_usize()];
164 Parser::new_starts_at(source, range.start(), ParseOptions::from(Mode::Expression))
165 .parse()
166 .try_into_expression()
167 .unwrap()
168 .into_result()
169}
170
171/// Parses a Python expression as if it is parenthesized.
172///
173/// It behaves similarly to [`parse_expression_range`] but allows what would be valid within parenthesis
174///
175/// # Example
176///
177/// Parsing an expression that would be valid within parenthesis:
178///
179/// ```
180/// use ruff_python_parser::parse_parenthesized_expression_range;
181/// # use ruff_text_size::{TextRange, TextSize};
182///
183/// let parsed = parse_parenthesized_expression_range("'''\n int | str'''", TextRange::new(TextSize::new(3), TextSize::new(14)));
184/// assert!(parsed.is_ok());
185pub fn parse_parenthesized_expression_range(
186 source: &str,
187 range: TextRange,
188) -> Result<Parsed<ModExpression>, ParseError> {
189 let source = &source[..range.end().to_usize()];
190 let parsed = Parser::new_starts_at(
191 source,
192 range.start(),
193 ParseOptions::from(Mode::ParenthesizedExpression),
194 )
195 .parse();
196 parsed.try_into_expression().unwrap().into_result()
197}
198
199/// Parses a Python expression from a string annotation.
200///
201/// # Example
202///
203/// Parsing a string annotation:
204///
205/// ```
206/// use ruff_python_parser::parse_string_annotation;
207/// use ruff_python_ast::{StringLiteral, StringLiteralFlags, AtomicNodeIndex};
208/// use ruff_text_size::{TextRange, TextSize};
209///
210/// let string = StringLiteral {
211/// value: "'''\n int | str'''".to_string().into_boxed_str(),
212/// flags: StringLiteralFlags::empty(),
213/// range: TextRange::new(TextSize::new(0), TextSize::new(16)),
214/// node_index: AtomicNodeIndex::NONE
215/// };
216/// let parsed = parse_string_annotation("'''\n int | str'''", &string);
217/// assert!(!parsed.is_ok());
218/// ```
219pub fn parse_string_annotation(
220 source: &str,
221 string: &StringLiteral,
222) -> Result<Parsed<ModExpression>, ParseError> {
223 let range = string
224 .range()
225 .add_start(string.flags.opener_len())
226 .sub_end(string.flags.closer_len());
227 let source = &source[..range.end().to_usize()];
228 if string.flags.is_triple_quoted() {
229 parse_parenthesized_expression_range(source, range)
230 } else {
231 parse_expression_range(source, range)
232 }
233}
234
235/// Parse the given Python source code using the specified [`ParseOptions`].
236///
237/// This function is the most general function to parse Python code. Based on the [`Mode`] supplied
238/// via the [`ParseOptions`], it can be used to parse a single expression, a full Python program,
239/// an interactive expression or a Python program containing IPython escape commands.
240///
241/// # Example
242///
243/// If we want to parse a simple expression, we can use the [`Mode::Expression`] mode during
244/// parsing:
245///
246/// ```
247/// use ruff_python_parser::{parse, Mode, ParseOptions};
248///
249/// let parsed = parse("1 + 2", ParseOptions::from(Mode::Expression));
250/// assert!(parsed.is_ok());
251/// ```
252///
253/// Alternatively, we can parse a full Python program consisting of multiple lines:
254///
255/// ```
256/// use ruff_python_parser::{parse, Mode, ParseOptions};
257///
258/// let source = r#"
259/// class Greeter:
260///
261/// def greet(self):
262/// print("Hello, world!")
263/// "#;
264/// let parsed = parse(source, ParseOptions::from(Mode::Module));
265/// assert!(parsed.is_ok());
266/// ```
267///
268/// Additionally, we can parse a Python program containing IPython escapes:
269///
270/// ```
271/// use ruff_python_parser::{parse, Mode, ParseOptions};
272///
273/// let source = r#"
274/// %timeit 1 + 2
275/// ?str.replace
276/// !ls
277/// "#;
278/// let parsed = parse(source, ParseOptions::from(Mode::Ipython));
279/// assert!(parsed.is_ok());
280/// ```
281pub fn parse(source: &str, options: ParseOptions) -> Result<Parsed<Mod>, ParseError> {
282 parse_unchecked(source, options).into_result()
283}
284
285/// Parse the given Python source code using the specified [`ParseOptions`].
286///
287/// This is same as the [`parse`] function except that it doesn't check for any [`ParseError`]
288/// and returns the [`Parsed`] as is.
289pub fn parse_unchecked(source: &str, options: ParseOptions) -> Parsed<Mod> {
290 Parser::new(source, options).parse()
291}
292
293/// Parse the given Python source code using the specified [`PySourceType`].
294pub fn parse_unchecked_source(source: &str, source_type: PySourceType) -> Parsed<ModModule> {
295 // SAFETY: Safe because `PySourceType` always parses to a `ModModule`
296 Parser::new(source, ParseOptions::from(source_type))
297 .parse()
298 .try_into_module()
299 .unwrap()
300}
301
302/// Parses each `range` of `source` as an independent module and concatenates the results into a
303/// single [`Parsed<ModModule>`] whose nodes keep their offsets into `source`.
304///
305/// This validates sources such as Jupyter notebooks, where each cell must be syntactically valid on
306/// its own while later cells can still reference earlier definitions.
307/// The `ranges` must be ordered and non-overlapping.
308///
309/// Consecutive ranges must be separated by a single-byte `\n`: each range ends just before the
310/// separator and the next range starts just after it, as Ruff's notebook cells are. A syntax error
311/// anchored at a cell's trailing offset then lands on that separator, the cell's own last line, so
312/// it is attributed to that cell rather than to the following one.
313pub fn parse_cells_unchecked(
314 source: &str,
315 ranges: impl IntoIterator<Item = TextRange>,
316 options: &ParseOptions,
317) -> Parsed<ModModule> {
318 let mut ranges = ranges.into_iter().peekable();
319 let mut body = Suite::new();
320 let mut tokens = Vec::new();
321 let mut errors = Vec::new();
322 let mut unsupported_syntax_errors = Vec::new();
323 let mut module_range: Option<TextRange> = None;
324
325 while let Some(range) = ranges.next() {
326 if let Some(previous) = module_range {
327 assert!(previous.end() <= range.start());
328 }
329
330 // The cell is lexed from `range.start()`, so the slice must keep the leading text to
331 // preserve absolute offsets into the concatenated source.
332 let cell_source = &source[TextRange::up_to(range.end())];
333 let Parsed {
334 syntax,
335 tokens: cell_tokens,
336 errors: cell_errors,
337 unsupported_syntax_errors: cell_unsupported_syntax_errors,
338 } = Parser::new_starts_at(cell_source, range.start(), options.clone())
339 .parse()
340 .try_into_module()
341 .expect("module options should parse into a module");
342
343 body.extend(syntax.body);
344 tokens.extend(cell_tokens);
345 errors.extend(cell_errors);
346 unsupported_syntax_errors.extend(cell_unsupported_syntax_errors);
347
348 // Each range excludes its trailing `\n` separator (see the doc comment above), leaving a
349 // one-byte gap in the token stream. Cover it with a `NonLogicalNewline` so token-based
350 // checks don't treat the separator as another logical line terminator. The final cell's
351 // separator is the file-final newline and is deliberately left uncovered.
352 if let Some(next) = ranges.peek() {
353 let separator = TextRange::new(range.end(), next.start());
354 assert_eq!(&source[separator], "\n");
355 tokens.push(Token::new(
356 TokenKind::NonLogicalNewline,
357 separator,
358 TokenFlags::empty(),
359 ));
360 }
361
362 module_range = Some(match module_range {
363 Some(previous) => TextRange::new(previous.start(), range.end()),
364 None => range,
365 });
366 }
367
368 body.shrink_to_fit();
369 tokens.shrink_to_fit();
370 errors.shrink_to_fit();
371 unsupported_syntax_errors.shrink_to_fit();
372
373 Parsed {
374 syntax: ModModule {
375 node_index: AtomicNodeIndex::NONE,
376 range: module_range.unwrap_or_default(),
377 body,
378 },
379 tokens: Tokens::new(tokens),
380 errors,
381 unsupported_syntax_errors,
382 }
383}
384
385/// Represents the parsed source code.
386#[derive(Debug, PartialEq, Clone, get_size2::GetSize)]
387pub struct Parsed<T> {
388 syntax: T,
389 tokens: Tokens,
390 errors: Vec<ParseError>,
391 unsupported_syntax_errors: Vec<UnsupportedSyntaxError>,
392}
393
394impl<T> Parsed<T> {
395 /// Returns the syntax node represented by this parsed output.
396 pub fn syntax(&self) -> &T {
397 &self.syntax
398 }
399
400 /// Returns all the tokens for the parsed output.
401 pub fn tokens(&self) -> &Tokens {
402 &self.tokens
403 }
404
405 /// Returns a list of syntax errors found during parsing.
406 pub fn errors(&self) -> &[ParseError] {
407 &self.errors
408 }
409
410 /// Returns a list of version-related syntax errors found during parsing.
411 pub fn unsupported_syntax_errors(&self) -> &[UnsupportedSyntaxError] {
412 &self.unsupported_syntax_errors
413 }
414
415 /// Consumes the [`Parsed`] output and returns the contained syntax node.
416 pub fn into_syntax(self) -> T {
417 self.syntax
418 }
419
420 /// Consumes the [`Parsed`] output and returns a list of syntax errors found during parsing.
421 fn into_errors(self) -> Vec<ParseError> {
422 self.errors
423 }
424
425 /// Returns `true` if the parsed source code is valid i.e., it has no [`ParseError`]s.
426 ///
427 /// Note that this does not include version-related [`UnsupportedSyntaxError`]s.
428 ///
429 /// See [`Parsed::has_no_syntax_errors`] for a version that takes these into account.
430 pub fn has_valid_syntax(&self) -> bool {
431 self.errors.is_empty()
432 }
433
434 /// Returns `true` if the parsed source code is invalid i.e., it has [`ParseError`]s.
435 ///
436 /// Note that this does not include version-related [`UnsupportedSyntaxError`]s.
437 ///
438 /// See [`Parsed::has_no_syntax_errors`] for a version that takes these into account.
439 pub fn has_invalid_syntax(&self) -> bool {
440 !self.has_valid_syntax()
441 }
442
443 /// Returns `true` if the parsed source code does not contain any [`ParseError`]s *or*
444 /// [`UnsupportedSyntaxError`]s.
445 ///
446 /// See [`Parsed::has_valid_syntax`] for a version specific to [`ParseError`]s.
447 pub fn has_no_syntax_errors(&self) -> bool {
448 self.has_valid_syntax() && self.unsupported_syntax_errors.is_empty()
449 }
450
451 /// Returns `true` if the parsed source code contains any [`ParseError`]s *or*
452 /// [`UnsupportedSyntaxError`]s.
453 ///
454 /// See [`Parsed::has_invalid_syntax`] for a version specific to [`ParseError`]s.
455 pub fn has_syntax_errors(&self) -> bool {
456 !self.has_no_syntax_errors()
457 }
458
459 /// Returns the [`Parsed`] output as a [`Result`], returning [`Ok`] if it has no syntax errors,
460 /// or [`Err`] containing the first [`ParseError`] encountered.
461 ///
462 /// Note that any [`unsupported_syntax_errors`](Parsed::unsupported_syntax_errors) will not
463 /// cause [`Err`] to be returned.
464 pub fn as_result(&self) -> Result<&Parsed<T>, &[ParseError]> {
465 if self.has_valid_syntax() {
466 Ok(self)
467 } else {
468 Err(&self.errors)
469 }
470 }
471
472 /// Consumes the [`Parsed`] output and returns a [`Result`] which is [`Ok`] if it has no syntax
473 /// errors, or [`Err`] containing the first [`ParseError`] encountered.
474 ///
475 /// Note that any [`unsupported_syntax_errors`](Parsed::unsupported_syntax_errors) will not
476 /// cause [`Err`] to be returned.
477 fn into_result(self) -> Result<Parsed<T>, ParseError> {
478 if self.has_valid_syntax() {
479 Ok(self)
480 } else {
481 Err(self.into_errors().into_iter().next().unwrap())
482 }
483 }
484}
485
486impl Parsed<Mod> {
487 /// Attempts to convert the [`Parsed<Mod>`] into a [`Parsed<ModModule>`].
488 ///
489 /// This method checks if the `syntax` field of the output is a [`Mod::Module`]. If it is, the
490 /// method returns [`Some(Parsed<ModModule>)`] with the contained module. Otherwise, it
491 /// returns [`None`].
492 ///
493 /// [`Some(Parsed<ModModule>)`]: Some
494 pub fn try_into_module(self) -> Option<Parsed<ModModule>> {
495 match self.syntax {
496 Mod::Module(module) => Some(Parsed {
497 syntax: module,
498 tokens: self.tokens,
499 errors: self.errors,
500 unsupported_syntax_errors: self.unsupported_syntax_errors,
501 }),
502 Mod::Expression(_) => None,
503 }
504 }
505
506 /// Attempts to convert the [`Parsed<Mod>`] into a [`Parsed<ModExpression>`].
507 ///
508 /// This method checks if the `syntax` field of the output is a [`Mod::Expression`]. If it is,
509 /// the method returns [`Some(Parsed<ModExpression>)`] with the contained expression.
510 /// Otherwise, it returns [`None`].
511 ///
512 /// [`Some(Parsed<ModExpression>)`]: Some
513 fn try_into_expression(self) -> Option<Parsed<ModExpression>> {
514 match self.syntax {
515 Mod::Module(_) => None,
516 Mod::Expression(expression) => Some(Parsed {
517 syntax: expression,
518 tokens: self.tokens,
519 errors: self.errors,
520 unsupported_syntax_errors: self.unsupported_syntax_errors,
521 }),
522 }
523 }
524}
525
526impl Parsed<ModModule> {
527 /// Returns the module body contained in this parsed output as a [`Suite`].
528 pub fn suite(&self) -> &Suite {
529 &self.syntax.body
530 }
531
532 /// Consumes the [`Parsed`] output and returns the module body as a [`Suite`].
533 pub fn into_suite(self) -> Suite {
534 self.syntax.body
535 }
536}
537
538impl Parsed<ModExpression> {
539 /// Returns the expression contained in this parsed output.
540 pub fn expr(&self) -> &Expr {
541 &self.syntax.body
542 }
543
544 /// Returns a mutable reference to the expression contained in this parsed output.
545 fn expr_mut(&mut self) -> &mut Expr {
546 &mut self.syntax.body
547 }
548
549 /// Consumes the [`Parsed`] output and returns the contained [`Expr`].
550 pub fn into_expr(self) -> Expr {
551 *self.syntax.body
552 }
553}
554
555/// Control in the different modes by which a source file can be parsed.
556///
557/// The mode argument specifies in what way code must be parsed.
558#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
559pub enum Mode {
560 /// The code consists of a sequence of statements.
561 Module,
562
563 /// The code consists of a single expression.
564 Expression,
565
566 /// The code consists of a single expression and is parsed as if it is parenthesized. The parentheses themselves aren't required.
567 /// This allows for having valid multiline expression without the need of parentheses
568 /// and is specifically useful for parsing string annotations.
569 ParenthesizedExpression,
570
571 /// The code consists of a sequence of statements which can include the
572 /// escape commands that are part of IPython syntax.
573 ///
574 /// ## Supported escape commands:
575 ///
576 /// - [Magic command system] which is limited to [line magics] and can start
577 /// with `?` or `??`.
578 /// - [Dynamic object information] which can start with `?` or `??`.
579 /// - [System shell access] which can start with `!` or `!!`.
580 /// - [Automatic parentheses and quotes] which can start with `/`, `;`, or `,`.
581 ///
582 /// [Magic command system]: https://ipython.readthedocs.io/en/stable/interactive/reference.html#magic-command-system
583 /// [line magics]: https://ipython.readthedocs.io/en/stable/interactive/magics.html#line-magics
584 /// [Dynamic object information]: https://ipython.readthedocs.io/en/stable/interactive/reference.html#dynamic-object-information
585 /// [System shell access]: https://ipython.readthedocs.io/en/stable/interactive/reference.html#system-shell-access
586 /// [Automatic parentheses and quotes]: https://ipython.readthedocs.io/en/stable/interactive/reference.html#automatic-parentheses-and-quotes
587 Ipython,
588}
589
590impl std::str::FromStr for Mode {
591 type Err = ModeParseError;
592 fn from_str(s: &str) -> Result<Self, ModeParseError> {
593 match s {
594 "exec" | "single" => Ok(Mode::Module),
595 "eval" => Ok(Mode::Expression),
596 "ipython" => Ok(Mode::Ipython),
597 _ => Err(ModeParseError),
598 }
599 }
600}
601
602/// A type that can be represented as [Mode].
603pub trait AsMode {
604 fn as_mode(&self) -> Mode;
605}
606
607impl AsMode for PySourceType {
608 fn as_mode(&self) -> Mode {
609 match self {
610 PySourceType::Python | PySourceType::Stub => Mode::Module,
611 PySourceType::Ipynb => Mode::Ipython,
612 }
613 }
614}
615
616/// Returned when a given mode is not valid.
617#[derive(Debug)]
618pub struct ModeParseError;
619
620impl std::fmt::Display for ModeParseError {
621 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
622 write!(f, r#"mode must be "exec", "eval", "ipython", or "single""#)
623 }
624}