Compare commits

..

5 Commits

7 changed files with 254 additions and 43 deletions
+75 -36
View File
@@ -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/复合赋值。 1. **去糖优先** — 复杂语法在 parser 层去糖为简单原语,零 sema/codegen 改动。已验证: for/match/复合赋值。
2. **博采众长** — 从多语言吸收优秀特性,而非单一模仿 Rust: 2. **博采众长L 化适配** — 从多语言吸收优秀特性,但必须适配 L 的语法风格,**不能直接照抄**。每条特性要问:这个特性的核心价值是什么?在 L 里最自然的表达方式是什么?
- **Elixir/F#** → 管道运算符 `|>`
- **Go** → defer、多返回值
- **Swift** → guard、字符串插值、命名参数
- **Kotlin** → 扩展函数、when 表达式
- **Python** → 列表推导式、装饰器
- **Zig** → comptime 常量折叠
3. **渐进演进** — 先加语法糖(parser 层),后加强类型(sema/codegen 层) 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)` | 复用类型标注位置,前缀修饰 |
### 候选特性优先级 ### 候选特性优先级
| 优先级 | 特性 | 来源 | 实现层 | 预计行数 | 独特性 | | 优先级 | 特性 | 来源 | 实现层 | 预计行数 | L 化关键 |
|--------|------|------|--------|---------|--------| |--------|------|------|--------|---------|---------|
| **P0** | 管道 `\|>` | Elixir/F# | parser | ~30 | 极高 | | **P0** | 管道 `\|>` | Elixir/F# | parser | ~30 | 保持 `()` 调用风格 |
| **P0** | guard 语句 | Swift | parser | ~15 | | | **P0** | guard 语句 | Swift | parser | ~15 | `else` + 花括号块 |
| **P0** | 字符串插值 | Swift/Python | lexer+parser | ~60 | | | **P0** | 字符串插值 | Swift | lexer+parser | ~60 | `\\(expr)` 避免 `{}` 歧义 |
| **P0** | 命名参数 | Swift/Kotlin | parser | ~50 | | | **P0** | 命名参数 | Swift | parser | ~50 | `name: val` 与类型标注统一 |
| P1 | 列表推导式 | Python | parser | ~50 | | | P1 | 列表推导式 | Python | parser | ~50 | `[for x in arr if cond: expr]`,不用 Python 后缀式 |
| P1 | 装饰器 `@` | Python | parser | ~40 | | | P1 | 装饰器 | Python | parser | ~40 | 考虑 `#[attr]` 而非裸 `@` |
| P1 | defer | Go/Zig | parser+codegen | ~60 | | | P1 | defer | Go | parser+codegen | ~60 | 关键字 + 花括号/单语句 |
| P1 | 扩展函数 | Kotlin | parser+sema | ~80 | | | P1 | 扩展函数 | Kotlin | parser+sema | ~80 | 复用 `impl` 块,不造新语法 |
| P1 | when 表达式 | Kotlin | parser | ~30 | | | P1 | match 表达式 | Kotlin | parser | ~30 | 扩展现有 match,不引入 `when` |
| P2 | 多返回值 | Go | 全流水线 | ~200 | | | P2 | 多返回值 | Go | 全流水线 | ~200 | `(T, U)` 元组语法,不用裸逗号 |
| P2 | 空安全 `?.` / `??` | Kotlin | sema+codegen | ~150 | | | P2 | 空安全 `?.` / `??` | Kotlin | sema+codegen | ~150 | 保留符号运算符(符号在此场景比关键字更清晰) |
| P2 | comptime 求值 | Zig | sema+codegen | ~200 | 极高 | | P2 | comptime 求值 | Zig | sema+codegen | ~200 | `comptime` 作为表达式前缀 |
| P3 | 隐式接口 | Go | sema+codegen | ~300 | | | P3 | 隐式接口 | Go | sema+codegen | ~300 | 暂远,与 trait 体系统一设计 |
| P3 | in/out 参数 | Carbon/Cpp2 | sema+codegen | ~150 | | | P3 | in/out 参数 | Carbon/Cpp2 | sema+codegen | ~150 | 前缀修饰符,放在 `name: Type` 之前 |
### P0 特性设计 ### P0 特性设计L 化版)
**管道 `|>`** — 数据流从左到右: **管道 `|>`** — 数据流从左到右,保持 `()` 调用:
```rust ```rust
let result = 10 |> double() |> add(5); 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 ```rust
guard x >= 0 else { return -1; } guard x >= 0 else { return -1; }
// 糖为: if !(x >= 0) { return -1; } // 糖为: if !(x >= 0) { return -1; }
// guard 块内只允许 return/continue/break 等跳转语句
// else 块必须用花括号,即使只有一条语句
``` ```
**字符串插值**: **字符串插值** — 用 `\(expr)` 而非 `{expr}`,避免与块语法冲突:
```rust ```rust
let msg = "Hello, {name}! Age: {age}"; let name = "World";
// 脱糖为: "Hello, " + name + "! Age: " + age let msg = "Hello, \(name)!";
// 去糖为: "Hello, " + name + "!"
// 转义: 字面量 \( 写作 \\\(,字面量 \ 写作 \\
// 与 Swift 的区别: Swift 用 \(),L 也采用此方案因为与 C 转义字符 \n \t 视觉一致
``` ```
**命名参数**调用点可选标注: **命名参数**`name: value` 与类型标注 `name: Type` 视觉统一:
```rust ```rust
fn drawRect(width: i64, height: i64) { ... } fn draw_rect(width: i64, height: i64) -> void { ... }
drawRect(width: 10, height: 20); // 命名
drawRect(10, 20); // 位置 draw_rect(width: 10, height: 20); // 命名调用
draw_rect(10, 20); // 位置调用(等价)
// 命名参数与位置参数可混用: draw_rect(10, height: 20)
// 命名参数必须放在位置参数之后
// 与 Swift 的区别: 不需要外部/内部参数名区分,参数名即标签名
``` ```
## 构建命令 ## 构建命令
+4 -4
View File
@@ -8,7 +8,7 @@
<img src="https://img.shields.io/badge/C-17-555555" alt="C"> <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/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/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"> <img src="https://img.shields.io/badge/license-MIT-green" alt="license">
</p> </p>
@@ -156,11 +156,11 @@ mingw32-make -j4
# 构建 # 构建
cd build && mingw32-make -j4 cd build && mingw32-make -j4
# 运行全部测试 (145 单元 + 23 集成) # 运行全部测试 (158 单元 + 24 集成)
./l_lang_lexer_test.exe # 词法分析 (41 tests) ./l_lang_lexer_test.exe # 词法分析 (41 tests)
./l_lang_test.exe # 语法分析 (15 tests) ./l_lang_test.exe # 语法分析 (15 tests)
./l_lang_sema_test.exe # 语义分析 (65 tests) ./l_lang_sema_test.exe # 语义分析 (74 tests)
./l_lang_codegen_test.exe # 代码生成 (24 tests) ./l_lang_codegen_test.exe # 代码生成 (28 tests)
# 集成测试 # 集成测试
for f in ../test/programs/*.l; do for f in ../test/programs/*.l; do
+4 -2
View File
@@ -1,6 +1,6 @@
# L Language 语言参考手册 # L Language 语言参考手册
> 版本 v0.5 | 145 测试全部通过 | LLVM 后端编译型语言 > 版本 v0.5 | 158 测试全部通过 | LLVM 后端编译型语言
--- ---
@@ -350,13 +350,15 @@ let is_red = (c == Color::Red); // false
### 声明类型 ### 声明类型
```rust ```rust
let arr: [i64; 5] = arr; // 未初始化的 5 个 i64 数组 let arr: [i64; 5] = arr; // 声明未初始化的 5 个 i64 数组
``` ```
数组类型语法: `[元素类型; 大小]` 数组类型语法: `[元素类型; 大小]`
- `= arr` 是固定写法,表示声明未初始化的数组(`arr` 是变量名自身)
- 大小必须是整数常量 - 大小必须是整数常量
- 元素类型支持: `i64`, `f64`, `bool` - 元素类型支持: `i64`, `f64`, `bool`
- 数组元素默认值为零值(`0` / `0.0` / `false`
### 索引 ### 索引
+10 -1
View File
@@ -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, error_add(errors, "<sema>", node->loc.line, node->loc.col,
"数组索引必须是 i64 类型, 得到 '%s'", type_name(idx->type.kind)); "数组索引必须是 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) { if (val->type.kind != TYPE_ERROR && val->type.kind != elem_kind) {
error_add(errors, "<sema>", node->loc.line, node->loc.col, error_add(errors, "<sema>", node->loc.line, node->loc.col,
"数组元素类型不匹配: 期望 '%s',得到 '%s'", "数组元素类型不匹配: 期望 '%s',得到 '%s'",
type_name(elem_kind), type_name(val->type.kind)); 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; node->type.kind = TYPE_VOID;
break; break;
} }
+12
View File
@@ -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;
}
+93
View File
@@ -1,5 +1,6 @@
#include "test_utils.h" #include "test_utils.h"
#include "codegen.h" #include "codegen.h"
#include "sema.h"
#include "ast.h" #include "ast.h"
#include "arena.h" #include "arena.h"
#include <llvm-c/Analysis.h> #include <llvm-c/Analysis.h>
@@ -418,6 +419,97 @@ void test_codegen_method_call() {
arena_destroy(&a); 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) { int main(void) {
TEST_RUN(test_codegen_simple_function); TEST_RUN(test_codegen_simple_function);
TEST_RUN(test_codegen_if_else); TEST_RUN(test_codegen_if_else);
@@ -428,5 +520,6 @@ int main(void) {
TEST_RUN(test_codegen_enum); TEST_RUN(test_codegen_enum);
TEST_RUN(test_codegen_array); TEST_RUN(test_codegen_array);
TEST_RUN(test_codegen_method_call); TEST_RUN(test_codegen_method_call);
TEST_RUN(test_codegen_match);
return test_summary(); return test_summary();
} }
+56
View File
@@ -365,6 +365,59 @@ void test_method_undefined() {
arena_destroy(&a); 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) { int main(void) {
TEST_RUN(test_type_error); TEST_RUN(test_type_error);
TEST_RUN(test_undefined_var); TEST_RUN(test_undefined_var);
@@ -387,5 +440,8 @@ int main(void) {
TEST_RUN(test_array_assign_ok); TEST_RUN(test_array_assign_ok);
TEST_RUN(test_method_call_ok); TEST_RUN(test_method_call_ok);
TEST_RUN(test_method_undefined); 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(); return test_summary();
} }