From b55fd9b313844735a8c2552297714d6c6b34b939 Mon Sep 17 00:00:00 2001 From: tiye Date: Sun, 23 Aug 2026 02:53:21 +0800 Subject: [PATCH] test: rebase numeric stdlib onto MSRV contract --- README.md | 2 +- RFCs/0000-project-scope.md | 2 +- RFCs/0001-language-core.md | 2 +- RFCs/0113-numeric-standard-library.md | 20 +++++++++++ docs/manual.classical-zh.html | 2 +- docs/manual.classical-zh.md | 2 +- docs/manual.devanagari-sa.html | 2 +- docs/manual.devanagari.sa.md | 2 +- docs/manual.en.html | 2 +- docs/manual.en.md | 2 +- docs/manual.latin.html | 2 +- docs/manual.latin.md | 2 +- docs/manual.zh-CN.html | 2 +- docs/manual.zh-CN.md | 2 +- docs/syntax.en.md | 2 ++ docs/syntax.zh-CN.md | 2 ++ manuals/manual.classical-zh.qc | 2 +- manuals/manual.devanagari-sa.qc | 2 +- manuals/manual.en.qc | 2 +- manuals/manual.latin.qc | 2 +- manuals/manual.zh-CN.qc | 2 +- src/vm.rs | 49 +++++++++++++++++++++++++++ tests/rfc_core.rs | 26 ++++++++++++++ tests/scripts/stdlib.qc | 2 +- 24 files changed, 118 insertions(+), 19 deletions(-) create mode 100644 RFCs/0113-numeric-standard-library.md diff --git a/README.md b/README.md index 4c3b289..7e6ccc2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ QuickCoffee 是一台以 Rust 编写、受 CoffeeScript 启发的字节码脚本引擎。它保留紧凑、可读的表达式语法,却不兼容 JavaScript:没有原型链、`this`、`eval` 或嵌入 JavaScript。 -当前实现遵循 [RFCs/0000-project-scope.md](RFCs/0000-project-scope.md) 至 [RFCs/0112-qbench-workload-selection.md](RFCs/0112-qbench-workload-selection.md)。 +当前实现遵循 [RFCs/0000-project-scope.md](RFCs/0000-project-scope.md) 至 [RFCs/0113-numeric-standard-library.md](RFCs/0113-numeric-standard-library.md)。 构建要求 Rust 1.85 或更新版本(Edition 2024);CI 同时验证 MSRV 与 stable 工具链。 ```coffee diff --git a/RFCs/0000-project-scope.md b/RFCs/0000-project-scope.md index 74e8a3e..bdbba12 100644 --- a/RFCs/0000-project-scope.md +++ b/RFCs/0000-project-scope.md @@ -20,4 +20,4 @@ QuickCoffee 是一个 Rust 实现的、受 CoffeeScript 2016 启发的脚本引 本仓库中的测试即 0.1 的语义基线。对语法或运行时的新增特性必须先以 RFC 补充定义,并至少添加:成功测试、错误测试及字节码验证测试。 -当前已实现的后续语义与工具 RFC 延伸至 RFC 0112;其中 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 延伸至 RFC 0113;其中 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 定义严格数值标准库函数,均不改变脚本语言值模型的原型无关约束。 diff --git a/RFCs/0001-language-core.md b/RFCs/0001-language-core.md index 66c6429..3c7d514 100644 --- a/RFCs/0001-language-core.md +++ b/RFCs/0001-language-core.md @@ -39,7 +39,7 @@ UTF-8 文本,以换行或分号分隔语句。缩进可形成 `if`、`unless` RFC 0095 扩展字符串 `for` 也可使用 `by` 步长;RFC 0100 规定其支持非零有限有符号整数并以负步长倒序;RFC 0097 规定 `do (name) -> ...` 从同名外层变量转发立即调用实参。 -预置函数是普通名称,不是对象原型方法:`print(value...)`、`len(value)`、`type(value)`、`range(start, end)`、`str(value)`、`keys(map)`、`values(map)`、`join(array, separator)`、`split(string, separator)`、`assert(bool, message?)`。它们的行为由 RFC 0003 的宿主接口定义。不存在 `console`、`Object.prototype`、`Array.prototype`。 +预置函数是普通名称,不是对象原型方法:`print(value...)`、`len(value)`、`type(value)`、`range(start, end)`、`str(value)`、`abs(number)`、`sum(array)`、`min(array)`、`max(array)`、`keys(map)`、`values(map)`、`join(array, separator)`、`split(string, separator)`、`assert(bool, message?)`。数值聚合只接受有限数;空数组的 `sum` 为 `0`,`min`/`max` 要求非空。它们的行为由 RFC 0003 与 RFC 0113 的宿主接口定义。不存在 `console`、`Object.prototype`、`Array.prototype`。 ### 后续契约修订 diff --git a/RFCs/0113-numeric-standard-library.md b/RFCs/0113-numeric-standard-library.md new file mode 100644 index 0000000..bd9f85f --- /dev/null +++ b/RFCs/0113-numeric-standard-library.md @@ -0,0 +1,20 @@ +# RFC 0113:严格数值标准库函数 + +- 状态:已采纳 +- 依赖:RFC 0001、RFC 0043、RFC 0108、RFC 0111 + +## 动机 + +QuickCoffee 的标准库是普通函数,而非 JavaScript 原型方法。已有容器与字符串函数足以演示数据流,但数值脚本仍需宿主自行注册最常见的绝对值和聚合操作。内建这些函数可让 qtest 语料、嵌入宿主和性能负载共享同一严格语义。 + +## 契约 + +- `abs(number)` 接受恰好一个有限数,返回其绝对值。 +- `sum(array)` 接受恰好一个数组;数组元素必须都是有限数,空数组和为 `0`。 +- `min(array)` 与 `max(array)` 接受恰好一个非空数组;元素必须都是有限数,分别返回最小值或最大值。 +- 参数数量、容器类型、元素类型或有限性不满足时,函数返回结构化运行时错误;不发生隐式转换,也不读取原型或成员方法。 +- 返回值仍是普通 `Number`,不改变字节码验证、fuel 或宿主注册函数规则。 + +## 验收 + +`tests/scripts/stdlib.qc` 验证成功路径;核心测试验证空数组、错误参数和非有限宿主数值的错误边界;五份文学手册列出函数。`make check` 必须继续通过 debug/release 全量测试及 qdocco 门禁。 diff --git a/docs/manual.classical-zh.html b/docs/manual.classical-zh.html index e45fafb..b23cfc1 100644 --- a/docs/manual.classical-zh.html +++ b/docs/manual.classical-zh.html @@ -35,7 +35,7 @@

Cargo 包志指仓、docs.rs API、README 与许可证,使嵌者易寻其用。

Context::last_execution() 示所试指令与余燃料,而不露 VM 之帧。

-- 后之参,以常字符串数组 argv 见于文中。

-

其内府皆常函,如 print、len、type、range、str、keys、values、join、split、assert。

+

其内府皆常函,如 print、len、type、range、str、abs、sum、min、max、keys、values、join、split、assert;数聚函唯受有限数之列。

函式取词法之境;末常参可书 y = 2,参缺或传 nil 则于函中取其值;末有余参,则书 tail...。

常名之参可省其括:left, right -> left + right;常值、余参与解构仍须括之。

return expression 惟函中可用,反其值而终此函;徒 return 得 nil,且清环行 finally。

diff --git a/docs/manual.classical-zh.md b/docs/manual.classical-zh.md index cce4f91..d96ff9f 100644 --- a/docs/manual.classical-zh.md +++ b/docs/manual.classical-zh.md @@ -14,7 +14,7 @@ QuickCoffee 者,Rust 所为字节码机也,非 JavaScript 之运行时。其 严等与数之比较,相连可书 `1 < middle() < 3`;中项惟求一遍,前较既否,后项不求。 -欲试之,曰:`qcoffee -e "print(range(1, 4))"`。`--check FILE` 者,析、编、验其文而不行;`--fuel N` 者,限所行指令之数也;数尽则止而报误。内府有 `print`、`len`、`type`、`range`、`str`、`keys`、`values`、`join`、`split`、`assert`;`range(a, b)` 取自 a 至 b 前。 +欲试之,曰:`qcoffee -e "print(range(1, 4))"`。`--check FILE` 者,析、编、验其文而不行;`--fuel N` 者,限所行指令之数也;数尽则止而报误。内府有 `print`、`len`、`type`、`range`、`str`、`abs`、`sum`、`min`、`max`、`keys`、`values`、`join`、`split`、`assert`;数聚函唯受有限数之列;`range(a, b)` 取自 a 至 b 前。 `qcoffee -` 自标准输入读其文,便于管道;`qcoffee --dump-bytecode -` 则析其指令而不行之。 `qcoffee --stats` 则书所试指令与余燃料于标准错误,程序之标准输出不改;每次惟一源码输入,执行模式相冲则报用法之误。 diff --git a/docs/manual.devanagari-sa.html b/docs/manual.devanagari-sa.html index edcc014..03b3978 100644 --- a/docs/manual.devanagari-sa.html +++ b/docs/manual.devanagari-sa.html @@ -36,7 +36,7 @@

yes/on true, no/off false; is/isnt strict-साम्यम् स्तः।

! strict-Bool not-पर्यायः अस्ति; != strict-असाम्यमेव तिष्ठति।

chained-comparison मध्ये मध्य-मूल्य एकवारं, पूर्व-false चेत् short-circuit भवति।

-

सामान्य-library साधारण-function रूपेण print, len, type, range, str, keys, values, join, split, assert ददाति।

+

सामान्य-library साधारण-function रूपेण print, len, type, range, str, abs, sum, min, max, keys, values, join, split, assert ददाति; संख्या-संग्रहाः केवलं finite-संख्याः गृह्णन्ति।

कार्यं lexical-environment गृह्णाति; y = 2 omitting अथवा nil दत्ते कार्यस्य अन्तरे default भवति; अन्तिमः rest-parameter tail... इति लिख्यते।

bare-name lambda left, right -> left + right भवति; default, rest, pattern तु parentheses गृह्णन्ति।

return expression केवलं वर्तमान-कार्यं समाप्तं करोति; केवलः return nil फलति, loop शुद्धीकरोति, finally च चलयति।

diff --git a/docs/manual.devanagari.sa.md b/docs/manual.devanagari.sa.md index 2dc2466..65d5dc9 100644 --- a/docs/manual.devanagari.sa.md +++ b/docs/manual.devanagari.sa.md @@ -14,7 +14,7 @@ CoffeeScript-नामानि type न परिवर्तयन्ति: ` strict अथवा numeric-comparison श्रृङ्खला भवति: `1 < middle() < 3` मध्ये middle एकवारं मूल्यते, पूर्वं false चेत् परं न मूल्यते। -`qcoffee -e "print(range(1, 4))"` प्रयुञ्जीत। `qcoffee --check FILE` स्रोतं compile-verify करोति, न चालयति; `--fuel N` निर्देश-संख्यां सीमयति। `print`, `len`, `type`, `range`, `str`, `keys`, `values`, `join`, `split`, `assert` मानक-सहायकाः; `range(a, b)` मध्ये `b` न गृह्यते। +`qcoffee -e "print(range(1, 4))"` प्रयुञ्जीत। `qcoffee --check FILE` स्रोतं compile-verify करोति, न चालयति; `--fuel N` निर्देश-संख्यां सीमयति। `print`, `len`, `type`, `range`, `str`, `abs`, `sum`, `min`, `max`, `keys`, `values`, `join`, `split`, `assert` मानक-सहायकाः; संख्या-संग्रहाः केवलं finite-संख्याः गृह्णन्ति; `range(a, b)` मध्ये `b` न गृह्यते। `qcoffee -` मानक-input तः स्रोतः पठति; `qcoffee --dump-bytecode -` तमेव स्रोतं चालनं विना विच्छिनत्ति। `qcoffee --stats` instruction-संख्या तथा अवशिष्ट-fuel standard error मध्ये लिखति, stdout अपरिवर्तितं स्थापयति; एकमेव source ग्राह्यः, विरोधि execution-mode तु usage-दोषं जनयति। diff --git a/docs/manual.en.html b/docs/manual.en.html index 1631674..cb419cf 100644 --- a/docs/manual.en.html +++ b/docs/manual.en.html @@ -32,7 +32,7 @@

