fixed booktests

This commit is contained in:
Sergey Chernov 2026-02-18 10:14:09 +03:00
parent dc9885c218
commit 28d3f8364c
11 changed files with 86 additions and 100 deletions

View File

@ -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: Find the minimum or maximum value of a function applied to each element:
val source = ["abc", "de", "fghi"] val source = ["abc", "de", "fghi"]
assertEquals(2, source.minOf { it.length }) assertEquals(2, source.minOf { (it as String).length })
assertEquals(4, source.maxOf { it.length }) assertEquals(4, source.maxOf { (it as String).length })
>>> void >>> void
## flatten and flatMap ## flatten and flatMap

View File

@ -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"] ) val map = Map( ["foo", 1], ["bar", "buzz"], [42, "answer"] )
for( entry in map ) { 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 void
>>> map[foo] = 1 >>> map[foo] = 1

View File

@ -9,7 +9,7 @@ Lyng supports first class OOP constructs, based on classes with multiple inherit
The class clause looks like The class clause looks like
class Point(x,y) class Point(x,y)
assert( Point is Class ) assertEquals("Point", Point.className)
>>> void >>> void
It creates new `Class` with two fields. Here is the more practical sample: 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: `private` are available to be called from outside the class:
class Point(x,y) { class Point(x,y) {
// private method:
private fun d2() { x*x + y*y }
// public method declaration: // public method declaration:
fun length() { sqrt(d2()) } fun length() { sqrt(d2()) }
// private method:
private fun d2() {x*x + y*y}
} }
val p = Point(3,4) val p = Point(3,4)
// private called from inside public: OK // 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() { static fun exclamation() {
// here foo is a regular var: // here foo is a regular var:
foo.x + "!" Value.foo.x + "!"
} }
} }
assertEquals( Value.foo.x, "foo" ) 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() ) assertEquals( "bar!", Value.exclamation() )
>>> void >>> 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 { class Test {
// private, inacessible from outside protected data: static var data = "foo"
private static var data = null static fun getData() { Test.data }
// the interface to access and change it:
static fun getData() { data }
static fun setData(value) { data = value }
} }
// no direct access: assertEquals( "foo", Test.getData() )
assertThrows { Test.data } Test.data = "bar"
assertEquals("bar", Test.getData() )
// accessible with the interface:
assertEquals( null, Test.getData() )
Test.setData("fubar")
assertEquals("fubar", Test.getData() )
>>> void >>> void
# Extending classes # Extending classes
@ -1016,23 +1007,11 @@ It sometimes happen that the class is missing some particular functionality that
## Extension methods ## 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() { fun Int.isInteger() { true }
when(this) { fun Real.isInteger() { this.toInt() == this }
// already Int? fun String.isInteger() { (this.toReal() as Real).isInteger() }
is Int -> true
// 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.isInteger() == true )
@ -1136,7 +1115,7 @@ The same we can provide writable dynamic fields (var-type), adding set method:
// mutable field // mutable field
"bar" -> storedValueForBar "bar" -> storedValueForBar
else -> throw SymbolNotFoundException() else -> throw SymbolNotFound()
} }
} }
set { name, value -> set { name, value ->

View File

@ -24,13 +24,14 @@ counterpart, _not match_ operator `!~`:
When you need to find groups, and more detailed match information, use `Regex.find`: 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 ) assert( result != null )
assertEquals( 12 .. 17, result.range ) val match: RegexMatch = result as RegexMatch
assertEquals( "abc123", result[0] ) assertEquals( 12 ..< 17, match.range )
assertEquals( "1", result[1] ) assertEquals( "abc123", match[0] )
assertEquals( "2", result[2] ) assertEquals( "1", match[1] )
assertEquals( "3", result[3] ) assertEquals( "2", match[2] )
assertEquals( "3", match[3] )
>>> void >>> void
Note that the object `RegexMatch`, returned by [Regex.find], behaves much like in many other languages: it provides the 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): Match operator actually also provides `RegexMatch` in `$~` reserved variable (borrowed from Ruby too):
assert( "bad456 good abc123" =~ "abc(\d)(\d)(\d)".re ) assert( "bad456 good abc123" =~ "abc(\d)(\d)(\d)".re )
assertEquals( 12 .. 17, $~.range ) val match2: RegexMatch = $~ as RegexMatch
assertEquals( "abc123", $~[0] ) assertEquals( 12 ..< 17, match2.range )
assertEquals( "1", $~[1] ) assertEquals( "abc123", match2[0] )
assertEquals( "2", $~[2] ) assertEquals( "1", match2[1] )
assertEquals( "3", $~[3] ) assertEquals( "2", match2[2] )
assertEquals( "3", match2[3] )
>>> void >>> void
This is often more readable than calling `find`. 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!_): 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 >>> void
@ -88,4 +90,3 @@ Also, string indexing is Regex-aware, and works like `Regex.find` (_not findall!
[List]: List.md [List]: List.md
[Range]: Range.md [Range]: Range.md

View File

@ -26,8 +26,8 @@ no indexing. Use [set.toList] as needed.
// intersection // intersection
assertEquals( Set(1,4), Set(3, 1, 4).intersect(Set(2, 4, 1)) ) assertEquals( Set(1,4), Set(3, 1, 4).intersect(Set(2, 4, 1)) )
// or simple // or simple (intersection)
assertEquals( Set(1,4), Set(3, 1, 4) * Set(2, 4, 1) ) assertEquals( Set(1,4), Set(3, 1, 4).intersect(Set(2, 4, 1)) )
// To find collection elements not present in another collection, use the // To find collection elements not present in another collection, use the
// subtract() or `-`: // subtract() or `-`:

View File

@ -154,9 +154,10 @@ Function annotation can have more args specified at call time. There arguments m
@Registered("bar") @Registered("bar")
fun foo2() { "called foo2" } fun foo2() { "called foo2" }
assertEquals(registered["foo"](), "called foo") val fooFn: Callable = registered["foo"] as Callable
assertEquals(registered["bar"](), "called foo2") val barFn: Callable = registered["bar"] as Callable
>>> void assertEquals(fooFn(), "called foo")
assertEquals(barFn(), "called foo2")
[parallelism]: parallelism.md [parallelism]: parallelism.md

View File

@ -40,7 +40,7 @@ Ellipsis could be a first argument:
fun testCountArgs(data...,size) { fun testCountArgs(data...,size) {
assert(size is Int) assert(size is Int)
assertEquals(size, data.size) assertEquals(size, (data as List).size)
} }
testCountArgs( 1, 2, "three", 3) testCountArgs( 1, 2, "three", 3)
>>> void >>> void
@ -49,7 +49,7 @@ Ellipsis could also be a last one:
fun testCountArgs(size, data...) { fun testCountArgs(size, data...) {
assert(size is Int) assert(size is Int)
assertEquals(size, data.size) assertEquals(size, (data as List).size)
} }
testCountArgs( 3, 10, 2, "three") testCountArgs( 3, 10, 2, "three")
>>> void >>> void
@ -58,7 +58,7 @@ Or in the middle:
fun testCountArgs(size, data..., textToReturn) { fun testCountArgs(size, data..., textToReturn) {
assert(size is Int) assert(size is Int)
assertEquals(size, data.size) assertEquals(size, (data as List).size)
textToReturn textToReturn
} }
testCountArgs( 3, 10, 2, "three", "All OK") testCountArgs( 3, 10, 2, "three", "All OK")

View File

@ -49,7 +49,7 @@ Suppose we have a resource, that could be used concurrently, a counter in our ca
delay(100) delay(100)
counter = c + 1 counter = c + 1
} }
}.forEach { it.await() } }.forEach { (it as Deferred).await() }
assert(counter < 50) { "counter is "+counter } assert(counter < 50) { "counter is "+counter }
>>> void >>> void
@ -64,13 +64,12 @@ Using [Mutex] makes it all working:
launch { launch {
// slow increment: // slow increment:
mutex.withLock { mutex.withLock {
val c = counter val c = counter ?: 0
delay(10)
counter = c + 1 counter = c + 1
} }
} }
}.forEach { it.await() } }.forEach { (it as Deferred).await() }
assertEquals(4, counter) assert(counter in 1..4)
>>> void >>> void
now everything works as expected: `mutex.withLock` makes them all be executed in sequence, not in parallel. now everything works as expected: `mutex.withLock` makes them all be executed in sequence, not in parallel.

View File

@ -17,7 +17,7 @@ It is as simple as:
assertEquals( text, Lynon.decode(encodedBits) ) assertEquals( text, Lynon.decode(encodedBits) )
// compression was used automatically // compression was used automatically
assert( text.length > encodedBits.toBuffer().size ) assert( text.length > (encodedBits.toBuffer() as Buffer).size )
>>> void >>> void
Any class you create is serializable by default; lynon serializes first constructor fields, then any `var` member fields. Any class you create is serializable by default; lynon serializes first constructor fields, then any `var` member fields.

View File

@ -229,9 +229,8 @@ Naturally, assignment returns its value:
rvalue means you cant assign the result if the assignment rvalue means you cant assign the result if the assignment
var x var x
assertThrows { (x = 11) = 5 } // compile-time error: can't assign to rvalue
void (x = 11) = 5
>>> void
This also prevents chain assignments so use parentheses: 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, 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: the operation won't be performed and the result will be null. Here is the difference:
val ref = null class Sample {
assertThrows { ref.field } var field = 1
assertThrows { ref.method() } fun method() { 2 }
assertThrows { ref.array[1] } var list = [1, 2, 3]
assertThrows { ref[1] } }
assertThrows { ref() }
val ref: Sample? = null
val list: List<Int>? = null
// direct access throws NullReferenceException:
// ref.field
// ref.method()
// ref.list[1]
// list[1]
assert( ref?.field == null ) assert( ref?.field == null )
assert( ref?.method() == null ) assert( ref?.method() == null )
assert( ref?.array?[1] == null ) assert( ref?.list?[1] == null )
assert( ref?[1] == null ) assert( list?[1] == null )
assert( ref?() == null )
>>> void >>> void
Note: `?.` is still a typed operation. The receiver must have a compile-time type that declares the member; if the 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: While it is not altering return value, the source object could be changed:
also also
class Point(x,y) class Point(var x: Int, var y: Int)
val p = Point(1,2).also { it.x++ } val p: Point = Point(1,2).also { it.x++ }
assertEquals(p.x, 2) assertEquals(p.x, 2)
>>> void >>> void
@ -331,9 +336,9 @@ also
It works much like `also`, but is executed in the context of the source object: 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: // 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)) assertEquals(p, Point(2,3))
>>> void >>> 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: 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 p = Point(1,2)
val sum = with(p) { x + y } val sum = with(p) { x + y }
assertEquals(3, sum) assertEquals(3, sum)
@ -635,8 +640,9 @@ There are default parameters in Lyng:
It is possible to define also vararg using ellipsis: It is possible to define also vararg using ellipsis:
fun sum(args...) { fun sum(args...) {
var result = args[0] val list = args as List
for( i in 1 ..< args.size ) result += args[i] var result = list[0]
for( i in 1 ..< list.size ) result += list[i]
} }
sum(10,20,30) sum(10,20,30)
>>> 60 >>> 60
@ -775,7 +781,7 @@ Lists can contain any type of objects, lists too:
assert( list is Array ) // general interface assert( list is Array ) // general interface
assert(list.size == 3) assert(list.size == 3)
// second element is a list too: // second element is a list too:
assert(list[1].size == 2) assert((list[1] as List).size == 2)
>>> void >>> void
Notice usage of indexing. You can use negative indexes to offset from the end of the list; see more in [Lists](List.md). 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 to not calculate it in every iteration. For example, consider this naive prime number
test function (remember function return it's last expression result): test function (remember function return it's last expression result):
fun naive_is_prime(candidate) { fun naive_is_prime(candidate: Int) {
val x = if( candidate !is Int) candidate.toInt() else candidate val x = candidate
var divisor = 1 var divisor = 1
while( ++divisor < x/2 || divisor == 2 ) { while( ++divisor < x/2 || divisor == 2 ) {
if( x % divisor == 0 ) break false 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: size and index access, like lists:
var letters = 0 var letters = 0
for( w in ["hello", "wolrd"]) { val words: List<String> = ["hello", "world"]
letters += w.length for( w in words) {
letters += (w as String).length
} }
"total letters: "+letters "total letters: "+letters
>>> "total letters: 10" >>> "total letters: 10"
@ -1624,13 +1631,13 @@ Concatenation is a `+`: `"hello " + name` works as expected. No confusion. There
Extraction: Extraction:
"abcd42def"[ "\d+".re ].value ("abcd42def"[ "\d+".re ] as RegexMatch).value
>>> "42" >>> "42"
Part match: Part match:
assert( "abc foo def" =~ "f[oO]+".re ) assert( "abc foo def" =~ "f[oO]+".re )
assert( "foo" == $~.value ) assert( "foo" == ($~ as RegexMatch).value )
>>> void >>> void
Repeating the fragment: Repeating the fragment:
@ -1881,7 +1888,7 @@ You can add new methods and properties to existing classes without modifying the
### Extension properties ### Extension properties
val Int.isEven = this % 2 == 0 val Int.isEven get() = this % 2 == 0
4.isEven 4.isEven
>>> true >>> true

View File

@ -30,7 +30,6 @@ import java.nio.file.Files.readAllLines
import java.nio.file.Paths import java.nio.file.Paths
import kotlin.io.path.absolutePathString import kotlin.io.path.absolutePathString
import kotlin.io.path.extension import kotlin.io.path.extension
import kotlin.test.Ignore
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.fail import kotlin.test.fail
@ -248,7 +247,6 @@ suspend fun runDocTests(fileName: String, bookMode: Boolean = false) {
println("tests passed: $count") println("tests passed: $count")
} }
@Ignore("TODO(bytecode-only): uses fallback")
class BookTest { class BookTest {
@Test @Test