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.ext.DefaultModuleRegistry.requestModuleProviderForPath import io.smnp.ext.ModuleProvider import io.smnp.interpreter.LanguageModuleInterpreter import io.smnp.runtime.model.CallStack import io.smnp.type.model.Value import io.smnp.type.module.Module class DefaultEnvironment(private val rootModule: Module = Module.create("")) : Environment { private val loadedModules = mutableListOf() private val callStack = CallStack() init { callStack.push(rootModule, "", emptyList()) } override fun loadModule(path: String) { requestModuleProviderForPath(path).let { loadModule(it) loadDependencies(it) } } private fun loadModule(moduleProvider: ModuleProvider, consumer: (ModuleProvider) -> Unit = {}) { if (!loadedModules.contains(moduleProvider.path)) { rootModule.addSubmodule(moduleProvider.provideModule(LanguageModuleInterpreter())) loadedModules.add(moduleProvider.path) consumer(moduleProvider) } } private fun loadDependencies(moduleProvider: ModuleProvider) { moduleProvider.dependencies().forEach { dependency -> loadModule(requestModuleProviderForPath(dependency)) { loadDependencies(it) } } } override fun printModules(printContent: Boolean) { rootModule.pretty(printContent) } override fun invokeFunction(name: String, arguments: List): 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 $name: [${foundFunctions.map { it.module.canonicalName }.joinToString()}]") } 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 { 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 $name for ${format(obj)}: [${foundMethods.map { it.module.canonicalName }.joinToString()}]") } 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() } override fun defineFunction(function: Function) { rootModule.addFunction(function) } override fun defineMethod(method: Method) { rootModule.addMethod(method) } override fun pushScope(scope: MutableMap) = callStack.top().pushScope(scope) override fun popScope() = callStack.top().popScope() override fun printScopes() = callStack.top().prettyScope() override fun setVariable(name: String, value: Value) = callStack.top().setVariable(name, value) override fun getVariable(name: String) = callStack.top().getVariable(name) }