Compare commits

...

3 Commits

7 changed files with 97 additions and 57 deletions

View File

@ -5,7 +5,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
group = "net.sergeych" group = "net.sergeych"
version = "0.3.0-SNAPSHOT" version = "0.3.1-SNAPSHOT"
buildscript { buildscript {
repositories { repositories {

View File

@ -23,7 +23,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
*/ */
suspend fun assignToContext( suspend fun assignToContext(
context: Context, context: Context,
_fromArgs: Arguments = context.args, arguments: Arguments = context.args,
defaultAccessType: AccessType = AccessType.Var, defaultAccessType: AccessType = AccessType.Var,
defaultVisibility: Visibility = Visibility.Public defaultVisibility: Visibility = Visibility.Public
) { ) {
@ -33,15 +33,25 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
} }
// will be used with last lambda arg fix // will be used with last lambda arg fix
val fromArgs = _fromArgs val callArgs: List<Obj>
val paramsSize: Int
if( arguments.tailBlockMode ) {
paramsSize = params.size - 1
assign(params.last(), arguments.list.last())
callArgs = arguments.list.dropLast(1)
} else {
paramsSize = params.size
callArgs = arguments.list
}
suspend fun processHead(index: Int): Int { suspend fun processHead(index: Int): Int {
var i = index var i = index
while (i != params.size) { while (i != paramsSize) {
val a = params[i] val a = params[i]
if (a.isEllipsis) break if (a.isEllipsis) break
val value = when { val value = when {
i < fromArgs.size -> fromArgs[i] i < callArgs.size -> callArgs[i]
a.defaultValue != null -> a.defaultValue.execute(context) a.defaultValue != null -> a.defaultValue.execute(context)
else -> context.raiseArgumentError("too few arguments for the call") else -> context.raiseArgumentError("too few arguments for the call")
} }
@ -52,14 +62,14 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
} }
suspend fun processTail(index: Int): Int { suspend fun processTail(index: Int): Int {
var i = params.size - 1 var i = paramsSize - 1
var j = fromArgs.size - 1 var j = callArgs.size - 1
while (i > index) { while (i > index) {
val a = params[i] val a = params[i]
if (a.isEllipsis) break if (a.isEllipsis) break
val value = when { val value = when {
j >= index -> { j >= index -> {
fromArgs[j--] callArgs[j--]
} }
a.defaultValue != null -> a.defaultValue.execute(context) a.defaultValue != null -> a.defaultValue.execute(context)
@ -74,16 +84,16 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
fun processEllipsis(index: Int, toFromIndex: Int) { fun processEllipsis(index: Int, toFromIndex: Int) {
val a = params[index] val a = params[index]
val l = if (index > toFromIndex) ObjList() val l = if (index > toFromIndex) ObjList()
else ObjList(fromArgs.values.subList(index, toFromIndex + 1).toMutableList()) else ObjList(callArgs.subList(index, toFromIndex + 1).toMutableList())
assign(a, l) assign(a, l)
} }
val leftIndex = processHead(0) val leftIndex = processHead(0)
if (leftIndex < params.size) { if (leftIndex < paramsSize) {
val end = processTail(leftIndex) val end = processTail(leftIndex)
processEllipsis(leftIndex, end) processEllipsis(leftIndex, end)
} else { } else {
if (leftIndex < fromArgs.size) if (leftIndex < callArgs.size)
context.raiseArgumentError("too many arguments for the call") context.raiseArgumentError("too many arguments for the call")
} }
} }

View File

@ -2,53 +2,40 @@ package net.sergeych.lyng
data class ParsedArgument(val value: Statement, val pos: Pos, val isSplat: Boolean = false) data class ParsedArgument(val value: Statement, val pos: Pos, val isSplat: Boolean = false)
suspend fun Collection<ParsedArgument>.toArguments(context: Context): Arguments { suspend fun Collection<ParsedArgument>.toArguments(context: Context,tailBlockMode: Boolean): Arguments {
val list = mutableListOf<Arguments.Info>() val list = mutableListOf<Obj>()
for (x in this) { for (x in this) {
val value = x.value.execute(context) val value = x.value.execute(context)
if (x.isSplat) { if (x.isSplat) {
when { when {
value is ObjList -> { value is ObjList -> {
for (subitem in value.list) list.add(Arguments.Info(subitem, x.pos)) for (subitem in value.list) list.add(subitem)
} }
value.isInstanceOf(ObjIterable) -> { value.isInstanceOf(ObjIterable) -> {
val i = (value.invokeInstanceMethod(context, "toList") as ObjList).list val i = (value.invokeInstanceMethod(context, "toList") as ObjList).list
i.forEach { list.add(Arguments.Info(it, x.pos)) } i.forEach { list.add(it) }
} }
else -> context.raiseClassCastError("expected list of objects for splat argument") else -> context.raiseClassCastError("expected list of objects for splat argument")
} }
} else } else
list.add(Arguments.Info(value, x.pos)) list.add(value)
} }
return Arguments(list) return Arguments(list,tailBlockMode)
} }
data class Arguments(val list: List<Info>) : Iterable<Obj> { data class Arguments(val list: List<Obj>,val tailBlockMode: Boolean = false) : List<Obj> by list {
data class Info(val value: Obj, val pos: Pos) fun firstAndOnly(pos: Pos = Pos.UNKNOWN): Obj {
if (list.size != 1) throw ScriptError(pos, "expected one argument, got ${list.size}")
val size by list::size return list.first()
operator fun get(index: Int): Obj = list[index].value
val values: List<Obj> by lazy { list.map { it.value } }
fun firstAndOnly(): Obj {
if (list.size != 1) throw IllegalArgumentException("Expected one argument, got ${list.size}")
return list.first().value
} }
companion object { companion object {
val EMPTY = Arguments(emptyList()) val EMPTY = Arguments(emptyList())
fun from(values: Collection<Obj>) = Arguments(values.map { Info(it, Pos.UNKNOWN) }) fun from(values: Collection<Obj>) = Arguments(values.toList())
} }
override fun iterator(): Iterator<Obj> {
return list.map { it.value }.iterator()
}
} }

View File

@ -149,7 +149,7 @@ class Compiler(
v.invokeInstanceMethod( v.invokeInstanceMethod(
context, context,
next.value, next.value,
args.toArguments(context) args.toArguments(context,false)
), isMutable = false ), isMutable = false
) )
} }
@ -157,7 +157,7 @@ class Compiler(
Token.Type.LBRACE -> { Token.Type.LBRACE -> {
// single lambda arg, like assertTrows { ... } // single lambda arg, like assertTrows { ... }
cc.next() cc.next()
isCall = true isCall = true
val lambda = val lambda =
parseExpression(cc) ?: throw ScriptError(t.pos, "expected valid lambda here") parseExpression(cc) ?: throw ScriptError(t.pos, "expected valid lambda here")
@ -170,7 +170,7 @@ class Compiler(
v.invokeInstanceMethod( v.invokeInstanceMethod(
context, context,
next.value, next.value,
Arguments(listOf(Arguments.Info(lambda, t.pos))) Arguments(listOf(lambda),true)
), isMutable = false ), isMutable = false
) )
} }
@ -379,7 +379,7 @@ class Compiler(
val context = closure!!.copy(pos, args) val context = closure!!.copy(pos, args)
if (argsDeclaration == null) { if (argsDeclaration == null) {
// no args: automatic var 'it' // no args: automatic var 'it'
val l = args.values val l = args.list
val itValue: Obj = when (l.size) { val itValue: Obj = when (l.size) {
// no args: it == void // no args: it == void
0 -> ObjVoid 0 -> ObjVoid
@ -611,7 +611,7 @@ class Compiler(
v.value.callOn( v.value.callOn(
context.copy( context.copy(
context.pos, context.pos,
args.toArguments(context) args.toArguments(context, blockArgument)
// Arguments( // Arguments(
// args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) } // args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) }
// ), // ),

View File

@ -41,8 +41,8 @@ class Context(
inline fun <reified T : Obj> requiredArg(index: Int): T { inline fun <reified T : Obj> requiredArg(index: Int): T {
if (args.list.size <= index) raiseError("Expected at least ${index + 1} argument, got ${args.list.size}") if (args.list.size <= index) raiseError("Expected at least ${index + 1} argument, got ${args.list.size}")
return (args.list[index].value as? T) return (args.list[index] as? T)
?: raiseClassCastError("Expected type ${T::class.simpleName}, got ${args.list[index].value::class.simpleName}") ?: raiseClassCastError("Expected type ${T::class.simpleName}, got ${args.list[index]::class.simpleName}")
} }
inline fun <reified T : Obj> requireOnlyArg(): T { inline fun <reified T : Obj> requireOnlyArg(): T {
@ -80,7 +80,7 @@ class Context(
value: Obj, value: Obj,
visibility: Visibility = Visibility.Public visibility: Visibility = Visibility.Public
): ObjRecord { ): ObjRecord {
return ObjRecord(value, isMutable, visibility).also { objects.put(name, it) } return ObjRecord(value, isMutable, visibility).also { objects[name] = it }
} }
fun getOrCreateNamespace(name: String): ObjClass { fun getOrCreateNamespace(name: String): ObjClass {

View File

@ -61,7 +61,7 @@ open class Obj {
fun isInstanceOf(someClass: Obj) = someClass === objClass || objClass.allParentsSet.contains(someClass) fun isInstanceOf(someClass: Obj) = someClass === objClass || objClass.allParentsSet.contains(someClass)
suspend fun invokeInstanceMethod(context: Context, name: String, vararg args: Obj): Obj = suspend fun invokeInstanceMethod(context: Context, name: String, vararg args: Obj): Obj =
invokeInstanceMethod(context, name, Arguments(args.map { Arguments.Info(it, context.pos) })) invokeInstanceMethod(context, name, Arguments(args.toList()))
inline suspend fun <reified T : Obj> callMethod( inline suspend fun <reified T : Obj> callMethod(
context: Context, context: Context,
@ -218,7 +218,7 @@ open class Obj {
callOn( callOn(
context.copy( context.copy(
context.pos, context.pos,
args = Arguments(args.map { Arguments.Info(it, context.pos) }), args = Arguments(args.toList()),
newThisObj = thisObj newThisObj = thisObj
) )
) )

View File

@ -1585,16 +1585,59 @@ class ScriptTest {
assertEquals("1.0E-6", eval("1e-6").toString()) assertEquals("1.0E-6", eval("1e-6").toString())
} }
// @Test @Test
// fun testLambdaLastArgAfterDetault() = runTest { fun testCallLastBlockAfterDetault() = runTest {
// val c = Context() eval("""
// eval(""" // this means last is lambda:
// // this means last is lambda: fun f(e=1, f) {
// fun f(e=1, f) { "e="+e+"f="+f()
// "e="+e+"f="+f() }
// } assertEquals("e=1f=xx", f { "xx" })
// assertEquals("e=1f=xx", f { "xx" }) """.trimIndent())
// """.trimIndent())
// }
// }
@Test
fun testCallLastBlockWithEllipsis() = runTest {
eval("""
// this means last is lambda:
fun f(e..., f) {
"e="+e+"f="+f()
}
assertEquals("e=[]f=xx", f { "xx" })
assertEquals("e=[1, 2]f=xx", f(1,2) { "xx" })
""".trimIndent())
}
@Test
fun testMethodCallLastBlockAfterDefault() = runTest {
eval("""
class Foo {
// this means last is lambda:
fun f(e=1, f) {
"e="+e+"f="+f()
}
}
val f = Foo()
assertEquals("e=1f=xx", f.f { "xx" })
""".trimIndent())
}
@Test
fun testMethodCallLastBlockWithEllipsis() = runTest {
eval("""
class Foo {
// this means last is lambda:
fun f(e..., f) {
"e="+e+"f="+f()
}
}
val f = Foo()
assertEquals("e=[]f=xx", f.f { "xx" })
assertEquals("e=[1, 2]f=xx", f.f(1,2) { "xx" })
""".trimIndent())
}
} }