File Coverage

blib/lib/Lingua/LTZ/Num2Word.pm
Criterion Covered Total %
statement 67 78 85.9
branch 44 60 73.3
condition 18 36 50.0
subroutine 10 12 83.3
pod 3 3 100.0
total 142 189 75.1


line stmt bran cond sub pod time code
1             # For Emacs: -*- mode:cperl; eval: (folding-mode 1); coding:utf-8 -*-
2              
3             package Lingua::LTZ::Num2Word;
4             # ABSTRACT: Number to word conversion in Luxembourgish
5              
6 1     1   141236 use 5.16.0;
  1         3  
7 1     1   4 use utf8;
  1         1  
  1         11  
8 1     1   21 use warnings;
  1         1  
  1         41  
9              
10             # {{{ use block
11              
12 1     1   3 use Carp;
  1         1  
  1         65  
13 1     1   438 use Export::Attrs;
  1         8207  
  1         5  
14 1     1   587 use Readonly;
  1         3099  
  1         410  
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             # {{{ num2ltz_cardinal convert number to text
25              
26             sub num2ltz_cardinal :Export {
27 49     49 1 255939 my $positive = shift;
28              
29 49 100 66     744 croak 'You should specify a number from interval [0, 999_999_999]'
      66        
      100        
30             if !defined $positive
31             || $positive !~ m{\A\d+\z}xms
32             || $positive < 0
33             || $positive > 999_999_999;
34              
35             # 0..12 are irregular standalone forms
36 47         258 my @tokens1 = qw(null een zwee dräi véier fënnef sechs siwen aacht néng zéng eelef zwielef);
37              
38             # tens 20..90 (index 0=20, 1=30, ...)
39 47         161 my @tokens2 = qw(zwanzeg drësseg véierzeg fofzeg sechzeg siwwenzeg achtzeg nonzeg);
40              
41             # standalone 0
42 47 100       121 return 'null' if $positive == 0;
43              
44             # standalone 1 has trailing 't'
45 46 100       115 return 'eent' if $positive == 1;
46              
47 45 100 66     193 return $tokens1[$positive] if ($positive >= 2 && $positive < 13); # 2 .. 12
48 35 100       87 return 'fofzéng' if ($positive == 15); # 15 exception
49 34 100       84 return 'siechzéng' if ($positive == 16); # 16 exception
50 33 100       89 return 'siwwenzéng' if ($positive == 17); # 17 exception
51 32 100       88 return 'uechtzéng' if ($positive == 18); # 18 exception
52 31 100       75 return 'nonzéng' if ($positive == 19); # 19 exception
53 29 100 66     129 return $tokens1[$positive-10] . 'zéng' if ($positive > 12 && $positive < 20); # 13, 14
54              
55 28         95 my $out; # string for return value construction
56             my $one_idx; # index for array
57 28         0 my $remain; # remainder
58              
59 28 100 66     160 if ($positive > 19 && $positive < 100) { # 20 .. 99
    100 66        
    100 66        
    50 33        
60 15         49 $one_idx = int ($positive / 10);
61 15         30 $remain = $positive % 10;
62              
63 15 100       34 if ($remain) {
64 14         54 my $unit = $tokens1[$remain];
65 14         37 my $tens = $tokens2[$one_idx - 2];
66 14         38 my $connector = _connector($tens);
67 14         49 $out = $unit . $connector . $tens;
68             }
69             else {
70 1         3 $out = $tokens2[$one_idx - 2];
71             }
72             }
73             elsif ($positive >= 100 && $positive < 1000) { # 100 .. 999
74 7         21 $one_idx = int ($positive / 100);
75 7         13 $remain = $positive % 100;
76              
77 7 100       24 $out = ($one_idx == 1 ? '' : $tokens1[$one_idx]) . 'honnert';
78 7 100       32 $out .= $remain ? num2ltz_cardinal($remain) : '';
79             }
80             elsif ($positive > 999 && $positive < 1_000_000) { # 1000 .. 999_999
81 4         13 $one_idx = int ($positive / 1000);
82 4         9 $remain = $positive % 1000;
83              
84 4 100       16 $out = ($one_idx == 1 ? '' : num2ltz_cardinal($one_idx)) . 'dausend';
85 4 100       15 $out .= $remain ? num2ltz_cardinal($remain) : '';
86             }
87             elsif ( $positive > 999_999
88             && $positive < 1_000_000_000) { # 1_000_000 .. 999_999_999
89 2         7 $one_idx = int ($positive / 1000000);
90 2         5 $remain = $positive % 1000000;
91              
92             # "eng Millioun" for 1M, "zwee Milliounen" for 2M+
93 2 100       6 if ($one_idx == 1) {
94 1         3 $out = 'eng Millioun';
95             }
96             else {
97 1         6 $out = num2ltz_cardinal($one_idx) . ' Milliounen';
98             }
99 2 50       7 $out .= $remain ? ' ' . num2ltz_cardinal($remain) : '';
100             }
101              
102 28         137 return $out;
103 1     1   7 }
  1         1  
  1         5  
104              
105             # }}}
106             # {{{ _connector apply n-rule (Eifel rule) for 'an'
107              
108             sub _connector {
109 14     14   31 my $tens = shift;
110              
111             # The Eifel rule (n-Regel): final 'n' is dropped before a consonant,
112             # EXCEPT before n, d, t, z, h.
113             # Applied to the connector 'an' before the tens word.
114 14         59 my $first_char = substr($tens, 0, 1);
115              
116             # Before a vowel: keep 'n'
117 14 100       59 return 'an' if $first_char =~ m/[aeiouäëéAEIOU]/;
118              
119             # Before consonants n, d, t, z, h: keep 'n'
120 13 100       62 return 'an' if $first_char =~ m/[ndtzh]/;
121              
122             # Before all other consonants: drop 'n'
123 5         18 return 'a';
124             }
125              
126             # }}}
127             # {{{ num2ltz_ordinal convert number to ordinal text
128              
129             sub num2ltz_ordinal :Export {
130 0     0 1   my $number = shift;
131              
132 0 0 0       croak 'You should specify a number from interval [1, 999_999_999]'
      0        
      0        
133             if !defined $number
134             || $number !~ m{\A\d+\z}xms
135             || $number < 1
136             || $number > 999_999_999;
137              
138             # Fully irregular forms
139 0 0         return 'éischt' if $number == 1;
140 0 0         return 'zweet' if $number == 2;
141 0 0         return 'drëtt' if $number == 3;
142              
143             # Stem irregulars
144 0 0         return 'siiwent' if $number == 7;
145 0 0         return 'aacht' if $number == 8;
146              
147 0           my $cardinal = num2ltz_cardinal($number);
148              
149             # Numbers 4-19 get suffix "t", 20+ get "st"
150 0 0         my $suffix = $number < 20 ? 't' : 'st';
151              
152 0           return $cardinal . $suffix;
153 1     1   461 }
  1         6  
  1         4  
154              
155             # }}}
156              
157             # {{{ capabilities declare supported features
158              
159             sub capabilities {
160             return {
161 0     0 1   cardinal => 1,
162             ordinal => 1,
163             };
164             }
165              
166             # }}}
167             1;
168              
169             __END__