Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a256d9be1 | |||
| a45f7d8e2f | |||
| beac40fd74 | |||
| a0a78de26e | |||
| 095604dc9f |
@@ -6,62 +6,101 @@ L Language v0.5 — 用 C17 实现的静态类型编译型编程语言,Rust
|
||||
|
||||
## 语言设计哲学
|
||||
|
||||
L 借鉴 Rust 语法骨架(let/mut/struct/impl/match),但有自己的设计理念:
|
||||
L 借鉴 Rust 语法骨架(let/mut/fn/struct/impl/match),但有自己的设计理念:
|
||||
|
||||
1. **去糖优先** — 复杂语法在 parser 层去糖为简单原语,零 sema/codegen 改动。已验证: for/match/复合赋值。
|
||||
2. **博采众长** — 从多语言吸收优秀特性,而非单一模仿 Rust:
|
||||
- **Elixir/F#** → 管道运算符 `|>`
|
||||
- **Go** → defer、多返回值
|
||||
- **Swift** → guard、字符串插值、命名参数
|
||||
- **Kotlin** → 扩展函数、when 表达式
|
||||
- **Python** → 列表推导式、装饰器
|
||||
- **Zig** → comptime 常量折叠
|
||||
2. **博采众长,L 化适配** — 从多语言吸收优秀特性,但必须适配 L 的语法风格,**不能直接照抄**。每条特性要问:这个特性的核心价值是什么?在 L 里最自然的表达方式是什么?
|
||||
3. **渐进演进** — 先加语法糖(parser 层),后加强类型(sema/codegen 层)
|
||||
|
||||
### L 化适配原则
|
||||
|
||||
从其他语言借鉴特性时,必须按 L 的风格加以改造:
|
||||
|
||||
| 规则 | 说明 | 反例(直接照抄) | 正例(L 化后) |
|
||||
|------|------|-----------------|---------------|
|
||||
| **花括号块** | 所有代码块用 `{}`,不引入缩进块 | Python `for x in arr:` + 缩进 | `for x in arr { ... }` |
|
||||
| **类型后置** | 类型标注用 `name: Type`,不用前置类型 | C `int x`、Go `func f() int` | `let x: i64`、`fn f() -> i64` |
|
||||
| **snake_case** | 函数/变量用 snake_case,类型用 PascalCase | `drawRect()`、`MAX_SIZE` | `draw_rect()`、类型 `MaxSize`(但常量仍 `MAX_SIZE`) |
|
||||
| **显式 return** | 返回值必须写 `return`,不用隐式尾表达式 | Rust `fn f() -> i64 { 42 }` | `fn f() -> i64 { return 42; }` |
|
||||
| **关键字优先** | 新特性优先用关键字,不引入符号噪音 | `x?.y`(空安全链) | 暂不引入,等类型系统成熟后用 `?` 也可接受 |
|
||||
| **保持对称** | 开闭符号成对,不用 `end`/`done` 等结尾词 | Ruby `if ... end` | `if ... { }` |
|
||||
| **语义透明** | 语法糖的去糖结果必须可预测,文档里写清楚 | 隐藏的隐式转换 | 每个 sugar 标注 `// 去糖为: ...` |
|
||||
| **一致缩写** | 关键字缩写风格统一,不混用缩写与全称 | `func` + `def` + `fn` 混用 | 全部用短关键字: `fn`/`let`/`mut` |
|
||||
|
||||
### 各语言特性借鉴分析
|
||||
|
||||
分析每种语言时,区分"核心思想"和"表面语法",取其思想、适配语法:
|
||||
|
||||
| 来源语言 | 特性 | 核心思想 | L 化语法 | 适配要点 |
|
||||
|----------|------|---------|---------|---------|
|
||||
| **Elixir/F#** | 管道 | 数据从左到右流经函数 | `10 \|> double() \|> add(5)` | 保持 `()` 调用、不用空格传参 |
|
||||
| **Swift** | guard | 提前返回(早退模式) | `guard x >= 0 else { return -1; }` | 保留 `else` 关键字 + 花括号块 |
|
||||
| **Swift** | 字符串插值 | 表达式嵌入字符串 | `"Hello, \\(name)!"` | 用 `\\(...)` 而非 `{...}`,避免与块语法冲突 |
|
||||
| **Swift/Kotlin** | 命名参数 | 调用点标注参数名 | `draw_rect(width: 10, height: 20)` | 用 `:` 分隔(与类型标注一致) |
|
||||
| **Python** | 列表推导式 | 声明式生成列表 | `[for x in arr if x > 0: x * 2]` | 用 L 的 `for-in` 语法,不用 Python 后缀式 |
|
||||
| **Python** | 装饰器 | 函数/类型的元编程包装 | 慎用 `@` 符号,考虑用 `#[attr]`(Rust 风格更搭 L) | 语法需与 attribute 体系统一设计 |
|
||||
| **Go** | defer | 作用域退出时执行 | `defer cleanup();` | 关键字 + 常规调用语法 |
|
||||
| **Go** | 多返回值 | 函数返回多个值 | `fn div(a: i64, b: i64) -> (i64, i64)` | 用元组括号,不用 Go 的裸逗号 |
|
||||
| **Zig** | comptime | 编译期求值 | `const x = comptime 1 + 2;` | `const` 表明编译期常量 |
|
||||
| **Kotlin** | 扩展函数 | 为已有类型添加方法 | `impl str { fn is_email(self: str) -> bool { ... } }` | 复用 `impl` 块,不用 `Type.method` 语法 |
|
||||
| **Kotlin** | when 表达式 | 多分支条件 + 返回值 | 已有 `match`,将 match 升级为表达式 | 不用新关键字,扩展现有语法 |
|
||||
| **Carbon/Cpp2** | in/out 参数 | 明确参数传递方向 | `fn f(in x: i64, out y: i64)` | 复用类型标注位置,前缀修饰 |
|
||||
|
||||
### 候选特性优先级
|
||||
|
||||
| 优先级 | 特性 | 来源 | 实现层 | 预计行数 | 独特性 |
|
||||
|--------|------|------|--------|---------|--------|
|
||||
| **P0** | 管道 `\|>` | Elixir/F# | parser | ~30 | 极高 |
|
||||
| **P0** | guard 语句 | Swift | parser | ~15 | 高 |
|
||||
| **P0** | 字符串插值 | Swift/Python | lexer+parser | ~60 | 高 |
|
||||
| **P0** | 命名参数 | Swift/Kotlin | parser | ~50 | 高 |
|
||||
| P1 | 列表推导式 | Python | parser | ~50 | 高 |
|
||||
| P1 | 装饰器 `@` | Python | parser | ~40 | 高 |
|
||||
| P1 | defer | Go/Zig | parser+codegen | ~60 | 中 |
|
||||
| P1 | 扩展函数 | Kotlin | parser+sema | ~80 | 高 |
|
||||
| P1 | when 表达式 | Kotlin | parser | ~30 | 中 |
|
||||
| P2 | 多返回值 | Go | 全流水线 | ~200 | 中 |
|
||||
| P2 | 空安全 `?.` / `??` | Kotlin | sema+codegen | ~150 | 中 |
|
||||
| P2 | comptime 求值 | Zig | sema+codegen | ~200 | 极高 |
|
||||
| P3 | 隐式接口 | Go | sema+codegen | ~300 | 中 |
|
||||
| P3 | in/out 参数 | Carbon/Cpp2 | sema+codegen | ~150 | 高 |
|
||||
| 优先级 | 特性 | 来源 | 实现层 | 预计行数 | L 化关键 |
|
||||
|--------|------|------|--------|---------|---------|
|
||||
| **P0** | 管道 `\|>` | Elixir/F# | parser | ~30 | 保持 `()` 调用风格 |
|
||||
| **P0** | guard 语句 | Swift | parser | ~15 | `else` + 花括号块 |
|
||||
| **P0** | 字符串插值 | Swift | lexer+parser | ~60 | `\\(expr)` 避免 `{}` 歧义 |
|
||||
| **P0** | 命名参数 | Swift | parser | ~50 | `name: val` 与类型标注统一 |
|
||||
| P1 | 列表推导式 | Python | parser | ~50 | 用 `[for x in arr if cond: expr]`,不用 Python 后缀式 |
|
||||
| P1 | 装饰器 | Python | parser | ~40 | 考虑 `#[attr]` 而非裸 `@` |
|
||||
| P1 | defer | Go | parser+codegen | ~60 | 关键字 + 花括号/单语句 |
|
||||
| P1 | 扩展函数 | Kotlin | parser+sema | ~80 | 复用 `impl` 块,不造新语法 |
|
||||
| P1 | match 表达式 | Kotlin | parser | ~30 | 扩展现有 match,不引入 `when` |
|
||||
| P2 | 多返回值 | Go | 全流水线 | ~200 | `(T, U)` 元组语法,不用裸逗号 |
|
||||
| P2 | 空安全 `?.` / `??` | Kotlin | sema+codegen | ~150 | 保留符号运算符(符号在此场景比关键字更清晰) |
|
||||
| P2 | comptime 求值 | Zig | sema+codegen | ~200 | `comptime` 作为表达式前缀 |
|
||||
| P3 | 隐式接口 | Go | sema+codegen | ~300 | 暂远,与 trait 体系统一设计 |
|
||||
| P3 | in/out 参数 | Carbon/Cpp2 | sema+codegen | ~150 | 前缀修饰符,放在 `name: Type` 之前 |
|
||||
|
||||
### P0 特性设计
|
||||
### P0 特性设计(L 化版)
|
||||
|
||||
**管道 `|>`** — 数据流从左到右:
|
||||
**管道 `|>`** — 数据流从左到右,保持 `()` 调用:
|
||||
```rust
|
||||
let result = 10 |> double() |> add(5);
|
||||
// 脱糖为: add(double(10), 5)
|
||||
// 去糖为: add(double(10), 5)
|
||||
// 注意: RHS 必须是函数调用形式(带括号),不支持 `10 |> double`(与 Elixir 不同)
|
||||
// 管道优先级低于所有运算符: a + b |> f() → f(a + b)
|
||||
```
|
||||
|
||||
**guard** — 提前返回的语法糖:
|
||||
**guard** — 提前返回,保留 `else` 关键字 + 花括号:
|
||||
```rust
|
||||
guard x >= 0 else { return -1; }
|
||||
// 脱糖为: if !(x >= 0) { return -1; }
|
||||
// 去糖为: if !(x >= 0) { return -1; }
|
||||
// guard 块内只允许 return/continue/break 等跳转语句
|
||||
// else 块必须用花括号,即使只有一条语句
|
||||
```
|
||||
|
||||
**字符串插值**:
|
||||
**字符串插值** — 用 `\(expr)` 而非 `{expr}`,避免与块语法冲突:
|
||||
```rust
|
||||
let msg = "Hello, {name}! Age: {age}";
|
||||
// 脱糖为: "Hello, " + name + "! Age: " + age
|
||||
let name = "World";
|
||||
let msg = "Hello, \(name)!";
|
||||
// 去糖为: "Hello, " + name + "!"
|
||||
// 转义: 字面量 \( 写作 \\\(,字面量 \ 写作 \\
|
||||
// 与 Swift 的区别: Swift 用 \(),L 也采用此方案因为与 C 转义字符 \n \t 视觉一致
|
||||
```
|
||||
|
||||
**命名参数** — 调用点可选标注:
|
||||
**命名参数** — `name: value` 与类型标注 `name: Type` 视觉统一:
|
||||
```rust
|
||||
fn drawRect(width: i64, height: i64) { ... }
|
||||
drawRect(width: 10, height: 20); // 命名
|
||||
drawRect(10, 20); // 位置
|
||||
fn draw_rect(width: i64, height: i64) -> void { ... }
|
||||
|
||||
draw_rect(width: 10, height: 20); // 命名调用
|
||||
draw_rect(10, 20); // 位置调用(等价)
|
||||
// 命名参数与位置参数可混用: draw_rect(10, height: 20)
|
||||
// 命名参数必须放在位置参数之后
|
||||
// 与 Swift 的区别: 不需要外部/内部参数名区分,参数名即标签名
|
||||
```
|
||||
|
||||
## 构建命令
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<img src="https://img.shields.io/badge/C-17-555555" alt="C">
|
||||
<img src="https://img.shields.io/badge/LLVM-22.1.7-4B8BBE" alt="LLVM">
|
||||
<img src="https://img.shields.io/badge/GCC-15.x-darkgreen" alt="GCC">
|
||||
<img src="https://img.shields.io/badge/tests-145%20passed-brightgreen" alt="tests">
|
||||
<img src="https://img.shields.io/badge/tests-158%20passed-brightgreen" alt="tests">
|
||||
<img src="https://img.shields.io/badge/license-MIT-green" alt="license">
|
||||
</p>
|
||||
|
||||
@@ -156,11 +156,11 @@ mingw32-make -j4
|
||||
# 构建
|
||||
cd build && mingw32-make -j4
|
||||
|
||||
# 运行全部测试 (145 单元 + 23 集成)
|
||||
# 运行全部测试 (158 单元 + 24 集成)
|
||||
./l_lang_lexer_test.exe # 词法分析 (41 tests)
|
||||
./l_lang_test.exe # 语法分析 (15 tests)
|
||||
./l_lang_sema_test.exe # 语义分析 (65 tests)
|
||||
./l_lang_codegen_test.exe # 代码生成 (24 tests)
|
||||
./l_lang_sema_test.exe # 语义分析 (74 tests)
|
||||
./l_lang_codegen_test.exe # 代码生成 (28 tests)
|
||||
|
||||
# 集成测试
|
||||
for f in ../test/programs/*.l; do
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# L Language 语言参考手册
|
||||
|
||||
> 版本 v0.5 | 145 测试全部通过 | LLVM 后端编译型语言
|
||||
> 版本 v0.5 | 158 测试全部通过 | LLVM 后端编译型语言
|
||||
|
||||
---
|
||||
|
||||
@@ -350,13 +350,15 @@ let is_red = (c == Color::Red); // false
|
||||
### 声明类型
|
||||
|
||||
```rust
|
||||
let arr: [i64; 5] = arr; // 未初始化的 5 个 i64 数组
|
||||
let arr: [i64; 5] = arr; // 声明未初始化的 5 个 i64 数组
|
||||
```
|
||||
|
||||
数组类型语法: `[元素类型; 大小]`
|
||||
|
||||
- `= arr` 是固定写法,表示声明未初始化的数组(`arr` 是变量名自身)
|
||||
- 大小必须是整数常量
|
||||
- 元素类型支持: `i64`, `f64`, `bool`
|
||||
- 数组元素默认值为零值(`0` / `0.0` / `false`)
|
||||
|
||||
### 索引
|
||||
|
||||
|
||||
+10
-1
@@ -719,12 +719,21 @@ static void analyze_node(AstNode* node, Scope* scope, ErrorList* errors, Arena*
|
||||
error_add(errors, "<sema>", node->loc.line, node->loc.col,
|
||||
"数组索引必须是 i64 类型, 得到 '%s'", type_name(idx->type.kind));
|
||||
}
|
||||
TypeKind elem_kind = sym->type == TYPE_ARRAY ? TYPE_I64 : sym->type;
|
||||
TypeKind elem_kind = (sym->type == TYPE_ARRAY) ? sym->array_element_type : sym->type;
|
||||
if (val->type.kind != TYPE_ERROR && val->type.kind != elem_kind) {
|
||||
error_add(errors, "<sema>", node->loc.line, node->loc.col,
|
||||
"数组元素类型不匹配: 期望 '%s',得到 '%s'",
|
||||
type_name(elem_kind), type_name(val->type.kind));
|
||||
}
|
||||
// struct 元素类型时还需检查结构体名是否匹配
|
||||
if (val->type.kind != TYPE_ERROR && elem_kind == TYPE_STRUCT
|
||||
&& sym->array_element_struct_name && val->type.struct_name) {
|
||||
if (strcmp(sym->array_element_struct_name, val->type.struct_name) != 0) {
|
||||
error_add(errors, "<sema>", node->loc.line, node->loc.col,
|
||||
"数组元素类型不匹配: 期望 '%s',得到 '%s'",
|
||||
sym->array_element_struct_name, val->type.struct_name);
|
||||
}
|
||||
}
|
||||
node->type.kind = TYPE_VOID;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
struct Point { x: i64, y: i64 }
|
||||
|
||||
fn main() -> i64 {
|
||||
let arr: [Point; 2] = arr;
|
||||
arr[0] = Point { x: 10, y: 20 };
|
||||
arr[1] = Point { x: 30, y: 40 };
|
||||
print_i64(arr[0].x);
|
||||
print_i64(arr[0].y);
|
||||
print_i64(arr[1].x);
|
||||
print_i64(arr[1].y);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "test_utils.h"
|
||||
#include "codegen.h"
|
||||
#include "sema.h"
|
||||
#include "ast.h"
|
||||
#include "arena.h"
|
||||
#include <llvm-c/Analysis.h>
|
||||
@@ -418,6 +419,97 @@ void test_codegen_method_call() {
|
||||
arena_destroy(&a);
|
||||
}
|
||||
|
||||
/* === match 代码生成测试 === */
|
||||
|
||||
void test_codegen_match() {
|
||||
Arena a = arena_create(1);
|
||||
|
||||
/* enum Color { Red, Green, Blue } */
|
||||
const char* variants[] = {"Red", "Green", "Blue"};
|
||||
AstNode* enum_decl = ast_make_enum_decl(&a, "Color", variants, 3, loc_at(1, 1));
|
||||
AstNode* enums[] = { enum_decl };
|
||||
|
||||
/* fn main() -> i64 {
|
||||
let c = Color::Green;
|
||||
match c {
|
||||
Color::Red => { return 1; }
|
||||
Color::Green => { return 2; }
|
||||
_ => { return 0; }
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
但 match 在 parser 去糖, 这里直接构造去糖后的 AST:
|
||||
{ let __match_val = Color::Green;
|
||||
if __match_val == Color::Red { return 1; }
|
||||
else if __match_val == Color::Green { return 2; }
|
||||
else { return 0; }
|
||||
}
|
||||
*/
|
||||
AstNode* match_val = ast_make_enum_variant(&a, "Color", "Green", loc_at(1, 1));
|
||||
match_val->type.kind = TYPE_ENUM;
|
||||
|
||||
AstNode* let_stmt = ast_make_let(&a, "__match_val", TYPE_UNKNOWN, false, false,
|
||||
match_val, NULL, 0, NULL, 0, loc_at(1, 1));
|
||||
let_stmt->type.kind = TYPE_ENUM;
|
||||
|
||||
AstNode* match_ident = ast_make_ident(&a, "__match_val", loc_at(1, 1));
|
||||
match_ident->type.kind = TYPE_ENUM;
|
||||
|
||||
// if __match_val == Color::Red { return 1; }
|
||||
AstNode* red_variant = ast_make_enum_variant(&a, "Color", "Red", loc_at(1, 1));
|
||||
red_variant->type.kind = TYPE_ENUM;
|
||||
AstNode* red_cond = ast_make_binary(&a, OP_EQ, match_ident, red_variant, loc_at(1, 1));
|
||||
red_cond->type.kind = TYPE_BOOL;
|
||||
AstNode* red_ret = ast_make_return(&a, ast_make_literal_i64(&a, 1, loc_at(1, 1)), loc_at(1, 1));
|
||||
AstNode* red_stmts[] = { red_ret };
|
||||
AstNode* red_block = ast_make_block(&a, red_stmts, 1, loc_at(1, 1));
|
||||
|
||||
// if __match_val == Color::Green { return 2; }
|
||||
AstNode* match_ident2 = ast_make_ident(&a, "__match_val", loc_at(1, 1));
|
||||
match_ident2->type.kind = TYPE_ENUM;
|
||||
AstNode* green_variant = ast_make_enum_variant(&a, "Color", "Green", loc_at(1, 1));
|
||||
green_variant->type.kind = TYPE_ENUM;
|
||||
AstNode* green_cond = ast_make_binary(&a, OP_EQ, match_ident2, green_variant, loc_at(1, 1));
|
||||
green_cond->type.kind = TYPE_BOOL;
|
||||
AstNode* green_ret = ast_make_return(&a, ast_make_literal_i64(&a, 2, loc_at(1, 1)), loc_at(1, 1));
|
||||
AstNode* green_stmts[] = { green_ret };
|
||||
AstNode* green_block = ast_make_block(&a, green_stmts, 1, loc_at(1, 1));
|
||||
|
||||
// else { return 0; }
|
||||
AstNode* else_ret = ast_make_return(&a, ast_make_literal_i64(&a, 0, loc_at(1, 1)), loc_at(1, 1));
|
||||
AstNode* else_stmts[] = { else_ret };
|
||||
AstNode* else_block = ast_make_block(&a, else_stmts, 1, loc_at(1, 1));
|
||||
|
||||
// if-else 链: if red_cond { red_block } else if green_cond { green_block } else { else_block }
|
||||
AstNode* inner_if = ast_make_if(&a, green_cond, green_block, else_block, loc_at(1, 1));
|
||||
AstNode* outer_if = ast_make_if(&a, red_cond, red_block, inner_if, loc_at(1, 1));
|
||||
|
||||
AstNode* main_stmts[] = { let_stmt, outer_if };
|
||||
AstNode* main_body = ast_make_block(&a, main_stmts, 2, loc_at(1, 1));
|
||||
AstNode* main_fn = ast_make_function(&a, "main", NULL, 0, TYPE_I64, NULL, main_body, loc_at(1, 1));
|
||||
|
||||
AstNode* fns[] = { main_fn };
|
||||
AstNode* prog = ast_make_program(&a, fns, 1, NULL, 0, NULL, 0, enums, 1, NULL, 0, loc_at(1, 1));
|
||||
|
||||
ErrorList errors; error_init(&errors);
|
||||
sema_analyze(prog, &errors, &a);
|
||||
ASSERT(errors.count == 0);
|
||||
|
||||
const char* err = NULL;
|
||||
LLVMContextRef ctx = NULL;
|
||||
LLVMModuleRef mod = codegen_module(prog, &a, "test_match", &err, &ctx);
|
||||
ASSERT(mod != NULL);
|
||||
ASSERT(err == NULL);
|
||||
|
||||
char* verify_err = NULL;
|
||||
int failed = LLVMVerifyModule(mod, LLVMReturnStatusAction, &verify_err);
|
||||
ASSERT(!failed);
|
||||
|
||||
LLVMDisposeModule(mod);
|
||||
LLVMContextDispose(ctx);
|
||||
arena_destroy(&a);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
TEST_RUN(test_codegen_simple_function);
|
||||
TEST_RUN(test_codegen_if_else);
|
||||
@@ -428,5 +520,6 @@ int main(void) {
|
||||
TEST_RUN(test_codegen_enum);
|
||||
TEST_RUN(test_codegen_array);
|
||||
TEST_RUN(test_codegen_method_call);
|
||||
TEST_RUN(test_codegen_match);
|
||||
return test_summary();
|
||||
}
|
||||
|
||||
@@ -365,6 +365,59 @@ void test_method_undefined() {
|
||||
arena_destroy(&a);
|
||||
}
|
||||
|
||||
/* === match 语义分析测试 === */
|
||||
|
||||
void test_match_enum_sema_ok() {
|
||||
Arena a = arena_create(1);
|
||||
size_t tc; ErrorInfo lex_err = {0};
|
||||
Token* toks = lex(&a,
|
||||
"enum Color { Red, Green, Blue } fn main() -> i64 { let c = Color::Green; match c { Color::Red => { return 1; } Color::Green => { return 2; } _ => { return 0; } } return 0; }",
|
||||
"test", &tc, &lex_err);
|
||||
ASSERT(toks != NULL);
|
||||
ErrorInfo parse_err = {0};
|
||||
AstNode* ast = parse(&a, toks, tc, "test", &parse_err);
|
||||
ASSERT(ast != NULL);
|
||||
|
||||
ErrorList errors; error_init(&errors);
|
||||
sema_analyze(ast, &errors, &a);
|
||||
ASSERT(errors.count == 0); // match 去糖后应通过类型检查
|
||||
arena_destroy(&a);
|
||||
}
|
||||
|
||||
void test_match_int_sema_ok() {
|
||||
Arena a = arena_create(1);
|
||||
size_t tc; ErrorInfo lex_err = {0};
|
||||
Token* toks = lex(&a,
|
||||
"fn main() -> i64 { let x: i64 = 42; match x { 1 => { return 10; } 42 => { return 20; } _ => { return 0; } } return 0; }",
|
||||
"test", &tc, &lex_err);
|
||||
ASSERT(toks != NULL);
|
||||
ErrorInfo parse_err = {0};
|
||||
AstNode* ast = parse(&a, toks, tc, "test", &parse_err);
|
||||
ASSERT(ast != NULL);
|
||||
|
||||
ErrorList errors; error_init(&errors);
|
||||
sema_analyze(ast, &errors, &a);
|
||||
ASSERT(errors.count == 0);
|
||||
arena_destroy(&a);
|
||||
}
|
||||
|
||||
void test_match_wildcard_only_sema_ok() {
|
||||
Arena a = arena_create(1);
|
||||
size_t tc; ErrorInfo lex_err = {0};
|
||||
Token* toks = lex(&a,
|
||||
"fn main() -> i64 { let x: i64 = 42; match x { _ => { return 99; } } return 0; }",
|
||||
"test", &tc, &lex_err);
|
||||
ASSERT(toks != NULL);
|
||||
ErrorInfo parse_err = {0};
|
||||
AstNode* ast = parse(&a, toks, tc, "test", &parse_err);
|
||||
ASSERT(ast != NULL);
|
||||
|
||||
ErrorList errors; error_init(&errors);
|
||||
sema_analyze(ast, &errors, &a);
|
||||
ASSERT(errors.count == 0); // 纯通配符 match 应通过
|
||||
arena_destroy(&a);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
TEST_RUN(test_type_error);
|
||||
TEST_RUN(test_undefined_var);
|
||||
@@ -387,5 +440,8 @@ int main(void) {
|
||||
TEST_RUN(test_array_assign_ok);
|
||||
TEST_RUN(test_method_call_ok);
|
||||
TEST_RUN(test_method_undefined);
|
||||
TEST_RUN(test_match_enum_sema_ok);
|
||||
TEST_RUN(test_match_int_sema_ok);
|
||||
TEST_RUN(test_match_wildcard_only_sema_ok);
|
||||
return test_summary();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user