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

commonMain.com.sunnychung.lib.multiplatform.kotlite.Parser.kt Maven / Gradle / Ivy

Go to download

A Kotlin Multiplatform library to interpret Kotlite code, which is a subset of Kotlin language, in runtime in a safe way.

The newest version!
package com.sunnychung.lib.multiplatform.kotlite

import com.sunnychung.lib.multiplatform.kotlite.error.ExpectTokenMismatchException
import com.sunnychung.lib.multiplatform.kotlite.error.ParseException
import com.sunnychung.lib.multiplatform.kotlite.error.UnexpectedTokenException
import com.sunnychung.lib.multiplatform.kotlite.extension.removeAfterIndex
import com.sunnychung.lib.multiplatform.kotlite.lexer.Lexer
import com.sunnychung.lib.multiplatform.kotlite.model.IntegerNode
import com.sunnychung.lib.multiplatform.kotlite.model.ASTNode
import com.sunnychung.lib.multiplatform.kotlite.model.AsOpNode
import com.sunnychung.lib.multiplatform.kotlite.model.AssignmentNode
import com.sunnychung.lib.multiplatform.kotlite.model.BinaryOpNode
import com.sunnychung.lib.multiplatform.kotlite.model.BlockNode
import com.sunnychung.lib.multiplatform.kotlite.model.BooleanNode
import com.sunnychung.lib.multiplatform.kotlite.model.BreakNode
import com.sunnychung.lib.multiplatform.kotlite.model.CatchNode
import com.sunnychung.lib.multiplatform.kotlite.model.CharNode
import com.sunnychung.lib.multiplatform.kotlite.model.ClassDeclarationNode
import com.sunnychung.lib.multiplatform.kotlite.model.ClassInstanceInitializerNode
import com.sunnychung.lib.multiplatform.kotlite.model.ClassMemberReferenceNode
import com.sunnychung.lib.multiplatform.kotlite.model.ClassModifier
import com.sunnychung.lib.multiplatform.kotlite.model.ClassParameterNode
import com.sunnychung.lib.multiplatform.kotlite.model.ClassPrimaryConstructorNode
import com.sunnychung.lib.multiplatform.kotlite.model.ContinueNode
import com.sunnychung.lib.multiplatform.kotlite.model.DoWhileNode
import com.sunnychung.lib.multiplatform.kotlite.model.DoubleNode
import com.sunnychung.lib.multiplatform.kotlite.model.ElvisOpNode
import com.sunnychung.lib.multiplatform.kotlite.model.EnumEntryNode
import com.sunnychung.lib.multiplatform.kotlite.model.ForNode
import com.sunnychung.lib.multiplatform.kotlite.model.FunctionBodyFormat
import com.sunnychung.lib.multiplatform.kotlite.model.FunctionCallArgumentNode
import com.sunnychung.lib.multiplatform.kotlite.model.FunctionCallNode
import com.sunnychung.lib.multiplatform.kotlite.model.FunctionDeclarationNode
import com.sunnychung.lib.multiplatform.kotlite.model.FunctionModifier
import com.sunnychung.lib.multiplatform.kotlite.model.FunctionTypeNode
import com.sunnychung.lib.multiplatform.kotlite.model.FunctionValueParameterModifier
import com.sunnychung.lib.multiplatform.kotlite.model.FunctionValueParameterNode
import com.sunnychung.lib.multiplatform.kotlite.model.IfNode
import com.sunnychung.lib.multiplatform.kotlite.model.IndexOpNode
import com.sunnychung.lib.multiplatform.kotlite.model.InfixFunctionCallNode
import com.sunnychung.lib.multiplatform.kotlite.model.LabelNode
import com.sunnychung.lib.multiplatform.kotlite.model.LambdaLiteralNode
import com.sunnychung.lib.multiplatform.kotlite.model.LongNode
import com.sunnychung.lib.multiplatform.kotlite.model.NavigationNode
import com.sunnychung.lib.multiplatform.kotlite.model.NullNode
import com.sunnychung.lib.multiplatform.kotlite.model.PropertyAccessorsNode
import com.sunnychung.lib.multiplatform.kotlite.model.PropertyDeclarationNode
import com.sunnychung.lib.multiplatform.kotlite.model.PropertyModifier
import com.sunnychung.lib.multiplatform.kotlite.model.ReturnNode
import com.sunnychung.lib.multiplatform.kotlite.model.ScopeType
import com.sunnychung.lib.multiplatform.kotlite.model.ScriptNode
import com.sunnychung.lib.multiplatform.kotlite.model.SourcePosition
import com.sunnychung.lib.multiplatform.kotlite.model.StringFieldIdentifierNode
import com.sunnychung.lib.multiplatform.kotlite.model.StringLiteralNode
import com.sunnychung.lib.multiplatform.kotlite.model.StringNode
import com.sunnychung.lib.multiplatform.kotlite.model.ThrowNode
import com.sunnychung.lib.multiplatform.kotlite.model.Token
import com.sunnychung.lib.multiplatform.kotlite.model.TokenType
import com.sunnychung.lib.multiplatform.kotlite.model.TryNode
import com.sunnychung.lib.multiplatform.kotlite.model.TypeNode
import com.sunnychung.lib.multiplatform.kotlite.model.TypeParameterNode
import com.sunnychung.lib.multiplatform.kotlite.model.UnaryOpNode
import com.sunnychung.lib.multiplatform.kotlite.model.ValueParameterDeclarationNode
import com.sunnychung.lib.multiplatform.kotlite.model.VariableReferenceNode
import com.sunnychung.lib.multiplatform.kotlite.model.WhenConditionNode
import com.sunnychung.lib.multiplatform.kotlite.model.WhenEntryNode
import com.sunnychung.lib.multiplatform.kotlite.model.WhenNode
import com.sunnychung.lib.multiplatform.kotlite.model.WhenSubjectNode
import com.sunnychung.lib.multiplatform.kotlite.model.WhileNode

val ACCEPTED_MODIFIERS = setOf(
    "open", "override", "operator", "vararg", "enum", "abstract", "infix", "nullaware"
)

/**
 * Reference grammar: https://kotlinlang.org/spec/syntax-and-grammar.html#grammar-rule-expression
 */
open class Parser(protected val lexer: Lexer) {
    internal val allTokens = mutableListOf()
    internal val tokenCharIndexes = mutableListOf()

    internal var currentToken: Token

    /**
     * Current token index
     */
    internal var tokenIndex = 0

    init {
//        log.v { "Tokens = $allTokens" }
        tokenCharIndexes += lexer.currentCursor()
        currentToken = readToken()
        tokenIndex = 0
    }

    internal fun readToken(): Token {
        if (tokenIndex + 1 < allTokens.size) {
            return allTokens[tokenIndex + 1]
        } else if (allTokens.lastOrNull()?.type == TokenType.EOF) {
            throw IndexOutOfBoundsException()
        }
        return lexer.readToken().also {
            currentToken = it
            allTokens += it
            tokenCharIndexes += lexer.currentCursor()
            ++tokenIndex
        }
    }

    private fun peekNextToken(): Token {
        val originalTokenIndex = tokenIndex
        val t = try {
            readToken()
        } catch (e: Throwable) {
            Token(TokenType.Unknown, "", lexer.makeSourcePosition())
        }
        resetTokenToIndex(originalTokenIndex)
        return t
    }

    private fun lastToken() = allTokens[tokenIndex - 1]

    internal fun resetTokenToIndex(index: Int) {
        tokenIndex = index
        currentToken = allTokens[index]
        lexer.resetCursorTo(tokenCharIndexes[index + 1]) // cursor pointing to start of next token
        // discard already read tokens, because they are invalid after changing mode
        allTokens.removeAfterIndex(index)
        tokenCharIndexes.removeAfterIndex(index + 1)
    }

    private fun switchMode(mode: Lexer.Mode) {
        lexer.switchToMode(mode)
    }

    private fun exitMode() {
        lexer.switchToPreviousMode()
    }

    fun eat(tokenType: TokenType): Token {
        if (currentToken.type != tokenType) throw ExpectTokenMismatchException("$tokenType", currentToken.position)
        log.v { "ate $tokenType: ${currentToken.value}" }
        val t = currentToken
        if (t.type != TokenType.EOF) {
            currentToken = readToken()
        }
        return t
    }

    fun eat(tokenType: TokenType, value: Any): Token {
        if (currentToken.type != tokenType || currentToken.value != value) throw ExpectTokenMismatchException("$tokenType `$value`", currentToken.position)
        log.v { "ate $tokenType $value" }
        val t = currentToken
        currentToken = readToken()
        return t
    }

    fun isCurrentToken(type: TokenType, value: Any) =
        currentToken.type == type && currentToken.value == value

    fun currentTokenExcludingNL(isResetIndex: Boolean = true): Token {
        val originalTokenIndex = tokenIndex
        var t: Token = currentToken
        while (t.type == TokenType.NewLine) {
            t = readToken()
        }
        if (isResetIndex) {
            resetTokenToIndex(originalTokenIndex)
        }
        return t
    }

    fun isCurrentTokenLabel(): Boolean {
        val t = currentToken
        return parseAndRollback {
            try {
                val next1 = readToken()
                val next2 = readToken()

                t.type == TokenType.Identifier && next1.`is`(
                    TokenType.Symbol,
                    "@"
                ) && areTokensConsecutive(t, next1) && !areTokensConsecutive(next1, next2)
            } catch (_: Throwable) {
                false
            }
        }
    }

    fun currentTokenExcludingLabel(isResetIndex: Boolean = true): Token {
        val originalTokenIndex = tokenIndex
        var t: Token = currentToken
        while (t.type == TokenType.Identifier && peekNextToken().`is`(TokenType.Symbol, "@") && areTokensConsecutive(t, peekNextToken())) {
            readToken()
            t = readToken()
        }
        if (isResetIndex) {
            resetTokenToIndex(originalTokenIndex)
        }
        return t
    }

    fun isCurrentTokenExcludingNL(type: TokenType, value: Any): Boolean {
        val t = currentTokenExcludingNL()
        return t.type == type && t.value == value
    }

    fun isLastToken(type: TokenType, value: Any): Boolean {
        val t = lastToken()
        return t.type == type && t.value == value
    }

    fun repeatedNL() {
        while (currentToken.type == TokenType.NewLine) {
            eat(TokenType.NewLine)
        }
    }

    fun semi() {
        if (currentToken.type == TokenType.Semicolon) {
            eat(TokenType.Semicolon)
        } else {
            eat(TokenType.NewLine)
        }
        repeatedNL()
    }

    /**
     * semis:
     *     ';' | NL {';' | NL}
     */
    fun semis() {
        if (currentToken.type == TokenType.Semicolon) {
            eat(TokenType.Semicolon)
        } else {
            eat(TokenType.NewLine)
            while (currentToken.type in setOf(TokenType.Semicolon, TokenType.NewLine)) {
                eat(currentToken.type)
            }
        }
    }

