Ingegneria del Software

Progetto di una calcolatrice

Antonio Bonifati, matr. 76515 unical

CALC v1.1
calc.tar.gz

Il formalismo degli ASF

Il linguaggio degli Automi a Stati Finiti (ASF) è un utile modello per la specifica dei requisiti di molti sistemi software (o misti, che hanno parti gestite manualmente e parti gestite automaticamente). Questo formalismo matematico, a volte in pratica usato anche in maniera semi-formale (usando descrizioni di stati e transizioni in linguaggio naturale), è in grado di descrivere il comportamento dei sistemi, relativamente al loro flusso di controllo. Infatti ci può dire come e quando le funzioni di un sistema vengono eseguite in risposta a determinati eventi o azioni che pervengono dal mondo esterno. Troverete una descrizione degli ASF in ogni testo di ingegneria del software, tra cui l'[1]. Nel seguito ne dò una breve descrizione semi-formale, in linguaggio naturale, ma se ne potrebbe dare una descrizione matematica, del tutto formalizzata.

Il più semplice ASF è definito come costituito da una tripla composta da:

  1. un insieme finito di stati, chiamiamolo Q
  2. un insieme finito di ingressi, diciamo I
  3. una funzione di transizione (in genere parziale), che chiamiamo d:
    d: Q x I -> Q
    

d presa una coppia (stato, ingresso) calcola uno stato. x indica il prodotto scalare. "Parziale" significa che non è detto che sia definita per ogni coppia (stato, ingresso).

Si usa comunemente associare una rappresentazione grafica a ciascun ASF. Il diagramma di un ASF è un grafo orientato, e si ottiene rappresentando con un circolo (nodo) etichettato col nome dello stato (ad es. q0, q1, q2, ecc.) ogni stato dell'insieme Q. L'insieme degli stati viene quindi rappresentato dall'insieme dei nodi del grafo. L'insieme I degli ingressi coincide con l'insieme delle etichette degli archi (ad es. a, b, c, ecc.). La funzione di transizione d viene descritta ad esempio in questo modo: d(q0, a)=q1 viene rappresentato con un arco orientato che va dal nodo associato a q0 al nodo associato allo stato q1, etichettato a. L'insieme delle transizioni viene quindi descritto dall'insieme degli archi etichettati che collegano tra di loro due stati.

La rappresentazione grafica di un ASF può essere animata in presenza del committente. per mostrare come il sistema, in base alla specifica, evolve cambiando di stato. Questo è utile nella fase di convalida della specifica e dei requisiti. L'animazione dell'automa mette in evidenza infatti i comportamenti campione del sistema modellato, che si possono confrontare con quelli del sistema reale, per stabilire se il modello è corretto. Gli stati rappresentano stati della realtà modellata. Le transizioni rappresentano gli eventi, le azioni, le operazioni o i segnali che nella realtà modellata fanno avvenire un cambiamento di stato nel sistema.

Gli ASF si prestano ad una descrizione top-down di un sistema. Un esempio con soli due livelli: ad primo un livello superiore di descrizione si rappresentano macro-stati di un sistema, e ad un livello inferiore ogni macro-stato viene ulteriormente dettagliato in un ulteriore ASF. Una transizione che entra in un macro-stato corrisponde al livello successivo all'entrata in uno degli stati dell'ASF in cui il macro-stato si dettaglia.

Un caso particolare di ASF sono gli ASF riconoscitori in cui è definito:

Uno stato finale può coincidere con lo stato iniziale. Si definisce una sequenza accettata dall'automa come una sequenza di ingressi che fa passare dallo stato iniziale ad uno stato finale. Gli ASF riconoscitori modellano appunto il software che implementa i riconoscitori di linguaggi: le sequenze di simboli o comandi sintatticamente corrette di un linguaggio sono quelle che fanno passare l'automa riconoscitore dallo stato iniziale ad un certo stato finale.

Un'ulteriore estensione del formalismo degli ASF è quella degli ASF trasduttori, per i quali la funzione di transizione è del tipo:

d: Q x I -> Q x O

O è l'insieme delle uscite (output). d genera una coppia (stato, uscita) a partire da una coppia (stato, ingresso). Ad esempio d(q0, a)=(q1, o1) indica che nello stato q0, se arriva a in ingresso, il sistema si porta nello stato q1 e produce l'uscita o1. Le uscite rappresentano gli eventi generati, i segnali emessi o le azioni compiute dal sistema nel passaggio da uno stato all'altro per effetto di un certo ingresso.

In un ASF trasduttore, gli ingressi possono essere visti come cause che danno luogo ad una transizione di stato a partire da un certo stato iniziale e producono un certo effetto visibile che è l'uscita del sistema.

Gli ASF trasduttori, in cui nella transizione da uno stato all'altro viene generato un'uscita, sono anche detti automi di Mealy.

Convenzionalmente si distingue l'ingresso dall'uscita prodotta nelle etichette degli archi che rappresentano una transizione separandoli tramite uno slash (/). Nel caso in cui una sola uscita non produca alcuna transizione si può convenzionalmente indicarlo con un carattere nullo, rappresentato ad esempio dall'underscore (_) che segue lo slash (/_).

Come esempio applicativo semplice ma reale, si consideri il problema di contare i caratteri, le linee e le parole di un testo. Questo problema è risolvibile facilmente tramite un ASF trasduttore con soli due stati, che ho disegnato tramite il il programma xfig [3] (wc.fig).

Come definizione di parola, si assume che sia una qualsiasi sequenza di caratteri priva di spazi, di caratteri di nuova linea e di tabulazione. L'utility UNIX wc(1) risolve questo problema e di seguito è riportata una versione semplificata di wc tratta dal classico libro di Kernighan & Ritchie sul linguaggio C [2]. La costante carattere '...' non è legale in C, ma è stata utilizzata per indicare un qualsiasi carattere che può far parte di una parola (con la nostra definizione di parola, vale a dire qualsiasi carattere diverso da spazio, tabulazione e newline).

The wc automata
   #include <stdio.h>

   #define IN   1  /* inside a word */
   #define OUT  0  /* outside a word */

   /* count lines, words, and characters in input */
   main()
   {
       int c, nl, nw, nc, state;

       state = OUT;
       nl = nw = nc = 0;
       while ((c = getchar()) != EOF)
       {
           ++nc;
           if (c == '\n')
               ++nl;
           if (c == ' ' || c == '\n' || c == '\t')
               state = OUT;
           else if (state == OUT) {
               state = IN;
               ++nw;
           }
       }
       printf("%d %d %d\n", nl, nw, nc);
   }
Un semplice programma word count in C che implementa l'ASF precedente (wc.c)

Notare che per semplificare il diagramma ho rappresentato più transizioni omogenee con un solo arco.

Il diagramma seguente mostra una rappresentazione alternativa dello stesso ASF ottenuta tramite il software [4] (wc.std), che supporta anche il controllo automatico sintattico dei diagrammi. Si chiamano diagrammi stato-transizione di Mealy (State Transition Diagrams o STD) e rispetto a quelli classici nel nostro caso cambia solo la notazione.

The wc automata (Mealy notation)

Altri due esempi di problemi risolubili tramite ASF, semplici ma molto comuni in pratica: il calcolo della parità di un numero binario (parity.fig) e la conversione di una stringa in un intero, a la atoi(3) del linguaggio C (atoi.fig). Questo banale esempio, mette in evidenza che lo stato corrente può essere rappresentato implicitamente dalla posizione in cui ci si trova nel codice, anziché esplicitamente tramite una variabile di stato.

The parity automata
   #define EVEN   0
   #define ODD    1

   int parity(unsigned int n)
   { int state=EVEN;

     do
     {
       if (n & 1)
         state = !state;
     }
     while (n >>= 1);

     return state;
   }
The atoi automata
   /* atoi:  convert s to integer */
   int atoi(char s[])
   {
       int i, n;

       n = 0;
       for (i = 0; s[i] >= '0' && s[i] <= '9'; ++i)
           n = 10 * n + (s[i] - '0');
       return n;
   }

