-module(hanguleam@composer). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -define(FILEPATH, "src/hanguleam/composer.gleam"). -export([combine_vowels/2, combine_character/3, combine_character_unsafe/3, assemble/1]). -export_type([assemble_error/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -type assemble_error() :: {invalid_choseong, binary()} | {invalid_jungseong, binary()} | {invalid_jongseong, binary()}. -file("src/hanguleam/composer.gleam", 27). ?DOC( " Combines two Korean vowels into a single complex vowel if possible.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " combine_vowels(\"ㅗ\", \"ㅏ\")\n" " // -> \"ㅘ\"\n" "\n" " combine_vowels(\"ㅣ\", \"ㅏ\")\n" " // -> \"ㅣㅏ\" (no combination possible)\n" " ```\n" ). -spec combine_vowels(binary(), binary()) -> binary(). combine_vowels(Vowel1, Vowel2) -> hanguleam@internal@unicode:assemble_vowel_string( <> ). -file("src/hanguleam/composer.gleam", 101). -spec do_combine(binary(), binary(), binary()) -> binary(). do_combine(Choseong, Jungseong, Jongseong) -> Result = begin gleam@result:'try'( hanguleam@internal@utils:find_index( [<<"ㄱ"/utf8>>, <<"ㄲ"/utf8>>, <<"ㄴ"/utf8>>, <<"ㄷ"/utf8>>, <<"ㄸ"/utf8>>, <<"ㄹ"/utf8>>, <<"ㅁ"/utf8>>, <<"ㅂ"/utf8>>, <<"ㅃ"/utf8>>, <<"ㅅ"/utf8>>, <<"ㅆ"/utf8>>, <<"ㅇ"/utf8>>, <<"ㅈ"/utf8>>, <<"ㅉ"/utf8>>, <<"ㅊ"/utf8>>, <<"ㅋ"/utf8>>, <<"ㅌ"/utf8>>, <<"ㅍ"/utf8>>, <<"ㅎ"/utf8>>], Choseong ), fun(Choseong_idx) -> gleam@result:'try'( hanguleam@internal@utils:find_index( [<<"ㅏ"/utf8>>, <<"ㅐ"/utf8>>, <<"ㅑ"/utf8>>, <<"ㅒ"/utf8>>, <<"ㅓ"/utf8>>, <<"ㅔ"/utf8>>, <<"ㅕ"/utf8>>, <<"ㅖ"/utf8>>, <<"ㅗ"/utf8>>, <<"ㅘ"/utf8>>, <<"ㅙ"/utf8>>, <<"ㅚ"/utf8>>, <<"ㅛ"/utf8>>, <<"ㅜ"/utf8>>, <<"ㅝ"/utf8>>, <<"ㅞ"/utf8>>, <<"ㅟ"/utf8>>, <<"ㅠ"/utf8>>, <<"ㅡ"/utf8>>, <<"ㅢ"/utf8>>, <<"ㅣ"/utf8>>], Jungseong ), fun(Jungseong_idx) -> gleam@result:'try'( hanguleam@internal@utils:find_index( [<<""/utf8>>, <<"ㄱ"/utf8>>, <<"ㄲ"/utf8>>, <<"ㄳ"/utf8>>, <<"ㄴ"/utf8>>, <<"ㄵ"/utf8>>, <<"ㄶ"/utf8>>, <<"ㄷ"/utf8>>, <<"ㄹ"/utf8>>, <<"ㄺ"/utf8>>, <<"ㄻ"/utf8>>, <<"ㄼ"/utf8>>, <<"ㄽ"/utf8>>, <<"ㄾ"/utf8>>, <<"ㄿ"/utf8>>, <<"ㅀ"/utf8>>, <<"ㅁ"/utf8>>, <<"ㅂ"/utf8>>, <<"ㅄ"/utf8>>, <<"ㅅ"/utf8>>, <<"ㅆ"/utf8>>, <<"ㅇ"/utf8>>, <<"ㅈ"/utf8>>, <<"ㅊ"/utf8>>, <<"ㅋ"/utf8>>, <<"ㅌ"/utf8>>, <<"ㅍ"/utf8>>, <<"ㅎ"/utf8>>], Jongseong ), fun(Jongseong_idx) -> {ok, {Choseong_idx, Jungseong_idx, Jongseong_idx}} end ) end ) end ) end, case Result of {ok, {Cho, Jung, Jong}} -> Codepoint_int = ((16#AC00 + ((Cho * 21) * 28)) + (Jung * 28)) + Jong, case gleam@string:utf_codepoint(Codepoint_int) of {ok, Codepoint} -> gleam_stdlib:utf_codepoint_list_to_string([Codepoint]); {error, _} -> <<""/utf8>> end; {error, _} -> <<""/utf8>> end. -file("src/hanguleam/composer.gleam", 81). ?DOC( " Combines Korean jamo components into a complete Hangul syllable.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " combine_character(choseong: \"ㄱ\", jungseong: \"ㅏ\", jongseong: \"\")\n" " // -> Ok(\"가\")\n" "\n" " combine_character(choseong: \"ㄲ\", jungseong: \"ㅠ\", jongseong: \"ㅇ\")\n" " // -> Ok(\"뀽\")\n" " ```\n" ). -spec combine_character(binary(), binary(), binary()) -> {ok, binary()} | {error, assemble_error()}. combine_character(Choseong, Jungseong, Jongseong) -> Jungseong@1 = hanguleam@internal@unicode:assemble_vowel_string(Jungseong), Jongseong@1 = hanguleam@internal@unicode:assemble_consonant_string( Jongseong ), case {hanguleam@validator:can_be_choseong(Choseong), hanguleam@validator:can_be_jungseong(Jungseong@1), hanguleam@validator:can_be_jongseong(Jongseong@1)} of {true, true, true} -> {ok, do_combine(Choseong, Jungseong@1, Jongseong@1)}; {false, _, _} -> {error, {invalid_choseong, Choseong}}; {_, false, _} -> {error, {invalid_jungseong, Jungseong@1}}; {_, _, false} -> {error, {invalid_jongseong, Jongseong@1}} end. -file("src/hanguleam/composer.gleam", 39). ?DOC( " ⚠️ **UNSAFE**: Combines jamo into Hangul syllable without validation. **WILL PANIC** on invalid inputs.\n" " Use `combine_character()` for safe operation or ensure inputs are pre-validated.\n" ). -spec combine_character_unsafe(binary(), binary(), binary()) -> binary(). combine_character_unsafe(Choseong, Jungseong, Jongseong) -> case combine_character(Choseong, Jungseong, Jongseong) of {ok, Combined} -> Combined; {error, {invalid_choseong, Invalid}} -> Msg = <<<<"Internal error: invalid choseong '"/utf8, Invalid/binary>>/binary, "' from validated syllable"/utf8>>, erlang:error(#{gleam_error => panic, message => Msg, file => <>, module => <<"hanguleam/composer"/utf8>>, function => <<"combine_character_unsafe"/utf8>>, line => 51}); {error, {invalid_jungseong, Invalid@1}} -> Msg@1 = <<<<"Internal error: invalid jungseong '"/utf8, Invalid@1/binary>>/binary, "' from validated syllable"/utf8>>, erlang:error(#{gleam_error => panic, message => Msg@1, file => <>, module => <<"hanguleam/composer"/utf8>>, function => <<"combine_character_unsafe"/utf8>>, line => 58}); {error, {invalid_jongseong, Invalid@2}} -> Msg@2 = <<<<"Internal error: invalid jongseong '"/utf8, Invalid@2/binary>>/binary, "' from validated syllable"/utf8>>, erlang:error(#{gleam_error => panic, message => Msg@2, file => <>, module => <<"hanguleam/composer"/utf8>>, function => <<"combine_character_unsafe"/utf8>>, line => 65}) end. -file("src/hanguleam/composer.gleam", 225). -spec extend_syllable_without_batchim( binary(), binary(), hanguleam@types:jamo() ) -> binary(). extend_syllable_without_batchim(Cho, Jung, Jamo) -> case Jamo of {consonant, Consonant} -> case hanguleam@validator:can_be_jongseong(Consonant) of true -> combine_character_unsafe(Cho, Jung, Consonant); false -> <<(combine_character_unsafe(Cho, Jung, <<""/utf8>>))/binary, Consonant/binary>> end; {vowel, Vowel} -> Extended_vowel = <>, case hanguleam@validator:can_be_jungseong(Extended_vowel) of true -> combine_character_unsafe(Cho, Extended_vowel, <<""/utf8>>); false -> <<(combine_character_unsafe(Cho, Jung, <<""/utf8>>))/binary, Vowel/binary>> end end. -file("src/hanguleam/composer.gleam", 246). -spec extend_syllable_with_batchim( binary(), binary(), binary(), hanguleam@types:jamo() ) -> binary(). extend_syllable_with_batchim(Cho, Jung, Jong, Jamo) -> case Jamo of {vowel, Vowel} -> case hanguleam@validator:can_be_choseong(Jong) of true -> <<(combine_character_unsafe(Cho, Jung, <<""/utf8>>))/binary, (combine_character_unsafe(Jong, Vowel, <<""/utf8>>))/binary>>; false -> <<(combine_character_unsafe(Cho, Jung, Jong))/binary, Vowel/binary>> end; {consonant, Consonant} -> Complex_batchim = <>, case hanguleam@validator:can_be_jongseong(Complex_batchim) of true -> combine_character_unsafe(Cho, Jung, Complex_batchim); false -> <<(combine_character_unsafe(Cho, Jung, Jong))/binary, Consonant/binary>> end end. -file("src/hanguleam/composer.gleam", 270). -spec extend_complex_batchim_syllable( binary(), binary(), binary(), hanguleam@types:jamo() ) -> binary(). extend_complex_batchim_syllable(Cho, Jung, Jong, Jamo) -> case Jamo of {vowel, Vowel} -> Leading_consonant = gleam@string:slice(Jong, 0, 1), Tailing_consonant = gleam@string:slice(Jong, 1, 1), case hanguleam@validator:can_be_choseong(Tailing_consonant) of true -> <<(combine_character_unsafe(Cho, Jung, Leading_consonant))/binary, (combine_character_unsafe( Tailing_consonant, Vowel, <<""/utf8>> ))/binary>>; false -> <<(combine_character_unsafe(Cho, Jung, Jong))/binary, Vowel/binary>> end; {consonant, Consonant} -> <<(combine_character_unsafe(Cho, Jung, Jong))/binary, Consonant/binary>> end. -file("src/hanguleam/composer.gleam", 186). -spec try_extend_syllable( hanguleam@types:hangul_syllable(), hanguleam@types:jamo() ) -> binary(). try_extend_syllable(Syllable, Jamo) -> Parsed = hanguleam@internal@character:parse_hangul_syllable(Syllable), case Parsed of {simple_c_v, {choseong, Cho}, {jungseong, Jung}} -> extend_syllable_without_batchim(Cho, Jung, Jamo); {compound_c_v, {choseong, Cho@1}, {jungseong, Jung@1}} -> extend_syllable_without_batchim(Cho@1, Jung@1, Jamo); {simple_c_v_c, {choseong, Cho@2}, {jungseong, Jung@2}, {jongseong, Jong}} -> extend_syllable_with_batchim(Cho@2, Jung@2, Jong, Jamo); {compound_c_v_c, {choseong, Cho@3}, {jungseong, Jung@3}, {jongseong, Jong@1}} -> extend_syllable_with_batchim(Cho@3, Jung@3, Jong@1, Jamo); {complex_batchim, {choseong, Cho@4}, {jungseong, Jung@4}, {jongseong, Jong@2}} -> extend_complex_batchim_syllable(Cho@4, Jung@4, Jong@2, Jamo); {compound_complex_batchim, {choseong, Cho@5}, {jungseong, Jung@5}, {jongseong, Jong@3}} -> extend_syllable_with_batchim(Cho@5, Jung@5, Jong@3, Jamo) end. -file("src/hanguleam/composer.gleam", 292). -spec try_combine_jamos(hanguleam@types:jamo(), hanguleam@types:jamo()) -> binary(). try_combine_jamos(Last, First) -> case {Last, First} of {{consonant, Consonant}, {vowel, Vowel}} -> case hanguleam@validator:can_be_choseong(Consonant) of true -> combine_character_unsafe(Consonant, Vowel, <<""/utf8>>); false -> <> end; {{vowel, V1}, {vowel, V2}} -> Combined = combine_vowels(V1, V2), case string:length(Combined) =:= 1 of true -> Combined; false -> <> end; {{vowel, V1@1}, {consonant, C1}} -> <>; {{consonant, C1@1}, {consonant, C2}} -> <> end. -file("src/hanguleam/composer.gleam", 312). -spec get_char_type_at( binary(), fun((binary()) -> {ok, binary()} | {error, nil}) ) -> hanguleam@types:character_type(). get_char_type_at(Text, Position) -> _pipe = Text, _pipe@1 = Position(_pipe), _pipe@2 = gleam@result:map( _pipe@1, fun hanguleam@internal@character:get_character_type/1 ), gleam@result:unwrap(_pipe@2, empty). -file("src/hanguleam/composer.gleam", 152). -spec merge_fragments({binary(), hanguleam@types:character_type()}, binary()) -> {binary(), hanguleam@types:character_type()}. merge_fragments(Acc, Fragment) -> {Accumulated, Last_state} = Acc, Next_char_type = get_char_type_at(Fragment, fun gleam@string:first/1), case {Last_state, Next_char_type} of {{incomplete_hangul, Last_jamo}, {incomplete_hangul, First_jamo}} -> Combined = try_combine_jamos(Last_jamo, First_jamo), New_text = <<<<(gleam@string:drop_end(Accumulated, 1))/binary, Combined/binary>>/binary, (gleam@string:drop_start(Fragment, 1))/binary>>, New_state = get_char_type_at(New_text, fun gleam@string:last/1), {New_text, New_state}; {{complete_hangul, Syllable}, {incomplete_hangul, Jamo}} -> Extended = try_extend_syllable(Syllable, Jamo), New_text@1 = <<(gleam@string:drop_end(Accumulated, 1))/binary, Extended/binary>>, New_state@1 = get_char_type_at(Extended, fun gleam@string:last/1), {New_text@1, New_state@1}; {_, _} -> New_text@2 = <>, New_state@2 = get_char_type_at(New_text@2, fun gleam@string:last/1), {New_text@2, New_state@2} end. -file("src/hanguleam/composer.gleam", 146). ?DOC( " Assembles Korean text fragments by intelligently combining characters.\n" " Processes fragments according to Korean linguistic rules including \n" " consonant-vowel combinations and Korean linking (연음).\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " assemble([\"ㄱ\", \"ㅏ\", \"ㅂ\"])\n" " // -> \"갑\"\n" "\n" " assemble([\"안녕하\", \"ㅅ\", \"ㅔ\", \"요\"])\n" " // -> \"안녕하세요\"\n" "\n" " assemble([\"뀽\", \"ㅏ\"])\n" " // -> \"뀨아\"\n" " ```\n" ). -spec assemble(list(binary())) -> binary(). assemble(Fragments) -> _pipe = Fragments, _pipe@1 = gleam@list:fold( _pipe, {<<""/utf8>>, empty}, fun merge_fragments/2 ), gleam@pair:first(_pipe@1).