Introduce basic error handling

This commit is contained in:
2020-03-13 19:55:58 +01:00
parent 7eb543f2bc
commit a0a09ecb55
48 changed files with 534 additions and 263 deletions

View File

@@ -2,7 +2,6 @@ package io.smnp.callable.function
import io.smnp.environment.Environment import io.smnp.environment.Environment
import io.smnp.error.FunctionInvocationException import io.smnp.error.FunctionInvocationException
import io.smnp.error.RuntimeException
import io.smnp.type.model.Value import io.smnp.type.model.Value
import io.smnp.type.module.Module import io.smnp.type.module.Module
@@ -25,7 +24,7 @@ abstract class Function(val name: String) {
val (definition, args) = definitions val (definition, args) = definitions
.map { Pair(it, it.signature.parse(arguments.toList())) } .map { Pair(it, it.signature.parse(arguments.toList())) }
.firstOrNull { (_, args) -> args.signatureMatched } .firstOrNull { (_, args) -> args.signatureMatched }
?: throw FunctionInvocationException(this, arguments) ?: throw FunctionInvocationException(this, arguments, environment)
return definition.body(environment, args.arguments) return definition.body(environment, args.arguments)
} }

View File

@@ -2,7 +2,6 @@ package io.smnp.callable.method
import io.smnp.environment.Environment import io.smnp.environment.Environment
import io.smnp.error.MethodInvocationException import io.smnp.error.MethodInvocationException
import io.smnp.error.RuntimeException
import io.smnp.type.matcher.Matcher import io.smnp.type.matcher.Matcher
import io.smnp.type.model.Value import io.smnp.type.model.Value
import io.smnp.type.module.Module import io.smnp.type.module.Module
@@ -28,7 +27,7 @@ abstract class Method(val typeMatcher: Matcher, val name: String) {
val (definition, args) = definitions val (definition, args) = definitions
.map { Pair(it, it.signature.parse(arguments.toList())) } .map { Pair(it, it.signature.parse(arguments.toList())) }
.firstOrNull { (_, args) -> args.signatureMatched } .firstOrNull { (_, args) -> args.signatureMatched }
?: throw MethodInvocationException(this, obj, arguments) ?: throw MethodInvocationException(this, obj, arguments, environment)
return definition.body(environment, obj, args.arguments) return definition.body(environment, obj, args.arguments)
} }

View File

@@ -11,6 +11,7 @@ interface Environment {
fun invokeFunction(name: String, arguments: List<Value>): Value fun invokeFunction(name: String, arguments: List<Value>): Value
fun invokeMethod(obj: Value, name: String, arguments: List<Value>): Value fun invokeMethod(obj: Value, name: String, arguments: List<Value>): Value
fun printCallStack() fun printCallStack()
fun stackTrace(): String
fun defineFunction(function: Function) fun defineFunction(function: Function)
fun defineMethod(method: Method) fun defineMethod(method: Method)
fun pushScope(scope: MutableMap<String, Value> = mutableMapOf()) fun pushScope(scope: MutableMap<String, Value> = mutableMapOf())

View File

@@ -1,3 +1,3 @@
package io.smnp.error package io.smnp.error
class CustomException(message: String?) : Exception(message) class CustomException(message: String?) : SmnpException("Error", message)

View File

@@ -0,0 +1,8 @@
package io.smnp.error
import io.smnp.environment.Environment
class EnvironmentException(exception: SmnpException, val environment: Environment) : SmnpException(
exception.friendlyName,
"${exception.message}\n\nStack trace:\n${environment.stackTrace()}"
)

View File

@@ -0,0 +1,3 @@
package io.smnp.error
class EvaluationException(message: String?) : SmnpException("Runtime error", message)

View File

@@ -2,8 +2,14 @@ package io.smnp.error
import io.smnp.callable.function.Function import io.smnp.callable.function.Function
import io.smnp.callable.signature.ActualSignatureFormatter.format import io.smnp.callable.signature.ActualSignatureFormatter.format
import io.smnp.environment.Environment
import io.smnp.type.model.Value import io.smnp.type.model.Value
class FunctionInvocationException(private val function: Function, private val passedArguments: Array<out Value>) : Exception() { class FunctionInvocationException(
override fun toString() = "Invalid signature: ${function.name}${format(passedArguments)}\nAllowed signatures:\n${function.signature}" function: Function,
} passedArguments: Array<out Value>,
val environment: Environment
) : SmnpException(
"Function invocation error",
"Invalid signature: ${function.name}${format(passedArguments)}\nAllowed signatures:\n${function.signature}"
)

View File

@@ -2,8 +2,15 @@ package io.smnp.error
import io.smnp.callable.method.Method import io.smnp.callable.method.Method
import io.smnp.callable.signature.ActualSignatureFormatter.format import io.smnp.callable.signature.ActualSignatureFormatter.format
import io.smnp.environment.Environment
import io.smnp.type.model.Value import io.smnp.type.model.Value
class MethodInvocationException(private val method: Method, private val obj: Value, private val passedArguments: Array<out Value>) : Exception() { class MethodInvocationException(
override fun toString() = "Invalid signature: $obj.${method.name}${format(passedArguments)}\nAllowed signatures:\n${method.signature}" method: Method,
} obj: Value,
passedArguments: Array<out Value>,
val environment: Environment
) : SmnpException(
"Method invocation error",
"Invalid signature: $obj.${method.name}${format(passedArguments)}\nAllowed signatures:\n${method.signature}"
)

View File

@@ -1,3 +0,0 @@
package io.smnp.error
class RuntimeException(message: String?) : Exception(message)

View File

@@ -0,0 +1,3 @@
package io.smnp.error
abstract class SmnpException(val friendlyName: String, message: String? = null) : Exception(message)

View File

@@ -21,4 +21,6 @@ class CallStack {
fun pretty() { fun pretty() {
items.asReversed().forEachIndexed { index, item -> println("[${items.size - index - 1}] $item") } items.asReversed().forEachIndexed { index, item -> println("[${items.size - index - 1}] $item") }
} }
fun stackTrace() = items.asReversed().mapIndexed { index, item -> "[${items.size - index - 1}] $item" }.joinToString("\n")
} }

View File

