我为什么要创造一门中文编程语言

言有所指,序有所行。
这便是“言序”这个名字最初的含义。

我是刘秀。

我写过网站,做过服务,维护过机器人和游戏项目,也经常花很多时间折腾那些并不会立刻带来回报的东西。

有些项目是为了解决问题,有些项目是为了学习技术,还有一些项目,只是因为心里始终有一个问题没有得到答案。

言序,属于最后一种。

我想知道:

中文能不能不只是出现在变量名和注释里,而是真正成为一门现代编程语言的表达基础?

不是把 if 改成“如果”,把 function 改成“函数”,再把 Python 或 JavaScript 的语法重新涂上一层中文外壳。

而是从词法、语法、类型、模块、错误信息、工具链到运行时,都重新思考一次:

当我们使用中文描述程序时,代码究竟应该是什么样子?

言序:中文编程语言与独立开发者

关于言序,以及我想寻找的另一种程序表达方式。

中文编程,为什么总像一个玩笑?

第一次听到“中文编程语言”时,很多人的反应大概都是相似的:

“是不是把英文关键词翻译成中文?”

“这不就是儿童编程吗?”

“写代码为什么不用英语?”

“中文歧义这么多,真的适合编程吗?”

这些问题并不冒犯,甚至非常合理。

过去的不少中文编程尝试,确实容易停留在关键词替换、语法演示或者教学玩具阶段。它们能够证明“中文可以写代码”,却很少继续回答另一个更困难的问题:

中文写出来的程序,能不能像一门真正的语言一样长期演进?

一门编程语言不仅要有几条漂亮的示例代码。

它还要面对变量作用域、闭包、类型检查、模块边界、依赖管理、异常传播、调试协议、编辑器补全、格式化、版本兼容、运行性能和软件供应链。

当一个项目开始面对这些问题时,它才真正从“语法创意”走向“语言工程”。

我不想让言序只停留在一张能够发到社交平台上的截图里。

我希望它能够运行真实程序,能够拆分模块,能够管理依赖,能够定位错误,能够在编辑器里补全和跳转,也能够在版本升级以后继续运行过去写下的代码。

所以从一开始,我真正想做的就不是“中文语法展示器”。

我想做的是一门语言。

关键词翻译与完整语言设计的区别

关键词翻译只改变表面;语言设计必须建立完整而稳定的工程结构。

我想寻找一种属于中文的程序节奏

英文编程语言有自己已经成熟的表达习惯。

ifelsereturnclassimport,经过几十年的使用,早已成为程序员下意识能够识别的符号。

中文当然可以直接翻译它们。

但我越来越觉得,单纯翻译并不能构成真正的中文表达。

比如,函数一定要叫“函数”吗?

代码文件一定要叫“文件”吗?

返回值一定要写成“返回”吗?

一个程序块结束时,中文有没有比右花括号更具有节奏感的表达方式?

在言序里,我选择了“法”表示可调用的方法与过程,用“文卷”称呼源代码文件,用“归”交回计算结果,用“终”收束一段结构。

一段简单的代码可以写成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
公 类 人 则
公 只 域 姓名:文;

法 初始化(姓名:文)则
置 此.姓名 为 姓名;


法 问候():文 则
归 「吾名」 加 此.姓名;



令 子:人 为 人(「言序」);
言 子.问候();

这段代码并不是文言文。

至少,它不是为了模仿古人说话而设计的文言文。

我更愿意把它称为一种文言风格的现代程序表达

它保留中文短句的节奏和收束感,但不会为了追求古雅而牺牲精确性。因为程序语言首先必须明确,其次才谈得上美感。

“法”“归”“终”这些词存在的意义,也不是为了让代码看起来更古老,而是希望它们在长时间使用后,能够形成属于言序自己的阅读习惯。

我希望使用者看到“归”时,能够像看到 return 一样立刻理解它的作用。

看到“终”时,能够自然意识到一段结构已经闭合。

看到“文卷”时,想到的不是一张普通磁盘文件,而是一份能够被解释、检查、格式化和执行的程序文本。

语言的成立,从来不仅依赖语法规则。

它还依赖使用者在长期阅读中逐渐建立的直觉。

中文不是为了排斥英文

创造一门中文编程语言,并不意味着我认为英文编程语言有什么问题。

相反,言序本身就是使用 Rust 编写的。

它的实现离不开 Rust 生态,也离不开操作系统、编辑器协议、版本管理、自动化构建和许多已有的开源项目。

言序使用中文,不是为了证明中文比英文“更先进”,更不是为了制造语言之间的对立。

我只是认为,编程语言本来就不应该只有一种文化外观。

一门语言可以来自工程需求,也可以来自数学传统、教育场景、艺术实验,或者某种对表达方式的好奇。

有人喜欢极简符号,有人喜欢强类型约束,有人喜欢函数式表达,有人愿意把程序写得接近自然语言。

