Affix

 view release on metacpan or  search on metacpan

lib/Affix/Wrap.pm  view on Meta::CPAN

                    if    ( $current_tag eq 'brief' )                           { $data->{brief}                  .= ' ' . $line; }
                    elsif ( $current_tag eq 'param' && defined $current_param ) { $data->{params}{$current_param} .= ' ' . $line; }
                    elsif ( $current_tag eq 'return' )                          { $data->{return}                 .= ' ' . $line; }
                    else                                                        { $data->{desc} .= ( length( $data->{desc} ) ? "\n" : '' ) . $line; }
                }
            }
            if ( length( $data->{brief} ) == 0 && length( $data->{desc} ) > 0 ) {
                if ( $data->{desc} =~ s/^(.+?\.)\s+//s ) { $data->{brief} = $1; }
            }
            return $doc_data = $data;
        }

        method pod {
            my $d   = $self->parse_doc;
            my $out = '=head2 ' . $self->name . "\n\n";
            $out .= $self->_format_pod( $d->{brief} ) . "\n\n" if length $d->{brief};
            $out .= $self->_format_pod( $d->{desc} ) . "\n\n"  if length $d->{desc};

            # Format parameters
            if ( keys %{ $d->{params} } ) {
                $out .= "=over\n\n";
                my @param_names = sort keys %{ $d->{params} };

                # If we have args metadata (e.g. Function), use it for ordering
                if ( $self->can('args') && ref( $self->args ) eq 'ARRAY' ) {
                    @param_names = map { $_->name } grep { exists $d->{params}{ $_->name } } @{ $self->args };

                    # Fallback for params documented but not in signature (rare but possible in C macros/varargs)
                    my %seen = map { $_ => 1 } @param_names;
                    push @param_names, grep { !$seen{$_} } sort keys %{ $d->{params} };
                }
                for my $name (@param_names) {
                    $out .= "=item C<$name>\n\n" . $self->_format_pod( $d->{params}{$name} ) . "\n\n";
                }
                $out .= "=back\n\n";
            }

            # Format return value
            if ( length $d->{return} ) {
                $out .= "B<Returns:> " . $self->_format_pod( $d->{return} ) . "\n\n";
            }
            $out;
        }
        method affix( $lib //= (), $pkg //= () ) { return undef }
    }
    class    #
        Affix::Wrap::Member {
        use Affix qw[Void];
        field $name       : reader : param //= '';
        field $type       : reader : param //= '';
        field $doc        : reader : param //= ();
        field $definition : reader : param //= ();

        method affix_type {
            return $definition->affix_type if defined $definition;
            return $type->affix_type;
        }

        method affix {
            return $definition->affix if defined $definition;
            return $type->affix       if builtin::blessed($type);

            # Fallback: if it's just a string, wrap it in a Reference object
            return Affix::Type::Reference->new( name => $type =~ s/^@//r ) if defined $type;
            return Affix::Void();
        }
    }
    class    #
        Affix::Wrap::Macro : isa(Affix::Wrap::Entity) {
        field $value : reader : param //= ();
        method set_value ($v) { $value = $v }

        method affix_type {
            my $v = $self->value // return '';

            # Sanitize C string concatenations in macros (e.g. "a" "b" -> "ab")
            $v =~ s/"\s+"//g;

            # Strip outer quotes from C string literals
            if    ( $v =~ /^"(.*)"$/ ) { $v = $1; $v =~ s/\\(.)/$1/g; }
            elsif ( $v =~ /^'(.*)'$/ ) { $v = $1; }

            # Protect against Perl-internal reserved names (starting with __)
            # and macros containing unresolved C calls or backslashes
            if ( $self->name =~ /^__/ || $v =~ /[\\()]/ ) {
                return '# use constant ' . $self->name . " => $v";
            }
            if ( $v =~ /^-?(?:0x[\da-fA-F]+|\d+(?:\.\d+)?)$/ ) {
                return sprintf 'use constant %s => %s', $self->name, $v;
            }
            $v =~ s/'/\\'/g;
            return "use constant " . $self->name . " => '$v'";
        }

        method affix ( $lib //= (), $pkg //= () ) {
            if ( $pkg && defined $value && length $value ) {
                my $val = $value;
                if ( $val =~ /^"(.*)"$/ || $val =~ /^'(.*)'$/ ) { $val = $1; }
                if ( $pkg =~ /^[a-zA-Z_]\w*(::\w+)*$/ && $self->name =~ /^[a-zA-Z_]\w*$/ ) {
                    no strict 'refs';
                    no warnings 'redefine';
                    *{ "${pkg}::" . $self->name } = sub () {$val};
                }
            }
            sub () {$value};
        }
        };
    class    #
        Affix::Wrap::Variable : isa(Affix::Wrap::Entity) {
        field $type : reader : param;

        method affix_type {

            # Safely declare the package var, then attempt to pin it
            sprintf "our \$%s;\n    try { Affix::pin(\$%s, \$lib, '%s', %s) } catch(\$e) { }", $self->name, $self->name, $self->name,
                $type->affix_type;
        }

        method affix ( $lib, $pkg //= () ) {
            my $var;
            try {

lib/Affix/Wrap.pm  view on Meta::CPAN

                # Function pointer: ret (*name)(args)
                if ( $b =~ s/^\s*([\w\s\*]+?)\s*\(\*\s*(\w+)\)\s*\((.*?)\)\s*;// ) {
                    my ( $ret_str, $name, $args_str ) = ( $1, $2, $3 );
                    my $ret = Affix::Wrap::Type->parse($ret_str);
                    my @args;
                    if ( $args_str ne '' && $args_str ne 'void' ) {
                        @args = map { Affix::Wrap::Type->parse($_) } split( /\s*,\s*/, $args_str );
                    }
                    my $type_obj = Affix::Wrap::Type::CodeRef->new( ret => $ret, params => \@args );
                    push @m, Affix::Wrap::Member->new( name => $name, type => $type_obj, doc => $clean->($pending_doc) );
                    $pending_doc = '';
                    next;
                }
                if ( $b =~ s/^\s*(.+?)([\s\*]+)([a-zA-Z_]\w*(?:\[.*?\])?)\s*;// ) {
                    my ( $t, $sep, $n ) = ( $1, $2, $3 );
                    $t .= $sep;
                    $t =~ s/^\s+|\s+$//g;
                    if ( $n =~ s/(\[.*\])$// ) { $t .= $1 }
                    push @m, Affix::Wrap::Member->new( name => $n, type => Affix::Wrap::Type->parse($t), doc => $clean->($pending_doc) );
                    $pending_doc = '';
                    next;
                }
                substr( $b, 0, 1 ) = '';
                $pending_doc = '';
            }
            return \@m;
        }
        method _ln( $c, $o ) { ( substr( $c, 0, $o ) =~ tr/\n// ) + 1 }

        method _doc( $c, $o ) {
            return undef if $o == 0;
            my @l = split /\n/, substr( $c, 0, $o );
            my @d;
            my $cap = 0;
            while ( my $l = pop @l ) {
                next if !$cap && $l =~ /^\s*$/;
                if    ( $l =~ s/\s*\*\/\s*$// ) { $cap = 1; }
                elsif ( $l =~ m{^\s*//} )       { $cap = 1; }
                if    ($cap) {
                    unshift @d, $l;
                    last if $l =~ /^\s*\/\*/;
                    last if $l =~ m{^\s*//} && ( !@l || $l[-1] !~ m{^\s*//} );
                }
                else {last}
            }
            return undef unless @d;
            my $t = join "\n", @d;
            $t =~ s/^\s*(\/\*+|\*+\/|\*|\/\/)\s?//mg;
            $t =~ s/^\s+|\s+$//g;
            return $t;
        }
    }

    class Affix::Wrap {
        field $driver        : param //= ();
        field $project_files : param //= $driver->project_files;
        field $include_dirs  : param //= [];
        field $types         : param //= {};
        #
        ADJUST {
            if ( defined $driver && !builtin::blessed($driver) ) {
                if    ( $driver eq 'Clang' ) { $driver = Affix::Wrap::Driver::Clang->new( project_files => $project_files ); }
                elsif ( $driver eq 'Regex' ) { $driver = Affix::Wrap::Driver::Regex->new( project_files => $project_files ); }
                else                         { die "Unknown driver '$driver'"; }
            }
            elsif ( !defined $driver ) {

                # Wrap in a localized warn-handler to suppress the internal "Can't spawn" error
                my $exit = do {
                    local $SIG{__WARN__} = sub { };
                    system( 'clang', '--version', '>', File::Spec->devnull, '2>&1' );
                };
                my $use_clang = ( $exit == 0 );
                $driver = $use_clang ? Affix::Wrap::Driver::Clang->new( project_files => $project_files ) :
                    Affix::Wrap::Driver::Regex->new( project_files => $project_files );
            }
        }

        method parse( $entry_point //= () ) {
            $entry_point //= $project_files->[0];
            my @nodes = $driver->parse( $entry_point, $include_dirs );
            $self->_resolve_macros( \@nodes );
            return @nodes;
        }

        method _resolve_macros ($nodes) {
            my %macros;
            for my $node (@$nodes) {
                if ( $node isa Affix::Wrap::Macro ) {
                    my $val = $node->value // '';
                    $val =~ s/(?<=\d)[Uu][Ll]{0,2}//g;    # Strip C suffixes like 100ULL
                    $macros{ $node->name } = $val;
                }
            }
            my %cache;
            my $resolve;
            $resolve = sub {
                my ($token) = @_;
                return undef unless defined $token;
                $token =~ s/^\s+|\s+$//g;
                return oct($token)    if $token =~ /^0x[\da-fA-F]+$/i;
                return $token + 0     if $token =~ /^-?\d+(?:\.\d+)?$/;
                return $cache{$token} if exists $cache{$token};
                local $cache{$token} = undef;
                my $expr = $macros{$token};
                return undef unless defined $expr;
                1 while $expr =~ s/^\((.*)\)$/$1/;    # Strip outer parens

                # Resolve bitwise and arithmetic expressions
                if ( $expr =~ /[|&<>+\-*\/]/ ) {
                    my $evaluable = $expr;

                    # Using {} delimiters so the // operator doesn't break the regex parser
                    $evaluable =~ s{\b([a-zA-Z_]\w*)\b}{ $resolve->($1) // $1 }ge;

                    # Clean up any C-style suffixes that might have survived
                    $evaluable =~ s/\b(\d+)L+\b/$1/g;

                    # If everything is now numeric/operators/whitespace, we can safely eval
                    if ( $evaluable =~ /^[0-9\s|&<>\+\-\*\/\(\).xXa-fA-F]+$/ ) {



( run in 1.033 second using v1.01-cache-2.11-cpan-a49fcb8fa48 )