# holang
**Repository Path**: foreverofprogrammer/holang
## Basic Information
- **Project Name**: holang
- **Description**: 用GLM5.3 Flash开发的Linux下的编译器
- **Primary Language**: 其他
- **License**: GPL-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-10-07
- **Last Updated**: 2026-10-07
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Ho —— 一门「语法可扩展」的静态类型编译型语言
Ho 是一门从零实现的小型系统级编程语言:**静态类型 + 局部类型推导**、**自动内存管理(标记-清除 GC)**、
**编译到 x86-64 原生汇编**,并且——本项目的核心卖点——**语言自身的语法可以被用户在源码里扩展**。
编译器、运行时、示例、测试与文档全部包含在本仓库内,**不依赖任何第三方库或网络下载**,
只用系统自带的 `g++ / gcc / as / ld` 与 `python3` 即可从零构建出可执行文件。
```
$ python3 scripts/build.py # 构建编译器与运行时(约 20 秒)
$ python3 scripts/hoc.py -r examples/hello.ho
你好,Ho 语言!
Hello, Ho!
你好,世界,欢迎使用 Ho。
```
---
## 1. 一分钟看懂 Ho
```ho
// 声明式语法扩展:给语言加一条新语句
syntax unless ($cond:expr) $body:block => if (!($cond)) $body;
// 自定义运算符:右结合的幂运算
infix "**" prec 14 right (a, b) => pow(a, b);
class Shape {
string name;
Shape(string n) { name = n; }
f64 area() { return 0.0; }
}
class Circle : Shape {
f64 r;
Circle(f64 radius) : Shape("圆") { r = radius; }
f64 area() { return 3.141592653589793 * r * r; }
}
func main() -> int {
Shape* s = new Circle(2.0);
println(s->name, " 面积 = ", s->area()); // 圆 面积 = 12.566370614359172
int score = 75;
unless (score < 60) { println("及格"); } // 使用自定义语法
println("2 ** 10 = ", 2 ** 10); // 使用自定义运算符 → 1024
return 0;
}
```
## 2. 快速开始
### 2.1 环境要求
| 组件 | 版本 | 用途 |
|---|---|---|
| g++ | 支持 C++17 | 编译 `hoc` 编译器 |
| gcc | 任意 | 编译运行时、链接可执行文件 |
| as (binutils) | 2.30+ | 汇编 `hoc` 生成的 `.s` |
| python3 | 3.6+ | 构建脚本、编译驱动、测试 |
无需 `nasm`、`clang`、`lld`,也无需安装任何 Python 包。
### 2.2 构建
```bash
python3 scripts/build.py # 构建 hoc 编译器 + build/ho_runtime.o
python3 scripts/build.py --clean # 清理后重建
```
### 2.3 编译并运行一个 Ho 程序
```bash
python3 scripts/hoc.py -r hello.ho # 编译 + 链接 + 立即运行
python3 scripts/hoc.py hello.ho # 只生成 hello.s
python3 scripts/hoc.py -c hello.ho # 生成 hello.o
python3 scripts/hoc.py -o out hello.ho # 指定输出名
python3 scripts/hoc.py --keep-asm hello.ho # 保留中间汇编
python3 scripts/hoc.py -f hello.ho # 强制重编(跳过增量判断)
```
目标可执行文件比源文件、界面文件(`.hml`/`.hss`)、编译器与运行时产物都新时,
`hoc.py` 会直接跳过编译并打印 `[跳过] 已是最新`。界面是**编译期烧进二进制**的,
所以界面文件也算依赖——改了 `.hml`/`.hss` 会触发重编,不会拿到旧界面。
需要看依赖清单时问编译器:`./hoc hello.ho --print-deps`(每行一个路径)。
也可以直接使用底层工具链(`hoc` 只负责生成汇编):
```bash
./hoc hello.ho -o hello.s
as --64 -o hello.o hello.s
gcc -no-pie -o hello hello.o build/ho_runtime.o -lm
./hello
```
### 2.4 运行测试
```bash
python3 scripts/test.py --examples -v # 运行 tests/ 与 examples/ 下全部用例
python3 scripts/test.py gc # 只跑名字含 gc 的用例
```
当前状态:**57 / 57 用例全部通过**(46 个 `tests/` 用例 + 11 个 `examples/` 示例,
其中 GC 用例以 `HO_GC_STRESS=1` 运行)。
`tests/t53_hml_baked.ho` 会真的开窗口,需要显示器:有 `DISPLAY` 直接跑,
没有就用 `xvfb-run -a` 兜住;两样都没有时该用例报「跳过」而不算失败。
## 3. 目录结构
```
ho-lang/
├── README.md 项目总览(本文件)
├── docs/ 文档(自包含单文件 HTML,可直接用浏览器打开)
│ ├── language.html 语言手册:类型、语法、语句、内建函数、内存模型
│ ├── extensions.html 语法扩展手册:macro / syntax / 自定义运算符
│ └── architecture.html 编译器架构与 GC 设计
├── doc-src/ 上述 HTML 的 Markdown 源
│ ├── language.md
│ ├── extensions.md
│ └── architecture.md
├── src/ 编译器源码(C++17,约 6100 行)
│ ├── ho_common.h/.cpp Token、运算符表、诊断、标点串切分
│ ├── ho_ast.h/.cpp 类型系统与 AST
│ ├── ho_lexer.h/.cpp 两阶段词法分析
│ ├── ho_macro.h/.cpp 宏 / 语法规则 / 自定义运算符的收集与展开
│ ├── ho_parser.h/.cpp 递归下降语法分析
│ ├── ho_sema.h/.cpp 语义分析、类型检查、方法静态绑定
│ ├── ho_codegen.h/.cpp x86-64 汇编代码生成
│ └── main.cpp 编译驱动、内建序言、import 内联
├── runtime/
│ ├── ho_runtime.c Ho 运行时:GC、字符串、动态数组、打印、断言
│ └── gui/ 界面运行时:.hml/.hss 解析、控件树、布局、事件、绘制
├── scripts/
│ ├── build.py 一键构建
│ ├── hoc.py 编译驱动(.ho → .s → .o → 可执行文件,带增量判断)
│ ├── test.py 测试套件运行器
│ └── gen_docs_html.py doc-src/*.md → docs/*.html
├── examples/ 11 个演示程序(含 ui_demo 的 .hml/.hss)
└── tests/ 46 个回归用例 + tests/lib/ 模块
```
## 4. 语言特性一览
### 类型系统
| 类别 | 类型 |
|---|---|
| 整型 | `i8 i16 i32 i64`、`u8 u16 u32 u64` |
| 浮点 | `f32 f64`(内部统一按 f64 计算) |
| 其它标量 | `bool`、`char`、`void` |
| 引用类型 | `string`、动态数组 `T[]`(GC 管理,引用语义) |
| 值类型 | 定长数组 `T[N]`、结构体 / 类(栈上就地存放,值语义) |
| 复合 | 指针 `T*`、函数指针 `Ret (*fp)(P...)`、`enum`、`alias` |
| 推导 | `let` / `auto` / `var` |
### 语言能力
- 完整的表达式与语句集:算术 / 位运算 / 比较 / 逻辑 / 三元 / 复合赋值 / `++` `--`
- 控制流:`if` `else`、`while`、`do-while`、`for`、`for (T x : 容器)`、`switch` `case` `default`、`break` `continue`
- 函数:递归、默认参数、变参(`...`,用于 `printf`)、指针出参、函数指针、lambda `(x: int) -> int => expr`
- 面向对象:`struct`(值语义)、`class`(堆分配 + 继承)、构造函数与基类初始化列表;**方法静态绑定,无虚函数**
- 模块:`import "path.ho"` 在 token 流上递归内联
- C 互操作:`extern` 声明直接调用 libc(`printf`、`strlen`、`malloc`…)
- 运行时保障:数组 / 字符串下标越界检查、`null` 解引用检查、GC
### 语法扩展(核心特性,详见 `docs/extensions.html`)
| 机制 | 写法 | 能力 |
|---|---|---|
| 具名宏 | `macro SQR(x) { ((x)*(x)) }` | token 级替换 + **卫生重命名**,不与调用处同名变量冲突 |
| 语法规则 | `syntax unless ($c:expr) $b:block => if (!($c)) $b;` | 按 token 模式给语言**增加新语句**,支持 `:ident :group :block :rest :expr` 匹配器 |
| 自定义运算符 | `infix "**" prec 14 right (a,b) => pow(a,b);` | 任意标点符号的**中缀 / 前缀 / 后缀**运算符,可指定优先级与结合性 |
这三种机制都在**词法与语法分析之间**统一展开,因此可以互相组合
(例如宏的输出可以被语法规则继续匹配,语法规则的替换体里也可以调用宏)。
## 5. 内存管理
Ho 用**引用计数**管理堆对象(string、动态数组、`new` 出来的结构体 / 类),没有 GC 停顿。
### 5.1 赋值默认共享,`x.copy()` 才独立
变量绑定的是带引用计数的**存储单元**。`b = a` 让两者共享同一单元,任何别名写入对所有别名可见;**对所有类型成立,包括 `int` / `bool` / `char` / `f64`**。函数传参同样是共享,函数内改形参会改到实参。
```ho
int a = 5;
int b = a;
b = 10;
println(a); // 10
int c = 7;
int d = c.copy(); // 值拷贝,独立空间
d = 99;
println(c); // 7
```
### 5.2 手动管理:`defer` 与 `deferm`
```ho
defer 变量名; // 立即释放:引用减一,归零则回收;此后该变量视为未定义
deferm 函数名; // 登记:函数退出时统一释放本函数全部局部变量
```
手动与自动共存,且保证幂等:
| 情形 | 行为 |
| --- | --- |
| 手动 `defer` 过 | 槽位已置空,自动释放不再处理 |
| 自动释放过 | 手动再 `defer` 是空操作(对象已不在活跃表) |
| 共享别名仍有引用 | 引用减一,内存不回收 |
| 最后一个引用释放 | 内存立即归还 |
运行时维护活跃对象表,释放非受管指针是空操作,因此不会二次回收、也不会破坏堆。
### 5.3 观察与限制
`gcLiveObjects()` / `gcLiveBytes()` / `gcTotalAlloc()` / `gcTotalFreed()` 可查看存活对象数与累计分配 / 释放量。
**限制**:循环引用会泄漏(纯引用计数不回收环),需要用户自己断开。
## 6. 工具链与运行开关
`hoc` 编译器选项:
```
-o <文件> 输出文件路径(默认与源文件同名,后缀 .s)
-S 只生成汇编(默认行为)
--dump-tokens 打印词法单元
--dump-ast 打印语法树
--dump-types 打印类型检查结果
-v, --verbose 打印各阶段统计信息
--version 显示版本
-h, --help 显示帮助
```
运行时环境变量:
```
HO_GC_THRESHOLD GC 触发阈值(字节),默认 8388608
HO_GC_STRESS=1 每次分配都触发 GC(用于验证 GC 正确性)
HO_GC_DISABLE=1 关闭自动 GC
```
## 7. 示例程序
| 文件 | 内容 |
|---|---|
| `examples/hello.ho` | 最小编译单元:字符串、UTF-8 标识符 |
| `examples/basics.ho` | 类型家族、控制流、递归、内置函数 |
| `examples/oop.ho` | 结构体 / 类 / 继承 / 方法静态绑定 |
| `examples/containers.ho` | 字符串方法、动态数组、定长数组、嵌套数组 |
| `examples/gc_demo.ho` | GC 演示:制造垃圾 → 回收 → 存活对象完好 |
| `examples/macro_demo.ho` | 宏与语法规则(含中文规则名) |
| `examples/operator_demo.ho` | 自定义中缀 / 前缀 / 后缀运算符 |
## 8. 文档
`docs/` 下是三份**自包含的单文件 HTML**,直接用浏览器打开即可阅读:带左侧目录导航、
首屏图解、代码高亮块与响应式排版,不依赖任何 CDN、外部字体或网络请求。
| 文件 | 内容 |
|---|---|
| [`docs/language.html`](docs/language.html) | 语言手册:类型、表达式、语句、内建函数、内存模型 |
| [`docs/extensions.html`](docs/extensions.html) | 语法扩展手册(宏 / 语法规则 / 自定义运算符) |
| [`docs/architecture.html`](docs/architecture.html) | 编译器架构与 GC 设计 |
Markdown 源保留在 `doc-src/`,重新生成:
```bash
python3 scripts/gen_docs_html.py # 生成 docs/ 下全部 HTML
python3 scripts/gen_docs_html.py language # 只生成指定文档
```
## 9. 自定义界面控件
内置控件不够用时,可以完全在 `.hml` 里声明一种新控件:它由几块图形拼出来,
发哪些事件、什么时候发也由声明方定;绘制是**声明式**的,写属性,不写绘制代码。
```html
```
```css
/* .hss 既能选控件本体,也能选里面的图形块 */
gauge { width: 200px; height: 12px; }
gauge .track { background: #D5DBDB; }
gauge .bar { background: #8E44AD; }
```
```ho
func onChange(int id) -> int {
println("现在 ", uiGetAttr(id, "value"));
return 1; // 1 表示要重绘
}
func main() {
uiInit();
uiLoad("gauge.hml", "gauge.hss");
int g = uiFind("g1");
uiSetAttr(g, "value", "70%"); // 值真的变了才发 valuechange
uiEmit(g, "change"); // 也可以按名字主动发一个事件
}
```
要点:
- `` 只声明类型,自己不进控件树;实例的标签就是类型名。
- 几何值可以写 `12`(像素)、`50%`(相对控件宽高)或 `{属性名}`(读实例属性)。
- `` 的 `when` 决定触发方式(`click` / `press` / `release` / `enter` / `leave` /
`move` / `valuechange` / `key`),`area` 能把触发范围限制在某个图形块里。
- 事件统一写成 `on="事件名:函数名"`,回调签名是 `func f(int id) -> int`;
内置控件也支持 `on="click:onOk"`,等价于 `onclick="onOk"`。
- 编译期会校验:事件名声明过、函数存在、参数个数对、返回类型是整型(浮点不行),
报错位置指到 `.hml` 里那个属性上。
- 事件绑定在编译期就烧成函数指针,热路径上没有名字查表。
- 界面是编译期烧进二进制的,所以 `on="名:函数"` 能在编译期绑好;若界面改成运行时
才加载(`uiLoad` 的路径不是字面量),运行时没有 Ho 函数的地址,需要用
`uiBindEvent(node, "事件名", 函数)` 自己绑。
完整语法(`` / `` / `` 全部属性、`when` 取值表、`.hss` 选择器、
Ho 侧 `uiSetAttr` / `uiGetAttr` / `uiEmit` / `uiEventNames`)见 `doc-src/hml-hss.md`。
## 动态库互操作
Ho 可以调用 `.so` / `.dll`,也可以把自己编译成动态库。
**调用外部库** —— `lib` 声明依赖,`extern ... from` 声明符号。编译器读取库的导出表做
编译期校验(符号是否存在、是函数还是变量),链接参数自动推导,源码里不用写 `-lm`:
```ho
lib "libm.so" as m;
extern f64 sqrt(f64 x) from m missing: 0.0;
func main() { println(sqrt(2.0)); }
```
`missing:` 逐处指定降级策略。库不存在、符号找不到、甚至**库内部段错误**,都按该策略
返回默认值或终止,不会让进程莫名崩掉。
**编译成动态库**:
```bash
python3 scripts/hoc.py mylib.ho --shared -o mylib.so
```
带 `extern` 声明的函数即导出接口(函数体由同名 `func` 提供)。编译器自动生成 C ABI
适配层、隐藏运行时内部符号、并在库加载时自动完成运行时初始化。外部按普通 C 函数调用:
```c
int (*fib)(int) = dlsym(h, "fib");
fib(10); // 55
```
## 文件 IO 与命令行参数
运行期内建,不需要 `lib` 声明:
```ho
func main() {
for (int i = 0; i < argc(); i = i + 1) { println(argv(i)); }
writeFile("/tmp/out.txt", "第一行\n第二行\n");
string text = readFile("/tmp/out.txt");
println("读了 ", text.length, " 字节");
removeFile("/tmp/out.txt");
}
```
`readFile` / `writeFile` / `appendFile` / `fileExists` / `fileSize` / `removeFile`,
失败一律返回可判断的值(空串 / `-1` / `false`),任何一个都不会终止进程。
`readFile` 读不到文件时返回**空串而不是空指针**,可以直接取长度、比较、打印。
字符串下标 `s[i]` 的类型是 `char`(单个字节),可与 `'\n'` 这类字符字面量比较。