Import Upstream version 0.16
This commit is contained in:
commit
50b956e507
|
@ -0,0 +1,28 @@
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use lib 'inc';
|
||||||
|
use Module::Build::with::XSTests;
|
||||||
|
|
||||||
|
my $build = Module::Build::with::XSTests->new(
|
||||||
|
module_name => 'XS::Parse::Sublike',
|
||||||
|
requires => {
|
||||||
|
'perl' => '5.016', # pad_add_name_pvn
|
||||||
|
# Not actually needed but used by hax/lexer-additions.c.inc
|
||||||
|
},
|
||||||
|
test_requires => {
|
||||||
|
'Sub::Util' => 0,
|
||||||
|
'Test::Fatal' => 0,
|
||||||
|
'Test::More' => '0.88', # done_testing
|
||||||
|
},
|
||||||
|
configure_requires => {
|
||||||
|
'Module::Build' => '0.4004', # test_requires
|
||||||
|
},
|
||||||
|
license => 'perl',
|
||||||
|
create_license => 1,
|
||||||
|
create_readme => 1,
|
||||||
|
|
||||||
|
extra_compiler_flags => [qw( -I. -Ihax )],
|
||||||
|
);
|
||||||
|
|
||||||
|
$build->create_build_script;
|
|
@ -0,0 +1,109 @@
|
||||||
|
Revision history for XS-Parse-Sublike
|
||||||
|
|
||||||
|
0.16 2021-12-16
|
||||||
|
[CHANGES]
|
||||||
|
* No longer support ABI version 3 even in back-compatibility mode
|
||||||
|
|
||||||
|
[BUGFIXES]
|
||||||
|
* Remember to adjust the SET_CVNAME and INSTALL_SYMBOL actions bits
|
||||||
|
in the non-dynamic-actions case
|
||||||
|
* Avoid using C99-style `//` comments as some compilers dislike it
|
||||||
|
(mistagged RT140487)
|
||||||
|
|
||||||
|
0.15 2021-12-15
|
||||||
|
[CHANGES]
|
||||||
|
* Added ctx->actions, the DYNAMIC_ACTIONS compat flag, and associated
|
||||||
|
flags and machinery
|
||||||
|
* Added ctx->moddata
|
||||||
|
|
||||||
|
0.14 2021-10-28
|
||||||
|
[CHANGES]
|
||||||
|
* Added XS_PARSE_SUBLIKE_FLAG_PREFIX; discourage the _any() function
|
||||||
|
to achieve similar effect
|
||||||
|
* Added XS_PARSE_SUBLIKE_FLAG_BODY_OPTIONAL to permit bodyless sublike
|
||||||
|
declarations
|
||||||
|
* Better compiletime warnings when a sub declaration lacks a
|
||||||
|
body-delimiting brace
|
||||||
|
|
||||||
|
[BUGFIXES]
|
||||||
|
* Fix for Perl < 5.20 on Win32 (thanks ilmari) (RT139296)
|
||||||
|
* Handle the new OP_ARGCHECK aux structure of perl 5.31.5 (thanks
|
||||||
|
ilmari) (RT139295)
|
||||||
|
* Accomodate newly-added core sv_setrv_*() functions
|
||||||
|
|
||||||
|
0.13 2021-08-30
|
||||||
|
[CHANGES]
|
||||||
|
* New ABI version 4
|
||||||
|
+ Set up functions in PL_modglobal instead of perl-visible global
|
||||||
|
symbol table
|
||||||
|
+ Add a `permit_hintkey` convenient shortcut
|
||||||
|
* Better code structure of XS::Parse::Sublike::Builder
|
||||||
|
|
||||||
|
0.12 2021-06-21
|
||||||
|
[BUGFIXES]
|
||||||
|
* Try to work around parse_block()'s unbalanced scopestack messups
|
||||||
|
* Namespace the static variables in the .h file appropriately in case
|
||||||
|
both this as XSParseKeyword are loaded
|
||||||
|
|
||||||
|
0.11 2021-01-18
|
||||||
|
[BUGFIXES]
|
||||||
|
* Don't SEGV on signature parse error (RT133865)
|
||||||
|
* Account for change of signature validation error message in latest
|
||||||
|
bleadperl (RT134012)
|
||||||
|
|
||||||
|
0.10 2020-06-20
|
||||||
|
[BREAKING CHANGES]
|
||||||
|
* New ABI version 3
|
||||||
|
|
||||||
|
[CHANGES]
|
||||||
|
* Pass an additional 'hookdata' parameter around between caller and
|
||||||
|
hook stage functions
|
||||||
|
|
||||||
|
0.09 2020-06-15
|
||||||
|
[CHANGES]
|
||||||
|
* Define API for an optional stage for filtering and applying
|
||||||
|
attributes during parsing
|
||||||
|
|
||||||
|
0.08 2020-06-12
|
||||||
|
[CHANGES]
|
||||||
|
* Define API to either require or skip parsing the name of a sub, or
|
||||||
|
skip parsing attributes or signatures
|
||||||
|
|
||||||
|
0.07 2020-04-02
|
||||||
|
[BUGFIXES]
|
||||||
|
* Ensure that empty-signature functions are still parsed correctly
|
||||||
|
and include arg count check (RT132284)
|
||||||
|
|
||||||
|
0.06 2020-03-27
|
||||||
|
[CHANGES]
|
||||||
|
* Pass a standard shared context struct through the various hook
|
||||||
|
stages
|
||||||
|
* Add an early hook stage before the call to start_subparse()
|
||||||
|
|
||||||
|
0.05 2020-03-26
|
||||||
|
[BUGFIXES]
|
||||||
|
* Fix hax/parse_subsignature.c.inc to cope with whitespace between
|
||||||
|
parameter declarations
|
||||||
|
|
||||||
|
0.04 2020-03-18
|
||||||
|
[CHANGES]
|
||||||
|
* Pass a required module version number to boot_xs_parse_sublike()
|
||||||
|
* Added lots of unit tests
|
||||||
|
|
||||||
|
0.03 2020-03-18
|
||||||
|
[CHANGES]
|
||||||
|
* Added xs_parse_sublike_any() allowing custom keywords to combine
|
||||||
|
with registered hooks
|
||||||
|
|
||||||
|
[BUGFIXES]
|
||||||
|
* Avoid double-free of sigops in case of parse failure
|
||||||
|
* Fix hax/parse_subsignature.c.inc for named parameters with default
|
||||||
|
expressions
|
||||||
|
|
||||||
|
0.02 2020-03-17
|
||||||
|
[CHANGES]
|
||||||
|
* Added `register_xs_parse_sublike()` and the `permit` hook stage
|
||||||
|
* Added toplevel PL_keyword_hook parser function
|
||||||
|
|
||||||
|
0.01 2020-03-15
|
||||||
|
First version, released on an unsuspecting world.
|
|
@ -0,0 +1,379 @@
|
||||||
|
This software is copyright (c) 2021 by Paul Evans <leonerd@leonerd.org.uk>.
|
||||||
|
|
||||||
|
This is free software; you can redistribute it and/or modify it under
|
||||||
|
the same terms as the Perl 5 programming language system itself.
|
||||||
|
|
||||||
|
Terms of the Perl programming language system itself
|
||||||
|
|
||||||
|
a) the GNU General Public License as published by the Free
|
||||||
|
Software Foundation; either version 1, or (at your option) any
|
||||||
|
later version, or
|
||||||
|
b) the "Artistic License"
|
||||||
|
|
||||||
|
--- The GNU General Public License, Version 1, February 1989 ---
|
||||||
|
|
||||||
|
This software is Copyright (c) 2021 by Paul Evans <leonerd@leonerd.org.uk>.
|
||||||
|
|
||||||
|
This is free software, licensed under:
|
||||||
|
|
||||||
|
The GNU General Public License, Version 1, February 1989
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 1, February 1989
|
||||||
|
|
||||||
|
Copyright (C) 1989 Free Software Foundation, Inc.
|
||||||
|
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The license agreements of most software companies try to keep users
|
||||||
|
at the mercy of those companies. By contrast, our General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. The
|
||||||
|
General Public License applies to the Free Software Foundation's
|
||||||
|
software and to any other program whose authors commit to using it.
|
||||||
|
You can use it for your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Specifically, the General Public License is designed to make
|
||||||
|
sure that you have the freedom to give away or sell copies of free
|
||||||
|
software, that you receive source code or can get it if you want it,
|
||||||
|
that you can change the software or use pieces of it in new free
|
||||||
|
programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of a such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. You must make sure that they, too, receive or can get the
|
||||||
|
source code. And you must tell them their rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License Agreement applies to any program or other work which
|
||||||
|
contains a notice placed by the copyright holder saying it may be
|
||||||
|
distributed under the terms of this General Public License. The
|
||||||
|
"Program", below, refers to any such program or work, and a "work based
|
||||||
|
on the Program" means either the Program or any work containing the
|
||||||
|
Program or a portion of it, either verbatim or with modifications. Each
|
||||||
|
licensee is addressed as "you".
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's source
|
||||||
|
code as you receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice and
|
||||||
|
disclaimer of warranty; keep intact all the notices that refer to this
|
||||||
|
General Public License and to the absence of any warranty; and give any
|
||||||
|
other recipients of the Program a copy of this General Public License
|
||||||
|
along with the Program. You may charge a fee for the physical act of
|
||||||
|
transferring a copy.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion of
|
||||||
|
it, and copy and distribute such modifications under the terms of Paragraph
|
||||||
|
1 above, provided that you also do the following:
|
||||||
|
|
||||||
|
a) cause the modified files to carry prominent notices stating that
|
||||||
|
you changed the files and the date of any change; and
|
||||||
|
|
||||||
|
b) cause the whole of any work that you distribute or publish, that
|
||||||
|
in whole or in part contains the Program or any part thereof, either
|
||||||
|
with or without modifications, to be licensed at no charge to all
|
||||||
|
third parties under the terms of this General Public License (except
|
||||||
|
that you may choose to grant warranty protection to some or all
|
||||||
|
third parties, at your option).
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively when
|
||||||
|
run, you must cause it, when started running for such interactive use
|
||||||
|
in the simplest and most usual way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a notice
|
||||||
|
that there is no warranty (or else, saying that you provide a
|
||||||
|
warranty) and that users may redistribute the program under these
|
||||||
|
conditions, and telling the user how to view a copy of this General
|
||||||
|
Public License.
|
||||||
|
|
||||||
|
d) You may charge a fee for the physical act of transferring a
|
||||||
|
copy, and you may at your option offer warranty protection in
|
||||||
|
exchange for a fee.
|
||||||
|
|
||||||
|
Mere aggregation of another independent work with the Program (or its
|
||||||
|
derivative) on a volume of a storage or distribution medium does not bring
|
||||||
|
the other work under the scope of these terms.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a portion or derivative of
|
||||||
|
it, under Paragraph 2) in object code or executable form under the terms of
|
||||||
|
Paragraphs 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) accompany it with the complete corresponding machine-readable
|
||||||
|
source code, which must be distributed under the terms of
|
||||||
|
Paragraphs 1 and 2 above; or,
|
||||||
|
|
||||||
|
b) accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party free (except for a nominal charge
|
||||||
|
for the cost of distribution) a complete machine-readable copy of the
|
||||||
|
corresponding source code, to be distributed under the terms of
|
||||||
|
Paragraphs 1 and 2 above; or,
|
||||||
|
|
||||||
|
c) accompany it with the information you received as to where the
|
||||||
|
corresponding source code may be obtained. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form alone.)
|
||||||
|
|
||||||
|
Source code for a work means the preferred form of the work for making
|
||||||
|
modifications to it. For an executable file, complete source code means
|
||||||
|
all the source code for all modules it contains; but, as a special
|
||||||
|
exception, it need not include source code for modules which are standard
|
||||||
|
libraries that accompany the operating system on which the executable
|
||||||
|
file runs, or for standard header files or definitions files that
|
||||||
|
accompany that operating system.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, distribute or transfer the
|
||||||
|
Program except as expressly provided under this General Public License.
|
||||||
|
Any attempt otherwise to copy, modify, sublicense, distribute or transfer
|
||||||
|
the Program is void, and will automatically terminate your rights to use
|
||||||
|
the Program under this License. However, parties who have received
|
||||||
|
copies, or rights to use copies, from you under this General Public
|
||||||
|
License will not have their licenses terminated so long as such parties
|
||||||
|
remain in full compliance.
|
||||||
|
|
||||||
|
5. By copying, distributing or modifying the Program (or any work based
|
||||||
|
on the Program) you indicate your acceptance of this license to do so,
|
||||||
|
and all its terms and conditions.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the original
|
||||||
|
licensor to copy, distribute or modify the Program subject to these
|
||||||
|
terms and conditions. You may not impose any further restrictions on the
|
||||||
|
recipients' exercise of the rights granted herein.
|
||||||
|
|
||||||
|
7. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program
|
||||||
|
specifies a version number of the license which applies to it and "any
|
||||||
|
later version", you have the option of following the terms and conditions
|
||||||
|
either of that version or of any later version published by the Free
|
||||||
|
Software Foundation. If the Program does not specify a version number of
|
||||||
|
the license, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
8. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
|
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||||
|
make exceptions for this. Our decision will be guided by the two goals
|
||||||
|
of preserving the free status of all derivatives of our free software and
|
||||||
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||||
|
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
|
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||||
|
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||||
|
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||||
|
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
|
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
Appendix: How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to humanity, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these
|
||||||
|
terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest to
|
||||||
|
attach them to the start of each source file to most effectively convey
|
||||||
|
the exclusion of warranty; and each file should have at least the
|
||||||
|
"copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) 19yy <name of author>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 1, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA
|
||||||
|
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this
|
||||||
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) 19xx name of author
|
||||||
|
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the
|
||||||
|
appropriate parts of the General Public License. Of course, the
|
||||||
|
commands you use may be called something other than `show w' and `show
|
||||||
|
c'; they could even be mouse-clicks or menu items--whatever suits your
|
||||||
|
program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
|
necessary. Here a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||||
|
program `Gnomovision' (a program to direct compilers to make passes
|
||||||
|
at assemblers) written by James Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1989
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
That's all there is to it!
|
||||||
|
|
||||||
|
|
||||||
|
--- The Artistic License 1.0 ---
|
||||||
|
|
||||||
|
This software is Copyright (c) 2021 by Paul Evans <leonerd@leonerd.org.uk>.
|
||||||
|
|
||||||
|
This is free software, licensed under:
|
||||||
|
|
||||||
|
The Artistic License 1.0
|
||||||
|
|
||||||
|
The Artistic License
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The intent of this document is to state the conditions under which a Package
|
||||||
|
may be copied, such that the Copyright Holder maintains some semblance of
|
||||||
|
artistic control over the development of the package, while giving the users of
|
||||||
|
the package the right to use and distribute the Package in a more-or-less
|
||||||
|
customary fashion, plus the right to make reasonable modifications.
|
||||||
|
|
||||||
|
Definitions:
|
||||||
|
|
||||||
|
- "Package" refers to the collection of files distributed by the Copyright
|
||||||
|
Holder, and derivatives of that collection of files created through
|
||||||
|
textual modification.
|
||||||
|
- "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
|
||||||
|
Holder.
|
||||||
|
- "Copyright Holder" is whoever is named in the copyright or copyrights for
|
||||||
|
the 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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
received it.
|
||||||
|
|
||||||
|
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
|
||||||
|
duplicate all of the original copyright notices and associated disclaimers.
|
||||||
|
|
||||||
|
2. You may apply bug fixes, portability fixes and other modifications derived
|
||||||
|
from the Public Domain or from the Copyright Holder. A Package modified in such
|
||||||
|
a way shall still be considered the Standard Version.
|
||||||
|
|
||||||
|
3. You may otherwise modify your copy of this Package in any way, provided that
|
||||||
|
you insert a prominent notice in each changed file stating how and when you
|
||||||
|
changed that file, and provided that you do at least ONE of the following:
|
||||||
|
|
||||||
|
a) place your modifications in the Public Domain or otherwise make them
|
||||||
|
Freely Available, such as by posting said modifications to Usenet or an
|
||||||
|
equivalent medium, or placing the modifications on a major archive site
|
||||||
|
such as ftp.uu.net, or by allowing the Copyright Holder to include your
|
||||||
|
modifications in the Standard Version of the Package.
|
||||||
|
|
||||||
|
b) use the modified Package only within your corporation or organization.
|
||||||
|
|
||||||
|
c) rename any non-standard executables so the names do not conflict with
|
||||||
|
standard executables, which must also be provided, and provide a separate
|
||||||
|
manual page for each non-standard executable that clearly documents how it
|
||||||
|
differs from the Standard Version.
|
||||||
|
|
||||||
|
d) make other distribution arrangements with the Copyright Holder.
|
||||||
|
|
||||||
|
4. You may distribute the programs of this Package in object code or executable
|
||||||
|
form, provided that you do at least ONE of the following:
|
||||||
|
|
||||||
|
a) distribute a Standard Version of the executables and library files,
|
||||||
|
together with instructions (in the manual page or equivalent) on where to
|
||||||
|
get the Standard Version.
|
||||||
|
|
||||||
|
b) accompany the distribution with the machine-readable source of the Package
|
||||||
|
with your modifications.
|
||||||
|
|
||||||
|
c) accompany any non-standard executables with their corresponding Standard
|
||||||
|
Version executables, giving the non-standard executables non-standard
|
||||||
|
names, and clearly documenting the differences in manual pages (or
|
||||||
|
equivalent), together with instructions on where to get the Standard
|
||||||
|
Version.
|
||||||
|
|
||||||
|
d) make other distribution arrangements with the Copyright Holder.
|
||||||
|
|
||||||
|
5. You may charge a reasonable copying fee for any distribution of this
|
||||||
|
Package. You may charge any fee you choose for support of this Package. You
|
||||||
|
may not charge a fee for this Package itself. However, you may distribute this
|
||||||
|
Package in aggregate with other (possibly commercial) programs as part of a
|
||||||
|
larger (possibly commercial) software distribution provided that you do not
|
||||||
|
advertise this Package as a product of your own.
|
||||||
|
|
||||||
|
6. The scripts and library files supplied as input to or produced as output
|
||||||
|
from the programs of this Package do not automatically fall under the copyright
|
||||||
|
of this Package, but belong to whomever generated them, and may be sold
|
||||||
|
commercially, and may be aggregated with this Package.
|
||||||
|
|
||||||
|
7. C or perl subroutines supplied by you and linked into this Package shall not
|
||||||
|
be considered part of this Package.
|
||||||
|
|
||||||
|
8. The name of the Copyright Holder may not be used to endorse or promote
|
||||||
|
products derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
|
||||||
|
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
|
||||||
|
The End
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
Build.PL
|
||||||
|
Changes
|
||||||
|
hax/block_end.c.inc
|
||||||
|
hax/block_start.c.inc
|
||||||
|
hax/COP_SEQ_RANGE_HIGH_set.c.inc
|
||||||
|
hax/lexer-additions.c.inc
|
||||||
|
hax/make_argcheck_aux.c.inc
|
||||||
|
hax/pad_block_start.c.inc
|
||||||
|
hax/pad_leavemy.c.inc
|
||||||
|
hax/parse_subsignature.c.inc
|
||||||
|
hax/scalarseq.c.inc
|
||||||
|
hax/sv_setrv.c.inc
|
||||||
|
hax/wrap_keyword_plugin.c.inc
|
||||||
|
inc/Module/Build/with/XSTests.pm
|
||||||
|
lib/XS/Parse/Sublike.pm
|
||||||
|
lib/XS/Parse/Sublike.xs
|
||||||
|
lib/XS/Parse/Sublike/Builder.pm
|
||||||
|
lib/XS/Parse/Sublike/Builder_data.pm.PL
|
||||||
|
LICENSE
|
||||||
|
MANIFEST This list of files
|
||||||
|
META.json
|
||||||
|
META.yml
|
||||||
|
README
|
||||||
|
t/00use.t
|
||||||
|
t/01func.t
|
||||||
|
t/02func-signatures.t
|
||||||
|
t/03func-deparse.t
|
||||||
|
t/10stages-permit.t
|
||||||
|
t/11stages-pre_subparse.t
|
||||||
|
t/12stages-post_blockstart.t
|
||||||
|
t/13stages-pre_blockend.t
|
||||||
|
t/14stages-post_newcv.t
|
||||||
|
t/15stages-filter_attr.t
|
||||||
|
t/20registrations.t
|
||||||
|
t/30any.t
|
||||||
|
t/31prefix.t
|
||||||
|
t/40parts-name.t
|
||||||
|
t/41parts-attrs.t
|
||||||
|
t/42parts-signature.t
|
||||||
|
t/50flags-no-body.t
|
||||||
|
t/60actions.t
|
||||||
|
t/99pod.t
|
||||||
|
t/actions.xs
|
||||||
|
t/any.xs
|
||||||
|
t/flags.xs
|
||||||
|
t/func.xs
|
||||||
|
t/parts.xs
|
||||||
|
t/prefix.xs
|
||||||
|
t/registrations.xs
|
||||||
|
t/stages.xs
|
||||||
|
t/testcase.pm
|
||||||
|
XSParseSublike.h
|
|
@ -0,0 +1,58 @@
|
||||||
|
{
|
||||||
|
"abstract" : "XS functions to assist in parsing C<sub>-like syntax",
|
||||||
|
"author" : [
|
||||||
|
"Paul Evans <leonerd@leonerd.org.uk>"
|
||||||
|
],
|
||||||
|
"dynamic_config" : 1,
|
||||||
|
"generated_by" : "Module::Build version 0.4231",
|
||||||
|
"license" : [
|
||||||
|
"perl_5"
|
||||||
|
],
|
||||||
|
"meta-spec" : {
|
||||||
|
"url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
|
||||||
|
"version" : 2
|
||||||
|
},
|
||||||
|
"name" : "XS-Parse-Sublike",
|
||||||
|
"prereqs" : {
|
||||||
|
"build" : {
|
||||||
|
"requires" : {
|
||||||
|
"ExtUtils::CBuilder" : "0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"configure" : {
|
||||||
|
"requires" : {
|
||||||
|
"Module::Build" : "0.4004"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtime" : {
|
||||||
|
"requires" : {
|
||||||
|
"perl" : "5.016"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test" : {
|
||||||
|
"requires" : {
|
||||||
|
"Sub::Util" : "0",
|
||||||
|
"Test::Fatal" : "0",
|
||||||
|
"Test::More" : "0.88"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"provides" : {
|
||||||
|
"XS::Parse::Sublike" : {
|
||||||
|
"file" : "lib/XS/Parse/Sublike.pm",
|
||||||
|
"version" : "0.16"
|
||||||
|
},
|
||||||
|
"XS::Parse::Sublike::Builder" : {
|
||||||
|
"file" : "lib/XS/Parse/Sublike/Builder.pm",
|
||||||
|
"version" : "0.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"release_status" : "stable",
|
||||||
|
"resources" : {
|
||||||
|
"license" : [
|
||||||
|
"http://dev.perl.org/licenses/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"version" : "0.16",
|
||||||
|
"x_serialization_backend" : "JSON::PP version 4.05"
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
abstract: 'XS functions to assist in parsing C<sub>-like syntax'
|
||||||
|
author:
|
||||||
|
- 'Paul Evans <leonerd@leonerd.org.uk>'
|
||||||
|
build_requires:
|
||||||
|
ExtUtils::CBuilder: '0'
|
||||||
|
Sub::Util: '0'
|
||||||
|
Test::Fatal: '0'
|
||||||
|
Test::More: '0.88'
|
||||||
|
configure_requires:
|
||||||
|
Module::Build: '0.4004'
|
||||||
|
dynamic_config: 1
|
||||||
|
generated_by: 'Module::Build version 0.4231, CPAN::Meta::Converter version 2.150010'
|
||||||
|
license: perl
|
||||||
|
meta-spec:
|
||||||
|
url: http://module-build.sourceforge.net/META-spec-v1.4.html
|
||||||
|
version: '1.4'
|
||||||
|
name: XS-Parse-Sublike
|
||||||
|
provides:
|
||||||
|
XS::Parse::Sublike:
|
||||||
|
file: lib/XS/Parse/Sublike.pm
|
||||||
|
version: '0.16'
|
||||||
|
XS::Parse::Sublike::Builder:
|
||||||
|
file: lib/XS/Parse/Sublike/Builder.pm
|
||||||
|
version: '0.16'
|
||||||
|
requires:
|
||||||
|
perl: '5.016'
|
||||||
|
resources:
|
||||||
|
license: http://dev.perl.org/licenses/
|
||||||
|
version: '0.16'
|
||||||
|
x_serialization_backend: 'CPAN::Meta::YAML version 0.018'
|
|
@ -0,0 +1,345 @@
|
||||||
|
NAME
|
||||||
|
|
||||||
|
XS::Parse::Sublike - XS functions to assist in parsing sub-like syntax
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
|
||||||
|
This module provides some XS functions to assist in writing parsers for
|
||||||
|
sub-like syntax, primarily for authors of keyword plugins using the
|
||||||
|
PL_keyword_plugin hook mechanism. It is unlikely to be of much use to
|
||||||
|
anyone else; and highly unlikely to be any use when writing perl code
|
||||||
|
using these. Unless you are writing a keyword plugin using XS, this
|
||||||
|
module is not for you.
|
||||||
|
|
||||||
|
This module is also currently experimental, and the design is still
|
||||||
|
evolving and subject to change. Later versions may break ABI
|
||||||
|
compatibility, requiring changes or at least a rebuild of any module
|
||||||
|
that depends on it.
|
||||||
|
|
||||||
|
XS FUNCTIONS
|
||||||
|
|
||||||
|
boot_xs_parse_sublike
|
||||||
|
|
||||||
|
void boot_xs_parse_sublike(double ver)
|
||||||
|
|
||||||
|
Call this function from your BOOT section in order to initialise the
|
||||||
|
module and parsing hooks.
|
||||||
|
|
||||||
|
ver should either be 0 or a decimal number for the module version
|
||||||
|
requirement; e.g.
|
||||||
|
|
||||||
|
boot_xs_parse_sublike(0.04);
|
||||||
|
|
||||||
|
xs_parse_sublike
|
||||||
|
|
||||||
|
int xs_parse_sublike(const struct XSParseSublikeHooks *hooks, void *hookdata, OP **op_ptr)
|
||||||
|
|
||||||
|
This function performs the actual parsing of a sub-like keyword. It
|
||||||
|
expects the lexer to be at a position just after the introduction
|
||||||
|
keyword has been consumed, and will proceed to parse an optional name,
|
||||||
|
list of attributes, signature (if enabled by use feature 'signatures'),
|
||||||
|
and code body. The return value and op_ptr can be used directly from
|
||||||
|
the keyword plugin function. It is intended this function be invoked
|
||||||
|
from it, and the result returned directly.
|
||||||
|
|
||||||
|
For a more automated handling of keywords, see
|
||||||
|
"register_xs_parse_sublike".
|
||||||
|
|
||||||
|
hooks should be a structure that can provide optional function pointers
|
||||||
|
used to customise the parsing process at various stages. hookdata is an
|
||||||
|
opaque pointer which is passed through to each of the hook stage
|
||||||
|
functions.
|
||||||
|
|
||||||
|
register_xs_parse_sublike
|
||||||
|
|
||||||
|
void register_xs_parse_sublike(const char *keyword,
|
||||||
|
const struct XSParseSublikeHooks *hooks, void *hookdata)
|
||||||
|
|
||||||
|
This function installs a set of parsing hooks to be associated with the
|
||||||
|
given keyword. Such a keyword will then be handled automatically by a
|
||||||
|
keyword parser installed by XS::Parse::Sublike itself.
|
||||||
|
|
||||||
|
When the keyword is encountered, the hook's permit function is first
|
||||||
|
tested to see if the keyword is permitted at this point. If the
|
||||||
|
function returns true then the keyword is consumed and parsed as per
|
||||||
|
"xs_parse_sublike".
|
||||||
|
|
||||||
|
hookdata is an opaque pointer which is passed through to each of the
|
||||||
|
hook stage functions when they are invoked.
|
||||||
|
|
||||||
|
xs_parse_sublike_any
|
||||||
|
|
||||||
|
int xs_parse_sublike_any(const struct XSParseSublikeHooks *hooks, void *hookdata,
|
||||||
|
OP **op_ptr)
|
||||||
|
|
||||||
|
This function expects to consume an introduction keyword at the lexer
|
||||||
|
position which is either sub or the name of another sub-like keyword,
|
||||||
|
which has been previously registered using "register_xs_parse_sublike".
|
||||||
|
It then proceeds to parse the subsequent syntax similar to how it would
|
||||||
|
have parsed if encountered by the module's own keyword parser plugin,
|
||||||
|
except that the second set of hooks given here also take effect.
|
||||||
|
|
||||||
|
If a regular sub is encountered, then this is parsed using the hooks in
|
||||||
|
a similar way to xs_parse_sublike().
|
||||||
|
|
||||||
|
If a different registered sub-like keyword is encountered, then parsing
|
||||||
|
is performed using both sets of hooks - the ones given to this function
|
||||||
|
as well as the ones registered with the keyword. This allows their
|
||||||
|
effects to combined. The hooks given by the hooks argument are
|
||||||
|
considered to be on the "outside" from those of the registered keyword
|
||||||
|
"inside". The outside ones run first for all stages, except
|
||||||
|
pre_blockend which runs them inside-out.
|
||||||
|
|
||||||
|
hookdata is an opaque pointer which is passed through to each of the
|
||||||
|
hook stage functions when they are invoked.
|
||||||
|
|
||||||
|
Note that this function is now vaguely discouraged, in favour of using
|
||||||
|
a prefixing keyword instead, by using the XS_PARSE_SUBLIKE_FLAG_PREFIX
|
||||||
|
flag.
|
||||||
|
|
||||||
|
PARSE CONTEXT
|
||||||
|
|
||||||
|
The various hook stages all share state about the ongoing parse process
|
||||||
|
using various fields of the XSParseSublikeContext structure.
|
||||||
|
|
||||||
|
struct XSParseSublikeContext {
|
||||||
|
SV *name;
|
||||||
|
OP *attrs;
|
||||||
|
OP *body;
|
||||||
|
CV *cv;
|
||||||
|
U32 actions;
|
||||||
|
HV *moddata;
|
||||||
|
}
|
||||||
|
|
||||||
|
The actions field will contain a bitmask of action flags that control
|
||||||
|
the various steps that XS::Parse::Sublike might take inbetween invoking
|
||||||
|
hook stages. The initial value of this field is set after the
|
||||||
|
name-parsing stage, depending on whether or not a name is found. Stage
|
||||||
|
hook functions may modify the field to adjust the subsequent behaviour.
|
||||||
|
|
||||||
|
At the current ABI version, a module will have to set the
|
||||||
|
XS_PARSE_SUBLIKE_COMPAT_FLAG_DYNAMIC_ACTIONS bit of the flags field in
|
||||||
|
order to make use of the actions field. A future ABI version may remove
|
||||||
|
this restriction.
|
||||||
|
|
||||||
|
XS_PARSE_SUBLIKE_ACTION_CVf_ANON
|
||||||
|
|
||||||
|
If set, the start_subparse() call will be set up for an anonymous
|
||||||
|
function protosub; if not it will be set for a named function. This
|
||||||
|
is set by default if a name was not found.
|
||||||
|
|
||||||
|
XS_PARSE_SUBLIKE_ACTION_SET_CVNAME
|
||||||
|
|
||||||
|
If set, the newly-constructed CV will have the given name set on it.
|
||||||
|
This is set by default if a name was found.
|
||||||
|
|
||||||
|
On Perl versions 5.22 and above, this flag can be set even if
|
||||||
|
XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL is not. In this case, the CV
|
||||||
|
will not be reachable via the symbol table, even though it knows its
|
||||||
|
own name and pretends that it is. On earlier versions of perl this
|
||||||
|
flag will be ignored in that case.
|
||||||
|
|
||||||
|
XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL
|
||||||
|
|
||||||
|
If set, the newly-constructed CV will be installed into the symbol
|
||||||
|
table at its given name. Note that it is not possible to enable this
|
||||||
|
flag without also enabling XS_PARSE_SUBLIKE_ACTION_SET_CVNAME. This
|
||||||
|
is set by default if a name was found.
|
||||||
|
|
||||||
|
XS_PARSE_SUBLIKE_ACTION_REFGEN_ANONCODE
|
||||||
|
|
||||||
|
If set, the syntax will yield the OP_REFGEN / OP_ANONCODE optree
|
||||||
|
fragment typical of anonymous code expressions; if not it will be
|
||||||
|
OP_NULL. This is set by default if a name was not found.
|
||||||
|
|
||||||
|
XS_PARSE_SUBLIKE_ACTION_RET_EXPR
|
||||||
|
|
||||||
|
If set, the syntax will parse like an expression; if not it will
|
||||||
|
parse like a statement. This is set by default if a name was not
|
||||||
|
found.
|
||||||
|
|
||||||
|
The moddata field will point towards an HV that modules can used to
|
||||||
|
store extra data between stages. As a naming convention a module should
|
||||||
|
prefix its keys with its own module name and a slash character,
|
||||||
|
"Some::Module/field". The field will point to a newly-created HV for
|
||||||
|
every parse invocation, and will be released when each parse is
|
||||||
|
complete.
|
||||||
|
|
||||||
|
PARSE HOOKS
|
||||||
|
|
||||||
|
The XSParseSublikeHooks structure provides the following hook stages,
|
||||||
|
which are invoked in the given order.
|
||||||
|
|
||||||
|
The structure has a flags field, which controls various optional parts
|
||||||
|
of operation. The following flags are defined.
|
||||||
|
|
||||||
|
XS_PARSE_SUBLIKE_FLAG_FILTERATTRS
|
||||||
|
|
||||||
|
If set, the optional filter_attr stage will be invoked.
|
||||||
|
|
||||||
|
XS_PARSE_SUBLIKE_FLAG_BODY_OPTIONAL
|
||||||
|
|
||||||
|
If not set, the require_parts field will imply the
|
||||||
|
XS_PARSE_SUBLIKE_PART_BODY flag, making the body part required. By
|
||||||
|
setting this flag this will no longer happen. If all hooks agree,
|
||||||
|
then the body will become optional.
|
||||||
|
|
||||||
|
XS_PARSE_SUBLIKE_FLAG_PREFIX
|
||||||
|
|
||||||
|
If set, the keyword is considered to be a prefix that can be placed
|
||||||
|
in front of sub or another sub-like keyword, to add its set of hooks
|
||||||
|
in addition to those of the following keyword. These prefices may be
|
||||||
|
further stacked.
|
||||||
|
|
||||||
|
In addition there are two U8 fields named require_parts and skip_parts
|
||||||
|
which control the behaviour of various parts of the syntax which are
|
||||||
|
usually optional. Any parts with bits set in require_parts become
|
||||||
|
non-optional, and an error if they are missing. Any parts with bits set
|
||||||
|
in skip_parts will skip the relevant part of the parsing process.
|
||||||
|
|
||||||
|
When multiple sets of hooks are combined by the xs_parse_sublike_any
|
||||||
|
function, or as part of parsing prefixing keywords, these bitmasks are
|
||||||
|
accumulated together with inclusive or. Any part required by any set of
|
||||||
|
hooks will still be required; any step skipped by either will be
|
||||||
|
skipped entirely.
|
||||||
|
|
||||||
|
If the same bit is set in both fields then the relevant parsing step
|
||||||
|
will not be performed but it will still be an error for that section to
|
||||||
|
be missing. This is likely not useful.
|
||||||
|
|
||||||
|
Note that for skipped parts, only the actual parsing steps are skipped.
|
||||||
|
A hook function can still set the relevant fields in the context
|
||||||
|
structure anyway to force a particular value for those parts.
|
||||||
|
|
||||||
|
XS_PARSE_SUBLIKE_PART_NAME
|
||||||
|
|
||||||
|
The name of the function.
|
||||||
|
|
||||||
|
XS_PARSE_SUBLIKE_PART_ATTRS
|
||||||
|
|
||||||
|
The attributes of the function.
|
||||||
|
|
||||||
|
This part can be skipped, but the bit is ignored when in
|
||||||
|
require_parts. It is always permitted to not provide any additional
|
||||||
|
attributes to a function definition.
|
||||||
|
|
||||||
|
XS_PARSE_SUBLIKE_PART_SIGNATURE
|
||||||
|
|
||||||
|
The parameter signature of the function.
|
||||||
|
|
||||||
|
This part can be skipped, but the bit is ignored when in
|
||||||
|
require_parts. It is always permitted not to provide a signature for
|
||||||
|
a function definition, because such syntax only applies when use
|
||||||
|
feature 'signatures' is in effect, and only on supporting perl
|
||||||
|
versions.
|
||||||
|
|
||||||
|
XS_PARSE_SUBLIKE_PART_BODY
|
||||||
|
|
||||||
|
The actual body of the function, expressed as a brace-delimited
|
||||||
|
block.
|
||||||
|
|
||||||
|
This part cannot be skipped, but it can be made optional by omitting
|
||||||
|
it from the require_parts field. Instead of the block, it is
|
||||||
|
permitted to place a single semicolon (;) to act as a statement
|
||||||
|
terminator; thus giving the same syntax as a subroutine forward
|
||||||
|
declaration.
|
||||||
|
|
||||||
|
In this case, the body and cv fields of the context structure will
|
||||||
|
remain NULL.
|
||||||
|
|
||||||
|
This flag is currently implied on the require_parts field if the hook
|
||||||
|
does not supply the XS_PARSE_SUBLIKE_FLAG_BODY_OPTIONAL flag; meaning
|
||||||
|
that most use-cases will make it a required part.
|
||||||
|
|
||||||
|
The permit Stage
|
||||||
|
|
||||||
|
const char *permit_hintkey
|
||||||
|
bool (*permit)(pTHX_ void *hookdata)
|
||||||
|
|
||||||
|
Called by the installed keyword parser hook which is used to handle
|
||||||
|
keywords registered by "register_xs_parse_sublike".
|
||||||
|
|
||||||
|
As a shortcut for the common case, the permit_hintkey may point to a
|
||||||
|
string to look up from the hints hash. If the given key name is not
|
||||||
|
found in the hints hash then the keyword is not permitted. If the key
|
||||||
|
is present then the permit function is invoked as normal.
|
||||||
|
|
||||||
|
If not rejected by a hint key that was not found in the hints hash, the
|
||||||
|
function part of the stage is called next and should inspect whether
|
||||||
|
the keyword is permitted at this time perhaps by inspecting other
|
||||||
|
lexical clues, and return true only if the keyword is permitted.
|
||||||
|
|
||||||
|
Both the string and the function are optional. Either or both may be
|
||||||
|
present. If neither is present then the keyword is always permitted -
|
||||||
|
which is likely not what you wanted to do.
|
||||||
|
|
||||||
|
Parse Name
|
||||||
|
|
||||||
|
At this point, the optional name is parsed and filled into the name
|
||||||
|
field of the context.
|
||||||
|
|
||||||
|
The pre_subparse Stage
|
||||||
|
|
||||||
|
void (*pre_subparse)(pTHX_ struct XSParseSublikeContext *ctx, void *hookdata)
|
||||||
|
|
||||||
|
Invoked just before start_subparse() is called.
|
||||||
|
|
||||||
|
Parse Attrs
|
||||||
|
|
||||||
|
At this point the optional sub attributes are parsed and filled into
|
||||||
|
the attrs field of the context, then block_start() is called.
|
||||||
|
|
||||||
|
The optional filter_attr Stage
|
||||||
|
|
||||||
|
bool (*filter_attr)(pTHX_ struct XSParseSublikeContext *ctx,
|
||||||
|
SV *attr, SV *val, void *hookdata);
|
||||||
|
|
||||||
|
If the flags field includes XS_PARSE_SUBLIKE_FLAG_FILTERATTRS then each
|
||||||
|
individual attribute is passed through this optional filter function
|
||||||
|
immediately as each is parsed. attr will be a string SV containing the
|
||||||
|
name of the attribute, and val will either be NULL, or a string SV
|
||||||
|
containing the contents of the parens after its name (without the
|
||||||
|
parens themselves).
|
||||||
|
|
||||||
|
If the filter returns true, it indicates that it has in some way
|
||||||
|
handled the attribute and it should not be added to the list given to
|
||||||
|
newATTRSUB(). If the filter returns false it will be handled in the
|
||||||
|
usual way; equivalent to the case where the filter function did not
|
||||||
|
exist.
|
||||||
|
|
||||||
|
The post_blockstart Stage
|
||||||
|
|
||||||
|
void (*post_blockstart)(pTHX_ struct XSParseSublikeContext *ctx, void *hookdata)
|
||||||
|
|
||||||
|
Invoked after the block_start() function has been called. This hook
|
||||||
|
stage may wish to perform any alterations of PL_compcv or related,
|
||||||
|
inspect or alter the lexical pad, provide hints hash values, or any
|
||||||
|
other tasks before the signature and code body are parsed.
|
||||||
|
|
||||||
|
Parse Body
|
||||||
|
|
||||||
|
At this point, the main body of the function is parsed and the optree
|
||||||
|
is stored in the body field of the context. If the perl version
|
||||||
|
supports sub signatures and they are enabled and found, the body will
|
||||||
|
be prefixed with the signature ops as well.
|
||||||
|
|
||||||
|
The pre_blockend Stage
|
||||||
|
|
||||||
|
void (*pre_blockend)(pTHX_ struct XSParseSublikeContext *ctx, void *hookdata)
|
||||||
|
|
||||||
|
Invoked just before the block_end() function is invoked. The hook stage
|
||||||
|
may wish to inspect or alter the optree stored in the body context
|
||||||
|
field.
|
||||||
|
|
||||||
|
The post_newcv Stage
|
||||||
|
|
||||||
|
void (*post_newcv)(pTHX_ struct XSParseSublikeContext *ctx, void *hookdata)
|
||||||
|
|
||||||
|
Invoked just after newATTRSUB() has been invoked on the optree. The
|
||||||
|
hook stage may wish to inspect or alter the CV stored in the cv context
|
||||||
|
field.
|
||||||
|
|
||||||
|
AUTHOR
|
||||||
|
|
||||||
|
Paul Evans <leonerd@leonerd.org.uk>
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
#ifndef __XS_PARSE_SUBLIKE_H__
|
||||||
|
#define __XS_PARSE_SUBLIKE_H__
|
||||||
|
|
||||||
|
#define XSPARSESUBLIKE_ABI_VERSION 4
|
||||||
|
|
||||||
|
struct XSParseSublikeContext {
|
||||||
|
SV *name; /* may be NULL for anon subs */
|
||||||
|
/* STAGE pre_subparse */
|
||||||
|
OP *attrs; /* may be NULL */
|
||||||
|
/* STAGE post_blockstart */
|
||||||
|
OP *body;
|
||||||
|
/* STAGE pre_blockend */
|
||||||
|
CV *cv;
|
||||||
|
/* STAGE post_newcv */
|
||||||
|
|
||||||
|
U32 actions;
|
||||||
|
|
||||||
|
/* Unused by XS::Parse::Sublike itself but can be handy for modules to store
|
||||||
|
* data in between stages */
|
||||||
|
HV *moddata;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
XS_PARSE_SUBLIKE_FLAG_FILTERATTRS = 1<<0,
|
||||||
|
XS_PARSE_SUBLIKE_FLAG_BODY_OPTIONAL = 1<<1,
|
||||||
|
XS_PARSE_SUBLIKE_FLAG_PREFIX = 1<<2,
|
||||||
|
|
||||||
|
/* Back-compat flags we hope to remove in the next ABI version */
|
||||||
|
XS_PARSE_SUBLIKE_COMPAT_FLAG_DYNAMIC_ACTIONS = 1<<15,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
XS_PARSE_SUBLIKE_PART_NAME = 1<<0,
|
||||||
|
XS_PARSE_SUBLIKE_PART_ATTRS = 1<<1,
|
||||||
|
XS_PARSE_SUBLIKE_PART_SIGNATURE = 1<<2,
|
||||||
|
XS_PARSE_SUBLIKE_PART_BODY = 1<<3,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
XS_PARSE_SUBLIKE_ACTION_CVf_ANON = (1<<0), /* should start_subparse() take CVf_ANON ? */
|
||||||
|
XS_PARSE_SUBLIKE_ACTION_SET_CVNAME = (1<<1), /* do we set a CvNAME? */
|
||||||
|
XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL = (1<<2), /* do we install the new CV into the symbol table? */
|
||||||
|
XS_PARSE_SUBLIKE_ACTION_REFGEN_ANONCODE = (1<<3), /* do we emit OP_REFGEN of OP_ANONCODE, or simply OP_NULL ? */
|
||||||
|
XS_PARSE_SUBLIKE_ACTION_RET_EXPR = (1<<4), /* do we return KEYWORD_PLUGIN_EXPR, or KEYWORD_PLUGIN_STMT ? */
|
||||||
|
};
|
||||||
|
|
||||||
|
struct XSParseSublikeHooks {
|
||||||
|
U16 flags;
|
||||||
|
U8 require_parts;
|
||||||
|
U8 skip_parts;
|
||||||
|
|
||||||
|
/* These two hooks are ANDed together; both must pass, if present */
|
||||||
|
const char *permit_hintkey;
|
||||||
|
bool (*permit)(pTHX_ void *hookdata);
|
||||||
|
|
||||||
|
void (*pre_subparse) (pTHX_ struct XSParseSublikeContext *ctx, void *hookdata);
|
||||||
|
void (*post_blockstart)(pTHX_ struct XSParseSublikeContext *ctx, void *hookdata);
|
||||||
|
void (*pre_blockend) (pTHX_ struct XSParseSublikeContext *ctx, void *hookdata);
|
||||||
|
void (*post_newcv) (pTHX_ struct XSParseSublikeContext *ctx, void *hookdata);
|
||||||
|
|
||||||
|
/* if flags & XS_PARSE_SUBLIKE_FLAG_FILTERATTRS */
|
||||||
|
bool (*filter_attr) (pTHX_ struct XSParseSublikeContext *ctx, SV *attr, SV *val, void *hookdata);
|
||||||
|
};
|
||||||
|
|
||||||
|
static int (*parse_xs_parse_sublike_func)(pTHX_ const struct XSParseSublikeHooks *hooks, void *hookdata, OP **op_ptr);
|
||||||
|
#define xs_parse_sublike(hooks, hookdata, op_ptr) S_xs_parse_sublike(aTHX_ hooks, hookdata, op_ptr)
|
||||||
|
static int S_xs_parse_sublike(pTHX_ const struct XSParseSublikeHooks *hooks, void *hookdata, OP **op_ptr)
|
||||||
|
{
|
||||||
|
if(!parse_xs_parse_sublike_func)
|
||||||
|
croak("Must call boot_xs_parse_sublike() first");
|
||||||
|
|
||||||
|
return (*parse_xs_parse_sublike_func)(aTHX_ hooks, hookdata, op_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void (*register_xs_parse_sublike_func)(pTHX_ const char *kw, const struct XSParseSublikeHooks *hooks, void *hookdata);
|
||||||
|
#define register_xs_parse_sublike(kw, hooks, hookdata) S_register_xs_parse_sublike(aTHX_ kw, hooks, hookdata)
|
||||||
|
static void S_register_xs_parse_sublike(pTHX_ const char *kw, const struct XSParseSublikeHooks *hooks, void *hookdata)
|
||||||
|
{
|
||||||
|
if(!register_xs_parse_sublike_func)
|
||||||
|
croak("Must call boot_xs_parse_sublike() first");
|
||||||
|
|
||||||
|
return (*register_xs_parse_sublike_func)(aTHX_ kw, hooks, hookdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int (*parseany_xs_parse_sublike_func)(pTHX_ const struct XSParseSublikeHooks *hooks, void *hookdata, OP **op_ptr);
|
||||||
|
#define xs_parse_sublike_any(hooks, hookdata, op_ptr) S_xs_parse_sublike_any(aTHX_ hooks, hookdata, op_ptr)
|
||||||
|
static int S_xs_parse_sublike_any(pTHX_ const struct XSParseSublikeHooks *hooks, void *hookdata, OP **op_ptr)
|
||||||
|
{
|
||||||
|
if(!parseany_xs_parse_sublike_func)
|
||||||
|
croak("Must call boot_xs_parse_sublike() first");
|
||||||
|
|
||||||
|
return (*parseany_xs_parse_sublike_func)(aTHX_ hooks, hookdata, op_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define boot_xs_parse_sublike(ver) S_boot_xs_parse_sublike(aTHX_ ver)
|
||||||
|
static void S_boot_xs_parse_sublike(pTHX_ double ver) {
|
||||||
|
SV **svp;
|
||||||
|
SV *versv = ver ? newSVnv(ver) : NULL;
|
||||||
|
|
||||||
|
load_module(PERL_LOADMOD_NOIMPORT, newSVpvs("XS::Parse::Sublike"), versv, NULL);
|
||||||
|
|
||||||
|
svp = hv_fetchs(PL_modglobal, "XS::Parse::Sublike/ABIVERSION_MIN", 0);
|
||||||
|
if(!svp)
|
||||||
|
croak("XS::Parse::Sublike ABI minimum version missing");
|
||||||
|
int abi_ver = SvIV(*svp);
|
||||||
|
if(abi_ver > XSPARSESUBLIKE_ABI_VERSION)
|
||||||
|
croak("XS::Parse::Sublike ABI version mismatch - library supports >= %d, compiled for %d",
|
||||||
|
abi_ver, XSPARSESUBLIKE_ABI_VERSION);
|
||||||
|
|
||||||
|
svp = hv_fetchs(PL_modglobal, "XS::Parse::Sublike/ABIVERSION_MAX", 0);
|
||||||
|
abi_ver = SvIV(*svp);
|
||||||
|
if(abi_ver < XSPARSESUBLIKE_ABI_VERSION)
|
||||||
|
croak("XS::Parse::Sublike ABI version mismatch - library supports <= %d, compiled for %d",
|
||||||
|
abi_ver, XSPARSESUBLIKE_ABI_VERSION);
|
||||||
|
|
||||||
|
parse_xs_parse_sublike_func = INT2PTR(int (*)(pTHX_ const struct XSParseSublikeHooks *, void *, OP**),
|
||||||
|
SvUV(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/parse()@4", 0)));
|
||||||
|
|
||||||
|
register_xs_parse_sublike_func = INT2PTR(void (*)(pTHX_ const char *, const struct XSParseSublikeHooks *, void *),
|
||||||
|
SvUV(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/register()@4", 0)));
|
||||||
|
|
||||||
|
parseany_xs_parse_sublike_func = INT2PTR(int (*)(pTHX_ const struct XSParseSublikeHooks *, void *, OP**),
|
||||||
|
SvUV(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/parseany()@4", 0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,8 @@
|
||||||
|
/* vi: set ft=c : */
|
||||||
|
|
||||||
|
#ifndef COP_SEQ_RANGE_HIGH_set
|
||||||
|
|
||||||
|
#define COP_SEQ_RANGE_HIGH_set(SV, VAL) \
|
||||||
|
STMT_START { ((XPVNV*)SvANY(SV))->xnv_u.xpad_cop_seq.xhigh = (VAL); } STMT_END
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,45 @@
|
||||||
|
/* vi: set ft=c : */
|
||||||
|
|
||||||
|
#ifndef block_end
|
||||||
|
|
||||||
|
#include "scalarseq.c.inc"
|
||||||
|
#include "pad_leavemy.c.inc"
|
||||||
|
|
||||||
|
#define block_end(A, B) S_block_end(aTHX_ A, B)
|
||||||
|
|
||||||
|
static OP *S_block_end(pTHX_ I32 floor, OP *seq) {
|
||||||
|
dVAR;
|
||||||
|
const int needblockscope = PL_hints & HINT_BLOCK_SCOPE;
|
||||||
|
OP *retval = scalarseq(seq);
|
||||||
|
OP *o;
|
||||||
|
|
||||||
|
CALL_BLOCK_HOOKS(bhk_pre_end, &retval);
|
||||||
|
|
||||||
|
LEAVE_SCOPE(floor);
|
||||||
|
#if !HAVE_PERL_VERSION(5, 19, 3)
|
||||||
|
CopHINTS_set(&PL_compiling, PL_hints);
|
||||||
|
#endif
|
||||||
|
if (needblockscope)
|
||||||
|
PL_hints |= HINT_BLOCK_SCOPE; /* propagate out */
|
||||||
|
|
||||||
|
o = pad_leavemy();
|
||||||
|
if (o) {
|
||||||
|
#if HAVE_PERL_VERSION(5, 17, 4)
|
||||||
|
OP *kid = o->op_flags & OPf_KIDS ? cLISTOPo->op_first : o;
|
||||||
|
OP *const last = o->op_flags & OPf_KIDS ? cLISTOPo->op_last : o;
|
||||||
|
for (;; kid = kid->op_sibling) {
|
||||||
|
OP *newkid = newOP(OP_CLONECV, 0);
|
||||||
|
newkid->op_targ = kid->op_targ;
|
||||||
|
o = op_append_elem(OP_LINESEQ, o, newkid);
|
||||||
|
if (kid == last) break;
|
||||||
|
}
|
||||||
|
retval = op_prepend_elem(OP_LINESEQ, o, retval);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
CALL_BLOCK_HOOKS(bhk_post_end, &retval);
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,25 @@
|
||||||
|
/* vi: set ft=c : */
|
||||||
|
|
||||||
|
#ifndef block_start
|
||||||
|
|
||||||
|
#include "pad_block_start.c.inc"
|
||||||
|
|
||||||
|
#define block_start(A) S_block_start(aTHX_ A)
|
||||||
|
|
||||||
|
static int S_block_start(pTHX_ int full) {
|
||||||
|
dVAR;
|
||||||
|
const int retval = PL_savestack_ix;
|
||||||
|
|
||||||
|
pad_block_start(full);
|
||||||
|
SAVEHINTS();
|
||||||
|
PL_hints &= ~HINT_BLOCK_SCOPE;
|
||||||
|
SAVECOMPILEWARNINGS();
|
||||||
|
PL_compiling.cop_warnings = DUP_WARNINGS(PL_compiling.cop_warnings);
|
||||||
|
|
||||||
|
CALL_BLOCK_HOOKS(bhk_start, full);
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,298 @@
|
||||||
|
/* vi: set ft=c : */
|
||||||
|
|
||||||
|
/* Perls before 5.18 lack isIDCONT_uni, but baring minor differences of weird
|
||||||
|
* Unicode characters, isALNUM_uni is close enough
|
||||||
|
*/
|
||||||
|
#ifndef isIDCONT_uni
|
||||||
|
#define isIDCONT_uni(c) isALNUM_uni(c)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define sv_cat_c(sv, c) MY_sv_cat_c(aTHX_ sv, c)
|
||||||
|
static void MY_sv_cat_c(pTHX_ SV *sv, U32 c)
|
||||||
|
{
|
||||||
|
char ds[UTF8_MAXBYTES + 1], *d;
|
||||||
|
d = (char *)uvchr_to_utf8((U8 *)ds, c);
|
||||||
|
if (d - ds > 1) {
|
||||||
|
sv_utf8_upgrade(sv);
|
||||||
|
}
|
||||||
|
sv_catpvn(sv, ds, d - ds);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define lex_consume(s) MY_lex_consume(aTHX_ s)
|
||||||
|
static int MY_lex_consume(pTHX_ char *s)
|
||||||
|
{
|
||||||
|
/* I want strprefix() */
|
||||||
|
size_t i;
|
||||||
|
for(i = 0; s[i]; i++) {
|
||||||
|
if(s[i] != PL_parser->bufptr[i])
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
lex_read_to(PL_parser->bufptr + i);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum {
|
||||||
|
LEX_IDENT_PACKAGENAME = (1<<0),
|
||||||
|
};
|
||||||
|
|
||||||
|
#define lex_scan_ident( ) MY_lex_scan_ident(aTHX_ 0)
|
||||||
|
#define lex_scan_packagename() MY_lex_scan_ident(aTHX_ LEX_IDENT_PACKAGENAME)
|
||||||
|
static SV *MY_lex_scan_ident(pTHX_ int flags)
|
||||||
|
{
|
||||||
|
I32 c;
|
||||||
|
bool at_start = TRUE;
|
||||||
|
|
||||||
|
char *ident = PL_parser->bufptr;
|
||||||
|
|
||||||
|
while((c = lex_peek_unichar(0))) {
|
||||||
|
if(at_start ? isIDFIRST_uni(c) : isALNUM_uni(c))
|
||||||
|
at_start = FALSE;
|
||||||
|
/* TODO: This sucks in the case of a false Foo:Bar match */
|
||||||
|
else if((flags & LEX_IDENT_PACKAGENAME) && (c == ':')) {
|
||||||
|
lex_read_unichar(0);
|
||||||
|
if(lex_read_unichar(0) != ':')
|
||||||
|
croak("Expected colon to be followed by another in package name");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
|
||||||
|
lex_read_unichar(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
STRLEN len = PL_parser->bufptr - ident;
|
||||||
|
if(!len)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
SV *ret = newSVpvn(ident, len);
|
||||||
|
if(lex_bufutf8())
|
||||||
|
SvUTF8_on(ret);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define lex_scan_attrval_into(name, val) MY_lex_scan_attrval_into(aTHX_ name, val)
|
||||||
|
static bool MY_lex_scan_attrval_into(pTHX_ SV *name, SV *val)
|
||||||
|
{
|
||||||
|
/* TODO: really want lex_scan_ident_into() */
|
||||||
|
SV *n = lex_scan_ident();
|
||||||
|
if(!n)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
sv_setsv(name, n);
|
||||||
|
SvREFCNT_dec(n);
|
||||||
|
|
||||||
|
if(name != val)
|
||||||
|
SvPOK_off(val);
|
||||||
|
|
||||||
|
/* Do not read space here as space is not allowed between NAME(ARGS) */
|
||||||
|
|
||||||
|
if(lex_peek_unichar(0) != '(')
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
lex_read_unichar(0);
|
||||||
|
if(name == val)
|
||||||
|
sv_cat_c(val, '(');
|
||||||
|
else
|
||||||
|
sv_setpvs(val, "");
|
||||||
|
|
||||||
|
int count = 1;
|
||||||
|
I32 c = lex_peek_unichar(0);
|
||||||
|
while(count && c != -1) {
|
||||||
|
if(c == '(')
|
||||||
|
count++;
|
||||||
|
if(c == ')')
|
||||||
|
count--;
|
||||||
|
if(c == '\\') {
|
||||||
|
/* The next char does not bump count even if it is ( or );
|
||||||
|
* the \\ is still captured
|
||||||
|
*/
|
||||||
|
sv_cat_c(val, lex_read_unichar(0));
|
||||||
|
c = lex_peek_unichar(0);
|
||||||
|
if(c == -1)
|
||||||
|
goto unterminated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Don't append final closing ')' on split name/val */
|
||||||
|
if(count || (name == val))
|
||||||
|
sv_cat_c(val, c);
|
||||||
|
lex_read_unichar(0);
|
||||||
|
|
||||||
|
c = lex_peek_unichar(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(c == -1)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
unterminated:
|
||||||
|
croak("Unterminated attribute parameter in attribute list");
|
||||||
|
}
|
||||||
|
|
||||||
|
#define lex_scan_attr() MY_lex_scan_attr(aTHX)
|
||||||
|
static SV *MY_lex_scan_attr(pTHX)
|
||||||
|
{
|
||||||
|
SV *ret = newSV(0);
|
||||||
|
if(MY_lex_scan_attrval_into(aTHX_ ret, ret))
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
SvREFCNT_dec(ret);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define lex_scan_attrs(compcv) MY_lex_scan_attrs(aTHX_ compcv)
|
||||||
|
static OP *MY_lex_scan_attrs(pTHX_ CV *compcv)
|
||||||
|
{
|
||||||
|
/* Attributes are supplied to newATTRSUB() as an OP_LIST containing
|
||||||
|
* OP_CONSTs, one attribute in each as a plain SV. Note that we don't have
|
||||||
|
* to parse inside the contents of the parens; that is handled by the
|
||||||
|
* attribute handlers themselves
|
||||||
|
*/
|
||||||
|
OP *attrs = NULL;
|
||||||
|
SV *attr;
|
||||||
|
|
||||||
|
lex_read_space(0);
|
||||||
|
while((attr = lex_scan_attr())) {
|
||||||
|
lex_read_space(0);
|
||||||
|
|
||||||
|
if(compcv && strEQ(SvPV_nolen(attr), "lvalue")) {
|
||||||
|
CvLVALUE_on(compcv);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!attrs)
|
||||||
|
attrs = newLISTOP(OP_LIST, 0, NULL, NULL);
|
||||||
|
|
||||||
|
attrs = op_append_elem(OP_LIST, attrs, newSVOP(OP_CONST, 0, attr));
|
||||||
|
|
||||||
|
/* Accept additional colons to prefix additional attrs */
|
||||||
|
if(lex_peek_unichar(0) == ':') {
|
||||||
|
lex_read_unichar(0);
|
||||||
|
lex_read_space(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define lex_scan_lexvar() MY_lex_scan_lexvar(aTHX)
|
||||||
|
static SV *MY_lex_scan_lexvar(pTHX)
|
||||||
|
{
|
||||||
|
int sigil = lex_peek_unichar(0);
|
||||||
|
switch(sigil) {
|
||||||
|
case '$':
|
||||||
|
case '@':
|
||||||
|
case '%':
|
||||||
|
lex_read_unichar(0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
croak("Expected a lexical variable");
|
||||||
|
}
|
||||||
|
|
||||||
|
SV *ret = lex_scan_ident();
|
||||||
|
if(!ret)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* prepend sigil - which we know to be a single byte */
|
||||||
|
SvGROW(ret, SvCUR(ret) + 1);
|
||||||
|
Move(SvPVX(ret), SvPVX(ret) + 1, SvCUR(ret), char);
|
||||||
|
SvPVX(ret)[0] = sigil;
|
||||||
|
SvCUR(ret)++;
|
||||||
|
|
||||||
|
SvPVX(ret)[SvCUR(ret)] = 0;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define lex_scan_parenthesized() MY_lex_scan_parenthesized(aTHX)
|
||||||
|
static SV *MY_lex_scan_parenthesized(pTHX)
|
||||||
|
{
|
||||||
|
I32 c;
|
||||||
|
int parencount = 0;
|
||||||
|
SV *ret = newSVpvs("");
|
||||||
|
if(lex_bufutf8())
|
||||||
|
SvUTF8_on(ret);
|
||||||
|
|
||||||
|
c = lex_peek_unichar(0);
|
||||||
|
|
||||||
|
while(c != -1) {
|
||||||
|
sv_cat_c(ret, lex_read_unichar(0));
|
||||||
|
|
||||||
|
switch(c) {
|
||||||
|
case '(': parencount++; break;
|
||||||
|
case ')': parencount--; break;
|
||||||
|
}
|
||||||
|
if(!parencount)
|
||||||
|
break;
|
||||||
|
|
||||||
|
c = lex_peek_unichar(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(SvCUR(ret))
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
SvREFCNT_dec(ret);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define lex_scan_version(flags) MY_lex_scan_version(aTHX_ flags)
|
||||||
|
static SV *MY_lex_scan_version(pTHX_ int flags)
|
||||||
|
{
|
||||||
|
I32 c;
|
||||||
|
SV *tmpsv = sv_2mortal(newSVpvs(""));
|
||||||
|
|
||||||
|
/* scan_version() expects a version to end in linefeed, semicolon or
|
||||||
|
* openbrace; gets confused if other keywords are fine. We'll have to
|
||||||
|
* extract it first.
|
||||||
|
* https://rt.cpan.org/Ticket/Display.html?id=132903
|
||||||
|
*/
|
||||||
|
|
||||||
|
while((c = lex_peek_unichar(0))) {
|
||||||
|
/* Allow a single leading v before accepting only digits, dot, underscore */
|
||||||
|
if((!SvCUR(tmpsv) && (c == 'v')) || strchr("0123456789._", c))
|
||||||
|
sv_cat_c(tmpsv, lex_read_unichar(0));
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!SvCUR(tmpsv) && (flags & PARSE_OPTIONAL))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
SV *ret = newSV(0);
|
||||||
|
scan_version(SvPVX(tmpsv), ret, FALSE);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define parse_lexvar() MY_parse_lexvar(aTHX)
|
||||||
|
static PADOFFSET MY_parse_lexvar(pTHX)
|
||||||
|
{
|
||||||
|
/* TODO: Rewrite this in terms of using lex_scan_lexvar()
|
||||||
|
*/
|
||||||
|
char *lexname = PL_parser->bufptr;
|
||||||
|
|
||||||
|
if(lex_read_unichar(0) != '$')
|
||||||
|
croak("Expected a lexical scalar at %s", lexname);
|
||||||
|
|
||||||
|
if(!isIDFIRST_uni(lex_peek_unichar(0)))
|
||||||
|
croak("Expected a lexical scalar at %s", lexname);
|
||||||
|
lex_read_unichar(0);
|
||||||
|
while(isIDCONT_uni(lex_peek_unichar(0)))
|
||||||
|
lex_read_unichar(0);
|
||||||
|
|
||||||
|
/* Forbid $_ */
|
||||||
|
if(PL_parser->bufptr - lexname == 2 && lexname[1] == '_')
|
||||||
|
croak("Can't use global $_ in \"my\"");
|
||||||
|
|
||||||
|
return pad_add_name_pvn(lexname, PL_parser->bufptr - lexname, 0, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define parse_scoped_block(flags) MY_parse_scoped_block(aTHX_ flags)
|
||||||
|
static OP *MY_parse_scoped_block(pTHX_ int flags)
|
||||||
|
{
|
||||||
|
OP *ret;
|
||||||
|
I32 save_ix = block_start(TRUE);
|
||||||
|
ret = parse_block(flags);
|
||||||
|
return block_end(save_ix, ret);
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/* vi: set ft=c : */
|
||||||
|
|
||||||
|
#ifndef make_argcheck_aux
|
||||||
|
|
||||||
|
#define make_argcheck_aux(params, opt_params, slurpy) S_make_argcheck_aux(aTHX_ params, opt_params, slurpy)
|
||||||
|
static inline UNOP_AUX_item *S_make_argcheck_aux(pTHX_ UV params, UV opt_params, char slurpy)
|
||||||
|
{
|
||||||
|
# if HAVE_PERL_VERSION(5, 31, 5)
|
||||||
|
struct op_argcheck_aux *aux = (struct op_argcheck_aux*)
|
||||||
|
PerlMemShared_malloc(sizeof(struct op_argcheck_aux));
|
||||||
|
aux->params = params;
|
||||||
|
aux->opt_params = opt_params;
|
||||||
|
aux->slurpy = slurpy;
|
||||||
|
return (UNOP_AUX_item *)aux;
|
||||||
|
# else
|
||||||
|
UNOP_AUX_item *aux = (UNOP_AUX_item *)PerlMemShared_malloc(sizeof(UNOP_AUX_item) * 3);
|
||||||
|
aux[0].iv = params;
|
||||||
|
aux[1].iv = opt_params;
|
||||||
|
aux[2].iv = slurpy;
|
||||||
|
return aux;
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,25 @@
|
||||||
|
/* vi: set ft=c : */
|
||||||
|
|
||||||
|
#ifndef pad_block_start
|
||||||
|
|
||||||
|
#define pad_block_start(A) S_pad_block_start(aTHX_ A)
|
||||||
|
|
||||||
|
static void S_pad_block_start(pTHX_ int full) {
|
||||||
|
dVAR;
|
||||||
|
ASSERT_CURPAD_ACTIVE("pad_block_start");
|
||||||
|
SAVEI32(PL_comppad_name_floor);
|
||||||
|
PL_comppad_name_floor = AvFILLp(PL_comppad_name);
|
||||||
|
if (full)
|
||||||
|
PL_comppad_name_fill = PL_comppad_name_floor;
|
||||||
|
if (PL_comppad_name_floor < 0)
|
||||||
|
PL_comppad_name_floor = 0;
|
||||||
|
SAVEI32(PL_min_intro_pending);
|
||||||
|
SAVEI32(PL_max_intro_pending);
|
||||||
|
PL_min_intro_pending = 0;
|
||||||
|
SAVEI32(PL_comppad_name_fill);
|
||||||
|
SAVEI32(PL_padix_floor);
|
||||||
|
PL_padix_floor = PL_padix;
|
||||||
|
PL_pad_reset_pending = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,64 @@
|
||||||
|
/* vi: set ft=c : */
|
||||||
|
|
||||||
|
#ifndef pad_leavemy
|
||||||
|
|
||||||
|
#include "COP_SEQ_RANGE_HIGH_set.c.inc"
|
||||||
|
|
||||||
|
#define pad_leavemy() S_pad_leavemy(aTHX)
|
||||||
|
|
||||||
|
#if HAVE_PERL_VERSION(5, 19, 3)
|
||||||
|
#define IF_HAVE_PERL_5_19_3(YES, NO) YES
|
||||||
|
#else
|
||||||
|
#define IF_HAVE_PERL_5_19_3(YES, NO) NO
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static OP *S_pad_leavemy(pTHX) {
|
||||||
|
dVAR;
|
||||||
|
I32 off;
|
||||||
|
OP *o = NULL;
|
||||||
|
SV * const * const svp = AvARRAY(PL_comppad_name);
|
||||||
|
|
||||||
|
PL_pad_reset_pending = FALSE;
|
||||||
|
|
||||||
|
ASSERT_CURPAD_ACTIVE("pad_leavemy");
|
||||||
|
if (PL_min_intro_pending && PL_comppad_name_fill < PL_min_intro_pending) {
|
||||||
|
for (off = PL_max_intro_pending; off >= PL_min_intro_pending; off--) {
|
||||||
|
const SV * const sv = svp[off];
|
||||||
|
if (sv && IF_HAVE_PERL_5_19_3(PadnameLEN(sv), sv != &PL_sv_undef) && !SvFAKE(sv))
|
||||||
|
Perl_ck_warner_d(aTHX_ packWARN(WARN_INTERNAL),
|
||||||
|
"%"SVf" never introduced",
|
||||||
|
SVfARG(sv));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* "Deintroduce" my variables that are leaving with this scope. */
|
||||||
|
for (off = AvFILLp(PL_comppad_name); off > PL_comppad_name_fill; off--) {
|
||||||
|
SV * const sv = svp[off];
|
||||||
|
if (sv && IF_HAVE_PERL_5_19_3(PadnameLEN(sv), sv != &PL_sv_undef) && !SvFAKE(sv)
|
||||||
|
&& COP_SEQ_RANGE_HIGH(sv) == PERL_PADSEQ_INTRO)
|
||||||
|
{
|
||||||
|
COP_SEQ_RANGE_HIGH_set(sv, PL_cop_seqmax);
|
||||||
|
DEBUG_Xv(PerlIO_printf(Perl_debug_log,
|
||||||
|
"Pad leavemy: %ld \"%s\", (%lu,%lu)\n",
|
||||||
|
(long)off, SvPVX_const(sv),
|
||||||
|
(unsigned long)COP_SEQ_RANGE_LOW(sv),
|
||||||
|
(unsigned long)COP_SEQ_RANGE_HIGH(sv))
|
||||||
|
);
|
||||||
|
#if HAVE_PERL_VERSION(5, 17, 4)
|
||||||
|
if (!PadnameIsSTATE(sv) && !PadnameIsOUR(sv)
|
||||||
|
&& *PadnamePV(sv) == '&' && PadnameLEN(sv) > 1) {
|
||||||
|
OP *kid = newOP(OP_INTROCV, 0);
|
||||||
|
kid->op_targ = off;
|
||||||
|
o = op_prepend_elem(OP_LINESEQ, kid, o);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PL_cop_seqmax++;
|
||||||
|
if (PL_cop_seqmax == PERL_PADSEQ_INTRO) /* not a legal value */
|
||||||
|
PL_cop_seqmax++;
|
||||||
|
DEBUG_Xv(PerlIO_printf(Perl_debug_log,
|
||||||
|
"Pad leavemy: seq = %ld\n", (long)PL_cop_seqmax));
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,220 @@
|
||||||
|
/* 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
|
|
@ -0,0 +1,32 @@
|
||||||
|
/* vi: set ft=c : */
|
||||||
|
|
||||||
|
#ifndef scalarseq
|
||||||
|
|
||||||
|
#define scalarseq(A) S_scalarseq(aTHX_ A)
|
||||||
|
|
||||||
|
static OP *S_scalarseq(pTHX_ OP *o) {
|
||||||
|
dVAR;
|
||||||
|
if (o) {
|
||||||
|
const OPCODE type = o->op_type;
|
||||||
|
|
||||||
|
if (type == OP_LINESEQ || type == OP_SCOPE ||
|
||||||
|
type == OP_LEAVE || type == OP_LEAVETRY)
|
||||||
|
{
|
||||||
|
OP *kid;
|
||||||
|
for (kid = cLISTOPo->op_first; kid; kid = kid->op_sibling) {
|
||||||
|
if (kid->op_sibling) {
|
||||||
|
op_contextualize(kid, G_VOID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PL_curcop = &PL_compiling;
|
||||||
|
}
|
||||||
|
o->op_flags &= ~OPf_PARENS;
|
||||||
|
if (PL_hints & HINT_BLOCK_SCOPE)
|
||||||
|
o->op_flags |= OPf_PARENS;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
o = newOP(OP_STUB, 0);
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,13 @@
|
||||||
|
/* vi: set ft=c : */
|
||||||
|
|
||||||
|
#ifndef sv_setrv_noinc
|
||||||
|
# define sv_setrv_noinc(sv, rv) S_sv_setrv(aTHX_ sv, rv)
|
||||||
|
# define sv_setrv_inc(sv, rv) S_sv_setrv(aTHX_ sv, SvREFCNT_inc(rv))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void S_sv_setrv(pTHX_ SV *sv, SV *rv)
|
||||||
|
{
|
||||||
|
SV *tmp = newRV_noinc(rv);
|
||||||
|
sv_setsv(sv, tmp);
|
||||||
|
SvREFCNT_dec(tmp);
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/* vi: set ft=c : */
|
||||||
|
|
||||||
|
#ifndef OP_CHECK_MUTEX_LOCK /* < 5.15.8 */
|
||||||
|
# define OP_CHECK_MUTEX_LOCK ((void)0)
|
||||||
|
# define OP_CHECK_MUTEX_UNLOCK ((void)0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define wrap_keyword_plugin(func, var) S_wrap_keyword_plugin(aTHX_ func, var)
|
||||||
|
|
||||||
|
static void S_wrap_keyword_plugin(pTHX_ Perl_keyword_plugin_t func, Perl_keyword_plugin_t *var)
|
||||||
|
{
|
||||||
|
/* BOOT can potentially race with other threads (RT123547) */
|
||||||
|
|
||||||
|
/* Perl doesn't really provide us a nice mutex for doing this so this is the
|
||||||
|
* best we can find. See also
|
||||||
|
* https://rt.perl.org/Public/Bug/Display.html?id=132413
|
||||||
|
*/
|
||||||
|
if(*var)
|
||||||
|
return;
|
||||||
|
|
||||||
|
OP_CHECK_MUTEX_LOCK;
|
||||||
|
if(!*var) {
|
||||||
|
*var = PL_keyword_plugin;
|
||||||
|
PL_keyword_plugin = func;
|
||||||
|
}
|
||||||
|
|
||||||
|
OP_CHECK_MUTEX_UNLOCK;
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package Module::Build::with::XSTests;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use base qw( Module::Build );
|
||||||
|
|
||||||
|
# Stolen and edited from Module::Build::Base::_infer_xs_spec
|
||||||
|
sub _infer_xs_spec
|
||||||
|
{
|
||||||
|
my $self = shift;
|
||||||
|
my ( $file ) = @_;
|
||||||
|
|
||||||
|
my $spec = $self->SUPER::_infer_xs_spec( $file );
|
||||||
|
|
||||||
|
if( $file =~ m{^t/} ) {
|
||||||
|
$spec->{$_} = File::Spec->catdir( "t", $spec->{$_} )
|
||||||
|
for qw( archdir bs_file lib_file );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Various bits stolen from Module::Build::Base::
|
||||||
|
# process_xs_files()
|
||||||
|
sub ACTION_testlib
|
||||||
|
{
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
my $testxsfiles = $self->_find_file_by_type('xs', 't');
|
||||||
|
|
||||||
|
foreach my $from ( sort keys %$testxsfiles ) {
|
||||||
|
my $to = $testxsfiles->{$from};
|
||||||
|
|
||||||
|
if( $to ne $from ) {
|
||||||
|
$self->add_to_cleanup( $to );
|
||||||
|
$self->copy_if_modified( from => $from, to => $to );
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->process_xs( $to );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ACTION_test
|
||||||
|
{
|
||||||
|
my $self = shift;
|
||||||
|
$self->depends_on( "testlib" );
|
||||||
|
|
||||||
|
$self->SUPER::ACTION_test( @_ );
|
||||||
|
}
|
||||||
|
|
||||||
|
0x55AA;
|
|
@ -0,0 +1,360 @@
|
||||||
|
# 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, 2020-2021 -- leonerd@leonerd.org.uk
|
||||||
|
|
||||||
|
package XS::Parse::Sublike 0.16;
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
require XSLoader;
|
||||||
|
XSLoader::load( __PACKAGE__, our $VERSION );
|
||||||
|
|
||||||
|
=encoding UTF-8
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
C<XS::Parse::Sublike> - XS functions to assist in parsing C<sub>-like syntax
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
This module provides some XS functions to assist in writing parsers for
|
||||||
|
C<sub>-like syntax, primarily for authors of keyword plugins using the
|
||||||
|
C<PL_keyword_plugin> hook mechanism. It is unlikely to be of much use to
|
||||||
|
anyone else; and highly unlikely to be any use when writing perl code using
|
||||||
|
these. Unless you are writing a keyword plugin using XS, this module is not
|
||||||
|
for you.
|
||||||
|
|
||||||
|
This module is also currently experimental, and the design is still evolving
|
||||||
|
and subject to change. Later versions may break ABI compatibility, requiring
|
||||||
|
changes or at least a rebuild of any module that depends on it.
|
||||||
|
|
||||||
|
=head1 XS FUNCTIONS
|
||||||
|
|
||||||
|
=head2 boot_xs_parse_sublike
|
||||||
|
|
||||||
|
void boot_xs_parse_sublike(double ver)
|
||||||
|
|
||||||
|
Call this function from your C<BOOT> section in order to initialise the module
|
||||||
|
and parsing hooks.
|
||||||
|
|
||||||
|
I<ver> should either be 0 or a decimal number for the module version
|
||||||
|
requirement; e.g.
|
||||||
|
|
||||||
|
boot_xs_parse_sublike(0.04);
|
||||||
|
|
||||||
|
=head2 xs_parse_sublike
|
||||||
|
|
||||||
|
int xs_parse_sublike(const struct XSParseSublikeHooks *hooks, void *hookdata, OP **op_ptr)
|
||||||
|
|
||||||
|
This function performs the actual parsing of a C<sub>-like keyword. It expects
|
||||||
|
the lexer to be at a position just after the introduction keyword has been
|
||||||
|
consumed, and will proceed to parse an optional name, list of attributes,
|
||||||
|
signature (if enabled by C<use feature 'signatures'>), and code body. The
|
||||||
|
return value and C<op_ptr> can be used directly from the keyword plugin
|
||||||
|
function. It is intended this function be invoked from it, and the result
|
||||||
|
returned directly.
|
||||||
|
|
||||||
|
For a more automated handling of keywords, see L</register_xs_parse_sublike>.
|
||||||
|
|
||||||
|
I<hooks> should be a structure that can provide optional function pointers
|
||||||
|
used to customise the parsing process at various stages. I<hookdata> is an
|
||||||
|
opaque pointer which is passed through to each of the hook stage functions.
|
||||||
|
|
||||||
|
=head2 register_xs_parse_sublike
|
||||||
|
|
||||||
|
void register_xs_parse_sublike(const char *keyword,
|
||||||
|
const struct XSParseSublikeHooks *hooks, void *hookdata)
|
||||||
|
|
||||||
|
This function installs a set of parsing hooks to be associated with the given
|
||||||
|
keyword. Such a keyword will then be handled automatically by a keyword parser
|
||||||
|
installed by C<XS::Parse::Sublike> itself.
|
||||||
|
|
||||||
|
When the keyword is encountered, the hook's C<permit> function is first tested
|
||||||
|
to see if the keyword is permitted at this point. If the function returns true
|
||||||
|
then the keyword is consumed and parsed as per L</xs_parse_sublike>.
|
||||||
|
|
||||||
|
I<hookdata> is an opaque pointer which is passed through to each of the hook
|
||||||
|
stage functions when they are invoked.
|
||||||
|
|
||||||
|
=head2 xs_parse_sublike_any
|
||||||
|
|
||||||
|
int xs_parse_sublike_any(const struct XSParseSublikeHooks *hooks, void *hookdata,
|
||||||
|
OP **op_ptr)
|
||||||
|
|
||||||
|
This function expects to consume an introduction keyword at the lexer position
|
||||||
|
which is either C<sub> or the name of another C<sub>-like keyword, which has
|
||||||
|
been previously registered using L</register_xs_parse_sublike>. It then
|
||||||
|
proceeds to parse the subsequent syntax similar to how it would have parsed if
|
||||||
|
encountered by the module's own keyword parser plugin, except that the second
|
||||||
|
set of hooks given here also take effect.
|
||||||
|
|
||||||
|
If a regular C<sub> is encountered, then this is parsed using the I<hooks> in
|
||||||
|
a similar way to C<xs_parse_sublike()>.
|
||||||
|
|
||||||
|
If a different registered C<sub>-like keyword is encountered, then parsing is
|
||||||
|
performed using B<both> sets of hooks - the ones given to this function as
|
||||||
|
well as the ones registered with the keyword. This allows their effects to
|
||||||
|
combined. The hooks given by the I<hooks> argument are considered to be on the
|
||||||
|
"outside" from those of the registered keyword "inside". The outside ones run
|
||||||
|
first for all stages, except C<pre_blockend> which runs them inside-out.
|
||||||
|
|
||||||
|
I<hookdata> is an opaque pointer which is passed through to each of the hook
|
||||||
|
stage functions when they are invoked.
|
||||||
|
|
||||||
|
Note that this function is now vaguely discouraged, in favour of using a
|
||||||
|
prefixing keyword instead, by using the C<XS_PARSE_SUBLIKE_FLAG_PREFIX> flag.
|
||||||
|
|
||||||
|
=head1 PARSE CONTEXT
|
||||||
|
|
||||||
|
The various hook stages all share state about the ongoing parse process using
|
||||||
|
various fields of the C<XSParseSublikeContext> structure.
|
||||||
|
|
||||||
|
struct XSParseSublikeContext {
|
||||||
|
SV *name;
|
||||||
|
OP *attrs;
|
||||||
|
OP *body;
|
||||||
|
CV *cv;
|
||||||
|
U32 actions;
|
||||||
|
HV *moddata;
|
||||||
|
}
|
||||||
|
|
||||||
|
The C<actions> field will contain a bitmask of action flags that control the
|
||||||
|
various steps that C<XS::Parse::Sublike> might take inbetween invoking hook
|
||||||
|
stages. The initial value of this field is set after the name-parsing stage,
|
||||||
|
depending on whether or not a name is found. Stage hook functions may modify
|
||||||
|
the field to adjust the subsequent behaviour.
|
||||||
|
|
||||||
|
At the current ABI version, a module will have to set the
|
||||||
|
C<XS_PARSE_SUBLIKE_COMPAT_FLAG_DYNAMIC_ACTIONS> bit of the C<flags> field in
|
||||||
|
order to make use of the I<actions> field. A future ABI version may remove
|
||||||
|
this restriction.
|
||||||
|
|
||||||
|
=over 4
|
||||||
|
|
||||||
|
=item XS_PARSE_SUBLIKE_ACTION_CVf_ANON
|
||||||
|
|
||||||
|
If set, the C<start_subparse()> call will be set up for an anonymous function
|
||||||
|
protosub; if not it will be set for a named function. This is set by default
|
||||||
|
if a name was not found.
|
||||||
|
|
||||||
|
=item XS_PARSE_SUBLIKE_ACTION_SET_CVNAME
|
||||||
|
|
||||||
|
If set, the newly-constructed CV will have the given name set on it. This is
|
||||||
|
set by default if a name was found.
|
||||||
|
|
||||||
|
On Perl versions 5.22 and above, this flag can be set even if
|
||||||
|
C<XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL> is not. In this case, the CV will
|
||||||
|
not be reachable via the symbol table, even though it knows its own name and
|
||||||
|
pretends that it is. On earlier versions of perl this flag will be ignored in
|
||||||
|
that case.
|
||||||
|
|
||||||
|
=item XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL
|
||||||
|
|
||||||
|
If set, the newly-constructed CV will be installed into the symbol table at
|
||||||
|
its given name. Note that it is not possible to enable this flag without also
|
||||||
|
enabling C<XS_PARSE_SUBLIKE_ACTION_SET_CVNAME>. This is set by default if a
|
||||||
|
name was found.
|
||||||
|
|
||||||
|
=item XS_PARSE_SUBLIKE_ACTION_REFGEN_ANONCODE
|
||||||
|
|
||||||
|
If set, the syntax will yield the C<OP_REFGEN> / C<OP_ANONCODE> optree
|
||||||
|
fragment typical of anonymous code expressions; if not it will be C<OP_NULL>.
|
||||||
|
This is set by default if a name was not found.
|
||||||
|
|
||||||
|
=item XS_PARSE_SUBLIKE_ACTION_RET_EXPR
|
||||||
|
|
||||||
|
If set, the syntax will parse like an expression; if not it will parse like a
|
||||||
|
statement. This is set by default if a name was not found.
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
The I<moddata> field will point towards an HV that modules can used to store
|
||||||
|
extra data between stages. As a naming convention a module should prefix its
|
||||||
|
keys with its own module name and a slash character, C<"Some::Module/field">.
|
||||||
|
The field will point to a newly-created HV for every parse invocation, and
|
||||||
|
will be released when each parse is complete.
|
||||||
|
|
||||||
|
=head1 PARSE HOOKS
|
||||||
|
|
||||||
|
The C<XSParseSublikeHooks> structure provides the following hook stages, which
|
||||||
|
are invoked in the given order.
|
||||||
|
|
||||||
|
The structure has a I<flags> field, which controls various optional parts of
|
||||||
|
operation. The following flags are defined.
|
||||||
|
|
||||||
|
=over 4
|
||||||
|
|
||||||
|
=item XS_PARSE_SUBLIKE_FLAG_FILTERATTRS
|
||||||
|
|
||||||
|
If set, the optional C<filter_attr> stage will be invoked.
|
||||||
|
|
||||||
|
=item XS_PARSE_SUBLIKE_FLAG_BODY_OPTIONAL
|
||||||
|
|
||||||
|
If B<not> set, the I<require_parts> field will imply the
|
||||||
|
C<XS_PARSE_SUBLIKE_PART_BODY> flag, making the body part required. By setting
|
||||||
|
this flag this will no longer happen. If all hooks agree, then the body will
|
||||||
|
become optional.
|
||||||
|
|
||||||
|
=item XS_PARSE_SUBLIKE_FLAG_PREFIX
|
||||||
|
|
||||||
|
If set, the keyword is considered to be a prefix that can be placed in front
|
||||||
|
of C<sub> or another sub-like keyword, to add its set of hooks in addition to
|
||||||
|
those of the following keyword. These prefices may be further stacked.
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
In addition there are two C<U8> fields named I<require_parts> and
|
||||||
|
I<skip_parts> which control the behaviour of various parts of the syntax which
|
||||||
|
are usually optional. Any parts with bits set in I<require_parts> become
|
||||||
|
non-optional, and an error if they are missing. Any parts with bits set in
|
||||||
|
I<skip_parts> will skip the relevant part of the parsing process.
|
||||||
|
|
||||||
|
When multiple sets of hooks are combined by the C<xs_parse_sublike_any>
|
||||||
|
function, or as part of parsing prefixing keywords, these bitmasks are
|
||||||
|
accumulated together with inclusive or. Any part required by any set of hooks
|
||||||
|
will still be required; any step skipped by either will be skipped entirely.
|
||||||
|
|
||||||
|
If the same bit is set in both fields then the relevant parsing step will not
|
||||||
|
be performed but it will still be an error for that section to be missing.
|
||||||
|
This is likely not useful.
|
||||||
|
|
||||||
|
Note that for skipped parts, only the actual parsing steps are skipped. A hook
|
||||||
|
function can still set the relevant fields in the context structure anyway to
|
||||||
|
force a particular value for those parts.
|
||||||
|
|
||||||
|
=over 4
|
||||||
|
|
||||||
|
=item XS_PARSE_SUBLIKE_PART_NAME
|
||||||
|
|
||||||
|
The name of the function.
|
||||||
|
|
||||||
|
=item XS_PARSE_SUBLIKE_PART_ATTRS
|
||||||
|
|
||||||
|
The attributes of the function.
|
||||||
|
|
||||||
|
This part can be skipped, but the bit is ignored when in I<require_parts>. It
|
||||||
|
is always permitted to not provide any additional attributes to a function
|
||||||
|
definition.
|
||||||
|
|
||||||
|
=item XS_PARSE_SUBLIKE_PART_SIGNATURE
|
||||||
|
|
||||||
|
The parameter signature of the function.
|
||||||
|
|
||||||
|
This part can be skipped, but the bit is ignored when in I<require_parts>. It
|
||||||
|
is always permitted not to provide a signature for a function definition,
|
||||||
|
because such syntax only applies when C<use feature 'signatures'> is in
|
||||||
|
effect, and only on supporting perl versions.
|
||||||
|
|
||||||
|
=item XS_PARSE_SUBLIKE_PART_BODY
|
||||||
|
|
||||||
|
The actual body of the function, expressed as a brace-delimited block.
|
||||||
|
|
||||||
|
This part cannot be skipped, but it can be made optional by omitting it from
|
||||||
|
the I<require_parts> field. Instead of the block, it is permitted to place a
|
||||||
|
single semicolon (C<;>) to act as a statement terminator; thus giving the same
|
||||||
|
syntax as a subroutine forward declaration.
|
||||||
|
|
||||||
|
In this case, the C<body> and C<cv> fields of the context structure will
|
||||||
|
remain C<NULL>.
|
||||||
|
|
||||||
|
This flag is currently implied on the I<require_parts> field if the hook does
|
||||||
|
not supply the C<XS_PARSE_SUBLIKE_FLAG_BODY_OPTIONAL> flag; meaning that most
|
||||||
|
use-cases will make it a required part.
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
=head2 The C<permit> Stage
|
||||||
|
|
||||||
|
const char *permit_hintkey
|
||||||
|
bool (*permit)(pTHX_ void *hookdata)
|
||||||
|
|
||||||
|
Called by the installed keyword parser hook which is used to handle keywords
|
||||||
|
registered by L</register_xs_parse_sublike>.
|
||||||
|
|
||||||
|
As a shortcut for the common case, the C<permit_hintkey> may point to a string
|
||||||
|
to look up from the hints hash. If the given key name is not found in the
|
||||||
|
hints hash then the keyword is not permitted. If the key is present then the
|
||||||
|
C<permit> function is invoked as normal.
|
||||||
|
|
||||||
|
If not rejected by a hint key that was not found in the hints hash, the
|
||||||
|
function part of the stage is called next and should inspect whether the
|
||||||
|
keyword is permitted at this time perhaps by inspecting other lexical clues,
|
||||||
|
and return true only if the keyword is permitted.
|
||||||
|
|
||||||
|
Both the string and the function are optional. Either or both may be present.
|
||||||
|
If neither is present then the keyword is always permitted - which is likely
|
||||||
|
not what you wanted to do.
|
||||||
|
|
||||||
|
=head2 Parse Name
|
||||||
|
|
||||||
|
At this point, the optional name is parsed and filled into the C<name> field
|
||||||
|
of the context.
|
||||||
|
|
||||||
|
=head2 The C<pre_subparse> Stage
|
||||||
|
|
||||||
|
void (*pre_subparse)(pTHX_ struct XSParseSublikeContext *ctx, void *hookdata)
|
||||||
|
|
||||||
|
Invoked just before C<start_subparse()> is called.
|
||||||
|
|
||||||
|
=head2 Parse Attrs
|
||||||
|
|
||||||
|
At this point the optional sub attributes are parsed and filled into the
|
||||||
|
C<attrs> field of the context, then C<block_start()> is called.
|
||||||
|
|
||||||
|
=head2 The optional C<filter_attr> Stage
|
||||||
|
|
||||||
|
bool (*filter_attr)(pTHX_ struct XSParseSublikeContext *ctx,
|
||||||
|
SV *attr, SV *val, void *hookdata);
|
||||||
|
|
||||||
|
If the I<flags> field includes C<XS_PARSE_SUBLIKE_FLAG_FILTERATTRS> then each
|
||||||
|
individual attribute is passed through this optional filter function
|
||||||
|
immediately as each is parsed. I<attr> will be a string SV containing the name
|
||||||
|
of the attribute, and I<val> will either be C<NULL>, or a string SV containing
|
||||||
|
the contents of the parens after its name (without the parens themselves).
|
||||||
|
|
||||||
|
If the filter returns C<true>, it indicates that it has in some way handled
|
||||||
|
the attribute and it should not be added to the list given to C<newATTRSUB()>.
|
||||||
|
If the filter returns C<false> it will be handled in the usual way; equivalent
|
||||||
|
to the case where the filter function did not exist.
|
||||||
|
|
||||||
|
=head2 The C<post_blockstart> Stage
|
||||||
|
|
||||||
|
void (*post_blockstart)(pTHX_ struct XSParseSublikeContext *ctx, void *hookdata)
|
||||||
|
|
||||||
|
Invoked after the C<block_start()> function has been called. This hook stage
|
||||||
|
may wish to perform any alterations of C<PL_compcv> or related, inspect or
|
||||||
|
alter the lexical pad, provide hints hash values, or any other tasks before
|
||||||
|
the signature and code body are parsed.
|
||||||
|
|
||||||
|
=head2 Parse Body
|
||||||
|
|
||||||
|
At this point, the main body of the function is parsed and the optree is
|
||||||
|
stored in the C<body> field of the context. If the perl version supports sub
|
||||||
|
signatures and they are enabled and found, the body will be prefixed with the
|
||||||
|
signature ops as well.
|
||||||
|
|
||||||
|
=head2 The C<pre_blockend> Stage
|
||||||
|
|
||||||
|
void (*pre_blockend)(pTHX_ struct XSParseSublikeContext *ctx, void *hookdata)
|
||||||
|
|
||||||
|
Invoked just before the C<block_end()> function is invoked. The hook stage may
|
||||||
|
wish to inspect or alter the optree stored in the C<body> context field.
|
||||||
|
|
||||||
|
=head2 The C<post_newcv> Stage
|
||||||
|
|
||||||
|
void (*post_newcv)(pTHX_ struct XSParseSublikeContext *ctx, void *hookdata)
|
||||||
|
|
||||||
|
Invoked just after C<newATTRSUB()> has been invoked on the optree. The hook
|
||||||
|
stage may wish to inspect or alter the CV stored in the C<cv> context field.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
=head1 AUTHOR
|
||||||
|
|
||||||
|
Paul Evans <leonerd@leonerd.org.uk>
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
0x55AA;
|
|
@ -0,0 +1,579 @@
|
||||||
|
/* 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, 2019-2021 -- leonerd@leonerd.org.uk
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "EXTERN.h"
|
||||||
|
#include "perl.h"
|
||||||
|
#include "XSUB.h"
|
||||||
|
|
||||||
|
#include "XSParseSublike.h"
|
||||||
|
|
||||||
|
#define HAVE_PERL_VERSION(R, V, 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, 31, 3)
|
||||||
|
# include "parse_subsignature.c.inc"
|
||||||
|
# endif
|
||||||
|
|
||||||
|
# include "make_argcheck_aux.c.inc"
|
||||||
|
|
||||||
|
# define HAVE_PARSE_SUBSIGNATURE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !HAVE_PERL_VERSION(5, 22, 0)
|
||||||
|
# include "block_start.c.inc"
|
||||||
|
# include "block_end.c.inc"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef wrap_keyword_plugin
|
||||||
|
# include "wrap_keyword_plugin.c.inc"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "lexer-additions.c.inc"
|
||||||
|
|
||||||
|
struct HooksAndData {
|
||||||
|
const struct XSParseSublikeHooks *hooks;
|
||||||
|
void *data;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define FOREACH_HOOKS_FORWARD \
|
||||||
|
for(hooki = 0; \
|
||||||
|
(hooki < nhooks) && (hooks = hooksanddata[hooki].hooks, hookdata = hooksanddata[hooki].data), (hooki < nhooks); \
|
||||||
|
hooki++)
|
||||||
|
|
||||||
|
#define FOREACH_HOOKS_REVERSE \
|
||||||
|
for(hooki = nhooks - 1; \
|
||||||
|
(hooki >= 0) && (hooks = hooksanddata[hooki].hooks, hookdata = hooksanddata[hooki].data), (hooki >= 0); \
|
||||||
|
hooki--)
|
||||||
|
|
||||||
|
static int parse(pTHX_
|
||||||
|
struct HooksAndData hooksanddata[],
|
||||||
|
size_t nhooks,
|
||||||
|
OP **op_ptr)
|
||||||
|
{
|
||||||
|
struct XSParseSublikeContext ctx = { 0 };
|
||||||
|
|
||||||
|
IV hooki;
|
||||||
|
const struct XSParseSublikeHooks *hooks;
|
||||||
|
void *hookdata;
|
||||||
|
|
||||||
|
U8 require_parts = 0, skip_parts = 0;
|
||||||
|
bool have_dynamic_actions = FALSE;
|
||||||
|
|
||||||
|
ENTER_with_name("parse_sublike");
|
||||||
|
/* From here onwards any `return` must be prefixed by LEAVE_with_name() */
|
||||||
|
U32 was_scopestack_ix = PL_scopestack_ix;
|
||||||
|
|
||||||
|
ctx.moddata = newHV();
|
||||||
|
SAVEFREESV(ctx.moddata);
|
||||||
|
|
||||||
|
FOREACH_HOOKS_FORWARD {
|
||||||
|
require_parts |= hooks->require_parts;
|
||||||
|
skip_parts |= hooks->skip_parts;
|
||||||
|
if(!(hooks->flags & XS_PARSE_SUBLIKE_FLAG_BODY_OPTIONAL))
|
||||||
|
require_parts |= XS_PARSE_SUBLIKE_PART_BODY;
|
||||||
|
if(hooks->flags & XS_PARSE_SUBLIKE_COMPAT_FLAG_DYNAMIC_ACTIONS)
|
||||||
|
have_dynamic_actions = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!(skip_parts & XS_PARSE_SUBLIKE_PART_NAME)) {
|
||||||
|
ctx.name = lex_scan_ident();
|
||||||
|
lex_read_space(0);
|
||||||
|
}
|
||||||
|
if((require_parts & XS_PARSE_SUBLIKE_PART_NAME) && !ctx.name)
|
||||||
|
croak("Expected name for sub-like construction");
|
||||||
|
|
||||||
|
/* Initial idea of actions are determined by whether we have a name */
|
||||||
|
ctx.actions = ctx.name
|
||||||
|
? /* named */ XS_PARSE_SUBLIKE_ACTION_SET_CVNAME|XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL
|
||||||
|
: /* anon */ XS_PARSE_SUBLIKE_ACTION_CVf_ANON|XS_PARSE_SUBLIKE_ACTION_REFGEN_ANONCODE|XS_PARSE_SUBLIKE_ACTION_RET_EXPR;
|
||||||
|
|
||||||
|
FOREACH_HOOKS_FORWARD {
|
||||||
|
if(hooks->pre_subparse)
|
||||||
|
(*hooks->pre_subparse)(aTHX_ &ctx, hookdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUGGING
|
||||||
|
if(PL_scopestack_ix != was_scopestack_ix)
|
||||||
|
croak("ARGH: pre_subparse broke the scopestack (was %d, now %d)\n",
|
||||||
|
was_scopestack_ix, PL_scopestack_ix);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if(!have_dynamic_actions) {
|
||||||
|
if(ctx.name)
|
||||||
|
ctx.actions &= ~XS_PARSE_SUBLIKE_ACTION_CVf_ANON;
|
||||||
|
else
|
||||||
|
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);
|
||||||
|
SAVEFREESV(PL_compcv);
|
||||||
|
|
||||||
|
if(!(skip_parts & XS_PARSE_SUBLIKE_PART_ATTRS) && (lex_peek_unichar(0) == ':')) {
|
||||||
|
lex_read_unichar(0);
|
||||||
|
lex_read_space(0);
|
||||||
|
|
||||||
|
ctx.attrs = newLISTOP(OP_LIST, 0, NULL, NULL);
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
SV *attr = newSV(0);
|
||||||
|
SV *val = newSV(0);
|
||||||
|
if(!lex_scan_attrval_into(attr, val))
|
||||||
|
break;
|
||||||
|
lex_read_space(0);
|
||||||
|
if(lex_peek_unichar(0) == ':') {
|
||||||
|
lex_read_unichar(0);
|
||||||
|
lex_read_space(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool handled = FALSE;
|
||||||
|
|
||||||
|
FOREACH_HOOKS_FORWARD {
|
||||||
|
if((hooks->flags & XS_PARSE_SUBLIKE_FLAG_FILTERATTRS) && (hooks->filter_attr))
|
||||||
|
handled |= (*hooks->filter_attr)(aTHX_ &ctx, attr, val, hookdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(handled) {
|
||||||
|
SvREFCNT_dec(attr);
|
||||||
|
SvREFCNT_dec(val);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(strEQ(SvPVX(attr), "lvalue")) {
|
||||||
|
CvLVALUE_on(PL_compcv);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(SvPOK(val))
|
||||||
|
sv_catpvf(attr, "(%" SVf ")", val);
|
||||||
|
SvREFCNT_dec(val);
|
||||||
|
|
||||||
|
ctx.attrs = op_append_elem(OP_LIST, ctx.attrs, newSVOP(OP_CONST, 0, attr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PL_hints |= HINT_LOCALIZE_HH;
|
||||||
|
I32 save_ix = block_start(TRUE);
|
||||||
|
|
||||||
|
FOREACH_HOOKS_FORWARD {
|
||||||
|
if(hooks->post_blockstart)
|
||||||
|
(*hooks->post_blockstart)(aTHX_ &ctx, hookdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUGGING
|
||||||
|
if(PL_scopestack_ix != was_scopestack_ix)
|
||||||
|
croak("ARGH: post_blockstart broke the scopestack (was %d, now %d)\n",
|
||||||
|
was_scopestack_ix, PL_scopestack_ix);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_PARSE_SUBSIGNATURE
|
||||||
|
OP *sigop = NULL;
|
||||||
|
if(!(skip_parts & XS_PARSE_SUBLIKE_PART_SIGNATURE) && (lex_peek_unichar(0) == '(')) {
|
||||||
|
lex_read_unichar(0);
|
||||||
|
lex_read_space(0);
|
||||||
|
|
||||||
|
#if HAVE_PERL_VERSION(5, 31, 3)
|
||||||
|
/* core's parse_subsignature doesn't seem able to handle empty sigs
|
||||||
|
* RT132284
|
||||||
|
* https://github.com/Perl/perl5/issues/17689
|
||||||
|
*/
|
||||||
|
if(lex_peek_unichar(0) == ')') {
|
||||||
|
/* Inject an empty OP_ARGCHECK much as core would do if it encountered
|
||||||
|
* an empty signature */
|
||||||
|
UNOP_AUX_item *aux = make_argcheck_aux(0, 0, 0);
|
||||||
|
|
||||||
|
sigop = op_prepend_elem(OP_LINESEQ, newSTATEOP(0, NULL, NULL),
|
||||||
|
newUNOP_AUX(OP_ARGCHECK, 0, NULL, aux));
|
||||||
|
|
||||||
|
/* a nextstate at the end handles context correctly for an empty
|
||||||
|
* sub body */
|
||||||
|
sigop = op_append_elem(OP_LINESEQ, sigop, newSTATEOP(0, NULL, NULL));
|
||||||
|
|
||||||
|
#if HAVE_PERL_VERSION(5,31,5)
|
||||||
|
/* wrap the list of arg ops in a NULL aux op. This serves two
|
||||||
|
* purposes. First, it makes the arg list a separate subtree
|
||||||
|
* from the body of the sub, and secondly the null op may in
|
||||||
|
* future be upgraded to an OP_SIGNATURE when implemented. For
|
||||||
|
* now leave it as ex-argcheck
|
||||||
|
*/
|
||||||
|
sigop = newUNOP_AUX(OP_ARGCHECK, 0, sigop, NULL);
|
||||||
|
op_null(sigop);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
sigop = parse_subsignature(0);
|
||||||
|
|
||||||
|
if(PL_parser->error_count) {
|
||||||
|
assert(PL_scopestack_ix == was_scopestack_ix);
|
||||||
|
LEAVE_with_name("parse_sublike");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(lex_peek_unichar(0) != ')')
|
||||||
|
croak("Expected ')'");
|
||||||
|
lex_read_unichar(0);
|
||||||
|
lex_read_space(0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if(lex_peek_unichar(0) == '{') {
|
||||||
|
/* TODO: technically possible to have skip body flag */
|
||||||
|
ctx.body = parse_block(0);
|
||||||
|
SvREFCNT_inc(PL_compcv);
|
||||||
|
}
|
||||||
|
else if(require_parts & XS_PARSE_SUBLIKE_PART_BODY)
|
||||||
|
croak("Expected '{' for block body");
|
||||||
|
else if(lex_peek_unichar(0) == ';') {
|
||||||
|
/* nothing to be done */
|
||||||
|
}
|
||||||
|
else
|
||||||
|
croak("Expected '{' for block body or ';'");
|
||||||
|
|
||||||
|
#ifdef HAVE_PARSE_SUBSIGNATURE
|
||||||
|
if(ctx.body && sigop) {
|
||||||
|
/* parse_block() returns an empy block as a stub op.
|
||||||
|
* no need to keep that if we we have a signature.
|
||||||
|
*/
|
||||||
|
if (ctx.body->op_type == OP_STUB) {
|
||||||
|
op_free(ctx.body);
|
||||||
|
ctx.body = NULL;
|
||||||
|
}
|
||||||
|
ctx.body = op_append_list(OP_LINESEQ, sigop, ctx.body);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if(PL_parser->error_count) {
|
||||||
|
/* parse_block() still sometimes returns a valid body even if a parse
|
||||||
|
* error happens.
|
||||||
|
* We need to destroy this partial body before returning a valid(ish)
|
||||||
|
* state to the keyword hook mechanism, so it will find the error count
|
||||||
|
* correctly
|
||||||
|
* See https://rt.cpan.org/Ticket/Display.html?id=130417
|
||||||
|
*/
|
||||||
|
op_free(ctx.body);
|
||||||
|
|
||||||
|
/* REALLY??! Do I really have to do this??
|
||||||
|
* See also:
|
||||||
|
* https://www.nntp.perl.org/group/perl.perl5.porters/2021/06/msg260642.html
|
||||||
|
*/
|
||||||
|
while(PL_scopestack_ix > was_scopestack_ix)
|
||||||
|
LEAVE;
|
||||||
|
|
||||||
|
*op_ptr = newOP(OP_NULL, 0);
|
||||||
|
if(ctx.name) {
|
||||||
|
SvREFCNT_dec(ctx.name);
|
||||||
|
assert(PL_scopestack_ix == was_scopestack_ix);
|
||||||
|
LEAVE_with_name("parse_sublike");
|
||||||
|
return KEYWORD_PLUGIN_STMT;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assert(PL_scopestack_ix == was_scopestack_ix);
|
||||||
|
LEAVE_with_name("parse_sublike");
|
||||||
|
return KEYWORD_PLUGIN_EXPR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FOREACH_HOOKS_REVERSE {
|
||||||
|
if(hooks->pre_blockend)
|
||||||
|
(*hooks->pre_blockend)(aTHX_ &ctx, hookdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUGGING
|
||||||
|
if(PL_scopestack_ix != was_scopestack_ix)
|
||||||
|
croak("ARGH: pre_blockend broke the scopestack (was %d, now %d)\n",
|
||||||
|
was_scopestack_ix, PL_scopestack_ix);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if(ctx.body) {
|
||||||
|
ctx.body = block_end(save_ix, ctx.body);
|
||||||
|
|
||||||
|
if(!have_dynamic_actions) {
|
||||||
|
if(ctx.name)
|
||||||
|
ctx.actions |= XS_PARSE_SUBLIKE_ACTION_SET_CVNAME|XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL;
|
||||||
|
else
|
||||||
|
ctx.actions &= ~(XS_PARSE_SUBLIKE_ACTION_SET_CVNAME|XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we want both SET_CVNAME and INSTALL_SYMBOL actions we might as well
|
||||||
|
* let newATTRSUB() do it. If we only wanted one we need to be more subtle
|
||||||
|
*/
|
||||||
|
bool action_set_cvname = ctx.actions & XS_PARSE_SUBLIKE_ACTION_SET_CVNAME;
|
||||||
|
bool action_install_symbol = ctx.actions & XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL;
|
||||||
|
OP *nameop = NULL;
|
||||||
|
if(ctx.name && action_set_cvname && action_install_symbol)
|
||||||
|
nameop = newSVOP(OP_CONST, 0, SvREFCNT_inc(ctx.name));
|
||||||
|
|
||||||
|
if(!nameop && action_install_symbol)
|
||||||
|
warn("Setting XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL without _ACTION_SET_CVNAME is nonsensical");
|
||||||
|
|
||||||
|
ctx.cv = newATTRSUB(floor_ix, nameop, NULL, ctx.attrs, ctx.body);
|
||||||
|
|
||||||
|
if(!nameop && action_set_cvname) {
|
||||||
|
#if HAVE_PERL_VERSION(5,22,0)
|
||||||
|
STRLEN namelen;
|
||||||
|
const char *name = SvPV_const(ctx.name, namelen);
|
||||||
|
U32 hash;
|
||||||
|
PERL_HASH(hash, name, namelen);
|
||||||
|
|
||||||
|
/* Core's CvNAME_HEK_set macro uses unshare_hek() which isn't exposed. But we
|
||||||
|
* likely don't need it here */
|
||||||
|
#ifndef unshare_hek
|
||||||
|
# define unshare_hek(h) (void)0
|
||||||
|
#endif
|
||||||
|
assert(!CvNAME_HEK(ctx.cv));
|
||||||
|
|
||||||
|
CvNAME_HEK_set(ctx.cv,
|
||||||
|
share_hek(name, SvUTF8(ctx.name) ? -namelen : namelen, hash));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.attrs = NULL;
|
||||||
|
ctx.body = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
FOREACH_HOOKS_FORWARD {
|
||||||
|
if(hooks->post_newcv)
|
||||||
|
(*hooks->post_newcv)(aTHX_ &ctx, hookdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(PL_scopestack_ix == was_scopestack_ix);
|
||||||
|
LEAVE_with_name("parse_sublike");
|
||||||
|
|
||||||
|
if(!have_dynamic_actions) {
|
||||||
|
if(!ctx.name)
|
||||||
|
ctx.actions |= XS_PARSE_SUBLIKE_ACTION_REFGEN_ANONCODE;
|
||||||
|
else
|
||||||
|
ctx.actions &= ~XS_PARSE_SUBLIKE_ACTION_REFGEN_ANONCODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!(ctx.actions & XS_PARSE_SUBLIKE_ACTION_REFGEN_ANONCODE)) {
|
||||||
|
*op_ptr = newOP(OP_NULL, 0);
|
||||||
|
|
||||||
|
SvREFCNT_dec(ctx.name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
*op_ptr = newUNOP(OP_REFGEN, 0,
|
||||||
|
newSVOP(OP_ANONCODE, 0, (SV *)ctx.cv));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!have_dynamic_actions) {
|
||||||
|
if(!ctx.name)
|
||||||
|
ctx.actions |= XS_PARSE_SUBLIKE_ACTION_RET_EXPR;
|
||||||
|
else
|
||||||
|
ctx.actions &= ~XS_PARSE_SUBLIKE_ACTION_RET_EXPR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ctx.actions & XS_PARSE_SUBLIKE_ACTION_RET_EXPR) ? KEYWORD_PLUGIN_EXPR : KEYWORD_PLUGIN_STMT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int IMPL_xs_parse_sublike_v4(pTHX_ const struct XSParseSublikeHooks *hooks, void *hookdata, OP **op_ptr)
|
||||||
|
{
|
||||||
|
struct HooksAndData hd = { .hooks = hooks, .data = hookdata };
|
||||||
|
return parse(aTHX_ &hd, 1, op_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int IMPL_xs_parse_sublike_v3(pTHX_ const void *hooks, void *hookdata, OP **op_ptr)
|
||||||
|
{
|
||||||
|
croak("XS::Parse::Sublike ABI v3 is no longer supported; the caller should be rebuilt to use v4");
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Registration;
|
||||||
|
struct Registration {
|
||||||
|
int ver;
|
||||||
|
struct Registration *next;
|
||||||
|
const char *kw;
|
||||||
|
STRLEN kwlen;
|
||||||
|
union {
|
||||||
|
const struct XSParseSublikeHooks *hooks;
|
||||||
|
};
|
||||||
|
void *hookdata;
|
||||||
|
|
||||||
|
STRLEN permit_hintkey_len;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define REGISTRATIONS_LOCK OP_CHECK_MUTEX_LOCK
|
||||||
|
#define REGISTRATIONS_UNLOCK OP_CHECK_MUTEX_UNLOCK
|
||||||
|
|
||||||
|
static struct Registration *registrations;
|
||||||
|
|
||||||
|
static void register_sublike(pTHX_ const char *kw, const void *hooks, void *hookdata, int ver)
|
||||||
|
{
|
||||||
|
struct Registration *reg;
|
||||||
|
Newx(reg, 1, struct Registration);
|
||||||
|
|
||||||
|
reg->kw = savepv(kw);
|
||||||
|
reg->kwlen = strlen(kw);
|
||||||
|
reg->ver = ver;
|
||||||
|
reg->hooks = hooks;
|
||||||
|
reg->hookdata = hookdata;
|
||||||
|
|
||||||
|
if(reg->ver >= 4 && reg->hooks->permit_hintkey)
|
||||||
|
reg->permit_hintkey_len = strlen(reg->hooks->permit_hintkey);
|
||||||
|
else
|
||||||
|
reg->permit_hintkey_len = 0;
|
||||||
|
|
||||||
|
REGISTRATIONS_LOCK;
|
||||||
|
{
|
||||||
|
reg->next = registrations;
|
||||||
|
registrations = reg;
|
||||||
|
}
|
||||||
|
REGISTRATIONS_UNLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void IMPL_register_xs_parse_sublike_v4(pTHX_ const char *kw, const struct XSParseSublikeHooks *hooks, void *hookdata)
|
||||||
|
{
|
||||||
|
register_sublike(aTHX_ kw, hooks, hookdata, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void IMPL_register_xs_parse_sublike_v3(pTHX_ const char *kw, const void *hooks, void *hookdata)
|
||||||
|
{
|
||||||
|
croak("XS::Parse::Sublike ABI v3 is no longer supported; the caller should be rebuilt to use v4");
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct Registration *find_permitted(pTHX_ const char *kw, STRLEN kwlen)
|
||||||
|
{
|
||||||
|
const struct Registration *reg;
|
||||||
|
|
||||||
|
HV *hints = GvHV(PL_hintgv);
|
||||||
|
|
||||||
|
for(reg = registrations; reg; reg = reg->next) {
|
||||||
|
if(reg->kwlen != kwlen || !strEQ(reg->kw, kw))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(reg->hooks->permit_hintkey &&
|
||||||
|
(!hints || !hv_fetch(hints, reg->hooks->permit_hintkey, reg->permit_hintkey_len, 0)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(reg->hooks->permit &&
|
||||||
|
!(*reg->hooks->permit)(aTHX_ reg->hookdata))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
return reg;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int IMPL_xs_parse_sublike_any_v4(pTHX_ const struct XSParseSublikeHooks *hooksA, void *hookdataA, OP **op_ptr)
|
||||||
|
{
|
||||||
|
SV *kwsv = lex_scan_ident();
|
||||||
|
if(!kwsv || !SvCUR(kwsv))
|
||||||
|
croak("Expected a keyword to introduce a sub or sub-like construction");
|
||||||
|
|
||||||
|
const char *kw = SvPV_nolen(kwsv);
|
||||||
|
STRLEN kwlen = SvCUR(kwsv);
|
||||||
|
|
||||||
|
lex_read_space(0);
|
||||||
|
|
||||||
|
const struct Registration *reg = NULL;
|
||||||
|
/* We permit 'sub' as a NULL set of hooks; anything else should be a registered keyword */
|
||||||
|
if(kwlen != 3 || !strEQ(kw, "sub")) {
|
||||||
|
reg = find_permitted(aTHX_ kw, kwlen);
|
||||||
|
if(!reg)
|
||||||
|
croak("Expected a keyword to introduce a sub or sub-like construction, found \"%.*s\"",
|
||||||
|
kwlen, kw);
|
||||||
|
}
|
||||||
|
|
||||||
|
SvREFCNT_dec(kwsv);
|
||||||
|
|
||||||
|
struct HooksAndData hd[] = {
|
||||||
|
{ .hooks = hooksA, .data = hookdataA },
|
||||||
|
{ 0 }
|
||||||
|
};
|
||||||
|
struct XSParseSublikeHooks hooks;
|
||||||
|
|
||||||
|
if(reg) {
|
||||||
|
hd[1].hooks = reg->hooks;
|
||||||
|
hd[1].data = reg->hookdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parse(aTHX_ hd, 1 + !!reg, op_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int IMPL_xs_parse_sublike_any_v3(pTHX_ const void *hooksA, void *hookdataA, OP **op_ptr)
|
||||||
|
{
|
||||||
|
croak("XS::Parse::Sublike ABI v3 is no longer supported; the caller should be rebuilt to use v4");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int (*next_keyword_plugin)(pTHX_ char *, STRLEN, OP **);
|
||||||
|
|
||||||
|
static int my_keyword_plugin(pTHX_ char *kw, STRLEN kwlen, OP **op_ptr)
|
||||||
|
{
|
||||||
|
const struct Registration *reg = find_permitted(aTHX_ kw, kwlen);
|
||||||
|
|
||||||
|
if(!reg)
|
||||||
|
return (*next_keyword_plugin)(aTHX_ kw, kwlen, op_ptr);
|
||||||
|
|
||||||
|
lex_read_space(0);
|
||||||
|
|
||||||
|
/* We'll abuse the SvPVX storage of an SV to keep an array of HooksAndData
|
||||||
|
* structures
|
||||||
|
*/
|
||||||
|
SV *hdlsv = newSV(4 * sizeof(struct HooksAndData));
|
||||||
|
SAVEFREESV(hdlsv);
|
||||||
|
struct HooksAndData *hd = (struct HooksAndData *)SvPVX(hdlsv);
|
||||||
|
size_t nhooks = 1;
|
||||||
|
|
||||||
|
struct XSParseSublikeHooks *hooks = (struct XSParseSublikeHooks *)reg->hooks;
|
||||||
|
hd[0].hooks = hooks;
|
||||||
|
hd[0].data = reg->hookdata;
|
||||||
|
|
||||||
|
while(hooks->flags & XS_PARSE_SUBLIKE_FLAG_PREFIX) {
|
||||||
|
/* After a prefixing keyword, expect another one */
|
||||||
|
SV *kwsv = lex_scan_ident();
|
||||||
|
SAVEFREESV(kwsv);
|
||||||
|
|
||||||
|
if(!kwsv || !SvCUR(kwsv))
|
||||||
|
croak("Expected a keyword to introduce a sub or sub-like construction");
|
||||||
|
|
||||||
|
kw = SvPV_nolen(kwsv);
|
||||||
|
kwlen = SvCUR(kwsv);
|
||||||
|
|
||||||
|
lex_read_space(0);
|
||||||
|
|
||||||
|
/* We permit 'sub' as a NULL set of hooks; anything else should be a registered keyword */
|
||||||
|
if(kwlen == 3 && strEQ(kw, "sub"))
|
||||||
|
break;
|
||||||
|
|
||||||
|
reg = find_permitted(aTHX_ kw, kwlen);
|
||||||
|
if(!reg)
|
||||||
|
croak("Expected a keyword to introduce a sub or sub-like construction, found \"%.*s\"",
|
||||||
|
kwlen, kw);
|
||||||
|
|
||||||
|
hooks = (struct XSParseSublikeHooks *)reg->hooks;
|
||||||
|
|
||||||
|
if(SvLEN(hdlsv) < (nhooks + 1) * sizeof(struct HooksAndData)) {
|
||||||
|
SvGROW(hdlsv, SvLEN(hdlsv) * 2);
|
||||||
|
hd = (struct HooksAndData *)SvPVX(hdlsv);
|
||||||
|
}
|
||||||
|
hd[nhooks].hooks = hooks;
|
||||||
|
hd[nhooks].data = reg->hookdata;
|
||||||
|
nhooks++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parse(aTHX_ hd, nhooks, op_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
MODULE = XS::Parse::Sublike PACKAGE = XS::Parse::Sublike
|
||||||
|
|
||||||
|
BOOT:
|
||||||
|
/* Legacy lookup mechanism using perl symbol table */
|
||||||
|
sv_setiv(get_sv("XS::Parse::Sublike::ABIVERSION", GV_ADDMULTI), 4);
|
||||||
|
sv_setuv(get_sv("XS::Parse::Sublike::PARSE", GV_ADDMULTI), PTR2UV(&IMPL_xs_parse_sublike_v3));
|
||||||
|
sv_setuv(get_sv("XS::Parse::Sublike::REGISTER", GV_ADDMULTI), PTR2UV(&IMPL_register_xs_parse_sublike_v3));
|
||||||
|
sv_setuv(get_sv("XS::Parse::Sublike::PARSEANY", GV_ADDMULTI), PTR2UV(&IMPL_xs_parse_sublike_any_v3));
|
||||||
|
|
||||||
|
/* 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_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/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));
|
||||||
|
|
||||||
|
wrap_keyword_plugin(&my_keyword_plugin, &next_keyword_plugin);
|
|
@ -0,0 +1,208 @@
|
||||||
|
# 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, 2020-2021 -- leonerd@leonerd.org.uk
|
||||||
|
|
||||||
|
package XS::Parse::Sublike::Builder 0.16;
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
C<XS::Parse::Sublike::Builder> - build-time support for C<XS::Parse::Sublike>
|
||||||
|
|
||||||
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
|
In F<Build.PL>:
|
||||||
|
|
||||||
|
use XS::Parse::Sublike::Builder;
|
||||||
|
|
||||||
|
my $build = Module::Build->new(
|
||||||
|
...,
|
||||||
|
configure_requires => {
|
||||||
|
...
|
||||||
|
'XS::Parse::Sublike::Builder' => 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
XS::Parse::Sublike::Builder->extend_module_build( $build );
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
This module provides a build-time helper to assist authors writing XS modules
|
||||||
|
that use L<XS::Parse::Sublike>. It prepares a L<Module::Build>-using
|
||||||
|
distribution to be able to make use of C<XS::Parse::Sublike>.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
require XS::Parse::Sublike::Builder_data;
|
||||||
|
|
||||||
|
=head1 FUNCTIONS
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
=head2 write_XSParseSublike_h
|
||||||
|
|
||||||
|
XS::Parse::Sublike::Builder->write_XSParseSublike_h
|
||||||
|
|
||||||
|
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>.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub write_XSParseSublike_h
|
||||||
|
{
|
||||||
|
shift;
|
||||||
|
|
||||||
|
open my $out, ">", "XSParseSublike.h" or
|
||||||
|
die "Cannot open XSParseSublike.h for writing - $!\n";
|
||||||
|
|
||||||
|
$out->print( XS::Parse::Sublike::Builder_data->XSPARSESUBLIKE_H );
|
||||||
|
}
|
||||||
|
|
||||||
|
=head2 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
|
||||||
|
compiler invocation. This enables the C compiler to find the
|
||||||
|
F<XSParseSublike.h> file.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub extra_compiler_flags
|
||||||
|
{
|
||||||
|
shift;
|
||||||
|
return "-I.";
|
||||||
|
}
|
||||||
|
|
||||||
|
=head2 extend_module_build
|
||||||
|
|
||||||
|
XS::Parse::Sublike::Builder->extend_module_build( $build )
|
||||||
|
|
||||||
|
A convenient shortcut for performing all the tasks necessary to make a
|
||||||
|
L<Module::Build>-based distribution use the helper.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub extend_module_build
|
||||||
|
{
|
||||||
|
my $self = shift;
|
||||||
|
my ( $build ) = @_;
|
||||||
|
|
||||||
|
eval { $self->write_XSParseSublike_h } or do {
|
||||||
|
warn $@;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
# preserve existing flags
|
||||||
|
my @flags = @{ $build->extra_compiler_flags };
|
||||||
|
push @flags, $self->extra_compiler_flags;
|
||||||
|
|
||||||
|
$build->extra_compiler_flags( @flags );
|
||||||
|
}
|
||||||
|
|
||||||
|
=head1 AUTHOR
|
||||||
|
|
||||||
|
Paul Evans <leonerd@leonerd.org.uk>
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
0x55AA;
|
||||||
|
|
||||||
|
__DATA__
|
||||||
|
#ifndef __XS_PARSE_SUBLIKE_H__
|
||||||
|
#define __XS_PARSE_SUBLIKE_H__
|
||||||
|
|
||||||
|
#define XSPARSESUBLIKE_ABI_VERSION 3
|
||||||
|
|
||||||
|
struct XSParseSublikeContext {
|
||||||
|
SV *name; /* may be NULL for anon subs */
|
||||||
|
/* STAGE pre_subparse */
|
||||||
|
OP *attrs; /* may be NULL */
|
||||||
|
/* STAGE post_blockstart */
|
||||||
|
OP *body;
|
||||||
|
/* STAGE pre_blockend */
|
||||||
|
CV *cv;
|
||||||
|
/* STAGE post_newcv */
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
XS_PARSE_SUBLIKE_FLAG_FILTERATTRS = 1<<0,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
XS_PARSE_SUBLIKE_PART_NAME = 1<<0,
|
||||||
|
XS_PARSE_SUBLIKE_PART_ATTRS = 1<<1,
|
||||||
|
XS_PARSE_SUBLIKE_PART_SIGNATURE = 1<<2,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct XSParseSublikeHooks {
|
||||||
|
U16 flags;
|
||||||
|
U8 require_parts;
|
||||||
|
U8 skip_parts;
|
||||||
|
bool (*permit) (pTHX_ void *hookdata);
|
||||||
|
void (*pre_subparse) (pTHX_ struct XSParseSublikeContext *ctx, void *hookdata);
|
||||||
|
void (*post_blockstart)(pTHX_ struct XSParseSublikeContext *ctx, void *hookdata);
|
||||||
|
void (*pre_blockend) (pTHX_ struct XSParseSublikeContext *ctx, void *hookdata);
|
||||||
|
void (*post_newcv) (pTHX_ struct XSParseSublikeContext *ctx, void *hookdata);
|
||||||
|
|
||||||
|
/* if flags & XS_PARSE_SUBLIKE_FLAG_FILTERATTRS */
|
||||||
|
bool (*filter_attr) (pTHX_ struct XSParseSublikeContext *ctx, SV *attr, SV *val, void *hookdata);
|
||||||
|
};
|
||||||
|
|
||||||
|
static int (*parse_xs_parse_sublike_func)(pTHX_ const struct XSParseSublikeHooks *hooks, void *hookdata, OP **op_ptr);
|
||||||
|
#define xs_parse_sublike(hooks, hookdata, op_ptr) S_xs_parse_sublike(aTHX_ hooks, hookdata, op_ptr)
|
||||||
|
static int S_xs_parse_sublike(pTHX_ const struct XSParseSublikeHooks *hooks, void *hookdata, OP **op_ptr)
|
||||||
|
{
|
||||||
|
if(!parse_xs_parse_sublike_func)
|
||||||
|
croak("Must call boot_xs_parse_sublike() first");
|
||||||
|
|
||||||
|
return (*parse_xs_parse_sublike_func)(aTHX_ hooks, hookdata, op_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void (*register_xs_parse_sublike_func)(pTHX_ const char *kw, const struct XSParseSublikeHooks *hooks, void *hookdata);
|
||||||
|
#define register_xs_parse_sublike(kw, hooks, hookdata) S_register_xs_parse_sublike(aTHX_ kw, hooks, hookdata)
|
||||||
|
static void S_register_xs_parse_sublike(pTHX_ const char *kw, const struct XSParseSublikeHooks *hooks, void *hookdata)
|
||||||
|
{
|
||||||
|
if(!register_xs_parse_sublike_func)
|
||||||
|
croak("Must call boot_xs_parse_sublike() first");
|
||||||
|
|
||||||
|
return (*register_xs_parse_sublike_func)(aTHX_ kw, hooks, hookdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int (*parseany_xs_parse_sublike_func)(pTHX_ const struct XSParseSublikeHooks *hooks, void *hookdata, OP **op_ptr);
|
||||||
|
#define xs_parse_sublike_any(hooks, hookdata, op_ptr) S_xs_parse_sublike_any(aTHX_ hooks, hookdata, op_ptr)
|
||||||
|
static int S_xs_parse_sublike_any(pTHX_ const struct XSParseSublikeHooks *hooks, void *hookdata, OP **op_ptr)
|
||||||
|
{
|
||||||
|
if(!parseany_xs_parse_sublike_func)
|
||||||
|
croak("Must call boot_xs_parse_sublike() first");
|
||||||
|
|
||||||
|
return (*parseany_xs_parse_sublike_func)(aTHX_ hooks, hookdata, op_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define boot_xs_parse_sublike(ver) S_boot_xs_parse_sublike(aTHX_ ver)
|
||||||
|
static void S_boot_xs_parse_sublike(pTHX_ double ver) {
|
||||||
|
SV *versv = ver ? newSVnv(ver) : NULL;
|
||||||
|
|
||||||
|
load_module(PERL_LOADMOD_NOIMPORT, newSVpvs("XS::Parse::Sublike"), versv, NULL);
|
||||||
|
|
||||||
|
int abi_version = SvIV(get_sv("XS::Parse::Sublike::ABIVERSION", 0));
|
||||||
|
if(abi_version != XSPARSESUBLIKE_ABI_VERSION)
|
||||||
|
croak("XS::Parse::Sublike ABI version mismatch - library provides %d, compiled for %d",
|
||||||
|
abi_version, XSPARSESUBLIKE_ABI_VERSION);
|
||||||
|
|
||||||
|
parse_xs_parse_sublike_func = INT2PTR(int (*)(pTHX_ const struct XSParseSublikeHooks *, void *, OP**),
|
||||||
|
SvUV(get_sv("XS::Parse::Sublike::PARSE", 0)));
|
||||||
|
|
||||||
|
register_xs_parse_sublike_func = INT2PTR(void (*)(pTHX_ const char *, const struct XSParseSublikeHooks *, void *),
|
||||||
|
SvUV(get_sv("XS::Parse::Sublike::REGISTER", 0)));
|
||||||
|
|
||||||
|
parseany_xs_parse_sublike_func = INT2PTR(int (*)(pTHX_ const struct XSParseSublikeHooks *, void *, OP**),
|
||||||
|
SvUV(get_sv("XS::Parse::Sublike::PARSEANY", 0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,39 @@
|
||||||
|
# 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, 2021 -- leonerd@leonerd.org.uk
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use B qw( perlstring );
|
||||||
|
use Module::Build;
|
||||||
|
|
||||||
|
open my $outh, ">", $ARGV[0] or
|
||||||
|
die "Cannot write $ARGV[0] - $!\n";
|
||||||
|
|
||||||
|
local $/;
|
||||||
|
|
||||||
|
$outh->print( scalar do { <DATA> } );
|
||||||
|
|
||||||
|
$outh->print( scalar do {
|
||||||
|
open my $in_h, "<", "XSParseSublike.h" or
|
||||||
|
die "Cannot open XSParseSublike.h - $!";
|
||||||
|
<$in_h> } );
|
||||||
|
|
||||||
|
__DATA__
|
||||||
|
package XS::Parse::Sublike::Builder_data 0.16;
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
# The contents of the "XSParseSublike.h" file
|
||||||
|
my $XSParseSublike_h = do {
|
||||||
|
local $/;
|
||||||
|
readline DATA;
|
||||||
|
};
|
||||||
|
sub XSPARSESUBLIKE_H() { $XSParseSublike_h }
|
||||||
|
|
||||||
|
0x55AA;
|
||||||
|
|
||||||
|
__DATA__
|
|
@ -0,0 +1,10 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
|
use_ok( "XS::Parse::Sublike" );
|
||||||
|
|
||||||
|
done_testing;
|
|
@ -0,0 +1,43 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
|
use lib "t";
|
||||||
|
use testcase "t::func";
|
||||||
|
|
||||||
|
# named func
|
||||||
|
{
|
||||||
|
func example { return 123; }
|
||||||
|
|
||||||
|
is( example(), 123, 'named func' );
|
||||||
|
}
|
||||||
|
|
||||||
|
# anon func
|
||||||
|
{
|
||||||
|
my $ex2 = func { return 456; };
|
||||||
|
|
||||||
|
is( $ex2->(), 456, 'anon func' );
|
||||||
|
}
|
||||||
|
|
||||||
|
# func still obtains :ATTRS
|
||||||
|
{
|
||||||
|
my $modify_invoked;
|
||||||
|
|
||||||
|
sub MODIFY_CODE_ATTRIBUTES
|
||||||
|
{
|
||||||
|
my ( $pkg, $sub, $attr ) = @_;
|
||||||
|
$modify_invoked++;
|
||||||
|
Test::More::is( $attr, "MyCustomAttribute(value here)",
|
||||||
|
'MODIFY_CODE_ATTRIBUTES takes attribute' );
|
||||||
|
|
||||||
|
return ();
|
||||||
|
}
|
||||||
|
|
||||||
|
func withattr :MyCustomAttribute(value here) { }
|
||||||
|
is( $modify_invoked, 1, 'MODIFY_CODE_ATTRIBUTES invoked' );
|
||||||
|
}
|
||||||
|
|
||||||
|
done_testing;
|
|
@ -0,0 +1,73 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
BEGIN {
|
||||||
|
$] >= 5.026000 or plan skip_all => "No parse_subsignature()";
|
||||||
|
}
|
||||||
|
use Test::Fatal;
|
||||||
|
|
||||||
|
use feature 'signatures';
|
||||||
|
no warnings 'experimental';
|
||||||
|
|
||||||
|
use lib "t";
|
||||||
|
use testcase "t::func";
|
||||||
|
|
||||||
|
# basic sig
|
||||||
|
{
|
||||||
|
func withparam($x) { return $x + 1 }
|
||||||
|
|
||||||
|
is( withparam( 4 ), 5, 'func with param' );
|
||||||
|
}
|
||||||
|
|
||||||
|
# The following are additional tests that our pre-5.31.3 backported
|
||||||
|
# parse_subsignature() works correctly
|
||||||
|
{
|
||||||
|
func sum(@x) {
|
||||||
|
my $ret = 0;
|
||||||
|
$ret += $_ for @x;
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
is( sum( 10, 20, 30 ), 60, 'func with slurpy parameter' );
|
||||||
|
|
||||||
|
func firstandthird($x, $, $z) {
|
||||||
|
return $x . $z;
|
||||||
|
}
|
||||||
|
|
||||||
|
is( firstandthird(qw( a b c )), "ac", 'func with unnamed parameter' );
|
||||||
|
|
||||||
|
func withoptparam($one = 1) { return $one + 2 }
|
||||||
|
|
||||||
|
is( withoptparam, 3, 'func with optional param missing' );
|
||||||
|
is( withoptparam( 2 ), 4, 'func with optional param present' );
|
||||||
|
|
||||||
|
func has_whitespace (
|
||||||
|
$x
|
||||||
|
) {
|
||||||
|
return $x;
|
||||||
|
}
|
||||||
|
|
||||||
|
is( has_whitespace( "value" ), "value", 'func with whitespace in signature' );
|
||||||
|
|
||||||
|
# RT132284
|
||||||
|
func noparams() { return "constant" }
|
||||||
|
|
||||||
|
is( noparams, "constant", 'func with no params' );
|
||||||
|
like( exception { noparams( 1, 2, 3 ) },
|
||||||
|
# message was extended somewhere in perl 5.33
|
||||||
|
qr/^Too many arguments for subroutine 'main::noparams' (\(.*\) )?at /,
|
||||||
|
'Exception thrown from empty signature validation failure' );
|
||||||
|
}
|
||||||
|
|
||||||
|
# RT131571
|
||||||
|
{
|
||||||
|
func withattr :method ($self, @args) { }
|
||||||
|
|
||||||
|
ok( scalar( grep { m/^method$/ } attributes::get( \&withattr ) ),
|
||||||
|
'func with attr and signture does not collide' );
|
||||||
|
}
|
||||||
|
|
||||||
|
done_testing;
|
|
@ -0,0 +1,54 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
BEGIN {
|
||||||
|
$] >= 5.026000 or plan skip_all => "No parse_subsignature()";
|
||||||
|
}
|
||||||
|
|
||||||
|
use feature 'signatures';
|
||||||
|
no warnings 'experimental';
|
||||||
|
|
||||||
|
use lib "t";
|
||||||
|
use testcase "t::func";
|
||||||
|
|
||||||
|
use B::Deparse;
|
||||||
|
|
||||||
|
my $deparser = B::Deparse->new();
|
||||||
|
|
||||||
|
# check that signatured functions deparse the right way
|
||||||
|
# (RT132335)
|
||||||
|
|
||||||
|
# with signature
|
||||||
|
{
|
||||||
|
my $sub = sub ($x, $y) { return $x + $y; };
|
||||||
|
my $func = func ($x, $y) { return $x + $y; };
|
||||||
|
|
||||||
|
my $code = $deparser->coderef2text( $sub ); # the reference source text
|
||||||
|
is( $deparser->coderef2text( $func ), $code,
|
||||||
|
'Deparsed func with signature identical to deparsed code' );
|
||||||
|
}
|
||||||
|
|
||||||
|
# empty signature
|
||||||
|
{
|
||||||
|
my $sub = sub () { return 123; };
|
||||||
|
my $func = func () { return 123; };
|
||||||
|
|
||||||
|
my $code = $deparser->coderef2text( $sub ); # the reference source text
|
||||||
|
is( $deparser->coderef2text( $func ), $code,
|
||||||
|
'Deparsed func with empty signature identical to deparsed code' );
|
||||||
|
}
|
||||||
|
|
||||||
|
# empty body
|
||||||
|
{
|
||||||
|
my $sub = sub () {};
|
||||||
|
my $func = func () {};
|
||||||
|
|
||||||
|
my $code = $deparser->coderef2text( $sub ); # the reference source text
|
||||||
|
is( $deparser->coderef2text( $func ), $code,
|
||||||
|
'Deparsed func with empty body identical to deparsed code' );
|
||||||
|
}
|
||||||
|
|
||||||
|
done_testing;
|
|
@ -0,0 +1,33 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
|
use lib "t";
|
||||||
|
use testcase "t::stages";
|
||||||
|
|
||||||
|
sub stages { return $_[0] }
|
||||||
|
|
||||||
|
# not permitted
|
||||||
|
{
|
||||||
|
my $ret = stages { one => "one" };
|
||||||
|
|
||||||
|
is_deeply( $ret, { one => "one" },
|
||||||
|
'not permitted keyword falls through to regular symbol lookup' );
|
||||||
|
}
|
||||||
|
|
||||||
|
# permitted
|
||||||
|
{
|
||||||
|
BEGIN { $^H{"t::stages/permit"} = 1; }
|
||||||
|
|
||||||
|
my $ret = stages { two => "two" };
|
||||||
|
|
||||||
|
is( ref $ret, "CODE",
|
||||||
|
'permitted keyword becomes a CODE ref' );
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO: test that it can throw
|
||||||
|
|
||||||
|
done_testing;
|
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
|
use lib "t";
|
||||||
|
use testcase "t::stages";
|
||||||
|
|
||||||
|
our $VAR;
|
||||||
|
BEGIN { $^H{"t::stages/permit"} = 1; }
|
||||||
|
|
||||||
|
{
|
||||||
|
BEGIN { $^H{'t::stages/pre_subparse-capture'} = 1; }
|
||||||
|
|
||||||
|
BEGIN { $VAR = "before" }
|
||||||
|
stages capture {
|
||||||
|
BEGIN { $VAR = "inside" }
|
||||||
|
}
|
||||||
|
|
||||||
|
is( $t::stages::captured, "before",
|
||||||
|
'captured value of $VAR before block' );
|
||||||
|
}
|
||||||
|
|
||||||
|
done_testing;
|
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
|
use lib "t";
|
||||||
|
use testcase "t::stages";
|
||||||
|
|
||||||
|
our $VAR;
|
||||||
|
BEGIN { $^H{"t::stages/permit"} = 1; }
|
||||||
|
|
||||||
|
{
|
||||||
|
BEGIN { $^H{'t::stages/post_blockstart-capture'} = 1; }
|
||||||
|
|
||||||
|
BEGIN { $VAR = "before" }
|
||||||
|
stages capture {
|
||||||
|
BEGIN { $VAR = "inside" }
|
||||||
|
}
|
||||||
|
|
||||||
|
is( $t::stages::captured, "before",
|
||||||
|
'captured value of $VAR before block' );
|
||||||
|
}
|
||||||
|
|
||||||
|
done_testing;
|
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
|
use lib "t";
|
||||||
|
use testcase "t::stages";
|
||||||
|
|
||||||
|
our $VAR;
|
||||||
|
BEGIN { $^H{"t::stages/permit"} = 1; }
|
||||||
|
|
||||||
|
{
|
||||||
|
BEGIN { $^H{'t::stages/pre_blockend-capture'} = 1; }
|
||||||
|
|
||||||
|
BEGIN { $VAR = "before" }
|
||||||
|
stages capture {
|
||||||
|
BEGIN { $VAR = "inside" }
|
||||||
|
}
|
||||||
|
|
||||||
|
is( $t::stages::captured, "inside",
|
||||||
|
'captured value of $VAR inside block' );
|
||||||
|
}
|
||||||
|
|
||||||
|
done_testing;
|
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
|
use lib "t";
|
||||||
|
use testcase "t::stages";
|
||||||
|
|
||||||
|
BEGIN { $^H{"t::stages/permit"} = 1; }
|
||||||
|
|
||||||
|
{
|
||||||
|
BEGIN { $^H{'t::stages/post_newcv-capture-cv'} = 1; }
|
||||||
|
|
||||||
|
stages capture { }
|
||||||
|
|
||||||
|
is( $t::stages::captured, \&capture,
|
||||||
|
'captured value of new CV' );
|
||||||
|
}
|
||||||
|
|
||||||
|
done_testing;
|
|
@ -0,0 +1,23 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
|
use lib "t";
|
||||||
|
use testcase "t::stages";
|
||||||
|
|
||||||
|
our $VAR;
|
||||||
|
BEGIN { $^H{"t::stages/permit"} = 1; }
|
||||||
|
|
||||||
|
{
|
||||||
|
BEGIN { $^H{'t::stages/filter_attr-capture'} = 1; }
|
||||||
|
|
||||||
|
stages withattr :atname(atvalue) { }
|
||||||
|
|
||||||
|
is_deeply( $t::stages::captured, [ atname => "atvalue" ],
|
||||||
|
'captured an attribute by attr filter' );
|
||||||
|
}
|
||||||
|
|
||||||
|
done_testing;
|
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
|
use lib "t";
|
||||||
|
use testcase "t::registrations";
|
||||||
|
|
||||||
|
# Check the individual `func` registrations do not clash
|
||||||
|
{
|
||||||
|
{
|
||||||
|
BEGIN { $^H{"t::registrations/red"} = 1 }
|
||||||
|
func returns_red { }
|
||||||
|
}
|
||||||
|
{
|
||||||
|
BEGIN { $^H{"t::registrations/blue"} = 1 }
|
||||||
|
func returns_blue { }
|
||||||
|
}
|
||||||
|
|
||||||
|
is( returns_red(), "red", 'returns red' );
|
||||||
|
is( returns_blue(), "blue", 'returns blue' );
|
||||||
|
}
|
||||||
|
|
||||||
|
done_testing;
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
|
use lib "t";
|
||||||
|
use testcase "t::any";
|
||||||
|
|
||||||
|
our $LOG; BEGIN { $LOG = "" };
|
||||||
|
|
||||||
|
prefixed func example {
|
||||||
|
BEGIN { $LOG .= "B" }
|
||||||
|
}
|
||||||
|
|
||||||
|
is( $LOG, "SpSfEpEfBLfLpNpNf", 'stages run in correct order' );
|
||||||
|
|
||||||
|
done_testing;
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
|
use lib "t";
|
||||||
|
use testcase "t::prefix";
|
||||||
|
|
||||||
|
our $LOG; BEGIN { $LOG = "" };
|
||||||
|
|
||||||
|
prefixed func example {
|
||||||
|
BEGIN { $LOG .= "B" }
|
||||||
|
}
|
||||||
|
|
||||||
|
is( $LOG, "SpSfEpEfBLfLpNpNf", 'stages run in correct order' );
|
||||||
|
|
||||||
|
done_testing;
|
|
@ -0,0 +1,35 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
|
use lib "t";
|
||||||
|
use testcase "t::parts";
|
||||||
|
|
||||||
|
# name optional
|
||||||
|
{
|
||||||
|
parts NAME { }
|
||||||
|
|
||||||
|
my $code = parts { };
|
||||||
|
is( ref $code, "CODE", '$code is CODEref' );
|
||||||
|
}
|
||||||
|
|
||||||
|
# name required
|
||||||
|
{
|
||||||
|
BEGIN { $^H{"t::parts/require-name"} = 1; }
|
||||||
|
|
||||||
|
ok( !defined eval 'my $code = parts { };',
|
||||||
|
'anon func fails to parse when name required' );
|
||||||
|
}
|
||||||
|
|
||||||
|
# name skipped
|
||||||
|
{
|
||||||
|
BEGIN { $^H{"t::parts/skip-name"} = 1; }
|
||||||
|
|
||||||
|
ok( !defined eval 'parts OTHERNAME { }; 1',
|
||||||
|
'named func fails to parse when name skipped' );
|
||||||
|
}
|
||||||
|
|
||||||
|
done_testing;
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
|
use lib "t";
|
||||||
|
use testcase "t::parts";
|
||||||
|
|
||||||
|
# attrs optional
|
||||||
|
{
|
||||||
|
parts NAME { }
|
||||||
|
|
||||||
|
parts ANAME :method { }
|
||||||
|
|
||||||
|
pass( 'Optional attributes permitted but not required' );
|
||||||
|
}
|
||||||
|
|
||||||
|
# attrs skipped
|
||||||
|
{
|
||||||
|
BEGIN { $^H{"t::parts/skip-attrs"} = 1; }
|
||||||
|
|
||||||
|
ok( !defined eval 'parts OTHERNAME :method { }; 1',
|
||||||
|
'func with attrs to parse when attrs skipped' );
|
||||||
|
}
|
||||||
|
|
||||||
|
done_testing;
|
|
@ -0,0 +1,34 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
BEGIN {
|
||||||
|
$] >= 5.026000 or plan skip_all => "No parse_subsignature()";
|
||||||
|
}
|
||||||
|
|
||||||
|
use feature 'signatures';
|
||||||
|
no warnings 'experimental';
|
||||||
|
|
||||||
|
use lib "t";
|
||||||
|
use testcase "t::parts";
|
||||||
|
|
||||||
|
# signature optional
|
||||||
|
{
|
||||||
|
parts NAME { }
|
||||||
|
|
||||||
|
parts ANAME ($x) { }
|
||||||
|
|
||||||
|
pass( 'Optional attributes permitted but not required' );
|
||||||
|
}
|
||||||
|
|
||||||
|
# signature skipped
|
||||||
|
{
|
||||||
|
BEGIN { $^H{"t::parts/skip-signature"} = 1; }
|
||||||
|
|
||||||
|
ok( !defined eval 'parts OTHERNAME ($x) { }; 1',
|
||||||
|
'func with signature to parse when signature skipped' );
|
||||||
|
}
|
||||||
|
|
||||||
|
done_testing;
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
|
use lib "t";
|
||||||
|
use testcase "t::flags";
|
||||||
|
|
||||||
|
BEGIN { $^H{"t::flags/no_body"} = 1 }
|
||||||
|
|
||||||
|
{
|
||||||
|
no_body justaname;
|
||||||
|
my $name; BEGIN { $name = $t::flags::captured_name; }
|
||||||
|
is( $name, "justaname", 'no_body saw just the function name' );
|
||||||
|
}
|
||||||
|
|
||||||
|
done_testing;
|
|
@ -0,0 +1,36 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
use Sub::Util 'subname';
|
||||||
|
|
||||||
|
use lib "t";
|
||||||
|
use testcase "t::actions";
|
||||||
|
|
||||||
|
BEGIN { $^H{"t::actions/action"} = 1 }
|
||||||
|
|
||||||
|
{
|
||||||
|
action name { "OK" }
|
||||||
|
ok( name(), 'default actions parses like named sub' );
|
||||||
|
ok( defined &name, '&name is on symbol table' );
|
||||||
|
is( subname( \&name ), "main::name", '&name has subname' );
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
my $code = action nameER { "OK" };
|
||||||
|
ok( $code->(), 'RET_EXRP + REFGEN_ANONCODE behaves as anon sub' );
|
||||||
|
ok( defined &nameER, '&nameER is on symbol table' );
|
||||||
|
is( subname( $code ), "main::nameER", '$code has subname' );
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
my $code = action nameiER { "OK" };
|
||||||
|
ok( $code->(), 'Unininstalled CV is still invokable' );
|
||||||
|
ok( !defined &nameiER, '&nameiER not on symbol table' );
|
||||||
|
# CvNAME_HEK_set() doesn't work before perl 5.22
|
||||||
|
is( subname( $code ), "main::nameiER", '$code still has a subname anyway' ) if $] >= 5.022;
|
||||||
|
}
|
||||||
|
|
||||||
|
done_testing;
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use v5.14;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
|
eval "use Test::Pod 1.00";
|
||||||
|
plan skip_all => "Test::Pod 1.00 required for testing POD" if $@;
|
||||||
|
|
||||||
|
all_pod_files_ok();
|
|
@ -0,0 +1,38 @@
|
||||||
|
/* 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, 2021 -- leonerd@leonerd.org.uk
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "EXTERN.h"
|
||||||
|
#include "perl.h"
|
||||||
|
#include "XSUB.h"
|
||||||
|
|
||||||
|
#include "XSParseSublike.h"
|
||||||
|
|
||||||
|
static void action_pre_subparse(pTHX_ struct XSParseSublikeContext *ctx, void *_)
|
||||||
|
{
|
||||||
|
const char *namestr = SvPVX(ctx->name);
|
||||||
|
|
||||||
|
if(strchr(namestr, 'i'))
|
||||||
|
ctx->actions &= ~XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL;
|
||||||
|
if(strchr(namestr, 'R'))
|
||||||
|
ctx->actions |= XS_PARSE_SUBLIKE_ACTION_REFGEN_ANONCODE;
|
||||||
|
if(strchr(namestr, 'E'))
|
||||||
|
ctx->actions |= XS_PARSE_SUBLIKE_ACTION_RET_EXPR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct XSParseSublikeHooks parse_action_hooks = {
|
||||||
|
.permit_hintkey = "t::actions/action",
|
||||||
|
.flags = XS_PARSE_SUBLIKE_COMPAT_FLAG_DYNAMIC_ACTIONS,
|
||||||
|
.require_parts = XS_PARSE_SUBLIKE_PART_NAME,
|
||||||
|
|
||||||
|
.pre_subparse = action_pre_subparse,
|
||||||
|
};
|
||||||
|
|
||||||
|
MODULE = t::actions PACKAGE = t::actions
|
||||||
|
|
||||||
|
BOOT:
|
||||||
|
boot_xs_parse_sublike(0);
|
||||||
|
|
||||||
|
register_xs_parse_sublike("action", &parse_action_hooks, NULL);
|
|
@ -0,0 +1,99 @@
|
||||||
|
/* 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, 2020 -- leonerd@leonerd.org.uk
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "EXTERN.h"
|
||||||
|
#include "perl.h"
|
||||||
|
#include "XSUB.h"
|
||||||
|
|
||||||
|
#include "XSParseSublike.h"
|
||||||
|
|
||||||
|
#ifndef wrap_keyword_plugin
|
||||||
|
# include "wrap_keyword_plugin.c.inc"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void func_pre_subparse(pTHX_ struct XSParseSublikeContext *ctx, void *_logsv)
|
||||||
|
{
|
||||||
|
SV *logsv = _logsv;
|
||||||
|
sv_catpvs(logsv, "Sf");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void func_post_blockstart(pTHX_ struct XSParseSublikeContext *ctx, void *_logsv)
|
||||||
|
{
|
||||||
|
SV *logsv = _logsv;
|
||||||
|
sv_catpvs(logsv, "Ef");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void func_pre_blockend(pTHX_ struct XSParseSublikeContext *ctx, void *_logsv)
|
||||||
|
{
|
||||||
|
SV *logsv = _logsv;
|
||||||
|
sv_catpvs(logsv, "Lf");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void func_post_newcv(pTHX_ struct XSParseSublikeContext *ctx, void *_logsv)
|
||||||
|
{
|
||||||
|
SV *logsv = _logsv;
|
||||||
|
sv_catpvs(logsv, "Nf");
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct XSParseSublikeHooks parse_func_hooks = {
|
||||||
|
.pre_subparse = func_pre_subparse,
|
||||||
|
.post_blockstart = func_post_blockstart,
|
||||||
|
.pre_blockend = func_pre_blockend,
|
||||||
|
.post_newcv = func_post_newcv,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void prefixed_pre_subparse(pTHX_ struct XSParseSublikeContext *ctx, void *_logsv)
|
||||||
|
{
|
||||||
|
SV *logsv = _logsv;
|
||||||
|
sv_catpvs(logsv, "Sp");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void prefixed_post_blockstart(pTHX_ struct XSParseSublikeContext *ctx, void *_logsv)
|
||||||
|
{
|
||||||
|
SV *logsv = _logsv;
|
||||||
|
sv_catpvs(logsv, "Ep");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void prefixed_pre_blockend(pTHX_ struct XSParseSublikeContext *ctx, void *_logsv)
|
||||||
|
{
|
||||||
|
SV *logsv = _logsv;
|
||||||
|
sv_catpvs(logsv, "Lp");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void prefixed_post_newcv(pTHX_ struct XSParseSublikeContext *ctx, void *_logsv)
|
||||||
|
{
|
||||||
|
SV *logsv = _logsv;
|
||||||
|
sv_catpvs(logsv, "Np");
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct XSParseSublikeHooks parse_prefixed_hooks = {
|
||||||
|
.pre_subparse = prefixed_pre_subparse,
|
||||||
|
.post_blockstart = prefixed_post_blockstart,
|
||||||
|
.pre_blockend = prefixed_pre_blockend,
|
||||||
|
.post_newcv = prefixed_post_newcv,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int (*next_keyword_plugin)(pTHX_ char *, STRLEN, OP **);
|
||||||
|
|
||||||
|
static int my_keyword_plugin(pTHX_ char *kw, STRLEN kwlen, OP **op_ptr)
|
||||||
|
{
|
||||||
|
if(kwlen != 8 || !strEQ(kw, "prefixed"))
|
||||||
|
return (*next_keyword_plugin)(aTHX_ kw, kwlen, op_ptr);
|
||||||
|
|
||||||
|
lex_read_space(0);
|
||||||
|
|
||||||
|
return xs_parse_sublike_any(&parse_prefixed_hooks, SvREFCNT_inc(get_sv("main::LOG", 0)),
|
||||||
|
op_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
MODULE = t::any PACKAGE = t::any
|
||||||
|
|
||||||
|
BOOT:
|
||||||
|
boot_xs_parse_sublike(0);
|
||||||
|
|
||||||
|
register_xs_parse_sublike("func", &parse_func_hooks, SvREFCNT_inc(get_sv("main::LOG", GV_ADD)));
|
||||||
|
|
||||||
|
wrap_keyword_plugin(&my_keyword_plugin, &next_keyword_plugin);
|
|
@ -0,0 +1,35 @@
|
||||||
|
/* 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, 2020 -- leonerd@leonerd.org.uk
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "EXTERN.h"
|
||||||
|
#include "perl.h"
|
||||||
|
#include "XSUB.h"
|
||||||
|
|
||||||
|
#include "XSParseSublike.h"
|
||||||
|
|
||||||
|
static void no_body_post_newcv(pTHX_ struct XSParseSublikeContext *ctx, void *_)
|
||||||
|
{
|
||||||
|
if(ctx->body)
|
||||||
|
croak("Expected no_body to have ctx->body == NULL");
|
||||||
|
if(ctx->cv)
|
||||||
|
croak("Expected no_body to have ctx->cv == NULL");
|
||||||
|
|
||||||
|
sv_setsv(get_sv("t::flags::captured_name", GV_ADD), ctx->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct XSParseSublikeHooks parse_no_body_hooks = {
|
||||||
|
.permit_hintkey = "t::flags/no_body",
|
||||||
|
.flags = XS_PARSE_SUBLIKE_FLAG_BODY_OPTIONAL,
|
||||||
|
|
||||||
|
.post_newcv = no_body_post_newcv,
|
||||||
|
};
|
||||||
|
|
||||||
|
MODULE = t::flags PACKAGE = t::flags
|
||||||
|
|
||||||
|
BOOT:
|
||||||
|
boot_xs_parse_sublike(0);
|
||||||
|
|
||||||
|
register_xs_parse_sublike("no_body", &parse_no_body_hooks, NULL);
|
|
@ -0,0 +1,22 @@
|
||||||
|
/* 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, 2020 -- leonerd@leonerd.org.uk
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "EXTERN.h"
|
||||||
|
#include "perl.h"
|
||||||
|
#include "XSUB.h"
|
||||||
|
|
||||||
|
#include "XSParseSublike.h"
|
||||||
|
|
||||||
|
static const struct XSParseSublikeHooks parse_func_hooks = {
|
||||||
|
/* empty */
|
||||||
|
};
|
||||||
|
|
||||||
|
MODULE = t::func PACKAGE = t::func
|
||||||
|
|
||||||
|
BOOT:
|
||||||
|
boot_xs_parse_sublike(0);
|
||||||
|
|
||||||
|
register_xs_parse_sublike("func", &parse_func_hooks, NULL);
|
|
@ -0,0 +1,43 @@
|
||||||
|
/* 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, 2020 -- leonerd@leonerd.org.uk
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "EXTERN.h"
|
||||||
|
#include "perl.h"
|
||||||
|
#include "XSUB.h"
|
||||||
|
|
||||||
|
#include "XSParseSublike.h"
|
||||||
|
|
||||||
|
static bool parts_permit(pTHX_ void *_);
|
||||||
|
|
||||||
|
static struct XSParseSublikeHooks parse_parts_hooks = {
|
||||||
|
.permit = parts_permit,
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool parts_permit(pTHX_ void *_)
|
||||||
|
{
|
||||||
|
parse_parts_hooks.require_parts = 0;
|
||||||
|
parse_parts_hooks.skip_parts = 0;
|
||||||
|
|
||||||
|
if(hv_fetchs(GvHV(PL_hintgv), "t::parts/require-name", 0))
|
||||||
|
parse_parts_hooks.require_parts |= XS_PARSE_SUBLIKE_PART_NAME;
|
||||||
|
if(hv_fetchs(GvHV(PL_hintgv), "t::parts/skip-name", 0))
|
||||||
|
parse_parts_hooks.skip_parts |= XS_PARSE_SUBLIKE_PART_NAME;
|
||||||
|
|
||||||
|
if(hv_fetchs(GvHV(PL_hintgv), "t::parts/skip-attrs", 0))
|
||||||
|
parse_parts_hooks.skip_parts |= XS_PARSE_SUBLIKE_PART_ATTRS;
|
||||||
|
|
||||||
|
if(hv_fetchs(GvHV(PL_hintgv), "t::parts/skip-signature", 0))
|
||||||
|
parse_parts_hooks.skip_parts |= XS_PARSE_SUBLIKE_PART_SIGNATURE;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
MODULE = t::parts PACKAGE = t::parts
|
||||||
|
|
||||||
|
BOOT:
|
||||||
|
boot_xs_parse_sublike(0);
|
||||||
|
|
||||||
|
register_xs_parse_sublike("parts", &parse_parts_hooks, NULL);
|
|
@ -0,0 +1,83 @@
|
||||||
|
/* 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, 2020 -- leonerd@leonerd.org.uk
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "EXTERN.h"
|
||||||
|
#include "perl.h"
|
||||||
|
#include "XSUB.h"
|
||||||
|
|
||||||
|
#include "XSParseSublike.h"
|
||||||
|
|
||||||
|
static void func_pre_subparse(pTHX_ struct XSParseSublikeContext *ctx, void *_logsv)
|
||||||
|
{
|
||||||
|
SV *logsv = _logsv;
|
||||||
|
sv_catpvs(logsv, "Sf");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void func_post_blockstart(pTHX_ struct XSParseSublikeContext *ctx, void *_logsv)
|
||||||
|
{
|
||||||
|
SV *logsv = _logsv;
|
||||||
|
sv_catpvs(logsv, "Ef");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void func_pre_blockend(pTHX_ struct XSParseSublikeContext *ctx, void *_logsv)
|
||||||
|
{
|
||||||
|
SV *logsv = _logsv;
|
||||||
|
sv_catpvs(logsv, "Lf");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void func_post_newcv(pTHX_ struct XSParseSublikeContext *ctx, void *_logsv)
|
||||||
|
{
|
||||||
|
SV *logsv = _logsv;
|
||||||
|
sv_catpvs(logsv, "Nf");
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct XSParseSublikeHooks parse_func_hooks = {
|
||||||
|
.pre_subparse = func_pre_subparse,
|
||||||
|
.post_blockstart = func_post_blockstart,
|
||||||
|
.pre_blockend = func_pre_blockend,
|
||||||
|
.post_newcv = func_post_newcv,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void prefixed_pre_subparse(pTHX_ struct XSParseSublikeContext *ctx, void *_logsv)
|
||||||
|
{
|
||||||
|
SV *logsv = _logsv;
|
||||||
|
sv_catpvs(logsv, "Sp");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void prefixed_post_blockstart(pTHX_ struct XSParseSublikeContext *ctx, void *_logsv)
|
||||||
|
{
|
||||||
|
SV *logsv = _logsv;
|
||||||
|
sv_catpvs(logsv, "Ep");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void prefixed_pre_blockend(pTHX_ struct XSParseSublikeContext *ctx, void *_logsv)
|
||||||
|
{
|
||||||
|
SV *logsv = _logsv;
|
||||||
|
sv_catpvs(logsv, "Lp");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void prefixed_post_newcv(pTHX_ struct XSParseSublikeContext *ctx, void *_logsv)
|
||||||
|
{
|
||||||
|
SV *logsv = _logsv;
|
||||||
|
sv_catpvs(logsv, "Np");
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct XSParseSublikeHooks parse_prefixed_hooks = {
|
||||||
|
.flags = XS_PARSE_SUBLIKE_FLAG_PREFIX,
|
||||||
|
|
||||||
|
.pre_subparse = prefixed_pre_subparse,
|
||||||
|
.post_blockstart = prefixed_post_blockstart,
|
||||||
|
.pre_blockend = prefixed_pre_blockend,
|
||||||
|
.post_newcv = prefixed_post_newcv,
|
||||||
|
};
|
||||||
|
|
||||||
|
MODULE = t::prefix PACKAGE = t::prefix
|
||||||
|
|
||||||
|
BOOT:
|
||||||
|
boot_xs_parse_sublike(0);
|
||||||
|
|
||||||
|
register_xs_parse_sublike("func", &parse_func_hooks, SvREFCNT_inc(get_sv("main::LOG", GV_ADD)));
|
||||||
|
register_xs_parse_sublike("prefixed", &parse_prefixed_hooks, SvREFCNT_inc(get_sv("main::LOG", GV_ADD)));
|
|
@ -0,0 +1,43 @@
|
||||||
|
/* 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, 2020 -- leonerd@leonerd.org.uk
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "EXTERN.h"
|
||||||
|
#include "perl.h"
|
||||||
|
#include "XSUB.h"
|
||||||
|
|
||||||
|
#include "XSParseSublike.h"
|
||||||
|
|
||||||
|
static void red_pre_blockend(pTHX_ struct XSParseSublikeContext *ctx, void *_)
|
||||||
|
{
|
||||||
|
/* Throw away the entire function body; replace it with a constant */
|
||||||
|
op_free(ctx->body);
|
||||||
|
ctx->body = newSVOP(OP_CONST, 0, newSVpv("red", 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct XSParseSublikeHooks parse_red_hooks = {
|
||||||
|
.permit_hintkey = "t::registrations/red",
|
||||||
|
.pre_blockend = red_pre_blockend,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void blue_pre_blockend(pTHX_ struct XSParseSublikeContext *ctx, void *_)
|
||||||
|
{
|
||||||
|
/* Throw away the entire function body; replace it with a constant */
|
||||||
|
op_free(ctx->body);
|
||||||
|
ctx->body = newSVOP(OP_CONST, 0, newSVpv("blue", 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct XSParseSublikeHooks parse_blue_hooks = {
|
||||||
|
.permit_hintkey = "t::registrations/blue",
|
||||||
|
.pre_blockend = blue_pre_blockend,
|
||||||
|
};
|
||||||
|
|
||||||
|
MODULE = t::registrations PACKAGE = t::registrations
|
||||||
|
|
||||||
|
BOOT:
|
||||||
|
boot_xs_parse_sublike(0);
|
||||||
|
|
||||||
|
register_xs_parse_sublike("func", &parse_red_hooks, NULL);
|
||||||
|
register_xs_parse_sublike("func", &parse_blue_hooks, NULL);
|
|
@ -0,0 +1,80 @@
|
||||||
|
/* 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, 2020 -- leonerd@leonerd.org.uk
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "EXTERN.h"
|
||||||
|
#include "perl.h"
|
||||||
|
#include "XSUB.h"
|
||||||
|
|
||||||
|
#include "XSParseSublike.h"
|
||||||
|
|
||||||
|
#include "sv_setrv.c.inc"
|
||||||
|
|
||||||
|
static bool stage_permit(pTHX_ void *_)
|
||||||
|
{
|
||||||
|
if(!hv_fetchs(GvHV(PL_hintgv), "t::stages/permit", 0))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stage_pre_subparse(pTHX_ struct XSParseSublikeContext *ctx, void *_)
|
||||||
|
{
|
||||||
|
if(hv_fetchs(GvHV(PL_hintgv), "t::stages/pre_subparse-capture", 0)) {
|
||||||
|
sv_setsv(get_sv("t::stages::captured", GV_ADD), get_sv("main::VAR", 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stage_post_blockstart(pTHX_ struct XSParseSublikeContext *ctx, void *_)
|
||||||
|
{
|
||||||
|
if(hv_fetchs(GvHV(PL_hintgv), "t::stages/post_blockstart-capture", 0)) {
|
||||||
|
sv_setsv(get_sv("t::stages::captured", GV_ADD), get_sv("main::VAR", 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stage_pre_blockend(pTHX_ struct XSParseSublikeContext *ctx, void *_)
|
||||||
|
{
|
||||||
|
if(hv_fetchs(GvHV(PL_hintgv), "t::stages/pre_blockend-capture", 0)) {
|
||||||
|
sv_setsv(get_sv("t::stages::captured", GV_ADD), get_sv("main::VAR", 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stage_post_newcv(pTHX_ struct XSParseSublikeContext *ctx, void *_)
|
||||||
|
{
|
||||||
|
if(hv_fetchs(GvHV(PL_hintgv), "t::stages/post_newcv-capture-cv", 0)) {
|
||||||
|
sv_setrv_inc(get_sv("t::stages::captured", GV_ADD), (SV *)ctx->cv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool stage_filter_attr(pTHX_ struct XSParseSublikeContext *ctx, SV *attr, SV *value, void *_)
|
||||||
|
{
|
||||||
|
if(!hv_fetchs(GvHV(PL_hintgv), "t::stages/filter_attr-capture", 0))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
AV *av = newAV();
|
||||||
|
av_push(av, SvREFCNT_inc(attr));
|
||||||
|
av_push(av, SvREFCNT_inc(value));
|
||||||
|
|
||||||
|
sv_setrv_noinc(get_sv("t::stages::captured", GV_ADD), (SV *)av);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct XSParseSublikeHooks parse_stages_hooks = {
|
||||||
|
.flags = XS_PARSE_SUBLIKE_FLAG_FILTERATTRS,
|
||||||
|
.permit = stage_permit,
|
||||||
|
.pre_subparse = stage_pre_subparse,
|
||||||
|
.post_blockstart = stage_post_blockstart,
|
||||||
|
.pre_blockend = stage_pre_blockend,
|
||||||
|
.post_newcv = stage_post_newcv,
|
||||||
|
|
||||||
|
.filter_attr = stage_filter_attr,
|
||||||
|
};
|
||||||
|
|
||||||
|
MODULE = t::stages PACKAGE = t::stages
|
||||||
|
|
||||||
|
BOOT:
|
||||||
|
boot_xs_parse_sublike(0);
|
||||||
|
|
||||||
|
register_xs_parse_sublike("stages", &parse_stages_hooks, NULL);
|
|
@ -0,0 +1,17 @@
|
||||||
|
package testcase;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use lib "t/blib", "t/blib/arch";
|
||||||
|
|
||||||
|
use XS::Parse::Sublike;
|
||||||
|
|
||||||
|
sub import
|
||||||
|
{
|
||||||
|
shift;
|
||||||
|
require XSLoader;
|
||||||
|
XSLoader::load( $_[0], $XS::Parse::Sublike::VERSION );
|
||||||
|
}
|
||||||
|
|
||||||
|
0x55AA;
|
Loading…
Reference in New Issue