2009-09-11 45 views
10

我正在做如何在bison + flex中使用縮進作爲塊分隔符。就像在Python中一樣。我正在編寫自己的編程語言(主要是爲了好玩,但我打算將它與遊戲引擎一起使用),我會嘗試提出一些特殊的東西,最大限度地減少樣板並最大限度地提高開發速度。如何使用縮進作爲塊分隔符使用野牛和flex

我已經寫了一個編譯器(實際上是一個`langToy'給Nasm翻譯器),但是失敗了。由於某種原因,它只能處理整個源文件中的一個字符串(當然,我已經醒了超過48小時 - 所以......你知道,大腦崩潰了)。

我不知道大括號和/或begin - > end是否更容易實現(我沒有這個問題),或者只是我的大腦鎖定了。

在此先感謝!


更新:好吧,我不知道如何使用Flex做的線索。將多個DEDENT返回給解析器時遇到問題。 Flex/Bison對我而言是比較新的。


更新2: 這是柔性的文件我已經想出了爲止;它不完全明白它:

%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(); 
} 

你可以嘗試玩它,也許你看到我失蹤。返回多個dedent在haXe中會很容易(return t_dedent(num);)。

此代碼並不總是正確匹配縮進/縮進。


更新3:我認爲我會放棄對柔性希望,做我自己的方式,如果有人知道如何做到這一點的柔性我會很樂意聽到它反正。

回答

13

你需要做的是讓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 ... 

你必須確保你開始縮進模式解析(拿到第一行縮進)。

+0

看起來好像你已經得到了它,但我希望製表符被計爲2個空格。所以我猜這條線應該是 current_line_indent =(current_line_indent + 2)& ~1; – Frank 2009-10-03 17:39:00

+0

是的 - 當你看到一個標籤時,你需要將current_line_indent撞到下一個tabstop。 – 2009-10-04 05:37:10

1

如果您使用標記器去除所有空格(使用僅用於分隔標記),大括號(以及其他)就更簡單了。有關Python標記化的一些想法,請參閱this page(「編譯器如何解析縮進?」一節)。

如果您在解析之前沒有進行標記化,那麼可能還有其他工作要做,這取決於您如何構建解析器。

+0

感謝您的有用的鏈接,我會給它一個裂縫,看看我成功這個時候。 – Frank 2009-09-11 21:33:34

0

你需要一個規則,看起來類似於這樣(假設你使用的標籤,瞭解縮進):

\ T:{回報TABDENT; }

老實說,我已經總是發現花括號(或開始/結束)更容易編寫和閱讀,無論是作爲一個人和作爲一個詞法分析器/解析器作家。

+0

好吧,看起來至少使用特殊的塊開始和塊結束符號來編寫詞法分析器會更容易。在我的本地化鍵盤上,它不是**更容易編寫{和}; D – Frank 2009-09-12 00:38:16

5

克里斯的答案走了一個可行的解決方案很長的路要走,感謝一堆! 不幸的是,它缺少我需要一些更重要的方面:在一次

  • 多outdents(unindents)。考慮呼叫後,下面的代碼應該發出 outdents到baz

    def foo(): 
        if bar: 
        baz() 
    
  • 的Emit outdents當達到文件的末尾,仍然是一些縮進級別。

  • 不同大小的縮進級別。克里斯目前的代碼只適用於1空間縮進。

基於克里斯的代碼,我想出了一個解決方案,它適用於我迄今爲止遇到的所有情況。我創建了一個模板項目,用於在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); } 
+0

我的代碼爲*每個*縮進空間產生一個INDENT/UNINDENT。因此,對於具有雙空間縮進的示例,它將在第一行之後生成兩個INDENT標記,第二行之後將生成兩個INDENT標記,並在結尾處生成四個UNINDENT。所以你需要讓解析器「忽略」額外的冗餘INDENT/UNINDENT對。如果你想正確地捕捉尾隨縮減縮進,但在詞法分析器中摺疊它們卻很困難,但如果你不關心它,可以使用一堆縮進級別而不是單個計數器。 – 2016-12-30 19:00:22