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

commonMain.com.copperleaf.ballast.navigation.internal.PathParser.kt Maven / Gradle / Ivy

There is a newer version: 4.2.1
Show newest version
package com.copperleaf.ballast.navigation.internal

import com.copperleaf.ballast.navigation.routing.PathSegment
import com.copperleaf.kudzu.checkNotEmpty
import com.copperleaf.kudzu.node.NodeContext
import com.copperleaf.kudzu.node.chars.CharNode
import com.copperleaf.kudzu.node.choice.Choice2Node
import com.copperleaf.kudzu.node.choice.Choice4Node
import com.copperleaf.kudzu.node.mapped.ValueNode
import com.copperleaf.kudzu.node.text.TextNode
import com.copperleaf.kudzu.parser.ParseFunction
import com.copperleaf.kudzu.parser.Parser
import com.copperleaf.kudzu.parser.ParserContext
import com.copperleaf.kudzu.parser.ParserException
import com.copperleaf.kudzu.parser.chars.CharInParser
import com.copperleaf.kudzu.parser.choice.ExactChoiceParser
import com.copperleaf.kudzu.parser.many.ManyParser
import com.copperleaf.kudzu.parser.many.SeparatedByParser
import com.copperleaf.kudzu.parser.mapped.FlatMappedParser
import com.copperleaf.kudzu.parser.mapped.MappedParser
import com.copperleaf.kudzu.parser.maybe.MaybeParser
import com.copperleaf.kudzu.parser.runParser
import com.copperleaf.kudzu.parser.sequence.SequenceParser
import com.copperleaf.kudzu.parser.text.IdentifierTokenParser
import com.copperleaf.kudzu.parser.text.LiteralTokenParser

internal object PathParser {

// Path Segments
// ---------------------------------------------------------------------------------------------------------------------

    private val pathSegmentTextParser: Parser = FlatMappedParser(
        SequenceParser(
            IdentifierTokenParser(),
            MaybeParser(
                ManyParser(
                    SequenceParser(
                        CharInParser('-', '_'),
                        IdentifierTokenParser()
                    )
                )
            )
        )
    ) {
        TextNode(it.text, it.context)
    }

    private val staticSegmentParser: Parser> = MappedParser(pathSegmentTextParser) {
        PathSegment.Static(it.text)
    }

    private val wildcardSegmentParser: Parser> = MappedParser(CharInParser('*')) {
        PathSegment.Wildcard
    }

    private val parameterSegmentParser: Parser> = MappedParser(
        ExactChoiceParser(
            SequenceParser(
                CharInParser(':'),
                IdentifierTokenParser()
            ),
            SequenceParser(
                CharInParser('{'),
                IdentifierTokenParser(),
                MaybeParser(CharInParser('?')),
                CharInParser('}'),
            ),
        )
    ) { choiceNode ->
        when (choiceNode) {
            is Choice2Node.Option1 -> {
                PathSegment.Parameter(
                    name = choiceNode.node.node2.text,
                    optional = false
                )
            }

            is Choice2Node.Option2 -> {
                PathSegment.Parameter(
                    name = choiceNode.node.node2.text,
                    optional = choiceNode.node.node3.node != null
                )
            }
        }
    }

    private val tailcardSegmentParser: Parser> = MappedParser(
        SequenceParser(
            CharInParser('{'),
            MaybeParser(IdentifierTokenParser()),
            LiteralTokenParser("..."),
            CharInParser('}'),
        ),
    ) { sequenceNode ->
        PathSegment.Tailcard(
            name = sequenceNode.node2.node?.text
        )
    }

    private val segmentParser: Parser> = MappedParser(
        ExactChoiceParser(
            staticSegmentParser,
            wildcardSegmentParser,
            parameterSegmentParser,
            tailcardSegmentParser,
        )
    ) { choiceNode ->
        when (choiceNode) {
            is Choice4Node.Option1 -> choiceNode.node.value
            is Choice4Node.Option2 -> choiceNode.node.value
            is Choice4Node.Option3 -> choiceNode.node.value
            is Choice4Node.Option4 -> choiceNode.node.value
        }
    }

    internal fun parsePathSegment(segment: String): PathSegment {
        return segmentParser.parse(ParserContext.fromString(segment)).first.value
    }

// Full /-separated path
// ---------------------------------------------------------------------------------------------------------------------

    private class LeadingSlashParser : Parser {
        override fun predict(input: ParserContext): Boolean {
            return input.validateNextChar { it == '/' }
        }

        override val parse: ParseFunction = runParser { input ->
            checkNotEmpty(input)

            val (nextChar, remaining) = input.nextChar()

            if (nextChar != '/') throw ParserException(
                "Path must start with a leading slash",
                this@LeadingSlashParser,
                input
            )

            CharNode(nextChar, NodeContext(input, remaining)) to remaining
        }
    }

    internal val pathParser: Parser>> = MappedParser(
        SequenceParser(
            LeadingSlashParser(),
            MaybeParser(
                SeparatedByParser(
                    term = segmentParser,
                    separator = CharInParser('/'),
                )
            )
        )
    ) { (_, _, segments) ->
        segments.node?.nodeList?.map { it.value } ?: emptyList()
    }

    internal fun parsePath(path: String): List {
        return pathParser.parse(ParserContext.fromString(path)).first.value
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy