[inizio] [indice generale] [precedente] [successivo] [indice analitico] [contributi]

123. Analisi sintattica e stilistica

L'analisi sintattica di un testo è un problema ben più complicato della semplice verifica delle parole con un dizionario. Esistono però alcuni tipi di errori sintattici, o stilistici, che si possono identificare con l'aiuto di espressioni regolari (regular expression).

La lingua italiana consente spesso l'utilizzo di forme espressive differenti, per le quali dovrebbe esserci almeno uniformità all'interno di uno stesso documento. Per esempio, occorre decidere se si vuole scrivere: «una aula» oppure «un'aula», «ed anche» oppure «e anche»,...

In questo capitolo si vuole mostrare un programma Perl che può aiutare a definire delle regole rappresentate in forma di espressioni regolari, per segnalare degli errori sintattici o stilistici. Con questo programma è possibile indicare anche delle regole di eccezione e delle particolarità riferite a un singolo documento. Il programma in questione è ALerrori, abbinato al pacchetto ALtools, che a sua volta accompagna la distribuzione di questo documento.

123.1 ALerrori

Il programma che viene proposto, scandisce un file generando un altro file contenente le parti di testo che risulterebbero errate (oltre a un file diagnostico contenente la registrazione del procedimento di verifica). Prima di iniziare a leggere il file da esaminare, vengono caricati dei modelli che esprimono degli errori, espressi in forma di espressione regolare, seguiti eventualmente da dei modelli di eccezione. Infine, vengono caricate anche delle particolarità riferite al testo che si elabora, trattate in forma letterale, e non più secondo il modello di un'espressione regolare.

	+------------------+         +-------------+
	|     regexp       |         |    brani    |
	| errori+eccezioni |         | particolari |
	+------------------+         +-------------+
                 |                          |
	         +------+        +----------+
			|        |
			V        V
+-----------+	    +-----------------+			+---------+
| documento |	    |                 |			| errori  |
|    da     |------>|     ALerrori    |---------------->| trovati |
| esaminare |	    |                 |			+---------+
+-----------+	    +-----------------+
                             |
                             |             +-------------+
                             +------------>|  registro   |
					   | diagnostico |
                                           +-------------+

Figura 123.1: Schema di funzionamento di ALerrori.

Gli errori che si possono ricercare attraverso delle espressioni regolari, riguardano la vicinanza di parole che hanno caratteristiche determinate, come l'uso o meno di articoli apostrofati. Sotto questo aspetto, diventa importante che, nel file di testo originale, ogni paragrafo si trovi su una sola riga, cioè non sia interrotto su più righe.

A fianco di questo problema, si aggiunge il fatto che il file sorgente che si vuole esaminare potrebbe contenere dei codici di controllo, come nel caso di TeX (o LaTeX) e di HTML. In tutte queste situazioni, occorre predisporre un programma in grado di ripulire il testo da questi codici di controllo, generando un file di testo puro, in cui ogni paragrafo si trovi su una sola riga. Al limite, può essere sufficiente che ogni periodo, cioè ogni frase completa che termina con un punto, si trovi su una sola riga.

123.1.1 Espressioni regolari

Il blocco principale di ALerrori è scritto in Perl, e le espressioni regolari che possono essere gestite sono quelle di questo linguaggio di programmazione. Per motivi che si chiariranno analizzando il sorgente, non è possibile identificare l'inizio e la fine di una riga (con i simboli ^ e $), inoltre non è possibile utilizzare le parentesi tonde.

A titolo di esempio, si propone il problema della «d» eufonica, per la precisione il caso di «ad». Supponendo di volerla utilizzare solo quando la parola successiva inizia con la vocale «a», escludendo il caso in cui la parola continui con un'altra «d» (per esempio: «ad amare», ma non «ad adattare»), si possono usare le espressioni regolari seguenti per individuare gli errori.

\ba\s+a[^d]\w*\b
\bad\s+ad\w*\b
\bad\s+[^a]\w*\b

Per intendere meglio il significato di ciò che è scritto, la prima riga significa:

Nello stesso tempo, però, si può decidere di accettare un'eccezione: «ad esempio», che secondo quando stabilito con l'ultima delle espressioni regolari appena mostrate, dovrebbe essere un errore. Si può usare quindi l'espressione regolare seguente, tra le eccezioni.

\bad\s+esempio\b

123.1.2 $ ALerrori

