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
|
||||
|
||||
- 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). 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,19 @@ Ready features:
|
||||
- [x] exception handling: throw, try-catch-finally, exception classes.
|
||||
- [x] multiplatform maven publication
|
||||
- [x] documentation for the current state
|
||||
|
||||
Under way:
|
||||
|
||||
- [x] maps, sets and sequences (flows?)
|
||||
- [ ] regular exceptions
|
||||
- [x] modules
|
||||
- [x] string formatting and tools
|
||||
- [ ] multiple inheritance for user classes
|
||||
- [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
|
||||
- [ ] kotlin part public API good docs, integration focused
|
||||
- [ ] better stack reporting
|
||||
@@ -158,12 +172,12 @@ Planned features.
|
||||
|
||||
- [ ] type specifications
|
||||
- [ ] 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
|
||||
|
||||
- [ ] 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
|
||||
+57
@@ -235,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:
|
||||
|
||||
@@ -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
|
||||
+13
-1
@@ -1259,7 +1259,19 @@ 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
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
group = "net.sergeych"
|
||||
version = "0.8.4-SNAPSHOT"
|
||||
version = "0.8.6-SNAPSHOT"
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
|
||||
@@ -65,9 +65,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)
|
||||
@@ -833,8 +841,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("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)
|
||||
@@ -1541,7 +1559,7 @@ class Compiler(
|
||||
}
|
||||
|
||||
private suspend fun
|
||||
parseFunctionDeclaration(
|
||||
parseFunctionDeclaration(
|
||||
visibility: Visibility = Visibility.Public,
|
||||
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false,
|
||||
isExtern: Boolean = false,
|
||||
@@ -1613,7 +1631,7 @@ class Compiler(
|
||||
(thisObj as? ObjInstance)?.let { i ->
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,6 +375,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 +409,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 +424,12 @@ private class Parser(fromPos: Pos) {
|
||||
}
|
||||
}
|
||||
|
||||
'\n', '\r'-> {
|
||||
newlineDetected = true
|
||||
sb.append(currentChar)
|
||||
pos.advance()
|
||||
}
|
||||
|
||||
else -> {
|
||||
sb.append(currentChar)
|
||||
pos.advance()
|
||||
@@ -404,7 +437,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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,6 +29,11 @@ class Script(
|
||||
|
||||
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)
|
||||
|
||||
internal val rootScope: Scope = Scope(null).apply {
|
||||
@@ -165,6 +170,11 @@ 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 ) {
|
||||
|
||||
@@ -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() )
|
||||
""".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++
|
||||
}
|
||||
assertEquals(p, Point(2,3))
|
||||
>>> void
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
@@ -2393,8 +2391,6 @@ class ScriptTest {
|
||||
y++
|
||||
}
|
||||
assertEquals(p, Point(2,3))
|
||||
>>> void
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
@@ -2794,4 +2790,45 @@ class ScriptTest {
|
||||
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 net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.Script
|
||||
import net.sergeych.lyng.leftMargin
|
||||
import net.sergeych.lyng.obj.ObjVoid
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Files.readAllLines
|
||||
@@ -16,18 +17,6 @@ import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
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(
|
||||
val fileName: String,
|
||||
val line: Int,
|
||||
|
||||
@@ -13,7 +13,6 @@ dependencyResolutionManagement {
|
||||
maven("https://maven.universablockchain.com/")
|
||||
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
|
||||
mavenLocal()
|
||||
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user