diff --git a/app/src/main/kotlin/io/smnp/callable/function/FunctionSignatureParser.kt b/app/src/main/kotlin/io/smnp/callable/function/FunctionSignatureParser.kt new file mode 100644 index 0000000..8133b15 --- /dev/null +++ b/app/src/main/kotlin/io/smnp/callable/function/FunctionSignatureParser.kt @@ -0,0 +1,132 @@ +package io.smnp.callable.function + +import io.smnp.callable.signature.Signature +import io.smnp.dsl.ast.model.node.* +import io.smnp.error.ShouldNeverReachThisLineException +import io.smnp.type.enumeration.DataType +import io.smnp.type.matcher.Matcher +import io.smnp.type.matcher.Matcher.Companion.allTypes +import io.smnp.type.matcher.Matcher.Companion.listOfMatchers +import io.smnp.type.matcher.Matcher.Companion.mapOfMatchers +import io.smnp.type.matcher.Matcher.Companion.ofType +import io.smnp.type.matcher.Matcher.Companion.oneOf +import io.smnp.type.matcher.Matcher.Companion.optional + +object FunctionSignatureParser { + private data class SignatureMetadata(val hasRegular: Boolean, val hasOptional: Boolean, val hasVararg: Boolean) + + fun parseSignature(signature: FunctionDefinitionArgumentsNode): Signature { + val (_, _, hasVararg) = assertArgumentsPositions(signature) + assertArgumentsNames(signature) + + val matchers = signature.items.map { + when (it) { + is RegularFunctionDefinitionArgumentNode -> matcherForUnionTypeNode(it.type as UnionTypeNode) + is OptionalFunctionDefinitionArgumentNode -> optional(matcherForUnionTypeNode(it.type as UnionTypeNode)) + else -> throw ShouldNeverReachThisLineException() + } + } + + return if (hasVararg) Signature.vararg( + matchers.last(), + *matchers.dropLast(1).toTypedArray() + ) else Signature.simple(*matchers.toTypedArray()) + } + + private fun assertArgumentsNames(signature: FunctionDefinitionArgumentsNode) { + signature.items.groupingBy { + when (it) { + is RegularFunctionDefinitionArgumentNode -> (it.identifier as IdentifierNode).token.rawValue + is OptionalFunctionDefinitionArgumentNode -> (it.identifier as IdentifierNode).token.rawValue + else -> throw ShouldNeverReachThisLineException() + } + }.eachCount().forEach { + if (it.value > 1) { + throw RuntimeException("Duplicated argument name of '${it.key}'") + } + } + } + + + private fun assertArgumentsPositions(signature: FunctionDefinitionArgumentsNode): SignatureMetadata { + val firstOptional = signature.items.indexOfFirst { it is OptionalFunctionDefinitionArgumentNode } + val lastRegular = signature.items.indexOfLast { it is RegularFunctionDefinitionArgumentNode } + val vararg = + signature.items.indexOfFirst { it is RegularFunctionDefinitionArgumentNode && it.vararg != Node.NONE } + + val metadata = SignatureMetadata(lastRegular != -1, firstOptional != -1, vararg != -1) + + if (metadata.hasVararg && metadata.hasOptional) { + throw RuntimeException("Optional arguments and vararg cannot be mixed in same signature") + } + + if (metadata.hasRegular && metadata.hasOptional && firstOptional < lastRegular) { + throw RuntimeException("Optional arguments should be at the very end of arguments list") + } + + if (metadata.hasVararg && vararg != signature.items.size - 1) { + throw RuntimeException("Vararg arguments should be at the very end of arguments list") + } + + return metadata + } + + private fun matcherForUnionTypeNode(unionTypeNode: UnionTypeNode): Matcher { + if (unionTypeNode.items.isEmpty()) { + return allTypes() + } + + if (unionTypeNode.items.size == 1) { + return matcherForSingleTypeNode(unionTypeNode.items[0] as SingleTypeNode) + } + + return oneOf(*unionTypeNode.items.map { matcherForSingleTypeNode(it as SingleTypeNode) }.toTypedArray()) + } + + private fun matcherForSingleTypeNode(singleTypeNode: SingleTypeNode): Matcher { + // TODO + val type = DataType.valueOf((singleTypeNode.type as IdentifierNode).token.rawValue.toUpperCase()) + if (singleTypeNode.specifiers == Node.NONE) { + return ofType(type) + } else if (type == DataType.LIST && singleTypeNode.specifiers.children.size == 1) { + return listSpecifier(singleTypeNode.specifiers.children[0] as TypeSpecifierNode) + } else if (type == DataType.MAP && singleTypeNode.specifiers.children.size == 2) { + return mapSpecifier( + singleTypeNode.specifiers.children[0] as TypeSpecifierNode, + singleTypeNode.specifiers.children[1] as TypeSpecifierNode + ) + } + + throw RuntimeException("Unknown type") + } + + private fun listSpecifier(listSpecifierNode: TypeSpecifierNode): Matcher { + val types = mutableListOf() + + if (listSpecifierNode.items.isEmpty()) { + types.add(allTypes()) + } + + listSpecifierNode.items.forEach { types.add(matcherForSingleTypeNode(it as SingleTypeNode)) } + + return listOfMatchers(*types.toTypedArray()) + } + + private fun mapSpecifier(keySpecifierNode: TypeSpecifierNode, valueSpecifierNode: TypeSpecifierNode): Matcher { + val keys = mutableListOf() + val values = mutableListOf() + + if (keySpecifierNode.items.isEmpty()) { + keys.add(allTypes()) + } + + if (valueSpecifierNode.items.isEmpty()) { + values.add(allTypes()) + } + + keySpecifierNode.items.forEach { keys.add(matcherForSingleTypeNode(it as SingleTypeNode)) } + valueSpecifierNode.items.forEach { values.add(matcherForSingleTypeNode(it as SingleTypeNode)) } + + return mapOfMatchers(keys, values) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/io/smnp/dsl/ast/model/node/TypeNode.kt b/app/src/main/kotlin/io/smnp/dsl/ast/model/node/SingleTypeNode.kt similarity index 72% rename from app/src/main/kotlin/io/smnp/dsl/ast/model/node/TypeNode.kt rename to app/src/main/kotlin/io/smnp/dsl/ast/model/node/SingleTypeNode.kt index c55bf87..5aff4df 100644 --- a/app/src/main/kotlin/io/smnp/dsl/ast/model/node/TypeNode.kt +++ b/app/src/main/kotlin/io/smnp/dsl/ast/model/node/SingleTypeNode.kt @@ -1,6 +1,6 @@ package io.smnp.dsl.ast.model.node -class TypeNode(type: Node, specifiers: Node) : Node(2, type.position) { +class SingleTypeNode(type: Node, specifiers: Node) : Node(2, type.position) { val type: Node get() = children[0] diff --git a/app/src/main/kotlin/io/smnp/dsl/ast/model/node/UnionTypeNode.kt b/app/src/main/kotlin/io/smnp/dsl/ast/model/node/UnionTypeNode.kt new file mode 100644 index 0000000..5f4d6d8 --- /dev/null +++ b/app/src/main/kotlin/io/smnp/dsl/ast/model/node/UnionTypeNode.kt @@ -0,0 +1,5 @@ +package io.smnp.dsl.ast.model.node + +import io.smnp.dsl.token.model.entity.TokenPosition + +class UnionTypeNode(items: List, position: TokenPosition) : AbstractIterableNode(items, position) \ No newline at end of file diff --git a/app/src/main/kotlin/io/smnp/dsl/ast/parser/ExtendParser.kt b/app/src/main/kotlin/io/smnp/dsl/ast/parser/ExtendParser.kt index 4a1f195..82a0201 100644 --- a/app/src/main/kotlin/io/smnp/dsl/ast/parser/ExtendParser.kt +++ b/app/src/main/kotlin/io/smnp/dsl/ast/parser/ExtendParser.kt @@ -11,7 +11,7 @@ class ExtendParser : Parser() { override fun tryToParse(input: TokenList): ParserOutput { val simpleExtendParser = allOf( terminal(TokenType.EXTEND), - assert(TypeParser(), "type to be extended"), + assert(SingleTypeParser(), "type to be extended"), assert(terminal(TokenType.AS), "'as' keyword with identifier"), assert(SimpleIdentifierParser(), "identifier"), terminal(TokenType.WITH), @@ -22,7 +22,7 @@ class ExtendParser : Parser() { val complexExtendParser = allOf( terminal(TokenType.EXTEND), - assert(TypeParser(), "type to be extended"), + assert(SingleTypeParser(), "type to be extended"), assert(terminal(TokenType.AS), "'as' keyword with identifier"), assert(SimpleIdentifierParser(), "identifier"), assert(loop(terminal(TokenType.OPEN_CURLY), assert(FunctionDefinitionParser(), "method definition or }"), terminal(TokenType.CLOSE_CURLY)) { diff --git a/app/src/main/kotlin/io/smnp/dsl/ast/parser/OptionalFunctionDefinitionArgumentParser.kt b/app/src/main/kotlin/io/smnp/dsl/ast/parser/OptionalFunctionDefinitionArgumentParser.kt index a64366d..81af64f 100644 --- a/app/src/main/kotlin/io/smnp/dsl/ast/parser/OptionalFunctionDefinitionArgumentParser.kt +++ b/app/src/main/kotlin/io/smnp/dsl/ast/parser/OptionalFunctionDefinitionArgumentParser.kt @@ -2,7 +2,9 @@ package io.smnp.dsl.ast.parser import io.smnp.dsl.ast.model.entity.ParserOutput import io.smnp.dsl.ast.model.node.OptionalFunctionDefinitionArgumentNode +import io.smnp.dsl.ast.model.node.UnionTypeNode import io.smnp.dsl.token.model.entity.TokenList +import io.smnp.dsl.token.model.entity.TokenPosition import io.smnp.dsl.token.model.enumeration.TokenType class OptionalFunctionDefinitionArgumentParser : Parser() { @@ -12,7 +14,7 @@ class OptionalFunctionDefinitionArgumentParser : Parser() { optional(allOf( terminal(TokenType.COLON), TypeParser() - ) { it[1] }), + ) { it[1] }) { UnionTypeNode(emptyList(), TokenPosition.NONE) }, terminal(TokenType.ASSIGN), assert(ExpressionParser(), "expression") ) { diff --git a/app/src/main/kotlin/io/smnp/dsl/ast/parser/Parser.kt b/app/src/main/kotlin/io/smnp/dsl/ast/parser/Parser.kt index 0480727..5ac504d 100644 --- a/app/src/main/kotlin/io/smnp/dsl/ast/parser/Parser.kt +++ b/app/src/main/kotlin/io/smnp/dsl/ast/parser/Parser.kt @@ -187,11 +187,11 @@ abstract class Parser { } // optional -> a? - fun optional(parser: Parser): Parser { + fun optional(parser: Parser, createFallbackNode: () -> Node = { Node.NONE }): Parser { return object : Parser() { override fun tryToParse(input: TokenList): ParserOutput { val output = parser.parse(input) - return if (output.result == ParsingResult.OK) output else ParserOutput.ok(Node.NONE) + return if (output.result == ParsingResult.OK) output else ParserOutput.ok(createFallbackNode()) } } } diff --git a/app/src/main/kotlin/io/smnp/dsl/ast/parser/RegularFunctionDefinitionArgumentParser.kt b/app/src/main/kotlin/io/smnp/dsl/ast/parser/RegularFunctionDefinitionArgumentParser.kt index 3e5b943..43b3d5f 100644 --- a/app/src/main/kotlin/io/smnp/dsl/ast/parser/RegularFunctionDefinitionArgumentParser.kt +++ b/app/src/main/kotlin/io/smnp/dsl/ast/parser/RegularFunctionDefinitionArgumentParser.kt @@ -2,7 +2,9 @@ package io.smnp.dsl.ast.parser import io.smnp.dsl.ast.model.entity.ParserOutput import io.smnp.dsl.ast.model.node.RegularFunctionDefinitionArgumentNode +import io.smnp.dsl.ast.model.node.UnionTypeNode import io.smnp.dsl.token.model.entity.TokenList +import io.smnp.dsl.token.model.entity.TokenPosition import io.smnp.dsl.token.model.enumeration.TokenType class RegularFunctionDefinitionArgumentParser : Parser() { @@ -13,9 +15,9 @@ class RegularFunctionDefinitionArgumentParser : Parser() { optional(allOf( terminal(TokenType.COLON), TypeParser() - ) { it[1] }) - ) { - RegularFunctionDefinitionArgumentNode(it[1], it[2], it[0]) + ) { it[1] }) { UnionTypeNode(emptyList(), TokenPosition.NONE) } + ) { (vararg, identifier, type) -> + RegularFunctionDefinitionArgumentNode(identifier, type, vararg) }.parse(input) } } \ No newline at end of file diff --git a/app/src/main/kotlin/io/smnp/dsl/ast/parser/SingleTypeParser.kt b/app/src/main/kotlin/io/smnp/dsl/ast/parser/SingleTypeParser.kt new file mode 100644 index 0000000..ed411cf --- /dev/null +++ b/app/src/main/kotlin/io/smnp/dsl/ast/parser/SingleTypeParser.kt @@ -0,0 +1,17 @@ +package io.smnp.dsl.ast.parser + +import io.smnp.dsl.ast.model.entity.ParserOutput +import io.smnp.dsl.ast.model.node.SingleTypeNode +import io.smnp.dsl.ast.model.node.TypeSpecifiersNode +import io.smnp.dsl.token.model.entity.TokenList + +class SingleTypeParser : Parser() { + override fun tryToParse(input: TokenList): ParserOutput { + return allOf( + SimpleIdentifierParser(), + optional(repeat(TypeSpecifierParser()) { list, tokenPosition -> TypeSpecifiersNode(list, tokenPosition) }) + ) { + SingleTypeNode(it[0], it[1]) + }.parse(input) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/io/smnp/dsl/ast/parser/TypeParser.kt b/app/src/main/kotlin/io/smnp/dsl/ast/parser/TypeParser.kt index 206c451..1596b28 100644 --- a/app/src/main/kotlin/io/smnp/dsl/ast/parser/TypeParser.kt +++ b/app/src/main/kotlin/io/smnp/dsl/ast/parser/TypeParser.kt @@ -1,17 +1,17 @@ package io.smnp.dsl.ast.parser import io.smnp.dsl.ast.model.entity.ParserOutput -import io.smnp.dsl.ast.model.node.TypeNode -import io.smnp.dsl.ast.model.node.TypeSpecifiersNode +import io.smnp.dsl.ast.model.node.SingleTypeNode +import io.smnp.dsl.ast.model.node.UnionTypeNode import io.smnp.dsl.token.model.entity.TokenList class TypeParser : Parser() { override fun tryToParse(input: TokenList): ParserOutput { - return allOf( - SimpleIdentifierParser(), - optional(repeat(TypeSpecifierParser()) { list, tokenPosition -> TypeSpecifiersNode(list, tokenPosition) }) - ) { - TypeNode(it[0], it[1]) + return mapNode(oneOf( + SingleTypeParser(), + UnionTypeParser() + )) { + node -> if(node is SingleTypeNode) UnionTypeNode(listOf(node), node.position) else node }.parse(input) } } \ No newline at end of file diff --git a/app/src/main/kotlin/io/smnp/dsl/ast/parser/TypeSpecifierParser.kt b/app/src/main/kotlin/io/smnp/dsl/ast/parser/TypeSpecifierParser.kt index d733676..783e3a0 100644 --- a/app/src/main/kotlin/io/smnp/dsl/ast/parser/TypeSpecifierParser.kt +++ b/app/src/main/kotlin/io/smnp/dsl/ast/parser/TypeSpecifierParser.kt @@ -4,6 +4,6 @@ import io.smnp.dsl.ast.model.node.TypeSpecifierNode import io.smnp.dsl.token.model.enumeration.TokenType class TypeSpecifierParser : - AbstractIterableParser(TokenType.OPEN_ANGLE, TypeParser(), TokenType.CLOSE_ANGLE, { list, tokenPosition -> + AbstractIterableParser(TokenType.OPEN_ANGLE, SingleTypeParser(), TokenType.CLOSE_ANGLE, { list, tokenPosition -> TypeSpecifierNode(list, tokenPosition) }) \ No newline at end of file diff --git a/app/src/main/kotlin/io/smnp/dsl/ast/parser/UnionTypeParser.kt b/app/src/main/kotlin/io/smnp/dsl/ast/parser/UnionTypeParser.kt new file mode 100644 index 0000000..33b9356 --- /dev/null +++ b/app/src/main/kotlin/io/smnp/dsl/ast/parser/UnionTypeParser.kt @@ -0,0 +1,8 @@ +package io.smnp.dsl.ast.parser + +import io.smnp.dsl.ast.model.node.UnionTypeNode +import io.smnp.dsl.token.model.enumeration.TokenType + +class UnionTypeParser : AbstractIterableParser(TokenType.OPEN_ANGLE, SingleTypeParser(), TokenType.CLOSE_ANGLE, { list, tokenPosition -> + UnionTypeNode(list, tokenPosition) +}) \ No newline at end of file