Tutti gli automi presentati in questo articolo sono deterministici. Per quanto riguarda la distinzione tra automi deterministici e non, nonché la relazione tra automi a stati finiti e linguaggi regolari e grammatiche, vedere [5].

I limiti dei diagrammi a stati finiti sono:

L'ASF calcolatrice

Questa applicazione rappresenta un esempio di programma la cui progettazione si può basare completamente sugli ASF, addirittura usati in modo del tutto formale. Si tratta di un esempio né troppo complesso né troppo banale e soprattutto reale, in quanto ogni sistema grafico (GUI) ha una applicazione di questo tipo, ad esempio il sistema grafico multipiattaforma X Window ha xcalc(1).

Come visto prima, uno dei limiti dei formalismi utilizzati per descrivere i programmi è che tendono a diventare più complessi dei programmi che descrivono.

E difatti, nonostante si tratti di una calcolatrice non scientifica e senza i tasti delle parentesi, quindi molto semplice, non è stato possibile produrre un diagramma stati-transizioni leggibile (e con pochi incroci di linee) in un solo foglio A4, ma ho dovuto suddividere il diagramma in due fogli A4 (calc.std). Comunque appaiando i due fogli rimane interamente visibile una descrizione formale completa del programma, molto più semplice da capire rispetto al codice sorgente dell'implementazione.

Nel primo foglio sono indicate tutte le transizioni tra due stati diversi; nel secondo viene dettagliato ciascuno stato del primo foglio in uno stato con lo stesso nome (per semplicità) e sono indicate tutte le transizioni di ogni stato in sé (autoanelli). Per ragioni di spazio, non ho potuto indicare in maniera precisa le azioni associate a ciascun evento, ma le ho indicate tramite una chiamata di procedura effettivamente presente nell'implementazione. A seconda dello stato da cui si parte la transizione che provoca questa azione, la procedura può fare cose differenti e per i dettagli si rimanda al codice; del resto con gli ASF non si ha la pretesa di rappresentare esattamente il codice, in quanto non sono un linguaggio di programmazione!

In questo diagramma compare una estensione degli STD di Mealy molto utile: i decision point. Un punto di decisione di un diagramma STD rappresenta uno stato intermedio assunto dall'automa nel corso di una transizione e viene rappresentato tramite un esagono. Nel nostro caso la macchina deve decidere se si è verificato un errore matematico. Gli errori matematici che si possono verificare sono quelli di divisione per zero, l'errore dovuto ad una estrazione di radice di un numero negativo e gli errori di overflow. Mentre si aspetta la verifica dell'errore, l'automa di Mealy si trova nel punto di decisione.

Per non complicare troppo la rappresentazione ci si è limitati ad una gestione esplicita del solo errore di divisione per zero e nell'implementazione si è in effetti mostrato solo come intercettare questo tipo di errore. In pratica in JavaScript risulta accettabile anche non gestire nessuno di questi errori, in quanto si ottiene nel display l'indicazione NaN (Not a Number) o Infinity senza bisogno di codice per la gestione esplicita dell'errore, vale a dire che la gestione di default fatta dall'interprete risulta accettabile.

Complementa il diagramma la descrizione, in linguaggio naturale, del significato di ciascuno stato in cui può trovarsi la calcolatrice:

CALCULATED (GOT_UNARYOP1)

È lo stato iniziale, quando appena accesa la calcolatrice sul display compare 0 come risultato, e quello in cui ci si viene a trovare non appena viene calcolato e visualizzato il risultato di un calcolo che non produce errore, anche se si tratta di un risultato solo parziale.

Coincide inoltre con lo stato in cui si trova dopo che si è applicato al primo operando un operatore unario. Sono operatori unari la quadratura e la radice quadrata. Inoltre sono considerati alla stregua di operatori unari le operazioni di memoria. È importante notare che lo stato GOT_UNARYOP1 è concettualmente diverso da GETOP1 o GETOP1_DEC.

