domenica 30 aprile 2017

Baby-Perl

Ecco un altro esempio, questa volta in italiano, di uso infantile di Perl e in generale scorretto della formattazione di stringhe.

L'idea è semplice: occorre stampare in un file a formato fisso delle linee di valori. Si noti bene che il formato fisso si presta bene all'uso della printf, e sicuramente all'uso dei formati Perl, anche se con campi molto lunghi questi diventano scomodi e francamente poco leggibili.

Ma l'ignoranza e l'inesperienza facevano da padroni nei primi anni della programmazione, così seguendo il fantastico principio del reinventare la ruota ecco che si implementava una sorta di sprintf del poveraccio:


my $INIZIO_CAMPO = "|";
my $FINE_CAMPO   = "|";

sub formatta($$$){
    my ($stringa, $lunghezza, $fineRiga) = @_;

    $stringa =~ s/,/./g;

    if( length($stringa) > $lunghezza ){
 $stringa = substr($stringa, 0, $lunghezza);
    }


    if( $fineRiga != 1 ){
 return $INIZIO_CAMPO . $stringa . $FINE_CAMPO . $SEPARATORE_CAMPO;
    } else {
 return $INIZIO_CAMPO . $stringa . $FINE_CAMPO . $FINE_LINEA;
    }


}

Piuttosto elementare:
  1. si sostituisce il carattere , con il ., e questo ha poco a che fare con la stampa stessa;
  2. se la stringa di partenza $stringa supera la lunghezza del campo (specificata in $lunghezza)
    si tronca la stringa (mentre non c'è un padding qualora la stringa sia piu' corta);
  3. si concatena la stringa con i delimitatori di campo e si aggiunge, eventualmente, un carattere di
    fine linea.

Come si potrebbe fare una versione compatta? O meglio, come affronterei il problema oggi?
Beh, come già detto, printf(3p) è la salvezza:


sub formatta{
    my ( $stringa, $lunghezza, $fine_riga ) = @_;

    my $format_string = sprintf "%%s%%%ds%%s%%s", $lunghezza;
    return sprintf $format_string, $INIZIO_CAMPO, $stringa, $FINE_CAMPO, ( $fine_riga ? "\n" : "" );
}

L'unica complicazione evidente è la gestione della lunghezza della stringa, che deve essere specificata nella stringa di formato.
Si supponga di chiamare la funzione formatta come segue:

say formatta 'foo', 5, 1;


Ora quello che succede è che:
  1. $format_string viene valorizzato come %s%5s%s%s ovvero sprintf "%%s%%%ds%%s%%s", $lunghezza; converte la stringa passata
    come argomento
    • %%s viene convertito come %s, il primo è il separatore di inizio campo, il penultimo di fine campo, l'ultimo è l'evetuale
      fine riga;
    • %%%ds viene convertito in %$lunghezzas ovvero %5s;
  2. la stringa composta in $format_string è ancora una stringa valida per la printf, che quindi viene riempita con
    i valori appositi.

Sicuramente questa versione è piu' compatta e piu' manutenibile, a patto di leggere correttamente il formato della pritnf e il "doppio formato"
costruito nel primo passaggio (es. %%s).

E' anche possibile compattare ancora di piu' cecando di evitare i doppi passaggi, o meglio di limitarli al massimo:


sub formatta{
    my ( $stringa, $lunghezza, $fine_riga ) = @_;

    my $format_string = sprintf "%1s%%%ds%1s%%1s", $INIZIO_CAMPO, $lunghezza, $FINE_CAMPO;
    return sprintf $format_string,  $stringa, ( $fine_riga ? "\n" : "" );
}

Il concetto rimane il medesimo, ma la $format_string in uscita dalla prima invocazione di sprintf vale qualcosa come |%5s|%1s
e quindi richiede solo due parametri (la stringa e il fine riga).

E' poi possibile allineare la stringa a sinistra (invece che a destra) semplicemente usando un padding negativo (es -5%s).

La lezione imparata è dunque: la printf è molto versatile, e nella maggior parte dei casi può risolvere parecchi grattacapi di formattazione!

Nessun commento: