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.error.FunctionInvocationException
import io.smnp.error.RuntimeException
import io.smnp.type.model.Value
import io.smnp.type.module.Module
@@ -25,7 +24,7 @@ abstract class Function(val name: String) {
val (definition, args) = definitions
.map { Pair(it, it.signature.parse(arguments.toList())) }
.firstOrNull { (_, args) -> args.signatureMatched }
?: throw FunctionInvocationException(this, arguments)
?: throw FunctionInvocationException(this, arguments, environment)
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.error.MethodInvocationException
import io.smnp.error.RuntimeException
import io.smnp.type.matcher.Matcher
import io.smnp.type.model.Value
import io.smnp.type.module.Module
@@ -28,7 +27,7 @@ abstract class Method(val typeMatcher: Matcher, val name: String) {
val (definition, args) = definitions
.map { Pair(it, it.signature.parse(arguments.toList())) }
.firstOrNull { (_, args) -> args.signatureMatched }
?: throw MethodInvocationException(this, obj, arguments)
?: throw MethodInvocationException(this, obj, arguments, environment)
return definition.body(environment, obj, args.arguments)
}

View File

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

View File

@@ -1,3 +1,3 @@
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.signature.ActualSignatureFormatter.format
import io.smnp.environment.Environment
import io.smnp.type.model.Value
class FunctionInvocationException(private val function: Function, private val passedArguments: Array<out Value>) : Exception() {
override fun toString() = "Invalid signature: ${function.name}${format(passedArguments)}\nAllowed signatures:\n${function.signature}"
}
class FunctionInvocationException(
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.signature.ActualSignatureFormatter.format
import io.smnp.environment.Environment
import io.smnp.type.model.Value
class MethodInvocationException(private val method: Method, private val obj: Value, private val passedArguments: Array<out Value>) : Exception() {
override fun toString() = "Invalid signature: $obj.${method.name}${format(passedArguments)}\nAllowed signatures:\n${method.signature}"
}
class MethodInvocationException(
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() {
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.collection.Stack
import io.smnp.error.EvaluationException
import io.smnp.type.model.Value
import io.smnp.type.module.Module
@@ -24,7 +25,7 @@ data class CallStackFrame(
}
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() {

View File

@@ -1,5 +1,9 @@
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.dsl.ast.model.node.*
import io.smnp.error.InvalidSignatureException
import io.smnp.error.ShouldNeverReachThisLineException
import io.smnp.type.enumeration.DataType
import io.smnp.type.matcher.Matcher
@@ -42,7 +43,7 @@ object FunctionSignatureParser {
}
}.eachCount().forEach {
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) {
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) {
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) {
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
@@ -107,7 +108,7 @@ object FunctionSignatureParser {
)
}
throw RuntimeException("Unknown type")
throw ShouldNeverReachThisLineException()
}
private fun listSpecifier(listSpecifierNode: TypeSpecifierNode): Matcher {

View File

@@ -14,7 +14,7 @@ class ConditionParser : Parser() {
assert(SubexpressionParser(), "expression"),
terminal(TokenType.CLOSE_PAREN),
assert(StatementParser(), "statement")
) { (ifToken, condition, trueBranch) ->
) { (ifToken, _, condition, _, trueBranch) ->
ConditionNode(ifToken, condition, trueBranch, Node.NONE, Node.NONE)
}
@@ -26,7 +26,7 @@ class ConditionParser : Parser() {
assert(StatementParser(), "statement"),
terminal(TokenType.ELSE),
assert(StatementParser(), "statement")
) { (ifToken, condition, trueBranch, elseToken, falseBranch) ->
) { (ifToken, _, condition, _, trueBranch, elseToken, falseBranch) ->
ConditionNode(ifToken, condition, trueBranch, elseToken, falseBranch)
}
@@ -36,3 +36,7 @@ class ConditionParser : Parser() {
).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)) {
begin, methods, end -> BlockNode(begin, methods, end)
}, "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)
}

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.enumeration.TokenType
import io.smnp.error.InvalidSyntaxException
import io.smnp.error.PositionException
abstract class Parser {
fun parse(input: TokenList): ParserOutput {
@@ -178,7 +179,7 @@ abstract class Parser {
val output = parser.parse(input)
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

View File

@@ -6,10 +6,10 @@ data class TokenPosition(val line: Int, val beginCol: Int, val endCol: Int) {
}
override fun toString(): String {
return "[line ${line+1}, col ${beginCol}]"
return "[line ${line+1}, col ${beginCol+1}]"
}
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.separated
import io.smnp.error.InvalidSyntaxException
import io.smnp.error.PositionException
class DefaultTokenizer : Tokenizer {
private val tokenizers = listOf(
@@ -91,7 +92,7 @@ class DefaultTokenizer : Tokenizer {
val output = tokenize(line, current, index)
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

View File

@@ -3,6 +3,7 @@ package io.smnp.environment
import io.smnp.callable.function.Function
import io.smnp.callable.method.Method
import io.smnp.callable.signature.ActualSignatureFormatter.format
import io.smnp.error.EvaluationException
import io.smnp.ext.DefaultModuleRegistry.requestModuleProviderForPath
import io.smnp.ext.ModuleProvider
import io.smnp.interpreter.LanguageModuleInterpreter
@@ -49,11 +50,11 @@ class DefaultEnvironment : Environment {
override fun invokeFunction(name: String, arguments: List<Value>): Value {
val foundFunctions = rootModule.findFunction(name)
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) {
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 {
val foundMethods = rootModule.findMethod(obj, name)
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) {
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]
@@ -89,6 +98,10 @@ class DefaultEnvironment : Environment {
callStack.pretty()
}
override fun stackTrace(): String {
return callStack.stackTrace();
}
override fun defineFunction(function: 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
import io.smnp.dsl.token.model.entity.TokenPosition
class InvalidSyntaxException(message: String?, val position: TokenPosition?) : Exception(message) {
override val message: String?
get() = super.message + if(position != null) " $position" else ""
}
class InvalidSyntaxException(message: String?) : SmnpException("Syntax error", message)

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,7 +2,10 @@ package io.smnp.evaluation.evaluator
import io.smnp.dsl.ast.model.node.*
import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.EvaluationException
import io.smnp.error.MethodInvocationException
import io.smnp.error.PositionException
import io.smnp.evaluation.model.entity.EvaluatorOutput
class AccessOperatorEvaluator : Evaluator() {
@@ -16,16 +19,36 @@ class AccessOperatorEvaluator : Evaluator() {
return when (rhsNode) {
is IdentifierNode -> {
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!! }
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)
}
}
else -> {
throw EvaluationException("Invalid property access type - only property name and method call are allowed", rhsNode.position)
throw PositionException(
EnvironmentException(
EvaluationException("Invalid property access type - only property name and method call are allowed"),
environment
), 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.Node
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.type.enumeration.DataType
@@ -19,8 +21,11 @@ class AssignmentOperatorEvaluator : Evaluator() {
val value = evaluator.evaluate(valueNode, environment).value!!
if (value.type == DataType.VOID) {
throw EvaluationException(
"Right hand side expression of assignment operation has returned nothing",
throw PositionException(
EnvironmentException(
EvaluationException("Right hand side expression of assignment operation has returned nothing"),
environment
),
valueNode.position
)
}

View File

@@ -4,21 +4,30 @@ import io.smnp.dsl.ast.model.node.ConditionNode
import io.smnp.dsl.ast.model.node.Node
import io.smnp.dsl.ast.model.node.NoneNode
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.type.enumeration.DataType
class ConditionEvaluator : Evaluator() {
private val expressionEvaluator = ExpressionEvaluator()
private val defaultEvaluator = DefaultEvaluator()
override fun supportedNodes() = listOf(ConditionNode::class)
override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput {
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) {
throw EvaluationException("Condition should be of bool type, found '${condition.value}'", conditionNode.position)
throw PositionException(
EnvironmentException(
EvaluationException("Condition should be of bool type, found '${condition.value}'"),
environment
),
conditionNode.position
)
}
if (condition.value!! as Boolean) {

View File

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

View File

@@ -4,6 +4,9 @@ import io.smnp.callable.method.CustomMethod
import io.smnp.callable.util.FunctionSignatureParser
import io.smnp.dsl.ast.model.node.*
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
class ExtendEvaluator : Evaluator() {
@@ -15,8 +18,14 @@ class ExtendEvaluator : Evaluator() {
val identifier = (identifierNode as IdentifierNode).token.rawValue
methodsNode.children
.map { CustomMethod.create(type, identifier, it as FunctionDefinitionNode) }
.forEach { environment.defineMethod(it) }
.map { it to CustomMethod.create(type, identifier, it as FunctionDefinitionNode) }
.forEach {
try {
environment.defineMethod(it.second)
} catch (e: SmnpException) {
throw PositionException(EnvironmentException(e, environment), it.first.position)
}
}
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.Node
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
class FunctionCallEvaluator : Evaluator() {
@@ -16,6 +20,12 @@ class FunctionCallEvaluator : Evaluator() {
val identifier = (identifierNode as IdentifierNode).token.rawValue
val arguments = (argsNode as FunctionCallArgumentsNode).items.map { evaluator.evaluate(it, environment).value!! }
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.Node
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
class FunctionDefinitionEvaluator : Evaluator() {
@@ -11,7 +14,13 @@ class FunctionDefinitionEvaluator : Evaluator() {
override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput {
val function = CustomFunction.create(node as FunctionDefinitionNode)
try {
environment.defineFunction(function)
} catch(e: SmnpException) {
throw PositionException(EnvironmentException(e, environment), node.position)
}
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.Node
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
class IdentifierEvaluator : Evaluator() {
@@ -10,6 +13,11 @@ class IdentifierEvaluator : Evaluator() {
override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput {
val identifier = (node as IdentifierNode).token.rawValue
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.Node
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
class ImportEvaluator : Evaluator() {
@@ -11,7 +14,12 @@ class ImportEvaluator : Evaluator() {
override fun tryToEvaluate(node: Node, environment: Environment): EvaluatorOutput {
val path = (node as ImportNode).path.joinToString(".") { (it as IdentifierNode).token.rawValue }
try {
environment.loadModule(path)
} catch(e: SmnpException) {
throw PositionException(EnvironmentException(e, environment), node.position)
}
return EvaluatorOutput.ok()
}

View File

@@ -5,7 +5,9 @@ import io.smnp.dsl.ast.model.node.Node
import io.smnp.dsl.ast.model.node.TokenNode
import io.smnp.dsl.token.model.enumeration.TokenType
import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.EvaluationException
import io.smnp.error.PositionException
import io.smnp.error.ShouldNeverReachThisLineException
import io.smnp.evaluation.model.entity.EvaluatorOutput
import io.smnp.type.enumeration.DataType
@@ -22,17 +24,31 @@ class LogicOperatorEvaluator : Evaluator() {
val operator = (opNode as TokenNode).token.type
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) {
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.OR -> Value.bool((lhs.value as Boolean) || (rhs.value as Boolean))
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.Node
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.enumeration.EvaluationResult
import io.smnp.type.enumeration.DataType.*
@@ -26,9 +28,13 @@ class LoopEvaluator : Evaluator() {
LIST -> evaluateForList(iterator, parametersNode, statementNode, filterNode, environment)
MAP -> evaluateForMap(iterator, parametersNode, statementNode, filterNode, environment)
BOOL -> evaluateForBool(iteratorNode, parametersNode, statementNode, filterNode, environment)
else -> throw 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()}",
iteratorNode.position
else -> throw PositionException(
EnvironmentException(
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()
@@ -134,11 +140,22 @@ class LoopEvaluator : Evaluator() {
environment: Environment
): EvaluatorOutput {
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) {
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 ->
@@ -162,7 +179,15 @@ class LoopEvaluator : Evaluator() {
if (filterNode != Node.NONE) {
val condition = expressionEvaluator.evaluate(filterNode, environment).value!!
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

View File

@@ -5,7 +5,9 @@ import io.smnp.dsl.ast.model.node.MapEntryNode
import io.smnp.dsl.ast.model.node.MapNode
import io.smnp.dsl.ast.model.node.Node
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.type.enumeration.DataType.*
import io.smnp.type.model.Value
@@ -31,7 +33,13 @@ class MapEvaluator : Evaluator() {
}
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

View File

@@ -3,7 +3,9 @@ package io.smnp.evaluation.evaluator
import io.smnp.dsl.ast.model.node.MinusOperatorNode
import io.smnp.dsl.ast.model.node.Node
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.type.enumeration.DataType
import io.smnp.type.model.Value
@@ -16,12 +18,20 @@ class MinusOperatorEvaluator : Evaluator() {
val (_, operandNode) = (node as MinusOperatorNode)
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.FLOAT -> Value.float(-1.0f * operand.value.value as Float)
DataType.STRING -> Value.string((operand.value.value as String).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,7 +3,9 @@ package io.smnp.evaluation.evaluator
import io.smnp.dsl.ast.model.node.Node
import io.smnp.dsl.ast.model.node.NotOperatorNode
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.type.enumeration.DataType
import io.smnp.type.model.Value
@@ -17,7 +19,13 @@ class NotOperatorEvaluator : Evaluator() {
val operand = evaluator.evaluate(operandNode, environment).value!!
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)))

View File

@@ -3,7 +3,9 @@ package io.smnp.evaluation.evaluator
import io.smnp.dsl.ast.model.node.Node
import io.smnp.dsl.ast.model.node.PowerOperatorNode
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.type.model.Value
import kotlin.math.pow
@@ -18,7 +20,13 @@ class PowerOperatorEvaluator : Evaluator() {
val rhs = evaluator.evaluate(rhsNode, environment).value!!
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())))

View File

@@ -5,7 +5,9 @@ import io.smnp.dsl.ast.model.node.ProductOperatorNode
import io.smnp.dsl.ast.model.node.TokenNode
import io.smnp.dsl.token.model.enumeration.TokenType
import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.EvaluationException
import io.smnp.error.PositionException
import io.smnp.error.ShouldNeverReachThisLineException
import io.smnp.evaluation.model.entity.EvaluatorOutput
import io.smnp.evaluation.util.NumberUnification.unify
@@ -22,13 +24,27 @@ class ProductOperatorEvaluator : Evaluator() {
val operator = (opNode as TokenNode).token.type
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(
when (operator) {
TokenType.ASTERISK -> unify(lhs, 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) })
TokenType.ASTERISK -> unify(
lhs,
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,7 +4,9 @@ import io.smnp.dsl.ast.model.node.Node
import io.smnp.dsl.ast.model.node.RelationOperatorNode
import io.smnp.dsl.ast.model.node.TokenNode
import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.EvaluationException
import io.smnp.error.PositionException
import io.smnp.error.ShouldNeverReachThisLineException
import io.smnp.evaluation.model.entity.EvaluatorOutput
import io.smnp.evaluation.util.NumberUnification.unify
@@ -30,7 +32,13 @@ class RelationOperatorEvaluator : Evaluator() {
}
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(

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.token.model.enumeration.TokenType
import io.smnp.environment.Environment
import io.smnp.error.EnvironmentException
import io.smnp.error.EvaluationException
import io.smnp.error.PositionException
import io.smnp.error.ShouldNeverReachThisLineException
import io.smnp.evaluation.model.entity.EvaluatorOutput
import io.smnp.evaluation.util.NumberUnification.unify
@@ -24,27 +26,31 @@ class SumOperatorEvaluator : Evaluator() {
return EvaluatorOutput.value(
when (operator) {
TokenType.PLUS -> plus(lhs, opNode, rhs)
TokenType.MINUS -> minus(lhs, opNode, rhs)
TokenType.PLUS -> plus(lhs, opNode, rhs, environment)
TokenType.MINUS -> minus(lhs, opNode, rhs, environment)
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))
unify(lhs, rhs, int = { (l, r) -> Value.int(l + r) }, float = { (l, r) -> Value.float(l + r) })
else if (lhs.type == DataType.STRING)
Value.string(lhs.value!! as String + rhs.value.toString())
else if (areLists(lhs, rhs))
Value.list(lhs.value!! as List<Value> + rhs.value!! as List<Value>)
else throw EvaluationException(
"The ${lhs.type.name.toLowerCase()} and ${rhs.type.name.toLowerCase()} are not supported by + operator",
plusNode.position
else throw PositionException(
EnvironmentException(
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))
unify(
lhs,
@@ -52,7 +58,13 @@ class SumOperatorEvaluator : Evaluator() {
int = { (l, r) -> Value.int(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()

View File

@@ -1,5 +1,6 @@
package io.smnp.ext
import io.smnp.error.ModuleException
import org.pf4j.DefaultPluginManager
object DefaultModuleRegistry : ModuleRegistry {
@@ -15,7 +16,7 @@ object DefaultModuleRegistry : ModuleRegistry {
}
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()

View File

@@ -4,21 +4,43 @@ import io.smnp.dsl.ast.parser.RootParser
import io.smnp.dsl.token.tokenizer.DefaultTokenizer
import io.smnp.environment.DefaultEnvironment
import io.smnp.environment.Environment
import io.smnp.error.SmnpException
import io.smnp.evaluation.evaluator.RootEvaluator
import io.smnp.evaluation.model.enumeration.EvaluationResult
import java.io.File
import java.lang.System.err
import kotlin.system.exitProcess
class DefaultInterpreter : Interpreter {
private val tokenizer = DefaultTokenizer()
private val parser = RootParser()
private val evaluator = RootEvaluator()
fun run(code: String, printTokens: Boolean = false, printAst: Boolean = false, dryRun: Boolean = false): Environment {
fun run(
code: String,
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 {
try {
return tryToRun(lines, printTokens, printAst, dryRun)
} catch (e: SmnpException) {
printError(e)
exitProcess(1)
}
}
private fun printError(e: SmnpException) {
err.println(e.friendlyName)
err.println(e.message)
}
private fun tryToRun(lines: List<String>, printTokens: Boolean, printAst: Boolean, dryRun: Boolean): Environment {
val environment = createEnvironment()
val tokens = tokenizer.tokenize(lines)
val ast = parser.parse(tokens)

View File

@@ -3,7 +3,7 @@ package io.smnp.ext.lang.method
import io.smnp.callable.method.Method
import io.smnp.callable.method.MethodDefinitionTool
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.STRING
import io.smnp.type.matcher.Matcher.Companion.ofType
@@ -12,7 +12,7 @@ import io.smnp.type.model.Value
class CharAtMethod : Method(ofType(STRING),"charAt") {
override fun define(new: MethodDefinitionTool) {
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.MethodDefinitionTool
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.LIST
import io.smnp.type.matcher.Matcher.Companion.ofType
@@ -16,7 +16,7 @@ class ListAccessMethod : Method(ofType(LIST), "get") {
val i = index.value!! as Int
if(i >= list.size) {
throw RuntimeException("Index '$i' runs out of array bounds")
throw EvaluationException("Index '$i' runs out of array bounds")
}
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.MethodDefinitionTool
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.matcher.Matcher.Companion.allTypes
import io.smnp.type.matcher.Matcher.Companion.ofType
@@ -13,7 +13,7 @@ class MapAccessMethod : Method(ofType(MAP), "get") {
override fun define(new: MethodDefinitionTool) {
new method simple(allTypes()) body { _, obj, (key) ->
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")
}
}
}