GETOP1
Stato in cui ci si trova quando si sta dando in input il primo operando di un calcolo e l'operando attualmente è un numero intero.
GETOP1_DEC
Si è in questo stato quando si sta specificando la parte decimale (quella dopo il punto) del primo operando di un calcolo.
GOT_OPERAT
Stato in cui ci si trova dopo aver stabilito l'operatore binario da applicare. Nel nostro caso gli operatori binari sono i quattro operatori aritmetici semplici: +,-,*,/
GETOP2
Come GETOP1, ma per il secondo operando.
GETOP2_DEC
Come GETOP1_DEC, ma per il secondo operando.
GOT_UNARYOP2
Come GOT_UNARYOP1, ma l'operatore unario si è applicato al secondo operando di un'operazione binaria.

Notare che i comportamenti del nostro automa coincidono con quelli di una calcolatrice tascabile fisica. Ad esempio l'autoanello innescato dall'evento di pressione del tasto di un operatore binario nello stato GOT_OPERAT permette di cambiare la selezione dell'operatore, utile per correggere la scelta in caso si scelga erroneamente l'operatore sbagliato.

ASF calcolatrice, pagina 1 ASF calcolatrice, pagina 2

Per la stampa si raccomanda la versione PostScript.

Implementazione in JavaScript

JavaScript è stato scelto per la sua semplicità, perché ampliamente implementato nei browser più diffusi e adatto per una applicazione di questo tipo.

Anche Tcl/Tk [6], [7] sarebbe stato opportuno; poiché la maggiore difficoltà sta nella fase di progetto e non nella implementazione, almeno per questo tipo di applicazione, la conversione del codice in Tcl/Tk, Java o qualsiasi altro linguaggio non presenta particolari problemi: basta seguire le indicazioni dell'ASF e il codice quasi si scrive da solo! Questo mette in evidenza l'importanza di una corretta e precisa attività di progettazione del software, che deve precedere l'implementazione o avvenire in concomitanza con essa.

Il tasto OFF generalmente funziona solo se la calcolatrice viene aperta in una finestra popup. Molti browser infatti per difendere l'utente da attacchi DoS, impediscono ad uno script di chiudere una finestra del browser, se non è stata aperta tramite script (ad esempio è stata aperta dall'utente).

L'indicatore di contenuto non nullo della memoria è stato implementato facendo diventare in grassetto l'etichetta (MS) del tasto per memorizzare un operando nella cella di memoria.

Il layout dei tasti e della calcolatrice è stato ottenuto tramite tabelle HTML. Lo stile è stato separato dai contenuti tramite l'uso di fogli di stile, che è stato limitato alla versione 1 dello standard CSS. Anche il codice JavaScript e il codice HTML sono stati separti in file distinti per quanto possibile.

L'implementazione permette non solo il copia e incolla nel display, ma è persino possibile scrivere intere espressioni con la sintassi di JavaScript nel display, che verranno valutate al loro risultato non appena si preme il tasto Invio o si preme un tasto della tastiera della calcolatrice (in generale allorquando il display perde il focus, nei diagrammi indicato col termine display blur). Il risultato della espressione digitata può così essere utilizzato per ulteriori computazioni oppure si può correggere un operando semplicemente editando il display anziché utilizzare il tasto CE e riscriverlo daccapo.

calc.js

// CALC v.1.1
// by Antonio Bonifati <http://monitor.deis.unical.it/ant>

// This program is covered by the GNU General Public Licence
// see <http://www.gnu.org/licenses/licenses.html#GPL>


Op1=0;      // primo operando
Op2=0;      // secondo operando
// Op       // operatore
// Weight_Dec   // peso cifra decimale da inserire
Mem=0;      // registro di memoria

CALCULATED  =0; // ID degli stati
GETOP1      =1;
GETOP1_DEC  =2;
GETOP2      =3;
GETOP2_DEC  =4;
GOT_OPERAT  =5;
//GOT_UNARYOP1  =6; // coincide con calculated
GOT_UNARYOP2    =7; // stato in cui si va dopo l'applicazione
                    // di un operatore unario al 2° operando
    
