Introduction

上一篇文章我们分析了Python是如何对语法文件Grammar进行预处理,生成语法数据,并在运行时生成Acclerators加速语法分析的过程。当分析完这些内容之后,下一步便是分析Python中语法分析的机制。回顾一下Python的整个处理流程:

1.PyTokenizer进行词法分析,把源程序分解为Token

2.PyParser根据Token创建CST

3.CST被转换为AST

4.AST被编译为字节码

5.执行字节码

语法分析处于整个流程的第二步,其目标是处理token生成CST(Concrete Syntax Tree)。因此,在具体分析PyParser的工作方式之前,我们先看一下什么是CST。

CST (Concrete Syntax Tree)

CST (Concrete Syntax Tree) 和AST (Abstract Syntax Tree) 类似,都是语法分析所获得的中间结果。他们的不同之处在于,CST直接对应语法分析的匹配的过程,是直接生成的,含有大量冗余信息。而AST省略了中间的冗余信息,直接对应实际的语义,也就是分析的结果。用例子说明要清楚一些:

假设有这样一个表达式a,

CST是这样的:(->表示从父结点到子结点)

file_input -> stmt -> simple_stmt -> small_stmt -> expr_stmt -> testlist -> test ->or_test ->and_test ->not_test -> comparison -> expr -> xor_expr -> and_expr -> shift_expr -> arith_expr -> term -> factor -> power -> atom -> (NAME, “a”)

而AST则是:

(stmt_ty, expr_kind) -> (expr_ty, name_kind) ->(“a”)

可以看到CST表述了整个分析a的过程,从file_input一直推导到最后的NAME,每一步推导都成了树的结点,而大部分信息都可以说是无用的。AST的结构要简单和直接的多,直接表明a是一个表达式语句(假定a是一个单独的语句),内容是一个标示符,值为”a”。Python的语法分析生成的是CST而非AST,之后Python会调用PyAst_FromNode将CST转换为AST。

CST的结点称为Node,其结构定义在node.h中:

typedef struct _node {

shortn_type;

char*n_str;

intn_lineno;

intn_col_offset;

intn_nchildren;

struct _node*n_child;

} node;

Field

Description

n_type

结点类型,终结符定义在token.h中,而非终结符定义在graminit.h中

n_str

结点所对应的字符串的内容

n_lineno

对应的行号

n_col_offset

列号

n_nchildren

子结点的个数

n_child

子结点数组,动态分配内存

Python提供了下面的函数/宏来操作CST,同样定义在node.h中:

PyAPI_FUNC(node *) PyNode_New(int type);

PyAPI_FUNC(int) PyNode_AddChild(node *n, int type,

char *str, int lineno, int col_offset);

PyAPI_FUNC(void) PyNode_Free(node *n);

/* Node access functions */

#define NCH(n)((n)->n_nchildren)

#define CHILD(n, i)(&(n)->n_child[i])

#define RCHILD(n, i)(CHILD(n, NCH(n) + i))

#define TYPE(n)((n)->n_type)

#define STR(n)((n)->n_str)

/* Assert that the type of a node is what we expect */

#define REQ(n, type) assert(TYPE(n) == (type))

PyAPI_FUNC(void) PyNode_ListTree(node *);

PyNode_New和PyNode_Delete负责创建和释放node结构:

node *

PyNode_New(int type)

{

node *n = (node *) PyObject_MALLOC(1 * sizeof(node));

if (n == NULL)

return NULL;

n->n_type = type;

n->n_str = NULL;

n->n_lineno = 0;

n->n_nchildren = 0;

n->n_child = NULL;

return n;

}

void

PyNode_Free(node *n)

{

if (n != NULL) {

freechildren(n);

PyObject_FREE(n);

}

}

static void

freechildren(node *n)

{

int i;

for (i = NCH(n); --i >= 0; )

freechildren(CHILD(n, i));

if (n->n_child != NULL)

PyObject_FREE(n->n_child);

if (STR(n) != NULL)

PyObject_FREE(STR(n));

}

NCH/CHILD/RCHILD/TYPE/STR是用来封装对node的成员的访问的。需要提一下的是,CHILD(n, i)是从左边开始算,传入i的是正数,而RCHILD(n, i)则是从右边往左,传入的参数i是负数。

PyNode_AddChild将一个新的子结点加入到子结点数组中。由于结点数量是动态变化的,因此在当前分配的结点数组大小不够的时候,Python会调用realloc重新分配内存。内存分配是一个非常耗时的动作,因此Python在PyNode_AddChild之中用到了和std::vector类似的技巧来尽量减少内存分配的次数,每次增长的时候都会根据某个规则进行RoundUp,而不是需要多少就分配多少。XXXROUNDUP函数负责进行此运算。n<=1时,返回n。1128, 会调用fancy_roundup来RoundUp到2的幂。

#define XXXROUNDUP(n) ((n) <= 1 ? (n) : /

(n) <= 128 ? (((n) + 3) & ~3) :/

fancy_roundup(n))

有了XXXROUNDUP之后,实现PyNode_AddChild就非常直接了:

int

PyNode_AddChild(register node *n1, int type, char *str, int lineno, int col_offset)

{

const int nch = n1->n_nchildren;

int current_capacity;

int required_capacity;

node *n;

if (nch == INT_MAX || nch < 0)

return E_OVERFLOW;

current_capacity = XXXROUNDUP(nch);

required_capacity = XXXROUNDUP(nch + 1);

if (current_capacity < 0 || required_capacity < 0)

return E_OVERFLOW;

if (current_capacity < required_capacity) {

n = n1->n_child;

n = (node *) PyObject_REALLOC(n,

required_capacity * sizeof(node));

if (n == NULL)

return E_NOMEM;

n1->n_child = n;

}

n = &n1->n_child[n1->n_nchildren++];

n->n_type = type;

n->n_str = str;

n->n_lineno = lineno;

n->n_col_offset = col_offset;

n->n_nchildren = 0;

n->n_child = NULL;

return 0;

}

值得注意的是该函数并没有记录下当前的最大容量,这个可以通过XXXROUNDUP(nch)计算出来。

PyParser

PyParser所作的事情就是根据token生成CST。整个生成树的过程其实也就是一个遍历语法图的过程。在前一篇文章中我们知道语法图是由多个DFA组成的,而输入的token和当前的所处的状态结点可以决定下一个状态结点。由于PyParser是在多个DFA中遍历,因此当结束了某个DFA的遍历需要回到上一个DFA,这些信息都是由一个专门的栈保存着。PyParser所对应的结构是parser_state,这个结构保存着PyParser的内部状态,如下:

typedef struct {

stackp_stack;/* Stack of parser states */

grammar*p_grammar;/* Grammar to use */

node*p_tree;/* Top of parse tree */

#ifdef PY_PARSER_REQUIRES_FUTURE_KEYWORD

unsigned longp_flags;/* see co_flags in Include/code.h */

#endif

} parser_state;

