108 votes

Quelle est la différence entre les analyseurs syntaxiques LR, SLR et LALR ?

Quelle est la différence réelle entre les analyseurs LR, SLR et LALR ? Je sais que SLR et LALR sont des types de parsers LR, mais quelle est la différence réelle en ce qui concerne leurs tables de parsing ?

Et comment montrer si une grammaire est LR, SLR, ou LALR ? Pour une grammaire LL, il suffit de montrer que toute cellule de la table d'analyse ne doit pas contenir plusieurs règles de production. Existe-t-il des règles similaires pour LALR, SLR, et LR ?

Par exemple, comment pouvons-nous montrer que la grammaire

S --> Aa | bAc | dc | bda
A --> d

est LALR(1) mais pas SLR(1) ?


EDIT (ybungalobill) : Je n'ai pas obtenu de réponse satisfaisante pour savoir quelle est la différence entre LALR et LR. Les tableaux de LALR sont donc plus petits en taille mais il ne peut reconnaître qu'un sous-ensemble de grammaires LR. Quelqu'un peut-il nous en dire plus sur la différence entre LALR et LR ? LALR(1) et LR(1) seront suffisants pour une réponse. Elles utilisent toutes deux un jeton d'anticipation et un jeton de réponse. les deux sont axés sur les tables ! En quoi sont-ils différents ?

0 votes

Eh bien, même si je suis à la recherche d'une réponse appropriée à ce sujet, LALR(1) est juste une légère modification de LR(1), où la taille de la table est réduite de sorte que nous pouvons minimiser l'utilisation de la mémoire ...

4voto

tgoneil Points 382

La différence fondamentale entre les tables d'analyse générées avec SLR et LR, est que les actions de réduction sont basées sur l'ensemble Follows pour les tables SLR. Cela peut s'avérer trop restrictif, provoquant finalement un conflit shift-reduce.

Un analyseur LR, par contre, base les décisions de réduction uniquement sur l'ensemble des terminaux qui peuvent effectivement suivre le non-terminal à réduire. Cet ensemble de terminaux est souvent un sous-ensemble adéquat de l'ensemble Follows d'un tel non-terminal, et a donc moins de chance d'entrer en conflit avec les actions de changement.

Les analyseurs LR sont plus puissants pour cette raison. Cependant, les tables d'analyse LR peuvent être extrêmement volumineuses.

Un analyseur syntaxique LALR part de l'idée de construire une table d'analyse LR, mais combine les états générés d'une manière qui permet de réduire considérablement la taille de la table. L'inconvénient est qu'une petite chance de conflits serait introduite pour certaines grammaires qu'une table LR aurait autrement évitée.

Les analyseurs LALR sont légèrement moins puissants que les analyseurs LR, mais toujours plus puissants que les analyseurs SLR. YACC et d'autres générateurs d'analyseurs ont tendance à utiliser LALR pour cette raison.

P.S. Par souci de concision, SLR, LALR et LR ci-dessus signifient en réalité SLR(1), LALR(1) et LR(1), ce qui implique une anticipation d'un jeton.

3voto

En plus des réponses ci-dessus, ce diagramme illustre les relations entre les différents analyseurs syntaxiques :

enter image description here

0voto

Sandipan Dey Points 13663

En plus des réponses ci-dessus, la différence entre les analyseurs individuels de la classe des analyseurs LR ascendants est de savoir s'ils entraînent des conflits shift/reduce ou reduce/reduce lors de la génération des tables d'analyse. Moins il y aura de conflits, plus la grammaire sera puissante (LR(0) < SLR(1) < LALR(1) < CLR(1)).

Par exemple, considérons la grammaire d'expression suivante :

E → E + T

E → T

T → F

T → T * F

F → ( E )

F → id

Ce n'est pas LR(0) mais SLR(1). En utilisant le code suivant, nous pouvons construire l'automate LR0 et construire la table d'analyse syntaxique (nous devons augmenter la grammaire, calculer le DFA avec fermeture, calculer les ensembles d'action et de goto) :

from copy import deepcopy
import pandas as pd

def update_items(I, C):
    if len(I) == 0:
         return C
    for nt in C:
         Int = I.get(nt, [])
         for r in C.get(nt, []):
              if not r in Int:
                  Int.append(r)
          I[nt] = Int
     return I

def compute_action_goto(I, I0, sym, NTs): 
    #I0 = deepcopy(I0)
    I1 = {}
    for NT in I:
        C = {}
        for r in I[NT]:
            r = r.copy()
            ix = r.index('.')
            #if ix == len(r)-1: # reduce step
            if ix >= len(r)-1 or r[ix+1] != sym:
                continue
            r[ix:ix+2] = r[ix:ix+2][::-1]    # read the next symbol sym
            C = compute_closure(r, I0, NTs)
            cnt = C.get(NT, [])
            if not r in cnt:
                cnt.append(r)
            C[NT] = cnt
        I1 = update_items(I1, C)
    return I1

def construct_LR0_automaton(G, NTs, Ts):
    I0 = get_start_state(G, NTs, Ts)
    I = deepcopy(I0)
    queue = [0]
    states2items = {0: I}
    items2states = {str(to_str(I)):0}
    parse_table = {}
    cur = 0
    while len(queue) > 0:
        id = queue.pop(0)
        I = states[id]
        # compute goto set for non-terminals
        for NT in NTs:
            I1 = compute_action_goto(I, I0, NT, NTs) 
            if len(I1) > 0:
                state = str(to_str(I1))
                if not state in statess:
                    cur += 1
                    queue.append(cur)
                    states2items[cur] = I1
                    items2states[state] = cur
                    parse_table[id, NT] = cur
                else:
                    parse_table[id, NT] = items2states[state]
        # compute actions for terminals similarly
        # ... ... ...

    return states2items, items2states, parse_table

