rsexpr/
lib.rs

1//! # rsexpr
2//!
3//! Small and simple S-expression parsing and manipulation with support for
4//! square-bracketed groups and strings. Used by
5//! [syntastica](https://crates.io/crates/syntastica) for processing tree-sitter
6//! queries.
7//!
8//! Have a look at [`Sexpr`], [`OwnedSexpr`], [`from_slice`], and [`from_slice_multi`] for more
9//! information.
10#![cfg_attr(
11    feature = "docs",
12    cfg_attr(doc, doc = ::document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#))
13)]
14#![cfg_attr(all(doc, CHANNEL_NIGHTLY), feature(doc_auto_cfg))]
15#![warn(rust_2018_idioms)]
16#![deny(missing_docs)]
17
18mod display;
19mod error;
20mod lex;
21mod parser;
22
23use std::{
24    borrow::Cow,
25    fmt::{self, Display, Formatter},
26    ops::{Deref, DerefMut},
27};
28
29pub use error::*;
30use lex::Token;
31use parser::Parser;
32
33/// Parse a [`Sexpr`] from bytes. This fails if there is more than one S-expression in the
34/// input. To allow an arbitrary amount of S-expressions, have a look at [`from_slice_multi`].
35///
36/// ## Example
37/// ```
38/// let sexpr = rsexpr::from_slice(b"((\"foo bar\")(baz [1 2 3]))").unwrap();
39/// println!("{sexpr:#}");
40/// if let rsexpr::Sexpr::List(list) = sexpr {
41///     assert_eq!(list.len(), 2);
42/// }
43/// ```
44///
45/// ## Errors
46/// If the parsing failed, a list of [`Error`]s is returned.
47/// Additionally, the function will fail if the input does not contain exactly _one_ S-expression
48/// (see [`Error::EmptyInput`] and [`Error::ExtraSexprs`]).
49pub fn from_slice(input: &(impl AsRef<[u8]> + ?Sized)) -> Result<Sexpr<'_>> {
50    let (mut sexprs, mut errors) = Parser::parse(lex::lex(input.as_ref()));
51    if sexprs.len() > 1 {
52        errors.push(Error::ExtraSexprs);
53    }
54    match errors.is_empty() {
55        true => match sexprs.is_empty() {
56            true => Err(vec![Error::EmptyInput]),
57            false => Ok(sexprs.swap_remove(0)),
58        },
59        false => Err(errors),
60    }
61}
62
63/// Parse multiple [`Sexpr`]s from bytes. To only parse a single one, have a look at
64/// [`from_slice`].
65///
66/// ## Example
67/// ```
68/// let sexprs = rsexpr::from_slice_multi(b"(\"foo bar\") (baz [1 2 3])").unwrap();
69/// for sexpr in &sexprs {
70///     println!("{sexpr:#}\n");
71/// }
72/// assert_eq!(sexprs.len(), 2);
73/// ```
74///
75/// ## Errors
76/// If the parsing failed, a list of [`Error`]s is returned.
77pub fn from_slice_multi(input: &(impl AsRef<[u8]> + ?Sized)) -> Result<Sexprs<'_>> {
78    let (sexprs, errors) = Parser::parse(lex::lex(input.as_ref()));
79    match errors.is_empty() {
80        true => Ok(sexprs),
81        false => Err(errors),
82    }
83}
84
85/// A thin wrapper around `Vec<Sexpr>` with its own [`Display`] implementation.
86/// See [`Sexpr`] for more information.
87#[derive(Debug, Clone, Hash, PartialEq, Eq)]
88pub struct Sexprs<'src>(Vec<Sexpr<'src>>);
89
90/// A thin wrapper around `Vec<OwnedSexpr>` with its own [`Display`] implementation.
91/// See [`OwnedSexpr`] for more information.
92#[derive(Debug, Clone, Hash, PartialEq, Eq)]
93pub struct OwnedSexprs(Vec<OwnedSexpr>);
94
95/// A single node of the tree. The [`Atom`](Sexpr::Atom) and [`String`](Sexpr::String) variants
96/// reference the input slice. For an owned version have a look at [`OwnedSexpr`].
97///
98/// ## Display
99/// [`Sexpr`] implements the [`Display`] trait for serializing to strings. By default, the output
100/// will try to minimize the amount of spaces used and the resulting output will be on one line.
101/// Enabling the formatter's `alternate` flag using `#`, causes the output to be human-friendly /
102/// pretty-printed. Setting the `precision` with `.` additionally allows to specify the number of
103/// spaces used for indentation (2 by default).
104///
105/// For example:
106///
107/// ```
108/// let sexpr = rsexpr::from_slice(b"[ a b c ]").unwrap();
109/// assert_eq!(format!("{sexpr}"), "[a b c]");
110/// # #[rustfmt::skip]
111/// assert_eq!(format!("{sexpr:#}"), "[
112///   a
113///   b
114///   c
115/// ]");
116/// # #[rustfmt::skip]
117/// assert_eq!(format!("{sexpr:#.4}"), "[
118///     a
119///     b
120///     c
121/// ]");
122/// ```
123#[derive(Debug, Clone, Hash, PartialEq, Eq)]
124pub enum Sexpr<'src> {
125    /// A list of [`Sexpr`]s surrounded by parentheses `(`, `)`
126    List(Sexprs<'src>),
127    /// A list of [`Sexpr`]s surrounded by brackets `[`, `]`
128    Group(Sexprs<'src>),
129    /// A sequence of bytes surrounded by quotes `"`
130    String(Cow<'src, [u8]>),
131    /// A sequence of bytes not including whitespace, parens, and quotes
132    Atom(&'src [u8]),
133    /// A line comment, including the leading `;`
134    #[cfg(feature = "comments")]
135    Comment(&'src [u8]),
136}
137
138/// An owned version of [`Sexpr`]. You can convert to and from [`Sexpr`] using the [`From`] trait.
139///
140/// ## Display
141/// [`OwnedSexpr`] implements the [`Display`] trait for serializing to strings. By default, the output
142/// will try to minimize the amount of spaces used and the resulting output will be on one line.
143/// Enabling the formatter's `alternate` flag using `#`, causes the output to be human-friendly /
144/// pretty-printed. Setting the `precision` with `.` additionally allows to specify the number of
145/// spaces used for indentation (2 by default).
146///
147/// For example:
148///
149/// ```
150/// let sexpr = rsexpr::from_slice(b"[ a b c ]").unwrap();
151/// assert_eq!(format!("{sexpr}"), "[a b c]");
152/// assert_eq!(format!("{sexpr:#}"), "[
153///   a
154///   b
155///   c
156/// ]");
157/// assert_eq!(format!("{sexpr:#.4}"), "[
158///     a
159///     b
160///     c
161/// ]");
162/// ```
163#[derive(Debug, Clone, Hash, PartialEq, Eq)]
164pub enum OwnedSexpr {
165    /// A list of [`OwnedSexpr`]s surrounded by parentheses `(`, `)`
166    List(OwnedSexprs),
167    /// A list of [`OwnedSexpr`]s surrounded by brackets `[`, `]`
168    Group(OwnedSexprs),
169    /// A sequence of bytes surrounded by quotes `"`
170    String(Vec<u8>),
171    /// A sequence of bytes not including whitespace, parens, and quotes
172    Atom(Vec<u8>),
173    /// A line comment, including the leading `;`
174    #[cfg(feature = "comments")]
175    Comment(Vec<u8>),
176}
177
178///////////////////////////
179// Trait implementations //
180///////////////////////////
181
182impl<'src> Deref for Sexprs<'src> {
183    type Target = Vec<Sexpr<'src>>;
184
185    fn deref(&self) -> &Self::Target {
186        &self.0
187    }
188}
189
190impl Deref for OwnedSexprs {
191    type Target = Vec<OwnedSexpr>;
192
193    fn deref(&self) -> &Self::Target {
194        &self.0
195    }
196}
197
198impl DerefMut for Sexprs<'_> {
199    fn deref_mut(&mut self) -> &mut Self::Target {
200        &mut self.0
201    }
202}
203
204impl DerefMut for OwnedSexprs {
205    fn deref_mut(&mut self) -> &mut Self::Target {
206        &mut self.0
207    }
208}
209
210impl<'src> FromIterator<Sexpr<'src>> for Sexprs<'src> {
211    fn from_iter<T: IntoIterator<Item = Sexpr<'src>>>(iter: T) -> Self {
212        Self(Vec::from_iter(iter))
213    }
214}
215
216impl FromIterator<OwnedSexpr> for OwnedSexprs {
217    fn from_iter<T: IntoIterator<Item = OwnedSexpr>>(iter: T) -> Self {
218        Self(Vec::from_iter(iter))
219    }
220}
221
222impl<'src> IntoIterator for Sexprs<'src> {
223    type Item = Sexpr<'src>;
224    type IntoIter = std::vec::IntoIter<Sexpr<'src>>;
225
226    fn into_iter(self) -> Self::IntoIter {
227        self.0.into_iter()
228    }
229}
230
231impl IntoIterator for OwnedSexprs {
232    type Item = OwnedSexpr;
233    type IntoIter = std::vec::IntoIter<OwnedSexpr>;
234
235    fn into_iter(self) -> Self::IntoIter {
236        self.0.into_iter()
237    }
238}
239
240impl<'a, 'src> IntoIterator for &'a Sexprs<'src> {
241    type Item = &'a Sexpr<'src>;
242    type IntoIter = std::slice::Iter<'a, Sexpr<'src>>;
243
244    fn into_iter(self) -> Self::IntoIter {
245        self.0.iter()
246    }
247}
248
249impl<'a> IntoIterator for &'a OwnedSexprs {
250    type Item = &'a OwnedSexpr;
251    type IntoIter = std::slice::Iter<'a, OwnedSexpr>;
252
253    fn into_iter(self) -> Self::IntoIter {
254        self.0.iter()
255    }
256}
257
258impl<'a, 'src> IntoIterator for &'a mut Sexprs<'src> {
259    type Item = &'a mut Sexpr<'src>;
260    type IntoIter = std::slice::IterMut<'a, Sexpr<'src>>;
261
262    fn into_iter(self) -> Self::IntoIter {
263        self.0.iter_mut()
264    }
265}
266
267impl<'a> IntoIterator for &'a mut OwnedSexprs {
268    type Item = &'a mut OwnedSexpr;
269    type IntoIter = std::slice::IterMut<'a, OwnedSexpr>;
270
271    fn into_iter(self) -> Self::IntoIter {
272        self.0.iter_mut()
273    }
274}
275
276impl<'a> From<&'a OwnedSexprs> for Sexprs<'a> {
277    fn from(value: &'a OwnedSexprs) -> Self {
278        value.iter().map(Sexpr::from).collect()
279    }
280}
281
282impl From<Sexprs<'_>> for OwnedSexprs {
283    fn from(value: Sexprs<'_>) -> Self {
284        value.into_iter().map(OwnedSexpr::from).collect()
285    }
286}
287
288impl<'src> From<Vec<Sexpr<'src>>> for Sexprs<'src> {
289    fn from(value: Vec<Sexpr<'src>>) -> Self {
290        Self(value)
291    }
292}
293
294impl From<Vec<OwnedSexpr>> for OwnedSexprs {
295    fn from(value: Vec<OwnedSexpr>) -> Self {
296        Self(value)
297    }
298}
299
300impl Default for Sexprs<'_> {
301    fn default() -> Self {
302        Self::new()
303    }
304}
305
306impl Default for OwnedSexprs {
307    fn default() -> Self {
308        Self::new()
309    }
310}
311
312impl<'a> From<&'a OwnedSexpr> for Sexpr<'a> {
313    fn from(value: &'a OwnedSexpr) -> Self {
314        match value {
315            OwnedSexpr::List(list) => Self::List(list.into()),
316            OwnedSexpr::Group(group) => Self::Group(group.iter().map(Sexpr::from).collect()),
317            OwnedSexpr::String(string) => Self::String(Cow::Borrowed(string)),
318            OwnedSexpr::Atom(atom) => Self::Atom(atom),
319            #[cfg(feature = "comments")]
320            OwnedSexpr::Comment(comment) => Self::Comment(comment),
321        }
322    }
323}
324
325impl From<Sexpr<'_>> for OwnedSexpr {
326    fn from(value: Sexpr<'_>) -> Self {
327        match value {
328            Sexpr::List(list) => Self::List(list.into()),
329            Sexpr::Group(group) => Self::Group(group.into()),
330            Sexpr::String(string) => Self::String(string.into_owned()),
331            Sexpr::Atom(atom) => Self::Atom(atom.to_vec()),
332            #[cfg(feature = "comments")]
333            Sexpr::Comment(comment) => Self::Comment(comment.to_vec()),
334        }
335    }
336}
337
338////////////////////////////
339// Method implementations //
340////////////////////////////
341
342impl Sexprs<'_> {
343    /// Create a new, empty list of [`Sexpr`]s
344    pub fn new() -> Self {
345        Self(vec![])
346    }
347}
348
349impl OwnedSexprs {
350    /// Create a new, empty list of [`OwnedSexpr`]s
351    pub fn new() -> Self {
352        Self(vec![])
353    }
354}
355
356macro_rules! impl_unwrap {
357    ($type:ident, $func_name:ident, $func_name_ref:ident, $variant:ident, $result:ty, $name:literal) => {
358        #[doc = concat!("Returns the contained [`", stringify!($variant), "`](", stringify!($type), "::", stringify!($variant), ") value, consuming `self`.")]
359        #[doc = ""]
360        #[doc = "## Panics"]
361        #[doc = concat!("Panics if `self` is not [`", stringify!($variant), "`](", stringify!($type), "::", stringify!($variant), ").")]
362        pub fn $func_name(self) -> $result {
363            match self {
364                $type::$variant(val) => val,
365                other => panic!(
366                    concat!(
367                        "called `",
368                        stringify!($type),
369                        "::",
370                        stringify!($func_name),
371                        "()` on a non-",
372                        $name,
373                        " value: {}"
374                    ),
375                    other
376                ),
377            }
378        }
379
380        #[doc = concat!("Returns the contained [`", stringify!($variant), "`](", stringify!($type), "::", stringify!($variant), ") value by reference.")]
381        #[doc = ""]
382        #[doc = "## Panics"]
383        #[doc = concat!("Panics if `self` is not [`", stringify!($variant), "`](", stringify!($type), "::", stringify!($variant), ").")]
384        pub fn $func_name_ref(&self) -> &$result {
385            match self {
386                $type::$variant(val) => val,
387                other => panic!(
388                    concat!(
389                        "called `",
390                        stringify!($type),
391                        "::",
392                        stringify!($func_name_ref),
393                        "()` on a non-",
394                        $name,
395                        " value: {}"
396                    ),
397                    other
398                ),
399            }
400        }
401    };
402}
403
404impl<'src> Sexpr<'src> {
405    impl_unwrap!(
406        Sexpr,
407        unwrap_list,
408        unwrap_list_ref,
409        List,
410        Sexprs<'src>,
411        "list"
412    );
413    impl_unwrap!(
414        Sexpr,
415        unwrap_group,
416        unwrap_group_ref,
417        Group,
418        Sexprs<'src>,
419        "group"
420    );
421    impl_unwrap!(
422        Sexpr,
423        unwrap_string,
424        unwrap_string_ref,
425        String,
426        Cow<'src, [u8]>,
427        "string"
428    );
429    impl_unwrap!(
430        Sexpr,
431        unwrap_atom,
432        unwrap_atom_ref,
433        Atom,
434        &'src [u8],
435        "atom"
436    );
437}
438
439impl OwnedSexpr {
440    impl_unwrap!(
441        OwnedSexpr,
442        unwrap_list,
443        unwrap_list_ref,
444        List,
445        OwnedSexprs,
446        "list"
447    );
448    impl_unwrap!(
449        OwnedSexpr,
450        unwrap_group,
451        unwrap_group_ref,
452        Group,
453        OwnedSexprs,
454        "group"
455    );
456    impl_unwrap!(
457        OwnedSexpr,
458        unwrap_string,
459        unwrap_string_ref,
460        String,
461        Vec<u8>,
462        "string"
463    );
464    impl_unwrap!(
465        OwnedSexpr,
466        unwrap_atom,
467        unwrap_atom_ref,
468        Atom,
469        Vec<u8>,
470        "atom"
471    );
472}
473
474/// A kind of parentheses. Used in [`Error::MissingClosingParen`] and [`Error::ExtraClosingParen`]
475/// to indicate the kind of parentheses that caused the error.
476#[derive(Debug, Clone, PartialEq, Eq)]
477pub enum ParenKind {
478    /// Round parentheses: `()`
479    Round,
480    /// Square brackets: `[]`
481    Square,
482}
483
484impl From<&ParenKind> for Token<'_> {
485    fn from(value: &ParenKind) -> Self {
486        match value {
487            ParenKind::Round => Self::RParen,
488            ParenKind::Square => Self::RBrack,
489        }
490    }
491}
492
493impl Display for ParenKind {
494    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
495        match self {
496            Self::Round => write!(f, ")"),
497            Self::Square => write!(f, "]"),
498        }
499    }
500}