Skip to main content

cadmpeg_codec_step/
lex.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Byte-oriented ISO 10303-21 lexical analysis.
3
4use std::ops::Range;
5
6use cadmpeg_core::decode::DecodeContext;
7use cadmpeg_core::CodecError;
8use cadmpeg_ir::scalar::FiniteReal;
9
10/// The entity or value occurrence class.
11#[derive(Debug, Clone, Copy)]
12enum OccurrencePrefix {
13    Entity,
14    Value,
15}
16
17/// A lexical token with its exact source-byte extent.
18#[derive(Debug, Clone, PartialEq)]
19pub(crate) struct Token {
20    /// Parsed token category.
21    pub(crate) kind: TokenKind,
22    /// Half-open byte range in the exchange structure.
23    pub(crate) span: Range<usize>,
24}
25
26/// Part 21 token categories.
27#[derive(Debug, Clone, PartialEq)]
28pub(crate) enum TokenKind {
29    /// A lexical defect deferred to its containing statement's recovery.
30    InvalidSource(String),
31    /// Standard keyword or entity name.
32    Name(String),
33    /// User-defined `!`-prefixed keyword.
34    UserName(String),
35    /// Numeric `#`-prefixed entity-instance name.
36    Instance(u64),
37    /// Numeric `@`-prefixed value-instance name.
38    ValueInstance(u64),
39    /// `#`-prefixed EXPRESS entity constant name.
40    ConstantEntity(String),
41    /// `@`-prefixed EXPRESS value constant name.
42    ConstantValue(String),
43    /// Signed decimal integer.
44    Integer(i64),
45    /// Decimal real, including an optional exponent.
46    Real(FiniteReal),
47    /// A bounded metadata literal that cannot enter its typed domain.
48    UninterpretedLiteral,
49    /// Dot-delimited enumeration or logical literal.
50    Enumeration(String),
51    /// Bytes between apostrophe delimiters, before escape decoding.
52    String(Vec<u8>),
53    /// Decoded quoted hexadecimal binary literal.
54    Binary(BinaryValue),
55    /// Edition-3 resource token.
56    Resource(String),
57    /// Opening parenthesis.
58    LParen,
59    /// Closing parenthesis.
60    RParen,
61    /// Parameter separator.
62    Comma,
63    /// Statement terminator.
64    Semicolon,
65    /// Assignment operator.
66    Equals,
67    /// Omitted-value marker `$`.
68    Omitted,
69    /// Derived-value marker `*`.
70    Derived,
71    /// Anchor-tag name, preserving source case.
72    TagName(String),
73    /// Opening anchor-tag delimiter.
74    LBrace,
75    /// Closing anchor-tag delimiter.
76    RBrace,
77    /// Anchor-tag name/value separator.
78    Colon,
79}
80
81/// Binary literal payload packed most-significant nibble first.
82#[derive(Debug, Clone, PartialEq, Eq)]
83pub(crate) struct BinaryValue {
84    /// Unused low-order bits in the final byte, including nibble padding.
85    unused_bits: u8,
86    data: Box<[u8]>,
87}
88
89impl BinaryValue {
90    pub(crate) fn bit_len(&self) -> usize {
91        self.data.len() * 8 - usize::from(self.unused_bits)
92    }
93
94    pub(crate) fn data(&self) -> &[u8] {
95        &self.data
96    }
97}
98
99/// Lexical failure with a stable byte position.
100#[derive(Debug, thiserror::Error)]
101#[error("{message} at byte {offset}")]
102pub(crate) struct LexError {
103    /// Byte offset at which tokenization failed.
104    offset: usize,
105    /// Violated lexical invariant.
106    pub(crate) message: String,
107    resource: Option<CodecError>,
108}
109
110impl LexError {
111    pub(crate) fn offset(&self) -> usize {
112        self.offset
113    }
114    pub(crate) fn into_codec_error(self) -> CodecError {
115        match self.resource {
116            Some(error) => error,
117            None => CodecError::malformed(format_args!("{} at byte {}", self.message, self.offset)),
118        }
119    }
120
121    pub(crate) fn into_resource_error(self) -> Option<CodecError> {
122        self.resource
123    }
124}
125
126#[derive(Clone, Copy)]
127pub(crate) enum LiteralAdmission {
128    Required,
129    Metadata,
130}
131
132#[derive(Clone, Copy)]
133enum ExchangeSyntax {
134    Standard,
135    Draft,
136}
137
138pub(crate) struct Lexer<'a, 'ctx, 'arena> {
139    input: &'a [u8],
140    budget: &'ctx DecodeContext<'arena>,
141    literal_admission: LiteralAdmission,
142    at: usize,
143    allow_print_controls: bool,
144    previous_was_signature: bool,
145    tag_name_expected: bool,
146    syntax: ExchangeSyntax,
147}
148
149pub(crate) fn print_control_end(input: &[u8], at: usize) -> Option<usize> {
150    match_exact_ignoring_controls(input, at, b"\\N\\")
151        .or_else(|| match_exact_ignoring_controls(input, at, b"\\F\\"))
152}
153
154impl<'a, 'ctx, 'arena> Lexer<'a, 'ctx, 'arena> {
155    pub(crate) fn new(input: &'a [u8], ctx: &'ctx DecodeContext<'arena>) -> Self {
156        let draft_offset = crate::codec::draft_exchange_offset(input);
157        Self {
158            input,
159            budget: ctx,
160            literal_admission: LiteralAdmission::Required,
161            at: draft_offset.unwrap_or(0),
162            allow_print_controls: true,
163            previous_was_signature: false,
164            tag_name_expected: false,
165            syntax: if draft_offset.is_some() {
166                ExchangeSyntax::Draft
167            } else {
168                ExchangeSyntax::Standard
169            },
170        }
171    }
172
173    pub(crate) fn set_literal_admission(&mut self, admission: LiteralAdmission) {
174        self.literal_admission = admission;
175    }
176
177    pub(crate) fn allows_uninterpreted_literals(&self) -> bool {
178        matches!(self.literal_admission, LiteralAdmission::Metadata)
179    }
180
181    pub(crate) fn set_allow_print_controls(&mut self, allow: bool) {
182        self.allow_print_controls = allow;
183    }
184
185    pub(crate) fn input(&self) -> &[u8] {
186        self.input
187    }
188
189    pub(crate) fn is_draft(&self) -> bool {
190        matches!(self.syntax, ExchangeSyntax::Draft)
191    }
192
193    pub(crate) fn seek(&mut self, at: usize) {
194        self.at = at;
195        self.tag_name_expected = false;
196        self.previous_was_signature = false;
197    }
198
199    pub(crate) fn next_token(&mut self) -> Result<Option<Token>, LexError> {
200        if !self.skip_trivia()? {
201            return Ok(None);
202        }
203        let token = self.token()?;
204        let skip_signature =
205            self.previous_was_signature && matches!(&token.kind, TokenKind::Semicolon);
206        self.previous_was_signature =
207            matches!(&token.kind, TokenKind::Name(name) if name == "SIGNATURE");
208        if skip_signature {
209            self.skip_signature_payload()?;
210        }
211        Ok(Some(token))
212    }
213
214    fn skip_signature_payload(&mut self) -> Result<(), LexError> {
215        let start = self.at;
216        let name_len = b"ENDSEC".len();
217        let mut at = start;
218        let mut boundary_allowed = true;
219        while at + name_len <= self.input.len() {
220            if self.input.get(at..at + 2) == Some(b"/*") {
221                let comment_start = at;
222                at += 2;
223                let Some(end) = self.input[at..].windows(2).position(|w| w == b"*/") else {
224                    return Err(Self::error(comment_start, "unterminated comment"));
225                };
226                at += end + 2;
227                boundary_allowed = true;
228                continue;
229            }
230            if let Some(end) = self.print_control_end(at) {
231                at = end;
232                boundary_allowed = true;
233                continue;
234            }
235            if self
236                .input
237                .get(at)
238                .is_some_and(|byte| byte.is_ascii_control() || *byte == b' ')
239            {
240                at += 1;
241                boundary_allowed = true;
242                continue;
243            }
244            let candidate = at;
245            let Some(mut after_name) = match_ignoring_controls(self.input, candidate, b"ENDSEC")
246            else {
247                at += 1;
248                boundary_allowed = false;
249                continue;
250            };
251            while self
252                .input
253                .get(after_name)
254                .is_some_and(|byte| byte.is_ascii_control() || *byte == b' ')
255            {
256                after_name += 1;
257            }
258            if boundary_allowed && self.input.get(after_name) == Some(&b';') {
259                self.validate_signature_payload(start, candidate)?;
260                self.at = candidate;
261                return Ok(());
262            }
263            at += 1;
264            boundary_allowed = false;
265        }
266        Err(Self::error(start, "unterminated signature section"))
267    }
268
269    fn validate_signature_payload(&self, start: usize, end: usize) -> Result<(), LexError> {
270        let mut quantum_len = 0;
271        let mut padding = 0;
272        let mut finished = false;
273        let mut saw_content = false;
274        let mut trailing_separator = false;
275        let mut relative = 0;
276        while relative < end - start {
277            let byte = self.input[start + relative];
278            if byte.is_ascii_control() {
279                relative += 1;
280                continue;
281            }
282            if byte == b' ' {
283                trailing_separator |= saw_content;
284                relative += 1;
285                continue;
286            }
287            if let Some(separator_end) = self.print_control_end(start + relative) {
288                if separator_end > end {
289                    return Err(Self::error(
290                        start + relative,
291                        "invalid SIGNATURE base64 character",
292                    ));
293                }
294                trailing_separator |= saw_content;
295                relative = separator_end - start;
296                continue;
297            }
298            if self.input.get(start + relative..start + relative + 2) == Some(b"/*") {
299                let comment_start = start + relative;
300                let comment_body = comment_start + 2;
301                let Some(comment_end) = self.input[comment_body..end]
302                    .windows(2)
303                    .position(|window| window == b"*/")
304                else {
305                    return Err(Self::error(comment_start, "unterminated comment"));
306                };
307                trailing_separator |= saw_content;
308                relative = comment_body + comment_end + 2 - start;
309                continue;
310            }
311            if trailing_separator {
312                return Err(Self::error(
313                    start + relative,
314                    "invalid SIGNATURE base64 character",
315                ));
316            }
317            saw_content = true;
318            let is_alphabet = byte.is_ascii_alphanumeric() || matches!(byte, b'+' | b'/');
319            if finished || (padding != 0 && is_alphabet) {
320                return Err(Self::error(
321                    start + relative,
322                    "invalid SIGNATURE base64 padding",
323                ));
324            }
325            if is_alphabet {
326                quantum_len += 1;
327            } else if byte == b'=' {
328                if quantum_len < 2 || padding == 2 {
329                    return Err(Self::error(
330                        start + relative,
331                        "invalid SIGNATURE base64 padding",
332                    ));
333                }
334                padding += 1;
335                quantum_len += 1;
336            } else {
337                return Err(Self::error(
338                    start + relative,
339                    "invalid SIGNATURE base64 character",
340                ));
341            }
342            if quantum_len == 4 {
343                finished = padding != 0;
344                quantum_len = 0;
345                padding = 0;
346            }
347            relative += 1;
348        }
349        if !saw_content {
350            return Err(Self::error(
351                start,
352                "SIGNATURE section has empty base64 content",
353            ));
354        }
355        if quantum_len != 0 {
356            return Err(Self::error(
357                end,
358                "SIGNATURE base64 content has incomplete quantum",
359            ));
360        }
361        Ok(())
362    }
363
364    fn skip_trivia(&mut self) -> Result<bool, LexError> {
365        loop {
366            while self.input.get(self.at).is_some_and(u8::is_ascii_control) {
367                self.at += 1;
368            }
369            if let Some(end) = self.print_control_end(self.at) {
370                if !self.allow_print_controls {
371                    return Err(Self::error(
372                        self.at,
373                        "print control directive is not allowed in this section",
374                    ));
375                }
376                self.at = end;
377                continue;
378            }
379            if self.input.get(self.at) == Some(&b' ') {
380                self.at += 1;
381                continue;
382            }
383            let close = match self.input.get(self.at..self.at + 2) {
384                Some(b"/*") => b"*/",
385                Some(b"!*") if self.is_draft() => b"*!",
386                _ => return Ok(self.at < self.input.len()),
387            };
388            let start = self.at;
389            self.at += 2;
390            let Some(end) = self.input[self.at..].windows(2).position(|w| w == close) else {
391                return Err(Self::error(start, "unterminated comment"));
392            };
393            self.at += end + 2;
394        }
395    }
396
397    fn token(&mut self) -> Result<Token, LexError> {
398        let start = self.at;
399        if self.tag_name_expected {
400            self.tag_name_expected = false;
401            let kind = self.tag_name()?;
402            return Ok(Token {
403                kind,
404                span: start..self.at,
405            });
406        }
407        let byte = self.input[self.at];
408        let kind = match byte {
409            b'(' => self.one(TokenKind::LParen),
410            b')' => self.one(TokenKind::RParen),
411            b',' => self.one(TokenKind::Comma),
412            b';' => self.one(TokenKind::Semicolon),
413            b'=' => self.one(TokenKind::Equals),
414            b'{' => self.one(TokenKind::LBrace),
415            b'}' => self.one(TokenKind::RBrace),
416            b':' => self.one(TokenKind::Colon),
417            b'$' => self.one(TokenKind::Omitted),
418            b'*' => self.one(TokenKind::Derived),
419            b'#' => self.occurrence(OccurrencePrefix::Entity)?,
420            b'@' => self.occurrence(OccurrencePrefix::Value)?,
421            b'\'' => self.string()?,
422            b'"' => self.binary()?,
423            b'<' => self.resource()?,
424            b'.' if self
425                .next_non_ignored(self.at + 1)
426                .is_some_and(|byte| byte.is_ascii_alphabetic() || byte == b'_') =>
427            {
428                self.enumeration()?
429            }
430            b'!' => self.user_name()?,
431            b'+' | b'-' | b'0'..=b'9' | b'.' => self.number()?,
432            b if b.is_ascii_alphabetic() || b == b'_' => TokenKind::Name(self.name()),
433            _ => return Err(Self::error(start, "unexpected byte")),
434        };
435        Ok(Token {
436            kind,
437            span: start..self.at,
438        })
439    }
440
441    fn one(&mut self, kind: TokenKind) -> TokenKind {
442        self.at += 1;
443        if matches!(kind, TokenKind::LBrace) {
444            self.tag_name_expected = true;
445        }
446        kind
447    }
448
449    fn name(&mut self) -> String {
450        let start = self.at;
451        self.at += 1;
452        while self.input.get(self.at).is_some_and(|b| {
453            b.is_ascii_alphanumeric() || *b == b'_' || *b == b'-' || b.is_ascii_control()
454        }) {
455            self.at += 1;
456        }
457        let mut name = self.normalized(start, self.at);
458        name.make_ascii_uppercase();
459        name
460    }
461
462    fn tag_name(&mut self) -> Result<TokenKind, LexError> {
463        let start = self.at;
464        if !self
465            .input
466            .get(self.at)
467            .is_some_and(|byte| byte.is_ascii_alphabetic() || *byte == b'_')
468        {
469            return Err(Self::error(start, "tag name has no identifier"));
470        }
471        self.at += 1;
472        while self.input.get(self.at).is_some_and(|byte| {
473            byte.is_ascii_alphanumeric() || *byte == b'_' || byte.is_ascii_control()
474        }) {
475            self.at += 1;
476        }
477        let name = self.normalized(start, self.at);
478        Ok(TokenKind::TagName(name))
479    }
480
481    fn user_name(&mut self) -> Result<TokenKind, LexError> {
482        let start = self.at;
483        self.at += 1;
484        self.skip_ignored();
485        if !self
486            .input
487            .get(self.at)
488            .is_some_and(|byte| byte.is_ascii_alphabetic() || *byte == b'_')
489        {
490            return Err(Self::error(start, "user-defined name has no identifier"));
491        }
492        Ok(TokenKind::UserName(self.name()))
493    }
494
495    fn occurrence(&mut self, prefix: OccurrencePrefix) -> Result<TokenKind, LexError> {
496        let start = self.at;
497        self.at += 1;
498        self.skip_ignored();
499        match self.input.get(self.at).copied() {
500            Some(byte) if byte.is_ascii_digit() => {
501                let digits = self.at;
502                while let Some(byte) = self.input.get(self.at).copied() {
503                    if byte.is_ascii_digit() || byte.is_ascii_control() {
504                        self.at += 1;
505                    } else {
506                        break;
507                    }
508                }
509                let raw = self.normalized(digits, self.at);
510                let value = raw
511                    .parse::<u64>()
512                    .ok()
513                    .ok_or_else(|| Self::error(start, "instance name is out of range"))?;
514                if value == 0 {
515                    return Err(Self::error(start, "instance name must not be zero"));
516                }
517                match prefix {
518                    OccurrencePrefix::Entity => Ok(TokenKind::Instance(value)),
519                    OccurrencePrefix::Value => Ok(TokenKind::ValueInstance(value)),
520                }
521            }
522            Some(byte) if byte.is_ascii_alphabetic() || byte == b'_' => {
523                let name_start = self.at;
524                self.at += 1;
525                while let Some(byte) = self.input.get(self.at).copied() {
526                    if byte.is_ascii_alphanumeric() || byte == b'_' || byte.is_ascii_control() {
527                        self.at += 1;
528                    } else {
529                        break;
530                    }
531                }
532                let mut name = self.normalized(name_start, self.at);
533                name.make_ascii_uppercase();
534                match prefix {
535                    OccurrencePrefix::Entity => Ok(TokenKind::ConstantEntity(name)),
536                    OccurrencePrefix::Value => Ok(TokenKind::ConstantValue(name)),
537                }
538            }
539            _ => Err(Self::error(start, "occurrence name has no identifier")),
540        }
541    }
542
543    fn number(&mut self) -> Result<TokenKind, LexError> {
544        let start = self.at;
545        if matches!(self.input[self.at], b'+' | b'-') {
546            self.at += 1;
547            self.skip_ignored();
548        }
549        let mut dot = false;
550        let mut exponent = false;
551        while let Some(&b) = self.input.get(self.at) {
552            match b {
553                byte if byte.is_ascii_control() => self.at += 1,
554                b'0'..=b'9' => self.at += 1,
555                b'.' if !dot => {
556                    dot = true;
557                    self.at += 1;
558                }
559                b'E' | b'e' | b'D' | b'd' if !exponent => {
560                    exponent = true;
561                    self.at += 1;
562                    self.skip_ignored();
563                    if self
564                        .input
565                        .get(self.at)
566                        .is_some_and(|b| matches!(b, b'+' | b'-'))
567                    {
568                        self.at += 1;
569                        self.skip_ignored();
570                    }
571                }
572                _ => break,
573            }
574        }
575        let mut raw = self.normalized(start, self.at);
576        if exponent && raw.ends_with('.') {
577            raw.pop();
578        }
579        let admitted = if dot || exponent {
580            raw.make_ascii_uppercase();
581            let mut index = 0;
582            while index < raw.len() {
583                if raw.as_bytes()[index] == b'D' {
584                    raw.replace_range(index..=index, "E");
585                }
586                index += 1;
587            }
588            raw.parse::<f64>()
589                .map_err(|_| Self::error(start, "invalid real"))
590                .and_then(|value| {
591                    FiniteReal::new(value)
592                        .map(TokenKind::Real)
593                        .ok_or_else(|| Self::error(start, "real exceeds finite binary64 range"))
594                })
595        } else {
596            raw.parse()
597                .map(TokenKind::Integer)
598                .map_err(|_| Self::error(start, "invalid integer"))
599        };
600        match (self.literal_admission, admitted) {
601            (LiteralAdmission::Metadata, Err(_)) => Ok(TokenKind::UninterpretedLiteral),
602            (_, result) => result,
603        }
604    }
605
606    fn enumeration(&mut self) -> Result<TokenKind, LexError> {
607        let start = self.at;
608        self.at += 1;
609        self.skip_ignored();
610        let name_start = self.at;
611        while self
612            .input
613            .get(self.at)
614            .is_some_and(|b| b.is_ascii_alphanumeric() || *b == b'_' || b.is_ascii_control())
615        {
616            self.at += 1;
617        }
618        self.skip_ignored();
619        if self.input.get(self.at) != Some(&b'.') {
620            return Err(Self::error(start, "unterminated enumeration"));
621        }
622        let mut name = self.normalized(name_start, self.at);
623        name.make_ascii_uppercase();
624        self.at += 1;
625        Ok(TokenKind::Enumeration(name))
626    }
627
628    fn string(&mut self) -> Result<TokenKind, LexError> {
629        let start = self.at;
630        self.at += 1;
631        let content = self.at;
632        loop {
633            match self.input.get(self.at).copied() {
634                Some(b'\'') => {
635                    if let Some(end) = self.match_exact_ignoring_controls(self.at, b"''") {
636                        self.at = end;
637                    } else {
638                        let mut bytes = self.input[content..self.at].to_vec();
639                        bytes.retain(|byte| !byte.is_ascii_control());
640                        self.at += 1;
641                        return Ok(TokenKind::String(bytes));
642                    }
643                }
644                Some(byte) if byte.is_ascii_control() => {
645                    self.at += 1;
646                }
647                Some(b'\\') => {
648                    if let Some(end) = self.print_control_end(self.at) {
649                        if !self.allow_print_controls {
650                            return Err(Self::error(
651                                self.at,
652                                "print control directive is not allowed in this section",
653                            ));
654                        }
655                        self.at = end;
656                    } else {
657                        self.at += 1;
658                    }
659                }
660                Some(_) => self.at += 1,
661                None => return Err(Self::error(start, "unterminated string")),
662            }
663        }
664    }
665
666    fn binary(&mut self) -> Result<TokenKind, LexError> {
667        let start = self.at;
668        self.binary_value()
669            .or_else(|error| self.recover_metadata_literal(start, b'"', error))
670    }
671
672    fn recover_metadata_literal(
673        &mut self,
674        start: usize,
675        closing: u8,
676        error: LexError,
677    ) -> Result<TokenKind, LexError> {
678        if error.resource.is_some() || !matches!(self.literal_admission, LiteralAdmission::Metadata)
679        {
680            return Err(error);
681        }
682        for end in start + 1..self.input.len() {
683            if self.input[end] == closing {
684                self.at = end + 1;
685                return Ok(TokenKind::UninterpretedLiteral);
686            }
687        }
688        Err(error)
689    }
690
691    fn binary_value(&mut self) -> Result<TokenKind, LexError> {
692        let start = self.at;
693        self.at += 1;
694        let content = self.at;
695        let mut digit_count = 0usize;
696        while let Some(byte) = self.input.get(self.at).copied() {
697            if HexDigit::new(byte).is_some() {
698                digit_count += 1;
699                self.at += 1;
700            } else if byte.is_ascii_control() {
701                self.at += 1;
702            } else if byte == b'\\' {
703                let Some(after_print_control) = self.print_control_end(self.at) else {
704                    break;
705                };
706                if !self.allow_print_controls {
707                    return Err(Self::error(
708                        self.at,
709                        "print control directive is not allowed in this section",
710                    ));
711                }
712                self.at = after_print_control;
713            } else {
714                break;
715            }
716        }
717        if self.input.get(self.at) != Some(&b'"') {
718            return Err(Self::error(start, "invalid binary literal"));
719        }
720        let mut raw = self
721            .budget
722            .alloc_filled(digit_count, HexDigit(0), "step_binary_hex_digits")
723            .map_err(|error| Self::resource_error(start, error))?;
724        let mut cursor = content;
725        let mut written = 0usize;
726        while cursor < self.at {
727            let byte = self.input[cursor];
728            if let Some(digit) = HexDigit::new(byte) {
729                raw[written] = digit;
730                written += 1;
731                cursor += 1;
732            } else if byte.is_ascii_control() {
733                cursor += 1;
734            } else if byte == b'\\' {
735                let Some(end) = self.print_control_end(cursor) else {
736                    return Err(Self::error(cursor, "invalid binary literal"));
737                };
738                cursor = end;
739            } else {
740                return Err(Self::error(cursor, "invalid binary literal"));
741            }
742        }
743        let Some((&indicator, digits)) = raw.split_first() else {
744            return Err(Self::error(
745                start,
746                "binary literal has no unused-bit indicator",
747            ));
748        };
749        let unused_bits = indicator.nibble();
750        if unused_bits > 3 {
751            return Err(Self::error(
752                start,
753                "binary unused-bit indicator exceeds three",
754            ));
755        }
756        if digits.is_empty() && unused_bits != 0 {
757            return Err(Self::error(start, "empty binary payload has unused bits"));
758        }
759        if unused_bits != 0
760            && digits
761                .last()
762                .is_some_and(|digit| digit.nibble() & ((1 << unused_bits) - 1) != 0)
763        {
764            return Err(Self::error(start, "unused binary bits are not zero"));
765        }
766        let packed_len = digits.len().div_ceil(2);
767        let mut data = self
768            .budget
769            .alloc_filled(packed_len, 0_u8, "step_binary_packed_bytes")
770            .map_err(|error| Self::resource_error(start, error))?;
771        let mut output = 0usize;
772        let mut pairs = digits.chunks_exact(2);
773        for pair in &mut pairs {
774            data[output] = (pair[0].nibble() << 4) | pair[1].nibble();
775            output += 1;
776        }
777        if let [last] = pairs.remainder() {
778            data[output] = last.nibble() << 4;
779        }
780        let unused_bits = unused_bits + if digits.len() % 2 == 1 { 4 } else { 0 };
781        self.at += 1;
782        Ok(TokenKind::Binary(BinaryValue {
783            unused_bits,
784            data: data.into_boxed_slice(),
785        }))
786    }
787
788    fn resource(&mut self) -> Result<TokenKind, LexError> {
789        let start = self.at;
790        self.resource_value()
791            .or_else(|error| self.recover_metadata_literal(start, b'>', error))
792    }
793
794    fn resource_value(&mut self) -> Result<TokenKind, LexError> {
795        let start = self.at;
796        self.at += 1;
797        let content = self.at;
798        let mut value_len = 0usize;
799        while let Some(byte) = self.input.get(self.at).copied() {
800            if byte == b'>' {
801                break;
802            }
803            if self.print_control_end(self.at).is_some() {
804                return Err(Self::error(
805                    self.at,
806                    "print control directive is not allowed in a resource",
807                ));
808            }
809            if !byte.is_ascii_control() {
810                value_len += 1;
811            }
812            self.at += 1;
813        }
814        if self.input.get(self.at) != Some(&b'>') {
815            return Err(Self::error(start, "unterminated resource token"));
816        }
817        let mut value = self
818            .budget
819            .alloc_filled(value_len, 0_u8, "step_uri_lexeme_bytes")
820            .map_err(|error| Self::resource_error(start, error))?;
821        let mut written = 0usize;
822        for &byte in &self.input[content..self.at] {
823            if !byte.is_ascii_control() {
824                value[written] = byte;
825                written += 1;
826            }
827        }
828        let value = String::from_utf8(value)
829            .map_err(|_| Self::error(content, "resource token is not UTF-8"))?;
830        self.at += 1;
831        Ok(TokenKind::Resource(value))
832    }
833
834    fn skip_ignored(&mut self) {
835        while self.input.get(self.at).is_some_and(u8::is_ascii_control) {
836            self.at += 1;
837        }
838    }
839
840    fn next_non_ignored(&self, mut at: usize) -> Option<u8> {
841        while self.input.get(at).is_some_and(u8::is_ascii_control) {
842            at += 1;
843        }
844        self.input.get(at).copied()
845    }
846
847    fn normalized(&self, start: usize, end: usize) -> String {
848        self.input[start..end]
849            .iter()
850            .filter(|byte| !byte.is_ascii_control())
851            .map(|byte| char::from(*byte))
852            .collect()
853    }
854
855    fn print_control_end(&self, at: usize) -> Option<usize> {
856        print_control_end(self.input, at)
857    }
858
859    fn match_exact_ignoring_controls(&self, at: usize, expected: &[u8]) -> Option<usize> {
860        match_exact_ignoring_controls(self.input, at, expected)
861    }
862
863    fn error(offset: usize, message: &str) -> LexError {
864        LexError {
865            offset,
866            message: message.into(),
867            resource: None,
868        }
869    }
870
871    fn resource_error(offset: usize, error: CodecError) -> LexError {
872        LexError {
873            offset,
874            message: error.to_string(),
875            resource: Some(error),
876        }
877    }
878}
879
880/// An ASCII hexadecimal digit, carrying the four bits it names.
881///
882/// The type makes the nibble map total: a value of this type exists only
883/// because the byte it came from names a hexadecimal digit, so the map from a
884/// scanned binary literal to its nibbles has no unreachable arm.
885#[derive(Debug, Clone, Copy, PartialEq, Eq)]
886struct HexDigit(u8);
887
888impl HexDigit {
889    /// The digit an ASCII byte names, or `None` when the byte names none.
890    const fn new(byte: u8) -> Option<Self> {
891        Some(Self(match byte {
892            b'0'..=b'9' => byte - b'0',
893            b'a'..=b'f' => byte - b'a' + 10,
894            b'A'..=b'F' => byte - b'A' + 10,
895            _ => return None,
896        }))
897    }
898
899    /// The four bits the digit names, 0 through 15.
900    const fn nibble(self) -> u8 {
901        self.0
902    }
903}
904
905pub(crate) fn match_exact_ignoring_controls(
906    input: &[u8],
907    mut at: usize,
908    expected: &[u8],
909) -> Option<usize> {
910    for &byte in expected {
911        while input.get(at).is_some_and(u8::is_ascii_control) {
912            at += 1;
913        }
914        if input.get(at) != Some(&byte) {
915            return None;
916        }
917        at += 1;
918    }
919    Some(at)
920}
921
922pub(crate) fn match_ignoring_controls(
923    input: &[u8],
924    mut at: usize,
925    expected: &[u8],
926) -> Option<usize> {
927    for &byte in expected {
928        while input.get(at).is_some_and(u8::is_ascii_control) {
929            at += 1;
930        }
931        if !input
932            .get(at)
933            .is_some_and(|value| value.eq_ignore_ascii_case(&byte))
934        {
935            return None;
936        }
937        at += 1;
938    }
939    Some(at)
940}
941
942#[cfg(test)]
943mod tests;