PUSH DOWN AUTOMATON
Per poter riconoscere i linguaggi di tipo 2 e necessario poter processare stringhe che presentino forme di self embedding, di conseguenza un automa a stati finiti non sarebbe in grado in quanto dovrebbe avere un numero di stati non noto a priori (non finiti)
Viene quindi introdotto il push down automaton (PDA), la differenza rispetto a un automa a stati finiti e la presenza di uno stack quindi la funzione di stato considera anche l’insieme dell alfabeto interno Z come ingresso
dove:
- e un alfabeto
- e l’insieme degli stati
- e lo stato iniziale ()
- e l’insieme degli stati finali
- l’alfabeto degli elementi che possono essere inseriti nello stack (alfabeto interno)
- e la funzione di stato che computa lo stato futuro del sistema e il nuovo simbolo dello stack in funzione dell’input e del simbolo in cima allo stack
PROCESSO DI RICONOSCIMENTO
Per ogni stato l’automa calcola lo stato futuro e il simbolo da inserire nello stack in funzione dell’ingresso e del simbolo in cima allo stack
flowchart TD A[read from X input] B[read from Z stack] C[compute next state] D[push symbol to the stack] A --> B--> C--> D
CRITERI DI RICONOSCIMENTO
Si può definire l’insieme delle frasi riconosciute da un PDA con due criteri
CRITERIO DELLO STACK VUOTO | CRITERIO DEGLI STATI FINALI |
---|---|
Un PDA riconosce correttamente una frase se essa svuota lo stack | Un PDA riconosce correttamente una frase se raggiunge uno stato finale (come RSF) |
PDA NON DETERMINISTICI
I PDA come gli RSF possono presentare regole non deterministiche, ovvero produzioni dove l’automa non e in grado di decidere in quale stato portarsi
Un PDA e non deterministico se con un singolo ingresso si porta in più stati futuri:
Oppure se può transitare di stato senza consumare l’ingresso
A differenza degli RSF i PDA non sempre possono essere portati in una forma deterministica equivalente, inoltre
la classe di linguaggi di tipo 2 coincide con quella riconosciuta da un PDA non deterministico
Inoltre i tempi di riconoscimento di un PDA non deterministico non sono lineari (pessime prestazioni), tuttavia
alcuni linguaggi context free sono riconoscibili da PDA deterministici
Di conseguenza e sufficiente definire linguaggi context free di questa particolare sottoclasse per poter avere tempi di risoluzione lineari
SVANTAGGI DI UN PDA DETERMINISTICO
Un PDA deterministico presenta alcuni svantaggi rispetto a un PDA non deterministico, in particolare
- il criterio dello stack vuoto risulta meno potente del criterio stati finali
- una limitazione sul numero di stati interni o sul numero di configurazioni finali riduce l’insieme dei linguaggi riconoscibili
- l’assenza di riduce l’insieme dei linguaggi riconoscibili.
IMPLEMENTAZIONE DI UN PDA: ANALISI RICORSIVA DISCENDENTE
Un modo intelligente per implementare un PDA deterministico e quello di sfruttare le capacita dei linguaggi, che già gestiscono memorie stack (praticamente tutti quelli che supportano la ricorsione)
In questo tipo di implementazione ogni metasimbolo si mappa in una funzione che ne riconosce il sottolinguaggio generato dalle produzioni che hanno il metasimbolo sulla sinistra
Per esempio, un caso molto rilevante come il bilanciamento delle parentesi può essere riconosciuto come segue, data la grammatica
Si ottiene il seguente riconoscitore implementato sfruttando l’analisi ricorsiva discendente
# variabili di comodo e input fissato (per semplicita)
i=0
text="(((c)))"
# funzione di comodo per ottenere il prossimo carattere della stringa
def next():
global i
result=text[i]
i+=1
return result
# metasimbolo S
def S():
c=next()
if c == 'c':
return True
if c == '(':
if S():
if next() == ')':
return True
else:
return False
return False
# esecuzione dello scopo della grammatica e controllo risultato
if S() and len(text) == i: print("good")
else: print("bad")
IMPROVEMENT INGEGNERISTICI: SEPARAZIONE FRA ENGINE E REGOLE
L’implementazione come mostrata sopra risulta intuitiva ma poco scalabile, e molto più vantaggioso separare il motore del PDA dalle regole stesse della grammatica tramite una tabella di parsing, dato il linguaggio che segue
la tabella di parsing risultante:
0 | 1 | c | |
---|---|---|---|
LIMITI DELL’ANALISI RICORSIVA DISCENDENTE
L’analisi ricorsiva discendente e applicabile solo in caso in cui la grammatica sia deterministica, ovvero deve essere possibile dedurre la produzione corretta dalle informazioni disponibili in quel momento(no guessing)
- stato attuale
- memoria del passato (stack)
- ma anche parte del input ancora da consumare
Risulta quindi necessario definire un sottoinsieme di linguaggi di tipo 2 che siano riconoscibili in maniera deterministica guardando al più simboli in avanti, e il caso delle grammatiche llk