tytanic_filter/ast/
str.rs

1use std::fmt::Debug;
2use std::ops::Deref;
3
4use ecow::{eco_vec, EcoString};
5use pest::iterators::Pair;
6
7use super::{Error, PairExt, PairsExt, Rule};
8use crate::eval::{self, Context, Eval, Test, TryFromValue, Type, Value};
9
10/// A string literal node.
11#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct Str(pub EcoString);
13
14impl Str {
15    /// The inner string.
16    pub fn as_str(&self) -> &str {
17        self.0.as_str()
18    }
19
20    /// Unwraps the inner eco string.
21    pub fn into_inner(self) -> EcoString {
22        self.0
23    }
24}
25
26impl Debug for Str {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        self.0.fmt(f)
29    }
30}
31
32impl Deref for Str {
33    type Target = str;
34
35    fn deref(&self) -> &Self::Target {
36        self.as_str()
37    }
38}
39
40impl AsRef<str> for Str {
41    fn as_ref(&self) -> &str {
42        self.as_str()
43    }
44}
45
46impl From<EcoString> for Str {
47    fn from(value: EcoString) -> Self {
48        Self(value)
49    }
50}
51
52impl From<String> for Str {
53    fn from(value: String) -> Self {
54        Self(value.into())
55    }
56}
57
58impl From<&str> for Str {
59    fn from(value: &str) -> Self {
60        Self(value.into())
61    }
62}
63
64impl From<Str> for EcoString {
65    fn from(value: Str) -> Self {
66        value.into_inner()
67    }
68}
69
70impl<T: Test> Eval<T> for Str {
71    fn eval(&self, _ctx: &Context<T>) -> Result<Value<T>, eval::Error> {
72        Ok(Value::Str(self.clone()))
73    }
74}
75
76impl<T> TryFromValue<T> for Str {
77    fn try_from_value(value: Value<T>) -> Result<Self, eval::Error> {
78        Ok(match value {
79            Value::Str(str) => str,
80            _ => {
81                return Err(eval::Error::TypeMismatch {
82                    expected: eco_vec![Type::Str],
83                    found: value.as_type(),
84                })
85            }
86        })
87    }
88}
89
90impl Str {
91    pub(super) fn parse(pair: Pair<'_, Rule>) -> Result<Self, Error> {
92        pair.expect_rules(&[Rule::str_single, Rule::str_double])?;
93
94        let mut pairs = pair.into_inner();
95        let start = pairs.expect_pair(&[Rule::str_single_delim, Rule::str_double_delim])?;
96        let inner = pairs.expect_pair(&[Rule::str_single_inner, Rule::str_double_inner])?;
97        let _ = pairs.expect_pair(&[start.as_rule()])?;
98        pairs.expect_end()?;
99
100        match inner.as_rule() {
101            Rule::str_single_inner => Ok(Self(inner.as_str().into())),
102            Rule::str_double_inner => {
103                if !inner.as_str().contains('\\') {
104                    Ok(Self(inner.as_str().into()))
105                } else {
106                    let mut buf = String::with_capacity(inner.as_str().len());
107
108                    let mut rest = inner.as_str();
109                    while let Some((lit, esc)) = rest.split_once('\\') {
110                        buf.push_str(lit);
111
112                        if esc.starts_with(['\\', '"', 'n', 'r', 't']) {
113                            match esc.as_bytes()[0] {
114                                b'\\' => buf.push('\\'),
115                                b'"' => buf.push('"'),
116                                b'n' => buf.push('\n'),
117                                b'r' => buf.push('\r'),
118                                b't' => buf.push('\t'),
119                                _ => unreachable!(),
120                            }
121                            rest = &esc[1..];
122                        } else if let Some(esc) = esc.strip_prefix("u{") {
123                            let (digits, other) =
124                                esc.split_once('}').expect("parser ensure closing '}'");
125
126                            buf.push(
127                                u32::from_str_radix(digits, 16)
128                                    .expect("parser ensures hex digits only")
129                                    .try_into()?,
130                            );
131
132                            rest = other;
133                        } else {
134                            unreachable!(
135                                "unhandled string escape sequence: {:?}",
136                                esc.split_once(' ').map(|(p, _)| p).unwrap_or(esc)
137                            );
138                        }
139                    }
140
141                    Ok(Self(buf.into()))
142                }
143            }
144            _ => unreachable!(),
145        }
146    }
147}