Skip to content

Commit

Permalink
Refactor method dispatch code generation so that .*@foo and other sim…
Browse files Browse the repository at this point in the history
…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.