@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.
Files changed (41) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +45 -0
  3. package/package.json +31 -0
  4. package/src/ast/flat_ruleset.rs +52 -0
  5. package/src/ast/ruleset/mod.rs +229 -0
  6. package/src/ast/ruleset/rule.rs +114 -0
  7. package/src/ast/selector/attribute.rs +90 -0
  8. package/src/ast/selector/combinator.rs +58 -0
  9. package/src/ast/selector/mod.rs +115 -0
  10. package/src/ast/selector/selector_path.rs +230 -0
  11. package/src/ast/selector/selector_term.rs +325 -0
  12. package/src/ast/token/mod.rs +18 -0
  13. package/src/ast/token/space.rs +157 -0
  14. package/src/ast/token/string.rs +59 -0
  15. package/src/ast/token/symbol.rs +32 -0
  16. package/src/ast/tree_ruleset.rs +252 -0
  17. package/src/ast.rs +216 -0
  18. package/src/builder.rs +189 -0
  19. package/src/js_builder.rs +59 -0
  20. package/src/lib.rs +151 -0
  21. package/src/main.rs +40 -0
  22. package/src/parser.rs +36 -0
  23. package/src/render.rs +84 -0
  24. package/src/transform.rs +14 -0
  25. package/src/transformers/apply_import.rs +53 -0
  26. package/src/transformers/apply_mixin.rs +73 -0
  27. package/src/transformers/apply_var.rs +41 -0
  28. package/src/transformers/dedupe.rs +35 -0
  29. package/src/transformers/filter_refs.rs +27 -0
  30. package/src/transformers/flat_self.rs +30 -0
  31. package/src/transformers/inline_url.rs +52 -0
  32. package/src/transformers/mod.rs +43 -0
  33. package/src/utils.rs +90 -0
  34. package/target/cjs/procss.d.ts +22 -0
  35. package/target/cjs/procss.js +217 -0
  36. package/target/cjs/procss_bg.wasm +0 -0
  37. package/target/cjs/procss_bg.wasm.d.ts +12 -0
  38. package/target/esm/procss.d.ts +58 -0
  39. package/target/esm/procss.js +283 -0
  40. package/target/esm/procss_bg.wasm +0 -0
  41. 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
+ }