more stdlib and docs, bugfixes
This commit is contained in:
		
							parent
							
								
									202e70a99a
								
							
						
					
					
						commit
						b07452e66e
					
				@ -26,36 +26,58 @@ Just remember at this stage typed declarations are not yet supported.
 | 
			
		||||
Having `Iterable` in base classes allows to use it in for loop. Also, each `Iterable` has some utility functions available, for example
 | 
			
		||||
 | 
			
		||||
    val r = 1..10  // Range is Iterable!  
 | 
			
		||||
    assertEquals( [9,10] r.takeLast(2) )
 | 
			
		||||
    assertEquals( [1,2,3] r.take(3) )
 | 
			
		||||
    assertEquals( [9,10] r.drop(8) )
 | 
			
		||||
    assertEquals( [1,2] r.dropLast(8) )
 | 
			
		||||
    assertEquals( [9,10], r.takeLast(2).toList() )
 | 
			
		||||
    assertEquals( [1,2,3], r.take(3).toList() )
 | 
			
		||||
    assertEquals( [9,10], r.drop(8).toList() )
 | 
			
		||||
    assertEquals( [1,2], r.dropLast(8).toList() )
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
## joinToString
 | 
			
		||||
 | 
			
		||||
This methods convert any iterable to a string joining string representation of each element, optionally transforming it and joining using specified suffix.
 | 
			
		||||
 | 
			
		||||
    Iterable.joinToString(suffux=' ', transform=null)
 | 
			
		||||
 | 
			
		||||
- if `Iterable` `isEmpty`, the empty string `""` is returned.
 | 
			
		||||
- `suffix` is inserted between items when there are more than one. 
 | 
			
		||||
- `transform` of specified is applied to each element, otherwise its `toString()` method is used.
 | 
			
		||||
 | 
			
		||||
Here is the sample:
 | 
			
		||||
 | 
			
		||||
    assertEquals( (1..3).joinToString(), "1 2 3")
 | 
			
		||||
    assertEquals( (1..3).joinToString(":"), "1:2:3")
 | 
			
		||||
    assertEquals( (1..3).joinToString { it * 10 }, "10 20 30")
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
## Instance methods:
 | 
			
		||||
 | 
			
		||||
| fun/method      | description                                                                     |
 | 
			
		||||
|-----------------|---------------------------------------------------------------------------------|
 | 
			
		||||
| toList()        | create a list from iterable                                                     |
 | 
			
		||||
| toSet()         | create a set from iterable                                                      |
 | 
			
		||||
| contains(i)     | check that iterable contains `i`                                                |
 | 
			
		||||
| `i in iterator` | same as `contains(i)`                                                           |
 | 
			
		||||
| isEmpty()       | check iterable is empty                                                         |
 | 
			
		||||
| forEach(f)      | call f for each element                                                         |
 | 
			
		||||
| toMap()         | create a map from list of key-value pairs (arrays of 2 items or like)           |
 | 
			
		||||
| map(f)          | create a list of values returned by `f` called for each element of the iterable |
 | 
			
		||||
| indexOf(i)      | return index if the first encounter of i or a negative value if not found       |
 | 
			
		||||
| associateBy(kf) | create a map where keys are returned by kf that will be called for each element |
 | 
			
		||||
| first           | first element (1)                                                               |
 | 
			
		||||
| last            | last element (1)                                                                |
 | 
			
		||||
| take(n)         | return [Iterable] of up to n first elements                                     |
 | 
			
		||||
| taleLast(n)     | return [Iterable] of up to n last elements                                      |
 | 
			
		||||
| drop(n)         | return new [Iterable] without first n elements                                  |
 | 
			
		||||
| dropLast(n)     | return new [Iterable] without last n elements                                   |
 | 
			
		||||
 | 
			
		||||
| fun/method        | description                                                               |
 | 
			
		||||
|-------------------|---------------------------------------------------------------------------|
 | 
			
		||||
| toList()          | create a list from iterable                                               |
 | 
			
		||||
| toSet()           | create a set from iterable                                                |
 | 
			
		||||
| contains(i)       | check that iterable contains `i`                                          |
 | 
			
		||||
| `i in iterator`   | same as `contains(i)`                                                     |
 | 
			
		||||
| isEmpty()         | check iterable is empty                                                   |
 | 
			
		||||
