Spintax 引擎家族

一套 spintax 语法,各自独立的引擎——JavaScript、PHP、Python、Object Pascal 和 .NET。每一个都是独立实现而非互相移植,而且全都由同一套 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
.NET(netstandard2.0、net472)Spintax.Coredotnet add package Spintax.CoreMIT

全都是零依赖。GPL-2.0 的 WordPress 插件内嵌了 PHP 引擎,并在其上叠加编辑器、缓存和字段绑定;上表的包则是引擎本身,对你的模板放在哪里不做任何假设。

不是移植——是被同一套语料约束的独立实现

“五门语言、同一套语法”说起来容易,守住很难。两个手写解析器,只要其中一个修了另一个没见过的边界情况,立刻就会分道扬镳。把这个家族拴在一起的不是共享代码——共享的几乎没有——而是共享的 golden corpus:一组语言无关的用例,每条都是一个输入模板,加上它必须产出的精确输出、诊断或提取结果。

每个引擎的测试套件都加载同一套语料并据此断言。一个在 TypeScript 里通过、在 Pascal 里失败的用例,是 Pascal 的 bug,在发布前就被抓住——而不是用户在生产环境里发现的“差异”。语料是机器校验的一致性,不是 README 里的承诺。

  • 语料与参考引擎同住。新用例写在 @spintax/core 里;PHP、Python、Pascal 和 .NET 的套件读的是完全相同的 JSON。
  • 语义被锁定,随机性没有。用例钉住的是构件的含义——哪些选项合法、复数如何一致、条件选中什么。它们刻意不钉随机抽取:带 seed 的输出在同一引擎内可复现,而跨引擎的随机序列一致不是目标。那些用例属于引擎私有,被有意跳过。

实际的好处:你可以对着本站的演练场写模板——它跑的是 JavaScript 引擎——然后放心地相信,生产环境里渲染它的 PHP 任务、Python 脚本、Pascal 可执行文件或 .NET 自动化宿主会以同样的方式读它。

按运行时挑选

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。它实现了完整超集,通过语料 258 个用例中的 254 个,跳过的四个是引擎私有的 RNG 断言。同一份源码在 UTF-16 的 Object Pascal 编译器下也能不加修改地编译;这份可移植性是“顺带保持”的:受支持的副作用,不是第二个平台。

.NET — Spintax.Core

第五个引擎,也是最新的一个(NuGet,2026-08-22):C# 的独立实现,MIT,零依赖,从同一份源码构建出 netstandard2.0net472,因此既能跑在 .NET Framework 4.7.2+ 上,也能跑在之后的每一个 .NET 上。它之所以存在,是因为 .NET 上以 spintax 命名的那些包实现的都是 2010 年代的扁平 {a|b} 方言;这个引擎实现的是超集,而且是拿共享语料来检验,而不是拿自己的预期——258 个用例在两个目标上全部通过,包括四个 kind:rng 用例,它们通过引擎的 RNG 接缝驱动。

dotnet add package Spintax.Core
using Spintax.Core;

Engine.Render("{Hello|Hi} there!", new RenderOptions { Seed = "42" }); // same seed, same bytes
foreach (var d in Engine.Validate("{a|b"))
    Console.WriteLine($"{d.Severity} {d.Line}:{d.Column} [{d.Code}] {d.Message}");
// Error 1:1 [bracket.unclosed] Unclosed '{'.
Engine.Combinations("{a|b} {c|d|e}"); // 6

API 表面刻意做得很扁——一个静态的 Engine,带 RenderValidateExtractAnalyzeNeutralize;字符串、字典和小的选项对象,没有 Task<T>,没有可变的静态状态——因为它的第一个家是 ZennoPoster 这类自动化宿主:按名字加载 dll,再针对它编译代码片段。正是这种形状让几十个线程用同一个 seed 渲染出完全相同的字节。两个其他引擎没有的调用:Combinations 通过遍历语法树来数一个模板能产生多少个不同的文本({a|a} 算 2;传入变量就按那一行数据来数),MaxLength 给出最长渲染的上界。渲染是宽容的,默认带后处理;Context 里的值会被当作模板重新解析,所以来自表格的数据要先过一遍 Engine.Neutralize,和家族里其他成员一样。源码:spintax-dotnet 仓库;包:NuGet

共享什么,差在哪里

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

JavaScriptPHPPythonPascal.NET
可复现输出seed注入选择器seed注入 TSpRngSeed
后处理默认值关(可选参数)关(零值记录)
可复用 AST无(刻意的扁平表面)
稳定诊断代码
精确变体计数Combinations
本站深度指南仓库仓库仓库

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

全都 MIT,所以能组合

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