[BIG REFACTOR] Create new project structure and prepare scaffolding for external modules system

This commit is contained in:
2020-03-09 19:30:43 +01:00
parent ae9406cc50
commit dcdecee28f
155 changed files with 290 additions and 192 deletions

View File

@@ -0,0 +1,27 @@
package io.smnp.callable.function
import io.smnp.environment.Environment
import io.smnp.error.FunctionInvocationException
import io.smnp.type.model.Value
abstract class Function(val name: String) {
private var definitions: List<FunctionDefinition> = mutableListOf()
abstract fun define(new: FunctionDefinitionTool)
init {
definitions = FunctionDefinitionTool().apply { define(this) }.definitions
}
fun call(environment: Environment, vararg arguments: Value): Value {
val (definition, args) = definitions
.map { Pair(it, it.signature.parse(arguments.toList())) }
.firstOrNull { (_, args) -> args.signatureMatched }
?: throw FunctionInvocationException(this, arguments)
return definition.body(environment, args.arguments)
}
val signature: String
get() = definitions.joinToString("\nor\n") { "$name${it.signature}" }
}

View File

@@ -0,0 +1,7 @@
package io.smnp.callable.function
import io.smnp.callable.signature.Signature
import io.smnp.environment.Environment
import io.smnp.type.model.Value
class FunctionDefinition(val signature: Signature, val body: (Environment, List<Value>) -> Value)

View File

@@ -0,0 +1,20 @@
package io.smnp.callable.function
import io.smnp.callable.signature.Signature
import io.smnp.environment.Environment
import io.smnp.type.model.Value
class FunctionDefinitionTool {
val definitions: MutableList<FunctionDefinition> = mutableListOf()
infix fun function(signature: Signature): FunctionDefinitionToolStage2 {
return FunctionDefinitionToolStage2(signature)
}
inner class FunctionDefinitionToolStage2(private val signature: Signature) {
infix fun define(body: (Environment, List<Value>) -> Value) {
definitions.add(FunctionDefinition(signature, body))
}
}
}

View File

@@ -0,0 +1,30 @@
package io.smnp.callable.method
import io.smnp.environment.Environment
import io.smnp.error.MethodInvocationException
import io.smnp.type.matcher.Matcher
import io.smnp.type.model.Value
abstract class Method(val typeMatcher: Matcher, val name: String) {
private var definitions: List<MethodDefinition> = mutableListOf()
abstract fun define(new: MethodDefinitionTool)
init {
definitions = MethodDefinitionTool().apply { define(this) }.definitions
}
fun verifyType(type: Value) = typeMatcher.match(type)
fun call(environment: Environment, obj: Value, vararg arguments: Value): Value {
val (definition, args) = definitions
.map { Pair(it, it.signature.parse(arguments.toList())) }
.firstOrNull { (_, args) -> args.signatureMatched }
?: throw MethodInvocationException(this, obj, arguments)
return definition.body(environment, obj, args.arguments)
}
val signature: String
get() = definitions.joinToString("\nor\n") { "$typeMatcher.$name${it.signature}" }
}

View File

@@ -0,0 +1,7 @@
package io.smnp.callable.method
import io.smnp.callable.signature.Signature
import io.smnp.environment.Environment
import io.smnp.type.model.Value
class MethodDefinition(val signature: Signature, val body: (Environment, Value, List<Value>) -> Value)

View File

@@ -0,0 +1,20 @@
package io.smnp.callable.method
import io.smnp.callable.signature.Signature
import io.smnp.environment.Environment
import io.smnp.type.model.Value
class MethodDefinitionTool {
val definitions: MutableList<MethodDefinition> = mutableListOf()
infix fun method(signature: Signature): MethodDefinitionToolStage2 {
return MethodDefinitionToolStage2(signature)
}
inner class MethodDefinitionToolStage2(private val signature: Signature) {
infix fun define(body: (Environment, Value, List<Value>) -> Value) {
definitions.add(MethodDefinition(signature, body))
}
}
}

View File