ALerrori <tipo> <file-da-analizzare> [<errori-risultanti>]

All'interno dell'applicativo ALerrori, l'eseguibile omonimo, ALerrori, si comporta da programma frontale nei confronti di ciò che fa veramente l'elaborazione. Il suo scopo è quello di trasformare il file indicato per l'analisi in modo conveniente per la ricerca di questi errori.

Il primo argomento è una parola chiave che definisce come deve essere trasformato il file prima dell'analisi. Può trattarsi di:

123.1.3 $ ALerrori-motore

ALerrori-motore <file-da-analizzare> <errori-risultanti> <registro-diagnostico>

Il programma Perl ALerrori-motore è ciò che svolge effettivamente il lavoro di analisi. Si aspetta di ricevere come primo argomento il nome di un file di testo già pronto per l'analisi, e questo è infatti il compito di ALerrori che provvede da solo ad avviare ALerrori-motore.

Il file indicato come secondo argomento viene creato per inserirvi i brani di testo contenenti gli errori; il terzo serve ad annotare il procedimento.

#!/usr/bin/perl
#=======================================================================
# ALerrori-motore <file-da-analizzare> <file-risultato> <file-diag>
#
# Analizza il documento alla ricerca di errori comuni, composti
# prevalentemente da abbinamenti di parole che solitamente sono
# infelici.
#=======================================================================

#-----------------------------------------------------------------------
# Simbolo separatore dei campi.
#-----------------------------------------------------------------------
$SEPARATORE = "____";

#-----------------------------------------------------------------------
# Acquisizione degli argomenti
#-----------------------------------------------------------------------
$file_da_verificare = $ARGV[0];
$file_risultato = $ARGV[1];
$file_diagnostico = $ARGV[2];

#-----------------------------------------------------------------------
# File dei modelli degli errori ed elenco delle particolarità.
#-----------------------------------------------------------------------
$file_degli_errori_e_delle_eccezioni =
    "/opt/ALtools/var/ALerrorieccezioni.dat";

$file_dei_casi_particolari = "/opt/ALtools/var/ALparticolari.dat";

#-----------------------------------------------------------------------
# Il contenuti di questi file viene caricato all'interno di
# diversi array.
#-----------------------------------------------------------------------
@array_errori = ();
@array_spiegazioni = ();
@array_eccezioni = ();
@array_particolari = ();
$indice_errori = 0;
$indice_eccezioni = 0;
$indice_particolari = 0;

#-----------------------------------------------------------------------
# Variabili scalari utilizzate per leggere e trattare gli errori,
# le eccezioni e le particolarità
#-----------------------------------------------------------------------
$record = "";
$errore_re = "";
$errore_spiegazione = "";
$eccezione = "";
$particolare = "";

#-----------------------------------------------------------------------
# Variabile utilizzata per leggere il file da analizzare.
#-----------------------------------------------------------------------
$riga = "";

#-----------------------------------------------------------------------
# Parti del testo contenente un errore o ciò che si ritiene tale.
#-----------------------------------------------------------------------
$testa = "";
$contenuto = "";
$coda = "";
$insieme = "";

#-----------------------------------------------------------------------
# Flag che segnala la presenza o la persistenza di un errore
#-----------------------------------------------------------------------
$errato = 0;

#-----------------------------------------------------------------------
# Carica il file degli errori nell'array.
#-----------------------------------------------------------------------
open ( ERRORIECCEZIONI, "< $file_degli_errori_e_delle_eccezioni" );

while ( $record = <ERRORIECCEZIONI> ) {

    #-------------------------------------------------------------------
    # Toglie il codice di interruzione di riga.
    #-------------------------------------------------------------------
    chomp( $record );

    #-------------------------------------------------------------------
    # Se il record è vuoto, oppure se contiene un commento, ripete il
    # ciclo.
    #-------------------------------------------------------------------
    if ( $record =~ m/^\s*$/ ) {
        next;
    };
    if ( $record =~ m/^\s*#.*$/ ) {
        next;
    };

    #-------------------------------------------------------------------
    # Analizza in base al tipo di record.
    #-------------------------------------------------------------------
    if ( $record =~ m/^ERR/ ) {

        #---------------------------------------------------------------
	# Si tratta della descrizione di un errore.
        #---------------------------------------------------------------

        #---------------------------------------------------------------
	# Estrae il contenuto
        #---------------------------------------------------------------
	if ( $record =~ m/^ERR$SEPARATORE(.*)$SEPARATORE(.*)$/ ) {

	    $errore_re = $1;
	    $errore_spiegazione = $2;

	} elsif ( $record =~ m/^ERR$SEPARATORE(.*)$/ ) {

	    $errore_re = $1;
	    $errore_spiegazione = "";

	} else {

    	    #-----------------------------------------------------------
	    # C'è qualcosa che non va nel formato del record.
    	    #-----------------------------------------------------------
	    print STDOUT "Errore di sintassi nel record:\n";
	    print STDOUT "$record\n";

    	    next;
	}

	#---------------------------------------------------------------
	# Trasferisce nell'array.
	#---------------------------------------------------------------
	$indice_errori = $#array_errori+1;
	@array_errori[$indice_errori] = $errore_re;
	@array_spiegazioni[$indice_errori] = $errore_spiegazione;

	#---------------------------------------------------------------
	# Prepara l'array delle eccezioni.
	#---------------------------------------------------------------
	@array_eccezioni[$indice_errori] = ();

    } elsif ( $record =~ m/^ECC/ ) {

        #---------------------------------------------------------------
	# Si tratta della descrizione di un'eccezione.
        #---------------------------------------------------------------

        #---------------------------------------------------------------
	# Estrae il contenuto
        #---------------------------------------------------------------
	if ( $record =~ m/^ECC$SEPARATORE(.*)$SEPARATORE(.*)$/ ) {

	    $eccezione = $1;
	    print STDOUT "Nei record delle eccezioni non è previsto il ";
	    print STDOUT "campo di spiegazione:\n";

	} elsif ( $record =~ m/^ECC$SEPARATORE(.*)$/ ) {

	    $eccezione = $1;

	} else {

    	    #-----------------------------------------------------------
	    # C'è qualcosa che non va nel formato del record.
    	    #-----------------------------------------------------------
	    print STDOUT "Errore di sintassi nel record:\n";
	    print STDOUT "$record\n";

    	    next;
	}

	#---------------------------------------------------------------
	# Trasferisce nell'array.
	#---------------------------------------------------------------
	$indice_eccezioni = $#{$array_eccezioni[$indice_errori]} +1 ;
	$array_eccezioni[$indice_errori][$indice_eccezioni] = $eccezione;

    }
}

#-----------------------------------------------------------------------
# Chiude il file degli errori e delle eccezioni.
#-----------------------------------------------------------------------
close ( ERRORIECCEZIONI );
    
#-----------------------------------------------------------------------
# Carica il file delle particolarità.
#-----------------------------------------------------------------------
open ( PARTICOLARI, "< $file_dei_casi_particolari" );

while ( $particolare = <PARTICOLARI> ) {

    #-------------------------------------------------------------------
    # Toglie il codice di interruzione di riga.
    #-------------------------------------------------------------------
    chomp( $particolare );

    #-------------------------------------------------------------------
    # Se il record è vuoto, ripete il ciclo (non sono ammessi commenti).
    #-------------------------------------------------------------------
    if ( $particolare =~ m/^\s*$/ ) {
        next;
    };

    #-------------------------------------------------------------------
    # Trasferisce nell'array.
    #-------------------------------------------------------------------
    $indice_particolari = $#array_particolari+1;
    @array_particolari[$indice_particolari] = $particolare;
}

#-----------------------------------------------------------------------
# Chiude il file.
#-----------------------------------------------------------------------
close ( PARTICOLARI );

#-----------------------------------------------------------------------
# Inizia la scansione del documento.
#-----------------------------------------------------------------------
open ( DOCUMENTO, "< $file_da_verificare" );
open ( RISULTATO, "> $file_risultato" );
open ( DIAGNOSI, "> $file_diagnostico" );

