Soluzioni Proposte






Esercizio1.1

S::= ( T F
T::= L
T::= [ L , S ]
F::= , T F
F::= )
L::= A
L::= B
L::= C
L::= D
L::= E
Esercizio2.1

    La grammatica sopra di esercizio1.1 genera il linguaggio richiesto ed e' analizzabile LR(1).
 

Esercizio3.1

        S::= L S
        S::= e
        L::= &
        L::= A
        L::= B
        L::= C

La grammatica data e' LL(1), adatta per analisi discendente.
Una seconda grammatica, pero', LR(1) e' la seguente:

        S::= & L S
        S::= T L S
        S::= L
        S::= e
        L::= T
        L::= &
        T::= A
        T::= B
        T::= C

Questa grammatica offre alcuni vantaggi nell'analisi richiesta dal resto dell'esercizio, ovvero la possibilita' di vedere sempre i due simboli adiacenti

Esercizio5

Utiliziamo la seguente grammatica che verifichiamo essere LL(1), quindi adatta ad un'analisi discendente:

        Program::= Block
     B::= begin Declaration; Command Rest_of_program end
        R::= ; C R
        R::= e
        C::= Block
        C::= Statement
        D::= var ide List_of_identifiers : Type
      L ::= , ide L
        L::= e
        T ::= real
        T ::= int

Per lo schema utiliziamo i seguenti attributi:
per il calcolo della occupazione di memoria:
    M per i non terminali B, D, R, C, T. E' un sintetizzato ed ' usato con significati diversi a seconda del simbolo. Esso contiene il numero di word
                    se B: richieste dalla dichiarazione di B e il massimo delle word richieste dai blocchi occorrenti nel corpo di B
                    se D: richieste dalle variabili in D
                    se R, C: richieste dagli statements o blocchi attraversati
                    se T: richieste per l'allocazione di un tipo int o real
       n per il non terminale L. E' un sintetizzato, contiene il numero di variabili attraversate.
per il calcolo del livello di scoping o profondita':
       in per i simboli B, C, R. E' un ereditato, contiene il livello in cui occorre la struttura derivabile dal simbolo
    H per i simboli B, C: contiene la profondita' calcolata per la struttura attraversata.
 
 

P::= {B.in:=0}
      B
B::= begin 
      D; {C.in:= B.in +1}
      C  {R.in:= B.in +1}
      R end {B.M:= D.M + max(C.M, R.M);   B.H:= B.in +1}
R1::= ;  {C.in:= R1.in}
      C {R2.in:= R1.in}
      R2 {R1.M:= max(C.M, R2.M)}
R::= e {R.M:= 0}
C::= {B.in:= C.in}
      B {C.M:= B.M;  C.H:= C.in}
C::= 
      S {C.M:= 0;   C.H:= C.in}
D::= var ide 
      L
      T {D.M:= (1+L.n) * T.M}
L1 ::= , ide 
      L2{L1.n:= 1 + L2.n}
L::= e {L.n:= 0}
T ::= real {T.M:= 4}
T ::= int {T.M:= 2}

 

Esercizio 6.1
 

Utiliziamo la seguente grammatica che verifichiamo essere LL(1), quindi adatta ad un'analisi discendente:

       Program::= Qprocedure Rest_of_procedure
    R::= ; Q R
    R::= e
    Q::= procedure ide Formals; Body
    F::= ( List_of_ide)
    L::= ide Again_ide
    L::= e
    A::= , ide A
    A::= ide
    B::= Declaration Command ; Sequence_of_commands
    B::= Command ; Sequence_of_commands
    D::= var ide A ;
    C::= read ( ide A )
    C::= write ( ide A )
    C::= ide := ide Expression
    C::= while ide = ide do C S od
    C::= call ide F
    E::= + ide
    E::= e
Per lo schema utiliziamo i seguenti attributi:
    r per i non terminali P, Q, R, C, S. E' un booleano sintetizzato contiene true se e solo se la struttura attraversata contiene un comando while-do non terminante
       h per il non terminale Q. E' un sintetizzato, contiene una coppia (ide, header) dove ide e' il nome della procedura ed header e' una lista dove in corrispondenza di ogni parametro vi e' un booleano che e' true se e solo se il corpo della procedura ha statements che possono modificare tale parametro
       a per i non terminali B, C, S. E' un sintetizzato contenente le variabili modificate nella struttura attraversata dal simbolo
       list per i non terminali L, A, D, F. Sintetizzato contenente la lista di identificatori attraversati dall'analisi del simbolo
       inp per il non terminali Q, R, C, B. Sintetizzato contenente la lista di identificatori attraversati dall'analisi del simbolo

