Compare commits

...

43 Commits

Author SHA1 Message Date
23737f9b5c removed debug stuff 2025-08-18 01:48:23 +03:00
fb6e2aa49e bugfix: elvis + throw 2025-08-18 01:19:49 +03:00
95c1da60ed better elvis + throw
+added run {}
2025-08-18 00:56:52 +03:00
835333dfad fixed objClass initialization in ObjInt.Zero, added Buffer.toBitInput, minimal descrription of Lynon 2025-08-17 23:51:50 +03:00
eefecae7b4 more Buffer encodings 2025-08-17 19:26:15 +03:00
2ac92a1d09 more elegant Iterable.all 2025-08-17 18:21:52 +03:00
464a6dcb99 fixed many bugs with variable visibility in mixed scopes 2025-08-17 12:24:39 +03:00
eca746b189 commented out delegate creation 2025-08-14 14:34:44 +03:00
b07452e66e more stdlib and docs, bugfixes 2025-08-14 14:31:51 +03:00
202e70a99a less debug noise 2025-08-14 01:36:37 +03:00
f45310f7d9 default implementation for Iterator.cancelIteration (does nothing) and not implemented errors for iterator methods (better error messages) 2025-08-13 23:34:54 +03:00
48a7f0839c less warnings 2025-08-13 23:24:51 +03:00
2adb0ff512 +copyrght, apache 2.0 2025-08-13 23:22:04 +03:00
6735499959 more enum tests 2025-08-12 14:57:52 +03:00
b5e89c7e78 smple enums w/serialization 2025-08-12 14:47:41 +03:00
84e345b04e more docs on function annotations 2025-08-12 05:01:02 +03:00
9bd7aa368e cleanup 2025-08-12 04:52:40 +03:00
9704f18284 function annotation and some docs for it 2025-08-12 04:51:26 +03:00
804087f16d version bump 2025-08-12 00:16:09 +03:00
6d8eed7b8c added "dynamic" fields (get/set/call fields by name using dynamic standard function 2025-08-12 00:15:45 +03:00
e916d9805a correct end-block token processing (detect more errors) 2025-08-11 22:48:43 +03:00
c398496ee0 refining the readme/2 2025-08-11 14:41:35 +03:00
299738cffd refining the readme 2025-08-11 10:36:29 +03:00
62461c09cc readme fix 2025-08-10 23:41:57 +03:00
63de82393a multiline string literals 2025-08-10 23:32:03 +03:00
ed0a21cb06 added test config 2025-08-10 09:51:04 +03:00
1baf69f40f more docs on Iterable 2025-08-09 23:23:05 +03:00
ba8d543d87 more docs on Iterable 2025-08-09 23:20:25 +03:00
d3785afa6f adopt stdlib to CLI tool 2025-08-09 22:59:43 +03:00
3948283481 0.8.4: stadlib in Lyng! added universal Iterable functions 2025-08-09 22:50:48 +03:00
f9198fe583 parallelism and flows + bugfixes release 2025-08-09 15:55:30 +03:00
4917f99197 docs 2025-08-09 15:54:46 +03:00
e0ed27a01f fixed many bugs in closures processing also in flows 2025-08-09 15:48:41 +03:00
9aae33d564 fix #48 flows 2025-08-08 19:33:17 +03:00
1a90b25b1e fix #51 ref #48 flows started. bad closure-based bug fixed 2025-08-08 19:23:43 +03:00
f7f020f4d6 0.8.2-SNAPSHOT 2025-08-07 21:50:14 +03:00
0f54e2f845 ref #47 yield 2025-08-07 21:46:45 +03:00
19f8b6605b fix #47 parallelism: launch, Deferred, CompletableDeferred, Mutex (delay was already implemented) 2025-08-07 20:31:36 +03:00
de71c7c8ec 0.8.1 release version 2025-08-06 23:12:25 +03:00
be370dfdef fix #45 private statics and docs 2025-08-06 23:10:33 +03:00
53a6a88924 fix #42 class static fields and methods 2025-08-06 22:48:32 +03:00
2c2dae4d85 well redesigned class vals and vars, classScope introduced for class functions 2025-08-06 11:47:45 +03:00
63b2808109 well redesigned class vals and vars, classScope introduced for class functions 2025-08-06 11:46:17 +03:00
118 changed files with 4702 additions and 399 deletions

View File

@ -0,0 +1,42 @@
<!--
~ 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.
~
-->
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Tests in 'lyng.lynglib.jvmTest'" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":lynglib:cleanJvmTest" />
<option value=":lynglib:jvmTest" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>true</RunAsTest>
<method v="2" />
</configuration>
</component>

3
NOTICE Normal file
View File

@ -0,0 +1,3 @@
The Lyng programming language.
Copyright (c) 2024-2025 Sergey Chernov real.sergeych@gmail.com

View File

@ -2,7 +2,7 @@
A KMP library and a standalone interpreter
- simple, compact, intuitive and elegant modern code style:
- simple, compact, intuitive and elegant modern code:
```
class Point(x,y) {
@ -15,23 +15,31 @@ fun swapEnds(first, args..., last, f) {
}
```
- extremely simple Kotlin integration on any platform
- extremely simple Kotlin integration on any platform (JVM, JS, WasmJS, Lunux, MacOS, iOS, Windows)
- 100% secure: no access to any API you didn't explicitly provide
- 100% coroutines! Every function/script is a coroutine, it does not block the thread, no async/await/suspend keyword garbage:
- 100% coroutines! Every function/script is a coroutine, it does not block the thread, no async/await/suspend keyword garbage, see [parallelism]
```
val deferred = launch {
delay(1.5) // coroutine is delayed for 1.5s, thread is not blocked!
"done"
}
// ...
// suspend current coroutine, no thread is blocked again,
// and wait for deferred to return something:
assertEquals("donw", deferred.await())
```
and it is multithreaded on platforms supporting it (automatically, no code changes required, just
`launch` more coroutines and they will be executed concurrently if possible)/
`launch` more coroutines and they will be executed concurrently if possible). See [parallelism]
- functional style and OOP together, multiple inheritance, implementing interfaces for existing classes, writing extensions.
- Any unicode letters can be used as identifiers: `assert( sin(π/2) == 1 )`.
- Any Unicode letters can be used as identifiers: `assert( sin(π/2) == 1 )`.
## Resources:
- [introduction and tutorial](docs/tutorial.md) - start here please
- [Samples directory](docs/samples)
- [Books directory](docs)
## Integration in Kotlin multiplatform
@ -84,8 +92,8 @@ import com.sun.source.tree.Scope
import new.sergeych.lyng.*
// simple function
val scope = Scope().apply {
addFn("addArgs") {
val scope = Script.newScope().apply {
addFn("sumOf") {
var sum = 0.0
for (a in args) sum += a.toDouble()
ObjReal(sum)
@ -96,12 +104,14 @@ val scope = Scope().apply {
// suspend fun doSomeWork(text: String): Int
addFn("doSomeWork") {
// this _is_ a suspend lambda, we can call suspend function,
// and it won't consume the thread:
doSomeWork(args[0].toString()).toObj()
// and it won't consume the thread.
// note that in kotlin handler, `args` is a list of `Obj` arguments
// and return value from this lambda should be Obj too:
doSomeWork(args[0]).toObj()
}
}
// adding constant:
scope.eval("addArgs(1,2,3)") // <- 6
scope.eval("sumOf(1,2,3)") // <- 6
```
Note that the scope stores all changes in it so you can make calls on a single scope to preserve state between calls.
@ -119,7 +129,7 @@ Designed to add scripting to kotlin multiplatform application in easy and effici
- Javascript, WasmJS, native, JVM, android - batteries included.
- dynamic types in most elegant and concise way
- async, 100% coroutines, supports multiple cores where platofrm supports thread
- async, 100% coroutines, supports multiple cores where platform supports thread
- good for functional an object-oriented style
# Language Roadmap
@ -139,15 +149,20 @@ Ready features:
- [x] exception handling: throw, try-catch-finally, exception classes.
- [x] multiplatform maven publication
- [x] documentation for the current state
- [x] maps, sets and sequences (flows?)
- [x] modules
- [x] string formatting and tools
- [x] launch, deferred, CompletableDeferred, Mutex, etc.
- [x] multiline strings
- [x] typesafe bit-effective serialization
- [x] compression/decompression (integrated in serialization)
- [x] dynamic fields
- [x] function annotations
Under way:
### Under way:
- [ ] maps, sets and sequences (flows?)
- [ ] regular exceptions
- [ ] modules
- [ ] string interpolation and more string tools
- [ ] multiple inheritance for user classes
- [ ] launch, deferred, coroutineScope, mutex, etc.
- [ ] site with integrated interpreter to give a try
- [ ] kotlin part public API good docs, integration focused
- [ ] better stack reporting
@ -158,10 +173,12 @@ Planned features.
- [ ] type specifications
- [ ] source docs and maybe lyng.md to a standard
- [ ] moacro-style kotlin integration or something else to simplify it
- [ ] metadata first class access from lyng
Further
- [ ] client with GUI support based on compose multiplatform somehow
- [ ] notebook - style workbooks with graphs, formulaes, etc.
- [ ] notebook - style workbooks with graphs, formulae, etc.
- [ ] language server or compose-based lyng-aware editor
[parallelism]: docs/parallelism.md

View File

@ -1,5 +1,22 @@
#!/bin/bash
#
# 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.
#
#
set -e
root=./lyng/build/install/lyng-jvm/

View File

@ -1,5 +1,22 @@
#!/bin/bash
#
# 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.
#
#
set -e
file=./lyng/build/bin/linuxX64/releaseExecutable/lyng.kexe

View File

@ -1,3 +1,20 @@
/*
* 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.
*
*/
plugins {
alias(libs.plugins.androidLibrary) apply false
alias(libs.plugins.kotlinMultiplatform) apply false

View File

@ -102,20 +102,50 @@ As with [List], it is possible to use ranges as indexes to slice a Buffer:
>>> void
## Encoding
You can encode `String` to buffer using buffer constructor, as was shown. Also, buffer supports out of the box base64 (
which is used in `toString`) and hex encoding:
import lyng.buffer
// to UTF8 and back:
val b = Buffer("hello")
assertEquals( "hello", b.decodeUtf8() )
// to base64 and back:
assertEquals( b, Buffer.decodeBase64(b.base64) )
assertEquals( b, Buffer.decodeHex(b.hex) )
>>> void
## Members
| name | meaning | type |
|---------------|------------------------------------|---------------|
|----------------------------|-----------------------------------------|---------------|
| `size` | size | Int |
| `decodeUtf8` | decodee to String using UTF8 rules | Any |
| `decodeUtf8` | decode to String using UTF8 rules | Any |
| `+` | buffer concatenation | Any |
| `toMutable()` | create a mutable copy | MutableBuffer |
| `hex` | encode to hex strign | String |
| `Buffer.decodeHex(hexStr) | decode hex string | Buffer |
| `base64` | encode to base64 (url flavor) (2) | String |
| `Buffer.decodeBase64(str)` | decode base64 to new Buffer (2) | Buffer |
| `toBitInput()` | create bit input from a byte buffer (3) | |
(1)
: optimized implementation that override `Iterable` one
(2)
: base64url alphabet is used without trailing '=', which allows string to be used in URI without escaping. Note that
decoding supports both traditional and URL alphabets automatically, and ignores filling `=` characters. Base64URL is
well known and mentioned in the internet, for example, [here](https://base64.guru/standards/base64url).
(3)
: `BitInput` is a bit buffer that is used, for example, in [Lynon.decode](serialization.md)
Also, it inherits methods from [Iterable] and [Array].
[Range]: Range.md
[Iterable]: Iterable.md

View File

@ -1,5 +1,11 @@
# Iterable interface
The interface for anything that can be iterated, e.g. finite or infinite ordered set of data that can be accessed sequentially. Almost any data container in `Lyng` implements it: `List`, `Set`, `Buffer`, `RingBuffer`, `BitBuffer`, `Range` and many others are `Iterable`, also `Collection` and `Array` interfaces inherit it.
`Map` and `String` have `Iterable` members to access its contents too.
## Definition:
Iterable is a class that provides function that creates _the iterator_:
class Iterable {
@ -17,12 +23,37 @@ Iterator itself is a simple interface that should provide only to method:
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:
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).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:
## Instance methods
| fun/method | description |
|-----------------|---------------------------------------------------------------------------------|
|-------------------|---------------------------------------------------------------------------|
| toList() | create a list from iterable |
| toSet() | create a set from iterable |
| contains(i) | check that iterable contains `i` |
@ -33,7 +64,19 @@ Having `Iterable` in base classes allows to use it in for loop. Also, each `Iter
| 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
@ -45,7 +88,7 @@ Having `Iterable` in base classes allows to use it in for loop. Also, each `Iter
fun Iterable.associateBy( keyMaker: (Any?)->Any): Map
## Abstract methods
## Abstract methods:
fun iterator(): Iterator
@ -57,7 +100,9 @@ Creates a list by iterating to the end. So, the Iterator should be finite to be
## Implemented in classes:
- [List], [Range]
- [List], [Range], [Buffer](Buffer.md), [BitBuffer], [Buffer], [Set], [RingBuffer]
[List]: List.md
[Range]: Range.md
[Set]: Set.md
[RingBuffer]: RingBuffer.md

View File

@ -147,7 +147,7 @@ In many cases it is necessary to implement custom comparison and `toString`, sti
each class is provided with default implementations:
- default toString outputs class name and its _public_ fields.
- default comparison compares compares all public fields in order of appearance.
- default comparison compares all fields in order of appearance.
For example, for our class Point:
@ -160,6 +160,47 @@ For example, for our class Point:
Point(1,1+1)
>>> Point(x=1,y=2)
## Statics: class fields and class methods
You can mark a field or a method as static. This is borrowed from Java as more plain version of a kotlin's companion object or Scala's object. Static field and functions is one for a class, not for an instance. From inside the class, e.g. from the class method, it is a regular var. From outside, it is accessible as `ClassName.field` or method:
class Value(x) {
static var foo = Value("foo")
static fun exclamation() {
// here foo is a regular var:
foo.x + "!"
}
}
assertEquals( Value.foo.x, "foo" )
assertEquals( "foo!", Value.exclamation() )
// we can access foo from outside like this:
Value.foo = Value("bar")
assertEquals( "bar!", Value.exclamation() )
>>> void
As usual, private statics are not accessible from the outside:
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 }
}
// no direct access:
assertThrows { Test.data }
// accessible with the interface:
assertEquals( null, Test.getData() )
Test.setData("fubar")
assertEquals("fubar", Test.getData() )
>>> void
# Extending classes
It sometimes happen that the class is missing some particular functionality that can be _added to it_ without rewriting its inner logic and using its private state. In this case _extension methods_ could be used, for example. we want to create an extension method
@ -194,6 +235,63 @@ as they are modifying the type, not the context.
Beware of it. We might need to reconsider it later.
## dynamic symbols
Sometimes it is convenient to provide methods and variables whose names are not known at compile time. For example, it could be external interfaces not known to library code, user-defined data fields, etc. You can use `dynamic` function to create such:
// val only dynamic object
val accessor = dynamic {
// all symbol reads are redirected here:
get { name ->
// lets provide one dynamic symbol:
if( name == "foo" ) "bar" else null
// consider also throw SymbolNotDefinedException
}
}
// now we can access dynamic "fields" of accessor:
assertEquals("bar", accessor.foo)
assertEquals(null, accessor.bar)
>>> void
The same we can provide writable dynamic fields (var-type), adding set method:
// store one dynamic field here
var storedValueForBar = null
// create dynamic object with 2 fields:
val accessor = dynamic {
get { name ->
when(name) {
// constant field
"foo" -> "bar"
// mutable field
"bar" -> setValueForBar
else -> throw SymbolNotFoundException()
}
}
set { name, value ->
// only 'bar' is mutable:
if( name == "bar" )
storedValueForBar = value
// the rest is immotable. consider throw also
// SymbolNotFoundException when needed.
else throw IllegalAssignmentException("Can't assign "+name)
}
}
assertEquals("bar", accessor.foo)
assertEquals(null, accessor.bar)
accessor.bar = "buzz"
assertEquals("buzz", accessor.bar)
assertThrows {
accessor.bad = "!23"
}
Of course, you can return any object from dynamic fields; returning lambdas let create _dynamic methods_ - the callable method. It is very convenient to implement libraries with dynamic remote interfaces, etc.
# Theory
## Basic principles:

52
docs/RingBuffer.md Normal file
View File

@ -0,0 +1,52 @@
# RingBuffer
This is a fixed size buffer that allow to store N last elements with _O(1)_ effectiveness (no data shifting).
Here is the sample:
val r = RingBuffer(3)
assert( r is RingBuffer )
assertEquals(0, r.size)
assertEquals(3, r.capacity)
r += 10
assertEquals(1, r.size)
assertEquals(10, r.first)
r += 20
assertEquals(2, r.size)
assertEquals( [10, 20], r.toList() )
r += 30
assertEquals(3, r.size)
assertEquals( [10, 20, 30], r.toList() )
// now first value is lost:
r += 40
assertEquals(3, r.size)
assertEquals( [20, 30, 40], r.toList() )
assertEquals(3, r.capacity)
>>> void
Ring buffer implements [Iterable], so any of its methods are available for `RingBuffer`, e.g. `first`, `last`, `toList`,
`take`, `drop`, `takelast`, `dropLast`, etc.
## Constructor
RinbBuffer(capacity: Int)
## Instance methods
| method | description | remarks |
|-------------|------------------------|---------|
| capacity | max size of the buffer | |
| size | current size | (1) |
| operator += | add new item | (1) |
| add(item) | add new item | (1) |
| iterator() | return iterator | (1) |
(1)
: Ringbuffer is not threadsafe, protect it with a mutex to avoid RC where necessary.
[Iterable]: Iterable.md

View File

@ -1,5 +1,7 @@
# Advanced topics
__See also:__ [parallelism].
## Closures/scopes isolation
Each block has own scope, in which it can safely use closures and override
@ -110,4 +112,49 @@ arguments list in almost arbitrary ways. For example:
)
>>> void
,
# Annotations
Annotation in Lyng resembles these proposed for Javascript. Annotation is just regular functions that, if used as annotation, are called when defining a function, var, val or class.
## Function annotation
When used without params, annotation calls a function with two arguments: actual function name and callable function body. Function annotation __must return callable for the function__, either what it received as a second argument (most often), or something else. Annotation name convention is upper scaled:
var annotated = false
// this is annotation function:
fun Special(name, body) {
assertEquals("foo", name)
annotated = true
{ body(it) + 100 }
}
@Special
fun foo(value) { value + 1 }
assert(annotated)
assertEquals(111, foo( 10 ))
>>> void
Function annotation can have more args specified at call time. There arguments must follow two mandatory ones (name and body). Use default values in order to allow parameterless annotation to be used simultaneously.
val registered = Map()
// it is recommended to provide defaults for extra parameters:
fun Registered(name, body, overrideName = null) {
registered[ overrideName ?: name ] = body
body
}
// witout parameters is Ok as we provided default value
@Registered
fun foo() { "called foo" }
@Registered("bar")
fun foo2() { "called foo2" }
assertEquals(registered["foo"](), "called foo")
assertEquals(registered["bar"](), "called foo2")
>>> void
[parallelism]: parallelism.md

View File

@ -0,0 +1,14 @@
Provide:
fun outer(a1)
// a1 is caller.a1:arg
val a1_local = a1 + 1
// we return lambda:
{ it ->
// a1_local
a1_lcoal + it
}
}

207
docs/parallelism.md Normal file
View File

@ -0,0 +1,207 @@
# Parallelism in Lyng
Lyng is built to me multithreaded where possible (e.g. all targets byt JS and wasmJS as for now)
and cooperatively parallel (coroutine based) everywhere.
In Lyng, every function, every lambda are _coroutines_. It means, you can have as many of these as you want without risking running out of memory on threads stack, or get too many threads.
Depending on the platform, these coroutines may be executed on different CPU and cores, too, truly in parallel. Where not, like Javascript browser, they are still executed cooperatively. You should not care about the platform capabilities, just call `launch`:
// track coroutine call:
var xIsCalled = false
// launch coroutine in parallel
val x = launch {
// wait 10ms to let main code to be executed
delay(10)
// now set the flag
xIsCalled = true
// and return something useful:
"ok"
}
// corouine is launhed, but not yet executed
// due to delay call:
assert(!xIsCalled)
// now we wait for it to be executed:
assertEquals( x.await(), "ok")
// now glag should be set:
assert(xIsCalled)
>>> void
This example shows how to launch a coroutine with `launch` which returns [Deferred] instance, the latter have ways to await for the coroutine completion and retrieve possible result.
Launch has the only argument which should be a callable (lambda usually) that is run in parallel (or cooperatively in parallel), and return anything as the result.
## Synchronization: Mutex
Suppose we have a resource, that could be used concurrently, a coutner in our case. If we won'r protect it, concurrent usage cause RC, Race Condition, providing wrong result:
var counter = 0
(1..4).map {
launch {
// slow increment:
val c = counter
delay(10)
counter = c + 1
}
}.forEach { it.await() }
assert(counter < 4)
>>> void
The obviously wrong result is not 4, as all coroutines capture the counter value, which is 1, then sleep for 5ms, then save 1 + 1 as result. May some coroutines will pass, so it will be 1 or 2, most likely.
Using [Mutex] makes it all working:
var counter = 0
val mutex = Mutex()
(1..4).map {
launch {
// slow increment:
mutex.withLock {
val c = counter
delay(10)
counter = c + 1
}
}
}.forEach { it.await() }
assertEquals(4, counter)
>>> void
now everything works as expected: `mutex.withLock` makes them all be executed in sequence, not in parallel.
## Completable deferred
Sometimes it is convenient to manually set completion status of some deferred result. This is when [CompletableDeferred] is used:
// this variable will be completed later:
val done = CompletableDeferred()
// complete it ater delay
launch {
delay(10)
// complete it setting the result:
done.complete("ok")
}
// now it is still not completed: coroutine is delayed
// (ot not started on sinthe-threaded platforms):
assert(!done.isCompleted)
assert(done.isActive)
// then we can just await it as any other deferred:
assertEquals( done.await(), "ok")
// and as any other deferred it is now complete:
assert(done.isCompleted)
## True parallelism
Cooperative, coroutine-based parallelism is automatically available on all platforms. Depending on the platform, though, the coroutines could be dispatched also in different threads; where there are multiple cores and/or CPU available, it means the coroutines could be exuted truly in parallel, unless [Mutex] is used:
| platofrm | multithreaded |
|------------|---------------|
| JVM | yes |
| Android | yes |
| Javascript | NO |
| wasmJS | NO |
| IOS | yes |
| MacOSX | yes |
| Linux | yes |
| Windows | yes |
So it is important to always use [Mutex] where concurrent execution could be a problem (so called Race Conditions, or RC).
## Yield
When the coroutine is executed, on the single-threaded environment all other coroutines are suspended until active one will wait for something. Sometimes, it is undesirable; the coroutine may perform long calculations or some other CPU consuming task. The solution is to call `yield()` periodically. Unlike `delay()`, yield does not pauses the coroutine for some specified time, but it just makes all other coroutines to be executed. In other word, yield interrupts current coroutines and out it to the end of the dispatcher list of active coroutines. It is especially important on Javascript and wasmJS targets as otherwise UI thread could be blocked.
Usage example:
fun someLongTask() { // ...
do {
// execute step
if( done ) break
yield()
} while(true)
}
# Data exchange for coroutines
## Flow
Flow is an async cold sequence; it is named after kotlin's Flow as it resembles it closely. The cold means the flow is only evaluated when iterated (collected, in Kotlin terms), before it is inactive. Sequence means that it is potentially unlimited, as in our example of glorious Fibonacci number generator:
// Fibonacch numbers flow!
val f = flow {
println("Starting generator")
var n1 = 0
var n2 = 1
emit(n1)
emit(n2)
while(true) {
val n = n1 + n2
emit(n)
n1 = n2
n2 = n
}
}
val correctFibs = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
println("Generation starts")
assertEquals( correctFibs, f.take(correctFibs.size))
>>> Generation starts
>>> Starting generator
>>> void
Great: the generator is not executed until collected bu the `f.take()` call, which picks specified number of elements from the flow, can cancel it.
Important difference from the channels or like, every time you collect the flow, you collect it anew:
var isStarted = false
val f = flow {
emit("start")
isStarted = true
(1..4).forEach { emit(it) }
}
// flow is not yet started, e.g. not got execited,
// that is called 'cold':
assertEquals( false, isStarted )
// let's collect flow:
val result = []
for( x in f ) result += x
println(result)
assertEquals( true, isStarted)
// let's collect it once again, it should be the same:
println(f.toList())
// and again:
assertEquals( result, f.toList() )
>>> ["start", 1, 2, 3, 4]
>>> ["start", 1, 2, 3, 4]
>>> void
Notice that flow's lambda is not called until actual collection is started. Cold flows are
better in terms of resource consumption.
Flows allow easy transforming of any [Iterable]. See how the standard Lyng library functions use it:
fun Iterable.filter(predicate) {
val list = this
flow {
for( item in list ) {
if( predicate(item) ) {
emit(item)
}
}
}
}
[Iterable]: Iterable.md

View File

@ -48,6 +48,7 @@ $$ \ln(1+x)=x-{\dfrac {x^{2}}{2}}+{\dfrac {x^{3}}{3}}-\cdots =\sum \limits _{n=0
}
assert( почти_равны( 0.0005, 0.000501 ) )
Во многих случаях вычисление $n+1$ члена значительно проще cчитается от предыдущего члена, в нашем случае это можно было бы записать через итератор, что мы вскоре добавим.
Во многих случаях вычисление $n+1$ члена значительно проще cчитается от предыдущего члена, в нашем случае это можно было бы записать через итератор, см [Iterable] и `flow` в [parallelism].
(продолжение следует)
[Iterable]: ../Iterable.md
[parallelism]: ../parallelism.md

46
docs/serialization.md Normal file
View File

@ -0,0 +1,46 @@
# Lyng serialization
Lyng has builting binary bit-effective serialization format, called Lynon for LYng Object Notation. It is typed, binary, implements caching, automatic compression, variable-length ints, one-bit Booleans an many nice features.
It is as simple as:
import lyng.serialization
val text = "
We hold these truths to be self-evident, that all men are created equal,
that they are endowed by their Creator with certain unalienable Rights,
that among these are Life, Liberty and the pursuit of Happiness.
"
val encodedBits = Lynon.encode(text)
// decode bits source:
assertEquals( text, Lynon.decode(encodedBits) )
// compression was used automatically
assert( text.length > encodedBits.toBuffer().size )
>>> void
Any class you create is serializable by default; lynon serializes first constructor fields, then any `var` member fields:
import lyng.serialization
class Point(x,y)
val p = Lynon.decode( Lynon.encode( Point(5,6) ) )
assertEquals( 5, p.x )
assertEquals( 6, p.y )
>>> void
just as expected.
Important is to understand that normally `Lynon.decode` wants [BitBuffer], as `Lynon.encode` produces. If you have the regular [Buffer], be sure to convert it:
buffer.toBitInput()
this possibly creates extra zero bits at the end, as bit content could be shorter than byte-grained but for the Lynon format it does not make sense. Note that when you serialize [BitBuffer], exact number of bits is written. To convert bit buffer to bytes:
Lynon.encode("hello").toBuffer()
(topic is incomplete and under construction)

View File

@ -14,8 +14,11 @@ __Other documents to read__ maybe after this one:
- [Advanced topics](advanced_topics.md), [declaring arguments](declaring_arguments.md)
- [OOP notes](OOP.md), [exception handling](exceptions_handling.md)
- [math in Lyng](math.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 loops: [сумма ряда](samples/сумма_ряда.lyng.md). More at [samples folder](samples)
- [time](time.md) and [parallelism](parallelism.md)
- [parallelism] - multithreaded code, coroutines, etc.
- Some class references: [List], [Set], [Map], [Real], [Range], [Iterable], [Iterator], [time manipulation](time.md), [RingBuffer], [Buffer].
- Some samples: [combinatorics](samples/combinatorics.lyng.md), national vars and
loops: [сумма ряда](samples/сумма_ряда.lyng.md). More at [samples folder](samples)
# Expressions
@ -170,7 +173,8 @@ allow to improve code look and readability. There are borrowed from Kotlin:
### let
`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
`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
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
@ -221,6 +225,19 @@ It works much like `also`, but is executed in the context of the source object:
assertEquals(p, Point(2,3))
>>> void
## run
Executes a block after it returning the value passed by the block. for example, can be used with elvis operator:
var someVar = null
val result = someVar ?: run {
someVar = 121
"reset"
}
assertEquals("reset", result)
assertEquals(121, someVar)
>>> void
## Math
It is rather simple, like everywhere else:
@ -464,7 +481,6 @@ after function call, it is treated as a last argument to the call, e.g.:
assert( [11, 21, 31] == mapped)
>>> void
# Lists (aka arrays)
Lyng has built-in mutable array class `List` with simple literals:
@ -472,7 +488,7 @@ Lyng has built-in mutable array class `List` with simple literals:
[1, "two", 3.33].size
>>> 3
[List] is an implementation of the type `Array`, and through it `Collection` and [Iterable].
[List] is an implementation of the type `Array`, and through it `Collection` and [Iterable]. Please read [Iterable], many collection based methods are implemented there.
Lists can contain any type of objects, lists too:
@ -534,7 +550,8 @@ The simplest way to concatenate lists is using `+` and `+=`:
void
>>> void
***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:
***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:
var list = [1, 2]
val other = [3, 4]
@ -562,7 +579,6 @@ Use `list.add` to avoid confusion:
assert( list == [1, 2, [3, 4], (10..12)])
>>> void
To add elements to the list:
val x = [1,2]
@ -595,7 +611,6 @@ Using splat arguments can simplify inserting list in list:
x
>>> [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:
val x = [1,2,3]
@ -741,9 +756,11 @@ Also, you can check the type too:
#### Contains:
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.
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.
Typical builtin types that are containers (e.g. support `conain`):
Typical builtin types that are containers (e.g. support `contains`):
| class | notes |
|------------|------------------------------------------------|
@ -752,12 +769,15 @@ 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
(2)
: 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:
: 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:
assert( "more" in "a".."z") // string range ok
assert( 'x' !in "a".."z") // char in string range: probably error
@ -766,7 +786,9 @@ Typical builtin types that are containers (e.g. support `conain`):
>>> void
(3)
: `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` 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).
So we recommend not to mix characters and string ranges; use `ch in str` that works
as expected:
@ -844,7 +866,8 @@ We can skip the rest of the loop and restart it, as usual, with `continue` opera
"found even numbers: " + countEven
>>> "found even numbers: 5"
`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):
`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):
var count = 0
var total = 0
@ -904,7 +927,8 @@ flowchart TD
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 empty break was executed.
- `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.
- value returned from `break value' statement
- 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.
@ -928,7 +952,8 @@ available in the condition:
} while( continueLoop )
>>> "OK"
This is sometimes convenient when condition is complex and has to be calculated inside the loop body. Notice the value returning by the loop:
This is sometimes convenient when condition is complex and has to be calculated inside the loop body. Notice the value
returning by the loop:
fun readLine() { "done: result" }
val result = do {
@ -939,7 +964,6 @@ This is sometimes convenient when condition is complex and has to be calculated
Suppose readLine() here reads some stream of lines.
## For loops
For loop are intended to traverse collections, and all other objects that supports
@ -982,7 +1006,8 @@ We can use labels too:
# Exception handling
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:
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:
var error = "not caught"
var finallyCaught = false
@ -1035,7 +1060,6 @@ many more.
- see [exception handling](exceptions_handling.md) for detailed exceptions tutorial and reference.
# Self-assignments in expression
There are auto-increments and auto-decrements:
@ -1111,6 +1135,25 @@ These should be imported from [lyng.time](time.md). For example:
See [more docs on time manipulation](time.md)
# Enums
For the moment, only simple enums are implemented. Enum is a list of constants, represented also by their _ordinal_ - [Int] value.
enum Color {
RED, GREEN, BLUE
}
assert( Color.RED is Color )
assertEquals( 2, Color.BLUE.ordinal )
assertEquals( "BLUE", Color.BLUE.name )
assertEquals( [Color.RED,Color.GREEN,Color.BLUE], Color.entries)
assertEquals( Color.valueOf("GREEN"), Color.GREEN )
>>> void
Enums are serialized as ordinals. Please note that due to caching, serialized string arrays could be even more compact than enum arrays, until `Lynon.encodeTyped` will be implemented.
# Comments
// single line comment
@ -1158,7 +1201,6 @@ Are the same as in string literals with little difference:
| code | Int | Unicode code for the character |
| | | |
## String details
Strings are arrays of Unicode characters. It can be indexed, and indexing will
@ -1186,7 +1228,10 @@ To format a string use sprintf-style modifiers like:
assertEquals( "hello :11 ", "%-6s:%-6d"(a, b) )
>>> void
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.
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.
This list will be extended.
@ -1218,7 +1263,7 @@ Concatenation is a `+`: `"hello " + name` works as expected. No confusion.
Typical set of String functions includes:
| fun/prop | description / notes |
|-------------------|------------------------------------------------------------|
|--------------------|------------------------------------------------------------|
| lower() | change case to unicode upper |
| upper() | change case to unicode lower |
| startsWith(prefix) | true if starts with a prefix |
@ -1241,9 +1286,6 @@ Typical set of String functions includes:
(1)
: List is mutable therefore a new copy is created on each call.
### Literals
String literal could be multiline:
@ -1251,16 +1293,39 @@ String literal could be multiline:
"Hello
World"
though multiline literals is yet work in progress.
In this case, it will be passed literally ot "hello\n World". But, if there are
several lines with common left indent, it will be removed, also, forst and last lines,
if blank, will be removed too, for example:
println("
This is a multiline text.
This is a second line.
")
>>> This is a multiline text.
>>> This is a second line.
>>> void
- as expected, empty lines and common indent were removed. It is much like kotlin's `""" ... """.trimIndent()` technique, but simpler ;)
# Built-in functions
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 |
| println(args...) | Open for overriding, it prints to stdout. |
| 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 |
| let, also, apply, run | see above, flow controls |
# Built-in constants
@ -1270,12 +1335,25 @@ See [math functions](math.md). Other general purpose functions are:
| π | See [math](math.md) |
[List]: List.md
[Iterable]: Iterable.md
[Iterator]: Iterator.md
[Real]: Real.md
[Range]: Range.md
[String]: String.md
[string formatting]: https://github.com/sergeych/mp_stools?tab=readme-ov-file#sprintf-syntax-summary
[Set]: Set.md
[Map]: Map.md
[Buffer]: Buffer.md
[parallelism]: parallelism.md
[RingBuffer]: RingBuffer.md

View File

@ -1,3 +1,20 @@
#
# 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.
#
#
#Gradle
org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M"
org.gradle.caching=true

View File

@ -4,7 +4,7 @@ clikt = "5.0.3"
kotlin = "2.1.21"
android-minSdk = "24"
android-compileSdk = "34"
kotlinx-coroutines = "1.10.1"
kotlinx-coroutines = "1.9.0"
mp_bintools = "0.1.12"
firebaseCrashlyticsBuildtools = "3.0.3"
okioVersion = "3.10.2"

View File

@ -1,3 +1,20 @@
#
# 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.
#
#
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip

5
gradlew vendored
View File

@ -1,13 +1,13 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# 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
#
# https://www.apache.org/licenses/LICENSE-2.0
# 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,
@ -15,6 +15,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
##############################################################################
#

View File

@ -1,3 +1,20 @@
/*
* 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.
*
*/
plugins {
kotlin("multiplatform") version "2.1.21"
}

View File

@ -1,3 +1,20 @@
/*
* 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
import com.github.ajalt.clikt.core.CliktCommand
@ -10,10 +27,11 @@ import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import kotlinx.coroutines.runBlocking
import net.sergeych.lyng.LyngVersion
import net.sergeych.lyng.Scope
import net.sergeych.lyng.Script
import net.sergeych.lyng.ScriptError
import net.sergeych.lyng.Source
import net.sergeych.lyng.obj.*
import net.sergeych.mp_tools.globalDefer
import okio.FileSystem
import okio.Path.Companion.toPath
import okio.SYSTEM
@ -38,14 +56,13 @@ data class CommandResult(
val error: String
)
val baseScope = Scope().apply {
val baseScopeDefer = globalDefer {
Script.newScope().apply {
addFn("exit") {
exit(requireOnlyArg<ObjInt>().toInt())
ObjVoid
}
// ObjString.type.addFn("shell") {
//
// }
}
}
fun runMain(args: Array<String>) {
@ -88,6 +105,8 @@ private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand()
""".trimIndent()
override fun run() {
runBlocking {
val baseScope = baseScopeDefer.await()
when {
version -> {
println("Lyng language version ${LyngVersion}")
@ -128,10 +147,11 @@ private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand()
}
}
}
}
fun executeFileWithArgs(fileName: String, args: List<String>) {
runBlocking {
baseScope.addConst("ARGV", ObjList(args.map { ObjString(it) }.toMutableList()))
baseScopeDefer.await().addConst("ARGV", ObjList(args.map { ObjString(it) }.toMutableList()))
executeFile(fileName)
}
}
@ -148,7 +168,7 @@ suspend fun executeFile(fileName: String) {
text = text.substring(pos + 1)
}
processErrors {
baseScope.eval(Source(fileName, text))
baseScopeDefer.await().eval(Source(fileName, text))
}
}

View File

@ -1,3 +1,20 @@
/*
* 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
import kotlin.system.exitProcess

View File

@ -1,3 +1,20 @@
/*
* 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.
*
*/
@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
package net.sergeych

View File

@ -1,3 +1,20 @@
/*
* 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_cli
import net.sergeych.runMain

View File

@ -1,3 +1,20 @@
/*
* 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.
*
*/
import net.sergeych.runMain
fun main(args: Array<String>) {

View File

@ -1,4 +1,23 @@
@file:OptIn(ExperimentalForeignApi::class, ExperimentalForeignApi::class, ExperimentalForeignApi::class)
/*
* 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.
*
*/
@file:OptIn(ExperimentalForeignApi::class, ExperimentalForeignApi::class, ExperimentalForeignApi::class,
ExperimentalForeignApi::class
)
package net.sergeych

View File

@ -1,10 +1,27 @@
/*
* 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.
*
*/
import com.codingfeline.buildkonfig.compiler.FieldSpec.Type.STRING
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
group = "net.sergeych"
version = "0.8.0-SNAPSHOT"
version = "0.8.14-SNAPSHOT"
buildscript {
repositories {
@ -65,6 +82,7 @@ kotlin {
languageSettings.optIn("kotlin.contracts.ExperimentalContracts")
languageSettings.optIn("kotlin.ExperimentalUnsignedTypes")
languageSettings.optIn("kotlin.coroutines.DelicateCoroutinesApi")
languageSettings.optIn("kotlinx.coroutines.flow.DelicateCoroutinesApi")
}
val commonMain by getting {

View File

@ -1,3 +1,20 @@
/*
* 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
enum class AccessType(val isMutable: Boolean) {

View File

@ -1,19 +0,0 @@
package net.sergeych.lyng
import net.sergeych.lyng.obj.ObjRecord
/**
* Special version of the [Scope] used to `apply` new this object to
* _parent context property.
*
* @param _parent context to apply to
* @param args arguments for the new context
* @param appliedScope the new context to apply, it will have lower priority except for `this` which
* will be reset by appliedContext's `this`.
*/
class AppliedScope(_parent: Scope, args: Arguments, val appliedScope: Scope)
: Scope(_parent, args, appliedScope.pos, appliedScope.thisObj) {
override fun get(name: String): ObjRecord? =
if (name == "this") thisObj.asReadonly
else super.get(name) ?: appliedScope[name]
}

View File

@ -1,3 +1,20 @@
/*
* 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
import net.sergeych.lyng.obj.Obj
@ -31,12 +48,12 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
arguments: Arguments = scope.args,
defaultAccessType: AccessType = AccessType.Var,
defaultVisibility: Visibility = Visibility.Public,
defaultRecordType: ObjRecord.Type = ObjRecord.Type.ConstructorField
) {
fun assign(a: Item, value: Obj) {
scope.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, value,
scope.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable,
value.byValueCopy(),
a.visibility ?: defaultVisibility,
recordType = defaultRecordType)
recordType = ObjRecord.Type.Argument)
}
// will be used with last lambda arg fix

View File

@ -1,3 +1,20 @@
/*
* 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
import net.sergeych.lyng.obj.Obj
@ -36,7 +53,7 @@ data class Arguments(val list: List<Obj>, val tailBlockMode: Boolean = false) :
fun firstAndOnly(pos: Pos = Pos.UNKNOWN): Obj {
if (list.size != 1) throw ScriptError(pos, "expected one argument, got ${list.size}")
return list.first()
return list.first().byValueCopy()
}
/**
@ -48,7 +65,6 @@ data class Arguments(val list: List<Obj>, val tailBlockMode: Boolean = false) :
fun inspect(): String = list.joinToString(", ") { it.inspect() }
companion object {
val EMPTY = Arguments(emptyList())
fun from(values: Collection<Obj>) = Arguments(values.toList())

View File

@ -1,3 +1,20 @@
/*
* 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
//fun buildDoubleFromParts(

View File

@ -0,0 +1,56 @@
/*
* 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
import net.sergeych.lyng.obj.ObjRecord
/**
* Scope that adds a "closure" to caller; most often it is used to apply class instance to caller scope.
* Inherits [Scope.args] and [Scope.thisObj] from [callScope] and adds lookup for symbols
* from [closureScope] with proper precedence
*/
class ClosureScope(val callScope: Scope, val closureScope: Scope) :
Scope(callScope, callScope.args, thisObj = callScope.thisObj) {
override fun get(name: String): ObjRecord? {
// we take arguments from the callerScope, the rest
// from the closure.
// note using super, not callScope, as arguments are assigned by the constructor
// and are not assigned yet to vars in callScope self:
super.objects[name]?.let {
// if( name == "predicate" ) {
// println("predicate: ${it.type.isArgument}: ${it.value}")
// }
if( it.type.isArgument ) return it
}
return closureScope.get(name)
}
}
class ApplyScope(_parent: Scope,val applied: Scope) : Scope(_parent, thisObj = applied.thisObj) {
override fun get(name: String): ObjRecord? {
return applied.get(name) ?: super.get(name)
}
override fun applyClosure(closure: Scope): Scope {
return this
}
}

View File

@ -0,0 +1,24 @@
/*
* 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
sealed class CodeContext {
class Module(@Suppress("unused") val packageName: String?): CodeContext()
class Function(val name: String): CodeContext()
class ClassBody(val name: String): CodeContext()
}

View File

@ -1,5 +1,23 @@
/*
* 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
import ObjEnumClass
import net.sergeych.lyng.obj.*
import net.sergeych.lyng.pacman.ImportProvider
@ -17,6 +35,27 @@ class Compiler(
class Settings
private val initStack = mutableListOf<MutableList<Statement>>()
val currentInitScope: MutableList<Statement>
get() =
initStack.lastOrNull() ?: cc.syntaxError("no initialization scope exists here")
private fun pushInitScope(): MutableList<Statement> = mutableListOf<Statement>().also { initStack.add(it) }
private fun popInitScope(): MutableList<Statement> = initStack.removeLast()
private val codeContexts = mutableListOf<CodeContext>(CodeContext.Module(null))
private suspend fun <T> inCodeContext(context: CodeContext, f: suspend () -> T): T {
return try {
codeContexts.add(context)
f()
} finally {
codeContexts.removeLast()
}
}
private suspend fun parseScript(): Script {
val statements = mutableListOf<Statement>()
val start = cc.currentPos()
@ -55,9 +94,17 @@ class Compiler(
}
}
}
parseStatement(braceMeansLambda = true)?.also {
val s = parseStatement(braceMeansLambda = true)?.also {
statements += it
} ?: break
}
if (s == null) {
when (t.type) {
Token.Type.RBRACE, Token.Type.EOF, Token.Type.SEMICOLON -> {}
else ->
throw ScriptError(t.pos, "unexpeced `${t.value}` here")
}
break
}
} while (true)
return Script(start, statements)//returnScope.needCatch)
@ -78,7 +125,10 @@ class Compiler(
return result.toString()
}
private var lastAnnotation: (suspend (Scope, ObjString, Statement) -> Statement)? = null
private suspend fun parseStatement(braceMeansLambda: Boolean = false): Statement? {
lastAnnotation = null
while (true) {
val t = cc.next()
return when (t.type) {
@ -95,6 +145,11 @@ class Compiler(
parseExpression()
}
Token.Type.ATLABEL -> {
lastAnnotation = parseAnnotation(t)
continue
}
Token.Type.LABEL -> continue
Token.Type.SINLGE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT -> continue
@ -321,6 +376,13 @@ class Compiler(
}
"throw" -> {
val s = parseThrowStatement()
operand = Accessor {
s.execute(it).asReadonly
}
}
else -> operand?.let { left ->
// selector: <lvalue>, '.' , <id>
// we replace operand with selector code, that
@ -460,7 +522,7 @@ class Compiler(
val callStatement = statement {
// and the source closure of the lambda which might have other thisObj.
val context = AppliedScope(closure!!, args, this)
val context = this.applyClosure(closure!!)
if (argsDeclaration == null) {
// no args: automatic var 'it'
val l = args.list
@ -472,7 +534,7 @@ class Compiler(
// more args: it is a list of args
else -> ObjList(l.toMutableList())
}
context.addItem("it", false, itValue)
context.addItem("it", false, itValue, recordType = ObjRecord.Type.Argument)
} else {
// assign vars as declared the standard way
argsDeclaration.assignToContext(context, defaultAccessType = AccessType.Val)
@ -481,7 +543,7 @@ class Compiler(
}
return Accessor { x ->
if (closure == null) closure = x
closure = x
callStatement.asReadonly
}
}
@ -800,6 +862,26 @@ class Compiler(
return parseNumberOrNull(isPlus) ?: throw ScriptError(cc.currentPos(), "Expecting number")
}
suspend fun parseAnnotation(t: Token): (suspend (Scope, ObjString, Statement) -> Statement) {
val extraArgs = parseArgsOrNull()
println("annotation ${t.value}: args: $extraArgs")
return { scope, name, body ->
val extras = extraArgs?.first?.toArguments(scope, extraArgs.second)?.list
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}")
(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))
parseArgs()
else
null
/**
* Parse keyword-starting statement.
* @return parsed statement or null if, for example. [id] is not among keywords
@ -813,7 +895,8 @@ class Compiler(
"break" -> parseBreakStatement(id.pos)
"continue" -> parseContinueStatement(id.pos)
"if" -> parseIfStatement()
"class" -> parseClassDeclaration(false)
"class" -> parseClassDeclaration()
"enum" -> parseEnumDeclaration()
"try" -> parseTryStatement()
"throw" -> parseThrowStatement()
"when" -> parseWhenStatement()
@ -823,6 +906,18 @@ class Compiler(
val isExtern = cc.skipId("extern")
when {
cc.matchQualifiers("fun", "private") -> parseFunctionDeclaration(Visibility.Private, isExtern)
cc.matchQualifiers("fun", "private", "static") -> parseFunctionDeclaration(
Visibility.Private,
isExtern,
isStatic = true
)
cc.matchQualifiers("fun", "static") -> parseFunctionDeclaration(
Visibility.Public,
isExtern,
isStatic = true
)
cc.matchQualifiers("fn", "private") -> parseFunctionDeclaration(Visibility.Private, isExtern)
cc.matchQualifiers("fun", "open") -> parseFunctionDeclaration(isOpen = true, isExtern = isExtern)
cc.matchQualifiers("fn", "open") -> parseFunctionDeclaration(isOpen = true, isExtern = isExtern)
@ -830,7 +925,21 @@ class Compiler(
cc.matchQualifiers("fun") -> parseFunctionDeclaration(isOpen = false, isExtern = isExtern)
cc.matchQualifiers("fn") -> parseFunctionDeclaration(isOpen = false, isExtern = isExtern)
cc.matchQualifiers("val", "private", "static") -> parseVarDeclaration(
false,
Visibility.Private,
isStatic = true
)
cc.matchQualifiers("val", "static") -> parseVarDeclaration(false, Visibility.Public, isStatic = true)
cc.matchQualifiers("val", "private") -> parseVarDeclaration(false, Visibility.Private)
cc.matchQualifiers("var", "static") -> parseVarDeclaration(true, Visibility.Public, isStatic = true)
cc.matchQualifiers("var", "static", "private") -> parseVarDeclaration(
true,
Visibility.Private,
isStatic = true
)
cc.matchQualifiers("var", "private") -> parseVarDeclaration(true, Visibility.Private)
cc.matchQualifiers("val", "open") -> parseVarDeclaration(false, Visibility.Private, true)
cc.matchQualifiers("var", "open") -> parseVarDeclaration(true, Visibility.Private, true)
@ -1074,8 +1183,44 @@ class Compiler(
}
}
private suspend fun parseClassDeclaration(isStruct: Boolean): Statement {
private fun parseEnumDeclaration(): Statement {
val nameToken = cc.requireToken(Token.Type.ID)
// so far only simplest enums:
val names = mutableListOf<String>()
// skip '{'
cc.skipTokenOfType(Token.Type.LBRACE)
do {
val t = cc.skipWsTokens()
when (t.type) {
Token.Type.ID -> {
names += t.value
val t1 = cc.skipWsTokens()
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)
return statement {
ObjEnumClass.createSimpleEnum(nameToken.value, names).also {
addItem(nameToken.value, false, it, recordType = ObjRecord.Type.Enum)
}
}
}
private suspend fun parseClassDeclaration(): Statement {
val nameToken = cc.requireToken(Token.Type.ID)
return inCodeContext(CodeContext.ClassBody(nameToken.value)) {
val constructorArgsDeclaration =
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
parseArgsDeclaration(isClassDeclaration = true)
@ -1090,6 +1235,8 @@ class Compiler(
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
val t = cc.next()
pushInitScope()
val bodyInit: Statement? = if (t.type == Token.Type.LBRACE) {
// parse body
parseScript().also {
@ -1100,11 +1247,13 @@ class Compiler(
null
}
val initScope = popInitScope()
// create class
val className = nameToken.value
@Suppress("UNUSED_VARIABLE") val defaultAccess = if (isStruct) AccessType.Var else AccessType.Initialization
@Suppress("UNUSED_VARIABLE") val defaultVisibility = Visibility.Public
// @Suppress("UNUSED_VARIABLE") val defaultAccess = if (isStruct) AccessType.Var else AccessType.Initialization
// @Suppress("UNUSED_VARIABLE") val defaultVisibility = Visibility.Public
// create instance constructor
// create custom objClass with all fields and instance constructor
@ -1127,14 +1276,22 @@ class Compiler(
constructorMeta = constructorArgsDeclaration
}
return statement {
statement {
// the main statement should create custom ObjClass instance with field
// accessors, constructor registration, etc.
addItem(className, false, newClass)
if (initScope.isNotEmpty()) {
val classScope = copy(newThisObj = newClass)
newClass.classScope = classScope
for (s in initScope)
s.execute(classScope)
}
newClass
}
}
}
private fun getLabel(maxDepth: Int = 2): String? {
var cnt = 0
@ -1506,7 +1663,8 @@ class Compiler(
private suspend fun parseFunctionDeclaration(
visibility: Visibility = Visibility.Public,
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false,
isExtern: Boolean = false
isExtern: Boolean = false,
isStatic: Boolean = false,
): Statement {
var t = cc.next()
val start = t.pos
@ -1515,6 +1673,9 @@ class Compiler(
throw ScriptError(t.pos, "Expected identifier after 'fun'")
else t.value
val annotation = lastAnnotation
val parentContext = codeContexts.last()
t = cc.next()
// Is extension?
if (t.type == Token.Type.DOT) {
@ -1538,6 +1699,8 @@ class Compiler(
if (cc.current().type == Token.Type.COLON) parseTypeDeclaration()
return inCodeContext(CodeContext.Function(name)) {
// Here we should be at open body
val fnStatements = if (isExtern)
statement { raiseError("extern function not provided: $name") }
@ -1548,9 +1711,12 @@ class Compiler(
val fnBody = statement(t.pos) { callerContext ->
callerContext.pos = start
// restore closure where the function was defined, and making a copy of it
// for local space (otherwise it will write local stuff to closure!)
val context = closure?.copy() ?: callerContext.raiseError("bug: closure not set")
// for local space. If there is no closure, we are in, say, class context where
// the closure is in the class initialization and we needn't more:
val context = closure?.let { ClosureScope(callerContext, it) }
?: callerContext
// load params from caller context
argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val)
@ -1559,23 +1725,39 @@ class Compiler(
}
fnStatements.execute(context)
}
return statement(start) { context ->
val fnCreateStatement = statement(start) { context ->
// we added fn in the context. now we must save closure
// for the function
// for the function, unless we're in the class scope:
if (isStatic || parentContext !is CodeContext.ClassBody)
closure = context
val annotatedFnBody = annotation?.invoke(context, ObjString(name), fnBody)
?: fnBody
extTypeName?.let { typeName ->
// class extension method
val type = context[typeName]?.value ?: context.raiseSymbolNotFound("class $typeName not found")
if (type !is ObjClass) context.raiseClassCastError("$typeName is not the class instance")
type.addFn(name, isOpen = true) {
fnBody.execute(this)
// ObjInstance has a fixed instance scope, so we need to build a closure
(thisObj as? ObjInstance)?.let { i ->
annotatedFnBody.execute(ClosureScope(this, i.instanceScope))
}
// other classes can create one-time scope for this rare case:
?: annotatedFnBody.execute(thisObj.autoInstanceScope(this))
}
}
// regular function/method
?: context.addItem(name, false, fnBody, visibility)
?: context.addItem(name, false, annotatedFnBody, visibility)
// as the function can be called from anywhere, we have
// saved the proper context in the closure
fnBody
annotatedFnBody
}
if (isStatic) {
currentInitScope += fnCreateStatement
NopStatement
} else
fnCreateStatement
}
}
@ -1600,7 +1782,8 @@ class Compiler(
private suspend fun parseVarDeclaration(
isMutable: Boolean,
visibility: Visibility,
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false,
isStatic: Boolean = false
): Statement {
val nameToken = cc.next()
val start = nameToken.pos
@ -1610,6 +1793,10 @@ class Compiler(
val eqToken = cc.next()
var setNull = false
val isDelegate = if (eqToken.isId("by")) {
true
} else {
if (eqToken.type != Token.Type.ASSIGN) {
if (!isMutable)
throw ScriptError(start, "val must be initialized")
@ -1618,22 +1805,60 @@ class Compiler(
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?
// 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)
addItem(name, isMutable, initValue, visibility, ObjRecord.Type.Field)
ObjVoid
}
return NopStatement
}
return statement(nameToken.pos) { context ->
if (context.containsLocal(name))
throw ScriptError(nameToken.pos, "Variable $name is already defined")
if (isDelegate) {
TODO()
// 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
}
}
}
data class Operator(
val tokenType: Token.Type,
@ -1750,7 +1975,15 @@ class Compiler(
Operator.simple(Token.Type.IS, lastPriority) { _, a, b -> ObjBool(a.isInstanceOf(b)) },
Operator.simple(Token.Type.NOTIS, lastPriority) { _, a, b -> ObjBool(!a.isInstanceOf(b)) },
Operator.simple(Token.Type.ELVIS, ++lastPriority) { _, a, b -> if (a == ObjNull) b else a },
Operator(Token.Type.ELVIS, ++lastPriority, 2) { _: Pos, a: Accessor, b: Accessor ->
Accessor {
val aa = a.getter(it).value
(
if (aa != ObjNull) aa
else b.getter(it).value
).asReadonly
}
},
// shuttle <=> 6
Operator.simple(Token.Type.SHUTTLE, ++lastPriority) { c, a, b ->

View File

@ -1,3 +1,20 @@
/*
* 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
class CompilerContext(val tokens: List<Token>) {
@ -49,6 +66,10 @@ class CompilerContext(val tokens: List<Token>) {
throw ScriptError(at, message)
}
fun syntaxError(message: String = "Syntax error"): Nothing {
throw ScriptError(currentPos(), message)
}
fun currentPos(): Pos = tokens[currentIndex].pos
/**
@ -89,6 +110,20 @@ class CompilerContext(val tokens: List<Token>) {
} else true
}
/**
* If next token is one of these types, skip it.
* @return true if token was found and skipped
*/
fun skipNextIf(vararg types: Token.Type): Boolean {
val t = next()
return if (t.type in types)
true
else {
previous()
false
}
}
@Suppress("unused")
fun skipTokens(vararg tokenTypes: Token.Type) {
while (next().type in tokenTypes) { /**/
@ -144,10 +179,15 @@ class CompilerContext(val tokens: List<Token>) {
when (t.type) {
Token.Type.ID -> {
if (t.value in qualifiers) count++
else { restorePos(pos); return false }
else {
restorePos(pos); return false
}
}
Token.Type.MULTILINE_COMMENT, Token.Type.SINLGE_LINE_COMMENT, Token.Type.NEWLINE -> {}
else -> { restorePos(pos); return false }
else -> {
restorePos(pos); return false
}
}
}
val t = next()

View File

@ -1,3 +1,20 @@
/*
* 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
import net.sergeych.lyng.obj.Accessor

View File

@ -1,3 +1,20 @@
/*
* 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
import net.sergeych.lyng.obj.Obj

View File

@ -1,3 +1,20 @@
/*
* 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
import net.sergeych.lyng.obj.ObjRecord

View File

@ -1,3 +1,20 @@
/*
* 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
val digitsSet = ('0'..'9').toSet()
@ -375,6 +392,32 @@ private class Parser(fromPos: Pos) {
private val currentChar: Char get() = pos.currentChar
private fun fixMultilineStringLiteral(source: String): String {
val sizes = mutableListOf<Int>()
val lines = source.lines().toMutableList()
if( lines.size == 0 ) return ""
if( lines[0].isBlank() ) lines.removeFirst()
if( lines.isEmpty()) return ""
if( lines.last().isBlank() ) lines.removeLast()
val normalized = lines.map { l ->
if( l.isBlank() ) {
sizes.add(-1)
""
}
else {
val margin = leftMargin(l)
sizes += margin
" ".repeat(margin) + l.trim()
}
}
val commonMargin = sizes.filter { it >= 0 }.min()
val fixed = if( commonMargin < 1 ) lines else normalized.map {
if( it.isBlank() ) "" else it.drop(commonMargin)
}
return fixed.joinToString("\n")
}
private fun loadStringToken(): Token {
val start = currentPos
@ -383,6 +426,7 @@ private class Parser(fromPos: Pos) {
// start = start.back()
val sb = StringBuilder()
var newlineDetected = false
while (currentChar != '"') {
if (pos.end) throw ScriptError(start, "unterminated string started there")
when (currentChar) {
@ -397,6 +441,12 @@ private class Parser(fromPos: Pos) {
}
}
'\n', '\r'-> {
newlineDetected = true
sb.append(currentChar)
pos.advance()
}
else -> {
sb.append(currentChar)
pos.advance()
@ -404,7 +454,10 @@ private class Parser(fromPos: Pos) {
}
}
pos.advance()
return Token(sb.toString(), start, Token.Type.STRING)
val result = sb.toString().let { if( newlineDetected ) fixMultilineStringLiteral(it) else it }
return Token(result, start, Token.Type.STRING)
}
/**

View File

@ -1,3 +1,20 @@
/*
* 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
data class Pos(val source: Source, val line: Int, val column: Int) {

View File

@ -1,3 +1,20 @@
/*
* 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
import net.sergeych.lyng.obj.*
@ -16,7 +33,7 @@ import net.sergeych.lyng.pacman.ImportProvider
*
* There are special types of scopes:
*
* - [AppliedScope] - scope used to apply a closure to some thisObj scope
* - [ClosureScope] - scope used to apply a closure to some thisObj scope
*/
open class Scope(
val parent: Scope?,
@ -50,6 +67,9 @@ open class Scope(
fun raiseIllegalState(message: String = "Illegal argument error"): Nothing =
raiseError(ObjIllegalStateException(this, message))
fun raiseIllegalAssignment(message: String): Nothing =
raiseError(ObjIllegalAssignmentException(this, message))
@Suppress("unused")
fun raiseNoSuchElement(message: String = "No such element"): Nothing =
raiseError(ObjIllegalArgumentException(this, message))
@ -68,9 +88,14 @@ open class Scope(
throw ExecutionError(obj)
}
@Suppress("unused")
fun raiseNotFound(message: String="not found"): Nothing {
throw ExecutionError(ObjNotFoundException(this,message))
}
inline fun <reified T : Obj> requiredArg(index: Int): T {
if (args.list.size <= index) raiseError("Expected at least ${index + 1} argument, got ${args.list.size}")
return (args.list[index] as? T)
return (args.list[index].byValueCopy() as? T)
?: raiseClassCastError("Expected type ${T::class.simpleName}, got ${args.list[index]::class.simpleName}")
}
@ -91,17 +116,31 @@ open class Scope(
raiseError("This function does not accept any arguments")
}
inline fun <reified T : Obj> thisAs(): T = (thisObj as? T)
?: raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")
inline fun <reified T : Obj> thisAs(): T {
var s: Scope? = this
do {
val t = s!!.thisObj
if (t is T) return t
s = s.parent
} while(s != null)
raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")
}
internal val objects = mutableMapOf<String, ObjRecord>()
open operator fun get(name: String): ObjRecord? =
if (name == "this") thisObj.asReadonly
else {
objects[name]
(objects[name]
?: parent?.get(name)
?: thisObj.objClass.getInstanceMemberOrNull(name)
?: thisObj.objClass
.getInstanceMemberOrNull(name)
)
// ?.also {
// if( name == "predicate") {
// println("got predicate $it")
// }
// }
}
fun copy(pos: Pos, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Scope =
@ -153,13 +192,14 @@ open class Scope(
fun addConst(name: String, value: Obj) = addItem(name, false, value)
suspend fun eval(code: String): Obj =
Compiler.compile(code.toSource(), currentImportProvider).execute(this)
eval(code.toSource())
suspend fun eval(source: Source): Obj =
Compiler.compile(
suspend fun eval(source: Source): Obj {
return Compiler.compile(
source,
currentImportProvider
).execute(this)
}
fun containsLocal(name: String): Boolean = name in objects
@ -190,6 +230,27 @@ open class Scope(
val importManager by lazy { (currentImportProvider as? ImportManager)
?: throw IllegalStateException("this scope has no manager in the chain (provided $currentImportProvider") }
override fun toString(): String {
val contents = objects.entries.joinToString { "${if( it.value.isMutable ) "var" else "val" } ${it.key}=${it.value.value}" }
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
}
open fun applyClosure(closure: Scope): Scope = ClosureScope(this, closure)
companion object {
fun new(): Scope =

View File

@ -1,9 +1,29 @@
/*
* 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
import kotlinx.coroutines.delay
import kotlinx.coroutines.yield
import net.sergeych.lyng.obj.*
import net.sergeych.lyng.pacman.ImportManager
import net.sergeych.lyng.stdlib_included.rootLyng
import net.sergeych.lynon.ObjLynonClass
import net.sergeych.mp_tools.globalDefer
import kotlin.math.*
class Script(
@ -20,12 +40,28 @@ class Script(
return lastResult
}
suspend fun execute() = execute(defaultImportManager.newModule())
suspend fun execute() = execute(
defaultImportManager.newStdScope()
)
companion object {
private val rootScope: Scope = Scope(null).apply {
/**
* Create new scope using standard safe set of modules, using [defaultImportManager]. It is
* suspended as first time calls requires compilation of standard library or other
* asynchronous initialization.
*/
suspend fun newScope(pos: Pos = Pos.builtIn) = defaultImportManager.newStdScope(pos)
internal val rootScope: Scope = Scope(null).apply {
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") {
for ((i, a) in args.withIndex()) {
if (i > 0) print(' ' + a.asStr.value)
@ -152,6 +188,31 @@ class Script(
result ?: raiseError(ObjAssertionFailedException(this,"Expected exception but nothing was thrown"))
}
addFn("dynamic") {
ObjDynamic.create(this, requireOnlyArg())
}
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") {
this.trace(args.getOrNull(0)?.toString() ?: "")
ObjVoid
}
addVoidFn("delay") {
delay((this.args.firstAndOnly().toDouble()/1000.0).roundToLong())
}
@ -173,8 +234,31 @@ class Script(
addConst("Iterable", ObjIterable)
addConst("Collection", ObjCollection)
addConst("Array", ObjArray)
addConst("RingBuffer", ObjRingBuffer.type)
addConst("Class", ObjClassType)
addConst("Deferred", ObjDeferred.type)
addConst("CompletableDeferred", ObjCompletableDeferred.type)
addConst("Mutex", ObjMutex.type)
addFn("launch") {
val callable = requireOnlyArg<Statement>()
ObjDeferred(globalDefer {
callable.execute(this@addFn)
})
}
addFn("yield") {
yield()
ObjVoid
}
addFn("flow") {
// important is: current context contains closure often used in call;
// we'll need it for the producer
ObjFlow(requireOnlyArg<Statement>(), this)
}
val pi = ObjReal(PI)
addConst("π", pi)
getOrCreateNamespace("Math").apply {
@ -184,6 +268,9 @@ class Script(
val defaultImportManager: ImportManager by lazy {
ImportManager(rootScope, SecurityManager.allowAll).apply {
addTextPackages(
rootLyng
)
addPackage("lyng.buffer") {
it.addConst("Buffer", ObjBuffer.type)
it.addConst("MutableBuffer", ObjMutableBuffer.type)

View File

@ -1,3 +1,20 @@
/*
* 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.
*
*/
@file:Suppress("CanBeParameter")
package net.sergeych.lyng
@ -14,6 +31,8 @@ open class ScriptError(val pos: Pos, val errorMessage: String, cause: Throwable?
cause
)
class ScriptFlowIsNoMoreCollected: Exception()
class ExecutionError(val errorObject: ObjException) : ScriptError(errorObject.scope.pos, errorObject.message)
class ImportException(pos: Pos, message: String) : ScriptError(pos, message)

View File

@ -1,3 +1,20 @@
/*
* 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
interface SecurityManager {

View File

@ -1,3 +1,20 @@
/*
* 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
class Source(val fileName: String, text: String) {

View File

@ -1,8 +1,33 @@
/*
* 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
data class Token(val value: String, val pos: Pos, val type: Type) {
fun raiseSyntax(text: String): Nothing {
throw ScriptError(pos, text)
}
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,

View File

@ -1,3 +1,20 @@
/*
* 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.
*
*/
@file:Suppress("unused")
package net.sergeych.lyng

View File

@ -1,3 +1,20 @@
/*
* 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
enum class Visibility {

View File

@ -1,3 +1,20 @@
/*
* 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

View File

@ -1,3 +1,20 @@
/*
* 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

View File

@ -1,3 +1,20 @@
/*
* 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 kotlinx.coroutines.sync.Mutex
@ -55,7 +72,8 @@ open class Obj {
scope: Scope,
name: String,
args: Arguments = Arguments.EMPTY
): T = invokeInstanceMethod(scope, name, args) as T
): T =
invokeInstanceMethod(scope, name, args) as T
/**
* Invoke a method of the object if exists
@ -67,10 +85,15 @@ open class Obj {
name: String,
args: Arguments = Arguments.EMPTY,
onNotFoundResult: Obj?=null
): Obj =
objClass.getInstanceMemberOrNull(name)?.value?.invoke(scope, this, args)
): Obj {
return objClass.getInstanceMemberOrNull(name)?.value?.invoke(
scope,
this,
args
)
?: onNotFoundResult
?: scope.raiseSymbolNotFound(name)
}
open suspend fun getInstanceMethod(
scope: Scope,
@ -140,6 +163,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
@ -251,12 +276,23 @@ open class Obj {
scope.raiseNotImplemented()
}
fun autoInstanceScope(parent: Scope): Scope {
val scope = parent.copy(newThisObj = this, args = parent.args)
for( m in objClass.members) {
scope.objects[m.key] = m.value
}
return scope
}
companion object {
val rootObjectType = ObjClass("Obj").apply {
addFn("toString") {
thisObj.asStr
}
addFn("inspect", true) {
thisObj.inspect().toObj()
}
addFn("contains") {
ObjBool(thisObj.contains(this, args.firstAndOnly()))
}
@ -265,15 +301,23 @@ open class Obj {
args.firstAndOnly().callOn(copy(Arguments(thisObj)))
}
addFn("apply") {
val newContext = (thisObj as? ObjInstance)?.instanceScope ?: this
args.firstAndOnly()
.callOn(newContext)
val body = args.firstAndOnly()
(thisObj as? ObjInstance)?.let {
println("apply in ${thisObj is ObjInstance}, ${it.instanceScope}")
body.callOn(ApplyScope(this, it.instanceScope))
} ?: run {
println("apply on non-instance $thisObj")
body.callOn(this)
}
thisObj
}
addFn("also") {
args.firstAndOnly().callOn(copy(Arguments(thisObj)))
thisObj
}
addFn("run") {
args.firstAndOnly().callOn(this)
}
addFn("getAt") {
requireExactCount(1)
thisObj.getAt(this, requiredArg<Obj>(0))
@ -501,6 +545,7 @@ open class ObjException(exceptionClass: ExceptionClass, val scope: Scope, val me
"IterationEndException",
"AccessException",
"UnknownException",
"NotFoundException"
)) {
scope.addConst(name, getOrCreateExceptionClass(name))
}
@ -544,3 +589,6 @@ class ObjUnknownException(scope: Scope, message: String = "access not allowed er
class ObjIllegalOperationException(scope: Scope, message: String = "Operation is illegal") :
ObjException("IllegalOperationException", scope, message)
class ObjNotFoundException(scope: Scope, message: String = "not found") :
ObjException("NotFoundException", scope, message)

View File

@ -1,3 +1,20 @@
/*
* 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
val ObjArray by lazy {

View File

@ -1,3 +1,20 @@
/*
* 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.Scope

View File

@ -1,3 +1,20 @@
/*
* 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.bintools.toDump

View File

@ -1,3 +1,20 @@
/*
* 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.Scope

View File

@ -1,20 +1,44 @@
/*
* 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 kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList
import net.sergeych.bintools.decodeHex
import net.sergeych.bintools.encodeToHex
import net.sergeych.bintools.toDump
import net.sergeych.lyng.Scope
import net.sergeych.lyng.statement
import net.sergeych.lynon.BitArray
import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType
import net.sergeych.mp_tools.decodeBase64Url
import net.sergeych.mp_tools.encodeToBase64Url
import kotlin.math.min
open class ObjBuffer(val byteArray: UByteArray) : Obj() {
override val objClass: ObjClass = type
val hex by lazy { byteArray.encodeToHex("")}
val base64 by lazy { byteArray.toByteArray().encodeToBase64Url()}
fun checkIndex(scope: Scope, index: Obj): Int {
if (index !is ObjInt)
scope.raiseIllegalArgument("index must be Int")
@ -66,9 +90,7 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() {
} else scope.raiseIllegalArgument("can't concatenate buffer with ${other.inspect()}")
}
override fun toString(): String {
return "Buffer(${byteArray.encodeToHex()})"
}
override fun toString(): String = base64
override fun equals(other: Any?): Boolean {
if (this === other) return true
@ -85,6 +107,8 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() {
encoder.encodeCachedBytes(byteArray.asByteArray())
}
override fun inspect(): String = "Buf($base64)"
companion object {
private suspend fun createBufferFrom(scope: Scope, obj: Obj): ObjBuffer =
when (obj) {
@ -141,11 +165,27 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() {
})
}.apply {
addClassFn("decodeBase64") {
ObjBuffer(requireOnlyArg<Obj>().toString().decodeBase64Url().asUByteArray())
}
addClassFn("decodeHex") {
ObjBuffer(requireOnlyArg<Obj>().toString().decodeHex().asUByteArray())
}
createField("size",
statement {
(thisObj as ObjBuffer).byteArray.size.toObj()
}
)
createField("hex",
statement {
thisAs<ObjBuffer>().hex.toObj()
}
)
createField("base64",
statement {
thisAs<ObjBuffer>().base64.toObj()
}
)
addFn("decodeUtf8") {
ObjString(
thisAs<ObjBuffer>().byteArray.toByteArray().decodeToString()
@ -161,6 +201,9 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() {
thisAs<ObjBuffer>().byteArray.toByteArray().toDump()
)
}
addFn("toBitInput") {
ObjBitBuffer(BitArray(thisAs<ObjBuffer>().byteArray, 8))
}
}
}
}

View File

@ -1,3 +1,20 @@
/*
* 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.Scope

View File

@ -1,3 +1,20 @@
/*
* 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.*
@ -16,6 +33,16 @@ open class ObjClass(
var constructorMeta: ArgsDeclaration? = null
var instanceConstructor: Statement? = null
/**
* the scope for class methods, initialize class vars, etc.
*
* Important notice. When create a user class, e.g. from Lyng source, it should
* be set to a scope by compiler, so it could access local closure, etc. Otherwise,
* it will be initialized to default scope on first necessity, e.g. when used in
* external, kotlin classes with [addClassConst] and [addClassFn], etc.
*/
var classScope: Scope? = null
val allParentsSet: Set<ObjClass> =
parents.flatMap {
listOf(it) + it.allParentsSet
@ -23,9 +50,10 @@ open class ObjClass(
override val objClass: ObjClass by lazy { ObjClassType }
// members: fields most often
private val members = mutableMapOf<String, ObjRecord>()
private val classMembers = mutableMapOf<String, ObjRecord>()
/**
* members: fields most often. These are called with [ObjInstance] withs ths [ObjInstance.objClass]
*/
internal val members = mutableMapOf<String, ObjRecord>()
override fun toString(): String = className
@ -40,6 +68,7 @@ open class ObjClass(
return instance
}
fun createField(
name: String,
initialValue: Obj,
@ -53,6 +82,11 @@ open class ObjClass(
members[name] = ObjRecord(initialValue, isMutable, visibility)
}
private fun initClassScope(): Scope {
if( classScope == null ) classScope = Scope()
return classScope!!
}
fun createClassField(
name: String,
initialValue: Obj,
@ -60,10 +94,11 @@ open class ObjClass(
visibility: Visibility = Visibility.Public,
pos: Pos = Pos.builtIn
) {
val existing = classMembers[name]
initClassScope()
val existing = classScope!!.objects[name]
if( existing != null)
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
classMembers[name] = ObjRecord(initialValue, isMutable, visibility)
classScope!!.addItem(name, isMutable, initialValue, visibility)
}
fun addFn(name: String, isOpen: Boolean = false, code: suspend Scope.() -> Obj) {
@ -91,15 +126,23 @@ open class ObjClass(
?: throw ScriptError(atPos, "symbol doesn't exist: $name")
override suspend fun readField(scope: Scope, name: String): ObjRecord {
classMembers[name]?.let {
return it
classScope?.objects?.get(name)?.let {
if( it.visibility.isPublic ) return it
}
return super.readField(scope, name)
}
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
initClassScope().objects[name]?.let {
if( it.isMutable) it.value = newValue
else scope.raiseIllegalAssignment("can't assign $name is not mutable")
}
?: super.writeField(scope, name, newValue)
}
override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments,
onNotFoundResult: Obj?): Obj {
return classMembers[name]?.value?.invoke(scope, this, args)
return classScope?.objects?.get(name)?.value?.invoke(scope, this, args)
?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
}

View File

@ -1,3 +1,20 @@
/*
* 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
/**

View File

@ -0,0 +1,39 @@
/*
* 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 kotlinx.coroutines.CompletableDeferred
import net.sergeych.lyng.Scope
class ObjCompletableDeferred(val completableDeferred: CompletableDeferred<Obj>): ObjDeferred(completableDeferred) {
override val objClass = type
companion object {
val type = object: ObjClass("CompletableDeferred", ObjDeferred.type){
override suspend fun callOn(scope: Scope): Obj {
return ObjCompletableDeferred(CompletableDeferred())
}
}.apply {
addFn("complete") {
thisAs<ObjCompletableDeferred>().completableDeferred.complete(args.firstAndOnly())
ObjVoid
}
}
}
}

View File

@ -0,0 +1,49 @@
/*
* 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 kotlinx.coroutines.Deferred
import net.sergeych.lyng.Scope
open class ObjDeferred(val deferred: Deferred<Obj>): Obj() {
override val objClass = type
companion object {
val type = object: ObjClass("Deferred"){
override suspend fun callOn(scope: Scope): Obj {
scope.raiseError("Deferred constructor is not directly callable")
}
}.apply {
addFn("await") {
thisAs<ObjDeferred>().deferred.await()
}
addFn("isCompleted") {
thisAs<ObjDeferred>().deferred.isCompleted.toObj()
}
addFn("isActive") {
thisAs<ObjDeferred>().deferred.isActive.toObj()
}
addFn("isCancelled") {
thisAs<ObjDeferred>().deferred.isCancelled.toObj()
}
}
}
}

View File

@ -0,0 +1,42 @@
/*
* 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
//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 {
//
// }
// }
//
//}

View File

@ -1,3 +1,20 @@
/*
* 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.Scope

View File

@ -0,0 +1,82 @@
/*
* 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
class ObjDynamicContext(val delegate: ObjDynamic) : Obj() {
override val objClass: ObjClass = type
companion object {
val type = ObjClass("DelegateContext").apply {
addFn("get") {
val d = thisAs<ObjDynamicContext>().delegate
if (d.readCallback != null)
raiseIllegalState("get already defined")
d.readCallback = requireOnlyArg()
ObjVoid
}
addFn("set") {
val d = thisAs<ObjDynamicContext>().delegate
if (d.writeCallback != null)
raiseIllegalState("set already defined")
d.writeCallback = requireOnlyArg()
ObjVoid
}
}
}
}
class ObjDynamic : Obj() {
internal var readCallback: Statement? = null
internal var writeCallback: Statement? = null
override suspend fun readField(scope: Scope, name: String): ObjRecord {
return readCallback?.execute(scope.copy(Arguments(ObjString(name))))?.let {
if (writeCallback != null)
it.asMutable
else
it.asReadonly
}
?: super.readField(scope, name)
}
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
writeCallback?.execute(scope.copy(Arguments(ObjString(name), newValue)))
?: super.writeField(scope, name, newValue)
}
companion object {
suspend fun create(scope: Scope, builder: Statement): ObjDynamic {
val delegate = ObjDynamic()
val context = ObjDynamicContext(delegate)
builder.execute(scope.copy(newThisObj = context))
return delegate
}
val type = object : ObjClass("Delegate") {}
}
}

View File

@ -0,0 +1,79 @@
/*
* 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.
*
*/
import net.sergeych.lyng.Scope
import net.sergeych.lyng.obj.*
import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType
open class ObjEnumEntry(enumClass: ObjEnumClass, val name: ObjString, val ordinal: ObjInt) : Obj() {
override val objClass = enumClass
override fun toString(): String {
return "$objClass.$name"
}
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
encoder.encodeUnsigned(ordinal.value.toULong())
}
override suspend fun compareTo(scope: Scope, other: Obj): Int {
if( other !is ObjEnumEntry) return -2
if( other.objClass != objClass ) return -2
return ordinal.compareTo(scope, other.ordinal)
}
}
object EnumBase : ObjClass("Enum") {
}
class ObjEnumClass(val name: String) : ObjClass(name, EnumBase) {
val objEntries = ObjList()
val byName by lazy { objEntries.list.associateBy { (it as ObjEnumEntry).name } }
init {
addClassConst("entries", objEntries )
addClassFn("valueOf") {
val name = requireOnlyArg<ObjString>()
byName[name] ?: raiseSymbolNotFound("does not exists: enum ${className}.$name")
}
addFn("name") { thisAs<ObjEnumEntry>().name }
addFn("ordinal") { thisAs<ObjEnumEntry>().ordinal }
}
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
val index = decoder.unpackUnsigned().toInt()
return objEntries.list[index]
}
companion object {
fun createSimpleEnum(enumName: String, names: List<String>): ObjEnumClass {
val klass = ObjEnumClass(enumName)
names.forEachIndexed { index, name ->
val entry = ObjEnumEntry(klass, ObjString(name), ObjInt(index.toLong(), isConst = true))
klass.objEntries.list += entry
klass.addClassConst(name, entry)
}
return klass
}
}
}

View File

@ -0,0 +1,156 @@
/*
* 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 kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ChannelResult
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.sergeych.lyng.*
import net.sergeych.mp_tools.globalLaunch
import kotlin.coroutines.cancellation.CancellationException
class ObjFlowBuilder(val output: SendChannel<Obj>) : Obj() {
override val objClass = type
companion object {
@OptIn(DelicateCoroutinesApi::class)
val type = object : ObjClass("FlowBuilder") {}.apply {
addFn("emit") {
val data = requireOnlyArg<Obj>()
try {
val channel = thisAs<ObjFlowBuilder>().output
if (!channel.isClosedForSend)
channel.send(data)
else
throw ScriptFlowIsNoMoreCollected()
} catch (x: Exception) {
if (x !is CancellationException)
x.printStackTrace()
throw ScriptFlowIsNoMoreCollected()
}
ObjVoid
}
}
}
}
private fun createLyngFlowInput(scope: Scope, producer: Statement): ReceiveChannel<Obj> {
val channel = Channel<Obj>(Channel.RENDEZVOUS)
val builder = ObjFlowBuilder(channel)
val builderScope = scope.copy(newThisObj = builder)
globalLaunch {
try {
producer.execute(builderScope)
} catch (x: ScriptFlowIsNoMoreCollected) {
x.printStackTrace()
// premature flow closing, OK
} catch (x: Exception) {
x.printStackTrace()
}
channel.close()
}
return channel
}
class ObjFlow(val producer: Statement, val scope: Scope) : Obj() {
override val objClass = type
companion object {
val type = object : ObjClass("Flow", ObjIterable) {
override suspend fun callOn(scope: Scope): Obj {
scope.raiseError("Flow constructor is not available")
}
}.apply {
addFn("iterator") {
val objFlow = thisAs<ObjFlow>()
ObjFlowIterator(statement {
objFlow.producer.execute(
ClosureScope(this, objFlow.scope)
)
})
}
}
}
}
class ObjFlowIterator(val producer: Statement) : Obj() {
override val objClass: ObjClass = type
private var channel: ReceiveChannel<Obj>? = null
private var nextItem: ChannelResult<Obj>? = null
private var isCancelled = false
private fun checkNotCancelled(scope: Scope) {
if (isCancelled)
scope.raiseIllegalState("iteration is cancelled")
}
suspend fun hasNext(scope: Scope): ObjBool {
checkNotCancelled(scope)
// cold start:
if (channel == null) channel = createLyngFlowInput(scope, producer)
if (nextItem == null) nextItem = channel!!.receiveCatching()
return ObjBool(nextItem!!.isSuccess)
}
suspend fun next(scope: Scope): Obj {
checkNotCancelled(scope)
if (hasNext(scope).value == false) scope.raiseIllegalState("iteration is done")
return nextItem!!.getOrThrow().also { nextItem = null }
}
private val access = Mutex()
suspend fun cancel() {
access.withLock {
if (!isCancelled) {
isCancelled = true
channel?.cancel()
}
}
}
companion object {
val type = object : ObjClass("FlowIterator", ObjIterator) {
}.apply {
addFn("hasNext") {
thisAs<ObjFlowIterator>().hasNext(this).toObj()
}
addFn("next") {
val x = thisAs<ObjFlowIterator>()
x.next(this)
}
addFn("cancelIteration") {
val x = thisAs<ObjFlowIterator>()
x.cancel()
ObjVoid
}
}
}
}

View File

@ -1,3 +1,20 @@
/*
* 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
@ -34,7 +51,10 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
onNotFoundResult: Obj?): Obj =
instanceScope[name]?.let {
if (it.visibility.isPublic)
it.value.invoke(scope, this, args)
it.value.invoke(
instanceScope,
this,
args)
else
scope.raiseError(ObjAccessException(scope, "can't invoke non-public method $name"))
}
@ -68,20 +88,17 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
val vars = instanceVars.values.map { it.value }
if( vars.isNotEmpty()) {
encoder.encodeAnyList(scope, vars)
println("serialized state vars $vars")
}
}
internal suspend fun deserializeStateVars(scope: Scope, decoder: LynonDecoder) {
val localVars = instanceVars.values.toList()
if( localVars.isNotEmpty() ) {
println("gonna read vars")
val vars = decoder.decodeAnyList(scope)
if (vars.size > instanceVars.size)
scope.raiseIllegalArgument("serialized vars has bigger size than instance vars")
println("deser state vars $vars")
for ((i, v) in vars.withIndex()) {
localVars[i].value = vars[i]
localVars[i].value = v
}
}
}

View File

@ -1,3 +1,20 @@
/*
* 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
@ -10,11 +27,8 @@ import net.sergeych.lynon.LynonType
*/
class ObjInstanceClass(val name: String) : ObjClass(name) {
// val onDeserilaized =
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
val args = decoder.decodeAnyList(scope)
println("deserializing constructor $name, $args params")
val actualSize = constructorMeta?.params?.size ?: 0
if( args.size > actualSize )
scope.raiseIllegalArgument("constructor $name has only $actualSize but serialized version has ${args.size}")

View File

@ -1,3 +1,20 @@
/*
* 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 kotlinx.datetime.Clock

View File

@ -1,3 +1,20 @@
/*
* 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.Scope
@ -45,7 +62,7 @@ class ObjInt(var value: Long, override val isConst: Boolean = false) : Obj(), Nu
override fun toString(): String = value.toString()
override val objClass: ObjClass = type
override val objClass: ObjClass by lazy { type }
override suspend fun plus(scope: Scope, other: Obj): Obj =
if (other is ObjInt)

View File

@ -1,3 +1,20 @@
/*
* 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
@ -42,6 +59,9 @@ val ObjIterable by lazy {
}
addFn("toSet") {
if( thisObj.isInstanceOf(ObjSet.type) )
thisObj
else {
val result = mutableSetOf<Obj>()
val it = thisObj.invokeInstanceMethod(this, "iterator")
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
@ -49,6 +69,7 @@ val ObjIterable by lazy {
}
ObjSet(result)
}
}
addFn("toMap") {
val result = ObjMap()
@ -86,6 +107,24 @@ val ObjIterable by lazy {
ObjList(result)
}
addFn("take") {
var n = requireOnlyArg<ObjInt>().value.toInt()
val result = mutableListOf<Obj>()
if (n > 0) {
thisObj.enumerate(this) {
result += it
--n > 0
}
}
ObjList(result)
}
// addFn("drop" ) {
// var n = requireOnlyArg<ObjInt>().value.toInt()
// if( n < 0 ) raiseIllegalArgument("drop($n): should be positive")
// val it = callMethod<>()
// }
addFn("isEmpty") {
ObjBool(
thisObj.invokeInstanceMethod(this, "iterator")
@ -93,6 +132,5 @@ val ObjIterable by lazy {
.not()
)
}
}
}

View File

@ -1,3 +1,42 @@
/*
* 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
val ObjIterator by lazy { ObjClass("Iterator") }
/**
* Iterator should provide lyng-level iterator functions:
*
* - hasNext()
* - next()
* - optional cancelIteration() that _may_ be called when iteration is performed
* only on the part of the iterable entity. Implement it when there are resources
* to be reclaimed on iteration interruption.
*/
val ObjIterator by lazy {
ObjClass("Iterator").apply {
addFn("cancelIteration", true) {
ObjVoid
}
addFn("hasNext", true) {
raiseNotImplemented("hasNext() is not implemented")
}
addFn("next", true) {
raiseNotImplemented("next() is not implemented")
}
}
}

View File

@ -1,3 +1,20 @@
/*
* 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.
*
*/
@file:Suppress("unused")
package net.sergeych.lyng.obj
@ -36,7 +53,9 @@ class ObjKotlinObjIterator(val iterator: Iterator<Obj>) : Obj() {
addFn("next") {
thisAs<ObjKotlinObjIterator>().iterator.next()
}
addFn("hasNext") { thisAs<ObjKotlinIterator>().iterator.hasNext().toObj() }
addFn("hasNext") {
thisAs<ObjKotlinObjIterator>().iterator.hasNext().toObj()
}
}
}
@ -56,3 +75,25 @@ fun Obj.toFlow(scope: Scope): Flow<Obj> = flow {
emit(next.invoke(scope, iterator))
}
}
/**
* Call [callback] for each element of this obj considering it provides [Iterator]
* methods `hasNext` and `next`.
*
* IF callback returns false, iteration is stopped.
*/
suspend fun Obj.enumerate(scope: Scope,callback: suspend (Obj)->Boolean) {
val iterator = invokeInstanceMethod(scope, "iterator")
val hasNext = iterator.getInstanceMethod(scope, "hasNext")
val next = iterator.getInstanceMethod(scope, "next")
var closeIt = false
while (hasNext.invoke(scope, iterator).toBool()) {
val nextValue = next.invoke(scope, iterator)
if( !callback(nextValue) ) {
closeIt = true
break
}
}
if( closeIt )
iterator.invokeInstanceMethod(scope, "cancelIteration", onNotFoundResult = ObjVoid)
}

View File

@ -1,3 +1,20 @@
/*
* 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.Scope

View File

@ -1,3 +1,20 @@
/*
* 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.Scope

View File

@ -1,3 +1,20 @@
/*
* 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 kotlinx.coroutines.flow.map

View File

@ -0,0 +1,40 @@
/*
* 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 kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
class ObjMutex(val mutex: Mutex): Obj() {
override val objClass = type
companion object {
val type = object: ObjClass("Mutex") {
override suspend fun callOn(scope: Scope): Obj {
return ObjMutex(Mutex())
}
}.apply {
addFn("withLock") {
val f = requiredArg<Statement>(0)
thisAs<ObjMutex>().mutex.withLock { f.execute(this) }
}
}
}
}

View File

@ -1,3 +1,20 @@
/*
* 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.Scope

View File

@ -1,3 +1,20 @@
/*
* 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.Scope
@ -39,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()
}

View File

@ -1,3 +1,20 @@
/*
* 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.Pos

View File

@ -1,3 +1,20 @@
/*
* 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.Scope
@ -18,8 +35,15 @@ data class ObjRecord(
Field(true, true),
@Suppress("unused")
Fun,
@Suppress("unused")
ConstructorField(true, true),
Other
Argument(true, true),
@Suppress("unused")
Class,
Enum,
Other;
val isArgument get() = this == Argument
}
@Suppress("unused")
fun qualifiedName(name: String): String =

View File

@ -0,0 +1,108 @@
/*
* 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.Scope
class RingBuffer<T>(val maxSize: Int) : Iterable<T> {
private val data = arrayOfNulls<Any>(maxSize)
var size = 0
private set
private var start = 0
init {
check(maxSize > 0) { "Max size should be a positive number: $maxSize" }
}
fun add(item: T) {
if (size < maxSize)
size++
else
start = (start + 1) % maxSize
data[(start + size - 1) % maxSize] = item
}
@Suppress("unused")
fun addAll(vararg items: T) {
for (i in items) add(i)
}
@Suppress("unused")
fun addAll(elements: Iterable<T>) {
elements.forEach { add(it) }
}
@Suppress("unused")
fun clear() {
start = 0
size = 0
for (i in data.indices) {
data[i] = null
}
}
override fun iterator(): Iterator<T> =
object : Iterator<T> {
private var i = 0
override fun hasNext(): Boolean = i < size
override fun next(): T {
if (!hasNext()) throw NoSuchElementException()
@Suppress("UNCHECKED_CAST")
return data[(start + i++) % maxSize] as T
}
}
}
class ObjRingBuffer(val capacity: Int) : Obj() {
val buffer = RingBuffer<Obj>(capacity)
override val objClass: ObjClass = type
override suspend fun plusAssign(scope: Scope, other: Obj): Obj {
buffer.add(other.byValueCopy())
return this
}
companion object {
val type = object : ObjClass("RingBuffer", ObjIterable) {
override suspend fun callOn(scope: Scope): Obj {
return ObjRingBuffer(scope.requireOnlyArg<ObjInt>().toInt())
}
}.apply {
addFn("capacity") {
thisAs<ObjRingBuffer>().capacity.toObj()
}
addFn("size") {
thisAs<ObjRingBuffer>().buffer.size.toObj()
}
addFn("iterator") {
val buffer = thisAs<ObjRingBuffer>().buffer
ObjKotlinObjIterator(buffer.iterator())
}
addFn("add") {
thisAs<ObjRingBuffer>().apply { buffer.add(requireOnlyArg<Obj>()) }
}
}
}
}

View File

@ -1,3 +1,20 @@
/*
* 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.Scope
@ -92,7 +109,7 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
companion object {
val type = object : ObjClass("Set", ObjCollection) {
val type: ObjClass = object : ObjClass("Set", ObjCollection) {
override suspend fun callOn(scope: Scope): Obj {
return ObjSet(scope.args.list.toMutableSet())
}

View File

@ -1,3 +1,20 @@
/*
* 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 kotlinx.serialization.SerialName
@ -93,7 +110,9 @@ data class ObjString(val value: String) : Obj() {
ObjString(decoder.unpackBinaryData().decodeToString())
}.apply {
addFn("toInt") {
ObjInt(thisAs<ObjString>().value.toLong())
ObjInt(thisAs<ObjString>().value.toLongOrNull()
?: raiseIllegalArgument("can't convert to int: $thisObj")
)
}
addFn("startsWith") {
ObjBool(thisAs<ObjString>().value.startsWith(requiredArg<ObjString>(0).value))
@ -137,7 +156,9 @@ data class ObjString(val value: String) : Obj() {
}
addFn("encodeUtf8") { ObjBuffer(thisAs<ObjString>().value.encodeToByteArray().asUByteArray()) }
addFn("size") { ObjInt(thisAs<ObjString>().value.length.toLong()) }
addFn("toReal") { ObjReal(thisAs<ObjString>().value.toDouble()) }
addFn("toReal") {
ObjReal(thisAs<ObjString>().value.toDouble())
}
}
}
}

View File

@ -1,3 +1,20 @@
/*
* 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.pacman
import net.sergeych.lyng.*

View File

@ -1,6 +1,24 @@
/*
* 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.pacman
import net.sergeych.lyng.*
import net.sergeych.mptools.CachedExpression
/**
* Package manager INTERFACE (abstract class). Performs import routines
@ -45,6 +63,15 @@ abstract class ImportProvider(
fun newModuleAt(pos: Pos): ModuleScope =
ModuleScope(this, pos, "unknown")
private var cachedStdScope = CachedExpression<Scope>()
suspend fun newStdScope(pos: Pos = Pos.builtIn): Scope =
cachedStdScope.get {
newModuleAt(pos).also {
it.eval("import lyng.stdlib\n")
}
}.copy()
}

View File

@ -1,3 +1,20 @@
/*
* 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.pacman
import kotlinx.coroutines.CompletableDeferred

View File

@ -1,13 +1,35 @@
/*
* 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
import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.ObjNull
import net.sergeych.lyng.obj.ObjVoid
fun String.toSource(name: String = "eval"): Source = Source(name, this)
sealed class ObjType {
object Any : ObjType()
object Int : ObjType()
object Void: ObjType()
companion object {
}
}
@ -24,6 +46,7 @@ abstract class Statement(
abstract suspend fun execute(scope: Scope): Obj
override suspend fun compareTo(scope: Scope, other: Obj): Int {
if( other == ObjNull || other == ObjVoid ) return 1
throw UnsupportedOperationException("not comparable")
}
@ -62,4 +85,8 @@ fun statement(isStaticConst: Boolean = false, isConst: Boolean = false, f: suspe
override suspend fun execute(scope: Scope): Obj = f(scope)
}
object NopStatement: Statement(true, true, ObjType.Void) {
override val pos: Pos = Pos.builtIn
override suspend fun execute(scope: Scope): Obj = ObjVoid
}

View File

@ -0,0 +1,108 @@
/*
* 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.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 {
for( item in list ) {
if( predicate(item) ) {ln
emit(item)
}
}
}
}
fun Iterable.drop(n) {
var cnt = 0
filter { cnt++ >= n }
}
fun Iterable.first() {
val i = iterator()
if( !i.hasNext() ) throw NoSuchElementException()
i.next().also { i.cancelIteration() }
}
fun Iterable.last() {
var found = false
var element = null
for( i in this ) {
element = i
found = true
}
if( !found ) throw NoSuchElementException()
element
}
fun Iterable.dropLast(n) {
val list = this
val buffer = RingBuffer(n)
flow {
for( item in list ) {
if( buffer.size == n )
emit( buffer.first() )
buffer += item
}
}
}
fun Iterable.takeLast(n) {
val list = this
val buffer = RingBuffer(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 ?: ""
}
fun Iterable.any(predicate): Bool {
for( i in this ) {
if( predicate(i) )
break true
} else false
}
fun Iterable.all(predicate): Bool {
!any { !predicate(it) }
}
""".trimIndent()

View File

@ -0,0 +1,30 @@
/*
* 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
fun leftMargin(s: String): Int {
var cnt = 0
for (c in s) {
when (c) {
' ' -> cnt++
'\t' -> cnt = (cnt / 4.0 + 0.9).toInt() * 4
else -> break
}
}
return cnt
}

View File

@ -1,3 +1,20 @@
/*
* 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.lynon
interface BitInput {
@ -82,6 +99,8 @@ interface BitInput {
fun decompressStringOrNull(): String? = decompressOrNull()?.decodeToString()
fun decompressString(): String = decompress().decodeToString()
@Suppress("unused")
fun unpackDouble(): Double {
val bits = getBits(64)
return Double.fromBits(bits.toLong())

View File

@ -1,3 +1,20 @@
/*
* 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.lynon
@Suppress("unused")

View File

@ -1,3 +1,20 @@
/*
* 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.lynon
interface BitOutput {
@ -84,7 +101,6 @@ interface BitOutput {
val compressed = LZW.compress(source.asUByteArray())
// check that compression is effective including header bits size:
if( compressed.size + 2 < source.size * 8L) {
println("write compressed")
putBit(1)
// LZW algorithm
putBits(0, 2)

View File

@ -1,3 +1,20 @@
/*
* 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.lynon
class DecompressionException(message: String) : IllegalArgumentException(message) {}

View File

@ -1,3 +1,20 @@
/*
* 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.lynon
import net.sergeych.lyng.Scope
@ -48,7 +65,6 @@ open class LynonDecoder(val bin: BitInput, val settings: LynonSettings = LynonSe
private suspend fun decodeClassObj(scope: Scope): ObjClass {
val className = decodeObject(scope, ObjString.type, null) as ObjString
println("expected class name $className")
return scope.get(className.value)?.value?.let {
if (it !is ObjClass)
scope.raiseClassCastError("Expected obj class but got ${it::class.simpleName}")

View File

@ -1,3 +1,20 @@
/*
* 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.lynon
import net.sergeych.bintools.ByteChunk

View File

@ -1,3 +1,20 @@
/*
* 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.lynon
import net.sergeych.lyng.obj.ObjBool

View File

@ -1,3 +1,20 @@
/*
* 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.lynon

Some files were not shown because too many files have changed in this diff Show More