Spintax engines
One spintax syntax, four separate engines — for JavaScript, PHP, Python and Object Pascal. Each is an independent implementation, not a port of the others, and all four are held to the same corpus of golden fixtures so a template renders the same wherever you run it. MIT-licensed, zero dependencies, no external service in the loop.
One syntax, four runtimes
The spintax syntax documented on this site is a superset of the flat {a|b|c} that most tools stop at. It adds permutations, scoped variables, value-driven conditionals, locale-aware plural agreement, includes and a post-processing pass. That superset is not tied to one language: four engines implement it, and you pick by the runtime you already have.
| Runtime | Package | Install | License |
|---|---|---|---|
| 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 | the spintax-win repository | git clone (no package registry) | MIT |
All four are zero-dependency. The GPL-2.0 WordPress plugin embeds the PHP engine and adds an editor, caching and field bindings on top; the packages above are the engine on its own, with no assumptions about where your templates live.
Not ports — independent implementations held to one corpus
"The same syntax in four languages" is easy to claim and hard to keep true. Two hand-written parsers drift the moment one fixes an edge case the other has not seen. What holds this family together is not shared code — there is almost none — but a shared golden corpus: a set of language-neutral fixtures, each an input template plus the exact output, diagnostics or extraction it must produce.
Every engine's test suite loads that same corpus and asserts against it. A case that passes in TypeScript and fails in Pascal is a bug in Pascal, caught before release, not a difference users discover in production. The corpus is machine-checked parity, not a promise in a README.
- The corpus lives with the reference engine.
@spintax/coreis where new fixtures are written; the PHP, Python and Pascal suites consume the exact same JSON. - Semantics are gated, randomness is not. The fixtures pin what a construct means — which options are valid, how a plural agrees, what a conditional selects. They deliberately do not pin the random pick: seeded output is reproducible within one engine, and identical random sequences across engines is a non-goal. Those cases are engine-private and skipped on purpose.
The practical payoff: you can author a template against the playground on this site — which runs the JavaScript engine — and trust that the PHP job, the Python script or the Pascal binary that renders it in production will read it the same way.
Pick by runtime
JavaScript & TypeScript — @spintax/core
The reference engine, and the home of the corpus. Zero runtime dependencies, ESM-first with dual CJS, and it runs unchanged on Node 18+, Cloudflare Workers and in the browser — it is what powers the playground on this site.
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
One render() call runs the whole pipeline. It carries the richest tooling of the four — a reusable AST from parse, plus analyze and neutralize — and its diagnostics carry a stable, parity-gated code, which is what lets the playground map an error to a translated message. The full JavaScript guide covers the API surface in depth.
PHP — spintax/core
Framework-agnostic, PHP 8.0+, ext-mbstring, nothing else. Unlike the other three, this package is not an independent reimplementation: it is an extraction of the WordPress plugin's own engine by the same copyright holder, relicensed MIT so any PHP app can use it while the plugin stays GPL.
use Spintax\Core\Pipeline;
$pipeline = new Pipeline();
echo $pipeline->render('{Hello|Hi} %name%!', ['name' => 'Ada']);
There is no seed argument — you inject determinism by constructing the pipeline with your own selector — and no stable diagnostic code; the validator reports a message with a line and column, and a host branches on structure. The full PHP guide covers what the package does and deliberately does not carry.
Python — spintax-core
Python 3.10+, an independent implementation held to the corpus. The API is snake_case and reads the way Python expects: 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!"
Source and full parity notes: the spintax-py repository.
Object Pascal / Free Pascal
The fourth engine, and the newest: a zero-dependency implementation in Object Pascal, built with Free Pascal 3.2.2+ in {$mode delphi}. It has no package registry — you clone the repository and add the unit to your program.
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;
Determinism is a seam rather than a seed: leave ctx.Rng nil for non-deterministic output, or inject one of the shipped strategies — TFirstRng, TLastRng, TSequenceRng, or a seeded TMulberry32Rng. It implements the full superset and passes 168 of the corpus's 172 cases, the four skipped being the engine-private RNG assertions. The source also compiles unchanged under a UTF-16 Object Pascal compiler; that portability is kept, not maintained — a supported side effect, not a second platform.
What they share, where they differ
Every engine renders the same syntax and returns a valid pick for any well-formed template. Where they differ is ergonomics — how you reach for determinism, how much tooling ships around the core, and how diagnostics come back.
| JavaScript | PHP | Python | Pascal | |
|---|---|---|---|---|
| Reproducible output | seed | injected selector | seed | injected TSpRng |
| Post-processing default | on | off (opt-in arg) | on | off (zeroed record) |
| Reusable AST | yes | yes | yes | yes |
| Stable diagnostic code | yes | no | no | no |
| Deep guide on this site | yes | yes | repo | repo |
What is gated across all four is the verdict — whether a template is valid and what a construct renders to — not the wording used to explain a problem or the exact random pick.
All MIT, so they compose
The four packages are MIT-licensed and depend on nothing. The WordPress plugin stays GPL-2.0, and MIT is GPL-compatible, so the GPL plugin can consume the MIT PHP engine while the reverse would not hold. Everything renders locally: no external service, no per-render call, no data leaving your runtime.