Packages

A Gleam library for Korean text processing, providing comprehensive tools for handling Hangul characters

Current section

Files

Jump to
hanguleam src hanguleam@composer.erl
Raw

src/hanguleam@composer.erl

-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(
<<Vowel1/binary, Vowel2/binary>>
).
-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 => <<?FILEPATH/utf8>>,
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 => <<?FILEPATH/utf8>>,
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 => <<?FILEPATH/utf8>>,
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 = <<Jung/binary, Vowel/binary>>,
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 = <<Jong/binary, Consonant/binary>>,
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 ->
<<Consonant/binary, Vowel/binary>>
end;
{{vowel, V1}, {vowel, V2}} ->
Combined = combine_vowels(V1, V2),
case string:length(Combined) =:= 1 of
true ->
Combined;
false ->
<<V1/binary, V2/binary>>
end;
{{vowel, V1@1}, {consonant, C1}} ->
<<V1@1/binary, C1/binary>>;
{{consonant, C1@1}, {consonant, C2}} ->
<<C1@1/binary, C2/binary>>
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 = <<Accumulated/binary, Fragment/binary>>,
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).