while ( $riga = <DOCUMENTO> ) {

    #-------------------------------------------------------------------
    # Toglie il codice di interruzione di riga.
    #-------------------------------------------------------------------
    chomp( $riga );

    #-------------------------------------------------------------------
    # Esclude le righe vuote.
    #-------------------------------------------------------------------
    if ( $riga =~ m/^\s*$/ ) {
        next;
    };

    #-------------------------------------------------------------------
    # Azzera il flag.
    #-------------------------------------------------------------------
    $errato = 0;

    #-------------------------------------------------------------------
    # Scandisce l'array degli errori.
    #-------------------------------------------------------------------
    for ( $indice_errori = 0 ; $indice_errori <= $#array_errori ;
	    $indice_errori++ ) {

	$errore_re = $array_errori[$indice_errori];
	$errore_spiegazione = $array_spiegazioni[$indice_errori];

	#---------------------------------------------------------------
	# Esegue la comparazione, badando alla differenza tra
	# maiuscole e minuscole.
	# Si comincia con una comparazione semplice, per non appesantire
	# la ricerca.
	#---------------------------------------------------------------
	if ( $riga =~ m/$errore_re/ ) {

	    #-----------------------------------------------------------
	    # Visto che l'errore c'è, cerca di estrarre tre parole
	    # prima e dopo, possibilmente con uno spazio prima e dopo.
	    #-----------------------------------------------------------
	    $riga =~
	    	m/(\S*\s*\S*\s*\S*\s*)($errore_re)(\s*\S*\s*\S*\s*\S*)/;

	    #-----------------------------------------------------------
	    # Attiva il flag che segnala la presenza di un errore.
	    #-----------------------------------------------------------
	    $errato = 1;

	    #-----------------------------------------------------------
	    # Salva il risultato della comparazione.
	    #-----------------------------------------------------------
	    $testa = "$1";
	    $contenuto = "$2";
	    $coda = "$3";

	    $insieme = "${testa}${contenuto}${coda}" ;

	    #-----------------------------------------------------------
	    # Annota nel file diagnostico.
	    #-----------------------------------------------------------
	    print DIAGNOSI "\n";
	    print DIAGNOSI "??? ${testa}>>${contenuto}<<${coda}\n";
	    print DIAGNOSI "ERR $errore_re\n";

	} else {
	
	    #-----------------------------------------------------------
	    # Se questo modello non corrisponde, salta al prossimo
	    # ciclo.
	    #-----------------------------------------------------------
	    next;
	    
	}

	if ( $errato ) {
	
	    #-----------------------------------------------------------
	    # Scandisce le eccezioni.
	    #-----------------------------------------------------------
	    
	    #-----------------------------------------------------------
	    # $array_eccezioni[$indice_errori] contiene un altro array,
	    # quello delle eccezioni specifiche di un certo tipo di
	    # errore, per cui, $#{$array_eccezioni[$indice_errori]} è
	    # l'indice dell'ultimo elemento di tale array.
	    # Se tale array è vuoto, il valore di
	    # $#{$array_eccezioni[$indice_errori]} dovrebbe essere
	    # negativo.
	    #-----------------------------------------------------------
	    for ( $indice_eccezioni = 0 ;
		    $indice_eccezioni <=
			$#{$array_eccezioni[$indice_errori]}
			    && $#{$array_eccezioni[$indice_errori]} >= 0 ;
		    $indice_eccezioni++ ) {

		$eccezione =
		    $array_eccezioni[$indice_errori][$indice_eccezioni];

		#-------------------------------------------------------
		# Annota nel file di diagnosi, ammesso che sia il caso.
		#-------------------------------------------------------
		print DIAGNOSI "ecc $eccezione\n";

		#-------------------------------------------------------
		# Esegue la comparazione.
		#-------------------------------------------------------
		if ( $insieme =~ m/$eccezione/ ) {

		    #---------------------------------------------------
		    # Dal momento che l'eccezione corrisponde,
		    # quel tipo di errore non va considerato e si
		    # passa all'analisi di quello successivo.
		    #---------------------------------------------------
		    $errato = 0;

		    #---------------------------------------------------
		    # Annota nel file diagnostico.
		    #---------------------------------------------------
		    print DIAGNOSI "ECC $eccezione\n";

		    #---------------------------------------------------
		    # Esce dal ciclo for.
		    #---------------------------------------------------
		    last;

		}
	    }
	}

	#---------------------------------------------------------------
	# Verifica se c'è ancora l'errore.
	#---------------------------------------------------------------
	if ( $errato ) {

	    #-----------------------------------------------------------
	    # Scandisce le particolarità.
	    #-----------------------------------------------------------
	    for ( $indice_particolari = 0 ;
		    $indice_particolari <= $#array_particolari ;
		    $indice_particolari++ ) {

		$particolare = $array_particolari[$indice_particolari];

		#-------------------------------------------------------
		# Esegue la comparazione.
		#-------------------------------------------------------
		if ( $insieme eq $particolare ) {

		    #---------------------------------------------------
		    # Per quanto riguarda questo errore, la riga va bene
		    # così: il flag viene sistemato, e si controlla
		    # per un altro errore.
		    #---------------------------------------------------
		    $errato = 0;

		    #---------------------------------------------------
		    # Annota nel file diagnostico.
		    #---------------------------------------------------
		    print DIAGNOSI "PAR $particolare\n";

		}
	    }
	}

	#---------------------------------------------------------------
	# Verifica se c'è ancora l'errore.
	#---------------------------------------------------------------
	if ( $errato ) {
	
	    #-----------------------------------------------------------
	    # Emette il pezzo nel file degli errori risultanti.
	    #-----------------------------------------------------------
	    print RISULTATO "$insieme\n";

	    #-----------------------------------------------------------
	    # Emette il pezzo attraverso lo standard output,
	    # evidenziando la parte centrale corrispondente
	    # all'espressione regolare di partenza.
	    #-----------------------------------------------------------
	    print STDOUT "$errore_spiegazione\n";
	    print STDOUT "    $testa>>$contenuto<<$coda\n";

	    #-----------------------------------------------------------
	    # Annota nel file diagnostico.
	    #-----------------------------------------------------------
	    print DIAGNOSI "!!! $testa>>$contenuto<<$coda\n";
	}
    }
}

