Spintax engines

One spintax syntax, separate engines for JavaScript, PHP, Python, Object Pascal and .NET. Each is an independent implementation, not a port of the others, and every one of them is 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, five 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: five engines implement it, and you pick by the runtime you already have.

RuntimePackageInstallLicense
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 Pascalthe spintax-win repositorygit clone (no package registry)MIT
.NET (netstandard2.0, net472)Spintax.Coredotnet add package Spintax.CoreMIT

All of them 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 five 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/core is where new fixtures are written; the PHP, Python, Pascal and .NET 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, the Pascal binary or the .NET automation host 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 family — 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 others, 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: 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 254 of the corpus's 258 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.

.NET — Spintax.Core

The fifth engine, and the newest (NuGet, 2026-08-22): an independent implementation in C#, MIT, zero dependencies, built as netstandard2.0 and net472 from one source, so it runs on .NET Framework 4.7.2+ and on every .NET since. It exists because the .NET packages named for spintax implement the flat {a|b} dialect of the 2010s; this one implements the superset, and it is tested against the shared corpus rather than against its own expectations — all 258 cases pass on both targets, the four kind:rng ones included, driven through the engine's RNG seam.

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

The surface is deliberately flat — a static Engine with Render, Validate, Extract, Analyze, Neutralize; strings, dictionaries and small option objects, no Task<T>, no mutable static state — because its first home is the kind of automation host that loads a dll by name and compiles snippets against it, ZennoPoster-style. That shape is what lets dozens of threads render with one seed and get identical bytes. Two calls the other engines do not have: Combinations counts the distinct texts a template can produce by walking the tree ({a|a} is 2; pass the variables and it counts for that one row of data), and MaxLength bounds the longest render. Rendering is lenient and post-processed by default; values in Context are re-parsed as templates, so data from a table goes through Engine.Neutralize first, as everywhere in the family. Source: the spintax-dotnet repository; package: NuGet.

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.

JavaScriptPHPPythonPascal.NET
Reproducible outputseedinjected selectorseedinjected TSpRngSeed
Post-processing defaultonoff (opt-in arg)onoff (zeroed record)on
Reusable ASTyesyesyesyesno (flat surface by design)
Stable diagnostic codeyesnononoyes
Exact variant countnonononoCombinations
Deep guide on this siteyesyesreporeporepo

What is gated across all five 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 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.