Compare commits

..

No commits in common. "f9198fe583af3dfb5e23cd5592cc1a205b78e3fd" and "9aae33d56402661913b1630b13127bf13dbb1f96" have entirely different histories.

9 changed files with 71 additions and 210 deletions

View File

@ -16,8 +16,7 @@ __Other documents to read__ maybe after this one:
- [math in Lyng](math.md) - [math in Lyng](math.md)
- [parallelism] - multithreaded code, coroutines, etc. - [parallelism] - multithreaded code, coroutines, etc.
- Some class references: [List], [Set], [Map], [Real], [Range], [Iterable], [Iterator], [time manipulation](time.md) - Some class references: [List], [Set], [Map], [Real], [Range], [Iterable], [Iterator], [time manipulation](time.md)
- Some samples: [combinatorics](samples/combinatorics.lyng.md), national vars and - Some samples: [combinatorics](samples/combinatorics.lyng.md), national vars and loops: [сумма ряда](samples/сумма_ряда.lyng.md). More at [samples folder](samples)
loops: [сумма ряда](samples/сумма_ряда.lyng.md). More at [samples folder](samples)
# Expressions # Expressions
@ -170,10 +169,9 @@ There is also "elvis operator", null-coalesce infix operator '?:' that returns r
The following functions simplify nullable values processing and The following functions simplify nullable values processing and
allow to improve code look and readability. There are borrowed from Kotlin: allow to improve code look and readability. There are borrowed from Kotlin:
### let ### let
`value.let {}` passes to the block value as the single parameter (by default it is assigned to `it`) and return block's `value.let {}` passes to the block value as the single parameter (by default it is assigned to `it`) and return block's returned value. It is useful dealing with null or to
returned value. It is useful dealing with null or to
get a snapshot of some externally varying value, or with `?.` to process nullable value in a safe manner: get a snapshot of some externally varying value, or with `?.` to process nullable value in a safe manner:
// this state is changed from parallel processes // this state is changed from parallel processes
@ -467,6 +465,7 @@ after function call, it is treated as a last argument to the call, e.g.:
assert( [11, 21, 31] == mapped) assert( [11, 21, 31] == mapped)
>>> void >>> void
# Lists (aka arrays) # Lists (aka arrays)
Lyng has built-in mutable array class `List` with simple literals: Lyng has built-in mutable array class `List` with simple literals:
@ -536,8 +535,7 @@ The simplest way to concatenate lists is using `+` and `+=`:
void void
>>> void >>> void
***Important note***: the pitfall of using `+=` is that you can't append in [Iterable] instance as an object: it will ***Important note***: the pitfall of using `+=` is that you can't append in [Iterable] instance as an object: it will always add all its contents. Use `list.add` to add a single iterable instance:
always add all its contents. Use `list.add` to add a single iterable instance:
var list = [1, 2] var list = [1, 2]
val other = [3, 4] val other = [3, 4]
@ -565,6 +563,7 @@ Use `list.add` to avoid confusion:
assert( list == [1, 2, [3, 4], (10..12)]) assert( list == [1, 2, [3, 4], (10..12)])
>>> void >>> void
To add elements to the list: To add elements to the list:
val x = [1,2] val x = [1,2]
@ -597,6 +596,7 @@ Using splat arguments can simplify inserting list in list:
x x
>>> [1, 0, 100, 0, 2, 3] >>> [1, 0, 100, 0, 2, 3]
Note that to add to the end you still need to use `add` or positive index of the after-last element: Note that to add to the end you still need to use `add` or positive index of the after-last element:
val x = [1,2,3] val x = [1,2,3]
@ -740,11 +740,9 @@ Also, you can check the type too:
### supported when conditions: ### supported when conditions:
#### Contains: #### Contains:
You can thest that _when expression_ is _contained_, or not contained, in some object using `in container` and You can thest that _when expression_ is _contained_, or not contained, in some object using `in container` and `!in container`. The container is any object that provides `contains` method, otherwise the runtime exception will be thrown.
`!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 `conain`):
@ -760,8 +758,7 @@ Typical builtin types that are containers (e.g. support `conain`):
: Iterable is not the container as it can be infinite : Iterable is not the container as it can be infinite
(2) (2)
: Depending on the inclusivity and open/closed range parameters. BE careful here: String range is allowed, but it is : Depending on the inclusivity and open/closed range parameters. BE careful here: String range is allowed, but it is usually not what you expect of it:
usually not what you expect of it:
assert( "more" in "a".."z") // string range ok assert( "more" in "a".."z") // string range ok
assert( 'x' !in "a".."z") // char in string range: probably error assert( 'x' !in "a".."z") // char in string range: probably error
@ -770,9 +767,7 @@ usually not what you expect of it:
>>> void >>> void
(3) (3)
: `String` also can provide array of characters directly with `str.characters()`, which is [Iterable] and [Array]. : `String` also can provide array of characters directly with `str.characters()`, which is [Iterable] and [Array]. String itself is not iterable as otherwise it will interfere when adding strigns to lists (it will add _characters_ it it would be iterable).
String itself is not iterable as otherwise it will interfere when adding strigns to lists (it will add _characters_ it
it would be iterable).
So we recommend not to mix characters and string ranges; use `ch in str` that works So we recommend not to mix characters and string ranges; use `ch in str` that works
as expected: as expected:
@ -850,8 +845,7 @@ We can skip the rest of the loop and restart it, as usual, with `continue` opera
"found even numbers: " + countEven "found even numbers: " + countEven
>>> "found even numbers: 5" >>> "found even numbers: 5"
`continue` can't "return" anything: it just restarts the loop. It can use labeled loops to restart outer ones (we `continue` can't "return" anything: it just restarts the loop. It can use labeled loops to restart outer ones (we intentionally avoid using for loops here):
intentionally avoid using for loops here):
var count = 0 var count = 0
var total = 0 var total = 0
@ -897,22 +891,21 @@ test function (remember function return it's last expression result):
```mermaid ```mermaid
flowchart TD flowchart TD
S((start)) --> Cond{check} S((start)) --> Cond{check}
Cond -- false, no else ---> V((void)) Cond--false, no else--->V((void))
Cond -- true --> E(["last = loop_body()"]) Cond--true-->E(["last = loop_body()" ])
E -- break value ----> BV((value)) E--break value---->BV((value))
E --> Check2{check} E--> Check2{check}
E -- break ----> V E--break---->V
Check2 -- false --> E Check2 --false-->E
Check2 -- true, no else ---> L((last)) Check2 --true, no else--->L((last))
Check2 -- true, else --> Else(["else_clause()"]) Check2 --true, else-->Else(["else_clause()"])
Cond -- false, else ---> Else Cond--false, else--->Else
Else --> Ele4$nr((else)) Else --> Ele4$nr((else))
``` ```
So the returned value, as seen from diagram could be one of: So the returned value, as seen from diagram could be one of:
- `void`, if the loop was not executed, e.g. `condition` was initially false, and there was no `else` clause, or if the - `void`, if the loop was not executed, e.g. `condition` was initially false, and there was no `else` clause, or if the empty break was executed.
empty break was executed.
- value returned from `break value' statement - value returned from `break value' statement
- value returned from the `else` clause, of the loop was not broken - value returned from the `else` clause, of the loop was not broken
- value returned from the last execution of loop body, if there was no `break` and no `else` clause. - value returned from the last execution of loop body, if there was no `break` and no `else` clause.
@ -936,8 +929,7 @@ available in the condition:
} while( continueLoop ) } while( continueLoop )
>>> "OK" >>> "OK"
This is sometimes convenient when condition is complex and has to be calculated inside the loop body. Notice the value This is sometimes convenient when condition is complex and has to be calculated inside the loop body. Notice the value returning by the loop:
returning by the loop:
fun readLine() { "done: result" } fun readLine() { "done: result" }
val result = do { val result = do {
@ -948,6 +940,7 @@ returning by the loop:
Suppose readLine() here reads some stream of lines. Suppose readLine() here reads some stream of lines.
## For loops ## For loops
For loop are intended to traverse collections, and all other objects that supports For loop are intended to traverse collections, and all other objects that supports
@ -990,8 +983,7 @@ We can use labels too:
# Exception handling # Exception handling
Very much like in Kotlin. Try block returns its body block result, if no exception was cauht, or the result from the Very much like in Kotlin. Try block returns its body block result, if no exception was cauht, or the result from the catch block that caught the exception:
catch block that caught the exception:
var error = "not caught" var error = "not caught"
var finallyCaught = false var finallyCaught = false
@ -1038,12 +1030,13 @@ And even shortest, for the Lying lang tradition, missing var is `it`:
assert( caught is IllegalArgumentException ) assert( caught is IllegalArgumentException )
>>> void >>> void
It is possible to catch several exceptions in the same block too, use It is possible to catch several exceptions in the same block too, use
`catch( varName: ExceptionClass1, ExceptionClass2)`, etc, use short form of throw and `catch( varName: ExceptionClass1, ExceptionClass2)`, etc, use short form of throw and
many more. many more.
- see [exception handling](exceptions_handling.md) for detailed exceptions tutorial and reference. - see [exception handling](exceptions_handling.md) for detailed exceptions tutorial and reference.
# Self-assignments in expression # Self-assignments in expression
There are auto-increments and auto-decrements: There are auto-increments and auto-decrements:
@ -1166,6 +1159,7 @@ Are the same as in string literals with little difference:
| code | Int | Unicode code for the character | | code | Int | Unicode code for the character |
| | | | | | | |
## String details ## String details
Strings are arrays of Unicode characters. It can be indexed, and indexing will Strings are arrays of Unicode characters. It can be indexed, and indexing will
@ -1193,10 +1187,7 @@ To format a string use sprintf-style modifiers like:
assertEquals( "hello :11 ", "%-6s:%-6d"(a, b) ) assertEquals( "hello :11 ", "%-6s:%-6d"(a, b) )
>>> void >>> void
List of format specifiers closely resembles C sprintf() one. List of format specifiers closely resembles C sprintf() one. See [format specifiers](https://github.com/sergeych/mp_stools?tab=readme-ov-file#sprintf-syntax-summary), this is doe using [mp_stools kotlin multiplatform library](https://github.com/sergeych/mp_stools). Currently supported Lyng types are `String`, `Int`, `Real`, `Bool`, the rest are displayed using their `toString()` representation.
See [format specifiers](https://github.com/sergeych/mp_stools?tab=readme-ov-file#sprintf-syntax-summary), this is doe
using [mp_stools kotlin multiplatform library](https://github.com/sergeych/mp_stools). Currently supported Lyng types
are `String`, `Int`, `Real`, `Bool`, the rest are displayed using their `toString()` representation.
This list will be extended. This list will be extended.
@ -1227,30 +1218,33 @@ Concatenation is a `+`: `"hello " + name` works as expected. No confusion.
Typical set of String functions includes: Typical set of String functions includes:
| fun/prop | description / notes | | fun/prop | description / notes |
|--------------------|------------------------------------------------------------| |-------------------|------------------------------------------------------------|
| lower() | change case to unicode upper | | lower() | change case to unicode upper |
| upper() | change case to unicode lower | | upper() | change case to unicode lower |
| startsWith(prefix) | true if starts with a prefix | | startsWith(prefix) | true if starts with a prefix |
| endsWith(prefix) | true if ends with a prefix | | endsWith(prefix) | true if ends with a prefix |
| take(n) | get a new string from up to n first characters | | take(n) | get a new string from up to n first characters |
| takeLast(n) | get a new string from up to n last characters | | takeLast(n) | get a new string from up to n last characters |
| drop(n) | get a new string dropping n first chars, or empty string | | drop(n) | get a new string dropping n first chars, or empty string |
| dropLast(n) | get a new string dropping n last chars, or empty string | | dropLast(n) | get a new string dropping n last chars, or empty string |
| size | size in characters like `length` because String is [Array] | | size | size in characters like `length` because String is [Array] |
| (args...) | sprintf-like formatting, see [string formatting] | | (args...) | sprintf-like formatting, see [string formatting] |
| [index] | character at index | | [index] | character at index |
| [Range] | substring at range | | [Range] | substring at range |
| s1 + s2 | concatenation | | s1 + s2 | concatenation |
| s1 += s2 | self-modifying concatenation | | s1 += s2 | self-modifying concatenation |
| toReal() | attempts to parse string as a Real value | | toReal() | attempts to parse string as a Real value |
| toInt() | parse string to Int value | | toInt() | parse string to Int value |
| characters() | create [List] of characters (1) | | characters() | create [List] of characters (1) |
| encodeUtf8() | returns [Buffer] with characters encoded to utf8 | | encodeUtf8() | returns [Buffer] with characters encoded to utf8 |
(1) (1)
: List is mutable therefore a new copy is created on each call. : List is mutable therefore a new copy is created on each call.
### Literals ### Literals
String literal could be multiline: String literal could be multiline:
@ -1264,18 +1258,10 @@ though multiline literals is yet work in progress.
See [math functions](math.md). Other general purpose functions are: See [math functions](math.md). Other general purpose functions are:
| name | description | | name | description |
|--------------------------------------------|-----------------------------------------------------------| |----------------------------------------------|----------------------------------------------------------|
| assert(condition,message="assertion failed") | runtime code check. There will be an option to skip them | | assert(condition,message="assertion failed") | runtime code check. There will be an option to skip them |
| assertEquals(a,b) | | | println(args...) | Open for overriding, it prints to stdout. |
| 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] |
# Built-in constants # Built-in constants
@ -1285,23 +1271,13 @@ See [math functions](math.md). Other general purpose functions are:
| π | See [math](math.md) | | π | See [math](math.md) |
[List]: List.md [List]: List.md
[Iterable]: Iterable.md [Iterable]: Iterable.md
[Iterator]: Iterator.md [Iterator]: Iterator.md
[Real]: Real.md [Real]: Real.md
[Range]: Range.md [Range]: Range.md
[String]: String.md [String]: String.md
[string formatting]: https://github.com/sergeych/mp_stools?tab=readme-ov-file#sprintf-syntax-summary [string formatting]: https://github.com/sergeych/mp_stools?tab=readme-ov-file#sprintf-syntax-summary
[Set]: Set.md [Set]: Set.md
[Map]: Map.md [Map]: Map.md
[Buffer]: Buffer.md [Buffer]: Buffer.md
[parallelism]: parallelism.md [parallelism]: parallelism.md

View File

@ -4,7 +4,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.8.3-SNAPSHOT" version = "0.8.2-SNAPSHOT"
buildscript { buildscript {
repositories { repositories {

View File

@ -205,20 +205,6 @@ open class Scope(
return "S[this=$thisObj $contents]" return "S[this=$thisObj $contents]"
} }
fun trace(text: String="") {
println("trace Scope: $text ------------------")
var p = this.parent
var level = 0
while (p != null) {
println(" parent#${++level}: $p")
println(" ( ${p.args.list} )")
p = p.parent
}
println("--------------------")
ObjVoid
}
companion object { companion object {
fun new(): Scope = fun new(): Scope =

View File

@ -26,15 +26,8 @@ class Script(
companion object { companion object {
internal val rootScope: Scope = Scope(null).apply { private val rootScope: Scope = Scope(null).apply {
ObjException.addExceptionsToContext(this) ObjException.addExceptionsToContext(this)
addFn("print") {
for ((i, a) in args.withIndex()) {
if (i > 0) print(' ' + a.asStr.value)
else print(a.asStr.value)
}
ObjVoid
}
addFn("println") { addFn("println") {
for ((i, a) in args.withIndex()) { for ((i, a) in args.withIndex()) {
if (i > 0) print(' ' + a.asStr.value) if (i > 0) print(' ' + a.asStr.value)
@ -160,24 +153,14 @@ class Script(
} }
result ?: raiseError(ObjAssertionFailedException(this,"Expected exception but nothing was thrown")) result ?: raiseError(ObjAssertionFailedException(this,"Expected exception but nothing was thrown"))
} }
addFn("require") {
val condition = requiredArg<ObjBool>(0)
if( !condition.value ) {
val message = args.list.getOrNull(1)?.toString() ?: "requirement not met"
raiseIllegalArgument(message)
}
ObjVoid
}
addFn("check") {
val condition = requiredArg<ObjBool>(0)
if( !condition.value ) {
val message = args.list.getOrNull(1)?.toString() ?: "check failed"
raiseIllegalState(message)
}
ObjVoid
}
addFn("traceScope") { addFn("traceScope") {
this.trace(args.getOrNull(0)?.toString() ?: "") println("trace Scope: $this")
var p = this.parent
var level = 0
while (p != null) {
println(" parent#${++level}: $p")
p = p.parent
}
ObjVoid ObjVoid
} }
@ -221,9 +204,7 @@ class Script(
} }
addFn("flow") { addFn("flow") {
// important is: current context contains closure often used in call; ObjFlow(requireOnlyArg<Statement>())
// we'll need it for the producer
ObjFlow(requireOnlyArg<Statement>(), this)
} }
val pi = ObjReal(PI) val pi = ObjReal(PI)

View File

@ -7,7 +7,9 @@ import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import net.sergeych.lyng.* import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScriptFlowIsNoMoreCollected
import net.sergeych.lyng.Statement
import net.sergeych.mp_tools.globalLaunch import net.sergeych.mp_tools.globalLaunch
import kotlin.coroutines.cancellation.CancellationException import kotlin.coroutines.cancellation.CancellationException
@ -58,7 +60,7 @@ private fun createLyngFlowInput(scope: Scope, producer: Statement): ReceiveChann
return channel return channel
} }
class ObjFlow(val producer: Statement, val scope: Scope) : Obj() { class ObjFlow(val producer: Statement) : Obj() {
override val objClass = type override val objClass = type
@ -69,8 +71,7 @@ class ObjFlow(val producer: Statement, val scope: Scope) : Obj() {
} }
}.apply { }.apply {
addFn("iterator") { addFn("iterator") {
val objFlow = thisAs<ObjFlow>() ObjFlowIterator(thisAs<ObjFlow>().producer)
ObjFlowIterator( statement { objFlow.producer.execute(ClosureScope(this,objFlow.scope)) } )
} }
} }
} }

View File

@ -111,5 +111,6 @@ val ObjIterable by lazy {
.not() .not()
) )
} }
} }
} }

View File

@ -1,5 +0,0 @@
package net.sergeych.lyng.stdlib_included
internal val rootLyng = """
""".trimIndent()

View File

@ -1,16 +0,0 @@
fun Iterable.filter( predicate ) {
flow {
for( item in this )
if( predicate(item) )
emit(item)
}
}
fun Iterable.drop(n) {
require( n >= 0, "drop amount must be non-negative")
var count = 0
filter {
count++ < N
}
}

View File

@ -106,67 +106,4 @@ class TestCoroutines {
assertEquals( result, f.toList()) assertEquals( result, f.toList())
""".trimIndent()) """.trimIndent())
} }
@Test
fun testFlowClosures() = runTest {
eval("""
fun filter( a, b ) {
println("filter: %s, %s"(a,b))
flow {
emit(a)
emit(b)
}
}
assertEquals( [5, 1], filter(5,1).toList() )
assertEquals( [2, 3], filter(2,3).toList() )
""".trimIndent())
}
@Test
fun testFilterFlow() = runTest {
eval("""
fun filter( list, predicate ) {
val p = predicate
println("predicate "+predicate+" / "+p)
flow {
// here p is captured only once and does not change!
for( item in list ) {
print("filter "+p+" "+item+": ")
if( p(item) ) {
println("OK")
emit(item)
}
else println("NO")
}
}
}
// fun drop(i, n) {
// require( n >= 0, "drop amount must be non-negative")
// var count = 0
// println("drop %d"(n))
// filter(i) {
// count++ >= n
// }
// }
val src = (1..1).toList()
assertEquals( 1, filter(src) { true }.toList().size )
println("----------------------------------------------------------")
println("----------------------------------------------------------")
println("----------------------------------------------------------")
println("----------------------------------------------------------")
assertEquals( 0, filter(src) { false }.toList().size )
// assertEquals( 3, filter(src) { true }.size() )
// assertEquals( [7,8], drop((1..8).toList(),6).toList())
// assertEquals( [1,3,5,7], filter((1..8).toList()) {
// println("call2")
// it % 2 == 1
// }.toList())
""".trimIndent())
}
} }