php-dextrin

Document
in package

FinalYes

A `.dxn` document with everything it holds: its value, and every comment, blank line and comma around it (`design/comment-retention.md` in `dextrin`), mirroring `Dextrin.Document`.

Dextrin::decode() keeps only the value, which is right for data and wrong for a file people annotate: decoding, changing one entry and encoding again deletes every comment. A document keeps the syntax tree instead -- every token, with the trivia between them -- and the value beside it. It is immutable: every edit answers a new document.

$doc = Document::parse($source);
$doc->toValue();   // what decode() returns
$doc->render();    // the source, byte for byte

One parser. The tree is recorded by the same parser decode() runs (Parser::parseTree()), so a document parses exactly when decode() would, and refuses with the same errors.

Edits -- self::replace(), self::delete(), self::insert() -- name what they change by a path from the value: a map's key as it decodes (new DXNKeyword('deps')), a struct's field name ('x'), a list's, tuple's or set's index. Each splices the source at the edited node and parses the result again, so every byte it does not touch is rendered as it was.

A comment belongs to an entry, by lines alone: a block starting on an entry's own line is its trailing block, however many lines it runs to; every other block of comment lines is above the next entry, and a blank line separates blocks. A deleted entry takes its trailing block and the blocks above it; a replaced value keeps every comment around it; an inserted entry goes above the comments of the entry it lands before.

self::update() turns a new value into these edits, leaving what is equal alone, and refuses by default where guessing which edit was meant would lose a comment.

Table of Contents

Properties

$source  : string
$tokens  : array<string|int, mixed>
$tree  : SyntaxNode
$registry  : Registry|null
$strict  : bool|null
$value  : mixed

Methods

delete()  : self
This document without the entry at `$path` -- a map's key and value, a list's item -- and without the comments that belong to it.
entries()  : array<int, SyntaxNode>
format()  : string
This document laid out the way the pretty printer lays out a value, with every comment kept beside the entry it belongs to. Layout only: every token is written as it was. `$indent` is the spaces per level, `$width` the columns a nested collection's one line must fit in.
insert()  : self
This document with `$value` added to the collection at `$path`: before the item at `$where` in a list, tuple or set (`-1` for the end), or under the new key `$where` in a map or a struct, after its last entry. A key already there is refused; that is {@see self::replace()}'s.
items()  : array<int, SyntaxNode>
parse()  : self
Parses `$source`, keeping everything in it. `$registry` and `$strict` are {@see Dextrin::decode()}'s and mean the same; so do the errors.
render()  : string
This document as `.dxn` text: its source, byte for byte, when nothing was edited.
replace()  : self
This document with the value at `$path` replaced by `$value`, written as {@see Dextrin::encode()} writes it -- over lines at the indentation it replaces when it needs more than one. Every comment around it stays.
span()  : array{0: int, 1: int}
A node's `[from, to)` byte range in the source.
toValue()  : mixed
The value this document holds: what {@see Dextrin::decode()} gives for its source.
update()  : array{0: self, 1: list}
This document holding `$value`, by the fewest edits that make it, and those edits. Lists compare item by item along their longest common subsequence. A deleted item followed by an inserted one is ambiguous -- a replace keeps the deleted one's comments, a delete and an insert drop them -- and `$onAmbiguity` decides: `'safe'` reads a replace where the deleted item owns no comment and refuses where it does; `'guess'` always reads a replace; `'error'` refuses every one.
__construct()  : mixed
aboveStart()  : int
Where an entry's above blocks start: the comment lines straight above it, blank lines between blocks allowed, up to a line of code -- less a topmost block that continues the previous entry's trailing comment with no blank line between.
afterNewline()  : int
aloneAfter()  : bool
Whether nothing but whitespace, commas and a comment follows `$at` on its line.
aloneBefore()  : bool
Whether nothing but whitespace and commas precedes `$at` on its line.
blockEnd()  : int
blocksOf()  : array<int, array<int, array{0: int, 1: int}>>
Comments, top to bottom, as blocks: a blank line ends one.
bodyNode()  : SyntaxNode
changesBetween()  : array<int, array{op: string, path: list, where?: mixed, value?: mixed}>
The edits that turn `$old`, at `$path`, into `$now`. Paths are where the edit applies once every edit before it has been made.
collectionAt()  : SyntaxNode
collectionNode()  : SyntaxNode|null
The collection or scalar a `value` node holds, past its discards; a struct's keyed or positional body.
comments()  : array<int, array{0: int, 1: int}>
Every comment in the document, as `[from, to)` -- the `#` to the end of its line, newline excluded -- found in the trivia between tokens, where a `#` can only begin one.
entryText()  : string
The text of a new map entry, `key: value`, as the pretty printer writes a one-entry map.
entryValue()  : SyntaxNode
inBytes()  : array<int, Token>
`$tokens` with their offsets in bytes. The lexer counts code points, and everything here slices the source in bytes -- `substr`, `strpos`, `preg` offsets -- so a character of more than one byte before an entry put every offset after it off, and formatting or editing such a document garbled it.
indentation()  : string
inserted()  : self
keyedChanges()  : array<int, array{op: string, path: list, where?: mixed, value?: mixed}>
A map's or a keyed struct's entries: the keys gone are deleted, the keys kept are compared, the keys new are inserted after the last.
keyOf()  : mixed
A map entry's key as it decodes: a shorthand `name:` is the keyword `:name` in a map and the field name `'name'` in a struct; an arrow entry's key is its value.
lineEnd()  : int
lineStart()  : int
listedChanges()  : array<int, array{op: string, path: list, where?: mixed, value?: mixed}>
A list's edit script, item by item: `$at` is where the next edit applies, `$was` where the item it looks at sat in the document as parsed -- which is where its comments are.
located()  : SyntaxNode>}
What a path leads to: the `value` node it names, the node standing for the whole entry, and the entries beside it.
mapPairs()  : array<int, array{0: mixed, 1: mixed}>|null
A map's pairs, whatever shape it decoded to, or null for anything that is not a map.
ownedSpan()  : array{0: int, 1: int}
The bytes an entry owns: the blocks above it and its trailing block, whole lines, when it stands on a line of its own; itself and the separator beside it when it shares its line.
ownsComment()  : bool
script()  : array<int, array{op: "eq", count: int, items: list, inserted: list}|array{op: "change", count: int, items: list, inserted: list}>
`$old` into `$now` as runs along their longest common subsequence: an equal run, or a deleted run with whatever was inserted in its place (possibly nothing), or an inserted run alone.
spliced()  : self
This document with `[$from, $to)` of its text replaced by `$text`, parsed again with the options it was parsed with.
stepInto()  : SyntaxNode>}|null
trailingEnd()  : int
The end of an entry's trailing block -- the comment on its own line and every comment line straight after it -- or of its own line.
written()  : string
`$value` as `.dxn` text, its continuation lines at `$indent`.

