diff --git a/src/html/local_name.rs b/src/html/local_name.rs
index a334e000..3ad70b07 100644
--- a/src/html/local_name.rs
+++ b/src/html/local_name.rs
@@ -26,53 +26,60 @@ use encoding_rs::Encoding;
// we are safe here, since we'll just get first character shifted left
// by zeroes as repetitave 1 digits get added to the hash.
//
+// LocalNameHash is built incrementally as tags are parsed, so it needs
+// to be able to invalidate itself if parsing an unrepresentable name.
+// `EMPTY_HASH` is used as a sentinel value.
+//
// Pub only for integration tests
#[derive(Debug, PartialEq, Eq, Copy, Clone, Default, Hash)]
-pub struct LocalNameHash(Option);
+pub struct LocalNameHash(u64);
+
+const EMPTY_HASH: u64 = !0;
impl LocalNameHash {
#[inline]
#[must_use]
pub const fn new() -> Self {
- Self(Some(0))
+ Self(0)
}
#[inline]
#[must_use]
pub const fn is_empty(&self) -> bool {
- self.0.is_none()
+ self.0 == EMPTY_HASH
}
#[inline]
pub fn update(&mut self, ch: u8) {
- if let Some(h) = self.0 {
- // NOTE: check if we still have space for yet another
- // character and if not then invalidate the hash.
- // Note, that we can't have `1` (which is encoded as 0b00000) as
- // a first character of a tag name, so it's safe to perform
- // check this way.
- self.0 = if h >> (64 - 5) == 0 {
- match ch {
- // NOTE: apply 0x1F mask on ASCII alpha to convert it to the
- // number from 1 to 26 (character case is controlled by one of
- // upper bits which we eliminate with the mask). Then add
- // 5, since numbers from 0 to 5 are reserved for digits.
- // Aftwerards put result as 5 lower bits of the hash.
- b'a'..=b'z' | b'A'..=b'Z' => Some((h << 5) | ((u64::from(ch) & 0x1F) + 5)),
-
- // NOTE: apply 0x0F mask on ASCII digit to convert it to number
- // from 1 to 6. Then subtract 1 to make it zero-based.
- // Afterwards, put result as lower bits of the hash.
- b'1'..=b'6' => Some((h << 5) | ((u64::from(ch) & 0x0F) - 1)),
-
- // NOTE: for any other characters hash function is not
- // applicable, so we completely invalidate the hash.
- _ => None,
- }
- } else {
- None
- };
- }
+ let h = self.0;
+
+ // NOTE: check if we still have space for yet another
+ // character and if not then invalidate the hash.
+ // Note, that we can't have `1` (which is encoded as 0b00000) as
+ // a first character of a tag name, so it's safe to perform
+ // check this way.
+ // EMPTY_HASH has all bits set, so it will fail this check.
+ self.0 = if h >> (64 - 5) == 0 {
+ match ch {
+ // NOTE: apply 0x1F mask on ASCII alpha to convert it to the
+ // number from 1 to 26 (character case is controlled by one of
+ // upper bits which we eliminate with the mask). Then add
+ // 5, since numbers from 0 to 5 are reserved for digits.
+ // Aftwerards put result as 5 lower bits of the hash.
+ b'a'..=b'z' | b'A'..=b'Z' => (h << 5) | ((u64::from(ch) & 0x1F) + 5),
+
+ // NOTE: apply 0x0F mask on ASCII digit to convert it to number
+ // from 1 to 6. Then subtract 1 to make it zero-based.
+ // Afterwards, put result as lower bits of the hash.
+ b'1'..=b'6' => (h << 5) | ((u64::from(ch) & 0x0F) - 1),
+
+ // NOTE: for any other characters hash function is not
+ // applicable, so we completely invalidate the hash.
+ _ => EMPTY_HASH,
+ }
+ } else {
+ EMPTY_HASH
+ };
}
}
@@ -92,10 +99,7 @@ impl From<&str> for LocalNameHash {
impl PartialEq for LocalNameHash {
#[inline]
fn eq(&self, tag: &Tag) -> bool {
- match self.0 {
- Some(h) => *tag as u64 == h,
- None => false,
- }
+ self.0 == *tag as u64
}
}
@@ -159,7 +163,10 @@ impl PartialEq> for LocalName<'_> {
use LocalName::{Bytes, Hash};
match (self, other) {
- (Hash(s), Hash(o)) => s == o,
+ (Hash(s), Hash(o)) => {
+ debug_assert!(!s.is_empty());
+ s == o
+ },
(Bytes(s), Bytes(o)) => s.eq_ignore_ascii_case(o),
_ => false,
}
@@ -172,7 +179,7 @@ mod tests {
#[test]
fn from_str() {
- assert_eq!(LocalNameHash::from("div"), LocalNameHash(Some(9691u64)));
+ assert_eq!(LocalNameHash::from("div"), LocalNameHash(9691u64));
}
#[test]