Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions PERFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,34 @@ rest 绑定会复制剩余元素到新的不可变数组,以保持宿主存储

## 已知性能边界

## RFC 0113/0116 数值标准库

标准库数值路径由四个负载覆盖:`stdlib-abs` 测量单值绝对值,`stdlib-sum` 测量小数组聚合,`stdlib-min-max` 测量严格最小/最大值,`stdlib-range-sum` 测量 `range` 与 `sum` 的组合。四者同时存在于 `qbench --json` 与 `cargo bench --bench core`,并检查最终值 `42`、`10`、`4`、`4950`。

复现机器可读记录:

```sh
cargo run --locked --release --bin qbench -- --json --only stdlib-sum --iterations 100 --repeat 3
```

复现完整 release 负载与持续门禁:

```sh
make qbench-check
make bench
```

这些负载只用于同一实现、同一环境的回归跟踪;报告不设跨机器硬时间阈值,比较时应记录 `rustc -Vv`、机器、操作系统、迭代次数和重复次数。

本轮 Apple arm64 release 基准样本(`cargo bench --locked --bench core`,单位 ms;仅作仓库内回归锚点):

| 工作负载 | 编译 | 验证 | 执行 |
|---|---:|---:|---:|
| stdlib-abs(20,000 次) | 23.700 | 1.428 | 32.712 |
| stdlib-sum(20,000 次) | 34.830 | 1.457 | 33.823 |
| stdlib-min-max(20,000 次) | 56.280 | 1.849 | 37.775 |
| stdlib-range-sum(10,000 次) | 17.852 | 0.896 | 30.155 |

## RFC 0076 负索引

负索引在数组上做一次长度归一化,在字符串上按 Unicode 标量计数后定位;两者均保持越界错误,不复制序列。`negative-indexing` workload(20,000 次)单次样本为:编译 75.609 ms,验证 2.750 ms,执行 33.463 ms。
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

QuickCoffee 是一台以 Rust 编写、受 CoffeeScript 启发的字节码脚本引擎。它保留紧凑、可读的表达式语法,却不兼容 JavaScript:没有原型链、`this`、`eval` 或嵌入 JavaScript。

当前实现遵循 [RFCs/0000-project-scope.md](RFCs/0000-project-scope.md) 至 [RFCs/0114-context-builder-api.md](RFCs/0114-context-builder-api.md)。
当前实现遵循 [RFCs/0000-project-scope.md](RFCs/0000-project-scope.md) 至 [RFCs/0117-qcoffee-json-output.md](RFCs/0117-qcoffee-json-output.md)。
构建要求 Rust 1.85 或更新版本(Edition 2024);CI 同时验证 MSRV 与 stable 工具链。

```coffee
Expand All @@ -26,6 +26,7 @@ cargo run -- example.qc -- first second
cargo run -- --check example.qc
cargo run -- --dump-bytecode example.qc
cargo run -- --fingerprint example.qc
cargo run -- --json -e "{answer: 42}"
cargo run --release --bin qbench -- --json --iterations 100
cargo run --release --bin qbench -- --json --iterations 100 --repeat 3
cargo run --release --bin qbench -- --list
Expand Down
2 changes: 1 addition & 1 deletion RFCs/0000-project-scope.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ QuickCoffee 是一个 Rust 实现的、受 CoffeeScript 2016 启发的脚本引

本仓库中的测试即 0.1 的语义基线。对语法或运行时的新增特性必须先以 RFC 补充定义,并至少添加:成功测试、错误测试及字节码验证测试。

当前已实现的后续语义与工具 RFC 延伸至 RFC 0114;其中 RFC 0077 定义 JSON 输出、RFC 0079 定义 TAP 输出、RFC 0080 定义 CLI 字节码指纹、RFC 0081 定义可机器读取的基准输出、RFC 0082 规范化指纹编码、RFC 0083 定义 Markdown 文学编程产物、RFC 0084 定义嵌入上下文 fuel 控制、RFC 0085 定义可执行 Rust 嵌入示例、RFC 0086 定义 crate 发布元数据、RFC 0094 定义 qdocco 最终值门禁、RFC 0095 定义字符串步进迭代、RFC 0096 定义其性能基准、RFC 0097 定义 `do` 参数转发、RFC 0098 定义 RFC 索引门禁、RFC 0099 定义 `!` 否定别名、RFC 0100 定义有符号 `by` 步长、RFC 0101 定义 qdocco 原子输出、RFC 0102 定义 qtest 规范文件去重、RFC 0103 定义 qbench schema 版本、RFC 0104 定义 qdocco 块注释代码保留、RFC 0105 定义 qbench 重复采样中位数、RFC 0106 定义 crate 发布包验收门禁、RFC 0107 定义 release qbench 持续门禁、RFC 0108 定义 qtest 可执行示例语料、RFC 0109 定义 qbench 核心负载全套护栏、RFC 0110 定义 Rust MSRV 契约、RFC 0111 定义 release profile 完整测试门禁、RFC 0112 定义 qbench 负载枚举与选择、RFC 0113 定义严格数值标准库函数、RFC 0114 定义链式嵌入上下文配置,均不改变脚本语言值模型的原型无关约束。
当前已实现的后续语义与工具 RFC 延伸至 RFC 0117;其中 RFC 0077 定义 JSON 输出、RFC 0079 定义 TAP 输出、RFC 0080 定义 CLI 字节码指纹、RFC 0081 定义可机器读取的基准输出、RFC 0082 规范化指纹编码、RFC 0083 定义 Markdown 文学编程产物、RFC 0084 定义嵌入上下文 fuel 控制、RFC 0085 定义可执行 Rust 嵌入示例、RFC 0086 定义 crate 发布元数据、RFC 0094 定义 qdocco 最终值门禁、RFC 0095 定义字符串步进迭代、RFC 0096 定义其性能基准、RFC 0097 定义 `do` 参数转发、RFC 0098 定义 RFC 索引门禁、RFC 0099 定义 `!` 否定别名、RFC 0100 定义有符号 `by` 步长、RFC 0101 定义 qdocco 原子输出、RFC 0102 定义 qtest 规范文件去重、RFC 0103 定义 qbench schema 版本、RFC 0104 定义 qdocco 块注释代码保留、RFC 0105 定义 qbench 重复采样中位数、RFC 0106 定义 crate 发布包验收门禁、RFC 0107 定义 release qbench 持续门禁、RFC 0108 定义 qtest 可执行示例语料、RFC 0109 定义 qbench 核心负载全套护栏、RFC 0110 定义 Rust MSRV 契约、RFC 0111 定义 release profile 完整测试门禁、RFC 0112 定义 qbench 负载枚举与选择、RFC 0113 定义严格数值标准库函数、RFC 0114 定义链式嵌入上下文配置、RFC 0115 定义 qtest 语料筛选与枚举、RFC 0116 定义数值标准库性能负载覆盖、RFC 0117 定义 qcoffee 单结果 JSON 输出,均不改变脚本语言值模型的原型无关约束。
19 changes: 19 additions & 0 deletions RFCs/0115-qtest-selection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# RFC 0115:qtest 语料筛选与枚举

- 状态:已采纳
- 依赖:RFC 0068、RFC 0077、RFC 0079、RFC 0102、RFC 0111

## 动机

qtest 递归执行目录中的全部 `.qc` 文件,适合持续门禁;调试大型语料库时,宿主需要先确定会执行哪些文件,再只运行匹配路径的测试。筛选必须不改变默认排序、去重、fuel 和结果格式。

## 契约

1. `qtest --filter TEXT PATH...` 保留规范化路径中包含 `TEXT` 的测试文件,匹配大小写敏感且不使用 glob;未指定时运行完整集合。
2. `qtest --list [--filter TEXT] PATH...` 按最终确定性顺序逐行输出文件路径,不执行源码;不能与 `--json`、`--tap` 或 `--stats` 同用。
3. 筛选后无文件以退出码 2 失败;`--filter` 缺少或为空参数也以退出码 2 失败。
4. JSON、TAP、普通输出、fuel、符号链接去重和错误退出码在筛选结果集内保持既有契约。

## 验收

`tests/cli_tools.rs` 必须覆盖单文件筛选、列表枚举、无匹配和参数冲突;`make check` 继续执行完整 qtest 语料,不能把筛选误当作默认门禁。
18 changes: 18 additions & 0 deletions RFCs/0116-stdlib-benchmark-coverage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# RFC 0116:数值标准库性能负载覆盖

- 状态:已采纳
- 依赖:RFC 0045、RFC 0096、RFC 0109、RFC 0113

## 动机

RFC 0113 新增了严格数值标准库函数,但既有 benchmark 只覆盖语言运算和容器路径。若不把标准库调用纳入同一编译、验证、执行计时口径,性能报告无法发现其回归,也无法比较宿主回调与 VM 内建函数的实际成本。

## 契约

1. `qbench` 和 `cargo bench --bench core` 都必须包含同名的 `stdlib-abs`、`stdlib-sum`、`stdlib-min-max`、`stdlib-range-sum` 负载。
2. 每个负载在编译、验证和执行阶段都检查 RFC 0113 的最终值;qbench JSON schema、默认完整集合和 `--only` 选择语义不变。
3. 标准 benchmark 继续记录重复迭代吞吐,不设置跨机器的硬时间阈值;性能报告必须说明样本口径和环境。

## 验收

`tests/cli_tools.rs` 必须枚举并验证四个机器可读负载名;`make qbench-check` 和 `make bench` 必须执行全套;`PERFORMANCE.md` 必须列出新增负载及复现实验命令。
20 changes: 20 additions & 0 deletions RFCs/0117-qcoffee-json-output.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# RFC 0117:`qcoffee` 单结果 JSON 输出

- 状态:已采纳
- 依赖:RFC 0002、RFC 0047、RFC 0062、RFC 0077

## 动机

`qtest` 与 `qbench` 已有稳定的机器输出,而 `qcoffee` 执行模式仍把值和错误写成面向人的文本。CI、编辑器和嵌入宿主若直接消费 CLI,必须自行解析展示文本,既不能可靠区分 `nil` 与字符串,也无法稳定取得错误类别和源码行。

## 契约

1. `qcoffee --json -e SOURCE`、`qcoffee --json FILE` 和 `qcoffee --json -` 各输出恰好一行 JSON;成功时形如 `{"ok":true,"value":VALUE}`,`nil` 映射为 JSON `null`。
2. QuickCoffee 的 Bool、有限 Number、String、Array、Map 递归映射为对应 JSON 值;函数是 `{"$quickcoffee":"function"}`,以保留其为不可序列化宿主值的类型信息。Map 键按确定性字典序输出,字符串和控制字符采用 JSON 转义。
3. 编译或执行失败时退出码仍为 `1`,标准输出形如 `{"ok":false,"kind":KIND,"message":TEXT,"line":N}`;`line` 无来源时为 `null`。读取文件失败使用 `stage:"read"` 与 `kind:"io"`。错误模式不向标准错误重复输出详情。
4. `--json` 只适用于单次执行,不得与 `--interactive`、`--check`、`--dump-bytecode` 或 `--fingerprint` 合用;`--stats` 仍可使用且只写标准错误。
5. JSON 协议不改变普通输出、退出码、fuel 或 QuickCoffee 值模型;它不引入 JavaScript `undefined`、原型或隐式转换。

## 验收

`tests/cli_tools.rs` 必须覆盖复合值、`nil`、解析错误、fuel 运行时错误和 JSON/普通模式隔离;五份可执行手册与中英文语法索引说明该选项。`make check` 必须继续通过。
24 changes: 24 additions & 0 deletions benches/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,30 @@ fn main() {
iterations: 20_000,
expected: "100",
},
Workload {
name: "stdlib-abs",
source: "abs(-42)",
iterations: 20_000,
expected: "42",
},
Workload {
name: "stdlib-sum",
source: "sum([1, 2, 3, 4])",
iterations: 20_000,
expected: "10",
},
Workload {
name: "stdlib-min-max",
source: "min([3, 1, 2]) + max([3, 1, 2])",
iterations: 20_000,
expected: "4",
},
Workload {
name: "stdlib-range-sum",
source: "sum(range(1, 100))",
iterations: 10_000,
expected: "4950",
},
Workload {
name: "postfix-loops",
source: "sum = 0\ni = 0\ni = i + 1 while i < 100\nsum + i",
Expand Down
5 changes: 4 additions & 1 deletion docs/manual.classical-zh.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
<p>qtest --stats 更书各篇所试指令与余燃料于标准错误,而 ok 之出不改。</p>
<p>qtest --json 每篇出一行 JSON,便于 CI 取用;--stats 仍书于标准错误。</p>
<p>qtest --tap 出 TAP 13 及定次之记录;--json 与 --tap 不可并用。</p>
<p>qtest --filter TEXT 依路径择篇;qtest --list 但列所择之篇而不行其文。</p>
<p>qcoffee --json 一行以 JSON 载其值或错状,俾 CI 与宿主取用。</p>
<p>宿主之误,有 ErrorKind::Parse、Verify、Runtime 三类,且可别取其详;error.position() 或示从一始之源码行。</p>
<p>Engine::compile_program 创时验之;Context::run_program 屡行则复用不可变已验字节码。</p>
<p>Program::fingerprint 出确定 u64 码键,便宿主缓存,而不改执行。</p>
Expand Down Expand Up @@ -62,7 +64,8 @@
<p>数组之环可书 by step;步惟求一遍,须非零有限整数,负者自末起,映射环弗用之。</p>
<p>数组之环亦可系从一始之下标:for value, index in items then value + index。</p>
<p>后置之推导亦循严收集:value * 2 for value in items,或括以 [value * 2 for value in items]。</p>
</main><main><h1>Code</h1><pre><code>甲 = 6
</main><main><h1>Code</h1><pre><code>
甲 = 6
倍 = (x) -&gt; x * 2
shorthand = 'yes'
[first, {point: [x, y]}] = [0, {point: [20, 22]}]
Expand Down
3 changes: 3 additions & 0 deletions docs/manual.classical-zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ qtest --fuel N 者,为各可行文别限其指令之数。
qtest --stats 更书各篇所试指令与余燃料于标准错误,而 ok 之出不改。
qtest --json 每篇出一行 JSON,便于 CI 取用;--stats 仍书于标准错误。
qtest --tap 出 TAP 13 及定次之记录;--json 与 --tap 不可并用。
qtest --filter TEXT 依路径择篇;qtest --list 但列所择之篇而不行其文。
qcoffee --json 一行以 JSON 载其值或错状,俾 CI 与宿主取用。
宿主之误,有 ErrorKind::Parse、Verify、Runtime 三类,且可别取其详;error.position() 或示从一始之源码行。
Engine::compile_program 创时验之;Context::run_program 屡行则复用不可变已验字节码。
Program::fingerprint 出确定 u64 码键,便宿主缓存,而不改执行。
Expand Down Expand Up @@ -69,6 +71,7 @@ for 所系可为严式:for [left, right] in pairs,每项全合乃易名。
## Code

````quickcoffee

甲 = 6
倍 = (x) -> x * 2
shorthand = 'yes'
Expand Down
5 changes: 4 additions & 1 deletion docs/manual.devanagari-sa.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
<p>qtest --stats प्रत्येकस्य documentस्य instruction-संख्या तथा अवशिष्ट-fuel standard error मध्ये लिखति, ok-निर्गमं न परिवर्तयति।</p>
<p>qtest --json प्रत्येकस्य लेखस्य स्थिरं JSON फलम् एकस्मिन् पङ्क्तौ लिखति; --stats stderr मध्ये एव।</p>
<p>qtest --tap TAP 13 तथा नियत-सङ्ख्याङ्कितानि फलानि लिखति; --json च --tap च परस्परं निषिद्धे।</p>
<p>qtest --filter TEXT मार्ग-साम्येन परीक्षां चिनोति; qtest --list चयनित-पत्राणि केवलं गणयति, न चालयति।</p>
<p>qcoffee --json एकस्मिन् प्रयोगे JSON-मूल्यं वा संरचितं दोषं एकया पङ्क्त्या ददाति, CI-होष्ट्रयोः उपयोगाय।</p>
<p>host-error ErrorKind::Parse, Verify, Runtime तथा प्रदर्शनात् स्वतन्त्रं विवरणं ददाति; error.position() कदाचित् एकतः गणितां स्रोत-पङ्क्तिं ददाति।</p>
<p>Engine::compile_program एकवारं verify करोति; Context::run_program पुनःचालने अपरिवर्तनीय-सत्यापित-bytecode पुनरुपयुङ्क्ते।</p>
<p>Program::fingerprint होस्ट-सञ्चयाय नियतं u64 बीजं ददाति, निष्पादनं न परिवर्तयति।</p>
Expand Down Expand Up @@ -63,7 +65,8 @@
<p>array-for by step उपयुज्यते; non-zero finite integer step एकवारं मूल्यते, negative क्रमः अन्तिम-पदात् आरभते, map तु न।</p>
<p>array-for शून्यात् गणितं index अपि बध्नाति: for value, index in items then value + index।</p>
<p>postfix-comprehension समानं strict-collection वहति: value * 2 for value in items, अथवा [value * 2 for value in items]।</p>
</main><main><h1>Code</h1><pre><code>base = 40
</main><main><h1>Code</h1><pre><code>
base = 40
add = (x) -&gt; x + base
shorthand = 'yes'
[first, {point: [x, y]}] = [0, {point: [20, 22]}]
Expand Down
3 changes: 3 additions & 0 deletions docs/manual.devanagari-sa.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ qtest --fuel N प्रत्येक executable-document पृथक् inst
qtest --stats प्रत्येकस्य documentस्य instruction-संख्या तथा अवशिष्ट-fuel standard error मध्ये लिखति, ok-निर्गमं न परिवर्तयति।
qtest --json प्रत्येकस्य लेखस्य स्थिरं JSON फलम् एकस्मिन् पङ्क्तौ लिखति; --stats stderr मध्ये एव।
qtest --tap TAP 13 तथा नियत-सङ्ख्याङ्कितानि फलानि लिखति; --json च --tap च परस्परं निषिद्धे।
qtest --filter TEXT मार्ग-साम्येन परीक्षां चिनोति; qtest --list चयनित-पत्राणि केवलं गणयति, न चालयति।
qcoffee --json एकस्मिन् प्रयोगे JSON-मूल्यं वा संरचितं दोषं एकया पङ्क्त्या ददाति, CI-होष्ट्रयोः उपयोगाय।
host-error ErrorKind::Parse, Verify, Runtime तथा प्रदर्शनात् स्वतन्त्रं विवरणं ददाति; error.position() कदाचित् एकतः गणितां स्रोत-पङ्क्तिं ददाति।
Engine::compile_program एकवारं verify करोति; Context::run_program पुनःचालने अपरिवर्तनीय-सत्यापित-bytecode पुनरुपयुङ्क्ते।
Program::fingerprint होस्ट-सञ्चयाय नियतं u64 बीजं ददाति, निष्पादनं न परिवर्तयति।
Expand Down Expand Up @@ -70,6 +72,7 @@ postfix-comprehension समानं strict-collection वहति: value * 2
## Code

````quickcoffee

base = 40
add = (x) -> x + base
shorthand = 'yes'
Expand Down
5 changes: 4 additions & 1 deletion docs/manual.en.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
<p>qtest --stats writes each file's instruction count and remaining fuel to stderr without changing its ok output.</p>
<p>qtest --json writes one stable JSON result per file for CI consumers; --stats remains on stderr.</p>
<p>qtest --tap writes TAP version 13 records with deterministic numbering; --json and --tap are mutually exclusive.</p>
<p>qtest --filter TEXT selects matching paths, while qtest --list enumerates selected files without executing them.</p>
<p>qcoffee --json emits one JSON value or structured error for a single execution, suitable for CI and hosts.</p>
<p>Rust embedding errors expose ErrorKind::Parse, Verify, or Runtime plus a display-independent message; host callbacks may return Error::runtime(&quot;message&quot;), and error.position() may give a one-based source line.</p>
<p>Engine::compile_program verifies once; Context::run_program reuses the immutable verified bytecode for repeated embedding calls.</p>
<p>Program::fingerprint provides a deterministic u64 bytecode cache key without changing execution.</p>
Expand Down Expand Up @@ -66,7 +68,8 @@
<p>Arithmetic also has floor division // and dividend-dependent modulo %%: -7 // 5 is -2, while -7 %% 5 is 3.</p>
<p>return expression exits only its current function; bare return yields nil, cleans loops, and runs enclosing finally blocks.</p>
<p>Parameters may use strict nested array/map patterns; defaults and rest stay name-only.</p>
</main><main><h1>Code</h1><pre><code>base = 20
</main><main><h1>Code</h1><pre><code>
base = 20
add = (x) -&gt;
result = x + base
result
Expand Down
Loading