procss/ast/token/
space.rs1use nom::branch::alt;
13use nom::bytes::complete::{is_not, tag};
14use nom::character::complete::{anychar, multispace1};
15use nom::error::ParseError;
16use nom::multi::{many0, many1, many_till};
17use nom::sequence::preceded;
18use nom::IResult;
19
20pub trait NeedsWhitespaceStringExt {
23 fn needs_pre_ws(&self) -> bool;
25
26 fn needs_post_ws(&self) -> bool;
28}
29
30impl NeedsWhitespaceStringExt for str {
31 fn needs_pre_ws(&self) -> bool {
32 self.chars()
33 .next()
34 .map(|x| {
35 x.is_ascii_alphanumeric()
36 || x == '-'
37 || x == '_'
38 || x == '%'
39 || x == '+'
40 || x == '"'
41 || x == '\''
42 || x == '('
43 })
44 .unwrap_or_default()
45 }
46
47 fn needs_post_ws(&self) -> bool {
48 self.chars()
49 .last()
50 .map(|x| {
51 x.is_ascii_alphanumeric()
52 || x == '"'
53 || x == '\''
54 || x == '-'
55 || x == '_'
56 || x == '%'
57 || x == '+'
58 || x == ')'
59 })
60 .unwrap_or_default()
61 }
62}
63
64pub fn trim_whitespace(s: &str, f: &mut std::fmt::Formatter<'_>) {
67 let mut last_alpha = false;
68 s.split_whitespace().for_each(|w| {
69 if last_alpha && w.needs_pre_ws() {
70 write!(f, " ").unwrap();
71 }
72
73 last_alpha = w.needs_post_ws();
74 write!(f, "{}", w).unwrap();
75 });
76}
77
78fn parse_comment<'a, E>(input: &'a str) -> IResult<&'a str, (), E>
79where
80 E: ParseError<&'a str>,
81{
82 ignore(preceded(tag("//"), many0(is_not("\r\n"))))(input)
83}
84
85fn parse_multi_comment<'a, E>(input: &'a str) -> IResult<&'a str, (), E>
86where
87 E: ParseError<&'a str>,
88{
89 ignore(preceded(tag("/*"), many_till(anychar, tag("*/"))))(input)
90}
91
92fn ignore<'a, T, E, F>(mut f: F) -> impl FnMut(&'a str) -> IResult<&'a str, (), E>
93where
94 F: FnMut(&'a str) -> IResult<&'a str, T, E>,
95{
96 move |input| {
97 let (input, _) = f(input)?;
98 Ok((input, ()))
99 }
100}
101
102pub fn comment0<'a, E>(input: &'a str) -> IResult<&'a str, (), E>
104where
105 E: ParseError<&'a str>,
106{
107 let (input, _) = many0(alt((
108 ignore(multispace1),
109 parse_comment,
110 parse_multi_comment,
111 )))(input)?;
112 Ok((input, ()))
113}
114
115pub fn comment1<'a, E>(input: &'a str) -> IResult<&'_ str, (), E>
117where
118 E: ParseError<&'a str>,
119{
120 ignore(many1(alt((
121 ignore(multispace1),
122 parse_comment,
123 parse_multi_comment,
124 ))))(input)
125}
126
127pub fn sep0<'a, E>(input: &'a str) -> IResult<&'_ str, (), E>
129where
130 E: ParseError<&'a str>,
131{
132 ignore(many0(alt((comment1, ignore(tag(";"))))))(input)
133}
134
135#[cfg(test)]
136mod tests {
137 use std::assert_matches::assert_matches;
138
139 use super::*;
140
141 #[test]
142 fn test_multiline_comment() {
143 assert_matches!(
144 comment0::<()>(
145 "
146 /*
147 * test
148 */"
149 ),
150 Ok(("", ()))
151 )
152 }
153
154 #[test]
155 fn test_forward_slash() {
156 assert_matches!(comment0::<()>("// test"), Ok(("", ())))
157 }
158
159 #[test]
160 fn test_semicolons() {
161 assert_matches!(comment0::<()>("/* test; test */"), Ok(("", ())))
162 }
163}