带你一步步调试CPython源码(二、词法分析)

本篇文章是《带你一步步调试CPython源码》系列的第二篇文章,主要介绍CPython解释器是如何做Python代码的词法分析处理。想了解这一系列的背景请移步第一篇文章

在大学读书的时候,编译原理课程的老师就讲述了编译阶段分为四大步骤------词法分析、语法分析、IR生成和优化、最终代码生成。实际上CPython解释器也是如此,只是最后一步是字节码执行。在CPython词法分析阶段中,Python源码也会被解释器逐字符读取,并转换成CPython内部的字节流供下一步处理。

CPython项目中的词法分析和语法分析过程没有严格的界限,很多词法分析逻辑都是通过语法分析器顺带处理的,所以经常能看到parser相关函数中包含了词法分析的处理。

虽然本代码采用了当前最新的Python3.13a2版本,但是最近几个月tokenizer部分仍然做了大量修改,这导致本篇文章和最新的对应不上。

一、词法分析器

词法分析是指将用户输入的字符转换成token供后续处理的步骤。一个token表示一类词法,比如数字、字符、换行、逗号等等。词法分析的意义是简化语法分析的复杂度,因为token和字符是不能一一对应的,所以需要一个专门的编译步骤对原始字符做解析形成token流。

CPython中所有的字符都存储在Grammar/Tokens中。这份文件分为两列,左边一列代表token名称,右边一列代表该token对应的符号表示。如果token的符号表示不唯一(比如说NUMBER),那么就没有右边一列。

这份文件不直接参与CPython项目的编译,但是可以根据它生成对应的词法分析器。比如在57行添加如下代码

arduino 复制代码
QUESMARK '?'

再在命令行执行.\PCbuild\build.bat --regen。通过git status可以看到整个项目有如下文件发生变化------

其中最重要的是token.c文件,它包含了词法分析器的主要逻辑。打开这个文件,发现它是自动生成的,而且包含了有关词法分析的核心逻辑,这个文件将在下一节介绍如何被调用的。

二、断点调试源码

上一篇文章梳理了Python以交互模式(REPL)运行的主流程,但是遗留了很多存档。在存档12中,LOAD 12,CPython调用_PyParser_ASTFromFile函数,将字符串转化为AST树。

存档的意思是这里的逻辑太复杂,需要以后用大篇幅文章来介绍,所以暂时做个记号。记号用斜体加粗的SAVE+序号表示,后面会有相应的读档LOAD+序号,表示详细的介绍这块逻辑。

Python/pythonrun.c文件第243行打上断点,让程序运行到这里。

进入_PyParser_ASTFromFile函数。经过一个审计事件后,CPython会调用_PyPegen_run_parser_from_file_pointer函数。该函数来自于Parser/pengen.c文件,说明CPython进入了词法分析和语法分析阶段。

在这个函数中,程序会首先调用_PyTokenizer_FromFile函数初始化一个tok_stateSAVE 18 ,记录词法分析过程的状态。其次调用_PyPegen_Parser_New初始化语法分析器parser,然后调用_PyPegen_run_parser做词法分析和语法分析。

重点关注_PyPegen_run_parser函数,它是处理词法分析和语法分析的核心逻辑。在Parser/parser.c文件的1191行打上断点,让程序运行到这个地方。

在经历过模式判断后程序会进入interactive_rule函数。该函数会调用statement_newline_rule函数实现读取用户的命令行输入,并完成词法分析和语法分析。

该函数会调用_PyPegen_fill_token函数将用户输入做词法分析,然后尝试匹配以下四种语句类型------

  1. compound_stmt NEWLINE

  2. simple_stmts

  3. NEWLINE

  4. $(即退出REPL模式)

这四种类型匹配成功后进入语法分析的范畴,不在本篇文章做介绍,SAVE 19 。我们重点关注_PyPegen_fill_token函数是如何做词法分析的。

Parser/tokenizer.c文件第1793行打上断点,让程序运行到这。这个函数包含一个无限循环,循环内调用tok_nextc函数不断获取用户的输入,直到遇到换行符等终止符号。比如当我输入a=1时,第一次循环会把a读取出来,第二次循环会把=读出来,如此往复直到读取到换行。

当把字符(比如=)读取出来以后,它会调用tok_backup函数存储一个字符。这是因为有的符号需要不止一个字符来表示,比如>=->...等。

然后,程序通过粗暴的if语句判断当前字符属于哪个token,当最后都确定下来后调用MAKE_TOKEN宏将该token写入先前初始化好的tok_state中,而tok_state存储在parser结构体中供语法分析处理。比如当确定好=代表EQUAL这类token后会调用MAKE_TOKEN(_PyToken_OneChar(c))。其中_PyToken_OneChar函数为本篇第一部分介绍的通过Grammar/Token文件自动生成的函数。

至此,词法分析逻辑全部理清了。

介绍到这里还有一个细节忽略了,CPython是如何从键盘获取用户的输入的呢?在tok_nextc函数中,程序会调用PyOS_Readline函数获取操作系统的stdin字节流,SAVE 20。通过不同系统的系统调用可以直接获取用户的输入。

当然不是每次调用tok_nextc函数都会触发系统调用的,只有当当前输入字符被词法分析器处理完后,也就是下一个prompt开始的时候,才会调用这个系统调用。

三、词法分析器自动生成

CPython的词法分析器的分析逻辑是手动编写的,但是token的生成逻辑是在Parser/token.c文件中,该文件是根据Grammar/Tokens文件自动生成的。Tokens文件在文章开头已经展示过,而解析它的程序是一个Python脚本,位于Tools/build/generate_token.py

第二篇就到此结束了,下一篇会介绍语法分析器,引入著名的Pegen。

相关推荐
black0moonlight1 分钟前
ISAAC Gym 7. 使用箭头进行数据可视化
开发语言·python
程序员黄同学29 分钟前
Python 中如何创建多行字符串?
前端·python
feilieren44 分钟前
信创改造 - TongRDS 替换 Redis
java·spring boot·后端
hani19901 小时前
beikeshop 与swoole结合,让网站打开飞起
后端·swoole
一点一木1 小时前
AI与数据集:从零基础到全面应用的深度解析(超详细教程)
人工智能·python·tensorflow
A.sir啊1 小时前
Python知识点精汇:集合篇精解!
python·pycharm
周某人姓周1 小时前
利用爬虫爬取网页小说
爬虫·python
花生糖@1 小时前
OpenCV图像基础处理:通道分离与灰度转换
人工智能·python·opencv·计算机视觉
knoci2 小时前
【Go】-go中的锁机制
后端·学习·golang
Mike_188702783512 小时前
深入探索Golang的GMP调度机制:源码解析与实现原理
开发语言·后端·golang