All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fernice.flare.selector.Parser.kt Maven / Gradle / Ivy

/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package org.fernice.flare.selector

import org.fernice.flare.cssparser.*
import org.fernice.flare.style.QuirksMode
import org.fernice.std.*

/**
 * The selector specific error kinds
 */
sealed class SelectorParseErrorKind : ParseErrorKind() {

    data object EmptySelector : SelectorParseErrorKind()
    data object DanglingCombinator : SelectorParseErrorKind()
    data object PseudoElementExpectedColon : SelectorParseErrorKind()
    data object NoIdentifierForPseudo : SelectorParseErrorKind()
    data object ExpectedNamespace : SelectorParseErrorKind()
    data object ExpectedBarAttributeSelector : SelectorParseErrorKind()
    data object InvalidQualifiedNameInAttributeSelector : SelectorParseErrorKind()
    data object ExplicitNamespaceUnexpectedToken : SelectorParseErrorKind()
    data object ClassNeedsIdentifier : SelectorParseErrorKind()
    data object PseudoNeedsIdentifier : SelectorParseErrorKind()
    data object EmptyNegation : SelectorParseErrorKind()
    data object NonSimpleSelectorInNegation : SelectorParseErrorKind()
    data object NoQualifiedNameInAttributeSelector : SelectorParseErrorKind()
    data class UnexpectedTokenInAttributeSelector(val token: Token) : SelectorParseErrorKind()
    data object InvalidState : SelectorParseErrorKind()

    override fun toString(): String {
        return "SelectorParseErrorKind::${javaClass.simpleName}"
    }
}

interface SelectorParserContext {
    fun defaultNamespace(): NamespaceUrl?
    fun namespacePrefix(prefix: String): NamespacePrefix
    fun namespaceForPrefix(prefix: NamespacePrefix): NamespaceUrl?
}

class SelectorParsingState(value: UByte) : U8Bitflags(value) {

    override val all: UByte get() = ALL

    operator fun plus(value: UByte): SelectorParsingState = of(this.value or value)
    operator fun minus(value: UByte): SelectorParsingState = of(this.value and value.inv())

    fun allowsPseudos(): Boolean = !intersects(AFTER_PSEUDO_ELEMENT or DISALLOW_PSEUDOS)
    fun allowsSlotted(): Boolean = !intersects(AFTER_PSEUDO or DISALLOW_PSEUDOS)
    fun allowsPart(): Boolean = !intersects(AFTER_PSEUDO or DISALLOW_PSEUDOS)
    fun allowsCustomFunctionalPseudoClasses(): Boolean = !intersects(AFTER_PSEUDO)
    fun allowsNonFunctionalPseudoClasses(): Boolean = !intersects(AFTER_SLOTTED or AFTER_NON_STATEFUL_PSEUDO_ELEMENT)
    fun allowsTreeStructuralPseudoClasses(): Boolean = !intersects(AFTER_PSEUDO)
    fun allowsCombinators(): Boolean = !intersects(DISALLOW_COMBINATORS)

    companion object {
        const val SKIP_DEFAULT_NAMESPACE: UByte = 0b0000_0001u
        const val AFTER_SLOTTED: UByte = 0b0000_0010u
        const val AFTER_PART: UByte = 0b0000_0100u
        const val AFTER_PSEUDO_ELEMENT: UByte = 0b0000_1000u
        const val AFTER_NON_STATEFUL_PSEUDO_ELEMENT: UByte = 0b0001_0000u
        val AFTER_PSEUDO: UByte = AFTER_PART or AFTER_SLOTTED or AFTER_PSEUDO_ELEMENT

        const val DISALLOW_COMBINATORS: UByte = 0b0010_0000u
        const val DISALLOW_PSEUDOS: UByte = 0b0100_0000u
        const val DISALLOW_RELATIVE_SELECTORS: UByte = 0b1000_0000u

        private val ALL: UByte = SKIP_DEFAULT_NAMESPACE or AFTER_SLOTTED or AFTER_PART or AFTER_PSEUDO_ELEMENT or AFTER_NON_STATEFUL_PSEUDO_ELEMENT or
                DISALLOW_COMBINATORS or DISALLOW_PSEUDOS or DISALLOW_RELATIVE_SELECTORS

        fun empty(): SelectorParsingState = SelectorParsingState(0u)
        fun all(): SelectorParsingState = SelectorParsingState(ALL)
        fun of(value: UByte): SelectorParsingState = SelectorParsingState(value and ALL)
    }
}

enum class ParseForgiving {
    No,
    Yes,
}

enum class ParseRelative {
    No,
    ForNesting,
    ForHas,
}

private fun parseInnerCompoundSelector(
    context: SelectorParserContext,
    input: Parser,
    state: SelectorParsingState,
): Result {
    return parseSelector(
        context,
        input,
        state + SelectorParsingState.DISALLOW_PSEUDOS + SelectorParsingState.DISALLOW_COMBINATORS,
        ParseRelative.No,
    )
}

internal fun parseSelector(
    context: SelectorParserContext,
    input: Parser,
    state: SelectorParsingState,
    parseRelative: ParseRelative,
): Result {
    val builder = SelectorBuilder()

    input.skipWhitespace()

    if (parseRelative != ParseRelative.No) {
        val combinator = tryParseCombinator(input)
        when (parseRelative) {
            ParseRelative.ForHas -> {
                builder.pushSimpleSelector(Component.RelativeSelectorAnchor)
                builder.pushCombinator(combinator.unwrapOr(Combinator.Descendant))
            }

            ParseRelative.ForNesting -> {
                combinator.ifOk {
                    builder.pushSimpleSelector(Component.ParentSelector)
                    builder.pushCombinator(it)
                }
            }

            else -> error("unreachable")
        }
    }

    while (true) {
        val empty = when (val compoundResult = parseCompoundSelector(context, input, builder, state)) {
            is Ok -> compoundResult.value
            is Err -> return compoundResult
        }

        if (empty) {
            return if (builder.hasDanglingCombinator()) {
                Err(input.newError(SelectorParseErrorKind.DanglingCombinator))
            } else {
                Err(input.newError(SelectorParseErrorKind.EmptySelector))
            }
        }

        if (state.intersects(SelectorParsingState.AFTER_PSEUDO)) {
            break
        }

        val combinator = when (val result = tryParseCombinator(input)) {
            is Ok -> result.value
            is Err -> break
        }

        if (!state.allowsCombinators()) {
            return Err(input.newError(SelectorParseErrorKind.InvalidState))
        }

        builder.pushCombinator(combinator)
    }

    return Ok(builder.build())
}

private fun tryParseCombinator(input: Parser): Result {
    var seenWhitespace = false
    while (true) {
        val state = input.state()
        val token = when (val token = input.nextIncludingWhitespace()) {
            is Ok -> token.value
            is Err -> return Err()
        }

        when (token) {
            is Token.Whitespace -> {
                seenWhitespace = true
            }

            is Token.Gt -> return Ok(Combinator.Child)
            is Token.Plus -> return Ok(Combinator.NextSibling)
            is Token.Tidle -> return Ok(Combinator.LaterSibling)
            else -> {
                input.reset(state)
                if (seenWhitespace) return Ok(Combinator.Descendant)
                return Err()
            }
        }
    }
}

private fun parseCompoundSelector(
    context: SelectorParserContext,
    input: Parser,
    builder: SelectorBuilder,
    state: SelectorParsingState,
): Result {
    input.skipWhitespace()

    var empty = true

    if (parseTypeSelector(context, input, builder::pushSimpleSelector).unwrap { return it }) {
        empty = false
    }

    while (true) {
        val result = parseOneSimpleSelector(context, input, state).unwrap { return it } ?: break

        if (empty) {
            context.defaultNamespace()?.let {
                val ignoreDefaultNamespace = state.intersects(SelectorParsingState.SKIP_DEFAULT_NAMESPACE)
                        || result is SimpleSelectorParseResult.SimpleSelector && result.component is Component.Host

                if (!ignoreDefaultNamespace) {
                    builder.pushSimpleSelector(Component.DefaultNamespace(it))
                }
            }
        }

        empty = false

        when (result) {
            is SimpleSelectorParseResult.SimpleSelector -> {
                builder.pushSimpleSelector(result.component)
            }

            is SimpleSelectorParseResult.PartPseudo -> {
                state.add(SelectorParsingState.AFTER_PART)
                builder.pushCombinator(Combinator.Part)
                builder.pushSimpleSelector(Component.Part(result.names))
            }

            is SimpleSelectorParseResult.SlottedPseudo -> {
                state.add(SelectorParsingState.AFTER_SLOTTED)
                builder.pushCombinator(Combinator.SlotAssignment)
                builder.pushSimpleSelector(Component.Slotted(result.selector))
            }

            is SimpleSelectorParseResult.PseudoElement -> {
                state.add(SelectorParsingState.AFTER_PSEUDO_ELEMENT)
                if (!result.pseudoElement.acceptsStatePseudoClasses()) {
                    state.add(SelectorParsingState.AFTER_NON_STATEFUL_PSEUDO_ELEMENT)
                }
                builder.pushCombinator(Combinator.PseudoElement)
                builder.pushSimpleSelector(Component.PseudoElement(result.pseudoElement))
            }
        }
    }

    return Ok(empty)
}

private sealed class QualifiedNamePrefix {

    data object ImplicitNoNamespace : QualifiedNamePrefix()

    data object ImplicitAnyNamespace : QualifiedNamePrefix()

