From 5591a66af3d9be7bef877611079d2b674fea1c19 Mon Sep 17 00:00:00 2001 From: sergeych Date: Sun, 8 Jun 2025 14:39:39 +0400 Subject: [PATCH] more dics & minor bugs fixed --- docs/declaring_arguments.md | 76 +++++++++++++++++++ docs/tutorial.md | 13 ++++ .../net/sergeych/lyng/ArgsDeclaration.kt | 14 +++- .../kotlin/net/sergeych/lyng/Script.kt | 14 ++++ library/src/jvmTest/kotlin/BookTest.kt | 7 +- 5 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 docs/declaring_arguments.md diff --git a/docs/declaring_arguments.md b/docs/declaring_arguments.md new file mode 100644 index 0000000..911b44c --- /dev/null +++ b/docs/declaring_arguments.md @@ -0,0 +1,76 @@ +# Declaring arguments in Lyng + +It is a common thing that occurs in many places in Lyng, function declarations, +lambdas, struct and class declarations. + +## Regular + +## default values + +Default parameters should not be mixed with mandatory ones: + + // ok: + fun validFun(a, b, c=0, d=1) {} + + // this is a compilration error + fun invalidFun(a, b=1, c) {} // throw error + +Valid examples: + + fun foo(bar, baz="buz", end= -1) { + println(bar + ' ' + baz + ' ' + end) + } + foo("bar") + foo("nobar", "buzz") + foo("nobar", "buzz", 120) + >>> bar buz -1 + >>> nobar buzz -1 + >>> nobar buzz 120 + >>> void + +# Ellipsis + +Ellipsis are used to declare variadic arguments. It basically means "all the arguments available here". It means, ellipsis argument could be in any part of the list, being, end or middle, but there could be only one ellipsis argument and it must not have default value, its default value is always `[]`, en empty list. + +Ellipsis argument receives what is left from arguments after processing regular one that could be before or after. + +Ellipsis could be a first argument: + + fun testCountArgs(data...,size) { + assert(size is Int) + assertEquals(size, data.size) + } + testCountArgs( 1, 2, "three", 3) + >>> void + +Ellipsis could also be a last one: + + fun testCountArgs(size, data...) { + assert(size is Int) + assertEquals(size, data.size) + } + testCountArgs( 3, 10, 2, "three") + >>> void + +Or in the middle: + + fun testCountArgs(size, data..., textToReturn) { + assert(size is Int) + assertEquals(size, data.size) + textToReturn + } + testCountArgs( 3, 10, 2, "three", "All OK") + >>> "All OK" + +## Destructuring with splats + +When combined with splat arguments discussed in the [tutorial] it could be used to effectively +destructuring arrays when calling functions and lambdas: + + fun getFirstAndLast(first, args..., last) { + [ first, last ] + } + getFirstAndLast( ...(1..10).toList() ) + >>> [1, 10] + +[tutorial]: tutorial.md diff --git a/docs/tutorial.md b/docs/tutorial.md index 951f3c5..2c523ee 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -238,6 +238,8 @@ but Lyng syntax requires using the __lambda syntax__ to create such. See lambdas section below. +## Declaring arguments + There are default parameters in Lyng: fn check(amount, prefix = "answer: ") { @@ -250,6 +252,17 @@ There are default parameters in Lyng: check(120) >>> "answer: enough" +It is possible to define also vararg using ellipsis: + + fun sum(args...) { + var result = args[0] + for( i in 1 ..< args.size ) result += args[i] + } + sum(10,20,30) + >>> 60 + +See the [arguments reference](declaring_arguments.md) for more details. + ## Closures Each __block has an isolated context that can be accessed from closures__. For example: diff --git a/library/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt b/library/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt index 215b751..3d6a739 100644 --- a/library/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt +++ b/library/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt @@ -8,6 +8,13 @@ data class ArgsDeclaration(val args: List, val endTokenType: Token.Type) { init { val i = args.count { it.isEllipsis } if (i > 1) throw ScriptError(args[i].pos, "there can be only one argument") + val start = args.indexOfFirst { it.defaultValue != null } + if (start >= 0) + for (j in start + 1 until args.size) + if (args[j].defaultValue == null) throw ScriptError( + args[j].pos, + "required argument can't follow default one" + ) } /** @@ -61,7 +68,7 @@ data class ArgsDeclaration(val args: List, val endTokenType: Token.Type) { fun processEllipsis(index: Int, toFromIndex: Int) { val a = args[index] val l = if (index > toFromIndex) ObjList() - else ObjList( fromArgs.values.subList(index, toFromIndex+1).toMutableList()) + else ObjList(fromArgs.values.subList(index, toFromIndex + 1).toMutableList()) assign(a, l) } @@ -69,9 +76,8 @@ data class ArgsDeclaration(val args: List, val endTokenType: Token.Type) { if (leftIndex < args.size) { val end = processTail(leftIndex) processEllipsis(leftIndex, end) - } - else { - if( leftIndex < fromArgs.size) + } else { + if (leftIndex < fromArgs.size) context.raiseArgumentError("too many arguments for the call") } } diff --git a/library/src/commonMain/kotlin/net/sergeych/lyng/Script.kt b/library/src/commonMain/kotlin/net/sergeych/lyng/Script.kt index 47b85ca..3646cf1 100644 --- a/library/src/commonMain/kotlin/net/sergeych/lyng/Script.kt +++ b/library/src/commonMain/kotlin/net/sergeych/lyng/Script.kt @@ -125,6 +125,20 @@ class Script( if( a.compareTo(this, b) != 0 ) raiseError(ObjAssertionError(this,"Assertion failed: ${a.inspect()} == ${b.inspect()}")) } + addFn("assertThrows") { + val code = requireOnlyArg() + val result =try { + code.execute(this) + null + } + catch( e: ExecutionError ) { + e.errorObject + } + catch (e: ScriptError) { + ObjNull + } + result ?: raiseError(ObjAssertionError(this,"Expected exception but nothing was thrown")) + } addVoidFn("delay") { delay((this.args.firstAndOnly().toDouble()/1000.0).roundToLong()) diff --git a/library/src/jvmTest/kotlin/BookTest.kt b/library/src/jvmTest/kotlin/BookTest.kt index ab4a8d5..efe0af2 100644 --- a/library/src/jvmTest/kotlin/BookTest.kt +++ b/library/src/jvmTest/kotlin/BookTest.kt @@ -195,7 +195,7 @@ suspend fun DocTest.test(context: Context = Context()) { System.err.println("\nfailed: ${this.detailedString}") } error?.let { - fail("test failed", it) + fail(it.message, it) } assertEquals(expectedOutput, collectedOutput.toString(), "script output do not match") assertEquals(expectedResult, result.toString(), "script result does not match") @@ -263,4 +263,9 @@ class BookTest { } } } + + @Test + fun testArgumentBooks() = runTest { + runDocTests("../docs/declaring_arguments.md") + } } \ No newline at end of file