Python源码分析3 – 词法分析器PyTokenizer

Introduction

上次我们分析了Python中执行程序可分为5个步骤:

  1. Tokenizer进行词法分析,把源程序分解为Token
  2. Parser根据Token创建CST
  3. CST被转换为AST
  4. AST被编译为字节码
  5. 执行字节码

本文将介绍Python程序执行的第一步,也就是词法分析。词法分析简单来说就是把源程序的字符分解组合成Token。比如sum=0可以分解成3个token,'sum', '=', '0'。程序中的whitespace通常只作为分隔符用,最终会被忽略掉,因此没有出现在token的列表中。不过在Python之中,由于语法规则的关系,Tab/Space需要用来分析程序的缩进,因此Python中对于Whitespace的处理比一般C/C++编译器的处理会要稍微复杂一些。

在Python中词法分析的实现在Parser目录下的tokenizer.h和tokenizer.cpp。Python的其他部分会直接调用tokenizer.h中定义的函数,如下:

extern struct tok_state *PyTokenizer_FromString(const char *);
extern struct tok_state *PyTokenizer_FromFile(FILE *, char *, char *);
extern void PyTokenizer_Free(struct tok_state *);
extern int PyTokenizer_Get(struct tok_state *, char **, char **);

这些函数均以PyTokenizer开头。这是Python源代码中的一个约定。虽然Python是用C语言实现的,其实现方式借鉴了很多面对对象的思想。拿词法分析来说,这四个函数均可以看作PyTokenizer的成员函数。头两个函数PyTokenizer_FromXXXX可以看作是构造函数,返回PyTokenizer的instance。PyTokenizer对象内部状态,也就是成员变量,储存在tok_state之中。PyTokenizer_Free可以看作是析构函数,负责释放PyTokenizer,也就是tok_state所占用的内存。PyTokenizer_Get则是PyTokenizer的一个成员函数,负责取得在字符流中下一个Token。这两个函数均需要传入tok_state的指针,和C++中需要隐含传入this指针给成员函数的道理是一致的。可以看到,OO的思想其实是和语言无关的,即使是C这样的结构化的语言,也可以写出面对对象的程序。

tok_state

tok_state等价于PyTokenizer这个class本身的状态,也就是内部的私有成员的集合。部分定义如下:

/* Tokenizer state */
struct tok_state {
/* Input state; buf <= cur <= inp <= end */
/* NB an entire line is held in the buffer */ 
    char *buf;    /* Input buffer, or NULL; malloc'ed if fp != NULL */ 
    char *cur;    /* Next character in buffer */ 
    char *inp;    /* End of data in buffer */ 
    char *end;    /* End of input buffer if buf != NULL */ 
    char *start;    /* Start of current token if not NULL */ 
    int done;    /* E_OK normally, E_EOF at EOF, otherwise error code 
    /* NB If done != E_OK, cur must be == inp!!! */ 
    FILE *fp;    /* Rest of input; NULL if tokenizing a string */ 
    int tabsize;    /* Tab spacing */ 
    int indent;    /* Current indentation index */ 
    int indstack[MAXINDENT];    /* Stack of indents */ 
    int atbol;    /* Nonzero if at begin of new line */ 
    int pendin;    /* Pending indents (if > 0) or dedents (if < 0) */ 
    char *prompt, *nextprompt;    /* For interactive prompting */ 
    int lineno;    /* Current line number */ 
    int level;    /* () [] {} Parentheses nesting level */ 
            /* Used to allow free continuations inside them */
};

 

最重要的是buf, cur, inp, end, start。这些field直接决定了缓冲区的内容:

buf是缓冲区的开始。假如PyTokenizer处于字符串模式,那么buf指向字符串本身,否则,指向文件读入的缓冲区。
cur指向缓冲区中下一个字符。
inp指向缓冲区中有效数据的结束位置。PyTokenizer是以行为单位进行处理的,每一行的内容存入从buf到inp之间,包括/n。一般情况下 ,PyTokenizer会直接从缓冲区中取下一个字符,一旦到达inp所指向的位置,就会准备取下一行。当PyTokenizer处于不同模式下面,具体的行为会稍有不同。
end是缓冲区的结束,在字符串模式下没有用到。
start指向当前token的开始位置,如果现在还没有开始分析token,start为NULL。

PyTokenzer_FromString & PyTokenizer_FromFile

PyTokenizer_FromString & PyTokenizer_FromFile可以说是PyTokenizer的构造函数。从这两个函数的命名可以看出,PyTokenizer支持两种模式:字符串和文件。由于标准输入STDIN也可以看作是文件,因此实际上PyTokenizer支持3种模式:字符串,交互,文件。

PyTokenizer_FromFile的实现和PyTokenizer_FromString的实现大致相同。后者的实现如下:

/* Set up tokenizer for string */ 
struct tok_state *
PyTokenizer_FromString(const char *str)

    struct tok_state *tok = tok_new(); 
    if (tok == NULL) 
        return NULL; 
    str = (char *)decode_str(str, tok); 
    if (str == NULL) { 
        PyTokenizer_Free(tok); 
        return NULL; 
    } 
    /* XXX: constify members. */ 
    tok->buf = tok->cur = tok->end = tok->inp = (char*)str; 
    return tok;
}

直接调用tok_new返回一个tok_state的instance,后面的decode_str负责对str进行解码,然后赋给tok->buf/cur/end/inp。

PyTokenizer_Get

下面我们来分析一下PyTokenizer_Get函数。该函数

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值