Home / Indice sezione
 www.icosaedro.it 

 M2 - Tutorial

In questo tutorial vedremo alcune delle caratteristiche di M2 attraverso semplici esempi. Daremo per scontata la conoscenza delle nozioni di base della programmazione, e quindi non ci dilungheremo a spiegare cos'è una variabile o cosè una istruzione per il controllo di flusso. Alcuni esempi si possono provare on-line: basta cliccare sull'ancora sotto il sorgente per accedere alla maschera di editing, compilazione ed esecuzione del programma. Naturalmente è anche possibile modificare i programmi proposti.

Indice

Esempio 1 - Il classico Hello World
Esempio 2 - Controllo di flusso, cicli
Esempio 3 - Leggere un file
Esempio 4 - Array dinamici, funzioni
Esempio 5 - Manipolare le stringhe
Esempio 6 - Strutture dati dinamiche
Esempio 7 - Moduli
Esempio 8 - Qualificazione dei nomi
Come proseguire

Esempio 1: Il classico Hello World

Il primo esempio è il tradizionale "Hello, world!", un programma che stampa un messaggio sullo schermo:

MODULE HelloWorld
(*
** The basic of all the programs from any time ever: hello world!
*)
IMPORT m2
BEGIN
    print("Hello, world!\n")
END
(provalo on-line)

Questo è il nostro primo sorgente M2. Salviamo questo testo nel file Hello.mod. Lo compiliamo ed eseguiamo dando il comando

m2 Hello.mod -r

Se tutto è andato bene, vedremo apparire sullo schermo il famigerato messaggio. Osservando il sorgente, notiamo che:

Esempio 2: Controllo di flusso, cicli

Un altro esempio un po' più articolato: stampare le tabelline della moltiplicazione:

MODULE PythagorasTables

(*
** Print the multiplication tables. An example of nested FOR loops.
*)

IMPORT m2

VAR
    a, b: INTEGER

BEGIN
    FOR a=1 TO 10 DO
        print("Table of the " + a + ":\n")
        FOR b=1 TO 10 DO
            print( "\t" + a + " * " + b + " = " + (a*b) + "\n" )
        END
        print("\n")
    END
END
(provalo on-line)

Salviamo questo testo nel file Tabelline.mod. Lo possiamo compilare ed eseguire come abbiamo fatto per l'esempio 1. In questo programma notiamo che:

Esempio 3: Leggere un file

Il terzo esempio introduce le stringhe e le operazioni sui file: il programma richiede all'operatore il nome di un file, quindi lo legge e lo visualizza sullo schermo. Probabilmente il file in questione sarà un testo, altrimenti il risultato sullo schermo potrebbe essere un po' confuso!

MODULE LeggiFile

IMPORT m2, io

VAR
    fn: STRING    (* nome del file *)
    f: FILE       (* file descriptor *)
    buf: STRING   (* i/o buffer *)

BEGIN
    print("File: ")
    fn = input()  # leggi da tastiera il nome del file
    Open(f, fn, "r")
    LOOP
        (* leggi un blocco di 4 KB: *)
        buf = Read(f, 1024*4)
        IF buf = NIL THEN
            EXIT
        END
        print(buf)
    END
    Close(f)
END

In questo esempio notiamo che:

Esempio 4: Array dinamici, funzioni

In questo esempio illustriamo le funzioni e gli array dinamici. La funzione LeggiFile(fn) legge l'intero contenuto di un file di testo di nome fn e lo ritorna in un array di stringhe. Ogni elemento dell'array contiene una riga del file. La funzione Trova(parola, a) stampa tutte le stringhe dell'array che contengono la parola indicata.

MODULE Ricerca

IMPORT m2, io

    FUNCTION LeggiFile(fn: STRING): ARRAY OF STRING
    VAR f: FILE  i: INTEGER  line: STRING  a: ARRAY OF STRING
    BEGIN
        Open(f, fn, "r")
        i = 0
        LOOP
            line = ReadLine(f)
            IF line = NIL THEN
                EXIT
            END
            a[i] = line
            i = i+1
        END
        Close(f)
        RETURN a
    END


    FUNCTION Trova(p: STRING, a: ARRAY OF STRING)
    VAR n, i: INTEGER
    BEGIN
        n = 0
        FOR i = 0 TO count(a)-1 DO
            IF match(a[i], p) THEN
                print(a[i] + "\n")
                n = n+1
            END
        END
        print("Trovate " + n + " ricorrenze.\n")
    END

