chonk/parsers/delimiting.rs
1//! Parsers for matching delimited structures.
2use crate::framework::*;
3
4#[inline]
5/// Match a parser, delimited on the left and right with by other parsers, return the results.
6///
7/// ```
8/// # use chonk::prelude::*;
9/// fn parser<'a>() -> impl Parser<'a, &'a str, ()> {
10/// move |ctx| {
11/// delimited(is('['), take(1..3, is(any)), is(']')).parse(ctx)
12/// }
13/// }
14///
15/// assert_eq!(
16/// parser().parse("[123]"),
17/// Ok((
18/// ParserContext {
19/// input: "[123]",
20/// bounds: 4..5,
21/// },
22/// "123"
23/// ))
24/// );
25/// ```
26pub fn delimited<'a, Lhs, LhsRes, Par, ParRes, Rhs, RhsRes, Msg>(
27 mut prefix: Lhs,
28 mut parser: Par,
29 mut suffix: Rhs,
30) -> impl Parser<'a, ParRes, Msg>
31where
32 Lhs: Parser<'a, LhsRes, Msg>,
33 Par: Parser<'a, ParRes, Msg>,
34 Rhs: Parser<'a, RhsRes, Msg>,
35{
36 move |ctx| {
37 prefix
38 .parse(ctx)
39 .and_then(|(ctx, _)| parser.parse(ctx))
40 .and_then(|(ctx, res)| match suffix.parse(ctx) {
41 Ok((ctx, _)) => Ok(ctx.with_data(res)),
42 Err(error) => Err(error),
43 })
44 }
45}
46
47#[inline]
48/// Match a parser, delimited on the left by another parser, return the results.
49///
50/// ```
51/// # use chonk::prelude::*;
52/// fn parser<'a>() -> impl Parser<'a, &'a str, ()> {
53/// move |ctx| {
54/// left_delimited(is('$'), take(.., is(digit))).parse(ctx)
55/// }
56/// }
57///
58/// let ctx = ParserContext::from("$123");
59///
60/// assert_eq!(
61/// parser().parse(ctx.clone()),
62/// Ok((ctx.select(1..4), "123"))
63/// );
64/// ```
65pub fn left_delimited<'a, Lhs, LhsRes, Par, ParRes, Msg>(
66 mut left: Lhs,
67 mut parser: Par,
68) -> impl Parser<'a, ParRes, Msg>
69where
70 Lhs: Parser<'a, LhsRes, Msg>,
71 Par: Parser<'a, ParRes, Msg>,
72{
73 move |ctx| left.parse(ctx).and_then(|ctx| parser.parse(ctx))
74}
75
76#[inline]
77/// Match a parser, delimited on the right by another parser, return the results.
78///
79/// ```
80/// # use chonk::prelude::*;
81/// fn parser<'a>() -> impl Parser<'a, &'a str, ()> {
82/// move |ctx| {
83/// right_delimited(take(.., is(digit)), is('%')).parse(ctx)
84/// }
85/// }
86///
87/// let ctx = ParserContext::from("123%");
88///
89/// assert_eq!(
90/// parser().parse(ctx.clone()),
91/// Ok((ctx.select(3..4), "123"))
92/// );
93/// ```
94pub fn right_delimited<'a, Par, ParRes, Rhs, RhsRes, Msg>(
95 mut parser: Par,
96 mut right: Rhs,
97) -> impl Parser<'a, ParRes, Msg>
98where
99 Par: Parser<'a, ParRes, Msg>,
100 Rhs: Parser<'a, RhsRes, Msg>,
101{
102 move |ctx| {
103 parser
104 .parse(ctx)
105 .and_then(|(ctx, res)| match right.parse(ctx) {
106 Ok((ctx, _)) => Ok(ctx.with_data(res)),
107 Err(error) => Err(error),
108 })
109 }
110}
111
112#[inline]
113/// Match a parser surrounded by whitespace, return the results of the parser.
114///
115/// ```
116/// # use chonk::prelude::*;
117/// fn parser<'a>() -> impl Parser<'a, &'a str, ()> {
118/// move |ctx| {
119/// trim(is("foobar")).parse(ctx)
120/// }
121/// }
122///
123/// assert!(parser().test(" foobar "));
124/// ```
125pub fn trim<'a, Par, Res, Msg>(mut parser: Par) -> impl Parser<'a, Res, Msg>
126where
127 Par: Parser<'a, Res, Msg>,
128{
129 move |ctx| {
130 trim_left_delimiter()
131 .parse(ctx)
132 .and_then(|ctx| parser.parse(ctx))
133 .and_then(|(ctx, res)| match trim_right_delimiter().parse(ctx) {
134 Ok((ctx, _)) => Ok((ctx, res)),
135 Err((ctx, _)) => Ok((ctx, res)),
136 })
137 }
138}
139
140fn trim_left_delimiter<'a, Msg>() -> impl Parser<'a, &'a str, Msg> {
141 use crate::parsers::matching::{is, take};
142
143 move |ctx| take(0.., is(whitespace)).parse(ctx)
144}
145
146fn trim_right_delimiter<'a>() -> impl Parser<'a, &'a str, ()> {
147 use crate::parsers::matching::space;
148
149 move |ctx| space(0..).parse(ctx)
150}