    fun isSemi(): Boolean {
        return currentToken.type in setOf(TokenType.Semicolon, TokenType.NewLine)
    }

    fun userDefinedIdentifier(): String {
        return eat(TokenType.Identifier).value as String // TODO validate not reserved words
    }

    fun label(): LabelNode {
        val identifier = eat(TokenType.Identifier)
        val at = eat(TokenType.Symbol, "@")
        if (!areTokensConsecutive(identifier, at)) {
            throw ExpectTokenMismatchException("@", identifier.position.let {
                it.copy(col = it.col + (identifier.value as String).length)
            })
        }
        repeatedNL()
        return LabelNode(position = identifier.position, label = identifier.value as String)
    }

    fun  tryParse(operation: () -> T): T? {
        val originalIndex = tokenIndex
        val modeStackLastIndex = lexer.mode.lastIndex
        return try {
            operation()
        } catch (_: ParseException) {
            resetTokenToIndex(originalIndex)
            lexer.mode.removeAfterIndex(modeStackLastIndex)
            null
        }
    }

    fun  parseAndRollback(operation: () -> T): T {
        val originalIndex = tokenIndex
        val modeStackLastIndex = lexer.mode.lastIndex
        return try {
            operation()
        } finally {
            resetTokenToIndex(originalIndex)
            lexer.mode.removeAfterIndex(modeStackLastIndex)
        }
    }

    fun parenthesizedExpression(): ASTNode {
        eat(TokenType.Operator, "(")
        val node = expression()
        eat(TokenType.Operator, ")")
        return node
    }

    /**
     * unaryPrefix:
     *     annotation
     *     | label
     *     | (prefixUnaryOperator {NL})
     */
    fun unaryPrefix(): ASTNode? { // TODO complete
        if (currentToken.type == TokenType.Operator && currentToken.value in setOf("++", "--", "-", "+", "!")) {
            val t = eat(TokenType.Operator)
            return when (t.value) {
                "++", "--" -> UnaryOpNode(position = t.position, operator = "pre${t.value}", node = null)
                else -> UnaryOpNode(position = t.position, operator = t.value as String, node = null)
            }
        } else if (isCurrentTokenLabel()) {
            return label()
        }
        return null
    }

    /**
     *
     * prefixUnaryExpression:
     *     {unaryPrefix} postfixUnaryExpression
     *
     */
    fun prefixUnaryExpression(): ASTNode {
        val nodes = mutableListOf()
        var label: LabelNode? = null
        do {
            val curr = unaryPrefix()
            if (curr is UnaryOpNode) {
                if (nodes.isNotEmpty()) {
                    nodes.last().node = curr
                }
                nodes += curr
            } else if (curr is LabelNode) {
                label = curr
            }
        } while (curr != null)
        val expr = postfixUnaryExpression(label = label)
        if (nodes.isNotEmpty()) {
            nodes.last().node = expr
            return nodes.first()
        } else {
            return expr
        }
    }

    /**
     * postfixUnaryExpression:
     *     primaryExpression {postfixUnarySuffix}
     */
    fun postfixUnaryExpression(label: LabelNode? = null): ASTNode {
        var result = primaryExpression(label) // TODO complete expression
        while (currentTokenExcludingNL().type in setOf(TokenType.Operator, TokenType.Symbol)) {
            val newResult = postfixUnarySuffix(result)
            if (newResult == result) break
            result = newResult
        }
        return result
    }

    /**
     * navigationSuffix:
     *     memberAccessOperator {NL} (simpleIdentifier | parenthesizedExpression | 'class')
     *
     * memberAccessOperator:
     *     ({NL} '.')
     *     | ({NL} safeNav)
     *     | '::'
     *
     * safeNav:
     *     QUEST_NO_WS '.'
     *
     * QUEST_NO_WS:
     *     '?'
     *
     */
    fun navigationSuffix(subject: ASTNode): NavigationNode {
        repeatedNL()
        val operator = eat(TokenType.Operator)
        if (operator.value !in setOf(".", "?.")) {
            throw UnexpectedTokenException(operator)
        }
        repeatedNL()
        val memberExpression = if (isCurrentToken(TokenType.Operator, "(")) {
            /*
                Use case: https://discuss.kotlinlang.org/t/how-is-parser-able-to-process-function-calls-on-objects-like-a-foo/25579/7

                fun getMember() : Int.() -> Int = { this + 1 }

                fun main() {
                    println(42.(getMember())())
                }

                This will only be supported AFTER lambda is supported
             */
            throw UnsupportedOperationException("Lambda is not supported")
//            parenthesizedExpression()
        } else {
            val t = eat(TokenType.Identifier)
            ClassMemberReferenceNode(position = t.position, t.value as String) // any better node?
        }
        return NavigationNode(operator.position, subject, operator.value as String, memberExpression)
    }

    /**
     * indexingSuffix:
     *     '['
     *     {NL}
     *     expression
     *     {{NL} ',' {NL} expression}
     *     [{NL} ',']
     *     {NL}
     *     ']'
     */
    fun indexingSuffix(subject: ASTNode): IndexOpNode {
        val expressions = mutableListOf()
        var hasEatenComma = false
        val t = eat(TokenType.Operator, "[")
        repeatedNL()
        expressions += expression()
        repeatedNL()
        if (isCurrentToken(TokenType.Symbol, ",")) {
            eat(TokenType.Symbol, ",")
            hasEatenComma = true
            repeatedNL()
        }
        while (!isCurrentToken(TokenType.Operator, "]")) {
            if (!hasEatenComma) throw ExpectTokenMismatchException(", ", currentToken.position)
            expressions += expression()
            hasEatenComma = false
            repeatedNL()
            if (isCurrentToken(TokenType.Symbol, ",")) {
                eat(TokenType.Symbol, ",")
                hasEatenComma = true
                repeatedNL()
            }
        }
        eat(TokenType.Operator, "]")
        return IndexOpNode(position = t.position, subject = subject, arguments = expressions)
    }

    /**
     * postfixUnarySuffix:
     *     postfixUnaryOperator
     *     | typeArguments
     *     | callSuffix
     *     | indexingSuffix
     *     | navigationSuffix
     */
    fun postfixUnarySuffix(subject: ASTNode): ASTNode {
        val originalTokenIndex = tokenIndex
        val t = currentToken
        when (val op = t.value as? String) { // TODO complete
            "[" -> return indexingSuffix(subject)
            "++", "--" -> { eat(TokenType.Operator, op); return UnaryOpNode(t.position, subject, "post$op") }
            "!" -> { // this rule prevents conflict with consecutive boolean "Not" operators
                val nextToken = peekNextToken()
                if (nextToken.type == TokenType.Operator && nextToken.value == "!") {
                    eat(TokenType.Operator, op)
                    eat(TokenType.Operator, op)
                    return UnaryOpNode(t.position, subject, "!!")
                }
            }
            "(", "{", "<" -> try {
                return callSuffix(subject)
            } catch (_: ParseException) {
                resetTokenToIndex(originalTokenIndex)
            }
        }
        if (isCurrentTokenLabel()) {
            return callSuffix(subject)
        }
        when (currentTokenExcludingNL().value) {
            ".", "?." -> return navigationSuffix(subject)
        }
        return subject
    }

    /**
     * callSuffix:
     *     [typeArguments] (([valueArguments] annotatedLambda) | valueArguments)
     */
    fun callSuffix(subject: ASTNode): FunctionCallNode {
        val position = currentToken.position
        val arguments = mutableListOf()
        val typeArguments = if (isCurrentToken(TokenType.Operator, "<")) {
            typeArguments()
        } else emptyList()
        if (isCurrentToken(TokenType.Operator, "(")) {
            arguments += valueArguments()
        }
        val label = if (isCurrentTokenLabel()) {
            label()
        } else null
        if (isCurrentToken(TokenType.Symbol, "{")) {
            val lambda = lambdaLiteral()
            arguments += FunctionCallArgumentNode(
                position = lambda.position,
                index = arguments.size,
                value = lambda
            )
        } else if (label != null) {
            throw ExpectTokenMismatchException("{", currentToken.position)
        }
        return FunctionCallNode(
            function = subject,
            arguments = arguments,
            declaredTypeArguments = typeArguments,
            position = position
        )
    }

    /**
     * valueArguments:
     *     '(' {NL} [valueArgument {{NL} ',' {NL} valueArgument} [{NL} ','] {NL}] ')'
     *
     */
    fun valueArguments(): List {
        val arguments = mutableListOf()
        var isLastTokenComma = false
        eat(TokenType.Operator, "(")
        repeatedNL()
        while (!isCurrentToken(TokenType.Operator, ")")) {
            if (arguments.isNotEmpty()) {
                if (!isLastTokenComma) throw UnexpectedTokenException(currentToken)
            }
            arguments += valueArgument(index = arguments.size)
            repeatedNL()
            isLastTokenComma = false
            if (isCurrentToken(TokenType.Symbol, ",")) {
                eat(TokenType.Symbol, ",")
                repeatedNL()
                isLastTokenComma = true
            }
        }
        eat(TokenType.Operator, ")")
        return arguments
    }

    /**
     * valueArgument:
     *     [annotation]
     *     {NL}
     *     [simpleIdentifier {NL} '=' {NL}]
     *     ['*']
     *     {NL}
     *     expression
     */
    fun valueArgument(index: Int): FunctionCallArgumentNode {
        repeatedNL()
        val t = currentToken
        val name = if (peekNextToken().type == TokenType.Symbol && peekNextToken().value == "=") {
            val name = userDefinedIdentifier()
            eat(TokenType.Symbol, "=")
            name
        } else null
        repeatedNL()
        val value = expression()
        return FunctionCallArgumentNode(position = t.position, index = index, name = name, value = value)
    }

    /**
     * assignableExpression:
     *     prefixUnaryExpression
     *     | parenthesizedAssignableExpression
     */
    fun assignableExpression(): ASTNode {
        val currentToken = currentToken
        when (currentToken.type) {
            TokenType.Operator -> {
                if (currentToken.value == "(") {
                    return parenthesizedExpression()
                }
            }
            else -> return prefixUnaryExpression()
        }
        throw UnexpectedTokenException(currentToken)
    }

