Implement a bunch of accessors to help reading terms.
This commit is contained in:
parent
911da57d74
commit
c507b592c3
2 changed files with 436 additions and 66 deletions
|
@ -3,13 +3,12 @@ use std::collections::HashMap;
|
||||||
use nom::{
|
use nom::{
|
||||||
branch::alt,
|
branch::alt,
|
||||||
bytes::complete::{tag, take_while, take_while1},
|
bytes::complete::{tag, take_while, take_while1},
|
||||||
combinator::{opt, map},
|
combinator::{map, opt},
|
||||||
multi::{separated_list0, separated_list1},
|
multi::{separated_list0, separated_list1},
|
||||||
IResult,
|
IResult, InputLength,
|
||||||
InputLength,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::dec::{NonListTerm, Term, debug};
|
use crate::dec::{debug, AnyTerm, NonListTerm, Term};
|
||||||
|
|
||||||
const DICT_OPEN: &[u8] = b"{";
|
const DICT_OPEN: &[u8] = b"{";
|
||||||
const DICT_CLOSE: &[u8] = b"}";
|
const DICT_CLOSE: &[u8] = b"}";
|
||||||
|
@ -19,46 +18,52 @@ const STR_EXTRA_CHARS: &[u8] = b"._-*?";
|
||||||
|
|
||||||
// ----
|
// ----
|
||||||
|
|
||||||
|
/// The error kind returned by the `decode` function.
|
||||||
#[derive(Eq, PartialEq)]
|
#[derive(Eq, PartialEq)]
|
||||||
pub enum Error<'a> {
|
pub enum DecodeError<'a> {
|
||||||
|
/// Indicates that there is trailing garbage at the end of the decoded string
|
||||||
Garbage(&'a [u8]),
|
Garbage(&'a [u8]),
|
||||||
|
/// Indicates that the entered string does not represent a complete NetText term
|
||||||
IncompleteInput,
|
IncompleteInput,
|
||||||
|
/// Indicates a syntax error in the decoded term
|
||||||
NomError(&'a [u8], nom::error::ErrorKind),
|
NomError(&'a [u8], nom::error::ErrorKind),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> std::fmt::Debug for Error<'a> {
|
impl<'a> std::fmt::Debug for DecodeError<'a> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
match self {
|
match self {
|
||||||
Error::Garbage(g) => write!(f, "Garbage: `{}`", debug(g)),
|
DecodeError::Garbage(g) => write!(f, "Garbage: `{}`", debug(g)),
|
||||||
Error::IncompleteInput => write!(f, "Incomplete input"),
|
DecodeError::IncompleteInput => write!(f, "Incomplete input"),
|
||||||
Error::NomError(s, e) => write!(f, "Nom: {:?}, at: `{}`", e, debug(s)),
|
DecodeError::NomError(s, e) => write!(f, "Nom: {:?}, at: `{}`", e, debug(s)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<'a, T> = std::result::Result<T, Error<'a>>;
|
/// The result type returned by the `decode` function
|
||||||
|
pub type DecodeResult<'a, T> = std::result::Result<T, DecodeError<'a>>;
|
||||||
|
|
||||||
impl<'a> From<nom::Err<nom::error::Error<&'a [u8]>>> for Error<'a> {
|
impl<'a> From<nom::Err<nom::error::Error<&'a [u8]>>> for DecodeError<'a> {
|
||||||
fn from(e: nom::Err<nom::error::Error<&'a [u8]>>) -> Error<'a> {
|
fn from(e: nom::Err<nom::error::Error<&'a [u8]>>) -> DecodeError<'a> {
|
||||||
match e {
|
match e {
|
||||||
nom::Err::Incomplete(_) => Error::IncompleteInput,
|
nom::Err::Incomplete(_) => DecodeError::IncompleteInput,
|
||||||
nom::Err::Error(e) | nom::Err::Failure(e) => Error::NomError(e.input, e.code),
|
nom::Err::Error(e) | nom::Err::Failure(e) => DecodeError::NomError(e.input, e.code),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----
|
// ----
|
||||||
|
|
||||||
pub fn decode<'a>(input: &'a [u8]) -> Result<'a, Term<'a>> {
|
/// Decodes a NetText string into the term it represents.
|
||||||
|
pub fn decode<'a>(input: &'a [u8]) -> DecodeResult<'a, Term<'a, 'static>> {
|
||||||
let (rest, term) = decode_term(input)?;
|
let (rest, term) = decode_term(input)?;
|
||||||
let (end, _) = take_while(is_whitespace)(rest)?;
|
let (end, _) = take_while(is_whitespace)(rest)?;
|
||||||
if !end.is_empty() {
|
if !end.is_empty() {
|
||||||
return Err(Error::Garbage(end));
|
return Err(DecodeError::Garbage(end));
|
||||||
}
|
}
|
||||||
Ok(term)
|
Ok(Term(term))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decode_term<'a>(input: &'a [u8]) -> IResult<&'a [u8], Term<'a>> {
|
fn decode_term<'a>(input: &'a [u8]) -> IResult<&'a [u8], AnyTerm<'a, 'static>> {
|
||||||
eprintln!("DT: `{}`", debug(input));
|
eprintln!("DT: `{}`", debug(input));
|
||||||
let (start, _) = take_while(is_whitespace)(input)?;
|
let (start, _) = take_while(is_whitespace)(input)?;
|
||||||
eprintln!("DT2: `{}`", debug(start));
|
eprintln!("DT2: `{}`", debug(start));
|
||||||
|
@ -70,11 +75,11 @@ pub fn decode_term<'a>(input: &'a [u8]) -> IResult<&'a [u8], Term<'a>> {
|
||||||
} else {
|
} else {
|
||||||
let raw_len = start.input_len() - rest.input_len();
|
let raw_len = start.input_len() - rest.input_len();
|
||||||
let list_raw = &start[..raw_len];
|
let list_raw = &start[..raw_len];
|
||||||
Ok((rest, Term::List(list_raw, list)))
|
Ok((rest, AnyTerm::List(list_raw, list)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decode_nonlist_term<'a>(input: &'a [u8]) -> IResult<&'a [u8], NonListTerm<'a>> {
|
fn decode_nonlist_term<'a>(input: &'a [u8]) -> IResult<&'a [u8], NonListTerm<'a, 'static>> {
|
||||||
eprintln!("DNLT: `{}`", debug(input));
|
eprintln!("DNLT: `{}`", debug(input));
|
||||||
let (rest, term) = alt((
|
let (rest, term) = alt((
|
||||||
map(decode_str, NonListTerm::Str),
|
map(decode_str, NonListTerm::Str),
|
||||||
|
@ -90,7 +95,7 @@ fn decode_str<'a>(input: &'a [u8]) -> IResult<&'a [u8], &'a [u8]> {
|
||||||
Ok((rest, data))
|
Ok((rest, data))
|
||||||
}
|
}
|
||||||
|
|
||||||
type DictType<'a> = (&'a [u8], HashMap<&'a [u8], Term<'a>>);
|
type DictType<'a> = (&'a [u8], HashMap<&'a [u8], AnyTerm<'a, 'static>>);
|
||||||
|
|
||||||
fn decode_dict<'a>(dict_begin: &'a [u8]) -> IResult<&'a [u8], DictType<'a>> {
|
fn decode_dict<'a>(dict_begin: &'a [u8]) -> IResult<&'a [u8], DictType<'a>> {
|
||||||
eprintln!("DDbegin: `{}`", debug(dict_begin));
|
eprintln!("DDbegin: `{}`", debug(dict_begin));
|
||||||
|
@ -98,7 +103,7 @@ fn decode_dict<'a>(dict_begin: &'a [u8]) -> IResult<&'a [u8], DictType<'a>> {
|
||||||
eprintln!("DD2: `{}`", debug(d));
|
eprintln!("DD2: `{}`", debug(d));
|
||||||
let (d, items) = separated_list0(dict_separator, decode_dict_item)(d)?;
|
let (d, items) = separated_list0(dict_separator, decode_dict_item)(d)?;
|
||||||
eprintln!("DD3: `{}`", debug(d));
|
eprintln!("DD3: `{}`", debug(d));
|
||||||
let (d, _) = opt(dict_separator)(d)?;
|
let (d, _) = opt(dict_separator)(d)?;
|
||||||
let (d, _) = take_while(is_whitespace)(d)?;
|
let (d, _) = take_while(is_whitespace)(d)?;
|
||||||
eprintln!("DD4: `{}`", debug(d));
|
eprintln!("DD4: `{}`", debug(d));
|
||||||
let (dict_end, _) = tag(DICT_CLOSE)(d)?;
|
let (dict_end, _) = tag(DICT_CLOSE)(d)?;
|
||||||
|
@ -118,7 +123,7 @@ fn dict_separator<'a>(d: &'a [u8]) -> IResult<&'a [u8], ()> {
|
||||||
Ok((d, ()))
|
Ok((d, ()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_dict_item<'a>(d: &'a [u8]) -> IResult<&'a [u8], (&'a [u8], Term<'a>)> {
|
fn decode_dict_item<'a>(d: &'a [u8]) -> IResult<&'a [u8], (&'a [u8], AnyTerm<'a, 'static>)> {
|
||||||
eprintln!("DDI: `{}`", debug(d));
|
eprintln!("DDI: `{}`", debug(d));
|
||||||
let (d, _) = take_while(is_whitespace)(d)?;
|
let (d, _) = take_while(is_whitespace)(d)?;
|
||||||
eprintln!("DDI1: `{}`", debug(d));
|
eprintln!("DDI1: `{}`", debug(d));
|
||||||
|
@ -149,7 +154,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn simple_str() {
|
fn simple_str() {
|
||||||
let bytes = b" plop ";
|
let bytes = b" plop ";
|
||||||
assert_eq!(decode(bytes), Ok(Term::Str(b"plop")));
|
assert_eq!(decode(bytes), Ok(AnyTerm::Str(b"plop").into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -157,7 +162,7 @@ mod tests {
|
||||||
let bytes = b" plop plap plip ploup ";
|
let bytes = b" plop plap plip ploup ";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decode(bytes),
|
decode(bytes),
|
||||||
Ok(Term::List(
|
Ok(AnyTerm::List(
|
||||||
b"plop plap plip ploup",
|
b"plop plap plip ploup",
|
||||||
vec![
|
vec![
|
||||||
NonListTerm::Str(b"plop"),
|
NonListTerm::Str(b"plop"),
|
||||||
|
@ -165,7 +170,8 @@ mod tests {
|
||||||
NonListTerm::Str(b"plip"),
|
NonListTerm::Str(b"plip"),
|
||||||
NonListTerm::Str(b"ploup"),
|
NonListTerm::Str(b"ploup"),
|
||||||
]
|
]
|
||||||
))
|
)
|
||||||
|
.into())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,22 +180,23 @@ mod tests {
|
||||||
let bytes = b" { aze = hello, by = bojzkz pipo, ccde = ke } ";
|
let bytes = b" { aze = hello, by = bojzkz pipo, ccde = ke } ";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decode(bytes),
|
decode(bytes),
|
||||||
Ok(Term::Dict(
|
Ok(AnyTerm::Dict(
|
||||||
b"{ aze = hello, by = bojzkz pipo, ccde = ke }",
|
b"{ aze = hello, by = bojzkz pipo, ccde = ke }",
|
||||||
[
|
[
|
||||||
(&b"aze"[..], Term::Str(b"hello")),
|
(&b"aze"[..], AnyTerm::Str(b"hello")),
|
||||||
(
|
(
|
||||||
&b"by"[..],
|
&b"by"[..],
|
||||||
Term::List(
|
AnyTerm::List(
|
||||||
b"bojzkz pipo",
|
b"bojzkz pipo",
|
||||||
vec![NonListTerm::Str(b"bojzkz"), NonListTerm::Str(b"pipo")]
|
vec![NonListTerm::Str(b"bojzkz"), NonListTerm::Str(b"pipo")]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
(&b"ccde"[..], Term::Str(b"ke")),
|
(&b"ccde"[..], AnyTerm::Str(b"ke")),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect()
|
.collect()
|
||||||
))
|
)
|
||||||
|
.into())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,22 +205,23 @@ mod tests {
|
||||||
let bytes = b" { aze = hello, by = bojzkz pipo , ccde = ke , } ";
|
let bytes = b" { aze = hello, by = bojzkz pipo , ccde = ke , } ";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decode(bytes),
|
decode(bytes),
|
||||||
Ok(Term::Dict(
|
Ok(AnyTerm::Dict(
|
||||||
b"{ aze = hello, by = bojzkz pipo , ccde = ke , }",
|
b"{ aze = hello, by = bojzkz pipo , ccde = ke , }",
|
||||||
[
|
[
|
||||||
(&b"aze"[..], Term::Str(b"hello")),
|
(&b"aze"[..], AnyTerm::Str(b"hello")),
|
||||||
(
|
(
|
||||||
&b"by"[..],
|
&b"by"[..],
|
||||||
Term::List(
|
AnyTerm::List(
|
||||||
b"bojzkz pipo",
|
b"bojzkz pipo",
|
||||||
vec![NonListTerm::Str(b"bojzkz"), NonListTerm::Str(b"pipo")]
|
vec![NonListTerm::Str(b"bojzkz"), NonListTerm::Str(b"pipo")]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
(&b"ccde"[..], Term::Str(b"ke")),
|
(&b"ccde"[..], AnyTerm::Str(b"ke")),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect()
|
.collect()
|
||||||
))
|
)
|
||||||
|
.into())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,10 +230,11 @@ mod tests {
|
||||||
let bytes = b"HEAD alexpubkey";
|
let bytes = b"HEAD alexpubkey";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decode(bytes),
|
decode(bytes),
|
||||||
Ok(Term::List(
|
Ok(AnyTerm::List(
|
||||||
b"HEAD alexpubkey",
|
b"HEAD alexpubkey",
|
||||||
vec![NonListTerm::Str(b"HEAD"), NonListTerm::Str(b"alexpubkey")]
|
vec![NonListTerm::Str(b"HEAD"), NonListTerm::Str(b"alexpubkey")]
|
||||||
)),
|
)
|
||||||
|
.into()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,7 +243,7 @@ mod tests {
|
||||||
let bytes = b"STANCE sthash stsign { author = alexpubkey, height = 12, parent = parenthash, data = MESSAGE { text = hello } }";
|
let bytes = b"STANCE sthash stsign { author = alexpubkey, height = 12, parent = parenthash, data = MESSAGE { text = hello } }";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decode(bytes),
|
decode(bytes),
|
||||||
Ok(Term::List(
|
Ok(AnyTerm::List(
|
||||||
&bytes[..],
|
&bytes[..],
|
||||||
vec![
|
vec![
|
||||||
NonListTerm::Str(b"STANCE"),
|
NonListTerm::Str(b"STANCE"),
|
||||||
|
@ -242,17 +251,17 @@ mod tests {
|
||||||
NonListTerm::Str(b"stsign"),
|
NonListTerm::Str(b"stsign"),
|
||||||
NonListTerm::Dict(b"{ author = alexpubkey, height = 12, parent = parenthash, data = MESSAGE { text = hello } }",
|
NonListTerm::Dict(b"{ author = alexpubkey, height = 12, parent = parenthash, data = MESSAGE { text = hello } }",
|
||||||
[
|
[
|
||||||
(&b"author"[..], Term::Str(b"alexpubkey")),
|
(&b"author"[..], AnyTerm::Str(b"alexpubkey")),
|
||||||
(&b"height"[..], Term::Str(b"12")),
|
(&b"height"[..], AnyTerm::Str(b"12")),
|
||||||
(&b"parent"[..], Term::Str(b"parenthash")),
|
(&b"parent"[..], AnyTerm::Str(b"parenthash")),
|
||||||
(&b"data"[..], Term::List(
|
(&b"data"[..], AnyTerm::List(
|
||||||
b"MESSAGE { text = hello }",
|
b"MESSAGE { text = hello }",
|
||||||
vec![
|
vec![
|
||||||
NonListTerm::Str(b"MESSAGE"),
|
NonListTerm::Str(b"MESSAGE"),
|
||||||
NonListTerm::Dict(
|
NonListTerm::Dict(
|
||||||
b"{ text = hello }",
|
b"{ text = hello }",
|
||||||
[
|
[
|
||||||
(&b"text"[..], Term::Str(b"hello")),
|
(&b"text"[..], AnyTerm::Str(b"hello")),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -261,7 +270,7 @@ mod tests {
|
||||||
))
|
))
|
||||||
].into_iter().collect()
|
].into_iter().collect()
|
||||||
),
|
),
|
||||||
])),
|
]).into(),
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
401
src/dec/mod.rs
401
src/dec/mod.rs
|
@ -4,67 +4,428 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
pub use decode::*;
|
pub use decode::*;
|
||||||
|
|
||||||
#[derive(Eq, PartialEq)]
|
/// A parsed NetText term.
|
||||||
pub enum Term<'a> {
|
///
|
||||||
|
/// Lifetime 'a is the lifetime of the buffer containing the encoded data.
|
||||||
|
///
|
||||||
|
/// Lifetime 'b is the lifetime of another Term from which this one is borrowed, when it
|
||||||
|
/// is returned by one of the helper functions, or 'static when first returned from
|
||||||
|
/// `decode()`
|
||||||
|
#[derive(Eq, PartialEq, Debug)]
|
||||||
|
pub struct Term<'a, 'b>(AnyTerm<'a, 'b>);
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Clone)]
|
||||||
|
pub(crate) enum AnyTerm<'a, 'b> {
|
||||||
Str(&'a [u8]),
|
Str(&'a [u8]),
|
||||||
Dict(&'a [u8], HashMap<&'a [u8], Term<'a>>),
|
Dict(&'a [u8], HashMap<&'a [u8], AnyTerm<'a, 'b>>),
|
||||||
List(&'a [u8], Vec<NonListTerm<'a>>),
|
DictRef(&'a [u8], &'b HashMap<&'a [u8], AnyTerm<'a, 'b>>),
|
||||||
|
List(&'a [u8], Vec<NonListTerm<'a, 'b>>),
|
||||||
|
ListRef(&'a [u8], &'b [NonListTerm<'a, 'b>]),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Eq, PartialEq)]
|
#[derive(Eq, PartialEq, Clone)]
|
||||||
pub enum NonListTerm<'a> {
|
pub(crate) enum NonListTerm<'a, 'b> {
|
||||||
Str(&'a [u8]),
|
Str(&'a [u8]),
|
||||||
Dict(&'a [u8], HashMap<&'a [u8], Term<'a>>),
|
Dict(&'a [u8], HashMap<&'a [u8], AnyTerm<'a, 'b>>),
|
||||||
|
DictRef(&'a [u8], &'b HashMap<&'a [u8], AnyTerm<'a, 'b>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<NonListTerm<'a>> for Term<'a> {
|
impl<'a, 'b> From<NonListTerm<'a, 'b>> for AnyTerm<'a, 'b> {
|
||||||
fn from(x: NonListTerm<'a>) -> Term<'a> {
|
fn from(x: NonListTerm<'a, 'b>) -> AnyTerm<'a, 'b> {
|
||||||
match x {
|
match x {
|
||||||
NonListTerm::Str(s) => Term::Str(s),
|
NonListTerm::Str(s) => AnyTerm::Str(s),
|
||||||
NonListTerm::Dict(raw, d) => Term::Dict(raw, d),
|
NonListTerm::Dict(raw, d) => AnyTerm::Dict(raw, d),
|
||||||
|
NonListTerm::DictRef(raw, d) => AnyTerm::DictRef(raw, d),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----
|
impl<'a, 'b> TryFrom<AnyTerm<'a, 'b>> for NonListTerm<'a, 'b> {
|
||||||
|
type Error = ();
|
||||||
|
fn try_from(x: AnyTerm<'a, 'b>) -> Result<NonListTerm<'a, 'b>, ()> {
|
||||||
|
match x {
|
||||||
|
AnyTerm::Str(s) => Ok(NonListTerm::Str(s)),
|
||||||
|
AnyTerm::Dict(raw, d) => Ok(NonListTerm::Dict(raw, d)),
|
||||||
|
AnyTerm::DictRef(raw, d) => Ok(NonListTerm::DictRef(raw, d)),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<AnyTerm<'a, 'static>> for Term<'a, 'static> {
|
||||||
|
fn from(x: AnyTerm<'a, 'static>) -> Term<'a, 'static> {
|
||||||
|
Term(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- PUBLIC IMPLS ----
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum TypeError {
|
||||||
|
WrongType(&'static str),
|
||||||
|
WrongLength(usize, usize),
|
||||||
|
MissingKey(String),
|
||||||
|
UnexpectedKey(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> Term<'a, 'b> {
|
||||||
|
/// Get the term's raw representation
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use nettext::dec::decode;
|
||||||
|
///
|
||||||
|
/// let term = decode(b"hello world").unwrap();
|
||||||
|
/// assert_eq!(term.raw(), b"hello world");
|
||||||
|
/// ```
|
||||||
|
pub fn raw(&self) -> &'a [u8] {
|
||||||
|
self.0.raw()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the term is a single string, get that string
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use nettext::dec::decode;
|
||||||
|
///
|
||||||
|
/// let term1 = decode(b" hello ").unwrap();
|
||||||
|
/// assert_eq!(term1.str().unwrap(), b"hello");
|
||||||
|
///
|
||||||
|
/// let term2 = decode(b"hello world").unwrap();
|
||||||
|
/// assert!(term2.str().is_err());
|
||||||
|
/// ```
|
||||||
|
pub fn str(&self) -> Result<&'a [u8], TypeError> {
|
||||||
|
match &self.0 {
|
||||||
|
AnyTerm::Str(s) => Ok(s),
|
||||||
|
_ => Err(TypeError::WrongType("STR")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the term is a single string, or a list containing only strings,
|
||||||
|
/// get its raw representation
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use nettext::dec::decode;
|
||||||
|
///
|
||||||
|
/// let term1 = decode(b" hello world ").unwrap();
|
||||||
|
/// assert_eq!(term1.string().unwrap(), b"hello world");
|
||||||
|
///
|
||||||
|
/// let term2 = decode(b"hello { a= 5}").unwrap();
|
||||||
|
/// assert!(term2.string().is_err());
|
||||||
|
/// ```
|
||||||
|
pub fn string(&self) -> Result<&'a [u8], TypeError> {
|
||||||
|
match &self.0 {
|
||||||
|
AnyTerm::Str(s) => Ok(s),
|
||||||
|
AnyTerm::List(r, l) if l.iter().all(|x| matches!(x, NonListTerm::Str(_))) => Ok(r),
|
||||||
|
_ => Err(TypeError::WrongType("STRING")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a list of terms made from this term.
|
||||||
|
/// If it is a str or a dict, returns a list of a single term.
|
||||||
|
/// If it is a list, that's the list of terms we return.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use nettext::dec::decode;
|
||||||
|
///
|
||||||
|
/// let term1 = decode(b" hello ").unwrap();
|
||||||
|
/// let list1 = term1.list();
|
||||||
|
/// assert_eq!(list1.len(), 1);
|
||||||
|
/// assert_eq!(list1[0].str().unwrap(), b"hello");
|
||||||
|
///
|
||||||
|
/// let term2 = decode(b" hello world ").unwrap();
|
||||||
|
/// let list2 = term2.list();
|
||||||
|
/// assert_eq!(list2.len(), 2);
|
||||||
|
/// assert_eq!(list2[0].str().unwrap(), b"hello");
|
||||||
|
/// assert_eq!(list2[1].str().unwrap(), b"world");
|
||||||
|
/// ```
|
||||||
|
pub fn list(&self) -> Vec<Term<'a, '_>> {
|
||||||
|
match self.0.mkref() {
|
||||||
|
AnyTerm::ListRef(_r, l) => l.iter().map(|x| Term(x.mkref().into())).collect::<Vec<_>>(),
|
||||||
|
x => vec![Term(x)],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as `.list()`, but deconstructs it in a const length array.
|
||||||
|
/// This allows to directly bind the resulting list into discrete variables.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use nettext::dec::decode;
|
||||||
|
///
|
||||||
|
/// let term1 = decode(b" hello ").unwrap();
|
||||||
|
/// let [s1] = term1.list_of().unwrap();
|
||||||
|
/// assert_eq!(s1.str().unwrap(), b"hello");
|
||||||
|
///
|
||||||
|
/// let term2 = decode(b" hello world ").unwrap();
|
||||||
|
/// let [s2a, s2b] = term2.list_of().unwrap();
|
||||||
|
/// assert_eq!(s2a.str().unwrap(), b"hello");
|
||||||
|
/// assert_eq!(s2b.str().unwrap(), b"world");
|
||||||
|
/// ```
|
||||||
|
pub fn list_of<const N: usize>(&self) -> Result<[Term<'a, '_>; N], TypeError> {
|
||||||
|
let list = self.list();
|
||||||
|
let list_len = list.len();
|
||||||
|
list.try_into()
|
||||||
|
.map_err(|_| TypeError::WrongLength(list_len, N))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as `.list_of()`, but only binds the first N-1 terms.
|
||||||
|
/// If there are exactly N terms, the last one is bound to the Nth return variable.
|
||||||
|
/// If there are more then N terms, the remaining terms are bound to a new list term
|
||||||
|
/// that is returned as the Nth return variable.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use nettext::dec::decode;
|
||||||
|
///
|
||||||
|
/// let term1 = decode(b" hello world ").unwrap();
|
||||||
|
/// let [s1a, s1b] = term1.list_of_first().unwrap();
|
||||||
|
/// assert_eq!(s1a.str().unwrap(), b"hello");
|
||||||
|
/// assert_eq!(s1b.str().unwrap(), b"world");
|
||||||
|
///
|
||||||
|
/// let term2 = decode(b" hello mighty world ").unwrap();
|
||||||
|
/// let [s2a, s2b] = term2.list_of_first().unwrap();
|
||||||
|
/// assert_eq!(s2a.str().unwrap(), b"hello");
|
||||||
|
/// assert_eq!(s2b.list().len(), 2);
|
||||||
|
/// assert_eq!(s2b.raw(), b"mighty world");
|
||||||
|
/// ```
|
||||||
|
pub fn list_of_first<const N: usize>(&self) -> Result<[Term<'a, '_>; N], TypeError> {
|
||||||
|
match self.0.mkref() {
|
||||||
|
AnyTerm::ListRef(raw, list) => {
|
||||||
|
if list.len() < N {
|
||||||
|
Err(TypeError::WrongLength(list.len(), N))
|
||||||
|
} else if list.len() == N {
|
||||||
|
Ok(list
|
||||||
|
.iter()
|
||||||
|
.map(|x| Term(x.mkref().into()))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.try_into()
|
||||||
|
.unwrap())
|
||||||
|
} else {
|
||||||
|
let mut ret = Vec::with_capacity(N);
|
||||||
|
for item in list[0..N - 1].iter() {
|
||||||
|
ret.push(Term(item.mkref().into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let remaining_begin = list[N - 1].raw().as_ptr() as usize;
|
||||||
|
let remaining_offset = remaining_begin - raw.as_ptr() as usize;
|
||||||
|
let remaining_raw = &raw[remaining_offset..];
|
||||||
|
|
||||||
|
let remaining = list[N - 1..]
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.mkref())
|
||||||
|
.collect::<Vec<NonListTerm<'a, '_>>>();
|
||||||
|
ret.push(Term(AnyTerm::List(remaining_raw, remaining)));
|
||||||
|
|
||||||
|
Ok(ret.try_into().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x if N == 1 => Ok([Term(x)]
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.try_into()
|
||||||
|
.unwrap()),
|
||||||
|
_ => Err(TypeError::WrongLength(1, N)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks term is a dictionnary and returns hashmap of inner terms
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use nettext::dec::decode;
|
||||||
|
///
|
||||||
|
/// let term = decode(b"{ k1 = v1, k2 = v2 }").unwrap();
|
||||||
|
/// let dict = term.dict().unwrap();
|
||||||
|
/// assert_eq!(dict.get(&b"k1"[..]).unwrap().str().unwrap(), b"v1");
|
||||||
|
/// assert_eq!(dict.get(&b"k2"[..]).unwrap().str().unwrap(), b"v2");
|
||||||
|
/// ```
|
||||||
|
pub fn dict(&self) -> Result<HashMap<&'a [u8], Term<'a, '_>>, TypeError> {
|
||||||
|
match self.0.mkref() {
|
||||||
|
AnyTerm::DictRef(_, d) => Ok(d.iter().map(|(k, t)| (*k, Term(t.mkref()))).collect()),
|
||||||
|
_ => Err(TypeError::WrongType("DICT")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks term is a dictionnary whose keys are exactly those supplied,
|
||||||
|
/// and returns the associated values as a list.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use nettext::dec::decode;
|
||||||
|
///
|
||||||
|
/// let term = decode(b"{ k1 = v1, k2 = v2 }").unwrap();
|
||||||
|
/// let [s1, s2] = term.dict_of([b"k1", b"k2"]).unwrap();
|
||||||
|
/// assert_eq!(s1.str().unwrap(), b"v1");
|
||||||
|
/// assert_eq!(s2.str().unwrap(), b"v2");
|
||||||
|
/// ```
|
||||||
|
pub fn dict_of<const N: usize>(
|
||||||
|
&self,
|
||||||
|
keys: [&'static [u8]; N],
|
||||||
|
) -> Result<[Term<'a, '_>; N], TypeError> {
|
||||||
|
match self.0.mkref() {
|
||||||
|
AnyTerm::DictRef(_, dict) => {
|
||||||
|
// Check all required keys exist in dictionnary
|
||||||
|
for k in keys.iter() {
|
||||||
|
if !dict.contains_key(k) {
|
||||||
|
return Err(TypeError::MissingKey(debug(k).to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check that dictionnary contains no extraneous keys
|
||||||
|
for k in dict.keys() {
|
||||||
|
if !keys.contains(k) {
|
||||||
|
return Err(TypeError::UnexpectedKey(debug(k).to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(keys.map(|k| Term(dict.get(k).unwrap().mkref())))
|
||||||
|
}
|
||||||
|
_ => Err(TypeError::WrongType("DICT")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks term is a dictionnary whose keys are included in those supplied,
|
||||||
|
/// and returns the associated values as a list of options.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use nettext::dec::decode;
|
||||||
|
///
|
||||||
|
/// let term = decode(b"{ k1 = v1, k2 = v2 }").unwrap();
|
||||||
|
/// let [s1, s2, s3] = term.dict_of_opt([b"k1", b"k2", b"k3"]).unwrap();
|
||||||
|
/// assert_eq!(s1.unwrap().str().unwrap(), b"v1");
|
||||||
|
/// assert_eq!(s2.unwrap().str().unwrap(), b"v2");
|
||||||
|
/// assert!(s3.is_none());
|
||||||
|
/// ```
|
||||||
|
pub fn dict_of_opt<const N: usize>(
|
||||||
|
&self,
|
||||||
|
keys: [&'static [u8]; N],
|
||||||
|
) -> Result<[Option<Term<'a, '_>>; N], TypeError> {
|
||||||
|
match self.0.mkref() {
|
||||||
|
AnyTerm::DictRef(_, dict) => {
|
||||||
|
// Check that dictionnary contains no extraneous keys
|
||||||
|
for k in dict.keys() {
|
||||||
|
if !keys.contains(k) {
|
||||||
|
return Err(TypeError::UnexpectedKey(debug(k).to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(keys.map(|k| dict.get(k).map(|x| Term(x.mkref()))))
|
||||||
|
}
|
||||||
|
_ => Err(TypeError::WrongType("DICT")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- INTERNAL IMPLS ----
|
||||||
|
|
||||||
|
impl<'a, 'b> AnyTerm<'a, 'b> {
|
||||||
|
fn raw(&self) -> &'a [u8] {
|
||||||
|
match self {
|
||||||
|
AnyTerm::Str(s) => s,
|
||||||
|
AnyTerm::Dict(r, _)
|
||||||
|
| AnyTerm::DictRef(r, _)
|
||||||
|
| AnyTerm::List(r, _)
|
||||||
|
| AnyTerm::ListRef(r, _) => r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mkref(&self) -> AnyTerm<'a, '_> {
|
||||||
|
match &self {
|
||||||
|
AnyTerm::Str(s) => AnyTerm::Str(s),
|
||||||
|
AnyTerm::Dict(r, d) => AnyTerm::DictRef(r, &d),
|
||||||
|
AnyTerm::DictRef(r, d) => AnyTerm::DictRef(r, d),
|
||||||
|
AnyTerm::List(r, l) => AnyTerm::ListRef(r, &l[..]),
|
||||||
|
AnyTerm::ListRef(r, l) => AnyTerm::ListRef(r, l),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> NonListTerm<'a, 'b> {
|
||||||
|
fn raw(&self) -> &'a [u8] {
|
||||||
|
match &self {
|
||||||
|
NonListTerm::Str(s) => s,
|
||||||
|
NonListTerm::Dict(r, _) | NonListTerm::DictRef(r, _) => r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mkref(&self) -> NonListTerm<'a, '_> {
|
||||||
|
match &self {
|
||||||
|
NonListTerm::Str(s) => NonListTerm::Str(s),
|
||||||
|
NonListTerm::Dict(r, d) => NonListTerm::DictRef(r, &d),
|
||||||
|
NonListTerm::DictRef(r, d) => NonListTerm::DictRef(r, d),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- DISPLAY REPR = Raw NetText representation ----
|
||||||
|
|
||||||
|
impl<'a, 'b> std::fmt::Display for AnyTerm<'a, 'b> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
std::str::from_utf8(self.raw()).map_err(|_| Default::default())?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> std::fmt::Display for Term<'a, 'b> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- DEBUG REPR ----
|
||||||
|
|
||||||
pub fn debug<'a>(x: &'a [u8]) -> &'a str {
|
pub fn debug<'a>(x: &'a [u8]) -> &'a str {
|
||||||
std::str::from_utf8(x).unwrap_or("<invalid ascii>")
|
std::str::from_utf8(x).unwrap_or("<invalid ascii>")
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> std::fmt::Debug for Term<'a> {
|
impl<'a, 'b> std::fmt::Debug for AnyTerm<'a, 'b> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
match self {
|
match self.mkref() {
|
||||||
Term::Str(s) => write!(f, "Str(`{}`)", debug(s)),
|
AnyTerm::Str(s) => write!(f, "Str(`{}`)", debug(s)),
|
||||||
Term::Dict(raw, d) => {
|
AnyTerm::DictRef(raw, d) => {
|
||||||
write!(f, "Dict<`{}`", debug(raw))?;
|
write!(f, "Dict<`{}`", debug(raw))?;
|
||||||
for (k, v) in d.iter() {
|
for (k, v) in d.iter() {
|
||||||
write!(f, "\n `{}`={:?}", debug(k), v)?;
|
write!(f, "\n `{}`={:?}", debug(k), v)?;
|
||||||
}
|
}
|
||||||
write!(f, ">")
|
write!(f, ">")
|
||||||
}
|
}
|
||||||
Term::List(raw, l) => {
|
AnyTerm::ListRef(raw, l) => {
|
||||||
write!(f, "List[`{}`", debug(raw))?;
|
write!(f, "List[`{}`", debug(raw))?;
|
||||||
for i in l.iter() {
|
for i in l.iter() {
|
||||||
write!(f, "\n {:?}", i)?;
|
write!(f, "\n {:?}", i)?;
|
||||||
}
|
}
|
||||||
write!(f, "]")
|
write!(f, "]")
|
||||||
}
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> std::fmt::Debug for NonListTerm<'a> {
|
impl<'a, 'b> std::fmt::Debug for NonListTerm<'a, 'b> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
match self {
|
match self.mkref() {
|
||||||
NonListTerm::Str(s) => write!(f, "Str(`{}`)", debug(s)),
|
NonListTerm::Str(s) => write!(f, "Str(`{}`)", debug(s)),
|
||||||
NonListTerm::Dict(raw, d) => {
|
NonListTerm::DictRef(raw, d) => {
|
||||||
write!(f, "Dict<`{}`", debug(raw))?;
|
write!(f, "Dict<`{}`", debug(raw))?;
|
||||||
for (k, v) in d.iter() {
|
for (k, v) in d.iter() {
|
||||||
write!(f, "\n `{}`={:?}", debug(k), v)?;
|
write!(f, "\n `{}`={:?}", debug(k), v)?;
|
||||||
}
|
}
|
||||||
write!(f, ">")
|
write!(f, ">")
|
||||||
}
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue