50 通过expl3设计“根据\tl_count:N”结果加载不通模块的error?

发布于 2024-10-05 15:33:11

如题,我想设计一个通过对 \tl_count:N 结果判断而加载不同模块的设计. 而要判断的 token list,则是通过某个命令对keys_set得到的.

首先,我在 skyrmion.cls 文件中写道:

\def\skyrmion@date{2024/10/05}
\def\skyrmion@version{0.1.1}

\ExplSyntaxOn
\cs_new_protected_nopar:Npn \skyrmion_provide_module:n #1
  {
    \ProvidesExplFile{skyrmion-#1-module.code.tex}{\skyrmion@date}{\skyrmion@version}
    {skyrmion~ \text_titlecase:n { #1 } ~Module}
  }
\ExplSyntaxOff

\ProvidesExplClass{skyrmion} {\skyrmion@date} {\skyrmion@version}
{Skyrmion in Hong Kong}

\cs_new_protected:Npn \skyrmion_msg_new:nn #1#2 
  { \msg_new:nnn { skyrmion } { #1 } { #2 } }
\cs_new_protected:Npn \skyrmion_msg_error:nn #1#2
  { \msg_error:nnn { skyrmion } { #1 } { #2 } }
\cs_generate_variant:Nn \skyrmion_msg_error:nn { nx }
\skyrmion_msg_new:nn { not found module }
  {
    The~skyrmion~module~`#1'~not~found.
  }
\cs_new_protected_nopar:Npn \skyrmion_load_module:n #1 
  {
    \clist_map_inline:nn { #1 }
    {
      \file_if_exist_input:nF { skyrmion-##1-module.code.tex }
      {
        \skyrmion_msg_error:nn { not found module } { ##1 }
      }
    }
  }

\DeclareOption*{\PassOptionsToClass{\CurrentOption}{article}}
\ProcessOptions\relax
\LoadClass[a4paper]{article}

无非就是平常编写cls时用的 \LoadClass 等文档类信息,和加载 skyrmion-#1-module.code.tex 模块文件的模块. 可以在 skyrmion.cls 中直接使用命令
\skyrmion_load_module:n {#1} 来加载模块文件,模块文件命名当然是要 skyrmion-#1-module.code 这样. (这里参见@u19850 的ElegantBook-l3重构项目设计).

接下来,我定义了个keys组:

\keys_define:nn {skyrmion/foo}
{
  tel.tl_set:N = \l__skyrmion_tel_tl
}

然后重头戏来了:定义个命令,用强制参数(m-argument)输入键值

\NewDocumentCommand{\foo}{m}
{
  \keys_set:nn {skyrmion/foo} {#1}
}

这时,在用户创建的tex文件中,在preamble中执行命令 \foo{tel = 12345678},此时 \l__skyrmion_tel_tl 已经被赋为 12345678\tl_count:N \l__skyrmion_tel_tl 理所当然会输出 8. 我就想利用根据用户输入长度是否为8还是10为判断,来加载不同的模组.
在模组文件 skyrmion-part1-module.code 中我写到:

\skyrmion_provide_module:n {part1}
  
\newcommand \myfoo [1] {This~is~module~1:~#1}

\endinput

在模组文件 skyrmion-part2-module.code 中我写到:

\skyrmion_provide_module:n {part2}
  
\newcommand \myfoo [1] {This~is~module~2:~#1}

\endinput

那么,我判断自然不能直接放 skyrmion.cls 里,因为判断必须得在命令 \foo执行后,或者至少键 \l__skyrmion_tel_tl 被赋值后才能进行,否则 \tl_count:N 输出肯定为0. 那我就索性把判断塞 \foo 里面,并且在 \keys_set:nn 后面,进而达到在执行命令 \foo 时先给键 \l__skyrmion_tel_tl 赋值,再判断,也就是

\NewDocumentCommand{\foo}{m}
{
  \keys_set:nn {skyrmion/foo} {#1}
  \int_compare:nNnT {\l__skyrmion_tel_tl} = {8}
  {
    \skyrmion_load_module:n {part1}
  }
  \int_compare:nNnT {\l__skyrmion_tel_tl} = {10}
  {
    \skyrmion_load_module:n {part2}
  }
}

然后我在 mwe.tex 中进行测试

\documentclass{skyrmion}

\foo{tel=12345678}

\begin{document}

\myfoo{1}

\ExplSyntaxOn
There~are~\tl_count:N \l__skyrmion_tel_tl{}~numbers~in~\l__skyrmion_tel_tl.
\ExplSyntaxOff

\end{document}

迎接我的是报错 Undefined control sequence.. 说明两个模组并未被加载(说句废话:如果我用int_compare:nNnTF,False设置为加载另一个模组,那当然能顺利编译,但是结果肯定是错误的,因为一直被指向False分支).

那我就想:先把判断去掉试试?于是改为

\NewDocumentCommand{\foo}{m}
{
  \keys_set:nn {skyrmion/foo} {#1}
  \skyrmion_load_module:n {part1}
}

报错更热闹了:

    Undefined control sequence.
    Missing $ inserted.
    <inserted text>
    Missing \begin{document}.
    Missing $ inserted.
    <inserted text>

我意识到可能是expl3语法环境被淹没了?于是有重新加了组\ExplSyntaxOn/Off

\NewDocumentCommand{\foo}{m}
{
  \keys_set:nn {skyrmion/foo} {#1}
  \ExplSyntaxOn
  \skyrmion_load_module:n {part1}
  \ExplSyntaxOff
}

编译正常,讽刺的是同为expl3语法的 \keys_set:nn 在外面却安然无恙.

话又说回来,我试着加上判断外面再套一层 \ExplSyntaxOn/Off

\NewDocumentCommand{\foo}{m}
{
  \ExplSyntaxOn
  \keys_set:nn {skyrmion/foo} {#1}
  \int_compare:nNnT {\tl_count:N \l__skyrmion_tel_tl} = {8}
  {
    \skyrmion_load_module:n {part1}
  }
  \int_compare:nNnT {\tl_count:N \l__skyrmion_tel_tl} = {10}
  {
    \skyrmion_load_module:n {part1}
  }
  \ExplSyntaxOff
}

结果还是 Undefined control sequence.

呼,问题实在是太难描述,写了个小作文😆. 最后请问expl3大师们,这个问题出在了哪里?问题中的.tex, .code.tex, .cls 文件见压缩包.Archive.zip

查看更多

关注者
0
被浏览
252
雾月
雾月 2024-10-05
这家伙很懒,什么也没写!

报错信息要看完整。

(./skyrmion-part1-module.code.tex
! Undefined control sequence.
l.1 \skyrmion
             _provide_module:n {part1}
? 

说明是在 skyrmion-part1-module.code.tex 这个文件的第一行报错,明显是由于没有处于 LaTeX3 环境而出错。因为是在导言区导入的模块,并不是 LaTeX3 环境。把名字替换成 \SkyrmionProvideModule 就好了。

另外,只是简单的用 \file_if_exist_input:n..,如果重复加载一个模块,就会出现重复定义的问题,还可能会像加载 tikz 库一样,遇到类别码的问题,建议使用 \@onefilewithoptions 加载文件,不仅可以解决这两个问题,还可以像加载宏包时有宏包选项一样,有自己的模块选项。

%% skyrmion.cls, 另外两个模块文件的第一行也要修改
\def\skyrmion@date{2024/10/05}
\def\skyrmion@version{0.1.1}

\ExplSyntaxOn
\cs_new_protected_nopar:Npn \SkyrmionProvideModule #1
  {
    \ProvidesExplFile{skyrmion-#1-module.code.tex}{\skyrmion@date}{\skyrmion@version}
    {skyrmion~ \text_titlecase:n { #1 } ~Module}
  }
\ExplSyntaxOff

\ProvidesExplClass{skyrmion} {\skyrmion@date} {\skyrmion@version}
{Skyrmion in Hong Kong}

\cs_new_protected:Npn \skyrmion_msg_new:nn #1#2 
  { \msg_new:nnn { skyrmion } { #1 } { #2 } }
\cs_new_protected:Npn \skyrmion_msg_error:nn #1#2
  { \msg_error:nnn { skyrmion } { #1 } { #2 } }
\cs_generate_variant:Nn \skyrmion_msg_error:nn { nx }
\skyrmion_msg_new:nn { not found module }
  {
    The~skyrmion~module~`#1'~not~found.
  }
\tl_const:Nn \c__skyrmion_module_ext_tl { tex }
\cs_new_protected_nopar:Npn \skyrmion_load_module:n #1 
  {
    \clist_map_inline:nn { #1 }
    {
      \file_if_exist:nTF { skyrmion-##1-module.code. \c__skyrmion_module_ext_tl }
      { \@onefilewithoptions { skyrmion-##1-module.code } [{}] [0000-00-00] { \c__skyrmion_module_ext_tl } } % 第一个方括号是模块选项,第二个是日期
      {
        \skyrmion_msg_error:nn { not found module } { ##1 }
      }
    }
  }

\DeclareOption*{\PassOptionsToClass{\CurrentOption}{article}}
\ProcessOptions\relax
\LoadClass[a4paper]{article}

\keys_define:nn {skyrmion/foo}
{
  tel.tl_set:N = \l__skyrmion_tel_tl
}

\NewDocumentCommand{\foo}{m}
{
  \keys_set:nn {skyrmion/foo} {#1}
  \int_compare:nNnT { \tl_count:N \l__skyrmion_tel_tl } = {8}
   {
     \skyrmion_load_module:n {part1}
   }
  \int_compare:nNnT { \tl_count:N \l__skyrmion_tel_tl } = {10}
   {
     \skyrmion_load_module:n {part2}
   }
}
% \skyrmion_load_module:n {part1} % ?

\endinput
1 个回答

撰写答案

请登录后再发布答案,点击登录

发布
问题

分享
好友

手机
浏览

扫码手机浏览