Domanda

Sto cercando di usare flex e bison per creare un filtro, perché voglio ottenere determinati elementi grammaticali da un linguaggio complesso. Il mio piano è usare flex + bison per riconoscere la grammatica e scaricare la posizione degli elementi di interesse. (Quindi utilizzare uno script per afferrare il testo in base alle posizioni scaricate.)

Ho scoperto che flex può supportare una funzione bisonte chiamata bison-locations, ma come funziona esattamente. Ho provato l'esempio nel documento flessibile, sembra che lo yylloc non sia impostato automaticamente da flex, ottengo sempre (1,0) - (1,0) . Il flex può calcolare automaticamente la posizione di ogni token? In caso contrario, quale funzione di interfaccia è definita per me da implementare? C'è qualche esempio?

Qualunque soluzione migliore per quanto riguarda gli strumenti?

I migliori saluti, Kevin

Modifica

Ora l'interfaccia per yylex diventa:

int yylex(YYSTYPE * yylval_param,YYLTYPE * yylloc_param );

bison manual non specifica come implementare lexer per impostare correttamente yylloc_param. Per me è difficile tracciare manualmente il numero di colonna di ciascun token.

È stato utile?

Soluzione

Dai un'occhiata alla sezione 3.6 di il manuale Bison - che sembra coprire le posizioni in alcuni dettagli. Combinato con quello che hai trovato nel manuale Flex, potrebbe essere sufficiente.

Altri suggerimenti

Probabilmente la dichiarazione yylex è cambiata perché hai usato un rientrante o un parser puro. Sembra che molti documenti sul Web suggeriscano che è necessario se si desidera che le ubicazioni dei bisonti funzionino, ma non è necessario.

Avevo bisogno anche dei numeri di riga e ho trovato confusa la documentazione di Bison al riguardo. La soluzione semplice (usando il var yylloc globale): Nel tuo file Bison basta aggiungere la direttiva% locations:

%{
...
%}
%locations
...
%%
...

nel tuo lexer:

%{
...
#include "yourprser.tab.h"  /* This is where it gets the definition for yylloc from */
#define YY_USER_ACTION yylloc.first_line = yylloc.last_line = yylineno;
%}
%option yylineno
...
%%
...

La macro YY_USER_ACTION è " chiamata " prima di ciascuna delle tue azioni token e aggiorna yylloc. Ora puoi usare le regole @N / @ $ in questo modo:

statement : error ';'   { fprintf(stderr, "Line %d: Bad statement.\n", @1.first_line); }

oppure usa yylloc global var:

void yyerror(char *s)
{
  fprintf(stderr, "ERROR line %d: %s\n", yylloc.first_line, s);
}

Mi piace la risposta di Shlomi.

Inoltre stavo cercando anche l'aggiornamento della posizione della colonna. Trovato http://oreilly.com/linux/excerpts/9780596155971/error- reporting-recovery.html che aveva più senso dopo aver letto la risposta di Shlomi.

Sfortunatamente c'è un refuso su quella pagina per yylloc. L'ho semplificato un po 'di seguito.

Nel tuo parser aggiungi:

%locations

nel tuo lexer:

%{

#include "parser.tab.h"

int yycolumn = 1;

#define YY_USER_ACTION yylloc.first_line = yylloc.last_line = yylineno; \
    yylloc.first_column = yycolumn; yylloc.last_column = yycolumn + yyleng - 1; \
    yycolumn += yyleng; \
    yylval.str = strdup(yytext);

%}

%option yylineno

Potrebbe esserci qualcosa in corso con la posizione della colonna che non tiene traccia rigorosamente delle colonne ma piuttosto continua ad aumentare. Questa è solo la mia ignoranza e mi scuso se confonde qualcuno. Attualmente sto usando la colonna per mantenere un conteggio dei caratteri del file che nel mio caso è più vantaggioso della posizione della colonna.

Spero che sia d'aiuto.

bison flex aggiornano automaticamente yylloc , ma in realtà non è difficile farlo da soli & # 8212; se conosci il trucco.

Il trucco per implementare il supporto yylloc è che, sebbene yyparse () dichiari yylloc , non lo cambia mai. Ciò significa che se modifichi yylloc in una chiamata al lexer, troverai gli stessi valori nella chiamata successiva. Pertanto, yylloc conterrà la posizione dell'ultimo token. Poiché la fine dell'ultimo token è la stessa dell'inizio del token corrente, puoi utilizzare il vecchio valore yylloc per aiutarti a determinare il nuovo valore.

In altre parole, yylex () non dovrebbe calcolare yylloc ; dovrebbe aggiornare yylloc .