python实现语法分析器_Python源码分析5 – 语法分析器PyParser | 学步园相关推荐

  1. python程序代码解析_Python源码分析3 – 词法分析器PyTokenizer

    Introduction 上次我们分析了Python中执行程序可分为5个步骤: Tokenizer进行词法分析,把源程序分解为Token Parser根据Token创建CST CST被转换为AST A ...

  2. Python微型Web框架Bottle源码分析

    Bottle 是一个快速,简单和轻量级的 WSGI 微型 Web 框架的 Python.它作为单个文件模块分发,除了 Python 标准库之外没有依赖关系. 选择源码分析的版本是 Release 于 ...

  3. django之:网页伪静态 JsonResponse form表单携带文件数据 CBV源码分析 模板语法传值 模板语法之过滤器 标签 自定义标签函数 过滤器、inclusion_tag模板的继承导入

    目录标题 一:网页伪静态 1.定义 2.如何实现 二:视图层 1.视图函数返回值问题 2.视图层返回json格式的数据 3.form表单携带文件数据 4.CBV源码分析 1.CBV和FBV: 2.CB ...

  4. python运行不了程序代码_Python源码分析2 - 一个简单的Python程序的执行

    本文主要通过跟踪一个非常简单的Python程序的执行,简单讨论Python实现的基本框架和结构. 要执行Python程序如下,功能非常简单:从1加到10再打印出来 # test program sum ...

  5. python用一行代码编写一个回声程序_Python源码分析2 - 一个简单的Python程序的执行...

    本文主要通过跟踪一个非常简单的Python程序的执行,简单讨论Python实现的基本框架和结构. 要执行Python程序如下,功能非常简单:从1加到10再打印出来 # test program sum ...

  6. java编译器源码分析之语法分析器

    token流到抽象语法树的过程是语法分析. 前面认识到token流,这部分将介绍抽象语法树(AST). 那么什么是抽象语法树(AST)?AST长啥样?我们的token流是如何转变成AST的?下面围绕这 ...

  7. Python wordcloud词云:源码分析及简单使用

    Python版本的词云生成模块从2015年的v1.0到现在,已经更新到了v1.7. 下载请移步至:https://pypi.org/project/wordcloud/ wordcloud简单应用: ...

  8. Python 进阶:enum 模块源码分析

    作者:weapon 来源:https://zhuanlan.zhihu.com/p/52056538 起步 上一篇<Python 的枚举类型> (https://zhuanlan.zhih ...

  9. python整型图_python源码研究之整型对象探索

    ​ 1.python的整型对象是PyIntObject对象,这个对象是一个不可变对象,即没有ob_size这个变量,这个对象在c层面实现,只是在基本的pyobject中添加了long ob_ival对 ...

  10. python字符串代码对象_Python源码剖析 - Python中的字符串对象

    1. 前言 我们已经在 [Python中的整数对象] 章节中对定长对象进行了详细的讲解,接下来我们将介绍变长对象,而字符串类型,则是这类对象的典型代表. 这里必须先引入一个概念: Python 中的变 ...

最新文章

  1. Win10 | Mac 在server上统一办公
  2. Maven安装中央仓库没有的jar到本地
  3. ZABBIX企业微信新版告警
  4. Angular 8.0.0-beta.5 发布,Web 前端框架
  5. rabbitmq beam.smp cpu利用率过高
  6. libssh 认证绕过漏洞(cve-2018-10933)分析
  7. spring service ,controller反向代理生成AOP代理类流程
  8. gimp修改图片部分区域的对比度
  9. 【一起学OpenFOAM】04 OpenFOAM的学习资源
  10. CPU上跑到 33 FPS 的简单轻量级人体姿态估计网络
  11. UUID 查看linux的UUID 与 SVN 工程的 UUID。(两者之间没有联系)
  12. LightOJ 1135 - Count the Multiples of 3 线段树
  13. oracle查询显示小写,oracle查询区分大小写
  14. linux下c语言按q退出_解析Linux环境下RAID 6的Q校验算法
  15. Proe/Creo经典曲面造型实战案例大合集
  16. 左耳朵耗子:公司监控员工行为,这事逻辑就不对
  17. [转]安装win7系统不产生100M保留分区
  18. html:optionscollection 默认值,关于html:options collection= /的使用
  19. 阿里云香港服务器和大陆服务器区别及选择
  20. 打造Win10+WSL开发环境(2)

热门文章

  1. mysql认证 成都考点_CKA概述、考试形式、考试地址、考纲占比等
  2. 想学PLC编程,先弄清5种PLC专用语言
  3. 江山三侠—Flash短片轻松学(第2季)
  4. surfacert能跑java么_不怕天气糟糕 出行全靠Surface来帮忙
  5. 语音信号处理、语音特征提取
  6. devExpress chart c# 折线图绘制
  7. 条码软件如何制作SCC-14条形码
  8. 屏幕录像专家V7.5注册机
  9. 绝对干货!百度文档 用python一键下载
  10. 数据安全分类分级剖析