File Coverage

blib/lib/Text/CaffeinatedMarkup/PullParser.pm
Criterion Covered Total %
statement 375 483 77.6
branch 192 248 77.4
condition 5 5 100.0
subroutine 21 21 100.0
pod 2 3 66.6
total 595 760 78.2


line stmt bran cond sub pod time code
1             package Text::CaffeinatedMarkup::PullParser;
2              
3 5     5   2410 use strict;
  5         12  
  5         223  
4 5     5   26 use warnings;
  5         13  
  5         237  
5              
6             our $VERSION = 0.01;
7              
8 5     5   28 use Log::Log4perl qw(:easy);
  5         9  
  5         52  
9             Log::Log4perl->easy_init($OFF);
10              
11 5     5   12564 use Moo;
  5         45334  
  5         31  
12              
13             has 'pml' => (is=>'rw');
14             has 'pml_chars' => (is=>'rw');
15             has 'num_pml_chars' => (is=>'rw');
16              
17             has 'temporary_token' => (is=>'rw');
18             has 'temporary_token_context' => (is=>'rw');
19              
20             has 'tokens' => (is=>'rw');
21             has 'has_finished_parsing' => (is=>'rw');
22             has 'pointer' => (is=>'rw');
23             has 'state' => (is=>'rw');
24              
25             has 'token' => (is=>'rw'); # Output token
26              
27             has 'data_context' => (is=>'rw');
28              
29              
30             my $SYM_STRONG = '*';
31             my $SYM_EMPHASIS = '/';
32             my $SYM_UNDERLINE = '_';
33             my $SYM_DELETE = '-';
34              
35             my $SYM_LINK_START = '[';
36             my $SYM_LINK_END = ']';
37             my $SYM_LINK_CONTEXT_SWITCH = '|';
38              
39             my $SYM_IMAGE_START = '{';
40             my $SYM_IMAGE_END = '}';
41             my $SYM_IMAGE_CONTEXT_SWITCH = '|';
42              
43             my $SYM_NEWLINE = "\n";
44             my $SYM_SECTION_BREAK = '~';
45             my $SYM_HEADER = '#';
46              
47             my $SYM_ROW = '=';
48             my $SYM_COLUMN = '|';
49              
50             my $SYM_QUOTE = '"';
51             my $SYM_QUOTE_CONTEXT_SWITCH = '|';
52              
53             my $SYM_ESCAPE = "\\";
54              
55              
56             # ------------------------------------------------------------------------------
57              
58             sub BUILD {
59 77     77 0 155810 my ($self) = @_;
60              
61 77 100       409 die "Must supply 'pml' to ".__PACKAGE__."::new()\n\n" unless $self->pml;
62              
63             # Presplit the input before parsing
64 76         4574 $self->pml_chars([split //,$self->pml]);
65 76         599 $self->num_pml_chars( scalar @{$self->pml_chars} );
  76         244  
66              
67             # Initialise
68 76         201 $self->tokens([]);
69 76         144 $self->has_finished_parsing(0);
70              
71 76         146 $self->pointer(0);
72 76         177 $self->state('newline');
73              
74 76         241 $self->data_context([]);
75             #$self->data_context('data');
76              
77 76         1842 return;
78             }
79              
80             # ============================================================== PUBLIC API ====
81              
82             sub get_next_token {
83 415     415 1 534 my ($self) = @_;
84              
85 415 100       1142 return 0 if $self->has_finished_parsing;
86 348         702 return $self->_get_next_token;
87             }
88              
89             # ------------------------------------------------------------------------------
90              
91             sub get_all_tokens {
92 75     75 1 573 my ($self) = @_;
93              
94             # Not finished parsing yet (or started at all) so get_next_token until
95             # we run out of document! Otherwise we just return what we have.
96 75 50       227 unless ($self->has_finished_parsing) {
97 75         202 while ($self->get_next_token) {}
98             }
99 71 50       178 return wantarray ? @{$self->tokens} : $self->tokens;
  71         540  
100             }
101              
102             # ================================================================ INTERNAL ====
103              
104             sub _get_next_token {
105 348     348   428 my ($self) = @_;
106              
107 348         816 while(!$self->token) {
108 6098         12173 my $state = $self->state;
109 6098         13835 my $char = $self->pml_chars->[$self->pointer];
110              
111 6098         12255 $self->_increment_pointer;
112 6098         20804 TRACE "State is '$state'";
113            
114 6098 100       42800 $char = 'EOF' unless defined $char;
115              
116 6098         19286 TRACE " Read char [$char]";
117              
118 6098 100       40828 if ($state eq 'data') {
119              
120             #$self->data_context('data'); # REMOVE
121              
122 3092 100       6111 if ($char eq $SYM_STRONG) { $self->_switch_state('strong'); next; }
  14         40  
  14         37  
123 3078 100       6307 if ($char eq $SYM_EMPHASIS) { $self->_switch_state('emphasis'); next; }
  9         21  
  9         23  
124 3069 100       9919 if ($char eq $SYM_UNDERLINE){ $self->_switch_state('underline'); next; }
  8         18  
  8         21  
125 3061 100       5782 if ($char eq $SYM_DELETE) { $self->_switch_state('delete'); next; }
  10         27  
  10         30  
126              
127 3051 100       5740 if ($char eq $SYM_QUOTE) {
128 5         15 $self->_switch_state('quote-start');
129 5         17 next;
130             }
131              
132 3046 100       5566 if ($char eq $SYM_LINK_START) {
133 10         40 $self->_switch_state('link-start');
134 10         76 next;
135             }
136              
137 3036 100       8702 if ($char eq $SYM_IMAGE_START) {
138 17         52 $self->_switch_state('image-start');
139 17         52 next;
140             }
141              
142 3019 100       4995 if ($char eq $SYM_NEWLINE) {
143 16         83 $self->_create_token({type=>'NEWLINE'});
144 16         42 $self->_switch_state('newline');
145 16         55 next;
146             }
147              
148 3003 100       5650 if ($char eq 'EOF') {
149 53         139 $self->_switch_state('end_of_data');
150 53         199 next;
151             }
152              
153             # "Anything else"
154             # Append to string char, emitting if there was a
155             # previous token that wasn't a string.
156 2950         5555 my $previous_token = $self->_append_to_string_token( $char );
157 2950         8094 next;
158             }
159              
160             # ---------------------------------------
161              
162 3006 100       6116 if ($state eq 'strong') {
163              
164 16 50       44 if ($char eq $SYM_STRONG) {
165 16         58 $self->_create_token({type=>'STRONG'});
166 16         37 $self->_switch_to_data_state;
167 16         49 next;
168             }
169              
170 0 0       0 if ($char eq 'EOF') {
171 0         0 $self->_append_to_string_token( $SYM_STRONG );
172 0         0 $self->_switch_state('end_of_data');
173 0         0 next;
174             }
175              
176             # "Anything else"
177             # Append a star (*) to the current string token, reconsume char
178             # and switch to data state.
179 0         0 $self->_append_to_string_token( $SYM_STRONG );
180 0         0 $self->_decrement_pointer;
181 0         0 $self->_switch_to_data_state;
182 0         0 next;
183             }
184              
185             # ---------------------------------------
186              
187 2990 100       5709 if ($state eq 'emphasis') {
188              
189 11 100       26 if ($char eq $SYM_EMPHASIS) {
190 10         37 $self->_create_token({type=>'EMPHASIS'});
191 10         24 $self->_switch_to_data_state;
192 10         30 next;
193             }
194              
195 1 50       4 if ($char eq 'EOF') {
196 0         0 $self->_append_to_string_token( $SYM_EMPHASIS );
197 0         0 $self->_switch_state('end_of_data');
198 0         0 next;
199             }
200              
201             # "Anything else"
202             # Append a foreslash (/) to the current string token, reconsume char
203             # and switch to data state.
204 1         9 $self->_append_to_string_token( $SYM_EMPHASIS );
205 1         3 $self->_decrement_pointer;
206 1         6 $self->_switch_to_data_state;
207 1         3 next;
208             }
209              
210             # ---------------------------------------
211              
212 2979 100       13775 if ($state eq 'underline') {
213              
214 10 50       24 if ($char eq $SYM_UNDERLINE) {
215 10         31 $self->_create_token({type=>'UNDERLINE'});
216 10         24 $self->_switch_to_data_state;
217 10         29 next;
218             }
219              
220 0 0       0 if ($char eq 'EOF') {
221 0         0 $self->_append_to_string_token( $SYM_UNDERLINE );
222 0         0 $self->_switch_state('end_of_data');
223 0         0 next;
224             }
225              
226             # "Anything else"
227             # Append an underscore (_) to the current string token, reconsume char
228             # and switch to data state.
229 0         0 $self->_append_to_string_token( $SYM_UNDERLINE );
230 0         0 $self->_decrement_pointer;
231 0         0 $self->_switch_to_data_state;
232 0         0 next;
233             }
234              
235             # ---------------------------------------
236              
237 2969 100       5076 if ($state eq 'delete') {
238              
239 14 100       45 if ($char eq $SYM_DELETE) {
240 10         34 $self->_create_token({type=>'DEL'});
241 10         21 $self->_switch_to_data_state;
242 10         28 next;
243             }
244              
245 4 50       18 if ($char eq 'EOF') {
246 0         0 $self->_append_to_string_token( $SYM_DELETE );
247 0         0 $self->_switch_state('end_of_data');
248 0         0 next;
249             }
250              
251             # "Anything else"
252             # Append a dash (-) to the current string token, reconsume char
253             # and switch to data state.
254 4         17 $self->_append_to_string_token( $SYM_DELETE );
255 4         17 $self->_decrement_pointer;
256 4         33 $self->_switch_to_data_state;
257 4         15 next;
258             }
259              
260             # ---------------------------------------
261              
262 2955 100       5363 if ($state eq 'link-start') {
263              
264 13 50       45 if ($char eq $SYM_LINK_START) {
265 13         116 $self->_create_token({type=>'LINK',href=>'',text=>''});
266 13         39 $self->temporary_token_context('href');
267 13         27 $self->_switch_state('link-href');
268 13         47 next;
269             }
270              
271 0 0       0 if ($char eq 'EOF') {
272 0         0 $self->_append_to_string_token( $SYM_LINK_START );
273 0         0 $self->_switch_state('end_of_data');
274 0         0 next;
275             }
276              
277             # "Anything else"
278             # Append an open square bracket ([) to the current string token,
279             # reconsume char and switch to data state.
280 0         0 $self->_append_to_string_token( $SYM_LINK_START );
281 0         0 $self->_decrement_pointer;
282 0         0 $self->_switch_to_data_state;
283 0         0 next;
284             }
285              
286             # ---------------------------------------
287              
288 2942 100       5365 if ($state eq 'link-href') {
289              
290 450 100       830 if ($char eq $SYM_LINK_CONTEXT_SWITCH) {
291 10         37 $self->temporary_token_context('text');
292 10         66 $self->_switch_state('link-text');
293 10         32 next;
294             }
295 440 100       769 if ($char eq $SYM_LINK_END) { $self->_switch_state('link-end'); next }
  3         9  
  3         9  
296            
297 437 50       754 if ($char eq 'EOF') {
298 0         0 $self->_raise_parse_error("Unexpected 'EOF' while parsing link href");
299             }
300              
301             # "Anything else"
302             # Append to open link token href
303 437 50       1417 if ($self->temporary_token->{type} eq 'LINK') {
304 437         766 $self->temporary_token->{href} .= $char;
305 437         1171 next;
306             }
307              
308             # Oops
309 0         0 $self->_raise_parse_error("Attempt to append link href data to non-link token");
310             }
311              
312             # ---------------------------------------
313              
314 2492 100       4171 if ($state eq 'link-text') {
315              
316 106 100       219 if ($char eq $SYM_LINK_END) {
317 10         24 $self->_switch_state('link-end');
318 10         33 next;
319             }
320              
321 96 50       170 if ($char eq 'EOF') {
322 0         0 $self->_raise_parse_error("Unexpected 'EOF' while parsing link text");
323             }
324              
325             # "Anything else"
326             # Append to open link token href
327 96 50       277 if ($self->temporary_token->{type} eq 'LINK') {
328 96         304 $self->temporary_token->{text} .= $char;
329 96         252 next;
330             }
331              
332             # Oops
333 0         0 $self->_raise_parse_error("Attempt to append link text data to non-link token");
334             }
335              
336             # ---------------------------------------
337              
338 2386 100       5553 if ($state eq 'link-end') {
339              
340 13 50       39 if ($char eq $SYM_LINK_END) {
341 13         42 $self->_switch_to_data_state;
342 13         38 next;
343             }
344              
345 0 0       0 if ($char eq 'EOF') {
346 0         0 $self->_raise_parse_error("Unexpected 'EOF' while parsing link end");
347             }
348              
349             # "Anything else"
350             # Append to href or text depending on context
351 0         0 my $context = $self->temporary_token_context;
352            
353 0 0       0 if ($context =~ /^(?:href|text)$/o) {
354 0         0 $self->temporary_token->{$context} .= $char;
355 0         0 next;
356             }
357              
358 0         0 $self->_raise_parse_error("Missing or bad link token context");
359             }
360              
361             # ---------------------------------------
362              
363 2373 100       10578 if ($state eq 'image-start') {
364              
365 22 50       65 if ($char eq $SYM_IMAGE_START) {
366 22         150 $self->_create_token({type=>'IMAGE',src=>'',options=>''});
367 22         65 $self->temporary_token_context('src');
368 22         50 $self->_switch_state('image-src');
369 22         71 next;
370             }
371              
372 0 0       0 if ($char eq 'EOF') {
373 0         0 $self->_append_to_string_token( $SYM_IMAGE_START );
374 0         0 $self->_switch_state('end_of_data');
375 0         0 next;
376             }
377              
378             # "Anything else"
379             # Append an open curly bracket ({}) to the current string token,
380             # reconsume char and switch to data state.
381 0         0 $self->_append_to_string_token( $SYM_IMAGE_START );
382 0         0 $self->_decrement_pointer;
383 0         0 $self->_switch_to_data_state;
384 0         0 next;
385             }
386              
387             # ---------------------------------------
388              
389 2351 100       4278 if ($state eq 'image-src') {
390              
391 205 100       428 if ($char eq $SYM_IMAGE_CONTEXT_SWITCH) { $self->_switch_state('image-options'); next }
  18         48  
  18         51  
392 187 100       316 if ($char eq $SYM_IMAGE_END) { $self->_switch_state('image-end'); next }
  4         12  
  4         12  
393            
394 183 50       325 if ($char eq 'EOF') {
395 0         0 $self->_raise_parse_error("Unexpected 'EOF' while parsing image src");
396             }
397              
398             # "Anything else"
399             # Append to open link token href
400 183 50       623 if ($self->temporary_token->{type} eq 'IMAGE') {
401 183         330 $self->temporary_token->{src} .= $char;
402 183         479 next;
403             }
404              
405             # Oops
406 0         0 $self->_raise_parse_error("Attempt to append image src data to non-image token");
407             }
408              
409             # ---------------------------------------
410              
411 2146 100       3640 if ($state eq 'image-options') {
412              
413 148 100       281 if ($char eq $SYM_IMAGE_END) {
414 18         46 $self->_switch_state('image-end');
415 18         50 next;
416             }
417              
418 130 50       225 if ($char eq 'EOF') {
419 0         0 $self->_raise_parse_error("Unexpected 'EOF' while parsing image options");
420             }
421              
422             # "Anything else"
423             # Append to open link token href
424 130 50       465 if ($self->temporary_token->{type} eq 'IMAGE') {
425 130         242 $self->temporary_token->{options} .= $char;
426 130         430 next;
427             }
428              
429             # Oops
430 0         0 $self->_raise_parse_error("Attempt to append image options data to non-image token");
431             }
432              
433             # ---------------------------------------
434              
435 1998 100       4503 if ($state eq 'image-end') {
436              
437 22 50       60 if ($char eq $SYM_IMAGE_END) {
438 22         53 $self->_switch_to_data_state;
439 22         71 next;
440             }
441              
442 0 0       0 if ($char eq 'EOF') {
443 0         0 $self->_raise_parse_error("Unexpected 'EOF' while parsing image end");
444             }
445              
446             # "Anything else"
447             # Append to src or options depending on context
448 0         0 my $context = $self->temporary_token_context;
449            
450 0 0       0 if ($context =~ /^(?:src|options)$/o) {
451 0         0 $self->temporary_token->{$context} .= $char;
452 0         0 next;
453             }
454              
455 0         0 $self->_raise_parse_error("Missing or bad image token context");
456             }
457            
458             # ---------------------------------------
459              
460 1976 100       3376 if ($state eq 'newline') {
461              
462 154 100       305 if ($char eq 'EOF') { $self->_switch_state('end_of_data'); next }
  4         14  
  4         14  
463 150 100       307 if ($char eq $SYM_NEWLINE) { $self->_create_token({type=>'NEWLINE'}); next }
  19         76  
  19         67  
464 131 50       304 if ($char eq ' ') { next }
  0         0  
465              
466 131 100       246 if ($char eq $SYM_HEADER) {
467 8         49 $self->_create_token({type=>'HEADER',level=>1});
468 8         20 $self->_switch_state('header');
469 8         25 next;
470             }
471              
472 123 100       242 if ($char eq $SYM_SECTION_BREAK) {
473 1         6 $self->_switch_state('section-break');
474 1         3 next;
475             }
476              
477 122 100       239 if ($char eq $SYM_ROW) {
478 41         106 $self->_switch_state('row');
479 41         129 next;
480             }
481              
482             # Anything else
483 81         218 $self->_switch_to_data_state;
484 81         205 $self->_decrement_pointer;
485 81         561 next;
486             }
487              
488             # ---------------------------------------
489              
490 1822 100       3459 if ($state eq 'section-break') {
491              
492 1 50       4 if ($char eq $SYM_SECTION_BREAK) {
493 1         7 $self->_create_token({type=>'SECTIONBREAK'});
494 1         6 $self->_switch_to_data_state;
495 1         4 next;
496             }
497              
498 0 0       0 if ($char eq 'EOF') {
499 0         0 $self->_append_to_string_token( $SYM_SECTION_BREAK );
500 0         0 $self->_switch_state('end_of_data');
501 0         0 next;
502             }
503            
504             # Anything else
505 0         0 $self->_append_to_string_token( $SYM_SECTION_BREAK );
506 0         0 $self->_decrement_pointer;
507 0         0 $self->_switch_to_data_state;
508 0         0 next;
509             }
510              
511             # ---------------------------------------
512              
513 1821 100       3069 if ($state eq 'header') {
514              
515 23 50       53 if ($char eq 'EOF') {
516 0         0 my $cur_level = $self->temporary_token->{level};
517 0         0 $self->_discard_token;
518 0         0 my $new_string = $SYM_HEADER x $cur_level;
519 0         0 $self->_create_token({type=>'STRING',content=>$new_string});
520 0         0 $self->_switch_state('end_of_data');
521 0         0 next;
522             }
523              
524 23 100       49 if ($char eq ' ') { next }
  8         24  
525              
526 15 100       36 if ($char eq $SYM_HEADER) {
527 7         18 $self->temporary_token->{level}++;
528 7         21 next;
529             }
530              
531             # Anything else
532 8         18 $self->_switch_state('header-text');
533 8         18 $self->_decrement_pointer;
534 8         51 next;
535             }
536              
537             # ---------------------------------------
538              
539 1798 100       3419 if ($state eq 'header-text') {
540              
541 93 100       164 if ($char eq 'EOF') {
542 4         11 $self->_switch_state('end_of_data');
543 4         12 next;
544             }
545              
546 89 100       152 if ($char eq $SYM_NEWLINE) {
547 4         12 $self->_switch_to_data_state;
548 4         15 next;
549             }
550              
551             # Anything else
552 85         179 $self->temporary_token->{text} .= $char;
553 85         237 next;
554             }
555              
556             # ---------------------------------------
557              
558 1705 100       3239 if ($state eq 'row') {
559              
560 41 50       100 if ($char eq $SYM_ROW) {
561 41         112 $self->_discard_token; # Discard previous newline
562 41         157 $self->_create_token({type=>'ROW'});
563 41         138 $self->_switch_state('row-end-state');
564 41         185 next;
565             }
566              
567 0 0       0 if ($char eq 'EOF') {
568 0         0 $self->_append_to_string_token( $SYM_ROW );
569 0         0 $self->_switch_state('end_of_data');
570 0         0 next;
571             }
572            
573             # Anything else
574 0         0 $self->_append_to_string_token( $SYM_ROW );
575 0         0 $self->_decrement_pointer;
576 0         0 $self->_switch_to_data_state;
577 0         0 next;
578             }
579              
580             # ---------------------------------------
581              
582 1664 100       3131 if ($state eq 'row-end-state') {
583              
584 41 100       93 if ($char eq $SYM_NEWLINE) {
585 30 100       72 if ($self->_get_data_context eq 'column-data') {
586 8         35 TRACE " -> Data context is 'column-data'";
587 8         56 $self->_emit_token;
588 8         28 $self->_pop_data_context;
589 8         73 $self->_pop_data_context; # Pop two levels (column then row)
590 8         62 $self->_switch_state('newline');
591             }
592             else {
593 22         61 TRACE " -> Data context other than 'column-data'";
594 22         144 $self->_push_data_context('row-data');
595 22         47 $self->_switch_state('row-data-state');
596             }
597 30         103 next;
598             }
599              
600 11 100       33 if ($char eq 'EOF') {
601 10 50       26 if ($self->_get_data_context eq 'column-data') {
602             # Oops expecting a newline. We'll be nice though and
603             # close the sequence as if it were there.
604             }
605             else {
606 0         0 $self->_discard_token;
607 0         0 $self->_create_token({type=>'STRING',content=>"$SYM_ROW$SYM_ROW"});
608             }
609 10         25 $self->_switch_state('end_of_data');
610 10         32 next;
611             }
612              
613             # Anything else
614 1         4 $self->_discard_token;
615 1         6 $self->_create_token({type=>'STRING',content=>"$SYM_ROW$SYM_ROW"});
616 1         5 $self->_decrement_pointer;
617 1         6 $self->_switch_to_data_state;
618 1         4 next;
619             }
620              
621             # ---------------------------------------
622              
623 1623 100       3750 if ($state eq 'row-data-state') {
624            
625 22 100       53 if ($char eq $SYM_COLUMN) {
626 21         49 $self->_switch_state('first-column');
627 21         60 next;
628             }
629              
630             # Anything else
631 1         8 $self->_raise_parse_error('Unexpected char at start of row data');
632             }
633              
634             # ---------------------------------------
635              
636 1601 100       2677 if ($state eq 'first-column') {
637              
638 21 100       46 if ($char eq $SYM_COLUMN) {
639 20         44 $self->_push_data_context('column-data');
640 20         75 $self->_create_token({type=>'COLUMN'});
641 20         45 $self->_switch_state('column-data');
642 20         63 next;
643             }
644              
645             # Anything else
646 1         4 $self->_raise_parse_error('Unexpected char in first column tag');
647             }
648              
649             # ---------------------------------------
650              
651 1580 100       3654 if ($state eq 'column') {
652              
653 16 100       40 if ($char eq $SYM_COLUMN) {
654 15 100       72 $self->_discard_token if $self->temporary_token->{type} eq 'NEWLINE'; # Discard previous newline
655 15         40 $self->_push_data_context('column-data');
656 15         70 $self->_create_token({type=>'COLUMN'});
657 15         38 $self->_switch_state('column-data');
658 15         50 next;
659             }
660              
661 1 50       6 if ($char eq 'EOF') {
662 1         3 $self->_raise_parse_error('Unexpected end of data in column tag');
663             }
664              
665             # Anything else
666 0         0 $self->_append_to_string_token($SYM_COLUMN);
667 0         0 $self->_decrement_pointer;
668 0         0 $self->_switch_state('column-data');
669 0         0 next;
670             }
671              
672             # ---------------------------------------
673              
674 1564 100       3281 if ($state eq 'column-data') {
675              
676 1346 100       2779 if ($char eq $SYM_COLUMN) {
677 16         433 $self->_switch_state('column');
678 16         48 next;
679             }
680              
681 1330 100       2473 if ($char eq $SYM_STRONG) { $self->_switch_state('strong'); next; }
  2         6  
  2         155  
682 1328 100       5315 if ($char eq $SYM_EMPHASIS) { $self->_switch_state('emphasis'); next; }
  2         5  
  2         5  
683 1326 100       2252 if ($char eq $SYM_UNDERLINE){ $self->_switch_state('underline'); next; }
  2         8  
  2         6  
684 1324 100       2383 if ($char eq $SYM_DELETE) { $self->_switch_state('delete'); next; }
  4         17  
  4         14  
685              
686 1320 100       2248 if ($char eq $SYM_QUOTE) {
687 1         4 $self->_switch_state('quote-start');
688 1         5 next;
689             }
690              
691 1319 100       2156 if ($char eq $SYM_LINK_START) {
692 3         16 $self->_switch_state('link-start');
693 3         13 next;
694             }
695              
696 1316 100       2523 if ($char eq $SYM_IMAGE_START) {
697 5         14 $self->_switch_state('image-start');
698 5         17 next;
699             }
700              
701 1311 100       2297 if ($char eq $SYM_NEWLINE) {
702 36         176 $self->_create_token({type=>'NEWLINE'});
703 36         99 $self->_switch_state('newline');
704 36         128 next;
705             }
706              
707 1275 100       2526 if ($char eq 'EOF') {
708 1         4 $self->_raise_parse_error('Unexpected end of data in column data');
709             }
710              
711             # Anything else
712 1274         2955 $self->_append_to_string_token($char);
713 1274         3810 next;
714             }
715              
716             # ---------------------------------------
717              
718 218 100       393 if ($state eq 'end_of_data') {
719 71         189 DEBUG "End of data reached - finish parse";
720 71         442 $self->has_finished_parsing(1);
721 71         155 $self->_emit_token;
722              
723 71 50       300 if ($self->temporary_token) {
724 0         0 TRACE "Still got temp token!";
725             }
726              
727 71         259 last;
728             }
729              
730             # ---------------------------------------
731              
732 147 100       258 if ($state eq 'quote-start') {
733 6 50       16 if ($char eq $SYM_QUOTE) {
734 6         40 $self->_create_token({type=>'QUOTE',body=>'',cite=>''});
735 6         20 $self->temporary_token_context('body');
736 6         14 $self->_switch_state('quote-body');
737 6         20 $self->_push_data_context('quote-body');
738 6         22 next;
739             }
740              
741 0 0       0 if ($char eq 'EOF') {
742 0         0 $self->_append_to_string_token('"');
743 0         0 $self->_switch_state('end_of_data');
744 0         0 next;
745             }
746              
747             # Anything else
748 0         0 $self->_append_to_string_token('"');
749 0         0 $self->_decrement_pointer;
750 0         0 $self->_switch_to_data_state;
751 0         0 next;
752             }
753              
754             # ---------------------------------------
755              
756 141 100       230 if ($state eq 'quote-end') {
757 6 50       18 if ($char eq $SYM_QUOTE) {
758 6         23 $self->_pop_data_context;
759 6         46 $self->_switch_to_data_state;
760 6         34 next;
761             }
762              
763 0 0       0 if ($char eq 'EOF') {
764 0         0 $self->_raise_parse_error("unexpected end of file in quote end sequence");
765             }
766              
767             # Anything else
768 0         0 my $context = $self->temporary_token_context;
769            
770 0 0       0 if ($context =~ /^(?:body|cite)$/o) {
771 0         0 $self->temporary_token->{$context} .= $char;
772 0         0 next;
773             }
774              
775 0         0 $self->_raise_parse_error("Missing or bad quote token context");
776             }
777              
778             # ---------------------------------------
779              
780 135 100       243 if ($state eq 'quote-body') {
781 116 50       229 if ($char eq 'EOF') {
782 0         0 $self->_raise_parse_error('unexpected end of file in quote');
783             }
784              
785 116 100       192 if ($char eq $SYM_QUOTE_CONTEXT_SWITCH) {
786 2         14 $self->temporary_token_context('cite');
787 2         10 $self->_switch_state('quote-cite');
788 2         7 next;
789             }
790              
791 114 100       190 if ($char eq $SYM_QUOTE) {
792 4         16 $self->_switch_state('quote-end');
793 4         12 next;
794             }
795              
796             # Anything else
797 110         365 $self->temporary_token->{body} .= $char;
798 110         267 next;
799             }
800              
801             # ---------------------------------------
802              
803 19 50       42 if ($state eq 'quote-cite') {
804 19 50       38 if ($char eq 'EOF') {
805 0         0 $self->_raise_parse_error('unexpected end of file in quote');
806             }
807              
808 19 100       49 if ($char eq $SYM_QUOTE) {
809 2         9 $self->_switch_state('quote-end');
810 2         8 next;
811             }
812              
813             # Anything else
814 17         40 $self->temporary_token->{cite} .= $char;
815 17         60 next;
816             }
817              
818             # ---------------------------------------
819              
820             # Shouldn't ever get here!
821 0         0 $self->_raise_parse_error("Invalid state! '$state'");
822              
823             }
824              
825 344   100     941 my $token = $self->token || 0;
826              
827 344         561 $self->token(undef);
828              
829 344         1342 return $token;
830             }
831              
832             # ------------------------------------------------------------------------------
833              
834 6098     6098   18549 sub _increment_pointer { $_[0]->pointer( $_[0]->pointer + 1) }
835 95     95   307 sub _decrement_pointer { $_[0]->pointer( $_[0]->pointer - 1); TRACE " -> Requeue char" }
  95         220  
836              
837             # ------------------------------------------------------------------------------
838              
839             # Emit the current "temporary token" if there is one.
840             # Doing this returns to the client as well as adding to the token bucket.
841             sub _emit_token {
842 347     347   466 my ($self) = @_;
843              
844 347 100       852 return unless my $token = $self->temporary_token;
845 343         375 push @{$self->tokens}, $token;
  343         844  
846              
847             # Reset the temporary token
848 343         653 $self->temporary_token(undef);
849              
850 343         1424 DEBUG " >> Emit token [ ".$token->{type}.' ]';
851              
852 343         2588 $self->token($token); # Mark the token for output;
853 343         601 return;
854             }
855              
856             # ------------------------------------------------------------------------------
857              
858             sub _raise_parse_error {
859 6     6   2431 my ($self, $msg) = @_;
860 6         31 ERROR "!!Parse error [$msg]";
861 6         138 die "Encountered parse error [$msg]\n\n";
862             }
863              
864             # ------------------------------------------------------------------------------
865              
866             sub _switch_to_data_state {
867 179     179   236 my ($self) = @_;
868 179         476 $self->_switch_state( $self->_get_data_context );
869             }
870              
871             # ------------------------------------------------------------------------------
872              
873             sub _switch_state {
874 709     709   4326 my ($self, $switch_to) = @_;
875 709         2254 TRACE " Switching to state [ $switch_to ]";
876 709         5598 $self->state($switch_to);
877             }
878              
879             # ------------------------------------------------------------------------------
880              
881             # If there's a temporary token, get rid of it
882             sub _discard_token {
883 54     54   3197 my ($self) = @_;
884 54         145 $self->temporary_token(undef);
885 54         107 $self->temporary_token_context(undef);
886 54         97 return;
887             }
888              
889             # ------------------------------------------------------------------------------
890              
891             sub _push_data_context {
892 67     67   106 my ($self, $context) = @_;
893 67         217 TRACE "Stack data context '$context'";
894 67         368 unshift @{$self->data_context}, $context
  67         248  
895             }
896              
897             sub _pop_data_context {
898 25     25   43 my ($self) = @_;
899 25         32 shift @{$self->data_context};
  25         67  
900 25         83 TRACE "Popped data context stack back to '".$self->_get_data_context."'";
901             }
902              
903 1     1   7 sub _clear_data_context { $_[0]->data_context([]) };
904              
905 250 100   250   6234 sub _get_data_context { $_[0]->data_context->[0] || 'data' }
906              
907             # ------------------------------------------------------------------------------
908              
909             # Append a given char to the current string token or, if there isn't one,
910             # create one (emitting existing tokens as appropriate)
911             #
912             # @param char character to add
913             #
914             sub _append_to_string_token {
915 4232     4232   12246 my ($self, $char) = @_;
916              
917 4232         21426 TRACE " Append [ $char ] to string token";
918              
919             # Look at the current temporary token (if there is one).
920 4232         27436 my $tmp_token = $self->temporary_token;
921              
922 4232 100 100     20380 if ($tmp_token && $tmp_token->{type} eq 'STRING') {
923 4099         10628 TRACE " -> Has existing string token";
924 4099         27478 $self->temporary_token->{content} .= $char;
925 4099         9492 return; # Nothing to return
926             }
927              
928             # Otherwise create a new token and return the previous one
929             # if there was one.
930 133         612 $self->_create_token({type=>'STRING',content=>$char});
931 133         218 return;
932             }
933              
934             # ------------------------------------------------------------------------------
935              
936             # Create a new token in the temporary store. If a token already exists
937             # there then this method returns it.
938             #
939             # @param tpken initial token
940             # @returns the old temporary token
941             # before it was replaced
942             # with the new one.
943             #
944             sub _create_token {
945 381     381   8149 my ($self, $token) = @_;
946              
947 381 100       1516 $self->_raise_parse_error("No token data passed to _create_token()") unless $token;
948              
949 380         2256 TRACE " Create new token [ ".$token->{type}." ]";
950 380         2173 my $old_temporary_token = undef;
951              
952 380 100       1120 if ($self->temporary_token) {
953 268         568 $self->_emit_token;
954             }
955              
956             # Clear any current context
957 380         754 $self->temporary_token_context(undef);
958              
959 380         678 $self->temporary_token( $token );
960 380         560 return undef;
961             }
962              
963             1;
964              
965             __END__