close ( DOCUMENTO );
close ( RISULTATO );
close ( DIAGNOSI );

#=======================================================================
# Fine.
#=======================================================================

123.1.4 File degli errori, delle eccezioni e delle particolarità

ALerrori non è un applicativo molto sofisticato, tanto che i nomi e i percorsi dei file utilizzati come contenitori degli elenchi dei modelli di errore, di eccezione e come casi particolari, sono specificati nelle prime righe del programma stesso. ALerrori fa parte di ALtools, e come tale utilizza le convenzioni di questo pacchetto.

Il file dei modelli di errore e di eccezione, accetta le righe vuote o bianche, che vengono ignorate, e la presenza di commenti, introdotti dal simbolo #, purché questi appaiano da soli nelle righe. I record che rappresentano un errore sono nella forma:

ERR____<regexp>[____<spiegazione>]

I record che rappresentano un'eccezione sono nella forma:

ECC____<regexp>

In pratica, la stringa ____ viene usata per separare i vari elementi: tipo di record, espressione regolare, spiegazione eventuale (solo per gli errori).

Le eccezioni sono valide in quanto riferite all'ultimo modello di errore indicato, e non più in modo generalizzato per tutte le situazioni.

Si osservino gli esempi seguenti, che contengono solo una parte dei modelli utilizzati nel pacchetto ALtools.

#-----------------------------------------------------------------------
# /opt/ALtools/var/ALerrorieccezioni.dat
#-----------------------------------------------------------------------

#=======================================================================
# d eufonica
# a|e|o prendono una «d» eufonica se sono seguite da una parola che
# inizia con la stessa vocale, a meno che ci sia subito dopo un'altra
# «d».
#=======================================================================
ERR____\ba\s+a[^d]\w*\b____a --> ad
ECC____\bda\s+a\s+a\b

ERR____\bad\s+ad\w*\b____ad --> a

ERR____\bad\s+[^aA]\w*\b____ad --> a
ECC____\b[aA]d\s+esempio\b
ECC____\b[aA]d\s+ora\b

ERR____\be\s+e[^d]\w*\b____e --> ed
ERR____\bed\s+[eE]d\w*\b____ed --> e
ERR____\bed\s+[^eèE]\w*\b____ed --> e

ERR____\bo\s+[oO][^d]\w*\b____o --> od
ERR____\bod\s+[oO]d\w*\b____od --> o
ERR____\bod\s+[^oO]\w*\b____od --> o

Il file delle particolarità è diverso; serve a contenere gli errori particolari che devono essere tollerati nel documento. Per la precisione, quando si utilizza ALerrori, si ottiene un file contenente i brani, uno per ogni riga, che sono ritenuti errati. Se alcuni di questi sono intesi essere corretti, le righe relative, vanno aggiunte così come sono al file delle particolarità.

Questo file non può contenere espressioni regolari e nemmeno commenti. Sono tollerate, e ignorate, solo le righe vuote e quelle bianche.

---------------------------

Appunti Linux 1999.09.21 --- Copyright © 1997-1999 Daniele Giacomini --  daniele @ pluto.linux.it


[inizio] [indice generale] [precedente] [successivo] [indice analitico] [contributi]