python缩进设计_设计类Python编译器时如何处理tab和space缩进?

token [NAME]: func

token [NAME]: fab

token [LPAR]: (

token [NAME]: number

token [RPAR]: )

token [COLON]: :

token [NEWLINE]:

token [INDENT]:

token [NAME]: if

token [LPAR]: (

token [NAME]: number

token [EQEQUAL]: ==

token [NUMBER]: 1

token [RPAR]: )

token [COLON]: :

token [NEWLINE]:

token [INDENT]:

token [NAME]: return

token [NUMBER]: 1

token [NEWLINE]:

token [DEDENT]:

token [NAME]: if

token [LPAR]: (

token [NAME]: number

token [EQEQUAL]: ==

token [NUMBER]: 2

token [RPAR]: )

token [COLON]: :

token [NEWLINE]:

token [INDENT]:

token [NAME]: return

token [NUMBER]: 2

token [NEWLINE]:

token [DEDENT]:

token [NAME]: return

token [NAME]: fab

token [LPAR]: (

token [NAME]: number

token [MINUS]: -

token [NUMBER]: 1

token [RPAR]: )

token [PLUS]: +

token [NAME]: fab

token [LPAR]: (

token [NAME]: number

token [MINUS]: -

token [NUMBER]: 2

token [RPAR]: )

token [NEWLINE]:

token [DEDENT]:

关键词: layout sensitive parsing, off-side rule

@Kontinuation 讲的 lexer 插入伪 INDENT / DEDENT token 的方式是主流做法, 不过维护一个状态变量会对实现 scannerless parser, online parser 或者 parallel parser 带来一些困难.

另一个做法是在语法描述中添加 layout constraint --- 当然你的分析器生成程序要支持对语法添加约束, 例如 Antlr 可以用 {boolean-expr}? 指定 semantic predicate. 然后解析 python 函数定义的语法可以像这么写 (伪代码):

funcdef: 'def' NAME parameters ':' funcdef_body {

$5.line > $1.line && $5.col > $1.col

}?

;

我有一个想法。

首先确定源代码的缩进是由空格控制还是制表符控制,并在之后使用一致的方式进行处理。为了便于说明,下面不妨假设我们需要处理的代码都是由制表符控制缩进的。

在词法解析的阶段,需要把制表符输出为一个token,比如叫tabToken。然后对于每个语句,就有这样一个特点:在换行符的后边的tabToken数目就是代码的层级数(特殊情况另说)。根据这个原理,处理的时候就可以在层级数变化的时候,插入花括号token:当层级数变大的时候,插入左花括号token;变小的时候,插入右花括号token。

那么,我们就可以使用处理C代码的方法来处理Python代码了。

我思考过这个问题: Cirru 解析缩进的方案

最后想的方案是用一个变量记录上一次缩进的层级, 后面每解析一行做一次比对, 并刷新记录,

我只考虑了空格, 而且语法也限定在很简单的情况.

大多数基于缩进的语言都是同时支持 Tab 跟空格的, 我好像只看到 Nim 只支持空格的.

如果是发布给他人使用, 照顾不同的使用习惯是有必要考虑的.

先说结论:

正如 @饭米 和 @薛银松 所说,处理这种基于缩进的语言,首先把 Tab 按照统一的规则替换成空格,然后根据空格的层级,解析生成 INDENT 和 DEDENT 两种 Token,其实也就相当于把空格根据上下文缩进替换成 { 和 },然后缩进错误神马的,用一个栈来存,INDENT 就进栈,DEDENT 就弹栈,大概这样

然后说说如何解决的

首先查阅 Python 官方文档(2. Lexical analysis),关于 Lexical Analysis 这块的 Indentation 处理是这么说的

Leading whitespace (spaces and tabs) at the beginning of a logical line is used

to compute the indentation level of the line, which in turn is used to determine

the grouping of statements.

First, tabs are replaced (from left to right) by one to eight spaces such that

the total number of characters up to and including the replacement is a multiple

of eight (this is intended to be the same rule as used by Unix). The total

number of spaces preceding the first non-blank character then determines the

line’s indentation. Indentation cannot be split over multiple physical lines

