Domanda

Sto riscontrando un problema nel comprendere lo spostamento / riduzione del conflitto per una grammatica che so non avere ambiguità. Il caso è uno del tipo if else, ma non è il problema "dangling else" poiché ho clausole END obbligatorie che delimitano i blocchi di codice.

Ecco la grammatica di gppg (è un compilatore di compilatori simili a Bison ... e che non era un'eco):

%output=program.cs

%start program

%token FOR
%token END
%token THINGS
%token WHILE
%token SET
%token IF
%token ELSEIF
%token ELSE
%%

program : statements
        ;

statements : /*empty */
           | statements stmt
           ;

stmt : flow
     | THINGS
     ;

flow : '#' IF '(' ')' statements else
     ;

else : '#' END
     | '#' ELSE statements '#' END
     | elseifs
     ;

elseifs : elseifs '#' ELSEIF statements else
        | '#' ELSEIF statements else
        ;

Ecco l'output del conflitto:

// Parser Conflict Information for grammar file "program.y"

Shift/Reduce conflict on symbol "'#'", parser will shift
 Reduce 10: else -> elseifs
 Shift "'#'":   State-22 -> State-23
  Items for From-state State 22
    10 else: elseifs .
    -lookahead: '#', THINGS, EOF
    11 elseifs: elseifs . '#' ELSEIF statements else 
  Items for Next-state State 23
    11 elseifs: elseifs '#' . ELSEIF statements else 

// End conflict information for parser

Ho già cambiato tutto e so come risolverlo, ma quella soluzione implica rinunciare alla ricorsione a sinistra su "elseif" per una ricorsione a destra.

Ho esaminato tutta la scarsa documentazione che ho trovato su Internet riguardo a questo problema (inserisco alcuni link alla fine) e non ho ancora trovato una soluzione elegante. Conosco ANTLR e non voglio considerarlo in questo momento. Limitare la soluzione ai parser Yacc / Bison.

Gradirei soluzioni eleganti, sono riuscito a farlo eliminando le regole / * empty * / e la duplicazione di tutto ciò che necessitava di un elenco vuoto ma nella grammatica più ampia su cui sto lavorando finisce solo come "sindrome grammaticale sparghetti".

Ecco alcuni link:

http://nitsan.org/~maratb/cs164/bison.html

http://compilers.iecc.com/comparch/article/98- 01-079

GPPG, il parser che sto usando

Manuale del bisonte

È stato utile?

Soluzione

La tua regola ELSEIF rivista non ha marcatori per una condizione - dovrebbe essere nominalmente '(' e ')' aggiunti.

Più seriamente, ora hai una regola per

elsebody : else
         | elseifs else
         ;

e

elseifs : /* Nothing */
        | elseifs ...something... 
        ;

Il 'nulla' non è necessario; è implicitamente curato dall '"altro" senza gli "altri".

Sarei molto propenso a usare le regole 'opt_elseifs', 'opt_else' e 'end':

flow : '#' IF '(' ')' statements opt_elseifs opt_else end
     ;

opt_elseifs : /* Nothing */
            | opt_elseifs '#' ELSIF '(' ')' statements 
            ;

opt_else : /* Nothing */
         | '#' ELSE statements
         ;

end : '#' END
    ;

Non ho eseguito questo tramite un generatore di parser, ma lo trovo relativamente facile da capire.

Altri suggerimenti

Penso che il problema sia nella clausola elseifs.

elseifs : elseifs '#' ELSEIF statements else
        | '#' ELSEIF statements else
        ;

Penso che la prima versione non sia richiesta, poiché la clausola else si riferisce comunque a elseifs:

else : '#' END
     | '#' ELSE statements '#' END
     | elseifs
     ;

Cosa succede se cambi altro ?:

elseifs : '#' ELSEIF statements else
        ;

La risposta di Jonathan sopra sembra che sarebbe la migliore, ma poiché non funziona per te ho alcuni suggerimenti che potresti provare che ti aiuteranno nel debug dell'errore.

In primo luogo hai preso in considerazione l'idea di rendere il simbolo hash / sharp parte dei token stessi (ovvero #END, #IF, ecc.)? In modo che vengano eliminati dal lexer, il che significa che non devono essere inclusi nel parser.

In secondo luogo, ti esorto a riscrivere le regole senza duplicare alcun flusso di token. (Parte del principio Don't Repeat Yourself.) Quindi la regola " '#' Dichiarazioni ELSEIF altro " dovrebbe esistere solo in un posto in quel file (non due come sopra).

Infine, suggerisco di esaminare la precedenza e l'associatività dei token IF / ELSEIF / ELSE. So che dovresti essere in grado di scrivere un parser che non lo richiede, ma potrebbe essere la cosa di cui hai bisogno in questo caso.

Sto ancora cambiando le cose e la mia domanda originale aveva degli errori dato che la sequenza elseif aveva sempre un altro alla fine che era sbagliato. Ecco un'altra interpretazione della domanda, questa volta ottengo due turni di spostamento / riduzione:

flow : '#' IF '(' ')' statements elsebody 
     ;

elsebody : else 
         | elseifs else
         ;

else : '#' ELSE statements '#' END
     | '#' END
     ;

elseifs : /* empty */
        | elseifs '#' ELSEIF statements
        ;

I conflitti ora sono:

// Parser Conflict Information for grammar file "program.y"

Shift/Reduce conflict on symbol "'#'", parser will shift
 Reduce 12: elseifs -> /* empty */
 Shift "'#'":   State-10 -> State-13
  Items for From-state State 10
    7 flow: '#' IF '(' ')' statements . elsebody 
    4 statements: statements . stmt 
  Items for Next-state State 13
    10 else: '#' . ELSE statements '#' END 
    11 else: '#' . END 
    7 flow: '#' . IF '(' ')' statements elsebody 

Shift/Reduce conflict on symbol "'#'", parser will shift
 Reduce 13: elseifs -> elseifs, '#', ELSEIF, statements
 Shift "'#'":   State-24 -> State-6
  Items for From-state State 24
    13 elseifs: elseifs '#' ELSEIF statements .
    -lookahead: '#'
    4 statements: statements . stmt 
  Items for Next-state State 6
    7 flow: '#' . IF '(' ')' statements elsebody 

// End conflict information for parser

Le regole vuote non fanno altro che aggravare il gppg che ho paura. Ma sembrano così naturali da usare che continuo a provarli.

So già che la giusta ricorsione risolve il problema come ha detto 1800 INFORMAZIONI . Ma sto cercando una soluzione con ricorsione a sinistra nella clausola elseifs .

elsebody : elseifs else
         | elseifs
         ;

elseifs : /* empty */
        | elseifs '#' ELSEIF statements
        ;

else : '#' ELSE statements '#' END
     ;

Penso che questo dovrebbe lasciare la recessione e terminare sempre.

OK - ecco una grammatica (non minima) per i blocchi if. L'ho estratto da un codice che ho (chiamato adhoc, basato su hoc da Kernighan & amp; Plauger "L'ambiente di programmazione UNIX"). Questo schema grammaticale viene compilato con Yacc senza conflitti.

%token  NUMBER IF ELSE
%token  ELIF END
%token  THEN
%start program

%%

program
    :   stmtlist
    ;

stmtlist
    :   /* Nothing */
    |   stmtlist stmt
    ;

stmt
    :   ifstmt
    ;

ifstmt
    :   ifcond endif
    |   ifcond else begin
    |   ifcond eliflist begin
    ;

ifcond
    :   ifstart cond then stmtlist
    ;

ifstart
    :   IF
    ;

cond
    :   '(' expr ')'
    ;

then
    :   /* Nothing */
    |   THEN
    ;

endif
    :   END IF begin
    ;

else
    :   ELSE stmtlist END IF
    ;

eliflist
    :   elifblock
    |   elifcond eliflist begin         /* RIGHT RECURSION */
    ;

elifblock
    :   elifcond else begin
    |   elifcond endif
    ;

elifcond
    :   elif cond then stmtlist end
    ;

elif
    :   ELIF
    ;

begin
    :   /* Nothing */
    ;

end
    :   /* Nothing */
    ;

expr
    :   NUMBER
    ;

%%

Ho usato 'NUMBER' come elemento fittizio, invece di THINGS, e ho usato ELIF invece di ELSEIF. Include THEN, ma è facoltativo. Le operazioni 'inizio' e 'fine' sono state usate per afferrare il contatore del programma nel programma generato - e quindi dovrebbero essere rimovibili da questo senza influenzarlo.

C'era una ragione per cui pensavo di dover usare la ricorsione giusta invece della normale ricorsione sinistra - ma penso che avesse a che fare con la strategia di generazione del codice che stavo usando, piuttosto che con qualsiasi altra cosa. Il punto interrogativo nel commento era nell'originale; Ricordo di non esserne soddisfatto. Il programma nel suo insieme funziona - è un progetto che è stato in secondo piano negli ultimi dieci anni circa (hmmm ... ho fatto un po 'di lavoro alla fine del 2004 e all'inizio del 2005; prima di ciò, era il 1992 e 1993).

Non ho passato il tempo a capire perché questo compila senza conflitti e ciò che ho descritto in precedenza non lo fa. Spero che sia d'aiuto.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top