Keyword-Declare

 view release on metacpan or  search on metacpan

lib/Keyword/Declare.pm  view on Meta::CPAN

        use re 'eval';
        $$src_ref =~ s{
            \A
            (?<syntax>
                            (?&PerlNWS)
                (?{ $expected = "new type name"; $failed_at = pos() })
                (?<typesigil> \$?+ )
                (?<newtype>   (?&PerlIdentifier) )
                              (?&PerlOWS)
                (?{ $expected = "'is <existing type>'"; $failed_at = pos() })
                              is
                              (?&PerlOWS)
                (?{ $expected = "existing typename or literal string or regex after 'is'"; $failed_at = pos() })
                (?<oldtype>
                    (?<oldtyperegex>  (?&PerlMatch)  )
                |
                    (?<oldtypestring> (?&PerlString) )
                |
                    (?<oldtypetype>   $TYPE_JUNCTION )
                )
            )

            $PPR::GRAMMAR
        }{}xms
            or croak "Invalid keytype definition. Expected $expected\nbut found: ",
                     substr($$src_ref, $failed_at) =~ /(\S+)/;

        # Save the information from the keyword definition...
        my %keytype_info = ( %+, location => "$file line $line", hashline => "$line $file" );

        # Set up the sigil...
        my $sigil_decl = q{};
        if ($keytype_info{typesigil}) {
            my $var = qq{$keytype_info{typesigil}$keytype_info{newtype}};
            if ($keytype_info{oldtyperegex}) {
                $keytype_info{oldtyperegex} =~ s{^m}{};
                if ($keytype_info{oldtyperegex} =~ /\(\?\&Perl[A-Z]/) {
                    $keytype_info{oldtyperegex} =~ s{^\s*(\S)}{$1\$PPR::GRAMMAR};
                }
                $sigil_decl = qq{my $var; BEGIN { $var = qr$keytype_info{oldtyperegex} }}
            }
            elsif ($keytype_info{oldtypestring}) {
                $sigil_decl = qq{my $var; BEGIN { $var = $keytype_info{oldtypestring} }}
            }
            else {
                croak "Invalid keytype definition. Can only specify a sigil on new typename ($keytype_info{typesigil}$keytype_info{newtype}) if type is specified as a string or regex";
            }
        }

        # Debug, if requested...
        if (${^H}{"Keyword::Declare debug"}) {
            my $msg = ("#" x 50) . "\n"
                    . " Installed keytype at $keytype_info{location}:\n\n$keytype_info{syntax}\n\n"
                    . ("#" x 50) . "\n";
            $msg =~ s{^}{###}gm;
            warn $msg;
        }

        # Install the lexical type definition...
        $$src_ref
            = qq{BEGIN{\$^H{q{Keyword::Declare keytype:$keytype_info{newtype}=$keytype_info{oldtype}}} = 1;}}
            . $sigil_decl
            . $$src_ref;
    };

    # Install the 'keyword' (meta-)keyword...
    Keyword::Simple::define 'keyword', sub {
        # Unpack trailing code...
        my ($src_ref) = @_;

        # Where was this keyword declared???
        my ($file, $line) = (caller)[1,2];

        # Which keywords are allowed in nested code at this point...
        my @active_IDs = @^H{ grep { m{^ Keyword::Declare \s+ active:}xms } keys %^H };
        my $lexical_keywords
            = @active_IDs ? join '|', reverse sort map { $keyword_impls[$_]{skip_matcher} } @active_IDs
            :               '(?!)';

        # These track error messages and help decompose the parameter list...
        # (they have to be package vars, so they're visible to in-regex code blocks in older Perls)
        our ($expected, $failed_at, $block_start, @params) = ('keyword name', 0, 0);

        # Match and extract the keyword definition...
        use re 'eval';
        $$src_ref =~ s{
            \A
            (?<____K_D___KeywordDeclaration>
                            (?&PerlNWS)
                (?<____K_D___keyword> (?&PerlIdentifier)                  )
                            (?&PerlOWS)
                (?{ $expected = "keyword parameters or block, or 'from' specifier"; $failed_at = pos() })
                (?<____K_D___params>  (?&____K_D___ParamList)                       )
                            (?&PerlOWS)
                (?{ $expected = 'keyword block or attribute'; $failed_at = pos() })
                (?<____K_D___attrs>   (?&PerlAttributes)?+                )
                            (?&PerlOWS)
                (?{ $expected = 'keyword block'; $failed_at = $block_start = pos() })
                (?<____K_D___block>   \{\{\{ .*? \}\}\} | (?&PerlBlock)   )
            )

            (?(DEFINE)
                (?<____K_D___ParamList>
                    \(
                        (?&____K_D___ParamSet)?+
                        (?:
                            (?&PerlOWS) \) (?&PerlOWS) :then\(
                            (?{ push @Keyword::Declare::params, undef; })
                            (?&____K_D___ParamSet)
                        )?+
                    \)
                |
                    # Nothing
                )

                (?<____K_D___ParamSet>
                                        (?&PerlOWS) (?&____K_D___Param)
                    (?: (?&PerlOWS) ,   (?&PerlOWS) (?&____K_D___Param) )*+
                                    ,?+ (?&PerlOWS)
                )

lib/Keyword/Declare.pm  view on Meta::CPAN

        }

        # Install the keyword, exporting it as well if it's in an import() or unimport() sub...
        $$src_ref = qq{ if (((caller 0)[3]//q{}) =~ /\\b(?:un)?import\\Z/) { $keyword_defn } }
                  .  q{ Keyword::Declare::_install_data_handler(); }
                  . qq{ BEGIN{ $keyword_defn } }
                  . "\n#line $line $file\n"
                  . $$src_ref;

        # Pre-empt addition of extraneous trailing newline by Keyword::Simple...
        # [REMOVE IF UPSTREAM MODULE (Keyword::Simple) IS FIXED]
        $$src_ref =~ s{\n\z}{};
    };

    # Install the 'unkeyword' (anti-meta-)keyword...
    Keyword::Simple::define 'unkeyword', sub {
        # Unpack trailing code...
        my ($src_ref) = @_;

        # Where was this keyword declared???
        my ($file, $line) = (caller)[1,2];

        # Match and extract the keyword definition...
        use re 'eval';
        $$src_ref =~ s{
            \A
            (?<leadingspace> (?&PerlNWS) )
            (?:
                (?<keyword> (?&PerlIdentifier) )
            |
                (?<unexpected> \S+ )
            )

            $PPR::GRAMMAR
        }{}xms;

        my %keyword_info = %+;

        croak "Invalid unkeyword definition. Expected keyword name (identifier)\n"
            . " but found: $keyword_info{unexpected}"
                if defined $keyword_info{unexpected};

        # Check for excessive meta-ness...
        if ($keyword_info{keyword} =~ /^(?:keyword|keytype)$/) {
            croak "Can't undefine '$keyword_info{keyword}' keyword";
        }

        # Report installation of keyword if requested...
        if (${^H}{"Keyword::Declare debug"}) {
            my $msg = ("#" x 50) . "\n"
                      . " Uninstalled keyword macro: $keyword_info{keyword}(...)\n"
                      . " at $file line $line\n"
                      . ("#" x 50) . "\n";
            $msg =~ s{^}{###}gm;
            warn $msg;
        }

        # How to remove the Keyword::Simple keyword (with workaround for earlier versions)...
        my $keyword_defn = q{Keyword::Simple::undefine( 'KEYWORD' );};
        if ($Keyword::Simple::VERSION < 0.04) {
            $keyword_defn .= "\$^H{'Keyword::Simple/keywords'} =~ s{ KEYWORD:-?\\d*}{}g;" ;
        }

        # How to remove the Keyword::Declare keywords...
        $keyword_defn .= q{
            delete @^H{ grep m{^ Keyword::Declare \s+ active:KEYWORD:}xms, keys %^H };
        };
        $keyword_defn =~ s{KEYWORD}{$keyword_info{keyword}}g;

        # Uninstall the keyword, exporting it as well if it's in an import() or unimport() sub...
        $$src_ref = qq{ if (((caller 0)[3]//q{}) =~ /\\b(?:un)?import\\Z/) { $keyword_defn } }
                  . qq{ BEGIN{ $keyword_defn } }
                  . "\n#line $line $file\n"
                  . $$src_ref;
    };
}

# Keyword::Simple::define() has a bug: it can't define keywords starting with _
# [REMOVE WHEN UPSTREAM MODULE (Keyword::Simple) IS FIXED]
sub _replacement_define {
    package Keyword::Simple;

    my ($kw, $sub) = @_;
    $kw =~ /^[^\W\d]\w*\z/ or croak "'$kw' doesn't look like an identifier";
    ref($sub) eq 'CODE' or croak "'$sub' doesn't look like a coderef";

    if ($Keyword::Simple::VERSION < 0.04) {
        our @meta;
        my $n = @meta;
        push @meta, $sub;

        $^H{+HINTK_KEYWORDS} .= " $kw:$n";
        use B::Hooks::EndOfScope;
        on_scope_end {
            delete $meta[$n];
        };
    }
    else {
        my %keywords = %{$^H{+HINTK_KEYWORDS} // {}};
        $keywords{$kw} = $sub;
        $^H{+HINTK_KEYWORDS} = \%keywords;
    }
}

# Install a __DATA__ keyword to overcome bug in Keyword::Simple...
# [REMOVE WHEN UPSTREAM MODULE (Keyword::Simple) IS FIXED]
sub _install_data_handler {
    my $DATA_HANDLER = sub {
        # Unpack trailing code...
        my ($src_ref) = @_;

        # Convert to data handle...
        my $data = $$src_ref;
        $data =~ s{ \A [^\n]* \n }{}xms;
        $data .= "\n" unless substr($data,-1) eq "\n";

        # Create end-of-__DATA__ marker unlikely to be in the data...
        my $END_DATA = "\3" x 253;  # \3 is ASCII END-OF-TEXT, 253 is max ident length

        # Replace trailing code with code that opens a local *DATA handle...
        $$src_ref = qq{BEGIN {open *DATA, '<', \\<<'$END_DATA'\n$data$END_DATA\n} 1; };



( run in 2.085 seconds using v1.01-cache-2.11-cpan-2398b32b56e )