    data class ImplicitDefaultNamespace(val url: NamespaceUrl) : QualifiedNamePrefix()

    data object ExplicitNoNamespace : QualifiedNamePrefix()

    data object ExplicitAnyNamespace : QualifiedNamePrefix()

    data class ExplicitNamespace(val prefix: NamespacePrefix, val url: NamespaceUrl) : QualifiedNamePrefix()
}

private sealed class QualifiedName {

    data object None : QualifiedName()

    data class Some(val prefix: QualifiedNamePrefix, val localName: String?) : QualifiedName()
}

private fun parseTypeSelector(
    context: SelectorParserContext,
    input: Parser,
    sink: (Component) -> Unit,
): Result {
    val qualifiedName = when (val qualifiedName = parseQualifiedName(context, input, false)) {
        is Ok -> qualifiedName.value
        is Err -> {
            return if (input.isExhausted()) {
                Ok(false)
            } else {
                qualifiedName
            }
        }
    }

    return when (qualifiedName) {
        is QualifiedName.Some -> {
            when (qualifiedName.prefix) {
                is QualifiedNamePrefix.ImplicitNoNamespace -> throw IllegalStateException("unreachable")
                is QualifiedNamePrefix.ImplicitDefaultNamespace -> sink(Component.ExplicitNoNamespace)
                is QualifiedNamePrefix.ExplicitNoNamespace -> sink(Component.ExplicitNoNamespace)
                is QualifiedNamePrefix.ExplicitAnyNamespace -> {
                    when (val defaultNamespace = context.defaultNamespace()) {
                        null -> sink(Component.ExplicitAnyNamespace)
                        else -> sink(Component.DefaultNamespace(defaultNamespace))
                    }
                }

                is QualifiedNamePrefix.ExplicitNamespace -> {
                    when (val defaultNamespace = context.defaultNamespace()) {
                        null -> sink(Component.DefaultNamespace(qualifiedName.prefix.url))
                        else -> {
                            if (defaultNamespace == qualifiedName.prefix.url) {
                                sink(Component.DefaultNamespace(qualifiedName.prefix.url))
                            } else {
                                sink(Component.Namespace(qualifiedName.prefix.prefix, qualifiedName.prefix.url))
                            }
                        }
                    }
                }

                else -> {}
            }

            when (qualifiedName.localName) {
                null -> sink(Component.ExplicitUniversalType)
                else -> {
                    val localName = qualifiedName.localName
                    val localNameLower = localName.lowercase()

                    sink(Component.LocalName(localName, localNameLower))
                }
            }

            Ok(true)
        }

        is QualifiedName.None -> {
            Ok(false)
        }
    }
}

private fun parseQualifiedName(
    context: SelectorParserContext,
    input: Parser,
    attributeSelector: Boolean,
): Result {
    val state = input.state()

    val token = when (val token = input.nextIncludingWhitespace()) {
        is Ok -> token.value
        is Err -> {
            input.reset(state)
            return token
        }
    }

    return when (token) {
        is Token.Identifier -> {
            val afterIdentState = input.state()

            val innerToken = when (val innerToken = input.nextIncludingWhitespace()) {
                is Ok -> innerToken.value
                is Err -> {
                    input.reset(afterIdentState)

                    return if (attributeSelector) {
                        Ok(QualifiedName.Some(QualifiedNamePrefix.ImplicitNoNamespace, token.name))
                    } else {
                        defaultNamespace(context, token.name)
                    }
                }
            }

            when (innerToken) {
                is Token.Pipe -> {
                    val prefix = context.namespacePrefix(token.name)

                    when (val namespace = context.namespaceForPrefix(prefix)) {
                        null -> {
                            Err(afterIdentState.location().newError(SelectorParseErrorKind.ExpectedNamespace))
                        }

                        else -> {
                            explicitNamespace(
                                input,
                                QualifiedNamePrefix.ExplicitNamespace(prefix, namespace),
                                attributeSelector
                            )
                        }
                    }
                }

                else -> {
                    input.reset(afterIdentState)
                    if (attributeSelector) {
                        Ok(QualifiedName.Some(QualifiedNamePrefix.ImplicitNoNamespace, token.name))
                    } else {
                        defaultNamespace(context, token.name)
                    }
                }
            }
        }

        is Token.Asterisk -> {
            val afterAsteriskState = input.state()

            val innerToken = when (val innerToken = input.nextIncludingWhitespace()) {
                is Ok -> innerToken.value
                is Err -> {
                    input.reset(afterAsteriskState)

                    return if (attributeSelector) {
                        innerToken
                    } else {
                        defaultNamespace(context, null)
                    }
                }
            }

            when (innerToken) {
                is Token.Pipe -> {
                    explicitNamespace(input, QualifiedNamePrefix.ExplicitAnyNamespace, attributeSelector)
                }

                else -> {
                    input.reset(afterAsteriskState)

                    if (attributeSelector) {
                        Err(afterAsteriskState.location().newError(SelectorParseErrorKind.ExpectedBarAttributeSelector))
                    } else {
                        defaultNamespace(context, null)
                    }
                }
            }
        }

        is Token.Pipe -> {
            explicitNamespace(input, QualifiedNamePrefix.ExplicitNoNamespace, attributeSelector)
        }

        else -> {
            input.reset(state)
            Ok(QualifiedName.None)
        }
    }
}

