说明
现在的debugger支持最基本的两个命令,next step和watch variable。
语言定义
上一节实现了简单的解释器,语言的定义:
程序:语句序列
语句:语句序列|赋值语句(语句;)
赋值语句:变量=表达式
表达式:包含变量的四则运算
源码示例:
{
number=2;
a=number;
b=10*a+10;
c=a--b
};
x=11;
y=x*10
调试器使用
package lsbasi;
public class TestDebug {
public static void main(String[] args) throws Exception {
Lexer lexer = new Lexer("{number=2;a=number;b=10*a+10;c=a--b};x=11;y=x*10");
Parser p = new Parser(lexer);
Interpreter i = new Interpreter(p);
i.debugger();
System.out.println(i.GLOBAL_SCOPE);
}
}
调试器实现
package lsbasi;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import lsbasi.ast.StatementAST;
import lsbasi.visit.NodeVisitor;
public class Debugger {
Scanner s = new Scanner(System.in);
private Map<String, Integer> scope;
private List<StatementAST> list;
private int index;
public Debugger(List<StatementAST> list, Map<String, Integer> scope) {
this.list = list;
this.scope = scope;
this.index = 0;
}
public void help() {
System.out.println("h:help");
System.out.println("n:next");
System.out.println("w:watch");
}
public String waitUser() {
return s.next();
}
public void debugger() throws Exception {
help();
while (index < list.size() ) {
switch (waitUser()) {
case "n":
NodeVisitor.visit(list.get(index), scope);
index++;
break;
case "h":
help();
break;
case "w":
System.out.println(scope);
break;
default:
break;
}
}
}
}
值得改进的地方当然很多了,例如调试器功能不完整,这个要考虑的扩展性的话,需要拆分代码。让每种调试功能成为一个实例,在需要的时候进行注册,组合子对象模式真的好像不错。
后记
相信很多人对调试器没有任何兴趣,但是据我所知很多年轻人编程几乎离不开调试器,比如我,这当然是很受高手鄙视的。
我一直在想调试器的工作原理是什么样的,有什么好处和坏处,怎么可以摆脱调试器束缚,让我也成为高手一下,而不是遇到问题就下断点,以前觉得这是必选项。当我听说,仅仅是听说,有人可以一次将代码写对的时候,我彻底改变了以前的想法,觉得自己的做法太low了。我想要离开调试器的话,我们必须对语言很熟悉,对正在操作的数据很熟悉,有一些辅助日志帮助,把逻辑设计的天衣无缝,一口气全写对。如果有一口气全写对的,请联系我,让我见识一下。
调试器的哲学是让计算的世界停下来,慢下来,让我们仔细思考我们的代码,看到变量值和当初考虑不完全的地方,有时候这种错误很难捕捉,即使最好的调试器也经不起拙略程序员的莽撞行为。我想另一个方法是我们可以让自己慢下来,来应对程序的部分错误。
但是很多时候我们接手不熟悉的代码,甚至得在线定位,或者对完整的代码不熟悉,这时候就得用调试器碰碰运气了。当然终极杀招似乎还是类似alert print之类的输出。
对于初学者调试器是种逆天的功能,可以让人对代码每一步有直观了解,弥补了对细节掌控的不足。但是任何牛皮的工具都不会替猿类的懒惰和不思进取买单,如果你不明白你自己正在干什么,做的再好都是垃圾。
网友评论