-
Notifications
You must be signed in to change notification settings - Fork 15
/
Metamodel.pm6
808 lines (704 loc) · 27.1 KB
/
Metamodel.pm6
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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
class Metamodel;
method locstr($fo, $lo, $fn, $ln) {
$fo := $fo // '???';
$lo := $lo // '???';
$fn := $fn // '???';
$ln := $ln // '???';
$fn eq $fo ?? " (see line $lo)" !! " (see $fo line $lo)";
}
# keep synchronized with the list of Kernel.PHASER_XXX values
enum Phaser < INIT END UNIT_INIT KEEP UNDO LEAVE ENTER PRE POST CATCH CONTROL >;
### NIECZA COMPILER METAMODEL
# The metamodel exists to create a timeline inside the compiler. Previously,
# the compiler operated as a pure tree transformer with no conception of how
# PRE-INIT code would play out, thus precluding a lot of important
# optimizations (based on precomputing immutable objects and optimizing
# references to them, mostly).
#
# The metamodel has two main life stages. First, it is built; an incremental
# process logically called BEGIN. Then, it is processed to perform closed-
# world optimizations and generate code; this is (UNIT)CHECK.
#
# Kinds of objects which exist in the metamodel
# - Static subs
# - Packages (incl. classes, modules, grammars)
# - Stashes (Foo::)
#
# This graph is a lot more random than the old trees were...
# While manipulating metamodel bits during BEGIN, these contextuals are needed:
# $*unit: current unit for new objects to attach to
# %*units: maps unit names to unit objects
# $*CURSUB<!sub>: the top non-transparent sub
# Almost all longname and most identifier uses in Perl6 can be divided into
# two groups.
#
# DECLARATIVE references, like class Foo::Bar::Baz {}, have an ending token,
# and the remainder identifies a stash. Leading :: is ignored; if 0 tokens,
# anon is forced, if 1, scope-sensitive special behavior, if 2+, our required.
# Evaluating a declarative reference returns a (stash,name) pair.
#
# REFERENTIAL names, like $Foo::Bar::baz, are interpreted as referring to a
# single variable; in many cases this is used to look for a type object.
# Referential names default to MY:: if 1 token and 0 leading colon.
# Evaluating a referential name returns or binds a variable.
#
# The one exception seems to be method calls, which take a referential name
# plus an extra identifier to name the method.
#
# Trailing :: is forbidden when declaring and means .WHO when referencing.
#
# Functions for handling names in actions:
#
# package_var: Basic function for handling referential names, produces Op.
#
# immed_ref: Like package_var in a BEGIN context.
#
# decl_expr:
#
# immed_decl:
# A stash is an object like Foo::. Stashes are named to allow them to be
# sensibly named across merges.
#
# 'my' stashes are really 'our' stashes with gensym mergable names. Because
# stashes have no identity beyond their contents and set of names, they don't
# mind being copied around a lot.
#
# Stashes are not referencable objects in precompilation mode. You need to
# keep the paths around, instead.
#
# This object holds the stash universe for a unit.
# XXX forward decls are a little broken
my $Package;
class Namespace {
# all maps stash names to stashes. Stashes are represented as simple
# hashes here; the values are always arrays like [$xref, $file, $line].
# $xref may be undefined to indicate a stash entry with no compile-time
# value (our $x, my $x is export).
#
# Stash names are keyed like "GLOBAL::Foo::Bar" or "MAIN:15". Stashes
# outside GLOBAL or PROCESS are anonymous packages, for my aliasing.
has %.all;
# Records *local* operations, so they may be stored and used to
# set up the runtime stashes. Read-only log access is part of the
# public API.
#
# Each entry is an arrayref of the form [$who, $name, $xref, $file, $line].
has @.log;
# This is set up post-creation by NieczaGrammar. It points to a package
# with a who of ''.
has $.root is rw;
method _merge_item($i1, $i2, $who, $name) {
# supress absent entries
return $i2 unless defined $i1;
return $i1 unless defined $i2;
# suppress simple COMMONs if no absent
return $i2 unless defined $i1[0];
return $i1 unless defined $i2[0];
# ooh, we now know we have no COMMONs
my $item1 = $*unit.deref($i1[0]);
my $item2 = $*unit.deref($i2[0]);
return $i1 if $item1 === $item2;
if $item1.^isa($Package) && $item2.^isa($Package) &&
$item1.who eq $item2.who &&
($item1.WHAT === $Package || $item2.WHAT === $Package) {
return $i1;
}
die "Two definitions found for symbol {$who}::$name\n\n" ~
" first at $i1[1] line $i1[2]\n" ~
" second at $i2[1] line $i2[2]";
}
method exists($who, $item) {
return ?(%!all{$who}{$item});
}
method get($who, $item) {
return %!all{$who}{$item}[0]
}
method bind($who, $name, $item, :$file, :$line, :$pos) { #OK not used
my $slot := %!all{$who}{$name};
$slot = self._merge_item($slot, [ $item,
$file // '???', $line // '???' ], $who, $name);
push @!log, [ $who, $name, $item, $file, $line ];
}
method get_pkg($from is copy, *@names, :$auto) {
for @names {
my $sl = self.get($from.who, $_);
my $pkg;
if $sl && $sl[0] && ($pkg = $*unit.deref($sl)).^isa($Package) {
} elsif !$auto {
die "Name component $_ not found in $from.who()";
} else {
$pkg = $Package.new(name => $_, who => $from.who ~ '::' ~ $_);
self.bind($from.who, $_, $pkg.xref);
}
$from = $pkg;
}
$from;
}
# Add a new unit set to the from-set and checks mergability
method add_from($from) {
for %*units{$from}.ns.log -> $logent {
# not using bind since we don't want this in the log
my $slot := %!all{$logent[0]}{$logent[1]};
$slot = self._merge_item($slot, [ $logent[2], $logent[3],
$logent[4] ], $logent[0], $logent[1]);
}
}
# List objects in a stash for use by the importer; returns pairs
# of [name, xref]
method list_stash($who) {
my $h = %!all{$who};
map -> $a { $a => $h{$a}[0] }, sort keys $h;
}
}
class RefTarget {
has $.xref;
has $.name = 'ANON';
# TODO BUILD
method new(:$no_xref, *%_) {
my $n = callwith(self, |%_);
return $n if $no_xref;
$n.xref = [ $*unit.name, +$*unit.xref, $n.name ];
push $*unit.xref, $n;
$n
}
method set_name($name) {
$!xref[2] = $!name = $name;
}
}
class Package is RefTarget {
has $.closed;
has $.who;
method close() { $!closed = True; }
}
$Package = Package;
class Module is Package {
}
class Method {
# normally a Str, but may be Op for param roles
has $.name = die "Method.name is required";
# normal, private, meta, sub
has $.kind = die "Method.kind is required"; # Str
has $.multi = die "Method.multi is required"; # Str
has $.var; # Str
has $.body; # Xref
has $.file;
has $.line;
}
class Attribute {
has $.name; # Str, required
has $.sigil; # Str, required
has $.public; # Bool
has $.ivar; # Str
has $.ibody; # Xref
has $.typeconstraint; # Xref
has $.file;
has $.line;
}
class Class is Module {
has $.attributes = [];
has $.methods = [];
has $.superclasses = [];
has $.linearized_mro; # is rw
has $!closing;
method add_attribute($name, $sigil, $public, $ivar, $ibody,
$typeconstraint, :$file, :$line, :$pos) { #OK not used
if grep $name eq *.name, @($!attributes) -> $O {
die "Two definitions of attribute $name" ~ Metamodel.locstr($O[0].file, $O[0].line, $file, $line);
}
push $.attributes, Metamodel::Attribute.new(:$name, :$sigil,
:$public, :$ivar, :$ibody, :$typeconstraint, :$file, :$line);
$.attributes.[*-1];
}
method add_method($multi, $kind, $name, $var, $body, :$file, :$line, :$pos) { #OK not used
if $name ~~ Str && $multi eq 'only' &&
grep { $name eq .name && $kind eq .kind }, @($!methods) -> $O {
die "Two definitions of method $name" ~ Metamodel.locstr($O[0].file, $O[0].line, $file, $line);
}
push $.methods, Metamodel::Method.new(:$name, :$body, :$kind, :$multi,
:$file, :$line);
}
method add_super($targ) {
die "bad attempt to add null super" unless $targ;
push $.superclasses, $targ;
}
sub c3clear($item, @lists) {
for @lists -> $l {
my $i = 1;
while $i < $l {
return False if $*unit.deref($l[$i]) === $*unit.deref($item);
$i++;
}
}
for @lists -> $l {
$l.shift if $l && $*unit.deref($l[0]) === $*unit.deref($item);
}
True;
}
sub c3merge(@onto, @lists) {
my $ix = 0;
while $ix < @lists {
#say "C3 MRO status ($ix):";
#say "Onto: ", @onto.map({ $*unit.deref($_).name }).join(" <- ");
#say $_.map({ $*unit.deref($_).name }).join(" <- ") for @lists;
#say "---";
my $l = @lists[$ix];
if !$l || !c3clear((my $item = $l[0]), @lists) {
$ix++;
next;
}
push @onto, $item;
$ix = 0;
}
my $bad = False;
for @lists -> $l { $bad ||= $l }
if $bad {
my @hrl = @lists.grep(*.Bool).map(
{ $^l.map({ $*unit.deref($^i).name }).join(" <- ") });
die "C3-MRO wedged! @hrl.join(" | ")";
}
}
method close() {
return if $.closed;
if ($!closing) {
die "Class hierarchy circularty detected at $.name\n";
}
$!closing = True;
if (($.name ne 'Mu' || !$*unit.is_true_setting)
&& !$.superclasses) {
self.add_super($*CURLEX<!sub>.compile_get_pkg(self._defsuper).xref);
}
my @merge;
push @merge, [ $.xref ];
for @$.superclasses -> $x {
my $d = $*unit.deref($x);
$d.close unless $d.linearized_mro;
push @merge, [ @( $d.linearized_mro ) ];
}
push @merge, [ @( $.superclasses ) ];
my @mro;
c3merge(@mro, @merge);
$.linearized_mro = @mro;
nextsame;
}
method _defsuper() { 'CORE', 'Any' }
}
# roles come in two types; Role objects are used for simple roles, while roles
# with parameters get ParametricRole. Instantiations of parametric roles
# would get ConcreteRole, but that won't be implemented in Niecza A since it
# requires evaluating role parameters, unless we restrict it to typenames or
# something.
class Role is Module {
has $.attributes = [];
has $.methods = [];
has $.superclasses = [];
method add_attribute($name, $sigil, $public, $ivar, $ibody, $typeconstraint, :$file, :$line, :$pos) { #OK not used
if grep $name eq *.name, @($!attributes) -> $O {
die "Two definitions of attribute $name" ~ Metamodel.locstr($O[0].file, $O[0].line, $file, $line);
}
push $.attributes, Metamodel::Attribute.new(:$name, :$sigil,
:$public, :$ivar, :$ibody, :$typeconstraint, :$file, :$line);
$.attributes.[*-1];
}
method add_method($multi, $kind, $name, $var, $body, :$file, :$line, :$pos) { #OK not used
if $name ~~ Str && $multi eq 'only' &&
grep { $name eq .name && $kind eq .kind }, @($!methods) -> $O {
die "Two definitions of method $name" ~ Metamodel.locstr($O[0].file, $O[0].line, $file, $line);
}
if $name !~~ Str {
die "Computed names are legal only in parametric roles";
}
push $.methods, Metamodel::Method.new(:$name, :$body, :$kind,
:$multi, :$file, :$line);
}
method add_super($targ) {
die "bad attempt to add null super" unless $targ;
push $.superclasses, $targ;
}
}
class ParametricRole is Module {
has $.attributes = [];
has $.methods = [];
has $.superclasses = [];
method add_attribute($name, $sigil, $public, $ivar, $ibody, $typeconstraint, :$file, :$line, :$pos) { #OK not used
if grep $name eq *.name, @($!attributes) -> $O {
die "Two definitions of attribute $name" ~ Metamodel.locstr($O[0].file, $O[0].line, $file, $line);
}
push $.attributes, Metamodel::Attribute.new(:$name, :$sigil,
:$public, :$ivar, :$ibody, :$typeconstraint, :$file, :$line);
$.attributes.[*-1];
}
method add_method($multi, $kind, $name, $var, $body, :$file, :$line, :$pos) { #OK not used
if $name ~~ Str && $multi eq 'only' &&
grep { $name eq .name && $kind eq .kind }, @($!methods) -> $O {
die "Two definitions of method $name" ~ Metamodel.locstr($O[0].file, $O[0].line, $file, $line);
}
push $.methods, ::Metamodel::Method.new(:$name, :$body, :$var, :$kind, :$multi, :$file, :$line);
}
method add_super($targ) {
die "bad attempt to add null super" unless $targ;
push $.superclasses, $targ;
}
}
class Grammar is Class {
method _defsuper() { 'CORE', 'Grammar' }
}
# subsets are a bit simpler than roles/grammars/classes, as they have
# no body and so attributes &c cannot be added to them directly.
class Subset is Module {
# subset <longname>? <trait>* [where <EXPR>]?
has $.basetype is rw;
# Xref to a sub which will be called once the first time the subset
# is used.
has $.where is rw;
}
#####
# This is a static lexical; they exist in finite number per unit. They may
# occupy specific slots in pads, or globals, or something else entirely.
class Lexical {
has $.file;
has $.line;
has $.pos;
# my $foo, @foo, %foo, &foo
class Simple is Lexical {
has Bool $.list = False;
has Bool $.hash = False;
has Bool $.noinit = False;
has Bool $.defouter = False;
has Bool $.roinit = False;
has $.typeconstraint; # Xref
}
# These are used for $?foo et al, and should be inaccessible until assigned,
# although the current code won't enforce that well.
class Hint is Lexical {
}
# These store destinations for lexotic control transfers, and clone like
# subs to handle recursion properly.
class Label is Lexical {
}
class Dispatch is Lexical {
}
# our...
class Common is Lexical {
has $.pkg = die "M:L:Common.path required"; # Xref to Package
has $.name = die "M:L:Common.name required"; # Str
}
# mostly for state
class Alias is Lexical {
has $.to = die "M:L:Alias.to required"; # Str
}
# sub foo { ... }
class SubDef is Lexical {
has $.body; # Metamodel::StaticSub
}
# my class Foo { } or our class Foo { }; either case, the true
# stash lives in stashland. Actually this points at a package now.
class Stash is Lexical {
has $.pkg; # Xref
}
}
# The life cycle of a static sub has three phases.
# 1. Open - the end of the sub hasn't been seen, so the full code is absent.
# 2. Closing - all attributes are available but no references exist. The
# perfect time for most optimizations, especially ones that look like
# escape analyses.
# 3. Closed - references exist, possibly even from BEGIN-run code. The sub
# must be treated as semantically immutable. The code can probably still
# be changed to reflect new information, though.
# figure out how post-declared lexicals should interact with codegen
# std accepts: sub foo() { bar }; BEGIN { foo }; sub bar() { }
# DONE: TimToady says bar can be compiled to a runtime search
class StaticSub is RefTarget {
has $.unit; # Metamodel::Unit
has $.outerx; # Xref
# points directly to the outer so that explain_mystery doesn't need
# to worry about inlining...
has $.outer_direct is rw;
has Bool $.run_once is rw = False;
has Bool $.spad_exists is rw = False;
has Bool $.transparent = False; # ignored by OUTER::
has %.lexicals;
has $.code is rw; # Op
has $.signature is rw; # Sig
has $.zyg = []; # Array of Metamodel::StaticSub
# inject a take EMPTY
has Bool $.gather_hack is rw = False;
# inject a role constructor (Xref)
has $.parametric_role_hack is rw;
# some tuples for method definitions; munged into a phaser
has $.augment_hack is rw;
# emit code to assign to a hint; [ $subref, $name ]
has $.hint_hack is rw;
has $.is_phaser is rw; # Int
has Bool $.strong_used is rw = False; # prevents elision
has $.body_of is rw; # Xref of Package
has $.in_class is rw; # Xref of Package
has $.cur_pkg is rw; # Xref of Package
has Bool $.returnable is rw = False; # catches &return
has Bool $.augmenting is rw = False; # traps add_attribute
has Bool $.unsafe is rw = False; # disallowed in safe mode
has Str $.class is rw = 'Sub';
has $.ltm is rw;
# a place to hang off extra stuff that's not used for most subs
# currently: "prec" for operators, "builtin" for primitives
has $.extend is rw;
# used during parse only
has Str $.outervar is rw; # Xref, used during parse
has $.methodof is rw; # Xref, used during parse
has %.lexicals-used;
# not just local lexicals, but all in parse; from current or any inside
# block
method outer() {
$!outer_direct //= ($!outerx ?? $*unit.deref($!outerx) !! StaticSub)
}
method true_setting() {
my $cursor = self;
while $cursor && !$cursor.unit.is_true_setting {
$cursor = $cursor.outer;
}
$cursor || self;
}
method to_unit() {
my $cursor = self;
my $unit = self.unit;
my $outer;
while ($outer = $cursor.outer) && $outer.unit === $unit {
$cursor = $outer
}
$cursor;
}
method is_routine() {
state %routine = (:Routine, :Sub, :Submethod, :Method, :Regex);
%routine{$!class}
}
method add_child($z) { push $.zyg, $z }
method children() { @$.zyg }
method clear_optree() {
$.code = Any;
$.ltm = Any;
}
method create_static_pad() {
return Nil if $!spad_exists;
$!spad_exists = True;
$.outer.create_static_pad if $.outer && $.outer.unit === $!unit;
}
method noninlinable() {
loop (my $c = self; $c && $c.unit === $*unit; $c = $c.outer) {
$c.strong_used = True;
}
}
method topicalizer() {
$.signature && ?( grep { .slot && .slot eq '$_' }, @( $.signature.params ) )
}
# helper for compile_get_pkg; handles stuff like SETTING::OUTER::Foo,
# recursively.
method _lexy_ref(*@names, :$auto) {
@names || die "Cannot use a lexical pseudopackage as a compile time package reference";
self // die "Passed top of lexical tree";
given shift @names {
when 'OUTER' { return self.outer._lexy_ref(@names, :$auto) }
when 'SETTING' { return self.to_unit.outer._lexy_ref(@names, :$auto) }
when 'UNIT' { return self.to_unit._lexy_ref(@names, :$auto) }
when 'CALLER' { die "Cannot use CALLER in a compile time name" }
default {
my $lex = self.find_lex($_);
$lex // die "No lexical found for $_";
$lex.^isa(Metamodel::Lexical::Stash) || die "Lexical $_ is not a package";
return $*unit.get_pkg($*unit.deref($lex.pkg), @names, :$auto);
}
}
}
# returns direct reference to package, or dies
method compile_get_pkg(*@names, :$auto) {
@names || die "Cannot make a compile time reference to the semantic root package";
my $n0 = shift(@names);
if $n0 eq 'OUR' {
return $*unit.get_pkg($*unit.deref($!cur_pkg), @names, :$auto);
} elsif $n0 eq 'PROCESS' or $n0 eq 'GLOBAL' {
return $*unit.abs_pkg($n0, @names, :$auto);
} elsif $n0 eq any < COMPILING DYNAMIC CLR CALLER > {
# Yes, COMPILING is right here. Because COMPILING is only valid
# when recursively running code within the compiler, but this
# function is only called directly from the compiler. The closest
# it comes to making sense is if you use eval in a macro. Don't
# do that, okay?
die "Pseudo package $n0 may not be used in compile time reference";
} elsif $n0 eq 'MY' {
return self._lexy_ref(@names, :$auto);
} elsif $n0 eq 'CORE' {
return self.true_setting._lexy_ref(@names, :$auto);
} elsif $n0 eq 'OUTER' or $n0 eq 'SETTING' or $n0 eq 'UNIT' {
return self._lexy_ref($n0, @names, :$auto);
} elsif $n0 ne 'PARENT' && self.find_lex($n0) {
return self._lexy_ref($n0, @names, :$auto);
} elsif $n0 ~~ /^\W/ {
return $*unit.get_pkg($*unit.deref($!cur_pkg), $n0, @names, :$auto);
} else {
return $*unit.abs_pkg('GLOBAL', $n0, @names, :$auto);
}
}
method bind_our_name($path, $name, $item, *%_) {
my $pkg = self.compile_get_pkg($path ?? @$path !! 'OUR', :auto);
$*unit.bind($pkg, $name, $item, |%_);
}
method find_lex($name) {
my $l = %!lexicals{$name};
if $l {
return $l.^isa(Metamodel::Lexical::Alias) ??
self.find_lex($l.to) !! $l;
}
return ($.outer ?? $.outer.find_lex($name) !! Metamodel::Lexical);
}
method delete_lex($name) {
my $l = %!lexicals{$name};
if $l {
if $l.^isa(Metamodel::Lexical::Alias) { self.delete_lex($l.to) }
else { %!lexicals{$name}:delete }
} else {
$.outer && $.outer.unit === $.unit && $.outer.delete_lex($name);
}
}
method add_lex($slot, $item) {
if %!lexicals{$slot} -> $o {
my $l = Metamodel.locstr($o.file, $o.line, $item.file, $item.line);
if $slot ~~ /^\w/ {
die "Illegal redeclaration of symbol '$slot'$l";
} elsif $slot ~~ /^\&/ {
die "Illegal redeclaration of routine '$slot.substr(1)'$l";
} else {
$*worry.("Useless redeclaration of variable $slot$l");
return;
}
}
# We don't know in advance if $_ exists. This is OK.
# TODO: The semantics are off here. $_ should be in every block.
elsif $slot ne '$_' && %!lexicals-used{$slot} -> $p {
my $truename = $slot;
my $c = self;
while $c && !$c.lexicals{$slot} {
$truename ~~ s/<?before \w>/OUTER::/;
$c = $c.outer;
}
die "Lexical tracking inconsistency" unless $c;
my $o = $c.lexicals{$slot};
die "Lexical symbol '$slot' is already bound to an outer symbol{Metamodel.locstr($o.file, $o.line, $item.file, $item.line)};\n the implicit outer binding at line $p.value() must be rewritten as $truename\n before you can unambiguously declare a new '$slot' in this scope";
}
%!lexicals{$slot} = $item;
if substr($slot,0,1) eq '&' && (%*MYSTERY{substr($slot,1)}:exists) {
%!lexicals-used{$slot} = True;
}
}
method add_my_name($slot, *%param) {
self.add_lex($slot, Metamodel::Lexical::Simple.new(|%param));
}
method add_hint($slot, *%params) {
self.add_lex($slot, Metamodel::Lexical::Hint.new(|%params));
}
method add_label($slot, *%params) {
self.add_lex($slot, Metamodel::Lexical::Label.new(|%params));
}
method add_dispatcher($slot, *%params) {
self.add_lex($slot, Metamodel::Lexical::Dispatch.new(|%params));
}
method add_common_name($slot, $pkg, $name, :$file, :$line, :$pos) {
$*unit.bind($*unit.deref($pkg), $name, Any, :$file, :$line)
unless $*unit.ns.exists($*unit.deref($pkg).who, $name);
self.add_lex($slot, Metamodel::Lexical::Common.new(:$pkg, :$name,
:$file, :$line, :$pos));
}
method add_state_name($slot, $back, *%param) {
# outermost sub isn't cloned so a fallback to my is safe
my $up = (self === self.to_unit) ?? self !! self.outer;
$up.lexicals{$back} = Metamodel::Lexical::Simple.new(|%param);
if defined($slot) {
self.add_lex($slot, Metamodel::Lexical::Alias.new(to => $back,
|%param));
}
}
method add_my_stash($slot, $pkg, *%params) {
self.add_lex($slot, Metamodel::Lexical::Stash.new(:$pkg, |%params));
}
method add_my_sub($slot, $body, *%params) {
self.add_lex($slot, Metamodel::Lexical::SubDef.new(:$body, |%params));
}
method add_exports($name, $xref, $tags) {
for @$tags -> $tag {
$*unit.bind($*unit.get_pkg($*unit.deref($!cur_pkg), 'EXPORT',
$tag, :auto), $name, $xref);
}
+$tags;
}
method close() { }
}
class Unit {
has Metamodel::StaticSub $.mainline is rw;
has Str $.name;
has Metamodel::Namespace $.ns;
has $.setting_ref is rw;
has $.bottom_ref is rw;
has $.xref = [];
has $.tdeps = {};
has Str $.filename is rw;
has $.modtime is rw; # Numeric
has Int $.next_anon_stash is rw = 0; # is rw, Int
has @.stubbed_stashes; # Pair[Stash,Cursor]
method bind($pkg,$name,$item,*%_) { $!ns.bind($pkg.who,$name,$item,|%_) }
method list_stash($pkg) { $!ns.list_stash($pkg.who) }
method get($pkg,$name) { $!ns.get($pkg.who,$name) }
method get_pkg($pkg,*@names,:$auto) { $!ns.get_pkg($pkg,@names,:$auto) }
method abs_pkg(*@names, :$auto) {
$!ns.get_pkg($*unit.deref($!ns.root),@names,:$auto)
}
method is_true_setting() { $!name eq 'CORE' }
method get_unit($name) { %*units{$name} }
method anon_stash() { "{$.name}:{$.next_anon_stash++}" }
method deref($thing) {
die "trying to dereference null" unless $thing;
self.get_unit($thing[0]).xref[$thing[1]] // die "invalid ref @$thing";
}
method visit_units_preorder($cb) {
my %seen;
sub rec {
return Nil if %seen{$_};
%seen{$_} = True;
for sort keys self.get_unit($_).tdeps { rec($_) }
$cb(self.get_unit($_));
}
rec($.name);
}
method visit_local_packages($cb) {
for @$.xref -> $x {
$cb($x) if defined($x) && $x.^isa(Metamodel::Package);
}
}
method clear_optrees() {
self.visit_local_subs_postorder({ $_.clear_optree })
}
method visit_local_subs_postorder($cb) {
sub rec {
for $_.children { rec($_) }
$cb($_);
}
rec($.mainline);
}
method visit_local_subs_preorder($cb) {
sub rec {
$cb($_);
for $_.children { rec($_) }
}
rec($.mainline);
}
method need_unit($u2name) {
return %*units{$u2name} if $.tdeps{$u2name};
my $u2 = %*units{$u2name} //= $*module_loader.($u2name);
$.tdeps{$u2name} = [ $u2.filename, $u2.modtime ];
my @new = $u2name;
for keys $u2.tdeps -> $k {
next if $.tdeps{$k};
push @new, $k;
%*units{$k} //= $*module_loader.($k);
$.tdeps{$k} = $u2.tdeps{$k};
}
$!ns.add_from($_) for @new;
$u2;
}
}