惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Fortinet All Blogs
宝玉的分享
宝玉的分享
酷 壳 – CoolShell
酷 壳 – CoolShell
T
The Exploit Database - CXSecurity.com
Help Net Security
Help Net Security
腾讯CDC
Project Zero
Project Zero
C
CXSECURITY Database RSS Feed - CXSecurity.com
IT之家
IT之家
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Tailwind CSS Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
D
Darknet – Hacking Tools, Hacker News & Cyber Security
L
LINUX DO - 最新话题
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
T
Threatpost
N
News | PayPal Newsroom
C
Cybersecurity and Infrastructure Security Agency CISA
Hacker News - Newest:
Hacker News - Newest: "LLM"
S
SegmentFault 最新的问题
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
P
Proofpoint News Feed
A
Arctic Wolf
B
Blog RSS Feed
Forbes - Security
Forbes - Security
P
Privacy & Cybersecurity Law Blog
Attack and Defense Labs
Attack and Defense Labs
V2EX - 技术
V2EX - 技术
P
Proofpoint News Feed
I
Intezer
Application and Cybersecurity Blog
Application and Cybersecurity Blog
阮一峰的网络日志
阮一峰的网络日志
aimingoo的专栏
aimingoo的专栏
T
Tenable Blog
MyScale Blog
MyScale Blog
U
Unit 42
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
WordPress大学
WordPress大学
W
WeLiveSecurity
D
DataBreaches.Net
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
G
GRAHAM CLULEY
有赞技术团队
有赞技术团队
Martin Fowler
Martin Fowler
罗磊的独立博客
The Last Watchdog
The Last Watchdog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
V
Vulnerabilities – Threatpost
美团技术团队
Microsoft Security Blog
Microsoft Security Blog

Blog - Astral

Vulnerability and malware checks in uv Open source security at Astral Astral to join OpenAI Ruff v0.15.0 ty: An extremely fast Python type checker and language server Python 3.14 Ruff v0.13.0 Astral OSS Fund: One Year Later pyx: a Python-native package registry, now in Beta An experimental, variant-enabled build of uv uv security advisory: ZIP payload obfuscation Ruff v0.12.0 Ruff v0.10.0 Ruff v0.9.0 A new home for python-build-standalone Ruff v0.8.0 Ruff v0.7.0 uv: Unified Python packaging Ruff v0.6.0 Announcing the Astral OSS Fund Ruff v0.5.0 Ruff v0.4.5: Ruff's language server is now written in Rust Ruff v0.3.0 uv: Python packaging in Rust Ruff v0.2.0 Ruff v0.1.8 Ruff v0.1.5 The Ruff Formatter: An extremely fast, Black-compatible Python formatter Ruff v0.1.0 Ruff v0.0.292 Ruff v0.0.285 Ruff v0.0.283 Ruff v0.0.281 Ruff v0.0.278 Ruff v0.0.276 Announcing Astral, the company behind Ruff
Ruff v0.4.0: a hand-written recursive descent parser for Python
dhruv@astral · 2024-04-18 · via Blog - Astral

Ruff v0.4.0 is available now! Install it from PyPI, or your package manager of choice:

pip install --upgrade ruff

Ruff is an extremely fast Python linter and formatter, written in Rust. Ruff can be used to replace Black, Flake8 (plus dozens of plugins), isort, pydocstyle, pyupgrade, and more, all while executing tens or hundreds of times faster than any individual tool.


This release marks an important milestone in Ruff’s development as we switch from a generated to a hand-written recursive descent parser.

Ruff's new parser is >2x faster, which translates to a 20-40% speedup for all linting and formatting invocations.

RepositoryLinter (v0.3)Linter (v0.4)Formatter (v0.3)Formatter (v0.4)
home-assistant/core449.9364.1381.9307.8
pytorch/pytorch328.7251.8351.1274.9
python/cpython134.694.4180.2138.3
huggingface/transformers198.5143.6239.0184.1

Time in milliseconds to lint and format popular repositories. Lower is better.

A hand-written parser also opens the door to future optimizations and improvements, especially in error recovery.

Read on for discussion of the major changes, or take a look at the changelog.

Parsers form the foundational layer of any static analysis tool, transforming raw source code into Abstract Syntax Trees (ASTs), which serve as the basis for analysis.

Ruff v0.4.0 introduces a hand-written recursive descent parser, replacing the existing generated parser.

The difference between the two lies in how they are implemented:

  • A generated parser is created using a tool called a parser generator (in our case, LALRPOP). Typically, a parser generator requires that the grammar is defined in a Domain Specific Language (DSL), which is then converted into executable code by the generator. In our case, rules were defined in a .lalrpop file, which LALRPOP converted into Rust code.

  • On the other hand, a hand-written parser involves encoding the parsing rules directly in Rust code, using functions to define the parsing logic for individual nodes.

On initial release, Ruff used the Python parser from the RustPython project. As Ruff evolved, we learned that a Python interpreter and linter have different needs, and the ideal AST for those two use cases can look pretty different. Ultimately, we pulled the parser into Ruff and maintained it separately as we evolved our AST structure.

Enter Victor Hugo Gomes, a contributor to the Ruff project who opened a pull request to introduce a hand-written recursive descent parser. It was an ambitious proposal, but one that made a lot of sense for Ruff's future, given that...

  1. We were already maintaining the parser separately from RustPython.
  2. We had a clear understanding of the AST structure we needed.
  3. Victor had already demonstrated that the hand-written parser would significantly outperform the generated parser.
  4. The generated parser, ironically, had become harder to maintain. Parser generators come with limitations around the grammars that they can support, and we'd already found ourselves fighting LALRPOP to support the latest Python syntax.
  5. A hand-written parser would give us more control over error handling and recovery, which is especially important for building editor-friendly tools, which need to be resilient to syntax errors.

