New upstream version 0.21

This commit is contained in:
zhouganqing 2024-07-10 16:56:00 +08:00
parent 50b956e507
commit 30ed21a535
51 changed files with 1518 additions and 303 deletions

4
.editorconfig Normal file
View File

@ -0,0 +1,4 @@
root = true
[*.{pm,pl,t}]
indent_size = 3

View File

@ -1,9 +1,23 @@
use v5;
use strict; use strict;
use warnings; use warnings;
use lib 'inc'; use lib 'inc';
use Module::Build::with::XSTests; use Module::Build::with::XSTests;
my @extra_compiler_flags = qw( -I. -Iinclude -Ihax );
# Perl 5.36 made -std=c99 standard; before then we'll have to request it specially
push @extra_compiler_flags, qw( -std=c99 ) if $^V lt v5.36.0;
push @extra_compiler_flags, qw( -DDEBUGGING=-g ) if $^X =~ m|/debugperl|;
use Config;
if( $Config{ccname} eq "gcc" ) {
# Enable some extra gcc warnings, largely just for author interest
push @extra_compiler_flags, qw( -Wall -Wno-unused-function -Wno-unused-value );
}
my $build = Module::Build::with::XSTests->new( my $build = Module::Build::with::XSTests->new(
module_name => 'XS::Parse::Sublike', module_name => 'XS::Parse::Sublike',
requires => { requires => {
@ -12,8 +26,7 @@ my $build = Module::Build::with::XSTests->new(
}, },
test_requires => { test_requires => {
'Sub::Util' => 0, 'Sub::Util' => 0,
'Test::Fatal' => 0, 'Test2::V0' => 0,
'Test::More' => '0.88', # done_testing
}, },
configure_requires => { configure_requires => {
'Module::Build' => '0.4004', # test_requires 'Module::Build' => '0.4004', # test_requires
@ -22,7 +35,8 @@ my $build = Module::Build::with::XSTests->new(
create_license => 1, create_license => 1,
create_readme => 1, create_readme => 1,
extra_compiler_flags => [qw( -I. -Ihax )], extra_compiler_flags => \@extra_compiler_flags,
c_source => [ "src/" ],
); );
$build->create_build_script; $build->create_build_script;

42
Changes
View File

@ -1,5 +1,47 @@
Revision history for XS-Parse-Sublike Revision history for XS-Parse-Sublike
0.21 2023-10-11
[BUGFIXES]
* Ensure that exceptions thrown from runtime signature handling
appear to come from the callsite and not declaration
* Fix signedness of printf format when complaining about
mismatched `->ver`
0.20 2023-09-09
[BUGFIXES]
* Correct ->VERSION check for Object::Pad or Future::AsyncAwait
cross-module tests (RT149700)
* Don't upset gcc's -Wformat by passing an unbounded STRLEN into
%.*s format (RT133035)
* Avoid colons in filename of t/71extended+Object-Pad.t because
Windows doesn't like them (RT149712)
* Ensure that mixed positional + named params in signatures work
properly (thanks alh)
* Ensure name shadowing of param names prints the right diagnostic
warning (thanks alh)
0.19 2023-09-07
[CHANGES]
* Added `Sublike::Extended`, a prefix keyword to enable the extended
signature parser for named params and attributes
* Beginnings of an (experimental) XS-level API for attributes on
parameters
* Complain on attempts to register a sublike keyword with neither a
permit function nor hinthash key
0.18 2023-06-14
[CHANGES]
* Swap all the unit tests from Test::More to Test2::V0
[BUGFIXES]
* Remember to set `-std=c99` compiler flag on Perls before v5.36
0.17 2023-03-21
[CHANGES]
* Experimental support for named param syntax in parse_subsignature()
* Support core perl's `method` syntax when available by setting
CVf_IsMETHOD (perl >= 5.37.10)
0.16 2021-12-16 0.16 2021-12-16
[CHANGES] [CHANGES]
* No longer support ABI version 3 even in back-compatibility mode * No longer support ABI version 3 even in back-compatibility mode

18
LICENSE
View File

@ -1,4 +1,4 @@
This software is copyright (c) 2021 by Paul Evans <leonerd@leonerd.org.uk>. This software is copyright (c) 2023 by Paul Evans <leonerd@leonerd.org.uk>.
This is free software; you can redistribute it and/or modify it under This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself. the same terms as the Perl 5 programming language system itself.
@ -12,7 +12,7 @@ b) the "Artistic License"
--- The GNU General Public License, Version 1, February 1989 --- --- The GNU General Public License, Version 1, February 1989 ---
This software is Copyright (c) 2021 by Paul Evans <leonerd@leonerd.org.uk>. This software is Copyright (c) 2023 by Paul Evans <leonerd@leonerd.org.uk>.
This is free software, licensed under: This is free software, licensed under:
@ -272,7 +272,7 @@ That's all there is to it!
--- The Artistic License 1.0 --- --- The Artistic License 1.0 ---
This software is Copyright (c) 2021 by Paul Evans <leonerd@leonerd.org.uk>. This software is Copyright (c) 2023 by Paul Evans <leonerd@leonerd.org.uk>.
This is free software, licensed under: This is free software, licensed under:
@ -292,21 +292,21 @@ Definitions:
- "Package" refers to the collection of files distributed by the Copyright - "Package" refers to the collection of files distributed by the Copyright
Holder, and derivatives of that collection of files created through Holder, and derivatives of that collection of files created through
textual modification. textual modification.
- "Standard Version" refers to such a Package if it has not been modified, - "Standard Version" refers to such a Package if it has not been modified,
or has been modified in accordance with the wishes of the Copyright or has been modified in accordance with the wishes of the Copyright
Holder. Holder.
- "Copyright Holder" is whoever is named in the copyright or copyrights for - "Copyright Holder" is whoever is named in the copyright or copyrights for
the package. the package.
- "You" is you, if you're thinking about copying or distributing this Package. - "You" is you, if you're thinking about copying or distributing this Package.
- "Reasonable copying fee" is whatever you can justify on the basis of media - "Reasonable copying fee" is whatever you can justify on the basis of media
cost, duplication charges, time of people involved, and so on. (You will cost, duplication charges, time of people involved, and so on. (You will
not be required to justify it to the Copyright Holder, but only to the not be required to justify it to the Copyright Holder, but only to the
computing community at large as a market that must bear the fee.) computing community at large as a market that must bear the fee.)
- "Freely Available" means that no fee is charged for the item itself, though - "Freely Available" means that no fee is charged for the item itself, though
there may be fees involved in handling the item. It also means that there may be fees involved in handling the item. It also means that
recipients of the item may redistribute it under the same conditions they recipients of the item may redistribute it under the same conditions they
received it. received it.
1. You may make and give away verbatim copies of the source form of the 1. You may make and give away verbatim copies of the source form of the
Standard Version of this Package without restriction, provided that you Standard Version of this Package without restriction, provided that you
@ -373,7 +373,7 @@ products derived from this software without specific prior written permission.
9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
The End The End

View File

@ -1,17 +1,23 @@
.editorconfig
Build.PL Build.PL
Changes Changes
hax/block_end.c.inc hax/block_end.c.inc
hax/block_start.c.inc hax/block_start.c.inc
hax/COP_SEQ_RANGE_HIGH_set.c.inc hax/COP_SEQ_RANGE_HIGH_set.c.inc
hax/croak_from_caller.c.inc
hax/lexer-additions.c.inc hax/lexer-additions.c.inc
hax/LOGOP_ANY.c.inc
hax/make_argcheck_aux.c.inc hax/make_argcheck_aux.c.inc
hax/newSV_with_free.c.inc
hax/pad_block_start.c.inc hax/pad_block_start.c.inc
hax/pad_leavemy.c.inc hax/pad_leavemy.c.inc
hax/parse_subsignature.c.inc
hax/scalarseq.c.inc hax/scalarseq.c.inc
hax/sv_setrv.c.inc hax/sv_setrv.c.inc
hax/wrap_keyword_plugin.c.inc hax/wrap_keyword_plugin.c.inc
inc/Module/Build/with/XSTests.pm inc/Module/Build/with/XSTests.pm
include/parse_subsignature_ex.h
lib/Sublike/Extended.pm
lib/Sublike/Extended.xs
lib/XS/Parse/Sublike.pm lib/XS/Parse/Sublike.pm
lib/XS/Parse/Sublike.xs lib/XS/Parse/Sublike.xs
lib/XS/Parse/Sublike/Builder.pm lib/XS/Parse/Sublike/Builder.pm
@ -21,10 +27,13 @@ MANIFEST This list of files
META.json META.json
META.yml META.yml
README README
src/parse_subsignature_ex.c
t/00use.t t/00use.t
t/01func.t t/01func.t
t/02func-signatures.t t/02func-signatures.t
t/03func-deparse.t t/03func-deparse.t
t/04func-named-params.t
t/05func-sig-attributes.t
t/10stages-permit.t t/10stages-permit.t
t/11stages-pre_subparse.t t/11stages-pre_subparse.t
t/12stages-post_blockstart.t t/12stages-post_blockstart.t
@ -39,6 +48,10 @@ t/41parts-attrs.t
t/42parts-signature.t t/42parts-signature.t
t/50flags-no-body.t t/50flags-no-body.t
t/60actions.t t/60actions.t
t/70extended.t
t/80extended+async.t
t/80extended+feature-class.t
t/80extended+Object-Pad.t
t/99pod.t t/99pod.t
t/actions.xs t/actions.xs
t/any.xs t/any.xs

View File

@ -4,7 +4,7 @@
"Paul Evans <leonerd@leonerd.org.uk>" "Paul Evans <leonerd@leonerd.org.uk>"
], ],
"dynamic_config" : 1, "dynamic_config" : 1,
"generated_by" : "Module::Build version 0.4231", "generated_by" : "Module::Build version 0.4234",
"license" : [ "license" : [
"perl_5" "perl_5"
], ],
@ -32,19 +32,22 @@
"test" : { "test" : {
"requires" : { "requires" : {
"Sub::Util" : "0", "Sub::Util" : "0",
"Test::Fatal" : "0", "Test2::V0" : "0"
"Test::More" : "0.88"
} }
} }
}, },
"provides" : { "provides" : {
"Sublike::Extended" : {
"file" : "lib/Sublike/Extended.pm",
"version" : "0.21"
},
"XS::Parse::Sublike" : { "XS::Parse::Sublike" : {
"file" : "lib/XS/Parse/Sublike.pm", "file" : "lib/XS/Parse/Sublike.pm",
"version" : "0.16" "version" : "0.21"
}, },
"XS::Parse::Sublike::Builder" : { "XS::Parse::Sublike::Builder" : {
"file" : "lib/XS/Parse/Sublike/Builder.pm", "file" : "lib/XS/Parse/Sublike/Builder.pm",
"version" : "0.16" "version" : "0.21"
} }
}, },
"release_status" : "stable", "release_status" : "stable",
@ -53,6 +56,6 @@
"http://dev.perl.org/licenses/" "http://dev.perl.org/licenses/"
] ]
}, },
"version" : "0.16", "version" : "0.21",
"x_serialization_backend" : "JSON::PP version 4.05" "x_serialization_backend" : "JSON::PP version 4.07"
} }

View File

