作者: 一了 <1liao3@funlang.org> 日期: 2026-10-16 一句话总结: AI 做的 Fun 语言源码深度分析报告, 联名作者: 一了, 元宝。 Fun语言源码深度分析报告 - 元宝
分析对象:
https://github.com/funlang/fun,分支main,版本 9.0 分析方式:拉取完整源码后逐层静态阅读(非 README 转述)。文中所有行号以仓库src/为基准。 分析日期:2026-10-16
一、结论先行
Fun 不是玩具,但也不是一门"通用替代型"脚本语言。它的真实定位是:
一个用 Pascal 写的、极紧凑的树遍历解释器内核(自写代码约 1.27 万行),外挂一条「从数据结构一路下沉到机器码」的能力阶梯,并把 61 个标准库模块用 Fun 自己重写了一遍。
三个最值得注意的判断:
- 内核极小、但边界极宽
。真正手写的内核只有 ~12.7k 行 Pascal,却同时覆盖了词法/语法分析、对象系统、闭包、正则(PCRE)、FFI、运行时 C 编译、JIT、内联汇编。代价是每一块都做得"够用即止",而非工业级完备。 - 所谓 JIT 不是"把 Fun 编译成机器码"
。JIT 只对你写在脚本里的 C 片段或汇编片段生效;Fun 代码本身永远是树遍历解释执行,且没有常量折叠、没有类型特化——仓库自带基准显示简单算术 78–143 ns/次,同一计算比内联汇编慢约 613 倍(详见 §10)。这是最容易误读的一点,README 的措辞容易让人以为有语言级 JIT。 - 能力阶梯的代价是安全边界
。仓库自带 SAFETY.zh.md,把getapi/@toCallback/TCC/汇编/任意内存读写标记为 NAT/MEM 类危险能力,并明确写着:拦截开关-safe尚未实现。也就是说,当前版本跑 Fun 脚本 ≈ 跑任意本机代码,没有沙箱。
二、仓库画像(实测数据)
src/ | ||
| 12,692 行 | ||
ytab/ltab/yact/yacc.inc) | ||
src/regex/pcre/ | ||
src/3rd/ | ||
| 12,778 行 / 88 个文件 | fun/lib/ | |
fun/demofun/test(59 脚本 + 93 份快照) | ||
| 开源仅一个多月,社区几乎为零 | ||
语言分布有个冷知识:GitHub 把它标成 "Standard ML",因为 .fun 后缀被 linguist 误判(SML 的关键字正是 fun)。
三、执行流水线
源码文本 → lex(ltab.inc 生成的 DFA 表 + lex.inc) → yacc LALR 归约(ytab.inc/yact.inc),动作直接调用 CParser.BuildXXX → 直接构造 AST(CNode 树,无独立 IR、无字节码) → CRun.run(env) 树遍历解释执行关键观察:没有字节码、没有编译期优化、没有 AST→IR 这一步。语法动作的语义动作(如 builder.BuildIf、CreateExp)边归约边建树;执行时 CRuns.run 顺序遍历子节点,CExp.calc 递归求值。这决定了性能上限,也是"小"的来源。
四、内核设计解剖
4.1 值模型:把 Pascal Variant 玩到极致
// src/core/base.pas:28 CValue = System.Variant; PValue = ^CValue; CData = System.TVarData;所有 Fun 值就是 Pascal Variant;代码大量直接操作TVarData(PData(val)^.VType / VInteger / VPointer),绕过 RTL 做零拷贝读写。自定义两个变体类型 VarObject/VarObjNew(fun.pas:76-82,FPC 下取$201/$202、Delphi 下2010/2011)承载对象指针;VarObjNew表示"接管引用",clear()时会_Release。- 整数默认是 int32
,即使在 64 位目标上: fun.pas:34明写int = int32; // NOT int64 !!!。指针宽度另用intptr/uintptr(FunPtrWide宏区分)。这带来一整类 64 位移植坑——代码里到处是补丁式注释(base.pas:272-321的asPtr/rawPtr就专门处理"64 位下地址不能从 32 位字段读")。 浮点用 Double,时间用TDateTime;ToFunTime甚至在 FPC 分支里手写 ISO 时间解析,因为 FPC 的VarToDateTime受宿主 locale 影响(base.pas:193-247)。
4.2 内存管理:原子引用计数,不是 tracing GC
// src/core/base.pas:413-421 function CBase._AddRef : fun.int; begin Result := lockInc(FRefCount); end; function CBase._Release: fun.int; begin Result := lockDec(FRefCount); end;CBase 手写 IInterface,用原子自增/自减做引用计数;add()/del() 是全局入口。含义:
无分代、无标记清除,循环引用不会被回收(闭包捕获 + 集合互相嵌套是很容易写出环的)。 官方宣传口径里的 "GC",在实现层面就是这套引用计数 + Variant 值语义。 源码里能看到明确的"泄漏补丁"痕迹,例如 CId.enil(core.pas:256-264)注释说明:原来每次求值CExp.new(nil)会泄漏一个节点,2 万次循环涨 1.8MB RSS,改成预分配单个 nil 单元。
4.3 AST 节点即存储:变量是树上的槽
这是整个设计里最有意思的一点:CExp(表达式节点)同时是 AST 节点和运行时值的容器:
// src/core/core.pas:190-214 CExp = class(CNode) protected p_val: PValue; // 值指针 v_val: CValue; // 内联值 ...变量(CVar)、集合成员、对象属性都是 CExp 实例,赋值就是往节点里写 Variant。于是:
不需要独立的 Environment/Activation Record 结构,符号表就是 AST 的一部分; 递归调用靠 CFunStack/CVarStack(base.pas:129-157)把同名变量按调用深度压栈/出栈,而不是新建帧;副作用是赋值天然是引用语义: new []出来的集合赋给另一个变量不会拷贝,SAFETY.zh.md明确记了作者决定"不做 COW"。
4.4 作用域与模块
符号查找 CNode.UID(core.pas:493)沿 parent 链上溯;CRuns.ID用CHash(Trie 实现,base.pas:48-105,Max_Char=39)做名字索引。模块: use 'file.fun'生成CModu(core.pas:142-154),带runed标志保证只加载运行一次,next串成链表做去重。use ':file'是特殊语法——在解析期把文件内容内联进脚本( lib-asm-pro.fun用它吃 1MB 的asm-list.fd;后来改成.gz+ zlib 解压,注释里明确写了这条优化)。- 平台中性名
: use 'lib-proc.fun'不必写平台后缀——CParser.PlatformFile(parse.pas:1032-1060)先按目标试平台专属变体(macOS-darwin、Linux-lnx),没有再退共享的-unx,命中即用;只有 Unix 上都没命中(或在 Windows 上,这段探测整段{。
.# 3
- 关键字别名层
: ParseAlias(parse.pas:179)在词法期把别名换成规范关键字,-key:key_cn.ini就能用中文关键字(函数=fun、如果=if……)。语言本身是关键字中立的,别名包只是本地化层。注意fun/key_cn.ini同样是 GBK。 六、能力阶梯:真正的技术含量所在
6.1 FFI:两个完全不同的后端
签名串约定
'iiii:i'(参数类型字符 +:+ 返回类型),i=int、l/n=64 位、s/a/w=字符串、f/d=浮点、p=指针、c=回调、v=void、C=cdecl 标记。- Windows 32 位(winapi.pas)
:手搓。倒序 for遍历参数,按类型把值塞进ret,然后 内联汇编push ret压栈,最后CWinAPI(hfun)()直接调用(winapi.pas:167-260)。回调则是VirtualAlloc出可执行内存,手写机器码 thunk:B8 xx(mov eax, obj)+E8 rel(call)+C2 n(ret n)(winapi.pas:266-300)。 - Linux/macOS/win64(lffi.pas,基于 libffi)
:正规得多—— ffi_prep_cif+ffi_call,回调用ffi_closure。还内置了运行时 ABI 探测:libffi ≥3.4 的FFI_UNIX64=2,老版本是 1,代码逐个试(lffi.pas:100-118);Darwin arm64 可变参数走ffi_prep_cif_var;Windows 上还会给裸 API 名自动补A后缀。
两个后端的存在直接解释了 CHANGELOG 里大量"只在某个平台显形"的 bug。
6.2 运行时 C 编译(lib-tcc.fun)
60 行 Fun 代码:
getapi出tcc_new/tcc_compile_string/tcc_relocate/tcc_get_symbol,把字符串 C 源码编译、重定位、取符号、再包成可调用对象。注意它给源码前缀拼了void _start(){}并用-nostdlib -nostdinc -O2,是一套很务实的"最小可用"配置。6.3 JIT:名不副实但很有用
// fun/lib/lib-jit.fun fun NewJit(code, args, names, name) if code =~ /^#!c\b/i then result = ccompile(code, flag: args, name: name); // TCC 路径 else result = Assembly(code: code, args: args, names: names).Load(); // 汇编路径它只做"源码形态分发",不做 Fun→机器码编译。 Fun 代码本身仍由树遍历解释。真正的价值在于:C/asm 产物能通过
@toCallback回调进 Fun 函数,实现"热点用 C 写、编排用 Fun 写"的同进程混编。6.4 内联汇编:自带一个 x86 汇编器
lib-asm-pro.fun(113 行)+ 数据表asm-list.fd(1 MB,压缩后asm-list.fd.2.gz93 KB):把mov eax, <test>这类助记符 查表翻译成机器码字节,其中<name>占位符替换为回调地址。工程细节相当扎实,明显是踩过坑的:
64 位下 @toCallback的 libffi closure 地址高于 4GB,表的 32 位mov r,%8装不下,于是运行时注入%16形式的mov r64, imm64(lib-asm-pro.fun:41-49);Linux/macOS 用 mmap取可执行内存而非VirtualAlloc;macOS arm64 必须MAP_JIT+pthread_jit_write_protect_np逐线程切换 W^X(lib-asm.fun:19-47);aarch64 需要 __clear_cache刷指令缓存,x86 不需要(_asm_flush钩子);ArchOk()会在错误的 CPU 上拒绝执行,避免 SIGILL。
lib-asm-a64.fun(641 行)是 aarch64 后端。可以说,"能在一门脚本里手写 x86 汇编并直接跑"是 Fun 最不可替代的能力。七、FD 与自举:设计上最漂亮的一环
FD 是 Fun 自有的类 Markdown 数据格式(缩进 + 空格 +
-,兼容 JSON 结构)。它的实现是真的自举:fun/lib/fd.bnf.fd—— 用 FD 写的 FD 文法; lib-bnf.fd( bnf.fd)—— 用 FD 写的"BNF 的 BNF",且自带@regex字段(BNF 编译出的正则);lib-bnf.fun(114 行)—— 把 BNF 文法展开成带命名组的正则; lib-fd.fun(113 行)—— 用正则匹配 + 缩进栈语义动作,200 行内完成 FD 解析。
// fun/lib/lib-fd.fun 核心:语义动作就是一个缩进栈 Key: (o,r) { ... while stak.peek().ind >= rt.ind do stak.pop(); end do; ... }同时内核里另有一份 Pascal 手写 FD/JSON 解析(
lib/libfd.inc328 行)供getJson(fd:true)高性能路径使用,标准库里还有 zlib/SSE 压缩分支。同一份格式,三套实现(Pascal 内核、Fun 自举、压缩变体),这是它"数据驱动 + 自描述"哲学的兑现。顺带一提,Fun 的官方文档体系也是这个思路:demo 文件既是文档、又是测试、又是教程。
八、标准库与跨平台约定
61 个模块、12.8k 行纯 Fun 代码,覆盖 JSON/YAML/XML/Base64/MD5/CStruct、集合与算法、字符串/正则/Unicode、文件/OS/进程/时间、网络(winsock/ajax/jsonrpc,这一轮又加了 HTTP 服务端
http-server与阻塞式客户端http-client,服务端目前 Linux/macOS 可用)、数据库(ORM 系列、ADO)、UI(甚至有一个用 Fun 写的记事本notepad--.fun)、异步。平台后端约定清晰:Windows 用
lib-x.fun,Unix 用lib-x-unx.fun(旧的-lnx变薄封装),共享逻辑放lib-x-base.fun;中性名use 'lib-x.fun'现在会自动探平台专属变体(macOS-darwin、Linux-lnx,再退-unx)。例如lib-asm在 Linux 走 libcmmap、macOS 走libSystem、Windows 走VirtualAlloc;lib-proc这一轮补了 macOS 后端(libproc+ mach VM,202 行),全部在 Fun 层用getapi切换——没有一行 C 胶水。九、工程实践:意外地认真
- 构建矩阵
:9 个目标(Win32/Win64/WinCE/Linux i386/x86_64/aarch64/macOS x86_64/aarch64),脚本集中在 src/prj/fun/,Windows 侧路径由setenv.bat统一。macOS 与 Linux-arm64 走 Zig 0.13 提供 clang/lld,无需目标 SDK。 - 第三方依赖刻意不入库
(PCRE 8、TCC 0.9.27、libffi),缺失时降级并打印提示而非中断构建——这个决策在 CI 上被反复验证。 - CI 五平台全绿
(Linux/macOS/Windows × x64 + Linux/macOS arm64),快照回归:59 个测试脚本对比 expected/下 93 份快照,平台差异用regression-skip-<platform>.txt声明,--update写平台专属快照;这一轮 Linux arm64 的lib-asm-a64.fun快照也从 skip 名单移出,真在 CI 上跑。 .gitattributes精确到每个扩展名的 eol,并把测试快照标为 verbatim逐字节比对(CHANGELOG 里记过一次"文本模式复制把 GBK 字节重编码导致永远对不上"的坑)。注释密度:内核约 8–18%, lffi.pas达 17.5%,且大量注释在解释"为什么"(ABI 版本差异、locale、堆归属),不是复述代码。
十、性能:仓库里其实有基准数字
更正一处:仓库并非"没有任何基准数字"。
fun/demos/benchmarks/下有两个文件把实跑输出直接贴在末尾的#* ... #注释块里,是可用的量化参考。计时口径已核实:
lib-time.fun的tick(-2)走QueryPerformanceCounter(Linux 走clock_gettime,libase.pas:613-620),get()换算结果为毫秒。测试统一跑 10⁶ 次循环,所以「ms 数 ≈ 每次迭代的 ns 数」,读起来很直观。10.1 逐操作开销(
test-speed.fun,1e6 次迭代)操作 耗时(ms) ≈ ns/次 空循环体( for i=1 to n,1e8 次)642 6.4 ⚠️ var a=147 47 1+2(常量) 78 78 1.1+2.1(常量浮点) 94 94 i1+i2(变量) 141 141 d1/d2156 156 "1"&"2"(连接) 267 267 "1"+"2"329 329 f()空匿名函数 94 94 f2(1,2)两参调用 157 157 1>2比较 172 172 n.f()对象方法 298 298 n .+ 2自定义运算符 375 375 10.2 四个可以从数据里读出的结论
整型与浮点几乎同价(78 vs 94 ns)。差值只有 ~16 ns,仅够覆盖 Variant 类型提升——没有任何按类型分派的 specialization,所有算术都走同一条 Variant 通路。这是"值模型就是 Variant"的直接代价。
完全没有常量折叠。
1+2(78) 与i1+i2(141) 相差 63 ns,而这 63 ns 纯粹是两次变量读取(≈31 ns/次:Trie 哈希查找 + Variant 拷贝 + 引用计数)。语法树里写的就是运行时做的,1+2每轮都会真的算一遍。空循环 6.4 ns:数字本身很漂亮,但不能当基线——它更重要的身份是对照组。
flow.pas:283-305的CForTo.run是特化快路径:上界emax只求值一次并存成裸fun.int,计数器用inc(vi, si)自增,每轮只往预构造好的TVarData里写整数。拆开看一轮的全部工作只有 7 件:vv.VInteger := vi→evar.assign()(就是p_val^ := val,一次 16 字节 Variant 拷贝,没有引用计数,因为没走clear())→all.run(env)(CRuns.run对空块是for i := 0 to -1,零迭代)→isExit读 →env.trace()(仅lastNode := n一次指针写)→inc→ while 条件。6.4 ns ≈ 32 cycles(@5GHz),摊到 7 个操作约 4.5 cycles/操作——热路径上没有原子操作、没有 Variant 类型分派、没有深层虚调用。对非 JIT 的树遍历解释器,这是相当干净的数字,FPC 生成的代码质量也确实是够的。
但真正有价值的是拿它做对照:同样是"往一个变量里写个整数",特化路径 6.4 ns,通用路径(
var a=1)47 ns —— 差 7.3 倍。 这 7.3 倍就是纯粹的"树遍历 + 通用分派税":CRuns.run→CRun.run虚调用 →CExp.calc→CExp.assign这条链,每多一层就多一份成本。所以 6.4 ns 不能外推到一般语句,真正的语句基线要按 47 ns 算;而 6.4 ns 的意义在于它给出了这条链的理论下限。(另一处嫌疑已排除:循环自身的evar与函数体内的var都会生成CVarRE,其assign先调stack.Push();但CFunStack.PushVar(base.pas:847-854)带avar.level < level-start守卫——循环里不再发生函数调用、level恒定,每个变量最多压level-start次就永久短路,摊到 1e6 次循环可忽略——故这 40 ns 确实来自分派,不是变量模型。)调用开销比算术贵,对象派发再翻一倍。函数调用于 94–157 ns,对象方法 298 ns(≈2 倍),自定义运算符 375 ns(≈2.5 倍)——与 §4.5 里"方法/getter/setter 统一走
.派发 + 构造期拷贝成员"的实现完全对得上。
10.2b 一个分层成本模型(把上面的数字串起来)
把基线逐层剥离,可以得到相当自洽的成本分解:
层 推导 成本 循环骨架 + 整数变量写(特化) test-speed空循环 6.4 ns 通用语句基线 var a=147 ns (增量 +40.6) 一次 Variant 二元运算 1+2(78) − var a=1(47)31 ns 一次变量读取 ( i1+i2141 −1+278) ÷ 231.5 ns 变量读取 ≈ Variant 加法 ≈ 31 ns。 这个相等未必是巧合,但别把它读成"瓶颈只有一个"——上表其实分出了两个性质完全不同的成本。
精度提示:
test-speed一共 18 个不同取值。直接排序作差,最小非零差是 1 ns(78/79、125/126、140/141、156/157、266/267 这五对就是 ±1 抖动);把这五对抖动合并后,相邻档位间距落在 14–16 ns,均值约 15.6 ns。也就是说这份数据的有效分辨率约 15 ns,上面 31 ns 的减法只有约 2 个量子、40.6 ns 约 2.6 个——量级可信,精确拆分不可信。10.2c 地板与天花板:两层成本,性质完全不同
分派税(天花板) Variant 税(地板) 在哪儿付 每条语句 付一次 每个值操作 付一次 实测 6.4 → 47 ns,差 40 ns 一次加法 31 ns 随什么增长 代码行数 / 语句数 计算量 能优化掉吗 能 ——纯工程问题 当前架构里不能 ——除非加特化层或改值模型 证据 CForTo特化后就是 6.4 ns 原生整数加法本应 <1 ns,这里 31 ns 关键的区分在于有没有下限:
- Variant 是地板(对这套解释器架构而言)。
一次整数加法在硬件上是亚纳秒级的,这里要 31 ns(约两个数量级)。这 31 ns 不来自分派,来自"值就是 Variant"这件事本身——16 字节容器 + 运行时类型判定 + RTL 辅助调用。没有特化的解释器(包括朴素字节码 VM)削不掉它;能削它的是内联缓存 / 类型特化——CPython 3.11+ 就是在保留统一对象模型和引用计数的前提下,靠特化把 int 加法压到几纳秒——或干脆改值模型(NaN-boxing / tagged union)。注意特化不必改语言的值模型,但在这套"AST 即存储、CExp 直接持有 Variant"的内核上,那基本等于重写执行器。 - 分派是天花板。
40 ns 的每语句开销是可以靠工程手段削的—— CForTo就是活证据:同样"往变量里写个整数",特化后 6.4 ns,通用路径 47 ns,7.3 倍纯属遍历与虚分派。
这正好解释了 Fun 的那个设计判断:为什么它选择"往 C/汇编下沉",而不是"优化解释器"。计算密集型代码撞的是地板,工程优化救不了;而
CForTo那 6.4 ns 恰好证明——只要绕开 Variant,Pascal 写的骨架本身并不慢。于是"Fun 慢不慢"这个问题要看你在哪一侧:做数据胶水(JSON/FD 变换、编排、调原生库)时你是天花板主导,每语句 47 ns ≈ 2100 万语句/秒,够用;做计算时你撞地板,每操作 31 ns,在这套没有特化的解释器里靠调参逃不掉(要逃得加特化层,那是重写执行器)——这时候成本最低的动作是换一层(C / 汇编),也就是能力阶梯。 语言把自己定位成"从 JSON 到机器码的胶水层",而不是"跑得快的脚本",与这套成本结构是自洽的。
10.3 能力阶梯的实际收益(
test-lib-asm-fun-9-2.fun)这个文件把同一件事(求 1..n 的和)用两种写法各跑一遍,且输出的是全精度浮点数(143.192199853098 / 23.3613999065171 ms),比
test-speed.fun那些整数值可信得多:写法 规模 实测 折算每次 Fun 原生 for i=1 to 1e6: s += i1e6 143.19 ms 143 ns 内联汇编 @sum1ton1e8 23.36 ms 0.234 ns 两处交叉验证:
test-speed的i1+i2= 141 ns 与这里的 143.19 ns 吻合在 1.5% 内,说明两份数据大概率来自同一台机器,可互相印证。把规模拉平:Fun 原生跑 1e8 次约需 14.3 秒,汇编 23.4 毫秒 —— 差距约 613 倍。(两个数字来自同机同日输出,613 是实测端到端比值而非外推;下面已核对 23.4 ms 对应 ~4.3×10⁹ 次/秒,在 5 GHz 级核心上属正常范围,不构成"样本可疑"的理由。)
(顺带一提,
@sum1ton的定义就在fun/lib/asm-list.fd第 3 行:33C033D203C183D2004975f8,即xor eax,eax / xor edx,edx / add eax,ecx / adc edx,0 / dec ecx / jnz -8—— 6 条指令的紧凑循环。23.36 ms 跑完 1e8 次意味着 4.3×10⁹ 次/秒,只有在 5GHz 级 CPU 上才做得到,说明这台机器相当快;跨机器比较时,"613 倍"这个比值比任何绝对数字都更稳。)10.4 怎么定位这个性能
简单算术吞吐 ≈ 7–13 M ops/s(视是否走变量),字符串连接掉到 ~3 M ops/s,方法调用 ~3.4 M ops/s。 量级上属于典型树遍历解释器:比 CPython 3.x 同题慢 2–4 倍(后者有专门的二元运算特化与栈式字节码),与早期 Tcl/Ruby 1.8 大致同一档。此项为跨语言估算,非实测。 因此 README 里那句"从 JSON 到机器码"应该这么理解:Fun 自己的代码始终在慢的那一侧,能力阶梯的意义是让你把热点搬到快的那一侧——代价是这段热点得用 C 或汇编手写,且只在 x86(Windows 32 位 ABI)上有现成表。
数据可信度提示:均为作者机器上的单次结果,未标注 CPU/系统,无方差;
test-speed的整数值呈现 ~15.6 ns 的量化台阶(相邻档位间距,可能是取整或计时分辨率),故 30 ns 以内的差异应视为噪声,只读趋势不读绝对位。十一、客观评价
亮点
- 内核与体积的比值惊人
:1.27 万行换来了 JSON/FD + 对象 + 闭包 + 正则 + FFI + C 编译 + 汇编的完整栈。 - 能力阶梯是真的打通的
:同一脚本内高层数据处理与裸机器码混写,且能双向回调,市面上极少有第二家。 - 标准库全用自己写
:既是能力自证,也让"改语言"和"改库"用同一套心智。 - 跨平台降级设计
:可选能力缺失不阻断构建,CI 探针报告实际能力。 - 自举与自描述
(FD/BNF)在工程上真的落地了,不是 PPT。
风险与短板
- 无安全边界
: getapi/TCC/汇编/任意地址读写全部开放,-safe拦截尚未实现(SAFETY.zh.md自述)。运行第三方 Fun 脚本 = 授予本机代码执行权。 - 性能上限来自值模型 +"无特化"执行器的组合,而非解释器骨架本身
:无字节码、无语言级 JIT、无常量折叠与类型特化。仓库自带基准(§10)显示两条独立的成本线——每语句分派税 ~40 ns(可优化)与每值操作 Variant 税 ~31 ns(在这套"AST 即存储 + Variant"的执行器里是结构性开销;要动它得加内联缓存/类型特化,或换值模型);同一计算比内联汇编慢约 613 倍。能力阶梯的收益是真的,但它不改变 Fun 自身代码的性能档位。 - 引用计数不回收环
,长生命周期的互引用结构会泄漏;作者已明确不做 COW,别名语义容易踩坑。 **整数 32 位 + 双编译器(Delphi/FPC)**带来持续的移植成本:CHANGELOG 里满是"只在 Windows/FPC 显形"的差异修复(locale 时间、代码页输出、PCRE 堆归属、libffi ABI 版本……)。 - 中文资产是 GBK 编码
( fun.ebnf、fd.fd、key_cn.ini、部分快照),在现代工具链里是持续的摩擦源。 - 文法改动门槛高
:需要重跑 TP Yacc/Lex 生成 .inc,新人贡献链路不顺。 - 社区近乎为零
:19 star、1 fork、2 贡献者,开源一个多月。生态、issue 响应、第三方模块都谈不上有保障。 - 许可证是双许可
:MIT + 商业条款(改内核闭源分发 / 嵌入 fun.dll到闭源商业产品需商业授权)。做产品前必须过一遍法务。
十二、适用 / 不适用
适合
需要嵌入式脚本、且脚本要能摸到本机 API / 驱动 / 芯片的 Windows 桌面或工业软件; 想把"数据编排"和"热点用 C/汇编"塞进同一个文件、同一进程的工具作者; 研究"小内核 + 大能力"语言实现、Pascal 现代实践、FFI/JIT 工程细节的学习者; 已有 Delphi/Pascal 资产,想找一门能复用 Pascal 模块的动态语言。
不适合
需要性能可预期的高吞吐服务(数值/Web 后端); 需要跑不可信第三方脚本的场景(无沙箱); 需要成熟生态、包管理、活跃社区的生产项目; 对闭源商业集成敏感、又不愿谈商业授权的团队。
上手路径(推荐顺序)
src/parse/bnf/yacc.y—— 一页看懂全部语法; src/core/base.pas→ core.pas—— 值模型与节点即存储;src/lib/host.pas( CLib责任链,187 行)→libase.pas尾部(内置函数注册)→lffi.pas—— 看懂"原生能力是怎么接进来的";fun/lib/lib-bnf.fun+ lib-fd.fun—— 自举的精华;fun/demos/benchmarks/—— 先看 test-speed.fun和test-lib-asm-fun-9-2.fun末尾的实跑输出(本报告 §10),再从test-jit-cb.fun看回调贯通、fractal-benchmark-{,asm,c}.fun看三档性能阶梯。
本报告首发于 2026-10-16,同日随上游
cf2d6a3刷新了一轮:标准库计数、网络章节与平台后端说明(新增 HTTP 服务端/客户端、lib-proc的 macOS 后端、平台中性文件名解析)。报告基于main分支源码逐文件阅读;未能在本环境中编译运行(缺少 FPC 工具链),故实现性结论来自代码与文档推断。§10 的性能数字取自仓库中随源码提交的实跑输出(test-speed.fun、test-lib-asm-fun-9-2.fun末尾注释块),非本次实测。- Windows 32 位(winapi.pas)

研报速递
发表评论
发表评论: