关于中文编程的一些思考

0. 为什么写这篇文章

笔者曾写过一篇《我眼中的C语言及其起源》(在其他地方叫《C语言发展史的点点滴滴》)的文章,文中从历史角度结合Unix系统源码来追溯了C语言的起源以及发展史(C语言的伟大毋庸置疑,但是其他语言带来的多样化也是十分重要),至于文章内容这里就不做过多赘述,感兴趣的读者可以查看原文。

本文则缘起于,在头条转载的本文评论中一位读者提到的一句话:”国产易语言在角落瑟瑟发抖”。看到这句话之后,顿时就对中文编程语言的原理产生了兴趣。由于平时业务开发过程中使用的主要语言是PHP/Golang,所以就从某种角度出发,并结合PHP、编译原理来分析一下中文编程语言的某些相似性原理。如果感兴趣的话,可以跟随笔者的步伐一探究竟。

1. 中文编程语言

如果说,给中文编程语言下一个定义的话:那就是,如其字面义,中文编程语言就是用中文汉字去编写代码逻辑,这些代码在经过编译处理之后,能够在现代计算机系统上跑起来的编程语言。

1.1 现存的中文编程语言

查了一下维基百科了解到:目前,中文编程语言大概有下面几种:
https://zh.wikipedia.org/wiki/%E4%B8%AD%E6%96%87%E7%B7%A8%E7%A8%8B%E8%AA%9E%E8%A8%80

  • BCY语言(1964年)
  • 朱邦复 曾设计过一些中文语言,如中文培基与中文cobol(1980年代)
  • 伙计培基(1987年)
  • 易语言(2000年)
  • 丙正正 C++语言的中文版本(2000年代 2001)
  • 唐凤 曾经设计过能以文言文写作的perl模块PerlYuYan(2002年)
  • 中蟒(2002年)
  • O语言 中文汇编语言(2005年)
  • ZhPy(周蟒,2007年)
  • 习语言 支持中文的C语言(2009年)
  • 中文小海龟 中文化的Logo语言(2009年)
  • 蝉语(2014年)
  • wenyan-lang 类文言文的编程语言(2020年)

对于中文编程语言,笔者也说不好其意义与价值。只是想说一句话:凡存在即合理。这个世界,也正是因为包容性,才有了今天的多彩多姿。(维基百科里面有这么一句话:使用中文编程语言,不需要记住大量的英文语句,中文用户可以在自己母语的基础上从更高方面入手。而且可以减少学习英语的成本,为汉语区的软件产业发展提供帮助。 间接的说明了其存在的意义)。

1.2 中文代码demo

[ 中文编写的示例代码 ]

上面程序执行结果为:这里是如果。

看到这里,你可能认为这段代码是使用上面的某种中文编程语言编写的。当然,相关中文编程语言也是可以实现该功能。但是,如果我告诉你,这是段PHP/Go代码的话,你会做何感想。

执行代码的PHP/Go程序用官方源码编译
代码是标准的PHP/Go格式代码

[ PHP执行示例代码 ]
[ Go执行示例代码 ]

是不是有种不可思议的感觉?是的话,那就对了。因为,这起码说明一个道理:大多数时候,你从主观角度看到的事实,往往不一定是其真实的写照。

你可能会产生下面疑惑:

1.PHP/Go怎么可能使用中文编写代码?
2.这个跟中文编程语言有什么关系?

如果你想到这两个问题的话,那我的目的也就达到了,具体原因且往下看。

2. 编程语言分类

从某种角度来看编程语言,大概分为以下几种类型:

1.编译型语言(C、C++、golang ……)
2.解释型语言(Python、JavaScript、PHP ……)
3.翻译 + 解析型语言(Java、C# ……)

语言类型 执行过程
编译型语言 源代码->汇编代码->机器码->CPU执行
解释型语言 源代码->中间代码->虚拟机->CPU执行
翻译+解析型语言 源代码->中间代码->虚拟机->机器码->CPU执行

首先,需要说明的是:上面几种分类包含的语言,都是属于自然语言的范畴。其次,计算机最终执行的,都是由01组成的机器语言。

不知道,你有没有跟笔者一样想过这样一个问题:这些自然语言最终是怎么在计算机上跑起来的?

2.1 编译器

编译器(compiler)是一种计算机程序,它会将某种编程语言写成的源代码(原始语言)转换成另一种编程语言(目标语言)。
https://zh.wikipedia.org/wiki/%E7%B7%A8%E8%AD%AF%E5%99%A8

它主要的目的是将便于人编写、阅读、维护的高级计算机语言所写作的源代码程序,翻译为计算机能解读、运行的低阶机器语言的程序,也就是可执行文件。编译器将原始程序(source program)作为输入,翻译产生使用目标语言(target language)的等价程序。源代码一般为高级语言(High-level language),如Pascal、C、C++、C# 、Java等,而目标语言则是汇编语言或目标机器的目标代码(Object code),有时也称作机器代码(Machine code)。

一个现代编译器的主要工作流程如下:
源代码(source code)→ 预处理器(preprocessor)→ 编译器(compiler)→ 汇编程序(assembler)→ 目标代码(object code)→ 链接器(linker)→ 可执行文件(executables),最后打包好的文件就可以给计算机去运行了。

[ 现代编译器的主要工作流程 ]

2.2 编译过程

编译器一般分为:前端和后端。

编译器的前端一般承担着词法分析、语法分析、类型检查和中间代码生成几部分工作(该部分也是设计一门新的编程语言,需要重点关注的部分。同时,也是各种编程语言差异化最大的部分)。而编译器后端主要负责目标代码的生成和优化,也就是将中间代码翻译成目标机器能够运行的机器码。

[ 经典的程序编译过程 ]

对应步骤所起到的作用为:

  • 词法分析 通过词法分析器识别出最小的语法单位,并用对应token表示其词性。(源程序字符串 → 单词符号)
  • 语法分析 通过语法分析器根据单词词性,分析语法。(单词符号 → 语法单位)
  • 语义分析与中间代码生成 根据单词值分析语义是否正确,生成中间代码。(语法单位 → 中间代码)
  • 代码优化 对中间代码进行优化,以期产生更高效的目标代码。(中间代码 → 优化后的中间代码)
  • 目标代码生成 将中间代码转换成低级语言代码。(中间代码 → 目标代码)
各种语言都有上述步骤,底层的实现也基本相似。

3. PHP词法分析

笔者这里使用PHP语言,来一步步分析其实现中文编程代码的原理。

3.1 PHP执行过程

从编程语言分类来看,PHP属于解释型语言,对于解释型语言来说,都会有一个解释器(php的解释器为zend引擎,zend这个词汇来源于最初两位核心开发者的名字组合的单词,后来成立了zend公司)。PHP源码的执行过程,大概分为下面几个步骤:

1.词法分析 (此处是本文重点)
2.语法分析
3.生成抽象语法树AST
4.生成中间码opcode
5.zend引擎执行opcode

[ PHP源码执行过程 ]

opcode在笔者看来是有点类似于汇编码的中间码,不过这里的中间码是被zend引擎执行的。

[ opcode结构 ]

为什么说,类似于汇编码呢?通过上面结构体可以看到几个关键字段:
操作数:op1、op2
返回值:result
操作数类型:op1_type、op2_type
返回值类型:result_type
操作动作:handler指针指向具体的操作动作处理方法

3.2 PHP中间代码与C汇编

下面分别使用解释型语言PHP、编译型语言C来实现相同的加法功能,并查看其中间码(下图显示的是简化之后的中间码),进行对比分析。

[ 实现加法功能的PHP源码与中间码opcode ]
这里获取PHP中间码opcode(可以结合3.2中zend_op结构体来分析),使用的是vld扩展。
[ 实现加法功能的C源码与中间码汇编 ]
这里获取C的中间码汇编,使用的是objdump命令。

通过对比可以发现,PHP的中间码与C语言的中间码具有某种相似性。

例如,实现a+b的操作:

1
2
3
4
// 汇编
add %edx,%eax
// opcode
ADD ~4 !0, !1

1.都有对应的操作指令2.都有两个操作数3.都有存储结果的位置

看到这里,是不是感觉发现了什么不得了的东西。

解释型语言的虚拟机,对应着的是编译型语言更底层执行逻辑的抽象。解释型语言的中间码,对应着的是编译型语言中间码的抽象。

各种语言的实现方式可能不太一样,但是实现原理是类似的。有些比较也可能看似有点牵强,但是采用这种方式却更容易理解。

3.3 自定义词法规则

笔者本次所用php源码版本为 php-7.4.10
词法分析规则文件:php-7.4.10/Zend/zend_language_scanner.l

[ PHP原生词法规则 ]
[ 原生词法规则与token的映射 ]
为了实现1.2示例中的代码,添加了几个自定义了词法规则。
[ 添加自定义词法规则 ]
[ 添加的自定义词法规则与token的映射 ]

词法分析器遇到一个词汇,就去查询一本词典(词法规则)。如果查到了该词汇的话,则会得到一个合规的token,接下来把token传递给语法分析器进行后续处理。

在这里,笔者只是在词法分析器的词典中多添加了几个条目,把新增的几个条目映射到原有的合法token上去,也正是这几个条目使得1.2处代码可以正确的执行(虽然是中文编写的,但是词法分析器可以正确的识别其含义)。

3.4 自定义词法验证

俗话说,无图无真相。下面来验证自定义的词法所产生的token是否是预期的。

[ 获取1.2示例代码的token ]
[ 获取1.2示例代码的token ]
从获取的token序列中可以看到,添加的词法规则被识别成了正确的对应token:
1
2
3
'如果' -> T_IF
'否则' -> T_ELSE
'输出' -> T_ECHO
也正是这一步操作,使得1.2示例中的中文代码demo可以被PHP程序正确执行。
  1. 添加自定义词法规则步骤
    这里详细记录一下,笔者添加自定义词法规则以及编译的过程。

1.编译安装re2c
re2c官网以及github仓库 http://re2c.org/ https://github.com/skvadrik/re2c
安装过程自行百度、Google,我这里使用的是 re2c 1.3。

2.下载并解压缩PHP官方源码
这里使用的版本是 7.4.10 https://www.php.net/distributions/php-7.4.10.tar.gz

3.添加自定义词法规则 编辑php-7.4.10/Zend/zend_language_scanner.l文件,添加下面代码。

1
2
3
4
5
6
7
8
9
10
11
<ST_IN_SCRIPTING>"如果" {
RETURN_TOKEN(T_IF);
}

<ST_IN_SCRIPTING>"否则" {
RETURN_TOKEN(T_ELSE);
}

<ST_IN_SCRIPTING>"输出" {
RETURN_TOKEN(T_ECHO);
}

4.使用re2c重新生成最新的词法分析器代码

1
/usr/local/re2c/bin/re2c --no-generation-date --case-inverted -cbdF -o zend_language_scanner.c zend_language_scanner.l

5.编译安装PHP程序 回到PHP源码根目录,编译安装PHP。这里笔者只是为了简单的测试使用,没有添加、开启其他选项。

1
2
3
./configure --prefix=/usr/local/php7.4
make
make install

到这里,恭喜你不仅已经拥有了一个跟笔者一样的,可以执行中文编写的1.2示例代码的PHP程序。而且,还可以基于PHP,来自定义一个属于自己的中文编程规范。

5. 中文编程语言的思考

从编程语言的分类角度来看,想要开发一种新的中文编程语言。一般需要考虑下面几件事情:

1.语言是编译型还是解释型2.如何定义语言词法规则3.如何定义语言语法规则4.什么样的中间码5.中间码生成机器码还是运行在虚拟机上

5.1 设计中文编程语言的思路

一般有两种选择:

I. 想要减小底层设计的复杂度,重点关注上层语言层面特性。一般是使用其他语言来构建当前语言,使用虚拟机来执行特定格式的中间码。例如:PHP使用C语言来编写底层内核逻辑,PHP源码生成的中间码opencode,运行在zend虚拟机之上。
II. 不依赖于其他语言,从底层到上层全部重新设计。最关键的是,要有一个支持该语言的编译器,能够直接将该语言编译成某种中间码或者机器码。例如:Golang语言(go语言前期几个版本使用C语言编写底层逻辑。后面实现了自举,底层除了少数文件使用汇编,其他全部使用go语言编写)。

貌似还有第三种选择:那就是跟笔者上面的例子一样,使用某种编程语言,仅仅是修改一下其词法规则,让其支持中文编程。(这种方法相当于穿了个新的马甲,不能称之为新的编程语言,称之为汉化更合适。当然,很多时候也是让人嗤之以鼻的。不禁让笔者想起来了若干年前,关于国产CPU的一件事情:买一颗其他厂商的CPU,打磨掉旧的logo,换个新的logo。总之,前路漫漫,道阻且长。还是希望国内高手们能齐心协力,发明一款国人亲手打造的、被广泛使用的编程语言)。

从目前的情况来看,大部分的中文编程语言采用的是第一种实现方法。但是,也不排除部分采用第二种方法,当然该方法对开发人员来说也是难度最大的一种。

这里想要着重说明一点:中文编程不是简单的编程关键字替换为中文,而是中文编程时所使用关键字的底层API调用、类库支持、系统交互等一系列的设计。

5.2 中文编程的意义及未来

正所谓,仁者见仁智者见智。至于中文编程的意义以及未来,笔者也搜索了一些文章,或支持、或反对,每个人有自己的独特见解。感兴趣的话读者可以搜索相关资料。

如果,你问我:你相信中文编程会有未来吗?

那么,我可能会说:我永远相信美好的事情,一定会发生。

这里抛出一个笔者个人的观点(不引战,杠精绕道):中文编程对国人来说也是另一种选择,排除那些专业的开发人员。起码,对那些不懂编程的爱好者或者低龄儿童来说,只要稍微熟悉一些逻辑,也能编写出一些优秀的软件。又何乐而不为呢?

当然,想要搞出一款纯粹的中文编程语言也会是有一定难度。从计算机体系结构来看,排除高层次的编程语言层面来看,底层的操作系统、指令集也牵扯巨量工程。如果,非要集国人之力搞出这么一套也不是不可能,只是从成本角度来看,投入跟产出比不会太大。当前,世界正在成为一个地球村。各种文化的融合交汇,也促使着时代滚滚向前的进程,很多新事物往往也是顺应着时代潮流,在各个行业众多的顶尖人才参与下才得以出现。我们也应该顺应时代潮流,参与进去,增加影响力、参与标准化的制定,这才是一个明智的选择。同时,也应该掌控一些核心科技才是王道。

最后,最重要的一点就是:生态。编程语言只是个工具,有了工具(哪怕是非常好用的工具)也需要有人、有很多人来用,才能更体现其价值(有句古话:酒香也怕巷子深)。有很多人用,自然会围绕该语言构建出一些优秀的软件、系统。往往也牵扯很多行业的支持,整个就构成了一个生态。没有很好的生态,是很难存活下去的(历史上不乏有很多例子)。

6. 总结

本文以自定义词法规则来实现PHP的中文编程为例,引入编程语言的词法分析步骤。同时,通过简述编译器的执行过程,引出设计一种编程语言所应该具有的一些思路与逻辑。这里,也仅仅是抛砖引玉。

这世界上唯一不变的,就是变化。编程语言排行榜之上,各种语言浮浮沉沉,出现、消亡,都是时代所驱使的结果。但是,有一点是具有共通性的,那就是实现原理。想必,掌握了这一点,也就基本掌握了大部分的不变。

鉴于个人能力有限,如有问题或者缺陷,欢迎指正。

7. 参考资料及扩展阅读

[1]https://juejin.im/post/6867124787576373256 聊一聊编译过程
[2]https://zh.wikipedia.org/wiki/%E7%B7%A8%E8%AD%AF%E5%99%A8
[3]http://www.ruanyifeng.com/blog/2014/11/compiler.html 编译器的工作过程
[4]https://zhuanlan.zhihu.com/p/53336801 人人都能读懂的编译器原理
[5]https://zhuanlan.zhihu.com/p/64768035 编译原理:语法分析器的设计和实现
[6]https://draveness.me/golang/docs/part1-prerequisite/ch02-compile/golang-compile-intro/ go编译原理
[7]https://www.cnblogs.com/OceanEyes/p/implement_a_interpreter.html 从编译原理看一个解释器的实现