2010-07-26 4 views
8

Étant donnéDétection des variables de package déclarées en perl

# package main; 
our $f; 
sub f{} 
sub g {} 
1; 

Comment puis-je déterminer que $f, mais pas $g, a été déclarée? Hors de la manchette, je pensais que *{main::g}{SCALAR} pourrait être indéfini, mais c'est un bona fide SCALAR réf. Fond: Je voudrais importer une variable dans main::, mais la carpe ou le croassement si cette variable est déjà déclarée.

EDIT Ajout d'un sous-programme f en réponse à la réponse initiale de @ DVK.

ANSWER (2010-07-27)

Ce n'est pas facile, mais il est possible.

Un eval technique est le plus portable, travaillant sur des perls plus anciens que 5.10. Dans des perls plus récents, des modules introspectifs comme Devel::Peek et B peuvent discriminer.

Répondre

1

Je lui ai donné mon meilleur, allant même jusqu'à tenter de demander eval STRING si $main::f avait été déclarée par our ou my. (Cela nécessitait de dupliquer, de fermer et de restaurer plus tard STDERR pour réduire le bavardage.) Une fois que vous avez modifié les paquets, ces déclarations ne semblent plus visibles sur une interruption temporaire.

La technique ci-dessous détecter si $f a été déclarée par

use vars qw/ $f /; 

code ci-dessous:

package MyModule; 

use warnings; 
use strict; 

# using $f will confuse the compiler, generating 
# warnings of 'Variable "%f" is not available' 
# although we're going for $main::f 
my $__f = "from MyModule"; 

my %IMPORT_OK = (
    '$f' => [f => \$__f], 
); 

sub import { 
    my($pkg,@imports) = @_; 
    my $callpkg = caller; 

    die "I don't speak your dirty Pig-Latin" 
    if $callpkg !~ /\A\w+(::\w+)*\z/ || 
     grep !/\A[\[email protected]%]\w+\z/, @imports; 

    foreach my $name (@imports) { 
    my($sym,$ref) = @{ $IMPORT_OK{$name} || [] }; 
    die "unknown import: $name" unless $sym; 

    open my $saverr, ">&", \*STDERR or die "dup STDERR: $!"; 
    close STDERR; 

    my $declared = eval qq{ 
     package $callpkg; 
     my(undef)=$name; 
     1; 
    }; 

    open STDERR, ">&", $saverr or print "restore STDERR: $!"; 
    die "${callpkg}::$sym already exists" if $declared; 

    { 
     no strict 'refs'; 
     *{$callpkg . "::" . $sym} = $ref; 
    } 
    } 
} 

1; 
+0

+1, et je n'augmente pas normalement eval() s string. :) C'est plus ou moins mon approche actuelle. Il est important de noter que la vérification eval n'invoque * pas * la méthode FETCH de tie() d scalar - ce serait No Good (tm). Je me demande, est-ce que local() isant $ SIG {__ WARN__} s'occupe des messages d'erreur? – pilcrow

+0

Oui, FWIW, dans mon test si vous localisez le gestionnaire _ \ _ WARN \ _ \ _ (et $ @, aussi, par politesse) avant l'eval, vous faites taire les erreurs sans duppage de descripteur de fichier. – pilcrow

4

RÉSUMÉ

À ce stade, après des recherches assez vaste, je suis d'une opinion ferme que dans une situation où une entrée de table de symbole avec le nom « X » a été déclarée mais non affecté à, il est impossible de distinguer génériquement lequel des types de référence dans un glob a été réellement déclaré sans utiliser le sondage approfondi de Devel :: stuff.

En d'autres termes, vous pouvez dire que les 2 situations distinctes:

  1. X n'a ​​pas été déclarée à tous (entrée de table de symbole n'existe pas)

  2. X a été déclaré et certains les types de glob ont été réellement assignés à.

    Dans ce second cas,

    • Vous peut trouver parmi les types de glob ont été affectés à et qui ne sont pas

    • MAIS, vous ne peut pas figure sur lequel des les types de globaux non affectés à ont été déclarés et non assignés par rapport à ceux qui n'ont pas été déclarés du tout.

    En d'autres termes, pour our $f = 1; our @f;; nous pouvons dire que $main::f est un scalaire; mais nous ne pouvons pas dire si @f et %f ont été déclarés ou non - il ne se distingue pas du tout de our $f = 1; our %f;. Veuillez noter que les définitions de sous-routine suivent également cette deuxième règle, mais que la déclaration d'un sous nommé lui attribue automatiquement une valeur (le bloc de code), donc vous ne pouvez jamais avoir un sous-nom dans un "déclaré mais non attribué" state (caveat: peut-être pas vrai pour les prototypes ??? aucun indice).

ORIGINAL RÉPONSE

Eh bien, très limité (et à mon avis un peu fragile) solution à distinguer un scalaire à partir d'un sous-programme pourrait être d'utiliser UNIVERSAL :: peut:

use strict; 
our $f; 
sub g {}; 
foreach my $n ("f","g","h") { 
    # First off, check if we are in main:: namespace, 
    # and if we are, that we are a scalar 
    no strict "refs"; 
    next unless exists $main::{$n} && *{"main::$n"}; 
    use strict "refs"; 
    # Now, we are a declared scalr, unless we are a executable subroutine: 
    print "Declared: \$$n\n" unless UNIVERSAL::can("main",$n) 
} 

Résultat :

Declared: $f 

Veuillez noter que {SCALAR} ne semble pas fonctionner à éliminer les non-scalaires dans mes tests - il a heureusement passé par @A et %H si je les déclarais et ajouté à la boucle.

MISE À JOUR

J'ai essayé d brian l'approche de foy du chapitre 8 du « Maîtriser perl » et en quelque sorte n'a pas pu obtenir de travailler pour scalaires, hachages ou de tableaux; mais comme il est indiqué ci-dessous par draegtun il travaille pour des sous-routines ou pour les variables qui ont été attribuées à déjà:

> perl5.8 -we '{use strict; use Data::Dumper; 
    our $f; sub g {}; our @A=(); sub B{}; our $B; our %H=(); 
    foreach my $n ("f","g","h","STDOUT","A","H","B") { 
     no strict "refs"; 
     next unless exists $main::{$n}; 
     print "Exists: $n\n"; 
     if (defined ${$n}) { print "Defined scalar: $n\n"}; 
     if (defined @{$n}) { print "Defined ARRAY: $n\n"}; 
     if (defined %{$n}) { print "Defined HASH: $n\n"}; 
     if (defined &{$n}) { print "Defined SUB: $n\n"}; 
     use strict "refs";}}'  

Exists: f 
Exists: g 
Defined SUB: g   <===== No other defined prints worked 
Exists: STDOUT 
Exists: A 
Exists: H 
Exists: B 
Defined SUB: B   <===== No other defined prints worked 
+0

+1 Excellente tentative. FWIW, je ne suis pas sûr que \ * {nom} {SCALAR} * soit jamais faux * - c'est le test existant (qui peut être fait 'strict'-ly) qui saute" h ". Cette vérification échoue cependant si sub f {} est également défini. – pilcrow

+0

@pilcrow - ouais, c'est exactement ce que je voulais dire dans le dernier paragraphe. Et vous avez raison sur '$ f' +' & f' ... comme je l'ai dit, c'était un peu fragile et limité. – DVK

+1

@pilcrow @DVK De perlref: "' * foo {THING} 'retourne' undef' si ce THING particulier n'a pas encore été utilisé, sauf dans le cas des scalaires. '* Foo {SCALAR}' retourne une référence à un scalaire anonyme si '$ foo' n'a pas encore été utilisé. Cela pourrait changer dans une future version." Cela rend '& main :: f' versus' $ main :: f' un cas confondant. –

0

Vous pouvez vérifier un sous-programme défini comme ceci:

say 'g() defined in main' if defined &{'main::g'}; 

Malheureusement, la même méthode que fonctionne sur la variable de package si une valeur a été affectée:

our $f = 1; 
say '$f defined with value in main' if defined ${'main::f'}; 

/I3az/

1

Devel :: Peek semble pouvoir distinguer entre les choses utilisés et non utilisés dans la fente SCALAIRE:

use strict; 
use warnings; 
use Devel::Peek; 

our $f; 
sub f { } 
sub g { } 

Dump(*f); 
Dump(*g); 

La sortie est:

SV = PVGV(0x187360c) at 0x182c0f4 
    REFCNT = 3 
    FLAGS = (MULTI,IN_PAD) 
    NAME = "f" 
    NAMELEN = 1 
    GvSTASH = 0x24a084 "main" 
    GP = 0x1874bd4 
    SV = 0x182c0a4 
    REFCNT = 1 
    IO = 0x0 
    FORM = 0x0 
    AV = 0x0 
    HV = 0x0 
    CV = 0x24a234 
    CVGEN = 0x0 
    LINE = 6 
    FILE = "c:\temp\foo.pl" 
    FLAGS = 0xa 
    EGV = 0x182c0f4 "f" 
SV = PVGV(0x187362c) at 0x18514dc 
    REFCNT = 2 
    FLAGS = (MULTI,IN_PAD) 
    NAME = "g" 
    NAMELEN = 1 
    GvSTASH = 0x24a084 "main" 
    GP = 0x1874cbc 
    SV = 0x0 
    REFCNT = 1 
    IO = 0x0 
    FORM = 0x0 
    AV = 0x0 
    HV = 0x0 
    CV = 0x1865234 
    CVGEN = 0x0 
    LINE = 8 
    FILE = "c:\temp\foo.pl" 
    FLAGS = 0xa 
    EGV = 0x18514dc "g" 

Les lignes d'intérêt sont dans la section GP = , en particulier SV, AV, HV et CV (scalaire, tableau, hachage et code, respectivement). Notez que le vidage de *g montre SV = 0x0. Malheureusement, il ne semble pas y avoir de moyen programmatique pour obtenir cette information. Une approche d'instrument contondant serait de capturer la sortie de Dump() et l'analyser.

+1

Et les anciens perls (pré-5.10) auront toujours quelque chose dans la fente scalaire ... Sur les plus nouveaux perls, vous pouvez tester '$ {B :: svrev_2object (\\ * f) -> SV} == 0 ' – ysth

3

ancien Perls (pré-5.10) auront toujours quelque chose dans la fente scalaire.

Sur les plus récents perls, il semble que l'ancien comportement est imité lorsque vous essayez de faire * FOO {SCALAR}.

Vous pouvez utiliser le module d'introspection B pour vérifier l'emplacement scalaire, cependant:

# package main; 
our $f; 
sub f {} 
sub g {} 

use B; 
use 5.010; 
if (${ B::svref_2object(\*f)->SV }) { 
    say "f: Thar be a scalar tharrr!"; 
} 
if (${ B::svref_2object(\*g)->SV }) { 
    say "g: Thar be a scalar tharrr!"; 
} 

1; 
Questions connexes