File Coverage

blib/lib/Business/UDC/Tokenizer.pm
Criterion Covered Total %
statement 84 88 95.4
branch 32 34 94.1
condition n/a
subroutine 9 9 100.0
pod 0 1 0.0
total 125 132 94.7


line stmt bran cond sub pod time code
1             package Business::UDC::Tokenizer;
2              
3 13     13   122769 use base qw(Exporter);
  13         39  
  13         1852  
4 13     13   89 use strict;
  13         40  
  13         382  
5 13     13   59 use warnings;
  13         23  
  13         780  
6              
7 13     13   6816 use Error::Pure qw(err);
  13         127308  
  13         341  
8 13     13   963 use Readonly;
  13         34  
  13         674  
9 13     13   6502 use Unicode::UTF8 qw(decode_utf8);
  13         8894  
  13         26078  
10              
11             Readonly::Array our @EXPORT_OK => qw(tokenize);
12              
13             our $VERSION = 0.08;
14              
15             sub tokenize {
16 113     113 0 404166 my ($input) = @_;
17 113         206 my @tokens;
18 113         426 my $left_double_quote = decode_utf8('โ€œ');
19 113         288 my $right_double_quote = decode_utf8('โ€');
20 113         1246 my $time_quote = qr/"|''|$left_double_quote|$right_double_quote/;
21              
22 113         472 pos($input) = 0;
23              
24 113         457 while (pos($input) < length($input)) {
25 324         676 my $start = pos($input);
26              
27 324 100       1044 if ($input =~ /\G( +(?=\p{L})\p{L}(?:[\p{L}\p{N}._#,]|\+(?! )|\/(?! )|-(?!\d)| +(?=[\p{L}\p{N}._]))* *)/gcu) {
28 5         21 _push_token(\@tokens, 'ALPHA_SPEC', $1, $start, 1);
29 5         18 next;
30             }
31              
32 319 100       4102 if ($input =~ /\G( +(?=($time_quote)(?:(?!$time_quote)[\s\S])*\p{L})(?:$time_quote)(?:(?!$time_quote)[\s\S])*$time_quote *)/gcu) {
33 3         14 _push_token(\@tokens, 'ALPHA_SPEC', $1, $start, 1);
34 3         10 next;
35             }
36              
37 316 100       1200 if ($input =~ /\G(\s+)/gc) {
38 12         35 _push_token(\@tokens, 'WHITESPACE', $1, $start, 1);
39 12         24 next;
40             }
41              
42 304 100       1120 if ($input =~ /\G(\d+(?:[\.,]\d+)*)/gc) {
43 120         412 _push_token(\@tokens, 'NUMBER', $1, $start);
44 120         465 next;
45             }
46              
47 184 100       459 if ($input =~ /\G(\.\d+(?:\.\d+)*)/gc) {
48 9         29 _push_token(\@tokens, 'AUX_DOT', $1, $start);
49 9         60 next;
50             }
51              
52 175 100       386 if ($input =~ /\G(\[)/gc) {
53 4         15 _push_token(\@tokens, 'LBRACK', $1, $start);
54 4         13 next;
55             }
56              
57 171 100       373 if ($input =~ /\G(\])/gc) {
58 7         26 _push_token(\@tokens, 'RBRACK', $1, $start);
59 7         19 next;
60             }
61              
62 164 100       498 if ($input =~ /\G([:+\/])/gc) {
63 37         118 _push_token(\@tokens, 'OP', $1, $start);
64 37         104 next;
65             }
66              
67 127 100       383 if ($input =~ /\G(-\d+(?:\.\d+)*)/gc) {
68 22         77 _push_token(\@tokens, 'FORM', $1, $start);
69 22         70 next;
70             }
71              
72 105 100       361 if ($input =~ /\G(\([^)]+\))/gc) {
73 41         153 _push_token(\@tokens, 'AUX_GROUP', $1, $start, 1);
74 41         113 next;
75             }
76              
77 64 100       891 if ($input =~ /\G(($time_quote)(?:(?!$time_quote)[\s\S])*$time_quote)/gc) {
78 13         35 my $value = $1;
79 13 50       50 if ($value =~ /\p{L}/u) {
80 0         0 _push_token(\@tokens, 'ALPHA_SPEC', $value, $start, 1);
81             } else {
82 13         35 _push_token(\@tokens, 'AUX_TIME', $value, $start);
83             }
84 13         43 next;
85             }
86              
87 51 100       173 if ($input =~ /\G(=+(?:[A-Za-z]+|\d+(?:\.\d+)*))/gc) {
88 4         18 _push_token(\@tokens, 'AUX_LANG', $1, $start);
89 4         13 next;
90             }
91              
92 47 100       282 if ($input =~ /\G(\p{L}(?:[\p{L}\p{N}._#,]|\+(?! )|\/(?! )|-(?!\d)| +(?=[\p{L}\p{N}._]))* *)/gcu) {
93 19         73 _push_token(\@tokens, 'ALPHA_SPEC', $1, $start, 1);
94 19         62 next;
95             }
96              
97 28         91 my $a = decode_utf8('โ€™');
98 28         63 my $acute = decode_utf8('ยด');
99 28 100       405 if ($input =~ /\G((?:'|`|'|$a|$acute)\d+(?:\.\d+)*)/gc) {
100 25         87 _push_token(\@tokens, 'APOS_AUX', $1, $start);
101 25         80 next;
102             }
103              
104 3         9 my $bad = substr($input, $start, 20);
105 3         17 err "Unrecognized input near '$bad'.",
106             'position' => $start,
107             ;
108             }
109              
110 110         787 return \@tokens;
111             }
112              
113             sub _check_whitespace {
114 241     241   551 my ($value, $start) = @_;
115              
116 241 50       922 if ($value =~ /^(.*?)\s/s) {
117 0         0 my $ws_pos = length($1);
118 0         0 my $char = substr($value, $ws_pos, 1);
119 0         0 err "Whitespace is not allowed in UDC string.",
120             'position' => $start + $ws_pos,
121             'character' => $char,
122             ;
123             }
124              
125 241         501 return;
126             }
127              
128             sub _push_token {
129 321     321   1303 my ($tokens_ar, $type, $value, $start, $allow_whitespace) = @_;
130              
131 321 100       831 if (! $allow_whitespace) {
132 241         549 _check_whitespace($value, $start);
133             }
134              
135 321         551 push @{$tokens_ar}, {
  321         1378  
136             type => $type,
137             value => $value,
138             pos => $start,
139             };
140              
141 321         633 return;
142             }
143              
144              
145             1;
146              
147             __END__