在写第一行代码之前,先搞清楚 xlang 是什么、从哪里来、装好了长什么样。
每一门语言的第一课都不该急着讲语法。工具没装好、命令认不全,后面每一步都会磕磕绊绊。本章解决三件事:
xl.exe,弄清发行包里每个文件的角色;run 立即执行,再用 build 编译出原生 exe,最后认识编译器的九个命令。本章是全书的"地基验收"。学完你应该能独立完成:安装、验证、运行、编译、看懂最基本的报错。从第 2 章开始,我们才正式进入语法。
书中所有命令与代码都使用写作时的当前版本 xlang DEMO6 RC0(发布候选)实际执行验证过;所有报错文本均为编译器真实输出(仅对过长的路径做了缩短)。命令行示例以 Windows 为准,Linux/macOS 下路径写法略有差异,命令本身完全一致。
xlang 是一门编译到 C 的现代化编程语言。你写的 .xl 源文件,会被编译器翻译成 C 代码,再由 C 编译器变成机器码。这条路线带来一个直接的好处:凡是 C 能到达的平台——Windows、Linux、Android、macOS、iOS、鸿蒙,x86、ARM、RISC-V、LoongArch——xlang 都能到达。
但 xlang 并不要求你先装一个 C 编译器。编译器内部内嵌了 TCC(Tiny C Compiler):生成的 C 代码直接交给内嵌 TCC 在内存中编译执行,整个过程不落地任何临时文件。这就是 xl run 能"像脚本一样"秒跑 xlang 程序的原因。
由此,xlang 程序有两种对等的部署形态:
xl build 产出独立的 exe,拷到没有安装 xlang 的机器上也能直接运行,性能对标手写 C;xl.dll / xlc.dll 提供的 C API,任何 C/C++ 程序都能加载并执行 xlang 脚本,把 xlang 当作嵌入式脚本层使用。一套代码,两种形态——这是 xlang 区别于绝大多数语言的根本定位。
本书基于 xlang DEMO6 RC0,即 DEMO6 的发布候选版本。DEMO6 是 xlang 准备正式发布的第一个版本:四阶段编译管线(词法 → 语法 → 语义与 IR → C 代码生成与 TCC 后端)已经完整,配套 xl.exe 命令行工具、xl.dll 嵌入式运行库和 515 个标准库函数。RC0 意味着功能已经冻结、进入发布守门阶段,书中内容可以放心作为稳定口径学习。
拿到 xlang 有两条路,按你的目的二选一。
从下载页获取 xlang_0.6.0_Preview_windows_x64.zip,解压后进入目录即可使用,无需安装:
这条命令正常退出(退出码 0)就说明工具链完好。examples\hello.xl 是发行包自带的第一个范例——它只做了一次加法运算、没有输出,所以"什么也没打印"恰恰是正确的。
构建完成后,可执行文件位于 x-lang\demo6\release\xl.exe。源码树还附带回归测试 test.bat,想确认构建质量可以跑一遍。
两条路得到的 xl.exe 完全等价。本书后续示例统一写成 xl(Windows 下即 xl.exe),并假设当前目录就是发行包根目录(xlang_new 或 release)。
解压(或构建)后的目录里,你最先会看到这几样东西:
| 文件 / 目录 | 角色 |
|---|---|
xl.exe | 命令行编译器 / 运行器,约 37 KB。它只是一个"薄壳":负责解析命令行参数,真正的编译能力全部由 xlc.dll 提供。 |
xlc.dll | 编译版动态库,约 6.4 MB。词法、语法、语义、代码生成、内嵌 TCC 与完整编译资源都在这里,xl.exe 的每个命令都映射到它的 API。 |
xl.dll | 轻量运行版动态库,约 4.1 MB。供 C/C++ 宿主嵌入 xlang 脚本使用,只携带脚本运行所需的最小资源,共 76 个公开 C API。 |
examples\ | 153 个官方范例,按语言特性、标准库、教学顺序分类,是学习写法的最佳参照。 |
extlibs\ | 扩展库(package/import)发布资产,第 35 章会专门讲解。 |
include\ | 对外 C API 头文件(xl.h、xlc.h),嵌入宿主开发时使用,第 38 章详解。 |
现在只需要记住一个结论:日常写代码,你只跟 xl.exe 打交道;其余文件是它在背后调用的能力和资源。
按惯例,第一个程序向世界问好。新建一个文本文件,命名为 hello.xl,内容只有一行:
print("Hello, xlang!")
print 是标准库的全局函数,无需 import 即可使用;双引号包起来的是字符串字面量。保存后在发行包根目录执行:
恭喜,第一个 xlang 程序已经跑起来了。注意你没有经历"编译 → 链接 → 运行"三步:run 一条命令完成全部工作,源码改完立刻重跑,这正是脚本式开发体验。
func main,程序会先执行顶层语句,再调用 main。程序结构是第 2 章的主题。脚本式运行适合开发,交付给别人用时则需要独立可执行文件。把 run 换成 build:
-o hello.exe 指定输出文件名;省略 -o 时,默认用源文件名生成(hello.xl → hello.exe)。这个 exe 是自包含的原生程序:笔者实测约 493 KB,里面静态携带了程序所需的 XRT 运行时与标准库实现,拷到任何一台没装 xlang、没装任何运行时的 Windows 机器上都能直接运行。
至此你已经体验完 xlang 的第一种部署形态。第二种形态(嵌入 C 宿主)需要 C 语言基础,安排在第 38 章。
xl 实际上是一个"编译器瑞士军刀"。不带参数运行它,会打印完整用法——我们实测的输出(命令行部分)如下:
九个命令各司其职:
| 命令 | 一句话职责 |
|---|---|
lex | 词法分析:把源码切成 token 序列。 |
parse | 语法分析:把 token 组装成 AST 语法树。 |
check | 只做语义检查,不生成代码,通过时输出 ok。 |
ir | 输出编译器中间表示 IR。 |
cgen | 生成 C 代码(可同时生成供 C 调用的头文件)。 |
abi | 输出 C ABI 信息:导出符号表、extern 声明等。 |
run | 内存编译并立即运行程序(脚本式)。 |
build | 构建原生 exe 或共享库(交付式)。 |
impdef | 从 DLL 生成导入定义文件(.def),C 链接时使用。 |
前六个命令恰好对应编译管线的各个阶段——这意味着你可以随时"拆开"编译器,看看它在想什么。我们拿 hello.xl 实际演示两个。
每一行是一个 token:行号:列号、token 类型、原始文本。编译器第一眼看到的不是"打印一句话",而是 IDENT(标识符 print)、LPAREN(左括号)、STRING(字符串字面量)……直到 EOF(文件结束)。报错信息里的"1:7"这样的坐标,指的就是这个 token 坐标系。
打开生成的 hello_gen.c,开头是编译器签名与运行时声明:
找到我们那一行 print 对应的部分(节选):
可以看到:print 被翻译成对 XRT 运行时函数 xl6_xrt_print 的普通 C 调用,字符串被包装成 xlang 的统一值类型 xvalue。文件尾部则是标准的 main 函数,它初始化运行环境后调用 xlang_main 入口。"xlang 编译到 C"不是一句口号,而是随时可以打开检查的客观事实。
其余几个命令本书会在对应章节用到:check(第 39 章错误诊断)、ir/abi(第 33、40 章)、impdef(第 37 章)。现在混个脸熟即可。
两个命令前面都用过,现在把本质讲透:
xl run:编译器生成 C 代码后,挂载到动态 VFS(虚拟文件系统),调用内嵌 TCC 在内存中完成编译,然后直接调用程序入口 xlang_main(xvalue arg)。全程不产生 app.exe、不启动子进程、零临时文件。改一行源码重跑一次,反馈以毫秒计——这是为开发期设计的路径。xl build:同样生成 C 代码、同样走 TCC 后端,但最终把机器码链接成磁盘上的原生 exe(或加 -shared 生成共享库)。产物脱离编译器独立存在——这是为交付期设计的路径。两者共享同一套前端(词法/语法/语义)和同一套 TCC 文件解析规则,所以同一份源码在 run 和 build 下的行为完全一致,不存在"调试时好好的、编译后变了"的问题。日常开发用 run 快速迭代,定型交付用 build——记住这个节奏就够了。
以下三则拾遗帮你把本章的"为什么"补全。第一遍阅读可以跳过,不影响后续章节。
因为它几乎不含任何编译能力。xl.exe 的全部职责是解析命令行参数,然后把 lex、run、build 这些命令映射到 xlc.dll 的 C API 上。词法器、语法器、语义检查、C 代码生成、内嵌 TCC、编译资源包,全部住在约 6.4 MB 的 xlc.dll 里。这种"薄壳 + 能力库"分层有一个重要红利:你的 C/C++ 程序可以直接链接 xlc.dll,获得与命令行完全一致的编译能力——命令行工具本身只是这套 API 的一个最小宿主(详见第 38 章)。
两个设计叠加的结果。其一,xl.dll 与 xlc.dll 都静态内嵌 libtcc,不搜索、不复制、不依赖外部 libtcc.dll,更不需要系统里有 GCC。其二,TCC 编译时需要的头文件、启动库等资源,被打包成一组 LZMA 压缩的 VFS 资源包(如 tcc_include.lzma、xrt_header.lzma)直接编入二进制;运行期只链接一个 LZMA 解码器负责解包。编译时 TCC 按"动态 VFS → 外部文件系统 → 静态 VFS"的顺序查找文件,内置资源永远兜底。所以整个发行包解压即用,零外部依赖。
因为它是自包含(self-contained)的:xlang 程序依赖的 XRT 运行时与标准库实现被静态链接进了 exe,而不是依赖目标机器上预装的 DLL。代价是几百 KB 的体积,换来的是"拷贝即运行"——对命令行工具和桌面小程序来说,这几乎总是划算的交换。
以下是初学者在本章最容易踩的四个坑。报错文本均为真实输出。
原因:相对路径以当前工作目录为基准解析。如果你不在 xlang_new(或 release)目录下,examples\hello.xl 指向的位置根本没有文件。正确做法:先 cd 到发行包根目录再运行,或使用完整路径。这也是拿到任何新工具后第一条命令报错时最该检查的事。
原因:xl 的第一个参数必须是九个子命令之一,它不会猜测你想运行还是编译。正确做法:运行写 xl run hello.xl,编译写 xl build hello.xl。忘了有哪些命令时不带参数运行 xl,随时回看用法。
原因:报错格式是"文件:行:列: [阶段] 说明"。1:14 表示第 1 行第 14 列——正是该行结尾,编译器在这里期待右括号却遇到了换行。[parse] 说明问题出在语法分析阶段。正确做法:看到报错先读坐标,回到源码对应位置检查括号、引号是否配对;拿不准时用 xl lex 看编译器实际切出了哪些 token。
原因:xlang 的 + 不做隐式类型转换,拼接两侧必须都是 string。注意阶段标记是 [sem](语义检查)——语法没问题,是类型规则不允许。正确做法:用多参数输出让 print 家族自动处理类型——println("n =", n),实测输出 n = 42;或者显式转换(第 7 章详解)。
| 知识点 | 速查 |
|---|---|
| xlang 定位 | 编译到 C 的现代语言;内嵌 TCC 即时执行;既能编译原生 exe,也能嵌入 C 宿主。 |
| 当前版本 | DEMO6 RC0(发布候选),四阶段编译管线完整。 |
| 获取方式 | 下载 xlang_0.6.0_Preview_windows_x64.zip 解压即用;或 git clone 后 cd x-lang/demo6 执行 build.bat。 |
| 发行包三件套 | xl.exe(37 KB 薄壳)+ xlc.dll(编译能力)+ xl.dll(嵌入式运行)。 |
| 运行程序 | xl run hello.xl——内存编译,零临时文件。 |
| 编译程序 | xl build hello.xl -o hello.exe——产自包含原生 exe。 |
| CLI 九命令 | lex / parse / check / ir / cgen / abi / run / build / impdef;不带参数运行 xl 查看用法。 |
| 报错格式 | 文件:行:列: [阶段] 说明;阶段有 [parse](语法)、[sem](语义)等。 |
| print 家族 | print/println 全局可用,多参数自动处理类型;+ 拼接要求两侧都是 string。 |
xl run 执行程序时,生成的 C 代码和机器码存放在哪里?为什么说它"零临时文件"?xl.exe、xlc.dll、xl.dll 三者的分工是什么?demo.xl:3:7: [parse] 语法错误 中,3:7 和 [parse] 各表示什么?编写 me.xl,用三行输出介绍自己(姓名、来自哪里、为什么学 xlang),先 xl run me.xl 运行,再 xl build me.xl -o me.exe 编译并直接运行产物。
参考思路:三行 println 即可,例如 println("我是", "小李") 利用多参数自动拼接。重点体验 run 与 build 的输出是否完全一致。
对练习 1 的 me.xl 依次执行 xl lex me.xl 和 xl cgen me.xl --emit me_gen.c。在 lex 输出中数一数你的程序包含几个 STRING token;在生成的 C 文件里找到 xl6_xrt_print 出现了几次,和源码中的 println 调用次数对照。
参考思路:每个字符串字面量对应一个 STRING token;每次 println 调用生成一次 xl6_xrt_print 调用。这个练习帮你建立"源码 → token → C"的直观映射。
故意把练习 1 中某个 println 的右括号删掉,运行 xl run me.xl,记录报错的行:列坐标和阶段标记;再用 xl check me.xl 对比(check 只做语义检查),最后修复并确认输出 ok。
参考思路:报错坐标指向编译器"发现不对劲"的位置,通常就是缺失符号之处或其下一行。养成先读坐标再改代码的习惯,比凭肉眼通篇找错快得多。