Affix

 view release on metacpan or  search on metacpan

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

                Affix::Wrap::Enum->new(
                name         => $n->{name} // '(anonymous)',
                file         => $f,
                line         => $l,
                end_line     => $el,
                constants    => \@c,
                doc          => $self->_doc_w_trail( $f, $s, $e ),
                start_offset => $s,
                end_offset   => $e
                );
        }

        method _var( $n, $acc, $f ) {
            my ( $s, $e, $l, $el ) = $self->_meta($n);
            push @$acc,
                Affix::Wrap::Variable->new(
                name         => $n->{name},
                file         => $f,
                line         => $l,
                end_line     => $el,
                type         => Affix::Wrap::Type->parse( $n->{type}{qualType} ),
                doc          => $self->_doc_w_trail( $f, $s, $e ),
                start_offset => $s,
                end_offset   => $e
                );
        }

        method _func( $n, $acc, $f ) {
            return if ( $n->{storageClass} // '' ) eq 'static';
            my ( $s, $e, $l, $el ) = $self->_meta($n);
            my $ret_str = $n->{type}{qualType};
            $ret_str =~ s/\(.*\)//;
            my $ret_obj = Affix::Wrap::Type->parse($ret_str);
            my @args;
            for ( @{ $n->{inner} } ) {
                if ( ( $_->{kind} // '' ) eq 'ParmVarDecl' ) {
                    my $pt = Affix::Wrap::Type->parse( $_->{type}{qualType} );
                    my $pn = $_->{name} // '';
                    push @args, Affix::Wrap::Argument->new( type => $pt, name => $pn );
                }
            }
            push @args, Affix::Wrap::Argument->new( type => Affix::Wrap::Type->new( name => '...' ) ) if $n->{variadic};
            push @$acc,
                Affix::Wrap::Function->new(
                name         => $n->{name},
                mangled_name => $n->{mangledName},
                file         => $f,
                line         => $l,
                end_line     => $el,
                ret          => $ret_obj,
                args         => \@args,
                doc          => $self->_doc_w_trail( $f, $s, $e ),
                start_offset => $s,
                end_offset   => $e
                );
        }

        method _get_content($f) {
            my $abs = $self->_normalize($f);
            return $file_cache->{$abs} if exists $file_cache->{$abs};
            if ( -e $abs ) { return $file_cache->{$abs} = Path::Tiny::path($abs)->slurp_utf8; }
            return '';
        }

        method _extract_doc( $f, $off ) {
            return undef unless defined $off;
            my $content = $self->_get_content($f);
            return undef unless length($content);
            my $pre   = substr( $content, 0, $off );
            my @lines = split /\n/, $pre;
            my @d;
            my $cap = 0;
            while ( my $line = pop @lines ) {
                next if !$cap && $line =~ /^\s*$/;
                if    ( $line =~ /\*\/\s*$/ ) { $cap = 1; }
                elsif ( $line =~ /^\s*\/\// ) { $cap = 1; }
                if    ($cap) {
                    unshift @d, $line;
                    last if $line =~ /^\s*\/\*/;
                    if ( $line =~ /^\s*\/\// && ( !@lines || $lines[-1] !~ /^\s*\/\// ) ) { last; }
                }
                else { last; }
            }
            return undef unless @d;
            my $t = join( "\n", @d );
            $t =~ s/^\s*\/\*\*?//mg;
            $t =~ s/\s*\*\/$//mg;
            $t =~ s/^\s*\*\s?//mg;
            $t =~ s/^\s*\/\/\s?//mg;
            $t =~ s/^\s+|\s+$//g;
            return $t;
        }

        method _extract_trailing( $f, $off ) {
            return '' unless defined $off;
            my $content = $self->_get_content($f);
            return '' unless length($content);
            my $post   = substr( $content, $off );
            my ($line) = split /\R/, $post, 2;
            return '' unless defined $line;
            if ( $line =~ /\/\/(.*)$/ ) {
                my $c = $1;
                $c =~ s/^\s+|\s+$//g;
                return $c;
            }
            return '';
        }

        method _extract_raw( $f, $s, $e ) {
            return '' unless defined $s && defined $e;
            my $content = $self->_get_content($f);
            return '' unless length($content) >= $e;
            return substr( $content, $s, $e - $s );
        }

        method _extract_macro_val( $n, $f ) {
            my $off = $n->{range}{begin}{offset};
            return '' unless defined $off;
            my $content = $self->_get_content($f);
            return '' unless length($content);
            my $r = substr( $content, $off );

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

                        line         => $node->line,
                        end_line     => $node->end_line,
                        doc          => $node->doc,
                        start_offset => $node->start_offset,
                        end_offset   => $node->end_offset
                    );
                    $node->mark_merged();
                    $objs->[$i] = $new;
                }
            }
        }

        method _get_triple {
            my $arch = $Config{archname} =~ /aarch64|arm64/i ? 'aarch64' : $Config{archname} =~ /x64|x86_64/i ? 'x86_64' : 'i686';
            if ( $^O eq 'MSWin32' ) {
                if   ( $Config{cc} =~ /gcc/i ) { return "$arch-pc-windows-gnu"; }
                else                           { return "$arch-pc-windows-msvc"; }
            }
            elsif ( $^O eq 'linux' )  { return "$arch-unknown-linux-gnu"; }
            elsif ( $^O eq 'darwin' ) { return "$arch-apple-darwin"; }
            my ($out) = Capture::Tiny::capture { system $clang, '-print-target-triple' };
            $out =~ s/\s+//g if $out;
            return $out // "$arch-unknown-unknown";
        }
    }
    class    #
        Affix::Wrap::Driver::Regex {
        field $project_files : param : reader;
        field $file_cache = {};

        method _normalize ($path) {
            return '' unless defined $path && length $path;
            my $abs = Path::Tiny::path($path)->absolute->stringify;
            $abs =~ s{\\}{/}g;
            return $abs;
        }

        method _is_valid_file ($f) {
            return 0 unless defined $f && length $f;
            return $f !~ m{^/usr/(include|lib|share|local/include)} &&
                $f !~ m{^/System/Library} &&
                $f !~ m{(Program Files|Strawberry|MinGW|Windows|cygwin|msys)}i;
        }

        method parse( $entry_point, $ids //= [] ) {
            my @objs;
            for my $f (@$project_files) {
                my $abs = $self->_normalize($f);
                next unless length $abs;
                next unless $self->_is_valid_file($abs);
                if ( $f =~ /\.h(pp|xx)?$/i ) { $self->_scan( $f, \@objs ); $self->_scan_funcs( $f, \@objs ); }
                else                         { $self->_scan_funcs( $f, \@objs ); }
            }
            @objs = sort { ( $a->file cmp $b->file ) || ( $a->start_offset <=> $b->start_offset ) } @objs;
            @objs;
        }

        method _read($f) {
            my $abs = $self->_normalize($f);
            return $file_cache->{$abs} if exists $file_cache->{$abs};
            return $file_cache->{$abs} = Path::Tiny::path($f)->slurp_utf8;
        }

        method _scan( $f, $acc ) {
            my $c = $self->_read($f);

            # Macros
            while ( $c =~ /^\s*#\s*define\s+(\w+)(?:[ \t]+(.*?))?$/gm ) {
                my $name = $1;
                my $val  = $2 // '';
                my $s    = $-[0];
                my $e    = $+[0];
                $val =~ s/\/\/.*$//;
                $val =~ s/\/\*.*?\*\///g;
                $val =~ s/^\s+|\s+$//g;
                push @$acc,
                    Affix::Wrap::Macro->new(
                    name         => $name,
                    value        => $val,
                    file         => $f,
                    line         => $self->_ln( $c, $s ),
                    end_line     => $self->_ln( $c, $e ),
                    doc          => $self->_doc( $c, $s ),
                    start_offset => $s,
                    end_offset   => $e
                    );
            }

            # Structs
            while ( $c =~ /typedef\s+struct\s*(?:\w+\s*)?(\{(?:[^{}]++|(?1))*\})\s*(\w+)\s*;/gs ) {
                my $s      = $-[0];
                my $e      = $+[0];
                my $mem    = $self->_mem( substr( $1, 1, -1 ) );
                my $struct = Affix::Wrap::Struct->new(
                    name         => '',
                    tag          => 'struct',
                    members      => $mem,
                    file         => $f,
                    line         => $self->_ln( $c, $s ),
                    end_line     => $self->_ln( $c, $e ),
                    doc          => undef,
                    start_offset => $s,
                    end_offset   => $e
                );
                push @$acc,
                    Affix::Wrap::Typedef->new(
                    name         => $2,
                    underlying   => $struct,
                    file         => $f,
                    line         => $self->_ln( $c, $s ),
                    end_line     => $self->_ln( $c, $e ),
                    doc          => $self->_doc( $c, $s ),
                    start_offset => $s,
                    end_offset   => $e
                    );
            }

            # Enums (typedef)
            while ( $c =~ /typedef\s+enum\s*(?:\w+\s*)?(\{(?:[^{}]++|(?1))*\})\s*(\w+)\s*;/gs ) {
                my $s    = $-[0];
                my $e    = $+[0];

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

                # Check cache (recursion guard)
                return $cache{$token} if exists $cache{$token};
                local $cache{$token} = undef;

                # Look up definition
                my $expr = $macros{$token};
                return undef unless defined $expr;                     # Not found (maybe a string or unknown)

                # Parse expression
                # Strip outer parentheses recursively: ((A|B)) -> A|B
                1 while $expr =~ s/^\((.*)\)$/$1/;

                # Handle bitwise OR chains (e.g. "FLAG_A | FLAG_B")
                if ( $expr =~ /\|/ ) {
                    my $accum = 0;
                    for my $part ( split /\|/, $expr ) {
                        my $val = $resolve->($part);
                        return undef unless defined $val;    # Abort if any part is non-numeric
                        $accum |= $val;
                    }
                    return $cache{$token} = $accum;
                }

                # 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 );
                    if ( defined $val ) {
                        $node->set_value($val);
                    }
                }
            }
        }

        method generate ( $lib, $pkg, $file ) {
            my @nodes = $self->parse;
            $self->_resolve_macros( \@nodes );
            my $out =<<~"";
            package $pkg {
                use v5.36;
                use Affix;
                #
                my \$lib = '$lib';

            for my $name ( keys %$types ) {
                my $type     = $types->{$name};
                my $type_str = builtin::blessed($type) ? $type : "'$type'";    # Quote user types
                $out .= "typedef '$name' => $type_str;\n";
            }
            for my $node (@nodes) {
                if ( ( $node isa Affix::Wrap::Typedef || $node isa Affix::Wrap::Struct || $node isa Affix::Wrap::Enum ) &&
                    exists $types->{ $node->name } ) {
                    next;
                }
                my $code = $node->affix_type;
                if ($code) { $out .= "$code;\n"; }
            }
            $out .= "\n};\n1;\n";
            Path::Tiny::path($file)->spew_utf8($out);
        }

        method wrap ( $lib, $target //= [caller]->[0] ) {

            # Pre-register User Types
            # This ensures they are available in the Affix registry before signatures are parsed,
            # and allows using them in recursive definitions or opaque handles.
            for my $name ( keys %$types ) {
                my $type     = $types->{$name};
                my $type_str = builtin::blessed($type) ? $type : "$type";
                Affix::typedef( $name, $type_str );
            }
            my @nodes = $self->parse;

            #  Macro resolution pass
            $self->_resolve_macros( \@nodes );

            # Generation pass
            my @installed;
            for my $node (@nodes) {

                # Skip definitions if the user provided a manual type override
                if ( ( $node isa Affix::Wrap::Typedef || $node isa Affix::Wrap::Struct || $node isa Affix::Wrap::Enum ) &&
                    exists $types->{ $node->name } ) {
                    next;
                }
                if ( $node->can('affix') ) {
                    $node->affix( $lib, $target );
                    push @installed, $node;
                }
            }
            @installed;
        }
    }
}
1;
__END__
Copyright (C) Sanko Robinson.

This library is free software; you can redistribute it and/or modify it under
the terms found in the Artistic License 2. Other copyrights, terms, and
conditions may apply to data transmitted through this module.



( run in 2.428 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )