如果我难以解释乳胶文件中的错误信息,我会注释掉一半的文件,进行编译,看看它是否有效,然后通过连续的二分法找到有问题的部分。
有没有自动的方法可以做到这一点?我知道这可能很难实现,因为你必须找到安全的二分点位置。不过我猜这在原则上是可以解决的,所以我想知道是否已经有解决方案了。
我正在使用 linux 和 emacs,因此我更喜欢尊重此环境的解决方案。
这个问题部分受到 emacs 包的启发漏洞猎手, 哪个 ”自动调试和二分你的初始化(.emacs)文件!“ (也可以看看这里)。
编辑
这是一个简单的例子,我觉得很难找到错误。在这种情况下,我会尝试二分法:
\documentclass{article}
\errorcontextlines=1000
\usepackage{exsheets}
\usepackage{tikz}
\begin{document}
\begin{question}
Test
\end{question}
\begin{solution}
\begin{tikzpicture}
\draw (0,0) -- (1,1)
\end{tikzpicture}
\end{solution}
\begin{question}
Test
\end{question}
\begin{solution}
Test
\end{solution}
\printsolutions
\end{document}
日志文件的输出:https://pastebin.com/kFfWu2zL
答案1
这是对特定示例的解释,以及为什么它难以调试。
堆栈跟踪发生了什么?
因为尾调用优化,缺少重要的错误上下文行。
比较:如果在错误的 tikzpicture 环境后添加\relax
(或任何非空内容),则会打印相关的上下文行。
! Package tikz Error: Giving up on this path. Did you forget a semicolon?.
See the tikz package documentation for explanation.
Type H <return> for immediate help.
...ackage tikz Error: Giving up on this path. Did you forget a semicolon?.
See the tikz package documentation for explanation.
Type H <return> for immediate help\@err@
\GenericError ... \@empty \def \MessageBreak
#1 \def \errmessage #2.
#3
Type H <return> for immediate help\@err@
\endgroup
\pgfutil@next ->\advance \tikz@expandcount by -1 \ifnum \tikz@expandcount <0\relax \tikzerror {Giving up on this path. Did you forget a semicolon?}
\let \pgfutil@next =\tikz@finish \else \let \pgfutil@next =\tikz@@expand \fi \pgfutil@next
\pgf@let@token ->\tikz@atend@picture \global
\let \pgf@shift@baseline@smuggle =\pgf@baseline \global \let \pgf@trimleft@final@smuggle =\pgf@trimleft \global \let \pgf@trimright@final@smuggle =\pgf@trimright \global \let \pgf@remember@smuggle =\ifpgfre...
\end #1->\romannumeral \IfHookEmptyTF {env/#1/end}{\expandafter \z@ }{\z@ \UseHook {env/#1/end}}\csname end#1\endcsname
\@checkend {#1}\expandafter \endgroup \if@endpe \@doendpe \fi \UseHook {env/#1/after}\if@ignore \@ignorefalse \ignorespaces \fi
<argument> \begin {tikzpicture} \draw (0,0) -- (1,1) \end {tikzpicture}
\relax
\exsheetsprintsolution #1#2->#1#2
\__exsheets_surround_with:nnn #1#2#3->#2#1
#3
\__exsheets_print_solution:nnn ...tl {#2}{#1}}{\exp_not:V \l__exsheets_solutions_pre_body_hook_tl \exp_not:n {#3}\exp_not:V \l__exsheets_solutions_post_body_hook_tl }}\l__exsheets_solutions_pre_hook_tl \l__exsheets_solutions_post_hook_tl
\exsheets_add...
\__prop_map_function:Nwwn #1#2\__prop_pair:wn #3\s__prop #4->#2#1{#3}{#4}
\__prop_map_function:Nwwn #1
\g_exsheets_question_identification_prop ->\s__prop \__prop_pair:wn 1\s__prop {-0-1}
\__prop_pair:wn 2\s__prop {-0-2}
\prop_map_function:NN #1#2->\exp_after:wN \use_i_ii:nnn \exp_after:wN \__prop_map_function:Nwwn \exp_after:wN #2#1
\prg_break: \__prop_pair:wn \s__prop {}\prg_break_point: \prg_break_point:Nn \prop_map_break: {}
\__keys_set_keyval:nnn ...path_str \s__keys_stop \l__keys_module_str \l_keys_key_str \tl_set_eq:NN \l_keys_key_tl \l_keys_key_str \__keys_value_or_default:n {#3}\bool_if:NTF \l__keys_selective_bool \__keys_set_selective: \__keys_execute:
\str_set:Nn \...
\__keyval_key:nn #1#2->\__keyval_if_blank:w \s__keyval_mark #1\s__keyval_nil \s__keyval_stop \__keyval_blank_key_error:w \s__keyval_mark \s__keyval_stop \exp_not:n {#2{#1}}
\__keyval_loop_other:nnw {#2}
\__keyval_loop_other:nnw ..._keyval_if_recursion_tail:w #3\__keyval_end_loop_other:w \s__keyval_tail \__keyval_split_active:w #3\s__keyval_nil \s__keyval_mark \__keyval_split_active_auxi:w =\s__keyval_mark \__keyval_clean_up_active:w {#1}
{#2}\s__keyva...
\__keyval_loop_active:nnw #1#2#3,->\__keyval_if_recursion_tail:w #3\__keyval_end_loop_active:w \s__keyval_tail \__keyval_loop_other:nnw {#1}{#2}#3,
\s__keyval_tail ,
\keyval_parse:NNn #1#2#3->\__keyval_loop_active:nnw {#1}{#2}\s__keyval_mark #3,
\s__keyval_tail ,
\__keys_set:nnn #1#2#3->\str_set:Nx \l__keys_module_str {\__keys_trim_spaces:n {#2}}\keyval_parse:NNn \__keys_set_keyval:n \__keys_set_keyval:nn {#3}
\str_set:Nn \l__keys_module_str {#1}
\l__exp_internal_tl ...\l__keys_only_known_bool \bool_set_false:N \l__keys_filtered_bool \bool_set_false:N \l__keys_selective_bool \tl_set:Nn \l__keys_relative_tl {\q__keys_no_value }\__keys_set:nn {exsheets/exsheets_print_solutions}{all}
\tl_set:Nn \l...
\exsheets_print_solutions:n ...exsheets_solutions_print_bool \bool_set_true:N \l__exsheets_inside_solution_bool \cs_set:Npn \S ##1{\exref {exse:##1}}\cs_set:Npn \C ##1{\exref {exch:##1}}\keys_set:nn {exsheets/exsheets_print_solutions}{#1}
\group_end:
<to be read again>
\end
l.32 \end
{document}
注意
<argument> \begin {tikzpicture} \draw (0,0) -- (1,1) \end {tikzpicture}
\relax
这就是您输入的内容。
行号怎么了?
您在 TeX 中显示的代码大致相当于 Python 中的以下(伪)代码:
# ======== tikz library
def tikzPicture(s: str):
if ";" not in s: raise RuntimeError("No semicolon")
return "figure"
# ======== exsheets library
solutions=[]
def addSolution(s: str):
solutions.append(s)
def printSolutions():
for solution in solutions: print(eval(solution))
# ======== your code
addSolution("'Test'")
addSolution("tikzPicture('draw (0, 0) -- (1, 1);')")
addSolution("tikzPicture('draw (0, 0) -- (1, 1)')") # line 21 here
printSolutions()
Python 中的回溯是
Traceback (most recent call last):
File "FILE", line 23, in <module>
printSolutions()
File "FILE", line 15, in printSolutions
for solution in solutions: print(eval(solution))
File "<string>", line 1, in <module>
File "FILE", line 5, in tikzPicture
if ";" not in s: raise RuntimeError("No semicolon")
RuntimeError: No semicolon
如您所见,回溯仅指向第 23 行(printSolutions
),而不是您实际添加代码的第 21 行。
(另请注意,在 TeX 中,“最近调用”是第一次,而不是最后一次)
Python:还能更好吗?
事实上 Python 甚至更差在这个特定的例子中!(这是可以修复的,参见stackoverflow 问题)
您在“普通”编程语言中不会遇到它的唯一原因是您极少使用eval
/ exec
。
理论上的 Python 库还能改进什么?
- 在 上
addSolution
,将堆栈内容存储在某处,然后将其打印回来。这样您就可以得到“第 21 行”。 - 出现
printSolutions
错误时打印出用户的源代码。
明显的缺点是时间/内存/代码复杂性/维护难度开销。
TeX:还有更好的吗?
上述两种解决方案均适用,但都需要对exsheets
包进行修改。
可以将当前行号
\begin{solution}
与逐字解决方案文本一起定义,并在执行时打印它(在某处,例如在日志中)如果没有这个,包仍然可以在呈现每个解决方案之前打印出解决方案的内容。
(TeX 中没有
try...catch
ortry...except
)对于尾部调用优化情况,如上面链接问题中提到的,可以查看输出来
\tracingmacros=1
确定上下文。对于此特定版本的此特定软件包,省略的“用户层”位于该层的正上方(比该层更近) 。因此,在日志中搜索回溯之前
exsheetsprintsolution
的最后一次出现。在此特定情况下,它是\exsheetsprintsolution
\exsheetsprintsolution #1#2->#1#2 #1<-\exsheets_solutions_print_name:Vnn \l__exsheets_tmpa_tl {\GetTranslation {exsheets-solution-name}}{1} #2<-\begin {tikzpicture} \draw (0,0) -- (1,1) \end {tikzpicture}
您可以清楚地看到这
#2
就是您的代码。或者(提醒:这是特定于此特定包实现/版本)可以
I\ExplSyntaxOn\show\l__exsheets_tmpb_tl
在 TeX 错误提示处输入来打印出你的环境内容(已经标记化)。
换行符发生了什么?
这是包的另一个问题exsheets
:环境中的内容不是逐字存储的,而是直接解析的,解析会删除换行符。
您可以轻松看到exsheets
没有正确存储逐字内容:
\begin{solution}
\verb+%+
\end{solution}
(与该问题无关,但您可以\cprotEnv
在cprotect
包中使用来修复该问题。)
为什么\scantokens
( exec
-equivalent) 在 TeX 中如此常见?
基本上,为了在一般情况下正确解析 TeX,您必须执行代码。
所以当你想存储内容而不执行代码, 你必须逐字存储它(大致“作为字符串”),然后稍后将其作为字符串执行。
尽管在这个特殊情况下,它被收集为一个参数。这也会破坏行号。