Skip to content

Commit

Permalink
Add a builtin:: namespace, with true/false/isbool
Browse files Browse the repository at this point in the history
This finishes the perl-visible API required for RFC 0008
  https://github.com/Perl/RFCs/blob/master/rfcs/rfc0008.md

It also begins the "builtin::" namespace of RFC 0009
  https://github.com/Perl/RFCs/blob/master/rfcs/rfc0009.md
  • Loading branch information
leonerd committed Nov 29, 2021
1 parent 2a98b8c commit 6a2e756
Show file tree
Hide file tree
Showing 15 changed files with 322 additions and 10 deletions.
3 changes: 3 additions & 0 deletions MANIFEST
Expand Up @@ -14,6 +14,7 @@ AUTHORS Contact info for contributors
autodoc.pl Creates pod/perlintern.pod and pod/perlapi.pod
av.c Array value code
av.h Array value header
builtin.c Functions in the builtin:: namespace
caretx.c C file to create $^X
cflags.SH A script that emits C compilation flags per file
Changes Describe how to peruse changes between releases
Expand Down Expand Up @@ -4806,6 +4807,8 @@ lib/Benchmark.pm Measure execution time
lib/Benchmark.t See if Benchmark works
lib/blib.pm For "use blib"
lib/blib.t blib.pm test
lib/builtin.pm builtin function namespace
lib/builtin.t test builtin function namespace
lib/bytes.pm Pragma to enable byte operations
lib/bytes.t bytes.pm test
lib/bytes_heavy.pl Support routines for byte pragma
Expand Down
4 changes: 2 additions & 2 deletions Makefile.SH
Expand Up @@ -532,7 +532,7 @@ h = $(h1) $(h2) $(h3) $(h4) $(h5) $(h6)
c1 = av.c scope.c op.c doop.c doio.c dump.c gv.c hv.c mg.c reentr.c mro_core.c perl.c
c2 = perly.c pp.c pp_hot.c pp_ctl.c pp_sys.c regcomp.c regexec.c utf8.c sv.c
c3 = taint.c toke.c util.c deb.c run.c universal.c pad.c globals.c keywords.c
c3 = taint.c toke.c util.c deb.c run.c builtin.c universal.c pad.c globals.c keywords.c
c4 = perlio.c numeric.c mathoms.c locale.c pp_pack.c pp_sort.c caretx.c dquote.c time64.c
c5 = $(mallocsrc)
Expand All @@ -548,7 +548,7 @@ $spitshell >>$Makefile <<'!NO!SUBS!'
c = $(c1) $(c2) $(c3) $(c4) $(c5) miniperlmain.c $(mini_only_src)
obj1 = $(mallocobj) gv$(OBJ_EXT) toke$(OBJ_EXT) perly$(OBJ_EXT) pad$(OBJ_EXT) regcomp$(OBJ_EXT) dump$(OBJ_EXT) util$(OBJ_EXT) mg$(OBJ_EXT) reentr$(OBJ_EXT) mro_core$(OBJ_EXT) keywords$(OBJ_EXT)
obj1 = $(mallocobj) gv$(OBJ_EXT) toke$(OBJ_EXT) perly$(OBJ_EXT) pad$(OBJ_EXT) regcomp$(OBJ_EXT) dump$(OBJ_EXT) util$(OBJ_EXT) mg$(OBJ_EXT) reentr$(OBJ_EXT) mro_core$(OBJ_EXT) keywords$(OBJ_EXT) builtin$(OBJ_EXT)
obj2 = hv$(OBJ_EXT) av$(OBJ_EXT) run$(OBJ_EXT) pp_hot$(OBJ_EXT) sv$(OBJ_EXT) pp$(OBJ_EXT) scope$(OBJ_EXT) pp_ctl$(OBJ_EXT) pp_sys$(OBJ_EXT)
obj3 = doop$(OBJ_EXT) doio$(OBJ_EXT) regexec$(OBJ_EXT) utf8$(OBJ_EXT) taint$(OBJ_EXT) deb$(OBJ_EXT) globals$(OBJ_EXT) perlio$(OBJ_EXT) numeric$(OBJ_EXT) mathoms$(OBJ_EXT) locale$(OBJ_EXT) pp_pack$(OBJ_EXT) pp_sort$(OBJ_EXT) caretx$(OBJ_EXT) dquote$(OBJ_EXT) time64$(OBJ_EXT)
Expand Down
1 change: 1 addition & 0 deletions Porting/Maintainers.pl
Expand Up @@ -1439,6 +1439,7 @@ package Maintainers;
lib/User/pwent.{pm,t}
lib/_charnames.pm
lib/blib.{pm,t}
lib/builtin.{pm,t}
lib/bytes.{pm,t}
lib/bytes_heavy.pl
lib/charnames.{pm,t}
Expand Down
103 changes: 103 additions & 0 deletions builtin.c
@@ -0,0 +1,103 @@
/* builtin.c
*
* Copyright (C) 2021 by Paul Evans and others
*
* You may distribute under the terms of either the GNU General Public
* License or the Artistic License, as specified in the README file.
*
*/

/* This file contains the code that implements functions in perl's "builtin::"
* namespace
*/

#include "EXTERN.h"
#include "perl.h"

#include "XSUB.h"

XS(XS_builtin_true);
XS(XS_builtin_true)
{
dXSARGS;
if(items)
croak_xs_usage(cv, "");
XSRETURN_YES;
}

XS(XS_builtin_false);
XS(XS_builtin_false)
{
dXSARGS;
if(items)
croak_xs_usage(cv, "");
XSRETURN_NO;
}

XS(XS_builtin_isbool);
XS(XS_builtin_isbool)
{
dXSARGS;
if(items != 1)
croak_xs_usage(cv, "sv");

SV *sv = ST(0);
if(SvIsBOOL(sv))
XSRETURN_YES;
else
XSRETURN_NO;
}

XS(XS_builtin_import);
XS(XS_builtin_import)
{
dXSARGS;

if(!PL_compcv)
Perl_croak(aTHX_
"builtin::import can only be called at compiletime");

/* We need to have PL_comppad / PL_curpad set correctly for lexical importing */
ENTER;
SAVESPTR(PL_comppad_name); PL_comppad_name = PadlistNAMES(CvPADLIST(PL_compcv));
SAVESPTR(PL_comppad); PL_comppad = PadlistARRAY(CvPADLIST(PL_compcv))[1];
SAVESPTR(PL_curpad); PL_curpad = PadARRAY(PL_comppad);

for(int i = 1; i < items; i++) {
SV *sym = ST(i);
if(strEQ(SvPV_nolen(sym), "import")) goto unavailable;

SV *ampname = sv_2mortal(Perl_newSVpvf(aTHX_ "&%" SVf, SVfARG(sym)));
SV *fqname = sv_2mortal(Perl_newSVpvf(aTHX_ "builtin::%" SVf, SVfARG(sym)));

CV *cv = get_cv(SvPV_nolen(fqname), SvUTF8(fqname) ? SVf_UTF8 : 0);
if(!cv) goto unavailable;

PADOFFSET off = pad_add_name_sv(ampname, padadd_STATE, 0, 0);
SvREFCNT_dec(PL_curpad[off]);
PL_curpad[off] = SvREFCNT_inc(cv);
continue;

unavailable:
Perl_croak(aTHX_
"'%" SVf "' is not recognised as a builtin function", sym);
}

intro_my();

LEAVE;
}

void
Perl_boot_core_builtin(pTHX)
{
newXS_flags("builtin::true", &XS_builtin_true, __FILE__, NULL, 0);
newXS_flags("builtin::false", &XS_builtin_false, __FILE__, NULL, 0);
newXS_flags("builtin::isbool", &XS_builtin_isbool, __FILE__, NULL, 0);

newXS_flags("builtin::import", &XS_builtin_import, __FILE__, NULL, 0);
}

/*
* ex: set ts=8 sts=4 sw=4 et:
*/
1 change: 1 addition & 0 deletions embed.fnc
Expand Up @@ -666,6 +666,7 @@ ApR |U8 |block_gimme
: Used in perly.y
ApdR |int |block_start |int full
Aodxp |void |blockhook_register |NN BHK *hk
p |void |boot_core_builtin
: Used in perl.c
p |void |boot_core_UNIVERSAL
: Used in perl.c
Expand Down
1 change: 1 addition & 0 deletions embed.h
Expand Up @@ -1241,6 +1241,7 @@
#define bind_match(a,b,c) Perl_bind_match(aTHX_ a,b,c)
#define boot_core_PerlIO() Perl_boot_core_PerlIO(aTHX)
#define boot_core_UNIVERSAL() Perl_boot_core_UNIVERSAL(aTHX)
#define boot_core_builtin() Perl_boot_core_builtin(aTHX)
#define boot_core_mro() Perl_boot_core_mro(aTHX)
#define cando(a,b,c) Perl_cando(aTHX_ a,b,c)
#define check_utf8_print(a,b) Perl_check_utf8_print(aTHX_ a,b)
Expand Down
102 changes: 102 additions & 0 deletions lib/builtin.pm
@@ -0,0 +1,102 @@
package builtin 0.001;

use strict;
use warnings;

# All code, including &import, is implemented by always-present functions in
# the perl interpreter itself.
# See also `builtin.c` in perl source

1;
__END__
=head1 NAME
builtin - Perl pragma to import built-in utility functions
=head1 SYNOPSIS
use builtin qw( true false isbool );
=head1 DESCRIPTION
Perl provides several utility functions in the C<builtin> package. These are
plain functions, and look and behave just like regular user-defined functions
do. They do not provide new syntax or require special parsing. These functions
are always present in the interpreter and can be called at any time by their
fully-qualified names. By default they are not available as short names, but
can be requested for convenience.
Individual named functions can be imported by listing them as import
parameters on the C<use> statement for this pragma.
=head2 Lexical Import
This pragma module creates I<lexical> aliases in the currently-compiling scope
to these builtin functions. This is similar to the lexical effect of other
pragmas such as L<strict> and L<feature>.
sub classify
{
my $sv = shift;
use builtin 'isbool';
return isbool($sv) ? "boolean" : "not a boolean";
}
# the isbool() function is no longer visible here
# but may still be called by builtin::isbool()
Because these functions are imported lexically, rather than by package
symbols, the user does not need to take any special measures to ensure they
don't accidentally appear as object methods from a class.
package An::Object::Class {
use builtin 'true', 'false';
...
}
# does not appear as a method
An::Object::Class->true;
# Can't locate object method "true" via package "An::Object::Class"
# at ...
=head1 FUNCTIONS
=head2 true
$val = true;
Returns the boolean truth value. While any scalar value can be tested for
truth and most defined, non-empty and non-zero values are considered "true"
by perl, this one is special in that L</isbool> considers it to be a
distinguished boolean value.
This gives an equivalent value to expressions like C<!!1> or C<!0>.
=head2 false
$val = false;
Returns the boolean fiction value. While any non-true scalar value is
considered "false" by perl, this one is special in that L</isbool> considers
it to be a distinguished boolean value.
This gives an equivalent value to expressions like C<!!0> or C<!1>.
=head2 isbool
$bool = isbool($val);
Returns true when given a distinguished boolean value, or false if not. A
distinguished boolean value is the result of any boolean-returning builtin
function (such as C<true> or C<isbool> itself), boolean-returning operator
(such as the C<eq> or C<==> comparison tests or the C<!> negation operator),
or any variable containing one of these results.
=head1 SEE ALSO
L<perlop>, L<perlfunc>, L<Scalar::Util>
=cut
78 changes: 78 additions & 0 deletions lib/builtin.t
@@ -0,0 +1,78 @@
#!./perl

BEGIN {
chdir 't' if -d 't';
require './test.pl';
set_up_inc('../lib');
}

use strict;
use warnings;

