rusteerio 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/CONTRIBUTING.md +73 -0
  2. package/Cargo.lock +1051 -0
  3. package/Cargo.toml +36 -0
  4. package/LICENSE +21 -0
  5. package/README.md +252 -2
  6. package/RELEASING.md +138 -0
  7. package/THIRD_PARTY_NOTICES.md +7443 -0
  8. package/build.rs +3 -0
  9. package/dist/chunk-DKULX6VO.mjs +3185 -0
  10. package/dist/chunk-DKULX6VO.mjs.map +1 -0
  11. package/dist/chunk-OHPGEY3E.mjs +32 -0
  12. package/dist/chunk-OHPGEY3E.mjs.map +1 -0
  13. package/dist/index.cjs +3230 -0
  14. package/dist/index.cjs.map +1 -0
  15. package/dist/index.d.mts +113 -0
  16. package/dist/index.d.ts +113 -0
  17. package/dist/index.mjs +38 -0
  18. package/dist/index.mjs.map +1 -0
  19. package/dist/slim.cjs +2441 -0
  20. package/dist/slim.cjs.map +1 -0
  21. package/dist/slim.d.mts +11 -0
  22. package/dist/slim.d.ts +11 -0
  23. package/dist/slim.mjs +19 -0
  24. package/dist/slim.mjs.map +1 -0
  25. package/dist/types-BP6TpxE5.d.mts +532 -0
  26. package/dist/types-BP6TpxE5.d.ts +532 -0
  27. package/dist/utils.cjs +61 -0
  28. package/dist/utils.cjs.map +1 -0
  29. package/dist/utils.d.mts +13 -0
  30. package/dist/utils.d.ts +13 -0
  31. package/dist/utils.mjs +17 -0
  32. package/dist/utils.mjs.map +1 -0
  33. package/native/src/arena.rs +1781 -0
  34. package/native/src/attributes.rs +923 -0
  35. package/native/src/codec.rs +172 -0
  36. package/native/src/diagnostics.rs +1249 -0
  37. package/native/src/extraction.rs +226 -0
  38. package/native/src/gc.rs +166 -0
  39. package/native/src/hook_sink.rs +727 -0
  40. package/native/src/hook_stream.rs +513 -0
  41. package/native/src/lib.rs +2095 -0
  42. package/native/src/location.rs +392 -0
  43. package/native/src/runtime.rs +189 -0
  44. package/native/src/selector.rs +1287 -0
  45. package/native/src/sink.rs +261 -0
  46. package/native/src/stream.rs +334 -0
  47. package/native/src/tokenizer.rs +355 -0
  48. package/native/src/tracker.rs +232 -0
  49. package/native/vendor/README.md +25 -0
  50. package/native/vendor/html5ever-0.35.0/.cargo_vcs_info.json +6 -0
  51. package/native/vendor/html5ever-0.35.0/Cargo.lock +828 -0
  52. package/native/vendor/html5ever-0.35.0/Cargo.toml +85 -0
  53. package/native/vendor/html5ever-0.35.0/LICENSE-APACHE +201 -0
  54. package/native/vendor/html5ever-0.35.0/LICENSE-MIT +25 -0
  55. package/native/vendor/html5ever-0.35.0/README.md +60 -0
  56. package/native/vendor/html5ever-0.35.0/benches/html5ever.rs +86 -0
  57. package/native/vendor/html5ever-0.35.0/data/bench/lipsum-zh.html +19 -0
  58. package/native/vendor/html5ever-0.35.0/data/bench/lipsum.html +40 -0
  59. package/native/vendor/html5ever-0.35.0/data/bench/medium-fragment.html +24 -0
  60. package/native/vendor/html5ever-0.35.0/data/bench/small-fragment.html +7 -0
  61. package/native/vendor/html5ever-0.35.0/data/bench/strong.html +1 -0
  62. package/native/vendor/html5ever-0.35.0/data/bench/tiny-fragment.html +1 -0
  63. package/native/vendor/html5ever-0.35.0/examples/arena.rs +356 -0
  64. package/native/vendor/html5ever-0.35.0/examples/capi/tokenize.c +74 -0
  65. package/native/vendor/html5ever-0.35.0/examples/noop-tokenize.rs +47 -0
  66. package/native/vendor/html5ever-0.35.0/examples/noop-tree-builder.rs +132 -0
  67. package/native/vendor/html5ever-0.35.0/examples/print-tree-actions.rs +185 -0
  68. package/native/vendor/html5ever-0.35.0/examples/tokenize.rs +111 -0
  69. package/native/vendor/html5ever-0.35.0/src/driver.rs +148 -0
  70. package/native/vendor/html5ever-0.35.0/src/lib.rs +30 -0
  71. package/native/vendor/html5ever-0.35.0/src/macros.rs +34 -0
  72. package/native/vendor/html5ever-0.35.0/src/serialize/mod.rs +255 -0
  73. package/native/vendor/html5ever-0.35.0/src/tokenizer/char_ref/mod.rs +451 -0
  74. package/native/vendor/html5ever-0.35.0/src/tokenizer/interface.rs +99 -0
  75. package/native/vendor/html5ever-0.35.0/src/tokenizer/mod.rs +2305 -0
  76. package/native/vendor/html5ever-0.35.0/src/tokenizer/states.rs +97 -0
  77. package/native/vendor/html5ever-0.35.0/src/tree_builder/data.rs +170 -0
  78. package/native/vendor/html5ever-0.35.0/src/tree_builder/mod.rs +1848 -0
  79. package/native/vendor/html5ever-0.35.0/src/tree_builder/rules.rs +1494 -0
  80. package/native/vendor/html5ever-0.35.0/src/tree_builder/tag_sets.rs +114 -0
  81. package/native/vendor/html5ever-0.35.0/src/tree_builder/types.rs +91 -0
  82. package/native/vendor/html5ever-0.35.0/src/util/str.rs +52 -0
  83. package/native/vendor/html5gum-0.8.4/Cargo.lock +1006 -0
  84. package/native/vendor/html5gum-0.8.4/Cargo.toml +109 -0
  85. package/native/vendor/html5gum-0.8.4/LICENSE +19 -0
  86. package/native/vendor/html5gum-0.8.4/README.md +110 -0
  87. package/native/vendor/html5gum-0.8.4/benches/files/0050.html +2131 -0
  88. package/native/vendor/html5gum-0.8.4/benches/files/6D30.html +1004 -0
  89. package/native/vendor/html5gum-0.8.4/benches/files.rs +32 -0
  90. package/native/vendor/html5gum-0.8.4/benches/patterns.rs +31 -0
  91. package/native/vendor/html5gum-0.8.4/src/arrayvec.rs +29 -0
  92. package/native/vendor/html5gum-0.8.4/src/char_validator.rs +134 -0
  93. package/native/vendor/html5gum-0.8.4/src/emitters/callback.rs +676 -0
  94. package/native/vendor/html5gum-0.8.4/src/emitters/default.rs +207 -0
  95. package/native/vendor/html5gum-0.8.4/src/emitters/emitter.rs +344 -0
  96. package/native/vendor/html5gum-0.8.4/src/emitters/html5ever.rs +306 -0
  97. package/native/vendor/html5gum-0.8.4/src/emitters/mod.rs +25 -0
  98. package/native/vendor/html5gum-0.8.4/src/entities.rs +3342 -0
  99. package/native/vendor/html5gum-0.8.4/src/error.rs +96 -0
  100. package/native/vendor/html5gum-0.8.4/src/htmlstring.rs +130 -0
  101. package/native/vendor/html5gum-0.8.4/src/lib.rs +66 -0
  102. package/native/vendor/html5gum-0.8.4/src/machine.rs +2136 -0
  103. package/native/vendor/html5gum-0.8.4/src/machine_helper.rs +277 -0
  104. package/native/vendor/html5gum-0.8.4/src/read_helper.rs +244 -0
  105. package/native/vendor/html5gum-0.8.4/src/reader.rs +440 -0
  106. package/native/vendor/html5gum-0.8.4/src/span.rs +98 -0
  107. package/native/vendor/html5gum-0.8.4/src/state.rs +16 -0
  108. package/native/vendor/html5gum-0.8.4/src/testutils.rs +71 -0
  109. package/native/vendor/html5gum-0.8.4/src/tokenizer.rs +189 -0
  110. package/native/vendor/html5gum-0.8.4/src/utils.rs +88 -0
  111. package/native/vendor/markup5ever-0.35.0/.cargo_vcs_info.json +6 -0
  112. package/native/vendor/markup5ever-0.35.0/Cargo.toml +45 -0
  113. package/native/vendor/markup5ever-0.35.0/LICENSE-APACHE +201 -0
  114. package/native/vendor/markup5ever-0.35.0/LICENSE-MIT +25 -0
  115. package/native/vendor/markup5ever-0.35.0/interface/mod.rs +422 -0
  116. package/native/vendor/markup5ever-0.35.0/interface/tree_builder.rs +280 -0
  117. package/native/vendor/markup5ever-0.35.0/lib.rs +59 -0
  118. package/native/vendor/markup5ever-0.35.0/serialize.rs +77 -0
  119. package/native/vendor/markup5ever-0.35.0/util/buffer_queue.rs +333 -0
  120. package/native/vendor/markup5ever-0.35.0/util/smallcharset.rs +88 -0
  121. package/package.json +137 -4
  122. package/rusteerio.darwin-arm64.node +0 -0
  123. package/rusteerio.darwin-x64.node +0 -0
  124. package/rusteerio.linux-arm64-gnu.node +0 -0
  125. package/rusteerio.linux-arm64-musl.node +0 -0
  126. package/rusteerio.linux-x64-gnu.node +0 -0
  127. package/rusteerio.linux-x64-musl.node +0 -0
  128. package/rusteerio.win32-arm64-msvc.node +0 -0
  129. package/rusteerio.win32-x64-msvc.node +0 -0
  130. package/scripts/check-native.mjs +15 -0
  131. package/scripts/check-release.mjs +235 -0
  132. package/scripts/fix-declarations.mjs +27 -0
  133. package/scripts/generate-notices.mjs +176 -0
  134. package/scripts/notices/README.md +13 -0
  135. package/scripts/notices/cheerio-MIT.txt +21 -0
  136. package/scripts/notices/html5ever-AUTHORS.txt +20 -0
  137. package/scripts/notices/html5ever-COPYRIGHT.txt +8 -0
  138. package/scripts/notices/html5ever-LICENSE-APACHE.txt +201 -0
  139. package/scripts/notices/html5ever-LICENSE-MIT.txt +25 -0
  140. package/scripts/notices/napi-rs-MIT.txt +43 -0
  141. package/scripts/notices/neon-MIT.txt +19 -0
  142. package/scripts/notices/runtime-01c266bced4a434da005.txt +3 -0
  143. package/scripts/notices/runtime-0a54e647fe54104658b5.txt +14 -0
  144. package/scripts/notices/runtime-0b74dfa0bcee5c420c6b.txt +25 -0
  145. package/scripts/notices/runtime-0d542e0c8804e39aa7f3.txt +177 -0
  146. package/scripts/notices/runtime-0f96a83840e146e43c0e.txt +21 -0
  147. package/scripts/notices/runtime-123a331b5dbf04c30097.txt +25 -0
  148. package/scripts/notices/runtime-172020dbfd5b53a226df.txt +34 -0
  149. package/scripts/notices/runtime-209fbbe0ad52d9235e37.txt +26 -0
  150. package/scripts/notices/runtime-23f18e03dc49df91622f.txt +23 -0
  151. package/scripts/notices/runtime-268872b9816f90fd8e85.txt +220 -0
  152. package/scripts/notices/runtime-35242e7a83f69875e6ed.txt +176 -0
  153. package/scripts/notices/runtime-378f5840b258e2779c39.txt +25 -0
  154. package/scripts/notices/runtime-4108245a1f2df9d4e94d.txt +25 -0
  155. package/scripts/notices/runtime-4455bf75a91154108304.txt +21 -0
  156. package/scripts/notices/runtime-553fffcd9b1cb158bc3e.txt +13 -0
  157. package/scripts/notices/runtime-62c7a1e35f56406896d7.txt +176 -0
  158. package/scripts/notices/runtime-6485b8ed310d3f0340bf.txt +25 -0
  159. package/scripts/notices/runtime-69fef7b0f322a6555415.txt +201 -0
  160. package/scripts/notices/runtime-6df43f6f4b5d4587f3d8.txt +187 -0
  161. package/scripts/notices/runtime-799e9ca9d179295ef372.txt +24 -0
  162. package/scripts/notices/runtime-7b63ecd5f1902af1b637.txt +25 -0
  163. package/scripts/notices/runtime-7bc7d5de2a0b793d4e1a.txt +21 -0
  164. package/scripts/notices/runtime-7e12e5df4bae12cb2158.txt +24 -0
  165. package/scripts/notices/runtime-861399f8c21c042b1105.txt +12 -0
  166. package/scripts/notices/runtime-8ada45cd9f843acf64e4.txt +201 -0
  167. package/scripts/notices/runtime-90eb64f0279b0d9432ac.txt +12 -0
  168. package/scripts/notices/runtime-a60eea817514531668d7.txt +201 -0
  169. package/scripts/notices/runtime-a7c936ff1ed8fa340172.txt +25 -0
  170. package/scripts/notices/runtime-ab6eec6caf0fa5775e41.txt +275 -0
  171. package/scripts/notices/runtime-b1181a40b2a7b25cf66f.txt +19 -0
  172. package/scripts/notices/runtime-b71bd43a069ca0641a9e.txt +25 -0
  173. package/scripts/notices/runtime-c16f8dcf1a368b83be78.txt +201 -0
  174. package/scripts/notices/runtime-c2cfccb812fe482101a8.txt +21 -0
  175. package/scripts/notices/runtime-e99d88d232bf57d70f0f.txt +25 -0
  176. package/scripts/notices/runtime-f32fb3b417a194167cfa.txt +9 -0
  177. package/scripts/notices/runtime-f5062c9a188d81dfe66b.txt +39 -0
  178. package/scripts/notices/runtime-f9070a832833fa7488fd.txt +4 -0
  179. package/scripts/notices/runtime-fab3dd6bdab226f1c086.txt +373 -0
  180. package/scripts/notices/runtime-fba3318d0c600380177e.txt +62 -0
  181. package/scripts/notices/runtime-ff8f68cb076caf8cefe7.txt +25 -0
  182. package/scripts/notices/runtime-ff92bed461f50338dd70.txt +74 -0
  183. package/scripts/notices/runtime.json +1632 -0
  184. package/scripts/notices/sources.json +555 -0
  185. package/scripts/notices/xray-MIT.txt +21 -0
  186. package/src/attributes.ts +271 -0
  187. package/src/codec.ts +74 -0
  188. package/src/dom.ts +87 -0
  189. package/src/extract-plan.ts +97 -0
  190. package/src/extract.ts +130 -0
  191. package/src/hooks.ts +220 -0
  192. package/src/index.ts +223 -0
  193. package/src/io.ts +491 -0
  194. package/src/native.ts +135 -0
  195. package/src/nodes.ts +290 -0
  196. package/src/parse.ts +53 -0
  197. package/src/parser-types.ts +157 -0
  198. package/src/router.ts +327 -0
  199. package/src/selection.ts +566 -0
  200. package/src/slim.ts +12 -0
  201. package/src/transport.ts +104 -0
  202. package/src/types.ts +186 -0
  203. package/src/utils.ts +29 -0
  204. package/src/workers.ts +21 -0
  205. package/tsconfig.json +15 -0
  206. package/tsup.config.ts +16 -0
@@ -0,0 +1,2305 @@
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
+
12
+ pub use self::interface::{CharacterTokens, EOFToken, NullCharacterToken, ParseError};
13
+ pub use self::interface::{CommentToken, DoctypeToken, TagToken, Token};
14
+ pub use self::interface::{Doctype, EndTag, StartTag, Tag, TagKind};
15
+ pub use self::interface::{TokenSink, TokenSinkResult};
16
+
17
+ use self::states::{DoctypeIdKind, Public, System};
18
+ use self::states::{DoubleEscaped, Escaped};
19
+ use self::states::{DoubleQuoted, SingleQuoted, Unquoted};
20
+ use self::states::{Rawtext, Rcdata, ScriptData, ScriptDataEscaped};
21
+
22
+ use self::char_ref::{CharRef, CharRefTokenizer};
23
+
24
+ use crate::util::str::lower_ascii_letter;
25
+
26
+ use log::{debug, trace};
27
+ use markup5ever::{ns, small_char_set, TokenizerResult};
28
+ use std::borrow::Cow::{self, Borrowed};
29
+ use std::cell::{Cell, RefCell, RefMut};
30
+ use std::collections::BTreeMap;
31
+ use std::mem;
32
+
33
+ pub use crate::buffer_queue::{BufferQueue, FromSet, NotFromSet, SetResult};
34
+ use crate::macros::{time, unwrap_or_return};
35
+ use crate::tendril::StrTendril;
36
+ use crate::{Attribute, LocalName, QualName, SmallCharSet};
37
+
38
+ mod char_ref;
39
+ mod interface;
40
+ pub mod states;
41
+
42
+ pub enum ProcessResult<Handle> {
43
+ Continue,
44
+ Suspend,
45
+ Script(Handle),
46
+ }
47
+
48
+ fn option_push(opt_str: &mut Option<StrTendril>, c: char) {
49
+ match *opt_str {
50
+ Some(ref mut s) => s.push_char(c),
51
+ None => *opt_str = Some(StrTendril::from_char(c)),
52
+ }
53
+ }
54
+
55
+ /// Tokenizer options, with an impl for `Default`.
56
+ #[derive(Clone)]
57
+ pub struct TokenizerOpts {
58
+ /// Report all parse errors described in the spec, at some
59
+ /// performance penalty? Default: false
60
+ pub exact_errors: bool,
61
+
62
+ /// Escape decoded private-use characters for rusteerio's lossless UTF-16
63
+ /// transport. This extension is opt-in; ordinary html5ever users retain
64
+ /// the upstream character-reference behavior.
65
+ pub rusteerio_codec: bool,
66
+
67
+ /// Discard a `U+FEFF BYTE ORDER MARK` if we see one at the beginning
68
+ /// of the stream? Default: true
69
+ pub discard_bom: bool,
70
+
71
+ /// Keep a record of how long we spent in each state? Printed
72
+ /// when `end()` is called. Default: false
73
+ pub profile: bool,
74
+
75
+ /// Initial state override. Only the test runner should use
76
+ /// a non-`None` value!
77
+ pub initial_state: Option<states::State>,
78
+
79
+ /// Last start tag. Only the test runner should use a
80
+ /// non-`None` value!
81
+ ///
82
+ /// FIXME: Can't use Tendril because we want TokenizerOpts
83
+ /// to be Send.
84
+ pub last_start_tag_name: Option<String>,
85
+ }
86
+
87
+ impl Default for TokenizerOpts {
88
+ fn default() -> TokenizerOpts {
89
+ TokenizerOpts {
90
+ exact_errors: false,
91
+ rusteerio_codec: false,
92
+ discard_bom: true,
93
+ profile: false,
94
+ initial_state: None,
95
+ last_start_tag_name: None,
96
+ }
97
+ }
98
+ }
99
+
100
+ /// The HTML tokenizer.
101
+ pub struct Tokenizer<Sink> {
102
+ /// Options controlling the behavior of the tokenizer.
103
+ opts: TokenizerOpts,
104
+
105
+ /// Destination for tokens we emit.
106
+ pub sink: Sink,
107
+
108
+ /// The abstract machine state as described in the spec.
109
+ state: Cell<states::State>,
110
+
111
+ /// Are we at the end of the file, once buffers have been processed
112
+ /// completely? This affects whether we will wait for lookahead or not.
113
+ at_eof: Cell<bool>,
114
+
115
+ /// Tokenizer for character references, if we're tokenizing
116
+ /// one at the moment.
117
+ char_ref_tokenizer: RefCell<Option<Box<CharRefTokenizer>>>,
118
+
119
+ /// Current input character. Just consumed, may reconsume.
120
+ current_char: Cell<char>,
121
+
122
+ /// Should we reconsume the current input character?
123
+ reconsume: Cell<bool>,
124
+
125
+ /// Did we just consume \r, translating it to \n? In that case we need
126
+ /// to ignore the next character if it's \n.
127
+ ignore_lf: Cell<bool>,
128
+
129
+ /// Discard a U+FEFF BYTE ORDER MARK if we see one? Only done at the
130
+ /// beginning of the stream.
131
+ discard_bom: Cell<bool>,
132
+
133
+ /// Current tag kind.
134
+ current_tag_kind: Cell<TagKind>,
135
+
136
+ /// Current tag name.
137
+ current_tag_name: RefCell<StrTendril>,
138
+
139
+ /// Current tag is self-closing?
140
+ current_tag_self_closing: Cell<bool>,
141
+
142
+ /// Current tag attributes.
143
+ current_tag_attrs: RefCell<Vec<Attribute>>,
144
+
145
+ /// Current attribute name.
146
+ current_attr_name: RefCell<StrTendril>,
147
+
148
+ /// Current attribute value.
149
+ current_attr_value: RefCell<StrTendril>,
150
+
151
+ /// Current comment.
152
+ current_comment: RefCell<StrTendril>,
153
+
154
+ /// Current doctype token.
155
+ current_doctype: RefCell<Doctype>,
156
+
157
+ /// Last start tag name, for use in checking "appropriate end tag".
158
+ last_start_tag_name: RefCell<Option<LocalName>>,
159
+
160
+ /// The "temporary buffer" mentioned in the spec.
161
+ temp_buf: RefCell<StrTendril>,
162
+
163
+ /// Record of how many ns we spent in each state, if profiling is enabled.
164
+ state_profile: RefCell<BTreeMap<states::State, u64>>,
165
+
166
+ /// Record of how many ns we spent in the token sink.
167
+ time_in_sink: Cell<u64>,
168
+
169
+ /// Track current line
170
+ current_line: Cell<u64>,
171
+ }
172
+
173
+ impl<Sink: TokenSink> Tokenizer<Sink> {
174
+ /// Create a new tokenizer which feeds tokens to a particular `TokenSink`.
175
+ pub fn new(sink: Sink, mut opts: TokenizerOpts) -> Tokenizer<Sink> {
176
+ let start_tag_name = opts
177
+ .last_start_tag_name
178
+ .take()
179
+ .map(|s| LocalName::from(&*s));
180
+ let state = opts.initial_state.unwrap_or(states::Data);
181
+ let discard_bom = opts.discard_bom;
182
+ Tokenizer {
183
+ opts,
184
+ sink,
185
+ state: Cell::new(state),
186
+ char_ref_tokenizer: RefCell::new(None),
187
+ at_eof: Cell::new(false),
188
+ current_char: Cell::new('\0'),
189
+ reconsume: Cell::new(false),
190
+ ignore_lf: Cell::new(false),
191
+ discard_bom: Cell::new(discard_bom),
192
+ current_tag_kind: Cell::new(StartTag),
193
+ current_tag_name: RefCell::new(StrTendril::new()),
194
+ current_tag_self_closing: Cell::new(false),
195
+ current_tag_attrs: RefCell::new(vec![]),
196
+ current_attr_name: RefCell::new(StrTendril::new()),
197
+ current_attr_value: RefCell::new(StrTendril::new()),
198
+ current_comment: RefCell::new(StrTendril::new()),
199
+ current_doctype: RefCell::new(Doctype::default()),
200
+ last_start_tag_name: RefCell::new(start_tag_name),
201
+ temp_buf: RefCell::new(StrTendril::new()),
202
+ state_profile: RefCell::new(BTreeMap::new()),
203
+ time_in_sink: Cell::new(0),
204
+ current_line: Cell::new(1),
205
+ }
206
+ }
207
+
208
+ /// Feed an input string into the tokenizer.
209
+ pub fn feed(&self, input: &BufferQueue) -> TokenizerResult<Sink::Handle> {
210
+ if input.is_empty() {
211
+ return TokenizerResult::Done;
212
+ }
213
+
214
+ if self.discard_bom.get() {
215
+ if let Some(c) = input.peek() {
216
+ if c == '\u{feff}' {
217
+ input.next();
218
+ }
219
+ } else {
220
+ return TokenizerResult::Done;
221
+ }
222
+ };
223
+
224
+ self.run(input)
225
+ }
226
+
227
+ pub fn set_plaintext_state(&self) {
228
+ self.state.set(states::Plaintext);
229
+ }
230
+
231
+ fn process_token(&self, token: Token) -> TokenSinkResult<Sink::Handle> {
232
+ if self.opts.profile {
233
+ let (ret, dt) = time!(self.sink.process_token(token, self.current_line.get()));
234
+ self.time_in_sink.set(self.time_in_sink.get() + dt);
235
+ ret
236
+ } else {
237
+ self.sink.process_token(token, self.current_line.get())
238
+ }
239
+ }
240
+
241
+ fn process_token_and_continue(&self, token: Token) {
242
+ assert!(matches!(
243
+ self.process_token(token),
244
+ TokenSinkResult::Continue
245
+ ));
246
+ }
247
+
248
+ //§ preprocessing-the-input-stream
249
+ // Get the next input character, which might be the character
250
+ // 'c' that we already consumed from the buffers.
251
+ fn get_preprocessed_char(&self, mut c: char, input: &BufferQueue) -> Option<char> {
252
+ if self.ignore_lf.get() {
253
+ self.ignore_lf.set(false);
254
+ if c == '\n' {
255
+ c = input.next()?;
256
+ }
257
+ }
258
+
259
+ if c == '\r' {
260
+ self.ignore_lf.set(true);
261
+ c = '\n';
262
+ }
263
+
264
+ if c == '\n' {
265
+ self.current_line.set(self.current_line.get() + 1);
266
+ }
267
+
268
+ if self.opts.exact_errors
269
+ && match c as u32 {
270
+ 0x01..=0x08 | 0x0B | 0x0E..=0x1F | 0x7F..=0x9F | 0xFDD0..=0xFDEF => true,
271
+ n if (n & 0xFFFE) == 0xFFFE => true,
272
+ _ => false,
273
+ }
274
+ {
275
+ let msg = format!("Bad character {c}");
276
+ self.emit_error(Cow::Owned(msg));
277
+ }
278
+
279
+ trace!("got character {c}");
280
+ self.current_char.set(c);
281
+ Some(c)
282
+ }
283
+
284
+ //§ tokenization
285
+ // Get the next input character, if one is available.
286
+ fn get_char(&self, input: &BufferQueue) -> Option<char> {
287
+ if self.reconsume.get() {
288
+ self.reconsume.set(false);
289
+ Some(self.current_char.get())
290
+ } else {
291
+ input
292
+ .next()
293
+ .and_then(|c| self.get_preprocessed_char(c, input))
294
+ }
295
+ }
296
+
297
+ fn pop_except_from(&self, input: &BufferQueue, set: SmallCharSet) -> Option<SetResult> {
298
+ // Bail to the slow path for various corner cases.
299
+ // This means that `FromSet` can contain characters not in the set!
300
+ // It shouldn't matter because the fallback `FromSet` case should
301
+ // always do the same thing as the `NotFromSet` case.
302
+ if self.opts.exact_errors || self.reconsume.get() || self.ignore_lf.get() {
303
+ return self.get_char(input).map(FromSet);
304
+ }
305
+
306
+ let d = input.pop_except_from(set);
307
+ trace!("got characters {d:?}");
308
+ match d {
309
+ Some(FromSet(c)) => self.get_preprocessed_char(c, input).map(FromSet),
310
+
311
+ // NB: We don't set self.current_char for a run of characters not
312
+ // in the set. It shouldn't matter for the codepaths that use
313
+ // this.
314
+ _ => d,
315
+ }
316
+ }
317
+
318
+ // Check if the next characters are an ASCII case-insensitive match. See
319
+ // BufferQueue::eat.
320
+ //
321
+ // NB: this doesn't set the current input character.
322
+ fn eat(&self, input: &BufferQueue, pat: &str, eq: fn(&u8, &u8) -> bool) -> Option<bool> {
323
+ if self.ignore_lf.get() {
324
+ self.ignore_lf.set(false);
325
+ if self.peek(input) == Some('\n') {
326
+ self.discard_char(input);
327
+ }
328
+ }
329
+
330
+ input.push_front(mem::take(&mut self.temp_buf.borrow_mut()));
331
+ match input.eat(pat, eq) {
332
+ None if self.at_eof.get() => Some(false),
333
+ None => {
334
+ while let Some(data) = input.next() {
335
+ self.temp_buf.borrow_mut().push_char(data);
336
+ }
337
+ None
338
+ }
339
+ Some(matched) => Some(matched),
340
+ }
341
+ }
342
+
343
+ /// Run the state machine for as long as we can.
344
+ fn run(&self, input: &BufferQueue) -> TokenizerResult<Sink::Handle> {
345
+ if self.opts.profile {
346
+ loop {
347
+ let state = self.state.get();
348
+ let old_sink = self.time_in_sink.get();
349
+ let (run, mut dt) = time!(self.step(input));
350
+ dt -= (self.time_in_sink.get() - old_sink);
351
+ let new = match self.state_profile.borrow_mut().get_mut(&state) {
352
+ Some(x) => {
353
+ *x += dt;
354
+ false
355
+ }
356
+ None => true,
357
+ };
358
+ if new {
359
+ // do this here because of borrow shenanigans
360
+ self.state_profile.borrow_mut().insert(state, dt);
361
+ }
362
+ match run {
363
+ ProcessResult::Continue => (),
364
+ ProcessResult::Suspend => break,
365
+ ProcessResult::Script(node) => return TokenizerResult::Script(node),
366
+ }
367
+ }
368
+ } else {
369
+ loop {
370
+ match self.step(input) {
371
+ ProcessResult::Continue => (),
372
+ ProcessResult::Suspend => break,
373
+ ProcessResult::Script(node) => return TokenizerResult::Script(node),
374
+ }
375
+ }
376
+ }
377
+ TokenizerResult::Done
378
+ }
379
+
380
+ #[inline]
381
+ fn bad_char_error(&self) {
382
+ #[cfg(feature = "trace_tokenizer")]
383
+ trace!(" error");
384
+
385
+ let msg = if self.opts.exact_errors {
386
+ Cow::from("Bad character")
387
+ } else {
388
+ let c = self.current_char.get();
389
+ let state = self.state.get();
390
+ Cow::from(format!("Saw {c} in state {state:?}"))
391
+ };
392
+ self.emit_error(msg);
393
+ }
394
+
395
+ #[inline]
396
+ fn bad_eof_error(&self) {
397
+ #[cfg(feature = "trace_tokenizer")]
398
+ trace!(" error_eof");
399
+
400
+ let msg = if self.opts.exact_errors {
401
+ Cow::from("Unexpected EOF")
402
+ } else {
403
+ let state = self.state.get();
404
+ Cow::from(format!("Saw EOF in state {state:?}"))
405
+ };
406
+ self.emit_error(msg);
407
+ }
408
+
409
+ fn emit_char(&self, c: char) {
410
+ #[cfg(feature = "trace_tokenizer")]
411
+ trace!(" emit");
412
+
413
+ self.process_token_and_continue(match c {
414
+ '\0' => NullCharacterToken,
415
+ _ => CharacterTokens(StrTendril::from_char(c)),
416
+ });
417
+ }
418
+
419
+ // The string must not contain '\0'!
420
+ fn emit_chars(&self, b: StrTendril) {
421
+ self.process_token_and_continue(CharacterTokens(b));
422
+ }
423
+
424
+ fn emit_current_tag(&self) -> ProcessResult<Sink::Handle> {
425
+ self.finish_attribute();
426
+
427
+ let name = LocalName::from(&**self.current_tag_name.borrow());
428
+ self.current_tag_name.borrow_mut().clear();
429
+
430
+ match self.current_tag_kind.get() {
431
+ StartTag => {
432
+ *self.last_start_tag_name.borrow_mut() = Some(name.clone());
433
+ }
434
+ EndTag => {
435
+ if !self.current_tag_attrs.borrow().is_empty() {
436
+ self.emit_error(Borrowed("Attributes on an end tag"));
437
+ }
438
+ if self.current_tag_self_closing.get() {
439
+ self.emit_error(Borrowed("Self-closing end tag"));
440
+ }
441
+ }
442
+ }
443
+
444
+ let token = TagToken(Tag {
445
+ kind: self.current_tag_kind.get(),
446
+ name,
447
+ self_closing: self.current_tag_self_closing.get(),
448
+ attrs: std::mem::take(&mut self.current_tag_attrs.borrow_mut()),
449
+ });
450
+
451
+ match self.process_token(token) {
452
+ TokenSinkResult::Continue => ProcessResult::Continue,
453
+ TokenSinkResult::Plaintext => {
454
+ self.state.set(states::Plaintext);
455
+ ProcessResult::Continue
456
+ }
457
+ TokenSinkResult::Script(node) => {
458
+ self.state.set(states::Data);
459
+ ProcessResult::Script(node)
460
+ }
461
+ TokenSinkResult::RawData(kind) => {
462
+ self.state.set(states::RawData(kind));
463
+ ProcessResult::Continue
464
+ }
465
+ }
466
+ }
467
+
468
+ fn emit_temp_buf(&self) {
469
+ #[cfg(feature = "trace_tokenizer")]
470
+ trace!(" emit_temp");
471
+
472
+ // FIXME: Make sure that clearing on emit is spec-compatible.
473
+ let buf = mem::take(&mut *self.temp_buf.borrow_mut());
474
+ self.emit_chars(buf);
475
+ }
476
+
477
+ fn clear_temp_buf(&self) {
478
+ // Do this without a new allocation.
479
+ self.temp_buf.borrow_mut().clear();
480
+ }
481
+
482
+ fn emit_current_comment(&self) {
483
+ let comment = mem::take(&mut *self.current_comment.borrow_mut());
484
+ self.process_token_and_continue(CommentToken(comment));
485
+ }
486
+
487
+ fn discard_tag(&self) {
488
+ self.current_tag_name.borrow_mut().clear();
489
+ self.current_tag_self_closing.set(false);
490
+ *self.current_tag_attrs.borrow_mut() = vec![];
491
+ }
492
+
493
+ fn create_tag(&self, kind: TagKind, c: char) {
494
+ self.discard_tag();
495
+ self.current_tag_name.borrow_mut().push_char(c);
496
+ self.current_tag_kind.set(kind);
497
+ }
498
+
499
+ fn have_appropriate_end_tag(&self) -> bool {
500
+ match self.last_start_tag_name.borrow().as_ref() {
501
+ Some(last) => {
502
+ (self.current_tag_kind.get() == EndTag)
503
+ && (**self.current_tag_name.borrow() == **last)
504
+ }
505
+ None => false,
506
+ }
507
+ }
508
+
509
+ fn create_attribute(&self, c: char) {
510
+ self.finish_attribute();
511
+
512
+ self.current_attr_name.borrow_mut().push_char(c);
513
+ }
514
+
515
+ fn finish_attribute(&self) {
516
+ if self.current_attr_name.borrow().is_empty() {
517
+ return;
518
+ }
519
+
520
+ // Check for a duplicate attribute.
521
+ // FIXME: the spec says we should error as soon as the name is finished.
522
+ let dup = {
523
+ let name = &*self.current_attr_name.borrow();
524
+ self.current_tag_attrs
525
+ .borrow()
526
+ .iter()
527
+ .any(|a| *a.name.local == **name)
528
+ };
529
+
530
+ if dup {
531
+ self.emit_error(Borrowed("Duplicate attribute"));
532
+ self.current_attr_name.borrow_mut().clear();
533
+ self.current_attr_value.borrow_mut().clear();
534
+ } else {
535
+ let name = LocalName::from(&**self.current_attr_name.borrow());
536
+ self.current_attr_name.borrow_mut().clear();
537
+ self.current_tag_attrs.borrow_mut().push(Attribute {
538
+ // The tree builder will adjust the namespace if necessary.
539
+ // This only happens in foreign elements.
540
+ name: QualName::new(None, ns!(), name),
541
+ value: mem::take(&mut self.current_attr_value.borrow_mut()),
542
+ });
543
+ }
544
+ }
545
+
546
+ fn emit_current_doctype(&self) {
547
+ let doctype = self.current_doctype.take();
548
+ self.process_token_and_continue(DoctypeToken(doctype));
549
+ }
550
+
551
+ fn doctype_id(&self, kind: DoctypeIdKind) -> RefMut<'_, Option<StrTendril>> {
552
+ let current_doctype = self.current_doctype.borrow_mut();
553
+ match kind {
554
+ Public => RefMut::map(current_doctype, |d| &mut d.public_id),
555
+ System => RefMut::map(current_doctype, |d| &mut d.system_id),
556
+ }
557
+ }
558
+
559
+ fn clear_doctype_id(&self, kind: DoctypeIdKind) {
560
+ let mut id = self.doctype_id(kind);
561
+ match *id {
562
+ Some(ref mut s) => s.clear(),
563
+ None => *id = Some(StrTendril::new()),
564
+ }
565
+ }
566
+
567
+ fn consume_char_ref(&self) {
568
+ *self.char_ref_tokenizer.borrow_mut() = Some(Box::new(CharRefTokenizer::new(matches!(
569
+ self.state.get(),
570
+ states::AttributeValue(_)
571
+ ))));
572
+ }
573
+
574
+ fn emit_eof(&self) {
575
+ self.process_token_and_continue(EOFToken);
576
+ }
577
+
578
+ fn peek(&self, input: &BufferQueue) -> Option<char> {
579
+ if self.reconsume.get() {
580
+ Some(self.current_char.get())
581
+ } else {
582
+ input.peek()
583
+ }
584
+ }
585
+
586
+ fn discard_char(&self, input: &BufferQueue) {
587
+ // peek() deals in un-processed characters (no newline normalization), while get_char()
588
+ // does.
589
+ //
590
+ // since discard_char is supposed to be used in combination with peek(), discard_char must
591
+ // discard a single raw input character, not a normalized newline.
592
+ if self.reconsume.get() {
593
+ self.reconsume.set(false);
594
+ } else {
595
+ input.next();
596
+ }
597
+ }
598
+
599
+ fn emit_error(&self, error: Cow<'static, str>) {
600
+ self.process_token_and_continue(ParseError(error));
601
+ }
602
+ }
603
+ //§ END
604
+
605
+ // Shorthand for common state machine behaviors.
606
+ macro_rules! shorthand (
607
+ ( $me:ident : create_tag $kind:ident $c:expr ) => ( $me.create_tag($kind, $c) );
608
+ ( $me:ident : push_tag $c:expr ) => ( $me.current_tag_name.borrow_mut().push_char($c) );
609
+ ( $me:ident : discard_tag ) => ( $me.discard_tag() );
610
+ ( $me:ident : discard_char $input:expr ) => ( $me.discard_char($input) );
611
+ ( $me:ident : push_temp $c:expr ) => ( $me.temp_buf.borrow_mut().push_char($c) );
612
+ ( $me:ident : clear_temp ) => ( $me.clear_temp_buf() );
613
+ ( $me:ident : create_attr $c:expr ) => ( $me.create_attribute($c) );
614
+ ( $me:ident : push_name $c:expr ) => ( $me.current_attr_name.borrow_mut().push_char($c) );
615
+ ( $me:ident : push_value $c:expr ) => ( $me.current_attr_value.borrow_mut().push_char($c) );
616
+ ( $me:ident : append_value $c:expr ) => ( $me.current_attr_value.borrow_mut().push_tendril($c));
617
+ ( $me:ident : push_comment $c:expr ) => ( $me.current_comment.borrow_mut().push_char($c) );
618
+ ( $me:ident : append_comment $c:expr ) => ( $me.current_comment.borrow_mut().push_slice($c) );
619
+ ( $me:ident : emit_comment ) => ( $me.emit_current_comment() );
620
+ ( $me:ident : clear_comment ) => ( $me.current_comment.borrow_mut().clear() );
621
+ ( $me:ident : create_doctype ) => ( *$me.current_doctype.borrow_mut() = Doctype::default() );
622
+ ( $me:ident : push_doctype_name $c:expr ) => ( option_push(&mut $me.current_doctype.borrow_mut().name, $c) );
623
+ ( $me:ident : push_doctype_id $k:ident $c:expr ) => ( option_push(&mut $me.doctype_id($k), $c) );
624
+ ( $me:ident : clear_doctype_id $k:ident ) => ( $me.clear_doctype_id($k) );
625
+ ( $me:ident : force_quirks ) => ( $me.current_doctype.borrow_mut().force_quirks = true);
626
+ ( $me:ident : emit_doctype ) => ( $me.emit_current_doctype() );
627
+ );
628
+
629
+ // Tracing of tokenizer actions. This adds significant bloat and compile time,
630
+ // so it's behind a cfg flag.
631
+ #[cfg(feature = "trace_tokenizer")]
632
+ macro_rules! sh_trace ( ( $me:ident : $($cmds:tt)* ) => ({
633
+ trace!(" {:?}", stringify!($($cmds)*));
634
+ shorthand!($me : $($cmds)*);
635
+ }));
636
+
637
+ #[cfg(not(feature = "trace_tokenizer"))]
638
+ macro_rules! sh_trace ( ( $me:ident : $($cmds:tt)* ) => ( shorthand!($me: $($cmds)*) ) );
639
+
640
+ // A little DSL for sequencing shorthand actions.
641
+ macro_rules! go (
642
+ // A pattern like $($cmd:tt)* ; $($rest:tt)* causes parse ambiguity.
643
+ // We have to tell the parser how much lookahead we need.
644
+
645
+ ( $me:ident : $a:tt ; $($rest:tt)* ) => ({ sh_trace!($me: $a); go!($me: $($rest)*); });
646
+ ( $me:ident : $a:tt $b:tt ; $($rest:tt)* ) => ({ sh_trace!($me: $a $b); go!($me: $($rest)*); });
647
+ ( $me:ident : $a:tt $b:tt $c:tt ; $($rest:tt)* ) => ({ sh_trace!($me: $a $b $c); go!($me: $($rest)*); });
648
+ ( $me:ident : $a:tt $b:tt $c:tt $d:tt ; $($rest:tt)* ) => ({ sh_trace!($me: $a $b $c $d); go!($me: $($rest)*); });
649
+
650
+ // These can only come at the end.
651
+
652
+ ( $me:ident : to $s:ident ) => ({ $me.state.set(states::$s); return ProcessResult::Continue; });
653
+ ( $me:ident : to $s:ident $k1:expr ) => ({ $me.state.set(states::$s($k1)); return ProcessResult::Continue; });
654
+ ( $me:ident : to $s:ident $k1:ident $k2:expr ) => ({ $me.state.set(states::$s($k1($k2))); return ProcessResult::Continue; });
655
+
656
+ ( $me:ident : reconsume $s:ident ) => ({ $me.reconsume.set(true); go!($me: to $s); });
657
+ ( $me:ident : reconsume $s:ident $k1:expr ) => ({ $me.reconsume.set(true); go!($me: to $s $k1); });
658
+ ( $me:ident : reconsume $s:ident $k1:ident $k2:expr ) => ({ $me.reconsume.set(true); go!($me: to $s $k1 $k2); });
659
+
660
+ ( $me:ident : consume_char_ref ) => ({ $me.consume_char_ref(); return ProcessResult::Continue; });
661
+
662
+ // We have a default next state after emitting a tag, but the sink can override.
663
+ ( $me:ident : emit_tag $s:ident ) => ({
664
+ $me.state.set(states::$s);
665
+ return $me.emit_current_tag();
666
+ });
667
+
668
+ ( $me:ident : eof ) => ({ $me.emit_eof(); return ProcessResult::Suspend; });
669
+
670
+ // If nothing else matched, it's a single command
671
+ ( $me:ident : $($cmd:tt)+ ) => ( sh_trace!($me: $($cmd)+) );
672
+
673
+ // or nothing.
674
+ ( $me:ident : ) => (());
675
+ );
676
+
677
+ // This is a macro because it can cause early return
678
+ // from the function where it is used.
679
+ macro_rules! get_char ( ($me:expr, $input:expr) => (
680
+ unwrap_or_return!($me.get_char($input), ProcessResult::Suspend)
681
+ ));
682
+
683
+ macro_rules! peek ( ($me:expr, $input:expr) => (
684
+ unwrap_or_return!($me.peek($input), ProcessResult::Suspend)
685
+ ));
686
+
687
+ macro_rules! pop_except_from ( ($me:expr, $input:expr, $set:expr) => (
688
+ unwrap_or_return!($me.pop_except_from($input, $set), ProcessResult::Suspend)
689
+ ));
690
+
691
+ macro_rules! eat ( ($me:expr, $input:expr, $pat:expr) => (
692
+ unwrap_or_return!($me.eat($input, $pat, u8::eq_ignore_ascii_case), ProcessResult::Suspend)
693
+ ));
694
+
695
+ macro_rules! eat_exact ( ($me:expr, $input:expr, $pat:expr) => (
696
+ unwrap_or_return!($me.eat($input, $pat, u8::eq), ProcessResult::Suspend)
697
+ ));
698
+
699
+ impl<Sink: TokenSink> Tokenizer<Sink> {
700
+ // Run the state machine for a while.
701
+ // Return true if we should be immediately re-invoked
702
+ // (this just simplifies control flow vs. break / continue).
703
+ #[allow(clippy::never_loop)]
704
+ fn step(&self, input: &BufferQueue) -> ProcessResult<Sink::Handle> {
705
+ if self.char_ref_tokenizer.borrow().is_some() {
706
+ return self.step_char_ref_tokenizer(input);
707
+ }
708
+
709
+ trace!("processing in state {:?}", self.state);
710
+ match self.state.get() {
711
+ //§ data-state
712
+ states::Data => loop {
713
+ let set = small_char_set!('\r' '\0' '&' '<' '\n');
714
+
715
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
716
+ let set_result = if !(self.opts.exact_errors
717
+ || self.reconsume.get()
718
+ || self.ignore_lf.get())
719
+ && Self::is_supported_simd_feature_detected()
720
+ {
721
+ let front_buffer = input.peek_front_chunk_mut();
722
+ let Some(mut front_buffer) = front_buffer else {
723
+ return ProcessResult::Suspend;
724
+ };
725
+
726
+ // Special case: The fast path is not worth taking if the first character is already in the set,
727
+ // which is fairly common
728
+ let first_char = front_buffer
729
+ .chars()
730
+ .next()
731
+ .expect("Input buffers are never empty");
732
+
733
+ if matches!(first_char, '\r' | '\0' | '&' | '<' | '\n') {
734
+ drop(front_buffer);
735
+ self.pop_except_from(input, set)
736
+ } else {
737
+ // SAFETY:
738
+ // This CPU is guaranteed to support SIMD due to the is_supported_simd_feature_detected check above
739
+ let result = unsafe { self.data_state_simd_fast_path(&mut front_buffer) };
740
+
741
+ if front_buffer.is_empty() {
742
+ drop(front_buffer);
743
+ input.pop_front();
744
+ }
745
+
746
+ result
747
+ }
748
+ } else {
749
+ self.pop_except_from(input, set)
750
+ };
751
+
752
+ #[cfg(not(any(
753
+ target_arch = "x86",
754
+ target_arch = "x86_64",
755
+ target_arch = "aarch64"
756
+ )))]
757
+ let set_result = self.pop_except_from(input, set);
758
+
759
+ let Some(set_result) = set_result else {
760
+ return ProcessResult::Suspend;
761
+ };
762
+ match set_result {
763
+ FromSet('\0') => {
764
+ self.bad_char_error();
765
+ self.emit_char('\0');
766
+ }
767
+ FromSet('&') => go!(self: consume_char_ref),
768
+ FromSet('<') => go!(self: to TagOpen),
769
+ FromSet(c) => {
770
+ self.emit_char(c);
771
+ }
772
+ NotFromSet(b) => self.emit_chars(b),
773
+ }
774
+ },
775
+
776
+ //§ rcdata-state
777
+ states::RawData(Rcdata) => loop {
778
+ match pop_except_from!(self, input, small_char_set!('\r' '\0' '&' '<' '\n')) {
779
+ FromSet('\0') => {
780
+ self.bad_char_error();
781
+ self.emit_char('\u{fffd}');
782
+ }
783
+ FromSet('&') => go!(self: consume_char_ref),
784
+ FromSet('<') => go!(self: to RawLessThanSign Rcdata),
785
+ FromSet(c) => self.emit_char(c),
786
+ NotFromSet(b) => self.emit_chars(b),
787
+ }
788
+ },
789
+
790
+ //§ rawtext-state
791
+ states::RawData(Rawtext) => loop {
792
+ match pop_except_from!(self, input, small_char_set!('\r' '\0' '<' '\n')) {
793
+ FromSet('\0') => {
794
+ self.bad_char_error();
795
+ self.emit_char('\u{fffd}');
796
+ }
797
+ FromSet('<') => go!(self: to RawLessThanSign Rawtext),
798
+ FromSet(c) => self.emit_char(c),
799
+ NotFromSet(b) => self.emit_chars(b),
800
+ }
801
+ },
802
+
803
+ //§ script-data-state
804
+ states::RawData(ScriptData) => loop {
805
+ match pop_except_from!(self, input, small_char_set!('\r' '\0' '<' '\n')) {
806
+ FromSet('\0') => {
807
+ self.bad_char_error();
808
+ self.emit_char('\u{fffd}');
809
+ }
810
+ FromSet('<') => go!(self: to RawLessThanSign ScriptData),
811
+ FromSet(c) => self.emit_char(c),
812
+ NotFromSet(b) => self.emit_chars(b),
813
+ }
814
+ },
815
+
816
+ //§ script-data-escaped-state
817
+ states::RawData(ScriptDataEscaped(Escaped)) => loop {
818
+ match pop_except_from!(self, input, small_char_set!('\r' '\0' '-' '<' '\n')) {
819
+ FromSet('\0') => {
820
+ self.bad_char_error();
821
+ self.emit_char('\u{fffd}');
822
+ }
823
+ FromSet('-') => {
824
+ self.emit_char('-');
825
+ go!(self: to ScriptDataEscapedDash Escaped);
826
+ }
827
+ FromSet('<') => go!(self: to RawLessThanSign ScriptDataEscaped Escaped),
828
+ FromSet(c) => self.emit_char(c),
829
+ NotFromSet(b) => self.emit_chars(b),
830
+ }
831
+ },
832
+
833
+ //§ script-data-double-escaped-state
834
+ states::RawData(ScriptDataEscaped(DoubleEscaped)) => loop {
835
+ match pop_except_from!(self, input, small_char_set!('\r' '\0' '-' '<' '\n')) {
836
+ FromSet('\0') => {
837
+ self.bad_char_error();
838
+ self.emit_char('\u{fffd}');
839
+ }
840
+ FromSet('-') => {
841
+ self.emit_char('-');
842
+ go!(self: to ScriptDataEscapedDash DoubleEscaped);
843
+ }
844
+ FromSet('<') => {
845
+ self.emit_char('<');
846
+ go!(self: to RawLessThanSign ScriptDataEscaped DoubleEscaped)
847
+ }
848
+ FromSet(c) => self.emit_char(c),
849
+ NotFromSet(b) => self.emit_chars(b),
850
+ }
851
+ },
852
+
853
+ //§ plaintext-state
854
+ states::Plaintext => loop {
855
+ match pop_except_from!(self, input, small_char_set!('\r' '\0' '\n')) {
856
+ FromSet('\0') => {
857
+ self.bad_char_error();
858
+ self.emit_char('\u{fffd}');
859
+ }
860
+ FromSet(c) => self.emit_char(c),
861
+ NotFromSet(b) => self.emit_chars(b),
862
+ }
863
+ },
864
+
865
+ //§ tag-open-state
866
+ states::TagOpen => loop {
867
+ match get_char!(self, input) {
868
+ '!' => go!(self: to MarkupDeclarationOpen),
869
+ '/' => go!(self: to EndTagOpen),
870
+ '?' => {
871
+ self.bad_char_error();
872
+ go!(self: clear_comment; reconsume BogusComment)
873
+ }
874
+ c => match lower_ascii_letter(c) {
875
+ Some(cl) => go!(self: create_tag StartTag cl; to TagName),
876
+ None => {
877
+ self.bad_char_error();
878
+ self.emit_char('<');
879
+ go!(self: reconsume Data)
880
+ }
881
+ },
882
+ }
883
+ },
884
+
885
+ //§ end-tag-open-state
886
+ states::EndTagOpen => loop {
887
+ match get_char!(self, input) {
888
+ '>' => {
889
+ self.bad_char_error();
890
+ go!(self: to Data)
891
+ }
892
+ c => match lower_ascii_letter(c) {
893
+ Some(cl) => go!(self: create_tag EndTag cl; to TagName),
894
+ None => {
895
+ self.bad_char_error();
896
+ go!(self: clear_comment; reconsume BogusComment)
897
+ }
898
+ },
899
+ }
900
+ },
901
+
902
+ //§ tag-name-state
903
+ states::TagName => loop {
904
+ match get_char!(self, input) {
905
+ '\t' | '\n' | '\x0C' | ' ' => go!(self: to BeforeAttributeName),
906
+ '/' => go!(self: to SelfClosingStartTag),
907
+ '>' => go!(self: emit_tag Data),
908
+ '\0' => {
909
+ self.bad_char_error();
910
+ go!(self: push_tag '\u{fffd}')
911
+ }
912
+ c => go!(self: push_tag (c.to_ascii_lowercase())),
913
+ }
914
+ },
915
+
916
+ //§ script-data-escaped-less-than-sign-state
917
+ states::RawLessThanSign(ScriptDataEscaped(Escaped)) => loop {
918
+ match get_char!(self, input) {
919
+ '/' => go!(self: clear_temp; to RawEndTagOpen ScriptDataEscaped Escaped),
920
+ c => match lower_ascii_letter(c) {
921
+ Some(cl) => {
922
+ go!(self: clear_temp; push_temp cl);
923
+ self.emit_char('<');
924
+ self.emit_char(c);
925
+ go!(self: to ScriptDataEscapeStart DoubleEscaped);
926
+ }
927
+ None => {
928
+ self.emit_char('<');
929
+ go!(self: reconsume RawData ScriptDataEscaped Escaped);
930
+ }
931
+ },
932
+ }
933
+ },
934
+
935
+ //§ script-data-double-escaped-less-than-sign-state
936
+ states::RawLessThanSign(ScriptDataEscaped(DoubleEscaped)) => loop {
937
+ match get_char!(self, input) {
938
+ '/' => {
939
+ go!(self: clear_temp);
940
+ self.emit_char('/');
941
+ go!(self: to ScriptDataDoubleEscapeEnd);
942
+ }
943
+ _ => go!(self: reconsume RawData ScriptDataEscaped DoubleEscaped),
944
+ }
945
+ },
946
+
947
+ //§ rcdata-less-than-sign-state rawtext-less-than-sign-state script-data-less-than-sign-state
948
+ // otherwise
949
+ states::RawLessThanSign(kind) => loop {
950
+ match get_char!(self, input) {
951
+ '/' => go!(self: clear_temp; to RawEndTagOpen kind),
952
+ '!' if kind == ScriptData => {
953
+ self.emit_char('<');
954
+ self.emit_char('!');
955
+ go!(self: to ScriptDataEscapeStart Escaped);
956
+ }
957
+ _ => {
958
+ self.emit_char('<');
959
+ go!(self: reconsume RawData kind);
960
+ }
961
+ }
962
+ },
963
+
964
+ //§ rcdata-end-tag-open-state rawtext-end-tag-open-state script-data-end-tag-open-state script-data-escaped-end-tag-open-state
965
+ states::RawEndTagOpen(kind) => loop {
966
+ let c = get_char!(self, input);
967
+ match lower_ascii_letter(c) {
968
+ Some(cl) => go!(self: create_tag EndTag cl; push_temp c; to RawEndTagName kind),
969
+ None => {
970
+ self.emit_char('<');
971
+ self.emit_char('/');
972
+ go!(self: reconsume RawData kind);
973
+ }
974
+ }
975
+ },
976
+
977
+ //§ rcdata-end-tag-name-state rawtext-end-tag-name-state script-data-end-tag-name-state script-data-escaped-end-tag-name-state
978
+ states::RawEndTagName(kind) => loop {
979
+ let c = get_char!(self, input);
980
+ if self.have_appropriate_end_tag() {
981
+ match c {
982
+ '\t' | '\n' | '\x0C' | ' ' => go!(self: clear_temp; to BeforeAttributeName),
983
+ '/' => go!(self: clear_temp; to SelfClosingStartTag),
984
+ '>' => go!(self: clear_temp; emit_tag Data),
985
+ _ => (),
986
+ }
987
+ }
988
+
989
+ match lower_ascii_letter(c) {
990
+ Some(cl) => go!(self: push_tag cl; push_temp c),
991
+ None => {
992
+ go!(self: discard_tag);
993
+ self.emit_char('<');
994
+ self.emit_char('/');
995
+ self.emit_temp_buf();
996
+ go!(self: reconsume RawData kind);
997
+ }
998
+ }
999
+ },
1000
+
1001
+ //§ script-data-double-escape-start-state
1002
+ states::ScriptDataEscapeStart(DoubleEscaped) => loop {
1003
+ let c = get_char!(self, input);
1004
+ match c {
1005
+ '\t' | '\n' | '\x0C' | ' ' | '/' | '>' => {
1006
+ let esc = if &**self.temp_buf.borrow() == "script" {
1007
+ DoubleEscaped
1008
+ } else {
1009
+ Escaped
1010
+ };
1011
+ self.emit_char(c);
1012
+ go!(self: to RawData ScriptDataEscaped esc);
1013
+ }
1014
+ _ => match lower_ascii_letter(c) {
1015
+ Some(cl) => {
1016
+ go!(self: push_temp cl);
1017
+ self.emit_char(c);
1018
+ }
1019
+ None => go!(self: reconsume RawData ScriptDataEscaped Escaped),
1020
+ },
1021
+ }
1022
+ },
1023
+
1024
+ //§ script-data-escape-start-state
1025
+ states::ScriptDataEscapeStart(Escaped) => loop {
1026
+ match get_char!(self, input) {
1027
+ '-' => {
1028
+ self.emit_char('-');
1029
+ go!(self: to ScriptDataEscapeStartDash);
1030
+ }
1031
+ _ => go!(self: reconsume RawData ScriptData),
1032
+ }
1033
+ },
1034
+
1035
+ //§ script-data-escape-start-dash-state
1036
+ states::ScriptDataEscapeStartDash => loop {
1037
+ match get_char!(self, input) {
1038
+ '-' => {
1039
+ self.emit_char('-');
1040
+ go!(self: to ScriptDataEscapedDashDash Escaped);
1041
+ }
1042
+ _ => go!(self: reconsume RawData ScriptData),
1043
+ }
1044
+ },
1045
+
1046
+ //§ script-data-escaped-dash-state script-data-double-escaped-dash-state
1047
+ states::ScriptDataEscapedDash(kind) => loop {
1048
+ match get_char!(self, input) {
1049
+ '-' => {
1050
+ self.emit_char('-');
1051
+ go!(self: to ScriptDataEscapedDashDash kind);
1052
+ }
1053
+ '<' => {
1054
+ if kind == DoubleEscaped {
1055
+ self.emit_char('<');
1056
+ }
1057
+ go!(self: to RawLessThanSign ScriptDataEscaped kind);
1058
+ }
1059
+ '\0' => {
1060
+ self.bad_char_error();
1061
+ self.emit_char('\u{fffd}');
1062
+ go!(self: to RawData ScriptDataEscaped kind)
1063
+ }
1064
+ c => {
1065
+ self.emit_char(c);
1066
+ go!(self: to RawData ScriptDataEscaped kind);
1067
+ }
1068
+ }
1069
+ },
1070
+
1071
+ //§ script-data-escaped-dash-dash-state script-data-double-escaped-dash-dash-state
1072
+ states::ScriptDataEscapedDashDash(kind) => loop {
1073
+ match get_char!(self, input) {
1074
+ '-' => {
1075
+ self.emit_char('-');
1076
+ }
1077
+ '<' => {
1078
+ if kind == DoubleEscaped {
1079
+ self.emit_char('<');
1080
+ }
1081
+ go!(self: to RawLessThanSign ScriptDataEscaped kind);
1082
+ }
1083
+ '>' => {
1084
+ self.emit_char('>');
1085
+ go!(self: to RawData ScriptData);
1086
+ }
1087
+ '\0' => {
1088
+ self.bad_char_error();
1089
+ self.emit_char('\u{fffd}');
1090
+ go!(self: to RawData ScriptDataEscaped kind)
1091
+ }
1092
+ c => {
1093
+ self.emit_char(c);
1094
+ go!(self: to RawData ScriptDataEscaped kind);
1095
+ }
1096
+ }
1097
+ },
1098
+
1099
+ //§ script-data-double-escape-end-state
1100
+ states::ScriptDataDoubleEscapeEnd => loop {
1101
+ let c = get_char!(self, input);
1102
+ match c {
1103
+ '\t' | '\n' | '\x0C' | ' ' | '/' | '>' => {
1104
+ let esc = if &**self.temp_buf.borrow() == "script" {
1105
+ Escaped
1106
+ } else {
1107
+ DoubleEscaped
1108
+ };
1109
+ self.emit_char(c);
1110
+ go!(self: to RawData ScriptDataEscaped esc);
1111
+ }
1112
+ _ => match lower_ascii_letter(c) {
1113
+ Some(cl) => {
1114
+ go!(self: push_temp cl);
1115
+ self.emit_char(c);
1116
+ }
1117
+ None => go!(self: reconsume RawData ScriptDataEscaped DoubleEscaped),
1118
+ },
1119
+ }
1120
+ },
1121
+
1122
+ //§ before-attribute-name-state
1123
+ states::BeforeAttributeName => loop {
1124
+ match get_char!(self, input) {
1125
+ '\t' | '\n' | '\x0C' | ' ' => (),
1126
+ '/' => go!(self: to SelfClosingStartTag),
1127
+ '>' => go!(self: emit_tag Data),
1128
+ '\0' => {
1129
+ self.bad_char_error();
1130
+ go!(self: create_attr '\u{fffd}'; to AttributeName)
1131
+ }
1132
+ c => match lower_ascii_letter(c) {
1133
+ Some(cl) => go!(self: create_attr cl; to AttributeName),
1134
+ None => {
1135
+ if matches!(c, '"' | '\'' | '<' | '=') {
1136
+ self.bad_char_error();
1137
+ }
1138
+
1139
+ go!(self: create_attr c; to AttributeName);
1140
+ }
1141
+ },
1142
+ }
1143
+ },
1144
+
1145
+ //§ attribute-name-state
1146
+ states::AttributeName => loop {
1147
+ match get_char!(self, input) {
1148
+ '\t' | '\n' | '\x0C' | ' ' => go!(self: to AfterAttributeName),
1149
+ '/' => go!(self: to SelfClosingStartTag),
1150
+ '=' => go!(self: to BeforeAttributeValue),
1151
+ '>' => go!(self: emit_tag Data),
1152
+ '\0' => {
1153
+ self.bad_char_error();
1154
+ go!(self: push_name '\u{fffd}')
1155
+ }
1156
+ c => match lower_ascii_letter(c) {
1157
+ Some(cl) => go!(self: push_name cl),
1158
+ None => {
1159
+ if matches!(c, '"' | '\'' | '<') {
1160
+ self.bad_char_error();
1161
+ }
1162
+ go!(self: push_name c);
1163
+ }
1164
+ },
1165
+ }
1166
+ },
1167
+
1168
+ //§ after-attribute-name-state
1169
+ states::AfterAttributeName => loop {
1170
+ match get_char!(self, input) {
1171
+ '\t' | '\n' | '\x0C' | ' ' => (),
1172
+ '/' => go!(self: to SelfClosingStartTag),
1173
+ '=' => go!(self: to BeforeAttributeValue),
1174
+ '>' => go!(self: emit_tag Data),
1175
+ '\0' => {
1176
+ self.bad_char_error();
1177
+ go!(self: create_attr '\u{fffd}'; to AttributeName)
1178
+ }
1179
+ c => match lower_ascii_letter(c) {
1180
+ Some(cl) => go!(self: create_attr cl; to AttributeName),
1181
+ None => {
1182
+ if matches!(c, '"' | '\'' | '<') {
1183
+ self.bad_char_error();
1184
+ }
1185
+
1186
+ go!(self: create_attr c; to AttributeName);
1187
+ }
1188
+ },
1189
+ }
1190
+ },
1191
+
1192
+ //§ before-attribute-value-state
1193
+ // Use peek so we can handle the first attr character along with the rest,
1194
+ // hopefully in the same zero-copy buffer.
1195
+ states::BeforeAttributeValue => loop {
1196
+ match peek!(self, input) {
1197
+ '\t' | '\n' | '\r' | '\x0C' | ' ' => go!(self: discard_char input),
1198
+ '"' => go!(self: discard_char input; to AttributeValue DoubleQuoted),
1199
+ '\'' => go!(self: discard_char input; to AttributeValue SingleQuoted),
1200
+ '>' => {
1201
+ go!(self: discard_char input);
1202
+ self.bad_char_error();
1203
+ go!(self: emit_tag Data)
1204
+ }
1205
+ _ => go!(self: to AttributeValue Unquoted),
1206
+ }
1207
+ },
1208
+
1209
+ //§ attribute-value-(double-quoted)-state
1210
+ states::AttributeValue(DoubleQuoted) => loop {
1211
+ match pop_except_from!(self, input, small_char_set!('\r' '"' '&' '\0' '\n')) {
1212
+ FromSet('"') => go!(self: to AfterAttributeValueQuoted),
1213
+ FromSet('&') => go!(self: consume_char_ref),
1214
+ FromSet('\0') => {
1215
+ self.bad_char_error();
1216
+ go!(self: push_value '\u{fffd}')
1217
+ }
1218
+ FromSet(c) => go!(self: push_value c),
1219
+ NotFromSet(ref b) => go!(self: append_value b),
1220
+ }
1221
+ },
1222
+
1223
+ //§ attribute-value-(single-quoted)-state
1224
+ states::AttributeValue(SingleQuoted) => loop {
1225
+ match pop_except_from!(self, input, small_char_set!('\r' '\'' '&' '\0' '\n')) {
1226
+ FromSet('\'') => go!(self: to AfterAttributeValueQuoted),
1227
+ FromSet('&') => go!(self: consume_char_ref),
1228
+ FromSet('\0') => {
1229
+ self.bad_char_error();
1230
+ go!(self: push_value '\u{fffd}')
1231
+ }
1232
+ FromSet(c) => go!(self: push_value c),
1233
+ NotFromSet(ref b) => go!(self: append_value b),
1234
+ }
1235
+ },
1236
+
1237
+ //§ attribute-value-(unquoted)-state
1238
+ states::AttributeValue(Unquoted) => loop {
1239
+ match pop_except_from!(
1240
+ self,
1241
+ input,
1242
+ small_char_set!('\r' '\t' '\n' '\x0C' ' ' '&' '>' '\0')
1243
+ ) {
1244
+ FromSet('\t') | FromSet('\n') | FromSet('\x0C') | FromSet(' ') => {
1245
+ go!(self: to BeforeAttributeName)
1246
+ }
1247
+ FromSet('&') => go!(self: consume_char_ref),
1248
+ FromSet('>') => go!(self: emit_tag Data),
1249
+ FromSet('\0') => {
1250
+ self.bad_char_error();
1251
+ go!(self: push_value '\u{fffd}')
1252
+ }
1253
+ FromSet(c) => {
1254
+ if matches!(c, '"' | '\'' | '<' | '=' | '`') {
1255
+ self.bad_char_error();
1256
+ }
1257
+ go!(self: push_value c);
1258
+ }
1259
+ NotFromSet(ref b) => go!(self: append_value b),
1260
+ }
1261
+ },
1262
+
1263
+ //§ after-attribute-value-(quoted)-state
1264
+ states::AfterAttributeValueQuoted => loop {
1265
+ match get_char!(self, input) {
1266
+ '\t' | '\n' | '\x0C' | ' ' => go!(self: to BeforeAttributeName),
1267
+ '/' => go!(self: to SelfClosingStartTag),
1268
+ '>' => go!(self: emit_tag Data),
1269
+ _ => {
1270
+ self.bad_char_error();
1271
+ go!(self: reconsume BeforeAttributeName)
1272
+ }
1273
+ }
1274
+ },
1275
+
1276
+ //§ self-closing-start-tag-state
1277
+ states::SelfClosingStartTag => loop {
1278
+ match get_char!(self, input) {
1279
+ '>' => {
1280
+ self.current_tag_self_closing.set(true);
1281
+ go!(self: emit_tag Data);
1282
+ }
1283
+ _ => {
1284
+ self.bad_char_error();
1285
+ go!(self: reconsume BeforeAttributeName)
1286
+ }
1287
+ }
1288
+ },
1289
+
1290
+ //§ comment-start-state
1291
+ states::CommentStart => loop {
1292
+ match get_char!(self, input) {
1293
+ '-' => go!(self: to CommentStartDash),
1294
+ '\0' => {
1295
+ self.bad_char_error();
1296
+ go!(self: push_comment '\u{fffd}'; to Comment)
1297
+ }
1298
+ '>' => {
1299
+ self.bad_char_error();
1300
+ go!(self: emit_comment; to Data)
1301
+ }
1302
+ c => go!(self: push_comment c; to Comment),
1303
+ }
1304
+ },
1305
+
1306
+ //§ comment-start-dash-state
1307
+ states::CommentStartDash => loop {
1308
+ match get_char!(self, input) {
1309
+ '-' => go!(self: to CommentEnd),
1310
+ '\0' => {
1311
+ self.bad_char_error();
1312
+ go!(self: append_comment "-\u{fffd}"; to Comment)
1313
+ }
1314
+ '>' => {
1315
+ self.bad_char_error();
1316
+ go!(self: emit_comment; to Data)
1317
+ }
1318
+ c => go!(self: push_comment '-'; push_comment c; to Comment),
1319
+ }
1320
+ },
1321
+
1322
+ //§ comment-state
1323
+ states::Comment => loop {
1324
+ match get_char!(self, input) {
1325
+ c @ '<' => go!(self: push_comment c; to CommentLessThanSign),
1326
+ '-' => go!(self: to CommentEndDash),
1327
+ '\0' => {
1328
+ self.bad_char_error();
1329
+ go!(self: push_comment '\u{fffd}')
1330
+ }
1331
+ c => go!(self: push_comment c),
1332
+ }
1333
+ },
1334
+
1335
+ //§ comment-less-than-sign-state
1336
+ states::CommentLessThanSign => loop {
1337
+ match get_char!(self, input) {
1338
+ c @ '!' => go!(self: push_comment c; to CommentLessThanSignBang),
1339
+ c @ '<' => go!(self: push_comment c),
1340
+ _ => go!(self: reconsume Comment),
1341
+ }
1342
+ },
1343
+
1344
+ //§ comment-less-than-sign-bang
1345
+ states::CommentLessThanSignBang => loop {
1346
+ match get_char!(self, input) {
1347
+ '-' => go!(self: to CommentLessThanSignBangDash),
1348
+ _ => go!(self: reconsume Comment),
1349
+ }
1350
+ },
1351
+
1352
+ //§ comment-less-than-sign-bang-dash
1353
+ states::CommentLessThanSignBangDash => loop {
1354
+ match get_char!(self, input) {
1355
+ '-' => go!(self: to CommentLessThanSignBangDashDash),
1356
+ _ => go!(self: reconsume CommentEndDash),
1357
+ }
1358
+ },
1359
+
1360
+ //§ comment-less-than-sign-bang-dash-dash
1361
+ states::CommentLessThanSignBangDashDash => loop {
1362
+ match get_char!(self, input) {
1363
+ '>' => go!(self: reconsume CommentEnd),
1364
+ _ => {
1365
+ self.bad_char_error();
1366
+ go!(self: reconsume CommentEnd)
1367
+ }
1368
+ }
1369
+ },
1370
+
1371
+ //§ comment-end-dash-state
1372
+ states::CommentEndDash => loop {
1373
+ match get_char!(self, input) {
1374
+ '-' => go!(self: to CommentEnd),
1375
+ '\0' => {
1376
+ self.bad_char_error();
1377
+ go!(self: append_comment "-\u{fffd}"; to Comment)
1378
+ }
1379
+ c => go!(self: push_comment '-'; push_comment c; to Comment),
1380
+ }
1381
+ },
1382
+
1383
+ //§ comment-end-state
1384
+ states::CommentEnd => loop {
1385
+ match get_char!(self, input) {
1386
+ '>' => go!(self: emit_comment; to Data),
1387
+ '!' => go!(self: to CommentEndBang),
1388
+ '-' => go!(self: push_comment '-'),
1389
+ _ => go!(self: append_comment "--"; reconsume Comment),
1390
+ }
1391
+ },
1392
+
1393
+ //§ comment-end-bang-state
1394
+ states::CommentEndBang => loop {
1395
+ match get_char!(self, input) {
1396
+ '-' => go!(self: append_comment "--!"; to CommentEndDash),
1397
+ '>' => {
1398
+ self.bad_char_error();
1399
+ go!(self: emit_comment; to Data)
1400
+ }
1401
+ '\0' => {
1402
+ self.bad_char_error();
1403
+ go!(self: append_comment "--!\u{fffd}"; to Comment)
1404
+ }
1405
+ c => go!(self: append_comment "--!"; push_comment c; to Comment),
1406
+ }
1407
+ },
1408
+
1409
+ //§ doctype-state
1410
+ states::Doctype => loop {
1411
+ match get_char!(self, input) {
1412
+ '\t' | '\n' | '\x0C' | ' ' => go!(self: to BeforeDoctypeName),
1413
+ '>' => go!(self: reconsume BeforeDoctypeName),
1414
+ _ => {
1415
+ self.bad_char_error();
1416
+ go!(self: reconsume BeforeDoctypeName)
1417
+ }
1418
+ }
1419
+ },
1420
+
1421
+ //§ before-doctype-name-state
1422
+ states::BeforeDoctypeName => loop {
1423
+ match get_char!(self, input) {
1424
+ '\t' | '\n' | '\x0C' | ' ' => (),
1425
+ '\0' => {
1426
+ self.bad_char_error();
1427
+ go!(self: create_doctype; push_doctype_name '\u{fffd}'; to DoctypeName)
1428
+ }
1429
+ '>' => {
1430
+ self.bad_char_error();
1431
+ go!(self: create_doctype; force_quirks; emit_doctype; to Data)
1432
+ }
1433
+ c => go!(self: create_doctype; push_doctype_name (c.to_ascii_lowercase());
1434
+ to DoctypeName),
1435
+ }
1436
+ },
1437
+
1438
+ //§ doctype-name-state
1439
+ states::DoctypeName => loop {
1440
+ match get_char!(self, input) {
1441
+ '\t' | '\n' | '\x0C' | ' ' => go!(self: clear_temp; to AfterDoctypeName),
1442
+ '>' => go!(self: emit_doctype; to Data),
1443
+ '\0' => {
1444
+ self.bad_char_error();
1445
+ go!(self: push_doctype_name '\u{fffd}')
1446
+ }
1447
+ c => go!(self: push_doctype_name (c.to_ascii_lowercase())),
1448
+ }
1449
+ },
1450
+
1451
+ //§ after-doctype-name-state
1452
+ states::AfterDoctypeName => loop {
1453
+ if eat!(self, input, "public") {
1454
+ go!(self: to AfterDoctypeKeyword Public);
1455
+ } else if eat!(self, input, "system") {
1456
+ go!(self: to AfterDoctypeKeyword System);
1457
+ } else {
1458
+ match get_char!(self, input) {
1459
+ '\t' | '\n' | '\x0C' | ' ' => (),
1460
+ '>' => go!(self: emit_doctype; to Data),
1461
+ _ => {
1462
+ self.bad_char_error();
1463
+ go!(self: force_quirks; reconsume BogusDoctype)
1464
+ }
1465
+ }
1466
+ }
1467
+ },
1468
+
1469
+ //§ after-doctype-public-keyword-state after-doctype-system-keyword-state
1470
+ states::AfterDoctypeKeyword(kind) => loop {
1471
+ match get_char!(self, input) {
1472
+ '\t' | '\n' | '\x0C' | ' ' => go!(self: to BeforeDoctypeIdentifier kind),
1473
+ '"' => {
1474
+ self.bad_char_error();
1475
+ go!(self: clear_doctype_id kind; to DoctypeIdentifierDoubleQuoted kind)
1476
+ }
1477
+ '\'' => {
1478
+ self.bad_char_error();
1479
+ go!(self: clear_doctype_id kind; to DoctypeIdentifierSingleQuoted kind)
1480
+ }
1481
+ '>' => {
1482
+ self.bad_char_error();
1483
+ go!(self: force_quirks; emit_doctype; to Data)
1484
+ }
1485
+ _ => {
1486
+ self.bad_char_error();
1487
+ go!(self: force_quirks; reconsume BogusDoctype)
1488
+ }
1489
+ }
1490
+ },
1491
+
1492
+ //§ before-doctype-public-identifier-state before-doctype-system-identifier-state
1493
+ states::BeforeDoctypeIdentifier(kind) => loop {
1494
+ match get_char!(self, input) {
1495
+ '\t' | '\n' | '\x0C' | ' ' => (),
1496
+ '"' => go!(self: clear_doctype_id kind; to DoctypeIdentifierDoubleQuoted kind),
1497
+ '\'' => go!(self: clear_doctype_id kind; to DoctypeIdentifierSingleQuoted kind),
1498
+ '>' => {
1499
+ self.bad_char_error();
1500
+ go!(self: force_quirks; emit_doctype; to Data)
1501
+ }
1502
+ _ => {
1503
+ self.bad_char_error();
1504
+ go!(self: force_quirks; reconsume BogusDoctype)
1505
+ }
1506
+ }
1507
+ },
1508
+
1509
+ //§ doctype-public-identifier-(double-quoted)-state doctype-system-identifier-(double-quoted)-state
1510
+ states::DoctypeIdentifierDoubleQuoted(kind) => loop {
1511
+ match get_char!(self, input) {
1512
+ '"' => go!(self: to AfterDoctypeIdentifier kind),
1513
+ '\0' => {
1514
+ self.bad_char_error();
1515
+ go!(self: push_doctype_id kind '\u{fffd}')
1516
+ }
1517
+ '>' => {
1518
+ self.bad_char_error();
1519
+ go!(self: force_quirks; emit_doctype; to Data)
1520
+ }
1521
+ c => go!(self: push_doctype_id kind c),
1522
+ }
1523
+ },
1524
+
1525
+ //§ doctype-public-identifier-(single-quoted)-state doctype-system-identifier-(single-quoted)-state
1526
+ states::DoctypeIdentifierSingleQuoted(kind) => loop {
1527
+ match get_char!(self, input) {
1528
+ '\'' => go!(self: to AfterDoctypeIdentifier kind),
1529
+ '\0' => {
1530
+ self.bad_char_error();
1531
+ go!(self: push_doctype_id kind '\u{fffd}')
1532
+ }
1533
+ '>' => {
1534
+ self.bad_char_error();
1535
+ go!(self: force_quirks; emit_doctype; to Data)
1536
+ }
1537
+ c => go!(self: push_doctype_id kind c),
1538
+ }
1539
+ },
1540
+
1541
+ //§ after-doctype-public-identifier-state
1542
+ states::AfterDoctypeIdentifier(Public) => loop {
1543
+ match get_char!(self, input) {
1544
+ '\t' | '\n' | '\x0C' | ' ' => {
1545
+ go!(self: to BetweenDoctypePublicAndSystemIdentifiers)
1546
+ }
1547
+ '>' => go!(self: emit_doctype; to Data),
1548
+ '"' => {
1549
+ self.bad_char_error();
1550
+ go!(self: clear_doctype_id System; to DoctypeIdentifierDoubleQuoted System)
1551
+ }
1552
+ '\'' => {
1553
+ self.bad_char_error();
1554
+ go!(self: clear_doctype_id System; to DoctypeIdentifierSingleQuoted System)
1555
+ }
1556
+ _ => {
1557
+ self.bad_char_error();
1558
+ go!(self: force_quirks; reconsume BogusDoctype)
1559
+ }
1560
+ }
1561
+ },
1562
+
1563
+ //§ after-doctype-system-identifier-state
1564
+ states::AfterDoctypeIdentifier(System) => loop {
1565
+ match get_char!(self, input) {
1566
+ '\t' | '\n' | '\x0C' | ' ' => (),
1567
+ '>' => go!(self: emit_doctype; to Data),
1568
+ _ => {
1569
+ self.bad_char_error();
1570
+ go!(self: reconsume BogusDoctype)
1571
+ }
1572
+ }
1573
+ },
1574
+
1575
+ //§ between-doctype-public-and-system-identifiers-state
1576
+ states::BetweenDoctypePublicAndSystemIdentifiers => loop {
1577
+ match get_char!(self, input) {
1578
+ '\t' | '\n' | '\x0C' | ' ' => (),
1579
+ '>' => go!(self: emit_doctype; to Data),
1580
+ '"' => {
1581
+ go!(self: clear_doctype_id System; to DoctypeIdentifierDoubleQuoted System)
1582
+ }
1583
+ '\'' => {
1584
+ go!(self: clear_doctype_id System; to DoctypeIdentifierSingleQuoted System)
1585
+ }
1586
+ _ => {
1587
+ self.bad_char_error();
1588
+ go!(self: force_quirks; reconsume BogusDoctype)
1589
+ }
1590
+ }
1591
+ },
1592
+
1593
+ //§ bogus-doctype-state
1594
+ states::BogusDoctype => loop {
1595
+ match get_char!(self, input) {
1596
+ '>' => go!(self: emit_doctype; to Data),
1597
+ '\0' => {
1598
+ self.bad_char_error();
1599
+ }
1600
+ _ => (),
1601
+ }
1602
+ },
1603
+
1604
+ //§ bogus-comment-state
1605
+ states::BogusComment => loop {
1606
+ match get_char!(self, input) {
1607
+ '>' => go!(self: emit_comment; to Data),
1608
+ '\0' => {
1609
+ self.bad_char_error();
1610
+ go!(self: push_comment '\u{fffd}')
1611
+ }
1612
+ c => go!(self: push_comment c),
1613
+ }
1614
+ },
1615
+
1616
+ //§ markup-declaration-open-state
1617
+ states::MarkupDeclarationOpen => loop {
1618
+ if eat_exact!(self, input, "--") {
1619
+ go!(self: clear_comment; to CommentStart);
1620
+ } else if eat!(self, input, "doctype") {
1621
+ go!(self: to Doctype);
1622
+ } else {
1623
+ if self
1624
+ .sink
1625
+ .adjusted_current_node_present_but_not_in_html_namespace()
1626
+ && eat_exact!(self, input, "[CDATA[")
1627
+ {
1628
+ go!(self: clear_temp; to CdataSection);
1629
+ }
1630
+ self.bad_char_error();
1631
+ go!(self: clear_comment; to BogusComment);
1632
+ }
1633
+ },
1634
+
1635
+ //§ cdata-section-state
1636
+ states::CdataSection => loop {
1637
+ match get_char!(self, input) {
1638
+ ']' => go!(self: to CdataSectionBracket),
1639
+ '\0' => {
1640
+ self.emit_temp_buf();
1641
+ self.emit_char('\0');
1642
+ }
1643
+ c => go!(self: push_temp c),
1644
+ }
1645
+ },
1646
+
1647
+ //§ cdata-section-bracket
1648
+ states::CdataSectionBracket => match get_char!(self, input) {
1649
+ ']' => go!(self: to CdataSectionEnd),
1650
+ _ => go!(self: push_temp ']'; reconsume CdataSection),
1651
+ },
1652
+
1653
+ //§ cdata-section-end
1654
+ states::CdataSectionEnd => loop {
1655
+ match get_char!(self, input) {
1656
+ ']' => go!(self: push_temp ']'),
1657
+ '>' => {
1658
+ self.emit_temp_buf();
1659
+ go!(self: to Data);
1660
+ }
1661
+ _ => go!(self: push_temp ']'; push_temp ']'; reconsume CdataSection),
1662
+ }
1663
+ },
1664
+ //§ END
1665
+ }
1666
+ }
1667
+
1668
+ fn step_char_ref_tokenizer(&self, input: &BufferQueue) -> ProcessResult<Sink::Handle> {
1669
+ // FIXME HACK: Take and replace the tokenizer so we don't
1670
+ // double-mut-borrow self. This is why it's boxed.
1671
+ let mut tok = self.char_ref_tokenizer.take().unwrap();
1672
+ let outcome = tok.step(self, input);
1673
+
1674
+ let progress = match outcome {
1675
+ char_ref::Done => {
1676
+ self.process_char_ref(tok.get_result());
1677
+ return ProcessResult::Continue;
1678
+ }
1679
+
1680
+ char_ref::Stuck => ProcessResult::Suspend,
1681
+ char_ref::Progress => ProcessResult::Continue,
1682
+ };
1683
+
1684
+ *self.char_ref_tokenizer.borrow_mut() = Some(tok);
1685
+ progress
1686
+ }
1687
+
1688
+ fn process_char_ref(&self, char_ref: CharRef) {
1689
+ let CharRef {
1690
+ mut chars,
1691
+ mut num_chars,
1692
+ } = char_ref;
1693
+
1694
+ if num_chars == 0 {
1695
+ chars[0] = '&';
1696
+ num_chars = 1;
1697
+ }
1698
+
1699
+ for i in 0..num_chars {
1700
+ let c = chars[i as usize];
1701
+ let prefix = (self.opts.rusteerio_codec && ('\u{e000}'..='\u{e800}').contains(&c))
1702
+ .then_some('\u{e800}');
1703
+ for c in prefix.into_iter().chain(std::iter::once(c)) {
1704
+ match self.state.get() {
1705
+ states::Data | states::RawData(states::Rcdata) => self.emit_char(c),
1706
+
1707
+ states::AttributeValue(_) => go!(self: push_value c),
1708
+
1709
+ _ => panic!(
1710
+ "state {:?} should not be reachable in process_char_ref",
1711
+ self.state.get()
1712
+ ),
1713
+ }
1714
+ }
1715
+ }
1716
+ }
1717
+
1718
+ /// Indicate that we have reached the end of the input.
1719
+ pub fn end(&self) {
1720
+ // Handle EOF in the char ref sub-tokenizer, if there is one.
1721
+ // Do this first because it might un-consume stuff.
1722
+ let input = BufferQueue::default();
1723
+ match self.char_ref_tokenizer.take() {
1724
+ None => (),
1725
+ Some(mut tok) => {
1726
+ tok.end_of_file(self, &input);
1727
+ self.process_char_ref(tok.get_result());
1728
+ }
1729
+ }
1730
+
1731
+ // Process all remaining buffered input.
1732
+ // If we're waiting for lookahead, we're not gonna get it.
1733
+ self.at_eof.set(true);
1734
+ assert!(matches!(self.run(&input), TokenizerResult::Done));
1735
+ assert!(input.is_empty());
1736
+
1737
+ loop {
1738
+ match self.eof_step() {
1739
+ ProcessResult::Continue => (),
1740
+ ProcessResult::Suspend => break,
1741
+ ProcessResult::Script(_) => unreachable!(),
1742
+ }
1743
+ }
1744
+
1745
+ self.sink.end();
1746
+
1747
+ if self.opts.profile {
1748
+ self.dump_profile();
1749
+ }
1750
+ }
1751
+
1752
+ fn dump_profile(&self) {
1753
+ let mut results: Vec<(states::State, u64)> = self
1754
+ .state_profile
1755
+ .borrow()
1756
+ .iter()
1757
+ .map(|(s, t)| (*s, *t))
1758
+ .collect();
1759
+ results.sort_by(|&(_, x), &(_, y)| y.cmp(&x));
1760
+
1761
+ let total: u64 = results
1762
+ .iter()
1763
+ .map(|&(_, t)| t)
1764
+ .fold(0, ::std::ops::Add::add);
1765
+ println!("\nTokenizer profile, in nanoseconds");
1766
+ println!(
1767
+ "\n{:12} total in token sink",
1768
+ self.time_in_sink.get()
1769
+ );
1770
+ println!("\n{total:12} total in tokenizer");
1771
+
1772
+ for (k, v) in results.into_iter() {
1773
+ let pct = 100.0 * (v as f64) / (total as f64);
1774
+ println!("{v:12} {pct:4.1}% {k:?}");
1775
+ }
1776
+ }
1777
+
1778
+ fn eof_step(&self) -> ProcessResult<Sink::Handle> {
1779
+ debug!("processing EOF in state {:?}", self.state.get());
1780
+ match self.state.get() {
1781
+ states::Data
1782
+ | states::RawData(Rcdata)
1783
+ | states::RawData(Rawtext)
1784
+ | states::RawData(ScriptData)
1785
+ | states::Plaintext => go!(self: eof),
1786
+
1787
+ states::TagName
1788
+ | states::RawData(ScriptDataEscaped(_))
1789
+ | states::BeforeAttributeName
1790
+ | states::AttributeName
1791
+ | states::AfterAttributeName
1792
+ | states::AttributeValue(_)
1793
+ | states::AfterAttributeValueQuoted
1794
+ | states::SelfClosingStartTag
1795
+ | states::ScriptDataEscapedDash(_)
1796
+ | states::ScriptDataEscapedDashDash(_) => {
1797
+ self.bad_eof_error();
1798
+ go!(self: to Data)
1799
+ }
1800
+
1801
+ states::BeforeAttributeValue => go!(self: reconsume AttributeValue Unquoted),
1802
+
1803
+ states::TagOpen => {
1804
+ self.bad_eof_error();
1805
+ self.emit_char('<');
1806
+ go!(self: to Data);
1807
+ }
1808
+
1809
+ states::EndTagOpen => {
1810
+ self.bad_eof_error();
1811
+ self.emit_char('<');
1812
+ self.emit_char('/');
1813
+ go!(self: to Data);
1814
+ }
1815
+
1816
+ states::RawLessThanSign(ScriptDataEscaped(DoubleEscaped)) => {
1817
+ go!(self: to RawData ScriptDataEscaped DoubleEscaped)
1818
+ }
1819
+
1820
+ states::RawLessThanSign(kind) => {
1821
+ self.emit_char('<');
1822
+ go!(self: to RawData kind);
1823
+ }
1824
+
1825
+ states::RawEndTagOpen(kind) => {
1826
+ self.emit_char('<');
1827
+ self.emit_char('/');
1828
+ go!(self: to RawData kind);
1829
+ }
1830
+
1831
+ states::RawEndTagName(kind) => {
1832
+ self.emit_char('<');
1833
+ self.emit_char('/');
1834
+ self.emit_temp_buf();
1835
+ go!(self: to RawData kind)
1836
+ }
1837
+
1838
+ states::ScriptDataEscapeStart(kind) => go!(self: to RawData ScriptDataEscaped kind),
1839
+
1840
+ states::ScriptDataEscapeStartDash => go!(self: to RawData ScriptData),
1841
+
1842
+ states::ScriptDataDoubleEscapeEnd => {
1843
+ go!(self: to RawData ScriptDataEscaped DoubleEscaped)
1844
+ }
1845
+
1846
+ states::CommentStart
1847
+ | states::CommentStartDash
1848
+ | states::Comment
1849
+ | states::CommentEndDash
1850
+ | states::CommentEnd
1851
+ | states::CommentEndBang => {
1852
+ self.bad_eof_error();
1853
+ go!(self: emit_comment; to Data)
1854
+ }
1855
+
1856
+ states::CommentLessThanSign | states::CommentLessThanSignBang => {
1857
+ go!(self: reconsume Comment)
1858
+ }
1859
+
1860
+ states::CommentLessThanSignBangDash => go!(self: reconsume CommentEndDash),
1861
+
1862
+ states::CommentLessThanSignBangDashDash => go!(self: reconsume CommentEnd),
1863
+
1864
+ states::Doctype | states::BeforeDoctypeName => {
1865
+ self.bad_eof_error();
1866
+ go!(self: create_doctype; force_quirks; emit_doctype; to Data)
1867
+ }
1868
+
1869
+ states::DoctypeName
1870
+ | states::AfterDoctypeName
1871
+ | states::AfterDoctypeKeyword(_)
1872
+ | states::BeforeDoctypeIdentifier(_)
1873
+ | states::DoctypeIdentifierDoubleQuoted(_)
1874
+ | states::DoctypeIdentifierSingleQuoted(_)
1875
+ | states::AfterDoctypeIdentifier(_)
1876
+ | states::BetweenDoctypePublicAndSystemIdentifiers => {
1877
+ self.bad_eof_error();
1878
+ go!(self: force_quirks; emit_doctype; to Data)
1879
+ }
1880
+
1881
+ states::BogusDoctype => go!(self: emit_doctype; to Data),
1882
+
1883
+ states::BogusComment => go!(self: emit_comment; to Data),
1884
+
1885
+ states::MarkupDeclarationOpen => {
1886
+ self.bad_char_error();
1887
+ go!(self: to BogusComment)
1888
+ }
1889
+
1890
+ states::CdataSection => {
1891
+ self.emit_temp_buf();
1892
+ self.bad_eof_error();
1893
+ go!(self: to Data)
1894
+ }
1895
+
1896
+ states::CdataSectionBracket => go!(self: push_temp ']'; to CdataSection),
1897
+
1898
+ states::CdataSectionEnd => go!(self: push_temp ']'; push_temp ']'; to CdataSection),
1899
+ }
1900
+ }
1901
+
1902
+ /// Checks for supported SIMD feature, which is now either SSE2 for x86/x86_64 or NEON for aarch64.
1903
+ fn is_supported_simd_feature_detected() -> bool {
1904
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
1905
+ {
1906
+ is_x86_feature_detected!("sse2")
1907
+ }
1908
+
1909
+ #[cfg(target_arch = "aarch64")]
1910
+ {
1911
+ std::arch::is_aarch64_feature_detected!("neon")
1912
+ }
1913
+
1914
+ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
1915
+ false
1916
+ }
1917
+
1918
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
1919
+ /// Implements the [data state] with SIMD instructions.
1920
+ /// Calls SSE2- or NEON-specific function for chunks and processes any remaining bytes.
1921
+ ///
1922
+ /// The algorithm implemented is the naive SIMD approach described [here].
1923
+ ///
1924
+ /// ### SAFETY:
1925
+ /// Calling this function on a CPU that supports neither SSE2 nor NEON causes undefined behaviour.
1926
+ ///
1927
+ /// [data state]: https://html.spec.whatwg.org/#data-state
1928
+ /// [here]: https://lemire.me/blog/2024/06/08/scan-html-faster-with-simd-instructions-chrome-edition/
1929
+ unsafe fn data_state_simd_fast_path(&self, input: &mut StrTendril) -> Option<SetResult> {
1930
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
1931
+ let (mut i, mut n_newlines) = self.data_state_sse2_fast_path(input);
1932
+
1933
+ #[cfg(target_arch = "aarch64")]
1934
+ let (mut i, mut n_newlines) = self.data_state_neon_fast_path(input);
1935
+
1936
+ // Process any remaining bytes (less than STRIDE)
1937
+ while let Some(c) = input.as_bytes().get(i) {
1938
+ if matches!(*c, b'<' | b'&' | b'\r' | b'\0') {
1939
+ break;
1940
+ }
1941
+ if *c == b'\n' {
1942
+ n_newlines += 1;
1943
+ }
1944
+
1945
+ i += 1;
1946
+ }
1947
+
1948
+ let set_result = if i == 0 {
1949
+ let first_char = input.pop_front_char().unwrap();
1950
+ debug_assert!(matches!(first_char, '<' | '&' | '\r' | '\0'));
1951
+
1952
+ // FIXME: Passing a bogus input queue is only relevant when c is \n, which can never happen in this case.
1953
+ // Still, it would be nice to not have to do that.
1954
+ // The same is true for the unwrap call.
1955
+ let preprocessed_char = self
1956
+ .get_preprocessed_char(first_char, &BufferQueue::default())
1957
+ .unwrap();
1958
+ SetResult::FromSet(preprocessed_char)
1959
+ } else {
1960
+ debug_assert!(
1961
+ input.len() >= i,
1962
+ "Trying to remove {:?} bytes from a tendril that is only {:?} bytes long",
1963
+ i,
1964
+ input.len()
1965
+ );
1966
+ let consumed_chunk = input.unsafe_subtendril(0, i as u32);
1967
+ input.unsafe_pop_front(i as u32);
1968
+ SetResult::NotFromSet(consumed_chunk)
1969
+ };
1970
+
1971
+ self.current_line.set(self.current_line.get() + n_newlines);
1972
+
1973
+ Some(set_result)
1974
+ }
1975
+
1976
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
1977
+ #[target_feature(enable = "sse2")]
1978
+ /// Implements the [data state] with SSE2 instructions for x86/x86_64.
1979
+ /// Returns a pair of the number of bytes processed and the number of newlines found.
1980
+ ///
1981
+ /// ### SAFETY:
1982
+ /// Calling this function on a CPU that does not support NEON causes undefined behaviour.
1983
+ ///
1984
+ /// [data state]: https://html.spec.whatwg.org/#data-state
1985
+ unsafe fn data_state_sse2_fast_path(&self, input: &mut StrTendril) -> (usize, u64) {
1986
+ #[cfg(target_arch = "x86")]
1987
+ use std::arch::x86::{
1988
+ __m128i, _mm_cmpeq_epi8, _mm_loadu_si128, _mm_movemask_epi8, _mm_or_si128,
1989
+ _mm_set1_epi8,
1990
+ };
1991
+ #[cfg(target_arch = "x86_64")]
1992
+ use std::arch::x86_64::{
1993
+ __m128i, _mm_cmpeq_epi8, _mm_loadu_si128, _mm_movemask_epi8, _mm_or_si128,
1994
+ _mm_set1_epi8,
1995
+ };
1996
+
1997
+ debug_assert!(!input.is_empty());
1998
+
1999
+ let quote_mask = _mm_set1_epi8('<' as i8);
2000
+ let escape_mask = _mm_set1_epi8('&' as i8);
2001
+ let carriage_return_mask = _mm_set1_epi8('\r' as i8);
2002
+ let zero_mask = _mm_set1_epi8('\0' as i8);
2003
+ let newline_mask = _mm_set1_epi8('\n' as i8);
2004
+
2005
+ let raw_bytes: &[u8] = input.as_bytes();
2006
+ let start = raw_bytes.as_ptr();
2007
+
2008
+ const STRIDE: usize = 16;
2009
+ let mut i = 0;
2010
+ let mut n_newlines = 0;
2011
+ while i + STRIDE <= raw_bytes.len() {
2012
+ // Load a 16 byte chunk from the input
2013
+ let data = _mm_loadu_si128(start.add(i) as *const __m128i);
2014
+
2015
+ // Compare the chunk against each mask
2016
+ let quotes = _mm_cmpeq_epi8(data, quote_mask);
2017
+ let escapes = _mm_cmpeq_epi8(data, escape_mask);
2018
+ let carriage_returns = _mm_cmpeq_epi8(data, carriage_return_mask);
2019
+ let zeros = _mm_cmpeq_epi8(data, zero_mask);
2020
+ let newlines = _mm_cmpeq_epi8(data, newline_mask);
2021
+
2022
+ // Combine all test results and create a bitmask from them.
2023
+ // Each bit in the mask will be 1 if the character at the bit position is in the set and 0 otherwise.
2024
+ let test_result = _mm_or_si128(
2025
+ _mm_or_si128(quotes, zeros),
2026
+ _mm_or_si128(escapes, carriage_returns),
2027
+ );
2028
+ let bitmask = _mm_movemask_epi8(test_result);
2029
+ let newline_mask = _mm_movemask_epi8(newlines);
2030
+
2031
+ if (bitmask != 0) {
2032
+ // We have reached one of the characters that cause the state machine to transition
2033
+ let position = if cfg!(target_endian = "little") {
2034
+ bitmask.trailing_zeros() as usize
2035
+ } else {
2036
+ bitmask.leading_zeros() as usize
2037
+ };
2038
+
2039
+ n_newlines += (newline_mask & ((1 << position) - 1)).count_ones() as u64;
2040
+ i += position;
2041
+ break;
2042
+ } else {
2043
+ n_newlines += newline_mask.count_ones() as u64;
2044
+ }
2045
+
2046
+ i += STRIDE;
2047
+ }
2048
+
2049
+ (i, n_newlines)
2050
+ }
2051
+
2052
+ #[cfg(target_arch = "aarch64")]
2053
+ #[target_feature(enable = "neon")]
2054
+ /// Implements the [data state] with NEON SIMD instructions for AArch64.
2055
+ /// Returns a pair of the number of bytes processed and the number of newlines found.
2056
+ ///
2057
+ /// ### SAFETY:
2058
+ /// Calling this function on a CPU that does not support NEON causes undefined behaviour.
2059
+ ///
2060
+ /// [data state]: https://html.spec.whatwg.org/#data-state
2061
+ unsafe fn data_state_neon_fast_path(&self, input: &mut StrTendril) -> (usize, u64) {
2062
+ use std::arch::aarch64::{vceqq_u8, vdupq_n_u8, vld1q_u8, vmaxvq_u8, vorrq_u8};
2063
+
2064
+ debug_assert!(!input.is_empty());
2065
+
2066
+ let quote_mask = vdupq_n_u8(b'<');
2067
+ let escape_mask = vdupq_n_u8(b'&');
2068
+ let carriage_return_mask = vdupq_n_u8(b'\r');
2069
+ let zero_mask = vdupq_n_u8(b'\0');
2070
+ let newline_mask = vdupq_n_u8(b'\n');
2071
+
2072
+ let raw_bytes: &[u8] = input.as_bytes();
2073
+ let start = raw_bytes.as_ptr();
2074
+
2075
+ const STRIDE: usize = 16;
2076
+ let mut i = 0;
2077
+ let mut n_newlines = 0;
2078
+ while i + STRIDE <= raw_bytes.len() {
2079
+ // Load a 16 byte chunk from the input
2080
+ let data = vld1q_u8(start.add(i));
2081
+
2082
+ // Compare the chunk against each mask
2083
+ let quotes = vceqq_u8(data, quote_mask);
2084
+ let escapes = vceqq_u8(data, escape_mask);
2085
+ let carriage_returns = vceqq_u8(data, carriage_return_mask);
2086
+ let zeros = vceqq_u8(data, zero_mask);
2087
+ let newlines = vceqq_u8(data, newline_mask);
2088
+
2089
+ // Combine all test results and create a bitmask from them.
2090
+ // Each bit in the mask will be 1 if the character at the bit position is in the set and 0 otherwise.
2091
+ let test_result =
2092
+ vorrq_u8(vorrq_u8(quotes, zeros), vorrq_u8(escapes, carriage_returns));
2093
+ let bitmask = vmaxvq_u8(test_result);
2094
+ let newline_mask = vmaxvq_u8(newlines);
2095
+ if bitmask != 0 {
2096
+ // We have reached one of the characters that cause the state machine to transition
2097
+ let chunk_bytes = std::slice::from_raw_parts(start.add(i), STRIDE);
2098
+ let position = chunk_bytes
2099
+ .iter()
2100
+ .position(|&b| matches!(b, b'<' | b'&' | b'\r' | b'\0'))
2101
+ .unwrap();
2102
+
2103
+ n_newlines += chunk_bytes[..position]
2104
+ .iter()
2105
+ .filter(|&&b| b == b'\n')
2106
+ .count() as u64;
2107
+
2108
+ i += position;
2109
+ break;
2110
+ } else if newline_mask != 0 {
2111
+ let chunk_bytes = std::slice::from_raw_parts(start.add(i), STRIDE);
2112
+ n_newlines += chunk_bytes.iter().filter(|&&b| b == b'\n').count() as u64;
2113
+ }
2114
+
2115
+ i += STRIDE;
2116
+ }
2117
+
2118
+ (i, n_newlines)
2119
+ }
2120
+ }
2121
+
2122
+ #[cfg(test)]
2123
+ #[allow(non_snake_case)]
2124
+ mod test {
2125
+ use super::option_push; // private items
2126
+ use crate::tendril::{SliceExt, StrTendril};
2127
+
2128
+ use super::{TokenSink, TokenSinkResult, Tokenizer, TokenizerOpts};
2129
+
2130
+ use super::interface::{CharacterTokens, EOFToken, NullCharacterToken, ParseError};
2131
+ use super::interface::{EndTag, StartTag, Tag, TagKind};
2132
+ use super::interface::{TagToken, Token};
2133
+
2134
+ use markup5ever::buffer_queue::BufferQueue;
2135
+ use std::cell::RefCell;
2136
+
2137
+ use crate::LocalName;
2138
+
2139
+ // LinesMatch implements the TokenSink trait. It is used for testing to see
2140
+ // if current_line is being updated when process_token is called. The lines
2141
+ // vector is a collection of the line numbers that each token is on.
2142
+ struct LinesMatch {
2143
+ tokens: RefCell<Vec<Token>>,
2144
+ current_str: RefCell<StrTendril>,
2145
+ lines: RefCell<Vec<(Token, u64)>>,
2146
+ }
2147
+
2148
+ impl LinesMatch {
2149
+ fn new() -> LinesMatch {
2150
+ LinesMatch {
2151
+ tokens: RefCell::new(vec![]),
2152
+ current_str: RefCell::new(StrTendril::new()),
2153
+ lines: RefCell::new(vec![]),
2154
+ }
2155
+ }
2156
+
2157
+ fn push(&self, token: Token, line_number: u64) {
2158
+ self.finish_str();
2159
+ self.lines.borrow_mut().push((token, line_number));
2160
+ }
2161
+
2162
+ fn finish_str(&self) {
2163
+ if !self.current_str.borrow().is_empty() {
2164
+ let s = self.current_str.take();
2165
+ self.tokens.borrow_mut().push(CharacterTokens(s));
2166
+ }
2167
+ }
2168
+ }
2169
+
2170
+ impl TokenSink for LinesMatch {
2171
+ type Handle = ();
2172
+
2173
+ fn process_token(&self, token: Token, line_number: u64) -> TokenSinkResult<Self::Handle> {
2174
+ match token {
2175
+ CharacterTokens(b) => {
2176
+ self.current_str.borrow_mut().push_slice(&b);
2177
+ }
2178
+
2179
+ NullCharacterToken => {
2180
+ self.current_str.borrow_mut().push_char('\0');
2181
+ }
2182
+
2183
+ ParseError(_) => {
2184
+ panic!("unexpected parse error");
2185
+ }
2186
+
2187
+ TagToken(mut t) => {
2188
+ // The spec seems to indicate that one can emit
2189
+ // erroneous end tags with attrs, but the test
2190
+ // cases don't contain them.
2191
+ match t.kind {
2192
+ EndTag => {
2193
+ t.self_closing = false;
2194
+ t.attrs = vec![];
2195
+ }
2196
+ _ => t.attrs.sort_by(|a1, a2| a1.name.cmp(&a2.name)),
2197
+ }
2198
+ self.push(TagToken(t), line_number);
2199
+ }
2200
+
2201
+ EOFToken => (),
2202
+
2203
+ _ => self.push(token, line_number),
2204
+ }
2205
+ TokenSinkResult::Continue
2206
+ }
2207
+ }
2208
+
2209
+ // Take in tokens, process them, and return vector with line
2210
+ // numbers that each token is on
2211
+ fn tokenize(input: Vec<StrTendril>, opts: TokenizerOpts) -> Vec<(Token, u64)> {
2212
+ let sink = LinesMatch::new();
2213
+ let tok = Tokenizer::new(sink, opts);
2214
+ let buffer = BufferQueue::default();
2215
+ for chunk in input.into_iter() {
2216
+ buffer.push_back(chunk);
2217
+ let _ = tok.feed(&buffer);
2218
+ }
2219
+ tok.end();
2220
+ tok.sink.lines.take()
2221
+ }
2222
+
2223
+ // Create a tag token
2224
+ fn create_tag(token: StrTendril, tagkind: TagKind) -> Token {
2225
+ let name = LocalName::from(&*token);
2226
+
2227
+ TagToken(Tag {
2228
+ kind: tagkind,
2229
+ name,
2230
+ self_closing: false,
2231
+ attrs: vec![],
2232
+ })
2233
+ }
2234
+
2235
+ #[test]
2236
+ fn push_to_None_gives_singleton() {
2237
+ let mut s: Option<StrTendril> = None;
2238
+ option_push(&mut s, 'x');
2239
+ assert_eq!(s, Some("x".to_tendril()));
2240
+ }
2241
+
2242
+ #[test]
2243
+ fn push_to_empty_appends() {
2244
+ let mut s: Option<StrTendril> = Some(StrTendril::new());
2245
+ option_push(&mut s, 'x');
2246
+ assert_eq!(s, Some("x".to_tendril()));
2247
+ }
2248
+
2249
+ #[test]
2250
+ fn push_to_nonempty_appends() {
2251
+ let mut s: Option<StrTendril> = Some(StrTendril::from_slice("y"));
2252
+ option_push(&mut s, 'x');
2253
+ assert_eq!(s, Some("yx".to_tendril()));
2254
+ }
2255
+
2256
+ #[test]
2257
+ fn check_lines() {
2258
+ let opts = TokenizerOpts {
2259
+ exact_errors: false,
2260
+ discard_bom: true,
2261
+ profile: false,
2262
+ initial_state: None,
2263
+ last_start_tag_name: None,
2264
+ };
2265
+ let vector = vec![
2266
+ StrTendril::from("<a>\n"),
2267
+ StrTendril::from("<b>\n"),
2268
+ StrTendril::from("</b>\n"),
2269
+ StrTendril::from("</a>\n"),
2270
+ ];
2271
+ let expected = vec![
2272
+ (create_tag(StrTendril::from("a"), StartTag), 1),
2273
+ (create_tag(StrTendril::from("b"), StartTag), 2),
2274
+ (create_tag(StrTendril::from("b"), EndTag), 3),
2275
+ (create_tag(StrTendril::from("a"), EndTag), 4),
2276
+ ];
2277
+ let results = tokenize(vector, opts);
2278
+ assert_eq!(results, expected);
2279
+ }
2280
+
2281
+ #[test]
2282
+ fn check_lines_with_new_line() {
2283
+ let opts = TokenizerOpts {
2284
+ exact_errors: false,
2285
+ discard_bom: true,
2286
+ profile: false,
2287
+ initial_state: None,
2288
+ last_start_tag_name: None,
2289
+ };
2290
+ let vector = vec![
2291
+ StrTendril::from("<a>\r\n"),
2292
+ StrTendril::from("<b>\r\n"),
2293
+ StrTendril::from("</b>\r\n"),
2294
+ StrTendril::from("</a>\r\n"),
2295
+ ];
2296
+ let expected = vec![
2297
+ (create_tag(StrTendril::from("a"), StartTag), 1),
2298
+ (create_tag(StrTendril::from("b"), StartTag), 2),
2299
+ (create_tag(StrTendril::from("b"), EndTag), 3),
2300
+ (create_tag(StrTendril::from("a"), EndTag), 4),
2301
+ ];
2302
+ let results = tokenize(vector, opts);
2303
+ assert_eq!(results, expected);
2304
+ }
2305
+ }