VAR
    fn: STRING
    parola: STRING
    a: ARRAY OF STRING

BEGIN
    print("Inserisci il nome del file: ")
    fn = input()
    a = LeggiFile(fn)
    print("Inserisci la parola da cercare (espressione regolare): ")
    parola = input()
    Trova(parola, a)
END

In questo esempio notiamo che:

Anche in questo esempio abbiamo ignorato la gestione degli errori. La compilazione degli esempi precedenti produce parecchi messaggi di warning che ci avvisano che certe funzioni che abbiamo impiegato potrebbero causare condizioni di errore:

Ricerca.mod:8:14: warning: the function `Open' may RAISE ERROR - use TRY...END
Ricerca.mod:11:29: warning: the function `ReadLine' may RAISE ERROR - use TRY...END
Ricerca.mod:18:15: warning: the function `Close' may RAISE ERROR - use TRY...END

Il compilatore ci avverte che queste funzioni possono generare condizioni di errore: ignorandole, il nostro programma verrebbe terminato qualora una di queste condizioni dovesse verificarsi. Per esempio, se tentassimo di aprire un file inesistente di nome xyz ecco cosa apparirebbe:

File: xyz
io.Open(), line 108: "xyz": No such file or directory (code 2)

Il messaggio fa riferimento alla posizione della funzione nel modulo di libreria io. Per la gestione degli errori M2 dispone di una apposita istruzione TRY...END, ma non ce ne occuperemo qui. Diciamo solo che, usando questa istruzione, il messaggio di errore sarebbe stato più circostanziato e avrebbe riportato anche la posizione esatta nel nostro programma dell'istruzione che ha causato l'errore.

Esempio 5: Manipolare le stringhe.

Manipolare stringhe di caratteri in M2 è molto semplice, e si possono manipolare singoli caratteri o lunghe sequenze con uguale facilità. L'operatore di sottostringa [i,j] restituisce l'intervallo della stringa indicato. L'operatore di sottostringa [i] restituisce un carattere, ed è semplicemente la forma abbreviata dell'intervallo [i,i+1]. La figura qui sotto dovrebbe chiarire meglio come gli intervalli di caratteri sono determinati.

In questo esempio la funzione Reverse(s) ritorna la stringa ottenuta invertendo l'ordine dei caratteri nella stringa passata per argomento. Avremmo potuto usare un semplice ciclo FOR, ma qui per rendere le cose più interessanti lo facciamo usando la ricorsione. Il meccanismo è semplice: la funzione spezza a metà la stringa, quindi chiama sè stessa per invertire le due parti, poi unisce i pezzi scambiandone l'ordine. Mentre le chiamate ricorsive della funzione proseguono, ogni nuova istanza della funzione riceve una stringa più breve, fino a quando essa si riduce a un carattere soltanto o alla stringa vuota: in questi due casi non è necessario invertire nulla.

MODULE ReverseString
(*
** Reverse the characters in a string using a recursive method.
** Demostrate the usage of the substrings and of the recursive
** functions.
*)

IMPORT m2

CONST
    MYSTRING = "Programming in M2"

FUNCTION Reverse(s: STRING): STRING
VAR l: INTEGER
BEGIN
    l = length(s)
    IF l < 2 THEN
        RETURN s
    ELSE
        RETURN Reverse(s[l DIV 2, l]) + Reverse(s[0, l DIV 2])
    END
END

BEGIN
    print( MYSTRING + " <=> " + Reverse( MYSTRING ) + "\n" )
END
(provalo on-line)

Da notare che l'operatore di divisione tra numeri interi è "DIV" e non "/" per sottolineare che il risultato è troncato della parte decimale: 7 DIV 5 dà 1 e non 1.4. L'operatore MOD dà invece il resto della stessa divisione: 7 MOD 5 dà infatti 2. Infine, la funzione length(s) dà la lunghezza della stringa.

Esempio 6: Strutture dati dinamiche

Il programma di questo esempio illustra come si possono costruire strutture dati dinamiche in M2. Il programma costruisce un elenco di parole, ordina le parole in un albero binario, e quindi le stampa in ordine alfabetico:

MODULE BinaryTree

(*
** Classical binary tree: insertion, deletion, traversing.
** Illustrate handling of dynamical data structures and
** function recursion.
*)

IMPORT m2

TYPE Node =
    RECORD
        left, right: Node
        key: STRING
    END


FUNCTION insert(key: STRING, VAR n: Node)
BEGIN
    IF n = NIL THEN
        n[key] = key
    ELSIF key < n[key] THEN
        insert(key, n[left])
    ELSIF key > n[key] THEN
        insert(key, n[right])
    ELSE
        error("insert(): ignoro chiave duplicata `" + key + "'\n")
    END
