diff --git a/api/src/main/kotlin/io/smnp/ext/HybridModuleProvider.kt b/api/src/main/kotlin/io/smnp/ext/HybridModuleProvider.kt new file mode 100644 index 0000000..28c5eb0 --- /dev/null +++ b/api/src/main/kotlin/io/smnp/ext/HybridModuleProvider.kt @@ -0,0 +1,45 @@ +package io.smnp.ext + +import io.smnp.callable.function.Function +import io.smnp.callable.method.Method +import io.smnp.interpreter.LanguageModuleInterpreter +import io.smnp.type.module.Module + +abstract class HybridModuleProvider(path: String) : ModuleProvider(path) { + open fun functions(): List = emptyList() + open fun methods(): List = emptyList() + open fun files() = listOf("main.mus") + + override fun provideModule(interpreter: LanguageModuleInterpreter): Module { + return provideNativeModule(interpreter).merge(provideLanguageModule(interpreter)) + } + + private fun provideNativeModule(interpreter: LanguageModuleInterpreter): Module { + return object : NativeModuleProvider(path) { + override fun functions() = this@HybridModuleProvider.functions() + override fun methods() = this@HybridModuleProvider.methods() + }.provideModule(interpreter) + } + + // Disclaimer: + // Unfortunately, LanguageModuleProvider cannot be reused here because + // getResource() method returns null if is invoked from anonymous class's object instance. + // Therefore it is need to have following code here. + private fun provideLanguageModule(interpreter: LanguageModuleInterpreter): Module { + val segments = path.split(".") + val parentNodesChainPath = segments.dropLast(1).joinToString(".") + val moduleName = segments.last() + + val module = files() + .asSequence() + .map { it to javaClass.classLoader.getResource(it) } + .map { it.first to (it.second?.readText() ?: throw RuntimeException("Module '$path' does not contain '${it.first}' file")) } + .map { interpreter.run(it.second, "module $path::${it.first}") } + .map { it.getRootModule() } + .reduce { acc, module -> acc.merge(module) } + + module.name = moduleName + + return Module.create(parentNodesChainPath, children = listOf(module)) + } +} \ No newline at end of file diff --git a/modules/collection/src/main/resources/list.mus b/modules/collection/src/main/resources/list.mus index 4933c3f..804e947 100644 --- a/modules/collection/src/main/resources/list.mus +++ b/modules/collection/src/main/resources/list.mus @@ -14,6 +14,16 @@ function _flatten(list: list, output: list) { return output; } +function shuffle(list: list) { + shuffled = list; + list.size-1 as first ^ { + second = random(0, list.size); + shuffled = shuffled.swap(first, second); + } + + return shuffled; +} + extend list { function flatten() { return flatten(this); @@ -52,4 +62,41 @@ extend list { function isNotEmpty() { return not this.isEmpty(); } + + function dropIndex(index: int) { + output = []; + i = 0; + this as item ^ { + if(index != i) { + output = output + [this.get(i)]; + } + + i = i + 1; + } + + return output; + } + + function put(index: int, value) { + return (index as i ^ this.get(i)) + [value] + ((this.size-index) as i ^ this.get(i+index)); + } + + function replace(index: int, value) { + return this + .dropIndex(index) + .put(index, value); + } + + function swap(a: int, b: int) { + A = this.get(a); + B = this.get(b); + + return this + .replace(a, B) + .replace(b, A); + } + + function shuffle() { + return shuffle(this); + } } \ No newline at end of file diff --git a/modules/lang/src/main/kotlin/io/smnp/ext/lang/LangModule.kt b/modules/lang/src/main/kotlin/io/smnp/ext/lang/LangModule.kt index 5427330..d9b4f15 100644 --- a/modules/lang/src/main/kotlin/io/smnp/ext/lang/LangModule.kt +++ b/modules/lang/src/main/kotlin/io/smnp/ext/lang/LangModule.kt @@ -7,10 +7,11 @@ import io.smnp.ext.lang.function.TypeOfFunction import io.smnp.ext.lang.method.CharAtMethod import io.smnp.ext.lang.method.ListAccessMethod import io.smnp.ext.lang.method.MapAccessMethod +import io.smnp.ext.lang.method.StringifyMethod import org.pf4j.Extension @Extension class LangModule : NativeModuleProvider("smnp.lang") { override fun functions() = listOf(IntConstructor(), NoteConstructor(), TypeOfFunction()) - override fun methods() = listOf(ListAccessMethod(), MapAccessMethod(), CharAtMethod()) + override fun methods() = listOf(ListAccessMethod(), MapAccessMethod(), CharAtMethod(), StringifyMethod()) } \ No newline at end of file diff --git a/modules/lang/src/main/kotlin/io/smnp/ext/lang/method/StringifyMethod.kt b/modules/lang/src/main/kotlin/io/smnp/ext/lang/method/StringifyMethod.kt new file mode 100644 index 0000000..06ae3a5 --- /dev/null +++ b/modules/lang/src/main/kotlin/io/smnp/ext/lang/method/StringifyMethod.kt @@ -0,0 +1,15 @@ +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.type.matcher.Matcher.Companion.anyType +import io.smnp.type.model.Value + +class StringifyMethod : Method(anyType(), "toString") { + override fun define(new: MethodDefinitionTool) { + new method simple() body { _, obj, _ -> + Value.string(obj.stringify()) + } + } +} \ No newline at end of file diff --git a/modules/math/src/main/kotlin/io/smnp/ext/MathModule.kt b/modules/math/src/main/kotlin/io/smnp/ext/MathModule.kt index 19eb421..c4a142a 100644 --- a/modules/math/src/main/kotlin/io/smnp/ext/MathModule.kt +++ b/modules/math/src/main/kotlin/io/smnp/ext/MathModule.kt @@ -1,10 +1,12 @@ package io.smnp.ext import io.smnp.ext.function.ModuloFunction +import io.smnp.ext.function.RandomFunction import io.smnp.ext.function.RangeFunction import org.pf4j.Extension @Extension -class MathModule : NativeModuleProvider("smnp.math") { - override fun functions() = listOf(ModuloFunction(), RangeFunction()) +class MathModule : HybridModuleProvider("smnp.math") { + override fun functions() = listOf(ModuloFunction(), RangeFunction(), RandomFunction()) + override fun dependencies() = listOf("smnp.lang", "smnp.collection") } \ No newline at end of file diff --git a/modules/math/src/main/kotlin/io/smnp/ext/function/RandomFunction.kt b/modules/math/src/main/kotlin/io/smnp/ext/function/RandomFunction.kt new file mode 100644 index 0000000..12922c5 --- /dev/null +++ b/modules/math/src/main/kotlin/io/smnp/ext/function/RandomFunction.kt @@ -0,0 +1,15 @@ +package io.smnp.ext.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 +import kotlin.random.Random + +class RandomFunction : Function("random") { + override fun define(new: FunctionDefinitionTool) { + new function simple() body { _, _ -> + Value.float(Random.nextFloat()) + } + } +} \ No newline at end of file diff --git a/modules/math/src/main/resources/main.mus b/modules/math/src/main/resources/main.mus new file mode 100644 index 0000000..16bb609 --- /dev/null +++ b/modules/math/src/main/resources/main.mus @@ -0,0 +1,82 @@ +function random(min: int, max: int) { + return Int(random() * (max-min) + min) +} + +function random(min: float, max: float) { + return random() * (max-min) + min +} + +function min(numbers: list) { + if(numbers.isEmpty()) { + throw "Empty lists are not supported"; + } + + min = numbers.get(0); + numbers as number ^ { + if(number < min) { + min = number; + } + } + + return min; +} + +function max(numbers: list) { + if(numbers.isEmpty()) { + throw "Empty lists are not supported"; + } + + max = numbers.get(0); + numbers as number ^ { + if(number > max) { + max = number; + } + } + + return max; +} + +function sample(list: list) { + return list.get(random(0, list.size)); +} + +function pick(...items: map<>) { + return pick(items); +} + +function pick(items: list<>>) { + acc = 0; + + items as (item, index) ^ { + if(item.size != 2) { + throw "Expected lists with two items: 'chance' and 'value'"; + } + + if(not item.containsKey("chance")) { + throw "Item " + (index+1) + " does not have 'chance' key"; + } + + if(not item.containsKey("value")) { + throw "Item " + (index+1) + " does not have 'value' key"; + } + + if(typeOf(item.get("chance")) != "int") { + throw "Expected 'chance' to be of int type"; + } + + acc = acc + item.get("chance"); + } + + if(acc != 100) { + throw "The total sum of each item ('chance' key) should be equal to 100"; + } + + acc = 0; + random = random(0, 100); + items as item ^ { + acc = acc + item.get("chance"); + if(random < acc) { + return item.get("value"); + } + } +} \ No newline at end of file