diff options
Diffstat (limited to 'ext/XS-APItest')
-rw-r--r-- | ext/XS-APItest/APItest.pm | 98 | ||||
-rw-r--r-- | ext/XS-APItest/APItest.xs | 228 | ||||
-rw-r--r-- | ext/XS-APItest/Makefile.PL | 2 | ||||
-rw-r--r-- | ext/XS-APItest/t/keyword_multiline.t | 27 | ||||
-rw-r--r-- | ext/XS-APItest/t/keyword_plugin.t | 76 | ||||
-rw-r--r-- | ext/XS-APItest/t/stuff_svcur_bug.t | 12 | ||||
-rw-r--r-- | ext/XS-APItest/t/swaptwostmts.t | 158 |
7 files changed, 597 insertions, 4 deletions
diff --git a/ext/XS-APItest/APItest.pm b/ext/XS-APItest/APItest.pm index ca121adcc9..474d528fb4 100644 --- a/ext/XS-APItest/APItest.pm +++ b/ext/XS-APItest/APItest.pm @@ -1,6 +1,6 @@ package XS::APItest; -use 5.008; +{ use 5.011001; } use strict; use warnings; use Carp; @@ -35,6 +35,11 @@ sub import { } } } + foreach (keys %{$exports||{}}) { + next unless /\A(?:rpn|calcrpn|stufftest|swaptwostmts)\z/; + $^H{"XS::APItest/$_"} = 1; + delete $exports->{$_}; + } if ($exports) { my @carp = keys %$exports; if (@carp) { @@ -45,7 +50,7 @@ sub import { } } -our $VERSION = '0.22'; +our $VERSION = '0.23'; use vars '$WARNINGS_ON_BOOTSTRAP'; use vars map "\$${_}_called_PP", qw(BEGIN UNITCHECK CHECK INIT END); @@ -101,6 +106,10 @@ XS::APItest - Test the perl C API use XS::APItest; print_double(4); + use XS::APItest qw(rpn calcrpn); + $triangle = rpn($n $n 1 + * 2 /); + calcrpn $triangle { $n $n 1 + * 2 / } + =head1 ABSTRACT This module tests the perl C API. Also exposes various bit of the perl @@ -226,6 +235,86 @@ Exercises the C function of the same name. Returns nothing. =back +=head1 KEYWORDS + +These are not supplied by default, but must be explicitly imported. +They are lexically scoped. + +=over + +=item rpn(EXPRESSION) + +This construct is a Perl expression. I<EXPRESSION> must be an RPN +arithmetic expression, as described below. The RPN expression is +evaluated, and its value is returned as the value of the Perl expression. + +=item calcrpn VARIABLE { EXPRESSION } + +This construct is a complete Perl statement. (No semicolon should +follow the closing brace.) I<VARIABLE> must be a Perl scalar C<my> +variable, and I<EXPRESSION> must be an RPN arithmetic expression as +described below. The RPN expression is evaluated, and its value is +assigned to the variable. + +=back + +=head2 RPN expression syntax + +Tokens of an RPN expression may be separated by whitespace, but such +separation is usually not required. It is required only where unseparated +tokens would look like a longer token. For example, C<12 34 +> can be +written as C<12 34+>, but not as C<1234 +>. + +An RPN expression may be any of: + +=over + +=item C<1234> + +A sequence of digits is an unsigned decimal literal number. + +=item C<$foo> + +An alphanumeric name preceded by dollar sign refers to a Perl scalar +variable. Only variables declared with C<my> or C<state> are supported. +If the variable's value is not a native integer, it will be converted +to an integer, by Perl's usual mechanisms, at the time it is evaluated. + +=item I<A> I<B> C<+> + +Sum of I<A> and I<B>. + +=item I<A> I<B> C<-> + +Difference of I<A> and I<B>, the result of subtracting I<B> from I<A>. + +=item I<A> I<B> C<*> + +Product of I<A> and I<B>. + +=item I<A> I<B> C</> + +Quotient when I<A> is divided by I<B>, rounded towards zero. +Division by zero generates an exception. + +=item I<A> I<B> C<%> + +Remainder when I<A> is divided by I<B> with the quotient rounded towards zero. +Division by zero generates an exception. + +=back + +Because the arithmetic operators all have fixed arity and are postfixed, +there is no need for operator precedence, nor for a grouping operator +to override precedence. This is half of the point of RPN. + +An RPN expression can also be interpreted in another way, as a sequence +of operations on a stack, one operation per token. A literal or variable +token pushes a value onto the stack. A binary operator pulls two items +off the stack, performs a calculation with them, and pushes the result +back onto the stack. The stack starts out empty, and at the end of the +expression there must be exactly one value left on the stack. + =head1 SEE ALSO L<XS::Typemap>, L<perlapi>. @@ -234,13 +323,16 @@ L<XS::Typemap>, L<perlapi>. Tim Jenness, E<lt>t.jenness@jach.hawaii.eduE<gt>, Christian Soeller, E<lt>csoelle@mph.auckland.ac.nzE<gt>, -Hugo van der Sanden E<lt>hv@crypt.compulink.co.ukE<gt> +Hugo van der Sanden E<lt>hv@crypt.compulink.co.ukE<gt>, +Andrew Main (Zefram) <zefram@fysh.org> =head1 COPYRIGHT AND LICENSE Copyright (C) 2002,2004 Tim Jenness, Christian Soeller, Hugo van der Sanden. All Rights Reserved. +Copyright (C) 2009 Andrew Main (Zefram) <zefram@fysh.org> + This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. diff --git a/ext/XS-APItest/APItest.xs b/ext/XS-APItest/APItest.xs index 48542dd6d0..67c27380dc 100644 --- a/ext/XS-APItest/APItest.xs +++ b/ext/XS-APItest/APItest.xs @@ -372,6 +372,224 @@ my_rpeep (pTHX_ OP *o) } } +/** RPN keyword parser **/ + +#define sv_is_glob(sv) (SvTYPE(sv) == SVt_PVGV) +#define sv_is_regexp(sv) (SvTYPE(sv) == SVt_REGEXP) +#define sv_is_string(sv) \ + (!sv_is_glob(sv) && !sv_is_regexp(sv) && \ + (SvFLAGS(sv) & (SVf_IOK|SVf_NOK|SVf_POK|SVp_IOK|SVp_NOK|SVp_POK))) + +static SV *hintkey_rpn_sv, *hintkey_calcrpn_sv, *hintkey_stufftest_sv; +static SV *hintkey_swaptwostmts_sv; +static int (*next_keyword_plugin)(pTHX_ char *, STRLEN, OP **); + +/* low-level parser helpers */ + +#define PL_bufptr (PL_parser->bufptr) +#define PL_bufend (PL_parser->bufend) + +/* RPN parser */ + +#define parse_var() THX_parse_var(aTHX) +static OP *THX_parse_var(pTHX) +{ + char *s = PL_bufptr; + char *start = s; + PADOFFSET varpos; + OP *padop; + if(*s != '$') croak("RPN syntax error"); + while(1) { + char c = *++s; + if(!isALNUM(c)) break; + } + if(s-start < 2) croak("RPN syntax error"); + lex_read_to(s); + { + /* because pad_findmy() doesn't really use length yet */ + SV *namesv = sv_2mortal(newSVpvn(start, s-start)); + varpos = pad_findmy(SvPVX(namesv), s-start, 0); + } + if(varpos == NOT_IN_PAD || PAD_COMPNAME_FLAGS_isOUR(varpos)) + croak("RPN only supports \"my\" variables"); + padop = newOP(OP_PADSV, 0); + padop->op_targ = varpos; + return padop; +} + +#define push_rpn_item(o) \ + (tmpop = (o), tmpop->op_sibling = stack, stack = tmpop) +#define pop_rpn_item() \ + (!stack ? (croak("RPN stack underflow"), (OP*)NULL) : \ + (tmpop = stack, stack = stack->op_sibling, \ + tmpop->op_sibling = NULL, tmpop)) + +#define parse_rpn_expr() THX_parse_rpn_expr(aTHX) +static OP *THX_parse_rpn_expr(pTHX) +{ + OP *stack = NULL, *tmpop; + while(1) { + I32 c; + lex_read_space(0); + c = lex_peek_unichar(0); + switch(c) { + case /*(*/')': case /*{*/'}': { + OP *result = pop_rpn_item(); + if(stack) croak("RPN expression must return a single value"); + return result; + } break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': { + UV val = 0; + do { + lex_read_unichar(0); + val = 10*val + (c - '0'); + c = lex_peek_unichar(0); + } while(c >= '0' && c <= '9'); + push_rpn_item(newSVOP(OP_CONST, 0, newSVuv(val))); + } break; + case '$': { + push_rpn_item(parse_var()); + } break; + case '+': { + OP *b = pop_rpn_item(); + OP *a = pop_rpn_item(); + lex_read_unichar(0); + push_rpn_item(newBINOP(OP_I_ADD, 0, a, b)); + } break; + case '-': { + OP *b = pop_rpn_item(); + OP *a = pop_rpn_item(); + lex_read_unichar(0); + push_rpn_item(newBINOP(OP_I_SUBTRACT, 0, a, b)); + } break; + case '*': { + OP *b = pop_rpn_item(); + OP *a = pop_rpn_item(); + lex_read_unichar(0); + push_rpn_item(newBINOP(OP_I_MULTIPLY, 0, a, b)); + } break; + case '/': { + OP *b = pop_rpn_item(); + OP *a = pop_rpn_item(); + lex_read_unichar(0); + push_rpn_item(newBINOP(OP_I_DIVIDE, 0, a, b)); + } break; + case '%': { + OP *b = pop_rpn_item(); + OP *a = pop_rpn_item(); + lex_read_unichar(0); + push_rpn_item(newBINOP(OP_I_MODULO, 0, a, b)); + } break; + default: { + croak("RPN syntax error"); + } break; + } + } +} + +#define parse_keyword_rpn() THX_parse_keyword_rpn(aTHX) +static OP *THX_parse_keyword_rpn(pTHX) +{ + OP *op; + lex_read_space(0); + if(lex_peek_unichar(0) != '('/*)*/) + croak("RPN expression must be parenthesised"); + lex_read_unichar(0); + op = parse_rpn_expr(); + if(lex_peek_unichar(0) != /*(*/')') + croak("RPN expression must be parenthesised"); + lex_read_unichar(0); + return op; +} + +#define parse_keyword_calcrpn() THX_parse_keyword_calcrpn(aTHX) +static OP *THX_parse_keyword_calcrpn(pTHX) +{ + OP *varop, *exprop; + lex_read_space(0); + varop = parse_var(); + lex_read_space(0); + if(lex_peek_unichar(0) != '{'/*}*/) + croak("RPN expression must be braced"); + lex_read_unichar(0); + exprop = parse_rpn_expr(); + if(lex_peek_unichar(0) != /*{*/'}') + croak("RPN expression must be braced"); + lex_read_unichar(0); + return newASSIGNOP(OPf_STACKED, varop, 0, exprop); +} + +#define parse_keyword_stufftest() THX_parse_keyword_stufftest(aTHX) +static OP *THX_parse_keyword_stufftest(pTHX) +{ + I32 c; + bool do_stuff; + lex_read_space(0); + do_stuff = lex_peek_unichar(0) == '+'; + if(do_stuff) { + lex_read_unichar(0); + lex_read_space(0); + } + c = lex_peek_unichar(0); + if(c == ';') { + lex_read_unichar(0); + } else if(c != /*{*/'}') { + croak("syntax error"); + } + if(do_stuff) lex_stuff_pvs(" ", 0); + return newOP(OP_NULL, 0); +} + +#define parse_keyword_swaptwostmts() THX_parse_keyword_swaptwostmts(aTHX) +static OP *THX_parse_keyword_swaptwostmts(pTHX) +{ + OP *a, *b; + a = parse_fullstmt(0); + b = parse_fullstmt(0); + if(a && b) + PL_hints |= HINT_BLOCK_SCOPE; + /* should use append_list(), but that's not part of the public API */ + return !a ? b : !b ? a : newLISTOP(OP_LINESEQ, 0, b, a); +} + +/* plugin glue */ + +#define keyword_active(hintkey_sv) THX_keyword_active(aTHX_ hintkey_sv) +static int THX_keyword_active(pTHX_ SV *hintkey_sv) +{ + HE *he; + if(!GvHV(PL_hintgv)) return 0; + he = hv_fetch_ent(GvHV(PL_hintgv), hintkey_sv, 0, + SvSHARED_HASH(hintkey_sv)); + return he && SvTRUE(HeVAL(he)); +} + +static int my_keyword_plugin(pTHX_ + char *keyword_ptr, STRLEN keyword_len, OP **op_ptr) +{ + if(keyword_len == 3 && strnEQ(keyword_ptr, "rpn", 3) && + keyword_active(hintkey_rpn_sv)) { + *op_ptr = parse_keyword_rpn(); + return KEYWORD_PLUGIN_EXPR; + } else if(keyword_len == 7 && strnEQ(keyword_ptr, "calcrpn", 7) && + keyword_active(hintkey_calcrpn_sv)) { + *op_ptr = parse_keyword_calcrpn(); + return KEYWORD_PLUGIN_STMT; + } else if(keyword_len == 9 && strnEQ(keyword_ptr, "stufftest", 9) && + keyword_active(hintkey_stufftest_sv)) { + *op_ptr = parse_keyword_stufftest(); + return KEYWORD_PLUGIN_STMT; + } else if(keyword_len == 12 && + strnEQ(keyword_ptr, "swaptwostmts", 12) && + keyword_active(hintkey_swaptwostmts_sv)) { + *op_ptr = parse_keyword_swaptwostmts(); + return KEYWORD_PLUGIN_STMT; + } else { + return next_keyword_plugin(aTHX_ keyword_ptr, keyword_len, op_ptr); + } +} + #include "const-c.inc" MODULE = XS::APItest PACKAGE = XS::APItest @@ -1319,3 +1537,13 @@ BOOT: cv = GvCV(*meth); CvLVALUE_on(cv); } + +BOOT: +{ + hintkey_rpn_sv = newSVpvs_share("XS::APItest/rpn"); + hintkey_calcrpn_sv = newSVpvs_share("XS::APItest/calcrpn"); + hintkey_stufftest_sv = newSVpvs_share("XS::APItest/stufftest"); + hintkey_swaptwostmts_sv = newSVpvs_share("XS::APItest/swaptwostmts"); + next_keyword_plugin = PL_keyword_plugin; + PL_keyword_plugin = my_keyword_plugin; +} diff --git a/ext/XS-APItest/Makefile.PL b/ext/XS-APItest/Makefile.PL index 340fc7e910..3af0eb4f96 100644 --- a/ext/XS-APItest/Makefile.PL +++ b/ext/XS-APItest/Makefile.PL @@ -8,7 +8,7 @@ WriteMakefile( 'VERSION_FROM' => 'APItest.pm', # finds $VERSION 'PREREQ_PM' => {}, # e.g., Module::Name => 1.1 ABSTRACT_FROM => 'APItest.pm', # retrieve abstract from module - AUTHOR => 'Tim Jenness <t.jenness@jach.hawaii.edu>, Christian Soeller <csoelle@mph.auckland.ac.nz>, Hugo van der Sanden <hv@crypt.compulink.co.uk>', + AUTHOR => 'Tim Jenness <t.jenness@jach.hawaii.edu>, Christian Soeller <csoelle@mph.auckland.ac.nz>, Hugo van der Sanden <hv@crypt.compulink.co.uk>, Andrew Main (Zefram) <zefram@fysh.org>', 'C' => ['exception.c', 'core.c', 'notcore.c'], 'OBJECT' => '$(BASEEXT)$(OBJ_EXT) $(O_FILES)', realclean => {FILES => 'const-c.inc const-xs.inc'}, diff --git a/ext/XS-APItest/t/keyword_multiline.t b/ext/XS-APItest/t/keyword_multiline.t new file mode 100644 index 0000000000..e2cbdb87cb --- /dev/null +++ b/ext/XS-APItest/t/keyword_multiline.t @@ -0,0 +1,27 @@ +use warnings; +use strict; + +use Test::More tests => 4; + +my($t, $n); +$n = 5; + +use XS::APItest qw(rpn); +$t = rpn($n + $n 1 + + * #wibble +#wobble +2 + / +); +is $t, 15; +is __LINE__, 18; + +$t = 0; +$t = rpn($n $n 1 + * +#line 100 + 2 /); +is $t, 15; +is __LINE__, 102; + +1; diff --git a/ext/XS-APItest/t/keyword_plugin.t b/ext/XS-APItest/t/keyword_plugin.t new file mode 100644 index 0000000000..a20c952dcb --- /dev/null +++ b/ext/XS-APItest/t/keyword_plugin.t @@ -0,0 +1,76 @@ +use warnings; +use strict; + +use Test::More tests => 13; + +BEGIN { $^H |= 0x20000; } +no warnings; + +my($triangle, $num); +$num = 5; + +$triangle = undef; +eval q{ + use XS::APItest (); + $triangle = rpn($num $num 1 + * 2 /); +}; +isnt $@, ""; + +$triangle = undef; +eval q{ + use XS::APItest qw(rpn); + $triangle = rpn($num $num 1 + * 2 /); +}; +is $@, ""; +is $triangle, 15; + +$triangle = undef; +eval q{ + use XS::APItest qw(rpn); + $triangle = join(":", "x", rpn($num $num 1 + * 2 /), "y"); +}; +is $@, ""; +is $triangle, "x:15:y"; + +$triangle = undef; +eval q{ + use XS::APItest qw(rpn); + $triangle = 1 + rpn($num $num 1 + * 2 /) * 10; +}; +is $@, ""; +is $triangle, 151; + +$triangle = undef; +eval q{ + use XS::APItest qw(rpn); + $triangle = rpn($num $num 1 + * 2 /); + $triangle++; +}; +is $@, ""; +is $triangle, 16; + +$triangle = undef; +eval q{ + use XS::APItest qw(rpn); + $triangle = rpn($num $num 1 + * 2 /) + $triangle++; +}; +isnt $@, ""; + +$triangle = undef; +eval q{ + use XS::APItest qw(calcrpn); + calcrpn $triangle { $num $num 1 + * 2 / } + $triangle++; +}; +is $@, ""; +is $triangle, 16; + +$triangle = undef; +eval q{ + use XS::APItest qw(calcrpn); + 123 + calcrpn $triangle { $num $num 1 + * 2 / } ; +}; +isnt $@, ""; + +1; diff --git a/ext/XS-APItest/t/stuff_svcur_bug.t b/ext/XS-APItest/t/stuff_svcur_bug.t new file mode 100644 index 0000000000..6d0544cc87 --- /dev/null +++ b/ext/XS-APItest/t/stuff_svcur_bug.t @@ -0,0 +1,12 @@ +use warnings; +use strict; + +use Test::More tests => 1; +ok 1; + +use XS::APItest qw(stufftest); + +# In the buggy case, a syntax error occurs at EOF. +# Adding a semicolon, any following statements, or anything else +# causes the bug not to show itself. +stufftest+;() diff --git a/ext/XS-APItest/t/swaptwostmts.t b/ext/XS-APItest/t/swaptwostmts.t new file mode 100644 index 0000000000..c11d650962 --- /dev/null +++ b/ext/XS-APItest/t/swaptwostmts.t @@ -0,0 +1,158 @@ +use warnings; +use strict; + +use Test::More tests => 22; + +BEGIN { $^H |= 0x20000; } + +my $t; + +$t = ""; +eval q{ + use XS::APItest (); + $t .= "a"; + swaptwostmts + $t .= "b"; + $t .= "c"; + $t .= "d"; +}; +isnt $@, ""; + +$t = ""; +eval q{ + use XS::APItest qw(swaptwostmts); + $t .= "a"; + swaptwostmts + $t .= "b"; + $t .= "c"; + $t .= "d"; +}; +is $@, ""; +is $t, "acbd"; + +$t = ""; +eval q{ + use XS::APItest qw(swaptwostmts); + $t .= "a"; + swaptwostmts + if(1) { $t .= "b"; } + $t .= "c"; + $t .= "d"; +}; +is $@, ""; +is $t, "acbd"; + +$t = ""; +eval q{ + use XS::APItest qw(swaptwostmts); + $t .= "a"; + swaptwostmts + $t .= "b"; + if(1) { $t .= "c"; } + $t .= "d"; +}; +is $@, ""; +is $t, "acbd"; + +$t = ""; +eval q{ + use XS::APItest qw(swaptwostmts); + $t .= "a"; + swaptwostmts + $t .= "b"; + foreach(1..3) { + $t .= "c"; + swaptwostmts + $t .= "d"; + $t .= "e"; + $t .= "f"; + } + $t .= "g"; +}; +is $@, ""; +is $t, "acedfcedfcedfbg"; + +$t = ""; +eval q{ + use XS::APItest qw(swaptwostmts); + $t .= "a"; + swaptwostmts + $t .= "b"; + $t .= "c"; +}; +is $@, ""; +is $t, "acb"; + +$t = ""; +eval q{ + use XS::APItest qw(swaptwostmts); + $t .= "a"; + swaptwostmts + $t .= "b"; + $t .= "c" +}; +is $@, ""; +is $t, "acb"; + +$t = ""; +eval q{ + use XS::APItest qw(swaptwostmts); + $t .= "a"; + swaptwostmts + $t .= "b" +}; +isnt $@, ""; + +$t = ""; +eval q{ + use XS::APItest qw(swaptwostmts); + $_ = $t; + $_ .= "a"; + swaptwostmts + if(1) { $_ .= "b"; } + tr/a-z/A-Z/; + $_ .= "d"; + $t = $_; +}; +is $@, ""; +is $t, "Abd"; + +$t = ""; +eval q{ + use XS::APItest qw(swaptwostmts); + sub add_to_t { $t .= $_[0]; } + add_to_t "a"; + swaptwostmts + if(1) { add_to_t "b"; } + add_to_t "c"; + add_to_t "d"; +}; +is $@, ""; +is $t, "acbd"; + +$t = ""; +eval q{ + use XS::APItest qw(swaptwostmts); + { $t .= "a"; } + swaptwostmts + if(1) { { $t .= "b"; } } + { $t .= "c"; } + { $t .= "d"; } +}; +is $@, ""; +is $t, "acbd"; + +$t = ""; +eval q{ + use XS::APItest qw(swaptwostmts); + no warnings "void"; + "@{[ $t .= 'a' ]}"; + swaptwostmts + if(1) { "@{[ $t .= 'b' ]}"; } + "@{[ $t .= 'c' ]}"; + "@{[ $t .= 'd' ]}"; +}; +is $@, ""; +is $t, "acbd"; + +1; |