java_signatures/
internal.rs

1/// Implements the parsing of java signatures.
2use std::{borrow::Cow, error::Error, fmt::Display, ops::Range};
3
4// this one is published outside out crate
5#[derive(Debug, Copy, Clone, PartialEq, Eq)]
6pub enum BaseType {
7    Byte,    // B
8    Char,    // C
9    Double,  // D
10    Float,   // F
11    Int,     // I
12    Long,    // J
13    Short,   // S
14    Boolean, // Z
15}
16
17#[derive(Debug)]
18pub enum JavaType {
19    Base(BaseType),
20    Reference(ReferenceType),
21}
22
23#[derive(Debug)]
24pub enum TypeArgument {
25    Unbounded,
26    Default(ReferenceType),
27    Extends(ReferenceType),
28    Super(ReferenceType),
29}
30
31#[derive(Debug, PartialEq, Eq)]
32pub struct Slice {
33    start: u16, // ~ inclusive
34    end: u16,   // ~ exclusive
35}
36
37impl Slice {
38    pub fn start(&self) -> usize {
39        self.start as usize
40    }
41
42    /// Panics if `s` is too to be indexed by this slice.
43    pub fn apply<'a>(&self, s: &'a str) -> &'a str {
44        &s[self.start as usize..self.end as usize]
45    }
46}
47
48/// Panics if the range includes boundaries greater than `u16::MAX`
49/// or if the upper bounds bound is equal to or less the lower bound.
50impl From<Range<usize>> for Slice {
51    fn from(value: Range<usize>) -> Self {
52        if value.start >= value.end {
53            panic!("{} >= {}!", value.start, value.end);
54        }
55        if value.start >= u16::MAX as usize || value.end >= u16::MAX as usize {
56            panic!("boundary/ies too big!");
57        }
58        Self {
59            start: value.start as u16,
60            end: value.end as u16,
61        }
62    }
63}
64
65impl From<Slice> for Range<usize> {
66    fn from(val: Slice) -> Self {
67        (val.start as usize)..(val.end as usize)
68    }
69}
70
71#[derive(Debug)]
72pub struct SimpleClassType(pub Slice, pub Vec<TypeArgument>);
73
74#[derive(Debug)]
75pub struct ClassType(pub SimpleClassType, pub Vec<SimpleClassType>);
76
77#[derive(Debug)]
78pub enum ReferenceType {
79    Class(ClassType),
80    Variable(Slice),
81    Array { dimension: usize, ty: Box<JavaType> },
82}
83
84#[derive(Debug)]
85pub struct TypeParameter {
86    pub name: Slice,
87    pub class_bound: Option<ReferenceType>,
88    pub iface_bounds: Vec<ReferenceType>,
89}
90
91#[derive(Debug)]
92pub enum ResultType {
93    VoidType,
94    ValueType(JavaType),
95}
96
97#[derive(Debug)]
98pub enum ThrowsType {
99    ClassType(ClassType),
100    /// Specify the name (as a slice) of the type variable being referenced
101    TypeVariable(Slice),
102}
103
104#[derive(Debug)]
105pub struct ClassSignature {
106    pub type_params: Vec<TypeParameter>,
107    pub super_class: ClassType,
108    pub super_ifaces: Vec<ClassType>,
109}
110
111#[derive(Debug)]
112pub struct MethodSignature {
113    pub type_params: Vec<TypeParameter>,
114    pub parameters: Vec<JavaType>,
115    pub result: ResultType,
116    pub throws: Vec<ThrowsType>,
117}
118
119// ----------------------------------------------------------------
120
121pub fn parse<'a, F, T, MI, I>(name: &'static str, parser: F, s: &'a str, make_iter: MI) -> Result<T>
122where
123    I: Iterator<Item = Character>,
124    F: Fn(&mut I) -> Result<T>,
125    MI: Fn(&'a str) -> I + 'a,
126{
127    if s.is_empty() {
128        Err(ParseError::new(name, 0, "empty input"))
129    } else {
130        let mut chars = make_iter(s);
131        parser(&mut chars)
132            .map_err(|mut e| {
133                if e.position == EOF_POSITION {
134                    e.position = s.len();
135                }
136                e
137            })
138            .and_then(|value| {
139                if let Some((pos, _)) = chars.next() {
140                    Err(ParseError::new(
141                        name,
142                        pos,
143                        "bad input; expected end of input",
144                    ))
145                } else {
146                    Ok(value)
147                }
148            })
149    }
150}
151
152// ----------------------------------------------------------------
153
154// ~ constant used to denote the (fictive) position of an unexpected
155// eof occurance. lower-level code can raise the `eof` error without
156// knowing the exact position, and higher-level code can patch up the
157// position based on the parsed input string's length.
158const EOF_POSITION: usize = usize::MAX;
159
160#[derive(Debug, PartialEq)]
161pub struct ParseError {
162    pub context: &'static str,
163    pub position: usize,
164    pub error: Cow<'static, str>,
165}
166
167impl Display for ParseError {
168    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169        write!(
170            f,
171            "{}: [position: {} / context: {}]",
172            self.error, self.position, self.context
173        )
174    }
175}
176
177impl Error for ParseError {}
178
179impl ParseError {
180    fn new<E: Into<Cow<'static, str>>>(context: &'static str, position: usize, error: E) -> Self {
181        Self {
182            position,
183            error: error.into(),
184            context,
185        }
186    }
187    fn eof(context: &'static str) -> Self {
188        Self {
189            position: EOF_POSITION,
190            error: Cow::Borrowed("unexpected end of input"),
191            context,
192        }
193    }
194}
195
196type Result<T> = std::result::Result<T, ParseError>;
197
198/// Consumes the next available character or fails with an "unexpected
199/// end of input" error if the `chars` stream is exhausted.
200fn consume_next(
201    context: &'static str,
202    chars: &mut impl Iterator<Item = Character>,
203) -> Result<Character> {
204    match chars.next() {
205        None => Err(ParseError::eof(context)),
206        Some(c) => Ok(c),
207    }
208}
209
210/// "Consumes" the given character (assumed to the next in the
211/// consumed character stream) and fails if it doesn't match the
212/// `expected` char.
213fn consume_next_expected(
214    context: &'static str,
215    next: Option<Character>,
216    expected: char,
217) -> Result<()> {
218    match next {
219        None => Err(ParseError::eof(context)),
220        Some((_, next)) if next == expected => Ok(()),
221        Some((pos, _)) => Err(ParseError::new(
222            context,
223            pos,
224            format!("bad input; expected: `{expected}`"),
225        )),
226    }
227}
228
229/// Consumes the next available character if any (if there is no more,
230/// returns `Ok(None)`) and fails if it doesn't not match the
231/// `expected` char. Returns `Ok(Some(()))` in the
232/// positive case.
233fn consume_next_eof_or_expected(
234    context: &'static str,
235    chars: &mut impl Iterator<Item = Character>,
236    expected: char,
237) -> Result<Option<()>> {
238    match chars.next() {
239        None => Ok(None),
240        c => {
241            consume_next_expected(context, c, expected)?;
242            Ok(Some(()))
243        }
244    }
245}
246
247// ~ the type being passed around when consuming characters while
248// parsing signatures; essentially the character together with its
249// position in the input stream
250type Character = (usize, char);
251
252fn to_base_type(c: char) -> Option<BaseType> {
253    match c {
254        'B' => Some(BaseType::Byte),
255        'C' => Some(BaseType::Char),
256        'D' => Some(BaseType::Double),
257        'F' => Some(BaseType::Float),
258        'I' => Some(BaseType::Int),
259        'J' => Some(BaseType::Long),
260        'S' => Some(BaseType::Short),
261        'Z' => Some(BaseType::Boolean),
262        _ => None,
263    }
264}
265
266pub fn consume_reference_type_signature(
267    chars: &mut impl Iterator<Item = Character>,
268) -> Result<ReferenceType> {
269    consume_next("ReferenceTypeSignature", chars)
270        .and_then(|c| consume_reference_type_signature_(c, chars))
271}
272
273fn consume_reference_type_signature_(
274    mut c: Character,
275    more_chars: &mut impl Iterator<Item = Character>,
276) -> Result<ReferenceType> {
277    fn maybe_as_array(dimension: usize, ty: ReferenceType) -> ReferenceType {
278        if dimension == 0 {
279            ty
280        } else {
281            ReferenceType::Array {
282                dimension,
283                ty: Box::new(JavaType::Reference(ty)),
284            }
285        }
286    }
287    // ~ as soon as `>0` we're having an ArrayType; once an
288    // `ArrayType` is started there can only be following more
289    // `ArrayType` dimensions or a terminating ClassType or
290    // TypeVariable
291    let mut dimension = 0;
292    loop {
293        match c.1 {
294            '[' => {
295                dimension += 1;
296
297                // ArrayTypeSignature
298                let next = consume_next("ReferenceTypeSignature", more_chars)?;
299                match to_base_type(next.1) {
300                    Some(ty) => {
301                        debug_assert!(dimension > 0);
302                        return Ok(ReferenceType::Array {
303                            dimension,
304                            ty: Box::new(JavaType::Base(ty)),
305                        });
306                    }
307                    None => {
308                        c = next; // ~ and recurse/repeat
309                    }
310                }
311            }
312            'L' => {
313                // ClassTypeSignature
314                return consume_class_type_signature(more_chars)
315                    .map(|ty| maybe_as_array(dimension, ReferenceType::Class(ty)));
316            }
317            'T' => {
318                // TypeVariableSignature
319                let r = consume_unqualified_identifer(more_chars)?;
320                return match r.1 {
321                    None => Err(ParseError::eof("ReferenceTypeSignature")),
322                    Some((_, ';')) => Ok(maybe_as_array(dimension, ReferenceType::Variable(r.0))),
323                    Some((pos, _)) => Err(ParseError::new(
324                        "ReferenceTypeSignature",
325                        pos,
326                        "bad input; expected `;`",
327                    )),
328                };
329            }
330            _ => {
331                return Err(ParseError::new(
332                    "ReferenceTypeSignature",
333                    c.0,
334                    "bad input; expected `[`, `L`, or `T`",
335                ));
336            }
337        }
338    }
339}
340
341fn consume_class_type_signature(chars: &mut impl Iterator<Item = Character>) -> Result<ClassType> {
342    let mut r = consume_unqualified_identifer(chars)?;
343    let start_pos = r.0.start();
344    loop {
345        match r.1 {
346            Some((end_pos, ';')) => {
347                return Ok(ClassType(
348                    SimpleClassType((start_pos..end_pos).into(), Vec::new()),
349                    Vec::new(),
350                ));
351            }
352            Some((_, '/')) => {
353                // next there must be an unqualified identifier; repeat
354            }
355            Some((end_pos, '.')) => {
356                return Ok(ClassType(
357                    SimpleClassType((start_pos..end_pos).into(), Vec::new()),
358                    consume_class_type_signature_suffixes(chars)?,
359                ));
360            }
361            Some((end_pos, '<')) => {
362                let class_type =
363                    SimpleClassType((start_pos..end_pos).into(), consume_type_arguments(chars)?);
364                return match consume_next("ClassTypeSignature", chars)? {
365                    (_, '.') => Ok(ClassType(
366                        class_type,
367                        consume_class_type_signature_suffixes(chars)?,
368                    )),
369                    (_, ';') => Ok(ClassType(class_type, Vec::new())),
370                    (pos, _) => Err(ParseError::new(
371                        "ClassTypeSignature",
372                        pos,
373                        "bad input; expected `.` or `;`",
374                    )),
375                };
376            }
377            Some((pos, _)) => {
378                return Err(ParseError::new(
379                    "ClassTypeSignature",
380                    pos,
381                    "bad input; expected `;`, `/`, `.`, or `<`",
382                ))
383            }
384            None => return Err(ParseError::eof("ClassTypeSignature")),
385        }
386        r = consume_unqualified_identifer(chars)?;
387    }
388}
389
390fn consume_class_type_signature_suffixes(
391    chars: &mut impl Iterator<Item = Character>,
392) -> Result<Vec<SimpleClassType>> {
393    let mut suffixes = Vec::new();
394    loop {
395        let r = consume_unqualified_identifer(chars)?;
396        match r.1 {
397            Some((_, ';')) => {
398                suffixes.push(SimpleClassType(r.0, Vec::new()));
399                return Ok(suffixes);
400            }
401            Some((_, '.')) => {
402                suffixes.push(SimpleClassType(r.0, Vec::new()));
403                // next there must be another suffix; repeat
404            }
405            Some((_, '<')) => {
406                let args = consume_type_arguments(chars)?;
407                match consume_next("ClassTypeSignatureSuffix", chars)? {
408                    (_, '.') => {
409                        suffixes.push(SimpleClassType(r.0, args));
410                        // must be followed by another suffix; repeat
411                    }
412                    (_, ';') => {
413                        suffixes.push(SimpleClassType(r.0, args));
414                        return Ok(suffixes);
415                    }
416                    (pos, _) => {
417                        return Err(ParseError::new(
418                            "ClassTypeSignature",
419                            pos,
420                            "bad input; expected `.` or end of input",
421                        ));
422                    }
423                }
424            }
425            Some((pos, _)) => {
426                return Err(ParseError::new(
427                    "ClassTypeSignatureSuffix",
428                    pos,
429                    "bad input; expected `;`, `.`, or `<`>",
430                ))
431            }
432            None => return Err(ParseError::eof("ClassTypeSignatureSuffix")),
433        }
434    }
435}
436
437// Assumes a leading '<' was already consumed; parses `chars` until a
438// matching '>' (while consuming it) as `{ TypeArgument }`.
439fn consume_type_arguments(
440    chars: &mut impl Iterator<Item = Character>,
441) -> Result<Vec<TypeArgument>> {
442    let mut args = Vec::new();
443    loop {
444        match consume_next("TypeArgument", chars)? {
445            (pos, '>') => {
446                if args.is_empty() {
447                    return Err(ParseError::new(
448                        "TypeArguments",
449                        pos,
450                        "type-argument missing",
451                    ));
452                } else {
453                    return Ok(args);
454                }
455            }
456            (_, '*') => {
457                args.push(TypeArgument::Unbounded);
458            }
459            (_, '+') => {
460                args.push(TypeArgument::Extends(consume_reference_type_signature(
461                    chars,
462                )?));
463            }
464            (_, '-') => {
465                args.push(TypeArgument::Super(consume_reference_type_signature(
466                    chars,
467                )?));
468            }
469            c => {
470                args.push(TypeArgument::Default(consume_reference_type_signature_(
471                    c, chars,
472                )?));
473            }
474        }
475    }
476}
477
478/// Consumes an unqualified idenfier returning the slice representing
479/// it and the next character (if there was one) which is _not_
480/// considered part of that identifier any more.
481fn consume_unqualified_identifer(
482    chars: &mut impl Iterator<Item = Character>,
483) -> Result<(Slice, Option<Character>)> {
484    consume_unqualified_identifer_(consume_next("Identifier", chars)?, chars)
485}
486
487fn consume_unqualified_identifer_(
488    mut c: Character,
489    more_chars: &mut impl Iterator<Item = Character>,
490) -> Result<(Slice, Option<Character>)> {
491    let start_pos = c.0;
492    let mut len = 0;
493    loop {
494        if matches!(c.1, '.' | ';' | '[' | '/' | '<' | '>' | ':') {
495            if len == 0 {
496                return Err(ParseError::new("Identifier", c.0, "empty identifier"));
497            } else {
498                return Ok(((start_pos..c.0).into(), Some(c)));
499            }
500        } else {
501            // XXX validate the accepted characters (e.g. rejecting whitespace, non-printable, etc)
502            // see also https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.8
503        }
504        len += 1;
505        match more_chars.next() {
506            None => return Ok(((start_pos..start_pos + len).into(), None)),
507            Some(next) => c = next,
508        }
509    }
510}
511
512pub fn consume_class_signature(
513    chars: &mut impl Iterator<Item = Character>,
514) -> Result<ClassSignature> {
515    let (type_params, next) = consume_type_parameters(chars)?;
516    consume_next_expected("ClassSignature", next, 'L')?;
517    let super_class = consume_class_type_signature(chars)?;
518    let mut super_ifaces = Vec::new();
519    loop {
520        match consume_next_eof_or_expected("ClassSignature", chars, 'L')? {
521            None => {
522                return Ok(ClassSignature {
523                    type_params,
524                    super_class,
525                    super_ifaces,
526                });
527            }
528            Some(_) => {
529                super_ifaces.push(consume_class_type_signature(chars)?);
530                // ~ and repeat
531            }
532        }
533    }
534}
535
536pub fn consume_method_signature(
537    chars: &mut impl Iterator<Item = Character>,
538) -> Result<MethodSignature> {
539    let (type_params, next) = consume_type_parameters(chars)?;
540    consume_next_expected("MethodSignature", next, '(')?;
541    let (parameters, next) = consume_method_parameters(chars)?;
542    let result = match next {
543        None => return Err(ParseError::eof("MethodSignature")),
544        Some((_, 'V')) => ResultType::VoidType,
545        Some(c) => ResultType::ValueType(consume_java_type_signature(c, chars)?),
546    };
547    let throws = consume_throws_signature(chars)?;
548    Ok(MethodSignature {
549        type_params,
550        parameters,
551        result,
552        throws,
553    })
554}
555
556fn consume_method_parameters(
557    chars: &mut impl Iterator<Item = Character>,
558) -> Result<(Vec<JavaType>, Option<Character>)> {
559    let mut params = Vec::new();
560    loop {
561        match consume_next("MethodParameter", chars)? {
562            (_, ')') => return Ok((params, chars.next())),
563            c => params.push(consume_java_type_signature(c, chars)?),
564        };
565    }
566}
567
568fn consume_throws_signature(
569    chars: &mut impl Iterator<Item = Character>,
570) -> Result<Vec<ThrowsType>> {
571    let mut throws = Vec::new();
572    loop {
573        match chars.next() {
574            None => return Ok(throws),
575            Some((_, '^')) => match consume_next("ThrowsSignature", chars)? {
576                (_, 'T') => {
577                    throws.push(consume_unqualified_identifer(chars).and_then(
578                        |(identifier, next)| {
579                            consume_next_expected("ThrowsSignature", next, ';')?;
580                            Ok(ThrowsType::TypeVariable(identifier))
581                        },
582                    )?);
583                }
584                (_, 'L') => {
585                    throws.push(consume_class_type_signature(chars).map(ThrowsType::ClassType)?);
586                }
587                (pos, _) => {
588                    return Err(ParseError::new(
589                        "ThrowsSignature",
590                        pos,
591                        "bad input; expected `T` or `L`",
592                    ))
593                }
594            },
595            Some((pos, _)) => {
596                return Err(ParseError::new(
597                    "ThrowsSignature",
598                    pos,
599                    "bad input; expected `^` or end of input",
600                ))
601            }
602        }
603    }
604}
605
606fn consume_java_type_signature(
607    c: Character,
608    more_chars: &mut impl Iterator<Item = Character>,
609) -> Result<JavaType> {
610    if let Some(ty) = to_base_type(c.1) {
611        Ok(JavaType::Base(ty))
612    } else {
613        consume_reference_type_signature_(c, more_chars).map(JavaType::Reference)
614    }
615}
616
617// Optionally matches: `'<' { Identifier ':' [ReferenceTypeSignature] { ':' ReferenceTypeSignature } }+ '>'`
618// Returns the next character not part of `TypeParameters` anymore
619fn consume_type_parameters(
620    chars: &mut impl Iterator<Item = Character>,
621) -> Result<(Vec<TypeParameter>, Option<Character>)> {
622    fn consume_more_reference_type_signatures(
623        mut c: Character,
624        more_chars: &mut impl Iterator<Item = Character>,
625        accumulator: &mut Vec<ReferenceType>,
626    ) -> Result<Option<Character>> {
627        loop {
628            match c {
629                (_, ':') => {
630                    accumulator.push(consume_reference_type_signature(more_chars)?);
631                    c = consume_next("TypeParameter", more_chars)?;
632                    // ~ and repeat
633                }
634                c => {
635                    return Ok(Some(c));
636                }
637            }
638        }
639    }
640
641    let mut params = Vec::new();
642    match chars.next() {
643        Some((_, '<')) => {
644            let mut next = consume_unqualified_identifer(chars)?;
645            loop {
646                match next.1 {
647                    Some((_, ':')) => {
648                        // ~ the occurance (of a reference-type-signature) after the first colon is optional
649                        match consume_next("TypeParameter", chars)? {
650                            (_, '>') => {
651                                params.push(TypeParameter {
652                                    name: next.0,
653                                    class_bound: None,
654                                    iface_bounds: Vec::new(),
655                                });
656                                return Ok((params, chars.next()));
657                            }
658                            c @ (_, ':') => {
659                                let mut iface_bounds = Vec::new();
660                                match consume_more_reference_type_signatures(
661                                    c,
662                                    chars,
663                                    &mut iface_bounds,
664                                )? {
665                                    None => return Err(ParseError::eof("TypeParameter")),
666                                    Some((_, '>')) => {
667                                        params.push(TypeParameter {
668                                            name: next.0,
669                                            class_bound: None,
670                                            iface_bounds,
671                                        });
672                                        return Ok((params, chars.next()));
673                                    }
674                                    Some(c) => {
675                                        params.push(TypeParameter {
676                                            name: next.0,
677                                            class_bound: None,
678                                            iface_bounds,
679                                        });
680                                        next = consume_unqualified_identifer_(c, chars)?;
681                                        // ~ and repeat
682                                    }
683                                }
684                            }
685                            c => {
686                                let class_bound = consume_reference_type_signature_(c, chars)?;
687                                let mut iface_bounds = Vec::new();
688                                match consume_more_reference_type_signatures(
689                                    consume_next("TypeParameter", chars)?,
690                                    chars,
691                                    &mut iface_bounds,
692                                )? {
693                                    None => return Err(ParseError::eof("TypeParameter")),
694                                    Some(c) => {
695                                        params.push(TypeParameter {
696                                            name: next.0,
697                                            class_bound: Some(class_bound),
698                                            iface_bounds,
699                                        });
700                                        if c.1 == '>' {
701                                            return Ok((params, chars.next()));
702                                        } else {
703                                            next = consume_unqualified_identifer_(c, chars)?;
704                                            // ~ and repeat
705                                        }
706                                    }
707                                }
708                            }
709                        }
710                    }
711                    Some((pos, _)) => {
712                        return Err(ParseError::new(
713                            "TypeParameter",
714                            pos,
715                            "bad input; expected `:`",
716                        ))
717                    }
718                    None => return Err(ParseError::eof("TypeParameter")),
719                }
720            }
721        }
722        c => Ok((params, c)),
723    }
724}
725
726#[cfg(test)]
727mod tests {
728    use super::*;
729
730    #[test]
731    fn test_consume_method_signature() {
732        let s = "([Ljava/lang/String;Ljava/util/Hashtable<**>;)Ljava/lang/Object;";
733        let mut chars = &mut s.char_indices();
734        let r = consume_method_signature(&mut chars);
735        assert_eq!(None, chars.next());
736        let r = r.expect("signature not parsed successfully");
737        assert!(r.type_params.is_empty());
738        assert_eq!(2, r.parameters.len());
739        assert!(matches!(
740            r.parameters[0],
741            JavaType::Reference(ReferenceType::Array {
742                dimension: 1,
743                ty: _
744            })
745        ));
746    }
747
748    #[test]
749    fn test_consume_type_parameters() {
750        for s in &[
751            "<T::Ljava/io/Serializable;>",
752            "<T::Ljava/io/Serializable;:Ljava/lang/Comparable<TT;>;>",
753            "<K:Ljava/lang/Object;V:Ljava/lang/Object;>",
754            "<OP::Ljdk/incubator/vector/VectorOperators$Operator;T:Ljava/lang/Object;>",
755        ] {
756            let mut chars = &mut s.char_indices();
757            assert_eq!(
758                Ok(None),
759                consume_type_parameters(&mut chars).map(|(_, next)| next),
760                "failed to recognize: `{}`",
761                s
762            );
763        }
764    }
765
766    #[test]
767    fn test_structure_consume_type_parameters() {
768        for s in &[
769            "<Entity::Lfoo/bar/core/model/Identifiable<TId;>;Id::Ljava/io/Serializable;>",
770            "<K:Ljava/lang/Object;V:Ljava/lang/Object;>",
771        ] {
772            let (params, next) =
773                consume_type_parameters(&mut s.char_indices()).expect("invalid signature");
774            assert_eq!(None, next, "failed on signature: {}", s);
775            assert_eq!(2, params.len(), "on signature: {}", s);
776        }
777    }
778
779    #[test]
780    fn test_consume_unqualified_identifer() {
781        let chars = &mut ".".char_indices();
782        assert!(consume_unqualified_identifer(chars).is_err());
783
784        let chars = &mut "foobar".char_indices();
785        assert_eq!(
786            Ok(((0..6).into(), None)),
787            consume_unqualified_identifer(chars)
788        );
789
790        let chars = &mut "foo/bar".char_indices();
791        assert_eq!(
792            Ok(((0..3).into(), Some((3, '/')))),
793            consume_unqualified_identifer(chars)
794        );
795        assert_eq!(Some((4, 'b')), chars.next());
796    }
797}