Python源码分析3 – 词法分析器PyTokenizer
2007-12-14 12:55
549 查看
2006年12月12日 00:20:00
Tokenizer进行词法分析,把源程序分解为Token
Parser根据Token创建CST
CST被转换为AST
AST被编译为字节码
执行字节码
本文将介绍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中定义的函数,如下:
这些函数均以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这样的结构化的语言,也可以写出面对对象的程序。
最重要的是buf, cur, inp, end, start。这些field直接决定了缓冲区的内容:
buf是缓冲区的开始。假如PyTokenizer处于字符串模式,那么buf指向字符串本身,否则,指向文件读入的缓冲区。
cur指向缓冲区中下一个字符。
inp指向缓冲区中有效数据的结束位置。PyTokenizer是以行为单位进行处理的,每一行的内容存入从buf到inp之间,包括/n。一般情况下 ,PyTokenizer会直接从缓冲区中取下一个字符,一旦到达inp所指向的位置,就会准备取下一行。当PyTokenizer处于不同模式下面,具体的行为会稍有不同。
end是缓冲区的结束,在字符串模式下没有用到。
start指向当前token的开始位置,如果现在还没有开始分析token,start为NULL。
PyTokenizer_FromFile的实现和PyTokenizer_FromString的实现大致相同。后者的实现如下:
直接调用tok_new返回一个tok_state的instance,后面的decode_str负责对str进行解码,然后赋给tok-
tok_get负责以下几件事情:
上面的代码负责计算缩进了多少列。由于tab键可能有多种设定,PyTokenizer对tab键有两套处理方案:tok-
接下来,如果遇到了注释或者是空行,则不加以处理,直接跳过,这样做是避免影响缩进。唯一的例外是在交互模式下的完全的空行(只有一个换行符)需要被处理,因为在交互模式下空行意味着一组语句将要结束,而在非交互模式下完全的空行是要被直接忽略掉的。
最后,根据col和当前indstack的栈顶(也就是当前缩进的位置),确定是哪一种情况,具体请参看上面的代码。上面的代码有所删减,去掉了一些错误处理,加上了一点注释。需要说明的是PyTokenizer维护两个栈indstack & altindstack,分别对应col和altcol,保存着缩进的位置,而tok-
假如当前字符是字母或者是下划线,则开始当作标示符进行分析,否则,继续执行下面的语句,处理其他的可能性。不过还有一种可能性,Python中字符串可以是用r或者u开头,比如r"string", u"string"。r代表raw string,u代表unicode string。一旦遇到了r或者u的情况下,直接跳转到letter_quote标号处,开始作为字符串进行分析。如果不是r/u,反复拿到下一个字符直到下一个字符不是字母,数字或者下划线为止。由于最后一次拿到的字符不属于当前标示符,应该被放到下一次进行分析,因此调用tok_backup把字符c回送到缓冲区中,类似ungetch()。最后,设置好p_start & p_end,返回NAME。这样,返回的结果表明下一个token是NAME,开始于p_start,结束于p_end。
大部分情况,tok_nextc会直接返回*tok-
尝试获得下一行的末尾处作为新的inp,否则,说明下一行结尾处没有/n换行符(说明这是最后一行)或者当前行就是最后一行。在前者的情况下,inp就是字符串/0的位置,否则,返回EOF。当获得了下一行之后,返回下一个字符Py_CHARMASK(*tok-
首先调用PyOs_Readline,获得下一行。注意newtok所对应的内存是被malloc出来的,最后需要free。由于在交互模式下,第一句话的prompt是<<<,保存在tok-
作者: ATField
E-Mail: atfield_zhang@hotmail.com
Blog: http://blog.csdn.net/atfield
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1439068
Introduction
上次我们分析了Python中执行程序可分为5个步骤:Tokenizer进行词法分析,把源程序分解为Token
Parser根据Token创建CST
CST被转换为AST
AST被编译为字节码
执行字节码
本文将介绍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 **); |
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是缓冲区的开始。假如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- return tok; } |
Int PyTokenizer_Get(struct tok_state *tok, char **p_start, char **p_end) { int result = tok_get(tok, p_start, p_end); if (tok- result = ERRORTOKEN; tok- } return result; } |
1. 处理缩进
缩进的处理只在一行开始的时候。如果tok_state::atbol(at beginning of line)非0,说明当前处于一行的开始,否则不做处理。/* Get indentation level */ if (tok- register int col = 0; register int altcol = 0; tok- for (;;) { c = tok_nextc(tok); if (c == ' ') col++, altcol++; else if (c == '/t') { col = (col/tok- altcol = (altcol/tok- * tok- } else if (c == '/014') /* Control-L (formfeed) */ col = altcol = 0; /* For Emacs users */ else break; } tok_backup(tok, c); |
if (c == '#' || c == '/n') { /* Lines with only whitespace and/or comments shouldn't affect the indentation and are not passed to the parser as NEWLINE tokens, except *totally* empty lines in interactive mode, which signal the end of a command group. */ if (col == 0 && c == '/n' && tok- blankline = 0; /* Let it through */ else blankline = 1; /* Ignore completely */ /* We can't jump back right here since we still may need to skip to the end of a comment */ } |
if (!blankline && tok- if (col == tok- // 情况1:col=当前缩进,不变 } else if (col < tok- // 情况2:col<当前缩进,进栈 tok- tok- tok- } else /* col > tok- // 情况3:col>当前缩进,退栈 while (tok- col > tok- tok- tok- } } } |
/* Identifier (most frequent token!) */ if (isalpha(c) || c == '_') { /* Process r"", u"" and ur"" */ switch (c) { case 'r': case 'R': c = tok_nextc(tok); if (c == '"' || c == '/'') goto letter_quote; break; case 'u': case 'U': c = tok_nextc(tok); if (c == 'r' || c == 'R') c = tok_nextc(tok); if (c == '"' || c == '/'') goto letter_quote; break; } while (isalnum(c) || c == '_') { c = tok_nextc(tok); } tok_backup(tok, c); *p_start = tok- *p_end = tok- return NAME; } |
tok_nextc
tok_nextc负责从缓冲区中取出下一个字符,可以说是整个PyTokenizer的最核心的部分。/* Get next char, updating state; error code goes into tok- static int tok_nextc(register struct tok_state *tok) { for (;;) { if (tok- // cur没有移动到inp,直接返回*tok- return Py_CHARMASK(*tok- } if (tok- // 字符串模式 } if (tok- // 交互模式 } else { // 磁盘文件模式 } } } |
char *end = strchr(tok- if (end != NULL) end++; else { end = strchr(tok- if (end == tok- tok- return EOF; } } if (tok- tok- tok- tok- tok- return Py_CHARMASK(*tok- |
char *newtok = PyOS_Readline(stdin, stdout, tok- if (tok- tok- if (newtok == NULL) tok- else if (*newtok == '/0') { PyMem_FREE(newtok); tok- } #if !defined(PGEN) && defined(Py_USING_UNICODE) else if (tok_stdin_decode(tok, &newtok) != 0) PyMem_FREE(newtok); #endif else if (tok- size_t start = tok- size_t oldlen = tok- size_t newlen = oldlen + strlen(newtok); char *buf = tok- buf = (char *)PyMem_REALLOC(buf, newlen+1); tok- if (buf == NULL) { PyMem_FREE(tok- tok- PyMem_FREE(newtok); tok- return EOF; } tok- tok- tok- strcpy(tok- PyMem_FREE(newtok); tok- tok- tok- } |
作者: ATField
E-Mail: atfield_zhang@hotmail.com
Blog: http://blog.csdn.net/atfield
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1439068
相关文章推荐
- Python源码分析3 – 词法分析器PyTokenizer
- Python源码分析3 – 词法分析器PyTokenizer
- Python源码分析3 – 词法分析器PyTokenizer http://blog.csdn.net/atfield/article/details/1439068
- Python之美[从菜鸟到高手]--urllib源码分析
- zg手册 之 python2.7.7源码分析(3)-- list 对象和 dict 对象
- Python源码分析4 – Grammar文件和语法分析
- python string.py 源码分析 三:maketrans
- Python字典部分源码分析,字典是无序的
- Python源码分析2 - 一个简单的Python程序的执行
- Ansible源码分析之Python的multiprocessing模块使用
- zg手册 之 python2.7.7源码分析(2)-- python 的整数对象和字符串对象
- 看书 Python 源码分析笔记 (十) Python的初始化
- 【Python】实现漏洞扫描器之MS15-034漏洞批量扫描+源码分析
- 基于Python的datetime模块和time模块源码阅读分析
- 【Python】实现网站常见漏洞扫描+备份扫描+源码分析
- Python源码中的算法分析 之 字符串匹配算法
- python3.6 源码分析(一)
- Python源码分析5 – 语法分析器PyParser
- zg手册 之 python2.7.7源码分析(5)-- python的作用域和名空间
- Python源码分析之dis