日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > python >内容正文

python

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

發(fā)布時(shí)間:2023/12/10 python 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python程序代码解析_Python源码分析3 – 词法分析器PyTokenizer 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Introduction

上次我們分析了Python中執(zhí)行程序可分為5個(gè)步驟:

Tokenizer進(jìn)行詞法分析,把源程序分解為T(mén)oken

Parser根據(jù)Token創(chuàng)建CST

CST被轉(zhuǎn)換為AST

AST被編譯為字節(jié)碼

執(zhí)行字節(jié)碼

本文將介紹Python程序執(zhí)行的第一步,也就是詞法分析。詞法分析簡(jiǎn)單來(lái)說(shuō)就是把源程序的字符分解組合成Token。比如sum=0可以分解成3個(gè)token,'sum', '=', '0'。程序中的whitespace通常只作為分隔符用,最終會(huì)被忽略掉,因此沒(méi)有出現(xiàn)在token的列表中。不過(guò)在Python之中,由于語(yǔ)法規(guī)則的關(guān)系,Tab/Space需要用來(lái)分析程序的縮進(jìn),因此Python中對(duì)于Whitespace的處理比一般C/C++編譯器的處理會(huì)要稍微復(fù)雜一些。

在Python中詞法分析的實(shí)現(xiàn)在Parser目錄下的tokenizer.h和tokenizer.cpp。Python的其他部分會(huì)直接調(diào)用tokenizer.h中定義的函數(shù),如下:

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 **);

這些函數(shù)均以PyTokenizer開(kāi)頭。這是Python源代碼中的一個(gè)約定。雖然Python是用C語(yǔ)言實(shí)現(xiàn)的,其實(shí)現(xiàn)方式借鑒了很多面對(duì)對(duì)象的思想。拿詞法分析來(lái)說(shuō),這四個(gè)函數(shù)均可以看作PyTokenizer的成員函數(shù)。頭兩個(gè)函數(shù)PyTokenizer_FromXXXX可以看作是構(gòu)造函數(shù),返回PyTokenizer的instance。PyTokenizer對(duì)象內(nèi)部狀態(tài),也就是成員變量,儲(chǔ)存在tok_state之中。PyTokenizer_Free可以看作是析構(gòu)函數(shù),負(fù)責(zé)釋放PyTokenizer,也就是tok_state所占用的內(nèi)存。PyTokenizer_Get則是PyTokenizer的一個(gè)成員函數(shù),負(fù)責(zé)取得在字符流中下一個(gè)Token。這兩個(gè)函數(shù)均需要傳入tok_state的指針,和C++中需要隱含傳入this指針給成員函數(shù)的道理是一致的??梢钥吹?#xff0c;OO的思想其實(shí)是和語(yǔ)言無(wú)關(guān)的,即使是C這樣的結(jié)構(gòu)化的語(yǔ)言,也可以寫(xiě)出面對(duì)對(duì)象的程序。

tok_state

tok_state等價(jià)于PyTokenizer這個(gè)class本身的狀態(tài),也就是內(nèi)部的私有成員的集合。部分定義如下:

