Skip to main content

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}