Spintax 引擎家族

一套 spintax 语法,四个各自独立的引擎——JavaScript、PHP、Python 和 Object Pascal。每一个都是独立实现而非互相移植,而且四个都由同一套 golden fixtures 语料共同约束,所以同一个模板无论跑在哪里,渲染结果都一致。MIT 许可,零依赖,链路里没有任何外部服务。

一套语法,四个运行时

本站文档的 spintax 语法是大多数工具止步的扁平 {a|b|c} 的超集:它加入了排列、有作用域的变量、按值分支的条件、按 locale 的复数一致、include 和一道后处理工序。这套超集不绑定任何一门语言:四个引擎实现了它,你按自己已有的运行时来挑。

运行时安装许可
JavaScript / TypeScript@spintax/corenpm install @spintax/coreMIT
PHP 8.0+spintax/corecomposer require spintax/coreMIT
Python 3.10+spintax-corepip install spintax-coreMIT
Object Pascal / Free Pascalspintax-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,外加 analyzeneutralize——而且它的诊断带有稳定、纳入一致性闸门的 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 得到非确定输出,或注入随附的策略之一——TFirstRngTLastRngTSequenceRng,或带种子的 TMulberry32Rng。它实现了完整超集,通过语料 172 个用例中的 168 个,跳过的四个是引擎私有的 RNG 断言。同一份源码在 UTF-16 的 Object Pascal 编译器下也能不加修改地编译;这份可移植性是“顺带保持”的:受支持的副作用,不是第二个平台。

共享什么,差在哪里

每个引擎渲染同一套语法,对任何合法模板都返回一个有效抽取。差别在人机工程——怎样够到确定性、核心之外带多少工具、诊断以什么形式返回。

JavaScriptPHPPythonPascal
可复现输出seed注入选择器seed注入 TSpRng
后处理默认值关(可选参数)关(零值记录)
可复用 AST
稳定诊断代码
本站深度指南仓库仓库

四个引擎之间被闸门锁住的是结论——模板是否有效、构件渲染成什么——而不是解释问题的措辞,也不是具体的随机抽取。

全都 MIT,所以能组合

四个包都是 MIT 许可、零依赖。WordPress 插件保持 GPL-2.0,而 MIT 与 GPL 兼容,所以 GPL 插件可以使用 MIT 的 PHP 引擎,反过来则不行。一切都在本地渲染:没有外部服务,没有按次调用,没有数据离开你的运行时。