Как использовать отступы как разделители блоков с бизонами и гибкими

Я понял, как реализовать отступы как разделители блоков в bison + flex. Так же, как в python. Я пишу свой собственный язык программирования (в основном для удовольствия, но я намерен использовать его вместе с движком игры), я попытаюсь придумать что-то особенное, которое минимизирует шаблон и максимизирует скорость dev.

Я уже написал компилятор (фактически "langToy" для переводчика Nasm) на C, но не смог. По какой-то причине он мог обрабатывать только одну строку во всем исходном файле (ну, я просыпался более 48 часов - так... Знаешь, мозговой кризис).

Я не знаю, легче ли реализовать фигурные скобки и/или начать → конец (у меня нет проблемы с этим), или если это только мой мозг, который блокируется.

Спасибо заранее!


Обновление: Хорошо, я понятия не имею, как это сделать с помощью 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();
}

Вы можете попробовать поиграть с ним, может быть, вы видите, что мне не хватает. возвращение нескольких разделителей было бы легкостью в haXe (return t_dedent (num);).

Этот код не всегда правильно соответствует отступов/разделителей.


Обновление 3: Я думаю, что я откажусь от надежды на flex и сделаю это по-своему. Если кто-нибудь знает, как это сделать в flex, я был бы счастлив услышать это в любом случае.

Ответ 1

То, что вам нужно сделать, - это вычислить количество пробелов в начале каждой строки и вставить соответствующее количество токенов 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 ...

Вам нужно убедиться, что вы начинаете разбор в режиме отступа (чтобы получить отступ в первой строке).

Ответ 2

Ответ Криса проходит долгий путь к полезному решению, благодаря этой связке! К сожалению, мне не хватает еще нескольких важных аспектов, которые мне нужны:

  • Несколько внешних (одновременно). Рассмотрим следующий код: после вызова baz следует испустить два:

    def foo():
      if bar:
        baz()
    
  • Извлечь outdents, когда конец файла достигнут и все еще находится на некотором уровне отступа.

  • Уровни отступов разного размера. Текущий код Криса работает корректно только для 1-пространственных отступов.

Основываясь на коде Криса, я придумал решение, которое работает во всех случаях, с которыми я сталкивался до сих пор. Я создал шаблонный проект для синтаксического анализа текста с отступом, используя flex (и bison) в github: https://github.com/lucasb-eyer/flex-bison-indentation. Это полностью работающий (CMake-based) проект, который также отслеживает позицию линии и диапазон столбцов текущего токена.

На всякий случай ссылка должна сломаться по какой-либо причине, вот мясо лексера:

#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 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); }

Ответ 3

Кудрявые скобки (и такие) проще только в том случае, если вы используете токенизатор, который удаляет все пробелы (используя только разграничение токенов). См. на этой странице (раздел "Как компилятор разбирает отступ?" ) Для некоторых идей по токенизации python.

Если вы не выполняете токенизацию перед синтаксическим анализом, тогда может потребоваться дополнительная работа, это зависит от того, как вы создаете парсер.

Ответ 4

Вам нужно правило, которое выглядит аналогично этому (предположим, что вы используете вкладки для ваших отступов):

\ t: {return TABDENT; }

Честно говоря, я всегда находил скобки (или начало/конец), чтобы их легче было писать и читать, как в качестве человека, так и в качестве автора лексира/парсера.