/* 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直接決定了緩沖區(qū)的內(nèi)容:

buf是緩沖區(qū)的開(kāi)始。假如PyTokenizer處于字符串模式,那么buf指向字符串本身,否則,指向文件讀入的緩沖區(qū)。

cur指向緩沖區(qū)中下一個(gè)字符。

inp指向緩沖區(qū)中有效數(shù)據(jù)的結(jié)束位置。PyTokenizer是以行為單位進(jìn)行處理的,每一行的內(nèi)容存入從buf到inp之間,包括\n。一般情況下 ,PyTokenizer會(huì)直接從緩沖區(qū)中取下一個(gè)字符,一旦到達(dá)inp所指向的位置,就會(huì)準(zhǔn)備取下一行。當(dāng)PyTokenizer處于不同模式下面,具體的行為會(huì)稍有不同。

end是緩沖區(qū)的結(jié)束,在字符串模式下沒(méi)有用到。

start指向當(dāng)前token的開(kāi)始位置,如果現(xiàn)在還沒(méi)有開(kāi)始分析token,start為NULL。

PyTokenzer_FromString & PyTokenizer_FromFile

PyTokenizer_FromString & PyTokenizer_FromFile可以說(shuō)是PyTokenizer的構(gòu)造函數(shù)。從這兩個(gè)函數(shù)的命名可以看出,PyTokenizer支持兩種模式:字符串和文件。由于標(biāo)準(zhǔn)輸入STDIN也可以看作是文件,因此實(shí)際上PyTokenizer支持3種模式:字符串,交互,文件。

PyTokenizer_FromFile的實(shí)現(xiàn)和PyTokenizer_FromString的實(shí)現(xiàn)大致相同。后者的實(shí)現(xiàn)如下:

/* 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;}

直接調(diào)用tok_new返回一個(gè)tok_state的instance,后面的decode_str負(fù)責(zé)對(duì)str進(jìn)行解碼,然后賦給tok->buf/cur/end/inp。

PyTokenizer_Get

下面我們來(lái)分析一下PyTokenizer_Get函數(shù)。該函數(shù)的作用是在PyTokenizer所綁定的字符流(可以是字符串也可以是文件)中取出下一個(gè)token,比如sum=0剛?cè)〉搅?#39;sum',那么下一個(gè)取到的就是'='。一個(gè)返回的token由兩部分參數(shù)描述,一個(gè)是表示token類型的int,一個(gè)是token的具體內(nèi)容,也就是一個(gè)字符串。Python會(huì)把不同token分為若干種類型,這些不同的類型定義在include/token.h里面以宏的形式存在,如NAME,NUMBER,STRING,NEWLINE等。舉例來(lái)說(shuō),'sum'這個(gè)token可以表示成(NAME, 'sum')。NAME是類型,表明sum是一個(gè)名稱(注意請(qǐng)和字符串區(qū)分開(kāi))。此時(shí)Python并不判定該名稱是關(guān)鍵字還是標(biāo)識(shí)符,一律統(tǒng)稱為NAME。而這個(gè)NAME的內(nèi)容是'sum'。PyTokenizer_Get返回的int便是token的類型,而兩個(gè)參數(shù)char **p_start, char **p_end是輸出參數(shù),指向token在PyTokenizer內(nèi)部緩沖區(qū)中的位置。這里采用返回一個(gè)p_start和p_end的意圖是避免構(gòu)造一份token內(nèi)容的copy,而是直接給出token在緩沖區(qū)中的開(kāi)始和結(jié)束的位置。這樣做顯然是為了提高效率。

PyTokenizer_Get的實(shí)現(xiàn)如下,直接調(diào)用tok_get函數(shù):

IntPyTokenizer_Get(struct tok_state *tok, char **p_start, char **p_end){int result = tok_get(tok, p_start, p_end);if (tok->decoding_erred) {result = ERRORTOKEN;tok->done = E_DECODE;}return result;}

tok_get負(fù)責(zé)以下幾件事情:

1. 處理縮進(jìn)

縮進(jìn)的處理只在一行開(kāi)始的時(shí)候。如果tok_state::atbol(at beginning of line)非0,說(shuō)明當(dāng)前處于一行的開(kāi)始,否則不做處理。

/* Get indentation level */

if (tok->atbol) {

register int col = 0;

register int altcol = 0;

tok->atbol = 0;

for (;;) {

c = tok_nextc(tok);

if (c == ' ')

col++, altcol++;

else if (c == '\t') {

col = (col/tok->tabsize + 1) * tok->tabsize;

altcol = (altcol/tok->alttabsize + 1)

* tok->alttabsize;

}

else if (c == '\014') /* Control-L (formfeed) */

col = altcol = 0; /* For Emacs users */

else

break;

}

tok_backup(tok, c);

上面的代碼負(fù)責(zé)計(jì)算縮進(jìn)了多少列。由于tab鍵可能有多種設(shè)定,PyTokenizer對(duì)tab鍵有兩套處理方案:tok->tabsize保存著"標(biāo)準(zhǔn)"的tab的大小,缺省為8(一般不要修改此值)。Tok->alttabsize保存著另外的tab大小,缺省在tok_new中初始化為1。col和altcol保存著在兩種不同tab設(shè)置之下的列數(shù),遇到空格+1,遇到\t則跳到下一個(gè)tabstop,直到遇到其他字符為止。

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->prompt != NULL)

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 */

}

接下來(lái),如果遇到了注釋或者是空行,則不加以處理,直接跳過(guò),這樣做是避免影響縮進(jìn)。唯一的例外是在交互模式下的完全的空行(只有一個(gè)換行符)需要被處理,因?yàn)樵诮换ツJ较驴招幸馕吨唤M語(yǔ)句將要結(jié)束,而在非交互模式下完全的空行是要被直接忽略掉的。

if (!blankline && tok->level == 0) {

if (col == tok->indstack[tok->indent]) {

//情況1:col=當(dāng)前縮進(jìn),不變

}

else if (col > tok->indstack[tok->indent]) {

//情況2:col>當(dāng)前縮進(jìn),進(jìn)棧

tok->pendin++;

tok->indstack[++tok->indent] = col;

tok->altindstack[tok->indent] = altcol;

}

else /* col < tok->indstack[tok->indent] */ {

//情況3:col<當(dāng)前縮進(jìn),退棧

while (tok->indent > 0 &&

col < tok->indstack[tok->indent]) {

tok->pendin--;

tok->indent--;

}

}

}

