我以前用C写过一个简单的解释器,它接受类似BASIC的代码,并解释执行。一个解释器主要包含以下部分:
- 词法分析器(lexer),用来把代码分割成一连串记号(token),如标识符、数字、运算符等,并且忽略空白和注释。
- 语法分析器(parser),用来分析代码的层次结构,并构造抽象语法树(AST)。
- 后端(backend),用来解释执行所生成的AST。
如果是编译器,那么后端的作用是生成机器代码,以便直接被CPU执行。
词法分析器可以手写也可以用lex自动生成。因为比较简单,所以往往采用手写的方式。语法分析器可以手写,通常采用递归下降(recursive-descent)算法;也可以用yacc自动生成,后者使用的是一种称为LALR(1)的技术。
最近发现Go的官方工具链自带了一个yacc工具(go tool yacc
),所以就试着用它写了一个类似的解释器。功能被进一步削减,只能算是一个demo。
能解释运行像这样的代码:
SUM = 0
I = 1
WHILE NOT I > 100 DO
SUM = SUM + I
I = I + 1
END WHILE
PRINT SUM
继续阅读 →Go从1.5版本开始支持动态链接库。目前官方工具链仅在linux-amd64平台支持动态链接;gccgo则支持更多的平台。
在此之前,Go的所有程序都采用静态链接。一个很简单的"hello, world"程序,因为引入了fmt
库(这个库进一步依赖其他代码),所以最终得到的可执行文件也比较大。如果将Go的标准库编译为动态链接库,就可以减小Go生成的可执行文件的大小。
我在Gentoo上安装了Go 1.6.3,要将标准库编译为动态链接库,需要以root权限执行
go build -buildmode=shared -linkshared std
这样就会产生/usr/lib/go/pkg/linux_amd64/dynlink/libstd.so
文件。这个文件在我的机器上有32MB大,包含了Go标准库的所有内容。
继续阅读 →生成器(generator)是Python的一个语法特性,例如生成平方数序列的生成器可以写成
def squares(n):
i = 1
while i <= n:
yield i * i
i += 1
若要求前10个平方数之和,只需
>>> sum(squares(10))
385
在Go语言中,使用goroutine配合channel,可以实现相同的功能:
func squares(n int) <-chan int {
ch := make(chan int)
go func() {
for i := 1; i <= n; i++ {
ch <- i * i
}
close(ch)
}()
return ch
}
然后可以用for语句遍历:
sum := 0
for term := range squares(10) {
sum += term
}
继续阅读 →乌鸦啊 为什么啼叫
因为乌鸦在那高山上
有着七位最可爱的
孩子等着她回家
好可爱呀好可爱
乌鸦如此啼叫着
好可爱呀好可爱
如此啼叫着的呀
到那山中的古巢中
走走看看吧
都是些有着圆圆眼睛的
好孩子们唷
继续阅读 →C语言的设计非常如同公理体系般简洁,而所能够演绎出来的内容又足够完备。这是对C的美的一种简单概括。
我要说的“公理体系”中的最核心的组件,便是指针。有智者说到,计算机科学中的一切问题,都可以通过增加一层间接(indirection)来解决 。指针可以用来解决什么问题呢?
继续阅读 →基本上只有到了一年一度的高考日子才有兴趣研究那么几道数学题。我已脱离高考三年,能力也是退化了许多。户枢不蠹,流水不腐。重温几道数学题,仿佛可以将已锈蚀的脑子润滑起来。
问题:已知锐角\(\triangle ABC\)满足\(\sin A = 2\sin B \sin C\)。则\(\tan A \tan B \tan C\)的最小值为______。
解答:因为
\[ \tan A \tan B \tan C = \frac{\sin A \sin B \sin C}{\cos A \cos B \cos C}, \]
代入
\[\left\lbrace\begin{aligned}
\sin A &= 2\sin B\sin C, \\
\cos A &= -\cos(B + C) = \sin B \sin C - \cos B \cos C
\end{aligned}\right.\]
得
继续阅读 →Vala是一个建立在GLib上的语言。GLib是基于C语言的基础功能库,和Boost之于C++的地位有几分类似。GLib除了提供了一些数据结构、线程操作、输入输出外,还有一套完全基于C语言的对象系统GObject。在此基础之上,才有了GTK+,这个在Linux上比较流行的图形工具包。
GLib很不错,善用之可以避免重新造很多轮子。GObject可能是摆脱C++后不得已的选择,因为图形界面这样的东西太依赖于面向对象的特性了。它们都基于C,设计得或许很漂亮,可用起来就比较麻烦了,因为C编译器不像C++那样能自动生成代码。例如,C++的shared_ptr
可以以几乎透明的方式实现引用计数:每一份拷贝在进入和退出作用域的地方,C++编译器可以自动插入增加引用和减少引用的代码。换作C的话,则需要在代码中明确写出来。
为了减轻开发人员的负担,提高开发效率,Vala被发明出来,它模仿C#的语法,在语法层面上支持面向对象。值得指出的是,Vala的编译器先将vala源文件转换成C源文件,然后调用C编译器来编译。因为幕后是C语言,所以一方面不会带来性能上的退化,另一方面也不会引入兼容性的困难。
继续阅读 →我对虚拟机中运行的FreeBSD总是很满意。所以产生了在真实环境中运行的念头。但是我天真了。尝试了才发现FreeBSD并不能驱动我的无线网卡,上不了网的话其他的也不要谈了。
只好乖乖地转投了Linux对怀抱。在Arch和Gentoo之间选择了一会儿。Arch很好很方便,我还用了装上去用了一会儿。它的软件版本之新也着实把我吓了一跳(例如gcc已经更新到了6.1.0版本)。
我最终选择了更加折腾的Gentoo。Gentoo配置起来更麻烦一些,所以有必要把相关步骤记录下来,以供日后参考。
继续阅读 →suckless.org有两个小程序:dwm和surf。我很喜欢。
现在的软件有一个不好的趋势,那就是越做越大。这可能和计算机资源越来越充裕有关系。随随便便就有一个上百GB的硬盘,那么占用5GB甚至更多的空间安装一个Gnome桌面环境也未尝不可。
软件之间互相依赖。我只需要A软件的功能,但是A需要B的功能X,所以我还得安装B。然而B却自带了其他与之不相关的功能Y, Z, …功能Y, Z又依赖其他的软件C, D, …这样下去就没完没了了。
继续阅读 →微分方程的数值解法是一个庞大的话题。这里描述一种简单的、容易想到的数值解法,它可以用简单的计算机程序实现。
以微分方程
\[\frac{dv}{dt} + v = \sin t\]
为例,这是一个一阶线性微分方程。求解的时候,把时间离散化,即\(t=n\Delta T\),\(\Delta T\)是最小的时间单位。假设我们只关心\(t\geq 0\)时的解,那么\(n\)是自然数。
考虑导数的定义
\[\frac{dv}{dt} = \lim_{\Delta t \to 0} \frac{\Delta v}{\Delta t} = \lim_{\Delta t \to 0} \frac{v(t)-v(t-\Delta t)}{\Delta t}.\]
在离散化的时间里,我们不可能让\(\Delta t\)无穷小,最小也只能等于时间单位\(\Delta T\)。所以时间离散化以后,我们用\(\frac{\Delta v}{\Delta T}\)来代替导数\(\frac{dv}{dt}\)。
在一个\(\Delta T\)内,\(v\)的增量\(\Delta v = v(t) - v(t-\Delta T)\)。为了书写方便,我们用记号\(v[n]\)表示\(v(n\Delta T)\)。考虑到\(t=n\Delta T\),有
\[\Delta v = v[n] - v[n-1].\]
于是,原来的微分方程就可以写成离散的形式
\[\frac{v[n]-v[n-1]}{\Delta T} + v[n] = \sin t.\]
将\(v[n]\)解出,得
\[v[n] = \frac{v[n-1] + \Delta T\sin t}{1+\Delta T}.\]
这就是一个递推式,给定初始值\(v[0]\),就可以递推地计算出后续的所有\(v[n]\).
继续阅读 →