diff --git a/MANIFEST b/MANIFEST index 4718b8e4646a..94fc2d8cdf8a 100644 --- a/MANIFEST +++ b/MANIFEST @@ -5191,7 +5191,10 @@ ext/XS-APItest/t/stuff_modify_bug.t test for eval side-effecting source string ext/XS-APItest/t/stuff_svcur_bug.t test for a bug in lex_stuff_pvn ext/XS-APItest/t/subcall.t Test XSUB calls ext/XS-APItest/t/subsignature.t Test parse_subsignature() +ext/XS-APItest/t/sv_numcmp.t Test sv_numcmp ext/XS-APItest/t/sv_numeq.t Test sv_numeq +ext/XS-APItest/t/sv_numlget.t Test sv_num[lg][et] +ext/XS-APItest/t/sv_numne.t Test sv_numne ext/XS-APItest/t/sv_streq.t Test sv_streq ext/XS-APItest/t/svcat.t Test sv_catpvn ext/XS-APItest/t/svcatpvf.t Test sv_catpvf argument reordering diff --git a/embed.fnc b/embed.fnc index 7e6ea1afe56d..e6110cf8b329 100644 --- a/embed.fnc +++ b/embed.fnc @@ -3414,11 +3414,41 @@ Cdp |SV * |sv_newref |NULLOK SV * const sv Adp |void |sv_nosharing |NULLOK SV *sv : Used in pp.c, pp_hot.c, sv.c dpx |SV * |sv_2num |NN SV * const sv +Admp |I32 |sv_numcmp |NULLOK SV *sv1 \ + |NULLOK SV *sv2 +Adp |I32 |sv_numcmp_flags|NULLOK SV *sv1 \ + |NULLOK SV *sv2 \ + |const U32 flags Admp |bool |sv_numeq |NULLOK SV *sv1 \ |NULLOK SV *sv2 Adp |bool |sv_numeq_flags |NULLOK SV *sv1 \ |NULLOK SV *sv2 \ |const U32 flags +Admp |bool |sv_numge |NULLOK SV *sv1 \ + |NULLOK SV *sv2 +Adp |bool |sv_numge_flags |NULLOK SV *sv1 \ + |NULLOK SV *sv2 \ + |const U32 flags +Admp |bool |sv_numgt |NULLOK SV *sv1 \ + |NULLOK SV *sv2 +Adp |bool |sv_numgt_flags |NULLOK SV *sv1 \ + |NULLOK SV *sv2 \ + |const U32 flags +Admp |bool |sv_numle |NULLOK SV *sv1 \ + |NULLOK SV *sv2 +Adp |bool |sv_numle_flags |NULLOK SV *sv1 \ + |NULLOK SV *sv2 \ + |const U32 flags +Admp |bool |sv_numlt |NULLOK SV *sv1 \ + |NULLOK SV *sv2 +Adp |bool |sv_numlt_flags |NULLOK SV *sv1 \ + |NULLOK SV *sv2 \ + |const U32 flags +Admp |bool |sv_numne |NULLOK SV *sv1 \ + |NULLOK SV *sv2 +Adp |bool |sv_numne_flags |NULLOK SV *sv1 \ + |NULLOK SV *sv2 \ + |const U32 flags Adip |NV |SvNV |NN SV *sv Adp |NV |sv_2nv_flags |NN SV * const sv \ |const I32 flags @@ -6055,6 +6085,12 @@ S |const char *|sv_display|NN SV * const sv \ |NN char *tmpbuf \ |STRLEN tmpbuf_size S |bool |sv_2iuv_common |NN SV * const sv +S |bool |sv_numcmp_common \ + |NULLOK SV **sv1 \ + |NULLOK SV **sv2 \ + |const U32 flags \ + |int method \ + |NN SV **result S |STRLEN |sv_pos_b2u_midway \ |SPTR const U8 * const s \ |MPTR const U8 * const target \ diff --git a/embed.h b/embed.h index 42a12ea3dc79..9ccf8ff72262 100644 --- a/embed.h +++ b/embed.h @@ -726,7 +726,13 @@ # define sv_newmortal() Perl_sv_newmortal(aTHX) # define sv_newref(a) Perl_sv_newref(aTHX_ a) # define sv_nosharing(a) Perl_sv_nosharing(aTHX_ a) +# define sv_numcmp_flags(a,b,c) Perl_sv_numcmp_flags(aTHX_ a,b,c) # define sv_numeq_flags(a,b,c) Perl_sv_numeq_flags(aTHX_ a,b,c) +# define sv_numge_flags(a,b,c) Perl_sv_numge_flags(aTHX_ a,b,c) +# define sv_numgt_flags(a,b,c) Perl_sv_numgt_flags(aTHX_ a,b,c) +# define sv_numle_flags(a,b,c) Perl_sv_numle_flags(aTHX_ a,b,c) +# define sv_numlt_flags(a,b,c) Perl_sv_numlt_flags(aTHX_ a,b,c) +# define sv_numne_flags(a,b,c) Perl_sv_numne_flags(aTHX_ a,b,c) # define sv_peek(a) Perl_sv_peek(aTHX_ a) # define sv_pos_b2u(a,b) Perl_sv_pos_b2u(aTHX_ a,b) # define sv_pos_b2u_flags(a,b,c) Perl_sv_pos_b2u_flags(aTHX_ a,b,c) @@ -1685,6 +1691,7 @@ # define sv_2iuv_common(a) S_sv_2iuv_common(aTHX_ a) # define sv_add_arena(a,b,c) S_sv_add_arena(aTHX_ a,b,c) # define sv_display(a,b,c) S_sv_display(aTHX_ a,b,c) +# define sv_numcmp_common(a,b,c,d,e) S_sv_numcmp_common(aTHX_ a,b,c,d,e) # define sv_pos_b2u_midway(a,b,c,d) S_sv_pos_b2u_midway(aTHX_ a,b,c,d) # define sv_pos_u2b_cached(a,b,c,d,e,f,g) S_sv_pos_u2b_cached(aTHX_ a,b,c,d,e,f,g) # define sv_pos_u2b_forwards S_sv_pos_u2b_forwards @@ -2344,7 +2351,13 @@ # define Perl_sv_force_normal(mTHX,a) sv_force_normal(a) # define Perl_sv_insert(mTHX,a,b,c,d,e) sv_insert(a,b,c,d,e) # define Perl_sv_mortalcopy(mTHX,a) sv_mortalcopy(a) +# define Perl_sv_numcmp(mTHX,a,b) sv_numcmp(a,b) # define Perl_sv_numeq(mTHX,a,b) sv_numeq(a,b) +# define Perl_sv_numge(mTHX,a,b) sv_numge(a,b) +# define Perl_sv_numgt(mTHX,a,b) sv_numgt(a,b) +# define Perl_sv_numle(mTHX,a,b) sv_numle(a,b) +# define Perl_sv_numlt(mTHX,a,b) sv_numlt(a,b) +# define Perl_sv_numne(mTHX,a,b) sv_numne(a,b) # define Perl_sv_pv(mTHX,a) sv_pv(a) # define Perl_sv_pvbyte(mTHX,a) sv_pvbyte(a) # define Perl_sv_pvn_force(mTHX,a,b) sv_pvn_force(a,b) @@ -2445,7 +2458,13 @@ # define Perl_sv_force_normal sv_force_normal # define Perl_sv_insert sv_insert # define Perl_sv_mortalcopy sv_mortalcopy +# define Perl_sv_numcmp sv_numcmp # define Perl_sv_numeq sv_numeq +# define Perl_sv_numge sv_numge +# define Perl_sv_numgt sv_numgt +# define Perl_sv_numle sv_numle +# define Perl_sv_numlt sv_numlt +# define Perl_sv_numne sv_numne # define Perl_sv_pv sv_pv # define Perl_sv_pvbyte sv_pvbyte # define Perl_sv_pvn_force sv_pvn_force diff --git a/ext/XS-APItest/APItest.pm b/ext/XS-APItest/APItest.pm index ae76f0519e12..33c23f50f2c7 100644 --- a/ext/XS-APItest/APItest.pm +++ b/ext/XS-APItest/APItest.pm @@ -4,7 +4,7 @@ use strict; use warnings; use Carp; -our $VERSION = '1.47'; +our $VERSION = '1.48'; require XSLoader; diff --git a/ext/XS-APItest/APItest.xs b/ext/XS-APItest/APItest.xs index dbd03f8314d8..124326516aa6 100644 --- a/ext/XS-APItest/APItest.xs +++ b/ext/XS-APItest/APItest.xs @@ -1640,6 +1640,8 @@ signal_thread_start(void *arg) { # define hwm_checks_enabled() false #endif +typedef SV *nullable_SV; + MODULE = XS::APItest PACKAGE = XS::APItest INCLUDE: const-xs.inc @@ -5005,21 +5007,75 @@ test_HvNAMEf_QUOTEDPREFIX(sv) OUTPUT: RETVAL +TYPEMAP: < 24; +use XS::APItest; +use Config; +use strict; + +my $four = 4; +is sv_numcmp($four, 4), 0, '$four == 4'; +is sv_numcmp($four, 5), -1, '$four < 5'; + +is sv_numcmp(5, $four), 1, '5 > $four'; + +SKIP: +{ + $Config{d_double_has_nan} + or skip "No NAN", 2; + my $nan = 0+"NaN"; + is sv_numcmp($nan, 0), 2, '$nan not comparable'; + is sv_numcmp($nan, $nan), 2, '$nan not comparable even with itself'; +} + +my $six_point_five = 6.5; # an exact float, so == is fine +is sv_numcmp($six_point_five, 6.5), 0, '$six_point_five == 6.5'; +is sv_numcmp($six_point_five, 6.6), -1, '$six_point_five < 6.6'; + +# NULLs +is sv_numcmp(undef, 1), -1, "NULL sv1"; +is sv_numcmp(1, undef), 1, "NULL sv2"; + +# GMAGIC +"10" =~ m/(\d+)/; +is sv_numcmp_flags($1, 10, 0), -1, 'sv_numcmp_flags with no flags does not GETMAGIC'; +is sv_numcmp_flags($1, 10, SV_GMAGIC), 0, 'sv_numecmp_flags with SV_GMAGIC does'; + +# overloading +{ + package AlwaysTen { + use overload + '<=>' => sub { + return $_[2] ? $_[1] <=> 10 : 10 <=> $_[1] + }, + '0+' => sub { 123456 }; + } + my $obj = bless([], "AlwaysTen"); + + is sv_numcmp($obj, 10), 0, 'AlwaysTen is 10'; + is sv_numcmp($obj, 11), -1, 'AlwaysTen is not 11'; + is sv_numcmp(10, $obj), 0, 'AlwaysTen is 10 on the right'; + is sv_numcmp(11, $obj), 1, 'AlwaysTen is not 11 on the right'; + + SKIP: + { + $Config{d_double_has_nan} + or skip "No NAN", 1; + my $nan = 0+"NaN"; + + is sv_numcmp($obj, $nan), 2, 'AlwaysTen vs $nan is not comparable'; + } + + is sv_numcmp_flags($obj, 10, SV_SKIP_OVERLOAD), 1, + 'AlwaysTen is not 10 with SV_SKIP_OVERLOAD'; +} + +# +0 overloading with large numbers and using fallback +{ + my $big = ~0; + my $bigm1 = $big-1; + package MyBigNum { + use overload "0+" => sub { $_[0][0] }, + fallback => 1; + } + my $o1 = bless [ $big ], "MyBigNum"; + my $o2 = bless [ $big ], "MyBigNum"; + my $o3 = bless [ $bigm1 ], "MyBigNum"; + + is $o1 <=> $o2, 0, "perl op gets it right"; + is $o1 <=> $bigm1, 1, "perl op still gets it right for left overload"; + is $o1 <=> $o3, 1, "perl op still gets it right for different values"; + is sv_numcmp($o1, $o2), 0, "sv_numcmp two overloads"; + is sv_numcmp($o1, $o3), 1, "sv_numcmp two different overloads"; + is sv_numcmp($o1, $big), 0, "sv_numcmp left overload"; + is sv_numcmp($bigm1, $o3), 0, "sv_numcmp right overload"; +} diff --git a/ext/XS-APItest/t/sv_numeq.t b/ext/XS-APItest/t/sv_numeq.t index 6439a48d2b6b..12c319677a9a 100644 --- a/ext/XS-APItest/t/sv_numeq.t +++ b/ext/XS-APItest/t/sv_numeq.t @@ -1,15 +1,29 @@ #!perl -use Test::More tests => 9; +use Test::More tests => 22; use XS::APItest; +use Config; my $four = 4; ok sv_numeq($four, 4), '$four == 4'; ok !sv_numeq($four, 5), '$four != 5'; +SKIP: +{ + $Config{d_double_has_nan} + or skip "No NAN", 2; + my $nan = 0+"NaN"; + ok !sv_numeq($nan, 0), '$nan != 0'; + ok !sv_numeq($nan, $nan), '$nan != $nan'; +} + my $six_point_five = 6.5; # an exact float, so == is fine ok sv_numeq($six_point_five, 6.5), '$six_point_five == 6.5'; -ok !sv_numeq($six_point_five, 6.6), '$six_point_five == 6.6'; +ok !sv_numeq($six_point_five, 6.6), '$six_point_five != 6.6'; + +# NULLs +ok sv_numeq(undef, 0), "NULL sv1"; +ok sv_numeq(0, undef), "NULL sv2"; # GMAGIC "10" =~ m/(\d+)/; @@ -27,6 +41,30 @@ ok sv_numeq_flags($1, 10, SV_GMAGIC), 'sv_numeq_flags with SV_GMAGIC does'; ok sv_numeq($obj, 10), 'AlwaysTen is 10'; ok !sv_numeq($obj, 11), 'AlwaysTen is not 11'; + ok sv_numeq(10, $obj), 'AlwaysTen is 10 on the right'; + ok !sv_numeq(11, $obj), 'AlwaysTen is not 11 on the right'; ok !sv_numeq_flags($obj, 10, SV_SKIP_OVERLOAD), 'AlwaysTen is not 10 with SV_SKIP_OVERLOAD' } + +# +0 overloading with large numbers and using fallback +{ + my $big = ~0; + my $bigm1 = $big-1; + package MyBigNum { + use overload "0+" => sub { $_[0][0] }, + fallback => 1; + } + my $o1 = bless [ $big ], "MyBigNum"; + my $o2 = bless [ $big ], "MyBigNum"; + my $o3 = bless [ $bigm1 ], "MyBigNum"; + + ok $o1 == $o2, "perl op gets it right"; + ok $o1 == $big, "perl op still gets it right for left overload"; + ok !($o1 == $o3), "perl op still gets it right for different values"; + ok sv_numeq($o1, $o2), "sv_numeq two overloads"; + ok !sv_numeq($o1, $o3), "sv_numeq two different overloads" + or diag sprintf "%x vs %x", $o1, $o3; + ok sv_numeq($o1, $big), "sv_numeq left overload"; + ok sv_numeq($bigm1, $o3), "sv_numeq right overload"; +} diff --git a/ext/XS-APItest/t/sv_numlget.t b/ext/XS-APItest/t/sv_numlget.t new file mode 100644 index 000000000000..3bb27dbc0b71 --- /dev/null +++ b/ext/XS-APItest/t/sv_numlget.t @@ -0,0 +1,53 @@ +#!perl +# tests the numeric sv_num[lg][te]() APIs + +use Test::More; +use XS::APItest; +use strict; + +# overloading +package AlwaysTen { + use overload + '<=>' => sub { + return $_[2] ? $_[1] <=> 10 : 10 <=> $_[1] + }, + '0+' => sub { 123456 }; +} + +# +0 overloading with large numbers and using fallback +package MyBigNum { + use overload + "0+" => sub { $_[0][0] }, + fallback => 1; +} + +my @values = + ( + [ ~0 ], + [ ~0-1 ], + [ -int(~0/2) ], + [ 1.001 ], + [ 1.002 ], + [ bless([ ~0 ], "MyBigNum"), "bignum ~0" ], + [ bless([ ~0 ], "MyBigNum"), "bignum ~0 #2" ], + [ bless([ ~0-1 ], "MyBigNum"), "bignum ~0-1" ], + [ undef(), "undef" ], + [ 0+"NaN", "NaN" ], + ); + +for my $x (@values) { + for my $y (@values) { + for my $func ( [ "le", sub { $_[0] <= $_[1] }, \&sv_numle ], + [ "lt", sub { $_[0] < $_[1] }, \&sv_numlt ], + [ "ge", sub { $_[0] >= $_[1] }, \&sv_numge ], + [ "gt", sub { $_[0] > $_[1] }, \&sv_numgt ]) { + my ($op, $native, $api) = @$func; + my $lname = $x->[1] // $x->[0]; + my $rname = $y->[1] // $y->[0]; + is($api->($x->[0], $x->[1]), $native->($x->[0], $x->[1]), + "$lname $op $rname"); + } + } +} + +done_testing; diff --git a/ext/XS-APItest/t/sv_numne.t b/ext/XS-APItest/t/sv_numne.t new file mode 100644 index 000000000000..f74dc393af1d --- /dev/null +++ b/ext/XS-APItest/t/sv_numne.t @@ -0,0 +1,74 @@ +#!perl + +use Test::More tests => 24; +use XS::APItest; +use Config; + +my $four = 4; +ok !sv_numne($four, 4), '$four != 4'; +ok sv_numne($four, 5), '$four == 5'; + +SKIP: +{ + $Config{d_double_has_nan} + or skip "No NAN", 2; + my $nan = 0+"NaN"; + ok sv_numne($nan, 0), '$nan != 0'; + ok sv_numne($nan, $nan), '$nan != $nan'; +} + +my $six_point_five = 6.5; # an exact float, so == is fine +ok !sv_numne($six_point_five, 6.5), '$six_point_five == 6.5'; +ok sv_numne($six_point_five, 6.6), '$six_point_five != 6.6'; + +# NULLs +ok sv_numne(undef, 1), "NULL sv1"; +ok sv_numne(1, undef), "NULL sv2"; + +# GMAGIC +"11" =~ m/(\d+)/; +ok sv_numne_flags($1, 11, 0), 'sv_numne_flags with no flags does not GETMAGIC'; +ok !sv_numne_flags($1, 11, SV_GMAGIC), 'sv_numne_flags with SV_GMAGIC does'; + +{ + package AlwaysTwelve { + use overload + '!=' => sub { return $_[1] != 12 }, + '0+' => sub { 11 }; + } + my $obj = bless([], "AlwaysTwelve"); + + ok !sv_numne($obj, 12), 'AlwaysTwelve is 12'; + ok sv_numne($obj, 11), 'AlwaysTwelve is not 11'; + ok !sv_numne(12, $obj), 'AlwaysTwelve is 12 on right'; + ok sv_numne(11, $obj), 'AlwayeTwelve is not 11 on the right'; + + ok !sv_numne_flags($obj, 11, SV_SKIP_OVERLOAD), 'AlwaysTwelve is 12 with SV_SKIP_OVERLOAD'; + + my $result; + void_sv_numne($obj, 11, $result); + ok($result, "overloaded sv_numne() (ne) in void context"); + void_sv_numne($obj, 12, $result); + ok(!$result, "overloaded sv_numne() (eq) in void context"); +} + +# +0 overloading with large numbers and using fallback +{ + my $big = ~0; + my $bigm1 = $big-1; + package MyBigNum { + use overload "0+" => sub { $_[0][0] }, + fallback => 1; + } + my $o1 = bless [ $big ], "MyBigNum"; + my $o2 = bless [ $big ], "MyBigNum"; + my $o3 = bless [ $bigm1 ], "MyBigNum"; + + ok !($o1 != $o2), "perl op gets it right"; + ok $o1 != $bigm1, "perl op still gets it right for left overload"; + ok $o1 != $o3, "perl op still gets it right for different values"; + ok !sv_numne($o1, $o2), "sv_numne two overloads"; + ok sv_numne($o1, $o3), "sv_numne two different overloads"; + ok !sv_numne($o1, $big), "sv_numne left overload"; + ok !sv_numne($bigm1, $o3), "sv_numne right overload"; +} diff --git a/gv.c b/gv.c index bfbce344cbb0..b0fcd1751a8e 100644 --- a/gv.c +++ b/gv.c @@ -3748,8 +3748,20 @@ The operation is done only on just one operand. The operation changes one of the operands, e.g., $x += 1 +=item C + +In many cases amagic_call() uses the L context of the +current OP when calling the sub handling the overload. This flag +forces amagic_call() to use scalar context. + =back +=for apidoc Amnh||AMGf_noleft +=for apidoc Amnh||AMGf_noright +=for apidoc Amnh||AMGf_unary +=for apidoc Amnh||AMGf_assign +=for apidoc Amnh||AMGf_force_scalar + =cut */ @@ -4142,7 +4154,7 @@ Perl_amagic_call(pTHX_ SV *left, SV *right, int method, int flags) * with the context of individual concats being scalar, * regardless of the overall context of the multiconcat op */ - U8 gimme = (force_scalar || !PL_op || PL_op->op_type == OP_MULTICONCAT) + U8 gimme = (force_scalar || (flags & AMGf_force_scalar) || !PL_op ) ? G_SCALAR : GIMME_V; CATCH_SET(TRUE); diff --git a/pod/perldelta.pod b/pod/perldelta.pod index 4e87d9d6228b..908604392000 100644 --- a/pod/perldelta.pod +++ b/pod/perldelta.pod @@ -359,9 +359,23 @@ whether the string buffer contents are "live" or not.) See GH#23967 for an example of where such a copy was noticeable. +Fixed a bug in the L API where values with numeric +("0+") overloading but not equality or numeric comparison overloading +would always be compared as floating point values. This could lead to +large integers being reported as equal when they weren't. + =item * -XXX +Added L, sv_numle, sv_numlt, sv_numge, sv_numgt and +L APIs that perform numeric comparison in the same +way perl does, including overloading. Separate APIs for each +comparison are needed to invoke their corresponding overload when +needed. Inspired by [GH #23918] + +=item * + +Added the C flag to the L> +API to force scalar context for overload calls. =back diff --git a/pp.h b/pp.h index c06817ddef17..c1ae8c3ad55e 100644 --- a/pp.h +++ b/pp.h @@ -653,14 +653,15 @@ Does not use C. See also C>, C> and C>. (void)Perl_tmps_grow_p(aTHX_ eMiX); \ } STMT_END -#define AMGf_noright 1 -#define AMGf_noleft 2 -#define AMGf_assign 4 /* op supports mutator variant, e.g. $x += 1 */ -#define AMGf_unary 8 -#define AMGf_numeric 0x10 /* for Perl_try_amagic_bin */ - -#define AMGf_want_list 0x40 -#define AMGf_numarg 0x80 +#define AMGf_noright 1 +#define AMGf_noleft 2 +#define AMGf_assign 4 /* op supports mutator variant, e.g. $x += 1 */ +#define AMGf_unary 8 +#define AMGf_numeric 0x0010 /* for Perl_try_amagic_bin */ + +#define AMGf_want_list 0x0040 +#define AMGf_numarg 0x0080 +#define AMGf_force_scalar 0x0100 /* do SvGETMAGIC on the stack args before checking for overload */ diff --git a/pp_hot.c b/pp_hot.c index b8ec0eb46875..ac5d2067c26b 100644 --- a/pp_hot.c +++ b/pp_hot.c @@ -1377,7 +1377,8 @@ PP(pp_multiconcat) ) { SV * const tmpsv = amagic_call(left, right, concat_amg, - (nextappend ? AMGf_assign: 0)); + (nextappend ? AMGf_assign: 0) + | AMGf_force_scalar); if (tmpsv) { /* NB: tryAMAGICbin_MG() includes an OPpTARGET_MY test * here, which isn't needed as any implicit diff --git a/proto.h b/proto.h index acd96ff396ad..8aab855c908b 100644 --- a/proto.h +++ b/proto.h @@ -4812,6 +4812,13 @@ PERL_CALLCONV void Perl_sv_nosharing(pTHX_ SV *sv); #define PERL_ARGS_ASSERT_SV_NOSHARING +/* PERL_CALLCONV I32 +Perl_sv_numcmp(pTHX_ SV *sv1, SV *sv2); */ + +PERL_CALLCONV I32 +Perl_sv_numcmp_flags(pTHX_ SV *sv1, SV *sv2, const U32 flags); +#define PERL_ARGS_ASSERT_SV_NUMCMP_FLAGS + /* PERL_CALLCONV bool Perl_sv_numeq(pTHX_ SV *sv1, SV *sv2); */ @@ -4819,6 +4826,41 @@ PERL_CALLCONV bool Perl_sv_numeq_flags(pTHX_ SV *sv1, SV *sv2, const U32 flags); #define PERL_ARGS_ASSERT_SV_NUMEQ_FLAGS +/* PERL_CALLCONV bool +Perl_sv_numge(pTHX_ SV *sv1, SV *sv2); */ + +PERL_CALLCONV bool +Perl_sv_numge_flags(pTHX_ SV *sv1, SV *sv2, const U32 flags); +#define PERL_ARGS_ASSERT_SV_NUMGE_FLAGS + +/* PERL_CALLCONV bool +Perl_sv_numgt(pTHX_ SV *sv1, SV *sv2); */ + +PERL_CALLCONV bool +Perl_sv_numgt_flags(pTHX_ SV *sv1, SV *sv2, const U32 flags); +#define PERL_ARGS_ASSERT_SV_NUMGT_FLAGS + +/* PERL_CALLCONV bool +Perl_sv_numle(pTHX_ SV *sv1, SV *sv2); */ + +PERL_CALLCONV bool +Perl_sv_numle_flags(pTHX_ SV *sv1, SV *sv2, const U32 flags); +#define PERL_ARGS_ASSERT_SV_NUMLE_FLAGS + +/* PERL_CALLCONV bool +Perl_sv_numlt(pTHX_ SV *sv1, SV *sv2); */ + +PERL_CALLCONV bool +Perl_sv_numlt_flags(pTHX_ SV *sv1, SV *sv2, const U32 flags); +#define PERL_ARGS_ASSERT_SV_NUMLT_FLAGS + +/* PERL_CALLCONV bool +Perl_sv_numne(pTHX_ SV *sv1, SV *sv2); */ + +PERL_CALLCONV bool +Perl_sv_numne_flags(pTHX_ SV *sv1, SV *sv2, const U32 flags); +#define PERL_ARGS_ASSERT_SV_NUMNE_FLAGS + PERL_CALLCONV char * Perl_sv_peek(pTHX_ SV *sv); #define PERL_ARGS_ASSERT_SV_PEEK @@ -9260,6 +9302,11 @@ S_sv_display(pTHX_ SV * const sv, char *tmpbuf, STRLEN tmpbuf_size); # define PERL_ARGS_ASSERT_SV_DISPLAY \ assert(sv); assert(tmpbuf) +STATIC bool +S_sv_numcmp_common(pTHX_ SV **sv1, SV **sv2, const U32 flags, int method, SV **result); +# define PERL_ARGS_ASSERT_SV_NUMCMP_COMMON \ + assert(result) + STATIC STRLEN S_sv_pos_b2u_midway(pTHX_ const U8 * const s, const U8 * const target, const U8 *end, STRLEN endu); # define PERL_ARGS_ASSERT_SV_POS_B2U_MIDWAY \ diff --git a/sv.c b/sv.c index 4e161322646f..f4b4699d94a9 100644 --- a/sv.c +++ b/sv.c @@ -8711,26 +8711,112 @@ Perl_sv_streq_flags(pTHX_ SV *sv1, SV *sv2, const U32 flags) return sv_eq_flags(sv1, sv2, 0); } +PERL_STATIC_INLINE bool +S_sv_numcmp_common(pTHX_ SV **sv1, SV **sv2, const U32 flags, + int method, SV **result) { + if(flags & SV_GMAGIC) { + if(*sv1) + SvGETMAGIC(*sv1); + if(*sv2 && (!*sv1 || *sv1 != *sv2)) + SvGETMAGIC(*sv2); + } + + /* Treat NULL as undef */ + if(!*sv1) + *sv1 = &PL_sv_undef; + if(!*sv2) + *sv2 = &PL_sv_undef; + + if(!(flags & SV_SKIP_OVERLOAD) && + (SvAMAGIC(*sv1) || SvAMAGIC(*sv2))) { + if ((*result = amagic_call(*sv1, *sv2, method, AMGf_force_scalar))) + return true; + + /* normally handled by try_amagic_bin */ + if (SvROK(*sv1)) + *sv1 = sv_2num(*sv1); + if (SvROK(*sv2)) + *sv2 = sv_2num(*sv2); + } + + return false; +} + /* =for apidoc sv_numeq =for apidoc_item sv_numeq_flags +=for apidoc_item sv_numne +=for apidoc_item sv_numne_flags +=for apidoc_item sv_numge +=for apidoc_item sv_numge_flags +=for apidoc_item sv_numgt +=for apidoc_item sv_numgt_flags +=for apidoc_item sv_numle +=for apidoc_item sv_numle_flags +=for apidoc_item sv_numlt +=for apidoc_item sv_numlt_flags + +These return a boolean that is the result of the corresponding numeric +comparison: + +=over + +=item C, C + +Numeric equality, the same as S>. -These each return a boolean indicating if the numbers in the two SV arguments -are identical, coercing them to numbers if necessary, basically behaving like -the Perl code S>. +=item C, C + +Numeric inequality, the same as S>. + +=item C, C + +Numeric less than or equal, the same as S= $sv2>>. + +=item C, C + +Numeric less than, the same as S $sv2>>. + +=item C, C + +Numeric greater than or equal, the same as S= $sv2>>. + +=item C, C + +Numeric greater than, the same as S $sv2>>. + +=back + +Beware that in the presence of overloading the comparisons might not +have their normal properties, eg. C< sv_numeq(sv1, sv2) > might be +different to C< !sv_numne(sv1, sv2) >. + +The non-C<_flags> suffix versions of these functions always perform +get magic and handle the appropriate type of overloading. See +L for details. + +These each return a boolean indicating if the numbers in the two SV +arguments satisfy the given relationship, coercing them to numbers if +necessary, basically behaving like the Perl code. A NULL SV is treated as C. -C always performs 'get' magic. C performs 'get' -magic only if C has the C bit set. +The C<_flags> variants of these functions accept these flags: -C always checks for, and if present, handles C<==> overloading. If -not present, regular numerical comparison will be used instead. -C normally does the same, but setting the C -bit set in C causes it to use regular numerical comparison. +=over -Otherwise, the functions behave identically. +=item C + +Perform 'get' magic on both C amd C if this flag is set, +otherwise 'get' magic is ignored. + +=item C + +Skip any operator overloading implemented for this type and operator. +Be aware that numeric, C<+0>, overloading will still be applied, unless in the scope of C. + +=back =for apidoc Amnh||SV_SKIP_OVERLOAD @@ -8742,27 +8828,148 @@ Perl_sv_numeq_flags(pTHX_ SV *sv1, SV *sv2, const U32 flags) { PERL_ARGS_ASSERT_SV_NUMEQ_FLAGS; - if(flags & SV_GMAGIC) { - if(sv1) - SvGETMAGIC(sv1); - if(sv2) - SvGETMAGIC(sv2); - } + SV *result; + if (UNLIKELY(sv_numcmp_common(&sv1, &sv2, flags, eq_amg, &result))) + return SvTRUE(result); - /* Treat NULL as undef */ - if(!sv1) - sv1 = &PL_sv_undef; - if(!sv2) - sv2 = &PL_sv_undef; + return do_ncmp(sv1, sv2) == 0; +} - if(!(flags & SV_SKIP_OVERLOAD) && - (SvAMAGIC(sv1) || SvAMAGIC(sv2))) { - SV *ret = amagic_call(sv1, sv2, eq_amg, 0); - if(ret) - return SvTRUE(ret); +bool +Perl_sv_numne_flags(pTHX_ SV *sv1, SV *sv2, const U32 flags) +{ + PERL_ARGS_ASSERT_SV_NUMNE_FLAGS; + + SV *result; + if (UNLIKELY(sv_numcmp_common(&sv1, &sv2, flags, ne_amg, &result))) + return SvTRUE(result); + + return do_ncmp(sv1, sv2) != 0; +} + +bool +Perl_sv_numle_flags(pTHX_ SV *sv1, SV *sv2, const U32 flags) +{ + PERL_ARGS_ASSERT_SV_NUMLE_FLAGS; + + SV *result; + if (UNLIKELY(sv_numcmp_common(&sv1, &sv2, flags, le_amg, &result))) + return SvTRUE(result); + + return do_ncmp(sv1, sv2) <= 0; +} + +bool +Perl_sv_numlt_flags(pTHX_ SV *sv1, SV *sv2, const U32 flags) +{ + PERL_ARGS_ASSERT_SV_NUMLT_FLAGS; + + SV *result; + if (UNLIKELY(sv_numcmp_common(&sv1, &sv2, flags, lt_amg, &result))) + return SvTRUE(result); + + return do_ncmp(sv1, sv2) < 0; +} + +bool +Perl_sv_numge_flags(pTHX_ SV *sv1, SV *sv2, const U32 flags) +{ + PERL_ARGS_ASSERT_SV_NUMGE_FLAGS; + + SV *result; + if (UNLIKELY(sv_numcmp_common(&sv1, &sv2, flags, ge_amg, &result))) + return SvTRUE(result); + + I32 cmp = do_ncmp(sv1, sv2); + + return cmp != 2 && cmp >= 0; +} + +bool +Perl_sv_numgt_flags(pTHX_ SV *sv1, SV *sv2, const U32 flags) +{ + PERL_ARGS_ASSERT_SV_NUMGT_FLAGS; + + SV *result; + if (UNLIKELY(sv_numcmp_common(&sv1, &sv2, flags, gt_amg, &result))) + return SvTRUE(result); + + I32 cmp = do_ncmp(sv1, sv2); + + return cmp != 2 && cmp > 0; +} + +/* +=for apidoc sv_numcmp +=for apidoc_item sv_numcmp_flags + +This returns an integer indicating the ordering of the two SV +arguments, coercing them to numbers if necessary, basically behaving +like the Perl code S $sv2 >>. + +A NULL SV is treated as C. + +This will return one of the following values: + +=over + +=item * + +C<1> - C is numerically greater than C + +=item * + +C<0> - C and C are numerically equal. + +=item * + +C<-1> - C is numerically less than C + +=item * + +C<2> - C and C are not numerically comparable, probably +because one of them is C, though overloads can extend that. + +=back + +C always performs 'get' magic. C performs +'get' magic on if C has the C bit set. + +C always checks for, and if present, handles C<< <=> >> +overloading. If not present, regular numerical comparison will be +used instead. +C normally does the same, but if the +C bit is set in C any C<< <=> >> overloading +is ignored and a regular numerical comparison is done instead. + +=cut +*/ + +#define SANE_ORDERING_RESULT(val) \ + ((val) < 0 ? -1 : (val) > 0 ? 1 : 0) + +I32 +Perl_sv_numcmp_flags(pTHX_ SV *sv1, SV *sv2, const U32 flags) +{ + PERL_ARGS_ASSERT_SV_NUMCMP_FLAGS; + + SV *result; + if (UNLIKELY(sv_numcmp_common(&sv1, &sv2, flags, ncmp_amg, &result))) { + /* Similar to what sort() does in amagic_ncmp() */ + if (SvIOK(result) && !SvIsUV(result)) { + IV i = SvIVX(result); + return SANE_ORDERING_RESULT(i); + } + else if (!SvOK(result)) { + return 2; + } + else { + NV nv = SvNV(result); + return SANE_ORDERING_RESULT(nv); + } } - return do_ncmp(sv1, sv2) == 0; + return do_ncmp(sv1, sv2); } /* diff --git a/sv.h b/sv.h index 29f81f907c18..65592bd67981 100644 --- a/sv.h +++ b/sv.h @@ -2322,6 +2322,12 @@ Usually accessed via the C macro. #define sv_cmp(sv1, sv2) sv_cmp_flags(sv1, sv2, SV_GMAGIC) #define sv_cmp_locale(sv1, sv2) sv_cmp_locale_flags(sv1, sv2, SV_GMAGIC) #define sv_numeq(sv1, sv2) sv_numeq_flags(sv1, sv2, SV_GMAGIC) +#define sv_numne(sv1, sv2) sv_numne_flags(sv1, sv2, SV_GMAGIC) +#define sv_numle(sv1, sv2) sv_numle_flags(sv1, sv2, SV_GMAGIC) +#define sv_numlt(sv1, sv2) sv_numlt_flags(sv1, sv2, SV_GMAGIC) +#define sv_numge(sv1, sv2) sv_numge_flags(sv1, sv2, SV_GMAGIC) +#define sv_numgt(sv1, sv2) sv_numgt_flags(sv1, sv2, SV_GMAGIC) +#define sv_numcmp(sv1, sv2) sv_numcmp_flags(sv1, sv2, SV_GMAGIC) #define sv_streq(sv1, sv2) sv_streq_flags(sv1, sv2, SV_GMAGIC) #define sv_collxfrm(sv, nxp) sv_collxfrm_flags(sv, nxp, SV_GMAGIC) #define sv_2bool(sv) sv_2bool_flags(sv, SV_GMAGIC)