Properties

$tokens read-only

public array<string|int, mixed> $tokens

$strict read-only

private bool|null $strict

Methods

delete()

This document without the entry at `$path` -- a map's key and value, a list's item -- and without the comments that belong to it.

public delete(array<int, mixed> $path) : self
Parameters
$path : array<int, mixed>
Tags
throws
DocumentError

when $path leads nowhere.

Return values
self

format()

This document laid out the way the pretty printer lays out a value, with every comment kept beside the entry it belongs to. Layout only: every token is written as it was. `$indent` is the spaces per level, `$width` the columns a nested collection's one line must fit in.

public format([int $indent = 2 ][, int $width = 80 ]) : string
Parameters
$indent : int = 2
$width : int = 80
Return values
string

insert()

This document with `$value` added to the collection at `$path`: before the item at `$where` in a list, tuple or set (`-1` for the end), or under the new key `$where` in a map or a struct, after its last entry. A key already there is refused; that is {@see self::replace()}'s.

public insert(array<int, mixed> $path, mixed $where, mixed $value) : self
Parameters
$path : array<int, mixed>
$where : mixed
$value : mixed
Tags
throws
DocumentError

when $path leads nowhere, to no collection, or $where is a key already there.

Return values
self

parse()

Parses `$source`, keeping everything in it. `$registry` and `$strict` are {@see Dextrin::decode()}'s and mean the same; so do the errors.

public static parse(string $source[, Registry|null $registry = null ][, bool|null $strict = null ]) : self
Parameters
$source : string
$registry : Registry|null = null
$strict : bool|null = null
Tags
throws
DXNError

when $source is not a document decode() accepts.

Return values
self

render()

This document as `.dxn` text: its source, byte for byte, when nothing was edited.

public render() : string
Return values
string

replace()

This document with the value at `$path` replaced by `$value`, written as {@see Dextrin::encode()} writes it -- over lines at the indentation it replaces when it needs more than one. Every comment around it stays.

public replace(array<int, mixed> $path, mixed $value) : self
Parameters
$path : array<int, mixed>
$value : mixed
Tags
throws
DocumentError

