~ubuntu-branches/ubuntu/vivid/nqp/vivid-proposed

« back to all changes in this revision

Viewing changes to src/how/NQPClassHOW.nqp

  • Committer: Package Import Robot
  • Author(s): Alessandro Ghedini
  • Date: 2013-11-01 12:09:18 UTC
  • mfrom: (1.1.4)
  • Revision ID: package-import@ubuntu.com-20131101120918-kx51sl0sxl3exsxi
Tags: 2013.10-1
* New upstream release
* Bump versioned (Build-)Depends on parrot
* Update patches
* Install new README.pod
* Fix vcs-field-not-canonical
* Do not install rubyish examples
* Do not Depends on parrot-devel anymore
* Add 07_disable-serialization-tests.patch

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# This is a first cut at a ClassHOW for NQP. It doesn't support all the stuff
 
2
# that Perl 6 needs, but it's sufficient for NQP. Supports methods, attributes,
 
3
# role composition, inheritance (single and multiple) and introspection.
 
4
 
 
5
knowhow NQPClassHOW {
 
6
    ##
 
7
    ## Attributes
 
8
    ##
 
9
 
 
10
    # Name of the class.
 
11
    has $!name;
 
12
 
 
13
    # Attributes, methods, parents and roles directly added.
 
14
    has @!attributes;
 
15
    has %!methods;
 
16
    has @!method_order;
 
17
    has @!multi_methods_to_incorporate;
 
18
    has @!parents;
 
19
    has @!roles;
 
20
    has $!default_parent;
 
21
 
 
22
    # Vtable and mapping of method names to slots.
 
23
    has @!vtable;
 
24
    has %!method-vtable-slots;
 
25
 
 
26
    # Have we been composed?
 
27
    has $!composed;
 
28
 
 
29
    # Cached MRO (list of the type objects).
 
30
    has @!mro;
 
31
 
 
32
    # Full list of roles that we do.
 
33
    has @!done;
 
34
    
 
35
    # Cached values, which are thrown away if the class changes.
 
36
    has %!caches;
 
37
    has $!is_mixin;
 
38
 
 
39
#?if parrot
 
40
    # Parrot-specific vtable mapping hash. Maps vtable name to method.
 
41
    has %!parrot_vtable_mapping;
 
42
    has %!parrot_vtable_handler_mapping;
 
43
#?endif
 
44
 
 
45
    # Call tracing.
 
46
    has $!trace;
 
47
    has $!trace_depth;
 
48
    has @!trace_exclude;
 
49
    
 
50
    # Build plan.
 
51
    has @!BUILDALLPLAN;
 
52
    has @!BUILDPLAN;
 
53
    
 
54
    my $archetypes := Archetypes.new( :nominal(1), :inheritable(1) );
 
55
    method archetypes() {
 
56
        $archetypes
 
57
    }
 
58
 
 
59
    ##
 
60
    ## Declarative.
 
61
    ##
 
62
 
 
63
    # Creates a new instance of this meta-class.
 
64
    method new(:$name = '<anon>') {
 
65
        my $obj := nqp::create(self);
 
66
        $obj.BUILD(:name($name));
 
67
        $obj
 
68
    }
 
69
 
 
70
    method BUILD(:$name = '<anon>') {
 
71
        $!name := $name;
 
72
        @!attributes := nqp::list();
 
73
        %!methods := nqp::hash();
 
74
        @!method_order := nqp::list();
 
75
        @!multi_methods_to_incorporate := nqp::list();
 
76
        @!parents := nqp::list();
 
77
        @!roles := nqp::list();
 
78
        @!vtable := nqp::list();
 
79
        %!method-vtable-slots := nqp::hash();
 
80
        @!mro := nqp::list();
 
81
        @!done := nqp::list();
 
82
#?if parrot
 
83
        %!parrot_vtable_mapping := nqp::hash();
 
84
        %!parrot_vtable_handler_mapping := nqp::hash();
 
85
#?endif
 
86
        @!BUILDALLPLAN := nqp::list();
 
87
        @!BUILDPLAN := nqp::list();
 
88
        $!is_mixin := 0;
 
89
        $!trace := 0;
 
90
        $!trace_depth := 0;
 
91
        $!composed := 0;
 
92
    }
 
93
 
 
94
    # Create a new meta-class instance, and then a new type object
 
95
    # to go with it, and return that.
 
96
    method new_type(:$name = '<anon>', :$repr = 'P6opaque') {
 
97
        my $metaclass := self.new(:name($name));
 
98
        nqp::setwho(nqp::newtype($metaclass, $repr), {});
 
99
    }
 
100
 
 
101
    method add_method($obj, $name, $code_obj) {
 
102
        if nqp::existskey(%!methods, $name) {
 
103
            nqp::die("This class already has a method named " ~ $name);
 
104
        }
 
105
        if nqp::isnull($code_obj) || !nqp::defined($code_obj) {
 
106
            nqp::die("Cannot add a null method '$name' to class '$!name'");
 
107
        }
 
108
        nqp::setmethcacheauth($obj, 0);
 
109
        %!caches{nqp::where(self)} := {} unless nqp::isnull(%!caches);
 
110
        nqp::push(@!method_order, %!methods{$name} := $code_obj);
 
111
    }
 
112
 
 
113
    method add_multi_method($obj, $name, $code_obj) {
 
114
        # We can't incorporate these right away as we don't know all
 
115
        # parents yet, maybe, which influences whether we even can
 
116
        # have multis, need to generate a proto or worreva. So just
 
117
        # queue them up in a todo list and we handle it at class
 
118
        # composition time.
 
119
        my %todo;
 
120
        %todo<name> := $name;
 
121
        %todo<code> := $code_obj;
 
122
        nqp::push(@!multi_methods_to_incorporate, %todo);
 
123
        nqp::setmethcacheauth($obj, 0);
 
124
        $code_obj;
 
125
    }
 
126
 
 
127
    method add_attribute($obj, $meta_attr) {
 
128
        my $name := $meta_attr.name;
 
129
        for @!attributes {
 
130
            if $_.name eq $name {
 
131
                nqp::die("This class already has an attribute named " ~ $name);
 
132
            }
 
133
        }
 
134
        nqp::push(@!attributes, $meta_attr);
 
135
    }
 
136
 
 
137
    method add_parent($obj, $parent) {
 
138
        if $!composed {
 
139
            nqp::die("NQPClassHOW does not support adding parents after being composed.");
 
140
        }
 
141
        if $obj =:= $parent {
 
142
            nqp::die("Class '$!name' cannot inherit from itself.");
 
143
        }
 
144
        for @!parents {
 
145
            if $_ =:= $parent {
 
146
                nqp::die("Already have " ~ $parent ~ " as a parent class.");
 
147
            }
 
148
        }
 
149
        nqp::push(@!parents, $parent);
 
150
    }
 
151
    
 
152
    method set_default_parent($obj, $parent) {
 
153
        $!default_parent := $parent;
 
154
    }
 
155
    
 
156
    # Changes the object's parent. Conditions: it has exactly one parent, and that
 
157
    # parent has no attributes, and nor does the new one.
 
158
    method reparent($obj, $new_parent) {
 
159
        if nqp::elems(@!parents) != 1 {
 
160
            nqp::die("Can only re-parent a class with exactly one parent");
 
161
        }
 
162
        for @!parents[0].HOW.mro(@!parents[0]) {
 
163
            if nqp::elems($_.HOW.attributes($_, :local)) {
 
164
                nqp::die("Can only re-parent a class whose parent has no attributes");
 
165
            }
 
166
        }
 
167
        for $new_parent.HOW.mro($new_parent) {
 
168
            if nqp::elems($_.HOW.attributes($_, :local)) {
 
169
                nqp::die("Can only re-parent to a class with no attributes");
 
170
            }
 
171
        }
 
172
        @!parents[0] := $new_parent;
 
173
        @!mro := compute_c3_mro($obj);
 
174
        self.publish_type_cache($obj);
 
175
        self.publish_method_cache($obj);
 
176
        self.publish_boolification_spec($obj);
 
177
#?if parrot
 
178
        self.publish_parrot_vtable_mapping($obj);
 
179
                self.publish_parrot_vtablee_handler_mapping($obj);
 
180
#?endif
 
181
        1;
 
182
    }
 
183
 
 
184
    method add_role($obj, $role) {
 
185
        for @!roles {
 
186
            if $_ =:= $role {
 
187
                nqp::die("The role " ~ $role ~ " has already been added.");
 
188
            }
 
189
        }
 
190
        nqp::push(@!roles, $role);
 
191
    }
 
192
 
 
193
#?if parrot
 
194
    method add_parrot_vtable_mapping($obj, $name, $meth) {
 
195
        if nqp::defined(%!parrot_vtable_mapping{$name}) {
 
196
            nqp::die("Class '" ~ $!name ~
 
197
                "' already has a Parrot v-table override for '" ~
 
198
                $name ~ "'");
 
199
        }
 
200
        %!parrot_vtable_mapping{$name} := $meth;
 
201
    }
 
202
 
 
203
    method add_parrot_vtable_handler_mapping($obj, $name, $att_name) {
 
204
        if nqp::defined(%!parrot_vtable_handler_mapping{$name}) {
 
205
            nqp::die("Class '" ~ $!name ~
 
206
                "' already has a Parrot v-table handler for '" ~
 
207
                $name ~ "'");
 
208
        }
 
209
        %!parrot_vtable_handler_mapping{$name} := [ $obj, $att_name ];
 
210
    }
 
211
#?endif
 
212
 
 
213
    method compose($obj) {
 
214
        # Incorporate roles. First, specialize them with the type object
 
215
        # for this type (so their $?CLASS is correct). Then delegate to
 
216
        # the composer.
 
217
        if @!roles {
 
218
            my @specialized_roles;
 
219
            for @!roles {
 
220
                my $ins := $_.HOW.specialize($_, $obj);
 
221
                nqp::push(@specialized_roles, $ins);
 
222
                nqp::push(@!done, $_);
 
223
                nqp::push(@!done, $ins);
 
224
            }
 
225
            RoleToClassApplier.apply($obj, @specialized_roles);
 
226
        }
 
227
 
 
228
        # If we have no parents and we're not called NQPMu then add the
 
229
        # default parent.
 
230
        if nqp::elems(@!parents) == 0 && $!name ne 'NQPMu' {
 
231
            self.add_parent($obj, $!default_parent)
 
232
        }
 
233
 
 
234
        # Compute the MRO.
 
235
        @!mro := compute_c3_mro($obj);
 
236
 
 
237
        # Incorporate any new multi candidates (needs MRO built).
 
238
        self.incorporate_multi_candidates($obj);
 
239
 
 
240
        # Compose attributes.
 
241
        for self.attributes($obj, :local<0> ) { $_.compose($obj) }
 
242
 
 
243
        # Publish type and method caches and boolification spec.
 
244
        self.publish_type_cache($obj);
 
245
        self.publish_method_cache($obj);
 
246
        self.publish_boolification_spec($obj);
 
247
 
 
248
#?if parrot
 
249
        # Install Parrot v-table mapping.
 
250
        self.publish_parrot_vtable_mapping($obj);
 
251
                self.publish_parrot_vtablee_handler_mapping($obj);
 
252
#?endif
 
253
        
 
254
        # Create BUILDPLAN.
 
255
        self.create_BUILDPLAN($obj);
 
256
        
 
257
        # Compose the representation.
 
258
        unless $!composed {
 
259
            self.compose_repr($obj);
 
260
        }
 
261
        
 
262
        # Mark as composed.
 
263
        $!composed := 1;
 
264
 
 
265
        $obj
 
266
    }
 
267
    
 
268
    method compose_repr($obj) {
 
269
        # Use any attribute information to produce attribute protocol
 
270
        # data. The protocol consists of an array...
 
271
        my @repr_info;
 
272
        
 
273
        # ...which contains an array per MRO entry...
 
274
        for @!mro -> $type_obj {
 
275
            my @type_info;
 
276
            nqp::push(@repr_info, @type_info);
 
277
 
 
278
            # ...which in turn contains the current type in the MRO...
 
279
            nqp::push(@type_info, $type_obj);
 
280
        
 
281
            # ...then an array of hashes per attribute...
 
282
            my @attrs;
 
283
            nqp::push(@type_info, @attrs);
 
284
            for $type_obj.HOW.attributes($type_obj, :local) -> $attr {
 
285
                my %attr_info;
 
286
                %attr_info<name> := $attr.name;
 
287
                %attr_info<type> := $attr.type;
 
288
                if $attr.box_target {
 
289
                    # Merely having the key serves as a "yes".
 
290
                    %attr_info<box_target> := 1;
 
291
                }
 
292
                if nqp::can($attr, 'auto_viv_container') {
 
293
                    %attr_info<auto_viv_container> := $attr.auto_viv_container;
 
294
                }
 
295
                if $attr.positional_delegate {
 
296
                    %attr_info<positional_delegate> := 1;
 
297
                }
 
298
                if $attr.associative_delegate {
 
299
                    %attr_info<associative_delegate> := 1;
 
300
                }
 
301
                nqp::push(@attrs, %attr_info);
 
302
            }
 
303
        
 
304
            # ...followed by a list of immediate parents.
 
305
            nqp::push(@type_info, $type_obj.HOW.parents($type_obj, :local));
 
306
        }
 
307
        
 
308
        # Compose the representation using it.
 
309
        my $info := nqp::hash();
 
310
        $info<attribute> := @repr_info;
 
311
        nqp::composetype($obj, $info)
 
312
    }
 
313
 
 
314
    method incorporate_multi_candidates($obj) {
 
315
        my $num_todo := nqp::elems(@!multi_methods_to_incorporate);
 
316
        my $i := 0;
 
317
        while $i != $num_todo {
 
318
            # Get method name and code.
 
319
            my $name := @!multi_methods_to_incorporate[$i]<name>;
 
320
            my $code := @!multi_methods_to_incorporate[$i]<code>;
 
321
 
 
322
            # Do we have anything in the methods table already in
 
323
            # this class?
 
324
            my $dispatcher := %!methods{$name};
 
325
            if nqp::defined($dispatcher) {
 
326
                # Yes. Only or dispatcher, though? If only, error. If
 
327
                # dispatcher, simply add new dispatchee.
 
328
                if nqp::can($dispatcher, 'is_dispatcher') && $dispatcher.is_dispatcher {
 
329
                    $dispatcher.add_dispatchee($code);
 
330
                }
 
331
                else {
 
332
                    nqp::die("Cannot have a multi candidate for $name when an only method is also in the class");
 
333
                }
 
334
            }
 
335
            else {
 
336
                # Go hunting in the MRO for a proto.
 
337
                my $j := 1;
 
338
                my $found := 0;
 
339
                while $j != nqp::elems(@!mro) && !$found {
 
340
                    my $parent := @!mro[$j];
 
341
                    my %meths := $parent.HOW.method_table($parent);
 
342
                    my $dispatcher := %meths{$name};
 
343
                    if nqp::defined($dispatcher) {
 
344
                        # Found a possible - make sure it's a dispatcher, not
 
345
                        # an only.
 
346
                        if nqp::can($dispatcher, 'is_dispatcher') && $dispatcher.is_dispatcher {
 
347
                            my $new_disp := $dispatcher.derive_dispatcher();
 
348
                            $new_disp.add_dispatchee($code);
 
349
                            %!methods{$name} := $new_disp;
 
350
                            $found := 1;
 
351
                        }
 
352
                        else {
 
353
                            nqp::die("Could not find a proto for multi $name (it may exist, but an only is hiding it if so)");
 
354
                        }
 
355
                    }
 
356
                    $j := $j + 1;
 
357
                }
 
358
                unless $found {
 
359
                    nqp::die("Could not find a proto for multi $name, and proto generation is NYI");
 
360
                }
 
361
            }
 
362
            $i := $i + 1;
 
363
        }
 
364
    }
 
365
 
 
366
    # Computes C3 MRO.
 
367
    sub compute_c3_mro($class) {
 
368
        my @immediate_parents := $class.HOW.parents($class, :local);
 
369
 
 
370
        # Provided we have immediate parents...
 
371
        my @result;
 
372
        if nqp::elems(@immediate_parents) {
 
373
            if nqp::elems(@immediate_parents) == 1 {
 
374
                @result := compute_c3_mro(@immediate_parents[0]);
 
375
            } else {
 
376
                # Build merge list of lineraizations of all our parents, add
 
377
                # immediate parents and merge.
 
378
                my @merge_list;
 
379
                for @immediate_parents {
 
380
                    nqp::push(@merge_list, compute_c3_mro($_));
 
381
                }
 
382
                nqp::push(@merge_list, @immediate_parents);
 
383
                @result := c3_merge(@merge_list);
 
384
            }
 
385
        }
 
386
 
 
387
        # Put this class on the start of the list, and we're done.
 
388
        nqp::unshift(@result, $class);
 
389
        return @result;
 
390
    }
 
391
 
 
392
    # C3 merge routine.
 
393
    sub c3_merge(@merge_list) {
 
394
        my @result;
 
395
        my $accepted;
 
396
        my $something_accepted := 0;
 
397
        my $cand_count := 0;
 
398
 
 
399
        # Try to find something appropriate to add to the MRO.
 
400
        for @merge_list {
 
401
            my @cand_list := $_;
 
402
            if @cand_list {
 
403
                my $rejected := 0;
 
404
                my $cand_class := @cand_list[0];
 
405
                $cand_count := $cand_count + 1;
 
406
                for @merge_list {
 
407
                    # Skip current list.
 
408
                    unless $_ =:= @cand_list {
 
409
                        # Is current candidate in the tail? If so, reject.
 
410
                        my $cur_pos := 1;
 
411
                        while $cur_pos <= nqp::elems($_) {
 
412
                            if $_[$cur_pos] =:= $cand_class {
 
413
                                $rejected := 1;
 
414
                            }
 
415
                            $cur_pos := $cur_pos + 1;
 
416
                        }
 
417
                    }
 
418
                }
 
419
 
 
420
                # If we didn't reject it, this candidate will do.
 
421
                unless $rejected {
 
422
                    $accepted := $cand_class;
 
423
                    $something_accepted := 1;
 
424
                    last;
 
425
                }
 
426
            }
 
427
        }
 
428
 
 
429
        # If we never found any candidates, return an empty list.
 
430
        if $cand_count == 0 {
 
431
            return @result;
 
432
        }
 
433
 
 
434
        # If we didn't find anything to accept, error.
 
435
        unless $something_accepted {
 
436
            nqp::die("Could not build C3 linearization: ambiguous hierarchy");
 
437
        }
 
438
 
 
439
        # Otherwise, remove what was accepted from the merge lists.
 
440
        my $i := 0;
 
441
        while $i < nqp::elems(@merge_list) {
 
442
            my @new_list;
 
443
            for @merge_list[$i] {
 
444
                unless $_ =:= $accepted {
 
445
                    nqp::push(@new_list, $_);
 
446
                }
 
447
            }
 
448
            @merge_list[$i] := @new_list;
 
449
            $i := $i + 1;
 
450
        }
 
451
 
 
452
        # Need to merge what remains of the list, then put what was accepted on
 
453
        # the start of the list, and we're done.
 
454
        @result := c3_merge(@merge_list);
 
455
        nqp::unshift(@result, $accepted);
 
456
        return @result;
 
457
    }
 
458
 
 
459
    method publish_type_cache($obj) {
 
460
        my @tc;
 
461
        for @!mro { nqp::push(@tc, $_); }
 
462
        for @!done { nqp::push(@tc, $_); }
 
463
        nqp::settypecache($obj, @tc)
 
464
    }
 
465
    
 
466
    sub reverse(@in) {
 
467
        my @out;
 
468
        for @in { nqp::unshift(@out, $_) }
 
469
        @out
 
470
    }
 
471
 
 
472
    method publish_method_cache($obj) {
 
473
        # Walk MRO and add methods to cache, unless another method
 
474
        # lower in the class hierarchy "shadowed" it.
 
475
        my %cache;
 
476
        my @mro_reversed := reverse(@!mro);
 
477
        for @mro_reversed {
 
478
            for $_.HOW.method_table($_) {
 
479
                %cache{nqp::iterkey_s($_)} := nqp::iterval($_);
 
480
            }
 
481
        }
 
482
        nqp::setmethcache($obj, %cache);
 
483
        nqp::setmethcacheauth($obj, 1);
 
484
    }
 
485
 
 
486
    method publish_boolification_spec($obj) {
 
487
        my $bool_meth := self.find_method($obj, 'Bool');
 
488
        if nqp::defined($bool_meth) {
 
489
            nqp::setboolspec($obj, 0, $bool_meth)
 
490
        }
 
491
        else {
 
492
            nqp::setboolspec($obj, 5, nqp::null())
 
493
        }
 
494
    }
 
495
 
 
496
#?if parrot
 
497
    method publish_parrot_vtable_mapping($obj) {
 
498
        my %mapping;
 
499
        my %seen_handlers;
 
500
        for @!mro {
 
501
            for $_.HOW.parrot_vtable_handler_mappings($_, :local(1)) {
 
502
                %seen_handlers{$_.key} := 1;
 
503
            }
 
504
            for $_.HOW.parrot_vtable_mappings($_, :local(1)) {
 
505
                unless nqp::existskey(%mapping, $_.key)
 
506
                        || nqp::existskey(%seen_handlers, $_.key) {
 
507
                    %mapping{$_.key} := $_.value;
 
508
                }
 
509
            }
 
510
        }
 
511
        if +%mapping {
 
512
            pir::stable_publish_vtable_mapping__0PP($obj, %mapping);
 
513
        }
 
514
    }
 
515
 
 
516
    method publish_parrot_vtablee_handler_mapping($obj) {
 
517
        my %mapping;
 
518
        my @mro_reversed := reverse(@!mro);
 
519
        for @mro_reversed {
 
520
            for $_.HOW.parrot_vtable_handler_mappings($_, :local(1)) {
 
521
                %mapping{$_.key} := $_.value;
 
522
            }
 
523
        }
 
524
        if +%mapping {
 
525
            pir::stable_publish_vtable_handler_mapping__0PP($obj, %mapping);
 
526
        }
 
527
    }
 
528
#?endif
 
529
 
 
530
    # Creates the plan for building up the object. This works
 
531
    # out what we'll need to do up front, so we can just zip
 
532
    # through the "todo list" each time we need to make an object.
 
533
    # The plan is an array of arrays. The first element of each
 
534
    # nested array is an "op" representing the task to perform:
 
535
    #   0 code = call specified BUILD method
 
536
    #   1 class name attr_name = try to find initialization value
 
537
    #   2 class name attr_name = try to find initialization value, or set nqp::list()
 
538
    #   3 class name attr_name = try to find initialization value, or set nqp::hash()
 
539
    #   4 class attr_name code = call default value closure if needed
 
540
    method create_BUILDPLAN($obj) {
 
541
        # First, we'll create the build plan for just this class.
 
542
        my @plan;
 
543
        my @attrs := $obj.HOW.attributes($obj, :local(1));
 
544
        
 
545
        # Does it have its own BUILD?
 
546
        my $build := $obj.HOW.method_table($obj)<BUILD>;
 
547
        if nqp::defined($build) {
 
548
            # We'll call the custom one.
 
549
            nqp::push(@plan, [0, $build]);
 
550
        }
 
551
        else {
 
552
            # No custom BUILD. Rather than having an actual BUILD
 
553
            # in Mu, we produce ops here per attribute that may
 
554
            # need initializing.
 
555
            for @attrs {
 
556
                my $attr_name := $_.name;
 
557
                my $name      := nqp::substr($attr_name, 2);
 
558
                my $sigil     := nqp::substr($attr_name, 0, 1);
 
559
                my $sigop     := $sigil eq '@' ?? 2 !! $sigil eq '%' ?? 3 !! 1;
 
560
                nqp::push(@plan, [$sigop, $obj, $name, $attr_name]);
 
561
            }
 
562
        }
 
563
        
 
564
        # Check if there's any default values to put in place.
 
565
        for @attrs {
 
566
            if nqp::can($_, 'build') {
 
567
                my $default := $_.build;
 
568
                if nqp::defined($default) {
 
569
                    nqp::push(@plan, [4, $obj, $_.name, $default]);
 
570
                }
 
571
            }
 
572
        }
 
573
        
 
574
        # Install plan for this class.
 
575
        @!BUILDPLAN := @plan;
 
576
        
 
577
        # Now create the full plan by getting the MRO, and working from
 
578
        # least derived to most derived, copying the plans.
 
579
        my @all_plan;
 
580
        my @mro := self.mro($obj);
 
581
        my $i := nqp::elems(@mro);
 
582
        while $i > 0 {
 
583
            $i := $i - 1;
 
584
            my $class := @mro[$i];
 
585
            for $class.HOW.BUILDPLAN($class) {
 
586
                nqp::push(@all_plan, $_);
 
587
            }
 
588
        }
 
589
        @!BUILDALLPLAN := @all_plan;
 
590
    }
 
591
    
 
592
    method BUILDPLAN($obj) {
 
593
        @!BUILDPLAN
 
594
    }
 
595
    
 
596
    method BUILDALLPLAN($obj) {
 
597
        @!BUILDALLPLAN
 
598
    }
 
599
 
 
600
    ##
 
601
    ## Introspecty
 
602
    ##
 
603
 
 
604
    method parents($obj, :$local = 0) {
 
605
        $local ?? @!parents !! @!mro
 
606
    }
 
607
    
 
608
    method mro($obj) {
 
609
        @!mro
 
610
    }
 
611
 
 
612
    method roles($obj, :$local!) {
 
613
        @!roles
 
614
    }
 
615
 
 
616
    method methods($obj, :$local = 0) {
 
617
        if $local {
 
618
            @!method_order
 
619
        }
 
620
        else {
 
621
            my @meths;
 
622
            for @!mro {
 
623
                for $_.HOW.methods($_, :local) {
 
624
                    nqp::push(@meths, $_)
 
625
                }
 
626
            }
 
627
            @meths
 
628
        }
 
629
    }
 
630
 
 
631
    method method_table($obj) {
 
632
        %!methods
 
633
    }
 
634
 
 
635
    method name($obj) {
 
636
        $!name
 
637
    }
 
638
 
 
639
    method traced($obj) {
 
640
        $!trace
 
641
    }
 
642
 
 
643
    method trace_depth($obj) {
 
644
        $!trace_depth
 
645
    }
 
646
 
 
647
    method attributes($obj, :$local = 0) {
 
648
        my @attrs;
 
649
        if $local {
 
650
            for @!attributes {
 
651
                nqp::push(@attrs, $_);
 
652
            }
 
653
        }
 
654
        else {
 
655
            for @!mro {
 
656
                for $_.HOW.attributes($_, :local) {
 
657
                    nqp::push(@attrs, $_);
 
658
                }
 
659
            }
 
660
        }
 
661
        @attrs
 
662
    }
 
663
 
 
664
#?if parrot
 
665
    method parrot_vtable_mappings($obj, :$local!) {
 
666
        %!parrot_vtable_mapping
 
667
    }
 
668
 
 
669
    method parrot_vtable_handler_mappings($obj, :$local!) {
 
670
        %!parrot_vtable_handler_mapping
 
671
    }
 
672
#?endif
 
673
 
 
674
    ##
 
675
    ## Checky
 
676
    ##
 
677
 
 
678
    method isa($obj, $check) {
 
679
        my $check-class := $check.WHAT;
 
680
        my $i := nqp::elems(@!mro);
 
681
        while $i > 0 {
 
682
            $i := $i - 1;
 
683
            if @!mro[$i] =:= $check-class {
 
684
                return 1;
 
685
            }
 
686
        }
 
687
        return 0;
 
688
    }
 
689
 
 
690
    method does($obj, $check) {
 
691
        my $i := nqp::elems(@!done);
 
692
        while $i > 0 {
 
693
            $i := $i - 1;
 
694
            if @!done[$i] =:= $check {
 
695
                return 1;
 
696
            }
 
697
        }
 
698
        return 0;
 
699
    }
 
700
 
 
701
    method can($obj, $name) {
 
702
        for @!mro {
 
703
            my %meths := $_.HOW.method_table($obj);
 
704
            my $can := %meths{$name};
 
705
            if nqp::defined($can) {
 
706
                return $can;
 
707
            }
 
708
        }
 
709
        return 0;
 
710
    }
 
711
 
 
712
    ##
 
713
    ## Dispatchy
 
714
    ##
 
715
    method find_method($obj, $name, :$no_fallback = 0, :$no_trace = 0) {
 
716
        for @!mro {
 
717
            my %meths := $_.HOW.method_table($obj);
 
718
            if nqp::existskey(%meths, $name) {
 
719
                my $found := %meths{$name};
 
720
                return $!trace && !$no_trace && self.should_trace($obj, $name) ??
 
721
                    -> *@pos, *%named { 
 
722
                        nqp::say(nqp::x('  ', $!trace_depth) ~ "Calling $name");
 
723
                        $!trace_depth := $!trace_depth + 1;
 
724
                        my $result := $found(|@pos, |%named);
 
725
                        $!trace_depth := $!trace_depth - 1;
 
726
                        $result
 
727
                    } !!
 
728
                    $found;
 
729
            }
 
730
        }
 
731
        nqp::null()
 
732
    }
 
733
 
 
734
    ##
 
735
    ## Cache-related
 
736
    ##
 
737
    method cache($obj, $key, $value_generator) {
 
738
        %!caches := nqp::hash() unless nqp::ishash(%!caches);
 
739
        nqp::existskey(%!caches, $key) ??
 
740
            %!caches{$key} !!
 
741
            (%!caches{$key} := $value_generator())
 
742
    }
 
743
    
 
744
    method flush_cache($obj) {
 
745
        %!caches := {} unless nqp::isnull(%!caches)
 
746
    }
 
747
    
 
748
    ##
 
749
    ## Mix-ins
 
750
    ## 
 
751
    has @!mixin_cache;
 
752
    method set_is_mixin($obj) { $!is_mixin := 1 }
 
753
    method is_mixin($obj) { $!is_mixin }
 
754
 
 
755
    method mixin($obj, $role) {
 
756
        # See if we mixed in before.
 
757
        my $found := 0;
 
758
        my $new_type;
 
759
        unless nqp::isnull(@!mixin_cache) {
 
760
            for @!mixin_cache -> $c_role, $c_type {
 
761
                if $c_role =:= $role {
 
762
                    $new_type := $c_type;
 
763
                    $found := 1;
 
764
                    last;
 
765
                }
 
766
            }
 
767
        }
 
768
        
 
769
        # Create and cache mixin-type if needed.
 
770
        unless $found {
 
771
            # Flush its cache as promised, otherwise outdated NFAs will stick around.
 
772
            self.flush_cache($obj) if !nqp::isnull($obj) || self.is_mixin($obj);
 
773
            # Work out a type name for the post-mixed-in role.
 
774
            my $new_name := self.name($obj) ~ '+{' ~ $role.HOW.name($role) ~ '}';
 
775
            
 
776
            # Create new type, derive it from ourself and then add
 
777
            # all the roles we're mixing it.
 
778
            $new_type := self.new_type(:name($new_name), :repr($obj.REPR));
 
779
            $new_type.HOW.set_is_mixin($new_type);
 
780
            $new_type.HOW.add_parent($new_type, $obj.WHAT);
 
781
            $new_type.HOW.add_role($new_type, $role);
 
782
            $new_type.HOW.compose($new_type);
 
783
            
 
784
            # Store the type.
 
785
            nqp::scwbdisable();
 
786
            @!mixin_cache := [] if nqp::isnull(@!mixin_cache);
 
787
            nqp::push(@!mixin_cache, $role);
 
788
            nqp::push(@!mixin_cache, $new_type);
 
789
            nqp::scwbenable();
 
790
            1;
 
791
        }
 
792
        
 
793
        # If the original object was concrete, change its type by calling a
 
794
        # low level op. Otherwise, we just return the new type object
 
795
        nqp::isconcrete($obj) ??
 
796
            nqp::rebless($obj, $new_type) !!
 
797
            $new_type
 
798
    }
 
799
    
 
800
    ##
 
801
    ## Tracing
 
802
    ##
 
803
    method trace-on($obj, $depth?, :@exclude = <MATCH CAPHASH CREATE orig pos>) {
 
804
        $!trace := 1;
 
805
        $!trace_depth := $depth // 0;
 
806
        @!trace_exclude := @exclude;
 
807
        nqp::setmethcacheauth($obj, 0);
 
808
        nqp::setmethcache($obj, nqp::hash());
 
809
    }
 
810
    method trace-off($obj) {
 
811
        $!trace := 0;
 
812
    }
 
813
    method should_trace($obj, $name) {
 
814
        return 0 if nqp::substr($name, 0, 1) eq '!';
 
815
        for @!trace_exclude {
 
816
            return 0 if $name eq $_;
 
817
        }
 
818
        1;
 
819
    }
 
820
}