diff --git a/pythainlp/util/syllable.py b/pythainlp/util/syllable.py index e193e1a87..46610a927 100644 --- a/pythainlp/util/syllable.py +++ b/pythainlp/util/syllable.py @@ -95,6 +95,17 @@ def sound_syllable(syllable: str) -> str: ): return "live" + # Handle syllables with only อ (no other consonants from thai_consonants_all) + if len(consonants) == 0 and "อ" in syllable: + # Syllables with only อ and long vowels are live + if any((c in set("าีืแูเโไใำ")) for c in syllable): + return "live" + # Short vowels or special cases + if any((c in set(short)) for c in syllable): + return "dead" + # Default to live for vowel-only syllables + return "live" + # get spelling consonants spelling_consonant = consonants[-1] if (spelling_consonant in _check_2) and ( @@ -228,6 +239,10 @@ def _check_sonorant_syllable(syllable: str) -> bool: _sonorant = [i for i in syllable if i in thai_low_sonorants] consonants = [i for i in syllable if i in list(thai_consonants)] + # Return False if no sonorants or not enough consonants + if not _sonorant or len(consonants) < 2: + return False + if _sonorant[-1] == consonants[-2]: return True @@ -267,6 +282,11 @@ def tone_detector(syllable: str) -> str: s = sound_syllable(syllable) # get consonants consonants = [i for i in syllable if i in list(thai_consonants)] + + # Handle syllables with no consonants (e.g., ฤ, ฦ) + if len(consonants) == 0: + return "" + initial_consonant = consonants[0] tone_mark = _tone_mark_detector(syllable) syllable_check = syllable_open_close_detector(syllable) @@ -274,80 +294,92 @@ def tone_detector(syllable: str) -> str: initial_consonant_type = thai_initial_consonant_to_type[initial_consonant] # r for store value r = "" + # Special handling for อ and ห with sonorants if len(consonants) > 1 and (initial_consonant in ("อ", "ห")): consonant_ending = _check_sonorant_syllable(syllable) - if ( - initial_consonant == "อ" - and consonant_ending - and s == "live" - and tone_mark == "่" - ): - r = "l" - elif ( - initial_consonant == "ห" - and consonant_ending - and s == "live" - and tone_mark == "่" - ): - r = "l" - elif initial_consonant == "อ" and consonant_ending and s == "dead": - r = "l" - elif ( - initial_consonant == "ห" - and consonant_ending - and s == "live" - and tone_mark == "้" - ): - r = "f" - elif initial_consonant == "ห" and consonant_ending and s == "dead": - r = "l" - elif initial_consonant == "ห" and consonant_ending and s == "live": - r = "r" - elif initial_consonant_type == "high" and s == "live" and tone_mark == "่": + if consonant_ending: + # Only apply special rules if there are sonorants + if ( + initial_consonant == "อ" + and s == "live" + and tone_mark == "่" + ): + r = "l" + elif ( + initial_consonant == "ห" + and s == "live" + and tone_mark == "่" + ): + r = "l" + elif initial_consonant == "อ" and s == "dead": + r = "l" + elif ( + initial_consonant == "ห" + and s == "live" + and tone_mark == "้" + ): + r = "f" + elif initial_consonant == "ห" and s == "dead": + r = "l" + elif initial_consonant == "ห" and s == "live": + r = "r" + # If r is still empty, apply general tone rules + if r == "" and initial_consonant_type == "high" and s == "live" and tone_mark == "่": r = "l" - elif initial_consonant_type == "mid" and s == "live" and tone_mark == "่": + if r == "" and initial_consonant_type == "mid" and s == "live" and tone_mark == "่": r = "l" - elif initial_consonant_type == "low" and tone_mark == "้": + if r == "" and initial_consonant_type == "low" and tone_mark == "้": r = "h" - elif initial_consonant_type == "mid" and tone_mark == "๋": + if r == "" and initial_consonant_type == "mid" and tone_mark == "๋": r = "r" - elif initial_consonant_type == "mid" and tone_mark == "๊": + if r == "" and initial_consonant_type == "mid" and tone_mark == "๊": r = "h" - elif initial_consonant_type == "low" and tone_mark == "่": + if r == "" and initial_consonant_type == "low" and tone_mark == "่": r = "f" - elif initial_consonant_type == "mid" and tone_mark == "้": + if r == "" and initial_consonant_type == "mid" and tone_mark == "้": r = "f" - elif initial_consonant_type == "high" and tone_mark == "้": + if r == "" and initial_consonant_type == "high" and tone_mark == "้": r = "f" - elif ( - initial_consonant_type == "low" + if ( + r == "" + and initial_consonant_type == "low" and syllable_check_length == "short" and syllable_check == "close" and s == "dead" ): r = "h" - elif ( - initial_consonant_type == "low" + if ( + r == "" + and initial_consonant_type == "low" and syllable_check_length == "long" and syllable_check == "close" and s == "dead" ): r = "f" - elif ( - initial_consonant_type == "low" + if ( + r == "" + and initial_consonant_type == "low" and syllable_check_length == "short" and syllable_check == "open" ): r = "h" - elif initial_consonant_type == "mid" and s == "dead": + if ( + r == "" + and initial_consonant_type == "low" + and syllable_check_length == "long" + and syllable_check == "open" + and s == "dead" + ): + r = "f" + if r == "" and initial_consonant_type == "mid" and s == "dead": r = "l" - elif initial_consonant_type == "high" and s == "dead": + if r == "" and initial_consonant_type == "high" and s == "dead": r = "l" - elif initial_consonant_type == "low" and s == "live": + if r == "" and initial_consonant_type == "low" and s == "live": r = "m" - elif initial_consonant_type == "mid" and s == "live": + if r == "" and initial_consonant_type == "mid" and s == "live": r = "m" - elif initial_consonant_type == "high" and s == "live": + if r == "" and initial_consonant_type == "high" and s == "live": r = "r" return r diff --git a/tests/core/test_util.py b/tests/core/test_util.py index f9cfc5405..4202827c5 100644 --- a/tests/core/test_util.py +++ b/tests/core/test_util.py @@ -725,6 +725,58 @@ def test_tone_detector(self): ("h", "ครับ"), ("f", "ค่ะ"), ("m", "เอ"), + # Test cases from issue #1176 - syllables that previously returned UNKNOWN_tone + # Low consonant + dead + long + open → falling tone + ("f", "คอ"), + ("f", "พฤ"), + ("f", "พอ"), + ("f", "หญ้า"), + ("f", "อ้อม"), + ("f", "อ้าง"), + ("f", "เท"), + ("f", "เธอ"), + ("f", "เพ"), + ("f", "เภอ"), + ("f", "เอื้อ"), + ("f", "แฟ"), + ("f", "โค"), + ("f", "โฆ"), + ("f", "ไอ้"), + # Mid/high consonant + dead → low tone + ("l", "หรอก"), + ("l", "หลอก"), + ("l", "หัก"), + ("l", "หาก"), + ("l", "ห่อ"), + ("l", "อด"), + ("l", "อดีต"), + ("l", "อธิ"), + ("l", "อบ"), + ("l", "ออก"), + ("l", "อะ"), + ("l", "อัด"), + ("l", "อาจ"), + ("l", "อาด"), + ("l", "อิ"), + ("l", "อีก"), + ("l", "อุต"), + ("l", "อุป"), + ("l", "อู่"), + ("l", "เหตุ"), + ("l", "เอก"), + ("l", "แอบ"), + ("l", "ใหญ่"), + # Mid consonant + live → mid tone (especially syllables with only อ) + ("m", "อนา"), + ("m", "อัตรา"), + ("m", "อา"), + ("m", "อำ"), + ("m", "เอง"), + ("m", "เอา"), + ("m", "แอ"), + ("m", "โอ"), + ("m", "โอน"), + ("m", "ไอ"), ] for i, j in data: self.assertEqual(