use pest::Parser;
use pest_derive::Parser;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use crate::{
core::Unique,
token::{Sequence, Token, TokenParsingError},
};
mod _parser {
use super::*;
#[derive(Parser)]
#[grammar = "layout.pest"]
pub(crate) struct LayoutParser;
}
pub(crate) use _parser::{LayoutParser, Rule};
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct OrdTokenLayout(Vec<Token>);
impl AsRef<[Token]> for OrdTokenLayout {
#[inline]
fn as_ref(&self) -> &[Token] {
&self.0
}
}
impl OrdTokenLayout {
#[inline]
pub fn iter_tokens(&self) -> std::slice::Iter<'_, Token> {
self.0.iter()
}
#[inline]
pub fn iter_sequences(&self) -> impl Iterator<Item = &Sequence> {
self.iter_tokens().flat_map(Token::iter)
}
#[doc(alias = "glyph count")]
#[doc(alias = "region count")]
#[doc(alias = "unique sequence")]
#[inline]
pub fn unique<'a>(&'a self) -> Unique<'a> {
self.iter_tokens().into()
}
#[inline]
pub fn with(mut self, token: impl Into<Token>) -> Self {
self.0.push(token.into());
self
}
#[inline]
pub fn push(&mut self, token: impl Into<Token>) {
self.0.push(token.into());
}
pub fn parse(input: impl AsRef<str>) -> Self {
input.as_ref().parse().unwrap()
}
}
impl FromStr for OrdTokenLayout {
type Err = TokenParsingError;
#[inline]
fn from_str(input: &str) -> Result<Self, Self::Err> {
let pairs = LayoutParser::parse(Rule::tokens, input)?;
let mut tokens = Vec::new();
let mut sub_tokens = Vec::new();
let mut string = String::new();
for pair in pairs {
match pair.as_rule() {
Rule::any_char => {
let s = pair.as_str();
if s.is_empty() {
return Err(TokenParsingError::EmptyToken);
} else {
tokens.push(Token::from(Sequence::new_unchecked(s)));
}
}
Rule::multi_union => {
let sub_sequences = pair.into_inner();
for sub in sub_sequences {
#[cfg(debug_assertions)]
debug_assert!(
matches!(sub.as_rule(), Rule::sub),
"Expected sub rule in multi_union sequence, found {:?}",
sub.as_rule()
);
for seq in sub.into_inner() {
#[cfg(debug_assertions)]
debug_assert!(
matches!(seq.as_rule(), Rule::any_char | Rule::RESERVED),
"Expected any_char or RESERVED rule in multi_union::sub::seq, found {:?}",
seq.as_rule()
);
let s = seq.as_str();
string.push_str(s);
}
if string.is_empty() {
return Err(TokenParsingError::EmptyToken);
}
sub_tokens.push(Sequence::new_unchecked(std::mem::take(&mut string)));
}
tokens.push(Token::from(std::mem::take(&mut sub_tokens)));
}
Rule::EOI => {
break;
}
unknown => {
unreachable!("Unexpected rule while parsing layout tokens: {:?}", unknown)
}
}
}
#[cfg(debug_assertions)]
debug_assert!(
sub_tokens.is_empty(),
"Sequences should be cleared after processing a multi_union"
);
Ok(Self(tokens))
}
}
impl<'de> Deserialize<'de> for OrdTokenLayout {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct RasterFontLayoutVisitor;
impl<'de> serde::de::Visitor<'de> for RasterFontLayoutVisitor {
type Value = OrdTokenLayout;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a raster font layout string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
v.parse().map_err(serde::de::Error::custom)
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
v.parse().map_err(serde::de::Error::custom)
}
fn visit_char<E>(self, v: char) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(OrdTokenLayout(vec![Token::from(Sequence::from(v))]))
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let s = std::str::from_utf8(v).map_err(serde::de::Error::custom)?;
s.parse().map_err(serde::de::Error::custom)
}
}
deserializer.deserialize_any(RasterFontLayoutVisitor)
}
}
impl Serialize for OrdTokenLayout {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut s = String::new();
for token in &self.0 {
s.push_str(&token.to_string());
}
serializer.serialize_str(&s)
}
}
impl std::fmt::Display for OrdTokenLayout {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for token in &self.0 {
write!(f, "{}", token)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vec_parser() {
let text = r#"a$\\($(bc)d$(ef)$($g)$(h|(H) |$(x|y)$(|||)$(||a|)$(\||a|)"#;
let layout = OrdTokenLayout::parse(text);
let expected = OrdTokenLayout(vec![
Token::from(Sequence::from('a')),
Token::from(Sequence::from('$')),
Token::from(Sequence::from('\\')),
Token::from(Sequence::from('(')),
Token::from(Sequence::new_unchecked("bc")),
Token::from(Sequence::from('d')),
Token::from(Sequence::new_unchecked("ef")),
Token::from(Sequence::new_unchecked("$g")),
Token::from(vec![Sequence::from('h'), Sequence::new_unchecked("(H")]),
Token::from(Sequence::from(' ')),
Token::from(Sequence::from('|')),
Token::from(vec![Sequence::from('x'), Sequence::from('y')]),
Token::from(Sequence::from('a')),
Token::from(vec![Sequence::from('|'), Sequence::from('a')]),
]);
for (i, token) in layout.iter_tokens().enumerate() {
println!("Token {}: {}", i, token);
assert_eq!(token, &expected.0[i]);
}
}
fn remove_spaces(s: &str) -> String {
s.chars().filter(|c| !c.is_whitespace()).collect()
}
#[test]
fn can_escape() {
let text = r#"$( a \4 \$(b\) | $b )c\d\$($\()$(x)"#;
let text = remove_spaces(text);
let tokens = OrdTokenLayout::from_str(&text).unwrap();
let expected = OrdTokenLayout(vec![
Token::from(vec![
Sequence::new_unchecked("a4$(b)"),
Sequence::new_unchecked("$b"),
]),
Token::from(Sequence::new_unchecked("c")),
Token::from(Sequence::new_unchecked("d")),
Token::from(Sequence::new_unchecked("$")),
Token::from(Sequence::new_unchecked("(")),
Token::from(Sequence::new_unchecked("$")),
Token::from(Sequence::new_unchecked("(")),
Token::from(Sequence::new_unchecked(")")),
Token::from(Sequence::new_unchecked("x")),
]);
assert_eq!(tokens, expected);
}
#[test]
fn with_spaces() -> Result<(), TokenParsingError> {
let text = r#"$( a \4 \$(b\) | $b )"#;
let tokens = OrdTokenLayout::from_str(text)?;
let expected = OrdTokenLayout(vec![Token::from(vec![
Sequence::new_unchecked(" a 4 $(b) "),
Sequence::new_unchecked(" $b "),
])]);
assert_eq!(tokens, expected);
Ok(())
}
}