private fun explicitNamespace(
    input: Parser,
    prefix: QualifiedNamePrefix,
    attributeSelector: Boolean,
): Result {
    val location = input.sourceLocation()

    val token = when (val token = input.nextIncludingWhitespace()) {
        is Ok -> token.value
        is Err -> return token
    }

    return when (token) {
        is Token.Identifier -> {
            Ok(QualifiedName.Some(prefix, token.name))
        }

        is Token.Asterisk -> {
            if (!attributeSelector) {
                Ok(QualifiedName.Some(prefix, null))
            } else {
                Err(location.newError(SelectorParseErrorKind.InvalidQualifiedNameInAttributeSelector))
            }
        }

        else -> {
            if (attributeSelector) {
                Err(location.newError(SelectorParseErrorKind.InvalidQualifiedNameInAttributeSelector))
            } else {
                Err(location.newError(SelectorParseErrorKind.ExplicitNamespaceUnexpectedToken))
            }
        }
    }
}

private fun defaultNamespace(context: SelectorParserContext, name: String?): Result {
    val namespace = when (val namespace = context.defaultNamespace()) {
        null -> QualifiedNamePrefix.ImplicitAnyNamespace
        else -> QualifiedNamePrefix.ImplicitDefaultNamespace(namespace)
    }

    return Ok(QualifiedName.Some(namespace, name))
}

private sealed class SimpleSelectorParseResult {

    data class SimpleSelector(val component: Component) : SimpleSelectorParseResult()
    data class PartPseudo(val names: List) : SimpleSelectorParseResult()
    data class SlottedPseudo(val selector: Selector) : SimpleSelectorParseResult()
    data class PseudoElement(val pseudoElement: org.fernice.flare.selector.PseudoElement) : SimpleSelectorParseResult()
}

private fun parseOneSimpleSelector(
    context: SelectorParserContext,
    input: Parser,
    state: SelectorParsingState,
): Result {
    val parseState = input.state()
    val token = when (val token = input.nextIncludingWhitespace()) {
        is Ok -> token.value
        is Err -> {
            input.reset(parseState)
            return Ok(null)
        }
    }

    when (token) {
        is Token.IdHash -> {
            if (state.intersects(SelectorParsingState.AFTER_PSEUDO)) {
                return Err(input.newError(SelectorParseErrorKind.InvalidState))
            }

            val component = Component.ID(token.value)

            return Ok(SimpleSelectorParseResult.SimpleSelector(component))
        }

        is Token.Ampersand -> {
            if (state.intersects(SelectorParsingState.AFTER_PSEUDO)) {
                return Err(input.newError(SelectorParseErrorKind.InvalidState))
            }

            return Ok(SimpleSelectorParseResult.SimpleSelector(Component.ParentSelector))
        }

        is Token.Dot -> {
            if (state.intersects(SelectorParsingState.AFTER_PSEUDO)) {
                return Err(input.newError(SelectorParseErrorKind.InvalidState))
            }

            val location = input.sourceLocation()

            return when (val nextToken = input.nextIncludingWhitespace().unwrap { return it }) {
                is Token.Identifier -> {
                    val component = Component.Class(nextToken.name)

                    Ok(SimpleSelectorParseResult.SimpleSelector(component))
                }

                else -> {
                    Err(location.newError(SelectorParseErrorKind.ClassNeedsIdentifier))
                }
            }
        }

        is Token.LBracket -> {
            if (state.intersects(SelectorParsingState.AFTER_PSEUDO)) {
                return Err(input.newError(SelectorParseErrorKind.InvalidState))
            }
            val attributeSelector = input.parseNestedBlock { parseAttributeSelector(context, it) }.unwrap { return it }
            return Ok(SimpleSelectorParseResult.SimpleSelector(attributeSelector))
        }

        is Token.Colon -> {
            val location = input.sourceLocation()

            val (doubleColon, nextToken) = when (val t = input.nextIncludingWhitespace().unwrap { return it }) {
                is Token.Colon -> true to input.nextIncludingWhitespace().unwrap { return it }
                else -> false to t
            }

            val (name, functional) = when (nextToken) {
                is Token.Identifier -> nextToken.name to false
                is Token.Function -> nextToken.name to true
                else -> return Err(location.newError(SelectorParseErrorKind.PseudoNeedsIdentifier))
            }

            return if (doubleColon || isCSS2PseudoElement(name)) {
                if (!state.allowsPseudos()) {
                    return Err(location.newError(SelectorParseErrorKind.InvalidState))
                }

                val pseudoElement = if (functional) {
                    if (name.equals("part", ignoreCase = true)) {
                        if (!state.allowsPart()) {
                            return Err(location.newError(SelectorParseErrorKind.InvalidState))
                        }

                        val names = input.parseNestedBlock { nestedInput ->
                            val elements = mutableListOf()
                            elements.add(nestedInput.expectIdentifier().unwrap { return@parseNestedBlock it })
                            while (!nestedInput.isExhausted()) {
                                elements.add(nestedInput.expectIdentifier().unwrap { return@parseNestedBlock it })
                            }
                            Ok(elements.resized())
                        }.unwrap { return it }

                        return Ok(SimpleSelectorParseResult.PartPseudo(names))
                    }
                    if (name.equals("slotted", ignoreCase = true)) {
                        if (!state.allowsSlotted()) {
                            return Err(location.newError(SelectorParseErrorKind.InvalidState))
                        }

                        val selector = input.parseNestedBlock { nestedInput ->
                            parseInnerCompoundSelector(context, nestedInput, state)
                        }.unwrap { return it }

                        return Ok(SimpleSelectorParseResult.SlottedPseudo(selector))
                    }

                    input.parseNestedBlock { parseFunctionalPseudoElement(it, location, name) }.unwrap { return it }
                } else {
                    parsePseudoElement(location, name).unwrap { return it }
                }

                if (state.intersects(SelectorParsingState.AFTER_SLOTTED) && !pseudoElement.validAfterSlotted()) {
                    return Err(location.newError(SelectorParseErrorKind.InvalidState))
                }

                Ok(SimpleSelectorParseResult.PseudoElement(pseudoElement))
            } else {
                val pseudoClass = if (functional) {
                    input.parseNestedBlock { parseFunctionalPseudoClass(context, it, location, name, state) }.unwrap { return it }
                } else {
                    parsePseudoClass(location, name, state).unwrap { return it }
                }

                Ok(SimpleSelectorParseResult.SimpleSelector(pseudoClass))
            }
        }

        else -> {
            input.reset(parseState)
            return Ok(null)
        }
    }
}

