1700436675
1700436676
图5-3 迈克尔·莱斯克,约1981年(杰勒德·霍尔兹曼供图)
1700436677
1700436678
迈克[4]写了Lex的第1版。1976年夏天,一位刚从普林斯顿大学毕业的实习生很快对它做了修改。迈克回忆说:
1700436679
1700436680
“埃里克·施密特(Eric Schmidt)在暑期实习时几乎重写了Lex。我之前的版本采用非确定性分析器,无法处理超过16个状态的规则。阿尔·阿霍很不爽,给我找了个暑期生来修复。埃里克适逢其会。”
1700436681
1700436682
埃里克后来在伯克利分校获得博士学位,并于2001年至2011年担任谷歌公司首席执行官一职。
1700436683
1700436684
Yacc和Lex紧密协同。解析过程中,Yacc会反复调用Lex,Lex每次读取足够多的输入来构造完整词元,并将其传回给Yacc。Yacc/Lex组合将编译器的前端组件机械化,能够同时处理复杂的语法和词法结构。例如,有些编程语言的运算符长达两三个字符,如C语言中的++运算符,当词法分析器看到符号+的时候,要接着看,才能知道待处理的运算符是++还是普通的+。手工写这种代码并不算难,但若有人代劳就尤其方便。使用Lex,人们只需要写
1700436685
1700436686
“++” { return PLUSPLUS; }“+” { return PLUS; }
1700436687
1700436688
1700436689
1700436690
1700436691
1700436692
就能区别开上述两种情形。(PLUS和PLUSPLUS是数学符号的名称,这样C语言代码就比较容易处理。)
1700436693
1700436694
图5-4展示了在创建C语言程序时如何使用Yacc和Lex。该程序是某种语言的编译器。Yacc为语法分析器生成一个C语言文件,Lex为词法分析器生成另一个C语言文件。这两个C语言文件与其他包含语义的C语言文件组合在一起,由C语言编译器编译成可执行程序。这张图是用Pic程序制作的,Pic的结构也正是如此。
1700436695
1700436696
1700436697
1700436698
1700436699
图5-4 使用Yacc和Lex创建编译器
1700436700
1700436701
尽管Lex如此简单而强大,但长期以来,它并没有像Yacc那样得到广泛应用。也许这是因为对于相对缺乏经验的程序员来说,为复杂的语言编写语法分析器令人生畏,而编写词法分析器则不然。但是,亲手编写词法分析器,无论看起来多么简单直接,都未必是个好主意。
1700436702
1700436703
我在awk脚本语言上的经验(本章后文将讨论)可以借鉴。awk的第1版实现使用Yacc处理语法,使用Lex分解输入内容。然而,当我们试图将awk移植到非Unix环境时,Lex要么无法使用,要么虽然能用但生成了错误的词法分析器。几年后,我勉力用C语言重写了awk的词法部分,这样它就可以移植到所有环境中。但在之后的几年里,那段手工写出来的词法代码产生了许多缺陷和小问题,这些麻烦在Lex生成的版本中原本不存在。
1700436704
1700436705
这证明了一个普遍规则:程序帮你写的代码会比你自己手写的更正确、更可靠。如果改进了生成器,例如能生成更好的代码,那么每个人都会受益;相反,对手写程序的改进并不能改善其他程序。像Yacc和Lex这样的工具是这一规则的极好例子,Unix也提供了许多其他工具。编写程序的程序总是值得尝试。就像道格·麦基尔罗伊所言,“任何你必须重复做的事都有待自动化。”
1700436706
1700436707
3. Make
1700436708
1700436709
1700436710
1700436711
1700436712
1700436713
多数大型程序都由多个源文件组成,这些源文件必须被编译并连接在一起,才能创建可执行程序。这通常可以通过执行单个命令来完成,如用cc *.c来编译一个C语言程序的所有源文件。但是,在20世纪70年代,计算机速度非常慢。在对某个文件进行修改后,重新编译包含多文件的程序,花费的时间可能会以分钟计而非以秒计。更有效率的做法是,只重新编译修改后的文件,并将结果与之前编译的其他文件连接起来。
1700436714
1700436715
然而,记住哪些文件最近编译过,哪些需要重新编译,是件麻烦事,很容易出错。有一天,史蒂夫·约翰逊向斯图·费尔德曼(图5-5)诉苦说,在花了几个小时调试无果后,他才意识到,问题出在没重新编译他修改过的一个文件。
1700436716
1700436717
1700436718
1700436719
1700436720
图5-5 斯图·费尔德曼,约1981年(杰勒德·霍尔兹曼供图)
1700436721
1700436722
凑巧的是,斯图也有同样经历。他也曾白费劲调试过肯定正确只是没重新编译的程序。他想出了妙招:使用某种规格语言来描述程序的各个部分是如何相互依赖的。他写的Make程序分析这些规格,并根据文件修改时间来做尽可能少的重新编译,使所有东西都能同步向前。Make的第1版实现是在1976年。
1700436723
1700436724
“我在周末写出Make,然后在下个周末用宏重新写了一遍(因为内置代码列表太长了)。我没有修正tab-in-column-1[5]问题,因为很快就有了十几个忠实用户群,我不想惹恼他们。”
[
上一页 ]
[ :1.700436675e+09 ]
[
下一页 ]