
org.fernice.flare.style.stylesheet.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.style.stylesheet
import org.fernice.flare.cssparser.*
import org.fernice.flare.selector.ParseRelative
import org.fernice.flare.selector.SelectorList
import org.fernice.flare.selector.SelectorParser
import org.fernice.flare.style.ContextualError
import org.fernice.flare.style.ParserContext
import org.fernice.flare.style.StyleParseErrorKind
import org.fernice.flare.style.properties.DeclarationParserState
import org.fernice.flare.style.properties.PropertyDeclarationBlock
import org.fernice.flare.style.source.StyleRule
import org.fernice.std.*
import java.util.*
sealed class AtRulePrelude {
// empty shells are used to at support the
// parse-order checks.
// v
data object Import : AtRulePrelude()
data object Namespace : AtRulePrelude()
data object Layer : AtRulePrelude()
// ^
}
sealed class CssRule {
data class Style(val styleRule: StyleRule) : CssRule()
data class Media(val mediaRule: MediaRule) : CssRule()
override fun toString(): String {
return when (this) {
is Style -> "CssRule::Style($styleRule)"
is Media -> "CssRule::Media($mediaRule)"
}
}
}
enum class RuleParserState {
Start,
EarlyLayers,
Imports,
Namespaces,
Body,
}
class RuleParserLevelData(
val declarationParserState: DeclarationParserState,
val errorReportingState: LinkedList,
val rules: MutableList,
) {
fun nest(): RuleParserLevelData {
return RuleParserLevelData(
declarationParserState = DeclarationParserState(),
errorReportingState = errorReportingState,
rules = mutableListOf(),
)
}
companion object {
fun root(): RuleParserLevelData {
return RuleParserLevelData(
declarationParserState = DeclarationParserState(),
errorReportingState = LinkedList(),
rules = mutableListOf(),
)
}
}
}
class TopLevelRuleParser private constructor(
val context: ParserContext,
private var state: RuleParserState,
var domError: Boolean,
val level: RuleParserLevelData,
) : AtRuleParser, QualifiedRuleParser {
private fun checkState(state: RuleParserState): Boolean {
if (state < this.state) {
domError = true
return false
}
return true
}
private fun nested(): NestedRuleParser {
// I've had recurring nightmares. I was transmuting myself into
// a transparent representation of myself, the NestedRuleParser,
// just to take and swap parts of who I am, so I could pretend
// to be someone else.
return NestedRuleParser(context, level)
}
override fun parseAtRulePrelude(name: String, input: Parser): Result {
when (name) {
"import" -> {
if (!checkState(RuleParserState.Imports)) {
return Err(input.newError(StyleParseErrorKind.UnexpectedImportRule))
}
// TODO implementation (at-rule: import)
return Ok(AtRulePrelude.Import)
}
"namespace" -> {
if (!checkState(RuleParserState.Namespaces)) {
return Err(input.newError(StyleParseErrorKind.UnexpectedNamespaceRule))
}
// TODO implementation (at-rule: namespace)
return Ok(AtRulePrelude.Namespace)
}
"charset" -> {
domError = true
return Err(input.newError(StyleParseErrorKind.UnexpectedCharsetRule))
}
"layer" -> {
val stateToCheck = when {
state <= RuleParserState.EarlyLayers -> RuleParserState.EarlyLayers
else -> RuleParserState.Body
}
if (!checkState(stateToCheck)) {
return Err(input.newError(StyleParseErrorKind.UnexpectedLayerRule))
}
// TODO implementation (at-rule: layer)
// This is supposed to be a fallthrough, but the return
// is required for advancing the state correctly. Remove
// when implementing.
return Ok(AtRulePrelude.Layer)
}
else -> {
// at-rules with block are checked in parseAtRuleBlock
}
}
return nested().parseAtRulePrelude(name, input)
}
override fun parseAtRuleBlock(start: ParserState, prelude: AtRulePrelude, input: Parser): Result {
if (!checkState(RuleParserState.Body)) {
return Err(input.newError(ParseErrorKind.Unspecified))
}
nested().parseAtRuleBlock(start, prelude, input).propagate { return it }
state = RuleParserState.Body
return Ok(start.position())
}
override fun parseAtRuleWithoutBlock(start: ParserState, prelude: AtRulePrelude): Result {
when (prelude) {
AtRulePrelude.Import -> {
// TODO implementation (at-rule: import)
state = RuleParserState.Imports
}
AtRulePrelude.Namespace -> {
// TODO implementation (at-rule: namespace)
state = RuleParserState.Namespaces
}
AtRulePrelude.Layer -> {
nested().parseAtRuleWithoutBlock(start, prelude).propagate { return it }
state = if (state < RuleParserState.EarlyLayers) {
RuleParserState.EarlyLayers
} else {
RuleParserState.Body
}
}
else -> nested().parseAtRuleWithoutBlock(start, prelude).propagate { return it }
}
return Ok(start.position())
}
override fun parseQualifiedRulePrelude(input: Parser): Result {
if (!checkState(RuleParserState.Body)) {
return Err(input.newError(ParseErrorKind.Unspecified))
}
return nested().parseQualifiedRulePrelude(input)
}
override fun parseQualifiedRuleBlock(
start: ParserState,
prelude: SelectorList,
input: Parser,
): Result {
nested().parseQualifiedRuleBlock(start, prelude, input)
state = RuleParserState.Body
return Ok(start.position())
}
companion object {
fun from(
context: ParserContext,
): TopLevelRuleParser {
return TopLevelRuleParser(
context = context,
state = RuleParserState.Start,
domError = false,
level = RuleParserLevelData.root(),
)
}
}
}
private class NestedRuleParser(
private val context: ParserContext,
private val level: RuleParserLevelData,
) : RuleBodyItemParser {
private fun isInStyleRule(): Boolean {
return context.ruleTypes.contains(CssRuleType.Style)
}
private inline fun nestForRule(ruleType: CssRuleType, crossinline block: (NestedRuleParser) -> R): R {
val nestedParser = NestedRuleParser(
context = context,
level = level.nest(),
)
return context.nestForRule(ruleType) {
block(nestedParser)
}
}
private fun parseNested(input: Parser, ruleType: CssRuleType): NestedParseResult {
return nestForRule(ruleType) { parser ->
val shouldParseDeclarations = parser.shouldParseDeclarations()
val iter = RuleBodyParser(input, parser)
while (true) {
val result = iter.next() ?: break
when (result) {
is Ok -> {}
is Err -> {
val (error, slice) = result.value
if (shouldParseDeclarations) {
parser.level.declarationParserState.didError(parser.context, error, slice)
} else {
context.reportError(error.location, ContextualError.InvalidRule(slice, error))
}
}
}
}
val declarations = if (shouldParseDeclarations) {
parser.level.declarationParserState.flushErrors(parser.context, parser.level.errorReportingState)
parser.level.declarationParserState.takeDeclarations()
} else {
PropertyDeclarationBlock()
}
assert(!parser.level.declarationParserState.hasDeclarations()) { "declarations were parsed but not consumed" }
NestedParseResult(
parser.level.rules.drain(),
declarations,
)
}
}
private inline fun withErrorReportingState(selectors: SelectorList, block: () -> R): R {
level.errorReportingState.push(selectors)
val result = block()
level.errorReportingState.pop()
return result
}
/// AtRuleParser
override fun parseAtRulePrelude(name: String, input: Parser): Result {
return Err(input.newError(ParseErrorKind.UnsupportedFeature))
}
override fun parseAtRuleBlock(start: ParserState, prelude: AtRulePrelude, input: Parser): Result {
return Err(input.newError(ParseErrorKind.UnsupportedFeature))
}
override fun parseAtRuleWithoutBlock(start: ParserState, prelude: AtRulePrelude): Result {
return Err()
}
/// QualifiedRuleParser
override fun parseQualifiedRulePrelude(input: Parser): Result {
val parser = SelectorParser()
val parseRelative = when {
isInStyleRule() -> ParseRelative.ForNesting
else -> ParseRelative.No
}
return SelectorList.parse(parser, input, parseRelative)
}
override fun parseQualifiedRuleBlock(start: ParserState, prelude: SelectorList, input: Parser): Result {
val result = withErrorReportingState(prelude) {
parseNested(input, CssRuleType.Style)
}
val block = result.declarations
val rules = result.rules.ifEmpty { null }
level.rules.add(
CssRule.Style(
StyleRule(
prelude,
block,
rules,
start.location(),
)
)
)
return Ok()
}
/// DeclarationParser
override fun parseValue(name: String, input: Parser): Result {
return level.declarationParserState.parseValue(context, name, input)
}
/// RuleBodyItemParser
override fun shouldParseDeclarations(): Boolean = true
override fun shouldParseQualifiedRule(): Boolean = isInStyleRule()
}
private class NestedParseResult(
val rules: List,
val declarations: PropertyDeclarationBlock,
)
© 2015 - 2025 Weber Informatics LLC | Privacy Policy