| forEach(f)        | call f for each element                                                   |
 | 
			
		||||
| toMap()           | create a map from list of key-value pairs (arrays of 2 items or like)     |
 | 
			
		||||
| map(f)            | create a list of values returned by `f` called for each element of the iterable |
 | 
			
		||||
| indexOf(i)        | return index if the first encounter of i or a negative value if not found |
 | 
			
		||||
| associateBy(kf)   | create a map where keys are returned by kf that will be called for each element |
 | 
			
		||||
| first             | first element (1)                                                         |
 | 
			
		||||
| last              | last element (1)                                                          |
 | 
			
		||||
| take(n)           | return [Iterable] of up to n first elements                               |
 | 
			
		||||
| taleLast(n)       | return [Iterable] of up to n last elements                                |
 | 
			
		||||
| drop(n)           | return new [Iterable] without first n elements                            |
 | 
			
		||||
| dropLast(n)       | return new [Iterable] without last n elements                             |
 | 
			
		||||
| joinToString(s,t) | convert iterable to string, see (2)                                       |
 | 
			
		||||
 | 
			
		||||
(1)
 | 
			
		||||
: throws `NoSuchElementException` if there is no such element 
 | 
			
		||||
 | 
			
		||||
(2)
 | 
			
		||||
: `joinToString(suffix=" ",transform=null)`: suffix is inserted between items if there are more than one, trasnfom is optional function applied to each item that must return result string for an item, otherwise `item.toString()` is used. 
 | 
			
		||||
 | 
			
		||||
    fun Iterable.toList(): List
 | 
			
		||||
    fun Iterable.toSet(): Set
 | 
			
		||||
    fun Iterable.indexOf(element): Int
 | 
			
		||||
 | 
			
		||||
@ -747,7 +747,7 @@ You can thest that _when expression_ is _contained_, or not contained, in some o
 | 
			
		||||
`!in container`. The container is any object that provides `contains` method, otherwise the runtime exception will be
 | 
			
		||||
thrown.
 | 
			
		||||
 | 
			
		||||
Typical builtin types that are containers (e.g. support `conain`):
 | 
			
		||||
Typical builtin types that are containers (e.g. support `contains`):
 | 
			
		||||
 | 
			
		||||
| class      | notes                                          |
 | 
			
		||||
|------------|------------------------------------------------|
 | 
			
		||||
@ -756,6 +756,8 @@ Typical builtin types that are containers (e.g. support `conain`):
 | 
			
		||||
| List       | faster than Array's                            |
 | 
			
		||||
| String     | character in string or substring in string (3) |
 | 
			
		||||
| Range      | object is included in the range (2)            |
 | 
			
		||||
| Buffer     | byte is in buffer                              |
 | 
			
		||||
| RingBuffer | object is in buffer                            |
 | 
			
		||||
 | 
			
		||||
(1)
 | 
			
		||||
: Iterable is not the container as it can be infinite
 | 
			
		||||
@ -1296,18 +1298,20 @@ if blank, will be removed too, for example:
 | 
			
		||||
 | 
			
		||||
See [math functions](math.md). Other general purpose functions are:
 | 
			
		||||
 | 
			
		||||
| name                                       | description                                               |
 | 
			
		||||
|--------------------------------------------|-----------------------------------------------------------|
 | 
			
		||||
| assert(condition,message="assertion failed") | runtime code check. There will be an option to skip them  |
 | 
			
		||||
| assertEquals(a,b)                          |                                                           |
 | 
			
		||||
| assertNotEquals(a,b)                       |                                                           |
 | 
			
		||||
| assertTrows { /* block */ }                |                                                           |
 | 
			
		||||
| check(condition, message=<default>)        | throws IllegalStateException" of condition isn't met      |
 | 
			
		||||
| require(condition, message=<default>)      | throws IllegalArgumentException" of condition isn't met   |
 | 
			
		||||
| println(args...)                           | Open for overriding, it prints to stdout with newline.    |
 | 
			
		||||
| print(args...)                             | Open for overriding, it prints to stdout without newline. |
 | 
			
		||||
| flow {}                                    | create flow sequence, see [parallelism]                   |
 | 
			
		||||
| delay, launch, yield                       | see [parallelism]                                         |
 | 
			
		||||
| name                                         | description                                                |
 | 
			
		||||
