=begin pod

=head1 NAME

CompUnit::Util - Utilities for introspecting compunits and re-exporting

=head1 SYNOPSIS

=begin code :lang<raku>

use CompUnit::Util;

=end code

=head1 DESCRIPTION

CompUnit::Util contains set of utilities to introspect C<CompUnit>
stuff and a bunch of compile time symbol manipulation tools. Its main
purpose is to encapsulate the compiler hacks needed to implement
features like 're-exporting' which don't exist in rakudo yet.

B<WARNING> this module relies on unspec'd rakudo internals and can
(and has and will) break without warning.

=head1 CompUnit UTILITIES

C<CompUnit> introspection utilities.

Apart from C<load> none of the routines here will load a compunit.
All parameters named C<$handle> are converted to a C<CompUnit::Handle>
object automatically. If the C<$handle> you pass is not a defined
C<CompUnit> or C<CompUnit::Handle>, C<&find-loaded> will be used toi
search for a loaded compunit matching it.

=head2 load

=begin code :lang<raku>
use CompUnit::Util :load;
my CompUnit $cu = load('Test');
# or even
my $cu = load('MyModule', version => v3);
=end code

Loads a compunit by name. All named arguments to
C<CompUnit::DependencySpecification> are accepted (other than
C<short-name> which is the positional argument). At the moment C<load>
is just short for:

=begin code :lang<raku>
$*REPO.need(CompUnit::Dependencyspecification.new(:short_name<MyModule>));
=end code

=head2 find-loaded

=begin code :lang<raku>
use CompUnit::Util :find-loaded;
need SomeModule;
my CompUnit $some-module = find-loaded('SomeModule');
=end code

Searches all the C<CompUnit::Repository>s until it finds a loaded
compunit matching C<$match>. Returns a failure otherwise.

=head2 all-loaded

=begin code :lang<raku>
use CompUnit::Util :all-loaded;
.note for all-loaded;
=end code

Returns all loaded C<CompUnit>s.

=head2 at-unit

=begin code :lang<raku>
use CompUnit::Util :at-unit;
say at-unit('CompUnit::Util','$=pod');
=end code

Gets a symbol from the C<UNIT> scope of the compunit. If you want to do
this at compile time while a compunit is loading see C<get-unit>.

=head2 unit-to-hash

=begin code :lang<raku>
use CompUnit::Util :unit-to-hash;
my %unit := unit-to-hash('SomeModule');
=end code

Returns a C<Hash> representing the C<UNIT::> of the module.

=head2 capture-import

=begin code :lang<raku>
use CompUnit::Util :capture-import;
need SomeModule;
my %symbols = capture-import('SomeModule',:tag);
=end code

Attempts to simulate a C<use> statement. Returns a hash of all the
symbols the compunit would export if it were C<use>d.

=head1 WHO Utilities

=head2 set-in-WHO

=begin code :lang<raku>
use CompUnit::Util :who;
my package Example {};
BEGIN set-in-WHO(Example.WHO,'Foo::Bar::$Baz','win');

say Example::Foo::Bar::<$Baz>; #-> win
=end code

Convenience routine for setting a symbol's value inside a package that
might not exist yet. Only useful outside the compunit being compiled.

=head2 descend-WHO

=begin code :lang<raku>
use CompUnit::Util :who;
my package Example {};
BEGIN set-in-WHO(Example.WHO,'Foo::Bar::Baz','win');
BEGIN note descend-WHO(Example.WHO,'Foo::Bar::Baz'); #-> win
=end code

Convenience routine for getting a symbol's value with a path from a
C<Stash> like C<.WHO>. Only useful outside the currently compiling
compunit (where you can just use the normal syntax).

## Re-Exporting

The following routines provide re-exporting which is not yet
implemented in rakudo.

=head2 re-export

B<This routine can only be called at C<BEGIN> time>

=begin code :lang<raku>
use CompUnit::Util :re-export;
need SomeModule;
BEGIN re-export('SomeModule');
# This compunit will now export everything that SomeModule does
=end code

Merges the C<EXPORT> package from C<$handle> into the
present C<UNIT::EXPORT>.

=head2 re-exporthow

B<This routine can only be called at C<BEGIN> time>

=begin code :lang<raku>
use CompUnit::Util :re-export;
need SomeModule;
BEGIN re-exporthow('SomeModule');
# This compunit now exports SomeModule's custom declarators
=end code

Merges the C<EXPORTHOW> from C<$handle> into the present
C<UNIT::EXPORTHOW>. C<UNIT::EXPORTHOW> will be created if it doesn't
exist but it won't clobber it if it does.

=head2 steal-export-sub

B<This routine can only be called at C<BEGIN> time>

=begin code :lang<raku>
use CompUnit::Util :re-export;
need SomeModule;
BEGIN steal-export-sub('SomeModule');
# This compunit now has the same &EXPORT as SomeModule
=end code

Sets C<UNIT::<&EXPORT>> to C<$handle>'s C<&EXPORT>.

=head2 steal-globalish

B<This routine can only be called at C<BEGIN> time>

=begin code :lang<raku>
use CompUnit::Util :re-export,:load;
BEGIN steal-globalish(load('SomeModule'));
# This compunit now has everything in SomeModule in it's globalish
=end code

