php-dextrin

Parser
in package

FinalYes

Hand-written recursive-descent `.dxn` parser -- the reverse of {@see Printer}, built directly on {@see Lexer}'s token stream.

A struct decodes against a compiled schema when $registry has one for that name (Reader::read(), which picks the schema by the struct's version, same as the binary decoder); with no registry, or no schema registered for that name, it decodes to an opaque DXNStruct. Same story for a non-built-in tag against the registry's own tag table -- falls back to DXNCustomTag -- never a hard failure, matching dxn/DXN.md §1.4's "opaque tagged value, not an error" contract.

Table of Contents

Constants

BUILT_IN_TAGS  : mixed = ['ordered' => true, 'sorted-set' => true, 'arra...
REGEX_FLAGS  : mixed = ['i', 'm', 's', 'u', 'x', 'f', 'r']
A regex's flag letters, as a header may give them (DXN.md §1.2).

Properties

$pos  : int
$recording  : array<int, SyntaxNode>|null
The syntax tree being recorded, when one was asked for ({@see self::parseTree()}): a stack of open nodes, the document at its bottom.
$registry  : Registry|null
$tokens  : array<int, Token>

Methods

parse()  : mixed
parseTree()  : Token>}
`$text` parsed the way {@see self::parse()} parses it -- the same parser, the same errors -- with the syntax tree beside the value.
__construct()  : mixed
advance()  : Token
advanceAnd()  : mixed
Advances past the current token (already matched by the caller) and returns `$value` -- for literals with no token property worth extracting.
at()  : bool
Reads `$this->pos`, mutable parser state -- PHPStan otherwise assumes a "pure" method called twice with the same argument returns the same result both times, which doesn't hold here once something between the two calls (typically `next()`) advances the cursor.
begin()  : void
Opens a node of `$kind` at the next token -- or at token `$first` -- when a tree is being recorded.
buildMap()  : array<int|string, mixed>|DXNMap
dispatchTag()  : mixed
end()  : void
Closes the innermost open node at the last token consumed, under its parent.
isDiscard()  : bool
keyAllowed()  : void
Refuses `$key` as a map key (DXN.md §1.6) unless it may be one, placed at `$offset`, where the key was written -- as the reference places it. Checked here rather than once the map is built, where nothing says which entry it was.
mark()  : void
Notes on the innermost open node that the last token consumed is its `$field`.
materializeStruct()  : mixed
next()  : Token
parseArrayTag()  : array<int, mixed>
parseAt()  : mixed
`@{...}` (set), or `@identifier value` (tag_form), or `@regex(flags) value` -- a header carries a regex's flags, as `%Name(3)` carries a version, and stands on no other tag (DXN.md §1.2). Discard is already stripped in {@see self::parseValue()}.
parseBitsTag()  : DXNBits|DXNBytes
`@bits {"..." n}` (DXN.md §1.4): base64 and a bit count; a whole number of bytes reads as bytes, which is what it is.
parseBracketedItems()  : array<int, mixed>
parseBytesTag()  : DXNBytes
parseCharToken()  : DXNChar
parseDocument()  : mixed
Top-level entry: optional header, then exactly one value.
parseEntries()  : array<int, array{0: mixed, 1: mixed}>
`{map_entry*}`, ordered pairs -- used by both `map_lit` and `struct_keyed`. The colon-shorthand key (`MAP_KEY` token) is keyword-typed per the grammar (`name: value` and `:name => value` are the exact same value), so it's wrapped as {@see DXNKeyword} here just like an explicit `:name` would be -- callers (map vs struct) each reduce that down to whatever shape they actually need.
parseFloatToken()  : float
parseKeywordToken()  : DXNKeyword
parseList()  : DXNList
parseOrderedTag()  : DXNOrderedMap
parsePercent()  : mixed
`%{...}` (map) or `%Name{...}`/`%Name[...]` (struct), the struct optionally carrying the version it was written with: `%Name(3){...}`.
parsePositionalBody()  : array{0: list, 1: list}
`[items... entries...]`: a positional struct's items, then its extra fields as entries (DXNS.md §7). An item is a value not followed by `=>`; once an entry has begun, an item is a syntax error.
parseRationalToken()  : DXNRational
parseRegexTag()  : DXNRegex
`@regex "pattern"`, its flags the header's letters, each once (DXN.md §1.2). A pattern PCRE cannot compile is no regex: `syntax`, as the reference refuses one Erlang's `:re` cannot. `f` (firstline) has no PHP modifier, so the pattern is compiled without it.
parseSet()  : DXNSet
parseSortedSetTag()  : DXNSortedSet
parseStringValue()  : string
parseStructBody()  : mixed
The rest of a struct after its `%`: its name, its version, and its keyed or positional body. `$start` is where its `%` is, for its errors.
parseStructVersion()  : int|null
The `(3)` of `%Name(3){...}` -- the schema version a struct was written with (DXNS.md §8), a positive integer -- or `null` when there is none.
parseTuple()  : DXNTuple
parseValue()  : mixed
`value = {skip}, value_body` -- `skip` here is only ever a discard; comments never reach the token stream.
parseValueBody()  : mixed
One value, read off the tokens.
parseValueToken()  : mixed
The value `$tok` begins.
peek()  : Token
rewrapAsEntry()  : void
Makes the value just recorded, starting at token `$first`, the key of a new entry: a positional struct's item turns out to be an extra field's key once `=>` follows it.
skipDiscards()  : void
tryConsumeHeader()  : void

Constants

BUILT_IN_TAGS

private mixed BUILT_IN_TAGS = ['ordered' => true, 'sorted-set' => true, 'array' => true, 'uuid' => true, 'objectid' => true, 'ip' => true, 'uri' => true, 'bytes' => true, 'bits' => true, 'date' => true, 'time' => true, 'datetime' => true, 'local-datetime' => true, 'duration' => true, 'regex' => true]

REGEX_FLAGS

A regex's flag letters, as a header may give them (DXN.md §1.2).

private mixed REGEX_FLAGS = ['i', 'm', 's', 'u', 'x', 'f', 'r']

Properties

$recording

The syntax tree being recorded, when one was asked for ({@see self::parseTree()}): a stack of open nodes, the document at its bottom.

private array<int, SyntaxNode>|null $recording

Methods

parse()

public static parse(string $text[, Registry|null $registry = null ]) : mixed
Parameters
$text : string
$registry : Registry|null = null

parseTree()

`$text` parsed the way {@see self::parse()} parses it -- the same parser, the same errors -- with the syntax tree beside the value.

public static parseTree(string $text[, Registry|null $registry = null ]) : Token>}

What Document::parse() reads with.

Parameters
$text : string
$registry : Registry|null = null
Return values
Token>}

__construct()

private __construct(array<int, Token> $tokens, Registry|null $registry[, bool $record = false ]) : mixed
Parameters
$tokens : array<int, Token>
$registry : Registry|null
$record : bool = false

advanceAnd()

Advances past the current token (already matched by the caller) and returns `$value` -- for literals with no token property worth extracting.

private advanceAnd(mixed $value) : mixed
Parameters
$value : mixed

at()

Reads `$this->pos`, mutable parser state -- PHPStan otherwise assumes a "pure" method called twice with the same argument returns the same result both times, which doesn't hold here once something between the two calls (typically `next()`) advances the cursor.

private at(string $type) : bool
Parameters
$type : string
Tags
phpstan-impure
Return values
bool

begin()

Opens a node of `$kind` at the next token -- or at token `$first` -- when a tree is being recorded.

private begin(string $kind[, int|null $first = null ]) : void
Parameters
$kind : string
$first : int|null = null

buildMap()

private buildMap(array<int, array{0: mixed, 1: mixed}> $pairs) : array<int|string, mixed>|DXNMap
Parameters
$pairs : array<int, array{0: mixed, 1: mixed}>
Return values
array<int|string, mixed>|DXNMap

dispatchTag()

private dispatchTag(string $name, int $offset) : mixed
Parameters
$name : string
$offset : int

end()

Closes the innermost open node at the last token consumed, under its parent.

private end() : void

isDiscard()

private isDiscard() : bool
Return values
bool

keyAllowed()

Refuses `$key` as a map key (DXN.md §1.6) unless it may be one, placed at `$offset`, where the key was written -- as the reference places it. Checked here rather than once the map is built, where nothing says which entry it was.

private keyAllowed(mixed $key, int $offset) : void
Parameters
$key : mixed
$offset : int

mark()

Notes on the innermost open node that the last token consumed is its `$field`.

private mark(string $field) : void
Parameters
$field : string

materializeStruct()

private materializeStruct(string $name, DXNStruct $struct, int $start) : mixed
Parameters
$name : string
$struct : DXNStruct
$start : int

next()

private next(string $type) : Token
Parameters
$type : string
Return values
Token

parseArrayTag()

private parseArrayTag(int $offset) : array<int, mixed>
Parameters
$offset : int
Return values
array<int, mixed>

parseAt()

`@{...}` (set), or `@identifier value` (tag_form), or `@regex(flags) value` -- a header carries a regex's flags, as `%Name(3)` carries a version, and stands on no other tag (DXN.md §1.2). Discard is already stripped in {@see self::parseValue()}.

private parseAt() : mixed

parseBitsTag()

`@bits {"..." n}` (DXN.md §1.4): base64 and a bit count; a whole number of bytes reads as bytes, which is what it is.

private parseBitsTag(int $offset) : DXNBits|DXNBytes
Parameters
$offset : int
Return values
DXNBits|DXNBytes

parseBracketedItems()

private parseBracketedItems(string $kind, string $open, string $close[, int|null $first = null ]) : array<int, mixed>
Parameters
$kind : string
$open : string
$close : string
$first : int|null = null
Return values
array<int, mixed>

parseBytesTag()

private parseBytesTag(string $name, int $offset) : DXNBytes
Parameters
$name : string
$offset : int
Return values
DXNBytes

parseDocument()

Top-level entry: optional header, then exactly one value.

private parseDocument() : mixed

parseEntries()

`{map_entry*}`, ordered pairs -- used by both `map_lit` and `struct_keyed`. The colon-shorthand key (`MAP_KEY` token) is keyword-typed per the grammar (`name: value` and `:name => value` are the exact same value), so it's wrapped as {@see DXNKeyword} here just like an explicit `:name` would be -- callers (map vs struct) each reduce that down to whatever shape they actually need.

private parseEntries(bool $ownNode) : array<int, array{0: mixed, 1: mixed}>
Parameters
$ownNode : bool
Return values
array<int, array{0: mixed, 1: mixed}>

parseFloatToken()

private parseFloatToken(string $text) : float
Parameters
$text : string
Return values
float

parsePercent()

`%{...}` (map) or `%Name{...}`/`%Name[...]` (struct), the struct optionally carrying the version it was written with: `%Name(3){...}`.

private parsePercent() : mixed

parsePositionalBody()

`[items... entries...]`: a positional struct's items, then its extra fields as entries (DXNS.md §7). An item is a value not followed by `=>`; once an entry has begun, an item is a syntax error.

private parsePositionalBody() : array{0: list, 1: list}
Return values
array{0: list, 1: list}

parseRegexTag()

`@regex "pattern"`, its flags the header's letters, each once (DXN.md §1.2). A pattern PCRE cannot compile is no regex: `syntax`, as the reference refuses one Erlang's `:re` cannot. `f` (firstline) has no PHP modifier, so the pattern is compiled without it.

private parseRegexTag(string $flags, int $offset) : DXNRegex
Parameters
$flags : string
$offset : int
Tags
throws
InvalidArgumentException

for a flag given twice, an unknown one, or a pattern that is no regex.

Return values
DXNRegex

parseSet()

private parseSet(int $at) : DXNSet
Parameters
$at : int

the index of the set's @, already consumed.

Return values
DXNSet

parseStringValue()

private parseStringValue(string $tagName, int $offset) : string
Parameters
$tagName : string
$offset : int
Return values
string

parseStructBody()

The rest of a struct after its `%`: its name, its version, and its keyed or positional body. `$start` is where its `%` is, for its errors.

private parseStructBody(int $start) : mixed
Parameters
$start : int

parseStructVersion()

The `(3)` of `%Name(3){...}` -- the schema version a struct was written with (DXNS.md §8), a positive integer -- or `null` when there is none.

private parseStructVersion() : int|null
Return values
int|null

parseValue()

`value = {skip}, value_body` -- `skip` here is only ever a discard; comments never reach the token stream.

private parseValue() : mixed

parseValueBody()

One value, read off the tokens.

private parseValueBody() : mixed

A value written well that is not one -- an impossible date, a uuid that is not one, an unknown escape -- is text that does not parse: the \InvalidArgumentException the value's own constructor throws becomes a syntax error at the token the value began with, as the reference answers. One that names its own category (a map key not allowed) and any other error pass through as they are.

parseValueToken()

The value `$tok` begins.

private parseValueToken(Token $tok) : mixed
Parameters
$tok : Token

rewrapAsEntry()

Makes the value just recorded, starting at token `$first`, the key of a new entry: a positional struct's item turns out to be an extra field's key once `=>` follows it.

private rewrapAsEntry(int $first) : void
Parameters
$first : int

skipDiscards()

private skipDiscards() : void

tryConsumeHeader()

private tryConsumeHeader() : void
On this page

Search results