File Coverage

blib/lib/Lingua/IND/Words2Nums.pm
Criterion Covered Total %
statement 91 132 68.9
branch 24 48 50.0
condition 12 28 42.8
subroutine 14 17 82.3
pod 10 10 100.0
total 151 235 64.2


line stmt bran cond sub pod time code
1             # For Emacs: -*- mode:cperl; eval: (folding-mode 1) -*-
2              
3             package Lingua::IND::Words2Nums;
4             # ABSTRACT: convert Indonesian verbage to number.
5              
6 1     1   125942 use 5.16.0;
  1         4  
7 1     1   6 use utf8;
  1         2  
  1         13  
8 1     1   52 use warnings;
  1         4  
  1         75  
9              
10             # {{{ use block
11              
12 1     1   689 use Export::Attrs;
  1         12463  
  1         10  
13              
14             # }}}
15             # {{{ variables declaration
16             our $VERSION = '0.2603300';
17              
18             our %Digits = (
19             nol => 0, kosong => 0,
20             se => 1, satu => 1,
21             dua => 2,
22             tiga => 3,
23             empat => 4,
24             lima => 5,
25             enam => 6,
26             tujuh => 7,
27             delapan => 8,
28             sembilan => 9
29             );
30              
31             our %Mults = (
32             puluh => 1e1,
33             ratus => 1e2,
34             ribu => 1e3,
35             juta => 1e6,
36             milyar => 1e9, milyard => 1e9, miliar => 1e9, miliard => 1e9,
37             triliun => 1e12, trilyun => 1e12
38             );
39              
40             our %Words = (
41             %Digits,
42             %Mults,
43             belas => 0
44             );
45              
46             our $Neg_pat = '(?:negatif|min|minus)';
47             our $Exp_pat = '(?:(?:di)?kali(?:kan)? sepuluh pangkat)';
48             our $Dec_pat = '(?:koma|titik)';
49              
50             # }}}
51              
52             ### public subs
53             # {{{ words2nums
54              
55             sub words2nums :Export {
56 6     6 1 224213 my @a = @_;
57 6         23 return w2n1(@a);
58 1     1   297 }
  1         3  
  1         14  
59              
60             # }}}
61             # {{{ words2nums
62              
63             sub words2nums_simple :Export {
64 0     0 1 0 my @a = @_;
65 0         0 return w2n5(@a);
66 1     1   337 }
  1         2  
  1         4  
67              
68             # }}}
69              
70             ### private subs
71              
72             # for debugging
73             our $DEBUG = 0;
74 66 50   66 1 141 sub hmm___ {my @a = @_; print "(", (caller 1)[3], ") Hmm, ", @a if $DEBUG; return; }
  66         149  
  66         124  
75              
76             # {{{ handle exponential
77             sub w2n1 {
78 6   100 6 1 27 my $words = shift // '';
79 6         15 $words = lc $words;
80 6         13 my ($num1, $num2);
81              
82 6 50       73 if( $words =~ /(.+)\b$Exp_pat\b(.+)/ ) {
83 0         0 hmm___ "it's an exponent.\n";
84 0         0 $num1 = w2n2($1);
85 0         0 $num2 = w2n2($2);
86 0         0 hmm___ "\$num1 is $num1, \$num2 is $num2\n";
87 0 0 0     0 not defined $num1 or not defined $num2 and return;
88 0         0 return $num1 * 10 ** $num2
89             } else {
90 6         18 hmm___ "not an exponent.\n";
91 6         18 $num1 = w2n2($words);
92 6 100       21 not defined $num1 and return;
93 4         11 hmm___ "\$num1 = $num1\n";
94 4         20 return $num1
95             }
96             }
97              
98             # }}}
99             # {{{ handle negative
100              
101             sub w2n2 {
102 6     6 1 42 my $words = lc shift;
103 6         25 my $num1;
104              
105 6 50       159 if( $words =~ /^[\s\t]*$Neg_pat\b(.+)/ ) {
106 0         0 hmm___ "it's negative.\n";
107 0         0 $num1 = -w2n3($1);
108 0 0       0 not defined $num1 and return;
109 0         0 hmm___ "\$num1 = $num1\n";
110 0         0 return $num1
111             } else {
112 6         20 hmm___ "it's not negative.\n";
113 6         15 $num1 = w2n3($words);
114 6 100       16 not defined $num1 and return;
115 4         13 hmm___ "\$num1 = $num1\n";
116 4         11 return $num1
117             }
118             }
119              
120             # }}}
121             # {{{ handle decimal
122              
123             sub w2n3 {
124 6     6 1 13 my $words = lc shift;
125 6         12 my ($num1, $num2);
126              
127 6 50       142 if( $words =~ /(.+)\b$Dec_pat\b(.+)/ ) {
128 0         0 hmm___ "it has decimals.\n";
129 0         0 $num1 = w2n4($1);
130 0         0 $num2 = w2n5($2);
131 0 0 0     0 not defined $num1 or not defined $num2 and return;
132 0         0 hmm___ "\$num1 is $num1, \$num2 is $num2\n";
133 0         0 return $num1 + "0.".$num2
134             } else {
135 6         18 hmm___ "it's an integer.\n";
136 6         13 $num1 = w2n4($words);
137 6 100       19 not defined $num1 and return;
138 4         15 hmm___ "\$num1 is $num1\n";
139 4         13 return $num1
140             }
141             }
142              
143             # }}}
144             # {{{ handle words before decimal (e.g, 'seratus dua puluh tiga', ...)
145              
146             sub w2n4 {
147 6     6 1 22 my @words = &split_it( lc shift );
148 6         30 my ($num, $mult);
149 6         11 my $seen_digits = 0;
150 6         11 my ($aa, $subtot, $tot);
151 6         12 my @nums = ();
152              
153 6 100 100     44 (defined $words[0] and $words[0] eq 'ERR') and return;
154 4         22 hmm___ "the words are @words.\n";
155              
156 4         10 for my $w (@words) {
157 14 100       39 if( defined $Digits{$w} ) { # digits (satuan)
    50          
158 8         27 hmm___ "saw a digit: $w.\n";
159 0         0 $seen_digits and do { push @nums, ((10 * (pop @nums)) + $Digits{$w}) }
160 8 50 33     22 or do { push @nums, $Digits{$w} ; $seen_digits = 1 }
  8         16  
  8         17  
161             }
162              
163             elsif( $w eq 'belas' ) { # special case, teens (belasan)
164 0         0 hmm___ "saw a teen: $w.\n";
165 0 0       0 return unless $seen_digits ; # (salah penulisan belasan)
166 0         0 push @nums, 10 + pop @nums;
167 0         0 $seen_digits = 0;
168             }
169              
170             else{ # must be a multiplier
171 6         20 hmm___ "saw a multiplier: $w.\n";
172 6 50       16 return unless @nums ; # (salah penulisan puluhan/pengali)
173              
174 6         10 $a = 0 ; $subtot = 0;
  6         21  
175 10         17 do { $aa = pop @nums ; $subtot += $aa }
  10         50  
176 6   100     26 until ( $aa > $Mults{$w} || !@nums );
177              
178 6 100       17 if( $aa > $Mults{$w} ) { push @nums, $aa; $subtot -= $aa }
  4         7  
  4         15  
179 6         15 push @nums, $Mults{$w}*$subtot;
180 6         12 $seen_digits = 0;
181             }
182             }
183              
184             # calculate total
185 4         13 $tot = 0;
186 4         12 while( @nums ){ $tot += shift @nums }
  8         19  
187              
188 4         13 return $tot;
189             }
190              
191             # {{{ handle words after decimal (simple with no 'belas', 'puluh', 'ratus', ...)
192             sub w2n5 {
193 0   0 0 1 0 my $words = shift // '';
194 0         0 my @words = &split_it( lc $words );
195 0         0 my ($num, $mult);
196              
197 0 0 0     0 (defined $words[0] and $words[0] eq 'ERR') and return;
198              
199 0         0 $num = 0;
200 0         0 $mult = 1;
201 0         0 for my $w (reverse @words) {
202 0 0       0 not defined $Digits{$w} and return;
203 0         0 $num += $Digits{$w}*$mult;
204 0         0 $mult *= 10;
205             }
206              
207 0         0 return $num;
208             }
209              
210             # }}}
211             # {{{ split string into array of words. also splits 'sepuluh' -> (se, puluh), 'tigabelas' -> (tiga, belas), etc.
212              
213             sub split_it {
214 6     6 1 14 my $words = lc shift;
215 6         10 my @words = ();
216              
217 6         43 for my $w ($words =~ /\b(\w+)\b/g) {
218 14         47 hmm___ "saw $w.\n";
219 14 100 66     128 if( $w =~ /^se(.+)$/ and defined $Words{$1} ) {
    50 33        
    100          
220 2         10 hmm___ "i should split $w.\n";
221 2         7 push @words, 'se', $1 }
222             elsif( $w =~ /^(.+)(belas|puluh|ratus|ribu|juta|mil[iy]ard?|tril[iy]un)$/ and defined $Words{$1} ) {
223 0         0 hmm___ "i should split $w.\n";
224 0         0 push @words, $1, $2 }
225             elsif( defined $Words{$w} ) {
226 10         27 push @words, $w }
227             else {
228 2         11 hmm___ "i don't recognize $w.\n";
229 2         6 unshift @words, 'ERR';
230 2         5 last }
231             }
232              
233 6         26 return @words;
234             }
235              
236             # }}}
237             # {{{ ordinal2cardinal convert ordinal text to cardinal text
238              
239             sub ordinal2cardinal :Export {
240 0   0 0 1   my $input = shift // return;
241              
242             # Indonesian ordinals: prefix "ke" to cardinal.
243             # Special: "pertama" (1st) → "satu"
244 0 0         return 'satu' if $input eq 'pertama';
245              
246             # Regular: strip "ke" prefix (e.g. "kedua" → "dua", "ketiga" → "tiga")
247 0 0         $input =~ s{\Ake}{}xms and return $input;
248              
249 0           return; # not an ordinal
250 1     1   2181 }
  1         2  
  1         5  
251              
252             # }}}
253              
254             1;
255             __END__