Merges the C<GLOBALish> from C<$handle> into the present
C<UNIT::GLOBALish>.

This is the least interesting of all the re-exports, and if you've
already done C<need SomeModule;> you won't need it. But it's here for
completeness. The above example should be the same as this anyway:

=begin code :lang<raku>
BEGIN require ::('SomeModule');
=end code

=head2 re-export-everything

B<This routine can only be called at C<BEGIN> time>

=begin code :lang<raku>
use CompUnit::Util :re-export;
BEGIN re-export-everything('SomeModule');
# use [this-module]; should now do the same thing as use SomeModule;
=end code

A convenience method for calling all the other functions under
re-export functions with the same argument.

=head1 SYMBOL SETTING

The following routines manipulate the symbols of the compunit being
compiled. They are probably of most use inside an C<&EXPORT> sub or in
a trait.

They each take a map of symbol names to values and install them in
different places.

B<NOTE:> These subs will overwrite existing symbols without warning.

Inserts name/value pairs into the present C<UNIT::EXPORT> under C<$tag>.

=head2 set-unit

B<This routine can only be called at C<BEGIN> time>

=begin code :lang<raku>
# like is export, but prefixes the the exported name with 'fun-'
sub trait_mod:<is>(Routine:D $r, :$export-fun!) {
    my $exported-name = '&fun-' ~ $r.name;
    set-unit("EXPORT::DEFAULT::{$exported-name}",$r);
    {};}
=end code

Inserts the C<$value> at C<$path> in C<UNIT> of the currently compiling
compunit.

=head2 set-lexpad

B<This routine can only be called at C<BEGIN> time>

The same as C<set-unit> but inserts the $value into the lexical
scope being compiled.

=head1 SYMBOL GETTING

=head2 get-unit

=begin code :lang<raku>
use CompUnit::Util :get-symbols;
sub foo is export { };
BEGIN note get-unit('EXPORT::DEFAULT::&foo') === &foo; #-> True
=end code

=head2 get-lexpad

The same as C<get-unit> but looks for the symbol in the lexpad
being compiled.

=head2 get-lexical

Like C<get-lexpad> but does a full lexical lookup. At the moment it can
only take a single C<$name> with no C<::>.

=head1 DISPATCHER MANIPULATION

These routines help you construct C<multi> dispatchers B<candidate by
candidate> in a procedural manner. Useful when you want to construct a
trait that adds a multi candidate each time it's called. Parameters
marked C<$multi> can be any C<Routine:D>. If you pass a dispatcher it
will just use it as the dispatcher or die if you are trying to push
onto an existing dispatcher.

If you try and push a non-multi/dispatcher onto an empty slot it will
not vivify one for you.

=head2 push-multi

Adds the C<$candidate> onto C<$target>.

=begin code :lang<raku>
use CompUnit::Util :push-multi;
multi foo('one') { 'one' }
multi foo('two') { 'two' }

&foo.&push-multi(sub ('three') { "win" });
say foo('three') #-> "win"
=end code

B<NOTE:> This is NYI in rakudo. The design docs says that protos should
have a C<.push> method. See L<S06|https://github.com/Raku/old-design-docs/blob/master/S06-routines.pod>.

=head2 push-unit-multi

B<This routine can only be called at C<BEGIN> time>

=begin code :lang<raku>
## lib/SillyModule.rakumod
use CompUnit::Util :push-multi
# exports the multi under a sub named after its first letter
sub trait_mod:<is>(Routine:D $r,:$one-letter-export!) {
    my $exported-name = '&' ~ $r.name.comb[0];
    push-unit-multi("EXPORT::DEFAULT::{$exported-name}",$r);
}

multi bar(Str) is one-letter-export { say "bar" }
multi baz(Int) is one-letter-export { say "baz" }

...
use SillyModule;

b("string"); #-> bar
b(1) #-> baz
=end code

Takes C<$multi> and pushses it onto a dispatcher located at C<$path>. If
one doesn't exist it will be created. You can pass a C<proto> instead
of a multi but only when C<$path> is empty (ie only the first time). It
will become the dispatcher for any further calls.

=head2 push-lexpad-multi

B<This routine can only be called at C<BEGIN> time>

The same as C<push-unit-multi> but pushes onto a symbol in the lexical
scope currently being compiled.

=head2 push-lexical-multi

B<This routine can only be called at C<BEGIN> time>

The smart version of C<push-lexpad-multi>. If it doesn't find a
dispatcher in the current lexpad it will do a lexical lookup for one
of the same C<$name>. If it finds one it clones it, installs it in the
current lexpad and pushes C<$multi> onto it. Like
C<get-lexical>, it can't take a C<$name> with C<::> in it.

=head1 AUTHOR

Lloyd Fournier

Source can be located at: https://github.com/raku-community-modules/CompUnit-Util .
Comments and Pull Requests are welcome.

=head1 COPYRIGHT AND LICENSE

Copyright 2016 - 2023 Lloyd Fournier

Copyright 2026 Raku Community

This library is free software; you can redistribute it and/or modify it under MIT License.

=end pod

# vim: expandtab shiftwidth=4
