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( return Matcher(
null, null,
{ it.type != DataType.VOID }, { 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 val typeName: String
get() = type.toString() 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 { override fun toString(): String {
return "$type($value)" return "$type($value)"
} }

View File

@@ -6,7 +6,7 @@ import io.smnp.error.InvalidSignatureException
import io.smnp.error.ShouldNeverReachThisLineException import io.smnp.error.ShouldNeverReachThisLineException
import io.smnp.type.enumeration.DataType import io.smnp.type.enumeration.DataType
import io.smnp.type.matcher.Matcher 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.listOfMatchers
import io.smnp.type.matcher.Matcher.Companion.mapOfMatchers import io.smnp.type.matcher.Matcher.Companion.mapOfMatchers
import io.smnp.type.matcher.Matcher.Companion.ofType import io.smnp.type.matcher.Matcher.Companion.ofType
@@ -78,7 +78,7 @@ object FunctionSignatureParser {
private fun matcherForUnionTypeNode(unionTypeNode: UnionTypeNode): Matcher { private fun matcherForUnionTypeNode(unionTypeNode: UnionTypeNode): Matcher {
if (unionTypeNode.items.isEmpty()) { if (unionTypeNode.items.isEmpty()) {
return allTypes() return anyType()
} }
if (unionTypeNode.items.size == 1) { if (unionTypeNode.items.size == 1) {
@@ -115,7 +115,7 @@ object FunctionSignatureParser {
val types = mutableListOf<Matcher>() val types = mutableListOf<Matcher>()
if (listSpecifierNode.items.isEmpty()) { if (listSpecifierNode.items.isEmpty()) {
types.add(allTypes()) types.add(anyType())
} }
listSpecifierNode.items.forEach { types.add( listSpecifierNode.items.forEach { types.add(
@@ -132,11 +132,11 @@ object FunctionSignatureParser {
val values = mutableListOf<Matcher>() val values = mutableListOf<Matcher>()
if (keySpecifierNode.items.isEmpty()) { if (keySpecifierNode.items.isEmpty()) {
keys.add(allTypes()) keys.add(anyType())
} }
if (valueSpecifierNode.items.isEmpty()) { if (valueSpecifierNode.items.isEmpty()) {
values.add(allTypes()) values.add(anyType())
} }
keySpecifierNode.items.forEach { keys.add( 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.Function
import io.smnp.callable.function.FunctionDefinitionTool import io.smnp.callable.function.FunctionDefinitionTool
import io.smnp.callable.signature.Signature.Companion.vararg 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 import io.smnp.type.model.Value
class PrintlnFunction : Function("println") { class PrintlnFunction : Function("println") {
override fun define(new: FunctionDefinitionTool) { 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() }) println((vararg.value as List<Value>).joinToString("") { it.stringify() })
Value.void() 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.Function
import io.smnp.callable.function.FunctionDefinitionTool import io.smnp.callable.function.FunctionDefinitionTool
import io.smnp.callable.signature.Signature.Companion.simple 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 import io.smnp.type.model.Value
class TypeOfFunction : Function("typeOf") { class TypeOfFunction : Function("typeOf") {
override fun define(new: FunctionDefinitionTool) { override fun define(new: FunctionDefinitionTool) {
new function simple(allTypes()) body { _, (obj) -> new function simple(anyType()) body { _, (obj) ->
Value.string(obj.typeName) 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.callable.signature.Signature.Companion.simple
import io.smnp.error.EvaluationException import io.smnp.error.EvaluationException
import io.smnp.type.enumeration.DataType.MAP 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.matcher.Matcher.Companion.ofType
import io.smnp.type.model.Value import io.smnp.type.model.Value
class MapAccessMethod : Method(ofType(MAP), "get") { class MapAccessMethod : Method(ofType(MAP), "get") {
override fun define(new: MethodDefinitionTool) { 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>) val map = (obj.value as Map<Value, Value>)
map[key] ?: throw EvaluationException("Key '${key.value}' not found") 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.error.EvaluationException
import io.smnp.ext.midi.MidiSequencer import io.smnp.ext.midi.MidiSequencer
import io.smnp.type.enumeration.DataType.* 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.listOf
import io.smnp.type.matcher.Matcher.Companion.listOfMatchers import io.smnp.type.matcher.Matcher.Companion.listOfMatchers
import io.smnp.type.matcher.Matcher.Companion.mapOfMatchers import io.smnp.type.matcher.Matcher.Companion.mapOfMatchers
@@ -19,39 +19,28 @@ class MidiFunction : Function("midi") {
override fun define(new: FunctionDefinitionTool) { override fun define(new: FunctionDefinitionTool) {
new function vararg( new function vararg(
listOf(NOTE, INT, STRING), listOf(NOTE, INT, STRING),
mapOfMatchers(ofType(INT), allTypes()) mapOfMatchers(ofType(STRING), anyType())
) body { _, (config, lines) -> ) body { _, (config, lines) ->
MidiSequencer.playLines(lines.unwrap() as List<List<Any>>, unwrapConfig(config))
val lines = (lines.value as List<Value>).map { it.value as List<Value> }
val parameters = configParametersMap(config.value)
MidiSequencer.playLines(lines, parameters)
Value.void() Value.void()
} }
new function simple( new function simple(
mapOfMatchers(allTypes(), allTypes()), mapOfMatchers(anyType(), anyType()),
mapOfMatchers(ofType(INT), listOfMatchers(listOf(NOTE, INT, STRING))) mapOfMatchers(ofType(INT), listOfMatchers(listOf(NOTE, INT, STRING)))
) body { _, (config, channels) -> ) body { _, (config, channels) ->
val channels = (channels.value as Map<Value, Value>).map { (key, value) -> MidiSequencer.playChannels(channels.unwrap() as Map<Int, List<List<Any>>>, unwrapConfig(config))
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)
Value.void() Value.void()
} }
} }
private fun configParametersMap(config: Any): Map<String, Any> { private fun unwrapConfig(config: Value): Map<String, Any> {
return (config as Map<Value, Value>) return (config.unwrap() as Map<String, Any>)
.map { (key, value) -> key.value as String to value }
.map { (key, value) -> .map { (key, value) ->
key to when (key) { 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 else -> value
} }
} }.toMap()
.toMap()
} }
} }

View File

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