那么,为什么不能有人尝试让中文本身成为程序结构的一部分?

这不是要替代什么。

它更像是在已经非常宽广的编程语言世界里,再开辟一条小路。

这条路未必通向所有人,也未必会成为主流。

但只要它能够走得足够远,沿途就一定会出现过去没有见过的风景。

不同语言共同进入同一个技术世界

不同的表达路径,可以共同构成同一个开放的技术生态。

真正困难的,从来不是把关键词换成中文

言序最早也只是从词法分析、语法解析和树遍历解释器开始。

源码被切分成一个个词法单元,再被组合成抽象语法树。解释器沿着语法树执行变量、分支、循环、函数、递归和闭包。

到了这里,我已经能够让一些中文代码运行起来。

但那只是最开始的部分。

当我继续往下做,问题很快变得复杂。

如何让错误信息准确指向中文全角字符?

如何让函数、类和模块拥有清晰的可见性边界?

如何检查参数类型、返回类型和不可变变量?

如何实现联合类型、泛型容器和控制流收窄?

如何让模块之间能够导入、缓存并识别循环依赖?

如何让同一份源码既可以由树解释器执行,也可以编译成字节码交给虚拟机运行?

如何确保两种执行器不会产生悄无声息的语义差异?

如何让 VS Code 提供补全、跳转、重命名、悬停和格式化?

如何让程序能够设置断点、单步执行并查看作用域变量?

如何管理项目依赖、锁定版本并支持离线安装?

又该怎样保证语言升级之后,过去的源码不会突然失效?

这些问题与中文还是英文已经没有太大关系了。

它们是任何一门认真发展的编程语言都必须面对的问题。

也是在处理这些问题的过程中,我逐渐确认:

我真正感兴趣的,并不只是中文语法,而是语言从无到有的完整过程。

现在的言序,已经走到了哪里?

截至写下这篇文章时,言序已经不再只是最初的解释器原型。

它拥有一套使用 Rust 编写的语言核心,包括词法分析、语法解析、语义检查、静态类型检查和源码级诊断。

它同时保留了两条执行路径:

一条是作为语义参考的树解释器。

另一条是不依赖抽象语法树回退的独立字节码虚拟机。

函数、闭包、类、继承、协议、异常、模块、容器和任务,都需要在两条执行路径中保持一致。每当语言加入一种新能力,我都不能只让其中一套实现“看起来能跑”,而是要让它们在正常结果、错误行为和边界条件上互相验证。

在语言之外,言序也开始拥有自己的工具链:

  • 静态检查与格式化;
  • REPL 交互环境;
  • LSP 语言服务器;
  • DAP 调试适配器;
  • VS Code 官方扩展;
  • Tree-sitter 语法;
  • API 文档生成;
  • 项目清单与锁文件;
  • 独立包管理器“言包”;
  • Rust 嵌入接口、C ABI 与 WASI 入口;
  • 版本化语言规范与兼容语料;
  • 面向旧语法的自动迁移工具。

此外,我还使用言序本身编写了“言据”。

言据是一种纯中文数据交换格式。它的解析器和序列化器没有使用 Rust 重写,也没有借用 JSON 解析器,而是由言序源码直接实现。

对一个成熟语言来说,这或许只是一个很小的库。

但对言序而言,它意味着这门语言开始不只能够运行演示代码,还能够反过来建设属于自己的生态。

言序语言核心与工具链全景

从中文源码到双执行路径,再从语言核心延伸到编辑器、调试、包管理与生态工具。

为什么选择 Rust?

很多人看到中文编程语言,可能会下意识觉得它更适合使用 Python 或 JavaScript 快速实现。

我最终选择 Rust,并不是因为它最容易。

恰恰相反,在开发初期,Rust 的所有权、生命周期和类型约束,确实让不少原本可以快速拼出来的结构变得更加谨慎。

但我希望言序不是一段只在某台电脑上能够运行的实验代码。

语言实现需要长期处理大量复杂状态:语法树、环境链、闭包捕获、对象引用、调用帧、模块缓存、虚拟机堆、错误踪迹和宿主权限。

Rust 的约束让我不得不更早面对这些结构的边界。

它也让我能够在保持原生性能的同时,减少许多内存安全问题,并较为自然地构建命令行程序、动态库和 WASM/WASI 入口。

更重要的是,我喜欢 Rust 所代表的那种工程态度:

很多事情不能因为“当前能够运行”就算完成。

边界必须明确,错误必须被处理,接口必须经过设计。

这与我希望赋予言序的性格十分接近。

言序的表面是中文的,内部却不应该含糊。

它可以有文言的节奏,但必须拥有现代工程需要的精确。

中文错误信息,不只是把英文报错翻译过来

中文语言还有一个常被忽略的问题:错误信息。

当程序出现问题时,用户真正面对的不是语言宣传页上的示例,而是终端里那一行失败提示。

