Newsgroup: it.comp.lang.perl
Titolo: Individuare i parametri

Salve,
sono di nuovo a chiedervi un consiglio: dovrei individuare i parametri 
di un "comando". Ad esempio, nel caso...
comando 10*5,func(5,2),"Saluti, Daniele"
...vorrei ricavare un bell'array...
10*5
func(5,2)
"Saluti, Daniele"
...prima di scrivere del codice, reinventando l'acqua calda, 
preferirei usare un modulo.
Ho provato i vari Text::Balanced, Text::CSV, ma senza ottenere il 
risultato sperato.
Potete aiutarmi? Grazie!
-- 
Daniele
togli i sette per scrivermi


Getopt::Long
http://search.cpan.org/~jv/Getopt-Long-2.34/
Getopt::Plus
http://search.cpan.org/~fluffy/Getopt-Plus-0.96/
Getopt::Auto
http://search.cpan.org/~simon/Getopt-Auto-1.00/
etc.
Puoi dare un'occhiata anche a Scriptalicious
http://search.cpan.org/~samv/Scriptalicious-1.10/
... E, come sempre, puň darsi che ci sia anche qualcosa di ancora piů 
calibrato sulle tue esigenze, perché CPAN č un pozzo senza fondo...
Ciao,
Emanuele.

 fondo... 
Ho cercato per giorni :-) ma non ho trovato l'oggetto che fa al caso 
mio. Anche i Getopt::* sono adatti all'interpretazione della command 
line, ma non mi pare per parametri separati da virgole.
Ho controllato anche qualche Text::*, ma anche la CSV fallisce 
(ovviamente) nei casi simili all'esempio proposto...
10*5,func(5,2),"Saluti, Daniele"
In attesa di implementare qualcosa di pronto, scrivo una sub...
:-)
Ciao!


 :-)
Ma sei sicuro di voler usare quella sintassi per i parametri di un comando?
Beninteso, con Perl puoi fare quello che preferisci, ma tieni presente 
che la tua sintassi non č conforme allo standard POSIX per i parametri 
della linea di comando, potendo creare quindi complicazioni sia agli 
utenti (operatori umani) che ad altri programmi che utilizzano il tuo 
comando.
Per inciso, una possibile variante POSIX-compliant dell'invocazione del 
tuo comando sarebbe cosě:
tuo_comando --expr 4*5 --func func(5,2) --message "Saluti, Daniele"
oppure:
tuo_comando -e 4*5 -f func(5,2) -m "Saluti, Daniele"
e, con Getopt::Long, una volta definita la prima (con 3 righe) ottieni 
automaticamente (gratis) anche la seconda, ed anche questa ulteriore 
variante:
tuo_comando -expr 4*5 -func func(5,2) -message "Ciao Daniele!"
Fatto tutto questo discorso, tieni infine presente che se solo rinunci 
alle virgole, separando i tuoi argomenti con degli spazi, senza l'uso di 
alcun modulo aggiuntivo, invocando il tuo comando in questo modo:
tuo_comando 4*5 func(5,2) "Saluti, Daniele"
automaticamente ti ritrovi i 3 parametri nei primi 3 elementi dell'array 
predefinito @ARGV.
(In questo modo hai perň implicitamente stabilito un vincolo posizionale 
per i tuoi argomenti, cosa che accadeva anche con la virgola ovviamente).
Ciao,
Emanuele.

 comando 10*5,func(5,2),"Saluti, Daniele"
Cosa intendi con "comando" ?
Leggendo l'altro ramo del thread mi viene in mente che forse
stai cercando di implementare un piccolo linguaggio in Perl.
Corretto?
ciao,
S.
-- 
Stefano Rodighiero      |  
http://larsen.perlmonk.org/blog
mailto:
lar...@perl.it
   |  Italian Perl Mongers: 
http://www.perl.it

 POSIX per i parametri della linea di comando [...]
Il fatto e' che il mio "comando" non e' da interndersi "a linea di 
comando", ma bensi facente parte di una specie di script con cui 
programmo una mia applicazione. Mi spiego meglio: l'utente scrite un 
testo (lo script) e con la mia app Perl devo interpretarlo ed eseguire 
le operazioni richieste.
Grazie!


 Corretto?
