File Coverage

blib/lib/Lingua/SRP/Word2Num.pm
Criterion Covered Total %
statement 23 36 63.8
branch 0 6 0.0
condition 2 19 10.5
subroutine 9 10 90.0
pod 3 3 100.0
total 37 74 50.0


line stmt bran cond sub pod time code
1             # For Emacs: -*- mode:cperl; mode:folding; coding:utf-8; -*-
2              
3             package Lingua::SRP::Word2Num;
4             # ABSTRACT: Word to number conversion in Serbian
5              
6 1     1   131393 use 5.16.0;
  1         4  
7 1     1   5 use utf8;
  1         1  
  1         9  
8 1     1   18 use warnings;
  1         3  
  1         52  
9              
10             # {{{ use block
11              
12 1     1   476 use Export::Attrs;
  1         8085  
  1         5  
13 1     1   1060 use Parse::RecDescent;
  1         31320  
  1         7  
14              
15             # }}}
16             # {{{ var block
17             our $VERSION = '0.2603300';
18             my $parser = srp_numerals();
19              
20             # }}}
21              
22             # {{{ w2n convert text to number
23              
24             sub w2n :Export {
25 26   100 26 1 1152069 my $input = shift // return;
26              
27 25         271 return $parser->numeral($input);
28 1     1   97 }
  1         5  
  1         7  
