Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
804087f16d | ||
|
|
6d8eed7b8c | ||
|
|
e916d9805a | ||
|
|
c398496ee0 | ||
|
|
299738cffd | ||
|
|
62461c09cc | ||
|
|
63de82393a | ||
|
|
ed0a21cb06 |
No files matched your search
@@ -0,0 +1,25 @@
|
|||||||
|
<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>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
A KMP library and a standalone interpreter
|
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) {
|
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% 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!
|
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
|
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). See [parallelism]
|
`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 )`.
|
||||||
|
|
||||||
## Resources:
|
## Resources:
|
||||||
|
|
||||||
- [introduction and tutorial](docs/tutorial.md) - start here please
|
- [introduction and tutorial](docs/tutorial.md) - start here please
|
||||||
- [Samples directory](docs/samples)
|
- [Samples directory](docs/samples)
|
||||||
|
- [Books directory](docs)
|
||||||
|
|
||||||
## Integration in Kotlin multiplatform
|
## Integration in Kotlin multiplatform
|
||||||
|
|
||||||
@@ -84,8 +92,8 @@ import com.sun.source.tree.Scope
|
|||||||
import new.sergeych.lyng.*
|
import new.sergeych.lyng.*
|
||||||
|
|
||||||
// simple function
|
// simple function
|
||||||
val scope = Scope().apply {
|
val scope = Script.newScope().apply {
|
||||||
addFn("addArgs") {
|
addFn("sumOf") {
|
||||||
var sum = 0.0
|
var sum = 0.0
|
||||||
for (a in args) sum += a.toDouble()
|
for (a in args) sum += a.toDouble()
|
||||||
ObjReal(sum)
|
ObjReal(sum)
|
||||||
@@ -96,12 +104,14 @@ val scope = Scope().apply {
|
|||||||
// suspend fun doSomeWork(text: String): Int
|
// suspend fun doSomeWork(text: String): Int
|
||||||
addFn("doSomeWork") {
|
addFn("doSomeWork") {
|
||||||
// this _is_ a suspend lambda, we can call suspend function,
|
// this _is_ a suspend lambda, we can call suspend function,
|
||||||
// and it won't consume the thread:
|
// and it won't consume the thread.
|
||||||
doSomeWork(args[0].toString()).toObj()
|
// 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:
|
// 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.
|
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.
|
- Javascript, WasmJS, native, JVM, android - batteries included.
|
||||||
- dynamic types in most elegant and concise way
|
- 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
|
- good for functional an object-oriented style
|
||||||
|
|
||||||
# Language Roadmap
|
# Language Roadmap
|
||||||
@@ -139,15 +149,19 @@ Ready features:
|
|||||||
- [x] exception handling: throw, try-catch-finally, exception classes.
|
- [x] exception handling: throw, try-catch-finally, exception classes.
|
||||||
- [x] multiplatform maven publication
|
- [x] multiplatform maven publication
|
||||||
- [x] documentation for the current state
|
- [x] documentation for the current state
|
||||||
|
|
||||||
Under way:
|
|
||||||
|
|
||||||
- [x] maps, sets and sequences (flows?)
|
- [x] maps, sets and sequences (flows?)
|
||||||
- [ ] regular exceptions
|
|
||||||
- [x] modules
|
- [x] modules
|
||||||
- [x] string formatting and tools
|
- [x] string formatting and tools
|
||||||
- [ ] multiple inheritance for user classes
|
|
||||||
- [x] launch, deferred, CompletableDeferred, Mutex, etc.
|
- [x] launch, deferred, CompletableDeferred, Mutex, etc.
|
||||||
|
- [x] multiline strings
|
||||||
|
- [x] typesafe bit-effective serialization
|
||||||
|
- [x] compression/decompression (integrated in serialization)
|
||||||
|
- [x] dynamic fields
|
||||||
|
|
||||||
|
### Under way:
|
||||||
|
|
||||||
|
- [ ] regular exceptions
|
||||||
|
- [ ] multiple inheritance for user classes
|
||||||
- [ ] 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,12 +172,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
|
||||||
- [ ] macro-style kotlin integration or something else to simplify it
|
- [ ] metadata first class access from lyng
|
||||||
|
|
||||||
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, formulae, etc.
|
||||||
- [ ] language server or compose-based lyng-aware editor
|
- [ ] language server or compose-based lyng-aware editor
|
||||||
|
|
||||||
[parallelism]: docs/parallelism.md
|
[parallelism]: docs/parallelism.md
|
||||||
+57
@@ -235,6 +235,63 @@ as they are modifying the type, not the context.
|
|||||||
|
|
||||||
Beware of it. We might need to reconsider it later.
|
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
|
# Theory
|
||||||
|
|
||||||
## Basic principles:
|
## Basic principles:
|
||||||
|
|||||||
@@ -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 ) )
|
assert( почти_равны( 0.0005, 0.000501 ) )
|
||||||
|
|
||||||
Во многих случаях вычисление $n+1$ члена значительно проще cчитается от предыдущего члена, в нашем случае это можно было бы записать через итератор, что мы вскоре добавим.
|
Во многих случаях вычисление $n+1$ члена значительно проще cчитается от предыдущего члена, в нашем случае это можно было бы записать через итератор, см [Iterable] и `flow` в [parallelism].
|
||||||
|
|
||||||
(продолжение следует)
|
[Iterable]: ../Iterable.md
|
||||||
|
[parallelism]: ../parallelism.md
|
||||||
+13
-1
@@ -1259,7 +1259,19 @@ String literal could be multiline:
|
|||||||
"Hello
|
"Hello
|
||||||
World"
|
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
|
# Built-in functions
|
||||||
|
|
||||||
|
|||||||
@@ -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.4-SNAPSHOT"
|
version = "0.8.6-SNAPSHOT"
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
|
|||||||
@@ -65,9 +65,17 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
parseStatement(braceMeansLambda = true)?.also {
|
val s = parseStatement(braceMeansLambda = true)?.also {
|
||||||
statements += it
|
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)
|
} while (true)
|
||||||
return Script(start, statements)//returnScope.needCatch)
|
return Script(start, statements)//returnScope.needCatch)
|
||||||
@@ -833,8 +841,18 @@ 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", "private", "static") -> parseFunctionDeclaration(
|
||||||
cc.matchQualifiers("fun", "static") -> parseFunctionDeclaration(Visibility.Public, isExtern, isStatic = true)
|
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)
|
||||||
@@ -1541,7 +1559,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun
|
private suspend fun
|
||||||
parseFunctionDeclaration(
|
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,
|
||||||
@@ -1613,7 +1631,7 @@ class Compiler(
|
|||||||
(thisObj as? ObjInstance)?.let { i ->
|
(thisObj as? ObjInstance)?.let { i ->
|
||||||
fnBody.execute(ClosureScope(this, i.instanceScope))
|
fnBody.execute(ClosureScope(this, i.instanceScope))
|
||||||
}
|
}
|
||||||
// other classes can create one-time scope for this rare case:
|
// other classes can create one-time scope for this rare case:
|
||||||
?: fnBody.execute(thisObj.autoInstanceScope(this))
|
?: fnBody.execute(thisObj.autoInstanceScope(this))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -375,6 +375,32 @@ private class Parser(fromPos: Pos) {
|
|||||||
|
|
||||||
private val currentChar: Char get() = pos.currentChar
|
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 {
|
private fun loadStringToken(): Token {
|
||||||
val start = currentPos
|
val start = currentPos
|
||||||
|
|
||||||
@@ -383,6 +409,7 @@ private class Parser(fromPos: Pos) {
|
|||||||
// start = start.back()
|
// start = start.back()
|
||||||
|
|
||||||
val sb = StringBuilder()
|
val sb = StringBuilder()
|
||||||
|
var newlineDetected = false
|
||||||
while (currentChar != '"') {
|
while (currentChar != '"') {
|
||||||
if (pos.end) throw ScriptError(start, "unterminated string started there")
|
if (pos.end) throw ScriptError(start, "unterminated string started there")
|
||||||
when (currentChar) {
|
when (currentChar) {
|
||||||
@@ -397,6 +424,12 @@ private class Parser(fromPos: Pos) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
'\n', '\r'-> {
|
||||||
|
newlineDetected = true
|
||||||
|
sb.append(currentChar)
|
||||||
|
pos.advance()
|
||||||
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
sb.append(currentChar)
|
sb.append(currentChar)
|
||||||
pos.advance()
|
pos.advance()
|
||||||
@@ -404,7 +437,10 @@ private class Parser(fromPos: Pos) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pos.advance()
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ class Script(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
suspend fun newScope(pos: Pos = Pos.builtIn) = defaultImportManager.newStdScope(pos)
|
||||||
|
|
||||||
internal val rootScope: Scope = Scope(null).apply {
|
internal val rootScope: Scope = Scope(null).apply {
|
||||||
@@ -165,6 +170,11 @@ class Script(
|
|||||||
}
|
}
|
||||||
result ?: raiseError(ObjAssertionFailedException(this,"Expected exception but nothing was thrown"))
|
result ?: raiseError(ObjAssertionFailedException(this,"Expected exception but nothing was thrown"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addFn("dynamic") {
|
||||||
|
ObjDynamic.create(this, requireOnlyArg())
|
||||||
|
}
|
||||||
|
|
||||||
addFn("require") {
|
addFn("require") {
|
||||||
val condition = requiredArg<ObjBool>(0)
|
val condition = requiredArg<ObjBool>(0)
|
||||||
if( !condition.value ) {
|
if( !condition.value ) {
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
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") {}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -43,4 +43,50 @@ class OOTest {
|
|||||||
assertEquals( "foo!", Point.getData() )
|
assertEquals( "foo!", Point.getData() )
|
||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDynamicGet() = runTest {
|
||||||
|
eval("""
|
||||||
|
val accessor = dynamic {
|
||||||
|
get { name ->
|
||||||
|
if( name == "foo" ) "bar" else null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println("-- " + accessor.foo)
|
||||||
|
assertEquals("bar", accessor.foo)
|
||||||
|
assertEquals(null, accessor.bar)
|
||||||
|
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDelegateSet() = runTest {
|
||||||
|
eval("""
|
||||||
|
var setValueForBar = null
|
||||||
|
val accessor = dynamic {
|
||||||
|
get { name ->
|
||||||
|
when(name) {
|
||||||
|
"foo" -> "bar"
|
||||||
|
"bar" -> setValueForBar
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set { name, value ->
|
||||||
|
if( name == "bar" )
|
||||||
|
setValueForBar = value
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2375,8 +2375,6 @@ class ScriptTest {
|
|||||||
x++; y++
|
x++; y++
|
||||||
}
|
}
|
||||||
assertEquals(p, Point(2,3))
|
assertEquals(p, Point(2,3))
|
||||||
>>> void
|
|
||||||
|
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -2393,8 +2391,6 @@ class ScriptTest {
|
|||||||
y++
|
y++
|
||||||
}
|
}
|
||||||
assertEquals(p, Point(2,3))
|
assertEquals(p, Point(2,3))
|
||||||
>>> void
|
|
||||||
|
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -2794,4 +2790,45 @@ class ScriptTest {
|
|||||||
println(y.list)
|
println(y.list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMultilineStrings() = runTest {
|
||||||
|
assertEquals(
|
||||||
|
"""
|
||||||
|
This is a multiline text.
|
||||||
|
This is a second line.
|
||||||
|
""".trimIndent(), eval(
|
||||||
|
"""
|
||||||
|
"
|
||||||
|
This is a multiline text.
|
||||||
|
This is a second line.
|
||||||
|
"
|
||||||
|
""".trimIndent()
|
||||||
|
).toString()
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
"""
|
||||||
|
This is a multiline text.
|
||||||
|
""".trimIndent(), eval(
|
||||||
|
"""
|
||||||
|
"
|
||||||
|
This is a multiline text.
|
||||||
|
"
|
||||||
|
""".trimIndent()
|
||||||
|
).toString()
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
"""
|
||||||
|
|
||||||
|
This is a multiline text.
|
||||||
|
""".trimIndent(), eval(
|
||||||
|
"""
|
||||||
|
"
|
||||||
|
|
||||||
|
This is a multiline text.
|
||||||
|
"
|
||||||
|
""".trimIndent()
|
||||||
|
).toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import kotlinx.coroutines.runBlocking
|
|||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.Script
|
import net.sergeych.lyng.Script
|
||||||
|
import net.sergeych.lyng.leftMargin
|
||||||
import net.sergeych.lyng.obj.ObjVoid
|
import net.sergeych.lyng.obj.ObjVoid
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Files.readAllLines
|
import java.nio.file.Files.readAllLines
|
||||||
@@ -16,18 +17,6 @@ import kotlin.test.Test
|
|||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.fail
|
import kotlin.test.fail
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
data class DocTest(
|
data class DocTest(
|
||||||
val fileName: String,
|
val fileName: String,
|
||||||
val line: Int,
|
val line: Int,
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ dependencyResolutionManagement {
|
|||||||
maven("https://maven.universablockchain.com/")
|
maven("https://maven.universablockchain.com/")
|
||||||
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
|
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user