% !TeX encoding = UTF-8 % Ce fichier contient le code de l'extension "tokstools" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % \def\tktlname {tokstools} % \def\tktlver {0.1} % % % \def\tktldate {2026/03/29} % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % Author : Christian Tellechea % % Status : Maintained % % Email : unbonpetit@netc.fr % % Package URL: https://www.ctan.org/pkg/tokstools % % Copyright : Christian Tellechea 2026 % % Licence : Released under the LaTeX Project Public License v1.3c % % or later, see http://www.latex-project.org/lppl.txt % % Files : 1) tokstools.tex (this file) % % 2) tokstools.sty (sty file for latex use) % % 3) tokstools-fr.tex (source of manual in french) % % 4) tokstools-fr.pdf (pdf of manual in french) % % 5) tokstools-en.tex (source of manual in english) % % 6) tokstools-en.pdf (pdf of manual in english) % % 7) README.md % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %--------------------------------------------------------------------- %---------------- Annonce package et autres pré-requis --------------- %--------------------------------------------------------------------- \csname tktl_load_once\endcsname \expandafter\let\csname tktl_load_once\endcsname\endinput \ifdefined\tktlstyfile \else \immediate\write-1 {Package: \tktlname\space\tktldate\space\space v\tktlver\space\space Tools for tokens (CT)}% \fi \expandafter\edef\csname tktl_restore_catcode\endcsname{\catcode\number`\_=\number\catcode`\_\relax} \catcode`\_11 % Macros de manipulation d'arguments \long\def\tktl_exec_first#1#2{#1} \long\def\tktl_exec_second#1#2{#2} \long\def\tktl_gob_arg#1{} \long\def\tktl_id#1{#1} \long\def\tktl_firsttonil#1#2\_nil{#1} \def\tktl_def_tok#1#2{\let#1= #2\relax} \tktl_def_tok\tktl_sp_token{ } \def\tktl_unexpand_earg#1#{% \tktl_unexpand_earg_a{#1}% } \def\tktl_unexpand_earg_a#1#2{% #1=instructions #2=arg -> #1{*#2} \unexpanded{#1}{\unexpanded\expandafter{#2}}% } \def\tktl_expand_n_times#1#2#3{% développe #1 fois l'argument #3 de la _macro_ #2 : #2{*^n#3} \tktl_ifnum{#1>0 } {\expandafter\tktl_expand_n_times\expandafter{\the\numexpr#1-1\expandafter}\expandafter#2\expandafter{#3}} {#2{#3}}% } \long\def\tktl_antefi#1#2\fi{#2\fi#1} \def\tktl_ifnxttok#1#2#3{% \tktl_def_tok\tktl_toksmatch{#1}% \def\tktl__truecode{#2}% \def\tktl__falsecode{#3}% \tktl_ifnxttok_a } \def\tktl_ifnxttok_a{\futurelet\tktl__futurtok\tktl_ifnxttok_b} \def\tktl_ifnxttok_b{% \tktl_ifx{\tktl__futurtok\tktl_sp_token} {% \afterassignment\tktl_ifnxttok_a \let\tktl__futurtok= } {% \tktl_ifx{\tktl__futurtok\tktl_toksmatch} \tktl__truecode \tktl__falsecode }% } \def\tktl_futurelet_nospace#1#2{% \def\tktl_futurelet_nospace_a{\futurelet#1\tktl_futurelet_nospace_b}% \def\tktl_futurelet_nospace_b{% \tktl_ifx{\tktl__futurtok\tktl_sp_token} {% \afterassignment\tktl_futurelet_nospace_a \let#1= } {% #2% }% }% \tktl_futurelet_nospace_a } % Quarks \def\active_quark{\active_quark} \def\end_parse{\end_parse} \def\tktl_gob_enctoks_to_end_parse#1\end_parse{} % Retrait d'espaces \def\tktl_temp#1{% \long\def\tktl_stripsp##1{\expanded{\tktl_stripsp_i\_marksp##1\__nil\_marksp#1\_marksp\_nil}}% \long\def\tktl_stripsp_i##1\_marksp#1##2\_marksp##3\_nil{\tktl_stripsp_ii##3##1##2\__nil#1\__nil\_nil}% \long\def\tktl_stripsp_ii##1#1\__nil##2\_nil{\tktl_stripsp_iii##1##2\_nil}% \long\def\tktl_stripsp_iii##1##2\__nil##3\_nil{\unexpanded{##2}}% }\tktl_temp{ } \long\def\tktl_earg#1#2{\expandafter\tktl_earg_i\expandafter{#2}{#1}} \long\def\tktl_eearg#1#2{\expandafter\expandafter\expandafter\tktl_earg_i\expandafter\expandafter\expandafter{#2}{#1}} \long\def\tktl_earg_i#1#2{#2{#1}} \long\def\tktl_etwoargs#1#2#3{\tktl_earg{\tktl_earg{#1}{#2}}{#3}} \long\def\tktl_addtomacro#1#2{\expandafter\def\expandafter#1\expandafter{#1#2}} \long\def\tktl_eaddtomacro#1#2{\expandafter\tktl_addtomacro\expandafter#1\expandafter{#2}} \def\tktl_addtotoks#1#2{#1\expandafter{\the#1#2}} \def\tktl_eaddtotoks#1#2{\expandafter\tktl_addtotoks\expandafter#1\expandafter{#2}} % Macros de test \long\def\tktl_ifempty_or_space#1{\tktl_ifempty_or_space_i#1\_nil\_nil\tktl_exec_second\tktl_exec_first\__nil}% \long\def\tktl_ifempty_or_space_i#1#2\_nil#3#4#5\__nil{#4} \def\tktl_ifx#1{\ifx#1\expandafter\tktl_exec_first\else\expandafter\tktl_exec_second\fi} \def\tktl_ifnum#1{\ifnum#1\expandafter\tktl_exec_first\else\expandafter\tktl_exec_second\fi} \long\def\tktl_ifcat#1{\ifcat#1\expandafter\tktl_exec_first\else\expandafter\tktl_exec_second\fi} \long\def\tktl_ifinstr#1#2{% la chaine #1 est-elle dans #2 ? \long\def\tktl_ifinstr_i##1#1##2\tktl_ifinstr_i{\ifcat\relax\detokenize{##2}\relax\expandafter\tktl_exec_second\else\expandafter\tktl_exec_first\fi}% \tktl_ifinstr_i#2#1\tktl_ifinstr_i } % Macros auxiliaires \def\tktl_addto_special_list#1{\tktl_earg{\def\tktl_list_specials}{\tktl_list_specials#1,}} \let\tktl_bg_token{ \let\tktl_eg_token} \def\tktl_ifnumcase_endif{\tktl_ifnumcase_endif} \def\tktl_ifnumcase_elseif{\tktl_ifnumcase_elseif} \def\tktl_ifnumcase#1#2{% #1= #2=prochain argument \tktl_ifx{\tktl_ifnumcase_elseif#2}% {% \tktl_idto_endif } {\tktl_ifx{\tktl_ifnumcase_endif#2}% {} {% \tktl_ifnum{#1=#2 } {\tktl_firstto_endif} {\tktl_ifnumcase_i{#1}}% }% }% } \def\tktl_ifnumcase_i#1#2{\tktl_ifnumcase{#1}} \def\tktl_idto_endif#1\tktl_ifnumcase_endif{#1} \long\def\tktl_firstto_endif#1#2\tktl_ifnumcase_endif{#1} %--------------------------------------------------------------------- %---------------- Macro \tktl_encode_tokens_char_range --------------- %--------------------------------------------------------------------- % \tktl_encode_tokens_char_range{} % % analyse le et renvoie dans les 2 macros les charcodes % min et max correspondant à , sachant que est de la forme : % . - et sont 2 caractères entourés d'éventuels espaces % . un caractère unique entouré d'éventuels d'espaces % . "-" compris comme le caractère unique "-" % . " " compris comme le caractère unique de charcode 32 % . - ou - ou - : les donnent 32 % % Retour : %* * et qui seront égales pour un caractère unique % ou pour - % * un \errmessage est renvoyé si % . est vide % . ou est, après retrait d'espces avant/après, constitué de plusieurs caractères % . si le charcode de est supérieur à celui de %--------------------------------------------------------------------- \def\tktl_char_range_sep{-} \def\tktl_encode_tokens_char_range#1#2#3{% #1=intervalle de la forme '-' ou '' #2=charcode min #3=charcodemax \def\tktl_current_char_range{#1}% \ifx\tktl_current_char_range\empty \errmessage{Empty char range, '0' inserted}% \edef#2{\number`\0}% \let#3#2% \else \ifx\tktl_current_char_range\space \def#2{32}% \let#3#2% \else \tktl_ifinstr-{#1} {% \ifx\tktl_current_char_range\tktl_char_range_sep% si "-" seul, le prendre comme char \tktl_encode_tokens_char_range_b{-}#2% \let#3#2% \else \tktl_encode_tokens_char_range_a#1\_nil#2#3% \fi } {% \tktl_encode_tokens_char_range_b{#1}#2% \let#3#2% }% \fi \fi } \def\tktl_encode_tokens_char_range_a#1-#2\_nil#3#4{% \def\tktl_current_lo_range{#1}% \def\tktl_current_hi_range{#2}% \ifnum0\ifx\tktl_current_lo_range\empty1\fi\ifx\tktl_current_hi_range\empty1\fi>0 \errmessage{Missing bound in char range \detokenize{#1-#2}, "0-0" inserted}% \def\tktl_current_lo_range{0}% \def\tktl_current_hi_range{0}% \fi \ifx\tktl_current_lo_range\space \def#3{32}% \ifx\tktl_current_hi_range\space \def#4{32}% \else \tktl_encode_tokens_char_range_b{#2}#4% \fi \else \tktl_encode_tokens_char_range_b{#1}#3% \ifx\tktl_current_hi_range\space \def#4{32}% \else \tktl_encode_tokens_char_range_b{#2}#4% \fi \fi \ifnum#4<#3 \errmessage{Unsorted interval '\detokenize{#1-#2}', '\detokenize{#2-#1}' inserted}% \let\tktl__tmp#4% \let#4#3% \let#3\tktl__tmp \fi } \def\tktl_encode_tokens_char_range_b#1#2{% #1=char non espace #2=macro recevant le charcode \tktl_eearg{\def#2}{\tktl_stripsp{#1}}% \tktl_eearg\tktl_ifempty_or_space{\expandafter\tktl_gob_arg#2} {% \edef#2{\number\expandafter`\csname#2\endcsname}% } {% \errmessage{Multiple token '\detokenize{#1}', '0' inserted}% \edef#2{\number`\0}% }% } \let\charrange\tktl_encode_tokens_char_range %--------------------------------------------------------------------- %----------------- Macro \tktl_encode_tokensinterval ----------------- %--------------------------------------------------------------------- % \tktl_encode_tokensinterval admet un argument et % retourne dans 2 macros (arg #2 et #3) le minimum et maximum % correspondant à l'intervalle % = "n" ou "n-m" avec n et m entiers positifs % éventuellement vides % Utilisation : % \tktl_encode_tokensinterval{#1}\macroA\macroB % - si #1 vide, \tktl_intervalmin=\tktl_intervalmax=0 (\errmessage) % - #1 est de la forme % . x ou {x} % si x non entier \tktl_intervalmin=\tktl_intervalmax=0 (\errmessage) % si x<0, \tktl_intervalmin=\tktl_intervalmax=-x (\errmessage) % sinon \tktl_intervalmin=\tktl_intervalmax=x % . x-y ou {x}-{y}, x et y entiers % si les 2 vides, \tktl_intervalmin=\tktl_intervalmax=1 (\errmessage) % si entier négatif, on prend l'opposé % si x non entier ou vide \tktl_intervalmin=0 (\errmessage) % si y non entier ou vide \tktl_intervalmax=maxint (\errmessage) % si x>y, \tktl_intervalmin=y et \tktl_intervalmax=x (\errmessage) % sinon, \tktl_intervalmin=x et \tktl_intervalmax=y % Retour : % macros \macroA (borne inf) et \macroB (borne sup) %--------------------------------------------------------------------- \def\test_iftexnumber#1{\test_iftexnumber_i#1`\_nil} \def\test_iftexnumber_i#1`#2\_nil{\tktl_ifempty_or_space{#1}}% \edef\tktl_maxint_repeat{1114109}%{\number\dimexpr\maxdimen} \newcount\tktl_test_cnt \newif\iftktl_error_ \def\tktl_ifinteger#1{% \tktl_ifempty_or_space{#1} {% \tktl_exec_second } {% \afterassignment\tktl_ifinteger_i \tktl_test_cnt\numexpr\test_iftexnumber{#1}{}0#1\relax\relax\_nil }% } \def\tktl_ifinteger_i#1\relax\_nil{\tktl_ifempty_or_space{#1}} \def\tktl_encode_tokensinterval#1#2#3{% #1 est de la forme "n" ou "min-max" #2=macro recevant min #3=macro recevant max \tktl_error_false \tktl_eearg{\def#2}{\tktl_stripsp{#1}}% \let\tktl_interval_input#2% \ifx#2\empty \tktl_error_true% \def#2{0}% \let#3#2% \else \tktl_ifinstr-{#1} {% \tktl_encode_tokensinterval_i#1\_nil#2#3% } {\tktl_earg\tktl_ifinteger#2% {% \edef#2{\number\numexpr#2\relax}% }% {% \tktl_error_true \def#2{0}% }% \let#3#2% }% \fi } \def\tktl_encode_tokensinterval_i#1-#2\_nil{% \tktl_eearg{\tktl_eearg\tktl_encode_tokensinterval_ii{\tktl_stripsp{#1}}}{\tktl_stripsp{#2}}% } \def\tktl_encode_tokensinterval_ii#1#2#3#4{% \tktl_ifempty_or_space{#1#2} {% \def#3{1}% \def#4{1}% \tktl_error_true } {% \def#3{#1}\def#4{#2}% \ifx\empty#3% \def#3{0}% \fi \ifx\empty#4% \let#4\tktl_maxint_repeat \fi \tktl_earg\tktl_ifinteger#3% {% \edef#3{\number\numexpr#3\relax}% } {% \tktl_error_true \def#3{0}% }% \tktl_earg\tktl_ifinteger#4% {% \edef#4{\number\numexpr#4\relax}% } {% \tktl_error_true \let#4\tktl_maxint_repeat }% \ifnum#3>#4 \tktl_error_true \let\__#4% \let#4#3% \let#3\__ \fi }% \iftktl_error_ \errmessage{"\detokenize\expandafter{\tktl_interval_input}" invalid: "#3-#4" retained}% \fi } %--------------------------------------------------------------------- %--------------------- Macro \tktl_test_interval --------------------- %--------------------------------------------------------------------- % Utilisation : % \tktl_test_interval{#1}{#2}{true}{false} % - #1 _doit_ être un entier % - #2 est une liste i1,i2,...in où les éléments sont des intervalles de % de la forme % . * (wildcard) qui est toute valeur entière, renvoie true et bypasse les autres intervalles % . intervalle au sens de \tktl_encode_tokensinterval (forme "x" ou "x-y") % chaque intervalle est 1-développé avant d'etre lu % Retour : % true si #1 appartient à l'un des intervalles, false sinon %--------------------------------------------------------------------- \def\tktl_test_interval#1#2{% teste si l'entier #1 est dans la liste d'intervalles #2 de type "i..j" ou entier seul "i" \tktl_ifinteger{#1} {} {\errmessage{"\detokenize{#1}" is not an integer, this error should not occur}}% \edef\tktl_int{\number\numexpr#1\relax}% \tktl_test_interval_i#2,\end_parse,% } \def\tktl_star{*}% \def\tktl_test_interval_i#1,{% \tktl_ifx{\end_parse#1} {% \tktl_exec_second } {% \tktl_eearg{\def\tedt_currentinterval}{\tktl_stripsp{#1}}% \tktl_ifx{\tedt_currentinterval\tktl_star} {% \tktl_test_interval_gobtoend } {% \tktl_encode_tokensinterval{#1}\__min\__max \tktl_ifnum{\numexpr(\tktl_int-\__min)*(\tktl_int-\__max)>0 }% si n'appartient pas à [min ; max] \tktl_test_interval_i \tktl_test_interval_gobtoend }% }% } \def\tktl_test_interval_gobtoend#1\end_parse,{\tktl_exec_first} %--------------------------------------------------------------------- %--------------------- Macro \tktl_encode_tokens --------------------- %--------------------------------------------------------------------- % Utilisation : % \tktl_encode_tokens{#1} où #1 est un texte équilibré en tokens de catcodes 1 et 2 % % Retours : % 1) le registre de tokens \tktl_enctoks_toks contient la liste des tokens % constituant #1 sous la forme % (:) où: % - x est le charcode du token ou le token lui-même si séquence de contrôle % - y est le catcode du token ou 16 si séquence de contrôle % 2) le compteur \tktl_enctoks_len_cnt contient le nombre de tokens %--------------------------------------------------------------------- \newtoks\tktl_enctoks_toks \newcount\tktl_enctoks_len_cnt \long\def\tktl_encode_tokens#1{% \tktl_enctoks_len_cnt 0 \let\tktl_list_specials\empty \let\tktl_do_with_specials\tktl_scan_special \let\tktl_do_with_normals\tktl_gob_tok_and_loop % Première passe % tous charcodes des tokens spéciaux (catcode 1, 2, 6 et 10) sont mis dans \tktl_list_specials. % Les autres tokens (les "normaux") sont ignorés \tktl_encode_tokens_encode#1\end_parse % Avant la 2e passe : % On cherche à écarter les caractères actifs qui ont été lus comme tokens % spéciaux (car ils sont \let-égaux à un token spécial) % Pour ce faire, on modifie via \uccode et \uppercase tous les tokens contenus dans \tktl_list_specials. % Seuls les caractères actifs seront ainsi modifiés, ils sont neutralisés et rendus égaux à % \active_quark, ce qui leur enlèvera leur statut de spécial \tktl_active_specials % Deuxième passe % les caractères actifs ayant été reprogrammés, seuls les charcodes des _vrais_ % tokens spéciaux seront mis dans \tktl_list_specials \let\tktl_list_specials\empty \tktl_encode_tokens_encode#1\end_parse % Troisième passe \tktl_enctoks_toks{}% \let\tktl_do_with_specials\tktl_encode_specials \let\tktl_do_with_normals\tktl_encode_normal \tktl_encode_tokens_encode#1\end_parse } \long\def\tktl_encode_tokens_encode{% \futurelet\tktl_nxt_tok\tktl_encode_tokens_encode_i } \def\tktl_encode_tokens_encode_i{% \let\tktl_do_next\tktl_do_with_normals% a priori, token normal \ifx\tktl_nxt_tok\end_parse\let\tktl_do_next\tktl_encode_gob_to_end\fi% fin de parsing ? % les tokens "#", " ", "{" et "}" sont considérés spéciaux \ifcat##\noexpand\tktl_nxt_tok \let\tktl_do_next\tktl_do_with_specials \def\tok_catcode{6}% \else \ifcat\tktl_sp_token\noexpand\tktl_nxt_tok \let\tktl_do_next\tktl_do_with_specials \def\tok_catcode{10}% \else \ifcat\tktl_bg_token\noexpand\tktl_nxt_tok \let\tktl_do_next\tktl_do_with_specials \def\tok_catcode{1}% \else \ifcat\tktl_eg_token\noexpand\tktl_nxt_tok \let\tktl_do_next\tktl_do_with_specials \def\tok_catcode{2}% \fi\fi\fi\fi \tktl_do_next } \def\tktl_encode_gob_to_end\end_parse{}% plus sûr que "\let\tktl_encode_gob_to_end\tktl_gob_arg" \def\tktl_scan_special{% \begingroup \escapechar\if\tktl_nxt_tok\string @`A\else`@\fi\relax% choisir le bon caractère d'échappement \expandafter\tktl_scan_special_i\string% pour le token qui suit. Si c'est une macro, seuls les espaces dans son nom seront comptabilisés en spéciaux } \def\tktl_scan_special_i{% \futurelet\tktl_nxt_tok\tktl_scan_special_ii } \def\tktl_scan_special_ii{% \tktl_ifcat{\tktl_nxt_tok\tktl_sp_token} {% \endgroup \tktl_addto_special_list{32}% tous les catcode 10 ont un charcode de 32 \tktl_gob_tok_and_loop } {% \tktl_scan_special_iii }% } \long\def\tktl_scan_special_iii#1{% traite les autres cas spéciaux \tktl_earg{% \endgroup \tktl_addto_special_list}{\number\tktl_ifnum{\escapechar=`#1 }{-1}{`#1} }% si macro : -1 (et eventuellement des 32 si son nom contient des espaces) \tktl_encode_tokens_encode } \def\tktl_active_specials{% \expandafter\tktl_active_specials_i\tktl_list_specials\active_quark,% } \begingroup \catcode0 13 \gdef\tktl_active_specials_i#1,{% \tktl_ifx{\active_quark#1} {} {% \tktl_ifnum{#1<0 } {} {% \uccode0 #1 \uppercase{\let^^00\active_quark}% }% \tktl_active_specials_i }% } \endgroup \def\tktl_gob_tok_and_loop{% \afterassignment\tktl_encode_tokens_encode \let\tktl_nxt_tok= } \def\tktl_read_special_list{% \expandafter\tktl_read_special_list_i\tktl_list_specials\tktl_read_special_list } \def\tktl_read_special_list_i#1,#2\tktl_read_special_list{% \def\tktl_current_charcode{#1}% \def\tktl_list_specials{#2}% } \def\tktl_encode_specials{% \tktl_read_special_list \tktl_ifnum{\tktl_current_charcode=-1 } {% \tktl_encode_macro } {% \tktl_earg{\tktl_earg\tktl_add_token{\tktl_current_charcode}}{\tok_catcode}% \tktl_gob_tok_and_loop }% } \long\def\tktl_encode_macro#1{% \tktl_clean_special_list#1% enlève les "32" de la liste qui correspondent aux espaces dans les noms des macros \tktl_add_token{#1}{16}% \tktl_encode_tokens_encode } \def\tktl_clean_special_list#1{% \expandafter\tktl_clean_special_list_i\string#1 \end_parse } \def\tktl_clean_special_list_i#1 {% \futurelet\tktl_nxt_tok\tktl_clean_special_list_ii } \def\tktl_clean_special_list_ii{% \tktl_ifx{\tktl_nxt_tok\end_parse} {% \tktl_encode_gob_to_end } {% \tktl_read_special_list \tktl_clean_special_list_i }% } \long\def\tktl_encode_normal#1{% \tktl_ifcat{\relax\noexpand#1} {% \tktl_add_token{#1}{16}% } {% \tktl_earg{\tktl_earg\tktl_add_token{\number`#1}}{\number\tktl_thecatcode#1 }% }% \tktl_encode_tokens_encode } \long\def\tktl_add_token#1#2{% \advance\tktl_enctoks_len_cnt1 \tktl_add_to_parsetoks{(#1:#2)}% } % La macro \tktl_thecatcode{} est purement développable % et renvoit le catcode de \begingroup \edef\tktl_dotcatcode{\catcode\number`\.=} \tktl_dotcatcode3 \gdef\tktl_cat_C{.} \tktl_dotcatcode4 \gdef\tktl_cat_D{.} \tktl_dotcatcode7 \gdef\tktl_cat_G{.} \tktl_dotcatcode8 \gdef\tktl_cat_H{.} \tktl_dotcatcode11 \gdef\tktl_cat_K{.} \tktl_dotcatcode12 \gdef\tktl_cat_L{.} \tktl_dotcatcode13 \gdef\tktl_cat_M{.} \endgroup \def\tktl_thecatcode#1{% n'envisager les tokens non spéciaux (qui eux, ont un catcode de 1, 2, 6 ou 10) \ifcat\tktl_cat_K\noexpand#111\else \ifcat\tktl_cat_L\noexpand#112\else \ifcat\tktl_cat_C\noexpand#13\else \ifcat\tktl_cat_D\noexpand#14\else \ifcat\tktl_cat_G\noexpand#17\else \ifcat\tktl_cat_H\noexpand#18\else \ifcat\expandafter\noexpand\tktl_cat_M\noexpand#113\else \errmessage{This error in \string\tktl_thecatcode\space should not occur with token "\detokenize{#1}"}% \fi\fi\fi\fi\fi\fi\fi } %--------------------------------------------------------------------- %--------------------- Macro \tktl_decode_enctoks -------------------- %--------------------------------------------------------------------- % Utilisation : % \tktl_decode_enctoks{#1}{#2} où #1 est une liste de tokens codés sous la forme et #2 une consigne d'assignation : #2{} % (:) où: % - x est le charcode du token ou le token lui-même si séquence de contrôle % - y est le catcode du token ou 0 si séquence de contrôle % Retour : % L'assignation demandée via #2 contient les tokens % Si les tokens de catcode 1 et 2 ne sont pas équilibrés dans #1 -> erreur %--------------------------------------------------------------------- \newcount\tktl_openbrace_cnt \long\def\tktl_decode_enctoks#1#2{% #1=(a:b)(c:d)...(x:y) #2=assignation du résultat \begingroup \global\tktl_openbrace_cnt0 \tktl_enctoks_toks{}% \tktl_decode_enctoks_a#1(-1:-1)% \tktl_earg{% \endgroup #2}{\unexpanded\expandafter{\the\tktl_enctoks_toks}}% } \long\def\tktl_decode_enctoks_a(#1){% \tktl_decode_enctoks_b(#1:0:)% } \long\def\tktl_decode_enctoks_b(#1:#2:#3){% #1=charcode ou macro si #2=0 ; #2=catcode ; #3=reliquat \tktl_ifnumcase{#2} {-1}{% \tktl_ifnum{\tktl_openbrace_cnt>0 } {% trop d'accolades ouvrantes \errmessage{Unbalanced open-group token, \the\tktl_openbrace_cnt\space close-group token added}% \expandafter\tktl_decode_enctoks_c } {}% \tktl_gob_arg } {16}{% \tktl_add_to_parsetoks{#1}% } {1} {% \global\advance\tktl_openbrace_cnt1 \begingroup \tktl_enctoks_toks{}% \lccode`\{ #1 } {2} {% \tktl_ifnum{\tktl_openbrace_cnt<1 } {% \errmessage{Unbalanced close-group token ignored}% } {% \global\advance\tktl_openbrace_cnt-1 \lccode`\} #1 \lowercase{\toks0{\expandafter{\the\tktl_enctoks_toks}}}% \tktl_enctoks_toks\expandafter\expandafter\expandafter{\the\toks0}% \expandafter \endgroup \expandafter\tktl_add_to_parsetoks\expandafter{\the\tktl_enctoks_toks}% }% } {6} {% \begingroup \lccode`\# #1 \lowercase{% \endgroup \tktl_add_to_parsetoks{##}}% }% \tktl_ifnumcase_elseif \begingroup \endlinechar-1 \everyeof{\noexpand}% \scantokens{% \catcode`\* #2 \lccode `\* #1 \catcode`\_ 11 \lowercase{% \endgroup \tktl_add_to_parsetoks{*}% }%* }% \tktl_ifnumcase_endif \tktl_decode_enctoks_a } \def\tktl_decode_enctoks_c{% ajoute accolade(s) fermante(s) manquantes à la fin du décodage si besoin \global\advance\tktl_openbrace_cnt-1 \lowercase\expandafter{\expandafter \endgroup \expandafter\tktl_add_to_parsetoks\expandafter{\expandafter{\the\tktl_enctoks_toks}}}% \ifnum\tktl_openbrace_cnt>0 \expandafter\tktl_decode_enctoks_c \fi } \long\def\tktl_add_to_parsetoks#1{\tktl_enctoks_toks\expandafter{\the\tktl_enctoks_toks#1}} %--------------------------------------------------------------------- %------------------ Capture d'un ----------------- %--------------------------------------------------------------------- % Entrée : le motif est composé de : % - optionnelle indiquée par \c % - optionnel : "!" ou "&" % - macro ayant 1 caractère qui spécifie le type de motif : % \S{} : le prochain token matche avec un des tokens de la % \s{} : les prochains tokens sont les mêmes que ceux de la % \R{csv de charcodes range "-" ou "" ou "*":csv de catcodes "-" ou "" ou "*"} et si csv de catcodes absent: "*". Macros interdites dans les ranges. % \r{csv de chars range "-" ou "":csv de catcodes "-" ou "" ou "*"} et si csv de catcodes absent: "*". Macros interdites dans les ranges. % \. : correspond avec tout token (équiv à \R{*:*}) % {} groupe de motifs (correspond à la macro interne \tktl_patterngroup) % - nombre de correspondance requis (optionnel): % + : 1 ou + , équivalant à ^{1-} % * : 0 ou + , équivalant à ^{0-} % ? : 0 ou 1 , équivalant à ^{0-1} % ^{n} ou ^{n-m} : de n (0 si absent) à m (max si absent) inlcus % si omis : ^{1} % - optionnelle : \c qui capturera seulement la position % % Renvoie : % - \tktl_current_precapture_directive est la capture demandée 0(aucune), 1(pos), 2(match pattern), 3(pos et match pattern) % - \tktl_current_predicate est le prédicat capturé : 0 (aucun) 1 (&) ou -1 (!) % - \tktl_current_macro est la macro spécifiant le type de motif (\s, \S, \r, \R ou \.) % - \tktl_current_macro_arg est l'argument de la macro (forcément vide pour \.) % - \tktl_min_current_repeat est le nombre min de répétitions % - \tktl_max_current_repeat est le nombre min de répétitions % - \tktl_current_postcapture_directive est la capture demandée 0 (aucune) ou 1 (position) %--------------------------------------------------------------------- \newcount\tktl_capture_pos_cnt% position d'un token (si on capture la position) \newcount\tktl_position_saved_cnt% position sauvegardée d'un token (si on capture la position) \newcount\tktl_capture_pos_index_cnt% numéro de la demande de capture de position \def\tktl_macro_dot{\.}% \def\tktl_macro_s{\s}% \def\tktl_macro_S{\S}% \def\tktl_macro_r{\r}% \def\tktl_macro_R{\R}% \def\tktl_macro_group{\tktl_patterngroup} \newif\iftktl_allow_captures_% tenir compte des captures par \c ou pas ? \long\def\tktl_grab_pattern#1{% #1= pattern unique ou {} \let\tktl_current_predicate\empty \let\tktl_current_macro\empty \let\tktl_current_macro_arg\empty \let\tktl_current_repeat\empty \def\tktl_current_precapture_directive{0}% \tktl_grab_pattern_search_precapture#1\_nil } \def\tktl_grab_pattern_search_precapture{% \tktl_futurelet_nospace\tktl__futurtok\tktl_grab_pattern_search_precapture_a } \def\tktl_grab_pattern_search_precapture_a{% \ifx\tktl__futurtok\c \iftktl_allow_captures_ \def\tktl_current_precapture_directive{1}% \fi \let\tktl_do_next\tktl_grab_pattern_grab_precapture \else \let\tktl_do_next\tktl_grab_pattern_search_predicate \fi \tktl_do_next } \def\tktl_grab_pattern_grab_precapture#1{% #1=\c \tktl_futurelet_nospace\tktl__futurtok\tktl_grab_pattern_search_predicate } \def\tktl_grab_pattern_search_predicate{% \ifx!\tktl__futurtok \def\tktl_current_predicate{-1}% \let\tktl_do_next\tktl_grab_predicate \else \ifx&\tktl__futurtok \def\tktl_current_predicate{1}% \let\tktl_do_next\tktl_grab_predicate \else \def\tktl_current_predicate{0}% \let\tktl_do_next\tktl_test_post_predicate \fi\fi \tktl_do_next } \def\tktl_grab_predicate#1{% #1= ! ou & \tktl_futurelet_nospace\tktl__futurtok\tktl_test_post_predicate } \def\tktl_test_post_predicate{% \ifx\bgroup\tktl__futurtok% si après le prédicat il y a "{" \let\tktl_do_next\tktl_grab_group_of_patterns% prendre le groupe de patterns \else \let\tktl_do_next\tktl_grab_pattern_grab_macro% sinon, prendre la macro \fi \tktl_do_next } \long\def\tktl_grab_group_of_patterns#1{% #1= ensemble de motifs entre accolades \def\tktl_current_macro{\tktl_patterngroup}% \def\tktl_current_macro_arg{#1}% \tktl_grab_pattern_grab_repeat_char } \def\tktl_grab_pattern_grab_macro#1{% #1 = macro \tktl_ifinstr{,#1,}{,\s,\S,\r,\R,\.,} {% \def\tktl_current_macro{#1}% \tktl_ifx{\.#1} {% \tktl_grab_pattern_grab_repeat_char } {% aller capturer l'argument de la macro \tktl_futurelet_nospace\tktl__futurtok\tktl_grab_pattern_grab_macro_arg }% } {% \tktl_ifmacro{#1} {% aller voir si c'est une macro-pattern \tktl_grab_pattern_grab_macro_pattern#1% } {% \errmessage{Found "\detokenize{#1}" when expecting \string\r, \string\R, \string\s, \string\S\space or \string\.}% } }% } \def\tktl_grab_pattern_grab_macro_pattern#1{% #1=macro donnée par l'utilisateur \tktl_earg{\tktl_ifinstr{#1}}\tktl_list_of_patterns {% si #1 est une macro-pattern \tktl_earg{\tktl_ifinstr{#1}}\tktl_list_of_single_patterns {% et contient un pattern unique, reprendre avec le 1-dév de #1 \tktl_ifnum{\tktl_current_predicate\tktl_current_precapture_directive=0 } {% si ni prédicat ni capture -> recommencer la capture du motif depuis le début avec le contenu de la macro-pattern \expandafter\expandafter\expandafter\tktl_grab_pattern_search_precapture\csname tktl_pattern_\string#1\endcsname } {% si prédicat et/ou capture -> le pattern unique _doit_ être considéré comme un groupe de patterns \def\tktl_current_macro{\tktl_patterngroup}% \expandafter\let\expandafter\tktl_current_macro_arg\csname tktl_pattern_\string#1\endcsname% \tktl_grab_pattern_grab_repeat_char } } {% si #1 ne contient pas un pattern unique -> groupe de patterns \def\tktl_current_macro{\tktl_patterngroup}% \expandafter\let\expandafter\tktl_current_macro_arg\csname tktl_pattern_\string#1\endcsname% \tktl_grab_pattern_grab_repeat_char }% } {% \errmessage{Macro "\string#1" is not \string\r, \string\R, \string\s, \string\S\space, \string\. or a defined pattern}% }% } \def\tktl_grab_pattern_grab_macro_arg{% \tktl_ifx{\bgroup\tktl__futurtok} {% \tktl_grab_pattern_grab_macro_arg_a } {% \errmessage{No open brace found after "\detokenize\expandafter{\tktl_current_macro}", aborting pattern}% \let\tktl_current_predicate\empty \let\tktl_current_macro\empty \let\tktl_current_macro_arg\empty \let\tktl_current_repeat\empty \tktl_gob_enctoks_to_end_parse }% } \long\def\tktl_grab_pattern_grab_macro_arg_a#1{% argument de la macro \def\tktl_current_macro_arg{#1}% \tktl_grab_pattern_grab_repeat_char } \def\tktl_grab_pattern_grab_repeat_char{% \tktl_futurelet_nospace\tktl__futurtok\tktl_grab_pattern_grab_repeat_char_a } \def\tktl_grab_pattern_grab_repeat_char_a{% \let\tktl_do_next\tktl_grab_pattern_search_postcapture% aucun motif de répétition à priori \def\tktl_min_current_repeat{1}% \def\tktl_max_current_repeat{1}% 1 répétition a priori \ifx*\tktl__futurtok \let\tktl_do_next\tktl_grab_pattern_gobble_char \def\tktl_min_current_repeat{0}% \let\tktl_max_current_repeat\tktl_maxint_repeat \else \ifx+\tktl__futurtok \let\tktl_do_next\tktl_grab_pattern_gobble_char \def\tktl_min_current_repeat{1}% \let\tktl_max_current_repeat\tktl_maxint_repeat \else \ifx?\tktl__futurtok \let\tktl_do_next\tktl_grab_pattern_gobble_char \def\tktl_min_current_repeat{0}% \def\tktl_max_current_repeat{1}% \else \ifx^\tktl__futurtok \let\tktl_do_next\tktl_grab_pattern_gobble_char \let\tktl_do_next\tktl_grab_pattern_grab_repeat_arg \fi\fi\fi\fi \tktl_do_next } \def\tktl_grab_pattern_gobble_char#1{% #1 = + * ? ou ^ \tktl_grab_pattern_search_postcapture } \def\tktl_grab_pattern_grab_repeat_arg^#1{% #1=argument spécifiant le nombre de répétitions \tktl_encode_tokensinterval{#1}\tktl_min_current_repeat\tktl_max_current_repeat \tktl_grab_pattern_search_postcapture } \def\tktl_grab_pattern_search_postcapture{% \tktl_futurelet_nospace\tktl__futurtok\tktl_grab_pattern_search_postcapture_a } \def\tktl_grab_pattern_search_postcapture_a{% \ifx\c\tktl__futurtok \iftktl_allow_captures_ \iftktl_allow_post_pattern_catpure_ \def\tktl_current_postcapture_directive{1}% \let\tktl_do_next\tktl_grab_pattern_grab_postcapture \else \errmessage{Capture after a pattern not allowed, \string\c\space ignored}% \def\tktl_current_postcapture_directive{0}% \let\tktl_do_next\tktl_grab_pattern_grab_postcapture \fi \else \def\tktl_current_postcapture_directive{0}% \let\tktl_do_next\tktl_grab_pattern_grab_postcapture \fi \else \def\tktl_current_postcapture_directive{0}% \let\tktl_do_next\tktl_grab_pattern_remain \fi \tktl_do_next } \def\tktl_grab_pattern_grab_postcapture\c{\tktl_grab_pattern_remain} \def\tktl_grab_pattern_remain#1\_nil{% \tktl_ifempty_or_space{#1} {} {% \errmessage{Unexpected "\detokenize{#1}" found, I ignore it}% }% } \newif\iftktl_if_single_pattern_% vrai si pattern unique (faux pour un groupe ou plusieurs patterns) \def\tktl_grab_pattern_remain_test_single#1\_nil{% \ifx\tktl_current_macro\tktl_macro_group \tktl_if_single_pattern_false \else \tktl_ifempty_or_space{#1} \tktl_if_single_pattern_true \tktl_if_single_pattern_false \fi } %--------------------------------------------------------------------- %-------------- changement de catcode dans des -------------- %--------------------------------------------------------------------- % La macro \tktl_catcode_string{} agit sur lorsqu'elle % contient : % - \c{}{} ou \c{} % - \c{} ou \c % et modifie le catcode des tokens concernés. % Limitations : % 1) la macro \c ne peut pas se trouver dans son 2e argument (imbrication impossible) % 2) le premier argument doit être un catcode valide (1,2,3,4,6,7,8,10,11,12 ou 13) % 3) si une \macro se trouve dans la , le seul catcode qu'elle peut revêtir est 12 % et dans ce cas, les tokens provenant de \string\macro sont créés. % Si le catcode demandé est différent de 12, la macro reste inchangée avec son catcode de 16 % % Entrée : la qui contient 0, 1 ou plusieurs occurences de \c avec ses 2 arguments % Sortie : % - le registre de tokens \tktl_string_toks qui contient les tokens composants la % encodés sous la forme (:) % - le compteur \tktl_string_len_cnt contient le nombre de tokens de la %--------------------------------------------------------------------- \newtoks\tktl_string_toks \newcount\tktl_string_len_cnt \long\def\tktl_catcode_string#1{% #1=tokens contenant ou pas \c \tktl_encode_tokens{#1}% \tktl_string_len_cnt=\tktl_enctoks_len_cnt \tktl_earg{\tktl_ifinstr{(\c:16)}}{\the\tktl_enctoks_toks} {% si la chaine de tokens contient "\c" \tktl_string_toks{}% \expandafter\tktl_catcode_string_a\the\tktl_enctoks_toks(0:-1)% } {% \tktl_string_toks=\tktl_enctoks_toks }% } \def\tktl_catcode_string_a#1(\c:16)(#2:#3){% \advance\tktl_string_len_cnt -1 % retirer le token "\c" \tktl_addtotoks\tktl_string_toks{#1}% \tktl_ifnum{#3=1 } {% \c est suivi de "{" \advance\tktl_string_len_cnt -1 % retirer le token "{" \let\tktl_catcode_directive\empty \tktl_catcode_string_b } {%\c n'est pas suivi de "{" -> le chiffre #2 est le catcode demandé % TODO : vérifier que #3=12 ??? \edef\tktl_catcode_directive{\tktl_charcode_to_digit{#2}}% \tktl_catcode_string_c }% } \def\tktl_catcode_string_b(#1:#2){% \c est suivi de "{" qu'on a déjà lu \advance\tktl_string_len_cnt -1 % retirer le token correspondant au chiffre du catcode ou à "}" \tktl_ifnum{#2=2 } {% si #1="}" -> fini de lire le catcode demandé \tktl_earg\tktl_ifempty_or_space{\tktl_catcode_directive} {% vérifier que ce catcode n'est pas vide \errmessage{Empty argument found after\string\c}% \def\tktl_catcode_directive{11}% } {% et qu'il n'est pas illégal \tktl_earg\tktl_ifinstr{\expandafter,\tktl_catcode_directive,}{,1,2,3,4,6,7,8,10,11,12,13,} {} {% \errmessage{Illegal catcode of \tktl_catcode_directive\space in argument of\string\c}% \def\tktl_catcode_directive{11}% }% }% \tktl_catcode_string_c } {% pas d'accolade ouvrante -> on ajoute le chiffre et on recommence \edef\tktl_catcode_directive{\tktl_catcode_directive\tktl_charcode_to_digit{#1}}% \tktl_catcode_string_b }% } \newtoks\tktl_string_collect_toks \def\tktl_catcode_string_c(#1:#2){% \c ou \c{} a été lu et le est dans \tktl_catcode_directive \tktl_ifnum{#2=1 } {% si c'est suivi d'une accolade ouvrante \def\tktl_bg_number{0}% nombre d'accolades ouvrante rencontrées \tktl_string_collect_toks{}% \advance\tktl_string_len_cnt -1 % retirer le token "{" \tktl_catcode_string_e } {% n'est pas suivi d'une "{" \ifnum#2=16 % si #1 est une macro \ifnum\tktl_catcode_directive=12 % à détokéniser ? \begingroup \tktl_earg\tktl_encode_tokens{\string#1}% \expanded{% \endgroup \noexpand\tktl_addtotoks\noexpand\tktl_string_toks{\the\tktl_enctoks_toks}% \advance\noexpand\tktl_string_len_cnt\the\tktl_enctoks_len_cnt\relax% ajoute les tokens provenant de \string#1 }% \else \tktl_addtotoks\tktl_string_toks{(#1:16)}% \fi \else \tktl_addtotoks\tktl_string_toks{(#1:}% \tktl_eaddtotoks\tktl_string_toks{\tktl_catcode_directive)}% \fi \tktl_catcode_string_d }% } \def\tktl_catcode_string_d#1(0:-1){% \tktl_ifinstr{(\c:16)}{#1} {% \tktl_catcode_string_a#1(0:-1)% } {% \tktl_addtotoks\tktl_string_toks{#1}% }% } \def\tktl_catcode_string_e(#1:#2){% \c\ était suivi d'une accolade \let\tktl_do_next\tktl_catcode_string_e% on reboucle à priori \ifnum#2=2 % si accolade fermante \ifnum\tktl_bg_number=0 % et équilibrage en accolades -> fin de l'argument \advance\tktl_string_len_cnt -1 % retirer le token "}" \tktl_eaddtotoks\tktl_string_toks{\the\tktl_string_collect_toks}% \let\tktl_do_next\tktl_catcode_string_d \else% sinon, ajouter l'accolade fermante avec le catcode demandé \tktl_addtotoks\tktl_string_collect_toks{(#1:}% \tktl_eaddtotoks\tktl_string_collect_toks{\tktl_catcode_directive)}% \edef\tktl_bg_number{\the\numexpr\tktl_bg_number-1}% et décrémenter \fi \else \ifnum#2=1 % si accolade ouvrante \edef\tktl_bg_number{\the\numexpr\tktl_bg_number-1}% incrémenter \tktl_addtotoks\tktl_string_collect_toks{(#1:}% \tktl_eaddtotoks\tktl_string_collect_toks{\tktl_catcode_directive)}% \else \ifnum#2=16 % si macro \ifnum\tktl_catcode_directive=12 % à détokéniser ? \begingroup \tktl_earg\tktl_encode_tokens{\string#1}% \advance\tktl_enctoks_len_cnt -1 % pour compenser le token que représente la macro #1 elle-même \expanded{% \endgroup \noexpand\tktl_addtotoks\noexpand\tktl_string_collect_toks{\the\tktl_enctoks_toks}% \advance\noexpand\tktl_string_len_cnt\the\tktl_enctoks_len_cnt\relax% ajoute les tokens provenant de \string#1 }% \else \tktl_addtotoks\tktl_string_collect_toks{(#1:}% \tktl_addtotoks\tktl_string_collect_toks{16)}% \fi \else \tktl_addtotoks\tktl_string_collect_toks{(#1:}% \tktl_eaddtotoks\tktl_string_collect_toks{\tktl_catcode_directive)}% \fi \fi \fi \tktl_do_next } \def\tktl_charcode_to_digit#1{% \ifcase\numexpr#1-`\0\relax 0\or1\or2\or3\or4\or5\or6\or7\or8\or9% \else \errmessage{Catcode directive not a number}% \fi } %--------------------------------------------------------------------- %--- Test si un token matche les intervalles de charcode et catcode -- %--------------------------------------------------------------------- % teste si les entiers #1 et #2 sont dans les intervalles #3 et #4. \long\def\tktl_iftokmatch#1#2#3#4{% #1=tok_charcode #2=tok_catcode #3=interval_charcode #4=interval_catcode \tktl_ifnum{#2=16 }% si le token est une macro {% \tktl_test_interval{#2}{#4}% {% 16 est dans l'intervalle des catcodes \tktl_eearg{\def\tktl_temp_macro_charcode_interval}{\tktl_stripsp{#3}}% \tktl_ifx{\tktl_temp_macro_charcode_interval\tktl_star} {% si interval charcode = "*" -> renvoyer vrai \tktl_exec_first } {% \tktl_ifinstr{#1}{#3}% la macro #1 est-elle dans la liste de macros #3 ? }% } {% \tktl_exec_second }% } {% \tktl_test_interval{#1}{#3} {% charcode correspond \tktl_ifempty_or_space{#4} {% si catcode non spécifié -> wildcard \tktl_exec_first } {% si catcode spécifié \tktl_test_interval{#2}{#4}% }% } {% mauvais charcode -> renvoyer faux \tktl_exec_second }% }% } %--------------------------------------------------------------------- %----- Teste si un motif matche avec le début des tokens encodés ----- %--------------------------------------------------------------------- % La macro principale du package : % \tktl_test_match_pattern{}{} % Entrée : % - le pattern est de la forme % {} % - les sont encodés de la forme (charcode:catcode) % Sortie : % 1) \iftktl_match_success_ booléen s'il y a correspondance % 2) les tokens encodés \tktl_match_tokens contiennent la partie du % début des qui a matché % 3) \tktl_postmatch_enctokens contient les tokens encodés qui % restent après la partie qui a matché %--------------------------------------------------------------------- \newif\iftktl_match_success_ \newif\iftktl_in_capture_ % vrai quand une capture de tokens est en cours \newif\iftktl_in_patterngroup_ \newcount\tktl_capture_tok_index_cnt % numérote les capture de tokens (non imbriquées) \newcount\tktl_repeat_match_cnt \long\def\tktl_test_match_pattern#1#2{% #1=pattern #2=tokens encodés (charcode:catcode) \def\tktl_postmatch_enctokens{#2}% \let\tktl_match_tokens\empty \let\tktl_postmatch_enctokens_saved\tktl_postmatch_enctokens% sauvegarde en cas de prédicat ou de non match \tktl_position_saved_cnt=\tktl_capture_pos_cnt \let\tktl_capture_pos_list_saved\tktl_capture_pos_list \let\tktl_capture_tok_list_saved\tktl_capture_tok_list \tktl_grab_pattern{#1}% \ifnum\tktl_current_precapture_directive=1 % si capture de tokens demandée \iftktl_in_capture_ % si déjà en train de capturer \errmessage{Ignoring nested capture of tokens.}% \else \advance\tktl_capture_tok_index_cnt 1 \tktl_in_capture_true \fi \fi \tktl_repeat_match_cnt=0 \csname tktl_test_match_pattern_\expandafter\string\tktl_current_macro\endcsname } \expandafter\def\csname tktl_test_match_pattern_\expandafter\string\tktl_macro_s\endcsname{% \tktl_earg\tktl_catcode_string{\tktl_current_macro_arg}% prendre en compte les \c{}{texte} dans l'argument de \s \tktl_etwoargs\tktl_test_match_s{\the\tktl_string_toks}{\tktl_postmatch_enctokens}% \tktl_test_match_pattern_a } \expandafter\def\csname tktl_test_match_pattern_\expandafter\string\tktl_macro_dot\endcsname{% \expandafter\tktl_test_match_dot\tktl_postmatch_enctokens(-1:-1)% \tktl_test_match_pattern_a } \expandafter\def\csname tktl_test_match_pattern_\expandafter\string\tktl_macro_S\endcsname{% \tktl_earg\tktl_catcode_string{\tktl_current_macro_arg}% prendre en compte les \c{}{texte} dans l'argument de \S \edef\tktl_pattern_string{\the\tktl_string_toks}% \long\def\tktl_token_test_R_or_S(##1:##2){\tktl_earg{\tktl_ifinstr{(##1:##2)}}{\tktl_pattern_string}}% \expandafter\tktl_test_match_R_or_S\tktl_postmatch_enctokens(-1:-1)% \tktl_test_match_pattern_a } \expandafter\def\csname tktl_test_match_pattern_\expandafter\string\tktl_macro_R\endcsname{% \tktl_earg\tktl_analyse_R_pattern{\tktl_current_macro_arg}% \long\def\tktl_token_test_R_or_S(##1:##2){\tktl_etwoargs{\tktl_iftokmatch{##1}{##2}}{\tktl_charcode_interval}{\tktl_catcode_interval}}% \expandafter\tktl_test_match_R_or_S\tktl_postmatch_enctokens(-1:-1)% \tktl_test_match_pattern_a } \expandafter\def\csname tktl_test_match_pattern_\expandafter\string\tktl_macro_r\endcsname{% \tktl_earg\tktl_analyse_R_pattern{\tktl_current_macro_arg}% \tktl_earg\tktl_convert_charcsv_to_charcodecsv{\tktl_charcode_interval}% \long\def\tktl_token_test_R_or_S(##1:##2){\tktl_etwoargs{\tktl_iftokmatch{##1}{##2}}{\tktl_charcode_interval}{\tktl_catcode_interval}}% \expandafter\tktl_test_match_R_or_S\tktl_postmatch_enctokens(-1:-1)% \tktl_test_match_pattern_a } \expandafter\def\csname tktl_test_match_pattern_\expandafter\string\tktl_macro_group\endcsname{% \begingroup \tktl_repeat_match_cnt 0 \iftktl_in_capture_ \tktl_in_patterngroup_true \else% si pas de capture en cours -> captures permises à l'intérieur du groupe \tktl_in_patterngroup_false \fi \let\tktl_match_tokens_patterngroup\empty \tktl_test_match_patterngroup% \expanded{% \endgroup \iftktl_match_success_ \tktl_unexpand_earg\def\tktl_postmatch_enctokens{\tktl_postmatch_enctokens}% \tktl_unexpand_earg\def\tktl_match_tokens{\tktl_match_tokens_patterngroup}% \tktl_unexpand_earg\def\tktl_capture_pos_list{\tktl_capture_pos_list}% \tktl_unexpand_earg\def\tktl_capture_tok_list{\tktl_capture_tok_list}% \noexpand\tktl_capture_pos_cnt=\the\tktl_capture_pos_cnt\relax \noexpand\tktl_capture_pos_index_cnt=\the\tktl_capture_pos_index_cnt\relax \noexpand\tktl_capture_tok_index_cnt=\the\tktl_capture_tok_index_cnt\relax \noexpand\tktl_match_success_true \else \noexpand\tktl_match_success_false \fi }% \tktl_test_match_pattern_a } \def\tktl_test_match_pattern_a{% \unless\ifnum\tktl_current_predicate=0 % si le motif est un prédicat \tktl_capture_pos_cnt=\tktl_position_saved_cnt \let\tktl_postmatch_enctokens\tktl_postmatch_enctokens_saved% ne rien consommer \let\tktl_match_tokens\empty% aucune correspondance \ifnum\tktl_current_predicate=-1 % si prédicat "!" \iftktl_match_success_% inverser le booléen \tktl_match_success_false \else \tktl_match_success_true \fi \fi \fi \ifnum\tktl_current_precapture_directive=1 \advance\tktl_capture_pos_index_cnt 1 \edef\tktl_capture_pos_list{\tktl_capture_pos_list<\the\tktl_capture_pos_index_cnt=\the\tktl_position_saved_cnt>}% \fi \ifnum\tktl_current_postcapture_directive=1 \advance\tktl_capture_pos_index_cnt 1 \edef\tktl_capture_pos_list{\tktl_capture_pos_list<\the\tktl_capture_pos_index_cnt=\the\tktl_capture_pos_cnt>}% \fi \iftktl_in_capture_\unless\iftktl_in_patterngroup_ % si en cours de capture et hors d'un groupe \tktl_in_capture_false % fin de la capture de tokens \tktl_eaddtomacro\tktl_capture_tok_list{\expanded{<\the\tktl_capture_tok_index_cnt=\unexpanded\expandafter{\tktl_match_tokens}}>}% \fi\fi } \long\def\tktl_gob_enctoks_to_end#1(-1:-1){} \def\tktl_test_match_patterngroup{% \ifx\tktl_postmatch_enctokens\empty% fin atteinte ? \let\tktl_do_next\empty% on sort de toutes façons \tktl_etwoargs\tktl_test_match_full_patterns\tktl_current_macro_arg\tktl_postmatch_enctokens% \iftktl_match_success_% si correspondance -> motif epsilon a matché \advance\tktl_repeat_match_cnt 1 \fi \ifnum\tktl_repeat_match_cnt<\tktl_min_current_repeat\relax% si nb répétition mini non atteint \tktl_match_success_false \tktl_capture_pos_cnt=\tktl_position_saved_cnt \let\tktl_capture_pos_list\tktl_capture_pos_list_saved \let\tktl_capture_tok_list\tktl_capture_tok_list_saved \let\tktl_postmatch_enctokens\tktl_postmatch_enctokens_saved% ne rien consommer \else \tktl_match_success_true \let\tktl_postmatch_enctokens\empty \fi \else \tktl_etwoargs\tktl_test_match_full_patterns\tktl_current_macro_arg\tktl_postmatch_enctokens% \iftktl_match_success_% si correspondance \advance\tktl_repeat_match_cnt 1 \tktl_eaddtomacro\tktl_match_tokens_patterngroup\tktl_match_full_patterns \ifnum\tktl_repeat_match_cnt<\tktl_min_current_repeat\relax% si nb répétition mini non atteint \let\tktl_do_next\tktl_test_match_patterngroup \else \ifnum\tktl_repeat_match_cnt=\tktl_max_current_repeat\relax% max atteint \tktl_match_success_true \let\tktl_do_next\empty% on sort \else \let\tktl_do_next\tktl_test_match_patterngroup \fi \fi \else% si pas correspondance \let\tktl_do_next\empty% on sort \ifnum\numexpr(\tktl_repeat_match_cnt-\tktl_min_current_repeat)*(\tktl_repeat_match_cnt-\tktl_max_current_repeat)>0 % si le nombre de répétition n'est pas atteint \tktl_match_success_false \let\tktl_match_tokens_patterngroup\empty \tktl_capture_pos_cnt=\tktl_position_saved_cnt \let\tktl_capture_pos_list\tktl_capture_pos_list_saved \let\tktl_capture_tok_list\tktl_capture_tok_list_saved \let\tktl_postmatch_enctokens\tktl_postmatch_enctokens_saved% ne rien consommer \else% nombre de répétition atteint \tktl_match_success_true \fi \fi \fi \tktl_do_next } \long\def\tktl_test_match_dot(#1:#2){% #1=token courant forme (charcode,catcode) \ifnum#2=-1 % fin atteinte? \let\tktl_do_next\empty \ifnum\tktl_repeat_match_cnt<\tktl_min_current_repeat\relax% si nb répétition mini non atteint \tktl_match_success_false \tktl_capture_pos_cnt=\tktl_position_saved_cnt \let\tktl_capture_pos_list\tktl_capture_pos_list_saved \let\tktl_capture_tok_list\tktl_capture_tok_list_saved \let\tktl_postmatch_enctokens\tktl_postmatch_enctokens_saved% ne rien consommer \else \tktl_match_success_true \let\tktl_postmatch_enctokens\empty \fi \else \advance\tktl_repeat_match_cnt 1 \advance\tktl_capture_pos_cnt1 \tktl_addtomacro\tktl_match_tokens{(#1:#2)}% \ifnum\tktl_repeat_match_cnt<\tktl_min_current_repeat\relax% si nb répétition mini non atteint \let\tktl_do_next\tktl_test_match_dot \else \ifnum\tktl_repeat_match_cnt=\tktl_max_current_repeat\relax% max atteint \tktl_match_success_true \def\tktl_do_next{\tktl_test_match_assign_remaining_tokens{\def\tktl_postmatch_enctokens}}% \else \let\tktl_do_next\tktl_test_match_dot \fi \fi \fi \tktl_do_next } \long\def\tktl_test_match_assign_remaining_tokens#1#2(-1:-1){% #1=consigne de stockage #2=tokens restants #1{#2}% } \long\def\tktl_test_match_s#1#2{% #1=motif forme (charcode,catcode) #2=texte forme (charcode,catcode) \def\tktl_test_match_s_a##1#1##2\_nil{% \tktl_ifempty_or_space{##1} {% si le texte commence par le motif \advance\tktl_capture_pos_cnt\tktl_string_len_cnt \tktl_addtomacro\tktl_match_tokens{#1}% \advance\tktl_repeat_match_cnt 1 \edef\tktl_postmatch_enctokens{\tktl_test_match_s_b##2\_nil}% prendre ce qui est après le motif \ifnum\tktl_repeat_match_cnt<\tktl_min_current_repeat\relax% si nb répétition mini non atteint \def\tktl_do_next{\expandafter\tktl_test_match_s_a\tktl_postmatch_enctokens#1\_nil}% recommencer \else% mini de répétitions atteint \ifnum\tktl_repeat_match_cnt=\tktl_max_current_repeat\relax% max atteint \tktl_match_success_true \let\tktl_do_next\empty \else% max non atteint -> comportement gourmand \def\tktl_do_next{\expandafter\tktl_test_match_s_a\tktl_postmatch_enctokens#1\_nil}% recommencer \fi \fi \tktl_do_next } {% si le texte ne commence pas par le motif \ifnum\tktl_repeat_match_cnt<\tktl_min_current_repeat\relax% si nb répétition mini non atteint \tktl_match_success_false \tktl_capture_pos_cnt=\tktl_position_saved_cnt \let\tktl_capture_pos_list\tktl_capture_pos_list_saved \let\tktl_capture_tok_list\tktl_capture_tok_list_saved \let\tktl_postmatch_enctokens\tktl_postmatch_enctokens_saved% ne rien consommer \else \tktl_match_success_true \fi }% }% \def\tktl_test_match_s_b##1#1\_nil{% \unexpanded{##1}% garder ce qui est après le motif et avant le motif mis en dernier }% \tktl_ifempty_or_space{#1}% si motif ε -> renvoyer vrai {% \tktl_match_success_true } {% \tktl_ifempty_or_space{#2} {% \tktl_match_success_false } {% \tktl_test_match_s_a#2#1\_nil }% }% } \def\tktl_analyse_R_pattern#1{% #1 est de la forme "interval charcode" ou "interval charcode:interval catcode" \tktl_analyse_R_pattern_a#1:*:\_nil } \def\tktl_analyse_R_pattern_a#1:#2:#3\_nil{% \def\tktl_charcode_interval{#1}% \def\tktl_catcode_interval{#2}% } \def\tktl_convert_charcsv_to_charcodecsv#1{% \let\tktl_charcode_interval\empty \tktl_convert_charcsv_to_charcodecsv_a#1,\tktl_convert_charcsv_to_charcodecsv_a,% \ifx\tktl_charcode_interval\empty\else% retirer la dernière virgule \expandafter\tktl_convert_charcsv_to_charcodecsv_b\tktl_charcode_interval\_nil\tktl_charcode_interval \fi } \def\tktl_convert_charcsv_to_charcodecsv_a#1,{% \ifx\tktl_convert_charcsv_to_charcodecsv_a#1% \else% TODO : tester si #1 est vide ? \tktl_encode_tokens_char_range{#1}\tktl_charcode_min\tktl_charcodemax \edef\tktl_charcode_interval{\tktl_charcode_interval\tktl_charcode_min-\tktl_charcodemax,}% \expandafter\tktl_convert_charcsv_to_charcodecsv_a \fi } \def\tktl_convert_charcsv_to_charcodecsv_b#1,\_nil#2{% \def#2{#1}% } \long\def\tktl_test_match_R_or_S(#1:#2){% \ifnum#2=-1 % fin atteinte? \let\tktl_do_next\empty \ifnum\tktl_repeat_match_cnt<\tktl_min_current_repeat\relax% si nb répétition mini non atteint \tktl_match_success_false \tktl_capture_pos_cnt=\tktl_position_saved_cnt \let\tktl_capture_pos_list\tktl_capture_pos_list_saved \let\tktl_capture_tok_list\tktl_capture_tok_list_saved \let\tktl_postmatch_enctokens\tktl_postmatch_enctokens_saved% ne rien consommer \else \tktl_match_success_true \let\tktl_postmatch_enctokens\empty \fi \else \tktl_token_test_R_or_S(#1:#2) {% si le token courant correspond aux intervalles \advance\tktl_capture_pos_cnt1 \advance\tktl_repeat_match_cnt 1 \tktl_addtomacro\tktl_match_tokens{(#1:#2)}% \ifnum\tktl_repeat_match_cnt<\tktl_min_current_repeat\relax% si nb répétition mini non atteint \let\tktl_do_next\tktl_test_match_R_or_S \else \ifnum\tktl_repeat_match_cnt=\tktl_max_current_repeat\relax% max atteint \tktl_match_success_true \def\tktl_do_next{\tktl_test_match_assign_remaining_tokens{\def\tktl_postmatch_enctokens}}% tout manger et assigner \else \let\tktl_do_next\tktl_test_match_R_or_S \fi \fi } {%le token courant ne correspond pas \ifnum\numexpr(\tktl_repeat_match_cnt-\tktl_min_current_repeat)*(\tktl_repeat_match_cnt-\tktl_max_current_repeat)>0 % si le nombre de répétition n'est aps atteint \tktl_match_success_false \let\tktl_match_tokens\empty \tktl_capture_pos_cnt=\tktl_position_saved_cnt \let\tktl_capture_pos_list\tktl_capture_pos_list_saved \let\tktl_capture_tok_list\tktl_capture_tok_list_saved \let\tktl_postmatch_enctokens\tktl_postmatch_enctokens_saved% ne rien consommer \let\tktl_do_next\tktl_gob_enctoks_to_end% tout manger \else% nombre de répétition atteint \tktl_match_success_true \def\tktl_do_next{\tktl_test_match_assign_remaining_tokens{\def\tktl_postmatch_enctokens}(#1:#2)}% remettre le token actuel \fi }% \fi \tktl_do_next } %--------------------------------------------------------------------- %------------ Test si des motifs qui se suivent matchent ------------- %------------ avec le début des tokens encodés ------------- %--------------------------------------------------------------------- % Les motifs sont séparés par des ":" qui est le connecteur de concaténation % Entrée : % #1 = motifs séparés par des ":" % #2 = tokens encodés sous la forme (charcode:catcode) % Revoie : % \iftktl_match_success_ : le booléen selon qu'il y a correspondance ou pas % \tktl_postmatch_enctokens : tokens encodés restant (#1 si booléen faux) % \tktl_match_and_patterns : vide si booléen faux et tokens encodés qui ont matché %--------------------------------------------------------------------- \long\def\tktl_test_match_and_patterns#1#2{% #1= and_patterns séparés par ":" #2=tok_encodés (charcode:catcode) \begingroup \def\tktl_postmatch_enctokens{#2}% \let\tktl_match_and_patterns\empty \let\tktl_postmatch_enctokens_init\tktl_postmatch_enctokens \tktl_test_match_and_patterns_a#1:\relax:% \expanded{% \endgroup \iftktl_match_success_ \tktl_unexpand_earg\def\tktl_match_and_patterns{\tktl_match_and_patterns}% \tktl_unexpand_earg\def\tktl_postmatch_enctokens{\tktl_postmatch_enctokens}% \tktl_unexpand_earg\def\tktl_capture_pos_list{\tktl_capture_pos_list}% \tktl_unexpand_earg\def\tktl_capture_tok_list{\tktl_capture_tok_list}% \noexpand\tktl_capture_pos_cnt=\the\tktl_capture_pos_cnt\relax \noexpand\tktl_capture_pos_index_cnt=\the\tktl_capture_pos_index_cnt\relax \noexpand\tktl_capture_tok_index_cnt=\the\tktl_capture_tok_index_cnt\relax \noexpand\tktl_match_success_true \else \noexpand\tktl_match_success_false \fi }% } \long\def\tktl_test_match_and_patterns_a#1:{% #1= and_pattern courant \def\tktl_current_pattern{#1}% \ifx\tktl_current_pattern\tktl_end_of_patterns \let\tktl_do_next\empty \else \let\tktl_match_tokens\empty \tktl_earg{\tktl_test_match_pattern{#1}}{\tktl_postmatch_enctokens}% \iftktl_match_success_ \tktl_eaddtomacro\tktl_match_and_patterns\tktl_match_tokens \let\tktl_do_next\tktl_test_match_and_patterns_a \else% ce match échoue ? \let\tktl_match_and_patterns\empty% vider les tokens ayant matché \let\tktl_postmatch_enctokens\tktl_postmatch_enctokens_init% ne rien consommer \let\tktl_do_next\tktl_gob_all_and_patterns% manger tous les pattern qui restent \fi \fi \tktl_do_next } \def\tktl_gob_all_and_patterns#1\relax:{} \def\tktl_end_of_patterns{\relax} %--------------------------------------------------------------------- %------------- Teste si des motifs quelconques matchent -------------- %------------- avec le début des tokens encodés -------------- %--------------------------------------------------------------------- % Entrée : % #1 = motifs concaténés séparés par des "|" % #2 = tokens encodés sous la forme (charcode:catcode) % Revoie : % \iftktl_match_success_ : le booléen selon qu'il y a correspondance ou pas % \tktl_postmatch_enctokens : tokens encodés restant (=#1 si booléen faux) % \tktl_match_full_patterns : tokens encodés qui ont matché (vide si booléen faux) % \tktl_prematch_pos position avant match % \tktl_postmatch_pos position après match (égale à \tktl_prematch_pos si pas de match) %--------------------------------------------------------------------- \long\def\tktl_test_match_full_patterns#1#2{% #1=patterns séparés par "|" #2=tok_encodés \def\tktl_postmatch_enctokens{#2}% \let\tktl_match_full_patterns\empty \let\tktl_postmatch_enctokens_init\tktl_postmatch_enctokens \edef\tktl_prematch_pos{\the\tktl_capture_pos_cnt}% \tktl_test_match_full_patterns_a#1|\relax|% \edef\tktl_postmatch_pos{\the\tktl_capture_pos_cnt}% } \long\def\tktl_test_match_full_patterns_a#1|{% \def\tktl_current_pattern{#1}% \ifx\tktl_current_pattern\tktl_end_of_patterns% tous les patterns épuisés : \tktl_match_success_false% pas de match \let\tktl_match_full_patterns\empty% vider les tokens ayant matché \let\tktl_postmatch_enctokens\tktl_postmatch_enctokens_init% ne rien consommer \let\tktl_do_next\empty \else \tktl_earg{\tktl_test_match_and_patterns{#1}}{\tktl_postmatch_enctokens}% \iftktl_match_success_ \tktl_eaddtomacro\tktl_match_full_patterns\tktl_match_and_patterns \let\tktl_do_next\tktl_gob_all_full_patterns \else \let\tktl_do_next\tktl_test_match_full_patterns_a \fi \fi \tktl_do_next } \long\def\tktl_gob_all_full_patterns#1\relax|{} %--------------------------------------------------------------------- %------------- Gestion des macros contenant des patterns ------------- %--------------------------------------------------------------------- \let\tktl_list_of_patterns\empty \let\tktl_list_of_single_patterns\empty \def\tktl_ifmacro#1{% teste si #1 est une macro \begingroup \escapechar=92 % "\" \ifnum 0% \tktl_ifempty_or_space{#1} {} {% \tktl_earg\tktl_ifempty_or_space{\tktl_gob_arg#1} {% \ifnum\expandafter\expandafter\expandafter`\expandafter\tktl_firsttonil\string#1.\_nil=92 1% \fi }% {}% }% =1 \endgroup \expandafter\tktl_exec_first \else \endgroup \expandafter\tktl_exec_second \fi } \def\tktl_remove_pattern_in_list#1#2{% #1=macro pattern utilisateur #2=macro contenant une liste \tktl_ifmacro{#1} {% \tktl_earg{\tktl_ifinstr{#1}}{#2} {% \tktl_remove_pattern_in_list_a#1#2% } {}% } {% \errmessage{Internal error in \string\tktl_remove_pattern_in_list : \detokenize{#1} is not a macro}% }% } \def\tktl_remove_pattern_in_list_a#1#2{% #1=macro pattern interne #2=macro contenant une liste \def\tktl_remove_pattern_in_list_b##1#1##2\_nil{\def#2{##1##2}}% \expandafter\tktl_remove_pattern_in_list_b#2\_nil } \def\tktl_add_pattern_in_list#1#2{% #1=macro pattern utilisateur #2=macro contenant une liste \tktl_ifmacro{#1} {% \tktl_earg{\tktl_ifinstr{#1}}{#2} {} {% \tktl_addtomacro#2{#1}% }% } {% \errmessage{Internal error in \string\tktl_add_pattern_in_list : \detokenize{#1} is not a macro}% }% } \long\def\defpattern#1#2{% #1 macro représentant le pattern #2=code du pattern \tktl_ifmacro{#1} {% \tktl_ifempty_or_space{#2} {% \errmessage{Pattern code is empty, definition ignored}% } {% \tktl_ifinstr{#1}{\s\S\r\R\.\relax} {% \errmessage{Macro \string#1\space is reserved, a pattern cannot be named with it}% } {% \begingroup% tester si le pattern est un unique motif \let\tktl_grab_pattern_remain\tktl_grab_pattern_remain_test_single \tktl_grab_pattern{#2}% \expandafter \endgroup% pas de prédicat ET motif unique ? \ifnum\tktl_current_predicate\iftktl_if_single_pattern_0\else1\fi=0 \tktl_add_pattern_in_list#1\tktl_list_of_single_patterns \else \tktl_remove_pattern_in_list#1\tktl_list_of_single_patterns \fi \tktl_add_pattern_in_list#1\tktl_list_of_patterns \expandafter\def\csname tktl_pattern_\string#1\endcsname{#2}% }% }% } {% \errmessage{Found "\detokenize{#1}" after \string\defpattern, definition aborted}% }% } %--------------------------------------------------------------------- %------------------ Gestion des captures effectuées ------------------ %--------------------------------------------------------------------- \def\tktl_ifcontains_ddots#1{% la chaine #1 contient-elle ":" ? \tktl_ifcontains_ddots_a#1:\_nil } \def\tktl_ifcontains_ddots_a#1:#2\_nil{% \ifcat\relax\detokenize{#2}\relax\expandafter\tktl_exec_second\else\expandafter\tktl_exec_first\fi } \def\tktl_process_pos_capture#1#2#3{% #1=liste des captures #2=nom des captures #3=maxindex \def\tktl_temp_capture_name{#2}% \expandafter\def\csname tktl_capture_position_maxindex_#2\endcsname{#3}% \let\tktl_capture_pos_list\empty \tktl_process_pos_capture_a#1<-1=-1>% \expandafter\let\csname tktl_pos_capture_\tktl_temp_capture_name0\endcsname\tktl_capture_pos_list } \def\tktl_process_pos_capture_a<#1=#2>{% \tktl_ifnum{#1>0 } {% \tktl_addtomacro\tktl_capture_pos_list{,#2}% \expandafter\def\csname tktl_pos_capture_\tktl_temp_capture_name#1\endcsname{#2}% \tktl_process_pos_capture_a } {% \ifx\tktl_capture_pos_list\empty\else% retirer la première virgule \tktl_eearg{\def\tktl_capture_pos_list}{\expandafter\tktl_gob_arg\tktl_capture_pos_list}% \fi }% } \long\def\tktl_process_tok_capture#1#2#3{% #1=liste des captures #2=nom des captures #3=maxindex \def\tktl_temp_capture_name{#2}% \expandafter\def\csname tktl_capture_tokens_maxindex_#2\endcsname{#3}% \let\tktl_capture_tok_list\empty \tktl_process_tok_capture_a#1<-1=-1>% \expandafter\let\csname tktl_tok_capture_\tktl_temp_capture_name0\endcsname\tktl_capture_tok_list } \newtoks\tktl_decode_capture_toks \long\def\tktl_process_tok_capture_a<#1=#2>{% \tktl_ifnum{#1>0 } {% \tktl_decode_enctoks{#2}\tktl_decode_capture_toks \expandafter\edef\csname tktl_tok_capture_\tktl_temp_capture_name#1\endcsname{\unexpanded\expandafter{\the\tktl_decode_capture_toks}}% \tktl_eaddtomacro\tktl_capture_tok_list{\unexpanded\expandafter{\expandafter,\expandafter{\the\tktl_decode_capture_toks}}}% \tktl_process_tok_capture_a } {% \ifx\tktl_capture_tok_list\empty\else% retirer la première virgule \tktl_eearg{\def\tktl_capture_tok_list}{\expandafter\tktl_gob_arg\tktl_capture_tok_list}% \fi }% } \def\poscapture#1{% \expanded{% \tktl_ifcontains_ddots{#1} {\tktl_poscapture_a#1\_nil} {\tktl_eearg{\tktl_poscapture_b{}}{\tktl_stripsp{#1}}}% }% } \def\tktl_poscapture_a#1:#2\_nil{% \tktl_eearg{\tktl_eearg\tktl_poscapture_b{\tktl_stripsp{#1}}}{\tktl_stripsp{#2}}% } \def\tktl_poscapture_b#1#2{% \ifcsname tktl_pos_capture_\detokenize{#1}#2\endcsname \ifnum\numexpr#2*(#2-\csname tktl_capture_position_maxindex_\detokenize{#1}\endcsname)>0 % si #2 n'appartient pas à [0 ; max] \tktl_capture_undefined{position}{#1}{#2}% \else \unexpanded\expandafter\expandafter\expandafter{\csname tktl_pos_capture_\detokenize{#1}#2\endcsname}% \fi \else \tktl_capture_undefined{position}{#1}{#2}% \fi } \def\tktl_capture_undefined#1#2#3{% \errmessage{Undefined #1 capture \ifcat\relax\detokenize{#2}\relax \else named "\detokenize{#2}"\fi at index "\detokenize{#3}"}% } \def\tokscapture#1{% #1= : ou \expanded{% \tktl_ifcontains_ddots{#1} {\tktl_tokcapture_a#1\_nil} {\tktl_eearg{\tktl_tokcapture_b{}}{\tktl_stripsp{#1}}}% }% } \def\tktl_tokcapture_a#1:#2\_nil{% \tktl_eearg{\tktl_eearg\tktl_tokcapture_b{\tktl_stripsp{#1}}}{\tktl_stripsp{#2}}% } \def\tktl_tokcapture_b#1#2{% \ifcsname tktl_tok_capture_\detokenize{#1}#2\endcsname \ifnum\numexpr#2*(#2-\csname tktl_capture_tokens_maxindex_\detokenize{#1}\endcsname)>0 % si #2 n'appartient pas à [0 ; max] \tktl_capture_undefined{token}{#1}{#2}% \else \unexpanded\expandafter\expandafter\expandafter{\csname tktl_tok_capture_\detokenize{#1}#2\endcsname}% \fi \else \tktl_capture_undefined{token}{#1}{#2}% \fi } %--------------------------------------------------------------------- %--------------------- Macro publique \ifpegmatch -------------------- %--------------------------------------------------------------------- % Utilisation : % \ifpegmatch[]{}{}{T}{F} % teste si les matchent avec les , % et exécute T ou F selon l'issue du test % % Sortie : % - les qui ont matché sont stockés par défaut dans la macro \matchtoks % - les qui restent sont stockés par défaut dans la macro \remaintoks % - la macro \poscapture{:} ou \poscapture{} renvoie en 2 développements % la position dans les capturés par \c de la capture numéro . % Si vaut 0, la liste des positions est renvoyée sous la forme csv % ",,...," % - la macro \matchposition contient la position du match dans les . % Elle vaut 0 si pas de match. % - la macro \tokscapture{:} ou \tokscapture{} renvoie en 2 développements % les capturés par \c de la capture numéro . % Si vaut 0, la liste des capturés est renvoyée sous la forme csv % "{},{},...,{}" %--------------------------------------------------------------------- \defKV[pegmatch]{ mode = \def\tktl_match_mode{#1}, assign match=\def\tktl_assign_match{#1}, assign postmatch=\def\tktl_assign_remain{#1}, assign prematch=\def\tktl_assign_prematch{#1}, capture name=\edef\tktl_capture_name{\detokenize{#1}}, expand arg= \tktl_ifinteger{#1} {\def\tktl_expand_arg_value{#1}} {\errmessage{Key "expand arg" require an integer}\def\tktl_expand_arg_value{0}}, } \setKVdefault[pegmatch]{ mode=1,% 0=match la totalité 1=match début des 2=match à n'importe quelle position assign match=\def\matchtoks,% comment assigner les tokens qui ont matché assign postmatch=\def\remaintoks,% comment assigner les tokens qui restent assign prematch=\def\prematchtoks, capture name={},% pour les noms des captures expand arg=0,% développer le 2e argument obligatoire de \ifpegmatch ? } \newcount\tktl_match_position_cnt \def\ifpegmatch{% \tktl_ifnxttok[% {\tktl_ifpegmatch_a} {\tktl_ifpegmatch_a[]}% } \long\def\tktl_ifpegmatch_a[#1]#2#3{% \begingroup \tktl_allow_post_pattern_catpure_true \tktl_allow_captures_true% permet les captures \setKV[pegmatch]{#1}% \tktl_ifnum{\tktl_expand_arg_value>0 } {\tktl_earg\tktl_expand_n_times\tktl_expand_arg_value} {}% \tktl_encode_tokens{#3}% \tktl_init_match \tktl_match_position_cnt=1 \let\tktl_prematch_enctokens\empty \tktl_earg{\tktl_test_match_full_patterns{#2}}{\the\tktl_enctoks_toks}% \iftktl_match_success_ \ifnum\tktl_match_mode=0 \unless\ifx\tktl_postmatch_enctokens\empty \let\tktl_match_full_patterns\empty% si mode 0 et pas de match au début \let\tktl_prematch_enctokens\empty \edef\tktl_postmatch_enctokens{\the\tktl_enctoks_toks}% \tktl_match_success_false \fi\fi \else \ifnum\tktl_match_mode=2 % mode 2 et pas de match au début \tktl_capture_pos_cnt=1 \tktl_earg\tktl_ifpegmatch_b{\tktl_postmatch_enctokens}{#2}% \fi \fi \expanded{% sortir du groupe les tokens avant le match, ceux du match, ceux qui restent et les listes de captures \endgroup \edef\noexpand\matchposition{\iftktl_match_success_\the\tktl_match_position_cnt\else0\fi}% \tktl_unexpand_earg\tktl_decode_enctoks{\tktl_match_full_patterns}{\unexpanded\expandafter{\tktl_assign_match}}% \tktl_unexpand_earg\tktl_decode_enctoks{\tktl_prematch_enctokens}{\unexpanded\expandafter{\tktl_assign_prematch}}% \tktl_unexpand_earg\tktl_decode_enctoks{\tktl_postmatch_enctokens}{\unexpanded\expandafter{\tktl_assign_remain}}% \tktl_unexpand_earg\tktl_process_pos_capture{\tktl_capture_pos_list}{\tktl_capture_name}{\the\tktl_capture_pos_index_cnt}% \tktl_unexpand_earg\tktl_process_tok_capture{\tktl_capture_tok_list}{\tktl_capture_name}{\the\tktl_capture_tok_index_cnt}% \expandafter}% on décide avant de sortir du groupe si on renvoie T ou F \csname tktl_exec_% \iftktl_match_success_ first\else second\fi \endcsname } \long\def\tktl_ifpegmatch_b#1#2{% racourcir les enctokens #1 jusqu'à vide ou match succes #2=pattern \tktl_ifempty_or_space{#1} {} {\tktl_ifpegmatch_c#1\_nil{#2}}% } \long\def\tktl_ifpegmatch_c(#1)#2\_nil#3{% #2=enctokens restant sans le 1er #3=pattern \advance\tktl_capture_pos_cnt 1 \advance\tktl_match_position_cnt 1 \tktl_addtomacro\tktl_prematch_enctokens{(#1)}% \tktl_capture_pos_index_cnt=0 \tktl_capture_tok_index_cnt=0 \let\tktl_capture_pos_list\empty \let\tktl_capture_tok_list\empty \tktl_in_capture_false \tktl_in_patterngroup_false \tktl_test_match_full_patterns{#3}{#2}% \unless\iftktl_match_success_ \tktl_antefi{\tktl_ifpegmatch_b{#2}{#3}}% \fi } %--------------------------------------------------------------------- %--------------------- Macro publique \printtoks --------------------- %--------------------------------------------------------------------- % \printtoks{#1} affiche les tokens composant #1 % avec leur charcode et leur catcode si besoin %--------------------------------------------------------------------- \defKV[printtoks]{ baselinecoeff=\edef\tktl_baselineskip_coeff{\tktl_ifempty_or_space{#1}{1}{#1}}, expand arg= \tktl_ifinteger{#1} {\def\tktl_expand_arg_value{#1}} {\errmessage{Key "expand arg" require an integer}\def\tktl_expand_arg_value{0}}, } \setKVdefault[printtoks]{ expand arg=0, intertoks = 0.33em, printcharcode = true, printcatcode = true, hexcharcode = false,% TODO : convertir en hexadécimal baselinecoeff = 0.8, vlines=true, boxed = true,% le tout dans une \hbox ? code={}, } \def\printtoks{% \tktl_ifnxttok[% {\tktl_printtoks_a} {\tktl_printtoks_a[]}% } \long\def\tktl_printtoks_a[#1]#2{% \begingroup \setKV[printtoks]{#1}% \leavevmode \tktl_ifnum{\tktl_expand_arg_value>0 } {\tktl_earg\tktl_expand_n_times\tktl_expand_arg_value} {}% \tktl_encode_tokens{#2}% \ifboolKV[printtoks]{boxed} {% \hbox{% \ifboolKV[printtoks]{vlines} {% \vrule \hskip.5\dimexpr\useKV[printtoks]{intertoks}\relax } {% \hskip0pt }% \expandafter\tktl_printtoks_b\the\tktl_enctoks_toks(-1:-1)% }% } {% \ifboolKV[printtoks]{vlines} {% \vrule \hskip.5\dimexpr\useKV[printtoks]{intertoks}\relax } {% \hskip0pt }% \expandafter\tktl_printtoks_b\the\tktl_enctoks_toks(-1:-1)% }% \endgroup } \long\def\tktl_printtoks_b(#1:#2){% \tktl_ifnum{#2=-1 } {\unskip} {% \vtop{% \baselineskip=\tktl_baselineskip_coeff\baselineskip \halign{% \hss##\hss\cr \useKV[printtoks]{code}\tktl_ifnum{#2=16 }\string\char#1\relax\cr \ifboolKV[printtoks]{printcharcode} {% \unless\ifnum#2=16 $\scriptscriptstyle \ifboolKV[printtoks]{hexcharcode} {\tktl_dectohex{#1}} {#1}% $% \fi \cr } {}% \ifboolKV[printtoks]{printcatcode} {$\scriptscriptstyle#2$\cr} {}% }% }% \hskip.5\dimexpr\useKV[printtoks]{intertoks}\relax \vrule \hskip.5\dimexpr\useKV[printtoks]{intertoks}\relax \tktl_printtoks_b }% } \def\tktl_dectohex#1{% #1=nombre base 10 \def\tktl_hexbase_result{}% \tktl_dectohex_a{#1}% \tktl_hexbase_result } \def\tktl_dectohex_a#1{% \tktl_test_cnt#1\relax \divide\tktl_test_cnt16 \edef\tktl_hexbase_result{% \ifcase\numexpr#1-16*\tktl_test_cnt\relax 0\or1\or2\or3\or4\or5\or6\or7\or8\or9\or A\or B\or C\or D\or E\or F% \fi \tktl_hexbase_result }% \unless\ifnum\tktl_test_cnt=0 \tktl_antefi{\tktl_earg\tktl_dectohex_a{\the\tktl_test_cnt}}% \fi } %--------------------------------------------------------------------- %--------------------- Macro publique \pegreplace -------------------- %--------------------------------------------------------------------- % \pegreplace[clés=val]{}{}{} % où : % - est un motif ou une succession de motifs à chercher, sachant % que chaque motif, groupe de motifs ou macro-motif déclarée par \defpattern % peut être précédé de "\c" pour en faire une capture. Il peut y avoir autant % de captures que l'on souhaite % Les captures \c après pattern sont interdites -> \errmessage % - est un ensemble de tokens équilibrés en accolades dans lequel le % est cherché % - le est un code tex dans lequel \0 signifie la capture entière % correspondant au , \1 est la première capture faite par un \c, \2 la deuxième, % etc jusqu'à \9. % % Pour chaque correspondance au trouvée dans le , Chaque occurence % de \0...\9 dans le est remplacée par la capture concernée. % % Si la clé "assign" est vide, les tokens obtenus après les remplacements sont laissés % dans le flux de lecture de TeX. Sinon, une assignation à une macro ou à un registre de % tokens (obligatoire si le remplacmeent contient '#') est faite, selon la valeur dubooléen % 'assign to toks' %--------------------------------------------------------------------- \newif\iftktl_allow_post_pattern_catpure_ \defKV[pegreplace]{ assign=\tktl_ifempty_or_space{#1} {\let\tktl_assign_result\tktl_id} {\def\tktl_assign_result{#1}}, expand arg= \tktl_ifinteger{#1} {\def\tktl_expand_arg_value{#1}} {\errmessage{Key "expand arg" require an integer}\def\tktl_expand_arg_value{0}}, } \setKVdefault[pegreplace]{ all=true,% true : remplace toutes les occurrences du pattern false : 1re occurrence seulement assign={},% comment assigner les tokens qui ont matché (si vide -> les afficher) expand arg=0,% développer le 2e argument obligatoire de \pegreplace ? } \def\pegreplace{% \tktl_ifnxttok[%] {\tktl_pegreplace_a} {\tktl_pegreplace_a[]}% } \long\def\tktl_pegreplace_a[#1]#2#3#4{% #1=clé=val #2=pattern #3=texte #4=replace directive \begingroup \tktl_allow_post_pattern_catpure_false \tktl_allow_captures_true% permet les captures \setKV[pegreplace]{#1}% \tktl_ifnum{\tktl_expand_arg_value>0 } {\tktl_expand_n_times\tktl_expand_arg_value} {}% \tktl_encode_tokens{#3}% \edef\tktl_replace_text{\the\tktl_enctoks_toks}% \tktl_encode_tokens{#4}% \edef\tktl_replace_pattern{\the\tktl_enctoks_toks}% \let\tktl_prematch_enctokens\empty \tktl_capture_pos_cnt=1 \ifboolKV[pegreplace]{all} {\tktl_earg\tktl_pegreplace_all_matches} {\tktl_earg\tktl_pegreplace_to_first_match} {\tktl_replace_text}{#2}% \tktl_eaddtomacro\tktl_prematch_enctokens\tktl_postmatch_enctokens \expanded{% sortir du groupe les tokens avant le match, ceux du match, ceux qui restent et les listes de captures \endgroup \tktl_unexpand_earg\tktl_decode_enctoks{\unexpanded\expandafter{\tktl_prematch_enctokens}}{% \ifx\tktl_assign_result\empty \noexpand\tktl_id \else \unexpanded\expandafter{\tktl_assign_result}% \fi }% }% } \def\tktl_init_match{% \tktl_capture_pos_cnt=1 \tktl_capture_pos_index_cnt=0 \tktl_capture_tok_index_cnt=0 \let\tktl_capture_pos_list\empty \let\tktl_capture_tok_list\empty \tktl_in_capture_false \tktl_in_patterngroup_false \def\c{tktl_capture}% } \def\tktl_pegreplace_to_first_match#1#2{% #1=encotkens du texte #2=pattern \tktl_ifempty_or_space{#1}% si plus de tokens -> fin {} {% \tktl_init_match \tktl_test_match_full_patterns{#2}{#1}% match ? \iftktl_match_success_% si match -> faire les remplacements \tktl_pegreplace_to_first_match_b \else% si pas de match -> appel de la macro récursive qui mange un à un les enctokens \tktl_antefi{\expandafter\tktl_pegreplace_to_first_match_a\tktl_postmatch_enctokens\_nil{#2}}% \fi }% } \def\tktl_pegreplace_to_first_match_a(#1)#2\_nil#3{% #2=enctokens restant sans le 1er #3=pattern \tktl_addtomacro\tktl_prematch_enctokens{(#1)}% \tktl_init_match \tktl_test_match_full_patterns{#3}{#2}% \iftktl_match_success_% si ça matche avec les enctokens #2 \tktl_pegreplace_to_first_match_b% faire les remplacements \else% si pas de match -> recommencer avec les enctokens qui restent \tktl_antefi{\tktl_pegreplace_to_first_match{#2}{#3}}% \fi } \def\tktl_pegreplace_to_first_match_b{% \edef\tktl_capture_tok_list{<0=\unexpanded\expandafter{\tktl_match_full_patterns}>\unexpanded\expandafter{\tktl_capture_tok_list}}% \unless\ifx\empty\tktl_capture_tok_list% on procède au remplacement dans \tktl_capture_tok_list \expandafter\tktl_process_replace\tktl_replace_pattern\_nil\00\11\22\33\44\55\66\77\88\99\relax\relax \fi \tktl_eaddtomacro\tktl_prematch_enctokens\tktl_match_full_patterns% ajout les enctokens ayant matché et fin } \def\tktl_pegreplace_all_matches#1#2{% #1=encotkens du texte #2=pattern \tktl_pegreplace_to_first_match{#1}{#2}% \ifx\tktl_postmatch_enctokens\empty \else \tktl_antefi{\tktl_earg\tktl_pegreplace_all_matches{\tktl_postmatch_enctokens}{#2}}% \fi } \long\def\tktl_process_replace#1\_nil#2#3{% #1=liste des remplacements <\d=code> #2=\ #3= \tktl_ifx{\relax#2} {% \def\tktl_match_full_patterns{#1}% } {% définition de la macro de remplacement \long\def\tktl_process_replace_b##1<#3=##2>##3\_nil##4(#2:16)##5\_nil{% \tktl_process_replace_a{##4##2##5}{#2}{#3}% reboucle pour faire tous les remplacements de \ s'il y en a plusieurs }% \tktl_process_replace_a{#1}{#2}{#3}% }% } \long\def\tktl_process_replace_a#1#2#3{% \tktl_ifinstr{(#2:16)}{#1} {% \expandafter\tktl_process_replace_b\tktl_capture_tok_list\_nil#1\_nil } {% \tktl_process_replace#1\_nil }% } %--------------------------------------------------------------------- %--------------------- Macro publique \pegcount ---------------------- %--------------------------------------------------------------------- % \pegcount[clés=valeur]{}{} % compte combien de fois le correspond dans les % % Les captures \c, où qu'elles soient, sont ignorées % % Renvoie : % - le nombre de correspondances (dans le flux ou dans une macro, selon la clé "assign") % - la liste des positions de correspondances (dans la macro spécifiée par la clé "assign positions") % - les correspondances trouvées consultables par \tokscapture{}. % Si =0, livre la liste des correspondances dans une csv : {capt1},{capt2},...,{captn} %--------------------------------------------------------------------- \defKV[pegcount]{ assign=\def\tktl_assign_result{#1}, expand arg= \tktl_ifinteger{#1} {\def\tktl_expand_arg_value{#1}} {% \errmessage{Key "expand arg" requires an integer}% \def\tktl_expand_arg_value{0}% }, assign positions=\def\tktl_pegcount_assign_list{#1}, name=\def\tktl_pegcount_capturename{#1}, } \setKVdefault[pegcount]{ assign={},% comment assigner les tokens qui ont matché (si vide -> les afficher) expand arg=0,% développer le 2e argument obligatoire de \pegcount ? assign positions=\def\matchposlist, name={},% nom des captures des correspondances } \newcount\tktl_pegcount_cnt% compteur des occurrences trouvées \newcount\tktl_pegcount_pos_cnt% compteur de la position des occurrences trouvées \def\pegcount{% \tktl_ifnxttok[%] {\tktl_pegcount_a} {\tktl_pegcount_a[]}% } \long\def\tktl_pegcount_a[#1]#2#3{% #1=clé=val #2=pattern #3=texte \begingroup \tktl_allow_post_pattern_catpure_false \tktl_allow_captures_false% ignore les captures \setKV[pegcount]{#1}% \tktl_ifnum{\tktl_expand_arg_value>0 } {\tktl_expand_n_times\tktl_expand_arg_value} {}% \tktl_encode_tokens{#3}% \tktl_pegcount_cnt=0 \tktl_pegcount_pos_cnt=1 \let\tktl_pegcount_pos_list\empty \let\tktl_pegcount_match_list\empty \tktl_earg\tktl_pegcount_all_matches{\the\tktl_enctoks_toks}{#2}% \expanded{% sortir du groupe les tokens avant le match, ceux du match, ceux qui restent et les listes de captures \endgroup \tktl_unexpand_earg\tktl_process_tok_capture{\tktl_pegcount_match_list}{\tktl_pegcount_capturename}{\the\tktl_pegcount_cnt}% \unexpanded\expandafter{\tktl_pegcount_assign_list}{% \unless\ifx\tktl_pegcount_pos_list\empty \expandafter\tktl_gob_arg\tktl_pegcount_pos_list \fi }% \ifx\tktl_assign_result\empty \noexpand\tktl_id \else \unexpanded\expandafter{\tktl_assign_result}% \fi{\the\tktl_pegcount_cnt}% }% } \long\def\tktl_pegcount_to_first_match#1#2{% #1=encotkens du texte #2=pattern \tktl_ifempty_or_space{#1}% si plus de tokens -> fin {} {% \tktl_init_match \tktl_test_match_full_patterns{#2}{#1}% match ? \iftktl_match_success_% si match \advance\tktl_pegcount_cnt 1 \tktl_eaddtomacro\tktl_pegcount_match_list{\expanded{<\the\tktl_pegcount_cnt={\unexpanded\expandafter{\tktl_match_full_patterns}}>}}% \tktl_eaddtomacro\tktl_pegcount_pos_list{\expandafter,\the\tktl_pegcount_pos_cnt}% \advance\tktl_pegcount_pos_cnt\numexpr\tktl_postmatch_pos-1\relax \else% si pas de match -> appel de la macro récursive qui mange un à un les enctokens \tktl_antefi{\expandafter\tktl_pegcount_to_first_match_a\tktl_postmatch_enctokens\_nil{#2}}% \fi }% } \long\def\tktl_pegcount_to_first_match_a(#1)#2\_nil#3{% \def\tktl_postmatch_enctokens{#2}% \advance\tktl_pegcount_pos_cnt 1 \tktl_pegcount_to_first_match{#2}{#3}% } \long\def\tktl_pegcount_all_matches#1#2{% #1=encotkens du texte #2=pattern \tktl_pegcount_to_first_match{#1}{#2}% \unless\ifx\tktl_postmatch_enctokens\empty \tktl_antefi{\tktl_earg\tktl_pegcount_all_matches{\tktl_postmatch_enctokens}{#2}}% \fi } %--------------------------------------------------------------------- %-------- Capture d'un motif de correspondance pour *1* token -------- %--------------------------------------------------------------------- %%%%%%%%%%%% capture d'un motif de correspondance pour *1* token % Entrée : le motif est composé de : % - macro ayant 1 caractère qui spécifie le type de motif : % \S{} : le token matche avec un des tokens de la % \R{csv de charcodes range "-" ou "" ou "*":csv de catcodes "-" ou "" ou "*"} et si csv de catcodes absent: "*". Macros interdites dans les ranges. % \r{csv de chars range "-" ou "":csv de catcodes "-" ou "" ou "*"} et si csv de catcodes absent: "*". Macros interdites dans les ranges. % % Renvoie : % - \tktl_current_macro est la macro spécifiant le type de motif (\S, \r, ou \R) % - \tktl_current_macro_arg est l'argument de la macro %--------------------------------------------------------------------- \long\def\tktl_grab_tokpattern#1{% #1= pattern unique pour 1 token \let\tktl_current_macro\empty \let\tktl_current_macro_arg\empty \tktl_grab_tokpattern_search_precapture#1\_nil } \def\tktl_grab_tokpattern_search_precapture{% \tktl_futurelet_nospace\tktl__futurtok\tktl_grab_tokpattern_grab_macro } \def\tktl_grab_tokpattern_grab_macro#1{% #1 = macro \tktl_ifinstr{,#1,}{,\S,\r,\R,} {% \def\tktl_current_macro{#1}% \tktl_futurelet_nospace\tktl__futurtok\tktl_grab_tokpattern_grab_macro_arg } {% \errmessage{Found "\detokenize{#1}" when expecting \string\r, \string\R, or \string\S}% }% } \def\tktl_grab_tokpattern_grab_macro_arg{% \tktl_ifx{\bgroup\tktl__futurtok} {% \tktl_grab_tokpattern_grab_macro_arg_a } {% \errmessage{No open brace found after "\detokenize\expandafter{\tktl_current_macro}", aborting pattern}% \let\tktl_current_macro\empty \let\tktl_current_macro_arg\empty \tktl_gob_enctoks_to_end_parse }% } \long\def\tktl_grab_tokpattern_grab_macro_arg_a#1{% argument de la macro \def\tktl_current_macro_arg{#1}% \tktl_grab_pattern_remain } \long\def\tktl_compile_tokpatterns#1#2{% #1=numéro de groupe de pattern (commencer à 0) #2=groupe de pattern \edef\tked_compile_tokpattern_n{\the\numexpr1000*#1}% compte les macros de compilation \tktl_compile_tokpatterns_a#2|\relax|% } \long\def\tktl_compile_tokpatterns_a#1|{% compiler les n patterns dans n+1 macros \edef\tked_compile_tokpattern_n{\the\numexpr\tked_compile_tokpattern_n+1}% \ifx\relax#1% macro n+1 -> échec, aucun match \expandafter\long\expandafter\def\csname tktl_test_match_tokpattern_\romannumeral\tked_compile_tokpattern_n\endcsname(##1:##2){\tktl_match_success_false}% \else \tktl_grab_tokpattern{#1}% \ifx\tktl_current_macro\tktl_macro_S \tktl_earg\tktl_catcode_string{\tktl_current_macro_arg}% prendre en compte les \c{}{texte} dans l'argument de \s \expandafter\long\expandafter\edef\csname tktl_test_match_tokpattern_\romannumeral\tked_compile_tokpattern_n\endcsname(##1:##2){% \noexpand\tktl_ifinstr{(##1:##2)}{\the\tktl_string_toks}% {\noexpand\tktl_match_success_true} {\expandafter\noexpand\csname tktl_test_match_tokpattern_\romannumeral\the\numexpr\tked_compile_tokpattern_n+1\relax\endcsname(##1:##2)}% }% \else% si \R ou \r \ifnum0\ifx\tktl_current_macro\tktl_macro_r1\else\ifx\tktl_current_macro\tktl_macro_R1\fi\fi=1 \tktl_earg\tktl_analyse_R_pattern{\tktl_current_macro_arg}% \ifx\tktl_current_macro\tktl_macro_r \tktl_earg\tktl_convert_charcsv_to_charcodecsv{\tktl_charcode_interval}% \fi \expandafter\long\expandafter\edef\csname tktl_test_match_tokpattern_\romannumeral\tked_compile_tokpattern_n\endcsname(##1:##2){% \noexpand\tktl_iftokmatch{##1}{##2}{\unexpanded\expandafter{\tktl_charcode_interval}}{\unexpanded\expandafter{\tktl_catcode_interval}} {\noexpand\tktl_match_success_true} {\expandafter\noexpand\csname tktl_test_match_tokpattern_\romannumeral\the\numexpr\tked_compile_tokpattern_n+1\relax\endcsname(##1:##2)}% }% \fi\fi \expandafter\tktl_compile_tokpatterns_a \fi } %--------------------------------------------------------------------- %------------------- Macro utilisateur \tokscount ------------------- %--------------------------------------------------------------------- % \tokscount[clés=valeurs]{ | | ... }{texte} % % où est un pattern pour 1 token, c'est-à-dire \r{}, \R{}, \S{} % % Compte combien de tokens dans le texte correspondent aux . % % Renvoie : % - si aucun -> compte le nombre de tokens % - sinon : nombre de correspondances (dans le flux ou dans une macro selon la clé "assign") % - les tokens ayant matché dans la consigne "assign match". Seuls les tokens de catcode % 3,4,7,8,10,11,12,13,16 sont capturés, les autres sont ignorés. % Si la valeur de "assign match" est vide, ne renvoie aucun token %--------------------------------------------------------------------- \defKV[tokscount]{ assign=\def\tktl_assign_result{#1}, expand arg= \tktl_ifinteger{#1} {\def\tktl_expand_arg_value{#1}} {% \errmessage{Key "expand arg" requires an integer}% \def\tktl_expand_arg_value{0}% }, assign match= \def\tktl_assign_tokmatch{#1}, } \setKVdefault[tokscount]{ assign={},% comment assigner les tokens qui ont matché (si vide -> les afficher) expand arg=0,% développer le 2e argument obligatoire de \tokscount ? assign match={},% pas d'export de collecte par défaut } \def\tokscount{% \tktl_ifnxttok[%] {\tktl_tokscount_a} {\tktl_tokscount_a[]}% } \long\def\tktl_tokscount_a[#1]#2#3{% #1=clés/valeurs #2=patterns #3=texte \begingroup \let\tktl_tokscount_collect\empty \tktl_allow_post_pattern_catpure_false \tktl_allow_captures_false% ignore les captures \setKV[tokscount]{#1}% \tktl_ifnum{\tktl_expand_arg_value>0 } {\tktl_expand_n_times\tktl_expand_arg_value} {}% \tktl_encode_tokens{#3}% \tktl_ifempty_or_space{#2} {% \tktl_pegcount_cnt=\tktl_enctoks_len_cnt \let\tktl_tokscount_collect\empty% aucune collecte de tokens si pas de pattern } {% \edef\tktl_tokscount_enctokens{\the\tktl_enctoks_toks}% \tktl_compile_tokpatterns0{#2}% compiler les patterns \tktl_pegcount_cnt=0 \expandafter\tktl_tokscount_b\tktl_tokscount_enctokens(-1:-1)% }% \expanded{% \endgroup \unless\ifx\tktl_assign_tokmatch\empty \tktl_unexpand_earg\tktl_decode_enctoks{\tktl_tokscount_collect}{\unexpanded\expandafter{\tktl_assign_tokmatch}}% \fi \tktl_ifx{\tktl_assign_result\empty} {\noexpand\tktl_id} {\unexpanded\expandafter{\tktl_assign_result}}% {\the\tktl_pegcount_cnt}% }% } \long\def\tktl_tokscount_b(#1:#2){% tester tous les enctokens \ifnum#2>0 \tktl_test_match_tokpattern_i(#1:#2)% le token matche-t-il ? \iftktl_match_success_ \tktl_ifinstr{,#2,}{,3,4,7,8,10,11,12,13,16,}% si catcode pas embêtant (différent de 1, 2 ou 6) {\tktl_addtomacro\tktl_tokscount_collect{(#1:#2)}}% ajouter au collecteur de tokens ayant matché {}% \advance\tktl_pegcount_cnt 1 \fi \expandafter\tktl_tokscount_b \fi } %--------------------------------------------------------------------- %--------------------- Macro utilisateur \toksdo -------------------- %--------------------------------------------------------------------- \newif\iftktl_toksdo_collect_ \newif\iftktl_is_one_token_ \newtoks\tktl_result_toks \def\tktl_check_valid_assign_directive#1#2{% #1=consigne #2=macro recevant le résultat \tktl_ifmacro{#1} {% \def#2{#1}% } {% \tktl_ifinstr\def{#1} {% \tktl_check_valid_assign_directive_a#1\_nil#2% } {% \errmessage{Invalid assignment directive \detokenize{#1} ignored}% \let#2\empty } }% } \def\tktl_check_valid_assign_directive_a#1\def#2\_nil#3{% \tktl_ifempty_or_space{#1} {% \tktl_ifmacro{#2} {% \def#3{\def#2}% } {% \errmessage{Invalid assignment directive \detokenize{#1\def#2} ignored}% \let#3\empty } } {% \errmessage{Invalid assignment directive \detokenize{#1\def#2} ignored}% \let#3\empty }% } \defKV[toksdo]{% assign=\tktl_ifempty_or_space{#1} {\let\tktl_assign_result\empty} {\tktl_check_valid_assign_directive{#1}\tktl_assign_result}, expand arg= \tktl_ifinteger{#1} {\def\tktl_expand_arg_value{#1}} {% \errmessage{Key "expand arg" requires an integer}% \def\tktl_expand_arg_value{0}% }, collect = \testboolKV{#1}\tktl_toksdo_collect_true\tktl_toksdo_collect_false, } \setKVdefault[toksdo]{ assign={},% comment assigner les tokens qui ont matché (si vide -> les afficher) expand arg=0,% développer le 2e argument obligatoire de \toksdo ? collect = true,% collecter les tokens obtenus ? } \long\def\tktl_add_token_toksdo#1#2{% \advance\tktl_enctoks_len_cnt1 \tktl_earg\tktl_add_to_parsetoks{\expandafter(\expandafter[\the\tktl_enctoks_len_cnt]#1:#2)}% }% \def\toksdo{% \tktl_ifnxttok[%] {\tktl_toksdo_a} {\tktl_toksdo_a[]}% } \long\def\tktl_toksdo_a[#1]#2#3{% #1=clés/valeurs #2=patterns #3=texte \let\setcharcode_saved\setcharcode \let\setcharcode\tktl_setcharcode \let\setcatcode_saved\setcatcode \let\setcatcode\tktl_setcatcode \let\deltok_saved\deltok \let\deltok\tktl_deltok \let\addtok_saved\addtok \let\addtok\tktl_addtokt \let\selfcharcode_saved\selfcharcode \let\selfcatcode_saved\selfcatcode \let\selfindex_saved\selfindex \let\tktl_toksdo_collect\empty \tktl_allow_post_pattern_catpure_false \tktl_allow_captures_false% ignore les captures \restoreKV[toksdo]% \setKV[toksdo]{#1}% \let\tktl_add_token_saved\tktl_add_token \let\tktl_add_token\tktl_add_token_toksdo \tktl_ifnum{\tktl_expand_arg_value>0 } {\tktl_expand_n_times\tktl_expand_arg_value} {}% \tktl_encode_tokens{#3}% \let\tktl_add_token\tktl_add_token_saved \edef\tokslen{\the\tktl_enctoks_len_cnt}% nombre de tokens \edef\tktl_toksdo_enctokens{\the\tktl_enctoks_toks}% \tktl_ifempty_or_space{#2} {% \errmessage{No pattern and action found, using \string\R\detokenize{{*:*}->{}}}% \tktl_compile_toksdo_patterns{\R{*:*}->{}}% } {% \tktl_compile_toksdo_patterns{#2}% }% \let\tktl_toksdo_pattern_maxindex\tktl_toksdo_pattern_index \let\tktl_toksdo_enctok_collect\empty \expandafter\tktl_toksdo_b\tktl_toksdo_enctokens([-1]-1:-1)% \let\setcharcode\setcharcode_saved \let\setcatcode\setcatcode_saved \let\deltok\deltok_saved \let\addtok\addtok_saved \tktl_ifx{\tktl_assign_result\empty} {% \tktl_earg\tktl_decode_enctoks{\tktl_toksdo_enctok_collect}{\tktl_result_toks}% \the\tktl_result_toks } {% \tktl_etwoargs\tktl_decode_enctoks{\tktl_toksdo_enctok_collect}{\tktl_assign_result}% }% } \long\def\tktl_toksdo_b([#1]#2:#3){% tester chaque enctokens \ifnum#3>0 \def\selfindex{#1}% \def\selfcharcode{#2}% \def\selfcatcode{#3}% \def\tktl_toksdo_pattern_index{-1}% \let\tktl_addtok_code\tktl_addtok_code_default% ajouter le token seul par défaut \def\tktl_current_token{(#2:#3)}% \tktl_is_one_token_true \tktl_toksdo_c(#2:#3)% \unless\ifx\tktl_addtok_code\empty \iftktl_toksdo_collect_ % si ajout de token et collecte des tokens \ifx\tktl_addtok_code\tktl_addtok_code_default% si pas de redéfinition par \addtok, ne pas perdre de temps et ajouter le token \tktl_eaddtomacro\tktl_toksdo_enctok_collect\tktl_current_token \else \begingroup \tktl_earg\tktl_encode_tokens{\tktl_addtok_code}% \expanded{% \endgroup \noexpand\tktl_subst_all% remplacer {(\noexpand\self:16)}% tous les \self par \tktl_current_token qui est (charcode:catcode) lorsqu'unique token {\unexpanded\expandafter{\tktl_current_token}} {\the\tktl_enctoks_toks}% et ajouter le résultat à \tktl_toksdo_enctok_collect {\noexpand\tktl_eaddtomacro\noexpand\tktl_toksdo_enctok_collect}% }% \fi \fi\fi \expandafter\tktl_toksdo_b \fi } \long\def\tktl_toksdo_c(#1:#2){% tester le token pour tous les patterns/actions spécifiés par l'utilisateur \edef\tktl_toksdo_pattern_index{\the\numexpr\tktl_toksdo_pattern_index+1}% \csname tktl_test_match_tokpattern_\romannumeral\numexpr1000*\tktl_toksdo_pattern_index+1\endcsname(#1:#2)% le token matche-t-il ? \iftktl_match_success_% si oui, effectuer l'action prévue et fin \expandafter\let\expandafter\tktl_do_next\csname tktl_toksdo_action_\romannumeral\tktl_toksdo_pattern_index\endcsname \else \ifnum\tktl_toksdo_pattern_index=\tktl_toksdo_pattern_maxindex\relax% dernier pattern atteint \let\tktl_do_next\empty \else \def\tktl_do_next{\tktl_toksdo_c(#1:#2)}% \fi \fi \tktl_do_next } \long\def\tktl_compile_toksdo_patterns#1{% #1 contient -> , -> , etc \def\tktl_toksdo_pattern_index{-1}% \tktl_compile_toksdo_patterns_a#1,\relax,% } \long\def\tktl_compile_toksdo_patterns_a#1,{% \tktl_ifempty_or_space{#1} {% ignorer si vide \tktl_compile_toksdo_patterns_a } {% \def\tktl_current_pattern{#1}% \unless\ifx\tktl_end_of_patterns\tktl_current_pattern \tktl_antefi{\tktl_compile_toksdo_patterns_b#1,}% \fi }% } \long\def\tktl_compile_toksdo_patterns_b#1->#2,{% \edef\tktl_toksdo_pattern_index{\the\numexpr\tktl_toksdo_pattern_index+1}% \tktl_earg\tktl_compile_tokpatterns\tktl_toksdo_pattern_index{#1}% compiler les patterns \expandafter\def \csname tktl_toksdo_action_\romannumeral\tktl_toksdo_pattern_index\expandafter\expandafter\expandafter\endcsname \expandafter\expandafter\expandafter{\tktl_stripsp{#2}}% sauvegarder l'action à faire \tktl_compile_toksdo_patterns_a } \def\tktl_addtokt#{% comment ajouter le token en cours. Dans #1, la macro \self serprésente le token modifié (ou pas) \def\tktl_addtok_code } \def\tktl_addtok_code_default{% \self } \long\def\tktl_subst_all#1#2#3#4{% remplace toutes les occurrences de #1 par #2 dans #3, #4=consigne d'assignation \long\def\tktl_subst_all_a##1#1##2\_nil{% \tktl_ifinstr{#1}{##1#2##2} {\tktl_subst_all_a##1#2##2\_nil} {#4{##1#2##2}}% }% \tktl_ifinstr{#1}{#3} {\tktl_subst_all_a#3\_nil} {#4{#3}}% } \def\tktl_deltok{% équivalent à \addotk{} \let\tktl_addtok_code\empty } \def\tktl_setcharcode#1{% \iftktl_is_one_token_% que si c'est 1 seul token \unless\ifnum\selfcatcode=16 % ne rien toucher si une macro \ifnum\numexpr#1<0 \errmessage{Negative charcode in \string\setcharcode\detokenize{{#1}}, directive ignored}% \else \edef\selfcharcode{\the\numexpr#1\relax}% \tktl_earg{\expandafter\tktl_setcharcode_a\tktl_current_token}\selfcharcode \fi \fi \fi } \def\tktl_setcharcode_a(#1:#2)#3{% \def\tktl_current_token{(#3:#2)}% } \def\tktl_setcatcode#1{% \iftktl_is_one_token_% que si c'est 1 seul token \edef\tktl_tmp{\the\numexpr#1\relax}% \ifnum\selfcatcode=16 % si token courant est une macro \ifnum\tktl_tmp=12 % et si catcode demandé=12 \begingroup \tktl_eearg\tktl_encode_tokens{\expandafter\string\selfcharcode}% \expandafter \endgroup\expandafter \def\expandafter\tktl_current_token\expandafter{\the\tktl_enctoks_toks}% \tktl_is_one_token_false \fi \else % si si token courant n'est pas une macro \tktl_earg\tktl_ifinstr{\expandafter,\tktl_tmp,}{,1,2,3,4,6,7,8,10,11,12,13,} {% \let\selfcatcode\tktl_tmp \tktl_earg{\expandafter\tktl_setcatcode_a\tktl_current_token}\selfcatcode } {% \errmessage{Illegal catcode in \string\setcatcode\detokenize{{#1}}, directive ignored}% }% \fi \fi } \def\tktl_setcatcode_a(#1:#2)#3{% \def\tktl_current_token{(#1:#3)}% } \tktl_restore_catcode \endinput