Esatto, diciamo che la mia applicazione deve essere "programmata" con 
degli script che devo interpretare.
Ciao!


 le operazioni richieste.
Insomma un mini linguaggio, come diceva Stefano.
(Ti risparmio qualche pedanteria sul piano lessicale, che sarei tentato 
di non reprimere ;-)
Se devi implementare un mini linguaggio ti tocca quasi obbligatoriamente 
(tranne giusto nei casi piů elementari) scriverti un parser ricorsivo 
(per questo ci sono Parse::RecDescent o Parse::Yapp, che ti permettono 
di farlo dichiarativamente), e poi ti tocca anche scrivere un "lexer" o 
"tokenizer" che dir si voglia (ma questo č in genere piuttosto semplice...)
Che dirti... Auguri! ;-)
Ciao,
Emanuele.

 Che dirti... Auguri! ;-)


 degli script che devo interpretare.
Allora potresti usare Parse::RecDescent :)
Trovi parecchie informazioni nel pod e su Parse::RecDescent::FAQ [1].
HTH,
[1] 
http://search.cpan.org/search?mode=module&query=parse::recdescent
.
-- 
.           .  |"In girum imus nocte et consumimur igni"
| , ,-. ,-. |  |                             -- Virgilio
|<  |   ,-| |  |
' ` '   `-^ `' |<uggc://jjj.gurqvenpfrn.bet>

 Riciclero' del vecchio codice che scrissi tempo addietro...
Purtroppo è sufficiente che nel tuo mini linguaggio ci siano strutture 
ricorsive con annidamento arbitrario (coppie di parentesi, funtori etc.) 
perché le regex non bastino più e si esca quindi dal caso elementare (ed 
è proprio per la frequenza di questi casi che Perl 6 ha introdotto le 
"rule" e le "grammar", che permettono di definire un parser ricorsivo 
avanzato direttamente nel linguaggio.)
Se non permetti annidamenti o se ne limiti il livello massimo, un parser 
basato su regex è possibile (ma risulta comunque complicato da mantenere...)
Facci sapere come va a finire ;-)
Ciao,
Emanuele.

 Facci sapere come va a finire ;-)
Non so se e' finita, ma per ora e' andata cosi'...
sub SplitArgs {
my @arg;
$args=shift(@_);
$nargs=0;
$flag34=0;
$pcount=0;
for($i=0;$i<=length($args);$i++) {
  $this=substr($args,$i,1);
  if ($this eq '(') { $pcount++; }
  if ($pcount == 0) {
    if ($this eq ',' && $flag34==0) {
      $this='';
      $nargs++;
    }
    if ($this eq chr(34)) { $flag34=not $flag34; }
  } else {
    if ($this eq ')') { $pcount--; }
  }
  $arg[$nargs].=$this;
}
return @arg;
}
...perdonate un po' la prolissita' del mio Perl. La funzione accetta i 
parametri dell'istruzione, quindi ad esempio:
10*2,"Grazie, prego!",func(func2("jjj",1),123)
...e mi ritorna l'array @arg del tipo...
10*2
"Grazie, prego!"
func(func2("jjj",1),123)
...che passo poi a Safe per la valutazione.
Qualsiasi ottimizzazione/correzione che vorrete propormi e' gradita!
Ciao,