private fun isCSS2PseudoElement(name: String): Boolean {
    return when (name) {
        "before",
        "after",
        "first-letter",
        "first-line",
        -> true

        else -> false
    }
}

private fun parseFunctionalPseudoElement(
    @Suppress("UNUSED_PARAMETER") input: Parser,
    location: SourceLocation,
    name: String,
): Result {
    return Err(location.newUnexpectedTokenError(Token.Function(name)))
}

private fun parsePseudoElement(location: SourceLocation, name: String): Result {
    return when (name.lowercase()) {
        "before" -> Ok(PseudoElement.Before)
        "after" -> Ok(PseudoElement.After)
        "selection" -> Ok(PseudoElement.Selection)
        "first-letter" -> Ok(PseudoElement.FirstLetter)
        "first-line" -> Ok(PseudoElement.FirstLine)
        "placeholder" -> Ok(PseudoElement.Placeholder)
        "icon" -> Ok(PseudoElement.Flare_Icon)
        else -> Err(location.newUnexpectedTokenError(Token.Identifier(name)))
    }
}

private fun parseFunctionalPseudoClass(
    context: SelectorParserContext,
    input: Parser,
    location: SourceLocation,
    name: String,
    state: SelectorParsingState,
): Result {
    return when (name.lowercase()) {
        "nth-child" -> parseNthPseudoClass(context, input, state, NthType.Child)
        "nth-of-type" -> parseNthPseudoClass(context, input, state, NthType.OfType)
        "nth-last-child" -> parseNthPseudoClass(context, input, state, NthType.LastChild)
        "nth-last-of-type" -> parseNthPseudoClass(context, input, state, NthType.LastOfType)
        "is" -> parseIsOrWhere(context, input, state, Component::Is)
        "where" -> parseIsOrWhere(context, input, state, Component::Where)
        "has" -> parseHas(context, input, state)
        "host" -> {
            if (!state.allowsTreeStructuralPseudoClasses()) {
                return Err(input.newError(SelectorParseErrorKind.InvalidState))
            }

            val selector = parseInnerCompoundSelector(context, input, state).unwrap { return it }
            return Ok(Component.Host(selector))
        }

        "not" -> parseNegation(context, input, state)

        else -> {
            if (!state.allowsCustomFunctionalPseudoClasses()) {
                return Err(input.newError(SelectorParseErrorKind.InvalidState))
            }

            parseNonTSFunctionalPseudoClass(input, location, name).map(Component::NonTSFPseudoClass)
        }
    }
}

