# 扩展运行机制

## 功能

扩展机制把“脚本能看到的 API”和“扩展内部如何执行”分开。脚本只通过 `bindings.json` 里声明的入口、对象和方法调用扩展；BT 宿主负责加载 `.bts` 包、校验描述文件、建立注册表，并把调用分发给对应 Runner。

## 语法

扩展入口调用语法和普通函数一致：

```bt
object = calc(1)
value = object.add(2).value()

// 输出：3
print value
```

`calc` 来自 `bindings.json` 的公开入口声明，`add` 和 `value` 来自对象方法声明。

## 参数

扩展调用的参数由 bindings 决定。宿主会按 bindings 的参数数量、类型和 role 处理脚本传入的值，再把调用交给对应 Runner。

## 返回值

扩展调用可以返回原始值，也可以返回扩展对象句柄。对象句柄只表示“某个扩展模块中的某个对象”，真实状态由扩展后端保存。

## 加载流程

项目启动时，BT 会按以下流程处理扩展：

1. 查找项目根目录下的 `extensions/`。
2. 按文件名顺序读取目录中的 `.bts` 文件。
3. 校验 `.bts` 后缀、zip 条目路径、条目数量、单文件大小和总解压大小。
4. 读取并校验 `manifest.json`。
5. 读取并校验 `bindings.json`。
6. 根据 `manifest.kind` 初始化纯 BT Runner 或 WASM Runner。
7. 建立扩展注册表，检查公开入口名是否冲突。
8. 把公开入口注入到当前 VM 的用户全局环境。

加载失败时，项目启动会失败并输出中文错误；BT 不会加载半成功的扩展包。

## 调用流程

脚本调用扩展入口时：

宿主会按入口名 `calc` 找到 bindings 中的函数声明，检查参数数量和类型，再把调用交给扩展所属 Runner。返回值如果是普通值，就直接回到脚本；如果是扩展对象，脚本拿到的是一个受宿主管理的对象句柄。

脚本调用对象方法时，宿主会先根据对象句柄找到扩展模块和对象类型，再查找方法名。WASM 方法调用时，接收者对象句柄会作为第一个参数传给 WASM；纯 BT 方法调用时，宿主会在内部 VM 中把方法调用到对应对象上。

## kind 和 abi

`kind` 是后端类型，决定入口文件由谁执行。`abi` 是调用协议版本，决定宿主和后端如何交换参数、返回值和对象句柄。

| kind | abi | Runner |
| ------ | ------ | ------ |
| `bt` | `bts-bt-1` | 纯 BT Runner，复用 BT Parser、Compiler 和 VM。 |
| `wasm` | `bts-wasi-1` | WASM Runner，使用 WASI P1 和 BtValueBinary。 |

这两个字段不能随意组合。`kind=bt` 必须使用 `bts-bt-1`，`kind=wasm` 必须使用 `bts-wasi-1`。

## 代码示例

同一个 `bindings.json` 风格可以对应不同后端。脚本侧调用保持一致：

```bt
value = calc(4).add(6).value()

// 输出：10
print value
```

差别只在扩展包内部：纯 BT 扩展由 `src/lib.bt` 实现；WASM 扩展由 `module.wasm` 实现。

## 注意事项

- 公开入口会成为全局常量，脚本不能重新赋值。
- `env('calc')` 可以读取扩展入口，`has_env('calc')` 返回 `true`。
- `envs('calc')` 和 `has_envs('calc')` 只表示系统函数和系统常量，不会把扩展入口当作系统能力。
- 扩展入口名不能和 BT 系统环境名称冲突，也不能和其他扩展入口重复。
- 扩展对象不应跨越不支持对象句柄的边界，例如 `task()` 快照。
