From d3f6138a8b64fd4d07c5918eeba096ec3cbbd327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Przemys=C5=82aw=20Pluta?= Date: Sat, 14 Mar 2020 14:10:31 +0100 Subject: [PATCH] Refactor code (rename allTypes matcher to anyType) and create unwrap*() methods which converts wrapped with Value values to raw Kotlin's objects --- .../kotlin/io/smnp/type/matcher/Matcher.kt | 2 +- .../main/kotlin/io/smnp/type/model/Value.kt | 16 +++++++ .../callable/util/FunctionSignatureParser.kt | 10 ++--- .../smnp/ext/io/function/PrintlnFunction.kt | 4 +- .../smnp/ext/lang/function/TypeOfFunction.kt | 4 +- .../smnp/ext/lang/method/MapAccessMethod.kt | 4 +- .../io/smnp/ext/function/MidiFunction.kt | 31 +++++-------- .../kotlin/io/smnp/ext/midi/MidiSequencer.kt | 43 ++++++++++--------- 8 files changed, 60 insertions(+), 54 deletions(-) diff --git a/api/src/main/kotlin/io/smnp/type/matcher/Matcher.kt b/api/src/main/kotlin/io/smnp/type/matcher/Matcher.kt index c16d90b..6e0dbce 100644 --- a/api/src/main/kotlin/io/smnp/type/matcher/Matcher.kt +++ b/api/src/main/kotlin/io/smnp/type/matcher/Matcher.kt @@ -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 }, diff --git a/api/src/main/kotlin/io/smnp/type/model/Value.kt b/api/src/main/kotlin/io/smnp/type/model/Value.kt index 744f48a..e35afa1 100644 --- a/api/src/main/kotlin/io/smnp/type/model/Value.kt +++ b/api/src/main/kotlin/io/smnp/type/model/Value.kt @@ -16,6 +16,22 @@ data class Value(val type: DataType, val value: Any, val properties: Map (value as List).map { it.unwrapCollections() } + DataType.MAP -> (value as Map).map { (k, v) -> k.unwrapCollections() to v.unwrapCollections() }.toMap() + else -> this + } + } + + fun unwrap(): Any { + return when(type) { + DataType.LIST -> (value as List).map { it.unwrap() } + DataType.MAP -> (value as Map).map { (k, v) -> k.unwrap() to v.unwrap() }.toMap() + else -> value + } + } + override fun toString(): String { return "$type($value)" } diff --git a/app/src/main/kotlin/io/smnp/callable/util/FunctionSignatureParser.kt b/app/src/main/kotlin/io/smnp/callable/util/FunctionSignatureParser.kt index 8d568ba..8c2db26 100644 --- a/app/src/main/kotlin/io/smnp/callable/util/FunctionSignatureParser.kt +++ b/app/src/main/kotlin/io/smnp/callable/util/FunctionSignatureParser.kt @@ -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() if (listSpecifierNode.items.isEmpty()) { - types.add(allTypes()) + types.add(anyType()) } listSpecifierNode.items.forEach { types.add( @@ -132,11 +132,11 @@ object FunctionSignatureParser { val values = mutableListOf() 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( diff --git a/modules/io/src/main/kotlin/io/smnp/ext/io/function/PrintlnFunction.kt b/modules/io/src/main/kotlin/io/smnp/ext/io/function/PrintlnFunction.kt index a4dc5ba..081fe9c 100644 --- a/modules/io/src/main/kotlin/io/smnp/ext/io/function/PrintlnFunction.kt +++ b/modules/io/src/main/kotlin/io/smnp/ext/io/function/PrintlnFunction.kt @@ -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).joinToString("") { it.stringify() }) Value.void() } diff --git a/modules/lang/src/main/kotlin/io/smnp/ext/lang/function/TypeOfFunction.kt b/modules/lang/src/main/kotlin/io/smnp/ext/lang/function/TypeOfFunction.kt index 773e4f0..e839010 100644 --- a/modules/lang/src/main/kotlin/io/smnp/ext/lang/function/TypeOfFunction.kt +++ b/modules/lang/src/main/kotlin/io/smnp/ext/lang/function/TypeOfFunction.kt @@ -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) } } diff --git a/modules/lang/src/main/kotlin/io/smnp/ext/lang/method/MapAccessMethod.kt b/modules/lang/src/main/kotlin/io/smnp/ext/lang/method/MapAccessMethod.kt index f629ab8..369c940 100644 --- a/modules/lang/src/main/kotlin/io/smnp/ext/lang/method/MapAccessMethod.kt +++ b/modules/lang/src/main/kotlin/io/smnp/ext/lang/method/MapAccessMethod.kt @@ -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) map[key] ?: throw EvaluationException("Key '${key.value}' not found") } diff --git a/modules/midi/src/main/kotlin/io/smnp/ext/function/MidiFunction.kt b/modules/midi/src/main/kotlin/io/smnp/ext/function/MidiFunction.kt index 3c44123..7edcaaf 100644 --- a/modules/midi/src/main/kotlin/io/smnp/ext/function/MidiFunction.kt +++ b/modules/midi/src/main/kotlin/io/smnp/ext/function/MidiFunction.kt @@ -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).map { it.value as List } - val parameters = configParametersMap(config.value) - MidiSequencer.playLines(lines, parameters) + MidiSequencer.playLines(lines.unwrap() as List>, 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).map { (key, value) -> - key.value as Int to ((value.value as List).map { it.value as List }) - }.toMap() - - val parameters = configParametersMap(config.value) - MidiSequencer.playChannels(channels, parameters) - + MidiSequencer.playChannels(channels.unwrap() as Map>>, unwrapConfig(config)) Value.void() } } - private fun configParametersMap(config: Any): Map { - return (config as Map) - .map { (key, value) -> key.value as String to value } + private fun unwrapConfig(config: Value): Map { + return (config.unwrap() as Map) .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") + key to when(key) { + "bpm" -> value as? Int ?: throw EvaluationException("Invalid parameter type: 'bpm' is supposed to be of int type") else -> value } - } - .toMap() + }.toMap() } } \ No newline at end of file diff --git a/modules/midi/src/main/kotlin/io/smnp/ext/midi/MidiSequencer.kt b/modules/midi/src/main/kotlin/io/smnp/ext/midi/MidiSequencer.kt index cc589b9..bd543d0 100644 --- a/modules/midi/src/main/kotlin/io/smnp/ext/midi/MidiSequencer.kt +++ b/modules/midi/src/main/kotlin/io/smnp/ext/midi/MidiSequencer.kt @@ -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>>, config: Map) { + private object Command { + const val NOTE_OFF = 0x80 + const val NOTE_ON = 0x90 + const val PROGRAM_CHANGE = 0xC0 + } + + fun playChannels(channels: Map>>, config: Map) { 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>, config: Map) { + fun playLines(lines: List>, config: Map) { 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, channel: Int, sequence: Sequence) { + private fun playLine(line: List, 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) }