    /**
     * ifExpression:
     *     'if'
     *     {NL}
     *     '('
     *     {NL}
     *     expression
     *     {NL}
     *     ')'
     *     {NL}
     *     (controlStructureBody | ([controlStructureBody] {NL} [';'] {NL} 'else' {NL} (controlStructureBody | ';')) | ';')
     *
     */
    fun ifExpression(): ASTNode {
        val t = eat(TokenType.Identifier, "if")
        repeatedNL()
        eat(TokenType.Operator, "(")
        repeatedNL()
        val condition = expression()
        repeatedNL()
        eat(TokenType.Operator, ")")
        repeatedNL()
        val trueBlock = if (isCurrentToken(TokenType.Semicolon, ";") || isCurrentToken(TokenType.Identifier, "else")) {
            null
        } else {
            controlStructureBody(ScopeType.If)
        }
        var hasEatSemicolonAfterCondition = if (isCurrentToken(TokenType.Semicolon, ";")) {
            eat(TokenType.Semicolon, ";")
            true
        } else false
        val falseBlock = if (isCurrentTokenExcludingNL(TokenType.Identifier, "else")) {
            repeatedNL()
            eat(TokenType.Identifier, "else")
            repeatedNL()
            if (isCurrentToken(TokenType.Semicolon, ";")) {
                eat(TokenType.Semicolon, ";")
                null
            } else {
                controlStructureBody(ScopeType.If)
            }
        } else null
        if (trueBlock == null && falseBlock == null) {
            if (!hasEatSemicolonAfterCondition) throw ExpectTokenMismatchException(";", lastToken().position)
        }
        return IfNode(position = t.position, condition = condition, trueBlock = trueBlock, falseBlock = falseBlock)
    }

    fun stringContentOrExpression(addNode: (ASTNode) -> Unit) {
        when (currentToken.type) {
            TokenType.StringLiteral -> {
                val t = eat(TokenType.StringLiteral)
                addNode(StringLiteralNode(t.position, t.value as String))
            }
            TokenType.StringFieldIdentifier -> {
                val t = eat(TokenType.StringFieldIdentifier)
                addNode(StringFieldIdentifierNode(t.position, t.value as String))
            }
            TokenType.Symbol -> {
                if (currentToken.value == "\${") {
                    switchMode(Lexer.Mode.Main)
                    eat(TokenType.Symbol, "\${")
                    repeatedNL()
                    addNode(expression())
                    repeatedNL()
                    exitMode() // switch mode before reading next token
                    eat(TokenType.Symbol, "}")
                } else {
                    throw UnexpectedTokenException(currentToken)
                }
            }
            else -> throw UnexpectedTokenException(currentToken)
        }
    }

    /**
     *
     * lineStringLiteral:
     *     '"' {lineStringContent | lineStringExpression} '"'
     *
     * lineStringContent:
     *     LineStrText
     *     | LineStrEscapedChar
     *     | LineStrRef
     *
     * lineStringExpression:
     *     '${'
     *     {NL}
     *     expression
     *     {NL}
     *     '}'
     *
     */
    fun lineStringLiteral(): ASTNode {
        val nodes = mutableListOf()
        switchMode(Lexer.Mode.QuotedString) // switch mode before reading next token
        val t = eat(TokenType.Symbol, "\"")
        while (!isCurrentToken(TokenType.Symbol, "\"")) {
            stringContentOrExpression { nodes += it }
        }
        exitMode()
        eat(TokenType.Symbol, "\"")
        return StringNode(t.position, nodes)
    }

    /**
     * multiLineStringLiteral:
     *     '"""' {multiLineStringContent | multiLineStringExpression | '"'} TRIPLE_QUOTE_CLOSE
     *
     * multiLineStringContent:
     *     MultiLineStrText
     *     | '"'
     *     | MultiLineStrRef
     *
     * multiLineStringExpression:
     *     '${'
     *     {NL}
     *     expression
     *     {NL}
     *     '}'
     */
    fun multiLineStringLiteral(): ASTNode {
        val nodes = mutableListOf()
        switchMode(Lexer.Mode.MultilineString) // switch mode before reading next token
        val t = eat(TokenType.Symbol, "\"\"\"")
        while (!isCurrentToken(TokenType.Symbol, "\"\"\"")) {
            stringContentOrExpression { nodes += it }
        }
        exitMode()
        eat(TokenType.Symbol, "\"\"\"")
        return StringNode(t.position, nodes)
    }

    /**
     * stringLiteral:
     *     lineStringLiteral
     *     | multiLineStringLiteral
     *
     */
    fun stringLiteral(): ASTNode {
        if (currentToken.type != TokenType.Symbol) throw UnexpectedTokenException(currentToken)
        return when (currentToken.value) {
            "\"" -> lineStringLiteral()
            "\"\"\"" -> multiLineStringLiteral()
            else -> throw UnexpectedTokenException(currentToken)
        }
    }

    /**
     * lambdaLiteral:
     *     '{'
     *     {NL}
     *     [[lambdaParameters] {NL} '->' {NL}]
     *     statements
     *     {NL}
     *     '}'
     *
     * lambdaParameters:
     *     lambdaParameter {{NL} ',' {NL} lambdaParameter} [{NL} ',']
     *
     * lambdaParameter:
     *     variableDeclaration
     *     | (multiVariableDeclaration [{NL} ':' {NL} type])
     *
     */
    fun lambdaLiteral(label: LabelNode? = null): LambdaLiteralNode {
        val parameters = mutableListOf()

        val position = eat(TokenType.Symbol, "{").position
        repeatedNL()

        val originalIndex = tokenIndex
        val modeStackLastIndex = lexer.mode.lastIndex
        try {
            var hasEatenComma = false
            while (!isCurrentTokenExcludingNL(TokenType.Symbol, "->")) {
                if (parameters.isNotEmpty() && !hasEatenComma) {
                    throw ExpectTokenMismatchException(",", currentToken.position)
                }
                val t = currentToken
                val (name, type) = variableDeclaration()
                parameters += FunctionValueParameterNode(
                    position = t.position,
                    name = name,
                    declaredType = type,
                    modifiers = emptySet(),
                    defaultValue = null,
                )
                if (isCurrentTokenExcludingNL(TokenType.Symbol, ",")) {
                    repeatedNL()
                    eat(TokenType.Symbol, ",")
                    repeatedNL()
                    hasEatenComma = true
                }
            }
            eat(TokenType.Symbol, "->")
            repeatedNL()
        } catch (_: ParseException) {
            resetTokenToIndex(originalIndex)
            lexer.mode.removeAfterIndex(modeStackLastIndex)
            parameters.clear()
        }

        val statements = statements()

        repeatedNL()
        eat(TokenType.Symbol, "}")

        return LambdaLiteralNode(
            position = position,
            declaredValueParameters = parameters,
            body = BlockNode(statements, position, ScopeType.FunctionBlock, FunctionBodyFormat.Lambda),
            label = label,
        )
    }

    /**
     *
     * functionLiteral:
     *     lambdaLiteral
     *     | anonymousFunction
     *
     */
    fun functionLiteral(label: LabelNode? = null) = lambdaLiteral(label = label)

    /**
     * catchBlock:
     *     'catch'
     *     {NL}
     *     '('
     *     {annotation}
     *     simpleIdentifier
     *     ':'
     *     type
     *     [{NL} ',']
     *     ')'
     *     {NL}
     *     block
     *
     */
    fun catchBlock(): CatchNode {
        val t = eat(TokenType.Identifier, "catch")
        repeatedNL()
        eat(TokenType.Operator, "(")
        val valueName = userDefinedIdentifier()
        eat(TokenType.Symbol, ":")
        val type = type(isTryParenthesizedType = false)
        eat(TokenType.Operator, ")")
        repeatedNL()
        val catchBlock = block(ScopeType.Catch)
        return CatchNode(position = t.position, valueName = valueName, catchType = type, block = catchBlock)
    }

    /**
     * finallyBlock:
     *     'finally' {NL} block
     *
     */
    fun finallyBlock(): BlockNode {
        eat(TokenType.Identifier, "finally")
        repeatedNL()
        return block(ScopeType.Finally)
    }

    /**
     * tryExpression:
     *     'try' {NL} block (
     *         ( ({NL} catchBlock {{NL} catchBlock}) [{NL} finallyBlock] )
     *         |
     *         ({NL} finallyBlock)
     *     )
     *
     */
    fun tryExpression(): TryNode {
        val t = eat(TokenType.Identifier, "try")
        repeatedNL()
        val mainBlock = block(ScopeType.Try)
        repeatedNL()
        val catchNodes = mutableListOf()
        if (currentToken.`is`(TokenType.Identifier, "catch")) {
            while (currentTokenExcludingNL().`is`(TokenType.Identifier, "catch")) {
                repeatedNL()
                catchNodes += catchBlock()
            }
        } else {
            if (!currentToken.`is`(TokenType.Identifier, "finally")) {
                throw ExpectTokenMismatchException("catch / finally", currentToken.position)
            }
        }
        val finallyBlock = if (currentTokenExcludingNL().`is`(TokenType.Identifier, "finally")) {
            repeatedNL()
            finallyBlock()
        } else null
        return TryNode(
            position = t.position,
            mainBlock = mainBlock,
            catchBlocks = catchNodes,
            finallyBlock = finallyBlock,
        )
    }

    /**
     * whenSubject:
     *     '(' [{annotation} {NL} 'val' {NL} variableDeclaration {NL} '=' {NL}] expression ')'
     *
     */
    fun whenSubject(): WhenSubjectNode {
        val t = eat(TokenType.Operator, "(")
        var valueDeclaration: Pair? = null
        repeatedNL()
        if (currentToken.`is`(TokenType.Identifier, "val")) {
            eat(TokenType.Identifier, "val")
            repeatedNL()
            valueDeclaration = variableDeclaration()
            repeatedNL()
            eat(TokenType.Symbol, "=")
            repeatedNL()
        }
        val expr = expression()
        repeatedNL()
        eat(TokenType.Operator, ")")
        return WhenSubjectNode(
            position = t.position,
            valueName = valueDeclaration?.first,
            declaredType = valueDeclaration?.second,
            value = expr,
        )
    }

    /**
     * whenCondition:
     *     expression
     *     | rangeTest
     *     | typeTest
     *
     * rangeTest:
     *     inOperator {NL} expression
     *
     * typeTest:
     *     isOperator {NL} type
     *
     */
    fun whenCondition(): WhenConditionNode {
        // TODO: rangeTest
        if (
            currentToken.`is`(TokenType.Identifier, "is")
            || (currentToken.`is`(TokenType.Operator, "!") && peekNextToken().`is`(TokenType.Identifier, "is"))
        ) {
            val isNegate = if (currentToken.`is`(TokenType.Operator, "!")) {
                eat(TokenType.Operator, "!")
                true
            } else {
                false
            }
            val t = eat(TokenType.Identifier, "is")
            repeatedNL()
            val type = type()
            return WhenConditionNode(
                position = t.position,
                testType = WhenConditionNode.TestType.TypeTest,
                isNegateResult = isNegate,
                expression = type,
            )
        } else if (
            currentToken.`is`(TokenType.Identifier, "in")
            || (currentToken.`is`(TokenType.Operator, "!") && peekNextToken().`is`(TokenType.Identifier, "in"))
        ) {
            val isNegate = if (currentToken.`is`(TokenType.Operator, "!")) {
                eat(TokenType.Operator, "!")
                true
            } else {
                false
            }
            val t = eat(TokenType.Identifier, "in")
            repeatedNL()
            val expr = expression()
            return WhenConditionNode(
                position = t.position,
                testType = WhenConditionNode.TestType.RangeTest,
                isNegateResult = isNegate,
                expression = expr,
            )
        } else {
            val t = currentToken
            val expr = expression()
            return WhenConditionNode(
                position = t.position,
                testType = WhenConditionNode.TestType.Regular,
                isNegateResult = false,
                expression = expr,
            )
        }
    }

