Skip to content

Commit 6e64f21

Browse files
committed
refactor: move fundamental combinators from mod.rs to the primitive module
1 parent 9b67307 commit 6e64f21

File tree

8 files changed

+150
-128
lines changed

8 files changed

+150
-128
lines changed

src/items/combined.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::items::space;
2222

2323
use super::{
2424
date::{self, Date},
25-
s,
25+
primitive::s,
2626
time::{self, Time},
2727
};
2828

src/items/date.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ use winnow::{
3535
ModalResult, Parser,
3636
};
3737

38-
use super::{dec_uint, s};
38+
use super::primitive::{dec_uint, s};
3939
use crate::ParseDateTimeError;
4040

4141
#[derive(PartialEq, Eq, Clone, Debug, Default)]

src/items/mod.rs

Lines changed: 9 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -30,23 +30,26 @@
3030
mod combined;
3131
mod date;
3232
mod ordinal;
33+
mod primitive;
3334
mod relative;
3435
mod time;
3536
mod weekday;
3637

3738
mod epoch {
3839
use winnow::{combinator::preceded, ModalResult, Parser};
3940

40-
use super::{dec_int, s};
41+
use super::primitive::{dec_int, s};
42+
4143
pub fn parse(input: &mut &str) -> ModalResult<i32> {
4244
s(preceded("@", dec_int)).parse_next(input)
4345
}
4446
}
4547

