Compare commits

...
11 Commits
Author SHA1 Message Date
sergeych 65963ce537 v0.9.3 published for all platforms; dyanimc improvements & more 2025-11-02 14:20:35 +01:00
sergeych 9d222be924 added random bitArray 2025-11-01 22:19:40 +01:00
sergeych f00631cd88 some more docs 2025-11-01 22:10:26 +01:00
sergeych b8d6ff01a6 fixed invoke overriden field problem in dynamic and the dynamic's Lyng class. 2025-11-01 22:07:22 +01:00
sergeych ba725fc9ed less debug noise
open ObjDynamic
found a pfoblem with dynamic, see todo in OOTest.kt
2025-11-01 18:33:37 +01:00
sergeych 5d4d548275 lzw utils 2025-11-01 10:28:04 +01:00
sergeych 7e289382ed Scope.copy renamed to createChild for clarity; fixed error with Exception self serializing. 2025-10-31 14:58:02 +04:00
sergeych c854b06683 fixed comment before else in if;
dynamic now support indexing access
2025-10-04 11:25:19 +04:00
sergeych e8b630715a optimized BitArray.equals 2025-09-26 20:06:54 +04:00
sergeych 3481a718b1 serializable and hashable BitArray. Comparable BitList. Small improvements 2025-09-26 19:45:28 +04:00
sergeych 2696f1546d upgrading to kotlin 2.2 2025-09-26 11:48:40 +04:00
39 changed files with 465 additions and 224 deletions

No files matched your search

