Il modulo utils contiene definizioni che sono di carattere generale, che possono essere semplicemente delle costanti o funzionalità comuni a più parti del codice.
4.2.1
L’ambiente predefinito.
Nel listato 4.2 è specificata la funzione makeTEnvStart, la quale costruisce l’ambiente iniziale Γ0 per la validazione delle signature presentata nella sezione 3.5. Inizialmente
sono dichiarate delle costanti per parametrizzare la libreria dalle definizioni (righe 1–13).
Alla riga 15 inizia la dichiarazione di makeTEnvStart, con unico parametro la tabella dei simboli del programma in analisi. Il binding tEnvStart specifica l’ambiente predefinito del linguaggio senza le costanti numeriche. Esso contiene in ordine:
• le definizioni dei valori di tipo predefiniti che sono number, bool e void. • le costanti booleane: true e false . Le costanti numeriche non sono presenti poi-
ché sono potenzialmente tutte le costanti numeriche, e sono inserite nell’ambien- te solo quelle presenti nella tabella dei simboli. In IoT-Lysa non sono predefinite costanti void.
• gli operatori binari di congiunzione logica e di disgiunzione logica, e gli operatori binari per la somma, sottrazione, moltiplicazione e divisione di valori numerici. • l’operatore < per indicare la relazione di minoranza tra due valori numerici. L’o- peratore di uguaglianza, =, non è presente perché viene gestito durante l’analisi come un caso speciale.
• gli operatori unari di opposto di un numero e negazione di un valore booleano (con gli operatori −− e not). In realtà durante il parsing, l’operazione op- posta è riconosciuta dal classico operatore unario −, ma viene rappresentata nell’ambiente con il simbolo −− per non collidere con la rappresentazione della sottrazione.
Le ultime 4 linee finali costruiscono l’ambiente effettivo del programma in analisi, che comprendono anche il pre-processing della tabella dei simboli (righe 38–42). Infatti, alla riga 39 sono filtrate tutte le costanti numeriche dalla tabella dei simboli (con il test symbolType = NumericId). Ogni simbolo filtrato viene trasformato nel valore costante corrispondente e aggiunto a tEnvStart, ottenendo l’ambiente desiderato.
Listing 4.2: Codice per la costruzione dell’ambiente predefinito. 1 l e t NumericName = " number "
2 l e t NumericType = Type ( NumericName )
3
4 l e t BoolName = " b o o l "
5 l e t BoolType = Type ( BoolName )
6
7 l e t FalseName = " f a l s e " 8 l e t TrueName = " t r u e " 9
10 l e t VoidName = " v o i d "
11 l e t VoidType = Type ( VoidName )
12
13 l e t EqualName = "=" 14
15 l e t makeTEnvStart ( t a b l e : SymbolTable ) : TEnv = 16 l e t t E n v S t a r t : TEnv = Map <| s e q [
17 BoolName , TEImType BoolType 18 NumericName , TEImType NumericType 19 VoidName , TEImType <| VoidType 20
21 FalseName , TEImFunc <| Func ( FalseName , [ ] , BoolType ) 22 TrueName , TEImFunc <| Func ( TrueName , [ ] , BoolType ) 23
24 "&&", TEImFunc <| Func ( "&&", [ BoolType ; BoolType ] , BoolType ) 25 " | | ", TEImFunc <| Func ( " | | ", [ BoolType ; BoolType ] , BoolType ) 26
27 "+", TEImFunc <| Func ( "+", [ NumericType ; NumericType ] , NumericType ) 28 "−", TEImFunc <| Func ( "−", [ NumericType ; NumericType ] , NumericType ) 29 " ∗ ", TEImFunc <| Func ( " ∗ ", [ NumericType ; NumericType ] , NumericType ) 30 " / ", TEImFunc <| Func ( " / ", [ NumericType ; NumericType ] , NumericType ) 31
32 "<", TEImFunc <| Func ( "<", [ NumericType ; NumericType ] , BoolType ) 33
34 "−−", TEImFunc <| Func ( "−−", [ NumericType ] , NumericType ) 35 " n o t ", TEImFunc <| Func ( " n o t ", [ BoolType ] , BoolType )
36 ]
37
38 t a b l e . Symbols |>
39 Seq . f i l t e r ( fun sym −> sym . symbolType = NumericId ) |> 40 Seq . map (fun sym −>
41 ( sym . v a l u e , TEImFunc ( Func ( sym . v a l u e , [ ] , NumericType ) ) ) ) |> 42 Seq . f o l d (fun ( s : TEnv ) p −> s . Add p ) t E n v S t a r t
Listing 4.3: Codice della funzione GlobalState.
1 l e t G l o b a l S t a t e ( t e n v : TEnv ) ( n d e c l s : NodeDecl l i s t ) : TStore = 2 L i s t . f o l d
3 ( fun ( s t o r e : TStore ) ( ( NDecl ( nodeId , _) ) ) −> 4 i f IsKeyword n o d e I d . v a l u e then 5 r a i s e <| K e y w o r d D u p l i c a t i o n E x c e p t i o n n o d e I d 6 7 i f s t o r e . ContainsKey n o d e I d . v a l u e then 8 r a i s e <| N a m e D u p l i c a t i o n E x c e p t i o n n o d e I d 9 10 i f t e n v . ContainsKey n o d e I d . v a l u e then 11 r a i s e <| N a m e D u p l i c a t i o n E x c e p t i o n n o d e I d 12 13 s t o r e . Add ( n o d e I d . v a l u e , NODEID) 14 )
15 (Map . empty : TStore ) 16 n d e c l s
4.2.2
La funzione GlobalState.
Il listato 4.3 mostra l’implementazione della funzione GlobalState (che come la funzione omonima definita nella sezione 3.5) ha come scopo di creare la memoria iniziale per le valutazioni dei nodi, ovvero una memoria composta solo dai binding degli identificativi dei nodi.
La funzione richiede come parametri l’ambiente e la lista dei nodi, e restituisce un valore TStore. Ogni identificativo di nodo per essere valido deve valere che:
• non sia una keyword del linguaggio (righe 4–5);
• non identifichi un altro nodo già incontrato (righe 7–8 ); • non denoti oggetti dell’ambiente (righe 10–11);
Se una delle condizioni si verifica è lanciata un’eccezione definita dalla libreria. Mag- giori informazioni sulle eccezioni della libreria sono presenti alla sezione 4.3.
4.2.3
Riconoscimento delle keyword.
Uno degli errori che il type-system deve restituire anche se non è stato formalizzato nelle regole è il tentativo di riutilizzare una keyword per denotare oggetti. Per tale scopo nel modulo Utils è presente la funzione IsKeyword che restituisce true se e solo se l’identificatore fornito è una keyword. L’implementazione è mostrata nel listato 4.4.
Tutte le parole chiave sono contenute nell’array keyWords utilizzata per costruire un HashSet, che garantisce un tempo di ricerca costante di un elemento. La funzione IsKeyword è la funzione di ricerca Contains nell’insieme. Il costo della costruzione dell’insieme è pagato soltanto una volta al caricamento del programma.
Listing 4.4: Codice per il riconoscimento delle keyword. 1 l e t IsKeyword : ( I d e −> b o o l ) =
2 l e t keyWords =
3 [ |
4 BoolName ; NumericName ; VoidName
5 " t y p e "; " f u n c "; " a c t u a t o r _ t y p e "; " r e c " 6 " s e n s o r "; " a c t u a t o r "; " p r o c e s s "; " node " 7 " r e c e i v e "; " s e n d "; " snd "; " r c v "; " w a i t _ f o r " 8 " p r o b e "; " t o "; " i f "; " t h e n "; " e l s e "; " s w i t c h " 9 " n i l "; " t a u "; TrueName ; FalseName ; " n o t " 10 | ] 11
12 l e t h = new System . C o l l e c t i o n s . G e n e r i c . HashSet<s t r i n g >( keyWords ) 13 h . C o n t a i n s
Listing 4.5: Codice per il supporto delle variabili generiche. 1 l e t f r e s h T y p e V a r G e n e r a t o r = 2 l e t mutable f r e s h I d = −1 3 fun ( ) −> 4 f r e s h I d <− f r e s h I d + 1 5 TypeVar ( f r e s h I d ) 6 7 l e t s u b s t i t u i t e A l l ( t s t o r e : TStore ) ( o l d V a l u e : TypeVar )
8 ( newValue : TStoreIm ) : TStore =
9 t s t o r e |>
10 Map . f i l t e r ( fun _ v −> v = TSImTypeVar ( o l d V a l u e ) ) |> 11 Map . f o l d ( fun s k _ −> s . Add ( k , newValue ) ) s t o r e
4.2.4
Gestione delle variabili generiche.
Il listato 4.5 mostra il codice per supportare il tipo TypeVar (introdotto nella sezio- ne 4.1) per simulare il guessing di tipo (vedi sezione 3.12). Un valore di TypeVar rappresenta una variabile generica del programma di cui non è stato possibile inferire il tipo.
Il tipo è usato nella valutazione di una receive contenente una variabile “nuo- va” usata come destinazione. In questo caso è necessario assicurarsi che il nuovo tipo generico sia univoco a tutti quelli precedenti. Il compito è affidato alla funzio- ne freshTypeVarGenerator, che ad ogni chiamata restituisce un nuovo valore di tipo generico. Il nuovo identificatore è ottenuto incrementando un contatore interno.
Se viene inferito il tipo di una variabile generica da un suo utilizzo, si procede all’instanzazione del tipo generico con uno specifico. Caso particolare si ha quando è inferito che due tipi generici coincidono, e allora si procede all’unificazione dei due tipi. La funzione substituteAll sostituisce nella memoria tstore tutti i binding per il valore tipo generico oldValue con un nuovo valore newValue di TStoreIm. Vengono filtrati i binding da sostituire, dopodiché si agisce sulla stessa memoria, ridefinendo i binding con la funzione Add. L’unificazione è implementata indicando un valore di TypeVar per il parametro newValue.
Listing 4.6: Codice delle dichiarazioni di alcune eccezioni. 1 module E x c e p t i o n s = 2 // / A s i g n a t u r e h a s a d u p l i c a t e d name . 3 e x c e p t i o n N a m e D u p l i c a t i o n E x c e p t i o n o f sym : Symbol 4 with 5 o v e r r i d e t h i s . Message = s p r i n t f
6 "ERROR: TYPE_CHECK_1: t h e name \"% s \" i s a l r e a d y u s e d . " t h i s . sym . v a l u e 7
8 // / A s i g n a t u r e t r y t o u s e a t y p e n o t p r e v i o u s l y d e c l a r e d . 9 e x c e p t i o n T y p e N o t D e c l a r e d E x c e p t i o n o f sym : Symbol
10 with
11 o v e r r i d e t h i s . Message = s p r i n t f
12 "ERROR: TYPE_CHECK_2: t h e t y p e \"% s \" i s n o t d e c l a r e d . " t h i s . sym . v a l u e 13 . . .