@@ -2,6 +2,7 @@ package io.smnp.runtime.model
import io.smnp.callable.signature.ActualSignatureFormatter import io.smnp.callable.signature.ActualSignatureFormatter
import io.smnp.collection.Stack import io.smnp.collection.Stack
import io.smnp.error.EvaluationException
import io.smnp.type.model.Value import io.smnp.type.model.Value
import io.smnp.type.module.Module import io.smnp.type.module.Module
@@ -24,7 +25,7 @@ data class CallStackFrame(
} }
fun getVariable(name: String): Value { fun getVariable(name: String): Value {
return scopes.lastOrNull { it.containsKey(name) }?.get(name) ?: throw RuntimeException("Undefined variable `$name`") return scopes.lastOrNull { it.containsKey(name) }?.get(name) ?: throw EvaluationException("Undefined variable `$name`")
} }
fun prettyScope() { fun prettyScope() {

View File

@@ -1,5 +1,9 @@
package io.smnp package io.smnp
fun main(args: Array<String>) { import io.smnp.interpreter.DefaultInterpreter
import java.io.File;
fun main(args: Array<String>) {
val interpreter = DefaultInterpreter()
interpreter.run(File("/home/bartek/Developent/SMNP-Kotlin/examples/scratchpad.mus"))
} }

View File

@@ -2,6 +2,7 @@ package io.smnp.callable.util
import io.smnp.callable.signature.Signature import io.smnp.callable.signature.Signature
import io.smnp.dsl.ast.model.node.* import io.smnp.dsl.ast.model.node.*
import io.smnp.error.InvalidSignatureException
import io.smnp.error.ShouldNeverReachThisLineException import io.smnp.error.ShouldNeverReachThisLineException
import io.smnp.type.enumeration.DataType import io.smnp.type.enumeration.DataType
import io.smnp.type.matcher.Matcher import io.smnp.type.matcher.Matcher
@@ -42,7 +43,7 @@ object FunctionSignatureParser {
} }
}.eachCount().forEach { }.eachCount().forEach {
if (it.value > 1) { if (it.value > 1) {
throw RuntimeException("Duplicated argument name of '${it.key}'") throw InvalidSignatureException("Duplicated argument name of '${it.key}'")
} }
} }
} }
@@ -61,15 +62,15 @@ object FunctionSignatureParser {
) )
if (metadata.hasVararg && metadata.hasOptional) { if (metadata.hasVararg && metadata.hasOptional) {
throw RuntimeException("Optional arguments and vararg cannot be mixed in same signature") throw InvalidSignatureException("Optional arguments and vararg cannot be mixed in same signature")
} }
if (metadata.hasRegular && metadata.hasOptional && firstOptional < lastRegular) { if (metadata.hasRegular && metadata.hasOptional && firstOptional < lastRegular) {
throw RuntimeException("Optional arguments should be at the very end of arguments list") throw InvalidSignatureException("Optional arguments should be at the very end of arguments list")
} }
if (metadata.hasVararg && vararg != signature.items.size - 1) { if (metadata.hasVararg && vararg != signature.items.size - 1) {
throw RuntimeException("Vararg arguments should be at the very end of arguments list") throw InvalidSignatureException("Vararg arguments should be at the very end of arguments list")
} }
return metadata return metadata
@@ -107,7 +108,7 @@ object FunctionSignatureParser {
) )
} }
throw RuntimeException("Unknown type") throw ShouldNeverReachThisLineException()
} }
private fun listSpecifier(listSpecifierNode: TypeSpecifierNode): Matcher { private fun listSpecifier(listSpecifierNode: TypeSpecifierNode): Matcher {

View File

@@ -14,7 +14,7 @@ class ConditionParser : Parser() {
assert(SubexpressionParser(), "expression"), assert(SubexpressionParser(), "expression"),
terminal(TokenType.CLOSE_PAREN), terminal(TokenType.CLOSE_PAREN),
assert(StatementParser(), "statement") assert(StatementParser(), "statement")
) { (ifToken, condition, trueBranch) -> ) { (ifToken, _, condition, _, trueBranch) ->
ConditionNode(ifToken, condition, trueBranch, Node.NONE, Node.NONE) ConditionNode(ifToken, condition, trueBranch, Node.NONE, Node.NONE)
} }
@@ -26,7 +26,7 @@ class ConditionParser : Parser() {
assert(StatementParser(), "statement"), assert(StatementParser(), "statement"),
terminal(TokenType.ELSE), terminal(TokenType.ELSE),
assert(StatementParser(), "statement") assert(StatementParser(), "statement")
) { (ifToken, condition, trueBranch, elseToken, falseBranch) -> ) { (ifToken, _, condition, _, trueBranch, elseToken, falseBranch) ->
ConditionNode(ifToken, condition, trueBranch, elseToken, falseBranch) ConditionNode(ifToken, condition, trueBranch, elseToken, falseBranch)
} }
@@ -35,4 +35,8 @@ class ConditionParser : Parser() {
ifStatementParser ifStatementParser
).parse(input) ).parse(input)
} }
} }
// It is required for destructing list of nodes in ifElseStatementParser object
private operator fun <E> List<E>.component6() = this[5];
private operator fun <E> List<E>.component7() = this[6];

View File

@@ -28,7 +28,7 @@ ExtendNode(targetType, identifier, method, extendToken.position)
assert(loop(terminal(TokenType.OPEN_CURLY), assert(FunctionDefinitionParser(), "method definition or }"), terminal(TokenType.CLOSE_CURLY)) { assert(loop(terminal(TokenType.OPEN_CURLY), assert(FunctionDefinitionParser(), "method definition or }"), terminal(TokenType.CLOSE_CURLY)) {
begin, methods, end -> BlockNode(begin, methods, end) begin, methods, end -> BlockNode(begin, methods, end)
}, "block with methods' definitions or 'with' keyword with single method definition") }, "block with methods' definitions or 'with' keyword with single method definition")
) { (extendToken, targetType, identifier, methods) -> ) { (extendToken, targetType, _, identifier, methods) ->
ExtendNode(targetType, identifier, methods, extendToken.position) ExtendNode(targetType, identifier, methods, extendToken.position)
} }

View File

