Linguaggi e modelli computazionali LS Manni Tiziano 0000279932 Realizzare un’applicazione che permetta di descrivere movimenti ed azioni del mouse (click) e della tastiera (pressione tasti) in modo da poterli automatizzare. Introdurre inoltre: costrutti per il controllo del flusso di esecuzione(repeat, for, if else) concetti di variabile, funzione e scope di visibilità. Vediamo quindi un esempio del linguaggio che si vuole ottenere: var i; fun Run { for (i=0; i<10; i=i+1) { doubleClick 50*i+10 300; Esempio; clickDx 10 10; clickSx 40 50; } } fun Esempio { write “simulazione tasti” + i; } Possibilità di collocare frammenti di codice in opportune procedure VN = <PROG, BLOCK, STATEMENT, OPERATION, ASSIGN, DICVAR, WAIT, MOUSE, KEYBOARD, Le funzioni FUNCTION, EXP, TERM, FACTOR, COND, iniziano per lettera maiuscola CONTROL, FOR, REPEAT, IF>. VT = <+, -, *, /, <, >, <=, >=, ==, !=, =, id, idfun, num, string, for, repeat, if, else, doubleClick, clickDx, clickSx, clickSxUp, clickSxDw>. Le variabili iniziano per S = <PROG> lettera minuscola Definiamo le regole di produzione del parser. 1. <PROG> ::= { fun idfun <BLOCK> | <DICVAR> ';' } fun Run <BLOCK> { fun idfun <BLOCK> | <DICVAR> ';' }; Self-Embedding 2. Funzione di <BLOCK> partenza. ::= 3. <STATEMENT> ::= <OPERATION> ';' | <CONTROL> ; 4. < OPERATION> ::= <ASSIGN> | <DICVAR> | <WAIT> | <MOUSE> | <KEYBOARD> | <FUNCTION> ; 5. <CONTROL>::= for <FOR> | repeat <REPEAT> | if <IF>; '{' { <STATEMENT> } '}'; 6. <FUNCTION> ::= <idfun>; Variabili LOOSELY TYPED 7. <DICVAR> ::= var id [ assign <EXP> ]; 8. <ASSIGN>::= id assign <EXP>; Può cambiare il tipo della variabile. L’assegnamento non è interpretato come operatore. 10. <EXP> ::= <TERM> { + <TERM> | - <TERM>}; 11. <TERM> ::= <FACTOR> { * <FACTOR> | / <FACTOR>}; 12. <FACTOR> ::= number | string | id | '(' <EXP> ')'; 13. <COND>::= <EXP> ( < | > | <= | >= | == Operazione meno prioritaria delle altre operazioni matematiche Possibilità di | !=)definire <EXP>; espressioni anche fra tipo diversi. 14. <MOUSE> ::= (doubleClick | clickSx | clickDx | clickSxUp | clickSxDw) <EXP> ' ' <EXP>; 15. <KEYBOARD> ::= write <EXP>; 16. <WAIT> ::= wait <EXP>; Stringa da simulare Attesa (espressa in secondi) Coordinate di azione espresse in pixel. 16. <FOR> ::= '(' <ASSIGN> ';' <COND> ';' <ASSIGN> ')' <BLOCK>; Istruzione di inizializzazione Blocco di istruzioni Istruzione di modifica 17. <REPEAT> ::= '(' <COND> ')' <BLOCK>; Condizione di controllo 18. <IF> ::= '(' <COND> ')' <BLOCK> [ else <BLOCK> ]; Blocco da eseguire con condizione vera … nel caso sia falsa La grammatica ottenuta è di tipo 2 … … e risulta LL(1) in quanto per qualunque simbolo non terminale della grammatica gli starter symbol sono disgiunti. → il parser sa sempre quale produzione usare. Per l’analisi lessicale del programma scritto dall’utente sono stati realizzati due componenti: Lo scanner: scorre la stringa (che rappresenta il programma scritto dall’utente) e lo separa in sotto-stringhe in base ad una lista di delimitatori. Il lexer che riceve dallo scanner le varie sotto-stringhe, le esamina assegnando una corrispondente categoria lessicale: costruisce quindi una serie di oggetti “Token” passati poi all’analizzatore sintattico. Essendo la grammatica LL(1), risulta possibile utilizzare l’analisi ricorsiva discendente per analizzare le frasi passate in input: 1) Si introducono tante funzioni quanti i simboli non terminali della grammatica. 2) Si fa sì che ogni funzione riconosca il sotto-linguaggio generato da ciascun simbolo non terminale. 3) Risulta quindi semplice effettuare chiamate ricorsive di funzioni. L’analisi sintattica viene svolta dal componente Parser: esso ottiene l’istanza della classe Lexer e la utilizza per ottenere i vari Token di cui la frase da analizzare è costituita. Ricevuto in ingresso il primo Token di una frase, il parser(essendo la grammatica LL(1)) è in grado di richiamare la funzione corrispondente che analizzerà la correttezza dei Token successivi. In caso di errore il Parser provvede a lanciare una nuova istanza della classe ParserException. I visitor creati sono due, ed entrambi implementano ovviamente la comune classe astratta “Visitor”: ExecuteVisitor: ha il compito di interpretare il programma scritto da utente e simulare le azioni descritte dal programma stesso. TreeViewVisitor: ha il compito di disegnare graficamente l’albero restituito dal parser. Il visitor ExecuteVisitor provvede inoltre a lanciare un’opportuna eccezione “RunTimeException” nel caso in cui trovi frasi senza senso nel programma: Riferimenti a variabili non esistenti Tipi non compatibili. Utilizzo di variabili non inizializzate. Operatori non compatibili con gli argomenti passati. Richiamo di funzioni non definite. Per individuare errori a RunTime è stata introdotta nell’ExecuteVisitor una modalità di simulazione (valutazione delle variabili, espressioni e funzioni). L’applicazione è stata suddivisa in due thread: Un flusso si occupa dell’interfaccia grafica, L’altro flusso esegue l’ExecuteVisitor. → l’utente ha la possibilità di interrompere l’esecuzione in presenza ad esempio di errori (esempio: loop infiniti, ecc). Inoltre è stato implementato un sistema di “generazione di codice” per aiutare la programmazione: Recupero della posizione corrente del mouse e di eventuali tasti premuti, Recupero del codice corrispondente ad alcuni tasti speciali della tastiera (esempio: tab, enter, ctrl, alt, ecc). Scanner Programma TreeViewVisitor ExecuteVisitor Lexer Parser Introduzione di comandi per simulare la pressione di combinazioni di tasti della tastiera (esempio ctrl + … , ctrl+alt+… , ecc. ). Esecuzione di funzioni con passaggio di dati. Introduzione di comandi per interagire con l’utente durante l’esecuzione.