|----------------------------------------------|------------------------------------------------------------|
 | 
			
		||||
| assert(condition,message="assertion failed") | runtime code check. There will be an option to skip them   |
 | 
			
		||||
| assertEquals(a,b)                            |                                                            |
 | 
			
		||||
| assertNotEquals(a,b)                         |                                                            |
 | 
			
		||||
| assertTrows { /* block */ }                  |                                                            |
 | 
			
		||||
| check(condition, message=<default>)          | throws IllegalStateException" of condition isn't met       |
 | 
			
		||||
| require(condition, message=<default>)        | throws IllegalArgumentException" of condition isn't met    |
 | 
			
		||||
| println(args...)                             | Open for overriding, it prints to stdout with newline.     |
 | 
			
		||||
| print(args...)                               | Open for overriding, it prints to stdout without newline.  |
 | 
			
		||||
| flow {}                                      | create flow sequence, see [parallelism]                    |
 | 
			
		||||
| delay, launch, yield                         | see [parallelism]                                          |
 | 
			
		||||
| cached(builder)                              | remembers builder() on first invocation and return it then |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Built-in constants
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
 | 
			
		||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
 | 
			
		||||
 | 
			
		||||
group = "net.sergeych"
 | 
			
		||||
version = "0.8.9-SNAPSHOT"
 | 
			
		||||
version = "0.8.10-SNAPSHOT"
 | 
			
		||||
 | 
			
		||||