@@ -0,0 +1,55 @@
package io.smnp.callable.signature
import io.smnp.type.enumeration.DataType
import io.smnp.type.model.Value
object ActualSignatureFormatter {
fun format(arguments: Array<out Value>, parentheses: Boolean = true): String {
val output = mutableListOf<String>()
for(argument in arguments) {
output.add(when(argument.type) {
DataType.LIST -> listTypes(
argument.value as List<Value>
)
DataType.MAP -> mapTypes(
argument.value as Map<Value, Value>
)
else -> argument.type.name.toLowerCase()
})
}
return if(parentheses) "(${output.joinToString()})" else output.joinToString()
}
private fun listTypes(list: List<Value>, output: MutableList<String> = mutableListOf()): String {
for (item in list) {
output.add(when (item.type) {
DataType.LIST -> listTypes(
item.value as List<Value>
)
DataType.MAP -> mapTypes(
item.value as Map<Value, Value>
)
else -> item.type.name.toLowerCase()
})
}
return "list<${output.toSet().joinToString()}>"
}
private fun mapTypes(map: Map<Value, Value>, output: MutableMap<Value, String> = mutableMapOf()): String {
for ((k, v) in map) {
output[k] = when (v.type) {
DataType.LIST -> listTypes(
v.value as List<Value>
)
DataType.MAP -> mapTypes(
v.value as Map<Value, Value>
)
else -> v.type.name.toLowerCase()
}
}
return "map<${output.keys.toSet().joinToString()}><${output.values.toSet().joinToString()}}>"
}
}

View File

@@ -0,0 +1,21 @@
package io.smnp.callable.signature
import io.smnp.type.model.Value
class ArgumentsList(val signatureMatched: Boolean, val arguments: List<Value>) {
operator fun get(index: Int) = arguments[index]
fun toArray() = arguments.toTypedArray()
override fun toString() = if(signatureMatched) "valid($arguments)" else "invalid"
companion object {
fun valid(arguments: List<Value>): ArgumentsList {
return ArgumentsList(true, arguments)
}
fun invalid(): ArgumentsList {
return ArgumentsList(false, emptyList())
}
}
}

View File

@@ -0,0 +1,18 @@
package io.smnp.callable.signature
import io.smnp.type.matcher.Matcher
import io.smnp.type.model.Value
interface Signature {
fun parse(arguments: List<Value>): ArgumentsList
companion object {
fun simple(vararg signature: Matcher): Signature {
return SimpleSignature(*signature)
}
fun vararg(varargMatcher: Matcher, vararg signature: Matcher): Signature {
return VarargSignature(varargMatcher, *signature)
}
}
}

View File

@@ -0,0 +1,21 @@
package io.smnp.callable.signature
import io.smnp.type.matcher.Matcher
import io.smnp.type.model.Value
class SimpleSignature(private vararg val signature: Matcher) :
Signature {
override fun parse(arguments: List<Value>): ArgumentsList {
if (arguments.size > signature.size || arguments.size < signature.count { !it.optional }) {
return ArgumentsList.invalid()
}
return ArgumentsList(signature.zip(arguments).all { (matcher, argument) ->
matcher.match(
argument
)
}, arguments)
}
override fun toString() = "(${signature.joinToString(", ")})"
}

View File

@@ -0,0 +1,43 @@
package io.smnp.callable.signature
import io.smnp.type.matcher.Matcher
import io.smnp.type.model.Value
class VarargSignature(private val varargMatcher: Matcher, private vararg val signature: Matcher) :
Signature {
override fun parse(arguments: List<Value>): ArgumentsList {
if ((arrayListOf(varargMatcher) + signature).any { it.optional }) {
throw RuntimeException("Vararg signature does not support optional arguments")
}
if (signature.size > arguments.size) {
return ArgumentsList.invalid()
}
for (i in signature.indices) {
if (!signature[i].match(arguments[i])) {
return ArgumentsList.invalid()
}
}
for (i in signature.size until arguments.size) {
if (!varargMatcher.match(arguments[i])) {
return ArgumentsList.invalid()
}
}
return ArgumentsList.valid(
arguments.subList(0, signature.size) + listOf(
Value.list(
arguments.subList(
signature.size,
arguments.size
)
)
)
)
}
override fun toString() =
"(${signature.joinToString(", ")}${if (signature.isNotEmpty()) ", " else ""}...$varargMatcher)"
}

View File