@ -5,27 +5,29 @@ author:
build_requires: build_requires:
ExtUtils::CBuilder: '0' ExtUtils::CBuilder: '0'
Sub::Util: '0' Sub::Util: '0'
Test::Fatal: '0' Test2::V0: '0'
Test::More: '0.88'
configure_requires: configure_requires:
Module::Build: '0.4004' Module::Build: '0.4004'
dynamic_config: 1 dynamic_config: 1
generated_by: 'Module::Build version 0.4231, CPAN::Meta::Converter version 2.150010' generated_by: 'Module::Build version 0.4234, CPAN::Meta::Converter version 2.150010'
license: perl license: perl
meta-spec: meta-spec:
url: http://module-build.sourceforge.net/META-spec-v1.4.html url: http://module-build.sourceforge.net/META-spec-v1.4.html
version: '1.4' version: '1.4'
name: XS-Parse-Sublike name: XS-Parse-Sublike
provides: provides:
Sublike::Extended:
file: lib/Sublike/Extended.pm
version: '0.21'
XS::Parse::Sublike: XS::Parse::Sublike:
file: lib/XS/Parse/Sublike.pm file: lib/XS/Parse/Sublike.pm
version: '0.16' version: '0.21'
XS::Parse::Sublike::Builder: XS::Parse::Sublike::Builder:
file: lib/XS/Parse/Sublike/Builder.pm file: lib/XS/Parse/Sublike/Builder.pm
version: '0.16' version: '0.21'
requires: requires:
perl: '5.016' perl: '5.016'
resources: resources:
license: http://dev.perl.org/licenses/ license: http://dev.perl.org/licenses/
version: '0.16' version: '0.21'
x_serialization_backend: 'CPAN::Meta::YAML version 0.018' x_serialization_backend: 'CPAN::Meta::YAML version 0.018'

11
README
View File

@ -227,12 +227,17 @@ PARSE HOOKS
The parameter signature of the function. The parameter signature of the function.
This part can be skipped, but the bit is ignored when in This part can be skipped, but it is always permitted not to provide a
require_parts. It is always permitted not to provide a signature for signature for a function definition even if the bit it set in
a function definition, because such syntax only applies when use require_parts. This is because such syntax only applies when use
feature 'signatures' is in effect, and only on supporting perl feature 'signatures' is in effect, and only on supporting perl
versions. versions.
However, setting the bit in require_parts instead has the effect of
enabling use feature 'signatures' (at least on supporting perl
versions), thus permitting the syntax to use a signature even if the
signatures feature was not previously enabled.
XS_PARSE_SUBLIKE_PART_BODY XS_PARSE_SUBLIKE_PART_BODY
The actual body of the function, expressed as a brace-delimited The actual body of the function, expressed as a brace-delimited

View File

