From 6edb75df1287e49a8cc1f5812c708107d4939ff7 Mon Sep 17 00:00:00 2001 From: Dmitry Karasik <dmitry@karasik.eu.org> Date: Fri, 3 Aug 2018 11:49:49 +0200 Subject: [PATCH 1/3] experimental: implement expressions as well as statements --- .github/README.md | 5 +++-- Simple.xs | 33 +++++++++++++++++++++++++++------ lib/Keyword/Simple.pm | 10 ++++++---- t/basic.t | 7 ++++++- 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/.github/README.md b/.github/README.md index 51e4567..9e57d5e 100644 --- a/.github/README.md +++ b/.github/README.md @@ -40,7 +40,8 @@ that's currently being compiled. - `Keyword::Simple::define` - Takes two arguments, the name of a keyword and a coderef. Injects the keyword + Takes three arguments, the name of a keyword, a coderef, and a boolean flag if + the result of the keyword handler is an expression. Injects the keyword in the lexical scope currently being compiled. For every occurrence of the keyword, your coderef will be called with one argument: A reference to a scalar holding the rest of the source code (following the keyword). @@ -60,7 +61,7 @@ that's currently being compiled. This module depends on the [pluggable keyword](https://metacpan.org/pod/perlapi.html#PL_keyword_plugin) API introduced in perl 5.12. Older versions of perl are not supported. -Every new keyword is actually a complete statement by itself. The parsing magic +Every new keyword is actually a complete statement or an expression by itself. The parsing magic only happens afterwards. This means that e.g. the code in the ["SYNOPSIS"](#synopsis) actually does this: diff --git a/Simple.xs b/Simple.xs index e8dd5f8..076d735 100644 --- a/Simple.xs +++ b/Simple.xs @@ -114,9 +114,10 @@ WARNINGS_ENABLE static int (*next_keyword_plugin)(pTHX_ char *, STRLEN, OP **); -static SV *kw_handler(pTHX_ const char *kw_ptr, STRLEN kw_len) { +static SV *kw_handler(pTHX_ const char *kw_ptr, STRLEN kw_len, int * is_expr) { HV *hints; SV **psv, *sv, *sv2; + AV *av; I32 kw_xlen; @@ -152,10 +153,24 @@ static SV *kw_handler(pTHX_ const char *kw_ptr, STRLEN kw_len) { } sv = *psv; - if (!(SvROK(sv) && (sv2 = SvRV(sv), SvTYPE(sv2) == SVt_PVCV))) { - croak("%s: internal error: $^H{'%s'}{'%.*s'} not a coderef: %"SVf, MY_PKG, HINTK_KEYWORDS, (int)kw_len, kw_ptr, SVfARG(sv)); + if (!(SvROK(sv) && (av = (AV*)SvRV(sv), SvTYPE((SV*)av) == SVt_PVAV))) { + croak("%s: internal error: $^H{'%s'}{'%.*s'} not an arrayref: %"SVf, MY_PKG, HINTK_KEYWORDS, (int)kw_len, kw_ptr, SVfARG(sv)); } + if (av_len(av) != 1) { + croak("%s: internal error: $^H{'%s'}{'%.*s'} bad arrayref: %"SVf, MY_PKG, HINTK_KEYWORDS, (int)kw_len, kw_ptr, SVfARG(sv)); + } + + if ( !( psv = av_fetch(av, 0, 0))) { + croak("%s: internal error: $^H{'%s'}{'%.*s'} bad item #0: %"SVf, MY_PKG, HINTK_KEYWORDS, (int)kw_len, kw_ptr, SVfARG(sv)); + } + sv2 = *psv; + + if ( !( psv = av_fetch(av, 1, 0))) { + croak("%s: internal error: $^H{'%s'}{'%.*s'} bad item #1: %"SVf, MY_PKG, HINTK_KEYWORDS, (int)kw_len, kw_ptr, SVfARG(sv)); + } + *is_expr = SvIV(*psv); + return sv2; } @@ -240,11 +255,17 @@ static void total_recall(pTHX_ SV *cb) { static int my_keyword_plugin(pTHX_ char *keyword_ptr, STRLEN keyword_len, OP **op_ptr) { SV *cb; + int is_expr; - if ((cb = kw_handler(aTHX_ keyword_ptr, keyword_len))) { + if ((cb = kw_handler(aTHX_ keyword_ptr, keyword_len, &is_expr))) { total_recall(aTHX_ cb); - *op_ptr = newOP(OP_NULL, 0); - return KEYWORD_PLUGIN_STMT; + if ( is_expr ) { + *op_ptr = parse_fullexpr(0); + return KEYWORD_PLUGIN_EXPR; + } else { + *op_ptr = newOP(OP_NULL, 0); + return KEYWORD_PLUGIN_STMT; + } } return next_keyword_plugin(aTHX_ keyword_ptr, keyword_len, op_ptr); diff --git a/lib/Keyword/Simple.pm b/lib/Keyword/Simple.pm index a4dbff4..d139b7a 100644 --- a/lib/Keyword/Simple.pm +++ b/lib/Keyword/Simple.pm @@ -2,6 +2,7 @@ package Keyword::Simple; use v5.12.0; use warnings; +our %kw; use Carp qw(croak); @@ -12,12 +13,12 @@ BEGIN { } sub define { - my ($kw, $sub) = @_; + my ($kw, $sub, $expression) = @_; $kw =~ /^\p{XIDS}\p{XIDC}*\z/ or croak "'$kw' doesn't look like an identifier"; ref($sub) eq 'CODE' or croak "'$sub' doesn't look like a coderef"; my %keywords = %{$^H{+HINTK_KEYWORDS} // {}}; - $keywords{$kw} = $sub; + $keywords{$kw} = [ $sub, $expression ? 1 : 0 ]; $^H{+HINTK_KEYWORDS} = \%keywords; } @@ -80,7 +81,8 @@ that's currently being compiled. =item C<Keyword::Simple::define> -Takes two arguments, the name of a keyword and a coderef. Injects the keyword +Takes three arguments, the name of a keyword, a coderef, and a boolean flag if +the result of the keyword handler is an expression. Injects the keyword in the lexical scope currently being compiled. For every occurrence of the keyword, your coderef will be called with one argument: A reference to a scalar holding the rest of the source code (following the keyword). @@ -102,7 +104,7 @@ method to make the C<no Foo;> syntax work. This module depends on the L<pluggable keyword|perlapi.html/PL_keyword_plugin> API introduced in perl 5.12. Older versions of perl are not supported. -Every new keyword is actually a complete statement by itself. The parsing magic +Every new keyword is actually a complete statement or an expression by itself. The parsing magic only happens afterwards. This means that e.g. the code in the L</SYNOPSIS> actually does this: diff --git a/t/basic.t b/t/basic.t index 5322095..95df6a8 100644 --- a/t/basic.t +++ b/t/basic.t @@ -2,7 +2,7 @@ use warnings FATAL => 'all'; use strict; -use Test::More tests => 2; +use Test::More tests => 3; { package Foo; @@ -13,10 +13,14 @@ use Test::More tests => 2; Keyword::Simple::define peek => sub { substr ${$_[0]}, 0, 0, "ok 1, 'synthetic test';"; }; + Keyword::Simple::define poke => sub { + substr ${$_[0]}, 0, 0, "ok 2, 'expression' + ' test';"; + }, 1; } sub unimport { Keyword::Simple::undefine 'peek'; + Keyword::Simple::undefine 'poke'; } BEGIN { $INC{"Foo.pm"} = 1; } @@ -26,3 +30,4 @@ use Foo; peek ok 1, "natural test"; +ok 2, "expression test"; From bcdedfda6afa3c3f3e18801d0f157b0173ae0ea9 Mon Sep 17 00:00:00 2001 From: Dmitry Karasik <dmitry@karasik.eu.org> Date: Fri, 3 Aug 2018 12:09:55 +0200 Subject: [PATCH 2/3] bump minimal version to 5.14, because 5.12 lacks parse_fullexpr() --- Makefile_PL_settings.plx | 2 +- lib/Keyword/Simple.pm | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Makefile_PL_settings.plx b/Makefile_PL_settings.plx index 1430ac7..34db455 100644 --- a/Makefile_PL_settings.plx +++ b/Makefile_PL_settings.plx @@ -5,7 +5,7 @@ return { NAME => 'Keyword::Simple', AUTHOR => q{Lukas Mai <l.mai@web.de>}, - MIN_PERL_VERSION => '5.12.0', + MIN_PERL_VERSION => '5.14.0', CONFIGURE_REQUIRES => {}, BUILD_REQUIRES => {}, TEST_REQUIRES => { diff --git a/lib/Keyword/Simple.pm b/lib/Keyword/Simple.pm index d139b7a..c973704 100644 --- a/lib/Keyword/Simple.pm +++ b/lib/Keyword/Simple.pm @@ -1,6 +1,6 @@ package Keyword::Simple; -use v5.12.0; +use v5.14.0; use warnings; our %kw; @@ -102,7 +102,8 @@ method to make the C<no Foo;> syntax work. =head1 BUGS AND LIMITATIONS This module depends on the L<pluggable keyword|perlapi.html/PL_keyword_plugin> -API introduced in perl 5.12. Older versions of perl are not supported. +API introduced in perl 5.12. C<parse_> functions were introduced in 5.14. +Older versions of perl are not supported. Every new keyword is actually a complete statement or an expression by itself. The parsing magic only happens afterwards. This means that e.g. the code in the L</SYNOPSIS> From 4259fd9f5723370b33a1f919333e1aae6a398629 Mon Sep 17 00:00:00 2001 From: Dmitry Karasik <dmitry@karasik.eu.org> Date: Fri, 3 Aug 2018 12:13:23 +0200 Subject: [PATCH 3/3] don't test on 5.12 --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 211ad23..b1305fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: perl install: cpanm --quiet --installdeps --with-develop --notest . perl: - - "5.12" - "5.14" - "5.16" - "5.18"