From 15e29c9946a84a540602a3381c2882be1de68e6a Mon Sep 17 00:00:00 2001 From: taadis Date: Wed, 21 Oct 2026 14:16:13 +0800 Subject: [PATCH] feat: stdlib uuid module with fun test --- README.md | 11 +-- README.zh.md | 11 +-- fun/lib/uuid.fun | 72 ++++++++++++++++ fun/test/expected/lib-fun-uuid-test.fun.txt | 3 + fun/test/expected/lib-loadcheck.sh.txt | 3 +- fun/test/expected/macos/lib-loadcheck.sh.txt | 3 +- fun/test/fun-uuid/package.fun | 12 +++ fun/test/fun-uuid/uuid_test.fun | 38 +++++++++ fun/test/lib-fun-uuid-test.fun | 89 ++++++++++++++++++++ fun/test/regression-list.txt | 1 + 10 files changed, 231 insertions(+), 12 deletions(-) create mode 100644 fun/lib/uuid.fun create mode 100644 fun/test/expected/lib-fun-uuid-test.fun.txt create mode 100644 fun/test/fun-uuid/package.fun create mode 100644 fun/test/fun-uuid/uuid_test.fun create mode 100644 fun/test/lib-fun-uuid-test.fun diff --git a/README.md b/README.md index 3b5709f..369d025 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ In one sentence: **Fun is a scripting language that goes all the way from JSON to machine code.** - One runtime spans the whole spectrum, from high-level data work (JSON/FD) down to low-level system programming (FFI, C at runtime, JIT, machine code); -- 62 standard-library modules written in Fun itself ship together as one embeddable `fun.dll`; +- 63 standard-library modules written in Fun itself ship together as one embeddable `fun.dll`; - in AI-agent and domestic-IT scenarios, it is the lightweight glue connecting OSes, databases, chips, and business logic. Fun is designed around a simple premise: **data and code should be equally first-class, and a script should be able to reach down to the machine without leaving the language.** @@ -70,7 +70,7 @@ Fun is written in Pascal (Delphi / Free Pascal). The core is a small tree-walkin - **Keyword aliasing** — a canonical English keyword set with pluggable alias packs (Chinese, French, custom) selected at runtime (see [below](#keyword-aliasing)). - **Cross-platform** — Windows (32/64-bit), Linux, ARM, and Windows CE. - **Dual distribution** — command-line `fun.exe` and embeddable runtime `fun.dll` exporting a simple `Run` interface. -- **Self-hosted standard library** — 62 modules written in Fun itself, from JSON/YAML/XML to ORM, async, UI, and native bindings. +- **Self-hosted standard library** — 63 modules written in Fun itself, from JSON/YAML/XML to ORM, async, UI, and native bindings. --- @@ -371,7 +371,7 @@ fun/ │ ├── utils/ # Utility functions │ └── prj/fun/ # Projects & build scripts (funcmd.dpr, Delphi/FPC) ├── fun/ -│ ├── lib/ # Standard library (pure Fun, 62 modules) +│ ├── lib/ # Standard library (pure Fun, 63 modules) │ ├── demo/ # Language syntax demos │ ├── demos/ # Full applications & benchmarks │ └── key_*.ini # Keyword alias packs (CN / FR / PUA) @@ -496,11 +496,12 @@ win64/aarch64 target, so there is nothing to build with. ## Standard library -The standard library in [`fun/lib`](fun/lib) is written in pure Fun (62 modules): +The standard library in [`fun/lib`](fun/lib) is written in pure Fun (63 modules): -> In total roughly 6,000 lines of logical code (excluding comments), about 100 lines per module — all 62 modules written in Fun itself. +> In total roughly 6,000 lines of logical code (excluding comments), about 100 lines per module — all 63 modules written in Fun itself. - **Data**: `lib-json`, `lib-yaml`, `lib-xml`, `lib-base64`, `lib-cstruct`, `lib-md5`, `lib-crypt` +- **Utility**: `uuid` - **Collections / algorithms**: `lib-set`, `lib-tree`, `lib-stack`, `lib-dyns`, `lib-math` - **Text**: `lib-string`, `lib-regex`, `lib-match`, `lib-unicode` - **System / IO**: `lib-file`, `lib-os`, `lib-host`, `lib-time`, `lib-cmdline`, `lib-proc` diff --git a/README.zh.md b/README.zh.md index 2f05802..fc657ab 100644 --- a/README.zh.md +++ b/README.zh.md @@ -39,7 +39,7 @@ 一句话定位:**Fun 是一门从 JSON 一路写到机器码的脚本语言。** - 用同一个运行时,覆盖从高层数据处理(JSON/FD)到底层系统编程(FFI、运行时 C、JIT、机器码)的全谱系; -- 自带 62 个用 Fun 自身编写的标准库模块,全栈能力打包进单个可嵌入的 `fun.dll`; +- 自带 63 个用 Fun 自身编写的标准库模块,全栈能力打包进单个可嵌入的 `fun.dll`; - 在信创与 AI Agent 场景中,作为连接国产 OS、数据库、芯片与业务逻辑的轻量级胶水。 Fun 的设计出发点很简单:**数据与代码都应该是头等公民,而且一段脚本应该能在不离开语言的情况下一路触达机器底层。** @@ -70,7 +70,7 @@ Fun 使用 Pascal(Delphi / Free Pascal)编写。核心是一个小巧的树 - **关键字别名** —— 规范英文关键字 + 可插拔的别名包(中文、法文、自定义),运行时选择(见[下文](#关键字别名))。 - **跨平台** —— Windows(32/64 位)、Linux、ARM 与 Windows CE。 - **双形态分发** —— 命令行 `fun.exe` 与嵌入运行时 `fun.dll`(导出简单的 `Run` 接口)。 -- **自托管的标准库** —— 62 个用 Fun 自身编写的模块,覆盖 JSON/YAML/XML 到 ORM、异步、UI 与原生绑定。 +- **自托管的标准库** —— 63 个用 Fun 自身编写的模块,覆盖 JSON/YAML/XML 到 ORM、异步、UI 与原生绑定。 --- @@ -364,7 +364,7 @@ fun/ │ ├── utils/ # 工具函数 │ └── prj/fun/ # 工程与构建脚本(funcmd.dpr,Delphi/FPC) ├── fun/ -│ ├── lib/ # 标准库(纯 Fun 编写,62 个模块) +│ ├── lib/ # 标准库(纯 Fun 编写,63 个模块) │ ├── demo/ # 语言语法演示 │ ├── demos/ # 完整应用与基准测试 │ └── key_*.ini # 关键字别名包(CN / FR / PUA) @@ -476,11 +476,12 @@ float/混合参数与 `@toCallback` 闭包在 win64 上也有覆盖;`ffi-scala ## 标准库 -标准库位于 [`fun/lib`](fun/lib),以纯 Fun 编写(62 个模块): +标准库位于 [`fun/lib`](fun/lib),以纯 Fun 编写(63 个模块): -> 总代码量约 6,000 行逻辑代码(不含注释),平均每个模块约 100 行——62 个模块全部用 Fun 自身写成。 +> 总代码量约 6,000 行逻辑代码(不含注释),平均每个模块约 100 行——63 个模块全部用 Fun 自身写成。 - **数据**:`lib-json`、`lib-yaml`、`lib-xml`、`lib-base64`、`lib-cstruct`、`lib-md5`、`lib-crypt` +- **工具**:`uuid` - **集合 / 算法**:`lib-set`、`lib-tree`、`lib-stack`、`lib-dyns`、`lib-math` - **文本**:`lib-string`、`lib-regex`、`lib-match`、`lib-unicode` - **系统 / IO**:`lib-file`、`lib-os`、`lib-host`、`lib-time`、`lib-cmdline`、`lib-proc` diff --git a/fun/lib/uuid.fun b/fun/lib/uuid.fun new file mode 100644 index 0000000..adc7004 --- /dev/null +++ b/fun/lib/uuid.fun @@ -0,0 +1,72 @@ +// Copyright (c) 2010-2026 Zhang Weidong +// SPDX-License-Identifier: MIT +// +// uuid: RFC 9562 version-4 UUIDs from the OS CSPRNG. +// +// use 'uuid.fun'; +// ?. UUID(); // 4f3c2b1a-9d8e-4f7a-8b6c-0123456789ab +// +// Random bytes come from getentropy() on Unix and RtlGenRandom +// (advapi32.SystemFunction036) on Windows. There is no seeding, no global +// state, and no pseudo-random fallback: UUID() raises when the OS source is +// unavailable. +use 'lib-host.fun'; + +var _isWin = IsWindows(); +var _rand = nil; +var _randWin = false; + +if _isWin then + _randWin = true; + try + _rand = 'advapi32'.getapi('SystemFunction036', 'pi:i'); + except + _rand = nil; + end try; +else + try + _rand = LibC().getapi('getentropy', 'pi:i'); + except + _rand = nil; + end try; +end if; + +var _hex = '0123456789abcdef'; + +fun _rand16() + result = ''; + if _rand = nil then + raise 'uuid: OS random source is not available'; + end if; + var b = 0.toChar().x(16); + if _randWin then + if _rand(b.toNum(-1), 16) = 0 then + raise 'uuid: RtlGenRandom failed'; + end if; + else + if _rand(b.toNum(-1), 16) <> 0 then + raise 'uuid: getentropy failed'; + end if; + end if; + result = b; +end fun; + +fun _byteHex(v) + result = _hex.substr(v >> 4, 1) & _hex.substr(v bit and 15, 1); +end fun; + +fun UUID() + var b = _rand16(); + var h = ''; + for i = 0 to 15 do + var v = b.toByte(i); + if i = 6 then + v = (v bit and 0x0f) bit or 0x40; // version 4 + elsif i = 8 then + v = (v bit and 0x3f) bit or 0x80; // variant 10xx + end if; + h &= _byteHex(v); + end do; + result = h.substr(0, 8) & '-' & h.substr(8, 4) & '-' & h.substr(12, 4) & + '-' & h.substr(16, 4) & '-' & h.substr(20, 12); +end fun; diff --git a/fun/test/expected/lib-fun-uuid-test.fun.txt b/fun/test/expected/lib-fun-uuid-test.fun.txt new file mode 100644 index 0000000..09694e4 --- /dev/null +++ b/fun/test/expected/lib-fun-uuid-test.fun.txt @@ -0,0 +1,3 @@ +uuid project runs: True +uuid tests pass: True +uuid summary: True diff --git a/fun/test/expected/lib-loadcheck.sh.txt b/fun/test/expected/lib-loadcheck.sh.txt index dfb5449..28ea513 100644 --- a/fun/test/expected/lib-loadcheck.sh.txt +++ b/fun/test/expected/lib-loadcheck.sh.txt @@ -62,5 +62,6 @@ LOAD lib-xml LOAD lib-yaml LOAD lib-zlib LOAD test +LOAD uuid -loaded=58 failed=6 \ No newline at end of file +loaded=59 failed=6 \ No newline at end of file diff --git a/fun/test/expected/macos/lib-loadcheck.sh.txt b/fun/test/expected/macos/lib-loadcheck.sh.txt index 03ee12f..31c25c0 100644 --- a/fun/test/expected/macos/lib-loadcheck.sh.txt +++ b/fun/test/expected/macos/lib-loadcheck.sh.txt @@ -62,5 +62,6 @@ LOAD lib-xml LOAD lib-yaml LOAD lib-zlib LOAD test +LOAD uuid -loaded=58 failed=6 \ No newline at end of file +loaded=59 failed=6 \ No newline at end of file diff --git a/fun/test/fun-uuid/package.fun b/fun/test/fun-uuid/package.fun new file mode 100644 index 0000000..19f7fdf --- /dev/null +++ b/fun/test/fun-uuid/package.fun @@ -0,0 +1,12 @@ +fun package() + result = [ + name: 'fun-uuid-test', + version: '0.1.0', + bin: '', + lib: '', + dependencies: [], + // >>> generated + resolved: [] + // <<< generated + ]; +end fun; diff --git a/fun/test/fun-uuid/uuid_test.fun b/fun/test/fun-uuid/uuid_test.fun new file mode 100644 index 0000000..4314ef5 --- /dev/null +++ b/fun/test/fun-uuid/uuid_test.fun @@ -0,0 +1,38 @@ +use 'test.fun'; +use '../../lib/uuid.fun'; + +fun isHexChar(c) + result = '0123456789abcdef'.subpos(c) >= 0; +end fun; + +test('format', { + var u = UUID(); + assert.equal(u.length(), 36, 'length'); + var i = 0; + while i < 36 do + var c = u.substr(i, 1); + if i = 8 or i = 13 or i = 18 or i = 23 then + assert.equal(c, '-', 'dash at ' & i); + else + assert.is_true(isHexChar(c), 'hex at ' & i); + end if; + i += 1; + end do; +}); + +test('version and variant', { + var u = UUID(); + assert.equal(u.substr(14, 1), '4'); + assert.is_true('89ab'.subpos(u.substr(19, 1)) >= 0); + assert.equal(u, u.lower()); +}); + +test('unique', { + var seen = new []; + var i = 0; + while i < 1000 do + seen[UUID()] = true; + i += 1; + end do; + assert.equal(seen.@count(), 1000); +}); diff --git a/fun/test/lib-fun-uuid-test.fun b/fun/test/lib-fun-uuid-test.fun new file mode 100644 index 0000000..f8a72ee --- /dev/null +++ b/fun/test/lib-fun-uuid-test.fun @@ -0,0 +1,89 @@ +// End-to-end test for fun/lib/uuid.fun. The UUID checks themselves live in +// fun/test/fun-uuid/ and run through `fun test`; this wrapper only proves the +// project run succeeds on every host (including the Windows RNG path). +use 'fun/lib/lib-host.fun'; + +var _isWin = IsWindows(); +var sep = HostSep(); +var nl = 10.toChar(); +var dq = 34.toChar(); + +fun q(s) + result = dq & s & dq; +end fun; + +var _getpid; +var _system; +if _isWin then + _getpid = 'kernel32'.getapi('GetCurrentProcessId', ':i'); + _system = LibC().getapi('system', 'a:i'); +else + _getpid = LibC().getapi('getpid', ':i'); + _system = LibC().getapi('system', 's:i'); +end if; +var _pid = _getpid(); +var _funHome = ''; + +fun runIn(dir, cmd, logFile) + if _isWin then + var crlf = 13.toChar() & nl; + var bat = logFile & '.bat'; + var body = '@echo off' & crlf; + if dir <> '' then body &= 'cd /d ' & q(dir) & crlf; end if; + if _funHome <> '' then body &= 'set "FUN_HOME=' & _funHome & '"' & crlf; end if; + body &= cmd & ' >' & q(logFile) & ' 2>&1' & crlf; + bat.save(body); + result = _system(q(q(bat))); + else + var c = cmd; + if _funHome <> '' then c = 'FUN_HOME=' & q(_funHome) & ' ' & c; end if; + if dir <> '' then c = 'cd ' & q(dir) & ' && ' & c; end if; + c &= ' >' & q(logFile) & ' 2>&1'; + result = _system(c) div 256; + end if; +end fun; + +fun loadIf(p) + result = ''; + try + result = p.load(); + except + end try; +end fun; + +fun line(s) + result = s.replace(13.toChar(), '').replace(nl, ' '); +end fun; + +fun has(s, part) + result = s.subpos(part) >= 0; +end fun; + +fun absPath(p) + result = p; + if _isWin then + if p.substr(1, 1) <> ':' then result = HostCurrPath() & sep & p; end if; + else + if p.substr(0, 1) <> '/' then result = HostCurrPath() & sep & p; end if; + end if; +end fun; + +var base = HostTempPath('fun-uuid-test-' & _pid & sep); +(base & '.seed').save('x'); +_funHome = base & 'funhome'; +var funcmd = q(absPath(0.arg())); +var proj = HostCurrPath() & sep & 'fun' & sep & 'test' & sep & 'fun-uuid'; +var log = base & 'run.log'; + +var rc = runIn(proj, funcmd & ' test', log); +var out = loadIf(log); +var okRun = rc = 0; +var okPass = has(out, 'PASS (3)'); +var okSum = has(out, '1 file, 3 tests: all passed'); + +?. 'uuid project runs: ' & okRun; +?. 'uuid tests pass: ' & okPass; +?. 'uuid summary: ' & okSum; +if not okRun or not okPass or not okSum then + ?. 'failed uuid detail: rc=' & rc & ' out=[' & line(out) & ']'; +end if; diff --git a/fun/test/regression-list.txt b/fun/test/regression-list.txt index 094cf77..599f9b7 100644 --- a/fun/test/regression-list.txt +++ b/fun/test/regression-list.txt @@ -97,3 +97,4 @@ lib-netif-test.fun test/lib-netif-test.fun snap lib-fun-pack-test.fun test/lib-fun-pack-test.fun snap lib-fun-package-test.fun test/lib-fun-package-test.fun snap lib-fun-test-test.fun test/lib-fun-test-test.fun snap +lib-fun-uuid-test.fun test/lib-fun-uuid-test.fun snap