File Coverage

blib/lib/Language/AttributeGrammar/Engine.pm
Criterion Covered Total %
statement 72 78 92.3
branch 10 10 100.0
condition 4 8 50.0
subroutine 19 21 90.4
pod 6 6 100.0
total 111 123 90.2


line stmt bran cond sub pod time code
1             package Language::AttributeGrammar::Engine;
2              
3             =head1 NAME
4              
5             Language::AttributeGrammar::Engine - Attribute grammar combinators
6              
7             =head1 DESCRIPTION
8              
9             =over
10              
11             =cut
12              
13 5     5   24 use strict;
  5         8  
  5         141  
14 5     5   22 use warnings;
  5         36  
  5         125  
15 5     5   34 no warnings 'uninitialized';
  5         21  
  5         148  
16              
17 5     5   4350 use Carp::Clan '^Language::AttributeGrammar';
  5         23367  
  5         36  
18 5     5   4730 use Perl6::Attributes;
  5         145551  
  5         33  
19 5     5   9376 use Language::AttributeGrammar::Thunk;
  5         19  
  5         1245  
20              
21             sub new {
22 23     23 1 53 my ($class) = @_;
23 23   33     267 bless {
24             cases => {},
25             } => ref $class || $class;
26             }
27              
28             =item * new
29              
30             Create a new engine. No initialization is needed.
31              
32             =cut
33              
34             sub add_case {
35 50     50 1 81 my ($self, $case) = @_;
36 50   100     401 $.cases{$case}{visit} ||= [];
37             }
38              
39             =item * $engine->add_case($case_name)
40              
41             Make sure a visitor is installed for class $case_name. Usually this is not necessary.
42             You need this only when you want a visitor installed for a class that has no attributes
43             defined.
44              
45             =cut
46              
47             sub add_visitor {
48 89     89 1 154 my ($self, $case, $visitor) = @_;
49 89         100 push @{$.cases{$case}{visit}}, $visitor;
  89         491  
50             }
51              
52             =item * $engine->add_visitor($case, $visitor)
53              
54             Add an action to perform when the an object of class $case is visited.
55              
56             $engine->add_visitor(Foo => sub { ... });
57              
58             =cut
59              
60             sub make_visitor {
61 23     23 1 61 my ($self, $visit) = @_;
62            
63 23         36 for my $case (keys %.cases) {
  23         117  
64             $.cases{$case}{visit_all} = sub {
65 457     457   472 $_->(@_) for @{$.cases{$case}{visit}};
  457         4052  
66 90         558 };
67 90 100       223 next if $case eq 'ROOT';
68 5     5   27 no strict 'refs';
  5         11  
  5         2193  
69 85         153 *{"$case\::$visit"} = $.cases{$case}{visit_all};
  85         691  
70             }
71             }
72              
73             =item * $engine->make_visitor($method_name)
74              
75             Install a visitor named $method_name in all the defined cases. This actually
76             modifies the packages, so it's probably a good idea to choose a non-conflicting
77             method name like 'MODULENAME_visit0001'.
78              
79             =cut
80              
81             sub annotate {
82 39     39 1 77 my ($self, $visit, $top, $topattr) = @_;
83 39         56 my @nodeq;
84            
85             my $attrs = Language::AttributeGrammar::Engine::Vivifier->new(sub {
86 454     454   685 push @nodeq, $_[0];
87             Language::AttributeGrammar::Engine::Vivifier->new(sub {
88 782         4838 Language::AttributeGrammar::Thunk->new;
89 454         2227 });
90 39         312 });
91              
92 39 100       123 if ($topattr) {
93 18         62 for my $key (keys %$topattr) {
94 18     18   90 $attrs->get($top)->get($key)->set(sub { $topattr->{$key} });
  18         61  
95             }
96             }
97              
98 39         100 $attrs->get($top); # seed the queue
99            
100 39 100       181 if ($.cases{ROOT}{visit_all}) {
101 5         22 $.cases{ROOT}{visit_all}->($top, $attrs);
102             }
103              
104 39         217 while (my $node = shift @nodeq) {
105 453 100       1712 if ($node->can($visit)) {
106 452         965 $node->$visit($attrs);
107             }
108             else {
109 1         7 croak "No case defined: " . ref($node);
110             }
111             }
112              
113 36         116 return $attrs;
114             }
115              
116             =item * $engine->annotate($method_name, $tree, $top_attrs)
117              
118             Run the visitors on $tree, after having installed a visitor using
119             C in the method name $method_name. Set attributes $top_attrs (a
120             hash) on the top node of the tree. Returns a structure where you can query
121             any attribute of any visited node using:
122              
123             my $attrs = $engine($method_name, $tree, {});
124             my $attr_value = $attrs->get($node)->get('attr')->get;
125              
126             Using the annotated tree directly uses a bunch of memory, since it has to hold
127             every attribute pair. If you are only interested in one attribute of the top node,
128             use:
129              
130             =cut
131              
132             sub evaluate {
133 39     39 1 105 my ($self, $visit, $top, $attr, $topattr) = @_;
134 39         139 my $attrs = $self->annotate($visit, $top, $topattr);
135 36         90 my $head = $attrs->get($top)->get($attr);
136 36         104 undef $attrs; # allow intermediate values to go away
137 36         5591 $head->get($attr, 'top level');
138             }
139              
140             =item * $engine->evaluate($method_name, $tree, $attr, $top_attrs)
141              
142             Does the same as annotate, but returns the value of $attr on the root node of
143             the tree all in one pass. Doing this in one pass allows the engine to clean up
144             intermediate values when they are not needed anymore. This is the preferred
145             form of usage.
146              
147             =back
148              
149             =cut
150              
151             package Language::AttributeGrammar::Engine::Vivifier;
152              
153 5     5   35 use overload ();
  5         20  
  5         1219  
154              
155             sub new {
156 493     493   706 my ($class, $vivi) = @_;
157 493   33     3684 bless {
158             hash => {},
159             vivi => $vivi,
160             } => ref $class || $class;
161             }
162              
163             sub get {
164 3079     3079   4327 my ($self, $key) = @_;
165 3079         6454 my $kval = overload::StrVal($key);
166 3079 100       17016 unless (exists $.hash{$kval}) {
167 1236         2774 my $value = $.vivi->($key);
168 1236         5414 $.hash{$kval} = { key => $key, value => $value };
169 1236         6882 $value;
170             }
171             else {
172 1843         10207 $.hash{$kval}{value}
173             }
174             }
175              
176             sub put {
177 0     0     my ($self, $key, $value) = @_;
178 0           $.hash{overload::StrVal($key)} = { key => $key, value => $value };
179 0           $value;
180             }
181              
182             sub keys {
183 0     0     my ($self) = @_;
184 0           keys %.hash;
  0            
185             }
186              
187             1;