when $path leads nowhere.

Return values
self

span()

A node's `[from, to)` byte range in the source.

public span(SyntaxNode $node) : array{0: int, 1: int}
Parameters
$node : SyntaxNode
Return values
array{0: int, 1: int}

toValue()

The value this document holds: what {@see Dextrin::decode()} gives for its source.

public toValue() : mixed

update()

This document holding `$value`, by the fewest edits that make it, and those edits. Lists compare item by item along their longest common subsequence. A deleted item followed by an inserted one is ambiguous -- a replace keeps the deleted one's comments, a delete and an insert drop them -- and `$onAmbiguity` decides: `'safe'` reads a replace where the deleted item owns no comment and refuses where it does; `'guess'` always reads a replace; `'error'` refuses every one.

public update(mixed $value[, string $onAmbiguity = 'safe' ]) : array{0: self, 1: list}
Parameters
$value : mixed
$onAmbiguity : string = 'safe'
Tags
throws
DocumentError

when an ambiguity is refused.

Return values
array{0: self, 1: list}

__construct()

private __construct(string $source, SyntaxNode $tree, array<int, Token> $tokens, mixed $value, Registry|null $registry, bool|null $strict) : mixed
Parameters
$source : string
$tree : SyntaxNode
$tokens : array<int, Token>
$value : mixed
$registry : Registry|null
$strict : bool|null

aboveStart()

Where an entry's above blocks start: the comment lines straight above it, blank lines between blocks allowed, up to a line of code -- less a topmost block that continues the previous entry's trailing comment with no blank line between.

private aboveStart(array<int, array{0: int, 1: int}> $all, int $at) : int
Parameters
$all : array<int, array{0: int, 1: int}>
$at : int
Return values
int

afterNewline()

private afterNewline(int $at) : int
Parameters
$at : int
Return values
int

aloneAfter()

Whether nothing but whitespace, commas and a comment follows `$at` on its line.

private aloneAfter(array<int, array{0: int, 1: int}> $all, int $at) : bool
Parameters
$all : array<int, array{0: int, 1: int}>
$at : int
Return values
bool

aloneBefore()

Whether nothing but whitespace and commas precedes `$at` on its line.

private aloneBefore(int $at) : bool
Parameters
$at : int
Return values
bool

blockEnd()

private blockEnd(array<int, array{0: int, 1: int}> $all, int $to) : int
Parameters
$all : array<int, array{0: int, 1: int}>
$to : int
Return values
int

blocksOf()

Comments, top to bottom, as blocks: a blank line ends one.

private blocksOf(array<int, array{0: int, 1: int}> $list) : array<int, array<int, array{0: int, 1: int}>>
Parameters
$list : array<int, array{0: int, 1: int}>
Return values
array<int, array<int, array{0: int, 1: int}>>

changesBetween()

The edits that turn `$old`, at `$path`, into `$now`. Paths are where the edit applies once every edit before it has been made.

private changesBetween(array<int, mixed> $path, mixed $old, mixed $now, string $mode) : array<int, array{op: string, path: list, where?: mixed, value?: mixed}>
Parameters
$path : array<int, mixed>
$old : mixed
$now : mixed
$mode : string
Return values
array<int, array{op: string, path: list, where?: mixed, value?: mixed}>

collectionNode()

The collection or scalar a `value` node holds, past its discards; a struct's keyed or positional body.

private static collectionNode(SyntaxNode $value) : SyntaxNode|null
Parameters
$value : SyntaxNode
Return values
SyntaxNode|null

comments()

Every comment in the document, as `[from, to)` -- the `#` to the end of its line, newline excluded -- found in the trivia between tokens, where a `#` can only begin one.

private comments() : array<int, array{0: int, 1: int}>
Return values
array<int, array{0: int, 1: int}>

entryText()

The text of a new map entry, `key: value`, as the pretty printer writes a one-entry map.

private entryText(SyntaxNode $collection, mixed $key, mixed $value) : string
Parameters
$collection : SyntaxNode
$key : mixed
$value : mixed
Return values
string

inBytes()

`$tokens` with their offsets in bytes. The lexer counts code points, and everything here slices the source in bytes -- `substr`, `strpos`, `preg` offsets -- so a character of more than one byte before an entry put every offset after it off, and formatting or editing such a document garbled it.

private static inBytes(string $source, array<int, Token> $tokens) : array<int, Token>
Parameters
$source : string
$tokens : array<int, Token>
Return values
array<int, Token>

