Data-Checks-0.10000755001750001750 014660677075 12244 5ustar00leoleo000000000000Data-Checks-0.10/.editorconfig000444001750001750 5314660677075 15014 0ustar00leoleo000000000000root = true [*.{pm,pl,t}] indent_size = 3 Data-Checks-0.10/Build.PL000444001750001750 132714660677075 13700 0ustar00leoleo000000000000use v5; use strict; use warnings; use lib 'inc'; use Module::Build::with::XSTests; my $build = Module::Build::with::XSTests->new( module_name => 'Data::Checks', requires => { perl => '5.022', # op_convert_list() ( $^V lt v5.38 ) ? ( 'builtin::Backport' => 0, ) : (), }, test_requires => { 'Test2::V0' => '0.000148', }, configure_requires => { 'Module::Build' => '0.4004', # test_requires }, share_dir => { module => { "Data::Checks" => [ 'share' ] }, }, extra_compiler_flags => [qw( -Ishare/include -Iinclude -Ihax )], c_source => [ "src/" ], license => 'perl', create_license => 1, create_readme => 1, ); $build->create_build_script; Data-Checks-0.10/Changes000444001750001750 512214660677075 13674 0ustar00leoleo000000000000Revision history for Data-Checks 0.10 2024-08-19 [CHANGES] * Optional flags arguments to `make_assertop` * Neater use in optrees by passing OPf_WANT_VOID to `make_assertop` * Optimise `All()` combinations of multiple number bounds checks 0.09 2024-07-31 [CHANGES] * Deprecate the use of plain CODE references as constraint checkers * Added `->check` method to constraint checker class, allowing direct use from pureperl code * Better formatting of debug inspection strings * Automatically generate stringified constraint names for assert message if one is not supplied 0.08 2024-07-16 [CHANGES] * Added `StrMatch()` * More efficient implementation of `Any()` and `All()` with 1 argument or in nested trees * Support the `|` infix operator as a shorthand for `Any()` 0.07 2024-07-12 [CHANGES] * Added `ArrayRef` and `HashRef` constraints * Added `Any` and `All` hyper-constraints * More extensive unit testing of constraints by automatically generating reject cases 0.06 2024-07-09 [CHANGES] * Added `Callable` constraint * Apply const folding where possible at compile-time so that constraint expressions become runtime constants * Added a SYNOPSIS example demonstrating use with `Syntax::Operator::Is` 0.05 2024-07-01 [CHANGES] * Added `NumEq`, `NumRange`, various single-ended numerical bounded constraints * Added `StrEq` [BUGFIXES] * Ensure that the benchmark tests are suitable version-guarded for external `:Checked` attribute modules 0.04 2024-06-27 [CHANGES] * Added `Isa()` and `Maybe()` parametric constraints * Declare `Data::Checks::Builder` version 0.43 to fix earlier version numbering error - no actual code change 0.03 2024-06-23 [CHANGES] * Added a few basic constraints: `Defined`, `Str`, `Num`, `Object` * Added some cross-module integration tests for `Signature::Attribute::Checked` and `Object::Pad::FieldAttr::Checked` * Added SYNOPSIS documentation section giving examples of each 0.02 2024-06-21 [CHANGES] * Added `free_checkdata()` and `gen_assertmess()` API functions * Better management of SV reference counts * `struct DataChecks_Checker` is now an opaque structure 0.01 2024-06-19 First version, released on an unsuspecting world. Data-Checks-0.10/LICENSE000444001750001750 4653414660677075 13442 0ustar00leoleo000000000000This software is copyright (c) 2024 by Paul Evans . 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) 2024 by Paul Evans . 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. Copyright (C) 19yy 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. , 1 April 1989 Ty Coon, President of Vice That's all there is to it! --- The Perl Artistic License 1.0 --- This software is Copyright (c) 2024 by Paul Evans . This is free software, licensed under: The Perl 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 as specified below. "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 uunet.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) give non-standard executables non-standard names, and clearly document 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. You may embed this Package's interpreter within an executable of yours (by linking); this shall be construed as a mere form of aggregation, provided that the complete Standard Version of the interpreter is so embedded. 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 whoever generated them, and may be sold commercially, and may be aggregated with this Package. If such scripts or library files are aggregated with this Package via the so-called "undump" or "unexec" methods of producing a binary executable image, then distribution of such an image shall neither be construed as a distribution of this Package nor shall it fall under the restrictions of Paragraphs 3 and 4, provided that you do not represent such an executable image as a Standard Version of this Package. 7. C subroutines (or comparably compiled subroutines in other languages) supplied by you and linked into this Package in order to emulate subroutines and variables of the language defined by this Package shall not be considered part of this Package, but are the equivalent of input as in Paragraph 6, provided these subroutines do not change the language in any way that would cause it to fail the regression tests for the language. 8. Aggregation of this Package with a commercial distribution is always permitted provided that the use of this Package is embedded; that is, when no overt attempt is made to make this Package's interfaces visible to the end user of the commercial distribution. Such use shall not be construed as a distribution of this Package. 9. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End Data-Checks-0.10/MANIFEST000444001750001750 151714660677075 13536 0ustar00leoleo000000000000.editorconfig Build.PL Changes hax/ckcall_constfold.c.inc hax/make_argcheck_aux.c.inc hax/make_argcheck_ops.c.inc hax/newOP_CUSTOM.c.inc hax/optree-additions.c.inc hax/perl-backcompat.c.inc hax/sv_numcmp.c.inc hax/sv_regexp_match.c.inc hax/sv_streq.c.inc inc/Module/Build/with/XSTests.pm include/constraints.h lib/Data/Checks.pm lib/Data/Checks.xs lib/Data/Checks/Builder.pm LICENSE MANIFEST This list of files META.json META.yml README share/include/DataChecks.h src/constraints.c t/00use.t t/01check-obj.t t/02check-pkg.t t/03check-sub.t t/10constraints.t t/11constraints-num.t t/12constraints-str.t t/13constraints-structural.t t/20inline-constraints.t t/21assertop.t t/22optimise-numbounds.t t/80checks+field.t t/80checks+signature.t t/80match-is.t t/95benchmark-fields.t t/95benchmark-signature.t t/99pod.t t/test.c t/test.xs t/testcase.pm Data-Checks-0.10/META.json000444001750001750 237114660677075 14025 0ustar00leoleo000000000000{ "abstract" : "Value constraint checking", "author" : [ "Paul Evans " ], "dynamic_config" : 1, "generated_by" : "Module::Build version 0.4234", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Data-Checks", "prereqs" : { "build" : { "requires" : { "ExtUtils::CBuilder" : "0" } }, "configure" : { "requires" : { "Module::Build" : "0.4004" } }, "runtime" : { "requires" : { "File::ShareDir" : "1.00", "perl" : "5.022" } }, "test" : { "requires" : { "Test2::V0" : "0.000148" } } }, "provides" : { "Data::Checks" : { "file" : "lib/Data/Checks.pm", "version" : "0.10" }, "Data::Checks::Builder" : { "file" : "lib/Data/Checks/Builder.pm", "version" : "0.44" } }, "release_status" : "stable", "resources" : { "license" : [ "http://dev.perl.org/licenses/" ] }, "version" : "0.10", "x_serialization_backend" : "JSON::PP version 4.16" } Data-Checks-0.10/META.yml000444001750001750 137414660677075 13657 0ustar00leoleo000000000000--- abstract: 'Value constraint checking' author: - 'Paul Evans ' build_requires: ExtUtils::CBuilder: '0' Test2::V0: '0.000148' configure_requires: Module::Build: '0.4004' dynamic_config: 1 generated_by: 'Module::Build version 0.4234, 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: Data-Checks provides: Data::Checks: file: lib/Data/Checks.pm version: '0.10' Data::Checks::Builder: file: lib/Data/Checks/Builder.pm version: '0.44' requires: File::ShareDir: '1.00' perl: '5.022' resources: license: http://dev.perl.org/licenses/ version: '0.10' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' Data-Checks-0.10/README000444001750001750 2741214660677075 13307 0ustar00leoleo000000000000NAME Data::Checks - Value constraint checking SYNOPSIS With Signature::Attribute::Checked: use v5.26; use Sublike::Extended; use Signature::Attribute::Checked; use Data::Checks qw( Str ); extended sub greet ( $message :Checked(Str) ) { say "Hello, $message"; } greet( "world" ); # is fine greet( undef ); # throws an exception With Object::Pad::FieldAttr::Checked: use v5.22; use Object::Pad; use Object::Pad::FieldAttr::Checked; use Data::Checks qw( Str ); class Datum { field $name :param :reader :Checked(Str); } my $x = Datum->new( name => "something" ); # is fine my $y = Datum->new( name => undef ); # throws an exception With Syntax::Operator::Is on Perl v5.38 or later: use v5.38; use Syntax::Operator::Is; use Data::Checks qw( Num Object ); my $x = ...; if($x is Num) { say "x can be used as a number"; } elsif($x is Object) { say "x can be used as an object"; } DESCRIPTION This module provides functions that implement various value constraint checking behaviours. These are the parts made visible by the use Data::Checks ... import line, in Perl code. It also provides the underlying common framework XS functions to assist in writing modules that actually implement such constraint checking. These parts are not visible in Perl code, but instead made visible at the XS level by the #include "DataChecks.h" directive. See the "SYNOPSIS" section above for several examples of other CPAN modules that make direct use of these constraint checks. CONSTRAINTS The following constraint checks are inspired by the same-named ones in Types::Standard. They may be called fully-qualified, or imported lexically into the calling scope. Note to users familiar with Types::Standard: some of these functions behave slightly differently. In particular, these constraints are generally happy to accept an object reference to a class that provides a conversion overload, whereas the ones in Types::Standard often are not. Additionally functions that are parametric take their parameters in normal Perl function argument lists, not wrapped in additional array references. Defined Defined() Accepts any defined value, rejects only undef. Object Object() Accepts any blessed object reference, rejects non-references or references to unblessed data. Str Str() Accepts any defined non-reference value, or a reference to an object in a class that overloads stringification. Rejects undefined, unblessed references, or references to objects in classes that do not overload stringification. StrEq StrEq($s) StrEq($s1, $s2, ...) Since version 0.05. Accepts any value that passes the Str check, and additionally is exactly equal to any of the given strings. StrMatch StrMatch(qr/pattern/) Since version 0.08. Accepts any value that passes the Str check, and additionally matches the given regexp pattern. Remember that the pattern must be supplied as a qr/.../ expression, not simply m/.../ or /.../. Num Num() Accepts any defined non-reference value that is either a plain number, or a string that could be used as one without warning, or a reference to an object in a class that overloads numification. Rejects undefined, not-a-number, strings that would raise a warning if converted to a number, unblessed references, or references to objects in classes that do not overload numification. NumGT NumGE NumLE NumLT NumGT($bound) NumGE($bound) NumLE($bound) NumLT($bound) Since version 0.05. Accepts any value that passes the Num check, and additionally is within the bound given. NumGT and NumLT exclude the bound value itself, NumGE and NumLE include it. NumRange NumRange($boundge, $boundlt) Since version 0.05. Accepts any value that passes the Num check, and additionally is between the two bounds given. The lower bound is inclusive, and the upper bound is exclusive. This choice is made so that a set of NumRange constraints can easily be created that cover distinct sets of numbers: NumRange(0, 10), NumRange(10, 20), NumRange(20, 30), ... To implement checks with both lower and upper bounds but other kinds of inclusivity, use two Num... checks combined with an All(). For example, to test between 0 and 100 inclusive at both ends: All(NumGE(0), NumLE(100)) Combinations like this are internally implemented as efficiently as a single NumRange() constraint. NumEq NumEq($n) NumEq($n1, $n2, ...) Since version 0.05. Accepts any value that passes the Num check, and additionally is exactly equal to any of the given numbers. Isa Isa($classname) Since version 0.04. Accepts any blessed object reference to an instance of the given class name, or a subclass derived from it (i.e. anything accepted by the isa operator). ArrayRef ArrayRef() Since version 0.07. Accepts any plain reference to an array, or any object reference to an instance of a class that provides an array dereference overload. HashRef HashRef() Since version 0.07. Accepts any plain reference to a hash, or any object reference to an instance of a class that provides a hash dereference overload. Callable Callable() Since version 0.06. Accepts any plain reference to a subroutine, or any object reference to an instance of a class that provides a subroutine dereference overload. Maybe Maybe($C) Since version 0.04. Accepts undef in addition to anything else accepted by the given constraint. Any Any($C1, $C2, ...) Since version 0.07. Accepts a value that is accepted by at least one of the given constraints. Rejects if none of them accept it. At least one constraint is required; it is an error to try to call Any() with no arguments. If you need a constraint that accepts any value at all, see "All". $C1 | $C2 | ... Since version 0.08. This function is used to implement | operator overloading, so constraint checks can be written using this more convenient syntax. All All($C1, $C2, ...) All() Since version 0.07. Accepts a value that is accepted by every one of the given constraints. Rejects if at least one of them rejects it. Note that if no constraints are given, this accepts all possible values. This may be useful as an "accept-all" fallback case for generated code, or other situations where it is required to provide a constraint check but you do not wish to constraint allowed values. CONSTRAINT METHODS While not intended to be called from regular Perl code, these constraints still act like objects with the following methods. check $ok = $constraint->check( $value ); Since version 0.09. Returns a boolean value indicating whether the constraint accepts the given value. XS FUNCTIONS The following functions are provided by the DataChecks.h header file for use in XS modules that implement value constraint checking. boot_data_checks void boot_data_checks(double ver); Call this function from your BOOT section in order to initialise the module and load the rest of the support functions. ver should either be 0 or a decimal number for the module version requirement; e.g. boot_data_checks(0.01); make_checkdata struct DataChecks_Checker *make_checkdata(SV *checkspec); Creates a struct DataChecks_Checker structure, which wraps the intent of the value constraint check. The returned value is used as the checker argument for the remaining functions. The constraint check itself is specified by the SV given by checkspec, which should come directly from the user code. The constraint check may be specified in any of three ways: * An object reference in a class which has a check method. Value checks will be invoked as $ok = $checkerobj->check( $value ); * A package name as a plain string of a package which has a check method. Value checks will be invoked as $ok = $checkerpkg->check( $value ); * A code reference. Value checks will be invoked with a single argument, as $ok = $checkersub->( $value ); Since version 0.09 this form is now deprecated, because it does not easily support a way to query the constraint for its name or stringified form, which is useful when generating error messages. * Additionally, the constraint check functions provided by this module may be implemented using any of the above mechanisms, or may use an unspecified fourth different mechanism. Outside code should not rely on what that mechanism may be. Once constructed into a checker structure, the choice of which implementation is used is fixed, and if a method lookup is involved its result is stored directly as a CV pointer for efficiency of later invocations. In either of the first two cases, the reference count on the checkspec SV is increased to account for the argument value used on each invocation. In the third case, the reference SV is not retained, but the underlying CV it refers to has its reference count increased. free_checkdata void free_checkdata(struct DataChecks_Checker *checker); Releases any stored SVs in the checker structure, and the structure itself. gen_assertmess void gen_assertmess(struct DataChecks_Checker *checker, SV *name, SV *constraint); Generates and stores a message string for the assert message to be used by "make_assertop" and "assert_value". The message will take the form NAME requires a value satisfying CONSTRAINT Both name and constraint SVs used as temporary strings to generate the stored message string. Neither SV is retained by the checker directly. make_assertop OP *make_assertop(struct DataChecks_Checker *checker, OP *argop); Shortcut to calling "make_assertop_flags" with flags set to zero. make_assertop_flags OP *make_assertop_flags(struct DataChecks_Checker *checker, U32 flags, OP *argop); Creates an optree fragment for a value check assertion operation. Given an optree fragment in scalar context that generates an argument value (argop), constructs a larger optree fragment that consumes it and checks that the value is accepted by the constraint check given by checker. The behaviours of the returned optree fragment will depend on the flags. If flags is OPf_WANT_VOID the returned optree will yield nothing. If flags is zero, the return behaviour is not otherwise specified. check_value bool check_value(struct DataChecks_Checker *checker, SV *value); Checks whether a given SV is accepted by the given constraint check, returning true if so, or false if not. assert_value void assert_value(struct DataChecks_Checker *checker, SV *value); Checks whether a given SV is accepted by the given constraint check, throwing its assertion message if it does not. TODO * Unit constraints - maybe Int, some plain-only variants of Str and Num, some reference types, etc... * Structural constraints - HashOf, ArrayOf, etc... * Think about a convenient name for inclusive-bounded numerical constraints. * Look into making const-folding work with the MIN .. MAX flip-flop operator AUTHOR Paul Evans Data-Checks-0.10/hax000755001750001750 014660677075 13024 5ustar00leoleo000000000000Data-Checks-0.10/hax/ckcall_constfold.c.inc000444001750001750 440414660677075 17403 0ustar00leoleo000000000000/* vi: set ft=c : */ static bool op_is_const(OP *o) { switch(o->op_type) { case OP_CONST: return true; case OP_LIST: { OP *oelem = cLISTOPo->op_first; if(oelem->op_type == OP_PUSHMARK) oelem = OpSIBLING(oelem); for(; oelem; oelem = OpSIBLING(oelem)) if(oelem->op_type != OP_CONST) return false; return true; } default: return false; } } static OP *ckcall_constfold(pTHX_ OP *o, GV *namegv, SV *ckobj) { assert(o->op_type == OP_ENTERSUB); OP *kid = cUNOPo->op_first; /* The first kid is usually an ex-list whose ->op_first begins the actual args list */ if(kid->op_type == OP_NULL && kid->op_targ == OP_LIST) kid = cUNOPx(kid)->op_first; /* First actual arg is likely a OP_PUSHMARK */ if(kid->op_type == OP_PUSHMARK) kid = OpSIBLING(kid); OP *firstarg = kid; for(; kid && OpSIBLING(kid); kid = OpSIBLING(kid)) { if(op_is_const(kid)) continue; return o; } CV *cv = GvCV(namegv); assert(SvTYPE(cv) == SVt_PVCV); /* We've not rejected it now, so lets invoke it and inline the result */ /* TODO: I tried invoking the actual optree by linking it, setting it as * PL_op and invoking CALLRUNOPS(), but it seems the pad isn't set up * correctly yet to permit this for OP_PADCV ops. * Instead, we'll simulated it by PUSHs()ing ourselves */ dSP; ENTER; SAVETMPS; PUSHMARK(SP); for(OP *oarg = firstarg; oarg && OpSIBLING(oarg); oarg = OpSIBLING(oarg)) { switch(oarg->op_type) { case OP_CONST: PUSHs(cSVOPx(oarg)->op_sv); break; case OP_LIST: { OP *oelem = cUNOPx(oarg)->op_first; if(oelem->op_type == OP_PUSHMARK) oelem = OpSIBLING(oelem); for(; oelem; oelem = OpSIBLING(oelem)) { assert(oelem->op_type == OP_CONST); PUSHs(cSVOPx(oelem)->op_sv); } break; } } } PUTBACK; /* TODO: Currently always presume scalar context */ I32 count = call_sv((SV *)cv, G_SCALAR|G_EVAL); bool got_err = SvTRUE(GvSV(PL_errgv)); SPAGAIN; SV *retval = SvREFCNT_inc(POPs); PUTBACK; FREETMPS; LEAVE; if(got_err) /* Error was raised; abort */ return o; op_free(o); o = newSVOP(OP_CONST, 0, retval); return o; } Data-Checks-0.10/hax/make_argcheck_aux.c.inc000444001750001750 132314660677075 17515 0ustar00leoleo000000000000/* 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 Data-Checks-0.10/hax/make_argcheck_ops.c.inc000444001750001750 553714660677075 17534 0ustar00leoleo000000000000/* vi: set ft=c : */ #define make_croak_op(message) S_make_croak_op(aTHX_ message) static OP *S_make_croak_op(pTHX_ SV *message) { #if HAVE_PERL_VERSION(5, 22, 0) sv_catpvs(message, " at %s line %d.\n"); /* die sprintf($message, (caller)[1,2]) */ return op_convert_list(OP_DIE, 0, op_convert_list(OP_SPRINTF, 0, op_append_list(OP_LIST, newSVOP(OP_CONST, 0, message), newSLICEOP(0, op_append_list(OP_LIST, newSVOP(OP_CONST, 0, newSViv(1)), newSVOP(OP_CONST, 0, newSViv(2))), newOP(OP_CALLER, 0))))); #else /* For some reason I can't work out, the above tree isn't correct. Attempts * to correct it still make OP_SPRINTF crash with "Out of memory!". For now * lets just avoid the sprintf */ sv_catpvs(message, "\n"); return newLISTOP(OP_DIE, 0, newOP(OP_PUSHMARK, 0), newSVOP(OP_CONST, 0, message)); #endif } #if HAVE_PERL_VERSION(5, 26, 0) # define HAVE_OP_ARGCHECK # include "make_argcheck_aux.c.inc" #endif #define make_argcheck_ops(required, optional, slurpy, subname) S_make_argcheck_ops(aTHX_ required, optional, slurpy, subname) static OP *S_make_argcheck_ops(pTHX_ int required, int optional, char slurpy, SV *subname) { int params = required + optional; #ifdef HAVE_OP_ARGCHECK UNOP_AUX_item *aux = make_argcheck_aux(params, optional, slurpy); return op_prepend_elem(OP_LINESEQ, newSTATEOP(0, NULL, NULL), op_prepend_elem(OP_LINESEQ, newUNOP_AUX(OP_ARGCHECK, 0, NULL, aux), NULL)); #else /* Older perls lack the convenience of OP_ARGCHECK so we'll have to build an * optree ourselves. For now we only support required + optional, no slurpy * * This code heavily inspired by Perl_parse_subsignature() in toke.c from perl 5.24 */ OP *ret = NULL; if(required > 0) { SV *message = newSVpvf("Too few arguments for subroutine '%" SVf "'", subname); /* @_ >= required or die ... */ OP *checkop = newSTATEOP(0, NULL, newLOGOP(OP_OR, 0, newBINOP(OP_GE, 0, /* scalar @_ */ op_contextualize(newUNOP(OP_RV2AV, 0, newGVOP(OP_GV, 0, PL_defgv)), G_SCALAR), newSVOP(OP_CONST, 0, newSViv(required))), make_croak_op(message))); ret = op_append_list(OP_LINESEQ, ret, checkop); } if(!slurpy) { SV *message = newSVpvf("Too many arguments for subroutine '%" SVf "'", subname); /* @_ <= (required+optional) or die ... */ OP *checkop = newSTATEOP(0, NULL, newLOGOP(OP_OR, 0, newBINOP(OP_LE, 0, /* scalar @_ */ op_contextualize(newUNOP(OP_RV2AV, 0, newGVOP(OP_GV, 0, PL_defgv)), G_SCALAR), newSVOP(OP_CONST, 0, newSViv(params))), make_croak_op(message))); ret = op_append_list(OP_LINESEQ, ret, checkop); } /* TODO: If slurpy is % then maybe complain about odd number of leftovers */ return ret; #endif } Data-Checks-0.10/hax/newOP_CUSTOM.c.inc000444001750001750 1035114660677075 16237 0ustar00leoleo000000000000/* vi: set ft=c : */ /* Before perl 5.22 under -DDEBUGGING, various new*OP() functions throw assert * failures on OP_CUSTOM. * https://rt.cpan.org/Ticket/Display.html?id=128562 */ #define newOP_CUSTOM(func, flags) S_newOP_CUSTOM(aTHX_ func, flags) #define newUNOP_CUSTOM(func, flags, first) S_newUNOP_CUSTOM(aTHX_ func, flags, first) #define newUNOP_AUX_CUSTOM(func, flags, first, aux) S_newUNOP_AUX_CUSTOM(aTHX_ func, flags, first, aux) #define newSVOP_CUSTOM(func, flags, sv) S_newSVOP_CUSTOM(aTHX_ func, flags, sv) #define newBINOP_CUSTOM(func, flags, first, last) S_newBINOP_CUSTOM(aTHX_ func, flags, first, last) #define newLISTOP_CUSTOM(func, flags, first, last) S_newLISTOP_CUSTOM(aTHX_ func, flags, first, last) #define newLOGOP_CUSTOM(func, flags, first, other) S_newLOGOP_CUSTOM(aTHX_ func, flags, first, other) static OP *S_newOP_CUSTOM(pTHX_ OP *(*func)(pTHX), U32 flags) { OP *op = newOP(OP_CUSTOM, flags); op->op_ppaddr = func; return op; } static OP *S_newUNOP_CUSTOM(pTHX_ OP *(*func)(pTHX), U32 flags, OP *first) { UNOP *unop; #if HAVE_PERL_VERSION(5,22,0) unop = (UNOP *)newUNOP(OP_CUSTOM, flags, first); #else NewOp(1101, unop, 1, UNOP); unop->op_type = (OPCODE)OP_CUSTOM; unop->op_first = first; unop->op_flags = (U8)(flags | OPf_KIDS); unop->op_private = (U8)(1 | (flags >> 8)); #endif unop->op_ppaddr = func; return (OP *)unop; } static OP *S_newUNOP_AUX_CUSTOM(pTHX_ OP *(*func)(pTHX), U32 flags, OP *first, UNOP_AUX_item *aux) { UNOP_AUX *unop; #if HAVE_PERL_VERSION(5,22,0) unop = (UNOP_AUX *)newUNOP_AUX(OP_CUSTOM, flags, first, aux); #else croak("TODO: create newUNOP_AUX_CUSTOM"); #endif unop->op_ppaddr = func; return (OP *)unop; } static OP *S_newSVOP_CUSTOM(pTHX_ OP *(*func)(pTHX), U32 flags, SV *sv) { SVOP *svop; #if HAVE_PERL_VERSION(5,22,0) svop = (SVOP *)newSVOP(OP_CUSTOM, flags, sv); #else NewOp(1101, svop, 1, SVOP); svop->op_type = (OPCODE)OP_CUSTOM; svop->op_sv = sv; svop->op_next = (OP *)svop; svop->op_flags = 0; svop->op_private = 0; #endif svop->op_ppaddr = func; return (OP *)svop; } static OP *S_newBINOP_CUSTOM(pTHX_ OP *(*func)(pTHX), U32 flags, OP *first, OP *last) { BINOP *binop; #if HAVE_PERL_VERSION(5,22,0) binop = (BINOP *)newBINOP(OP_CUSTOM, flags, first, last); #else NewOp(1101, binop, 1, BINOP); binop->op_type = (OPCODE)OP_CUSTOM; binop->op_first = first; first->op_sibling = last; binop->op_last = last; binop->op_flags = (U8)(flags | OPf_KIDS); binop->op_private = (U8)(2 | (flags >> 8)); #endif binop->op_ppaddr = func; return (OP *)binop; } static OP *S_newLISTOP_CUSTOM(pTHX_ OP *(*func)(pTHX), U32 flags, OP *first, OP *last) { LISTOP *listop; #if HAVE_PERL_VERSION(5,22,0) listop = (LISTOP *)newLISTOP(OP_CUSTOM, flags, first, last); #else NewOp(1101, listop, 1, LISTOP); listop->op_type = (OPCODE)OP_CUSTOM; listop->op_first = first; if(first) first->op_sibling = last; listop->op_last = last; listop->op_flags = (U8)(flags | OPf_KIDS); if(last) listop->op_private = (U8)(2 | (flags >> 8)); else if(first) listop->op_private = (U8)(1 | (flags >> 8)); else listop->op_private = (U8)(flags >> 8); #endif listop->op_ppaddr = func; return (OP *)listop; } static OP *S_newLOGOP_CUSTOM(pTHX_ OP *(*func)(pTHX), U32 flags, OP *first, OP *other) { OP *o; #if HAVE_PERL_VERSION(5,22,0) o = newLOGOP(OP_CUSTOM, flags, first, other); #else /* Parts of this code copypasted from perl 5.20.0's op.c S_new_logop() */ LOGOP *logop; first = op_contextualize(first, G_SCALAR); NewOp(1101, logop, 1, LOGOP); logop->op_type = (OPCODE)OP_CUSTOM; logop->op_ppaddr = NULL; /* Because caller only overrides it anyway */ logop->op_first = first; logop->op_flags = (U8)(flags | OPf_KIDS); logop->op_other = LINKLIST(other); logop->op_private = (U8)(1 | (flags >> 8)); /* Link in postfix order */ logop->op_next = LINKLIST(first); first->op_next = (OP *)logop; first->op_sibling = other; /* No CHECKOP for OP_CUSTOM */ o = newUNOP(OP_NULL, 0, (OP *)logop); other->op_next = o; #endif /* the returned op is actually an UNOP that's either NULL or NOT; the real * logop is the op_next of it */ cUNOPx(o)->op_first->op_ppaddr = func; return o; } Data-Checks-0.10/hax/optree-additions.c.inc000444001750001750 516314660677075 17354 0ustar00leoleo000000000000/* vi: set ft=c : */ #define newAELEMOP(flags, first, key) S_newAELEMOP(aTHX_ flags, first, key) static OP *S_newAELEMOP(pTHX_ U32 flags, OP *first, I32 key) { #if HAVE_PERL_VERSION(5,16,0) if(key >= -128 && key < 128 && first->op_type == OP_PADAV) { OP *o = newOP(OP_AELEMFAST_LEX, flags); o->op_private = (I8)key; o->op_targ = first->op_targ; op_free(first); return o; } #endif return newBINOP(OP_AELEM, flags, first, newSVOP(OP_CONST, 0, newSViv(key))); } #if HAVE_PERL_VERSION(5, 22, 0) # define HAVE_UNOP_AUX #endif #ifndef HAVE_UNOP_AUX typedef struct UNOP_with_IV { UNOP baseop; IV iv; } UNOP_with_IV; #define newUNOP_with_IV(type, flags, first, iv) S_newUNOP_with_IV(aTHX_ type, flags, first, iv) static OP *S_newUNOP_with_IV(pTHX_ I32 type, I32 flags, OP *first, IV iv) { /* Cargoculted from perl's op.c:Perl_newUNOP() */ UNOP_with_IV *op = PerlMemShared_malloc(sizeof(UNOP_with_IV) * 1); NewOp(1101, op, 1, UNOP_with_IV); if(!first) first = newOP(OP_STUB, 0); UNOP *unop = (UNOP *)op; unop->op_type = (OPCODE)type; unop->op_first = first; unop->op_ppaddr = NULL; unop->op_flags = (U8)flags | OPf_KIDS; unop->op_private = (U8)(1 | (flags >> 8)); op->iv = iv; return (OP *)op; } #endif #define newMETHOD_REDIR_OP(rclass, methname, flags) S_newMETHOD_REDIR_OP(aTHX_ rclass, methname, flags) static OP *S_newMETHOD_REDIR_OP(pTHX_ SV *rclass, SV *methname, I32 flags) { #if HAVE_PERL_VERSION(5, 22, 0) OP *op = newMETHOP_named(OP_METHOD_REDIR, flags, methname); # ifdef USE_ITHREADS { /* cargoculted from S_op_relocate_sv() */ PADOFFSET ix = pad_alloc(OP_CONST, SVf_READONLY); PAD_SETSV(ix, rclass); cMETHOPx(op)->op_rclass_targ = ix; } # else cMETHOPx(op)->op_rclass_sv = rclass; # endif #else OP *op = newUNOP(OP_METHOD, flags, newSVOP(OP_CONST, 0, newSVpvf("%" SVf "::%" SVf, rclass, methname))); #endif return op; } /* If `@_` is called "snail", then elements of it can be called "slugs"; i.e. * snails without their container */ #define newSLUGOP(idx) S_newSLUGOP(aTHX_ idx) static OP *S_newSLUGOP(pTHX_ int idx) { OP *op = newGVOP(OP_AELEMFAST, 0, PL_defgv); op->op_private = idx; return op; } #ifndef newLISTOPn /* newLISTOPn was added in 5.39.3 */ # define newLISTOPn(type, flags, ...) S_newLISTOPn(aTHX_ type, flags, __VA_ARGS__) static OP *S_newLISTOPn(pTHX_ OPCODE type, U32 flags, ...) { va_list args; va_start(args, flags); OP *o = newLISTOP(OP_LIST, 0, NULL, NULL); OP *kid; while((kid = va_arg(args, OP *))) o = op_append_elem(OP_LIST, o, kid); va_end(args); return op_convert_list(type, flags, o); } #endif Data-Checks-0.10/hax/perl-backcompat.c.inc000444001750001750 1456314660677075 17172 0ustar00leoleo000000000000/* vi: set ft=c : */ #define HAVE_PERL_VERSION(R, V, S) \ (PERL_REVISION > (R) || (PERL_REVISION == (R) && (PERL_VERSION > (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION >= (S)))))) #ifndef NOT_REACHED # define NOT_REACHED assert(0) #endif #ifndef SvTRUE_NN # define SvTRUE_NN(sv) SvTRUE(sv) #endif #ifndef G_LIST # define G_LIST G_ARRAY #endif #if !HAVE_PERL_VERSION(5, 18, 0) typedef AV PADNAMELIST; # define PadlistARRAY(pl) ((PAD **)AvARRAY(pl)) # define PadlistNAMES(pl) (*PadlistARRAY(pl)) typedef SV PADNAME; # define PadnamePV(pn) (SvPOKp(pn) ? SvPVX(pn) : NULL) # define PadnameLEN(pn) SvCUR(pn) # define PadnameIsSTATE(pn) (!!SvPAD_STATE(pn)) # define PadnameOUTER(pn) (SvFAKE(pn) && !SvPAD_STATE(pn)) # define PadnamelistARRAY(pnl) AvARRAY(pnl) # define PadnamelistMAX(pnl) AvFILLp(pnl) # define PadARRAY(p) AvARRAY(p) # define PadMAX(pad) AvFILLp(pad) #endif #if !HAVE_PERL_VERSION(5, 22, 0) # define CvPADLIST_set(cv, padlist) (CvPADLIST(cv) = padlist) # define newPADNAMEpvn(p,n) S_newPADNAMEpvn(aTHX_ p,n) static PADNAME *S_newPADNAMEpvn(pTHX_ const char *pv, STRLEN n) { PADNAME *pn = newSVpvn(pv, n); /* PADNAMEs need to be at least SVt_PVNV in order to store the COP_SEQ_* * fields */ sv_upgrade(pn, SVt_PVNV); return pn; } # define PadnameREFCNT_dec(pn) SvREFCNT_dec(pn) #endif #ifndef av_count # define av_count(av) (AvFILL(av) + 1) #endif #ifndef av_fetch_simple # define av_fetch_simple(av, idx, lval) av_fetch(av, idx, lval) #endif #ifndef av_push_simple # define av_push_simple(av, sv) av_push(av, sv) #endif #ifndef av_store_simple # define av_store_simple(av, key, sv) av_store(av, key, sv) #endif #ifndef av_top_index # define av_top_index(av) AvFILL(av) #endif #ifndef block_end # define block_end(a,b) Perl_block_end(aTHX_ a,b) #endif #ifndef block_start # define block_start(a) Perl_block_start(aTHX_ a) #endif #ifndef cophh_exists_pvs # define cophh_exists_pvs(a,b,c) cBOOL(cophh_fetch_pvs(a,b,c)) #endif #ifndef cv_clone # define cv_clone(a) Perl_cv_clone(aTHX_ a) #endif #ifndef intro_my # define intro_my() Perl_intro_my(aTHX) #endif #ifndef pad_alloc # define pad_alloc(a,b) Perl_pad_alloc(aTHX_ a,b) #endif #ifndef CX_CUR # define CX_CUR() (&cxstack[cxstack_ix]) #endif #if HAVE_PERL_VERSION(5, 24, 0) # define OLDSAVEIX(cx) (cx->blk_oldsaveix) #else # define OLDSAVEIX(cx) (PL_scopestack[cx->blk_oldscopesp-1]) #endif #ifndef OpSIBLING # define OpSIBLING(op) ((op)->op_sibling) #endif #ifndef OpHAS_SIBLING # define OpHAS_SIBLING(op) (cBOOL(OpSIBLING(op))) #endif #ifndef OpMORESIB_set # define OpMORESIB_set(op,sib) ((op)->op_sibling = (sib)) #endif #ifndef OpLASTSIB_set /* older perls don't need to store this at all */ # define OpLASTSIB_set(op,parent) ((op)->op_sibling = NULL) #endif #ifndef op_convert_list # define op_convert_list(type, flags, o) S_op_convert_list(aTHX_ type, flags, o) static OP *S_op_convert_list(pTHX_ I32 type, I32 flags, OP *o) { /* A minimal recreation just for our purposes */ assert( /* A hardcoded list of the optypes we know this will work for */ type == OP_ENTERSUB || type == OP_JOIN || type == OP_PUSH || 0); o->op_type = type; o->op_flags |= flags; o->op_ppaddr = PL_ppaddr[type]; o = PL_check[type](aTHX_ o); /* op_std_init() */ if(PL_opargs[type] & OA_RETSCALAR) o = op_contextualize(o, G_SCALAR); if(PL_opargs[type] & OA_TARGET && !o->op_targ) o->op_targ = pad_alloc(type, SVs_PADTMP); return o; } #endif #ifndef newMETHOP_named # define newMETHOP_named(type, flags, name) newSVOP(type, flags, name) #endif #ifndef PARENT_PAD_INDEX_set # if HAVE_PERL_VERSION(5, 22, 0) # define PARENT_PAD_INDEX_set(pn,val) (PARENT_PAD_INDEX(pn) = val) # else /* stolen from perl-5.20.0's pad.c */ # define PARENT_PAD_INDEX_set(sv,val) \ STMT_START { ((XPVNV*)SvANY(sv))->xnv_u.xpad_cop_seq.xlow = (val); } STMT_END # endif #endif /* On Perl 5.14 this had a different name */ #ifndef pad_add_name_pvn #define pad_add_name_pvn(name, len, flags, typestash, ourstash) MY_pad_add_name(aTHX_ name, len, flags, typestash, ourstash) static PADOFFSET MY_pad_add_name(pTHX_ const char *name, STRLEN len, U32 flags, HV *typestash, HV *ourstash) { /* perl 5.14's Perl_pad_add_name requires a NUL-terminated name */ SV *namesv = sv_2mortal(newSVpvn(name, len)); return Perl_pad_add_name(aTHX_ SvPV_nolen(namesv), SvCUR(namesv), flags, typestash, ourstash); } #endif #if !HAVE_PERL_VERSION(5, 26, 0) # define isIDFIRST_utf8_safe(s, e) (PERL_UNUSED_ARG(e), isIDFIRST_utf8(s)) # define isIDCONT_utf8_safe(s, e) (PERL_UNUSED_ARG(e), isIDCONT_utf8(s)) #endif #ifndef CXp_EVALBLOCK /* before perl 5.34 this was called CXp_TRYBLOCK */ # define CXp_EVALBLOCK CXp_TRYBLOCK #endif #if !HAVE_PERL_VERSION(5, 26, 0) # define sv_set_undef(sv) sv_setsv(sv, &PL_sv_undef) #endif #ifndef newAVav # define newAVav(av) S_newAVav(aTHX_ av) static AV *S_newAVav(pTHX_ AV *av) { AV *ret = newAV(); U32 count = av_count(av); U32 i; for(i = 0; i < count; i++) av_push(ret, newSVsv(AvARRAY(av)[i])); return ret; } #endif #ifndef newAV_alloc_x # define newAV_alloc_x(n) S_newAV_alloc_x(aTHX_ n) static AV *S_newAV_alloc_x(pTHX_ SSize_t n) { AV *av = newAV(); av_extend(av, n-1); return av; } #endif #if !defined(sv_derived_from_hv) && HAVE_PERL_VERSION(5, 16, 0) # define sv_derived_from_hv(sv, hv) MY_sv_derived_from_hv(aTHX_ sv, hv) static bool MY_sv_derived_from_hv(pTHX_ SV *sv, HV *hv) { char *hvname = HvNAME(hv); if(!hvname) return FALSE; return sv_derived_from_pvn(sv, hvname, HvNAMELEN(hv), HvNAMEUTF8(hv) ? SVf_UTF8 : 0); } #endif #ifndef xV_FROM_REF # ifdef PERL_USE_GCC_BRACE_GROUPS # define xV_FROM_REF(XV, ref) \ ({ SV *_ref = ref; assert(SvROK(_ref)); assert(SvTYPE(SvRV(_ref)) == SVt_PV ## XV); (XV *)(SvRV(_ref)); }) # else # define xV_FROM_REF(XV, ref) ((XV *)SvRV(ref)) # endif # define AV_FROM_REF(ref) xV_FROM_REF(AV, ref) # define CV_FROM_REF(ref) xV_FROM_REF(CV, ref) # define HV_FROM_REF(ref) xV_FROM_REF(HV, ref) #endif #ifndef newPADxVOP # define newPADxVOP(type, flags, padix) S_newPADxVOP(aTHX_ type, flags, padix) static OP *S_newPADxVOP(pTHX_ I32 type, I32 flags, PADOFFSET padix) { OP *op = newOP(type, flags); op->op_targ = padix; return op; } #endif Data-Checks-0.10/hax/sv_numcmp.c.inc000444001750001750 346414660677075 16113 0ustar00leoleo000000000000/* vi: set ft=c : */ /* We'd like to call Perl_do_ncmp, except that isn't an exported API function * Here's a near-copy of it */ #define sv_numcmp(left, right) S_sv_numcmp(aTHX_ left, right) static int S_sv_numcmp(pTHX_ SV *left, SV *right) { #ifndef HAVE_BOOL_SvIV_please_nomg /* Before perl 5.18, SvIV_please_nomg() was void-returning */ SvIV_please_nomg(left); SvIV_please_nomg(right); #endif if( #ifdef HAVE_BOOL_SvIV_please_nomg SvIV_please_nomg(right) && SvIV_please_nomg(left) #else SvIOK(left) && SvIOK(right) #endif ) { /* Compare as integers */ switch((SvUOK(left) ? 1 : 0) | (SvUOK(right) ? 2 : 0)) { case 0: /* IV == IV */ { const IV liv = SvIVX(left), riv = SvIVX(right); if (liv < riv) return -1; else if(liv > riv) return 1; else return 0; } case 1: /* UV == IV */ { const IV riv = SvUVX(right); if(riv < 0) return 1; const IV liv = SvIVX(left); if (liv < riv) return -1; else if(liv > riv) return 1; else return 0; } case 2: /* IV == UV */ { const IV liv = SvUVX(left); if(liv < 0) return -1; const IV riv = SvIVX(right); if (liv < riv) return -1; else if(liv > riv) return 1; else return 0; } case 3: /* UV == UV */ { const UV luv = SvUVX(left), ruv = SvUVX(right); if (luv < ruv) return -1; else if(luv > ruv) return 1; else return 0; } } } else { /* Compare NVs */ NV const rnv = SvNV_nomg(right); NV const lnv = SvNV_nomg(left); if (lnv < rnv) return -1; else if(lnv > rnv) return 1; else return 0; } } Data-Checks-0.10/hax/sv_regexp_match.c.inc000444001750001750 127214660677075 17255 0ustar00leoleo000000000000/* vi: set ft=c : */ #define sv_regexp_match(sv, rx) S_sv_regexp_match(aTHX_ sv, rx) static bool S_sv_regexp_match(pTHX_ SV *sv, REGEXP *rx) { STRLEN len; /* These don't get modified, but CALLREGEXEC() doesn't take consts. */ char *strbeg = SvPV(sv, len); char *strend = strbeg + len; STRLEN minlen = RX_MINLEN(rx); if(minlen && len < minlen) /* string is already shorter than the shortest possible match */ return false; /* Entirely unclear from docs what data or flags should be but in practice * it turns out that NULL/0 seems to work fine. * minend can just be 1. */ I32 ret = CALLREGEXEC(rx, strbeg, strend, strbeg, 1, sv, NULL, 0); return (ret != 0); } Data-Checks-0.10/hax/sv_streq.c.inc000444001750001750 122114660677075 15737 0ustar00leoleo000000000000/* vi: set ft=c : */ #ifndef sv_streq_flags # define sv_streq_flags(lhs, rhs, flags) S_sv_streq_flags(aTHX_ lhs, rhs, flags) static bool S_sv_streq_flags(pTHX_ SV *lhs, SV *rhs, U32 flags) { if(flags & SV_GMAGIC) { if(lhs) SvGETMAGIC(lhs); if(rhs) SvGETMAGIC(rhs); } if(!lhs) lhs = &PL_sv_undef; if(!rhs) rhs = &PL_sv_undef; if(!(flags & SV_SKIP_OVERLOAD) && (SvAMAGIC(lhs) || SvAMAGIC(rhs))) { SV *ret = amagic_call(lhs, rhs, seq_amg, 0); if(ret) return SvTRUE(ret); } return sv_eq_flags(lhs, rhs, 0); } #endif #ifndef sv_streq # define sv_streq(lhs, rhs) sv_streq_flags(lhs, rhs, 0) #endif Data-Checks-0.10/inc000755001750001750 014660677075 13015 5ustar00leoleo000000000000Data-Checks-0.10/inc/Module000755001750001750 014660677075 14242 5ustar00leoleo000000000000Data-Checks-0.10/inc/Module/Build000755001750001750 014660677075 15301 5ustar00leoleo000000000000Data-Checks-0.10/inc/Module/Build/with000755001750001750 014660677075 16254 5ustar00leoleo000000000000Data-Checks-0.10/inc/Module/Build/with/XSTests.pm000444001750001750 174514660677075 20333 0ustar00leoleo000000000000package Module::Build::with::XSTests; use v5.22; 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; Data-Checks-0.10/include000755001750001750 014660677075 13667 5ustar00leoleo000000000000Data-Checks-0.10/include/constraints.h000444001750001750 132314660677075 16543 0ustar00leoleo000000000000#ifndef __CONSTRAINTS_H__ #define __CONSTRAINTS_H__ struct Constraint; typedef bool ConstraintFunc(pTHX_ struct Constraint *c, SV *value); struct Constraint { ConstraintFunc *func; int flags; /* avoids needing an entire SV just for a few numeric flag bits */ size_t n; SV *args[0]; }; #define stringify_constraint(c) DataChecks_stringify_constraint(aTHX_ c) #define stringify_constraint_sv(csv) DataChecks_stringify_constraint(aTHX_ (struct Constraint *)SvPVX(csv)) SV *DataChecks_stringify_constraint(pTHX_ struct Constraint *c); #define extract_constraint(sv) DataChecks_extract_constraint(aTHX_ sv) SV *DataChecks_extract_constraint(pTHX_ SV *sv); void boot_Data_Checks__constraints(pTHX); #endif Data-Checks-0.10/lib000755001750001750 014660677075 13012 5ustar00leoleo000000000000Data-Checks-0.10/lib/Data000755001750001750 014660677075 13663 5ustar00leoleo000000000000Data-Checks-0.10/lib/Data/Checks.pm000444001750001750 3072414660677075 15604 0ustar00leoleo000000000000# 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, 2024 -- leonerd@leonerd.org.uk package Data::Checks 0.10; use v5.22; use warnings; use Carp; use builtin qw( export_lexically ); no warnings "experimental::builtin"; sub import { shift; my @syms = @_; # @EXPORT_OK is provided by XS code foreach my $sym ( @syms ) { grep { $sym eq $_ } our @EXPORT_OK or croak "$sym is not exported by ".__PACKAGE__; export_lexically( $sym => \&$sym ); } } require XSLoader; XSLoader::load( __PACKAGE__, our $VERSION ); =head1 NAME C - Value constraint checking =head1 SYNOPSIS With L: =for highlighter perl use v5.26; use Sublike::Extended; use Signature::Attribute::Checked; use Data::Checks qw( Str ); extended sub greet ( $message :Checked(Str) ) { say "Hello, $message"; } greet( "world" ); # is fine greet( undef ); # throws an exception With L: use v5.22; use Object::Pad; use Object::Pad::FieldAttr::Checked; use Data::Checks qw( Str ); class Datum { field $name :param :reader :Checked(Str); } my $x = Datum->new( name => "something" ); # is fine my $y = Datum->new( name => undef ); # throws an exception With L on Perl v5.38 or later: use v5.38; use Syntax::Operator::Is; use Data::Checks qw( Num Object ); my $x = ...; if($x is Num) { say "x can be used as a number"; } elsif($x is Object) { say "x can be used as an object"; } =head1 DESCRIPTION This module provides functions that implement various value constraint checking behaviours. These are the parts made visible by the C import line, in Perl code. It also provides the underlying common framework XS functions to assist in writing modules that actually implement such constraint checking. These parts are not visible in Perl code, but instead made visible at the XS level by the C<#include "DataChecks.h"> directive. See the L section above for several examples of other CPAN modules that make direct use of these constraint checks. =cut =head1 CONSTRAINTS The following constraint checks are inspired by the same-named ones in L. They may be called fully-qualified, or imported I into the calling scope. B to users familiar with C: some of these functions behave slightly differently. In particular, these constraints are generally happy to accept an object reference to a class that provides a conversion overload, whereas the ones in C often are not. Additionally functions that are parametric take their parameters in normal Perl function argument lists, not wrapped in additional array references. =head2 Defined Defined() Accepts any defined value, rejects only C. =head2 Object Object() Accepts any blessed object reference, rejects non-references or references to unblessed data. =head2 Str Str() Accepts any defined non-reference value, or a reference to an object in a class that overloads stringification. Rejects undefined, unblessed references, or references to objects in classes that do not overload stringification. =head2 StrEq StrEq($s) StrEq($s1, $s2, ...) I Accepts any value that passes the L check, and additionally is exactly equal to I the given strings. =head2 StrMatch StrMatch(qr/pattern/) I Accepts any value that passes the L check, and additionally matches the given regexp pattern. Remember that the pattern must be supplied as a C expression, not simply C or C. =head2 Num Num() Accepts any defined non-reference value that is either a plain number, or a string that could be used as one without warning, or a reference to an object in a class that overloads numification. Rejects undefined, not-a-number, strings that would raise a warning if converted to a number, unblessed references, or references to objects in classes that do not overload numification. =head2 NumGT =head2 NumGE =head2 NumLE =head2 NumLT NumGT($bound) NumGE($bound) NumLE($bound) NumLT($bound) I Accepts any value that passes the L check, and additionally is within the bound given. C and C exclude the bound value itself, C and C include it. =head2 NumRange NumRange($boundge, $boundlt) I Accepts any value that passes the L check, and additionally is between the two bounds given. The lower bound is inclusive, and the upper bound is exclusive. This choice is made so that a set of C constraints can easily be created that cover distinct sets of numbers: NumRange(0, 10), NumRange(10, 20), NumRange(20, 30), ... To implement checks with both lower and upper bounds but other kinds of inclusivity, use two C checks combined with an C. For example, to test between 0 and 100 inclusive at both ends: All(NumGE(0), NumLE(100)) Combinations like this are internally implemented as efficiently as a single C constraint. =head2 NumEq NumEq($n) NumEq($n1, $n2, ...) I Accepts any value that passes the L check, and additionally is exactly equal to I the given numbers. =head2 Isa Isa($classname) I Accepts any blessed object reference to an instance of the given class name, or a subclass derived from it (i.e. anything accepted by the C operator). =head2 ArrayRef ArrayRef() I Accepts any plain reference to an array, or any object reference to an instance of a class that provides an array dereference overload. =head2 HashRef HashRef() I Accepts any plain reference to a hash, or any object reference to an instance of a class that provides a hash dereference overload. =head2 Callable Callable() I Accepts any plain reference to a subroutine, or any object reference to an instance of a class that provides a subroutine dereference overload. =head2 Maybe Maybe($C) I Accepts C in addition to anything else accepted by the given constraint. =head2 Any Any($C1, $C2, ...) I Accepts a value that is accepted by at least one of the given constraints. Rejects if none of them accept it. At least one constraint is required; it is an error to try to call C with no arguments. If you need a constraint that accepts any value at all, see L. $C1 | $C2 | ... I This function is used to implement C<|> operator overloading, so constraint checks can be written using this more convenient syntax. =head2 All All($C1, $C2, ...) All() I Accepts a value that is accepted by every one of the given constraints. Rejects if at least one of them rejects it. Note that if no constraints are given, this accepts all possible values. This may be useful as an "accept-all" fallback case for generated code, or other situations where it is required to provide a constraint check but you do not wish to constraint allowed values. =head1 CONSTRAINT METHODS While not intended to be called from regular Perl code, these constraints still act like objects with the following methods. =head2 check $ok = $constraint->check( $value ); I Returns a boolean value indicating whether the constraint accepts the given value. =cut { package # hide from indexer Data::Checks::Constraint; use overload '|' => sub { my ( $lhs, $rhs ) = @_; return Data::Checks::Any( $lhs, $rhs ) }; # For now we won't support or encourage & to mean All() because parsing # of expressions like `Str & Object` doesn't actually work properly. } =head1 XS FUNCTIONS The following functions are provided by the F header file for use in XS modules that implement value constraint checking. =for highlighter c =head2 boot_data_checks void boot_data_checks(double ver); Call this function from your C section in order to initialise the module and load the rest of the support functions. I should either be 0 or a decimal number for the module version requirement; e.g. boot_data_checks(0.01); =head2 make_checkdata struct DataChecks_Checker *make_checkdata(SV *checkspec); Creates a C structure, which wraps the intent of the value constraint check. The returned value is used as the I argument for the remaining functions. The constraint check itself is specified by the C given by I, which should come directly from the user code. The constraint check may be specified in any of three ways: =for highlighter perl =over 4 =item * An B reference in a class which has a C method. Value checks will be invoked as $ok = $checkerobj->check( $value ); =item * A B name as a plain string of a package which has a C method. Value checks will be invoked as $ok = $checkerpkg->check( $value ); =item * A B. Value checks will be invoked with a single argument, as $ok = $checkersub->( $value ); I this form is now deprecated, because it does not easily support a way to query the constraint for its name or stringified form, which is useful when generating error messages. =item * Additionally, the constraint check functions provided by this module may be implemented using any of the above mechanisms, or may use an unspecified fourth different mechanism. Outside code should not rely on what that mechanism may be. =back =for highlighter c Once constructed into a checker structure, the choice of which implementation is used is fixed, and if a method lookup is involved its result is stored directly as a CV pointer for efficiency of later invocations. In either of the first two cases, the reference count on the I SV is increased to account for the argument value used on each invocation. In the third case, the reference SV is not retained, but the underlying CV it refers to has its reference count increased. =head2 free_checkdata void free_checkdata(struct DataChecks_Checker *checker); Releases any stored SVs in the checker structure, and the structure itself. =head2 gen_assertmess void gen_assertmess(struct DataChecks_Checker *checker, SV *name, SV *constraint); Generates and stores a message string for the assert message to be used by L and L. The message will take the form =for highlighter NAME requires a value satisfying CONSTRAINT =for highlighter c Both I and I SVs used as temporary strings to generate the stored message string. Neither SV is retained by the checker directly. =head2 make_assertop OP *make_assertop(struct DataChecks_Checker *checker, OP *argop); Shortcut to calling L with I set to zero. =head2 make_assertop_flags OP *make_assertop_flags(struct DataChecks_Checker *checker, U32 flags, OP *argop); Creates an optree fragment for a value check assertion operation. Given an optree fragment in scalar context that generates an argument value (I), constructs a larger optree fragment that consumes it and checks that the value is accepted by the constraint check given by I. The behaviours of the returned optree fragment will depend on the I. If I is C the returned optree will yield nothing. If I is zero, the return behaviour is not otherwise specified. =head2 check_value bool check_value(struct DataChecks_Checker *checker, SV *value); Checks whether a given SV is accepted by the given constraint check, returning true if so, or false if not. =head2 assert_value void assert_value(struct DataChecks_Checker *checker, SV *value); Checks whether a given SV is accepted by the given constraint check, throwing its assertion message if it does not. =cut =head1 TODO =over 4 =item * Unit constraints - maybe C, some plain-only variants of C and C, some reference types, etc... =item * Structural constraints - C, C, etc... =item * Think about a convenient name for inclusive-bounded numerical constraints. =item * Look into making const-folding work with the C flip-flop operator =back =cut =head1 AUTHOR Paul Evans =cut 0x55AA; Data-Checks-0.10/lib/Data/Checks.xs000444001750001750 1711014660677075 15614 0ustar00leoleo000000000000/* 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, 2024 -- leonerd@leonerd.org.uk */ #define PERL_NO_GET_CONTEXT #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #define HAVE_DATA_CHECKS_IMPL #include "DataChecks.h" struct DataChecks_Checker { CV *cv; struct Constraint *constraint; SV *arg0; SV *assertmess; }; #include "perl-backcompat.c.inc" #include "newOP_CUSTOM.c.inc" #include "optree-additions.c.inc" #include "constraints.h" #define warn_deprecated(...) Perl_ck_warner(aTHX_ packWARN(WARN_DEPRECATED), __VA_ARGS__) static struct DataChecks_Checker *S_DataChecks_make_checkdata(pTHX_ SV *checkspec) { HV *stash = NULL; CV *checkcv = NULL; struct Constraint *constraint = NULL; if(SvROK(checkspec) && SvOBJECT(SvRV(checkspec))) stash = SvSTASH(SvRV(checkspec)); else if(SvPOK(checkspec) && (stash = gv_stashsv(checkspec, GV_NOADD_NOINIT))) ; /* checkspec is package name */ else if(SvROK(checkspec) && !SvOBJECT(SvRV(checkspec)) && SvTYPE(SvRV(checkspec)) == SVt_PVCV) { /* checkspec is a code reference */ warn_deprecated("Using a CODE reference as a constraint checker is deprecated"); checkcv = (CV *)SvREFCNT_inc(SvRV(checkspec)); checkspec = NULL; } else croak("Expected the checker expression to yield an object or code reference or package name; got %" SVf " instead", SVfARG(checkspec)); if(stash && sv_isa(checkspec, "Data::Checks::Constraint")) { constraint = (struct Constraint *)SvPVX(SvRV(checkspec)); /* arg0 will store checkspec pointer, thus ensuring this SV is retained */ } else if(!checkcv) { GV *methgv; if(!(methgv = gv_fetchmeth_pv(stash, "check", -1, 0))) croak("Expected that the checker expression can ->check"); if(!GvCV(methgv)) croak("Expected that methgv has a GvCV"); checkcv = (CV *)SvREFCNT_inc(GvCV(methgv)); } struct DataChecks_Checker *checker; Newx(checker, 1, struct DataChecks_Checker); *checker = (struct DataChecks_Checker){ .cv = checkcv, .constraint = constraint, .arg0 = SvREFCNT_inc(checkspec), }; return checker; } static void S_DataChecks_free_checkdata(pTHX_ struct DataChecks_Checker *checker) { if(checker->assertmess) SvREFCNT_dec(checker->assertmess); SvREFCNT_dec(checker->cv); if(checker->arg0) SvREFCNT_dec(checker->arg0); Safefree(checker); } static void S_DataChecks_gen_assertmess(pTHX_ struct DataChecks_Checker *checker, SV *name, SV *constraint) { if(!constraint || !SvOK(constraint)) { if(checker->constraint) constraint = stringify_constraint(checker->constraint); else if(checker->arg0) { constraint = sv_newmortal(); sv_copypv(constraint, checker->arg0); } else croak("gen_assertmess requires a constraint name if the constraint is a CODE reference"); } checker->assertmess = newSVpvf("%" SVf " requires a value satisfying %" SVf, SVfARG(name), SVfARG(constraint)); } static XOP xop_invoke_checkfunc; static OP *pp_invoke_checkfunc(pTHX) { dSP; struct Constraint *constraint = (struct Constraint *)cUNOP_AUX->op_aux; SV *value = POPs; PUSHs(boolSV((*constraint->func)(aTHX_ constraint, value))); RETURN; } #define make_checkop(checker, argop) S_DataChecks_make_checkop(aTHX_ checker, argop) static OP *S_DataChecks_make_checkop(pTHX_ struct DataChecks_Checker *checker, OP *argop) { if(checker->constraint) { return newUNOP_AUX_CUSTOM(&pp_invoke_checkfunc, OPf_WANT_SCALAR, argop, (UNOP_AUX_item *)checker->constraint); } if(checker->cv && checker->arg0) /* checkcv($checker, ARGOP) ... */ return newLISTOPn(OP_ENTERSUB, OPf_WANT_SCALAR|OPf_STACKED, newSVOP(OP_CONST, 0, SvREFCNT_inc(checker->arg0)), argop, newSVOP(OP_CONST, 0, SvREFCNT_inc(checker->cv)), NULL); if(checker->cv) /* checkcv(ARGOP) ... */ return newLISTOPn(OP_ENTERSUB, OPf_WANT_SCALAR|OPf_STACKED, argop, newSVOP(OP_CONST, 0, SvREFCNT_inc(checker->cv)), NULL); croak("ARGH unsure how to make checkop"); } static OP *S_DataChecks_make_assertop(pTHX_ struct DataChecks_Checker *checker, U32 flags, OP *argop) { U32 want = flags & OPf_WANT; flags &= ~OPf_WANT; bool want_void = (want == OPf_WANT_VOID); if(flags) croak("TODO: make_assertop with flags 0x%x", flags); OP *o = newLOGOP(OP_OR, 0, make_checkop(checker, argop), /* ... or die MESSAGE */ newLISTOPn(OP_DIE, 0, newSVOP(OP_CONST, 0, SvREFCNT_inc(checker->assertmess)), NULL)); if(want_void) { /* Wrap it in a full enter/leave pair so it unstacks correctly */ o->op_flags |= OPf_PARENS; o = op_contextualize(op_scope(o), OPf_WANT_VOID); } return o; } static OP *S_DataChecks_make_assertop_v0(pTHX_ struct DataChecks_Checker *checker, OP *argop) { return S_DataChecks_make_assertop(aTHX_ checker, 0, argop); } static bool S_DataChecks_check_value(pTHX_ struct DataChecks_Checker *checker, SV *value) { if(checker->constraint) { return (*checker->constraint->func)(aTHX_ checker->constraint, value); } dSP; ENTER; SAVETMPS; EXTEND(SP, 2); PUSHMARK(SP); if(checker->arg0) PUSHs(sv_mortalcopy(checker->arg0)); PUSHs(value); /* Yes we're pushing the SV itself */ PUTBACK; call_sv((SV *)checker->cv, G_SCALAR); SPAGAIN; bool ok = SvTRUEx(POPs); PUTBACK; FREETMPS; LEAVE; return ok; } static void S_DataChecks_assert_value(pTHX_ struct DataChecks_Checker *checker, SV *value) { if(check_value(checker, value)) return; croak_sv(checker->assertmess); } MODULE = Data::Checks PACKAGE = Data::Checks::Debug void stringify_constraint(SV *sv) PPCODE: /* Prevent XSUB from double-mortalising it */ PUSHs(stringify_constraint_sv(extract_constraint(sv))); XSRETURN(1); MODULE = Data::Checks PACKAGE = Data::Checks::Constraint void DESTROY(SV *self) CODE: { struct Constraint *c = (struct Constraint *)SvPVX(SvRV(self)); for(int i = c->n - 1; i >= 0; i--) SvREFCNT_dec(c->args[i]); } bool check(SV *self, SV *value) CODE: struct Constraint *c = (struct Constraint *)SvPVX(SvRV(self)); RETVAL = (c->func)(aTHX_ c, value); OUTPUT: RETVAL MODULE = Data::Checks PACKAGE = Data::Checks BOOT: sv_setiv(*hv_fetchs(PL_modglobal, "Data::Checks/ABIVERSION_MIN", GV_ADD), 0); sv_setiv(*hv_fetchs(PL_modglobal, "Data::Checks/ABIVERSION_MAX", GV_ADD), DATACHECKS_ABI_VERSION); sv_setuv(*hv_fetchs(PL_modglobal, "Data::Checks/make_checkdata()@0", GV_ADD), PTR2UV(&S_DataChecks_make_checkdata)); sv_setuv(*hv_fetchs(PL_modglobal, "Data::Checks/free_checkdata()@0", GV_ADD), PTR2UV(&S_DataChecks_free_checkdata)); sv_setuv(*hv_fetchs(PL_modglobal, "Data::Checks/gen_assertmess()@0", GV_ADD), PTR2UV(&S_DataChecks_gen_assertmess)); sv_setuv(*hv_fetchs(PL_modglobal, "Data::Checks/make_assertop()@0", GV_ADD), PTR2UV(&S_DataChecks_make_assertop_v0)); sv_setuv(*hv_fetchs(PL_modglobal, "Data::Checks/make_assertop()@1", GV_ADD), PTR2UV(&S_DataChecks_make_assertop)); sv_setuv(*hv_fetchs(PL_modglobal, "Data::Checks/check_value()@0", GV_ADD), PTR2UV(&S_DataChecks_check_value)); sv_setuv(*hv_fetchs(PL_modglobal, "Data::Checks/assert_value()@0", GV_ADD), PTR2UV(&S_DataChecks_assert_value)); boot_Data_Checks__constraints(aTHX); XopENTRY_set(&xop_invoke_checkfunc, xop_name, "invoke_checkfunc"); XopENTRY_set(&xop_invoke_checkfunc, xop_desc, "invoke checkfunc"); XopENTRY_set(&xop_invoke_checkfunc, xop_class, OA_UNOP_AUX); Perl_custom_op_register(aTHX_ &pp_invoke_checkfunc, &xop_invoke_checkfunc); Data-Checks-0.10/lib/Data/Checks000755001750001750 014660677075 15063 5ustar00leoleo000000000000Data-Checks-0.10/lib/Data/Checks/Builder.pm000444001750001750 343014660677075 17144 0ustar00leoleo000000000000# 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, 2024 -- leonerd@leonerd.org.uk package Data::Checks::Builder 0.44; use v5.22; use warnings; =head1 NAME C - build-time support for C =head1 SYNOPSIS In F: use Data::Checks::Builder; my $build = Module::Build->new( ..., configure_requires => { ... 'Data::Checks::Builder' => 0, } ); Data::Checks::Builder->extend_module_build( $build ); ... =head1 DESCRIPTION This module provides a build-time helper to assist authors writing XS modules that use L. It prepares a L-using distribution to be able to make use of C. =cut =head2 extra_compiler_flags @flags = Data::Checks::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 file. =cut sub extra_compiler_flags { shift; require File::ShareDir; require File::Spec; require Data::Checks; return "-I" . File::Spec->catdir( File::ShareDir::module_dir( "Data::Checks" ), "include" ); } =head2 extend_module_build Data::Checks::Builder->extend_module_build( $build ); A convenient shortcut for performing all the tasks necessary to make a L-based distribution use the helper. =cut sub extend_module_build { my $self = shift; my ( $build ) = @_; # preserve existing flags my @flags = @{ $build->extra_compiler_flags }; push @flags, $self->extra_compiler_flags; $build->extra_compiler_flags( @flags ); } =head1 AUTHOR Paul Evans =cut 0x55AA; Data-Checks-0.10/share000755001750001750 014660677075 13346 5ustar00leoleo000000000000Data-Checks-0.10/share/include000755001750001750 014660677075 14771 5ustar00leoleo000000000000Data-Checks-0.10/share/include/DataChecks.h000444001750001750 1035114660677075 17311 0ustar00leoleo000000000000#ifndef __DATA_CHECKS_H__ #define __DATA_CHECKS_H__ #ifdef HAVE_DATA_CHECKS_IMPL # define DECLARE_FUNCTION(name, rettype, args, argnames) \ static rettype S_DataChecks_##name args; #else # define DECLARE_FUNCTION(name, rettype, args, argnames) \ static rettype (*name##_func) args; \ static rettype S_DataChecks_##name args \ { \ if(!name##_func) \ croak("Must call boot_data_checks() first"); \ return (*name##_func) argnames; \ } #endif #define DATACHECKS_ABI_VERSION 1 struct DataChecks_Checker; #define make_checkdata(checkspec) S_DataChecks_make_checkdata(aTHX_ checkspec) DECLARE_FUNCTION(make_checkdata, struct DataChecks_Checker *, (pTHX_ SV *checkspec), (aTHX_ checkspec)) #define free_checkdata(checker) S_DataChecks_free_checkdata(aTHX_ checker) DECLARE_FUNCTION(free_checkdata, void, (pTHX_ struct DataChecks_Checker *checker), (aTHX_ checker)) #define gen_assertmess(checker, name, constraint) S_DataChecks_gen_assertmess(aTHX_ checker, name, constraint) DECLARE_FUNCTION(gen_assertmess, void, (pTHX_ struct DataChecks_Checker *checker, SV *name, SV *constraint), (aTHX_ checker, name, constraint)) #define make_assertop(checker, argop) S_DataChecks_make_assertop_flags(aTHX_ checker, 0, argop) #define make_assertop_flags(checker, flags, argop) S_DataChecks_make_assertop_flags(aTHX_ checker, flags, argop) DECLARE_FUNCTION(make_assertop_flags, OP *, (pTHX_ struct DataChecks_Checker *checker, U32 flags, OP *argop), (aTHX_ checker, flags, argop)) #define check_value(checker, value) S_DataChecks_check_value(aTHX_ checker, value) DECLARE_FUNCTION(check_value, bool, (pTHX_ struct DataChecks_Checker *checker, SV *value), (aTHX_ checker, value)) #define assert_value(checker, value) S_DataChecks_assert_value(aTHX_ checker, value) DECLARE_FUNCTION(assert_value, void, (pTHX_ struct DataChecks_Checker *checker, SV *value), (aTHX_ checker, value)) #ifndef HAVE_DATA_CHECKS_IMPL #define must_SvUV_from_modglobal(key) S_must_SvUV_from_modglobal(aTHX_ key) static UV S_must_SvUV_from_modglobal(pTHX_ const char *key) { SV **svp = hv_fetch(PL_modglobal, key, strlen(key), 0); if(!svp) croak("Cannot load DataChecks.h: Expected to find %s in PL_modglobal", key); return SvUV(*svp); } #define boot_data_checks(ver) S_boot_data_checks(aTHX_ ver) static void S_boot_data_checks(pTHX_ double ver) { SV **svp; if(ver < 0.02) ver = 0.02; SV *versv = newSVnv(ver); load_module(PERL_LOADMOD_NOIMPORT, newSVpvs("Data::Checks"), versv, NULL); svp = hv_fetchs(PL_modglobal, "Data::Checks/ABIVERSION_MIN", 0); if(!svp) croak("Data::Checks ABI minimum version missing"); int abi_ver = SvIV(*svp); if(abi_ver > DATACHECKS_ABI_VERSION) croak("Data::Checks ABI version mismatch - library supports >= %d, compiled for %d", abi_ver, DATACHECKS_ABI_VERSION); svp = hv_fetchs(PL_modglobal, "Data::Checks/ABIVERSION_MAX", 0); abi_ver = SvIV(*svp); if(abi_ver < DATACHECKS_ABI_VERSION) croak("Data::Checks ABI version mismatch - library supports <= %d, compiled for %d", abi_ver, DATACHECKS_ABI_VERSION); make_checkdata_func = INT2PTR(struct DataChecks_Checker *(*)(pTHX_ SV *checkspec), must_SvUV_from_modglobal("Data::Checks/make_checkdata()@0")); free_checkdata_func = INT2PTR(void (*)(pTHX_ struct DataChecks_Checker *checker), must_SvUV_from_modglobal("Data::Checks/free_checkdata()@0")); gen_assertmess_func = INT2PTR(void (*)(pTHX_ struct DataChecks_Checker *checker, SV *name, SV *constraint), must_SvUV_from_modglobal("Data::Checks/gen_assertmess()@0")); make_assertop_flags_func = INT2PTR(OP *(*)(pTHX_ struct DataChecks_Checker *checker, U32 flags, OP *argop), must_SvUV_from_modglobal("Data::Checks/make_assertop()@1")); check_value_func = INT2PTR(bool (*)(pTHX_ struct DataChecks_Checker *checker, SV *value), must_SvUV_from_modglobal("Data::Checks/check_value()@0")); assert_value_func = INT2PTR(void (*)(pTHX_ struct DataChecks_Checker *checker, SV *value), must_SvUV_from_modglobal("Data::Checks/assert_value()@0")); } #endif /* defined HAVE_DATA_CHECKS_IMPL */ #endif Data-Checks-0.10/src000755001750001750 014660677075 13033 5ustar00leoleo000000000000Data-Checks-0.10/src/constraints.c000444001750001750 5726114660677075 15736 0ustar00leoleo000000000000/* 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, 2024 -- leonerd@leonerd.org.uk */ #include "EXTERN.h" #include "perl.h" #include "constraints.h" #include "perl-backcompat.c.inc" #include "make_argcheck_ops.c.inc" #include "newOP_CUSTOM.c.inc" #include "optree-additions.c.inc" #include "sv_regexp_match.c.inc" #include "sv_streq.c.inc" #include "sv_numcmp.c.inc" #include "ckcall_constfold.c.inc" #define newSVsv_num(osv) S_newSVsv_num(aTHX_ osv) static SV *S_newSVsv_num(pTHX_ SV *osv) { if(SvNOK(osv)) return newSVnv(SvNV(osv)); if(SvIOK(osv) && SvIsUV(osv)) return newSVuv(SvUV(osv)); return newSViv(SvIV(osv)); } #define newSVsv_str(osv) S_newSVsv_str(aTHX_ osv) static SV *S_newSVsv_str(pTHX_ SV *osv) { SV *nsv = newSV(0); sv_copypv(nsv, osv); return nsv; } #if !HAVE_PERL_VERSION(5, 32, 0) # define sv_isa_sv(sv, namesv) S_sv_isa_sv(aTHX_ sv, namesv) static bool S_sv_isa_sv(pTHX_ SV *sv, SV *namesv) { if(!SvROK(sv) || !SvOBJECT(SvRV(sv))) return FALSE; /* TODO: ->isa invocation */ return sv_derived_from_sv(sv, namesv, 0); } #endif #ifndef op_force_list # define op_force_list(o) S_op_force_list(aTHX_ o) static OP *S_op_force_list(pTHX_ OP *o) /* Sufficiently good enough for our purposes */ { op_null(o); return o; } #endif #define alloc_constraint(svp, constraintp, func, n) S_alloc_constraint(aTHX_ svp, constraintp, func, n) static void S_alloc_constraint(pTHX_ SV **svp, struct Constraint **constraintp, ConstraintFunc *func, size_t n) { HV *constraint_stash = gv_stashpvs("Data::Checks::Constraint", GV_ADD); SV *sv = newSV(sizeof(struct Constraint) + n*sizeof(SV *)); SvPOK_on(sv); struct Constraint *constraint = (struct Constraint *)SvPVX(sv); *constraint = (struct Constraint){ .func = func, .n = n, }; for(int i = 0; i < n; i++) constraint->args[i] = NULL; *svp = sv_bless(newRV_noinc(sv), constraint_stash); *constraintp = constraint; } SV *DataChecks_extract_constraint(pTHX_ SV *sv) { if(!sv_isa(sv, "Data::Checks::Constraint")) croak("Expected a Constraint instance as argument"); return SvRV(sv); } #define sv_has_overload(sv, method) S_sv_has_overload(aTHX_ sv, method) static bool S_sv_has_overload(pTHX_ SV *sv, int method) { assert(SvROK(sv)); HV *stash = SvSTASH(SvRV(sv)); if(!stash || !Gv_AMG(stash)) return false; MAGIC *mg = mg_find((const SV *)stash, PERL_MAGIC_overload_table); if(!mg) return false; CV **cvp = NULL; if(AMT_AMAGIC((AMT *)mg->mg_ptr)) cvp = ((AMT *)mg->mg_ptr)->table; if(!cvp) return false; CV *cv = cvp[method]; if(!cv) return false; return true; } static bool constraint_Defined(pTHX_ struct Constraint *c, SV *value) { return SvOK(value); } static bool constraint_Object(pTHX_ struct Constraint *c, SV *value) { return SvROK(value) && SvOBJECT(SvRV(value)); } static bool constraint_Str(pTHX_ struct Constraint *c, SV *value) { if(!SvOK(value)) return false; if(SvROK(value)) { SV *rv = SvRV(value); if(!SvOBJECT(rv)) return false; if(sv_has_overload(value, string_amg)) return true; return false; } else { return true; } } static bool constraint_StrEq(pTHX_ struct Constraint *c, SV *value) { if(!constraint_Str(aTHX_ c, value)) return false; SV *strs = c->args[0]; if(SvTYPE(strs) != SVt_PVAV) return sv_streq(value, strs); /* TODO: If we were to sort the values initially we could binary-search * these much faster */ size_t n = av_count((AV *)strs); SV **svp = AvARRAY(strs); for(size_t i = 0; i < n; i++) if(sv_streq(value, svp[i])) return true; return false; } static SV *mk_constraint_StrEq(pTHX_ size_t nargs, SV **args) { SV *ret; struct Constraint *c; alloc_constraint(&ret, &c, &constraint_StrEq, 1); sv_2mortal(ret); if(!nargs) croak("Require at least one string for StrEq()"); if(nargs == 1) /* We can just store a single string directly */ c->args[0] = newSVsv_str(args[0]); else { AV *strs = newAV_alloc_x(nargs); for(size_t i = 0; i < nargs; i++) av_store(strs, i, newSVsv_str(args[i])); c->args[0] = (SV *)strs; } return ret; } static bool constraint_StrMatch(pTHX_ struct Constraint *c, SV *value) { if(!constraint_Str(aTHX_ c, value)) return false; return sv_regexp_match(value, (REGEXP *)c->args[0]); } static SV *mk_constraint_StrMatch(pTHX_ SV *arg0) { SV *ret; struct Constraint *c; alloc_constraint(&ret, &c, &constraint_StrMatch, 1); sv_2mortal(ret); if(!SvROK(arg0) || !SvRXOK(SvRV(arg0))) croak("Require a pre-compiled regexp pattern for StrMatch()"); c->args[0] = SvREFCNT_inc(SvRV(arg0)); return ret; } static bool constraint_Num(pTHX_ struct Constraint *c, SV *value) { if(!SvOK(value)) return false; if(SvROK(value)) { SV *rv = SvRV(value); if(!SvOBJECT(rv)) return false; if(sv_has_overload(value, numer_amg)) return true; return false; } else if(SvPOK(value)) { if(!looks_like_number(value)) return false; // reject NaN if(SvPVX(value)[0] == 'N' || SvPVX(value)[0] == 'n') return false; return true; } else { // reject NaN if(SvNOK(value) && Perl_isnan(SvNV(value))) return false; return true; } } enum { NUMBOUND_LOWER_INCLUSIVE = (1<<0), NUMBOUND_UPPER_INCLUSIVE = (1<<1), }; static bool constraint_NumBound(pTHX_ struct Constraint *c, SV *value) { /* First off it must be a Num */ if(!constraint_Num(aTHX_ c, value)) return false; if(c->args[0]) { int cmp = sv_numcmp(c->args[0], value); if(cmp > 0 || (cmp == 0 && !(c->flags & NUMBOUND_LOWER_INCLUSIVE))) return false; } if(c->args[1]) { int cmp = sv_numcmp(value, c->args[1]); if(cmp > 0 || (cmp == 0 && !(c->flags & NUMBOUND_UPPER_INCLUSIVE))) return false; } return true; } static SV *mk_constraint_NumGT(pTHX_ SV *arg0) { SV *ret; struct Constraint *c; alloc_constraint(&ret, &c, &constraint_NumBound, 2); sv_2mortal(ret); c->args[0] = newSVsv_num(arg0); c->args[1] = NULL; return ret; } static SV *mk_constraint_NumGE(pTHX_ SV *arg0) { SV *ret; struct Constraint *c; alloc_constraint(&ret, &c, &constraint_NumBound, 2); sv_2mortal(ret); c->flags = NUMBOUND_LOWER_INCLUSIVE; c->args[0] = newSVsv_num(arg0); c->args[1] = NULL; return ret; } static SV *mk_constraint_NumLE(pTHX_ SV *arg0) { SV *ret; struct Constraint *c; alloc_constraint(&ret, &c, &constraint_NumBound, 2); sv_2mortal(ret); c->flags = NUMBOUND_UPPER_INCLUSIVE; c->args[0] = NULL; c->args[1] = newSVsv_num(arg0); return ret; } static SV *mk_constraint_NumLT(pTHX_ SV *arg0) { SV *ret; struct Constraint *c; alloc_constraint(&ret, &c, &constraint_NumBound, 2); sv_2mortal(ret); c->args[0] = NULL; c->args[1] = newSVsv_num(arg0); return ret; } static SV *mk_constraint_NumRange(pTHX_ SV *arg0, SV *arg1) { SV *ret; struct Constraint *c; alloc_constraint(&ret, &c, &constraint_NumBound, 2); sv_2mortal(ret); c->flags = NUMBOUND_LOWER_INCLUSIVE; c->args[0] = newSVsv_num(arg0); c->args[1] = newSVsv_num(arg1); return ret; } static bool constraint_NumEq(pTHX_ struct Constraint *c, SV *value) { if(!constraint_Num(aTHX_ c, value)) return false; SV *nums = c->args[0]; if(SvTYPE(nums) != SVt_PVAV) return sv_numcmp(value, nums) == 0; /* TODO: If we were to sort the values initially we could binary-search * these much faster */ size_t n = av_count((AV *)nums); SV **svp = AvARRAY(nums); for(size_t i = 0; i < n; i++) if(sv_numcmp(value, svp[i]) == 0) return true; return false; } static SV *mk_constraint_NumEq(pTHX_ size_t nargs, SV **args) { SV *ret; struct Constraint *c; alloc_constraint(&ret, &c, &constraint_NumEq, 1); sv_2mortal(ret); if(!nargs) croak("Require at least one number for NumEq()"); if(nargs == 1) /* We can just store a single number directly */ c->args[0] = newSVsv_num(args[0]); else { AV *nums = newAV_alloc_x(nargs); for(size_t i = 0; i < nargs; i++) av_store(nums, i, newSVsv_num(args[i])); c->args[0] = (SV *)nums; } return ret; } static bool constraint_Isa(pTHX_ struct Constraint *c, SV *value) { return sv_isa_sv(value, c->args[0]); } static SV *mk_constraint_Isa(pTHX_ SV *arg0) { SV *ret; struct Constraint *c; alloc_constraint(&ret, &c, &constraint_Isa, 1); c->args[0] = newSVsv(arg0); return sv_2mortal(ret); } static bool constraint_ArrayRef(pTHX_ struct Constraint *c, SV *value) { if(!SvOK(value) || !SvROK(value)) return false; SV *rv = SvRV(value); if(!SvOBJECT(rv)) /* plain ref */ return SvTYPE(rv) == SVt_PVAV; else return sv_has_overload(value, to_av_amg); } static bool constraint_HashRef(pTHX_ struct Constraint *c, SV *value) { if(!SvOK(value) || !SvROK(value)) return false; SV *rv = SvRV(value); if(!SvOBJECT(rv)) /* plain ref */ return SvTYPE(rv) == SVt_PVHV; else return sv_has_overload(value, to_hv_amg); } static bool constraint_Callable(pTHX_ struct Constraint *c, SV *value) { if(!SvOK(value) || !SvROK(value)) return false; SV *rv = SvRV(value); if(!SvOBJECT(rv)) /* plain ref */ return SvTYPE(rv) == SVt_PVCV; else return sv_has_overload(value, to_cv_amg); } static bool constraint_Maybe(pTHX_ struct Constraint *c, SV *value) { if(!SvOK(value)) return true; struct Constraint *inner = (struct Constraint *)SvPVX(c->args[0]); return (*inner->func)(aTHX_ inner, value); } static SV *mk_constraint_Maybe(pTHX_ SV *arg0) { SV *inner = extract_constraint(arg0); SV *ret; struct Constraint *c; alloc_constraint(&ret, &c, &constraint_Maybe, 1); sv_2mortal(ret); c->args[0] = SvREFCNT_inc(inner); return ret; } static bool constraint_Any(pTHX_ struct Constraint *c, SV *value) { AV *inners = (AV *)c->args[0]; SV **innersvs = AvARRAY(inners); size_t n = av_count(inners); for(size_t i = 0; i < n; i++) { struct Constraint *inner = (struct Constraint *)SvPVX(innersvs[i]); if((*inner->func)(aTHX_ inner, value)) return true; } return false; } static SV *mk_constraint_Any(pTHX_ size_t nargs, SV **args) { if(!nargs) croak("Any() requires at least one inner constraint"); if(nargs == 1) return args[0]; AV *inners = newAV(); sv_2mortal((SV *)inners); // in case of croak during construction for(size_t i = 0; i < nargs; i++) { SV *innersv = extract_constraint(args[i]); struct Constraint *inner = (struct Constraint *)SvPVX(innersv); if(inner->func == &constraint_Any) { AV *kidav = (AV *)inner->args[0]; size_t nkids = av_count(kidav); for(size_t kidi = 0; kidi < nkids; kidi++) { av_push(inners, SvREFCNT_inc(AvARRAY(kidav)[kidi])); } } else av_push(inners, SvREFCNT_inc(innersv)); } SV *ret; struct Constraint *c; alloc_constraint(&ret, &c, &constraint_Any, 1); sv_2mortal(ret); c->args[0] = SvREFCNT_inc(inners); return ret; } static bool constraint_All(pTHX_ struct Constraint *c, SV *value) { AV *inners = (AV *)c->args[0]; if(!inners) return true; SV **innersvs = AvARRAY(inners); size_t n = av_count(inners); for(size_t i = 0; i < n; i++) { struct Constraint *inner = (struct Constraint *)SvPVX(innersvs[i]); if(!(*inner->func)(aTHX_ inner, value)) return false; } return true; } static SV *mk_constraint_All(pTHX_ size_t nargs, SV **args) { /* nargs == 0 is valid */ if(nargs == 1) return args[0]; AV *inners = NULL; if(nargs) { inners = newAV(); sv_2mortal((SV *)inners); // in case of croak during construction /* However many NumBound constraints are in 'inners' it's always possible to * optimise them down into just one */ struct Constraint *all_nums = NULL; SV *all_nums_sv; for(size_t i = 0; i < nargs; i++) { SV *innersv = extract_constraint(args[i]); struct Constraint *inner = (struct Constraint *)SvPVX(innersv); if(inner->func == &constraint_All) { AV *kidav = (AV *)inner->args[0]; size_t nkids = av_count(kidav); for(size_t kidi = 0; kidi < nkids; kidi++) { av_push(inners, SvREFCNT_inc(AvARRAY(kidav)[kidi])); } } else if(inner->func == &constraint_NumBound) { if(!all_nums) { alloc_constraint(&all_nums_sv, &all_nums, &constraint_NumBound, 2); av_push(inners, SvRV(all_nums_sv)); /* no SvREFCNT_inc() */ } SV *innerL = inner->args[0], *innerU = inner->args[1]; int cmp; if(innerL) { if(!all_nums->args[0] || (cmp = sv_numcmp(all_nums->args[0], innerL)) < 0) { SvREFCNT_dec(all_nums->args[0]); all_nums->args[0] = newSVsv_num(innerL); all_nums->flags = (all_nums->flags & ~NUMBOUND_LOWER_INCLUSIVE) | (inner->flags & NUMBOUND_LOWER_INCLUSIVE); } else if(cmp == 0 && !(inner->flags & NUMBOUND_LOWER_INCLUSIVE)) all_nums->flags &= ~NUMBOUND_LOWER_INCLUSIVE; } if(innerU) { if(!all_nums->args[1] || (cmp = sv_numcmp(all_nums->args[1], innerU)) > 0) { SvREFCNT_dec(all_nums->args[1]); all_nums->args[1] = newSVsv_num(innerU); all_nums->flags = (all_nums->flags & ~NUMBOUND_UPPER_INCLUSIVE) | (inner->flags & NUMBOUND_UPPER_INCLUSIVE); } else if(cmp == 0 && !(inner->flags & NUMBOUND_UPPER_INCLUSIVE)) all_nums->flags &= ~NUMBOUND_UPPER_INCLUSIVE; } } else av_push(inners, SvREFCNT_inc(innersv)); } /* it's possible we've now squashed all the Num* bounds into a single one * and nothing else is left */ if(all_nums_sv && av_count(inners) == 1) return all_nums_sv; } SV *ret; struct Constraint *c; alloc_constraint(&ret, &c, &constraint_All, 1); sv_2mortal(ret); c->args[0] = SvREFCNT_inc(inners); return ret; } #define MAKE_0ARG_CONSTRAINT(name) S_make_0arg_constraint(aTHX_ #name, &constraint_##name) static void S_make_0arg_constraint(pTHX_ const char *name, ConstraintFunc *func) { HV *stash = gv_stashpvs("Data::Checks", GV_ADD); AV *exportok = get_av("Data::Checks::EXPORT_OK", GV_ADD); SV *namesv = newSVpvf("Data::Checks::%s", name); /* Before perl 5.38, XSUBs cannot be exported lexically. newCONSTSUB() makes * XSUBs. We'll have to build our own constant-value sub instead */ I32 floor_ix = start_subparse(FALSE, 0); SV *sv; struct Constraint *constraint; alloc_constraint(&sv, &constraint, func, 0); OP *body = make_argcheck_ops(0, 0, 0, namesv); body = op_append_elem(OP_LINESEQ, body, newSTATEOP(0, NULL, newSVOP(OP_CONST, 0, sv))); CV *cv = newATTRSUB(floor_ix, newSVOP(OP_CONST, 0, namesv), NULL, NULL, body); cv_set_call_checker(cv, &ckcall_constfold, &PL_sv_undef); av_push(exportok, newSVpv(name, 0)); } static XOP xop_make_constraint; static OP *pp_make_constraint(pTHX) { dSP; int nargs = PL_op->op_private; SV *ret; switch(nargs) { case 1: { SV *(*mk_constraint)(pTHX_ SV *arg0) = (SV * (*)(pTHX_ SV *))cUNOP_AUX->op_aux; SV *arg0 = POPs; ret = (*mk_constraint)(aTHX_ arg0); break; } case 2: { SV *(*mk_constraint)(pTHX_ SV *arg0, SV *arg1) = (SV * (*)(pTHX_ SV *, SV *))cUNOP_AUX->op_aux; SV *arg1 = POPs; SV *arg0 = POPs; ret = (*mk_constraint)(aTHX_ arg0, arg1); break; } case (U8)-1: { SV *(*mk_constraint)(pTHX_ size_t nargs, SV **args) = (SV * (*)(pTHX_ size_t, SV **))cUNOP_AUX->op_aux; SV **svp = PL_stack_base + POPMARK + 1; size_t nargs = SP - svp + 1; SP -= nargs; if(!nargs) EXTEND(SP, 1); ret = (*mk_constraint)(aTHX_ nargs, svp); break; } default: croak("ARGH unreachable nargs=%d", nargs); } PUSHs(ret); RETURN; } #define MAKE_1ARG_CONSTRAINT(name) S_make_1arg_constraint(aTHX_ #name, &mk_constraint_##name) static void S_make_1arg_constraint(pTHX_ const char *name, SV *(*mk_constraint)(pTHX_ SV *arg0)) { HV *stash = gv_stashpvs("Data::Checks", GV_ADD); HV *constraint_stash = gv_stashpvs("Data::Checks::Constraint", GV_ADD); AV *exportok = get_av("Data::Checks::EXPORT_OK", GV_ADD); SV *namesv = newSVpvf("Data::Checks::%s", name); I32 floor_ix = start_subparse(FALSE, 0); OP *mkop = newUNOP_AUX_CUSTOM(&pp_make_constraint, 0, newSLUGOP(0), (UNOP_AUX_item *)mk_constraint); mkop->op_private = 1; OP *body = make_argcheck_ops(1, 0, 0, namesv); body = op_append_elem(OP_LINESEQ, body, newSTATEOP(0, NULL, mkop)); CV *cv = newATTRSUB(floor_ix, newSVOP(OP_CONST, 0, namesv), NULL, NULL, body); cv_set_call_checker(cv, &ckcall_constfold, &PL_sv_undef); av_push(exportok, newSVpv(name, 0)); } #define MAKE_2ARG_CONSTRAINT(name) S_make_2arg_constraint(aTHX_ #name, &mk_constraint_##name) static void S_make_2arg_constraint(pTHX_ const char *name, SV *(*mk_constraint)(pTHX_ SV *arg0, SV *arg1)) { HV *stash = gv_stashpvs("Data::Checks", GV_ADD); HV *constraint_stash = gv_stashpvs("Data::Checks::Constraint", GV_ADD); AV *exportok = get_av("Data::Checks::EXPORT_OK", GV_ADD); SV *namesv = newSVpvf("Data::Checks::%s", name); I32 floor_ix = start_subparse(FALSE, 0); OP *mkop = newUNOP_AUX_CUSTOM(&pp_make_constraint, 0, newLISTOPn(OP_LIST, OPf_WANT_LIST, newSLUGOP(0), newSLUGOP(1), NULL), (UNOP_AUX_item *)mk_constraint); mkop->op_private = 2; OP *body = make_argcheck_ops(2, 0, 0, namesv); body = op_append_elem(OP_LINESEQ, body, newSTATEOP(0, NULL, mkop)); CV *cv = newATTRSUB(floor_ix, newSVOP(OP_CONST, 0, namesv), NULL, NULL, body); cv_set_call_checker(cv, &ckcall_constfold, &PL_sv_undef); av_push(exportok, newSVpv(name, 0)); } #define MAKE_nARG_CONSTRAINT(name) S_make_narg_constraint(aTHX_ #name, &mk_constraint_##name) static void S_make_narg_constraint(pTHX_ const char *name, SV *(*mk_constraint)(pTHX_ size_t nargs, SV **args)) { HV *stash = gv_stashpvs("Data::Checks", GV_ADD); HV *constraint_stash = gv_stashpvs("Data::Checks::Constraint", GV_ADD); AV *exportok = get_av("Data::Checks::EXPORT_OK", GV_ADD); SV *namesv = newSVpvf("Data::Checks::%s", name); I32 floor_ix = start_subparse(FALSE, 0); OP *mkop = newUNOP_AUX_CUSTOM(&pp_make_constraint, 0, op_force_list(newLISTOPn(OP_LIST, OPf_WANT_LIST, newUNOP(OP_RV2AV, OPf_WANT_LIST, newGVOP(OP_GV, 0, PL_defgv)), NULL)), (UNOP_AUX_item *)mk_constraint); mkop->op_private = -1; OP *body = make_argcheck_ops(0, 0, '@', namesv); body = op_append_elem(OP_LINESEQ, body, newSTATEOP(0, NULL, mkop)); CV *cv = newATTRSUB(floor_ix, newSVOP(OP_CONST, 0, namesv), NULL, NULL, body); cv_set_call_checker(cv, &ckcall_constfold, &PL_sv_undef); av_push(exportok, newSVpv(name, 0)); } /* This does NOT use SVf_quoted as that is intended for C's quoting * rules; we want qq()-style perlish ones. This means that $ and @ need to be * escaped as well. */ #define sv_catsv_quoted(buf, sv, quote) S_sv_catsv_quoted(aTHX_ buf, sv, quote) static void S_sv_catsv_quoted(pTHX_ SV *buf, SV *sv, char quote) { STRLEN len; const char *s = SvPV_const(sv, len); sv_catpvn(buf, "e, 1); for(STRLEN i = 0; i < len; i++) { if(len == 256) { sv_catpvs(buf, "..."); break; } char c = s[i]; if(c == '\\' || c == quote || (quote != '\'' && (c == '$' || c == '@'))) sv_catpvs(buf, "\\"); /* TODO: UTF-8 */ sv_catpvn(buf, &c, 1); } sv_catpvn(buf, "e, 1); } SV *DataChecks_stringify_constraint(pTHX_ struct Constraint *c) { const char *name = NULL; SV *args = sv_2mortal(newSVpvn("", 0)); /* such a shame C doesn't let us use function addresses as case labels */ // 0arg if (c->func == &constraint_Defined) name = "Defined"; else if(c->func == &constraint_Object) name = "Object"; else if(c->func == &constraint_ArrayRef) name = "ArrayRef"; else if(c->func == &constraint_HashRef) name = "HashRef"; else if(c->func == &constraint_Callable) name = "Callable"; else if(c->func == &constraint_Num) name = "Num"; else if(c->func == &constraint_Str) name = "Str"; // 1arg else if(c->func == &constraint_Isa) { name = "Isa"; sv_catsv_quoted(args, c->args[0], '"'); } else if(c->func == &constraint_StrMatch) { name = "StrMatch"; sv_catpvs(args, "qr"); sv_catsv_quoted(args, c->args[0], '/'); } else if(c->func == &constraint_Maybe) { name = "Maybe"; args = stringify_constraint_sv(c->args[0]); } // 2arg else if(c->func == &constraint_NumBound) { if(!c->args[0]) name = (c->flags & NUMBOUND_UPPER_INCLUSIVE ) ? "NumLE" : "NumLT"; else if(!c->args[1]) name = (c->flags & NUMBOUND_LOWER_INCLUSIVE ) ? "NumGE" : "NumGT"; else if(c->flags == NUMBOUND_LOWER_INCLUSIVE) name = "NumRange"; else { /* This was optimised from an All() call on at least two different ones; * we'll have to just stringify it as best we can */ name = "All"; sv_catpvf(args, "NumG%c(%" SVf "), NumL%c(%" SVf ")", (c->flags & NUMBOUND_LOWER_INCLUSIVE) ? 'E' : 'T', SVfARG(c->args[0]), (c->flags & NUMBOUND_UPPER_INCLUSIVE) ? 'E' : 'T', SVfARG(c->args[1])); } if(!SvCUR(args)) { if(c->args[0]) sv_catsv(args, c->args[0]); if(c->args[0] && c->args[1]) sv_catpvs(args, ", "); if(c->args[1]) sv_catsv(args, c->args[1]); } } // narg else if(c->func == &constraint_NumEq) { name = "NumEq"; if(SvTYPE(c->args[0]) != SVt_PVAV) sv_catsv(args, c->args[0]); else { U32 n = av_count((AV *)c->args[0]); SV **vals = AvARRAY(c->args[0]); for(U32 i = 0; i < n; i++) { if(i > 0) sv_catpvs(args, ", "); sv_catsv(args, vals[i]); } } } else if(c->func == &constraint_StrEq) { name = "StrEq"; if(SvTYPE(c->args[0]) != SVt_PVAV) sv_catsv_quoted(args, c->args[0], '"'); else { U32 n = av_count((AV *)c->args[0]); SV **vals = AvARRAY(c->args[0]); for(U32 i = 0; i < n; i++) { if(i > 0) sv_catpvs(args, ", "); sv_catsv_quoted(args, vals[i], '"'); } } } else if(c->func == &constraint_Any || c->func == &constraint_All) { name = (c->func == &constraint_Any) ? "Any" : "All"; if(c->args[0]) { U32 n = av_count((AV *)c->args[0]); SV **inners = AvARRAY(c->args[0]); for(U32 i = 0; i < n; i++) { if(i > 0) sv_catpvs(args, ", "); sv_catsv(args, stringify_constraint_sv(inners[i])); } } } else return newSVpvs_flags("TODO: debug inspect constraint", SVs_TEMP); SV *ret = newSVpvf("%s", name); if(SvCUR(args)) sv_catpvf(ret, "(%" SVf ")", SVfARG(args)); return sv_2mortal(ret); } void boot_Data_Checks__constraints(pTHX) { MAKE_0ARG_CONSTRAINT(Defined); MAKE_0ARG_CONSTRAINT(Object); MAKE_0ARG_CONSTRAINT(Str); MAKE_0ARG_CONSTRAINT(Num); MAKE_nARG_CONSTRAINT(StrEq); MAKE_1ARG_CONSTRAINT(StrMatch); MAKE_1ARG_CONSTRAINT(NumGT); MAKE_1ARG_CONSTRAINT(NumGE); MAKE_1ARG_CONSTRAINT(NumLE); MAKE_1ARG_CONSTRAINT(NumLT); MAKE_2ARG_CONSTRAINT(NumRange); MAKE_nARG_CONSTRAINT(NumEq); MAKE_1ARG_CONSTRAINT(Isa); MAKE_0ARG_CONSTRAINT(ArrayRef); MAKE_0ARG_CONSTRAINT(HashRef); MAKE_0ARG_CONSTRAINT(Callable); MAKE_1ARG_CONSTRAINT(Maybe); MAKE_nARG_CONSTRAINT(Any); MAKE_nARG_CONSTRAINT(All); XopENTRY_set(&xop_make_constraint, xop_name, "make_constraint"); XopENTRY_set(&xop_make_constraint, xop_desc, "make constraint"); XopENTRY_set(&xop_make_constraint, xop_class, OA_UNOP_AUX); Perl_custom_op_register(aTHX_ &pp_make_constraint, &xop_make_constraint); } Data-Checks-0.10/t000755001750001750 014660677075 12507 5ustar00leoleo000000000000Data-Checks-0.10/t/00use.t000444001750001750 16714660677075 13751 0ustar00leoleo000000000000#!/usr/bin/perl use v5.22; use warnings; use Test2::V0; require Data::Checks; pass "Modules loaded"; done_testing; Data-Checks-0.10/t/01check-obj.t000444001750001750 202214660677075 15013 0ustar00leoleo000000000000#!/usr/bin/perl use v5.22; use warnings; use Test2::V0; use lib "t"; use testcase "t::test"; package CheckerClass { sub new { bless [], shift } sub check { shift; return $_[0] eq "ok" } } # checker as object { my $checker = t::test::make_checkdata( CheckerClass->new, "Value", "CheckerClass" ); ok( $checker, 'checker is defined' ); ok( t::test::check_value( $checker, "ok" ), 'check_value OK' ); ok( !t::test::check_value( $checker, "bad" ), 'check_value bad' ); is( dies { t::test::assert_value( $checker, "ok" ) }, undef, 'assert_value OK' ); like( dies { t::test::assert_value( $checker, "bad" ) }, qr/^Value requires a value satisfying CheckerClass at /, 'assert_value bad' ); my $asserter = t::test::make_asserter_sub( $checker ); is( dies { $asserter->( "ok" ) }, undef, 'asserter OK' ); like( dies { $asserter->( "bad" ) }, qr/^Value requires a value satisfying CheckerClass at /, 'asserter bad' ); t::test::free_checkdata( $checker ); } done_testing; Data-Checks-0.10/t/02check-pkg.t000444001750001750 177314660677075 15037 0ustar00leoleo000000000000#!/usr/bin/perl use v5.22; use warnings; use Test2::V0; use lib "t"; use testcase "t::test"; package CheckerPackage { sub check { shift; return $_[0] eq "ok" } } # checker as package { my $checker = t::test::make_checkdata( "CheckerPackage", "Value", "CheckerPackage" ); ok( $checker, 'checker is defined' ); ok( t::test::check_value( $checker, "ok" ), 'check_value OK' ); ok( !t::test::check_value( $checker, "bad" ), 'check_value bad' ); is( dies { t::test::assert_value( $checker, "ok" ) }, undef, 'assert_value OK' ); like( dies { t::test::assert_value( $checker, "bad" ) }, qr/^Value requires a value satisfying CheckerPackage at /, 'assert_value bad' ); my $asserter = t::test::make_asserter_sub( $checker ); is( dies { $asserter->( "ok" ) }, undef, 'asserter OK' ); like( dies { $asserter->( "bad" ) }, qr/^Value requires a value satisfying CheckerPackage at /, 'asserter bad' ); t::test::free_checkdata( $checker ); } done_testing; Data-Checks-0.10/t/03check-sub.t000444001750001750 207214660677075 15041 0ustar00leoleo000000000000#!/usr/bin/perl use v5.22; use warnings; use Test2::V0; use lib "t"; use testcase "t::test"; sub CheckFunction { return $_[0] eq "ok" } # These are now deprecated but for now we'll just quiet the warning no warnings 'deprecated'; # checker as code ref { my $checker = t::test::make_checkdata( \&CheckFunction, "Value", "CheckFunction" ); ok( $checker, 'checker is defined' ); ok( t::test::check_value( $checker, "ok" ), 'check_value OK' ); ok( !t::test::check_value( $checker, "bad" ), 'check_value bad' ); is( dies { t::test::assert_value( $checker, "ok" ) }, undef, 'assert_value OK' ); like( dies { t::test::assert_value( $checker, "bad" ) }, qr/^Value requires a value satisfying CheckFunction at /, 'assert_value bad' ); my $asserter = t::test::make_asserter_sub( $checker ); is( dies { $asserter->( "ok" ) }, undef, 'asserter OK' ); like( dies { $asserter->( "bad" ) }, qr/^Value requires a value satisfying CheckFunction at /, 'asserter bad' ); t::test::free_checkdata( $checker ); } done_testing; Data-Checks-0.10/t/10constraints.t000444001750001750 571614660677075 15552 0ustar00leoleo000000000000#!/usr/bin/perl use v5.22; use warnings; use Test2::V0; use lib "t"; use testcase "t::test", qw( test_constraint ); use Data::Checks qw( Defined Object Str Isa ArrayRef HashRef Callable ); # Defined { my $checker = t::test::make_checkdata( Defined, "Value" ); ok( t::test::check_value( $checker, "ok" ), 'Defined accepts value' ); ok( !t::test::check_value( $checker, undef ), 'Defined rejects undef' ); is( dies { t::test::assert_value( $checker, "ok" ) }, undef, 'Defined assert_value OK' ); like( dies { t::test::assert_value( $checker, undef ) }, qr/^Value requires a value satisfying Defined at /, 'Defined assert_value bad' ); my $asserter = t::test::make_asserter_sub( $checker ); is( dies { $asserter->( "ok" ) }, undef, 'Defined asserter OK' ); like( dies { $asserter->( undef ) }, qr/^Value requires a value satisfying Defined at /, 'Defined asserter bad' ); t::test::free_checkdata( $checker ); } # Constraints have a ->check method directly { ok( Defined->check( 123 ), 'Defined->check accepts defined value' ); ok( !Defined->check( undef ), 'Defined->check rejects undef' ); } # Object test_constraint Object => Object, [ 'object' => BaseClass->new, ]; # unit constraint functions don't take arguments { # Perls before 5.34 did not include argument count in the message my $argc_re = $^V ge v5.34 ? qr/ \(got 1; expected 0\)/ : ""; like( dies { Defined(123) }, qr/^Too many arguments for subroutine 'Data::Checks::Defined'$argc_re at /, 'unit constraint functions complain if given arguments' ); } # Isa test_constraint Isa => Isa("BaseClass"), [ 'object' => BaseClass->new, 'subclass' => DerivedClass->new, ], [ 'class name' => "BaseClass", 'other instance' => DifferentClass->new, ]; # ArrayRef test_constraint ArrayRef => ArrayRef, [ 'plain arrayref' => [], 'object with @{}' => ClassWithArrayRefify->new, ]; # HashRef test_constraint HashRef => HashRef, [ 'plain hashref' => {}, 'object with %{}' => ClassWithHashRefify->new, ]; # Callable test_constraint Callable => Callable, [ 'plain coderef' => sub {}, 'ref to CORE::join' => \&CORE::join, 'object with &{}' => ClassWithCodeRefify->new, ]; # Stringification is( Data::Checks::Debug::stringify_constraint( Defined ), "Defined", 'debug stringify Defined' ); is( Data::Checks::Debug::stringify_constraint( Object ), "Object", 'debug stringify Object' ); is( Data::Checks::Debug::stringify_constraint( Isa("Base::Class") ), "Isa(\"Base::Class\")", 'debug stringify Isa("Base::Class")' ); is( Data::Checks::Debug::stringify_constraint( ArrayRef ), "ArrayRef", 'debug stringify ArrayRef' ); is( Data::Checks::Debug::stringify_constraint( HashRef ), "HashRef", 'debug stringify HashRef' ); is( Data::Checks::Debug::stringify_constraint( Callable ), "Callable", 'debug stringify Callable' ); done_testing; Data-Checks-0.10/t/11constraints-num.t000444001750001750 1027314660677075 16362 0ustar00leoleo000000000000#!/usr/bin/perl use v5.22; use warnings; use Test2::V0; use lib "t"; use testcase "t::test", qw( test_constraint ); use Data::Checks qw( Num NumGT NumGE NumLE NumLT NumRange NumEq ); use constant { NAN => 0+"NaN", }; # Num test_constraint Num => Num, [ 'plain integer' => 1234, 'plain float' => 5.67, 'stringified number' => "89", 'object with numify' => ClassWithNumOverload->new, ], [ 'object with stringifty' => ClassWithStrOverload->new, 'not-a-number' => NAN, '"NaN"' => NAN . "", '"nan"' => "nan", ]; # Num bounded { my $checker_gt = t::test::make_checkdata( NumGT(0), "Value" ); ok( t::test::check_value( $checker_gt, 123 ), 'NumGT accepts plain integer' ); ok( !t::test::check_value( $checker_gt, 0 ), 'NumGT rejects bound' ); ok( !t::test::check_value( $checker_gt, -123 ), 'NumGT rejects negative integer' ); my $checker_ge = t::test::make_checkdata( NumGE(0), "Value" ); ok( t::test::check_value( $checker_ge, 123 ), 'NumGE accepts plain integer' ); ok( t::test::check_value( $checker_ge, 0 ), 'NumGE accepts bound' ); ok( !t::test::check_value( $checker_ge, -123 ), 'NumGE rejects negative integer' ); my $checker_le = t::test::make_checkdata( NumLE(100), "Value" ); ok( t::test::check_value( $checker_le, 25 ), 'NumLE accepts plain integer' ); ok( t::test::check_value( $checker_le, 100 ), 'NumLE accepts bound' ); ok( !t::test::check_value( $checker_le, 200 ), 'NumLE rejects too large' ); my $checker_lt = t::test::make_checkdata( NumLT(100), "Value" ); ok( t::test::check_value( $checker_lt, 25 ), 'NumLT accepts plain integer' ); ok( !t::test::check_value( $checker_lt, 100 ), 'NumLT rejects bound' ); ok( !t::test::check_value( $checker_lt, 200 ), 'NumLT rejects too large' ); } # Num range { my $checker = t::test::make_checkdata( NumRange(10, 20), "Value" ); ok( !t::test::check_value( $checker, 0 ), 'NumRange rejects below lower bound' ); ok( t::test::check_value( $checker, 10 ), 'NumRange accepts lower bound' ); ok( t::test::check_value( $checker, 15 ), 'NumRange accepts midway' ); ok( !t::test::check_value( $checker, 25 ), 'NumRange rejects upper bound' ); ok( !t::test::check_value( $checker, 40 ), 'NumRange rejects above upper bound' ); } # Num eq set { # Stack discipline test my @vals = ( 2, 4, NumEq(1, 3, 5), 6, 8 ); is( scalar @vals, 5, '5 values in the array' ); ok( ref $vals[2], 'constraint is some kind of ref' ); my $checker = t::test::make_checkdata( $vals[2], "Value" ); ok( t::test::check_value( $checker, 1 ), 'NumEq accepts a value' ); ok( t::test::check_value( $checker, 5 ), 'NumEq accepts a value' ); ok( !t::test::check_value( $checker, 2 ), 'NumEq rejects a value not in the list' ); my $checker_10 = t::test::make_checkdata( NumEq(10), "Value" ); ok( t::test::check_value( $checker_10, 10 ), 'NumEq singleton accepts the value' ); ok( !t::test::check_value( $checker_10, 20 ), 'NumEq singleton rejects a different value' ); my $checker_zero = t::test::make_checkdata( NumEq(0), "Value" ); ok( t::test::check_value( $checker_zero, 0 ), 'NumEq zero accepts zero' ); ok( !t::test::check_value( $checker_zero, undef ), 'NumEq zero rejects undef' ); } # Stringify is( Data::Checks::Debug::stringify_constraint( Num ), "Num", 'debug stringify Num' ); is( Data::Checks::Debug::stringify_constraint( NumGT(10) ), "NumGT(10)", 'debug stringify NumGT(10)' ); is( Data::Checks::Debug::stringify_constraint( NumGE(10) ), "NumGE(10)", 'debug stringify NumGE(10)' ); is( Data::Checks::Debug::stringify_constraint( NumLE(10) ), "NumLE(10)", 'debug stringify NumLE(10)' ); is( Data::Checks::Debug::stringify_constraint( NumLT(10) ), "NumLT(10)", 'debug stringify NumLT(10)' ); is( Data::Checks::Debug::stringify_constraint( NumRange(10, 20) ), "NumRange(10, 20)", 'debug stringify NumRange(10, 20)' ); is( Data::Checks::Debug::stringify_constraint( NumEq(10) ), "NumEq(10)", 'debug stringify NumEq(10)' ); is( Data::Checks::Debug::stringify_constraint( NumEq(10, 20) ), "NumEq(10, 20)", 'debug stringify NumEq(10, 20)' ); done_testing; Data-Checks-0.10/t/12constraints-str.t000444001750001750 440614660677075 16355 0ustar00leoleo000000000000#!/usr/bin/perl use v5.22; use warnings; use Test2::V0; use lib "t"; use testcase "t::test", qw( test_constraint ); use Data::Checks qw( Str StrEq StrMatch ); # Str test_constraint Str => Str, [ 'plain string' => "a string", 'empty string' => "", 'plain integer' => 1234, 'object with stringifty' => ClassWithStrOverload->new, ], [ 'object with numify' => ClassWithNumOverload->new, ]; # Str eq set { my $checker = t::test::make_checkdata( StrEq(qw( A C E )), "Value" ); ok( t::test::check_value( $checker, "A" ), 'StrEq accepts a value' ); ok( t::test::check_value( $checker, "E" ), 'StrEq accepts a value' ); ok( !t::test::check_value( $checker, "B" ), 'StrEq rejects a value not in the list' ); my $checker_Z = t::test::make_checkdata( StrEq("Z"), "Value" ); ok( t::test::check_value( $checker_Z, "Z" ), 'StrEq singleton accepts the value' ); ok( !t::test::check_value( $checker_Z, "x" ), 'StrEq singleton rejects a different value' ); my $checker_empty = t::test::make_checkdata( StrEq(""), "Value" ); ok( t::test::check_value( $checker_empty, "" ), 'StrEq empty accepts empty' ); ok( !t::test::check_value( $checker_empty, undef ), 'StrEq empty rejects undef' ); } # StrMatch test_constraint 'StrMatch(qr/^[A-Z]/i)' => StrMatch(qr/^[A-Z]/i), [ 'plain string' => "a string", 'matching string' => "MATCH", ], [ 'non-matching string' => "123", ]; # Stringify is( Data::Checks::Debug::stringify_constraint( Str ), "Str", 'debug stringify Str' ); is( Data::Checks::Debug::stringify_constraint( StrEq("A") ), "StrEq(\"A\")", 'debug stringify StrEq("A")' ); is( Data::Checks::Debug::stringify_constraint( StrEq("A", "B") ), "StrEq(\"A\", \"B\")", 'debug stringify StrEq("A", "B")' ); is( Data::Checks::Debug::stringify_constraint( StrEq('"quoted value"') ), q(StrEq("\\"quoted value\\"")), 'debug stringify StrEq(\'"quoted value"\')' ); is( Data::Checks::Debug::stringify_constraint( StrEq('literal $dollar') ), q(StrEq("literal \\$dollar")), 'debug stringify StrEq(\'literal $dollar\')' ); is( Data::Checks::Debug::stringify_constraint( StrMatch(qr/ABC/) ), "StrMatch(qr/(?^u:ABC)/)", 'debug stringify StrMatch(qr/ABC/)' ); done_testing; Data-Checks-0.10/t/13constraints-structural.t000444001750001750 447414660677075 17763 0ustar00leoleo000000000000#!/usr/bin/perl use v5.22; use warnings; use Test2::V0; use lib "t"; use testcase "t::test", qw( test_constraint ); use Data::Checks qw( Maybe Any All Str Object NumGE NumLE ); # Maybe test_constraint 'Maybe(Str)' => Maybe(Str), [ 'undef' => undef, 'plain string' => "a string", 'plain integer' => 1234, ]; # Any test_constraint 'Any(Str, Object)' => Any(Str, Object), [ 'plain string' => "a string", 'plain integer' => 1234, 'object' => BaseClass->new, ]; # All(C...) # behaves a bit like NumRange() test_constraint 'All(NumGE(0), NumLE(10))' => All(NumGE(0), NumLE(10)), [ 'zero' => 0, 'ten' => 10, ], [ '20' => 20, ]; # All() empty test_constraint 'All' => All, [ 'undef' => undef, 'plain string' => "a string", 'plain integer' => 1234, 'plain arrayref' => [], 'plain hashref' => {}, 'plain coderef' => sub {}, 'object', => BaseClass->new, ]; is( Data::Checks::Debug::stringify_constraint( Maybe(Str) ), "Maybe(Str)", 'debug stringify Maybe(Str)' ); is( Data::Checks::Debug::stringify_constraint( Any(Str, Object) ), "Any(Str, Object)", 'debug stringify Any(Str, Object)' ); is( Data::Checks::Debug::stringify_constraint( All(Str, Object) ), "All(Str, Object)", 'debug stringify All(Str, Object)' ); is( Data::Checks::Debug::stringify_constraint( All() ), "All", 'debug stringify All()' ); # Any() or All() of 1 item might as well just be the thing is( Data::Checks::Debug::stringify_constraint( Any(Str) ), "Str", 'debug stringify Any(Str)' ); is( Data::Checks::Debug::stringify_constraint( All(Str) ), "Str", 'debug stringify All(Str)' ); # Flatten trees of nested Any/Any or All/All is( Data::Checks::Debug::stringify_constraint( Any(Str, Any(Str, Str)) ), "Any(Str, Str, Str)", 'debug stringify Any(Str, Any(Str, Str))' ); is( Data::Checks::Debug::stringify_constraint( All(Str, All(Str, Str)) ), "All(Str, Str, Str)", 'debug stringify All(Str, All(Str, Str))' ); # Infix | operator acts like Any() is( Data::Checks::Debug::stringify_constraint( Str|Object ), "Any(Str, Object)", 'debug stringify Str|Object' ); is( Data::Checks::Debug::stringify_constraint( Str|Str|Str ), "Any(Str, Str, Str)", 'debug stringify Str|Str|Str' ); done_testing; Data-Checks-0.10/t/20inline-constraints.t000444001750001750 311614660677075 17017 0ustar00leoleo000000000000#!/usr/bin/perl use v5.22; use warnings; use Test2::V0; use B qw( svref_2object walkoptree ); use Data::Checks qw( Defined Object Str Isa Maybe NumRange StrEq ); sub count_ops { my ( $code ) = @_; my %opcounts; # B::walkoptree() is stupid # https://github.com/Perl/perl5/issues/19101 no warnings 'once'; local *B::OP::collect_opnames = sub { my ( $op ) = @_; $opcounts{ $op->name }++ unless $op->name eq "null"; }; walkoptree( svref_2object( $code )->ROOT, "collect_opnames" ); return %opcounts; } sub const_inlined_ok { my ( $code, $name ) = @_; my %opcounts = count_ops $code; is( $opcounts{const}, 1, "$name uses 1 x OP_CONST" ); is( $opcounts{entersub} // 0, 0, "$name does not use OP_ENTERSUB" ); } # Calls to 0arg constraints get inlined const_inlined_ok sub { Defined }, 'Defined'; const_inlined_ok sub { Str }, 'Str'; const_inlined_ok sub { Object }, 'Object'; # Calls to 1arg constraints get inlined if possible const_inlined_ok sub { Isa "Some::Class" }, 'Isa'; const_inlined_ok sub { Maybe Str }, 'Maybe Str'; # Calls to 2arg constraints const_inlined_ok sub { NumRange 0, 10 }, 'NumRange 0, 10'; # Calls to narg constraints const_inlined_ok sub { StrEq "A", "B", "C" }, 'StrEq A, B, C'; const_inlined_ok sub { StrEq qw( A B C ) }, 'StrEq qw( A B C )'; # TODO: const_inlined_ok sub { StrEq "A" .. "C" }, 'StrEq A .. C'; # Non-inlinable calls still work my %opcounts = count_ops sub { my $constraint = Maybe Isa $_[0] }; is( $opcounts{entersub}, 2, 'Maybe Isa $_[0] still has two OP_ENTERSUB' ); done_testing; Data-Checks-0.10/t/21assertop.t000444001750001750 135314660677075 15036 0ustar00leoleo000000000000#!/usr/bin/perl use v5.22; use warnings; use Test2::V0; use lib "t"; use testcase "t::test", qw( test_constraint ); use Data::Checks qw( Defined ); my $checker = t::test::make_checkdata( Defined, "Value" ); # no flags { my $asserter = t::test::make_asserter_sub( $checker ); is( dies { $asserter->( "ok" ) }, undef, 'Defined asserter OK' ); like( dies { $asserter->( undef ) }, qr/^Value requires a value satisfying Defined at /, 'Defined asserter bad' ); } # OPf_WANT_VOID clears the result { my $asserter = t::test::make_asserter_sub( $checker, 'void' ); is( [ $asserter->( "the-value" ) ], [], 'asserter with OPf_WANT_VOID yields nothing' ); } t::test::free_checkdata( $checker ); done_testing; Data-Checks-0.10/t/22optimise-numbounds.t000444001750001750 405114660677075 17036 0ustar00leoleo000000000000#!/usr/bin/perl use v5.22; use warnings; use Test2::V0; use Data::Checks qw( NumGT NumGE NumLE NumLT NumRange Isa All ); *stringify = \&Data::Checks::Debug::stringify_constraint; # All() of two same single-sided bounds should always pick the tighter is( stringify( All(NumGT(10), NumGT(20)) ), "NumGT(20)", 'All(NumGT(10), NumGT(20))' ); is( stringify( All(NumGE(10), NumGE(20)) ), "NumGE(20)", 'All(NumGE(10), NumGE(20))' ); is( stringify( All(NumLE(10), NumLE(20)) ), "NumLE(10)", 'All(NumLE(10), NumLE(20))' ); is( stringify( All(NumLT(10), NumLT(20)) ), "NumLT(10)", 'All(NumLT(10), NumLT(20))' ); # Inclusive followed by exclusive of same bound loses the flag is( stringify( All(NumGE(30), NumGT(30)) ), "NumGT(30)", 'All(NumGE(30), NumGT(30))' ); is( stringify( All(NumLE(40), NumLT(40)) ), "NumLT(40)", 'All(NumLE(40), NumLT(40))' ); # All() of NumGE and NumLT stringifies as NumRange() is( stringify( All(NumGE(50), NumLT(60)) ), "NumRange(50, 60)", 'All(NumGE(50), NumLT(60))' ); # others stringify as All() combos is( stringify( All(NumGT(50), NumLT(60)) ), "All(NumGT(50), NumLT(60))", 'All(NumGT(50), NumLT(60))' ); is( stringify( All(NumGT(50), NumLE(60)) ), "All(NumGT(50), NumLE(60))", 'All(NumGT(50), NumLE(60))' ); is( stringify( All(NumGE(50), NumLE(60)) ), "All(NumGE(50), NumLE(60))", 'All(NumGE(50), NumLE(60))' ); # NumRange() combines with others is( stringify( All(NumRange(10, 30), NumGT(20)) ), "All(NumGT(20), NumLT(30))", 'All(NumRange(10, 30), NumGT(20))' ); is( stringify( All(NumRange(10, 30), NumGE(20)) ), "NumRange(20, 30)", 'All(NumRange(10, 30), NumGE(20))' ); is( stringify( All(NumRange(10, 30), NumLE(20)) ), "All(NumGE(10), NumLE(20))", 'All(NumRange(10, 30), NumLE(20))' ); is( stringify( All(NumRange(10, 30), NumLT(20)) ), "NumRange(10, 20)", 'All(NumRange(10, 30), NumLT(20))' ); # Other constraints are still preserved is( stringify( All(NumGE(10), NumLT(30), Isa("SomeClass")) ), "All(NumRange(10, 30), Isa(\"SomeClass\"))", 'All(NumGE(10), NumLT(30), Isa("SomeClass"))' ); done_testing; Data-Checks-0.10/t/80checks+field.t000444001750001750 214114660677075 15516 0ustar00leoleo000000000000#!/usr/bin/perl use v5.22; use warnings; use Test2::V0; BEGIN { plan skip_all => "Data::Checks >= 0.03 is not available" unless eval { require Data::Checks; Data::Checks->VERSION( '0.03' ) }; plan skip_all => "Object::Pad::FieldAttr::Checked >= 0.10 is not available" unless eval { require Object::Pad::FieldAttr::Checked; Object::Pad::FieldAttr::Checked->VERSION( '0.10' ) }; Data::Checks->import(qw( Defined )); Object::Pad::FieldAttr::Checked->import; diag( "Data::Checks $Data::Checks::VERSION, " . "Object::Pad::FieldAttr::Checked $Object::Pad::FieldAttr::Checked::VERSION" ); } # We know this must be available since Object::Pad::FieldAttr::Checked would # depend on it use Object::Pad; class TestClass { field $x :param :reader :Checked(Defined); } is( TestClass->new( x => 123 )->x, 123, 'Field $x accepts defined argument' ); ok( dies { TestClass->new( x => undef ) }, 'rejects undefined argument' ); # Don't be overly sensitive on the format of the message, in case it changes. # It's just for human interest done_testing; Data-Checks-0.10/t/80checks+signature.t000444001750001750 211414660677075 16434 0ustar00leoleo000000000000#!/usr/bin/perl use v5.22; use warnings; use Test2::V0; BEGIN { plan skip_all => "Data::Checks >= 0.03 is not available" unless eval { require Data::Checks; Data::Checks->VERSION( '0.03' ) }; plan skip_all => "Signature::Attribute::Checked >= 0.04 is not available" unless eval { require Signature::Attribute::Checked; Signature::Attribute::Checked->VERSION( '0.04' ) }; Data::Checks->import(qw( Defined )); Signature::Attribute::Checked->import; diag( "Data::Checks $Data::Checks::VERSION, " . "Signature::Attribute::Checked $Signature::Attribute::Checked::VERSION" ); } # We know this must be available since Signature::Attribute::Checked would # depend on it use Sublike::Extended; use experimental qw( signatures ); extended sub func ( $x :Checked(Defined) ) { return $x } is( func(123), 123, 'func() accepts defined argument' ); ok( dies { func(undef) }, 'rejects undefined argument' ); # Don't be overly sensitive on the format of the message, in case it changes. # It's just for human interest done_testing; Data-Checks-0.10/t/80match-is.t000444001750001750 227114660677075 14710 0ustar00leoleo000000000000#!/usr/bin/perl use v5.14; use warnings; use Test2::V0; BEGIN { plan skip_all => "Syntax::Keyword::Match >= 0.08 is not available" unless eval { require Syntax::Keyword::Match; Syntax::Keyword::Match->VERSION( '0.08' ); }; plan skip_all => "Syntax::Operator::Is is not available" unless eval { require Syntax::Operator::Is }; Syntax::Keyword::Match->import; Syntax::Operator::Is->import; diag( "Syntax::Keyword::Match $Syntax::Keyword::Match::VERSION, " . "Syntax::Operator::Is $Syntax::Operator::Is::VERSION" ); } # if we have Syntax::Operator::Is available then we know we must have # Data::Checks as well use Data::Checks qw( Num Object ); { sub func { match( $_[0] : is ) { case( Num ) { return "arg is a number" } case( Object ) { return "arg is an object" } default { return "arg is neither" } } } Test2::V0::is( func( 123 ), "arg is a number", 'func() on number' ); Test2::V0::is( func( bless [], "SomeClass" ), "arg is an object", 'func() on object' ); Test2::V0::is( func( [] ), "arg is neither", 'func() on arrayref' ); } done_testing; Data-Checks-0.10/t/95benchmark-fields.t000444001750001750 335414660677075 16412 0ustar00leoleo000000000000#!/usr/bin/perl use v5.14; use warnings; use Test2::V0; # This "test" never fails, but prints a benchmark comparison between # Data::Checks and Types::Standard performing the same :Checked attribute # assertions on an object field. BEGIN { eval { require Object::Pad::FieldAttr::Checked; Object::Pad::FieldAttr::Checked->VERSION( '0.10' ) } or plan skip_all => "Object::Pad::FieldAttr::Checked >= 0.10 is not available"; eval { require Types::Standard } or plan skip_all => "Types::Standard is not available"; } use Time::HiRes qw( gettimeofday tv_interval ); sub measure(&) { my ( $code ) = @_; my $start = [ gettimeofday ]; $code->(); return tv_interval $start; } use Object::Pad; use Object::Pad::FieldAttr::Checked; use Data::Checks; use Types::Standard; class TestClass_DC { field $x :param :Checked(Data::Checks::Defined); } class TestClass_TS { field $x :param :Checked(Types::Standard::Defined); } my $COUNT = 10_000; my $elapsed_DC = 0; my $elapsed_TS = 0; # To reduce the influence of bursts of timing noise, interleave many small runs # of each type. foreach ( 1 .. 20 ) { my $overhead = measure {}; $elapsed_DC += -$overhead + measure { TestClass_DC->new( x => 123 ) for 1 .. $COUNT; }; $elapsed_TS += -$overhead + measure { TestClass_TS->new( x => 123 ) for 1 .. $COUNT; }; } pass( "Benchmarked" ); if( $elapsed_DC > $elapsed_TS ) { diag( sprintf "Types::Standard took %.3fsec, ** this was SLOWER at %.3fsec **", $elapsed_TS, $elapsed_DC ); } else { my $speedup = ( $elapsed_TS - $elapsed_DC ) / $elapsed_TS; diag( sprintf "Types::Standard took %.3fsec, this was %d%% faster at %.3fsec", $elapsed_TS, $speedup * 100, $elapsed_DC ); } done_testing; Data-Checks-0.10/t/95benchmark-signature.t000444001750001750 457314660677075 17151 0ustar00leoleo000000000000#!/usr/bin/perl use v5.14; use warnings; use Test2::V0; # This "test" never fails, but prints a benchmark comparison between # Data::Checks and Types::Standard performing the same :Checked attribute # assertions on a function. It also compares against a manually-written check # for interest. BEGIN { eval { require Signature::Attribute::Checked; Signature::Attribute::Checked->VERSION( '0.04' ) } or plan skip_all => "Signature::Attribute::Checked >= 0.04 is not available"; eval { require Types::Standard } or plan skip_all => "Types::Standard is not available"; } use Time::HiRes qw( gettimeofday tv_interval ); sub measure(&) { my ( $code ) = @_; my $start = [ gettimeofday ]; $code->(); return tv_interval $start; } use Sublike::Extended; use Signature::Attribute::Checked; use Data::Checks; use Types::Standard; use experimental qw( signatures ); extended sub func_DC ( $x :Checked(Data::Checks::Defined) ) { return $x; } extended sub func_TS ( $x :Checked(Types::Standard::Defined) ) { return $x; } sub func_manual ( $x ) { defined $x or die "Require defined value for \$x"; return $x; } my $COUNT = 50_000; my $elapsed_DC = 0; my $elapsed_TS = 0; my $elapsed_manual= 0; # To reduce the influence of bursts of timing noise, interleave many small runs # of each type. foreach ( 1 .. 20 ) { my $overhead = measure {}; $elapsed_DC += -$overhead + measure { func_DC( 123 ) for 1 .. $COUNT; }; $elapsed_TS += -$overhead + measure { func_TS( 123 ) for 1 .. $COUNT; }; $elapsed_manual += -$overhead + measure { func_manual( 123 ) for 1 .. $COUNT; }; } pass( "Benchmarked" ); if( $elapsed_DC > $elapsed_TS ) { diag( sprintf "Types::Standard took %.3fsec, ** this was SLOWER at %.3fsec **", $elapsed_TS, $elapsed_DC ); } else { my $speedup = ( $elapsed_TS - $elapsed_DC ) / $elapsed_TS; diag( sprintf "Types::Standard took %.3fsec, this was %d%% faster at %.3fsec", $elapsed_TS, $speedup * 100, $elapsed_DC ); } my $speedup = ( $elapsed_manual - $elapsed_DC ) / $elapsed_manual; if( $elapsed_DC > $elapsed_manual ) { diag( sprintf "manual took %.3fsec, this was %d%% slower at %.3fsec", $elapsed_manual, -$speedup * 100, $elapsed_DC ); } else { diag( sprintf "manual took %.3fsec, this was %d%% faster at %.3fsec", $elapsed_manual, $speedup * 100, $elapsed_DC ); } done_testing; Data-Checks-0.10/t/99pod.t000444001750001750 25514660677075 13757 0ustar00leoleo000000000000#!/usr/bin/perl use v5.22; use warnings; use Test2::V0; eval "use Test::Pod 1.00"; plan skip_all => "Test::Pod 1.00 required for testing POD" if $@; all_pod_files_ok(); Data-Checks-0.10/t/test.c000444001750001750 2267114660677075 14017 0ustar00leoleo000000000000/* * This file was generated automatically by ExtUtils::ParseXS version 3.51 from the * contents of test.xs. Do not edit this file, edit test.xs instead. * * ANY CHANGES MADE HERE WILL BE LOST! * */ #line 1 "t/test.xs" /* 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, 2024 -- leonerd@leonerd.org.uk */ #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include "DataChecks.h" #define HAVE_PERL_VERSION(R, V, S) \ (PERL_REVISION > (R) || (PERL_REVISION == (R) && (PERL_VERSION > (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION >= (S)))))) #include "optree-additions.c.inc" #line 28 "t/test.c" #ifndef PERL_UNUSED_VAR # define PERL_UNUSED_VAR(var) if (0) var = var #endif #ifndef dVAR # define dVAR dNOOP #endif /* This stuff is not part of the API! You have been warned. */ #ifndef PERL_VERSION_DECIMAL # define PERL_VERSION_DECIMAL(r,v,s) (r*1000000 + v*1000 + s) #endif #ifndef PERL_DECIMAL_VERSION # define PERL_DECIMAL_VERSION \ PERL_VERSION_DECIMAL(PERL_REVISION,PERL_VERSION,PERL_SUBVERSION) #endif #ifndef PERL_VERSION_GE # define PERL_VERSION_GE(r,v,s) \ (PERL_DECIMAL_VERSION >= PERL_VERSION_DECIMAL(r,v,s)) #endif #ifndef PERL_VERSION_LE # define PERL_VERSION_LE(r,v,s) \ (PERL_DECIMAL_VERSION <= PERL_VERSION_DECIMAL(r,v,s)) #endif /* XS_INTERNAL is the explicit static-linkage variant of the default * XS macro. * * XS_EXTERNAL is the same as XS_INTERNAL except it does not include * "STATIC", ie. it exports XSUB symbols. You probably don't want that * for anything but the BOOT XSUB. * * See XSUB.h in core! */ /* TODO: This might be compatible further back than 5.10.0. */ #if PERL_VERSION_GE(5, 10, 0) && PERL_VERSION_LE(5, 15, 1) # undef XS_EXTERNAL # undef XS_INTERNAL # if defined(__CYGWIN__) && defined(USE_DYNAMIC_LOADING) # define XS_EXTERNAL(name) __declspec(dllexport) XSPROTO(name) # define XS_INTERNAL(name) STATIC XSPROTO(name) # endif # if defined(__SYMBIAN32__) # define XS_EXTERNAL(name) EXPORT_C XSPROTO(name) # define XS_INTERNAL(name) EXPORT_C STATIC XSPROTO(name) # endif # ifndef XS_EXTERNAL # if defined(HASATTRIBUTE_UNUSED) && !defined(__cplusplus) # define XS_EXTERNAL(name) void name(pTHX_ CV* cv __attribute__unused__) # define XS_INTERNAL(name) STATIC void name(pTHX_ CV* cv __attribute__unused__) # else # ifdef __cplusplus # define XS_EXTERNAL(name) extern "C" XSPROTO(name) # define XS_INTERNAL(name) static XSPROTO(name) # else # define XS_EXTERNAL(name) XSPROTO(name) # define XS_INTERNAL(name) STATIC XSPROTO(name) # endif # endif # endif #endif /* perl >= 5.10.0 && perl <= 5.15.1 */ /* The XS_EXTERNAL macro is used for functions that must not be static * like the boot XSUB of a module. If perl didn't have an XS_EXTERNAL * macro defined, the best we can do is assume XS is the same. * Dito for XS_INTERNAL. */ #ifndef XS_EXTERNAL # define XS_EXTERNAL(name) XS(name) #endif #ifndef XS_INTERNAL # define XS_INTERNAL(name) XS(name) #endif /* Now, finally, after all this mess, we want an ExtUtils::ParseXS * internal macro that we're free to redefine for varying linkage due * to the EXPORT_XSUB_SYMBOLS XS keyword. This is internal, use * XS_EXTERNAL(name) or XS_INTERNAL(name) in your code if you need to! */ #undef XS_EUPXS #if defined(PERL_EUPXS_ALWAYS_EXPORT) # define XS_EUPXS(name) XS_EXTERNAL(name) #else /* default to internal */ # define XS_EUPXS(name) XS_INTERNAL(name) #endif #ifndef PERL_ARGS_ASSERT_CROAK_XS_USAGE #define PERL_ARGS_ASSERT_CROAK_XS_USAGE assert(cv); assert(params) /* prototype to pass -Wmissing-prototypes */ STATIC void S_croak_xs_usage(const CV *const cv, const char *const params); STATIC void S_croak_xs_usage(const CV *const cv, const char *const params) { const GV *const gv = CvGV(cv); PERL_ARGS_ASSERT_CROAK_XS_USAGE; if (gv) { const char *const gvname = GvNAME(gv); const HV *const stash = GvSTASH(gv); const char *const hvname = stash ? HvNAME(stash) : NULL; if (hvname) Perl_croak_nocontext("Usage: %s::%s(%s)", hvname, gvname, params); else Perl_croak_nocontext("Usage: %s(%s)", gvname, params); } else { /* Pants. I don't think that it should be possible to get here. */ Perl_croak_nocontext("Usage: CODE(0x%" UVxf ")(%s)", PTR2UV(cv), params); } } #undef PERL_ARGS_ASSERT_CROAK_XS_USAGE #define croak_xs_usage S_croak_xs_usage #endif /* NOTE: the prototype of newXSproto() is different in versions of perls, * so we define a portable version of newXSproto() */ #ifdef newXS_flags #define newXSproto_portable(name, c_impl, file, proto) newXS_flags(name, c_impl, file, proto, 0) #else #define newXSproto_portable(name, c_impl, file, proto) (PL_Sv=(SV*)newXS(name, c_impl, file), sv_setpv(PL_Sv, proto), (CV*)PL_Sv) #endif /* !defined(newXS_flags) */ #if PERL_VERSION_LE(5, 21, 5) # define newXS_deffile(a,b) Perl_newXS(aTHX_ a,b,file) #else # define newXS_deffile(a,b) Perl_newXS_deffile(aTHX_ a,b) #endif #line 172 "t/test.c" XS_EUPXS(XS_t__test_make_checkdata); /* prototype to pass -Wmissing-prototypes */ XS_EUPXS(XS_t__test_make_checkdata) { dVAR; dXSARGS; if (items < 2 || items > 3) croak_xs_usage(cv, "checkspec, name, constraint= &PL_sv_undef"); { struct DataChecks_Checker * RETVAL; dXSTARG; SV * checkspec = ST(0) ; SV * name = ST(1) ; SV * constraint; if (items < 3) constraint = &PL_sv_undef; else { constraint = ST(2) ; } #line 26 "t/test.xs" RETVAL = make_checkdata(checkspec); gen_assertmess(RETVAL, name, constraint); #line 198 "t/test.c" XSprePUSH; PUSHi(PTR2IV(RETVAL)); } XSRETURN(1); } XS_EUPXS(XS_t__test_free_checkdata); /* prototype to pass -Wmissing-prototypes */ XS_EUPXS(XS_t__test_free_checkdata) { dVAR; dXSARGS; if (items != 1) croak_xs_usage(cv, "checker"); { struct DataChecks_Checker * checker = INT2PTR(struct DataChecks_Checker *,SvIV(ST(0))) ; free_checkdata(checker); } XSRETURN_EMPTY; } XS_EUPXS(XS_t__test_check_value); /* prototype to pass -Wmissing-prototypes */ XS_EUPXS(XS_t__test_check_value) { dVAR; dXSARGS; if (items != 2) croak_xs_usage(cv, "checker, value"); { bool RETVAL; struct DataChecks_Checker * checker = INT2PTR(struct DataChecks_Checker *,SvIV(ST(0))) ; SV * value = ST(1) ; RETVAL = check_value(checker, value); ST(0) = boolSV(RETVAL); } XSRETURN(1); } XS_EUPXS(XS_t__test_assert_value); /* prototype to pass -Wmissing-prototypes */ XS_EUPXS(XS_t__test_assert_value) { dVAR; dXSARGS; if (items != 2) croak_xs_usage(cv, "checker, value"); { struct DataChecks_Checker * checker = INT2PTR(struct DataChecks_Checker *,SvIV(ST(0))) ; SV * value = ST(1) ; assert_value(checker, value); } XSRETURN_EMPTY; } XS_EUPXS(XS_t__test_make_asserter_sub); /* prototype to pass -Wmissing-prototypes */ XS_EUPXS(XS_t__test_make_asserter_sub) { dVAR; dXSARGS; if (items < 1 || items > 2) croak_xs_usage(cv, "checker, flagname= &PL_sv_undef"); { SV * RETVAL; struct DataChecks_Checker * checker = INT2PTR(struct DataChecks_Checker *,SvIV(ST(0))) ; SV * flagname; if (items < 2) flagname = &PL_sv_undef; else { flagname = ST(1) ; } #line 39 "t/test.xs" { if(!PL_parser) { /* We need to generate just enough of a PL_parser to keep newSTATEOP() * happy, otherwise it will SIGSEGV */ SAVEVPTR(PL_parser); Newxz(PL_parser, 1, yy_parser); SAVEFREEPV(PL_parser); PL_parser->copline = NOLINE; PL_parser->preambling = NOLINE; } U32 flags = 0; if(flagname && SvOK(flagname)) { if(SvPOK(flagname) && strEQ(SvPVX(flagname), "void")) flags = OPf_WANT_VOID; } I32 floorix = start_subparse(FALSE, 0); OP *body = newLISTOPn(OP_RETURN, 0, make_assertop_flags(checker, flags, newSLUGOP(0)), NULL); CV *cv = newATTRSUB(floorix, NULL, NULL, NULL, body); RETVAL = newRV_noinc((SV *)cv); } #line 305 "t/test.c" RETVAL = sv_2mortal(RETVAL); ST(0) = RETVAL; } XSRETURN(1); } #ifdef __cplusplus extern "C" { #endif XS_EXTERNAL(boot_t__test); /* prototype to pass -Wmissing-prototypes */ XS_EXTERNAL(boot_t__test) { #if PERL_VERSION_LE(5, 21, 5) dVAR; dXSARGS; #else dVAR; dXSBOOTARGSXSAPIVERCHK; #endif #if PERL_VERSION_LE(5, 8, 999) /* PERL_VERSION_LT is 5.33+ */ char* file = __FILE__; #else const char* file = __FILE__; #endif PERL_UNUSED_VAR(file); PERL_UNUSED_VAR(cv); /* -W */ PERL_UNUSED_VAR(items); /* -W */ #if PERL_VERSION_LE(5, 21, 5) XS_VERSION_BOOTCHECK; # ifdef XS_APIVERSION_BOOTCHECK XS_APIVERSION_BOOTCHECK; # endif #endif newXS_deffile("t::test::make_checkdata", XS_t__test_make_checkdata); newXS_deffile("t::test::free_checkdata", XS_t__test_free_checkdata); newXS_deffile("t::test::check_value", XS_t__test_check_value); newXS_deffile("t::test::assert_value", XS_t__test_assert_value); newXS_deffile("t::test::make_asserter_sub", XS_t__test_make_asserter_sub); /* Initialisation Section */ #line 69 "t/test.xs" boot_data_checks(0); #line 351 "t/test.c" /* End of Initialisation Section */ #if PERL_VERSION_LE(5, 21, 5) # if PERL_VERSION_GE(5, 9, 0) if (PL_unitcheckav) call_list(PL_scopestack_ix, PL_unitcheckav); # endif XSRETURN_YES; #else Perl_xs_boot_epilog(aTHX_ ax); #endif } #ifdef __cplusplus } #endif Data-Checks-0.10/t/test.xs000444001750001750 350614660677075 14203 0ustar00leoleo000000000000/* 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, 2024 -- leonerd@leonerd.org.uk */ #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include "DataChecks.h" #define HAVE_PERL_VERSION(R, V, S) \ (PERL_REVISION > (R) || (PERL_REVISION == (R) && (PERL_VERSION > (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION >= (S)))))) #include "optree-additions.c.inc" MODULE = t::test PACKAGE = t::test TYPEMAP: <copline = NOLINE; PL_parser->preambling = NOLINE; } U32 flags = 0; if(flagname && SvOK(flagname)) { if(SvPOK(flagname) && strEQ(SvPVX(flagname), "void")) flags = OPf_WANT_VOID; } I32 floorix = start_subparse(FALSE, 0); OP *body = newLISTOPn(OP_RETURN, 0, make_assertop_flags(checker, flags, newSLUGOP(0)), NULL); CV *cv = newATTRSUB(floorix, NULL, NULL, NULL, body); RETVAL = newRV_noinc((SV *)cv); } OUTPUT: RETVAL BOOT: boot_data_checks(0); Data-Checks-0.10/t/testcase.pm000444001750001750 452414660677075 15022 0ustar00leoleo000000000000package testcase; use v5.22; use warnings; use Test2::V0; use lib "t/blib", "t/blib/arch"; use Data::Checks; sub import { my $pkg = shift; my $caller = caller; require XSLoader; XSLoader::load( shift, $Data::Checks::VERSION ); while( @_ ) { my $sym = shift; my $ref = __PACKAGE__->can( $sym ) or die "Cannot import $sym\n"; no strict 'refs'; *{"${caller}::${sym}"} = $ref; } } sub unimport { die "testcase cannot be unimported"; } # Some test classes package BaseClass { sub new { bless [], shift } } package DifferentClass { sub new { bless [], shift } } package DerivedClass { use base qw( BaseClass ); } package ClassWithStrOverload { use overload '""' => sub { "boo" }; sub new { bless [], shift } } package ClassWithNumOverload { use overload '0+' => sub { 123 }; sub new { bless [], shift } } package ClassWithArrayRefify { sub new { bless [], shift } use overload '@{}' => sub {}; } package ClassWithHashRefify { sub new { bless [], shift } use overload '%{}' => sub {}; } package ClassWithCodeRefify { sub new { bless [], shift } use overload '&{}' => sub {}; } sub test_constraint { my ( $name, $constraint, $accepts, $rejects ) = @_; $rejects //= []; subtest "$name constraint" => sub { my $checker = t::test::make_checkdata( $constraint, "Value" ); my %accepted; my @accepts = @$accepts; while( @accepts ) { my $valname = shift @accepts; my $value = shift @accepts; ok( t::test::check_value( $checker, $value ), "$name accepts $valname" ); $accepted{$valname}++; } my @rejects = @$rejects; while( @rejects ) { my $valname = shift @rejects; my $value = shift @rejects; ok( !t::test::check_value( $checker, $value ), "$name rejects $valname" ); } foreach ( [ 'undef' => undef ], [ 'plain string' => "a string" ], [ 'plain integer' => 1234 ], [ 'plain arrayref' => [] ], [ 'plain hashref' => {} ], [ 'plain coderef' => sub {} ], [ 'object' => BaseClass->new ], ) { my ( $valname, $value ) = @$_; next if $accepted{$valname}; ok( !t::test::check_value( $checker, $value ), "$name rejects $valname (autogenerated)" ); } } } 0x55AA;