A .dxn document with everything it holds: its value, and every
comment, blank line and comma around it (design/comment-retention.md).
Dextrin.decode/2 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
full-fidelity syntax tree instead -- every token, trivia included
(Ichor.Syntax) -- with the value as a projection of it:
{:ok, doc} = Dextrin.Document.parse(source)
Dextrin.Document.to_value(doc) # what Dextrin.decode/2 returns
Dextrin.Document.render(doc) # source, byte for byteOne grammar, parsed one way. The tree comes from the same
priv/grammar/dxn.aether as decode/2, through the lossless entry
point Ichor generates beside the fast one, and the same actions
build the value -- so a document parses exactly when decode/2
would, and refuses with the same errors.
An untouched node renders its own bytes. Preservation is a property of rendering, not something a printer has to get right.
Edits
replace/3, delete/2 and insert/4 name what they change by a
path from the document's value: a map's key as it decodes
(:deps, "x"), a struct's field name ("y"), a list's, tuple's or
set's index. Each splices the source text at the edited node and
parses the result again, so every byte it does not touch is rendered
as it was, and the edited document is checked by the same parser as
any other.
A comment belongs to an entry (design/comment-retention.md,
"Trivia ownership"), by lines alone: a block starting on an entry's
own line is that entry's 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. So a deleted entry takes its trailing
block and the blocks above it, and nobody else's; a replaced value
keeps every comment around it, even one it has made stale; an
inserted entry goes above the comments of the entry it lands before,
so they stay with that entry.
Updating to a new value
update/3 is a convenience over the edits, for a caller who has only
the new value: it compares it with the document's, leaves what is
equal alone -- 1.50 stays 1.50 when the new value is 1.5 -- and
answers the edits it made as a changeset. Lists are compared with
List.myers_difference/2. A deleted item followed by an inserted
one is ambiguous: a replace keeps the deleted item's comments, a
delete and an insert drop them. on_ambiguity: decides -- :safe
(the default) takes it as a replace where the deleted item owns no
comment, and refuses where it does; :guess always takes it as a
replace; :error refuses every such pair.
Summary
Types
Where a value sits in a document: one key, field name or index per level.
Why an edit was refused.
A parsed document: its syntax tree, the value it holds, and the options it was parsed with, which every edit parses again with.
Functions
doc without the entry at path -- a map's key and value, a list's
item -- and without the comments that belong to it: the blocks above
it and the block trailing it.
doc laid out the way the pretty printer lays out a value, with every
comment kept beside the entry it belongs to (Dextrin.Document.Formatter).
Layout only: every token is written as it was. :indent is the spaces
per level (2), :width the columns a nested collection's one line must
fit in (80).
doc with value added to the collection at path: before the item
at index in a list, tuple or set (-1 for the end), or under the new
key in a map or a struct, after its last entry. It is laid out like
the entries beside it. A key already there is refused; that is
replace/3's.
Parses source, keeping everything in it. opts are
Dextrin.decode/2's -- :registry, :trusted, :strict -- and mean
the same; so do the errors.
doc as .dxn text: its source, byte for byte, when nothing was edited.
doc with the value at path replaced by value, written as
Dextrin.encode/2 writes it -- laid out over lines at the indentation
it replaces when it needs more than one. Every comment around it stays.
The value doc holds: what Dextrin.decode/2 gives for its source.
doc holding value, by the fewest edits that make it, and those
edits. See "Updating to a new value" above for on_ambiguity:.
Types
@type change() :: {:replace, {path(), term()}} | {:delete, path()} | {:insert, {path(), integer() | term(), term()}}
An edit update/3 made, in the order it made them.
@type path() :: [term()]
Where a value sits in a document: one key, field name or index per level.
@type refusal() :: {:not_found, path()} | {:ambiguous, path()} | {:exists, path()} | {:not_a_collection, path()} | Dextrin.Error.t() | [Dextrin.Error.t()]
Why an edit was refused.
@type t() :: %Dextrin.Document{ opts: keyword(), tree: Ichor.Syntax.Node.t(), value: term() }
A parsed document: its syntax tree, the value it holds, and the options it was parsed with, which every edit parses again with.
Functions
doc without the entry at path -- a map's key and value, a list's
item -- and without the comments that belong to it: the blocks above
it and the block trailing it.
doc laid out the way the pretty printer lays out a value, with every
comment kept beside the entry it belongs to (Dextrin.Document.Formatter).
Layout only: every token is written as it was. :indent is the spaces
per level (2), :width the columns a nested collection's one line must
fit in (80).
doc with value added to the collection at path: before the item
at index in a list, tuple or set (-1 for the end), or under the new
key in a map or a struct, after its last entry. It is laid out like
the entries beside it. A key already there is refused; that is
replace/3's.
@spec parse(String.t(), keyword()) :: {:ok, t()} | {:error, Dextrin.Error.t() | [Dextrin.Error.t()]}
Parses source, keeping everything in it. opts are
Dextrin.decode/2's -- :registry, :trusted, :strict -- and mean
the same; so do the errors.
doc as .dxn text: its source, byte for byte, when nothing was edited.
doc with the value at path replaced by value, written as
Dextrin.encode/2 writes it -- laid out over lines at the indentation
it replaces when it needs more than one. Every comment around it stays.
The value doc holds: what Dextrin.decode/2 gives for its source.
doc holding value, by the fewest edits that make it, and those
edits. See "Updating to a new value" above for on_ambiguity:.