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.
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.
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.
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:
RETURN espressione (C, Java, JavaScript, Perl , PHP, Python, Ruby),
QUIT espressione (MUMPS),
RESULTIS espressione (BCPL, VSPL)
nomefunzione = espressione (Basic),
in mancanza di RETURN esplicito, il risultato dell’ultima assegnazione effettuata (Perl).
Molto diffusa l’assegnazione condizionale:
Basic: variabile = iif(condizione,val_x_vero,val_x_falso).
C, PHP, JavaScript variabile = (condizione ? val_x_vero : val_x_falso)
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.
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).
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.
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 |
/ |
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 |
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.
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.