@ -1,7 +1,7 @@
#ifndef __XS_PARSE_SUBLIKE_H__ #ifndef __XS_PARSE_SUBLIKE_H__
#define __XS_PARSE_SUBLIKE_H__ #define __XS_PARSE_SUBLIKE_H__
#define XSPARSESUBLIKE_ABI_VERSION 4 #define XSPARSESUBLIKE_ABI_VERSION 5
struct XSParseSublikeContext { struct XSParseSublikeContext {
SV *name; /* may be NULL for anon subs */ SV *name; /* may be NULL for anon subs */
@ -25,6 +25,11 @@ enum {
XS_PARSE_SUBLIKE_FLAG_BODY_OPTIONAL = 1<<1, XS_PARSE_SUBLIKE_FLAG_BODY_OPTIONAL = 1<<1,
XS_PARSE_SUBLIKE_FLAG_PREFIX = 1<<2, XS_PARSE_SUBLIKE_FLAG_PREFIX = 1<<2,
/* *Experimental* named parameter parsing support */
XS_PARSE_SUBLIKE_FLAG_SIGNATURE_NAMED_PARAMS = 1<<3,
/* *Experimental* parameter attribute parsing support */
XS_PARSE_SUBLIKE_FLAG_SIGNATURE_PARAM_ATTRIBUTES = 1<<4,
/* Back-compat flags we hope to remove in the next ABI version */ /* Back-compat flags we hope to remove in the next ABI version */
XS_PARSE_SUBLIKE_COMPAT_FLAG_DYNAMIC_ACTIONS = 1<<15, XS_PARSE_SUBLIKE_COMPAT_FLAG_DYNAMIC_ACTIONS = 1<<15,
}; };
@ -92,6 +97,43 @@ static int S_xs_parse_sublike_any(pTHX_ const struct XSParseSublikeHooks *hooks,
return (*parseany_xs_parse_sublike_func)(aTHX_ hooks, hookdata, op_ptr); return (*parseany_xs_parse_sublike_func)(aTHX_ hooks, hookdata, op_ptr);
} }
/* Experimental support for subroutine parameter attributes.
* Only supported on Perl v5.26 or later
*/
struct XPSSignatureParamContext {
bool is_named;
PADOFFSET padix;
OP *varop;
/* apply phase runs here */
OP *defop;
OP *op;
/* post_defop phase runs here */
};
struct XPSSignatureAttributeFuncs {
U32 ver; /* caller must initialise to XSPARSESUBLIKE_ABI_VERSION */
U32 flags;
const char *permit_hintkey;
void (*apply)(pTHX_ struct XPSSignatureParamContext *ctx, SV *attrvalue, void **attrdata_ptr, void *funcdata);
void (*post_defop)(pTHX_ struct XPSSignatureParamContext *ctx, void *attrdata, void *funcdata);
void (*free)(pTHX_ void *attrdata, void *funcdata);
};
static void (*register_xps_signature_attribute_func)(pTHX_ const char *name, const struct XPSSignatureAttributeFuncs *funcs, void *funcdata);
#define register_xps_signature_attribute(name, funcs, funcdata) S_register_xps_signature_attribute(aTHX_ name, funcs, funcdata)
static void S_register_xps_signature_attribute(pTHX_ const char *name, const struct XPSSignatureAttributeFuncs *funcs, void *funcdata)
{
if(!register_xps_signature_attribute_func)
croak("Must call boot_xs_parse_sublike() first");
(*register_xps_signature_attribute_func)(aTHX_ name, funcs, funcdata);
}
#define boot_xs_parse_sublike(ver) S_boot_xs_parse_sublike(aTHX_ ver) #define boot_xs_parse_sublike(ver) S_boot_xs_parse_sublike(aTHX_ ver)
static void S_boot_xs_parse_sublike(pTHX_ double ver) { static void S_boot_xs_parse_sublike(pTHX_ double ver) {
SV **svp; SV **svp;
@ -121,6 +163,9 @@ static void S_boot_xs_parse_sublike(pTHX_ double ver) {
parseany_xs_parse_sublike_func = INT2PTR(int (*)(pTHX_ const struct XSParseSublikeHooks *, void *, OP**), parseany_xs_parse_sublike_func = INT2PTR(int (*)(pTHX_ const struct XSParseSublikeHooks *, void *, OP**),
SvUV(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/parseany()@4", 0))); SvUV(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/parseany()@4", 0)));
register_xps_signature_attribute_func = INT2PTR(void (*)(pTHX_ const char *, const struct XPSSignatureAttributeFuncs *, void *),
SvUV(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/register_sigattr()@5", 0)));
} }
#endif #endif

41
hax/LOGOP_ANY.c.inc Normal file
View File

@ -0,0 +1,41 @@
/* vi: set ft=c : */
struct logop_any {
BASEOP
OP *op_first;
OP *op_other;
ANY *op_any;
};
typedef struct logop_any LOGOP_ANY;
#define cLOGOP_ANYx(o) ((LOGOP_ANY *)o)
#define cLOGOP_ANY cLOGOP_ANYx(PL_op)
#define cLOGOP_ANYo cLOGOP_ANYx(o)
#ifndef OpTYPE_set
# define OpTYPE_set(op, type) \
STMT_START { \
op->op_type = (OPCODE)type; \
op->op_ppaddr = PL_ppaddr[type]; \
} STMT_END
#endif
#define alloc_LOGOP_ANY(type, first, other) MY_alloc_LOGOP_ANY(aTHX_ type, first, other)
static LOGOP_ANY *MY_alloc_LOGOP_ANY(pTHX_ I32 type, OP *first, OP *other)
{
dVAR;
LOGOP_ANY *logop;
OP *kid = first;
NewOp(1101, logop, 1, LOGOP_ANY);
OpTYPE_set(logop, type);
logop->op_first = first;
logop->op_other = other;
if (first)
logop->op_flags = OPf_KIDS;
while (kid && OpHAS_SIBLING(kid))
kid = OpSIBLING(kid);
if (kid)
OpLASTSIB_set(kid, (OP*)logop);
return logop;
}

View File

@ -0,0 +1,36 @@
/* vi: set ft=c : */
static void S_croaksv_from_caller(pTHX_ SV *msg_sv)
__attribute__noreturn__;
static void S_croaksv_from_caller(pTHX_ SV *msg_sv)
{
I32 count = 0;
const PERL_CONTEXT *cx;
while((cx = caller_cx(count, NULL))) {
count++;
/* TODO: Skip internal call frames? */
/* warn("TODO: maybe croak from caller where caller stash is %s\n",
* HvNAME(CopSTASH(cx->blk_oldcop)));
*/
PL_curcop = cx->blk_oldcop;
break;
}
croak_sv(msg_sv);
}
#define croak_from_caller(fmt, ...) S_croak_from_caller(aTHX_ fmt, __VA_ARGS__)
static void S_croak_from_caller(pTHX_ const char *fmt, ...)
__attribute__noreturn__;
static void S_croak_from_caller(pTHX_ const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
SV *msg_sv = sv_2mortal(vnewSVpvf(fmt, &args));
va_end(args);
S_croaksv_from_caller(aTHX_ msg_sv);
}

20
hax/newSV_with_free.c.inc Normal file
View File

@ -0,0 +1,20 @@
/* vi: set ft=c : */
static int magic_free(pTHX_ SV *sv, MAGIC *mg)
{
void (*freefunc)(pTHX_ SV *sv) = (void *)mg->mg_ptr;
(*freefunc)(aTHX_ sv);
return 0;
}
static const MGVTBL vtbl_sv_with_free = {
.svt_free = magic_free,
};
#define newSV_with_free(size, freefunc) S_newSV_with_free(aTHX_ size, freefunc)
static SV *S_newSV_with_free(pTHX_ STRLEN size, void (*freefunc)(pTHX_ SV *sv))
{
SV *sv = newSV(size);
sv_magicext(sv, NULL, PERL_MAGIC_ext, &vtbl_sv_with_free, (void *)freefunc, 0);
return sv;
}

View File

@ -1,220 +0,0 @@
/* vi: set ft=c : */
#ifndef parse_subsignature
#define PERL_EXT
#include "feature.h"
#include "make_argcheck_aux.c.inc"
/*
* Need to grab some things that aren't quite core perl API
*/
/* yyerror() is a long function and hard to emulate or copy-paste for our
* purposes; we'll reïmplement a smaller version of it
*/
#define LEX_IGNORE_UTF8_HINTS 0x00000002
#define PL_linestr (PL_parser->linestr)
#ifdef USE_UTF8_SCRIPTS
# define UTF cBOOL(!IN_BYTES)
#else
# define UTF cBOOL((PL_linestr && DO_UTF8(PL_linestr)) || ( !(PL_parser->lex_flags & LEX_IGNORE_UTF8_HINTS) && (PL_hints & HINT_UTF8)))
#endif
#define yyerror(s) S_yyerror(aTHX_ s)
void S_yyerror(pTHX_ const char *s)
{
SV *message = sv_2mortal(newSVpvs_flags("", 0));
char *context = PL_parser->oldbufptr;
STRLEN contlen = PL_parser->bufptr - PL_parser->oldbufptr;
sv_catpvf(message, "%s at %s line %" IVdf,
s, OutCopFILE(PL_curcop), (IV)CopLINE(PL_curcop));
if(context)
sv_catpvf(message, ", near \"%" UTF8f "\"",
UTF8fARG(UTF, contlen, context));
sv_catpvf(message, "\n");
PL_parser->error_count++;
warn_sv(message);
}
/* Stolen from op.c */
#define OpTYPE_set(op, type) \
STMT_START { \
op->op_type = (OPCODE)type; \
op->op_ppaddr = PL_ppaddr[type]; \
} STMT_END
#define alloc_LOGOP(a,b,c) S_alloc_LOGOP(aTHX_ a,b,c)
static LOGOP *S_alloc_LOGOP(pTHX_ I32 type, OP *first, OP* other)
{
dVAR;
LOGOP *logop;
OP *kid = first;
NewOp(1101, logop, 1, LOGOP);
OpTYPE_set(logop, type);
logop->op_first = first;
logop->op_other = other;
if (first)
logop->op_flags = OPf_KIDS;
while (kid && OpHAS_SIBLING(kid))
kid = OpSIBLING(kid);
if (kid)
OpLASTSIB_set(kid, (OP*)logop);
return logop;
}
#define parse_sigelem() S_parse_sigelem(aTHX)
static OP *S_parse_sigelem(pTHX)
{
yy_parser *parser = PL_parser;
int c = lex_peek_unichar(0);
int flags;
switch(c) {
case '$': flags = OPpARGELEM_SV; break;
case '@': flags = OPpARGELEM_AV; break;
case '%': flags = OPpARGELEM_HV; break;
default:
croak("Expected a signature element at <%s>\n", parser->bufptr);
}
char *lexname = parser->bufptr;
OP *varop = NULL;
/* Consume sigil */
lex_read_unichar(0);
if(isIDFIRST_uni(lex_peek_unichar(0))) {
lex_read_unichar(0);
while(isALNUM_uni(lex_peek_unichar(0)))
lex_read_unichar(0);
varop = newUNOP_AUX(OP_ARGELEM, 0, NULL, INT2PTR(UNOP_AUX_item *, (parser->sig_elems)));
varop->op_private |= flags;
varop->op_targ = pad_add_name_pvn(lexname, PL_parser->bufptr - lexname, 0, NULL, NULL);
lex_read_space(0);
}
if(c == '$') {
if(parser->sig_slurpy)
yyerror("Slurpy parameters not last");
parser->sig_elems++;
if(lex_peek_unichar(0) == '=') {
lex_read_unichar(0);
lex_read_space(0);
parser->sig_optelems++;
OP *defexpr = parse_termexpr(0);
OP *defop = (OP *)alloc_LOGOP(OP_ARGDEFELEM, defexpr, LINKLIST(defexpr));
defop->op_targ = (PADOFFSET)(parser->sig_elems - 1);
varop->op_flags |= OPf_STACKED;
op_sibling_splice(varop, NULL, 0, defop);
defop = op_contextualize(defop, G_SCALAR);
LINKLIST(varop);
varop->op_next = defop;
defexpr->op_next = varop;
}
else {
if(parser->sig_optelems)
yyerror("Mandatory parameter follows optional parameter");
}
}
else {
if(parser->sig_slurpy)
yyerror("Multiple slurpy parameters not allowed");
parser->sig_slurpy = c;
if(lex_peek_unichar(0) == '=')
yyerror("A slurpy parameter may not have a default value");
}
return varop ? newSTATEOP(0, NULL, varop) : NULL;
}
#define parse_subsignature(flags) S_parse_subsignature(aTHX_ flags)
static OP *S_parse_subsignature(pTHX_ int flags)
{
/* Mostly reconstructed logic from perl 5.28.0's toke.c and perly.y
*/
yy_parser *parser = PL_parser;
ENTER;
SAVEIV(parser->sig_elems);
SAVEIV(parser->sig_optelems);
SAVEI8(parser->sig_slurpy);
parser->sig_elems = 0;
parser->sig_optelems = 0;
parser->sig_slurpy = 0;
OP *elems = NULL;
while(lex_peek_unichar(0) != ')') {
lex_read_space(0);
OP *elem = parse_sigelem();
elems = op_append_list(OP_LINESEQ, elems, elem);
if(PL_parser->error_count) {
LEAVE;
return NULL;
}
lex_read_space(0);
switch(lex_peek_unichar(0)) {
case ')': goto endofelems;
case ',': break;
default:
fprintf(stderr, "ARGH unsure how to proceed parse_subsignature at <%s>\n",
parser->bufptr);
croak("ARGH");
break;
}
lex_read_unichar(0);
lex_read_space(0);
}
endofelems:
if (!FEATURE_SIGNATURES_IS_ENABLED)
croak("Experimental subroutine signatures not enabled");
Perl_ck_warner_d(aTHX_ packWARN(WARN_EXPERIMENTAL__SIGNATURES),
"The signatures feature is experimental");
UNOP_AUX_item *aux = make_argcheck_aux(
parser->sig_elems, parser->sig_optelems, parser->sig_slurpy);
OP *checkop = newUNOP_AUX(OP_ARGCHECK, 0, NULL, aux);
checkop = op_prepend_elem(OP_LINESEQ, newSTATEOP(0, NULL, NULL),
op_prepend_elem(OP_LINESEQ, checkop, elems));
/* a nextstate at the end handles context correctly for an empty
* sub body */
checkop = op_append_elem(OP_LINESEQ, checkop, newSTATEOP(0, NULL, NULL));
LEAVE;
return checkop;
}
#endif

View File

@ -1,6 +1,6 @@
package Module::Build::with::XSTests; package Module::Build::with::XSTests;
use strict; use v5.14;
use warnings; use warnings;
use base qw( Module::Build ); use base qw( Module::Build );

View File

@ -0,0 +1,23 @@
/* vi: set ft=c : */
/* Some experimental extension flags. Not (currently) part of core perl API
*/
enum {
PARSE_SUBSIGNATURE_NAMED_PARAMS = (1<<0),
/* Permits ( :$foo, :$bar, :$splot = "default" ) named params
* They are accumulated into a slurpy hash then extracted afterwards
* As with positional params, any param without a defaulting expression is
* required; an error is thrown if the caller did not provide it
*/
PARSE_SUBSIGNATURE_PARAM_ATTRIBUTES = (1<<1),
/* Permits ( $param :Attribute(Value) ) attributes on params
* These must be registered by calling register_subsignature_attribute()
*/
};
#define parse_subsignature_ex(flags) XPS_parse_subsignature_ex(aTHX_ flags)
OP *XPS_parse_subsignature_ex(pTHX_ int flags);
#define register_subsignature_attribute(name, funcs, funcdata) XPS_register_subsignature_attribute(aTHX_ name, funcs, funcdata)
void XPS_register_subsignature_attribute(pTHX_ const char *name, const struct XPSSignatureAttributeFuncs *funcs, void *funcdata);

122
lib/Sublike/Extended.pm Normal file
View File

@ -0,0 +1,122 @@
# You may distribute under the terms of either the GNU General Public License
# or the Artistic License (the same terms as Perl itself)
#
# (C) Paul Evans, 2023 -- leonerd@leonerd.org.uk
package Sublike::Extended 0.21;
use v5.14;
use warnings;
require XSLoader;
XSLoader::load( __PACKAGE__, our $VERSION );
=head1 NAME
C<Sublike::Extended> - enable extended features when parsing C<sub>-like syntax
=head1 SYNOPSIS
use v5.26;
use Sublike::Extended;
use experimental 'signatures';
extended sub greet (:$name = "world") {
say "Hello, $name";
}
greet( name => $ENV{USER} );
=head1 DESCRIPTION
This module extends the syntax for declaring named or anonymous subroutines
using Perl's builtin C<sub> keyword, or other similar keywords provided by
third-party modules, to enable parsing of extra features.
Currently, the only extended features that are provided are related to the
parsing of a subroutine signature. Since signatures are only available on Perl
version 5.26 or later, this module is unlikely to be useful in earlier
versions of Perl.
=head2 Named parameters
Extended subroutines can be declare named parameters in the signature, after
any positional ones. These take the form of a name prefixed by a colon
character. The caller of such a function should pass values for these
parameters by the usual name-value pair syntax that would be used for passing
into a regular hash. Within the body of the subroutine the values passed into
these are unpacked into regular lexical variables.
extended sub colour (:$red, :$green, :$blue) {
... # $red, $green and $blue are available as regular lexicals
}
# argument order at the caller site is not important
colour(green => 1, blue => 2, red => 3);
As with positional parameters, they are normally mandatory, but can be made
optional by supplying a defaulting expression. If the caller fails to pass a
value corresponding to the parameter, the default expression is evaluated and
used instead.
extended sub f (:$x0, :$x1, :$x2 = 0) { ... }
# The caller must provide x0 and x1, but x2 is optional
An optional slurpy hash is also permitted after all of these. It will contain
the values of any other name-value pairs given by the caller, after those
corresponding to named parameters have already been extracted.
extended sub g (:$alpha, :$beta, %rest) { ... }
=head2 Parameter Attributes
Parameters to extended subroutines can use attribute syntax to apply extra
attributes to individual parameters.
extended sub info ($x :Attribute) { ... }
Any attributes that are available are ones that have been previously
registered with L<XS::Parse::Sublike> using its XS-level API. The particular
behaviour of such an attribute would be defined by whatever module provided
the attribute.
=head1 KEYWORDS
=head2 extended
extended sub NAME (SIGNATURE...) { BODY... }
extended sub (SIGNATURE...) { BODY... };
This prefix keyword enables extra parsing features when handling a C<sub> (or
other sub-like function keyword).
This keyword can be freely mixed with other C<sub>-prefix keywords, such as
C<async> from L<Future::AsyncAwait>
async extended sub f (:$param) { ... }
This can also be used with other keywords that provide C<sub>-like syntax,
such as C<method> from L<Object::Pad> or the core C<use feature 'class'>.
extended method f (:$param) { ... }
=cut
sub import
{
$^H{"Sublike::Extended/extended"}++;
}
sub unimport
{
delete $^H{"Sublike::Extended/extended"};
}
=head1 AUTHOR
Paul Evans <leonerd@leonerd.org.uk>
=cut
0x55AA;

27
lib/Sublike/Extended.xs Normal file
View File

@ -0,0 +1,27 @@
/* You may distribute under the terms of either the GNU General Public License
* or the Artistic License (the same terms as Perl itself)
*
* (C) Paul Evans, 2023 -- leonerd@leonerd.org.uk
*/
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "XSParseSublike.h"
static struct XSParseSublikeHooks hooks_extended = {
.permit_hintkey = "Sublike::Extended/extended",
.flags = XS_PARSE_SUBLIKE_FLAG_PREFIX|
XS_PARSE_SUBLIKE_FLAG_SIGNATURE_NAMED_PARAMS|
XS_PARSE_SUBLIKE_FLAG_SIGNATURE_PARAM_ATTRIBUTES,
/* No hooks */
};
MODULE = Sublike::Extended PACKAGE = Sublike::Extended
BOOT:
boot_xs_parse_sublike(0); // TODO
register_xs_parse_sublike("extended", &hooks_extended, NULL);

View File

@ -3,7 +3,7 @@
# #
# (C) Paul Evans, 2020-2021 -- leonerd@leonerd.org.uk # (C) Paul Evans, 2020-2021 -- leonerd@leonerd.org.uk
package XS::Parse::Sublike 0.16; package XS::Parse::Sublike 0.21;
use v5.14; use v5.14;
use warnings; use warnings;
@ -242,10 +242,16 @@ definition.
The parameter signature of the function. The parameter signature of the function.
This part can be skipped, but the bit is ignored when in I<require_parts>. It This part can be skipped, but it is always permitted not to provide a
is always permitted not to provide a signature for a function definition, signature for a function definition even if the bit it set in
because such syntax only applies when C<use feature 'signatures'> is in I<require_parts>. This is because such syntax only applies when
effect, and only on supporting perl versions. C<use feature 'signatures'> is in effect, and only on supporting perl
versions.
However, setting the bit in I<require_parts> instead has the effect of
enabling C<use feature 'signatures'> (at least on supporting perl versions),
thus permitting the syntax to use a signature even if the signatures feature
was not previously enabled.
=item XS_PARSE_SUBLIKE_PART_BODY =item XS_PARSE_SUBLIKE_PART_BODY

View File

@ -1,25 +1,41 @@
/* You may distribute under the terms of either the GNU General Public License /* You may distribute under the terms of either the GNU General Public License
* or the Artistic License (the same terms as Perl itself) * or the Artistic License (the same terms as Perl itself)
* *
* (C) Paul Evans, 2019-2021 -- leonerd@leonerd.org.uk * (C) Paul Evans, 2019-2023 -- leonerd@leonerd.org.uk
*/ */
#include "EXTERN.h" #include "EXTERN.h"
#include "perl.h" #include "perl.h"
#include "XSUB.h" #include "XSUB.h"
/* We need to be able to see FEATURE_*_IS_ENABLED */
#define PERL_EXT
#include "feature.h"
#include "XSParseSublike.h" #include "XSParseSublike.h"
#define HAVE_PERL_VERSION(R, V, S) \ #define HAVE_PERL_VERSION(R, V, S) \
(PERL_REVISION > (R) || (PERL_REVISION == (R) && (PERL_VERSION > (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION >= (S)))))) (PERL_REVISION > (R) || (PERL_REVISION == (R) && (PERL_VERSION > (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION >= (S))))))
#if HAVE_PERL_VERSION(5, 26, 0) #if HAVE_PERL_VERSION(5, 37, 10)
# if !HAVE_PERL_VERSION(5, 31, 3) /* feature 'class' first became available in 5.37.9 but it wasn't until
# include "parse_subsignature.c.inc" * 5.37.10 that we could pass CVf_IsMETHOD to start_subparse()
# endif */
# define HAVE_FEATURE_CLASS
#endif
/* We always need this included to get the struct and function definitions
* visible, even though we won't be calling it
*/
#include "parse_subsignature_ex.h"
#if HAVE_PERL_VERSION(5, 26, 0)
# include "make_argcheck_aux.c.inc" # include "make_argcheck_aux.c.inc"
# if !HAVE_PERL_VERSION(5, 31, 3)
# define parse_subsignature(flags) parse_subsignature_ex(0) /* ignore core flags as there are none */
# endif
# define HAVE_PARSE_SUBSIGNATURE # define HAVE_PARSE_SUBSIGNATURE
#endif #endif
@ -34,6 +50,9 @@
#include "lexer-additions.c.inc" #include "lexer-additions.c.inc"
#define QUOTED_PVNf "\"%.*s\"%s"
#define QUOTED_PVNfARG(pv,len) ((len) <= 255 ? (int)(len) : 255), (pv), ((len) <= 255 ? "" : "...")
struct HooksAndData { struct HooksAndData {
const struct XSParseSublikeHooks *hooks; const struct XSParseSublikeHooks *hooks;
void *data; void *data;
@ -49,6 +68,11 @@ struct HooksAndData {
(hooki >= 0) && (hooks = hooksanddata[hooki].hooks, hookdata = hooksanddata[hooki].data), (hooki >= 0); \ (hooki >= 0) && (hooks = hooksanddata[hooki].hooks, hookdata = hooksanddata[hooki].data), (hooki >= 0); \
hooki--) hooki--)
/* Non-documented internal flags we use for our own purposes */
enum {
XS_PARSE_SUBLIKE_ACTION_CVf_IsMETHOD = (1<<31), /* do we set CVf_IsMETHOD? */
};
static int parse(pTHX_ static int parse(pTHX_
struct HooksAndData hooksanddata[], struct HooksAndData hooksanddata[],
size_t nhooks, size_t nhooks,
@ -109,7 +133,15 @@ static int parse(pTHX_
ctx.actions |= XS_PARSE_SUBLIKE_ACTION_CVf_ANON; ctx.actions |= XS_PARSE_SUBLIKE_ACTION_CVf_ANON;
} }
I32 floor_ix = start_subparse(FALSE, ctx.actions & XS_PARSE_SUBLIKE_ACTION_CVf_ANON ? CVf_ANON : 0); int subparse_flags = 0;
if(ctx.actions & XS_PARSE_SUBLIKE_ACTION_CVf_ANON)
subparse_flags |= CVf_ANON;
#ifdef HAVE_FEATURE_CLASS
if(ctx.actions & XS_PARSE_SUBLIKE_ACTION_CVf_IsMETHOD)
subparse_flags |= CVf_IsMETHOD;
#endif
I32 floor_ix = start_subparse(FALSE, subparse_flags);
SAVEFREESV(PL_compcv); SAVEFREESV(PL_compcv);
if(!(skip_parts & XS_PARSE_SUBLIKE_PART_ATTRS) && (lex_peek_unichar(0) == ':')) { if(!(skip_parts & XS_PARSE_SUBLIKE_PART_ATTRS) && (lex_peek_unichar(0) == ':')) {
@ -175,6 +207,18 @@ static int parse(pTHX_
lex_read_unichar(0); lex_read_unichar(0);
lex_read_space(0); lex_read_space(0);
if(require_parts & XS_PARSE_SUBLIKE_PART_SIGNATURE) {
#if HAVE_PERL_VERSION(5, 32, 0)
SAVEI32(PL_compiling.cop_features);
PL_compiling.cop_features |= FEATURE_SIGNATURES_BIT;
#else
/* So far this is only used by the "method" keyword hack for perl 5.38
* onwards so this doesn't technically matter. Yet...
*/
croak("TODO: import_pragma(\"feature\", \"signatures\")");
#endif
}
#if HAVE_PERL_VERSION(5, 31, 3) #if HAVE_PERL_VERSION(5, 31, 3)
/* core's parse_subsignature doesn't seem able to handle empty sigs /* core's parse_subsignature doesn't seem able to handle empty sigs
* RT132284 * RT132284
@ -206,7 +250,18 @@ static int parse(pTHX_
else else
#endif #endif
{ {
sigop = parse_subsignature(0); U32 flags = 0;
FOREACH_HOOKS_FORWARD {
if(hooks->flags & XS_PARSE_SUBLIKE_FLAG_SIGNATURE_NAMED_PARAMS)
flags |= PARSE_SUBSIGNATURE_NAMED_PARAMS;
if(hooks->flags & XS_PARSE_SUBLIKE_FLAG_SIGNATURE_PARAM_ATTRIBUTES)
flags |= PARSE_SUBSIGNATURE_PARAM_ATTRIBUTES;
}
if(flags)
sigop = parse_subsignature_ex(flags);
else
sigop = parse_subsignature(0);
if(PL_parser->error_count) { if(PL_parser->error_count) {
assert(PL_scopestack_ix == was_scopestack_ix); assert(PL_scopestack_ix == was_scopestack_ix);
@ -413,11 +468,14 @@ static void register_sublike(pTHX_ const char *kw, const void *hooks, void *hook
reg->hooks = hooks; reg->hooks = hooks;
reg->hookdata = hookdata; reg->hookdata = hookdata;
if(reg->ver >= 4 && reg->hooks->permit_hintkey) if(reg->hooks->permit_hintkey)
reg->permit_hintkey_len = strlen(reg->hooks->permit_hintkey); reg->permit_hintkey_len = strlen(reg->hooks->permit_hintkey);
else else
reg->permit_hintkey_len = 0; reg->permit_hintkey_len = 0;
if(!reg->hooks->permit && !reg->hooks->permit_hintkey)
croak("Third-party sublike keywords require a permit callback or hinthash key");
REGISTRATIONS_LOCK; REGISTRATIONS_LOCK;
{ {
reg->next = registrations; reg->next = registrations;
@ -476,8 +534,8 @@ static int IMPL_xs_parse_sublike_any_v4(pTHX_ const struct XSParseSublikeHooks *
if(kwlen != 3 || !strEQ(kw, "sub")) { if(kwlen != 3 || !strEQ(kw, "sub")) {
reg = find_permitted(aTHX_ kw, kwlen); reg = find_permitted(aTHX_ kw, kwlen);
if(!reg) if(!reg)
croak("Expected a keyword to introduce a sub or sub-like construction, found \"%.*s\"", croak("Expected a keyword to introduce a sub or sub-like construction, found " QUOTED_PVNf,
kwlen, kw); QUOTED_PVNfARG(kw, kwlen));
} }
SvREFCNT_dec(kwsv); SvREFCNT_dec(kwsv);
@ -486,7 +544,6 @@ static int IMPL_xs_parse_sublike_any_v4(pTHX_ const struct XSParseSublikeHooks *
{ .hooks = hooksA, .data = hookdataA }, { .hooks = hooksA, .data = hookdataA },
{ 0 } { 0 }
}; };
struct XSParseSublikeHooks hooks;
if(reg) { if(reg) {
hd[1].hooks = reg->hooks; hd[1].hooks = reg->hooks;
@ -501,6 +558,42 @@ static int IMPL_xs_parse_sublike_any_v3(pTHX_ const void *hooksA, void *hookdata
croak("XS::Parse::Sublike ABI v3 is no longer supported; the caller should be rebuilt to use v4"); croak("XS::Parse::Sublike ABI v3 is no longer supported; the caller should be rebuilt to use v4");
} }
static void IMPL_register_xps_signature_attribute(pTHX_ const char *name, const struct XPSSignatureAttributeFuncs *funcs, void *funcdata)
{
if(funcs->ver < 5)
croak("Mismatch in signature param attribute ABI version field: module wants %u; we require >= 5\n",
funcs->ver);
if(funcs->ver > XSPARSESUBLIKE_ABI_VERSION)
croak("Mismatch in signature param attribute ABI version field: module wants %u; we support <= %d\n",
funcs->ver, XSPARSESUBLIKE_ABI_VERSION);
if(!name || !(name[0] >= 'A' && name[0] <= 'Z'))
croak("Signature param attribute names must begin with a capital letter");
if(!funcs->permit_hintkey)
croak("Signature param attributes require a permit hinthash key");
register_subsignature_attribute(name, funcs, funcdata);
}
#ifdef HAVE_FEATURE_CLASS
static bool permit_core_method(pTHX_ void *hookdata)
{
return FEATURE_CLASS_IS_ENABLED;
}
static void pre_subparse_core_method(pTHX_ struct XSParseSublikeContext *ctx, void *hookdata)
{
ctx->actions |= XS_PARSE_SUBLIKE_ACTION_CVf_IsMETHOD;
}
static const struct XSParseSublikeHooks hooks_core_method = {
.permit = &permit_core_method,
.pre_subparse = &pre_subparse_core_method,
.require_parts = XS_PARSE_SUBLIKE_PART_SIGNATURE, /* enable signatures feature */
};
#endif
static int (*next_keyword_plugin)(pTHX_ char *, STRLEN, OP **); static int (*next_keyword_plugin)(pTHX_ char *, STRLEN, OP **);
static int my_keyword_plugin(pTHX_ char *kw, STRLEN kwlen, OP **op_ptr) static int my_keyword_plugin(pTHX_ char *kw, STRLEN kwlen, OP **op_ptr)
@ -543,8 +636,8 @@ static int my_keyword_plugin(pTHX_ char *kw, STRLEN kwlen, OP **op_ptr)
reg = find_permitted(aTHX_ kw, kwlen); reg = find_permitted(aTHX_ kw, kwlen);
if(!reg) if(!reg)
croak("Expected a keyword to introduce a sub or sub-like construction, found \"%.*s\"", croak("Expected a keyword to introduce a sub or sub-like construction, found " QUOTED_PVNf,
kwlen, kw); QUOTED_PVNfARG(kw, kwlen));
hooks = (struct XSParseSublikeHooks *)reg->hooks; hooks = (struct XSParseSublikeHooks *)reg->hooks;
@ -572,8 +665,14 @@ BOOT:
/* Newer mechanism */ /* Newer mechanism */
sv_setiv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/ABIVERSION_MIN", 1), 4); sv_setiv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/ABIVERSION_MIN", 1), 4);
sv_setiv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/ABIVERSION_MAX", 1), XSPARSESUBLIKE_ABI_VERSION); sv_setiv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/ABIVERSION_MAX", 1), XSPARSESUBLIKE_ABI_VERSION);
sv_setuv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/parse()@4", 1), PTR2UV(&IMPL_xs_parse_sublike_v4)); sv_setuv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/parse()@4", 1), PTR2UV(&IMPL_xs_parse_sublike_v4));
sv_setuv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/register()@4", 1), PTR2UV(&IMPL_register_xs_parse_sublike_v4)); sv_setuv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/register()@4", 1), PTR2UV(&IMPL_register_xs_parse_sublike_v4));
sv_setuv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/parseany()@4", 1), PTR2UV(&IMPL_xs_parse_sublike_any_v4)); sv_setuv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/parseany()@4", 1), PTR2UV(&IMPL_xs_parse_sublike_any_v4));
sv_setuv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/register_sigattr()@5", 1), PTR2UV(&IMPL_register_xps_signature_attribute));
#ifdef HAVE_FEATURE_CLASS
register_sublike(aTHX_ "method", &hooks_core_method, NULL, 4);
#endif
wrap_keyword_plugin(&my_keyword_plugin, &next_keyword_plugin); wrap_keyword_plugin(&my_keyword_plugin, &next_keyword_plugin);

View File

@ -3,7 +3,7 @@
# #
# (C) Paul Evans, 2020-2021 -- leonerd@leonerd.org.uk # (C) Paul Evans, 2020-2021 -- leonerd@leonerd.org.uk
package XS::Parse::Sublike::Builder 0.16; package XS::Parse::Sublike::Builder 0.21;
use v5.14; use v5.14;
use warnings; use warnings;
@ -46,7 +46,7 @@ require XS::Parse::Sublike::Builder_data;
=head2 write_XSParseSublike_h =head2 write_XSParseSublike_h
XS::Parse::Sublike::Builder->write_XSParseSublike_h XS::Parse::Sublike::Builder->write_XSParseSublike_h;
Writes the F<XSParseSublike.h> file to the current working directory. To cause Writes the F<XSParseSublike.h> file to the current working directory. To cause
the compiler to actually find this file, see L</extra_compiler_flags>. the compiler to actually find this file, see L</extra_compiler_flags>.
@ -65,7 +65,7 @@ sub write_XSParseSublike_h
=head2 extra_compiler_flags =head2 extra_compiler_flags
@flags = XS::Parse::Sublike::Builder->extra_compiler_flags @flags = XS::Parse::Sublike::Builder->extra_compiler_flags;
Returns a list of extra flags that the build scripts should add to the Returns a list of extra flags that the build scripts should add to the
compiler invocation. This enables the C compiler to find the compiler invocation. This enables the C compiler to find the
@ -81,7 +81,7 @@ sub extra_compiler_flags
=head2 extend_module_build =head2 extend_module_build
XS::Parse::Sublike::Builder->extend_module_build( $build ) XS::Parse::Sublike::Builder->extend_module_build( $build );
A convenient shortcut for performing all the tasks necessary to make a A convenient shortcut for performing all the tasks necessary to make a
L<Module::Build>-based distribution use the helper. L<Module::Build>-based distribution use the helper.

View File

@ -22,7 +22,7 @@ $outh->print( scalar do {
<$in_h> } ); <$in_h> } );
__DATA__ __DATA__
package XS::Parse::Sublike::Builder_data 0.16; package XS::Parse::Sublike::Builder_data 0.21;
use v5.14; use v5.14;
use warnings; use warnings;

617
src/parse_subsignature_ex.c Normal file
View File

@ -0,0 +1,617 @@
/* vi: set ft=c : */
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#define HAVE_PERL_VERSION(R, V, S) \
(PERL_REVISION > (R) || (PERL_REVISION == (R) && (PERL_VERSION > (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION >= (S))))))
#include "XSParseSublike.h"
/* Skip this entire file on perls older than OP_ARGCHECK */
#if HAVE_PERL_VERSION(5, 26, 0)
#define PERL_EXT
/* We need to be able to see FEATURE_*_IS_ENABLED */
#include "feature.h"
/* Also need KEY_sigvar */
#include "keywords.h"
#include "parse_subsignature_ex.h"
#include "lexer-additions.c.inc"
#include "LOGOP_ANY.c.inc"
#include "croak_from_caller.c.inc"
#include "make_argcheck_aux.c.inc"
#include "newSV_with_free.c.inc"
#define newSVpvx(ptr) S_newSVpvx(aTHX_ ptr)
static SV *S_newSVpvx(pTHX_ void *ptr)
{
SV *sv = newSV(0);
sv_upgrade(sv, SVt_PV);
SvPVX(sv) = ptr;
return sv;
}
/*
* Need to grab some things that aren't quite core perl API
*/
/* yyerror() is a long function and hard to emulate or copy-paste for our
* purposes; we'll reïmplement a smaller version of it
*/
#define LEX_IGNORE_UTF8_HINTS 0x00000002
#define PL_linestr (PL_parser->linestr)
#ifdef USE_UTF8_SCRIPTS
# define UTF cBOOL(!IN_BYTES)
#else
# define UTF cBOOL((PL_linestr && DO_UTF8(PL_linestr)) || ( !(PL_parser->lex_flags & LEX_IGNORE_UTF8_HINTS) && (PL_hints & HINT_UTF8)))
#endif
#define yyerror(s) S_yyerror(aTHX_ s)
void S_yyerror(pTHX_ const char *s)
{
SV *message = sv_2mortal(newSVpvs_flags("", 0));
char *context = PL_parser->oldbufptr;
STRLEN contlen = PL_parser->bufptr - PL_parser->oldbufptr;
sv_catpvf(message, "%s at %s line %" IVdf,
s, OutCopFILE(PL_curcop), (IV)CopLINE(PL_curcop));
if(context)
sv_catpvf(message, ", near \"%" UTF8f "\"",
UTF8fARG(UTF, contlen, context));
sv_catpvf(message, "\n");
PL_parser->error_count++;
warn_sv(message);
}
/* Stolen from op.c */
#ifndef OpTYPE_set
# define OpTYPE_set(op, type) \
STMT_START { \
op->op_type = (OPCODE)type; \
op->op_ppaddr = PL_ppaddr[type]; \
} STMT_END
#endif
#define alloc_LOGOP(a,b,c) S_alloc_LOGOP(aTHX_ a,b,c)
static LOGOP *S_alloc_LOGOP(pTHX_ I32 type, OP *first, OP* other)
{
dVAR;
LOGOP *logop;
OP *kid = first;
NewOp(1101, logop, 1, LOGOP);
OpTYPE_set(logop, type);
logop->op_first = first;
logop->op_other = other;
if (first)
logop->op_flags = OPf_KIDS;
while (kid && OpHAS_SIBLING(kid))
kid = OpSIBLING(kid);
if (kid)
OpLASTSIB_set(kid, (OP*)logop);
return logop;
}
/* copypaste from core's pp.c */
static SV *
S_find_runcv_name(pTHX)
{
CV *cv;
GV *gv;
SV *sv;
cv = find_runcv(0);
if (!cv)
return &PL_sv_no;
gv = CvGV(cv);
if (!gv)
return &PL_sv_no;
sv = sv_newmortal();
gv_fullname4(sv, gv, NULL, TRUE);
return sv;
}
static OP *pp_namedargdefelem(pTHX)
{
dSP;
ANY *op_any = cLOGOP_ANY->op_any;
SV *keysv = op_any[0].any_sv;
HV *slurpy_hv = (HV *)PAD_SVl(op_any[1].any_iv);
assert(slurpy_hv && SvTYPE(slurpy_hv) == SVt_PVHV);
/* TODO: we could precompute the hash and store it in the ANY vector */
SV *value = hv_delete_ent(slurpy_hv, keysv, 0, 0);
if(value) {
EXTEND(SP, 1);
PUSHs(value);
RETURN;
}
if(cLOGOP->op_other)
return cLOGOP->op_other;
croak_from_caller("Missing argument '%" SVf "' for subroutine %" SVf,
SVfARG(keysv), SVfARG(S_find_runcv_name(aTHX)));
}
static OP *pp_checknomorenamed(pTHX)
{
HV *slurpy_hv = (HV *)PAD_SVl(PL_op->op_targ);
if(!hv_iterinit(slurpy_hv))
return NORMAL;
/* There are remaining named arguments; concat their names into a message */
HE *he = hv_iternext(slurpy_hv);
SV *keynames = newSVpvn("", 0);
SAVEFREESV(keynames);
sv_catpvf(keynames, "'%" SVf "'", SVfARG(HeSVKEY_force(he)));
IV nkeys = 1;
while((he = hv_iternext(slurpy_hv)))
sv_catpvf(keynames, ", '%" SVf "'", SVfARG(HeSVKEY_force(he))), nkeys++;
croak_from_caller("Unrecognised %s %" SVf " for subroutine %" SVf,
nkeys > 1 ? "arguments" : "argument",
SVfARG(keynames), SVfARG(S_find_runcv_name(aTHX)));
}
#define OP_IS_NAMED_PARAM(o) (o->op_type == OP_ARGELEM && cUNOPx(o)->op_first && \
cUNOPx(o)->op_first->op_type == OP_CUSTOM && \
cUNOPx(o)->op_first->op_ppaddr == &pp_namedargdefelem)
/* Parameter attribute extensions */
typedef struct SignatureAttributeRegistration SignatureAttributeRegistration;
struct SignatureAttributeRegistration {
SignatureAttributeRegistration *next;
const char *name;
STRLEN permit_hintkeylen;
const struct XPSSignatureAttributeFuncs *funcs;
void *funcdata;
};
static SignatureAttributeRegistration *sigattrs = NULL;
#define find_registered_attribute(name) S_find_registered_attribute(aTHX_ name)
static SignatureAttributeRegistration *S_find_registered_attribute(pTHX_ const char *name)
{
HV *hints = GvHV(PL_hintgv);
SignatureAttributeRegistration *reg;
for(reg = sigattrs; reg; reg = reg->next) {
if(!strEQ(name, reg->name))
continue;
if(reg->funcs->permit_hintkey &&
(!hints || !hv_fetch(hints, reg->funcs->permit_hintkey, reg->permit_hintkeylen, 0)))
continue;
return reg;
}
croak("Unrecognised signature parameter attribute :%s", name);
}
struct PendingSignatureFunc {
const struct XPSSignatureAttributeFuncs *funcs;
void *funcdata;
void *attrdata;
};
#define PENDING_FROM_SV(sv) ((struct PendingSignatureFunc *)SvPVX(sv))
static void pending_free(pTHX_ SV *sv)
{
struct PendingSignatureFunc *p = PENDING_FROM_SV(sv);
if(p->funcs->free)
(*p->funcs->free)(aTHX_ p->attrdata, p->funcdata);
}
#define NEW_SV_PENDING() newSV_with_free(sizeof(struct PendingSignatureFunc), &pending_free)
struct SignatureParsingContext {
AV *named_varops; /* SV ptrs to the varop of every named parameter */
OP *last_varop; /* the most recently-constructed varop */
};
static void free_parsing_ctx(pTHX_ void *_ctx)
{
struct SignatureParsingContext *ctx = _ctx;
if(ctx->named_varops)
SvREFCNT_dec((SV *)ctx->named_varops);
}
#define parse_sigelem(ctx, flags) S_parse_sigelem(aTHX_ ctx, flags)
static OP *S_parse_sigelem(pTHX_ struct SignatureParsingContext *ctx, U32 flags)
{
bool permit_attributes = flags & PARSE_SUBSIGNATURE_PARAM_ATTRIBUTES;
yy_parser *parser = PL_parser;
int c = lex_peek_unichar(0);
int private;
struct XPSSignatureParamContext paramctx = {};
AV *pending = NULL;
if((flags & PARSE_SUBSIGNATURE_NAMED_PARAMS) && c == ':') {
lex_read_unichar(0);
lex_read_space(0);
paramctx.is_named = true;
c = lex_peek_unichar(0);
}
switch(c) {
case '$': private = OPpARGELEM_SV; break;
case '@': private = OPpARGELEM_AV; break;
case '%': private = OPpARGELEM_HV; break;
default:
croak("Expected a signature element at <%s>\n", parser->bufptr);
}
char *lexname = parser->bufptr;
/* Consume sigil */
lex_read_unichar(0);
char *lexname_end = NULL;
if(isIDFIRST_uni(lex_peek_unichar(0))) {
lex_read_unichar(0);
while(isALNUM_uni(lex_peek_unichar(0)))
lex_read_unichar(0);
paramctx.varop = newUNOP_AUX(OP_ARGELEM, 0, NULL, INT2PTR(UNOP_AUX_item *, (parser->sig_elems)));
paramctx.varop->op_private |= private;
if(paramctx.is_named) {
if(!ctx->named_varops)
ctx->named_varops = newAV();
av_push(ctx->named_varops, newSVpvx(paramctx.varop));
}
ctx->last_varop = paramctx.varop;
ENTER;
SAVEI16(PL_parser->in_my);
PL_parser->in_my = KEY_sigvar;
lexname_end = PL_parser->bufptr;
paramctx.padix = paramctx.varop->op_targ =
pad_add_name_pvn(lexname, lexname_end - lexname, 0, NULL, NULL);
LEAVE;
lex_read_space(0);
}
if(permit_attributes && lex_peek_unichar(0) == ':') {
lex_read_unichar(0);
lex_read_space(0);
SV *attrname = sv_newmortal(), *attrval = sv_newmortal();
while(lex_scan_attrval_into(attrname, attrval)) {
lex_read_space(0);
SignatureAttributeRegistration *reg = find_registered_attribute(SvPV_nolen(attrname));
void *attrdata = NULL;
if(reg->funcs->apply)
(*reg->funcs->apply)(aTHX_ &paramctx, attrval, &attrdata, reg->funcdata);
if(attrdata || reg->funcs->post_defop) {
if(!pending) {
pending = newAV();
SAVEFREESV(pending);
}
SV *psv;
av_push(pending, psv = NEW_SV_PENDING());
PENDING_FROM_SV(psv)->funcs = reg->funcs;
PENDING_FROM_SV(psv)->funcdata = reg->funcdata;
PENDING_FROM_SV(psv)->attrdata = attrdata;
}
if(lex_peek_unichar(0) == ':') {
lex_read_unichar(0);
lex_read_space(0);
}
}
}
if(c == '$') {
SV *argname = NULL;
if(paramctx.is_named) {
parser->sig_slurpy = '+';
argname = newSVpvn(lexname + 1, lexname_end - lexname - 1);
}
else {
if(parser->sig_slurpy)
yyerror("Slurpy parameters not last");
parser->sig_elems++;
}
if(lex_peek_unichar(0) == '=') {
lex_read_unichar(0);
lex_read_space(0);
if(!paramctx.is_named)
parser->sig_optelems++;
OP *defexpr = parse_termexpr(0);
if(paramctx.is_named) {
paramctx.defop = (OP *)alloc_LOGOP_ANY(OP_CUSTOM, defexpr, LINKLIST(defexpr));
paramctx.defop->op_ppaddr = &pp_namedargdefelem;
}
else {
paramctx.defop = (OP *)alloc_LOGOP(OP_ARGDEFELEM, defexpr, LINKLIST(defexpr));
paramctx.defop->op_targ = (PADOFFSET)(parser->sig_elems - 1);
}
paramctx.varop->op_flags |= OPf_STACKED;
op_sibling_splice(paramctx.varop, NULL, 0, paramctx.defop);
paramctx.defop = op_contextualize(paramctx.defop, G_SCALAR);
LINKLIST(paramctx.varop);
paramctx.varop->op_next = paramctx.defop;
defexpr->op_next = paramctx.varop;
}
else {
if(parser->sig_optelems)
yyerror("Mandatory parameter follows optional parameter");
}
if(paramctx.is_named) {
OP *defop = paramctx.defop;
if(!defop) {
defop = (OP *)alloc_LOGOP_ANY(OP_CUSTOM, NULL, NULL);
defop->op_ppaddr = &pp_namedargdefelem;
paramctx.varop->op_flags |= OPf_STACKED;
op_sibling_splice(paramctx.varop, NULL, 0, defop);
LINKLIST(paramctx.varop);
paramctx.varop->op_next = defop;
}
ANY *op_any;
Newx(op_any, 2, ANY);
op_any[0].any_sv = argname;
/* [1] is filled in later */
cLOGOP_ANYx(defop)->op_any = op_any;
}
}
else {
if(paramctx.is_named)
yyerror("Slurpy parameters may not be named");
if(parser->sig_slurpy && parser->sig_slurpy != '+')
yyerror("Multiple slurpy parameters not allowed");
parser->sig_slurpy = c;
if(lex_peek_unichar(0) == '=')
yyerror("A slurpy parameter may not have a default value");
}
paramctx.op = paramctx.varop;
if(pending) {
for(int i = 0; i <= AvFILL(pending); i++) {
struct PendingSignatureFunc *p = PENDING_FROM_SV(AvARRAY(pending)[i]);
if(p->funcs->post_defop)
(*p->funcs->post_defop)(aTHX_ &paramctx, p->attrdata, p->funcdata);
}
}
return paramctx.op ? newSTATEOP(0, NULL, paramctx.op) : NULL;
}
OP *XPS_parse_subsignature_ex(pTHX_ int flags)
{
/* Mostly reconstructed logic from perl 5.28.0's toke.c and perly.y
*/
yy_parser *parser = PL_parser;
struct SignatureParsingContext ctx = {};
assert((flags & ~(PARSE_SUBSIGNATURE_NAMED_PARAMS|PARSE_SUBSIGNATURE_PARAM_ATTRIBUTES)) == 0);
ENTER;
SAVEDESTRUCTOR_X(&free_parsing_ctx, &ctx);
SAVEIV(parser->sig_elems);
SAVEIV(parser->sig_optelems);
SAVEI8(parser->sig_slurpy);
parser->sig_elems = 0;
parser->sig_optelems = 0;
parser->sig_slurpy = 0;
OP *elems = NULL;
OP *namedelems = NULL;
OP *final_elem = NULL;
while(lex_peek_unichar(0) != ')') {
lex_read_space(0);
OP *elem = parse_sigelem(&ctx, flags);
/* placeholder anonymous elems are NULL */
if(elem) {
/* elem should be an OP_LINESEQ[ OP_NEXTSTATE. actual elem ] */
assert(elem->op_type == OP_LINESEQ);
assert(cLISTOPx(elem)->op_first);
assert(OpSIBLING(cLISTOPx(elem)->op_first));
final_elem = OpSIBLING(cLISTOPx(elem)->op_first);
if(OP_IS_NAMED_PARAM(ctx.last_varop))
namedelems = op_append_list(OP_LIST, namedelems, elem);
else
elems = op_append_list(OP_LINESEQ, elems, elem);
}
if(PL_parser->error_count) {
LEAVE;
return NULL;
}
lex_read_space(0);
switch(lex_peek_unichar(0)) {
case ')': goto endofelems;
case ',': break;
default:
fprintf(stderr, "ARGH unsure how to proceed parse_subsignature at <%s>\n",
parser->bufptr);
croak("ARGH");
break;
}
lex_read_unichar(0);
lex_read_space(0);
}
endofelems:
if (!FEATURE_SIGNATURES_IS_ENABLED)
croak("Experimental subroutine signatures not enabled");
#if !HAVE_PERL_VERSION(5, 37, 0)
Perl_ck_warner_d(aTHX_ packWARN(WARN_EXPERIMENTAL__SIGNATURES),
"The signatures feature is experimental");
#endif
bool allow_extras_after_named = true;
if(ctx.named_varops) {
switch(PL_parser->sig_slurpy) {
case 0:
case '@':
NOT_REACHED;
case '+':
{
/* Pretend we have a new, unnamed slurpy hash */
OP *varop = newUNOP_AUX(OP_ARGELEM, 0, NULL, INT2PTR(UNOP_AUX_item *, (parser->sig_elems)));
varop->op_private |= OPpARGELEM_HV;
varop->op_targ = pad_add_name_pvs("%(params)", 0, NULL, NULL);
final_elem = varop;
OP *elem = newSTATEOP(0, NULL, varop);
elems = op_append_list(OP_LINESEQ, elems, elem);
PL_parser->sig_slurpy = '%';
allow_extras_after_named = false;
}
break;
case '%':
break;
}
}
UNOP_AUX_item *aux = make_argcheck_aux(
parser->sig_elems, parser->sig_optelems, parser->sig_slurpy);
OP *checkop = newUNOP_AUX(OP_ARGCHECK, 0, NULL, aux);
OP *ops = op_prepend_elem(OP_LINESEQ, newSTATEOP(0, NULL, NULL),
op_prepend_elem(OP_LINESEQ, checkop, elems));
if(ctx.named_varops) {
assert(final_elem->op_type == OP_ARGELEM);
assert(final_elem->op_private == OPpARGELEM_HV);
PADOFFSET slurpy_padix = final_elem->op_targ;
/* Tell all the pp_namedargdefelem()s where to find the slurpy hash */
for(int i = 0; i <= AvFILL(ctx.named_varops); i++) {
OP *elemop = (OP *)(SvPVX(AvARRAY(ctx.named_varops)[i]));
assert(elemop);
assert(OP_IS_NAMED_PARAM(elemop));
OP *defelemop = cUNOPx(elemop)->op_first;
assert(defelemop);
assert(defelemop->op_type == OP_CUSTOM &&
defelemop->op_ppaddr == &pp_namedargdefelem);
ANY *op_any = cLOGOP_ANYx(defelemop)->op_any;
op_any[1].any_iv = slurpy_padix;
}
ops = op_append_list(OP_LINESEQ, ops,
namedelems);
if(!allow_extras_after_named) {
ops = op_append_list(OP_LINESEQ, ops,
newSTATEOP(0, NULL, checkop = newOP(OP_CUSTOM, 0)));
checkop->op_ppaddr = &pp_checknomorenamed;
checkop->op_targ = slurpy_padix;
}
}
/* a nextstate at the end handles context correctly for an empty
* sub body */
ops = op_append_elem(OP_LINESEQ, ops, newSTATEOP(0, NULL, NULL));
LEAVE;
return ops;
}
void XPS_register_subsignature_attribute(pTHX_ const char *name, const struct XPSSignatureAttributeFuncs *funcs, void *funcdata)
{
SignatureAttributeRegistration *reg;
Newx(reg, 1, struct SignatureAttributeRegistration);
*reg = (struct SignatureAttributeRegistration){
.name = name,
.funcs = funcs,
.funcdata = funcdata,
};
if(funcs->permit_hintkey)
reg->permit_hintkeylen = strlen(funcs->permit_hintkey);
reg->next = sigattrs;
sigattrs = reg;
}
#else /* !HAVE_PERL_VERSION(5, 26, 0) */
void XPS_register_subsignature_attribute(pTHX_ const char *name, const struct XPSSignatureAttributeFuncs *funcs, void *funcdata)
{
croak("Custom subroutine signature attributes are not supported on this verison of Perl");
}
#endif

View File

@ -3,8 +3,9 @@
use v5.14; use v5.14;
use warnings; use warnings;
use Test::More; use Test2::V0;
use_ok( "XS::Parse::Sublike" ); require XS::Parse::Sublike;
pass( "Modules loaded" );
done_testing; done_testing;

View File

@ -3,11 +3,13 @@
use v5.14; use v5.14;
use warnings; use warnings;
use Test::More; use Test2::V0;
use lib "t"; use lib "t";
use testcase "t::func"; use testcase "t::func";
BEGIN { $^H{"t::func/func"}++ }
# named func # named func
{ {
func example { return 123; } func example { return 123; }
@ -30,7 +32,7 @@ use testcase "t::func";
{ {
my ( $pkg, $sub, $attr ) = @_; my ( $pkg, $sub, $attr ) = @_;
$modify_invoked++; $modify_invoked++;
Test::More::is( $attr, "MyCustomAttribute(value here)", ::is( $attr, "MyCustomAttribute(value here)",
'MODIFY_CODE_ATTRIBUTES takes attribute' ); 'MODIFY_CODE_ATTRIBUTES takes attribute' );
return (); return ();

View File

@ -3,11 +3,10 @@
use v5.14; use v5.14;
use warnings; use warnings;
use Test::More; use Test2::V0;
BEGIN { BEGIN {
$] >= 5.026000 or plan skip_all => "No parse_subsignature()"; $] >= 5.026000 or plan skip_all => "No parse_subsignature()";
} }
use Test::Fatal;
use feature 'signatures'; use feature 'signatures';
no warnings 'experimental'; no warnings 'experimental';
@ -15,6 +14,8 @@ no warnings 'experimental';
use lib "t"; use lib "t";
use testcase "t::func"; use testcase "t::func";
BEGIN { $^H{"t::func/func"}++ }
# basic sig # basic sig
{ {
func withparam($x) { return $x + 1 } func withparam($x) { return $x + 1 }
@ -56,7 +57,7 @@ use testcase "t::func";
func noparams() { return "constant" } func noparams() { return "constant" }
is( noparams, "constant", 'func with no params' ); is( noparams, "constant", 'func with no params' );
like( exception { noparams( 1, 2, 3 ) }, like( dies { noparams( 1, 2, 3 ) },
# message was extended somewhere in perl 5.33 # message was extended somewhere in perl 5.33
qr/^Too many arguments for subroutine 'main::noparams' (\(.*\) )?at /, qr/^Too many arguments for subroutine 'main::noparams' (\(.*\) )?at /,
'Exception thrown from empty signature validation failure' ); 'Exception thrown from empty signature validation failure' );

View File

@ -3,7 +3,7 @@
use v5.14; use v5.14;
use warnings; use warnings;
use Test::More; use Test2::V0;
BEGIN { BEGIN {
$] >= 5.026000 or plan skip_all => "No parse_subsignature()"; $] >= 5.026000 or plan skip_all => "No parse_subsignature()";
} }
@ -14,6 +14,8 @@ no warnings 'experimental';
use lib "t"; use lib "t";
use testcase "t::func"; use testcase "t::func";
BEGIN { $^H{"t::func/func"}++ }
use B::Deparse; use B::Deparse;
my $deparser = B::Deparse->new(); my $deparser = B::Deparse->new();

84
t/04func-named-params.t Normal file
View File

@ -0,0 +1,84 @@
#!/usr/bin/perl
use v5.14;
use warnings;
use Test2::V0;
BEGIN {
$] >= 5.026000 or plan skip_all => "No parse_subsignature()";
}
use feature 'signatures';
no warnings 'experimental';
use lib "t";
use testcase "t::func";
BEGIN { $^H{"t::func/nfunc"}++ }
# a signature with experimental named parameter support
{
my %was_rest;
nfunc withx(:$x, %rest) { %was_rest = %rest; return $x }
is( withx( x => 123 ), 123, 'named param extracts value' );
is( \%was_rest, {}, 'named param not visible in %rest' );
withx( x => 1, y => 2 );
is( \%was_rest, { y => 2 }, 'other params still visible in %rest' );
my $LINE = __LINE__+1;
like( dies { withx() },
qr/^Missing argument 'x' for subroutine main::withx at \S+ line $LINE\./,
'complaint from missing named param' );
nfunc with2x(:$x1, :$x2) { return "x1=$x1 x2=$x2"; }
is( with2x( x1 => 10, x2 => 20 ), "x1=10 x2=20",
'supports multiple named params' );
}
# named params can still have defaults
{
nfunc withy(:$y = "Y", %rest) { return $y }
is( withy( y => 456 ), 456, 'named param with default' );
is( withy(), "Y", 'named param applies default' );
}
# named params still work without a slurpy
{
nfunc withz(:$z) { return $z }
is( withz( z => 789 ), 789, 'named param without slurpy' );
my $LINE = __LINE__+1;
like( dies { withz( z => 1, w => 1 ); 1 },
qr/^Unrecognised argument 'w' for subroutine main::withz at \S+ line $LINE\./,
'complaint from unknown param' );
}
# mixed positional+named
{
nfunc withboth($x, :$y = "def") { return "x=$x y=$y"; }
is( withboth(1, y => 2), "x=1 y=2",
'supports mixed positional + named' );
is( withboth(1), "x=1 y=def",
'mixed still applies defaults' );
}
# diagnostics on duplicates
{
sub warnings_from ( $code ) {
my $warnings = "";
local $SIG{__WARN__} = sub { $warnings .= $_[0] };
eval( "$code; 1" ) or die $@;
return $warnings;
}
like( warnings_from( 'nfunc diag1($x, :$x) { }' ),
qr/^"my" variable \$x masks earlier declaration in same scope at /,
'warning from duplicated parameter name' );
}
done_testing;

30
t/05func-sig-attributes.t Normal file
View File

@ -0,0 +1,30 @@
#!/usr/bin/perl
use v5.14;
use warnings;
use Test2::V0;
BEGIN {
$] >= 5.026000 or plan skip_all => "No parse_subsignature()";
}
use feature 'signatures';
no warnings 'experimental';
use lib "t";
use testcase "t::func";
BEGIN { $^H{"t::func/afunc"}++ }
BEGIN { $^H{"t::func/Attribute"}++ }
our @ATTRIBUTE_APPLIED;
{
afunc withattr($x :Attribute, $y :Attribute(Value)) { }
is( \@ATTRIBUTE_APPLIED,
[ '$x' => undef, '$y' => "Value" ],
':Attribute applied to subroutine parameters' );
}
done_testing;