From there, we worked closely with Victor to integrate the parser into Ruff and add support for the latest Python syntax. Once the parser was fully compliant with Ruff's own test suite, we spent a few months testing and validating its accuracy and reliability across millions of lines of real-world Python code and fuzzer-generated inputs.

Advantages #

In line with our initial motivations, the introduction of a recursive descent parser brings several benefits over the generated parser.

Control and Flexibility #

A hand-written parser has complete control over the parsing process which allows for greater flexibility in handling edge cases. For example, parenthesized with items in Python introduce a syntactic ambiguity regarding which node the opening parenthesis "belongs" to:

# Parenthesis belongs to the `with` item
with (item): ...
#    ^^^^^^ with item

# Parenthesis belongs to the context expression which is part of the `with` item
with (item) as var: ...
#    ^^^^^^        context expression
#    ^^^^^^^^^^^^^ with item

Encoding this ambiguity in a generated parser can be challenging, while a hand-written parser gives you the flexibility you need to handle such cases.

Performance #

The hand-written parser is significantly faster. Optimizing the parser generator was difficult, since we had minimal control over the generated code and few opportunities to take advantage of domain-specific knowledge around hot paths and cold paths, and other properties of the data. While we could optimize our hand-written lexer, the parser remained a black box.

BenchmarkLALRPOP parserHand-written parserChange
parser[large/dataset.py]63.6 ms26.6 ms×2.4
parser[numpy/ctypeslib.py]10.8 ms5 ms×2.2
parser[numpy/globals.py]964.6 µs424.9 µs×2.3
parser[pydantic/types.py]24.4 ms10.9 ms×2.2
parser[unicode/pypinyin.py]3.8 ms1.7 ms×2.2

Micro-benchmark comparison between the two parsers.

Ruff's hand-written parser is >2x faster than the generated parser, which translates to a 20-40% speedup for all linting and formatting invocations.

Error handling #

With a hand-written parser, we can now provide better error messages on encountering a syntax error, as seen in the following examples:

--- a/ruff/parser/old_error_messages
+++ b/ruff/parser/new_error_messages
   |
 1 | from x import
-  |              ^ SyntaxError: Unexpected token Newline
+  |              ^ SyntaxError: Expected one or more symbol names after import

   |
 1 | async while test: ...
-  |       ^ SyntaxError: Unexpected token 'while'
+  |       ^ SyntaxError: Expected 'def', 'with' or 'for' to follow 'async', found 'while'
   |

   |
 1 | a; if b: pass; b
-  |    ^ SyntaxError: Unexpected token 'if'
+  |    ^ SyntaxError: Compound statements are not allowed on the same line as simple statements
   |

   |
 1 | with (item1, item2), item3,: ...
-  |                            ^ SyntaxError: Unexpected token ':'
+  |                           ^ SyntaxError: Trailing comma not allowed
   |

   |
 1 | x = *a and b
-  |        ^ SyntaxError: Unexpected token 'and'
+  |      ^ SyntaxError: Boolean expression cannot be used here
   |

Error resilience #

For many of our users, Ruff is a tool that lives in the editor, and in the editor, it's common to have syntax errors, even temporarily. Imagine, for example, that you're in the midst of defining a new function. You've typed out the def func (x):, but haven't filled in the function body. While your code is not syntactically valid, you'd still like to see linting and formatting diagnostics for the rest of the file.

A hand-written parser enables us to support error recovery, and thereby build error resilience into Ruff. That is, Ruff's parser can recover from syntax errors in the source code and continue parsing despite the interruption.

What does this look like in an editor? Imagine you've made multiple syntax errors in your code, as shown below:

import os  # unused-import (F401)


def fibonacci(n):
    """Compute the nth number in the Fibonacci sequence."""
    x = 1  # unused-variable (F841)
    if n in (0, 1)
        #         ^ SyntaxError: Expected ':', found newline
        return n
    else:
        return fibonacci(n - 1) + fibonacci(n - 2)


if __name__ == "__main__":
    import sys

    1 = int(sys.argv[1])
#   ^ SyntaxError: Invalid assignment target
    print(fibonacci(n))  # undefined-name (F821)

With an error-resilient parser, Ruff can continue to analyze the code even after encountering the above syntax errors, which then allows the linter to provide and even fix diagnostics in a single run.

While Ruff does not yet exhibit this error-resilient behavior, the hand-written parser lays the foundation for it, which we plan to implement in future releases.

What's next #

Looking ahead, we aim to further improve Ruff's parsing capabilities with the following objectives:

  • Complete error recovery: Ensure that the parser recovers from all syntax errors, providing developers with an uninterrupted experience in the editor.
  • Reporting all syntax errors: Display all syntax errors encountered during parsing, providing developers with a complete overview of the issues in their code.
  • Continuous analysis: Allow the linter to proceed with analysis even in the presence of syntax errors.

Ultimately, our goal is to enable a first-class editor experience for Ruff by making it both faster and, critically, resilient to syntax errors.

Thank you! #

Finally, we'd like to acknowledge the RustPython project for enabling us to leverage their Python parser. The RustPython parser was a significant enabler to Ruff's early development, and we're grateful for the opportunity to collaborate and build on their work.

We'd also like to thank Victor Hugo Gomes, for initiating the transition to a hand-written parser and all the work that went into making it Ruff-compliant; and Addison Crump, for contributing the fuzzer that we leveraged to validate the new parser.


View the full changelog on GitHub.

Read more about Astral — the company behind Ruff.