• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zbraniecki / icu4x / 17537320435

07 Sep 2025 05:00PM UTC coverage: 72.904% (-1.3%) from 74.164%
17537320435

push

github

web-flow
Export a zoneinfo64 const for testing (#6916)

This is useful for other crates for testing, and we vendor a copy of
this data already.

It's also easier for us to vendor it since the data is under the same
license. If temporal_rs wishes to vendor it the licensing situation gets
more complicated.

<!--
Thank you for your pull request to ICU4X!

Reminder: try to use [Conventional
Comments](https://conventionalcomments.org/) to make comments clearer.

Please see
https://github.com/unicode-org/icu4x/blob/main/CONTRIBUTING.md for
general
information on contributing to ICU4X.
-->

1 of 1 new or added line in 1 file covered. (100.0%)

9526 existing lines in 484 files now uncovered.

60429 of 82889 relevant lines covered (72.9%)

468973.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.8
/components/experimental/src/unicodeset_parse/parse.rs
1
// This file is part of ICU4X. For terms of use, please see the file
2
// called LICENSE at the top level of the ICU4X source tree
3
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4

5
use alloc::borrow::Cow;
6
use alloc::collections::{BTreeMap, BTreeSet};
7
use alloc::fmt::Display;
8
use alloc::format;
9
use alloc::string::{String, ToString};
10
use alloc::vec::Vec;
11
use core::{iter::Peekable, str::CharIndices};
12

13
use icu_collections::{
14
    codepointinvlist::{CodePointInversionList, CodePointInversionListBuilder},
15
    codepointinvliststringlist::CodePointInversionListAndStringList,
16
};
17
use icu_properties::script::ScriptWithExtensions;
18
use icu_properties::{
19
    props::{
20
        CanonicalCombiningClass, EnumeratedProperty, GeneralCategory, GeneralCategoryGroup,
21
        GraphemeClusterBreak, LineBreak, Script, SentenceBreak, WordBreak,
22
    },
23
    CodePointMapData,
24
};
25
use icu_properties::{
26
    props::{PatternWhiteSpace, XidContinue, XidStart},
27
    CodePointSetData,
28
};
29
use icu_properties::{provider::*, PropertyParser};
30
use icu_provider::prelude::*;
31

32
/// The kind of error that occurred.
33
#[derive(Debug, Clone, Copy, PartialEq, Eq, displaydoc::Display)]
48✔
34
#[non_exhaustive]
35
pub enum ParseErrorKind {
36
    /// An unexpected character was encountered.
37
    ///
38
    /// This variant implies the other variants
39
    /// (notably `UnknownProperty` and `Unimplemented`) do not apply.
40
    #[displaydoc("An unexpected character was encountered")]
41
    UnexpectedChar(char),
×
42
    /// The property name or value is unknown.
43
    ///
44
    /// For property names, make sure you use the spelling
45
    /// defined in [ECMA-262](https://tc39.es/ecma262/#table-nonbinary-unicode-properties).
46
    #[displaydoc("The property name or value is unknown")]
47
    UnknownProperty,
48
    /// A reference to an unknown variable.
49
    UnknownVariable,
50
    /// A variable of a certain type occurring in an unexpected context.
51
    UnexpectedVariable,
52
    /// The source is an incomplete unicode set.
53
    Eof,
54
    /// Something unexpected went wrong with our code. Please file a bug report on GitHub.
55
    Internal,
56
    /// The provided syntax is not supported by us.
57
    ///
58
    /// Note that unknown properties will return the
59
    /// `UnknownProperty` variant, not this one.
60
    #[displaydoc("The provided syntax is not supported by us.")]
61
    Unimplemented,
62
    /// The provided escape sequence is not a valid Unicode code point or represents too many code points.
63
    InvalidEscape,
64
}
65
use zerovec::VarZeroVec;
66
use ParseErrorKind as PEK;
67

68
impl ParseErrorKind {
69
    fn with_offset(self, offset: usize) -> ParseError {
302✔
70
        ParseError {
302✔
71
            offset: Some(offset),
302✔
72
            kind: self,
73
        }
74
    }
302✔
75
}
76

77
impl From<ParseErrorKind> for ParseError {
78
    fn from(kind: ParseErrorKind) -> Self {
14,433✔
79
        ParseError { offset: None, kind }
14,433✔
80
    }
14,433✔
81
}
82

83
/// The error type returned by the `parse` functions in this crate.
84
///
85
/// See [`ParseError::fmt_with_source`] for pretty-printing and [`ParseErrorKind`] of the
86
/// different types of errors represented by this struct.
87
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
×
88
pub struct ParseError {
89
    // offset is the index to an arbitrary byte in the last character in the source that makes sense
90
    // to display as location for the error, e.g., the unexpected character itself or
91
    // for an unknown property name the last character of the name.
92
    offset: Option<usize>,
×
93
    kind: ParseErrorKind,
×
94
}
95

96
type Result<T, E = ParseError> = core::result::Result<T, E>;
97

98
impl ParseError {
99
    /// Pretty-prints this error and if applicable, shows where the error occurred in the source.
100
    ///
101
    /// Must be called with the same source that was used to parse the set.
102
    ///
103
    /// # Examples
104
    ///
105
    /// ```
106
    /// use icu::experimental::unicodeset_parse::*;
107
    ///
108
    /// let source = "[[abc]-x]";
109
    /// let set = parse(source);
110
    /// assert!(set.is_err());
111
    /// let err = set.unwrap_err();
112
    /// assert_eq!(
113
    ///     err.fmt_with_source(source).to_string(),
114
    ///     "[[abc]-x← error: unexpected character 'x'"
115
    /// );
116
    /// ```
117
    ///
118
    /// ```
119
    /// use icu::experimental::unicodeset_parse::*;
120
    ///
121
    /// let source = r"[\N{LATIN CAPITAL LETTER A}]";
122
    /// let set = parse(source);
123
    /// assert!(set.is_err());
124
    /// let err = set.unwrap_err();
125
    /// assert_eq!(
126
    ///     err.fmt_with_source(source).to_string(),
127
    ///     r"[\N← error: unimplemented"
128
    /// );
129
    /// ```
130
    pub fn fmt_with_source(&self, source: &str) -> impl Display {
48✔
131
        let ParseError { offset, kind } = *self;
48✔
132

133
        if kind == ParseErrorKind::Eof {
48✔
134
            return format!("{source}← error: unexpected end of input");
4✔
135
        }
136
        let mut s = String::new();
44✔
137
        if let Some(offset) = offset {
44✔
138
            if offset < source.len() {
44✔
139
                // offset points to any byte of the last character we want to display.
140
                // in the case of ASCII, this is easy - we just display bytes [..=offset].
141
                // however, if the last character is more than one byte in UTF-8
142
                // we cannot use ..=offset, because that would potentially include only partial
143
                // bytes of last character in our string. hence we must find the start of the
144
                // following character and use that as the (exclusive) end of our string.
145

146
                // offset points into the last character we want to include, hence the start of the
147
                // first character we want to exclude is at least offset + 1.
148
                let mut exclusive_end = offset + 1;
44✔
149
                // TODO: replace this loop with str::ceil_char_boundary once stable
150
                for _ in 0..3 {
46✔
151
                    // is_char_boundary returns true at the latest once exclusive_end == source.len()
152
                    if source.is_char_boundary(exclusive_end) {
46✔
153
                        break;
154
                    }
155
                    exclusive_end += 1;
2✔
156
                }
157

158
                // exclusive_end is at most source.len() due to str::is_char_boundary and at least 0 by type
159
                s.push_str(&source[..exclusive_end]);
44✔
160
                s.push_str("← ");
44✔
161
            }
162
        }
163
        s.push_str("error: ");
44✔
164
        match kind {
44✔
165
            ParseErrorKind::UnexpectedChar(c) => {
31✔
166
                s.push_str(&format!("unexpected character '{}'", c.escape_debug()));
31✔
167
            }
168
            ParseErrorKind::UnknownProperty => {
169
                s.push_str("unknown property");
4✔
170
            }
171
            ParseErrorKind::UnknownVariable => {
UNCOV
172
                s.push_str("unknown variable");
×
173
            }
174
            ParseErrorKind::UnexpectedVariable => {
175
                s.push_str("unexpected variable");
6✔
176
            }
177
            ParseErrorKind::Eof => {
UNCOV
178
                s.push_str("unexpected end of input");
×
179
            }
180
            ParseErrorKind::Internal => {
UNCOV
181
                s.push_str("internal error");
×
182
            }
183
            ParseErrorKind::Unimplemented => {
184
                s.push_str("unimplemented");
1✔
185
            }
186
            ParseErrorKind::InvalidEscape => {
187
                s.push_str("invalid escape sequence");
2✔
188
            }
189
        }
190

191
        s
44✔
192
    }
48✔
193

194
    /// Returns the [`ParseErrorKind`] of this error.
UNCOV
195
    pub fn kind(&self) -> ParseErrorKind {
×
196
        self.kind
×
197
    }
×
198

199
    /// Returns the offset of this error in the source string, if it was specified.
UNCOV
200
    pub fn offset(&self) -> Option<usize> {
×
201
        self.offset
×
202
    }
×
203

204
    fn or_with_offset(self, offset: usize) -> Self {
4✔
205
        match self.offset {
4✔
UNCOV
206
            Some(_) => self,
×
207
            None => ParseError {
4✔
208
                offset: Some(offset),
4✔
209
                ..self
210
            },
4✔
211
        }
212
    }
4✔
213
}
214

215
/// The value of a variable in a UnicodeSet. Used as value type in [`VariableMap`].
216
#[derive(Debug, Clone)]
46✔
217
#[non_exhaustive]
218
pub enum VariableValue<'a> {
219
    /// A UnicodeSet, represented as a [`CodePointInversionListAndStringList`](CodePointInversionListAndStringList).
220
    UnicodeSet(CodePointInversionListAndStringList<'a>),
19✔
221
    // in theory, a one-code-point string is always the same as a char, but we might want to keep
222
    // this variant for efficiency?
223
    /// A single code point.
224
    Char(char),
23✔
225
    /// A string. It is guaranteed that when returned from a VariableMap, this variant contains never exactly one code point.
226
    String(Cow<'a, str>),
4✔
227
}
228

229
/// The map used for parsing UnicodeSets with variable support. See [`parse_with_variables`].
230
#[derive(Debug, Clone, Default)]
730✔
231
pub struct VariableMap<'a>(BTreeMap<String, VariableValue<'a>>);
365✔
232

233
impl<'a> VariableMap<'a> {
234
    /// Creates a new empty map.
235
    pub fn new() -> Self {
1✔
236
        Self::default()
1✔
237
    }
1✔
238

239
    /// Removes a key from the map, returning the value at the key if the key
240
    /// was previously in the map.
UNCOV
241
    pub fn remove(&mut self, key: &str) -> Option<VariableValue<'a>> {
×
242
        self.0.remove(key)
×
243
    }
×
244

245
    /// Get a reference to the value associated with this key, if it exists.
246
    pub fn get(&self, key: &str) -> Option<&VariableValue<'a>> {
7✔
247
        self.0.get(key)
7✔
248
    }
7✔
249

250
    /// Insert a `VariableValue` into the `VariableMap`.
251
    ///
252
    /// Returns `Err` with the old value, if it exists, and does not update the map.
253
    pub fn insert(
51✔
254
        &mut self,
255
        key: String,
256
        value: VariableValue<'a>,
257
    ) -> Result<(), &VariableValue<'_>> {
258
        // borrow-checker shenanigans, otherwise we could use if let
259
        if self.0.contains_key(&key) {
51✔
260
            // we just checked that this key exists
261
            #[expect(clippy::indexing_slicing)]
UNCOV
262
            return Err(&self.0[&key]);
×
263
        }
264

265
        if let VariableValue::String(s) = &value {
51✔
266
            let mut chars = s.chars();
21✔
267
            if let (Some(c), None) = (chars.next(), chars.next()) {
21✔
268
                self.0.insert(key, VariableValue::Char(c));
16✔
269
                return Ok(());
16✔
270
            };
271
        }
272

273
        self.0.insert(key, value);
35✔
274
        Ok(())
35✔
275
    }
51✔
276

277
    /// Insert a `char` into the `VariableMap`.    
278
    ///
279
    /// Returns `Err` with the old value, if it exists, and does not update the map.
280
    pub fn insert_char(&mut self, key: String, c: char) -> Result<(), &VariableValue<'_>> {
12✔
281
        // borrow-checker shenanigans, otherwise we could use if let
282
        if self.0.contains_key(&key) {
12✔
283
            // we just checked that this key exists
284
            #[expect(clippy::indexing_slicing)]
285
            return Err(&self.0[&key]);
1✔
286
        }
287

288
        self.0.insert(key, VariableValue::Char(c));
11✔
289
        Ok(())
11✔
290
    }
12✔
291

292
    /// Insert a `String` of any length into the `VariableMap`.
293
    ///
294
    /// Returns `Err` with the old value, if it exists, and does not update the map.
295
    pub fn insert_string(&mut self, key: String, s: String) -> Result<(), &VariableValue<'_>> {
3✔
296
        // borrow-checker shenanigans, otherwise we could use if let
297
        if self.0.contains_key(&key) {
3✔
298
            // we just checked that this key exists
299
            #[expect(clippy::indexing_slicing)]
UNCOV
300
            return Err(&self.0[&key]);
×
301
        }
302

303
        let mut chars = s.chars();
3✔
304
        let val = match (chars.next(), chars.next()) {
3✔
UNCOV
305
            (Some(c), None) => VariableValue::Char(c),
×
306
            _ => VariableValue::String(Cow::Owned(s)),
3✔
307
        };
308

309
        self.0.insert(key, val);
3✔
310
        Ok(())
3✔
311
    }
3✔
312

313
    /// Insert a `&str` of any length into the `VariableMap`.
314
    ///
315
    /// Returns `Err` with the old value, if it exists, and does not update the map.
UNCOV
316
    pub fn insert_str(&mut self, key: String, s: &'a str) -> Result<(), &VariableValue<'_>> {
×
317
        // borrow-checker shenanigans, otherwise we could use if let
318
        if self.0.contains_key(&key) {
×
319
            // we just checked that this key exists
320
            #[expect(clippy::indexing_slicing)]
321
            return Err(&self.0[&key]);
×
322
        }
323

324
        let mut chars = s.chars();
×
UNCOV
325
        let val = match (chars.next(), chars.next()) {
×
UNCOV
326
            (Some(c), None) => VariableValue::Char(c),
×
327
            _ => VariableValue::String(Cow::Borrowed(s)),
×
328
        };
329

UNCOV
330
        self.0.insert(key, val);
×
UNCOV
331
        Ok(())
×
UNCOV
332
    }
×
333

334
    /// Insert a [`CodePointInversionListAndStringList`](CodePointInversionListAndStringList) into the `VariableMap`.
335
    ///
336
    /// Returns `Err` with the old value, if it exists, and does not update the map.
337
    pub fn insert_set(
3✔
338
        &mut self,
339
        key: String,
340
        set: CodePointInversionListAndStringList<'a>,
341
    ) -> Result<(), &VariableValue<'_>> {
342
        // borrow-checker shenanigans, otherwise we could use if let
343
        if self.0.contains_key(&key) {
3✔
344
            // we just checked that this key exists
345
            #[expect(clippy::indexing_slicing)]
UNCOV
346
            return Err(&self.0[&key]);
×
347
        }
348
        self.0.insert(key, VariableValue::UnicodeSet(set));
3✔
349
        Ok(())
3✔
350
    }
