Spintax 引擎家族
一套 spintax 语法,四个各自独立的引擎——JavaScript、PHP、Python 和 Object Pascal。每一个都是独立实现而非互相移植,而且四个都由同一套 golden fixtures 语料共同约束,所以同一个模板无论跑在哪里,渲染结果都一致。MIT 许可,零依赖,链路里没有任何外部服务。
一套语法,四个运行时
本站文档的 spintax 语法是大多数工具止步的扁平 {a|b|c} 的超集:它加入了排列、有作用域的变量、按值分支的条件、按 locale 的复数一致、include 和一道后处理工序。这套超集不绑定任何一门语言:四个引擎实现了它,你按自己已有的运行时来挑。
| 运行时 | 包 | 安装 | 许可 |
|---|---|---|---|
| JavaScript / TypeScript | @spintax/core | npm install @spintax/core | MIT |
| PHP 8.0+ | spintax/core | composer require spintax/core | MIT |
| Python 3.10+ | spintax-core | pip install spintax-core | MIT |
| Object Pascal / Free Pascal | spintax-win 仓库 | git clone(无包注册表) | MIT |
四个都是零依赖。GPL-2.0 的 WordPress 插件内嵌了 PHP 引擎,并在其上叠加编辑器、缓存和字段绑定;上表的包则是引擎本身,对你的模板放在哪里不做任何假设。
不是移植——是被同一套语料约束的独立实现
“四门语言、同一套语法”说起来容易,守住很难。两个手写解析器,只要其中一个修了另一个没见过的边界情况,立刻就会分道扬镳。把这个家族拴在一起的不是共享代码——共享的几乎没有——而是共享的 golden corpus:一组语言无关的用例,每条都是一个输入模板,加上它必须产出的精确输出、诊断或提取结果。
每个引擎的测试套件都加载同一套语料并据此断言。一个在 TypeScript 里通过、在 Pascal 里失败的用例,是 Pascal 的 bug,在发布前就被抓住——而不是用户在生产环境里发现的“差异”。语料是机器校验的一致性,不是 README 里的承诺。
- 语料与参考引擎同住。新用例写在
@spintax/core里;PHP、Python 和 Pascal 的套件读的是完全相同的 JSON。 - 语义被锁定,随机性没有。用例钉住的是构件的含义——哪些选项合法、复数如何一致、条件选中什么。它们刻意不钉随机抽取:带 seed 的输出在同一引擎内可复现,而跨引擎的随机序列一致不是目标。那些用例属于引擎私有,被有意跳过。
实际的好处:你可以对着本站的演练场写模板——它跑的是 JavaScript 引擎——然后放心地相信,生产环境里渲染它的 PHP 任务、Python 脚本或 Pascal 可执行文件会以同样的方式读它。
按运行时挑选
JavaScript 和 TypeScript — @spintax/core
参考引擎,也是语料的家。运行时零依赖,ESM 优先兼发 CJS,在 Node 18+、Cloudflare Workers 和浏览器里不加修改直接运行——本站的演练场EN就是它驱动的。
import { render, validate, extract } from '@spintax/core';
render('{Hello|Hi} %name%!', { context: { name: 'Ada' }, seed: 42 });
// deterministic for a given seed; post-processed by default
一次 render() 调用跑完整条流水线。它带着四者中最丰富的工具层——来自 parse 的可复用 AST,外加 analyze 和 neutralize——而且它的诊断带有稳定、纳入一致性闸门的 code,演练场正是靠它把错误映射到翻译后的提示。API 细节见完整的 JavaScript 指南。
PHP — spintax/core
不绑定框架,PHP 8.0+,ext-mbstring,除此之外什么都不要。与其他三个不同,这个包不是独立重写:它是从 WordPress 插件自己的引擎中抽取出来的,抽取者就是版权持有者本人,改授 MIT,让任何 PHP 应用都能用,而插件保持 GPL。
use Spintax\Core\Pipeline;
$pipeline = new Pipeline();
echo $pipeline->render('{Hello|Hi} %name%!', ['name' => 'Ada']);
没有 seed 参数——确定性由你在构造流水线时注入自己的选择器——诊断也没有稳定的 code;校验器给出带行列的消息,宿主按结构分支。这个包带什么、刻意不带什么,见完整的 PHP 指南。
Python — spintax-core
Python 3.10+,被语料约束的独立实现。API 是 snake_case,读起来就是 Python 期望的样子:render(template, *, context=, seed=, post_process=)。
from spintax_core import render, validate, parse
render("{Hello|Hi} there!", seed=42) # same seed, same output
render("Hi %name%!", context={"name": "Sam"}) # "Hi Sam!"
源码与完整一致性说明:spintax-py 仓库。
Object Pascal / Free Pascal
第四个引擎,也是最新的一个:Object Pascal 的零依赖实现,用 Free Pascal 3.2.2+ 在 {$mode delphi} 下构建。它没有包注册表——克隆仓库,把单元加进你的程序。
uses Spintax;
var ctx: TSpContext;
begin
DefaultSystemCodePage := CP_UTF8; { declare UTF-8 once }
ctx := Default(TSpContext);
ctx.PostProcess := True;
SpRender('{Hello|Hi} there!', ctx);
end;
这里的确定性是一条接缝而不是一个 seed:让 ctx.Rng 保持 nil 得到非确定输出,或注入随附的策略之一——TFirstRng、TLastRng、TSequenceRng,或带种子的 TMulberry32Rng。它实现了完整超集,通过语料 172 个用例中的 168 个,跳过的四个是引擎私有的 RNG 断言。同一份源码在 UTF-16 的 Object Pascal 编译器下也能不加修改地编译;这份可移植性是“顺带保持”的:受支持的副作用,不是第二个平台。
共享什么,差在哪里
每个引擎渲染同一套语法,对任何合法模板都返回一个有效抽取。差别在人机工程——怎样够到确定性、核心之外带多少工具、诊断以什么形式返回。
| JavaScript | PHP | Python | Pascal | |
|---|---|---|---|---|
| 可复现输出 | seed | 注入选择器 | seed | 注入 TSpRng |
| 后处理默认值 | 开 | 关(可选参数) | 开 | 关(零值记录) |
| 可复用 AST | 有 | 有 | 有 | 有 |
| 稳定诊断代码 | 有 | 无 | 无 | 无 |
| 本站深度指南 | 有 | 有 | 仓库 | 仓库 |
四个引擎之间被闸门锁住的是结论——模板是否有效、构件渲染成什么——而不是解释问题的措辞,也不是具体的随机抽取。
全都 MIT,所以能组合
四个包都是 MIT 许可、零依赖。WordPress 插件保持 GPL-2.0,而 MIT 与 GPL 兼容,所以 GPL 插件可以使用 MIT 的 PHP 引擎,反过来则不行。一切都在本地渲染:没有外部服务,没有按次调用,没有数据离开你的运行时。