Files

857 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# libtry 模块技术手册
> 文档版本:2026-07-30
> 代码版本:try.c(~741 行)
> 语言:C(嵌入式解释器)
> 定位:教学级脚本解释器,用于操作界面 shell
---
## 一、模块概述
### 1.1 定位
**libtry** 是一个基于 C 语言手写的**教学级解释器**,魔改自 GitHub 上的 tryC 项目。用于解析 `.try` 脚本文件,设计目标是作为**操作界面的 shell**。
### 1.2 特点
| 特性 | 说明 |
|------|------|
| 手写解释器 | 仅 741 行实现完整的词法/语法/执行引擎 |
| 零依赖 | 仅依赖标准 C 库 + `console.h` 输出接口 |
| 宽字符支持 | 使用 `wchar_t`,支持中文/多语言脚本 |
| 递归下降 | 递归下降语法分析器,边解析边执行 |
| 嵌入式设计 | 提供 `init_tryc`/`enqueue_tryc`/`eval_tryc`/`free_tryc` 四个核心 API |
### 1.3 与微服务体系的关系
| 属性 | 说明 |
|------|------|
| 模块关系 | **独立底层库**,不直接与任何微服务交互 |
| 可能用途 | 作为前端 UI 的脚本解释器,或嵌入式脚本引擎 |
| 启动方式 | 被其他模块(可能是 gateway 或 static)动态加载调用 |
---
## 二、技术架构
### 2.1 架构图
```
┌─────────────────────────────────────────────────────────────┐
│ 上层应用 │
│ (gateway / static / 其他模块) │
└─────────────────────────┬───────────────────────────────────┘
│ 调用 API
┌─────────────────────────▼───────────────────────────────────┐
│ libtry 解释器 │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 词法分析器 (next) │ │
│ │ 逐字符扫描 → 识别 token → 更新符号表 │ │
│ └──────────────────────────┬──────────────────────────────┘ │
│ │ token 流 │
│ ┌──────────────────────────▼──────────────────────────────┐ │
│ │ 递归下降语法分析器 │ │
│ │ statement() / expression() / factor() / term() │ │
│ │ 边解析边执行(解释器模式) │ │
│ └──────────────────────────┬──────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────▼──────────────────────────────┐ │
│ │ 符号表 │ │
│ │ symtab[128] — 符号栈(变量/函数/数组) │ │
│ │ levelNum — 嵌套层级,支持作用域 │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────────┐
│ 执行结果输出 │
│ printtovga() / 控制台输出 │
└─────────────────────────────────────────────────────────────┘
```
### 2.2 执行流程
```
init_tryc() 初始化
│
├── memset(symtab, 0, ...) 清零符号表
├── 预注册关键字 (array, func, else, if, return, while, print, puts, read)
└── malloc(128) 分配源代码缓冲区
│
enqueue_tryc(wc) 追加字符(交互式输入)
│
└── src[top_src] = wc; src[++top_src] = L'\0';
│
eval_tryc() 执行
│
├── next() 获取第一个 token
├── while (token != L'\0') { statement(); } 逐条执行
└── 重置缓冲区
│
free_tryc() 释放资源
│
└── free(p_src);
```
### 2.3 词法分析流程
```
next() 函数:
│
├── 跳过空白符 (L' ', L'\t')
├── 跳过注释 (# 开头到换行)
├── 识别标识符 (字母/数字/下划线/中文)
│ ├── 在符号表中搜索
│ │ ├── 找到:根据 type 返回对应 token (Sym, FuncSym, ArraySym, Str)
│ │ └── 未找到:创建新符号,类型为 Void
├── 识别数字 (整数 + 小数)
├── 识别字符字面量 ('a')
├── 识别字符串字面量 ("hello")
├── 识别运算符 (=, ==, !=, <, >, <=, >=, ||, &&)
└── 识别标点符号 (;, ,, +, -, *, /, (, ), {, }, [, ])
```
---
## 三、代码结构
### 3.1 文件结构
```
libtry/
├── try.c # 核心解释器实现(~741 行)
├── t.try # 示例脚本(斐波那契数列)
├── test/ # 测试目录
│ ├── t_assign_basic.try # 赋值测试
│ ├── t_function_basic.try # 函数测试
│ └── t_output_basic.try # 输出测试
├── .gitignore
└── README.robot.md # 本文档
```
### 3.2 try.c 函数索引
| 函数 | 行号 | 功能 |
|------|------|------|
| `init_tryc()` | L650 | 初始化解释器,预注册关键字 |
| `free_tryc()` | L672 | 释放源代码缓冲区 |
| `enqueue_tryc(wc)` | L677 | 追加一个字符到缓冲区 |
| `eval_tryc()` | L687 | 解析并执行当前缓冲区脚本 |
| `next()` | L85 | 词法分析,获取下一个 token |
| `match(tk)` | L289 | 匹配并消费期望 token |
| `statement()` | L466 | 解析并执行语句 |
| `expression()` | L362 | 解析加减法表达式 |
| `term()` | L311 | 解析乘除法表达式 |
| `factor()` | L326 | 解析因子(变量/常量/函数调用/数组) |
| `function()` | L616 | 执行函数调用 |
| `boolexp()` | L378 | 解析布尔表达式 |
| `boolAND()` | L423 | 解析 AND 表达式(短路) |
| `boolOR()` | L438 | 解析 OR 表达式(短路) |
| `clear(level)` | L269 | 清理指定层级的符号 |
| `clearThis()` | L280 | 清理单个符号的动态内存 |
| `skipStatments()` | L453 | 跳过 if/else 未执行分支 |
| `skipBoolExpr()` | L413 | 跳过短路布尔表达式 |
| `__exit()` | L702 | 错误处理(死循环挂起) |
| `main()` | L710 | 文件执行模式(仅 `_DEBUG` 编译) |
### 3.3 核心数据结构
#### 符号表(symStruct)
```c
typedef struct symStruct {
int type; // 类型:Num, Char, Str, Array, Func
wchar_t name[16]; // 符号名(MAXNAMESIZE)
double value; // 值或数组/字符串长度
union {
wchar_t *funcp; // 函数指针或字符串内容
struct symStruct* list; // 数组元素指针
} pointer;
int levelNum; // 嵌套层级(作用域)
} symbol;
```
#### Token 类型枚举
```c
enum {
Num = 128, // 数字
Char, // 字符字面量
Str, // 字符串
Array, // array 关键字
Func, // func 关键字
Else, // else 关键字
If, // if 关键字
Return, // return 关键字
While, // while 关键字
Print, // print 关键字
Puts, // puts 关键字
Read, // read 关键字
Assign, // = 赋值
OR, // ||
AND, // &&
Equal, // ==
Sym, // 变量符号
FuncSym, // 函数符号
ArraySym, // 数组符号
Void, // 未初始化
Nequal, // !=
LessEqual, // <=
GreatEqual // >=
};
```
#### 全局变量
| 变量 | 类型 | 说明 |
|------|------|------|
| `symtab[128]` | `symbol[]` | 符号表栈(SYMTABSIZE) |
| `symPointer` | `int` | 符号表栈指针 |
| `currentlevel` | `int` | 当前嵌套层级 |
| `src` | `wchar_t*` | 当前扫描位置 |
| `old_src` | `wchar_t*` | 行首位置(调试用) |
| `p_src` | `wchar_t*` | 源代码缓冲区起始 |
| `top_src` | `int` | 缓冲区写入位置 |
| `token` | `int` | 当前 token 类型 |
| `token_val` | `union tokenValue` | 当前 token 值 |
| `return_val` | `double` | 函数返回值 |
| `compileState` | `int` | 编译状态(debug/run) |
---
## 四、API 接口
### 4.1 初始化与销毁
#### init_tryc()
```c
int init_tryc(void);
```
**功能**:初始化解释器
- 清零符号表
- 预注册 8 个关键字(array, func, else, if, return, while, print, puts, read)
- 分配 128 字节宽字符缓冲区
**返回值**:
- `0`:成功
- `-1`:内存分配失败
#### free_tryc()
```c
void free_tryc(void);
```
**功能**:释放源代码缓冲区
### 4.2 输入与执行
#### enqueue_tryc(wc)
```c
int enqueue_tryc(wchar_t wc);
```
**功能**:追加一个字符到源代码缓冲区(用于交互式输入)
**参数**:
- `wc`:要追加的宽字符
**返回值**:
- `0`:成功
- `-1`:缓冲区已满
#### eval_tryc()
```c
void eval_tryc(void);
```
**功能**:解析并执行当前缓冲区中的脚本
**说明**:执行完成后会重置缓冲区。
### 4.3 典型使用示例
#### 交互式模式
```c
#include "try.c"
int main() {
init_tryc();
// 交互式输入
wchar_t input;
while (read_input(&input)) {
enqueue_tryc(input);
if (input == L'\n') {
eval_tryc(); // 遇到换行执行
}
}
free_tryc();
return 0;
}
```
#### 文件执行模式
```c
#include "try.c"
int main(int argc, char **argv) {
init_tryc();
// 读取文件
FILE *file = fopen(argv[1], "r");
wchar_t wc;
int i = 0;
while ((wc = fgetwc(file)) != WEOF) {
src[i++] = wc;
}
src[i] = L'\0';
fclose(file);
// 执行
eval_tryc();
clear(0); // 清理全局符号
free_tryc();
return 0;
}
```
---
## 五、语言特性
### 5.1 数据类型
| 类型 | 说明 | 示例 |
|------|------|------|
| `Num` | 数字(整数或浮点) | `a = 5;`, `b = 3.14;` |
| `Char` | 单个字符 | `c = 'A';` |
| `Str` | 字符串(宽字符) | `s = "hello";` |
| `Array` | 数组(动态分配) | `array arr(15);` |
| `Func` | 函数 | `func name{...};` |
### 5.2 变量与作用域
#### 变量声明
变量通过首次赋值自动声明:
```
x = 5; // 声明并初始化数字变量
name = "hello"; // 声明并初始化字符串变量
flag = 'A'; // 声明并初始化字符变量
```
#### 作用域
- 全局变量:在顶层声明
- 局部变量:在函数内声明,函数结束后自动销毁
- 符号表使用栈结构,`levelNum` 标识嵌套层级
### 5.3 运算符
#### 算术运算符
| 运算符 | 说明 | 示例 |
|--------|------|------|
| `+` | 加法 | `a + b` |
| `-` | 减法 | `a - b` |
| `*` | 乘法 | `a * b` |
| `/` | 除法 | `a / b` |
#### 比较运算符
| 运算符 | 说明 | 示例 |
|--------|------|------|
| `>` | 大于 | `a > b` |
| `<` | 小于 | `a < b` |
| `>=` | 大于等于 | `a >= b` |
| `<=` | 小于等于 | `a <= b` |
| `==` | 等于 | `a == b` |
| `!=` | 不等于 | `a != b` |
#### 逻辑运算符
| 运算符 | 说明 | 示例 |
|--------|------|------|
| `\|\|` | 逻辑或(短路) | `a \|\| b` |
| `&&` | 逻辑与(短路) | `a && b` |
| `!` | 逻辑非 | `!a` |
#### 赋值运算符
| 运算符 | 说明 | 示例 |
|--------|------|------|
| `=` | 赋值 | `a = 5;` |
### 5.4 控制流
#### if/else 语句
```
if (condition) {
// true 分支
}
else {
// false 分支
}
```
#### while 循环
```
while (condition) {
// 循环体
}
```
#### return 语句
```
func name{
return(value);
};
```
### 5.5 函数
#### 函数定义
```
func function_name{
// 函数体
return(return_value);
};
```
**特性**:
- 支持递归调用
- 支持参数传递(通过符号表)
- `return` 语句会终止函数执行并返回值
#### 函数调用
```
result = function_name(arg1, arg2);
```
### 5.6 数组
#### 数组声明
```
array arr(size);
```
- `size` 必须是正整数
- 数组元素初始化为 0
- 数组元素类型为 `Num`(double)
#### 数组访问与赋值
```
arr[0] = 10;
value = arr[i];
```
### 5.7 输入输出
#### print()
```
print(expression);
```
**功能**:输出数字
**示例**:
```
print(42); // 输出: 42.000000
print(3.14); // 输出: 3.140000
print(arr[0]); // 输出数组元素
```
#### puts()
```
puts("string");
```
**功能**:输出字符串
**示例**:
```
puts("Hello, World!");
```
#### read()
```
read(variable);
```
**功能**:读取输入(当前实现返回 -1)
### 5.8 注释
```
# This is a comment
```
注释从 `#` 开始到行尾结束。
---
## 六、示例脚本
### 6.1 斐波那契数列
```
# Fibonacci sequence
func fun{
if(x <= 2){
return(1);
}
y = 0;
x = x - 1;
y = fun(x);
x = x - 1;
return(y + fun(x));
};
# 计算 1-15 的斐波那契数列并存入数组
array arr(15);
x = 0;
while(x<15){
arr[x] = fun(x) + 1000;
x = x + 1;
}
puts("Fibonacci sequence:");
i = 0;
while(i < 15){
print(arr[i]);
i=i+1;
}
```
### 6.2 基础赋值
```
a = 5;
b = "ok";
print(5);
puts(b);
```
### 6.3 基础函数
```
func f_test{
return(0);
};
r = f_test();
print(r);
```
### 6.4 基础输出
```
print(1);
puts("1");
```
---
## 七、已知限制
### 🔴 严重问题
| ID | 位置 | 描述 | 影响 |
|----|------|------|------|
| L1 | [L20](file:///Users/qingsong/micro_servers/libtry/try.c#L20) | **缓冲区大小固定**:`POOLSIZE = 128` 字节,无法修改 | 长脚本将溢出 |
| L2 | [L702](file:///Users/qingsong/micro_servers/libtry/try.c#L702) | **`__exit()` 死循环**:`while(1);` 错误时直接挂死程序 | 无法恢复 |
| L3 | [L186](file:///Users/qingsong/micro_servers/libtry/try.c#L186) | **字符串内存泄漏**:`malloc` 分配的字符串在重新赋值时未释放 | 长期运行泄漏 |
| L4 | [L528](file:///Users/qingsong/micro_servers/libtry/try.c#L528) | **数组越界**:无越界检查 | 未定义行为 |
### 🟡 中等问题
| ID | 位置 | 描述 |
|----|------|------|
| L5 | [L598](file:///Users/qingsong/micro_servers/libtry/try.c#L598) | **`puts` 实现存疑**:原 `wprintf` 被注释,改用 `printtovga` |
| L6 | [L518](file:///Users/qingsong/micro_servers/libtry/try.c#L518) | **`clearThis()` 被注释**:作者注释说"不知为何需要注释了才能跑" |
| L7 | [L706](file:///Users/qingsong/micro_servers/libtry/try.c#L706) | **文件模式仅限 `_DEBUG`**:正常构建时 `main()` 不编译 |
| L8 | [L103](file:///Users/qingsong/micro_servers/libtry/try.c#L103) | **栈缓冲区溢出风险**:`nameBuffer[16]` 但标识符长度限制由 `MAXNAMESIZE=16` 控制 |
| L9 | [L21](file:///Users/qingsong/micro_servers/libtry/try.c#L21) | **符号表大小固定**:`SYMTABSIZE = 128` 个符号 |
### 🟢 轻微问题
| ID | 描述 |
|----|------|
| L10 | 错误处理过于简单,`match()` 失败直接 `__exit(-1)` |
| L11 | 大量被注释的调试代码(`//wprintf(...)`) |
| L12 | 全局变量过多(`symtab`, `token`, `token_val`, `src`, `old_src`, `currentlevel` 等) |
| L13 | 无并发支持,非线程安全 |
| L14 | 无单元测试 |
### 修复建议
#### 优先级 1(安全相关)
1. **修复数组越界**:在 [L528](file:///Users/qingsong/micro_servers/libtry/try.c#L528) 添加检查
```c
if (index < 0 || index >= s->value) {
printtovga(L"Array index out of bounds\n");
__exit(-1);
}
```
2. **修复 `__exit()`**:改为返回错误码
```c
static int error_code = 0;
static void __exit(int code) {
error_code = code;
// 不挂起,让调用者处理错误
}
```
3. **修复字符串内存泄漏**:在赋值前释放旧字符串
```c
else if (token == Str) {
if (s->pointer.funcp != NULL) {
free(s->pointer.funcp); // 释放旧字符串
}
s->pointer.funcp = (wchar_t*)token_val.ptr;
s->type = Str;
match(Str);
}
```
#### 优先级 2(功能增强)
4. **动态缓冲区**:将 `POOLSIZE` 改为可配置或动态扩容
5. **错误回调**:添加错误回调函数,让调用者处理错误
6. **符号表扩容**:允许 `SYMTABSIZE` 动态增长
---
## 八、开发指南
### 8.1 添加新关键字
在 [init_tryc()](file:///Users/qingsong/micro_servers/libtry/try.c#L650) 中添加:
```c
// 1. 在 enum 中添加新 token 类型
enum {
Num = 128, Char, Str, Array, Func,
Else, If, Return, While, Print, Puts, Read,
Assign, OR, AND, Equal, Sym, FuncSym, ArraySym, Void,
Nequal, LessEqual, GreatEqual,
NewKeyword // 新增
};
// 2. 在 init_tryc() 中注册
src = L"array func else if return while print puts read new_keyword";
for (i = Array; i <= NewKeyword; ++i) {
next();
symtab[symPointer - 1].type = i;
}
// 3. 在 statement() 中添加处理逻辑
else if (token == NewKeyword) {
match(NewKeyword);
// 处理逻辑
}
```
### 8.2 添加新运算符
在 [next()](file:///Users/qingsong/micro_servers/libtry/try.c#L85) 中添加识别逻辑:
```c
// 例如:+= 运算符
else if (token == L'+') {
if (*src == L'=') {
src++;
token = AddAssign; // 新增 token 类型
}
}
```
在 [statement()](file:///Users/qingsong/micro_servers/libtry/try.c#L466) 中添加执行逻辑。
### 8.3 自定义输出
修改 `printtovga()` 的实现,或添加可配置的输出回调:
```c
// 函数指针类型
typedef void (*output_func)(const wchar_t*);
// 全局输出函数
static output_func current_output = default_output;
// 设置输出函数
void set_output_func(output_func func) {
current_output = func;
}
```
### 8.4 构建说明
#### 编译器要求
- C 编译器(GCC/Clang)
- 支持 C99 标准
- 支持宽字符(`wchar.h`)
#### 编译选项
```bash
# 基本编译
gcc -c try.c -o try.o -std=c99
# 调试模式(启用 main 函数)
gcc -c try.c -o try.o -std=c99 -D_DEBUG
# 优化编译
gcc -c try.c -o try.o -std=c99 -O2
```
#### 链接使用
```bash
# 创建测试程序
cat > test_main.c << 'EOF'
#include <stdio.h>
#include "try.h"
int main() {
init_tryc();
// 注入脚本
wchar_t script[] = L"print(42);";
for (int i = 0; script[i] != L'\0'; i++) {
enqueue_tryc(script[i]);
}
eval_tryc();
free_tryc();
return 0;
}
EOF
# 编译链接
gcc test_main.c try.o -o test_try -std=c99
./test_try
```
---
## 九、测试
### 9.1 现有测试
| 测试文件 | 内容 |
|----------|------|
| `test/t_assign_basic.try` | 变量赋值与输出 |
| `test/t_function_basic.try` | 函数定义与调用 |
| `test/t_output_basic.try` | print/puts 输出 |
| `t.try` | 斐波那契数列(复杂示例) |
### 9.2 运行测试
```bash
# 编译(调试模式)
gcc try.c -o try_interp -std=c99 -D_DEBUG
# 执行测试脚本
./try_interp test/t_assign_basic.try
./try_interp test/t_function_basic.try
./try_interp test/t_output_basic.try
./try_interp t.try
```
### 9.3 添加新测试
在 `test/` 目录下创建 `.try` 文件:
```
# test/test_new_feature.try
# 描述新功能
a = 10;
b = 20;
c = a + b;
print(c);
```
---
## 十、版本历史
| 版本 | 日期 | 主要变更 |
|------|------|---------|
| 当前 | 2025-08-27 | 增加宽字符支持,修复 Str 匹配 BUG,使用 longjmp 替代 exit() |
| 初始 | — | 魔改自 tryC 项目,手写简易解释器 |
### 修改记录(代码注释)
| 日期 | 作者 | 变更 |
|------|------|------|
| 2025-08-27 | qing | 增加宽字符支持 |
| 2025-08-27 | qing | 修复 Str 漏匹配的 BUG |
| 2025-08-27 | qing | 注明内存泄漏风险 |
| 2025-08-27 | qing | 使用 `longjmp()` 替代 `exit()` |
| 2026-07-07 | qing | 增加 `clearThis()` 函数,用于释放变量 |
---
## 附录
### A. 完整关键字列表
```
array, func, else, if, return, while, print, puts, read
```
### B. Token 类型完整列表
```
Num(128), Char(129), Str(130), Array(131), Func(132),
Else(133), If(134), Return(135), While(136), Print(137), Puts(138), Read(139),
Assign(140), OR(141), AND(142), Equal(143), Sym(144), FuncSym(145), ArraySym(146), Void(147),
Nequal(148), LessEqual(149), GreatEqual(150)
```
### C. 限制常量
| 常量 | 值 | 说明 |
|------|-----|------|
| `POOLSIZE` | 128 | 源代码缓冲区大小(字节) |
| `SYMTABSIZE` | 128 | 符号表大小 |
| `MAXNAMESIZE` | 16 | 标识符最大长度 |
| `RETURNFLAG` | DBL_MAX | return 语句标记 |
### D. 参考资源
- [Crafting Interpreters](https://craftinginterpreters.com/) — 解释器编写经典教程
- [Writing an Interpreter in Go](https://interpreterbook.com/) — Go 实现的解释器教程
- [tryC (GitHub)](https://github.com/) — 原始项目(已魔改)
---
*本文档基于 try.c 源码分析生成,供开发团队参考。使用前请评估已知限制。*