View File

@ -3,7 +3,7 @@
use v5.14; use v5.14;
use warnings; use warnings;
use Test::More; use Test2::V0;
use lib "t"; use lib "t";
use testcase "t::stages"; use testcase "t::stages";
@ -14,7 +14,7 @@ sub stages { return $_[0] }
{ {
my $ret = stages { one => "one" }; my $ret = stages { one => "one" };
is_deeply( $ret, { one => "one" }, is( $ret, { one => "one" },
'not permitted keyword falls through to regular symbol lookup' ); 'not permitted keyword falls through to regular symbol lookup' );
} }

View File

@ -3,7 +3,7 @@
use v5.14; use v5.14;
use warnings; use warnings;
use Test::More; use Test2::V0;
use lib "t"; use lib "t";
use testcase "t::stages"; use testcase "t::stages";

View File

@ -3,7 +3,7 @@
use v5.14; use v5.14;
use warnings; use warnings;
use Test::More; use Test2::V0;
use lib "t"; use lib "t";
use testcase "t::stages"; use testcase "t::stages";

View File

@ -3,7 +3,7 @@
use v5.14; use v5.14;
use warnings; use warnings;
use Test::More; use Test2::V0;
use lib "t"; use lib "t";
use testcase "t::stages"; use testcase "t::stages";

View File

@ -3,7 +3,7 @@
use v5.14; use v5.14;
use warnings; use warnings;
use Test::More; use Test2::V0;
use lib "t"; use lib "t";
use testcase "t::stages"; use testcase "t::stages";

View File

@ -3,7 +3,7 @@
use v5.14; use v5.14;
use warnings; use warnings;
use Test::More; use Test2::V0;
use lib "t"; use lib "t";
use testcase "t::stages"; use testcase "t::stages";
@ -16,7 +16,7 @@ BEGIN { $^H{"t::stages/permit"} = 1; }
stages withattr :atname(atvalue) { } stages withattr :atname(atvalue) { }
is_deeply( $t::stages::captured, [ atname => "atvalue" ], is( $t::stages::captured, [ atname => "atvalue" ],
'captured an attribute by attr filter' ); 'captured an attribute by attr filter' );
} }

View File

@ -3,7 +3,7 @@
use v5.14; use v5.14;
use warnings; use warnings;
use Test::More; use Test2::V0;
use lib "t"; use lib "t";
use testcase "t::registrations"; use testcase "t::registrations";

View File

