第 1 章 / 共 58 章 卷一 起步

认识 xlang 与安装

在写第一行代码之前,先搞清楚 xlang 是什么、从哪里来、装好了长什么样。

本章导读

每一门语言的第一课都不该急着讲语法。工具没装好、命令认不全,后面每一步都会磕磕绊绊。本章解决三件事:

  • 建立全局认识:xlang 是一门什么样的语言,"既能编译、也能嵌入"到底意味着什么;
  • 把工具装起来:通过下载包或源码构建两条路拿到 xl.exe,弄清发行包里每个文件的角色;
  • 跑通完整闭环:写第一个程序,用 run 立即执行,再用 build 编译出原生 exe,最后认识编译器的九个命令。

本章是全书的"地基验收"。学完你应该能独立完成:安装、验证、运行、编译、看懂最基本的报错。从第 2 章开始,我们才正式进入语法。

阅读约定

书中所有命令与代码都使用写作时的当前版本 xlang DEMO6 RC0(发布候选)实际执行验证过;所有报错文本均为编译器真实输出(仅对过长的路径做了缩短)。命令行示例以 Windows 为准,Linux/macOS 下路径写法略有差异,命令本身完全一致。

1.1 xlang 是什么

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;
  • 作为脚本引擎嵌入 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 意味着功能已经冻结、进入发布守门阶段,书中内容可以放心作为稳定口径学习。

1.2 获取 xlang

拿到 xlang 有两条路,按你的目的二选一。

途径一:下载发行包(推荐初学者)

从下载页获取 xlang_0.6.0_Preview_windows_x64.zip,解压后进入目录即可使用,无需安装:

# 解压下载的 ZIP 后 > cd xlang_new # 用自带范例验证一切正常 > xl.exe run examples\hello.xl

这条命令正常退出(退出码 0)就说明工具链完好。examples\hello.xl 是发行包自带的第一个范例——它只做了一次加法运算、没有输出,所以"什么也没打印"恰恰是正确的。

途径二:从源码构建(适合参与开发)

> git clone https://gitee.com/xywhsoft/x-lang.git > cd x-lang/demo6 > build.bat # Windows 一键构建 # 构建产物输出到 release\ 目录:xl.exe、xl.dll、xlc.dll、examples\ 等

构建完成后,可执行文件位于 x-lang\demo6\release\xl.exe。源码树还附带回归测试 test.bat,想确认构建质量可以跑一遍。

两条路得到的 xl.exe 完全等价。本书后续示例统一写成 xl(Windows 下即 xl.exe),并假设当前目录就是发行包根目录(xlang_new 或 release)。

1.3 认识发行包目录

解压(或构建)后的目录里,你最先会看到这几样东西:

文件 / 目录角色
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 打交道;其余文件是它在背后调用的能力和资源。

1.4 第一个程序

按惯例,第一个程序向世界问好。新建一个文本文件,命名为 hello.xl,内容只有一行:

hello.xl
print("Hello, xlang!")

print 是标准库的全局函数,无需 import 即可使用;双引号包起来的是字符串字面量。保存后在发行包根目录执行:

> xl run hello.xl Hello, xlang!

恭喜,第一个 xlang 程序已经跑起来了。注意你没有经历"编译 → 链接 → 运行"三步:run 一条命令完成全部工作,源码改完立刻重跑,这正是脚本式开发体验。

xlang 允许在文件顶层直接书写可执行语句(不需要包一层 main 函数);如果同时定义了 func main,程序会先执行顶层语句,再调用 main。程序结构是第 2 章的主题。

1.5 编译成原生 exe

脚本式运行适合开发,交付给别人用时则需要独立可执行文件。把 run 换成 build:

> xl build hello.xl -o hello.exe > hello.exe Hello, xlang!

-o hello.exe 指定输出文件名;省略 -o 时,默认用源文件名生成(hello.xl → hello.exe)。这个 exe 是自包含的原生程序:笔者实测约 493 KB,里面静态携带了程序所需的 XRT 运行时与标准库实现,拷到任何一台没装 xlang、没装任何运行时的 Windows 机器上都能直接运行。

至此你已经体验完 xlang 的第一种部署形态。第二种形态(嵌入 C 宿主)需要 C 语言基础,安排在第 38 章。

1.6 认识 CLI 九命令

xl 实际上是一个"编译器瑞士军刀"。不带参数运行它,会打印完整用法——我们实测的输出(命令行部分)如下:

> xl Usage: xl lex <file.xl> [--format text|json] [--emit output.tokens] xl parse <file.xl> [--format tree|json] [--emit output.ast] xl check <file.xl> [--json] xl ir <file.xl> [--format text|json] [--emit output.xlir] xl cgen <file.xl> [--emit output.c] [--emit-header output.h] [--dump-dir dir] xl abi <file.xl> [--json] [--emit output.abi.txt] xl run <file.xl|file.c|file.xlp> [helper.c ...] [--save-temps] ... xl build <file.xl|file.c|file.xlp> [helper.c ...] [-o <output.exe|dll>] ... xl impdef <file.dll> -o <output.def>

九个命令各司其职:

命令一句话职责
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 实际演示两个。

用 lex 看看编译器眼里的源码

> xl lex hello.xl 1:1 IDENT print 1:6 LPAREN ( 1:7 STRING "Hello, xlang!" 1:22 RPAREN ) 1:23 SEP 1:23 EOF

每一行是一个 token:行号:列号、token 类型、原始文本。编译器第一眼看到的不是"打印一句话",而是 IDENT(标识符 print)、LPAREN(左括号)、STRING(字符串字面量)……直到 EOF(文件结束)。报错信息里的"1:7"这样的坐标,指的就是这个 token 坐标系。

用 cgen 看看生成的 C 代码

> xl cgen hello.xl --emit hello_gen.c # 生成 hello_gen.c,共 500 行

打开生成的 hello_gen.c,开头是编译器签名与运行时声明:

/* Generated by xlang DEMO6 cgen. */

找到我们那一行 print 对应的部分(节选):

xvalue __xl6_print_args_0 = xl6_xrt_array_varargs(1, xvoCreateText((void*)("Hello, xlang!"), 0, false)); if (!xl6_error_has()) { xl6_xrt_print(__xl6_print_args_0, 0); }

可以看到:print 被翻译成对 XRT 运行时函数 xl6_xrt_print 的普通 C 调用,字符串被包装成 xlang 的统一值类型 xvalue。文件尾部则是标准的 main 函数,它初始化运行环境后调用 xlang_main 入口。"xlang 编译到 C"不是一句口号,而是随时可以打开检查的客观事实。

其余几个命令本书会在对应章节用到:check(第 39 章错误诊断)、ir/abi(第 33、40 章)、impdef(第 37 章)。现在混个脸熟即可。

1.7 run 和 build 的本质区别

两个命令前面都用过,现在把本质讲透:

  • xl run:编译器生成 C 代码后,挂载到动态 VFS(虚拟文件系统),调用内嵌 TCC 在内存中完成编译,然后直接调用程序入口 xlang_main(xvalue arg)。全程不产生 app.exe、不启动子进程、零临时文件。改一行源码重跑一次,反馈以毫秒计——这是为开发期设计的路径。
  • xl build:同样生成 C 代码、同样走 TCC 后端,但最终把机器码链接成磁盘上的原生 exe(或加 -shared 生成共享库)。产物脱离编译器独立存在——这是为交付期设计的路径。

两者共享同一套前端(词法/语法/语义)和同一套 TCC 文件解析规则,所以同一份源码在 run 和 build 下的行为完全一致,不存在"调试时好好的、编译后变了"的问题。日常开发用 run 快速迭代,定型交付用 build——记住这个节奏就够了。

深入细节

以下三则拾遗帮你把本章的"为什么"补全。第一遍阅读可以跳过,不影响后续章节。

拾遗 1:xl.exe 为什么只有约 37 KB?

因为它几乎不含任何编译能力。xl.exe 的全部职责是解析命令行参数,然后把 lex、run、build 这些命令映射到 xlc.dll 的 C API 上。词法器、语法器、语义检查、C 代码生成、内嵌 TCC、编译资源包,全部住在约 6.4 MB 的 xlc.dll 里。这种"薄壳 + 能力库"分层有一个重要红利:你的 C/C++ 程序可以直接链接 xlc.dll,获得与命令行完全一致的编译能力——命令行工具本身只是这套 API 的一个最小宿主(详见第 38 章)。

拾遗 2:为什么不需要安装 GCC、Python 或任何运行时?

两个设计叠加的结果。其一,xl.dll 与 xlc.dll 都静态内嵌 libtcc,不搜索、不复制、不依赖外部 libtcc.dll,更不需要系统里有 GCC。其二,TCC 编译时需要的头文件、启动库等资源,被打包成一组 LZMA 压缩的 VFS 资源包(如 tcc_include.lzma、xrt_header.lzma)直接编入二进制;运行期只链接一个 LZMA 解码器负责解包。编译时 TCC 按"动态 VFS → 外部文件系统 → 静态 VFS"的顺序查找文件,内置资源永远兜底。所以整个发行包解压即用,零外部依赖。

拾遗 3:hello.exe 为什么有 493 KB?

因为它是自包含(self-contained)的:xlang 程序依赖的 XRT 运行时与标准库实现被静态链接进了 exe,而不是依赖目标机器上预装的 DLL。代价是几百 KB 的体积,换来的是"拷贝即运行"——对命令行工具和桌面小程序来说,这几乎总是划算的交换。

⚠️ 常见错误

以下是初学者在本章最容易踩的四个坑。报错文本均为真实输出。

坑 1:在错误的目录运行范例,提示"无法读取源文件"
> xl run examples\hello.xl # 当前目录不是发行包根目录 examples/hello.xl:1:1: [parse] 无法读取源文件

原因:相对路径以当前工作目录为基准解析。如果你不在 xlang_new(或 release)目录下,examples\hello.xl 指向的位置根本没有文件。正确做法:先 cd 到发行包根目录再运行,或使用完整路径。这也是拿到任何新工具后第一条命令报错时最该检查的事。

坑 2:忘记写子命令,直接 "xl 文件名"
> xl hello unknown command: hello

原因:xl 的第一个参数必须是九个子命令之一,它不会猜测你想运行还是编译。正确做法:运行写 xl run hello.xl,编译写 xl build hello.xl。忘了有哪些命令时不带参数运行 xl,随时回看用法。

坑 3:括号没配对,看不懂"语法错误"在哪
# hello_bad.xl 内容:print("hello" ← 少了右括号 > xl run hello_bad.xl hello_bad.xl:1:14: [parse] 语法错误

原因:报错格式是"文件:行:列: [阶段] 说明"。1:14 表示第 1 行第 14 列——正是该行结尾,编译器在这里期待右括号却遇到了换行。[parse] 说明问题出在语法分析阶段。正确做法:看到报错先读坐标,回到源码对应位置检查括号、引号是否配对;拿不准时用 xl lex 看编译器实际切出了哪些 token。

坑 4:用 + 拼接字符串和数字
# bad.xl 内容: # int n = 42 # print("n = " + n) > xl run bad.xl bad.xl:2:14: [sem] 字符串拼接两侧都必须是 string

原因: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。

复习题

✍️ 先遮住答案想一想
  1. xlang 程序有哪两种部署形态?分别用哪条命令 / 哪个组件实现?
  2. xl run 执行程序时,生成的 C 代码和机器码存放在哪里?为什么说它"零临时文件"?
  3. xl.exe、xlc.dll、xl.dll 三者的分工是什么?
  4. 报错 demo.xl:3:7: [parse] 语法错误 中,3:7 和 [parse] 各表示什么?
  5. 为什么 xlang 不需要用户预先安装 GCC 或其他 C 编译器?

编程练习

练习 1:跑通完整闭环

编写 me.xl,用三行输出介绍自己(姓名、来自哪里、为什么学 xlang),先 xl run me.xl 运行,再 xl build me.xl -o me.exe 编译并直接运行产物。

参考思路:三行 println 即可,例如 println("我是", "小李") 利用多参数自动拼接。重点体验 run 与 build 的输出是否完全一致。

练习 2:看看编译器怎么想

对练习 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"的直观映射。

练习 3:主动制造并修复错误

故意把练习 1 中某个 println 的右括号删掉,运行 xl run me.xl,记录报错的行:列坐标和阶段标记;再用 xl check me.xl 对比(check 只做语义检查),最后修复并确认输出 ok。

参考思路:报错坐标指向编译器"发现不对劲"的位置,通常就是缺失符号之处或其下一行。养成先读坐标再改代码的习惯,比凭肉眼通篇找错快得多。