#![allow(clippy::enum_variant_names)]
use super::string_helpers;
use crate::value::{BitSequence, Composite, Primitive, Value, Variant};
use std::num::ParseIntError;
use yap::{IntoTokens, TokenLocation, Tokens};
pub fn from_str(s: &str) -> (Result<Value<()>, ParseError>, &str) {
let mut toks = s.into_tokens();
let res = parse_value(&mut toks);
let remaining = toks.remaining();
(res, remaining)
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub struct ParseError {
pub start_loc: usize,
pub end_loc: Option<usize>,
pub err: ParseErrorKind,
}
impl ParseError {
fn new_at(err: ParseErrorKind, loc: usize) -> Self {
Self { start_loc: loc, end_loc: None, err }
}
fn new_between(err: ParseErrorKind, start: usize, end: usize) -> Self {
Self { start_loc: start, end_loc: Some(end), err }
}
}
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(end_loc) = self.end_loc {
write!(f, "Error from char {} to {}: {}", self.start_loc, end_loc, self.err)
} else {
write!(f, "Error at char {}: {}", self.start_loc, self.err)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ParseErrorKind {
#[error("Expected a value")]
ExpectedValue,
#[error("{0}")]
Complex(#[from] ParseComplexError),
#[error("{0}")]
Char(#[from] ParseCharError),
#[error("{0}")]
String(#[from] ParseStringError),
#[error("{0}")]
Number(#[from] ParseNumberError),
#[error("{0}")]
BitSequence(#[from] ParseBitSequenceError),
}
macro_rules! at_between {
($ty:ident) => {
impl $ty {
#[allow(unused)]
fn at(self, loc: usize) -> ParseError {
let e: ParseErrorKind = self.into();
ParseError::new_at(e, loc)
}
#[allow(unused)]
fn at_one(self, loc: usize) -> ParseError {
let e: ParseErrorKind = self.into();
ParseError::new_between(e, loc, loc + 1)
}
#[allow(unused)]
fn between(self, start: usize, end: usize) -> ParseError {
let e: ParseErrorKind = self.into();
ParseError::new_between(e, start, end)
}
}
};
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ParseComplexError {
#[error("The first character in a field name should be alphabetic")]
InvalidStartingCharacterInIdent,
#[error("Field name is not valid (it should begin with an alphabetical char and then consist only of alphanumeric chars)")]
InvalidFieldName,
#[error("Missing field separator; expected {0}")]
MissingFieldSeparator(char),
#[error("Missing closing '{0}'")]
ExpectedCloserToMatch(char, usize),
}
at_between!(ParseComplexError);
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ParseCharError {
#[error("Expected a single character")]
ExpectedValidCharacter,
#[error("Expected an escape code to follow the '\\'")]
ExpectedValidEscapeCode,
#[error("Expected a closing quote to match the opening quote at position {0}")]
ExpectedClosingQuoteToMatch(usize),
}
at_between!(ParseCharError);
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ParseStringError {
#[error("Expected a closing quote to match the opening quote at position {0}")]
ExpectedClosingQuoteToMatch(usize),
#[error("Expected an escape code to follow the '\\'")]
ExpectedValidEscapeCode,
}
at_between!(ParseStringError);
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ParseNumberError {
#[error("Expected one or more digits")]
ExpectedDigit,
#[error("Failed to parse digits into an integer: {0}")]
ParsingFailed(ParseIntError),
}
at_between!(ParseNumberError);
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ParseBitSequenceError {
#[error("Expected a closing bracket ('>') to match the opening one at position {0}")]
ExpectedClosingBracketToMatch(usize),
#[error("Invalid character; expecting a 0 or 1")]
InvalidCharacter,
}
at_between!(ParseBitSequenceError);
fn parse_value(t: &mut impl Tokens<Item = char>) -> Result<Value<()>, ParseError> {
let val = yap::one_of!(t;
transpose_err(parse_bool(t).map(Value::bool).ok_or(None)),
transpose_err(parse_char(t).map(Value::char)),
transpose_err(parse_string(t).map(Value::string)),
transpose_err(parse_number(t).map(Value::primitive)),
transpose_err(parse_named_composite(t).map(|v| v.into())),
transpose_err(parse_unnamed_composite(t).map(|v| v.into())),
transpose_err(parse_bit_sequence(t).map(Value::bit_sequence)),
transpose_err(parse_variant(t).map(|v| v.into())),
);
match val {
Some(Ok(val)) => Ok(val),
Some(Err(e)) => Err(e),
None => Err(ParseError::new_at(ParseErrorKind::ExpectedValue, t.offset())),
}
}
fn parse_named_composite(
t: &mut impl Tokens<Item = char>,
) -> Result<Composite<()>, Option<ParseError>> {
let start = t.offset();
if !t.token('{') {
return Err(None);
}
skip_whitespace(t);
if t.token('}') {
return Ok(Composite::Named(vec![]));
}
let vals = t
.sep_by_err(|t| parse_field_name_and_value(t), |t| skip_spaced_separator(t, ','))
.collect::<Result<_, _>>()?;
skip_whitespace(t);
if !t.token('}') {
return Err(Some(ParseComplexError::ExpectedCloserToMatch('}', start).at_one(t.offset())));
}
Ok(Composite::Named(vals))
}
fn parse_unnamed_composite(
t: &mut impl Tokens<Item = char>,
) -> Result<Composite<()>, Option<ParseError>> {
let start = t.offset();
if !t.token('(') {
return Err(None);
}
skip_whitespace(t);
if t.token(')') {
return Ok(Composite::Unnamed(vec![]));
}
let vals = t
.sep_by_err(|t| parse_value(t), |t| skip_spaced_separator(t, ','))
.collect::<Result<_, _>>()?;
skip_whitespace(t);
if !t.token(')') {
return Err(Some(ParseComplexError::ExpectedCloserToMatch(')', start).at_one(t.offset())));
}
Ok(Composite::Unnamed(vals))
}
fn parse_variant(t: &mut impl Tokens<Item = char>) -> Result<Variant<()>, Option<ParseError>> {
let ident = match parse_optional_variant_ident(t) {
Some(ident) => ident,
None => return Err(None),
};
skip_whitespace(t);
let composite = yap::one_of!(t;
transpose_err(parse_named_composite(t)),
transpose_err(parse_unnamed_composite(t))
);
match composite {
Some(Ok(values)) => Ok(Variant { name: ident, values }),
Some(Err(e)) => Err(Some(e)),
None => Err(None),
}
}
fn parse_bit_sequence(t: &mut impl Tokens<Item = char>) -> Result<BitSequence, Option<ParseError>> {
let start = t.offset();
if !t.token('<') {
return Err(None);
}
let bits = t.tokens_while(|&c| c == '0' || c == '1').map(|c| c == '1');
let mut seq = BitSequence::new();
for bit in bits {
seq.push(bit);
}
if !t.token('>') {
return Err(Some(
ParseBitSequenceError::ExpectedClosingBracketToMatch(start)
.between(t.offset(), t.offset() + 1),
));
}
Ok(seq)
}
fn parse_bool(t: &mut impl Tokens<Item = char>) -> Option<bool> {
if t.tokens("true".chars()) {
Some(true)
} else if t.tokens("false".chars()) {
Some(false)
} else {
None
}
}
fn parse_char(t: &mut impl Tokens<Item = char>) -> Result<char, Option<ParseError>> {
let start = t.offset();
if !t.token('\'') {
return Err(None);
}
let char = match t.next() {
None => return Err(Some(ParseCharError::ExpectedValidCharacter.at_one(t.offset()))),
Some(c) => c,
};
let char = if char == '\\' {
let escape_code = match t.next() {
None => return Err(Some(ParseCharError::ExpectedValidEscapeCode.at_one(t.offset()))),
Some(c) => c,
};
match string_helpers::from_escape_code(escape_code) {
None => return Err(Some(ParseCharError::ExpectedValidEscapeCode.at_one(t.offset()))),
Some(c) => c,
}
} else {
char
};
if !t.token('\'') {
return Err(Some(ParseCharError::ExpectedClosingQuoteToMatch(start).at_one(t.offset())));
}
Ok(char)
}
fn parse_number(t: &mut impl Tokens<Item = char>) -> Result<Primitive, Option<ParseError>> {
let start_loc = t.offset();
let is_positive = t.token('+') || !t.token('-');
let sign = if is_positive { "".chars() } else { "-".chars() };
let mut seen_n = false;
let digits = t
.tokens_while(|c| {
if c.is_ascii_digit() {
seen_n = true;
true
} else {
seen_n && *c == '_'
}
})
.filter(|c| c.is_ascii_digit());
let n_str: String = sign.chain(digits).collect();
let end_loc = t.offset();
if end_loc == start_loc {
return Err(None);
}
if !seen_n {
return Err(Some(ParseNumberError::ExpectedDigit.between(end_loc, end_loc + 1)));
}
if is_positive {
n_str
.parse::<u128>()
.map(Primitive::u128)
.map_err(|e| Some(ParseNumberError::ParsingFailed(e).between(start_loc, end_loc)))
} else {
n_str
.parse::<i128>()
.map(Primitive::i128)
.map_err(|e| Some(ParseNumberError::ParsingFailed(e).between(start_loc, end_loc)))
}
}
fn parse_string(t: &mut impl Tokens<Item = char>) -> Result<String, Option<ParseError>> {
let start = t.offset();
if !t.token('"') {
return Err(None);
}
let mut out: String = String::new();
let mut next_is_escaped = false;
loop {
let pos = t.offset();
let char = match t.next() {
Some(c) => c,
None => {
return Err(Some(
ParseStringError::ExpectedClosingQuoteToMatch(start).at_one(t.offset()),
))
}
};
match char {
'\\' if !next_is_escaped => {
next_is_escaped = true;
}
c if next_is_escaped => match string_helpers::from_escape_code(c) {
Some(c) => {
out.push(c);
next_is_escaped = false;
}
None => {
return Err(Some(
ParseStringError::ExpectedValidEscapeCode.between(pos, pos + 1),
))
}
},
'"' => {
break; }
c => {
out.push(c);
}
}
}
Ok(out)
}
fn parse_field_name_and_value(
t: &mut impl Tokens<Item = char>,
) -> Result<(String, Value<()>), ParseError> {
let name = parse_field_name(t)?;
if !skip_spaced_separator(t, ':') {
return Err(ParseComplexError::MissingFieldSeparator(':').at_one(t.offset()));
}
let value = parse_value(t)?;
Ok((name, value))
}
fn parse_field_name(t: &mut impl Tokens<Item = char>) -> Result<String, ParseError> {
let field_name = yap::one_of!(t;
transpose_err(parse_string(t)),
Some(parse_ident(t)),
);
match field_name {
Some(Ok(name)) => Ok(name),
Some(Err(e)) => Err(e),
None => Err(ParseComplexError::InvalidFieldName.at(t.offset())),
}
}
fn parse_optional_variant_ident(t: &mut impl Tokens<Item = char>) -> Option<String> {
fn parse_i_string(t: &mut impl Tokens<Item = char>) -> Option<String> {
if t.next()? != 'v' {
return None;
}
parse_string(t).ok()
}
yap::one_of!(t;
parse_i_string(t),
parse_ident(t).ok()
)
}
fn parse_ident(t: &mut impl Tokens<Item = char>) -> Result<String, ParseError> {
let start = t.location();
if t.skip_tokens_while(|c| c.is_alphabetic()) == 0 {
return Err(ParseComplexError::InvalidStartingCharacterInIdent.at_one(start.offset()));
}
t.skip_tokens_while(|c| c.is_alphanumeric() || *c == '_');
let end = t.location();
let ident_str = t.slice(start, end).collect();
Ok(ident_str)
}
fn skip_whitespace(t: &mut impl Tokens<Item = char>) {
t.skip_tokens_while(|c| c.is_whitespace());
}
fn skip_spaced_separator(t: &mut impl Tokens<Item = char>, s: char) -> bool {
skip_whitespace(t);
let is_sep = t.token(s);
skip_whitespace(t);
is_sep
}
fn transpose_err<T, E>(r: Result<T, Option<E>>) -> Option<Result<T, E>> {
match r {
Ok(val) => Some(Ok(val)),
Err(Some(e)) => Some(Err(e)),
Err(None) => None,
}
}
#[cfg(test)]
mod test {
use super::*;
fn from(s: &str) -> Result<Value<()>, ParseError> {
let (res, remaining) = from_str(s);
if res.is_ok() {
assert_eq!(remaining.len(), 0, "was not expecting any unparsed output");
}
res
}
#[test]
fn parse_bools() {
assert_eq!(from("true"), Ok(Value::bool(true)));
assert_eq!(from("false"), Ok(Value::bool(false)));
}
#[test]
fn parse_numbers() {
assert_eq!(from("123"), Ok(Value::u128(123)));
assert_eq!(from("1_234_56"), Ok(Value::u128(123_456)));
assert_eq!(from("+1_234_56"), Ok(Value::u128(123_456)));
assert_eq!(from("-123_4"), Ok(Value::i128(-1234)));
assert_eq!(from("-abc"), Err(ParseNumberError::ExpectedDigit.between(1, 2)));
}
#[test]
fn parse_chars() {
assert_eq!(from("'a'"), Ok(Value::char('a')));
assert_eq!(from("'😀'"), Ok(Value::char('😀')));
assert_eq!(from("'\\n'"), Ok(Value::char('\n')));
assert_eq!(from("'\\t'"), Ok(Value::char('\t')));
assert_eq!(from("'\\\"'"), Ok(Value::char('"')));
assert_eq!(from("'\\\''"), Ok(Value::char('\'')));
assert_eq!(from("'\\r'"), Ok(Value::char('\r')));
assert_eq!(from("'\\\\'"), Ok(Value::char('\\')));
assert_eq!(from("'\\0'"), Ok(Value::char('\0')));
assert_eq!(from("'a"), Err(ParseCharError::ExpectedClosingQuoteToMatch(0).at_one(2)));
}
#[test]
fn parse_strings() {
assert_eq!(from("\"\\n \\r \\t \\0 \\\"\""), Ok(Value::string("\n \r \t \0 \"")));
assert_eq!(from("\"Hello there 😀\""), Ok(Value::string("Hello there 😀")));
assert_eq!(from("\"Hello\\n\\t there\""), Ok(Value::string("Hello\n\t there")));
assert_eq!(from("\"Hello\\\\ there\""), Ok(Value::string("Hello\\ there")));
assert_eq!(
from("\"Hello\\p there\""),
Err(ParseStringError::ExpectedValidEscapeCode.between(7, 8))
);
assert_eq!(from("\"Hi"), Err(ParseStringError::ExpectedClosingQuoteToMatch(0).at_one(3)));
}
#[test]
fn parse_unnamed_composites() {
assert_eq!(
from("( true, 1234 ,\t\n\t \"Hello!\" )"),
Ok(Value::unnamed_composite(vec![
Value::bool(true),
Value::u128(1234),
Value::string("Hello!")
]))
);
assert_eq!(from("()"), Ok(Value::unnamed_composite([])));
assert_eq!(from("(\n\n\t\t\n)"), Ok(Value::unnamed_composite([])));
}
#[test]
fn parse_named_composites() {
assert_eq!(
from(
"{
hello: true,
foo: 1234,
\"Hello there 😀\": \"Hello!\"
}"
),
Ok(Value::named_composite([
("hello", Value::bool(true)),
("foo", Value::u128(1234)),
("Hello there 😀", Value::string("Hello!"))
]))
);
}
#[test]
fn parse_variants() {
assert_eq!(
from(
"MyVariant {
hello: true,
foo: 1234,
\"Hello there 😀\": \"Hello!\"
}"
),
Ok(Value::named_variant(
"MyVariant",
[
("hello", Value::bool(true)),
("foo", Value::u128(1234)),
("Hello there 😀", Value::string("Hello!"))
]
))
);
assert_eq!(
from("Foo ( true, 1234 ,\t\n\t \"Hello!\" )"),
Ok(Value::unnamed_variant(
"Foo",
vec![Value::bool(true), Value::u128(1234), Value::string("Hello!")]
))
);
assert_eq!(from("Foo()"), Ok(Value::unnamed_variant("Foo", [])));
assert_eq!(from("Foo{}"), Ok(Value::named_variant::<_, String, _>("Foo", [])));
assert_eq!(from("Foo( \t)"), Ok(Value::unnamed_variant("Foo", [])));
assert_eq!(from("Foo{ }"), Ok(Value::named_variant::<_, String, _>("Foo", [])));
assert_eq!(
from("v\"variant name\" { }"),
Ok(Value::named_variant::<_, String, _>("variant name", []))
);
}
#[test]
fn parse_bit_sequences() {
use scale_bits::bits;
assert_eq!(
from("<011010110101101>"),
Ok(Value::bit_sequence(bits![0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1]))
);
assert_eq!(from("<01101>"), Ok(Value::bit_sequence(bits![0, 1, 1, 0, 1])));
assert_eq!(from("<0>"), Ok(Value::bit_sequence(bits![0])));
assert_eq!(from("<>"), Ok(Value::bit_sequence(bits![])));
}
}