File Coverage

blib/lib/Regexp/Rules.pm
Criterion Covered Total %
statement 118 122 96.7
branch 15 30 50.0
condition 2 2 100.0
subroutine 26 28 92.8
pod 0 6 0.0
total 161 188 85.6


line stmt bran cond sub pod time code
1             package Regexp::Rules;
2 3     2   47146 use 5.008005;
  2         7  
  2         68  
3 2     2   9 use strict;
  2         5  
  2         63  
4 2     2   21 use warnings;
  2         3  
  2         94  
5              
6             our $VERSION = "0.01";
7 2     2   1752 use parent qw(Exporter);
  2         618  
  2         10  
8              
9             use Parse::Keyword {
10 2         25 grammar => \&parse_grammar,
11             rule => \&parse_rule,
12             token => \&parse_token,
13 2     2   1704 };
  2         35090  
14             our @EXPORT = qw(grammar rule token);
15 2     2   392 use Carp ();
  2         11  
  2         383  
16              
17             our $PACKAGE;
18             our @RULES;
19             our @TOKENS;
20             our $TOP_OK;
21             our $ACTION;
22             our @STACK;
23              
24             our $NESTED;
25             BEGIN {
26 2     2   492 $NESTED = qr/ \{( [^{}] | (??{ $NESTED }) )* \} /x ;
27             # $NESTED = qr!\A ( \{ (?: [^{}] | (??{ $NESTED }) )* \} )!x;
28             }
29              
30             sub grammar {
31 1     1 0 4 my ($name, $block) = @_;
32 1         3 local $PACKAGE = $name;
33 1         3 local @RULES;
34 1         3 local @TOKENS;
35 1         2 local $TOP_OK;
36 1         5 $block->();
37              
38 1 50       4 unless ($TOP_OK) {
39 0         0 Carp::croak "Missing TOP rule in $name";
40             }
41              
42 1         5 my $re = _construct_regexp();
43              
44 2     2   11 no strict 'refs';
  2         4  
  2         3116  
45 1         2 unshift @{"${name}::ISA"}, 'Regexp::Rules::Base';
  1         17  
46 1     5   5 *{"${name}::regexp"} = sub { $re };
  1         15  
  5         16  
47             }
48              
49             sub _prepare {
50 33     33   801 push @STACK, [];
51             }
52              
53             sub _finalize {
54 33     33   43 my $name = shift;
55 33         46 my $top = pop @STACK;
56 33         159 push $STACK[-1], $ACTION->$name($top);
57             }
58              
59             sub _compile_re {
60 4     4   7 my ($name, $re, $is_token) = @_;
61 4 100       8 my $arg = $is_token ? '$^N' : do {
62 3         7 "Regexp::Rules::_pop_stack('$name')"
63             };
64 4         15 return " (?<$name> (?> (?{ Regexp::Rules::_prepare() }) $re (?{ Regexp::Rules::_finalize('$name') })))";
65             }
66              
67             sub _construct_regexp {
68 1     1   1 my @inner;
69 1         3 for my $rule (@RULES) {
70 3         6 my ($name, $re) = @$rule;
71 3         7 push @inner, _compile_re($name, $re, 0);
72             }
73 1         2 for my $token (@TOKENS) {
74 1         3 my ($name, $re) = @$token;
75 1         3 $re = "(?:$re)";
76 1         3 push @inner, _compile_re($name, $re, 1);
77             }
78 1         4 my $inner = join("\n", @inner);
79 2     2   17 use re 'eval';
  2         3  
  2         3369  
80 1         351 my $re = qr{
81             (?&TOP)
82             (?(DEFINE)
83             $inner
84             )}msx;
85 1         6 return $re;
86             # qr{
87             # (?&additive)
88             # (?(DEFINE)
89             # # additive <- multitive ([+-] multitive)*
90             # (?
91             # (?> (?&multitive) (?: ([+-]) (?&multitive) (?{ $a=shift @stack; $b=shift @stack; push @stack, [$^N, $a, $b] }))*) )
92             # # multitive <- primary ([*/] primary)*
93             # (?
94             # (?> (?&primary) (?:([*/]) (?&primary) (?{ $a= shift @stack; $b = shift @stack; push @stack, [$^N, $a, $b] }))*) )
95             # # primary <- [0-9]+ / [(] additive [)]
96             # (?
97             # (?>([0-9]+)(?{ push @stack, $^N }) | [(] (?&additive) [)]) )
98             # )
99             # }msx;
100             }
101              
102             sub rule {
103 3     3 0 6 my ($name, $re) = @_;
104 3 100       8 if ($name eq 'TOP') {
105 1         2 $TOP_OK++;
106             }
107 3         10 push @RULES, [$name, $re];
108             }
109              
110             sub token {
111 1     1 0 2 my ($name, $re) = @_;
112 1         3 push @TOKENS, [$name, $re];
113             }
114              
115             sub parse_grammar {
116 1     1 0 25655 lex_read_space;
117 1 0       7 die "syntax error." unless lex_peek(1024) =~ /\A([A-Z0-9a-z:]+)/;
118 1         5 my $name = $1;
119 1         5 lex_read(length($1));
120 1         2 lex_read_space;
121 1 0       3 die "syntax error!" unless lex_peek eq '{';
122 1         21 my $block= parse_block;
123 1         3 lex_read_space;
124              
125 1     1   2487 return (sub { $name, $block });
  0         0  
126             }
127              
128             sub parse_rule {
129 3     3 0 7 lex_read_space;
130              
131             # parse name
132 3 0       11 die "syntax error?" unless lex_peek(1024) =~ /\A([A-Z0-9a-z:]+)/;
133 3         9 my $name = $1;
134 3         6 lex_read(length($1));
135 3         5 lex_read_space;
136              
137             # TODO: support balanced parens like `rule foo { x{1,3} }`
138 3 0       102 die "syntax error!!" unless lex_peek(1024) =~ qr{\A ( $NESTED )}x;
139 3         14 my $re = $1;
140 3         8 lex_read(length($1));
141 3         7 $re =~ s/\A\{//;
142 3         9 $re =~ s/\}\z//;
143 3         6 lex_read_space;
144              
145 3     3   31 return (sub { $name, $re });
  3         15  
146             }
147              
148             # token NAME REGEXP
149             sub parse_token {
150 1     1 0 3 lex_read_space;
151              
152             # parse name
153 1 0       5 die "syntax error?" unless lex_peek(1024) =~ /\A([A-Z0-9a-z:]+)/;
154 1         3 my $name = $1;
155 1         4 lex_read(length($1));
156 1         2 lex_read_space;
157              
158             # TODO: support balanced parens like `token foo { x{1,3} }`
159 1 0       27 die "syntax error!!" unless lex_peek(1024) =~ qr{\A ( $NESTED )}x;
160 1         4 my $re = $1;
161 1         3 lex_read(length($1));
162 1         4 $re =~ s/\A\{//;
163 1         3 $re =~ s/\}\z//;
164 1         3 lex_read_space;
165              
166 1     1   19 return (sub { $name, $re });
  1         6  
167             }
168              
169             package Regexp::Rules::Base {
170             sub parse {
171 4     4   9497 my ($class, $expression, $action) = @_;
172 4   100     22 local $ACTION = $action // 'Regexp::Rules::DefaultAction';
173 4         13 local @STACK = ([]);
174 4         10 my $regexp = $class->regexp;
175 4         628 my $ok = ($expression =~ /\A(?:$regexp)\z/);
176 4 50       44 return $ok ? shift $STACK[0] : undef;
177             }
178             }
179              
180             package Regexp::Rules::DefaultAction {
181             our $AUTOLOAD;
182 0     0   0 sub DESTROY { }
183             sub AUTOLOAD {
184 10     10   13 my ($class, $stuff) = @_;
185 10         12 my $meth = substr $AUTOLOAD, length('Regexp::Rules::DefaultAction::');
186             # use Data::Dumper; warn Dumper([$meth, $stuff]);
187 10 100       23 if (defined $^N) {
188 6 100       10 if (@$stuff == 0) {
189 3         69 $^N;
190             } else {
191 3         86 [$^N, $stuff];
192             }
193             } else {
194 4 50       89 @$stuff == 1 ? $stuff->[0] : $stuff;
195             }
196             }
197             }
198              
199             package Regexp::Rules::SexpAction {
200             our $AUTOLOAD;
201 0     0   0 sub DESTROY { }
202             sub AUTOLOAD {
203 17     17   24 my ($class, $stuff) = @_;
204 17         24 my $meth = substr $AUTOLOAD, length('Regexp::Rules::SexpAction::');
205             # use Data::Dumper; warn Dumper([$meth, $stuff]);
206 17 100       38 if (defined $^N) {
207 11 100       20 if (@$stuff == 0) {
208 6         159 $^N;
209             } else {
210 5         146 "($^N " . join(" ", @$stuff) . ")";
211             }
212             } else {
213 6         130 join(' ', @$stuff);
214             }
215             }
216             }
217              
218             1;
219             __END__