4648
mod timezone {
47-
use super::time;
4849
use winnow::ModalResult;
4950

51+
use super::time;
52+
5053
pub(crate) fn parse(input: &mut &str) -> ModalResult<time::Offset> {
5154
time::timezone(input)
5255
}
@@ -55,12 +58,11 @@ mod timezone {
5558
use chrono::NaiveDate;
5659
use chrono::{DateTime, Datelike, FixedOffset, TimeZone, Timelike};
5760

61+
use primitive::space;
5862
use winnow::{
59-
ascii::{digit1, multispace0},
60-
combinator::{alt, delimited, not, opt, peek, preceded, repeat, separated, trace},
61-
error::{AddContext, ContextError, ErrMode, ParserError, StrContext, StrContextValue},
62-
stream::{AsChar, Stream},
63-
token::{none_of, one_of, take_while},
63+
combinator::{alt, trace},
64+
error::{AddContext, ContextError, ErrMode, StrContext, StrContextValue},
65+
stream::Stream,
6466
ModalResult, Parser,
6567
};
6668

@@ -78,121 +80,6 @@ pub enum Item {
7880
TimeZone(time::Offset),
7981
}
8082

81-
/// Allow spaces and comments before a parser
82-
///
83-
/// Every token parser should be wrapped in this to allow spaces and comments.
84-
/// It is only preceding, because that allows us to check mandatory whitespace
85-
/// after running the parser.
86-
fn s<'a, O, E>(p: impl Parser<&'a str, O, E>) -> impl Parser<&'a str, O, E>
87-
where
88-
E: ParserError<&'a str>,
89-
{
90-
preceded(space, p)
91-
}
92-
93-
/// Parse the space in-between tokens
94-
///
95-
/// You probably want to use the [`s`] combinator instead.
96-
fn space<'a, E>(input: &mut &'a str) -> winnow::Result<(), E>
97-
where
98-
E: ParserError<&'a str>,
99-
{
100-
separated(0.., multispace0, alt((comment, ignored_hyphen_or_plus))).parse_next(input)
101-
}
102-
103-
/// A hyphen or plus is ignored when it is not followed by a digit
104-
///
105-
/// This includes being followed by a comment! Compare these inputs:
106-
/// ```txt
107-
/// - 12 weeks
108-
/// - (comment) 12 weeks
109-
/// ```
110-
/// The last comment should be ignored.
111-
///
112-
/// The plus is undocumented, but it seems to be ignored.
113-
fn ignored_hyphen_or_plus<'a, E>(input: &mut &'a str) -> winnow::Result<(), E>
114-
where
115-
E: ParserError<&'a str>,
116-
{
117-
(
118-
alt(('-', '+')),
119-
multispace0,
120-
peek(not(take_while(1, AsChar::is_dec_digit))),
121-
)
122-
.void()
123-
.parse_next(input)
124-
}
125-
126-
/// Parse a comment
127-
///
128-
/// A comment is given between parentheses, which must be balanced. Any other
129-
/// tokens can be within the comment.
130-
fn comment<'a, E>(input: &mut &'a str) -> winnow::Result<(), E>
131-
where
132-
E: ParserError<&'a str>,
133-
{
134-
delimited(
135-
'(',
136-
repeat(0.., alt((none_of(['(', ')']).void(), comment))),
137-
')',
138-
)
139-
.parse_next(input)
140-
}
141-
142-
/// Parse a signed decimal integer.
143-
///
144-
/// Rationale for not using `winnow::ascii::dec_int`: When upgrading winnow from
145-
/// 0.5 to 0.7, we discovered that `winnow::ascii::dec_int` now accepts only the
146-
/// following two forms:
147-
///
148-
/// - 0
149-
/// - [+-]?[1-9][0-9]*
150-
///
151-
/// Inputs like [+-]?0[0-9]* (e.g., `+012`) are therefore rejected. We provide a
152-
/// custom implementation to support such zero-prefixed integers.
153-
fn dec_int<'a, E>(input: &mut &'a str) -> winnow::Result<i32, E>
154-
where
155-
E: ParserError<&'a str>,
156-
{
157-
(opt(one_of(['+', '-'])), digit1)
158-
.void()
159-
.take()
160-
.verify_map(|s: &str| s.parse().ok())
161-
.parse_next(input)
162-
}
163-
164-
/// Parse an unsigned decimal integer.
165-
///
166-
/// See the rationale for `dec_int` for why we don't use
167-
/// `winnow::ascii::dec_uint`.
168-
fn dec_uint<'a, E>(input: &mut &'a str) -> winnow::Result<u32, E>
169-
where
170-
E: ParserError<&'a str>,
171-
{
172-
digit1
173-
.void()
174-
.take()
175-
.verify_map(|s: &str| s.parse().ok())
176-
.parse_next(input)
177-
}
178-
179-
/// Parse a float number.
180-
///
181-
/// Rationale for not using `winnow::ascii::float`: the `float` parser provided
182-
/// by winnow accepts E-notation numbers (e.g., `1.23e4`), whereas GNU date
183-
/// rejects such numbers. To remain compatible with GNU date, we provide a
184-
/// custom implementation that only accepts inputs like [+-]?[0-9]+(\.[0-9]+)?.
185-
fn float<'a, E>(input: &mut &'a str) -> winnow::Result<f64, E>
186-
where
187-
E: ParserError<&'a str>,
188-
{
189-
(opt(one_of(['+', '-'])), digit1, opt(preceded('.', digit1)))
190-
.void()
191-
.take()
192-
.verify_map(|s: &str| s.parse().ok())
193-
.parse_next(input)
194-
}
195-
19683
// Parse an item
19784
pub fn parse_one(input: &mut &str) -> ModalResult<Item> {
19885
trace(

src/items/ordinal.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
// For the full copyright and license information, please view the LICENSE
22
// file that was distributed with this source code.
33

4-
use super::{dec_uint, s};
54
use winnow::{
65
ascii::alpha1,
76
combinator::{alt, opt},
87
ModalResult, Parser,
98
};
109

10+
use super::primitive::{dec_uint, s};
11+
1112
pub fn ordinal(input: &mut &str) -> ModalResult<i32> {
1213
alt((text_ordinal, number_ordinal)).parse_next(input)
1314
}

src/items/primitive.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// For the full copyright and license information, please view the LICENSE
2+
// file that was distributed with this source code.
3+
4+
//! Primitive combinators.
5+
6+
use winnow::{
7+
ascii::{digit1, multispace0},
8+
combinator::{alt, delimited, not, opt, peek, preceded, repeat, separated},
9+
error::ParserError,
10+
stream::AsChar,
11+
token::{none_of, one_of, take_while},
12+
Parser,
13+
};
14+
15+
/// Allow spaces and comments before a parser
16+
///
17+
/// Every token parser should be wrapped in this to allow spaces and comments.
18+
/// It is only preceding, because that allows us to check mandatory whitespace
19+
/// after running the parser.
20+
pub(super) fn s<'a, O, E>(p: impl Parser<&'a str, O, E>) -> impl Parser<&'a str, O, E>
21+
where
22+
E: ParserError<&'a str>,
23+
{
24+
preceded(space, p)
25+
}
26+
27+
/// Parse the space in-between tokens
28+
///
29+
/// You probably want to use the [`s`] combinator instead.
30+
pub(super) fn space<'a, E>(input: &mut &'a str) -> winnow::Result<(), E>
31+
where
32+
E: ParserError<&'a str>,
33+
{
34+
separated(0.., multispace0, alt((comment, ignored_hyphen_or_plus))).parse_next(input)
35+
}
36+
37+
/// A hyphen or plus is ignored when it is not followed by a digit
38+
///
39+
/// This includes being followed by a comment! Compare these inputs:
40+
/// ```txt
41+
/// - 12 weeks
42+
/// - (comment) 12 weeks
43+
/// ```
44+
/// The last comment should be ignored.
45+
///
46+
/// The plus is undocumented, but it seems to be ignored.
47+
fn ignored_hyphen_or_plus<'a, E>(input: &mut &'a str) -> winnow::Result<(), E>
48+
where
49+
E: ParserError<&'a str>,
50+
{
51+
(
52+
alt(('-', '+')),
53+
multispace0,
54+
peek(not(take_while(1, AsChar::is_dec_digit))),
55+
)
56+
.void()
57+
.parse_next(input)
58+
}
59+
60+
/// Parse a comment
61+
///
62+
/// A comment is given between parentheses, which must be balanced. Any other
63+
/// tokens can be within the comment.
64+
fn comment<'a, E>(input: &mut &'a str) -> winnow::Result<(), E>
65+
where
66+
E: ParserError<&'a str>,
67+
{
68+
delimited(
69+
'(',
70+
repeat(0.., alt((none_of(['(', ')']).void(), comment))),
71+
')',
72+
)
73+
.parse_next(input)
74+
}
75+
76+
/// Parse a signed decimal integer.
77+
///
78+
/// Rationale for not using `winnow::ascii::dec_int`: When upgrading winnow from
79+
/// 0.5 to 0.7, we discovered that `winnow::ascii::dec_int` now accepts only the
80+
/// following two forms:
81+
///
82+
/// - 0
83+
/// - [+-]?[1-9][0-9]*
84+
///
85+
/// Inputs like [+-]?0[0-9]* (e.g., `+012`) are therefore rejected. We provide a
86+
/// custom implementation to support such zero-prefixed integers.
87+
pub(super) fn dec_int<'a, E>(input: &mut &'a str) -> winnow::Result<i32, E>
88+
where
89+
E: ParserError<&'a str>,
90+
{
91+
(opt(one_of(['+', '-'])), digit1)
92+
.void()
93+
.take()
94+
.verify_map(|s: &str| s.parse().ok())
95+
.parse_next(input)
96+
}
97+
98+
/// Parse an unsigned decimal integer.
99+
///
100+
/// See the rationale for `dec_int` for why we don't use
101+
/// `winnow::ascii::dec_uint`.
102+
pub(super) fn dec_uint<'a, E>(input: &mut &'a str) -> winnow::Result<u32, E>
103+
where
104+
E: ParserError<&'a str>,
105+
{
106+
digit1
107+
.void()
108+
.take()
109+
.verify_map(|s: &str| s.parse().ok())
110+
.parse_next(input)
111+
}
112+
113+
/// Parse a float number.
114+
///
115+
/// Rationale for not using `winnow::ascii::float`: the `float` parser provided
116+
/// by winnow accepts E-notation numbers (e.g., `1.23e4`), whereas GNU date
117+
/// rejects such numbers. To remain compatible with GNU date, we provide a
118+
/// custom implementation that only accepts inputs like [+-]?[0-9]+(\.[0-9]+)?.
119+
pub(super) fn float<'a, E>(input: &mut &'a str) -> winnow::Result<f64, E>
120+
where
121+
E: ParserError<&'a str>,
122+
{
123+
(opt(one_of(['+', '-'])), digit1, opt(preceded('.', digit1)))
124+
.void()
125+
.take()
126+
.verify_map(|s: &str| s.parse().ok())
127+
.parse_next(input)
128+
}

src/items/relative.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ use winnow::{
3737
ModalResult, Parser,
3838
};
3939

40-
use super::{float, ordinal::ordinal, s};
40+
use super::{
41+
ordinal::ordinal,
42+
primitive::{float, s},
43+
};
4144

4245
#[derive(Clone, Copy, Debug, PartialEq)]
4346
pub enum Relative {

src/items/time.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ use winnow::{
5252

5353
use crate::ParseDateTimeError;
5454

55-
use super::{dec_uint, relative, s};
55+
use super::{
56+
primitive::{dec_uint, s},
57+
relative,
58+
};
5659

5760
#[derive(PartialEq, Debug, Clone, Default)]
5861
pub struct Offset {

src/items/weekday.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
2424
use winnow::{ascii::alpha1, combinator::opt, seq, ModalResult, Parser};
2525

26-
use super::{ordinal::ordinal, s};
26+
use super::{ordinal::ordinal, primitive::s};
2727

2828
#[derive(PartialEq, Eq, Debug)]
2929
pub(crate) enum Day {

0 commit comments

Comments
 (0)