@prospective.co/procss 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +45 -0
- package/package.json +31 -0
- package/src/ast/flat_ruleset.rs +52 -0
- package/src/ast/ruleset/mod.rs +229 -0
- package/src/ast/ruleset/rule.rs +114 -0
- package/src/ast/selector/attribute.rs +90 -0
- package/src/ast/selector/combinator.rs +58 -0
- package/src/ast/selector/mod.rs +115 -0
- package/src/ast/selector/selector_path.rs +230 -0
- package/src/ast/selector/selector_term.rs +325 -0
- package/src/ast/token/mod.rs +18 -0
- package/src/ast/token/space.rs +157 -0
- package/src/ast/token/string.rs +59 -0
- package/src/ast/token/symbol.rs +32 -0
- package/src/ast/tree_ruleset.rs +252 -0
- package/src/ast.rs +216 -0
- package/src/builder.rs +189 -0
- package/src/js_builder.rs +59 -0
- package/src/lib.rs +151 -0
- package/src/main.rs +40 -0
- package/src/parser.rs +36 -0
- package/src/render.rs +84 -0
- package/src/transform.rs +14 -0
- package/src/transformers/apply_import.rs +53 -0
- package/src/transformers/apply_mixin.rs +73 -0
- package/src/transformers/apply_var.rs +41 -0
- package/src/transformers/dedupe.rs +35 -0
- package/src/transformers/filter_refs.rs +27 -0
- package/src/transformers/flat_self.rs +30 -0
- package/src/transformers/inline_url.rs +52 -0
- package/src/transformers/mod.rs +43 -0
- package/src/utils.rs +90 -0
- package/target/cjs/procss.d.ts +22 -0
- package/target/cjs/procss.js +217 -0
- package/target/cjs/procss_bg.wasm +0 -0
- package/target/cjs/procss_bg.wasm.d.ts +12 -0
- package/target/esm/procss.d.ts +58 -0
- package/target/esm/procss.js +283 -0
- package/target/esm/procss_bg.wasm +0 -0
- package/target/esm/procss_bg.wasm.d.ts +12 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
// ┌───────────────────────────────────────────────────────────────────────────┐
|
|
2
|
+
// │ │
|
|
3
|
+
// │ ██████╗ ██████╗ ██████╗ Copyright (C) 2022, The Prospective Company │
|
|
4
|
+
// │ ██╔══██╗██╔══██╗██╔═══██╗ │
|
|
5
|
+
// │ ██████╔╝██████╔╝██║ ██║ This file is part of the Procss library, │
|
|
6
|
+
// │ ██╔═══╝ ██╔══██╗██║ ██║ distributed under the terms of the │
|
|
7
|
+
// │ ██║ ██║ ██║╚██████╔╝ Apache License 2.0. The full license can │
|
|
8
|
+
// │ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ be found in the LICENSE file. │
|
|
9
|
+
// │ │
|
|
10
|
+
// └───────────────────────────────────────────────────────────────────────────┘
|
|
11
|
+
|
|
12
|
+
use nom::branch::alt;
|
|
13
|
+
use nom::bytes::complete::tag;
|
|
14
|
+
use nom::combinator::opt;
|
|
15
|
+
use nom::error::ParseError;
|
|
16
|
+
use nom::multi::many0;
|
|
17
|
+
use nom::sequence::{delimited, preceded, tuple};
|
|
18
|
+
use nom::{IResult, Parser};
|
|
19
|
+
|
|
20
|
+
use super::attribute::SelectorAttr;
|
|
21
|
+
use crate::ast::token::*;
|
|
22
|
+
use crate::parser::*;
|
|
23
|
+
use crate::render::*;
|
|
24
|
+
|
|
25
|
+
/// A pseudo-selector component of a `Selector`, including optional argument
|
|
26
|
+
/// selector (parenthesis delimited).
|
|
27
|
+
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
|
28
|
+
pub struct Pseudo<'a> {
|
|
29
|
+
property: &'a str,
|
|
30
|
+
value: Option<SelectorTerm<'a, Option<&'a str>>>,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
impl<'a> ParseCss<'a> for Pseudo<'a> {
|
|
34
|
+
fn parse<E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Self, E> {
|
|
35
|
+
let (input, property) = preceded(tuple((tag(":"), opt(tag(":")))), parse_symbol)(input)?;
|
|
36
|
+
let (input, value) = opt(delimited(tag("("), SelectorTerm::parse, tag(")")))(input)?;
|
|
37
|
+
Ok((input, Pseudo { property, value }))
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
impl<'a> RenderCss for Pseudo<'a> {
|
|
42
|
+
fn render(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
43
|
+
write!(f, ":{}", self.property)?;
|
|
44
|
+
if let Some(x) = self.value.as_ref() {
|
|
45
|
+
write!(f, "(")?;
|
|
46
|
+
x.render(f)?;
|
|
47
|
+
write!(f, ")")?;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Ok(())
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
enum SelType<'a> {
|
|
55
|
+
Class(&'a str),
|
|
56
|
+
Id(&'a str),
|
|
57
|
+
Pseudo(Pseudo<'a>),
|
|
58
|
+
Attr(SelectorAttr<'a>),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// A single compound CSS selector, parameterized over it's `tag` field such
|
|
62
|
+
/// that the uniqu wildcard and self selectors can re-use the same struct and
|
|
63
|
+
/// some tag-irrelevent functions can be shared between impls.
|
|
64
|
+
#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
|
|
65
|
+
pub struct SelectorTerm<'a, T> {
|
|
66
|
+
pub id: Option<&'a str>,
|
|
67
|
+
pub class: Vec<&'a str>,
|
|
68
|
+
pub tag: T,
|
|
69
|
+
pub attribute: Vec<SelectorAttr<'a>>,
|
|
70
|
+
pub pseudo: Vec<Pseudo<'a>>,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
impl<'a, T: Clone> SelectorTerm<'a, T> {
|
|
74
|
+
/// Create a new `Selector`.
|
|
75
|
+
fn new(tag: T, qualifiers: &[SelType<'a>]) -> SelectorTerm<'a, T> {
|
|
76
|
+
let mut class = vec![];
|
|
77
|
+
let mut id: Option<&str> = None;
|
|
78
|
+
let mut attribute = vec![];
|
|
79
|
+
let mut pseudo = vec![];
|
|
80
|
+
for x in qualifiers {
|
|
81
|
+
match x {
|
|
82
|
+
SelType::Class(x) => class.push(*x),
|
|
83
|
+
SelType::Id(x) => id = Some(x),
|
|
84
|
+
SelType::Pseudo(x) => pseudo.push(x.clone()),
|
|
85
|
+
SelType::Attr(x) => attribute.push(x.clone()),
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
SelectorTerm {
|
|
90
|
+
id,
|
|
91
|
+
class,
|
|
92
|
+
tag,
|
|
93
|
+
attribute,
|
|
94
|
+
pseudo,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// Join to another "self" selector.
|
|
99
|
+
/// TODO Joining two selectors with populated `id` fields will discard the
|
|
100
|
+
/// parent's `id`.
|
|
101
|
+
pub fn join(&self, other: &SelectorTerm<'a, ()>) -> Self {
|
|
102
|
+
let mut class = self.class.clone();
|
|
103
|
+
let mut attribute = self.attribute.clone();
|
|
104
|
+
let mut pseudo = self.pseudo.clone();
|
|
105
|
+
let id = other.id.or(self.id);
|
|
106
|
+
class.append(&mut other.class.clone());
|
|
107
|
+
attribute.append(&mut other.attribute.clone());
|
|
108
|
+
pseudo.append(&mut other.pseudo.clone());
|
|
109
|
+
SelectorTerm {
|
|
110
|
+
id,
|
|
111
|
+
class,
|
|
112
|
+
tag: self.tag.clone(),
|
|
113
|
+
attribute,
|
|
114
|
+
pseudo,
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
impl<'a, T: RenderCss> RenderCss for SelectorTerm<'a, T> {
|
|
120
|
+
fn render(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
121
|
+
self.tag.render(f)?;
|
|
122
|
+
if let Some(tag) = &self.id {
|
|
123
|
+
write!(f, "#{}", tag)?;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for class in &self.class {
|
|
127
|
+
write!(f, ".{}", class)?;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if !self.attribute.is_empty() {
|
|
131
|
+
write!(f, "[")?;
|
|
132
|
+
let mut first = true;
|
|
133
|
+
for SelectorAttr { name, value } in &self.attribute {
|
|
134
|
+
if !first {
|
|
135
|
+
write!(f, ",")?;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
write!(f, "{}", name)?;
|
|
139
|
+
if let Some(val) = value {
|
|
140
|
+
write!(f, "={}", val)?;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
first = false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
write!(f, "]")?;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
for class in &self.pseudo {
|
|
150
|
+
class.render(f)?;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
Ok(())
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// TODO multiple ids dont work correctly, we discard all but last
|
|
158
|
+
|
|
159
|
+
impl<'a> ParseCss<'a> for SelectorTerm<'a, Option<&'a str>> {
|
|
160
|
+
fn parse<E>(input: &'a str) -> IResult<&'a str, Self, E>
|
|
161
|
+
where
|
|
162
|
+
E: ParseError<&'a str>,
|
|
163
|
+
{
|
|
164
|
+
let (rest, (tag, qualifiers)) = tuple((
|
|
165
|
+
opt(parse_symbol),
|
|
166
|
+
many0(alt((
|
|
167
|
+
preceded(tag("."), parse_symbol.map(SelType::Class)),
|
|
168
|
+
preceded(tag("#"), parse_symbol.map(SelType::Id)),
|
|
169
|
+
Pseudo::parse.map(SelType::Pseudo),
|
|
170
|
+
SelectorAttr::parse.map(SelType::Attr),
|
|
171
|
+
))),
|
|
172
|
+
))(input)?;
|
|
173
|
+
|
|
174
|
+
if tag.is_none() && qualifiers.is_empty() {
|
|
175
|
+
return nom::IResult::Err(nom::Err::Error(ParseError::from_error_kind(
|
|
176
|
+
rest,
|
|
177
|
+
nom::error::ErrorKind::Verify,
|
|
178
|
+
)));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
Ok((rest, SelectorTerm::new(tag, &qualifiers)))
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
impl<'a> ParseCss<'a> for SelectorTerm<'a, ()> {
|
|
186
|
+
fn parse<E>(input: &'a str) -> IResult<&'a str, Self, E>
|
|
187
|
+
where
|
|
188
|
+
E: ParseError<&'a str>,
|
|
189
|
+
{
|
|
190
|
+
let (rest, (_, qualifiers)) = tuple((
|
|
191
|
+
tag("&"),
|
|
192
|
+
many0(alt((
|
|
193
|
+
preceded(tag("."), parse_symbol.map(SelType::Class)),
|
|
194
|
+
preceded(tag("#"), parse_symbol.map(SelType::Id)),
|
|
195
|
+
Pseudo::parse.map(SelType::Pseudo),
|
|
196
|
+
SelectorAttr::parse.map(SelType::Attr),
|
|
197
|
+
))),
|
|
198
|
+
))(input)?;
|
|
199
|
+
|
|
200
|
+
Ok((rest, SelectorTerm::new((), &qualifiers)))
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
#[cfg(test)]
|
|
205
|
+
mod tests {
|
|
206
|
+
use std::assert_matches::assert_matches;
|
|
207
|
+
|
|
208
|
+
use super::*;
|
|
209
|
+
|
|
210
|
+
#[test]
|
|
211
|
+
fn test_tag() {
|
|
212
|
+
assert_matches!(
|
|
213
|
+
SelectorTerm::parse::<()>("--column-selector--background"),
|
|
214
|
+
Ok(("", SelectorTerm {
|
|
215
|
+
tag: Some("--column-selector--background"),
|
|
216
|
+
..
|
|
217
|
+
}))
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
#[test]
|
|
222
|
+
fn test_class() {
|
|
223
|
+
assert_matches!(
|
|
224
|
+
SelectorTerm::<Option<&str>>::parse::<()>(".column-selector--background"),
|
|
225
|
+
Ok(("", SelectorTerm {
|
|
226
|
+
class,
|
|
227
|
+
..
|
|
228
|
+
})) if class == vec!["column-selector--background"]
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
#[test]
|
|
233
|
+
fn test_classes() {
|
|
234
|
+
assert_matches!(
|
|
235
|
+
SelectorTerm::<Option<&str>>::parse::<()>(".column-selector.column-selector--background"),
|
|
236
|
+
Ok(("", SelectorTerm {
|
|
237
|
+
class,
|
|
238
|
+
..
|
|
239
|
+
})) if class == vec!["column-selector", "column-selector--background"]
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
#[test]
|
|
244
|
+
fn test_attribute() {
|
|
245
|
+
assert_matches!(
|
|
246
|
+
SelectorTerm::<Option<&str>>::parse::<()>("[name=test]"),
|
|
247
|
+
Ok(("", SelectorTerm {
|
|
248
|
+
attribute,
|
|
249
|
+
..
|
|
250
|
+
})) if attribute == vec![SelectorAttr{ name: "name", value: Some("test") }]
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
#[test]
|
|
255
|
+
fn test_id() {
|
|
256
|
+
assert_matches!(
|
|
257
|
+
SelectorTerm::<Option<&str>>::parse::<()>("#column-selector--background"),
|
|
258
|
+
Ok(("", SelectorTerm {
|
|
259
|
+
id: Some("column-selector--background"),
|
|
260
|
+
..
|
|
261
|
+
}))
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
#[test]
|
|
266
|
+
fn test_id_class_tag() {
|
|
267
|
+
assert_matches!(
|
|
268
|
+
SelectorTerm::<Option<&str>>::parse::<()>("div#column-selector.column-selector.column-selector--background"),
|
|
269
|
+
Ok(("", SelectorTerm {
|
|
270
|
+
id: Some("column-selector"),
|
|
271
|
+
class,
|
|
272
|
+
tag: Some("div"),
|
|
273
|
+
..
|
|
274
|
+
}))if class == vec!["column-selector", "column-selector--background"]
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
#[test]
|
|
279
|
+
fn test_pesudo() {
|
|
280
|
+
assert_matches!(
|
|
281
|
+
SelectorTerm::parse::<()>("div:hover"),
|
|
282
|
+
Ok(("", SelectorTerm {
|
|
283
|
+
tag: Some("div"),
|
|
284
|
+
pseudo,
|
|
285
|
+
..
|
|
286
|
+
})) if pseudo.len() == 1 && matches!(pseudo[0], Pseudo{property: "hover", value: None })
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
#[test]
|
|
291
|
+
fn test_parameterized_pesudo() {
|
|
292
|
+
assert_matches!(
|
|
293
|
+
SelectorTerm::parse::<()>("div:not(.test)"),
|
|
294
|
+
Ok(("", SelectorTerm {
|
|
295
|
+
tag: Some("div"),
|
|
296
|
+
pseudo,
|
|
297
|
+
..
|
|
298
|
+
})) if pseudo.len() == 1 && matches!(pseudo[0], Pseudo{ property: "not", value: Some(_) })
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
#[test]
|
|
303
|
+
fn test_parameterized_pesudo_nth_child() {
|
|
304
|
+
assert_matches!(
|
|
305
|
+
SelectorTerm::parse::<()>("div:nth-child(2)"),
|
|
306
|
+
Ok(("", SelectorTerm {
|
|
307
|
+
tag: Some("div"),
|
|
308
|
+
pseudo,
|
|
309
|
+
..
|
|
310
|
+
})) if pseudo.len() == 1 && matches!(pseudo[0], Pseudo{ property: "nth-child", value: Some(_) })
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
#[test]
|
|
315
|
+
fn test_parameterized_pesudo_renders_correctly() {
|
|
316
|
+
assert_matches!(
|
|
317
|
+
SelectorTerm::<Option<&str>>::parse::<nom::error::VerboseError<&str>>(
|
|
318
|
+
"div:nth-child(2)"
|
|
319
|
+
)
|
|
320
|
+
.map(|x| x.as_css_string())
|
|
321
|
+
.as_deref(),
|
|
322
|
+
Ok("div:nth-child(2)")
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// ┌───────────────────────────────────────────────────────────────────────────┐
|
|
2
|
+
// │ │
|
|
3
|
+
// │ ██████╗ ██████╗ ██████╗ Copyright (C) 2022, The Prospective Company │
|
|
4
|
+
// │ ██╔══██╗██╔══██╗██╔═══██╗ │
|
|
5
|
+
// │ ██████╔╝██████╔╝██║ ██║ This file is part of the Procss library, │
|
|
6
|
+
// │ ██╔═══╝ ██╔══██╗██║ ██║ distributed under the terms of the │
|
|
7
|
+
// │ ██║ ██║ ██║╚██████╔╝ Apache License 2.0. The full license can │
|
|
8
|
+
// │ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ be found in the LICENSE file. │
|
|
9
|
+
// │ │
|
|
10
|
+
// └───────────────────────────────────────────────────────────────────────────┘
|
|
11
|
+
|
|
12
|
+
mod space;
|
|
13
|
+
mod string;
|
|
14
|
+
mod symbol;
|
|
15
|
+
|
|
16
|
+
pub(crate) use space::*;
|
|
17
|
+
pub(crate) use string::*;
|
|
18
|
+
pub(crate) use symbol::*;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// ┌───────────────────────────────────────────────────────────────────────────┐
|
|
2
|
+
// │ │
|
|
3
|
+
// │ ██████╗ ██████╗ ██████╗ Copyright (C) 2022, The Prospective Company │
|
|
4
|
+
// │ ██╔══██╗██╔══██╗██╔═══██╗ │
|
|
5
|
+
// │ ██████╔╝██████╔╝██║ ██║ This file is part of the Procss library, │
|
|
6
|
+
// │ ██╔═══╝ ██╔══██╗██║ ██║ distributed under the terms of the │
|
|
7
|
+
// │ ██║ ██║ ██║╚██████╔╝ Apache License 2.0. The full license can │
|
|
8
|
+
// │ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ be found in the LICENSE file. │
|
|
9
|
+
// │ │
|
|
10
|
+
// └───────────────────────────────────────────────────────────────────────────┘
|
|
11
|
+
|
|
12
|
+
use nom::branch::alt;
|
|
13
|
+
use nom::bytes::complete::{is_not, tag};
|
|
14
|
+
use nom::character::complete::{anychar, multispace1};
|
|
15
|
+
use nom::error::ParseError;
|
|
16
|
+
use nom::multi::{many0, many1, many_till};
|
|
17
|
+
use nom::sequence::preceded;
|
|
18
|
+
use nom::IResult;
|
|
19
|
+
|
|
20
|
+
/// An "extension" trait for [`str`] which is used frequently to determine
|
|
21
|
+
/// whether whitepsace can be removed during [`crate::render::RenderCss`]
|
|
22
|
+
pub trait NeedsWhitespaceStringExt {
|
|
23
|
+
/// Does this string needs a leading whitespace character?
|
|
24
|
+
fn needs_pre_ws(&self) -> bool;
|
|
25
|
+
|
|
26
|
+
/// Does this string needs a trailing whitespace character?
|
|
27
|
+
fn needs_post_ws(&self) -> bool;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
impl NeedsWhitespaceStringExt for str {
|
|
31
|
+
fn needs_pre_ws(&self) -> bool {
|
|
32
|
+
self.chars()
|
|
33
|
+
.next()
|
|
34
|
+
.map(|x| x.is_ascii_alphanumeric() || x == '-' || x == '_' || x == '%' || x == '+')
|
|
35
|
+
.unwrap_or_default()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fn needs_post_ws(&self) -> bool {
|
|
39
|
+
self.chars()
|
|
40
|
+
.last()
|
|
41
|
+
.map(|x| x.is_ascii_alphanumeric() || x == '-' || x == '_' || x == '%' || x == '+')
|
|
42
|
+
.unwrap_or_default()
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Render `s` trimming all intermediate whitespace to a single character along
|
|
47
|
+
/// the way.
|
|
48
|
+
pub fn trim_whitespace(s: &str, f: &mut std::fmt::Formatter<'_>) {
|
|
49
|
+
let mut last_alpha = false;
|
|
50
|
+
s.split_whitespace().for_each(|w| {
|
|
51
|
+
if last_alpha && w.needs_pre_ws() {
|
|
52
|
+
write!(f, " ").unwrap();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
last_alpha = w.needs_post_ws();
|
|
56
|
+
write!(f, "{}", w).unwrap();
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// pub fn trim_whitespace(s: &str, f: &mut std::fmt::Formatter<'_>) {
|
|
61
|
+
// let mut flag = false;
|
|
62
|
+
// s.split_whitespace().for_each(|w| {
|
|
63
|
+
// if flag {
|
|
64
|
+
// write!(f, " ").unwrap();
|
|
65
|
+
// }
|
|
66
|
+
|
|
67
|
+
// flag = flag || !w.is_empty();
|
|
68
|
+
// write!(f, "{}", w).unwrap();
|
|
69
|
+
// });
|
|
70
|
+
// }
|
|
71
|
+
|
|
72
|
+
fn parse_comment<'a, E>(input: &'a str) -> IResult<&'a str, (), E>
|
|
73
|
+
where
|
|
74
|
+
E: ParseError<&'a str>,
|
|
75
|
+
{
|
|
76
|
+
ignore(preceded(tag("//"), many0(is_not("\r\n"))))(input)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
fn parse_multi_comment<'a, E>(input: &'a str) -> IResult<&'a str, (), E>
|
|
80
|
+
where
|
|
81
|
+
E: ParseError<&'a str>,
|
|
82
|
+
{
|
|
83
|
+
ignore(preceded(tag("/*"), many_till(anychar, tag("*/"))))(input)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
fn ignore<'a, T, E, F>(mut f: F) -> impl FnMut(&'a str) -> IResult<&'a str, (), E>
|
|
87
|
+
where
|
|
88
|
+
F: FnMut(&'a str) -> IResult<&'a str, T, E>,
|
|
89
|
+
{
|
|
90
|
+
move |input| {
|
|
91
|
+
let (input, _) = f(input)?;
|
|
92
|
+
Ok((input, ()))
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Parses 0 or more whitespace characters, including comments.
|
|
97
|
+
pub fn comment0<'a, E>(input: &'a str) -> IResult<&'a str, (), E>
|
|
98
|
+
where
|
|
99
|
+
E: ParseError<&'a str>,
|
|
100
|
+
{
|
|
101
|
+
let (input, _) = many0(alt((
|
|
102
|
+
ignore(multispace1),
|
|
103
|
+
parse_comment,
|
|
104
|
+
parse_multi_comment,
|
|
105
|
+
)))(input)?;
|
|
106
|
+
Ok((input, ()))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Parses 1 or more whitespace characters, including comments.
|
|
110
|
+
pub fn comment1<'a, E>(input: &'a str) -> IResult<&'_ str, (), E>
|
|
111
|
+
where
|
|
112
|
+
E: ParseError<&'a str>,
|
|
113
|
+
{
|
|
114
|
+
ignore(many1(alt((
|
|
115
|
+
ignore(multispace1),
|
|
116
|
+
parse_comment,
|
|
117
|
+
parse_multi_comment,
|
|
118
|
+
))))(input)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// Parses 0 or more whitespace characters, including comments and semicolons.
|
|
122
|
+
pub fn sep0<'a, E>(input: &'a str) -> IResult<&'_ str, (), E>
|
|
123
|
+
where
|
|
124
|
+
E: ParseError<&'a str>,
|
|
125
|
+
{
|
|
126
|
+
ignore(many0(alt((comment1, ignore(tag(";"))))))(input)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#[cfg(test)]
|
|
130
|
+
mod tests {
|
|
131
|
+
use std::assert_matches::assert_matches;
|
|
132
|
+
|
|
133
|
+
use super::*;
|
|
134
|
+
|
|
135
|
+
#[test]
|
|
136
|
+
fn test_multiline_comment() {
|
|
137
|
+
assert_matches!(
|
|
138
|
+
comment0::<()>(
|
|
139
|
+
"
|
|
140
|
+
/*
|
|
141
|
+
* test
|
|
142
|
+
*/"
|
|
143
|
+
),
|
|
144
|
+
Ok(("", ()))
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
#[test]
|
|
149
|
+
fn test_forward_slash() {
|
|
150
|
+
assert_matches!(comment0::<()>("// test"), Ok(("", ())))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#[test]
|
|
154
|
+
fn test_semicolons() {
|
|
155
|
+
assert_matches!(comment0::<()>("/* test; test */"), Ok(("", ())))
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// ┌───────────────────────────────────────────────────────────────────────────┐
|
|
2
|
+
// │ │
|
|
3
|
+
// │ ██████╗ ██████╗ ██████╗ Copyright (C) 2022, The Prospective Company │
|
|
4
|
+
// │ ██╔══██╗██╔══██╗██╔═══██╗ │
|
|
5
|
+
// │ ██████╔╝██████╔╝██║ ██║ This file is part of the Procss library, │
|
|
6
|
+
// │ ██╔═══╝ ██╔══██╗██║ ██║ distributed under the terms of the │
|
|
7
|
+
// │ ██║ ██║ ██║╚██████╔╝ Apache License 2.0. The full license can │
|
|
8
|
+
// │ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ be found in the LICENSE file. │
|
|
9
|
+
// │ │
|
|
10
|
+
// └───────────────────────────────────────────────────────────────────────────┘
|
|
11
|
+
|
|
12
|
+
use nom::branch::alt;
|
|
13
|
+
use nom::bytes::complete::is_not;
|
|
14
|
+
use nom::character::complete::char;
|
|
15
|
+
use nom::combinator::{map, recognize, verify};
|
|
16
|
+
use nom::error::{ErrorKind, ParseError};
|
|
17
|
+
use nom::multi::fold_many0;
|
|
18
|
+
use nom::sequence::{delimited, preceded};
|
|
19
|
+
use nom::IResult;
|
|
20
|
+
|
|
21
|
+
fn parse_escaped_char(input: &str) -> IResult<&str, &str> {
|
|
22
|
+
recognize(preceded(char('\\'), is_not(" \r\t\n\"")))(input)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fn parse_literal(input: &str) -> IResult<&str, &str> {
|
|
26
|
+
let not_quote_slash = is_not("\"\\");
|
|
27
|
+
verify(not_quote_slash, |s: &str| !s.is_empty())(input)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
enum StringFragment {
|
|
31
|
+
Literal(usize),
|
|
32
|
+
EscapedChar(usize),
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl StringFragment {
|
|
36
|
+
fn len(&self) -> usize {
|
|
37
|
+
match self {
|
|
38
|
+
StringFragment::Literal(s) => *s,
|
|
39
|
+
StringFragment::EscapedChar(s) => *s,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fn parse_fragment(input: &str) -> IResult<&str, StringFragment> {
|
|
45
|
+
alt((
|
|
46
|
+
map(parse_literal, |x| StringFragment::Literal(x.len())),
|
|
47
|
+
map(parse_escaped_char, |x| StringFragment::EscapedChar(x.len())),
|
|
48
|
+
))(input)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
pub fn parse_string_literal<'a, E: ParseError<&'a str>>(
|
|
52
|
+
) -> impl Fn(&'a str) -> IResult<&'a str, &'a str, E> {
|
|
53
|
+
move |input| {
|
|
54
|
+
let build_string = fold_many0(parse_fragment, || 2, |len, frag| frag.len() + len);
|
|
55
|
+
let offset = delimited(char('"'), build_string, char('"'));
|
|
56
|
+
let res = map(offset, |x| &input[..x])(input);
|
|
57
|
+
res.map_err(|_| nom::Err::Error(E::from_error_kind(input, ErrorKind::AlphaNumeric)))
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// ┌───────────────────────────────────────────────────────────────────────────┐
|
|
2
|
+
// │ │
|
|
3
|
+
// │ ██████╗ ██████╗ ██████╗ Copyright (C) 2022, The Prospective Company │
|
|
4
|
+
// │ ██╔══██╗██╔══██╗██╔═══██╗ │
|
|
5
|
+
// │ ██████╔╝██████╔╝██║ ██║ This file is part of the Procss library, │
|
|
6
|
+
// │ ██╔═══╝ ██╔══██╗██║ ██║ distributed under the terms of the │
|
|
7
|
+
// │ ██║ ██║ ██║╚██████╔╝ Apache License 2.0. The full license can │
|
|
8
|
+
// │ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ be found in the LICENSE file. │
|
|
9
|
+
// │ │
|
|
10
|
+
// └───────────────────────────────────────────────────────────────────────────┘
|
|
11
|
+
|
|
12
|
+
use nom::branch::alt;
|
|
13
|
+
use nom::bytes::complete::tag;
|
|
14
|
+
use nom::character::complete::alphanumeric1;
|
|
15
|
+
use nom::combinator::recognize;
|
|
16
|
+
use nom::error::ParseError;
|
|
17
|
+
use nom::multi::many1;
|
|
18
|
+
use nom::IResult;
|
|
19
|
+
|
|
20
|
+
pub fn parse_symbol<'a, E>(input: &'a str) -> IResult<&'a str, &'a str, E>
|
|
21
|
+
where
|
|
22
|
+
E: ParseError<&'a str>,
|
|
23
|
+
{
|
|
24
|
+
let mut parser = recognize(many1(alt((
|
|
25
|
+
alphanumeric1,
|
|
26
|
+
tag("-"),
|
|
27
|
+
tag("_"),
|
|
28
|
+
tag("*"),
|
|
29
|
+
tag("%"),
|
|
30
|
+
))));
|
|
31
|
+
parser(input)
|
|
32
|
+
}
|