# booleans
{
use builtin qw( true false isbool );

ok(true, 'true is true');
ok(!false, 'false is false');

ok(isbool(true), 'true is bool');
ok(isbool(false), 'false is bool');
ok(!isbool(undef), 'undef is not bool');
ok(!isbool(1), '1 is not bool');
ok(!isbool(""), 'empty is not bool');

my $truevar = (5 == 5);
my $falsevar = (5 == 6);

ok(isbool($truevar), '$truevar is bool');
ok(isbool($falsevar), '$falsevar is bool');

ok(isbool(isbool(true)), 'isbool true is bool');
ok(isbool(isbool(123)), 'isbool false is bool');
}

# imports are lexical; should not be visible here
{
my $ok = eval 'true()'; my $e = $@;
ok(!$ok, 'true() not visible outside of lexical scope');
like($e, qr/^Undefined subroutine &main::true called at /, 'failure from true() not visible');
}

# lexical imports work fine in a variety of situations
{
sub regularfunc {
use builtin 'true';
return true;
}
ok(regularfunc(), 'true in regular sub');

my sub lexicalfunc {
use builtin 'true';
return true;
}
ok(lexicalfunc(), 'true in lexical sub');

my $coderef = sub {
use builtin 'true';
return true;
};
ok($coderef->(), 'true in anon sub');

sub recursefunc {
use builtin 'true';
return recursefunc() if @_;
return true;
}
ok(recursefunc("rec"), 'true in self-recursive sub');

my $recursecoderef = sub {
use feature 'current_sub';
use builtin 'true';
return __SUB__->() if @_;
return true;
};
ok($recursecoderef->("rec"), 'true in self-recursive anon sub');
}

done_testing();
23 changes: 17 additions & 6 deletions pad.c
Expand Up @@ -2208,11 +2208,22 @@ S_cv_clone(pTHX_ CV *proto, CV *cv, CV *outside, HV *cloned)
CvNAME_HEK_set(cv, share_hek_hek(CvNAME_HEK(proto)));
else CvGV_set(cv,CvGV(proto));
CvSTASH_set(cv, CvSTASH(proto));
OP_REFCNT_LOCK;
CvROOT(cv) = OpREFCNT_inc(CvROOT(proto));
OP_REFCNT_UNLOCK;
CvSTART(cv) = CvSTART(proto);
CvOUTSIDE_SEQ(cv) = CvOUTSIDE_SEQ(proto);

/* It is unlikely that proto is an xsub, but it could happen; e.g. if a
* module has performed a lexical sub import trick on an xsub. This
* happens with builtin::import, for example
*/
if (UNLIKELY(CvISXSUB(proto))) {
CvXSUB(cv) = CvXSUB(proto);
CvXSUBANY(cv) = CvXSUBANY(proto);
}
else {
OP_REFCNT_LOCK;
CvROOT(cv) = OpREFCNT_inc(CvROOT(proto));
OP_REFCNT_UNLOCK;
CvSTART(cv) = CvSTART(proto);
CvOUTSIDE_SEQ(cv) = CvOUTSIDE_SEQ(proto);
}

if (SvPOK(proto)) {
sv_setpvn(MUTABLE_SV(cv), SvPVX_const(proto), SvCUR(proto));
Expand All @@ -2222,7 +2233,7 @@ S_cv_clone(pTHX_ CV *proto, CV *cv, CV *outside, HV *cloned)
if (SvMAGIC(proto))
mg_copy((SV *)proto, (SV *)cv, 0, 0);

if (CvPADLIST(proto))
if (!CvISXSUB(proto) && CvPADLIST(proto))
cv = S_cv_clone_pad(aTHX_ proto, cv, outside, cloned, newcv);

DEBUG_Xv(
Expand Down
1 change: 1 addition & 0 deletions perl.c
Expand Up @@ -2437,6 +2437,7 @@ S_parse_body(pTHX_ char **env, XSINIT_t xsinit)

boot_core_PerlIO();
boot_core_UNIVERSAL();
boot_core_builtin();
boot_core_mro();
newXS("Internals::V", S_Internals_V, __FILE__);

Expand Down

0 comments on commit 6a2e756

Please sign in to comment.