@@ -0,0 +1,17 @@
package io.smnp.data.entity
import io.smnp.data.enumeration.Pitch
class Note private constructor(val pitch: Pitch, val octave: Int, val duration: Int, val dot: Boolean) {
data class Builder(var pitch: Pitch = Pitch.A, var octave: Int = 4, var duration: Int = 4, var dot: Boolean = false) {
fun pitch(pitch: Pitch) = apply { this.pitch = pitch }
fun octave(octave: Int) = apply { this.octave = octave }
fun duration(duration: Int) = apply { this.duration = duration }
fun dot(dot: Boolean) = apply { this.dot = dot }
fun build() = Note(pitch, octave, duration, dot)
}
override fun toString(): String {
return "${pitch}${octave}:${duration}${if (dot) "d" else ""}"
}
}

View File

@@ -0,0 +1,51 @@
package io.smnp.data.enumeration
enum class Pitch {
C, C_S, D, D_S, E, F, F_S, G, G_S, A, A_S, H;
override fun toString(): String {
return when(this) {
C -> "C"
C_S -> "C#"
D -> "D"
D_S -> "D#"
E -> "E"
F -> "F"
F_S -> "F#"
G -> "G"
G_S -> "G#"
A -> "A"
A_S -> "A#"
H -> "H"
}
}
companion object {
fun parse(symbol: String): Pitch {
return when(symbol.toLowerCase()) {
"cb" -> H
"c" -> C
"c#" -> C_S
"db" -> C_S
"d" -> D
"d#" -> D_S
"eb" -> D_S
"e" -> E
"e#" -> F
"fb" -> E
"f" -> F
"f#" -> F_S
"gb" -> F_S
"g" -> G
"g#" -> G_S
"ab" -> G_S
"a" -> A
"a#" -> A_S
"b" -> A_S
"h" -> H
"h#" -> C
else -> throw RuntimeException("Unknown pitch symbol")
}
}
}
}

View File

@@ -0,0 +1,16 @@
package io.smnp.environment
import io.smnp.type.module.Module
class Environment {
private val rootModule = Module("<root>")
private val loadedModules = mutableListOf<String>()
fun loadModule(path: String) {
}
fun printModules(printContent: Boolean) {
rootModule.pretty(printContent)
}
}

View File

@@ -0,0 +1,3 @@
package io.smnp.error
class CustomException(message: String?) : Exception(message)

View File

@@ -0,0 +1,9 @@
package io.smnp.error
import io.smnp.callable.function.Function
import io.smnp.callable.signature.ActualSignatureFormatter.format
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}"
}

View File

@@ -0,0 +1,9 @@
package io.smnp.error
import io.smnp.callable.method.Method
import io.smnp.callable.signature.ActualSignatureFormatter.format
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}"
}

View File

@@ -0,0 +1,5 @@
package io.smnp.error
class ShouldNeverReachThisLineException : Exception(
"This exception should never be thrown. Please check stack trace and investigate the source of error."
)

View File

@@ -0,0 +1,45 @@
package io.smnp.type.enumeration
import io.smnp.data.entity.Note
import kotlin.reflect.KClass
enum class DataType(val kotlinType: KClass<out Any>?) {
INT(Int::class),
FLOAT(Float::class),
STRING(String::class),
LIST(List::class),
MAP(Map::class),
NOTE(Note::class),
BOOL(Boolean::class),
TYPE(DataType::class),
VOID(null);
fun isInstance(value: Any?): Boolean {
if(kotlinType == null) {
return value == null
}
return kotlinType.isInstance(value)
}
fun isNumeric(): Boolean {
return this == INT || this == FLOAT
}
override fun toString(): String {
return super.toString().toLowerCase()
}
companion object {
fun inference(value: Any?): DataType {
if (value == null) {
return VOID
}
return values()
.filter { it.kotlinType != null }
.find { it.kotlinType!!.isInstance(value) }
?: throw RuntimeException("Cannot inference type for '$value'")
}
}
}

View File