yes/on and no/off are Boolean aliases; is/isnt preserve strict equality.

! is a strict Bool alias for not; != remains strict inequality.

Chained strict or numeric comparisons keep the middle value once and short-circuit.

-

The standard library is ordinary functions: print, len, type, range, str, keys, values, join, split, and assert.

+

The standard library is ordinary functions: print, len, type, range, str, abs, sum, min, max, keys, values, join, split, and assert. Numeric aggregators accept strict finite-number arrays.

switch/when selects one strict-equality branch without fallthrough.

try/catch/finally handles QuickCoffee runtime errors without JavaScript Error objects.

Integer ranges use [1..3] for an inclusive end and [1...3] for an exclusive end.

diff --git a/docs/manual.en.md b/docs/manual.en.md index 965f217..e19b5e9 100644 --- a/docs/manual.en.md +++ b/docs/manual.en.md @@ -14,7 +14,7 @@ CoffeeScript-style spellings are available without changing runtime types: `yes` Adjacent strict or numeric comparisons may chain: `1 < middle() < 3` evaluates `middle()` once and stops before later operands when an earlier comparison is false. -Run `qcoffee -e "print(range(1, 4))"`, `qcoffee --fuel 10000 program.qc`, `qcoffee --check program.qc`, or `qcoffee --dump-bytecode program.qc`. `--check` parses, compiles, and verifies without executing. Fuel limits executed instructions; exhaustion is a safe error. The small standard library contains `print`, `len`, `type`, end-exclusive `range(a, b)`, `str`, `keys`, `values`, `join`, `split`, and `assert`. +Run `qcoffee -e "print(range(1, 4))"`, `qcoffee --fuel 10000 program.qc`, `qcoffee --check program.qc`, or `qcoffee --dump-bytecode program.qc`. `--check` parses, compiles, and verifies without executing. Fuel limits executed instructions; exhaustion is a safe error. The small standard library contains `print`, `len`, `type`, end-exclusive `range(a, b)`, `str`, `abs`, `sum`, `min`, `max`, `keys`, `values`, `join`, `split`, and `assert`; numeric aggregators accept strict finite-number arrays. `qcoffee -` reads source from standard input, which is convenient in pipelines; `qcoffee --dump-bytecode -` disassembles that same input instead of executing it. `qcoffee --stats` writes instruction and remaining-fuel counters to stderr while preserving program stdout; qcoffee accepts one source input and rejects conflicting execution modes. diff --git a/docs/manual.latin.html b/docs/manual.latin.html index 73cf013..47c45cf 100644 --- a/docs/manual.latin.html +++ b/docs/manual.latin.html @@ -36,7 +36,7 @@

