Home / Indice sezione
 www.icosaedro.it 

M2 Report

<= M2 per programmatori CRappresentazione dei dati in memoria =>
Frontespizio
Introduzione
Caratteristiche
Un semplice esempio
Moduli
Sezione IMPORT
Sezione CONST
Sezione TYPE
Tipo FORWARD
Sezione VAR
Sezione FUNCTION
Sezione BEGIN
Stringhe letterali
Sottostringhe
Commenti
Costanti predefinite
Variabili predefinite
Funzioni predefinite
Istruzione di assegnamento
Istruzione per la chiamata di funzione
Ordine di valutazione degli argomenti attuali
Istruzione IF
Istruzione SWITCH
Istruzione FOR
Istruzione WHILE
Istruzione REPEAT
Istruzione LOOP
Istruzione TRY
Istruzione RAISE ERROR
Istruzione RETURN
Espressioni logiche
Espressioni intere
Espressioni reali
Espressioni stringa
Espressioni tra tipi strutturati
Regole di scope
M2 per programmatori C
M2 per programmatori Java
Rappresentazione dei dati in memoria
Keywords
Messaggi di errore a runtime
Sintassi
 

M2 per programmatori Java

Il compilatore. Il compilatore M2 produce un sorgente in linguaggio C che viene a sua volta compilato in codice macchina direttamente eseguibile. Siccome tutti i sistemi operativi dispongono di un compilatore C, la portabilità del compilatore M2 sui vari sistemi è assicurata.

Integrazione con il linguaggio C. Inoltre in M2 è facile integrare porzioni di codice C dentro al codice M2. Si possono creare anche moduli interamente in C utilizzabili da M2. Siccome molte librerie di utilità sono scritte in C, esse si possono integrare rapidamente in un modulo di interfaccia M2.

Classi in M2. M2 è un linguaggio procedurale basato sulla suddivisione dei programmi in moduli e funzioni. M2 non supporta classi e oggetti. Ciò non impedisce di applicare almeno alcuni dei principi della OOP anche ai programmi M2. Ad esempio, possiamo rappresentare i campi della classe con i campi di un RECORD; gli oggetti sono istanze di questi RECORD; i metodi sono funzioni che agiscono su questi oggetti. Un esempio:

class Automobile {

    String marca;
    String modello;
    int anno_immatricolazione;
    int km_percorsi;

    void Automobile(String marca, String modello, int anno_immatricolazione,
        int km_percorsi)
    {
        this.marca = marca;
        this.modello = modello;
        this.anno_immatricolazione = anno_immatricolazione;
        this.km_percorsi = km_percorsi;
    }
}

...

Automobile a;

a = new Automobile("FIAT", "Duna", 1990, 15000);

Questa classe, che potrebbe rappresentare un catalogo di automobili usate, può essere riprodotta in M2 come un RECORD e una FUNCTION:

TYPE
    Automobile = RECORD
        marca: STRING
        modello: STRING
        anno_immatricolazione: INTEGER
        km_percorsi: INTEGER
    END

FUNCTION NewAutomobile(VAR this: Automobile, marca: STRING, modello: STRING,
    anno_immatricolazione: INTEGER, km_percorsi: INTEGER)
BEGIN
    this[marca] = marca
    this[modello] = modello
    this[anno_immatricolazione] = anno_immatricolazione
    this[km_percorsi] = km_percorsi
    RETURN this
END

...

VAR a: Automobile

NewAutomobile(a, "FIAT", "Duna", 1990, 15000)

In M2 la punteggiatura è ridotta al minimo. Non esiste il carattere di terminazione delle istruzioni ";".

Allocazione automatica degli oggetti. In M2 non esiste il costrutto new per la creazione di nuovi oggetti. Assegnando un elemento di un array o un campo di un record, l'oggetto viene allocato automaticamente. Nell'esempio, la variabile a ha il valore iniziale NIL, e poi viene allocata dalla funzione NewAutomobile(). In M2 tutte le variabili sono rimosse dalla memoria quando non sono più raggiungibili, esattamente come in Java.

In M2 i campi di una classe (cioè i campi di un RECORD) sono sempre public. L'istanza di un oggetto viene creata automaticamente quando si assegna almeno un campo. Pertanto in M2 non esiste una istruzione new e un nuovo oggetto della classe Automobile può essere creato indifferentemente chiamando la funzione NewAutomobile() oppure assegnando un campo qualsiasi alla variabile a:

a[marca] = "FIAT"

In questo caso i campi non assegnati assumono il valore default. Il valore default è zero per i numeri e NIL per STRING, RECORD e ARRAY. NIL corrisponde al null di Java.

Incapsulamento. I qualificatori static, final, private, protected sono implementati in M2 attraverso concetti leggermente diversi. I valori static dovranno essere specificati come variabili esterne al RECORD:

VAR variabileStatic: UnTipo

e i campi static final come costanti:

CONST nomeCostante = UnValore

Le entità private di una classe si possono implementare attraverso il meccanismo dei moduli. Il modulo di interfaccia dichiara le entità pubbliche, mentre il modulo di implementazione dichiara le entità private.

I "package". I package di Java si possono assimilare ai moduli di libreria M2. Un modulo di libreria si compone di due file: il DEFINITION MODULE e l'IMPLEMENTATION MODULE.

  • Il DEFINITION MODULE definisce l'interfaccia del modulo, cioè le costanti, i tipi le variabili e le funzioni disponibili per i moduli clienti (public). Questo modulo è solo un testo, spesso utilizzato per riportare le istruzioni per l'uso del modulo stesso.

  • L'IMPLEMENTATION MODULE definisce le costanti private, i tipi privati, le variabili private, le funzioni private e il codice che implementa le funzioni esportate nel modulo di definizione. Questo modulo viene compilato, e il sorgente non è più necessario per l'uso del package.

In M2 il tipo speciale FORWARD permette di dichiarare tipi opachi accessibili solo dai metodi della classe. I campi dell'oggetto si potranno accedere attraverso appositi metodi get/set appositamente predisposti.

Questo è un esempio di DEFINITION MODULE per il "package" AutoPackage. Questo file deve avere il nome AutoPackage.def:

DEFINITION MODULE AutoPackage

    (* CLASS: Automobile *)

    CONST
        (* static final: *)
        CV = 735.499 (* Watt *)
        HP = 746.000 (* Watt *)

    TYPE
        (* public fields: *)
        Automobile = RECORD
            marca: STRING
            modello: STRING
            anno_immatricolazione: INTEGER
            km_percorsi: INTEGER
        END

    (* methods: *)
    FUNCTION NewAutomobile(VAR this: Automobile, marca: STRING, modello: STRING,
        anno_immatricolazione: INTEGER, km_percorsi: INTEGER)

    (* More classes here *)

END

Il corrispondente modulo di definizione dichiara il codice dei metodi e definisce le entità private del "package":

IMPLEMENTATION MODULE AutoPackage

    (* private finalize: *)
    CONST (* costanti private *)

    TYPE (* tipi privati *)

    (* private static: *)
    VAR (* variabili private *)

    (* public method: *)
    FUNCTION NewAutomobile(VAR this: Automobile, marca: STRING, modello: STRING,
        anno_immatricolazione: INTEGER, km_percorsi: INTEGER)
    BEGIN
        (* stesso codice di prima *)
    END

END

Il modulo di implementazione si scrive nel file AutoPackage.imp e deve essere compilato prima di poterlo usare. Per utilizzare questo package, il modulo cliente lo deve prima importare:

MODULE AutoUsate
IMPORT AutoPackage
VAR a: Automobile
BEGIN
    NewAutomobile(a, "FIAT", "Duna", 1990, 15000)
END

Le collisioni nello spazio dei nomi. M2 risolve il problema delle collisioni nello spazio dei nomi con due meccanismi: la qualificazione dei nomi e la chiamata di funzione postfissa. Tutte le entità esportate da un modulo si possono qualificare. Un nome qualificato è costituito dal nome del modulo che lo esporta e dal nome dell'identificatore separati da un punto. Ad esempio AutoPackage.Automobile individua univocamente l'identificatore Automobile esportato dal modulo AutoPackage.

Nel caso delle funzioni è disponibile una alternativa alla qualificazione, detta notazione postfissa. La chiamata di funzione postfissa si fa con l'operatore "->" (freccina). Si tratta di una convenzione puramente formale che permette di scrivere x->f() invece che f(x), indicando cioè il primo argomento della funzione prima del nome della funzione stessa. Il vantaggio sta nel fatto che la funzione f() viene cercata nel modulo che esporta il tipo della variabile x, e questo permette spesso di evitare la qualificazione nella chiamata dei metodi/funzioni. Ad esempio, se il modulo "Files" e il modulo "Windows" esportano entrambi una funzione "Open()" avremo il problema dell'ambiguità del nome. Vediamo come la si può risolvere usando la qualificazione e la notazione postfissa:

IMPORT Files, Windows, AutoPackage

VAR
    f: FILE
    w: Window
    a: Automobile

BEGIN
    Files.Open(f, "data.txt", "r") (* same as: *)
    f->Open("data.txt", "r")

    Windows.Open(w, "Bar Chart", 400, 300) (* same as: *)
    w->Open("Bar Chart", 400, 300)

    AutoPackage.NewAutomobile(a, "FIAT", "Duna", 1990, 15000) (* same as: *)
    a->NewAutomobile("FIAT", "Duna", 1990, 15000)
END

M2 non supporta l'overloading dei metodi: il nome di una funzione individua univocamente la funzione stessa. Le funzioni dichiarate in un modulo devono avere nomi diversi anche se hanno diversa signature.

