11
votes

Comment utiliser l'indentation comme des délimiteurs de blocs avec Bison et Flex

Je wounder comment implémenter indentation comme délimiteurs de bloc de bison + flex. Tout comme en python. J'écris mon propre langage de programmation (surtout pour le plaisir, mais je l'intention de l'utiliser avec un moteur de jeu), je vais essayer de trouver quelque chose de spécial qui minimise BOILERPLATE et maximise la vitesse dev.

Je l'ai déjà écrit un compilateur (en fait un `langToy ' em> au traducteur Nasm) en C, mais a échoué. Par une raison quelconque, il était seulement capable de gérer une chaîne dans l'ensemble du fichier source (bien, j'avais été éveillé pendant plus de 48 heures - alors ... Tu sais, effondrement du cerveau) p>

Je ne. sais pas si accolades et / ou commencer -> fin sont plus faciles à mettre en œuvre (je n'ai pas problème à le faire) ou si elle est juste mon cerveau qui se bloque p>

Merci d'avance p.! >


Mise à jour: strong> d'accord, je n'ai aucune idée sur la façon de le faire avec flex. J'ai des problèmes avec le retour de plusieurs DEDENTs à l'analyseur. Flex / Bison sont relativement nouveau pour moi p>


Mise à jour 2:. Strong> Ceci est le fichier flex je suis venu avec jusqu'à présent; il n'a pas tout à fait obtenir: p>

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


0 commentaires

4 Réponses :


1
votes

Les supports bouclés (et tels) ne sont que plus simples si vous utilisez un tokenizer qui élimine tout blanc (en utilisant est juste pour séparer les jetons). Voir Cette page (la section "Comment le compilateur analyse-t-il l'indentation?" ) Pour certaines idées sur la tokénisation de Python.

Si vous ne faites pas de toboggan avant d'analyser, il peut y avoir du travail supplémentaire à faire, cela dépend de la manière dont vous construisez l'analyseur.


1 commentaires

Merci pour le lien utile, je vais lui donner une fissure et voir si je réussis ce temps.



0
votes

Vous avez besoin d'une règle qui a l'air analogue à cela (supposant que vous utilisiez des onglets pour vos retrait):

\ T: {Tabdent de retour; }

Franchement, j'ai toujours les bretelles (ou commencez / fin) pour être plus faciles à écrire et à lire, à la fois comme un homme humain et un écrivain Lxer / Parser.


1 commentaires

Eh bien, on dirait que c'est au moins plus facile de rédiger un Lexer avec des symboles spéciaux-commencement des blocs et des blocs de blocage. Il est pas plus facile à écrire {et} sur mon clavier localisé; D



15
votes

Ce que vous devez faire est de compter sur la flexion comptant la quantité de blancheurs au début de chaque ligne et d'insérer un nombre approprié de jetons indentés / indéfinissants pour l'analyseur à utiliser pour collecter des choses. Une question est de savoir ce que vous voulez faire à propos des onglets VS espaces - Voulez-vous simplement les avoir équivalents avec des arrêts à tabulation fixe ou souhaitez-vous avoir besoin d'indenter pour être cohérent (donc si une ligne commence par un onglet et la suivante Avec un espace, vous signalez une erreur, qui est probablement un peu plus difficile).

En supposant que vous souhaitiez que vous voulez des tablatures à 8 colonnes fixes, vous pouvez utiliser quelque chose comme P>

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

On dirait que vous l'avez eu, mais je veux que les Tabstops comptent comme 2 espaces. Donc, je suppose que la ligne doit être actuelle_line_indent = (actuel_line_indent + 2) & ~ 1;


Oui - Lorsque vous voyez un onglet, vous devez heurter Current_Line_indent au Tabstop suivant.



6
votes

La réponse de Chris va un long chemin vers une solution utilisable, merci un tas pour cela! Malheureusement, il manque quelques aspects plus importants dont j'avais besoin:

  • multiples idées (unindants) à la fois. Considérez que le code suivant devrait émettre deux forte> dépassement après l'appel à Baz code>: p> XXX PRE> LI>

  • Émettez-vous lorsque la fin du fichier est atteinte et est toujours à un niveau d'indentation. p> li>

  • niveaux d'indentation de taille différente. Le code actuel de Chris fonctionne uniquement correctement pour les tirettes 1 espace. Li> ul>

    basé sur le code de Chris, j'ai proposé une solution qui fonctionne dans tous les cas où j'ai rencontré jusqu'à présent. J'ai créé un projet de modèle pour analyser le texte basé sur l'indentation à l'aide de Flex (et Bison) sur Github: https://github.com/lucasb-eyer/flex-bison-indentation . Il s'agit d'un projet entièrement actif (basé sur la cmake) qui suit également la position de la ligne et la plage de colonnes du jeton actuel. P>

    Juste au cas où le lien devrait casser une raison quelconque, voici la viande de la Lexer: P>

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


1 commentaires

Mon code produit un indent / un indentent pour chaque espace d'indentation . Donc, pour votre exemple avec des tirets de 2 espaces, il produira deux jetons de tige après la première ligne, une autre 2 après le second, et 4 unindent à la fin. Donc, vous aurez besoin de votre analyseur "ignorer" un indentement redondant supplémentaire / des paires unindentes. Les effondrants dans le Lexer est difficile si vous souhaitez attraper un retrait réduit correctement, mais si vous ne vous souciez pas de cela, vous pouvez utiliser une pile de niveaux de retrait plutôt qu'un seul compteur.