indentation()

private indentation(int $at) : string
Parameters
$at : int
Return values
string

inserted()

private inserted(SyntaxNode $collection, mixed $where, string $text) : self
Parameters
$collection : SyntaxNode
$where : mixed
$text : string
Return values
self

keyedChanges()

A map's or a keyed struct's entries: the keys gone are deleted, the keys kept are compared, the keys new are inserted after the last.

private keyedChanges(array<int, mixed> $path, array<int, array{0: mixed, 1: mixed}> $old, array<int, array{0: mixed, 1: mixed}> $now, string $mode) : array<int, array{op: string, path: list, where?: mixed, value?: mixed}>
Parameters
$path : array<int, mixed>
$old : array<int, array{0: mixed, 1: mixed}>
$now : array<int, array{0: mixed, 1: mixed}>
$mode : string
Return values
array<int, array{op: string, path: list, where?: mixed, value?: mixed}>

keyOf()

A map entry's key as it decodes: a shorthand `name:` is the keyword `:name` in a map and the field name `'name'` in a struct; an arrow entry's key is its value.

private keyOf(SyntaxNode $entry, string $kind) : mixed
Parameters
$entry : SyntaxNode
$kind : string

lineEnd()

private lineEnd(int $at) : int
Parameters
$at : int
Return values
int

lineStart()

private lineStart(int $at) : int
Parameters
$at : int
Return values
int

listedChanges()

A list's edit script, item by item: `$at` is where the next edit applies, `$was` where the item it looks at sat in the document as parsed -- which is where its comments are.

private listedChanges(array<int, mixed> $path, array<int, mixed> $old, array<int, mixed> $now, string $mode) : array<int, array{op: string, path: list, where?: mixed, value?: mixed}>
Parameters
$path : array<int, mixed>
$old : array<int, mixed>
$now : array<int, mixed>
$mode : string
Return values
array<int, array{op: string, path: list, where?: mixed, value?: mixed}>

located()

What a path leads to: the `value` node it names, the node standing for the whole entry, and the entries beside it.

private located(array<int, mixed> $path) : SyntaxNode>}
Parameters
$path : array<int, mixed>
Return values
SyntaxNode>}

mapPairs()

A map's pairs, whatever shape it decoded to, or null for anything that is not a map.

private static mapPairs(mixed $value) : array<int, array{0: mixed, 1: mixed}>|null
Parameters
$value : mixed
Return values
array<int, array{0: mixed, 1: mixed}>|null

ownedSpan()

The bytes an entry owns: the blocks above it and its trailing block, whole lines, when it stands on a line of its own; itself and the separator beside it when it shares its line.

private ownedSpan(SyntaxNode $entry, array<int, SyntaxNode> $siblings) : array{0: int, 1: int}
Parameters
$entry : SyntaxNode
$siblings : array<int, SyntaxNode>
Return values
array{0: int, 1: int}

ownsComment()

private ownsComment(array<int, mixed> $path) : bool
Parameters
$path : array<int, mixed>
Return values
bool

script()

`$old` into `$now` as runs along their longest common subsequence: an equal run, or a deleted run with whatever was inserted in its place (possibly nothing), or an inserted run alone.

private static script(array<int, mixed> $old, array<int, mixed> $now) : array<int, array{op: "eq", count: int, items: list, inserted: list}|array{op: "change", count: int, items: list, inserted: list}>
Parameters
$old : array<int, mixed>
$now : array<int, mixed>
Return values
array<int, array{op: "eq", count: int, items: list, inserted: list}|array{op: "change", count: int, items: list, inserted: list}>

spliced()

This document with `[$from, $to)` of its text replaced by `$text`, parsed again with the options it was parsed with.

private spliced(int $from, int $to, string $text) : self
Parameters
$from : int
$to : int
$text : string
Return values
self

stepInto()

private stepInto(SyntaxNode|null $node, mixed $step) : SyntaxNode>}|null
Parameters
$node : SyntaxNode|null
$step : mixed
Return values
SyntaxNode>}|null

trailingEnd()

The end of an entry's trailing block -- the comment on its own line and every comment line straight after it -- or of its own line.

private trailingEnd(array<int, array{0: int, 1: int}> $all, int $at) : int
Parameters
$all : array<int, array{0: int, 1: int}>
$at : int
Return values
int

written()

`$value` as `.dxn` text, its continuation lines at `$indent`.

private written(mixed $value, string $indent) : string
Parameters
$value : mixed
$indent : string
Return values
string
On this page

Search results