Oggi vedremo cosa occorre per programmare in COBOL con un Raspberry PI: dal software di compilazione al codice di hello world, egestione GPIO.
Nella scorsa settimana abbiamo pubbllicato un articolo dedicato al retrocomputing con il Raspberry PI, mostrando come installare ed utilizzare un ambiente Fortran. Abbiamo quindi presentato alcuni esempi di codice Fortran, mostrando come fosse semplice ricompilarli per ARM. Dobbiamo ammettere che l’articolo ha suscitato l’interesse di diversi lettori, tra i quali i miei “vecchi” colleghi di programmazione Attilio e Paolo. Con loro il sottoscritto ha condiviso il periodo di sviluppo su mainframe in COBOL, e più scherzando che seriamente, entrambi mi han chiesto se fosse possibile programmare in COBOL sul Raspberry PI.
La risposta breve è sì, è possibile. Anche se il COBOL era noto per la sua farraginosità, pedanteria e prolissità nella descrizione del codice, l’esatto contrario, quindi, del C, siamo riusciti a trovare un compilatore COBOL per ARM che fa al caso nostro.
Ma andiamo con ordine…
Struttura di un programma COBOL
Chi ha lavorato sui mainframe ricorderà che la struttura di un programma COBOL è strictly typed, e definita nel seguente modo:
- IDENTIFICATION DIVISION. – Una sezione in cui appaiono gli identificatori obbligatori
- IDENTIFICATION DIVISION. serve ad identificare il programma, in essa infatti sono inseriti il nome del programma, l’autore, le date ed altre documentazioni.
- PROGRAM-ID. nomeprog. Il nome programma deve avere una lunghezza massima di 8 caratteri, il primo deve essere alfabetico, non deve contenere spazi e non può contenere caratteri speciali tranne il trattino (-), che però non può essere nè in prima né in ultima posizione.
- Altri identificatori non obbligatori, ma utili per tracciare l’autore del modulo, la data di scrittura, di compilazione e di installazione
- ENVIRONMENT DIVISION. Contiene informazioni sulla configurazione che sarà utilizzata dal programma.
- CONFIGURATION SECTION. La CONFIGURATION SECTION nella quale si definisce il tipo di macchina sulla quale si lavora e si specificano le sezioni che dichiarano particolari funzioni, il periodo DECIMAL-POINT IS COMMA ad esempio, serve per indicare al compilatore che desideriamo sostituire il punto decimale con una virgola in modo che l’interpretazione delle cifre decimali e cifre superiori a 999 unità ci sia più chiara; i compilatori COBOL adottano la notazione anglo-americana e quindi 1.000,00 lo scriverebbero 1,000.00.
- INPUT-OUTPUT SECTION. in cui si definiscono le unità logiche che puntano ai files elaborati dal programma, e soprattutto serve per definire il tipo di file, della modalità d’accesso, delle eventuali chiavi, del FILE STATUS (codice di ritorno che a seconda del valore contenuto ci comunica l’esito dell’operazione eseguita) e del LOCK MODE (modalità di controllo dei files in ambienti multiutente). L’organizzazione dei files (SEQUENTIAL, LINE-SEQUENTIAL, INDEX, RELATIVE), cioè il posizionamento dei records all’interno del file, viene scelta in funzione del modo con cui i records dovranno essere elaborati, mentre L’ACCESSO (SEQUENTIAL, RANDOM, DYNAMIC) varia in funzione del tipo di elaborazione che sarà svolta sul file.
- DATA DIVISION. Descrive i files e le aree di lavoro che saranno utilizzati. La DATA DIVISION si compone di due SECTION obbligatorie e due facoltative:
- FILE SECTION. cui si descrivono i record che costituiscono i file utilizzati dal programma dichiarati nella ENVIRONMENT DIVISION
- WORKING-STORAGE SECTION, nella quale si dichiarano le aree di lavoro
- LINKAGE SECTION necessaria per la dichiarazione delle aree comuni al programma chiamante
- SCREEN SECTION nella quale si dichiarano le aree dello schermo.
- PROCEDURE DIVISION. Contiene le istruzioni ed i comandi che l’elaboratore dovrà eseguire.
Il tutto va codificato secondo una strategia particolare, seguendo un incolonnamento prefissato (ad esempio i commenti vanno preceduti da un asterisco in colonna 7, le righe hanno una lunghezza di 72 colonne, è possibile andare a capo ma solo mediante operatori particolari…)
Per darvi un’idea di cosa fosse scrivere un programma in COBOL, ecco il listato di un programma che esegue la semplice lettura di un file sequenziale a linee:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Lettura di un archivio LINE SEQUENTIAL * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * IDENTIFICATION DIVISION. PROGRAM-ID. PROG-1. ENVIRONMENT DIVISION. CONFIGURATION SECTION. SPECIAL-NAMES. CONSOLE IS CRT DECIMAL-POINT IS COMMA. INPUT-OUTPUT SECTION. FILE-CONTROL. * * al nome interno ANAGRAFICA-MAG associo l'identificatore FILE-1 * a cui sarà assegnato il nome del file su disco * SELECT ANAGRAFICA-MAG ASSIGN TO FILE-1 ORGANIZATION IS LINE SEQUENTIAL ACCESS IS SEQUENTIAL LOCK MODE IS AUTOMATIC STATUS MAG-STAT. DATA DIVISION. FILE SECTION. * * descrizione della struttura dei records di ANAGRAFICA-MAG * FD ANAGRAFICA-MAG. 01 REC-MAG. 02 ARTICOLO PIC X(16). 02 ARTICOLO-FORNIT PIC X(16). 02 DESCRIZIONE PIC X(30). 02 UNITA-MISURA PIC XX. 02 GRUPPO-MERC PIC S9(3) COMP-3. 02 CODICE-IVA PIC 99. 02 SCORTA-MIN PIC S9(7) COMP-3. 02 COSTO-ULTIMO PIC S9(6)V99 COMP-3. 02 COSTO-MEDIO PIC S9(6)V99 COMP-3. 02 PREZZO-VEND PIC S9(6)V99 COMP-3 OCCURS 4. 02 ESISTENZA PIC S9(7)V99 COMP-3. 02 ORDINATO PIC S9(7)V99 COMP-3. 02 IMPEGNATO PIC S9(7)V99 COMP-3. 02 CONFEZIONE PIC 99. 02 CODICE-FORNIT PIC 9999. 02 PROG-CARICO PIC S9(7)V99 COMP-3 OCCURS 4. 02 PROG-SCARICO PIC S9(7)V99 COMP-3 OCCURS 4. 02 COSTO-VENDUTO PIC S9(7)V99 COMP-3. 02 FATTUR-NETTO PIC S9(7)V99 COMP-3. 02 ULTIMO-CARICO PIC 9(8) COMP-3. 02 ULTIMO-SCARICO PIC 9(8) COMP-3. 02 CARICO-ANNUO PIC S9(7)V99 COMP-3. 02 ESIST-INIZ PIC S9(7)V99 COMP-3. 02 ESIST-FINALE PIC S9(7)V99 COMP-3. 02 FILLER PIC X(22). * WORKING-STORAGE SECTION. * * campi per la gestione FILE-STATUS * 01 MAG-STAT PIC XX. 01 VIDEO-1. 02 VIDEO-2 PIC X(12). 01 INDICE PIC 99. 01 FILE-STAT. 02 S1 PIC X. 02 S2 PIC X. 01 STAT-BIN REDEFINES FILE-STAT PIC 9(4) COMP. 01 DISPLAY-STAT. 02 S1-VID PIC X. 02 FILLER PIC X(3). 02 S2-VID PIC 9(4). * * campi contenenti i nomi dei file correnti * la tabella viene ridefinita per indicizzare * la posizione del nome-file * 01 TAB-FILE. 02 FILE-01 PIC X(12) VALUE "MAGAZZ.LSQ ". 02 FILE-02 PIC X(12) VALUE " ". 02 FILE-03 PIC X(12) VALUE " ". 02 FILE-04 PIC X(12) VALUE " ". 02 FILE-05 PIC X(12) VALUE " ". 01 TAB-FIL REDEFINES TAB-FILE. 02 FIL PIC X(12) OCCURS 5. * 01 IN-KEY PIC X. PROCEDURE DIVISION. INIZIO. MOVE FILE-01 TO FILE-1. PERFORM SET-COLOR THRU TITOLO. APERTURA-FILE. OPEN INPUT ANAGRAFICA-MAG. MOVE MAG-STAT TO FILE-STAT. IF S1 NOT = ZERO MOVE 1 TO INDICE PERFORM STATUS-TEST THRU EX-STATUS-TEST. CICLO-LETTURA. READ ANAGRAFICA-MAG AT END GO TO CHIUSURA-FILE. * * qui vanno messe le istruzioni per le operazioni * da eseguire (ricerca/stampa/ecc.) * GO TO CICLO-LETTURA. CHIUSURA-FILE. CLOSE ANAGRAFICA-MAG. FINE-PROG. DISPLAY SPACE. STOP RUN. * * * * Routines * * * * pulisce il video e setta il colore del testo e del fondo * SET-COLOR. DISPLAY SPACE WITH BACKGROUND-COLOR 7 FOREGROUND-COLOR 8. TITOLO. DISPLAY " L E T T U R A D I U N F I L E L I N E S E - " Q U E N T I A L " AT 0108 WITH FOREGROUND-COLOR 9 BACKGROUND-COLOR 7 REVERSE-VIDEO. * * * * * inizio STATUS-TEST * * * * STATUS-TEST. MOVE FIL(INDICE) TO VIDEO-1. DISPLAY "Errore sul File" AT 2303 WITH FOREGROUND-COLOR 4. DISPLAY VIDEO-2 AT 2319 WITH FOREGROUND-COLOR 2. IF S1 = 1 DISPLAY "Fine File" AT 2334 WITH FOREGROUND-COLOR 4 GO TO EX-STAT. IF S1 = 2 DISPLAY "Chiave del record non valida" AT 2334 WITH FOREGROUND-COLOR 4 GO TO EX-STAT. IF S1 = 9 PERFORM TROVA-ERRORE THRU FINE-ERRORE. MOVE S1 TO S1-VID. MOVE LOW-VALUES TO S1. MOVE STAT-BIN TO S2-VID. DISPLAY "Tipo" AT 2403 WITH FOREGROUND-COLOR 4. DISPLAY S2-VID AT 2408 WITH FOREGROUND-COLOR 4. EX-STAT. DISPLAY "Premi un tasto per continuare " AT 2436. ACCEPT IN-KEY WITH AUTO-SKIP. STOP RUN. EX-STATUS-TEST. EXIT. * * * * * fine STATUS-TEST * * * * TROVA-ERRORE. MOVE LOW-VALUES TO S1. MOVE STAT-BIN TO S2-VID. IF S2-VID = 13 DISPLAY "File inesistente" AT 2413 WITH FOREGROUND-COLOR 4. IF S2-VID = 65 DISPLAY "File non disponibile" AT 2413 WITH FOREGROUND-COLOR 4. IF S2-VID = 68 DISPLAY "Record non disponibile" AT 2413 WITH FOREGROUND-COLOR 4. FINE-ERRORE. EXIT. ************************************************************* |
Impressionante, non è vero? E non abbiamo ancora toccato CICS o DB2…
Perché farsi del male in questo modo?
In realtà spesso il diavolo è meno brutto di quanto lo si dipinga. Questo sistema di programmazione era necessario per interfacciarsi con hardware particolari (mainframe) su sistemi operativi particolari (MVS) attraverso processori di comando (JCL) che assumevano il controllo e la gestione di periferiche (initiators, channels. control units, tapes, disks) ciascuna delle quali era gestita da un sistema di controllo autonomo, in modo da evitare collisioni durante le fasi di multiprogrammazione. Eravamo al termine degli Anni Settanta del secolo scorso.
Poco più tardi inizio la rivoluzione informatica, il “personal computer” fece capolino sulle nostre scrivanie, la la Xerox Palo Alto Research Center (PARC) presentò la metafora della scrivania, apparvero i primi sistemi operativi multithread per PC (IBM OS/2) e tutto iniziò a sembrare più semplice.
Tant’è vero che oggi il classico programma hello world in COBOL assume la forma seguente:
Fa quasi tenerezza…
Installiamo il COBOL sul PI
Per installare il compilatore COBOL sul nostro Raspberry, la procedura è semplice.
Innanzi tutto il classico aggiornamento di sistema:
1 2 |
sudo apt update sudo apt upgrade |
Al termine dell’aggiornamento installiamo il nostro compilatore:
1 |
sudo apt install open-cobol |
e controlliamo che tutto sia a posto:
1 2 3 4 5 6 7 8 9 10 |
~$ cobc --version cobc (GnuCOBOL) 2.2.0 Copyright (C) 2017 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Scritto da Keisuke Nishida, Roger While, Ron Norman, Simon Sobisch, Edward Hart Built Jul 17 2018 20:29:40 Packaged Sep 06 2017 18:48:43 UTC La versione di C è "8.1.0" |
E’ giunto il momento di scrivere il nostro primo programma in COBOL sul PI.
Inseriamo il seguente codice nel nostro editor preferito:
1 2 3 4 5 6 7 8 9 |
Identification Division. Program-ID. sampleCOBOL. Data Division. Procedure Division. Main-Paragraph. Display "Hello World!!" Stop Run. |
facendo ben attenzione a posizionare ogni inizio riga a colonna 7, pena la mancata compilazione del codice… Quindi salviamo il programma con hello.cbl, compiliamo con il comando
1 |
$ cobc -x -o hello hello.cbl |
e lanciamo il file eseguibile con
1 2 |
$ ./hello Hello World!! |
Voila: abbiamo creato il nostro primo programma in COBOL!
In realtà, se leggiamo l’ultima riga della risposta al flag –version, ci rendiamo conto di trovarci dietro ad un preprocessore per il C. In altri termini, non abbiamo un vero compilatore, bensì un traduttore da COBOL in C… ma tant’è.
Interfacciarsi con il mondo reale
Ovviamente non sarà sempre così facile… Cosa occorre fare, ad esempio, per gestire il GPIO del PI da COBOL?
Ecco un programma di esempio. Caricate nell editor il sorgente e salvatelo come RPiwithCOBOL.cobol, quindi compilate come al solito.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
IDENTIFICATION DIVISION. PROGRAM-ID. RPIwithCOBOL. DATA DIVISION. WORKING-STORAGE SECTION. 01 WS-GPIO-INIT PIC x(40) VALUE 'echo "21" > /sys/class/gpio/export'. 01 WS-GPIO-DIR PIC x(50) VALUE 'echo "out" > /sys/class/gpio/gpio21/direction'. 01 WS-GPIO-ON PIC x(40) VALUE 'echo "1" > /sys/class/gpio/gpio21/value'. 01 WS-GPIO-OFF PIC x(40) VALUE 'echo "0" > /sys/class/gpio/gpio21/value'. 01 WS-GPIO-ClR PIC x(40) VALUE 'echo "21" > /sys/class/gpio/unexport'. PROCEDURE DIVISION. display "This is COBOL running on a Raspberry Pi". display "Here's how to call GPIO from COBOL". CALL "SYSTEM" USING WS-GPIO-INIT. CALL "SYSTEM" USING WS-GPIO-DIR. PERFORM FLASH-LITE 3 TIMES. CALL "SYSTEM" USING WS-GPIO-CLR. STOP RUN. FLASH-LITE. CALL "SYSTEM" USING WS-GPIO-ON. display "LED is ON". CALL "C$SLEEP" USING 1. CALL "SYSTEM" USING WS-GPIO-OFF. display "LED is OFF". CALL "C$SLEEP" USING 1. END PROGRAM RPIwithCOBOL. |
L’effetto sul monitor dovrebbe essere il seguente:
mentre il LED collegato sul pin 21 inizierà a lampeggiare.
In realtà, chi programma in COBOL si sarà accorto di un potenziale problema: ogni riga di codice del linguaggio inizia a colonna 8 e termiina con un punto misurando al massimo 72 caratteri. Sarà quindi necessario rimaneggiare il nostro programma nel seguente modo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
IDENTIFICATION DIVISION. PROGRAM-ID. RPIwithCOBOL. DATA DIVISION. WORKING-STORAGE SECTION. 01 WS-GPIO-INIT PIC x(40) VALUE 'echo "21" > /sys/class/gpio/exp - 'ort'. 01 WS-GPIO-DIR PIC x(50) VALUE 'echo "out" > /sys/class/gpio/gpi - 'o21/direction'. 01 WS-GPIO-ON PIC x(40) VALUE 'echo "1" > /sys/class/gpio/gpio21 - '/value'. 01 WS-GPIO-OFF PIC x(40) VALUE 'echo "0" > /sys/class/gpio/gpio2 - '1/value'. 01 WS-GPIO-ClR PIC x(40) VALUE 'echo "21" > /sys/class/gpio/unex - 'port'. PROCEDURE DIVISION. display "This is COBOL running on a Raspberry Pi". display "Here's how to call GPIO from COBOL". CALL "SYSTEM" USING WS-GPIO-INIT. CALL "SYSTEM" USING WS-GPIO-DIR. PERFORM FLASH-LITE 3 TIMES. CALL "SYSTEM" USING WS-GPIO-CLR. STOP RUN. FLASH-LITE. CALL "SYSTEM" USING WS-GPIO-ON. display "LED is ON". CALL "C$SLEEP" USING 1. CALL "SYSTEM" USING WS-GPIO-OFF. display "LED is OFF". CALL "C$SLEEP" USING 1. END PROGRAM RPIwithCOBOL. |
Come è facile notare, è sufficiente spezzare le righe che vanno oltre colonna 80, andare a capo, partire dalla riga successiva a colonna 7, inserire il codice di continuazione ‘-‘, lasciare uno spazio, reinserire il codice stringa ‘ e terminare la nostra riga. QUesto per ciascuna delle righe che superano colonna 80, pena un errore in compilazione.
Salviamo il nuovo sorgente ma per compilarlo ci occorre un ulteriore escamotage: dal momento che l’accesso al GPIO (o per essere più specifici, l’accesso alle directory contenenti i system files dei devices necessari per colloquiare con il GPIO) richiede privilegi di root, occorre compilare lanciando sia il compilatore che l’eseguibile creato utilizzando sudo:
1 2 3 |
sudo cobc -x -o RPiwithCOBOL RPiwithCOBOL.cbl sudo ./RPiwithCOBOL |
Ora il sistema è pronto e il programma è compilato correttamente: il programma accederà al PIN 21 esportandolo come system file, determinerà la directory di azione, chiamerà (PERFORM) tre volte la routine FLASH-LITE e chiuderà la sessione con la chiamata di sistema definita in WS-GPIO-CLR. La procedura FLASH-LITE non farà altro che definire un path per i device del GPIO, inviare un valore di on ed uno di off con uno sleep di 1 secondo. Ora avrete un’idea del perché il COBOL viene definito un linguaggio “prolisso”…
Considerazioni finali
Niente male, vero? Certo, in C è più rapido, e in Python più veloce, ma il fascino di gestire il GPIO di un PI via COBOL non ha prezzo!
E anche per oggi abbiamo terminato. Ne è venuta fuori una puntata sostanziosa… Se ne volete altre del genere, scrivete!