Implement some new stdlib functions and methods

This commit is contained in:
2020-03-19 21:57:24 +01:00
parent c41a02f880
commit 518bc37108
7 changed files with 210 additions and 3 deletions

View File

@@ -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<Function> = emptyList()
open fun methods(): List<Method> = 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))
}
}

View File

@@ -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);
}
}

View File

@@ -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())
}

View File

@@ -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())
}
}
}

View File

@@ -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")
}

View File

@@ -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())
}
}
}

View File

@@ -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<int, float>) {
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<int, float>) {
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<string><>) {
return pick(items);
}
function pick(items: list<map<string><>>) {
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");
}
}
}