Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Refactor method dispatch code generation so that .*@foo and other sim…
…ilar things are no longer an error, which passes us some more tests. Also refactor .?, .+ and .* to work in terms of primitives provided by existing dispatchers, so we can rip out a dispatch helper, which duplicated the functionality they provided.
  • Loading branch information
jnthn committed Aug 14, 2009
1 parent 58bef37 commit 4a22048
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 112 deletions.
142 changes: 73 additions & 69 deletions src/classes/Object.pir
Expand Up @@ -685,25 +685,37 @@ method, and returns undef if there are none.
=cut

.sub '!.?' :method
.param pmc methods
.param string method_name
.param pmc pos_args :slurpy
.param pmc named_args :slurpy :named

# Get all possible methods.
.local pmc methods
methods = self.'!MANY_DISPATCH_HELPER'(method_name, pos_args, named_args)

# Do we have any?
# If we were already given a list, just check it's non-empty and use that.
if null methods goto no_list
$I0 = elements methods
if $I0 goto invoke
.tailcall '!FAIL'('Undefined value returned by invocation of undefined method')

# If we do have a method, call it.
invoke:
unless $I0 goto error
$P0 = methods[0]
.tailcall self.$P0(pos_args :flat, named_args :named :flat)

# If there's no list, use .can to try and get us one.
no_list:
$P0 = self.'HOW'()
$P0 = $P0.'can'(self, method_name)
unless $P0 goto error
push_eh check_error
.tailcall $P0(self, pos_args :flat, named_args :named :flat)
check_error:
.local pmc exception
.get_results (exception)
pop_eh
if exception == "No candidates found to invoke" goto error
rethrow exception

error:
.tailcall '!FAIL'('Undefined value returned by invocation of undefined method')
.end


=item !.*

Helper method for implementing the .* operator. Calls one or more matching
Expand All @@ -712,26 +724,53 @@ methods.
=cut

.sub '!.*' :method
.param pmc methods
.param string method_name
.param pmc pos_args :slurpy
.param pmc named_args :slurpy :named

# Get all possible methods.
.local pmc methods
methods = self.'!MANY_DISPATCH_HELPER'(method_name, pos_args, named_args)

# Build result capture list.
.local pmc pos_res, named_res, cap, result_list, it, cur_meth
# Set up result list.
.local pmc result_list
$P0 = get_hll_global 'list'
result_list = $P0()

# Get all possible methods, unless we already were given a list.
unless null methods goto have_methods
$P0 = self.'HOW'()
methods = $P0.'can'(self, method_name)
unless methods goto it_loop_end
have_methods:

# Call each method, expanding out any multis along the way.
.local pmc pos_res, named_res, cap, it, multi_it, cur_meth
it = iter methods
it_loop:
unless it goto it_loop_end
cur_meth = shift it
$I0 = isa cur_meth, 'Perl6MultiSub'
if $I0 goto is_multi
push_eh check_error
(pos_res :slurpy, named_res :named :slurpy) = cur_meth(self, pos_args :flat, named_args :named :flat)
pop_eh
cap = 'prefix:\\'(pos_res :flat, named_res :flat :named)
push result_list, cap
goto it_loop
is_multi:
$P0 = cur_meth.'find_possible_candidates'(self, pos_args :flat, named_args :named :flat)
multi_it = iter $P0
multi_it_loop:
unless multi_it goto it_loop
cur_meth = shift multi_it
(pos_res :slurpy, named_res :named :slurpy) = cur_meth(self, pos_args :flat, named_args :named :flat)
cap = 'prefix:\\'(pos_res :flat, named_res :flat :named)
push result_list, cap
goto multi_it_loop
check_error:
.local pmc exception
.get_results (exception)
pop_eh
if exception == "No candidates found to invoke" goto it_loop
rethrow exception
it_loop_end:

.return (result_list)
Expand All @@ -746,13 +785,14 @@ methods, dies if there are none.
=cut

.sub '!.+' :method
.param pmc methods
.param string method_name
.param pmc pos_args :slurpy
.param pmc named_args :slurpy :named

# Use !.* to produce a (possibly empty) list of result captures.
.local pmc result_list
result_list = self.'!.*'(method_name, pos_args :flat, named_args :flat :named)
result_list = self.'!.*'(methods, method_name, pos_args :flat, named_args :flat :named)

# If we got no elements at this point, we must die.
$I0 = elements result_list
Expand All @@ -769,82 +809,36 @@ methods, dies if there are none.
.end


=item !MANY_DISPATCH_HELPER

This is a helper for implementing .+, .? and .*. In the future, it may well be
the basis of WALK also. It returns all methods we could possibly call.

=cut

.sub '!MANY_DISPATCH_HELPER' :method
.param string method_name
.param pmc pos_args
.param pmc named_args

# We need to find all methods we could call with the right name.
.local pmc p6meta, result_list, class, mro, it
$P0 = get_hll_global 'list'
result_list = $P0()
class = typeof self
mro = inspect class, 'all_parents'
it = iter mro
mro_loop:
unless it goto mro_loop_end
.local pmc cur_class, meths, cur_meth
cur_class = shift it
meths = inspect cur_class, 'methods'
cur_meth = meths[method_name]
if null cur_meth goto mro_loop

# If we're here, found a method. But is it a multi?
$I0 = isa cur_meth, "Perl6MultiSub"
if $I0 goto multi_dispatch

# Single dispatch - add to the result list.
push result_list, cur_meth
goto mro_loop

# Multiple dispatch; get all applicable candidates.
multi_dispatch:
.local pmc possibles, possibles_it
possibles = cur_meth.'find_possible_candidates'(self, pos_args :flat, named_args :flat :named)
possibles_it = iter possibles
possibles_it_loop:
unless possibles_it goto possibles_it_loop_end
cur_meth = shift possibles_it
push result_list, cur_meth
goto possibles_it_loop
possibles_it_loop_end:
goto mro_loop
mro_loop_end:

.return (result_list)
.end

=item !.^

Helper for doing calls on the metaclass.

=cut

.sub '!.^' :method
.param pmc method
.param string method_name
.param pmc pos_args :slurpy
.param pmc named_args :slurpy :named

# Get the HOW or the object and do the call on that.
.local pmc how
how = self.'HOW'()
if null method goto by_name
.tailcall '!dispatch_method_indirect'(how, method, self, pos_args :flat, named_args :flat :named)
by_name:
.tailcall how.method_name(self, pos_args :flat, named_args :flat :named)
.end


=item !.=

Helper for doing .= calls.

=cut

.sub '!.=' :method
.param pmc method
.param string method_name
.param pmc pos_args :slurpy
.param pmc named_args :slurpy :named
Expand All @@ -854,13 +848,23 @@ Helper for doing .= calls.
# some other things happy.)
$P0 = find_lex_skip_current '$/'
.lex '$/', $P0
if null method goto by_name
$I0 = elements method
if $I0 != 1 goto too_many_methods
method = method[0]
($P0) = self.method(pos_args :flat, named_args :flat :named)
goto called
by_name:
($P0) = self.method_name(pos_args :flat, named_args :flat :named)
called:
$P1 = getinterp
$P1 = $P1['lexpad'; 1]
if null $P1 goto done
$P1['$/'] = $P0
done:
.tailcall 'infix:='(self, $P0)
too_many_methods:
'die'('.= indirect form can only be used to supply a single method')
.end

=back
Expand Down
97 changes: 54 additions & 43 deletions src/parser/actions.pm
Expand Up @@ -1318,63 +1318,74 @@ method post($/, $key) {
method dotty($/, $key) {
my $past;

if $key eq '.' {
# Just a normal method call.
$past := $<dottyop>.ast;
}
elsif $key eq '!' {
# Private method call. Need to put ! on the start of the name
# (unless it was call to a code object, in which case we don't do
# anything more).
$past := $<methodop>.ast;
my $methodop := $<methodop>;
if $methodop<name> {
$past.name('!' ~ $past.name());
}
elsif $methodop<quote> {
$past.name(
PAST::Op.new(
:pasttype('call'),
:name('infix:~'),
'!',
$past.name()
)
);
}
}
elsif $key eq '.*' {
if $key eq '.*' {
$past := $<dottyop>.ast;
if $/[0] eq '.?' || $/[0] eq '.+' || $/[0] eq '.*' || $/[0] eq '.^' || $/[0] eq '.=' {
my $name := $past.name();
unless $name {
$/.panic("Cannot use " ~ $/[0] ~ " when method is a code ref");
}
unless $name.isa(PAST::Node) {
if $name && !$name.isa(PAST::Node) {
$name := PAST::Val.new( :value($name) );
}
$past.unshift($name);
if $name {
$past.unshift($name);
$past.unshift(PAST::Op.new(:inline(' null %r')));
}
else {
my $cands := $past.shift();
$past.unshift('');
$past.unshift(PAST::Op.new(
:pasttype('callmethod'),
:name('list'),
$cands
));
}
$past.name('!' ~ $/[0]);
}
else {
$/.panic($/[0] ~ ' method calls not yet implemented');
}
}
elsif $key eq 'VAR' {
$past := PAST::Op.new(
:pasttype('call'),
:name('!VAR'),
:node($/)
);
}
else {
if $key eq '.' {
# Just a normal method call.
$past := $<dottyop>.ast;
}
elsif $key eq '!' {
# Private method call. Need to put ! on the start of the name
# (unless it was call to a code object, in which case we don't do
# anything more).
$past := $<methodop>.ast;
my $methodop := $<methodop>;
if $methodop<name> {
$past.name('!' ~ $past.name());
}
elsif $methodop<quote> {
$past.name(
PAST::Op.new(
:pasttype('call'),
:name('infix:~'),
'!',
$past.name()
)
);
}
}
elsif $key eq 'VAR' {
$past := PAST::Op.new(
:pasttype('call'),
:name('!VAR'),
:node($/)
);
}

# We actually need to send dispatches for named method calls (other than .*)
# through the.dispatcher.
if $<dottyop><methodop><variable> {
$past.name('!dispatch_method_indirect');
$past.pasttype('call');
# We actually need to send dispatches for named method calls (other than .*)
# through the.dispatcher.
if $<dottyop><methodop><variable> {
$past.name('!dispatch_method_indirect');
$past.pasttype('call');
}
}
$past<invocant_holder> := $past;

$past<invocant_holder> := $past;
make $past;
}

Expand Down

0 comments on commit 4a22048

Please sign in to comment.