M2 non supporta l'ereditarietà, e questo pone un serio limite all'uso di M2 nell'ambito dell'OOP.

Array. Gli array vengono allocati automaticamente quando si assegna un elemento, e la loro lunghezza cresce man mano che si allocano nuovi elementi. L'indice di un array è di tipo intero e parte sempre da zero. Gli elementi si possono assegnare in un ordine qualsiasi. Gli elementi non assegnati assumono il valore default per il loro tipo. In questo piccolo modulo assegnamo alcuni valori agli elementi di un array di stringhe, ordiniamo l'array e poi lo stampiamo:

MODULE Array
IMPORT m2
VAR
    a: ARRAY OF STRING
    i, j: INTEGER
    t: STRING
BEGIN
    a[0] = "una stringa"
    a[1] = "eccone un'altra ancora"
    a[2] = "questa e poi basta"
    FOR i = 0 TO count(a)-2 DO
        FOR j = i+1 TO count(a)-1 DO
            IF a[i] > a[j] THEN
                s=a[i]  a[i]=a[j]  a[j]=s
            END
        END
    END
    FOR i=0 TO count(a)-1 DO
        print("" + i + ") " + a[i] + "\n")
    END
END

Le matrici i cui elementi sono di tipo T si dichiarano come ARRAY OF ARRAY OF T.

Funzioni. Moduli e funzioni sono i principali elementi strutturali dei programmi M2. Le funzioni si possono nidificare: le funzioni più interne vedono le entità dichiarate nelle funzioni che le contengono, e sono sono accessibili solo da queste ultime. Le funzioni nidificate aiutano a organizzare il codice in modo più chiaro perchè evidenziano le relazioni di dipendenza e la visibilità delle funzioni. Inoltre le funzioni nidificate sono utili per semplificare il codice. Questo esempio, tratto da un programma reale (un parser HTML), mostra la struttura nidificata delle funzioni che compongono una porzione del programma:

FUNCTION ParseCode()
    FUNCTION ParseTABLE()
        FUNCTION ParseTX(tag_closing_sym: SYMBOL, tag_name: STRING)
    FUNCTION ParseList(tag: STRING, tag_end: INTEGER)
    FUNCTION ParseDL()
    FUNCTION ParseFORM()
    FUNCTION ParseINPUT()
    FUNCTION ParseLABEL()
    FUNCTION ParseTEXTAREA()
    FUNCTION ParseSimpleCode(simple_tag: STRING, simple_tag_line: INTEGER)
        FUNCTION ParseP()
        FUNCTION ParseB()
        FUNCTION ParseA()
        FUNCTION ParseIMG()
    FUNCTION ParseHX(tag_closing_sym: SYMBOL, tag_name: STRING)
    FUNCTION ParseDIV()
    FUNCTION ParseHR()
    FUNCTION ParseSELECT()
FUNCTION ParseHTML()
    FUNCTION ParseTITLE()
FUNCTION ParseFile(fn: STRING)

Gli argomenti delle funzioni vengono passati usando convenzioni simili a Java: i tipi semplici e le stringhe sono passate per valore, mentre i tipi strutturati (RECORD e ARRAY) vengono passati per indirizzo. In M2 esiste una seconda modalità: il passaggio per indirizzo. Se l'argomento viene passato per indirizzo, la funzione non solo ha accesso al valore del dato, ma può anche impostarne il contenuto. Questa possibilità può essere sfruttata in vari modi: ne vediamo alcuni.

  • Ritornare al chiamante due o più valori. Un esempio è la funzione match_array() tratta dal modulo m2:

    FUNCTION match_array(s: STRING, re: STRING, VAR a: ARRAY OF STRING): BOOLEAN
    

    La funzione ritorna TRUE se il matching della stringa con la espressione regolare ha avuto successo, e nell'array ritorna le sotto-espressioni regolari così matchate.

  • Assicurare che le variabili passate per argomento vengano impostate ad un valore appropriato dopo la chiamata. Un esempio è la funzione Close(f) esportata dal modulo io. Questa funzione non solo chiude il file, ma imposta anche al valore NIL l'oggetto f così impedendo che venga erroneamente utilizzato in seguito.

Istruzioni per il controllo del flusso di esecuzione. Per concludere questa panoramica delle differenze tra M2 e Java, vediamo come si scrivono nei due linguaggi le istruzioni più comuni:

JavaM2
if( i < 0 && ! b ){
    ...
} else if( i > 0 ){
    ...
} else {
    ...
}
IF (i < 0) AND NOT b THEN
    ...
ELSIF i > 0 THEN
    ...
ELSE
    ...
END
for( i=10; i>0; i-- ){
    ...
}
FOR i=10 TO 1 BY -1 DO
    ...
END
do {
    ...
} while( p != null );
REPEAT
    ...
UNTIL p = NIL
 
<= M2 per programmatori CRappresentazione dei dati in memoria =>

Umberto Salsi

Contatto
Mappa
Home / Indice sezione