3✔
351
}
352

353
// this ignores the ambiguity between \-escapes and \p{} perl properties. it assumes it is in a context where \p is just 'p'
354
// returns whether the provided char signifies the start of a literal char (raw or escaped - so \ is a legal char start)
355
// important: assumes c is not pattern_white_space
356
fn legal_char_start(c: char) -> bool {
1,648✔
357
    !(c == '&' || c == '-' || c == '$' || c == '^' || c == '[' || c == ']' || c == '{')
1,648✔
358
}
1,648✔
359

360
// same as `legal_char_start` but adapted to the charInString nonterminal. \ is allowed due to escapes.
361
// important: assumes c is not pattern_white_space
362
fn legal_char_in_string_start(c: char) -> bool {
283✔
363
    c != '}'
283✔
364
}
283✔
365

UNCOV
366
#[derive(Debug)]
×
367
enum SingleOrMultiChar {
368
    Single(char),
×
369
    // Multi is a marker that indicates parsing was paused and needs to be resumed using parse_multi_escape* when
370
    // this token is consumed. The contained char is the first char of the multi sequence.
UNCOV
371
    Multi(char),
×
372
}
373

374
// A char or a string. The Vec<char> represents multi-escapes in the 2+ case.
375
// invariant: a String is either zero or 2+ chars long, a one-char-string is equivalent to a single char.
376
// invariant: a char is 1+ chars long
377
#[derive(Debug)]
×
378
enum Literal {
UNCOV
379
    String(String),
×
380
    CharKind(SingleOrMultiChar),
×
381
}
382

383
#[derive(Debug)]
×
384
enum MainToken<'data> {
385
    // to be interpreted as value
UNCOV
386
    Literal(Literal),
×
387
    // inner set
UNCOV
388
    UnicodeSet(CodePointInversionListAndStringList<'data>),
×
389
    // anchor, only at the end of a set ([... $])
390
    DollarSign,
391
    // intersection operator, only inbetween two sets ([[...] & [...]])
392
    Ampersand,
393
    // difference operator, only inbetween two sets ([[...] - [...]])
394
    // or
395
    // range operator, only inbetween two chars ([a-z], [a-{z}])
396
    Minus,
397
    // ] to indicate the end of a set
398
    ClosingBracket,
399
}
400

401
impl<'data> MainToken<'data> {
402
    fn from_variable_value(val: VariableValue<'data>) -> Self {
44✔
403
        match val {
44✔
404
            VariableValue::Char(c) => {
21✔
405
                MainToken::Literal(Literal::CharKind(SingleOrMultiChar::Single(c)))
21✔
406
            }
21✔
407
            VariableValue::String(s) => {
4✔
408
                // we know that the VariableMap only contains non-length-1 Strings.
409
                MainToken::Literal(Literal::String(s.into_owned()))
4✔
410
            }
4✔
411
            VariableValue::UnicodeSet(set) => MainToken::UnicodeSet(set),
19✔
412
        }
413
    }
44✔
414
}
415

UNCOV
416
#[derive(Debug, Clone, Copy)]
×
417
enum Operation {
418
    Union,
419
    Difference,
420
    Intersection,
421
}
422

423
// this builds the set on-the-fly while parsing it
424
struct UnicodeSetBuilder<'a, 'b, P: ?Sized> {
425
    single_set: CodePointInversionListBuilder,
426
    string_set: BTreeSet<String>,
427
    iter: &'a mut Peekable<CharIndices<'b>>,
428
    source: &'b str,
429
    inverted: bool,
430
    variable_map: &'a VariableMap<'a>,
431
    xid_start: &'a CodePointInversionList<'a>,
432
    xid_continue: &'a CodePointInversionList<'a>,
433
    pat_ws: &'a CodePointInversionList<'a>,
434
    property_provider: &'a P,
435
}
436

437
impl<'a, 'b, P> UnicodeSetBuilder<'a, 'b, P>
438
where
439
    P: ?Sized
440
        + DataProvider<PropertyBinaryAlphabeticV1>
441
        + DataProvider<PropertyBinaryAsciiHexDigitV1>
442
        + DataProvider<PropertyBinaryBidiControlV1>
443
        + DataProvider<PropertyBinaryBidiMirroredV1>
444
        + DataProvider<PropertyBinaryCasedV1>
445
        + DataProvider<PropertyBinaryCaseIgnorableV1>
446
        + DataProvider<PropertyBinaryChangesWhenCasefoldedV1>
447
        + DataProvider<PropertyBinaryChangesWhenCasemappedV1>
448
        + DataProvider<PropertyBinaryChangesWhenLowercasedV1>
449
        + DataProvider<PropertyBinaryChangesWhenNfkcCasefoldedV1>
450
        + DataProvider<PropertyBinaryChangesWhenTitlecasedV1>
451
        + DataProvider<PropertyBinaryChangesWhenUppercasedV1>
452
        + DataProvider<PropertyBinaryDashV1>
453
        + DataProvider<PropertyBinaryDefaultIgnorableCodePointV1>
454
        + DataProvider<PropertyBinaryDeprecatedV1>
455
        + DataProvider<PropertyBinaryDiacriticV1>
456
        + DataProvider<PropertyBinaryEmojiComponentV1>
457
        + DataProvider<PropertyBinaryEmojiModifierBaseV1>
458
        + DataProvider<PropertyBinaryEmojiModifierV1>
459
        + DataProvider<PropertyBinaryEmojiPresentationV1>
460
        + DataProvider<PropertyBinaryEmojiV1>
461
        + DataProvider<PropertyBinaryExtendedPictographicV1>
462
        + DataProvider<PropertyBinaryExtenderV1>
463
        + DataProvider<PropertyBinaryGraphemeBaseV1>
464
        + DataProvider<PropertyBinaryGraphemeExtendV1>
465
        + DataProvider<PropertyBinaryHexDigitV1>
466
        + DataProvider<PropertyBinaryIdContinueV1>
467
        + DataProvider<PropertyBinaryIdeographicV1>
468
        + DataProvider<PropertyBinaryIdsBinaryOperatorV1>
469
        + DataProvider<PropertyBinaryIdStartV1>
470
        + DataProvider<PropertyBinaryIdsTrinaryOperatorV1>
471
        + DataProvider<PropertyBinaryJoinControlV1>
472
        + DataProvider<PropertyBinaryLogicalOrderExceptionV1>
473
        + DataProvider<PropertyBinaryLowercaseV1>
474
        + DataProvider<PropertyBinaryMathV1>
475
        + DataProvider<PropertyBinaryNoncharacterCodePointV1>
476
        + DataProvider<PropertyBinaryPatternSyntaxV1>
477
        + DataProvider<PropertyBinaryPatternWhiteSpaceV1>
478
        + DataProvider<PropertyBinaryQuotationMarkV1>
479
        + DataProvider<PropertyBinaryRadicalV1>
480
        + DataProvider<PropertyBinaryRegionalIndicatorV1>
481
        + DataProvider<PropertyBinarySentenceTerminalV1>
482
        + DataProvider<PropertyBinarySoftDottedV1>
483
        + DataProvider<PropertyBinaryTerminalPunctuationV1>
484
        + DataProvider<PropertyBinaryUnifiedIdeographV1>
485
        + DataProvider<PropertyBinaryUppercaseV1>
486
        + DataProvider<PropertyBinaryVariationSelectorV1>
487
        + DataProvider<PropertyBinaryWhiteSpaceV1>
488
        + DataProvider<PropertyBinaryXidContinueV1>
489
        + DataProvider<PropertyBinaryXidStartV1>
490
        + DataProvider<PropertyEnumCanonicalCombiningClassV1>
491
        + DataProvider<PropertyEnumGeneralCategoryV1>
492
        + DataProvider<PropertyEnumGraphemeClusterBreakV1>
493
        + DataProvider<PropertyEnumLineBreakV1>
494
        + DataProvider<PropertyEnumScriptV1>
495
        + DataProvider<PropertyEnumSentenceBreakV1>
496
        + DataProvider<PropertyEnumWordBreakV1>
497
        + DataProvider<PropertyNameParseCanonicalCombiningClassV1>
498
        + DataProvider<PropertyNameParseGeneralCategoryMaskV1>
499
        + DataProvider<PropertyNameParseGraphemeClusterBreakV1>
500
        + DataProvider<PropertyNameParseLineBreakV1>
501
        + DataProvider<PropertyNameParseScriptV1>
502
        + DataProvider<PropertyNameParseSentenceBreakV1>
503
        + DataProvider<PropertyNameParseWordBreakV1>
504
        + DataProvider<PropertyScriptWithExtensionsV1>,