state=CALCULATED;   // stato iniziale

// Riporta la calcolatrice allo stato iniziale
function Reset(form)
{ state = CALCULATED;      // stato iniziale
  Op1=form.display.value=0;
}

// aggiunge una cifra ad uno dei due operandi
function AddDigit(digit, form)
{ with (form.display)
    // i vecchi browser purtroppo non hanno l'istruzione switch :(
    if (state==CALCULATED)
    { value = Op1 = digit;
      state = GETOP1;
    }
    else if (state==GETOP1)
    { value = Op1 = Op1*10+digit;
      // autoanello
    }
    else if (state==GETOP1_DEC)
    { Weight_Dec /= 10;
      value = Op1 += digit*Weight_Dec;
      // autoanello
    }
    else if (state==GETOP2)
    { value = Op2 = Op2*10+digit;
      // autoanello
    }
    else if (state==GETOP2_DEC)
    { Weight_Dec /= 10;
      value = Op2 += digit*Weight_Dec;
      // autoanello
    }
    else if (state==GOT_OPERAT)
    { value = Op2 = digit;
      state = GETOP2;
    }
}

// applica un operatore e visualizza il risultato
// non e' un event-handler, ma e' per uso interno
// ritorna false in caso di errore (divisione per zero)
function applyOp(form)
{ if (Op == '+')
    Op1+=Op2;
  else if (Op == '-')
    Op1-=Op2;
  else if (Op == '*')
    Op1*=Op2;
  else
  { // Op=='/'
    if (Op2==0)
    { alert("Calc 1.1\nError: division by 0.\nPlease reinput a non-zero operator.");
      return false;
    }
    Op1/=Op2;
  }
  form.display.value=Op1;
  return true;
}

// imposta il flag della memoria
// non e' un event-handler, ma e' per uso interno
function setMemFlag(form)
{ with (form.ms.style)
    if (Mem)
      fontWeight="bold";
    else
      fontWeight="";
}

// processa il comando uguale
function DoCalc(form)
{ if (state == GOT_OPERAT || state == CALCULATED)
  { applyOp(form);
    // autoanello
  }
  else if (state == GETOP2 || state == GETOP2_DEC || state == GOT_UNARYOP2)
  { if (applyOp(form))
      state = CALCULATED;
  }
}

// processa un operatore
function ProcessOp(op, form)
{ if (state == CALCULATED || state == GETOP1 || state == GOT_OPERAT || state == GETOP1_DEC)
  { Op=op;
    state = GOT_OPERAT; // // autoanello per GOT_OPERAT
    Op2=Op1;    // valore predefinito di Op2
  }
  else if (state == GETOP2 || state == GETOP2_DEC || state == GOT_UNARYOP2)
  { if (applyOp(form))
    { Op=op;
      state = GOT_OPERAT;
      Op2=Op1;
    }
  }
}

// processa il punto
function ProcessDot(form)
{ with (form.display)
    if (state == CALCULATED)
    { Op1 = 0; Weight_Dec = 1;
      value = '0.';
      state = GETOP1_DEC;
    }
    else if (state == GETOP1)
    { Weight_Dec = 1;
      value = Op1+'.';
      state = GETOP1_DEC;
    }
    else if (state == GOT_OPERAT || state == GOT_UNARYOP2)
    { Op2 = 0; Weight_Dec = 1;
      value = '0.';
      state = GETOP2_DEC;
    }
    else if (state == GETOP2)
    { Weight_Dec = 1;
      value = Op2+'.';
      state = GETOP2_DEC;
    }
}

function DoSqrt(form)
{ with (form.display)
    if (state == GETOP1 || state == GETOP1_DEC || state==CALCULATED)
    { value = Op1 = Math.sqrt(Op1);
      state = CALCULATED; // autoanello per CALCULATED
    }
    else if (state == GETOP2 || state == GETOP2_DEC || state == GOT_UNARYOP2)
    { value = Op2 = Math.sqrt(Op2);
      state = GOT_UNARYOP2;   // autoanello per GOT_UNARYOP2
    }
}

