11

我了解如何在 bison + flex 中实现缩进作为块分隔符。就像在 python 中一样。我正在编写自己的编程语言(主要是为了好玩,但我打算将它与游戏引擎一起使用),我将尝试提出一些特别的东西,以最大限度地减少样板并最大限度地提高开发速度。

我已经用 C 编写了一个编译器(实际上是 Nasm 翻译器的“langToy”),但失败了。由于某种原因,它只能处理整个源文件中的一个字符串(好吧,我已经醒了超过 48 小时 - 所以......你知道,大脑崩溃了)。

我不知道大括号和/或开始 - > 结束是否更容易实现(我这样做没有问题)或者只是我的大脑被锁定了。

提前致谢!


更新:好的,我不知道如何用 flex 来做。我在将多个 DEDENT 返回到解析器时遇到问题。Flex/Bison 对我来说相对较新。


更新 2: 这是我迄今为止提出的 flex 文件;它不太明白:

%x t
%option noyywrap

%{
  int lineno = 0, ntab = 0, ltab = 0, dedent = 0;
%}

%%

<*>\n  { ntab = 0; BEGIN(t); }
<t>\t  { ++ntab; }
<t>.   { int i; /* my compiler complains not c99 if i use for( int i=0... */
         if( ntab > ltab )
           printf("> indent >\n");
         else if( ntab < ltab )
           for( i = 0; i < ltab - ntab; i++ )
             printf("< dedent <\n");
         else
           printf("=        =\n");

         ltab = ntab; ntab = 0;
         BEGIN(INITIAL);
         /* move to next rule */
         REJECT;}
.    /* ignore everything else for now */

%%

main()
{
  yyin = fopen( "test", "r" );
  yylex();
}

您可以尝试使用它,也许您会看到我缺少的东西。在 Haxe ( return t_dedent( num ); ) 中返回多个 dedent 会很容易。

此代码并不总是正确匹配缩进/缩进。


更新 3:我认为我会放弃对 flex 的希望并以自己的方式去做,如果有人知道如何在 flex 中做到这一点,无论如何我都会很高兴听到它。

4

4 回答 4

15

您需要做的是让 flex 计算每行开头的空白数量,并插入适当数量的 INDENT/UNINDENT 标记以供解析器用于对事物进行分组。一个问题是你想对制表符和空格做些什么——你只是想让它们与固定的制表位等效,还是你想要求缩进保持一致(所以如果一行以制表符开头,下一行有一个空格,你表示一个错误,这可能有点困难)。

假设您想要固定的 8 列制表位,您可以使用类似

%{
/* globals to track current indentation */
int current_line_indent = 0;   /* indentation of the current line */
int indent_level = 0;          /* indentation level passed to the parser */
%}

%x indent /* start state for parsing the indentation */
%s normal /* normal start state for everything else */

%%
<indent>" "      { current_line_indent++; }
<indent>"\t"     { current_line_indent = (current_line_indent + 8) & ~7; }
<indent>"\n"     { current_line_indent = 0; /*ignoring blank line */ }
<indent>.        {
                   unput(*yytext);
                   if (current_line_indent > indent_level) {
                       indent_level++;
                       return INDENT;
                   } else if (current_line_indent < indent_level) {
                       indent_level--;
                       return UNINDENT;
                   } else {
                       BEGIN normal;
                   }
                 }

<normal>"\n"     { current_line_indent = 0; BEGIN indent; }
... other flex rules ...

您必须确保以缩进模式开始解析(以获取第一行的缩进)。

于 2009-09-22T02:53:15.283 回答
6

克里斯的回答对一个可用的解决方案大有帮助,非常感谢!不幸的是,它缺少我需要的一些更重要的方面:

  • 一次有多个缩进(取消缩进)。考虑以下代码应该在调用后发出两个outdents baz

    def foo():
      if bar:
        baz()
    
  • 当到达文件末尾并且仍处于某个缩进级别时发出缩进。

  • 不同大小的缩进级别。Chris 当前的代码仅适用于 1 空格缩进。

基于 Chris 的代码,我想出了一个适用于我迄今为止遇到的所有情况的解决方案。我创建了一个模板项目,用于在 github 上使用 flex(和 bison)解析基于缩进的文本:https ://github.com/lucasb-eyer/flex-bison-indentation 。它是一个完全工作(基于 CMake)的项目,它还跟踪当前标记的行位置和列范围。

