Как использовать отступы как разделители блоков с бизонами и гибкими
Я понял, как реализовать отступы как разделители блоков в 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; }
Честно говоря, я всегда находил скобки (или начало/конец), чтобы их легче было писать и читать, как в качестве человека, так и в качестве автора лексира/парсера.