Per aggiornare yylloc , dobbiamo prima copiare i valori last_ in first_ , quindi aggiornare last_ valori per riflettere la lunghezza del token appena abbinato. (Questo non è il strlen () del token; è la lunghezza di righe e colonne.) Possiamo farlo nella macro YY_USER_ACTION , che si chiama poco prima viene eseguita qualsiasi azione lexer; ciò assicura che se una regola corrisponde ma non restituisce un valore (ad esempio, una regola che salta spazi bianchi o commenti), la posizione di quel non token viene ignorata, anziché essere inclusa all'inizio del token effettivo, oppure perso in un modo che rende impreciso il tracciamento della posizione.

Ecco una versione pensata per un parser rientrante; potresti modificarlo per un parser non rientrante scambiando gli operatori - > con . :

#define YY_USER_ACTION \
    yylloc->first_line = yylloc->last_line; \
    yylloc->first_column = yylloc->last_column; \
    for(int i = 0; yytext[i] != '\0'; i++) { \
        if(yytext[i] == '\n') { \
            yylloc->last_line++; \
            yylloc->last_column = 0; \
        } \
        else { \
            yylloc->last_column++; \
        } \
    }

Se preferisci, puoi invece inserire quel codice in una funzione e far chiamare la macro dalla funzione, ma le due tecniche sono equivalenti.

La risposta di Shomi è la soluzione più semplice se ti interessa solo mantenere il numero di riga. Tuttavia, se desideri anche i numeri di colonna, devi tenerne traccia.

Un modo per farlo è quello di aggiungere yycolumn = 1 ovunque compaia una nuova riga (come suggerito nella risposta di David Elson) ma se non vuoi tenere traccia di tutti i luoghi in cui una nuova riga potrebbe mostrare (spazi bianchi, commenti, ecc ...) un'alternativa sta ispezionando il buffer yytext all'inizio di ogni azione:

static void update_loc(){
  static int curr_line = 1;
  static int curr_col  = 1;

  yylloc.first_line   = curr_line;
  yylloc.first_column = curr_col;

  {char * s; for(s = yytext; *s != '\0'; s++){
    if(*s == '\n'){
      curr_line++;
      curr_col = 1;
    }else{
      curr_col++;
    }
  }}

  yylloc.last_line   = curr_line;
  yylloc.last_column = curr_col-1;
}

#define YY_USER_ACTION update_loc();

Infine, una cosa da notare è che una volta che inizi a tenere traccia dei numeri di colonna a mano potresti anche tenere traccia dei numeri di riga nello stesso posto e non preoccuparti di usare il yylineno di Flex opzione.

Quindi, ho ottenuto questo per "lavorare", ma con un paio di passaggi extra (potrei averli trascurati qui ... mi scuso in quel caso):

  1. In parser.y , dovevo dire:

    #define YYLEX_PARAM &yylval, &yylloc
    

    anche con % locations e bison --locations , per farlo passare i dati.

  2. In lexer.l ho dovuto usare - > invece di . per < code> yylloc

  3. Anche in lexer.l , ho resettato la colonna nell'azione:

    [\n] { yycolumn = 1; }
    

Ovviamente un po 'più complesso, per \ r ecc., ma almeno l'ho fatto funzionare.

Penso di essere riuscito a farlo funzionare (il merito va allo scrittore del manuale bisonte ltcalc analizzatore lessicale ). Per impostazione predefinita, bisonte crea yylloc che contiene

{ first_line, first_column , last_line , last_column }

Dobbiamo solo aggiornare quei valori nel nostro analizzatore lessicale. Es .:

[ \t]     { ++yylloc.last_column; }
[\n]      { yyloc.last_column = 0; return EOL; }
[a-zA-Z]+ { 
            yylloc.last_column += strlen(yytext);
            return IDENTIFIER;
          }

Ora in bisonte, per recuperare quei campi:

statement : IDENTIFIER '=' expression 
            { printf("%d - %d\n", @1.last_line, @1.last_column); }

Per impostazione predefinita, questi campi sono inizializzati su uno, dovremmo inizializzare i campi della colonna su zero, altrimenti segnaleranno la colonna sbagliata.

Un'aggiunta alla risposta di Shlomi:

Se stai usando% define api.pure in bisonte per creare un parser rientrante, devi anche specificare% option bison-locations in flex. Questo perché in un parser rientrante yylloc non è una variabile globale e deve essere passata al lexer.

Quindi, nel parser:

%define api.pure
%locations

nel lexer:

#include "yourprser.tab.h"
#define YY_USER_ACTION yylloc.first_line = yylloc.last_line = yylineno;
%option bison-locations
%option yylineno
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top