by M.Andreoli, (C) 1997 (andreoli@pisoft.it)
Significato delle opzioni:
g48 traduce sorgenti scritti in C in codice RPN, proprio dei calcolatori tascabili HP48G/X.
Dev'essere chiaro che il C riconosciuto da g48 e soltanto un'imitazione del C, il cui scopo principale non e' la scrittura di software compatibile ANSI-C, quanto piuttosto quello di facilitare per quanto piu' possibile la programmazione RPN, appoggiandosi ad una sintassi piu' malleabile. Tutte le volte che e' stato necessario scegliere tra il rispetto del C in quanto tale e un'implementazione efficiente dell'RPN, si e' preferita l'ultima soluzione.
Per ottenere questo risultato, il C standard e' stato notevolmente esteso, con l'aggiunta dei numeri complessi, delle liste, degli switch non interi, istruzioni RPN in-line, etc.
L'intero pacchetto consiste soltanto di due file: parser.y, contenente le direttive YACC + un minuscolo main(), e lexer.l, contenente le direttive LEX ed e' stato scritto e testato sotto Linux 2.0.0.
Sebbene senza molte pretese, g48 fa parte a tutti gli effetti della popolosa famiglia di pacchetti che traducono da un linguaggio all' altro (vedi f2c, b2c, e via di seguito), e con qualche semplice modifica potrebbe facilmente tradurre per altre marche di tascabili con porta seriale (tipo HP28S).
Il programma usa come I/O lo stdin e lo stdout, non apre nessun file addizionale e non presuppone hardware di nessun tipo, per cui il porting dovrebbe essere immediato anche sotto DOS.
Per comprendere il comportamento di g48 ( soprattutto quando, portato l'eseguibile su HP48, i risultati sono diversi da come ci si aspetta ) e' necessaria da parte dell'Utente una conoscenza anche sommaria delle particolarita' dell'RPN dell'HP48G, in special modo il funzionamento dello stack, sul quale si base tutta la gestione della chiamata e dell'uscita dalle funzioni.
E, mi raccomando: benche' sia cosa estremamente naturale per il programmatore C, non usare la variabile 'i' nei cicli for: le iterazioni nel campo dei numeri immaginari porterebbero a risultati imprevedibili!
Com'e' noto, l'HP48G e' una macchinetta tascabile dalle capacita' matematiche (sia simboliche che numeriche) veramente notevoli: numeri complessi, integrali, derivate, eq. differenziali, calcoli statistici, distribuzioni statistiche, sistemi di equazioni non lineari, tutta l'algebra lineare (inversione, autovalori e autovettori, decomposizioni LU, Schur, ...), 12 tipi di grafici diversi (2d, 3d, polari, ...), protocolli XMODEM, KERMIT, etc. etc. Non e' esagerato affermare che, se non fosse per le ridotte capacita' di calcolo, l'HP48G potrebbe rivaleggiare con Mathematica della Wolfram Research!!
Ora, l'HP ha un piccolo visore e una tastierina che non sono certo il massimo per introdurre e testare programmi in RPN, senza parlare della scarsa leggibilita' di questo tipo di codice. Per ovviare a questo problema e poiche' non mi e' mai sembrato razionale lasciare inutilizzata questa meraviglia, ho pensato di scrivere un programma che girasse su LINUX capace di tradurre programmi scritti nella sintassi che e' piu' nota al grande pubblico di questo sistema operativo: la sintassi del linguaggio C, o C-like.
g48 e' uno snello e agile software, costituito in effetti da poche centinaia di righe, interamente basato sulla celebre accoppiata YACC&FLEX. Quanto sia davvero "agile" l'Utente lo scoprira' non appena compilera' qualcosa: la risposta e' praticamente istantanea! La sintassi ammessa e' quella del C, con qualche ovvia eccezione per quanto riguarda le routines dell'I/O, ed altre. Ma si tratta di limitazioni di scarso rilievo, potendosi usare facilmente i potenti comandi di input/output propri di HP, implementabili in opportune librerie da #include-re. Il software e' corredato, inoltre, da una routine in PERL per la comunicazione LINUX-HP tramite porta seriale, basata sul protocollo di trasferimento KERMIT, capace di trasformare LINUX in un client HP a tutti gli effetti.
A dire il vero non so a quanti potra' mai interessare un software dal target cosi' limitato, ma chiunque abbia perduto qualche ora a cercare le lettere dell'alfabeto su una tastiera HP potra' senza'altro apprezzare l'utilita' di questo piccolo programma!
Elementi lessicali di base
Sono ammessi tutti gli elementi lessicali disponibili in HP48: nomi, numeri interi e reali (formato FORTRAN), binari HP #...., numeri complessi (x,y), array [ [...] [...] ...], liste { ...{..} ..}, stringhe "...", elementi quotati '...' (espressioni algebriche HP), programmi HP \<<...\>>.
Questa prima categoria di elementi viene semplicemente riconosciuta e introdotta nelle espressioni senza nessuna traduzione particolare. I nomi ( a differenza del C) possono contenere anche caratteri escape (introdotti col la barra \) che, com'e' noto, HP utilizza per l'alfabeto greco etc.
In aggiunta sono ammesse espressioni in-line (per cosi' dire) del tipo `...`, con i caratteri di back-quote, ad imitazione delle shell di UNIX. Questo tipo di elemento puo' essere utilizzato in vario modo, tutte le volte che si vuole introdurre direttamente codice in notazione RPN, invece che algebrica.
Gli spazi e i new_line vengono sistematicamente ignorati, per cui non e' necessario nessun carattere di continuazione-linea. E possibile interrompere la linea ovunque, perfino in una stringa. Questo rende la scrittura dei programmi piuttosto libera.
Elementi sintattici di base Allo stato attuale, g48 non riconosce tutte le istruzioni del C, ma soltanto quelle ritenute in prima stesura "urgenti": dichiarazioni di funzioni, di variabili (ignorate), operazioni di casting (ignorate), assegnamenti, chiamate a funzioni, ciclo for e while, switch, if-then-else, if ternario, commenti in formato C, UNIX e HP, oltre a tutte le operazioni aritmetiche e logiche, gli operatori ++ e --, nonche' gli assegnamenti aritmetici del +=, -=, etc..
La traduzione avviene passo passo, nella maniera tipica degli interpreti, piu' che dei compilatori: le espressioni aritmetiche e logiche e le chiamate a funzioni vengono tradotte in notazione polacca inversa e introdotte man mano nell'output ( e corrispondentemente nello stack di HP):
Es: 1+x^2 -> 1 x 2 ^ +
f(x) -> x f
x && (y || (!z)) -> x y z NOT OR AND
Tutte le istruzioni rilasciano il loro risultato nello stack, per cui l'istruzione return del C non e' necessaria. In compenso, l'output delle funzioni puo' ora essere strutturato a piacere, per poi essere raccolto eventualmente col comando DROP ( una specie di pop).
Grazie a quest'idea gli assegnamenti sono stati estesi grandemente rispetto al C: e' possibile assegnare anche a liste, mediante istruzioni del tipo:
{'A','B','C'}=f(x,y,{a,b,c},"...",..);
In quest'esempio, le variabili A, B e C vengono prelevate dallo stack HP, in un ordine che e' il piu' naturale: la C per prima e la A per ultima.
Lo stesso dicasi per l'istruzione switch: la variabile del case non deve necessariamente essere di tipo integer, ma puo' essere qualsiasi cosa, senza limitazioni.
Le funzioni
La dichiarazione avviene come nei linguaggi non tipizzati (tipo Bourne Shell di UNIX):
f(x,y,z,...)
{
istruzioni;
}
ma anche nel modo ancora piu' semplificato:
f(x,y,z) istruzione;
La specifica dei tipi del C, compreso i puntatori, e' ammessa ma ignorata, per cui negli esempi che seguono viene per semplicita' omessa.
g48 per dichiarazione di funzione intende la memorizzazione nella variabile globale f del relativo codice, nulla di piu', per cui e' possibile costruire funzioni anche mediante assegnamenti diretti del tipo:
f=`\<< \-> x \<< codice \>> \>>`
con, eventualmente, codice variabile a run-time.
Occorre tener presente che, a differenza che nel C, qui i parametri formali sono soltanto una via per utilizzare variabili locali dall'interno del nostro codice, inizializzandoli dallo stack, ma non sono l'unica maniera per passare input alla funzione (vedi esempio seguente).
La chiamata a funzioni, cioe' l'istruzione piu' importante in un programma HP, viene trattata in maniera piuttosto differente rispetto al C e, in sostanza, estendendo notevolmente la sintassi del C stesso.
Il motivo e' che il numero di parametri passati non e' controllato e non ha quindi relazione con il numero di parametri formali nella dichiarazione. In sostanza: la funzione puo' anche non essere stata dichiarata affatto, ed e' questo il caso delle funzioni built-in.
Uno statement del tipo f(g(h(x,y,z))) viene correttamente implementato in notazione postfissa come: x y z h g f ; analogamente f(`x y z`) viene tradotto in x y z f.
I valori passati possono essere di qualsiasi genere, anche comandi e funzioni. Nell'esempio che segue PICTURE e' un comando che non necessita di parametri di input, al pari di altri comandi grafici di HP quali FUNCTION, LABEL e DRAX: Un instruzione quale:
PICTURE( XRNG(-1,4),FUNCTION,INDEP('x'),LABEL,DRAX);
viene tradotta nella forma:
-1 4 XRNG FUNCTION 'x' INDEP LABEL DRAX DRAW PICTURE
cioe' come un segmento di codice RPN autosufficiente.
Se si vuole scrivere una funzione (o utilizzarne una built-in, dallo stesso comportamento) che prelevi l'input direttamente dallo stack, senza far uso di parametri formali, si puo' procedere come nell'esempio:
f()
{ x=DROP; y=DROP; etc; }
f(x,y,z,...); /* oppure f(`x y z`) se si preferisce */
Volendo, e' possibile scrivere programmi interamente fatti di codice RPN, che verra' spedito su HP senza particolari traduzioni. Es:
`
ERASE PICT NEG
PICT {#0 #0}
GROB 5 5 11A040A011 GXOR
LASTARG GXOR
` ;
Notare la presenza del punto e virgola finale: e' pur sempre un'istruzione C!
Variabili globali e locali
Tutte le variabili usate sono intese come variabili globali HP, ad eccezione di quelle formali nelle dichiarazioni di funzioni, le quali effettivamente sono visibili solo all'interno delle funzioni stesse.
Utilizzando accortamente le direttive C tipo #define o #include e' possibile simulare il comportamento di costanti e macro locali , cioe' che non compaiano nel menu VAR di HP alla fine dell'esecuzione.
Queste direttive, tipiche del C, non vengono espanse direttamente da g48 (per esigenze di compatibilita' e di porting ) ma vanno preprocessate mediante il comando:
gcc -E source.c |
e passate in pipe a g48, a cura dell'Utente.
Con un minimo di lavoro supplementare e' possibile organizzare una serie di #include per gestire con HP I/O, grafica, etc.
Struttura dei sorgenti
A differenza di gcc, g48 compila anche senza una specifica funzione main(), perche' anche le dichiarazioni di funzione sono implementate in realta' come statement eseguibili. Ne consegue che le dichiarazioni possono essere alternate liberamente con i comandi veri e propri. Cio' ovviamente non pregiudica la possibilita' di definire una funzione main() in ogni caso, purche' ci si prende cura di chiamarla, da qualche parte!
Se non si vuole che le variabili create dal programma sopravvivano al programma stesso, conviene effettuare alla fine un PURGE( {x,y,z,...}). E' anche buona norma salvare all'inizio dell'esecuzione lo stato dei flag dell'HP in una variabile, per poi ripristinarlo al termine:
#define ...
#include ...
status=RCLF;
f(x,y,z)
{
istruzioni;
}
g(x) x++; f(1.2,"come?",(2,3));
STOF(status);
PURGE( variabili ...);
/* end program */
Inutile dire che i comandi propri di HP vanno impostati correttamente in maiuscolo, fatto che migliora la leggibilita' di questo tipo di programma.
Un ultima cosa: abbondate pure con i commenti. Non vengono trasferiti sull'HP!
Poiche' l'HP48G/X e' in grado di applicare le 4 operazioni praticamente a tutti tipi di dati possibili, risulta che l'Algebra riconosciuta da g48 e' grandemente piu' ampia rispetto a quella del C. Quello che segue e' soltanto un breve riassunto di quanto l'HP puo' fare; per maggiori delucidazioni, consulatare la Documentazione relativa alla macchina stessa.
(1,2)*(3,4)
EXP(2*\pi*i)
etc..
A*B-INV(C)+4*D^2;
A=[ [1,2],[3,4]] ^2
etc.
{1} + 2 -> {1,2}
1+{2} -> {1,2}
HEAD({1,2}) -> 1
etc.
"sembra" + " incredibile!" -> "sembra incredibile!"
HP48G Series, Advanced User's Reference Manual
Si tratta di una versione del tutto avventurosa, per cui g48 resta pieno di incognite.
- Il difetto piu' grave e' nella messaggistica: in caso di errore di sintassi il compilatore semplicemente si blocca con un magnifico "sintax error" e non c'e' assolutamente modo di sapere in che punto, se non ispezionando uno stderr dalla lettura in verita' piuttosto criptica (optione -t).
- Le operazioni di shift bit-a-bit del C non sono state ancora implementate, perche' non hanno un diretto equivalente in RPN.
- Non e' stato completamente testato il corretto funzionamento della precedenza in alcuni operatori binari del C. Se si sospetta che il problema sia questo, forzare la precedenza giusta abbondando con le parentesi ().
- I commenti C non devono essere nidificati e non possono contenere il simbolo / (sic!!).
- Manca un operatore virgola in senso stretto.
- Lo switch implementato ha un break implicito. Che dire? Miglioriamo questo programma (se e' utile).