奇语语言核心入门:从你好世界到异步协程

奇语(Qi)是一门 100% 中文关键字、编译到原生机器码的语言。这篇教程用一个真实编译运行过的示例文件,串起包、变量、函数、控制流、自定义类型、标准库,最后落到 未来<整数> + 等待 的 M:N 协程异步——读完即可写出能跑的奇语程序。

Qi 语言核心概念

奇语是什么,为什么用中文关键字

奇语(Qi)是一门编译型语言:源码经词法分析、LALRPOP 生成的语法分析、AST、再到 LLVM IR,最终用 clang 链接成原生可执行文件。它没有解释器,跑起来就是机器码。

它最显眼的特点是全中文关键字:函数、变量、如果、否则、当、对于、返回。这不是噱头——对中文母语者,把控制流读成自然语言能显著降低初学者的认知负担,也让代码审查时「意图」一目了然。标点上中英文都接受:()【】;, 和 (){};, 等价。

本文所有代码都来自仓库里的一个示例文件,并用 qi 编译器真实跑过;文中的终端输出是真实输出,不是手写的。

提示 奇语有一批保留字不能当标识符:结果、类型、选项、通道、数组、任务、返回、等待 等。中文母语者很自然会写 变量 结果 = ... 或 变量 任务 = ...——这些会触发解析错误。改个名即可,比如 结果值、翻倍任务、数列。

安装与第一个程序

每个奇语程序都需要一个包声明和一个名为 入口 的函数(等价于其他语言的 main)。注释用 //,和 C/Rust 一样。

你好世界.qi
包 主程序;

函数 入口() {
    打印行("你好,Qi!");
}

用 qi run 一步编译并运行:

终端
$ qi run 你好世界.qi
你好,Qi!

打印行 输出后换行;打印 则不换行。要只做语法检查不生成可执行文件,用 qi check 文件.qi。

变量与基本类型

用 变量 声明可变变量,常量 声明不可变常量。类型可以省略让编译器推断,也可以用 名字: 类型 显式标注。

教程_语言核心.qi(节选)
变量 名字 = "奇语";                  // 类型推断:字符串
变量 年龄: 整数 = 1;                  // 整数(底层 i64)
变量 圆周率: 浮点数 = 3.14159;        // 浮点数(f64)
变量 启用: 布尔 = 真;                 // 布尔:真 / 假
打印行("语言: ", 名字, "  年龄: ", 年龄);

常用基本类型:整数(默认 64 位)、浮点数(64 位)、字符串(UTF-8)、布尔(真/假)。需要更窄的整数可用 短整数、字节。打印行 可以接收多个参数,会依次拼接输出。

提示 整数 映射的是 64 位的 i64,不是 32 位。浮点数打印会按 C 的 %f 风格输出(例如 3.14159 会显示成 3.141588 这样的近似),需要精确格式时自己控制位数。

函数与返回

函数用 函数 声明,参数写 名字: 类型,返回类型写在参数表后的冒号之后,用 返回 返回值。无返回值的函数省略返回类型即可。

教程_语言核心.qi(节选)
函数 加法(甲: 整数, 乙: 整数): 整数 {
    返回 甲 + 乙;
}

// 在 入口() 里调用:
打印行("3 + 4 = ", 加法(3, 4));
终端
3 + 4 =  7

控制流

三种主力结构:条件分支 如果 / 否则、while 风格的 当、以及遍历数组的 对于 … 在 …。注意条件不需要括号。

教程_语言核心.qi(节选)
// 如果 / 否则
如果 启用 == 真 {
    打印行("圆周率约等于 ", 圆周率);
} 否则 {
    打印行("未启用");
}

// 对于:遍历数组字面量
对于 i 在 [1, 2, 3] {
    打印行("循环第 ", i, " 次");
}

// 当:while 循环
变量 计数 = 0;
当 计数 < 3 {
    计数 = 计数 + 1;
}
打印行("计数累加到 ", 计数);
终端
圆周率约等于  3.141588
循环第  1  次
循环第  2  次
循环第  3  次
计数累加到  3
提示 对于 遍历的是数组字面量 […],目前不支持 1..4 这样的区间语法。需要无限循环用 循环 { … 跳出; },跳过本次迭代用 继续。

自定义类型与方法

用 类型 定义结构体,字段写成「类型在前、字段名在后」并以分号分隔。方法采用 Go 风格:接收者写在函数名前面的括号里,用 自身 访问字段。

教程_语言核心.qi(节选)
类型 矩形 {
    浮点数 宽;
    浮点数 高;
}

// 方法:接收者写在函数名前的括号里
函数 (自身 矩形) 面积(): 浮点数 {
    返回 自身.宽 * 自身.高;
}

// 在 入口() 里:用括号包裹的结构体字面量实例化
变量 矩 = (矩形 { 宽: 3.0, 高: 4.0 });
变量 面: 浮点数 = 矩.面积();   // 先存入有类型的变量再打印
打印行("矩形面积 = ", 面);
终端
矩形面积 =  12.000000
提示 上面特意把方法的浮点返回值先赋给一个显式标注 浮点数 的变量,再打印。如果直接写 打印行("面积 = ", 矩.面积()),当前编译器会在 IR 生成阶段丢失返回值类型、误用整数格式而报错。把中间结果落到带类型的变量上是稳妥写法。

字符串与标准库速览

字符串用 + 拼接。标准库通过 导入 标准库.X 引入,函数用 . 访问(也支持 :: 静态方法形式)。文本处理在 标准库.文本 里(同时也注册为 标准库.字符串)。

教程_语言核心.qi(节选)
导入 标准库.文本;

// 在 入口() 里:
变量 问候 = "你好," + 名字 + "!";
打印行(问候);
打印行("是否包含「奇」: ", 文本.包含(问候, "奇"));   // 1 = 是

变量 数列 = [10, 20, 30];
打印行("数组首项 = ", 数列[0]);
终端
你好,奇语!
是否包含「奇」:  1
数组首项 =  10

标准库还提供大量模块:标准库.输入输出(文件读写、控制台)、标准库.JSON、标准库.HTTP、标准库.网络(TCP/UDP)、标准库.时间、标准库.加密、标准库.随机、标准库.正则 等。许多 FFI 函数有自己的返回约定(文本函数失败返回空串、布尔型多数 1=成功),用前留意。

异步:未来<T> 与 等待

奇语内置 M:N 协程调度(多个协程跑在少量 OS 线程上,配合工作窃取调度器)。表层 API 很简洁:一个返回 未来<整数> 的函数,返回 的值会被自动包装成 Future;用 等待 取出结果。

教程_语言核心.qi(节选)
// 返回 未来<整数> 的函数,返回值自动包装为 Future
函数 异步翻倍(数值: 整数): 未来<整数> {
    返回 数值 * 2;
}

// 在 入口() 里:
变量 翻倍任务: 未来<整数> = 异步翻倍(21);
变量 结果值: 整数 = 等待 翻倍任务;          // 等待协程完成并取值
打印行("异步翻倍(21) = ", 结果值);

变量 就绪: 未来<整数> = 未来::就绪(7);   // 静态方法构造已就绪的 Future
变量 就绪值: 整数 = 等待 就绪;
打印行("就绪值 = ", 就绪值);
终端
异步翻倍(21) =  42
就绪值 =  7
提示 未来、等待、任务 都是保留字。给变量起名要避开它们——上面用了 翻倍任务 而不是 任务,用了 结果值 而不是 结果。未来::就绪(x) 是构造一个立即就绪 Future 的静态方法,适合把已有值塞进异步流程。

把它跑起来

上面所有片段都来自同一个示例文件 教程_语言核心.qi。完整文件一条命令即可运行:

终端
$ qi run 教程_语言核心.qi
===== 奇语语言核心 =====
语言:  奇语   年龄:  1
圆周率约等于  3.141588
3 + 4 =  7
循环第  1  次
循环第  2  次
循环第  3  次
计数累加到  3
矩形面积 =  12.000000
你好,奇语!
是否包含「奇」:  1
数组首项 =  10
异步翻倍(21) =  42
就绪值 =  7
===== 全部完成 =====

下一步

掌握了语言核心,就可以去用奇语写的三个框架做点真东西:

更系统的语法与标准库参考见文档。动手把本文的示例改一改、跑一跑,是最快的入门方式。