From 4d95def38582867d088897ace2d4bd496c0b5fbc Mon Sep 17 00:00:00 2001 From: gilmore606 Date: Fri, 11 Jul 2025 10:19:22 -0700 Subject: [PATCH 1/6] implement list ops --- src/main/kotlin/compiler/parser/Parser.kt | 11 +++--- src/main/kotlin/server/mcp/Task.kt | 22 ++++++++--- src/main/kotlin/value/VList.kt | 45 ++++++++++++++++++++++- src/main/kotlin/vm/VM.kt | 11 ++++-- src/test/kotlin/LangTest.kt | 34 +++++++++++++++++ 5 files changed, 107 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/compiler/parser/Parser.kt b/src/main/kotlin/compiler/parser/Parser.kt index 89f54f0..24ebbb2 100644 --- a/src/main/kotlin/compiler/parser/Parser.kt +++ b/src/main/kotlin/compiler/parser/Parser.kt @@ -655,14 +655,15 @@ class Parser(inputTokens: List) { // Parse lambda: { var, var -> ... } or { ... } private fun pLambdaFun(): N_EXPR? { consume(T_BRACE_OPEN)?.also { - var done = false val args = mutableListOf() - while (!done) { - done = true + while (nextAre(T_IDENTIFIER, T_COMMA)) { + consume(T_IDENTIFIER)?.also { args.add(N_IDENTIFIER(it.string)) } + consume(T_COMMA) + } + while (nextAre(T_IDENTIFIER, T_ARROW)) { consume(T_IDENTIFIER)?.also { args.add(N_IDENTIFIER(it.string)) } - consume(T_COMMA)?.also { done = false } } - if (args.isNotEmpty()) consume(T_ARROW) ?: fail("missing arrow after function literal var declaration") + if (args.isNotEmpty()) consume(T_ARROW) ?: fail("missing arrow after function var declaration") val code = mutableListOf() while (!nextIs(T_BRACE_CLOSE)) { pStatement()?.also { code.add(it) } ?: fail("non-statement in braces") diff --git a/src/main/kotlin/server/mcp/Task.kt b/src/main/kotlin/server/mcp/Task.kt index 29edcf4..e51c56b 100644 --- a/src/main/kotlin/server/mcp/Task.kt +++ b/src/main/kotlin/server/mcp/Task.kt @@ -3,9 +3,11 @@ package com.dlfsystems.yegg.server.mcp import com.dlfsystems.yegg.server.Connection import com.dlfsystems.yegg.server.Yegg import com.dlfsystems.yegg.util.NanoID +import com.dlfsystems.yegg.util.fail import com.dlfsystems.yegg.util.systemEpoch import com.dlfsystems.yegg.value.VObj import com.dlfsystems.yegg.value.VTask +import com.dlfsystems.yegg.value.VVoid import com.dlfsystems.yegg.value.Value import com.dlfsystems.yegg.vm.* import com.dlfsystems.yegg.vm.VMException.Type.* @@ -47,8 +49,8 @@ class Task( sealed interface Result { - data object Finished: Result - @JvmInline value class Suspend(val seconds: Int): Result + data class Finished(val v: Value?): Result + data class Suspend(val seconds: Int): Result } // Execute the top stack frame. @@ -84,11 +86,10 @@ class Task( connection?.sendText(e.toString()) connection?.sendText(stackDump()) } - return Result.Finished + return Result.Finished(vReturn) } - // Add a VM to the stack to run an exe. - fun push( + private fun push( vThis: VObj, exe: Executable, args: List, @@ -115,6 +116,17 @@ class Task( ) = Task(connection, vThis, vUser).apply { push(vThis, exe, args) } + + // Make a task and execute it immediately for a return value. The task cannot suspend. + // Used by system functions to call verb code. + fun runForValue( + exe: Executable, + args: List = listOf(), + connection: Connection? = null, + ): Value = make(exe, args, connection, vUser = connection?.user?.vThis ?: Yegg.vNullObj).execute().let { + if (it is Result.Suspend) fail(E_LIMIT, "cannot suspend in verbcode called by system function") + return (it as Result.Finished).v ?: VVoid + } } } diff --git a/src/main/kotlin/value/VList.kt b/src/main/kotlin/value/VList.kt index f34556e..da3c8f9 100644 --- a/src/main/kotlin/value/VList.kt +++ b/src/main/kotlin/value/VList.kt @@ -1,6 +1,7 @@ package com.dlfsystems.yegg.value import com.dlfsystems.yegg.server.Yegg +import com.dlfsystems.yegg.server.mcp.Task import com.dlfsystems.yegg.util.fail import com.dlfsystems.yegg.vm.Context import com.dlfsystems.yegg.vm.VMException.Type.* @@ -44,8 +45,8 @@ data class VList(var v: MutableList = mutableListOf()): Value() { private fun propSorted(): VList { if (v.isEmpty()) return make(v) return make(when (v[0]) { - is VInt -> v.sortedBy { (it as VInt).v } - is VFloat -> v.sortedBy { (it as VFloat).v } + is VInt -> v.sortedBy { (it as? VInt)?.v ?: 0 } + is VFloat -> v.sortedBy { (it as? VFloat)?.v ?: 0f } else -> v.sortedBy { it.asString() } }) } @@ -109,6 +110,10 @@ data class VList(var v: MutableList = mutableListOf()): Value() { "clear" -> verbClear(args) "reverse" -> verbReverse(args) "shuffle" -> verbShuffle(args) + "first" -> verbFirst(c, args) + "filter" -> verbFilter(c, args) + "map" -> verbMap(c, args) + "sortedBy" -> verbSortedBy(c, args) else -> null } @@ -222,6 +227,42 @@ data class VList(var v: MutableList = mutableListOf()): Value() { return VVoid } + private fun verbFirst(c: Context, args: List): Value { + requireArgCount(args, 1, 1) + if (args[0] !is VFun) fail(E_TYPE, "${args[0].type} is not FUN") + v.forEach { ele -> + Task.runForValue(args[0] as VFun, listOf(ele), c.connection).also { + if (it.isTrue()) return ele + } + } + return VVoid + } + + private fun verbFilter(c: Context, args: List): Value { + requireArgCount(args, 1, 1) + if (args[0] !is VFun) fail(E_TYPE, "${args[0].type} is not FUN") + return make(v.filter { Task.runForValue(args[0] as VFun, listOf(it), c.connection).isTrue() }) + } + + private fun verbMap(c: Context, args: List): Value { + requireArgCount(args, 1, 1) + if (args[0] !is VFun) fail(E_TYPE, "${args[0].type} is not FUN") + return make(v.map { Task.runForValue(args[0] as VFun, listOf(it), c.connection) }) + } + + private fun verbSortedBy(c: Context, args: List): Value { + requireArgCount(args, 1, 1) + if (args[0] !is VFun) fail(E_TYPE, "${args[0].type} is not FUN") + if (v.isEmpty()) return make(v) + val pairs = v.map { Pair(it, Task.runForValue(args[0] as VFun, listOf(it), c.connection)) } + return make(when (pairs[0].second) { + is VInt -> pairs.sortedBy { (it.second as? VInt)?.v ?: 0 } + is VFloat -> pairs.sortedBy { (it.second as? VFloat)?.v ?: 0f } + else -> pairs.sortedBy { it.second.asString() } + }.map { it.first }) + } + + // Check bounds of passed arg as a position in this list. private fun positionArg(arg: Value): Int { if (arg.type != Type.INT) fail(E_TYPE, "invalid ${arg.type} list position") val pos = (arg as VInt).v diff --git a/src/main/kotlin/vm/VM.kt b/src/main/kotlin/vm/VM.kt index 928140e..b006be1 100644 --- a/src/main/kotlin/vm/VM.kt +++ b/src/main/kotlin/vm/VM.kt @@ -38,6 +38,9 @@ class VM( // Used by optimizer opcodes. private var dropReturnValue: Boolean = false + // Value of this execution as an expression. Returned if we have no other return value. + private var exprValue: Value = VVoid + private inline fun push(v: Value) = stack.addFirst(v) private inline fun peek() = stack.first() private inline fun pop() = stack.removeFirst() @@ -95,7 +98,7 @@ class VM( when (word.opcode) { O_DISCARD -> { - if (stack.isNotEmpty()) pop() + if (stack.isNotEmpty()) exprValue = pop() } // Value ops @@ -165,10 +168,10 @@ class VM( O_JUMP -> { val addr = next().address!! // Unresolved jump dest means end-of-code - if (addr >= 0) pc = addr else return Result.Return(VVoid) + if (addr >= 0) pc = addr else return Result.Return(exprValue) } O_RETURN -> { - if (stack.isEmpty()) return Result.Return(VVoid) + if (stack.isEmpty()) return Result.Return(exprValue) if (stack.size > 1) fail(E_SYS, "stack polluted on return! ${dumpStack()}") return Result.Return(pop()) } @@ -397,7 +400,7 @@ class VM( else -> fail(E_SYS, "unknown opcode $word") } } - return Result.Return(if (stack.isEmpty()) VVoid else pop()) + return Result.Return(if (stack.isEmpty()) exprValue else pop()) } } diff --git a/src/test/kotlin/LangTest.kt b/src/test/kotlin/LangTest.kt index 52f6b4a..f94b3bf 100644 --- a/src/test/kotlin/LangTest.kt +++ b/src/test/kotlin/LangTest.kt @@ -184,4 +184,38 @@ class LangTest: YeggTest() { """) } + @Test + fun `List filter`() = yeggTest { + runForOutput($$""" + foo = [1,5,7,12,26,31,74].filter({ it % 2 == 0 }) + notifyConn(foo) + """, """ + 12, 26, 74 + """) + } + + @Test + fun `List map`() = yeggTest { + runForOutput($$""" + foo = [1,3,5].map({ "got $it" }) + for (x in foo) notifyConn("$x") + """, """ + got 1 + got 3 + got 5 + """) + } + + @Test + fun `List sortedBy`() = yeggTest { + runForOutput($$""" + foo = ["beer", "egg", "cheese", "me"].sortedBy({ it.length }) + for (x in foo) notifyConn("$x") + """, """ + me + egg + beer + cheese + """) + } } From 0eb3f4e8e032d8ae799fcb8b2e45531ec9fc02be Mon Sep 17 00:00:00 2001 From: gilmore606 Date: Sat, 12 Jul 2025 12:50:52 -0700 Subject: [PATCH 2/6] improved test --- src/test/kotlin/LangTest.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/test/kotlin/LangTest.kt b/src/test/kotlin/LangTest.kt index f94b3bf..07628e5 100644 --- a/src/test/kotlin/LangTest.kt +++ b/src/test/kotlin/LangTest.kt @@ -196,13 +196,17 @@ class LangTest: YeggTest() { @Test fun `List map`() = yeggTest { + verb("sys", "resultOf", $$""" + [input] = args + return input * 10 + """) runForOutput($$""" - foo = [1,3,5].map({ "got $it" }) + foo = [1,3,5].map({ "got ${$sys.resultOf(it)}" }) for (x in foo) notifyConn("$x") """, """ - got 1 - got 3 - got 5 + got 10 + got 30 + got 50 """) } From 4bdee89c8253d1a6ba7ed95834f8bff20c3e59b6 Mon Sep 17 00:00:00 2001 From: gilmore606 Date: Sat, 12 Jul 2025 15:19:49 -0700 Subject: [PATCH 3/6] execute system lambda on existing task stack --- src/main/kotlin/server/mcp/Task.kt | 29 ++++++++++++++--------------- src/main/kotlin/value/VList.kt | 8 ++++---- src/main/kotlin/vm/Context.kt | 3 +++ 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/main/kotlin/server/mcp/Task.kt b/src/main/kotlin/server/mcp/Task.kt index e51c56b..061d44d 100644 --- a/src/main/kotlin/server/mcp/Task.kt +++ b/src/main/kotlin/server/mcp/Task.kt @@ -58,12 +58,11 @@ class Task( // On a Call, push a new stack frame. // On a Return, pop a stack frame, and save the return value to pass to the next iteration (the previous frame). // Continue until the stack is empty. - - fun execute(): Result { + fun execute(toDepth: Int = 0): Result { var vReturn: Value? = resumeResult resumeResult = null try { - while (stack.isNotEmpty()) { + while (stack.size > toDepth) { stack.first().execute(vReturn).also { result -> vReturn = null when (result) { @@ -89,6 +88,16 @@ class Task( return Result.Finished(vReturn) } + // Execute an exe immediately for a return value. The task cannot suspend. + // Used by system functions to call verb code. + override fun executeLambda(exe: Executable, args: List): Value { + push(vThis, exe, args) + execute(toDepth = stack.size - 1).also { result -> + if (result is Result.Suspend) fail(E_LIMIT, "cannot suspend in verb called by system") + return (result as Result.Finished).v ?: VVoid + } + } + private fun push( vThis: VObj, exe: Executable, @@ -100,12 +109,13 @@ class Task( ) } - fun pop(): VM { + private fun pop(): VM { return stack.removeFirst() } fun stackDump() = stack.joinToString(prefix = "...", separator = "\n...", postfix = "\n") + companion object { fun make( exe: Executable, @@ -116,17 +126,6 @@ class Task( ) = Task(connection, vThis, vUser).apply { push(vThis, exe, args) } - - // Make a task and execute it immediately for a return value. The task cannot suspend. - // Used by system functions to call verb code. - fun runForValue( - exe: Executable, - args: List = listOf(), - connection: Connection? = null, - ): Value = make(exe, args, connection, vUser = connection?.user?.vThis ?: Yegg.vNullObj).execute().let { - if (it is Result.Suspend) fail(E_LIMIT, "cannot suspend in verbcode called by system function") - return (it as Result.Finished).v ?: VVoid - } } } diff --git a/src/main/kotlin/value/VList.kt b/src/main/kotlin/value/VList.kt index da3c8f9..346ac58 100644 --- a/src/main/kotlin/value/VList.kt +++ b/src/main/kotlin/value/VList.kt @@ -231,7 +231,7 @@ data class VList(var v: MutableList = mutableListOf()): Value() { requireArgCount(args, 1, 1) if (args[0] !is VFun) fail(E_TYPE, "${args[0].type} is not FUN") v.forEach { ele -> - Task.runForValue(args[0] as VFun, listOf(ele), c.connection).also { + c.executeLambda(args[0] as VFun, listOf(ele)).also { if (it.isTrue()) return ele } } @@ -241,20 +241,20 @@ data class VList(var v: MutableList = mutableListOf()): Value() { private fun verbFilter(c: Context, args: List): Value { requireArgCount(args, 1, 1) if (args[0] !is VFun) fail(E_TYPE, "${args[0].type} is not FUN") - return make(v.filter { Task.runForValue(args[0] as VFun, listOf(it), c.connection).isTrue() }) + return make(v.filter { c.executeLambda(args[0] as VFun, listOf(it)).isTrue() }) } private fun verbMap(c: Context, args: List): Value { requireArgCount(args, 1, 1) if (args[0] !is VFun) fail(E_TYPE, "${args[0].type} is not FUN") - return make(v.map { Task.runForValue(args[0] as VFun, listOf(it), c.connection) }) + return make(v.map { c.executeLambda(args[0] as VFun, listOf(it)) }) } private fun verbSortedBy(c: Context, args: List): Value { requireArgCount(args, 1, 1) if (args[0] !is VFun) fail(E_TYPE, "${args[0].type} is not FUN") if (v.isEmpty()) return make(v) - val pairs = v.map { Pair(it, Task.runForValue(args[0] as VFun, listOf(it), c.connection)) } + val pairs = v.map { Pair(it, c.executeLambda(args[0] as VFun, listOf(it))) } return make(when (pairs[0].second) { is VInt -> pairs.sortedBy { (it.second as? VInt)?.v ?: 0 } is VFloat -> pairs.sortedBy { (it.second as? VFloat)?.v ?: 0f } diff --git a/src/main/kotlin/vm/Context.kt b/src/main/kotlin/vm/Context.kt index 9a48fd7..1223273 100644 --- a/src/main/kotlin/vm/Context.kt +++ b/src/main/kotlin/vm/Context.kt @@ -2,6 +2,7 @@ package com.dlfsystems.yegg.vm import com.dlfsystems.yegg.server.mcp.Task import com.dlfsystems.yegg.value.VObj +import com.dlfsystems.yegg.value.Value interface Context { @@ -15,4 +16,6 @@ interface Context { var ticksLeft: Int var callsLeft: Int + fun executeLambda(exe: Executable, args: List): Value + } From 3c045a926ea4734c032fd1190636ee4c5b7c98b2 Mon Sep 17 00:00:00 2001 From: gilmore606 Date: Sat, 12 Jul 2025 15:26:59 -0700 Subject: [PATCH 4/6] cleanup --- src/main/kotlin/server/mcp/Task.kt | 11 +++++------ src/main/kotlin/value/VList.kt | 9 ++++----- src/main/kotlin/vm/Context.kt | 2 +- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/server/mcp/Task.kt b/src/main/kotlin/server/mcp/Task.kt index 061d44d..2d39f2e 100644 --- a/src/main/kotlin/server/mcp/Task.kt +++ b/src/main/kotlin/server/mcp/Task.kt @@ -3,7 +3,6 @@ package com.dlfsystems.yegg.server.mcp import com.dlfsystems.yegg.server.Connection import com.dlfsystems.yegg.server.Yegg import com.dlfsystems.yegg.util.NanoID -import com.dlfsystems.yegg.util.fail import com.dlfsystems.yegg.util.systemEpoch import com.dlfsystems.yegg.value.VObj import com.dlfsystems.yegg.value.VTask @@ -49,7 +48,7 @@ class Task( sealed interface Result { - data class Finished(val v: Value?): Result + data class Finished(val v: Value): Result data class Suspend(val seconds: Int): Result } @@ -85,16 +84,16 @@ class Task( connection?.sendText(e.toString()) connection?.sendText(stackDump()) } - return Result.Finished(vReturn) + return Result.Finished(vReturn ?: VVoid) } // Execute an exe immediately for a return value. The task cannot suspend. - // Used by system functions to call verb code. - override fun executeLambda(exe: Executable, args: List): Value { + // Used by system functions to call verb code in an existing Task. + override fun executeForResult(exe: Executable, args: List): Value { push(vThis, exe, args) execute(toDepth = stack.size - 1).also { result -> if (result is Result.Suspend) fail(E_LIMIT, "cannot suspend in verb called by system") - return (result as Result.Finished).v ?: VVoid + return (result as Result.Finished).v } } diff --git a/src/main/kotlin/value/VList.kt b/src/main/kotlin/value/VList.kt index 346ac58..8a01452 100644 --- a/src/main/kotlin/value/VList.kt +++ b/src/main/kotlin/value/VList.kt @@ -1,7 +1,6 @@ package com.dlfsystems.yegg.value import com.dlfsystems.yegg.server.Yegg -import com.dlfsystems.yegg.server.mcp.Task import com.dlfsystems.yegg.util.fail import com.dlfsystems.yegg.vm.Context import com.dlfsystems.yegg.vm.VMException.Type.* @@ -231,7 +230,7 @@ data class VList(var v: MutableList = mutableListOf()): Value() { requireArgCount(args, 1, 1) if (args[0] !is VFun) fail(E_TYPE, "${args[0].type} is not FUN") v.forEach { ele -> - c.executeLambda(args[0] as VFun, listOf(ele)).also { + c.executeForResult(args[0] as VFun, listOf(ele)).also { if (it.isTrue()) return ele } } @@ -241,20 +240,20 @@ data class VList(var v: MutableList = mutableListOf()): Value() { private fun verbFilter(c: Context, args: List): Value { requireArgCount(args, 1, 1) if (args[0] !is VFun) fail(E_TYPE, "${args[0].type} is not FUN") - return make(v.filter { c.executeLambda(args[0] as VFun, listOf(it)).isTrue() }) + return make(v.filter { c.executeForResult(args[0] as VFun, listOf(it)).isTrue() }) } private fun verbMap(c: Context, args: List): Value { requireArgCount(args, 1, 1) if (args[0] !is VFun) fail(E_TYPE, "${args[0].type} is not FUN") - return make(v.map { c.executeLambda(args[0] as VFun, listOf(it)) }) + return make(v.map { c.executeForResult(args[0] as VFun, listOf(it)) }) } private fun verbSortedBy(c: Context, args: List): Value { requireArgCount(args, 1, 1) if (args[0] !is VFun) fail(E_TYPE, "${args[0].type} is not FUN") if (v.isEmpty()) return make(v) - val pairs = v.map { Pair(it, c.executeLambda(args[0] as VFun, listOf(it))) } + val pairs = v.map { Pair(it, c.executeForResult(args[0] as VFun, listOf(it))) } return make(when (pairs[0].second) { is VInt -> pairs.sortedBy { (it.second as? VInt)?.v ?: 0 } is VFloat -> pairs.sortedBy { (it.second as? VFloat)?.v ?: 0f } diff --git a/src/main/kotlin/vm/Context.kt b/src/main/kotlin/vm/Context.kt index 1223273..284cfac 100644 --- a/src/main/kotlin/vm/Context.kt +++ b/src/main/kotlin/vm/Context.kt @@ -16,6 +16,6 @@ interface Context { var ticksLeft: Int var callsLeft: Int - fun executeLambda(exe: Executable, args: List): Value + fun executeForResult(exe: Executable, args: List): Value } From 4f73f2090077e08784313471f3863948a18d1dea Mon Sep 17 00:00:00 2001 From: gilmore606 Date: Sun, 13 Jul 2025 09:33:43 -0700 Subject: [PATCH 5/6] change exception handling --- src/main/kotlin/server/mcp/MCP.kt | 15 +++++++++++---- src/main/kotlin/server/mcp/Task.kt | 15 ++++++++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/server/mcp/MCP.kt b/src/main/kotlin/server/mcp/MCP.kt index 8409488..1b8e6a4 100644 --- a/src/main/kotlin/server/mcp/MCP.kt +++ b/src/main/kotlin/server/mcp/MCP.kt @@ -75,10 +75,17 @@ object MCP { timeMap.remove(task.timeID) taskMap.remove(task.id) val result = task.execute() - if (result is Task.Result.Suspend) { - task.setTime(result.seconds) - timeMap[task.timeID] = task - taskMap[task.id] = task + when (result) { + is Task.Result.Suspend -> { + task.setTime(result.seconds) + timeMap[task.timeID] = task + taskMap[task.id] = task + } + is Task.Result.Failed -> { + task.connection?.sendText(result.e.toString()) + task.connection?.sendText(task.stackDump()) + } + is Task.Result.Finished -> { } } task = getNextTask() } diff --git a/src/main/kotlin/server/mcp/Task.kt b/src/main/kotlin/server/mcp/Task.kt index 2d39f2e..466b8d2 100644 --- a/src/main/kotlin/server/mcp/Task.kt +++ b/src/main/kotlin/server/mcp/Task.kt @@ -50,6 +50,7 @@ class Task( sealed interface Result { data class Finished(val v: Value): Result data class Suspend(val seconds: Int): Result + data class Failed(val e: Exception): Result } // Execute the top stack frame. @@ -80,11 +81,11 @@ class Task( } } } + val result = Result.Finished(vReturn ?: VVoid) + return result } catch (e: Exception) { - connection?.sendText(e.toString()) - connection?.sendText(stackDump()) + return Result.Failed(e) } - return Result.Finished(vReturn ?: VVoid) } // Execute an exe immediately for a return value. The task cannot suspend. @@ -92,9 +93,13 @@ class Task( override fun executeForResult(exe: Executable, args: List): Value { push(vThis, exe, args) execute(toDepth = stack.size - 1).also { result -> - if (result is Result.Suspend) fail(E_LIMIT, "cannot suspend in verb called by system") - return (result as Result.Finished).v + when (result) { + is Result.Suspend -> fail(E_LIMIT, "cannot suspend in verb called by system") + is Result.Failed -> throw result.e + is Result.Finished -> return result.v + } } + return VVoid } private fun push( From 20deb020bf82381b1ffca5833bfc08a5808a2eaa Mon Sep 17 00:00:00 2001 From: gilmore606 Date: Sun, 13 Jul 2025 11:14:53 -0700 Subject: [PATCH 6/6] add mixed sort test --- src/main/kotlin/value/VList.kt | 2 +- src/test/kotlin/LangTest.kt | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/value/VList.kt b/src/main/kotlin/value/VList.kt index 8a01452..d4f43cf 100644 --- a/src/main/kotlin/value/VList.kt +++ b/src/main/kotlin/value/VList.kt @@ -253,7 +253,7 @@ data class VList(var v: MutableList = mutableListOf()): Value() { requireArgCount(args, 1, 1) if (args[0] !is VFun) fail(E_TYPE, "${args[0].type} is not FUN") if (v.isEmpty()) return make(v) - val pairs = v.map { Pair(it, c.executeForResult(args[0] as VFun, listOf(it))) } + val pairs = v.map { it to c.executeForResult(args[0] as VFun, listOf(it)) } return make(when (pairs[0].second) { is VInt -> pairs.sortedBy { (it.second as? VInt)?.v ?: 0 } is VFloat -> pairs.sortedBy { (it.second as? VFloat)?.v ?: 0f } diff --git a/src/test/kotlin/LangTest.kt b/src/test/kotlin/LangTest.kt index 07628e5..0edea2e 100644 --- a/src/test/kotlin/LangTest.kt +++ b/src/test/kotlin/LangTest.kt @@ -184,6 +184,16 @@ class LangTest: YeggTest() { """) } + @Test + fun `Mixed list sorted by first value type`() = yeggTest { + runForOutput($$""" + foo = [36, 9, "hello", 88, 5.6] + notifyConn(foo.sorted) + """, """ + "hello", 5.6, 9, 36, 88 + """) + } + @Test fun `List filter`() = yeggTest { runForOutput($$"""