Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to test if a scalar has been stringified or not?

Tags:

perl

I am writing a thing to output something similar to JSON, from a perl structure. I want the quoting to behave like this:

"string" outputs "string"
"05" outputs "05"
"5" outputs "5"
5 outputs 5
05 outputs 5, or 05 would be acceptable

JSON::XS handles this by testing if a scalar has been "stringified" or not, which I think is very cool. But I can't find a way to do this test myself without writing XS, which I'd rather avoid. Is this possible? I can't find this anywhere on CPAN without finding vast pedantry about Scalar::Util::looks_like_number, etc which completely isn't what I want. The only stopgap I can find is Devel::Peek, which feels evil. And also, just like JSON::XS, I'm fine with this secenario:

my $a = 5;
print $a."\n";
# now $a outputs "5" instead of 5)
like image 221
Yobert Avatar asked Mar 20 '12 20:03

Yobert


2 Answers

Inspect the output of B::svref_2object:

use B;
($x, $y, $z) = ("5", 5, 5.0);

print ref(B::svref_2object( \$x )), "\n";
print ref(B::svref_2object( \$y )), "\n";
print ref(B::svref_2object( \$z )), "\n";

Output:

B::PV
B::IV
B::NV

Or, as ikegami suggests, if you'd rather lookup the pPOK flag:

if (B::svref_2object( \$x )->FLAGS & B::SVp_POK) {
    print "I guess \$x is stringy\n";
}
like image 137
mob Avatar answered Sep 24 '22 20:09

mob


You can make it so a number no longer appears to have been stringified by using the following:

$x = 0+$x;

For example,

$ perl -MJSON::XS -E'
   $_ = 4;
   say encode_json([$_]);  # [4]
   "".$_;
   say encode_json([$_]);  # ["4"]
   $_ = 0 + $_;
   say encode_json([$_]);  # [4]
'

Detecting whether something has been stringified is tougher because JSON::XS is looking into Perl internals. One could use the following:

sub is_stringy {
   { no warnings 'void'; "".$_[0]; }
   return 1;
}

but I don't think that's what you want :) I don't know how to detect the "corruption" without writing some XS code. What you want to know is if SvPOKp is true for the scalar (after you call SvGETMAGIC on the scalar).

use Inline C => <<'__EOI__';

   SV* is_stringy(SV* sv) {
      SvGETMAGIC(sv);
      return SvPOKp(sv) ? &PL_sv_yes : &PL_sv_no;
   }

__EOI__

$_ = 4;
say is_stringy($_) ?1:0;   # 0
{ no warnings 'void'; "".$_; }
say is_stringy($_) ?1:0;   # 1
$_ = 0+$_;
say is_stringy($_) ?1:0;   # 0

oo! It turns out that B does provide SVp_POK, so it can (almost) be done in without writing new XS code

use B qw( svref_2object SVp_POK );

sub is_stringy {
   my ($s) = @_;
   my $sv = svref_2object(\$s);
   #$sv->GETMAGIC();  # Not available
   return $sv->FLAGS & SVp_POK;
}

Being unable to call SvGETMAGIC has drawbacks, but it will work almost all of the time.

like image 36
ikegami Avatar answered Sep 25 '22 20:09

ikegami