function DoSqr(form)
{ with (form.display)
    if (state == CALCULATED || state == GETOP1 || state == GETOP1_DEC)
    { value = Op1 *= Op1;
      state = CALCULATED; // autoanello per CALCULATED
    }
    else if (state == GETOP2 || state == GETOP2_DEC || state == GOT_UNARYOP2)
    { value = Op2 *= Op2;
      state = GOT_UNARYOP2;   // autoanello per GOT_UNARYOP2  
    }
}

function ChangeSign(form)
{ with (form.display)
    if (state == CALCULATED || state == GETOP1 || state == GETOP1_DEC)
      value = Op1 = -Op1;
    else if (state == GETOP2 || state == GETOP2_DEC || state == GOT_UNARYOP2)
      value = Op2 = -Op2;
}

// carica il valore corrente sul display
// come primo o secondo operando
function LoadVal(form)
{ with (form.display)
    if (state == CALCULATED)
    { value = Op1 = eval(value);
      state = GETOP1;
    }
    else if (state == GETOP1 || state == GETOP1_DEC)
      value = Op1 = eval(value);
    else if (state == GETOP2 || state == GETOP2_DEC || state == GOT_OPERAT || state == GOT_UNARYOP2)
      value = Op2 = eval(value);
}

// CE - azzera l'operando corrente
// se possibile (lo stato non e' GOT_OPERAT)
function ZeroOp(form)
{ with (form.display)
    if (state == CALCULATED || state == GETOP1 || state == GETOP1_DEC)
      value = Op1 = 0;
    else if (state == GETOP2 || state == GETOP2_DEC || state == GOT_UNARYOP2)
      value = Op2 = 0;
}

// mette in memoria cio' che c'e' sul display
function StoreInMem(form)
{ if (state == CALCULATED || state == GETOP1 || state == GETOP1_DEC)
  { Mem = Op1;
    state = CALCULATED; // autoanello per CALCULATED
  }
  else if (state == GOT_OPERAT)
  { Mem = Op1;
    // autoanello
  }
  else if (state == GETOP2 || state == GETOP2_DEC || state == GOT_UNARYOP2)
  { Mem = Op2;
    state = GOT_UNARYOP2;   // autoanello per GOT_UNARYOP2
  }
  setMemFlag(form);
}

// aggiunge in memoria cio' che c'e' sul display
function AddInMem(form)
{ if (state == CALCULATED || state == GETOP1 || state == GETOP1_DEC)
  { Mem += Op1;
    state = CALCULATED; // autoanello per CALCULATED
  }
  else if (state == GOT_OPERAT)
  { Mem += Op1;
    // autoanello
  }
  else if (state == GETOP2 || state == GETOP2_DEC || state == GOT_UNARYOP2)
  { Mem += Op2;
    state = GOT_UNARYOP2;   // autoanello per GOT_UNARYOP2
  }
  setMemFlag(form);
}

// recupera il contenuto della memoria e lo mette al posto
// dell'operando corrente o come secondo operatore
function RetrieveMem(form)
{ with (form.display)
    if (state == CALCULATED || state == GETOP1 || state == GETOP1_DEC)
    { value = Op1 = Mem;
      state = CALCULATED; // autoanello per CALCULATED
    }
    else if (state == GOT_OPERAT)
    { value = Op2 = Mem;
      state = GOT_UNARYOP2;
    }
    else if (state == GETOP2 || state == GETOP2_DEC || state == GOT_UNARYOP2)
    { value = Op2 = Mem;
      state = GOT_UNARYOP2;   // autoanello per GOT_UNARYOP2
    }
}


function PrintButton(label, action) {
    document.write('<input type="button" class="calcbut" value="' + label +
'" onClick="' + action + '"' + (label=='MS' ? ' name="ms"' : '') + '>');
}

calc.css

/* css1 style sheet for CALC v.1.1 */

table.calc
{ background: #ffffcc;
  border-style: outset;
}