29              
30             # }}}
31             # {{{ srp_numerals create parser for numerals
32              
33             sub srp_numerals {
34 1     1 1 5 return Parse::RecDescent->new(q{
35            
36              
37             numeral: mega
38             | kOhOd
39             | 'нула' { 0 }
40             | { }
41              
42             number: 'једанаест' { 11 }
43             | 'дванаест' { 12 }
44             | 'тринаест' { 13 }
45             | 'четрнаест' { 14 }
46             | 'петнаест' { 15 }
47             | 'шеснаест' { 16 }
48             | 'седамнаест' { 17 }
49             | 'осамнаест' { 18 }
50             | 'деветнаест' { 19 }
51             | 'један' { 1 }
52             | 'једна' { 1 }
53             | 'две' { 2 }
54             | 'два' { 2 }
55             | 'три' { 3 }
56             | 'четири' { 4 }
57             | 'пет' { 5 }
58             | 'шест' { 6 }
59             | 'седам' { 7 }
60             | 'осам' { 8 }
61             | 'девет' { 9 }
62             | 'десет' { 10 }
63              
64             tens: 'двадесет' { 20 }
65             | 'тридесет' { 30 }
66             | 'четрдесет' { 40 }
67             | 'педесет' { 50 }
68             | 'шездесет' { 60 }
69             | 'седамдесет' { 70 }
70             | 'осамдесет' { 80 }
71             | 'деведесет' { 90 }
72              
73             deca: tens number { $item[1] + $item[2] }
74             | tens
75             | number
76              
77             hecto: number /сто/ deca { $item[1] * 100 + $item[3] }
78             | number /сто/ { $item[1] * 100 }
79             | 'двеста' deca { 200 + $item[2] }
80             | 'двеста' { 200 }
81             | 'триста' deca { 300 + $item[2] }
82             | 'триста' { 300 }
83             | 'четиристо' deca { 400 + $item[2] }
84             | 'четиристо' { 400 }
85             | 'петсто' deca { 500 + $item[2] }
86             | 'петсто' { 500 }
87             | 'шестсто' deca { 600 + $item[2] }
88             | 'шестсто' { 600 }
89             | 'седамсто' deca { 700 + $item[2] }
90             | 'седамсто' { 700 }
91             | 'осамсто' deca { 800 + $item[2] }
92             | 'осамсто' { 800 }
93             | 'деветсто' deca { 900 + $item[2] }
94             | 'деветсто' { 900 }
95             | 'сто' deca { 100 + $item[2] }
96             | 'сто' { 100 }
97              
98             hOd: hecto
99             | deca
100              
101             kilo: hOd /хиљад[ае]?/ hOd { $item[1] * 1000 + $item[3] }
102             | hOd /хиљад[ае]?/ { $item[1] * 1000 }
103             | number /хиљад[ае]?/ hOd { $item[1] * 1000 + $item[3] }
104             | number /хиљад[ае]?/ { $item[1] * 1000 }
105             | /хиљада/ hOd { 1000 + $item[2] }
106             | /хиљада/ { 1000 }
107              
108             kOhOd: kilo
109             | hOd
110              
111             mega: hOd megas kOhOd { $item[1] * 1_000_000 + $item[3] }
112             | hOd megas { $item[1] * 1_000_000 }
113             | 'један' 'милион' kOhOd { 1_000_000 + $item[3] }
114             | 'један' 'милион' { 1_000_000 }
115             | 'милион' kOhOd { 1_000_000 + $item[2] }
116             | 'милион' { 1_000_000 }
117              
118             megas: 'милиона'
119             | 'милион'
120             });
121             }
122              
123             # }}}
124             # {{{ ordinal2cardinal convert ordinal text to cardinal text
125              
126             sub ordinal2cardinal :Export {
127 0   0 0 1   my $input = shift // return;
128              
129             # Serbian (Cyrillic) ordinals: strip gender/case suffixes, then map stems.
130             # Inflection: -и/-а/-о/-ог(а)/-ом(у)/-им (masc/fem/neut/oblique).
131              
132 0           my %irregular = (
133             'нулти' => 'нула',
134             'први' => 'један',
135             'други' => 'два',
136             'трећи' => 'три',
137             'четврти' => 'четири',
138             'пети' => 'пет',
139             'шести' => 'шест',
140             'седми' => 'седам',
141             'осми' => 'осам',
142             'девети' => 'девет',
143             'десети' => 'десет',
144             'једанаести' => 'једанаест',
145             'дванаести' => 'дванаест',
146             'тринаести' => 'тринаест',
147             'четрнаести' => 'четрнаест',
148             'петнаести' => 'петнаест',
149             'шеснаести' => 'шеснаест',
150             'седамнаести' => 'седамнаест',
151             'осамнаести' => 'осамнаест',
152             'деветнаести' => 'деветнаест',
153             'двадесети' => 'двадесет',
154             'тридесети' => 'тридесет',
155             'четрдесети' => 'четрдесет',
156             'педесети' => 'педесет',
157             'шездесети' => 'шездесет',
158             'седамдесети' => 'седамдесет',
159             'осамдесети' => 'осамдесет',
160             'деведесети' => 'деведесет',
161             'двестоти' => 'двеста',
162             'тристоти' => 'триста',
163             'четиристоти' => 'четиристо',
164             'петстоти' => 'петсто',
165             'шестстоти' => 'шестсто',
166             'седамстоти' => 'седамсто',
167             'осамстоти' => 'осамсто',
168             'деветстоти' => 'деветсто',
169             'стоти' => 'сто',
170             'хиљадити' => 'хиљада',
171             'милионти' => 'милион',
172             );
173              
174             # Compound ordinals: ALL components are ordinal forms.
175             # Normalize each word individually, then look up in the mapping.
176 0           my @words = split /\s+/, $input;
177 0           my @result;
178 0           my $matched = 0;
179              
180 0           for my $word (@words) {
181             # Strip gender/case suffixes to masculine nominative (-и)
182 0           my $norm = $word;
183 0 0 0       $norm =~ s{ога\z}{и}xms
      0        
      0        
      0        
      0        
184             or $norm =~ s{ог\z}{и}xms
185             or $norm =~ s{ому\z}{и}xms
186             or $norm =~ s{ом\z}{и}xms
187             or $norm =~ s{им\z}{и}xms
188             or $norm =~ s{а\z}{и}xms # fem: прва → први
189             or $norm =~ s{о\z}{и}xms; # neut: прво → први
190              
191 0 0         if (exists $irregular{$norm}) {
192 0           push @result, $irregular{$norm};
193 0           $matched = 1;
194             }
195             else {
196 0           push @result, $word; # pass through unchanged (connectors, etc.)
197             }
198             }
199              
200 0 0         return $matched ? join(' ', @result) : undef;
201 1     1   635 }
  1         2  
  1         4  
202              
203             # }}}
204              
205             1;
206              
207             __END__