1.1        Sintassi e morfologia dei linguaggi di programmazione

I linguaggi di programmazione sono un sottoinsieme dei linguaggi possibili e come i linguaggi naturali, sono formati da parole e frasi, che seguono regole di formazione il cui studio è l’oggetto della morfologia e della sintassi. Nei linguaggi di programmazione le parole sono le variabili, le costanti, gli operatori ed i comandi, queste unite fra di loro secondo certe regole sintattiche formano le istruzioni[1].

In un linguaggio di programmazione la morfologia studia le regole di formazione delle parole e la sintassi le sequenze accettabili delle stesse.

Non è possibile individuare una struttura generale per tutti i linguaggi di programmazione, le limitazioni delle macchine (per i linguaggi più antichi), le caratteristiche estetiche, la ricerca della leggibilità, l’ambizione della robustezza, l’utilizzo principale ed infine la fantasia dei progettisti hanno prodotto le soluzioni più variegate. Fra i linguaggi a struttura “rigida”, è largamente utilizzata la struttura che prevede:

dichiarazione dei moduli utilizzati

dichiarazione del nome del programma

dichiarazione di variabili e costanti

istruzioni del programma

La struttura dei primi linguaggi di programmazione è stata condizionata dai mezzi tecnici disponibili, in particolare dalla lentezza dei calcolatori e dalla scarsità di memoria; i sorgente dei programmi erano generalmente perforati su schede o su nastri di carta. Per questi motivi i linguaggi più famosi e più “anziani” come COBOL e FORTRAN, avevano, in origine, una struttura fissa, poiché questa semplificava, sebbene marginalmente, i compilatori, permettendo di individuare facilmente i riferimenti nel programma, i commenti e la continuazione delle istruzioni.

La leggibilità di un linguaggio è funzionale alla manutenzione dei programmi ed alla documentazione degli stessi; essa è stata esplicitamente ricercata in ADA e COBOL.

1.1.1                Le parole

La distinzione fra le parole è di norma effettuata da spazi ed é perlopiù accettata la tabulazione, il fine riga ed un numero variabile di spazi fra le parole[2]. In MUMPS lo spazio (uno e talvolta due) separa solo le istruzioni o il comando dai suoi operandi, ma non è ammesso nelle espressioni aritmetiche o di confronto.

Naturalmente ci sono linguaggi che permettono l’eliminazione degli spazi fra elementi sintattici ben distinguibili, come nel caso delle espressioni, ed altri in cui essi sono obbligatori.

La fine di una frase è data dalla fine riga, o da un carattere di interpunzione se il linguaggio accetta più frasi sulla stessa linea; il fine frase è talvolta obbligatorio, come in C, ma la tendenza nei linguaggi più evoluti è di richiederlo solo quando strettamente necessario, tant’è che in alcuni linguaggi la frase è considerata completa quando è sintatticamente finita ed è riconoscibile l’inizio della frase successiva.

Un linguaggio come APL, avendo istruzioni formate da simboli non alfanumerici, di fatto non ha necessità di separatori di parola.

I nomi per le variabili vanno dalla più ampia libertà del Forth, in cui qualsiasi carattere diverso da un white space è un nome valido, alla limitazione di nomi formati da caratteri alfanumerici più eventuali altri simboli e con l’imposizione che il primo carattere sia alfabetico. In quanto alla differenziazione fra lettere maiuscole e minuscole, ci sono linguaggi in cui questa è ignorata, altri in cui è significativa (C) e talvolta con implicazioni semantiche come in Haskell ed in Prolog.

La lunghezza dei nomi di variabile va da 1 (Laning and Zierler System) ai 6 del FORTRAN originario, fino a lunghezze che praticamente non pongono limiti alla significatività del nome.

1.1.2                Blocchi di istruzioni

Nelle istruzioni di controllo si deve poter individuare il blocco di istruzioni che l’istruzione condiziona, in alcuni linguaggi l'inizio è l'istruzione stessa, la fine è segnalata in vari modi: punto “.” in Cobol, parole riservate con predominio di END o NEXT. In molti linguaggi il blocco è delimitato esplicitamente o da una coppia di parentesi graffe {} o dagli insiemi DO ... END (T3X). Anche la coppia [] è utilizzata per determinare i blocchi, questa è in genere una scelta dei linguaggi per trattre liste (LISP) o basati su liste (LOGO, REBOL). In Python l’inizio del blocco è individuato da “:” è le istruzioni appartenenti al blocco sono indentate, il primo linguaggio ad utilizzare questa tecnica, detta  sintassi bidimensionale o layout, è stato, probabilmente, MADCAP (1960). Fra gli altri linguaggi a sintassi bidimensionale Haskell e MUMPS in cui l’indentazione è indicata con uno o più punti prima delle istruzioni..