以防万一链接由于某种原因而中断,这里是词法分析器的核心:

#include <stack>

int g_current_line_indent = 0;
std::stack<size_t> g_indent_levels;
int g_is_fake_outdent_symbol = 0;

static const unsigned int TAB_WIDTH = 2;

#define YY_USER_INIT { \
    g_indent_levels.push(0); \
    BEGIN(initial); \
}
#include "parser.hh"

%}

%x initial
%x indent
%s normal

%%
    int indent_caller = normal;

 /* Everything runs in the <normal> mode and enters the <indent> mode
    when a newline symbol is encountered.
    There is no newline symbol before the first line, so we need to go
    into the <indent> mode by hand there.
 */
<initial>.  { set_yycolumn(yycolumn-1); indent_caller = normal; yyless(0); BEGIN(indent); }
<initial>\n { indent_caller = normal; yyless(0); BEGIN(indent); }    

<indent>" "     { g_current_line_indent++; }
<indent>\t      { g_current_line_indent = (g_current_line_indent + TAB_WIDTH) & ~(TAB_WIDTH-1); }
<indent>\n      { g_current_line_indent = 0; /* ignoring blank line */ }
<indent><<EOF>> {
                    // When encountering the end of file, we want to emit an
                    // outdent for all indents currently left.
                    if(g_indent_levels.top() != 0) {
                        g_indent_levels.pop();

                        // See the same code below (<indent>.) for a rationale.
                        if(g_current_line_indent != g_indent_levels.top()) {
                            unput('\n');
                            for(size_t i = 0 ; i < g_indent_levels.top() ; ++i) {
                                unput(' ');
                            }
                        } else {
                            BEGIN(indent_caller);
                        }

                        return TOK_OUTDENT;
                    } else {
                        yyterminate();
                    }
                }

<indent>.       {
                    if(!g_is_fake_outdent_symbol) {
                        unput(*yytext);
                    }
                    g_is_fake_outdent_symbol = 0;
                    // -2: -1 for putting it back and -1 for ending at the last space.
                    set_yycolumn(yycolumn-1);

                    // Indentation level has increased. It can only ever
                    // increase by one level at a time. Remember how many
                    // spaces this level has and emit an indentation token.
                    if(g_current_line_indent > g_indent_levels.top()) {
                        g_indent_levels.push(g_current_line_indent);
                        BEGIN(indent_caller);
                        return TOK_INDENT;
                    } else if(g_current_line_indent < g_indent_levels.top()) {
                        // Outdenting is the most difficult, as we might need to
                        // outdent multiple times at once, but flex doesn't allow
                        // emitting multiple tokens at once! So we fake this by
                        // 'unput'ting fake lines which will give us the next
                        // outdent.
                        g_indent_levels.pop();

                        if(g_current_line_indent != g_indent_levels.top()) {
                            // Unput the rest of the current line, including the newline.
                            // We want to keep it untouched.
                            for(size_t i = 0 ; i < g_current_line_indent ; ++i) {
                                unput(' ');
                            }
                            unput('\n');
                            // Now, insert a fake character indented just so
                            // that we get a correct outdent the next time.
                            unput('.');
                            // Though we need to remember that it's a fake one
                            // so we can ignore the symbol.
                            g_is_fake_outdent_symbol = 1;
                            for(size_t i = 0 ; i < g_indent_levels.top() ; ++i) {
                                unput(' ');
                            }
                            unput('\n');
                        } else {
                            BEGIN(indent_caller);
                        }

                        return TOK_OUTDENT;
                    } else {
                        // No change in indentation, not much to do here...
                        BEGIN(indent_caller);
                    }
                }

<normal>\n    { g_current_line_indent = 0; indent_caller = YY_START; BEGIN(indent); }
于 2013-03-28T13:52:50.573 回答
1

如果您使用去除所有空格的标记器(仅用于分隔标记),大括号(等)只会更简单。有关python 标记化的一些想法,请参阅此页面(“编译器如何解析缩进?”部分)。

如果您在解析之前没有进行标记化,那么可能还有其他工作要做,这取决于您构建解析器的方式。

于 2009-09-11T21:15:48.660 回答
0

您需要一个看起来与此类似的规则(假设您使用制表符进行缩进):

\t: {返回 TABDENT; }

坦率地说,我总是发现大括号(或开始/结束)更容易编写和阅读,无论是作为人类还是作为词法分析器/解析器编写器。

于 2009-09-12T00:05:29.360 回答