private fun parseNthPseudoClass(
    context: SelectorParserContext,
    input: Parser,
    state: SelectorParsingState,
    type: NthType,
): Result {
    if (!state.allowsTreeStructuralPseudoClasses()) {
        return Err(input.newError(SelectorParseErrorKind.InvalidState))
    }

    val (a, b) = parseNth(input).unwrap { return it }
    val nthData = NthData(type, a, b, isFunction = true)

    if (type.isOfType) {
        return Ok(Component.Nth(nthData))
    }

    if (input.tryParse { it.expectIdentifierMatching("of") }.isErr()) {
        return Ok(Component.Nth(nthData))
    }

    val selectors = SelectorList.parseWithState(
        context,
        input,
        state + SelectorParsingState.SKIP_DEFAULT_NAMESPACE + SelectorParsingState.DISALLOW_PSEUDOS,
        ParseForgiving.No,
        ParseRelative.No,
    ).unwrap { return it }

    return Ok(Component.Nth(nthData, selectors.selectors))
}

private fun parseIsOrWhere(
    context: SelectorParserContext,
    input: Parser,
    state: SelectorParsingState,
    wrapper: (List) -> Component,
): Result {
    val selectors = SelectorList.parseWithState(
        context,
        input,
        state + SelectorParsingState.SKIP_DEFAULT_NAMESPACE + SelectorParsingState.DISALLOW_PSEUDOS,
        ParseForgiving.Yes,
        ParseRelative.No,
    ).unwrap { return it }

    return Ok(wrapper(selectors.selectors))
}

private fun parseHas(
    context: SelectorParserContext,
    input: Parser,
    state: SelectorParsingState,
): Result {
    if (state.intersects(SelectorParsingState.DISALLOW_RELATIVE_SELECTORS)) {
        return Err(input.newError(SelectorParseErrorKind.InvalidState))
    }

    val selectors = SelectorList.parseWithState(
        context,
        input,
        state + SelectorParsingState.SKIP_DEFAULT_NAMESPACE + SelectorParsingState.DISALLOW_PSEUDOS + SelectorParsingState.DISALLOW_RELATIVE_SELECTORS,
        ParseForgiving.No,
        ParseRelative.ForHas,
    ).unwrap { return it }

    return Ok(Component.Has(RelativeSelector.fromSelectorList(selectors)))
}

private fun parseNonTSFunctionalPseudoClass(
    input: Parser,
    location: SourceLocation,
    name: String,
): Result {
    return when (name.lowercase()) {
        "lang" -> {
            return when (val identifierResult = input.expectIdentifier()) {
                is Ok -> Ok(NonTSFPseudoClass.Lang(identifierResult.value))
                is Err -> identifierResult
            }
        }

        else -> Err(location.newUnexpectedTokenError(Token.Function(name)))
    }
}

private fun parsePseudoClass(
    location: SourceLocation,
    name: String,
    state: SelectorParsingState,
): Result {
    if (!state.allowsNonFunctionalPseudoClasses()) {
        return Err(location.newError(SelectorParseErrorKind.InvalidState))
    }

    if (state.allowsTreeStructuralPseudoClasses()) {
        when (name.lowercase()) {
            "first-child" -> return Ok(Component.Nth(NthData.first(ofType = false)))
            "last-child" -> return Ok(Component.Nth(NthData.last(ofType = false)))
            "only-child" -> return Ok(Component.Nth(NthData.only(ofType = false)))
            "first-of-type" -> return Ok(Component.Nth(NthData.first(ofType = true)))
            "last-of-type" -> return Ok(Component.Nth(NthData.last(ofType = true)))
            "only-of-type" -> return Ok(Component.Nth(NthData.only(ofType = true)))
            "root" -> return Ok(Component.Root)
            "empty" -> return Ok(Component.Empty)
            "scope" -> return Ok(Component.Scope)
            "host" -> return Ok(Component.Host(selector = null))
            else -> {}
        }
    }

    val pseudoClass = parseNonTSPseudoClass(location, name).unwrap { return it }
    if (state.intersects(SelectorParsingState.AFTER_PSEUDO_ELEMENT) && !pseudoClass.isUserActionState()) {
        return Err(location.newError(SelectorParseErrorKind.InvalidState))
    }
    return Ok(Component.NonTSPseudoClass(pseudoClass))
}