Algol è stato il riferimento per la coppia BEGIN ... END. Da segnalare la chiusura del blocco speculare con l’inizio: IF ... FI e DO ... OD.

Molti linguaggi hanno marcatori di blocco differenziati per tipo di blocco (Basic, Forth), in particolare nel Forth, data la sua struttura postfissa, il marcatore iniziale è l’istruzione stessa: DO, IF ecc, ed i termine blocco sono rispettivamente LOOP e THEN.

1.1.3                Assegnazione

Nella maggior parte dei linguaggi il simbolo = è una componente dell’operatore che assegna il risultato di un’espressione ad una variabile, ma ciò ha posto il problema se utilizzare o meno lo stesso simbolo anche per l’operatore di confronto. Anche in questo caso ci sono linguaggi in cui tale distinzione non esiste, ed altri in cui è presente. E’ stata utilizzata spesso la parola chiave LET variabile = espressione, (PROFAN2, HASKELL, Basic, ora opzionale) e talvolta SET (FOCAL, JOSS, MUMPS, Tcl). In COBOL l’assegnazione ha la forma COMPUTE VARIABILE = ESPRESSIONE, ma l’assegnazione è ottenibile anche con le istruzioni MOVE, ADD, SUBTRACT e DIVIDE. Nei linguaggi funzionali non esistono assegnazioni, ma associazione (binding) di valori ad oggetti, ed Haskell indica con <- il binding di una variabile con il risultato di una lettura di file, ciò per indicare che le operazioni sui files non sono funzioni, mentre utilizza la sintassi LET nome = nel caso delle espressioni.

BACAIC utilizza il simbolo * per l’assegnazione, essa ha la forma: espressione * variabile; è uno dei rari casi in cui l’espressione è posta a sinistra della variabile, un altro è dato da BETA e gbeta in cui il simbolo è ->. In REBOL l’operatore di assegnazione è : messo a suffisso del nome di variabile: variabile: espressione.

Nella Figura sottostante è riportato un minuscolo campionario dei simboli usati nell’assegnazione.

Linguaggi

Assegnazione

Uguale

Diverso

ADA

:=

=

/=

BACAIC

*

 

 

Basic

=

=

<>

BETA, gbeta

->

 

 

Pascal

:=

=

<>

C

=

==

!=

J

:=, =.

=

 

MUMPS

=

=

'=

REBOL

:

=

 

Nel C (e in diversi altri linguaggi) sono presenti assegnazioni, che sono scorciatoie per istruzioni molto frequenti come l’incremento/decremento di una variabile, una semplice operazione aritmetica o l’assegnazione condizionata:

   /* incrementi e decrementi y inizialmente contiene 2*/

   x++;             /* x = x + 1; */

   ++x;             /* x = x + 1; */

   z = y++;         /* z = 2; y = 3 */

   z = ++y;         /* z = 5; y = 4 */

   y--;             /* y = y + 1; */

   --y;             /* y = y + 1; */

   /* operatori aritmetici di assegnazione */

   a += 13;         /* a = a + 13; */

   a *= 3.1415;     /* a = a * 3.1415; */

   a -= b;          /* a = a – b; */

   a /= 10.0;       /* a = a / 10; */

   /* assegnazione condizionata */

   c = (a > b ? a : b);   /*   if (a > b) c = a;

                               else c = b;       */

Tuttavia questa soluzione produce un codice più efficiente in quanto la variabile è valutata una sola volta:

Molti linguaggi permettono assegnazioni multiple, simili nella forma a:

var1, var2, ..., varn = espr1, espr2, ... esprn oppure

var1, var2, ..., varn = espr1

Con il primo tipo di assegnazione è possibile lo scambio di valori fra due variabili.

Un caso particolare di assegnazione è il risultato restituito da una funzione:

Molto diffusa l’assegnazione condizionale:

1.1.4                Istruzioni condizionali

La struttura alternativa, quasi sempre indicata con IF, va dalla forma più semplice:

IF condizione comando

alla più sofisticata struttura:

IF condizione THEN comando ELSEIF comando ... ELSE comando

In Ruby si può scrivere unless condizione ... invece di if not condizione ....