buildscript {
 | 
			
		||||
    repositories {
 | 
			
		||||
 | 
			
		||||
@ -87,7 +87,7 @@ class Compiler(
 | 
			
		||||
                statements += it
 | 
			
		||||
            }
 | 
			
		||||
            if (s == null) {
 | 
			
		||||
                when( t.type ) {
 | 
			
		||||
                when (t.type) {
 | 
			
		||||
                    Token.Type.RBRACE, Token.Type.EOF, Token.Type.SEMICOLON -> {}
 | 
			
		||||
                    else ->
 | 
			
		||||
                        throw ScriptError(t.pos, "unexpeced `${t.value}` here")
 | 
			
		||||
@ -114,7 +114,7 @@ class Compiler(
 | 
			
		||||
        return result.toString()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private var lastAnnotation: (suspend (Scope, ObjString,Statement) -> Statement)? = null
 | 
			
		||||
    private var lastAnnotation: (suspend (Scope, ObjString, Statement) -> Statement)? = null
 | 
			
		||||
 | 
			
		||||
    private suspend fun parseStatement(braceMeansLambda: Boolean = false): Statement? {
 | 
			
		||||
        lastAnnotation = null
 | 
			
		||||
@ -138,6 +138,7 @@ class Compiler(
 | 
			
		||||
                    lastAnnotation = parseAnnotation(t)
 | 
			
		||||
                    continue
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Token.Type.LABEL -> continue
 | 
			
		||||
                Token.Type.SINLGE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT -> continue
 | 
			
		||||
 | 
			
		||||
@ -843,7 +844,7 @@ class Compiler(
 | 
			
		||||
        return parseNumberOrNull(isPlus) ?: throw ScriptError(cc.currentPos(), "Expecting number")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun parseAnnotation(t: Token): (suspend (Scope, ObjString,Statement)->Statement) {
 | 
			
		||||
    suspend fun parseAnnotation(t: Token): (suspend (Scope, ObjString, Statement) -> Statement) {
 | 
			
		||||
        val extraArgs = parseArgsOrNull()
 | 
			
		||||
        println("annotation ${t.value}: args: $extraArgs")
 | 
			
		||||
        return { scope, name, body ->
 | 
			
		||||
@ -851,14 +852,14 @@ class Compiler(
 | 
			
		||||
            val required = listOf(name, body)
 | 
			
		||||
            val args = extras?.let { required + it } ?: required
 | 
			
		||||
            val fn = scope.get(t.value)?.value ?: scope.raiseSymbolNotFound("annotation not found: ${t.value}")
 | 
			
		||||
            if( fn !is Statement ) scope.raiseIllegalArgument("annotation must be callable, got ${fn.objClass}")
 | 
			
		||||
            if (fn !is Statement) scope.raiseIllegalArgument("annotation must be callable, got ${fn.objClass}")
 | 
			
		||||
            (fn.execute(scope.copy(Arguments(args))) as? Statement)
 | 
			
		||||
                ?: scope.raiseClassCastError("function annotation must return callable")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun parseArgsOrNull(): Pair<List<ParsedArgument>, Boolean>? =
 | 
			
		||||
        if( cc.skipNextIf(Token.Type.LPAREN))
 | 
			
		||||
        if (cc.skipNextIf(Token.Type.LPAREN))
 | 
			
		||||
            parseArgs()
 | 
			
		||||
        else
 | 
			
		||||
            null
 | 
			
		||||
@ -1173,22 +1174,24 @@ class Compiler(
 | 
			
		||||
 | 
			
		||||
        do {
 | 
			
		||||
            val t = cc.skipWsTokens()
 | 
			
		||||
            when(t.type) {
 | 
			
		||||
            when (t.type) {
 | 
			
		||||
                Token.Type.ID -> {
 | 
			
		||||
                    names += t.value
 | 
			
		||||
                    val t1 = cc.skipWsTokens()
 | 
			
		||||
                    when(t1.type) {
 | 
			
		||||
                    when (t1.type) {
 | 
			
		||||
                        Token.Type.COMMA ->
 | 
			
		||||
                            continue
 | 
			
		||||
 | 
			
		||||
                        Token.Type.RBRACE -> break
 | 
			
		||||
                        else -> {
 | 
			
		||||
                            t1.raiseSyntax("unexpected token")
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                else -> t.raiseSyntax("expected enum entry name")
 | 
			
		||||
            }
 | 
			
		||||
        } while(true)
 | 
			
		||||
        } while (true)
 | 
			
		||||
 | 
			
		||||
        return statement {
 | 
			
		||||
            ObjEnumClass.createSimpleEnum(nameToken.value, names).also {
 | 
			
		||||
@ -1263,7 +1266,6 @@ class Compiler(
 | 
			
		||||
                newClass.classScope = classScope
 | 
			
		||||
                for (s in initScope)
 | 
			
		||||
                    s.execute(classScope)
 | 
			
		||||
                        .also { println("executed, ${classScope.objects}") }
 | 
			
		||||
            }
 | 
			
		||||
            newClass
 | 
			
		||||
        }
 | 
			
		||||
@ -1766,24 +1768,31 @@ class Compiler(
 | 
			
		||||
 | 
			
		||||
        val eqToken = cc.next()
 | 
			
		||||
        var setNull = false
 | 
			
		||||
        if (eqToken.type != Token.Type.ASSIGN) {
 | 
			
		||||
            if (!isMutable)
 | 
			
		||||
                throw ScriptError(start, "val must be initialized")
 | 
			
		||||
            else {
 | 
			
		||||
                cc.previous()
 | 
			
		||||
                setNull = true
 | 
			
		||||
 | 
			
		||||
        val isDelegate = if (eqToken.isId("by")) {
 | 
			
		||||
            true
 | 
			
		||||
        } else {
 | 
			
		||||
            if (eqToken.type != Token.Type.ASSIGN) {
 | 
			
		||||
                if (!isMutable)
 | 
			
		||||
                    throw ScriptError(start, "val must be initialized")
 | 
			
		||||
                else {
 | 
			
		||||
                    cc.previous()
 | 
			
		||||
                    setNull = true
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            false
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val initialExpression = if (setNull) null else parseStatement(true)
 | 
			
		||||
        val initialExpression = if (setNull) null
 | 
			
		||||
        else parseStatement(true)
 | 
			
		||||
            ?: throw ScriptError(eqToken.pos, "Expected initializer expression")
 | 
			
		||||
 | 
			
		||||
        if (isStatic) {
 | 
			
		||||
            // find objclass instance: this is tricky: this code executes in object initializer,
 | 
			
		||||
            // when creating instance, but we need to execute it in the class initializer which
 | 
			
		||||
            // is missing as for now. Add it to the compiler context?
 | 
			
		||||
            // add there
 | 
			
		||||
            // return
 | 
			
		||||
 | 
			
		||||
            if (isDelegate) throw ScriptError(start, "static delegates are not yet implemented")
 | 
			
		||||
            currentInitScope += statement {
 | 
			
		||||
                val initValue = initialExpression?.execute(this)?.byValueCopy() ?: ObjNull
 | 
			
		||||
                (thisObj as ObjClass).createClassField(name, initValue, isMutable, visibility, pos)
 | 
			
		||||
@ -1797,12 +1806,31 @@ class Compiler(
 | 
			
		||||
            if (context.containsLocal(name))
 | 
			
		||||
                throw ScriptError(nameToken.pos, "Variable $name is already defined")
 | 
			
		||||
 | 
			
		||||
            // init value could be a val; when we initialize by-value type var with it, we need to
 | 
			
		||||
            // create a separate copy:
 | 
			
		||||
            val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
 | 
			
		||||
 | 
			
		||||
            context.addItem(name, isMutable, initValue, visibility, recordType = ObjRecord.Type.Field)
 | 
			
		||||
            initValue
 | 
			
		||||
            if (isDelegate) {
 | 
			
		||||
                println("initial expr = $initialExpression")
 | 
			
		||||
                val initValue =
 | 
			
		||||
                    (initialExpression?.execute(context.copy(Arguments(ObjString(name)))) as? Statement)
 | 
			
		||||
                        ?.execute(context.copy(Arguments(ObjString(name))))
 | 
			
		||||
                    ?: context.raiseError("delegate initialization required")
 | 
			
		||||
                println("delegate init: $initValue")
 | 
			
		||||
                if (!initValue.isInstanceOf(ObjArray))
 | 
			
		||||
                    context.raiseIllegalArgument("delegate initialized must be an array")
 | 
			
		||||
                val s = initValue.getAt(context, 1)
 | 
			
		||||
                val setter = if (s == ObjNull) statement { raiseNotImplemented("setter is not provided") }
 | 
			
		||||
                else (s as? Statement) ?: context.raiseClassCastError("setter must be a callable")
 | 
			
		||||
                ObjDelegate(
 | 
			
		||||
                    (initValue.getAt(context, 0) as? Statement)
 | 
			
		||||
                        ?: context.raiseClassCastError("getter must be a callable"), setter
 | 
			
		||||
                ).also {
 | 
			
		||||
                    context.addItem(name, isMutable, it, visibility, recordType = ObjRecord.Type.Field)
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // init value could be a val; when we initialize by-value type var with it, we need to
 | 
			
		||||
                // create a separate copy:
 | 
			
		||||
                val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
 | 
			
		||||
                context.addItem(name, isMutable, initValue, visibility, recordType = ObjRecord.Type.Field)
 | 
			
		||||
                initValue
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -24,6 +24,10 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
 | 
			
		||||
 | 
			
		||||
    val isComment: Boolean by lazy { type == Type.SINLGE_LINE_COMMENT || type == Type.MULTILINE_COMMENT }
 | 
			
		||||
 | 
			
		||||
    fun isId(text: String) =
 | 
			
		||||
        type == Type.ID && value == text
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Suppress("unused")
 | 
			
		||||
    enum class Type {
 | 
			
		||||
        ID, INT, REAL, HEX, STRING, CHAR,
 | 
			
		||||
 | 
			
		||||
@ -161,6 +161,8 @@ open class Obj {
 | 
			
		||||
 | 
			
		||||
    open suspend fun assign(scope: Scope, other: Obj): Obj? = null
 | 
			
		||||
 | 
			
		||||
    open fun getValue(scope: Scope) = this
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * a += b
 | 
			
		||||
     * if( the operation is not defined, it returns null and the compiler would try
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,47 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package net.sergeych.lyng.obj
 | 
			
		||||
 | 
			
		||||
import net.sergeych.lyng.Arguments
 | 
			
		||||
import net.sergeych.lyng.Scope
 | 
			
		||||
import net.sergeych.lyng.Statement
 | 
			
		||||
import net.sergeych.lyng.statement
 | 
			
		||||
 | 
			
		||||
class ObjDelegateContext()
 | 
			
		||||
 | 
			
		||||
class ObjDelegate(
 | 
			
		||||
                  val getter: Statement,
 | 
			
		||||
                  val setter: Statement = statement { raiseNotImplemented("setter is not implemented") }
 | 
			
		||||
): Obj() {
 | 
			
		||||
 | 
			
		||||
    override suspend fun assign(scope: Scope, other: Obj): Obj? {
 | 
			
		||||
        setter.execute(scope.copy(Arguments(other)))
 | 
			
		||||
        return other
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type = object: ObjClass("Delegate") {
 | 
			
		||||
            override suspend fun callOn(scope: Scope): Obj {
 | 
			
		||||
                scope.raiseError("Delegate should not be constructed directly")
 | 
			
		||||
            }
 | 
			
		||||
        }.apply {
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -56,7 +56,7 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type = ObjClass("RangeIterator", ObjIterable).apply {
 | 
			
		||||
        val type = ObjClass("RangeIterator", ObjIterator).apply {
 | 
			
		||||
            addFn("hasNext") {
 | 
			
		||||
                thisAs<ObjRangeIterator>().hasNext().toObj()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,18 @@ package net.sergeych.lyng.stdlib_included
 | 
			
		||||
internal val rootLyng = """
 | 
			
		||||
package lyng.stdlib
 | 
			
		||||
    
 | 
			
		||||
fun cached(builder) {
 | 
			
		||||
    var calculated = false
 | 
			
		||||
    var value = null
 | 
			
		||||
    {
 | 
			
		||||
        if( !calculated ) {
 | 
			
		||||
            value = builder()
 | 
			
		||||
            calculated = true
 | 
			
		||||
        }
 | 
			
		||||
        value
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    
 | 
			
		||||
fun Iterable.filter(predicate) {
 | 
			
		||||
    val list = this
 | 
			
		||||
    flow {
 | 
			
		||||
@ -38,7 +50,7 @@ fun Iterable.drop(n) {
 | 
			
		||||
fun Iterable.first() {
 | 
			
		||||
    val i = iterator()
 | 
			
		||||
    if( !i.hasNext() ) throw NoSuchElementException()
 | 
			
		||||
    i.next()
 | 
			
		||||
    i.next().also { i.cancelIteration() }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun Iterable.last() {
 | 
			
		||||
@ -70,6 +82,16 @@ fun Iterable.takeLast(n) {
 | 
			
		||||
    for( item in list ) buffer += item
 | 
			
		||||
    buffer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun Iterable.joinToString(prefix=" ", transformer=null) {
 | 
			
		||||
    var result = null
 | 
			
		||||
    for( part in this ) {
 | 
			
		||||
        val transformed = transformer?(part)?.toString() ?: part.toString()
 | 
			
		||||
        if( result == null ) result = transformed
 | 
			
		||||
        else result += prefix + transformed
 | 
			
		||||
    }
 | 
			
		||||
    result ?: ""
 | 
			
		||||
}
 | 
			
		||||
    
 | 
			
		||||
""".trimIndent()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2913,5 +2913,33 @@ class ScriptTest {
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun cachedTest() = runTest {
 | 
			
		||||
        eval( """
 | 
			
		||||
        
 | 
			
		||||
        var counter = 0
 | 
			
		||||
        var value = cached {
 | 
			
		||||
            counter++
 | 
			
		||||
            "ok"
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        assertEquals(0, counter)
 | 
			
		||||
        assertEquals("ok", value())
 | 
			
		||||
        assertEquals(1, counter)
 | 
			
		||||
        assertEquals("ok", value())
 | 
			
		||||
        assertEquals(1, counter)
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testJoinToString() = runTest {
 | 
			
		||||
        eval("""
 | 
			
		||||
            assertEquals( (1..3).joinToString(), "1 2 3")
 | 
			
		||||
            assertEquals( (1..3).joinToString(":"), "1:2:3")
 | 
			
		||||
            assertEquals( (1..3).joinToString { it * 10 }, "10 20 30")
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -40,8 +40,9 @@ class StdlibTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testTake() = runTest {
 | 
			
		||||
        eval("""
 | 
			
		||||
            assertEquals([1,2,3], (1..8).take(3).toList() )
 | 
			
		||||
            assertEquals([7,8], (1..8).takeLast(2).toList() )
 | 
			
		||||
            val r = 1..8
 | 
			
		||||
            assertEquals([1,2,3], r.take(3).toList() )
 | 
			
		||||
            assertEquals([7,8], r.takeLast(2).toList() )
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -317,4 +317,9 @@ class BookTest {
 | 
			
		||||
        runDocTests("../docs/RingBuffer.md")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testIterable() = runBlocking {
 | 
			
		||||
        runDocTests("../docs/Iterable.md")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user