private fun parseNonTSPseudoClass(location: SourceLocation, name: String): Result {
    return when (name.lowercase()) {
        "active" -> Ok(NonTSPseudoClass.Active)
        "checked" -> Ok(NonTSPseudoClass.Checked)
        "autofill" -> Ok(NonTSPseudoClass.Autofill)
        "disabled" -> Ok(NonTSPseudoClass.Disabled)
        "enabled" -> Ok(NonTSPseudoClass.Enabled)
        "defined" -> Ok(NonTSPseudoClass.Defined)
        "focus" -> Ok(NonTSPseudoClass.Focus)
        "focus-visible" -> Ok(NonTSPseudoClass.FocusVisible)
        "focus-within" -> Ok(NonTSPseudoClass.FocusWithin)
        "hover" -> Ok(NonTSPseudoClass.Hover)
        "target" -> Ok(NonTSPseudoClass.Target)
        "indeterminate" -> Ok(NonTSPseudoClass.Indeterminate)
        "fullscreen" -> Ok(NonTSPseudoClass.Fullscreen)
        "modal" -> Ok(NonTSPseudoClass.Modal)
        "optional" -> Ok(NonTSPseudoClass.Optional)
        "required" -> Ok(NonTSPseudoClass.Required)
        "valid" -> Ok(NonTSPseudoClass.Valid)
        "invalid" -> Ok(NonTSPseudoClass.Invalid)
        "user-valid" -> Ok(NonTSPseudoClass.UserValid)
        "user-invalid" -> Ok(NonTSPseudoClass.UserInvalid)
        "in-range" -> Ok(NonTSPseudoClass.InRange)
        "out-of-range" -> Ok(NonTSPseudoClass.OutOfRange)
        "read-write" -> Ok(NonTSPseudoClass.ReadWrite)
        "read-only" -> Ok(NonTSPseudoClass.ReadOnly)
        "default" -> Ok(NonTSPseudoClass.Default)
        "placeholder-shown" -> Ok(NonTSPseudoClass.PlaceholderShown)
        "link" -> Ok(NonTSPseudoClass.Link)
        "any-link" -> Ok(NonTSPseudoClass.AnyLink)
        "visited" -> Ok(NonTSPseudoClass.Visited)
        else -> Err(location.newUnexpectedTokenError(Token.Identifier(name)))
    }
}

private fun parseNegation(
    context: SelectorParserContext,
    input: Parser,
    state: SelectorParsingState,
): Result {
    val selectorList = SelectorList.parseWithState(
        context,
        input,
        state + SelectorParsingState.SKIP_DEFAULT_NAMESPACE + SelectorParsingState.DISALLOW_PSEUDOS,
        ParseForgiving.No,
        ParseRelative.No,
    ).unwrap { return it }

    return Ok(Component.Negation(selectorList.selectors))
}


private fun parseAttributeSelector(context: SelectorParserContext, input: Parser): Result {
    val qualifiedName = when (val qualifiedName = parseQualifiedName(context, input, true)) {
        is Ok -> qualifiedName.value
        is Err -> return qualifiedName
    }

    val localName: String
    val namespace: NamespaceConstraint?

    when (qualifiedName) {
        is QualifiedName.Some -> {
            localName = qualifiedName.localName ?: error("unreachable")

            val prefix = qualifiedName.prefix
            namespace = when (prefix) {
                is QualifiedNamePrefix.ImplicitNoNamespace, is QualifiedNamePrefix.ExplicitNoNamespace -> null
                is QualifiedNamePrefix.ExplicitNamespace -> NamespaceConstraint.Specific(prefix.prefix, prefix.url)
                is QualifiedNamePrefix.ExplicitAnyNamespace -> NamespaceConstraint.Any
                is QualifiedNamePrefix.ImplicitAnyNamespace, is QualifiedNamePrefix.ImplicitDefaultNamespace -> error("unreachable")
            }
        }

        is QualifiedName.None -> {
            return Err(input.newError(SelectorParseErrorKind.NoQualifiedNameInAttributeSelector))
        }
    }

    val location = input.sourceLocation()

    val token = when (val token = input.next()) {
        is Ok -> token.value
        is Err -> {
            val localNameLower = localName.lowercase()
            return if (namespace != null) {
                Ok(
                    Component.AttributeOther(
                        namespace,
                        localName,
                        localNameLower,
                        AttributeSelectorOperation.Exists,
                        false
                    )
                )
            } else {
                Ok(
                    Component.AttributeInNoNamespaceExists(
                        localName,
                        localNameLower
                    )
                )
            }
        }
    }

    val operator = when (token) {
        is Token.Equal -> AttributeSelectorOperator.Equal
        is Token.IncludeMatch -> AttributeSelectorOperator.Includes
        is Token.DashMatch -> AttributeSelectorOperator.DashMatch
        is Token.PrefixMatch -> AttributeSelectorOperator.Prefix
        is Token.SubstringMatch -> AttributeSelectorOperator.Substring
        is Token.SuffixMatch -> AttributeSelectorOperator.Suffix
        else -> return Err(location.newError(SelectorParseErrorKind.UnexpectedTokenInAttributeSelector(token)))
    }

    val value = when (val value = input.expectIdentifierOrString()) {
        is Ok -> value.value
        is Err -> {
            val error = value.value

            return if (error.kind is ParseErrorKind.UnexpectedToken) {
                Err(value.value.location.newError(SelectorParseErrorKind.UnexpectedTokenInAttributeSelector(error.kind.token)))
            } else {
                value
            }
        }
    }

    val neverMatches = when (operator) {
        is AttributeSelectorOperator.Equal, is AttributeSelectorOperator.DashMatch -> false
        is AttributeSelectorOperator.Includes -> value.isEmpty() || value.contains(' ')
        is AttributeSelectorOperator.Prefix,
        is AttributeSelectorOperator.Substring,
        is AttributeSelectorOperator.Suffix,
        -> value.isEmpty()
    }

    val caseSensitive = when (val flagResult = parseAttributeSelectorFlags(input)) {
        is Ok -> flagResult.value
        is Err -> return flagResult
    }

    val localNameLower = localName.lowercase()

    return if (namespace != null) {
        Ok(
            Component.AttributeOther(
                namespace,
                localName,
                localNameLower,
                AttributeSelectorOperation.WithValue(
                    operator,
                    caseSensitive,
                    value
                ),
                neverMatches
            )
        )
    } else {
        Ok(
            Component.AttributeInNoNamespace(
                localName,
                localNameLower,
                operator,
                value,
                caseSensitive,
                neverMatches
            )
        )
    }
}