La necessità di individuare condizione e comando è stata risolta o con racchiudere fra parentesi  condizione (i linguaggi ispirati alla sintassi del C, FORTRAN) o con far precedere comando da un marcatore di blocco: THEN in BASIC : in Python, spazio in MUMPS.

In COBOL è utilizzato sia ELSE che OTHERWISE per introdurre le istruzioni da eseguire se condizione è falsa.

Esiste anche la differenziazione fra istruzioni condizionali semplici ed estese, per evitare possibili ambiguità nel linguaggio: in T3X IF è l’istruzione condizionale semplice, IE è la struttura che accetta il successivo ELSE, in REBOL le due strutture sono rispettivamente IF condizione [comandi] e EITHER condizione [comandi per vero] [comandi per falso].

Alcuni linguaggi (MUMPS, Ruby) prevedono la struttura:

comando IF condizione

In MUMPS ELSE è un’istruzione indipendente dall’IF, ed equivale a IF $TEST’=1 (' è l’operatore di negazione e $TEST la variabile contenente il risultato dell’ultima espressione booleana eseguita).

In RPG, scritto su schede con tracciato fisso, si potevano indicare fino a tre variabili booleane per condizionare l’istruzione contenuta sulla scheda.

1.1.5                Costanti

Costante

Linguaggio

QUOTE

COBOL

ZERO, ZEROES

COBOL

ONE

Scala

TRUE, FALSE

Basic

Le stringhe di costanti sono individuate da delimitatori, in genere " o ' o entrambi, in genere i numeri non sono delimitati. Nel caso delle stringhe di l’introduzione di caratteri particolari quali i delimitatori o quelli non stampabili, si effettua con un carattere di escare, seguito dal carattere voluto o dalla sua rappresentazione numerica. Alcuni linguaggi prevedono la ripetizione del delimitatore (Basic).

Alcune costanti particolari possono essere predichiarate, vedere esempi a lato.

La dichiarazione di una costante variano dal premettere la parola CONST davanti al nome (Basic), all’utilizzare un particolare livello nel COBOL (il livello 88) all’utilizzo di forme meno immediate, di fatto dichiarazioni di variabili immodificabili (final di JAVA e INV di Pearl).

1.1.6                Commenti

Sin dai primi linguaggi di programmazione i commenti sono stati un mezzo di documentazione dei programmi. Uno dei più sofisticati sistemi di commenti è quello di COLASL (1962), in esso tutto ciò che non è sintatticamente comprensibile è considerato commento. 

Nei primi linguaggi i commenti, individuati da un carattere in una posizione prefissata, occupavano una linea intera. Esistono linguaggi con commenti a fondo riga, commenti estesi su più linee, molto utilizzato /* ... */, e commenti all’interno di un’istruzione (FORTH, T3X).

Commento

Linguaggi

 

*
COBOL
In colonna 7, riga a se stante

'

BASIC, PROFAN2

Fino a fondo riga

REM

BASIC

Riga a se stante

;

REBOL

Fino a fondo riga

/* ... */

C, PHP 

Blocco

#

Python

 

//

C, PHP, COMAL

Fino a fondo riga

NB.

J

 

( commento)

FORTH

Anche all’interno di una riga

!

T3X

 

$

SETL

 

--

Haskell

 

{- commento -}

Haskell

Blocco

(* commento *)

BETA, gbeta

Blocco

%

POSTSCRIPT

Riga

comment commento ;

SIMULA

Blocco

! commento ;

SIMULA

Blocco

In JOSS * in fondo alla riga permetteva di scartare la riga stessa, di fatto può essere considerato un modo di commentare.

Un punto di vista diametralmente opposto è quello di inserire il sorgente dentro un testo, che è in genere una documentazione particolareggiata del programma (literate program) come in Haskell, che accetta sia testi in formato LATEX sia testi in cui le istruzioni si distinguono perché iniziano con >.

In POSTSCRIPT i commenti di tipo %% è’ utilizzata per includere comandi per il postprocessore EPS (Encapsulated PostScript); in JAVA /** sono commenti utilizzati dal programma javadoc per generre pagine HTML di documentazione, tecnica simili di autodocumentazione sono utilizzate da altri linguaggi.

1.1.7                Continuazione

La continuazione di un’istruzione sulle righe successive era necessario nei primi linguaggi per le dimensioni ridotte e fisse delle stesse, attualmente prevale l’esigenza di leggibilità del sorgente.

Alcuni linguaggi non permettono la continuazione, in altri non è necessario alcun simbolo di continuazione (T3X).

Linguaggi

Continuazione

BASIC

_ (underscore)

COBOL

Un carattere in posizione 7 diverso da * o /

FORTRAN

Un carattere in posizione 6 diverso da spazio

LOGO

~ (tilde)

Python

/

1.1.8                Trattamento delle espressioni

1.1.8.1       Espressioni aritmetiche

Nella trattazione delle espressioni aritmetiche e condizionali sono stati utilizzati diversi approcci, inizialmente, si sono accettate espressioni il più possibile simili alla notazione manuale, tramite dispositivi di input appositi (MAC-360, COLASL, MADCAP, MIRFAC, Klerer-May System, Laning and Zierler System), quindi con i segni per radici, integrali, con l’utilizzo di esponenti, subscritti, linee per la divisione e l’omissione del segno di moltiplicazione. Quest’ultima possibilità imponeva l’utilizzo di nomi di variabile di un solo carattere (Laning and Zierler System). In genere furono trattate correttamente le precedenze fra gli operatori, ma non mancarono linguaggi in cui queste erano ottenute solo tramite parentesi.

Si impose abbastanza presto l’utilizzo dei quattro operatori aritmetici classici, della notazione funzionale e del trattamento delle precedenze, a questo proposito si deve segnalare il linguaggio merd (sic) in cui la parentesi è sostituibile dal layout orizzontale: 1+2  * 3 equivale a (1+2) * 3.

La traduzione delle espressioni è una parte complicata del compilatore, tuttavia espressioni complesse sono relativamente rare nei programmi reali, non stupisce quindi che alcuni linguaggi, anche recenti e per certi aspetti evoluti, adottino o l’approccio della notazione postfissa come FORTH e POSTSCRIPT o accettino solamente funzioni (PROFAN2) o non ammettano precedenze fra gli operatori, se non quelle indicate dalle parentesi (REBOL, tuttavia gli operatori hanno la precedenza sulle funzioni).

Linguaggio

Espressione

Forth

c 2 ^ b * a +

PROFAN2

@ADD(a%,@MUL(b%,@POW(c%,2)))

Laning and Zierler System

a + bc2

1.1.8.3       Operatori e simboli utilizzati

Linguaggio

Moltip.

Elev.

And

Or

Non =

BACAIC

·

 

 

 

 

BASIC

*

^

And

Or

<>

FORTRAN

*

 

.AND.

.OR.

.NE.

IT

x

 

 

 

 

T3X

*

 

/\

\/

\=

C

*

**

&&

||

!=

L’evoluzione dei linguaggi ha portato ad una generale convergenza nell’adottare simboli comuni per gli stessi operatori. In genere gli operatori aritmetici + e sono stati universalmente adottati, tuttavia il segno negativo, generalmente è ~ in ML e % in T3X. Qui accanto un piccolo campionario di simboli.

In genere esistono gli operatori logici not, and e or, non è raro che siano previsti l’implicazione e l’equivalenza.

1.1.9                Rumori, sinonimi e syntactic sugar

Col termine Rumore (Noises o Cosmetics) si indicano quelle parole non necessarie al linguaggio ma che ne facilitano la leggibilità, un esempio è l’istruzione di assegnazione LET, del Basic e di altri linguaggi. Lo Short-Range Committee incaricato di stendere le specifiche del COBOL, già nel settembre 1959 prevedeva espressamente i noises, ad esempio la clausola AT END ..., l’istruzione MOVE ALL ZERO TO ....

I sinonimi sono parole intercambiabili, come ELSE e OTHERWISE in COBOL; talvolta ciò è dovuto all’evoluzione del linguaggio e per mantenere la compatibilità con le versioni precedenti.

I syntactic sugar o syntactic flavor sono facilitazioni nella scrittura dei programmi quali variabile++; in alternativa a variabile = variabile + 1; o la possibilità di scrivere una lista come [5,1,2,3,4] invece di 5:1:2:3:4:[] (Haskell).

Un'altra facilitazione di scrittura, presente in diversi linguaggi che contengono strutture complesse come oggetti e record in cui i nomi sono qualificati, è di individuare un blocco in cui si omette il qualificatore, come la clausola with di Visual Basic e Pascal o la clausola inspect di SIMULA.



[1] La terminologia adottata generalmente non è quella linguistica tradizionale, fa eccezione il linguaggio J (v. J par. 2.37 ), in cui i nomi sono le costanti, i pronomi le variabili, i verbi le funzioni, gli avverbi le funzioni su funzioni.

[2] Spazi, tabulazioni, carriage return, line feed e form feed sono spesso indicati col termine white spaces.