| /* | 
 |  * Copyright (C) 2014-2015 Apple Inc. All rights reserved. | 
 |  * | 
 |  * Redistribution and use in source and binary forms, with or without | 
 |  * modification, are permitted provided that the following conditions | 
 |  * are met: | 
 |  * 1. Redistributions of source code must retain the above copyright | 
 |  *    notice, this list of conditions and the following disclaimer. | 
 |  * 2. Redistributions in binary form must reproduce the above copyright | 
 |  *    notice, this list of conditions and the following disclaimer in the | 
 |  *    documentation and/or other materials provided with the distribution. | 
 |  * | 
 |  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' | 
 |  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, | 
 |  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | 
 |  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS | 
 |  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | 
 |  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | 
 |  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | 
 |  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | 
 |  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | 
 |  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF | 
 |  * THE POSSIBILITY OF SUCH DAMAGE. | 
 |  */ | 
 |  | 
 | #pragma once | 
 |  | 
 | #if ENABLE(CONTENT_EXTENSIONS) | 
 |  | 
 | #include "ContentExtensionsDebugging.h" | 
 | #include "NFA.h" | 
 | #include <unicode/utypes.h> | 
 | #include <wtf/ASCIICType.h> | 
 | #include <wtf/HashMap.h> | 
 | #include <wtf/Vector.h> | 
 | #include <wtf/text/StringBuilder.h> | 
 | #include <wtf/text/WTFString.h> | 
 |  | 
 | namespace WebCore { | 
 |  | 
 | namespace ContentExtensions { | 
 |  | 
 | enum class AtomQuantifier : uint8_t { | 
 |     One, | 
 |     ZeroOrOne, | 
 |     ZeroOrMore, | 
 |     OneOrMore | 
 | }; | 
 |  | 
 | class Term { | 
 | public: | 
 |     Term(); | 
 |     Term(char character, bool isCaseSensitive); | 
 |  | 
 |     enum UniversalTransitionTag { UniversalTransition }; | 
 |     explicit Term(UniversalTransitionTag); | 
 |  | 
 |     enum CharacterSetTermTag { CharacterSetTerm }; | 
 |     explicit Term(CharacterSetTermTag, bool isInverted); | 
 |  | 
 |     enum GroupTermTag { GroupTerm }; | 
 |     explicit Term(GroupTermTag); | 
 |  | 
 |     enum EndOfLineAssertionTermTag { EndOfLineAssertionTerm }; | 
 |     explicit Term(EndOfLineAssertionTermTag); | 
 |  | 
 |     Term(const Term& other); | 
 |     Term(Term&& other); | 
 |  | 
 |     enum EmptyValueTag { EmptyValue }; | 
 |     Term(EmptyValueTag); | 
 |  | 
 |     ~Term(); | 
 |  | 
 |     bool isValid() const; | 
 |  | 
 |     // CharacterSet terms only. | 
 |     void addCharacter(UChar character, bool isCaseSensitive); | 
 |  | 
 |     // Group terms only. | 
 |     void extendGroupSubpattern(const Term&); | 
 |  | 
 |     void quantify(const AtomQuantifier&); | 
 |  | 
 |     ImmutableCharNFANodeBuilder generateGraph(NFA&, ImmutableCharNFANodeBuilder& source, const ActionList& finalActions) const; | 
 |     void generateGraph(NFA&, ImmutableCharNFANodeBuilder& source, uint32_t destination) const; | 
 |  | 
 |     bool isEndOfLineAssertion() const; | 
 |  | 
 |     bool matchesAtLeastOneCharacter() const; | 
 |  | 
 |     // Matches any string, the empty string included. | 
 |     // This is very conservative. Patterns matching any string can return false here. | 
 |     bool isKnownToMatchAnyString() const; | 
 |  | 
 |     // Return true if the term can only match input of a single fixed length. | 
 |     bool hasFixedLength() const; | 
 |  | 
 |     Term& operator=(const Term& other); | 
 |     Term& operator=(Term&& other); | 
 |  | 
 |     bool operator==(const Term& other) const; | 
 |  | 
 |     unsigned hash() const; | 
 |  | 
 |     bool isEmptyValue() const; | 
 |  | 
 | #if CONTENT_EXTENSIONS_STATE_MACHINE_DEBUGGING | 
 |     String toString() const; | 
 | #endif | 
 |  | 
 | #if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING | 
 |     size_t memoryUsed() const; | 
 | #endif | 
 |      | 
 | private: | 
 |     // This is exact for character sets but conservative for groups. | 
 |     // The return value can be false for a group equivalent to a universal transition. | 
 |     bool isUniversalTransition() const; | 
 |  | 
 |     void generateSubgraphForAtom(NFA&, ImmutableCharNFANodeBuilder& source, const ImmutableCharNFANodeBuilder& destination) const; | 
 |     void generateSubgraphForAtom(NFA&, ImmutableCharNFANodeBuilder& source, uint32_t destination) const; | 
 |  | 
 |     void destroy(); | 
 |  | 
 |     enum class TermType : uint8_t { | 
 |         Empty, | 
 |         CharacterSet, | 
 |         Group | 
 |     }; | 
 |  | 
 |     TermType m_termType { TermType::Empty }; | 
 |     AtomQuantifier m_quantifier { AtomQuantifier::One }; | 
 |  | 
 |     class CharacterSet { | 
 |     public: | 
 |         void set(UChar character) | 
 |         { | 
 |             RELEASE_ASSERT(character < 128); | 
 |             m_characters[character / 64] |= (uint64_t(1) << (character % 64)); | 
 |         } | 
 |          | 
 |         bool get(UChar character) const | 
 |         { | 
 |             RELEASE_ASSERT(character < 128); | 
 |             return m_characters[character / 64] & (uint64_t(1) << (character % 64)); | 
 |         } | 
 |          | 
 |         void invert() | 
 |         { | 
 |             ASSERT(!m_inverted); | 
 |             m_inverted = true; | 
 |         } | 
 |          | 
 |         bool inverted() const { return m_inverted; } | 
 |          | 
 |         unsigned bitCount() const | 
 |         { | 
 |             return WTF::bitCount(m_characters[0]) + WTF::bitCount(m_characters[1]); | 
 |         } | 
 |          | 
 |         bool operator==(const CharacterSet& other) const | 
 |         { | 
 |             return other.m_inverted == m_inverted | 
 |                 && other.m_characters[0] == m_characters[0] | 
 |                 && other.m_characters[1] == m_characters[1]; | 
 |         } | 
 |  | 
 |         unsigned hash() const | 
 |         { | 
 |             return WTF::pairIntHash(WTF::pairIntHash(WTF::intHash(m_characters[0]), WTF::intHash(m_characters[1])), m_inverted); | 
 |         } | 
 |     private: | 
 |         bool m_inverted { false }; | 
 |         uint64_t m_characters[2] { 0, 0 }; | 
 |     }; | 
 |  | 
 |     struct Group { | 
 |         Vector<Term> terms; | 
 |  | 
 |         bool operator==(const Group& other) const | 
 |         { | 
 |             return other.terms == terms; | 
 |         } | 
 |  | 
 |         unsigned hash() const | 
 |         { | 
 |             unsigned hash = 6421749; | 
 |             for (const Term& term : terms) { | 
 |                 unsigned termHash = term.hash(); | 
 |                 hash = (hash << 16) ^ ((termHash << 11) ^ hash); | 
 |                 hash += hash >> 11; | 
 |             } | 
 |             return hash; | 
 |         } | 
 |     }; | 
 |  | 
 |     union AtomData { | 
 |         AtomData() | 
 |             : invalidTerm(0) | 
 |         { | 
 |         } | 
 |         ~AtomData() | 
 |         { | 
 |         } | 
 |  | 
 |         char invalidTerm; | 
 |         CharacterSet characterSet; | 
 |         Group group; | 
 |     } m_atomData; | 
 | }; | 
 |  | 
 | #if CONTENT_EXTENSIONS_STATE_MACHINE_DEBUGGING | 
 | inline String quantifierToString(AtomQuantifier quantifier) | 
 | { | 
 |     switch (quantifier) { | 
 |     case AtomQuantifier::One: | 
 |         return ""; | 
 |     case AtomQuantifier::ZeroOrOne: | 
 |         return "?"; | 
 |     case AtomQuantifier::ZeroOrMore: | 
 |         return "*"; | 
 |     case AtomQuantifier::OneOrMore: | 
 |         return "+"; | 
 |     } | 
 | } | 
 |      | 
 | inline String Term::toString() const | 
 | { | 
 |     switch (m_termType) { | 
 |     case TermType::Empty: | 
 |         return "(Empty)"; | 
 |     case TermType::CharacterSet: { | 
 |         StringBuilder builder; | 
 |         builder.append('['); | 
 |         for (UChar c = 0; c < 128; c++) { | 
 |             if (m_atomData.characterSet.get(c)) { | 
 |                 if (isASCIIPrintable(c) && !isASCIISpace(c)) | 
 |                     builder.append(c); | 
 |                 else { | 
 |                     builder.append('\\'); | 
 |                     builder.append('u'); | 
 |                     builder.appendNumber(c); | 
 |                 } | 
 |             } | 
 |         } | 
 |         builder.append(']'); | 
 |         builder.append(quantifierToString(m_quantifier)); | 
 |         return builder.toString(); | 
 |     } | 
 |     case TermType::Group: { | 
 |         StringBuilder builder; | 
 |         builder.append('('); | 
 |         for (const Term& term : m_atomData.group.terms) | 
 |             builder.append(term.toString()); | 
 |         builder.append(')'); | 
 |         builder.append(quantifierToString(m_quantifier)); | 
 |         return builder.toString(); | 
 |     } | 
 |     } | 
 | } | 
 | #endif | 
 |  | 
 | inline Term::Term() | 
 | { | 
 | } | 
 |  | 
 | inline Term::Term(char character, bool isCaseSensitive) | 
 |     : m_termType(TermType::CharacterSet) | 
 | { | 
 |     new (NotNull, &m_atomData.characterSet) CharacterSet(); | 
 |     addCharacter(character, isCaseSensitive); | 
 | } | 
 |  | 
 | inline Term::Term(UniversalTransitionTag) | 
 |     : m_termType(TermType::CharacterSet) | 
 | { | 
 |     new (NotNull, &m_atomData.characterSet) CharacterSet(); | 
 |     for (UChar i = 1; i < 128; ++i) | 
 |         m_atomData.characterSet.set(i); | 
 | } | 
 |  | 
 | inline Term::Term(CharacterSetTermTag, bool isInverted) | 
 |     : m_termType(TermType::CharacterSet) | 
 | { | 
 |     new (NotNull, &m_atomData.characterSet) CharacterSet(); | 
 |     if (isInverted) | 
 |         m_atomData.characterSet.invert(); | 
 | } | 
 |  | 
 | inline Term::Term(GroupTermTag) | 
 |     : m_termType(TermType::Group) | 
 | { | 
 |     new (NotNull, &m_atomData.group) Group(); | 
 | } | 
 |  | 
 | inline Term::Term(EndOfLineAssertionTermTag) | 
 |     : Term(CharacterSetTerm, false) | 
 | { | 
 |     m_atomData.characterSet.set(0); | 
 | } | 
 |  | 
 | inline Term::Term(const Term& other) | 
 |     : m_termType(other.m_termType) | 
 |     , m_quantifier(other.m_quantifier) | 
 | { | 
 |     switch (m_termType) { | 
 |     case TermType::Empty: | 
 |         break; | 
 |     case TermType::CharacterSet: | 
 |         new (NotNull, &m_atomData.characterSet) CharacterSet(other.m_atomData.characterSet); | 
 |         break; | 
 |     case TermType::Group: | 
 |         new (NotNull, &m_atomData.group) Group(other.m_atomData.group); | 
 |         break; | 
 |     } | 
 | } | 
 |  | 
 | inline Term::Term(Term&& other) | 
 |     : m_termType(WTFMove(other.m_termType)) | 
 |     , m_quantifier(WTFMove(other.m_quantifier)) | 
 | { | 
 |     switch (m_termType) { | 
 |     case TermType::Empty: | 
 |         break; | 
 |     case TermType::CharacterSet: | 
 |         new (NotNull, &m_atomData.characterSet) CharacterSet(WTFMove(other.m_atomData.characterSet)); | 
 |         break; | 
 |     case TermType::Group: | 
 |         new (NotNull, &m_atomData.group) Group(WTFMove(other.m_atomData.group)); | 
 |         break; | 
 |     } | 
 |     other.destroy(); | 
 | } | 
 |  | 
 | inline Term::Term(EmptyValueTag) | 
 |     : m_termType(TermType::Empty) | 
 | { | 
 | } | 
 |  | 
 | inline Term::~Term() | 
 | { | 
 |     destroy(); | 
 | } | 
 |  | 
 | inline bool Term::isValid() const | 
 | { | 
 |     return m_termType != TermType::Empty; | 
 | } | 
 |  | 
 | inline void Term::addCharacter(UChar character, bool isCaseSensitive) | 
 | { | 
 |     ASSERT(isASCII(character)); | 
 |  | 
 |     ASSERT_WITH_SECURITY_IMPLICATION(m_termType == TermType::CharacterSet); | 
 |     if (m_termType != TermType::CharacterSet) | 
 |         return; | 
 |  | 
 |     if (isCaseSensitive || !isASCIIAlpha(character)) | 
 |         m_atomData.characterSet.set(character); | 
 |     else { | 
 |         m_atomData.characterSet.set(toASCIIUpper(character)); | 
 |         m_atomData.characterSet.set(toASCIILower(character)); | 
 |     } | 
 | } | 
 |  | 
 | inline void Term::extendGroupSubpattern(const Term& term) | 
 | { | 
 |     ASSERT_WITH_SECURITY_IMPLICATION(m_termType == TermType::Group); | 
 |     if (m_termType != TermType::Group) | 
 |         return; | 
 |     m_atomData.group.terms.append(term); | 
 | } | 
 |  | 
 | inline void Term::quantify(const AtomQuantifier& quantifier) | 
 | { | 
 |     ASSERT_WITH_MESSAGE(m_quantifier == AtomQuantifier::One, "Transition to quantified term should only happen once."); | 
 |     m_quantifier = quantifier; | 
 | } | 
 |  | 
 | inline ImmutableCharNFANodeBuilder Term::generateGraph(NFA& nfa, ImmutableCharNFANodeBuilder& source, const ActionList& finalActions) const | 
 | { | 
 |     ImmutableCharNFANodeBuilder newEnd(nfa); | 
 |     generateGraph(nfa, source, newEnd.nodeId()); | 
 |     newEnd.setActions(finalActions.begin(), finalActions.end()); | 
 |     return newEnd; | 
 | } | 
 |  | 
 | inline void Term::generateGraph(NFA& nfa, ImmutableCharNFANodeBuilder& source, uint32_t destination) const | 
 | { | 
 |     ASSERT(isValid()); | 
 |  | 
 |     switch (m_quantifier) { | 
 |     case AtomQuantifier::One: { | 
 |         generateSubgraphForAtom(nfa, source, destination); | 
 |         break; | 
 |     } | 
 |     case AtomQuantifier::ZeroOrOne: { | 
 |         generateSubgraphForAtom(nfa, source, destination); | 
 |         source.addEpsilonTransition(destination); | 
 |         break; | 
 |     } | 
 |     case AtomQuantifier::ZeroOrMore: { | 
 |         ImmutableCharNFANodeBuilder repeatStart(nfa); | 
 |         source.addEpsilonTransition(repeatStart); | 
 |  | 
 |         ImmutableCharNFANodeBuilder repeatEnd(nfa); | 
 |         generateSubgraphForAtom(nfa, repeatStart, repeatEnd); | 
 |         repeatEnd.addEpsilonTransition(repeatStart); | 
 |  | 
 |         repeatEnd.addEpsilonTransition(destination); | 
 |         source.addEpsilonTransition(destination); | 
 |         break; | 
 |     } | 
 |     case AtomQuantifier::OneOrMore: { | 
 |         ImmutableCharNFANodeBuilder repeatStart(nfa); | 
 |         source.addEpsilonTransition(repeatStart); | 
 |  | 
 |         ImmutableCharNFANodeBuilder repeatEnd(nfa); | 
 |         generateSubgraphForAtom(nfa, repeatStart, repeatEnd); | 
 |         repeatEnd.addEpsilonTransition(repeatStart); | 
 |  | 
 |         repeatEnd.addEpsilonTransition(destination); | 
 |         break; | 
 |     } | 
 |     } | 
 | } | 
 |  | 
 | inline bool Term::isEndOfLineAssertion() const | 
 | { | 
 |     return m_termType == TermType::CharacterSet && m_atomData.characterSet.bitCount() == 1 && m_atomData.characterSet.get(0); | 
 | } | 
 |  | 
 | inline bool Term::matchesAtLeastOneCharacter() const | 
 | { | 
 |     ASSERT(isValid()); | 
 |  | 
 |     if (m_quantifier == AtomQuantifier::ZeroOrOne || m_quantifier == AtomQuantifier::ZeroOrMore) | 
 |         return false; | 
 |     if (isEndOfLineAssertion()) | 
 |         return false; | 
 |  | 
 |     if (m_termType == TermType::Group) { | 
 |         for (const Term& term : m_atomData.group.terms) { | 
 |             if (term.matchesAtLeastOneCharacter()) | 
 |                 return true; | 
 |         } | 
 |         return false; | 
 |     } | 
 |     return true; | 
 | } | 
 |  | 
 | inline bool Term::isKnownToMatchAnyString() const | 
 | { | 
 |     ASSERT(isValid()); | 
 |  | 
 |     switch (m_termType) { | 
 |     case TermType::Empty: | 
 |         ASSERT_NOT_REACHED(); | 
 |         break; | 
 |     case TermType::CharacterSet: | 
 |         // ".*" is the only simple term matching any string. | 
 |         return isUniversalTransition() && m_quantifier == AtomQuantifier::ZeroOrMore; | 
 |         break; | 
 |     case TermType::Group: { | 
 |         // There are infinitely many ways to match anything with groups, we just handle simple cases | 
 |         if (m_atomData.group.terms.size() != 1) | 
 |             return false; | 
 |  | 
 |         const Term& firstTermInGroup = m_atomData.group.terms.first(); | 
 |         // -(.*) with any quantifier. | 
 |         if (firstTermInGroup.isKnownToMatchAnyString()) | 
 |             return true; | 
 |  | 
 |         if (firstTermInGroup.isUniversalTransition()) { | 
 |             // -(.)*, (.+)*, (.?)* etc. | 
 |             if (m_quantifier == AtomQuantifier::ZeroOrMore) | 
 |                 return true; | 
 |  | 
 |             // -(.+)?. | 
 |             if (m_quantifier == AtomQuantifier::ZeroOrOne && firstTermInGroup.m_quantifier == AtomQuantifier::OneOrMore) | 
 |                 return true; | 
 |  | 
 |             // -(.?)+. | 
 |             if (m_quantifier == AtomQuantifier::OneOrMore && firstTermInGroup.m_quantifier == AtomQuantifier::ZeroOrOne) | 
 |                 return true; | 
 |         } | 
 |         break; | 
 |     } | 
 |     } | 
 |     return false; | 
 | } | 
 |  | 
 | inline bool Term::hasFixedLength() const | 
 | { | 
 |     ASSERT(isValid()); | 
 |  | 
 |     switch (m_termType) { | 
 |     case TermType::Empty: | 
 |         ASSERT_NOT_REACHED(); | 
 |         break; | 
 |     case TermType::CharacterSet: | 
 |         return m_quantifier == AtomQuantifier::One; | 
 |     case TermType::Group: { | 
 |         if (m_quantifier != AtomQuantifier::One) | 
 |             return false; | 
 |         for (const Term& term : m_atomData.group.terms) { | 
 |             if (!term.hasFixedLength()) | 
 |                 return false; | 
 |         } | 
 |         return true; | 
 |     } | 
 |     } | 
 |     return false; | 
 | } | 
 |  | 
 | inline Term& Term::operator=(const Term& other) | 
 | { | 
 |     destroy(); | 
 |     new (NotNull, this) Term(other); | 
 |     return *this; | 
 | } | 
 |  | 
 | inline Term& Term::operator=(Term&& other) | 
 | { | 
 |     destroy(); | 
 |     new (NotNull, this) Term(WTFMove(other)); | 
 |     return *this; | 
 | } | 
 |  | 
 | inline bool Term::operator==(const Term& other) const | 
 | { | 
 |     if (other.m_termType != m_termType || other.m_quantifier != m_quantifier) | 
 |         return false; | 
 |  | 
 |     switch (m_termType) { | 
 |     case TermType::Empty: | 
 |         return true; | 
 |     case TermType::CharacterSet: | 
 |         return m_atomData.characterSet == other.m_atomData.characterSet; | 
 |     case TermType::Group: | 
 |         return m_atomData.group == other.m_atomData.group; | 
 |     } | 
 |     ASSERT_NOT_REACHED(); | 
 |     return false; | 
 | } | 
 |  | 
 | inline unsigned Term::hash() const | 
 | { | 
 |     unsigned primary = static_cast<unsigned>(m_termType) << 16 | static_cast<unsigned>(m_quantifier); | 
 |     unsigned secondary = 0; | 
 |     switch (m_termType) { | 
 |     case TermType::Empty: | 
 |         secondary = 52184393; | 
 |         break; | 
 |     case TermType::CharacterSet: | 
 |         secondary = m_atomData.characterSet.hash(); | 
 |         break; | 
 |     case TermType::Group: | 
 |         secondary = m_atomData.group.hash(); | 
 |         break; | 
 |     } | 
 |     return WTF::pairIntHash(primary, secondary); | 
 | } | 
 |  | 
 | inline bool Term::isEmptyValue() const | 
 | { | 
 |     return m_termType == TermType::Empty; | 
 | } | 
 |  | 
 | inline bool Term::isUniversalTransition() const | 
 | { | 
 |     ASSERT(isValid()); | 
 |  | 
 |     switch (m_termType) { | 
 |     case TermType::Empty: | 
 |         ASSERT_NOT_REACHED(); | 
 |         break; | 
 |     case TermType::CharacterSet: | 
 |         return (m_atomData.characterSet.inverted() && !m_atomData.characterSet.bitCount()) | 
 |             || (!m_atomData.characterSet.inverted() && m_atomData.characterSet.bitCount() == 127 && !m_atomData.characterSet.get(0)); | 
 |     case TermType::Group: | 
 |         return m_atomData.group.terms.size() == 1 && m_atomData.group.terms.first().isUniversalTransition(); | 
 |     } | 
 |     return false; | 
 | } | 
 |  | 
 | inline void Term::generateSubgraphForAtom(NFA& nfa, ImmutableCharNFANodeBuilder& source, const ImmutableCharNFANodeBuilder& destination) const | 
 | { | 
 |     generateSubgraphForAtom(nfa, source, destination.nodeId()); | 
 | } | 
 |  | 
 | inline void Term::generateSubgraphForAtom(NFA& nfa, ImmutableCharNFANodeBuilder& source, uint32_t destination) const | 
 | { | 
 |     switch (m_termType) { | 
 |     case TermType::Empty: | 
 |         ASSERT_NOT_REACHED(); | 
 |         source.addEpsilonTransition(destination); | 
 |         break; | 
 |     case TermType::CharacterSet: { | 
 |         if (!m_atomData.characterSet.inverted()) { | 
 |             UChar i = 0; | 
 |             while (true) { | 
 |                 while (i < 128 && !m_atomData.characterSet.get(i)) | 
 |                     ++i; | 
 |                 if (i == 128) | 
 |                     break; | 
 |  | 
 |                 UChar start = i; | 
 |                 ++i; | 
 |                 while (i < 128 && m_atomData.characterSet.get(i)) | 
 |                     ++i; | 
 |                 source.addTransition(start, i - 1, destination); | 
 |             } | 
 |         } else { | 
 |             UChar i = 1; | 
 |             while (true) { | 
 |                 while (i < 128 && m_atomData.characterSet.get(i)) | 
 |                     ++i; | 
 |                 if (i == 128) | 
 |                     break; | 
 |  | 
 |                 UChar start = i; | 
 |                 ++i; | 
 |                 while (i < 128 && !m_atomData.characterSet.get(i)) | 
 |                     ++i; | 
 |                 source.addTransition(start, i - 1, destination); | 
 |             } | 
 |         } | 
 |         break; | 
 |     } | 
 |     case TermType::Group: { | 
 |         if (m_atomData.group.terms.isEmpty()) { | 
 |             // FIXME: any kind of empty term could be avoided in the parser. This case should turned into an assertion. | 
 |             source.addEpsilonTransition(destination); | 
 |             return; | 
 |         } | 
 |  | 
 |         if (m_atomData.group.terms.size() == 1) { | 
 |             m_atomData.group.terms.first().generateGraph(nfa, source, destination); | 
 |             return; | 
 |         } | 
 |  | 
 |         ImmutableCharNFANodeBuilder lastTarget = m_atomData.group.terms.first().generateGraph(nfa, source, ActionList()); | 
 |         for (unsigned i = 1; i < m_atomData.group.terms.size() - 1; ++i) { | 
 |             const Term& currentTerm = m_atomData.group.terms[i]; | 
 |             ImmutableCharNFANodeBuilder newNode = currentTerm.generateGraph(nfa, lastTarget, ActionList()); | 
 |             lastTarget = WTFMove(newNode); | 
 |         } | 
 |         const Term& lastTerm = m_atomData.group.terms.last(); | 
 |         lastTerm.generateGraph(nfa, lastTarget, destination); | 
 |         break; | 
 |     } | 
 |     } | 
 | } | 
 |  | 
 | inline void Term::destroy() | 
 | { | 
 |     switch (m_termType) { | 
 |     case TermType::Empty: | 
 |         break; | 
 |     case TermType::CharacterSet: | 
 |         m_atomData.characterSet.~CharacterSet(); | 
 |         break; | 
 |     case TermType::Group: | 
 |         m_atomData.group.~Group(); | 
 |         break; | 
 |     } | 
 |     m_termType = TermType::Empty; | 
 | } | 
 |  | 
 | #if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING | 
 | inline size_t Term::memoryUsed() const | 
 | { | 
 |     size_t extraMemory = 0; | 
 |     if (m_termType == TermType::Group) { | 
 |         for (const Term& term : m_atomData.group.terms) | 
 |             extraMemory += term.memoryUsed(); | 
 |     } | 
 |     return sizeof(Term) + extraMemory; | 
 | } | 
 | #endif | 
 |  | 
 | } // namespace ContentExtensions | 
 | } // namespace WebCore | 
 |  | 
 | #endif // ENABLE(CONTENT_EXTENSIONS) |