Parser
in package
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
$pos
private
int
$pos
= 0
$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
$registry
private
Registry|null
$registry
$tokens read-only
private
array<int, Token>
$tokens
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
advance()
private
advance() : Token
Return values
TokenadvanceAnd()
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
Return values
boolbegin()
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>|DXNMapdispatchTag()
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
boolkeyAllowed()
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
TokenparseArrayTag()
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|DXNBytesparseBracketedItems()
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
DXNBytesparseCharToken()
private
parseCharToken() : DXNChar
Return values
DXNCharparseDocument()
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
floatparseKeywordToken()
private
parseKeywordToken() : DXNKeyword
Return values
DXNKeywordparseList()
private
parseList() : DXNList
Return values
DXNListparseOrderedTag()
private
parseOrderedTag(int $offset) : DXNOrderedMap
Parameters
- $offset : int
Return values
DXNOrderedMapparsePercent()
`%{...}` (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: listparseRationalToken()
private
parseRationalToken(string $text) : DXNRational
Parameters
- $text : string
Return values
DXNRationalparseRegexTag()
`@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
Return values
DXNRegexparseSet()
private
parseSet(int $at) : DXNSet
Parameters
- $at : int
-
the index of the set's
@, already consumed.
Return values
DXNSetparseSortedSetTag()
private
parseSortedSetTag(int $offset) : DXNSortedSet
Parameters
- $offset : int
Return values
DXNSortedSetparseStringValue()
private
parseStringValue(string $tagName, int $offset) : string
Parameters
- $tagName : string
- $offset : int
Return values
stringparseStructBody()
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|nullparseTuple()
private
parseTuple() : DXNTuple
Return values
DXNTupleparseValue()
`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
peek()
private
peek() : Token
Return values
TokenrewrapAsEntry()
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