2009-09-11 8 views
10

Informo cómo implementar la indentación como delimitadores de bloque en bison + flex. Al igual que en Python. Estoy escribiendo mi propio lenguaje de programación (sobre todo por diversión, pero tengo la intención de usarlo junto con un motor de juego), intentaré encontrar algo especial que minimice los estándares y maximice la velocidad de desarrollo.Cómo utilizar la sangría como delimitadores de bloque con bison y flex

Ya he escrito un compilador (en realidad, un `langToy ' para el traductor de Nasm) en C, pero falló. Por alguna razón, solo fue capaz de manejar una cadena en todo el archivo fuente (bueno, había estado despierto durante más de 48 horas, así que ... Ya sabes, la crisis cerebral).

No sé si las llaves y/o el comienzo -> fin son más fáciles de implementar (no tengo problemas para hacer eso) o si es solo mi cerebro el que se traba.

¡Gracias de antemano!


Actualización: Está bien, no tengo ni idea de cómo hacerlo con la flexión. Tengo problemas para devolver varios DEDENTES al analizador. Flex/Bison son relativamente nuevos para mí.


Actualización 2: Este es el flex-archivo que he encontrado hasta el momento; no acaba de conseguirlo:

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

Puede tratar de jugar un rato con él, tal vez usted ve lo que me falta. devolver dedents múltiples sería una facilidad en haXe (return t_dedent (num);).

Este código no siempre coincide con las sangrías/dedents correctamente.


Actualización 3: creo que voy a perder la esperanza de flexión y hacerlo a mi manera, Si alguien sabe cómo hacerlo en flexión que estaría feliz de escuchar que de todos modos.

Respuesta

13

Lo que necesita hacer es tener un conteo flexible de la cantidad de espacios en blanco al comienzo de cada línea e insertar un número apropiado de tokens INDENT/UNINDENT para que el analizador lo use para agrupar elementos.Una pregunta es qué quiere hacer con respecto a las pestañas frente a los espacios: ¿quiere que sean equivalentes con pestañas fijas, o si desea que la indentación sea coherente (de modo que si una línea comienza con una pestaña y la siguiente)? con un espacio, indica un error, que probablemente sea un poco más difícil).

asumiendo que usted quiere tabStops 8-columna fija, se puede usar algo como

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

Usted tiene que asegurarse de que inicie el análisis sintáctico en el modo de guión (para obtener la sangría en la primera línea).

+0

Parece que lo tienes, pero quiero que las pestañas cuenten como 2 espacios. Así que supongo que la línea debe ser current_line_indent = (current_line_indent + 2) & ~1; – Frank

+0

Sí, cuando ve una pestaña, necesita colocar current_line_indent en la siguiente tabla de pestañas. –

1

Los corchetes (y más) solo son más simples si usa un tokenizador que elimina todos los espacios en blanco (usar es solo para separar tokens). Consulte this page (la sección "¿Cómo analiza el compilador la sangría?") Para obtener algunas ideas sobre el uso de tokens de python.

Si no está haciendo tokenización antes del análisis, entonces puede haber trabajo adicional que hacer, depende de cómo está construyendo el analizador.

+0

Gracias por el enlace útil, voy a darle una grieta y ver si I tiene éxito esta vez. – Frank

0

Se necesita una regla que parece análoga a esto (suponiendo que utilice fichas para sus guiones):

\ t: {return TABDENT; }

Francamente, no tengo siempre encontró apoyos (o comienzo/final) a ser más fácil de escribir y de leer, tanto como ser humano y como escritor analizador léxico/analizador.

+0

Bueno, parece que al menos es más fácil escribir un lexer con símbolos especiales de inicio de bloque y fin de bloque. ** No es ** más fácil escribir {y} en mi teclado localizado; D – Frank

5

La respuesta de Chris va un largo camino hacia una solución utilizable, ¡muchas gracias por esto! Por desgracia, no se encuentra un par de aspectos más importantes que yo necesitaba:

  • múltiples outdents (unindents) a la vez. Considere el siguiente código debe emitir dos outdents después de la llamada a baz:

    def foo(): 
        if bar: 
        baz() 
    
  • outdents Emitir cuando el final del archivo se alcanza y sigue siendo en cierto nivel de sangría.

  • Niveles de sangría de diferentes tamaños. El código actual de Chris solo funciona correctamente para sangrías de 1 espacio.

Basado en el código de Chris, se me ocurrió una solución que funciona en todos los casos que he encontrado hasta ahora. He creado un proyecto de plantilla para analizar texto basado en sangría utilizando flex (y bison) en github: https://github.com/lucasb-eyer/flex-bison-indentation. Es un proyecto completamente funcional (basado en CMake) que también rastrea la posición de la línea y el rango de columna del token actual.

Por si acaso el enlace se rompe por cualquier razón, aquí es la carne del léxico:

#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

Mi código produce un INDENT/UNINDENT para * cada * espacio de sangría. Entonces para su ejemplo con sangrías de 2 espacios, producirá dos testigos INDENT después de la primera línea, otros 2 después del segundo y 4 UNINDENT al final. Por lo tanto, necesitará que su analizador "ignore" los pares INDENT/UNINDENT extra redundantes. Colapctarlos en el Lexer es difícil si quieres capturar la sangría reducida final correctamente, pero si no te importa, puedes usar una pila de niveles de sangrado en lugar de un solo contador. –

Cuestiones relacionadas