最后,根據(jù)col和當(dāng)前indstack的棧頂(也就是當(dāng)前縮進(jìn)的位置),確定是哪一種情況,具體請(qǐng)參看上面的代碼。上面的代碼有所刪減,去掉了一些錯(cuò)誤處理,加上了一點(diǎn)注釋。需要說(shuō)明的是PyTokenizer維護(hù)兩個(gè)棧indstack & altindstack,分別對(duì)應(yīng)col和altcol,保存著縮進(jìn)的位置,而tok->indent保存著棧頂。

2. 跳過(guò)whitespace和注釋

代碼很簡(jiǎn)單,在此不做說(shuō)明。

3. 確定token

反復(fù)調(diào)用tok_nextc,獲得下一個(gè)字符,依據(jù)字符內(nèi)容判定是何種token,然后加以返回。具體的過(guò)程比較長(zhǎng),但是logic還是比較簡(jiǎn)單的。

下面舉一個(gè)處理標(biāo)識(shí)符(變量和關(guān)鍵字)的例子

/* 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->start;

*p_end = tok->cur;

return NAME;

}

假如當(dāng)前字符是字母或者是下劃線,則開(kāi)始當(dāng)作標(biāo)示符進(jìn)行分析,否則,繼續(xù)執(zhí)行下面的語(yǔ)句,處理其他的可能性。不過(guò)還有一種可能性,Python中字符串可以是用r或者u開(kāi)頭,比如r"string", u"string"。r代表raw string,u代表unicode string。一旦遇到了r或者u的情況下,直接跳轉(zhuǎn)到letter_quote標(biāo)號(hào)處,開(kāi)始作為字符串進(jìn)行分析。如果不是r/u,反復(fù)拿到下一個(gè)字符直到下一個(gè)字符不是字母,數(shù)字或者下劃線為止。由于最后一次拿到的字符不屬于當(dāng)前標(biāo)示符,應(yīng)該被放到下一次進(jìn)行分析,因此調(diào)用tok_backup把字符c回送到緩沖區(qū)中,類似ungetch()。最后,設(shè)置好p_start & p_end,返回NAME。這樣,返回的結(jié)果表明下一個(gè)token是NAME,開(kāi)始于p_start,結(jié)束于p_end。

tok_nextc

tok_nextc負(fù)責(zé)從緩沖區(qū)中取出下一個(gè)字符,可以說(shuō)是整個(gè)PyTokenizer的最核心的部分。

/* Get next char, updating state; error code goes into tok->done */

static int

tok_nextc(register struct tok_state *tok)

{

for (;;) {

if (tok->cur != tok->inp) {

// cur沒(méi)有移動(dòng)到inp,直接返回*tok->cur++

return Py_CHARMASK(*tok->cur++); /* Fast path */

}

if (tok->fp == NULL) {

//字符串模式

}

if (tok->prompt != NULL) {

//交互模式

}

else {

//磁盤(pán)文件模式

}

}

}

大部分情況,tok_nextc會(huì)直接返回*tok->cur++,直到tok->cur移動(dòng)到達(dá)tok->inp。一旦tok->cur==tok->inp,tok_nextc會(huì)讀入下一行。根據(jù)PyTokenizer處于模式的不同,處理方式會(huì)不太一樣:

1. 字符串模式

字符串的處理是最簡(jiǎn)單的一種情況,如下:

char *end = strchr(tok->inp, '\n');

if (end != NULL)

end++;

else {

end = strchr(tok->inp, '\0');

if (end == tok->inp) {

tok->done = E_EOF;

return EOF;

}

}

if (tok->start == NULL)

tok->buf = tok->cur;

tok->line_start = tok->cur;

tok->lineno++;

tok->inp = end;

return Py_CHARMASK(*tok->cur++);

嘗試獲得下一行的末尾處作為新的inp,否則,說(shuō)明下一行結(jié)尾處沒(méi)有\(zhòng)n換行符(說(shuō)明這是最后一行)或者當(dāng)前行就是最后一行。在前者的情況下,inp就是字符串\0的位置,否則,返回EOF。當(dāng)獲得了下一行之后,返回下一個(gè)字符Py_CHARMASK(*tok->cur++)。

2. 交互模式

代碼如下:

char *newtok = PyOS_Readline(stdin, stdout, tok->prompt);

if (tok->nextprompt != NULL)

tok->prompt = tok->nextprompt;

if (newtok == NULL)

tok->done = E_INTR;

else if (*newtok == '\0') {

PyMem_FREE(newtok);

tok->done = E_EOF;

}

#if !defined(PGEN) && defined(Py_USING_UNICODE)

else if (tok_stdin_decode(tok, &newtok) != 0)

PyMem_FREE(newtok);

#endif

