Files

231 lines
8.3 KiB
PHP
Raw Permalink Normal View History

2026-08-30 22:02:02 +00:00
<?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());
}
}