Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7f020f4d6 | ||
|
|
0f54e2f845 | ||
|
|
19f8b6605b | ||
|
|
de71c7c8ec | ||
|
|
be370dfdef | ||
|
|
53a6a88924 | ||
|
|
2c2dae4d85 | ||
|
|
63b2808109 |
No files matched your search
@@ -23,7 +23,7 @@ fun swapEnds(first, args..., last, f) {
|
|||||||
delay(1.5) // coroutine is delayed for 1.5s, thread is not blocked!
|
delay(1.5) // coroutine is delayed for 1.5s, thread is not blocked!
|
||||||
```
|
```
|
||||||
and it is multithreaded on platforms supporting it (automatically, no code changes required, just
|
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.
|
- 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 )`.
|
||||||
@@ -142,12 +142,12 @@ Ready features:
|
|||||||
|
|
||||||
Under way:
|
Under way:
|
||||||
|
|
||||||
- [ ] maps, sets and sequences (flows?)
|
- [x] maps, sets and sequences (flows?)
|
||||||
- [ ] regular exceptions
|
- [ ] regular exceptions
|
||||||
- [ ] modules
|
- [x] modules
|
||||||
- [ ] string interpolation and more string tools
|
- [x] string formatting and tools
|
||||||
- [ ] multiple inheritance for user classes
|
- [ ] multiple inheritance for user classes
|
||||||
- [ ] launch, deferred, coroutineScope, mutex, etc.
|
- [x] launch, deferred, CompletableDeferred, Mutex, etc.
|
||||||
- [ ] site with integrated interpreter to give a try
|
- [ ] site with integrated interpreter to give a try
|
||||||
- [ ] kotlin part public API good docs, integration focused
|
- [ ] kotlin part public API good docs, integration focused
|
||||||
- [ ] better stack reporting
|
- [ ] better stack reporting
|
||||||
@@ -158,10 +158,12 @@ Planned features.
|
|||||||
|
|
||||||
- [ ] type specifications
|
- [ ] type specifications
|
||||||
- [ ] source docs and maybe lyng.md to a standard
|
- [ ] source docs and maybe lyng.md to a standard
|
||||||
- [ ] moacro-style kotlin integration or something else to simplify it
|
- [ ] macro-style kotlin integration or something else to simplify it
|
||||||
|
|
||||||
Further
|
Further
|
||||||
|
|
||||||
- [ ] client with GUI support based on compose multiplatform somehow
|
- [ ] client with GUI support based on compose multiplatform somehow
|
||||||
- [ ] notebook - style workbooks with graphs, formulaes, etc.
|
- [ ] notebook - style workbooks with graphs, formulaes, etc.
|
||||||
- [ ] language server or compose-based lyng-aware editor
|
- [ ] language server or compose-based lyng-aware editor
|
||||||
|
|
||||||
|
[parallelism]: docs/parallelism.md
|
||||||
+42
-1
@@ -147,7 +147,7 @@ In many cases it is necessary to implement custom comparison and `toString`, sti
|
|||||||
each class is provided with default implementations:
|
each class is provided with default implementations:
|
||||||
|
|
||||||
- default toString outputs class name and its _public_ fields.
|
- 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:
|
For example, for our class Point:
|
||||||
|
|
||||||
@@ -160,6 +160,47 @@ For example, for our class Point:
|
|||||||
Point(1,1+1)
|
Point(1,1+1)
|
||||||
>>> Point(x=1,y=2)
|
>>> 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
|
# 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
|
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
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# Advanced topics
|
# Advanced topics
|
||||||
|
|
||||||
|
__See also:__ [parallelism].
|
||||||
|
|
||||||
## Closures/scopes isolation
|
## Closures/scopes isolation
|
||||||
|
|
||||||
Each block has own scope, in which it can safely use closures and override
|
Each block has own scope, in which it can safely use closures and override
|
||||||
@@ -111,3 +113,5 @@ arguments list in almost arbitrary ways. For example:
|
|||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
,
|
,
|
||||||
|
|
||||||
|
[parallelism]: parallelism.md
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
# 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)
|
||||||
|
}
|
||||||
+3
-1
@@ -14,6 +14,7 @@ __Other documents to read__ maybe after this one:
|
|||||||
- [Advanced topics](advanced_topics.md), [declaring arguments](declaring_arguments.md)
|
- [Advanced topics](advanced_topics.md), [declaring arguments](declaring_arguments.md)
|
||||||
- [OOP notes](OOP.md), [exception handling](exceptions_handling.md)
|
- [OOP notes](OOP.md), [exception handling](exceptions_handling.md)
|
||||||
- [math in Lyng](math.md)
|
- [math in Lyng](math.md)
|
||||||
|
- [parallelism] - multithreaded code, coroutines, etc.
|
||||||
- Some class references: [List], [Set], [Map], [Real], [Range], [Iterable], [Iterator], [time manipulation](time.md)
|
- Some class references: [List], [Set], [Map], [Real], [Range], [Iterable], [Iterator], [time manipulation](time.md)
|
||||||
- Some samples: [combinatorics](samples/combinatorics.lyng.md), national vars and loops: [сумма ряда](samples/сумма_ряда.lyng.md). More at [samples folder](samples)
|
- Some samples: [combinatorics](samples/combinatorics.lyng.md), national vars and loops: [сумма ряда](samples/сумма_ряда.lyng.md). More at [samples folder](samples)
|
||||||
|
|
||||||
@@ -1278,4 +1279,5 @@ See [math functions](math.md). Other general purpose functions are:
|
|||||||
[string formatting]: https://github.com/sergeych/mp_stools?tab=readme-ov-file#sprintf-syntax-summary
|
[string formatting]: https://github.com/sergeych/mp_stools?tab=readme-ov-file#sprintf-syntax-summary
|
||||||
[Set]: Set.md
|
[Set]: Set.md
|
||||||
[Map]: Map.md
|
[Map]: Map.md
|
||||||
[Buffer]: Buffer.md
|
[Buffer]: Buffer.md
|
||||||
|
[parallelism]: parallelism.md
|
||||||
@@ -4,7 +4,7 @@ clikt = "5.0.3"
|
|||||||
kotlin = "2.1.21"
|
kotlin = "2.1.21"
|
||||||
android-minSdk = "24"
|
android-minSdk = "24"
|
||||||
android-compileSdk = "34"
|
android-compileSdk = "34"
|
||||||
kotlinx-coroutines = "1.10.1"
|
kotlinx-coroutines = "1.9.0"
|
||||||
mp_bintools = "0.1.12"
|
mp_bintools = "0.1.12"
|
||||||
firebaseCrashlyticsBuildtools = "3.0.3"
|
firebaseCrashlyticsBuildtools = "3.0.3"
|
||||||
okioVersion = "3.10.2"
|
okioVersion = "3.10.2"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
|||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
group = "net.sergeych"
|
group = "net.sergeych"
|
||||||
version = "0.8.0-SNAPSHOT"
|
version = "0.8.2-SNAPSHOT"
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ data class Arguments(val list: List<Obj>, val tailBlockMode: Boolean = false) :
|
|||||||
|
|
||||||
fun inspect(): String = list.joinToString(", ") { it.inspect() }
|
fun inspect(): String = list.joinToString(", ") { it.inspect() }
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val EMPTY = Arguments(emptyList())
|
val EMPTY = Arguments(emptyList())
|
||||||
fun from(values: Collection<Obj>) = Arguments(values.toList())
|
fun from(values: Collection<Obj>) = Arguments(values.toList())
|
||||||
|
|||||||
@@ -17,6 +17,16 @@ class Compiler(
|
|||||||
|
|
||||||
class Settings
|
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 suspend fun parseScript(): Script {
|
private suspend fun parseScript(): Script {
|
||||||
val statements = mutableListOf<Statement>()
|
val statements = mutableListOf<Statement>()
|
||||||
val start = cc.currentPos()
|
val start = cc.currentPos()
|
||||||
@@ -823,6 +833,8 @@ class Compiler(
|
|||||||
val isExtern = cc.skipId("extern")
|
val isExtern = cc.skipId("extern")
|
||||||
when {
|
when {
|
||||||
cc.matchQualifiers("fun", "private") -> parseFunctionDeclaration(Visibility.Private, isExtern)
|
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("fn", "private") -> parseFunctionDeclaration(Visibility.Private, isExtern)
|
||||||
cc.matchQualifiers("fun", "open") -> parseFunctionDeclaration(isOpen = true, isExtern = isExtern)
|
cc.matchQualifiers("fun", "open") -> parseFunctionDeclaration(isOpen = true, isExtern = isExtern)
|
||||||
cc.matchQualifiers("fn", "open") -> parseFunctionDeclaration(isOpen = true, isExtern = isExtern)
|
cc.matchQualifiers("fn", "open") -> parseFunctionDeclaration(isOpen = true, isExtern = isExtern)
|
||||||
@@ -830,7 +842,21 @@ class Compiler(
|
|||||||
cc.matchQualifiers("fun") -> parseFunctionDeclaration(isOpen = false, isExtern = isExtern)
|
cc.matchQualifiers("fun") -> parseFunctionDeclaration(isOpen = false, isExtern = isExtern)
|
||||||
cc.matchQualifiers("fn") -> 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("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("var", "private") -> parseVarDeclaration(true, Visibility.Private)
|
||||||
cc.matchQualifiers("val", "open") -> parseVarDeclaration(false, Visibility.Private, true)
|
cc.matchQualifiers("val", "open") -> parseVarDeclaration(false, Visibility.Private, true)
|
||||||
cc.matchQualifiers("var", "open") -> parseVarDeclaration(true, Visibility.Private, true)
|
cc.matchQualifiers("var", "open") -> parseVarDeclaration(true, Visibility.Private, true)
|
||||||
@@ -1090,6 +1116,8 @@ class Compiler(
|
|||||||
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
|
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
|
||||||
val t = cc.next()
|
val t = cc.next()
|
||||||
|
|
||||||
|
pushInitScope()
|
||||||
|
|
||||||
val bodyInit: Statement? = if (t.type == Token.Type.LBRACE) {
|
val bodyInit: Statement? = if (t.type == Token.Type.LBRACE) {
|
||||||
// parse body
|
// parse body
|
||||||
parseScript().also {
|
parseScript().also {
|
||||||
@@ -1100,6 +1128,8 @@ class Compiler(
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val initScope = popInitScope()
|
||||||
|
|
||||||
// create class
|
// create class
|
||||||
val className = nameToken.value
|
val className = nameToken.value
|
||||||
|
|
||||||
@@ -1131,6 +1161,13 @@ class Compiler(
|
|||||||
// the main statement should create custom ObjClass instance with field
|
// the main statement should create custom ObjClass instance with field
|
||||||
// accessors, constructor registration, etc.
|
// accessors, constructor registration, etc.
|
||||||
addItem(className, false, newClass)
|
addItem(className, false, newClass)
|
||||||
|
if (initScope.isNotEmpty()) {
|
||||||
|
val classScope = copy(newThisObj = newClass)
|
||||||
|
newClass.classScope = classScope
|
||||||
|
for (s in initScope)
|
||||||
|
s.execute(classScope)
|
||||||
|
.also { println("executed, ${classScope.objects}") }
|
||||||
|
}
|
||||||
newClass
|
newClass
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1506,7 +1543,8 @@ class Compiler(
|
|||||||
private suspend fun parseFunctionDeclaration(
|
private suspend fun parseFunctionDeclaration(
|
||||||
visibility: Visibility = Visibility.Public,
|
visibility: Visibility = Visibility.Public,
|
||||||
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false,
|
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false,
|
||||||
isExtern: Boolean = false
|
isExtern: Boolean = false,
|
||||||
|
isStatic: Boolean = false,
|
||||||
): Statement {
|
): Statement {
|
||||||
var t = cc.next()
|
var t = cc.next()
|
||||||
val start = t.pos
|
val start = t.pos
|
||||||
@@ -1559,7 +1597,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
fnStatements.execute(context)
|
fnStatements.execute(context)
|
||||||
}
|
}
|
||||||
return statement(start) { context ->
|
val fnCreatestatement = statement(start) { context ->
|
||||||
// we added fn in the context. now we must save closure
|
// we added fn in the context. now we must save closure
|
||||||
// for the function
|
// for the function
|
||||||
closure = context
|
closure = context
|
||||||
@@ -1577,6 +1615,11 @@ class Compiler(
|
|||||||
// saved the proper context in the closure
|
// saved the proper context in the closure
|
||||||
fnBody
|
fnBody
|
||||||
}
|
}
|
||||||
|
return if (isStatic) {
|
||||||
|
currentInitScope += fnCreatestatement
|
||||||
|
NopStatement
|
||||||
|
} else
|
||||||
|
fnCreatestatement
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun parseBlock(skipLeadingBrace: Boolean = false): Statement {
|
private suspend fun parseBlock(skipLeadingBrace: Boolean = false): Statement {
|
||||||
@@ -1600,7 +1643,8 @@ class Compiler(
|
|||||||
private suspend fun parseVarDeclaration(
|
private suspend fun parseVarDeclaration(
|
||||||
isMutable: Boolean,
|
isMutable: Boolean,
|
||||||
visibility: Visibility,
|
visibility: Visibility,
|
||||||
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false
|
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false,
|
||||||
|
isStatic: Boolean = false
|
||||||
): Statement {
|
): Statement {
|
||||||
val nameToken = cc.next()
|
val nameToken = cc.next()
|
||||||
val start = nameToken.pos
|
val start = nameToken.pos
|
||||||
@@ -1622,6 +1666,21 @@ class Compiler(
|
|||||||
val initialExpression = if (setNull) null else parseStatement(true)
|
val initialExpression = if (setNull) null else parseStatement(true)
|
||||||
?: throw ScriptError(eqToken.pos, "Expected initializer expression")
|
?: throw ScriptError(eqToken.pos, "Expected initializer expression")
|
||||||
|
|
||||||
|
if (isStatic) {
|
||||||
|
// find objclass instance: this is tricky: this code executes in object initializer,
|
||||||
|
// when creating instance, but we need to execute it in the class initializer which
|
||||||
|
// is missing as for now. Add it to the compiler context?
|
||||||
|
// add there
|
||||||
|
// return
|
||||||
|
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 ->
|
return statement(nameToken.pos) { context ->
|
||||||
if (context.containsLocal(name))
|
if (context.containsLocal(name))
|
||||||
throw ScriptError(nameToken.pos, "Variable $name is already defined")
|
throw ScriptError(nameToken.pos, "Variable $name is already defined")
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ class CompilerContext(val tokens: List<Token>) {
|
|||||||
throw ScriptError(at, message)
|
throw ScriptError(at, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun syntaxError(message: String = "Syntax error"): Nothing {
|
||||||
|
throw ScriptError(currentPos(), message)
|
||||||
|
}
|
||||||
|
|
||||||
fun currentPos(): Pos = tokens[currentIndex].pos
|
fun currentPos(): Pos = tokens[currentIndex].pos
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ open class Scope(
|
|||||||
fun raiseIllegalState(message: String = "Illegal argument error"): Nothing =
|
fun raiseIllegalState(message: String = "Illegal argument error"): Nothing =
|
||||||
raiseError(ObjIllegalStateException(this, message))
|
raiseError(ObjIllegalStateException(this, message))
|
||||||
|
|
||||||
|
fun raiseIllegalAssignment(message: String): Nothing =
|
||||||
|
raiseError(ObjIllegalAssignmentException(this, message))
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun raiseNoSuchElement(message: String = "No such element"): Nothing =
|
fun raiseNoSuchElement(message: String = "No such element"): Nothing =
|
||||||
raiseError(ObjIllegalArgumentException(this, message))
|
raiseError(ObjIllegalArgumentException(this, message))
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.yield
|
||||||
import net.sergeych.lyng.obj.*
|
import net.sergeych.lyng.obj.*
|
||||||
import net.sergeych.lyng.pacman.ImportManager
|
import net.sergeych.lyng.pacman.ImportManager
|
||||||
import net.sergeych.lynon.ObjLynonClass
|
import net.sergeych.lynon.ObjLynonClass
|
||||||
|
import net.sergeych.mp_tools.globalDefer
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
|
|
||||||
class Script(
|
class Script(
|
||||||
@@ -175,6 +177,23 @@ class Script(
|
|||||||
addConst("Array", ObjArray)
|
addConst("Array", ObjArray)
|
||||||
addConst("Class", ObjClassType)
|
addConst("Class", ObjClassType)
|
||||||
|
|
||||||
|
addConst("Deferred", ObjDeferred.type)
|
||||||
|
addConst("CompletableDeferred", ObjCompletableDeferred.type)
|
||||||
|
addConst("Mutex", ObjMutex.type)
|
||||||
|
|
||||||
|
addFn("launch") {
|
||||||
|
val callable = args.firstAndOnly() as Statement
|
||||||
|
ObjDeferred(globalDefer {
|
||||||
|
callable.execute(this@addFn)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
addFn("yield") {
|
||||||
|
yield()
|
||||||
|
ObjVoid
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
val pi = ObjReal(PI)
|
val pi = ObjReal(PI)
|
||||||
addConst("π", pi)
|
addConst("π", pi)
|
||||||
getOrCreateNamespace("Math").apply {
|
getOrCreateNamespace("Math").apply {
|
||||||
|
|||||||
@@ -16,6 +16,16 @@ open class ObjClass(
|
|||||||
var constructorMeta: ArgsDeclaration? = null
|
var constructorMeta: ArgsDeclaration? = null
|
||||||
var instanceConstructor: Statement? = 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> =
|
val allParentsSet: Set<ObjClass> =
|
||||||
parents.flatMap {
|
parents.flatMap {
|
||||||
listOf(it) + it.allParentsSet
|
listOf(it) + it.allParentsSet
|
||||||
@@ -23,9 +33,10 @@ open class ObjClass(
|
|||||||
|
|
||||||
override val objClass: ObjClass by lazy { ObjClassType }
|
override val objClass: ObjClass by lazy { ObjClassType }
|
||||||
|
|
||||||
// members: fields most often
|
/**
|
||||||
|
* members: fields most often. These are called with [ObjInstance] withs ths [ObjInstance.objClass]
|
||||||
|
*/
|
||||||
private val members = mutableMapOf<String, ObjRecord>()
|
private val members = mutableMapOf<String, ObjRecord>()
|
||||||
private val classMembers = mutableMapOf<String, ObjRecord>()
|
|
||||||
|
|
||||||
override fun toString(): String = className
|
override fun toString(): String = className
|
||||||
|
|
||||||
@@ -40,6 +51,7 @@ open class ObjClass(
|
|||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun createField(
|
fun createField(
|
||||||
name: String,
|
name: String,
|
||||||
initialValue: Obj,
|
initialValue: Obj,
|
||||||
@@ -53,6 +65,11 @@ open class ObjClass(
|
|||||||
members[name] = ObjRecord(initialValue, isMutable, visibility)
|
members[name] = ObjRecord(initialValue, isMutable, visibility)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initClassScope(): Scope {
|
||||||
|
if( classScope == null ) classScope = Scope()
|
||||||
|
return classScope!!
|
||||||
|
}
|
||||||
|
|
||||||
fun createClassField(
|
fun createClassField(
|
||||||
name: String,
|
name: String,
|
||||||
initialValue: Obj,
|
initialValue: Obj,
|
||||||
@@ -60,10 +77,11 @@ open class ObjClass(
|
|||||||
visibility: Visibility = Visibility.Public,
|
visibility: Visibility = Visibility.Public,
|
||||||
pos: Pos = Pos.builtIn
|
pos: Pos = Pos.builtIn
|
||||||
) {
|
) {
|
||||||
val existing = classMembers[name]
|
initClassScope()
|
||||||
|
val existing = classScope!!.objects[name]
|
||||||
if( existing != null)
|
if( existing != null)
|
||||||
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
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) {
|
fun addFn(name: String, isOpen: Boolean = false, code: suspend Scope.() -> Obj) {
|
||||||
@@ -91,15 +109,23 @@ open class ObjClass(
|
|||||||
?: throw ScriptError(atPos, "symbol doesn't exist: $name")
|
?: throw ScriptError(atPos, "symbol doesn't exist: $name")
|
||||||
|
|
||||||
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||||
classMembers[name]?.let {
|
classScope?.objects?.get(name)?.let {
|
||||||
return it
|
if( it.visibility.isPublic ) return it
|
||||||
}
|
}
|
||||||
return super.readField(scope, name)
|
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,
|
override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments,
|
||||||
onNotFoundResult: Obj?): Obj {
|
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)
|
?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
scope.raiseIllegalArgument("serialized vars has bigger size than instance vars")
|
scope.raiseIllegalArgument("serialized vars has bigger size than instance vars")
|
||||||
println("deser state vars $vars")
|
println("deser state vars $vars")
|
||||||
for ((i, v) in vars.withIndex()) {
|
for ((i, v) in vars.withIndex()) {
|
||||||
localVars[i].value = vars[i]
|
localVars[i].value = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import net.sergeych.lynon.LynonType
|
|||||||
*/
|
*/
|
||||||
class ObjInstanceClass(val name: String) : ObjClass(name) {
|
class ObjInstanceClass(val name: String) : ObjClass(name) {
|
||||||
|
|
||||||
// val onDeserilaized =
|
|
||||||
|
|
||||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
|
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
|
||||||
val args = decoder.decodeAnyList(scope)
|
val args = decoder.decodeAnyList(scope)
|
||||||
println("deserializing constructor $name, $args params")
|
println("deserializing constructor $name, $args params")
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,16 @@ package net.sergeych.lyng
|
|||||||
|
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
import net.sergeych.lyng.obj.ObjClass
|
import net.sergeych.lyng.obj.ObjClass
|
||||||
|
import net.sergeych.lyng.obj.ObjVoid
|
||||||
|
|
||||||
fun String.toSource(name: String = "eval"): Source = Source(name, this)
|
fun String.toSource(name: String = "eval"): Source = Source(name, this)
|
||||||
|
|
||||||
sealed class ObjType {
|
sealed class ObjType {
|
||||||
object Any : ObjType()
|
object Any : ObjType()
|
||||||
object Int : ObjType()
|
object Void: ObjType()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -62,4 +66,8 @@ fun statement(isStaticConst: Boolean = false, isConst: Boolean = false, f: suspe
|
|||||||
override suspend fun execute(scope: Scope): Obj = f(scope)
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import net.sergeych.lyng.eval
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
class TestCoroutines {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLaunch() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
var passed = false
|
||||||
|
val x = launch {
|
||||||
|
delay(10)
|
||||||
|
passed = true
|
||||||
|
"ok"
|
||||||
|
}
|
||||||
|
assert(!passed)
|
||||||
|
assertEquals( x.await(), "ok")
|
||||||
|
assert(passed)
|
||||||
|
assert(x.isCompleted)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCompletableDeferred() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
val done = CompletableDeferred()
|
||||||
|
|
||||||
|
launch {
|
||||||
|
delay(10)
|
||||||
|
done.complete("ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(!done.isCompleted)
|
||||||
|
assert(done.isActive)
|
||||||
|
assertEquals( done.await(), "ok")
|
||||||
|
assert(done.isCompleted)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMutex() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
var counter = 0
|
||||||
|
val mutex = Mutex()
|
||||||
|
|
||||||
|
(1..4).map {
|
||||||
|
launch {
|
||||||
|
// mutex.withLock {
|
||||||
|
val c = counter
|
||||||
|
delay(5)
|
||||||
|
counter = c + 1
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}.forEach { it.await() }
|
||||||
|
println(counter)
|
||||||
|
assert( counter < 10 )
|
||||||
|
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import net.sergeych.lyng.eval
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
class OOTest {
|
||||||
|
@Test
|
||||||
|
fun testClassProps() = runTest {
|
||||||
|
eval("""
|
||||||
|
import lyng.time
|
||||||
|
|
||||||
|
class Point(x,y) {
|
||||||
|
static val origin = Point(0,0)
|
||||||
|
static var center = origin
|
||||||
|
}
|
||||||
|
assertEquals(Point(0,0), Point.origin)
|
||||||
|
assertEquals(Point(0,0), Point.center)
|
||||||
|
Point.center = Point(1,2)
|
||||||
|
assertEquals(Point(0,0), Point.origin)
|
||||||
|
assertEquals(Point(1,2), Point.center)
|
||||||
|
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun testClassMethods() = runTest {
|
||||||
|
eval("""
|
||||||
|
import lyng.time
|
||||||
|
|
||||||
|
class Point(x,y) {
|
||||||
|
private static var data = null
|
||||||
|
|
||||||
|
static fun getData() { data }
|
||||||
|
static fun setData(value) {
|
||||||
|
data = value
|
||||||
|
callFrom()
|
||||||
|
}
|
||||||
|
static fun callFrom() {
|
||||||
|
data = data + "!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(Point(0,0), Point(0,0) )
|
||||||
|
assertEquals(null, Point.getData() )
|
||||||
|
Point.setData("foo")
|
||||||
|
assertEquals( "foo!", Point.getData() )
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -299,4 +299,9 @@ class BookTest {
|
|||||||
runDocTests("../docs/time.md")
|
runDocTests("../docs/time.md")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testParallelismBook() = runBlocking {
|
||||||
|
runDocTests("../docs/parallelism.md")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user