Обнаружение объявленных переменных пакета в perl - PullRequest
9 голосов
/ 26 июля 2010

С учетом

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

Как я могу определить, что $f, но не $g, было объявлено? Если бы не манжета, я подумал, что *{main::g}{SCALAR} может быть неопределенным, но это добросовестный СКАЛЯРНЫЙ ref.

Справочная информация: Я хотел бы импортировать переменную в main::, но с карпом или хрипом, если эта переменная уже объявлена.

РЕДАКТИРОВАТЬ Добавлена ​​подпрограмма f в ответ на первоначальный ответ @ DVK.

ОТВЕТ (2010-07-27)

Это не легко, но возможно.

Метод eval является наиболее переносимым, работает на Perls старше 5.10. В более поздних версиях, интроспективные модули, такие как Devel::Peek и B, могут различать.

Ответы [ 5 ]

4 голосов
/ 26 июля 2010

РЕЗЮМЕ

На данный момент, после довольно обширных исследований, я твердо убежден, что в ситуации, когда запись в таблице символов с именем "X" была объявлена, но не присвоена, невозможно вообще отличить какой из ссылочных типов в глобале был фактически объявлен без использования глубокого исследования Devel :: stuff.

Другими словами, вы можете сказать только следующие 2 различные ситуации:

  1. X не был объявлен вообще (запись таблицы символов не существует)

  2. X был объявлен, и некоторые типы глобусов были фактически назначены.

    Во втором случае

    • Вы можете найти, КАКОЙ из типов глобусов был присвоен, а какие не были

    • НО, вы не можете выяснить, какие типы глобусов, не назначенные для, были объявлены и не назначены, а какие не были объявлены вообще.

    Другими словами, для our $f = 1; our @f;; мы можем сказать, что $main::f - скаляр; но мы НЕ можем сказать, были ли объявлены @f и %f или нет - это ничем не отличается от our $f = 1; our %f;.

    Обратите внимание, что определения подпрограмм также следуют этому второму правилу, но объявление именованного подпрограммы автоматически присваивает ему значение (блок кода), поэтому у вас никогда не может быть подимен в состоянии «объявлен, но не присвоен». (предостережение: может быть, это не так для прототипов ??? без понятия).

ОРИГИНАЛЬНЫЙ ОТВЕТ

Ну, очень ограниченным (и ИМХО несколько хрупким) решением отличить скаляр от подпрограммы может быть использование UNIVERSAL :: can:

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)
}

Результат:

Declared: $f

Обратите внимание, что {SCALAR}, похоже, не работает для отсеивания нескаляров в моем тестировании - оно успешно прошло через @A и %H, если я их объявил и добавил в цикл.

UPDATE

Я попробовал подход Брайана Д Фоя из главы 8 «Освоение perl» и почему-то не смог заставить его работать для скаляров, хэшей или массивов; но, как отмечено ниже, draegtun работает для подпрограмм или для переменных , которые уже были назначены :

> 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
3 голосов
/ 26 июля 2010

Старые perls (до 5.10) всегда будут иметь что-то в скалярном слоте.

На более новых perls кажется, что старое поведение имитируется, когда вы пытаетесь выполнить * FOO {SCALAR}.

Вы можете использовать модуль самоанализа B для проверки скалярного слота, хотя:

# 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;
1 голос
/ 26 июля 2010

Я приложил все усилия, даже пытаясь спросить eval STRING, было ли $main::f объявлено через our или my.(Это потребовало дублирования, закрытия и последующего восстановления STDERR, чтобы сократить болтливость.) После того, как вы изменили пакеты, эти объявления больше не кажутся видимыми при временном переключении.

Приведенный ниже метод обнаружитбыл ли $f объявлен с помощью

use vars qw/ $f /;

Код ниже:

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[\$@%]\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;
1 голос
/ 26 июля 2010

Devel :: Peek, по-видимому, может различать используемые и неиспользуемые вещи в слоте SCALAR:

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

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

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

Выход:

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"
* 1006в разделе GP =, в частности SV, AV, HV и CV (скаляр, массив, хэш и код соответственно).Обратите внимание, что дамп *g показывает SV = 0x0.К сожалению, не существует программного способа получить эту информацию.Подход тупого инструмента должен был бы захватить вывод Dump() и проанализировать его.
1 голос
/ 26 июля 2010

Вы можете проверить определенную подпрограмму следующим образом:

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

К сожалению, тот же метод работает только с переменной пакета, если ему присвоено значение:

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

/ I3az /

...