table.calc table.title
{ color: black;
  font-weight: bold;
}

table.calc td.display
{ background-image: url(disback.png);
}

table.calc input.calcbut
{ width: 3em;
  background: #ffcc00;
}

calc.html

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>CALC 1.1</title>
<style type="text/css">
  @import url(calc.css);
</style>
</head>
<body>
<script type="text/javascript" language="JavaScript" src="calc.js">
</script>

<noscript>CALC requires JavaScript support into your
browser.</noscript>
<form onsubmit="LoadVal(this); return false">
<table summary="simple calculator" class="calc" cellspacing="0"
cellpadding="3">
<tr>
<td>
<table summary="name and version" class="title" width="100%">
<tr>
<td>CALC</td>
<td align="right">v1.1</td>
</tr>
</table>
</td>
</tr>

<tr>
<td align="center" class="display" colspan="5"><input type="text"
name="display" size="20" value="0" onblur="LoadVal(form)"> 
<!-- also onchange is ok -->
<!-- form or this.form is the same --></td>
</tr>

<tr>
<td>
<table summary="calculator keypad">
<tr><!-- 1th ROW -->
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("+/-", "ChangeSign(form)");
</script>

 </td>
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("x&sup2;", "DoSqr(form)");
</script>

 </td>
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("&radic;", "DoSqrt(form)");
</script>

 </td>
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("OFF", "window.close()");
</script>

 </td>
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("C", "Reset(form)");
</script>

 </td>
</tr>

<tr><!-- 2nd ROW -->
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("MS", "StoreInMem(form)");
</script>

 </td>
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("7", "AddDigit(7,form)");
</script>

 </td>
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("8", "AddDigit(8,form)");
</script>

 </td>
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("9", "AddDigit(9,form)");
</script>

 </td>
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("/", "ProcessOp('/',form)");
</script>

 </td>
</tr>

<tr><!-- 3th ROW -->
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("MR", "RetrieveMem(form)");
</script>

 </td>
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("4", "AddDigit(4,form)");
</script>

 </td>
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("5", "AddDigit(5,form)");
</script>

 </td>
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("6", "AddDigit(6,form)");
</script>

 </td>
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("*", "ProcessOp('*',form)");
</script>

 </td>
</tr>

<tr><!-- 4th ROW -->
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("M+", "AddInMem(form)");
</script>

 </td>
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("1", "AddDigit(1,form)");
</script>

 </td>
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("2", "AddDigit(2,form)");
</script>

 </td>
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("3", "AddDigit(3,form)");
</script>

 </td>
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("-", "ProcessOp('-',form)");
</script>

 </td>
</tr>

<tr><!-- 5th ROW -->
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("CE", "ZeroOp(form)");
</script>

 </td>
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("0", "AddDigit(0,form)");
</script>

 </td>
<td>
<script type="text/javascript" language="JavaScript">
PrintButton(".", "ProcessDot(form)");
</script>

 </td>
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("=", "DoCalc(form)");
</script>

 </td>
<td>
<script type="text/javascript" language="JavaScript">
PrintButton("+", "ProcessOp('+',form)");
</script>

 </td>
</tr>
</table>
</td>
</tr>
</table>
</form>
</body>
</html>

Riferimenti

  1. Ingegneria del Software, C. Ghezzi, A. Fuggetta, S. Morasca, A. Morzenti, M. Pezzè, Mondadori Informatica 1991
  2. The C Programming Language, Brian W. Kernighan and Dennis M. Ritchie, Prentice-Hall 1988
  3. XFIG - Facility for Interactive Generation of figures under X11, http://www.xfig.org
  4. The Toolkit for Conceptual Modeling, http://wwwhome.cs.utwente.nl/~tcm/
  5. Introduzione alla Teoria dei Linguaggi ed alle Espressioni Regolari, A. Bonifati, http://monitor.deis.unical.it/ant/it/teoria/
  6. Tcl: An Embeddable Command Language, John K. Ousterhout
  7. An X11 Toolkit Based on the Tcl Language, John K. Ousterhout