private fun parseAttributeSelectorFlags(input: Parser): Result {
    val location = input.sourceLocation()

    val token = when (val token = input.next()) {
        is Ok -> token.value
        is Err -> return Ok(true)
    }

    return when (token) {
        is Token.Identifier -> {
            if (token.name.equals("i", true)) {
                Ok(false)
            } else {
                Err(location.newUnexpectedTokenError(token))
            }
        }

        else -> Err(location.newUnexpectedTokenError(token))
    }
}

private const val UPPER_EIGHT_BIT_MASK = 0xff shl 24

class AncestorHashes(val packedHashes: IntArray) {

    companion object {

        fun fromSelector(selector: Selector, quirksMode: QuirksMode): AncestorHashes {
            // compute the ancestor hashes lazily to prevent overhead
            val hashes = IntArray(4)
            AncestorIterator.fromSelector(selector)
                .asSequence()
                .mapNotNull { component -> component.ancestorHash(quirksMode) }
                .take(4)
                .forEachIndexed { index, ancestorHash -> hashes[index] = ancestorHash and HASH_BLOOM_MASK }

            // pack the fourth hash into the upper bytes of the first three
            val fourth = hashes[3]
            if (fourth != 0) {
                hashes[0] = hashes[0] or ((fourth and 0x000000ff) shl 24)
                hashes[1] = hashes[1] or ((fourth and 0x0000ff00) shl 16)
                hashes[2] = hashes[2] or ((fourth and 0x00ff0000) shl 8)
            }

            return AncestorHashes(
                intArrayOf(hashes[0], hashes[1], hashes[2])
            )
        }
    }

    fun fourthHash(): Int {
        return ((packedHashes[0] and UPPER_EIGHT_BIT_MASK) ushr 24) or
                ((packedHashes[0] and UPPER_EIGHT_BIT_MASK) ushr 16) or
                ((packedHashes[0] and UPPER_EIGHT_BIT_MASK) ushr 8)
    }
}

internal fun hashString(string: String): Int {
    return string.hashCode()
}

class AncestorIterator private constructor(private val iterator: SelectorIterator) : Iterator {

    companion object {

        fun fromSelector(selector: Selector): AncestorIterator {
            val iterator = selector.iterator()
            skipUntilAncestor(iterator)
            return AncestorIterator(iterator)
        }

        private fun skipUntilAncestor(iterator: SelectorIterator) {
            while (true) {
                // skip all component of this compound selector
                while (iterator.hasNext()) {
                    iterator.next()
                }

                // check whether there are any more compound selectors
                if (!iterator.hasNextSequence()) break

                val combinator = iterator.nextSequence()

                // stop if as soon as we reach an ancestor compound selector
                if (combinator == Combinator.Child || combinator == Combinator.Descendant) break
            }
        }
    }

    override fun hasNext(): Boolean {
        // evaluate if there are any remaining components in compound selector
        if (iterator.hasNext()) return true

        // advance the sequence and skip all sibling compound selectors
        if (!iterator.hasNextSequence()) return false
        val combinator = iterator.nextSequence()
        if (combinator != Combinator.Child && combinator != Combinator.Descendant) skipUntilAncestor(iterator)

        // reevaluate if there are any remaining components in compound selector
        return iterator.hasNext()
    }

    override fun next(): Component = iterator.next()
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy