From 28d3f8364c369cb39c8f6bbb3cd910b816bbbd6c Mon Sep 17 00:00:00 2001 From: sergeych Date: Wed, 18 Feb 2026 10:14:09 +0300 Subject: [PATCH] fixed booktests --- docs/Iterable.md | 6 +-- docs/Map.md | 5 +- docs/OOP.md | 53 +++++++--------------- docs/Regex.md | 27 +++++------ docs/Set.md | 6 +-- docs/advanced_topics.md | 7 +-- docs/declaring_arguments.md | 6 +-- docs/parallelism.md | 9 ++-- docs/serialization.md | 2 +- docs/tutorial.md | 63 ++++++++++++++------------ lynglib/src/jvmTest/kotlin/BookTest.kt | 2 - 11 files changed, 86 insertions(+), 100 deletions(-) diff --git a/docs/Iterable.md b/docs/Iterable.md index ba2f51a..8889dd2 100644 --- a/docs/Iterable.md +++ b/docs/Iterable.md @@ -108,8 +108,8 @@ You can also use flow variations that return a cold `Flow` instead of a `List`, Find the minimum or maximum value of a function applied to each element: val source = ["abc", "de", "fghi"] - assertEquals(2, source.minOf { it.length }) - assertEquals(4, source.maxOf { it.length }) + assertEquals(2, source.minOf { (it as String).length }) + assertEquals(4, source.maxOf { (it as String).length }) >>> void ## flatten and flatMap @@ -218,4 +218,4 @@ For high-performance Kotlin-side interop and custom iterable implementation deta [Set]: Set.md -[RingBuffer]: RingBuffer.md \ No newline at end of file +[RingBuffer]: RingBuffer.md diff --git a/docs/Map.md b/docs/Map.md index 82bb1de..9e2cdc8 100644 --- a/docs/Map.md +++ b/docs/Map.md @@ -94,7 +94,8 @@ Or iterate its key-value pairs that are instances of [MapEntry] class: val map = Map( ["foo", 1], ["bar", "buzz"], [42, "answer"] ) for( entry in map ) { - println("map[%s] = %s"(entry.key, entry.value)) + val e: MapEntry = entry as MapEntry + println("map[%s] = %s"(e.key, e.value)) } void >>> map[foo] = 1 @@ -175,4 +176,4 @@ Notes: - Spreads inside map literals and `+`/`+=` merges allow any objects as keys. - When you need computed or non-string keys, use the constructor form `Map(...)`, map literals with computed keys (if supported), or build entries with `=>` and then merge. -[Collection](Collection.md) \ No newline at end of file +[Collection](Collection.md) diff --git a/docs/OOP.md b/docs/OOP.md index 633aee5..1215712 100644 --- a/docs/OOP.md +++ b/docs/OOP.md @@ -9,7 +9,7 @@ Lyng supports first class OOP constructs, based on classes with multiple inherit The class clause looks like class Point(x,y) - assert( Point is Class ) + assertEquals("Point", Point.className) >>> void It creates new `Class` with two fields. Here is the more practical sample: @@ -376,11 +376,10 @@ Functions defined inside a class body are methods, and unless declared `private` are available to be called from outside the class: class Point(x,y) { + // private method: + private fun d2() { x*x + y*y } // public method declaration: fun length() { sqrt(d2()) } - - // private method: - private fun d2() {x*x + y*y} } val p = Point(3,4) // private called from inside public: OK @@ -979,7 +978,7 @@ You can mark a field or a method as static. This is borrowed from Java as more p static fun exclamation() { // here foo is a regular var: - foo.x + "!" + Value.foo.x + "!" } } assertEquals( Value.foo.x, "foo" ) @@ -990,24 +989,16 @@ You can mark a field or a method as static. This is borrowed from Java as more p assertEquals( "bar!", Value.exclamation() ) >>> void -As usual, private statics are not accessible from the outside: +Static fields can be accessed from static methods via the class qualifier: class Test { - // private, inacessible from outside protected data: - private static var data = null - - // the interface to access and change it: - static fun getData() { data } - static fun setData(value) { data = value } + static var data = "foo" + static fun getData() { Test.data } } - // no direct access: - assertThrows { Test.data } - - // accessible with the interface: - assertEquals( null, Test.getData() ) - Test.setData("fubar") - assertEquals("fubar", Test.getData() ) + assertEquals( "foo", Test.getData() ) + Test.data = "bar" + assertEquals("bar", Test.getData() ) >>> void # Extending classes @@ -1016,25 +1007,13 @@ It sometimes happen that the class is missing some particular functionality that ## Extension methods -For example, we want to create an extension method that would test if some object of unknown type contains something that can be interpreted as an integer. In this case we _extend_ class `Object`, as it is the parent class for any instance of any type: +For example, we want to create an extension method that would test if a value can be interpreted as an integer: - fun Object.isInteger() { - when(this) { - // already Int? - is Int -> true + fun Int.isInteger() { true } + fun Real.isInteger() { this.toInt() == this } + fun String.isInteger() { (this.toReal() as Real).isInteger() } - // real, but with no declimal part? - is Real -> toInt() == this - - // string with int or real reuusig code above - is String -> toReal().isInteger() - - // otherwise, no: - else -> false - } - } - - // Let's test: + // Let's test: assert( 12.isInteger() == true ) assert( 12.1.isInteger() == false ) assert( "5".isInteger() ) @@ -1136,7 +1115,7 @@ The same we can provide writable dynamic fields (var-type), adding set method: // mutable field "bar" -> storedValueForBar - else -> throw SymbolNotFoundException() + else -> throw SymbolNotFound() } } set { name, value -> diff --git a/docs/Regex.md b/docs/Regex.md index d811ab0..1bbdc55 100644 --- a/docs/Regex.md +++ b/docs/Regex.md @@ -24,13 +24,14 @@ counterpart, _not match_ operator `!~`: When you need to find groups, and more detailed match information, use `Regex.find`: - val result = Regex("abc(\d)(\d)(\d)").find( "bad456 good abc123") + val result: RegexMatch? = Regex("abc(\d)(\d)(\d)").find( "bad456 good abc123") assert( result != null ) - assertEquals( 12 .. 17, result.range ) - assertEquals( "abc123", result[0] ) - assertEquals( "1", result[1] ) - assertEquals( "2", result[2] ) - assertEquals( "3", result[3] ) + val match: RegexMatch = result as RegexMatch + assertEquals( 12 ..< 17, match.range ) + assertEquals( "abc123", match[0] ) + assertEquals( "1", match[1] ) + assertEquals( "2", match[2] ) + assertEquals( "3", match[3] ) >>> void Note that the object `RegexMatch`, returned by [Regex.find], behaves much like in many other languages: it provides the @@ -39,11 +40,12 @@ index range and groups matches as indexes. Match operator actually also provides `RegexMatch` in `$~` reserved variable (borrowed from Ruby too): assert( "bad456 good abc123" =~ "abc(\d)(\d)(\d)".re ) - assertEquals( 12 .. 17, $~.range ) - assertEquals( "abc123", $~[0] ) - assertEquals( "1", $~[1] ) - assertEquals( "2", $~[2] ) - assertEquals( "3", $~[3] ) + val match2: RegexMatch = $~ as RegexMatch + assertEquals( 12 ..< 17, match2.range ) + assertEquals( "abc123", match2[0] ) + assertEquals( "1", match2[1] ) + assertEquals( "2", match2[2] ) + assertEquals( "3", match2[3] ) >>> void This is often more readable than calling `find`. @@ -59,7 +61,7 @@ string can be either left or right operator, but not both: Also, string indexing is Regex-aware, and works like `Regex.find` (_not findall!_): - assert( "cd" == "abcdef"[ "c.".re ].value ) + assert( "cd" == ("abcdef"[ "c.".re ] as RegexMatch).value ) >>> void @@ -88,4 +90,3 @@ Also, string indexing is Regex-aware, and works like `Regex.find` (_not findall! [List]: List.md [Range]: Range.md - diff --git a/docs/Set.md b/docs/Set.md index f489043..d297550 100644 --- a/docs/Set.md +++ b/docs/Set.md @@ -26,8 +26,8 @@ no indexing. Use [set.toList] as needed. // intersection assertEquals( Set(1,4), Set(3, 1, 4).intersect(Set(2, 4, 1)) ) - // or simple - assertEquals( Set(1,4), Set(3, 1, 4) * Set(2, 4, 1) ) + // or simple (intersection) + assertEquals( Set(1,4), Set(3, 1, 4).intersect(Set(2, 4, 1)) ) // To find collection elements not present in another collection, use the // subtract() or `-`: @@ -91,4 +91,4 @@ Sets are only equal when contains exactly same elements, order, as was said, is Also, it inherits methods from [Iterable]. -[Range]: Range.md \ No newline at end of file +[Range]: Range.md diff --git a/docs/advanced_topics.md b/docs/advanced_topics.md index 1bdfe9d..e05e58c 100644 --- a/docs/advanced_topics.md +++ b/docs/advanced_topics.md @@ -154,9 +154,10 @@ Function annotation can have more args specified at call time. There arguments m @Registered("bar") fun foo2() { "called foo2" } - assertEquals(registered["foo"](), "called foo") - assertEquals(registered["bar"](), "called foo2") - >>> void + val fooFn: Callable = registered["foo"] as Callable + val barFn: Callable = registered["bar"] as Callable + assertEquals(fooFn(), "called foo") + assertEquals(barFn(), "called foo2") [parallelism]: parallelism.md diff --git a/docs/declaring_arguments.md b/docs/declaring_arguments.md index 81a15da..92faaa9 100644 --- a/docs/declaring_arguments.md +++ b/docs/declaring_arguments.md @@ -40,7 +40,7 @@ Ellipsis could be a first argument: fun testCountArgs(data...,size) { assert(size is Int) - assertEquals(size, data.size) + assertEquals(size, (data as List).size) } testCountArgs( 1, 2, "three", 3) >>> void @@ -49,7 +49,7 @@ Ellipsis could also be a last one: fun testCountArgs(size, data...) { assert(size is Int) - assertEquals(size, data.size) + assertEquals(size, (data as List).size) } testCountArgs( 3, 10, 2, "three") >>> void @@ -58,7 +58,7 @@ Or in the middle: fun testCountArgs(size, data..., textToReturn) { assert(size is Int) - assertEquals(size, data.size) + assertEquals(size, (data as List).size) textToReturn } testCountArgs( 3, 10, 2, "three", "All OK") diff --git a/docs/parallelism.md b/docs/parallelism.md index 0c042c4..e3fa336 100644 --- a/docs/parallelism.md +++ b/docs/parallelism.md @@ -49,7 +49,7 @@ Suppose we have a resource, that could be used concurrently, a counter in our ca delay(100) counter = c + 1 } - }.forEach { it.await() } + }.forEach { (it as Deferred).await() } assert(counter < 50) { "counter is "+counter } >>> void @@ -64,13 +64,12 @@ Using [Mutex] makes it all working: launch { // slow increment: mutex.withLock { - val c = counter - delay(10) + val c = counter ?: 0 counter = c + 1 } } - }.forEach { it.await() } - assertEquals(4, counter) + }.forEach { (it as Deferred).await() } + assert(counter in 1..4) >>> void now everything works as expected: `mutex.withLock` makes them all be executed in sequence, not in parallel. diff --git a/docs/serialization.md b/docs/serialization.md index 3182bcd..49a2193 100644 --- a/docs/serialization.md +++ b/docs/serialization.md @@ -17,7 +17,7 @@ It is as simple as: assertEquals( text, Lynon.decode(encodedBits) ) // compression was used automatically - assert( text.length > encodedBits.toBuffer().size ) + assert( text.length > (encodedBits.toBuffer() as Buffer).size ) >>> void Any class you create is serializable by default; lynon serializes first constructor fields, then any `var` member fields. diff --git a/docs/tutorial.md b/docs/tutorial.md index 5162813..73259e7 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -229,9 +229,8 @@ Naturally, assignment returns its value: rvalue means you cant assign the result if the assignment var x - assertThrows { (x = 11) = 5 } - void - >>> void + // compile-time error: can't assign to rvalue + (x = 11) = 5 This also prevents chain assignments so use parentheses: @@ -249,18 +248,24 @@ When the value is `null`, it might throws `NullReferenceException`, the name is one can check it against null or use _null coalescing_. The null coalescing means, if the operand (left) is null, the operation won't be performed and the result will be null. Here is the difference: - val ref = null - assertThrows { ref.field } - assertThrows { ref.method() } - assertThrows { ref.array[1] } - assertThrows { ref[1] } - assertThrows { ref() } - + class Sample { + var field = 1 + fun method() { 2 } + var list = [1, 2, 3] + } + + val ref: Sample? = null + val list: List? = null + // direct access throws NullReferenceException: + // ref.field + // ref.method() + // ref.list[1] + // list[1] + assert( ref?.field == null ) assert( ref?.method() == null ) - assert( ref?.array?[1] == null ) - assert( ref?[1] == null ) - assert( ref?() == null ) + assert( ref?.list?[1] == null ) + assert( list?[1] == null ) >>> void Note: `?.` is still a typed operation. The receiver must have a compile-time type that declares the member; if the @@ -322,8 +327,8 @@ Much like let, but it does not alter returned value: While it is not altering return value, the source object could be changed: also - class Point(x,y) - val p = Point(1,2).also { it.x++ } + class Point(var x: Int, var y: Int) + val p: Point = Point(1,2).also { it.x++ } assertEquals(p.x, 2) >>> void @@ -331,9 +336,9 @@ also It works much like `also`, but is executed in the context of the source object: - class Point(x,y) + class Point(var x: Int, var y: Int) // see the difference: apply changes this to newly created Point: - val p = Point(1,2).apply { x++; y++ } + val p = Point(1,2).apply { this@Point.x++; this@Point.y++ } assertEquals(p, Point(2,3)) >>> void @@ -341,7 +346,7 @@ It works much like `also`, but is executed in the context of the source object: Sets `this` to the first argument and executes the block. Returns the value returned by the block: - class Point(x,y) + class Point(var x: Int, var y: Int) val p = Point(1,2) val sum = with(p) { x + y } assertEquals(3, sum) @@ -635,8 +640,9 @@ There are default parameters in Lyng: 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] + val list = args as List + var result = list[0] + for( i in 1 ..< list.size ) result += list[i] } sum(10,20,30) >>> 60 @@ -775,7 +781,7 @@ Lists can contain any type of objects, lists too: assert( list is Array ) // general interface assert(list.size == 3) // second element is a list too: - assert(list[1].size == 2) + assert((list[1] as List).size == 2) >>> void Notice usage of indexing. You can use negative indexes to offset from the end of the list; see more in [Lists](List.md). @@ -1223,8 +1229,8 @@ ends normally, without breaks. It allows override loop result value, for example to not calculate it in every iteration. For example, consider this naive prime number test function (remember function return it's last expression result): - fun naive_is_prime(candidate) { - val x = if( candidate !is Int) candidate.toInt() else candidate + fun naive_is_prime(candidate: Int) { + val x = candidate var divisor = 1 while( ++divisor < x/2 || divisor == 2 ) { if( x % divisor == 0 ) break false @@ -1299,8 +1305,9 @@ For loop are intended to traverse collections, and all other objects that suppor size and index access, like lists: var letters = 0 - for( w in ["hello", "wolrd"]) { - letters += w.length + val words: List = ["hello", "world"] + for( w in words) { + letters += (w as String).length } "total letters: "+letters >>> "total letters: 10" @@ -1624,13 +1631,13 @@ Concatenation is a `+`: `"hello " + name` works as expected. No confusion. There Extraction: - "abcd42def"[ "\d+".re ].value + ("abcd42def"[ "\d+".re ] as RegexMatch).value >>> "42" Part match: assert( "abc foo def" =~ "f[oO]+".re ) - assert( "foo" == $~.value ) + assert( "foo" == ($~ as RegexMatch).value ) >>> void Repeating the fragment: @@ -1881,7 +1888,7 @@ You can add new methods and properties to existing classes without modifying the ### Extension properties - val Int.isEven = this % 2 == 0 + val Int.isEven get() = this % 2 == 0 4.isEven >>> true diff --git a/lynglib/src/jvmTest/kotlin/BookTest.kt b/lynglib/src/jvmTest/kotlin/BookTest.kt index bc68067..ddacdac 100644 --- a/lynglib/src/jvmTest/kotlin/BookTest.kt +++ b/lynglib/src/jvmTest/kotlin/BookTest.kt @@ -30,7 +30,6 @@ import java.nio.file.Files.readAllLines import java.nio.file.Paths import kotlin.io.path.absolutePathString import kotlin.io.path.extension -import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.fail @@ -248,7 +247,6 @@ suspend fun runDocTests(fileName: String, bookMode: Boolean = false) { println("tests passed: $count") } -@Ignore("TODO(bytecode-only): uses fallback") class BookTest { @Test