-54
View File
@@ -1,54 +0,0 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
name: Java CI with Gradle
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
workflow_call:
permissions:
contents: read
jobs:
build:
strategy:
matrix:
include:
- target: iosSimulatorArm64Test
os: macos-latest
- target: jvmTest
os: ubuntu-latest
- target: linuxX64Test
os: ubuntu-latest
- target: testDebugUnitTest
os: ubuntu-latest
- target: testReleaseUnitTest
os: ubuntu-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@v3
- uses: actions/cache@v3
with:
path: |
~/.konan
key: ${{ runner.os }}-${{ hashFiles('**/.lock') }}
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build with Gradle
uses: gradle/gradle-build-action@ce999babab2de1c4b649dc15f0ee67e6246c994f
with:
arguments: ${{ matrix.target }}
-25
View File
@@ -1,25 +0,0 @@
name: Publish
on:
release:
types: [released, prereleased]
jobs:
publish:
name: Release build and publish
runs-on: macOS-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 21
- name: Publish to MavenCentral
run: ./gradlew publishToMavenCentral --no-configuration-cache
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_KEY_ID }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_KEY_CONTENTS }}
@@ -1,42 +0,0 @@
<!--
~ 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>
+48 -1
View File
@@ -266,7 +266,7 @@ The same we can provide writable dynamic fields (var-type), adding set method:
// constant field // constant field
"foo" -> "bar" "foo" -> "bar"
// mutable field // mutable field
"bar" -> setValueForBar "bar" -> storedValueForBar
else -> throw SymbolNotFoundException() else -> throw SymbolNotFoundException()
} }
@@ -289,9 +289,56 @@ The same we can provide writable dynamic fields (var-type), adding set method:
assertThrows { assertThrows {
accessor.bad = "!23" accessor.bad = "!23"
} }
void
>>> void
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. 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.
### Dynamic indexers
Index access for dynamics is passed to the same getter and setter, so it is
generally the same:
var storedValue = "bar"
val x = dynamic {
get {
if( it == "foo" ) storedValue
else null
}
}
assertEquals("bar", x["foo"] )
assertEquals("bar", x.foo )
>>> void
And assigning them works the same. You can make it working
mimicking arrays, but remember, it is not Collection so
collection's sugar won't work with it:
var storedValue = "bar"
val x = dynamic {
get {
when(it) {
"size" -> 1
0 -> storedValue
else -> null
}
}
set { index, value ->
if( index == 0 ) storedValue = value
else throw "Illegal index: "+index
}
}
assertEquals("bar", x[0] )
assertEquals(1, x.size )
x[0] = "buzz"
assertThrows { x[1] = 1 }
assertEquals("buzz", storedValue)
assertEquals("buzz", x[0])
>>> void
If you want dynamic to function like an array, create a [feature
request](https://gitea.sergeych.net/SergeychWorks/lyng/issues).
# Theory # Theory
## Basic principles: ## Basic principles:
View File
Whitespace-only changes.
+26
View File
@@ -0,0 +1,26 @@
# String
# This document is for developer notes only
--------------------------
## Interpolation proposal
"""no $iterpolation"""
val inpterpolation1 = "foo"
"${interpolation1}"
>>> "foo"
"$interpolation2"
"no $$ interpolatino"
## Regexp vs div / ?
```EBNF
regex_literal = "/", { regchar }, "/", [ flag ]
foag = "i" | "n"....
regchar = x`x`
```
+5 -5
View File
@@ -21,10 +21,10 @@ import lyng.time
// //
// After all optimizations it takes ~120ms. // After all optimizations it takes ~120ms.
// //
//for( r in 1..100 ) { for( r in 1..100 ) {
// val start = Instant.now() val start = Instant.now()
val found = naiveCountHappyNumbers() val found = naiveCountHappyNumbers()
// println("Found happy numbers: %d time %s"(found, Instant.now() - start)) println("Found happy numbers: %d time %s"(found, Instant.now() - start))
assert( found == 55252 ) assert( found == 55252 )
// delay(0.01) delay(0.1)
//} }
+1 -1
View File
@@ -1403,7 +1403,7 @@ See [math functions](math.md). Other general purpose functions are:
[Range]: Range.md [Range]: Range.md
[String]: String.md [String]: development/String.md
[string formatting]: https://github.com/sergeych/mp_stools?tab=readme-ov-file#sprintf-syntax-summary [string formatting]: https://github.com/sergeych/mp_stools?tab=readme-ov-file#sprintf-syntax-summary
+2 -2
View File
@@ -1,10 +1,10 @@
[versions] [versions]
agp = "8.5.2" agp = "8.5.2"
clikt = "5.0.3" clikt = "5.0.3"
kotlin = "2.1.21" kotlin = "2.2.20"
android-minSdk = "24" android-minSdk = "24"
android-compileSdk = "34" android-compileSdk = "34"
kotlinx-coroutines = "1.9.0" kotlinx-coroutines = "1.10.2"
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"
+1 -1
View File
@@ -16,7 +16,7 @@
*/ */
plugins { plugins {
kotlin("multiplatform") version "2.1.21" kotlin("multiplatform") version "2.2.20"
} }
group = "net.sergeych" group = "net.sergeych"
+10 -6
View File
@@ -21,7 +21,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.9.0-SNAPSHOT" version = "0.9.3"
buildscript { buildscript {
repositories { repositories {
@@ -37,7 +37,7 @@ plugins {
alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidLibrary) alias(libs.plugins.androidLibrary)
// alias(libs.plugins.vanniktech.mavenPublish) // alias(libs.plugins.vanniktech.mavenPublish)
kotlin("plugin.serialization") version "2.2.0" kotlin("plugin.serialization") version "2.1.0"
id("com.codingfeline.buildkonfig") version "0.17.1" id("com.codingfeline.buildkonfig") version "0.17.1"
`maven-publish` `maven-publish`
} }
@@ -62,10 +62,14 @@ kotlin {
jvmTarget.set(JvmTarget.JVM_11) jvmTarget.set(JvmTarget.JVM_11)
} }
} }
// iosX64() iosX64()
// iosArm64() iosArm64()
// iosSimulatorArm64() iosSimulatorArm64()
macosX64()
macosArm64()
mingwX64()
linuxX64() linuxX64()
linuxArm64()
js { js {
browser() browser()
nodejs() nodejs()
@@ -79,9 +83,9 @@ kotlin {
sourceSets { sourceSets {
all { all {
languageSettings.optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") languageSettings.optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
languageSettings.optIn("kotlin.contracts.ExperimentalContracts")
languageSettings.optIn("kotlin.ExperimentalUnsignedTypes") languageSettings.optIn("kotlin.ExperimentalUnsignedTypes")
languageSettings.optIn("kotlin.coroutines.DelicateCoroutinesApi") languageSettings.optIn("kotlin.coroutines.DelicateCoroutinesApi")
languageSettings.optIn("kotlin.contracts.ExperimentalContracts")
languageSettings.optIn("kotlinx.coroutines.flow.DelicateCoroutinesApi") languageSettings.optIn("kotlinx.coroutines.flow.DelicateCoroutinesApi")
} }
@@ -796,7 +796,7 @@ class Compiler(
val v = left.getter(context) val v = left.getter(context)
if (v.value == ObjNull && isOptional) return@Accessor v.value.asReadonly if (v.value == ObjNull && isOptional) return@Accessor v.value.asReadonly
v.value.callOn( v.value.callOn(
context.copy( context.createChildScope(
context.pos, context.pos,
args.toArguments(context, detectedBlockArgument) args.toArguments(context, detectedBlockArgument)
// Arguments( // Arguments(
@@ -888,6 +888,7 @@ class Compiler(
} }
} }
@Suppress("SameParameterValue")
private fun parseNumber(isPlus: Boolean): Obj { private fun parseNumber(isPlus: Boolean): Obj {
return parseNumberOrNull(isPlus) ?: throw ScriptError(cc.currentPos(), "Expecting number") return parseNumberOrNull(isPlus) ?: throw ScriptError(cc.currentPos(), "Expecting number")
} }
@@ -901,7 +902,7 @@ class Compiler(
val args = extras?.let { required + it } ?: required val args = extras?.let { required + it } ?: required
val fn = scope.get(t.value)?.value ?: scope.raiseSymbolNotFound("annotation not found: ${t.value}") 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}") if (fn !is Statement) scope.raiseIllegalArgument("annotation must be callable, got ${fn.objClass}")
(fn.execute(scope.copy(Arguments(args))) as? Statement) (fn.execute(scope.createChildScope(Arguments(args))) as? Statement)
?: scope.raiseClassCastError("function annotation must return callable") ?: scope.raiseClassCastError("function annotation must return callable")
} }
} }
@@ -1196,7 +1197,7 @@ class Compiler(
} }
} }
if (exceptionObject != null) { if (exceptionObject != null) {
val catchContext = this.copy(pos = cdata.catchVar.pos) val catchContext = this.createChildScope(pos = cdata.catchVar.pos)
catchContext.addItem(cdata.catchVar.value, false, objException) catchContext.addItem(cdata.catchVar.value, false, objException)
result = cdata.block.execute(catchContext) result = cdata.block.execute(catchContext)
isCaught = true isCaught = true
@@ -1312,7 +1313,7 @@ class Compiler(
// accessors, constructor registration, etc. // accessors, constructor registration, etc.
addItem(className, false, newClass) addItem(className, false, newClass)
if (initScope.isNotEmpty()) { if (initScope.isNotEmpty()) {
val classScope = copy(newThisObj = newClass) val classScope = createChildScope(newThisObj = newClass)
newClass.classScope = classScope newClass.classScope = classScope
for (s in initScope) for (s in initScope)
s.execute(classScope) s.execute(classScope)
@@ -1366,7 +1367,7 @@ class Compiler(
return statement(body.pos) { cxt -> return statement(body.pos) { cxt ->
val forContext = cxt.copy(start) val forContext = cxt.createChildScope(start)
// loop var: StoredObject // loop var: StoredObject
val loopSO = forContext.addItem(tVar.value, true, ObjNull) val loopSO = forContext.addItem(tVar.value, true, ObjNull)
@@ -1535,7 +1536,7 @@ class Compiler(
var result: Obj = ObjVoid var result: Obj = ObjVoid
lateinit var doScope: Scope lateinit var doScope: Scope
do { do {
doScope = it.copy().apply { skipScopeCreation = true } doScope = it.createChildScope().apply { skipScopeCreation = true }
try { try {
result = body.execute(doScope) result = body.execute(doScope)
} catch (e: LoopBreakContinueException) { } catch (e: LoopBreakContinueException) {
@@ -1681,7 +1682,7 @@ class Compiler(
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true) cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
// could be else block: // could be else block:
val t2 = cc.next() val t2 = cc.nextNonWhitespace()
// we generate different statements: optimization // we generate different statements: optimization
return if (t2.type == Token.Type.ID && t2.value == "else") { return if (t2.type == Token.Type.ID && t2.value == "else") {
@@ -1815,7 +1816,7 @@ class Compiler(
val block = parseScript() val block = parseScript()
return statement(startPos) { return statement(startPos) {
// block run on inner context: // block run on inner context:
block.execute(if (it.skipScopeCreation) it else it.copy(startPos)) block.execute(if (it.skipScopeCreation) it else it.createChildScope(startPos))
}.also { }.also {
val t1 = cc.next() val t1 = cc.next()
if (t1.type != Token.Type.RBRACE) if (t1.type != Token.Type.RBRACE)
@@ -164,6 +164,7 @@ class CompilerContext(val tokens: List<Token>) {
} }
} }
@Suppress("NOTHING_TO_INLINE")
inline fun addBreak() { inline fun addBreak() {
breakFound = true breakFound = true
} }
@@ -28,7 +28,9 @@ data class Pos(val source: Source, val line: Int, val column: Int) {
else if( line > 0) Pos(source, line-1, source.lines[line-1].length - 1) else if( line > 0) Pos(source, line-1, source.lines[line-1].length - 1)
else throw IllegalStateException("can't go back from line 0, column 0") else throw IllegalStateException("can't go back from line 0, column 0")
val currentLine: String get() = if( end ) "EOF" else source.lines[line] val currentLine: String get() =
if( end ) "EOF"
else if( line >= 0 ) source.lines[line] else "<no line information>"
val end: Boolean get() = line >= source.lines.size val end: Boolean get() = line >= source.lines.size
@@ -138,13 +138,27 @@ open class Scope(
) )
} }
fun copy(pos: Pos, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Scope = /**
* Creates a new child scope using the provided arguments and optional `thisObj`.
*/
fun createChildScope(pos: Pos, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Scope =
Scope(this, args, pos, newThisObj ?: thisObj) Scope(this, args, pos, newThisObj ?: thisObj)
fun copy(args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Scope = /**
* Creates a new child scope using the provided arguments and optional `thisObj`.
* The child scope inherits the current scope's properties such as position and the existing `thisObj` if no new `thisObj` is provided.
*
* @param args The arguments to associate with the child scope. Defaults to [Arguments.EMPTY].
* @param newThisObj The new `thisObj` to associate with the child scope. Defaults to the current scope's `thisObj` if not provided.
* @return A new instance of [Scope] initialized with the specified arguments and `thisObj`.
*/
fun createChildScope(args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Scope =
Scope(this, args, pos, newThisObj ?: thisObj) Scope(this, args, pos, newThisObj ?: thisObj)
fun copy() = Scope(this, args, pos, thisObj) /**
* @return A child scope with the same arguments, position and [thisObj]
*/
fun createChildScope() = Scope(this, args, pos, thisObj)
fun addItem( fun addItem(
name: String, name: String,
@@ -240,8 +254,6 @@ open class Scope(
p = p.parent p = p.parent
} }
println("--------------------") println("--------------------")
ObjVoid
} }
open fun applyClosure(closure: Scope): Scope = ClosureScope(this, closure) open fun applyClosure(closure: Scope): Scope = ClosureScope(this, closure)
@@ -26,6 +26,7 @@ import net.sergeych.lynon.ObjLynonClass
import net.sergeych.mp_tools.globalDefer import net.sergeych.mp_tools.globalDefer
import kotlin.math.* import kotlin.math.*
@Suppress("TYPE_INTERSECTION_AS_REIFIED_WARNING")
class Script( class Script(
override val pos: Pos, override val pos: Pos,
private val statements: List<Statement> = emptyList(), private val statements: List<Statement> = emptyList(),
@@ -182,7 +183,7 @@ class Script(
catch( e: ExecutionError ) { catch( e: ExecutionError ) {
e.errorObject e.errorObject
} }
catch (e: ScriptError) { catch (_: ScriptError) {
ObjNull ObjNull
} }
result ?: raiseError(ObjAssertionFailedException(this,"Expected exception but nothing was thrown")) result ?: raiseError(ObjAssertionFailedException(this,"Expected exception but nothing was thrown"))
@@ -26,7 +26,7 @@ open class ScriptError(val pos: Pos, val errorMessage: String, cause: Throwable?
$pos: Error: $errorMessage $pos: Error: $errorMessage
${pos.currentLine} ${pos.currentLine}
${"-".repeat(pos.column)}^ ${if( pos.column >= 0 ) "-".repeat(pos.column) + "^" else ""}
""".trimIndent(), """.trimIndent(),
cause cause
) )
@@ -30,7 +30,7 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
@Suppress("unused") @Suppress("unused")
enum class Type { enum class Type {
ID, INT, REAL, HEX, STRING, CHAR, ID, INT, REAL, HEX, STRING, STRING2, REGEX, CHAR,
LPAREN, RPAREN, LBRACE, RBRACE, LBRACKET, RBRACKET, COMMA, LPAREN, RPAREN, LBRACE, RBRACE, LBRACKET, RBRACKET, COMMA,
SEMICOLON, COLON, SEMICOLON, COLON,
PLUS, MINUS, STAR, SLASH, PERCENT, PLUS, MINUS, STAR, SLASH, PERCENT,
@@ -17,15 +17,12 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.sergeych.lyng.* import net.sergeych.lyng.*
import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType import net.sergeych.lynon.LynonType
import net.sergeych.synctools.ProtectedOp
open class Obj { open class Obj {
@@ -39,12 +36,12 @@ open class Obj {
var isFrozen: Boolean = false var isFrozen: Boolean = false
private val monitor = Mutex() // private val monitor = Mutex()
// private val memberMutex = Mutex() // private val memberMutex = Mutex()
internal var parentInstances: MutableList<Obj> = mutableListOf() // internal var parentInstances: MutableList<Obj> = mutableListOf()
private val opInstances = ProtectedOp() // private val opInstances = ProtectedOp()
open suspend fun inspect(scope: Scope): String = toString(scope).value open suspend fun inspect(scope: Scope): String = toString(scope).value
@@ -100,7 +97,7 @@ open class Obj {
// note that getInstanceMember traverses the hierarchy // note that getInstanceMember traverses the hierarchy
objClass.getInstanceMember(scope.pos, name).value objClass.getInstanceMember(scope.pos, name).value
fun getMemberOrNull(name: String): Obj? = objClass.getInstanceMemberOrNull(name)?.value // fun getMemberOrNull(name: String): Obj? = objClass.getInstanceMemberOrNull(name)?.value
// methods that to override // methods that to override
@@ -232,14 +229,14 @@ open class Obj {
if (isFrozen) scope.raiseError("attempt to mutate frozen object") if (isFrozen) scope.raiseError("attempt to mutate frozen object")
} }
suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() } // suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
open suspend fun readField(scope: Scope, name: String): ObjRecord { open suspend fun readField(scope: Scope, name: String): ObjRecord {
// could be property or class field: // could be property or class field:
val obj = objClass.getInstanceMemberOrNull(name) ?: scope.raiseError("no such field: $name") val obj = objClass.getInstanceMemberOrNull(name) ?: scope.raiseError("no such field: $name")
return when (val value = obj.value) { return when (val value = obj.value) {
is Statement -> { is Statement -> {
ObjRecord(value.execute(scope.copy(scope.pos, newThisObj = this)), obj.isMutable) ObjRecord(value.execute(scope.createChildScope(scope.pos, newThisObj = this)), obj.isMutable)
} }
// could be writable property naturally // could be writable property naturally
// null -> ObjNull.asReadonly // null -> ObjNull.asReadonly
@@ -268,11 +265,11 @@ open class Obj {
} }
suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments): Obj = suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments): Obj =
callOn(scope.copy(scope.pos, args = args, newThisObj = thisObj)) callOn(scope.createChildScope(scope.pos, args = args, newThisObj = thisObj))
suspend fun invoke(scope: Scope, thisObj: Obj, vararg args: Obj): Obj = suspend fun invoke(scope: Scope, thisObj: Obj, vararg args: Obj): Obj =
callOn( callOn(
scope.copy( scope.createChildScope(
scope.pos, scope.pos,
args = Arguments(args.toList()), args = Arguments(args.toList()),
newThisObj = thisObj newThisObj = thisObj
@@ -281,7 +278,7 @@ open class Obj {
suspend fun invoke(scope: Scope, thisObj: Obj): Obj = suspend fun invoke(scope: Scope, thisObj: Obj): Obj =
callOn( callOn(
scope.copy( scope.createChildScope(
scope.pos, scope.pos,
args = Arguments.EMPTY, args = Arguments.EMPTY,
newThisObj = thisObj newThisObj = thisObj
@@ -289,7 +286,7 @@ open class Obj {
) )
suspend fun invoke(scope: Scope, atPos: Pos, thisObj: Obj, args: Arguments): Obj = suspend fun invoke(scope: Scope, atPos: Pos, thisObj: Obj, args: Arguments): Obj =
callOn(scope.copy(atPos, args = args, newThisObj = thisObj)) callOn(scope.createChildScope(atPos, args = args, newThisObj = thisObj))
val asReadonly: ObjRecord by lazy { ObjRecord(this, false) } val asReadonly: ObjRecord by lazy { ObjRecord(this, false) }
@@ -302,7 +299,7 @@ open class Obj {
} }
fun autoInstanceScope(parent: Scope): Scope { fun autoInstanceScope(parent: Scope): Scope {
val scope = parent.copy(newThisObj = this, args = parent.args) val scope = parent.createChildScope(newThisObj = this, args = parent.args)
for (m in objClass.members) { for (m in objClass.members) {
scope.objects[m.key] = m.value scope.objects[m.key] = m.value
} }
@@ -335,7 +332,7 @@ open class Obj {
} }
// utilities // utilities
addFn("let") { addFn("let") {
args.firstAndOnly().callOn(copy(Arguments(thisObj))) args.firstAndOnly().callOn(createChildScope(Arguments(thisObj)))
} }
addFn("apply") { addFn("apply") {
val body = args.firstAndOnly() val body = args.firstAndOnly()
@@ -347,7 +344,7 @@ open class Obj {
thisObj thisObj
} }
addFn("also") { addFn("also") {
args.firstAndOnly().callOn(copy(Arguments(thisObj))) args.firstAndOnly().callOn(createChildScope(Arguments(thisObj)))
thisObj thisObj
} }
addFn("run") { addFn("run") {
@@ -367,6 +364,7 @@ open class Obj {
} }
@Suppress("NOTHING_TO_INLINE")
inline fun from(obj: Any?): Obj { inline fun from(obj: Any?): Obj {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
return when (obj) { return when (obj) {
@@ -35,12 +35,12 @@ class ObjBitBuffer(val bitArray: BitArray) : Obj() {
}.apply { }.apply {
addFn("toBuffer") { addFn("toBuffer") {
requireNoArgs() requireNoArgs()
ObjBuffer(thisAs<ObjBitBuffer>().bitArray.asUbyteArray()) ObjBuffer(thisAs<ObjBitBuffer>().bitArray.asUByteArray())
} }
addFn("toDump") { addFn("toDump") {
requireNoArgs() requireNoArgs()
ObjString( ObjString(
thisAs<ObjBitBuffer>().bitArray.asUbyteArray().toDump() thisAs<ObjBitBuffer>().bitArray.asUByteArray().toDump()
) )
} }
addFn("size") { addFn("size") {
@@ -61,7 +61,7 @@ open class ObjClass(
override suspend fun callOn(scope: Scope): Obj { override suspend fun callOn(scope: Scope): Obj {
val instance = ObjInstance(this) val instance = ObjInstance(this)
instance.instanceScope = scope.copy(newThisObj = instance, args = scope.args) instance.instanceScope = scope.createChildScope(newThisObj = instance, args = scope.args)
if (instanceConstructor != null) { if (instanceConstructor != null) {
instanceConstructor!!.execute(instance.instanceScope) instanceConstructor!!.execute(instance.instanceScope)
} }
@@ -69,7 +69,7 @@ open class ObjClass(
} }
suspend fun callWithArgs(scope: Scope, vararg plainArgs: Obj): Obj { suspend fun callWithArgs(scope: Scope, vararg plainArgs: Obj): Obj {
return callOn(scope.copy(Arguments(*plainArgs))) return callOn(scope.createChildScope(Arguments(*plainArgs)))
} }
@@ -47,13 +47,20 @@ class ObjDynamicContext(val delegate: ObjDynamic) : Obj() {
} }
} }
class ObjDynamic : Obj() { /**
* Object that delegates all its field access/invocation operations to a callback. It is used to implement dynamic
* objects using "dynamic" keyword.
*/
open class ObjDynamic(var readCallback: Statement? = null, var writeCallback: Statement? = null) : Obj() {
internal var readCallback: Statement? = null override val objClass: ObjClass = type
internal var writeCallback: Statement? = null
/**
* Use read callback to dynamically resolve the field name. Note that it does not work
* with method invocation which is implemented separately in [invokeInstanceMethod] below.
*/
override suspend fun readField(scope: Scope, name: String): ObjRecord { override suspend fun readField(scope: Scope, name: String): ObjRecord {
return readCallback?.execute(scope.copy(Arguments(ObjString(name))))?.let { return readCallback?.execute(scope.createChildScope(Arguments(ObjString(name))))?.let {
if (writeCallback != null) if (writeCallback != null)
it.asMutable it.asMutable
else else
@@ -62,21 +69,53 @@ class ObjDynamic : Obj() {
?: super.readField(scope, name) ?: super.readField(scope, name)
} }
/**
* Notice that invocation currently does not rely on [readField], which is a buffy moment to be reconsidered
* in the future, so we implement it separately:
*/
override suspend fun invokeInstanceMethod(
scope: Scope,
name: String,
args: Arguments,
onNotFoundResult: (() -> Obj?)?
): Obj {
val over = readCallback?.execute(
scope.createChildScope(
Arguments(ObjString(name)
)
)
)
return over?.invoke(scope, scope.thisObj, args)
?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
}
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) { override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
writeCallback?.execute(scope.copy(Arguments(ObjString(name), newValue))) writeCallback?.execute(scope.createChildScope(Arguments(ObjString(name), newValue)))
?: super.writeField(scope, name, newValue) ?: super.writeField(scope, name, newValue)
} }
override suspend fun getAt(scope: Scope, index: Obj): Obj {
return readCallback?.execute(scope.createChildScope(Arguments(index)))
?: super.getAt(scope, index)
}
override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
writeCallback?.execute(scope.createChildScope(Arguments(index, newValue)))
?: super.putAt(scope, index, newValue)
}
companion object { companion object {
suspend fun create(scope: Scope, builder: Statement): ObjDynamic { suspend fun create(scope: Scope, builder: Statement): ObjDynamic {
val delegate = ObjDynamic() val delegate = ObjDynamic()
val context = ObjDynamicContext(delegate) val context = ObjDynamicContext(delegate)
builder.execute(scope.copy(newThisObj = context)) builder.execute(scope.createChildScope(newThisObj = context))
return delegate return delegate
} }
val type = object : ObjClass("Delegate") {} val type = object : ObjClass("Delegate") {}.apply {
// addClassConst("IndexGetName", operatorGetName)
}
} }
} }
@@ -28,9 +28,9 @@ import net.sergeych.synctools.withLock
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
/** /**
* note on [getStackTrace]. If [useStackTrace] is not null, it is used instead. Otherwise, it is calculated * Note on [getStackTrace]. If [useStackTrace] is not null, it is used instead. Otherwise, it is calculated
* from the current scope which is treated as exception scope. It is used to restore serialized * from the current scope, which is treated as an exception scope. It is used to restore a serialized
* exception with stack trace; the scope of the de-serialized exception is not valid * exception with stack trace; the scope of the deserialized exception is not valid
* for stack unwinding. * for stack unwinding.
*/ */
open class ObjException( open class ObjException(
@@ -127,7 +127,7 @@ open class ObjException(
} }
} }
val Root = ExceptionClass("Throwable").apply { val Root = ExceptionClass("Exception").apply {
addConst("message", statement { addConst("message", statement {
(thisObj as ObjException).message.toObj() (thisObj as ObjException).message.toObj()
}) })
@@ -58,7 +58,7 @@ class ObjFlowBuilder(val output: SendChannel<Obj>) : Obj() {
private fun createLyngFlowInput(scope: Scope, producer: Statement): ReceiveChannel<Obj> { private fun createLyngFlowInput(scope: Scope, producer: Statement): ReceiveChannel<Obj> {
val channel = Channel<Obj>(Channel.RENDEZVOUS) val channel = Channel<Obj>(Channel.RENDEZVOUS)
val builder = ObjFlowBuilder(channel) val builder = ObjFlowBuilder(channel)
val builderScope = scope.copy(newThisObj = builder) val builderScope = scope.createChildScope(newThisObj = builder)
globalLaunch { globalLaunch {
try { try {
producer.execute(builderScope) producer.execute(builderScope)
@@ -32,7 +32,7 @@ class ObjInstanceClass(val name: String) : ObjClass(name) {
val actualSize = constructorMeta?.params?.size ?: 0 val actualSize = constructorMeta?.params?.size ?: 0
if (args.size > actualSize) if (args.size > actualSize)
scope.raiseIllegalArgument("constructor $name has only $actualSize but serialized version has ${args.size}") scope.raiseIllegalArgument("constructor $name has only $actualSize but serialized version has ${args.size}")
val newScope = scope.copy(args = Arguments(args)) val newScope = scope.createChildScope(args = Arguments(args))
return (callOn(newScope) as ObjInstance).apply { return (callOn(newScope) as ObjInstance).apply {
deserializeStateVars(scope, decoder) deserializeStateVars(scope, decoder)
invokeInstanceMethod(scope, "onDeserialized") { ObjVoid } invokeInstanceMethod(scope, "onDeserialized") { ObjVoid }
@@ -93,7 +93,7 @@ val ObjIterable by lazy {
val fn = requiredArg<Statement>(0) val fn = requiredArg<Statement>(0)
while (it.invokeInstanceMethod(this, "hasNext").toBool()) { while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
val x = it.invokeInstanceMethod(this, "next") val x = it.invokeInstanceMethod(this, "next")
fn.execute(this.copy(Arguments(listOf(x)))) fn.execute(this.createChildScope(Arguments(listOf(x))))
} }
ObjVoid ObjVoid
} }
@@ -71,7 +71,7 @@ abstract class ImportProvider(
newModuleAt(pos).also { newModuleAt(pos).also {
it.eval("import lyng.stdlib\n") it.eval("import lyng.stdlib\n")
} }
}.copy() }.createChildScope()
} }
@@ -60,7 +60,7 @@ abstract class Statement(
val type = ObjClass("Callable") val type = ObjClass("Callable")
} }
suspend fun call(scope: Scope, vararg args: Obj) = execute(scope.copy(args = Arguments(*args))) suspend fun call(scope: Scope, vararg args: Obj) = execute(scope.createChildScope(args = Arguments(*args)))
} }
@@ -17,8 +17,10 @@
package net.sergeych.lynon package net.sergeych.lynon
import kotlin.math.min
@Suppress("unused") @Suppress("unused")
interface BitList { interface BitList: Comparable<BitList> {
operator fun get(bitIndex: Long): Int operator fun get(bitIndex: Long): Int
operator fun set(bitIndex: Long,value: Int) operator fun set(bitIndex: Long,value: Int)
val size: Long val size: Long
@@ -31,6 +33,22 @@ interface BitList {
if( index < size) this@BitList[index++] if( index < size) this@BitList[index++]
else null else null
} }
override fun compareTo(other: BitList): Int {
val m = min(size, other.size)
for( i in 0 ..< m) {
val a = this[i]
val b = other[i]
when {
a < b -> return -1
a > b -> return 1
}
}
if( size > other.size) return 1
if( size < other.size) return -1
return 0
}
} }
fun bitListOf(vararg bits: Int): BitList { fun bitListOf(vararg bits: Int): BitList {
@@ -91,14 +91,15 @@ open class LynonDecoder(val bin: BitInput, val settings: LynonSettings = LynonSe
val type = LynonType.entries[getBitsAsInt(4)] val type = LynonType.entries[getBitsAsInt(4)]
val list = mutableListOf<Obj>() val list = mutableListOf<Obj>()
val objClass = if (type == LynonType.Other) val objClass = if (type == LynonType.Other)
decodeClassObj(scope).also { println("detected class obj: $it") } decodeClassObj(scope)//.also { println("detected class obj: $it") }
else type.objClass else type.objClass
val size = fixedSize ?: bin.unpackUnsigned().toInt() val size = fixedSize ?: bin.unpackUnsigned().toInt()
println("detected homogenous list type $type, $size items") // println("detected homogenous list type $type, $size items")
for (i in 0..<size) { for (i in 0..<size) {
list += decodeObject(scope, objClass, type).also { list += decodeObject(scope, objClass, type)
println("decoded: $it") //.also {
} // println("decoded: $it")
// }
} }
list list
} else { } else {
@@ -17,13 +17,18 @@
package net.sergeych.lynon package net.sergeych.lynon
import kotlinx.serialization.Serializable
import kotlin.math.min import kotlin.math.min
/** /**
* BitList implementation as fixed suze array of bits; indexing works exactly same as if * BitList implementation as fixed suze array of bits; indexing works exactly same as if
* [MemoryBitInput] is used with [MemoryBitInput.getBit]. See [MemoryBitOutput] for * [MemoryBitInput] is used with [MemoryBitInput.getBit]. See [MemoryBitOutput] for
* bits order and more information. * bits order and more information.
*
* It is [BitList] - comparable, and provides valid [hashCode] and [equals], so it could
* also be used as a key in maps.
*/ */
@Serializable
class BitArray(val bytes: UByteArray, val lastByteBits: Int) : BitList { class BitArray(val bytes: UByteArray, val lastByteBits: Int) : BitList {
val bytesSize: Int get() = bytes.size val bytesSize: Int get() = bytes.size
@@ -73,7 +78,21 @@ class BitArray(val bytes: UByteArray, val lastByteBits: Int) : BitList {
fun asByteArray(): ByteArray = bytes.asByteArray() fun asByteArray(): ByteArray = bytes.asByteArray()
@Suppress("unused") @Suppress("unused")
fun asUbyteArray(): UByteArray = bytes fun asUByteArray(): UByteArray = bytes
override fun equals(other: Any?): Boolean {
return when(other) {
is BitArray ->
// important: size must match as trailing zero bits will generate false eq otherwise:
size == other.size && bytes contentEquals other.bytes
is BitList -> compareTo(other) == 0
else -> false
}
}
override fun hashCode(): Int {
return bytes.contentHashCode()
}
companion object { companion object {
@@ -17,6 +17,8 @@
package net.sergeych.lynon package net.sergeych.lynon
import kotlin.random.Random
/** /**
* Hoq many tetrades needed to store the value. It is faster to use this function * Hoq many tetrades needed to store the value. It is faster to use this function
* than to use sizeInBits * than to use sizeInBits
@@ -35,7 +37,7 @@ fun sizeInTetrades(value: ULong): Int {
} }
/** /**
* How many bits needed to store the value. Size for 0 is 1, * Calculates ow many bits needed to store the value. Size for 0, for example, is 1.
*/ */
@Suppress("unused") @Suppress("unused")
fun sizeInBits(value: ULong): Int { fun sizeInBits(value: ULong): Int {
@@ -50,3 +52,20 @@ fun sizeInBits(value: ULong): Int {
} }
fun sizeInBits(value: Int): Int = sizeInBits(value.toULong()) fun sizeInBits(value: Int): Int = sizeInBits(value.toULong())
/**
* Generates a random BitArray of the specified size. Important: this is
* __not cryptographically secure__. Use random from [crypto2](https://gitea.sergeych.net/sergeych/crypto2),
* `randomInt()`, `randomUBytes()`, etc., for cryptographically secure random data.
*
* @param sizeInBits The size of the BitArray to generate in bits.
* @return A BitArray of the specified size filled with random bits.
*/
@Suppress("unused")
fun BitArray.Companion.random(sizeInBits: Int): BitArray {
val result = BitArray.withBitSize(sizeInBits.toLong())
for (i in 0..<sizeInBits) {
result[i.toLong()] = Random.nextInt() and 1
}
return result
}
@@ -18,7 +18,6 @@
package net.sergeych.lynon package net.sergeych.lynon
import net.sergeych.collections.SortedList import net.sergeych.collections.SortedList
import net.sergeych.lynon.Huffman.Alphabet
/** /**
@@ -285,7 +284,7 @@ object Huffman {
fun <T>decompress(bin: BitInput,alphabet: Alphabet<T>): UByteArray { fun <T>decompress(bin: BitInput,alphabet: Alphabet<T>): UByteArray {
val codes = deserializeCanonicCodes(bin, alphabet) val codes = deserializeCanonicCodes(bin, alphabet)
return decompressUsingCodes(bin, codes, alphabet).asUbyteArray() return decompressUsingCodes(bin, codes, alphabet).asUByteArray()
} }
} }
@@ -21,7 +21,9 @@ import net.sergeych.bintools.ByteChunk
import kotlin.math.roundToInt import kotlin.math.roundToInt
/** /**
* LZW lightweight pure kotlin compression. * LZW lightweight pure kotlin compression. LZW works with but streams [BitInput] and [BitOutput]
* to be used effectively in Lynon or other bit-grained formats. To safely comress byte arresy and
* strings without boilerplate use [lzwCompress], [lzwDecompress], [lzwCompressUtf8] and [lzwDecompressUtf8].
*/ */
object LZW { object LZW {
@@ -30,8 +32,10 @@ object LZW {
val MAX_DICT_SIZE = (STOP_CODE * 0.92).roundToInt() val MAX_DICT_SIZE = (STOP_CODE * 0.92).roundToInt()
fun compress(input: ByteArray, bitOutput: BitOutput) /**
= compress(input.asUByteArray(), bitOutput) * Compress a byte array using LZW algorithm writing the result to [bitOutput]
*/
fun compress(input: ByteArray, bitOutput: BitOutput) = compress(input.asUByteArray(), bitOutput)
/** /**
* Compresses the input string using LZW algorithm * Compresses the input string using LZW algorithm
@@ -139,7 +143,42 @@ object LZW {
} }
} }
private operator fun ByteChunk.plus(byte: UByte): ByteChunk { private operator fun ByteChunk.plus(byte: UByte): ByteChunk {
return ByteChunk(data + byte) return ByteChunk(data + byte)
} }
/**
* Safely compress binary byte data using the LZW algorithm. This can use up to one byte more space than
* with [LZW.compress] and [BitOutput] but you often need byte array operations. Source data size is also
* encoded to prevent file-bomb-like attacks. Note that content protection is not included (we assume
* LZW is robust).
* @param source the data to compress
*/
fun lzwCompress(source: UByteArray): UByteArray {
val out = MemoryBitOutput()
out.packUnsigned(source.size.toULong())
LZW.compress(source, out)
return out.toBitArray().asUByteArray()
}
/**
* Safely decompress data compressed with [lzwCompress], checking size. Contect checks are not implemented
* here as we assume LZW is robust.
* @param packed the compressed data
* @throws DecompressionException if something goes wrong, like size mismatch or bad compressed data
*/
fun lzwDecompress(packed: UByteArray): UByteArray {
val inp = MemoryBitInput(packed, 8)
val size = inp.unpackUnsigned()
return LZW.decompress(inp, size.toInt())
}
/**
* Compress a text using utf-8 encoding and [lzwCompress]
*/
fun lzwCompressUtf8(text: String) = lzwCompress(text.encodeToByteArray().toUByteArray())
/**
* Decompress a text compressed with [lzwCompressUtf8]. See also [lzwDecompress]
*/
fun lzwDecompressUtf8(packed: UByteArray): String = lzwDecompress(packed).asByteArray().decodeToString()
@@ -56,7 +56,7 @@ object ObjLynonClass : ObjClass("Lynon") {
@Suppress("unused") @Suppress("unused")
suspend fun lynonEncodeAny(scope: Scope, value: Obj): UByteArray = suspend fun lynonEncodeAny(scope: Scope, value: Obj): UByteArray =
(ObjLynonClass.encodeAny(scope, value)) (ObjLynonClass.encodeAny(scope, value))
.bitArray.asUbyteArray() .bitArray.asUByteArray()
@Suppress("unused") @Suppress("unused")
suspend fun lynonDecodeAny(scope: Scope, encoded: UByteArray): Obj = suspend fun lynonDecodeAny(scope: Scope, encoded: UByteArray): Obj =
+69
View File
@@ -107,6 +107,26 @@ class OOTest {
""".trimIndent()) """.trimIndent())
} }
@Test
fun testDynamicIndexAccess() = runTest {
eval("""
val store = Map()
val accessor = dynamic {
get { name ->
store[name]
}
set { name, value ->
store[name] = value
}
}
assertEquals(null, accessor["foo"])
assertEquals(null, accessor.foo)
accessor["foo"] = "bar"
assertEquals("bar", accessor["foo"])
assertEquals("bar", accessor.foo)
""".trimIndent())
}
@Test @Test
fun testMultilineConstructor() = runTest { fun testMultilineConstructor() = runTest {
eval(""" eval("""
@@ -117,4 +137,53 @@ class OOTest {
assertEquals(Point(1,2), Point(1,2) ) assertEquals(Point(1,2), Point(1,2) )
""".trimIndent()) """.trimIndent())
} }
@Test
fun testDynamicClass() = runTest {
eval("""
fun getContract(contractName) {
dynamic {
get { name ->
println("Call: %s.%s"(contractName,name))
}
}
}
getContract("foo").bar
""")
}
@Test
fun testDynamicClassReturn2() = runTest {
// todo: should work without extra parenthesis
// see below
eval("""
fun getContract(contractName) {
println("1")
dynamic {
get { name ->
println("innrer %s.%s"(contractName,name))
{ args... ->
if( name == "bar" ) args.sum() else null
}
}
}
}
val cc = dynamic {
get { name ->
println("Call cc %s"(name))
getContract(name)
}
}
val x = cc.foo.bar
println(x)
x(1,2,3)
assertEquals(6, x(1,2,3))
// v HERE v
assertEquals(15, cc.foo.bar(10,2,3))
""")
}
} }
+47 -19
View File
@@ -3170,6 +3170,34 @@ class ScriptTest {
) )
} }
@Test
fun testExceptionSerializationPlain() = runTest {
eval(
"""
import lyng.serialization
val x = [1,2,3]
assertEquals( "[1,2,3]", x.toString() )
try {
throw "test"
}
catch (e) {
println(e.stackTrace)
e.printStackTrace()
val coded = Lynon.encode(e)
val decoded = Lynon.decode(coded)
assertEquals( e::class, decoded::class )
assertEquals( e.stackTrace, decoded.stackTrace )
assertEquals( e.message, decoded.message )
println("-------------------- e")
println(e.toString())
println("-------------------- dee")
println(decoded.toString())
assertEquals( e.toString(), decoded.toString() )
}
""".trimIndent()
)
}
@Test @Test
fun testThisInClosure() = runTest { fun testThisInClosure() = runTest {
eval( eval(
@@ -3287,25 +3315,25 @@ class ScriptTest {
// @Test // @Test
fun testMinimumOptimization() = runTest { // fun testMinimumOptimization() = runTest {
val x = Scope().eval( // val x = Scope().eval(
""" // """
fun naiveCountHappyNumbers() { // fun naiveCountHappyNumbers() {
var count = 0 // var count = 0
for( n1 in 0..9 ) // for( n1 in 0..9 )
for( n2 in 0..9 ) // for( n2 in 0..9 )
for( n3 in 0..9 ) // for( n3 in 0..9 )
for( n4 in 0..9 ) // for( n4 in 0..9 )
for( n5 in 0..9 ) // for( n5 in 0..9 )
for( n6 in 0..9 ) // for( n6 in 0..9 )
if( n1 + n2 + n3 == n4 + n5 + n6 ) count++ // if( n1 + n2 + n3 == n4 + n5 + n6 ) count++
count // count
} // }
naiveCountHappyNumbers() // naiveCountHappyNumbers()
""".trimIndent() // """.trimIndent()
).toInt() // ).toInt()
assertEquals(55252, x) // assertEquals(55252, x)
} // }
@Test @Test
fun testRegex1() = runTest { fun testRegex1() = runTest {
+7
View File
@@ -397,6 +397,13 @@ class LynonTests {
assertEquals(original, decompressed) assertEquals(original, decompressed)
} }
@Test
fun testCompressed() {
val compressed = lzwCompressUtf8(original)
println("${compressed.size/original.encodeToByteArray().size.toDouble()} compression ratio")
assertEquals(original, lzwDecompressUtf8(compressed))
}
@Test @Test
fun testTinyBits() { fun testTinyBits() {
var a0 = TinyBits() var a0 = TinyBits()
+32
View File
@@ -22,7 +22,10 @@ import net.sergeych.lyng.Source
import net.sergeych.lyng.eval import net.sergeych.lyng.eval
import net.sergeych.lyng.pacman.InlineSourcesImportProvider import net.sergeych.lyng.pacman.InlineSourcesImportProvider
import net.sergeych.lyng.toSource import net.sergeych.lyng.toSource
import net.sergeych.lynon.BitArray
import net.sergeych.lynon.BitList
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertNotEquals
class OtherTests { class OtherTests {
@Test @Test
@@ -67,4 +70,33 @@ class OtherTests {
Unit Unit
} }
@Test
fun testBitArrayEqAndHash() {
val b1 = BitArray.ofBits(1, 0, 1, 1)
val b11 = BitArray.ofBits(1, 0, 1, 1)
val b2 = BitArray.ofBits(1, 1, 1, 1)
val b3 = BitArray.ofBits(1, 0, 1, 1, 0)
assert( b3 > b1 )
assert( b2 > b1)
assert( b11.compareTo(b1) == 0)
assertEquals(b1, b11)
assertNotEquals(b1, b2)
assertNotEquals(b1, b3)
assert( b1.hashCode() == b11.hashCode() )
val x = mutableMapOf<BitList,String>()
x[b1] = "wrong"
x[b11] = "OK"
x[b2] = "other"
assertEquals("OK", x[b11])
assertEquals("OK", x[b1])
assertEquals("other", x[b2])
}
} }