Skip to main content

html5ever/tokenizer/
mod.rs

1// Copyright 2014-2017 The html5ever Project Developers. See the
2// COPYRIGHT file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10//! The HTML5 tokenizer.
11
12pub use self::interface::{CharacterTokens, EOFToken, NullCharacterToken, ParseError};
13pub use self::interface::{CommentToken, DoctypeToken, TagToken, Token};
14pub use self::interface::{Doctype, EndTag, StartTag, Tag, TagKind};
15pub use self::interface::{TokenSink, TokenSinkResult};
16
17use self::states::AttrValueKind::*;
18use self::states::DoctypeIdKind::{self, *};
19use self::states::RawKind::*;
20use self::states::ScriptEscapeKind::*;
21use self::states::State::{self, *};
22
23use self::char_ref::{CharRef, CharRefTokenizer};
24
25use crate::util::str::lower_ascii_letter;
26
27use log::{debug, trace};
28use markup5ever::{ns, small_char_set, TokenizerResult};
29use std::borrow::Cow::{self, Borrowed};
30use std::cell::{Cell, RefCell, RefMut};
31use std::cmp::Reverse;
32use std::collections::BTreeMap;
33use std::mem;
34
35pub use crate::buffer_queue::{BufferQueue, FromSet, NotFromSet, SetResult};
36use crate::macros::{time, unwrap_or_return};
37use crate::tendril::StrTendril;
38use crate::{Attribute, LocalName, QualName, SmallCharSet};
39
40mod char_ref;
41mod interface;
42pub mod states;
43
44/// The result of invoking the tokenizer once.
45pub enum ProcessResult<Handle> {
46    /// The tokenizer should be re-invoked immediately.
47    Continue,
48    /// The tokenizer has not finished, but it needs to wait for more
49    /// input to arrive before it can continue.
50    Suspend,
51    /// The tokenizer was blocked by a `<script>`.
52    ///
53    /// This `<script>` needs to be executed before tokenization
54    /// can continue, as it might invoke `document.write`.
55    Script(Handle),
56    /// The tokenizer was blocked because it found a `<meta charset>` tag.
57    ///
58    /// Such tags may force the user agent to re-parse the document with the new
59    /// encoding, but non-conformant implementations can reasonably treat
60    /// this as [Self::Continue].
61    EncodingIndicator(StrTendril),
62}
63
64fn option_push(opt_str: &mut Option<StrTendril>, c: char) {
65    match *opt_str {
66        Some(ref mut s) => s.push_char(c),
67        None => *opt_str = Some(StrTendril::from_char(c)),
68    }
69}
70
71/// Tokenizer options, with an impl for `Default`.
72#[derive(Clone)]
73pub struct TokenizerOpts {
74    /// Report all parse errors described in the spec, at some
75    /// performance penalty?  Default: false
76    pub exact_errors: bool,
77
78    /// Discard a `U+FEFF BYTE ORDER MARK` if we see one at the beginning
79    /// of the stream?  Default: true
80    pub discard_bom: bool,
81
82    /// Keep a record of how long we spent in each state?  Printed
83    /// when `end()` is called.  Default: false
84    pub profile: bool,
85
86    /// Initial state override.  Only the test runner should use
87    /// a non-`None` value!
88    pub initial_state: Option<states::State>,
89
90    /// Last start tag.  Only the test runner should use a
91    /// non-`None` value!
92    ///
93    /// FIXME: Can't use Tendril because we want TokenizerOpts
94    /// to be Send.
95    pub last_start_tag_name: Option<String>,
96}
97
98impl Default for TokenizerOpts {
99    fn default() -> TokenizerOpts {
100        TokenizerOpts {
101            exact_errors: false,
102            discard_bom: true,
103            profile: false,
104            initial_state: None,
105            last_start_tag_name: None,
106        }
107    }
108}
109
110/// The HTML tokenizer.
111pub struct Tokenizer<Sink> {
112    /// Options controlling the behavior of the tokenizer.
113    opts: TokenizerOpts,
114
115    /// Destination for tokens we emit.
116    pub sink: Sink,
117
118    /// The abstract machine state as described in the spec.
119    state: Cell<states::State>,
120
121    /// Are we at the end of the file, once buffers have been processed
122    /// completely? This affects whether we will wait for lookahead or not.
123    at_eof: Cell<bool>,
124
125    /// Tokenizer for character references, if we're tokenizing
126    /// one at the moment.
127    char_ref_tokenizer: RefCell<Option<CharRefTokenizer>>,
128
129    /// Current input character.  Just consumed, may reconsume.
130    current_char: Cell<char>,
131
132    /// Should we reconsume the current input character?
133    reconsume: Cell<bool>,
134
135    /// Did we just consume \r, translating it to \n?  In that case we need
136    /// to ignore the next character if it's \n.
137    ignore_lf: Cell<bool>,
138
139    /// Discard a U+FEFF BYTE ORDER MARK if we see one?  Only done at the
140    /// beginning of the stream.
141    discard_bom: Cell<bool>,
142
143    /// Current tag kind.
144    current_tag_kind: Cell<TagKind>,
145
146    /// Current tag name.
147    current_tag_name: RefCell<StrTendril>,
148
149    /// Current tag is self-closing?
150    current_tag_self_closing: Cell<bool>,
151
152    /// Current tag had duplicate attributes?
153    current_tag_had_duplicate_attributes: Cell<bool>,
154
155    /// Current tag attributes.
156    current_tag_attrs: RefCell<Vec<Attribute>>,
157
158    /// Current attribute name.
159    current_attr_name: RefCell<StrTendril>,
160
161    /// Current attribute value.
162    current_attr_value: RefCell<StrTendril>,
163
164    /// Current comment.
165    current_comment: RefCell<StrTendril>,
166
167    /// Current doctype token.
168    current_doctype: RefCell<Doctype>,
169
170    /// Last start tag name, for use in checking "appropriate end tag".
171    last_start_tag_name: RefCell<Option<LocalName>>,
172
173    /// The "temporary buffer" mentioned in the spec.
174    temp_buf: RefCell<StrTendril>,
175
176    /// Record of how many ns we spent in each state, if profiling is enabled.
177    state_profile: RefCell<BTreeMap<states::State, u64>>,
178
179    /// Record of how many ns we spent in the token sink.
180    time_in_sink: Cell<u64>,
181
182    /// Track current line
183    current_line: Cell<u64>,
184}
185
186impl<Sink: TokenSink> Tokenizer<Sink> {
187    /// Create a new tokenizer which feeds tokens to a particular `TokenSink`.
188    pub fn new(sink: Sink, mut opts: TokenizerOpts) -> Tokenizer<Sink> {
189        let start_tag_name = opts
190            .last_start_tag_name
191            .take()
192            .map(|s| LocalName::from(&*s));
193        let state = opts.initial_state.unwrap_or(states::Data);
194        let discard_bom = opts.discard_bom;
195        Tokenizer {
196            opts,
197            sink,
198            state: Cell::new(state),
199            char_ref_tokenizer: RefCell::new(None),
200            at_eof: Cell::new(false),
201            current_char: Cell::new('\0'),
202            reconsume: Cell::new(false),
203            ignore_lf: Cell::new(false),
204            discard_bom: Cell::new(discard_bom),
205            current_tag_kind: Cell::new(StartTag),
206            current_tag_name: RefCell::new(StrTendril::new()),
207            current_tag_self_closing: Cell::new(false),
208            current_tag_had_duplicate_attributes: Cell::new(false),
209            current_tag_attrs: RefCell::new(vec![]),
210            current_attr_name: RefCell::new(StrTendril::new()),
211            current_attr_value: RefCell::new(StrTendril::new()),
212            current_comment: RefCell::new(StrTendril::new()),
213            current_doctype: RefCell::new(Doctype::default()),
214            last_start_tag_name: RefCell::new(start_tag_name),
215            temp_buf: RefCell::new(StrTendril::new()),
216            state_profile: RefCell::new(BTreeMap::new()),
217            time_in_sink: Cell::new(0),
218            current_line: Cell::new(1),
219        }
220    }
221
222    /// Feed an input string into the tokenizer.
223    pub fn feed(&self, input: &BufferQueue) -> TokenizerResult<Sink::Handle> {
224        if input.is_empty() {
225            return TokenizerResult::Done;
226        }
227
228        if self.discard_bom.get() {
229            if let Some(c) = input.peek() {
230                if c == '\u{feff}' {
231                    input.next();
232                }
233            } else {
234                return TokenizerResult::Done;
235            }
236        };
237
238        self.run(input)
239    }
240
241    pub fn set_plaintext_state(&self) {
242        self.state.set(states::Plaintext);
243    }
244
245    fn process_token(&self, token: Token) -> TokenSinkResult<Sink::Handle> {
246        if self.opts.profile {
247            let (ret, dt) = time!(self.sink.process_token(token, self.current_line.get()));
248            self.time_in_sink.set(self.time_in_sink.get() + dt);
249            ret
250        } else {
251            self.sink.process_token(token, self.current_line.get())
252        }
253    }
254
255    fn process_token_and_continue(&self, token: Token) {
256        assert!(matches!(
257            self.process_token(token),
258            TokenSinkResult::Continue
259        ));
260    }
261
262    //§ preprocessing-the-input-stream
263    // Get the next input character, which might be the character
264    // 'c' that we already consumed from the buffers.
265    fn get_preprocessed_char(&self, mut c: char, input: &BufferQueue) -> Option<char> {
266        if self.ignore_lf.get() {
267            self.ignore_lf.set(false);
268            if c == '\n' {
269                c = input.next()?;
270            }
271        }
272
273        if c == '\r' {
274            self.ignore_lf.set(true);
275            c = '\n';
276        }
277
278        if c == '\n' {
279            self.current_line.set(self.current_line.get() + 1);
280        }
281
282        if self.opts.exact_errors
283            && match c as u32 {
284                0x01..=0x08 | 0x0B | 0x0E..=0x1F | 0x7F..=0x9F | 0xFDD0..=0xFDEF => true,
285                n if (n & 0xFFFE) == 0xFFFE => true,
286                _ => false,
287            }
288        {
289            let msg = format!("Bad character {c}");
290            self.emit_error(Cow::Owned(msg));
291        }
292
293        trace!("got character {c}");
294        self.current_char.set(c);
295        Some(c)
296    }
297
298    //§ tokenization
299    // Get the next input character, if one is available.
300    fn get_char(&self, input: &BufferQueue) -> Option<char> {
301        if self.reconsume.get() {
302            self.reconsume.set(false);
303            Some(self.current_char.get())
304        } else {
305            input
306                .next()
307                .and_then(|c| self.get_preprocessed_char(c, input))
308        }
309    }
310
311    fn pop_except_from(&self, input: &BufferQueue, set: SmallCharSet) -> Option<SetResult> {
312        // Bail to the slow path for various corner cases.
313        // This means that `FromSet` can contain characters not in the set!
314        // It shouldn't matter because the fallback `FromSet` case should
315        // always do the same thing as the `NotFromSet` case.
316        if self.opts.exact_errors || self.reconsume.get() || self.ignore_lf.get() {
317            return self.get_char(input).map(FromSet);
318        }
319
320        let d = input.pop_except_from(set);
321        trace!("got characters {d:?}");
322        match d {
323            Some(FromSet(c)) => self.get_preprocessed_char(c, input).map(FromSet),
324
325            // NB: We don't set self.current_char for a run of characters not
326            // in the set.  It shouldn't matter for the codepaths that use
327            // this.
328            _ => d,
329        }
330    }
331
332    // Check if the next characters are an ASCII case-insensitive match.  See
333    // BufferQueue::eat.
334    //
335    // NB: this doesn't set the current input character.
336    fn eat(&self, input: &BufferQueue, pat: &str, eq: fn(&u8, &u8) -> bool) -> Option<bool> {
337        if self.ignore_lf.get() {
338            self.ignore_lf.set(false);
339            if self.peek(input) == Some('\n') {
340                self.discard_char(input);
341            }
342        }
343
344        input.push_front(mem::take(&mut self.temp_buf.borrow_mut()));
345        match input.eat(pat, eq) {
346            None if self.at_eof.get() => Some(false),
347            None => {
348                while let Some(data) = input.next() {
349                    self.temp_buf.borrow_mut().push_char(data);
350                }
351                None
352            },
353            Some(matched) => Some(matched),
354        }
355    }
356
357    /// Run the state machine for as long as we can.
358    fn run(&self, input: &BufferQueue) -> TokenizerResult<Sink::Handle> {
359        if self.opts.profile {
360            loop {
361                let state = self.state.get();
362                let old_sink = self.time_in_sink.get();
363                let (run, mut dt) = time!(self.step(input));
364                dt -= (self.time_in_sink.get() - old_sink);
365                let new = match self.state_profile.borrow_mut().get_mut(&state) {
366                    Some(x) => {
367                        *x += dt;
368                        false
369                    },
370                    None => true,
371                };
372                if new {
373                    // do this here because of borrow shenanigans
374                    self.state_profile.borrow_mut().insert(state, dt);
375                }
376                match run {
377                    ProcessResult::Continue => (),
378                    ProcessResult::Suspend => break,
379                    ProcessResult::Script(node) => return TokenizerResult::Script(node),
380                    ProcessResult::EncodingIndicator(encoding) => {
381                        return TokenizerResult::EncodingIndicator(encoding)
382                    },
383                }
384            }
385        } else {
386            loop {
387                match self.step(input) {
388                    ProcessResult::Continue => (),
389                    ProcessResult::Suspend => break,
390                    ProcessResult::Script(node) => return TokenizerResult::Script(node),
391                    ProcessResult::EncodingIndicator(encoding) => {
392                        return TokenizerResult::EncodingIndicator(encoding)
393                    },
394                }
395            }
396        }
397        TokenizerResult::Done
398    }
399
400    #[inline]
401    fn bad_char_error(&self) {
402        #[cfg(feature = "trace_tokenizer")]
403        trace!("  error");
404
405        let msg = if self.opts.exact_errors {
406            Cow::from("Bad character")
407        } else {
408            let c = self.current_char.get();
409            let state = self.state.get();
410            Cow::from(format!("Saw {c} in state {state:?}"))
411        };
412        self.emit_error(msg);
413    }
414
415    #[inline]
416    fn bad_eof_error(&self) {
417        #[cfg(feature = "trace_tokenizer")]
418        trace!("  error_eof");
419
420        let msg = if self.opts.exact_errors {
421            Cow::from("Unexpected EOF")
422        } else {
423            let state = self.state.get();
424            Cow::from(format!("Saw EOF in state {state:?}"))
425        };
426        self.emit_error(msg);
427    }
428
429    fn emit_char(&self, c: char) {
430        #[cfg(feature = "trace_tokenizer")]
431        trace!("  emit");
432
433        self.process_token_and_continue(match c {
434            '\0' => NullCharacterToken,
435            _ => CharacterTokens(StrTendril::from_char(c)),
436        });
437    }
438
439    // The string must not contain '\0'!
440    fn emit_chars(&self, b: StrTendril) {
441        self.process_token_and_continue(CharacterTokens(b));
442    }
443
444    fn emit_current_tag(&self) -> ProcessResult<Sink::Handle> {
445        self.finish_attribute();
446
447        let name = LocalName::from(&**self.current_tag_name.borrow());
448        self.current_tag_name.borrow_mut().clear();
449
450        match self.current_tag_kind.get() {
451            StartTag => {
452                *self.last_start_tag_name.borrow_mut() = Some(name.clone());
453            },
454            EndTag => {
455                if !self.current_tag_attrs.borrow().is_empty() {
456                    self.emit_error(Borrowed("Attributes on an end tag"));
457                }
458                if self.current_tag_self_closing.get() {
459                    self.emit_error(Borrowed("Self-closing end tag"));
460                }
461            },
462        }
463
464        let token = TagToken(Tag {
465            kind: self.current_tag_kind.get(),
466            name,
467            self_closing: self.current_tag_self_closing.get(),
468            attrs: std::mem::take(&mut self.current_tag_attrs.borrow_mut()),
469            had_duplicate_attributes: self.current_tag_had_duplicate_attributes.get(),
470        });
471
472        match self.process_token(token) {
473            TokenSinkResult::Continue => ProcessResult::Continue,
474            TokenSinkResult::Plaintext => {
475                self.state.set(states::Plaintext);
476                ProcessResult::Continue
477            },
478            TokenSinkResult::Script(node) => {
479                self.state.set(states::Data);
480                ProcessResult::Script(node)
481            },
482            TokenSinkResult::RawData(kind) => {
483                self.state.set(states::RawData(kind));
484                ProcessResult::Continue
485            },
486            TokenSinkResult::EncodingIndicator(encoding) => {
487                ProcessResult::EncodingIndicator(encoding)
488            },
489        }
490    }
491
492    fn emit_temp_buf(&self) {
493        #[cfg(feature = "trace_tokenizer")]
494        trace!("  emit_temp");
495
496        // FIXME: Make sure that clearing on emit is spec-compatible.
497        let buf = mem::take(&mut *self.temp_buf.borrow_mut());
498        self.emit_chars(buf);
499    }
500
501    fn clear_temp_buf(&self) {
502        // Do this without a new allocation.
503        self.temp_buf.borrow_mut().clear();
504    }
505
506    fn emit_current_comment(&self) {
507        let comment = mem::take(&mut *self.current_comment.borrow_mut());
508        self.process_token_and_continue(CommentToken(comment));
509    }
510
511    fn discard_tag(&self) {
512        self.current_tag_name.borrow_mut().clear();
513        self.current_tag_self_closing.set(false);
514        self.current_tag_had_duplicate_attributes.set(false);
515        *self.current_tag_attrs.borrow_mut() = vec![];
516    }
517
518    fn create_tag(&self, kind: TagKind, c: char) {
519        self.discard_tag();
520        self.current_tag_name.borrow_mut().push_char(c);
521        self.current_tag_kind.set(kind);
522    }
523
524    fn have_appropriate_end_tag(&self) -> bool {
525        match self.last_start_tag_name.borrow().as_ref() {
526            Some(last) => {
527                (self.current_tag_kind.get() == EndTag)
528                    && (**self.current_tag_name.borrow() == **last)
529            },
530            None => false,
531        }
532    }
533
534    fn create_attribute(&self, c: char) {
535        self.finish_attribute();
536
537        self.current_attr_name.borrow_mut().push_char(c);
538    }
539
540    fn finish_attribute(&self) {
541        if self.current_attr_name.borrow().is_empty() {
542            return;
543        }
544        let name = LocalName::from(&**self.current_attr_name.borrow());
545        self.current_attr_name.borrow_mut().clear();
546        // Check for a duplicate attribute.
547        // FIXME: the spec says we should error as soon as the name is finished.
548        let dup = {
549            self.current_tag_attrs
550                .borrow()
551                .iter()
552                .any(|a| a.name.local == name)
553        };
554
555        if dup {
556            self.emit_error(Borrowed("Duplicate attribute"));
557            self.current_tag_had_duplicate_attributes.set(true);
558            self.current_attr_value.borrow_mut().clear();
559        } else {
560            self.current_tag_attrs.borrow_mut().push(Attribute {
561                // The tree builder will adjust the namespace if necessary.
562                // This only happens in foreign elements.
563                name: QualName::new(None, ns!(), name),
564                value: mem::take(&mut self.current_attr_value.borrow_mut()),
565            });
566        }
567    }
568
569    fn emit_current_doctype(&self) {
570        let doctype = self.current_doctype.take();
571        self.process_token_and_continue(DoctypeToken(doctype));
572    }
573
574    fn doctype_id(&self, kind: DoctypeIdKind) -> RefMut<'_, Option<StrTendril>> {
575        let current_doctype = self.current_doctype.borrow_mut();
576        match kind {
577            Public => RefMut::map(current_doctype, |d| &mut d.public_id),
578            System => RefMut::map(current_doctype, |d| &mut d.system_id),
579        }
580    }
581
582    fn clear_doctype_id(&self, kind: DoctypeIdKind) {
583        let mut id = self.doctype_id(kind);
584        match *id {
585            Some(ref mut s) => s.clear(),
586            None => *id = Some(StrTendril::new()),
587        }
588    }
589
590    fn start_consuming_character_reference(&self) {
591        debug_assert!(
592            self.char_ref_tokenizer.borrow().is_none(),
593            "Nested character references are impossible"
594        );
595
596        let is_in_attribute = matches!(self.state.get(), states::AttributeValue(_));
597        *self.char_ref_tokenizer.borrow_mut() = Some(CharRefTokenizer::new(is_in_attribute));
598    }
599
600    fn emit_eof(&self) {
601        self.process_token_and_continue(EOFToken);
602    }
603
604    fn peek(&self, input: &BufferQueue) -> Option<char> {
605        if self.reconsume.get() {
606            Some(self.current_char.get())
607        } else {
608            input.peek()
609        }
610    }
611
612    fn discard_char(&self, input: &BufferQueue) {
613        // peek() deals in un-processed characters (no newline normalization), while get_char()
614        // does.
615        //
616        // since discard_char is supposed to be used in combination with peek(), discard_char must
617        // discard a single raw input character, not a normalized newline.
618        if self.reconsume.get() {
619            self.reconsume.set(false);
620        } else {
621            input.next();
622        }
623    }
624
625    fn emit_error(&self, error: Cow<'static, str>) {
626        self.process_token_and_continue(ParseError(error));
627    }
628}
629//§ END
630
631// Shorthand for common state machine behaviors.
632macro_rules! shorthand (
633    ( $me:ident : create_tag $kind:ident $c:expr   ) => ( $me.create_tag($kind, $c)                           );
634    ( $me:ident : push_tag $c:expr                 ) => ( $me.current_tag_name.borrow_mut().push_char($c)     );
635    ( $me:ident : discard_tag                      ) => ( $me.discard_tag()                                   );
636    ( $me:ident : discard_char $input:expr         ) => ( $me.discard_char($input)                            );
637    ( $me:ident : push_temp $c:expr                ) => ( $me.temp_buf.borrow_mut().push_char($c)             );
638    ( $me:ident : clear_temp                       ) => ( $me.clear_temp_buf()                                );
639    ( $me:ident : create_attr $c:expr              ) => ( $me.create_attribute($c)                            );
640    ( $me:ident : push_name $c:expr                ) => ( $me.current_attr_name.borrow_mut().push_char($c)    );
641    ( $me:ident : push_value $c:expr               ) => ( $me.current_attr_value.borrow_mut().push_char($c)   );
642    ( $me:ident : append_value $c:expr             ) => ( $me.current_attr_value.borrow_mut().push_tendril($c));
643    ( $me:ident : push_comment $c:expr             ) => ( $me.current_comment.borrow_mut().push_char($c)      );
644    ( $me:ident : append_comment $c:expr           ) => ( $me.current_comment.borrow_mut().push_slice($c)     );
645    ( $me:ident : emit_comment                     ) => ( $me.emit_current_comment()                          );
646    ( $me:ident : clear_comment                    ) => ( $me.current_comment.borrow_mut().clear()            );
647    ( $me:ident : create_doctype                   ) => ( *$me.current_doctype.borrow_mut() = Doctype::default() );
648    ( $me:ident : push_doctype_name $c:expr        ) => ( option_push(&mut $me.current_doctype.borrow_mut().name, $c) );
649    ( $me:ident : push_doctype_id $k:ident $c:expr ) => ( option_push(&mut $me.doctype_id($k), $c)            );
650    ( $me:ident : clear_doctype_id $k:ident        ) => ( $me.clear_doctype_id($k)                            );
651    ( $me:ident : force_quirks                     ) => ( $me.current_doctype.borrow_mut().force_quirks = true);
652    ( $me:ident : emit_doctype                     ) => ( $me.emit_current_doctype()                          );
653);
654
655// Tracing of tokenizer actions.  This adds significant bloat and compile time,
656// so it's behind a cfg flag.
657#[cfg(feature = "trace_tokenizer")]
658macro_rules! sh_trace ( ( $me:ident : $($cmds:tt)* ) => ({
659    trace!("  {:?}", stringify!($($cmds)*));
660    shorthand!($me : $($cmds)*);
661}));
662
663#[cfg(not(feature = "trace_tokenizer"))]
664macro_rules! sh_trace ( ( $me:ident : $($cmds:tt)* ) => ( shorthand!($me: $($cmds)*) ) );
665
666// A little DSL for sequencing shorthand actions.
667macro_rules! go (
668    // A pattern like $($cmd:tt)* ; $($rest:tt)* causes parse ambiguity.
669    // We have to tell the parser how much lookahead we need.
670
671    ( $me:ident : $a:tt                   ; $($rest:tt)* ) => ({ sh_trace!($me: $a);          go!($me: $($rest)*); });
672    ( $me:ident : $a:tt $b:tt             ; $($rest:tt)* ) => ({ sh_trace!($me: $a $b);       go!($me: $($rest)*); });
673    ( $me:ident : $a:tt $b:tt $c:tt       ; $($rest:tt)* ) => ({ sh_trace!($me: $a $b $c);    go!($me: $($rest)*); });
674    ( $me:ident : $a:tt $b:tt $c:tt $d:tt ; $($rest:tt)* ) => ({ sh_trace!($me: $a $b $c $d); go!($me: $($rest)*); });
675
676    // These can only come at the end.
677
678    ( $me:ident : to $s:expr                  ) => ({ $me.state.set($s); return ProcessResult::Continue;                });
679    ( $me:ident : reconsume $s:expr           ) => ({ $me.reconsume.set(true); go!($me: to $s);                                 });
680    ( $me:ident : consume_char_ref             ) => ({ $me.start_consuming_character_reference(); return ProcessResult::Continue;});
681
682    // We have a default next state after emitting a tag, but the sink can override.
683    ( $me:ident : emit_tag $s:ident ) => ({
684        $me.state.set(states::$s);
685        return $me.emit_current_tag();
686    });
687
688    ( $me:ident : eof ) => ({ $me.emit_eof(); return ProcessResult::Suspend; });
689
690    // If nothing else matched, it's a single command
691    ( $me:ident : $($cmd:tt)+ ) => ( sh_trace!($me: $($cmd)+) );
692
693    // or nothing.
694    ( $me:ident : ) => (());
695);
696
697// This is a macro because it can cause early return
698// from the function where it is used.
699macro_rules! get_char ( ($me:expr, $input:expr) => (
700    unwrap_or_return!($me.get_char($input), ProcessResult::Suspend)
701));
702
703macro_rules! peek ( ($me:expr, $input:expr) => (
704    unwrap_or_return!($me.peek($input), ProcessResult::Suspend)
705));
706
707macro_rules! eat ( ($me:expr, $input:expr, $pat:expr) => (
708    unwrap_or_return!($me.eat($input, $pat, u8::eq_ignore_ascii_case), ProcessResult::Suspend)
709));
710
711macro_rules! eat_exact ( ($me:expr, $input:expr, $pat:expr) => (
712    unwrap_or_return!($me.eat($input, $pat, u8::eq), ProcessResult::Suspend)
713));
714
715impl<Sink: TokenSink> Tokenizer<Sink> {
716    // Run the state machine for a while.
717    // Return true if we should be immediately re-invoked
718    // (this just simplifies control flow vs. break / continue).
719    #[allow(clippy::never_loop)]
720    fn step(&self, input: &BufferQueue) -> ProcessResult<Sink::Handle> {
721        if self.char_ref_tokenizer.borrow().is_some() {
722            return self.step_char_ref_tokenizer(input);
723        }
724
725        trace!("processing in state {:?}", self.state);
726        match self.state.get() {
727            // https://html.spec.whatwg.org/#data-state
728            states::Data => loop {
729                // Step 1. Consume the next input character:
730                let set = small_char_set!('\r' '\0' '&' '<' '\n');
731
732                #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
733                let set_result = if !(self.opts.exact_errors
734                    || self.reconsume.get()
735                    || self.ignore_lf.get())
736                    && Self::is_supported_simd_feature_detected()
737                {
738                    let front_buffer = input.peek_front_chunk_mut();
739                    let Some(mut front_buffer) = front_buffer else {
740                        return ProcessResult::Suspend;
741                    };
742
743                    // Special case: The fast path is not worth taking if the first character is already in the set,
744                    // which is fairly common
745                    let first_char = front_buffer
746                        .chars()
747                        .next()
748                        .expect("Input buffers are never empty");
749
750                    if matches!(first_char, '\r' | '\0' | '&' | '<' | '\n') {
751                        drop(front_buffer);
752                        self.pop_except_from(input, set)
753                    } else {
754                        // SAFETY:
755                        // This CPU is guaranteed to support SIMD due to the is_supported_simd_feature_detected check above
756                        let result = unsafe { self.data_state_simd_fast_path(&mut front_buffer) };
757
758                        if front_buffer.is_empty() {
759                            drop(front_buffer);
760                            input.pop_front();
761                        }
762
763                        result
764                    }
765                } else {
766                    self.pop_except_from(input, set)
767                };
768
769                #[cfg(not(any(
770                    target_arch = "x86",
771                    target_arch = "x86_64",
772                    target_arch = "aarch64"
773                )))]
774                let set_result = self.pop_except_from(input, set);
775
776                let Some(set_result) = set_result else {
777                    return ProcessResult::Suspend;
778                };
779                match set_result {
780                    // ↪ U+0026 AMPERSAND (&)
781                    FromSet('&') => {
782                        // Set the return state to the data state. Switch to the character reference state.
783                        go!(self: consume_char_ref)
784                    },
785                    // ↪ U+003C LESS-THAN SIGN (<)
786                    FromSet('<') => {
787                        // Switch to the tag open state.
788                        go!(self: to State::TagOpen)
789                    },
790                    // ↪ U+0000 NULL
791                    FromSet('\0') => {
792                        // This is an unexpected-null-character parse error.
793                        // Emit the current input character as a character token.
794                        self.bad_char_error();
795                        self.emit_char('\0');
796                    },
797                    // ↪ Anything else
798                    //     Emit the current input character as a character token.
799                    FromSet(character) => self.emit_char(character),
800                    NotFromSet(characters) => self.emit_chars(characters),
801                }
802            },
803
804            // https://html.spec.whatwg.org/#rcdata-state
805            states::RawData(Rcdata) => loop {
806                // Consume the next input character:
807                let Some(set_result) =
808                    self.pop_except_from(input, small_char_set!('\r' '\0' '&' '<' '\n'))
809                else {
810                    return ProcessResult::Suspend;
811                };
812
813                match set_result {
814                    // ↪ U+0026 AMPERSAND (&)
815                    FromSet('&') => {
816                        go!(self: consume_char_ref)
817                    },
818                    // ↪ U+003C LESS-THAN SIGN (<)
819                    FromSet('<') => {
820                        // Switch to the RCDATA less-than sign state.
821                        go!(self: to State::RawLessThanSign(Rcdata))
822                    },
823                    // ↪ U+0000 NULL
824                    FromSet('\0') => {
825                        self.bad_char_error();
826                        self.emit_char('\u{fffd}');
827                    },
828                    // ↪ Anything else
829                    //     Emit the current input character as a character token.
830                    FromSet(character) => self.emit_char(character),
831                    NotFromSet(characters) => self.emit_chars(characters),
832                }
833            },
834
835            // https://html.spec.whatwg.org/#rawtext-state
836            states::RawData(Rawtext) => loop {
837                // Consume the next input character:
838                let Some(set_result) =
839                    self.pop_except_from(input, small_char_set!('\r' '\0' '<' '\n'))
840                else {
841                    return ProcessResult::Suspend;
842                };
843
844                match set_result {
845                    // ↪ U+003C LESS-THAN SIGN (<)
846                    FromSet('<') => {
847                        // Switch to the RAWTEXT less-than sign state.
848                        go!(self: to State::RawLessThanSign(Rawtext));
849                    },
850                    // ↪ U+0000 NULL
851                    FromSet('\0') => {
852                        // This is an unexpected-null-character parse error.
853                        // Emit a U+FFFD REPLACEMENT CHARACTER character token.
854                        self.bad_char_error();
855                        self.emit_char('\u{fffd}');
856                    },
857                    // ↪ Anything else
858                    //     Emit the current input character as a character token.
859                    FromSet(character) => self.emit_char(character),
860                    NotFromSet(characters) => self.emit_chars(characters),
861                }
862            },
863
864            // https://html.spec.whatwg.org/#script-data-state
865            states::RawData(ScriptData) => loop {
866                // Consume the next input character:
867                let Some(set_result) =
868                    self.pop_except_from(input, small_char_set!('\r' '\0' '<' '\n'))
869                else {
870                    return ProcessResult::Suspend;
871                };
872
873                match set_result {
874                    // ↪ U+003C LESS-THAN SIGN (<)
875                    FromSet('<') => {
876                        // Switch to the script data less-than sign state.
877                        go!(self: to State::RawLessThanSign(ScriptData));
878                    },
879                    // ↪ U+0000 NULL
880                    FromSet('\0') => {
881                        // This is an unexpected-null-character parse error.
882                        // Emit a U+FFFD REPLACEMENT CHARACTER character token.
883                        self.bad_char_error();
884                        self.emit_char('\u{fffd}');
885                    },
886                    // ↪ Anything else
887                    //     Emit the current input character as a character token.
888                    FromSet(character) => self.emit_char(character),
889                    NotFromSet(characters) => self.emit_chars(characters),
890                }
891            },
892
893            // https://html.spec.whatwg.org/#plaintext-state
894            states::Plaintext => loop {
895                // Consume the next input character:
896                let Some(set_result) = self.pop_except_from(input, small_char_set!('\r' '\0' '\n'))
897                else {
898                    return ProcessResult::Suspend;
899                };
900
901                match set_result {
902                    // ↪ U+0000 NULL
903                    FromSet('\0') => {
904                        // This is an unexpected-null-character parse error.
905                        // Emit a U+FFFD REPLACEMENT CHARACTER character token.
906                        self.bad_char_error();
907                        self.emit_char('\u{fffd}');
908                    },
909                    // ↪ Anything else
910                    //     Emit the current input character as a character token.
911                    FromSet(character) => self.emit_char(character),
912                    NotFromSet(characters) => self.emit_chars(characters),
913                }
914            },
915
916            // https://html.spec.whatwg.org/#tag-open-state
917            states::TagOpen => loop {
918                // Consume the next input character:
919                match get_char!(self, input) {
920                    // ↪ U+0021 EXCLAMATION MARK (!)
921                    '!' => {
922                        // Switch to the markup declaration open state.
923                        go!(self: to State::MarkupDeclarationOpen)
924                    },
925                    // ↪ U+002F SOLIDUS (/)
926                    '/' => {
927                        // Switch to the end tag open state.
928                        go!(self: to State::EndTagOpen)
929                    },
930                    // ↪ ASCII alpha
931                    character if character.is_ascii_alphabetic() => {
932                        // Create a new start tag token, set its tag name to the empty string.
933                        // Reconsume in the tag name state.
934                        // NOTE: We don't reconsume the character but instead immediately append it in lowercase
935                        // to the new tag (as that is what the "tag name" state would do).
936                        let character = character.to_ascii_lowercase();
937                        go!(self: create_tag StartTag character; to State::TagName)
938                    },
939                    // ↪ U+003F QUESTION MARK (?)
940                    '?' => {
941                        // Set the temporary buffer to the empty string.
942                        // Switch to the processing instruction open state.
943                        self.bad_char_error();
944                        go!(self: clear_comment; reconsume BogusComment)
945                    },
946                    // ↪ Anything else
947                    _ => {
948                        // This is an invalid-first-character-of-tag-name parse error.
949                        // Emit a U+003C LESS-THAN SIGN character token.
950                        // Reconsume in the data state.
951                        self.bad_char_error();
952                        self.emit_char('<');
953                        go!(self: reconsume Data)
954                    },
955                }
956            },
957
958            // https://html.spec.whatwg.org/#end-tag-open-state
959            states::EndTagOpen => loop {
960                // Consume the next input character:
961                match get_char!(self, input) {
962                    // ↪ ASCII alpha
963                    character if character.is_ascii_alphabetic() => {
964                        // Create a new end tag token, set its tag name to the empty string.
965                        // Reconsume in the tag name state.
966                        // NOTE: We don't reconsume the character but instead immediately append in lowercase
967                        // to the new tag (as that is what the "tag name" state would do).
968                        let character = character.to_ascii_lowercase();
969                        go!(self: create_tag EndTag character; to State::TagName)
970                    },
971                    // ↪ U+003E GREATER-THAN SIGN (>)
972                    '>' => {
973                        // This is a missing-end-tag-name parse error.
974                        // Switch to the data state.
975                        self.bad_char_error();
976                        go!(self: to State::Data)
977                    },
978                    // ↪ Anything else
979                    _ => {
980                        // This is an invalid-first-character-of-tag-name parse error.
981                        // Create a comment token whose data is the empty string.
982                        // Reconsume in the bogus comment state.
983                        self.bad_char_error();
984                        go!(self: clear_comment; reconsume BogusComment)
985                    },
986                }
987            },
988
989            // https://html.spec.whatwg.org/#tag-name-state
990            states::TagName => loop {
991                // Consume the next input character:
992                match get_char!(self, input) {
993                    // ↪ U+0009 CHARACTER TABULATION (tab)
994                    // ↪ U+000A LINE FEED (LF)
995                    // ↪ U+000C FORM FEED (FF)
996                    // ↪ U+0020 SPACE
997                    '\t' | '\n' | '\x0C' | ' ' => {
998                        // Switch to the before attribute name state.
999                        go!(self: to State::BeforeAttributeName)
1000                    },
1001                    // ↪ U+002F SOLIDUS (/)
1002                    '/' => {
1003                        // Switch to the self-closing start tag state.
1004                        go!(self: to State::SelfClosingStartTag)
1005                    },
1006                    // ↪ U+003E GREATER-THAN SIGN (>)
1007                    '>' => {
1008                        // Switch to the data state.
1009                        // Emit the current tag token.
1010                        go!(self: emit_tag Data)
1011                    },
1012                    // ↪ ASCII upper alpha
1013                    character if character.is_ascii_uppercase() => {
1014                        // Append the lowercase version of the current input character (add 0x0020 to the
1015                        // character's code point) to the current tag token's tag name.
1016                        go!(self: push_tag (character.to_ascii_lowercase()))
1017                    },
1018                    // ↪ U+0000 NULL
1019                    '\0' => {
1020                        self.bad_char_error();
1021                        go!(self: push_tag '\u{fffd}')
1022                    },
1023                    // ↪ Anything else
1024                    character => {
1025                        // Append the current input character to the current tag token's tag name.
1026                        go!(self: push_tag (character))
1027                    },
1028                }
1029            },
1030
1031            // https://html.spec.whatwg.org/#script-data-escaped-less-than-sign-state
1032            states::RawLessThanSign(ScriptDataEscaped(Escaped)) => loop {
1033                // Consume the next input character:
1034                match get_char!(self, input) {
1035                    // ↪ U+002F SOLIDUS (/)
1036                    '/' => {
1037                        go!(self: clear_temp; to State::RawEndTagOpen(ScriptDataEscaped(Escaped)))
1038                    },
1039                    character => match lower_ascii_letter(character) {
1040                        // ↪ ASCII alpha
1041                        Some(character_lowercase) => {
1042                            // Set the temporary buffer to the empty string.
1043                            // Emit a U+003C LESS-THAN SIGN character token.
1044                            // Reconsume in the script data double escape start state.
1045                            // NOTE: We don't reconsume, and instead emit the lowercased character immediately,
1046                            // as that is what the "script data double escape start" state would do.
1047                            go!(self: clear_temp; push_temp character_lowercase);
1048                            self.emit_char('<');
1049                            self.emit_char(character);
1050                            go!(self: to State::ScriptDataEscapeStart(DoubleEscaped));
1051                        },
1052                        // ↪ Anything else
1053                        None => {
1054                            self.emit_char('<');
1055                            go!(self: reconsume RawData(ScriptDataEscaped(Escaped)));
1056                        },
1057                    },
1058                }
1059            },
1060
1061            // https://html.spec.whatwg.org/#script-data-double-escaped-less-than-sign-state
1062            states::RawLessThanSign(ScriptDataEscaped(DoubleEscaped)) => loop {
1063                // Consume the next input character:
1064                match get_char!(self, input) {
1065                    // ↪ U+002F SOLIDUS (/)
1066                    '/' => {
1067                        // Set the temporary buffer to the empty string.
1068                        // Switch to the script data double escape end state.
1069                        // Emit a U+002F SOLIDUS character token.
1070                        go!(self: clear_temp);
1071                        self.emit_char('/');
1072                        go!(self: to State::ScriptDataDoubleEscapeEnd);
1073                    },
1074                    // ↪ Anything else
1075                    _ => {
1076                        // Reconsume in the script data double escaped state.
1077                        go!(self: reconsume RawData(ScriptDataEscaped(DoubleEscaped)))
1078                    },
1079                }
1080            },
1081
1082            // https://html.spec.whatwg.org/#rcdata-less-than-sign-state
1083            // https://html.spec.whatwg.org/#script-data-less-than-sign-state
1084            // https://html.spec.whatwg.org/#rawtext-less-than-sign-state
1085            states::RawLessThanSign(kind) => loop {
1086                // Consume the next input character:
1087                match get_char!(self, input) {
1088                    // ↪ U+002F SOLIDUS (/)
1089                    '/' => {
1090                        // Set the temporary buffer to the empty string.
1091                        // Switch to the RCDATA end tag open state.
1092                        go!(self: clear_temp; to State::RawEndTagOpen(kind))
1093                    },
1094                    // ↪ U+0021 EXCLAMATION MARK (!)
1095                    '!' if kind == ScriptData => {
1096                        // Switch to the script data escape start state.
1097                        // Emit a U+003C LESS-THAN SIGN character token and a U+0021 EXCLAMATION MARK character token.
1098                        self.emit_char('<');
1099                        self.emit_char('!');
1100                        go!(self: to State::ScriptDataEscapeStart(Escaped));
1101                    },
1102                    // ↪ Anything else
1103                    _ => {
1104                        // Emit a U+003C LESS-THAN SIGN character token.
1105                        // Reconsume in the RCDATA/script data/RAWTEXT state.
1106                        self.emit_char('<');
1107                        go!(self: reconsume RawData(kind));
1108                    },
1109                }
1110            },
1111
1112            // https://html.spec.whatwg.org/#rcdata-end-tag-open-state
1113            // https://html.spec.whatwg.org/#rawtext-end-tag-open-state
1114            // https://html.spec.whatwg.org/#script-data-end-tag-open-state
1115            // https://html.spec.whatwg.org/#script-data-escaped-end-tag-open-state
1116            states::RawEndTagOpen(kind) => loop {
1117                // Consume the next input character:
1118                let character = get_char!(self, input);
1119
1120                // ↪ ASCII alpha
1121                if character.is_ascii_alphabetic() {
1122                    // Create a new end tag token, set its tag name to the empty string.
1123                    // Reconsume in the RCDATA end tag name state.
1124                    // NOTE: We don't reconsume the character but instead immediately append its lowercase version
1125                    // to the end tag (as that is what the new state would do).
1126                    let character_lowercase = character.to_ascii_lowercase();
1127                    go!(self: create_tag EndTag character_lowercase; push_temp character; to State::RawEndTagName(kind))
1128                }
1129                // ↪ Anything else
1130                else {
1131                    // Emit a U+003C LESS-THAN SIGN character token and a U+002F SOLIDUS character token.
1132                    // Reconsume in the RCDATA/RAWTEXT/script data/script data escaped state.
1133                    self.emit_char('<');
1134                    self.emit_char('/');
1135                    go!(self: reconsume RawData(kind));
1136                }
1137            },
1138
1139            // https://html.spec.whatwg.org/#rcdata-end-tag-name-state
1140            // https://html.spec.whatwg.org/#rawtext-end-tag-name-state
1141            // https://html.spec.whatwg.org/#script-data-end-tag-name-state
1142            // https://html.spec.whatwg.org/#script-data-escaped-end-tag-name-state
1143            states::RawEndTagName(kind) => loop {
1144                let character = get_char!(self, input);
1145
1146                // NOTE: The first three match arms in the specification are treated as "anything else" if the current
1147                // end tag token is NOT an appropriate end tag, so we move them into their own match block.
1148                if self.have_appropriate_end_tag() {
1149                    match character {
1150                        // ↪ U+0009 CHARACTER TABULATION (tab)
1151                        // ↪ U+000A LINE FEED (LF)
1152                        // ↪ U+000C FORM FEED (FF)
1153                        // ↪ U+0020 SPACE
1154                        '\t' | '\n' | '\x0C' | ' ' => {
1155                            // Switch to the before attribute name state
1156                            go!(self: clear_temp; to State::BeforeAttributeName)
1157                        },
1158                        // ↪ U+002F SOLIDUS (/)
1159                        '/' => {
1160                            // Switch to the self-closing start tag state
1161                            go!(self: clear_temp; to State::SelfClosingStartTag)
1162                        },
1163                        // ↪ U+003E GREATER-THAN SIGN (>)
1164                        '>' => {
1165                            // Switch to the data state and emit the current tag token
1166                            go!(self: clear_temp; emit_tag Data)
1167                        },
1168                        _ => {},
1169                    }
1170                }
1171
1172                match lower_ascii_letter(character) {
1173                    // ↪ ASCII upper alpha
1174                    //     NOTE: This is the same as for lower alpha, but the character is lowercased first.
1175                    //     This is handled by lower_ascii_letter.
1176                    // ↪ ASCII lower alpha
1177                    Some(character_lowercase) => {
1178                        // Append the current input character to the current tag token's tag name.
1179                        // Append the current input character to the temporary buffer.
1180                        go!(self: push_tag character_lowercase; push_temp character)
1181                    },
1182                    // ↪ Anything else
1183                    None => {
1184                        // Emit a U+003C LESS-THAN SIGN character token, a U+002F SOLIDUS character token,
1185                        // and a character token for each of the characters in the temporary buffer
1186                        // (in the order they were added to the buffer).
1187                        // Reconsume in the RCDATA/RAWTEXT/script data/script data escaped state.
1188                        go!(self: discard_tag);
1189                        self.emit_char('<');
1190                        self.emit_char('/');
1191                        self.emit_temp_buf();
1192                        go!(self: reconsume RawData(kind));
1193                    },
1194                }
1195            },
1196
1197            // https://html.spec.whatwg.org/#script-data-double-escape-start-state
1198            states::ScriptDataEscapeStart(DoubleEscaped) => loop {
1199                // Consume the next input character:
1200                match get_char!(self, input) {
1201                    // ↪ U+0009 CHARACTER TABULATION (tab)
1202                    // ↪ U+000A LINE FEED (LF)
1203                    // ↪ U+000C FORM FEED (FF)
1204                    // ↪ U+0020 SPACE
1205                    // ↪ U+002F SOLIDUS (/)
1206                    // ↪ U+003E GREATER-THAN SIGN (>)
1207                    character @ ('\t' | '\n' | '\x0C' | ' ' | '/' | '>') => {
1208                        // If the temporary buffer is "script", then switch to the script data double escaped state.
1209                        // Otherwise, switch to the script data escaped state.
1210                        // Emit the current input character as a character token.
1211                        let escaped_kind = if &**self.temp_buf.borrow() == "script" {
1212                            DoubleEscaped
1213                        } else {
1214                            Escaped
1215                        };
1216                        self.emit_char(character);
1217                        go!(self: to State::RawData(ScriptDataEscaped(escaped_kind)));
1218                    },
1219                    // ↪ ASCII upper alpha
1220                    //     NOTE: This is the same as the "ASCII lower alpha" branch, except we lowercase the character first
1221                    // ↪ ASCII lower alpha
1222                    character => match lower_ascii_letter(character) {
1223                        Some(character_lowercase) => {
1224                            // Append the current input character to the temporary buffer.
1225                            // Emit the current input character as a character token.
1226                            go!(self: push_temp character_lowercase);
1227                            self.emit_char(character);
1228                        },
1229                        // ↪ Anything else
1230                        None => {
1231                            // Reconsume in the script data escaped state.
1232                            go!(self: reconsume RawData(ScriptDataEscaped(Escaped)))
1233                        },
1234                    },
1235                }
1236            },
1237
1238            // https://html.spec.whatwg.org/#script-data-double-escaped-state
1239            states::RawData(ScriptDataEscaped(DoubleEscaped)) => loop {
1240                // Consume the next input character:
1241                let Some(set_result) =
1242                    self.pop_except_from(input, small_char_set!('\r' '\0' '-' '<' '\n'))
1243                else {
1244                    return ProcessResult::Suspend;
1245                };
1246
1247                match set_result {
1248                    // ↪ U+002D HYPHEN-MINUS (-)
1249                    FromSet('-') => {
1250                        // Switch to the script data double escaped dash state.
1251                        // Emit a U+002D HYPHEN-MINUS character token.
1252                        self.emit_char('-');
1253                        go!(self: to State::ScriptDataEscapedDash(DoubleEscaped));
1254                    },
1255                    // ↪ U+003C LESS-THAN SIGN (<)
1256                    FromSet('<') => {
1257                        // Switch to the script data double escaped less-than sign state.
1258                        // Emit a U+003C LESS-THAN SIGN character token.
1259                        self.emit_char('<');
1260                        go!(self: to State::RawLessThanSign(ScriptDataEscaped(DoubleEscaped)))
1261                    },
1262                    // ↪ U+0000 NULL
1263                    FromSet('\0') => {
1264                        // This is an unexpected-null-character parse error.
1265                        // Emit a U+FFFD REPLACEMENT CHARACTER character token.
1266                        self.bad_char_error();
1267                        self.emit_char('\u{fffd}');
1268                    },
1269                    // ↪ Anything else
1270                    //     Emit the current input character as a character token.
1271                    FromSet(character) => self.emit_char(character),
1272                    NotFromSet(characters) => self.emit_chars(characters),
1273                }
1274            },
1275
1276            // https://html.spec.whatwg.org/#script-data-escape-start-state
1277            states::ScriptDataEscapeStart(Escaped) => loop {
1278                // Consume the next input character:
1279                match get_char!(self, input) {
1280                    // ↪ U+002D HYPHEN-MINUS (-)
1281                    '-' => {
1282                        // Switch to the script data escape start dash state.
1283                        // Emit a U+002D HYPHEN-MINUS character token.
1284                        self.emit_char('-');
1285                        go!(self: to State::ScriptDataEscapeStartDash);
1286                    },
1287                    // ↪ Anything else
1288                    _ => {
1289                        // Reconsume in the script data state.
1290                        go!(self: reconsume RawData(ScriptData))
1291                    },
1292                }
1293            },
1294
1295            // https://html.spec.whatwg.org/#script-data-escape-start-dash-state
1296            states::ScriptDataEscapeStartDash => loop {
1297                // Consume the next input character:
1298                match get_char!(self, input) {
1299                    // ↪ U+002D HYPHEN-MINUS (-)
1300                    '-' => {
1301                        // Switch to the script data escaped dash dash state.
1302                        // Emit a U+002D HYPHEN-MINUS character token.
1303                        self.emit_char('-');
1304                        go!(self: to State::ScriptDataEscapedDashDash(Escaped));
1305                    },
1306                    // ↪ Anything else
1307                    _ => {
1308                        // Reconsume in the script data state.
1309                        go!(self: reconsume RawData(ScriptData))
1310                    },
1311                }
1312            },
1313
1314            // https://html.spec.whatwg.org/#script-data-escaped-state
1315            states::RawData(ScriptDataEscaped(Escaped)) => loop {
1316                // Consume the next input character:
1317                let Some(set_result) =
1318                    self.pop_except_from(input, small_char_set!('\r' '\0' '-' '<' '\n'))
1319                else {
1320                    return ProcessResult::Suspend;
1321                };
1322
1323                match set_result {
1324                    // ↪ U+002D HYPHEN-MINUS (-)
1325                    FromSet('-') => {
1326                        // Switch to the script data escaped dash state.
1327                        // Emit a U+002D HYPHEN-MINUS character token.
1328                        self.emit_char('-');
1329                        go!(self: to State::ScriptDataEscapedDash(Escaped));
1330                    },
1331                    // ↪ U+003C LESS-THAN SIGN (<)
1332                    FromSet('<') => {
1333                        // Switch to the script data escaped less-than sign state.
1334                        go!(self: to State::RawLessThanSign(ScriptDataEscaped(Escaped)))
1335                    },
1336                    // ↪ U+0000 NULL
1337                    FromSet('\0') => {
1338                        // This is an unexpected-null-character parse error.
1339                        // Emit a U+FFFD REPLACEMENT CHARACTER character token.
1340                        self.bad_char_error();
1341                        self.emit_char('\u{fffd}');
1342                    },
1343                    // ↪ Anything else
1344                    //     Emit the current input character as a character token.
1345                    FromSet(character) => self.emit_char(character),
1346                    NotFromSet(characters) => self.emit_chars(characters),
1347                }
1348            },
1349
1350            // https://html.spec.whatwg.org/#script-data-escaped-dash-state
1351            // https://html.spec.whatwg.org/#script-data-double-escaped-dash-state
1352            states::ScriptDataEscapedDash(kind) => loop {
1353                // Consume the next input character:
1354                match get_char!(self, input) {
1355                    // ↪ U+002D HYPHEN-MINUS (-)
1356                    '-' => {
1357                        // Switch to the script data escaped dash dash/script data double escaped dash dash state.
1358                        // Emit a U+002D HYPHEN-MINUS character token.
1359                        self.emit_char('-');
1360                        go!(self: to State::ScriptDataEscapedDashDash(kind));
1361                    },
1362                    // ↪ U+003C LESS-THAN SIGN (<)
1363                    '<' => {
1364                        // Switch to the script data escaped less-than sign/script data double escaped less-than sign state.
1365                        if kind == DoubleEscaped {
1366                            self.emit_char('<');
1367                        }
1368                        go!(self: to State::RawLessThanSign(ScriptDataEscaped(kind)));
1369                    },
1370                    // ↪ U+0000 NULL
1371                    '\0' => {
1372                        // This is an unexpected-null-character parse error.
1373                        // Switch to the script data escaped/script data double escaped state.
1374                        // Emit a U+FFFD REPLACEMENT CHARACTER character token.
1375                        self.bad_char_error();
1376                        self.emit_char('\u{fffd}');
1377                        go!(self: to State::RawData(ScriptDataEscaped(kind)));
1378                    },
1379                    // ↪ Anything else
1380                    c => {
1381                        // Switch to the script data escaped/script data double escaped state.
1382                        // Emit the current input character as a character token.
1383                        self.emit_char(c);
1384                        go!(self: to State::RawData(ScriptDataEscaped(kind)));
1385                    },
1386                }
1387            },
1388
1389            // https://html.spec.whatwg.org/#script-data-escaped-dash-dash-state
1390            // https://html.spec.whatwg.org/#script-data-double-escaped-dash-dash-state
1391            states::ScriptDataEscapedDashDash(kind) => loop {
1392                // Consume the next input character:
1393                match get_char!(self, input) {
1394                    // ↪ U+002D HYPHEN-MINUS (-)
1395                    '-' => {
1396                        // Emit a U+002D HYPHEN-MINUS character token.
1397                        self.emit_char('-');
1398                    },
1399                    // ↪ U+003C LESS-THAN SIGN (<)
1400                    '<' => {
1401                        // Switch to the script data escaped less-than sign/script data double escaped less-than sign state.
1402                        if kind == DoubleEscaped {
1403                            self.emit_char('<');
1404                        }
1405                        go!(self: to State::RawLessThanSign(ScriptDataEscaped(kind)));
1406                    },
1407                    // ↪ U+003E GREATER-THAN SIGN (>)
1408                    '>' => {
1409                        // Switch to the script data state.
1410                        // Emit a U+003E GREATER-THAN SIGN character token.
1411                        self.emit_char('>');
1412                        go!(self: to State::RawData(ScriptData));
1413                    },
1414                    // ↪ U+0000 NULL
1415                    '\0' => {
1416                        // This is an unexpected-null-character parse error.
1417                        // Switch to the script data escaped/script data double escaped state.
1418                        // Emit a U+FFFD REPLACEMENT CHARACTER character token.
1419                        self.bad_char_error();
1420                        self.emit_char('\u{fffd}');
1421                        go!(self: to State::RawData(ScriptDataEscaped(kind)))
1422                    },
1423                    // ↪ Anything else
1424                    character => {
1425                        // Switch to the script data escaped/script data double escaped state.
1426                        // Emit the current input character as a character token.
1427                        self.emit_char(character);
1428                        go!(self: to State::RawData(ScriptDataEscaped(kind)));
1429                    },
1430                }
1431            },
1432
1433            // https://html.spec.whatwg.org/#script-data-double-escape-end-state
1434            states::ScriptDataDoubleEscapeEnd => loop {
1435                // Consume the next input character:
1436                match get_char!(self, input) {
1437                    // ↪ U+0009 CHARACTER TABULATION (tab)
1438                    // ↪ U+000A LINE FEED (LF)
1439                    // ↪ U+000C FORM FEED (FF)
1440                    // ↪ U+0020 SPACE
1441                    // ↪ U+002F SOLIDUS (/)
1442                    // ↪ U+003E GREATER-THAN SIGN (>)
1443                    character @ ('\t' | '\n' | '\x0C' | ' ' | '/' | '>') => {
1444                        // If the temporary buffer is "script", then switch to the script data escaped state.
1445                        // Otherwise, switch to the script data double escaped state.
1446                        // Emit the current input character as a character token.
1447                        let escaped_kind = if &**self.temp_buf.borrow() == "script" {
1448                            Escaped
1449                        } else {
1450                            DoubleEscaped
1451                        };
1452                        self.emit_char(character);
1453                        go!(self: to State::RawData(ScriptDataEscaped(escaped_kind)));
1454                    },
1455
1456                    character => match lower_ascii_letter(character) {
1457                        // ↪ ASCII upper alpha
1458                        //     NOTE: This is the same as "ASCII lower alpha", except we lowercase the character first.
1459                        //     That is handled by lower_ascii_letter.
1460                        // ↪ ASCII lower alpha
1461                        Some(character_lowercase) => {
1462                            // Append the current input character to the temporary buffer.
1463                            // Emit the current input character as a character token.
1464                            go!(self: push_temp character_lowercase);
1465                            self.emit_char(character);
1466                        },
1467                        // ↪ Anything else
1468                        None => {
1469                            // Reconsume in the script data double escaped state.
1470                            go!(self: reconsume RawData(ScriptDataEscaped(DoubleEscaped)))
1471                        },
1472                    },
1473                }
1474            },
1475
1476            // https://html.spec.whatwg.org/#before-attribute-name-state
1477            states::BeforeAttributeName => loop {
1478                match get_char!(self, input) {
1479                    // ↪ U+0009 CHARACTER TABULATION (tab)
1480                    // ↪ U+000A LINE FEED (LF)
1481                    // ↪ U+000C FORM FEED (FF)
1482                    // ↪ U+0020 SPACE
1483                    '\t' | '\n' | '\x0C' | ' ' => {
1484                        // Ignore the character.
1485                    },
1486                    // U+002F SOLIDUS (/)
1487                    '/' => {
1488                        // Reconsume in the after attribute name state.
1489                        // NOTE: Instead we move to the self closing start tag,
1490                        // as that is what the "after attribute name" state would do.
1491                        go!(self: to State::SelfClosingStartTag)
1492                    },
1493                    // U+003E GREATER-THAN SIGN (>)
1494                    '>' => {
1495                        // Reconsume in the after attribute name state.
1496                        // NOTE: Instead we emit the current tag and move to the data state,
1497                        // as that is what the "after attribute name" state would do.
1498                        go!(self: emit_tag Data)
1499                    },
1500                    // NOTE: In the "anything else" case we should reconsume in the attribute name state,
1501                    // but instead of reconsuming we inline what that state *would* do here.
1502                    '\0' => {
1503                        self.bad_char_error();
1504                        go!(self: create_attr '\u{fffd}'; to State::AttributeName)
1505                    },
1506                    character => match lower_ascii_letter(character) {
1507                        Some(character) => {
1508                            go!(self: create_attr character; to State::AttributeName)
1509                        },
1510                        None => {
1511                            if matches!(character, '"' | '\'' | '<' | '=') {
1512                                self.bad_char_error();
1513                            }
1514
1515                            go!(self: create_attr character; to State::AttributeName);
1516                        },
1517                    },
1518                }
1519            },
1520
1521            // https://html.spec.whatwg.org/#attribute-name-state
1522            states::AttributeName => loop {
1523                // Consume the next input character:
1524                match get_char!(self, input) {
1525                    // ↪ U+0009 CHARACTER TABULATION (tab)
1526                    // ↪ U+000A LINE FEED (LF)
1527                    // ↪ U+000C FORM FEED (FF)
1528                    // ↪ U+0020 SPACE
1529                    '\t' | '\n' | '\x0C' | ' ' => {
1530                        // Reconsume in the after attribute name state.
1531                        // NOTE: Instead we move to the after attribute name state and ignore
1532                        // the character, as that state would ignore it as well.
1533                        go!(self: to State::AfterAttributeName)
1534                    },
1535                    // ↪ U+002F SOLIDUS (/)
1536                    '/' => {
1537                        // Reconsume in the after attribute name state.
1538                        // NOTE: Instead we move to the self closing start tag state, as that is
1539                        // what the after attribute name state would do when it encounters a '/'.
1540                        go!(self: to State::SelfClosingStartTag)
1541                    },
1542                    // ↪ U+003E GREATER-THAN SIGN (>)
1543                    '>' => {
1544                        // Reconsume in the after attribute name state.
1545                        // NOTE: Instead we move to the data state, as that is
1546                        // what the after attribute name state would do when it encounters a '>'.
1547                        go!(self: emit_tag Data)
1548                    },
1549                    // ↪ U+003D EQUALS SIGN (=)
1550                    '=' => {
1551                        // Switch to the before attribute value state.
1552                        go!(self: to State::BeforeAttributeValue)
1553                    },
1554                    // ↪ U+0000 NULL
1555                    '\0' => {
1556                        // This is an unexpected-null-character parse error.
1557                        // Append a U+FFFD REPLACEMENT CHARACTER character to the current attribute's name.
1558                        self.bad_char_error();
1559                        go!(self: push_name '\u{fffd}')
1560                    },
1561                    character => match lower_ascii_letter(character) {
1562                        // ↪ ASCII upper alpha
1563                        Some(character_lowercase) => {
1564                            // Append the lowercase version of the current input character
1565                            // (add 0x0020 to the character's code point) to the current attribute's name.
1566                            go!(self: push_name character_lowercase)
1567                        },
1568                        None => {
1569                            // ↪ U+0022 QUOTATION MARK (")
1570                            // ↪ U+0027 APOSTROPHE (')
1571                            // ↪ U+003C LESS-THAN SIGN (<)
1572                            if matches!(character, '"' | '\'' | '<') {
1573                                // This is an unexpected-character-in-attribute-name parse error.
1574                                // Treat it as per the "anything else" entry below.
1575                                self.bad_char_error();
1576                            }
1577                            // ↪ Anything else
1578                            // Append the current input character to the current attribute's name.
1579                            go!(self: push_name character);
1580                        },
1581                    },
1582                }
1583            },
1584
1585            // https://html.spec.whatwg.org/#after-attribute-name-state
1586            states::AfterAttributeName => loop {
1587                // Consume the next input character:
1588                match get_char!(self, input) {
1589                    // ↪ U+0009 CHARACTER TABULATION (tab)
1590                    // ↪ U+000A LINE FEED (LF)
1591                    // ↪ U+000C FORM FEED (FF)
1592                    // ↪ U+0020 SPACE
1593                    '\t' | '\n' | '\x0C' | ' ' => {
1594                        // Ignore the character.
1595                    },
1596                    // ↪ U+002F SOLIDUS (/)
1597                    '/' => {
1598                        // Switch to the self-closing start tag state.
1599                        go!(self: to State::SelfClosingStartTag)
1600                    },
1601                    // ↪ U+003D EQUALS SIGN (=)
1602                    '=' => {
1603                        // Switch to the before attribute value state.
1604                        go!(self: to State::BeforeAttributeValue)
1605                    },
1606                    // ↪ U+003E GREATER-THAN SIGN (>)
1607                    '>' => {
1608                        // Switch to the data state. Emit the current tag token.
1609                        go!(self: emit_tag Data)
1610                    },
1611                    // NOTE: The "anything else" match arm in the specification reconsumes
1612                    // the input in the attribute name state. Instead of reconsuming we inline
1613                    // what the attribute name state *would* do, and the move to it.
1614                    '\0' => {
1615                        self.bad_char_error();
1616                        go!(self: create_attr '\u{fffd}'; to State::AttributeName)
1617                    },
1618                    character => match lower_ascii_letter(character) {
1619                        Some(character_lowercase) => {
1620                            go!(self: create_attr character_lowercase; to State::AttributeName)
1621                        },
1622                        None => {
1623                            if matches!(character, '"' | '\'' | '<') {
1624                                self.bad_char_error();
1625                            }
1626
1627                            go!(self: create_attr character; to State::AttributeName);
1628                        },
1629                    },
1630                }
1631            },
1632
1633            // https://html.spec.whatwg.org/#before-attribute-value-state
1634            // Use peek so we can handle the first attr character along with the rest,
1635            // hopefully in the same zero-copy buffer.
1636            states::BeforeAttributeValue => loop {
1637                // Consume the next input character:
1638                match peek!(self, input) {
1639                    // ↪ U+0009 CHARACTER TABULATION (tab)
1640                    // ↪ U+000A LINE FEED (LF)
1641                    // ↪ U+000C FORM FEED (FF)
1642                    // ↪ U+0020 SPACE
1643                    '\t' | '\n' | '\r' | '\x0C' | ' ' => {
1644                        // Ignore the character.
1645                        go!(self: discard_char input)
1646                    },
1647                    // ↪ U+0022 QUOTATION MARK (")
1648                    '"' => {
1649                        // Switch to the attribute value (double-quoted) state.
1650                        go!(self: discard_char input; to State::AttributeValue(DoubleQuoted))
1651                    },
1652                    // ↪ U+0027 APOSTROPHE (')
1653                    '\'' => {
1654                        // Switch to the attribute value (single-quoted) state.
1655                        go!(self: discard_char input; to State::AttributeValue(SingleQuoted))
1656                    },
1657                    // ↪ U+003E GREATER-THAN SIGN (>)
1658                    '>' => {
1659                        // This is a missing-attribute-value parse error.
1660                        // Switch to the data state.
1661                        // Emit the current tag token.
1662                        go!(self: discard_char input);
1663                        self.bad_char_error();
1664                        go!(self: emit_tag Data)
1665                    },
1666                    // ↪ Anything else
1667                    _ => {
1668                        // Reconsume in the attribute value (unquoted) state.
1669                        go!(self: to State::AttributeValue(Unquoted))
1670                    },
1671                }
1672            },
1673
1674            // https://html.spec.whatwg.org/#attribute-value-(double-quoted)-state
1675            states::AttributeValue(DoubleQuoted) => loop {
1676                // Consume the next input character:
1677                let Some(set_result) =
1678                    self.pop_except_from(input, small_char_set!('\r' '"' '&' '\0' '\n'))
1679                else {
1680                    return ProcessResult::Suspend;
1681                };
1682
1683                match set_result {
1684                    // ↪ U+0022 QUOTATION MARK (")
1685                    FromSet('"') => {
1686                        // Switch to the after attribute value (quoted) state.
1687                        go!(self: to State::AfterAttributeValueQuoted)
1688                    },
1689                    // ↪ U+0026 AMPERSAND (&)
1690                    FromSet('&') => {
1691                        // Set the return state to the attribute value (double-quoted) state.
1692                        // Switch to the character reference state.
1693                        go!(self: consume_char_ref)
1694                    },
1695                    // ↪ U+0000 NULL
1696                    FromSet('\0') => {
1697                        // This is an unexpected-null-character parse error.
1698                        // Append a U+FFFD REPLACEMENT CHARACTER character to the current attribute's value.
1699                        self.bad_char_error();
1700                        go!(self: push_value '\u{fffd}')
1701                    },
1702                    // ↪ Anything else
1703                    //     Append the current input character to the current attribute's value.
1704                    FromSet(character) => go!(self: push_value character),
1705                    NotFromSet(ref characters) => go!(self: append_value characters),
1706                }
1707            },
1708
1709            // https://html.spec.whatwg.org/#attribute-value-(single-quoted)-state
1710            states::AttributeValue(SingleQuoted) => loop {
1711                // Consume the next input character:
1712                let Some(set_result) =
1713                    self.pop_except_from(input, small_char_set!('\r' '\'' '&' '\0' '\n'))
1714                else {
1715                    return ProcessResult::Suspend;
1716                };
1717
1718                match set_result {
1719                    // ↪ U+0027 APOSTROPHE (')
1720                    FromSet('\'') => {
1721                        // Switch to the after attribute value (quoted) state.
1722                        go!(self: to State::AfterAttributeValueQuoted)
1723                    },
1724                    // ↪ U+0026 AMPERSAND (&)
1725                    FromSet('&') => {
1726                        // Set the return state to the attribute value (single-quoted) state.
1727                        // Switch to the character reference state.
1728                        go!(self: consume_char_ref)
1729                    },
1730                    // ↪ U+0000 NULL
1731                    FromSet('\0') => {
1732                        // This is an unexpected-null-character parse error.
1733                        // Append a U+FFFD REPLACEMENT CHARACTER character to the current attribute's value.
1734                        self.bad_char_error();
1735                        go!(self: push_value '\u{fffd}')
1736                    },
1737                    // ↪ Anything else
1738                    //     Append the current input character to the current attribute's value.
1739                    FromSet(character) => go!(self: push_value character),
1740                    NotFromSet(ref characters) => go!(self: append_value characters),
1741                }
1742            },
1743
1744            // https://html.spec.whatwg.org/#attribute-value-(unquoted)-state
1745            states::AttributeValue(Unquoted) => loop {
1746                // Consume the next input character:
1747                let Some(set_result) = self.pop_except_from(
1748                    input,
1749                    small_char_set!('\r' '\t' '\n' '\x0C' ' ' '&' '>' '\0'),
1750                ) else {
1751                    return ProcessResult::Suspend;
1752                };
1753
1754                match set_result {
1755                    // ↪ U+0009 CHARACTER TABULATION (tab)
1756                    // ↪ U+000A LINE FEED (LF)
1757                    // ↪ U+000C FORM FEED (FF)
1758                    // ↪ U+0020 SPACE
1759                    FromSet('\t') | FromSet('\n') | FromSet('\x0C') | FromSet(' ') => {
1760                        // Switch to the before attribute name state.
1761                        go!(self: to State::BeforeAttributeName)
1762                    },
1763                    // ↪ U+0026 AMPERSAND (&)
1764                    FromSet('&') => {
1765                        // Set the return state to the attribute value (unquoted) state.
1766                        // Switch to the character reference state.
1767                        go!(self: consume_char_ref)
1768                    },
1769                    // ↪ U+003E GREATER-THAN SIGN (>)
1770                    FromSet('>') => {
1771                        // Switch to the data state.
1772                        // Emit the current tag token.
1773                        go!(self: emit_tag Data)
1774                    },
1775                    // ↪ U+0000 NULL
1776                    FromSet('\0') => {
1777                        // This is an unexpected-null-character parse error.
1778                        // Append a U+FFFD REPLACEMENT CHARACTER character to the current attribute's value.
1779                        self.bad_char_error();
1780                        go!(self: push_value '\u{fffd}')
1781                    },
1782                    FromSet(c) => {
1783                        // ↪ U+0022 QUOTATION MARK (")
1784                        // ↪ U+0027 APOSTROPHE (')
1785                        // ↪ U+003C LESS-THAN SIGN (<)
1786                        // ↪ U+003D EQUALS SIGN (=)
1787                        // ↪ U+0060 GRAVE ACCENT (`)
1788                        if matches!(c, '"' | '\'' | '<' | '=' | '`') {
1789                            // This is an unexpected-character-in-unquoted-attribute-value parse error.
1790                            // Treat it as per the "anything else" entry below.
1791                            self.bad_char_error();
1792                        }
1793                        // ↪ Anything else
1794                        //     Append the current input character to the current attribute's value.
1795                        go!(self: push_value c);
1796                    },
1797                    // ↪ Anything else
1798                    NotFromSet(ref characters) => {
1799                        // Append the current input character to the current attribute's value.
1800                        go!(self: append_value characters)
1801                    },
1802                }
1803            },
1804
1805            // https://html.spec.whatwg.org/#after-attribute-value-(quoted)-state
1806            states::AfterAttributeValueQuoted => loop {
1807                // Consume the next input character:
1808                match get_char!(self, input) {
1809                    // ↪ U+0009 CHARACTER TABULATION (tab)
1810                    // ↪ U+000A LINE FEED (LF)
1811                    // ↪ U+000C FORM FEED (FF)
1812                    // ↪ U+0020 SPACE
1813                    '\t' | '\n' | '\x0C' | ' ' => {
1814                        // Switch to the before attribute name state.
1815                        go!(self: to State::BeforeAttributeName)
1816                    },
1817                    // ↪ U+002F SOLIDUS (/)
1818                    '/' => {
1819                        // Switch to the self-closing start tag state.
1820                        go!(self: to State::SelfClosingStartTag)
1821                    },
1822                    // ↪ U+003E GREATER-THAN SIGN (>)
1823                    '>' => {
1824                        // Switch to the data state. Emit the current tag token.
1825                        go!(self: emit_tag Data)
1826                    },
1827                    // ↪ Anything else
1828                    _ => {
1829                        // This is a missing-whitespace-between-attributes parse error.
1830                        // Reconsume in the before attribute name state.
1831                        self.bad_char_error();
1832                        go!(self: reconsume BeforeAttributeName)
1833                    },
1834                }
1835            },
1836
1837            // https://html.spec.whatwg.org/#self-closing-start-tag-state
1838            states::SelfClosingStartTag => loop {
1839                // Consume the next input character:
1840                match get_char!(self, input) {
1841                    // ↪ U+003E GREATER-THAN SIGN (>)
1842                    '>' => {
1843                        // Set the self-closing flag of the current tag token.
1844                        // Switch to the data state. Emit the current tag token.
1845                        self.current_tag_self_closing.set(true);
1846                        go!(self: emit_tag Data);
1847                    },
1848                    // ↪ Anything else
1849                    _ => {
1850                        // This is an unexpected-solidus-in-tag parse error.
1851                        // Reconsume in the before attribute name state.
1852                        self.bad_char_error();
1853                        go!(self: reconsume BeforeAttributeName)
1854                    },
1855                }
1856            },
1857
1858            //§ bogus-comment-state
1859            states::BogusComment => loop {
1860                // Consume the next input character:
1861                match get_char!(self, input) {
1862                    // ↪ U+003E GREATER-THAN SIGN (>)
1863                    '>' => {
1864                        // Switch to the data state. Emit the current comment token.
1865                        go!(self: emit_comment; to State::Data)
1866                    },
1867                    // ↪ U+0000 NULL
1868                    '\0' => {
1869                        // This is an unexpected-null-character parse error.
1870                        // Append a U+FFFD REPLACEMENT CHARACTER character to the comment token's data.
1871                        self.bad_char_error();
1872                        go!(self: push_comment '\u{fffd}')
1873                    },
1874                    // ↪ Anything else
1875                    character => {
1876                        // Append the current input character to the comment token's data.
1877                        go!(self: push_comment character)
1878                    },
1879                }
1880            },
1881
1882            // https://html.spec.whatwg.org/#markup-declaration-open-state
1883            states::MarkupDeclarationOpen => loop {
1884                // If the next few characters are:
1885                // ↪ Two U+002D HYPHEN-MINUS characters (-)
1886                if eat_exact!(self, input, "--") {
1887                    go!(self: clear_comment; to State::CommentStart);
1888                }
1889                // ↪ ASCII case-insensitive match for "DOCTYPE"
1890                else if eat!(self, input, "doctype") {
1891                    go!(self: to State::Doctype);
1892                } else {
1893                    // ↪ "[CDATA["
1894                    if self
1895                        .sink
1896                        .adjusted_current_node_present_but_not_in_html_namespace()
1897                        && eat_exact!(self, input, "[CDATA[")
1898                    {
1899                        // Consume those characters.
1900                        // If there is an adjusted current node and it is not an element in the HTML namespace,
1901                        // then switch to the CDATA section state. Otherwise, this is a cdata-in-html-content parse error.
1902                        // Create a comment token whose data is "[CDATA[". Switch to the bogus comment state.
1903                        // FIXME: Create that comment token.
1904                        go!(self: clear_temp; to State::CdataSection);
1905                    }
1906
1907                    // ↪ Anything else
1908                    //     This is an incorrectly-opened-comment parse error.
1909                    //     Create a comment token whose data is the empty string.
1910                    //     Switch to the bogus comment state (don't consume anything in the current state).
1911                    // FIXME: Create that comment token.
1912                    self.bad_char_error();
1913                    go!(self: clear_comment; to State::BogusComment);
1914                }
1915            },
1916
1917            // https://html.spec.whatwg.org/#comment-start-state
1918            states::CommentStart => loop {
1919                // Consume the next input character:
1920                match get_char!(self, input) {
1921                    // ↪ U+002D HYPHEN-MINUS (-)
1922                    '-' => {
1923                        // Switch to the comment start dash state.
1924                        go!(self: to State::CommentStartDash)
1925                    },
1926                    // ↪ U+003E GREATER-THAN SIGN (>)
1927                    '>' => {
1928                        // This is an abrupt-closing-of-empty-comment parse error.
1929                        // Switch to the data state.
1930                        // Emit the current comment token.
1931                        self.bad_char_error();
1932                        go!(self: emit_comment; to State::Data)
1933                    },
1934                    // NOTE: The "anything else" case in the specification reconsumes the character in the
1935                    // comment state, instead we inline what the comment state *would* do if it encountered
1936                    // that character.
1937                    '\0' => {
1938                        self.bad_char_error();
1939                        go!(self: push_comment '\u{fffd}'; to State::Comment)
1940                    },
1941                    character => go!(self: push_comment character; to State::Comment),
1942                }
1943            },
1944
1945            // https://html.spec.whatwg.org/#comment-start-dash-state
1946            states::CommentStartDash => loop {
1947                // Consume the next input character:
1948                match get_char!(self, input) {
1949                    // ↪ U+002D HYPHEN-MINUS (-)
1950                    '-' => {
1951                        // Switch to the comment end state.
1952                        go!(self: to State::CommentEnd)
1953                    },
1954                    // ↪ U+003E GREATER-THAN SIGN (>)
1955                    '>' => {
1956                        // This is an abrupt-closing-of-empty-comment parse error.
1957                        // Switch to the data state.
1958                        // Emit the current comment token.
1959                        self.bad_char_error();
1960                        go!(self: emit_comment; to State::Data)
1961                    },
1962                    // NOTE: The "anything else" case in the specification reconsumes the character in the
1963                    // comment state, instead we inline what the comment state *would* do if it encountered
1964                    // that character.
1965                    '\0' => {
1966                        self.bad_char_error();
1967                        go!(self: append_comment "-\u{fffd}"; to State::Comment)
1968                    },
1969                    character => {
1970                        go!(self: push_comment '-'; push_comment character; to State::Comment)
1971                    },
1972                }
1973            },
1974
1975            // https://html.spec.whatwg.org/#comment-state
1976            states::Comment => loop {
1977                // Consume the next input character:
1978                match get_char!(self, input) {
1979                    // ↪ U+003C LESS-THAN SIGN (<)
1980                    c @ '<' => {
1981                        // Append the current input character to the comment token's data.
1982                        // Switch to the comment less-than sign state.
1983                        go!(self: push_comment c; to State::CommentLessThanSign)
1984                    },
1985                    // ↪ U+002D HYPHEN-MINUS (-)
1986                    '-' => {
1987                        // Switch to the comment end dash state.
1988                        go!(self: to State::CommentEndDash)
1989                    },
1990                    // ↪ U+0000 NULL
1991                    '\0' => {
1992                        // This is an unexpected-null-character parse error.
1993                        // Append a U+FFFD REPLACEMENT CHARACTER character to the comment token's data.
1994                        self.bad_char_error();
1995                        go!(self: push_comment '\u{fffd}')
1996                    },
1997                    // ↪ Anything else
1998                    character => {
1999                        // Append the current input character to the comment token's data.
2000                        go!(self: push_comment character)
2001                    },
2002                }
2003            },
2004
2005            // https://html.spec.whatwg.org/#comment-less-than-sign-state
2006            states::CommentLessThanSign => loop {
2007                // Consume the next input character:
2008                match get_char!(self, input) {
2009                    // ↪ U+0021 EXCLAMATION MARK (!)
2010                    c @ '!' => {
2011                        // Append the current input character to the comment token's data.
2012                        // Switch to the comment less-than sign bang state.
2013                        go!(self: push_comment c; to State::CommentLessThanSignBang)
2014                    },
2015                    // ↪ U+003C LESS-THAN SIGN (<)
2016                    c @ '<' => {
2017                        // Append the current input character to the comment token's data.
2018                        go!(self: push_comment c)
2019                    },
2020                    // ↪ Anything else
2021                    _ => {
2022                        // Reconsume in the comment state.
2023                        go!(self: reconsume Comment)
2024                    },
2025                }
2026            },
2027
2028            // https://html.spec.whatwg.org/#comment-less-than-sign-bang-state
2029            states::CommentLessThanSignBang => loop {
2030                // Consume the next input character:
2031                match get_char!(self, input) {
2032                    // ↪ U+002D HYPHEN-MINUS (-)
2033                    '-' => {
2034                        // Switch to the comment less-than sign bang dash state.
2035                        go!(self: to State::CommentLessThanSignBangDash)
2036                    },
2037                    // ↪ Anything else
2038                    _ => {
2039                        // Reconsume in the comment state.
2040                        go!(self: reconsume Comment)
2041                    },
2042                }
2043            },
2044
2045            // https://html.spec.whatwg.org/#comment-less-than-sign-bang-dash-state
2046            states::CommentLessThanSignBangDash => loop {
2047                // Consume the next input character:
2048                match get_char!(self, input) {
2049                    // ↪ U+002D HYPHEN-MINUS (-)
2050                    '-' => {
2051                        // Switch to the comment less-than sign bang dash dash state.
2052                        go!(self: to State::CommentLessThanSignBangDashDash)
2053                    },
2054                    // ↪ Anything else
2055                    _ => {
2056                        // Reconsume in the comment end dash state.
2057                        go!(self: reconsume CommentEndDash)
2058                    },
2059                }
2060            },
2061
2062            // https://html.spec.whatwg.org/#comment-less-than-sign-bang-dash-dash-state
2063            states::CommentLessThanSignBangDashDash => loop {
2064                match get_char!(self, input) {
2065                    // ↪ U+003E GREATER-THAN SIGN (>)
2066                    '>' => {
2067                        // Reconsume in the comment end state.
2068                        go!(self: reconsume CommentEnd)
2069                    },
2070                    // ↪ Anything else
2071                    _ => {
2072                        // This is a nested-comment parse error.
2073                        // Reconsume in the comment end state.
2074                        self.bad_char_error();
2075                        go!(self: reconsume CommentEnd)
2076                    },
2077                }
2078            },
2079
2080            // https://html.spec.whatwg.org/#comment-end-dash-state
2081            states::CommentEndDash => loop {
2082                // Consume the next input character:
2083                match get_char!(self, input) {
2084                    // ↪ U+002D HYPHEN-MINUS (-)
2085                    '-' => {
2086                        // Switch to the comment end state.
2087                        go!(self: to State::CommentEnd)
2088                    },
2089                    // NOTE: The "anything else" case in the specification reconsumes the character in the
2090                    // comment state. Instead we inline what the comment state *would* do here.
2091                    '\0' => {
2092                        self.bad_char_error();
2093                        go!(self: append_comment "-\u{fffd}"; to State::Comment)
2094                    },
2095                    character => {
2096                        go!(self: push_comment '-'; push_comment character; to State::Comment)
2097                    },
2098                }
2099            },
2100
2101            // https://html.spec.whatwg.org/#comment-end-state
2102            states::CommentEnd => loop {
2103                // Consume the next input character:
2104                match get_char!(self, input) {
2105                    // ↪ U+003E GREATER-THAN SIGN (>)
2106                    '>' => {
2107                        // Switch to the data state.
2108                        // Emit the current comment token.
2109                        go!(self: emit_comment; to State::Data)
2110                    },
2111                    // ↪ U+0021 EXCLAMATION MARK (!)
2112                    '!' => {
2113                        // Switch to the comment end bang state.
2114                        go!(self: to State::CommentEndBang)
2115                    },
2116                    // ↪ U+002D HYPHEN-MINUS (-)
2117                    '-' => {
2118                        // Append a U+002D HYPHEN-MINUS character (-) to the comment token's data.
2119                        go!(self: push_comment '-')
2120                    },
2121                    // ↪ Anything else
2122                    _ => {
2123                        // Append two U+002D HYPHEN-MINUS characters (-) to the comment token's data.
2124                        // Reconsume in the comment state.
2125                        go!(self: append_comment "--"; reconsume Comment)
2126                    },
2127                }
2128            },
2129
2130            // https://html.spec.whatwg.org/#comment-end-bang-state
2131            states::CommentEndBang => loop {
2132                // Consume the next input character:
2133                match get_char!(self, input) {
2134                    // ↪ U+002D HYPHEN-MINUS (-)
2135                    '-' => go!(self: append_comment "--!"; to State::CommentEndDash),
2136                    // ↪ U+003E GREATER-THAN SIGN (>)
2137                    '>' => {
2138                        self.bad_char_error();
2139                        go!(self: emit_comment; to State::Data)
2140                    },
2141                    // NOTE: The "anything else" case in the specification reconsumes the character in the
2142                    // comment state. Instead we inline what the comment state *would* do here.
2143                    '\0' => {
2144                        self.bad_char_error();
2145                        go!(self: append_comment "--!\u{fffd}"; to State::Comment)
2146                    },
2147                    character => {
2148                        go!(self: append_comment "--!"; push_comment character; to State::Comment)
2149                    },
2150                }
2151            },
2152
2153            // https://html.spec.whatwg.org/#doctype-state
2154            states::Doctype => loop {
2155                // Consume the next input character:
2156                match get_char!(self, input) {
2157                    // ↪ U+0009 CHARACTER TABULATION (tab)
2158                    // ↪ U+000A LINE FEED (LF)
2159                    // ↪ U+000C FORM FEED (FF)
2160                    // ↪ U+0020 SPACE
2161                    '\t' | '\n' | '\x0C' | ' ' => {
2162                        // Switch to the before DOCTYPE name state.
2163                        go!(self: to State::BeforeDoctypeName)
2164                    },
2165                    // ↪ U+003E GREATER-THAN SIGN (>)
2166                    '>' => {
2167                        // Reconsume in the before DOCTYPE name state.
2168                        go!(self: reconsume BeforeDoctypeName)
2169                    },
2170                    // ↪ Anything else
2171                    _ => {
2172                        // This is a missing-whitespace-before-doctype-name parse error.
2173                        // Reconsume in the before DOCTYPE name state.
2174                        self.bad_char_error();
2175                        go!(self: reconsume BeforeDoctypeName)
2176                    },
2177                }
2178            },
2179
2180            // https://html.spec.whatwg.org/#before-doctype-name-state
2181            states::BeforeDoctypeName => loop {
2182                // Consume the next input character:
2183                match get_char!(self, input) {
2184                    // ↪ U+0009 CHARACTER TABULATION (tab)
2185                    // ↪ U+000A LINE FEED (LF)
2186                    // ↪ U+000C FORM FEED (FF)
2187                    // ↪ U+0020 SPACE
2188                    '\t' | '\n' | '\x0C' | ' ' => {
2189                        // Ignore the character.
2190                    },
2191                    // ↪ U+0000 NULL
2192                    '\0' => {
2193                        // This is an unexpected-null-character parse error.
2194                        // Create a new DOCTYPE token.
2195                        // Set the token's name to a U+FFFD REPLACEMENT CHARACTER character.
2196                        // Switch to the DOCTYPE name state.
2197                        self.bad_char_error();
2198                        go!(self: create_doctype; push_doctype_name '\u{fffd}'; to State::DoctypeName)
2199                    },
2200                    // ↪ U+003E GREATER-THAN SIGN (>)
2201                    '>' => {
2202                        // This is a missing-doctype-name parse error.
2203                        // Create a new DOCTYPE token.
2204                        // Set its force-quirks flag to on.
2205                        // Switch to the data state.
2206                        // Emit the current token.
2207                        self.bad_char_error();
2208                        go!(self: create_doctype; force_quirks; emit_doctype; to State::Data)
2209                    },
2210                    // ↪ ASCII upper alpha
2211                    //     NOTE: This is the same as "anythign else", except we use the lowercase version of the character.
2212                    // ↪ Anything else
2213                    character => {
2214                        // Create a new DOCTYPE token.
2215                        // Set the token's name to the current input character.
2216                        // Switch to the DOCTYPE name state.
2217                        go!(self: create_doctype; push_doctype_name (character.to_ascii_lowercase());
2218                                  to State::DoctypeName)
2219                    },
2220                }
2221            },
2222
2223            // https://html.spec.whatwg.org/#doctype-name-state
2224            states::DoctypeName => loop {
2225                // Consume the next input character:
2226                match get_char!(self, input) {
2227                    // ↪ U+0009 CHARACTER TABULATION (tab)
2228                    // ↪ U+000A LINE FEED (LF)
2229                    // ↪ U+000C FORM FEED (FF)
2230                    // ↪ U+0020 SPACE
2231                    '\t' | '\n' | '\x0C' | ' ' => {
2232                        // Switch to the after DOCTYPE name state.
2233                        go!(self: clear_temp; to State::AfterDoctypeName)
2234                    },
2235                    // ↪ U+003E GREATER-THAN SIGN (>)
2236                    '>' => {
2237                        // Switch to the data state. Emit the current DOCTYPE token.
2238                        go!(self: emit_doctype; to State::Data)
2239                    },
2240                    // ↪ U+0000 NULL
2241                    '\0' => {
2242                        // This is an unexpected-null-character parse error.
2243                        // Append a U+FFFD REPLACEMENT CHARACTER character to the current DOCTYPE token's name.
2244                        self.bad_char_error();
2245                        go!(self: push_doctype_name '\u{fffd}')
2246                    },
2247                    // ↪ ASCII upper alpha
2248                    //     NOTE: This is the same as "anythign else", except we use the lowercase version of the character.
2249                    // ↪ Anything else
2250                    character => {
2251                        // Append the current input character to the current DOCTYPE token's name.
2252                        go!(self: push_doctype_name (character.to_ascii_lowercase()))
2253                    },
2254                }
2255            },
2256
2257            // https://html.spec.whatwg.org/#after-doctype-name-state
2258            states::AfterDoctypeName => loop {
2259                // NOTE: We move some steps out of the "anything else" case to the front for convenience.
2260
2261                // If the six characters starting from the current input character are an
2262                // ASCII case-insensitive match for "PUBLIC", then consume those characters
2263                // and switch to the after DOCTYPE public keyword state.
2264                if eat!(self, input, "public") {
2265                    go!(self: to State::AfterDoctypeKeyword(Public));
2266                }
2267                // Otherwise, if the six characters starting from the current input character
2268                // are an ASCII case-insensitive match for "SYSTEM", then consume those characters
2269                // and switch to the after DOCTYPE system keyword state.
2270                else if eat!(self, input, "system") {
2271                    go!(self: to State::AfterDoctypeKeyword(System));
2272                } else {
2273                    // Consume the next input character:
2274                    match get_char!(self, input) {
2275                        // ↪ U+0009 CHARACTER TABULATION (tab)
2276                        // ↪ U+000A LINE FEED (LF)
2277                        // ↪ U+000C FORM FEED (FF)
2278                        // ↪ U+0020 SPACE
2279                        '\t' | '\n' | '\x0C' | ' ' => {
2280                            // Ignore the character.
2281                        },
2282                        // ↪ U+003E GREATER-THAN SIGN (>)
2283                        '>' => {
2284                            // Switch to the data state. Emit the current DOCTYPE token.
2285                            go!(self: emit_doctype; to State::Data)
2286                        },
2287                        // ↪ Anything else
2288                        _ => {
2289                            // Otherwise, this is an invalid-character-sequence-after-doctype-name parse error.
2290                            // Set the current DOCTYPE token's force-quirks flag to on.
2291                            // Reconsume in the bogus DOCTYPE state.
2292                            self.bad_char_error();
2293                            go!(self: force_quirks; reconsume BogusDoctype)
2294                        },
2295                    }
2296                }
2297            },
2298
2299            // https://html.spec.whatwg.org/#after-doctype-public-keyword-state
2300            // https://html.spec.whatwg.org/#after-doctype-system-keyword-state
2301            states::AfterDoctypeKeyword(kind) => loop {
2302                match get_char!(self, input) {
2303                    // ↪ U+0009 CHARACTER TABULATION (tab)
2304                    // ↪ U+000A LINE FEED (LF)
2305                    // ↪ U+000C FORM FEED (FF)
2306                    // ↪ U+0020 SPACE
2307                    '\t' | '\n' | '\x0C' | ' ' => {
2308                        // Switch to the before DOCTYPE public/system identifier state.
2309                        go!(self: to State::BeforeDoctypeIdentifier(kind))
2310                    },
2311                    // ↪ U+0022 QUOTATION MARK (")
2312                    '"' => {
2313                        // This is a missing-whitespace-after-doctype-public/system-keyword parse error.
2314                        // Set the current DOCTYPE token's public/system identifier to the empty string
2315                        // (not missing), then switch to the DOCTYPE public/system identifier (double-quoted)
2316                        // state.
2317                        self.bad_char_error();
2318                        go!(self: clear_doctype_id kind; to State::DoctypeIdentifierDoubleQuoted(kind))
2319                    },
2320                    // ↪ U+0027 APOSTROPHE (')
2321                    '\'' => {
2322                        // This is a missing-whitespace-after-doctype-public-keyword parse error.
2323                        // Set the current DOCTYPE token's public identifier to the empty string
2324                        // (not missing), then switch to the DOCTYPE public/system identifier (single-quoted)
2325                        // state.
2326                        self.bad_char_error();
2327                        go!(self: clear_doctype_id kind; to State::DoctypeIdentifierSingleQuoted(kind))
2328                    },
2329                    // ↪ U+003E GREATER-THAN SIGN (>)
2330                    '>' => {
2331                        // This is a missing-doctype-public-identifier parse error.
2332                        // Set the current DOCTYPE token's force-quirks flag to on.
2333                        // Switch to the data state.
2334                        // Emit the current DOCTYPE token.
2335                        self.bad_char_error();
2336                        go!(self: force_quirks; emit_doctype; to State::Data)
2337                    },
2338                    // ↪ Anything else
2339                    _ => {
2340                        // This is a missing-quote-before-doctype-public-identifier parse error.
2341                        // Set the current DOCTYPE token's force-quirks flag to on.
2342                        // Reconsume in the bogus DOCTYPE state.
2343                        self.bad_char_error();
2344                        go!(self: force_quirks; reconsume BogusDoctype)
2345                    },
2346                }
2347            },
2348
2349            // https://html.spec.whatwg.org/#before-doctype-public-identifier-state
2350            // https://html.spec.whatwg.org/#before-doctype-system-identifier-state
2351            states::BeforeDoctypeIdentifier(kind) => loop {
2352                // Consume the next input character:
2353                match get_char!(self, input) {
2354                    // ↪ U+0009 CHARACTER TABULATION (tab)
2355                    // ↪ U+000A LINE FEED (LF)
2356                    // ↪ U+000C FORM FEED (FF)
2357                    // ↪ U+0020 SPACE
2358                    '\t' | '\n' | '\x0C' | ' ' => {
2359                        // Ignore the character.
2360                    },
2361                    // ↪ U+0022 QUOTATION MARK (")
2362                    '"' => {
2363                        // Set the current DOCTYPE token's public/system identifier to the empty string
2364                        // (not missing), then switch to the DOCTYPE public/system identifier (double-quoted)
2365                        // state.
2366                        go!(self: clear_doctype_id kind; to State::DoctypeIdentifierDoubleQuoted(kind))
2367                    },
2368                    // ↪ U+0027 APOSTROPHE (')
2369                    '\'' => {
2370                        // Set the current DOCTYPE token's public/systen identifier to the empty string
2371                        // (not missing), then switch to the DOCTYPE public/system identifier (single-quoted)
2372                        // state.
2373                        go!(self: clear_doctype_id kind; to State::DoctypeIdentifierSingleQuoted(kind))
2374                    },
2375                    // ↪ U+003E GREATER-THAN SIGN (>)
2376                    '>' => {
2377                        // This is a missing-doctype-public/system-identifier parse error.
2378                        // Set the current DOCTYPE token's force-quirks flag to on.
2379                        // Switch to the data state.
2380                        // Emit the current DOCTYPE token.
2381                        self.bad_char_error();
2382                        go!(self: force_quirks; emit_doctype; to State::Data)
2383                    },
2384                    // ↪ Anything else
2385                    _ => {
2386                        // This is a missing-quote-before-doctype-public/system-identifier parse error.
2387                        // Set the current DOCTYPE token's force-quirks flag to on.
2388                        // Reconsume in the bogus DOCTYPE state.
2389                        self.bad_char_error();
2390                        go!(self: force_quirks; reconsume BogusDoctype)
2391                    },
2392                }
2393            },
2394
2395            // https://html.spec.whatwg.org/#doctype-public-identifier-(double-quoted)-state
2396            // https://html.spec.whatwg.org/#doctype-system-identifier-(double-quoted)-state
2397            states::DoctypeIdentifierDoubleQuoted(kind) => loop {
2398                // Consume the next input character:
2399                match get_char!(self, input) {
2400                    // ↪ U+0022 QUOTATION MARK (")
2401                    '"' => {
2402                        // Switch to the after DOCTYPE public/system identifier state.
2403                        go!(self: to State::AfterDoctypeIdentifier(kind))
2404                    },
2405                    // ↪ U+0000 NULL
2406                    '\0' => {
2407                        // This is an unexpected-null-character parse error.
2408                        // Append a U+FFFD REPLACEMENT CHARACTER character to the current
2409                        // DOCTYPE token's public identifier.
2410                        self.bad_char_error();
2411                        go!(self: push_doctype_id kind '\u{fffd}')
2412                    },
2413                    // ↪ U+003E GREATER-THAN SIGN (>)
2414                    '>' => {
2415                        // This is an abrupt-doctype-public-identifier parse error.
2416                        // Set the current DOCTYPE token's force-quirks flag to on.
2417                        // Switch to the data state.
2418                        // Emit the current DOCTYPE token.
2419                        self.bad_char_error();
2420                        go!(self: force_quirks; emit_doctype; to State::Data)
2421                    },
2422                    // ↪ Anything else
2423                    character => go!(self: push_doctype_id kind character),
2424                }
2425            },
2426
2427            // https://html.spec.whatwg.org/#doctype-public-identifier-(single-quoted)-state
2428            // https://html.spec.whatwg.org/#doctype-system-identifier-(single-quoted)-state
2429            states::DoctypeIdentifierSingleQuoted(kind) => loop {
2430                // Consume the next input character:
2431                match get_char!(self, input) {
2432                    // ↪ U+0027 APOSTROPHE (')
2433                    '\'' => {
2434                        // Switch to the after DOCTYPE public/system identifier state.
2435                        go!(self: to State::AfterDoctypeIdentifier(kind))
2436                    },
2437                    // ↪ U+0000 NULL
2438                    '\0' => {
2439                        // This is an unexpected-null-character parse error.
2440                        // Append a U+FFFD REPLACEMENT CHARACTER character
2441                        // to the current DOCTYPE token's public/system identifier.
2442                        self.bad_char_error();
2443                        go!(self: push_doctype_id kind '\u{fffd}')
2444                    },
2445                    // ↪ U+003E GREATER-THAN SIGN (>)
2446                    '>' => {
2447                        // This is an abrupt-doctype-public/system-identifier parse error.
2448                        // Set the current DOCTYPE token's force-quirks flag to on.
2449                        // Switch to the data state.
2450                        // Emit the current DOCTYPE token.
2451                        self.bad_char_error();
2452                        go!(self: force_quirks; emit_doctype; to State::Data)
2453                    },
2454                    // ↪ Anything else
2455                    character => {
2456                        // Append the current input character to the current DOCTYPE token's
2457                        // public/system identifier.
2458                        go!(self: push_doctype_id kind character)
2459                    },
2460                }
2461            },
2462
2463            // https://html.spec.whatwg.org/#after-doctype-public-identifier-state
2464            states::AfterDoctypeIdentifier(Public) => loop {
2465                // Consume the next input character:
2466                match get_char!(self, input) {
2467                    // ↪ U+0009 CHARACTER TABULATION (tab)
2468                    // ↪ U+000A LINE FEED (LF)
2469                    // ↪ U+000C FORM FEED (FF)
2470                    // ↪ U+0020 SPACE
2471                    '\t' | '\n' | '\x0C' | ' ' => {
2472                        // Switch to the between DOCTYPE public and system identifiers state.
2473                        go!(self: to State::BetweenDoctypePublicAndSystemIdentifiers)
2474                    },
2475                    // ↪ U+003E GREATER-THAN SIGN (>)
2476                    '>' => {
2477                        // Switch to the data state. Emit the current DOCTYPE token.
2478                        go!(self: emit_doctype; to State::Data)
2479                    },
2480                    // ↪ U+0022 QUOTATION MARK (")
2481                    '"' => {
2482                        // This is a missing-whitespace-between-doctype-public-and-system-identifiers
2483                        // parse error. Set the current DOCTYPE token's system identifier to the empty string
2484                        // (not missing), then switch to the DOCTYPE system identifier (double-quoted) state.
2485                        self.bad_char_error();
2486                        go!(self: clear_doctype_id System; to State::DoctypeIdentifierDoubleQuoted(System))
2487                    },
2488                    // ↪ U+0027 APOSTROPHE (')
2489                    '\'' => {
2490                        // This is a missing-whitespace-between-doctype-public-and-system-identifiers parse error.
2491                        // Set the current DOCTYPE token's system identifier to the empty string (not missing),
2492                        // then switch to the DOCTYPE system identifier (single-quoted) state.
2493                        self.bad_char_error();
2494                        go!(self: clear_doctype_id System; to State::DoctypeIdentifierSingleQuoted(System))
2495                    },
2496                    // ↪ Anything else
2497                    _ => {
2498                        // This is a missing-quote-before-doctype-system-identifier parse error.
2499                        // Set the current DOCTYPE token's force-quirks flag to on.
2500                        // Reconsume in the bogus DOCTYPE state.
2501                        self.bad_char_error();
2502                        go!(self: force_quirks; reconsume BogusDoctype)
2503                    },
2504                }
2505            },
2506
2507            // https://html.spec.whatwg.org/#between-doctype-public-and-system-identifiers-state
2508            states::BetweenDoctypePublicAndSystemIdentifiers => loop {
2509                // Consume the next input character:
2510                match get_char!(self, input) {
2511                    // ↪ U+0009 CHARACTER TABULATION (tab)
2512                    // ↪ U+000A LINE FEED (LF)
2513                    // ↪ U+000C FORM FEED (FF)
2514                    // ↪ U+0020 SPACE
2515                    '\t' | '\n' | '\x0C' | ' ' => {
2516                        // Ignore the character.
2517                    },
2518                    // ↪ U+003E GREATER-THAN SIGN (>)
2519                    '>' => {
2520                        // Switch to the data state. Emit the current DOCTYPE token.
2521                        go!(self: emit_doctype; to State::Data)
2522                    },
2523                    // ↪ U+0022 QUOTATION MARK (")
2524                    '"' => {
2525                        // Set the current DOCTYPE token's system identifier to the empty string (not missing),
2526                        // then switch to the DOCTYPE system identifier (double-quoted) state.
2527                        go!(self: clear_doctype_id System; to State::DoctypeIdentifierDoubleQuoted(System))
2528                    },
2529                    // ↪ U+0027 APOSTROPHE (')
2530                    '\'' => {
2531                        // Set the current DOCTYPE token's system identifier to the empty string (not missing),
2532                        // then switch to the DOCTYPE system identifier (single-quoted) state.
2533                        go!(self: clear_doctype_id System; to State::DoctypeIdentifierSingleQuoted(System))
2534                    },
2535                    // ↪ Anything else
2536                    _ => {
2537                        // This is a missing-quote-before-doctype-system-identifier parse error.
2538                        // Set the current DOCTYPE token's force-quirks flag to on.
2539                        // Reconsume in the bogus DOCTYPE state.
2540                        self.bad_char_error();
2541                        go!(self: force_quirks; reconsume BogusDoctype)
2542                    },
2543                }
2544            },
2545
2546            // https://html.spec.whatwg.org/#after-doctype-system-identifier-state
2547            states::AfterDoctypeIdentifier(System) => loop {
2548                // Consume the next input character:
2549                match get_char!(self, input) {
2550                    // ↪ U+0009 CHARACTER TABULATION (tab)
2551                    // ↪ U+000A LINE FEED (LF)
2552                    // ↪ U+000C FORM FEED (FF)
2553                    // ↪ U+0020 SPACE
2554                    '\t' | '\n' | '\x0C' | ' ' => {
2555                        // Ignore the character.
2556                    },
2557                    // ↪ U+003E GREATER-THAN SIGN (>)
2558                    '>' => {
2559                        // Switch to the data state. Emit the current DOCTYPE token.
2560                        go!(self: emit_doctype; to State::Data)
2561                    },
2562                    // ↪ Anything else
2563                    _ => {
2564                        // This is an unexpected-character-after-doctype-system-identifier parse error.
2565                        // Reconsume in the bogus DOCTYPE state.
2566                        self.bad_char_error();
2567                        go!(self: reconsume BogusDoctype)
2568                    },
2569                }
2570            },
2571
2572            // https://html.spec.whatwg.org/#bogus-doctype-state
2573            states::BogusDoctype => loop {
2574                // Consume the next input character:
2575                match get_char!(self, input) {
2576                    // ↪ U+003E GREATER-THAN SIGN (>)
2577                    '>' => {
2578                        // Switch to the data state.
2579                        // Emit the current DOCTYPE token.
2580                        go!(self: emit_doctype; to State::Data)
2581                    },
2582                    // ↪ U+0000 NULL
2583                    '\0' => {
2584                        // This is an unexpected-null-character parse error.
2585                        // Ignore the character.
2586                        self.bad_char_error();
2587                    },
2588                    // ↪ Anything else
2589                    _ => {
2590                        // Ignore the character.
2591                    },
2592                }
2593            },
2594
2595            // https://html.spec.whatwg.org/#cdata-section-state
2596            states::CdataSection => loop {
2597                // Consume the next input character:
2598                match get_char!(self, input) {
2599                    // ↪ U+005D RIGHT SQUARE BRACKET (])
2600                    ']' => {
2601                        // Switch to the CDATA section bracket state.
2602                        go!(self: to State::CdataSectionBracket)
2603                    },
2604                    // FIXME: This is not in the specification.
2605                    '\0' => {
2606                        self.emit_temp_buf();
2607                        self.emit_char('\0');
2608                    },
2609                    // ↪ Anything else
2610                    character => {
2611                        // Emit the current input character as a character token.
2612                        go!(self: push_temp character)
2613                    },
2614                }
2615            },
2616
2617            // https://html.spec.whatwg.org/#cdata-section-bracket-state
2618            states::CdataSectionBracket => {
2619                // Consume the next input character:
2620                match get_char!(self, input) {
2621                    // ↪ U+005D RIGHT SQUARE BRACKET (])
2622                    ']' => {
2623                        // Switch to the CDATA section end state.
2624                        go!(self: to State::CdataSectionEnd)
2625                    },
2626                    // ↪ Anything else
2627                    _ => {
2628                        // Emit a U+005D RIGHT SQUARE BRACKET character token. Reconsume in the CDATA section state.
2629                        go!(self: push_temp ']'; reconsume CdataSection)
2630                    },
2631                }
2632            },
2633
2634            // https://html.spec.whatwg.org/#cdata-section-end-state
2635            states::CdataSectionEnd => loop {
2636                // Consume the next input character:
2637                match get_char!(self, input) {
2638                    // U+005D RIGHT SQUARE BRACKET (])
2639                    ']' => {
2640                        // Emit a U+005D RIGHT SQUARE BRACKET character token.
2641                        go!(self: push_temp ']')
2642                    },
2643                    // U+003E GREATER-THAN SIGN (>)
2644                    '>' => {
2645                        // Switch to the data state.
2646                        self.emit_temp_buf();
2647                        go!(self: to State::Data);
2648                    },
2649                    // Anything else
2650                    _ => {
2651                        // Emit two U+005D RIGHT SQUARE BRACKET character tokens.
2652                        // Reconsume in the CDATA section state.
2653                        go!(self: push_temp ']'; push_temp ']'; reconsume CdataSection)
2654                    },
2655                }
2656            },
2657            // TODO: What about the processing-instruction related states?
2658            //§ END
2659        }
2660    }
2661
2662    fn step_char_ref_tokenizer(&self, input: &BufferQueue) -> ProcessResult<Sink::Handle> {
2663        let mut char_ref_tokenizer = self.char_ref_tokenizer.borrow_mut();
2664        let progress = match char_ref_tokenizer.as_mut().unwrap().step(self, input) {
2665            char_ref::Status::Done(char_ref) => {
2666                self.process_char_ref(char_ref);
2667                *char_ref_tokenizer = None;
2668                return ProcessResult::Continue;
2669            },
2670
2671            char_ref::Status::Stuck => ProcessResult::Suspend,
2672            char_ref::Status::Progress => ProcessResult::Continue,
2673        };
2674
2675        progress
2676    }
2677
2678    fn process_char_ref(&self, char_ref: CharRef) {
2679        let CharRef {
2680            mut chars,
2681            mut num_chars,
2682        } = char_ref;
2683
2684        if num_chars == 0 {
2685            chars[0] = '&';
2686            num_chars = 1;
2687        }
2688
2689        for i in 0..num_chars {
2690            let c = chars[i as usize];
2691            match self.state.get() {
2692                states::Data | states::RawData(states::Rcdata) => self.emit_char(c),
2693
2694                states::AttributeValue(_) => go!(self: push_value c),
2695
2696                _ => panic!(
2697                    "state {:?} should not be reachable in process_char_ref",
2698                    self.state.get()
2699                ),
2700            }
2701        }
2702    }
2703
2704    /// Indicate that we have reached the end of the input.
2705    pub fn end(&self) {
2706        // Handle EOF in the char ref sub-tokenizer, if there is one.
2707        // Do this first because it might un-consume stuff.
2708        let input = BufferQueue::default();
2709        match self.char_ref_tokenizer.take() {
2710            None => (),
2711            Some(mut tokenizer) => {
2712                self.process_char_ref(tokenizer.end_of_file(self, &input));
2713            },
2714        }
2715
2716        // Process all remaining buffered input.
2717        // If we're waiting for lookahead, we're not gonna get it.
2718        self.at_eof.set(true);
2719        assert!(matches!(self.run(&input), TokenizerResult::Done));
2720        assert!(input.is_empty());
2721
2722        loop {
2723            match self.eof_step() {
2724                ProcessResult::Continue => (),
2725                ProcessResult::Suspend => break,
2726                ProcessResult::Script(_) | ProcessResult::EncodingIndicator(_) => unreachable!(),
2727            }
2728        }
2729
2730        self.sink.end();
2731
2732        if self.opts.profile {
2733            self.dump_profile();
2734        }
2735    }
2736
2737    fn dump_profile(&self) {
2738        let mut results: Vec<(states::State, u64)> = self
2739            .state_profile
2740            .borrow()
2741            .iter()
2742            .map(|(s, t)| (*s, *t))
2743            .collect();
2744        results.sort_by_key(|&(_, x)| Reverse(x));
2745
2746        let total: u64 = results.iter().map(|&(_, t)| t).sum();
2747        println!("\nTokenizer profile, in nanoseconds");
2748        println!(
2749            "\n{:12}         total in token sink",
2750            self.time_in_sink.get()
2751        );
2752        println!("\n{total:12}         total in tokenizer");
2753
2754        for (k, v) in results.into_iter() {
2755            let pct = 100.0 * (v as f64) / (total as f64);
2756            println!("{v:12}  {pct:4.1}%  {k:?}");
2757        }
2758    }
2759
2760    fn eof_step(&self) -> ProcessResult<Sink::Handle> {
2761        debug!("processing EOF in state {:?}", self.state.get());
2762        match self.state.get() {
2763            states::Data
2764            | states::RawData(Rcdata)
2765            | states::RawData(Rawtext)
2766            | states::RawData(ScriptData)
2767            | states::Plaintext => go!(self: eof),
2768
2769            states::TagName
2770            | states::RawData(ScriptDataEscaped(_))
2771            | states::BeforeAttributeName
2772            | states::AttributeName
2773            | states::AfterAttributeName
2774            | states::AttributeValue(_)
2775            | states::AfterAttributeValueQuoted
2776            | states::SelfClosingStartTag
2777            | states::ScriptDataEscapedDash(_)
2778            | states::ScriptDataEscapedDashDash(_) => {
2779                self.bad_eof_error();
2780                go!(self: to State::Data)
2781            },
2782
2783            states::BeforeAttributeValue => go!(self: reconsume AttributeValue(Unquoted)),
2784
2785            states::TagOpen => {
2786                self.bad_eof_error();
2787                self.emit_char('<');
2788                go!(self: to State::Data);
2789            },
2790
2791            states::EndTagOpen => {
2792                self.bad_eof_error();
2793                self.emit_char('<');
2794                self.emit_char('/');
2795                go!(self: to State::Data);
2796            },
2797
2798            states::RawLessThanSign(ScriptDataEscaped(DoubleEscaped)) => {
2799                go!(self: to State::RawData(ScriptDataEscaped(DoubleEscaped)))
2800            },
2801
2802            states::RawLessThanSign(kind) => {
2803                self.emit_char('<');
2804                go!(self: to State::RawData(kind));
2805            },
2806
2807            states::RawEndTagOpen(kind) => {
2808                self.emit_char('<');
2809                self.emit_char('/');
2810                go!(self: to State::RawData(kind));
2811            },
2812
2813            states::RawEndTagName(kind) => {
2814                self.emit_char('<');
2815                self.emit_char('/');
2816                self.emit_temp_buf();
2817                go!(self: to State::RawData(kind))
2818            },
2819
2820            states::ScriptDataEscapeStart(kind) => {
2821                go!(self: to State::RawData(ScriptDataEscaped(kind)))
2822            },
2823
2824            states::ScriptDataEscapeStartDash => go!(self: to State::RawData(ScriptData)),
2825
2826            states::ScriptDataDoubleEscapeEnd => {
2827                go!(self: to State::RawData(ScriptDataEscaped(DoubleEscaped)))
2828            },
2829
2830            states::CommentStart
2831            | states::CommentStartDash
2832            | states::Comment
2833            | states::CommentEndDash
2834            | states::CommentEnd
2835            | states::CommentEndBang => {
2836                self.bad_eof_error();
2837                go!(self: emit_comment; to State::Data)
2838            },
2839
2840            states::CommentLessThanSign | states::CommentLessThanSignBang => {
2841                go!(self: reconsume Comment)
2842            },
2843
2844            states::CommentLessThanSignBangDash => go!(self: reconsume CommentEndDash),
2845
2846            states::CommentLessThanSignBangDashDash => go!(self: reconsume CommentEnd),
2847
2848            states::Doctype | states::BeforeDoctypeName => {
2849                self.bad_eof_error();
2850                go!(self: create_doctype; force_quirks; emit_doctype; to State::Data)
2851            },
2852
2853            states::DoctypeName
2854            | states::AfterDoctypeName
2855            | states::AfterDoctypeKeyword(_)
2856            | states::BeforeDoctypeIdentifier(_)
2857            | states::DoctypeIdentifierDoubleQuoted(_)
2858            | states::DoctypeIdentifierSingleQuoted(_)
2859            | states::AfterDoctypeIdentifier(_)
2860            | states::BetweenDoctypePublicAndSystemIdentifiers => {
2861                self.bad_eof_error();
2862                go!(self: force_quirks; emit_doctype; to State::Data)
2863            },
2864
2865            states::BogusDoctype => go!(self: emit_doctype; to State::Data),
2866
2867            states::BogusComment => go!(self: emit_comment; to State::Data),
2868
2869            states::MarkupDeclarationOpen => {
2870                self.bad_char_error();
2871                go!(self: to State::BogusComment)
2872            },
2873
2874            states::CdataSection => {
2875                self.emit_temp_buf();
2876                self.bad_eof_error();
2877                go!(self: to State::Data)
2878            },
2879
2880            states::CdataSectionBracket => go!(self: push_temp ']'; to State::CdataSection),
2881
2882            states::CdataSectionEnd => {
2883                go!(self: push_temp ']'; push_temp ']'; to State::CdataSection)
2884            },
2885        }
2886    }
2887
2888    /// Checks for supported SIMD feature, which is now either SSE2 for x86/x86_64 or NEON for aarch64.
2889    fn is_supported_simd_feature_detected() -> bool {
2890        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
2891        {
2892            is_x86_feature_detected!("sse2")
2893        }
2894
2895        #[cfg(target_arch = "aarch64")]
2896        {
2897            std::arch::is_aarch64_feature_detected!("neon")
2898        }
2899
2900        #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
2901        false
2902    }
2903
2904    #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
2905    /// Implements the [data state] with SIMD instructions.
2906    /// Calls SSE2- or NEON-specific function for chunks and processes any remaining bytes.
2907    ///
2908    /// The algorithm implemented is the naive SIMD approach described [here].
2909    ///
2910    /// ### SAFETY:
2911    /// Calling this function on a CPU that supports neither SSE2 nor NEON causes undefined behaviour.
2912    ///
2913    /// [data state]: https://html.spec.whatwg.org/#data-state
2914    /// [here]: https://lemire.me/blog/2024/06/08/scan-html-faster-with-simd-instructions-chrome-edition/
2915    unsafe fn data_state_simd_fast_path(&self, input: &mut StrTendril) -> Option<SetResult> {
2916        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
2917        let (mut i, mut n_newlines) = self.data_state_sse2_fast_path(input);
2918
2919        #[cfg(target_arch = "aarch64")]
2920        let (mut i, mut n_newlines) = self.data_state_neon_fast_path(input);
2921
2922        // Process any remaining bytes (less than STRIDE)
2923        while let Some(c) = input.as_bytes().get(i) {
2924            if matches!(*c, b'<' | b'&' | b'\r' | b'\0') {
2925                break;
2926            }
2927            if *c == b'\n' {
2928                n_newlines += 1;
2929            }
2930
2931            i += 1;
2932        }
2933
2934        let set_result = if i == 0 {
2935            let first_char = input.pop_front_char().unwrap();
2936            debug_assert!(matches!(first_char, '<' | '&' | '\r' | '\0'));
2937
2938            // FIXME: Passing a bogus input queue is only relevant when c is \n, which can never happen in this case.
2939            // Still, it would be nice to not have to do that.
2940            // The same is true for the unwrap call.
2941            let preprocessed_char = self
2942                .get_preprocessed_char(first_char, &BufferQueue::default())
2943                .unwrap();
2944            SetResult::FromSet(preprocessed_char)
2945        } else {
2946            debug_assert!(
2947                input.len() >= i,
2948                "Trying to remove {:?} bytes from a tendril that is only {:?} bytes long",
2949                i,
2950                input.len()
2951            );
2952            let consumed_chunk = input.unsafe_subtendril(0, i as u32);
2953            input.unsafe_pop_front(i as u32);
2954            SetResult::NotFromSet(consumed_chunk)
2955        };
2956
2957        self.current_line.set(self.current_line.get() + n_newlines);
2958
2959        Some(set_result)
2960    }
2961
2962    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
2963    #[target_feature(enable = "sse2")]
2964    /// Implements the [data state] with SSE2 instructions for x86/x86_64.
2965    /// Returns a pair of the number of bytes processed and the number of newlines found.
2966    ///
2967    /// ### SAFETY:
2968    /// Calling this function on a CPU that does not support NEON causes undefined behaviour.
2969    ///
2970    /// [data state]: https://html.spec.whatwg.org/#data-state
2971    unsafe fn data_state_sse2_fast_path(&self, input: &mut StrTendril) -> (usize, u64) {
2972        #[cfg(target_arch = "x86")]
2973        use std::arch::x86::{
2974            __m128i, _mm_cmpeq_epi8, _mm_loadu_si128, _mm_movemask_epi8, _mm_or_si128,
2975            _mm_set1_epi8,
2976        };
2977        #[cfg(target_arch = "x86_64")]
2978        use std::arch::x86_64::{
2979            __m128i, _mm_cmpeq_epi8, _mm_loadu_si128, _mm_movemask_epi8, _mm_or_si128,
2980            _mm_set1_epi8,
2981        };
2982
2983        debug_assert!(!input.is_empty());
2984
2985        let quote_mask = _mm_set1_epi8('<' as i8);
2986        let escape_mask = _mm_set1_epi8('&' as i8);
2987        let carriage_return_mask = _mm_set1_epi8('\r' as i8);
2988        let zero_mask = _mm_set1_epi8('\0' as i8);
2989        let newline_mask = _mm_set1_epi8('\n' as i8);
2990
2991        let raw_bytes: &[u8] = input.as_bytes();
2992        let start = raw_bytes.as_ptr();
2993
2994        const STRIDE: usize = 16;
2995        let mut i = 0;
2996        let mut n_newlines = 0;
2997        while i + STRIDE <= raw_bytes.len() {
2998            // Load a 16 byte chunk from the input
2999            let data = _mm_loadu_si128(start.add(i) as *const __m128i);
3000
3001            // Compare the chunk against each mask
3002            let quotes = _mm_cmpeq_epi8(data, quote_mask);
3003            let escapes = _mm_cmpeq_epi8(data, escape_mask);
3004            let carriage_returns = _mm_cmpeq_epi8(data, carriage_return_mask);
3005            let zeros = _mm_cmpeq_epi8(data, zero_mask);
3006            let newlines = _mm_cmpeq_epi8(data, newline_mask);
3007
3008            // Combine all test results and create a bitmask from them.
3009            // Each bit in the mask will be 1 if the character at the bit position is in the set and 0 otherwise.
3010            let test_result = _mm_or_si128(
3011                _mm_or_si128(quotes, zeros),
3012                _mm_or_si128(escapes, carriage_returns),
3013            );
3014            let bitmask = _mm_movemask_epi8(test_result);
3015            let newline_mask = _mm_movemask_epi8(newlines);
3016
3017            if (bitmask != 0) {
3018                // We have reached one of the characters that cause the state machine to transition
3019                let position = if cfg!(target_endian = "little") {
3020                    bitmask.trailing_zeros() as usize
3021                } else {
3022                    bitmask.leading_zeros() as usize
3023                };
3024
3025                n_newlines += (newline_mask & ((1 << position) - 1)).count_ones() as u64;
3026                i += position;
3027                break;
3028            } else {
3029                n_newlines += newline_mask.count_ones() as u64;
3030            }
3031
3032            i += STRIDE;
3033        }
3034
3035        (i, n_newlines)
3036    }
3037
3038    #[cfg(target_arch = "aarch64")]
3039    #[target_feature(enable = "neon")]
3040    /// Implements the [data state] with NEON SIMD instructions for AArch64.
3041    /// Returns a pair of the number of bytes processed and the number of newlines found.
3042    ///
3043    /// ### SAFETY:
3044    /// Calling this function on a CPU that does not support NEON causes undefined behaviour.
3045    ///
3046    /// [data state]: https://html.spec.whatwg.org/#data-state
3047    unsafe fn data_state_neon_fast_path(&self, input: &mut StrTendril) -> (usize, u64) {
3048        use std::arch::aarch64::{vceqq_u8, vdupq_n_u8, vld1q_u8, vmaxvq_u8, vorrq_u8};
3049
3050        debug_assert!(!input.is_empty());
3051
3052        let quote_mask = vdupq_n_u8(b'<');
3053        let escape_mask = vdupq_n_u8(b'&');
3054        let carriage_return_mask = vdupq_n_u8(b'\r');
3055        let zero_mask = vdupq_n_u8(b'\0');
3056        let newline_mask = vdupq_n_u8(b'\n');
3057
3058        let raw_bytes: &[u8] = input.as_bytes();
3059        let start = raw_bytes.as_ptr();
3060
3061        const STRIDE: usize = 16;
3062        let mut i = 0;
3063        let mut n_newlines = 0;
3064        while i + STRIDE <= raw_bytes.len() {
3065            // Load a 16 byte chunk from the input
3066            let data = vld1q_u8(start.add(i));
3067
3068            // Compare the chunk against each mask
3069            let quotes = vceqq_u8(data, quote_mask);
3070            let escapes = vceqq_u8(data, escape_mask);
3071            let carriage_returns = vceqq_u8(data, carriage_return_mask);
3072            let zeros = vceqq_u8(data, zero_mask);
3073            let newlines = vceqq_u8(data, newline_mask);
3074
3075            // Combine all test results and create a bitmask from them.
3076            // Each bit in the mask will be 1 if the character at the bit position is in the set and 0 otherwise.
3077            let test_result =
3078                vorrq_u8(vorrq_u8(quotes, zeros), vorrq_u8(escapes, carriage_returns));
3079            let bitmask = vmaxvq_u8(test_result);
3080            let newline_mask = vmaxvq_u8(newlines);
3081            if bitmask != 0 {
3082                // We have reached one of the characters that cause the state machine to transition
3083                let chunk_bytes = std::slice::from_raw_parts(start.add(i), STRIDE);
3084                let position = chunk_bytes
3085                    .iter()
3086                    .position(|&b| matches!(b, b'<' | b'&' | b'\r' | b'\0'))
3087                    .unwrap();
3088
3089                n_newlines += chunk_bytes[..position]
3090                    .iter()
3091                    .filter(|&&b| b == b'\n')
3092                    .count() as u64;
3093
3094                i += position;
3095                break;
3096            } else if newline_mask != 0 {
3097                let chunk_bytes = std::slice::from_raw_parts(start.add(i), STRIDE);
3098                n_newlines += chunk_bytes.iter().filter(|&&b| b == b'\n').count() as u64;
3099            }
3100
3101            i += STRIDE;
3102        }
3103
3104        (i, n_newlines)
3105    }
3106}
3107
3108#[cfg(test)]
3109#[allow(non_snake_case)]
3110mod test {
3111    use super::option_push; // private items
3112    use crate::tendril::{SliceExt, StrTendril};
3113
3114    use super::{TokenSink, TokenSinkResult, Tokenizer, TokenizerOpts};
3115
3116    use super::interface::{CharacterTokens, EOFToken, NullCharacterToken, ParseError};
3117    use super::interface::{EndTag, StartTag, Tag, TagKind};
3118    use super::interface::{TagToken, Token};
3119
3120    use markup5ever::buffer_queue::BufferQueue;
3121    use std::cell::RefCell;
3122
3123    use crate::LocalName;
3124
3125    // LinesMatch implements the TokenSink trait. It is used for testing to see
3126    // if current_line is being updated when process_token is called. The lines
3127    // vector is a collection of the line numbers that each token is on.
3128    struct LinesMatch {
3129        tokens: RefCell<Vec<Token>>,
3130        current_str: RefCell<StrTendril>,
3131        lines: RefCell<Vec<(Token, u64)>>,
3132    }
3133
3134    impl LinesMatch {
3135        fn new() -> LinesMatch {
3136            LinesMatch {
3137                tokens: RefCell::new(vec![]),
3138                current_str: RefCell::new(StrTendril::new()),
3139                lines: RefCell::new(vec![]),
3140            }
3141        }
3142
3143        fn push(&self, token: Token, line_number: u64) {
3144            self.finish_str();
3145            self.lines.borrow_mut().push((token, line_number));
3146        }
3147
3148        fn finish_str(&self) {
3149            if !self.current_str.borrow().is_empty() {
3150                let s = self.current_str.take();
3151                self.tokens.borrow_mut().push(CharacterTokens(s));
3152            }
3153        }
3154    }
3155
3156    impl TokenSink for LinesMatch {
3157        type Handle = ();
3158
3159        fn process_token(&self, token: Token, line_number: u64) -> TokenSinkResult<Self::Handle> {
3160            match token {
3161                CharacterTokens(b) => {
3162                    self.current_str.borrow_mut().push_slice(&b);
3163                },
3164
3165                NullCharacterToken => {
3166                    self.current_str.borrow_mut().push_char('\0');
3167                },
3168
3169                ParseError(_) => {
3170                    panic!("unexpected parse error");
3171                },
3172
3173                TagToken(mut t) => {
3174                    // The spec seems to indicate that one can emit
3175                    // erroneous end tags with attrs, but the test
3176                    // cases don't contain them.
3177                    match t.kind {
3178                        EndTag => {
3179                            t.self_closing = false;
3180                            t.attrs = vec![];
3181                        },
3182                        _ => t.attrs.sort_by(|a1, a2| a1.name.cmp(&a2.name)),
3183                    }
3184                    self.push(TagToken(t), line_number);
3185                },
3186
3187                EOFToken => (),
3188
3189                _ => self.push(token, line_number),
3190            }
3191            TokenSinkResult::Continue
3192        }
3193    }
3194
3195    // Take in tokens, process them, and return vector with line
3196    // numbers that each token is on
3197    fn tokenize(input: Vec<StrTendril>, opts: TokenizerOpts) -> Vec<(Token, u64)> {
3198        let sink = LinesMatch::new();
3199        let tok = Tokenizer::new(sink, opts);
3200        let buffer = BufferQueue::default();
3201        for chunk in input.into_iter() {
3202            buffer.push_back(chunk);
3203            let _ = tok.feed(&buffer);
3204        }
3205        tok.end();
3206        tok.sink.lines.take()
3207    }
3208
3209    // Create a tag token
3210    fn create_tag(token: StrTendril, tagkind: TagKind) -> Token {
3211        let name = LocalName::from(&*token);
3212
3213        TagToken(Tag {
3214            kind: tagkind,
3215            name,
3216            self_closing: false,
3217            attrs: vec![],
3218            had_duplicate_attributes: false,
3219        })
3220    }
3221
3222    #[test]
3223    fn push_to_None_gives_singleton() {
3224        let mut s: Option<StrTendril> = None;
3225        option_push(&mut s, 'x');
3226        assert_eq!(s, Some("x".to_tendril()));
3227    }
3228
3229    #[test]
3230    fn push_to_empty_appends() {
3231        let mut s: Option<StrTendril> = Some(StrTendril::new());
3232        option_push(&mut s, 'x');
3233        assert_eq!(s, Some("x".to_tendril()));
3234    }
3235
3236    #[test]
3237    fn push_to_nonempty_appends() {
3238        let mut s: Option<StrTendril> = Some(StrTendril::from_slice("y"));
3239        option_push(&mut s, 'x');
3240        assert_eq!(s, Some("yx".to_tendril()));
3241    }
3242
3243    #[test]
3244    fn check_lines() {
3245        let opts = TokenizerOpts {
3246            exact_errors: false,
3247            discard_bom: true,
3248            profile: false,
3249            initial_state: None,
3250            last_start_tag_name: None,
3251        };
3252        let vector = vec![
3253            StrTendril::from("<a>\n"),
3254            StrTendril::from("<b>\n"),
3255            StrTendril::from("</b>\n"),
3256            StrTendril::from("</a>\n"),
3257        ];
3258        let expected = vec![
3259            (create_tag(StrTendril::from("a"), StartTag), 1),
3260            (create_tag(StrTendril::from("b"), StartTag), 2),
3261            (create_tag(StrTendril::from("b"), EndTag), 3),
3262            (create_tag(StrTendril::from("a"), EndTag), 4),
3263        ];
3264        let results = tokenize(vector, opts);
3265        assert_eq!(results, expected);
3266    }
3267
3268    #[test]
3269    fn check_lines_with_new_line() {
3270        let opts = TokenizerOpts {
3271            exact_errors: false,
3272            discard_bom: true,
3273            profile: false,
3274            initial_state: None,
3275            last_start_tag_name: None,
3276        };
3277        let vector = vec![
3278            StrTendril::from("<a>\r\n"),
3279            StrTendril::from("<b>\r\n"),
3280            StrTendril::from("</b>\r\n"),
3281            StrTendril::from("</a>\r\n"),
3282        ];
3283        let expected = vec![
3284            (create_tag(StrTendril::from("a"), StartTag), 1),
3285            (create_tag(StrTendril::from("b"), StartTag), 2),
3286            (create_tag(StrTendril::from("b"), EndTag), 3),
3287            (create_tag(StrTendril::from("a"), EndTag), 4),
3288        ];
3289        let results = tokenize(vector, opts);
3290        assert_eq!(results, expected);
3291    }
3292}