File Coverage

blib/lib/Text/PromptBalanced.pm
Criterion Covered Total %
statement 63 63 100.0
branch 32 34 94.1
condition 6 9 66.6
subroutine 10 10 100.0
pod 0 1 0.0
total 111 117 94.8


line stmt bran cond sub pod time code
1             package Text::PromptBalanced;
2              
3 1     1   24758 use strict;
  1         2  
  1         35  
4 1     1   4 use warnings;
  1         2  
  1         35  
5              
6             require Exporter;
7              
8 1     1   5 use vars qw(@ISA $VERSION @EXPORT_OK);
  1         5  
  1         807  
9             @ISA = qw(Exporter);
10             $VERSION = 0.02;
11             @EXPORT_OK = qw(balance_factory);
12              
13             # {{{ sub balance_factory
14             sub balance_factory {
15 14     14 0 4521 my %config = @_;
16             # {{{ Clean up configuration
17             # Assign default behavior
18             #
19 14 100 66     78 if(defined $config{escape} and !ref($config{escape})) {
20 5         18 $config{escape} = { type => 'escape', open => '\\' }
21             }
22             # We implement the 'ignore_in' behavior of comments in code, so no
23             # rejiggering of the hash should be needed.
24              
25             # Canonicalize the hash.
26             # This basically means changing 'ignore_in' string values to arrays.
27             #
28 14         34 for my $key (keys %config) {
29 21 100       73 next unless defined $config{$key}{ignore_in};
30 2 50       6 next if ref($config{$key}{ignore_in});
31 2         8 $config{$key}{ignore_in} = [$config{$key}{ignore_in}];
32             }
33             # }}}
34             # {{{ Build state hash
35 14         29 my $state = {};
36 14         35 for(keys %config) {
37             # An end-of-line comment is only valid until the end of the input line, so
38             # it will never have any state.
39             # Escape characters by design don't affect balance, so they don't have
40             # a state either.
41             #
42 21 100 66     79 next if $_ eq 'comment' and $config{$_}{type} eq 'to-eol';
43 16 100       38 next if $_ eq 'escape';
44 11         27 $state->{$_} = 0;
45             }
46             # }}}
47             # {{{ Build actions
48 14         29 my $action = {};
49 14         31 for my $key (keys %config) {
50 21         30 my $conf = $config{$key};
51 21         32 my $type = $conf->{type};
52             # {{{ Balanced
53 21 100       88 if($type eq 'balanced') {
    100          
    100          
    100          
    50          
54             $action->{$conf->{open}} = {
55             type => $type,
56             name => $key,
57 15     15   31 action => sub { $state->{$key}++ },
58 4         32 ignore_in => $conf->{ignore_in},
59             };
60             $action->{$conf->{close}} = {
61             type => $type,
62             name => $key,
63 15     15   31 action => sub { --$state->{$key} },
64 4         45 ignore_in => $conf->{ignore_in},
65             };
66             }
67             # }}}
68             # {{{ Unbalanced
69             elsif($type eq 'unbalanced') {
70             $action->{$conf->{open}} = {
71             type => $type,
72             name => $key,
73 12     12   23 action => sub { $state->{$key} = 1 },
74 2         15 ignore_in => $conf->{ignore_in},
75             };
76             $action->{$conf->{close}} = {
77             type => $type,
78             name => $key,
79 8     8   21 action => sub { $state->{$key} = 0 },
80 2         18 ignore_in => $conf->{ignore_in},
81             };
82             }
83             # }}}
84             # {{{ To-EOL
85             elsif($type eq 'to-eol') {
86 5         32 $action->{$conf->{open}} = {
87             type => $type,
88             name => $key,
89             ignore_in => $conf->{ignore_in} },
90             }
91             # }}}
92             # {{{ Toggle
93             elsif($type eq 'toggle') {
94             $action->{$conf->{open}} = {
95             type => $type,
96             name => $key,
97 34     34   82 action => sub { $state->{$key} = !$state->{$key} },
98 5         40 ignore_in => $conf->{ignore_in},
99             };
100             }
101             # }}}
102             # {{{ Escape
103             elsif($type eq 'escape') {
104 5         31 $action->{$conf->{open}} = {
105             type => $type,
106             name => $key,
107             ignore_in => $conf->{ignore_in},
108             };
109             }
110             # }}}
111             }
112             # }}}
113             # {{{ Build the closure
114             return (
115             $state,
116             sub {
117 75     75   174 my $input = shift;
118             # {{{ Main loop
119 75         93 my $cur_char = 0;
120 75         75 my $escape = 0;
121 75         277 SPLIT: for my $char(split //,$input) {
122 580         523 $cur_char++;
123 580 100       1136 next unless exists $action->{$char}; # Skip non-metacharacters
124             # {{{ Handle meta characters
125             # Escape characters simply suppress the meta nature of the next
126             # character to come along.
127             #
128 136 100       387 if($escape == 1) {
    100          
129 16         18 $escape = 0;
130 16         25 next;
131             }
132             elsif($action->{$char}{type} eq 'escape') {
133 17         16 $escape = 1;
134 17         27 next;
135             }
136             # }}}
137             # {{{ Handle comments
138             # Effectively skip to the end if a comment to the end of line is
139             # encountered, unless the comment should be ignored.
140             #
141 103 100 66     318 if($action->{$char}{type} eq 'to-eol' and
142             $action->{$char}{name} eq 'comment') {
143 15         19 for(@{$action->{$char}{ignore_in}}) {
  15         40  
144 3 100       12 next SPLIT if $state->{$_} > 0;
145             }
146 14         25 last;
147             }
148             # }}}
149             # {{{ Handle ignore_in tags
150 88         99 for(@{$action->{$char}{ignore_in}}) {
  88         207  
151 8 100       26 next SPLIT if $state->{$_} == 1;
152             }
153 84         197 $action->{$char}{action}->();
154             # }}}
155             }
156             # }}}
157             # {{{ The string is balanced only if all states are zero.
158 75         222 for my $key (keys %$state) {
159 62 100       161 next if $state->{$key} == 0;
160 12         53 return 0;
161             }
162 63         925 return 1;
163             # }}}
164             }
165 14         109 );
166             # }}}
167             }
168             # }}}
169              
170             1;
171             __END__