yes/on sunt true, no/off false; is/isnt aequalitatem strictam servant.

! est negatio Bool stricta sicut not; != inaequalitas stricta manet.

Comparationes conectae medium semel servant atque priore falso breviant.

-

Bibliotheca communis functiones ordinarias habet: print, len, type, range, str, keys, values, join, split, assert.

+

Bibliotheca communis functiones ordinarias habet: print, len, type, range, str, abs, sum, min, max, keys, values, join, split, assert; aggregationes numericae solum series numerorum finitorum accipiunt.

Functiones ambitum lexicalem capiunt; y = 2 omissus vel nil intra functionem adhibetur; rest ultimus scribitur tail....

Nomina nuda parentheses omittere possunt: sinister, dexter -> sinister + dexter; default, rest, pattern eas servant.

return expressio functionem praesentem finit; nudum return nil dat, iterata purgat, et finally circumstantia peragit.

diff --git a/docs/manual.latin.md b/docs/manual.latin.md index 8493d84..8ca21df 100644 --- a/docs/manual.latin.md +++ b/docs/manual.latin.md @@ -14,7 +14,7 @@ Voces CoffeeScript sine mutatione generum valent: `yes`/`on` sunt `true`, `no`/` Comparationes strictae vel numericae conecti possunt: `1 < middle() < 3` medium semel aestimat et, priore falso, posteriora non aestimat. -Utere `qcoffee -e "print(range(1, 4))"`; `qcoffee --check FILE` fontem compilat atque verificat sine cursu; `--fuel N` numerum instructionum finit. Bibliotheca parva habet `print`, `len`, `type`, `range(a, b)`, `str`, `keys`, `values`, `join`, `split`, et `assert`; finis in `range` exclusus est. +Utere `qcoffee -e "print(range(1, 4))"`; `qcoffee --check FILE` fontem compilat atque verificat sine cursu; `--fuel N` numerum instructionum finit. Bibliotheca parva habet `print`, `len`, `type`, `range(a, b)`, `str`, `abs`, `sum`, `min`, `max`, `keys`, `values`, `join`, `split`, et `assert`; aggregationes numericae solum series numerorum finitorum accipiunt; finis in `range` exclusus est. `qcoffee -` fontem ex initio normali legit; `qcoffee --dump-bytecode -` illum fontem sine cursu explicat. `qcoffee --stats` numeros instructionum et alimenti reliqui ad errorem ordinarium scribit, dum exitus programmatis intactus manet; unus tantum fons admittitur et modi contrarii errorem usus reddunt. diff --git a/docs/manual.zh-CN.html b/docs/manual.zh-CN.html index 18b244c..3ad467a 100644 --- a/docs/manual.zh-CN.html +++ b/docs/manual.zh-CN.html @@ -33,7 +33,7 @@

yes/on 与 no/off 是布尔别名;is/isnt 保持严格相等。

! 是严格 Bool 的 not 别名;!= 仍为严格不等。

严格或数值比较可成链,保留中间值且前段失败会短路。

-

标准库皆为普通函数:print、len、type、range、str、keys、values、join、split 与 assert。

+

标准库皆为普通函数:print、len、type、range、str、abs、sum、min、max、keys、values、join、split 与 assert;数值聚合只收严格有限数数组。

映射字面量可从左至右展开:{...defaults, theme: 'dark'};后写键覆盖先写键。

映射解构末尾可用 ...metadata 捕获未列键,所得映射不可变。

数组与 Unicode 字符串支持负索引,-1 取末项。

diff --git a/docs/manual.zh-CN.md b/docs/manual.zh-CN.md index 2fd3e8e..9eacbfa 100644 --- a/docs/manual.zh-CN.md +++ b/docs/manual.zh-CN.md @@ -32,7 +32,7 @@ qcoffee --dump-bytecode program.qc `--` 之后的参数以普通字符串数组 `argv` 提供:`qcoffee program.qc -- first second` 中 `len(argv)` 为 `2`。引擎不会暴露宿主进程或环境对象。 -`--fuel` 是每次执行的指令上限,耗尽会安全失败。标准库包括 `print`、`len`、`type`、`range`、`str`、`keys`、`values`、`join`、`split` 与 `assert`;`range(a, b)` 生成 `[a, b)`。 +`--fuel` 是每次执行的指令上限,耗尽会安全失败。标准库包括 `print`、`len`、`type`、`range`、`str`、`abs`、`sum`、`min`、`max`、`keys`、`values`、`join`、`split` 与 `assert`;数值聚合只接受严格有限数数组,`range(a, b)` 生成 `[a, b)`。 ## 语法示例 diff --git a/docs/syntax.en.md b/docs/syntax.en.md index a000b7c..175de78 100644 --- a/docs/syntax.en.md +++ b/docs/syntax.en.md @@ -18,6 +18,8 @@ Destructuring array and map members may use dynamic defaults (`[first = 1, secon This is the English index of RFC 0001. An omitted CoffeeScript 2016 feature is deliberately unsupported, not silently compatible. +The standard library is ordinary functions: `print`, `len`, `type`, `range`, `str`, `abs`, `sum`, `min`, `max`, `keys`, `values`, `join`, `split`, and `assert`. Numeric aggregators accept one array of finite numbers; `sum([])` is `0`, while `min([])` and `max([])` are errors. + Ordinary single- and double-quoted strings decode `\\0`, `\\b`, `\\f`, `\\n`, `\\r`, `\\t`, `\\v`, quote/backslash escapes, two-digit `\\xNN`, four-digit `\\uNNNN`, and one-to-six-digit `\\u{...}` Unicode escapes. They may span physical lines; a normal newline becomes one space and indentation inside the string is ignored. A single unescaped backslash at the line end removes both the backslash and the newline. Double-quoted multiline strings retain `#{expression}` interpolation, while single-quoted strings remain literal. Triple-quoted heredocs preserve newlines: `"""…"""` interpolates and `'''…'''` is literal; an unclosed delimiter or invalid escape is a lexical error. `for` uses strict recursive patterns for its bindings: `for [left, right] in pairs then left + right` and `for own _, value of record then value` are supported. A pattern mismatch is a runtime error and never partially updates that iteration's bindings. diff --git a/docs/syntax.zh-CN.md b/docs/syntax.zh-CN.md index 57a7fa8..a8803e9 100644 --- a/docs/syntax.zh-CN.md +++ b/docs/syntax.zh-CN.md @@ -6,6 +6,8 @@ 这是 RFC 0001 的中文索引;未列出的 CoffeeScript 2016 特性不是“隐式兼容”,而是明确不支持。Cargo 包元数据提供仓库、README、许可证和 docs.rs API 链接。 +标准库是普通函数:`print`、`len`、`type`、`range`、`str`、`abs`、`sum`、`min`、`max`、`keys`、`values`、`join`、`split` 与 `assert`。数值聚合只收一个有限数数组;`sum([])` 为 `0`,`min([])` 与 `max([])` 报错。 + | 类别 | 支持 | 不支持(本版) | |---|---|---| | 字面量 | 十进制、十六进制 `0xff`、二进制 `0b1010`、八进制 `0o755` 与科学计数法数字、字符串、双引号 `#{expr}` 插值、保留换行的 `"""…"""` 插值 heredoc 与 `'''…'''` 字面 heredoc、`true`/`yes`/`on`、`false`/`no`/`off`、`nil`、数组与 `[head, items...]` 展开、整数区间 `[1..3]`(含上界)/`[1...3]`(不含上界)、映射、`{name}` 简写与映射展开 `{...base, key: value}` | 正则、JS 插值、`undefined` | diff --git a/manuals/manual.classical-zh.qc b/manuals/manual.classical-zh.qc index 0c2be39..81ca9f2 100644 --- a/manuals/manual.classical-zh.qc +++ b/manuals/manual.classical-zh.qc @@ -35,7 +35,7 @@ ## Cargo 包志指仓、docs.rs API、README 与许可证,使嵌者易寻其用。 ## Context::last_execution() 示所试指令与余燃料,而不露 VM 之帧。 ## -- 后之参,以常字符串数组 argv 见于文中。 -## 其内府皆常函,如 print、len、type、range、str、keys、values、join、split、assert。 +## 其内府皆常函,如 print、len、type、range、str、abs、sum、min、max、keys、values、join、split、assert;数聚函唯受有限数之列。 ## 函式取词法之境;末常参可书 y = 2,参缺或传 nil 则于函中取其值;末有余参,则书 tail...。 ## 常名之参可省其括:left, right -> left + right;常值、余参与解构仍须括之。 ## return expression 惟函中可用,反其值而终此函;徒 return 得 nil,且清环行 finally。 diff --git a/manuals/manual.devanagari-sa.qc b/manuals/manual.devanagari-sa.qc index 3f3de48..c8a1fdd 100644 --- a/manuals/manual.devanagari-sa.qc +++ b/manuals/manual.devanagari-sa.qc @@ -36,7 +36,7 @@ ## yes/on true, no/off false; is/isnt strict-साम्यम् स्तः। ## ! strict-Bool not-पर्यायः अस्ति; != strict-असाम्यमेव तिष्ठति। ## chained-comparison मध्ये मध्य-मूल्य एकवारं, पूर्व-false चेत् short-circuit भवति। -## सामान्य-library साधारण-function रूपेण print, len, type, range, str, keys, values, join, split, assert ददाति। +## सामान्य-library साधारण-function रूपेण print, len, type, range, str, abs, sum, min, max, keys, values, join, split, assert ददाति; संख्या-संग्रहाः केवलं finite-संख्याः गृह्णन्ति। ## कार्यं lexical-environment गृह्णाति; y = 2 omitting अथवा nil दत्ते कार्यस्य अन्तरे default भवति; अन्तिमः rest-parameter tail... इति लिख्यते। ## bare-name lambda left, right -> left + right भवति; default, rest, pattern तु parentheses गृह्णन्ति। ## return expression केवलं वर्तमान-कार्यं समाप्तं करोति; केवलः return nil फलति, loop शुद्धीकरोति, finally च चलयति। diff --git a/manuals/manual.en.qc b/manuals/manual.en.qc index 96b55ea..2168acd 100644 --- a/manuals/manual.en.qc +++ b/manuals/manual.en.qc @@ -32,7 +32,7 @@ ## yes/on and no/off are Boolean aliases; is/isnt preserve strict equality. ## ! is a strict Bool alias for not; != remains strict inequality. ## Chained strict or numeric comparisons keep the middle value once and short-circuit. -## The standard library is ordinary functions: print, len, type, range, str, keys, values, join, split, and assert. +## The standard library is ordinary functions: print, len, type, range, str, abs, sum, min, max, keys, values, join, split, and assert. Numeric aggregators accept strict finite-number arrays. ## switch/when selects one strict-equality branch without fallthrough. ## try/catch/finally handles QuickCoffee runtime errors without JavaScript Error objects. ## Integer ranges use [1..3] for an inclusive end and [1...3] for an exclusive end. diff --git a/manuals/manual.latin.qc b/manuals/manual.latin.qc index e0dbdd0..2db586c 100644 --- a/manuals/manual.latin.qc +++ b/manuals/manual.latin.qc @@ -36,7 +36,7 @@ ## yes/on sunt true, no/off false; is/isnt aequalitatem strictam servant. ## ! est negatio Bool stricta sicut not; != inaequalitas stricta manet. ## Comparationes conectae medium semel servant atque priore falso breviant. -## Bibliotheca communis functiones ordinarias habet: print, len, type, range, str, keys, values, join, split, assert. +## Bibliotheca communis functiones ordinarias habet: print, len, type, range, str, abs, sum, min, max, keys, values, join, split, assert; aggregationes numericae solum series numerorum finitorum accipiunt. ## Functiones ambitum lexicalem capiunt; y = 2 omissus vel nil intra functionem adhibetur; rest ultimus scribitur tail.... ## Nomina nuda parentheses omittere possunt: sinister, dexter -> sinister + dexter; default, rest, pattern eas servant. ## return expressio functionem praesentem finit; nudum return nil dat, iterata purgat, et finally circumstantia peragit. diff --git a/manuals/manual.zh-CN.qc b/manuals/manual.zh-CN.qc index 1f944d1..fa535e5 100644 --- a/manuals/manual.zh-CN.qc +++ b/manuals/manual.zh-CN.qc @@ -33,7 +33,7 @@ ## yes/on 与 no/off 是布尔别名;is/isnt 保持严格相等。 ## ! 是严格 Bool 的 not 别名;!= 仍为严格不等。 ## 严格或数值比较可成链,保留中间值且前段失败会短路。 -## 标准库皆为普通函数:print、len、type、range、str、keys、values、join、split 与 assert。 +## 标准库皆为普通函数:print、len、type、range、str、abs、sum、min、max、keys、values、join、split 与 assert;数值聚合只收严格有限数数组。 ## 映射字面量可从左至右展开:{...defaults, theme: 'dark'};后写键覆盖先写键。 ## 映射解构末尾可用 ...metadata 捕获未列键,所得映射不可变。 ## 数组与 Unicode 字符串支持负索引,-1 取末项。 diff --git a/src/vm.rs b/src/vm.rs index 0170458..8a21abe 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -532,6 +532,34 @@ impl Context { } Ok(Value::String(Rc::from(xs[0].to_string()))) }); + self.add_native("abs", |xs| { + if xs.len() != 1 { + return Err(Error::runtime("abs expects one number")); + } + let value = number(xs[0].clone())?; + if !value.is_finite() { + return Err(Error::runtime("abs expects a finite number")); + } + Ok(Value::Number(value.abs())) + }); + self.add_native("sum", |xs| { + let values = numeric_array(xs, "sum")?; + Ok(Value::Number(values.into_iter().sum())) + }); + self.add_native("min", |xs| { + let values = numeric_array(xs, "min")?; + let Some(value) = values.into_iter().reduce(f64::min) else { + return Err(Error::runtime("min expects a non-empty array")); + }; + Ok(Value::Number(value)) + }); + self.add_native("max", |xs| { + let values = numeric_array(xs, "max")?; + let Some(value) = values.into_iter().reduce(f64::max) else { + return Err(Error::runtime("max expects a non-empty array")); + }; + Ok(Value::Number(value)) + }); self.add_native("keys", |xs| { if xs.len() != 1 { return Err(Error::runtime("keys expects one argument")); @@ -1273,6 +1301,27 @@ fn numbers(xs: &[Value]) -> Result<(f64, f64), Error> { } Ok((number(xs[0].clone())?, number(xs[1].clone())?)) } +fn numeric_array(xs: &[Value], name: &str) -> Result, Error> { + if xs.len() != 1 { + return Err(Error::runtime(format!("{name} expects one array"))); + } + let Value::Array(values) = &xs[0] else { + return Err(Error::runtime(format!("{name} expects an array"))); + }; + values + .iter() + .map(|value| { + let value = number(value.clone())?; + if value.is_finite() { + Ok(value) + } else { + Err(Error::runtime(format!( + "{name} expects finite numeric elements" + ))) + } + }) + .collect() +} fn numeric_range(start: f64, end: f64, inclusive: bool) -> Result { if !start.is_finite() || !end.is_finite() diff --git a/tests/rfc_core.rs b/tests/rfc_core.rs index 734dec6..a3cccb4 100644 --- a/tests/rfc_core.rs +++ b/tests/rfc_core.rs @@ -1341,6 +1341,32 @@ fn redesigned_standard_library_is_function_based_not_prototype_based() { assert!(Context::new().eval("assert(false, 'expected')").is_err()); } #[test] +fn numeric_standard_library_is_strict_and_total() { + assert_eq!(eval("abs(-3)").as_number(), Some(3.)); + assert_eq!(eval("sum([])").as_number(), Some(0.)); + assert_eq!( + eval("min([3, 1, 2]) + max([3, 1, 2])").as_number(), + Some(4.) + ); + for source in [ + "abs()", + "abs('3')", + "sum(1)", + "sum([1, '2'])", + "min([])", + "max([true])", + ] { + assert!( + Context::new().eval(source).is_err(), + "expected {source} to fail" + ); + } + let mut host = Context::new(); + host.set_global("nan", Value::Number(f64::NAN)); + assert!(host.eval("abs(nan)").is_err()); + assert!(host.eval("sum([nan])").is_err()); +} +#[test] fn array_destructuring_is_strict_and_has_an_explicit_ignore_name() { assert_eq!( eval("left, right = [20, 22]\nleft + right").as_number(), diff --git a/tests/scripts/stdlib.qc b/tests/scripts/stdlib.qc index f3902b1..418fc25 100644 --- a/tests/scripts/stdlib.qc +++ b/tests/scripts/stdlib.qc @@ -1,4 +1,4 @@ # test: the prototype-free standard library stays ordinary and composable record = {a: 1, b: 2} numbers = range(1, 4) -len(numbers) == 3 and type(record) == 'map' and str(numbers[2]) == '3' and join(keys(record), ',') == 'a,b' and join(values(record), ',') == '1,2' and split('a,b', ',')[1] == 'b' and assert(true) == nil +len(numbers) == 3 and type(record) == 'map' and str(numbers[2]) == '3' and join(keys(record), ',') == 'a,b' and join(values(record), ',') == '1,2' and split('a,b', ',')[1] == 'b' and abs(-3) == 3 and sum(numbers) == 6 and min(numbers) == 1 and max(numbers) == 3 and assert(true) == nil