END


FUNCTION delete(key: STRING, VAR n: Node)

    FUNCTION del(VAR r: Node)
    VAR t: Node
    BEGIN
        IF r[right] = NIL THEN
            t = r
            r = r[left]
            t[left] = n[left]
            t[right] = n[right]
            n = t
        ELSE
            del(r[right])
        END
    END

BEGIN
    (* Search the key: *)
    IF n = NIL THEN
        error("delete(): chiave `" + key + "' non trovata\n")
        RETURN
    ELSIF key < n[key] THEN
        delete(key, n[left])
        RETURN
    ELSIF key > n[key] THEN
        delete(key, n[right])
        RETURN
    END

    (* Key found: *)
    IF n[left] = NIL THEN
        n = n[right]
    ELSIF n[right] = NIL THEN
        n = n[left]
    ELSE
        del(n[left])
    END
END
        

FUNCTION print_in_order(n: Node)
BEGIN
    IF n = NIL THEN
        RETURN
    END
    print_in_order(n[left])
    print(n[key] + "\n")
    print_in_order(n[right])
END
        

FUNCTION print_tree(n: Node, a: STRING, b: STRING)
BEGIN
    print(a)
    IF n = NIL THEN
        print("*\n")
        RETURN
    END
    print(n[key] + "\n")
    print_tree(n[right], b + "|`", b + "| ")
    print_tree(n[left], b + " `", b + "  ")
END


VAR root: Node

BEGIN
    insert("M", root)
    insert("O", root)
    insert("D", root)
    insert("U", root)
    insert("L", root)
    insert("E", root)

    print("Sorted list:\n")
    print_in_order(root)

    print("\nOriginal tree:\n")
    print_tree(root, NIL, NIL)

    print("\nNow delete node `E'. Resulting tree:\n")
    delete("E", root)
    print_tree(root, NIL, NIL)

    print("\nNow delete node `D'. Resulting tree:\n")
    delete("D", root)
    print_tree(root, NIL, NIL)

    print("\nNow delete node `M'. Resulting tree:\n")
    delete("M", root)
    print_tree(root, NIL, NIL)
END
(provalo on-line)

Tutte le parole sono disposte in un albero binario i cui nodi sono di tipo Node. Il tipo Node è un RECORD che contiene tre campi: il campo key è una parola, mentre i campi left e right sono i rami sinistro e destro dell'albero. La variabile globale root è la radice dell'albero.

La funzione insert(s, root) inserisce la parola s nell'albero la cui radice è root. Quando la funzione aggiunge una nuova parola viene automaticamente creato un nuovo nodo. I campi del nuovo nodo che non vengono esplicitamente assegnati assumono il valore default. Nel nostro caso significa che i campi left e right valgono NIL.

Una volta terminato l'inserimento, la funzione print_in_order(root) stampa tutte le parole nell'ordine alfabetico. Infine, la funzione print_tree(root, NIL, NIL) stampa l'albero binario in "arte ASCII" mettendo degli asterischi in corrispondenza dei campi NIL.

Supponiamo che le parole che vengono inserite nel programma siano "M", "O", "D", "U", "L", "A". L'output del programma sarà:

In-order list:
D
E
L
M
O
U
Tree structure:
M
|`O
| |`U
| | |`*
| |  `*
|  `*
 `D
  |`L
  | |`*
  |  `E
  |   |`*
  |    `*
   `*

Riportiamo anche una funzione che esegue la ricerca di una parola nell'albero:

FUNCTION search(key: STRING, n: Node): Node
BEGIN
    IF n = NIL THEN
        RETURN NIL
    ELSIF key < n[key] THEN
        RETURN search(key, n[left])
    ELSIF key > n[key] THEN
        RETURN search(key, n[right])
    ELSE
        RETURN n
    END
END

Richiamata ad esempio con search("D", root) questa funzione ritorna il nodo corrispondente. Se la chiave non viene trovata, la funzione ritorna NIL.

Cancellare un nodo dall'albero è più complicato:

FUNCTION delete(key: STRING, VAR n: Node)

    FUNCTION del(VAR r: Node)
    VAR t: Node
    BEGIN
        IF r[right] = NIL THEN
            t = r
            r = r[left]
            t[left] = n[left]
            t[right] = n[right]
            n = t
        ELSE
            del(r[right])
        END
    END

BEGIN
    IF n = NIL THEN
        error("delete(): key `" + key + "' not found.\n")
        RETURN
    ELSIF key < n[key] THEN
        delete(key, n[left])
        RETURN
    ELSIF key > n[key] THEN
        delete(key, n[right])
        RETURN
    END
    IF n[left] = NIL THEN
        n = n[right]
    ELSIF n[right] = NIL THEN
        n = n[left]
    ELSE
        del(n[left])
    END
END

Questa funzione cancella il nodo con chiave key dall'albero n. Ad esempio, la funzione potrebbe essere invocata come delete("D", root). La funzione prima ricerca il nodo da cancellare e se non lo trova stampa un messaggio di errore. Se il nodo da cancellare non ha discendenti il problema si risolve facilmente, basta togliere il nodo. Se il nodo da togliere ha un solo discendente, allora basta sostituire il nodo da cancellare con il nodo discendente.

Più complicato il caso in cui il nodo da cancellare ha due discendenti. Si hanno due possibilità: esaminare il sottoalbero di sinistra del nodo da cancellare e individuare il nodo più a destra e sostituire questo nodo a quello da cancellare; in alternativa, esaminare il sottoalbero di destra del nodo da cancellare e individuare il nodo più a sinistra e sostituire questo nodo a quello da cancellare. La funzione del() segue la prima strategia.

Ecco cosa succede cancellando in sequenza le parole "E", "D" e "M":

Situazione iniziale: delete("E", root) delete("D", root) delete("M", root)
M
|`O
| |`U
| | |`*
| |  `*
|  `*
 `D
  |`L
  | |`*
  |  `E
  |   |`*
  |    `*
   `*
M
|`O
| |`U
| | |`*
| |  `*
|  `*
 `D
  |`L
  | |`*
  |  `*
   `*
M
|`O
| |`U
| | |`*
| |  `*
|  `*
 `L
  |`*
   `*
L
|`O
| |`U
| | |`*
| |  `*
|  `*
 `*

Esempio 7: Moduli

Gli esempi precedenti erano tutti dei programmi stand-alone MODULE e risiedevano in file con estensione .mod. La compilazione dei programmi MODULE produce un programma eseguibile. M2 supporta il concetto di librerie di moduli: un modulo di libreria è costituito da un DEFINITION MODULE, che definisce l'interfaccia, e un IMPLEMENTATION MODULE che contiene il codice. I rispettivi file hanno estensione .def e .imp. A titolo di esempio, mostriamo un semplice modulo per la generazione di numeri casuali. Prima il DEFINITION MODULE:

DEFINITION MODULE Random
(* Banale implementazione per un generatore di numeri casuali. *)

    FUNCTION NewSequence(seed: INTEGER)
    (* genera una nuova sequenza di numeri "casuali"
       generati a partire da seed *)

    FUNCTION Rnd(): INTEGER
    (* Ritorna il prossimo numero "casuale". *)

