File Coverage

blib/lib/Parse/Tinymush.pm
Criterion Covered Total %
statement 156 202 77.2
branch 60 100 60.0
condition 25 45 55.5
subroutine 19 21 90.4
pod 2 12 16.6
total 262 380 68.9


line stmt bran cond sub pod time code
1             package Parse::Tinymush;
2              
3 1     1   9685 use 5.006;
  1         4  
  1         41  
4 1     1   5 use strict;
  1         2  
  1         34  
5 1     1   5 use warnings;
  1         11  
  1         50  
6              
7             our $VERSION = '1.01';
8              
9 1     1   6 use constant FN_VARARG => -1;
  1         1  
  1         87  
10              
11 1     1   6 use constant CODE_REF => 0;
  1         2  
  1         45  
12 1     1   5 use constant ARG_COUNT => 1;
  1         1  
  1         37  
13 1     1   5 use constant FNC_FLAGS => 2;
  1         2  
  1         41  
14              
15 1     1   4 use constant FNC_NO_FLAGS => 0;
  1         2  
  1         36  
16 1     1   5 use constant FNC_PASS_NAME => 1;
  1         1  
  1         2754  
17              
18             my $options = {
19             debug => 0,
20             space_compresion => 1,
21             };
22              
23             sub new {
24 1     1 1 471 my ($class, %args) = @_;
25              
26 1   50     14 my $self = {
      50        
27             brace_depth => 0,
28             func_depth => 0,
29             functions => $args{functions} || {},
30             options => $options,
31             output => '',
32             string => '',
33             temp => '',
34             variables => $args{variables} || {},
35             };
36              
37 1 50       4 if ( exists $args{options} ) {
38 0         0 foreach my $key ( keys %{ $args{options} } ) {
  0         0  
39 0 0       0 return if !exists $self->{options}->{$key};
40 0         0 $self->{options}->{$key} = $args{options}->{$key};
41             }
42             }
43              
44 1         3 bless $self, $class;
45             }
46              
47             sub pop {
48 122     122 0 148 my ($self, $count) = @_;
49 122 50       246 $count = 1 if !defined $count;
50 122 100       254 $count = length($self->{string}) if $count > length($self->{string});
51              
52 122         186 my $popped = substr($self->{string}, 0, $count);
53 122         163 substr($self->{string}, 0, $count) = '';
54              
55 122         297 return $popped;
56             }
57              
58             sub peek {
59 0     0 0 0 my ($self, $count) = @_;
60 0 0       0 $count = 1 if !defined $count;
61 0 0       0 $count = length($self->{string}) if $count > length($self->{string});
62              
63 0         0 my $peeked = substr($self->{string}, 0, $count);
64              
65 0         0 return $peeked;
66             }
67              
68             sub push {
69 6     6 0 11 my ($self, $string) = @_;
70              
71 6         13 $self->{string} = $string . $self->{string};
72             }
73              
74             sub temp : lvalue {
75 117     117 0 244 shift->{temp};
76             }
77              
78             sub flush {
79 33     33 0 47 my ($self, $var) = @_;
80              
81 33 50       68 if ( $self->{options}->{debug} ) {
82 0         0 print STDERR "Flushing: $self->{temp}\n";
83             }
84              
85 33 100       53 if ( $var ) {
86 16         26 $$var .= $self->{temp};
87             } else {
88 17         28 $self->{output} .= $self->{temp};
89             }
90 33         53 $self->{temp} = '';
91             }
92              
93             sub output : lvalue {
94 21     21 0 89 shift->{output};
95             }
96              
97             sub eval {
98 0     0 0 0 my ($self, $string) = @_;
99              
100 0         0 $self->parse($string);
101             }
102              
103             sub parse {
104 8     8 1 191 my ($self, $string) = @_;
105 8 50 33     40 return "" if !defined($string) || length($string) == 0;
106 8         18 $self->output = '';
107 8         18 $self->temp = '';
108 8         9 $self->{func_depth} = 0;
109 8         10 $self->{brace_depth} = 0;
110 8         12 $self->{string} = $string;
111              
112 8         10 my $current = '';
113 8         8 my $previous = '';
114              
115 8         8 OUTER: while (1) {
116 52         103 $current = $self->pop;
117 52 100 66     199 last if (!defined $current || length($current) == 0);
118              
119             SWITCH: {
120 44 100       44 if ( defined(my $parse = $self->parse_character($current, $previous)) ) {
  44         82  
121 8         18 $self->temp .= $parse;
122 8 100       28 if ( $parse =~ /\s/o ) {
123 4         8 $self->flush;
124             }
125 8         13 last SWITCH;
126             }
127              
128 36 50       63 if ( $current eq '[' ) {
129 0         0 $self->flush;
130 0         0 $self->temp .= $self->parse_function;
131 0 0       0 if ( $self->peek eq ']' ) {
132 0         0 $self->pop;
133             }
134 0         0 last SWITCH;
135             }
136              
137 36 100 66     74 if ( $current eq '(' && $self->output eq '' ) {
138 5         10 $self->push($self->temp . '(');
139 5         10 $self->temp = '';
140 5         10 $self->temp = $self->parse_function;
141 5         9 $self->flush;
142 5         7 last SWITCH;
143             }
144              
145 31         49 $self->temp .= $current;
146             }
147              
148 44         54 $previous = $current;
149             }
150 8         33 $self->flush;
151              
152 8         17 return $self->output;
153             }
154              
155             sub parse_variable {
156 6     6 0 6 my ($self, $char) = @_;
157              
158 6 50       19 if ( defined(my $variable = $self->{variables}->{$char}) ) {
159 6 100 33     31 if ( ref($variable) eq 'CODE' ) {
    50          
    50          
    50          
    50          
160 3         16 return $variable->($char, $self);
161             } elsif ( ref($variable) eq 'ARRAY' ) {
162 0         0 return $variable->[$char];
163             } elsif ( ref($variable) eq 'HASH' ) {
164 0         0 return $variable->{$char};
165             } elsif ( ref($variable) eq 'SCALAR' ) {
166 0         0 return $$variable;
167             } elsif ( ref($variable) && UNIVERSAL::can($variable, "eval") ) {
168 0         0 return $variable->eval($char, $self);
169             } else {
170 3         12 return $variable;
171             }
172             }
173              
174 0         0 return $char;
175             }
176              
177             sub parse_character {
178 114     114 0 164 my ($self, $char, $prev) = @_;
179              
180 114 0 0     256 if ( $self->{options}->{space_compression}
      33        
181             && ($prev =~ /\s/o
182             || ($self->{func_depth} && $prev eq ',')) ) {
183 0 0       0 return '' if ( $char =~ /\s/o );
184             }
185              
186 114 50       202 if ( $prev eq '\\' ) {
187             # The previous character was a literal \, so we take the current character
188             # with no parsing.
189 0         0 return $char;
190             }
191              
192 114 100       180 if ( $prev eq '%' ) {
193 6         12 return $self->parse_variable($char);
194             }
195              
196 108 100 100     258 if ( $self->{brace_depth} && $char eq '}' ) {
197 1         2 $self->{brace_depth}--;
198 1         4 return '';
199             }
200              
201 107 100 66     410 if ( $self->{brace_depth} || $char =~ /\s/o ) {
202 9         22 return $char;
203             }
204              
205 98 100       184 if ( $char eq '{' ) {
206 1         2 $self->{brace_depth}++;
207 1         3 return '';
208             }
209              
210 97 100       163 if ( $char eq '%' ) {
211 6         18 return '';
212             }
213              
214 91 50       146 if ( $char eq '\\' ) {
215 0         0 return '';
216             }
217              
218 91         203 return;
219             }
220              
221             sub parse_function {
222 6     6 0 8 my ($self) = @_;
223              
224 6         10 my $current = "";
225 6         6 my $previous = "";
226 6         8 my @funcargs = ( );
227 6         6 my $argc = 0;
228 6         6 my $output = undef;
229              
230 6         11 $self->{func_depth}++;
231 6 50       16 if ( $self->{options}->{debug} ) {
232 0         0 print STDERR "Function call: depth: $self->{func_depth}\n";
233 0         0 print STDERR "Function call: stack: $self->{string}\n";
234             }
235              
236 6         7 OUTER: while ( 1 ) {
237             # Get the next character from the workspace and study it for regex
238             # testing.
239 70         117 $current = $self->pop;
240 70 50 33     273 last if (!defined $current || length($current) == 0);
241 70         72 study $current;
242              
243 70         108 $output = \$funcargs[$argc];
244 70 100       168 $$output = '' if ( !defined $$output );
245             SWITCH: {
246 70 100       79 if ( defined(my $parse = $self->parse_character($current, $previous)) ) {
  70         128  
247 15         35 $self->temp .= $parse;
248 15         17 last SWITCH;
249             }
250              
251 55 100       95 if ( $current eq '(' ) {
252             # If we're currently changing the function name ($argc == 0), then this
253             # marks the end of the name and the beginning of the function arguments.
254             # So, we simply set $argc = 0 and move on.
255 7 100       16 if ( $argc == 0 ) {
256 6         13 $self->flush($output);
257 6         8 $argc++;
258 6         11 last SWITCH;
259             }
260              
261             # ( marks a function call. We check to see if OUTPUT is empty. If it is,
262             # then this is the first word of the statement and is passed as a function
263             # call. If it isn't, then we just print a ( and move on.
264 1 50       3 if ( $$output ne "" ) {
265 0         0 $self->temp .= $current;
266             } else {
267 1         3 $self->push($self->temp . "(");
268 1         2 $self->temp = '';
269 1         7 $$output .= $self->parse_function;
270             }
271 1         3 last SWITCH;
272             }
273              
274 48 100       78 if ( $current eq ',' ) {
275 4 50       6 if ( $argc == 0 ) {
276 0         0 $self->temp .= $current;
277 0         0 last SWITCH;
278             }
279              
280 4         8 $self->flush($output);
281 4         4 $argc++;
282 4         6 last SWITCH;
283             }
284              
285 44 100 66     148 if ( $current eq ')' || $current eq ']' ) {
286             # Things get sexy here. ) marks the end of the function call. But ] does
287             # as well. So, how do we handle )] or )blah]? Easy!
288             # First, send output to OUTPUT. This will REALLY hose your stack if you
289             # do )blah]. I want that (consider it a feature).
290 6         21 $self->flush($output);
291 6         11 last OUTER;
292             }
293              
294 38 50       66 if ( $current eq '[' ) {
295 0         0 $self->flush;
296 0         0 $self->temp .= $self->parse_function;
297 0 0       0 if ( $self->peek eq ']' ) {
298 0         0 $self->pop;
299             }
300 0         0 last SWITCH;
301             }
302              
303             # Default case
304 38         78 $self->temp .= $current;
305             }
306              
307 64         78 $previous = $current;
308             }
309              
310 6 50       14 if ( $self->{options}->{debug} ) {
311 0         0 print STDERR "Function return: depth: $self->{func_depth}\n";
312             }
313              
314 6         9 $self->{func_depth}--;
315              
316 6         7 my $func_info;
317 6         9 my $func_name = lc shift @funcargs;
318 6 50       18 if ( !defined($func_info = $self->{functions}->{$func_name}) ) {
319 0         0 return "#-1 FUNCTION (\U$func_name\E) NOT FOUND";
320             }
321              
322             # Clean out empty arguments
323 6   100     31 while ( $argc && $funcargs[$argc - 1] eq '' ) {
324 1         2 CORE::pop @funcargs;
325 1         3 $argc--;
326             }
327              
328 6         8 my $min_arg = FN_VARARG;
329 6         6 my $max_arg = FN_VARARG;
330 6 50       11 if ( ref($func_info->[ARG_COUNT]) eq 'ARRAY' ) {
331 0         0 ($min_arg, $max_arg) = @{ $func_info->[ARG_COUNT] };
  0         0  
332             } else {
333 6         8 $min_arg = $max_arg = $func_info->[ARG_COUNT];
334             }
335              
336 6 50 33     28 if ( $func_info->[ARG_COUNT] != FN_VARARG
      66        
337             && ($min_arg > $argc || $max_arg < $argc) ) {
338 0         0 my $error = "#-1 FUNCTION (\U$func_name\E) EXPECTS ";
339 0 0       0 if ( $min_arg == $max_arg ) {
340 0 0       0 $error .= "$min_arg ARGUMENT" . ($min_arg == 1 ? "" : "S");
341             } else {
342 0         0 $error .= "BETWEEN $min_arg AND $max_arg ARGUMENTS";
343             }
344 0         0 $error .= ", GOT $argc";
345              
346 0         0 return $error;
347             }
348              
349 6   100     21 my $flags = $func_info->[FNC_FLAGS] || 0;
350 6 100       10 if ( $flags & FNC_PASS_NAME ) {
351 1         2 unshift @funcargs, $func_name;
352             }
353 6         18 my $retval = $func_info->[CODE_REF]->(@funcargs);
354 6 50       35 if ( $self->{options}->{debug} ) {
355 0         0 print STDERR "Function return: value: $retval\n";
356             }
357              
358 6         22 return $retval;
359             }
360              
361             # Preloaded methods go here.
362              
363             1;
364             __END__