File Coverage

blib/lib/Lingua/KAZ/Num2Word.pm
Criterion Covered Total %
statement 49 61 80.3
branch 24 32 75.0
condition 17 41 41.4
subroutine 10 11 90.9
pod 3 3 100.0
total 103 148 69.5


line stmt bran cond sub pod time code
1             # For Emacs: -*- mode:cperl; eval: (folding-mode 1); coding:utf-8 -*-
2              
3             package Lingua::KAZ::Num2Word;
4             # ABSTRACT: Number to word conversion in Kazakh
5              
6 1     1   108158 use 5.16.0;
  1         2  
7 1     1   4 use utf8;
  1         2  
  1         10  
8 1     1   20 use warnings;
  1         2  
  1         46  
9              
10             # {{{ use block
11              
12 1     1   5 use Carp;
  1         1  
  1         65  
13 1     1   439 use Export::Attrs;
  1         12683  
  1         8  
14 1     1   920 use Readonly;
  1         5525  
  1         605  
15              
16             # }}}
17             # {{{ variable declarations
18              
19             my Readonly::Scalar $COPY = 'Copyright (c) PetaMem, s.r.o. 2002-present';
20             our $VERSION = '0.2603300';
21              
22             # }}}
23              
24             # {{{ num2kaz_cardinal convert number to text
25              
26             sub num2kaz_cardinal :Export {
27 52     52 1 223115 my $positive = shift;
28              
29 52 50 33     626 croak 'You should specify a number from interval [0, 999_999_999]'
      33        
      33        
30             if !defined $positive
31             || $positive !~ m{\A\d+\z}xms
32             || $positive < 0
33             || $positive > 999_999_999;
34              
35 52         232 my @ones = ('нөл', 'бір', 'екі', 'үш', 'төрт', 'бес', 'алты', 'жеті', 'сегіз', 'тоғыз');
36 52         143 my @tens = ('он', 'жиырма', 'отыз', 'қырық', 'елу', 'алпыс', 'жетпіс', 'сексен', 'тоқсан');
37              
38 52 100 66     233 return $ones[$positive] if ($positive >= 0 && $positive < 10); # 0 .. 9
39 37 100 66     218 return $tens[$positive / 10 - 1] if ($positive >= 10 && $positive < 100 && $positive % 10 == 0); # 10,20,..,90
      100        
40              
41 31         53 my $out;
42             my $remain;
43              
44 31 100 66     204 if ($positive > 9 && $positive < 100) { # 11 .. 99
    100 66        
    100 66        
    100 33        
    50          
45 11         27 my $ten_idx = int($positive / 10);
46 11         15 $remain = $positive % 10;
47              
48 11         33 $out = $tens[$ten_idx - 1] . ' ' . $ones[$remain];
49             }
50             elsif ($positive == 100) { # 100
51 2         6 $out = 'жүз';
52             }
53             elsif ($positive > 100 && $positive < 1000) { # 101 .. 999
54 9         35 my $hun_idx = int($positive / 100);
55 9         14 $remain = $positive % 100;
56              
57 9 100       27 $out = $hun_idx == 1 ? 'жүз' : $ones[$hun_idx] . ' жүз';
58 9 100       35 $out .= $remain ? ' ' . num2kaz_cardinal($remain) : '';
59             }
60             elsif ($positive >= 1000 && $positive < 1_000_000) { # 1000 .. 999_999
61 7         21 my $tho_idx = int($positive / 1000);
62 7         14 $remain = $positive % 1000;
63              
64 7 100       20 $out = $tho_idx == 1 ? 'мың' : num2kaz_cardinal($tho_idx) . ' мың';
65 7 100       23 $out .= $remain ? ' ' . num2kaz_cardinal($remain) : '';
66             }
67             elsif ($positive >= 1_000_000 && $positive < 1_000_000_000) { # 1_000_000 .. 999_999_999
68 2         7 my $mil_idx = int($positive / 1_000_000);
69 2         3 $remain = $positive % 1_000_000;
70              
71 2         6 $out = num2kaz_cardinal($mil_idx) . ' миллион';
72 2 100       5 $out .= $remain ? ' ' . num2kaz_cardinal($remain) : '';
73             }
74              
75 31         141 return $out;
76 1     1   10 }
  1         2  
  1         10  
77              
78             # }}}
79              
80             # {{{ num2kaz_ordinal convert number to ordinal text
81              
82             sub num2kaz_ordinal :Export {
83 0     0 1 0 my $number = shift;
84              
85 0 0 0     0 croak 'You should specify a number from interval [1, 999_999_999]'
      0        
      0        
86             if !defined $number
87             || $number !~ m{\A\d+\z}xms
88             || $number < 1
89             || $number > 999_999_999;
90              
91 0         0 my $cardinal = num2kaz_cardinal($number);
92              
93             # Kazakh ordinals: cardinal + suffix determined by vowel harmony.
94             # Cyrillic vowels: а, е, ә, і, о, ө, ұ, ү, ы
95             # After consonant: -(ы)ншы / -(і)нші / -(ұ)ншы / -(ү)нші
96             # After vowel: -ншы / -нші / -ншы / -нші
97              
98 0         0 my $last_vowel = q{};
99 0 0       0 if ($cardinal =~ m{([аеәіоөұүы])[^аеәіоөұүы]*\z}xms) {
100 0         0 $last_vowel = $1;
101             }
102              
103 0         0 my $ends_in_vowel = $cardinal =~ m{[аеәіоөұүы]\z}xms;
104              
105 0         0 my %suffix_v = ( # after vowel
106             'а' => 'ншы', 'ы' => 'ншы',
107             'о' => 'ншы', 'ұ' => 'ншы',
108             'е' => 'нші', 'і' => 'нші',
109             'ә' => 'нші', 'ө' => 'нші', 'ү' => 'нші',
110             );
111 0         0 my %suffix_c = ( # after consonant
112             'а' => 'ыншы', 'ы' => 'ыншы',
113             'о' => 'ыншы', 'ұ' => 'ыншы',
114             'е' => 'інші', 'і' => 'інші',
115             'ә' => 'інші', 'ө' => 'інші', 'ү' => 'інші',
116             );
117              
118 0 0       0 my $suffix_table = $ends_in_vowel ? \%suffix_v : \%suffix_c;
119 0   0     0 my $suffix = $suffix_table->{$last_vowel} // 'інші';
120              
121 0         0 return $cardinal . $suffix;
122 1     1   825 }
  1         3  
  1         4  
123              
124             # }}}
125              
126             # {{{ capabilities declare supported features
127              
128             sub capabilities {
129             return {
130 1     1 1 645 cardinal => 1,
131             ordinal => 1,
132             };
133             }
134              
135             # }}}
136             1;
137              
138             __END__