2009-09-11 34 views
10

Tôi làm cách nào để thực hiện thụt đầu dòng dưới dạng khối phân tách trong bison + flex. Cũng giống như trong python. Tôi đang viết ngôn ngữ lập trình của riêng mình (chủ yếu là để giải trí, nhưng tôi dự định sử dụng nó cùng với một công cụ trò chơi), tôi sẽ cố gắng tìm ra thứ gì đó đặc biệt để giảm thiểu sự sắp xếp và tối đa hóa tốc độ dev.Cách sử dụng thụt đầu dòng làm dấu phân tách khối với bison và flex

Tôi đã viết một trình biên dịch (thực tế là `langToy ' tới trình dịch Nasm) trong C, nhưng không thành công. Bởi một số lý do nó chỉ có thể xử lý một chuỗi trong toàn bộ tập tin nguồn (tốt, tôi đã tỉnh táo được hơn 48 giờ - vậy ... Bạn biết đấy, suy thoái não).

Tôi không biết nếu dấu ngoặc nhọn và/hoặc bắt đầu -> kết thúc dễ thực hiện hơn (tôi không gặp vấn đề khi làm điều đó) hay chỉ là bộ não của tôi bị khóa.

Cảm ơn trước!


Cập nhật: Được rồi, tôi không có đầu mối về làm thế nào để làm điều đó với flex. Tôi gặp sự cố khi trả lại nhiều DEDENT cho trình phân tích cú pháp. Flex/Bison tương đối mới đối với tôi.


Cập nhật 2: Đây là flex-file tôi đã đi lên với cho đến nay; nó không hoàn toàn nhận được nó:

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

Bạn có thể thử chơi với nó, có thể bạn thấy những gì tôi đang thiếu. trả về nhiều suy luận sẽ dễ dàng trong haXe (return t_dedent (num);).

Mã này không phải lúc nào cũng khớp với các thụt lề/suy luận một cách chính xác.


Cập nhật 3: Tôi nghĩ rằng tôi sẽ từ bỏ hy vọng về flex và làm theo cách của riêng tôi, Nếu có ai biết làm thế nào để làm điều đó trong flex Tôi sẽ rất vui khi nghe nó anyways.

Trả lời

13

Điều bạn cần làm là kiểm tra số lượng khoảng trắng ở đầu mỗi dòng và chèn số lượng mã thông báo INDENT/UNINDENT thích hợp để trình phân tích cú pháp sử dụng để nhóm mọi thứ.Một câu hỏi là những gì bạn muốn làm về các tab so với dấu cách - bạn chỉ muốn chúng tương đương với các điểm dừng tab cố định hay bạn muốn yêu cầu thụt lề để nhất quán (vì vậy nếu một dòng bắt đầu bằng tab và tiếp theo với một khoảng trắng, bạn báo hiệu lỗi, có thể khó hơn một chút).

Giả sử bạn muốn các tab 8 cột cố định, bạn có thể sử dụng giống như

%{ 
/* 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 ... 

Bạn phải chắc chắn rằng bạn bắt đầu phân tích cú pháp trong chế độ thụt lề (để có được thụt đầu dòng trên dòng đầu tiên).

+0

Có vẻ như bạn đã nhận được nó, nhưng tôi muốn các tabstops được tính là 2 dấu cách. Vì vậy, tôi đoán rằng dòng phải là current_line_indent = (current_line_indent + 2) & ~1; – Frank

+0

Có - khi bạn thấy tab, bạn cần phải bump current_line_indent vào tabstop tiếp theo. –

1

Dấu ngoặc nhọn (và như vậy) chỉ đơn giản hơn nếu bạn sử dụng trình mã thông báo để loại bỏ tất cả khoảng trắng (chỉ sử dụng cho các mã thông báo riêng biệt). Xem this page (phần "Trình biên dịch phân tích cú pháp thụt đầu dòng như thế nào?") Cho một số ý tưởng về mã thông báo python.

Nếu bạn không thực hiện mã thông báo trước khi phân tích cú pháp, thì có thể có thêm công việc để làm, tùy thuộc vào cách bạn đang xây dựng trình phân tích cú pháp.

+0

Cảm ơn bạn đã liên kết hữu ích, tôi sẽ cung cấp cho nó một vết nứt và xem nếu tôi thành công lần này. – Frank

0

Bạn cần một quy tắc đó trông tương tự như này (giả như bạn sử dụng các tab cho indents của bạn):

\ t: {return TABDENT; }

Thành thật mà nói, tôi đã luôn luôn tìm thấy dấu ngoặc nhọn (hoặc bắt đầu/kết thúc) để viết và đọc dễ dàng hơn, cả với tư cách con người và người viết lexer/parser.

+0

Vâng, có vẻ như nó ít nhất là dễ dàng hơn để viết một lexer với khối đặc biệt bắt đầu và khối kết thúc biểu tượng. Nó không ** dễ dàng hơn để viết {và} trên bàn phím cục bộ của tôi, D – Frank

5

Câu trả lời của Chris là một chặng đường dài hướng tới một giải pháp có thể sử dụng, cảm ơn một loạt cho việc này! Thật không may, nó thiếu một vài khía cạnh quan trọng hơn mà tôi cần:

  • Nhiều outdents (unindents) cùng một lúc. Xét đoạn mã sau được thải hai outdents sau khi cuộc gọi đến baz:

    def foo(): 
        if bar: 
        baz() 
    
  • Emit outdents khi phần cuối của tập tin được đạt được và vẫn còn trong một số mức độ thụt đầu dòng.

  • Mức thụt lề có kích thước khác nhau. Mã hiện tại của Chris chỉ hoạt động chính xác cho các lần thụt lề 1 dấu cách.

Dựa trên mã của Chris, tôi đã đưa ra giải pháp hoạt động trong tất cả các trường hợp tôi đã gặp phải từ trước đến nay. Tôi đã tạo một dự án mẫu để phân tích cú pháp văn bản dựa trên thụt lề bằng cách sử dụng flex (và bison) trên github: https://github.com/lucasb-eyer/flex-bison-indentation. Nó là một dự án hoàn toàn làm việc (dựa trên CMake), nó cũng theo dõi vị trí dòng và phạm vi cột của mã thông báo hiện tại.

Chỉ trong trường hợp liên kết nên phá vỡ vì lý do gì, đây là thịt của lexer:

#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

Mã của tôi tạo ra một INDENT/UNINDENT cho * mọi dấu cách thụt đầu dòng. Vì vậy, đối với ví dụ của bạn với thụt lề 2 dấu cách, nó sẽ tạo ra hai mã thông báo INDENT sau dòng đầu tiên, 2 mã khác sau dấu thứ hai và 4 UNINDENT ở cuối. Vì vậy, bạn sẽ cần phải có bộ phân tích cú pháp "bỏ qua" các cặp INDENT/UNINDENT thừa dư thừa. Thu gọn chúng trong lexer là khó nếu bạn muốn bắt dấu giảm thụt lề đúng cách, nhưng nếu bạn không quan tâm về điều đó, bạn có thể sử dụng một chồng các mức thụt lề thay vì một bộ đếm đơn. –

Các vấn đề liên quan