nettext/src/dec/decode.rs

245 lines
7.4 KiB
Rust
Raw Normal View History

use std::collections::HashMap;
use nom::{
branch::alt,
bytes::complete::{tag, take_while, take_while1},
combinator::{map, opt},
multi::{separated_list0, separated_list1},
IResult, InputLength,
};
use crate::dec::{debug, AnyTerm, NonListTerm, Term};
2022-11-17 16:55:50 +00:00
use crate::{is_string_char, is_whitespace, DICT_ASSIGN, DICT_CLOSE, DICT_DELIM, DICT_OPEN};
// ----
/// The error kind returned by the `decode` function.
#[derive(Eq, PartialEq)]
pub enum DecodeError<'a> {
/// Indicates that there is trailing garbage at the end of the decoded string
Garbage(&'a [u8]),
2022-11-17 15:35:06 +00:00
/// Indicates that the entered string does not represent a complete nettext term
IncompleteInput,
/// Indicates a syntax error in the decoded term
NomError(&'a [u8], nom::error::ErrorKind),
}
impl<'a> std::fmt::Debug for DecodeError<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
match self {
DecodeError::Garbage(g) => write!(f, "Garbage: `{}`", debug(g)),
DecodeError::IncompleteInput => write!(f, "Incomplete input"),
DecodeError::NomError(s, e) => write!(f, "Nom: {:?}, at: `{}`", e, debug(s)),
}
}
}
impl<'a> From<nom::Err<nom::error::Error<&'a [u8]>>> for DecodeError<'a> {
fn from(e: nom::Err<nom::error::Error<&'a [u8]>>) -> DecodeError<'a> {
match e {
nom::Err::Incomplete(_) => DecodeError::IncompleteInput,
nom::Err::Error(e) | nom::Err::Failure(e) => DecodeError::NomError(e.input, e.code),
}
}
}
// ----
2022-11-17 15:35:06 +00:00
/// Decodes a nettext string into the term it represents.
2022-11-17 16:55:50 +00:00
pub fn decode(input: &[u8]) -> std::result::Result<Term<'_, '_>, DecodeError<'_>> {
let (rest, term) = decode_term(input)?;
let (end, _) = take_while(is_whitespace)(rest)?;
if !end.is_empty() {
return Err(DecodeError::Garbage(end));
}
Ok(Term(term))
}
2022-11-17 16:55:50 +00:00
fn decode_term(input: &[u8]) -> IResult<&'_ [u8], AnyTerm<'_, '_>> {
let (start, _) = take_while(is_whitespace)(input)?;
let (rest, list) = separated_list1(take_while1(is_whitespace), decode_nonlist_term)(start)?;
if list.len() == 1 {
Ok((rest, list.into_iter().next().unwrap().into()))
} else {
let raw_len = start.input_len() - rest.input_len();
let list_raw = &start[..raw_len];
Ok((rest, AnyTerm::List(list_raw, list)))
}
}
2022-11-17 16:55:50 +00:00
fn decode_nonlist_term(input: &[u8]) -> IResult<&'_ [u8], NonListTerm<'_, '_>> {
let (rest, term) = alt((
map(decode_str, NonListTerm::Str),
map(decode_dict, |(raw, d)| NonListTerm::Dict(raw, d)),
))(input)?;
Ok((rest, term))
}
2022-11-17 14:02:33 +00:00
fn decode_str(input: &[u8]) -> IResult<&'_ [u8], &'_ [u8]> {
let (rest, data) = take_while1(is_string_char)(input)?;
Ok((rest, data))
}
2022-11-17 16:55:50 +00:00
type DictType<'a> = (&'a [u8], HashMap<&'a [u8], AnyTerm<'a, 'a>>);
2022-11-17 14:02:33 +00:00
fn decode_dict(dict_begin: &[u8]) -> IResult<&'_ [u8], DictType<'_>> {
2022-11-17 16:55:50 +00:00
let (d, _) = tag(&[DICT_OPEN][..])(dict_begin)?;
let (d, items) = separated_list0(dict_separator, decode_dict_item)(d)?;
let (d, _) = opt(dict_separator)(d)?;
let (d, _) = take_while(is_whitespace)(d)?;
2022-11-17 16:55:50 +00:00
let (dict_end, _) = tag(&[DICT_CLOSE][..])(d)?;
let dict = items.into_iter().collect::<HashMap<_, _>>();
let raw_len = dict_begin.input_len() - dict_end.input_len();
let dict_raw = &dict_begin[..raw_len];
Ok((dict_end, (dict_raw, dict)))
}
2022-11-17 14:02:33 +00:00
fn dict_separator(d: &[u8]) -> IResult<&'_ [u8], ()> {
let (d, _) = take_while(is_whitespace)(d)?;
2022-11-17 16:55:50 +00:00
let (d, _) = tag(&[DICT_DELIM][..])(d)?;
Ok((d, ()))
}
2022-11-17 16:55:50 +00:00
fn decode_dict_item(d: &[u8]) -> IResult<&'_ [u8], (&'_ [u8], AnyTerm<'_, '_>)> {
let (d, _) = take_while(is_whitespace)(d)?;
let (d, key) = decode_str(d)?;
let (d, _) = take_while(is_whitespace)(d)?;
2022-11-17 16:55:50 +00:00
let (d, _) = tag(&[DICT_ASSIGN][..])(d)?;
let (d, value) = decode_term(d)?;
Ok((d, (key, value)))
}
// ----
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_str() {
let bytes = b" plop ";
assert_eq!(decode(bytes), Ok(AnyTerm::Str(b"plop").into()));
}
#[test]
fn list_of_str_str() {
let bytes = b" plop plap plip ploup ";
assert_eq!(
decode(bytes),
Ok(AnyTerm::List(
b"plop plap plip ploup",
vec![
NonListTerm::Str(b"plop"),
NonListTerm::Str(b"plap"),
NonListTerm::Str(b"plip"),
NonListTerm::Str(b"ploup"),
]
)
.into())
);
}
#[test]
fn simple_dict() {
let bytes = b" { aze = hello, by = bojzkz pipo, ccde = ke } ";
assert_eq!(
decode(bytes),
Ok(AnyTerm::Dict(
b"{ aze = hello, by = bojzkz pipo, ccde = ke }",
[
(&b"aze"[..], AnyTerm::Str(b"hello")),
(
&b"by"[..],
AnyTerm::List(
b"bojzkz pipo",
vec![NonListTerm::Str(b"bojzkz"), NonListTerm::Str(b"pipo")]
)
),
(&b"ccde"[..], AnyTerm::Str(b"ke")),
]
.into_iter()
.collect()
)
.into())
);
}
#[test]
fn simple_dict_2() {
let bytes = b" { aze = hello, by = bojzkz pipo , ccde = ke , } ";
assert_eq!(
decode(bytes),
Ok(AnyTerm::Dict(
b"{ aze = hello, by = bojzkz pipo , ccde = ke , }",
[
(&b"aze"[..], AnyTerm::Str(b"hello")),
(
&b"by"[..],
AnyTerm::List(
b"bojzkz pipo",
vec![NonListTerm::Str(b"bojzkz"), NonListTerm::Str(b"pipo")]
)
),
(&b"ccde"[..], AnyTerm::Str(b"ke")),
]
.into_iter()
.collect()
)
.into())
);
}
#[test]
fn real_world_1() {
let bytes = b"HEAD alexpubkey";
assert_eq!(
decode(bytes),
Ok(AnyTerm::List(
b"HEAD alexpubkey",
vec![NonListTerm::Str(b"HEAD"), NonListTerm::Str(b"alexpubkey")]
)
.into()),
);
}
#[test]
fn real_world_2() {
let bytes = b"STANCE sthash stsign { author = alexpubkey, height = 12, parent = parenthash, data = MESSAGE { text = hello } }";
assert_eq!(
decode(bytes),
Ok(AnyTerm::List(
&bytes[..],
vec![
NonListTerm::Str(b"STANCE"),
NonListTerm::Str(b"sthash"),
NonListTerm::Str(b"stsign"),
NonListTerm::Dict(b"{ author = alexpubkey, height = 12, parent = parenthash, data = MESSAGE { text = hello } }",
[
(&b"author"[..], AnyTerm::Str(b"alexpubkey")),
(&b"height"[..], AnyTerm::Str(b"12")),
(&b"parent"[..], AnyTerm::Str(b"parenthash")),
(&b"data"[..], AnyTerm::List(
b"MESSAGE { text = hello }",
vec![
NonListTerm::Str(b"MESSAGE"),
NonListTerm::Dict(
b"{ text = hello }",
[
(&b"text"[..], AnyTerm::Str(b"hello")),
]
.into_iter()
.collect()
)
]
))
].into_iter().collect()
),
]).into(),
));
}
}