END

La compilazione del DEFINITION MODULE comporta la semplice verifica della sua correttezza e non genera alcun file. I moduli clienti, siano essi dei MODULE, dei DEFINITION MODULE o degli IMPLEMENTATION MODULE, potranno importare le costanti, i tipi, le variabili e le funzioni definite in questo DEFINITION MODULE (in questo caso esportiamo solo le due funzioni NewSequence() e Rnd()).

Ed ora il modulo di implementazione:

IMPLEMENTATION MODULE Random

    CONST
        a = 23
        b = 12345
        m = 65536

    VAR
        seed: INTEGER

    FUNCTION NewSequence(s: INTEGER)
    BEGIN
        seed = s MOD m
    END

    FUNCTION Rnd(): INTEGER
    BEGIN
        seed = (a*seed + b) MOD m
        RETURN seed
    END

END

La compilazione del modulo di implementazione del modulo Random produce un codice linkabile ai moduli clienti. I moduli clienti, siano essi dei MODULE, dei DEFINITION MODULE o degli IMPLEMENTATION MODULE, potranno importare le costanti, i tipi, le variabili e le funzioni dichiarate nel modulo di definizione usando la dichiarazione IMPORT Random.

Osserviamo che, mentre il modulo di definizione resta "in chiaro" (si tratta di un semplice testo), il modulo di implementazione consiste nel sorgente che abbiamo visto e dal quale si ricava il file linkabile. Nel complesso, i file generati saranno tre:

Random.def
Random.imp
Random.lib

Per utilizzare il modulo Random il compilatore necessita del modulo di definizione e del file linkabile; il sorgente del modulo di implementazione non è necessario.

Esempio 8: Qualificazione dei nomi

Pensiamo a quanti moduli potrebbero desiderare di definire una funzione di nome ``open()''. Ad esempio, il modulo io esporta la funzione open() che apre un file nella modalità non bufferizzata; il modulo win esporta una funzione con lo stesso nome che apre una finestra; il modulo pg apre una connessione con il DBMS PostgreSQL; infine, il modulo net esporta una funzione che apre una connessione di rete TCP. Una applicazione articolata può desiderare di utilizzare parecchi moduli, sicchè si presenta il problema della collisione dei nomi. M2 risolve la situazione con il meccanismo della qualificazione dei nomi. Un nome qualificato è costituito dal nome di un modulo, seguito dal punto, seguito dal nome dell'oggetto esportato da quel modulo. Esempio:

MODULE DBClient

IMPORT io, win, pg, net 

VAR
    f: FILE
    window: win.Window
    pg: pg.Connection
    socket: INTEGER

BEGIN
    io.open(f, "data", "r")
    window = win.open(display, screen, 0, 0, 200, 150)
    pg     = pg.open("masterdb")
    socket = net.open("localhost", 110)
    ...
END

L'identificatore FILE viene esportato solo dal modulo io e pertanto lo possiamo usare non qualificato senza ambiguità.

Invece esistono varie open() esportate da tanti moduli diversi: qui la qualificazione si rende necessaria per risolvere l'ambiguità. Se non avessimo usato la qualificazione il compilatore avrebbe segnalato il problema e l'elenco dei moduli che esportano l'identificatore in questione.

Il meccanismo della qualificazione si applica anche alle costanti, alle variabili e ai tipi. La qualificazione è obbligatoria solo in caso di ambiguità. Al contrario, la qualificazione può essere usata anche quando non strettamente necessaria quando ciò possa contribuire a migliorare la comprensibilità del sorgente.

Come proseguire

Per provare gli esempi, e per costruire i tuoi programmi, avrai bisogno del compilatore e delle librerie di M2. Vai nella sezione download e segui le istruzioni.

La descrizione completa del linguaggio si trova nel report.

Per sviluppare programmi più articolati avrai bisogno delle librerie di M2. La sezione library elenca tutte le librerie disponibili disponibili nel pacchetto di installazione.

Nella sezione applications trovi alcune applicazioni realizzate in M2.



Umberto Salsi
Contatto
Mappa
Home / Indice sezione