using backslashes; the whitespace up to the first backslash determines the

indentation.

然后放狗搜索 StackOverflow,发现有很多类似问题,比如下面这个

Parsing "off-side" (indentation-based) languages

然后下面这个链接给出了一个示例

c++ - Indentation control while developing a small python like language

最后,还是直接参考 Python 源码比较准确,相关代码位于 Python-2.7.9/Parser/tokenizer.c 的 1209 行到 1359 行

/* Get next token, after space stripping etc. */

static int

tok_get(register struct tok_state *tok, char **p_start, char **p_end)

{

register int c;

int blankline;

*p_start = *p_end = NULL;

nextline:

tok->start = NULL;

blankline = 0;

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

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

}

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

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

/* No change */

if (altcol != tok->altindstack[tok->indent]) {

if (indenterror(tok))

return ERRORTOKEN;

}

}

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

/* Indent -- always one */

if (tok->indent+1 >= MAXINDENT) {

tok->done = E_TOODEEP;

tok->cur = tok->inp;

return ERRORTOKEN;

}

if (altcol <= tok->altindstack[tok->indent]) {

if (indenterror(tok))

return ERRORTOKEN;

}

tok->pendin++;

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

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

}

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

/* Dedent -- any number, must be consistent */

while (tok->indent > 0 &&

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

tok->pendin--;

tok->indent--;

}

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

tok->done = E_DEDENT;

tok->cur = tok->inp;

return ERRORTOKEN;

}

if (altcol != tok->altindstack[tok->indent]) {

if (indenterror(tok))

return ERRORTOKEN;

}

}

}

}

tok->start = tok->cur;

/* Return pending indents/dedents */

if (tok->pendin != 0) {

if (tok->pendin < 0) {

tok->pendin++;

return DEDENT;

}

else {

tok->pendin--;

return INDENT;

}

}

again:

tok->start = NULL;

/* Skip spaces */

do {

c = tok_nextc(tok);

} while (c == ' ' || c == '\t' || c == '\014');

/* Set start of current token */

tok->start = tok->cur - 1;

/* Skip comment, while looking for tab-setting magic */

if (c == '#') {

static char *tabforms[] = {

"tab-width:", /* Emacs */

":tabstop=", /* vim, full form */

":ts=", /* vim, abbreviated form */

"set tabsize=", /* will vi never die? */

/* more templates can be added here to support other editors */

};

char cbuf[80];

char *tp, **cp;

tp = cbuf;

do {

*tp++ = c = tok_nextc(tok);

} while (c != EOF && c != '\n' &&

(size_t)(tp - cbuf + 1) < sizeof(cbuf));

*tp = '\0';

for (cp = tabforms;

cp < tabforms + sizeof(tabforms)/sizeof(tabforms[0]);

cp++) {

if ((tp = strstr(cbuf, *cp))) {

int newsize = atoi(tp + strlen(*cp));

if (newsize >= 1 && newsize <= 40) {

tok->tabsize = newsize;

if (Py_VerboseFlag)

PySys_WriteStderr(

"Tab size set to%d\n",

newsize);

}

}

}

while (c != EOF && c != '\n')

c = tok_nextc(tok);

}

个人感觉可以把tab和换行符当成token处理。\n\t* 这样,记录\t的数量,这样处理每一个statement都可以方便地知道在哪一层缩进了。

article_wechat2021.jpg?1111

相关标签:

本文原创发布php中文网,转载请注明出处,感谢您的尊重!

相关文章

相关视频

网友评论

文明上网理性发言,请遵守 新闻评论服务协议我要评论

user_avatar.jpg

立即提交

专题推荐5d1ef1e9e866e635.jpg独孤九贱-php全栈开发教程

全栈 100W+

主讲:Peter-Zhu 轻松幽默、简短易学,非常适合PHP学习入门

5d1ef236ca878949.jpg玉女心经-web前端开发教程

入门 50W+

主讲:灭绝师太 由浅入深、明快简洁,非常适合前端学习入门

5d1ef2477c7d7587.jpg天龙八部-实战开发教程

实战 80W+

主讲:西门大官人 思路清晰、严谨规范,适合有一定web编程基础学习

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值