@@ -9,6 +9,7 @@ import io.smnp.dsl.token.model.entity.TokenList
import io.smnp.dsl.token.model.entity.TokenPosition import io.smnp.dsl.token.model.entity.TokenPosition
import io.smnp.dsl.token.model.enumeration.TokenType import io.smnp.dsl.token.model.enumeration.TokenType
import io.smnp.error.InvalidSyntaxException import io.smnp.error.InvalidSyntaxException
import io.smnp.error.PositionException
abstract class Parser { abstract class Parser {
fun parse(input: TokenList): ParserOutput { fun parse(input: TokenList): ParserOutput {
@@ -178,7 +179,7 @@ abstract class Parser {
val output = parser.parse(input) val output = parser.parse(input)
if (output.result == ParsingResult.FAILED) { if (output.result == ParsingResult.FAILED) {
throw InvalidSyntaxException("Expected $expected, got '${input.current.rawValue}'", input.currentPos()) throw PositionException(InvalidSyntaxException("Expected $expected, got '${input.current.rawValue}'"), input.currentPos())
} }
return output return output

View File

@@ -6,10 +6,10 @@ data class TokenPosition(val line: Int, val beginCol: Int, val endCol: Int) {
} }
override fun toString(): String { override fun toString(): String {
return "[line ${line+1}, col ${beginCol}]" return "[line ${line+1}, col ${beginCol+1}]"
} }
fun short(): String { fun short(): String {
return "${line+1}:${beginCol}" return "${line+1}:${beginCol+1}"
} }
} }

View File

@@ -11,6 +11,7 @@ import io.smnp.dsl.token.tokenizer.Tokenizer.Companion.mapValue
import io.smnp.dsl.token.tokenizer.Tokenizer.Companion.regex import io.smnp.dsl.token.tokenizer.Tokenizer.Companion.regex
import io.smnp.dsl.token.tokenizer.Tokenizer.Companion.separated import io.smnp.dsl.token.tokenizer.Tokenizer.Companion.separated
import io.smnp.error.InvalidSyntaxException import io.smnp.error.InvalidSyntaxException
import io.smnp.error.PositionException
class DefaultTokenizer : Tokenizer { class DefaultTokenizer : Tokenizer {
private val tokenizers = listOf( private val tokenizers = listOf(
@@ -91,7 +92,7 @@ class DefaultTokenizer : Tokenizer {
val output = tokenize(line, current, index) val output = tokenize(line, current, index)
if (!output.consumed()) { if (!output.consumed()) {
throw InvalidSyntaxException("Unknown symbol ${line[current]}", TokenPosition(index, current, -1)) throw PositionException(InvalidSyntaxException("Unknown symbol ${line[current]}"), TokenPosition(index, current, -1))
} }
current += output.consumedChars current += output.consumedChars

View File

@@ -3,6 +3,7 @@ package io.smnp.environment
import io.smnp.callable.function.Function import io.smnp.callable.function.Function
import io.smnp.callable.method.Method import io.smnp.callable.method.Method
import io.smnp.callable.signature.ActualSignatureFormatter.format import io.smnp.callable.signature.ActualSignatureFormatter.format
import io.smnp.error.EvaluationException
import io.smnp.ext.DefaultModuleRegistry.requestModuleProviderForPath import io.smnp.ext.DefaultModuleRegistry.requestModuleProviderForPath
import io.smnp.ext.ModuleProvider import io.smnp.ext.ModuleProvider
import io.smnp.interpreter.LanguageModuleInterpreter import io.smnp.interpreter.LanguageModuleInterpreter
@@ -49,11 +50,11 @@ class DefaultEnvironment : Environment {
override fun invokeFunction(name: String, arguments: List<Value>): Value { override fun invokeFunction(name: String, arguments: List<Value>): Value {
val foundFunctions = rootModule.findFunction(name) val foundFunctions = rootModule.findFunction(name)
if (foundFunctions.isEmpty()) { if (foundFunctions.isEmpty()) {
throw RuntimeException("No function found with name of '$name'") throw EvaluationException("No function found with name of '$name'")
} }
if (foundFunctions.size > 1) { if (foundFunctions.size > 1) {
throw RuntimeException("Found ${foundFunctions.size} functions with name of $name: [${foundFunctions.map { it.module.canonicalName }.joinToString()}]") throw EvaluationException("Found ${foundFunctions.size} functions with name of $name: [${foundFunctions.map { it.module.canonicalName }.joinToString()}]")
} }
@@ -69,11 +70,19 @@ class DefaultEnvironment : Environment {
override fun invokeMethod(obj: Value, name: String, arguments: List<Value>): Value { override fun invokeMethod(obj: Value, name: String, arguments: List<Value>): Value {
val foundMethods = rootModule.findMethod(obj, name) val foundMethods = rootModule.findMethod(obj, name)
if (foundMethods.isEmpty()) { if (foundMethods.isEmpty()) {
throw RuntimeException("No method found with name of '$name' for ${format(obj)}") throw EvaluationException(
"No method found with name of '$name' for ${format(
obj
)}"
)
} }
if (foundMethods.size > 1) { if (foundMethods.size > 1) {
throw RuntimeException("Found ${foundMethods.size} methods with name of $name for ${format(obj)}: [${foundMethods.map { it.module.canonicalName }.joinToString()}]") throw EvaluationException(
"Found ${foundMethods.size} methods with name of $name for ${format(
obj
)}: [${foundMethods.map { it.module.canonicalName }.joinToString()}]"
)
} }
val method = foundMethods[0] val method = foundMethods[0]
@@ -89,6 +98,10 @@ class DefaultEnvironment : Environment {
callStack.pretty() callStack.pretty()
} }
override fun stackTrace(): String {
return callStack.stackTrace();
}
override fun defineFunction(function: Function) { override fun defineFunction(function: Function) {
rootModule.addFunction(function) rootModule.addFunction(function)
} }

View File

@@ -1,8 +0,0 @@
package io.smnp.error
import io.smnp.dsl.token.model.entity.TokenPosition
class EvaluationException(message: String?, val position: TokenPosition?) : Exception(message) {
override val message: String?
get() = super.message + if(position != null) " $position" else ""
}

View File

@@ -0,0 +1,3 @@
package io.smnp.error
class InvalidSignatureException(message: String?) : SmnpException("Invalid signature", message)

View File

@@ -1,8 +1,3 @@
package io.smnp.error package io.smnp.error
import io.smnp.dsl.token.model.entity.TokenPosition class InvalidSyntaxException(message: String?) : SmnpException("Syntax error", message)
class InvalidSyntaxException(message: String?, val position: TokenPosition?) : Exception(message) {
override val message: String?
get() = super.message + if(position != null) " $position" else ""
}

View File

@@ -0,0 +1,3 @@
package io.smnp.error
class ModuleException(message: String?) : SmnpException("Module error", message)

View File

@@ -0,0 +1,8 @@
package io.smnp.error
import io.smnp.dsl.token.model.entity.TokenPosition
class PositionException(exception: SmnpException, val position: TokenPosition) : SmnpException(
"${exception.friendlyName} $position",
exception.message
)

View File

@@ -2,31 +2,54 @@ package io.smnp.evaluation.evaluator
import io.smnp.dsl.ast.model.node.* import io.smnp.dsl.ast.model.node.*
import io.smnp.environment.Environment import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.EvaluationException import io.smnp.error.EvaluationException
import io.smnp.error.MethodInvocationException
import io.smnp.error.PositionException
import io.smnp.evaluation.model.entity.EvaluatorOutput import io.smnp.evaluation.model.entity.EvaluatorOutput
class AccessOperatorEvaluator : Evaluator() { class AccessOperatorEvaluator : Evaluator() {
override fun supportedNodes() = listOf(AccessOperatorNode::class) override fun supportedNodes() = listOf(AccessOperatorNode::class)
override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput { override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput {
val evaluator = ExpressionEvaluator() val evaluator = ExpressionEvaluator()
val (lhsNode, _, rhsNode) = (node as AccessOperatorNode) val (lhsNode, _, rhsNode) = (node as AccessOperatorNode)
val lhs = evaluator.evaluate(lhsNode, environment).value!! val lhs = evaluator.evaluate(lhsNode, environment).value!!
return when (rhsNode) { return when (rhsNode) {
is IdentifierNode -> { is IdentifierNode -> {
val rhs = rhsNode.token.rawValue val rhs = rhsNode.token.rawValue
EvaluatorOutput.value(lhs.properties[rhs] ?: throw EvaluationException("Unknown property $rhs of type ${lhs.type.name.toLowerCase()}", rhsNode.position)) EvaluatorOutput.value(
lhs.properties[rhs] ?: throw PositionException(
EnvironmentException(
EvaluationException("Unknown property $rhs of type ${lhs.type.name.toLowerCase()}"),
environment
),
rhsNode.position
)
)
}
is FunctionCallNode -> {
val (identifierNode, argsNode) = rhsNode
val identifier = (identifierNode as IdentifierNode).token.rawValue
val arguments =
(argsNode as FunctionCallArgumentsNode).items.map { evaluator.evaluate(it, environment).value!! }
try {
return EvaluatorOutput.value(environment.invokeMethod(lhs, identifier, arguments))
} catch(e: MethodInvocationException) {
throw PositionException(EnvironmentException(e, environment), identifierNode.position)
} catch(e: EvaluationException) {
throw PositionException(EnvironmentException(e, environment), identifierNode.position)
} }
is FunctionCallNode -> { }
val (identifierNode, argsNode) = rhsNode else -> {
val identifier = (identifierNode as IdentifierNode).token.rawValue throw PositionException(
val arguments = (argsNode as FunctionCallArgumentsNode).items.map { evaluator.evaluate(it, environment).value!! } EnvironmentException(
return EvaluatorOutput.value(environment.invokeMethod(lhs, identifier, arguments)) EvaluationException("Invalid property access type - only property name and method call are allowed"),
} environment
else -> { ), rhsNode.position
throw EvaluationException("Invalid property access type - only property name and method call are allowed", rhsNode.position) )
} }
} }
} }
} }

View File

@@ -4,7 +4,9 @@ import io.smnp.dsl.ast.model.node.AssignmentOperatorNode
import io.smnp.dsl.ast.model.node.IdentifierNode import io.smnp.dsl.ast.model.node.IdentifierNode
import io.smnp.dsl.ast.model.node.Node import io.smnp.dsl.ast.model.node.Node
import io.smnp.environment.Environment import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.EvaluationException import io.smnp.error.EvaluationException
import io.smnp.error.PositionException
import io.smnp.evaluation.model.entity.EvaluatorOutput import io.smnp.evaluation.model.entity.EvaluatorOutput
import io.smnp.type.enumeration.DataType import io.smnp.type.enumeration.DataType
@@ -19,8 +21,11 @@ class AssignmentOperatorEvaluator : Evaluator() {
val value = evaluator.evaluate(valueNode, environment).value!! val value = evaluator.evaluate(valueNode, environment).value!!
if (value.type == DataType.VOID) { if (value.type == DataType.VOID) {
throw EvaluationException( throw PositionException(
"Right hand side expression of assignment operation has returned nothing", EnvironmentException(
EvaluationException("Right hand side expression of assignment operation has returned nothing"),
environment
),
valueNode.position valueNode.position
) )
} }

View File

@@ -4,29 +4,38 @@ import io.smnp.dsl.ast.model.node.ConditionNode
import io.smnp.dsl.ast.model.node.Node import io.smnp.dsl.ast.model.node.Node
import io.smnp.dsl.ast.model.node.NoneNode import io.smnp.dsl.ast.model.node.NoneNode
import io.smnp.environment.Environment import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.EvaluationException import io.smnp.error.EvaluationException
import io.smnp.error.PositionException
import io.smnp.evaluation.model.entity.EvaluatorOutput import io.smnp.evaluation.model.entity.EvaluatorOutput
import io.smnp.type.enumeration.DataType import io.smnp.type.enumeration.DataType
class ConditionEvaluator : Evaluator() { class ConditionEvaluator : Evaluator() {
override fun supportedNodes() = listOf(ConditionNode::class) private val expressionEvaluator = ExpressionEvaluator()
private val defaultEvaluator = DefaultEvaluator()
override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput { override fun supportedNodes() = listOf(ConditionNode::class)
val expressionEvaluator = ExpressionEvaluator()
val defaultEvaluator = DefaultEvaluator()
val (conditionNode, trueBranchNode, falseBranchNode) = (node as ConditionNode)
val condition = expressionEvaluator.evaluate(conditionNode, environment).value!!
if(condition.type != DataType.BOOL) { override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput {
throw EvaluationException("Condition should be of bool type, found '${condition.value}'", conditionNode.position) val (conditionNode, trueBranchNode, falseBranchNode) = (node as ConditionNode)
} val condition = expressionEvaluator.evaluate(conditionNode, environment).value!!
if(condition.value!! as Boolean) { if (condition.type != DataType.BOOL) {
return defaultEvaluator.evaluate(trueBranchNode, environment) throw PositionException(
} else if(falseBranchNode !is NoneNode) { EnvironmentException(
return defaultEvaluator.evaluate(falseBranchNode, environment) EvaluationException("Condition should be of bool type, found '${condition.value}'"),
} environment
),
conditionNode.position
)
}
return EvaluatorOutput.ok() if (condition.value!! as Boolean) {
} return defaultEvaluator.evaluate(trueBranchNode, environment)
} else if (falseBranchNode !is NoneNode) {
return defaultEvaluator.evaluate(falseBranchNode, environment)
}
return EvaluatorOutput.ok()
}
} }

View File

@@ -2,7 +2,9 @@ package io.smnp.evaluation.evaluator
import io.smnp.dsl.ast.model.node.Node import io.smnp.dsl.ast.model.node.Node
import io.smnp.environment.Environment import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.EvaluationException import io.smnp.error.EvaluationException
import io.smnp.error.PositionException
import io.smnp.evaluation.model.entity.EvaluatorOutput import io.smnp.evaluation.model.entity.EvaluatorOutput
import io.smnp.evaluation.model.enumeration.EvaluationResult import io.smnp.evaluation.model.enumeration.EvaluationResult
import kotlin.reflect.KClass import kotlin.reflect.KClass
@@ -62,7 +64,10 @@ abstract class Evaluator {
val output = evaluator.evaluate(node, environment) val output = evaluator.evaluate(node, environment)
if (output.result == EvaluationResult.FAILED) { if (output.result == EvaluationResult.FAILED) {
throw EvaluationException("Expected $expected", node.position) throw PositionException(
EnvironmentException(EvaluationException("Expected $expected"), environment),
node.position
)
} }
return output return output

View File

@@ -4,20 +4,29 @@ import io.smnp.callable.method.CustomMethod
import io.smnp.callable.util.FunctionSignatureParser import io.smnp.callable.util.FunctionSignatureParser
import io.smnp.dsl.ast.model.node.* import io.smnp.dsl.ast.model.node.*
import io.smnp.environment.Environment import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.PositionException
import io.smnp.error.SmnpException
import io.smnp.evaluation.model.entity.EvaluatorOutput import io.smnp.evaluation.model.entity.EvaluatorOutput
class ExtendEvaluator : Evaluator() { class ExtendEvaluator : Evaluator() {
override fun supportedNodes() = listOf(ExtendNode::class) override fun supportedNodes() = listOf(ExtendNode::class)
override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput { override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput {
val (typeNode, identifierNode, methodsNode) = node as ExtendNode val (typeNode, identifierNode, methodsNode) = node as ExtendNode
val type = FunctionSignatureParser.matcherForSingleTypeNode(typeNode as SingleTypeNode) val type = FunctionSignatureParser.matcherForSingleTypeNode(typeNode as SingleTypeNode)
val identifier = (identifierNode as IdentifierNode).token.rawValue val identifier = (identifierNode as IdentifierNode).token.rawValue
methodsNode.children methodsNode.children
.map { CustomMethod.create(type, identifier, it as FunctionDefinitionNode) } .map { it to CustomMethod.create(type, identifier, it as FunctionDefinitionNode) }
.forEach { environment.defineMethod(it) } .forEach {
try {
environment.defineMethod(it.second)
} catch (e: SmnpException) {
throw PositionException(EnvironmentException(e, environment), it.first.position)
}
}
return EvaluatorOutput.ok() return EvaluatorOutput.ok()
} }
} }

View File

@@ -5,6 +5,10 @@ import io.smnp.dsl.ast.model.node.FunctionCallNode
import io.smnp.dsl.ast.model.node.IdentifierNode import io.smnp.dsl.ast.model.node.IdentifierNode
import io.smnp.dsl.ast.model.node.Node import io.smnp.dsl.ast.model.node.Node
import io.smnp.environment.Environment import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.EvaluationException
import io.smnp.error.FunctionInvocationException
import io.smnp.error.PositionException
import io.smnp.evaluation.model.entity.EvaluatorOutput import io.smnp.evaluation.model.entity.EvaluatorOutput
class FunctionCallEvaluator : Evaluator() { class FunctionCallEvaluator : Evaluator() {
@@ -16,6 +20,12 @@ class FunctionCallEvaluator : Evaluator() {
val identifier = (identifierNode as IdentifierNode).token.rawValue val identifier = (identifierNode as IdentifierNode).token.rawValue
val arguments = (argsNode as FunctionCallArgumentsNode).items.map { evaluator.evaluate(it, environment).value!! } val arguments = (argsNode as FunctionCallArgumentsNode).items.map { evaluator.evaluate(it, environment).value!! }
return EvaluatorOutput.value(environment.invokeFunction(identifier, arguments)) try {
return EvaluatorOutput.value(environment.invokeFunction(identifier, arguments))
} catch(e: FunctionInvocationException) {
throw PositionException(EnvironmentException(e, environment), node.position)
} catch(e: EvaluationException) {
throw PositionException(EnvironmentException(e, environment), node.position)
}
} }
} }

View File

@@ -4,6 +4,9 @@ import io.smnp.callable.function.CustomFunction
import io.smnp.dsl.ast.model.node.FunctionDefinitionNode import io.smnp.dsl.ast.model.node.FunctionDefinitionNode
import io.smnp.dsl.ast.model.node.Node import io.smnp.dsl.ast.model.node.Node
import io.smnp.environment.Environment import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.PositionException
import io.smnp.error.SmnpException
import io.smnp.evaluation.model.entity.EvaluatorOutput import io.smnp.evaluation.model.entity.EvaluatorOutput
class FunctionDefinitionEvaluator : Evaluator() { class FunctionDefinitionEvaluator : Evaluator() {
@@ -11,7 +14,13 @@ class FunctionDefinitionEvaluator : Evaluator() {
override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput { override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput {
val function = CustomFunction.create(node as FunctionDefinitionNode) val function = CustomFunction.create(node as FunctionDefinitionNode)
environment.defineFunction(function)
try {
environment.defineFunction(function)
} catch(e: SmnpException) {
throw PositionException(EnvironmentException(e, environment), node.position)
}
return EvaluatorOutput.ok() return EvaluatorOutput.ok()
} }
} }

View File

@@ -3,6 +3,9 @@ package io.smnp.evaluation.evaluator
import io.smnp.dsl.ast.model.node.IdentifierNode import io.smnp.dsl.ast.model.node.IdentifierNode
import io.smnp.dsl.ast.model.node.Node import io.smnp.dsl.ast.model.node.Node
import io.smnp.environment.Environment import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.EvaluationException
import io.smnp.error.PositionException
import io.smnp.evaluation.model.entity.EvaluatorOutput import io.smnp.evaluation.model.entity.EvaluatorOutput
class IdentifierEvaluator : Evaluator() { class IdentifierEvaluator : Evaluator() {
@@ -10,6 +13,11 @@ class IdentifierEvaluator : Evaluator() {
override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput { override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput {
val identifier = (node as IdentifierNode).token.rawValue val identifier = (node as IdentifierNode).token.rawValue
return EvaluatorOutput.value(environment.getVariable(identifier))
try {
return EvaluatorOutput.value(environment.getVariable(identifier))
} catch (e: EvaluationException) {
throw PositionException(EnvironmentException(e, environment), node.position)
}
} }
} }

View File

@@ -4,6 +4,9 @@ import io.smnp.dsl.ast.model.node.IdentifierNode
import io.smnp.dsl.ast.model.node.ImportNode import io.smnp.dsl.ast.model.node.ImportNode
import io.smnp.dsl.ast.model.node.Node import io.smnp.dsl.ast.model.node.Node
import io.smnp.environment.Environment import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.PositionException
import io.smnp.error.SmnpException
import io.smnp.evaluation.model.entity.EvaluatorOutput import io.smnp.evaluation.model.entity.EvaluatorOutput
class ImportEvaluator : Evaluator() { class ImportEvaluator : Evaluator() {
@@ -11,7 +14,12 @@ class ImportEvaluator : Evaluator() {
override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput { override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput {
val path = (node as ImportNode).path.joinToString(".") { (it as IdentifierNode).token.rawValue } val path = (node as ImportNode).path.joinToString(".") { (it as IdentifierNode).token.rawValue }
environment.loadModule(path)
try {
environment.loadModule(path)
} catch(e: SmnpException) {
throw PositionException(EnvironmentException(e, environment), node.position)
}
return EvaluatorOutput.ok() return EvaluatorOutput.ok()
} }

View File

@@ -5,34 +5,50 @@ import io.smnp.dsl.ast.model.node.Node
import io.smnp.dsl.ast.model.node.TokenNode import io.smnp.dsl.ast.model.node.TokenNode
import io.smnp.dsl.token.model.enumeration.TokenType import io.smnp.dsl.token.model.enumeration.TokenType
import io.smnp.environment.Environment import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.EvaluationException import io.smnp.error.EvaluationException
import io.smnp.error.PositionException
import io.smnp.error.ShouldNeverReachThisLineException import io.smnp.error.ShouldNeverReachThisLineException
import io.smnp.evaluation.model.entity.EvaluatorOutput import io.smnp.evaluation.model.entity.EvaluatorOutput
import io.smnp.type.enumeration.DataType import io.smnp.type.enumeration.DataType
import io.smnp.type.model.Value import io.smnp.type.model.Value
class LogicOperatorEvaluator : Evaluator() { class LogicOperatorEvaluator : Evaluator() {
override fun supportedNodes() = listOf(LogicOperatorNode::class) override fun supportedNodes() = listOf(LogicOperatorNode::class)
override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput { override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput {
val evaluator = ExpressionEvaluator() val evaluator = ExpressionEvaluator()
val (lhsNode, opNode, rhsNode) = (node as LogicOperatorNode) val (lhsNode, opNode, rhsNode) = (node as LogicOperatorNode)
val lhs = evaluator.evaluate(lhsNode, environment).value!! val lhs = evaluator.evaluate(lhsNode, environment).value!!
val rhs = evaluator.evaluate(rhsNode, environment).value!! val rhs = evaluator.evaluate(rhsNode, environment).value!!
val operator = (opNode as TokenNode).token.type val operator = (opNode as TokenNode).token.type
if(lhs.type != DataType.BOOL) { if (lhs.type != DataType.BOOL) {
throw EvaluationException("Operator '${operator.token}' supports only bool types", lhsNode.position) throw PositionException(
} EnvironmentException(
EvaluationException("Operator '${operator.token}' supports only bool types"),
environment
),
lhsNode.position
)
}
if(rhs.type != DataType.BOOL) { if (rhs.type != DataType.BOOL) {
throw EvaluationException("Operator '${operator.token}' supports only bool types", rhsNode.position) throw PositionException(
} EnvironmentException(
EvaluationException("Operator '${operator.token}' supports only bool types"),
environment
),
rhsNode.position
)
}
return EvaluatorOutput.value(when(operator) { return EvaluatorOutput.value(
when (operator) {
TokenType.AND -> Value.bool((lhs.value as Boolean) && (rhs.value as Boolean)) TokenType.AND -> Value.bool((lhs.value as Boolean) && (rhs.value as Boolean))
TokenType.OR -> Value.bool((lhs.value as Boolean) || (rhs.value as Boolean)) TokenType.OR -> Value.bool((lhs.value as Boolean) || (rhs.value as Boolean))
else -> throw ShouldNeverReachThisLineException() else -> throw ShouldNeverReachThisLineException()
}) }
} )
}
} }

View File

@@ -4,7 +4,9 @@ import io.smnp.dsl.ast.model.node.IdentifierNode
import io.smnp.dsl.ast.model.node.LoopNode import io.smnp.dsl.ast.model.node.LoopNode
import io.smnp.dsl.ast.model.node.Node import io.smnp.dsl.ast.model.node.Node
import io.smnp.environment.Environment import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.EvaluationException import io.smnp.error.EvaluationException
import io.smnp.error.PositionException
import io.smnp.evaluation.model.entity.EvaluatorOutput import io.smnp.evaluation.model.entity.EvaluatorOutput
import io.smnp.evaluation.model.enumeration.EvaluationResult import io.smnp.evaluation.model.enumeration.EvaluationResult
import io.smnp.type.enumeration.DataType.* import io.smnp.type.enumeration.DataType.*
@@ -26,9 +28,13 @@ class LoopEvaluator : Evaluator() {
LIST -> evaluateForList(iterator, parametersNode, statementNode, filterNode, environment) LIST -> evaluateForList(iterator, parametersNode, statementNode, filterNode, environment)
MAP -> evaluateForMap(iterator, parametersNode, statementNode, filterNode, environment) MAP -> evaluateForMap(iterator, parametersNode, statementNode, filterNode, environment)
BOOL -> evaluateForBool(iteratorNode, parametersNode, statementNode, filterNode, environment) BOOL -> evaluateForBool(iteratorNode, parametersNode, statementNode, filterNode, environment)
else -> throw EvaluationException( else -> throw PositionException(
"Expected for-loop with int iterator or foreach-loop with string, list or map iterator or while-loop with bool iterator, found ${iterator.type.name.toLowerCase()}", EnvironmentException(
iteratorNode.position EvaluationException(
"Expected for-loop with int iterator or foreach-loop with string, list or map iterator or while-loop with bool iterator, found ${iterator.type.name.toLowerCase()}"
),
environment
), iteratorNode.position
) )
} }
environment.popScope() environment.popScope()
@@ -71,7 +77,7 @@ class LoopEvaluator : Evaluator() {
{ id -> environment.setVariable(id, Value.int(index)) } { id -> environment.setVariable(id, Value.int(index)) }
) )
if(filter(filterNode, environment)) { if (filter(filterNode, environment)) {
outputs.add(defaultEvaluator.evaluate(statementNode, environment)) outputs.add(defaultEvaluator.evaluate(statementNode, environment))
index++ index++
} }
@@ -134,11 +140,22 @@ class LoopEvaluator : Evaluator() {
environment: Environment environment: Environment
): EvaluatorOutput { ): EvaluatorOutput {
if (parametersNode != Node.NONE) { if (parametersNode != Node.NONE) {
throw EvaluationException("Parameters are not supported in the while-loop", parametersNode.position) throw PositionException(
EnvironmentException(
EvaluationException("Parameters are not supported in the while-loop"),
environment
),
parametersNode.position
)
} }
if (filterNode != Node.NONE) { if (filterNode != Node.NONE) {
throw EvaluationException("Filter is not supported in the while-loop", filterNode.position) throw PositionException(
EnvironmentException(
EvaluationException("Filter is not supported in the while-loop"),
environment
), filterNode.position
)
} }
return output { outputs -> return output { outputs ->
@@ -162,7 +179,15 @@ class LoopEvaluator : Evaluator() {
if (filterNode != Node.NONE) { if (filterNode != Node.NONE) {
val condition = expressionEvaluator.evaluate(filterNode, environment).value!! val condition = expressionEvaluator.evaluate(filterNode, environment).value!!
if (condition.type != BOOL) { if (condition.type != BOOL) {
throw EvaluationException("Filter condition should be evaluated to bool type", filterNode.position) throw PositionException(
EnvironmentException(
EvaluationException(
"Filter condition should be evaluated to bool type"
),
environment
),
filterNode.position
)
} }
return condition.value as Boolean return condition.value as Boolean

View File

@@ -5,35 +5,43 @@ import io.smnp.dsl.ast.model.node.MapEntryNode
import io.smnp.dsl.ast.model.node.MapNode import io.smnp.dsl.ast.model.node.MapNode
import io.smnp.dsl.ast.model.node.Node import io.smnp.dsl.ast.model.node.Node
import io.smnp.environment.Environment import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.EvaluationException import io.smnp.error.EvaluationException
import io.smnp.error.PositionException
import io.smnp.evaluation.model.entity.EvaluatorOutput import io.smnp.evaluation.model.entity.EvaluatorOutput
import io.smnp.type.enumeration.DataType.* import io.smnp.type.enumeration.DataType.*
import io.smnp.type.model.Value import io.smnp.type.model.Value
class MapEvaluator : Evaluator() { class MapEvaluator : Evaluator() {
private val evaluator = ExpressionEvaluator() private val evaluator = ExpressionEvaluator()
override fun supportedNodes() = listOf(MapNode::class) override fun supportedNodes() = listOf(MapNode::class)
override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput { override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput {
val value = (node as MapNode).items val value = (node as MapNode).items
.map { it as MapEntryNode } .map { it as MapEntryNode }
.map { getKey(it.key, environment) to evaluator.evaluate(it.value, environment).value!! } .map { getKey(it.key, environment) to evaluator.evaluate(it.value, environment).value!! }
.toMap() .toMap()
return EvaluatorOutput.value(Value.map(value)) return EvaluatorOutput.value(Value.map(value))
} }
private fun getKey(keyNode: Node, environment: Environment): Value { private fun getKey(keyNode: Node, environment: Environment): Value {
val key = when(keyNode) { val key = when (keyNode) {
is IdentifierNode -> Value.string(keyNode.token.rawValue) is IdentifierNode -> Value.string(keyNode.token.rawValue)
else -> evaluator.evaluate(keyNode, environment).value!! else -> evaluator.evaluate(keyNode, environment).value!!
} }
if(key.type !in listOf(BOOL, INT, NOTE, STRING)) { if (key.type !in listOf(BOOL, INT, NOTE, STRING)) {
throw EvaluationException("Invalid map key's type ${key.type.name.toLowerCase()}", keyNode.position) throw PositionException(
} EnvironmentException(
EvaluationException("Invalid map key's type ${key.type.name.toLowerCase()}"),
environment
),
keyNode.position
)
}
return key return key
} }
} }

View File

@@ -3,25 +3,35 @@ package io.smnp.evaluation.evaluator
import io.smnp.dsl.ast.model.node.MinusOperatorNode import io.smnp.dsl.ast.model.node.MinusOperatorNode
import io.smnp.dsl.ast.model.node.Node import io.smnp.dsl.ast.model.node.Node
import io.smnp.environment.Environment import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.EvaluationException import io.smnp.error.EvaluationException
import io.smnp.error.PositionException
import io.smnp.evaluation.model.entity.EvaluatorOutput import io.smnp.evaluation.model.entity.EvaluatorOutput
import io.smnp.type.enumeration.DataType import io.smnp.type.enumeration.DataType
import io.smnp.type.model.Value import io.smnp.type.model.Value
class MinusOperatorEvaluator : Evaluator() { class MinusOperatorEvaluator : Evaluator() {
override fun supportedNodes() = listOf(MinusOperatorNode::class) override fun supportedNodes() = listOf(MinusOperatorNode::class)
override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput { override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput {
val evaluator = ExpressionEvaluator() val evaluator = ExpressionEvaluator()
val (_, operandNode) = (node as MinusOperatorNode) val (_, operandNode) = (node as MinusOperatorNode)
val operand = evaluator.evaluate(operandNode, environment) val operand = evaluator.evaluate(operandNode, environment)
return EvaluatorOutput.value(when(operand.value!!.type) { return EvaluatorOutput.value(
when (operand.value!!.type) {
DataType.INT -> Value.int(-1 * operand.value.value as Int) DataType.INT -> Value.int(-1 * operand.value.value as Int)
DataType.FLOAT -> Value.float(-1.0f * operand.value.value as Float) DataType.FLOAT -> Value.float(-1.0f * operand.value.value as Float)
DataType.STRING -> Value.string((operand.value.value as String).reversed()) DataType.STRING -> Value.string((operand.value.value as String).reversed())
DataType.LIST -> Value.list((operand.value.value as List<Value>).reversed()) DataType.LIST -> Value.list((operand.value.value as List<Value>).reversed())
else -> throw EvaluationException("Type ${operand.value.type.name.toLowerCase()} does not support minus operator", node.position) else -> throw PositionException(
}) EnvironmentException(
} EvaluationException("Type ${operand.value.type.name.toLowerCase()} does not support minus operator"),
environment
),
node.position
)
}
)
}
} }

View File

@@ -3,23 +3,31 @@ package io.smnp.evaluation.evaluator
import io.smnp.dsl.ast.model.node.Node import io.smnp.dsl.ast.model.node.Node
import io.smnp.dsl.ast.model.node.NotOperatorNode import io.smnp.dsl.ast.model.node.NotOperatorNode
import io.smnp.environment.Environment import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.EvaluationException import io.smnp.error.EvaluationException
import io.smnp.error.PositionException
import io.smnp.evaluation.model.entity.EvaluatorOutput import io.smnp.evaluation.model.entity.EvaluatorOutput
import io.smnp.type.enumeration.DataType import io.smnp.type.enumeration.DataType
import io.smnp.type.model.Value import io.smnp.type.model.Value
class NotOperatorEvaluator : Evaluator() { class NotOperatorEvaluator : Evaluator() {
val evaluator = assert(ExpressionEvaluator(), "expression") val evaluator = assert(ExpressionEvaluator(), "expression")
override fun supportedNodes() = listOf(NotOperatorNode::class) override fun supportedNodes() = listOf(NotOperatorNode::class)
override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput { override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput {
val (_, operandNode) = (node as NotOperatorNode) val (_, operandNode) = (node as NotOperatorNode)
val operand = evaluator.evaluate(operandNode, environment).value!! val operand = evaluator.evaluate(operandNode, environment).value!!
if(operand.type != DataType.BOOL) { if (operand.type != DataType.BOOL) {
throw EvaluationException("Only bool types can be negated", operandNode.position) throw PositionException(
} EnvironmentException(
EvaluationException("Only bool types can be negated"),
environment
),
operandNode.position
)
}
return EvaluatorOutput.value(Value.bool(!(operand.value as Boolean))) return EvaluatorOutput.value(Value.bool(!(operand.value as Boolean)))
} }
} }

View File

@@ -3,24 +3,32 @@ package io.smnp.evaluation.evaluator
import io.smnp.dsl.ast.model.node.Node import io.smnp.dsl.ast.model.node.Node
import io.smnp.dsl.ast.model.node.PowerOperatorNode import io.smnp.dsl.ast.model.node.PowerOperatorNode
import io.smnp.environment.Environment import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.EvaluationException import io.smnp.error.EvaluationException
import io.smnp.error.PositionException
import io.smnp.evaluation.model.entity.EvaluatorOutput import io.smnp.evaluation.model.entity.EvaluatorOutput
import io.smnp.type.model.Value import io.smnp.type.model.Value
import kotlin.math.pow import kotlin.math.pow
class PowerOperatorEvaluator : Evaluator() { class PowerOperatorEvaluator : Evaluator() {
override fun supportedNodes() = listOf(PowerOperatorNode::class) override fun supportedNodes() = listOf(PowerOperatorNode::class)
override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput { override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput {
val (lhsNode, _, rhsNode) = (node as PowerOperatorNode) val (lhsNode, _, rhsNode) = (node as PowerOperatorNode)
val evaluator = ExpressionEvaluator() val evaluator = ExpressionEvaluator()
val lhs = evaluator.evaluate(lhsNode, environment).value!! val lhs = evaluator.evaluate(lhsNode, environment).value!!
val rhs = evaluator.evaluate(rhsNode, environment).value!! val rhs = evaluator.evaluate(rhsNode, environment).value!!
if(!lhs.type.isNumeric() || !rhs.type.isNumeric()) { if (!lhs.type.isNumeric() || !rhs.type.isNumeric()) {
throw EvaluationException("Operator ** supports only numeric types", node.position) throw PositionException(
} EnvironmentException(
EvaluationException("Operator ** supports only numeric types"),
environment
),
node.position
)
}
return EvaluatorOutput.value(Value.float((lhs.value as Number).toFloat().pow((rhs.value as Number).toFloat()))) return EvaluatorOutput.value(Value.float((lhs.value as Number).toFloat().pow((rhs.value as Number).toFloat())))
} }
} }

View File

@@ -5,32 +5,48 @@ import io.smnp.dsl.ast.model.node.ProductOperatorNode
import io.smnp.dsl.ast.model.node.TokenNode import io.smnp.dsl.ast.model.node.TokenNode
import io.smnp.dsl.token.model.enumeration.TokenType import io.smnp.dsl.token.model.enumeration.TokenType
import io.smnp.environment.Environment import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.EvaluationException import io.smnp.error.EvaluationException
import io.smnp.error.PositionException
import io.smnp.error.ShouldNeverReachThisLineException import io.smnp.error.ShouldNeverReachThisLineException
import io.smnp.evaluation.model.entity.EvaluatorOutput import io.smnp.evaluation.model.entity.EvaluatorOutput
import io.smnp.evaluation.util.NumberUnification.unify import io.smnp.evaluation.util.NumberUnification.unify
import io.smnp.type.model.Value import io.smnp.type.model.Value
class ProductOperatorEvaluator : Evaluator() { class ProductOperatorEvaluator : Evaluator() {
override fun supportedNodes() = listOf(ProductOperatorNode::class) override fun supportedNodes() = listOf(ProductOperatorNode::class)
override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput { override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput {
val evaluator = ExpressionEvaluator() val evaluator = ExpressionEvaluator()
val (lhsNode, opNode, rhsNode) = (node as ProductOperatorNode) val (lhsNode, opNode, rhsNode) = (node as ProductOperatorNode)
val lhs = evaluator.evaluate(lhsNode, environment).value!! val lhs = evaluator.evaluate(lhsNode, environment).value!!
val rhs = evaluator.evaluate(rhsNode, environment).value!! val rhs = evaluator.evaluate(rhsNode, environment).value!!
val operator = (opNode as TokenNode).token.type val operator = (opNode as TokenNode).token.type
if (!lhs.type.isNumeric() || !rhs.type.isNumeric()) { if (!lhs.type.isNumeric() || !rhs.type.isNumeric()) {
throw EvaluationException("Operator ${operator.token} supports only numeric types", node.position) throw PositionException(
} EnvironmentException(
EvaluationException("Operator ${operator.token} supports only numeric types"),
environment
),
node.position
)
}
return EvaluatorOutput.value( return EvaluatorOutput.value(
when (operator) { when (operator) {
TokenType.ASTERISK -> unify(lhs, rhs, int = { (l, r) -> Value.int(l * r) }, float = { (l, r) -> Value.float(l * r) }) TokenType.ASTERISK -> unify(
TokenType.SLASH -> unify(lhs, rhs, int = { (l, r) -> Value.int(l / r) }, float = { (l, r) -> Value.float(l / r) }) lhs,
else -> throw ShouldNeverReachThisLineException() rhs,
} int = { (l, r) -> Value.int(l * r) },
) float = { (l, r) -> Value.float(l * r) })
} TokenType.SLASH -> unify(
lhs,
rhs,
int = { (l, r) -> Value.int(l / r) },
float = { (l, r) -> Value.float(l / r) })
else -> throw ShouldNeverReachThisLineException()
}
)
}
} }

View File

@@ -4,43 +4,51 @@ import io.smnp.dsl.ast.model.node.Node
import io.smnp.dsl.ast.model.node.RelationOperatorNode import io.smnp.dsl.ast.model.node.RelationOperatorNode
import io.smnp.dsl.ast.model.node.TokenNode import io.smnp.dsl.ast.model.node.TokenNode
import io.smnp.environment.Environment import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.EvaluationException import io.smnp.error.EvaluationException
import io.smnp.error.PositionException
import io.smnp.error.ShouldNeverReachThisLineException import io.smnp.error.ShouldNeverReachThisLineException
import io.smnp.evaluation.model.entity.EvaluatorOutput import io.smnp.evaluation.model.entity.EvaluatorOutput
import io.smnp.evaluation.util.NumberUnification.unify import io.smnp.evaluation.util.NumberUnification.unify
import io.smnp.type.model.Value import io.smnp.type.model.Value
class RelationOperatorEvaluator : Evaluator() { class RelationOperatorEvaluator : Evaluator() {
override fun supportedNodes() = listOf(RelationOperatorNode::class) override fun supportedNodes() = listOf(RelationOperatorNode::class)
override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput { override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput {
val evaluator = ExpressionEvaluator() val evaluator = ExpressionEvaluator()
val (lhsNode, opNode, rhsNode) = (node as RelationOperatorNode) val (lhsNode, opNode, rhsNode) = (node as RelationOperatorNode)
val lhs = evaluator.evaluate(lhsNode, environment).value!! val lhs = evaluator.evaluate(lhsNode, environment).value!!
val rhs = evaluator.evaluate(rhsNode, environment).value!! val rhs = evaluator.evaluate(rhsNode, environment).value!!
val operator = (opNode as TokenNode).token.rawValue val operator = (opNode as TokenNode).token.rawValue
if (operator in listOf("==", "!=")) { if (operator in listOf("==", "!=")) {
return EvaluatorOutput.value( return EvaluatorOutput.value(
Value.bool( Value.bool(
if (operator == "==") lhs.value == rhs.value if (operator == "==") lhs.value == rhs.value
else lhs.value != rhs.value else lhs.value != rhs.value
)
) )
} )
}
if (!lhs.type.isNumeric() || !rhs.type.isNumeric()) { if (!lhs.type.isNumeric() || !rhs.type.isNumeric()) {
throw EvaluationException("Operator $operator supports only numeric types", node.position) throw PositionException(
} EnvironmentException(
EvaluationException("Operator $operator supports only numeric types"),
environment
),
node.position
)
}
return EvaluatorOutput.value( return EvaluatorOutput.value(
when(operator) { when (operator) {
">" -> unify(lhs, rhs, int = { (l, r) -> Value.bool(l > r) }, float = { (l, r) -> Value.bool(l > r) }) ">" -> unify(lhs, rhs, int = { (l, r) -> Value.bool(l > r) }, float = { (l, r) -> Value.bool(l > r) })
"<" -> unify(lhs, rhs, int = { (l, r) -> Value.bool(l < r) }, float = { (l, r) -> Value.bool(l < r) }) "<" -> unify(lhs, rhs, int = { (l, r) -> Value.bool(l < r) }, float = { (l, r) -> Value.bool(l < r) })
">=" -> unify(lhs, rhs, int = { (l, r) -> Value.bool(l >= r) }, float = { (l, r) -> Value.bool(l >= r) }) ">=" -> unify(lhs, rhs, int = { (l, r) -> Value.bool(l >= r) }, float = { (l, r) -> Value.bool(l >= r) })
"<=" -> unify(lhs, rhs, int = { (l, r) -> Value.bool(l <= r) }, float = { (l, r) -> Value.bool(l <= r) }) "<=" -> unify(lhs, rhs, int = { (l, r) -> Value.bool(l <= r) }, float = { (l, r) -> Value.bool(l <= r) })
else -> throw ShouldNeverReachThisLineException() else -> throw ShouldNeverReachThisLineException()
} }
) )
} }
} }

View File

@@ -5,7 +5,9 @@ import io.smnp.dsl.ast.model.node.SumOperatorNode
import io.smnp.dsl.ast.model.node.TokenNode import io.smnp.dsl.ast.model.node.TokenNode
import io.smnp.dsl.token.model.enumeration.TokenType import io.smnp.dsl.token.model.enumeration.TokenType
import io.smnp.environment.Environment import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.EvaluationException import io.smnp.error.EvaluationException
import io.smnp.error.PositionException
import io.smnp.error.ShouldNeverReachThisLineException import io.smnp.error.ShouldNeverReachThisLineException
import io.smnp.evaluation.model.entity.EvaluatorOutput import io.smnp.evaluation.model.entity.EvaluatorOutput
import io.smnp.evaluation.util.NumberUnification.unify import io.smnp.evaluation.util.NumberUnification.unify
@@ -24,27 +26,31 @@ class SumOperatorEvaluator : Evaluator() {
return EvaluatorOutput.value( return EvaluatorOutput.value(
when (operator) { when (operator) {
TokenType.PLUS -> plus(lhs, opNode, rhs) TokenType.PLUS -> plus(lhs, opNode, rhs, environment)
TokenType.MINUS -> minus(lhs, opNode, rhs) TokenType.MINUS -> minus(lhs, opNode, rhs, environment)
else -> throw ShouldNeverReachThisLineException() else -> throw ShouldNeverReachThisLineException()
} }
) )
} }
private fun plus(lhs: Value, plusNode: Node, rhs: Value): Value { private fun plus(lhs: Value, plusNode: Node, rhs: Value, environment: Environment): Value {
return if (areNumeric(lhs, rhs)) return if (areNumeric(lhs, rhs))
unify(lhs, rhs, int = { (l, r) -> Value.int(l + r) }, float = { (l, r) -> Value.float(l + r) }) unify(lhs, rhs, int = { (l, r) -> Value.int(l + r) }, float = { (l, r) -> Value.float(l + r) })
else if (lhs.type == DataType.STRING) else if (lhs.type == DataType.STRING)
Value.string(lhs.value!! as String + rhs.value.toString()) Value.string(lhs.value!! as String + rhs.value.toString())
else if (areLists(lhs, rhs)) else if (areLists(lhs, rhs))
Value.list(lhs.value!! as List<Value> + rhs.value!! as List<Value>) Value.list(lhs.value!! as List<Value> + rhs.value!! as List<Value>)
else throw EvaluationException( else throw PositionException(
"The ${lhs.type.name.toLowerCase()} and ${rhs.type.name.toLowerCase()} are not supported by + operator", EnvironmentException(
plusNode.position EvaluationException(
"The ${lhs.type.name.toLowerCase()} and ${rhs.type.name.toLowerCase()} are not supported by + operator"
),
environment
), plusNode.position
) )
} }
private fun minus(lhs: Value, minusNode: Node, rhs: Value): Value { private fun minus(lhs: Value, minusNode: Node, rhs: Value, environment: Environment): Value {
return if (areNumeric(lhs, rhs)) return if (areNumeric(lhs, rhs))
unify( unify(
lhs, lhs,
@@ -52,7 +58,13 @@ class SumOperatorEvaluator : Evaluator() {
int = { (l, r) -> Value.int(l - r) }, int = { (l, r) -> Value.int(l - r) },
float = { (l, r) -> Value.float(l - r) } float = { (l, r) -> Value.float(l - r) }
) )
else throw EvaluationException("The - operator supports only numeric values", minusNode.position) else throw PositionException(
EnvironmentException(
EvaluationException("The - operator supports only numeric values"),
environment
),
minusNode.position
)
} }
private fun areNumeric(lhs: Value, rhs: Value) = lhs.type.isNumeric() && rhs.type.isNumeric() private fun areNumeric(lhs: Value, rhs: Value) = lhs.type.isNumeric() && rhs.type.isNumeric()

View File

@@ -1,5 +1,6 @@
package io.smnp.ext package io.smnp.ext
import io.smnp.error.ModuleException
import org.pf4j.DefaultPluginManager import org.pf4j.DefaultPluginManager
object DefaultModuleRegistry : ModuleRegistry { object DefaultModuleRegistry : ModuleRegistry {
@@ -15,7 +16,7 @@ object DefaultModuleRegistry : ModuleRegistry {
} }
override fun requestModuleProviderForPath(path: String): ModuleProvider { override fun requestModuleProviderForPath(path: String): ModuleProvider {
return modules[path] ?: throw RuntimeException("Module $path not found") return modules[path] ?: throw ModuleException("Module $path not found")
} }
override fun registeredModules() = modules.keys.toList() override fun registeredModules() = modules.keys.toList()

View File

@@ -4,50 +4,72 @@ import io.smnp.dsl.ast.parser.RootParser
import io.smnp.dsl.token.tokenizer.DefaultTokenizer import io.smnp.dsl.token.tokenizer.DefaultTokenizer
import io.smnp.environment.DefaultEnvironment import io.smnp.environment.DefaultEnvironment
import io.smnp.environment.Environment import io.smnp.environment.Environment
import io.smnp.error.SmnpException
import io.smnp.evaluation.evaluator.RootEvaluator import io.smnp.evaluation.evaluator.RootEvaluator
import io.smnp.evaluation.model.enumeration.EvaluationResult import io.smnp.evaluation.model.enumeration.EvaluationResult
import java.io.File import java.io.File
import java.lang.System.err
import kotlin.system.exitProcess
class DefaultInterpreter : Interpreter { class DefaultInterpreter : Interpreter {
private val tokenizer = DefaultTokenizer() private val tokenizer = DefaultTokenizer()
private val parser = RootParser() private val parser = RootParser()
private val evaluator = RootEvaluator() private val evaluator = RootEvaluator()
fun run(code: String, printTokens: Boolean = false, printAst: Boolean = false, dryRun: Boolean = false): Environment { fun run(
val lines = code.split("\n") code: String,
return run(lines, printTokens, printAst, dryRun) printTokens: Boolean = false,
} printAst: Boolean = false,
dryRun: Boolean = false
): Environment {
val lines = code.split("\n")
return run(lines, printTokens, printAst, dryRun)
}
private fun run(lines: List<String>, printTokens: Boolean, printAst: Boolean, dryRun: Boolean): Environment { private fun run(lines: List<String>, printTokens: Boolean, printAst: Boolean, dryRun: Boolean): Environment {
val environment = createEnvironment() try {
val tokens = tokenizer.tokenize(lines) return tryToRun(lines, printTokens, printAst, dryRun)
val ast = parser.parse(tokens) } catch (e: SmnpException) {
printError(e)
exitProcess(1)
}
}
if(printTokens) println(tokens) private fun printError(e: SmnpException) {
if(printAst) ast.node.pretty() err.println(e.friendlyName)
err.println(e.message)
}
if(!dryRun) { private fun tryToRun(lines: List<String>, printTokens: Boolean, printAst: Boolean, dryRun: Boolean): Environment {
val result = evaluator.evaluate(ast.node, environment) val environment = createEnvironment()
val tokens = tokenizer.tokenize(lines)
val ast = parser.parse(tokens)
if(result.result == EvaluationResult.FAILED) { if (printTokens) println(tokens)
throw RuntimeException("Evaluation failed") if (printAst) ast.node.pretty()
}
}
return environment if (!dryRun) {
} val result = evaluator.evaluate(ast.node, environment)
private fun createEnvironment(): Environment { if (result.result == EvaluationResult.FAILED) {
val environment = DefaultEnvironment() throw RuntimeException("Evaluation failed")
environment.loadModule("smnp.lang") }
}
return environment return environment
} }
fun run(file: File, printTokens: Boolean = false, printAst: Boolean = false, dryRun: Boolean = false): Environment { private fun createEnvironment(): Environment {
val lines = file.readLines() val environment = DefaultEnvironment()
return run(lines, printTokens, printAst, dryRun) environment.loadModule("smnp.lang")
}
override fun run(code: String) = run(code, printTokens = false, printAst = false, dryRun = false) return environment
}
fun run(file: File, printTokens: Boolean = false, printAst: Boolean = false, dryRun: Boolean = false): Environment {
val lines = file.readLines()
return run(lines, printTokens, printAst, dryRun)
}
override fun run(code: String) = run(code, printTokens = false, printAst = false, dryRun = false)
} }

View File

@@ -3,7 +3,7 @@ package io.smnp.ext.lang.method
import io.smnp.callable.method.Method import io.smnp.callable.method.Method
import io.smnp.callable.method.MethodDefinitionTool import io.smnp.callable.method.MethodDefinitionTool
import io.smnp.callable.signature.Signature.Companion.simple import io.smnp.callable.signature.Signature.Companion.simple
import io.smnp.error.RuntimeException import io.smnp.error.EvaluationException
import io.smnp.type.enumeration.DataType.INT import io.smnp.type.enumeration.DataType.INT
import io.smnp.type.enumeration.DataType.STRING import io.smnp.type.enumeration.DataType.STRING
import io.smnp.type.matcher.Matcher.Companion.ofType import io.smnp.type.matcher.Matcher.Companion.ofType
@@ -12,7 +12,7 @@ import io.smnp.type.model.Value
class CharAtMethod : Method(ofType(STRING),"charAt") { class CharAtMethod : Method(ofType(STRING),"charAt") {
override fun define(new: MethodDefinitionTool) { override fun define(new: MethodDefinitionTool) {
new method simple(ofType(INT)) body { _, obj, (index) -> new method simple(ofType(INT)) body { _, obj, (index) ->
Value.string((obj.value!! as String).getOrNull(index.value!! as Int)?.toString() ?: throw RuntimeException("Index '${index.value!!}' runs out of string bounds")) Value.string((obj.value!! as String).getOrNull(index.value!! as Int)?.toString() ?: throw EvaluationException("Index '${index.value!!}' runs out of string bounds"))
} }
} }
} }

View File

@@ -3,7 +3,7 @@ package io.smnp.ext.lang.method
import io.smnp.callable.method.Method import io.smnp.callable.method.Method
import io.smnp.callable.method.MethodDefinitionTool import io.smnp.callable.method.MethodDefinitionTool
import io.smnp.callable.signature.Signature.Companion.simple import io.smnp.callable.signature.Signature.Companion.simple
import io.smnp.error.RuntimeException import io.smnp.error.EvaluationException
import io.smnp.type.enumeration.DataType.INT import io.smnp.type.enumeration.DataType.INT
import io.smnp.type.enumeration.DataType.LIST import io.smnp.type.enumeration.DataType.LIST
import io.smnp.type.matcher.Matcher.Companion.ofType import io.smnp.type.matcher.Matcher.Companion.ofType
@@ -16,7 +16,7 @@ class ListAccessMethod : Method(ofType(LIST), "get") {
val i = index.value!! as Int val i = index.value!! as Int
if(i >= list.size) { if(i >= list.size) {
throw RuntimeException("Index '$i' runs out of array bounds") throw EvaluationException("Index '$i' runs out of array bounds")
} }
list[i] list[i]

View File

@@ -3,7 +3,7 @@ package io.smnp.ext.lang.method
import io.smnp.callable.method.Method import io.smnp.callable.method.Method
import io.smnp.callable.method.MethodDefinitionTool import io.smnp.callable.method.MethodDefinitionTool
import io.smnp.callable.signature.Signature.Companion.simple import io.smnp.callable.signature.Signature.Companion.simple
import io.smnp.error.RuntimeException import io.smnp.error.EvaluationException
import io.smnp.type.enumeration.DataType.MAP import io.smnp.type.enumeration.DataType.MAP
import io.smnp.type.matcher.Matcher.Companion.allTypes import io.smnp.type.matcher.Matcher.Companion.allTypes
import io.smnp.type.matcher.Matcher.Companion.ofType import io.smnp.type.matcher.Matcher.Companion.ofType
@@ -13,7 +13,7 @@ class MapAccessMethod : Method(ofType(MAP), "get") {
override fun define(new: MethodDefinitionTool) { override fun define(new: MethodDefinitionTool) {
new method simple(allTypes()) body { _, obj, (key) -> new method simple(allTypes()) body { _, obj, (key) ->
val map = (obj.value!! as Map<Value, Value>) val map = (obj.value!! as Map<Value, Value>)
map[key] ?: throw RuntimeException("Key '${key.value!!}' not found") map[key] ?: throw EvaluationException("Key '${key.value!!}' not found")
} }
} }
} }