File Coverage

blib/lib/Language/AttributeGrammar/Parser.pm
Criterion Covered Total %
statement 54 55 98.1
branch 5 6 83.3
condition 4 5 80.0
subroutine 13 13 100.0
pod 0 1 0.0
total 76 80 95.0


line stmt bran cond sub pod time code
1             package Language::AttributeGrammar::Parser;
2              
3 5     5   25 use strict;
  5         9  
  5         161  
4 5     5   26 use warnings;
  5         17  
  5         124  
5 5     5   24 no warnings 'uninitialized';
  5         8  
  5         149  
6              
7 5     5   2685 use Language::AttributeGrammar::Engine;
  5         16  
  5         144  
8 5     5   14539 use Parse::RecDescent;
  5         167823  
  5         37  
9 5     5   290 use Scalar::Util qw;
  5         10  
  5         700  
10 5     5   34 use Carp::Clan '^Language::AttributeGrammar';
  5         6  
  5         52  
11              
12             my $prefix = 'Language::AttributeGrammar::Parser';
13              
14             our $AUTOACTION = q {
15             if ($item[0] eq 'TOKEN') { 1 }
16             elsif (@item == 2) { $item[1] }
17             elsif ($item{TOKEN}) {
18             bless { value => $item[2], thisline => $thisline } => "$prefix\::$item[0]";
19             }
20             else {
21             bless { %item, thisline => $thisline } => "$prefix\::$item[0]";
22             }
23             };
24              
25             our $SKIP = qr/(?: \s+ | (?: \# .*? \n ) )*/x;
26              
27             our $GRAMMAR = <<'#\'END_GRAMMAR'; # vim hack
28             #\
29              
30             {
31             our $prefix = 'Language::AttributeGrammar::Parser';
32             }
33              
34             grammar: attrsdef(s?) /\z/
35             { bless { attrsdefs => $item[1] } => "$prefix\::$item[0]"; }
36              
37             attrsdef: case ':' attrdef(s? /\|/)
38             { bless { case => $item[1], attrdefs => $item[3] } => "$prefix\::$item[0]"; }
39             |
40              
41             attrdef: attrcall '=' attrblock
42              
43             attrcall: target '.' attr
44             |
45              
46             target: self | child | special
47              
48             attrblock: TOKEN
49             |
50              
51             case: TOKEN /(?: :: )? \w+ (?: :: \w+ )*/x
52             attr: TOKEN /\w+/
53             self: TOKEN '$/'
54             child: TOKEN /\$<\w+>/
55             special: TOKEN /`.*?`/
56              
57             TOKEN: # null
58              
59             #'END_GRAMMAR
60              
61             my $namecount = '0';
62              
63             sub _get_child {
64 100     100   277 my ($self, $child, $at) = @_;
65 100 50 66     873 if ($self->can($child)) { $self->$child }
  0 100       0  
66             elsif (reftype($self) eq 'HASH' && exists $self->{$child}) {
67 99         416 $self->{$child};
68             }
69             else {
70 1         9 croak "Cannot find a way to access $child of $self at $at";
71             }
72             }
73              
74             sub _filter_direct {
75 56     56   101 my ($code, $at) = @_;
76 56         108 $code =~ s[\$/][\$_AG_SELF]gx;
77 56         128 $code =~ s[\$<(\w+)>][Language::AttributeGrammar::Parser::_get_child(\$_AG_SELF, '$1', '$at')]gx;
78 56         137 $code;
79             }
80              
81             sub _filter_code {
82 56     56   128 my ($target, $attr, $code, $at) = @_;
83 56         69 my $result;
84             my $idxa = sub {
85 40     40   131 my ($itarget, $iattr) = @_;
86 40         103 my $id = '$_AG_N' . $namecount++;
87 40         183 $result .= "my $id = \$_AG_ATTR->get($itarget)->get('$iattr');\n";
88 40         208 "$id->get('$iattr', '$at')";
89 56         279 };
90              
91             my $idxarray = sub {
92 1     1   6 my ($itarget, $iattr) = @_;
93 1         4 my $id = '@_AG_N' . $namecount++;
94 1         7 $result .= "my $id = map { \$_AG_ATTR->get(\$_)->get('$iattr') } $itarget;\n";
95 1         8 "(map { \$_->get('$iattr', '$at') } $id)";
96 56         312 };
97            
98 56         217 $code =~ s[\$/ \s* \. \s* (\w+)]
99 19         47 [$idxa->('$_AG_SELF', $1)]gex;
100 56         188 $code =~ s[\$<(\w+)> \s* \. \s* (\w+)]
101 21         116 [$idxa->("Language::AttributeGrammar::Parser::_get_child(\$_AG_SELF, '$1', '$at')", $2)]gex;
102 56         180 $code = _filter_direct($code, $at);
103 56         112 $code =~ s[`(.*?)` \s* \. \s* (\w+)]
104 1         4 [$idxarray->($1, $2)]gex;
105            
106 56         207 $result .= "\$_AG_ATTR->get($target)->get('$attr')->set(sub $code, '$attr', '$at');\n";
107 56         635 $result;
108             }
109              
110             # use an attribute grammar to process the attribute grammar grammar
111             our $ENGINE = Language::AttributeGrammar::Engine->new;
112              
113             add_visitor $ENGINE "$prefix\::grammar" => sub {
114             my ($self, $attrs) = @_;
115            
116             my $prefix = $attrs->get($self)->get('prefix');
117             for (@{$self->{attrsdefs}}) {
118             $attrs->get($_)->get('prefix')->set(sub { $prefix->get });
119             }
120             my @defthunks = map { $attrs->get($_)->get('defthunks') } @{$self->{attrsdefs}};
121             my @cases = map { $attrs->get($_)->get('case') } @{$self->{attrsdefs}};
122              
123             $attrs->get($self)->get('engine')->set(sub {
124             my %visitors;
125             for my $defs (@defthunks) {
126             for my $def (@{$defs->get}) {
127             my $case = $def->{case}->get;
128             $visitors{$case} ||= "my (\$_AG_SELF, \$_AG_ATTR) = \@_;\n";
129             $visitors{$case} .= $def->{visitor}->get;
130             }
131             }
132              
133              
134             my $engine = Language::AttributeGrammar::Engine->new;
135            
136             for my $case (@cases) {
137             $engine->add_case($case->get);
138             }
139            
140             for my $case (keys %visitors) {
141             my $code = eval "sub {\n$visitors{$case}\n}" or croak $@;
142             $engine->add_visitor($case => $code);
143             }
144              
145             $engine;
146             });
147             };
148              
149             add_visitor $ENGINE "$prefix\::attrsdef" => sub {
150             my ($self, $attrs) = @_;
151             my $prefix = $attrs->get($self)->get('prefix');
152             $attrs->get($self->{case})->get('prefix')->set(sub { $prefix->get });
153             my $case = $attrs->get($self->{case})->get('name');
154             for (@{$self->{attrdefs}}) {
155             $attrs->get($_)->get('case')->set(sub { $case->get });
156             }
157             my @defthunks = map {
158             {
159             case => $case,
160             visitor => $attrs->get($_)->get('visitor'),
161             }
162             } @{$self->{attrdefs}};
163              
164             $attrs->get($self)->get('defthunks')->set(sub { \@defthunks });
165             $attrs->get($self)->get('case')->set(sub { $case->get });
166             };
167              
168             add_visitor $ENGINE "$prefix\::attrdef" => sub {
169             my ($self, $attrs) = @_;
170             my $target = $attrs->get($self->{attrcall})->get('target');
171             my $attr = $attrs->get($self->{attrcall})->get('attr');
172             my $code = $attrs->get($self->{attrblock})->get('code');
173             $attrs->get($self)->get('visitor')->set(sub {
174             _filter_code($target->get, $attr->get, $code->get, "grammar line $self->{thisline}");
175             });
176             };
177              
178             add_visitor $ENGINE "$prefix\::attrcall" => sub {
179             my ($self, $attrs) = @_;
180             my $invocant = $attrs->get($self->{target})->get('invocant');
181             my $attr = $attrs->get($self->{attr})->get('name');
182             $attrs->get($self)->get('target')->set(sub { $invocant->get });
183             $attrs->get($self)->get('attr')->set(sub { $attr->get });
184             };
185              
186             add_visitor $ENGINE "$prefix\::attrblock" => sub {
187             my ($self, $attrs) = @_;
188             $attrs->get($self)->get('code')->set(sub { $self->{value} });
189             };
190              
191             add_visitor $ENGINE "$prefix\::case" => sub {
192             my ($self, $attrs) = @_;
193             my $prefixa = $attrs->get($self)->get('prefix');
194             $attrs->get($self)->get('name')->set(sub {
195             my $prefix = $prefixa->get;
196             if ($self->{value} =~ /^::/) {
197             $self->{value};
198             }
199             else {
200             "$prefix$self->{value}";
201             }
202             });
203             };
204              
205             add_visitor $ENGINE "$prefix\::attr" => sub {
206             my ($self, $attrs) = @_;
207             $attrs->get($self)->get('name')->set(sub { $self->{value} });
208             };
209              
210             add_visitor $ENGINE "$prefix\::self" => sub {
211             my ($self, $attrs) = @_;
212             $attrs->get($self)->get('invocant')->set(sub { '$_AG_SELF' });
213             };
214              
215             add_visitor $ENGINE "$prefix\::child" => sub {
216             my ($self, $attrs) = @_;
217             my ($name) = $self->{value} =~ /^\$<(\w+)>$/;
218             $attrs->get($self)->get('invocant')->set(sub {
219             "Language::AttributeGrammar::Parser::_get_child(\$_AG_SELF, '$name', 'grammar line $self->{thisline}')"
220             });
221             };
222              
223             add_visitor $ENGINE "$prefix\::special" => sub {
224             my ($self, $attrs) = @_;
225             my ($code) = $self->{value} =~ /^`(.*)`$/;
226             $code = _filter_direct($code, $self->{thisline});
227             $attrs->get($self)->get('invocant')->set(sub { $code });
228             };
229              
230             $ENGINE->make_visitor('visit');
231              
232              
233             our $PARSER;
234             {
235             local $Parse::RecDescent::skip = $SKIP;
236             local $::RD_AUTOACTION = $AUTOACTION;
237             $PARSER = Parse::RecDescent->new($GRAMMAR);
238             }
239              
240             sub new {
241 19     19 0 56 my ($class, $grammar, $prefix) = @_;
242 19   100     105 $prefix ||= '';
243 19 100       220 my $tree = $PARSER->grammar($grammar) or croak "Parse error";
244 18         224338 return $ENGINE->evaluate('visit', $tree, 'engine', {
245             prefix => $prefix,
246             });
247             }