mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Read decimal digit values from the bundled character database (#8748)
`char_to_decimal` carried its own sorted table of 590 decimal digit characters, added in #5643 because `common` had no way to reach the Unicode numeric data at the time: it lived in `stdlib`, which sits above `common` in the dependency graph. The table's own comment said as much — "this should probably live in a crate like unic or unicode-properties". #8211 extracted that data into `rustpython-unicode`, below `common`, and made `common` depend on it, so the table can now be dropped in favour of `Ucd::decimal` — the same source `unicodedata.decimal` consults. This fixes a CPython incompatibility. The table was last updated for Unicode 10; the bundled UCD is Unicode 17, which has 180 more decimal digits across 18 scripts (Hanifi Rohingya, Wancho, Tangsa, Ol Onal, segmented digits and others). Those were rejected by `int()` and `float()` even though `unicodedata.decimal` knew their value: >>> int('\u{10D33}') # HANIFI ROHINGYA DIGIT THREE ValueError # CPython: 3 >>> unicodedata.decimal('\u{10D33}') 3 All 590 characters the table covered keep their previous value, so nothing that already worked changes, and the two lookups can no longer drift apart on a future Unicode update. The lookup costs about 1.5ns more per non-ASCII character, since it reads a property trie and a value table instead of binary-searching one 2.4KB array: `int()` over a 4000-digit non-ASCII string goes from 12.2 to 13.6 ns/char. ASCII input never reaches it — the sole caller, `transform_decimal_and_space_to_ascii`, returns early for ASCII strings — so ordinary numeric parsing is unaffected. Assisted-by: Claude Code:claude-opus-5
This commit is contained in:
1 parent
4c7cc0a30c
commit
587880b9e1
1 file changed
+20
-40
+20
-40
@@ -797,48 +797,15 @@ macro_rules! ascii {
|
||||
}
|
||||
pub use ascii;
|
||||
|
||||
// TODO: this should probably live in a crate like unic or unicode-properties
|
||||
const UNICODE_DECIMAL_VALUES: &[char] = &[
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨',
|
||||
'٩', '۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹', '߀', '߁', '߂', '߃', '߄', '߅', '߆', '߇',
|
||||
'߈', '߉', '०', '१', '२', '३', '४', '५', '६', '७', '८', '९', '০', '১', '২', '৩', '৪', '৫', '৬',
|
||||
'৭', '৮', '৯', '੦', '੧', '੨', '੩', '੪', '੫', '੬', '੭', '੮', '੯', '૦', '૧', '૨', '૩', '૪', '૫',
|
||||
'૬', '૭', '૮', '૯', '୦', '୧', '୨', '୩', '୪', '୫', '୬', '୭', '୮', '୯', '௦', '௧', '௨', '௩', '௪',
|
||||
'௫', '௬', '௭', '௮', '௯', '౦', '౧', '౨', '౩', '౪', '౫', '౬', '౭', '౮', '౯', '೦', '೧', '೨', '೩',
|
||||
'೪', '೫', '೬', '೭', '೮', '೯', '൦', '൧', '൨', '൩', '൪', '൫', '൬', '൭', '൮', '൯', '෦', '෧', '෨',
|
||||
'෩', '෪', '෫', '෬', '෭', '෮', '෯', '๐', '๑', '๒', '๓', '๔', '๕', '๖', '๗', '๘', '๙', '໐', '໑',
|
||||
'໒', '໓', '໔', '໕', '໖', '໗', '໘', '໙', '༠', '༡', '༢', '༣', '༤', '༥', '༦', '༧', '༨', '༩', '၀',
|
||||
'၁', '၂', '၃', '၄', '၅', '၆', '၇', '၈', '၉', '႐', '႑', '႒', '႓', '႔', '႕', '႖', '႗', '႘', '႙',
|
||||
'០', '១', '២', '៣', '៤', '៥', '៦', '៧', '៨', '៩', '᠐', '᠑', '᠒', '᠓', '᠔', '᠕', '᠖', '᠗', '᠘',
|
||||
'᠙', '᥆', '᥇', '᥈', '᥉', '᥊', '᥋', '᥌', '᥍', '᥎', '᥏', '᧐', '᧑', '᧒', '᧓', '᧔', '᧕', '᧖', '᧗',
|
||||
'᧘', '᧙', '᪀', '᪁', '᪂', '᪃', '᪄', '᪅', '᪆', '᪇', '᪈', '᪉', '᪐', '᪑', '᪒', '᪓', '᪔', '᪕', '᪖',
|
||||
'᪗', '᪘', '᪙', '᭐', '᭑', '᭒', '᭓', '᭔', '᭕', '᭖', '᭗', '᭘', '᭙', '᮰', '᮱', '᮲', '᮳', '᮴', '᮵',
|
||||
'᮶', '᮷', '᮸', '᮹', '᱀', '᱁', '᱂', '᱃', '᱄', '᱅', '᱆', '᱇', '᱈', '᱉', '᱐', '᱑', '᱒', '᱓', '᱔',
|
||||
'᱕', '᱖', '᱗', '᱘', '᱙', '꘠', '꘡', '꘢', '꘣', '꘤', '꘥', '꘦', '꘧', '꘨', '꘩', '꣐', '꣑', '꣒', '꣓',
|
||||
'꣔', '꣕', '꣖', '꣗', '꣘', '꣙', '꤀', '꤁', '꤂', '꤃', '꤄', '꤅', '꤆', '꤇', '꤈', '꤉', '꧐', '꧑', '꧒',
|
||||
'꧓', '꧔', '꧕', '꧖', '꧗', '꧘', '꧙', '꧰', '꧱', '꧲', '꧳', '꧴', '꧵', '꧶', '꧷', '꧸', '꧹', '꩐', '꩑',
|
||||
'꩒', '꩓', '꩔', '꩕', '꩖', '꩗', '꩘', '꩙', '꯰', '꯱', '꯲', '꯳', '꯴', '꯵', '꯶', '꯷', '꯸', '꯹', '0',
|
||||
'1', '2', '3', '4', '5', '6', '7', '8', '9', '𐒠', '𐒡', '𐒢', '𐒣', '𐒤', '𐒥', '𐒦', '𐒧',
|
||||
'𐒨', '𐒩', '𑁦', '𑁧', '𑁨', '𑁩', '𑁪', '𑁫', '𑁬', '𑁭', '𑁮', '𑁯', '𑃰', '𑃱', '𑃲', '𑃳', '𑃴', '𑃵', '𑃶',
|
||||
'𑃷', '𑃸', '𑃹', '𑄶', '𑄷', '𑄸', '𑄹', '𑄺', '𑄻', '𑄼', '𑄽', '𑄾', '𑄿', '𑇐', '𑇑', '𑇒', '𑇓', '𑇔', '𑇕',
|
||||
'𑇖', '𑇗', '𑇘', '𑇙', '𑋰', '𑋱', '𑋲', '𑋳', '𑋴', '𑋵', '𑋶', '𑋷', '𑋸', '𑋹', '𑑐', '𑑑', '𑑒', '𑑓', '𑑔',
|
||||
'𑑕', '𑑖', '𑑗', '𑑘', '𑑙', '𑓐', '𑓑', '𑓒', '𑓓', '𑓔', '𑓕', '𑓖', '𑓗', '𑓘', '𑓙', '𑙐', '𑙑', '𑙒', '𑙓',
|
||||
'𑙔', '𑙕', '𑙖', '𑙗', '𑙘', '𑙙', '𑛀', '𑛁', '𑛂', '𑛃', '𑛄', '𑛅', '𑛆', '𑛇', '𑛈', '𑛉', '𑜰', '𑜱', '𑜲',
|
||||
'𑜳', '𑜴', '𑜵', '𑜶', '𑜷', '𑜸', '𑜹', '𑣠', '𑣡', '𑣢', '𑣣', '𑣤', '𑣥', '𑣦', '𑣧', '𑣨', '𑣩', '𑱐', '𑱑',
|
||||
'𑱒', '𑱓', '𑱔', '𑱕', '𑱖', '𑱗', '𑱘', '𑱙', '𑵐', '𑵑', '𑵒', '𑵓', '𑵔', '𑵕', '𑵖', '𑵗', '𑵘', '𑵙', '𖩠',
|
||||
'𖩡', '𖩢', '𖩣', '𖩤', '𖩥', '𖩦', '𖩧', '𖩨', '𖩩', '𖭐', '𖭑', '𖭒', '𖭓', '𖭔', '𖭕', '𖭖', '𖭗', '𖭘', '𖭙',
|
||||
'𝟎', '𝟏', '𝟐', '𝟑', '𝟒', '𝟓', '𝟔', '𝟕', '𝟖', '𝟗', '𝟘', '𝟙', '𝟚', '𝟛', '𝟜', '𝟝', '𝟞', '𝟟', '𝟠',
|
||||
'𝟡', '𝟢', '𝟣', '𝟤', '𝟥', '𝟦', '𝟧', '𝟨', '𝟩', '𝟪', '𝟫', '𝟬', '𝟭', '𝟮', '𝟯', '𝟰', '𝟱', '𝟲', '𝟳',
|
||||
'𝟴', '𝟵', '𝟶', '𝟷', '𝟸', '𝟹', '𝟺', '𝟻', '𝟼', '𝟽', '𝟾', '𝟿', '𞥐', '𞥑', '𞥒', '𞥓', '𞥔', '𞥕', '𞥖',
|
||||
'𞥗', '𞥘', '𞥙',
|
||||
];
|
||||
|
||||
/// The decimal digit value of `ch`, if it has one — CPython's `Py_UNICODE_TODECIMAL`.
|
||||
///
|
||||
/// Reads the bundled character database, the same source `unicodedata.decimal`
|
||||
/// consults, so the two can never disagree and new scripts are picked up with the
|
||||
/// next Unicode update.
|
||||
#[must_use]
|
||||
pub fn char_to_decimal(ch: char) -> Option<u8> {
|
||||
UNICODE_DECIMAL_VALUES
|
||||
.binary_search(&ch)
|
||||
.ok()
|
||||
.map(|i| (i % 10) as u8)
|
||||
let value = rustpython_unicode::Ucd::new(true).decimal(CodePoint::from(ch))?;
|
||||
u8::try_from(value).ok()
|
||||
}
|
||||
|
||||
/// Replace Unicode decimal digits with their ASCII equivalents and any Unicode
|
||||
@@ -875,6 +842,19 @@ pub fn transform_decimal_and_space_to_ascii(s: &str) -> Cow<'_, str> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn char_to_decimal_values() {
|
||||
assert_eq!(char_to_decimal('7'), Some(7));
|
||||
// Decimal digits from any script, including scripts the previous
|
||||
// hand-written table predated.
|
||||
assert_eq!(char_to_decimal('٣'), Some(3)); // U+0663 Arabic-Indic
|
||||
assert_eq!(char_to_decimal('𐴵'), Some(5)); // U+10D35, Unicode 11
|
||||
assert_eq!(char_to_decimal('🯵'), Some(5)); // U+1FBF5, Unicode 13
|
||||
// Numeric but not a decimal digit, so it has no digit value.
|
||||
assert_eq!(char_to_decimal('½'), None);
|
||||
assert_eq!(char_to_decimal('가'), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transform_decimal_and_space() {
|
||||
// ASCII input is passed through untouched, without allocating.
|
||||
|
||||
Reference in new issue
Block a user