Refactor code (rename allTypes matcher to anyType) and create unwrap*() methods which converts wrapped with Value values to raw Kotlin's objects

This commit is contained in:
2020-03-14 14:10:31 +01:00
parent 5b03f55cd4
commit d3f6138a8b
8 changed files with 60 additions and 54 deletions

View File

@@ -104,7 +104,7 @@ class Matcher(val type: DataType?, private val matcher: (Value) -> Boolean, priv
)
}
fun allTypes(): Matcher {
fun anyType(): Matcher {
return Matcher(
null,
{ it.type != DataType.VOID },

View File

@@ -16,6 +16,22 @@ data class Value(val type: DataType, val value: Any, val properties: Map<String,
val typeName: String
get() = type.toString()
fun unwrapCollections(): Any {
return when(type) {
DataType.LIST -> (value as List<Value>).map { it.unwrapCollections() }
DataType.MAP -> (value as Map<Value, Value>).map { (k, v) -> k.unwrapCollections() to v.unwrapCollections() }.toMap()
else -> this
}
}
fun unwrap(): Any {
return when(type) {
DataType.LIST -> (value as List<Value>).map { it.unwrap() }
DataType.MAP -> (value as Map<Value, Value>).map { (k, v) -> k.unwrap() to v.unwrap() }.toMap()
else -> value
}
}
override fun toString(): String {
return "$type($value)"
}

View File

@@ -6,7 +6,7 @@ import io.smnp.error.InvalidSignatureException
import io.smnp.error.ShouldNeverReachThisLineException
import io.smnp.type.enumeration.DataType
import io.smnp.type.matcher.Matcher
import io.smnp.type.matcher.Matcher.Companion.allTypes
import io.smnp.type.matcher.Matcher.Companion.anyType
import io.smnp.type.matcher.Matcher.Companion.listOfMatchers
import io.smnp.type.matcher.Matcher.Companion.mapOfMatchers
import io.smnp.type.matcher.Matcher.Companion.ofType
@@ -78,7 +78,7 @@ object FunctionSignatureParser {
private fun matcherForUnionTypeNode(unionTypeNode: UnionTypeNode): Matcher {
if (unionTypeNode.items.isEmpty()) {
return allTypes()
return anyType()
}
if (unionTypeNode.items.size == 1) {
@@ -115,7 +115,7 @@ object FunctionSignatureParser {
val types = mutableListOf<Matcher>()
if (listSpecifierNode.items.isEmpty()) {
types.add(allTypes())
types.add(anyType())
}
listSpecifierNode.items.forEach { types.add(
@@ -132,11 +132,11 @@ object FunctionSignatureParser {
val values = mutableListOf<Matcher>()
if (keySpecifierNode.items.isEmpty()) {
keys.add(allTypes())
keys.add(anyType())
}
if (valueSpecifierNode.items.isEmpty()) {
values.add(allTypes())
values.add(anyType())
}
keySpecifierNode.items.forEach { keys.add(

View File

@@ -3,12 +3,12 @@ package io.smnp.ext.io.function
import io.smnp.callable.function.Function
import io.smnp.callable.function.FunctionDefinitionTool
import io.smnp.callable.signature.Signature.Companion.vararg
import io.smnp.type.matcher.Matcher.Companion.allTypes
import io.smnp.type.matcher.Matcher.Companion.anyType
import io.smnp.type.model.Value
class PrintlnFunction : Function("println") {
override fun define(new: FunctionDefinitionTool) {
new function vararg(allTypes()) body { _, (vararg) ->
new function vararg(anyType()) body { _, (vararg) ->
println((vararg.value as List<Value>).joinToString("") { it.stringify() })
Value.void()
}

View File

@@ -3,12 +3,12 @@ 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.matcher.Matcher.Companion.allTypes
import io.smnp.type.matcher.Matcher.Companion.anyType
import io.smnp.type.model.Value
class TypeOfFunction : Function("typeOf") {
override fun define(new: FunctionDefinitionTool) {
new function simple(allTypes()) body { _, (obj) ->
new function simple(anyType()) body { _, (obj) ->
Value.string(obj.typeName)
}
}

View File

@@ -5,13 +5,13 @@ import io.smnp.callable.method.MethodDefinitionTool
import io.smnp.callable.signature.Signature.Companion.simple
import io.smnp.error.EvaluationException
import io.smnp.type.enumeration.DataType.MAP
import io.smnp.type.matcher.Matcher.Companion.allTypes
import io.smnp.type.matcher.Matcher.Companion.anyType
import io.smnp.type.matcher.Matcher.Companion.ofType
import io.smnp.type.model.Value
class MapAccessMethod : Method(ofType(MAP), "get") {
override fun define(new: MethodDefinitionTool) {
new method simple(allTypes()) body { _, obj, (key) ->
new method simple(anyType()) body { _, obj, (key) ->
val map = (obj.value as Map<Value, Value>)
map[key] ?: throw EvaluationException("Key '${key.value}' not found")
}

View File

@@ -7,7 +7,7 @@ import io.smnp.callable.signature.Signature.Companion.vararg
import io.smnp.error.EvaluationException
import io.smnp.ext.midi.MidiSequencer
import io.smnp.type.enumeration.DataType.*
import io.smnp.type.matcher.Matcher.Companion.allTypes
import io.smnp.type.matcher.Matcher.Companion.anyType
import io.smnp.type.matcher.Matcher.Companion.listOf
import io.smnp.type.matcher.Matcher.Companion.listOfMatchers
import io.smnp.type.matcher.Matcher.Companion.mapOfMatchers
@@ -19,39 +19,28 @@ class MidiFunction : Function("midi") {
override fun define(new: FunctionDefinitionTool) {
new function vararg(
listOf(NOTE, INT, STRING),
mapOfMatchers(ofType(INT), allTypes())
mapOfMatchers(ofType(STRING), anyType())
) body { _, (config, lines) ->
val lines = (lines.value as List<Value>).map { it.value as List<Value> }
val parameters = configParametersMap(config.value)
MidiSequencer.playLines(lines, parameters)
MidiSequencer.playLines(lines.unwrap() as List<List<Any>>, unwrapConfig(config))
Value.void()
}
new function simple(
mapOfMatchers(allTypes(), allTypes()),
mapOfMatchers(anyType(), anyType()),
mapOfMatchers(ofType(INT), listOfMatchers(listOf(NOTE, INT, STRING)))
) body { _, (config, channels) ->
val channels = (channels.value as Map<Value, Value>).map { (key, value) ->
key.value as Int to ((value.value as List<Value>).map { it.value as List<Value> })
}.toMap()
val parameters = configParametersMap(config.value)
MidiSequencer.playChannels(channels, parameters)
MidiSequencer.playChannels(channels.unwrap() as Map<Int, List<List<Any>>>, unwrapConfig(config))
Value.void()
}
}
private fun configParametersMap(config: Any): Map<String, Any> {
return (config as Map<Value, Value>)
.map { (key, value) -> key.value as String to value }
private fun unwrapConfig(config: Value): Map<String, Any> {
return (config.unwrap() as Map<String, Any>)
.map { (key, value) ->
key to when(key) {
"bpm" -> if (value.type == INT) value.value else throw EvaluationException("Invalid parameter type: 'bpm' is supposed to be of int type")
"bpm" -> value as? Int ?: throw EvaluationException("Invalid parameter type: 'bpm' is supposed to be of int type")
else -> value
}
}
.toMap()
}.toMap()
}
}

View File

@@ -3,15 +3,20 @@ package io.smnp.ext.midi
import io.smnp.data.entity.Note
import io.smnp.error.EvaluationException
import io.smnp.error.ShouldNeverReachThisLineException
import io.smnp.type.enumeration.DataType.*
import io.smnp.type.model.Value
import javax.sound.midi.*
object MidiSequencer {
private const val PPQ = 1000
private const val DEFAULT_BPM = 120
private val sequencer = MidiSystem.getSequencer()
fun playChannels(channels: Map<Int, List<List<Value>>>, config: Map<String, Any>) {
private object Command {
const val NOTE_OFF = 0x80
const val NOTE_ON = 0x90
const val PROGRAM_CHANGE = 0xC0
}
fun playChannels(channels: Map<Int, List<List<Any>>>, config: Map<String, Any>) {
val sequence = Sequence(Sequence.PPQ, PPQ)
channels.forEach { (channel, lines) ->
@@ -19,7 +24,7 @@ object MidiSequencer {
}
sequencer.sequence = sequence
sequencer.tempoInBPM = (config.getOrDefault("bpm", 120) as Int).toFloat()
sequencer.tempoInBPM = (config.getOrDefault("bpm", DEFAULT_BPM) as Int).toFloat()
sequencer.start()
@@ -27,36 +32,35 @@ object MidiSequencer {
sequencer.stop()
}
fun playLines(lines: List<List<Value>>, config: Map<String, Any>) {
fun playLines(lines: List<List<Any>>, config: Map<String, Any>) {
val sequence = Sequence(Sequence.PPQ, PPQ)
lines.forEachIndexed { channel, line -> playLine(line, channel, sequence) }
sequencer.sequence = sequence
sequencer.tempoInBPM = (config.getOrDefault("bpm", 120) as Int).toFloat()
sequencer.tempoInBPM = (config.getOrDefault("bpm", DEFAULT_BPM) as Int).toFloat()
sequencer.start()
while(sequencer.isRunning) Thread.sleep(20)
sequencer.stop()
}
private fun playLine(line: List<Value>, channel: Int, sequence: Sequence) {
private fun playLine(line: List<Any>, channel: Int, sequence: Sequence) {
val track = sequence.createTrack()
line.fold(0L) { noteOnTick, item ->
when (item.type) {
NOTE -> {
when (item) {
is Note -> {
note(item, channel, noteOnTick, track)
}
INT -> noteOnTick + 4L * PPQ / (item.value as Int)
STRING -> command(item, channel, noteOnTick, track)
is Int -> noteOnTick + 4L * PPQ / item
is String -> command(item, channel, noteOnTick, track)
else -> throw ShouldNeverReachThisLineException()
}
}
}
private fun command(item: Value, channel: Int, beginTick: Long, track: Track): Long {
val instruction = item.value as String
private fun command(instruction: String, channel: Int, beginTick: Long, track: Track): Long {
if(instruction.isBlank()) {
throw EvaluationException("Empty strings are not allowed here")
}
@@ -64,16 +68,14 @@ object MidiSequencer {
val (command, args) = if(commandWithArguments.size == 2) commandWithArguments else listOf(commandWithArguments[0], "0,0")
val arguments = args.split(",")
val cmdCode = when(command) {
"i" -> 192 + channel
"pitch" -> 0xE0
"i" -> Command.PROGRAM_CHANGE + channel
else -> throw EvaluationException("Unknown command '$command'")
}
track.add(event(cmdCode, channel, arguments.getOrNull(0)?.toInt() ?: 0, arguments.getOrNull(1)?.toInt() ?: 0, beginTick))
return beginTick
}
private fun note(item: Value, channel: Int, noteOnTick: Long, track: Track): Long {
val note = item.value as Note
private fun note(note: Note, channel: Int, noteOnTick: Long, track: Track): Long {
val noteDuration = ((if (note.dot) 1.5 else 1.0) * 4L * PPQ / note.duration).toLong()
val noteOffTick = noteOnTick + noteDuration
track.add(noteOn(note, channel, noteOnTick))
@@ -82,16 +84,15 @@ object MidiSequencer {
}
private fun noteOn(note: Note, channel: Int, tick: Long): MidiEvent {
return event(144, channel, note.intPitch() + 12, 127, tick)
return event(Command.NOTE_ON, channel, note.intPitch() + 12, 127, tick)
}
private fun noteOff(note: Note, channel: Int, tick: Long): MidiEvent {
return event(128, channel, note.intPitch() + 12, 127, tick)
return event(Command.NOTE_OFF, channel, note.intPitch() + 12, 127, tick)
}
private fun event(command: Int, channel: Int, data1: Int, data2: Int, tick: Long): MidiEvent {
val message = ShortMessage()
message.setMessage(command, channel, data1, data2)
val message = ShortMessage(command, channel, data1, data2)
return MidiEvent(message, tick)
}