Add support for function/methods invocation on Environment + create call stack model

This commit is contained in:
2020-03-10 21:57:21 +01:00
parent 82e86ebc6a
commit d29ef61245
10 changed files with 163 additions and 18 deletions

View File

@@ -2,10 +2,21 @@ 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
abstract class Function(val name: String) { abstract class Function(val name: String) {
private var definitions: List<FunctionDefinition> = mutableListOf() private var definitions: List<FunctionDefinition> = mutableListOf()
private var _module: Module? = null
var module: Module
get() = _module ?: throw RuntimeException("Method has not set module yet")
set(value) {
if (_module != null) {
throw RuntimeException("Module of method is already set to ${module.canonicalName}")
}
_module = value
}
abstract fun define(new: FunctionDefinitionTool) abstract fun define(new: FunctionDefinitionTool)

View File

@@ -2,11 +2,22 @@ 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
abstract class Method(val typeMatcher: Matcher, val name: String) { abstract class Method(val typeMatcher: Matcher, val name: String) {
private var definitions: List<MethodDefinition> = mutableListOf() private var definitions: List<MethodDefinition> = mutableListOf()
private var _module: Module? = null
var module: Module
get() = _module ?: throw RuntimeException("Method has not set module yet")
set(value) {
if (_module != null) {
throw RuntimeException("Module of method is already set to ${module.canonicalName}")
}
_module = value
}
abstract fun define(new: MethodDefinitionTool) abstract fun define(new: MethodDefinitionTool)

View File

@@ -4,6 +4,10 @@ import io.smnp.type.enumeration.DataType
import io.smnp.type.model.Value import io.smnp.type.model.Value
object ActualSignatureFormatter { object ActualSignatureFormatter {
fun format(value: Value): String {
return format(arrayOf(value), false)
}
fun format(arguments: Array<out Value>, parentheses: Boolean = true): String { fun format(arguments: Array<out Value>, parentheses: Boolean = true): String {
val output = mutableListOf<String>() val output = mutableListOf<String>()
for(argument in arguments) { for(argument in arguments) {

View File

@@ -1,9 +1,11 @@
package io.smnp.environment package io.smnp.environment
import io.smnp.ext.ModuleRegistry import io.smnp.type.model.Value
import io.smnp.type.module.Module
interface Environment { interface Environment {
fun loadModule(path: String) fun loadModule(path: String)
fun printModules(printContent: Boolean) fun printModules(printContent: Boolean)
fun invokeFunction(name: String, arguments: List<Value>): Value
fun invokeMethod(obj: Value, name: String, arguments: List<Value>): Value
fun printCallStack()
} }

View File

@@ -0,0 +1,30 @@
package io.smnp.runtime.model
import io.smnp.type.model.Value
import io.smnp.type.module.Module
class CallStack {
private val items = mutableListOf<CallStackItem>()
fun push(module: Module, name: String, arguments: List<Value>) {
items.add(CallStackItem(module, name, arguments))
}
fun push(item: CallStackItem) {
items.add(item)
}
fun pop(): CallStackItem? {
if(items.isEmpty()) {
return null
}
val last = items.last()
items.removeAt(items.size-1)
return last
}
fun pretty() {
items.asReversed().forEachIndexed { index, item -> println("[${items.size - index - 1}] $item") }
}
}

View File

@@ -0,0 +1,13 @@
package io.smnp.runtime.model
import io.smnp.callable.signature.ActualSignatureFormatter
import io.smnp.type.model.Value
import io.smnp.type.module.Module
data class CallStackItem(
val module: Module,
val name: String,
val arguments: List<Value>
) {
override fun toString() = "${module.canonicalName}::$name${ActualSignatureFormatter.format(arguments.toTypedArray())}"
}

View File

@@ -17,6 +17,8 @@ class Module(
init { init {
children.forEach { addSubmodule(it) } children.forEach { addSubmodule(it) }
functions.forEach { it.module = this }
methods.forEach { it.module = this }
} }
fun addSubmodule(module: Module) { fun addSubmodule(module: Module) {
@@ -35,19 +37,19 @@ class Module(
} }
val canonicalName: String val canonicalName: String
get() { get() {
val modules = mutableListOf(this) val modules = mutableListOf(this)
var par = parent var par = parent
while(par != null) { while (par != null) {
modules.add(par) modules.add(par)
par = par.parent par = par.parent
}
return modules.reversed().joinToString(".") { it.name }
} }
return modules.reversed().joinToString(".") { it.name }
}
fun merge(module: Module): Module { fun merge(module: Module): Module {
if(name != module.name) { if (name != module.name) {
return this return this
} }
@@ -73,7 +75,12 @@ class Module(
} }
fun findMethod(value: Value, name: String): List<Method> { fun findMethod(value: Value, name: String): List<Method> {
return methods.filter { it.name == name && it.verifyType(value) } + children.flatMap { it.findMethod(value, name) } return methods.filter { it.name == name && it.verifyType(value) } + children.flatMap {
it.findMethod(
value,
name
)
}
} }
override fun toString() = name override fun toString() = name
@@ -84,7 +91,7 @@ class Module(
println(newPrefix + (if (first) "" else if (newLast) "└─ " else "├─ ") + name) println(newPrefix + (if (first) "" else if (newLast) "└─ " else "├─ ") + name)
newPrefix += if (newLast) " " else "" newPrefix += if (newLast) " " else ""
if(printContent) { if (printContent) {
val contentPrefix = newPrefix + if (children.isNotEmpty()) "|" else "" val contentPrefix = newPrefix + if (children.isNotEmpty()) "|" else ""
for ((index, function) in functions.withIndex()) { for ((index, function) in functions.withIndex()) {
println(contentPrefix + (if (index == functions.size - 1 && methods.isEmpty()) "" else "") + "${function.name}()") println(contentPrefix + (if (index == functions.size - 1 && methods.isEmpty()) "" else "") + "${function.name}()")
@@ -109,20 +116,22 @@ class Module(
children: List<Module> = emptyList() children: List<Module> = emptyList()
): Module { ): Module {
val modules = path.split(".") val modules = path.split(".")
if(modules.isEmpty()) { if (modules.isEmpty()) {
return Module(path, functions, methods, children) return Module(path, functions, methods, children)
} }
val root = modules.map { Module(it) }.reduceRight { m, n -> m.addSubmodule(n); m } val root = modules.map { Module(it) }.reduceRight { m, n -> m.addSubmodule(n); m }
var youngest = root var youngest = root
while(youngest.children.isNotEmpty()) { while (youngest.children.isNotEmpty()) {
youngest = youngest.children[0] youngest = youngest.children[0]
} }
youngest.functions.addAll(functions) youngest.functions.addAll(functions)
youngest.methods.addAll(methods) youngest.methods.addAll(methods)
youngest.children.addAll(children) youngest.children.addAll(children)
youngest.functions.forEach { it.module = youngest }
youngest.methods.forEach { it.module = youngest }
return root return root
} }

View File

@@ -1,11 +1,19 @@
package io.smnp.environment package io.smnp.environment
import io.smnp.callable.signature.ActualSignatureFormatter.format
import io.smnp.ext.DefaultModuleRegistry import io.smnp.ext.DefaultModuleRegistry
import io.smnp.runtime.model.CallStack
import io.smnp.type.model.Value
import io.smnp.type.module.Module import io.smnp.type.module.Module
class DefaultEnvironment : Environment { class DefaultEnvironment : Environment {
private val rootModule = Module("<root>") private val rootModule = Module("<root>")
private val loadedModules = mutableListOf<String>() private val loadedModules = mutableListOf<String>()
private val callStack = CallStack()
init {
callStack.push(rootModule, "<entrypoint>", emptyList())
}
override fun loadModule(path: String) { override fun loadModule(path: String) {
DefaultModuleRegistry.requestModulesForPath(path).forEach { DefaultModuleRegistry.requestModulesForPath(path).forEach {
@@ -17,4 +25,46 @@ class DefaultEnvironment : Environment {
override fun printModules(printContent: Boolean) { override fun printModules(printContent: Boolean) {
rootModule.pretty(printContent) rootModule.pretty(printContent)
} }
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'")
}
if(foundFunctions.size > 1) {
throw RuntimeException("Found ${foundFunctions.size} functions with name of $foundFunctions")
}
val function = foundFunctions[0]
callStack.push(function.module, function.name, arguments)
val value = function.call(this, *arguments.toTypedArray())
callStack.pop()
return value
}
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)}")
}
if(foundMethods.size > 1) {
throw RuntimeException("Found ${foundMethods.size} methods with name of '$foundMethods' for ${format(obj)}")
}
val method = foundMethods[0]
callStack.push(method.module, "${method.typeMatcher}.${method.name}", arguments)
val value = method.call(this, obj, *arguments.toTypedArray())
callStack.pop()
return value
}
override fun printCallStack() {
callStack.pretty()
}
} }

View File

@@ -1,7 +1,7 @@
package io.smnp.ext.lang package io.smnp.ext.lang
import io.smnp.callable.function.Function
import io.smnp.ext.ModuleDefinition import io.smnp.ext.ModuleDefinition
import io.smnp.ext.lang.function.DebugFunction
import io.smnp.ext.lang.method.ListAccessMethod import io.smnp.ext.lang.method.ListAccessMethod
import io.smnp.ext.lang.method.MapAccessMethod import io.smnp.ext.lang.method.MapAccessMethod
import org.pf4j.Extension import org.pf4j.Extension
@@ -10,7 +10,7 @@ import org.pf4j.Extension
class LangModule : ModuleDefinition { class LangModule : ModuleDefinition {
override fun modulePath() = "smnp.lang" override fun modulePath() = "smnp.lang"
override fun functions() = emptyList<Function>() override fun functions() = listOf(DebugFunction())
override fun methods() = listOf(ListAccessMethod(), MapAccessMethod()) override fun methods() = listOf(ListAccessMethod(), MapAccessMethod())
} }

View File

@@ -0,0 +1,15 @@
package io.smnp.ext.lang.function
import io.smnp.callable.function.Function
import io.smnp.callable.function.FunctionDefinitionTool
import io.smnp.callable.signature.Signature.Companion.simple
import io.smnp.type.model.Value
class DebugFunction : Function("debug") {
override fun define(new: FunctionDefinitionTool) {
new function simple() define { env, _ ->
env.printCallStack()
Value.void()
}
}
}