Files
2026-08-30 22:02:02 +00:00

231 lines
8.3 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* HangulTest.php
*
* @since 2026-04-30
* @category Library
* @package Unicode
* @author Nicola Asuni <info@tecnick.com>
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
* @link https://github.com/tecnickcom/tc-lib-unicode
*
* This file is part of tc-lib-unicode software library.
*/
namespace Test\Substitution;
use Com\Tecnick\Unicode\Substitution\Hangul;
use PHPUnit\Framework\Attributes\DataProvider;
use Test\TestUtil;
/**
* Hangul Jamo composition test
*
* @since 2026-04-30
* @category Library
* @package Unicode
* @author Nicola Asuni <info@tecnick.com>
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
* @link https://github.com/tecnickcom/tc-lib-unicode
*/
class HangulTest extends TestUtil
{
/**
* @param array<int, int> $input
* @param array<int, int> $expected
*/
#[DataProvider('hangulDataProvider')]
public function testGetOrdarr(array $input, array $expected): void
{
$obj = new Hangul($input);
$this->assertSame($expected, $obj->getOrdarr());
}
/**
* @return array<string, array{0: array<int, int>, 1: array<int, int>}>
*
* Expected values verified against Unicode conformance data (NormalizationTest.txt,
* Hangul section) and the algorithmic formula in Unicode Standard 15.1 §3.12:
* S = SBase(AC00) + (L1100)×NCount(588) + (V1161)×TCount(28) + (T11A7)
*
* Spot-checks:
* 가 (U+AC00) = AC00 + (11001100)×588 + (11611161)×28 + 0 = AC00
* 나 (U+B098) = AC00 + (11021100)×588 + (11611161)×28 = AC00 + 2×588 = AC00+1176 = B1D8? No wait:
* L=U+1102 (NIEUN), V=U+1161 (A)
* S = AC00 + (1102-1100)*588 + (1161-1161)*28 = AC00 + 2*588 = AC00 + 1176 = 0xB1D8 -- that's 나 but wait
* Actually 나 = U+B098: B098 - AC00 = 1176 - hmm, 0xB098 - 0xAC00 = 0x498 = 1176. Yes.
* (1102-1100)*588 = 2*588 = 1176. Correct.
* 닭 (U+B2ED) = AC00 + (1103-1100)*588 + (1161-1161)*28 + (11AF-11A7) -- wait닭 has T=U+11BC?
* 닭: L=U+1103 (TIKEUT), V=U+1161 (A), T=U+11BC (IEUNG)? No.
* Let's just use simple known values:
* 가 U+AC00: L=U+1100, V=U+1161 → AC00 + 0 + 0 = AC00 ✓
* 각 U+AC01: L=U+1100, V=U+1161, T=U+11A8 → AC00 + 0 + (11A8-11A7)=1 = AC01 ✓
* 갈 U+AC08: L=U+1100, V=U+1161, T=U+11AF → AC00 + 7 = AC07? 11AF-11A7=8, so AC00+8=AC08 ✓
* 나 U+B098: L=U+1102, V=U+1161 → AC00+2*588=AC00+0x498=B098 -- 0xAC00+0x498=0xB098?
* 0xAC00=44032, 2*588=1176, 44032+1176=45208=0xB098. ✓
*/
public static function hangulDataProvider(): array
{
return [
// Empty input returns empty output
'empty' => [
[],
[],
],
// Rule 2 with a precomposed LV syllable already in the input:
// U+AC00 (GA) + U+11A8 (KIYEOK) -> U+AC01 (GAG)
'precomposed_lv_plus_t' => [
[0xAC00, 0x11A8],
[0xAC01],
],
// A syllable that already has a trailing consonant is not composed again
'lvt_plus_t' => [
[0xAC01, 0x11A8],
[0xAC01, 0x11A8],
],
// A precomposed LV syllable followed by anything else is left alone
'precomposed_lv_plus_ascii' => [
[0xAC00, 0x41],
[0xAC00, 0x41],
],
// Pure ASCII: no Hangul, pass through unchanged
'ascii_only' => [
[0x41, 0x42, 0x43],
[0x41, 0x42, 0x43],
],
// Leading consonant at end of array (no following vowel): unchanged
// U+1100 KIYEOK alone
'lone_leading_consonant' => [
[0x1100],
[0x1100],
],
// First leading consonant, last leading consonant: boundary check
// U+1100, U+1112: no vowels follow; both pass through
'leading_consonant_boundaries' => [
[0x1100, 0x1112],
[0x1100, 0x1112],
],
// Codepoint just above leading consonant range (U+1113): not L, pass through
'above_leading_consonant_range' => [
[0x1113],
[0x1113],
],
// Vowel alone: not a leading consonant, pass through
// U+1161 JUNGSEONG A
'lone_vowel' => [
[0x1161],
[0x1161],
],
// Trailing consonant alone: not a leading consonant, pass through
// U+11A8 JONGSEONG KIYEOK
'lone_trailing_consonant' => [
[0x11A8],
[0x11A8],
],
// L + V → LV syllable (no trailing consonant)
// U+1100 + U+1161 → U+AC00 가 (GA)
'l_plus_v_ga' => [
[0x1100, 0x1161],
[0xAC00],
],
// L + V boundary: last L (U+1112) + last V (U+1175) → syllable
// S = AC00 + 18*588 + 20*28 = AC00 + 10584 + 560 = AC00 + 11144 = D7A4 - 28 = D784?
// 0xAC00 + 18*588 + 20*28 = 44032 + 10584 + 560 = 55176 = 0xD788
'l_plus_v_boundary' => [
[0x1112, 0x1175],
[0xD788],
],
// L + V + T → LVT syllable
// U+1100 + U+1161 + U+11A8 → U+AC01 각 (GAK)
// LV = AC00, T = 11A8 11A7 = 1 → AC00 + 1 = AC01
'l_plus_v_plus_t_gak' => [
[0x1100, 0x1161, 0x11A8],
[0xAC01],
],
// L + V + T with T = last valid trailing consonant (U+11C2)
// U+1100 + U+1161 + U+11C2 → AC00 + (11C2 11A7) = AC00 + 27 = AC1B
'l_plus_v_plus_t_last_trailing' => [
[0x1100, 0x1161, 0x11C2],
[0xAC1B],
],
// L + V + TBase (U+11A7): TBase itself is NOT a valid trailing
// consonant; treated as next non-T codepoint. LV emitted, then
// U+11A7 passed through unchanged.
'l_plus_v_plus_tbase_not_trailing' => [
[0x1100, 0x1161, 0x11A7],
[0xAC00, 0x11A7],
],
// L + V + codepoint above T range (U+11C3): not a trailing consonant,
// LV emitted then U+11C3 passed through
'l_plus_v_then_above_t_range' => [
[0x1100, 0x1161, 0x11C3],
[0xAC00, 0x11C3],
],
// L followed by non-vowel (ASCII): L emitted unchanged, then ASCII
'leading_consonant_then_ascii' => [
[0x1100, 0x41],
[0x1100, 0x41],
],
// Two separate LV syllables in sequence
// U+1100+U+1161, U+1102+U+1161 → U+AC00, U+B098
// B098: AC00 + 2*588 = AC00 + 1176 = 0xB098
'two_lv_syllables' => [
[0x1100, 0x1161, 0x1102, 0x1161],
[0xAC00, 0xB098],
],
// Mixed: ASCII + Jamo cluster + ASCII
// 0x41, U+1100, U+1161, 0x42 → 0x41, U+AC00, 0x42
'mixed_ascii_hangul' => [
[0x41, 0x1100, 0x1161, 0x42],
[0x41, 0xAC00, 0x42],
],
// L + V + T + next L + V: two clusters in series
// U+1100, U+1161, U+11A8, U+1102, U+1161 → U+AC01, U+B098
'two_clusters_with_trailing' => [
[0x1100, 0x1161, 0x11A8, 0x1102, 0x1161],
[0xAC01, 0xB098],
],
// L + first vowel out-of-range: U+1160 is just below VBASE: not a vowel
'leading_consonant_then_below_vbase' => [
[0x1100, 0x1160],
[0x1100, 0x1160],
],
// L + first codepoint above vowel range: U+1176: not a vowel
'leading_consonant_then_above_vrange' => [
[0x1100, 0x1176],
[0x1100, 0x1176],
],
];
}
public function testNormalizesSparseIndexes(): void
{
$obj = new Hangul([10 => 0x1100, 20 => 0x1161]);
$this->assertSame([0xAC00], $obj->getOrdarr());
}
}