From 4b40bf174d8dfae544dcd47b1d17eeba96de0ff8 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Fri, 27 Oct 2023 00:46:27 +0200 Subject: [PATCH] Document Preserves crate --- _includes/cheatsheet-binary-plaintext.md | 33 +++ _includes/cheatsheet-binary.md | 33 --- _includes/cheatsheet-text-plaintext.md | 44 ++++ cheatsheet-plaintext.md | 11 + git-hooks/pre-commit | 24 ++- implementations/rust/Makefile | 3 + .../doc/what-is-preserves-schema.md | 16 ++ implementations/rust/preserves/README.md | 8 + .../doc/cheatsheet-binary-plaintext.md | 33 +++ .../doc/cheatsheet-text-plaintext.md | 44 ++++ .../rust/preserves/doc/what-is-preserves.md | 12 ++ implementations/rust/preserves/src/de.rs | 18 ++ implementations/rust/preserves/src/error.rs | 31 +++ implementations/rust/preserves/src/hex.rs | 20 ++ implementations/rust/preserves/src/lib.rs | 5 + implementations/rust/preserves/src/ser.rs | 11 + implementations/rust/preserves/src/set.rs | 19 ++ implementations/rust/preserves/src/symbol.rs | 21 ++ .../rust/preserves/src/value/boundary.rs | 2 + .../rust/preserves/src/value/de.rs | 9 + .../rust/preserves/src/value/domain.rs | 17 ++ .../rust/preserves/src/value/magic.rs | 9 + .../rust/preserves/src/value/merge.rs | 9 + .../rust/preserves/src/value/mod.rs | 9 + .../preserves/src/value/packed/constants.rs | 6 +- .../rust/preserves/src/value/packed/mod.rs | 12 ++ .../rust/preserves/src/value/packed/reader.rs | 6 + .../rust/preserves/src/value/packed/writer.rs | 14 ++ .../rust/preserves/src/value/repr.rs | 201 +++++++++++++++++- .../rust/preserves/src/value/ser.rs | 10 + .../preserves/src/value/signed_integer.rs | 17 +- .../rust/preserves/src/value/suspendable.rs | 2 + .../rust/preserves/src/value/text/mod.rs | 10 + .../rust/preserves/src/value/text/reader.rs | 7 + .../rust/preserves/src/value/text/writer.rs | 27 +++ 35 files changed, 707 insertions(+), 46 deletions(-) create mode 100644 _includes/cheatsheet-binary-plaintext.md create mode 100644 _includes/cheatsheet-text-plaintext.md create mode 100644 cheatsheet-plaintext.md create mode 100644 implementations/rust/preserves-schema/doc/what-is-preserves-schema.md create mode 100644 implementations/rust/preserves/README.md create mode 100644 implementations/rust/preserves/doc/cheatsheet-binary-plaintext.md create mode 100644 implementations/rust/preserves/doc/cheatsheet-text-plaintext.md create mode 100644 implementations/rust/preserves/doc/what-is-preserves.md diff --git a/_includes/cheatsheet-binary-plaintext.md b/_includes/cheatsheet-binary-plaintext.md new file mode 100644 index 0000000..a90cda0 --- /dev/null +++ b/_includes/cheatsheet-binary-plaintext.md @@ -0,0 +1,33 @@ +For a value `V`, we write `«V»` for the binary encoding of `V`. + +```text + «#f» = [0x80] + «#t» = [0x81] + + «@W V» = [0x85] ++ «W» ++ «V» + «#!V» = [0x86] ++ «V» + + «V» if V ∈ Float = [0x87, 0x04] ++ binary32(V) + «V» if V ∈ Double = [0x87, 0x08] ++ binary64(V) + + «V» if V ∈ SignedInteger = [0xB0] ++ varint(|intbytes(V)|) ++ intbytes(V) + «V» if V ∈ String = [0xB1] ++ varint(|utf8(V)|) ++ utf8(V) + «V» if V ∈ ByteString = [0xB2] ++ varint(|V|) ++ V + «V» if V ∈ Symbol = [0xB3] ++ varint(|utf8(V)|) ++ utf8(V) + + «» = [0xB4] ++ «L» ++ «F_1» ++...++ «F_m» ++ [0x84] + «[X_1...X_m]» = [0xB5] ++ «X_1» ++...++ «X_m» ++ [0x84] + «#{E_1...E_m}» = [0xB6] ++ «E_1» ++...++ «E_m» ++ [0x84] + «{K_1:V_1...K_m:V_m}» = [0xB7] ++ «K_1» ++ «V_1» ++...++ «K_m» ++ «V_m» ++ [0x84] + + varint(n) = [n] if n < 128 + [(n & 127) | 128] ++ varint(n >> 7) if n ≥ 128 + + intbytes(n) = the empty sequence if n = 0, otherwise signedBigEndian(n) + + signedBigEndian(n) = [n & 255] if -128 ≤ n ≤ 127 + signedBigEndian(n >> 8) ++ [n & 255] otherwise +``` + +The functions `binary32(F)` and `binary64(D)` yield big-endian 4- and +8-byte IEEE 754 binary representations of `F` and `D`, respectively. diff --git a/_includes/cheatsheet-binary.md b/_includes/cheatsheet-binary.md index 9abec85..876a0b9 100644 --- a/_includes/cheatsheet-binary.md +++ b/_includes/cheatsheet-binary.md @@ -51,36 +51,3 @@ division](https://en.wikipedia.org/wiki/Euclidean_division); that is, if *n* = *dq* + *r* and 0 ≤ *r* < |d|. --> - - diff --git a/_includes/cheatsheet-text-plaintext.md b/_includes/cheatsheet-text-plaintext.md new file mode 100644 index 0000000..6c3a1ff --- /dev/null +++ b/_includes/cheatsheet-text-plaintext.md @@ -0,0 +1,44 @@ +```text +Document := Value ws +Value := ws (Record | Collection | Atom | Embedded | Annotated) +Collection := Sequence | Dictionary | Set +Atom := Boolean | ByteString | String | QuotedSymbol | Symbol | Number +ws := (space | tab | cr | lf | `,`)* + +Record := `<` Value+ ws `>` +Sequence := `[` Value* ws `]` +Dictionary := `{` (Value ws `:` Value)* ws `}` +Set := `#{` Value* ws `}` + +Boolean := `#t` | `#f` +ByteString := `#"` binchar* `"` + | `#x"` (ws hex hex)* ws `"` + | `#[` (ws base64char)* ws `]` +String := `"` («any unicode scalar except `\` or `"`» | escaped | `\"`)* `"` +QuotedSymbol := `|` («any unicode scalar except `\` or `|`» | escaped | `\|`)* `|` +Symbol := (`A`..`Z` | `a`..`z` | `0`..`9` | sympunct | symuchar)+ +Number := Float | Double | SignedInteger +Float := flt (`f`|`F`) | `#xf"` (ws hex hex)4 ws `"` +Double := flt | `#xd"` (ws hex hex)8 ws `"` +SignedInteger := int + +Embedded := `#!` Value +Annotated := Annotation Value +Annotation := `@` Value | `;` «any unicode scalar except cr or lf»* (cr | lf) + +escaped := `\\` | `\/` | `\b` | `\f` | `\n` | `\r` | `\t` | `\u` hex hex hex hex +binescaped := `\\` | `\/` | `\b` | `\f` | `\n` | `\r` | `\t` | `\x` hex hex +binchar := «any scalar ≥32 and ≤126, except `\` or `"`» | binescaped | `\"` +base64char := `A`..`Z` | `a`..`z` | `0`..`9` | `+` | `/` | `-` | `_` | `=` +sympunct := `~` | `!` | `$` | `%` | `^` | `&` | `*` | `?` + | `_` | `=` | `+` | `-` | `/` | `.` +symuchar := «any scalar value ≥128 whose Unicode category is + Lu, Ll, Lt, Lm, Lo, Mn, Mc, Me, Nd, Nl, No, Pc, + Pd, Po, Sc, Sm, Sk, So, or Co» + +flt := int ( frac exp | frac | exp ) +int := (`-`|`+`) (`0`..`9`)+ +frac := `.` (`0`..`9`)+ +exp := (`e`|`E`) (`-`|`+`) (`0`..`9`)+ +hex := `A`..`F` | `a`..`f` | `0`..`9` +``` diff --git a/cheatsheet-plaintext.md b/cheatsheet-plaintext.md new file mode 100644 index 0000000..8cbe30c --- /dev/null +++ b/cheatsheet-plaintext.md @@ -0,0 +1,11 @@ +--- +no_site_title: true +title: "Preserves Quick Reference (Plaintext)" +--- + +Tony Garnock-Jones +{{ site.version_date }}. Version {{ site.version }}. + +{% include cheatsheet-binary-plaintext.md %} + +{% include cheatsheet-text-plaintext.md %} diff --git a/git-hooks/pre-commit b/git-hooks/pre-commit index 18720af..81ae488 100755 --- a/git-hooks/pre-commit +++ b/git-hooks/pre-commit @@ -3,6 +3,12 @@ set -e exec 1>&2 +COMMAND=cmp +if [ "$1" = "--fix" ]; +then + COMMAND=cp +fi + # https://gitlab.com/preserves/preserves/-/issues/30 # # So it turns out that Racket's git-checkout mechanism pays attention @@ -16,10 +22,14 @@ exec 1>&2 # Ensure that various copies of schema.prs, schema.bin, path.bin, # samples.pr and samples.bin are in fact identical. -cmp path/path.bin implementations/python/preserves/path.prb -cmp path/path.bin implementations/rust/preserves-path/path.bin -cmp schema/schema.bin implementations/python/preserves/schema.prb -cmp schema/schema.prs implementations/racket/preserves/preserves-schema/schema.prs -cmp tests/samples.bin implementations/python/tests/samples.bin -cmp tests/samples.pr implementations/python/tests/samples.pr -cmp tests/samples.pr implementations/racket/preserves/preserves/tests/samples.pr +${COMMAND} path/path.bin implementations/python/preserves/path.prb +${COMMAND} path/path.bin implementations/rust/preserves-path/path.bin +${COMMAND} schema/schema.bin implementations/python/preserves/schema.prb +${COMMAND} schema/schema.prs implementations/racket/preserves/preserves-schema/schema.prs +${COMMAND} tests/samples.bin implementations/python/tests/samples.bin +${COMMAND} tests/samples.pr implementations/python/tests/samples.pr +${COMMAND} tests/samples.pr implementations/racket/preserves/preserves/tests/samples.pr +${COMMAND} _includes/what-is-preserves.md implementations/rust/preserves/doc/what-is-preserves.md +${COMMAND} _includes/what-is-preserves-schema.md implementations/rust/preserves-schema/doc/what-is-preserves-schema.md +${COMMAND} _includes/cheatsheet-binary-plaintext.md implementations/rust/preserves/doc/cheatsheet-binary-plaintext.md +${COMMAND} _includes/cheatsheet-text-plaintext.md implementations/rust/preserves/doc/cheatsheet-text-plaintext.md diff --git a/implementations/rust/Makefile b/implementations/rust/Makefile index 0aa547f..4432efb 100644 --- a/implementations/rust/Makefile +++ b/implementations/rust/Makefile @@ -5,6 +5,9 @@ all: cargo build --all-targets +doc: + cargo doc --workspace + x86_64-binary: x86_64-binary-release x86_64-binary-release: diff --git a/implementations/rust/preserves-schema/doc/what-is-preserves-schema.md b/implementations/rust/preserves-schema/doc/what-is-preserves-schema.md new file mode 100644 index 0000000..20c47d0 --- /dev/null +++ b/implementations/rust/preserves-schema/doc/what-is-preserves-schema.md @@ -0,0 +1,16 @@ +A Preserves schema connects Preserves `Value`s to host-language data +structures. Each definition within a schema can be processed by a +compiler to produce + + - a simple host-language *type definition*; + + - a partial *parsing* function from `Value`s to instances of the + produced type; and + + - a total *serialization* function from instances of the type to + `Value`s. + +Every parsed `Value` retains enough information to always be able to +be serialized again, and every instance of a host-language data +structure contains, by construction, enough information to be +successfully serialized. diff --git a/implementations/rust/preserves/README.md b/implementations/rust/preserves/README.md new file mode 100644 index 0000000..179459c --- /dev/null +++ b/implementations/rust/preserves/README.md @@ -0,0 +1,8 @@ +# Preserves core: value representation, codecs, and Serde support + +This crate implements [Preserves](https://preserves.dev/) for Rust, including + + - serde support (modules [de], [ser], [symbol], [set]) + + - plain Preserves value support, plus [text][crate::value::text] and + [binary][crate::value::packed] codecs (the [value] module) diff --git a/implementations/rust/preserves/doc/cheatsheet-binary-plaintext.md b/implementations/rust/preserves/doc/cheatsheet-binary-plaintext.md new file mode 100644 index 0000000..a90cda0 --- /dev/null +++ b/implementations/rust/preserves/doc/cheatsheet-binary-plaintext.md @@ -0,0 +1,33 @@ +For a value `V`, we write `«V»` for the binary encoding of `V`. + +```text + «#f» = [0x80] + «#t» = [0x81] + + «@W V» = [0x85] ++ «W» ++ «V» + «#!V» = [0x86] ++ «V» + + «V» if V ∈ Float = [0x87, 0x04] ++ binary32(V) + «V» if V ∈ Double = [0x87, 0x08] ++ binary64(V) + + «V» if V ∈ SignedInteger = [0xB0] ++ varint(|intbytes(V)|) ++ intbytes(V) + «V» if V ∈ String = [0xB1] ++ varint(|utf8(V)|) ++ utf8(V) + «V» if V ∈ ByteString = [0xB2] ++ varint(|V|) ++ V + «V» if V ∈ Symbol = [0xB3] ++ varint(|utf8(V)|) ++ utf8(V) + + «» = [0xB4] ++ «L» ++ «F_1» ++...++ «F_m» ++ [0x84] + «[X_1...X_m]» = [0xB5] ++ «X_1» ++...++ «X_m» ++ [0x84] + «#{E_1...E_m}» = [0xB6] ++ «E_1» ++...++ «E_m» ++ [0x84] + «{K_1:V_1...K_m:V_m}» = [0xB7] ++ «K_1» ++ «V_1» ++...++ «K_m» ++ «V_m» ++ [0x84] + + varint(n) = [n] if n < 128 + [(n & 127) | 128] ++ varint(n >> 7) if n ≥ 128 + + intbytes(n) = the empty sequence if n = 0, otherwise signedBigEndian(n) + + signedBigEndian(n) = [n & 255] if -128 ≤ n ≤ 127 + signedBigEndian(n >> 8) ++ [n & 255] otherwise +``` + +The functions `binary32(F)` and `binary64(D)` yield big-endian 4- and +8-byte IEEE 754 binary representations of `F` and `D`, respectively. diff --git a/implementations/rust/preserves/doc/cheatsheet-text-plaintext.md b/implementations/rust/preserves/doc/cheatsheet-text-plaintext.md new file mode 100644 index 0000000..6c3a1ff --- /dev/null +++ b/implementations/rust/preserves/doc/cheatsheet-text-plaintext.md @@ -0,0 +1,44 @@ +```text +Document := Value ws +Value := ws (Record | Collection | Atom | Embedded | Annotated) +Collection := Sequence | Dictionary | Set +Atom := Boolean | ByteString | String | QuotedSymbol | Symbol | Number +ws := (space | tab | cr | lf | `,`)* + +Record := `<` Value+ ws `>` +Sequence := `[` Value* ws `]` +Dictionary := `{` (Value ws `:` Value)* ws `}` +Set := `#{` Value* ws `}` + +Boolean := `#t` | `#f` +ByteString := `#"` binchar* `"` + | `#x"` (ws hex hex)* ws `"` + | `#[` (ws base64char)* ws `]` +String := `"` («any unicode scalar except `\` or `"`» | escaped | `\"`)* `"` +QuotedSymbol := `|` («any unicode scalar except `\` or `|`» | escaped | `\|`)* `|` +Symbol := (`A`..`Z` | `a`..`z` | `0`..`9` | sympunct | symuchar)+ +Number := Float | Double | SignedInteger +Float := flt (`f`|`F`) | `#xf"` (ws hex hex)4 ws `"` +Double := flt | `#xd"` (ws hex hex)8 ws `"` +SignedInteger := int + +Embedded := `#!` Value +Annotated := Annotation Value +Annotation := `@` Value | `;` «any unicode scalar except cr or lf»* (cr | lf) + +escaped := `\\` | `\/` | `\b` | `\f` | `\n` | `\r` | `\t` | `\u` hex hex hex hex +binescaped := `\\` | `\/` | `\b` | `\f` | `\n` | `\r` | `\t` | `\x` hex hex +binchar := «any scalar ≥32 and ≤126, except `\` or `"`» | binescaped | `\"` +base64char := `A`..`Z` | `a`..`z` | `0`..`9` | `+` | `/` | `-` | `_` | `=` +sympunct := `~` | `!` | `$` | `%` | `^` | `&` | `*` | `?` + | `_` | `=` | `+` | `-` | `/` | `.` +symuchar := «any scalar value ≥128 whose Unicode category is + Lu, Ll, Lt, Lm, Lo, Mn, Mc, Me, Nd, Nl, No, Pc, + Pd, Po, Sc, Sm, Sk, So, or Co» + +flt := int ( frac exp | frac | exp ) +int := (`-`|`+`) (`0`..`9`)+ +frac := `.` (`0`..`9`)+ +exp := (`e`|`E`) (`-`|`+`) (`0`..`9`)+ +hex := `A`..`F` | `a`..`f` | `0`..`9` +``` diff --git a/implementations/rust/preserves/doc/what-is-preserves.md b/implementations/rust/preserves/doc/what-is-preserves.md new file mode 100644 index 0000000..3c8d1bc --- /dev/null +++ b/implementations/rust/preserves/doc/what-is-preserves.md @@ -0,0 +1,12 @@ +*Preserves* is a data model, with associated serialization formats. + +It supports *records* with user-defined *labels*, embedded +*references*, and the usual suite of atomic and compound data types, +including *binary* data as a distinct type from text strings. Its +*annotations* allow separation of data from metadata such as comments, +trace information, and provenance information. + +Preserves departs from many other data languages in defining how to +*compare* two values. Comparison is based on the data model, not on +syntax or on data structures of any particular implementation +language. diff --git a/implementations/rust/preserves/src/de.rs b/implementations/rust/preserves/src/de.rs index 5b3f51c..2209b2b 100644 --- a/implementations/rust/preserves/src/de.rs +++ b/implementations/rust/preserves/src/de.rs @@ -1,3 +1,5 @@ +//! Support for Serde deserialization of Preserves terms described by Rust data types. + use serde::de::{DeserializeSeed, EnumAccess, MapAccess, SeqAccess, VariantAccess, Visitor}; use serde::Deserialize; @@ -11,13 +13,21 @@ use super::value::{IOValue, IOValueDomainCodec, PackedReader, TextReader, ViaCod pub use super::error::Error; +/// A [std::result::Result] type including [Error], the Preserves Serde deserialization error +/// type, as its error. pub type Result = std::result::Result; +/// Serde deserializer for Preserves-encoded Rust data. Use [Deserializer::from_reader] to +/// construct instances, or [from_bytes]/[from_text]/[from_read]/[from_reader] etc to +/// deserialize single terms directly. pub struct Deserializer<'de, 'r, R: Reader<'de, IOValue>> { + /// The underlying Preserves [reader][crate::value::reader::Reader]. pub read: &'r mut R, phantom: PhantomData<&'de ()>, } +/// Deserialize a `T` from `bytes`, which must contain a Preserves [machine-oriented binary +/// syntax][crate::value::packed] term corresponding to the Serde serialization of a `T`. pub fn from_bytes<'de, T>(bytes: &'de [u8]) -> Result where T: Deserialize<'de>, @@ -28,6 +38,8 @@ where )) } +/// Deserialize a `T` from `text`, which must contain a Preserves [text +/// syntax][crate::value::text] term corresponding to the Serde serialization of a `T`. pub fn from_text<'de, T>(text: &'de str) -> Result where T: Deserialize<'de>, @@ -38,6 +50,8 @@ where )) } +/// Deserialize a `T` from `read`, which must yield a Preserves [machine-oriented binary +/// syntax][crate::value::packed] term corresponding to the Serde serialization of a `T`. pub fn from_read<'de, 'r, IOR: io::Read + io::Seek, T>(read: &'r mut IOR) -> Result where T: Deserialize<'de>, @@ -48,6 +62,8 @@ where )) } +/// Deserialize a `T` from `read`, which must yield a Preserves term corresponding to the Serde +/// serialization of a `T`. pub fn from_reader<'r, 'de, R: Reader<'de, IOValue>, T>(read: &'r mut R) -> Result where T: Deserialize<'de>, @@ -58,6 +74,7 @@ where } impl<'r, 'de, R: Reader<'de, IOValue>> Deserializer<'de, 'r, R> { + /// Construct a Deserializer from `read`, a Preserves [reader][crate::value::Reader]. pub fn from_reader(read: &'r mut R) -> Self { Deserializer { read, @@ -344,6 +361,7 @@ impl<'r, 'de, 'a, R: Reader<'de, IOValue>> serde::de::Deserializer<'de> } } +#[doc(hidden)] pub struct Seq<'de, 'r, 'a, R: Reader<'de, IOValue>> { b: B::Type, i: B::Item, diff --git a/implementations/rust/preserves/src/error.rs b/implementations/rust/preserves/src/error.rs index 4e92ac6..c67638a 100644 --- a/implementations/rust/preserves/src/error.rs +++ b/implementations/rust/preserves/src/error.rs @@ -1,27 +1,47 @@ +//! Serde and plain-Preserves codec errors. + use num::bigint::BigInt; use std::convert::From; use std::io; +/// Representation of parse, deserialization, and other conversion errors. #[derive(Debug)] pub enum Error { + /// Generic IO error. Io(io::Error), + /// Generic message for the user. Message(String), + /// Invalid unicode scalar `n` found during interpretation of a `` record + /// as a Rust `char`. InvalidUnicodeScalar(u32), + /// Preserves supports arbitrary integers; when these are converted to specific Rust + /// machine word types, sometimes they exceed the available range. NumberOutOfRange(BigInt), + /// Serde has limited support for deserializing free-form data; this error is signalled + /// when one of the limits is hit. CannotDeserializeAny, + /// Syntax error: missing closing delimiter (`)`, `]`, `}`, `>` in text syntax; `0x84` in binary syntax; etc.) MissingCloseDelimiter, + /// Signalled when an expected term is not present. MissingItem, + /// Signalled when what was received did not match expectations. Expected(ExpectedKind, Received), + #[doc(hidden)] // TODO remove this enum variant? It isn't used StreamingSerializationUnsupported, } +/// Used in [Error::Expected] to indicate what was received. #[derive(Debug)] pub enum Received { + #[doc(hidden)] // TODO remove this enum variant? It isn't used ReceivedSomethingElse, + /// Received a record with the given label symbol text. ReceivedRecordWithLabel(String), + /// Received some other value, described in the `String` ReceivedOtherValue(String), } +/// Used in [Error::Expected] to indicate what was expected. #[derive(Debug, PartialEq)] pub enum ExpectedKind { Boolean, @@ -35,7 +55,9 @@ pub enum ExpectedKind { ByteString, Symbol, + /// Expected a record, either of a specific arity (length) or of no specific arity Record(Option), + /// Expected a record with a symbol label with text `String`, perhaps of some specific arity SimpleRecord(String, Option), Sequence, Set, @@ -87,14 +109,17 @@ impl std::fmt::Display for Error { //--------------------------------------------------------------------------- +/// True iff `e` is `Error::Io` pub fn is_io_error(e: &Error) -> bool { matches!(e, Error::Io(_)) } +/// Produce the generic "end of file" error, `Error::Io(`[io_eof]`())` pub fn eof() -> Error { Error::Io(io_eof()) } +/// True iff `e` is an "end of file" error; see [is_eof_io_error] pub fn is_eof_error(e: &Error) -> bool { if let Error::Io(ioe) = e { is_eof_io_error(ioe) @@ -103,10 +128,12 @@ pub fn is_eof_error(e: &Error) -> bool { } } +/// Produce a syntax error bearing the message `s` pub fn syntax_error(s: &str) -> Error { Error::Io(io_syntax_error(s)) } +/// True iff `e` is a syntax error; see [is_syntax_io_error] pub fn is_syntax_error(e: &Error) -> bool { if let Error::Io(ioe) = e { is_syntax_io_error(ioe) @@ -117,18 +144,22 @@ pub fn is_syntax_error(e: &Error) -> bool { //--------------------------------------------------------------------------- +/// Produce an [io::Error] of [io::ErrorKind::UnexpectedEof]. pub fn io_eof() -> io::Error { io::Error::new(io::ErrorKind::UnexpectedEof, "EOF") } +/// True iff `e` is [io::ErrorKind::UnexpectedEof] pub fn is_eof_io_error(e: &io::Error) -> bool { matches!(e.kind(), io::ErrorKind::UnexpectedEof) } +/// Produce a syntax error ([io::ErrorKind::InvalidData]) bearing the message `s` pub fn io_syntax_error(s: &str) -> io::Error { io::Error::new(io::ErrorKind::InvalidData, s) } +/// True iff `e` is an [io::ErrorKind::InvalidData] (a syntax error) pub fn is_syntax_io_error(e: &io::Error) -> bool { matches!(e.kind(), io::ErrorKind::InvalidData) } diff --git a/implementations/rust/preserves/src/hex.rs b/implementations/rust/preserves/src/hex.rs index 4e171e6..94f1f76 100644 --- a/implementations/rust/preserves/src/hex.rs +++ b/implementations/rust/preserves/src/hex.rs @@ -1,19 +1,38 @@ +//! Utilities for producing and flexibly parsing strings containing hexadecimal binary data. + +/// Utility for parsing hex binary data from strings. pub enum HexParser { + /// "Liberal" parsing simply ignores characters that are not (case-insensitive) hex digits. Liberal, + /// "Whitespace allowed" parsing ignores whitespace, but fails a parse on anything other + /// than hex or whitespace. WhitespaceAllowed, + /// "Strict" parsing accepts only (case-insensitive) hex digits; no whitespace, no other + /// characters. Strict, } +/// Utility for formatting binary data as hex. pub enum HexFormatter { + /// Produces LF-separated lines with a maximum of `usize` hex digits in each line. Lines(usize), + /// Simply packs hex digits in as tightly as possible. Packed, } +/// Convert a number 0..15 to a hex digit [char]. +/// +/// # Panics +/// +/// Panics if given `v` outside the range 0..15 inclusive. +/// pub fn hexdigit(v: u8) -> char { char::from_digit(v as u32, 16).expect("hexadecimal digit value") } impl HexParser { + /// Decode `s` according to the given rules for `self`; see [HexParser]. + /// If the parse fails, yield `None`. pub fn decode(&self, s: &str) -> Option> { let mut result = Vec::new(); let mut buf: u8 = 0; @@ -49,6 +68,7 @@ impl HexParser { } impl HexFormatter { + /// Encode `bs` according to the given rules for `self; see [HexFormatter]. pub fn encode(&self, bs: &[u8]) -> String { match self { HexFormatter::Lines(max_line_length) => { diff --git a/implementations/rust/preserves/src/lib.rs b/implementations/rust/preserves/src/lib.rs index 68c5f50..045cdf2 100644 --- a/implementations/rust/preserves/src/lib.rs +++ b/implementations/rust/preserves/src/lib.rs @@ -1,3 +1,8 @@ +#![doc = concat!( + include_str!("../doc/what-is-preserves.md"), + include_str!("../README.md"), +)] + pub mod de; pub mod error; pub mod hex; diff --git a/implementations/rust/preserves/src/ser.rs b/implementations/rust/preserves/src/ser.rs index 6167ba8..da910f5 100644 --- a/implementations/rust/preserves/src/ser.rs +++ b/implementations/rust/preserves/src/ser.rs @@ -1,3 +1,5 @@ +//! Support for Serde serialization of Rust data types into Preserves terms. + use super::value::boundary as B; use super::value::writer::{CompoundWriter, Writer}; use super::value::IOValueDomainCodec; @@ -7,11 +9,16 @@ pub use super::error::Error; type Result = std::result::Result; #[derive(Debug)] +/// Serde serializer for Preserves-encoding Rust data. Construct via [Serializer::new], and use +/// with [serde::Serialize::serialize] methods. pub struct Serializer<'w, W: Writer> { + /// The underlying Preserves [writer][crate::value::writer::Writer]. pub write: &'w mut W, } impl<'w, W: Writer> Serializer<'w, W> { + /// Construct a new [Serializer] targetting the given + /// [writer][crate::value::writer::Writer]. pub fn new(write: &'w mut W) -> Self { Serializer { write } } @@ -22,6 +29,7 @@ enum SequenceVariant { Record(W::RecWriter), } +#[doc(hidden)] pub struct SerializeCompound<'a, 'w, W: Writer> { b: B::Type, i: B::Item, @@ -29,6 +37,7 @@ pub struct SerializeCompound<'a, 'w, W: Writer> { c: SequenceVariant, } +#[doc(hidden)] pub struct SerializeDictionary<'a, 'w, W: Writer> { b: B::Type, ser: &'a mut Serializer<'w, W>, @@ -442,6 +451,8 @@ impl<'a, 'w, W: Writer> serde::ser::SerializeSeq for SerializeCompound<'a, 'w, W } } +/// Convenience function for directly serializing a Serde-serializable `T` to the given +/// `write`, a Preserves [writer][crate::value::writer::Writer]. pub fn to_writer(write: &mut W, value: &T) -> Result<()> { Ok(value.serialize(&mut Serializer::new(write))?) } diff --git a/implementations/rust/preserves/src/set.rs b/implementations/rust/preserves/src/set.rs index 13bef7a..2692661 100644 --- a/implementations/rust/preserves/src/set.rs +++ b/implementations/rust/preserves/src/set.rs @@ -1,7 +1,25 @@ +//! Serde support for serializing Rust collections as Preserves sets. +//! +//! Serde doesn't include sets in its data model, so we do some somewhat awful tricks to force +//! things to come out the way we want them. +//! +//! # Example +//! +//! Annotate collection-valued fields that you want to (en|de)code as Preserves `Set`s with +//! `#[serde(with = "preserves::set")]`: +//! +//! ```rust +//! struct Example { +//! #[serde(with = "preserves::set")] +//! items: preserves::value::Set, +//! } +//! ``` + use crate::value::{self, to_value, IOValue, UnwrappedIOValue}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::iter::IntoIterator; +#[doc(hidden)] pub fn serialize(s: T, serializer: S) -> Result where S: Serializer, @@ -12,6 +30,7 @@ where UnwrappedIOValue::from(s).wrap().serialize(serializer) } +#[doc(hidden)] pub fn deserialize<'de, D, T>(deserializer: D) -> Result where D: Deserializer<'de>, diff --git a/implementations/rust/preserves/src/symbol.rs b/implementations/rust/preserves/src/symbol.rs index 61cd8ce..f7e5fb0 100644 --- a/implementations/rust/preserves/src/symbol.rs +++ b/implementations/rust/preserves/src/symbol.rs @@ -1,5 +1,24 @@ +//! Serde support for serializing Rust data as Preserves symbols. +//! +//! Serde doesn't include symbols in its data model, so we do some somewhat awful tricks to +//! force things to come out the way we want them. +//! +//! # Example +//! +//! Either use [Symbol] directly in your data types, or annotate [String]-valued fields that +//! you want to (en|de)code as Preserves `Symbol`s with `#[serde(with = "preserves::symbol")]`: +//! +//! ```rust +//! struct Example { +//! sym1: preserves::symbol::Symbol, +//! #[serde(with = "preserves::symbol")] +//! sym2: String, +//! } +//! ``` + use crate::value::{IOValue, NestedValue}; +/// Wrapper for a string to coerce its Preserves-serialization to `Symbol`. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] pub struct Symbol(pub String); @@ -26,6 +45,7 @@ impl<'de> serde::Deserialize<'de> for Symbol { } } +#[doc(hidden)] pub fn serialize(s: &str, serializer: S) -> Result where S: serde::Serializer, @@ -34,6 +54,7 @@ where Symbol(s.to_string()).serialize(serializer) } +#[doc(hidden)] pub fn deserialize<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de>, diff --git a/implementations/rust/preserves/src/value/boundary.rs b/implementations/rust/preserves/src/value/boundary.rs index 86e85cf..24f7f77 100644 --- a/implementations/rust/preserves/src/value/boundary.rs +++ b/implementations/rust/preserves/src/value/boundary.rs @@ -1,3 +1,5 @@ +#![doc(hidden)] + #[derive(Default, Clone, Debug)] pub struct Type { pub closing: Option, diff --git a/implementations/rust/preserves/src/value/de.rs b/implementations/rust/preserves/src/value/de.rs index adae205..c767751 100644 --- a/implementations/rust/preserves/src/value/de.rs +++ b/implementations/rust/preserves/src/value/de.rs @@ -1,3 +1,5 @@ +//! Support Serde deserialization of Rust data types from Preserves *values* (not syntax). + use crate::error::{Error, ExpectedKind, Received}; use crate::value::repr::{Double, Float}; use crate::value::{IOValue, Map, NestedValue, UnwrappedIOValue, Value}; @@ -7,10 +9,14 @@ use std::iter::Iterator; pub type Result = std::result::Result; +/// Serde deserializer for constructing Rust data from an in-memory Preserves value. Use +/// [Deserializer::from_value] to construct instances, or [from_value] to deserialize single +/// values directly. pub struct Deserializer<'de> { input: &'de IOValue, } +/// Deserialize a `T` from `v`, a Preserves [IOValue]. pub fn from_value<'a, T>(v: &'a IOValue) -> Result where T: Deserialize<'a>, @@ -21,6 +27,7 @@ where } impl<'de> Deserializer<'de> { + /// Construct a Deserializer from `v`, an [IOValue]. pub fn from_value(v: &'de IOValue) -> Self { Deserializer { input: v } } @@ -331,6 +338,7 @@ impl<'de, 'a> serde::de::Deserializer<'de> for &'a mut Deserializer<'de> { } } +#[doc(hidden)] pub struct VecSeq<'a, 'de: 'a, I: Iterator> { iter: I, de: &'a mut Deserializer<'de>, @@ -359,6 +367,7 @@ impl<'de, 'a, I: Iterator> SeqAccess<'de> for VecSeq<'a, 'd } } +#[doc(hidden)] pub struct DictMap<'a, 'de: 'a> { pending: Option<&'de IOValue>, iter: Box + 'a>, diff --git a/implementations/rust/preserves/src/value/domain.rs b/implementations/rust/preserves/src/value/domain.rs index d45df47..1374c2e 100644 --- a/implementations/rust/preserves/src/value/domain.rs +++ b/implementations/rust/preserves/src/value/domain.rs @@ -1,3 +1,6 @@ +//! Traits for working with Preserves [embedded +//! values](https://preserves.dev/preserves.html#embeddeds). + use std::io; use super::packed; @@ -9,10 +12,12 @@ use super::NestedValue; use super::Reader; use super::Writer; +/// Implementations parse [IOValue]s to their own particular [Embeddable] values of type `D`. pub trait DomainParse { fn parse_embedded(&mut self, v: &IOValue) -> io::Result; } +/// Implementations read and parse from `src` to produce [Embeddable] values of type `D`. pub trait DomainDecode { fn decode_embedded<'de, 'src, S: BinarySource<'de>>( &mut self, @@ -21,6 +26,7 @@ pub trait DomainDecode { ) -> io::Result; } +/// Implementations unparse and write `D`s to `w`, a [writer][crate::value::writer::Writer]. pub trait DomainEncode { fn encode_embedded(&mut self, w: &mut W, d: &D) -> io::Result<()>; } @@ -41,6 +47,9 @@ impl<'a, D: Embeddable, T: DomainDecode> DomainDecode for &'a mut T { } } +/// Convenience codec: use this as embedded codec for encoding (only) when embedded values +/// should be serialized as Preserves `String`s holding their Rust [std::fmt::Debug] +/// representation. pub struct DebugDomainEncode; impl DomainEncode for DebugDomainEncode { @@ -49,6 +58,8 @@ impl DomainEncode for DebugDomainEncode { } } +/// Convenience codec: use this as embedded codec for decoding (only) when embedded values are +/// expected to conform to the syntax implicit in their [std::str::FromStr] implementation. pub struct FromStrDomainParse; impl, D: Embeddable + std::str::FromStr> DomainParse @@ -59,6 +70,8 @@ impl, D: Embeddable + std::str::FromStr> DomainP } } +/// Use this as embedded codec when embedded data are already [IOValue]s that can be directly +/// serialized and deserialized without further transformation. pub struct IOValueDomainCodec; impl DomainDecode for IOValueDomainCodec { @@ -77,6 +90,7 @@ impl DomainEncode for IOValueDomainCodec { } } +/// Use this as embedded codec to forbid use of embedded values; an [io::Error] is signalled. pub struct NoEmbeddedDomainCodec; impl DomainDecode for NoEmbeddedDomainCodec { @@ -101,9 +115,12 @@ impl DomainEncode for NoEmbeddedDomainCodec { } } +/// If some `C` implements [DomainDecode] but not [DomainParse], or vice versa, use `ViaCodec` +/// to promote the one to the other. Construct instances with [ViaCodec::new]. pub struct ViaCodec(C); impl ViaCodec { + /// Constructs a `ViaCodec` wrapper around an underlying codec of type `C`. pub fn new(c: C) -> Self { ViaCodec(c) } diff --git a/implementations/rust/preserves/src/value/magic.rs b/implementations/rust/preserves/src/value/magic.rs index 9a5acda..966e343 100644 --- a/implementations/rust/preserves/src/value/magic.rs +++ b/implementations/rust/preserves/src/value/magic.rs @@ -1,3 +1,12 @@ +#![doc(hidden)] + +//! A horrifying hack to Serde-serialize [IOValue] instances to Preserves *as themselves*. +//! +//! Frankly I think this portion of the codebase might not survive for long. I can't think of a +//! better way of achieving this, but the drawbacks of having this functionality are *severe*. +//! +//! See . + use super::repr::IOValue; pub static MAGIC: &str = "$____Preserves_Serde_Magic"; diff --git a/implementations/rust/preserves/src/value/merge.rs b/implementations/rust/preserves/src/value/merge.rs index 4eea4c3..3243be3 100644 --- a/implementations/rust/preserves/src/value/merge.rs +++ b/implementations/rust/preserves/src/value/merge.rs @@ -1,8 +1,13 @@ +//! Implements the Preserves +//! [merge](https://preserves.dev/preserves.html#appendix-merging-values) of values. + use super::Map; use super::NestedValue; use super::Record; use super::Value; +/// Merge two sequences of values according to [the +/// specification](https://preserves.dev/preserves.html#appendix-merging-values). pub fn merge_seqs(mut a: Vec, mut b: Vec) -> Option> { if a.len() > b.len() { std::mem::swap(&mut a, &mut b); @@ -16,6 +21,8 @@ pub fn merge_seqs(mut a: Vec, mut b: Vec) -> Option Some(r) } +/// Merge two values according to [the +/// specification](https://preserves.dev/preserves.html#appendix-merging-values). pub fn merge2(v: N, w: N) -> Option { let (mut v_anns, v_val) = v.pieces(); let (w_anns, w_val) = w.pieces(); @@ -52,6 +59,8 @@ pub fn merge2(v: N, w: N) -> Option { } } +/// Merge several values into a single value according to [the +/// specification](https://preserves.dev/preserves.html#appendix-merging-values). pub fn merge>(vs: I) -> Option { let mut vs = vs.into_iter(); let mut v = vs.next().expect("at least one value in merge()"); diff --git a/implementations/rust/preserves/src/value/mod.rs b/implementations/rust/preserves/src/value/mod.rs index 6c0dad1..378ab45 100644 --- a/implementations/rust/preserves/src/value/mod.rs +++ b/implementations/rust/preserves/src/value/mod.rs @@ -1,3 +1,11 @@ +//! Implementation of Preserves itself, separate from Serde. +//! +//! Preserves terms are [represented][repr] in memory as instances of [NestedValue]. +//! +//! - in-memory storage, representation, and APIs in module [repr] +//! - machine-oriented binary syntax codec in module [packed] +//! - human-oriented text syntax codec in module [text] + pub mod boundary; pub mod de; pub mod domain; @@ -56,6 +64,7 @@ pub use text::TextReader; pub use text::TextWriter; pub use writer::Writer; +#[doc(hidden)] pub fn invert_map(m: &Map) -> Map where A: Clone, diff --git a/implementations/rust/preserves/src/value/packed/constants.rs b/implementations/rust/preserves/src/value/packed/constants.rs index 66a0840..a9cb44c 100644 --- a/implementations/rust/preserves/src/value/packed/constants.rs +++ b/implementations/rust/preserves/src/value/packed/constants.rs @@ -1,6 +1,9 @@ +//! Definitions of the tags used in the binary encoding. + use std::convert::{From, TryFrom}; use std::io; +/// Rust representation of tags used in the binary encoding. #[derive(Debug, PartialEq, Eq)] pub enum Tag { False, @@ -19,8 +22,9 @@ pub enum Tag { Dictionary, } +/// Error value representing failure to decode a byte into a [Tag]. #[derive(Debug, PartialEq, Eq)] -pub struct InvalidTag(u8); +pub struct InvalidTag(pub u8); impl From for io::Error { fn from(v: InvalidTag) -> Self { diff --git a/implementations/rust/preserves/src/value/packed/mod.rs b/implementations/rust/preserves/src/value/packed/mod.rs index cf069ca..91ec9c8 100644 --- a/implementations/rust/preserves/src/value/packed/mod.rs +++ b/implementations/rust/preserves/src/value/packed/mod.rs @@ -1,3 +1,9 @@ +//! Implements the Preserves [machine-oriented binary +//! syntax](https://preserves.dev/preserves-binary.html). +//! +//! # Summary of Binary Syntax +#![doc = include_str!("../../../doc/cheatsheet-binary-plaintext.md")] + pub mod constants; pub mod reader; pub mod writer; @@ -9,6 +15,8 @@ use std::io; use super::{BinarySource, DomainDecode, IOValue, IOValueDomainCodec, NestedValue, Reader}; +/// Reads a value from the given byte vector `bs` using the binary encoding, discarding +/// annotations. pub fn from_bytes>( bs: &[u8], decode_embedded: Dec, @@ -18,10 +26,13 @@ pub fn from_bytes>( .demand_next(false) } +/// Reads an [IOValue] from the given byte vector `bs` using the binary encoding, discarding +/// annotations. pub fn iovalue_from_bytes(bs: &[u8]) -> io::Result { from_bytes(bs, IOValueDomainCodec) } +/// As [from_bytes], but includes annotations. pub fn annotated_from_bytes>( bs: &[u8], decode_embedded: Dec, @@ -31,6 +42,7 @@ pub fn annotated_from_bytes>( .demand_next(true) } +/// As [iovalue_from_bytes], but includes annotations. pub fn annotated_iovalue_from_bytes(bs: &[u8]) -> io::Result { annotated_from_bytes(bs, IOValueDomainCodec) } diff --git a/implementations/rust/preserves/src/value/packed/reader.rs b/implementations/rust/preserves/src/value/packed/reader.rs index b1fb423..24fb565 100644 --- a/implementations/rust/preserves/src/value/packed/reader.rs +++ b/implementations/rust/preserves/src/value/packed/reader.rs @@ -1,3 +1,5 @@ +//! Implementation of [Reader][crate::value::reader::Reader] for the binary encoding. + use crate::error::{self, io_syntax_error, is_eof_io_error, ExpectedKind, Received}; use num::bigint::BigInt; @@ -18,6 +20,7 @@ use super::super::{ }; use super::constants::Tag; +/// The binary encoding Preserves reader. pub struct PackedReader< 'de, 'src, @@ -25,7 +28,9 @@ pub struct PackedReader< Dec: DomainDecode, S: BinarySource<'de>, > { + /// Underlying source of bytes. pub source: &'src mut S, + /// Decoder for producing Rust values embedded in the binary data. pub decode_embedded: Dec, phantom: PhantomData<&'de N>, } @@ -67,6 +72,7 @@ fn out_of_range>(i: I) -> error::Error { impl<'de, 'src, N: NestedValue, Dec: DomainDecode, S: BinarySource<'de>> PackedReader<'de, 'src, N, Dec, S> { + /// Construct a new reader from a byte source and embedded-value decoder. #[inline(always)] pub fn new(source: &'src mut S, decode_embedded: Dec) -> Self { PackedReader { diff --git a/implementations/rust/preserves/src/value/packed/writer.rs b/implementations/rust/preserves/src/value/packed/writer.rs index 140f7f6..e1208ec 100644 --- a/implementations/rust/preserves/src/value/packed/writer.rs +++ b/implementations/rust/preserves/src/value/packed/writer.rs @@ -1,3 +1,5 @@ +//! Implementation of [Writer][crate::value::writer::Writer] for the binary encoding. + use super::super::boundary as B; use super::super::suspendable::Suspendable; use super::super::DomainEncode; @@ -13,9 +15,11 @@ use std::ops::DerefMut; use super::super::writer::{varint, CompoundWriter, Writer}; +/// The binary encoding Preserves writer. pub struct PackedWriter(Suspendable); impl PackedWriter<&mut Vec> { + /// Encodes `v` to a byte vector. #[inline(always)] pub fn encode>( enc: &mut Enc, @@ -26,6 +30,7 @@ impl PackedWriter<&mut Vec> { Ok(buf) } + /// Encodes `v` to a byte vector. #[inline(always)] pub fn encode_iovalue(v: &IOValue) -> io::Result> { Self::encode(&mut IOValueDomainCodec, v) @@ -33,26 +38,31 @@ impl PackedWriter<&mut Vec> { } impl PackedWriter { + /// Construct a writer from the given byte sink `write`. #[inline(always)] pub fn new(write: W) -> Self { PackedWriter(Suspendable::new(write)) } + /// Retrieve a mutable reference to the underlying byte sink. #[inline(always)] pub fn w(&mut self) -> &mut W { self.0.deref_mut() } + #[doc(hidden)] #[inline(always)] pub fn write_byte(&mut self, b: u8) -> io::Result<()> { self.w().write_all(&[b]) } + #[doc(hidden)] #[inline(always)] pub fn write_integer(&mut self, bs: &[u8]) -> io::Result<()> { self.write_atom(Tag::SignedInteger, bs) } + #[doc(hidden)] #[inline(always)] pub fn write_atom(&mut self, tag: Tag, bs: &[u8]) -> io::Result<()> { self.write_byte(tag.into())?; @@ -60,17 +70,20 @@ impl PackedWriter { self.w().write_all(bs) } + #[doc(hidden)] #[inline(always)] pub fn suspend(&mut self) -> Self { PackedWriter(self.0.suspend()) } + #[doc(hidden)] #[inline(always)] pub fn resume(&mut self, other: Self) { self.0.resume(other.0) } } +#[doc(hidden)] pub struct BinaryOrderWriter(Vec>); impl BinaryOrderWriter { @@ -119,6 +132,7 @@ impl BinaryOrderWriter { } } +#[doc(hidden)] pub trait WriteWriter: Writer { fn write_raw_bytes(&mut self, v: &[u8]) -> io::Result<()>; diff --git a/implementations/rust/preserves/src/value/repr.rs b/implementations/rust/preserves/src/value/repr.rs index 274eb55..c9ac9ef 100644 --- a/implementations/rust/preserves/src/value/repr.rs +++ b/implementations/rust/preserves/src/value/repr.rs @@ -1,3 +1,5 @@ +//! In-memory representation of Preserves `Value`s. + use num::bigint::BigInt; use num::traits::cast::ToPrimitive; use std::borrow::Cow; @@ -26,12 +28,19 @@ use super::TextWriter; use super::Writer; use crate::error::{Error, ExpectedKind, Received}; +/// A `Domain` implementation allows a Rust value to be placed as a Preserves [embedded +/// value](https://preserves.dev/preserves.html#embeddeds) inside a Preserves term. (See also +/// [Embeddable].) pub trait Domain: Sized + Debug + Eq + Hash + Ord { fn debug_encode(&self, w: &mut W) -> io::Result<()> { w.write_string(&format!("{:?}", self)) } } +/// Any Rust value that implements [`Domain`] and `Clone` is automatically `Embeddable`, and +/// may be placed as a Preserves [embedded +/// value](https://preserves.dev/preserves.html#embeddeds) inside a Preserves term. (See also +/// [Domain].) pub trait Embeddable: Domain + Clone {} impl Embeddable for T where T: Domain + Clone {} @@ -41,9 +50,15 @@ impl Domain for Arc { } } +/// This is the **primary programming interface** to Preserves values. The most common and +/// useful implementations of this trait are first [IOValue] and second [ArcValue]. pub trait NestedValue: Sized + Debug + Clone + Eq + Hash + Ord { + /// Every representation of Preserves values has an associated type: that of the Rust data + /// able to be [embedded](https://preserves.dev/preserves.html#embeddeds) inside a value. type Embedded: Embeddable; + /// `v` can be converted to a [Value]; `new` does this and then [wrap][Value::wrap]s it to + /// yield an instance of [Self]. #[inline(always)] fn new(v: V) -> Self where @@ -52,6 +67,8 @@ pub trait NestedValue: Sized + Debug + Clone + Eq + Hash + Ord { Value::from(v).wrap() } + /// [Embeds](https://preserves.dev/preserves.html#embeddeds) `e` to a Preserves embedded + /// value; `e` is first converted to [Self::Embedded]. #[inline(always)] fn domain(e: E) -> Self where @@ -60,28 +77,38 @@ pub trait NestedValue: Sized + Debug + Clone + Eq + Hash + Ord { Value::Embedded(e.into()).wrap() } + /// Yields a Preserves `Symbol` embodying the given text, `n`. #[inline(always)] fn symbol(n: &str) -> Self { Value::symbol(n).wrap() } + /// Yields a Preserves `ByteString`. #[inline(always)] fn bytestring<'a, V: Into>>(v: V) -> Self { Value::bytestring(v).wrap() } + /// Attaches the given [Annotations] to the [Value]. fn wrap(anns: Annotations, v: Value) -> Self; + /// Retrieves any annotations attached to `self`. fn annotations(&self) -> &Annotations; + /// Retrieves the underlying [Value] represented by `self`. fn value(&self) -> &Value; + /// Consumes `self`, yielding its annotations and underlying [Value]. fn pieces(self) -> (Annotations, Value); + /// Consumes `self`, yielding its underlying [Value] and discarding its annotations. fn value_owned(self) -> Value; + /// Retrieves the [ValueClass] of `self`. #[inline(always)] fn value_class(&self) -> ValueClass { self.value().value_class() } + /// Supplies an opportunity to customize debug formatting for `self`. Defaults to writing + /// `@`-prefixed annotations followed by the underlying value. fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for ann in self.annotations().slice() { write!(f, "@{:?} ", ann)?; @@ -89,10 +116,12 @@ pub trait NestedValue: Sized + Debug + Clone + Eq + Hash + Ord { self.value().fmt(f) } + /// Yields a deep copy of `self` with all annotations (recursively) removed. fn strip_annotations>(&self) -> M { M::wrap(Annotations::empty(), self.value().strip_annotations()) } + /// Yields a deep copy of `self`, mapping embedded values to a new type via `f`. fn copy_via(&self, f: &mut F) -> Result where F: FnMut(&Self::Embedded) -> Result, Err>, @@ -103,6 +132,7 @@ pub trait NestedValue: Sized + Debug + Clone + Eq + Hash + Ord { )) } + /// Calls `f` once for each (recursively) embedded [Self::Embedded] value in `self`. fn foreach_embedded(&self, f: &mut F) -> Result<(), Err> where F: FnMut(&Self::Embedded) -> Result<(), Err>, @@ -173,46 +203,56 @@ pub struct Float(pub f32); #[derive(Clone, Copy, Debug)] pub struct Double(pub f64); -/// A Record `Value` -- INVARIANT: length always non-zero +/// A Record `Value`. +/// +/// INVARIANT: The length of the contained vector **MUST** always be non-zero. #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Record(pub Vec); impl Record { + /// Retrieve the record's label. #[inline(always)] pub fn label(&self) -> &N { &self.0[0] } + /// Retrieve a mutable reference to the record's label. #[inline(always)] pub fn label_mut(&mut self) -> &mut N { &mut self.0[0] } + /// Retrieve the arity of the record, the number of fields it has. #[inline(always)] pub fn arity(&self) -> usize { self.0.len() - 1 } + /// Retrieve a slice containing the fields of the record. #[inline(always)] pub fn fields(&self) -> &[N] { &self.0[1..] } + /// Retrieve a mutable slice containing the fields of the record. #[inline(always)] pub fn fields_mut(&mut self) -> &mut [N] { &mut self.0[1..] } + /// Retrieve a reference to a vector containing the record's label and fields. #[inline(always)] pub fn fields_vec(&self) -> &Vec { &self.0 } + /// Retrieve a mutable reference to a vector containing the record's label and fields. #[inline(always)] pub fn fields_vec_mut(&mut self) -> &mut Vec { &mut self.0 } + /// Converts `self` into a [Value]. #[inline(always)] pub fn finish(self) -> Value where @@ -493,10 +533,12 @@ impl< //--------------------------------------------------------------------------- impl Value { + /// Converts `self` to a [NestedValue] by supplying an empty collection of annotations. pub fn wrap(self) -> N { N::wrap(Annotations::empty(), self) } + /// Retrieves the [ValueClass] of `self`. fn value_class(&self) -> ValueClass { match self { Value::Boolean(_) => ValueClass::Atomic(AtomClass::Boolean), @@ -514,6 +556,11 @@ impl Value { } } + /// Retrieve a vector of the "children" of `self`. + /// + /// For atoms, this is an empty vector. For records, it's all the fields (but not the + /// label). For sequences and sets, it's the contained values. For dictionaries, it's all + /// the values in the key-value mappings (but not the keys). pub fn children(&self) -> Vec { match self { Value::Boolean(_) @@ -539,11 +586,13 @@ impl Value { ) } + /// True iff this is a [Value::Boolean]. #[inline(always)] pub fn is_boolean(&self) -> bool { self.as_boolean().is_some() } + /// Yields `Some` iff this is a [Value::Boolean]. #[inline(always)] pub fn as_boolean(&self) -> Option { if let Value::Boolean(b) = self { @@ -553,6 +602,7 @@ impl Value { } } + /// Retrieve a mutable reference to the contained boolean value iff this is a [Value::Boolean]. #[inline(always)] pub fn as_boolean_mut(&mut self) -> Option<&mut bool> { if let Value::Boolean(b) = self { @@ -562,17 +612,20 @@ impl Value { } } + /// Yields `Ok` iff this is a [Value::Boolean]; else [Error::Expected]. #[inline(always)] pub fn to_boolean(&self) -> Result { self.as_boolean() .ok_or_else(|| self.expected(ExpectedKind::Boolean)) } + /// True iff this is a [Value::Float]. #[inline(always)] pub fn is_float(&self) -> bool { self.as_float().is_some() } + /// Yields `Some` iff this is a [Value::Float]. #[inline(always)] pub fn as_float(&self) -> Option<&Float> { if let Value::Float(f) = self { @@ -582,6 +635,7 @@ impl Value { } } + /// Retrieve a mutable reference to the contained [Float] value iff this is a [Value::Float]. #[inline(always)] pub fn as_float_mut(&mut self) -> Option<&mut Float> { if let Value::Float(f) = self { @@ -591,37 +645,44 @@ impl Value { } } + /// Yields `Ok` iff this is a [Value::Float]; else [Error::Expected]. #[inline(always)] pub fn to_float(&self) -> Result<&Float, Error> { self.as_float() .ok_or_else(|| self.expected(ExpectedKind::Float)) } + /// As [Self::is_float]. #[inline(always)] pub fn is_f32(&self) -> bool { self.is_float() } + /// As [Self::as_float], but yields [f32] instead of [Float]. #[inline(always)] pub fn as_f32(&self) -> Option { self.as_float().map(|f| f.0) } + /// As [Self::as_float_mut], but [f32] instead of [Float]. #[inline(always)] pub fn as_f32_mut(&mut self) -> Option<&mut f32> { self.as_float_mut().map(|f| &mut f.0) } + /// As [Self::to_float], but with [f32] instead of [Float]. #[inline(always)] pub fn to_f32(&self) -> Result { self.to_float().map(|f| f.0) } + /// True iff this is a [Value::Double]. #[inline(always)] pub fn is_double(&self) -> bool { self.as_double().is_some() } + /// Yields `Some` iff this is a [Value::Double]. #[inline(always)] pub fn as_double(&self) -> Option<&Double> { if let Value::Double(f) = self { @@ -631,6 +692,7 @@ impl Value { } } + /// Retrieve a mutable reference to the contained [Double] value iff this is a [Value::Double]. #[inline(always)] pub fn as_double_mut(&mut self) -> Option<&mut Double> { if let Value::Double(f) = self { @@ -640,37 +702,44 @@ impl Value { } } + /// Yields `Ok` iff this is a [Value::Double]; else [Error::Expected]. #[inline(always)] pub fn to_double(&self) -> Result<&Double, Error> { self.as_double() .ok_or_else(|| self.expected(ExpectedKind::Double)) } + /// As [Self::is_double]. #[inline(always)] pub fn is_f64(&self) -> bool { self.is_double() } + /// As [Self::as_double], but yields [f64] instead of [Double]. #[inline(always)] pub fn as_f64(&self) -> Option { self.as_double().map(|f| f.0) } + /// As [Self::as_double_mut], but [f64] instead of [Double]. #[inline(always)] pub fn as_f64_mut(&mut self) -> Option<&mut f64> { self.as_double_mut().map(|f| &mut f.0) } + /// As [Self::to_double], but with [f64] instead of [Double]. #[inline(always)] pub fn to_f64(&self) -> Result { self.to_double().map(|f| f.0) } + /// True iff this is a [Value::SignedInteger]. #[inline(always)] pub fn is_signedinteger(&self) -> bool { self.as_signedinteger().is_some() } + /// Yields `Some` iff this is a [Value::SignedInteger]. #[inline(always)] pub fn as_signedinteger(&self) -> Option<&SignedInteger> { if let Value::SignedInteger(n) = self { @@ -680,6 +749,7 @@ impl Value { } } + /// Retrieve a mutable reference to the contained SignedInteger value iff this is a [Value::SignedInteger]. #[inline(always)] pub fn as_signedinteger_mut(&mut self) -> Option<&mut SignedInteger> { if let Value::SignedInteger(n) = self { @@ -689,93 +759,114 @@ impl Value { } } + /// Yields `Ok` iff this is a [Value::SignedInteger]; else [Error::Expected]. #[inline(always)] pub fn to_signedinteger(&self) -> Result<&SignedInteger, Error> { self.as_signedinteger() .ok_or_else(|| self.expected(ExpectedKind::SignedInteger)) } + /// True iff [Self::as_i] yields `Some`. #[inline(always)] pub fn is_i(&self) -> bool { self.as_i().is_some() } + /// Yields `Some` if `self` is a [Value::SignedInteger] that fits in [i128]. #[inline(always)] pub fn as_i(&self) -> Option { self.as_signedinteger().and_then(|n| n.try_into().ok()) } + /// Yields `Ok` if `self` is a [Value::SignedInteger] that fits in [i128]; else [Error::Expected]. #[inline(always)] pub fn to_i(&self) -> Result { self.as_i() .ok_or_else(|| self.expected(ExpectedKind::SignedIntegerI128)) } + /// True iff [Self::as_u] yields `Some`. #[inline(always)] pub fn is_u(&self) -> bool { self.as_u().is_some() } + /// Yields `Some` if `self` is a [Value::SignedInteger] that fits in [u128]. #[inline(always)] pub fn as_u(&self) -> Option { self.as_signedinteger().and_then(|n| n.try_into().ok()) } + /// Yields `Ok` if `self` is a [Value::SignedInteger] that fits in [u128]; else [Error::Expected]. #[inline(always)] pub fn to_u(&self) -> Result { self.as_u() .ok_or_else(|| self.expected(ExpectedKind::SignedIntegerU128)) } + /// Yields `Some` if `self` is a [Value::SignedInteger] that fits in [u8]. #[inline(always)] pub fn as_u8(&self) -> Option { self.as_u().and_then(|i| i.to_u8()) } + /// Yields `Some` if `self` is a [Value::SignedInteger] that fits in [i8]. #[inline(always)] pub fn as_i8(&self) -> Option { self.as_i().and_then(|i| i.to_i8()) } + /// Yields `Some` if `self` is a [Value::SignedInteger] that fits in [u16]. #[inline(always)] pub fn as_u16(&self) -> Option { self.as_u().and_then(|i| i.to_u16()) } + /// Yields `Some` if `self` is a [Value::SignedInteger] that fits in [i16]. #[inline(always)] pub fn as_i16(&self) -> Option { self.as_i().and_then(|i| i.to_i16()) } + /// Yields `Some` if `self` is a [Value::SignedInteger] that fits in [u32]. #[inline(always)] pub fn as_u32(&self) -> Option { self.as_u().and_then(|i| i.to_u32()) } + /// Yields `Some` if `self` is a [Value::SignedInteger] that fits in [i32]. #[inline(always)] pub fn as_i32(&self) -> Option { self.as_i().and_then(|i| i.to_i32()) } + /// Yields `Some` if `self` is a [Value::SignedInteger] that fits in [u64]. #[inline(always)] pub fn as_u64(&self) -> Option { self.as_u().and_then(|i| i.to_u64()) } + /// Yields `Some` if `self` is a [Value::SignedInteger] that fits in [i64]. #[inline(always)] pub fn as_i64(&self) -> Option { self.as_i().and_then(|i| i.to_i64()) } + /// Yields `Some` if `self` is a [Value::SignedInteger] that fits in [u128]. #[inline(always)] pub fn as_u128(&self) -> Option { - self.as_u().and_then(|i| i.to_u128()) + self.as_u() } + /// Yields `Some` if `self` is a [Value::SignedInteger] that fits in [i128]. #[inline(always)] pub fn as_i128(&self) -> Option { - self.as_i().and_then(|i| i.to_i128()) + self.as_i() } + /// Yields `Some` if `self` is a [Value::SignedInteger] that fits in [usize]. #[inline(always)] pub fn as_usize(&self) -> Option { self.as_u().and_then(|i| i.to_usize()) } + /// Yields `Some` if `self` is a [Value::SignedInteger] that fits in [isize]. #[inline(always)] pub fn as_isize(&self) -> Option { self.as_i().and_then(|i| i.to_isize()) } + /// Yields `Ok` if `self` is a [Value::SignedInteger] that fits in [i8]; + /// otherwise, [Error::Expected] or [Error::NumberOutOfRange]. #[inline(always)] pub fn to_i8(&self) -> Result { match self.as_i() { @@ -786,6 +877,8 @@ impl Value { } } + /// Yields `Ok` if `self` is a [Value::SignedInteger] that fits in [u8]; + /// otherwise, [Error::Expected] or [Error::NumberOutOfRange]. #[inline(always)] pub fn to_u8(&self) -> Result { match self.as_u() { @@ -796,6 +889,8 @@ impl Value { } } + /// Yields `Ok` if `self` is a [Value::SignedInteger] that fits in [i16]; + /// otherwise, [Error::Expected] or [Error::NumberOutOfRange]. #[inline(always)] pub fn to_i16(&self) -> Result { match self.as_i() { @@ -806,6 +901,8 @@ impl Value { } } + /// Yields `Ok` if `self` is a [Value::SignedInteger] that fits in [u16]; + /// otherwise, [Error::Expected] or [Error::NumberOutOfRange]. #[inline(always)] pub fn to_u16(&self) -> Result { match self.as_u() { @@ -816,6 +913,8 @@ impl Value { } } + /// Yields `Ok` if `self` is a [Value::SignedInteger] that fits in [i32]; + /// otherwise, [Error::Expected] or [Error::NumberOutOfRange]. #[inline(always)] pub fn to_i32(&self) -> Result { match self.as_i() { @@ -826,6 +925,8 @@ impl Value { } } + /// Yields `Ok` if `self` is a [Value::SignedInteger] that fits in [u32]; + /// otherwise, [Error::Expected] or [Error::NumberOutOfRange]. #[inline(always)] pub fn to_u32(&self) -> Result { match self.as_u() { @@ -836,6 +937,8 @@ impl Value { } } + /// Yields `Ok` if `self` is a [Value::SignedInteger] that fits in [i64]; + /// otherwise, [Error::Expected] or [Error::NumberOutOfRange]. #[inline(always)] pub fn to_i64(&self) -> Result { match self.as_i() { @@ -846,6 +949,8 @@ impl Value { } } + /// Yields `Ok` if `self` is a [Value::SignedInteger] that fits in [u64]; + /// otherwise, [Error::Expected] or [Error::NumberOutOfRange]. #[inline(always)] pub fn to_u64(&self) -> Result { match self.as_u() { @@ -856,6 +961,8 @@ impl Value { } } + /// Yields `Ok` if `self` is a [Value::SignedInteger] that fits in [i128]; + /// otherwise, [Error::Expected] or [Error::NumberOutOfRange]. #[inline(always)] pub fn to_i128(&self) -> Result { match self.as_i() { @@ -866,6 +973,8 @@ impl Value { } } + /// Yields `Ok` if `self` is a [Value::SignedInteger] that fits in [u128]; + /// otherwise, [Error::Expected] or [Error::NumberOutOfRange]. #[inline(always)] pub fn to_u128(&self) -> Result { match self.as_u() { @@ -876,6 +985,8 @@ impl Value { } } + /// Yields `Ok` if `self` is a [Value::SignedInteger] that fits in [isize]; + /// otherwise, [Error::Expected] or [Error::NumberOutOfRange]. #[inline(always)] pub fn to_isize(&self) -> Result { match self.as_i() { @@ -886,6 +997,8 @@ impl Value { } } + /// Yields `Ok` if `self` is a [Value::SignedInteger] that fits in [usize]; + /// otherwise, [Error::Expected] or [Error::NumberOutOfRange]. #[inline(always)] pub fn to_usize(&self) -> Result { match self.as_u() { @@ -896,6 +1009,10 @@ impl Value { } } + /// Yields `Ok` if `self` is a record with label a symbol `UnicodeScalar` and single field + /// a SignedInteger that can represent a valid Unicode scalar value. Otherwise, + /// [Error::Expected] or [Error::InvalidUnicodeScalar]. otherwise, [Error::Expected] or + /// [Error::NumberOutOfRange]. #[inline(always)] pub fn to_char(&self) -> Result { let fs = self.to_simple_record("UnicodeScalar", Some(1))?; @@ -903,11 +1020,13 @@ impl Value { char::try_from(c).map_err(|_| Error::InvalidUnicodeScalar(c)) } + /// True iff this is a [Value::String]. #[inline(always)] pub fn is_string(&self) -> bool { self.as_string().is_some() } + /// Yields `Some` iff this is a [Value::String]. #[inline(always)] pub fn as_string(&self) -> Option<&String> { if let Value::String(s) = self { @@ -917,6 +1036,7 @@ impl Value { } } + /// Retrieve a mutable reference to the contained String value iff this is a [Value::String]. #[inline(always)] pub fn as_string_mut(&mut self) -> Option<&mut String> { if let Value::String(s) = self { @@ -926,6 +1046,7 @@ impl Value { } } + /// Consumes `self`, yielding a `String` iff `self` is a [Value::String]. #[inline(always)] pub fn into_string(self) -> Option { match self { @@ -934,22 +1055,26 @@ impl Value { } } + /// Yields `Ok` iff this is a [Value::String]; else [Error::Expected]. #[inline(always)] pub fn to_string(&self) -> Result<&String, Error> { self.as_string() .ok_or_else(|| self.expected(ExpectedKind::String)) } + /// Constructs a [Value::ByteString] from `v`. #[inline(always)] pub fn bytestring<'a, V: Into>>(v: V) -> Self { Value::ByteString(v.into().into_owned()) } + /// True iff this is a [Value::ByteString]. #[inline(always)] pub fn is_bytestring(&self) -> bool { self.as_bytestring().is_some() } + /// Yields `Some` iff this is a [Value::ByteString]. #[inline(always)] pub fn as_bytestring(&self) -> Option<&Vec> { if let Value::ByteString(s) = self { @@ -959,6 +1084,7 @@ impl Value { } } + /// Retrieve a mutable reference to the contained bytes value iff this is a [Value::ByteString]. #[inline(always)] pub fn as_bytestring_mut(&mut self) -> Option<&mut Vec> { if let Value::ByteString(s) = self { @@ -968,6 +1094,7 @@ impl Value { } } + /// Consumes `self`, yielding a `Vec` iff `self` is a [Value::ByteString]. #[inline(always)] pub fn into_bytestring(self) -> Option> { match self { @@ -976,22 +1103,26 @@ impl Value { } } + /// Yields `Ok` iff this is a [Value::ByteString]; else [Error::Expected]. #[inline(always)] pub fn to_bytestring(&self) -> Result<&Vec, Error> { self.as_bytestring() .ok_or_else(|| self.expected(ExpectedKind::ByteString)) } + /// Constructs a [Value::Symbol] from `v`. #[inline(always)] pub fn symbol(s: &str) -> Value { Value::Symbol(s.to_string()) } + /// True iff this is a [Value::Symbol]. #[inline(always)] pub fn is_symbol(&self) -> bool { self.as_symbol().is_some() } + /// Yields `Some` iff this is a [Value::Symbol]. #[inline(always)] pub fn as_symbol(&self) -> Option<&String> { if let Value::Symbol(s) = self { @@ -1001,6 +1132,7 @@ impl Value { } } + /// Retrieve a mutable reference to the contained Symbol's string iff this is a [Value::Symbol]. #[inline(always)] pub fn as_symbol_mut(&mut self) -> Option<&mut String> { if let Value::Symbol(s) = self { @@ -1010,6 +1142,7 @@ impl Value { } } + /// Consumes `self`, yielding a `String` iff `self` is a [Value::Symbol]. #[inline(always)] pub fn into_symbol(self) -> Option { match self { @@ -1018,12 +1151,16 @@ impl Value { } } + /// Yields `Ok` iff this is a [Value::Symbol]; else [Error::Expected]. #[inline(always)] pub fn to_symbol(&self) -> Result<&String, Error> { self.as_symbol() .ok_or_else(|| self.expected(ExpectedKind::Symbol)) } + /// Constructs a record with the given label and expected arity. The new record will + /// initially not have any fields, but will be allocated with capacity for `expected_arity` + /// fields. #[inline(always)] pub fn record(label: N, expected_arity: usize) -> Record { let mut v = Vec::with_capacity(expected_arity + 1); @@ -1031,11 +1168,13 @@ impl Value { Record(v) } + /// True iff this is a [Value::Record]. #[inline(always)] pub fn is_record(&self) -> bool { matches!(*self, Value::Record(_)) } + /// Yields `Some` iff this is a [Value::Record]. #[inline(always)] pub fn as_record(&self, arity: Option) -> Option<&Record> { if let Value::Record(r) = self { @@ -1049,6 +1188,7 @@ impl Value { } } + /// Consumes `self`, yielding a `Record` iff `self` is a [Value::Record]. #[inline(always)] pub fn into_record(self) -> Option> { match self { @@ -1057,6 +1197,7 @@ impl Value { } } + /// Retrieve a mutable reference to the contained Record value iff this is a [Value::Record]. #[inline(always)] pub fn as_record_mut(&mut self, arity: Option) -> Option<&mut Record> { if let Value::Record(r) = self { @@ -1070,22 +1211,27 @@ impl Value { } } + /// Yields `Ok` iff this is a [Value::Record]; else [Error::Expected]. #[inline(always)] pub fn to_record(&self, arity: Option) -> Result<&Record, Error> { self.as_record(arity) .ok_or_else(|| self.expected(ExpectedKind::Record(arity))) } + /// Like [Self::record], but for the common case where the label is to be a `Symbol` with a + /// given text. #[inline(always)] pub fn simple_record(label: &str, expected_arity: usize) -> Record { Self::record(N::symbol(label), expected_arity) } + /// Constructs a record with label a symbol with text `label`, and no fields. #[inline(always)] pub fn simple_record0(label: &str) -> Value { Self::simple_record(label, 0).finish() } + /// Constructs a record with label a symbol with text `label`, and one field. #[inline(always)] pub fn simple_record1(label: &str, field: N) -> Value { let mut r = Self::simple_record(label, 1); @@ -1093,11 +1239,15 @@ impl Value { r.finish() } + /// True iff `self` is a record with label a symbol with text `label` and arity matching + /// `arity`: any arity, if `arity == None`, or the specific `usize` concerned otherwise. #[inline(always)] pub fn is_simple_record(&self, label: &str, arity: Option) -> bool { self.as_simple_record(label, arity).is_some() } + /// Yields `Some` containing a reference to the record's fields iff + /// [`Self::is_simple_record`]`(label, arity)` returns true. #[inline(always)] pub fn as_simple_record(&self, label: &str, arity: Option) -> Option<&[N]> { self.as_record(arity).and_then(|r| match r.label().value() { @@ -1106,12 +1256,14 @@ impl Value { }) } + /// Like [Self::as_simple_record], but yields [Error::Expected] on failure. #[inline(always)] pub fn to_simple_record(&self, label: &str, arity: Option) -> Result<&[N], Error> { self.as_simple_record(label, arity) .ok_or_else(|| self.expected(ExpectedKind::SimpleRecord(label.to_owned(), arity))) } + /// Serde's "option" type is incoded in Preserves as `` or ``. #[inline(always)] pub fn to_option(&self) -> Result, Error> { match self.as_simple_record("None", Some(0)) { @@ -1123,11 +1275,13 @@ impl Value { } } + /// True iff this is a [Value::Sequence]. #[inline(always)] pub fn is_sequence(&self) -> bool { self.as_sequence().is_some() } + /// Yields `Some` iff this is a [Value::Sequence]. #[inline(always)] pub fn as_sequence(&self) -> Option<&Vec> { if let Value::Sequence(s) = self { @@ -1137,6 +1291,7 @@ impl Value { } } + /// Consumes `self`, yielding a [`Vec`] iff `self` is a [Value::Sequence]. #[inline(always)] pub fn into_sequence(self) -> Option> { match self { @@ -1145,6 +1300,7 @@ impl Value { } } + /// Retrieve a mutable reference to the contained [`Vec`] iff this is a [Value::Sequence]. #[inline(always)] pub fn as_sequence_mut(&mut self) -> Option<&mut Vec> { if let Value::Sequence(s) = self { @@ -1154,17 +1310,20 @@ impl Value { } } + /// Yields `Ok` iff this is a [Value::Sequence]; else [Error::Expected]. #[inline(always)] pub fn to_sequence(&self) -> Result<&Vec, Error> { self.as_sequence() .ok_or_else(|| self.expected(ExpectedKind::Sequence)) } + /// True iff this is a [Value::Set]. #[inline(always)] pub fn is_set(&self) -> bool { self.as_set().is_some() } + /// Yields `Some` iff this is a [Value::Set]. #[inline(always)] pub fn as_set(&self) -> Option<&Set> { if let Value::Set(s) = self { @@ -1174,6 +1333,7 @@ impl Value { } } + /// Consumes `self`, yielding a [`Set`] iff `self` is a [Value::Set]. #[inline(always)] pub fn into_set(self) -> Option> { match self { @@ -1182,6 +1342,7 @@ impl Value { } } + /// Retrieve a mutable reference to the contained Set value iff this is a [Value::Set]. #[inline(always)] pub fn as_set_mut(&mut self) -> Option<&mut Set> { if let Value::Set(s) = self { @@ -1191,17 +1352,20 @@ impl Value { } } + /// Yields `Ok` iff this is a [Value::Set]; else [Error::Expected]. #[inline(always)] pub fn to_set(&self) -> Result<&Set, Error> { self.as_set() .ok_or_else(|| self.expected(ExpectedKind::Set)) } + /// True iff this is a [Value::Dictionary]. #[inline(always)] pub fn is_dictionary(&self) -> bool { self.as_dictionary().is_some() } + /// Yields `Some` iff this is a [Value::Dictionary]. #[inline(always)] pub fn as_dictionary(&self) -> Option<&Map> { if let Value::Dictionary(s) = self { @@ -1211,6 +1375,7 @@ impl Value { } } + /// Consumes `self`, yielding a [`Map`] iff `self` is a [Value::Dictionary]. #[inline(always)] pub fn into_dictionary(self) -> Option> { match self { @@ -1219,6 +1384,7 @@ impl Value { } } + /// Retrieve a mutable reference to the contained Map value iff this is a [Value::Dictionary]. #[inline(always)] pub fn as_dictionary_mut(&mut self) -> Option<&mut Map> { if let Value::Dictionary(s) = self { @@ -1228,17 +1394,20 @@ impl Value { } } + /// Yields `Ok` iff this is a [Value::Dictionary]; else [Error::Expected]. #[inline(always)] pub fn to_dictionary(&self) -> Result<&Map, Error> { self.as_dictionary() .ok_or_else(|| self.expected(ExpectedKind::Dictionary)) } + /// True iff this is a [Value::Embedded]. #[inline(always)] pub fn is_embedded(&self) -> bool { self.as_embedded().is_some() } + /// Yields `Some` iff this is a [Value::Embedded]. #[inline(always)] pub fn as_embedded(&self) -> Option<&N::Embedded> { if let Value::Embedded(d) = self { @@ -1248,12 +1417,14 @@ impl Value { } } + /// Yields `Ok` iff this is a [Value::Embedded]; else [Error::Expected]. #[inline(always)] pub fn to_embedded(&self) -> Result<&N::Embedded, Error> { self.as_embedded() .ok_or_else(|| self.expected(ExpectedKind::Embedded)) } + /// Yields a deep copy of `self` with all annotations (recursively) removed. pub fn strip_annotations>(&self) -> Value { match self { Value::Boolean(b) => Value::Boolean(*b), @@ -1282,6 +1453,7 @@ impl Value { } } + /// Yields a deep copy of `self`, mapping embedded values to a new type via `f`. pub fn copy_via(&self, f: &mut F) -> Result, Err> where F: FnMut(&N::Embedded) -> Result, Err>, @@ -1319,6 +1491,7 @@ impl Value { }) } + /// Calls `f` once for each (recursively) embedded value in `self`. pub fn foreach_embedded(&self, f: &mut F) -> Result<(), Err> where F: FnMut(&N::Embedded) -> Result<(), Err>, @@ -1377,6 +1550,7 @@ impl Index<&N> for Value { //--------------------------------------------------------------------------- // This part is a terrible hack +#[doc(hidden)] impl serde::Serialize for UnwrappedIOValue { fn serialize(&self, serializer: S) -> Result where @@ -1386,6 +1560,7 @@ impl serde::Serialize for UnwrappedIOValue { } } +#[doc(hidden)] impl<'de> serde::Deserialize<'de> for UnwrappedIOValue { fn deserialize(deserializer: D) -> Result where @@ -1397,20 +1572,27 @@ impl<'de> serde::Deserialize<'de> for UnwrappedIOValue { //--------------------------------------------------------------------------- +/// Representation of a collection of annotations to be attached to a [Value]. +/// +/// The complex-seeming `Option>>` is used to save memory, since a `Box` is smaller +/// than a `Vec`. #[derive(Clone)] pub struct Annotations(Option>>); impl Annotations { + /// Yield the empty [Annotations] sequence. #[inline(always)] pub fn empty() -> Self { Annotations(None) } + /// Yield [Annotations] from a vector of values. #[inline(always)] pub fn new(anns: Option>) -> Self { Annotations(anns.map(Box::new)) } + /// Extract carried annotations, if there are any. #[inline(always)] pub fn maybe_slice(&self) -> Option<&[N]> { match &self.0 { @@ -1419,11 +1601,13 @@ impl Annotations { } } + /// Extract carried annotations, supplying an empty slice if there are none. #[inline(always)] pub fn slice(&self) -> &[N] { self.maybe_slice().unwrap_or(&[]) } + /// Produce a fresh [Vec] of the carried annotations. #[inline(always)] pub fn to_vec(self) -> Vec { use std::ops::DerefMut; @@ -1432,6 +1616,7 @@ impl Annotations { .unwrap_or_default() } + /// Allows in-place updating of the collection of carried annotations. pub fn modify(&mut self, f: F) -> &mut Self where F: FnOnce(&mut Vec), @@ -1455,6 +1640,7 @@ impl Annotations { self } + /// Yields a deep copy of `self`, mapping embedded values to a new type via `f`. pub fn copy_via(&self, f: &mut F) -> Result, Err> where F: FnMut(&N::Embedded) -> Result, Err>, @@ -1514,6 +1700,7 @@ impl Ord for AnnotatedValue { //--------------------------------------------------------------------------- +/// A simple tree representation without any reference counting. #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct PlainValue(AnnotatedValue>); @@ -1569,6 +1756,7 @@ impl Debug for PlainValue { use std::rc::Rc; +/// A representation of a Preserves Value using [Rc] for reference-counting of subvalues. #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct RcValue(Rc>>); @@ -1614,6 +1802,7 @@ impl Debug for RcValue { //--------------------------------------------------------------------------- +/// A representation of a Preserves Value using [Arc] for reference-counting of subvalues. #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct ArcValue(Arc>>); @@ -1660,6 +1849,8 @@ impl Debug for ArcValue { //--------------------------------------------------------------------------- +/// A representation of a Preserves Value using [Arc] for reference-counting of subvalues and +/// having [IOValue] as [NestedValue::Embedded]. #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct IOValue(Arc>); pub type UnwrappedIOValue = Value; @@ -1738,6 +1929,7 @@ impl<'de> serde::Deserialize<'de> for IOValue { //--------------------------------------------------------------------------- +/// A "dummy" value that has no structure at all. #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct DummyValue(AnnotatedValue>); @@ -1788,6 +1980,7 @@ impl NestedValue for DummyValue { //--------------------------------------------------------------------------- +#[doc(hidden)] // https://stackoverflow.com/questions/34304593/counting-length-of-repetition-in-macro/34324856 #[macro_export] //#[allow(unused_macros)] @@ -1796,6 +1989,8 @@ macro_rules! count__ { ( $x:tt $($xs:tt)* ) => (1usize + $crate::count__!($($xs)*)); } +/// Convenience syntax for efficiently constructing Preserves +/// [record][crate::value::Value::record] values. #[macro_export] macro_rules! rec { ( $label:expr $(, $item:expr)* ) => { diff --git a/implementations/rust/preserves/src/value/ser.rs b/implementations/rust/preserves/src/value/ser.rs index 117edce..749cb47 100644 --- a/implementations/rust/preserves/src/value/ser.rs +++ b/implementations/rust/preserves/src/value/ser.rs @@ -1,6 +1,10 @@ +//! Support for Serde serialization of Rust data types into Preserves *values* (not syntax). + use crate::value::{repr::Record, IOValue, Map, Value}; use serde::Serialize; +/// Empty/placeholder type for representing serialization errors: serialization to values +/// cannot fail. #[derive(Debug)] pub enum Error {} impl serde::ser::Error for Error { @@ -20,17 +24,22 @@ impl std::fmt::Display for Error { type Result = std::result::Result; +/// Serde serializer for converting Rust data to in-memory Preserves values, which can then be +/// serialized using text or binary syntax, analyzed further, etc. pub struct Serializer; +#[doc(hidden)] pub struct SerializeDictionary { next_key: Option, items: Map, } +#[doc(hidden)] pub struct SerializeRecord { r: Record, } +#[doc(hidden)] pub struct SerializeSequence { vec: Vec, } @@ -359,6 +368,7 @@ impl serde::ser::SerializeSeq for SerializeSequence { } } +/// Convenience function for directly converting a Serde-serializable `T` to an [IOValue]. pub fn to_value(value: T) -> IOValue where T: Serialize, diff --git a/implementations/rust/preserves/src/value/signed_integer.rs b/implementations/rust/preserves/src/value/signed_integer.rs index a4b39de..a8142cd 100644 --- a/implementations/rust/preserves/src/value/signed_integer.rs +++ b/implementations/rust/preserves/src/value/signed_integer.rs @@ -1,3 +1,6 @@ +//! Representation of Preserves `SignedInteger`s as [i128]/[u128] (if they fit) or [BigInt] (if +//! they don't). + use num::bigint::BigInt; use num::traits::cast::ToPrimitive; use num::traits::sign::Signed; @@ -7,8 +10,10 @@ use std::convert::TryFrom; use std::convert::TryInto; use std::fmt; -// Invariant: if I128 can be used, it will be; otherwise, if U128 can -// be used, it will be; otherwise, Big will be used. +/// Internal representation of Preserves `SignedInteger`s. +/// +/// Invariant: if I128 can be used, it will be; otherwise, if U128 can be used, it will be; +/// otherwise, Big will be used. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum SignedIntegerRepr { I128(i128), @@ -16,6 +21,7 @@ pub enum SignedIntegerRepr { Big(Box), } +/// Main representation of Preserves `SignedInteger`s. #[derive(Clone, PartialEq, Eq, Hash)] pub struct SignedInteger(SignedIntegerRepr); @@ -87,18 +93,25 @@ impl PartialOrd for SignedInteger { } impl SignedInteger { + /// Extract the internal representation. pub fn repr(&self) -> &SignedIntegerRepr { &self.0 } + /// Does this `SignedInteger` fit in an [i128]? (See also [the TryFrom instance for + /// i128](#impl-TryFrom<%26SignedInteger>-for-i128).) pub fn is_i(&self) -> bool { matches!(self.0, SignedIntegerRepr::I128(_)) } + /// Does this `SignedInteger` fit in a [u128], but not an [i128]? (See also [the TryFrom + /// instance for u128](#impl-TryFrom<%26SignedInteger>-for-u128).) pub fn is_u(&self) -> bool { matches!(self.0, SignedIntegerRepr::U128(_)) } + /// Does this `SignedInteger` fit neither in a [u128] nor an [i128]? (See also [the TryFrom + /// instance for BigInt](#impl-From<%26'a+SignedInteger>-for-BigInt).) pub fn is_big(&self) -> bool { matches!(self.0, SignedIntegerRepr::Big(_)) } diff --git a/implementations/rust/preserves/src/value/suspendable.rs b/implementations/rust/preserves/src/value/suspendable.rs index 7940492..4e0d789 100644 --- a/implementations/rust/preserves/src/value/suspendable.rs +++ b/implementations/rust/preserves/src/value/suspendable.rs @@ -1,3 +1,5 @@ +#![doc(hidden)] + use std::ops::{Deref, DerefMut}; pub enum Suspendable { diff --git a/implementations/rust/preserves/src/value/text/mod.rs b/implementations/rust/preserves/src/value/text/mod.rs index 94921ee..9471338 100644 --- a/implementations/rust/preserves/src/value/text/mod.rs +++ b/implementations/rust/preserves/src/value/text/mod.rs @@ -1,3 +1,9 @@ +//! Implements the Preserves [human-oriented text +//! syntax](https://preserves.dev/preserves-text.html). +//! +//! # Summary of Text Syntax +#![doc = include_str!("../../../doc/cheatsheet-text-plaintext.md")] + pub mod reader; pub mod writer; @@ -10,6 +16,7 @@ use std::io; use super::{DomainParse, IOValue, IOValueDomainCodec, NestedValue, Reader, ViaCodec}; +/// Reads a value from the given string using the text syntax, discarding annotations. pub fn from_str>( s: &str, decode_embedded: Dec, @@ -17,10 +24,12 @@ pub fn from_str>( TextReader::new(&mut BytesBinarySource::new(s.as_bytes()), decode_embedded).demand_next(false) } +/// Reads an [IOValue] from the given string using the text syntax, discarding annotations. pub fn iovalue_from_str(s: &str) -> io::Result { from_str(s, ViaCodec::new(IOValueDomainCodec)) } +/// As [from_str], but includes annotations. pub fn annotated_from_str>( s: &str, decode_embedded: Dec, @@ -28,6 +37,7 @@ pub fn annotated_from_str>( TextReader::new(&mut BytesBinarySource::new(s.as_bytes()), decode_embedded).demand_next(true) } +/// As [iovalue_from_str], but includes annotations. pub fn annotated_iovalue_from_str(s: &str) -> io::Result { annotated_from_str(s, ViaCodec::new(IOValueDomainCodec)) } diff --git a/implementations/rust/preserves/src/value/text/reader.rs b/implementations/rust/preserves/src/value/text/reader.rs index 8fa44ab..8be4df7 100644 --- a/implementations/rust/preserves/src/value/text/reader.rs +++ b/implementations/rust/preserves/src/value/text/reader.rs @@ -1,3 +1,5 @@ +//! Implementation of [Reader][crate::value::reader::Reader] for the text syntax. + use crate::error::io_syntax_error; use crate::error::is_eof_io_error; use crate::error::syntax_error; @@ -35,8 +37,11 @@ use std::io; use std::iter::FromIterator; use std::marker::PhantomData; +/// The text syntax Preserves reader. pub struct TextReader<'de, 'src, D: Embeddable, Dec: DomainParse, S: BinarySource<'de>> { + /// Underlying source of (utf8) bytes. pub source: &'src mut S, + /// Decoder for producing Rust values embedded in the text. pub dec: Dec, phantom: PhantomData<&'de D>, } @@ -56,6 +61,7 @@ fn append_codepoint(bs: &mut Vec, n: u32) -> io::Result<()> { impl<'de, 'src, D: Embeddable, Dec: DomainParse, S: BinarySource<'de>> TextReader<'de, 'src, D, Dec, S> { + /// Construct a new reader from a byte (utf8) source and embedded-value decoder. pub fn new(source: &'src mut S, dec: Dec) -> Self { TextReader { source, @@ -134,6 +140,7 @@ impl<'de, 'src, D: Embeddable, Dec: DomainParse, S: BinarySource<'de>> } } + /// Retrieve the next [IOValue] in the input stream. pub fn next_iovalue(&mut self, read_annotations: bool) -> io::Result { let mut r = TextReader::new(self.source, ViaCodec::new(IOValueDomainCodec)); let v = r.demand_next(read_annotations)?; diff --git a/implementations/rust/preserves/src/value/text/writer.rs b/implementations/rust/preserves/src/value/text/writer.rs index c2fa886..18fe21c 100644 --- a/implementations/rust/preserves/src/value/text/writer.rs +++ b/implementations/rust/preserves/src/value/text/writer.rs @@ -1,3 +1,5 @@ +//! Implementation of [Writer][crate::value::writer::Writer] for the text syntax. + use crate::hex::HexFormatter; use crate::value::suspendable::Suspendable; use crate::value::writer::CompoundWriter; @@ -15,17 +17,26 @@ use std::io; use super::super::boundary as B; +/// Specifies a comma style for printing using [TextWriter]. #[derive(Clone, Copy, Debug)] pub enum CommaStyle { + /// No commas will be printed. (Preserves text syntax treats commas as whitespace (!).) None, + /// Commas will be used to separate subterms. Separating, + /// Commas will be used to terminate subterms. Terminating, } +/// The (optionally pretty-printing) text syntax Preserves writer. pub struct TextWriter { w: Suspendable, + /// Selects a comma style to use when printing. pub comma_style: CommaStyle, + /// Specifies indentation to use when pretty-printing; 0 disables pretty-printing. pub indentation: usize, + /// An aid to use of printed terms in shell scripts: set `true` to escape spaces embedded + /// in strings and symbols. pub escape_spaces: bool, indent: String, } @@ -37,6 +48,8 @@ impl std::default::Default for CommaStyle { } impl TextWriter<&mut Vec> { + /// Writes `v` to `f` using text syntax. Selects indentation mode based on + /// [`f.alternate()`][std::fmt::Formatter::alternate]. pub fn fmt_value>( f: &mut std::fmt::Formatter<'_>, enc: &mut Enc, @@ -52,6 +65,7 @@ impl TextWriter<&mut Vec> { .map_err(|_| io::Error::new(io::ErrorKind::Other, "could not append to Formatter")) } + /// Encode `v` to a [String]. pub fn encode>( enc: &mut Enc, v: &N, @@ -61,12 +75,14 @@ impl TextWriter<&mut Vec> { Ok(String::from_utf8(buf).expect("valid UTF-8 from TextWriter")) } + /// Encode `v` to a [String]. pub fn encode_iovalue(v: &IOValue) -> io::Result { Self::encode(&mut IOValueDomainCodec, v) } } impl TextWriter { + /// Construct a writer from the given byte sink `w`. pub fn new(w: W) -> Self { TextWriter { w: Suspendable::new(w), @@ -77,16 +93,19 @@ impl TextWriter { } } + /// Update selected comma-printing style. pub fn set_comma_style(mut self, v: CommaStyle) -> Self { self.comma_style = v; self } + /// Update selected space-escaping style. pub fn set_escape_spaces(mut self, v: bool) -> Self { self.escape_spaces = v; self } + #[doc(hidden)] pub fn suspend(&mut self) -> Self { TextWriter { w: self.w.suspend(), @@ -95,10 +114,12 @@ impl TextWriter { } } + #[doc(hidden)] pub fn resume(&mut self, other: Self) { self.w.resume(other.w) } + #[doc(hidden)] pub fn write_stringlike_char_fallback(&mut self, c: char, f: F) -> io::Result<()> where F: FnOnce(&mut W, char) -> io::Result<()>, @@ -114,22 +135,26 @@ impl TextWriter { } } + #[doc(hidden)] pub fn write_stringlike_char(&mut self, c: char) -> io::Result<()> { self.write_stringlike_char_fallback(c, |w, c| write!(w, "{}", c)) } + #[doc(hidden)] pub fn add_indent(&mut self) { for _ in 0..self.indentation { self.indent.push(' ') } } + #[doc(hidden)] pub fn del_indent(&mut self) { if self.indentation > 0 { self.indent.truncate(self.indent.len() - self.indentation) } } + #[doc(hidden)] pub fn indent(&mut self) -> io::Result<()> { if self.indentation > 0 { write!(self.w, "{}", &self.indent) @@ -138,6 +163,7 @@ impl TextWriter { } } + #[doc(hidden)] pub fn indent_sp(&mut self) -> io::Result<()> { if self.indentation > 0 { write!(self.w, "{}", &self.indent) @@ -146,6 +172,7 @@ impl TextWriter { } } + /// Borrow the underlying byte sink. pub fn borrow_write(&mut self) -> &mut W { &mut self.w }