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:
@@ -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 },
|
||||
|
||||
@@ -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)"
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user