505
{
506
    fn new_internal(
688✔
507
        iter: &'a mut Peekable<CharIndices<'b>>,
508
        source: &'b str,
509
        variable_map: &'a VariableMap<'a>,
510
        xid_start: &'a CodePointInversionList<'a>,
511
        xid_continue: &'a CodePointInversionList<'a>,
512
        pat_ws: &'a CodePointInversionList<'a>,
513
        provider: &'a P,
514
    ) -> Self {
515
        UnicodeSetBuilder {
688✔
516
            single_set: CodePointInversionListBuilder::new(),
688✔
517
            string_set: Default::default(),
688✔
518
            iter,
519
            source,
520
            inverted: false,
521
            variable_map,
522
            xid_start,
523
            xid_continue,
524
            pat_ws,
525
            property_provider: provider,
UNCOV
526
        }
×
527
    }
688✔
528

529
    // the entry point, parses a full UnicodeSet. ignores remaining input
530
    fn parse_unicode_set(&mut self) -> Result<()> {
688✔
531
        match self.must_peek_char()? {
688✔
532
            '\\' => self.parse_property_perl(),
26✔
533
            '[' => {
534
                self.iter.next();
657✔
535
                if let Some(':') = self.peek_char() {
657✔
536
                    self.parse_property_posix()
85✔
537
                } else {
538
                    self.parse_unicode_set_inner()
572✔
539
                }
540
            }
541
            '$' => {
542
                // must be variable ref to a UnicodeSet
543
                let (offset, v) = self.parse_variable()?;
3✔
544
                match v {
2✔
545
                    Some(VariableValue::UnicodeSet(s)) => {
1✔
546
                        self.single_set.add_set(s.code_points());
1✔
547
                        self.string_set
2✔
548
                            .extend(s.strings().iter().map(ToString::to_string));
1✔
549
                        Ok(())
1✔
550
                    }
1✔
551
                    Some(_) => Err(PEK::UnexpectedVariable.with_offset(offset)),
1✔
UNCOV
552
                    None => Err(PEK::UnexpectedChar('$').with_offset(offset)),
×
553
                }
554
            }
555
            c => self.error_here(PEK::UnexpectedChar(c)),
1✔
556
        }
557
    }
688✔
558

559
    // beginning [ is already consumed
560
    fn parse_unicode_set_inner(&mut self) -> Result<()> {
594✔
561
        // special cases for the first chars after [
562
        if self.must_peek_char()? == '^' {
594✔
563
            self.iter.next();
111✔
564
            self.inverted = true;
111✔
565
        }
566
        // whitespace allowed between ^ and - in `[^ - ....]`
567
        self.skip_whitespace();
594✔
568
        if self.must_peek_char()? == '-' {
594✔
569
            self.iter.next();
7✔
570
            self.single_set.add_char('-');
7✔
571
        }
572

573
        // repeatedly parse the following:
574
        // char
575
        // char-char
576
        // {string}
577
        // unicodeset
578
        // & and - operators, but only between unicodesets
579
        // $variables in place of strings, chars, or unicodesets
580

UNCOV
581
        #[derive(Debug, Clone, Copy)]
×
582
        enum State {
583
            // a state equivalent to the beginning
584
            Begin,
585
            // a state after a char. implies `prev_char` is Some(_), because we need to buffer it
586
            // in case it is part of a range, e.g., a-z
587
            Char,
588
            // in the middle of parsing a range. implies `prev_char` is Some(_), and the next
589
            // element must be a char as well
590
            CharMinus,
591
            // state directly after parsing a recursive unicode set. operators are only allowed
592
            // in this state
593
            AfterUnicodeSet,
594
            // state directly after parsing an operator. forces the next element to be a recursive
595
            // unicode set
596
            AfterOp,
597
            // state after parsing a $ (that was not a variable reference)
598
            // the only valid next option is a closing bracket
599
            AfterDollar,
600
            // state after parsing a - in an otherwise invalid position
601
            // the only valid next option is a closing bracket
602
            AfterMinus,
603
        }
604
        use State::*;
605

606
        const DEFAULT_OP: Operation = Operation::Union;
607

608
        let mut state = Begin;
594✔
609
        let mut prev_char = None;
594✔
610
        let mut operation = Operation::Union;
594✔
611

612
        loop {
2,532✔
613
            self.skip_whitespace();
2,532✔
614

615
            // for error messages
616
            let (immediate_offset, immediate_char) = self.must_peek()?;
2,532✔
617

618
            let (tok_offset, from_var, tok) = self.parse_main_token()?;
2,532✔
619
            // warning: self.iter should not be advanced any more after this point on any path to
620
            // MT::Literal(Literal::CharKind(SingleOrMultiChar::Multi)), because that variant
621
            // expects a certain self.iter state
622

623
            use MainToken as MT;
624
            use SingleOrMultiChar as SMC;
625
            match (state, tok) {
2,518✔
626
                // the end of this unicode set
627
                (
628
                    Begin | Char | CharMinus | AfterUnicodeSet | AfterDollar | AfterMinus,
629
                    MT::ClosingBracket,
630
                ) => {
631
                    if let Some(prev) = prev_char.take() {
534✔
632
                        self.single_set.add_char(prev);
142✔
633
                    }
634
                    if matches!(state, CharMinus) {
534✔
635
                        self.single_set.add_char('-');
2✔
636
                    }
637

638
                    return Ok(());
534✔
639
                }
640
                // special case ends for -
641
                // [[a-z]-]
642
                (AfterOp, MT::ClosingBracket) if matches!(operation, Operation::Difference) => {
2✔
643
                    self.single_set.add_char('-');
1✔
644
                    return Ok(());
1✔
645
                }
646
                (Begin, MT::Minus) => {
9✔
647
                    self.single_set.add_char('-');
9✔
648
                    state = AfterMinus;
9✔
649
                }
650
                // inner unicode set
651
                (Begin | Char | AfterUnicodeSet | AfterOp, MT::UnicodeSet(set)) => {
209✔
652
                    if let Some(prev) = prev_char.take() {
209✔
653
                        self.single_set.add_char(prev);
3✔
654
                    }
655

656
                    self.process_chars(operation, set.code_points().clone());
209✔
657
                    self.process_strings(
209✔
658
                        operation,
209✔
659
                        set.strings().iter().map(ToString::to_string).collect(),
209✔
660
                    );
661

662
                    operation = DEFAULT_OP;
209✔
663
                    state = AfterUnicodeSet;
209✔
664
                }
209✔
665
                // a literal char (either individually or as the start of a range if char)
666
                (
667
                    Begin | Char | AfterUnicodeSet,
668
                    MT::Literal(Literal::CharKind(SMC::Single(c))),
1,074✔
669
                ) => {
670
                    if let Some(prev) = prev_char.take() {
1,074✔
671
                        self.single_set.add_char(prev);
613✔
672
                    }
673
                    prev_char = Some(c);
1,065✔
674
                    state = Char;
1,065✔
675
                }
1,065✔
676
                // a bunch of literal chars as part of a multi-escape sequence
677
                (
678
                    Begin | Char | AfterUnicodeSet,
679
                    MT::Literal(Literal::CharKind(SMC::Multi(first_c))),
6✔
680
                ) => {
681
                    if let Some(prev) = prev_char.take() {
6✔
UNCOV
682
                        self.single_set.add_char(prev);
×
683
                    }
684
                    self.single_set.add_char(first_c);
4✔
685
                    self.parse_multi_escape_into_set()?;
598✔
686

687
                    // Note we cannot go to the Char state, because a multi-escape sequence of
688
                    // length > 1 cannot initiate a range
689
                    state = Begin;
3✔
690
                }
3✔
691
                // a literal string (length != 1, by CharOrString invariant)
692
                (Begin | Char | AfterUnicodeSet, MT::Literal(Literal::String(s))) => {
59✔
693
                    if let Some(prev) = prev_char.take() {
59✔
694
                        self.single_set.add_char(prev);
21✔
695
                    }
696

697
                    self.string_set.insert(s);
59✔
698
                    state = Begin;
59✔
699
                }
59✔
700
                // parse a literal char as the end of a range
701
                (CharMinus, MT::Literal(Literal::CharKind(SMC::Single(c)))) => {
259✔
702
                    let start = prev_char.ok_or(PEK::Internal.with_offset(tok_offset))?;
259✔
703
                    let end = c;
704
                    if start > end {
259✔
705
                        // TODO(#3558): Better error message (e.g., "start greater than end in range")?
706
                        return Err(PEK::UnexpectedChar(end).with_offset(tok_offset));
4✔
707
                    }
708

709
                    self.single_set.add_range(start..=end);
255✔
710
                    prev_char = None;
255✔
711
                    state = Begin;
255✔
712
                }
255✔
713
                // start parsing a char range
714
                (Char, MT::Minus) => {
266✔
715
                    state = CharMinus;
266✔
716
                }
717
                // start parsing a unicode set difference
718
                (AfterUnicodeSet, MT::Minus) => {
30✔
719
                    operation = Operation::Difference;
30✔
720
                    state = AfterOp;
30✔
721
                }
722
                // start parsing a unicode set difference
723
                (AfterUnicodeSet, MT::Ampersand) => {
27✔
724
                    operation = Operation::Intersection;
27✔
725
                    state = AfterOp;
27✔
726
                }
727
                (Begin | Char | AfterUnicodeSet, MT::DollarSign) => {
37✔
728
                    if let Some(prev) = prev_char.take() {
28✔
729
                        self.single_set.add_char(prev);
21✔
730
                    }
731
                    self.single_set.add_char('\u{FFFF}');
37✔
732
                    state = AfterDollar;
37✔
733
                }
734
                _ => {
735
                    // TODO(#3558): We have precise knowledge about the following MainToken here,
736
                    //  should we make use of that?
737

738
                    if from_var {
18✔
739
                        // otherwise we get error messages such as
740
                        // [$a-$← error: unexpected character '$'
741
                        // for input [$a-$b], $a = 'a', $b = "string" ;
742
                        return Err(PEK::UnexpectedVariable.with_offset(tok_offset));
5✔
743
                    }
744
                    return Err(PEK::UnexpectedChar(immediate_char).with_offset(immediate_offset));
13✔
745
                }
746
            }
747
        }
2,496✔
748
    }
572✔
749

750
    fn parse_main_token(&mut self) -> Result<(usize, bool, MainToken<'a>)> {
2,532✔
751
        let (initial_offset, first) = self.must_peek()?;
2,532✔
752
        if first == ']' {
2,532✔
753
            self.iter.next();
536✔
754
            return Ok((initial_offset, false, MainToken::ClosingBracket));
536✔
755
        }
756
        let (_, second) = self.must_peek_double()?;
1,996✔
757
        match (first, second) {
2,330✔
758
            // variable or anchor
759
            ('$', _) => {
760
                let (offset, var_or_anchor) = self.parse_variable()?;
82✔
761
                match var_or_anchor {
81✔
762
                    None => Ok((offset, false, MainToken::DollarSign)),
37✔
763
                    Some(v) => Ok((offset, true, MainToken::from_variable_value(v.clone()))),
44✔
764
                }
765
            }
766
            // string
767
            ('{', _) => self
71✔
768
                .parse_string()
769
                .map(|(offset, l)| (offset, false, MainToken::Literal(l))),
70✔
770
            // inner set
771
            ('\\', 'p' | 'P') | ('[', _) => {
772
                let mut inner_builder = UnicodeSetBuilder::new_internal(
193✔
773
                    self.iter,
193✔
774
                    self.source,
193✔
775
                    self.variable_map,
193✔
776
                    self.xid_start,
193✔
777
                    self.xid_continue,
193✔
778
                    self.pat_ws,
193✔
779
                    self.property_provider,
193✔
780
                );
781
                inner_builder.parse_unicode_set()?;
2,725✔
782
                let (single, string_set) = inner_builder.finalize();
193✔
783
                // note: offset - 1, because we already consumed full set
784
                let offset = self.must_peek_index()? - 1;
193✔
785
                let mut strings = string_set.into_iter().collect::<Vec<_>>();
192✔
786
                strings.sort();
192✔
787
                let cpilasl = CodePointInversionListAndStringList::try_from(
192✔
788
                    single.build(),
192✔
789
                    VarZeroVec::from(&strings),
192✔
790
                )
192✔
UNCOV
791
                .map_err(|_| PEK::Internal.with_offset(offset))?;
×
792
                Ok((offset, false, MainToken::UnicodeSet(cpilasl)))
192✔
793
            }
193✔
794
            // note: c cannot be a whitespace, because we called skip_whitespace just before
795
            // (in the main parse loop), so it's safe to call this guard function
796
            (c, _) if legal_char_start(c) => self
1,648✔
797
                .parse_char()
798
                .map(|(offset, c)| (offset, false, MainToken::Literal(Literal::CharKind(c)))),
1,304✔
799
            ('-', _) => {
800
                self.iter.next();
306✔
801
                Ok((initial_offset, false, MainToken::Minus))
306✔
802
            }
803
            ('&', _) => {
804
                self.iter.next();
29✔
805
                Ok((initial_offset, false, MainToken::Ampersand))
29✔
806
            }
807
            (c, _) => Err(PEK::UnexpectedChar(c).with_offset(initial_offset)),
1✔
808
        }
809
    }
2,532✔
810

811
    // parses a variable or an anchor. expects '$' as next token.
812
    // if this is a single $ (eg `[... $ ]` or the invalid `$ a`), then this function returns Ok(None),
813
    // otherwise Ok(Some(variable_value)).
814
    fn parse_variable(&mut self) -> Result<(usize, Option<&'a VariableValue<'a>>)> {
85✔
815
        self.consume('$')?;
85✔
816

817
        let mut res = String::new();
85✔
818
        let (mut var_offset, first_c) = self.must_peek()?;
85✔
819

820
        if !self.xid_start.contains(first_c) {
84✔
821
            // -1 because we already consumed the '$'
822
            return Ok((var_offset - 1, None));
37✔
823
        }
824

825
        res.push(first_c);
47✔
826
        self.iter.next();
47✔
827
        // important: if we are parsing a root unicodeset as a variable, we might reach EOF as
828
        // a valid end of the variable name, so we cannot use must_peek here.
829
        while let Some(&(offset, c)) = self.iter.peek() {
238✔
830
            if !self.xid_continue.contains(c) {
236✔
831
                break;
832
            }
833
            // only update the offset if we're adding a new char to our variable
834
            var_offset = offset;
191✔
835
            self.iter.next();
191✔
836
            res.push(c);
191✔
837
        }
838

839
        if let Some(v) = self.variable_map.0.get(&res) {
47✔
840
            return Ok((var_offset, Some(v)));
46✔
841
        }
842

843
        Err(PEK::UnknownVariable.with_offset(var_offset))
1✔
844
    }
85✔
845

846
    // parses and consumes: '{' (s charInString)* s '}'
847
    fn parse_string(&mut self) -> Result<(usize, Literal)> {
71✔
848
        self.consume('{')?;
71✔
849

850
        let mut buffer = String::new();
71✔
851
        let mut last_offset;
852

853
        loop {
854
            self.skip_whitespace();
354✔
855
            last_offset = self.must_peek_index()?;
354✔
856
            match self.must_peek_char()? {
353✔
857
                '}' => {
858
                    self.iter.next();
70✔
859
                    break;
860
                }
861
                // note: c cannot be a whitespace, because we called skip_whitespace just before,
862
                // so it's safe to call this guard function
863
                c if legal_char_in_string_start(c) => {
283✔
864
                    // don't need the offset, because '}' will always be the last char
865
                    let (_, c) = self.parse_char()?;
283✔
866
                    match c {
283✔
867
                        SingleOrMultiChar::Single(c) => buffer.push(c),
282✔
868
                        SingleOrMultiChar::Multi(first) => {
1✔
869
                            buffer.push(first);
1✔
870
                            self.parse_multi_escape_into_string(&mut buffer)?;
72✔
871
                        }
872
                    }
873
                }
UNCOV
874
                c => return self.error_here(PEK::UnexpectedChar(c)),
×
875
            }
876
        }
877

878
        let mut chars = buffer.chars();
70✔
879
        let literal = match (chars.next(), chars.next()) {
70✔
880
            (Some(c), None) => Literal::CharKind(SingleOrMultiChar::Single(c)),
14✔
881
            _ => Literal::String(buffer),
56✔
882
        };
883
        Ok((last_offset, literal))
70✔
884
    }
71✔
885

886
    // finishes a partial multi escape parse. in case of a parse error, self.single_set
887
    // may be left in an inconsistent state
888
    fn parse_multi_escape_into_set(&mut self) -> Result<()> {
4✔
889
        // note: would be good to somehow merge the two multi_escape methods. splitting up the UnicodeSetBuilder into a more
890
        // conventional parser + lexer combo might allow this.
891
        // issue is that we cannot pass this method an argument that somehow mutates `self` in the current architecture.
892
        // self.lexer.parse_multi_into_charappendable(&mut self.single_set) should work because the lifetimes are separate
893

894
        // whitespace before first char of this loop (ie, second char in this multi_escape) must be
895
        // enforced when creating the SingleOrMultiChar::Multi.
896
        let mut first = true;
4✔
897
        loop {
4✔
898
            let skipped = self.skip_whitespace();
10✔
899
            match self.must_peek_char()? {
10✔
900
                '}' => {
901
                    self.iter.next();
3✔
902
                    return Ok(());
3✔
903
                }
904
                initial_c => {
905
                    if skipped == 0 && !first {
7✔
906
                        // bracketed hex code points must be separated by whitespace
907
                        return self.error_here(PEK::UnexpectedChar(initial_c));
1✔
908
                    }
909
                    first = false;
6✔
910

911
                    let (_, c) = self.parse_hex_digits_into_char(1, 6)?;
6✔
912
                    self.single_set.add_char(c);
6✔
913
                }
914
            }
915
        }
916
    }
4✔
917

918
    // finishes a partial multi escape parse. in case of a parse error, the caller must clean up the
919
    // string if necessary.
920
    fn parse_multi_escape_into_string(&mut self, s: &mut String) -> Result<()> {
1✔
921
        // whitespace before first char of this loop (ie, second char in this multi_escape) must be
922
        // enforced when creating the SingleOrMultiChar::Multi.
923
        let mut first = true;
1✔
924
        loop {
1✔
925
            let skipped = self.skip_whitespace();
3✔
926
            match self.must_peek_char()? {
3✔
927
                '}' => {
928
                    self.iter.next();
1✔
929
                    return Ok(());
1✔
930
                }
931
                initial_c => {
932
                    if skipped == 0 && !first {
2✔
933
                        // bracketed hex code points must be separated by whitespace
UNCOV
934
                        return self.error_here(PEK::UnexpectedChar(initial_c));
×
935
                    }
936
                    first = false;
2✔
937

938
                    let (_, c) = self.parse_hex_digits_into_char(1, 6)?;
2✔
939
                    s.push(c);
2✔
940
                }
941
            }
942
        }
943
    }
1✔
944

945
    // starts with \ and consumes the whole escape sequence if a single
946
    // char is escaped, otherwise pauses the parse after the first char
947
    fn parse_escaped_char(&mut self) -> Result<(usize, SingleOrMultiChar)> {
133✔
948
        self.consume('\\')?;
133✔
949

950
        let (offset, next_char) = self.must_next()?;
133✔
951

952
        match next_char {
133✔
953
            'u' | 'x' if self.peek_char() == Some('{') => {
60✔
954
                // bracketedHex
955
                self.iter.next();
20✔
956

957
                self.skip_whitespace();
20✔
958
                let (_, first_c) = self.parse_hex_digits_into_char(1, 6)?;
20✔
959
                let skipped = self.skip_whitespace();
16✔
960

961
                match self.must_peek()? {
16✔
962
                    (offset, '}') => {
7✔
963
                        self.iter.next();
7✔
964
                        Ok((offset, SingleOrMultiChar::Single(first_c)))
7✔
965
                    }
7✔
966
                    // note: enforcing whitespace after the first char here, because the parse_multi_escape functions
967
                    // won't have access to this information anymore
968
                    (offset, c) if c.is_ascii_hexdigit() && skipped > 0 => {
9✔
969
                        Ok((offset, SingleOrMultiChar::Multi(first_c)))
7✔
970
                    }
7✔
971
                    (_, c) => self.error_here(PEK::UnexpectedChar(c)),
2✔
972
                }
973
            }
974
            'u' => {
975
                // 'u' hex{4}
976
                self.parse_hex_digits_into_char(4, 4)
24✔
977
                    .map(|(offset, c)| (offset, SingleOrMultiChar::Single(c)))
24✔
978
            }
979
            'x' => {
980
                // 'x' hex{2}
981
                self.parse_hex_digits_into_char(2, 2)
16✔
982
                    .map(|(offset, c)| (offset, SingleOrMultiChar::Single(c)))
15✔
983
            }
984
            'U' => {
985
                // 'U00' ('0' hex{5} | '10' hex{4})
986
                self.consume('0')?;
3✔
987
                self.consume('0')?;
136✔
988
                self.parse_hex_digits_into_char(6, 6)
3✔
989
                    .map(|(offset, c)| (offset, SingleOrMultiChar::Single(c)))
3✔
990
            }
991
            'N' => {
992
                // parse code point with name in {}
993
                // tracking issue: https://github.com/unicode-org/icu4x/issues/1397
994
                Err(PEK::Unimplemented.with_offset(offset))
1✔
995
            }
996
            'a' => Ok((offset, SingleOrMultiChar::Single('\u{0007}'))),
×
UNCOV
997
            'b' => Ok((offset, SingleOrMultiChar::Single('\u{0008}'))),
×
UNCOV
998
            't' => Ok((offset, SingleOrMultiChar::Single('\u{0009}'))),
×
999
            'n' => Ok((offset, SingleOrMultiChar::Single('\u{000A}'))),
9✔
UNCOV
1000
            'v' => Ok((offset, SingleOrMultiChar::Single('\u{000B}'))),
×
UNCOV
1001
            'f' => Ok((offset, SingleOrMultiChar::Single('\u{000C}'))),
×
1002
            'r' => Ok((offset, SingleOrMultiChar::Single('\u{000D}'))),
8✔
1003
            _ => Ok((offset, SingleOrMultiChar::Single(next_char))),
52✔
1004
        }
1005
    }
133✔
1006

1007
    // starts with :, consumes the trailing :]
1008
    fn parse_property_posix(&mut self) -> Result<()> {
85✔
1009
        self.consume(':')?;
85✔
1010
        if self.must_peek_char()? == '^' {
85✔
1011
            self.inverted = true;
3✔
1012
            self.iter.next();
3✔
1013
        }
1014

1015
        self.parse_property_inner(':')?;
85✔
1016

1017
        self.consume(']')?;
162✔
1018

1019
        Ok(())
77✔
1020
    }
85✔
1021

1022
    // starts with \p{ or \P{, consumes the trailing }
1023
    fn parse_property_perl(&mut self) -> Result<()> {
26✔
1024
        self.consume('\\')?;
26✔
1025
        match self.must_next()? {
26✔
1026
            (_, 'p') => {}
1027
            (_, 'P') => self.inverted = true,
1✔
UNCOV
1028
            (offset, c) => return Err(PEK::UnexpectedChar(c).with_offset(offset)),
×
1029
        }
1030
        self.consume('{')?;
26✔
1031

1032
        self.parse_property_inner('}')?;
51✔
1033

1034
        Ok(())
22✔
1035
    }
26✔
1036

1037
    fn parse_property_inner(&mut self, end: char) -> Result<()> {
114✔
1038
        // UnicodeSet spec ignores whitespace, '-', and '_',
1039
        // but ECMA-262 requires '_', so we'll allow that.
1040
        // TODO(#3559): support loose matching on property names (e.g., "AS  -_-  CII_Hex_ D-igit")
1041
        // TODO(#3559): support more properties than ECMA-262
1042

1043
        let property_offset;
1044

1045
        let mut key_buffer = String::new();
114✔
1046
        let mut value_buffer = String::new();
114✔
1047

1048
        enum State {
1049
            // initial state, nothing parsed yet
1050
            Begin,
1051
            // non-empty property name
1052
            PropertyName,
1053
            // property name parsed, '=' or '≠' parsed, no value parsed yet
1054
            PropertyValueBegin,
1055
            // non-empty property name, non-empty property value
1056
            PropertyValue,
1057
        }
1058
        use State::*;
1059

1060
        let mut state = Begin;
114✔
1061
        // whether '=' (true) or '≠' (false) was parsed
1062
        let mut equality = true;
114✔
1063

1064
        loop {
114✔
1065
            self.skip_whitespace();
684✔
1066
            match (state, self.must_peek_char()?) {
1,355✔
1067
                // parse the end of the property expression
1068
                (PropertyName | PropertyValue, c) if c == end => {
539✔
1069
                    // byte index of (full) property name/value is one back
1070
                    property_offset = self.must_peek_index()? - 1;
103✔
1071
                    self.iter.next();
103✔
1072
                    break;
1073
                }
1074
                // parse the property name
1075
                // NOTE: this might be too strict, because in the case of e.g. [:value:], we might want to
1076
                // allow [:lower-case-letter:] ([:gc=lower-case-letter:] works)
1077
                (Begin | PropertyName, c) if c.is_ascii_alphanumeric() || c == '_' => {
437✔
1078
                    key_buffer.push(c);
400✔
1079
                    self.iter.next();
400✔
1080
                    state = PropertyName;
400✔
1081
                }
400✔
1082
                // parse the name-value separator
1083
                (PropertyName, c @ ('=' | '≠')) => {
33✔
1084
                    equality = c == '=';
33✔
1085
                    self.iter.next();
33✔
1086
                    state = PropertyValueBegin;
31✔
1087
                }
31✔
1088
                // parse the property value
1089
                (PropertyValue | PropertyValueBegin, c) if c != end => {
140✔
1090
                    value_buffer.push(c);
139✔
1091
                    self.iter.next();
139✔
1092
                    state = PropertyValue;
139✔
1093
                }
139✔
1094
                (_, c) => return self.error_here(PEK::UnexpectedChar(c)),
5✔
1095
            }
1096
        }
1097

1098
        if !equality {
103✔
1099
            self.inverted = !self.inverted;
5✔
1100
        }
1101

1102
        let inverted = self
103✔
1103
            .load_property_codepoints(&key_buffer, &value_buffer)
103✔
1104
            // any error that does not already have an offset should use the appropriate property offset
1105
            .map_err(|e| e.or_with_offset(property_offset))?;
8✔
1106
        if inverted {
99✔
1107
            self.inverted = !self.inverted;
3✔
1108
        }
1109

1110
        Ok(())
99✔
1111
    }
110✔
1112

1113
    // returns whether the set needs to be inverted or not
1114
    fn load_property_codepoints(&mut self, key: &str, value: &str) -> Result<bool> {
103✔
1115
        // we support:
1116
        // [:gc = value:]
1117
        // [:sc = value:]
1118
        // [:scx = value:]
1119
        // [:Grapheme_Cluster_Break = value:]
1120
        // [:Sentence_Break = value:]
1121
        // [:Word_Break = value:]
1122
        // [:value:] - looks up value in gc, sc
1123
        // [:prop:] - binary property, returns codepoints that have the property
1124
        // [:prop = truthy/falsy:] - same as above
1125

1126
        let mut inverted = false;
103✔
1127

1128
        // contains a value for the General_Category property that needs to be tried
1129
        let mut try_gc = Err(PEK::UnknownProperty.into());
103✔
1130
        // contains a value for the Script property that needs to be tried
1131
        let mut try_sc = Err(PEK::UnknownProperty.into());
103✔
1132
        // contains a value for the Script_Extensions property that needs to be tried
1133
        let mut try_scx = Err(PEK::UnknownProperty.into());
103✔
1134
        // contains a value for the Grapheme_Cluster_Break property that needs to be tried
1135
        let mut try_gcb = Err(PEK::UnknownProperty.into());
103✔
1136
        // contains a value for the Line_Break property that needs to be tried
1137
        let mut try_lb = Err(PEK::UnknownProperty.into());
103✔
1138
        // contains a value for the Sentence_Break property that needs to be tried
1139
        let mut try_sb = Err(PEK::UnknownProperty.into());
103✔
1140
        // contains a value for the Word_Break property that needs to be tried
1141
        let mut try_wb = Err(PEK::UnknownProperty.into());
103✔
1142
        // contains a supposed binary property name that needs to be tried
1143
        let mut try_binary = Err(PEK::UnknownProperty.into());
103✔
1144
        // contains a supposed canonical combining class property name that needs to be tried
1145
        let mut try_ccc: Result<&str, ParseError> = Err(PEK::UnknownProperty.into());
103✔
1146
        // contains a supposed block property name that needs to be tried
1147
        let mut try_block: Result<&str, ParseError> = Err(PEK::UnknownProperty.into());
103✔
1148

1149
        if !value.is_empty() {
103✔
1150
            // key is gc, sc, scx, grapheme cluster break, sentence break, word break
1151
            // value is a property value
1152
            // OR
1153
            // key is a binary property and value is a truthy/falsy value
1154

1155
            match key.as_bytes() {
47✔
1156
                GeneralCategory::NAME | GeneralCategory::SHORT_NAME => try_gc = Ok(value),
34✔
1157
                GraphemeClusterBreak::NAME | GraphemeClusterBreak::SHORT_NAME => {
20✔
1158
                    try_gcb = Ok(value)
1✔
1159
                }
1160
                LineBreak::NAME | LineBreak::SHORT_NAME => try_lb = Ok(value),
15✔
1161
                Script::NAME | Script::SHORT_NAME => try_sc = Ok(value),
19✔
1162
                SentenceBreak::NAME | SentenceBreak::SHORT_NAME => try_sb = Ok(value),
15✔
1163
                WordBreak::NAME | WordBreak::SHORT_NAME => try_wb = Ok(value),
1✔
1164
                CanonicalCombiningClass::NAME | CanonicalCombiningClass::SHORT_NAME => {
13✔
UNCOV
1165
                    try_ccc = Ok(value)
×
1166
                }
1167
                b"Script_Extensions" | b"scx" => try_scx = Ok(value),
17✔
1168
                b"Block" | b"blk" => try_block = Ok(value),
13✔
1169
                _ => {
1170
                    let normalized_value = value.to_ascii_lowercase();
14✔
1171
                    let truthy = matches!(normalized_value.as_str(), "true" | "t" | "yes" | "y");
14✔
1172
                    let falsy = matches!(normalized_value.as_str(), "false" | "f" | "no" | "n");
14✔
1173
                    // value must either match truthy or falsy
1174
                    if truthy == falsy {
14✔
UNCOV
1175
                        return Err(PEK::UnknownProperty.into());
×
1176
                    }
1177
                    // correctness: if we reach this point, only `try_binary` can be Ok, hence
1178
                    // it does not matter that further down we unconditionally return `inverted`,
1179
                    // because only `try_binary` can enter that code path.
1180
                    inverted = falsy;
14✔
1181
                    try_binary = Ok(key);
14✔
1182
                }
14✔
1183
            }
1184
        } else {
1185
            // key is binary property
1186
            // OR a value of gc, sc (only gc or sc are supported as implicit keys by UTS35!)
1187
            try_gc = Ok(key);
73✔
1188
            try_sc = Ok(key);
73✔
1189
            try_binary = Ok(key);
73✔
1190
        }
1191

1192
        try_gc
206✔
1193
            .and_then(|value| self.try_load_general_category_set(value))
77✔
1194
            .or_else(|_| try_sc.and_then(|value| self.try_load_script_set(value)))
71✔
1195
            .or_else(|_| try_scx.and_then(|value| self.try_load_script_extensions_set(value)))
36✔
1196
            .or_else(|_| try_binary.and_then(|value| self.try_load_ecma262_binary_set(value)))
51✔
1197
            .or_else(|_| try_gcb.and_then(|value| self.try_load_grapheme_cluster_break_set(value)))
8✔
1198
            .or_else(|_| try_lb.and_then(|value| self.try_load_line_break_set(value)))
6✔
1199
            .or_else(|_| try_sb.and_then(|value| self.try_load_sentence_break_set(value)))
7✔
1200
            .or_else(|_| try_wb.and_then(|value| self.try_load_word_break_set(value)))
6✔
1201
            .or_else(|_| try_ccc.and_then(|value| self.try_load_ccc_set(value)))
4✔
1202
            .or_else(|_| try_block.and_then(|value| self.try_load_block_set(value)))?;
8✔
1203
        Ok(inverted)
99✔
1204
    }
103✔
1205

1206
    fn finalize(mut self) -> (CodePointInversionListBuilder, BTreeSet<String>) {
635✔
1207
        if self.inverted {
635✔
1208
            // code point inversion; removes all strings
1209
            #[cfg(feature = "log")]
1210
            if !self.string_set.is_empty() {
111✔
1211
                log::info!(
3✔
1212
                    "Inverting a unicode set with strings. This removes all strings entirely."
1213
                );
1214
            }
1215
            self.string_set.clear();
111✔
1216
            self.single_set.complement();
111✔
1217
        }
1218

1219
        (self.single_set, self.string_set)
635✔
1220
    }
635✔
1221

1222
    // parses either a raw char or an escaped char. all chars are allowed, the caller must make sure to handle
1223
    // cases where some characters are not allowed
1224
    fn parse_char(&mut self) -> Result<(usize, SingleOrMultiChar)> {
1,595✔
1225
        let (offset, c) = self.must_peek()?;
1,595✔
1226
        match c {
1,595✔
1227
            '\\' => self.parse_escaped_char(),
133✔
1228
            _ => {
1229
                self.iter.next();
1,462✔
1230
                Ok((offset, SingleOrMultiChar::Single(c)))
1,462✔
1231
            }
1232
        }
1233
    }
1,595✔
1234

1235
    // note: could turn this from the current two-pass approach into a one-pass approach
1236
    // by manually parsing the digits instead of using u32::from_str_radix.
1237
    fn parse_hex_digits_into_char(&mut self, min: usize, max: usize) -> Result<(usize, char)> {
71✔
1238
        let first_offset = self.must_peek_index()?;
71✔
1239
        let end_offset = self.validate_hex_digits(min, max)?;
71✔
1240

1241
        // validate_hex_digits ensures that chars (including the last one) are ascii hex digits,
1242
        // which are all exactly one UTF-8 byte long, so slicing on these offsets always respects char boundaries
1243
        let hex_source = &self.source[first_offset..=end_offset];
68✔
1244
        let num = u32::from_str_radix(hex_source, 16).map_err(|_| PEK::Internal)?;
68✔
1245
        char::try_from(num)
68✔
1246
            .map(|c| (end_offset, c))
66✔
1247
            .map_err(|_| PEK::InvalidEscape.with_offset(end_offset))
2✔
1248
    }
71✔
1249

1250
    // validates [0-9a-fA-F]{min,max}, returns the offset of the last digit, consuming everything in the process
1251
    fn validate_hex_digits(&mut self, min: usize, max: usize) -> Result<usize> {
71✔
1252
        let mut last_offset = 0;
71✔
1253
        for count in 0..max {
292✔
1254
            let (offset, c) = self.must_peek()?;
245✔
1255
            if !c.is_ascii_hexdigit() {
245✔
1256
                if count < min {
24✔
1257
                    return Err(PEK::UnexpectedChar(c).with_offset(offset));
3✔
1258
                } else {
1259
                    break;
1260
                }
1261
            }
1262
            self.iter.next();
221✔
1263
            last_offset = offset;
221✔
1264
        }
1265
        Ok(last_offset)
68✔
1266
    }
71✔
1267

1268
    // returns the number of skipped whitespace chars
1269
    fn skip_whitespace(&mut self) -> usize {
4,186✔
1270
        let mut num = 0;
4,186✔
1271
        while let Some(c) = self.peek_char() {
4,332✔
1272
            if !self.pat_ws.contains(c) {
4,331✔
1273
                break;
1274
            }
1275
            self.iter.next();
146✔
1276
            num += 1;
146✔
1277
        }
1278
        num
4,186✔
1279
    }
4,186✔
1280

1281
    fn consume(&mut self, expected: char) -> Result<()> {
509✔
1282
        match self.must_next()? {
509✔
1283
            (offset, c) if c != expected => Err(PEK::UnexpectedChar(c).with_offset(offset)),
509✔
1284
            _ => Ok(()),
508✔
1285
        }
1286
    }
509✔
1287

1288
    // use this whenever an empty iterator would imply an Eof error
1289
    fn must_next(&mut self) -> Result<(usize, char)> {
668✔
1290
        self.iter.next().ok_or(PEK::Eof.into())
668✔
1291
    }
668✔
1292

1293
    // use this whenever an empty iterator would imply an Eof error
1294
    fn must_peek(&mut self) -> Result<(usize, char)> {
10,689✔
1295
        self.iter.peek().copied().ok_or(PEK::Eof.into())
10,689✔
1296
    }
10,689✔
1297

1298
    // must_peek, but looks two chars ahead. use sparingly
1299
    fn must_peek_double(&mut self) -> Result<(usize, char)> {
1,996✔
1300
        let mut copy = self.iter.clone();
1,996✔
1301
        copy.next();
1,996✔
1302
        copy.next().ok_or(PEK::Eof.into())
1,996✔
1303
    }
1,996✔
1304

1305
    // see must_peek
1306
    fn must_peek_char(&mut self) -> Result<char> {
2,963✔
1307
        self.must_peek().map(|(_, c)| c)
5,925✔
1308
    }
2,963✔
1309

1310
    // see must_peek
1311
    fn must_peek_index(&mut self) -> Result<usize> {
721✔
1312
        self.must_peek().map(|(idx, _)| idx)
1,440✔
1313
    }
721✔
1314

1315
    fn peek_char(&mut self) -> Option<char> {
5,048✔
1316
        self.iter.peek().map(|&(_, c)| c)
10,096✔
1317
    }
5,048✔
1318

1319
    // TODO: return Result<!> once ! is stable
1320
    #[inline]
1321
    fn error_here<T>(&mut self, kind: ParseErrorKind) -> Result<T> {
11✔
1322
        match self.iter.peek() {
11✔
UNCOV
1323
            None => Err(kind.into()),
×
1324
            Some(&(offset, _)) => Err(kind.with_offset(offset)),
11✔
1325
        }
1326
    }
11✔
1327

1328
    fn process_strings(&mut self, op: Operation, other_strings: BTreeSet<String>) {
209✔
1329
        match op {
209✔
1330
            Operation::Union => self.string_set.extend(other_strings),
157✔
1331
            Operation::Difference => {
1332
                self.string_set = self
27✔
1333
                    .string_set
1334
                    .difference(&other_strings)
1335
                    .cloned()
UNCOV
1336
                    .collect()
×
1337
            }
27✔
1338
            Operation::Intersection => {
1339
                self.string_set = self
25✔
1340
                    .string_set
1341
                    .intersection(&other_strings)
1342
                    .cloned()
UNCOV
1343
                    .collect()
×
1344
            }
25✔
1345
        }
1346
    }
209✔
1347

1348
    fn process_chars(&mut self, op: Operation, other_chars: CodePointInversionList) {
209✔
1349
        match op {
209✔
1350
            Operation::Union => self.single_set.add_set(&other_chars),
157✔
1351
            Operation::Difference => self.single_set.remove_set(&other_chars),
27✔
1352
            Operation::Intersection => self.single_set.retain_set(&other_chars),
25✔
1353
        }
1354
    }
209✔
1355

1356
    fn try_load_general_category_set(&mut self, name: &str) -> Result<()> {
77✔
1357
        // TODO(#3550): This could be cached; does not depend on name.
1358
        let name_map =
1359
            PropertyParser::<GeneralCategoryGroup>::try_new_unstable(self.property_provider)
77✔
UNCOV
1360
                .map_err(|_| PEK::Internal)?;
×
1361
        let gc_value = name_map
77✔
1362
            .as_borrowed()
1363
            .get_loose(name)
1364
            .ok_or(PEK::UnknownProperty)?;
98✔
1365
        // TODO(#3550): This could be cached; does not depend on name.
1366
        let set = CodePointMapData::<GeneralCategory>::try_new_unstable(self.property_provider)
56✔
UNCOV
1367
            .map_err(|_| PEK::Internal)?
×
1368
            .as_borrowed()
1369
            .get_set_for_value_group(gc_value);
56✔
1370
        self.single_set.add_set(&set.to_code_point_inversion_list());
56✔
1371
        Ok(())
56✔
1372
    }
77✔
1373

1374
    fn try_get_script(&self, name: &str) -> Result<Script> {
28✔
1375
        // TODO(#3550): This could be cached; does not depend on name.
1376
        let name_map = PropertyParser::<Script>::try_new_unstable(self.property_provider)
28✔
UNCOV
1377
            .map_err(|_| PEK::Internal)?;
×
1378
        name_map
28✔
1379
            .as_borrowed()
1380
            .get_loose(name)
1381
            .ok_or(PEK::UnknownProperty.into())
28✔
1382
    }
28✔
1383

1384
    fn try_load_script_set(&mut self, name: &str) -> Result<()> {
24✔
1385
        let sc_value = self.try_get_script(name)?;
24✔
1386
        // TODO(#3550): This could be cached; does not depend on name.
1387
        let property_map = CodePointMapData::<Script>::try_new_unstable(self.property_provider)
15✔
UNCOV
1388
            .map_err(|_| PEK::Internal)?;
×
1389
        let set = property_map.as_borrowed().get_set_for_value(sc_value);
15✔
1390
        self.single_set.add_set(&set.to_code_point_inversion_list());
15✔
1391
        Ok(())
15✔
1392
    }
24✔
1393

1394
    fn try_load_script_extensions_set(&mut self, name: &str) -> Result<()> {
4✔
1395
        // TODO(#3550): This could be cached; does not depend on name.
1396
        let scx = ScriptWithExtensions::try_new_unstable(self.property_provider)
4✔
UNCOV
1397
            .map_err(|_| PEK::Internal)?;
×
1398
        let sc_value = self.try_get_script(name)?;
4✔
1399
        let set = scx.as_borrowed().get_script_extensions_set(sc_value);
4✔
1400
        self.single_set.add_set(&set);
4✔
1401
        Ok(())
4✔
1402
    }
4✔
1403

1404
    fn try_load_ecma262_binary_set(&mut self, name: &str) -> Result<()> {
23✔
1405
        let set =
1406
            CodePointSetData::try_new_for_ecma262_unstable(self.property_provider, name.as_bytes())
46✔
1407
                .ok_or(PEK::UnknownProperty)?
23✔
1408
                .map_err(|_data_error| PEK::Internal)?;
×
1409
        self.single_set.add_set(&set.to_code_point_inversion_list());
21✔
1410
        Ok(())
21✔
1411
    }
23✔
1412

1413
    fn try_load_grapheme_cluster_break_set(&mut self, name: &str) -> Result<()> {
1✔
1414
        let parser =
1415
            PropertyParser::<GraphemeClusterBreak>::try_new_unstable(self.property_provider)
1✔
1416
                .map_err(|_| PEK::Internal)?;
×
1417
        let gcb_value = parser
1✔
1418
            .as_borrowed()
1419
            .get_loose(name)
1420
            .ok_or(PEK::UnknownProperty)?;
1✔
1421
        // TODO(#3550): This could be cached; does not depend on name.
1422
        let property_map =
1423
            CodePointMapData::<GraphemeClusterBreak>::try_new_unstable(self.property_provider)
1✔
1424
                .map_err(|_| PEK::Internal)?;
×
1425
        let set = property_map.as_borrowed().get_set_for_value(gcb_value);
1✔
1426
        self.single_set.add_set(&set.to_code_point_inversion_list());
1✔
1427
        Ok(())
1✔
1428
    }
1✔
1429

UNCOV
1430
    fn try_load_line_break_set(&mut self, name: &str) -> Result<()> {
×
UNCOV
1431
        let parser = PropertyParser::<LineBreak>::try_new_unstable(self.property_provider)
×
1432
            .map_err(|_| PEK::Internal)?;
×
UNCOV
1433
        let lb_value = parser
×
1434
            .as_borrowed()
1435
            .get_loose(name)
UNCOV
1436
            .ok_or(PEK::UnknownProperty)?;
×
1437
        // TODO(#3550): This could be cached; does not depend on name.
UNCOV
1438
        let property_map = CodePointMapData::<LineBreak>::try_new_unstable(self.property_provider)
×
UNCOV
1439
            .map_err(|_| PEK::Internal)?;
×
1440
        let set = property_map.as_borrowed().get_set_for_value(lb_value);
×
UNCOV
1441
        self.single_set.add_set(&set.to_code_point_inversion_list());
×
UNCOV
1442
        Ok(())
×
UNCOV
1443
    }
×
1444

1445
    fn try_load_sentence_break_set(&mut self, name: &str) -> Result<()> {
1✔
1446
        let parser = PropertyParser::<SentenceBreak>::try_new_unstable(self.property_provider)
1✔
1447
            .map_err(|_| PEK::Internal)?;
×
1448
        let sb_value = parser
1✔
1449
            .as_borrowed()
1450
            .get_loose(name)
1451
            .ok_or(PEK::UnknownProperty)?;
1✔
1452
        // TODO(#3550): This could be cached; does not depend on name.
1453
        let property_map =
1454
            CodePointMapData::<SentenceBreak>::try_new_unstable(self.property_provider)
1✔
1455
                .map_err(|_| PEK::Internal)?;
×
1456
        let set = property_map.as_borrowed().get_set_for_value(sb_value);
1✔
1457
        self.single_set.add_set(&set.to_code_point_inversion_list());
1✔
1458
        Ok(())
1✔
1459
    }
1✔
1460

1461
    fn try_load_word_break_set(&mut self, name: &str) -> Result<()> {
1✔
1462
        let parser = PropertyParser::<WordBreak>::try_new_unstable(self.property_provider)
1✔
UNCOV
1463
            .map_err(|_| PEK::Internal)?;
×
1464
        let wb_value = parser
1✔
1465
            .as_borrowed()
1466
            .get_loose(name)
1467
            .ok_or(PEK::UnknownProperty)?;
1✔
1468
        // TODO(#3550): This could be cached; does not depend on name.
1469
        let property_map = CodePointMapData::<WordBreak>::try_new_unstable(self.property_provider)
1✔
1470
            .map_err(|_| PEK::Internal)?;
×
1471
        let set = property_map.as_borrowed().get_set_for_value(wb_value);
1✔
1472
        self.single_set.add_set(&set.to_code_point_inversion_list());
1✔
1473
        Ok(())
1✔
1474
    }
1✔
1475

1476
    fn try_load_ccc_set(&mut self, name: &str) -> Result<()> {
×
1477
        let parser =
1478
            PropertyParser::<CanonicalCombiningClass>::try_new_unstable(self.property_provider)
×
1479
                .map_err(|_| PEK::Internal)?;
×
1480
        let value = parser
×
1481
            .as_borrowed()
1482
            .get_loose(name)
1483
            // TODO: make the property parser do this
1484
            .or_else(|| {
×
1485
                name.parse()
×
1486
                    .ok()
1487
                    .map(CanonicalCombiningClass::from_icu4c_value)
1488
            })
×
1489
            .ok_or(PEK::UnknownProperty)?;
×
1490
        // TODO(#3550): This could be cached; does not depend on name.
1491
        let property_map =
UNCOV
1492
            CodePointMapData::<CanonicalCombiningClass>::try_new_unstable(self.property_provider)
×
UNCOV
1493
                .map_err(|_| PEK::Internal)?;
×
UNCOV
1494
        let set = property_map.as_borrowed().get_set_for_value(value);
×
UNCOV
1495
        self.single_set.add_set(&set.to_code_point_inversion_list());
×
UNCOV
1496
        Ok(())
×
UNCOV
1497
    }
×
1498

UNCOV
1499
    fn try_load_block_set(&mut self, name: &str) -> Result<()> {
×
1500
        // TODO: source these from properties
UNCOV
1501
        self.single_set
×
UNCOV
1502
            .add_range(match name.to_ascii_lowercase().as_str() {
×
UNCOV
1503
                "arabic" => '\u{0600}'..'\u{06FF}',
×
UNCOV
1504
                "thaana" => '\u{0780}'..'\u{07BF}',
×
1505
                _ => {
1506
                    #[cfg(feature = "log")]
UNCOV
1507
                    log::warn!("Skipping :block={name}:");
×
UNCOV
1508
                    return Err(PEK::Unimplemented.into());
×
1509
                }
UNCOV
1510
            });
×
UNCOV
1511
        Ok(())
×
UNCOV
1512
    }
×
1513
}
1514

1515
/// Parses a UnicodeSet pattern and returns a UnicodeSet in the form of a [`CodePointInversionListAndStringList`](CodePointInversionListAndStringList),
1516
/// as well as the number of bytes consumed from the source string.
1517
///
1518
/// Supports UnicodeSets as described in [UTS #35 - Unicode Sets](https://unicode.org/reports/tr35/#Unicode_Sets).
1519
///
1520
/// The error type of the returned Result can be pretty-printed with [`ParseError::fmt_with_source`].
1521
///
1522
/// # Variables
1523
///
1524
/// If you need support for variables inside UnicodeSets (e.g., `[$start-$end]`), use [`parse_with_variables`].
1525
///
1526
/// # Limitations
1527
///
1528
/// * Currently, we only support the [ECMA-262 properties](https://tc39.es/ecma262/#table-nonbinary-unicode-properties).
1529
///   The property names must match the exact spelling listed in ECMA-262. Note that we do support UTS35 syntax for elided `General_Category`
1530
///   and `Script` property names, i.e., `[:Latn:]` and `[:Ll:]` are both valid, with the former implying the `Script` property, and the latter the
1531
///   `General_Category` property.
1532
/// * We do not support `\N{Unicode code point name}` character escaping. Use any other escape method described in UTS35.
1533
///
1534
/// ✨ *Enabled with the `compiled_data` Cargo feature.*
1535
///
1536
/// [📚 Help choosing a constructor](icu_provider::constructors)
1537
///
1538
/// # Examples
1539
///
1540
/// Parse ranges
1541
/// ```
1542
/// use icu::experimental::unicodeset_parse::parse;
1543
///
1544
/// let source = "[a-zA-Z0-9]";
1545
/// let (set, consumed) = parse(source).unwrap();
1546
/// let code_points = set.code_points();
1547
///
1548
/// assert!(code_points.contains_range('a'..='z'));
1549
/// assert!(code_points.contains_range('A'..='Z'));
1550
/// assert!(code_points.contains_range('0'..='9'));
1551
/// assert_eq!(consumed, source.len());
1552
/// ```
1553
///
1554
/// Parse properties, set operations, inner sets
1555
/// ```
1556
/// use icu::experimental::unicodeset_parse::parse;
1557
///
1558
/// let (set, _) =
1559
///     parse("[[:^ll:]-[^][:gc = Lowercase Letter:]&[^[[^]-[a-z]]]]").unwrap();
1560
/// assert!(set.code_points().contains_range('a'..='z'));
1561
/// assert_eq!(('a'..='z').count(), set.size());
1562
/// ```
1563
///
1564
/// Inversions remove strings
1565
/// ```
1566
/// use icu::experimental::unicodeset_parse::parse;
1567
///
1568
/// let (set, _) =
1569
///     parse(r"[[a-z{hello\ world}]&[^a-y{hello\ world}]]").unwrap();
1570
/// assert!(set.contains('z'));
1571
/// assert_eq!(set.size(), 1);
1572
/// assert!(!set.has_strings());
1573
/// ```
1574
///
1575
/// Set operators (including the implicit union) have the same precedence and are left-associative
1576
/// ```
1577
/// use icu::experimental::unicodeset_parse::parse;
1578
///
1579
/// let (set, _) = parse("[[ace][bdf] - [abc][def]]").unwrap();
1580
/// assert!(set.code_points().contains_range('d'..='f'));
1581
/// assert_eq!(set.size(), ('d'..='f').count());
1582
/// ```
1583
///
1584
/// Supports partial parses
1585
/// ```
1586
/// use icu::experimental::unicodeset_parse::parse;
1587
///
1588
/// let (set, consumed) = parse("[a-c][x-z]").unwrap();
1589
/// let code_points = set.code_points();
1590
/// assert!(code_points.contains_range('a'..='c'));
1591
/// assert!(!code_points.contains_range('x'..='z'));
1592
/// assert_eq!(set.size(), ('a'..='c').count());
1593
/// // only the first UnicodeSet is parsed
1594
/// assert_eq!(consumed, "[a-c]".len());
1595
/// ```
1596
#[cfg(feature = "compiled_data")]
1597
pub fn parse(source: &str) -> Result<(CodePointInversionListAndStringList<'static>, usize)> {
134✔
1598
    parse_unstable(source, &icu_properties::provider::Baked)
134✔
1599
}
134✔
1600

1601
/// Parses a UnicodeSet pattern with support for variables enabled.
1602
///
1603
/// See [`parse`] for more information.
1604
///
1605
/// # Examples
1606
///
1607
/// ```
1608
/// use icu::experimental::unicodeset_parse::*;
1609
///
1610
/// let (my_set, _) = parse("[abc]").unwrap();
1611
///
1612
/// let mut variable_map = VariableMap::new();
1613
/// variable_map.insert_char("start".into(), 'a').unwrap();
1614
/// variable_map.insert_char("end".into(), 'z').unwrap();
1615
/// variable_map.insert_string("str".into(), "Hello World".into()).unwrap();
1616
/// variable_map.insert_set("the_set".into(), my_set).unwrap();
1617
///
1618
/// // If a variable already exists, `Err` is returned, and the map is not updated.
1619
/// variable_map.insert_char("end".into(), 'Ω').unwrap_err();
1620
///
1621
/// let source = "[[$start-$end]-$the_set $str]";
1622
/// let (set, consumed) = parse_with_variables(source, &variable_map).unwrap();
1623
/// assert_eq!(consumed, source.len());
1624
/// assert!(set.code_points().contains_range('d'..='z'));
1625
/// assert!(set.contains_str("Hello World"));
1626
/// assert_eq!(set.size(), 1 + ('d'..='z').count());
1627
#[cfg(feature = "compiled_data")]
1628
pub fn parse_with_variables(
74✔
1629
    source: &str,
1630
    variable_map: &VariableMap<'_>,
1631
) -> Result<(CodePointInversionListAndStringList<'static>, usize)> {
1632
    parse_unstable_with_variables(source, variable_map, &icu_properties::provider::Baked)
74✔
1633
}
74✔
1634

1635
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, parse_with_variables)]
1636
pub fn parse_unstable_with_variables<P>(
495✔
1637
    source: &str,
1638
    variable_map: &VariableMap<'_>,
1639
    provider: &P,
1640
) -> Result<(CodePointInversionListAndStringList<'static>, usize)>
1641
where
1642
    P: ?Sized
1643
        + DataProvider<PropertyBinaryAlphabeticV1>
1644
        + DataProvider<PropertyBinaryAsciiHexDigitV1>
1645
        + DataProvider<PropertyBinaryBidiControlV1>
1646
        + DataProvider<PropertyBinaryBidiMirroredV1>
1647
        + DataProvider<PropertyBinaryCasedV1>
1648
        + DataProvider<PropertyBinaryCaseIgnorableV1>
1649
        + DataProvider<PropertyBinaryChangesWhenCasefoldedV1>
1650
        + DataProvider<PropertyBinaryChangesWhenCasemappedV1>
1651
        + DataProvider<PropertyBinaryChangesWhenLowercasedV1>
1652
        + DataProvider<PropertyBinaryChangesWhenNfkcCasefoldedV1>
1653
        + DataProvider<PropertyBinaryChangesWhenTitlecasedV1>
1654
        + DataProvider<PropertyBinaryChangesWhenUppercasedV1>
1655
        + DataProvider<PropertyBinaryDashV1>
1656
        + DataProvider<PropertyBinaryDefaultIgnorableCodePointV1>
1657
        + DataProvider<PropertyBinaryDeprecatedV1>
1658
        + DataProvider<PropertyBinaryDiacriticV1>
1659
        + DataProvider<PropertyBinaryEmojiComponentV1>
1660
        + DataProvider<PropertyBinaryEmojiModifierBaseV1>
1661
        + DataProvider<PropertyBinaryEmojiModifierV1>
1662
        + DataProvider<PropertyBinaryEmojiPresentationV1>
1663
        + DataProvider<PropertyBinaryEmojiV1>
1664
        + DataProvider<PropertyBinaryExtendedPictographicV1>
1665
        + DataProvider<PropertyBinaryExtenderV1>
1666
        + DataProvider<PropertyBinaryGraphemeBaseV1>
1667
        + DataProvider<PropertyBinaryGraphemeExtendV1>
1668
        + DataProvider<PropertyBinaryHexDigitV1>
1669
        + DataProvider<PropertyBinaryIdContinueV1>
1670
        + DataProvider<PropertyBinaryIdeographicV1>
1671
        + DataProvider<PropertyBinaryIdsBinaryOperatorV1>
1672
        + DataProvider<PropertyBinaryIdStartV1>
1673
        + DataProvider<PropertyBinaryIdsTrinaryOperatorV1>
1674
        + DataProvider<PropertyBinaryJoinControlV1>
1675
        + DataProvider<PropertyBinaryLogicalOrderExceptionV1>
1676
        + DataProvider<PropertyBinaryLowercaseV1>
1677
        + DataProvider<PropertyBinaryMathV1>
1678
        + DataProvider<PropertyBinaryNoncharacterCodePointV1>
1679
        + DataProvider<PropertyBinaryPatternSyntaxV1>
1680
        + DataProvider<PropertyBinaryPatternWhiteSpaceV1>
1681
        + DataProvider<PropertyBinaryQuotationMarkV1>
1682
        + DataProvider<PropertyBinaryRadicalV1>
1683
        + DataProvider<PropertyBinaryRegionalIndicatorV1>
1684
        + DataProvider<PropertyBinarySentenceTerminalV1>
1685
        + DataProvider<PropertyBinarySoftDottedV1>
1686
        + DataProvider<PropertyBinaryTerminalPunctuationV1>
1687
        + DataProvider<PropertyBinaryUnifiedIdeographV1>
1688
        + DataProvider<PropertyBinaryUppercaseV1>
1689
        + DataProvider<PropertyBinaryVariationSelectorV1>
1690
        + DataProvider<PropertyBinaryWhiteSpaceV1>
1691
        + DataProvider<PropertyBinaryXidContinueV1>
1692
        + DataProvider<PropertyBinaryXidStartV1>
1693
        + DataProvider<PropertyEnumCanonicalCombiningClassV1>
1694
        + DataProvider<PropertyEnumGeneralCategoryV1>
1695
        + DataProvider<PropertyEnumGraphemeClusterBreakV1>
1696
        + DataProvider<PropertyEnumLineBreakV1>
1697
        + DataProvider<PropertyEnumScriptV1>
1698
        + DataProvider<PropertyEnumSentenceBreakV1>
1699
        + DataProvider<PropertyEnumWordBreakV1>
1700
        + DataProvider<PropertyNameParseCanonicalCombiningClassV1>
1701
        + DataProvider<PropertyNameParseGeneralCategoryMaskV1>
1702
        + DataProvider<PropertyNameParseGraphemeClusterBreakV1>
1703
        + DataProvider<PropertyNameParseLineBreakV1>
1704
        + DataProvider<PropertyNameParseScriptV1>
1705
        + DataProvider<PropertyNameParseSentenceBreakV1>
1706
        + DataProvider<PropertyNameParseWordBreakV1>
1707
        + DataProvider<PropertyScriptWithExtensionsV1>,
1708
{
1709
    // TODO(#3550): Add function "parse_overescaped" that uses a custom iterator to de-overescape (i.e., maps \\ to \) on-the-fly?
1710
    // ^ will likely need a different iterator type on UnicodeSetBuilder
1711

1712
    let mut iter = source.char_indices().peekable();
495✔
1713

1714
    let xid_start =
1715
        CodePointSetData::try_new_unstable::<XidStart>(provider).map_err(|_| PEK::Internal)?;
495✔
1716
    let xid_start_list = xid_start.to_code_point_inversion_list();
495✔
1717
    let xid_continue =
1718
        CodePointSetData::try_new_unstable::<XidContinue>(provider).map_err(|_| PEK::Internal)?;
495✔
1719
    let xid_continue_list = xid_continue.to_code_point_inversion_list();
495✔
1720

1721
    let pat_ws = CodePointSetData::try_new_unstable::<PatternWhiteSpace>(provider)
495✔
UNCOV
1722
        .map_err(|_| PEK::Internal)?;
×
1723
    let pat_ws_list = pat_ws.to_code_point_inversion_list();
495✔
1724

1725
    let mut builder = UnicodeSetBuilder::new_internal(
495✔
1726
        &mut iter,
1727
        source,
1728
        variable_map,
1729
        &xid_start_list,
1730
        &xid_continue_list,
1731
        &pat_ws_list,
1732
        provider,
1733
    );
495✔
1734

1735
    builder.parse_unicode_set()?;
990✔
1736
    let (single, string_set) = builder.finalize();
442✔
1737
    let built_single = single.build();
442✔
1738

1739
    let mut strings = string_set.into_iter().collect::<Vec<_>>();
442✔
1740
    strings.sort();
442✔
1741
    let zerovec = (&strings).into();
442✔
1742

1743
    let cpinvlistandstrlist = CodePointInversionListAndStringList::try_from(built_single, zerovec)
442✔
UNCOV
1744
        .map_err(|_| PEK::Internal)?;
×
1745

1746
    let parsed_bytes = match iter.peek().copied() {
442✔
1747
        None => source.len(),
163✔
1748
        Some((offset, _)) => offset,
279✔
1749
    };
1750

1751
    Ok((cpinvlistandstrlist, parsed_bytes))
442✔
1752
}
495✔
1753

1754
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, parse)]
1755
pub fn parse_unstable<P>(
150✔
1756
    source: &str,
1757
    provider: &P,
1758
) -> Result<(CodePointInversionListAndStringList<'static>, usize)>
1759
where
1760
    P: ?Sized
1761
        + DataProvider<PropertyBinaryAlphabeticV1>
1762
        + DataProvider<PropertyBinaryAsciiHexDigitV1>
1763
        + DataProvider<PropertyBinaryBidiControlV1>
1764
        + DataProvider<PropertyBinaryBidiMirroredV1>
1765
        + DataProvider<PropertyBinaryCasedV1>
1766
        + DataProvider<PropertyBinaryCaseIgnorableV1>
1767
        + DataProvider<PropertyBinaryChangesWhenCasefoldedV1>
1768
        + DataProvider<PropertyBinaryChangesWhenCasemappedV1>
1769
        + DataProvider<PropertyBinaryChangesWhenLowercasedV1>
1770
        + DataProvider<PropertyBinaryChangesWhenNfkcCasefoldedV1>
1771
        + DataProvider<PropertyBinaryChangesWhenTitlecasedV1>
1772
        + DataProvider<PropertyBinaryChangesWhenUppercasedV1>
1773
        + DataProvider<PropertyBinaryDashV1>
1774
        + DataProvider<PropertyBinaryDefaultIgnorableCodePointV1>
1775
        + DataProvider<PropertyBinaryDeprecatedV1>
1776
        + DataProvider<PropertyBinaryDiacriticV1>
1777
        + DataProvider<PropertyBinaryEmojiComponentV1>
1778
        + DataProvider<PropertyBinaryEmojiModifierBaseV1>
1779
        + DataProvider<PropertyBinaryEmojiModifierV1>
1780
        + DataProvider<PropertyBinaryEmojiPresentationV1>
1781
        + DataProvider<PropertyBinaryEmojiV1>
1782
        + DataProvider<PropertyBinaryExtendedPictographicV1>
1783
        + DataProvider<PropertyBinaryExtenderV1>
1784
        + DataProvider<PropertyBinaryGraphemeBaseV1>
1785
        + DataProvider<PropertyBinaryGraphemeExtendV1>
1786
        + DataProvider<PropertyBinaryHexDigitV1>
1787
        + DataProvider<PropertyBinaryIdContinueV1>
1788
        + DataProvider<PropertyBinaryIdeographicV1>
1789
        + DataProvider<PropertyBinaryIdsBinaryOperatorV1>
1790
        + DataProvider<PropertyBinaryIdStartV1>
1791
        + DataProvider<PropertyBinaryIdsTrinaryOperatorV1>
1792
        + DataProvider<PropertyBinaryJoinControlV1>
1793
        + DataProvider<PropertyBinaryLogicalOrderExceptionV1>
1794
        + DataProvider<PropertyBinaryLowercaseV1>
1795
        + DataProvider<PropertyBinaryMathV1>
1796
        + DataProvider<PropertyBinaryNoncharacterCodePointV1>
1797
        + DataProvider<PropertyBinaryPatternSyntaxV1>
1798
        + DataProvider<PropertyBinaryPatternWhiteSpaceV1>
1799
        + DataProvider<PropertyBinaryQuotationMarkV1>
1800
        + DataProvider<PropertyBinaryRadicalV1>
1801
        + DataProvider<PropertyBinaryRegionalIndicatorV1>
1802
        + DataProvider<PropertyBinarySentenceTerminalV1>
1803
        + DataProvider<PropertyBinarySoftDottedV1>
1804
        + DataProvider<PropertyBinaryTerminalPunctuationV1>
1805
        + DataProvider<PropertyBinaryUnifiedIdeographV1>
1806
        + DataProvider<PropertyBinaryUppercaseV1>
1807
        + DataProvider<PropertyBinaryVariationSelectorV1>
1808
        + DataProvider<PropertyBinaryWhiteSpaceV1>
1809
        + DataProvider<PropertyBinaryXidContinueV1>
1810
        + DataProvider<PropertyBinaryXidStartV1>
1811
        + DataProvider<PropertyEnumCanonicalCombiningClassV1>
1812
        + DataProvider<PropertyEnumGeneralCategoryV1>
1813
        + DataProvider<PropertyEnumGraphemeClusterBreakV1>
1814
        + DataProvider<PropertyEnumLineBreakV1>
1815
        + DataProvider<PropertyEnumScriptV1>
1816
        + DataProvider<PropertyEnumSentenceBreakV1>
1817
        + DataProvider<PropertyEnumWordBreakV1>
1818
        + DataProvider<PropertyNameParseCanonicalCombiningClassV1>
1819
        + DataProvider<PropertyNameParseGeneralCategoryMaskV1>
1820
        + DataProvider<PropertyNameParseGraphemeClusterBreakV1>
1821
        + DataProvider<PropertyNameParseLineBreakV1>
1822
        + DataProvider<PropertyNameParseScriptV1>
1823
        + DataProvider<PropertyNameParseSentenceBreakV1>
1824
        + DataProvider<PropertyNameParseWordBreakV1>
1825
        + DataProvider<PropertyScriptWithExtensionsV1>,
1826
{
1827
    let dummy = Default::default();
150✔
1828
    parse_unstable_with_variables(source, &dummy, provider)
150✔
1829
}
150✔
1830

1831
#[cfg(test)]
1832
mod tests {
1833
    use core::ops::RangeInclusive;
1834
    use std::collections::HashSet;
1835

1836
    use super::*;
1837

1838
    // "aabxzz" => [a..=a, b..=x, z..=z]
1839
    fn range_iter_from_str(s: &str) -> impl Iterator<Item = RangeInclusive<u32>> {
139✔
1840
        debug_assert_eq!(
278✔
1841
            s.chars().count() % 2,
139✔
1842
            0,
1843
            "string \"{}\" does not contain an even number of code points",
1844
            s.escape_debug()
1845
        );
1846
        let mut res = vec![];
139✔
1847
        let mut skip = false;
139✔
1848
        for (a, b) in s.chars().zip(s.chars().skip(1)) {
382✔
1849
            if skip {
243✔
1850
                skip = false;
66✔
1851
                continue;
1852
            }
1853
            let a = a as u32;
177✔
1854
            let b = b as u32;
177✔
1855
            res.push(a..=b);
177✔
1856
            skip = true;
177✔
1857
        }
1858

1859
        res.into_iter()
139✔
1860
    }
139✔
1861

1862
    fn assert_set_equality<'a>(
139✔
1863
        source: &str,
1864
        cpinvlistandstrlist: &CodePointInversionListAndStringList,
1865
        single: impl Iterator<Item = RangeInclusive<u32>>,
1866
        strings: impl Iterator<Item = &'a str>,
1867
    ) {
1868
        let expected_ranges: HashSet<_> = single.collect();
139✔
1869
        let actual_ranges: HashSet<_> = cpinvlistandstrlist.code_points().iter_ranges().collect();
139✔
1870
        assert_eq!(
139✔
1871
            actual_ranges,
1872
            expected_ranges,
1873
            "got unexpected ranges {:?}, expected {:?} for parsed set \"{}\"",
1874
            actual_ranges,
1875
            expected_ranges,
1876
            source.escape_debug()
1877
        );
1878
        let mut expected_size = cpinvlistandstrlist.code_points().size();
139✔
1879
        for s in strings {
160✔
1880
            expected_size += 1;
21✔
UNCOV
1881
            assert!(
×
1882
                cpinvlistandstrlist.contains_str(s),
21✔
1883
                "missing string \"{}\" from parsed set \"{}\"",
UNCOV
1884
                s.escape_debug(),
×
UNCOV
1885
                source.escape_debug()
×
1886
            );
1887
        }
139✔
1888
        let actual_size = cpinvlistandstrlist.size();
139✔
1889
        assert_eq!(
139✔
1890
            actual_size,
1891
            expected_size,
1892
            "got unexpected size {}, expected {} for parsed set \"{}\"",
1893
            actual_size,
1894
            expected_size,
1895
            source.escape_debug()
1896
        );
1897
    }
139✔
1898

1899
    fn assert_is_error_and_message_eq(source: &str, expected_err: &str, vm: &VariableMap<'_>) {
46✔
1900
        let result = parse_with_variables(source, vm);
46✔
1901
        assert!(result.is_err(), "{source} does not cause an error!");
46✔
1902
        let err = result.unwrap_err();
46✔
1903
        assert_eq!(err.fmt_with_source(source).to_string(), expected_err);
92✔
1904
    }
46✔
1905

1906
    #[test]
1907
    fn test_semantics_with_variables() {
2✔
1908
        let mut map_char_char = VariableMap::default();
1✔
1909
        map_char_char.insert_char("a".to_string(), 'a').unwrap();
1✔
1910
        map_char_char.insert_char("var2".to_string(), 'z').unwrap();
1✔
1911

1912
        let mut map_headache = VariableMap::default();
1✔
1913
        map_headache.insert_char("hehe".to_string(), '-').unwrap();
1✔
1914

1915
        let mut map_char_string = VariableMap::default();
1✔
1916
        map_char_string.insert_char("a".to_string(), 'a').unwrap();
1✔
1917
        map_char_string
1✔
1918
            .insert_string("var2".to_string(), "abc".to_string())
2✔
1919
            .unwrap();
1920

1921
        let (set, _) = parse(r"[a-z {Hello,\ World!}]").unwrap();
1✔
1922
        let mut map_char_set = VariableMap::default();
1✔
1923
        map_char_set.insert_char("a".to_string(), 'a').unwrap();
1✔
1924
        map_char_set.insert_set("set".to_string(), set).unwrap();
1✔
1925

1926
        let cases: Vec<(_, _, _, Vec<&str>)> = vec![
2✔
1927
            // simple
1928
            (&map_char_char, "[$a]", "aa", vec![]),
1✔
1929
            (&map_char_char, "[ $a ]", "aa", vec![]),
1✔
1930
            (&map_char_char, "[$a$]", "aa\u{ffff}\u{ffff}", vec![]),
1✔
1931
            (&map_char_char, "[$a$ ]", "aa\u{ffff}\u{ffff}", vec![]),
1✔
1932
            (&map_char_char, "[$a$var2]", "aazz", vec![]),
1✔
1933
            (&map_char_char, "[$a - $var2]", "az", vec![]),
1✔
1934
            (&map_char_char, "[$a-$var2]", "az", vec![]),
1✔
1935
            (&map_headache, "[a $hehe z]", "aazz--", vec![]),
1✔
1936
            (
1✔
1937
                &map_char_char,
1938
                "[[$]var2]",
1939
                "\u{ffff}\u{ffff}vvaarr22",
1940
                vec![],
1✔
1941
            ),
1942
            // variable prefix escaping
1943
            (&map_char_char, r"[\$var2]", "$$vvaarr22", vec![]),
1✔
1944
            (&map_char_char, r"[\\$var2]", r"\\zz", vec![]),
1✔
1945
            // no variable dereferencing in strings
1946
            (&map_char_char, "[{$a}]", "", vec!["$a"]),
1✔
1947
            // set operations
1948
            (&map_char_set, "[$set & [b-z]]", "bz", vec![]),
1✔
1949
            (&map_char_set, "[[a-z]-[b-z]]", "aa", vec![]),
1✔
1950
            (&map_char_set, "[$set-[b-z]]", "aa", vec!["Hello, World!"]),
1✔
1951
            (&map_char_set, "[$set-$set]", "", vec![]),
1✔
1952
            (&map_char_set, "[[a-zA]-$set]", "AA", vec![]),
1✔
1953
            (&map_char_set, "[$set[b-z]]", "az", vec!["Hello, World!"]),
1✔
1954
            (&map_char_set, "[[a-a]$set]", "az", vec!["Hello, World!"]),
1✔
1955
            (&map_char_set, "$set", "az", vec!["Hello, World!"]),
1✔
1956
            // strings
1957
            (&map_char_string, "[$var2]", "", vec!["abc"]),
1✔
1958
        ];
1959
        for (variable_map, source, single, strings) in cases {
22✔
1960
            let parsed = parse_with_variables(source, variable_map);
21✔
1961
            if let Err(err) = parsed {
21✔
UNCOV
1962
                panic!(
×
1963
                    "{source} results in an error: {}",
UNCOV
1964
                    err.fmt_with_source(source)
×
1965
                );
1966
            }
1967
            let (set, consumed) = parsed.unwrap();
21✔
1968
            assert_eq!(consumed, source.len(), "{source:?} is not fully consumed");
21✔
1969
            assert_set_equality(
21✔
1970
                source,
1971
                &set,
1972
                range_iter_from_str(single),
21✔
1973
                strings.into_iter(),
21✔
1974
            );
21✔
1975
        }
22✔
1976
    }
2✔
1977

1978
    #[test]
1979
    fn test_semantics() {
2✔
1980
        const ALL_CHARS: &str = "\x00\u{10FFFF}";
1981
        let cases: Vec<(_, _, Vec<&str>)> = vec![
2✔
1982
            // simple
1983
            ("[a]", "aa", vec![]),
1✔
1984
            ("[]", "", vec![]),
1✔
1985
            ("[qax]", "aaqqxx", vec![]),
1✔
1986
            ("[a-z]", "az", vec![]),
1✔
1987
            ("[--]", "--", vec![]),
1✔
1988
            ("[a-b-]", "ab--", vec![]),
1✔
1989
            ("[[a-b]-]", "ab--", vec![]),
1✔
1990
            ("[{ab}-]", "--", vec!["ab"]),
1✔
1991
            ("[-a-b]", "ab--", vec![]),
1✔
1992
            ("[-a]", "--aa", vec![]),
1✔
1993
            // whitespace escaping
1994
            (r"[\n]", "\n\n", vec![]),
1✔
1995
            ("[\\\n]", "\n\n", vec![]),
1✔
1996
            // empty - whitespace is skipped
1997
            ("[\n]", "", vec![]),
1✔
1998
            ("[\u{9}]", "", vec![]),
1✔
1999
            ("[\u{A}]", "", vec![]),
1✔
2000
            ("[\u{B}]", "", vec![]),
1✔
2001
            ("[\u{C}]", "", vec![]),
1✔
2002
            ("[\u{D}]", "", vec![]),
1✔
2003
            ("[\u{20}]", "", vec![]),
1✔
2004
            ("[\u{85}]", "", vec![]),
1✔
2005
            ("[\u{200E}]", "", vec![]),
1✔
2006
            ("[\u{200F}]", "", vec![]),
1✔
2007
            ("[\u{2028}]", "", vec![]),
1✔
2008
            ("[\u{2029}]", "", vec![]),
1✔
2009
            // whitespace significance:
2010
            ("[^[^$]]", "\u{ffff}\u{ffff}", vec![]),
1✔
2011
            ("[^[^ $]]", "\u{ffff}\u{ffff}", vec![]),
1✔
2012
            ("[^[^ $ ]]", "\u{ffff}\u{ffff}", vec![]),
1✔
2013
            ("[^[^a$]]", "aa\u{ffff}\u{ffff}", vec![]),
1✔
2014
            ("[^[^a$ ]]", "aa\u{ffff}\u{ffff}", vec![]),
1✔
2015
            ("[-]", "--", vec![]),
1✔
2016
            ("[  -  ]", "--", vec![]),
1✔
2017
            ("[  - -  ]", "--", vec![]),
1✔
2018
            ("[ a-b -  ]", "ab--", vec![]),
1✔
2019
            ("[ -a]", "--aa", vec![]),
1✔
2020
            ("[a-]", "--aa", vec![]),
1✔
2021
            ("[a- ]", "--aa", vec![]),
1✔
2022
            ("[ :]", "::", vec![]),
1✔
2023
            ("[ :L:]", "::LL", vec![]),
1✔
2024
            // but not all "whitespace", only Pattern_White_Space:
2025
            ("[\u{A0}]", "\u{A0}\u{A0}", vec![]), // non-breaking space
1✔
2026
            // anchor
2027
            ("[$]", "\u{ffff}\u{ffff}", vec![]),
1✔
2028
            (r"[\$]", "$$", vec![]),
1✔
2029
            ("[{$}]", "$$", vec![]),
1✔
2030
            // set operations
2031
            ("[[a-z]&[b-z]]", "bz", vec![]),
1✔
2032
            ("[[a-z]-[b-z]]", "aa", vec![]),
1✔
2033
            ("[[a-z][b-z]]", "az", vec![]),
1✔
2034
            ("[[a-a][b-z]]", "az", vec![]),
1✔
2035
            ("[[a-z{abc}]&[b-z{abc}{abx}]]", "bz", vec!["abc"]),
1✔
2036
            ("[[{abx}a-z{abc}]&[b-z{abc}]]", "bz", vec!["abc"]),
1✔
2037
            ("[[a-z{abx}]-[{abx}b-z{abc}]]", "aa", vec![]),
1✔
2038
            ("[[a-z{abx}{abc}]-[{abx}b-z]]", "aa", vec!["abc"]),
1✔
2039
            ("[[a-z{abc}][b-z{abx}]]", "az", vec!["abc", "abx"]),
1✔
2040
            // strings
2041
            ("[{this is a minus -}]", "", vec!["thisisaminus-"]),
1✔
2042
            // associativity
2043
            ("[[a-a][b-z] - [a-d][e-z]]", "ez", vec![]),
1✔
2044
            ("[[a-a][b-z] - [a-d]&[e-z]]", "ez", vec![]),
1✔
2045
            ("[[a-a][b-z] - [a-z][]]", "", vec![]),
1✔
2046
            ("[[a-a][b-z] - [a-z]&[]]", "", vec![]),
1✔
2047
            ("[[a-a][b-z] & [a-z]-[]]", "az", vec![]),
1✔
2048
            ("[[a-a][b-z] & []-[a-z]]", "", vec![]),
1✔
2049
            ("[[a-a][b-z] & [a-b][x-z]]", "abxz", vec![]),
1✔
2050
            ("[[a-z]-[a-b]-[y-z]]", "cx", vec![]),
1✔
2051
            // escape tests
2052
            (r"[\x61-\x63]", "ac", vec![]),
1✔
2053
            (r"[a-\x63]", "ac", vec![]),
1✔
2054
            (r"[\x61-c]", "ac", vec![]),
1✔
2055
            (r"[\u0061-\x63]", "ac", vec![]),
1✔
2056
            (r"[\U00000061-\x63]", "ac", vec![]),
1✔
2057
            (r"[\x{61}-\x63]", "ac", vec![]),
1✔
2058
            (r"[\u{61}-\x63]", "ac", vec![]),
1✔
2059
            (r"[\u{61}{hello\ world}]", "aa", vec!["hello world"]),
1✔
2060
            (r"[{hello\ world}\u{61}]", "aa", vec!["hello world"]),
1✔
2061
            (r"[{h\u{65}llo\ world}]", "", vec!["hello world"]),
1✔
2062
            // complement tests
2063
            (r"[^]", ALL_CHARS, vec![]),
1✔
2064
            (r"[[^]-[^a-z]]", "az", vec![]),
1✔
2065
            (r"[^{h\u{65}llo\ world}]", ALL_CHARS, vec![]),
1✔
2066
            (
1✔
2067
                r"[^[{h\u{65}llo\ world}]-[{hello\ world}]]",
2068
                ALL_CHARS,
2069
                vec![],
1✔
2070
            ),
2071
            (
1✔
2072
                r"[^[\x00-\U0010FFFF]-[\u0100-\U0010FFFF]]",
2073
                "\u{100}\u{10FFFF}",
2074
                vec![],
1✔
2075
            ),
2076
            (r"[^[^a-z]]", "az", vec![]),
1✔
2077
            (r"[^[^\^]]", "^^", vec![]),
1✔
2078
            (r"[{\x{61 0062   063}}]", "", vec!["abc"]),
1✔
2079
            (r"[\x{61 0062   063}]", "ac", vec![]),
1✔
2080
            // binary properties
2081
            (r"[:AHex:]", "09afAF", vec![]),
1✔
2082
            (r"[:AHex=True:]", "09afAF", vec![]),
1✔
2083
            (r"[:AHex=T:]", "09afAF", vec![]),
1✔
2084
            (r"[:AHex=Yes:]", "09afAF", vec![]),
1✔
2085
            (r"[:AHex=Y:]", "09afAF", vec![]),
1✔
2086
            (r"[:^AHex≠True:]", "09afAF", vec![]),
1✔
2087
            (r"[:AHex≠False:]", "09afAF", vec![]),
1✔
2088
            (r"[[:^AHex≠False:]&[\x00-\x10]]", "\0\x10", vec![]),
1✔
2089
            (r"\p{AHex}", "09afAF", vec![]),
1✔
2090
            (r"\p{AHex=True}", "09afAF", vec![]),
1✔
2091
            (r"\p{AHex=T}", "09afAF", vec![]),
1✔
2092
            (r"\p{AHex=Yes}", "09afAF", vec![]),
1✔
2093
            (r"\p{AHex=Y}", "09afAF", vec![]),
1✔
2094
            (r"\P{AHex≠True}", "09afAF", vec![]),
1✔
2095
            (r"\p{AHex≠False}", "09afAF", vec![]),
1✔
2096
            // general category
2097
            (r"[[:gc=lower-case-letter:]&[a-zA-Z]]", "az", vec![]),
1✔
2098
            (r"[[:lower case letter:]&[a-zA-Z]]", "az", vec![]),
1✔
2099
            // general category groups
2100
            // equivalence between L and the union of all the L* categories
2101
            (
1✔
2102
                r"[[[:L:]-[\p{Ll}\p{Lt}\p{Lu}\p{Lo}\p{Lm}]][[\p{Ll}\p{Lt}\p{Lu}\p{Lo}\p{Lm}]-[:L:]]]",
2103
                "",
2104
                vec![],
1✔
2105
            ),
2106
            // script
2107
            (r"[[:sc=latn:]&[a-zA-Z]]", "azAZ", vec![]),
1✔
2108
            (r"[[:sc=Latin:]&[a-zA-Z]]", "azAZ", vec![]),
1✔
2109
            (r"[[:Latin:]&[a-zA-Z]]", "azAZ", vec![]),
1✔
2110
            (r"[[:latn:]&[a-zA-Z]]", "azAZ", vec![]),
1✔
2111
            // script extensions
2112
            (r"[[:scx=latn:]&[a-zA-Z]]", "azAZ", vec![]),
1✔
2113
            (r"[[:scx=Latin:]&[a-zA-Z]]", "azAZ", vec![]),
1✔
2114
            (r"[[:scx=Hira:]&[\u30FC]]", "\u{30FC}\u{30FC}", vec![]),
1✔
2115
            (r"[[:sc=Hira:]&[\u30FC]]", "", vec![]),
1✔
2116
            (r"[[:scx=Kana:]&[\u30FC]]", "\u{30FC}\u{30FC}", vec![]),
1✔
2117
            (r"[[:sc=Kana:]&[\u30FC]]", "", vec![]),
1✔
2118
            (r"[[:sc=Common:]&[\u30FC]]", "\u{30FC}\u{30FC}", vec![]),
1✔
2119
            // grapheme cluster break
2120
            (
1✔
2121
                r"\p{Grapheme_Cluster_Break=ZWJ}",
2122
                "\u{200D}\u{200D}",
2123
                vec![],
1✔
2124
            ),
2125
            // sentence break
2126
            (
1✔
2127
                r"\p{Sentence_Break=ATerm}",
2128
                "\u{002E}\u{002E}\u{2024}\u{2024}\u{FE52}\u{FE52}\u{FF0E}\u{FF0E}",
2129
                vec![],
1✔
2130
            ),
2131
            // word break
2132
            (r"\p{Word_Break=Single_Quote}", "\u{0027}\u{0027}", vec![]),
1✔
2133
            // more syntax edge cases from UTS35 directly
2134
            (r"[\^a]", "^^aa", vec![]),
1✔
2135
            (r"[{{}]", "{{", vec![]),
1✔
2136
            (r"[{}}]", "}}", vec![""]),
1✔
2137
            (r"[}]", "}}", vec![]),
1✔
2138
            (r"[{$var}]", "", vec!["$var"]),
1✔
2139
            (r"[{[a-z}]", "", vec!["[a-z"]),
1✔
2140
            (r"[ { [ a - z } ]", "", vec!["[a-z"]),
1✔
2141
            // TODO(#3556): Add more tests (specifically conformance tests if they exist)
2142
        ];
2143
        for (source, single, strings) in cases {
119✔
2144
            let parsed = parse(source);
118✔
2145
            if let Err(err) = parsed {
118✔
UNCOV
2146
                panic!(
×
2147
                    "{source} results in an error: {}",
UNCOV
2148
                    err.fmt_with_source(source)
×
2149
                );
2150
            }
2151
            let (set, consumed) = parsed.unwrap();
118✔
2152
            assert_eq!(consumed, source.len());
118✔
2153
            assert_set_equality(
118✔
2154
                source,
2155
                &set,
2156
                range_iter_from_str(single),
118✔
2157
                strings.into_iter(),
118✔
2158
            );
118✔
2159
        }
119✔
2160
    }
2✔
2161

2162
    #[test]
2163
    fn test_error_messages_with_variables() {
2✔
2164
        let mut map_char_char = VariableMap::default();
1✔
2165
        map_char_char.insert_char("a".to_string(), 'a').unwrap();
1✔
2166
        map_char_char.insert_char("var2".to_string(), 'z').unwrap();
1✔
2167

2168
        let mut map_char_string = VariableMap::default();
1✔
2169
        map_char_string.insert_char("a".to_string(), 'a').unwrap();
1✔
2170
        map_char_string
1✔
2171
            .insert_string("var2".to_string(), "abc".to_string())
2✔
2172
            .unwrap();
2173

2174
        let (set, _) = parse(r"[a-z {Hello,\ World!}]").unwrap();
1✔
2175
        let mut map_char_set = VariableMap::default();
1✔
2176
        map_char_set.insert_char("a".to_string(), 'a').unwrap();
1✔
2177
        map_char_set.insert_set("set".to_string(), set).unwrap();
1✔
2178

2179
        let cases = [
1✔
2180
            (&map_char_char, "[$$a]", r"[$$a← error: unexpected variable"),
1✔
2181
            (
1✔
2182
                &map_char_char,
2183
                "[$ a]",
2184
                r"[$ a← error: unexpected character 'a'",
2185
            ),
2186
            (&map_char_char, "$a", r"$a← error: unexpected variable"),
1✔
2187
            (&map_char_char, "$", r"$← error: unexpected end of input"),
1✔
2188
            (
1✔
2189
                &map_char_string,
2190
                "[$var2-$a]",
2191
                r"[$var2-$a← error: unexpected variable",
2192
            ),
2193
            (
1✔
2194
                &map_char_string,
2195
                "[$a-$var2]",
2196
                r"[$a-$var2← error: unexpected variable",
2197
            ),
2198
            (
1✔
2199
                &map_char_set,
2200
                "[$a-$set]",
2201
                r"[$a-$set← error: unexpected variable",
2202
            ),
2203
            (
1✔
2204
                &map_char_set,
2205
                "[$set-$a]",
2206
                r"[$set-$a← error: unexpected variable",
2207
            ),
2208
            (
1✔
2209
                &map_char_set,
2210
                "[$=]",
2211
                "[$=← error: unexpected character '='",
2212
            ),
2213
        ];
2214
        for (variable_map, source, expected_err) in cases {
10✔
2215
            assert_is_error_and_message_eq(source, expected_err, variable_map);
9✔
2216
        }
1✔
2217
    }
2✔
2218

2219
    #[test]
2220
    fn test_error_messages() {
2✔
2221
        let cases = [
1✔
2222
            (r"[a-z[\]]", r"[a-z[\]]← error: unexpected end of input"),
1✔
2223
            (r"", r"← error: unexpected end of input"),
1✔
2224
            (r"[{]", r"[{]← error: unexpected end of input"),
1✔
2225
            // we match ECMA-262 strictly, so case matters
2226
            (
1✔
2227
                r"[:general_category:]",
2228
                r"[:general_category← error: unknown property",
2229
            ),
2230
            (r"[:ll=true:]", r"[:ll=true← error: unknown property"),
1✔
2231
            (r"[:=", r"[:=← error: unexpected character '='"),
1✔
2232
            // property names may not be empty
2233
            (r"[::]", r"[::← error: unexpected character ':'"),
1✔
2234
            (r"[:=hello:]", r"[:=← error: unexpected character '='"),
1✔
2235
            // property values may not be empty
2236
            (r"[:gc=:]", r"[:gc=:← error: unexpected character ':'"),
1✔
2237
            (r"[\xag]", r"[\xag← error: unexpected character 'g'"),
1✔
2238
            (r"[a-b-z]", r"[a-b-z← error: unexpected character 'z'"),
1✔
2239
            // TODO(#3558): Might be better as "[a-\p← error: unexpected character 'p'"?
2240
            (r"[a-\p{ll}]", r"[a-\← error: unexpected character '\\'"),
1✔
2241
            (r"[a-&]", r"[a-&← error: unexpected character '&'"),
1✔
2242
            (r"[a&b]", r"[a&← error: unexpected character '&'"),
1✔
2243
            (r"[[set]&b]", r"[[set]&b← error: unexpected character 'b'"),
1✔
2244
            (r"[[set]&]", r"[[set]&]← error: unexpected character ']'"),
1✔
2245
            (r"[a-\x60]", r"[a-\x60← error: unexpected character '`'"),
1✔
2246
            (r"[a-`]", r"[a-`← error: unexpected character '`'"),
1✔
2247
            (r"[\x{6g}]", r"[\x{6g← error: unexpected character 'g'"),
1✔
2248
            (r"[\x{g}]", r"[\x{g← error: unexpected character 'g'"),
1✔
2249
            (r"[\x{}]", r"[\x{}← error: unexpected character '}'"),
1✔
2250
            (
1✔
2251
                r"[\x{dabeef}]",
2252
                r"[\x{dabeef← error: invalid escape sequence",
2253
            ),
2254
            (
1✔
2255
                r"[\x{10ffff0}]",
2256
                r"[\x{10ffff0← error: unexpected character '0'",
2257
            ),
2258
            (
1✔
2259
                r"[\x{11ffff}]",
2260
                r"[\x{11ffff← error: invalid escape sequence",
2261
            ),
2262
            (
1✔
2263
                r"[\x{10ffff 1 10ffff0}]",
2264
                r"[\x{10ffff 1 10ffff0← error: unexpected character '0'",
2265
            ),
2266
            // > 1 byte in UTF-8 edge case
2267
            (r"ä", r"ä← error: unexpected character 'ä'"),
1✔
2268
            (r"\p{gc=ä}", r"\p{gc=ä← error: unknown property"),
1✔
2269
            (r"\p{gc=ä}", r"\p{gc=ä← error: unknown property"),
1✔
2270
            (
1✔
2271
                r"[\xe5-\xe4]",
2272
                r"[\xe5-\xe4← error: unexpected character 'ä'",
2273
            ),
2274
            (r"[\xe5-ä]", r"[\xe5-ä← error: unexpected character 'ä'"),
1✔
2275
            // whitespace significance
2276
            (r"[ ^]", r"[ ^← error: unexpected character '^'"),
1✔
2277
            (r"[:]", r"[:]← error: unexpected character ']'"),
1✔
2278
            (r"[:L]", r"[:L]← error: unexpected character ']'"),
1✔
2279
            (r"\p {L}", r"\p ← error: unexpected character ' '"),
1✔
2280
            // multi-escapes are not allowed in ranges
2281
            (
1✔
2282
                r"[\x{61 62}-d]",
2283
                r"[\x{61 62}-d← error: unexpected character 'd'",
2284
            ),
2285
            (
1✔
2286
                r"[\x{61 63}-\x{62 64}]",
2287
                r"[\x{61 63}-\← error: unexpected character '\\'",
2288
            ),
2289
            // TODO(#3558): This is a bad error message.
2290
            (r"[a-\x{62 64}]", r"[a-\← error: unexpected character '\\'"),
1✔
2291
        ];
2292
        let vm = Default::default();
1✔
2293
        for (source, expected_err) in cases {
38✔
2294
            assert_is_error_and_message_eq(source, expected_err, &vm);
37✔
2295
        }
1✔
2296
    }
2✔
2297

2298
    #[test]
2299
    fn test_consumed() {
2✔
2300
        let cases = [
1✔
2301
            (r"[a-z\]{[}]".len(), r"[a-z\]{[}][]"),
1✔
2302
            (r"[a-z\]{[}]".len(), r"[a-z\]{[}] []"),
1✔
2303
            (r"[a-z\]{[}]".len(), r"[a-z\]{]}] []"),
1✔
2304
            (r"[a-z\]{{[}]".len(), r"[a-z\]{{]}] []"),
1✔
2305
            (r"[a-z\]{[}]".len(), r"[a-z\]{]}]\p{L}"),
1✔
2306
            (r"[a-z\]{[}]".len(), r"[a-z\]{]}]$var"),
1✔
2307
        ];
2308

2309
        let vm = Default::default();
1✔
2310
        for (expected_consumed, source) in cases {
7✔
2311
            let (_, consumed) = parse(source).unwrap();
6✔
2312
            assert_eq!(expected_consumed, consumed);
6✔
2313
            let (_, consumed) = parse_with_variables(source, &vm).unwrap();
6✔
2314
            assert_eq!(expected_consumed, consumed);
6✔
2315
        }
1✔
2316
    }
2✔
2317
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc