/[suikacvs]/webroot/regexp/lib/Regexp/Visualize/Simple.pm
Suika

Contents of /webroot/regexp/lib/Regexp/Visualize/Simple.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.5 - (show annotations) (download)
Tue Jan 13 14:15:46 2009 UTC (15 years, 10 months ago) by wakaba
Branch: MAIN
CVS Tags: HEAD
Changes since 1.4: +14 -0 lines
Error occurred while calculating annotation data.
++ swe/lib/Regexp/Visualize/ChangeLog	13 Jan 2009 14:12:50 -0000
2009-01-13  Wakaba  <wakaba@suika.fam.cx>

	* Simple.pod: New file.

	* Makefile: New file.

	* Simple.pm: Added license section.

1 package Regexp::Visualize::Simple;
2 our $VERSION=do{my @r=(q$Revision: 1.31 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
3 use strict;
4 use warnings;
5
6 use Graph::Easy;
7
8 use Scalar::Util qw/refaddr/;
9
10 my $default_map = {};
11 for (qw/. \C \w \W \s \S \d \D \X \1 \2 \3 \4 \5 \6 \7 \8 \9
12 \A ^ \B \b \G \Z \z $/) {
13 $default_map->{$_} = $_;
14 }
15
16 my $assertion_map = {
17 ifmatch => '(?=)',
18 '<ifmatch' => '(?<=)',
19 unlessm => '(?!)',
20 '<unlessm' => '(?<!)',
21 };
22
23 sub new ($) {
24 my $self = bless {}, shift;
25 $self->{next_index} = 0;
26 $self->{regexp_nodes} = [];
27 return $self;
28 } # new
29
30 sub _escape_value ($) {
31 my $v = shift;
32 $v =~ s/(\W)/sprintf '\x{%04X}', ord $1/ge;
33 $v;
34 } # _escape_value
35
36 sub _escape_code ($) {
37 my $v = shift;
38 $v =~ s/([^\x20-\x5B\x5D-\x7E])/sprintf '\x{%04X}', ord $1/ge;
39 $v;
40 } # _escape_code
41
42 sub push_regexp_node ($$) {
43 my $self = shift;
44 my $nodes = shift; # Regexp::Parser's node or array ref of nodes
45 push @{$self->{regexp}}, $nodes;
46 return $self->get_graph_index ($nodes);
47 } # push_regexp_node
48
49 sub shift_regexp_node ($) {
50 my $self = shift;
51 return shift @{$self->{regexp}};
52 } # shift_regexp_node
53
54 sub has_regexp_node ($) {
55 my $self = shift;
56 return scalar @{$self->{regexp}};
57 } # has_regexp_node
58
59 sub get_graph_index ($$) {
60 my $self = shift;
61 my $nodes = shift;
62 $self->{index}->{$nodes} //= $self->{next_index}++;
63 return $self->{index}->{$nodes};
64 } # get_graph_index
65
66 sub next_graph ($) {
67 my $self = shift;
68 my $root_nodes = $self->shift_regexp_node;
69 return (undef, undef) unless $root_nodes;
70
71 my $g = Graph::Easy->new;
72
73 $g->set_attributes ('node.start' => {fill => 'blue', color => 'white'});
74 $g->set_attributes ('node.success' => {fill => 'green', color => 'white'});
75 $g->set_attributes ('node.fail' => {fill => 'red', color => 'white'});
76 $g->set_attributes ('edge.fail' => {color => 'gray'});
77
78 my $start_n = $g->add_node ('START');
79 $start_n->set_attribute (class => 'start');
80 my $success_n = $g->add_node ('SUCCESS');
81 $success_n->set_attribute (class => 'success');
82 my $fail_n = $g->add_node ('FAIL');
83 $fail_n->set_attribute (class => 'fail');
84
85 my ($first_ns, $last_ns, $is_optional)
86 = $self->_add_to_graph ($root_nodes => $g);
87 $g->add_edge ($start_n => $_) for @$first_ns;
88 $g->add_edge ($_ => $success_n) for @$last_ns;
89 $g->add_edge ($start_n => $success_n) if $is_optional;
90
91 $g->del_node ($success_n) unless $success_n->incoming;
92 $g->del_node ($fail_n) unless $fail_n->incoming;
93 $_->set_attribute ('class' => 'fail') for $fail_n->outgoing;
94
95 return ($g, $self->get_graph_index ($root_nodes));
96 } # next_graph
97
98 sub _add_to_graph ($$$) {
99 my ($self, $node, $g) = @_;
100
101 my $family = ref $node eq 'ARRAY' ? '' : $node->family;
102 my $type = ref $node eq 'ARRAY' ? '' : $node->type;
103 if ($family eq 'quant') {
104 my ($min, $max) = ($node->min, $node->max);
105 return ([], [], 1) if $max eq '0';
106 my ($first_ns, $last_ns, $is_optional)
107 = $self->_add_to_graph ($node->data => $g);
108
109 my $label;
110 if ($max eq '') {
111 if ($min == 0) {
112 $is_optional = 1;
113 $label = '';
114
115 } elsif ($min == 1) {
116 $label = '';
117
118 } else {
119 $label = 'at least ' . ($min - 1);
120
121 }
122 } elsif ($max == 1) {
123 if ($min == 0) {
124 $is_optional = 1;
125
126 } else {
127
128 }
129 } else {
130 $label = 'at most ' . ($max - 1);
131 if ($min == 0) {
132 $is_optional = 1;
133
134
135 } elsif ($min == 1) {
136
137 } else {
138 $label = 'at least ' . ($min - 1) . ', ' . $label;
139
140 }
141 }
142
143 if (@$first_ns != 1 or @$last_ns != 1) {
144 my $n = $g->add_node (refaddr $first_ns);
145 $n->set_attribute (label => '');
146 my $m = $n;
147 unless ($is_optional) {
148 $m = $g->add_node (refaddr $last_ns);
149 $m->set_attribute (label => '');
150 } else {
151 $is_optional = 0;
152 }
153 $g->add_edge ($n => $_) for @$first_ns;
154 $g->add_edge ($_ => $m) for @$last_ns;
155 $first_ns = [$n];
156 $last_ns = [$m];
157 }
158
159 if (defined $label) {
160 my $e = $g->add_edge ($last_ns->[0] => $first_ns->[0]);
161 $e->set_attribute (label => $label);
162 }
163
164 return ($first_ns, $last_ns, $is_optional);
165 } elsif ($type eq 'branch') {
166 my @first_n;
167 my @last_n;
168 my $is_optional = 0;
169 for (@{$node->data}) {
170 my ($f_ns, $l_ns, $is_opt) = $self->_add_to_graph ($_ => $g);
171 push @first_n, @$f_ns;
172 push @last_n, @$l_ns;
173 $is_optional |= $is_opt;
174 }
175 return (\@first_n, \@last_n, $is_optional);
176 } elsif ($type eq 'anyof') {
177 my $data = $node->data;
178 if ($node->neg) {
179 if (@$data) {
180 my $nodes = Regexp::Parser::branch->new ($node->{rx});
181 $nodes->{data} = $node->data;
182
183 $self->push_regexp_node ($nodes);
184
185 my $n = $g->add_node (refaddr $nodes);
186 my $label = 'NOT #' . $self->get_graph_index ($nodes);
187 $n->set_attribute (label => $label);
188
189 return ([$n], [$n], 0);
190 } else {
191 my $n = $g->add_node (refaddr $node);
192 my $label = 'Any character';
193 $n->set_attribute (label => $label);
194
195 return ([$n], [$n], 0);
196 }
197 } else {
198 if (@$data) {
199 my @first_n;
200 my @last_n;
201 for (@{$node->data}) {
202 my ($f_ns, $l_ns) = $self->_add_to_graph ($_ => $g);
203 push @first_n, @$f_ns;
204 push @last_n, @$l_ns;
205 }
206 return (\@first_n, \@last_n, 0);
207 } else {
208 my $fail_n = $g->node ('FAIL');
209 return ([$fail_n], [$fail_n], 0);
210 }
211 }
212 } elsif ($family eq '' and $type eq '') {
213 my $prev_ns;
214 my $first_ns;
215 my $is_optional = 1;
216 for (@{$node}) {
217 my ($f_ns, $l_ns, $is_opt) = $self->_add_to_graph ($_ => $g);
218 if ($prev_ns) {
219 if (@$prev_ns > 1 and @$f_ns > 1) {
220 my $n = $g->add_node (refaddr $f_ns);
221 $n->set_attribute (label => '');
222 $g->add_edge ($_ => $n) for @$prev_ns;
223 $g->add_edge ($n => $_) for @$f_ns;
224 } else {
225 for my $prev_n (@$prev_ns) {
226 for my $f_n (@$f_ns) {
227 $g->add_edge ($prev_n => $f_n);
228 }
229 }
230 }
231 if ($is_optional) {
232 push @$first_ns, @$f_ns;
233 }
234 if ($is_opt) {
235 push @$prev_ns, @$l_ns;
236 } else {
237 $prev_ns = $l_ns if @$l_ns;
238 }
239 } else {
240 $first_ns = $f_ns;
241 $prev_ns = $l_ns if @$l_ns;
242 }
243 $is_optional &= $is_opt;
244 }
245 return ($first_ns || [], $prev_ns || [], $is_optional);
246 } elsif ($family eq 'group' or $family eq 'open' or $type eq 'suspend') {
247 ## TODO: (?:) vs () vs (?>), (?:)->on, (?:)->off
248 my ($f_ns, $l_ns, $is_opt) = $self->_add_to_graph ($node->data => $g);
249 return ($f_ns, $l_ns, $is_opt);
250 } elsif ($type eq 'ifthen') {
251 my $nodes = $node->data;
252
253 my $groupp = $nodes->[0];
254 my $label = $groupp ? '(?' . $groupp->visual . ')' : '';
255 my $n = $g->add_node (refaddr $groupp);
256 $n->set_attribute (label => $label);
257
258 my $l = $g->add_node (refaddr $nodes);
259 $l->set_attribute (label => '');
260
261 my $branch = $nodes->[1];
262 my $branches = $branch ? $branch->data : [];
263
264 my $true = $branches->[0];
265 if ($true) {
266 my ($f_ns, $l_ns, $is_opt) = $self->_add_to_graph ($true => $g);
267 $g->add_edge ($n => $_)->set_attribute (label => 'true') for @$f_ns;
268 $g->add_edge ($_ => $l) for @$l_ns;
269 $g->add_edge ($n => $l)->set_attribute (label => 'true') if $is_opt;
270 }
271
272 my $false = $branches->[1];
273 if ($false) {
274 my ($f_ns, $l_ns, $is_opt) = $self->_add_to_graph ($false => $g);
275 $g->add_edge ($n => $_)->set_attribute (label => 'false') for @$f_ns;
276 $g->add_edge ($_ => $l) for @$l_ns;
277 $g->add_edge ($n => $l)->set_attribute (label => 'false') if $is_opt;
278 }
279
280 return ([$n], [$l], 0);
281 } elsif ($type eq 'eval' or $type eq 'logical') {
282 my $n = $g->add_node (refaddr $node);
283 my $label = $type eq 'eval' ? '(?{})' : '(??{})';
284 $label .= ' ' . _escape_code $node->data;
285 $n->set_attribute (label => $label);
286 return ([$n], [$n], 0);
287 } elsif ($family eq 'assertion') {
288 my $nodes = $node->data;
289 $self->push_regexp_node ($nodes);
290
291 my $n = $g->add_node (refaddr $nodes);
292 $type = '<' . $type if $node->dir < 0;
293 my $label = $assertion_map->{$type} // $type;
294 $label .= ' #' . $self->get_graph_index ($nodes);
295 $n->set_attribute (label => $label);
296
297 return ([$n], [$n], 0);
298 } elsif ($family eq 'anyof_class') {
299 my $data = $node->data;
300 my $label;
301 if ($data eq 'POSIX') {
302 my $how = ${$node->{how}};
303 if ($how eq ':') {
304 $label = 'POSIX ' . $node->{type};
305 $label = 'NOT ' . $label if $node->neg;
306 } else {
307 $label = $how . $node->neg . $node->{type} . $how;
308 }
309 } else {
310 my $data_family = $data->family;
311 if ($data_family eq 'prop') {
312 $label = 'property ' . $node->type;
313 $label = 'NOT ' . $label if $node->neg;
314 } elsif ($data_family eq 'space') {
315 $label = $data->neg ? '\S' : '\s';
316 } elsif ($data_family eq 'alnum') {
317 $label = $data->neg ? '\W' : '\w';
318 } elsif ($data_family eq 'digit') {
319 $label = $data->neg ? '\D' : '\d';
320 } else {
321 $label = $data->visual;
322 }
323 }
324
325 my $n = $g->add_node (refaddr $node);
326 $n->set_attribute (label => $label);
327
328 return ([$n] => [$n]);
329 } elsif ($family eq 'exact' or $type eq 'anyof_char') {
330 my $n = $g->add_node (refaddr $node);
331
332 my $label = _escape_value $node->data;
333 $n->set_attribute (label => qq[ "$label" ]);
334
335 return ([$n] => [$n]);
336 } elsif ($family eq 'flags') {
337 ## TODO: scope
338 my $n = $g->add_node (refaddr $node);
339
340 my $label = $node->visual;
341 $n->set_attribute (label => $label);
342
343 return ([$n] => [$n], 0);
344 } elsif ($family eq 'minmod') {
345 my $nodes = $node->data;
346 $self->push_regexp_node ($nodes);
347
348 my $n = $g->add_node (refaddr $nodes);
349 my $label = 'non-greedy #' . $self->get_graph_index ($nodes);
350 $n->set_attribute (label => $label);
351
352 return ([$n], [$n], 0);
353 } elsif ($family eq 'anyof_range') {
354 my $n = $g->add_node (refaddr $node);
355
356 my $start = _escape_value $node->data->[0]->data;
357 my $end = _escape_value $node->data->[1]->data;
358 my $label = qq[ one of "$start" .. "$end" ];
359 $n->set_attribute (label => $label);
360
361 return ([$n] => [$n], 0);
362 } elsif ($family eq 'prop') {
363 my $n = $g->add_node (refaddr $node);
364
365 my $label = 'property ' . $node->type;
366 $label = 'NOT ' . $label if $node->neg;
367 $n->set_attribute (label => $label);
368
369 return ([$n], [$n], 0);
370 } else {
371 my $n = $g->add_node (refaddr $node);
372
373 my $label = $node->visual;
374 $label = $default_map->{$label} // _escape_value $label;
375 $label .= ' (' . $type . ')' unless $default_map->{$label};
376 $n->set_attribute (label => $label);
377
378 return ([$n] => [$n], 0);
379 }
380 } # _add_to_graph
381
382 1;
383
384 __END__
385
386 =head1 LICENSE
387
388 Copyright 2008-2009 Wakaba <w@suika.fam.cx>.
389
390 This library is free software; you can redistribute it and/or modify
391 it under the same terms as Perl itself.
392
393 =cut
394
395 # $Date:$

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24