Compiler
in package
Compiles a parsed `.dxns` document (an ordinary decoded DXN value -- a `.dxns` file is valid `.dxn`, no new grammar; what makes it a *schema* document is purely the shape of the value it parses to: a `map` from name to type expression -- a bare PHP array in this port) into {@see Compiled} entries.
%schema}/%field} are recognized here directly, as DXNStruct instances named "schema"/"field" straight out of
Dextrin::decode() -- never looked up in a registry, the same way
built-in tags are recognized by name in the text/binary codecs
rather than registered. This sidesteps an obvious bootstrapping
question (doesn't parsing a .dxns file need a schema for its own
schema/field structs?) the same way JSON Schema's own vocabulary
is fixed tooling knowledge rather than itself validated against a
schema.
An entry whose value isn't a %schema} is a named type
instead: a reusable name for some combination of the fixed
type_expr vocabulary, e.g. PositiveInt: {:refine :integer {min: 1}} -- purely data, no PHP code, no registry callback, so any
conformant reader in any language resolves it the same way.
A struct's field names, and a document's own top-level entry names,
are always plain PHP strings by the time they reach this class --
Parser already reduces a %schema}'s
own keys -- its field names -- to strings on the way in (its
own structFieldName()), and the document's top-level map keys
were already coerced to PHP array keys by MapBuilder -- unlike the
JS port's own entryName()/normalizeKeywords() helpers, neither
is needed here.
Table of Contents
Constants
- ANNOTATION_KEYS : mixed = ['title', 'description', 'examples', 'read-only...
- BOUNDS : mixed = ['min', 'max', 'exclusive-min', 'exclusive-max']
- CONTENT_READERS : mixed = ['application/dxn', 'application/dxnb']
- What `content` reads a string as, by its media type: none, or one of these.
- COUNTS : mixed = ['min-length', 'max-length', 'min-bytes', 'max-...
- ENCODINGS : mixed = ['base64', 'base64url', 'base32', 'base16']
- FLAGS : mixed = ['finite', 'float32', 'unique', 'before-now', '...
- PRIMITIVES_ANY : mixed = 'any'
- REFINE_KEYS : mixed = ['min', 'max', 'exclusive-min', 'exclusive-max'...
- SIZED : mixed = ['int32' => [-2147483648, 2147483647], 'int64'...
- The sized integers' ranges; a bound past PHP's int is its digits.
Properties
- $names : array<string, string>
- The names of the document being compiled (see {@see self::compile()}): set for one compile, which PHP runs one at a time.
Methods
- compile() : Registry
- Compiles every entry of a decoded `.dxns` document into `$baseRegistry` -- a struct schema for each `%schema(N){}` entry, a named type_expr for every other entry. A refusal is a {@see SchemaCompileError} (category `schema/compile`), or a {@see \JOetjen\Dextrin\DXNError} of category `schema/duplicate-version` for a version the registry has loaded already (DXNS.md §8).
- __construct() : mixed
- allOf() : bool
- Whether `$arg` is a list every item of which `$test` takes. A list, `[...]`, as the reference asks: a native PHP list is DXN's `@array[...]` here, which it refuses.
- annotation() : string|null
- What the annotation `$key` takes when `$value` is not one, `null` when it is; a key that is none, or an enum value's annotations that are wrong, throw.
- annotations() : array<string, mixed>
- A doc's annotations by name, each of its kind (DXNS.md §1b), and each enum value's under `values` keyed by name as well. `$type` is what they annotate -- `null` for a whole schema or one enum value -- so `values` can be checked against an enum's literals.
- applies() : void
- Whether `$name` is a generic taking `$count` arguments.
- argument() : string|null
- What `$key` takes when `$arg` is not one, `null` when it is.
- arguments() : void
- Every argument the kind its key takes, in key order.
- caseFields() : array<int, string>
- casesNameFields() : void
- Every `{:case field ...}` written in `$pairs`' field types names one of `$fields` -- told from the source, so a named type the case reaches through is not looked into.
- compileAliases() : Registry
- Every named type in a document sees every other (design/ shapes-and-case.md): each is compiled with its siblings standing as `named`, and those are put in place once all are read -- so the order of a map's entries, which DXN does not give, never matters.
- compileDocument() : Registry
- compileEntry() : Compiled
- One `%schema(N){field: type, ...}` entry: the body is the fields, in the order they are written; the header is the version, 1 when there is none.
- compileField() : Field
- compileNamed() : TypeExpr
- A named type's definition: a generic's parameters and body, a shape carrying its own name, or a type.
- compileTypeExpr() : TypeExpr
- constraintNames() : array<string, mixed>
- The constraints keyed by their names; a key that is no name is an error, not a crash.
- contentReadable() : void
- `content` reads a DXN document: text, or `.dxnb` under application/dxnb, which a string holds only encoded.
- describe() : string
- expected() : string
- fullName() : string
- The name a written symbol stands for: itself where it is in scope already (a generic's parameter, a name known so), otherwise the full name this document's names give it.
- getField() : mixed
- guarded() : void
- Every cycle among `$compiled`'s named types passes a guard: a field of a shape, or a collection's elements, so that each step of a check reads a smaller part of the value. A cycle with none -- `A: {:nilable A}` -- describes no value and would never finish.
- hasField() : bool
- inspect() : string
- `$value` as DXN text, for a message; its PHP type when it has none.
- isForm() : bool
- Whether `$value` is a tuple headed by the keyword `$form`.
- isInteger() : bool
- An integer, a big one too, and not below zero when `$nonNegative`.
- isMap() : bool
- Whether `$value` is a map: a {@see DXNMap}, or a native array not shaped like a list (a native list is an `@array`); the empty one is both, and taken.
- isOffset() : bool
- `Z`, or `±HH:MM`.
- knownKeys() : void
- literalName() : string|null
- The name a keyword, symbol or string gives, or `null`.
- namedArg() : array<string, mixed>
- `$key`'s argument as a name, one of `$allowed` when they are given.
- notFormat10() : void
- Refuses a schema written in Dextrin 0.1's syntax -- `fields:
- pairsOf() : array<int, array{0: mixed, 1: mixed}>
- A map's pairs, each key as DXN has it.
- particle() : Particle
- particles() : array<int, Particle>
- A `{:list-items}`'s or a group's items: {@see Particle::one()} for a type, {@see Particle::repeat()} for a repeat, its max `null` when there is no upper bound.
- refineConstraints() : array<string, mixed>
- A refine's constraints by name, every key one DXNS.md §2 knows and every argument one it takes: a misspelt key would otherwise refuse every value, silently, and a bad argument fail at check time. The types ({@see TypeExpr::TYPE_KEYS}) are compiled here; `unique-by`, `format`, `resolution` and `encoding` become names.
- resolved() : TypeExpr
- `$type` with every `named` replaced by that named type; a reference back to a type being resolved closes a cycle, which {@see self::guarded()} has passed, and stays a reference by name.
- takesArguments() : string
- undoc() : mixed
- An entry's value without the `{:doc ...}` around it.
- unguardedCycle() : array<int, string>|null
- unguardedNames() : array<int, string>
- The named types `$type` refers to without a guard on the way.
- valueAnnotations() : null
- An enum's `values:`: each key one of its literals, each value a map of that value's own annotations, which hold no `values` of their own.
Constants
ANNOTATION_KEYS
private
mixed
ANNOTATION_KEYS
= ['title', 'description', 'examples', 'read-only', 'write-only', 'deprecated', 'meta', 'values']
BOUNDS
private
mixed
BOUNDS
= ['min', 'max', 'exclusive-min', 'exclusive-max']
CONTENT_READERS
What `content` reads a string as, by its media type: none, or one of these.
private
mixed
CONTENT_READERS
= ['application/dxn', 'application/dxnb']
COUNTS
private
mixed
COUNTS
= ['min-length', 'max-length', 'min-bytes', 'max-bytes', 'min-count', 'max-count', 'min-contains', 'max-contains', 'min-precision', 'max-precision']
ENCODINGS
private
mixed
ENCODINGS
= ['base64', 'base64url', 'base32', 'base16']
FLAGS
private
mixed
FLAGS
= ['finite', 'float32', 'unique', 'before-now', 'after-now', 'prefix']
PRIMITIVES_ANY
private
mixed
PRIMITIVES_ANY
= 'any'
REFINE_KEYS
private
mixed
REFINE_KEYS
= ['min', 'max', 'exclusive-min', 'exclusive-max', 'multiple-of', 'min-length', 'max-length', 'min-bytes', 'max-bytes', 'pattern', 'min-count', 'max-count', 'finite', 'float32', 'offsets', 'version', 'prefix', 'contains', 'min-contains', 'max-contains', 'unique', 'unique-by', 'min-precision', 'max-precision', 'min-exponent', 'max-exponent', 'bits', 'before-now', 'after-now', 'within', 'resolution', 'format', 'encoding', 'media-type', 'content', 'encodes', 'encodes-seq']
SIZED
The sized integers' ranges; a bound past PHP's int is its digits.
private
mixed
SIZED
= ['int32' => [-2147483648, 2147483647], 'int64' => [PHP_INT_MIN, PHP_INT_MAX], 'uint32' => [0, 4294967295], 'uint64' => [0, '18446744073709551615']]
Properties
$names
The names of the document being compiled (see {@see self::compile()}): set for one compile, which PHP runs one at a time.
private
static array<string, string>
$names
= []
Methods
compile()
Compiles every entry of a decoded `.dxns` document into `$baseRegistry` -- a struct schema for each `%schema(N){}` entry, a named type_expr for every other entry. A refusal is a {@see SchemaCompileError} (category `schema/compile`), or a {@see \JOetjen\Dextrin\DXNError} of category `schema/duplicate-version` for a version the registry has loaded already (DXNS.md §8).
public
static compile(mixed $schemaDoc, Registry $baseRegistry[, array<string, string> $names = [] ]) : Registry
Every named type in a document sees every other, in any order, and
may reach itself through a guard -- a field, or a collection's
elements (DXNS.md §5, Jan, 2026-10-20). $names maps an entry's
key to the name it is registered under -- a tree's full names
(DXNS.md §6, Layout); a bare symbol naming a sibling
resolves to it.
Parameters
- $schemaDoc : mixed
- $baseRegistry : Registry
- $names : array<string, string> = []
Return values
Registry__construct()
private
__construct() : mixed
allOf()
Whether `$arg` is a list every item of which `$test` takes. A list, `[...]`, as the reference asks: a native PHP list is DXN's `@array[...]` here, which it refuses.
private
static allOf(mixed $arg, callable(mixed): bool $test) : bool
Parameters
- $arg : mixed
- $test : callable(mixed): bool
Return values
boolannotation()
What the annotation `$key` takes when `$value` is not one, `null` when it is; a key that is none, or an enum value's annotations that are wrong, throw.
private
static annotation(string $key, mixed $value, TypeExpr|null $type) : string|null
Parameters
- $key : string
- $value : mixed
- $type : TypeExpr|null
Return values
string|nullannotations()
A doc's annotations by name, each of its kind (DXNS.md §1b), and each enum value's under `values` keyed by name as well. `$type` is what they annotate -- `null` for a whole schema or one enum value -- so `values` can be checked against an enum's literals.
private
static annotations(mixed $raw, TypeExpr|null $type) : array<string, mixed>
Parameters
- $raw : mixed
- $type : TypeExpr|null
Return values
array<string, mixed>applies()
Whether `$name` is a generic taking `$count` arguments.
private
static applies(string $name, int $count, array<string, TypeExpr> $known) : void
Parameters
- $name : string
- $count : int
- $known : array<string, TypeExpr>
argument()
What `$key` takes when `$arg` is not one, `null` when it is.
private
static argument(string $key, mixed $arg) : string|null
Parameters
- $key : string
- $arg : mixed
Return values
string|nullarguments()
Every argument the kind its key takes, in key order.
private
static arguments(array<string, mixed> $named) : void
Parameters
- $named : array<string, mixed>
caseFields()
private
static caseFields(mixed $value) : array<int, string>
Parameters
- $value : mixed
Return values
array<int, string>casesNameFields()
Every `{:case field ...}` written in `$pairs`' field types names one of `$fields` -- told from the source, so a named type the case reaches through is not looked into.
private
static casesNameFields(iterable<string|int, array{0: string, 1: mixed}> $pairs, array<int, Field> $fields) : void
Parameters
- $pairs : iterable<string|int, array{0: string, 1: mixed}>
- $fields : array<int, Field>
compileAliases()
Every named type in a document sees every other (design/ shapes-and-case.md): each is compiled with its siblings standing as `named`, and those are put in place once all are read -- so the order of a map's entries, which DXN does not give, never matters.
private
static compileAliases(array<int, array{0: string, 1: mixed}> $aliasEntries, Registry $registry) : Registry
Parameters
- $aliasEntries : array<int, array{0: string, 1: mixed}>
- $registry : Registry
Return values
RegistrycompileDocument()
private
static compileDocument(array<int|string, mixed> $schemaDoc, Registry $baseRegistry, array<string, string> $names) : Registry
Parameters
- $schemaDoc : array<int|string, mixed>
- $baseRegistry : Registry
- $names : array<string, string>
Return values
RegistrycompileEntry()
One `%schema(N){field: type, ...}` entry: the body is the fields, in the order they are written; the header is the version, 1 when there is none.
private
static compileEntry(mixed $entry, array<string, TypeExpr> $aliases) : Compiled
Parameters
- $entry : mixed
- $aliases : array<string, TypeExpr>
Return values
CompiledcompileField()
private
static compileField(string $key, mixed $value, array<string, TypeExpr> $aliases) : Field
Parameters
- $key : string
-
a field name as a struct's keyed pairs hold it.
- $value : mixed
- $aliases : array<string, TypeExpr>
Return values
FieldcompileNamed()
A named type's definition: a generic's parameters and body, a shape carrying its own name, or a type.
private
static compileNamed(string $name, mixed $raw, array<string, TypeExpr> $scope) : TypeExpr
Parameters
- $name : string
- $raw : mixed
- $scope : array<string, TypeExpr>
Return values
TypeExprcompileTypeExpr()
private
static compileTypeExpr(mixed $value, array<string, TypeExpr> $aliases) : TypeExpr
Parameters
- $value : mixed
- $aliases : array<string, TypeExpr>
Return values
TypeExprconstraintNames()
The constraints keyed by their names; a key that is no name is an error, not a crash.
private
static constraintNames(array<int|string, mixed>|DXNMap $constraints) : array<string, mixed>
Parameters
- $constraints : array<int|string, mixed>|DXNMap
Return values
array<string, mixed>contentReadable()
`content` reads a DXN document: text, or `.dxnb` under application/dxnb, which a string holds only encoded.
private
static contentReadable(array<string, mixed> $named) : void
Parameters
- $named : array<string, mixed>
describe()
private
static describe(mixed $value) : string
Parameters
- $value : mixed
Return values
stringexpected()
private
static expected(array<int, string>|null $allowed) : string
Parameters
- $allowed : array<int, string>|null
Return values
stringfullName()
The name a written symbol stands for: itself where it is in scope already (a generic's parameter, a name known so), otherwise the full name this document's names give it.
private
static fullName(string $written, array<string, TypeExpr> $aliases) : string
Parameters
- $written : string
- $aliases : array<string, TypeExpr>
Return values
stringgetField()
private
static getField(array<int, array{0: string, 1: mixed}> $pairs, string $key[, mixed $fallback = null ]) : mixed
Parameters
- $pairs : array<int, array{0: string, 1: mixed}>
- $key : string
- $fallback : mixed = null
guarded()
Every cycle among `$compiled`'s named types passes a guard: a field of a shape, or a collection's elements, so that each step of a check reads a smaller part of the value. A cycle with none -- `A: {:nilable A}` -- describes no value and would never finish.
private
static guarded(array<string, TypeExpr> $compiled) : void
Parameters
- $compiled : array<string, TypeExpr>
hasField()
private
static hasField(array<int, array{0: string, 1: mixed}> $pairs, string $key) : bool
Parameters
- $pairs : array<int, array{0: string, 1: mixed}>
- $key : string
Return values
boolinspect()
`$value` as DXN text, for a message; its PHP type when it has none.
private
static inspect(mixed $value) : string
Parameters
- $value : mixed
Return values
stringisForm()
Whether `$value` is a tuple headed by the keyword `$form`.
private
static isForm(mixed $value, string $form) : bool
Parameters
- $value : mixed
- $form : string
Return values
boolisInteger()
An integer, a big one too, and not below zero when `$nonNegative`.
private
static isInteger(mixed $arg, bool $nonNegative) : bool
Parameters
- $arg : mixed
- $nonNegative : bool
Return values
boolisMap()
Whether `$value` is a map: a {@see DXNMap}, or a native array not shaped like a list (a native list is an `@array`); the empty one is both, and taken.
private
static isMap(mixed $value) : bool
Parameters
- $value : mixed
Tags
Return values
boolisOffset()
`Z`, or `±HH:MM`.
private
static isOffset(mixed $text) : bool
Parameters
- $text : mixed
Return values
boolknownKeys()
private
static knownKeys(array<string, mixed> $named) : void
Parameters
- $named : array<string, mixed>
literalName()
The name a keyword, symbol or string gives, or `null`.
private
static literalName(mixed $value) : string|null
Parameters
- $value : mixed
Return values
string|nullnamedArg()
`$key`'s argument as a name, one of `$allowed` when they are given.
private
static namedArg(array<string, mixed> $named, string $key, array<int, string>|null $allowed) : array<string, mixed>
Parameters
- $named : array<string, mixed>
- $key : string
- $allowed : array<int, string>|null
Return values
array<string, mixed>notFormat10()
Refuses a schema written in Dextrin 0.1's syntax -- `fields:
private
static notFormat10(iterable<string|int, array{0: string, 1: mixed}> $pairs) : void
Parameters
- $pairs : iterable<string|int, array{0: string, 1: mixed}>
Tags
pairsOf()
A map's pairs, each key as DXN has it.
private
static pairsOf(array<int|string, mixed>|DXNMap $map) : array<int, array{0: mixed, 1: mixed}>
Parameters
- $map : array<int|string, mixed>|DXNMap
Return values
array<int, array{0: mixed, 1: mixed}>particle()
private
static particle(mixed $value, array<string, TypeExpr> $aliases) : Particle
Parameters
- $value : mixed
- $aliases : array<string, TypeExpr>
Return values
Particleparticles()
A `{:list-items}`'s or a group's items: {@see Particle::one()} for a type, {@see Particle::repeat()} for a repeat, its max `null` when there is no upper bound.
private
static particles(array<int, mixed> $values, array<string, TypeExpr> $aliases) : array<int, Particle>
Parameters
- $values : array<int, mixed>
- $aliases : array<string, TypeExpr>
Return values
array<int, Particle>refineConstraints()
A refine's constraints by name, every key one DXNS.md §2 knows and every argument one it takes: a misspelt key would otherwise refuse every value, silently, and a bad argument fail at check time. The types ({@see TypeExpr::TYPE_KEYS}) are compiled here; `unique-by`, `format`, `resolution` and `encoding` become names.
private
static refineConstraints(array<int|string, mixed>|DXNMap $constraints, array<string, TypeExpr> $aliases) : array<string, mixed>
Parameters
Return values
array<string, mixed>resolved()
`$type` with every `named` replaced by that named type; a reference back to a type being resolved closes a cycle, which {@see self::guarded()} has passed, and stays a reference by name.
private
static resolved(TypeExpr $type, array<string, TypeExpr> $known, array<int, string> $path) : TypeExpr
Parameters
Return values
TypeExprtakesArguments()
private
static takesArguments(string $name, TypeExpr $generic) : string
Parameters
- $name : string
- $generic : TypeExpr
Return values
stringundoc()
An entry's value without the `{:doc ...}` around it.
private
static undoc(mixed $value) : mixed
Parameters
- $value : mixed
unguardedCycle()
private
static unguardedCycle(string $name, array<string, array<int, string>> $edges, array<int, string> $path) : array<int, string>|null
Parameters
- $name : string
- $edges : array<string, array<int, string>>
- $path : array<int, string>
Return values
array<int, string>|nullunguardedNames()
The named types `$type` refers to without a guard on the way.
private
static unguardedNames(TypeExpr $type) : array<int, string>
Parameters
- $type : TypeExpr
Return values
array<int, string>valueAnnotations()
An enum's `values:`: each key one of its literals, each value a map of that value's own annotations, which hold no `values` of their own.
private
static valueAnnotations(array<int|string, mixed>|DXNMap $values, array<int, mixed> $literals) : null
Always null; a wrong one throws.
Parameters
- $values : array<int|string, mixed>|DXNMap
- $literals : array<int, mixed>