%%
%% This is file `piton.sty',
%% generated with the docstrip utility.
%%
%% The original source files were:
%%
%% piton.dtx  (with options: `STY')
%% -------------------------------------------
%% Copyright (C) 2022-2025 by F. Pantigny
%% 
%% This file may be distributed and/or modified under the
%% conditions of the LaTeX Project Public License, either
%% version 1.3 of this license or (at your option) any later
%% version.  The latest version of this license is in:
%% 
%%      http://www.latex-project.org/lppl.txt
%% 
%% and version 1.3 or later is part of all distributions of
%% LaTeX version 2005/12/01 or later.
%% -------------------------------------------
%% 
\def\PitonFileVersion{4.2c}
\def\PitonFileDate{2025/03/04}





\NeedsTeXFormat{LaTeX2e}
\RequirePackage{l3keys2e}
\ProvidesExplPackage
  {piton}
  {\PitonFileDate}
  {\PitonFileVersion}
  {Highlight informatic listings with LPEG on LuaLaTeX}
\RequirePackage { amstext }
\cs_new_protected:Npn \__piton_error:n { \msg_error:nn { piton } }
\cs_new_protected:Npn \__piton_warning:n { \msg_warning:nn { piton } }
\cs_new_protected:Npn \__piton_error:nn { \msg_error:nnn { piton } }
\cs_new_protected:Npn \__piton_error:nnn { \msg_error:nnnn { piton } }
\cs_new_protected:Npn \__piton_fatal:n { \msg_fatal:nn { piton } }
\cs_new_protected:Npn \__piton_fatal:nn { \msg_fatal:nnn { piton } }
\cs_new_protected:Npn \__piton_msg_new:nn { \msg_new:nnn { piton } }
\cs_new_protected:Npn \__piton_gredirect_none:n #1
  {
    \group_begin:
    \globaldefs = 1
    \msg_redirect_name:nnn { piton } { #1 } { none }
    \group_end:
  }
\cs_new_protected:Npn \__piton_msg_new:nnn #1 #2 #3
  {
    \bool_if:NTF \g__piton_messages_for_Overleaf_bool
      { \msg_new:nnn { piton } { #1 } { #2 \\ #3 } }
      { \msg_new:nnnn { piton } { #1 } { #2 } { #3 } }
  }
\cs_new_protected:Npn \__piton_error_or_warning:n
  { \bool_if:NTF \g__piton_messages_for_Overleaf_bool \__piton_warning:n \__piton_error:n }
\bool_new:N \g__piton_messages_for_Overleaf_bool
\bool_gset:Nn \g__piton_messages_for_Overleaf_bool
  {
       \str_if_eq_p:on \c_sys_jobname_str { _region_ }  % for Emacs
    || \str_if_eq_p:on \c_sys_jobname_str { output }   % for Overleaf
  }
\__piton_msg_new:nn { LuaLaTeX~mandatory }
  {
    LuaLaTeX~is~mandatory.\\
    The~package~'piton'~requires~the~engine~LuaLaTeX.\\
    \str_if_eq:onT \c_sys_jobname_str { output }
      { If~you~use~Overleaf,~you~can~switch~to~LuaLaTeX~in~the~"Menu". \\}
    If~you~go~on,~the~package~'piton'~won't~be~loaded.
  }
\sys_if_engine_luatex:F { \msg_critical:nn { piton } { LuaLaTeX~mandatory } }
\RequirePackage { luatexbase }
\RequirePackage { luacode }
\__piton_msg_new:nnn { piton.lua~not~found }
  {
    The~file~'piton.lua'~can't~be~found.\\
    This~error~is~fatal.\\
    If~you~want~to~know~how~to~retrieve~the~file~'piton.lua',~type~H~<return>.
  }
  {
    On~the~site~CTAN,~go~to~the~page~of~'piton':~https://ctan.org/pkg/piton.~
    The~file~'README.md'~explains~how~to~retrieve~the~files~'piton.sty'~and~
    'piton.lua'.
  }
\file_if_exist:nF { piton.lua } { \__piton_fatal:n { piton.lua~not~found } }
\bool_new:N \g__piton_footnotehyper_bool
\bool_new:N \g__piton_footnote_bool
\bool_new:N \g__piton_math_comments_bool
\bool_new:N \g__piton_beamer_bool
\tl_new:N \g__piton_escape_inside_tl
\bool_new:N \l__piton_old_PitonInputFile_bool
\keys_define:nn { piton / package }
  {
    footnote .bool_gset:N = \g__piton_footnote_bool ,
    footnotehyper .bool_gset:N = \g__piton_footnotehyper_bool ,
    footnote .usage:n = load ,
    footnotehyper .usage:n = load ,

    beamer .bool_gset:N = \g__piton_beamer_bool ,
    beamer .default:n = true ,
    beamer .usage:n = load ,
    old-PitonInputFile .bool_set:N = \l__piton_old_PitonInputFile_bool ,
    old-PitonInputFile .default:n = true ,
    old-PitonInputFile .usage:n = load ,

    unknown .code:n = \__piton_error:n { Unknown~key~for~package }
  }
\__piton_msg_new:nn { Unknown~key~for~package }
  {
    Unknown~key.\\
    You~have~used~the~key~'\l_keys_key_str'~but~the~only~keys~available~here~
    are~'beamer',~'footnote',~'footnotehyper'~and~'old-PitonInputFile'.~
    Other~keys~are~available~in~\token_to_str:N \PitonOptions.\\
    That~key~will~be~ignored.
  }
\ProcessKeysOptions { piton / package }
\msg_new:nnn { piton } { old-PitonInputFile }
  {
    Be~careful:~The~key~'old-PitonInputFile'~will~be~deleted~
    in~a~future~version~of~'piton'.
  }
\bool_if:NT \l__piton_old_PitonInputFile_bool
  { \msg_warning:nn { piton } { old-PitonInputFile } }
\IfClassLoadedTF { beamer } { \bool_gset_true:N \g__piton_beamer_bool } { }
\IfPackageLoadedTF { beamerarticle } { \bool_gset_true:N \g__piton_beamer_bool } { }
\lua_now:n { piton = piton~or~{ } }
\bool_if:NT \g__piton_beamer_bool { \lua_now:n { piton.beamer = true } }
\hook_gput_code:nnn { begindocument / before } { . }
  { \IfPackageLoadedTF { xcolor } { } { \usepackage { xcolor } } }
\__piton_msg_new:nn { footnote~with~footnotehyper~package }
  {
    Footnote~forbidden.\\
    You~can't~use~the~option~'footnote'~because~the~package~
    footnotehyper~has~already~been~loaded.~
    If~you~want,~you~can~use~the~option~'footnotehyper'~and~the~footnotes~
    within~the~environments~of~piton~will~be~extracted~with~the~tools~
    of~the~package~footnotehyper.\\
    If~you~go~on,~the~package~footnote~won't~be~loaded.
  }
\__piton_msg_new:nn { footnotehyper~with~footnote~package }
  {
    You~can't~use~the~option~'footnotehyper'~because~the~package~
    footnote~has~already~been~loaded.~
    If~you~want,~you~can~use~the~option~'footnote'~and~the~footnotes~
    within~the~environments~of~piton~will~be~extracted~with~the~tools~
    of~the~package~footnote.\\
    If~you~go~on,~the~package~footnotehyper~won't~be~loaded.
  }
\bool_if:NT \g__piton_footnote_bool
  {
    \IfClassLoadedTF { beamer }
      { \bool_gset_false:N \g__piton_footnote_bool }
      {
        \IfPackageLoadedTF { footnotehyper }
          { \__piton_error:n { footnote~with~footnotehyper~package } }
          { \usepackage { footnote } }
      }
  }
\bool_if:NT \g__piton_footnotehyper_bool
  {
    \IfClassLoadedTF { beamer }
      { \bool_gset_false:N \g__piton_footnote_bool }
      {
        \IfPackageLoadedTF { footnote }
          { \__piton_error:n { footnotehyper~with~footnote~package } }
          { \usepackage { footnotehyper } }
        \bool_gset_true:N \g__piton_footnote_bool
      }
  }
\lua_now:n
  {
    piton.BeamerCommands = lpeg.P [[\uncover]]
       + [[\only]] + [[\visible]] + [[\invisible]] + [[\alert]] + [[\action]]
    piton.beamer_environments = { "uncoverenv" , "onlyenv" , "visibleenv" ,
               "invisibleenv" ,  "alertenv" ,  "actionenv" }
    piton.DetectedCommands = lpeg.P ( false )
    piton.last_code = ''
    piton.last_language = ''
  }
\str_new:N \l_piton_language_str
\str_set:Nn \l_piton_language_str { python }
\tl_new:N \g_piton_last_code_tl
\seq_new:N \l__piton_path_seq
\str_new:N \l__piton_path_write_str
\bool_new:N \l__piton_in_PitonOptions_bool
\bool_new:N \l__piton_in_PitonInputFile_bool
\tl_new:N \l__piton_font_command_tl
\tl_set:Nn \l__piton_font_command_tl { \ttfamily }
\int_new:N \l__piton_nb_lines_int
\int_new:N \l__piton_nb_non_empty_lines_int
\int_new:N \g__piton_line_int
\tl_new:N \g__piton_aux_tl
\int_new:N \l__piton_splittable_int
\int_set:Nn \l__piton_splittable_int { 100 }
\tl_new:N \l__piton_split_separation_tl
\tl_set:Nn \l__piton_split_separation_tl
  { \vspace { \baselineskip } \vspace { -1.25pt } }
\clist_new:N \l__piton_bg_color_clist
\tl_new:N \l__piton_prompt_bg_color_tl
\str_new:N \l__piton_begin_range_str
\str_new:N \l__piton_end_range_str
\str_new:N \l__piton_file_name_str
\int_new:N \g__piton_env_int
\str_new:N \l__piton_write_str
\seq_new:N \g__piton_write_seq
\bool_new:N \l__piton_show_spaces_bool
\bool_new:N \l__piton_break_lines_in_Piton_bool
\bool_new:N \l__piton_indent_broken_lines_bool
\tl_new:N \l__piton_continuation_symbol_tl
\tl_set:Nn \l__piton_continuation_symbol_tl { + }
\tl_new:N \l__piton_csoi_tl
\tl_set:Nn \l__piton_csoi_tl { $ \hookrightarrow \; $  }
\tl_new:N \l__piton_end_of_broken_line_tl
\tl_set:Nn \l__piton_end_of_broken_line_tl { \hspace*{0.5em} \textbackslash }
\bool_new:N \l__piton_break_lines_in_piton_bool

\dim_new:N \l__piton_width_dim
\dim_new:N \l__piton_line_width_dim
\bool_new:N \l__piton_width_min_bool
\dim_new:N \g__piton_tmp_width_dim
\dim_new:N \l__piton_left_margin_dim
\bool_new:N \l__piton_left_margin_auto_bool
\dim_new:N \l__piton_numbers_sep_dim
\dim_set:Nn \l__piton_numbers_sep_dim { 0.7 em }
\seq_new:N \g__piton_languages_seq
\int_new:N \l__piton_tab_size_int
\int_set:Nn \l__piton_tab_size_int { 4 }
\cs_new_protected:Npn \__piton_tab:
  {
    \bool_if:NTF \l__piton_show_spaces_bool
      {
        \hbox_set:Nn \l_tmpa_box
          { \prg_replicate:nn \l__piton_tab_size_int { ~ } }
        \dim_set:Nn \l_tmpa_dim { \box_wd:N \l_tmpa_box }
        \( \mathcolor { gray }
             { \hbox_to_wd:nn \l_tmpa_dim { \rightarrowfill } } \)
      }
      { \hbox:n { \prg_replicate:nn \l__piton_tab_size_int { ~ } } }
    \int_gadd:Nn \g__piton_indentation_int \l__piton_tab_size_int
  }
\int_new:N \l__piton_gobble_int
\tl_set_eq:NN \l__piton_space_in_string_tl \nobreakspace
\int_new:N \g__piton_indentation_int
\cs_new_protected:Npn \__piton_leading_space:
  { \int_gincr:N \g__piton_indentation_int }
\cs_new_protected:Npn \__piton_label:n #1
  {
    \bool_if:NTF \l__piton_line_numbers_bool
      {
        \@bsphack
        \protected@write \@auxout { }
          {
            \string \newlabel { #1 }
            {
              { \int_eval:n { \g__piton_visual_line_int + 1 } }
              { \thepage }
            }
          }
        \@esphack
      }
      { \__piton_error:n { label~with~lines~numbers } }
  }
\cs_new_protected:Npn \__piton_marker_beginning:n #1 { }
\cs_new_protected:Npn \__piton_marker_end:n #1 { }
\tl_new:N \g__piton_begin_line_hook_tl
\cs_new_protected:Npn \__piton_prompt:
  {
    \tl_gset:Nn \g__piton_begin_line_hook_tl
      {
        \tl_if_empty:NF \l__piton_prompt_bg_color_tl
          { \clist_set:No \l__piton_bg_color_clist \l__piton_prompt_bg_color_tl }
      }
  }
\cs_new_protected:Npn \__piton_trailing_space: { }
\cs_generate_variant:Nn \__piton_replace_spaces:n { o }
\cs_new_protected:Npn \__piton_replace_spaces:n #1
  {
    \tl_set:Nn \l_tmpa_tl { #1 }
    \bool_if:NTF \l__piton_show_spaces_bool
      {
        \tl_set:Nn \l__piton_space_in_string_tl { ␣ } % U+2423
        \regex_replace_all:nnN { \x20 } { ␣ } \l_tmpa_tl
      }
      {
        \bool_if:NT \l__piton_break_lines_in_Piton_bool
          {
            \regex_replace_all:nnN
              { \x20 }
              { \c { __piton_breakable_space: } }
              \l_tmpa_tl
            \regex_replace_all:nnN
              { \c { l__piton_space_in_string_tl } }
              { \c { __piton_breakable_space: } }
              \l_tmpa_tl
          }
      }
    \l_tmpa_tl
  }

\cs_set_protected:Npn \__piton_end_line: { }
\cs_set_protected:Npn \__piton_begin_line: #1 \__piton_end_line:
  {
    \group_begin:
    \g__piton_begin_line_hook_tl
    \int_gzero:N \g__piton_indentation_int
    \bool_if:NTF \l__piton_width_min_bool
      \__piton_put_in_coffin_ii:n
      \__piton_put_in_coffin_i:n
      {
        \language = -1
        \raggedright
        \strut
        \__piton_replace_spaces:n { #1 }
        \strut \hfil
      }
    \hbox_set:Nn \l_tmpa_box
      {
        \skip_horizontal:N \l__piton_left_margin_dim
        \bool_if:NT \l__piton_line_numbers_bool
          {
            \int_set:Nn \l_tmpa_int
              {
                \lua_now:e
                  {
                    tex.sprint
                      (
                        luatexbase.catcodetables.expl ,
                        tostring
                          ( piton.empty_lines
                              [ \int_eval:n { \g__piton_line_int + 1 } ]
                          )
                      )
                  }
              }
            \bool_lazy_or:nnT
              { \int_compare_p:nNn \l_tmpa_int = \c_one_int }
              { ! \l__piton_skip_empty_lines_bool }
              { \int_gincr:N \g__piton_visual_line_int }
            \bool_lazy_or:nnT
              { \int_compare_p:nNn \l_tmpa_int = \c_one_int }
              { ! \l__piton_skip_empty_lines_bool && \l__piton_label_empty_lines_bool }
              \__piton_print_number:
          }
        \clist_if_empty:NF \l__piton_bg_color_clist
          {
            \dim_compare:nNnT \l__piton_left_margin_dim = \c_zero_dim
              { \skip_horizontal:n { 0.5 em } }
          }
        \coffin_typeset:Nnnnn \l_tmpa_coffin T l \c_zero_dim \c_zero_dim
      }
    \box_set_dp:Nn \l_tmpa_box { \box_dp:N \l_tmpa_box + 1.25 pt }
    \box_set_ht:Nn \l_tmpa_box { \box_ht:N \l_tmpa_box + 1.25 pt }
    \mode_leave_vertical:
    \clist_if_empty:NTF \l__piton_bg_color_clist
      { \box_use_drop:N \l_tmpa_box }
      {
        \vtop
          {
            \hbox:n
              {
                \__piton_color:N \l__piton_bg_color_clist
                \vrule height \box_ht:N \l_tmpa_box
                       depth \box_dp:N \l_tmpa_box
                       width \l__piton_width_dim
              }
            \skip_vertical:n { - \box_ht_plus_dp:N \l_tmpa_box }
            \box_use_drop:N \l_tmpa_box
          }
      }
    \group_end:
    \tl_gclear:N \g__piton_begin_line_hook_tl
  }
\cs_set_protected:Npn \__piton_put_in_coffin_i:n
  { \vcoffin_set:Nnn \l_tmpa_coffin \l__piton_line_width_dim }
\cs_set_protected:Npn \__piton_put_in_coffin_ii:n #1
  {
    \hbox_set:Nn \l_tmpa_box { #1 }
    \dim_compare:nNnT { \box_wd:N \l_tmpa_box } > \g__piton_tmp_width_dim
      { \dim_gset:Nn \g__piton_tmp_width_dim { \box_wd:N \l_tmpa_box } }
    \hcoffin_set:Nn \l_tmpa_coffin
      {
        \hbox_to_wd:nn \l__piton_line_width_dim
          { \hbox_unpack:N \l_tmpa_box \hfil }
      }
  }
\cs_set_protected:Npn \__piton_color:N #1
  {
    \int_set:Nn \l_tmpa_int { \clist_count:N #1 }
    \int_set:Nn \l_tmpb_int { \int_mod:nn \g__piton_line_int \l_tmpa_int + 1 }
    \tl_set:Ne \l_tmpa_tl { \clist_item:Nn #1 \l_tmpb_int }
    \tl_if_eq:NnTF \l_tmpa_tl { none }
      { \dim_zero:N \l__piton_width_dim }
      { \__piton_color_i:o \l_tmpa_tl }
  }
\cs_generate_variant:Nn \__piton_color_i:n { o }
\cs_set_protected:Npn \__piton_color_i:n #1
  {
    \tl_if_head_eq_meaning:nNTF { #1 } [
      {
        \tl_set:Nn \l_tmpa_tl { #1 }
        \tl_set_rescan:Nno \l_tmpa_tl { } \l_tmpa_tl
        \exp_last_unbraced:No \color \l_tmpa_tl
      }
      { \color { #1 } }
  }
\cs_new_protected:Npn \__piton_newline:
  {
    \bool_if:NT \g__piton_footnote_bool \endsavenotes
    \int_gincr:N \g__piton_line_int
    \par
    \kern -2.5 pt
    \int_case:nn
      {
        \lua_now:e
          {
            tex.sprint
              (
                luatexbase.catcodetables.expl ,
                tostring ( piton.lines_status [ \int_use:N \g__piton_line_int ] )
              )
          }
      }
      { 1 { \penalty 100 } 2 \nobreak }
   \bool_if:NT \g__piton_footnote_bool \savenotes
  }
\cs_set_protected:Npn \__piton_breakable_space:
  {
    \discretionary
      { \hbox:n { \color { gray } \l__piton_end_of_broken_line_tl } }
      {
        \hbox_overlap_left:n
          {
            {
              \normalfont \footnotesize \color { gray }
              \l__piton_continuation_symbol_tl
            }
            \skip_horizontal:n { 0.3 em }
            \clist_if_empty:NF \l__piton_bg_color_clist
              { \skip_horizontal:n { 0.5 em } }
          }
        \bool_if:NT \l__piton_indent_broken_lines_bool
          {
            \hbox:n
              {
                \prg_replicate:nn { \g__piton_indentation_int } { ~ }
                { \color { gray } \l__piton_csoi_tl }
              }
          }
      }
      { \hbox { ~ } }
  }
\bool_new:N \l__piton_line_numbers_bool
\bool_new:N \l__piton_skip_empty_lines_bool
\bool_set_true:N \l__piton_skip_empty_lines_bool
\bool_new:N \l__piton_line_numbers_absolute_bool
\tl_new:N \l__piton_line_numbers_format_bool
\tl_set:Nn \l__piton_line_numbers_format_tl { \footnotesize \color { gray } }
\bool_new:N \l__piton_label_empty_lines_bool
\bool_set_true:N \l__piton_label_empty_lines_bool
\int_new:N \l__piton_number_lines_start_int
\bool_new:N \l__piton_resume_bool
\bool_new:N \l__piton_split_on_empty_lines_bool
\bool_new:N \l__piton_splittable_on_empty_lines_bool
\keys_define:nn { PitonOptions / marker }
  {
    beginning .code:n = \cs_set:Nn \__piton_marker_beginning:n { #1 } ,
    beginning .value_required:n = true ,
    end .code:n = \cs_set:Nn \__piton_marker_end:n { #1 } ,
    end .value_required:n = true ,
    include-lines .bool_set:N = \l__piton_marker_include_lines_bool ,
    include-lines .default:n = true ,
    unknown .code:n = \__piton_error:n { Unknown~key~for~marker }
  }
\keys_define:nn { PitonOptions / line-numbers }
  {
    true .code:n = \bool_set_true:N \l__piton_line_numbers_bool ,
    false .code:n = \bool_set_false:N \l__piton_line_numbers_bool ,

    start .code:n =
      \bool_set_true:N \l__piton_line_numbers_bool
      \int_set:Nn \l__piton_number_lines_start_int { #1 }  ,
    start .value_required:n = true ,

    skip-empty-lines .code:n =
      \bool_if:NF \l__piton_in_PitonOptions_bool
        { \bool_set_true:N \l__piton_line_numbers_bool }
      \str_if_eq:nnTF { #1 } { false }
        { \bool_set_false:N \l__piton_skip_empty_lines_bool }
        { \bool_set_true:N \l__piton_skip_empty_lines_bool } ,
    skip-empty-lines .default:n = true ,

    label-empty-lines .code:n =
      \bool_if:NF \l__piton_in_PitonOptions_bool
        { \bool_set_true:N \l__piton_line_numbers_bool }
      \str_if_eq:nnTF { #1 } { false }
        { \bool_set_false:N \l__piton_label_empty_lines_bool }
        { \bool_set_true:N \l__piton_label_empty_lines_bool } ,
    label-empty-lines .default:n = true ,

    absolute .code:n =
      \bool_if:NTF \l__piton_in_PitonOptions_bool
        { \bool_set_true:N \l__piton_line_numbers_absolute_bool }
        { \bool_set_true:N \l__piton_line_numbers_bool }
      \bool_if:NT \l__piton_in_PitonInputFile_bool
        {
          \bool_set_true:N \l__piton_line_numbers_absolute_bool
          \bool_set_false:N \l__piton_skip_empty_lines_bool
        } ,
    absolute .value_forbidden:n = true ,

    resume .code:n =
      \bool_set_true:N \l__piton_resume_bool
      \bool_if:NF \l__piton_in_PitonOptions_bool
        { \bool_set_true:N \l__piton_line_numbers_bool } ,
    resume .value_forbidden:n = true ,

    sep .dim_set:N = \l__piton_numbers_sep_dim ,
    sep .value_required:n = true ,

    format .tl_set:N = \l__piton_line_numbers_format_tl ,
    format .value_required:n = true ,

    unknown .code:n = \__piton_error:n { Unknown~key~for~line-numbers }
  }
\keys_define:nn { PitonOptions }
  {
    break-strings-anywhere .bool_set:N = \l__piton_break_strings_anywhere_bool ,
    break-strings-anywhere .default:n = true ,
    break-numbers-anywhere .bool_set:N = \l__piton_break_numbers_anywhere_bool ,
    break-numbers-anywhere .default:n = true  ,
    detected-commands .code:n =
      \lua_now:n { piton.addDetectedCommands('#1') } ,
    detected-commands .value_required:n = true ,
    detected-commands .usage:n = preamble ,
    detected-beamer-commands .code:n =
      \lua_now:n { piton.addBeamerCommands('#1') } ,
    detected-beamer-commands .value_required:n = true ,
    detected-beamer-commands .usage:n = preamble ,
    detected-beamer-environments .code:n =
      \lua_now:n { piton.addBeamerEnvironments('#1') } ,
    detected-beamer-environments .value_required:n = true ,
    detected-beamer-environments .usage:n = preamble ,
    begin-escape .code:n =
      \lua_now:e { piton.begin_escape = "\lua_escape:n{#1}" } ,
    begin-escape .value_required:n = true ,
    begin-escape .usage:n = preamble ,

    end-escape   .code:n =
      \lua_now:e { piton.end_escape = "\lua_escape:n{#1}" } ,
    end-escape   .value_required:n = true ,
    end-escape .usage:n = preamble ,

    begin-escape-math .code:n =
      \lua_now:e { piton.begin_escape_math = "\lua_escape:n{#1}" } ,
    begin-escape-math .value_required:n = true ,
    begin-escape-math .usage:n = preamble ,

    end-escape-math .code:n =
      \lua_now:e { piton.end_escape_math = "\lua_escape:n{#1}" } ,
    end-escape-math .value_required:n = true ,
    end-escape-math .usage:n = preamble ,

    comment-latex .code:n = \lua_now:n { comment_latex = "#1" } ,
    comment-latex .value_required:n = true ,
    comment-latex .usage:n = preamble ,

    math-comments .bool_gset:N = \g__piton_math_comments_bool ,
    math-comments .default:n  = true ,
    math-comments .usage:n = preamble ,
    language      .code:n =
      \str_set:Ne \l_piton_language_str { \str_lowercase:n { #1 } } ,
    language      .value_required:n  = true ,
    path          .code:n =
      \seq_clear:N \l__piton_path_seq
      \clist_map_inline:nn { #1 }
        {
          \str_set:Nn \l_tmpa_str { ##1 }
          \seq_put_right:No \l__piton_path_seq \l_tmpa_str
        } ,
    path             .value_required:n  = true ,
    path             .initial:n         = . ,
    path-write       .str_set:N         = \l__piton_path_write_str ,
    path-write       .value_required:n  = true ,
    font-command     .tl_set:N          = \l__piton_font_command_tl ,
    font-command     .value_required:n  = true ,
    gobble           .int_set:N         = \l__piton_gobble_int ,
    gobble           .value_required:n  = true ,
    auto-gobble      .code:n            = \int_set:Nn \l__piton_gobble_int { -1 } ,
    auto-gobble      .value_forbidden:n = true ,
    env-gobble       .code:n            = \int_set:Nn \l__piton_gobble_int { -2 } ,
    env-gobble       .value_forbidden:n = true ,
    tabs-auto-gobble .code:n            = \int_set:Nn \l__piton_gobble_int { -3 } ,
    tabs-auto-gobble .value_forbidden:n = true ,

    splittable-on-empty-lines .bool_set:N = \l__piton_splittable_on_empty_lines_bool ,
    splittable-on-empty-lines .default:n  = true ,

    split-on-empty-lines .bool_set:N = \l__piton_split_on_empty_lines_bool ,
    split-on-empty-lines .default:n  = true ,

    split-separation .tl_set:N         = \l__piton_split_separation_tl ,
    split-separation .value_required:n = true ,

    marker .code:n =
      \bool_lazy_or:nnTF
        \l__piton_in_PitonInputFile_bool
        \l__piton_in_PitonOptions_bool
        { \keys_set:nn { PitonOptions / marker } { #1 } }
        { \__piton_error:n { Invalid~key } } ,
    marker .value_required:n = true ,

    line-numbers .code:n =
      \keys_set:nn { PitonOptions / line-numbers } { #1 } ,
    line-numbers .default:n = true ,

    splittable       .int_set:N         = \l__piton_splittable_int ,
    splittable       .default:n         = 1 ,
    background-color .clist_set:N       = \l__piton_bg_color_clist ,
    background-color .value_required:n  = true ,
    prompt-background-color .tl_set:N         = \l__piton_prompt_bg_color_tl ,
    prompt-background-color .value_required:n = true ,

    width .code:n =
      \str_if_eq:nnTF  { #1 } { min }
        {
          \bool_set_true:N \l__piton_width_min_bool
          \dim_zero:N \l__piton_width_dim
        }
        {
          \bool_set_false:N \l__piton_width_min_bool
          \dim_set:Nn \l__piton_width_dim { #1 }
        } ,
    width .value_required:n  = true ,

    write .str_set:N = \l__piton_write_str ,
    write .value_required:n = true ,

    left-margin      .code:n =
      \str_if_eq:nnTF { #1 } { auto }
        {
          \dim_zero:N \l__piton_left_margin_dim
          \bool_set_true:N \l__piton_left_margin_auto_bool
        }
        {
          \dim_set:Nn \l__piton_left_margin_dim { #1 }
          \bool_set_false:N \l__piton_left_margin_auto_bool
        } ,
    left-margin      .value_required:n  = true ,

    tab-size         .int_set:N         = \l__piton_tab_size_int ,
    tab-size         .value_required:n  = true ,
    show-spaces      .bool_set:N        = \l__piton_show_spaces_bool ,
    show-spaces      .value_forbidden:n = true ,
    show-spaces-in-strings .code:n      =
        \tl_set:Nn \l__piton_space_in_string_tl { ␣ } , % U+2423
    show-spaces-in-strings .value_forbidden:n = true ,
    break-lines-in-Piton .bool_set:N    = \l__piton_break_lines_in_Piton_bool ,
    break-lines-in-Piton .default:n     = true ,
    break-lines-in-piton .bool_set:N    = \l__piton_break_lines_in_piton_bool ,
    break-lines-in-piton .default:n     = true ,
    break-lines .meta:n = { break-lines-in-piton , break-lines-in-Piton } ,
    break-lines .value_forbidden:n      = true ,
    indent-broken-lines .bool_set:N     = \l__piton_indent_broken_lines_bool ,
    indent-broken-lines .default:n      = true ,
    end-of-broken-line  .tl_set:N       = \l__piton_end_of_broken_line_tl ,
    end-of-broken-line  .value_required:n = true ,
    continuation-symbol .tl_set:N       = \l__piton_continuation_symbol_tl ,
    continuation-symbol .value_required:n = true ,
    continuation-symbol-on-indentation .tl_set:N = \l__piton_csoi_tl ,
    continuation-symbol-on-indentation .value_required:n = true ,

    first-line .code:n = \__piton_in_PitonInputFile:n
      { \int_set:Nn \l__piton_first_line_int { #1 } } ,
    first-line .value_required:n = true ,

    last-line .code:n = \__piton_in_PitonInputFile:n
      { \int_set:Nn \l__piton_last_line_int { #1 } } ,
    last-line .value_required:n = true ,

    begin-range .code:n = \__piton_in_PitonInputFile:n
      { \str_set:Nn \l__piton_begin_range_str { #1 } } ,
    begin-range .value_required:n = true ,

    end-range .code:n = \__piton_in_PitonInputFile:n
      { \str_set:Nn \l__piton_end_range_str { #1 } } ,
    end-range .value_required:n = true ,

    range .code:n = \__piton_in_PitonInputFile:n
      {
        \str_set:Nn \l__piton_begin_range_str { #1 }
        \str_set:Nn \l__piton_end_range_str { #1 }
      } ,
    range .value_required:n = true ,

    env-used-by-split .code:n =
      \lua_now:n { piton.env_used_by_split = '#1' } ,
    env-used-by-split .initial:n = Piton ,

    resume .meta:n = line-numbers/resume ,

    unknown .code:n = \__piton_error:n { Unknown~key~for~PitonOptions } ,

    % deprecated
    all-line-numbers .code:n =
      \bool_set_true:N \l__piton_line_numbers_bool
      \bool_set_false:N \l__piton_skip_empty_lines_bool ,
    all-line-numbers .value_forbidden:n = true
  }
\cs_new_protected:Npn \__piton_in_PitonInputFile:n #1
  {
    \bool_if:NTF \l__piton_in_PitonInputFile_bool
      { #1 }
      { \__piton_error:n { Invalid~key } }
  }
\NewDocumentCommand \PitonOptions { m }
  {
    \bool_set_true:N \l__piton_in_PitonOptions_bool
    \keys_set:nn { PitonOptions } { #1 }
    \bool_set_false:N \l__piton_in_PitonOptions_bool
  }
\NewDocumentCommand \__piton_fake_PitonOptions { }
  { \keys_set:nn { PitonOptions } }
\int_new:N \g__piton_visual_line_int
\cs_new_protected:Npn \__piton_incr_visual_line:
  {
    \bool_if:NF \l__piton_skip_empty_lines_bool
      { \int_gincr:N \g__piton_visual_line_int }
  }
\cs_new_protected:Npn \__piton_print_number:
  {
    \hbox_overlap_left:n
      {
        {
          \l__piton_line_numbers_format_tl
          { \int_to_arabic:n \g__piton_visual_line_int }
        }
        \skip_horizontal:N \l__piton_numbers_sep_dim
      }
  }
\cs_new_protected:Npn \__piton_write_aux:
  {
    \tl_if_empty:NF \g__piton_aux_tl
      {
        \iow_now:Nn \@mainaux { \ExplSyntaxOn }
        \iow_now:Ne \@mainaux
          {
            \tl_gset:cn { c__piton_ \int_use:N \g__piton_env_int _ tl }
              { \exp_not:o \g__piton_aux_tl }
          }
        \iow_now:Nn \@mainaux { \ExplSyntaxOff }
      }
    \tl_gclear:N \g__piton_aux_tl
  }
\cs_new_protected:Npn \__piton_width_to_aux:
  {
    \tl_gput_right:Ne \g__piton_aux_tl
      {
        \dim_set:Nn \l__piton_line_width_dim
          { \dim_eval:n { \g__piton_tmp_width_dim } }
      }
  }
\NewDocumentCommand { \NewPitonLanguage } { O { } m ! o }
  {
    \tl_if_novalue:nTF { #3 }
      { \__piton_NewPitonLanguage:nnn { #1 } { #2 } }
      { \__piton_NewPitonLanguage:nnnnn { #1 } { #2 } { #3 } }
  }
\prop_new:N \g__piton_languages_prop
\keys_define:nn { NewPitonLanguage }
  {
    morekeywords .code:n = ,
    otherkeywords .code:n = ,
    sensitive .code:n = ,
    keywordsprefix .code:n = ,
    moretexcs .code:n = ,
    morestring .code:n = ,
    morecomment .code:n = ,
    moredelim .code:n = ,
    moredirectives .code:n = ,
    tag .code:n = ,
    alsodigit .code:n = ,
    alsoletter .code:n = ,
    alsoother .code:n = ,
    unknown .code:n = \__piton_error:n { Unknown~key~NewPitonLanguage }
  }
\cs_new_protected:Npn \__piton_NewPitonLanguage:nnn #1 #2 #3
  {
    \tl_set:Ne \l_tmpa_tl
      {
        \tl_if_blank:nF { #1 } { [ \str_lowercase:n { #1 } ] }
        \str_lowercase:n { #2 }
      }
    \keys_set:nn { NewPitonLanguage } { #3 }
    \prop_gput:Non \g__piton_languages_prop \l_tmpa_tl { #3 }
    \__piton_NewPitonLanguage:on \l_tmpa_tl { #3 }
  }
\cs_generate_variant:Nn \__piton_NewPitonLanguage:nn { o }
\cs_new_protected:Npn \__piton_NewPitonLanguage:nn #1 #2
  {
    \hook_gput_code:nnn { begindocument } { . }
      { \lua_now:e { piton.new_language("#1","\lua_escape:n{#2}") } }
  }
\cs_new_protected:Npn \__piton_NewPitonLanguage:nnnnn #1 #2 #3 #4 #5
  {
    \tl_set:Ne \l_tmpa_tl
      {
        \tl_if_blank:nF { #3 } { [ \str_lowercase:n { #3 } ] }
        \str_lowercase:n { #4 }
      }
    \prop_get:NoNTF \g__piton_languages_prop \l_tmpa_tl \l_tmpb_tl
      { \__piton_NewPitonLanguage:nnno { #1 } { #2 } { #5 } \l_tmpb_tl }
      { \__piton_error:n { Language~not~defined } }
  }
\cs_generate_variant:Nn \__piton_NewPitonLanguage:nnnn { n n n o }
\cs_new_protected:Npn \__piton_NewPitonLanguage:nnnn #1 #2 #3 #4
  { \__piton_NewPitonLanguage:nnn { #1 } { #2 } { #4 , #3 } }
\NewDocumentCommand { \piton } { }
  { \peek_meaning:NTF \bgroup \__piton_piton_standard \__piton_piton_verbatim }
\NewDocumentCommand { \__piton_piton_standard } { m }
  {
    \group_begin:
    \bool_lazy_or:nnT
    \l__piton_break_lines_in_piton_bool
    \l__piton_break_strings_anywhere_bool
      { \tl_set_eq:NN \l__piton_space_in_string_tl \space }
    \automatichyphenmode = 1
    \cs_set_eq:NN \\ \c_backslash_str
    \cs_set_eq:NN \% \c_percent_str
    \cs_set_eq:NN \{ \c_left_brace_str
    \cs_set_eq:NN \} \c_right_brace_str
    \cs_set_eq:NN \$ \c_dollar_str
    \cs_set_eq:cN { ~ } \space
    \cs_set_eq:NN \__piton_begin_line: \prg_do_nothing:
    \tl_set:Ne \l_tmpa_tl
      {
        \lua_now:e
          { piton.ParseBis('\l_piton_language_str',token.scan_string()) }
          { #1 }
      }
    \bool_if:NTF \l__piton_show_spaces_bool
      { \regex_replace_all:nnN { \x20 } { ␣ } \l_tmpa_tl } % U+2423
      {
        \bool_if:NT \l__piton_break_lines_in_piton_bool
          { \regex_replace_all:nnN { \x20 } { \x20 } \l_tmpa_tl }
      }
    \if_mode_math:
       \text { \l__piton_font_command_tl \l_tmpa_tl }
    \else:
       \l__piton_font_command_tl \l_tmpa_tl
    \fi:
    \group_end:
  }
\NewDocumentCommand { \__piton_piton_verbatim } { v }
  {
    \group_begin:
    \automatichyphenmode = 1
    \cs_set_eq:NN \__piton_begin_line: \prg_do_nothing:
    \tl_set:Ne \l_tmpa_tl
      {
        \lua_now:e
          { piton.Parse('\l_piton_language_str',token.scan_string()) }
          { #1 }
      }
    \bool_if:NT \l__piton_show_spaces_bool
      { \regex_replace_all:nnN { \x20 } { ␣ } \l_tmpa_tl } % U+2423
    \if_mode_math:
       \text { \l__piton_font_command_tl \l_tmpa_tl }
    \else:
       \l__piton_font_command_tl \l_tmpa_tl
    \fi:
    \group_end:
  }

\cs_new_protected:Npn \__piton_piton:n #1
  { \tl_if_blank:nF { #1 } { \__piton_piton_i:n { #1 } } }

\cs_new_protected:Npn \__piton_piton_i:n #1
  {
    \group_begin:
    \cs_set_eq:NN \__piton_begin_line: \prg_do_nothing:
    \cs_set:cpn { pitonStyle _ \l_piton_language_str  _ Prompt } { }
    \cs_set:cpn { pitonStyle _ Prompt } { }
    \cs_set_eq:NN \__piton_trailing_space: \space
    \tl_set:Ne \l_tmpa_tl
      {
        \lua_now:e
          { piton.ParseTer('\l_piton_language_str',token.scan_string()) }
          { #1 }
      }
    \bool_if:NT \l__piton_show_spaces_bool
      { \regex_replace_all:nnN { \x20 } { ␣ } \l_tmpa_tl } % U+2423
    \__piton_replace_spaces:o \l_tmpa_tl
    \group_end:
  }
\cs_new:Npn \__piton_pre_env:
  {
    \automatichyphenmode = 1
    \int_gincr:N \g__piton_env_int
    \tl_gclear:N \g__piton_aux_tl
    \dim_compare:nNnT \l__piton_width_dim = \c_zero_dim
      { \dim_set_eq:NN \l__piton_width_dim \linewidth }
    \cs_if_exist_use:c { c__piton _ \int_use:N \g__piton_env_int _ tl }
    \bool_if:NF \l__piton_resume_bool { \int_gzero:N \g__piton_visual_line_int }
    \dim_gzero:N \g__piton_tmp_width_dim
    \int_gzero:N \g__piton_line_int
    \dim_zero:N \parindent
    \dim_zero:N \lineskip
    \cs_set_eq:NN \label \__piton_label:n
  }
\cs_generate_variant:Nn \__piton_compute_left_margin:nn { n o }
\cs_new_protected:Npn \__piton_compute_left_margin:nn #1 #2
  {
    \bool_lazy_and:nnT \l__piton_left_margin_auto_bool \l__piton_line_numbers_bool
       {
        \hbox_set:Nn \l_tmpa_box
          {
            \l__piton_line_numbers_format_tl
            \bool_if:NTF \l__piton_skip_empty_lines_bool
              {
                \lua_now:n
                  { piton.#1(token.scan_argument()) }
                  { #2 }
                \int_to_arabic:n
                  { \g__piton_visual_line_int + \l__piton_nb_non_empty_lines_int }
              }
              {
                \int_to_arabic:n
                  { \g__piton_visual_line_int + \l__piton_nb_lines_int }
              }
           }
         \dim_set:Nn \l__piton_left_margin_dim
           { \box_wd:N \l_tmpa_box + \l__piton_numbers_sep_dim + 0.1 em }
       }
  }
\cs_new_protected:Npn \__piton_compute_width:
  {
    \dim_compare:nNnTF \l__piton_line_width_dim = \c_zero_dim
      {
        \dim_set_eq:NN \l__piton_line_width_dim \l__piton_width_dim
        \clist_if_empty:NTF \l__piton_bg_color_clist
          { \dim_sub:Nn \l__piton_line_width_dim \l__piton_left_margin_dim }
          {
            \dim_sub:Nn \l__piton_line_width_dim { 0.5 em }
            \dim_compare:nNnTF \l__piton_left_margin_dim = \c_zero_dim
              { \dim_sub:Nn \l__piton_line_width_dim { 0.5 em } }
              { \dim_sub:Nn \l__piton_line_width_dim \l__piton_left_margin_dim }
          }
      }
      {
        \dim_set_eq:NN \l__piton_width_dim \l__piton_line_width_dim
        \clist_if_empty:NTF \l__piton_bg_color_clist
          { \dim_add:Nn \l__piton_width_dim \l__piton_left_margin_dim }
          {
            \dim_add:Nn \l__piton_width_dim { 0.5 em }
            \dim_compare:nNnTF \l__piton_left_margin_dim = \c_zero_dim
              { \dim_add:Nn \l__piton_width_dim { 0.5 em } }
              { \dim_add:Nn \l__piton_width_dim \l__piton_left_margin_dim }
          }
      }
  }
\NewDocumentCommand { \NewPitonEnvironment } { m m m m }
  {
    \use:x
      {
        \cs_set_protected:Npn
          \use:c { __piton_collect_ #1 :w }
          ####1
          \c_backslash_str end \c_left_brace_str #1 \c_right_brace_str
      }
          {
            \group_end:
            \mode_if_vertical:TF \noindent \newline
            \lua_now:e { piton.CountLines ( '\lua_escape:n{##1}' ) }
            \__piton_compute_left_margin:nn { CountNonEmptyLines } { ##1 }
            \__piton_compute_width:
            \l__piton_font_command_tl
            \dim_zero:N \parskip
            \noindent
            \str_if_empty:NTF \l__piton_path_write_str
              { \lua_now:e { piton.write = "\l__piton_write_str" } }
              {
                \lua_now:e
                  { piton.write = "\l__piton_path_write_str / \l__piton_write_str" }
              }
            \str_if_empty:NTF \l__piton_write_str
              { \lua_now:n { piton.write = '' } }
              {
                \seq_if_in:NoTF \g__piton_write_seq \l__piton_write_str
                  { \lua_now:n { piton.write_mode = "a" } }
                  {
                    \lua_now:n { piton.write_mode = "w" }
                    \seq_gput_left:No \g__piton_write_seq \l__piton_write_str
                  }
              }
            \bool_if:NTF \l__piton_split_on_empty_lines_bool
              \__piton_retrieve_gobble_split_parse:n
              \__piton_retrieve_gobble_parse:n
              { ##1 }
            \bool_if:NT \l__piton_width_min_bool \__piton_width_to_aux:
            \end { #1 }
            \__piton_write_aux:
          }
    \NewDocumentEnvironment { #1 } { #2 }
      {
        \cs_set_eq:NN \PitonOptions \__piton_fake_PitonOptions
        #3
        \__piton_pre_env:
        \int_compare:nNnT \l__piton_number_lines_start_int > \c_zero_int
          { \int_gset:Nn \g__piton_visual_line_int { \l__piton_number_lines_start_int - 1 } }
        \group_begin:
        \tl_map_function:nN
          { \ \\ \{ \} \$ \& \# \^ \_ \% \~ \^^I }
          \char_set_catcode_other:N
        \use:c { __piton_collect_ #1 :w }
      }
      { #4 }
    \AddToHook { env / #1 / begin } { \char_set_catcode_other:N \^^M }
  }
\cs_new_protected:Npn \__piton_retrieve_gobble_parse:n
  {
    \lua_now:e
      {
        piton.RetrieveGobbleParse
          (
            '\l_piton_language_str' ,
            \int_use:N \l__piton_gobble_int ,
            \bool_if:NTF \l__piton_splittable_on_empty_lines_bool
              { \int_eval:n { - \l__piton_splittable_int } }
              { \int_use:N \l__piton_splittable_int } ,
            token.scan_argument ( )
          )
      }
  }
\cs_new_protected:Npn \__piton_retrieve_gobble_split_parse:n
  {
    \lua_now:e
      {
        piton.RetrieveGobbleSplitParse
          (
            '\l_piton_language_str' ,
            \int_use:N \l__piton_gobble_int ,
            \int_use:N \l__piton_splittable_int ,
            token.scan_argument ( )
          )
      }
  }
\bool_if:NTF \g__piton_beamer_bool
  {
    \NewPitonEnvironment { Piton } { d < > O { } }
      {
        \keys_set:nn { PitonOptions } { #2 }
        \tl_if_novalue:nTF { #1 }
          { \begin { uncoverenv } }
          { \begin { uncoverenv } < #1 > }
      }
      { \end { uncoverenv } }
  }
  {
    \NewPitonEnvironment { Piton } { O { } }
      { \keys_set:nn { PitonOptions } { #1 } }
      { }
  }
\NewDocumentCommand { \PitonInputFileTF } { d < > O { } m m m }
  {
    \group_begin:
    \bool_if:NTF \l__piton_old_PitonInputFile_bool
      {
        \bool_set_false:N \l_tmpa_bool
        \seq_map_inline:Nn \l__piton_path_seq
          {
            \str_set:Nn \l__piton_file_name_str { ##1 / #3 }
            \file_if_exist:nT { \l__piton_file_name_str }
              {
                \__piton_input_file:nn { #1 } { #2 }
                \bool_set_true:N \l_tmpa_bool
                \seq_map_break:
              }
          }
        \bool_if:NTF \l_tmpa_bool { #4 } { #5 }
      }
      {
        \seq_concat:NNN
          \l_file_search_path_seq
          \l__piton_path_seq
          \l_file_search_path_seq
        \file_get_full_name:nNTF { #3 } \l__piton_file_name_str
          {
            \__piton_input_file:nn { #1 } { #2 }
            #4
          }
          { #5 }
      }
    \group_end:
  }
\cs_new_protected:Npn \__piton_unknown_file:n #1
  { \msg_error:nnn { piton } { Unknown~file } { #1 } }
\NewDocumentCommand { \PitonInputFile } { d < > O { } m }
  { \PitonInputFileTF < #1 >  [ #2 ] { #3 } { } { \__piton_unknown_file:n { #3 } } }
\NewDocumentCommand { \PitonInputFileT } { d < > O { } m m }
  { \PitonInputFileTF < #1 >  [ #2 ] { #3 } { #4 } { \__piton_unknown_file:n { #3 } } }
\NewDocumentCommand { \PitonInputFileF } { d < > O { } m m }
  { \PitonInputFileTF < #1 >  [ #2 ] { #3 } { } { #4 } }
\cs_new_protected:Npn \__piton_input_file:nn #1 #2
  {
    \tl_if_novalue:nF { #1 }
      {
        \bool_if:NTF \g__piton_beamer_bool
          { \begin { uncoverenv } < #1 > }
          { \__piton_error_or_warning:n { overlay~without~beamer } }
      }
    \group_begin:
      \iow_log:e {(\l__piton_file_name_str)}
      \int_zero_new:N \l__piton_first_line_int
      \int_zero_new:N \l__piton_last_line_int
      \int_set_eq:NN \l__piton_last_line_int \c_max_int
      \bool_set_true:N \l__piton_in_PitonInputFile_bool
      \keys_set:nn { PitonOptions } { #2 }
      \bool_if:NT \l__piton_line_numbers_absolute_bool
        { \bool_set_false:N \l__piton_skip_empty_lines_bool }
      \bool_if:nTF
        {
          (
            \int_compare_p:nNn \l__piton_first_line_int > \c_zero_int
            || \int_compare_p:nNn \l__piton_last_line_int < \c_max_int
          )
          && ! \str_if_empty_p:N \l__piton_begin_range_str
        }
        {
          \__piton_error_or_warning:n { bad~range~specification }
          \int_zero:N \l__piton_first_line_int
          \int_set_eq:NN \l__piton_last_line_int \c_max_int
        }
        {
          \str_if_empty:NF \l__piton_begin_range_str
            {
              \__piton_compute_range:
              \bool_lazy_or:nnT
                \l__piton_marker_include_lines_bool
                { ! \str_if_eq_p:NN \l__piton_begin_range_str \l__piton_end_range_str }
                {
                  \int_decr:N \l__piton_first_line_int
                  \int_incr:N \l__piton_last_line_int
                }
            }
        }
      \__piton_pre_env:
      \bool_if:NT \l__piton_line_numbers_absolute_bool
        { \int_gset:Nn \g__piton_visual_line_int { \l__piton_first_line_int - 1 } }
      \int_compare:nNnT \l__piton_number_lines_start_int > \c_zero_int
        {
          \int_gset:Nn \g__piton_visual_line_int
            { \l__piton_number_lines_start_int - 1 }
        }
      \int_compare:nNnT \g__piton_visual_line_int < \c_zero_int
        { \int_gzero:N \g__piton_visual_line_int }
      \mode_if_vertical:TF \mode_leave_vertical: \newline
      \dim_zero:N \parskip % added 2025/03/03
      \lua_now:e { piton.CountLinesFile ( '\l__piton_file_name_str' ) }
      \__piton_compute_left_margin:no { CountNonEmptyLinesFile } \l__piton_file_name_str
      \__piton_compute_width:
      \l__piton_font_command_tl
      \lua_now:e
        {
          piton.ParseFile(
           '\l_piton_language_str' ,
           '\l__piton_file_name_str' ,
           \int_use:N \l__piton_first_line_int ,
           \int_use:N \l__piton_last_line_int ,
           \bool_if:NTF \l__piton_splittable_on_empty_lines_bool
             { \int_eval:n { - \l__piton_splittable_int } }
             { \int_use:N \l__piton_splittable_int } ,
           \bool_if:NTF \l__piton_split_on_empty_lines_bool { 1 } { 0 } )
        }
      \bool_if:NT \l__piton_width_min_bool \__piton_width_to_aux:
    \group_end:
    \tl_if_novalue:nF { #1 }
      { \bool_if:NT \g__piton_beamer_bool { \end { uncoverenv } } }
    \__piton_write_aux:
  }
\cs_new_protected:Npn \__piton_compute_range:
  {
    \str_set:Ne \l_tmpa_str { \__piton_marker_beginning:n \l__piton_begin_range_str }
    \str_set:Ne \l_tmpb_str { \__piton_marker_end:n \l__piton_end_range_str }
    \regex_replace_all:nVN { \\\# } \c_hash_str \l_tmpa_str
    \regex_replace_all:nVN { \\\# } \c_hash_str \l_tmpb_str
    \lua_now:e
      {
        piton.ComputeRange
          ( '\l_tmpa_str' , '\l_tmpb_str' , '\l__piton_file_name_str' )
      }
  }
\NewDocumentCommand { \PitonStyle } { m }
  {
    \cs_if_exist_use:cF { pitonStyle _ \l_piton_language_str  _ #1 }
      { \use:c { pitonStyle _ #1 } }
  }
\NewDocumentCommand { \SetPitonStyle } { O { } m }
  {
    \str_clear_new:N \l__piton_SetPitonStyle_option_str
    \str_set:Ne \l__piton_SetPitonStyle_option_str { \str_lowercase:n { #1 } }
    \str_if_eq:onT \l__piton_SetPitonStyle_option_str { current-language }
      { \str_set_eq:NN \l__piton_SetPitonStyle_option_str \l_piton_language_str }
    \keys_set:nn { piton / Styles } { #2 }
  }
\cs_new_protected:Npn \__piton_math_scantokens:n #1
  { \normalfont \scantextokens { \begin{math} #1 \end{math} } }
\clist_new:N \g__piton_styles_clist
\clist_gset:Nn \g__piton_styles_clist
  {
    Comment ,
    Comment.LaTeX ,
    Discard ,
    Exception ,
    FormattingType ,
    Identifier.Internal ,
    Identifier ,
    InitialValues ,
    Interpol.Inside ,
    Keyword ,
    Keyword.Governing ,
    Keyword.Constant ,
    Keyword2 ,
    Keyword3 ,
    Keyword4 ,
    Keyword5 ,
    Keyword6 ,
    Keyword7 ,
    Keyword8 ,
    Keyword9 ,
    Name.Builtin ,
    Name.Class ,
    Name.Constructor ,
    Name.Decorator ,
    Name.Field ,
    Name.Function ,
    Name.Module ,
    Name.Namespace ,
    Name.Table ,
    Name.Type ,
    Number ,
    Number.Internal ,
    Operator ,
    Operator.Word ,
    Preproc ,
    Prompt ,
    String.Doc ,
    String.Interpol ,
    String.Long ,
    String.Long.Internal ,
    String.Short ,
    String.Short.Internal ,
    Tag ,
    TypeParameter ,
    UserFunction ,
    TypeExpression ,
    Directive
  }

\clist_map_inline:Nn \g__piton_styles_clist
  {
    \keys_define:nn { piton / Styles }
      {
        #1 .value_required:n = true ,
        #1 .code:n =
          \tl_set:cn
            {
              pitonStyle _
              \str_if_empty:NF \l__piton_SetPitonStyle_option_str
                { \l__piton_SetPitonStyle_option_str _ }
              #1
            }
            { ##1 }
      }
  }

\keys_define:nn { piton / Styles }
  {
    String       .meta:n = { String.Long = #1 , String.Short = #1 } ,
    Comment.Math .tl_set:c = pitonStyle _ Comment.Math  ,
    unknown         .code:n =
      \__piton_error:n { Unknown~key~for~SetPitonStyle }
  }
\SetPitonStyle[OCaml]
  {
    TypeExpression =
      \SetPitonStyle { Identifier = \PitonStyle { Name.Type } }
      \__piton_piton:n ,
  }
\clist_gput_left:Nn \g__piton_styles_clist { String }
\clist_gsort:Nn \g__piton_styles_clist
  {
    \str_compare:nNnTF { #1 } < { #2 }
      \sort_return_same:
      \sort_return_swapped:
  }
\cs_set_eq:NN \__piton_break_strings_anywhere:n \prg_do_nothing:

\cs_set_eq:NN \__piton_break_numbers_anywhere:n \prg_do_nothing:

\cs_new_protected:Npn \__piton_actually_break_anywhere:n #1
  {
    \tl_set:Nn \l_tmpa_tl { #1 }
    \regex_replace_all:nnN { \x20 } { \c { space } } \l_tmpa_tl
    \seq_clear:N \l_tmpa_seq % added 2025/03/03
    \tl_map_inline:Nn \l_tmpa_tl
      { \seq_put_right:Nn \l_tmpa_seq { ##1 } }
    \seq_use:Nn \l_tmpa_seq { \- }
  }
\cs_new_protected:Npn \__piton_string_long:n #1
  {
    \PitonStyle { String.Long }
      {
        \bool_if:NT \l__piton_break_strings_anywhere_bool
          { \__piton_actually_break_anywhere:n }
        { #1 }
      }
  }
\cs_new_protected:Npn \__piton_string_short:n #1
  {
    \PitonStyle { String.Short }
      {
        \bool_if:NT \l__piton_break_strings_anywhere_bool
          { \__piton_actually_break_anywhere:n }
        { #1 }
      }
  }
\cs_new_protected:Npn \__piton_number:n #1
  {
    \PitonStyle { Number }
      {
        \bool_if:NT \l__piton_break_numbers_anywhere_bool
          { \__piton_actually_break_anywhere:n }
        { #1 }
      }
  }
\SetPitonStyle
  {
    Comment               = \color[HTML]{0099FF} \itshape ,
    Exception             = \color[HTML]{CC0000} ,
    Keyword               = \color[HTML]{006699} \bfseries ,
    Keyword.Governing     = \color[HTML]{006699} \bfseries ,
    Keyword.Constant      = \color[HTML]{006699} \bfseries ,
    Name.Builtin          = \color[HTML]{336666} ,
    Name.Decorator        = \color[HTML]{9999FF},
    Name.Class            = \color[HTML]{00AA88} \bfseries ,
    Name.Function         = \color[HTML]{CC00FF} ,
    Name.Namespace        = \color[HTML]{00CCFF} ,
    Name.Constructor      = \color[HTML]{006000} \bfseries ,
    Name.Field            = \color[HTML]{AA6600} ,
    Name.Module           = \color[HTML]{0060A0} \bfseries ,
    Name.Table            = \color[HTML]{309030} ,
    Number                = \color[HTML]{FF6600} ,
    Number.Internal       = \__piton_number:n ,
    Operator              = \color[HTML]{555555} ,
    Operator.Word         = \bfseries ,
    String                = \color[HTML]{CC3300} ,
    String.Long.Internal  = \__piton_string_long:n ,
    String.Short.Internal = \__piton_string_short:n ,
    String.Doc            = \color[HTML]{CC3300} \itshape ,
    String.Interpol       = \color[HTML]{AA0000} ,
    Comment.LaTeX         = \normalfont \color[rgb]{.468,.532,.6} ,
    Name.Type             = \color[HTML]{336666} ,
    InitialValues         = \__piton_piton:n ,
    Interpol.Inside       = \l__piton_font_command_tl \__piton_piton:n ,
    TypeParameter         = \color[HTML]{336666} \itshape ,
    Preproc               = \color[HTML]{AA6600} \slshape ,
    Identifier.Internal   = \__piton_identifier:n ,
    Identifier            = ,
    Directive             = \color[HTML]{AA6600} ,
    Tag                   = \colorbox{gray!10},
    UserFunction          = \PitonStyle{Identifier} ,
    Prompt                = ,
    Discard               = \use_none:n
  }
\hook_gput_code:nnn { begindocument } { . }
  {
    \bool_if:NT \g__piton_math_comments_bool
      { \SetPitonStyle { Comment.Math = \__piton_math_scantokens:n } }
  }
\NewDocumentCommand { \SetPitonIdentifier } { o m m }
  {
    \clist_set:Nn \l_tmpa_clist { #2 }
    \tl_if_novalue:nTF { #1 }
      {
        \clist_map_inline:Nn \l_tmpa_clist
          { \cs_set:cpn { PitonIdentifier _ ##1 } { #3 } }
      }
      {
        \str_set:Ne \l_tmpa_str { \str_lowercase:n { #1 } }
        \str_if_eq:onT \l_tmpa_str { current-language }
          { \str_set_eq:NN \l_tmpa_str \l_piton_language_str }
        \clist_map_inline:Nn \l_tmpa_clist
          { \cs_set:cpn { PitonIdentifier _ \l_tmpa_str _ ##1 } { #3 } }
      }
  }
\cs_new_protected:Npn \__piton_identifier:n #1
  {
    \cs_if_exist_use:cF { PitonIdentifier _ \l_piton_language_str _ #1 }
      {
        \cs_if_exist_use:cF { PitonIdentifier _ #1 }
          { \PitonStyle { Identifier } }
      }
    { #1 }
  }
\cs_new_protected:cpn { pitonStyle _ Name.Function.Internal } #1
  {
    { \PitonStyle { Name.Function } { #1 } }
    \cs_gset_protected:cpn { PitonIdentifier _ \l_piton_language_str _ #1 }
      { \PitonStyle { UserFunction } }
    \seq_if_exist:cF { g__piton_functions _ \l_piton_language_str _ seq }
      { \seq_new:c { g__piton_functions _ \l_piton_language_str _ seq } }
    \seq_gput_right:cn { g__piton_functions _ \l_piton_language_str _ seq } { #1 }
    \seq_if_in:NoF \g__piton_languages_seq \l_piton_language_str
      { \seq_gput_left:No \g__piton_languages_seq \l_piton_language_str }
  }
\NewDocumentCommand \PitonClearUserFunctions { ! o }
  {
    \tl_if_novalue:nTF { #1 }
      { \__piton_clear_all_functions: }
      { \__piton_clear_list_functions:n { #1 } }
  }
\cs_new_protected:Npn \__piton_clear_list_functions:n #1
  {
    \clist_set:Nn \l_tmpa_clist { #1 }
    \clist_map_function:NN \l_tmpa_clist \__piton_clear_functions_i:n
    \clist_map_inline:nn { #1 }
      { \seq_gremove_all:Nn \g__piton_languages_seq { ##1 } }
  }
\cs_new_protected:Npn \__piton_clear_functions_i:n #1
  { \__piton_clear_functions_ii:n { \str_lowercase:n { #1 } } }
\cs_generate_variant:Nn \__piton_clear_functions_ii:n { e }
\cs_new_protected:Npn \__piton_clear_functions_ii:n #1
  {
    \seq_if_exist:cT { g__piton_functions _ #1 _ seq }
      {
        \seq_map_inline:cn { g__piton_functions _ #1 _ seq }
          { \cs_undefine:c { PitonIdentifier _ #1 _ ##1} }
        \seq_gclear:c { g__piton_functions _ #1 _ seq }
      }
  }
\cs_new_protected:Npn \__piton_clear_functions:n #1
  {
    \__piton_clear_functions_i:n { #1 }
    \seq_gremove_all:Nn \g__piton_languages_seq { #1 }
  }
\cs_new_protected:Npn \__piton_clear_all_functions:
  {
    \seq_map_function:NN \g__piton_languages_seq \__piton_clear_functions_i:n
    \seq_gclear:N \g__piton_languages_seq
  }
\AddToHook { env / piton / begin }
  { \__piton_fatal:n { No~environment~piton } }

\msg_new:nnn { piton } { No~environment~piton }
  {
    There~is~no~environment~piton!\\
    There~is~an~environment~{Piton}~and~a~command~
    \token_to_str:N \piton\ but~there~is~no~environment~
    {piton}.~This~error~is~fatal.
  }
\__piton_msg_new:nn { Language~not~defined }
  {
    Language~not~defined \\
    The~language~'\l_tmpa_tl'~has~not~been~defined~previously.\\
    If~you~go~on,~your~command~\token_to_str:N \NewPitonLanguage\
    will~be~ignored.
  }
\__piton_msg_new:nn { bad~version~of~piton.lua }
  {
    Bad~number~version~of~'piton.lua'\\
    The~file~'piton.lua'~loaded~has~not~the~same~number~of~
    version~as~the~file~'piton.sty'.~You~can~go~on~but~you~should~
    address~that~issue.
  }
\__piton_msg_new:nn { Unknown~key~NewPitonLanguage }
  {
    Unknown~key~for~\token_to_str:N \NewPitonLanguage.\\
    The~key~'\l_keys_key_str'~is~unknown.\\
    This~key~will~be~ignored.\\
  }
\__piton_msg_new:nn { Unknown~key~for~SetPitonStyle }
  {
    The~style~'\l_keys_key_str'~is~unknown.\\
    This~key~will~be~ignored.\\
    The~available~styles~are~(in~alphabetic~order):~
    \clist_use:Nnnn \g__piton_styles_clist { ~and~ } { ,~ } { ~and~ }.
  }
\__piton_msg_new:nn { Invalid~key }
  {
    Wrong~use~of~key.\\
    You~can't~use~the~key~'\l_keys_key_str'~here.\\
    That~key~will~be~ignored.
  }
\__piton_msg_new:nn { Unknown~key~for~line-numbers }
  {
    Unknown~key. \\
    The~key~'line-numbers / \l_keys_key_str'~is~unknown.\\
    The~available~keys~of~the~family~'line-numbers'~are~(in~
    alphabetic~order):~
    absolute,~false,~label-empty-lines,~resume,~skip-empty-lines,~
    sep,~start~and~true.\\
    That~key~will~be~ignored.
  }
\__piton_msg_new:nn { Unknown~key~for~marker }
  {
    Unknown~key. \\
    The~key~'marker / \l_keys_key_str'~is~unknown.\\
    The~available~keys~of~the~family~'marker'~are~(in~
    alphabetic~order):~ beginning,~end~and~include-lines.\\
    That~key~will~be~ignored.
  }
\__piton_msg_new:nn { bad~range~specification }
  {
    Incompatible~keys.\\
    You~can't~specify~the~range~of~lines~to~include~by~using~both~
    markers~and~explicit~number~of~lines.\\
    Your~whole~file~'\l__piton_file_name_str'~will~be~included.
  }
\cs_new_nopar:Nn \__piton_thepage:
  {
    \thepage
    \cs_if_exist:NT \insertframenumber
      {
        ~(frame~\insertframenumber
        \cs_if_exist:NT \beamer@slidenumber { ,~slide~\insertslidenumber }
        )
      }
  }
\__piton_msg_new:nn { SyntaxError }
  {
    Syntax~Error~on~page~\__piton_thepage:.\\
    Your~code~of~the~language~'\l_piton_language_str'~is~not~
    syntactically~correct.\\
    It~won't~be~printed~in~the~PDF~file.
  }
\__piton_msg_new:nn { FileError }
  {
    File~Error.\\
    It's~not~possible~to~write~on~the~file~'\l__piton_write_str'.\\
    \sys_if_shell_unrestricted:F { Be~sure~to~compile~with~'-shell-escape'.\\ }
    If~you~go~on,~nothing~will~be~written~on~the~file.
  }
\__piton_msg_new:nn { begin~marker~not~found }
  {
    Marker~not~found.\\
    The~range~'\l__piton_begin_range_str'~provided~to~the~
    command~\token_to_str:N \PitonInputFile\ has~not~been~found.~
    The~whole~file~'\l__piton_file_name_str'~will~be~inserted.
  }
\__piton_msg_new:nn { end~marker~not~found }
  {
    Marker~not~found.\\
    The~marker~of~end~of~the~range~'\l__piton_end_range_str'~
    provided~to~the~command~\token_to_str:N \PitonInputFile\
    has~not~been~found.~The~file~'\l__piton_file_name_str'~will~
    be~inserted~till~the~end.
  }
\__piton_msg_new:nn { Unknown~file }
  {
    Unknown~file. \\
    The~file~'#1'~is~unknown.\\
    Your~command~\token_to_str:N \PitonInputFile\ will~be~discarded.
  }
\__piton_msg_new:nnn { Unknown~key~for~PitonOptions }
  {
    Unknown~key. \\
    The~key~'\l_keys_key_str'~is~unknown~for~\token_to_str:N \PitonOptions.~
    It~will~be~ignored.\\
    For~a~list~of~the~available~keys,~type~H~<return>.
  }
  {
    The~available~keys~are~(in~alphabetic~order):~
    auto-gobble,~
    background-color,~
    begin-range,~
    break-lines,~
    break-lines-in-piton,~
    break-lines-in-Piton,~
    break-numbers-anywhere,~
    break-strings-anywhere,~
    continuation-symbol,~
    continuation-symbol-on-indentation,~
    detected-beamer-commands,~
    detected-beamer-environments,~
    detected-commands,~
    end-of-broken-line,~
    end-range,~
    env-gobble,~
    env-used-by-split,~
    font-command,~
    gobble,~
    indent-broken-lines,~
    language,~
    left-margin,~
    line-numbers/,~
    marker/,~
    math-comments,~
    path,~
    path-write,~
    prompt-background-color,~
    resume,~
    show-spaces,~
    show-spaces-in-strings,~
    splittable,~
    splittable-on-empty-lines,~
    split-on-empty-lines,~
    split-separation,~
    tabs-auto-gobble,~
    tab-size,~
    width~and~write.
  }
\__piton_msg_new:nn { label~with~lines~numbers }
  {
    You~can't~use~the~command~\token_to_str:N \label\
    because~the~key~'line-numbers'~is~not~active.\\
    If~you~go~on,~that~command~will~ignored.
  }
\__piton_msg_new:nn { overlay~without~beamer }
  {
    You~can't~use~an~argument~<...>~for~your~command~
    \token_to_str:N \PitonInputFile\ because~you~are~not~
    in~Beamer.\\
    If~you~go~on,~that~argument~will~be~ignored.
  }
\cs_new_protected:Npn \__piton_test_version:n #1
  {
    \str_if_eq:onF \PitonFileVersion { #1 }
      { \__piton_error:n { bad~version~of~piton.lua } }
  }
\hook_gput_code:nnn { begindocument } { . }
  {
    \lua_now:n
      {
        require ( "piton" )
        tex.sprint ( luatexbase.catcodetables.CatcodeTableExpl ,
                     "\\__piton_test_version:n {" .. piton_version ..  "}" )
      }
  }
\ExplSyntaxOff
\begin{luacode*}
    lpeg.locale(lpeg)
    local P , alpha , C , space , S , V
      = lpeg.P , lpeg.alpha , lpeg.C , lpeg.space , lpeg.S , lpeg.V
    local add
    function add(...)
      local s = P ( false )
      for _ , x in ipairs({...}) do s = s + x end
      return s
    end
    local my_lpeg =
      P {  "E" ,
           E = ( V "F" * ( "," * V "F" ) ^ 0 ) / add ,
           F = space ^ 0 * ( ( alpha ^ 1 ) / "\\%0" ) * space ^ 0
        }
    function piton.addDetectedCommands ( key_value )
      piton.DetectedCommands = piton.DetectedCommands + my_lpeg : match ( key_value )
    end
    function piton.addBeamerCommands( key_value )
      piton.BeamerCommands
       = piton.BeamerCommands + my_lpeg : match ( key_value )
    end
    for _ , v in ipairs ( { 'uncover', 'only',
            'visible', 'invisible', 'alert', 'action' } ) do
      piton.addBeamerCommands(v)
    end
    local insert
    function insert(x)
      local s = piton.beamer_environments
      table.insert(s,x)
      return s
    end
    local my_lpeg_bis =
      P {  "E" ,
           E = ( V "F" * ( "," * V "F" ) ^ 0 ) / insert ,
           F = space ^ 0 * ( alpha ^ 1 ) * space ^ 0
        }
    function piton.addBeamerEnvironments( key_value )
      piton.beamer_environments = my_lpeg_bis : match ( key_value )
    end
\end{luacode*}

\endinput
%%
%% End of file `piton.sty'.
