你在清华念的是电机系,后来在无线电厂做了十年技术员,七八年才考回科大读计算机系研究生。这个跨度不小。”
“是不小。”彭远征点点头:“在无线电厂那十年,我一直在自学计算机。最开始厂里没有科班出身的,所有的控制系统都是我自己摸索着写的。”
陆怀民点点头,问道:“彭师兄,你的简历上说你编译原理是自学的。正好,我们课题组要做的后处理编译器,本质上就是把一种描述语言翻译成另一种指令语言。我想问你一个问题。”
彭远征坐直了身子。
“假设你现在要写一个最简单的代码生成器,把数学表达式翻译成后缀表达式,也就是逆波兰表示法。比如a加b乘c”,要输出abc乘加”。你会怎么做?”
这是经典题,不算难。
彭远征几乎是下意识地回答:“用栈。遇到操作数就输出,遇到运算符就和栈顶比优先级。”
“好。”陆怀民点点头,追问了一句,“那如果表达式中出现了括号呢?比如a加b乘以c减d。”
“左括号直接压栈。遇到右括号,就把栈里的运算符依次弹出,直到匹配的左括号。”
彭远征答得很快,这些都是基础的东西,对他来说自然不是什么问题。
陆怀民点点头,继续问:“彭师兄,你刚才说的是中缀表达式转逆波兰。现在我们换个角度想。如果不用栈,你能不能换一种方式实现同样的功能?”
彭远征愣了一下。
不用栈?
他脑子里飞快地转了一圈。
栈是这个问题标准的、最直观的解。
不用栈,还有什么办法?这他倒确实没想过。
“我————没有想过这个问题。我一直用栈。”彭远征知道他一时半会儿肯定想不到答案,索性如实回答。
“栈没问题。“陆怀民说,“栈确实是最直观、内存开销最小的方法。但如果我们面对的不是简单的数学表达式呢?如果我们要处理的是一种更复杂的加工描述语言呢?”
他顿了顿,继续说道:“你想想,用栈来写的话,每加一种新的运算符,你就要改一次优先级表;
每加一种新的语法结构,你就要在栈的弹出逻辑里加一堆分支判断。到最后,整个程序会变成一团乱麻,没人敢改,也没人能看懂。那时候你怎么办?”
彭远征的眉头拧了起来。他隐约感觉到了陆怀民想说什么,但又抓不住那个点。
毕竟这个时代的代码复杂程度和后世是不可同日而语的。
很多后世的算法在这个时代极具有前瞻性。
“有一种方法,叫递归下降。”陆怀民说着,在面前摊开的笔记本上刷刷写了几行伪代码,把笔记本转过来,推到彭远征面前:“不用栈,用函数的调用栈来代替数据栈。比如a加b乘c”,你可以定义一个解析函数,遇到加号的时候,先把左操作数输出,然后递归处理右操作数,最后再输出加号本身。”
彭远征低头看去。
那几行伪代码写得清清爽爽,递归的调用顺序标注得明明白白。
他一行一行往下看,心里那层窗户纸哗一下就破了。
“这————这不就是编译原理教材里讲的语法树后序遍历吗?怎么我一直没想到用递归来————”
他差点想说“用递归来做表达式转换”,但话到嘴边,他自己先愣住了。
因为他突然意识到,他不是“没想到”。而是他这些年来,从来只在“栈“这一个框架里打转。
因为栈是这个问题公认标准解法,书上这么写,同行这么做,他在无线电厂写的每一个工控程序也都是这么写的。
所以他从来没有想过,这件事可以不这么做。
可眼前这个比他还小十几岁的年轻人,似乎压根就不觉得栈是唯一的路。
彭远征忽然想起自己在无线电厂那十年,那本油印编译原理教材时,书里有一页专门讲了“递归下降法”。
但那一页印得太模糊了,油墨糊成一团,只能勉强辨认出几个标题。当时他没在意,翻过去了。
现在想来,他可能错过了一扇门。
“递归下降解析法,是这几年国外在嵌入式编译器里应用得比较多的一种方法。”陆怀民说:“它比栈更灵活,能处理更复杂的语法结构,而且代码结构清淅,调试方便。如果我们课题组要做后处理编译器,这个方法可能用得上。”
彭远征点点头,心里翻涌着说不清的滋味。
“陆————”他抬起头,一时竟不知该怎么称呼,“陆师弟,我能问一个问题吗?”
“请讲。”
“在递归下降的基础上,如果要处理更复杂的语义一一比如说,数控代码里的循环、跳转、宏定义这些指令,你准备怎么设计语法规则?”
陆怀民笑了笑,反问:“彭师兄觉得呢?”
彭远征陷入了沉思。
陆怀民问的这个问题,他感受到的不是考较,而是一种邀请。
邀请他一起来思考。
“我觉