/[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.1 - (show annotations) (download)
Sun Dec 7 11:46:05 2008 UTC (17 years, 7 months ago) by wakaba
Branch: MAIN
++ swe/visualizer/ChangeLog	7 Dec 2008 11:45:48 -0000
	* regexp.cgi: Parser patch and graph constructor codes moved into
	their own modules.

2008-12-07  Wakaba  <wakaba@suika.fam.cx>

++ swe/lib/Regexp/Parser/ChangeLog	7 Dec 2008 11:45:06 -0000
2008-12-07  Wakaba  <wakaba@suika.fam.cx>

	* Perl58.pm: New module.

	* ChangeLog: New file.

++ swe/lib/Regexp/ChangeLog	7 Dec 2008 11:01:48 -0000
2008-12-07  Wakaba  <wakaba@suika.fam.cx>

	* ChangeLog: New file.

	* Parser/: New directory.

++ swe/lib/Regexp/Visualize/ChangeLog	7 Dec 2008 11:44:58 -0000
2008-12-07  Wakaba  <wakaba@suika.fam.cx>

	* Simple.pm: New module.

	* ChangeLog: New file.


++ swe/lib/ChangeLog	7 Dec 2008 11:01:35 -0000
2008-12-07  Wakaba  <wakaba@suika.fam.cx>

	* ChangeLog: New file.

	* Regexp/: New directory.


++ ChangeLog	7 Dec 2008 11:01:21 -0000
	* lib/: New directory.

2008-12-07  Wakaba  <wakaba@suika.fam.cx>

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24