Per la definizione delle azioni, utilizzeremo liste con la seguente notazione per gli operatori:
    [x1,...,xn] lista di n, eventualmente n=0, valori.
        | cons di un valore ad una lista
        + append di liste
        insect predicato per elementi comuni a due liste
in aggiunta a queste operazioni standard, introduciamo le funzioni definite induttivamente come di seguito:
filter:
        filter([], L2) = []
        filter(v | L1, L2) = ([v] insect L2) | (filter (L1,L2)
match:
        match([],[]) = []
        match(v | L, true | B) = v | match(L,B)
        match(v | L, false | B) = match(L,B)
select
        select(<i,h> | L, i) = h
        select(<i,h> | L, j) = select (h, j) if i<>j
 

 
P::= {Q.inp:= []}
      Q {R.inp:= [Q.h]}
      R {P.r:= Q.r or R.r}
R1::= ;  {Q.inp:=  R1.inp}
         Q {R2.inp:= [Q.h] + R1.inp}
         R2 {R1.r:= Q.r or R2.r}
R::= e {R.r:= false}
Q::= procedure ide
        F ; {B.inp:= Q.inp}
        B end {Q.h:=<ide.val, filter(F.list,B.a)>;   **coppia con i parametri effettivamente modi-
                      Q.r:= B.r}                                         ficabili ad ogni invocazione
F::= (
       L) {F.list:= L.list}
L::= ( ide
       A) {L.list:= [ide.val] + A.list}
L::= e{L.list:= []}
A1::= , ide
       A2 {A1.list:= [ide.val] + A2.list}
A::= ide {A.list:= []}
B::= D   {C.inp:= B.inp}
      C ; {S.inp:= B.inp}
      S    {B.a:= C.a + S.a - D.list;  **la portata dei formali e' coperta da D.list 
               B.r:= C.r + S.r}
B::=  {C.inp:= B.inp}
      C ; {S.inp:= B.inp}
       S {B.a:= C.a + S.a; B.r:= C.r or S.r }
D::= var ide 
      A ; {D.list:= [ide.val] + A.list}
C::= read ( ide 
      A ) {C.a:= [ide.val] + A.list; C.r:= false}
C::= write ( ide 
      A ){C.a:= []; C.r:= false}
C::= ide := ide
      E {C.a:= [ide.val]; C.r:= false}
C1::= while ide1 = ide2 do  {C2.inp:= C1.inp}
      C2 {S.inp:= C1.inp}
      S od {C1.a:= C2.a + S.a         **certamente modificati: C1.a:= []  --- nota
                 C1.r:= C2.r or S.r or (([ide1.val,ide2.val] insect (C2.a + S.a))=[]}
C::= call ide 
      F {C.a:= match(F.list, select(C.inp, ide.val)); C.r:= false} 
S::= ; {C.inp:= S1.inp}
       C{S.inp:= S1.inp}
      S {S1.a:= C.a + S2.a; S1.r:= C.r + S2.r}
S::= e {S.a:= []; S.r:= false}
E::= + ide 
E::= e
Nota. L'attributo "a" contiene la lista delle variabili che possono risultare modificate ad ogni invocazione, cio' significa che essa contiene anche variabili che, per qualche esecuzione, non risulteranno essere modificate. La raccolta di queste variabli consente di affermare che se nessuna delle variabili che controllano l'iteratore while occorre nella lista, relativa al corpo dell'iteratore, allora tale iteratore e' certamente non terminante. Possiamo tuttavia, decidere di essere piu' stretti ovvero richiedere di individuare non solo gli iteratori certamente non terminanti pure gli iteratori che potrebbero essere non terminanti in certe computazioni: il testo ci lascia ampia liberta'. In quest'ultimo caso, l'attributo "a" deve contenere la lista delle variabili che sono modificate in tutte le computazioni: ecco allora, che le variabili modificate all'interno di un blocco con accesso condizionato, come gli iteratori, non devono essere poste nella lista.
 
 

Esercizio 8.1

Per lo schema utiliziamo i seguenti attributi:

    r per i non terminali P. E' una lista di coppie identificatore di procedura lista delle variabili usate o procedure invocate ma non dichiarate (nello scope)
       r per i non terminali Q. E' una lcoppia identificatore di procedura - lista delle variabili usate o procedure invocate ma non dichiarate (nello scope)
    r per i non terminali B,R, C, S. E' una lista sintetizzata contiene identificatori variabile usati o procedura invovate, nella struttura attraversata, ma non dichiarati
       inpV per i non terminali Q, R, B, C, S. E' un ereditato contiene la lista degli identificatori di variabile nello scope della struttura da attarversare
       inpP per i non terminali Q, R, B, C, S. E' un ereditato contiene la lista degli identificatori di procedura nello scope della struttura da attarversare
       list per i non terminali D, A, E. Sintetizzato contenente la lista di identificatori attraversati dall'analisi del simbolo
       s per i non terminali B, C, S. Sintetizzato booleano contenente true se e solo se una variabile usata come procedura o viceversa occorre nella struttura attraversata
 

Per la definizione delle azioni, utilizzeremo liste con la seguente notazione per gli operatori:
    [x1,...,xn] lista di n, eventualmente n=0, valori.
        | cons di un valore ad una lista
        + append di liste
        \ differenza
        insect intersezione
 
 

P::= D {Q.inpV:= D.list; Q.inpP:= []}
      Q {R.inpV:= D.list , R.inpP:= [Q.h]}
      R {P.r:= Q.r + R.r}
R1::= ;  {Q.inpV:=  R1.inpV; Q.inpP:=  R1.inpP}
        Q {R2.inpV:= R1.inpV; R2.inpP:= [Q.h] + R1.inpP}
        R2 {R1.r:= Q.r + R2.r}
R::= e {R.r:= []}
Q::= procedure ide; {B.inpV:= Q.inpV; B.inpP:= [Q.h] + Q.inpP}
      B end {Q.h:=ide.val; 
                   Q.r:= <ide.val, B.r, B.s>} 
A1::= , ide
       A2 {A1.list:= [ide.val] + A2.list}
A::= ide {A.list:= []}
B::=  {C.inpV:= B.inpV + D.list; C.inpP:= B.inpP}
       C ;  {S.inpV:= B.inpV; S.inpP:= B.inpP}
       S    {B.r:= C.r + S.r; B.s:= C.s OR S.s}
B::=  {C.inpV:= B.inpV; C.inpP:= B.inpP}
       C ; {S.inpV:= B.inpV; S.inpP:= B.inpP}
        S {B.r:= C.r + S.r; B.s:= C.s OR S.s}
D::= var ide 
      A ; {D.list:= [ide.val] + A.list}
C::= read ( ide 
      A ) {C.r:= ([ide.val]+A.list)\C.inpV; C.s:= (([ide.val]+A.list) insect C.inpP) <> []}
C::= write ( ide 
      A ){C.r:= ([ide.val]+A.list)\C.inpV; C.s:= (([ide.val]+A.list) insect C.inpP) <> []}
C::= ide1:= ide2
      E {C.r:= ([ide1.val, ide2.va]+E.list)\C.inpV; C.s:= (([ide1.val, ide2.va]+A.list) insect C.inpP) <> []}
C1::= while ide1 = ide2 do  {C2.inpV:= C1.inpV; C2.inpP:= C1.inpP}
      C2 {S.inpV:= C1.inpV; S.inpP:= C1.inpP}
      S od {C1.r:= C2.r + S.r; C1.s:= C2.s OR S.s}
C::= call ide {C.r:= [ide.val]\C.inpP; C.s:= ([ide.val] insect C.inpV) <> []} 
S1::= ; {C.inpV:= S1.inpV; C.inpP:= S1.inpP; }
       C{S2.inpV:= S1.inpV; S2.inpP:= S1.inpP}
      S2 {S1.r:= C.r + S2.r; S1.s:= C.r OR S2.s; }
S::= e {S.r:= []}
E::= + ide {E.list:= [ide.val]}
E::= - ide {E.list:= [ide.val]}
E::= e {E.list:= []}

 

Esercizio9
 

Utiliziamo la seguente grammatica che verifichiamo essere LL(1), quindi adatta ad un'analisi discendente:

        E::= F E
      E::= "|" F E
      E::= e
        F::= H F
      F::= . H F
      F::= e
        H::= (E)M
        H::= "e"
        H::= a
        H::= b
        H::= c
        M::= *
        M::= e

Per lo schema utiliziamo i seguenti attributi:
    in per i non terminali  E, F, M. E' un ereditato  contiene la rappresentazione del primo operando (gia' attraversato e non visibile a tali simboli)
       instar per i non terminali E, F, M. E' un ereditato contiene un booleano on/off settato on allorche' il primo operando sia una stella di kleene.
     tree per tutti i non terminali. E' il sintetizzato contenente la rapresentazione di albero astratto per l'espressione attraversata dal simbolo.
       star per tutti i non terminali. E' il sintetizzato contenente il booleano on/off sul tipo di espressione attraversata da simbolo.

Per la definizione delle azioni, in particolare la costruzione degli alberi di sintassi astratta
    MkT _  _: string x forest -> tree // costruisce un albero con radice etichettata da string e foresta dei figli, forest.
       MKE :  -> forest // calcola una foresta vuota
       Add _ _ : tree x forest -> forest // aggiunge tree come prtimo albero della foresta forest
 

E::= F {E.in:= F.tree; E.instar:= F.star;}
     E{E.tree:= E.tree; E.star:= E.star}
E1::= "|" F {E2.in:= MkT("|", Add(E1.in, Add(F.tree, MKE()))); E2.instar:= "off"}
     E2 {E1.tree:= E2.tree; E1.star:= "off"}
E::= e {E.tree:= E.in; E.star:= E.instar; }
F::= H {F.in:= H.tree; F.instar:= H.star}
     F{F.tree:= F.tree; F.star:= F.star}
F1::= .H {F2.in:= MkT(".", Add(F1.in, Add(H.tree, MKE()))); F2.instar:= "off"}
     F2 {F1.tree:= F2.tree; F1.star:= F2.star}
H::= ( E {M.in:= E.tree; M.instar:= E.star}
        ) M {H.tree:= M.tree, H.star:= M.star}
M::= * {M.tree:= if (M.instar="on") then M.in else MkT("*", Add(M.in,MKE())); M.star:="on"}
M::= e {M.tree:= M.in; M.star:= M.instar}
H::= a {H.tree:= MkT("a",MKE()); H.star:= "off"}
H::= b {H.tree:= MkT("b",MKE()); H.star:= "off"}
H::= c {H.tree:= MkT("c",MKE()); H.star:= "off"}

 

Esercizio11
 

Utiliziamo la seguente grammatica che verifichiamo essere LL(1), quindi adatta ad un'analisi discendente:

        TypExpression::= Basic
        T::= Structured
        B::= integer
        B::= real
        S::= rec ide : T Rest_of_structure
        R::= , ide : T R
        R::= endrec

Per lo schema utiliziamo i seguenti attributi:
    Y per i non terminali T, B, S, R. E' un sintetizzato e contiene la rappresentazione richiesta per l'espressione o la sottoespressione attraversata dal simbolo.
       val per il terminale ide. E' un sintetizzato, contiene l'identificatore attraversato

Per la definizione delle azioni, in particolare la generazione degli opportuni puntatori e record della rappresentazione utilizzeremo:
    newtype _  _: string x pointer3 -> pointer2 // calcola un puntatore, pointer2, ad un record di due campi, il primo dei quali contiene la stringa, il secondo un
    newfield _ _ _: string x pointer2 x ponter3 -> pointer3 // calcola un puntatore, pointer3, ad un record di tre campi contenente quanto atteso.
    null: puntatore
      intoString _: identificatore -> string // calcola la stringa corrispondente all'identificatore
 

T::= B {T.Y:= B.Y}
T::= S {T.Y:= S.Y}
B::= integer {B.Y:= newtype("integer",null)}
B::= real {B.Y:= newtype("real",null)}
S::= rec
       ide : 
     T
      R{temp:= newfield(toString(ide.val), T.Y, R.Y); 
            S.Y:= newtype("rec", temp)}
R1::= ,
       ide : 
     T
      R2{R1.Y:= newfield(toString(ide.val), T.Y, R2.Y);}
R::= endrec {R.Y:=null}


Esercizio1.2

un solo attributo sintetizzato:
    tree per tutti i non terminali: contiene una copia dell'alberi di derivazione sintattica attraversato durante la derivazione dal non terminale
 
 

S::= (TF S.tree:= MkT("S", Add(MkT("(",MkE()), Add(T.tree, Add(F.tree, MkE()))))
T::= L  T.tree:= MkT("T",Add(L.tree, MkE()))
T::= [L,S] T.tree:= MkT("T",Add(MkT("[",MkE()), Add(L.tree, Add(MkT(",", MkE()),Add(S.tree, Add(MkT("]", MkE()))))))
F1::=,TF2 F1.tree:=MkT("F",Add(MkT(",",MKE()),Add(T.tree,Add(F2.tree,MKE()))))
F::= ) F.tree:=MkT("F",Add(MkT(")",MKE()))
L::= A L.tree:=MkT("L",Add(MkT("A",MKE()))
L::= B L.tree:=MkT("L",Add(MkT("B",MKE()))
L::= C L.tree:=MkT("L",Add(MkT("C",MKE()))
L::= D L.tree:=MkT("L",Add(MkT("D",MKE()))
L::= E L.tree:=MkT("L",Add(MkT("E",MKE()))

Esercizio2.2

    La grammatica sopra di esercizio1.2.
 

Esercizio3.2

Tre attributi: un booleano ereditato, cmd, un sintetizzato di tipo stringa, string, ed un sintetizzato di tipo stringa per i simboli attraversati, val:
    cmd per til solo S: contiene true se l'ultimo simbolo attraversato, prima della derivazione da S, e' il simbolo di controllo "&".
    string per il solo S: contiene la sequenza di simboli attraversata e risolta rispetto l'uso del simbolo di controllo "&"
    val per til solo L: contiene la stringa costituita dal simbolo attarversato.

La necessita' di un ereditato del simbolo distinto S comporta la modifica della grammatica data in esercizio3.1 con la grammatica estesa: cio' premette di introdurre le azioni per il calcolo dell'ereditato del simbolo S quando questi sara' istanziato per la prima volta.
 

S'::= {S.cmd:= false} 
      S
        {S'.string:= S.string}
S1::= L
         {S2.cmd:= (not S1.cmd) and (L.val = "&")}
      S2
     {S1.string:= if (S2.cmd) then S2.string
                                else if (S1.cmd) and (L.val <> "&") then "cmd("+L.val+")"+S2.string
                                          else L.val+S2.string}
S::= e{S.string:= if (S.cmd) then "&" else ""}
L::= &{L.val:= "&"}
L::= A {L.val:= "A"}
L::= B {L.val:= "B"}
L::= C {L.val:= "C"}

 

Esercizio6.2

Utiliziamo la seguente grammatica che verifichiamo essere LR(1), quindi adatta ad un'analisi ascendente, in piu' essa e' fattorizzata a sinistra rispetto alla lista delle dichiarzioni di procedura:

      Program::= Rest_of_procedure Qprocedure
        R::= R Q ;
        R::= e
        Q::= procedure ide Formals; Body
        F::= ( List_of_ide)
        L::= ide Again_ide
        L::= e
        A::= , ide A
        A::= ide
        B::= Declaration Command ; Sequence_of_commands
        B::= Command ; Sequence_of_commands
        D::= var ide A ;
        C::= read ( ide A )
        C::= write ( ide A )
        C::= ide := ide Expression
        C::= while ide = ide do C S od
        C::= call ide F
        E::= + ide
        E::= e

Per lo schema utiliziamo i seguenti attributi:
    p per i non terminali P, R. E' un sintetizzato e contiene la lista delle procedure attraversate prima della procedura attraversata con Q. Notare, peraltro, che esso e' l'attributo richiesto dall'esercizio e tale attributo e' associato alla procedura attraversata da Q in modo indiretto, ovvero e' associata al "padre" (che e' comunque, unico).
      h per il non terminale Q. E' un sintetizzato, contiene la coppia nome della procedura - lista dei formali.
      list per i non terminali F, L, A. E' un sintetizzato, contiene la lista degli identificatori occorrenti nella struttura attraversata
    val per il terminale ide. E' un sintetizzato, contiene il nome dell'identificatore attraversato.
 
 
 
 

P::= R Q {P.p:= R.p}
R1::= R2Q ; {R2.p:= R1.p + [Q.h]}
R::= e {R.p:= []}
Q::= procedure ide F ; B end {Q.h:=<ide.val, F.list>} 
F::= (L) {F.list:= L.list}
L::= ( ide A) {L.list:= [ide.val] + A.list}
L::= e{L.list:= []}
A1::= , ide A2 {A1.list:= [ide.val] + A2.list}
A::= ide {A.list:= []}
B::= CS
B::= C
D::= var ide  A
C::= read ( ide  A
C::= write ( ide  A )
C::= ide := ide E
C1::= while ide1 = ide2 do  C2 S od 
C::= call ide
E::= + ide 
E::= e


Esercizio1.3

tre attributi sintetizzati:
    forest per S ed F: contiene la lista di alberi di cui e' stato attraversato l'albero di derivazione sintattica da S o F rispettivamente
    tree per T: contiene l' albero di cui e' stato attraversato il parse tree da T
    label per L: contiene l'etichetta di cui e' stato attraversato il parse tree da L
 
 

S::= (TF S.forest:= Add(T.tree, Add(F.forest, MkE()))))
T::= L  T.tree:= MkT(L.label,MkE())
T::= [L,S] T.tree:= MkT(L.label, S.forest)
F1::=,TF2 F1.forest:= Add(T.tree, F2.forest)
F::= ) F.forest:= MKE()
L::= A L.label:= "A"
L::= B L.label:= "B"
L::= C L.label:= "C"
L::= D L.label:= "D"
L::= E L.label:= "E"

Esercizio2.3

    La grammatica sopra di esercizio1.3.
 
 

Esercizio3.3

Soluzione con i marcatori.
    1) Partiamo da una soluzione per l'analisi discendente: noi consideriamo quella data in esercizio3.2.
    2) Inseriamo marcatori ed e-produzioni, ottenendo la seguente tabella che tratta ereditati di un simbolo come sintetizzati del marcatore che lo precede:
 

S'::= M1
      S
        {S'.string:= S.string}
S1::= L
      M2
      S2
     {S1.string:= if (M2.cmd) then S2.string
                                else if (S1.cmd) and (L.val <> "&") then "cmd("+L.val+")"+S2.string
                                          else L.val+S2.string}
S::= e{S.string:= if (S.cmd) then "&" else ""}
L::= &{L.val:= "&"}
L::= A {L.val:= "A"}
L::= B {L.val:= "B"}
L::= C {L.val:= "C"}
M1::= e {M1.cmd:= false}
M2::= e {M2.cmd:= (not S1.cmd) and (L.val = "&")}

        3) Risolviamo i riferimenti ad attributi non locali mediante riferimenti sullo stack dei dati ed azioni che calcolano effetti laterali. Per i riferimenti sullo stack utiliziammo l'invariante seguente:
                "ogni maniglia A::=B1,...,Bk ha attributi sintetizzati di Bk-j in Top\j"
 

S'::= M1
      S
        {temp:= Top; pop(2); push(temp)}
S1::= L
      M2
      S2
     {temp:= if (Top\1) then Top
                                else if (Top\3) and (Top\2 <> "&") then "cmd("+Top\2+")"+Top
                                          else Top\2+Top;
          pop(3);
          push(temp)}
S::= e {if (Top) then push("&") else push("")}
L::= &{push("&")}
L::= A {push("A")}
L::= B {push("B")}
L::= C {push("C")}
M1::= e {push(false)}
M2::= e {push((not Top\1) and (Top = "&"))}

        Le azioni pop e push mantengono l'invariante richiesto
Questa soluzione e' interamente meccanica ed e' la soluzione adottata negli strumenti di analisi Cornell Syntetizer.
 

Soluzione con fattorizzazione

    1) Scriviamo una nuova grammatica che strutturi le frasi in modo tale che il problema non richieda piu' attributi ereditati.
 

S1::= S2
      L
     {S1.cmd:= (not S2.cmd) and (L.val = "&")
         S1.string:= if ((not S2.cmd) and (L.val = "&")) then S2.string
                                else if (S2.cmd) and (L.val <> "&") then S2.string + "cmd("+L.val+")"
                                          else S2.string+L.val}
S::= e{S.cmd:=false;
          S.string:= ""}
L::= &{L.val:= "&"}
L::= A {L.val:= "A"}
L::= B {L.val:= "B"}
L::= C {L.val:= "C"}