From 2b0ed2b9267c3cd8561de209e7f5ec201ba5ee0e Mon Sep 17 00:00:00 2001 From: youknowdot Date: Thu, 15 Oct 2026 16:25:05 +0900 Subject: [PATCH 1/9] Implement native Unicode 17 grapheme iteration Add the CPython extended grapheme state machine, full-codepoint properties, and traced Segment/iterator types with positional index handling. Refresh canonical unicodedata tests through update_lib while retaining pending markers. Include a losslessly range-compressed exact-final property oracle and differential test. Source-reviewed implementation; candidate runtime validation remains pending. Reference property generation completed against the pinned CPython Unicode extension. Assisted-by: OpenAI Codex:model version unavailable --- Lib/test/test_unicodedata.py | 32 +- crates/stdlib/src/unicodedata.rs | 177 ++ crates/unicode/src/grapheme.rs | 177 ++ crates/unicode/src/lib.rs | 1 + .../tests/data/cpython3.15_graphemes.txt | 2171 +++++++++++++++++ crates/unicode/tests/differential.rs | 83 +- crates/unicode/tests/generate_reference.py | 186 +- 7 files changed, 2809 insertions(+), 18 deletions(-) create mode 100644 crates/unicode/src/grapheme.rs create mode 100644 crates/unicode/tests/data/cpython3.15_graphemes.txt diff --git a/Lib/test/test_unicodedata.py b/Lib/test/test_unicodedata.py index aeb1835f6cc..254318b3093 100644 --- a/Lib/test/test_unicodedata.py +++ b/Lib/test/test_unicodedata.py @@ -107,7 +107,7 @@ def test_method_checksum(self): class BaseUnicodeFunctionsTest: - @unittest.skip # TODO: RUSTPYTHON; AssertionError: '7bda75e48a961a01ab328358980cefc5c0a1666d' != '68cd01e2c680b851c1fcab012efb5635' + @unittest.skip("TODO: RUSTPYTHON; AssertionError: '7bda75e48a961a01ab328358980cefc5c0a1666d' != '68cd01e2c680b851c1fcab012efb5635'") def test_function_checksum(self): db = self.db data = [] @@ -199,7 +199,7 @@ def test_no_names_in_pua(self): char = chr(i) self.assertRaises(ValueError, self.db.name, char) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: KeyError not raised by lookup + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: KeyError not raised by lookup def test_lookup_nonexistant(self): # just make sure that lookup can fail for nonexistent in [ @@ -360,7 +360,7 @@ def test_bidirectional_unassigned(self): self.assertEqual(self.db.bidirectional('\U0001FFFE'), '' if self.old else 'BN') self.assertEqual(self.db.bidirectional('\U00010D01'), '' if self.old else 'AL') - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ' 03A3 != '' + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ' 03A3 != '' def test_decomposition(self): self.assertEqual(self.db.decomposition('\uFFFE'),'') self.assertEqual(self.db.decomposition('\u00bc'), ' 0031 2044 0034') @@ -700,7 +700,7 @@ def test_east_asian_width(self): # New in 17.0.0 self.assertEqual(eaw('\U00016FF2'), 'N' if self.old else 'W') - @unittest.skip # TODO: RUSTPYTHON; AssertionError: 'N' != 'W' (passed on latest, fails on 3.2) + @unittest.skip("TODO: RUSTPYTHON; AssertionError: 'N' != 'W' (passed on latest, fails on 3.2)") def test_east_asian_width_unassigned(self): eaw = self.db.east_asian_width # unassigned @@ -769,7 +769,7 @@ def parse_cp(s): for cp in range(0, sys.maxunicode + 1): self.assertEqual(self.db.name(chr(cp), None), names_ref.get(cp)) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'isxidstart' + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'isxidstart' def test_isxidstart(self): self.assertTrue(self.db.isxidstart('S')) self.assertTrue(self.db.isxidstart('\u0AD0')) # GUJARATI OM @@ -783,7 +783,7 @@ def test_isxidstart(self): self.assertRaises(TypeError, self.db.isxidstart) self.assertRaises(TypeError, self.db.isxidstart, 'xx') - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'isxidcontinue' + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'isxidcontinue' def test_isxidcontinue(self): self.assertTrue(self.db.isxidcontinue('S')) self.assertTrue(self.db.isxidcontinue('_')) @@ -798,7 +798,7 @@ def test_isxidcontinue(self): self.assertRaises(TypeError, self.db.isxidcontinue) self.assertRaises(TypeError, self.db.isxidcontinue, 'xx') - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'grapheme_cluster_break' + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'grapheme_cluster_break' def test_grapheme_cluster_break(self): gcb = self.db.grapheme_cluster_break self.assertEqual(gcb(' '), 'Other') @@ -896,7 +896,7 @@ def test_grapheme_cluster_break(self): self.assertRaises(TypeError, gcb, '') self.assertRaises(TypeError, gcb, 'xx') - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'indic_conjunct_break' + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'indic_conjunct_break' def test_indic_conjunct_break(self): incb = self.db.indic_conjunct_break self.assertEqual(incb(' '), 'None') @@ -925,7 +925,7 @@ def test_indic_conjunct_break(self): self.assertRaises(TypeError, incb, '') self.assertRaises(TypeError, incb, 'xx') - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'extended_pictographic' + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'extended_pictographic' def test_extended_pictographic(self): ext_pict = self.db.extended_pictographic self.assertIs(ext_pict(' '), False) @@ -946,7 +946,7 @@ def test_extended_pictographic(self): self.assertRaises(TypeError, ext_pict, '') self.assertRaises(TypeError, ext_pict, 'xx') - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'iter_graphemes' + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'iter_graphemes' def test_grapheme_break(self): def graphemes(*args): return list(map(str, self.db.iter_graphemes(*args))) @@ -1017,7 +1017,7 @@ def graphemes(*args): 'a\U0001F1FA\U0001F1E6\U0001F1FA\U0001F1F3'), ['a', '\U0001F1FA\U0001F1E6', '\U0001F1FA\U0001F1F3']) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'block' + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'block' def test_block(self): self.assertEqual(self.db.block('\u0000'), 'Basic Latin') self.assertEqual(self.db.block('\u0041'), 'Basic Latin') @@ -1102,7 +1102,7 @@ def test_block(self): self.assertEqual(self.db.block('\U00100000'), 'Supplementary Private Use Area-B') self.assertEqual(self.db.block('\U0010FFFF'), 'Supplementary Private Use Area-B') - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'block' + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'block' def test_block_invalid_input(self): self.assertRaises(TypeError, self.db.block) self.assertRaises(TypeError, self.db.block, b'x') @@ -1169,7 +1169,7 @@ def test_failed_import_during_compiling(self): "(can't load unicodedata module)" self.assertIn(error, result.err.decode("ascii")) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError Process return code is 1 + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError Process return code is 1 def test_unicodedata_unload_reload(self): # gh-149449: dropping unicodedata and running gc must not leave the # cached _ucnhash_CAPI pointer dangling. @@ -1262,7 +1262,7 @@ def test_linebreak_7643(self): self.assertEqual(len(lines), 1, r"%a should not be a linebreak" % c) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'iter_graphemes' + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'iter_graphemes' def test_segment_object(self): segments = list(unicodedata.iter_graphemes('spa\u0300m')) self.assertEqual(len(segments), 4, segments) @@ -1383,8 +1383,8 @@ class MyStr(str): class GraphemeBreakTest(unittest.TestCase): + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError module 'unicodedata' has no attribute 'iter_graphemes' @requires_resource('network') - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError module 'unicodedata' has no attribute 'iter_graphemes' def test_grapheme_break(self): TESTDATAFILE = "GraphemeBreakTest.txt" testdata = download_test_data_file(TESTDATAFILE) @@ -1423,7 +1423,7 @@ def run_grapheme_break_tests(self, testdata): self.assertEqual([x.start for x in result], breaks[i:-1], comment) self.assertEqual([x.end for x in result], breaks[i+1:], comment) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'iter_graphemes' + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'iter_graphemes' def test_reference_loops(self): # Test that reference loops involving GraphemeBreakIterator or # Segment can be broken by the garbage collector. diff --git a/crates/stdlib/src/unicodedata.rs b/crates/stdlib/src/unicodedata.rs index e5ef959c66c..9a4e01749d2 100644 --- a/crates/stdlib/src/unicodedata.rs +++ b/crates/stdlib/src/unicodedata.rs @@ -35,10 +35,187 @@ mod unicodedata { use crate::vm::{ Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, builtins::{PyModule, PyStr, PyStrRef}, + common::lock::PyMutex, function::OptionalArg, + protocol::PyIterReturn, + types::{IterNext, Iterable, Representable, SelfIter}, }; use itertools::Itertools; use rustpython_common::wtf8::{CodePoint, Wtf8Buf}; + use unicode_core::grapheme::{self, GraphemeBreakState}; + + fn grapheme_char(character: &Py, vm: &VirtualMachine) -> PyResult { + character + .as_wtf8() + .code_points() + .exactly_one() + .map_err(|_| vm.new_type_error("argument must be a unicode character, not str")) + } + + #[pyfunction] + fn grapheme_cluster_break(chr: PyStrRef, vm: &VirtualMachine) -> PyResult<&'static str> { + grapheme_char(&chr, vm).map(grapheme::grapheme_cluster_break) + } + + #[pyfunction] + fn indic_conjunct_break(chr: PyStrRef, vm: &VirtualMachine) -> PyResult<&'static str> { + grapheme_char(&chr, vm).map(grapheme::indic_conjunct_break) + } + + #[pyfunction] + fn extended_pictographic(chr: PyStrRef, vm: &VirtualMachine) -> PyResult { + grapheme_char(&chr, vm).map(grapheme::extended_pictographic) + } + + #[pyclass(no_attr, name = "Segment", traverse)] + #[derive(Debug, PyPayload)] + struct Segment { + string: PyStrRef, + #[pytraverse(skip)] + start: usize, + #[pytraverse(skip)] + end: usize, + #[pytraverse(skip)] + bytes: core::ops::Range, + } + + #[pyclass(with(Representable), flags(IMMUTABLETYPE, DISALLOW_INSTANTIATION))] + impl Segment { + #[pygetset] + fn start(&self) -> usize { + self.start + } + + #[pygetset] + fn end(&self) -> usize { + self.end + } + + #[pymethod] + fn __str__(&self, vm: &VirtualMachine) -> PyStrRef { + vm.ctx.new_str(&self.string.as_wtf8()[self.bytes.clone()]) + } + } + + impl Representable for Segment { + #[inline] + fn repr_str(zelf: &Py, _vm: &VirtualMachine) -> PyResult { + Ok(format!("", zelf.start, zelf.end)) + } + } + + #[derive(Debug)] + struct GraphemePosition { + start: usize, + start_byte: usize, + pos: usize, + pos_byte: usize, + end: usize, + end_byte: usize, + state: GraphemeBreakState, + } + + #[pyclass(no_attr, name = "GraphemeBreakIterator", traverse)] + #[derive(Debug, PyPayload)] + struct GraphemeBreakIterator { + string: PyStrRef, + #[pytraverse(skip)] + position: PyMutex, + } + + #[pyclass(with(IterNext, Iterable), flags(IMMUTABLETYPE, DISALLOW_INSTANTIATION))] + impl GraphemeBreakIterator {} + + impl SelfIter for GraphemeBreakIterator {} + + impl IterNext for GraphemeBreakIterator { + fn next(zelf: &Py, vm: &VirtualMachine) -> PyResult { + let mut position = zelf.position.lock(); + if position.start >= position.end { + return Ok(PyIterReturn::StopIteration(None)); + } + let start = position.start; + let start_byte = position.start_byte; + let scan_byte = position.pos_byte; + let mut points = zelf.string.as_wtf8()[scan_byte..position.end_byte] + .code_point_indices() + .peekable(); + let (end, end_byte) = loop { + let Some((offset, point)) = points.next() else { + position.start = position.pos; + position.start_byte = position.end_byte; + break (position.pos, position.end_byte); + }; + let byte = scan_byte + offset; + let index = position.pos; + let boundary = position.state.advance(point); + position.pos += 1; + position.pos_byte = points + .peek() + .map_or(position.end_byte, |(offset, _)| scan_byte + offset); + if boundary { + position.start = index; + position.start_byte = byte; + break (index, byte); + } + }; + drop(position); + Ok(PyIterReturn::Return( + Segment { + string: zelf.string.clone(), + start, + end, + bytes: start_byte..end_byte, + } + .into_ref(&vm.ctx) + .into(), + )) + } + } + + #[pyfunction] + fn iter_graphemes( + unistr: PyStrRef, + start: OptionalArg, + end: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult> { + // Convert in argument order, including overflow, before adjusting bounds. + let index = |arg: OptionalArg, default| -> PyResult { + match arg { + OptionalArg::Present(value) => value.try_index(vm)?.try_to_primitive(vm), + OptionalArg::Missing => Ok(default), + } + }; + let mut start = index(start, 0)?; + let mut end = index(end, isize::MAX)?; + let len = unistr.char_len() as isize; + if end > len { + end = len; + } else if end < 0 { + end = (end + len).max(0); + } + if start < 0 { + start = (start + len).max(0); + } + let start = start as usize; + let end = end as usize; + let start_byte = unistr.char_index_to_byte(start); + let end_byte = unistr.char_index_to_byte(end); + Ok(GraphemeBreakIterator { + string: unistr, + position: PyMutex::new(GraphemePosition { + start, + start_byte, + pos: start, + pos_byte: start_byte, + end, + end_byte, + state: GraphemeBreakState::default(), + }), + } + .into_ref(&vm.ctx)) + } pub(crate) fn module_exec(vm: &VirtualMachine, module: &Py) -> PyResult<()> { __module_exec(vm, module); diff --git a/crates/unicode/src/grapheme.rs b/crates/unicode/src/grapheme.rs new file mode 100644 index 00000000000..544fc5d3c25 --- /dev/null +++ b/crates/unicode/src/grapheme.rs @@ -0,0 +1,177 @@ +//! Extended grapheme clusters over Python's full code point domain. +//! +//! State transitions and rule ordering follow CPython's Modules/unicodedata.c +//! at d220409a13b77de7a6c30abe16ea90db01528edc (Unicode 17.0.0). + +// spell-checker:ignore incb + +use icu_properties::{ + CodePointMapData, CodePointSetData, + props::{ExtendedPictographic, GraphemeClusterBreak as Gcb, IndicConjunctBreak as Incb}, +}; +use rustpython_wtf8::CodePoint; + +fn gcb(c: CodePoint) -> Gcb { + CodePointMapData::::new().get32(c.to_u32()) +} + +fn incb(c: CodePoint) -> Incb { + CodePointMapData::::new().get32(c.to_u32()) +} + +/// The long Grapheme_Cluster_Break property name. +pub fn grapheme_cluster_break(c: CodePoint) -> &'static str { + match gcb(c) { + Gcb::CR => "CR", + Gcb::LF => "LF", + Gcb::Control => "Control", + Gcb::Extend => "Extend", + Gcb::L => "L", + Gcb::V => "V", + Gcb::T => "T", + Gcb::LV => "LV", + Gcb::LVT => "LVT", + Gcb::SpacingMark => "SpacingMark", + Gcb::Prepend => "Prepend", + Gcb::RegionalIndicator => "Regional_Indicator", + Gcb::ZWJ => "ZWJ", + _ => "Other", + } +} + +/// The long Indic_Conjunct_Break property name. +pub fn indic_conjunct_break(c: CodePoint) -> &'static str { + match incb(c) { + Incb::Consonant => "Consonant", + Incb::Extend => "Extend", + Incb::Linker => "Linker", + _ => "None", + } +} + +/// Whether the code point has the Extended_Pictographic property. +pub fn extended_pictographic(c: CodePoint) -> bool { + CodePointSetData::new::().contains32(c.to_u32()) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ExtPictState { + Init, + Started, + Zwj, + Matched, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum IncbState { + Init, + Started, + Linker, + Matched, +} + +/// Streaming break state, initialized afresh at the requested slice start. +/// +/// A code point is consumed before its preceding boundary is returned. Keep +/// this state across cluster yields, rather than processing that point twice. +#[derive(Debug)] +pub struct GraphemeBreakState { + previous: Gcb, + pictographic: ExtPictState, + conjunct: IncbState, + regional_odd: bool, + started: bool, +} + +impl Default for GraphemeBreakState { + fn default() -> Self { + Self { + previous: Gcb::Other, + pictographic: ExtPictState::Init, + conjunct: IncbState::Init, + regional_odd: false, + started: false, + } + } +} + +impl GraphemeBreakState { + /// Consume one code point and report whether a new cluster starts here. + pub fn advance(&mut self, c: CodePoint) -> bool { + let current = gcb(c); + self.pictographic = if extended_pictographic(c) { + if self.pictographic == ExtPictState::Zwj { + ExtPictState::Matched + } else { + ExtPictState::Started + } + } else if matches!( + self.pictographic, + ExtPictState::Started | ExtPictState::Matched + ) { + match current { + Gcb::Extend => ExtPictState::Started, + Gcb::ZWJ => ExtPictState::Zwj, + _ => ExtPictState::Init, + } + } else { + ExtPictState::Init + }; + self.conjunct = match incb(c) { + Incb::Consonant => { + if self.conjunct == IncbState::Linker { + IncbState::Matched + } else { + IncbState::Started + } + } + Incb::Extend if self.conjunct != IncbState::Init => { + if self.conjunct == IncbState::Linker { + IncbState::Linker + } else { + IncbState::Started + } + } + Incb::Linker if self.conjunct != IncbState::Init => IncbState::Linker, + _ => IncbState::Init, + }; + self.regional_odd = current == Gcb::RegionalIndicator && !self.regional_odd; + let boundary = self.started && self.is_break(current); + self.previous = current; + self.started = true; + boundary + } + + fn is_break(&self, current: Gcb) -> bool { + let previous = self.previous; + // GB3, GB4, GB5: controls take precedence over attachment rules. + if previous == Gcb::CR && current == Gcb::LF { + return false; + } + if matches!(previous, Gcb::CR | Gcb::LF | Gcb::Control) + || matches!(current, Gcb::CR | Gcb::LF | Gcb::Control) + { + return true; + } + // GB6, GB7, GB8: Hangul syllables. + if previous == Gcb::L && matches!(current, Gcb::L | Gcb::V | Gcb::LV | Gcb::LVT) + || matches!(previous, Gcb::LV | Gcb::V) && matches!(current, Gcb::V | Gcb::T) + || matches!(previous, Gcb::LVT | Gcb::T) && current == Gcb::T + { + return false; + } + // GB9, GB9a, GB9b, GB9c, GB11. + if matches!(current, Gcb::Extend | Gcb::ZWJ | Gcb::SpacingMark) + || previous == Gcb::Prepend + || self.conjunct == IncbState::Matched + || self.pictographic == ExtPictState::Matched + { + return false; + } + // GB12, GB13: parity includes the current regional indicator. + if previous == Gcb::RegionalIndicator && current == previous { + return self.regional_odd; + } + true // GB999 + } +} diff --git a/crates/unicode/src/lib.rs b/crates/unicode/src/lib.rs index 319271b019e..fef618dfe17 100644 --- a/crates/unicode/src/lib.rs +++ b/crates/unicode/src/lib.rs @@ -12,6 +12,7 @@ extern crate alloc; pub mod case; pub mod classify; pub mod data; +pub mod grapheme; pub mod identifier; pub mod normalize; diff --git a/crates/unicode/tests/data/cpython3.15_graphemes.txt b/crates/unicode/tests/data/cpython3.15_graphemes.txt new file mode 100644 index 00000000000..78de1d3824c --- /dev/null +++ b/crates/unicode/tests/data/cpython3.15_graphemes.txt @@ -0,0 +1,2171 @@ +# CPython d220409a13b77de7a6c30abe16ea90db01528edc; Unicode 17.0.0 +# Inclusive contiguous ranges: start:end GCB InCB Extended_Pictographic +# Reconstructed raw SHA256 9aae5520167e69e08d2241b9899bc7c65b0865d1713ffde698a6e7e1c952284d +000000:000009 Control None 0 +00000A:00000A LF None 0 +00000B:00000C Control None 0 +00000D:00000D CR None 0 +00000E:00001F Control None 0 +000020:00007E Other None 0 +00007F:00009F Control None 0 +0000A0:0000A8 Other None 0 +0000A9:0000A9 Other None 1 +0000AA:0000AC Other None 0 +0000AD:0000AD Control None 0 +0000AE:0000AE Other None 1 +0000AF:0002FF Other None 0 +000300:00036F Extend Extend 0 +000370:000482 Other None 0 +000483:000489 Extend Extend 0 +00048A:000590 Other None 0 +000591:0005BD Extend Extend 0 +0005BE:0005BE Other None 0 +0005BF:0005BF Extend Extend 0 +0005C0:0005C0 Other None 0 +0005C1:0005C2 Extend Extend 0 +0005C3:0005C3 Other None 0 +0005C4:0005C5 Extend Extend 0 +0005C6:0005C6 Other None 0 +0005C7:0005C7 Extend Extend 0 +0005C8:0005FF Other None 0 +000600:000605 Prepend None 0 +000606:00060F Other None 0 +000610:00061A Extend Extend 0 +00061B:00061B Other None 0 +00061C:00061C Control None 0 +00061D:00064A Other None 0 +00064B:00065F Extend Extend 0 +000660:00066F Other None 0 +000670:000670 Extend Extend 0 +000671:0006D5 Other None 0 +0006D6:0006DC Extend Extend 0 +0006DD:0006DD Prepend None 0 +0006DE:0006DE Other None 0 +0006DF:0006E4 Extend Extend 0 +0006E5:0006E6 Other None 0 +0006E7:0006E8 Extend Extend 0 +0006E9:0006E9 Other None 0 +0006EA:0006ED Extend Extend 0 +0006EE:00070E Other None 0 +00070F:00070F Prepend None 0 +000710:000710 Other None 0 +000711:000711 Extend Extend 0 +000712:00072F Other None 0 +000730:00074A Extend Extend 0 +00074B:0007A5 Other None 0 +0007A6:0007B0 Extend Extend 0 +0007B1:0007EA Other None 0 +0007EB:0007F3 Extend Extend 0 +0007F4:0007FC Other None 0 +0007FD:0007FD Extend Extend 0 +0007FE:000815 Other None 0 +000816:000819 Extend Extend 0 +00081A:00081A Other None 0 +00081B:000823 Extend Extend 0 +000824:000824 Other None 0 +000825:000827 Extend Extend 0 +000828:000828 Other None 0 +000829:00082D Extend Extend 0 +00082E:000858 Other None 0 +000859:00085B Extend Extend 0 +00085C:00088F Other None 0 +000890:000891 Prepend None 0 +000892:000896 Other None 0 +000897:00089F Extend Extend 0 +0008A0:0008C9 Other None 0 +0008CA:0008E1 Extend Extend 0 +0008E2:0008E2 Prepend None 0 +0008E3:000902 Extend Extend 0 +000903:000903 SpacingMark None 0 +000904:000914 Other None 0 +000915:000939 Other Consonant 0 +00093A:00093A Extend Extend 0 +00093B:00093B SpacingMark None 0 +00093C:00093C Extend Extend 0 +00093D:00093D Other None 0 +00093E:000940 SpacingMark None 0 +000941:000948 Extend Extend 0 +000949:00094C SpacingMark None 0 +00094D:00094D Extend Linker 0 +00094E:00094F SpacingMark None 0 +000950:000950 Other None 0 +000951:000957 Extend Extend 0 +000958:00095F Other Consonant 0 +000960:000961 Other None 0 +000962:000963 Extend Extend 0 +000964:000977 Other None 0 +000978:00097F Other Consonant 0 +000980:000980 Other None 0 +000981:000981 Extend Extend 0 +000982:000983 SpacingMark None 0 +000984:000994 Other None 0 +000995:0009A8 Other Consonant 0 +0009A9:0009A9 Other None 0 +0009AA:0009B0 Other Consonant 0 +0009B1:0009B1 Other None 0 +0009B2:0009B2 Other Consonant 0 +0009B3:0009B5 Other None 0 +0009B6:0009B9 Other Consonant 0 +0009BA:0009BB Other None 0 +0009BC:0009BC Extend Extend 0 +0009BD:0009BD Other None 0 +0009BE:0009BE Extend Extend 0 +0009BF:0009C0 SpacingMark None 0 +0009C1:0009C4 Extend Extend 0 +0009C5:0009C6 Other None 0 +0009C7:0009C8 SpacingMark None 0 +0009C9:0009CA Other None 0 +0009CB:0009CC SpacingMark None 0 +0009CD:0009CD Extend Linker 0 +0009CE:0009D6 Other None 0 +0009D7:0009D7 Extend Extend 0 +0009D8:0009DB Other None 0 +0009DC:0009DD Other Consonant 0 +0009DE:0009DE Other None 0 +0009DF:0009DF Other Consonant 0 +0009E0:0009E1 Other None 0 +0009E2:0009E3 Extend Extend 0 +0009E4:0009EF Other None 0 +0009F0:0009F1 Other Consonant 0 +0009F2:0009FD Other None 0 +0009FE:0009FE Extend Extend 0 +0009FF:000A00 Other None 0 +000A01:000A02 Extend Extend 0 +000A03:000A03 SpacingMark None 0 +000A04:000A3B Other None 0 +000A3C:000A3C Extend Extend 0 +000A3D:000A3D Other None 0 +000A3E:000A40 SpacingMark None 0 +000A41:000A42 Extend Extend 0 +000A43:000A46 Other None 0 +000A47:000A48 Extend Extend 0 +000A49:000A4A Other None 0 +000A4B:000A4D Extend Extend 0 +000A4E:000A50 Other None 0 +000A51:000A51 Extend Extend 0 +000A52:000A6F Other None 0 +000A70:000A71 Extend Extend 0 +000A72:000A74 Other None 0 +000A75:000A75 Extend Extend 0 +000A76:000A80 Other None 0 +000A81:000A82 Extend Extend 0 +000A83:000A83 SpacingMark None 0 +000A84:000A94 Other None 0 +000A95:000AA8 Other Consonant 0 +000AA9:000AA9 Other None 0 +000AAA:000AB0 Other Consonant 0 +000AB1:000AB1 Other None 0 +000AB2:000AB3 Other Consonant 0 +000AB4:000AB4 Other None 0 +000AB5:000AB9 Other Consonant 0 +000ABA:000ABB Other None 0 +000ABC:000ABC Extend Extend 0 +000ABD:000ABD Other None 0 +000ABE:000AC0 SpacingMark None 0 +000AC1:000AC5 Extend Extend 0 +000AC6:000AC6 Other None 0 +000AC7:000AC8 Extend Extend 0 +000AC9:000AC9 SpacingMark None 0 +000ACA:000ACA Other None 0 +000ACB:000ACC SpacingMark None 0 +000ACD:000ACD Extend Linker 0 +000ACE:000AE1 Other None 0 +000AE2:000AE3 Extend Extend 0 +000AE4:000AF8 Other None 0 +000AF9:000AF9 Other Consonant 0 +000AFA:000AFF Extend Extend 0 +000B00:000B00 Other None 0 +000B01:000B01 Extend Extend 0 +000B02:000B03 SpacingMark None 0 +000B04:000B14 Other None 0 +000B15:000B28 Other Consonant 0 +000B29:000B29 Other None 0 +000B2A:000B30 Other Consonant 0 +000B31:000B31 Other None 0 +000B32:000B33 Other Consonant 0 +000B34:000B34 Other None 0 +000B35:000B39 Other Consonant 0 +000B3A:000B3B Other None 0 +000B3C:000B3C Extend Extend 0 +000B3D:000B3D Other None 0 +000B3E:000B3F Extend Extend 0 +000B40:000B40 SpacingMark None 0 +000B41:000B44 Extend Extend 0 +000B45:000B46 Other None 0 +000B47:000B48 SpacingMark None 0 +000B49:000B4A Other None 0 +000B4B:000B4C SpacingMark None 0 +000B4D:000B4D Extend Linker 0 +000B4E:000B54 Other None 0 +000B55:000B57 Extend Extend 0 +000B58:000B5B Other None 0 +000B5C:000B5D Other Consonant 0 +000B5E:000B5E Other None 0 +000B5F:000B5F Other Consonant 0 +000B60:000B61 Other None 0 +000B62:000B63 Extend Extend 0 +000B64:000B70 Other None 0 +000B71:000B71 Other Consonant 0 +000B72:000B81 Other None 0 +000B82:000B82 Extend Extend 0 +000B83:000BBD Other None 0 +000BBE:000BBE Extend Extend 0 +000BBF:000BBF SpacingMark None 0 +000BC0:000BC0 Extend Extend 0 +000BC1:000BC2 SpacingMark None 0 +000BC3:000BC5 Other None 0 +000BC6:000BC8 SpacingMark None 0 +000BC9:000BC9 Other None 0 +000BCA:000BCC SpacingMark None 0 +000BCD:000BCD Extend Extend 0 +000BCE:000BD6 Other None 0 +000BD7:000BD7 Extend Extend 0 +000BD8:000BFF Other None 0 +000C00:000C00 Extend Extend 0 +000C01:000C03 SpacingMark None 0 +000C04:000C04 Extend Extend 0 +000C05:000C14 Other None 0 +000C15:000C28 Other Consonant 0 +000C29:000C29 Other None 0 +000C2A:000C39 Other Consonant 0 +000C3A:000C3B Other None 0 +000C3C:000C3C Extend Extend 0 +000C3D:000C3D Other None 0 +000C3E:000C40 Extend Extend 0 +000C41:000C44 SpacingMark None 0 +000C45:000C45 Other None 0 +000C46:000C48 Extend Extend 0 +000C49:000C49 Other None 0 +000C4A:000C4C Extend Extend 0 +000C4D:000C4D Extend Linker 0 +000C4E:000C54 Other None 0 +000C55:000C56 Extend Extend 0 +000C57:000C57 Other None 0 +000C58:000C5A Other Consonant 0 +000C5B:000C61 Other None 0 +000C62:000C63 Extend Extend 0 +000C64:000C80 Other None 0 +000C81:000C81 Extend Extend 0 +000C82:000C83 SpacingMark None 0 +000C84:000CBB Other None 0 +000CBC:000CBC Extend Extend 0 +000CBD:000CBD Other None 0 +000CBE:000CBE SpacingMark None 0 +000CBF:000CC0 Extend Extend 0 +000CC1:000CC1 SpacingMark None 0 +000CC2:000CC2 Extend Extend 0 +000CC3:000CC4 SpacingMark None 0 +000CC5:000CC5 Other None 0 +000CC6:000CC8 Extend Extend 0 +000CC9:000CC9 Other None 0 +000CCA:000CCD Extend Extend 0 +000CCE:000CD4 Other None 0 +000CD5:000CD6 Extend Extend 0 +000CD7:000CE1 Other None 0 +000CE2:000CE3 Extend Extend 0 +000CE4:000CF2 Other None 0 +000CF3:000CF3 SpacingMark None 0 +000CF4:000CFF Other None 0 +000D00:000D01 Extend Extend 0 +000D02:000D03 SpacingMark None 0 +000D04:000D14 Other None 0 +000D15:000D3A Other Consonant 0 +000D3B:000D3C Extend Extend 0 +000D3D:000D3D Other None 0 +000D3E:000D3E Extend Extend 0 +000D3F:000D40 SpacingMark None 0 +000D41:000D44 Extend Extend 0 +000D45:000D45 Other None 0 +000D46:000D48 SpacingMark None 0 +000D49:000D49 Other None 0 +000D4A:000D4C SpacingMark None 0 +000D4D:000D4D Extend Linker 0 +000D4E:000D4E Prepend None 0 +000D4F:000D56 Other None 0 +000D57:000D57 Extend Extend 0 +000D58:000D61 Other None 0 +000D62:000D63 Extend Extend 0 +000D64:000D80 Other None 0 +000D81:000D81 Extend Extend 0 +000D82:000D83 SpacingMark None 0 +000D84:000DC9 Other None 0 +000DCA:000DCA Extend Extend 0 +000DCB:000DCE Other None 0 +000DCF:000DCF Extend Extend 0 +000DD0:000DD1 SpacingMark None 0 +000DD2:000DD4 Extend Extend 0 +000DD5:000DD5 Other None 0 +000DD6:000DD6 Extend Extend 0 +000DD7:000DD7 Other None 0 +000DD8:000DDE SpacingMark None 0 +000DDF:000DDF Extend Extend 0 +000DE0:000DF1 Other None 0 +000DF2:000DF3 SpacingMark None 0 +000DF4:000E30 Other None 0 +000E31:000E31 Extend Extend 0 +000E32:000E32 Other None 0 +000E33:000E33 SpacingMark None 0 +000E34:000E3A Extend Extend 0 +000E3B:000E46 Other None 0 +000E47:000E4E Extend Extend 0 +000E4F:000EB0 Other None 0 +000EB1:000EB1 Extend Extend 0 +000EB2:000EB2 Other None 0 +000EB3:000EB3 SpacingMark None 0 +000EB4:000EBC Extend Extend 0 +000EBD:000EC7 Other None 0 +000EC8:000ECE Extend Extend 0 +000ECF:000F17 Other None 0 +000F18:000F19 Extend Extend 0 +000F1A:000F34 Other None 0 +000F35:000F35 Extend Extend 0 +000F36:000F36 Other None 0 +000F37:000F37 Extend Extend 0 +000F38:000F38 Other None 0 +000F39:000F39 Extend Extend 0 +000F3A:000F3D Other None 0 +000F3E:000F3F SpacingMark None 0 +000F40:000F70 Other None 0 +000F71:000F7E Extend Extend 0 +000F7F:000F7F SpacingMark None 0 +000F80:000F84 Extend Extend 0 +000F85:000F85 Other None 0 +000F86:000F87 Extend Extend 0 +000F88:000F8C Other None 0 +000F8D:000F97 Extend Extend 0 +000F98:000F98 Other None 0 +000F99:000FBC Extend Extend 0 +000FBD:000FC5 Other None 0 +000FC6:000FC6 Extend Extend 0 +000FC7:000FFF Other None 0 +001000:00102A Other Consonant 0 +00102B:00102C Other None 0 +00102D:001030 Extend Extend 0 +001031:001031 SpacingMark None 0 +001032:001037 Extend Extend 0 +001038:001038 Other None 0 +001039:001039 Extend Linker 0 +00103A:00103A Extend Extend 0 +00103B:00103C SpacingMark None 0 +00103D:00103E Extend Extend 0 +00103F:00103F Other Consonant 0 +001040:00104F Other None 0 +001050:001055 Other Consonant 0 +001056:001057 SpacingMark None 0 +001058:001059 Extend Extend 0 +00105A:00105D Other Consonant 0 +00105E:001060 Extend Extend 0 +001061:001061 Other Consonant 0 +001062:001064 Other None 0 +001065:001066 Other Consonant 0 +001067:00106D Other None 0 +00106E:001070 Other Consonant 0 +001071:001074 Extend Extend 0 +001075:001081 Other Consonant 0 +001082:001082 Extend Extend 0 +001083:001083 Other None 0 +001084:001084 SpacingMark None 0 +001085:001086 Extend Extend 0 +001087:00108C Other None 0 +00108D:00108D Extend Extend 0 +00108E:00108E Other Consonant 0 +00108F:00109C Other None 0 +00109D:00109D Extend Extend 0 +00109E:0010FF Other None 0 +001100:00115F L None 0 +001160:0011A7 V None 0 +0011A8:0011FF T None 0 +001200:00135C Other None 0 +00135D:00135F Extend Extend 0 +001360:001711 Other None 0 +001712:001715 Extend Extend 0 +001716:001731 Other None 0 +001732:001734 Extend Extend 0 +001735:001751 Other None 0 +001752:001753 Extend Extend 0 +001754:001771 Other None 0 +001772:001773 Extend Extend 0 +001774:00177F Other None 0 +001780:0017B3 Other Consonant 0 +0017B4:0017B5 Extend Extend 0 +0017B6:0017B6 SpacingMark None 0 +0017B7:0017BD Extend Extend 0 +0017BE:0017C5 SpacingMark None 0 +0017C6:0017C6 Extend Extend 0 +0017C7:0017C8 SpacingMark None 0 +0017C9:0017D1 Extend Extend 0 +0017D2:0017D2 Extend Linker 0 +0017D3:0017D3 Extend Extend 0 +0017D4:0017DC Other None 0 +0017DD:0017DD Extend Extend 0 +0017DE:00180A Other None 0 +00180B:00180D Extend Extend 0 +00180E:00180E Control None 0 +00180F:00180F Extend Extend 0 +001810:001884 Other None 0 +001885:001886 Extend Extend 0 +001887:0018A8 Other None 0 +0018A9:0018A9 Extend Extend 0 +0018AA:00191F Other None 0 +001920:001922 Extend Extend 0 +001923:001926 SpacingMark None 0 +001927:001928 Extend Extend 0 +001929:00192B SpacingMark None 0 +00192C:00192F Other None 0 +001930:001931 SpacingMark None 0 +001932:001932 Extend Extend 0 +001933:001938 SpacingMark None 0 +001939:00193B Extend Extend 0 +00193C:001A16 Other None 0 +001A17:001A18 Extend Extend 0 +001A19:001A1A SpacingMark None 0 +001A1B:001A1B Extend Extend 0 +001A1C:001A1F Other None 0 +001A20:001A54 Other Consonant 0 +001A55:001A55 SpacingMark None 0 +001A56:001A56 Extend Extend 0 +001A57:001A57 SpacingMark None 0 +001A58:001A5E Extend Extend 0 +001A5F:001A5F Other None 0 +001A60:001A60 Extend Linker 0 +001A61:001A61 Other None 0 +001A62:001A62 Extend Extend 0 +001A63:001A64 Other None 0 +001A65:001A6C Extend Extend 0 +001A6D:001A72 SpacingMark None 0 +001A73:001A7C Extend Extend 0 +001A7D:001A7E Other None 0 +001A7F:001A7F Extend Extend 0 +001A80:001AAF Other None 0 +001AB0:001ADD Extend Extend 0 +001ADE:001ADF Other None 0 +001AE0:001AEB Extend Extend 0 +001AEC:001AFF Other None 0 +001B00:001B03 Extend Extend 0 +001B04:001B04 SpacingMark None 0 +001B05:001B0A Other None 0 +001B0B:001B0C Other Consonant 0 +001B0D:001B12 Other None 0 +001B13:001B33 Other Consonant 0 +001B34:001B3D Extend Extend 0 +001B3E:001B41 SpacingMark None 0 +001B42:001B43 Extend Extend 0 +001B44:001B44 Extend Linker 0 +001B45:001B4C Other Consonant 0 +001B4D:001B6A Other None 0 +001B6B:001B73 Extend Extend 0 +001B74:001B7F Other None 0 +001B80:001B81 Extend Extend 0 +001B82:001B82 SpacingMark None 0 +001B83:001BA0 Other Consonant 0 +001BA1:001BA1 SpacingMark None 0 +001BA2:001BA5 Extend Extend 0 +001BA6:001BA7 SpacingMark None 0 +001BA8:001BAA Extend Extend 0 +001BAB:001BAB Extend Linker 0 +001BAC:001BAD Extend Extend 0 +001BAE:001BAF Other Consonant 0 +001BB0:001BBA Other None 0 +001BBB:001BBD Other Consonant 0 +001BBE:001BE5 Other None 0 +001BE6:001BE6 Extend Extend 0 +001BE7:001BE7 SpacingMark None 0 +001BE8:001BE9 Extend Extend 0 +001BEA:001BEC SpacingMark None 0 +001BED:001BED Extend Extend 0 +001BEE:001BEE SpacingMark None 0 +001BEF:001BF3 Extend Extend 0 +001BF4:001C23 Other None 0 +001C24:001C2B SpacingMark None 0 +001C2C:001C33 Extend Extend 0 +001C34:001C35 SpacingMark None 0 +001C36:001C37 Extend Extend 0 +001C38:001CCF Other None 0 +001CD0:001CD2 Extend Extend 0 +001CD3:001CD3 Other None 0 +001CD4:001CE0 Extend Extend 0 +001CE1:001CE1 SpacingMark None 0 +001CE2:001CE8 Extend Extend 0 +001CE9:001CEC Other None 0 +001CED:001CED Extend Extend 0 +001CEE:001CF3 Other None 0 +001CF4:001CF4 Extend Extend 0 +001CF5:001CF6 Other None 0 +001CF7:001CF7 SpacingMark None 0 +001CF8:001CF9 Extend Extend 0 +001CFA:001DBF Other None 0 +001DC0:001DFF Extend Extend 0 +001E00:00200A Other None 0 +00200B:00200B Control None 0 +00200C:00200C Extend None 0 +00200D:00200D ZWJ Extend 0 +00200E:00200F Control None 0 +002010:002027 Other None 0 +002028:00202E Control None 0 +00202F:00203B Other None 0 +00203C:00203C Other None 1 +00203D:002048 Other None 0 +002049:002049 Other None 1 +00204A:00205F Other None 0 +002060:00206F Control None 0 +002070:0020CF Other None 0 +0020D0:0020F0 Extend Extend 0 +0020F1:002121 Other None 0 +002122:002122 Other None 1 +002123:002138 Other None 0 +002139:002139 Other None 1 +00213A:002193 Other None 0 +002194:002199 Other None 1 +00219A:0021A8 Other None 0 +0021A9:0021AA Other None 1 +0021AB:002319 Other None 0 +00231A:00231B Other None 1 +00231C:002327 Other None 0 +002328:002328 Other None 1 +002329:0023CE Other None 0 +0023CF:0023CF Other None 1 +0023D0:0023E8 Other None 0 +0023E9:0023F3 Other None 1 +0023F4:0023F7 Other None 0 +0023F8:0023FA Other None 1 +0023FB:0024C1 Other None 0 +0024C2:0024C2 Other None 1 +0024C3:0025A9 Other None 0 +0025AA:0025AB Other None 1 +0025AC:0025B5 Other None 0 +0025B6:0025B6 Other None 1 +0025B7:0025BF Other None 0 +0025C0:0025C0 Other None 1 +0025C1:0025FA Other None 0 +0025FB:0025FE Other None 1 +0025FF:0025FF Other None 0 +002600:002604 Other None 1 +002605:00260D Other None 0 +00260E:00260E Other None 1 +00260F:002610 Other None 0 +002611:002611 Other None 1 +002612:002613 Other None 0 +002614:002615 Other None 1 +002616:002617 Other None 0 +002618:002618 Other None 1 +002619:00261C Other None 0 +00261D:00261D Other None 1 +00261E:00261F Other None 0 +002620:002620 Other None 1 +002621:002621 Other None 0 +002622:002623 Other None 1 +002624:002625 Other None 0 +002626:002626 Other None 1 +002627:002629 Other None 0 +00262A:00262A Other None 1 +00262B:00262D Other None 0 +00262E:00262F Other None 1 +002630:002637 Other None 0 +002638:00263A Other None 1 +00263B:00263F Other None 0 +002640:002640 Other None 1 +002641:002641 Other None 0 +002642:002642 Other None 1 +002643:002647 Other None 0 +002648:002653 Other None 1 +002654:00265E Other None 0 +00265F:002660 Other None 1 +002661:002662 Other None 0 +002663:002663 Other None 1 +002664:002664 Other None 0 +002665:002666 Other None 1 +002667:002667 Other None 0 +002668:002668 Other None 1 +002669:00267A Other None 0 +00267B:00267B Other None 1 +00267C:00267D Other None 0 +00267E:00267F Other None 1 +002680:002691 Other None 0 +002692:002697 Other None 1 +002698:002698 Other None 0 +002699:002699 Other None 1 +00269A:00269A Other None 0 +00269B:00269C Other None 1 +00269D:00269F Other None 0 +0026A0:0026A1 Other None 1 +0026A2:0026A6 Other None 0 +0026A7:0026A7 Other None 1 +0026A8:0026A9 Other None 0 +0026AA:0026AB Other None 1 +0026AC:0026AF Other None 0 +0026B0:0026B1 Other None 1 +0026B2:0026BC Other None 0 +0026BD:0026BE Other None 1 +0026BF:0026C3 Other None 0 +0026C4:0026C5 Other None 1 +0026C6:0026C7 Other None 0 +0026C8:0026C8 Other None 1 +0026C9:0026CD Other None 0 +0026CE:0026CF Other None 1 +0026D0:0026D0 Other None 0 +0026D1:0026D1 Other None 1 +0026D2:0026D2 Other None 0 +0026D3:0026D4 Other None 1 +0026D5:0026E8 Other None 0 +0026E9:0026EA Other None 1 +0026EB:0026EF Other None 0 +0026F0:0026F5 Other None 1 +0026F6:0026F6 Other None 0 +0026F7:0026FA Other None 1 +0026FB:0026FC Other None 0 +0026FD:0026FD Other None 1 +0026FE:002701 Other None 0 +002702:002702 Other None 1 +002703:002704 Other None 0 +002705:002705 Other None 1 +002706:002707 Other None 0 +002708:00270D Other None 1 +00270E:00270E Other None 0 +00270F:00270F Other None 1 +002710:002711 Other None 0 +002712:002712 Other None 1 +002713:002713 Other None 0 +002714:002714 Other None 1 +002715:002715 Other None 0 +002716:002716 Other None 1 +002717:00271C Other None 0 +00271D:00271D Other None 1 +00271E:002720 Other None 0 +002721:002721 Other None 1 +002722:002727 Other None 0 +002728:002728 Other None 1 +002729:002732 Other None 0 +002733:002734 Other None 1 +002735:002743 Other None 0 +002744:002744 Other None 1 +002745:002746 Other None 0 +002747:002747 Other None 1 +002748:00274B Other None 0 +00274C:00274C Other None 1 +00274D:00274D Other None 0 +00274E:00274E Other None 1 +00274F:002752 Other None 0 +002753:002755 Other None 1 +002756:002756 Other None 0 +002757:002757 Other None 1 +002758:002762 Other None 0 +002763:002764 Other None 1 +002765:002794 Other None 0 +002795:002797 Other None 1 +002798:0027A0 Other None 0 +0027A1:0027A1 Other None 1 +0027A2:0027AF Other None 0 +0027B0:0027B0 Other None 1 +0027B1:0027BE Other None 0 +0027BF:0027BF Other None 1 +0027C0:002933 Other None 0 +002934:002935 Other None 1 +002936:002B04 Other None 0 +002B05:002B07 Other None 1 +002B08:002B1A Other None 0 +002B1B:002B1C Other None 1 +002B1D:002B4F Other None 0 +002B50:002B50 Other None 1 +002B51:002B54 Other None 0 +002B55:002B55 Other None 1 +002B56:002CEE Other None 0 +002CEF:002CF1 Extend Extend 0 +002CF2:002D7E Other None 0 +002D7F:002D7F Extend Extend 0 +002D80:002DDF Other None 0 +002DE0:002DFF Extend Extend 0 +002E00:003029 Other None 0 +00302A:00302F Extend Extend 0 +003030:003030 Other None 1 +003031:00303C Other None 0 +00303D:00303D Other None 1 +00303E:003098 Other None 0 +003099:00309A Extend Extend 0 +00309B:003296 Other None 0 +003297:003297 Other None 1 +003298:003298 Other None 0 +003299:003299 Other None 1 +00329A:00A66E Other None 0 +00A66F:00A672 Extend Extend 0 +00A673:00A673 Other None 0 +00A674:00A67D Extend Extend 0 +00A67E:00A69D Other None 0 +00A69E:00A69F Extend Extend 0 +00A6A0:00A6EF Other None 0 +00A6F0:00A6F1 Extend Extend 0 +00A6F2:00A801 Other None 0 +00A802:00A802 Extend Extend 0 +00A803:00A805 Other None 0 +00A806:00A806 Extend Extend 0 +00A807:00A80A Other None 0 +00A80B:00A80B Extend Extend 0 +00A80C:00A822 Other None 0 +00A823:00A824 SpacingMark None 0 +00A825:00A826 Extend Extend 0 +00A827:00A827 SpacingMark None 0 +00A828:00A82B Other None 0 +00A82C:00A82C Extend Extend 0 +00A82D:00A87F Other None 0 +00A880:00A881 SpacingMark None 0 +00A882:00A8B3 Other None 0 +00A8B4:00A8C3 SpacingMark None 0 +00A8C4:00A8C5 Extend Extend 0 +00A8C6:00A8DF Other None 0 +00A8E0:00A8F1 Extend Extend 0 +00A8F2:00A8FE Other None 0 +00A8FF:00A8FF Extend Extend 0 +00A900:00A925 Other None 0 +00A926:00A92D Extend Extend 0 +00A92E:00A946 Other None 0 +00A947:00A951 Extend Extend 0 +00A952:00A952 SpacingMark None 0 +00A953:00A953 Extend Extend 0 +00A954:00A95F Other None 0 +00A960:00A97C L None 0 +00A97D:00A97F Other None 0 +00A980:00A982 Extend Extend 0 +00A983:00A983 SpacingMark None 0 +00A984:00A988 Other None 0 +00A989:00A98B Other Consonant 0 +00A98C:00A98E Other None 0 +00A98F:00A9B2 Other Consonant 0 +00A9B3:00A9B3 Extend Extend 0 +00A9B4:00A9B5 SpacingMark None 0 +00A9B6:00A9B9 Extend Extend 0 +00A9BA:00A9BB SpacingMark None 0 +00A9BC:00A9BD Extend Extend 0 +00A9BE:00A9BF SpacingMark None 0 +00A9C0:00A9C0 Extend Linker 0 +00A9C1:00A9DF Other None 0 +00A9E0:00A9E4 Other Consonant 0 +00A9E5:00A9E5 Extend Extend 0 +00A9E6:00A9E6 Other None 0 +00A9E7:00A9EF Other Consonant 0 +00A9F0:00A9F9 Other None 0 +00A9FA:00A9FE Other Consonant 0 +00A9FF:00AA28 Other None 0 +00AA29:00AA2E Extend Extend 0 +00AA2F:00AA30 SpacingMark None 0 +00AA31:00AA32 Extend Extend 0 +00AA33:00AA34 SpacingMark None 0 +00AA35:00AA36 Extend Extend 0 +00AA37:00AA42 Other None 0 +00AA43:00AA43 Extend Extend 0 +00AA44:00AA4B Other None 0 +00AA4C:00AA4C Extend Extend 0 +00AA4D:00AA4D SpacingMark None 0 +00AA4E:00AA5F Other None 0 +00AA60:00AA6F Other Consonant 0 +00AA70:00AA70 Other None 0 +00AA71:00AA73 Other Consonant 0 +00AA74:00AA79 Other None 0 +00AA7A:00AA7A Other Consonant 0 +00AA7B:00AA7B Other None 0 +00AA7C:00AA7C Extend Extend 0 +00AA7D:00AA7D Other None 0 +00AA7E:00AA7F Other Consonant 0 +00AA80:00AAAF Other None 0 +00AAB0:00AAB0 Extend Extend 0 +00AAB1:00AAB1 Other None 0 +00AAB2:00AAB4 Extend Extend 0 +00AAB5:00AAB6 Other None 0 +00AAB7:00AAB8 Extend Extend 0 +00AAB9:00AABD Other None 0 +00AABE:00AABF Extend Extend 0 +00AAC0:00AAC0 Other None 0 +00AAC1:00AAC1 Extend Extend 0 +00AAC2:00AADF Other None 0 +00AAE0:00AAEA Other Consonant 0 +00AAEB:00AAEB SpacingMark None 0 +00AAEC:00AAED Extend Extend 0 +00AAEE:00AAEF SpacingMark None 0 +00AAF0:00AAF4 Other None 0 +00AAF5:00AAF5 SpacingMark None 0 +00AAF6:00AAF6 Extend Linker 0 +00AAF7:00ABBF Other None 0 +00ABC0:00ABDA Other Consonant 0 +00ABDB:00ABE2 Other None 0 +00ABE3:00ABE4 SpacingMark None 0 +00ABE5:00ABE5 Extend Extend 0 +00ABE6:00ABE7 SpacingMark None 0 +00ABE8:00ABE8 Extend Extend 0 +00ABE9:00ABEA SpacingMark None 0 +00ABEB:00ABEB Other None 0 +00ABEC:00ABEC SpacingMark None 0 +00ABED:00ABED Extend Extend 0 +00ABEE:00ABFF Other None 0 +00AC00:00AC00 LV None 0 +00AC01:00AC1B LVT None 0 +00AC1C:00AC1C LV None 0 +00AC1D:00AC37 LVT None 0 +00AC38:00AC38 LV None 0 +00AC39:00AC53 LVT None 0 +00AC54:00AC54 LV None 0 +00AC55:00AC6F LVT None 0 +00AC70:00AC70 LV None 0 +00AC71:00AC8B LVT None 0 +00AC8C:00AC8C LV None 0 +00AC8D:00ACA7 LVT None 0 +00ACA8:00ACA8 LV None 0 +00ACA9:00ACC3 LVT None 0 +00ACC4:00ACC4 LV None 0 +00ACC5:00ACDF LVT None 0 +00ACE0:00ACE0 LV None 0 +00ACE1:00ACFB LVT None 0 +00ACFC:00ACFC LV None 0 +00ACFD:00AD17 LVT None 0 +00AD18:00AD18 LV None 0 +00AD19:00AD33 LVT None 0 +00AD34:00AD34 LV None 0 +00AD35:00AD4F LVT None 0 +00AD50:00AD50 LV None 0 +00AD51:00AD6B LVT None 0 +00AD6C:00AD6C LV None 0 +00AD6D:00AD87 LVT None 0 +00AD88:00AD88 LV None 0 +00AD89:00ADA3 LVT None 0 +00ADA4:00ADA4 LV None 0 +00ADA5:00ADBF LVT None 0 +00ADC0:00ADC0 LV None 0 +00ADC1:00ADDB LVT None 0 +00ADDC:00ADDC LV None 0 +00ADDD:00ADF7 LVT None 0 +00ADF8:00ADF8 LV None 0 +00ADF9:00AE13 LVT None 0 +00AE14:00AE14 LV None 0 +00AE15:00AE2F LVT None 0 +00AE30:00AE30 LV None 0 +00AE31:00AE4B LVT None 0 +00AE4C:00AE4C LV None 0 +00AE4D:00AE67 LVT None 0 +00AE68:00AE68 LV None 0 +00AE69:00AE83 LVT None 0 +00AE84:00AE84 LV None 0 +00AE85:00AE9F LVT None 0 +00AEA0:00AEA0 LV None 0 +00AEA1:00AEBB LVT None 0 +00AEBC:00AEBC LV None 0 +00AEBD:00AED7 LVT None 0 +00AED8:00AED8 LV None 0 +00AED9:00AEF3 LVT None 0 +00AEF4:00AEF4 LV None 0 +00AEF5:00AF0F LVT None 0 +00AF10:00AF10 LV None 0 +00AF11:00AF2B LVT None 0 +00AF2C:00AF2C LV None 0 +00AF2D:00AF47 LVT None 0 +00AF48:00AF48 LV None 0 +00AF49:00AF63 LVT None 0 +00AF64:00AF64 LV None 0 +00AF65:00AF7F LVT None 0 +00AF80:00AF80 LV None 0 +00AF81:00AF9B LVT None 0 +00AF9C:00AF9C LV None 0 +00AF9D:00AFB7 LVT None 0 +00AFB8:00AFB8 LV None 0 +00AFB9:00AFD3 LVT None 0 +00AFD4:00AFD4 LV None 0 +00AFD5:00AFEF LVT None 0 +00AFF0:00AFF0 LV None 0 +00AFF1:00B00B LVT None 0 +00B00C:00B00C LV None 0 +00B00D:00B027 LVT None 0 +00B028:00B028 LV None 0 +00B029:00B043 LVT None 0 +00B044:00B044 LV None 0 +00B045:00B05F LVT None 0 +00B060:00B060 LV None 0 +00B061:00B07B LVT None 0 +00B07C:00B07C LV None 0 +00B07D:00B097 LVT None 0 +00B098:00B098 LV None 0 +00B099:00B0B3 LVT None 0 +00B0B4:00B0B4 LV None 0 +00B0B5:00B0CF LVT None 0 +00B0D0:00B0D0 LV None 0 +00B0D1:00B0EB LVT None 0 +00B0EC:00B0EC LV None 0 +00B0ED:00B107 LVT None 0 +00B108:00B108 LV None 0 +00B109:00B123 LVT None 0 +00B124:00B124 LV None 0 +00B125:00B13F LVT None 0 +00B140:00B140 LV None 0 +00B141:00B15B LVT None 0 +00B15C:00B15C LV None 0 +00B15D:00B177 LVT None 0 +00B178:00B178 LV None 0 +00B179:00B193 LVT None 0 +00B194:00B194 LV None 0 +00B195:00B1AF LVT None 0 +00B1B0:00B1B0 LV None 0 +00B1B1:00B1CB LVT None 0 +00B1CC:00B1CC LV None 0 +00B1CD:00B1E7 LVT None 0 +00B1E8:00B1E8 LV None 0 +00B1E9:00B203 LVT None 0 +00B204:00B204 LV None 0 +00B205:00B21F LVT None 0 +00B220:00B220 LV None 0 +00B221:00B23B LVT None 0 +00B23C:00B23C LV None 0 +00B23D:00B257 LVT None 0 +00B258:00B258 LV None 0 +00B259:00B273 LVT None 0 +00B274:00B274 LV None 0 +00B275:00B28F LVT None 0 +00B290:00B290 LV None 0 +00B291:00B2AB LVT None 0 +00B2AC:00B2AC LV None 0 +00B2AD:00B2C7 LVT None 0 +00B2C8:00B2C8 LV None 0 +00B2C9:00B2E3 LVT None 0 +00B2E4:00B2E4 LV None 0 +00B2E5:00B2FF LVT None 0 +00B300:00B300 LV None 0 +00B301:00B31B LVT None 0 +00B31C:00B31C LV None 0 +00B31D:00B337 LVT None 0 +00B338:00B338 LV None 0 +00B339:00B353 LVT None 0 +00B354:00B354 LV None 0 +00B355:00B36F LVT None 0 +00B370:00B370 LV None 0 +00B371:00B38B LVT None 0 +00B38C:00B38C LV None 0 +00B38D:00B3A7 LVT None 0 +00B3A8:00B3A8 LV None 0 +00B3A9:00B3C3 LVT None 0 +00B3C4:00B3C4 LV None 0 +00B3C5:00B3DF LVT None 0 +00B3E0:00B3E0 LV None 0 +00B3E1:00B3FB LVT None 0 +00B3FC:00B3FC LV None 0 +00B3FD:00B417 LVT None 0 +00B418:00B418 LV None 0 +00B419:00B433 LVT None 0 +00B434:00B434 LV None 0 +00B435:00B44F LVT None 0 +00B450:00B450 LV None 0 +00B451:00B46B LVT None 0 +00B46C:00B46C LV None 0 +00B46D:00B487 LVT None 0 +00B488:00B488 LV None 0 +00B489:00B4A3 LVT None 0 +00B4A4:00B4A4 LV None 0 +00B4A5:00B4BF LVT None 0 +00B4C0:00B4C0 LV None 0 +00B4C1:00B4DB LVT None 0 +00B4DC:00B4DC LV None 0 +00B4DD:00B4F7 LVT None 0 +00B4F8:00B4F8 LV None 0 +00B4F9:00B513 LVT None 0 +00B514:00B514 LV None 0 +00B515:00B52F LVT None 0 +00B530:00B530 LV None 0 +00B531:00B54B LVT None 0 +00B54C:00B54C LV None 0 +00B54D:00B567 LVT None 0 +00B568:00B568 LV None 0 +00B569:00B583 LVT None 0 +00B584:00B584 LV None 0 +00B585:00B59F LVT None 0 +00B5A0:00B5A0 LV None 0 +00B5A1:00B5BB LVT None 0 +00B5BC:00B5BC LV None 0 +00B5BD:00B5D7 LVT None 0 +00B5D8:00B5D8 LV None 0 +00B5D9:00B5F3 LVT None 0 +00B5F4:00B5F4 LV None 0 +00B5F5:00B60F LVT None 0 +00B610:00B610 LV None 0 +00B611:00B62B LVT None 0 +00B62C:00B62C LV None 0 +00B62D:00B647 LVT None 0 +00B648:00B648 LV None 0 +00B649:00B663 LVT None 0 +00B664:00B664 LV None 0 +00B665:00B67F LVT None 0 +00B680:00B680 LV None 0 +00B681:00B69B LVT None 0 +00B69C:00B69C LV None 0 +00B69D:00B6B7 LVT None 0 +00B6B8:00B6B8 LV None 0 +00B6B9:00B6D3 LVT None 0 +00B6D4:00B6D4 LV None 0 +00B6D5:00B6EF LVT None 0 +00B6F0:00B6F0 LV None 0 +00B6F1:00B70B LVT None 0 +00B70C:00B70C LV None 0 +00B70D:00B727 LVT None 0 +00B728:00B728 LV None 0 +00B729:00B743 LVT None 0 +00B744:00B744 LV None 0 +00B745:00B75F LVT None 0 +00B760:00B760 LV None 0 +00B761:00B77B LVT None 0 +00B77C:00B77C LV None 0 +00B77D:00B797 LVT None 0 +00B798:00B798 LV None 0 +00B799:00B7B3 LVT None 0 +00B7B4:00B7B4 LV None 0 +00B7B5:00B7CF LVT None 0 +00B7D0:00B7D0 LV None 0 +00B7D1:00B7EB LVT None 0 +00B7EC:00B7EC LV None 0 +00B7ED:00B807 LVT None 0 +00B808:00B808 LV None 0 +00B809:00B823 LVT None 0 +00B824:00B824 LV None 0 +00B825:00B83F LVT None 0 +00B840:00B840 LV None 0 +00B841:00B85B LVT None 0 +00B85C:00B85C LV None 0 +00B85D:00B877 LVT None 0 +00B878:00B878 LV None 0 +00B879:00B893 LVT None 0 +00B894:00B894 LV None 0 +00B895:00B8AF LVT None 0 +00B8B0:00B8B0 LV None 0 +00B8B1:00B8CB LVT None 0 +00B8CC:00B8CC LV None 0 +00B8CD:00B8E7 LVT None 0 +00B8E8:00B8E8 LV None 0 +00B8E9:00B903 LVT None 0 +00B904:00B904 LV None 0 +00B905:00B91F LVT None 0 +00B920:00B920 LV None 0 +00B921:00B93B LVT None 0 +00B93C:00B93C LV None 0 +00B93D:00B957 LVT None 0 +00B958:00B958 LV None 0 +00B959:00B973 LVT None 0 +00B974:00B974 LV None 0 +00B975:00B98F LVT None 0 +00B990:00B990 LV None 0 +00B991:00B9AB LVT None 0 +00B9AC:00B9AC LV None 0 +00B9AD:00B9C7 LVT None 0 +00B9C8:00B9C8 LV None 0 +00B9C9:00B9E3 LVT None 0 +00B9E4:00B9E4 LV None 0 +00B9E5:00B9FF LVT None 0 +00BA00:00BA00 LV None 0 +00BA01:00BA1B LVT None 0 +00BA1C:00BA1C LV None 0 +00BA1D:00BA37 LVT None 0 +00BA38:00BA38 LV None 0 +00BA39:00BA53 LVT None 0 +00BA54:00BA54 LV None 0 +00BA55:00BA6F LVT None 0 +00BA70:00BA70 LV None 0 +00BA71:00BA8B LVT None 0 +00BA8C:00BA8C LV None 0 +00BA8D:00BAA7 LVT None 0 +00BAA8:00BAA8 LV None 0 +00BAA9:00BAC3 LVT None 0 +00BAC4:00BAC4 LV None 0 +00BAC5:00BADF LVT None 0 +00BAE0:00BAE0 LV None 0 +00BAE1:00BAFB LVT None 0 +00BAFC:00BAFC LV None 0 +00BAFD:00BB17 LVT None 0 +00BB18:00BB18 LV None 0 +00BB19:00BB33 LVT None 0 +00BB34:00BB34 LV None 0 +00BB35:00BB4F LVT None 0 +00BB50:00BB50 LV None 0 +00BB51:00BB6B LVT None 0 +00BB6C:00BB6C LV None 0 +00BB6D:00BB87 LVT None 0 +00BB88:00BB88 LV None 0 +00BB89:00BBA3 LVT None 0 +00BBA4:00BBA4 LV None 0 +00BBA5:00BBBF LVT None 0 +00BBC0:00BBC0 LV None 0 +00BBC1:00BBDB LVT None 0 +00BBDC:00BBDC LV None 0 +00BBDD:00BBF7 LVT None 0 +00BBF8:00BBF8 LV None 0 +00BBF9:00BC13 LVT None 0 +00BC14:00BC14 LV None 0 +00BC15:00BC2F LVT None 0 +00BC30:00BC30 LV None 0 +00BC31:00BC4B LVT None 0 +00BC4C:00BC4C LV None 0 +00BC4D:00BC67 LVT None 0 +00BC68:00BC68 LV None 0 +00BC69:00BC83 LVT None 0 +00BC84:00BC84 LV None 0 +00BC85:00BC9F LVT None 0 +00BCA0:00BCA0 LV None 0 +00BCA1:00BCBB LVT None 0 +00BCBC:00BCBC LV None 0 +00BCBD:00BCD7 LVT None 0 +00BCD8:00BCD8 LV None 0 +00BCD9:00BCF3 LVT None 0 +00BCF4:00BCF4 LV None 0 +00BCF5:00BD0F LVT None 0 +00BD10:00BD10 LV None 0 +00BD11:00BD2B LVT None 0 +00BD2C:00BD2C LV None 0 +00BD2D:00BD47 LVT None 0 +00BD48:00BD48 LV None 0 +00BD49:00BD63 LVT None 0 +00BD64:00BD64 LV None 0 +00BD65:00BD7F LVT None 0 +00BD80:00BD80 LV None 0 +00BD81:00BD9B LVT None 0 +00BD9C:00BD9C LV None 0 +00BD9D:00BDB7 LVT None 0 +00BDB8:00BDB8 LV None 0 +00BDB9:00BDD3 LVT None 0 +00BDD4:00BDD4 LV None 0 +00BDD5:00BDEF LVT None 0 +00BDF0:00BDF0 LV None 0 +00BDF1:00BE0B LVT None 0 +00BE0C:00BE0C LV None 0 +00BE0D:00BE27 LVT None 0 +00BE28:00BE28 LV None 0 +00BE29:00BE43 LVT None 0 +00BE44:00BE44 LV None 0 +00BE45:00BE5F LVT None 0 +00BE60:00BE60 LV None 0 +00BE61:00BE7B LVT None 0 +00BE7C:00BE7C LV None 0 +00BE7D:00BE97 LVT None 0 +00BE98:00BE98 LV None 0 +00BE99:00BEB3 LVT None 0 +00BEB4:00BEB4 LV None 0 +00BEB5:00BECF LVT None 0 +00BED0:00BED0 LV None 0 +00BED1:00BEEB LVT None 0 +00BEEC:00BEEC LV None 0 +00BEED:00BF07 LVT None 0 +00BF08:00BF08 LV None 0 +00BF09:00BF23 LVT None 0 +00BF24:00BF24 LV None 0 +00BF25:00BF3F LVT None 0 +00BF40:00BF40 LV None 0 +00BF41:00BF5B LVT None 0 +00BF5C:00BF5C LV None 0 +00BF5D:00BF77 LVT None 0 +00BF78:00BF78 LV None 0 +00BF79:00BF93 LVT None 0 +00BF94:00BF94 LV None 0 +00BF95:00BFAF LVT None 0 +00BFB0:00BFB0 LV None 0 +00BFB1:00BFCB LVT None 0 +00BFCC:00BFCC LV None 0 +00BFCD:00BFE7 LVT None 0 +00BFE8:00BFE8 LV None 0 +00BFE9:00C003 LVT None 0 +00C004:00C004 LV None 0 +00C005:00C01F LVT None 0 +00C020:00C020 LV None 0 +00C021:00C03B LVT None 0 +00C03C:00C03C LV None 0 +00C03D:00C057 LVT None 0 +00C058:00C058 LV None 0 +00C059:00C073 LVT None 0 +00C074:00C074 LV None 0 +00C075:00C08F LVT None 0 +00C090:00C090 LV None 0 +00C091:00C0AB LVT None 0 +00C0AC:00C0AC LV None 0 +00C0AD:00C0C7 LVT None 0 +00C0C8:00C0C8 LV None 0 +00C0C9:00C0E3 LVT None 0 +00C0E4:00C0E4 LV None 0 +00C0E5:00C0FF LVT None 0 +00C100:00C100 LV None 0 +00C101:00C11B LVT None 0 +00C11C:00C11C LV None 0 +00C11D:00C137 LVT None 0 +00C138:00C138 LV None 0 +00C139:00C153 LVT None 0 +00C154:00C154 LV None 0 +00C155:00C16F LVT None 0 +00C170:00C170 LV None 0 +00C171:00C18B LVT None 0 +00C18C:00C18C LV None 0 +00C18D:00C1A7 LVT None 0 +00C1A8:00C1A8 LV None 0 +00C1A9:00C1C3 LVT None 0 +00C1C4:00C1C4 LV None 0 +00C1C5:00C1DF LVT None 0 +00C1E0:00C1E0 LV None 0 +00C1E1:00C1FB LVT None 0 +00C1FC:00C1FC LV None 0 +00C1FD:00C217 LVT None 0 +00C218:00C218 LV None 0 +00C219:00C233 LVT None 0 +00C234:00C234 LV None 0 +00C235:00C24F LVT None 0 +00C250:00C250 LV None 0 +00C251:00C26B LVT None 0 +00C26C:00C26C LV None 0 +00C26D:00C287 LVT None 0 +00C288:00C288 LV None 0 +00C289:00C2A3 LVT None 0 +00C2A4:00C2A4 LV None 0 +00C2A5:00C2BF LVT None 0 +00C2C0:00C2C0 LV None 0 +00C2C1:00C2DB LVT None 0 +00C2DC:00C2DC LV None 0 +00C2DD:00C2F7 LVT None 0 +00C2F8:00C2F8 LV None 0 +00C2F9:00C313 LVT None 0 +00C314:00C314 LV None 0 +00C315:00C32F LVT None 0 +00C330:00C330 LV None 0 +00C331:00C34B LVT None 0 +00C34C:00C34C LV None 0 +00C34D:00C367 LVT None 0 +00C368:00C368 LV None 0 +00C369:00C383 LVT None 0 +00C384:00C384 LV None 0 +00C385:00C39F LVT None 0 +00C3A0:00C3A0 LV None 0 +00C3A1:00C3BB LVT None 0 +00C3BC:00C3BC LV None 0 +00C3BD:00C3D7 LVT None 0 +00C3D8:00C3D8 LV None 0 +00C3D9:00C3F3 LVT None 0 +00C3F4:00C3F4 LV None 0 +00C3F5:00C40F LVT None 0 +00C410:00C410 LV None 0 +00C411:00C42B LVT None 0 +00C42C:00C42C LV None 0 +00C42D:00C447 LVT None 0 +00C448:00C448 LV None 0 +00C449:00C463 LVT None 0 +00C464:00C464 LV None 0 +00C465:00C47F LVT None 0 +00C480:00C480 LV None 0 +00C481:00C49B LVT None 0 +00C49C:00C49C LV None 0 +00C49D:00C4B7 LVT None 0 +00C4B8:00C4B8 LV None 0 +00C4B9:00C4D3 LVT None 0 +00C4D4:00C4D4 LV None 0 +00C4D5:00C4EF LVT None 0 +00C4F0:00C4F0 LV None 0 +00C4F1:00C50B LVT None 0 +00C50C:00C50C LV None 0 +00C50D:00C527 LVT None 0 +00C528:00C528 LV None 0 +00C529:00C543 LVT None 0 +00C544:00C544 LV None 0 +00C545:00C55F LVT None 0 +00C560:00C560 LV None 0 +00C561:00C57B LVT None 0 +00C57C:00C57C LV None 0 +00C57D:00C597 LVT None 0 +00C598:00C598 LV None 0 +00C599:00C5B3 LVT None 0 +00C5B4:00C5B4 LV None 0 +00C5B5:00C5CF LVT None 0 +00C5D0:00C5D0 LV None 0 +00C5D1:00C5EB LVT None 0 +00C5EC:00C5EC LV None 0 +00C5ED:00C607 LVT None 0 +00C608:00C608 LV None 0 +00C609:00C623 LVT None 0 +00C624:00C624 LV None 0 +00C625:00C63F LVT None 0 +00C640:00C640 LV None 0 +00C641:00C65B LVT None 0 +00C65C:00C65C LV None 0 +00C65D:00C677 LVT None 0 +00C678:00C678 LV None 0 +00C679:00C693 LVT None 0 +00C694:00C694 LV None 0 +00C695:00C6AF LVT None 0 +00C6B0:00C6B0 LV None 0 +00C6B1:00C6CB LVT None 0 +00C6CC:00C6CC LV None 0 +00C6CD:00C6E7 LVT None 0 +00C6E8:00C6E8 LV None 0 +00C6E9:00C703 LVT None 0 +00C704:00C704 LV None 0 +00C705:00C71F LVT None 0 +00C720:00C720 LV None 0 +00C721:00C73B LVT None 0 +00C73C:00C73C LV None 0 +00C73D:00C757 LVT None 0 +00C758:00C758 LV None 0 +00C759:00C773 LVT None 0 +00C774:00C774 LV None 0 +00C775:00C78F LVT None 0 +00C790:00C790 LV None 0 +00C791:00C7AB LVT None 0 +00C7AC:00C7AC LV None 0 +00C7AD:00C7C7 LVT None 0 +00C7C8:00C7C8 LV None 0 +00C7C9:00C7E3 LVT None 0 +00C7E4:00C7E4 LV None 0 +00C7E5:00C7FF LVT None 0 +00C800:00C800 LV None 0 +00C801:00C81B LVT None 0 +00C81C:00C81C LV None 0 +00C81D:00C837 LVT None 0 +00C838:00C838 LV None 0 +00C839:00C853 LVT None 0 +00C854:00C854 LV None 0 +00C855:00C86F LVT None 0 +00C870:00C870 LV None 0 +00C871:00C88B LVT None 0 +00C88C:00C88C LV None 0 +00C88D:00C8A7 LVT None 0 +00C8A8:00C8A8 LV None 0 +00C8A9:00C8C3 LVT None 0 +00C8C4:00C8C4 LV None 0 +00C8C5:00C8DF LVT None 0 +00C8E0:00C8E0 LV None 0 +00C8E1:00C8FB LVT None 0 +00C8FC:00C8FC LV None 0 +00C8FD:00C917 LVT None 0 +00C918:00C918 LV None 0 +00C919:00C933 LVT None 0 +00C934:00C934 LV None 0 +00C935:00C94F LVT None 0 +00C950:00C950 LV None 0 +00C951:00C96B LVT None 0 +00C96C:00C96C LV None 0 +00C96D:00C987 LVT None 0 +00C988:00C988 LV None 0 +00C989:00C9A3 LVT None 0 +00C9A4:00C9A4 LV None 0 +00C9A5:00C9BF LVT None 0 +00C9C0:00C9C0 LV None 0 +00C9C1:00C9DB LVT None 0 +00C9DC:00C9DC LV None 0 +00C9DD:00C9F7 LVT None 0 +00C9F8:00C9F8 LV None 0 +00C9F9:00CA13 LVT None 0 +00CA14:00CA14 LV None 0 +00CA15:00CA2F LVT None 0 +00CA30:00CA30 LV None 0 +00CA31:00CA4B LVT None 0 +00CA4C:00CA4C LV None 0 +00CA4D:00CA67 LVT None 0 +00CA68:00CA68 LV None 0 +00CA69:00CA83 LVT None 0 +00CA84:00CA84 LV None 0 +00CA85:00CA9F LVT None 0 +00CAA0:00CAA0 LV None 0 +00CAA1:00CABB LVT None 0 +00CABC:00CABC LV None 0 +00CABD:00CAD7 LVT None 0 +00CAD8:00CAD8 LV None 0 +00CAD9:00CAF3 LVT None 0 +00CAF4:00CAF4 LV None 0 +00CAF5:00CB0F LVT None 0 +00CB10:00CB10 LV None 0 +00CB11:00CB2B LVT None 0 +00CB2C:00CB2C LV None 0 +00CB2D:00CB47 LVT None 0 +00CB48:00CB48 LV None 0 +00CB49:00CB63 LVT None 0 +00CB64:00CB64 LV None 0 +00CB65:00CB7F LVT None 0 +00CB80:00CB80 LV None 0 +00CB81:00CB9B LVT None 0 +00CB9C:00CB9C LV None 0 +00CB9D:00CBB7 LVT None 0 +00CBB8:00CBB8 LV None 0 +00CBB9:00CBD3 LVT None 0 +00CBD4:00CBD4 LV None 0 +00CBD5:00CBEF LVT None 0 +00CBF0:00CBF0 LV None 0 +00CBF1:00CC0B LVT None 0 +00CC0C:00CC0C LV None 0 +00CC0D:00CC27 LVT None 0 +00CC28:00CC28 LV None 0 +00CC29:00CC43 LVT None 0 +00CC44:00CC44 LV None 0 +00CC45:00CC5F LVT None 0 +00CC60:00CC60 LV None 0 +00CC61:00CC7B LVT None 0 +00CC7C:00CC7C LV None 0 +00CC7D:00CC97 LVT None 0 +00CC98:00CC98 LV None 0 +00CC99:00CCB3 LVT None 0 +00CCB4:00CCB4 LV None 0 +00CCB5:00CCCF LVT None 0 +00CCD0:00CCD0 LV None 0 +00CCD1:00CCEB LVT None 0 +00CCEC:00CCEC LV None 0 +00CCED:00CD07 LVT None 0 +00CD08:00CD08 LV None 0 +00CD09:00CD23 LVT None 0 +00CD24:00CD24 LV None 0 +00CD25:00CD3F LVT None 0 +00CD40:00CD40 LV None 0 +00CD41:00CD5B LVT None 0 +00CD5C:00CD5C LV None 0 +00CD5D:00CD77 LVT None 0 +00CD78:00CD78 LV None 0 +00CD79:00CD93 LVT None 0 +00CD94:00CD94 LV None 0 +00CD95:00CDAF LVT None 0 +00CDB0:00CDB0 LV None 0 +00CDB1:00CDCB LVT None 0 +00CDCC:00CDCC LV None 0 +00CDCD:00CDE7 LVT None 0 +00CDE8:00CDE8 LV None 0 +00CDE9:00CE03 LVT None 0 +00CE04:00CE04 LV None 0 +00CE05:00CE1F LVT None 0 +00CE20:00CE20 LV None 0 +00CE21:00CE3B LVT None 0 +00CE3C:00CE3C LV None 0 +00CE3D:00CE57 LVT None 0 +00CE58:00CE58 LV None 0 +00CE59:00CE73 LVT None 0 +00CE74:00CE74 LV None 0 +00CE75:00CE8F LVT None 0 +00CE90:00CE90 LV None 0 +00CE91:00CEAB LVT None 0 +00CEAC:00CEAC LV None 0 +00CEAD:00CEC7 LVT None 0 +00CEC8:00CEC8 LV None 0 +00CEC9:00CEE3 LVT None 0 +00CEE4:00CEE4 LV None 0 +00CEE5:00CEFF LVT None 0 +00CF00:00CF00 LV None 0 +00CF01:00CF1B LVT None 0 +00CF1C:00CF1C LV None 0 +00CF1D:00CF37 LVT None 0 +00CF38:00CF38 LV None 0 +00CF39:00CF53 LVT None 0 +00CF54:00CF54 LV None 0 +00CF55:00CF6F LVT None 0 +00CF70:00CF70 LV None 0 +00CF71:00CF8B LVT None 0 +00CF8C:00CF8C LV None 0 +00CF8D:00CFA7 LVT None 0 +00CFA8:00CFA8 LV None 0 +00CFA9:00CFC3 LVT None 0 +00CFC4:00CFC4 LV None 0 +00CFC5:00CFDF LVT None 0 +00CFE0:00CFE0 LV None 0 +00CFE1:00CFFB LVT None 0 +00CFFC:00CFFC LV None 0 +00CFFD:00D017 LVT None 0 +00D018:00D018 LV None 0 +00D019:00D033 LVT None 0 +00D034:00D034 LV None 0 +00D035:00D04F LVT None 0 +00D050:00D050 LV None 0 +00D051:00D06B LVT None 0 +00D06C:00D06C LV None 0 +00D06D:00D087 LVT None 0 +00D088:00D088 LV None 0 +00D089:00D0A3 LVT None 0 +00D0A4:00D0A4 LV None 0 +00D0A5:00D0BF LVT None 0 +00D0C0:00D0C0 LV None 0 +00D0C1:00D0DB LVT None 0 +00D0DC:00D0DC LV None 0 +00D0DD:00D0F7 LVT None 0 +00D0F8:00D0F8 LV None 0 +00D0F9:00D113 LVT None 0 +00D114:00D114 LV None 0 +00D115:00D12F LVT None 0 +00D130:00D130 LV None 0 +00D131:00D14B LVT None 0 +00D14C:00D14C LV None 0 +00D14D:00D167 LVT None 0 +00D168:00D168 LV None 0 +00D169:00D183 LVT None 0 +00D184:00D184 LV None 0 +00D185:00D19F LVT None 0 +00D1A0:00D1A0 LV None 0 +00D1A1:00D1BB LVT None 0 +00D1BC:00D1BC LV None 0 +00D1BD:00D1D7 LVT None 0 +00D1D8:00D1D8 LV None 0 +00D1D9:00D1F3 LVT None 0 +00D1F4:00D1F4 LV None 0 +00D1F5:00D20F LVT None 0 +00D210:00D210 LV None 0 +00D211:00D22B LVT None 0 +00D22C:00D22C LV None 0 +00D22D:00D247 LVT None 0 +00D248:00D248 LV None 0 +00D249:00D263 LVT None 0 +00D264:00D264 LV None 0 +00D265:00D27F LVT None 0 +00D280:00D280 LV None 0 +00D281:00D29B LVT None 0 +00D29C:00D29C LV None 0 +00D29D:00D2B7 LVT None 0 +00D2B8:00D2B8 LV None 0 +00D2B9:00D2D3 LVT None 0 +00D2D4:00D2D4 LV None 0 +00D2D5:00D2EF LVT None 0 +00D2F0:00D2F0 LV None 0 +00D2F1:00D30B LVT None 0 +00D30C:00D30C LV None 0 +00D30D:00D327 LVT None 0 +00D328:00D328 LV None 0 +00D329:00D343 LVT None 0 +00D344:00D344 LV None 0 +00D345:00D35F LVT None 0 +00D360:00D360 LV None 0 +00D361:00D37B LVT None 0 +00D37C:00D37C LV None 0 +00D37D:00D397 LVT None 0 +00D398:00D398 LV None 0 +00D399:00D3B3 LVT None 0 +00D3B4:00D3B4 LV None 0 +00D3B5:00D3CF LVT None 0 +00D3D0:00D3D0 LV None 0 +00D3D1:00D3EB LVT None 0 +00D3EC:00D3EC LV None 0 +00D3ED:00D407 LVT None 0 +00D408:00D408 LV None 0 +00D409:00D423 LVT None 0 +00D424:00D424 LV None 0 +00D425:00D43F LVT None 0 +00D440:00D440 LV None 0 +00D441:00D45B LVT None 0 +00D45C:00D45C LV None 0 +00D45D:00D477 LVT None 0 +00D478:00D478 LV None 0 +00D479:00D493 LVT None 0 +00D494:00D494 LV None 0 +00D495:00D4AF LVT None 0 +00D4B0:00D4B0 LV None 0 +00D4B1:00D4CB LVT None 0 +00D4CC:00D4CC LV None 0 +00D4CD:00D4E7 LVT None 0 +00D4E8:00D4E8 LV None 0 +00D4E9:00D503 LVT None 0 +00D504:00D504 LV None 0 +00D505:00D51F LVT None 0 +00D520:00D520 LV None 0 +00D521:00D53B LVT None 0 +00D53C:00D53C LV None 0 +00D53D:00D557 LVT None 0 +00D558:00D558 LV None 0 +00D559:00D573 LVT None 0 +00D574:00D574 LV None 0 +00D575:00D58F LVT None 0 +00D590:00D590 LV None 0 +00D591:00D5AB LVT None 0 +00D5AC:00D5AC LV None 0 +00D5AD:00D5C7 LVT None 0 +00D5C8:00D5C8 LV None 0 +00D5C9:00D5E3 LVT None 0 +00D5E4:00D5E4 LV None 0 +00D5E5:00D5FF LVT None 0 +00D600:00D600 LV None 0 +00D601:00D61B LVT None 0 +00D61C:00D61C LV None 0 +00D61D:00D637 LVT None 0 +00D638:00D638 LV None 0 +00D639:00D653 LVT None 0 +00D654:00D654 LV None 0 +00D655:00D66F LVT None 0 +00D670:00D670 LV None 0 +00D671:00D68B LVT None 0 +00D68C:00D68C LV None 0 +00D68D:00D6A7 LVT None 0 +00D6A8:00D6A8 LV None 0 +00D6A9:00D6C3 LVT None 0 +00D6C4:00D6C4 LV None 0 +00D6C5:00D6DF LVT None 0 +00D6E0:00D6E0 LV None 0 +00D6E1:00D6FB LVT None 0 +00D6FC:00D6FC LV None 0 +00D6FD:00D717 LVT None 0 +00D718:00D718 LV None 0 +00D719:00D733 LVT None 0 +00D734:00D734 LV None 0 +00D735:00D74F LVT None 0 +00D750:00D750 LV None 0 +00D751:00D76B LVT None 0 +00D76C:00D76C LV None 0 +00D76D:00D787 LVT None 0 +00D788:00D788 LV None 0 +00D789:00D7A3 LVT None 0 +00D7A4:00D7AF Other None 0 +00D7B0:00D7C6 V None 0 +00D7C7:00D7CA Other None 0 +00D7CB:00D7FB T None 0 +00D7FC:00FB1D Other None 0 +00FB1E:00FB1E Extend Extend 0 +00FB1F:00FDFF Other None 0 +00FE00:00FE0F Extend Extend 0 +00FE10:00FE1F Other None 0 +00FE20:00FE2F Extend Extend 0 +00FE30:00FEFE Other None 0 +00FEFF:00FEFF Control None 0 +00FF00:00FF9D Other None 0 +00FF9E:00FF9F Extend Extend 0 +00FFA0:00FFEF Other None 0 +00FFF0:00FFFB Control None 0 +00FFFC:0101FC Other None 0 +0101FD:0101FD Extend Extend 0 +0101FE:0102DF Other None 0 +0102E0:0102E0 Extend Extend 0 +0102E1:010375 Other None 0 +010376:01037A Extend Extend 0 +01037B:0109FF Other None 0 +010A00:010A00 Other Consonant 0 +010A01:010A03 Extend Extend 0 +010A04:010A04 Other None 0 +010A05:010A06 Extend Extend 0 +010A07:010A0B Other None 0 +010A0C:010A0F Extend Extend 0 +010A10:010A13 Other Consonant 0 +010A14:010A14 Other None 0 +010A15:010A17 Other Consonant 0 +010A18:010A18 Other None 0 +010A19:010A35 Other Consonant 0 +010A36:010A37 Other None 0 +010A38:010A3A Extend Extend 0 +010A3B:010A3E Other None 0 +010A3F:010A3F Extend Linker 0 +010A40:010AE4 Other None 0 +010AE5:010AE6 Extend Extend 0 +010AE7:010D23 Other None 0 +010D24:010D27 Extend Extend 0 +010D28:010D68 Other None 0 +010D69:010D6D Extend Extend 0 +010D6E:010EAA Other None 0 +010EAB:010EAC Extend Extend 0 +010EAD:010EF9 Other None 0 +010EFA:010EFF Extend Extend 0 +010F00:010F45 Other None 0 +010F46:010F50 Extend Extend 0 +010F51:010F81 Other None 0 +010F82:010F85 Extend Extend 0 +010F86:010FFF Other None 0 +011000:011000 SpacingMark None 0 +011001:011001 Extend Extend 0 +011002:011002 SpacingMark None 0 +011003:011037 Other None 0 +011038:011046 Extend Extend 0 +011047:01106F Other None 0 +011070:011070 Extend Extend 0 +011071:011072 Other None 0 +011073:011074 Extend Extend 0 +011075:01107E Other None 0 +01107F:011081 Extend Extend 0 +011082:011082 SpacingMark None 0 +011083:0110AF Other None 0 +0110B0:0110B2 SpacingMark None 0 +0110B3:0110B6 Extend Extend 0 +0110B7:0110B8 SpacingMark None 0 +0110B9:0110BA Extend Extend 0 +0110BB:0110BC Other None 0 +0110BD:0110BD Prepend None 0 +0110BE:0110C1 Other None 0 +0110C2:0110C2 Extend Extend 0 +0110C3:0110CC Other None 0 +0110CD:0110CD Prepend None 0 +0110CE:0110FF Other None 0 +011100:011102 Extend Extend 0 +011103:011126 Other Consonant 0 +011127:01112B Extend Extend 0 +01112C:01112C SpacingMark None 0 +01112D:011132 Extend Extend 0 +011133:011133 Extend Linker 0 +011134:011134 Extend Extend 0 +011135:011143 Other None 0 +011144:011144 Other Consonant 0 +011145:011146 SpacingMark None 0 +011147:011147 Other Consonant 0 +011148:011172 Other None 0 +011173:011173 Extend Extend 0 +011174:01117F Other None 0 +011180:011181 Extend Extend 0 +011182:011182 SpacingMark None 0 +011183:0111B2 Other None 0 +0111B3:0111B5 SpacingMark None 0 +0111B6:0111BE Extend Extend 0 +0111BF:0111BF SpacingMark None 0 +0111C0:0111C0 Extend Extend 0 +0111C1:0111C1 Other None 0 +0111C2:0111C3 Prepend None 0 +0111C4:0111C8 Other None 0 +0111C9:0111CC Extend Extend 0 +0111CD:0111CD Other None 0 +0111CE:0111CE SpacingMark None 0 +0111CF:0111CF Extend Extend 0 +0111D0:01122B Other None 0 +01122C:01122E SpacingMark None 0 +01122F:011231 Extend Extend 0 +011232:011233 SpacingMark None 0 +011234:011237 Extend Extend 0 +011238:01123D Other None 0 +01123E:01123E Extend Extend 0 +01123F:011240 Other None 0 +011241:011241 Extend Extend 0 +011242:0112DE Other None 0 +0112DF:0112DF Extend Extend 0 +0112E0:0112E2 SpacingMark None 0 +0112E3:0112EA Extend Extend 0 +0112EB:0112FF Other None 0 +011300:011301 Extend Extend 0 +011302:011303 SpacingMark None 0 +011304:01133A Other None 0 +01133B:01133C Extend Extend 0 +01133D:01133D Other None 0 +01133E:01133E Extend Extend 0 +01133F:01133F SpacingMark None 0 +011340:011340 Extend Extend 0 +011341:011344 SpacingMark None 0 +011345:011346 Other None 0 +011347:011348 SpacingMark None 0 +011349:01134A Other None 0 +01134B:01134C SpacingMark None 0 +01134D:01134D Extend Extend 0 +01134E:011356 Other None 0 +011357:011357 Extend Extend 0 +011358:011361 Other None 0 +011362:011363 SpacingMark None 0 +011364:011365 Other None 0 +011366:01136C Extend Extend 0 +01136D:01136F Other None 0 +011370:011374 Extend Extend 0 +011375:01137F Other None 0 +011380:011389 Other Consonant 0 +01138A:01138A Other None 0 +01138B:01138B Other Consonant 0 +01138C:01138D Other None 0 +01138E:01138E Other Consonant 0 +01138F:01138F Other None 0 +011390:0113B5 Other Consonant 0 +0113B6:0113B7 Other None 0 +0113B8:0113B8 Extend Extend 0 +0113B9:0113BA SpacingMark None 0 +0113BB:0113C0 Extend Extend 0 +0113C1:0113C1 Other None 0 +0113C2:0113C2 Extend Extend 0 +0113C3:0113C4 Other None 0 +0113C5:0113C5 Extend Extend 0 +0113C6:0113C6 Other None 0 +0113C7:0113C9 Extend Extend 0 +0113CA:0113CA SpacingMark None 0 +0113CB:0113CB Other None 0 +0113CC:0113CD SpacingMark None 0 +0113CE:0113CF Extend Extend 0 +0113D0:0113D0 Extend Linker 0 +0113D1:0113D1 Prepend None 0 +0113D2:0113D2 Extend Extend 0 +0113D3:0113E0 Other None 0 +0113E1:0113E2 Extend Extend 0 +0113E3:011434 Other None 0 +011435:011437 SpacingMark None 0 +011438:01143F Extend Extend 0 +011440:011441 SpacingMark None 0 +011442:011444 Extend Extend 0 +011445:011445 SpacingMark None 0 +011446:011446 Extend Extend 0 +011447:01145D Other None 0 +01145E:01145E Extend Extend 0 +01145F:0114AF Other None 0 +0114B0:0114B0 Extend Extend 0 +0114B1:0114B2 SpacingMark None 0 +0114B3:0114B8 Extend Extend 0 +0114B9:0114B9 SpacingMark None 0 +0114BA:0114BA Extend Extend 0 +0114BB:0114BC SpacingMark None 0 +0114BD:0114BD Extend Extend 0 +0114BE:0114BE SpacingMark None 0 +0114BF:0114C0 Extend Extend 0 +0114C1:0114C1 SpacingMark None 0 +0114C2:0114C3 Extend Extend 0 +0114C4:0115AE Other None 0 +0115AF:0115AF Extend Extend 0 +0115B0:0115B1 SpacingMark None 0 +0115B2:0115B5 Extend Extend 0 +0115B6:0115B7 Other None 0 +0115B8:0115BB SpacingMark None 0 +0115BC:0115BD Extend Extend 0 +0115BE:0115BE SpacingMark None 0 +0115BF:0115C0 Extend Extend 0 +0115C1:0115DB Other None 0 +0115DC:0115DD Extend Extend 0 +0115DE:01162F Other None 0 +011630:011632 SpacingMark None 0 +011633:01163A Extend Extend 0 +01163B:01163C SpacingMark None 0 +01163D:01163D Extend Extend 0 +01163E:01163E SpacingMark None 0 +01163F:011640 Extend Extend 0 +011641:0116AA Other None 0 +0116AB:0116AB Extend Extend 0 +0116AC:0116AC SpacingMark None 0 +0116AD:0116AD Extend Extend 0 +0116AE:0116AF SpacingMark None 0 +0116B0:0116B7 Extend Extend 0 +0116B8:01171C Other None 0 +01171D:01171D Extend Extend 0 +01171E:01171E SpacingMark None 0 +01171F:01171F Extend Extend 0 +011720:011721 Other None 0 +011722:011725 Extend Extend 0 +011726:011726 SpacingMark None 0 +011727:01172B Extend Extend 0 +01172C:01182B Other None 0 +01182C:01182E SpacingMark None 0 +01182F:011837 Extend Extend 0 +011838:011838 SpacingMark None 0 +011839:01183A Extend Extend 0 +01183B:0118FF Other None 0 +011900:011906 Other Consonant 0 +011907:011908 Other None 0 +011909:011909 Other Consonant 0 +01190A:01190B Other None 0 +01190C:011913 Other Consonant 0 +011914:011914 Other None 0 +011915:011916 Other Consonant 0 +011917:011917 Other None 0 +011918:01192F Other Consonant 0 +011930:011930 Extend Extend 0 +011931:011935 SpacingMark None 0 +011936:011936 Other None 0 +011937:011938 SpacingMark None 0 +011939:01193A Other None 0 +01193B:01193D Extend Extend 0 +01193E:01193E Extend Linker 0 +01193F:01193F Prepend None 0 +011940:011940 SpacingMark None 0 +011941:011941 Prepend None 0 +011942:011942 SpacingMark None 0 +011943:011943 Extend Extend 0 +011944:0119D0 Other None 0 +0119D1:0119D3 SpacingMark None 0 +0119D4:0119D7 Extend Extend 0 +0119D8:0119D9 Other None 0 +0119DA:0119DB Extend Extend 0 +0119DC:0119DF SpacingMark None 0 +0119E0:0119E0 Extend Extend 0 +0119E1:0119E3 Other None 0 +0119E4:0119E4 SpacingMark None 0 +0119E5:0119FF Other None 0 +011A00:011A00 Other Consonant 0 +011A01:011A0A Extend Extend 0 +011A0B:011A32 Other Consonant 0 +011A33:011A38 Extend Extend 0 +011A39:011A39 SpacingMark None 0 +011A3A:011A3A Other None 0 +011A3B:011A3E Extend Extend 0 +011A3F:011A46 Other None 0 +011A47:011A47 Extend Linker 0 +011A48:011A4F Other None 0 +011A50:011A50 Other Consonant 0 +011A51:011A56 Extend Extend 0 +011A57:011A58 SpacingMark None 0 +011A59:011A5B Extend Extend 0 +011A5C:011A83 Other Consonant 0 +011A84:011A89 Prepend None 0 +011A8A:011A96 Extend Extend 0 +011A97:011A97 SpacingMark None 0 +011A98:011A98 Extend Extend 0 +011A99:011A99 Extend Linker 0 +011A9A:011B5F Other None 0 +011B60:011B60 Extend Extend 0 +011B61:011B61 SpacingMark None 0 +011B62:011B64 Extend Extend 0 +011B65:011B65 SpacingMark None 0 +011B66:011B66 Extend Extend 0 +011B67:011B67 SpacingMark None 0 +011B68:011C2E Other None 0 +011C2F:011C2F SpacingMark None 0 +011C30:011C36 Extend Extend 0 +011C37:011C37 Other None 0 +011C38:011C3D Extend Extend 0 +011C3E:011C3E SpacingMark None 0 +011C3F:011C3F Extend Extend 0 +011C40:011C91 Other None 0 +011C92:011CA7 Extend Extend 0 +011CA8:011CA8 Other None 0 +011CA9:011CA9 SpacingMark None 0 +011CAA:011CB0 Extend Extend 0 +011CB1:011CB1 SpacingMark None 0 +011CB2:011CB3 Extend Extend 0 +011CB4:011CB4 SpacingMark None 0 +011CB5:011CB6 Extend Extend 0 +011CB7:011D30 Other None 0 +011D31:011D36 Extend Extend 0 +011D37:011D39 Other None 0 +011D3A:011D3A Extend Extend 0 +011D3B:011D3B Other None 0 +011D3C:011D3D Extend Extend 0 +011D3E:011D3E Other None 0 +011D3F:011D45 Extend Extend 0 +011D46:011D46 Prepend None 0 +011D47:011D47 Extend Extend 0 +011D48:011D89 Other None 0 +011D8A:011D8E SpacingMark None 0 +011D8F:011D8F Other None 0 +011D90:011D91 Extend Extend 0 +011D92:011D92 Other None 0 +011D93:011D94 SpacingMark None 0 +011D95:011D95 Extend Extend 0 +011D96:011D96 SpacingMark None 0 +011D97:011D97 Extend Extend 0 +011D98:011EF2 Other None 0 +011EF3:011EF4 Extend Extend 0 +011EF5:011EF6 SpacingMark None 0 +011EF7:011EFF Other None 0 +011F00:011F01 Extend Extend 0 +011F02:011F02 Prepend None 0 +011F03:011F03 SpacingMark None 0 +011F04:011F10 Other Consonant 0 +011F11:011F11 Other None 0 +011F12:011F33 Other Consonant 0 +011F34:011F35 SpacingMark None 0 +011F36:011F3A Extend Extend 0 +011F3B:011F3D Other None 0 +011F3E:011F3F SpacingMark None 0 +011F40:011F41 Extend Extend 0 +011F42:011F42 Extend Linker 0 +011F43:011F59 Other None 0 +011F5A:011F5A Extend Extend 0 +011F5B:01342F Other None 0 +013430:01343F Control None 0 +013440:013440 Extend Extend 0 +013441:013446 Other None 0 +013447:013455 Extend Extend 0 +013456:01611D Other None 0 +01611E:016129 Extend Extend 0 +01612A:01612C SpacingMark None 0 +01612D:01612F Extend Extend 0 +016130:016AEF Other None 0 +016AF0:016AF4 Extend Extend 0 +016AF5:016B2F Other None 0 +016B30:016B36 Extend Extend 0 +016B37:016D62 Other None 0 +016D63:016D63 V None 0 +016D64:016D66 Other None 0 +016D67:016D6A V None 0 +016D6B:016F4E Other None 0 +016F4F:016F4F Extend Extend 0 +016F50:016F50 Other None 0 +016F51:016F87 SpacingMark None 0 +016F88:016F8E Other None 0 +016F8F:016F92 Extend Extend 0 +016F93:016FE3 Other None 0 +016FE4:016FE4 Extend Extend 0 +016FE5:016FEF Other None 0 +016FF0:016FF1 Extend Extend 0 +016FF2:01BC9C Other None 0 +01BC9D:01BC9E Extend Extend 0 +01BC9F:01BC9F Other None 0 +01BCA0:01BCA3 Control None 0 +01BCA4:01CEFF Other None 0 +01CF00:01CF2D Extend Extend 0 +01CF2E:01CF2F Other None 0 +01CF30:01CF46 Extend Extend 0 +01CF47:01D164 Other None 0 +01D165:01D169 Extend Extend 0 +01D16A:01D16C Other None 0 +01D16D:01D172 Extend Extend 0 +01D173:01D17A Control None 0 +01D17B:01D182 Extend Extend 0 +01D183:01D184 Other None 0 +01D185:01D18B Extend Extend 0 +01D18C:01D1A9 Other None 0 +01D1AA:01D1AD Extend Extend 0 +01D1AE:01D241 Other None 0 +01D242:01D244 Extend Extend 0 +01D245:01D9FF Other None 0 +01DA00:01DA36 Extend Extend 0 +01DA37:01DA3A Other None 0 +01DA3B:01DA6C Extend Extend 0 +01DA6D:01DA74 Other None 0 +01DA75:01DA75 Extend Extend 0 +01DA76:01DA83 Other None 0 +01DA84:01DA84 Extend Extend 0 +01DA85:01DA9A Other None 0 +01DA9B:01DA9F Extend Extend 0 +01DAA0:01DAA0 Other None 0 +01DAA1:01DAAF Extend Extend 0 +01DAB0:01DFFF Other None 0 +01E000:01E006 Extend Extend 0 +01E007:01E007 Other None 0 +01E008:01E018 Extend Extend 0 +01E019:01E01A Other None 0 +01E01B:01E021 Extend Extend 0 +01E022:01E022 Other None 0 +01E023:01E024 Extend Extend 0 +01E025:01E025 Other None 0 +01E026:01E02A Extend Extend 0 +01E02B:01E08E Other None 0 +01E08F:01E08F Extend Extend 0 +01E090:01E12F Other None 0 +01E130:01E136 Extend Extend 0 +01E137:01E2AD Other None 0 +01E2AE:01E2AE Extend Extend 0 +01E2AF:01E2EB Other None 0 +01E2EC:01E2EF Extend Extend 0 +01E2F0:01E4EB Other None 0 +01E4EC:01E4EF Extend Extend 0 +01E4F0:01E5ED Other None 0 +01E5EE:01E5EF Extend Extend 0 +01E5F0:01E6E2 Other None 0 +01E6E3:01E6E3 Extend Extend 0 +01E6E4:01E6E5 Other None 0 +01E6E6:01E6E6 Extend Extend 0 +01E6E7:01E6ED Other None 0 +01E6EE:01E6EF Extend Extend 0 +01E6F0:01E6F4 Other None 0 +01E6F5:01E6F5 Extend Extend 0 +01E6F6:01E8CF Other None 0 +01E8D0:01E8D6 Extend Extend 0 +01E8D7:01E943 Other None 0 +01E944:01E94A Extend Extend 0 +01E94B:01F003 Other None 0 +01F004:01F004 Other None 1 +01F005:01F02B Other None 0 +01F02C:01F02F Other None 1 +01F030:01F093 Other None 0 +01F094:01F09F Other None 1 +01F0A0:01F0AE Other None 0 +01F0AF:01F0B0 Other None 1 +01F0B1:01F0BF Other None 0 +01F0C0:01F0C0 Other None 1 +01F0C1:01F0CE Other None 0 +01F0CF:01F0D0 Other None 1 +01F0D1:01F0F5 Other None 0 +01F0F6:01F0FF Other None 1 +01F100:01F16F Other None 0 +01F170:01F171 Other None 1 +01F172:01F17D Other None 0 +01F17E:01F17F Other None 1 +01F180:01F18D Other None 0 +01F18E:01F18E Other None 1 +01F18F:01F190 Other None 0 +01F191:01F19A Other None 1 +01F19B:01F1AD Other None 0 +01F1AE:01F1E5 Other None 1 +01F1E6:01F1FF Regional_Indicator None 0 +01F200:01F200 Other None 0 +01F201:01F20F Other None 1 +01F210:01F219 Other None 0 +01F21A:01F21A Other None 1 +01F21B:01F22E Other None 0 +01F22F:01F22F Other None 1 +01F230:01F231 Other None 0 +01F232:01F23A Other None 1 +01F23B:01F23B Other None 0 +01F23C:01F23F Other None 1 +01F240:01F248 Other None 0 +01F249:01F25F Other None 1 +01F260:01F265 Other None 0 +01F266:01F321 Other None 1 +01F322:01F323 Other None 0 +01F324:01F393 Other None 1 +01F394:01F395 Other None 0 +01F396:01F397 Other None 1 +01F398:01F398 Other None 0 +01F399:01F39B Other None 1 +01F39C:01F39D Other None 0 +01F39E:01F3F0 Other None 1 +01F3F1:01F3F2 Other None 0 +01F3F3:01F3F5 Other None 1 +01F3F6:01F3F6 Other None 0 +01F3F7:01F3FA Other None 1 +01F3FB:01F3FF Extend Extend 0 +01F400:01F4FD Other None 1 +01F4FE:01F4FE Other None 0 +01F4FF:01F53D Other None 1 +01F53E:01F548 Other None 0 +01F549:01F54E Other None 1 +01F54F:01F54F Other None 0 +01F550:01F567 Other None 1 +01F568:01F56E Other None 0 +01F56F:01F570 Other None 1 +01F571:01F572 Other None 0 +01F573:01F57A Other None 1 +01F57B:01F586 Other None 0 +01F587:01F587 Other None 1 +01F588:01F589 Other None 0 +01F58A:01F58D Other None 1 +01F58E:01F58F Other None 0 +01F590:01F590 Other None 1 +01F591:01F594 Other None 0 +01F595:01F596 Other None 1 +01F597:01F5A3 Other None 0 +01F5A4:01F5A5 Other None 1 +01F5A6:01F5A7 Other None 0 +01F5A8:01F5A8 Other None 1 +01F5A9:01F5B0 Other None 0 +01F5B1:01F5B2 Other None 1 +01F5B3:01F5BB Other None 0 +01F5BC:01F5BC Other None 1 +01F5BD:01F5C1 Other None 0 +01F5C2:01F5C4 Other None 1 +01F5C5:01F5D0 Other None 0 +01F5D1:01F5D3 Other None 1 +01F5D4:01F5DB Other None 0 +01F5DC:01F5DE Other None 1 +01F5DF:01F5E0 Other None 0 +01F5E1:01F5E1 Other None 1 +01F5E2:01F5E2 Other None 0 +01F5E3:01F5E3 Other None 1 +01F5E4:01F5E7 Other None 0 +01F5E8:01F5E8 Other None 1 +01F5E9:01F5EE Other None 0 +01F5EF:01F5EF Other None 1 +01F5F0:01F5F2 Other None 0 +01F5F3:01F5F3 Other None 1 +01F5F4:01F5F9 Other None 0 +01F5FA:01F64F Other None 1 +01F650:01F67F Other None 0 +01F680:01F6C5 Other None 1 +01F6C6:01F6CA Other None 0 +01F6CB:01F6D2 Other None 1 +01F6D3:01F6D4 Other None 0 +01F6D5:01F6E5 Other None 1 +01F6E6:01F6E8 Other None 0 +01F6E9:01F6E9 Other None 1 +01F6EA:01F6EA Other None 0 +01F6EB:01F6F0 Other None 1 +01F6F1:01F6F2 Other None 0 +01F6F3:01F6FF Other None 1 +01F700:01F7D9 Other None 0 +01F7DA:01F7FF Other None 1 +01F800:01F80B Other None 0 +01F80C:01F80F Other None 1 +01F810:01F847 Other None 0 +01F848:01F84F Other None 1 +01F850:01F859 Other None 0 +01F85A:01F85F Other None 1 +01F860:01F887 Other None 0 +01F888:01F88F Other None 1 +01F890:01F8AD Other None 0 +01F8AE:01F8AF Other None 1 +01F8B0:01F8BB Other None 0 +01F8BC:01F8BF Other None 1 +01F8C0:01F8C1 Other None 0 +01F8C2:01F8CF Other None 1 +01F8D0:01F8D8 Other None 0 +01F8D9:01F8FF Other None 1 +01F900:01F90B Other None 0 +01F90C:01F93A Other None 1 +01F93B:01F93B Other None 0 +01F93C:01F945 Other None 1 +01F946:01F946 Other None 0 +01F947:01F9FF Other None 1 +01FA00:01FA57 Other None 0 +01FA58:01FA5F Other None 1 +01FA60:01FA6D Other None 0 +01FA6E:01FAFF Other None 1 +01FB00:01FBFF Other None 0 +01FC00:01FFFD Other None 1 +01FFFE:0DFFFF Other None 0 +0E0000:0E001F Control None 0 +0E0020:0E007F Extend Extend 0 +0E0080:0E00FF Control None 0 +0E0100:0E01EF Extend Extend 0 +0E01F0:0E0FFF Control None 0 +0E1000:10FFFF Other None 0 diff --git a/crates/unicode/tests/differential.rs b/crates/unicode/tests/differential.rs index 1bed13e7faf..f0920f8320b 100644 --- a/crates/unicode/tests/differential.rs +++ b/crates/unicode/tests/differential.rs @@ -11,7 +11,7 @@ //! Both data files use the same run-length format: one `predicate` line per //! str method, followed by comma-separated hex `start:end` inclusive ranges. -// spell-checker:ignore recategorized recategorizations +// spell-checker:ignore recategorized recategorizations incb #[cfg(test)] mod tests { @@ -422,3 +422,84 @@ mod tests { } } } + +#[test] +fn unicode17_grapheme_properties_exact_final() { + use rustpython_unicode::grapheme; + use rustpython_wtf8::CodePoint; + + const MAX: u32 = 0x110000; + const REFERENCE: &str = include_str!("data/cpython3.15_graphemes.txt"); + const GCB: [&str; 14] = [ + "Other", + "Prepend", + "CR", + "LF", + "Control", + "Extend", + "Regional_Indicator", + "SpacingMark", + "L", + "V", + "T", + "LV", + "LVT", + "ZWJ", + ]; + const INCB: [&str; 4] = ["None", "Linker", "Consonant", "Extend"]; + assert!(REFERENCE.len() <= 1024 * 1024, "oracle size bound"); + assert!( + REFERENCE + .starts_with("# CPython d220409a13b77de7a6c30abe16ea90db01528edc; Unicode 17.0.0\n"), + "wrong reference provenance" + ); + let mut next = 0; + let mut rows = 0; + let mut mismatches = 0usize; + let mut first = Vec::new(); + for line in REFERENCE.lines().filter(|line| !line.starts_with('#')) { + rows += 1; + assert!(rows <= 100000, "oracle range count bound"); + let fields: Vec<_> = line.split_whitespace().collect(); + assert_eq!(fields.len(), 4, "invalid oracle row"); + let (start, end) = fields[0].split_once(':').expect("start:end"); + let start = u32::from_str_radix(start, 16).expect("hex start"); + let end = u32::from_str_radix(end, 16).expect("hex end"); + assert_eq!(start, next, "gap, overlap, or unordered oracle range"); + assert!(start <= end && end < MAX, "invalid oracle range"); + assert!( + GCB.contains(&fields[1]) && INCB.contains(&fields[2]), + "invalid property name" + ); + let pictographic = match fields[3] { + "0" => false, + "1" => true, + _ => panic!("invalid Extended_Pictographic value"), + }; + for cp in start..=end { + // Includes surrogates; converting through char would lose coverage. + let point = CodePoint::from_u32(cp).expect("valid code point"); + let actual = ( + grapheme::grapheme_cluster_break(point), + grapheme::indic_conjunct_break(point), + grapheme::extended_pictographic(point), + ); + let expected = (fields[1], fields[2], pictographic); + if actual != expected { + mismatches += 1; + if first.len() < 20 { + first.push(format!("U+{cp:06X}: expected {expected:?}, got {actual:?}")); + } + } + } + next = end + 1; + } + assert_eq!(next, MAX, "oracle does not cover every code point"); + assert_eq!( + mismatches, + 0, + "first {} mismatches: {}", + first.len(), + first.join("; ") + ); +} diff --git a/crates/unicode/tests/generate_reference.py b/crates/unicode/tests/generate_reference.py index 6a78230de75..4f881153c9c 100644 --- a/crates/unicode/tests/generate_reference.py +++ b/crates/unicode/tests/generate_reference.py @@ -21,6 +21,176 @@ MAX = 0x110000 + +def generate_graphemes(args) -> int: + """Write a new Unicode 17 oracle, without touching the older fixtures.""" + import hashlib + import json + import subprocess + + commit = "d220409a13b77de7a6c30abe16ea90db01528edc" + gcb_names = ( + "Other", + "Prepend", + "CR", + "LF", + "Control", + "Extend", + "Regional_Indicator", + "SpacingMark", + "L", + "V", + "T", + "LV", + "LVT", + "ZWJ", + ) + incb_names = ("None", "Linker", "Consonant", "Extend") + + def digest(path): + h = hashlib.sha256() + with path.open("rb") as stream: + for chunk in iter(lambda: stream.read(65536), b""): + h.update(chunk) + return h.hexdigest() + + if sys.implementation.name != "cpython" or unicodedata.unidata_version != "17.0.0": + raise RuntimeError("requires the reviewed CPython Unicode 17 oracle") + executable = pathlib.Path(sys.executable).resolve(strict=True) + extension = pathlib.Path(unicodedata.__file__).resolve(strict=True) + if ( + digest(executable) != args.python_sha256 + or digest(extension) != args.unicodedata_sha256 + ): + raise RuntimeError("oracle executable or extension hash mismatch") + source = args.cpython_source.resolve(strict=True) + head = subprocess.check_output( + ["git", "-C", str(source), "rev-parse", "HEAD"], + timeout=5, + text=True, + ).strip() + if head != commit: + raise RuntimeError("CPython source is not the pinned final commit") + header = source / "Modules/unicodedata_db.h" + blob = subprocess.check_output( + ["git", "-C", str(source), "show", f"{commit}:Modules/unicodedata_db.h"], + timeout=5, + ) + if digest(header) != hashlib.sha256(blob).hexdigest(): + raise RuntimeError("CPython Unicode database header differs from pinned source") + + outputs = [ + args.graphemes_output, + args.graphemes_raw_output, + args.graphemes_manifest, + ] + outputs = [p.resolve() for p in outputs] + if len(set(outputs)) != 3: + raise RuntimeError("oracle output paths must be distinct") + old_data = pathlib.Path(__file__).resolve().parent / "data" + for path in outputs: + if path.exists() or path.is_relative_to(old_data): + raise RuntimeError( + "outputs must be new files outside existing fixture directory" + ) + if not path.parent.is_dir(): + raise RuntimeError("output parents must already exist") + + range_path, raw_path, manifest_path = outputs + ranges = [] + start = 0 + previous = None + raw_hash = hashlib.sha256() + # Exclusive creation prevents replacing any existing reference artifact. + with raw_path.open("xb") as raw: + buffer = bytearray() + for cp in range(MAX): + c = chr(cp) + triple = ( + gcb_names.index(unicodedata.grapheme_cluster_break(c)), + incb_names.index(unicodedata.indic_conjunct_break(c)), + int(unicodedata.extended_pictographic(c)), + ) + if triple != previous: + if previous is not None: + ranges.append((start, cp - 1, previous)) + if len(ranges) > 100000: + raise RuntimeError("range oracle exceeds reviewed count bound") + start, previous = cp, triple + buffer.extend(triple) + if len(buffer) >= 65535: + raw.write(buffer) + raw_hash.update(buffer) + buffer.clear() + raw.write(buffer) + raw_hash.update(buffer) + ranges.append((start, MAX - 1, previous)) + if raw_path.stat().st_size != MAX * 3: + raise RuntimeError("raw oracle length mismatch") + lines = [ + f"# CPython {commit}; Unicode 17.0.0", + "# Inclusive contiguous ranges: start:end GCB InCB Extended_Pictographic", + f"# Reconstructed raw SHA256 {raw_hash.hexdigest()}", + ] + for start, end, (gcb, incb, pictographic) in ranges: + lines.append( + f"{start:06X}:{end:06X} {gcb_names[gcb]} {incb_names[incb]} {pictographic}" + ) + text = ("\n".join(lines) + "\n").encode("ascii") + if len(text) > 1024 * 1024 or len(ranges) > 100000: + raise RuntimeError("range oracle exceeds reviewed size bound") + reconstructed = hashlib.sha256() + next_cp = 0 + for line in text.decode("ascii").splitlines(): + if line.startswith("#"): + continue + bounds, gcb, incb, pictographic = line.split() + first, last = (int(value, 16) for value in bounds.split(":")) + if first != next_cp or not first <= last < MAX: + raise RuntimeError("range encoding has a gap, overlap, or invalid bound") + triple = bytes( + (gcb_names.index(gcb), incb_names.index(incb), int(pictographic)) + ) + reconstructed.update(triple * (last - first + 1)) + next_cp = last + 1 + if next_cp != MAX or reconstructed.hexdigest() != raw_hash.hexdigest(): + raise RuntimeError("range reconstruction differs from the raw property oracle") + with range_path.open("xb") as output: + output.write(text) + manifest = { + "cpython_commit": commit, + "unicode_version": unicodedata.unidata_version, + "runtime_version": sys.version, + "runtime_git": getattr(sys, "_git", None), + "python": str(executable), + "python_sha256": digest(executable), + "unicodedata": str(extension), + "unicodedata_sha256": digest(extension), + "database_header_sha256": digest(header), + "generator_sha256": digest(pathlib.Path(__file__)), + "codepoints": MAX, + "properties": 3, + "ranges": len(ranges), + "gcb_encoding": gcb_names, + "incb_encoding": incb_names, + "raw_bytes": MAX * 3, + "raw_sha256": raw_hash.hexdigest(), + "ranges_bytes": len(text), + "ranges_sha256": hashlib.sha256(text).hexdigest(), + } + manifest_bytes = (json.dumps(manifest, indent=2) + "\n").encode("utf-8") + # Total artifact bound, independent of the launcher's per-file rlimit. + if ( + len(manifest_bytes) > 16384 + or MAX * 3 + len(text) + len(manifest_bytes) > 4_407_296 + ): + raise RuntimeError("total oracle artifact budget exceeded") + with manifest_path.open("xb") as output: + output.write(manifest_bytes) + print(json.dumps(manifest, sort_keys=True)) + return 0 + + # str predicates: name -> single-char method. STR_PREDICATES = { "isalpha": str.isalpha, @@ -111,4 +281,18 @@ def main() -> int: if __name__ == "__main__": - raise SystemExit(main()) + if len(sys.argv) == 1: + raise SystemExit(main()) + import argparse + + parser = argparse.ArgumentParser( + description="Generate only a new grapheme property oracle" + ) + parser.add_argument("--graphemes-only", action="store_true", required=True) + parser.add_argument("--graphemes-output", type=pathlib.Path, required=True) + parser.add_argument("--graphemes-raw-output", type=pathlib.Path, required=True) + parser.add_argument("--graphemes-manifest", type=pathlib.Path, required=True) + parser.add_argument("--cpython-source", type=pathlib.Path, required=True) + parser.add_argument("--python-sha256", required=True) + parser.add_argument("--unicodedata-sha256", required=True) + raise SystemExit(generate_graphemes(parser.parse_args())) From 695e0d4967298b58242440909d90d976b3f78a47 Mon Sep 17 00:00:00 2001 From: youknowdot Date: Thu, 15 Oct 2026 16:54:32 +0900 Subject: [PATCH 2/9] Bind grapheme Segment methods to the Python object Use the required Py receiver for native descriptors; preserve payload ownership and representation behavior. Assisted-by: OpenAI Codex:model version unavailable --- crates/stdlib/src/unicodedata.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stdlib/src/unicodedata.rs b/crates/stdlib/src/unicodedata.rs index 9a4e01749d2..3404c6c0f5f 100644 --- a/crates/stdlib/src/unicodedata.rs +++ b/crates/stdlib/src/unicodedata.rs @@ -80,7 +80,7 @@ mod unicodedata { } #[pyclass(with(Representable), flags(IMMUTABLETYPE, DISALLOW_INSTANTIATION))] - impl Segment { + impl Py { #[pygetset] fn start(&self) -> usize { self.start From c16f5e18bec53e68d7d047c31503368a5c3fa7a9 Mon Sep 17 00:00:00 2001 From: youknowdot Date: Thu, 15 Oct 2026 16:56:35 +0900 Subject: [PATCH 3/9] Use explicit object receivers for Segment descriptors Keep the inherent impl on the local payload and receive &Py, as required for stdlib classes outside the VM crate. Assisted-by: OpenAI Codex:model version unavailable --- crates/stdlib/src/unicodedata.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/stdlib/src/unicodedata.rs b/crates/stdlib/src/unicodedata.rs index 3404c6c0f5f..bc51264f93d 100644 --- a/crates/stdlib/src/unicodedata.rs +++ b/crates/stdlib/src/unicodedata.rs @@ -80,20 +80,20 @@ mod unicodedata { } #[pyclass(with(Representable), flags(IMMUTABLETYPE, DISALLOW_INSTANTIATION))] - impl Py { + impl Segment { #[pygetset] - fn start(&self) -> usize { - self.start + fn start(zelf: &Py) -> usize { + zelf.start } #[pygetset] - fn end(&self) -> usize { - self.end + fn end(zelf: &Py) -> usize { + zelf.end } #[pymethod] - fn __str__(&self, vm: &VirtualMachine) -> PyStrRef { - vm.ctx.new_str(&self.string.as_wtf8()[self.bytes.clone()]) + fn __str__(zelf: &Py, vm: &VirtualMachine) -> PyStrRef { + vm.ctx.new_str(&zelf.string.as_wtf8()[zelf.bytes.clone()]) } } From 1f0116d7f4490b5467da4ade38f86ab1a9a685bc Mon Sep 17 00:00:00 2001 From: youknowdot Date: Thu, 15 Oct 2026 17:03:06 +0900 Subject: [PATCH 4/9] Mark grapheme property lookups as must-use Satisfy strict Clippy for the three pure property functions without changing their behavior. Assisted-by: OpenAI Codex:model version unavailable --- crates/unicode/src/grapheme.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/unicode/src/grapheme.rs b/crates/unicode/src/grapheme.rs index 544fc5d3c25..b8a097e3710 100644 --- a/crates/unicode/src/grapheme.rs +++ b/crates/unicode/src/grapheme.rs @@ -20,6 +20,7 @@ fn incb(c: CodePoint) -> Incb { } /// The long Grapheme_Cluster_Break property name. +#[must_use] pub fn grapheme_cluster_break(c: CodePoint) -> &'static str { match gcb(c) { Gcb::CR => "CR", @@ -40,6 +41,7 @@ pub fn grapheme_cluster_break(c: CodePoint) -> &'static str { } /// The long Indic_Conjunct_Break property name. +#[must_use] pub fn indic_conjunct_break(c: CodePoint) -> &'static str { match incb(c) { Incb::Consonant => "Consonant", @@ -50,6 +52,7 @@ pub fn indic_conjunct_break(c: CodePoint) -> &'static str { } /// Whether the code point has the Extended_Pictographic property. +#[must_use] pub fn extended_pictographic(c: CodePoint) -> bool { CodePointSetData::new::().contains32(c.to_u32()) } From 81470d0535aea256762826d31fd76efdb212ae38 Mon Sep 17 00:00:00 2001 From: youknowdot Date: Thu, 15 Oct 2026 17:59:04 +0900 Subject: [PATCH 5/9] Enable validated Unicode grapheme canonical tests Remove seven stale expected-failure markers through update_lib metadata after native property parity, canonical methods, full Unicode 17 grapheme corpus, and reference-cycle GC validation. Assisted-by: OpenAI Codex:model version unavailable --- Lib/test/test_unicodedata.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Lib/test/test_unicodedata.py b/Lib/test/test_unicodedata.py index 254318b3093..256a010e8fd 100644 --- a/Lib/test/test_unicodedata.py +++ b/Lib/test/test_unicodedata.py @@ -798,7 +798,6 @@ def test_isxidcontinue(self): self.assertRaises(TypeError, self.db.isxidcontinue) self.assertRaises(TypeError, self.db.isxidcontinue, 'xx') - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'grapheme_cluster_break' def test_grapheme_cluster_break(self): gcb = self.db.grapheme_cluster_break self.assertEqual(gcb(' '), 'Other') @@ -896,7 +895,6 @@ def test_grapheme_cluster_break(self): self.assertRaises(TypeError, gcb, '') self.assertRaises(TypeError, gcb, 'xx') - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'indic_conjunct_break' def test_indic_conjunct_break(self): incb = self.db.indic_conjunct_break self.assertEqual(incb(' '), 'None') @@ -925,7 +923,6 @@ def test_indic_conjunct_break(self): self.assertRaises(TypeError, incb, '') self.assertRaises(TypeError, incb, 'xx') - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'extended_pictographic' def test_extended_pictographic(self): ext_pict = self.db.extended_pictographic self.assertIs(ext_pict(' '), False) @@ -946,7 +943,6 @@ def test_extended_pictographic(self): self.assertRaises(TypeError, ext_pict, '') self.assertRaises(TypeError, ext_pict, 'xx') - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'iter_graphemes' def test_grapheme_break(self): def graphemes(*args): return list(map(str, self.db.iter_graphemes(*args))) @@ -1262,7 +1258,6 @@ def test_linebreak_7643(self): self.assertEqual(len(lines), 1, r"%a should not be a linebreak" % c) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'iter_graphemes' def test_segment_object(self): segments = list(unicodedata.iter_graphemes('spa\u0300m')) self.assertEqual(len(segments), 4, segments) @@ -1383,7 +1378,6 @@ class MyStr(str): class GraphemeBreakTest(unittest.TestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError module 'unicodedata' has no attribute 'iter_graphemes' @requires_resource('network') def test_grapheme_break(self): TESTDATAFILE = "GraphemeBreakTest.txt" @@ -1423,7 +1417,6 @@ def run_grapheme_break_tests(self, testdata): self.assertEqual([x.start for x in result], breaks[i:-1], comment) self.assertEqual([x.end for x in result], breaks[i+1:], comment) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'iter_graphemes' def test_reference_loops(self): # Test that reference loops involving GraphemeBreakIterator or # Segment can be broken by the garbage collector. From 85647ab2db8b0ef47853d7c8c86aa8a7b2d66aa9 Mon Sep 17 00:00:00 2001 From: youknowdot Date: Thu, 15 Oct 2026 20:20:19 +0900 Subject: [PATCH 6/9] Accept platform line endings in grapheme oracle header Compare the complete provenance line without its line ending so the same exact Unicode 17 reference is accepted after Windows checkout conversion. Keep the complete code-point property sweep unchanged. Assisted-by: OpenAI Codex:model version unavailable --- crates/unicode/tests/differential.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/unicode/tests/differential.rs b/crates/unicode/tests/differential.rs index f0920f8320b..b8f8cae1f8a 100644 --- a/crates/unicode/tests/differential.rs +++ b/crates/unicode/tests/differential.rs @@ -448,9 +448,9 @@ fn unicode17_grapheme_properties_exact_final() { ]; const INCB: [&str; 4] = ["None", "Linker", "Consonant", "Extend"]; assert!(REFERENCE.len() <= 1024 * 1024, "oracle size bound"); - assert!( - REFERENCE - .starts_with("# CPython d220409a13b77de7a6c30abe16ea90db01528edc; Unicode 17.0.0\n"), + assert_eq!( + REFERENCE.lines().next(), + Some("# CPython d220409a13b77de7a6c30abe16ea90db01528edc; Unicode 17.0.0"), "wrong reference provenance" ); let mut next = 0; From 08cc0b6c6a9c1d99f7276b85168ec788c28fcb19 Mon Sep 17 00:00:00 2001 From: youknowdot Date: Thu, 15 Oct 2026 22:34:19 +0900 Subject: [PATCH 7/9] Keep grapheme property regression in test module Move the existing complete Unicode 17 property regression into the existing cfg(test) module to satisfy strict Clippy. Preserve the fixture, assertions, code-point coverage, and platform-independent provenance line check unchanged. Assisted-by: OpenAI Codex:model version unavailable --- crates/unicode/tests/differential.rs | 148 +++++++++++++-------------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/crates/unicode/tests/differential.rs b/crates/unicode/tests/differential.rs index b8f8cae1f8a..0d66c1eff14 100644 --- a/crates/unicode/tests/differential.rs +++ b/crates/unicode/tests/differential.rs @@ -421,85 +421,85 @@ mod tests { panic!("{msg}"); } } -} - -#[test] -fn unicode17_grapheme_properties_exact_final() { - use rustpython_unicode::grapheme; - use rustpython_wtf8::CodePoint; - const MAX: u32 = 0x110000; - const REFERENCE: &str = include_str!("data/cpython3.15_graphemes.txt"); - const GCB: [&str; 14] = [ - "Other", - "Prepend", - "CR", - "LF", - "Control", - "Extend", - "Regional_Indicator", - "SpacingMark", - "L", - "V", - "T", - "LV", - "LVT", - "ZWJ", - ]; - const INCB: [&str; 4] = ["None", "Linker", "Consonant", "Extend"]; - assert!(REFERENCE.len() <= 1024 * 1024, "oracle size bound"); - assert_eq!( - REFERENCE.lines().next(), - Some("# CPython d220409a13b77de7a6c30abe16ea90db01528edc; Unicode 17.0.0"), - "wrong reference provenance" - ); - let mut next = 0; - let mut rows = 0; - let mut mismatches = 0usize; - let mut first = Vec::new(); - for line in REFERENCE.lines().filter(|line| !line.starts_with('#')) { - rows += 1; - assert!(rows <= 100000, "oracle range count bound"); - let fields: Vec<_> = line.split_whitespace().collect(); - assert_eq!(fields.len(), 4, "invalid oracle row"); - let (start, end) = fields[0].split_once(':').expect("start:end"); - let start = u32::from_str_radix(start, 16).expect("hex start"); - let end = u32::from_str_radix(end, 16).expect("hex end"); - assert_eq!(start, next, "gap, overlap, or unordered oracle range"); - assert!(start <= end && end < MAX, "invalid oracle range"); - assert!( - GCB.contains(&fields[1]) && INCB.contains(&fields[2]), - "invalid property name" + #[test] + fn unicode17_grapheme_properties_exact_final() { + use rustpython_unicode::grapheme; + use rustpython_wtf8::CodePoint; + + const MAX: u32 = 0x110000; + const REFERENCE: &str = include_str!("data/cpython3.15_graphemes.txt"); + const GCB: [&str; 14] = [ + "Other", + "Prepend", + "CR", + "LF", + "Control", + "Extend", + "Regional_Indicator", + "SpacingMark", + "L", + "V", + "T", + "LV", + "LVT", + "ZWJ", + ]; + const INCB: [&str; 4] = ["None", "Linker", "Consonant", "Extend"]; + assert!(REFERENCE.len() <= 1024 * 1024, "oracle size bound"); + assert_eq!( + REFERENCE.lines().next(), + Some("# CPython d220409a13b77de7a6c30abe16ea90db01528edc; Unicode 17.0.0"), + "wrong reference provenance" ); - let pictographic = match fields[3] { - "0" => false, - "1" => true, - _ => panic!("invalid Extended_Pictographic value"), - }; - for cp in start..=end { - // Includes surrogates; converting through char would lose coverage. - let point = CodePoint::from_u32(cp).expect("valid code point"); - let actual = ( - grapheme::grapheme_cluster_break(point), - grapheme::indic_conjunct_break(point), - grapheme::extended_pictographic(point), + let mut next = 0; + let mut rows = 0; + let mut mismatches = 0usize; + let mut first = Vec::new(); + for line in REFERENCE.lines().filter(|line| !line.starts_with('#')) { + rows += 1; + assert!(rows <= 100000, "oracle range count bound"); + let fields: Vec<_> = line.split_whitespace().collect(); + assert_eq!(fields.len(), 4, "invalid oracle row"); + let (start, end) = fields[0].split_once(':').expect("start:end"); + let start = u32::from_str_radix(start, 16).expect("hex start"); + let end = u32::from_str_radix(end, 16).expect("hex end"); + assert_eq!(start, next, "gap, overlap, or unordered oracle range"); + assert!(start <= end && end < MAX, "invalid oracle range"); + assert!( + GCB.contains(&fields[1]) && INCB.contains(&fields[2]), + "invalid property name" ); - let expected = (fields[1], fields[2], pictographic); - if actual != expected { - mismatches += 1; - if first.len() < 20 { - first.push(format!("U+{cp:06X}: expected {expected:?}, got {actual:?}")); + let pictographic = match fields[3] { + "0" => false, + "1" => true, + _ => panic!("invalid Extended_Pictographic value"), + }; + for cp in start..=end { + // Includes surrogates; converting through char would lose coverage. + let point = CodePoint::from_u32(cp).expect("valid code point"); + let actual = ( + grapheme::grapheme_cluster_break(point), + grapheme::indic_conjunct_break(point), + grapheme::extended_pictographic(point), + ); + let expected = (fields[1], fields[2], pictographic); + if actual != expected { + mismatches += 1; + if first.len() < 20 { + first.push(format!("U+{cp:06X}: expected {expected:?}, got {actual:?}")); + } } } + next = end + 1; } - next = end + 1; + assert_eq!(next, MAX, "oracle does not cover every code point"); + assert_eq!( + mismatches, + 0, + "first {} mismatches: {}", + first.len(), + first.join("; ") + ); } - assert_eq!(next, MAX, "oracle does not cover every code point"); - assert_eq!( - mismatches, - 0, - "first {} mismatches: {}", - first.len(), - first.join("; ") - ); } From 52a10cff797511c854064a7bd067c34cf146f711 Mon Sep 17 00:00:00 2001 From: youknowdot Date: Fri, 16 Oct 2026 12:58:59 +0900 Subject: [PATCH 8/9] Qualify grapheme iterator return type for generated bindings Assisted-by: OpenAI Codex:model version unavailable --- crates/stdlib/src/unicodedata.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stdlib/src/unicodedata.rs b/crates/stdlib/src/unicodedata.rs index bc51264f93d..ab6d808e58a 100644 --- a/crates/stdlib/src/unicodedata.rs +++ b/crates/stdlib/src/unicodedata.rs @@ -179,7 +179,7 @@ mod unicodedata { start: OptionalArg, end: OptionalArg, vm: &VirtualMachine, - ) -> PyResult> { + ) -> PyResult> { // Convert in argument order, including overflow, before adjusting bounds. let index = |arg: OptionalArg, default| -> PyResult { match arg { From 07e76cd2eea70c06c6f406d2ba555693920b5e60 Mon Sep 17 00:00:00 2001 From: youknowdot Date: Fri, 16 Oct 2026 12:58:31 +0900 Subject: [PATCH 9/9] Fix deprecated PyO3 code input import in benchmarks Use the canonical code module path introduced in PyO3 0.30 without suppressing deprecation warnings. Assisted-by: OpenAI Codex:model version unavailable --- benches/execution.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benches/execution.rs b/benches/execution.rs index a09ab9c7890..c0c783f24ed 100644 --- a/benches/execution.rs +++ b/benches/execution.rs @@ -7,7 +7,7 @@ use criterion::{ Bencher, BenchmarkGroup, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main, measurement::WallTime, }; -use pyo3::types::{PyCode, PyCodeInput, PyCodeMethods, PyDict, PyDictMethods}; +use pyo3::types::{PyCode, PyCodeMethods, PyDict, PyDictMethods, code::PyCodeInput}; use rustpython_compiler::Mode; use rustpython_vm::{Interpreter, PyResult, Settings}; use std::{collections::HashMap, path::Path};