File Coverage

blib/lib/Lingua/JPN/Word2Num.pm
Criterion Covered Total %
statement 26 31 83.8
branch 0 6 0.0
condition 2 4 50.0
subroutine 9 10 90.0
pod 3 3 100.0
total 40 54 74.0


line stmt bran cond sub pod time code
1             # For Emacs: -*- mode:cperl; eval: (folding-mode 1); coding:utf-8; -*-
2             #
3             # Copyright (c) PetaMem, s.r.o. 2004-present
4              
5             package Lingua::JPN::Word2Num;
6             # ABSTRACT: Word to number conversion in Japanese
7              
8 1     1   127109 use 5.16.0;
  1         3  
9 1     1   4 use utf8;
  1         2  
  1         11  
10 1     1   20 use warnings;
  1         1  
  1         49  
11              
12             # {{{ use block
13              
14 1     1   460 use Export::Attrs;
  1         8316  
  1         4  
15 1     1   1167 use Parse::RecDescent;
  1         46161  
  1         13  
16              
17             # }}}
18             # {{{ var block
19              
20             our $VERSION = '0.2604300';
21             my $parser = jpn_numerals();
22              
23             # }}}
24              
25             # {{{ w2n convert text to number
26              
27             sub w2n :Export {
28 34   100 34 1 10596175 my $input = shift // return;
29              
30             # Strip ASCII hyphens (didactic romaji forms like "san-zen" or "ichi-man"
31             # become "sanzen" / "ichiman"). Also strip leading/trailing whitespace.
32 33         164 $input =~ s/-/ /g;
33 33         165 $input =~ s/\A\s+//;
34 33         136 $input =~ s/\s+\z//;
35              
36 33         325 return $parser->numeral($input);
37 1     1   278 }
  1         2  
  1         10  
38              
39             # }}}
40             # {{{ jpn_numerals create parser for Japanese numerals
41              
42             sub jpn_numerals {
43 1     1 1 4 return Parse::RecDescent->new(<<'GRAMMAR');
44              
45             # ── Top-level: try the largest scale first, fall through ─────────────
46             numeral: cho_block { $item[1] }
47             | oku_block { $item[1] }
48             | man_block { $item[1] }
49             | sen_block { $item[1] }
50             | hyaku_block { $item[1] }
51             | ju_block { $item[1] }
52             | digit { $item[1] }
53             | { undef }
54              
55             # ── Atomic digits 0..9 in three scripts ───────────────────────────────
56             digit: /(?:零|〇|ぜろ|zero)/i { 0 }
57             | /(?:一|いち|ichi)/i { 1 }
58             | /(?:二|に|ni)/i { 2 }
59             | /(?:三|さん|san)/i { 3 }
60             | /(?:四|よん|よ|yon|shi)/i { 4 }
61             | /(?:五|ご|go)/i { 5 }
62             | /(?:六|ろく|roku)/i { 6 }
63             | /(?:七|なな|しち|nana|shichi)/i { 7 }
64             | /(?:八|はち|hachi)/i { 8 }
65             | /(?:九|きゅう|く|kyu|kyuu|ku)/i { 9 }
66              
67             # ── Tens: "十" / "じゅう" / "ju" — bare or digit-prefixed ─────────────
68             ju_word: /(?:十|じゅう|ju)/i
69              
70             ju_block: digit ju_word digit { $item[1] * 10 + $item[3] }
71             | digit ju_word { $item[1] * 10 }
72             | ju_word digit { 10 + $item[2] }
73             | ju_word { 10 }
74              
75             # ── Hundreds — bare 100, irregular 300/600/800, regular 200/400/etc ──
76             hyaku_word: /(?:百|ひゃく|hyaku)/i
77              
78             # Irregular hundreds (rendaku/gemination) — kanji form has no
79             # phonological irregularity (三百), but hiragana/romaji do.
80             hyaku_irregular_3: /(?:さんびゃく|sanbyaku|san\s*byaku)/i { 300 }
81             hyaku_irregular_6: /(?:ろっぴゃく|roppyaku|roku\s*hyaku)/i { 600 }
82             hyaku_irregular_8: /(?:はっぴゃく|happyaku|hachi\s*hyaku)/i { 800 }
83              
84             hyaku_term: hyaku_irregular_3 { $item[1] }
85             | hyaku_irregular_6 { $item[1] }
86             | hyaku_irregular_8 { $item[1] }
87             | /三/ hyaku_word { 300 }
88             | /六/ hyaku_word { 600 }
89             | /八/ hyaku_word { 800 }
90             | digit hyaku_word { $item[1] * 100 }
91             | hyaku_word { 100 }
92              
93             hyaku_block: hyaku_term ju_block { $item[1] + $item[2] }
94             | hyaku_term digit { $item[1] + $item[2] }
95             | hyaku_term
96              
97             # ── Thousands — bare 1000, irregular 3000/8000, regular ──────────────
98             sen_word: /(?:千|せん|sen)/i
99              
100             sen_irregular_3: /(?:さんぜん|sanzen|san\s*zen|san\s*sen)/i { 3000 }
101             sen_irregular_8: /(?:はっせん|hassen|hachi\s*sen)/i { 8000 }
102              
103             sen_term: sen_irregular_3 { $item[1] }
104             | sen_irregular_8 { $item[1] }
105             | /三/ sen_word { 3000 }
106             | /八/ sen_word { 8000 }
107             | digit sen_word { $item[1] * 1000 }
108             | sen_word { 1000 }
109              
110             sen_block: sen_term hyaku_block { $item[1] + $item[2] }
111             | sen_term ju_block { $item[1] + $item[2] }
112             | sen_term digit { $item[1] + $item[2] }
113             | sen_term
114              
115             # ── 10,000 (man) ──────────────────────────────────────────────────────
116             man_word: /(?:万|まん|man)/i
117              
118             # 一万 — bare "man" is also valid input (like Mike Schilli's old API)
119             man_term: sen_block man_word { $item[1] * 10_000 }
120             | hyaku_block man_word { $item[1] * 10_000 }
121             | ju_block man_word { $item[1] * 10_000 }
122             | digit man_word { $item[1] * 10_000 }
123             | man_word { 10_000 }
124              
125             man_block: man_term sen_block { $item[1] + $item[2] }
126             | man_term hyaku_block { $item[1] + $item[2] }
127             | man_term ju_block { $item[1] + $item[2] }
128             | man_term digit { $item[1] + $item[2] }
129             | man_term
130              
131             # ── 100,000,000 (oku) ────────────────────────────────────────────────
132             oku_word: /(?:億|おく|oku)/i
133              
134             oku_term: sen_block oku_word { $item[1] * 100_000_000 }
135             | hyaku_block oku_word { $item[1] * 100_000_000 }
136             | ju_block oku_word { $item[1] * 100_000_000 }
137             | digit oku_word { $item[1] * 100_000_000 }
138             | oku_word { 100_000_000 }
139              
140             oku_block: oku_term man_block { $item[1] + $item[2] }
141             | oku_term sen_block { $item[1] + $item[2] }
142             | oku_term hyaku_block { $item[1] + $item[2] }
143             | oku_term ju_block { $item[1] + $item[2] }
144             | oku_term digit { $item[1] + $item[2] }
145             | oku_term
146              
147             # ── 1,000,000,000,000 (cho) — with whole-block irregular leaders ─────
148             cho_word: /(?:兆|ちょう|cho)/i
149              
150             cho_irregular_1: /(?:いっちょう|itcho|ittchou)/i { 1_000_000_000_000 }
151             cho_irregular_8: /(?:はっちょう|hatcho|hatchou)/i { 8_000_000_000_000 }
152             cho_irregular_10: /(?:じゅっちょう|jutcho|jucchou|juccho)/i { 10_000_000_000_000 }
153              
154             cho_term: cho_irregular_1 { $item[1] }
155             | cho_irregular_8 { $item[1] }
156             | cho_irregular_10 { $item[1] }
157             | sen_block cho_word { $item[1] * 1_000_000_000_000 }
158             | hyaku_block cho_word { $item[1] * 1_000_000_000_000 }
159             | ju_block cho_word { $item[1] * 1_000_000_000_000 }
160             | digit cho_word { $item[1] * 1_000_000_000_000 }
161             | cho_word { 1_000_000_000_000 }
162              
163             cho_block: cho_term oku_block { $item[1] + $item[2] }
164             | cho_term man_block { $item[1] + $item[2] }
165             | cho_term sen_block { $item[1] + $item[2] }
166             | cho_term hyaku_block { $item[1] + $item[2] }
167             | cho_term ju_block { $item[1] + $item[2] }
168             | cho_term digit { $item[1] + $item[2] }
169             | cho_term
170              
171             GRAMMAR
172             }
173              
174             # }}}
175             # {{{ ordinal2cardinal convert ordinal text to cardinal text
176              
177             sub ordinal2cardinal :Export {
178 0   0 0 1   my $input = shift // return;
179              
180             # Japanese ordinal suffix:
181             # kanji: 番目
182             # hiragana: ばんめ
183             # romaji: -ban-me (or "ban me" / "banme")
184 0 0         $input =~ s{番目\z}{}xms and return $input;
185 0 0         $input =~ s{ばんめ\z}{}xms and return $input;
186 0 0         $input =~ s{[\s-]?ban[\s-]?me\z}{}xms and return $input;
187              
188 0           return; # not an ordinal
189 1     1   538 }
  1         1  
  1         5  
190              
191             # }}}
192              
193             1;
194              
195             __END__