@ -3,11 +3,13 @@
use v5.14; use v5.14;
use warnings; use warnings;
use Test::More; use Test2::V0;
use lib "t"; use lib "t";
use testcase "t::any"; use testcase "t::any";
BEGIN { $^H{"t::any/func"}++ }
our $LOG; BEGIN { $LOG = "" }; our $LOG; BEGIN { $LOG = "" };
prefixed func example { prefixed func example {

View File

@ -3,11 +3,16 @@
use v5.14; use v5.14;
use warnings; use warnings;
use Test::More; use Test2::V0;
use lib "t"; use lib "t";
use testcase "t::prefix"; use testcase "t::prefix";
BEGIN {
$^H{"t::prefix/func"}++;
$^H{"t::prefix/prefixed"}++;
}
our $LOG; BEGIN { $LOG = "" }; our $LOG; BEGIN { $LOG = "" };
prefixed func example { prefixed func example {

View File

@ -3,7 +3,7 @@
use v5.14; use v5.14;
use warnings; use warnings;
use Test::More; use Test2::V0;
use lib "t"; use lib "t";
use testcase "t::parts"; use testcase "t::parts";

View File

@ -3,7 +3,7 @@
use v5.14; use v5.14;
use warnings; use warnings;
use Test::More; use Test2::V0;
use lib "t"; use lib "t";
use testcase "t::parts"; use testcase "t::parts";

View File

@ -3,7 +3,7 @@
use v5.14; use v5.14;
use warnings; use warnings;
use Test::More; use Test2::V0;
BEGIN { BEGIN {
$] >= 5.026000 or plan skip_all => "No parse_subsignature()"; $] >= 5.026000 or plan skip_all => "No parse_subsignature()";
} }

View File

@ -3,7 +3,7 @@
use v5.14; use v5.14;
use warnings; use warnings;
use Test::More; use Test2::V0;
use lib "t"; use lib "t";
use testcase "t::flags"; use testcase "t::flags";

View File

@ -3,7 +3,7 @@
use v5.14; use v5.14;
use warnings; use warnings;
use Test::More; use Test2::V0;
use Sub::Util 'subname'; use Sub::Util 'subname';
use lib "t"; use lib "t";

55
t/70extended.t Normal file
View File

@ -0,0 +1,55 @@
#!/usr/bin/perl
use v5.14;
use warnings;
use Test2::V0;
BEGIN {
$] >= 5.026000 or plan skip_all => "No parse_subsignature()";
}
use feature 'signatures';
no warnings 'experimental';
use Sublike::Extended;
# basic sub
{
extended sub t1 { return "named" }
my $t2 = extended sub { return "anon" };
is( t1(), "named",
'extended sub can make basic named' );
is( $t2->(), "anon",
'extended sub can make basic anon' );
}
# sub with named params
{
extended sub t3 (:$x, :$y) { return "x=$x y=$y"; }
my $t4 = extended sub ( :$i, :$j ) { return "i=$i j=$j"; };
is( t3( x => 10, y => 20 ), "x=10 y=20",
'extended sub can make named param subs' );
is( $t4->( j => 40, i => 30 ), "i=30 j=40",
'extended sub can make named param anon subs' );
}
use lib "t";
use testcase "t::func";
BEGIN { $^H{"t::func/Attribute"}++ }
our @ATTRIBUTE_APPLIED;
{
extended sub t5 ($x :Attribute, $y :Attribute(Value)) { }
# No need to call it just to see these
is( \@ATTRIBUTE_APPLIED,
[ '$x' => undef, '$y' => "Value" ],
':Attribute applied to subroutine parameters' );
}
done_testing;

30
t/80extended+Object-Pad.t Normal file
View File

@ -0,0 +1,30 @@
#!/usr/bin/perl
use v5.14;
use warnings;
use Test2::V0;
BEGIN {
$] >= 5.026000 or plan skip_all => "No parse_subsignature()";
eval { require Object::Pad;
Object::Pad->VERSION( '0.800' );
1; } or
plan skip_all => "No Object::Pad";
Object::Pad->import;
}
use Sublike::Extended;
# extended method
{
class C1 {
extended method f (:$x, :$y) { return "x=$x y=$y" }
}
is( C1->new->f( x => "first", y => "second" ), "x=first y=second",
'async method' );
}
done_testing;

31
t/80extended+async.t Normal file
View File

@ -0,0 +1,31 @@
#!/usr/bin/perl
use v5.14;
use warnings;
use Test2::V0;
BEGIN {
$] >= 5.026000 or plan skip_all => "No parse_subsignature()";
eval { require Future::AsyncAwait;
Future::AsyncAwait->VERSION( '0.66' );
1; } or
plan skip_all => "No Future::AsyncAwait";
Future::AsyncAwait->import;
}
use feature 'signatures';
no warnings 'experimental';
use Sublike::Extended;
# async extended sub
{
async extended sub f (:$x, :$y) { return "x=$x y=$y" }
is( await f( x => "first", y => "second" ), "x=first y=second",
'async extended sub' );
}
done_testing;

View File

@ -0,0 +1,31 @@
#!/usr/bin/perl
use v5.14;
use warnings;
use Test2::V0;
BEGIN {
$] >= 5.026000 or plan skip_all => "No parse_subsignature()";
# version 5.37.10 added the ability to start_subparse() with CVf_IsMETHOD,
# which we need
plan skip_all => "feature 'class' is not available"
unless $^V ge v5.37.10;
}
use Sublike::Extended;
use feature 'class';
no warnings 'experimental::class';
# extended method
{
class C1 {
extended method f (:$x, :$y) { return "x=$x y=$y" }
}
is( C1->new->f( x => "first", y => "second" ), "x=first y=second",
'async method' );
}
done_testing;

View File

@ -3,7 +3,7 @@
use v5.14; use v5.14;
use warnings; use warnings;
use Test::More; use Test2::V0;
eval "use Test::Pod 1.00"; eval "use Test::Pod 1.00";
plan skip_all => "Test::Pod 1.00 required for testing POD" if $@; plan skip_all => "Test::Pod 1.00 required for testing POD" if $@;

View File

@ -1,7 +1,7 @@
/* You may distribute under the terms of either the GNU General Public License /* You may distribute under the terms of either the GNU General Public License
* or the Artistic License (the same terms as Perl itself) * or the Artistic License (the same terms as Perl itself)
* *
* (C) Paul Evans, 2020 -- leonerd@leonerd.org.uk * (C) Paul Evans, 2020-2023 -- leonerd@leonerd.org.uk
*/ */
#include "EXTERN.h" #include "EXTERN.h"
@ -39,6 +39,7 @@ static void func_post_newcv(pTHX_ struct XSParseSublikeContext *ctx, void *_logs
} }
static const struct XSParseSublikeHooks parse_func_hooks = { static const struct XSParseSublikeHooks parse_func_hooks = {
.permit_hintkey = "t::any/func",
.pre_subparse = func_pre_subparse, .pre_subparse = func_pre_subparse,
.post_blockstart = func_post_blockstart, .post_blockstart = func_post_blockstart,
.pre_blockend = func_pre_blockend, .pre_blockend = func_pre_blockend,

View File

@ -1,7 +1,7 @@
/* You may distribute under the terms of either the GNU General Public License /* You may distribute under the terms of either the GNU General Public License
* or the Artistic License (the same terms as Perl itself) * or the Artistic License (the same terms as Perl itself)
* *
* (C) Paul Evans, 2020 -- leonerd@leonerd.org.uk * (C) Paul Evans, 2020-2023 -- leonerd@leonerd.org.uk
*/ */
#include "EXTERN.h" #include "EXTERN.h"
@ -10,13 +10,52 @@
#include "XSParseSublike.h" #include "XSParseSublike.h"
#if PERL_REVISION > 5 || (PERL_REVISION == 5 && PERL_VERSION >= 26)
# define HAVE_SUB_PARAM_ATTRIBUTES
#endif
static const struct XSParseSublikeHooks parse_func_hooks = { static const struct XSParseSublikeHooks parse_func_hooks = {
/* empty */ .permit_hintkey = "t::func/func",
}; };
static const struct XSParseSublikeHooks parse_nfunc_hooks = {
.permit_hintkey = "t::func/nfunc",
.flags = XS_PARSE_SUBLIKE_FLAG_SIGNATURE_NAMED_PARAMS,
};
static const struct XSParseSublikeHooks parse_afunc_hooks = {
.permit_hintkey = "t::func/afunc",
.flags = XS_PARSE_SUBLIKE_FLAG_SIGNATURE_PARAM_ATTRIBUTES,
};
#ifdef HAVE_SUB_PARAM_ATTRIBUTES
static void apply_Attribute(pTHX_ struct XPSSignatureParamContext *ctx, SV *attrvalue, void **attrdata_ptr, void *funcdata)
{
/* TODO: maybe the context should store a lexname string? */
PADNAME *pn = PadnamelistARRAY(PL_comppad_name)[ctx->padix];
AV *av = get_av("main::ATTRIBUTE_APPLIED", GV_ADD);
av_push(av, newSVsv(PadnameSV(pn)));
av_push(av, newSVsv(attrvalue));
}
static const struct XPSSignatureAttributeFuncs attr_funcs = {
.ver = XSPARSESUBLIKE_ABI_VERSION,
.permit_hintkey = "t::func/Attribute",
.apply = apply_Attribute,
};
#endif
MODULE = t::func PACKAGE = t::func MODULE = t::func PACKAGE = t::func
BOOT: BOOT:
boot_xs_parse_sublike(0); boot_xs_parse_sublike(0);
register_xs_parse_sublike("func", &parse_func_hooks, NULL); register_xs_parse_sublike("func", &parse_func_hooks, NULL);
register_xs_parse_sublike("nfunc", &parse_nfunc_hooks, NULL);
register_xs_parse_sublike("afunc", &parse_afunc_hooks, NULL);
#ifdef HAVE_SUB_PARAM_ATTRIBUTES
register_xps_signature_attribute("Attribute", &attr_funcs, NULL);
#endif

View File

@ -35,6 +35,7 @@ static void func_post_newcv(pTHX_ struct XSParseSublikeContext *ctx, void *_logs
} }
static const struct XSParseSublikeHooks parse_func_hooks = { static const struct XSParseSublikeHooks parse_func_hooks = {
.permit_hintkey = "t::prefix/func",
.pre_subparse = func_pre_subparse, .pre_subparse = func_pre_subparse,
.post_blockstart = func_post_blockstart, .post_blockstart = func_post_blockstart,
.pre_blockend = func_pre_blockend, .pre_blockend = func_pre_blockend,
@ -67,6 +68,7 @@ static void prefixed_post_newcv(pTHX_ struct XSParseSublikeContext *ctx, void *_
static const struct XSParseSublikeHooks parse_prefixed_hooks = { static const struct XSParseSublikeHooks parse_prefixed_hooks = {
.flags = XS_PARSE_SUBLIKE_FLAG_PREFIX, .flags = XS_PARSE_SUBLIKE_FLAG_PREFIX,
.permit_hintkey = "t::prefix/prefixed",
.pre_subparse = prefixed_pre_subparse, .pre_subparse = prefixed_pre_subparse,
.post_blockstart = prefixed_post_blockstart, .post_blockstart = prefixed_post_blockstart,

View File

@ -1,6 +1,6 @@
package testcase; package testcase;
use strict; use v5.14;
use warnings; use warnings;
use lib "t/blib", "t/blib/arch"; use lib "t/blib", "t/blib/arch";