@@ -0,0 +1,131 @@
package io.smnp.type.matcher
import io.smnp.type.enumeration.DataType
import io.smnp.type.model.Value
class Matcher(val type: DataType?, private val matcher: (Value) -> Boolean, private val string: String, val optional: Boolean = false) {
fun match(value: Value): Boolean =
(type != null && type == value.type && matcher(value)) || (type == null) && matcher(value)
infix fun and(matcher: Matcher): Matcher {
if (type != matcher.type) {
throw RuntimeException("Matchers' supported type are different to each other: $type != ${matcher.type}")
}
val string = "[${this.string} and ${matcher.string}]"
return Matcher(type, { this.match(it) && matcher.match(it) }, string)
}
infix fun or(matcher: Matcher): Matcher {
if (type != matcher.type) {
throw RuntimeException("Matchers' supported type are different to each other: $type != ${matcher.type}")
}
val string = "[${this.string} and ${matcher.string}]"
return Matcher(type, { this.match(it) || matcher.match(it) }, string)
}
override fun equals(other: Any?) = toString() == other.toString()
override fun toString() = string
companion object {
fun optional(matcher: Matcher): Matcher {
return Matcher(
matcher.type,
matcher.matcher,
"${matcher.string}?",
true
)
}
fun mapOfMatchers(keyMatchers: List<Matcher>, valueMatchers: List<Matcher>): Matcher {
return Matcher(
DataType.MAP,
{
(it.value!! as Map<Value, Value>).entries.all { (k, v) ->
keyMatchers.any { m -> m.match(k) } && valueMatchers.any { m ->
m.match(
v
)
}
}
},
"map<${keyMatchers.joinToString(", ") { it.toString() }}><${valueMatchers.joinToString(", ") { it.toString() }}>"
)
}
fun recursiveListMatcher(matcher: Matcher): Matcher {
if (matcher.type == DataType.LIST) {
throw RuntimeException("Passed matcher will be handling non-list types, so it cannot have supported type of ${DataType.LIST}")
}
fun match(value: Value): Boolean {
if (value.type != DataType.LIST) {
return matcher.match(value)
}
return (value.value!! as List<Value>).all { match(it) }
}
return Matcher(
DataType.LIST,
{ match(it) },
"[LIST OF $matcher]"
)
}
fun listOfMatchers(vararg matchers: Matcher): Matcher {
return Matcher(
DataType.LIST,
{ list ->
(list.value as List<Value>).all { item ->
matchers.any {
it.match(
item
)
}
}
},
"list<${matchers.joinToString(", ") { it.toString() }}>"
)
}
fun listOf(vararg types: DataType): Matcher {
return Matcher(
DataType.LIST,
{ list -> (list.value as List<Value>).all { it.type in types } },
"list<${types.joinToString(", ") { it.name.toLowerCase() }}>"
)
}
fun allTypes(): Matcher {
return Matcher(
null,
{ it.type != DataType.VOID },
"any"
)
}
fun ofTypes(vararg types: DataType): Matcher {
return Matcher(
null,
{ it.type in types },
"<${types.joinToString(", ") { it.name.toLowerCase() }}>"
)
}
fun ofType(type: DataType): Matcher {
return Matcher(null, { it.type == type }, type.name.toLowerCase())
}
fun oneOf(vararg matchers: Matcher): Matcher {
return Matcher(
null,
{ value -> matchers.any { matcher -> matcher.match(value) } },
"<${matchers.joinToString(", ") { it.toString() }}>"
)
}
}
}

View File

@@ -0,0 +1,87 @@
package io.smnp.type.model
import io.smnp.data.entity.Note
import io.smnp.error.ShouldNeverReachThisLineException
import io.smnp.type.enumeration.DataType
class Value private constructor(val type: DataType, val value: Any?, val properties: Map<String, Value> = emptyMap()) {
init {
if(!type.isInstance(value)) {
throw RuntimeException("'$value' is not of type $type")
}
}
override fun toString(): String {
return "$type($value)"
}
companion object {
fun int(value: Int): Value {
return Value(DataType.INT, value)
}
fun float(value: Float): Value {
return Value(
DataType.FLOAT,
value
)
}
fun numeric(value: Number): Value {
return when(value::class) {
Int::class -> int(value.toInt())
Float::class -> float(value.toFloat())
else -> throw ShouldNeverReachThisLineException()
}
}
fun string(value: String): Value {
return Value(
DataType.STRING, value, hashMapOf(
Pair("length", int(value.length))
)
)
}
fun list(value: List<Value>): Value {
return Value(
DataType.LIST, value, hashMapOf(
Pair("size", int(value.size))
)
)
}
fun map(value: Map<Value, Value>): Value {
return Value(
DataType.MAP, value, hashMapOf(
Pair("size", int(value.size)),
Pair("keys", list(value.keys.toList())),
Pair("values", list(value.values.toList()))
)
)
}
fun note(value: Note): Value {
return Value(
DataType.NOTE, value, hashMapOf(
Pair("pitch", string(value.pitch.toString())),
Pair("octave", int(value.octave)),
Pair("duration", int(value.duration)),
Pair("dot", bool(value.dot))
)
)
}
fun bool(value: Boolean): Value {
return Value(DataType.BOOL, value)
}
fun type(value: DataType): Value {
return Value(DataType.TYPE, value)
}
fun void(): Value {
return Value(DataType.VOID, null)
}
}
}

View File

@@ -0,0 +1,130 @@
package io.smnp.type.module
import io.smnp.callable.function.Function
import io.smnp.callable.method.Method
import io.smnp.type.model.Value
class Module(
val name: String,
functions: List<Function> = emptyList(),
methods: List<Method> = emptyList(),
children: List<Module> = emptyList()
) {
private var parent: Module? = null
private val children = mutableListOf<Module>()
private val functions = functions.toMutableList()
private val methods = methods.toMutableList()
init {
children.forEach { addSubmodule(it) }
}
fun addSubmodule(module: Module) {
module.parent = this
children.indexOfFirst { it.name == module.name }.let {
if (it > -1) {
children[it] = children[it].merge(module)
} else {
children.add(module)
}
}
}
fun attachTo(module: Module) {
module.addSubmodule(this)
}
val canonicalName: String
get() {
val modules = mutableListOf(this)
var par = parent
while(par != null) {
modules.add(par)
par = par.parent
}
return modules.reversed().joinToString(".") { it.name }
}
fun merge(module: Module): Module {
if(name != module.name) {
return this
}
val functions = functions + module.functions
val methods = methods + module.methods
val commonAndMyChildren = children.map { child ->
module.children.find { it.name == child.name }?.merge(child) ?: child
}
val moduleChildren = module.children.filter { child -> children.none { it.name == child.name } }
return Module(
name,
functions,
methods,
(commonAndMyChildren + moduleChildren).toMutableList()
)
}
fun findFunction(name: String): List<Function> {
return functions.filter { it.name == name } + children.flatMap { it.findFunction(name) }
}
fun findMethod(value: Value, name: String): List<Method> {
return methods.filter { it.name == name && it.verifyType(value) } + children.flatMap { it.findMethod(value, name) }
}
override fun toString() = name
fun pretty(printContent: Boolean = false, prefix: String = "", last: Boolean = true, first: Boolean = true) {
var newPrefix = prefix
var newLast = last
println(newPrefix + (if (first) "" else if (newLast) "└─ " else "├─ ") + name)
newPrefix += if (newLast) " " else ""
if(printContent) {
val contentPrefix = newPrefix + if (children.isNotEmpty()) "|" else ""
for ((index, function) in functions.withIndex()) {
println(contentPrefix + (if (index == functions.size - 1 && methods.isEmpty()) "" else "") + "${function.name}()")
}
for ((index, method) in methods.withIndex()) {
println(contentPrefix + (if (index == methods.size - 1) "" else "") + "${method.typeMatcher}.${method.name}()")
}
}
for ((index, child) in children.withIndex()) {
newLast = index == children.size - 1
child.pretty(printContent, newPrefix, newLast, false)
}
}
companion object {
fun create(
path: String,
functions: List<Function> = emptyList(),
methods: List<Method> = emptyList(),
children: List<Module> = emptyList()
): Module {
val modules = path.split(".")
if(modules.isEmpty()) {
return Module(path, functions, methods, children)
}
val root = modules.map { Module(it) }.reduceRight { m, n -> m.addSubmodule(n); m }
var youngest = root
while(youngest.children.isNotEmpty()) {
youngest = youngest.children[0]
}
youngest.functions.addAll(functions)
youngest.methods.addAll(methods)
youngest.children.addAll(children)
return root
}
}
}