如果错误信息只告诉你“语法错误”,却不说明错在哪里、为什么错、应该如何修改,那么无论语法多漂亮,这门语言都很难真正使用。

所以言序从早期开始,就尝试让词法、语法、语义、类型和运行时错误共享统一的源码位置。

报错不仅需要指出文件和行列,还要显示对应源码片段,用指示符标出具体范围,并在函数和模块调用之间保留调用踪迹。

而中文又带来了额外的问题。

汉字、全角标点和 ASCII 字符在终端中的显示宽度并不相同。如果只按照字节或普通字符数量计算位置,错误指示符就会发生偏移。

这是一件很小的事。

但一门语言是否认真,往往就体现在这些很小的地方。

我不希望言序只在正确运行时显得优雅。

我希望它在程序写错的时候,也能够认真地和使用者说话。

我并不认为“中文”天然更容易

中文编程经常会与“降低学习门槛”联系在一起。

但我对此一直比较谨慎。

把关键词写成中文,确实能够减少初学者面对陌生英文单词时的距离感。可编程真正困难的部分,从来不是记住几个关键词。

变量、状态、抽象、递归、并发、类型、复杂度,这些概念不会因为换成中文就自动变得简单。

一门糟糕的中文语言,甚至可能因为自然语言歧义而增加理解成本。

所以我不想宣称言序能够让所有人“零门槛学会编程”。

它真正能够提供的,是另一种进入程序世界的方式。

对于以中文思考和写作为主的人来说,代码中的语义或许可以更加直接。

对于编程语言爱好者来说,它可以成为研究 Unicode、语法、类型系统和工具链的实验场。

对于我自己来说,它则是一段持续学习的过程。

我通过它理解解释器、虚拟机、垃圾回收、语言服务器、调试协议、包管理和版本兼容。

中文只是这条道路的起点。

真正让我不断走下去的,是它背后几乎没有尽头的工程世界。

言序不会因为发布 1.0 就结束

软件项目很容易陷入对版本号的追逐。

仿佛只要写下 1.0.0,一个项目就突然成熟了。

但对于编程语言而言,1.0 的意义不应该是“功能很多”,而应该是从某个时间点开始,语言愿意为使用者承担兼容责任。

源码语义不能随意改变。

包清单和锁文件需要稳定。

字节码与机器输出要有明确版本。

新语法必须同步解释器、虚拟机、类型检查、格式化器、编辑器和文档。

被弃用的能力需要迁移周期,而不是在某次更新中突然消失。

因此,我更愿意把言序的 1.0 看作一份承诺,而不是一座终点。

它意味着这门语言终于完成了最基础的自我约束,也意味着之后的每一次变化,都不能再只考虑“这样写起来是不是更酷”。

语言设计者需要开始考虑那些已经写下代码的人。

从个人实验走向长期语言工程

1.0 是一座稳定的里程碑,也是通向标准库、生态、自举与社区的道路起点。

我希望言序最终成为什么?

我当然希望有一天,言序能够使用言序编写自己的更多组成部分。

现在,言据已经证明它能够承载真实的库实现。

未来,或许会有更多标准库、构建工具和生态项目逐渐由言序自身编写。再往后,它是否能够走向更完整的编译能力,甚至参与构建自己的编译器,是一个值得长期尝试的方向。

但比“自举”更重要的是,我希望言序拥有一种清晰的性格。

它应该是中文的,但不封闭。

它应该有传统文字的节奏,但不沉迷复古。

它应该允许代码显得优雅,但不能牺牲精确。

它应该欢迎实验,却对稳定版本保持克制。

它不需要假装自己能够替代现有主流语言,也不应该只满足于成为一次短暂的话题。

我希望它最终能够成为这样一门语言:

有人第一次看到它时,会因为中文代码感到新鲜。

有人继续了解它时,会发现它拥有真正的类型、运行时和工具链。

有人实际使用它时,会觉得其中一些设计确实自然。

也许最后,只有很少一部分人愿意长期使用它。

这也没有关系。

独立项目的价值,并不完全由用户数量决定。

有些项目的意义,是解决一个普遍问题。

有些项目的意义,是创造一个过去不存在的选择。

而言序对我来说,更接近后者。

写在最后

我创造言序,并不是因为世界缺少编程语言。

恰恰相反,这个世界已经有太多优秀的语言。

我创造它,是因为在这些语言之间,我仍然没有看到自己想要的那一种中文表达。

所以我决定亲手试一试。

从一个词法分析器开始,从第一棵语法树开始,从第一条能够运行的中文语句开始。

然后是类型、模块、解释器、虚拟机、编辑器、调试器、包管理器和语言规范。

每解决一个问题,后面都会出现更多问题。

但这大概正是创造一门语言最有趣的地方。

你永远无法真正“写完”它。

你只能让它在一次次选择中,逐渐变成自己应该成为的样子。

言有所指。

序有所行。

这就是言序。

也是我为什么想要创造一门中文编程语言。

项目入口