Beh, se č solo questo quello che devi fare, allora puoi cavartela piů a 
buon mercato cosě:
sub splitargs {
     return split /,(?![^\(]*\)|[^"]*"[^"]*(?:"[^"]*")*$)/, shift;
}
Ho detto "solo questo" perché il tuo mini parser non fa il parsing delle 
funzioni passate come argomento e non gestisce gli errori di sintassi 
(pensa ad esempio ad un'invocazione in cui l'utente si dimentica di 
chiudere una parentesi: func(func2("jjj",1,123),10*2,"Grazie, prego!"
in questo caso il tuo parser restituirebbe un unico argomento senza 
segnalare l'errore).
Se le specifiche sono solo queste quindi, una semplice regex basta e 
avanza (e anzi nel caso dell'errore di sintassi di cui nell'esempio si 
comporterebbe anche /un po'/ meglio, riuscendo comunque a restituire 3 
argomenti, di cui gli ultimi 2 corretti, e renderebbe piů facile il 
successivo controllo sulla correttezza del primo).
Detto questo la tua sub va benissimo (tranne qualche inosservanza 
stilistica ;-) e puň costituire la base per qualcosa di piů sofisticato 
(anche se in questo caso torno a consigliarti l'uso di Parse::RecDescent 
o Parse::Yapp, che sono un po' complicati all'inizio, ma che poi ti 
ripagano abbondantemente).
Ciao,
Emanuele.

 ,(?![^\(]*\)|[^"]*"[^"]*(?:"[^"]*")*$)/
...posto il prossimo messaggio non appena riusciro' a decifrare quel 
che hai scritto! :)
Scherzi a parte ti ringrazio davvero per il contributo, non tardero' 
ad approfittarne nuovamente visto che finora ho utilizzato Perl solo 
per piccole cose e ora mi sono invece imposto di sfruttarlo un po' 
piu' seriamente.
Ciao!


> Allora potresti usare Parse::RecDescent :)
Ti ringrazio, per ora applico la r.e. di Emanuele che mi e' 
sufficiente... vedremo un futuro...
Ciao,


 piu' seriamente.
E vedrai che verrai ampiamente ripagato.
Ciao,
Emanuele.

 sufficiente... vedremo un futuro...
E' proprio in quel "vedremo un futuro" la chiave di tutto! :D
Senza nulla togliere ovviamente al sistema delle regexp, che è
probabilmente più performante e più "immediato" da mettere in piedi che
non studiarsi un modulo complesso come Parse::RecDescent.
La differenza tra i due sistemi, parlando sempre dalla mia ridotta
esperienza e cmq ignorando quasi del tutto le tue necessità, sta nella
chiarezza e nella flessibilità.
Con Parse::RecDescent puoi creare tanti mattoncini per poi assemblarli
in regole chiare, riorganizzandoli poi a piacere. Anche lo stesso fatto
di poter implementare delle callback (o actions, come vengono chiamate)
rende il tutto ancora più flessibile. Tieni anche conto che la
grammatica può essere messa in un file a parte, così che a fronte di un
solo "motore" puoi avere risultati diversi a seconda della grammatica
che ti serve.
In conclusione ti dico: se sai che non dovrai *mai* toccare la parte
della tua applicazione che gestisce lo "scripting", allora vai di
regexp. Ma se pensi che dovrai toccarla spesso o anche solo una volta
tra qualche mese, magari dovendola un po' stravolgere (so come vanno
queste cose, i clienti chiedono un tostapane ed in realtà intendono
"forno a legna" :D) pensa alla possibilità Parse::RecDescent.
Andate in pace... ah, no, questa omelia mi ha confuso! :P
My 2 cents,
-- 
.           .  | "In girum imus nocte et consumimur igni."


 }
Altro errore purtroppo :-(
Non c'è niente da fare, è impossibile matchare i pattern annidati (con 
livello arbitrario) con una regex *normale*, e difatti anche la 
soluzione proposta sopra, in almeno un caso (lasciato come esercizio al 
lettore ;-) fallisce.
Ma fortunatamente Perl offre anche i /pattern ricorsivi/, con i quali si 
possono gestire queste situazioni (da precisare che non si tratta di 
regex pure, dal momento che viene valutato codice Perl (a match time) 
all'interno del pattern, per cui tutto quello detto in precedenza circa 
i limiti di applicabilità delle regex rimane valido).
In definitiva la soluzione corretta diventa:
sub splitargs_3 {
     my $bal; $bal = qr{\((?:(?>[^()]+)|(??{$bal}))*\)};
     return
         split /,(?=(?:[^()]*$bal[^()]*)*$|[^()]*$)
                 (?![^"]*"[^"]*(?:"[^"]*"[^"]*)*$)/x,
               shift
}
che, con una buona scorta di Optalidon e Cibalgina, non è poi così 
difficile da interpretare ;-)
Volendosi risparmiare i mal di testa, si potevano usare i moduli CPAN 
Regexp::Common::balanced e Regexp::Common::delimited (ma vuoi mettere la 
soddisfazione... ;-)
Buona notte,
Emanuele.

 esercizio al lettore ;-) fallisce.
Uhm... stavo sempre cercando di interpretare l'ultima funzione 
consigliata... con questa mi spiazzi davvero!
Suppongo ci sia una piccola perdita di prestazioni, ma sara' il niente 
rispetto alla sub originale...
Tante grazie ancora!!!


 rispetto alla sub originale...
In realtà la tua subroutine è estremamanete efficiente, quasi quanto di 
meglio si possa fare (almeno come complessità asintotica), perché legge 
la stringa degli argomenti una sola volta.
La regex invece viene applicata (dalla funzione split) di nuovo a tutta 
la porzione restante della stringa, ogni volta che si incontra una virgola.
A causa di questo fatto la complessità computazionale di splitargs_3 è 
(almeno) quadratica nella dimensione della stringa degli argomenti 
(mentre la tua subroutine è lineare).
Poi accade che per stringhe piccole vinca splitargs_3, ma solo perché la 
funzione split e le regex sono altamente ottimizzate nel Perl. Infatti 
con stringhe sufficientemente lunghe la tua SplitArgs originale prevale.
Ho fatto qualche esperimento (col modulo Benchmarks) e la cosa è 
pienamente confermata.
Giusto per completare la giornata, ho anche scritto una nuova subroutine 
ottimizzata per la velocità, con un approccio completamente diverso (una 
split iniziale che brutalmente splitta in corrispondenza di ogni 
virgola, e poi del codice che rimette assieme gli argomenti che non 
dovevano essere splittati), e ho messo in lizza anche questa soluzione:
sub splitargs_fast {
	my @args = split ',', $_[0];
	my $parens = $args[0] =~ tr/\(// - $args[0] =~ tr/\)//;
	my $delim  = $args[0] =~ tr/"//;
	my $j = 0;
	for my $i (1 .. $#args-1) {	
		if ( ! $parens && $delim % 2 == 0 ) {
			$args[++$j] = $args[$i]
		} else {
			$args[$j] .= ',' . $args[$i]
		}
		$delim  += $args[$i] =~ tr/"//;
		$parens += $args[$i] =~ tr/\(// - $args[$i] =~ tr/\)//;
	}
	if ( ! $parens && $delim % 2 == 0 ) {
		$args[++$j] = $args[$#args]
	} else {
		$args[$j] .= ',' . $args[$#args]
	}
	$#args = $j;
	return @args
}
Il risultato è che alla fine splitargs_fast prevale sempre, mentre man 
mano che la dimensione della stringa cresce si verifica l'inversione di 
splitargs_3 che diventa progressivamente la soluzione più lenta di 
tutte, come ci si aspettava.
Con una stringa di input corta:
func("s1","s2",3)
si ha:
                    Rate SplitArgs orig.  splitargs_fast     splitargs_3
SplitArgs orig. 23496/s              --            -59%            -70%
splitargs_fast  57372/s            144%              --            -26%
splitargs_3     78003/s            232%             36%              --
mentre con una stringa "media":
func(func2("jjj",1)),123,10*2,"Grazie, prego!"
si ha:
                    Rate SplitArgs orig.     splitargs_3  splitargs_fast
SplitArgs orig.  8798/s              --            -67%            -73%
splitargs_3     26274/s            199%              --            -20%
splitargs_fast  32960/s            275%             25%              --
Infine con una stringa lunga:
"a,a,,,","f(a,b)",f(a),f(b,c),"ame, 
rica","ff",f(d),",,,",f("a,a,a","b"),a,a,,,","f(a,b)",f(a),f(b,c),"ame, 
rica","ff",f(d),",,,",f("a,a,a","b")
si ha:
                   Rate     splitargs_3 SplitArgs orig.  splitargs_fast
splitargs_3     1492/s              --            -47%            -72%
SplitArgs orig. 2838/s             90%              --            -47%
splitargs_fast  5398/s            262%             90%              --
che mostra l'inversione tra SplitArgs originale e splitargs_3, che 
diventa la più lenta in assoluto, come ci si aspettava dall'analisi 
"teorica".
Un'altra ora buttata (ma è divertente ;-)
Ciao,
Emanuele.

 risultato sperato.
Riprendo questo thread perche` e` appena uscito un modulo [1] che
sembra fare al caso tuo: Data::Record.
    ----------------------------------------------------
    use strict;
    use warnings;
    use Regexp::Common;
    use Data::Record;
    use Data::Dumper;
    my $string = qq{10*5,func(5,2),"Saluti, Daniele"};
    my $record = Data::Record->new( {
        split  => qq{,} ,
        unless => $RE{quoted}
    } );
    print Dumper( $record->records( $string ) );
    ----------------------------------------------------
  calvin:~ larsen$ perl 
record.pl
 
  $VAR1 = [
            '10*5',
            'func(5',
            '2)',
            '"Saluti, Daniele"'
          ];
ciao,
S.
[1] Potrebbe essere necessario scaricarlo da 
search.cpan.org
. 
    Sul mirror che uso via CPAN.pm non era ancora presente. 
    Inoltre credo ci sia un errore nella documentazione, per 
    la precisione nella SYNOPSIS, dove viene detto di usare 
    una hash e non una hashref nel costruttore. L'ho gia`
    segnalato all'autore.


     segnalato all'autore.
E sembra che lui l'abbia apprezzato :-)
http://use.perl.org/~Ovid/journal/26780
Ciao,
Emanuele.

Emanuele Zeppieri <ema...@REtiscaliMOVE.it> wrote:

È stato abbastanza atipico anche per me. Non guardo assiduamente
search.cpan.org/recent
 (pur ritenendola una buona abitudine), e
invece stamane l'ho guardato. Ho visto Data::Record, e a dirla
tutta il primo pensiero e` stato "beh, _ecco_ un brutto nome per
un modulo" :), cosi` sono andato a vedere cosa caspita facesse. 
Leggendo la synopsis mi sono ricordato (male) del post di 
Daniele, e il resto lo sai pure tu .)
ciao,
S.


 sembra fare al caso tuo: Data::Record.
Ti ringrazio, non mancero' di provarlo, anche se, devo ammettere, la 
soluzione di Emanuale mi permette di "risparmiare" un modulo!
Magari Emanuele implementa le statistiche nella procedura usando anche 
questo modulo? ...e poi ci fa un bell'articolone! :-)
Grazie,
Daniele

 questo modulo?
Raccolgo il tuo invito (con Benchmarks ci vuole un attimo), utilizzando 
la seguente invocazione per Data::Record:
sub s_ovid {
     my $record = Data::Record->new( {
         split  => qq{,} ,
         unless => $RE{quoted}
     } );
     return $record->records( $_[0] )
}
che, come già detto, in realtà *non* risolve il tuo problema (quindi 
diamo ad s_ovid anche un bel vantaggio, perché gli risparmiamo una regex 
quadratica).
Utilizzando le stringhe di test del benchmark precedente (stringa breve, 
media e lunga) e rinominando per motivi di spazio le subroutine in 
lizza, i risultati sono:
            Rate  s_ovid s_orig.  s_fast     s_3
s_ovid   3931/s      --    -83%    -93%    -95%
s_orig. 23116/s    488%      --    -59%    -69%
s_fast  56402/s   1335%    144%      --    -25%
s_3     75643/s   1824%    227%     34%      --
            Rate  s_ovid s_orig.     s_3  s_fast
s_ovid   3809/s      --    -57%    -85%    -88%
s_orig.  8767/s    130%      --    -66%    -73%
s_3     25740/s    576%    194%      --    -20%
s_fast  32123/s    743%    266%     25%      --
           Rate     s_3  s_ovid s_orig.  s_fast
s_3     1453/s      --    -33%    -48%    -72%
s_ovid  2166/s     49%      --    -22%    -58%
s_orig. 2773/s     91%     28%      --    -46%
s_fast  5176/s    256%    139%     87%      --
Per essere corretti bisogna rilevare che s_ovid è l'unica soluzione del 
lotto che utilizza un'interfaccia OO (quella di data:.Record) che è 
notevolmente più lenta. Inoltre s_ovid utilizza anche un altro modulo 
esterno (Regexp::Common).
 > ...e poi ci fa un bell'articolone! :-)
Per quanto riguarda quest'invito, mi permetto invece di girarlo al 
mittente, che ormai ne sa più di me sull'argomento ;-)
Ciao,
Emanuele.

 parametro func(5,2), che invece doveva essere lasciato integro.
Dannaz. Non me ne ero accorto, sorry. 
S.


 mittente, che ormai ne sa più di me sull'argomento ;-)
Uhm... non riesco a fare troppe cose contemporaneamente... intanto 
porto avanti questo progetto, poi vediamo!
Ciao!!!