    /**
     *
     * whenEntry:
     *     (whenCondition {{NL} ',' {NL} whenCondition} [{NL} ','] {NL} '->' {NL} controlStructureBody [semi])
     *     | ('else' {NL} '->' {NL} controlStructureBody [semi])
     *
     */
    fun whenEntry(): WhenEntryNode {
        val t = currentToken
        val conditions = if (t.`is`(TokenType.Identifier, "else")) {
            eat(TokenType.Identifier, "else")
            repeatedNL()
            eat(TokenType.Symbol, "->")
            repeatedNL()
            emptyList()
        } else {
            buildList {
                var hasComma = false
                do {
                    if (isNotEmpty() && !hasComma) {
                        throw ExpectTokenMismatchException(",", currentToken.position)
                    }
                    add(whenCondition())
                    repeatedNL()
                    hasComma = false
                    if (currentToken.`is`(TokenType.Symbol, ",")) {
                        eat(TokenType.Symbol, ",")
                        repeatedNL()
                        hasComma = true
                    }
                } while (!isCurrentToken(TokenType.Symbol, "->"))
                eat(TokenType.Symbol, "->")
                repeatedNL()
            }
        }
        repeatedNL()
        val body = controlStructureBody(ScopeType.WhenBody)
        if (isSemi()) {
            semi()
        }
        return WhenEntryNode(
            position = t.position,
            conditions = conditions,
            body = body,
        )
    }

    /**
     * whenExpression:
     *     'when'
     *     {NL}
     *     [whenSubject]
     *     {NL}
     *     '{'
     *     {NL}
     *     {whenEntry {NL}}
     *     {NL}
     *     '}'
     */
    fun whenExpression(): WhenNode {
        val t = eat(TokenType.Identifier, "when")
        repeatedNL()
        val subject = if (currentToken.`is`(TokenType.Operator, "(")) {
            whenSubject().also { repeatedNL() }
        } else null
        eat(TokenType.Symbol, "{")
        repeatedNL()
        val entries = mutableListOf()
        while (!isCurrentTokenExcludingNL(TokenType.Symbol, "}")) {
            entries += whenEntry()
            repeatedNL()
        }
        eat(TokenType.Symbol, "}")
        return WhenNode(
            position = t.position,
            subject = subject,
            entries = entries,
        )
    }

    /**
     * primaryExpression:
     *     parenthesizedExpression
     *     | simpleIdentifier
     *     | literalConstant
     *     | stringLiteral
     *     | callableReference
     *     | functionLiteral
     *     | objectLiteral
     *     | collectionLiteral
     *     | thisExpression
     *     | superExpression
     *     | ifExpression
     *     | whenExpression
     *     | tryExpression
     *     | jumpExpression
     */
    fun primaryExpression(label: LabelNode? = null): ASTNode {
        val currentToken = currentToken
        when (currentToken.type) {
            TokenType.Operator -> {
                if (currentToken.value == "(") {
                    return parenthesizedExpression()
                }
            }
            TokenType.Integer -> {
                eat(TokenType.Integer)
                return IntegerNode(currentToken.position, currentToken.value as Int)
            }
            TokenType.Long -> {
                eat(TokenType.Long)
                return LongNode(currentToken.position, currentToken.value as Long)
            }
            TokenType.Double -> {
                eat(TokenType.Double)
                return DoubleNode(currentToken.position, currentToken.value as Double)
            }
            TokenType.Char -> {
                eat(TokenType.Char)
                return CharNode(currentToken.position, currentToken.value as Char)
            }
            TokenType.Identifier -> {
                when (currentToken.value) {
                    "throw", "return", "break", "continue" -> return jumpExpression()
                    "if" -> return ifExpression()
                    "when" -> return whenExpression()
                    "try" -> return tryExpression()

                    // literal
                    "true" -> { eat(TokenType.Identifier); return BooleanNode(currentToken.position, true) }
                    "false" -> { eat(TokenType.Identifier); return BooleanNode(currentToken.position, false) }
                    "null" -> { eat(TokenType.Identifier); return NullNode }
                }

                val t = eat(TokenType.Identifier)
                return VariableReferenceNode(t.position, t.value as String)
            }
            TokenType.Symbol -> {
                when (currentToken.value) {
                    "\"", "\"\"\"" -> return stringLiteral()
                    "{" -> return functionLiteral(label = label)
                }
            }
            // TODO other token types
            else -> Unit
        }
        throw UnexpectedTokenException(currentToken)
    }

    /**
     * disjunction:
     *     conjunction {{NL} '||' {NL} conjunction}
     */
    fun disjunction(): ASTNode {
        var n = conjunction()
        while (isCurrentTokenExcludingNL(TokenType.Operator, "||")) {
            repeatedNL()
            val t = eat(TokenType.Operator, "||")
            repeatedNL()
            val n2 = conjunction()
            n = BinaryOpNode(position = t.position, node1 = n, node2 = n2, operator = "||")
        }
        return n
    }

    /**
     * conjunction:
     *     equality {{NL} '&&' {NL} equality}
     */
    fun conjunction(): ASTNode {
        var n = equality()
        while (isCurrentTokenExcludingNL(TokenType.Operator, "&&")) {
            repeatedNL()
            val t = eat(TokenType.Operator, "&&")
            repeatedNL()
            val n2 = equality()
            n = BinaryOpNode(position = t.position, node1 = n, node2 = n2, operator = "&&")
        }
        return n
    }

    /**
     * equality:
     *     comparison {equalityOperator {NL} comparison}
     */
    fun equality(): ASTNode {
        var n = comparison()
        while (currentToken.type == TokenType.Operator && currentToken.value in setOf("!=", "!==", "==", "===")) {
            val t = eat(TokenType.Operator)
            repeatedNL()
            val n2 = comparison()
            n = BinaryOpNode(position = t.position, node1 = n, node2 = n2, operator = t.value as String)
        }
        return n
    }

    /**
     * comparison:
     *     genericCallLikeComparison {comparisonOperator {NL} genericCallLikeComparison}
     *
     */
    fun comparison(): ASTNode {
        var n = infixOperation()
        while (currentToken.type == TokenType.Operator && currentToken.value in setOf("<", ">", "<=", ">=")) {
            val t = eat(TokenType.Operator)
            repeatedNL()
            val n2 = infixOperation()
            n = BinaryOpNode(position = t.position, node1 = n, node2 = n2, operator = t.value as String)
        }
        return n
    }

    /**
     * infixOperation:
     *     elvisExpression {(inOperator {NL} elvisExpression) | (isOperator {NL} type)}
     *
     * isOperator:
     *     'is'
     *     | NOT_IS
     *
     * NOT_IS:
     *     '!is' (Hidden | NL)
     *
     */
    fun infixOperation(): ASTNode {
        var n = elvisExpression()
        while (
            (currentToken.type == TokenType.Identifier && currentToken.value in setOf("is"))
            || (currentToken.`is`(TokenType.Operator, "!") && peekNextToken().`is`(TokenType.Identifier, "is"))
            || (currentToken.type == TokenType.Identifier && currentToken.value in setOf("in"))
            || (currentToken.`is`(TokenType.Operator, "!") && peekNextToken().`is`(TokenType.Identifier, "in"))
        ) {
            val token = currentToken
            val operatorName = if (currentToken.type == TokenType.Identifier) {
                eat(TokenType.Identifier).value as String
            } else {
                eat(TokenType.Operator, "!")
                "!${eat(TokenType.Identifier).value}"
            }
            repeatedNL()
            val n2 = if (operatorName.contains("is")) {
                type(isParseDottedIdentifiers = true, isIncludeLastIdentifierAsTypeName = true)
            } else {
                elvisExpression()
            }
            n = InfixFunctionCallNode(position = token.position, node1 = n, node2 = n2, functionName = operatorName)
        }
        return n
    }

    /**
     * elvisExpression:
     *     infixFunctionCall {{NL} elvis {NL} infixFunctionCall}
     *
     * elvis:
     *     QUEST_NO_WS ':'
     */
    fun elvisExpression(): ASTNode {
        var n = infixFunctionCall()
        while (isCurrentTokenExcludingNL(TokenType.Operator, "?:")) {
            repeatedNL()
            val t = eat(TokenType.Operator, "?:")
            repeatedNL()
            val n2 = infixFunctionCall()
            n = ElvisOpNode(position = t.position, primaryNode = n, fallbackNode = n2)
        }
        return n
    }

    /**
     * infixFunctionCall:
     *     rangeExpression {simpleIdentifier {NL} rangeExpression}
     */
    fun infixFunctionCall(): ASTNode {
        var n = rangeExpression()
        while (currentToken.type == TokenType.Identifier && currentToken.value !in setOf("else", "is", "!is", "in", "!in", "val", "var", "fun", "class", "for", "while", "do")) {
            val t = eat(TokenType.Identifier)
            repeatedNL()
            val n2 = rangeExpression()
            n = InfixFunctionCallNode(position = t.position, node1 = n, node2 = n2, functionName = t.value as String)
        }
        return n
    }

    /**
     * rangeExpression:
     *     additiveExpression {('..' | '..<') {NL} additiveExpression}
     *
     */
    fun rangeExpression(): ASTNode {
        var n = additiveExpression()
        while (currentToken.type == TokenType.Operator && currentToken.value in setOf("..", "..<")) {
            val t = eat(TokenType.Operator)
            repeatedNL()
            val n2 = additiveExpression()
            n = BinaryOpNode(position = t.position, node1 = n, node2 = n2, operator = t.value as String)
        }
        return n
    }

    /**
     *
     * additiveExpression:
     *     multiplicativeExpression {additiveOperator {NL} multiplicativeExpression}
     *
     */
    fun additiveExpression(): ASTNode {
        var node = multiplicativeExpression() // TODO fix order
        while (currentToken.type == TokenType.Operator && currentToken.value in setOf("+", "-")) {
            val t = currentToken
            eat(TokenType.Operator)
            node = BinaryOpNode(t.position, node, multiplicativeExpression(), t.value.toString())
        }
        return node
    }

    /**
     *
     *
     * multiplicativeExpression:
     *     asExpression {multiplicativeOperator {NL} asExpression}
     *
     *
     */
    fun multiplicativeExpression(): ASTNode {
        var node = asExpression() // TODO fix order
        while (currentToken.type == TokenType.Operator && currentToken.value in setOf("*", "/", "%")) {
            val t = currentToken
            eat(TokenType.Operator)
            node = BinaryOpNode(t.position, node, asExpression(), t.value.toString())
        }
        return node
    }

