Sway 参考手册/调试
在目前阶段,您应该能够编写一些复杂的 Sway 程序。此外,我确信您也引入了某些复杂的错误。这正是程序员的生活!
编程中通常会遇到两种类型的错误:语法错误,源代码存在问题,无法编译和执行;以及语义错误,代码(以某种方式)运行,然后过早终止或产生错误的结果。
人类语言也存在语法和语义错误。以下是一些示例
The box mxyskd the water.
由于标记“mxyskd”不是一个单词,因此此句子不是有效的英语。这是一个语法错误。
The box drank water the
此句子也不是有效的英语,因为冠词“the”放错了位置,并且没有标点符号表示句子的结束。这些也是语法错误。
The box drank the water.
此句子在语法上是正确的,但在语义上没有意义:盒子通常不喝酒。
编程语言也有类似的错误,我们将在后续章节中探讨如何检测和修复它们。
在学习这些技巧时,请记住,调试的第一规则是尽早捕获错误。也就是说,找到对代码的最小改进,使您更接近最终版本,然后实现、测试和调试该步骤。然后重复。
对于尝试在测试之前编写整个项目的同学来说,真是苦不堪言!
语法错误总是看起来像这样
SYNTAX ERROR: :syntaxError file prog1.s,line 113 expecting OPEN_PARENTHESIS, found function instead error occurred prior to: ;[END OF LINE]
错误报告会给出文件名和行号。通常,这正是问题所在,但请意识到,这是检测到错误的位置;问题可能在文件中的更早位置开始。
许多不明显的错误涉及在错误的位置放置分号或在需要的地方省略分号。请记住,分号不跟在命名函数定义和某些函数调用(如if和while)之后。在下一章中,您将学习哪些函数调用需要后跟分号,哪些不需要。
如果您遇到一个无法追踪的奇怪语法错误,请尝试注释掉代码片段,直到错误消失。错误可能就在刚注释掉的代码中。
Sway 有三种类型的注释。第一种是行尾注释。双斜杠(两个连续的正斜杠)后面的行上的任何内容都将被忽略。
//removing the next function
//function id(x) { x; }
第二种是三斜杠。这会导致文件其余部分被忽略,对于调试语法非常有用。
///removing the rest of the file function id(x) { x; }
要使用三斜杠,请在文件的最顶部以“///”开头,以便忽略整个文件。这应该会消除语法错误。然后继续将“///”向下移动到文件中,直到语法错误出现。新发现的代码部分很可能包含错误。
最后一种注释是块注释。出现在斜杠星号和星号斜杠之间的任何内容都将被忽略
/* removing this function
function id(x) { x; }
*/
请注意,块注释不能包含另一个块注释。
语法错误是在 Sway 解释器读取代码时发现的,而语义错误是在解释器评估代码时发现的。语义错误报告看起来像这样
EVALUATION ERROR: :mathError file prog1.s,line 3: division: cannot divide by zero
CALL TRACE: initial call: prog1.s,line 5: inspect(f(3)); in <function f(x)>... prog1.s,line 3: x / 0;
会给出文件名和行号;与语法错误一样,这是检测到语义错误的位置。在错误描述之后是调用跟踪。调用跟踪从上到下读取。在示例中,问题始于第 5 行,当时调用了inspect函数。此调用触发了对函数f的调用,在该函数中,第 3 行尝试进行除以零操作。调用跟踪对于追踪问题非常有用。
语义错误通常发生在某个重要变量最终具有与您预期不同的值时。因此,在调试代码时查看变量的值非常重要。
您应该养成能够“可视化”程序状态的习惯。例如,假设您希望在程序中的多个不同点查看名为x的变量的值。最简单的可视化方法是使用以下形式的打印语句
println("x is ",x);
作为一名老师,我必须评论一下,学生们很少使用这个简单的工具。您应该大量使用打印语句来调试语义错误。
但是,添加这种打印语句相当繁琐,因为您只需在解决问题后删除这些行。需要一种更快的添加打印语句的方法。一种这样的方法是inspect函数。例如,以下两个函数调用是等价的
println("x is ",x); inspect(x);
这两个调用的输出完全相同。如果x的值为5,则这两个调用的输出为
x is 5
现在,在这种情况下,使用inspect节省的时间并不多(节省了键入 8 个字符),但当要检查的表达式变得复杂时,节省的时间就会增加。考虑这些调用
println("alpha . beta(gama,delta) is ",alpha . beta(gama,delta)); inspect(alpha . beta(gama,delta));
同样,每个输出都相同,但在使用inspect时节省的时间有所增加。
最后,如果即使inspect也写起来太多,定义一个变量,例如vv,表示“查看变量”
var vv = inspect;
并将其用于inspect代替
vv(x);
有时跟踪函数调用的执行(逐行)很有用。假设您希望调用一个名为f的函数并跟踪函数执行时的每一行。只需将调用包装在设置函数的filter组件的语句中即可。
f . filter = trace*; y = f(x); f . filter = :null;
或者更简单地说,使用包装函数trace:
y = trace(f(x));
将过滤器设置为trace*(trace的作用)将开启被调用函数的跟踪;将过滤器设置为null将关闭跟踪。您需要在文件顶部包含debug库。例如,此程序
include("debug");
function f(x) { var a = x + 1; var b = x - 1; a * b; }
var result = trace(f(5));
inspect(result);
产生以下输出
prog.s,line 5: var a = x + 1; prog.s,line 6: var b = x - 1; prog.s,line 7: a * b; result is 24
如果将过滤器设置为trace,则将显示被跟踪函数主体中的每一行。您需要按<Enter>键才能继续执行函数中的下一行。
如果将函数的过滤器设置为trace*,则程序将在打印出被跟踪语句后暂停,就像trace一样。不同之处在于,如果您键入任何非空格字符并按回车键,您将进入一个微型 Sway 解释器。在这里,您可以执行在交互式 Sway 解释器中执行的几乎任何操作。唯一的区别是,要执行的每个定义或表达式都必须在一行上输入。
单步执行函数类似于跟踪,但您的程序将在执行每个语句之前暂停。如果只是按<Enter>,则当前语句将被执行,下一条语句将被显示(如果存在),程序将再次暂停。
但是,如果您在暂停期间键入其他任何内容,则您的输入将被评估为 Sway 表达式并显示结果。此评估是在函数主体评估期间有效的环境下执行的。换句话说,您可以在函数调用期间检查(和修改)作用域内的变量。此迷你解释器将一直运行,直到您停止输入要评估的表达式。当您停止输入表达式时,程序将前进到函数调用中的下一行。
设置单步执行函数与跟踪类似
f . filter = step*; y = f(x); f . filter = :null;
或者更简单地说,使用包装函数step:
y = step(f(x));
在交互中(如上所述,但使用step),检查值a并修改b
prog.s,line 5: var a = x + 1;> prog.s,line 6: var b = x - 1;> a; INTEGER: 6 > prog.s,line 6: var b = x - 1;> prog.s,line 7: a * b;> b = 10; INTEGER: 10 > b; INTEGER: 10 > prog.s,line 7: a * b;> result is 60
请注意,在任何给定点显示的行尚未执行。因此,我们必须等到声明变量a后才能检查它。与跟踪一样,您必须包含debug库才能使用单步执行。
迷你解释器和 Sway 解释器之间的主要区别在于,传递给迷你解释器的表达式必须全部在一行上输入。
您可以通过输入 <Ctrl-d> 或输入空行来退出迷你解释器。
有时,您确切地知道希望在函数中的哪个位置暂停并检查变量。与其使用step,您可以使用sway函数直接调用迷你解释器。
include("debug");
function f(x) { var a = x + 1; var b = x - 1; println("breakpoint!"); sway(); //call the mini-interpreter println("done."); a * b; }
var result = f(5);
inspect(result);
运行此程序将产生
breakpoint! sway> a; INTEGER: 6 sway> b = 10; INTEGER: 10; sway> done. result is 60
与之前一样,必须在一行上输入迷你解释器的输入。
sway函数是内置的;因此,您无需包含debug。
您必须处理两种类型的错误。第一种是您的程序用户提供了错误的输入。这些类型的错误称为外部错误。“防弹”您的代码意味着添加处理外部错误的逻辑。
第二种错误源于代码本身的错误。这些称为内部错误。优秀的计算机科学家会预料到无论程序员多么优秀,这些错误都会发生,并且会使用断言尽早发现这些错误。例如,假设您“知道”函数的输入始终是非负整数。您可以使用断言来检测与该约束的偏差
function f(x) { assert(x >= 0); ... }
在代码开发期间,断言确保您没有不适当地调用此类函数。
完成代码后,您可以注释掉您的断言。