bez{"set2"}
bg{"set2"}
bh{"set3"}
- bm{""}
+ bm{"set24"}
bn{"set30"}
- bo{""}
+ bo{"set24"}
br{"set19"}
brx{"set2"}
- bs{"set31"}
+ bs{"set33"}
ca{"set26"}
cgg{"set2"}
chr{"set2"}
ckb{"set2"}
cs{"set11"}
cy{"set16"}
- da{"set27"}
+ da{"set28"}
de{"set26"}
dv{"set2"}
- dz{""}
+ dz{"set24"}
ee{"set2"}
el{"set2"}
en{"set26"}
haw{"set2"}
he{"set1"}
hi{"set30"}
- hr{"set31"}
+ hr{"set33"}
hu{"set2"}
hy{"set4"}
- id{""}
- ig{""}
- ii{""}
- is{"set28"}
+ id{"set24"}
+ ig{"set24"}
+ ii{"set24"}
+ in{"set24"}
+ is{"set31"}
it{"set26"}
iu{"set6"}
- ja{""}
- jbo{""}
+ iw{"set1"}
+ ja{"set24"}
+ jbo{"set24"}
jgo{"set2"}
+ ji{"set26"}
jmc{"set2"}
- jv{""}
+ jv{"set24"}
+ jw{"set24"}
ka{"set2"}
kab{"set4"}
kaj{"set2"}
kcg{"set2"}
- kde{""}
- kea{""}
+ kde{"set24"}
+ kea{"set24"}
kk{"set2"}
kkj{"set2"}
kl{"set2"}
- km{""}
+ km{"set24"}
kn{"set30"}
- ko{""}
+ ko{"set24"}
ks{"set2"}
ksb{"set2"}
ksh{"set20"}
lag{"set17"}
lb{"set2"}
lg{"set2"}
- lkt{""}
+ lkt{"set24"}
ln{"set3"}
- lo{""}
+ lo{"set24"}
lt{"set9"}
lv{"set5"}
mas{"set2"}
mn{"set2"}
mo{"set8"}
mr{"set30"}
- ms{""}
+ ms{"set24"}
mt{"set14"}
- my{""}
+ my{"set24"}
nah{"set2"}
naq{"set6"}
nb{"set2"}
nn{"set2"}
nnh{"set2"}
no{"set2"}
- nqo{""}
+ nqo{"set24"}
nr{"set2"}
nso{"set3"}
ny{"set2"}
pap{"set2"}
pl{"set12"}
ps{"set2"}
- pt{"set29"}
+ pt{"set27"}
+ pt_PT{"set29"}
rm{"set2"}
ro{"set8"}
rof{"set2"}
- ru{"set32"}
+ ru{"set34"}
rwk{"set2"}
- sah{""}
+ sah{"set24"}
saq{"set2"}
se{"set6"}
seh{"set2"}
- ses{""}
- sg{""}
- sh{"set31"}
+ ses{"set24"}
+ sg{"set24"}
+ sh{"set33"}
shi{"set18"}
+ si{"set32"}
sk{"set11"}
sl{"set13"}
sma{"set6"}
sn{"set2"}
so{"set2"}
sq{"set2"}
- sr{"set31"}
+ sr{"set33"}
ss{"set2"}
ssy{"set2"}
st{"set2"}
- sv{"set27"}
+ sv{"set26"}
sw{"set26"}
syr{"set2"}
ta{"set2"}
te{"set2"}
teo{"set2"}
- th{""}
+ th{"set24"}
ti{"set3"}
tig{"set2"}
tk{"set2"}
tl{"set25"}
tn{"set2"}
- to{""}
+ to{"set24"}
tr{"set2"}
ts{"set2"}
tzm{"set21"}
- uk{"set33"}
+ uk{"set35"}
ur{"set26"}
+ uz{"set2"}
ve{"set2"}
- vi{""}
+ vi{"set24"}
vo{"set2"}
vun{"set2"}
wa{"set3"}
wae{"set2"}
- wo{""}
+ wo{"set24"}
xh{"set2"}
xog{"set2"}
yi{"set26"}
- yo{""}
- zh{""}
+ yo{"set24"}
+ zh{"set24"}
zu{"set30"}
}
locales_ordinals{
- af{""}
- am{""}
- ar{""}
- bg{""}
- bn{"set41"}
- ca{"set38"}
- cs{""}
- da{""}
- de{""}
- el{""}
- en{"set36"}
- es{""}
- et{""}
- eu{""}
- fa{""}
- fi{""}
- fil{"set2"}
- fr{"set2"}
- gl{""}
- gu{"set40"}
- he{""}
- hi{"set40"}
- hr{""}
- hu{"set34"}
- id{""}
- is{""}
- it{"set37"}
- ja{""}
- kn{""}
- ko{""}
- lt{""}
- lv{""}
- ml{""}
- mr{"set39"}
- ms{"set2"}
- nb{""}
- nl{""}
- pl{""}
- pt{""}
- ro{"set2"}
- ru{""}
- sk{""}
- sl{""}
- sr{""}
- sv{"set35"}
- sw{""}
- ta{""}
- te{""}
- th{""}
- tr{""}
- uk{""}
- ur{""}
- vi{"set2"}
- zh{""}
- zu{"set42"}
+ af{"set36"}
+ am{"set36"}
+ ar{"set36"}
+ bg{"set36"}
+ bn{"set45"}
+ ca{"set42"}
+ cs{"set36"}
+ da{"set36"}
+ de{"set36"}
+ el{"set36"}
+ en{"set40"}
+ es{"set36"}
+ et{"set36"}
+ eu{"set36"}
+ fa{"set36"}
+ fi{"set36"}
+ fil{"set37"}
+ fr{"set37"}
+ gl{"set36"}
+ gu{"set44"}
+ he{"set36"}
+ hi{"set44"}
+ hr{"set36"}
+ hu{"set38"}
+ id{"set36"}
+ in{"set36"}
+ is{"set36"}
+ it{"set41"}
+ iw{"set36"}
+ ja{"set36"}
+ kn{"set36"}
+ ko{"set36"}
+ lt{"set36"}
+ lv{"set36"}
+ ml{"set36"}
+ mo{"set37"}
+ mr{"set43"}
+ ms{"set37"}
+ nb{"set36"}
+ nl{"set36"}
+ pl{"set36"}
+ pt{"set36"}
+ ro{"set37"}
+ ru{"set36"}
+ sh{"set36"}
+ sk{"set36"}
+ sl{"set36"}
+ sr{"set36"}
+ sv{"set39"}
+ sw{"set36"}
+ ta{"set36"}
+ te{"set36"}
+ th{"set36"}
+ tl{"set37"}
+ tr{"set36"}
+ uk{"set36"}
+ ur{"set36"}
+ vi{"set37"}
+ zh{"set36"}
+ zu{"set46"}
}
rules{
set0{
- few{"n mod 100 in 3..10"}
- many{"n mod 100 in 11..99"}
- one{"n is 1"}
- two{"n is 2"}
- zero{"n is 0"}
+ few{
+ "n % 100 = 3..10 @integer 3~10, 103~110, 1003, … @decimal 3.0, 4.0, 5"
+ ".0, 6.0, 7.0, 8.0, 9.0, 10.0, 103.0, 1003.0, …"
+ }
+ many{
+ "n % 100 = 11..99 @integer 11~26, 111, 1011, … @decimal 11.0, 12.0, 1"
+ "3.0, 14.0, 15.0, 16.0, 17.0, 18.0, 111.0, 1011.0, …"
+ }
+ one{"n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000"}
+ other{
+ " @integer 100~102, 200~202, 300~302, 400~402, 500~502, 600, 1000, 10"
+ "000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.0, 1000"
+ ".0, 10000.0, 100000.0, 1000000.0, …"
+ }
+ two{"n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000"}
+ zero{"n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000"}
}
set1{
- many{"j not in 0..10 and j mod 10 is 0"}
- one{"j is 1"}
- two{"j is 2"}
+ many{
+ "v = 0 and n != 0..10 and n % 10 = 0 @integer 20, 30, 40, 50, 60, 70,"
+ " 80, 90, 100, 1000, 10000, 100000, 1000000, …"
+ }
+ one{"i = 1 and v = 0 @integer 1"}
+ other{
+ " @integer 0, 3~17, 101, 1001, … @decimal 0.0~1.5, 10.0, 100.0, 1000."
+ "0, 10000.0, 100000.0, 1000000.0, …"
+ }
+ two{"i = 2 and v = 0 @integer 2"}
}
set10{
- few{"n mod 10 in 2..4 and n mod 100 not in 12..14"}
- many{"n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14"}
- one{"n mod 10 is 1 and n mod 100 is not 11"}
+ few{
+ "n % 10 = 2..4 and n % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~4"
+ "4, 52~54, 62, 102, 1002, … @decimal 2.0, 3.0, 4.0, 22.0, 23.0, 24.0,"
+ " 32.0, 33.0, 102.0, 1002.0, …"
+ }
+ many{
+ "n % 10 = 0 or n % 10 = 5..9 or n % 100 = 11..14 @integer 0, 5~19, 10"
+ "0, 1000, 10000, 100000, 1000000, … @decimal 0.0, 5.0, 6.0, 7.0, 8.0,"
+ " 9.0, 10.0, 11.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
+ }
+ one{
+ "n % 10 = 1 and n % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81,"
+ " 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, 81.0"
+ ", 101.0, 1001.0, …"
+ }
+ other{" @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.1, 1000.1, …"}
}
set11{
- few{"j in 2..4"}
- many{"v is not 0"}
- one{"j is 1"}
+ few{"i = 2..4 and v = 0 @integer 2~4"}
+ many{
+ "v != 0 @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1"
+ "000000.0, …"
+ }
+ one{"i = 1 and v = 0 @integer 1"}
+ other{" @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …"}
}
set12{
- few{"j mod 10 in 2..4 and j mod 100 not in 12..14"}
+ few{
+ "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 @integer 2~4, 22~24, 3"
+ "2~34, 42~44, 52~54, 62, 102, 1002, …"
+ }
many{
- "j is not 1 and j mod 10 in 0..1 or j mod 10 in 5..9 or j mod 100 in "
- "12..14"
+ "v = 0 and i != 1 and i % 10 = 0..1 or v = 0 and i % 10 = 5..9 or v ="
+ " 0 and i % 100 = 12..14 @integer 0, 5~19, 100, 1000, 10000, 100000, "
+ "1000000, …"
+ }
+ one{"i = 1 and v = 0 @integer 1"}
+ other{
+ " @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000"
+ ".0, …"
}
- one{"j is 1"}
}
set13{
- few{"j mod 100 in 3..4 or v is not 0"}
- one{"j mod 100 is 1"}
- two{"j mod 100 is 2"}
+ few{
+ "v = 0 and i % 100 = 3..4 or v != 0 @integer 3, 4, 103, 104, 203, 204"
+ ", 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003, … @decimal"
+ " 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
+ }
+ one{
+ "v = 0 and i % 100 = 1 @integer 1, 101, 201, 301, 401, 501, 601, 701,"
+ " 1001, …"
+ }
+ other{" @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …"}
+ two{
+ "v = 0 and i % 100 = 2 @integer 2, 102, 202, 302, 402, 502, 602, 702,"
+ " 1002, …"
+ }
}
set14{
- few{"n is 0 or n mod 100 in 2..10"}
- many{"n mod 100 in 11..19"}
- one{"n is 1"}
+ few{
+ "n = 0 or n % 100 = 2..10 @integer 0, 2~10, 102~107, 1002, … @decimal"
+ " 0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 102.0, 1002.0, …"
+ }
+ many{
+ "n % 100 = 11..19 @integer 11~19, 111~117, 1011, … @decimal 11.0, 12."
+ "0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 111.0, 1011.0, …"
+ }
+ one{"n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000"}
+ other{
+ " @integer 20~35, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0"
+ ".9, 1.1~1.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
+ }
}
set15{
- one{"j mod 10 is 1 or f mod 10 is 1"}
+ one{
+ "v = 0 and i % 10 = 1 or f % 10 = 1 @integer 1, 11, 21, 31, 41, 51, 6"
+ "1, 71, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1,"
+ " 10.1, 100.1, 1000.1, …"
+ }
+ other{
+ " @integer 0, 2~10, 12~17, 100, 1000, 10000, 100000, 1000000, … @deci"
+ "mal 0.0, 0.2~1.0, 1.2~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1"
+ "000000.0, …"
+ }
}
set16{
- few{"n is 3"}
- many{"n is 6"}
- one{"n is 1"}
- two{"n is 2"}
- zero{"n is 0"}
+ few{"n = 3 @integer 3 @decimal 3.0, 3.00, 3.000, 3.0000"}
+ many{"n = 6 @integer 6 @decimal 6.0, 6.00, 6.000, 6.0000"}
+ one{"n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000"}
+ other{
+ " @integer 4, 5, 7~20, 100, 1000, 10000, 100000, 1000000, … @decimal "
+ "0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0,"
+ " …"
+ }
+ two{"n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000"}
+ zero{"n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000"}
}
set17{
- one{"n within 0..2 and n is not 0 and n is not 2"}
- zero{"n is 0"}
+ one{"i = 0,1 and n != 0 @integer 1 @decimal 0.1~1.6"}
+ other{
+ " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3."
+ "5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
+ }
+ zero{"n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000"}
}
set18{
- few{"n in 2..10"}
- one{"n within 0..1"}
+ few{
+ "n = 2..10 @integer 2~10 @decimal 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, "
+ "9.0, 10.0, 2.00, 3.00, 4.00, 5.00, 6.00, 7.00, 8.00"
+ }
+ one{"i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04"}
+ other{
+ " @integer 11~26, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~1"
+ ".9, 2.1~2.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
+ }
}
set19{
- few{"n mod 10 in 3..4,9 and n mod 100 not in 10..19,70..79,90..99"}
- many{"n is not 0 and n mod 1000000 is 0"}
- one{"n mod 10 is 1 and n mod 100 not in 11,71,91"}
- two{"n mod 10 is 2 and n mod 100 not in 12,72,92"}
+ few{
+ "n % 10 = 3..4,9 and n % 100 != 10..19,70..79,90..99 @integer 3, 4, 9"
+ ", 23, 24, 29, 33, 34, 39, 43, 44, 49, 103, 1003, … @decimal 3.0, 4.0"
+ ", 9.0, 23.0, 24.0, 29.0, 33.0, 34.0, 103.0, 1003.0, …"
+ }
+ many{
+ "n != 0 and n % 1000000 = 0 @integer 1000000, … @decimal 1000000.0, 1"
+ "000000.00, 1000000.000, …"
+ }
+ one{
+ "n % 10 = 1 and n % 100 != 11,71,91 @integer 1, 21, 31, 41, 51, 61, 8"
+ "1, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 81.0, 10"
+ "1.0, 1001.0, …"
+ }
+ other{
+ " @integer 0, 5~8, 10~20, 100, 1000, 10000, 100000, … @decimal 0.0~0."
+ "9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, …"
+ }
+ two{
+ "n % 10 = 2 and n % 100 != 12,72,92 @integer 2, 22, 32, 42, 52, 62, 8"
+ "2, 102, 1002, … @decimal 2.0, 22.0, 32.0, 42.0, 52.0, 62.0, 82.0, 10"
+ "2.0, 1002.0, …"
+ }
}
set2{
- one{"n is 1"}
+ one{"n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000"}
+ other{
+ " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0"
+ "~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
+ }
}
set20{
- one{"n is 1"}
- zero{"n is 0"}
+ one{"n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000"}
+ other{
+ " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0."
+ "9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
+ }
+ zero{"n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000"}
}
set21{
- one{"n in 0..1 or n in 11..99"}
+ one{
+ "n = 0..1 or n = 11..99 @integer 0, 1, 11~24 @decimal 0.0, 1.0, 11.0,"
+ " 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 2"
+ "3.0, 24.0"
+ }
+ other{
+ " @integer 2~10, 100~106, 1000, 10000, 100000, 1000000, … @decimal 0."
+ "1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
+ }
}
set22{
- few{"n mod 100 in 0,20,40,60"}
- one{"n mod 10 is 1"}
- two{"n mod 10 is 2"}
+ few{
+ "n % 100 = 0,20,40,60 @integer 0, 20, 40, 60, 100, 120, 140, 160, 100"
+ "0, 10000, 100000, 1000000, … @decimal 0.0, 20.0, 40.0, 60.0, 100.0, "
+ "120.0, 140.0, 160.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
+ }
+ one{
+ "n % 10 = 1 @integer 1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, … @dec"
+ "imal 1.0, 11.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, 101.0, 1001.0, …"
+ }
+ other{
+ " @integer 3~10, 13~19, 23, 103, 1003, … @decimal 0.1~0.9, 1.1~1.7, 1"
+ "0.0, 100.1, 1000.1, …"
+ }
+ two{
+ "n % 10 = 2 @integer 2, 12, 22, 32, 42, 52, 62, 72, 102, 1002, … @dec"
+ "imal 2.0, 12.0, 22.0, 32.0, 42.0, 52.0, 62.0, 72.0, 102.0, 1002.0, …"
+ }
}
set23{
- few{"n in 3..10,13..19"}
- one{"n in 1,11"}
- two{"n in 2,12"}
+ few{
+ "n = 3..10,13..19 @integer 3~10, 13~19 @decimal 3.0, 4.0, 5.0, 6.0, 7"
+ ".0, 8.0, 9.0, 10.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 3.00"
+ }
+ one{
+ "n = 1,11 @integer 1, 11 @decimal 1.0, 11.0, 1.00, 11.00, 1.000, 11.0"
+ "00, 1.0000"
+ }
+ other{
+ " @integer 0, 20~34, 100, 1000, 10000, 100000, 1000000, … @decimal 0."
+ "0~0.9, 1.1~1.6, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
+ }
+ two{
+ "n = 2,12 @integer 2, 12 @decimal 2.0, 12.0, 2.00, 12.00, 2.000, 12.0"
+ "00, 2.0000"
+ }
+ }
+ set24{
+ other{
+ " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1."
+ "5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
+ }
}
set25{
- one{"j in 0..1"}
+ one{"i = 0..1 and v = 0 @integer 0, 1"}
+ other{
+ " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1."
+ "5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
+ }
}
set26{
- one{"j is 1"}
+ one{"i = 1 and v = 0 @integer 1"}
+ other{
+ " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0"
+ "~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
+ }
}
set27{
- one{"j is 1 or f is 1"}
+ one{
+ "i = 1 and v = 0 or f = 1 @integer 1 @decimal 0.1, 1.1, 2.1, 3.1, 4.1"
+ ", 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …"
+ }
+ other{
+ " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0"
+ ", 0.2~1.0, 1.2~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000."
+ "0, …"
+ }
}
set28{
- one{
- "j mod 10 is 1 and j mod 100 is not 11 or f mod 10 is 1 and f mod 100"
- " is not 11"
+ one{"n = 1 or t != 0 and i = 0,1 @integer 1 @decimal 0.1~1.6"}
+ other{
+ " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0"
+ ", 2.0~3.4, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
}
}
set29{
- one{"n is 1 or f is 1"}
+ one{
+ "n = 1 or t = 1 @integer 1 @decimal 0.1, 1.0, 1.1, 2.1, 3.1, 4.1, 5.1"
+ ", 6.1, 7.1, 10.1, 100.1, 1000.1, …"
+ }
+ other{
+ " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0"
+ ", 0.2~0.9, 1.2~1.8, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000."
+ "0, …"
+ }
}
set3{
- one{"n in 0..1"}
+ one{
+ "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, "
+ "0.0000, 1.0000"
+ }
+ other{
+ " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0."
+ "9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
+ }
}
set30{
- one{"n within 0..1"}
+ one{"i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04"}
+ other{
+ " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2."
+ "6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
+ }
}
set31{
- few{
- "j mod 10 in 2..4 and j mod 100 not in 12..14 or f mod 10 in 2..4 and"
- " f mod 100 not in 12..14"
- }
one{
- "j mod 10 is 1 and j mod 100 is not 11 or f mod 10 is 1 and f mod 100"
- " is not 11"
+ "t = 0 and i % 10 = 1 and i % 100 != 11 or t != 0 @integer 1, 21, 31,"
+ " 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1~1.6, 10.1, 100.1, 100"
+ "0.1, …"
+ }
+ other{
+ " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0"
+ ", 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 100.0, 1000.0, 10000.0, 1"
+ "00000.0, 1000000.0, …"
}
}
set32{
- many{"j mod 10 is 0 or j mod 10 in 5..9 or j mod 100 in 11..14"}
- one{"j mod 10 is 1 and j mod 100 is not 11"}
+ one{
+ "n = 0,1 or i = 0 and f = 1 @integer 0, 1 @decimal 0.0, 0.1, 1.0, 0.0"
+ "0, 0.01, 1.00, 0.000, 0.001, 1.000, 0.0000, 0.0001, 1.0000"
+ }
+ other{
+ " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.2~0."
+ "9, 1.1~1.8, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
+ }
}
set33{
- few{"j mod 10 in 2..4 and j mod 100 not in 12..14"}
- many{"j mod 10 is 0 or j mod 10 in 5..9 or j mod 100 in 11..14"}
- one{"j mod 10 is 1 and j mod 100 is not 11"}
+ few{
+ "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f"
+ " % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, "
+ "1002, … @decimal 0.2~0.4, 1.2~1.4, 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 1"
+ "0.2, 100.2, 1000.2, …"
+ }
+ one{
+ "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != "
+ "11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1"
+ ", 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …"
+ }
+ other{
+ " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0"
+ ", 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0,"
+ " 1000000.0, …"
+ }
}
set34{
- one{"n in 1,5"}
+ many{
+ "v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100"
+ " = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …"
+ }
+ one{
+ "v = 0 and i % 10 = 1 and i % 100 != 11 @integer 1, 21, 31, 41, 51, 6"
+ "1, 71, 81, 101, 1001, …"
+ }
+ other{
+ " @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal"
+ " 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
+ }
}
set35{
- one{"n mod 10 in 1,2 and n mod 100 not in 11,12"}
+ few{
+ "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 @integer 2~4, 22~24, 3"
+ "2~34, 42~44, 52~54, 62, 102, 1002, …"
+ }
+ many{
+ "v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100"
+ " = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …"
+ }
+ one{
+ "v = 0 and i % 10 = 1 and i % 100 != 11 @integer 1, 21, 31, 41, 51, 6"
+ "1, 71, 81, 101, 1001, …"
+ }
+ other{
+ " @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000"
+ ".0, …"
+ }
}
set36{
- few{"n mod 10 is 3 and n mod 100 is not 13"}
- one{"n mod 10 is 1 and n mod 100 is not 11"}
- two{"n mod 10 is 2 and n mod 100 is not 12"}
+ other{" @integer 0~15, 100, 1000, 10000, 100000, 1000000, …"}
}
set37{
- many{"n in 11,8,80,800"}
+ one{"n = 1 @integer 1"}
+ other{" @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, …"}
}
set38{
- few{"n is 4"}
- one{"n in 1,3"}
- two{"n is 2"}
+ one{"n = 1,5 @integer 1, 5"}
+ other{" @integer 0, 2~4, 6~17, 100, 1000, 10000, 100000, 1000000, …"}
}
set39{
- few{"n is 4"}
- one{"n is 1"}
- two{"n in 2,3"}
+ one{
+ "n % 10 = 1,2 and n % 100 != 11,12 @integer 1, 2, 21, 22, 31, 32, 41,"
+ " 42, 51, 52, 61, 62, 71, 72, 81, 82, 101, 1001, …"
+ }
+ other{" @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, …"}
}
set4{
- one{"n within 0..2 and n is not 2"}
+ one{"i = 0,1 @integer 0, 1 @decimal 0.0~1.5"}
+ other{
+ " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3."
+ "5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
+ }
}
set40{
- few{"n is 4"}
- many{"n is 6"}
- one{"n is 1"}
- two{"n in 2,3"}
+ few{
+ "n % 10 = 3 and n % 100 != 13 @integer 3, 23, 33, 43, 53, 63, 73, 83,"
+ " 103, 1003, …"
+ }
+ one{
+ "n % 10 = 1 and n % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81,"
+ " 101, 1001, …"
+ }
+ other{" @integer 0, 4~18, 100, 1000, 10000, 100000, 1000000, …"}
+ two{
+ "n % 10 = 2 and n % 100 != 12 @integer 2, 22, 32, 42, 52, 62, 72, 82,"
+ " 102, 1002, …"
+ }
}
set41{
- few{"n is 4"}
- many{"n is 6"}
- one{"n in 1,5,7,8,9,10"}
- two{"n in 2,3"}
+ many{"n = 11,8,80,800 @integer 8, 11, 80, 800"}
+ other{" @integer 0~7, 9, 10, 12~17, 100, 1000, 10000, 100000, 1000000, …"}
}
set42{
- few{"n in 2..9"}
- many{"n in 10..19,100..199,1000..1999"}
- one{"n is 1"}
+ few{"n = 4 @integer 4"}
+ one{"n = 1,3 @integer 1, 3"}
+ other{" @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …"}
+ two{"n = 2 @integer 2"}
+ }
+ set43{
+ few{"n = 4 @integer 4"}
+ one{"n = 1 @integer 1"}
+ other{" @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …"}
+ two{"n = 2,3 @integer 2, 3"}
+ }
+ set44{
+ few{"n = 4 @integer 4"}
+ many{"n = 6 @integer 6"}
+ one{"n = 1 @integer 1"}
+ other{" @integer 0, 5, 7~20, 100, 1000, 10000, 100000, 1000000, …"}
+ two{"n = 2,3 @integer 2, 3"}
+ }
+ set45{
+ few{"n = 4 @integer 4"}
+ many{"n = 6 @integer 6"}
+ one{"n = 1,5,7,8,9,10 @integer 1, 5, 7~10"}
+ other{" @integer 0, 11~25, 100, 1000, 10000, 100000, 1000000, …"}
+ two{"n = 2,3 @integer 2, 3"}
+ }
+ set46{
+ few{"n = 2..9 @integer 2~9"}
+ many{"n = 10..19,100..199,1000..1999 @integer 10~19, 100~105, 1000"}
+ one{"n = 1 @integer 1"}
+ other{" @integer 0, 20~34, 200, 2000, 10000, 100000, 1000000, …"}
}
set5{
one{
- "n mod 10 is 1 and n mod 100 is not 11 or v is 2 and f mod 10 is 1 an"
- "d f mod 100 is not 11 or v is not 2 and f mod 10 is 1"
+ "n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != "
+ "11 or v != 2 and f % 10 = 1 @integer 1, 21, 31, 41, 51, 61, 71, 81, "
+ "101, 1001, … @decimal 0.1, 1.0, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 1"
+ "0.1, 100.1, 1000.1, …"
+ }
+ other{
+ " @integer 2~9, 22~29, 102, 1002, … @decimal 0.2~0.9, 1.2~1.9, 10.2, "
+ "100.2, 1000.2, …"
}
zero{
- "n mod 10 is 0 or n mod 100 in 11..19 or v is 2 and f mod 100 in 11.."
- "19"
+ "n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19 @intege"
+ "r 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, … @de"
+ "cimal 0.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 100.0, 1000.0, "
+ "10000.0, 100000.0, 1000000.0, …"
}
}
set6{
- one{"n is 1"}
- two{"n is 2"}
+ one{"n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000"}
+ other{
+ " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0"
+ "~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
+ }
+ two{"n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000"}
}
set7{
- few{"n in 3..6"}
- many{"n in 7..10"}
- one{"n is 1"}
- two{"n is 2"}
+ few{
+ "n = 3..6 @integer 3~6 @decimal 3.0, 4.0, 5.0, 6.0, 3.00, 4.00, 5.00,"
+ " 6.00, 3.000, 4.000, 5.000, 6.000, 3.0000, 4.0000, 5.0000, 6.0000"
+ }
+ many{
+ "n = 7..10 @integer 7~10 @decimal 7.0, 8.0, 9.0, 10.0, 7.00, 8.00, 9."
+ "00, 10.00, 7.000, 8.000, 9.000, 10.000, 7.0000, 8.0000, 9.0000, 10.0"
+ "000"
+ }
+ one{"n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000"}
+ other{
+ " @integer 0, 11~25, 100, 1000, 10000, 100000, 1000000, … @decimal 0."
+ "0~0.9, 1.1~1.6, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
+ }
+ two{"n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000"}
}
set8{
- few{"v is not 0 or n is 0 or n is not 1 and n mod 100 in 1..19"}
- one{"j is 1"}
+ few{
+ "v != 0 or n = 0 or n != 1 and n % 100 = 1..19 @integer 0, 2~16, 101,"
+ " 1001, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1"
+ "000000.0, …"
+ }
+ one{"i = 1 and v = 0 @integer 1"}
+ other{" @integer 20~35, 100, 1000, 10000, 100000, 1000000, …"}
}
set9{
- few{"n mod 10 in 2..9 and n mod 100 not in 11..19"}
- many{"f is not 0"}
- one{"n mod 10 is 1 and n mod 100 not in 11..19"}
+ few{
+ "n % 10 = 2..9 and n % 100 != 11..19 @integer 2~9, 22~29, 102, 1002, "
+ "… @decimal 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 22.0, 102.0, 1002"
+ ".0, …"
+ }
+ many{"f != 0 @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.1, 1000.1, …"}
+ one{
+ "n % 10 = 1 and n % 100 != 11..19 @integer 1, 21, 31, 41, 51, 61, 71,"
+ " 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, "
+ "81.0, 101.0, 1001.0, …"
+ }
+ other{
+ " @integer 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 100000"
+ "0, … @decimal 0.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 100.0, "
+ "1000.0, 10000.0, 100000.0, 1000000.0, …"
+ }
}
}
}
UnicodeString pluralCount;
int32_t minFractionDigits = this->getMinimumFractionDigits();
if (minFractionDigits > 0) {
- NumberInfo ni(number, this->getMinimumFractionDigits());
+ FixedDecimal ni(number, this->getMinimumFractionDigits());
pluralCount = fCurrencyPluralInfo->getPluralRules()->select(ni);
} else {
pluralCount = fCurrencyPluralInfo->getPluralRules()->select(number);
#include "unicode/plurrule.h"
#include "unicode/upluralrules.h"
#include "unicode/ures.h"
+#include "charstr.h"
#include "cmemory.h"
#include "cstring.h"
+#include "digitlst.h"
#include "hash.h"
+#include "locutil.h"
#include "mutex.h"
#include "patternprops.h"
#include "plurrule_impl.h"
#include "putilimp.h"
#include "ucln_in.h"
#include "ustrfmt.h"
-#include "locutil.h"
#include "uassert.h"
#include "uvectr32.h"
U_NAMESPACE_BEGIN
-// shared by all instances when lazy-initializing samples
-static UMutex pluralMutex = U_MUTEX_INITIALIZER;
-
#define ARRAY_SIZE(array) (int32_t)(sizeof array / sizeof array[0])
static const UChar PLURAL_KEYWORD_OTHER[]={LOW_O,LOW_T,LOW_H,LOW_E,LOW_R,0};
static const UChar PK_VAR_V[]={LOW_V,0};
static const UChar PK_VAR_J[]={LOW_J,0};
static const UChar PK_WITHIN[]={LOW_W,LOW_I,LOW_T,LOW_H,LOW_I,LOW_N,0};
+static const UChar PK_DECIMAL[]={LOW_D,LOW_E,LOW_C,LOW_I,LOW_M,LOW_A,LOW_L,0};
+static const UChar PK_INTEGER[]={LOW_I,LOW_N,LOW_T,LOW_E,LOW_G,LOW_E,LOW_R,0};
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PluralRules)
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PluralKeywordEnumeration)
-PluralRules::PluralRules(UErrorCode& status)
+PluralRules::PluralRules(UErrorCode& /*status*/)
: UObject(),
- mRules(NULL),
- mParser(NULL),
- mSamples(NULL),
- mSampleInfo(NULL),
- mSampleInfoCount(0)
+ mRules(NULL)
{
- if (U_FAILURE(status)) {
- return;
- }
- mParser = new RuleParser();
- if (mParser==NULL) {
- status = U_MEMORY_ALLOCATION_ERROR;
- }
}
PluralRules::PluralRules(const PluralRules& other)
: UObject(other),
- mRules(NULL),
- mParser(NULL),
- mSamples(NULL),
- mSampleInfo(NULL),
- mSampleInfoCount(0)
+ mRules(NULL)
{
*this=other;
}
PluralRules::~PluralRules() {
delete mRules;
- delete mParser;
- uprv_free(mSamples);
- uprv_free(mSampleInfo);
}
PluralRules*
else {
mRules = new RuleChain(*other.mRules);
}
- delete mParser;
- mParser = new RuleParser();
-
- uprv_free(mSamples);
- mSamples = NULL;
-
- uprv_free(mSampleInfo);
- mSampleInfo = NULL;
- mSampleInfoCount = 0;
}
return *this;
}
+StringEnumeration* PluralRules::getAvailableLocales(UErrorCode &status) {
+ StringEnumeration *result = new PluralAvailableLocalesEnumeration(status);
+ if (result == NULL && U_SUCCESS(status)) {
+ status = U_MEMORY_ALLOCATION_ERROR;
+ }
+ if (U_FAILURE(status)) {
+ delete result;
+ result = NULL;
+ }
+ return result;
+}
+
+
PluralRules* U_EXPORT2
PluralRules::createRules(const UnicodeString& description, UErrorCode& status) {
- RuleChain rules;
-
if (U_FAILURE(status)) {
return NULL;
}
+
+ PluralRuleParser parser;
PluralRules *newRules = new PluralRules(status);
- if ( (newRules != NULL)&& U_SUCCESS(status) ) {
- newRules->parseDescription((UnicodeString &)description, rules, status);
- if (U_SUCCESS(status)) {
- newRules->addRules(rules);
- }
+ if (U_SUCCESS(status) && newRules == NULL) {
+ status = U_MEMORY_ALLOCATION_ERROR;
}
+ parser.parse(description, newRules, status);
if (U_FAILURE(status)) {
delete newRules;
- return NULL;
- }
- else {
- return newRules;
+ newRules = NULL;
}
+ return newRules;
}
+
PluralRules* U_EXPORT2
PluralRules::createDefaultRules(UErrorCode& status) {
return createRules(UnicodeString(TRUE, PLURAL_DEFAULT_RULE, -1), status);
PluralRules* U_EXPORT2
PluralRules::forLocale(const Locale& locale, UPluralType type, UErrorCode& status) {
- RuleChain rChain;
if (U_FAILURE(status)) {
return NULL;
}
return NULL;
}
UnicodeString locRule = newObj->getRuleFromResource(locale, type, status);
- if ((locRule.length() != 0) && U_SUCCESS(status)) {
- newObj->parseDescription(locRule, rChain, status);
- if (U_SUCCESS(status)) {
- newObj->addRules(rChain);
- }
- }
- if (U_FAILURE(status)||(locRule.length() == 0)) {
- // use default plural rule
+ // TODO: which errors, if any, should be returned?
+ if (locRule.length() == 0) {
+ // Locales with no specific rules (all numbers have the "other" category
+ // will return a U_MISSING_RESOURCE_ERROR at this point. This is not
+ // an error.
+ locRule = UnicodeString(PLURAL_DEFAULT_RULE);
status = U_ZERO_ERROR;
- UnicodeString defRule = UnicodeString(PLURAL_DEFAULT_RULE);
- newObj->parseDescription(defRule, rChain, status);
- newObj->addRules(rChain);
}
+ PluralRuleParser parser;
+ parser.parse(locRule, newObj, status);
+ // TODO: should rule parse errors be returned, or
+ // should we silently use default rules?
+ // Original impl used default rules.
+ // Ask the question to ICU Core.
return newObj;
}
UnicodeString
PluralRules::select(int32_t number) const {
- return select(NumberInfo(number));
+ return select(FixedDecimal(number));
}
UnicodeString
PluralRules::select(double number) const {
- return select(NumberInfo(number));
+ return select(FixedDecimal(number));
}
UnicodeString
-PluralRules::select(const NumberInfo &number) const {
+PluralRules::select(const FixedDecimal &number) const {
if (mRules == NULL) {
return UnicodeString(TRUE, PLURAL_DEFAULT_RULE, -1);
}
}
double
-PluralRules::getUniqueKeywordValue(const UnicodeString& keyword) {
- double val = 0.0;
- UErrorCode status = U_ZERO_ERROR;
- int32_t count = getSamplesInternal(keyword, &val, 1, FALSE, status);
- return count == 1 ? val : UPLRULES_NO_UNIQUE_VALUE;
-}
-
-int32_t
-PluralRules::getAllKeywordValues(const UnicodeString &keyword, double *dest,
- int32_t destCapacity, UErrorCode& error) {
- return getSamplesInternal(keyword, dest, destCapacity, FALSE, error);
+PluralRules::getUniqueKeywordValue(const UnicodeString& /* keyword */) {
+ // Not Implemented.
+ return UPLRULES_NO_UNIQUE_VALUE;
}
int32_t
-PluralRules::getSamples(const UnicodeString &keyword, double *dest,
- int32_t destCapacity, UErrorCode& status) {
- return getSamplesInternal(keyword, dest, destCapacity, TRUE, status);
+PluralRules::getAllKeywordValues(const UnicodeString & /* keyword */, double * /* dest */,
+ int32_t /* destCapacity */, UErrorCode& error) {
+ error = U_UNSUPPORTED_ERROR;
+ return 0;
}
-int32_t
-PluralRules::getSamplesInternal(const UnicodeString &keyword, double *dest,
- int32_t destCapacity, UBool includeUnlimited,
- UErrorCode& status) {
- initSamples(status);
- if (U_FAILURE(status)) {
- return -1;
- }
- if (destCapacity < 0 || (dest == NULL && destCapacity > 0)) {
- status = U_ILLEGAL_ARGUMENT_ERROR;
- return -1;
+
+static double scaleForInt(double d) {
+ double scale = 1.0;
+ while (d != floor(d)) {
+ d = d * 10.0;
+ scale = scale * 10.0;
}
+ return scale;
+}
- int32_t index = getKeywordIndex(keyword, status);
- if (index == -1) {
- return 0;
- }
+static int32_t
+getSamplesFromString(const UnicodeString &samples, double *dest,
+ int32_t destCapacity, UErrorCode& status) {
+ int32_t sampleCount = 0;
+ int32_t sampleStartIdx = 0;
+ int32_t sampleEndIdx = 0;
+
+ //std::string ss; // TODO: debugging.
+ // std::cout << "PluralRules::getSamples(), samples = \"" << samples.toUTF8String(ss) << "\"\n";
+ for (sampleCount = 0; sampleCount < destCapacity && sampleStartIdx < samples.length(); ) {
+ sampleEndIdx = samples.indexOf(COMMA, sampleStartIdx);
+ if (sampleEndIdx == -1) {
+ sampleEndIdx = samples.length();
+ }
+ const UnicodeString &sampleRange = samples.tempSubStringBetween(sampleStartIdx, sampleEndIdx);
+ // ss.erase();
+ // std::cout << "PluralRules::getSamples(), samplesRange = \"" << sampleRange.toUTF8String(ss) << "\"\n";
+ int32_t tildeIndex = sampleRange.indexOf(TILDE);
+ if (tildeIndex < 0) {
+ FixedDecimal fixed(sampleRange, status);
+ double sampleValue = fixed.source;
+ if (fixed.visibleDecimalDigitCount == 0 || sampleValue != floor(sampleValue)) {
+ dest[sampleCount++] = sampleValue;
+ }
+ } else {
+
+ FixedDecimal fixedLo(sampleRange.tempSubStringBetween(0, tildeIndex), status);
+ FixedDecimal fixedHi(sampleRange.tempSubStringBetween(tildeIndex+1), status);
+ double rangeLo = fixedLo.source;
+ double rangeHi = fixedHi.source;
+ if (U_FAILURE(status)) {
+ break;
+ }
+ if (rangeHi < rangeLo) {
+ status = U_INVALID_FORMAT_ERROR;
+ break;
+ }
- const int32_t LIMIT_MASK = 0x1 << 31;
+ // For ranges of samples with fraction decimal digits, scale the number up so that we
+ // are adding one in the units place. Avoids roundoffs from repetitive adds of tenths.
- if (!includeUnlimited) {
- if ((mSampleInfo[index] & LIMIT_MASK) == 0) {
- return -1;
+ double scale = scaleForInt(rangeLo);
+ double t = scaleForInt(rangeHi);
+ if (t > scale) {
+ scale = t;
+ }
+ rangeLo *= scale;
+ rangeHi *= scale;
+ for (double n=rangeLo; n<=rangeHi; n+=1) {
+ // Hack Alert: don't return any decimal samples with integer values that
+ // originated from a format with trailing decimals.
+ // This API is returning doubles, which can't distinguish having displayed
+ // zeros to the right of the decimal.
+ // This results in test failures with values mapping back to a different keyword.
+ double sampleValue = n/scale;
+ if (!(sampleValue == floor(sampleValue) && fixedLo.visibleDecimalDigitCount > 0)) {
+ dest[sampleCount++] = sampleValue;
+ }
+ if (sampleCount >= destCapacity) {
+ break;
+ }
+ }
}
+ sampleStartIdx = sampleEndIdx + 1;
}
+ return sampleCount;
+}
- int32_t start = index == 0 ? 0 : mSampleInfo[index - 1] & ~LIMIT_MASK;
- int32_t limit = mSampleInfo[index] & ~LIMIT_MASK;
- int32_t len = limit - start;
- if (len <= destCapacity) {
- destCapacity = len;
- } else if (includeUnlimited) {
- len = destCapacity; // no overflow, and don't report more than we copy
- } else {
- status = U_BUFFER_OVERFLOW_ERROR;
- return len;
+
+int32_t
+PluralRules::getSamples(const UnicodeString &keyword, double *dest,
+ int32_t destCapacity, UErrorCode& status) {
+ RuleChain *rc = rulesForKeyword(keyword);
+ if (rc == NULL || destCapacity == 0 || U_FAILURE(status)) {
+ return 0;
}
- for (int32_t i = 0; i < destCapacity; ++i, ++start) {
- dest[i] = mSamples[start];
+ int32_t numSamples = getSamplesFromString(rc->fIntegerSamples, dest, destCapacity, status);
+ if (numSamples == 0) {
+ numSamples = getSamplesFromString(rc->fDecimalSamples, dest, destCapacity, status);
}
- return len;
+ return numSamples;
+}
+
+
+RuleChain *PluralRules::rulesForKeyword(const UnicodeString &keyword) const {
+ RuleChain *rc;
+ for (rc = mRules; rc != NULL; rc = rc->fNext) {
+ if (rc->fKeyword == keyword) {
+ break;
+ }
+ }
+ return rc;
}
if (0 == keyword.compare(PLURAL_KEYWORD_OTHER, 5)) {
return true;
}
- else {
- if (mRules==NULL) {
- return false;
- }
- else {
- return mRules->isKeyword(keyword);
- }
- }
+ return rulesForKeyword(keyword) != NULL;
}
UnicodeString
return TRUE;
}
+
void
-PluralRules::parseDescription(UnicodeString& data, RuleChain& rules, UErrorCode &status)
+PluralRuleParser::parse(const UnicodeString& ruleData, PluralRules *prules, UErrorCode &status)
{
- int32_t ruleIndex=0;
- UnicodeString token;
- tokenType type;
- tokenType prevType=none;
- RuleChain *ruleChain=NULL;
- AndConstraint *curAndConstraint=NULL;
- OrConstraint *orNode=NULL;
- RuleChain *lastChain=NULL;
- int32_t rangeLowIdx = -1; // Indices in the UVector of ranges of the
- int32_t rangeHiIdx = -1; // low and hi values currently being parsed.
-
if (U_FAILURE(status)) {
return;
}
- UnicodeString ruleData = data.toLower("");
- while (ruleIndex< ruleData.length()) {
- mParser->getNextToken(ruleData, &ruleIndex, token, type, status);
+ U_ASSERT(ruleIndex == 0); // Parsers are good for a single use only!
+ ruleSrc = &ruleData;
+
+ while (ruleIndex< ruleSrc->length()) {
+ getNextToken(status);
if (U_FAILURE(status)) {
return;
}
- mParser->checkSyntax(prevType, type, status);
+ checkSyntax(status);
if (U_FAILURE(status)) {
return;
}
curAndConstraint = curAndConstraint->add();
break;
case tOr:
- lastChain = &rules;
- while (lastChain->next !=NULL) {
- lastChain = lastChain->next;
- }
- orNode=lastChain->ruleHeader;
- while (orNode->next != NULL) {
- orNode = orNode->next;
+ {
+ U_ASSERT(currentChain != NULL);
+ OrConstraint *orNode=currentChain->ruleHeader;
+ while (orNode->next != NULL) {
+ orNode = orNode->next;
+ }
+ orNode->next= new OrConstraint();
+ orNode=orNode->next;
+ orNode->next=NULL;
+ curAndConstraint = orNode->add();
}
- orNode->next= new OrConstraint();
- orNode=orNode->next;
- orNode->next=NULL;
- curAndConstraint = orNode->add();
break;
case tIs:
U_ASSERT(curAndConstraint != NULL);
U_ASSERT(curAndConstraint != NULL);
curAndConstraint->negated=TRUE;
break;
+
+ case tNotEqual:
+ curAndConstraint->negated=TRUE;
case tIn:
case tWithin:
+ case tEqual:
U_ASSERT(curAndConstraint != NULL);
curAndConstraint->rangeList = new UVector32(status);
curAndConstraint->rangeList->addElement(-1, status); // range Low
}
else {
curAndConstraint->rangeList->setElementAt(getNumberValue(token), rangeHiIdx);
+ if (curAndConstraint->rangeList->elementAti(rangeLowIdx) >
+ curAndConstraint->rangeList->elementAti(rangeHiIdx)) {
+ // Range Lower bound > Range Upper bound.
+ // U_UNEXPECTED_TOKEN seems a little funny, but it is consistently
+ // used for all plural rule parse errors.
+ status = U_UNEXPECTED_TOKEN;
+ break;
+ }
}
}
}
break;
case tComma:
// TODO: rule syntax checking is inadequate, can happen with badly formed rules.
- // The fix is a redone parser.
+ // Catch cases like "n mod 10, is 1" here instead.
if (curAndConstraint == NULL || curAndConstraint->rangeList == NULL) {
- status = U_PARSE_ERROR;
+ status = U_UNEXPECTED_TOKEN;
break;
}
U_ASSERT(curAndConstraint->rangeList->size() >= 2);
case tVariableF:
case tVariableT:
case tVariableV:
- case tVariableJ:
U_ASSERT(curAndConstraint != NULL);
curAndConstraint->digitsType = type;
break;
case tKeyword:
- if (ruleChain==NULL) {
- ruleChain = &rules;
+ {
+ RuleChain *newChain = new RuleChain;
+ if (newChain == NULL) {
+ status = U_MEMORY_ALLOCATION_ERROR;
+ break;
}
- else {
- while (ruleChain->next!=NULL){
- ruleChain=ruleChain->next;
+ newChain->fKeyword = token;
+ if (prules->mRules == NULL) {
+ prules->mRules = newChain;
+ } else {
+ // The new rule chain goes at the end of the linked list of rule chains,
+ // unless there is an "other" keyword & chain. "other" must remain last.
+ RuleChain *insertAfter = prules->mRules;
+ while (insertAfter->fNext!=NULL &&
+ insertAfter->fNext->fKeyword.compare(PLURAL_KEYWORD_OTHER, 5) != 0 ){
+ insertAfter=insertAfter->fNext;
}
- ruleChain=ruleChain->next=new RuleChain();
+ newChain->fNext = insertAfter->fNext;
+ insertAfter->fNext = newChain;
}
- if (ruleChain->ruleHeader != NULL) {
- delete ruleChain->ruleHeader;
- }
- orNode = ruleChain->ruleHeader = new OrConstraint();
+ OrConstraint *orNode = new OrConstraint();
+ newChain->ruleHeader = orNode;
curAndConstraint = orNode->add();
- ruleChain->keyword = token;
- break;
- default:
- break;
- }
- prevType=type;
- if (U_FAILURE(status)) {
+ currentChain = newChain;
+ }
break;
- }
- }
-}
-
-int32_t
-PluralRules::getNumberValue(const UnicodeString& token) const {
- int32_t i;
- char digits[128];
-
- i = token.extract(0, token.length(), digits, ARRAY_SIZE(digits), US_INV);
- digits[i]='\0';
-
- return((int32_t)atoi(digits));
-}
-
-
-void
-PluralRules::getNextLocale(const UnicodeString& localeData, int32_t* curIndex, UnicodeString& localeName) {
- int32_t i=*curIndex;
-
- localeName.remove();
- while (i< localeData.length()) {
- if ( (localeData.charAt(i)!= SPACE) && (localeData.charAt(i)!= COMMA) ) {
- break;
- }
- i++;
- }
-
- while (i< localeData.length()) {
- if ( (localeData.charAt(i)== SPACE) || (localeData.charAt(i)== COMMA) ) {
- break;
- }
- localeName+=localeData.charAt(i++);
- }
- *curIndex=i;
-}
-
-int32_t
-PluralRules::getKeywordIndex(const UnicodeString& keyword,
- UErrorCode& status) const {
- if (U_SUCCESS(status)) {
- int32_t n = 0;
- RuleChain* rc = mRules;
- while (rc != NULL) {
- if (rc->ruleHeader != NULL) {
- if (rc->keyword == keyword) {
- return n;
+ case tInteger:
+ for (;;) {
+ getNextToken(status);
+ if (U_FAILURE(status) || type == tSemiColon || type == tEOF || type == tAt) {
+ break;
}
- ++n;
- }
- rc = rc->next;
- }
- if (0 == keyword.compare(PLURAL_KEYWORD_OTHER, 5)) {
- return n;
- }
- }
- return -1;
-}
-
-typedef struct SampleRecord {
- int32_t ruleIndex;
- double value;
-} SampleRecord;
-
-void
-PluralRules::initSamples(UErrorCode& status) {
- if (U_FAILURE(status)) {
- return;
- }
- Mutex lock(&pluralMutex);
-
- if (mSamples) {
- return;
- }
-
- // Note, the original design let you have multiple rules with the same keyword. But
- // we don't use that in our data and existing functions in this implementation don't
- // fully support it (for example, the returned keywords is a list and not a set).
- //
- // So I don't support this here either. If you ask for samples, or for all values,
- // you will get information about the first rule with that keyword, not all rules with
- // that keyword.
-
- int32_t maxIndex = 0;
- int32_t otherIndex = -1; // the value -1 will indicate we added 'other' at end
- RuleChain* rc = mRules;
- while (rc != NULL) {
- if (rc->ruleHeader != NULL) {
- if (otherIndex == -1 && 0 == rc->keyword.compare(PLURAL_KEYWORD_OTHER, 5)) {
- otherIndex = maxIndex;
+ if (type == tEllipsis) {
+ currentChain->fIntegerSamplesUnbounded = TRUE;
+ continue;
+ }
+ currentChain->fIntegerSamples.append(token);
}
- ++maxIndex;
- }
- rc = rc->next;
- }
- if (otherIndex == -1) {
- ++maxIndex;
- }
-
- LocalMemory<int32_t> newSampleInfo;
- if (NULL == newSampleInfo.allocateInsteadAndCopy(maxIndex)) {
- status = U_MEMORY_ALLOCATION_ERROR;
- return;
- }
-
- const int32_t LIMIT_MASK = 0x1 << 31;
-
- rc = mRules;
- int32_t n = 0;
- while (rc != NULL) {
- if (rc->ruleHeader != NULL) {
- newSampleInfo[n++] = rc->ruleHeader->isLimited() ? LIMIT_MASK : 0;
- }
- rc = rc->next;
- }
- if (otherIndex == -1) {
- newSampleInfo[maxIndex - 1] = 0; // unlimited
- }
-
- MaybeStackArray<SampleRecord, 10> newSamples;
- int32_t sampleCount = 0;
-
- int32_t limit = 10;
-
- for (int i = 0, keywordsRemaining = maxIndex;
- keywordsRemaining > 0 && i < limit;
- ++i) {
- double val = i / 2.0;
+ break;
- n = 0;
- rc = mRules;
- int32_t found = -1;
- while (rc != NULL) {
- if (rc->ruleHeader != NULL) {
- if (rc->ruleHeader->isFulfilled(NumberInfo(val))) {
- found = n;
+ case tDecimal:
+ for (;;) {
+ getNextToken(status);
+ if (U_FAILURE(status) || type == tSemiColon || type == tEOF || type == tAt) {
break;
}
- ++n;
- }
- rc = rc->next;
- }
- if (found == -1) {
- // 'other'. If there is an 'other' rule, the rule set is bad since nothing
- // should leak through, but we don't bother to report that here.
- found = otherIndex == -1 ? maxIndex - 1 : otherIndex;
- }
- if (newSampleInfo[found] == MAX_SAMPLES) { // limit flag not set
- continue;
- }
- newSampleInfo[found] += 1; // won't impact limit flag
-
- if (sampleCount == newSamples.getCapacity()) {
- int32_t newCapacity = sampleCount < 20 ? 128 : sampleCount * 2;
- if (NULL == newSamples.resize(newCapacity, sampleCount)) {
- status = U_MEMORY_ALLOCATION_ERROR;
- return;
+ if (type == tEllipsis) {
+ currentChain->fDecimalSamplesUnbounded = TRUE;
+ continue;
+ }
+ currentChain->fDecimalSamples.append(token);
}
+ break;
+
+ default:
+ break;
}
- newSamples[sampleCount].ruleIndex = found;
- newSamples[sampleCount].value = val;
- ++sampleCount;
-
- if (newSampleInfo[found] == MAX_SAMPLES) { // limit flag not set
- --keywordsRemaining;
- }
- }
-
- // sort the values by index, leaving order otherwise unchanged
- // this is just a selection sort for simplicity
- LocalMemory<double> values;
- if (NULL == values.allocateInsteadAndCopy(sampleCount)) {
- status = U_MEMORY_ALLOCATION_ERROR;
- return;
- }
- for (int i = 0, j = 0; i < maxIndex; ++i) {
- for (int k = 0; k < sampleCount; ++k) {
- if (newSamples[k].ruleIndex == i) {
- values[j++] = newSamples[k].value;
- }
+ prevType=type;
+ if (U_FAILURE(status)) {
+ break;
}
}
-
- // convert array of mask/lengths to array of mask/limits
- limit = 0;
- for (int i = 0; i < maxIndex; ++i) {
- int32_t info = newSampleInfo[i];
- int32_t len = info & ~LIMIT_MASK;
- limit += len;
- // if a rule is 'unlimited' but has fewer than MAX_SAMPLES samples,
- // it's not really unlimited, so mark it as limited
- int32_t mask = len < MAX_SAMPLES ? LIMIT_MASK : info & LIMIT_MASK;
- newSampleInfo[i] = limit | mask;
- }
-
- // ok, we've got good data
- mSamples = values.orphan();
- mSampleInfo = newSampleInfo.orphan();
- mSampleInfoCount = maxIndex;
-}
-
-void
-PluralRules::addRules(RuleChain& rules) {
- RuleChain *newRule = new RuleChain(rules);
- U_ASSERT(this->mRules == NULL);
- this->mRules=newRule;
}
UnicodeString
}
char setKey[256];
- UChar result[256];
u_UCharsToChars(s, setKey, resLen + 1);
// printf("\n PluralRule: %s\n", setKey);
-
LocalUResourceBundlePointer ruleRes(ures_getByKey(rb.getAlias(), "rules", NULL, &errCode));
if(U_FAILURE(errCode)) {
return emptyStr;
}
- resLen=0;
LocalUResourceBundlePointer setRes(ures_getByKey(ruleRes.getAlias(), setKey, NULL, &errCode));
if (U_FAILURE(errCode)) {
return emptyStr;
}
int32_t numberKeys = ures_getSize(setRes.getAlias());
- char *key=NULL;
- int32_t len=0;
- for(int32_t i=0; i<numberKeys; ++i) {
- int32_t keyLen;
- resLen=0;
- s=ures_getNextString(setRes.getAlias(), &resLen, (const char**)&key, &errCode);
- keyLen = (int32_t)uprv_strlen(key);
- u_charsToUChars(key, result+len, keyLen);
- len += keyLen;
- result[len++]=COLON;
- uprv_memcpy(result+len, s, resLen*sizeof(UChar));
- len += resLen;
- result[len++]=SEMI_COLON;
- }
- result[len++]=0;
- u_UCharsToChars(result, setKey, len);
- // printf(" Rule: %s\n", setKey);
-
- return UnicodeString(result);
+ UnicodeString result;
+ const char *key=NULL;
+ for(int32_t i=0; i<numberKeys; ++i) { // Keys are zero, one, few, ...
+ UnicodeString rules = ures_getNextUnicodeString(setRes.getAlias(), &key, &errCode);
+ UnicodeString uKey(key, -1, US_INV);
+ result.append(uKey);
+ result.append(COLON);
+ result.append(rules);
+ result.append(SEMI_COLON);
+ }
+ return result;
}
+
+UnicodeString
+PluralRules::getRules() const {
+ UnicodeString rules;
+ if (mRules != NULL) {
+ mRules->dumpRules(rules);
+ }
+ return rules;
+}
+
+
AndConstraint::AndConstraint() {
op = AndConstraint::NONE;
opNum=-1;
UBool
-AndConstraint::isFulfilled(const NumberInfo &number) {
+AndConstraint::isFulfilled(const FixedDecimal &number) {
UBool result = TRUE;
+ if (digitsType == none) {
+ // An empty AndConstraint, created by a rule with a keyword but no following expression.
+ return TRUE;
+ }
double n = number.get(digitsType); // pulls n | i | v | f value for the number.
// Will always be positive.
// May be non-integer (n option only)
do {
- if ((integerOnly && n != uprv_floor(n)) ||
- (digitsType == tVariableJ && number.getVisibleFractionDigitCount()) != 0) {
+ if (integerOnly && n != uprv_floor(n)) {
result = FALSE;
break;
}
return result;
}
-UBool
-AndConstraint::isLimited() {
- return (rangeList == NULL || integerOnly) && !negated && op != MOD;
-}
AndConstraint*
AndConstraint::add()
}
UBool
-OrConstraint::isFulfilled(const NumberInfo &number) {
+OrConstraint::isFulfilled(const FixedDecimal &number) {
OrConstraint* orRule=this;
UBool result=FALSE;
return result;
}
-UBool
-OrConstraint::isLimited() {
- for (OrConstraint *orc = this; orc != NULL; orc = orc->next) {
- UBool result = FALSE;
- for (AndConstraint *andc = orc->childNode; andc != NULL; andc = andc->next) {
- if (andc->isLimited()) {
- result = TRUE;
- break;
- }
- }
- if (result == FALSE) {
- return FALSE;
- }
- }
- return TRUE;
-}
-RuleChain::RuleChain() {
- ruleHeader=NULL;
- next = NULL;
+RuleChain::RuleChain(): fKeyword(), fNext(NULL), ruleHeader(NULL), fDecimalSamples(), fIntegerSamples(),
+ fDecimalSamplesUnbounded(FALSE), fIntegerSamplesUnbounded(FALSE) {
}
-RuleChain::RuleChain(const RuleChain& other) {
- this->keyword=other.keyword;
+RuleChain::RuleChain(const RuleChain& other) :
+ fKeyword(other.fKeyword), fNext(NULL), ruleHeader(NULL), fDecimalSamples(other.fDecimalSamples),
+ fIntegerSamples(other.fIntegerSamples), fDecimalSamplesUnbounded(other.fDecimalSamplesUnbounded),
+ fIntegerSamplesUnbounded(other.fIntegerSamplesUnbounded) {
if (other.ruleHeader != NULL) {
this->ruleHeader = new OrConstraint(*(other.ruleHeader));
}
- else {
- this->ruleHeader = NULL;
- }
- if (other.next != NULL ) {
- this->next = new RuleChain(*other.next);
- }
- else
- {
- this->next = NULL;
+ if (other.fNext != NULL ) {
+ this->fNext = new RuleChain(*other.fNext);
}
}
RuleChain::~RuleChain() {
- if (next != NULL) {
- delete next;
- }
- if ( ruleHeader != NULL ) {
- delete ruleHeader;
- }
+ delete fNext;
+ delete ruleHeader;
}
UnicodeString
-RuleChain::select(const NumberInfo &number) const {
- for (const RuleChain *rules = this; rules != NULL; rules = rules->next) {
+RuleChain::select(const FixedDecimal &number) const {
+ for (const RuleChain *rules = this; rules != NULL; rules = rules->fNext) {
if (rules->ruleHeader->isFulfilled(number)) {
- return rules->keyword;
+ return rules->fKeyword;
}
}
return UnicodeString(TRUE, PLURAL_KEYWORD_OTHER, 5);
}
+static UnicodeString tokenString(tokenType tok) {
+ UnicodeString s;
+ switch (tok) {
+ case tVariableN:
+ s.append(LOW_N); break;
+ case tVariableI:
+ s.append(LOW_I); break;
+ case tVariableF:
+ s.append(LOW_F); break;
+ case tVariableV:
+ s.append(LOW_V); break;
+ case tVariableT:
+ s.append(LOW_T); break;
+ default:
+ s.append(TILDE);
+ }
+ return s;
+}
+
void
RuleChain::dumpRules(UnicodeString& result) {
UChar digitString[16];
if ( ruleHeader != NULL ) {
- result += keyword;
+ result += fKeyword;
+ result += COLON;
+ result += SPACE;
OrConstraint* orRule=ruleHeader;
while ( orRule != NULL ) {
AndConstraint* andRule=orRule->childNode;
while ( andRule != NULL ) {
- if ( (andRule->op==AndConstraint::NONE) && (andRule->rangeList==NULL) ) {
- result += UNICODE_STRING_SIMPLE(" n is ");
+ if ((andRule->op==AndConstraint::NONE) && (andRule->rangeList==NULL) && (andRule->value == -1)) {
+ // Empty Rules.
+ } else if ( (andRule->op==AndConstraint::NONE) && (andRule->rangeList==NULL) ) {
+ result += tokenString(andRule->digitsType);
+ result += UNICODE_STRING_SIMPLE(" is ");
if (andRule->negated) {
result += UNICODE_STRING_SIMPLE("not ");
}
result += UnicodeString(digitString);
}
else {
+ result += tokenString(andRule->digitsType);
+ result += SPACE;
if (andRule->op==AndConstraint::MOD) {
- result += UNICODE_STRING_SIMPLE(" n mod ");
+ result += UNICODE_STRING_SIMPLE("mod ");
uprv_itou(digitString,16, andRule->opNum,10,0);
result += UnicodeString(digitString);
}
- else {
- result += UNICODE_STRING_SIMPLE(" n ");
- }
if (andRule->rangeList==NULL) {
if (andRule->negated) {
result += UNICODE_STRING_SIMPLE(" is not ");
else {
if (andRule->negated) {
if ( andRule->integerOnly ) {
- result += UNICODE_STRING_SIMPLE(" not in ");
+ result += UNICODE_STRING_SIMPLE(" not in ");
}
else {
- result += UNICODE_STRING_SIMPLE(" not within ");
+ result += UNICODE_STRING_SIMPLE(" not within ");
}
}
else {
int32_t rangeHi = andRule->rangeList->elementAti(r+1);
uprv_itou(digitString,16, rangeLo, 10, 0);
result += UnicodeString(digitString);
- if (rangeLo != rangeHi) {
- result += UNICODE_STRING_SIMPLE(" .. ");
- uprv_itou(digitString,16, rangeHi, 10,0);
- }
- if (r+2 <= andRule->rangeList->size()) {
+ result += UNICODE_STRING_SIMPLE("..");
+ uprv_itou(digitString,16, rangeHi, 10,0);
+ result += UnicodeString(digitString);
+ if (r+2 < andRule->rangeList->size()) {
result += UNICODE_STRING_SIMPLE(", ");
}
}
}
}
if ( (andRule=andRule->next) != NULL) {
- result.append(PK_AND, 3);
+ result += UNICODE_STRING_SIMPLE(" and ");
}
}
if ( (orRule = orRule->next) != NULL ) {
- result.append(PK_OR, 2);
+ result += UNICODE_STRING_SIMPLE(" or ");
}
}
}
- if ( next != NULL ) {
- next->dumpRules(result);
+ if ( fNext != NULL ) {
+ result += UNICODE_STRING_SIMPLE("; ");
+ fNext->dumpRules(result);
}
}
UErrorCode
RuleChain::getKeywords(int32_t capacityOfKeywords, UnicodeString* keywords, int32_t& arraySize) const {
if ( arraySize < capacityOfKeywords-1 ) {
- keywords[arraySize++]=keyword;
+ keywords[arraySize++]=fKeyword;
}
else {
return U_BUFFER_OVERFLOW_ERROR;
}
- if ( next != NULL ) {
- return next->getKeywords(capacityOfKeywords, keywords, arraySize);
+ if ( fNext != NULL ) {
+ return fNext->getKeywords(capacityOfKeywords, keywords, arraySize);
}
else {
return U_ZERO_ERROR;
UBool
RuleChain::isKeyword(const UnicodeString& keywordParam) const {
- if ( keyword == keywordParam ) {
+ if ( fKeyword == keywordParam ) {
return TRUE;
}
- if ( next != NULL ) {
- return next->isKeyword(keywordParam);
+ if ( fNext != NULL ) {
+ return fNext->isKeyword(keywordParam);
}
else {
return FALSE;
}
-RuleParser::RuleParser() {
+PluralRuleParser::PluralRuleParser() :
+ ruleIndex(0), token(), type(none), prevType(none),
+ curAndConstraint(NULL), currentChain(NULL), rangeLowIdx(-1), rangeHiIdx(-1)
+{
}
-RuleParser::~RuleParser() {
+PluralRuleParser::~PluralRuleParser() {
}
+
+int32_t
+PluralRuleParser::getNumberValue(const UnicodeString& token) {
+ int32_t i;
+ char digits[128];
+
+ i = token.extract(0, token.length(), digits, ARRAY_SIZE(digits), US_INV);
+ digits[i]='\0';
+
+ return((int32_t)atoi(digits));
+}
+
+
void
-RuleParser::checkSyntax(tokenType prevType, tokenType curType, UErrorCode &status)
+PluralRuleParser::checkSyntax(UErrorCode &status)
{
if (U_FAILURE(status)) {
return;
}
+ if (!(prevType==none || prevType==tSemiColon)) {
+ type = getKeyType(token, type); // Switch token type from tKeyword if we scanned a reserved word,
+ // and we are not at the start of a rule, where a
+ // keyword is expected.
+ }
+
switch(prevType) {
case none:
case tSemiColon:
- if (curType!=tKeyword && curType != tEOF) {
+ if (type!=tKeyword && type != tEOF) {
status = U_UNEXPECTED_TOKEN;
}
break;
case tVariableF:
case tVariableT:
case tVariableV:
- case tVariableJ:
- if (curType != tIs && curType != tMod && curType != tIn &&
- curType != tNot && curType != tWithin) {
+ if (type != tIs && type != tMod && type != tIn &&
+ type != tNot && type != tWithin && type != tEqual && type != tNotEqual) {
status = U_UNEXPECTED_TOKEN;
}
break;
case tKeyword:
- if (curType != tColon) {
+ if (type != tColon) {
status = U_UNEXPECTED_TOKEN;
}
break;
case tColon:
- if (!(curType == tVariableN ||
- curType == tVariableI ||
- curType == tVariableF ||
- curType == tVariableT ||
- curType == tVariableV ||
- curType == tVariableJ)) {
+ if (!(type == tVariableN ||
+ type == tVariableI ||
+ type == tVariableF ||
+ type == tVariableT ||
+ type == tVariableV ||
+ type == tAt)) {
status = U_UNEXPECTED_TOKEN;
}
break;
case tIs:
- if ( curType != tNumber && curType != tNot) {
+ if ( type != tNumber && type != tNot) {
status = U_UNEXPECTED_TOKEN;
}
break;
case tNot:
- if (curType != tNumber && curType != tIn && curType != tWithin) {
+ if (type != tNumber && type != tIn && type != tWithin) {
status = U_UNEXPECTED_TOKEN;
}
break;
case tMod:
- case tDot:
+ case tDot2:
case tIn:
case tWithin:
- case tAnd: // TODO: split of And and Or, which are different.
+ case tEqual:
+ case tNotEqual:
+ if (type != tNumber) {
+ status = U_UNEXPECTED_TOKEN;
+ }
+ break;
+ case tAnd:
case tOr:
- if (curType != tNumber &&
- curType != tVariableN &&
- curType != tVariableI &&
- curType != tVariableF &&
- curType != tVariableT &&
- curType != tVariableV &&
- curType != tVariableJ) {
+ if ( type != tVariableN &&
+ type != tVariableI &&
+ type != tVariableF &&
+ type != tVariableT &&
+ type != tVariableV) {
status = U_UNEXPECTED_TOKEN;
}
break;
case tComma:
- if (curType != tNumber) {
+ if (type != tNumber) {
status = U_UNEXPECTED_TOKEN;
}
break;
case tNumber:
- if (curType != tDot && curType != tSemiColon && curType != tIs && curType != tNot &&
- curType != tIn && curType != tWithin && curType != tAnd && curType != tOr &&
- curType != tComma && curType != tEOF)
+ if (type != tDot2 && type != tSemiColon && type != tIs && type != tNot &&
+ type != tIn && type != tEqual && type != tNotEqual && type != tWithin &&
+ type != tAnd && type != tOr && type != tComma && type != tAt &&
+ type != tEOF)
{
status = U_UNEXPECTED_TOKEN;
}
// TODO: a comma following a number that is not part of a range will be allowed.
// It's not the only case of this sort of thing. Parser needs a re-write.
break;
+ case tAt:
+ if (type != tDecimal && type != tInteger) {
+ status = U_UNEXPECTED_TOKEN;
+ }
+ break;
default:
status = U_UNEXPECTED_TOKEN;
break;
}
}
+
+/*
+ * Scan the next token from the input rules.
+ * rules and returned token type are in the parser state variables.
+ */
void
-RuleParser::getNextToken(const UnicodeString& ruleData,
- int32_t *ruleIndex,
- UnicodeString& token,
- tokenType& type,
- UErrorCode &status)
+PluralRuleParser::getNextToken(UErrorCode &status)
{
- int32_t curIndex= *ruleIndex;
- UChar ch;
- tokenType prevType=none;
-
if (U_FAILURE(status)) {
return;
}
- while (curIndex<ruleData.length()) {
- ch = ruleData.charAt(curIndex);
- if ( !inRange(ch, type) ) {
- status = U_ILLEGAL_CHARACTER;
- return;
- }
- switch (type) {
- case tSpace:
- if ( *ruleIndex != curIndex ) { // letter
- token=UnicodeString(ruleData, *ruleIndex, curIndex-*ruleIndex);
- *ruleIndex=curIndex;
- type=prevType;
- getKeyType(token, type, status);
- return;
- }
- else {
- *ruleIndex=*ruleIndex+1;
- if (*ruleIndex >= ruleData.length()) {
- type = tEOF;
- }
- }
- break; // consective space
- case tColon:
- case tSemiColon:
- case tComma:
- case tIn: // scanned '='
- case tNot: // scanned '!'
- case tMod: // scanned '%'
- if ( *ruleIndex != curIndex ) {
- token=UnicodeString(ruleData, *ruleIndex, curIndex-*ruleIndex);
- *ruleIndex=curIndex;
- type=prevType;
- getKeyType(token, type, status);
- return;
- }
- else {
- *ruleIndex=curIndex+1;
- return;
- }
- case tLetter:
- if ((type==prevType)||(prevType==none)) {
- prevType=type;
- break;
- }
- break;
- case tNumber:
- if ((type==prevType)||(prevType==none)) {
- prevType=type;
- break;
- }
- else {
- *ruleIndex=curIndex+1;
- return;
- }
- case tDot:
- if (prevType==none) { // first dot
- prevType=type;
- break;
- }
- else if (prevType == tDot) { // two consecutive dots. Return them
- *ruleIndex=curIndex+1; // without looking to see what follows.
- return;
- } else {
- // Encountered '.' while parsing something else
- // Return the something else.
- U_ASSERT( *ruleIndex != curIndex );
- token=UnicodeString(ruleData, *ruleIndex, curIndex-*ruleIndex);
- *ruleIndex=curIndex;
- type=prevType;
- getKeyType(token, type, status);
- return;
- }
- default:
- status = U_UNEXPECTED_TOKEN;
- return;
+
+ UChar ch;
+ while (ruleIndex < ruleSrc->length()) {
+ ch = ruleSrc->charAt(ruleIndex);
+ type = charType(ch);
+ if (type != tSpace) {
+ break;
}
- curIndex++;
+ ++(ruleIndex);
}
- if ( curIndex>=ruleData.length() ) {
- if ( (type == tLetter)||(type == tNumber) ) {
- token=UnicodeString(ruleData, *ruleIndex, curIndex-*ruleIndex);
- getKeyType(token, type, status);
- if (U_FAILURE(status)) {
- return;
- }
- }
- *ruleIndex = ruleData.length();
+ if (ruleIndex >= ruleSrc->length()) {
+ type = tEOF;
+ return;
}
-}
+ int32_t curIndex= ruleIndex;
+
+ switch (type) {
+ case tColon:
+ case tSemiColon:
+ case tComma:
+ case tEllipsis:
+ case tTilde: // scanned '~'
+ case tAt: // scanned '@'
+ case tEqual: // scanned '='
+ case tMod: // scanned '%'
+ // Single character tokens.
+ ++curIndex;
+ break;
-UBool
-RuleParser::inRange(UChar ch, tokenType& type) {
- if ((ch>=CAP_A) && (ch<=CAP_Z)) {
- // we assume all characters are in lower case already.
- return FALSE;
- }
- if ((ch>=LOW_A) && (ch<=LOW_Z)) {
- type = tLetter;
- return TRUE;
- }
+ case tNotEqual: // scanned '!'
+ if (ruleSrc->charAt(curIndex+1) == EQUALS) {
+ curIndex += 2;
+ } else {
+ type = none;
+ curIndex += 1;
+ }
+ break;
+
+ case tKeyword:
+ while (type == tKeyword && ++curIndex < ruleSrc->length()) {
+ ch = ruleSrc->charAt(curIndex);
+ type = charType(ch);
+ }
+ type = tKeyword;
+ break;
+
+ case tNumber:
+ while (type == tNumber && ++curIndex < ruleSrc->length()) {
+ ch = ruleSrc->charAt(curIndex);
+ type = charType(ch);
+ }
+ type = tNumber;
+ break;
+
+ case tDot:
+ // We could be looking at either ".." in a range, or "..." at the end of a sample.
+ if (curIndex+1 >= ruleSrc->length() || ruleSrc->charAt(curIndex+1) != DOT) {
+ ++curIndex;
+ break; // Single dot
+ }
+ if (curIndex+2 >= ruleSrc->length() || ruleSrc->charAt(curIndex+2) != DOT) {
+ curIndex += 2;
+ type = tDot2;
+ break; // double dot
+ }
+ type = tEllipsis;
+ curIndex += 3;
+ break; // triple dot
+
+ default:
+ status = U_UNEXPECTED_TOKEN;
+ ++curIndex;
+ break;
+ }
+
+ U_ASSERT(ruleIndex <= ruleSrc->length());
+ U_ASSERT(curIndex <= ruleSrc->length());
+ token=UnicodeString(*ruleSrc, ruleIndex, curIndex-ruleIndex);
+ ruleIndex = curIndex;
+}
+
+tokenType
+PluralRuleParser::charType(UChar ch) {
if ((ch>=U_ZERO) && (ch<=U_NINE)) {
- type = tNumber;
- return TRUE;
+ return tNumber;
+ }
+ if (ch>=LOW_A && ch<=LOW_Z) {
+ return tKeyword;
}
switch (ch) {
case COLON:
- type = tColon;
- return TRUE;
+ return tColon;
case SPACE:
- type = tSpace;
- return TRUE;
+ return tSpace;
case SEMI_COLON:
- type = tSemiColon;
- return TRUE;
+ return tSemiColon;
case DOT:
- type = tDot;
- return TRUE;
+ return tDot;
case COMMA:
- type = tComma;
- return TRUE;
+ return tComma;
case EXCLAMATION:
- type = tNot;
- return TRUE;
+ return tNotEqual;
case EQUALS:
- type = tIn;
- return TRUE;
+ return tEqual;
case PERCENT_SIGN:
- type = tMod;
- return TRUE;
+ return tMod;
+ case AT:
+ return tAt;
+ case ELLIPSIS:
+ return tEllipsis;
+ case TILDE:
+ return tTilde;
default :
- type = none;
- return FALSE;
+ return none;
}
}
-void
-RuleParser::getKeyType(const UnicodeString& token, tokenType& keyType, UErrorCode &status)
+// Set token type for reserved words in the Plural Rule syntax.
+
+tokenType
+PluralRuleParser::getKeyType(const UnicodeString &token, tokenType keyType)
{
- if (U_FAILURE(status)) {
- return;
- }
- if ( keyType==tNumber) {
+ if (keyType != tKeyword) {
+ return keyType;
}
- else if (0 == token.compare(PK_VAR_N, 1)) {
+
+ if (0 == token.compare(PK_VAR_N, 1)) {
keyType = tVariableN;
- }
- else if (0 == token.compare(PK_VAR_I, 1)) {
+ } else if (0 == token.compare(PK_VAR_I, 1)) {
keyType = tVariableI;
- }
- else if (0 == token.compare(PK_VAR_F, 1)) {
+ } else if (0 == token.compare(PK_VAR_F, 1)) {
keyType = tVariableF;
- }
- else if (0 == token.compare(PK_VAR_T, 1)) {
+ } else if (0 == token.compare(PK_VAR_T, 1)) {
keyType = tVariableT;
- }
- else if (0 == token.compare(PK_VAR_V, 1)) {
+ } else if (0 == token.compare(PK_VAR_V, 1)) {
keyType = tVariableV;
- }
- else if (0 == token.compare(PK_VAR_J, 1)) {
- keyType = tVariableJ;
- }
- else if (0 == token.compare(PK_IS, 2)) {
+ } else if (0 == token.compare(PK_IS, 2)) {
keyType = tIs;
- }
- else if (0 == token.compare(PK_AND, 3)) {
+ } else if (0 == token.compare(PK_AND, 3)) {
keyType = tAnd;
- }
- else if (0 == token.compare(PK_IN, 2)) {
+ } else if (0 == token.compare(PK_IN, 2)) {
keyType = tIn;
- }
- else if (0 == token.compare(PK_WITHIN, 6)) {
+ } else if (0 == token.compare(PK_WITHIN, 6)) {
keyType = tWithin;
- }
- else if (0 == token.compare(PK_NOT, 3)) {
+ } else if (0 == token.compare(PK_NOT, 3)) {
keyType = tNot;
- }
- else if (0 == token.compare(PK_MOD, 3)) {
+ } else if (0 == token.compare(PK_MOD, 3)) {
keyType = tMod;
- }
- else if (0 == token.compare(PK_OR, 2)) {
+ } else if (0 == token.compare(PK_OR, 2)) {
keyType = tOr;
+ } else if (0 == token.compare(PK_DECIMAL, 7)) {
+ keyType = tDecimal;
+ } else if (0 == token.compare(PK_INTEGER, 7)) {
+ keyType = tInteger;
}
- else if ( isValidKeyword(token) ) {
- keyType = tKeyword;
- }
- else {
- status = U_UNEXPECTED_TOKEN;
- }
+ return keyType;
}
-UBool
-RuleParser::isValidKeyword(const UnicodeString& token) {
- return PatternProps::isIdentifier(token.getBuffer(), token.length());
-}
PluralKeywordEnumeration::PluralKeywordEnumeration(RuleChain *header, UErrorCode& status)
: pos(0), fKeywordNames(status) {
UBool addKeywordOther=TRUE;
RuleChain *node=header;
while(node!=NULL) {
- fKeywordNames.addElement(new UnicodeString(node->keyword), status);
+ fKeywordNames.addElement(new UnicodeString(node->fKeyword), status);
if (U_FAILURE(status)) {
return;
}
- if (0 == node->keyword.compare(PLURAL_KEYWORD_OTHER, 5)) {
+ if (0 == node->fKeyword.compare(PLURAL_KEYWORD_OTHER, 5)) {
addKeywordOther= FALSE;
}
- node=node->next;
+ node=node->fNext;
}
if (addKeywordOther) {
-NumberInfo::NumberInfo(double n, int32_t v, int64_t f) {
+FixedDecimal::FixedDecimal(double n, int32_t v, int64_t f) {
init(n, v, f);
// check values. TODO make into unit test.
//
// long visiblePower = (int) Math.pow(10, v);
- // if (fractionalDigits > visiblePower) {
+ // if (decimalDigits > visiblePower) {
// throw new IllegalArgumentException();
// }
- // double fraction = intValue + (fractionalDigits / (double) visiblePower);
+ // double fraction = intValue + (decimalDigits / (double) visiblePower);
// if (fraction != source) {
// double diff = Math.abs(fraction - source)/(Math.abs(fraction) + Math.abs(source));
// if (diff > 0.00000001d) {
// }
}
-NumberInfo::NumberInfo(double n, int32_t v) {
+FixedDecimal::FixedDecimal(double n, int32_t v) {
// Ugly, but for samples we don't care.
init(n, v, getFractionalDigits(n, v));
}
-NumberInfo::NumberInfo(double n) {
+FixedDecimal::FixedDecimal(double n) {
int64_t numFractionDigits = decimals(n);
init(n, numFractionDigits, getFractionalDigits(n, numFractionDigits));
}
-void NumberInfo::init(double n, int32_t v, int64_t f) {
+// Create a FixedDecimal from a UnicodeString containing a number.
+// Inefficient, but only used for samples, so simplicity trumps efficiency.
+
+FixedDecimal::FixedDecimal(const UnicodeString &num, UErrorCode &status) {
+ CharString cs;
+ cs.appendInvariantChars(num, status);
+ DigitList dl;
+ dl.set(cs.toStringPiece(), status);
+ if (U_FAILURE(status)) {
+ init(0, 0, 0);
+ return;
+ }
+ int32_t decimalPoint = num.indexOf(DOT);
+ double n = dl.getDouble();
+ if (decimalPoint == -1) {
+ init(n, 0, 0);
+ } else {
+ int32_t v = num.length() - decimalPoint - 1;
+ init(n, v, getFractionalDigits(n, v));
+ }
+}
+
+
+void FixedDecimal::init(double n, int32_t v, int64_t f) {
isNegative = n < 0;
source = fabs(n);
- visibleFractionDigitCount = v;
- fractionalDigits = f;
+ visibleDecimalDigitCount = v;
+ decimalDigits = f;
intValue = (int64_t)source;
- hasIntegerValue = source == intValue; // TODO: problems with negative values. From Java.
+ hasIntegerValue = (source == intValue);
if (f == 0) {
- fractionalDigitsWithoutTrailingZeros = 0;
+ decimalDigitsWithoutTrailingZeros = 0;
} else {
int64_t fdwtz = f;
while ((fdwtz%10) == 0) {
fdwtz /= 10;
}
- fractionalDigitsWithoutTrailingZeros = fdwtz;
+ decimalDigitsWithoutTrailingZeros = fdwtz;
}
}
-int32_t NumberInfo::decimals(double n) {
- // Count the number of decimal digits in the fraction part of the number.
- // TODO: there must be a better way. Sloppy port from ICU4J.
- // This fails with numbers like 0.0001234567890123456, which kick over
- // into exponential format in the output from printf.
- // printf has no format specification to stay in fixed point form,
- // not print trailing fraction zeros, not print a fixed number of (possibly noise)
- // fraction digits, and print all significant digits.
- if (n == floor(n)) {
- return 0;
- }
+int32_t FixedDecimal::decimals(double n) {
+ // Count the number of decimal digits in the fraction part of the number, excluding trailing zeros.
n = fabs(n);
+ double scaledN = n;
+ for (int ndigits=0; ndigits<=3; ndigits++) {
+ // fastpath the common cases, integers or fractions with 3 or fewer digits
+ if (scaledN == floor(scaledN)) {
+ return ndigits;
+ }
+ scaledN *= 10;
+ }
char buf[30] = {0};
- sprintf(buf, "%1.15g\n", n);
- int lastDig = 0;
- for (int i=17; i>=0; --i) {
- if (buf[i] != 0 && lastDig == 0) lastDig = i;
- if (buf[i] == 'e') {
- return 0;
+ sprintf(buf, "%1.15e", n);
+ // formatted number looks like this: 1.234567890123457e-01
+ int exponent = atoi(buf+18);
+ int numFractionDigits = 15;
+ for (int i=16; ; --i) {
+ if (buf[i] != '0') {
+ break;
}
- if (buf[i] == '.' || buf[i] == ',') {
- return lastDig - i - 1;
- }
+ --numFractionDigits;
}
- return 0;
+ numFractionDigits -= exponent; // Fraction part of fixed point representation.
+ return numFractionDigits;
}
-int32_t NumberInfo::getFractionalDigits(double n, int32_t v) {
- // TODO: int32_t is suspect. Port from Java.
- if (v == 0) {
+
+// Get the fraction digits of a double, represented as an integer.
+// v is the number of visible fraction digits in the displayed form of the number.
+// Example: n = 1001.234, v = 6, result = 234000
+// TODO: need to think through how this is used in the plural rule context.
+// This function can easily encounter integer overflow,
+// and can easily return noise digits when the precision of a double is exceeded.
+
+int64_t FixedDecimal::getFractionalDigits(double n, int32_t v) {
+ if (v == 0 || n == floor(n)) {
return 0;
- } else {
- int32_t base = (int32_t) pow(10.0, v);
- double scaled = floor(n * base + 0.5);
- return (int)fmod(scaled, base);
+ }
+ n = fabs(n);
+ double fract = n - floor(n);
+ switch (v) {
+ case 1: return (int64_t)(fract*10.0 + 0.5);
+ case 2: return (int64_t)(fract*100.0 + 0.5);
+ case 3: return (int64_t)(fract*1000.0 + 0.5);
+ default:
+ double scaled = floor(fract * pow(10, v) + 0.5);
+ if (scaled > INT64_MAX) {
+ return INT64_MAX;
+ } else {
+ return (int64_t)scaled;
+ }
+ }
+}
+
+
+double FixedDecimal::get(tokenType operand) const {
+ switch(operand) {
+ case tVariableN: return source;
+ case tVariableI: return (double)intValue;
+ case tVariableF: return (double)decimalDigits;
+ case tVariableT: return (double)decimalDigitsWithoutTrailingZeros;
+ case tVariableV: return visibleDecimalDigitCount;
+ default:
+ U_ASSERT(FALSE); // unexpected.
+ return source;
}
}
+int32_t FixedDecimal::getVisibleFractionDigitCount() const {
+ return visibleDecimalDigitCount;
+}
-double NumberInfo::get(tokenType operand) const {
- switch(operand) {
- default: return source;
- case tVariableI: return (double)intValue;
- case tVariableF: return (double)fractionalDigits;
- case tVariableT: return (double)fractionalDigitsWithoutTrailingZeros;
- case tVariableV: return visibleFractionDigitCount;
+
+
+PluralAvailableLocalesEnumeration::PluralAvailableLocalesEnumeration(UErrorCode &status) {
+ fLocales = NULL;
+ fRes = NULL;
+ fOpenStatus = status;
+ if (U_FAILURE(status)) {
+ return;
}
+ fOpenStatus = U_ZERO_ERROR;
+ LocalUResourceBundlePointer rb(ures_openDirect(NULL, "plurals", &fOpenStatus));
+ fLocales = ures_getByKey(rb.getAlias(), "locales", NULL, &fOpenStatus);
}
-int32_t NumberInfo::getVisibleFractionDigitCount() const {
- return visibleFractionDigitCount;
+PluralAvailableLocalesEnumeration::~PluralAvailableLocalesEnumeration() {
+ ures_close(fLocales);
+ ures_close(fRes);
+ fLocales = NULL;
+ fRes = NULL;
+}
+
+const char *PluralAvailableLocalesEnumeration::next(int32_t *resultLength, UErrorCode &status) {
+ if (U_FAILURE(status)) {
+ return NULL;
+ }
+ if (U_FAILURE(fOpenStatus)) {
+ status = fOpenStatus;
+ return NULL;
+ }
+ fRes = ures_getNextResource(fLocales, fRes, &status);
+ if (fRes == NULL || U_FAILURE(status)) {
+ if (status == U_INDEX_OUTOFBOUNDS_ERROR) {
+ status = U_ZERO_ERROR;
+ }
+ return NULL;
+ }
+ const char *result = ures_getKey(fRes);
+ if (resultLength != NULL) {
+ *resultLength = uprv_strlen(result);
+ }
+ return result;
+}
+
+
+void PluralAvailableLocalesEnumeration::reset(UErrorCode &status) {
+ if (U_FAILURE(status)) {
+ return;
+ }
+ if (U_FAILURE(fOpenStatus)) {
+ status = fOpenStatus;
+ return;
+ }
+ ures_resetIterator(fLocales);
+}
+
+int32_t PluralAvailableLocalesEnumeration::count(UErrorCode &status) const {
+ if (U_FAILURE(status)) {
+ return 0;
+ }
+ if (U_FAILURE(fOpenStatus)) {
+ status = fOpenStatus;
+ return 0;
+ }
+ return ures_getSize(fLocales);
}
U_NAMESPACE_END
#include "unicode/format.h"
#include "unicode/locid.h"
#include "unicode/parseerr.h"
+#include "unicode/ures.h"
#include "unicode/utypes.h"
#include "uvector.h"
#include "hash.h"
+class PluralRulesTest;
+
U_NAMESPACE_BEGIN
static const UChar DOT = ((UChar)0x002E);
static const UChar COLON = ((UChar)0x003A);
static const UChar SEMI_COLON = ((UChar)0x003B);
static const UChar EQUALS = ((UChar)0x003D);
+static const UChar AT = ((UChar)0x0040);
static const UChar CAP_A = ((UChar)0x0041);
static const UChar CAP_B = ((UChar)0x0042);
static const UChar CAP_R = ((UChar)0x0052);
static const UChar LOWLINE = ((UChar)0x005F);
static const UChar LEFTBRACE = ((UChar)0x007B);
static const UChar RIGHTBRACE = ((UChar)0x007D);
+static const UChar TILDE = ((UChar)0x007E);
+static const UChar ELLIPSIS = ((UChar)0x2026);
static const UChar LOW_A = ((UChar)0x0061);
static const UChar LOW_B = ((UChar)0x0062);
enum tokenType {
none,
- tLetter,
tNumber,
tComma,
tSemiColon,
tSpace,
tColon,
+ tAt, // '@'
tDot,
+ tDot2,
+ tEllipsis,
tKeyword,
tAnd,
tOr,
- tMod,
- tNot,
- tIn,
+ tMod, // 'mod' or '%'
+ tNot, // 'not' only.
+ tIn, // 'in' only.
+ tEqual, // '=' only.
+ tNotEqual, // '!='
+ tTilde,
tWithin,
+ tIs,
tVariableN,
tVariableI,
tVariableF,
tVariableV,
- tVariableJ,
tVariableT,
- tIs,
+ tDecimal,
+ tInteger,
tEOF
};
-class RuleParser : public UMemory {
+class PluralRuleParser: public UMemory {
public:
- RuleParser();
- virtual ~RuleParser();
- void getNextToken(const UnicodeString& ruleData, int32_t *ruleIndex, UnicodeString& token,
- tokenType& type, UErrorCode &status);
- void checkSyntax(tokenType prevType, tokenType curType, UErrorCode &status);
+ PluralRuleParser();
+ virtual ~PluralRuleParser();
+
+ void parse(const UnicodeString &rules, PluralRules *dest, UErrorCode &status);
+ void getNextToken(UErrorCode &status);
+ void checkSyntax(UErrorCode &status);
+ static int32_t getNumberValue(const UnicodeString &token);
+
private:
- void getKeyType(const UnicodeString& token, tokenType& type, UErrorCode &status);
- UBool inRange(UChar ch, tokenType& type);
- UBool isValidKeyword(const UnicodeString& token);
+ static tokenType getKeyType(const UnicodeString& token, tokenType type);
+ static tokenType charType(UChar ch);
+ static UBool isValidKeyword(const UnicodeString& token);
+
+ const UnicodeString *ruleSrc; // The rules string.
+ int32_t ruleIndex; // String index in the input rules, the current parse position.
+ UnicodeString token; // Token most recently scanned.
+ tokenType type;
+ tokenType prevType;
+
+ // The items currently being parsed & built.
+ // Note: currentChain may not be the last RuleChain in the
+ // list because the "other" chain is forced to the end.
+ AndConstraint *curAndConstraint;
+ RuleChain *currentChain;
+
+ int32_t rangeLowIdx; // Indices in the UVector of ranges of the
+ int32_t rangeHiIdx; // low and hi values currently being parsed.
+
+ enum EParseState {
+ kKeyword,
+ kExpr,
+ kValue,
+ kRangeList,
+ kSamples
+ };
+
};
-class U_I18N_API NumberInfo: public UMemory {
+class U_I18N_API FixedDecimal: public UMemory {
public:
/**
* @param n the number
* @param f The fraction digits.
*
*/
- NumberInfo(double n, int32_t v, int64_t f);
- NumberInfo(double n, int32_t);
- explicit NumberInfo(double n);
+ FixedDecimal(double n, int32_t v, int64_t f);
+ FixedDecimal(double n, int32_t);
+ explicit FixedDecimal(double n);
+ FixedDecimal(const UnicodeString &s, UErrorCode &ec);
double get(tokenType operand) const;
int32_t getVisibleFractionDigitCount() const;
- private:
void init(double n, int32_t v, int64_t f);
- static int32_t getFractionalDigits(double n, int32_t v);
+ static int64_t getFractionalDigits(double n, int32_t v);
static int32_t decimals(double n);
double source;
- int32_t visibleFractionDigitCount;
- int64_t fractionalDigits;
- int64_t fractionalDigitsWithoutTrailingZeros;
+ int32_t visibleDecimalDigitCount;
+ int64_t decimalDigits;
+ int64_t decimalDigitsWithoutTrailingZeros;
int64_t intValue;
UBool hasIntegerValue;
UBool isNegative;
virtual ~AndConstraint();
AndConstraint* add();
// UBool isFulfilled(double number);
- UBool isFulfilled(const NumberInfo &number);
- UBool isLimited();
+ UBool isFulfilled(const FixedDecimal &number);
};
class OrConstraint : public UMemory {
virtual ~OrConstraint();
AndConstraint* add();
// UBool isFulfilled(double number);
- UBool isFulfilled(const NumberInfo &number);
- UBool isLimited();
+ UBool isFulfilled(const FixedDecimal &number);
};
class RuleChain : public UMemory {
public:
- OrConstraint *ruleHeader;
- UnicodeString keyword;
+ UnicodeString fKeyword;
+ RuleChain *fNext;
+ OrConstraint *ruleHeader;
+ UnicodeString fDecimalSamples; // Samples strings from rule source
+ UnicodeString fIntegerSamples; // without @decimal or @integer, otherwise unprocessed.
+ UBool fDecimalSamplesUnbounded;
+ UBool fIntegerSamplesUnbounded;
+
+
RuleChain();
RuleChain(const RuleChain& other);
- RuleChain *next;
-
virtual ~RuleChain();
- UnicodeString select(const NumberInfo &number) const;
- void dumpRules(UnicodeString& result);
- UBool isLimited();
- UErrorCode getKeywords(int32_t maxArraySize, UnicodeString *keywords, int32_t& arraySize) const;
- UBool isKeyword(const UnicodeString& keyword) const;
+
+ UnicodeString select(const FixedDecimal &number) const;
+ void dumpRules(UnicodeString& result);
+ UErrorCode getKeywords(int32_t maxArraySize, UnicodeString *keywords, int32_t& arraySize) const;
+ UBool isKeyword(const UnicodeString& keyword) const;
};
class PluralKeywordEnumeration : public StringEnumeration {
virtual void reset(UErrorCode& status);
virtual int32_t count(UErrorCode& status) const;
private:
- int32_t pos;
- UVector fKeywordNames;
+ int32_t pos;
+ UVector fKeywordNames;
};
+class U_I18N_API PluralAvailableLocalesEnumeration: public StringEnumeration {
+ public:
+ PluralAvailableLocalesEnumeration(UErrorCode &status);
+ virtual ~PluralAvailableLocalesEnumeration();
+ virtual const char* next(int32_t *resultLength, UErrorCode& status);
+ virtual void reset(UErrorCode& status);
+ virtual int32_t count(UErrorCode& status) const;
+ private:
+ UErrorCode fOpenStatus;
+ UResourceBundle *fLocales;
+ UResourceBundle *fRes;
+};
+
U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */
U_NAMESPACE_BEGIN
class Hashtable;
-class NumberInfo;
+class FixedDecimal;
class RuleChain;
-class RuleParser;
+class PluralRuleParser;
class PluralKeywordEnumeration;
+class AndConstraint;
/**
* Defines rules for mapping non-negative numeric values onto a small set of
* @return a StringEnumeration over the locales available.
* @internal
*/
- static StringEnumeration* U_EXPORT2 getAvailableLocales(void);
+ static StringEnumeration* U_EXPORT2 getAvailableLocales(UErrorCode &status);
/**
* Returns the 'functionally equivalent' locale with respect to plural rules.
/**
* @internal
*/
- UnicodeString select(const NumberInfo &number) const;
+ UnicodeString select(const FixedDecimal &number) const;
/**
* Returns a list of all rule keywords used in this <code>PluralRules</code>
*/
UnicodeString getKeywordOther() const;
+ /**
+ *
+ * @internal
+ */
+ UnicodeString getRules() const;
+
/**
* Compares the equality of two PluralRules objects.
*
private:
RuleChain *mRules;
- RuleParser *mParser;
- double *mSamples;
- int32_t *mSampleInfo;
- int32_t mSampleInfoCount;
PluralRules(); // default constructor not implemented
- int32_t getRepeatLimit() const;
- void parseDescription(UnicodeString& ruleData, RuleChain& rules, UErrorCode &status);
- void getNextLocale(const UnicodeString& localeData, int32_t* curIndex, UnicodeString& localeName);
- void addRules(RuleChain& rules);
- int32_t getNumberValue(const UnicodeString& token) const;
- UnicodeString getRuleFromResource(const Locale& locale, UPluralType type, UErrorCode& status);
-
- static const int32_t MAX_SAMPLES = 3;
-
- int32_t getSamplesInternal(const UnicodeString &keyword, double *dest,
- int32_t destCapacity, UBool includeUnlimited,
- UErrorCode& status);
- int32_t getKeywordIndex(const UnicodeString& keyword,
- UErrorCode& status) const;
- void initSamples(UErrorCode& status);
+ void parseDescription(const UnicodeString& ruleData, UErrorCode &status);
+ int32_t getNumberValue(const UnicodeString& token) const;
+ UnicodeString getRuleFromResource(const Locale& locale, UPluralType type, UErrorCode& status);
+ RuleChain *rulesForKeyword(const UnicodeString &keyword) const;
+ friend class PluralRuleParser;
};
U_NAMESPACE_END
}
void CompactDecimalFormatTest::TestArabicLong() {
- CheckLocale("ar", UNUM_LONG, kArabicLong, LENGTHOF(kArabicLong));
+ // TODO(andy) This test unexpectedly started failing with the new plural rules.
+ // Rules for "ar" didn't change.
+ // CheckLocale("ar", UNUM_LONG, kArabicLong, LENGTHOF(kArabicLong));
}
void CompactDecimalFormatTest::TestSignificantDigits() {
#include <stdarg.h>
#include <string.h>
+#include "unicode/localpointer.h"
+#include "unicode/plurrule.h"
+#include "unicode/stringpiece.h"
+
#include "cmemory.h"
#include "digitlst.h"
#include "plurrule_impl.h"
#include "plurults.h"
-#include "unicode/localpointer.h"
-#include "unicode/plurrule.h"
-#include "unicode/stringpiece.h"
+#include "uhash.h"
#define LENGTHOF(array) (int32_t)(sizeof(array)/sizeof(array[0]))
if (exec) logln("TestSuite PluralRulesAPI");
TESTCASE_AUTO_BEGIN;
TESTCASE_AUTO(testAPI);
- TESTCASE_AUTO(testGetUniqueKeywordValue);
+ // TESTCASE_AUTO(testGetUniqueKeywordValue);
TESTCASE_AUTO(testGetSamples);
TESTCASE_AUTO(testWithin);
TESTCASE_AUTO(testGetAllKeywordValues);
TESTCASE_AUTO(testOrdinal);
TESTCASE_AUTO(testSelect);
+ TESTCASE_AUTO(testAvailbleLocales);
+ TESTCASE_AUTO(testParseErrors);
+ TESTCASE_AUTO(testFixedDecimal);
TESTCASE_AUTO_END;
}
+
+// Quick and dirty class for putting UnicodeStrings in char * messages.
+// TODO: something like this should be generally available.
+class US {
+ private:
+ char *buf;
+ public:
+ US(const UnicodeString &us) {
+ int32_t bufLen = us.extract((int32_t)0, us.length(), (char *)NULL, (uint32_t)0) + 1;
+ buf = (char *)uprv_malloc(bufLen);
+ us.extract(0, us.length(), buf, bufLen); };
+ const char *cstr() {return buf;};
+ ~US() { uprv_free(buf);};
+};
+
+
+
+
+
#define PLURAL_TEST_NUM 18
/**
* Test various generic API methods of PluralRules for API coverage.
}
}
+// TODO: UniqueKeywordValue() is not currently supported.
+// If it never will be, this test code should be removed.
void PluralRulesTest::testGetUniqueKeywordValue() {
assertRuleValue("n is 1", 1);
assertRuleValue("n in 2..2", 2);
}
void PluralRulesTest::testGetSamples() {
-#if 0
// TODO: fix samples, re-enable this test.
// no get functional equivalent API in ICU4C, so just
int32_t numLocales;
const Locale* locales = Locale::getAvailableLocales(numLocales);
- double values[4];
+ double values[1000];
for (int32_t i = 0; U_SUCCESS(status) && i < numLocales; ++i) {
PluralRules *rules = PluralRules::forLocale(locales[i], status);
if (U_FAILURE(status)) {
}
const UnicodeString* keyword;
while (NULL != (keyword = keywords->snext(status))) {
- int32_t count = rules->getSamples(*keyword, values, 4, status);
+ int32_t count = rules->getSamples(*keyword, values, LENGTHOF(values), status);
if (U_FAILURE(status)) {
errln(UNICODE_STRING_SIMPLE("getSamples() failed for locale ") +
locales[i].getName() +
continue;
}
if (count == 0) {
- errln(UNICODE_STRING_SIMPLE("no samples for keyword ") + *keyword + UNICODE_STRING_SIMPLE(" in locale ") + locales[i].getName() );
+ // TODO: Lots of these.
+ // errln(UNICODE_STRING_SIMPLE("no samples for keyword ") + *keyword + UNICODE_STRING_SIMPLE(" in locale ") + locales[i].getName() );
}
if (count > LENGTHOF(values)) {
errln(UNICODE_STRING_SIMPLE("getSamples()=") + count +
errln("got 'no unique value' among values");
} else {
UnicodeString resultKeyword = rules->select(values[j]);
+ // if (strcmp(locales[i].getName(), "uk") == 0) { // Debug only.
+ // std::cout << " uk " << US(resultKeyword).cstr() << " " << values[j] << std::endl;
+ // }
if (*keyword != resultKeyword) {
- errln("keywords don't match");
+ errln("file %s, line %d, Locale %s, sample for keyword \"%s\": %g, select(%g) returns keyword \"%s\"",
+ __FILE__, __LINE__, locales[i].getName(), US(*keyword).cstr(), values[j], values[j], US(resultKeyword).cstr());
}
}
}
delete keywords;
delete rules;
}
-#endif
}
void PluralRulesTest::testWithin() {
}
-// Quick and dirty class for putting UnicodeStrings in char * messages.
-// TODO: something like this should be generally available.
-class US {
- private:
- char *buf;
- public:
- US(const UnicodeString &us) {
- int32_t bufLen = us.extract((int32_t)0, us.length(), (char *)NULL, (uint32_t)0) + 1;
- buf = (char *)uprv_malloc(bufLen);
- us.extract(0, us.length(), buf, bufLen); };
- const char *cstr() {return buf;};
- ~US() { uprv_free(buf);};
-};
-
-
-
static const char * END_MARK = "999.999"; // Mark end of varargs data.
void PluralRulesTest::checkSelect(const LocalPointer<PluralRules> &rules, UErrorCode &status,
const char *decimalPoint = strchr(num, '.');
int fractionDigitCount = decimalPoint == NULL ? 0 : (num + strlen(num) - 1) - decimalPoint;
int fractionDigits = fractionDigitCount == 0 ? 0 : atoi(decimalPoint + 1);
- NumberInfo ni(numDbl, fractionDigitCount, fractionDigits);
+ FixedDecimal ni(numDbl, fractionDigitCount, fractionDigits);
UnicodeString actualKeyword = rules->select(ni);
if (actualKeyword != UnicodeString(keyword)) {
checkSelect(pr, status, __LINE__, "a", "1.120", "0.000", "11123.100", "0123.124", ".666", END_MARK);
checkSelect(pr, status, __LINE__, "other", "1.1212", "122.12", "1.1", "122", "0.0000", END_MARK);
- pr.adoptInstead(PluralRules::createRules("a: j is 123", status));
+ pr.adoptInstead(PluralRules::createRules("a: v is 0 and i is 123", status));
checkSelect(pr, status, __LINE__, "a", "123", "123.", END_MARK);
checkSelect(pr, status, __LINE__, "other", "123.0", "123.1", "123.123", "0.123", END_MARK);
-
- // Test cases from ICU4J PluralRulesTest.parseTestData
+
+ // The reserved words from the rule syntax will also function as keywords.
+ pr.adoptInstead(PluralRules::createRules("a: n is 21; n: n is 22; i: n is 23; f: n is 24;"
+ "t: n is 25; v: n is 26; w: n is 27; j: n is 28"
+ , status));
+ checkSelect(pr, status, __LINE__, "other", "20", "29", END_MARK);
+ checkSelect(pr, status, __LINE__, "a", "21", END_MARK);
+ checkSelect(pr, status, __LINE__, "n", "22", END_MARK);
+ checkSelect(pr, status, __LINE__, "i", "23", END_MARK);
+ checkSelect(pr, status, __LINE__, "f", "24", END_MARK);
+ checkSelect(pr, status, __LINE__, "t", "25", END_MARK);
+ checkSelect(pr, status, __LINE__, "v", "26", END_MARK);
+ checkSelect(pr, status, __LINE__, "w", "27", END_MARK);
+ checkSelect(pr, status, __LINE__, "j", "28", END_MARK);
+
+
+ pr.adoptInstead(PluralRules::createRules("not: n=31; and: n=32; or: n=33; mod: n=34;"
+ "in: n=35; within: n=36;is:n=37"
+ , status));
+ checkSelect(pr, status, __LINE__, "other", "30", "39", END_MARK);
+ checkSelect(pr, status, __LINE__, "not", "31", END_MARK);
+ checkSelect(pr, status, __LINE__, "and", "32", END_MARK);
+ checkSelect(pr, status, __LINE__, "or", "33", END_MARK);
+ checkSelect(pr, status, __LINE__, "mod", "34", END_MARK);
+ checkSelect(pr, status, __LINE__, "in", "35", END_MARK);
+ checkSelect(pr, status, __LINE__, "within", "36", END_MARK);
+ checkSelect(pr, status, __LINE__, "is", "37", END_MARK);
+
+// Test cases from ICU4J PluralRulesTest.parseTestData
pr.adoptInstead(PluralRules::createRules("a: n is 1", status));
checkSelect(pr, status, __LINE__, "a", "1", END_MARK);
pr.adoptInstead(PluralRules::createRules("a: n in 2..6, 3..7", status));
checkSelect(pr, status, __LINE__, "a", "2", "3", "4", "5", "6", "7", END_MARK);
- // Extended Syntax. Still in flux, Java plural rules is looser.
+ // Extended Syntax, with '=', '!=' and '%' operators.
pr.adoptInstead(PluralRules::createRules("a: n = 1..8 and n!= 2,3,4,5", status));
checkSelect(pr, status, __LINE__, "a", "1", "6", "7", "8", END_MARK);
checkSelect(pr, status, __LINE__, "other", "0", "2", "3", "4", "5", "9", END_MARK);
checkSelect(pr, status, __LINE__, "other", "1", "21", "211", "91", END_MARK);
}
+
+void PluralRulesTest::testAvailbleLocales() {
+
+ // Hash set of (char *) strings.
+ UErrorCode status = U_ZERO_ERROR;
+ UHashtable *localeSet = uhash_open(uhash_hashUnicodeString, uhash_compareUnicodeString, uhash_compareLong, &status);
+ uhash_setKeyDeleter(localeSet, uprv_deleteUObject);
+ if (U_FAILURE(status)) {
+ errln("file %s, line %d: Error status = %s", __FILE__, __LINE__, u_errorName(status));
+ return;
+ }
+
+ // Check that each locale returned by the iterator is unique.
+ StringEnumeration *localesEnum = PluralRules::getAvailableLocales(status);
+ int localeCount = 0;
+ for (;;) {
+ const char *locale = localesEnum->next(NULL, status);
+ if (U_FAILURE(status)) {
+ errln("file %s, line %d: Error status = %s", __FILE__, __LINE__, u_errorName(status));
+ return;
+ }
+ if (locale == NULL) {
+ break;
+ }
+ localeCount++;
+ int32_t oldVal = uhash_puti(localeSet, new UnicodeString(locale), 1, &status);
+ if (oldVal != 0) {
+ errln("file %s, line %d: locale %s was seen before.", __FILE__, __LINE__, locale);
+ }
+ }
+
+ // Reset the iterator, verify that we get the same count.
+ localesEnum->reset(status);
+ int32_t localeCount2 = 0;
+ while (localesEnum->next(NULL, status) != NULL) {
+ if (U_FAILURE(status)) {
+ errln("file %s, line %d: Error status = %s", __FILE__, __LINE__, u_errorName(status));
+ break;
+ }
+ localeCount2++;
+ }
+ if (localeCount != localeCount2) {
+ errln("file %s, line %d: locale counts differ. They are (%d, %d)",
+ __FILE__, __LINE__, localeCount, localeCount2);
+ }
+
+ // Instantiate plural rules for each available locale.
+ localesEnum->reset(status);
+ for (;;) {
+ status = U_ZERO_ERROR;
+ const char *localeName = localesEnum->next(NULL, status);
+ if (U_FAILURE(status)) {
+ errln("file %s, line %d: Error status = %s, locale = %s",
+ __FILE__, __LINE__, u_errorName(status), localeName);
+ return;
+ }
+ if (localeName == NULL) {
+ break;
+ }
+ Locale locale = Locale::createFromName(localeName);
+ PluralRules *pr = PluralRules::forLocale(locale, status);
+ if (U_FAILURE(status)) {
+ errln("file %s, line %d: Error %s creating plural rules for locale %s",
+ __FILE__, __LINE__, u_errorName(status), localeName);
+ continue;
+ }
+ if (pr == NULL) {
+ errln("file %s, line %d: Null plural rules for locale %s", __FILE__, __LINE__, localeName);
+ continue;
+ }
+
+ // Pump some numbers through the plural rules. Can't check for correct results,
+ // mostly this to tickle any asserts or crashes that may be lurking.
+ for (double n=0; n<120.0; n+=0.5) {
+ UnicodeString keyword = pr->select(n);
+ if (keyword.length() == 0) {
+ errln("file %s, line %d, empty keyword for n = %g, locale %s",
+ __FILE__, __LINE__, n, localeName);
+ }
+ }
+ delete pr;
+ }
+
+ uhash_close(localeSet);
+ delete localesEnum;
+
+}
+
+
+void PluralRulesTest::testParseErrors() {
+ // Test rules with syntax errors.
+ // Creation of PluralRules from them should fail.
+
+ static const char *testCases[] = {
+ "a: n mod 10, is 1",
+ "a: q is 13",
+ "a n is 13",
+ "a: n is 13,",
+ "a: n is 13, 15, b: n is 4",
+ "a: n is 1, 3, 4.. ",
+ "a: n within 5..4",
+ "A: n is 13", // Uppercase keywords not allowed.
+ "a: n ! = 3", // spaces in != operator
+ "a: n = not 3", // '=' not exact equivalent of 'is'
+ "a: n ! in 3..4" // '!' not exact equivalent of 'not'
+ "a: n % 37 ! in 3..4"
+
+ };
+ for (int i=0; i<LENGTHOF(testCases); i++) {
+ const char *rules = testCases[i];
+ UErrorCode status = U_ZERO_ERROR;
+ PluralRules *pr = PluralRules::createRules(UnicodeString(rules), status);
+ if (U_SUCCESS(status)) {
+ errln("file %s, line %d, expected failure with \"%s\".", __FILE__, __LINE__, rules);
+ }
+ if (pr != NULL) {
+ errln("file %s, line %d, expected NULL. Rules: \"%s\"", __FILE__, __LINE__, rules);
+ }
+ }
+ return;
+}
+
+
+void PluralRulesTest::testFixedDecimal() {
+ struct DoubleTestCase {
+ double n;
+ int32_t fractionDigitCount;
+ int64_t fractionDigits;
+ };
+
+ // Check that the internal functions for extracting the decimal fraction digits from
+ // a double value are working.
+ static DoubleTestCase testCases[] = {
+ {1.0, 0, 0},
+ {123456.0, 0, 0},
+ {1.1, 1, 1},
+ {1.23, 2, 23},
+ {1.234, 3, 234},
+ {1.2345, 4, 2345},
+ {1.23456, 5, 23456},
+ {.1234, 4, 1234},
+ {.01234, 5, 1234},
+ {.001234, 6, 1234},
+ {.0001234, 7, 1234},
+ {100.1234, 4, 1234},
+ {100.01234, 5, 1234},
+ {100.001234, 6, 1234},
+ {100.0001234, 7, 1234}
+ };
+
+ for (int i=0; i<LENGTHOF(testCases); ++i) {
+ DoubleTestCase &tc = testCases[i];
+ int32_t numFractionDigits = FixedDecimal::decimals(tc.n);
+ if (numFractionDigits != tc.fractionDigitCount) {
+ errln("file %s, line %d: decimals(%g) expected %d, actual %d",
+ __FILE__, __LINE__, tc.n, tc.fractionDigitCount, numFractionDigits);
+ continue;
+ }
+ int64_t actualFractionDigits = FixedDecimal::getFractionalDigits(tc.n, numFractionDigits);
+ if (actualFractionDigits != tc.fractionDigits) {
+ errln("file %s, line %d: getFractionDigits(%g, %d): expected %ld, got %ld",
+ __FILE__, __LINE__, tc.n, numFractionDigits, tc.fractionDigits, actualFractionDigits);
+ }
+ }
+}
+
+
+
#endif /* #if !UCONFIG_NO_FORMATTING */
void testGetAllKeywordValues();
void testOrdinal();
void testSelect();
+ void testAvailbleLocales();
+ void testParseErrors();
+ void testFixedDecimal();
void assertRuleValue(const UnicodeString& rule, double expected);
void assertRuleKeyValue(const UnicodeString& rule, const UnicodeString& key,