states, statess, parse_table = construct_LR0_automaton(G, NTs, Ts)

où la grammaire G, les symboles non-terminaux et terminaux sont définis comme suit

G = {}
NTs = ['E', 'T', 'F']
Ts = {'+', '*', '(', ')', 'id'}
G['E'] = [['E', '+', 'T'], ['T']]
G['T'] = [['T', '*', 'F'], ['F']]
G['F'] = [['(', 'E', ')'], ['id']]

Voici quelques autres fonctions utiles que j'ai implémentées avec les fonctions ci-dessus pour la génération de la table d'analyse LR(0) :

def augment(G, S): # start symbol S
    G[S + '1'] = [[S, '$']]
    NTs.append(S + '1')
    return G, NTs

def compute_closure(r, G, NTs):
    S = {}
    queue = [r]
    seen = []
    while len(queue) > 0:
        r = queue.pop(0)
        seen.append(r)
        ix = r.index('.') + 1
        if ix < len(r) and r[ix] in NTs:
            S[r[ix]] = G[r[ix]]
            for rr in G[r[ix]]:
                if not rr in seen:
                    queue.append(rr)
    return S

La figure suivante (agrandir pour voir) montre le DFA LR0 construit pour la grammaire en utilisant le code ci-dessus :

enter image description here

Le tableau suivant montre le tableau d'analyse syntaxique LR(0) généré sous forme de dataframe pandas, remarquez qu'il y a quelques conflits shift/reduce, indiquant que la grammaire n'est pas LR(0).

enter image description here

L'analyseur SLR(1) évite les conflits shift / reduce ci-dessus en réduisant seulement si le prochain token d'entrée est un membre du Follow Set du non-terminal en cours de réduction. Le tableau d'analyse suivant est généré par SLR :

enter image description here

L'animation suivante montre comment une expression d'entrée est analysée par la grammaire SLR(1) ci-dessus :

enter image description here

La grammaire de la question n'est pas non plus LR(0) :

#S --> Aa | bAc | dc | bda
#A --> d    
G = {}
NTs = ['S', 'A']
Ts = {'a', 'b', 'c', 'd'}
G['S'] = [['A', 'a'], ['b', 'A', 'c'], ['d', 'c'], ['b', 'd', 'a']]
G['A'] = [['d']]

comme on peut le voir dans le prochain DFA LR0 et la table d'analyse :

enter image description here

il y a un changement / réduction du conflit à nouveau :

enter image description here

Mais, la grammaire suivante qui accepte les chaînes de la forme a^ncb^n, n >= 1 est LR(0) :

A → a A b

A → c

S → A

# S --> A 
# A --> a A b | c
G = {}
NTs = ['S', 'A']
Ts = {'a', 'b', 'c'}
G['S'] = [['A']]
G['A'] = [['a', 'A', 'b'], ['c']]

enter image description here

Comme on peut le voir sur la figure suivante, il n'y a pas de conflit dans la table d'analyse générée.

enter image description here

Voici comment la chaîne d'entrée a^2cb^2 peut être analysé à l'aide de la table d'analyse LR(0) ci-dessus, en utilisant le code suivant :

def parse(input, parse_table, rules):
    input = 'aaacbbb$'
    stack = [0]
    df = pd.DataFrame(columns=['stack', 'input', 'action'])
    i, accepted = 0, False
    while i < len(input):
        state = stack[-1]
        char = input[i]
        action = parse_table.loc[parse_table.states == state, char].values[0]
        if action[0] == 's':   # shift
            stack.append(char)
            stack.append(int(action[-1]))
            i += 1
        elif action[0] == 'r': # reduce
            r = rules[int(action[-1])]
            l, r = r['l'], r['r']
            char = ''
            for j in range(2*len(r)):
                s = stack.pop()
                if type(s) != int:
                    char = s + char
            if char == r:
                goto = parse_table.loc[parse_table.states == stack[-1], l].values[0]
                stack.append(l)
                stack.append(int(goto[-1]))
        elif action == 'acc':  # accept
            accepted = True
        df2 = {'stack': ''.join(map(str, stack)), 'input': input[i:], 'action': action}
        df = df.append(df2, ignore_index = True)
        if accepted:
            break

    return df

parse(input, parse_table, rules)

L'animation suivante montre comment la chaîne d'entrée a^2cb^2 est analysé avec l'analyseur LR(0) en utilisant le code ci-dessus :

enter image description here

-3voto

Anil Kumar Points 439

Une réponse simple est que toutes les grammaires LR(1) sont des grammaires LALR(1). Par rapport à LALR(1), LR(1) a plus d'états dans la machine à états finis associée (plus du double des états). Et c'est la raison principale pour laquelle les grammaires LALR(1) nécessitent plus de code pour détecter les erreurs de syntaxe que les grammaires LR(1). Une autre chose importante à savoir concernant ces deux grammaires est que dans les grammaires LR(1), nous pouvons avoir moins de conflits réduction/réduction. Mais dans la grammaire LALR(1), il y a plus de possibilités de conflits de réduction/réduction.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X