else if (tok->start != NULL) {

size_t start = tok->start - tok->buf;

size_t oldlen = tok->cur - tok->buf;

size_t newlen = oldlen + strlen(newtok);

char *buf = tok->buf;

buf = (char *)PyMem_REALLOC(buf, newlen+1);

tok->lineno++;

if (buf == NULL) {

PyMem_FREE(tok->buf);

tok->buf = NULL;

PyMem_FREE(newtok);

tok->done = E_NOMEM;

return EOF;

}

tok->buf = buf;

tok->cur = tok->buf + oldlen;

tok->line_start = tok->cur;

strcpy(tok->buf + oldlen, newtok);

PyMem_FREE(newtok);

tok->inp = tok->buf + newlen;

tok->end = tok->inp + 1;

tok->start = tok->buf + start;

}

首先調(diào)用PyOs_Readline,獲得下一行。注意newtok所對(duì)應(yīng)的內(nèi)存是被malloc出來(lái)的,最后需要free。由于在交互模式下,第一句話的prompt是>>>,保存在tok->prompt中。從第二句開(kāi)始提示符是...,保存在tok->nextprompt中,因此需要設(shè)置tok->prompt = tok->nextprompt。最后一個(gè)else if (tok->start != NULL)的作用是,一旦當(dāng)讀入下一行的時(shí)候,當(dāng)前token還沒(méi)有結(jié)束(一個(gè)典型的例子是長(zhǎng)字符串"""可以跨越多行),由于buf原來(lái)的內(nèi)容不能丟棄,下一行的內(nèi)容必須加到buf的末尾,。PyTokenizer的做法是調(diào)用realloc改變buf的大小,然后把下一行的內(nèi)容strcpy到buf的末尾。這樣做雖然效率不高,由于一般情況下此種情況發(fā)生并不頻繁,而且是處于交互模式下,因此性能上面沒(méi)有問(wèn)題。

3. 文件模式

文件模式下的處理比上面兩種模式都復(fù)雜。主要原因是文件模式下一行可能比BUFSIZE大很多,因此一旦BUFSIZE不夠容納一整行的話,必須反復(fù)讀入,realloc緩沖區(qū)buf,然后把剛剛讀入的內(nèi)容append到buf的末尾,直到遇到行結(jié)束符為止。如果tok->start != NULL,說(shuō)明當(dāng)前正在讀入token之中,同樣的當(dāng)前的buf不能丟棄,因此讀入的新一行的內(nèi)容同樣也要append到buf的末尾,否則,新一行的內(nèi)容直接讀入到buf中。由于代碼比較多,這里就不給出了。

評(píng)論

#galahython 發(fā)表于2007-01-09 14:21:40 IP: 222.66.63.*樓主,抱歉,我沒(méi)仔細(xì)閱讀這一篇就想提問(wèn)題了,因?yàn)槟闶怯肅Python的C源碼來(lái)分析的,C程序看得費(fèi)勁。

我最近在看龍書(shū),上面有一個(gè)用DFA來(lái)識(shí)別字串的簡(jiǎn)單算法,我用Python寫(xiě)了一下,沒(méi)問(wèn)題,接下去有一個(gè)NFA轉(zhuǎn)DFA的算法,用到了subset-construction來(lái)實(shí)現(xiàn)epsilon-closure的,也用Python寫(xiě)出來(lái)了,但是生成的DFA不知道如何使用,因?yàn)檫@個(gè)DFA的每一個(gè)狀態(tài),都對(duì)應(yīng)著NFA的一個(gè)狀態(tài)集,我不是計(jì)算機(jī)科班出身的沒(méi)有理論基礎(chǔ),所以到這一步就不理解了,一個(gè)狀態(tài)對(duì)應(yīng)一個(gè)狀態(tài)集?這樣的DFA如何用?

不知樓主有空時(shí)能否講解一下?

我在網(wǎng)上下載了幾個(gè)Python的Parser,其中一個(gè)也用到了epsilon的算法來(lái)構(gòu)造DFA,但還沒(méi)來(lái)得及細(xì)看。

現(xiàn)在也就是簡(jiǎn)單地一問(wèn),先把心中的疑惑提出來(lái),呵呵

#ATField 發(fā)表于2007-01-12 22:41:13 IP: 124.78.245.*NFA轉(zhuǎn)DFA之后,NFA其實(shí)就沒(méi)有用了,DFA的狀態(tài)確實(shí)對(duì)應(yīng)NFA的狀態(tài)集,不過(guò)這一點(diǎn)不影響你對(duì)DFA的使用,只是定義了DFA和NFA之間的對(duì)應(yīng)關(guān)系,對(duì)DFA本身并沒(méi)有直接影響。因此直接用DFA匹配的算法就可以了,無(wú)需考慮NFA

總結(jié)

以上是生活随笔為你收集整理的python程序代码解析_Python源码分析3 – 词法分析器PyTokenizer的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。