作者用Python完整实现了一门新语言NexPro,涵盖词法分析、语法解析、AST、解释器全链路,适合想理解编译原理的开发者。
我不只是写了一门编程语言,我构建的是让这门语言运转的整个流水线。
🔗 NexPro GitHub:https://github.com/probal2005/NexPro
世界上已经存在成千上万种编程语言。
所以再构建一门听起来似乎没有必要。
但我创建 NexPro 的目标从来不是与 Python、JavaScript、Rust 或 C++ 竞争。
我想回答的是一个更简单的问题:
写代码和得到输出之间到底发生了什么?
所以我没有仅仅学习词法分析器、解析器、AST、解释器和运行时的理论,而是决定自己实现它们。
这篇文章记录了我实际构建了什么、什么在今天可以工作、我学到了什么,以及还有什么需要完成。
NexPro 是一门用 Python 实现的实验性编程语言。
它的源文件使用:
.pa
一个简单的 NexPro 程序是这样的:
name = "Probal"
city = "Kolkata"
say name
say city
重要的不是这个语法有多简单。
重要的是它内部发生了什么。
源代码不会直接从:
.pa 文件
变成:
输出
而是会经过多个阶段。
NexPro 的核心思想是:
┌───────────────────┐
│ NexPro Source │
│ .pa │
└─────────┬─────────┘
│
▼
┌───────────────────┐
│ Lexer │
│ Source → Tokens │
└─────────┬─────────┘
│
▼
┌───────────────────┐
│ Parser │
│ Tokens → AST │
└─────────┬─────────┘
│
▼
┌───────────────────┐
│ AST │
│ Program Structure │
└─────────┬─────────┘
│
▼
┌───────────────────┐
│ Interpreter │
│ Execute Nodes │
└─────────┬─────────┘
│
▼
┌───────────────────┐
│ Runtime │
│ Values & State │
└─────────┬─────────┘
│
▼
┌───────────────────┐
│ Output │
└───────────────────┘
这个流水线不仅仅是文档中的示意图。
这些是代码库中实际实现的概念组件。
项目围绕这门语言本身来组织:
NexPro/
│
├── nexpro/
│ ├── cli.py
│ ├── lexer.py
│ ├── parser.py
│ ├── interpreter.py
│ ├── runtime.py
│ ├── tokens.py
│ ├── ast.py
│ ├── errors.py
│ └── __version__.py
│
├── examples/
│ ├── hello.pa
│ └── variables.pa
│
├── tests/
│
├── README.md
├── LICENSE
└── pyproject.toml
每个部分都有其存在的理由。
这种分离很重要,因为当词法分析、解析、执行和运行时逻辑混在一起时,语言实现会变得难以维护。
语言项目不应该止步于语法图。
它需要执行程序。
NexPro 提供了一个 CLI 命令:
nexpro run examples/hello.pa
对于这样的程序:
say "Hello NexPro!"
解释器会产生:
Hello NexPro!
变量也可以使用:
name = "Probal"
city = "Kolkata"
say name
say city
Probal
Kolkata
这给我们一条完整的路径:
hello.pa
↓
CLI
↓
Lexer
↓
Parser
↓
AST
↓
Interpreter
↓
Output
name = 10
编程语言实现不需要把它当作一个巨大的字符串来处理。
词法分析器可以把它分解成有意义的片段:
IDENTIFIER(name)
ASSIGN(=)
NUMBER(10)
这种转换是基础性的。
"这些片段是什么?"
"这些片段之间是什么关系?"
解释器回答:
"这些关系应该做什么?"
这种分离是我在构建 NexPro 时理解的最重要的事情之一。
a = 10 + 20
解析器不需要简单地记住:
"10 + 20"
它可以从结构上表示这个表达式:
Assignment
/ \
a Binary(+)
/ \
10 20
这就是抽象语法树(AST)。
AST 给了解释器一个程序员所写代码的结构化表示。
"这个字符串里有哪些字符?"
解释器可以问:
"我正在执行的是什么类型的节点?"
这种区分对语言实现来说是基础性的。
a = 10
b = 20
say a + b
重要的表达式是:
a + b
+
/ \
a b
然后解释器可以解析:
a → 10
b → 20
10 + 20
30
这就是一个简单的语法特性如何开始展示完整语言流水线的地方。
起初,词法分析看起来很简单。
a = 10
但真实的语言语法很快就会引入问题。
a = 10 + 20
name = "Probal"
say "Hello World"
现在词法分析器需要区分:
IDENTIFIER
NUMBER
STRING
ASSIGN
PLUS
SAY
它还需要处理诸如:
Whitespace(空白字符)
Unknown characters(未知字符)
Strings(字符串)
Numbers(数字)
Identifiers(标识符)
Operators(运算符)
End of file(文件结束)
这是我最初的重大教训之一:
编程语言在解析之前就已经开始了。
解析器是语法变成结构的地方。
a = 10 + 20
并不等同于:
a + 10 = 20
解析器需要理解关系和优先级。
即使简单的算术也开始引发问题:
10 + 20 * 5
(10 + 20) * 5
10 + (20 * 5)
语言设计很快就会变成以下组合:
Syntax(语法)
+
Grammar(文法)
+
Precedence(优先级)
+
AST design(AST 设计)
这是我实现之前没有完全体会的事情。
解释器是语言变得可执行的地方。
它需要理解诸如:
Number(数字)
String(字符串)
Variable(变量)
Assignment(赋值)
Binary Expression(二元表达式)
Say(输出)
x = 100
say x
需要运行时维护状态:
Environment(环境)
x → 100
say x
需要解释器检索:
x → 100
并将值发送到输出。
这就是作用域、环境、值、求值和运行时状态这些概念从理论变成实践的地方。
NexPro 目前是用 Python 实现的。
这是有意为之。
对于一个实验性解释器,Python 给了我:
language design(语言设计)
+
lexing(词法分析)
+
parsing(解析)
+
AST(抽象语法树)
+
interpretation(解释执行)
而无需一开始就解决底层实现问题。
这并不意味着 Python 一定是最终的实现语言。
这只是说 Python 是一个实用的起点。
语言实现很容易崩溃。
Lexer 改动
↓
Token 改动
↓
Parser 崩溃
↓
AST 变化
↓
Interpreter 崩溃
这就是为什么 NexPro 包含测试。
项目有一个专门的:
tests/
目录用于语言行为测试。
随着 NexPro 的发展,我希望测试套件覆盖:
Lexer
↓
Parser
↓
AST
↓
Interpreter
↓
Runtime
↓
CLI
目标是让每一个新的语言特性都可衡量且可复现。
当前的实现有意做得很小。
项目已经从源代码模拟进化成了一个可执行解释器,包含了以下组件:
✓ CLI 执行
✓ 词法分析
✓ Token
✓ 解析
✓ AST 表示
✓ 变量
✓ 赋值
✓ 字符串
✓ 数字
✓ 基本表达式
✓ 解释执行
✓ 运行时结构
✓ 错误处理结构
✓ 测试
确切支持的语法将随着语言的发展继续变化。
这个区分很重要。
我记录的是存在的功能,而不是把计划中的特性当作已完成的特性来展示。
这同样重要。
❌ 生产级编译器
❌ Python 替代品
❌ 高性能语言
❌ 成熟的标准库
❌ 完整的生态系统
❌ 稳定的 1.0 语言
仍有很多重要领域需要开发:
Functions(函数)
Loops(循环)
Conditionals(条件判断)
Collections(集合)
Modules(模块)
Type system(类型系统)
Standard library(标准库)
REPL
Tooling(工具链)
Debugger(调试器)
Package management(包管理)
Performance(性能)
而这正是我认为它是一个有趣的工程项目的原因。
我的路线图目前分为三个阶段。
✓ Lexer
✓ Tokens
✓ Parser
✓ AST
✓ Interpreter
✓ Variables
✓ Basic expressions
✓ CLI
→ Conditionals
→ Loops
→ Functions
→ Collections
→ REPL
→ Better diagnostics
→ Formatter
→ Documentation
→ VS Code syntax highlighting
→ Debugging tools
→ Improved testing
→ Modules
→ Standard library
→ Package system
→ Bytecode
→ Performance improvements
→ Possible compilation
这些是目标,不是已完成的特性。
NexPro 最大的成果不是语法。
是理解。
在构建语言之前,我知道这些词汇:
Lexer
Parser
AST
Interpreter
Runtime
在实现它们之后,我开始把编程语言看作流水线。
say 10 + 20
我现在可以在脑海中看到:
SOURCE(源代码)
↓
TOKENS(Token 流)
↓
SYNTAX(语法)
↓
AST(抽象语法树)
↓
EVALUATION(求值)
↓
RUNTIME(运行时)
↓
30
这种理解上的转变才是我开始 NexPro 的真正原因。
下一个有趣的问题不仅仅是:
"NexPro 应该有什么语法?"
"我能把这门始于 Python 解释器的语言推进到什么程度?"
我想探索的一些问题:
NexPro 能有真正的类型系统吗?
age: number = 20
name: string = "Probal"
它能编译成字节码吗?
Source → AST → Interpreter
Source(源代码)
↓
AST
↓
Bytecode(字节码)
↓
Virtual Machine(虚拟机)
这门语言能有自己的包系统吗?
import math
它最终能有 IDE 体验吗?
NexPro
├── Language Server
├── Formatter
├── Debugger
└── VS Code Extension
这些是未来的实验。
我不想让读者仅仅信任一个描述,我希望代码库本身就是证据。
lexer.py
parser.py
ast.py
interpreter.py
runtime.py
tokens.py
errors.py
cli.py
tests/
examples/
你可以运行这些示例。
你可以审视实现。
你可以提出修改建议。
你可以尝试破坏它。
这就是我希望 NexPro 演进的方式。
我特别感兴趣的是来自以下领域开发者的反馈:
Compilers(编译器)
Interpreters(解释器)
Programming Languages(编程语言)
Parser Design(解析器设计)
ASTs(抽象语法树)
Python
Developer Tooling(开发者工具)
Language Design(语言设计)
一些我真诚希望得到反馈的问题:
当前的架构对于一个解释器来说合理吗?
下一应该实现哪个语言特性?
NexPro 应该保持动态类型还是最终引入静态类型?
下一个重要里程碑应该是 REPL、函数还是字节码虚拟机?
在项目变得更大之前,我应该修复哪些架构错误?
NexPro 始于一个简单的实验:
我能构建一门编程语言吗?
"我构建了下一个 Python。"
这不是这个项目的定位。
更准确的答案是:
我构建了一个小巧的、可执行的编程语言实现,并用它在探索语言是如何设计和执行的。
而这已经让我对编程语言内部原理的理解超过了单纯阅读相关资料所能达到的程度。
目前的旅程是这样的:
NEXPRO
Source Code(源代码)
│
▼
Lexer(词法分析器)
│
▼
Tokens(Token 流)
│
▼
Parser(解析器)
│
▼
AST
│
▼
Interpreter(解释器)
│
▼
Runtime(运行时)
│
▼
Output(输出)
有趣的是,这只是开始。
如果你想审视实现、实验语法或做出贡献:
🔗 GitHub:https://github.com/probal2005/NexPro
如果你发现有问题,打开一个 Issue。
如果你有想法,告诉我。
如果你想实验,fork 它。
如果你比我更懂编程语言实现,请告诉我我做错了什么。
这正是我想要的反馈。
NexPro 今天还很小。
但每一门编程语言都是从某个地方开始的。
你接下来会构建什么?
函数、REPL、类型系统,还是字节码虚拟机?