-
-
Notifications
You must be signed in to change notification settings - Fork 372
/
Dispatchers.nqp
178 lines (151 loc) Β· 6.01 KB
/
Dispatchers.nqp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
class Perl6::Metamodel::BaseDispatcher {
has @!candidates;
has $!idx;
has $!next_dispatcher; # The dispatcher we must pass control to when own queue exhausts
method candidates() { @!candidates }
method exhausted() { $!idx >= +@!candidates && (!nqp::isconcrete($!next_dispatcher) || $!next_dispatcher.exhausted()) }
method last() { @!candidates := [] }
method set_next_dispatcher($next_dispatcher) { $!next_dispatcher := $next_dispatcher }
# Wrapper-like dispatchers don't set dispatcher for the last candidate.
method is_wrapper_like() { 0 }
method get_call() { # Returns [$call, $is_dispatcher]
my $call := @!candidates[$!idx++];
my $disp;
try {
# XXX Are there any better way to determine a invocation handler with own dispatcher in $!dispatcher?
$disp := nqp::getattr($call, nqp::what($call), '$!dispatcher'); # If $call is a handler. But there must be better way to deal with this.
$disp := nqp::null() unless nqp::istype($disp, Perl6::Metamodel::BaseDispatcher); # Protect from multi-Routine dispatcher attribute
}
if nqp::isconcrete($disp) {
return [$disp, 1];
}
else {
my $last_candidate := $!idx >= +@!candidates;
if $last_candidate && nqp::isconcrete($!next_dispatcher) {
nqp::setdispatcherfor($!next_dispatcher, $call);
$!next_dispatcher := nqp::null();
}
else {
nqp::setdispatcherfor(self, $call) unless $last_candidate && self.is_wrapper_like;
}
}
[$call, 0]
}
method call_with_args(*@pos, *%named) {
my @call := self.get_call;
if @call[1] {
return @call[0].enter_with_args(@pos, %named, :next_dispatcher(self));
}
if self.has_invocant {
my $inv := self.invocant;
@call[0]($inv, |@pos, |%named);
}
else {
@call[0](|@pos, |%named);
}
}
method call_with_capture($capture) {
my @call := self.get_call;
if @call[1] { # Got a dispatcher
return @call[0].enter_with_capture($capture, :next_dispatcher(self));
}
nqp::invokewithcapture(@call[0], $capture);
}
method shift_callee() {
my $callee := @!candidates[$!idx];
$!idx := $!idx + 1;
nqp::decont($callee)
}
}
class Perl6::Metamodel::MethodDispatcher is Perl6::Metamodel::BaseDispatcher {
has $!obj;
method new(:@candidates, :$idx, :$obj) {
my $disp := nqp::create(self);
nqp::bindattr($disp, Perl6::Metamodel::BaseDispatcher, '@!candidates', @candidates);
nqp::bindattr($disp, Perl6::Metamodel::BaseDispatcher, '$!idx', $idx);
nqp::bindattr($disp, Perl6::Metamodel::MethodDispatcher, '$!obj', $obj);
$disp
}
method vivify_for($sub, $lexpad, $args) {
my $obj := $lexpad<self>;
my $name := $sub.name;
my @mro := nqp::can($obj.HOW, 'mro_unhidden')
?? $obj.HOW.mro_unhidden($obj)
!! $obj.HOW.mro($obj);
my @methods;
for @mro {
my %mt := nqp::hllize($_.HOW.method_table($_));
if nqp::existskey(%mt, $name) {
@methods.push(%mt{$name});
}
}
self.new(:candidates(@methods), :obj($obj), :idx(1))
}
method has_invocant() { 1 }
method invocant() { $!obj }
}
class Perl6::Metamodel::MultiDispatcher is Perl6::Metamodel::BaseDispatcher {
has $!has_invocant;
has $!invocant;
method new(:@candidates, :$idx, :$invocant, :$has_invocant) {
my $disp := nqp::create(self);
nqp::bindattr($disp, Perl6::Metamodel::BaseDispatcher, '@!candidates', @candidates);
nqp::bindattr($disp, Perl6::Metamodel::BaseDispatcher, '$!idx', $idx);
nqp::bindattr($disp, Perl6::Metamodel::MultiDispatcher, '$!invocant', $invocant);
nqp::bindattr($disp, Perl6::Metamodel::MultiDispatcher, '$!has_invocant', $has_invocant);
$disp
}
method vivify_for($sub, $lexpad, $args) {
my $disp := $sub.dispatcher();
my $has_invocant := nqp::existskey($lexpad, 'self');
my $invocant := $has_invocant && $lexpad<self>;
my @cands := $disp.find_best_dispatchee($args, 1);
self.new(:candidates(@cands), :idx(1), :invocant($invocant),
:has_invocant($has_invocant))
}
method has_invocant() { $!has_invocant }
method invocant() { $!invocant }
}
class Perl6::Metamodel::WrapDispatcher is Perl6::Metamodel::BaseDispatcher {
method new(:@candidates, :$idx, :$invocant, :$has_invocant) {
my $disp := nqp::create(self);
nqp::bindattr($disp, Perl6::Metamodel::BaseDispatcher, '@!candidates', @candidates);
nqp::bindattr($disp, Perl6::Metamodel::BaseDispatcher, '$!idx', 1);
$disp
}
method has_invocant() { 0 }
method is_wrapper_like() { 1 }
method add($wrapper) {
self.candidates.unshift($wrapper)
}
method remove($wrapper) {
my @cands := self.candidates;
my $i := 0;
while $i < +@cands {
if nqp::decont(@cands[$i]) =:= nqp::decont($wrapper) {
nqp::splice(@cands, [], $i, 1);
return 1;
}
$i := $i + 1;
}
return 0;
}
method get_first($next_dispatcher) {
my $fresh := nqp::clone(self);
$fresh.set_next_dispatcher($next_dispatcher) if $next_dispatcher;
my $first := self.candidates[0];
nqp::setdispatcherfor($fresh, $first);
$first
}
# This method is a bridge between Perl6 and NQP.
method enter(*@pos, *%named) {
self.enter_with_args(@pos, %named);
}
method enter_with_args(@pos, %named, :$next_dispatcher?) {
self.get_first($next_dispatcher)(|@pos, |%named)
}
method enter_with_capture($capture, :$next_dispatcher?) {
my $first := self.get_first($next_dispatcher);
nqp::invokewithcapture($first, $capture);
}
}