    /**
     *
     *
     * asExpression:
     *     prefixUnaryExpression {{NL} asOperator {NL} type}
     *
     *
     */
    fun asExpression(): ASTNode {
        fun isCurrentTokenExcludingNLAnAsOperator(): Boolean {
            val token = currentTokenExcludingNL()
            return token.`is`(TokenType.Identifier, "as") || token.`is`(TokenType.Identifier, "as?")
        }

        var node = prefixUnaryExpression()
        while (isCurrentTokenExcludingNLAnAsOperator()) {
            repeatedNL()
            val t = currentToken
            val isNullable = if (isCurrentToken(TokenType.Identifier, "as?")) {
                eat(TokenType.Identifier, "as?")
                true
            } else {
                eat(TokenType.Identifier, "as")
                false
            }
            repeatedNL()
            val type = type()
            node = AsOpNode(position = t.position, isNullable = isNullable, expression = node, type = type)
        }
        return node
    }

    /**
     * expression:
     *     disjunction
     */
    fun expression(): ASTNode {
        return disjunction()
    }

    /**
     * jumpExpression:
     *     ('throw' {NL} expression)
     *     | (('return' | RETURN_AT) [expression])
     *     | 'continue'
     *     | CONTINUE_AT
     *     | 'break'
     *     | BREAK_AT
     */
    fun jumpExpression(): ASTNode {
        val t = eat(TokenType.Identifier)
        when (t.value) {
            "throw" -> {
                repeatedNL()
                val expr = expression()
                return ThrowNode(position = t.position, value = expr)
            }
            "return" -> {
                var label = if (currentToken.`is`(TokenType.Symbol, "@")
                    && peekNextToken().type == TokenType.Identifier
                    && areTokensConsecutive(t, currentToken, peekNextToken())
                ) {
                    eat(TokenType.Symbol, "@")
                    eat(TokenType.Identifier).value as String
                } else ""
                val expr = if (!isSemi()) {
                    expression()
                } else null
                return ReturnNode(position = t.position, value = expr, returnToLabel = label, returnToAddress = "")
            }
            "break" -> return BreakNode(t.position, "", "")
            "continue" -> return ContinueNode(t.position, "", "")
        }
        TODO(t.value.toString())
    }

    /**
     * block:
     *     '{'
     *     {NL}
     *     statements
     *     {NL}
     *     '}'
     */
    fun block(type: ScopeType): BlockNode {
        val position = currentToken.position
        eat(TokenType.Symbol, "{")
        repeatedNL()
        val statements = statements()
        repeatedNL()
        eat(TokenType.Symbol, "}")
        return BlockNode(statements, position, type, FunctionBodyFormat.Block)
    }

    /**
     * controlStructureBody
     * (used by forStatement, whileStatement, doWhileStatement, ifExpression, whenEntry)
     *   : block
     *   | statement
     *   ;
     */
    fun controlStructureBody(type: ScopeType): BlockNode {
        return if (isCurrentToken(TokenType.Symbol, "{")) {
            block(type)
        } else {
            val position = currentToken.position
            BlockNode(listOf(statement()), position, type, FunctionBodyFormat.Statement)
        }
    }

    /**
     * typeArguments:
     *     '<'
     *     {NL}
     *     typeProjection
     *     {{NL} ',' {NL} typeProjection}
     *     [{NL} ',']
     *     {NL}
     *     '>'
     *
     * typeProjection:
     *     ([typeProjectionModifiers] type)
     *     | '*'
     */
    fun typeArguments(): List {
        val arguments = mutableListOf()
        eat(TokenType.Operator, "<")
        repeatedNL()
        arguments += type()
        repeatedNL()
        while (!isCurrentToken(TokenType.Operator, ">")) {
            eat(TokenType.Symbol, ",")
            repeatedNL()
            arguments += type()
            repeatedNL()
        }
        if (isCurrentToken(TokenType.Symbol, ",")) {
            eat(TokenType.Symbol, ",")
            repeatedNL()
        }
        eat(TokenType.Operator, ">")
        return arguments
    }

    /**
     * typeParameter:
     *     [typeParameterModifiers] {NL} simpleIdentifier [{NL} ':' {NL} type]
     *
     */
    fun typeParameter(): TypeParameterNode {
        repeatedNL()
        val t = currentToken
        val name = userDefinedIdentifier()
        val typeUpperBound = if (isCurrentTokenExcludingNL(TokenType.Symbol, ":")) {
            repeatedNL()
            eat(TokenType.Symbol, ":")
            repeatedNL()
            type()
        } else null
        return TypeParameterNode(position = t.position, name = name, typeUpperBound = typeUpperBound)
    }

    /**
     * typeParameters:
     *     '<'
     *     {NL}
     *     typeParameter
     *     {{NL} ',' {NL} typeParameter}
     *     [{NL} ',']
     *     {NL}
     *     '>'
     *
     */
    fun typeParameters(): List {
        val parameters = mutableListOf()
        eat(TokenType.Operator, "<")
        repeatedNL()
        parameters += typeParameter()
        repeatedNL()
        while (!isCurrentToken(TokenType.Operator, ">")) {
            eat(TokenType.Symbol, ",")
            repeatedNL()
            parameters += typeParameter()
            repeatedNL()
        }
        if (isCurrentToken(TokenType.Symbol, ",")) {
            eat(TokenType.Symbol, ",")
            repeatedNL()
        }
        eat(TokenType.Operator, ">")
        return parameters
    }

    /**
     * nullableType:
     *     (typeReference | parenthesizedType) {NL} (quest {quest})
     *
     * quest:
     *     QUEST_NO_WS
     *     | QUEST_WS
     *
     * typeReference:
     *     userType
     *     | 'dynamic'
     *
     * userType:
     *     simpleUserType {{NL} '.' {NL} simpleUserType}
     *
     * simpleUserType:
     *     simpleIdentifier [{NL} typeArguments]
     *
     */
    fun typeReference(isParseDottedIdentifiers: Boolean = false, isIncludeLastIdentifierAsTypeName: Boolean = false): TypeNode {
        if (isCurrentToken(TokenType.Operator, "*")) {
            val t = eat(TokenType.Operator, "*")
            return TypeNode(t.position, "*", null, false)
        }

        var cursorPosBeforeLastDot: Int? = null
        val nameB = StringBuilder()
        val t = eat(TokenType.Identifier)
        nameB.append(t.value as String)
        while (isParseDottedIdentifiers && isCurrentTokenExcludingNL(TokenType.Operator, ".")) {
            cursorPosBeforeLastDot = tokenIndex
            repeatedNL()
            eat(TokenType.Operator, ".")
            nameB.append(".")
            repeatedNL()
            if (currentToken.type == TokenType.Identifier) {
                nameB.append(eat(TokenType.Identifier).value as String)
            } else {
                resetTokenToIndex(cursorPosBeforeLastDot)
                break
            }
        }
        val name = nameB.toString()
        val argument = if (isCurrentTokenExcludingNL(TokenType.Operator, "<")) {
            typeArguments()
        } else null
        val isNullable = if (isCurrentTokenExcludingNL(TokenType.Symbol, "?")) {
            repeatedNL()
            eat(TokenType.Symbol, "?")
            true
        } else false
        if (!isIncludeLastIdentifierAsTypeName && argument == null && !isNullable && cursorPosBeforeLastDot != null) {
            resetTokenToIndex(cursorPosBeforeLastDot)
            return TypeNode(t.position, name.substringBeforeLast("."), argument, isNullable)
        }
        return TypeNode(t.position, name, argument, isNullable)
    }

    /**
     * functionType:
     *     [receiverType {NL} '.' {NL}]
     *     functionTypeParameters
     *     {NL}
     *     '->'
     *     {NL}
     *     type
     *
     * functionTypeParameters:
     *     '('
     *     {NL}
     *     [parameter | type]
     *     {{NL} ',' {NL} (parameter | type)}
     *     [{NL} ',']
     *     {NL}
     *     ')'
     *
     */
    fun functionType(): FunctionTypeNode {
        val typeParameters = mutableListOf()
        val receiverType = if (!currentToken.`is`(TokenType.Operator, "(")) {
            receiverTypeOrIdentifier().first
        } else null
        val t = eat(TokenType.Operator, "(")
        repeatedNL()
        var hasEatenComma = false
        while (!isCurrentTokenExcludingNL(TokenType.Operator, ")")) {
            if (typeParameters.isNotEmpty() && !hasEatenComma) {
                throw ExpectTokenMismatchException(",", currentToken.position)
            }

            val originalTokenIndex = tokenIndex
            try {
                eat(TokenType.Identifier)
                repeatedNL()
                eat(TokenType.Symbol, ":")
                repeatedNL()
            } catch (_: ParseException) {
                resetTokenToIndex(originalTokenIndex)
            }

            typeParameters += type()
            repeatedNL()
            if (isCurrentToken(TokenType.Symbol, ",")) {
                eat(TokenType.Symbol, ",")
                repeatedNL()
                hasEatenComma = true
            }
        }
        eat(TokenType.Operator, ")")

        repeatedNL()
        eat(TokenType.Symbol, "->")
        repeatedNL()
        val returnType = type()

        return FunctionTypeNode(
            position = t.position,
            receiverType = receiverType,
            parameterTypes = typeParameters,
            returnType = returnType,
            isNullable = false,
        )
    }

    /**
     * parenthesizedType:
     *     '('
     *     {NL}
     *     type
     *     {NL}
     *     ')'
     *
     * nullableType:
     *     (typeReference | parenthesizedType) {NL} (quest {quest})
     *
     */
    fun nullableParenthesizedType(): TypeNode {
        eat(TokenType.Operator, "(")
        repeatedNL()
        val type = type(isTryParenthesizedType = false)
        repeatedNL()
        eat(TokenType.Operator, ")")
        val isNullable = if (isCurrentTokenExcludingNL(TokenType.Symbol, "?")) {
            repeatedNL()
            eat(TokenType.Symbol, "?")
            true
        } else false
        return type.copy(isNullable)
    }

    /**
     * type:
     *     [typeModifiers] (functionType | parenthesizedType | nullableType | typeReference | definitelyNonNullableType)
     *
     */
    fun type(isTryParenthesizedType: Boolean = true, isParseDottedIdentifiers: Boolean = false, isIncludeLastIdentifierAsTypeName: Boolean = false): TypeNode {
        val originalTokenIndex = tokenIndex
        val lastException: Throwable?
        try {
            return functionType()
        } catch (e: ParseException) {
            resetTokenToIndex(originalTokenIndex)
            lastException = e
        }
        return when {
            isCurrentToken(TokenType.Operator, "(") ->
                if (isTryParenthesizedType) {
                    nullableParenthesizedType()
                } else {
                    throw lastException!!
                }
            else -> typeReference(isParseDottedIdentifiers = isParseDottedIdentifiers, isIncludeLastIdentifierAsTypeName = isIncludeLastIdentifierAsTypeName)
        }
    }

    /**
     * variableDeclaration:
     *     {annotation} {NL} simpleIdentifier [{NL} ':' {NL} type]
     *
     */
    fun variableDeclaration(): Pair {
        val name = userDefinedIdentifier()
        val type = if (isCurrentTokenExcludingNL(TokenType.Symbol, ":")) {
            repeatedNL()
            eat(TokenType.Symbol, ":")
            repeatedNL()
            type()
        } else null
        return name to type
    }

    fun Set.toPropertyModifiers() = this.map {
        when (it) {
            "open" -> PropertyModifier.open
            "override" -> PropertyModifier.override
            else -> throw ParseException("Modifier `$it` cannot be applied to properties")
        }
    }.toSet()

    /**
     * propertyDeclaration:
     *     [modifiers]
     *     ('val' | 'var')
     *     [{NL} typeParameters]
     *     [{NL} receiverType {NL} '.']
     *     ({NL} (multiVariableDeclaration | variableDeclaration))
     *     [{NL} typeConstraints]
     *     [{NL} (('=' {NL} expression) | propertyDelegate)]
     *     [{NL} ';']
     *     {NL}
     *     (([getter] [{NL} [semi] setter]) | ([setter] [{NL} [semi] getter]))
     *
     *
     */
    fun propertyDeclaration(modifiers: Set, isProcessBody: Boolean = true): PropertyDeclarationNode {
        val modifiers = modifiers.toPropertyModifiers()
        val t = currentToken
        val isMutable = eat(TokenType.Identifier).let {
            when (it.value) {
                "val" -> false
                "var" -> true
                else -> throw UnexpectedTokenException(it)
            }
        }
        repeatedNL()
        val typeParameters = if (currentToken.type == TokenType.Operator && currentToken.value == "<") {
            typeParameters()
        } else emptyList()
        val (receiver, name) = receiverTypeAndIdentifier()
        val type = if (isCurrentTokenExcludingNL(TokenType.Symbol, ":")) {
            repeatedNL()
            eat(TokenType.Symbol, ":")
            repeatedNL()
            type()
        } else null

        val initialValue = if (currentToken.type == TokenType.Symbol && currentToken.value == "=") {
            eat(TokenType.Symbol, "=")
            expression()
        } else {
            null
        }
//        repeatedNL() // this would cause the NL before the next statement not recognized

        fun nextNonNLSemiToken(): Token {
            val originalTokenIndex = tokenIndex
            var token = currentTokenExcludingNL(isResetIndex = false)
            if (token.type == TokenType.Semicolon) {
                token = readToken()
            }
            resetTokenToIndex(originalTokenIndex)
            return token
        }

        val nextToken = currentTokenExcludingNL()
        val accessors = when (nextToken.value.takeIf { initialValue == null && nextToken.type == TokenType.Identifier }) {
            "get" -> {
                repeatedNL()
                if (type == null) { // TODO make type infer possible
                    throw RuntimeException("Type is needed if there is custom accessor")
                }
                val getter = getter(type, isProcessBody)
                val next = nextNonNLSemiToken()
                val setter = if (next.type == TokenType.Identifier && next.value == "set") {
                    repeatedNL()
                    if (isSemi()) semi()
                    setter(type, isProcessBody)
                } else null
                PropertyAccessorsNode(nextToken.position, type, getter, setter)
            }
            "set" -> {
                repeatedNL()
                if (type == null) { // TODO make type infer possible
                    throw RuntimeException("Type is needed if there is custom accessor")
                }
                val setter = setter(type, isProcessBody)
                val next = nextNonNLSemiToken()
                val getter = if (next.type == TokenType.Identifier && next.value == "get") {
                    repeatedNL()
                    if (isSemi()) semi()
                    getter(type, isProcessBody)
                } else null
                PropertyAccessorsNode(nextToken.position, type, getter, setter)
            }
            else -> null
        }
        return PropertyDeclarationNode(position = t.position, name = name, declaredModifiers = modifiers, typeParameters = typeParameters, receiver = receiver, declaredType = type, isMutable = isMutable, initialValue = initialValue, accessors = accessors)
    }

    /**
     * getter:
     *     [modifiers] 'get' [{NL} '(' {NL} ')' [{NL} ':' {NL} type] {NL} functionBody]
     *
     */
    fun getter(type: TypeNode, isProcessBody: Boolean = true): FunctionDeclarationNode {
        val t = eat(TokenType.Identifier, "get")
        repeatedNL()
        eat(TokenType.Operator, "(")
        repeatedNL()
        eat(TokenType.Operator, ")")
        repeatedNL()
        val body = if (isProcessBody) functionBody() else dummyBlockNode()
        return FunctionDeclarationNode(position = t.position, name = "get", declaredReturnType = type, valueParameters = emptyList(), body = body)
    }

    /**
     * setter:
     *     [modifiers] 'set' [{NL} '(' {NL} functionValueParameterWithOptionalType [{NL} ','] {NL} ')' [{NL} ':' {NL} type] {NL} functionBody]
     *
     * functionValueParameterWithOptionalType:
     *     [parameterModifiers] parameterWithOptionalType [{NL} '=' {NL} expression]
     *
     * parameterWithOptionalType:
     *     simpleIdentifier {NL} [':' {NL} type]
     *
     */
    fun setter(type: TypeNode, isProcessBody: Boolean = true): FunctionDeclarationNode {
        val t = eat(TokenType.Identifier, "set")
        repeatedNL()
        eat(TokenType.Operator, "(")
        repeatedNL()
        val parameterName = userDefinedIdentifier()
        repeatedNL()
        eat(TokenType.Operator, ")")
        repeatedNL()
        val returnType = if (false /* Kotlin only permits Unit as return type. No point to support this syntax */
                && isCurrentToken(TokenType.Symbol, ":")) {
            eat(TokenType.Symbol, ":")
            repeatedNL()
            type().also { repeatedNL() }
        } else {
            TypeNode(t.position, "Unit", null, false)
        }
        val body = if (isProcessBody) functionBody() else dummyBlockNode()
        return FunctionDeclarationNode(
            position = t.position,
            name = "set",
            declaredReturnType = returnType,
            valueParameters = listOf(
                FunctionValueParameterNode(t.position, parameterName, type, null, emptySet())
            ),
            body = body
        )
    }

    /**
     * functionValueParameters:
     *     '('
     *     {NL}
     *     [functionValueParameter {{NL} ',' {NL} functionValueParameter} [{NL} ',']]
     *     {NL}
     *     ')'
     *
     *
     */
    fun functionValueParameters(): List {
        val parameters = mutableListOf()
        var isLastTokenComma = false
        eat(TokenType.Operator, "(")
        repeatedNL()
        while (currentToken.type == TokenType.Identifier) {
            if (parameters.isNotEmpty()) {
                if (!isLastTokenComma) {
                    throw UnexpectedTokenException(currentToken)
                }
            }
            parameters += functionValueParameter()
            isLastTokenComma = false
            repeatedNL()
            if (currentToken.type == TokenType.Symbol && currentToken.value == ",") {
                eat(TokenType.Symbol, ",")
                repeatedNL()
                isLastTokenComma = true
            }
        }
        eat(TokenType.Operator, ")")
        return parameters
    }

    /**
     * parameter:
     *     simpleIdentifier
     *     {NL}
     *     ':'
     *     {NL}
     *     type
     *
     */
    fun parameter(): Pair {
        val name = userDefinedIdentifier()
        repeatedNL()
        eat(TokenType.Symbol, ":")
        repeatedNL()
        val type = type()
        return name to type
    }

    fun Set.toFunctionValueParameterModifiers() = this.map {
        when (it) {
            "vararg" -> FunctionValueParameterModifier.vararg
            else -> throw ParseException("Modifier `$it` cannot be applied to function value parameters")
        }
    }.toSet()

    /**
     * functionValueParameter:
     *     [parameterModifiers] parameter [{NL} '=' {NL} expression]
     *
     * parameterModifiers:
     *     annotation | parameterModifier {annotation | parameterModifier}
     *
     * parameterModifier:
     *     'vararg'
     *     | 'noinline'
     *     | 'crossinline'
     *
     */
    fun functionValueParameter(): FunctionValueParameterNode {
        val t = currentToken
        val modifiers = modifiers().toFunctionValueParameterModifiers()
        val (name, type) = parameter()
        repeatedNL()
        val defaultValue = if (currentToken.type == TokenType.Symbol && currentToken.value == "=") {
            eat(TokenType.Symbol, "=")
            repeatedNL()
            expression()
        } else null
        return FunctionValueParameterNode(position = t.position, name = name, declaredType = type, defaultValue = defaultValue, modifiers = modifiers)
    }

    /**
     * functionBody:
     *     block
     *     | ('=' {NL} expression)
     */
    fun functionBody(): BlockNode {
        val position = currentToken.position
        if (currentToken.type == TokenType.Symbol && currentToken.value == "=") {
            eat(TokenType.Symbol, "=")
            repeatedNL()
            return BlockNode(listOf(expression()), position, ScopeType.Function, FunctionBodyFormat.Expression)
        } else {
            return block(ScopeType.Function)
        }
    }

    /**
     * receiverType:
     *     [typeModifiers] (parenthesizedType | nullableType | typeReference)
     *
     * nullableType:
     *     (typeReference | parenthesizedType) {NL} (quest {quest})
     *
     * typeReference:
     *     userType
     *     | 'dynamic'
     */
    fun receiverTypeOrIdentifier(): Pair {
        // TODO revisit when package is supported
//        val identifiers = mutableListOf()
//        var hasEatenQuestionMark = false
//        var numOfDotsAfterQuestionMark = 0
//        do {
//            if (hasEatenQuestionMark) {
//                ++numOfDotsAfterQuestionMark
//                if (numOfDotsAfterQuestionMark > 1) {
//                    throw ExpectTokenMismatchException("(", currentToken.position)
//                }
//            }
//            if (identifiers.isNotEmpty()) {
//                if (isCurrentToken(TokenType.Operator, "?.")) {
//                    eat(TokenType.Operator, "?.")
//                } else {
//                    eat(TokenType.Operator, ".")
//                }
//            }
//            var identifier = userDefinedIdentifier()
//            repeatedNL()
//            if (isCurrentToken(TokenType.Operator, "?.")) {
//                identifier += "?"
//                hasEatenQuestionMark = true
//            }
//            identifiers += identifier
//        } while (isCurrentToken(TokenType.Operator, ".") || isCurrentToken(TokenType.Operator, "?."))
//        val name = identifiers.removeLast()
//        val receiver = identifiers.takeIf { it.isNotEmpty() }?.joinToString(".")
//        return receiver to name

        val type = if (isCurrentToken(TokenType.Operator, "(")) {
            nullableParenthesizedType()
        } else {
            typeReference(isParseDottedIdentifiers = true)
        }
        val isNullable = if (isCurrentToken(TokenType.Operator, "?.")) {
            eat(TokenType.Operator, "?.")
            true
        } else if (isCurrentToken(TokenType.Operator, ".")) {
            eat(TokenType.Operator, ".")
            false
        } else { // no receiver
            if (!type.arguments.isNullOrEmpty()) {
                throw ParseException("Unexpected token '<'")
            }
            if (type.isNullable) {
                throw ParseException("Unexpected token '?'")
            }
            return null to type.name
        }
        return type.copy(isNullable = isNullable) to null
    }

    fun receiverTypeAndIdentifier(): Pair {
        val (receiverType, identifier) = receiverTypeOrIdentifier()
        if (receiverType == null) {
            return null to identifier!!
        }
        val name = userDefinedIdentifier()
        return receiverType to name
    }

    fun Set.toFunctionModifiers() = this.map {
        when (it) {
            "operator" -> FunctionModifier.operator
            "open" -> FunctionModifier.open
            "override" -> FunctionModifier.override
            "abstract" -> FunctionModifier.abstract
            "infix" -> FunctionModifier.infix
            "nullaware" -> FunctionModifier.nullaware
            else -> throw ParseException("Modifier `$it` cannot be applied to function")
        }
    }.toSet()

    /**
     *
     * functionDeclaration:
     *     [modifiers]
     *     'fun'
     *     [{NL} typeParameters]
     *     [{NL} receiverType {NL} '.']
     *     {NL}
     *     simpleIdentifier
     *     {NL}
     *     functionValueParameters
     *     [{NL} ':' {NL} type]
     *     [{NL} typeConstraints]
     *     [{NL} functionBody]
     *
     */
    fun functionDeclaration(modifiers: Set, isProcessBody: Boolean = true): FunctionDeclarationNode {
        val modifiers = modifiers.toFunctionModifiers()
        val t = eat(TokenType.Identifier, "fun")
        repeatedNL()
        val typeParameters = if (currentToken.type == TokenType.Operator && currentToken.value == "<") {
            typeParameters()
        } else emptyList()
        val (receiver, name) = receiverTypeAndIdentifier()
        val valueParameters = functionValueParameters()
        repeatedNL()
        val type = if (isCurrentToken(type = TokenType.Symbol, value = ":")) {
            eat(TokenType.Symbol, ":")
            repeatedNL()
            val type = type()
            repeatedNL()
            type
        } else {
            null
        }
        val body = if (!isProcessBody || FunctionModifier.abstract in modifiers) {
            null
        } else {
            functionBody()
        }
        return FunctionDeclarationNode(
            position = t.position,
            name = name,
            receiver = receiver,
            declaredReturnType = type ?: TypeNode(t.position, "Unit", null, false).takeIf { body == null || body.format == FunctionBodyFormat.Block },
            valueParameters = valueParameters,
            body = body,
            typeParameters = typeParameters,
            declaredModifiers = modifiers,
        )
    }

    fun dummyBlockNode() = BlockNode(emptyList(), SourcePosition("", 1, 1), ScopeType.Function, FunctionBodyFormat.Block)

    fun Set.toClassParameterModifiers(): List = this.map {
        when (it) {
            "vararg" -> /*FunctionValueParameterModifier.vararg*/ throw UnsupportedOperationException("vararg in class primary constructor is not supported")
            "open" -> PropertyModifier.open
            "override" -> PropertyModifier.override
            else -> throw ParseException("Modifier `$it` cannot be applied to class parameter")
        }
    }

    /**
     * classParameter:
     *     [modifiers]
     *     ['val' | 'var']
     *     {NL}
     *     simpleIdentifier
     *     ':'
     *     {NL}
     *     type
     *     [{NL} '=' {NL} expression]
     */
    fun classParameter(): ClassParameterNode {
        val t = currentToken
        val modifiers = modifiers().toClassParameterModifiers()
        val isMutable = if (currentToken.type == TokenType.Identifier && currentToken.value in setOf("val", "var")) {
            (currentToken.value == "var").also { eat(TokenType.Identifier) }
        } else null
        repeatedNL()
        val name = userDefinedIdentifier()
        eat(TokenType.Symbol, ":")
        repeatedNL()
        val type = type()
        val defaultValue = if (isCurrentTokenExcludingNL(TokenType.Symbol, "=")) {
            repeatedNL()
            eat(TokenType.Symbol, "=")
            repeatedNL()
            expression()
        } else null
        return ClassParameterNode(
            position = t.position,
            isProperty = isMutable != null,
            isMutable = isMutable == true,
            modifiers = modifiers.filterIsInstance().toSet(),
            parameter = FunctionValueParameterNode(
                position = t.position,
                name = name,
                declaredType = type,
                defaultValue = defaultValue,
                modifiers = modifiers.filterIsInstance().toSet(),
            )
        )
    }

    /**
     * primaryConstructor:
     *     [[modifiers] 'constructor' {NL}] classParameters
     *
     * classParameters:
     *     '('
     *     {NL}
     *     [classParameter {{NL} ',' {NL} classParameter} [{NL} ',']]
     *     {NL}
     *     ')'
     */
    fun primaryConstructor() : ClassPrimaryConstructorNode {
        val t = currentToken
        val parameters = mutableListOf()
        if (isCurrentToken(TokenType.Identifier, "constructor")) {
            eat(TokenType.Identifier, "constructor")
            repeatedNL()
        }
        eat(TokenType.Operator, "(")
        repeatedNL()
        var hasEatenComma = false
        while (!isCurrentTokenExcludingNL(TokenType.Operator, ")")) {
            if (parameters.isNotEmpty()) {
                if (!hasEatenComma) {
                    throw ExpectTokenMismatchException(",", currentToken.position)
                }
            }
            parameters += classParameter()
            repeatedNL()
            hasEatenComma = false
            if (isCurrentToken(TokenType.Symbol, ",")) {
                eat(TokenType.Symbol, ",")
                repeatedNL()
                hasEatenComma = true
            }
        }
        repeatedNL()
        eat(TokenType.Operator, ")")
        return ClassPrimaryConstructorNode(position = t.position, parameters = parameters)
    }

    /**
     * classMemberDeclarations:
     *     {classMemberDeclaration [semis]}
     *
     * classMemberDeclaration:
     *     declaration
     *     | companionObject
     *     | anonymousInitializer
     *     | secondaryConstructor
     *
     * anonymousInitializer:
     *     'init' {NL} block
     */
    fun classMemberDeclarations(isInterface: Boolean): List {
        val declarations = mutableListOf()
        while (!isCurrentTokenExcludingNL(TokenType.Symbol, "}")) {
            declarations += if (isCurrentToken(TokenType.Identifier, "init")) {
                val t = eat(TokenType.Identifier, "init")
                repeatedNL()
                val block = block(ScopeType.Initializer)
                ClassInstanceInitializerNode(position = t.position, block = block)
            } else {
                declaration(isInterface = isInterface)
            }

            if (isSemi()) {
                semis()
            }
        }
        return declarations
    }

    /**
     * classBody:
     *     '{'
     *     {NL}
     *     classMemberDeclarations
     *     {NL}
     *     '}'
     */
    fun classBody(isInterface: Boolean): List {
        eat(TokenType.Symbol, "{")
        repeatedNL()
        val declarations = classMemberDeclarations(isInterface = isInterface)
        repeatedNL()
        eat(TokenType.Symbol, "}")
        return declarations
    }

    /**
     * delegationSpecifiers:
     *     annotatedDelegationSpecifier {{NL} ',' {NL} annotatedDelegationSpecifier}
     *
     * annotatedDelegationSpecifier:
     *     {annotation} {NL} delegationSpecifier
     *
     * delegationSpecifier:
     *     constructorInvocation
     *     | explicitDelegation
     *     | userType
     *     | functionType
     *     | ('suspend' {NL} functionType)
     *
     */
    fun delegationSpecifiers(): List {
        return buildList {
            add(constructorInvocationOrUserType())
            while (isCurrentTokenExcludingNL(TokenType.Symbol, ",")) {
                repeatedNL()
                eat(TokenType.Symbol, ",")
                repeatedNL()
                add(constructorInvocationOrUserType())
            }
        }
    }
    /**
     * constructorInvocation:
     *     userType {NL} valueArguments
     *
     */
    fun constructorInvocationOrUserType(): ASTNode {
        val type = typeReference()
        if (!isCurrentTokenExcludingNL(TokenType.Operator, "(")) {
            return type
        }
        repeatedNL()
        val arguments = valueArguments()
        return FunctionCallNode(
            function = type,
            arguments = arguments,
            declaredTypeArguments = type.arguments ?: emptyList(),
            position = currentToken.position,
            isSuperclassConstruction = true,
        )
    }

    /**
     * modifiers:
     *     annotation | modifier {annotation | modifier}
     *
     * modifier:
     *     (classModifier | memberModifier | visibilityModifier | functionModifier | propertyModifier | inheritanceModifier | parameterModifier | platformModifier) {NL}
     *
     */
    fun modifiers(): Set {
        val modifiers = mutableSetOf()
        while (currentToken.type == TokenType.Identifier && currentToken.value in ACCEPTED_MODIFIERS) {
            modifiers += currentToken.value as String
            eat(TokenType.Identifier)
            repeatedNL()
        }
        return modifiers
    }

    fun Set.toClassModifiers() = this.map {
        when (it) {
            "open" -> ClassModifier.open
            "enum" -> ClassModifier.enum
            "abstract" -> ClassModifier.abstract
            else -> throw ParseException("Modifier `$it` cannot be applied to class")
        }
    }.toSet()

    /**
     * enumEntry:
     *     [modifiers {NL}] simpleIdentifier [{NL} valueArguments] [{NL} classBody]
     *
     */
    fun enumEntry(): EnumEntryNode {
        val t = currentToken
        val name = userDefinedIdentifier()
        val valueArguments = if (isCurrentTokenExcludingNL(TokenType.Operator, "(")) {
            repeatedNL()
            valueArguments()
        } else emptyList()
        return EnumEntryNode(position = t.position, name = name, arguments = valueArguments)
    }

    /**
     * enumClassBody:
     *     '{'
     *     {NL}
     *     [enumEntries]
     *     [{NL} ';' {NL} classMemberDeclarations]
     *     {NL}
     *     '}'
     *
     * enumEntries:
     *     enumEntry {{NL} ',' {NL} enumEntry} {NL} [',']
     *
     * @return Pair of enum entries and class member declarations
     */
    fun enumClassBody(): Pair, List> {
        eat(TokenType.Symbol, "{")
        repeatedNL()
        val enumEntries = buildList {
            while (!currentTokenExcludingNL().let {
                    it.`is`(TokenType.Symbol, ";") || it.`is`(TokenType.Symbol, "}")
                }) {
                var hasComma = false
                add(enumEntry())
                repeatedNL()
                hasComma = false
                if (isCurrentTokenExcludingNL(TokenType.Symbol, ",")) {
                    hasComma = true
                    eat(TokenType.Symbol, ",")
                    repeatedNL()
                }
            }
        }
        repeatedNL()
        eat(TokenType.Symbol, "}")
        return Pair(enumEntries, emptyList())
    }

    /**
     * classDeclaration:
     *     [modifiers]
     *     ('class' | (['fun' {NL}] 'interface'))
     *     {NL}
     *     simpleIdentifier
     *     [{NL} typeParameters]
     *     [{NL} primaryConstructor]
     *     [{NL} ':' {NL} delegationSpecifiers]
     *     [{NL} typeConstraints]
     *     [({NL} classBody) | ({NL} enumClassBody)]
     */
    fun classDeclaration(modifiers: Set): ClassDeclarationNode {
        val modifiers = modifiers.toClassModifiers()
        if (!currentToken.`is`(TokenType.Identifier, "class") && !currentToken.`is`(TokenType.Identifier, "interface")) {
            throw ExpectTokenMismatchException("\"class\" or \"interface\"", currentToken.position)
        }
        val t = eat(TokenType.Identifier)
        val isInterface = t.value == "interface"
        repeatedNL()
        val name = userDefinedIdentifier()
        var token = currentTokenExcludingNL()
        val typeParameters = if (token.`is`(TokenType.Operator, "<")) {
            repeatedNL()
            typeParameters().also { token = currentTokenExcludingNL() }
        } else emptyList()
        val primaryConstructor = if (
            (token.type == TokenType.Identifier && token.value == "constructor")
            || (token.type == TokenType.Operator && token.value == "(")
        ) {
            repeatedNL()
            primaryConstructor().also { token = currentTokenExcludingNL() }
        } else null
        val superInvocations = if (isCurrentTokenExcludingNL(TokenType.Symbol, ":")) {
            repeatedNL()
            eat(TokenType.Symbol, ":")
            repeatedNL()
            delegationSpecifiers()
        } else null
        var declarations: List = emptyList()
        var enumEntries: List = emptyList()
        if (isCurrentTokenExcludingNL(TokenType.Symbol, "{")) {
            repeatedNL()
            if (ClassModifier.enum in modifiers) {
                val (enumEntries_, declarations_) = enumClassBody()
                enumEntries = enumEntries_
                declarations = declarations_
            } else {
                declarations = classBody(isInterface = isInterface)
            }
        }
        return ClassDeclarationNode(
            position = t.position,
            name = name,
            isInterface = isInterface,
            declaredModifiers = modifiers,
            typeParameters = typeParameters,
            primaryConstructor = primaryConstructor,
            superInvocations = superInvocations,
            declarations = declarations,
            enumEntries = enumEntries,
        )
    }

    /**
     * declaration:
     *     classDeclaration
     *     | objectDeclaration
     *     | functionDeclaration
     *     | propertyDeclaration
     *     | typeAlias
     */
    fun declaration(isInterface: Boolean): ASTNode {
        if (currentToken.type != TokenType.Identifier) {
//            throw ParseException("Expected an identifier but missing")
            throw UnexpectedTokenException(currentToken)
        }
        var modifiers: Set? = null
        while (true) {
            when (currentToken.value as String) {
                "val", "var" -> return propertyDeclaration(modifiers ?: emptySet())
                "fun" -> return functionDeclaration(modifiers ?: emptySet(), isProcessBody = !isInterface)
                "class", "interface" -> return classDeclaration(modifiers ?: emptySet())
                in ACCEPTED_MODIFIERS -> {
                    if (modifiers == null) {
                        modifiers = modifiers()
                    } else {
                        throw UnexpectedTokenException(currentToken)
                    }
                }
                else -> throw UnexpectedTokenException(currentToken)
            }
        }
        throw UnexpectedTokenException(currentToken)
    }

    /**
     * assignment:
     *     ((directlyAssignableExpression '=') | (assignableExpression assignmentAndOperator)) {NL} expression
     *
     * assignmentAndOperator
     * (used by assignment)
     *   : '+='
     *   | '-='
     *   | '*='
     *   | '/='
     *   | '%='
     *   ;
     *
     */
    fun assignment(): ASTNode {
//        val name = userDefinedIdentifier()
        val subject = assignableExpression()
        val operator = eat(TokenType.Symbol).also {
            if (it.value !in setOf("=", "+=", "-=", "*=", "/=", "%=")) {
                throw UnexpectedTokenException(it)
            }
        }.value as String
        repeatedNL()
        val expr = expression()
        return AssignmentNode(subject = subject, operator = operator, value = expr)
    }

    /**
     * whileStatement:
     *     'while'
     *     {NL}
     *     '('
     *     expression
     *     ')'
     *     {NL}
     *     (controlStructureBody | ';')
     */
    fun whileStatement(): WhileNode {
        val t = eat(TokenType.Identifier, "while")
        repeatedNL()
        eat(TokenType.Operator, "(")
        repeatedNL()
        val condition = expression()
        repeatedNL()
        eat(TokenType.Operator, ")")
        repeatedNL()
        val loopBody = if (isCurrentToken(TokenType.Semicolon, ";")) {
            eat(TokenType.Semicolon, ";")
            null
        } else {
            controlStructureBody(ScopeType.While)
        }
        return WhileNode(position = t.position, condition = condition, body = loopBody)
    }

    /**
     * doWhileStatement:
     *     'do'
     *     {NL}
     *     [controlStructureBody]
     *     {NL}
     *     'while'
     *     {NL}
     *     '('
     *     expression
     *     ')'
     */
    fun doWhileStatement(): DoWhileNode {
        val t = eat(TokenType.Identifier, "do")
        repeatedNL()
        val body = controlStructureBody(ScopeType.DoWhile)
        repeatedNL()
        eat(TokenType.Identifier, "while")
        repeatedNL()
        eat(TokenType.Operator, "(")
        val condition = expression()
        eat(TokenType.Operator, ")")
        return DoWhileNode(position = t.position, condition = condition, body = body)
    }

    /**
     * forStatement:
     *     'for'
     *     {NL}
     *     '('
     *     {annotation}
     *     (variableDeclaration | multiVariableDeclaration)
     *     'in'
     *     expression
     *     ')'
     *     {NL}
     *     [controlStructureBody]
     */
    fun forStatement(): ForNode {
        val t = eat(TokenType.Identifier, "for")
        repeatedNL()
        eat(TokenType.Operator, "(")
        val varToken = currentToken
        val (varName, varType) = variableDeclaration() // TODO support multiVariableDeclaration
        eat(TokenType.Identifier, "in")
        val expr = expression()
        eat(TokenType.Operator, ")")
        repeatedNL()
        val loopBody = controlStructureBody(ScopeType.For) // intended to be mandatory. no point to allow infinite loops
        return ForNode(
            position = t.position,
            variables = listOf(ValueParameterDeclarationNode(varToken.position, varName, varType)),
            subject = expr,
            body = loopBody,
        )
    }

    /**
     * loopStatement:
     *     forStatement
     *     | whileStatement
     *     | doWhileStatement
     */
    fun loopStatement(): ASTNode {
        if (currentToken.type != TokenType.Identifier) throw UnexpectedTokenException(currentToken)
        return when (currentToken.value) {
            "for" -> forStatement()
            "while" -> whileStatement()
            "do" -> doWhileStatement()
            else -> TODO()
        }
    }

    /**
     * statement:
     *     {label | annotation} (declaration | assignment | loopStatement | expression)
     */
    fun statement(): ASTNode { // TODO complete
        if (currentToken.type == TokenType.Identifier) {
            when (currentToken.value) {
                "interface" -> return declaration(isInterface = true)
                "val", "var", "fun", "class", in ACCEPTED_MODIFIERS -> return declaration(isInterface = false)
                "for", "while", "do" -> return loopStatement()
            }
        }
        val tokenPos = tokenIndex
        return try {
            assignment()
        } catch (_: ParseException) {
            resetTokenToIndex(tokenPos)
            expression()
        }
    }

    /**
     * statements:
     *     [statement {semis statement}] [semis]
     *
     */
    fun statements(): List {
        val result = mutableListOf()
        var isLastTokenSemi = false
        while (!isCurrentToken(type = TokenType.Symbol, value = "}")) {
            if (result.isNotEmpty()) {
                if (!isLastTokenSemi) {
                    throw UnexpectedTokenException(currentToken)
                }
            }
            result += statement()
            isLastTokenSemi = false
            while (currentToken.type in setOf(TokenType.Semicolon, TokenType.NewLine)) {
                semis()
                isLastTokenSemi = true
            }
        }
        return result
    }

    /**
     * script:
     *     [shebangLine]
     *     {NL}
     *     {fileAnnotation}
     *     packageHeader
     *     importList
     *     {statement semi}
     *     EOF
     */
    fun script(): ScriptNode { // TODO complete
        val nodes = mutableListOf()
        val t = currentToken
//        do {
//            val curr = statement()
//            if (curr != null) {
//                nodes += curr
//                semi()
//            }
//        } while (curr != null)
        while (isSemi()) {
            semis()
        }
        while (currentToken.type != TokenType.EOF) {
            nodes += statement()
            if (currentToken.type in setOf(TokenType.Semicolon, TokenType.NewLine)) {
                semi()
            }
        }
        eat(TokenType.EOF)
        return ScriptNode(position = t.position, nodes = nodes)
    }

    /**
     * @return list of FunctionDeclarationNode and PropertyDeclarationNode
     */
    fun libHeaderFile(): List {
        val result = mutableListOf()
        var modifiers: Set? = null
        while (currentTokenExcludingNL().type != TokenType.EOF) {
            repeatedNL()
            if (isCurrentToken(TokenType.Identifier, "val") || isCurrentToken(TokenType.Identifier, "var")) {
                result += propertyDeclaration(modifiers ?: emptySet(), isProcessBody = false)
                modifiers = null
            } else if (isCurrentToken(TokenType.Identifier, "fun")) {
                result += functionDeclaration(modifiers ?: emptySet(), isProcessBody = false)
                modifiers = null
            } else if (currentToken.type == TokenType.Identifier && currentToken.value in ACCEPTED_MODIFIERS) {
                modifiers = modifiers()
            }
        }
        eat(TokenType.EOF)
        return result
    }


}

private fun Token.`is`(type: TokenType, value: Any) = this.type == type && this.value == value

private fun areTokensConsecutive(vararg tokens: Token): Boolean {
    val first = tokens.first()
    var expectedCol = first.position.col + (first.value as String).length
    var i = 1
    while (i < tokens.size) {
        if (tokens[i].position.lineNum != first.position.lineNum || tokens[i].position.col != expectedCol) {
            return false
        }
        expectedCol += (tokens[i].value as String).length
        ++i
    }
    return true
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy