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.
- package/CONTRIBUTING.md +73 -0
- package/Cargo.lock +1051 -0
- package/Cargo.toml +36 -0
- package/LICENSE +21 -0
- package/README.md +252 -2
- package/RELEASING.md +138 -0
- package/THIRD_PARTY_NOTICES.md +7443 -0
- package/build.rs +3 -0
- package/dist/chunk-DKULX6VO.mjs +3185 -0
- package/dist/chunk-DKULX6VO.mjs.map +1 -0
- package/dist/chunk-OHPGEY3E.mjs +32 -0
- package/dist/chunk-OHPGEY3E.mjs.map +1 -0
- package/dist/index.cjs +3230 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +113 -0
- package/dist/index.d.ts +113 -0
- package/dist/index.mjs +38 -0
- package/dist/index.mjs.map +1 -0
- package/dist/slim.cjs +2441 -0
- package/dist/slim.cjs.map +1 -0
- package/dist/slim.d.mts +11 -0
- package/dist/slim.d.ts +11 -0
- package/dist/slim.mjs +19 -0
- package/dist/slim.mjs.map +1 -0
- package/dist/types-BP6TpxE5.d.mts +532 -0
- package/dist/types-BP6TpxE5.d.ts +532 -0
- package/dist/utils.cjs +61 -0
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.d.mts +13 -0
- package/dist/utils.d.ts +13 -0
- package/dist/utils.mjs +17 -0
- package/dist/utils.mjs.map +1 -0
- package/native/src/arena.rs +1781 -0
- package/native/src/attributes.rs +923 -0
- package/native/src/codec.rs +172 -0
- package/native/src/diagnostics.rs +1249 -0
- package/native/src/extraction.rs +226 -0
- package/native/src/gc.rs +166 -0
- package/native/src/hook_sink.rs +727 -0
- package/native/src/hook_stream.rs +513 -0
- package/native/src/lib.rs +2095 -0
- package/native/src/location.rs +392 -0
- package/native/src/runtime.rs +189 -0
- package/native/src/selector.rs +1287 -0
- package/native/src/sink.rs +261 -0
- package/native/src/stream.rs +334 -0
- package/native/src/tokenizer.rs +355 -0
- package/native/src/tracker.rs +232 -0
- package/native/vendor/README.md +25 -0
- package/native/vendor/html5ever-0.35.0/.cargo_vcs_info.json +6 -0
- package/native/vendor/html5ever-0.35.0/Cargo.lock +828 -0
- package/native/vendor/html5ever-0.35.0/Cargo.toml +85 -0
- package/native/vendor/html5ever-0.35.0/LICENSE-APACHE +201 -0
- package/native/vendor/html5ever-0.35.0/LICENSE-MIT +25 -0
- package/native/vendor/html5ever-0.35.0/README.md +60 -0
- package/native/vendor/html5ever-0.35.0/benches/html5ever.rs +86 -0
- package/native/vendor/html5ever-0.35.0/data/bench/lipsum-zh.html +19 -0
- package/native/vendor/html5ever-0.35.0/data/bench/lipsum.html +40 -0
- package/native/vendor/html5ever-0.35.0/data/bench/medium-fragment.html +24 -0
- package/native/vendor/html5ever-0.35.0/data/bench/small-fragment.html +7 -0
- package/native/vendor/html5ever-0.35.0/data/bench/strong.html +1 -0
- package/native/vendor/html5ever-0.35.0/data/bench/tiny-fragment.html +1 -0
- package/native/vendor/html5ever-0.35.0/examples/arena.rs +356 -0
- package/native/vendor/html5ever-0.35.0/examples/capi/tokenize.c +74 -0
- package/native/vendor/html5ever-0.35.0/examples/noop-tokenize.rs +47 -0
- package/native/vendor/html5ever-0.35.0/examples/noop-tree-builder.rs +132 -0
- package/native/vendor/html5ever-0.35.0/examples/print-tree-actions.rs +185 -0
- package/native/vendor/html5ever-0.35.0/examples/tokenize.rs +111 -0
- package/native/vendor/html5ever-0.35.0/src/driver.rs +148 -0
- package/native/vendor/html5ever-0.35.0/src/lib.rs +30 -0
- package/native/vendor/html5ever-0.35.0/src/macros.rs +34 -0
- package/native/vendor/html5ever-0.35.0/src/serialize/mod.rs +255 -0
- package/native/vendor/html5ever-0.35.0/src/tokenizer/char_ref/mod.rs +451 -0
- package/native/vendor/html5ever-0.35.0/src/tokenizer/interface.rs +99 -0
- package/native/vendor/html5ever-0.35.0/src/tokenizer/mod.rs +2305 -0
- package/native/vendor/html5ever-0.35.0/src/tokenizer/states.rs +97 -0
- package/native/vendor/html5ever-0.35.0/src/tree_builder/data.rs +170 -0
- package/native/vendor/html5ever-0.35.0/src/tree_builder/mod.rs +1848 -0
- package/native/vendor/html5ever-0.35.0/src/tree_builder/rules.rs +1494 -0
- package/native/vendor/html5ever-0.35.0/src/tree_builder/tag_sets.rs +114 -0
- package/native/vendor/html5ever-0.35.0/src/tree_builder/types.rs +91 -0
- package/native/vendor/html5ever-0.35.0/src/util/str.rs +52 -0
- package/native/vendor/html5gum-0.8.4/Cargo.lock +1006 -0
- package/native/vendor/html5gum-0.8.4/Cargo.toml +109 -0
- package/native/vendor/html5gum-0.8.4/LICENSE +19 -0
- package/native/vendor/html5gum-0.8.4/README.md +110 -0
- package/native/vendor/html5gum-0.8.4/benches/files/0050.html +2131 -0
- package/native/vendor/html5gum-0.8.4/benches/files/6D30.html +1004 -0
- package/native/vendor/html5gum-0.8.4/benches/files.rs +32 -0
- package/native/vendor/html5gum-0.8.4/benches/patterns.rs +31 -0
- package/native/vendor/html5gum-0.8.4/src/arrayvec.rs +29 -0
- package/native/vendor/html5gum-0.8.4/src/char_validator.rs +134 -0
- package/native/vendor/html5gum-0.8.4/src/emitters/callback.rs +676 -0
- package/native/vendor/html5gum-0.8.4/src/emitters/default.rs +207 -0
- package/native/vendor/html5gum-0.8.4/src/emitters/emitter.rs +344 -0
- package/native/vendor/html5gum-0.8.4/src/emitters/html5ever.rs +306 -0
- package/native/vendor/html5gum-0.8.4/src/emitters/mod.rs +25 -0
- package/native/vendor/html5gum-0.8.4/src/entities.rs +3342 -0
- package/native/vendor/html5gum-0.8.4/src/error.rs +96 -0
- package/native/vendor/html5gum-0.8.4/src/htmlstring.rs +130 -0
- package/native/vendor/html5gum-0.8.4/src/lib.rs +66 -0
- package/native/vendor/html5gum-0.8.4/src/machine.rs +2136 -0
- package/native/vendor/html5gum-0.8.4/src/machine_helper.rs +277 -0
- package/native/vendor/html5gum-0.8.4/src/read_helper.rs +244 -0
- package/native/vendor/html5gum-0.8.4/src/reader.rs +440 -0
- package/native/vendor/html5gum-0.8.4/src/span.rs +98 -0
- package/native/vendor/html5gum-0.8.4/src/state.rs +16 -0
- package/native/vendor/html5gum-0.8.4/src/testutils.rs +71 -0
- package/native/vendor/html5gum-0.8.4/src/tokenizer.rs +189 -0
- package/native/vendor/html5gum-0.8.4/src/utils.rs +88 -0
- package/native/vendor/markup5ever-0.35.0/.cargo_vcs_info.json +6 -0
- package/native/vendor/markup5ever-0.35.0/Cargo.toml +45 -0
- package/native/vendor/markup5ever-0.35.0/LICENSE-APACHE +201 -0
- package/native/vendor/markup5ever-0.35.0/LICENSE-MIT +25 -0
- package/native/vendor/markup5ever-0.35.0/interface/mod.rs +422 -0
- package/native/vendor/markup5ever-0.35.0/interface/tree_builder.rs +280 -0
- package/native/vendor/markup5ever-0.35.0/lib.rs +59 -0
- package/native/vendor/markup5ever-0.35.0/serialize.rs +77 -0
- package/native/vendor/markup5ever-0.35.0/util/buffer_queue.rs +333 -0
- package/native/vendor/markup5ever-0.35.0/util/smallcharset.rs +88 -0
- package/package.json +137 -4
- package/rusteerio.darwin-arm64.node +0 -0
- package/rusteerio.darwin-x64.node +0 -0
- package/rusteerio.linux-arm64-gnu.node +0 -0
- package/rusteerio.linux-arm64-musl.node +0 -0
- package/rusteerio.linux-x64-gnu.node +0 -0
- package/rusteerio.linux-x64-musl.node +0 -0
- package/rusteerio.win32-arm64-msvc.node +0 -0
- package/rusteerio.win32-x64-msvc.node +0 -0
- package/scripts/check-native.mjs +15 -0
- package/scripts/check-release.mjs +235 -0
- package/scripts/fix-declarations.mjs +27 -0
- package/scripts/generate-notices.mjs +176 -0
- package/scripts/notices/README.md +13 -0
- package/scripts/notices/cheerio-MIT.txt +21 -0
- package/scripts/notices/html5ever-AUTHORS.txt +20 -0
- package/scripts/notices/html5ever-COPYRIGHT.txt +8 -0
- package/scripts/notices/html5ever-LICENSE-APACHE.txt +201 -0
- package/scripts/notices/html5ever-LICENSE-MIT.txt +25 -0
- package/scripts/notices/napi-rs-MIT.txt +43 -0
- package/scripts/notices/neon-MIT.txt +19 -0
- package/scripts/notices/runtime-01c266bced4a434da005.txt +3 -0
- package/scripts/notices/runtime-0a54e647fe54104658b5.txt +14 -0
- package/scripts/notices/runtime-0b74dfa0bcee5c420c6b.txt +25 -0
- package/scripts/notices/runtime-0d542e0c8804e39aa7f3.txt +177 -0
- package/scripts/notices/runtime-0f96a83840e146e43c0e.txt +21 -0
- package/scripts/notices/runtime-123a331b5dbf04c30097.txt +25 -0
- package/scripts/notices/runtime-172020dbfd5b53a226df.txt +34 -0
- package/scripts/notices/runtime-209fbbe0ad52d9235e37.txt +26 -0
- package/scripts/notices/runtime-23f18e03dc49df91622f.txt +23 -0
- package/scripts/notices/runtime-268872b9816f90fd8e85.txt +220 -0
- package/scripts/notices/runtime-35242e7a83f69875e6ed.txt +176 -0
- package/scripts/notices/runtime-378f5840b258e2779c39.txt +25 -0
- package/scripts/notices/runtime-4108245a1f2df9d4e94d.txt +25 -0
- package/scripts/notices/runtime-4455bf75a91154108304.txt +21 -0
- package/scripts/notices/runtime-553fffcd9b1cb158bc3e.txt +13 -0
- package/scripts/notices/runtime-62c7a1e35f56406896d7.txt +176 -0
- package/scripts/notices/runtime-6485b8ed310d3f0340bf.txt +25 -0
- package/scripts/notices/runtime-69fef7b0f322a6555415.txt +201 -0
- package/scripts/notices/runtime-6df43f6f4b5d4587f3d8.txt +187 -0
- package/scripts/notices/runtime-799e9ca9d179295ef372.txt +24 -0
- package/scripts/notices/runtime-7b63ecd5f1902af1b637.txt +25 -0
- package/scripts/notices/runtime-7bc7d5de2a0b793d4e1a.txt +21 -0
- package/scripts/notices/runtime-7e12e5df4bae12cb2158.txt +24 -0
- package/scripts/notices/runtime-861399f8c21c042b1105.txt +12 -0
- package/scripts/notices/runtime-8ada45cd9f843acf64e4.txt +201 -0
- package/scripts/notices/runtime-90eb64f0279b0d9432ac.txt +12 -0
- package/scripts/notices/runtime-a60eea817514531668d7.txt +201 -0
- package/scripts/notices/runtime-a7c936ff1ed8fa340172.txt +25 -0
- package/scripts/notices/runtime-ab6eec6caf0fa5775e41.txt +275 -0
- package/scripts/notices/runtime-b1181a40b2a7b25cf66f.txt +19 -0
- package/scripts/notices/runtime-b71bd43a069ca0641a9e.txt +25 -0
- package/scripts/notices/runtime-c16f8dcf1a368b83be78.txt +201 -0
- package/scripts/notices/runtime-c2cfccb812fe482101a8.txt +21 -0
- package/scripts/notices/runtime-e99d88d232bf57d70f0f.txt +25 -0
- package/scripts/notices/runtime-f32fb3b417a194167cfa.txt +9 -0
- package/scripts/notices/runtime-f5062c9a188d81dfe66b.txt +39 -0
- package/scripts/notices/runtime-f9070a832833fa7488fd.txt +4 -0
- package/scripts/notices/runtime-fab3dd6bdab226f1c086.txt +373 -0
- package/scripts/notices/runtime-fba3318d0c600380177e.txt +62 -0
- package/scripts/notices/runtime-ff8f68cb076caf8cefe7.txt +25 -0
- package/scripts/notices/runtime-ff92bed461f50338dd70.txt +74 -0
- package/scripts/notices/runtime.json +1632 -0
- package/scripts/notices/sources.json +555 -0
- package/scripts/notices/xray-MIT.txt +21 -0
- package/src/attributes.ts +271 -0
- package/src/codec.ts +74 -0
- package/src/dom.ts +87 -0
- package/src/extract-plan.ts +97 -0
- package/src/extract.ts +130 -0
- package/src/hooks.ts +220 -0
- package/src/index.ts +223 -0
- package/src/io.ts +491 -0
- package/src/native.ts +135 -0
- package/src/nodes.ts +290 -0
- package/src/parse.ts +53 -0
- package/src/parser-types.ts +157 -0
- package/src/router.ts +327 -0
- package/src/selection.ts +566 -0
- package/src/slim.ts +12 -0
- package/src/transport.ts +104 -0
- package/src/types.ts +186 -0
- package/src/utils.ts +29 -0
- package/src/workers.ts +21 -0
- package/tsconfig.json +15 -0
- 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
|
+
}
|