% \iffalse meta-comment % dtx/mahjong-tiles-04-parser.dtx -- parser for mahjong-tiles % % Copyright (c) 2021 Daniel Schmitz % Copyright (c) 2026 Lilia Chen May Wither % % This file is distributed under the MIT Licence; see LICENCE. % \fi % % \subsection{Parser} % % \begin{macrocode} %<*package> % ----------------------------------------------------------------------------- % Parser % ----------------------------------------------------------------------------- \quark_new:N \q__mahjongtiles_parse_stop \quark_new:N \q__mahjongtiles_parse_done \cs_new_protected:Npn \__mahjongtiles_parse:n #1 { \seq_clear:N \l__mahjongtiles_tiles_seq \seq_clear:N \l__mahjongtiles_pending_seq \bool_set_false:N \l__mahjongtiles_last_concealed_kong_tail_bool \__mahjongtiles_parse_loop:w #1 \q__mahjongtiles_parse_stop ] \q__mahjongtiles_parse_done \seq_if_empty:NF \l__mahjongtiles_pending_seq { \tl_clear:N \l__mahjongtiles_pending_digits_tl \seq_map_inline:Nn \l__mahjongtiles_pending_seq { \__mahjongtiles_collect_pending_digits:nnn ##1 } \msg_error:nnV { mahjong-tiles } { missing-suit } \l__mahjongtiles_pending_digits_tl } } \cs_new_protected:Npn \__mahjongtiles_parse_loop:w #1 { \tl_if_eq:nnTF {#1} { \q__mahjongtiles_parse_stop } { \__mahjongtiles_parse_finish:w } { \token_if_eq_meaning:NNTF #1 \mj { \int_compare:nNnTF { \l__mahjongtiles_mj_depth_int } > {0} { \__mahjongtiles_parse_nested_mj:n } { \__mahjongtiles_parse_inline_mj:n } } { \str_if_eq:nnTF {#1} { [ } { \__mahjongtiles_parse_overlay:w } { \__mahjongtiles_parse_token:n {#1} \__mahjongtiles_parse_loop:w } } } } \cs_new_protected:Npn \__mahjongtiles_parse_nested_mj:n #1 { \msg_error:nnn { mahjong-tiles } { nested-mj } {#1} \__mahjongtiles_parse_loop:w } \cs_new_protected:Npn \__mahjongtiles_parse_inline_mj:n #1 { \seq_if_empty:NF \l__mahjongtiles_pending_seq { \tl_clear:N \l__mahjongtiles_pending_digits_tl \seq_map_inline:Nn \l__mahjongtiles_pending_seq { \__mahjongtiles_collect_pending_digits:nnn ##1 } \msg_error:nnV { mahjong-tiles } { missing-suit } \l__mahjongtiles_pending_digits_tl \seq_clear:N \l__mahjongtiles_pending_seq } \__mahjongtiles_draw_put_from_notation:n {#1} \__mahjongtiles_parse_loop:w } \cs_new_protected:Npn \__mahjongtiles_draw_put_from_notation:n #1 { \bool_gset_false:N \g__mahjongtiles_inline_mj_valid_bool \tl_gclear:N \g__mahjongtiles_inline_mj_face_tl \tl_gclear:N \g__mahjongtiles_inline_mj_suit_tl \group_begin: \int_incr:N \l__mahjongtiles_mj_depth_int \__mahjongtiles_parse:n {#1} \int_compare:nNnTF { \seq_count:N \l__mahjongtiles_tiles_seq } = {1} { \seq_get_left:NN \l__mahjongtiles_tiles_seq \l__mahjongtiles_tmp_tl \exp_last_unbraced:NV \__mahjongtiles_extract_inline_mj_tile:nnnnnn \l__mahjongtiles_tmp_tl {#1} } { \msg_error:nnn { mahjong-tiles } { inline-mj-single-tile } {#1} } \group_end: \bool_if:NT \g__mahjongtiles_inline_mj_valid_bool { \__mahjongtiles_draw_put:VV \g__mahjongtiles_inline_mj_face_tl \g__mahjongtiles_inline_mj_suit_tl } } \cs_new_protected:Npn \__mahjongtiles_extract_inline_mj_tile:nnnnnn #1#2#3#4#5#6 { \str_if_eq:nnTF {#1} { tile } { \tl_gset:Nn \g__mahjongtiles_inline_mj_face_tl {#2} \tl_gset:Nn \g__mahjongtiles_inline_mj_suit_tl {#3} \bool_gset_true:N \g__mahjongtiles_inline_mj_valid_bool } { \msg_error:nnn { mahjong-tiles } { inline-mj-tile-only } {#6} } } \cs_new_protected:Npn \__mahjongtiles_parse_finish:w #1 \q__mahjongtiles_parse_done { } \cs_new_protected:Npn \__mahjongtiles_parse_overlay:w #1 ] { \tl_if_in:nnTF {#1} { \q__mahjongtiles_parse_stop } { \msg_error:nn { mahjong-tiles } { unclosed-overlay } \__mahjongtiles_parse_finish:w } { \__mahjongtiles_set_last_overlay:n {#1} \__mahjongtiles_parse_loop:w } } \cs_new_protected:Npn \__mahjongtiles_collect_pending_digits:nnn #1#2#3 { \tl_put_right:Nn \l__mahjongtiles_pending_digits_tl {#1} } \cs_new_protected:Npn \__mahjongtiles_parse_token:n #1 { \tl_if_blank:nTF {#1} { } { \tl_set:Nx \l__mahjongtiles_tmp_tl { \text_lowercase:n {#1} } \exp_args:NV \__mahjongtiles_parse_normalized_token:n \l__mahjongtiles_tmp_tl } } \cs_new_protected:Npn \__mahjongtiles_parse_normalized_token:n #1 { \tl_if_in:nnTF { 0123456789 } {#1} { \__mahjongtiles_pending_put:nn {#1} {0} } { \tl_if_in:nnTF { mpszf } {#1} { \__mahjongtiles_flush_suit:n {#1} } { \str_case:nnF {#1} { { - } { \__mahjongtiles_handle_dash: } { x } { \__mahjongtiles_tile_put:nnn { x } { } {0} } { ? } { \__mahjongtiles_tile_put:nnn { ? } { } {0} } { ' } { \__mahjongtiles_set_last_orientation:n {1} } { * } { \__mahjongtiles_set_last_orientation:n {1} } { " } { \__mahjongtiles_set_last_stack_marker:n { " } } { + } { \__mahjongtiles_set_last_stack_marker:n { + } } } { \msg_error:nnn { mahjong-tiles } { invalid-token } {#1} } } } } \cs_new_protected:Npn \__mahjongtiles_handle_dash: { \seq_if_empty:NTF \l__mahjongtiles_pending_seq { \__mahjongtiles_gap_put:n {7} } { \tl_clear:N \l__mahjongtiles_pending_digits_tl \seq_map_inline:Nn \l__mahjongtiles_pending_seq { \__mahjongtiles_collect_pending_digits:nnn ##1 } \__mahjongtiles_gap_put:V \l__mahjongtiles_pending_digits_tl \seq_clear:N \l__mahjongtiles_pending_seq } } \cs_generate_variant:Nn \__mahjongtiles_gap_put:n { V } \cs_new_protected:Npn \__mahjongtiles_flush_suit:n #1 { \seq_if_empty:NF \l__mahjongtiles_pending_seq { \__mahjongtiles_detect_concealed_kong: \bool_if:NTF \l__mahjongtiles_concealed_kong_bool { \tl_if_in:nnTF { mpsz } {#1} { \__mahjongtiles_flush_concealed_kong:n {#1} } { \__mahjongtiles_flush_pending_suit:n {#1} } } { \__mahjongtiles_flush_pending_suit:n {#1} } \seq_clear:N \l__mahjongtiles_pending_seq } } \cs_new_protected:Npn \__mahjongtiles_flush_pending_suit:n #1 { \seq_map_inline:Nn \l__mahjongtiles_pending_seq { \__mahjongtiles_flush_pending_item:nnnn ##1 {#1} } } \cs_new_protected:Npn \__mahjongtiles_flush_pending_item:nnnn #1#2#3#4 { \str_if_eq:nnTF {#4} {f} { \__mahjongtiles_flush_flower_item:nnn {#1} {#2} {#3} } { \str_if_eq:nnTF {#1} {0} { \tl_if_in:nnTF { mps } {#4} { \int_case:nnF {#2} { {3} { \__mahjongtiles_akastack_put:nnn {#4} {1} {#3} } {4} { \__mahjongtiles_akastack_put:nnn {#4} {2} {#3} } {5} { \__mahjongtiles_akastack_put:nnn {#4} {3} {#3} } } { \__mahjongtiles_tile_put_aka_aware:nnnn {#1} {#4} {#2} {#3} } } { \__mahjongtiles_tile_put_aka_aware:nnnn {#1} {#4} {#2} {#3} } } { \__mahjongtiles_tile_put_aka_aware:nnnn {#1} {#4} {#2} {#3} } } } \cs_new_protected:Npn \__mahjongtiles_flush_flower_item:nnn #1#2#3 { \tl_if_in:nnTF { 012345678 } {#1} { \__mahjongtiles_tile_put_aka_aware:nnnn {#1} {f} {#2} {#3} } { \msg_error:nnn { mahjong-tiles } { invalid-flower } {#1} } } \cs_new_protected:Npn \__mahjongtiles_detect_concealed_kong: { \bool_set_false:N \l__mahjongtiles_concealed_kong_bool \int_compare:nNnT { \seq_count:N \l__mahjongtiles_pending_seq } = {5} { \seq_get_left:NN \l__mahjongtiles_pending_seq \l__mahjongtiles_tmp_tl \exp_last_unbraced:NV \__mahjongtiles_remember_first_pending:nnn \l__mahjongtiles_tmp_tl \bool_set_true:N \l__mahjongtiles_concealed_kong_bool \seq_map_inline:Nn \l__mahjongtiles_pending_seq { \__mahjongtiles_check_concealed_kong_item:nnn ##1 } } } \cs_new_protected:Npn \__mahjongtiles_remember_first_pending:nnn #1#2#3 { \tl_set:Nn \l__mahjongtiles_first_face_tl {#1} } \cs_new_protected:Npn \__mahjongtiles_check_concealed_kong_item:nnn #1#2#3 { \str_if_eq:VnF \l__mahjongtiles_first_face_tl {#1} { \bool_set_false:N \l__mahjongtiles_concealed_kong_bool } \int_compare:nNnF {#2} = {0} { \bool_set_false:N \l__mahjongtiles_concealed_kong_bool } } \cs_new_protected:Npn \__mahjongtiles_remember_pending_overlay:nnn #1#2#3 { \tl_if_blank:nF {#3} { \tl_set:Nn \l__mahjongtiles_tmpb_tl {#3} } } \cs_new_protected:Npn \__mahjongtiles_put_aka_concealed_five_pair:n #1 { \int_compare:nNnTF { \g__mahjongtiles_aka_int } = {0} { \__mahjongtiles_tile_put:nnn {5} {#1} {0} \__mahjongtiles_tile_put:nnn {5} {#1} {0} } { \str_if_eq:nnTF {#1} {p} { \int_compare:nNnTF { \g__mahjongtiles_aka_int } = {2} { \__mahjongtiles_tile_put:nnn {0} {p} {0} \__mahjongtiles_tile_put:nnn {0} {p} {0} } { \__mahjongtiles_tile_put:nnn {0} {p} {0} \__mahjongtiles_tile_put:nnn {5} {p} {0} } } { \__mahjongtiles_tile_put:nnn {0} {#1} {0} \__mahjongtiles_tile_put:nnn {5} {#1} {0} } } } \cs_new_protected:Npn \__mahjongtiles_flush_concealed_kong:n #1 { % Five repeated digits followed by a suit are rendered as a concealed kong: % back, two visible tiles, back. The aka setting controls the visible pair % for 55555m/p/s: aka=0 uses two regular fives, aka=1 uses red plus regular, % and aka=2 gives 55555p an extra red 5p. \tl_clear:N \l__mahjongtiles_tmpb_tl \seq_map_inline:Nn \l__mahjongtiles_pending_seq { \__mahjongtiles_remember_pending_overlay:nnn ##1 } \__mahjongtiles_tile_put:nnn { x } { } {0} \str_if_eq:VnTF \l__mahjongtiles_first_face_tl {5} { \tl_if_in:nnTF { mps } {#1} { \__mahjongtiles_put_aka_concealed_five_pair:n {#1} } { \__mahjongtiles_tile_put:nnn {5} {#1} {0} \__mahjongtiles_tile_put:nnn {5} {#1} {0} } } { \__mahjongtiles_tile_put_aka_aware:Vnn \l__mahjongtiles_first_face_tl {#1} {0} \__mahjongtiles_tile_put_aka_aware:Vnn \l__mahjongtiles_first_face_tl {#1} {0} } \__mahjongtiles_tile_put:nnn { x } { } {0} \bool_set_true:N \l__mahjongtiles_last_concealed_kong_tail_bool \tl_if_blank:VF \l__mahjongtiles_tmpb_tl { \__mahjongtiles_output_set_last_overlay:V \l__mahjongtiles_tmpb_tl } } % % \end{macrocode} \endinput