Affix

 view release on metacpan or  search on metacpan

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

        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]+$/ ) {

                        # Using a string eval here to let Perl's engine handle C-like precedence
                        my $res = eval $evaluable;
                        return $cache{$token} = $res if defined $res;
                    }
                }

                # Fallback: Treat as simple alias (A -> B)
                return $cache{$token} = $resolve->($expr);
            };
            for my $node (@$nodes) {
                if ( $node isa Affix::Wrap::Macro ) {
                    my $val = $resolve->( $node->name );
                    $node->set_value($val) if defined $val;
                }
            }
        }

        method _generate_code( $lib, $pkg ) {
            Carp::croak("Affix::Wrap::generate/wrap requires a valid package name") unless defined $pkg && $pkg =~ /^[a-zA-Z_]\w*(::\w+)*$/;
            my @nodes = $self->parse;
            my %unique_types;
            my %referenced_names;
            for my $node (@nodes) {
                if ( $node->can('name') && $node->name && $node->name ne '(anonymous)' ) {
                    next if $node isa Affix::Wrap::Macro || $node isa Affix::Wrap::Function || $node isa Affix::Wrap::Variable;
                    $unique_types{ $node->name } = $node;
                }

                # Collect dependencies from affix_type strings (e.g. '@sockaddr')
                my $sig = eval { $node->affix . "" } // '';
                while ( $sig =~ /@([a-zA-Z_]\w*)/g ) { $referenced_names{$1} = 1; }
            }

            # Atomic Engine Batch
            my @fwd = sort keys %{ { map { $_ => 1 } ( keys %unique_types, keys %referenced_names ) } };
            my @batch_lines;
            for my $name ( sort keys %unique_types ) {
                my $sig = $unique_types{$name}->affix_type;
                push @batch_lines, "    typedef $name => $sig;" if $sig && $sig ne "$name()";
            }
            my $batch_str = @batch_lines ? join( "\n", @batch_lines ) . "\n" : "";

            # Perl Module Construction
            # Safely quote $lib: escape \ and ] within q[...] delimiters
            my $_lib = '';
            if ( defined $lib ) {
                my $safe_lib = $lib;
                $safe_lib =~ s/\\/\\\\/g;
                $safe_lib =~ s/\]/\\]/g;
                $_lib = "my \$lib = q[$safe_lib];";
            }
            my $out = <<~"PERL";
            package $pkg {
                use v5.40;
                use Affix qw[:all];



( run in 0.434 second using v1.01-cache-2.11-cpan-aadc1410aed )