ref #49 basic regex support (but not in when yet)
This commit is contained in:
		
							parent
							
								
									2743511b62
								
							
						
					
					
						commit
						ead2f7168e
					
				@ -1184,6 +1184,30 @@ See also [math operations](math.md)
 | 
			
		||||
 | 
			
		||||
The type for the character objects is `Char`.
 | 
			
		||||
 | 
			
		||||
### String literal escapes
 | 
			
		||||
 | 
			
		||||
| escape | ASCII value           |
 | 
			
		||||
|--------|-----------------------|
 | 
			
		||||
| \n     | 0x10, newline         |
 | 
			
		||||
| \r     | 0x13, carriage return |
 | 
			
		||||
| \t     | 0x07, tabulation      |
 | 
			
		||||
| \\     | \ slash character     |
 | 
			
		||||
| \"     | " double quote        |
 | 
			
		||||
 | 
			
		||||
Other `\c` combinations, where c is any char except mentioned above, are left intact, e.g.:
 | 
			
		||||
 | 
			
		||||
    val s = "\a"
 | 
			
		||||
    assert(s[0] == '\')
 | 
			
		||||
    assert(s[1] == 'a')
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
same as:
 | 
			
		||||
 | 
			
		||||
    val s = "\\a"
 | 
			
		||||
    assert(s[0] == '\')
 | 
			
		||||
    assert(s[1] == 'a')
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
### Char literal escapes
 | 
			
		||||
 | 
			
		||||
Are the same as in string literals with little difference:
 | 
			
		||||
@ -1191,6 +1215,7 @@ Are the same as in string literals with little difference:
 | 
			
		||||
| escape | ASCII value       |
 | 
			
		||||
|--------|-------------------|
 | 
			
		||||
| \n     | 0x10, newline     |
 | 
			
		||||
| \r     | 0x13, carriage return |
 | 
			
		||||
| \t     | 0x07, tabulation  |
 | 
			
		||||
| \\     | \ slash character |
 | 
			
		||||
| \'     | ' apostrophe      |
 | 
			
		||||
 | 
			
		||||
@ -1376,9 +1376,16 @@ class Compiler(
 | 
			
		||||
                if (sourceObj is ObjRange && sourceObj.isIntRange) {
 | 
			
		||||
                    loopIntRange(
 | 
			
		||||
                        forContext,
 | 
			
		||||
                        sourceObj.start!!.toInt(),
 | 
			
		||||
                        if (sourceObj.isEndInclusive) sourceObj.end!!.toInt() + 1 else sourceObj.end!!.toInt(),
 | 
			
		||||
                        loopSO, body, elseStatement, label, canBreak
 | 
			
		||||
                        sourceObj.start!!.toLong(),
 | 
			
		||||
                        if (sourceObj.isEndInclusive)
 | 
			
		||||
                            sourceObj.end!!.toLong() + 1
 | 
			
		||||
                        else
 | 
			
		||||
                            sourceObj.end!!.toLong(),
 | 
			
		||||
                        loopSO,
 | 
			
		||||
                        body,
 | 
			
		||||
                        elseStatement,
 | 
			
		||||
                        label,
 | 
			
		||||
                        canBreak
 | 
			
		||||
                    )
 | 
			
		||||
                } else if (sourceObj.isInstanceOf(ObjIterable)) {
 | 
			
		||||
                    loopIterable(forContext, sourceObj, loopSO, body, elseStatement, label, canBreak)
 | 
			
		||||
@ -1439,7 +1446,7 @@ class Compiler(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private suspend fun loopIntRange(
 | 
			
		||||
        forScope: Scope, start: Int, end: Int, loopVar: ObjRecord,
 | 
			
		||||
        forScope: Scope, start: Long, end: Long, loopVar: ObjRecord,
 | 
			
		||||
        body: Statement, elseStatement: Statement?, label: String?, catchBreak: Boolean
 | 
			
		||||
    ): Obj {
 | 
			
		||||
        var result: Obj = ObjVoid
 | 
			
		||||
@ -1447,7 +1454,7 @@ class Compiler(
 | 
			
		||||
        loopVar.value = iVar
 | 
			
		||||
        if (catchBreak) {
 | 
			
		||||
            for (i in start..<end) {
 | 
			
		||||
                iVar.value = i.toLong()
 | 
			
		||||
                iVar.value = i//.toLong()
 | 
			
		||||
                try {
 | 
			
		||||
                    result = body.execute(forScope)
 | 
			
		||||
                } catch (lbe: LoopBreakContinueException) {
 | 
			
		||||
@ -1459,7 +1466,7 @@ class Compiler(
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            for (i in start.toLong()..<end.toLong()) {
 | 
			
		||||
            for (i in start ..< end) {
 | 
			
		||||
                iVar.value = i
 | 
			
		||||
                result = body.execute(forScope)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -437,7 +437,11 @@ private class Parser(fromPos: Pos) {
 | 
			
		||||
                        'r' -> {sb.append('\r'); pos.advance()}
 | 
			
		||||
                        't' -> {sb.append('\t'); pos.advance()}
 | 
			
		||||
                        '"' -> {sb.append('"'); pos.advance()}
 | 
			
		||||
                        else -> sb.append('\\').append(currentChar)
 | 
			
		||||
                        '\\' -> {sb.append('\\'); pos.advance()}
 | 
			
		||||
                        else -> {
 | 
			
		||||
                            sb.append('\\').append(currentChar)
 | 
			
		||||
                            pos.advance()
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -136,11 +136,6 @@ open class Scope(
 | 
			
		||||
                ?: thisObj.objClass
 | 
			
		||||
                    .getInstanceMemberOrNull(name)
 | 
			
		||||
                    )
 | 
			
		||||
//                ?.also {
 | 
			
		||||
//                        if( name == "predicate") {
 | 
			
		||||
//                            println("got predicate $it")
 | 
			
		||||
//                        }
 | 
			
		||||
//                }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    fun copy(pos: Pos, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Scope =
 | 
			
		||||
 | 
			
		||||
@ -241,6 +241,8 @@ class Script(
 | 
			
		||||
            addConst("CompletableDeferred", ObjCompletableDeferred.type)
 | 
			
		||||
            addConst("Mutex", ObjMutex.type)
 | 
			
		||||
 | 
			
		||||
            addConst("Regex", ObjRegex.type)
 | 
			
		||||
 | 
			
		||||
            addFn("launch") {
 | 
			
		||||
                val callable = requireOnlyArg<Statement>()
 | 
			
		||||
                ObjDeferred(globalDefer {
 | 
			
		||||
 | 
			
		||||
@ -21,15 +21,11 @@ import kotlinx.coroutines.sync.Mutex
 | 
			
		||||
import kotlinx.coroutines.sync.withLock
 | 
			
		||||
import kotlinx.serialization.SerialName
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
import net.sergeych.bintools.encodeToHex
 | 
			
		||||
import net.sergeych.lyng.*
 | 
			
		||||
import net.sergeych.lynon.LynonDecoder
 | 
			
		||||
import net.sergeych.lynon.LynonEncoder
 | 
			
		||||
import net.sergeych.lynon.LynonType
 | 
			
		||||
import net.sergeych.mptools.CachedExpression
 | 
			
		||||
import net.sergeych.synctools.ProtectedOp
 | 
			
		||||
import net.sergeych.synctools.withLock
 | 
			
		||||
import kotlin.contracts.ExperimentalContracts
 | 
			
		||||
 | 
			
		||||
open class Obj {
 | 
			
		||||
 | 
			
		||||
@ -500,195 +496,4 @@ data class ObjNamespace(val name: String) : Obj() {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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
 | 
			
		||||
 * exception with stack trace; the scope of the de-serialized exception is not valid
 | 
			
		||||
 * for stack unwinding.
 | 
			
		||||
 */
 | 
			
		||||
open class ObjException(
 | 
			
		||||
    val exceptionClass: ExceptionClass,
 | 
			
		||||
    val scope: Scope,
 | 
			
		||||
    val message: ObjString,
 | 
			
		||||
    @Suppress("unused") val extraData: Obj = ObjNull,
 | 
			
		||||
    val useStackTrace: ObjList? = null
 | 
			
		||||
) : Obj() {
 | 
			
		||||
    constructor(name: String, scope: Scope, message: String) : this(
 | 
			
		||||
        getOrCreateExceptionClass(name),
 | 
			
		||||
        scope,
 | 
			
		||||
        ObjString(message)
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    private val cachedStackTrace = CachedExpression(initialValue = useStackTrace)
 | 
			
		||||
 | 
			
		||||
    suspend fun getStackTrace(): ObjList {
 | 
			
		||||
        return cachedStackTrace.get {
 | 
			
		||||
            val result = ObjList()
 | 
			
		||||
            val cls = scope.get("StackTraceEntry")!!.value as ObjClass
 | 
			
		||||
            var s: Scope? = scope
 | 
			
		||||
            var lastPos: Pos? = null
 | 
			
		||||
            while (s != null) {
 | 
			
		||||
                val pos = s.pos
 | 
			
		||||
                if (pos != lastPos && !pos.currentLine.isEmpty()) {
 | 
			
		||||
                    result.list += cls.callWithArgs(
 | 
			
		||||
                        scope,
 | 
			
		||||
                        pos.source.objSourceName,
 | 
			
		||||
                        ObjInt(pos.line.toLong()),
 | 
			
		||||
                        ObjInt(pos.column.toLong()),
 | 
			
		||||
                        ObjString(pos.currentLine)
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
                s = s.parent
 | 
			
		||||
                lastPos = pos
 | 
			
		||||
            }
 | 
			
		||||
            result
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constructor(scope: Scope, message: String) : this(Root, scope, ObjString(message))
 | 
			
		||||
 | 
			
		||||
    fun raise(): Nothing {
 | 
			
		||||
        throw ExecutionError(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override val objClass: ObjClass = exceptionClass
 | 
			
		||||
 | 
			
		||||
    override suspend fun toString(scope: Scope,calledFromLyng: Boolean): ObjString {
 | 
			
		||||
        val at = getStackTrace().list.firstOrNull()?.toString(scope)
 | 
			
		||||
            ?: ObjString("(unknown)")
 | 
			
		||||
        return ObjString("${objClass.className}: $message at $at")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
 | 
			
		||||
        encoder.encodeAny(scope, exceptionClass.classNameObj)
 | 
			
		||||
        encoder.encodeAny(scope, message)
 | 
			
		||||
        encoder.encodeAny(scope, extraData)
 | 
			
		||||
        encoder.encodeAny(scope, getStackTrace())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
 | 
			
		||||
        class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) {
 | 
			
		||||
            override suspend fun callOn(scope: Scope): Obj {
 | 
			
		||||
                val message = scope.args.getOrNull(0)?.toString(scope) ?: ObjString(name)
 | 
			
		||||
                return ObjException(this, scope, message)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            override fun toString(): String = "ExceptionClass[$name]@${hashCode().encodeToHex()}"
 | 
			
		||||
 | 
			
		||||
            override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
 | 
			
		||||
                return try {
 | 
			
		||||
                    val lyngClass = decoder.decodeAnyAs<ObjString>(scope).value.let {
 | 
			
		||||
                        ((scope[it] ?: scope.raiseIllegalArgument("Unknown exception class: $it"))
 | 
			
		||||
                            .value as? ExceptionClass)
 | 
			
		||||
                            ?: scope.raiseIllegalArgument("Not an exception class: $it")
 | 
			
		||||
                    }
 | 
			
		||||
                    ObjException(
 | 
			
		||||
                        lyngClass,
 | 
			
		||||
                        scope,
 | 
			
		||||
                        decoder.decodeAnyAs<ObjString>(scope),
 | 
			
		||||
                        decoder.decodeAny(scope),
 | 
			
		||||
                        decoder.decodeAnyAs<ObjList>(scope)
 | 
			
		||||
                    )
 | 
			
		||||
                } catch (e: ScriptError) {
 | 
			
		||||
                    throw e
 | 
			
		||||
                } catch (e: Exception) {
 | 
			
		||||
                    e.printStackTrace()
 | 
			
		||||
                    scope.raiseIllegalArgument("Failed to deserialize exception: ${e.message}")
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val Root = ExceptionClass("Throwable").apply {
 | 
			
		||||
            addConst("message", statement {
 | 
			
		||||
                (thisObj as ObjException).message.toObj()
 | 
			
		||||
            })
 | 
			
		||||
            addFn("stackTrace") {
 | 
			
		||||
                (thisObj as ObjException).getStackTrace()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private val op = ProtectedOp()
 | 
			
		||||
        private val existingErrorClasses = mutableMapOf<String, ExceptionClass>()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        @OptIn(ExperimentalContracts::class)
 | 
			
		||||
        protected fun getOrCreateExceptionClass(name: String): ExceptionClass {
 | 
			
		||||
            return op.withLock {
 | 
			
		||||
                existingErrorClasses.getOrPut(name) {
 | 
			
		||||
                    ExceptionClass(name, Root)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Get [ObjClass] for error class by name if exists.
 | 
			
		||||
         */
 | 
			
		||||
        @OptIn(ExperimentalContracts::class)
 | 
			
		||||
        fun getErrorClass(name: String): ObjClass? = op.withLock {
 | 
			
		||||
            existingErrorClasses[name]
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun addExceptionsToContext(scope: Scope) {
 | 
			
		||||
            scope.addConst("Exception", Root)
 | 
			
		||||
            existingErrorClasses["Exception"] = Root
 | 
			
		||||
            for (name in listOf(
 | 
			
		||||
                "NullReferenceException",
 | 
			
		||||
                "AssertionFailedException",
 | 
			
		||||
                "ClassCastException",
 | 
			
		||||
                "IndexOutOfBoundsException",
 | 
			
		||||
                "IllegalArgumentException",
 | 
			
		||||
                "NoSuchElementException",
 | 
			
		||||
                "IllegalAssignmentException",
 | 
			
		||||
                "SymbolNotDefinedException",
 | 
			
		||||
                "IterationEndException",
 | 
			
		||||
                "AccessException",
 | 
			
		||||
                "UnknownException",
 | 
			
		||||
                "NotFoundException"
 | 
			
		||||
            )) {
 | 
			
		||||
                scope.addConst(name, getOrCreateExceptionClass(name))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ObjNullReferenceException(scope: Scope) : ObjException("NullReferenceException", scope, "object is null")
 | 
			
		||||
 | 
			
		||||
class ObjAssertionFailedException(scope: Scope, message: String) :
 | 
			
		||||
    ObjException("AssertionFailedException", scope, message)
 | 
			
		||||
 | 
			
		||||
class ObjClassCastException(scope: Scope, message: String) : ObjException("ClassCastException", scope, message)
 | 
			
		||||
class ObjIndexOutOfBoundsException(scope: Scope, message: String = "index out of bounds") :
 | 
			
		||||
    ObjException("IndexOutOfBoundsException", scope, message)
 | 
			
		||||
 | 
			
		||||
class ObjIllegalArgumentException(scope: Scope, message: String = "illegal argument") :
 | 
			
		||||
    ObjException("IllegalArgumentException", scope, message)
 | 
			
		||||
 | 
			
		||||
class ObjIllegalStateException(scope: Scope, message: String = "illegal state") :
 | 
			
		||||
    ObjException("IllegalStateException", scope, message)
 | 
			
		||||
 | 
			
		||||
@Suppress("unused")
 | 
			
		||||
class ObjNoSuchElementException(scope: Scope, message: String = "no such element") :
 | 
			
		||||
    ObjException("IllegalArgumentException", scope, message)
 | 
			
		||||
 | 
			
		||||
class ObjIllegalAssignmentException(scope: Scope, message: String = "illegal assignment") :
 | 
			
		||||
    ObjException("NoSuchElementException", scope, message)
 | 
			
		||||
 | 
			
		||||
class ObjSymbolNotDefinedException(scope: Scope, message: String = "symbol is not defined") :
 | 
			
		||||
    ObjException("SymbolNotDefinedException", scope, message)
 | 
			
		||||
 | 
			
		||||
class ObjIterationFinishedException(scope: Scope) :
 | 
			
		||||
    ObjException("IterationEndException", scope, "iteration finished")
 | 
			
		||||
 | 
			
		||||
class ObjAccessException(scope: Scope, message: String = "access not allowed error") :
 | 
			
		||||
    ObjException("AccessException", scope, message)
 | 
			
		||||
 | 
			
		||||
class ObjUnknownException(scope: Scope, message: String = "access not allowed error") :
 | 
			
		||||
    ObjException("UnknownException", scope, message)
 | 
			
		||||
 | 
			
		||||
class ObjIllegalOperationException(scope: Scope, message: String = "Operation is illegal") :
 | 
			
		||||
    ObjException("IllegalOperationException", scope, message)
 | 
			
		||||
 | 
			
		||||
class ObjNotFoundException(scope: Scope, message: String = "not found") :
 | 
			
		||||
    ObjException("NotFoundException", scope, message)
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,221 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package net.sergeych.lyng.obj
 | 
			
		||||
 | 
			
		||||
import net.sergeych.bintools.encodeToHex
 | 
			
		||||
import net.sergeych.lyng.*
 | 
			
		||||
import net.sergeych.lynon.LynonDecoder
 | 
			
		||||
import net.sergeych.lynon.LynonEncoder
 | 
			
		||||
import net.sergeych.lynon.LynonType
 | 
			
		||||
import net.sergeych.mptools.CachedExpression
 | 
			
		||||
import net.sergeych.synctools.ProtectedOp
 | 
			
		||||
import net.sergeych.synctools.withLock
 | 
			
		||||
import kotlin.contracts.ExperimentalContracts
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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
 | 
			
		||||
 * exception with stack trace; the scope of the de-serialized exception is not valid
 | 
			
		||||
 * for stack unwinding.
 | 
			
		||||
 */
 | 
			
		||||
open class ObjException(
 | 
			
		||||
    val exceptionClass: ExceptionClass,
 | 
			
		||||
    val scope: Scope,
 | 
			
		||||
    val message: ObjString,
 | 
			
		||||
    @Suppress("unused") val extraData: Obj = ObjNull,
 | 
			
		||||
    val useStackTrace: ObjList? = null
 | 
			
		||||
) : Obj() {
 | 
			
		||||
    constructor(name: String, scope: Scope, message: String) : this(
 | 
			
		||||
        getOrCreateExceptionClass(name),
 | 
			
		||||
        scope,
 | 
			
		||||
        ObjString(message)
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    private val cachedStackTrace = CachedExpression(initialValue = useStackTrace)
 | 
			
		||||
 | 
			
		||||
    suspend fun getStackTrace(): ObjList {
 | 
			
		||||
        return cachedStackTrace.get {
 | 
			
		||||
            val result = ObjList()
 | 
			
		||||
            val cls = scope.get("StackTraceEntry")!!.value as ObjClass
 | 
			
		||||
            var s: Scope? = scope
 | 
			
		||||
            var lastPos: Pos? = null
 | 
			
		||||
            while (s != null) {
 | 
			
		||||
                val pos = s.pos
 | 
			
		||||
                if (pos != lastPos && !pos.currentLine.isEmpty()) {
 | 
			
		||||
                    result.list += cls.callWithArgs(
 | 
			
		||||
                        scope,
 | 
			
		||||
                        pos.source.objSourceName,
 | 
			
		||||
                        ObjInt(pos.line.toLong()),
 | 
			
		||||
                        ObjInt(pos.column.toLong()),
 | 
			
		||||
                        ObjString(pos.currentLine)
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
                s = s.parent
 | 
			
		||||
                lastPos = pos
 | 
			
		||||
            }
 | 
			
		||||
            result
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constructor(scope: Scope, message: String) : this(Root, scope, ObjString(message))
 | 
			
		||||
 | 
			
		||||
    fun raise(): Nothing {
 | 
			
		||||
        throw ExecutionError(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override val objClass: ObjClass = exceptionClass
 | 
			
		||||
 | 
			
		||||
    override suspend fun toString(scope: Scope, calledFromLyng: Boolean): ObjString {
 | 
			
		||||
        val at = getStackTrace().list.firstOrNull()?.toString(scope)
 | 
			
		||||
            ?: ObjString("(unknown)")
 | 
			
		||||
        return ObjString("${objClass.className}: $message at $at")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
 | 
			
		||||
        encoder.encodeAny(scope, exceptionClass.classNameObj)
 | 
			
		||||
        encoder.encodeAny(scope, message)
 | 
			
		||||
        encoder.encodeAny(scope, extraData)
 | 
			
		||||
        encoder.encodeAny(scope, getStackTrace())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
 | 
			
		||||
        class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) {
 | 
			
		||||
            override suspend fun callOn(scope: Scope): Obj {
 | 
			
		||||
                val message = scope.args.getOrNull(0)?.toString(scope) ?: ObjString(name)
 | 
			
		||||
                return ObjException(this, scope, message)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            override fun toString(): String = "ExceptionClass[$name]@${hashCode().encodeToHex()}"
 | 
			
		||||
 | 
			
		||||
            override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
 | 
			
		||||
                return try {
 | 
			
		||||
                    val lyngClass = decoder.decodeAnyAs<ObjString>(scope).value.let {
 | 
			
		||||
                        ((scope[it] ?: scope.raiseIllegalArgument("Unknown exception class: $it"))
 | 
			
		||||
                            .value as? ExceptionClass)
 | 
			
		||||
                            ?: scope.raiseIllegalArgument("Not an exception class: $it")
 | 
			
		||||
                    }
 | 
			
		||||
                    ObjException(
 | 
			
		||||
                        lyngClass,
 | 
			
		||||
                        scope,
 | 
			
		||||
                        decoder.decodeAnyAs<ObjString>(scope),
 | 
			
		||||
                        decoder.decodeAny(scope),
 | 
			
		||||
                        decoder.decodeAnyAs<ObjList>(scope)
 | 
			
		||||
                    )
 | 
			
		||||
                } catch (e: ScriptError) {
 | 
			
		||||
                    throw e
 | 
			
		||||
                } catch (e: Exception) {
 | 
			
		||||
                    e.printStackTrace()
 | 
			
		||||
                    scope.raiseIllegalArgument("Failed to deserialize exception: ${e.message}")
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val Root = ExceptionClass("Throwable").apply {
 | 
			
		||||
            addConst("message", statement {
 | 
			
		||||
                (thisObj as ObjException).message.toObj()
 | 
			
		||||
            })
 | 
			
		||||
            addFn("stackTrace") {
 | 
			
		||||
                (thisObj as ObjException).getStackTrace()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private val op = ProtectedOp()
 | 
			
		||||
        private val existingErrorClasses = mutableMapOf<String, ExceptionClass>()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        @OptIn(ExperimentalContracts::class)
 | 
			
		||||
        protected fun getOrCreateExceptionClass(name: String): ExceptionClass {
 | 
			
		||||
            return op.withLock {
 | 
			
		||||
                existingErrorClasses.getOrPut(name) {
 | 
			
		||||
                    ExceptionClass(name, Root)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Get [ObjClass] for error class by name if exists.
 | 
			
		||||
         */
 | 
			
		||||
        @OptIn(ExperimentalContracts::class)
 | 
			
		||||
        fun getErrorClass(name: String): ObjClass? = op.withLock {
 | 
			
		||||
            existingErrorClasses[name]
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun addExceptionsToContext(scope: Scope) {
 | 
			
		||||
            scope.addConst("Exception", Root)
 | 
			
		||||
            existingErrorClasses["Exception"] = Root
 | 
			
		||||
            for (name in listOf(
 | 
			
		||||
                "NullReferenceException",
 | 
			
		||||
                "AssertionFailedException",
 | 
			
		||||
                "ClassCastException",
 | 
			
		||||
                "IndexOutOfBoundsException",
 | 
			
		||||
                "IllegalArgumentException",
 | 
			
		||||
                "NoSuchElementException",
 | 
			
		||||
                "IllegalAssignmentException",
 | 
			
		||||
                "SymbolNotDefinedException",
 | 
			
		||||
                "IterationEndException",
 | 
			
		||||
                "AccessException",
 | 
			
		||||
                "UnknownException",
 | 
			
		||||
                "NotFoundException"
 | 
			
		||||
            )) {
 | 
			
		||||
                scope.addConst(name, getOrCreateExceptionClass(name))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ObjNullReferenceException(scope: Scope) : ObjException("NullReferenceException", scope, "object is null")
 | 
			
		||||
 | 
			
		||||
class ObjAssertionFailedException(scope: Scope, message: String) :
 | 
			
		||||
    ObjException("AssertionFailedException", scope, message)
 | 
			
		||||
 | 
			
		||||
class ObjClassCastException(scope: Scope, message: String) : ObjException("ClassCastException", scope, message)
 | 
			
		||||
class ObjIndexOutOfBoundsException(scope: Scope, message: String = "index out of bounds") :
 | 
			
		||||
    ObjException("IndexOutOfBoundsException", scope, message)
 | 
			
		||||
 | 
			
		||||
class ObjIllegalArgumentException(scope: Scope, message: String = "illegal argument") :
 | 
			
		||||
    ObjException("IllegalArgumentException", scope, message)
 | 
			
		||||
 | 
			
		||||
class ObjIllegalStateException(scope: Scope, message: String = "illegal state") :
 | 
			
		||||
    ObjException("IllegalStateException", scope, message)
 | 
			
		||||
 | 
			
		||||
@Suppress("unused")
 | 
			
		||||
class ObjNoSuchElementException(scope: Scope, message: String = "no such element") :
 | 
			
		||||
    ObjException("IllegalArgumentException", scope, message)
 | 
			
		||||
 | 
			
		||||
class ObjIllegalAssignmentException(scope: Scope, message: String = "illegal assignment") :
 | 
			
		||||
    ObjException("NoSuchElementException", scope, message)
 | 
			
		||||
 | 
			
		||||
class ObjSymbolNotDefinedException(scope: Scope, message: String = "symbol is not defined") :
 | 
			
		||||
    ObjException("SymbolNotDefinedException", scope, message)
 | 
			
		||||
 | 
			
		||||
class ObjIterationFinishedException(scope: Scope) :
 | 
			
		||||
    ObjException("IterationEndException", scope, "iteration finished")
 | 
			
		||||
 | 
			
		||||
class ObjAccessException(scope: Scope, message: String = "access not allowed error") :
 | 
			
		||||
    ObjException("AccessException", scope, message)
 | 
			
		||||
 | 
			
		||||
class ObjUnknownException(scope: Scope, message: String = "access not allowed error") :
 | 
			
		||||
    ObjException("UnknownException", scope, message)
 | 
			
		||||
 | 
			
		||||
class ObjIllegalOperationException(scope: Scope, message: String = "Operation is illegal") :
 | 
			
		||||
    ObjException("IllegalOperationException", scope, message)
 | 
			
		||||
 | 
			
		||||
class ObjNotFoundException(scope: Scope, message: String = "not found") :
 | 
			
		||||
    ObjException("NotFoundException", scope, message)
 | 
			
		||||
@ -0,0 +1,90 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package net.sergeych.lyng.obj
 | 
			
		||||
 | 
			
		||||
import net.sergeych.lyng.Scope
 | 
			
		||||
 | 
			
		||||
class ObjRegex(val regex: Regex) : Obj() {
 | 
			
		||||
    override val objClass = type
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type by lazy {
 | 
			
		||||
            object : ObjClass("Regex") {
 | 
			
		||||
                override suspend fun callOn(scope: Scope): Obj {
 | 
			
		||||
                    println(scope.requireOnlyArg<ObjString>().value)
 | 
			
		||||
                    return ObjRegex(
 | 
			
		||||
                        scope.requireOnlyArg<ObjString>().value.toRegex()
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }.apply {
 | 
			
		||||
                addFn("matches") {
 | 
			
		||||
                    ObjBool(args.firstAndOnly().toString().matches(thisAs<ObjRegex>().regex))
 | 
			
		||||
                }
 | 
			
		||||
                addFn("find") {
 | 
			
		||||
                    val s = requireOnlyArg<ObjString>().value
 | 
			
		||||
                    thisAs<ObjRegex>().regex.find(s)?.let { ObjRegexMatch(it) } ?: ObjNull
 | 
			
		||||
                }
 | 
			
		||||
                addFn("findAll") {
 | 
			
		||||
                    val s = requireOnlyArg<ObjString>().value
 | 
			
		||||
                    ObjList(thisAs<ObjRegex>().regex.findAll(s).map { ObjRegexMatch(it) }.toMutableList())
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ObjRegexMatch(val match: MatchResult) : Obj() {
 | 
			
		||||
    override val objClass = type
 | 
			
		||||
 | 
			
		||||
    val objGroups: ObjList by lazy {
 | 
			
		||||
        ObjList(
 | 
			
		||||
            match.groups.map { it?.let { ObjString(it.value) } ?: ObjNull }.toMutableList()
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val objValue by lazy { ObjString(match.value) }
 | 
			
		||||
 | 
			
		||||
    val objRange: ObjRange by lazy {
 | 
			
		||||
        val r = match.range
 | 
			
		||||
        ObjRange(
 | 
			
		||||
            ObjInt(r.first.toLong()),
 | 
			
		||||
            ObjInt(r.last.toLong()),
 | 
			
		||||
            false
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type by lazy {
 | 
			
		||||
            object : ObjClass("RegexMatch") {
 | 
			
		||||
                override suspend fun callOn(scope: Scope): Obj {
 | 
			
		||||
                    scope.raiseError("RegexMatch can't be constructed directly")
 | 
			
		||||
                }
 | 
			
		||||
            }.apply {
 | 
			
		||||
                addFn("groups") {
 | 
			
		||||
                    thisAs<ObjRegexMatch>().objGroups
 | 
			
		||||
                }
 | 
			
		||||
                addFn("value") {
 | 
			
		||||
                    thisAs<ObjRegexMatch>().objValue
 | 
			
		||||
                }
 | 
			
		||||
                addFn("range") {
 | 
			
		||||
                    thisAs<ObjRegexMatch>().objRange
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -165,6 +165,8 @@ fun Exception.printStackTrace() {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun String.re() { Regex(this) }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
""".trimIndent()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -3242,7 +3242,45 @@ class ScriptTest {
 | 
			
		||||
                result.insertAt(-i-1, x)
 | 
			
		||||
            }
 | 
			
		||||
            assertEquals( src.sorted(), result )
 | 
			
		||||
            """.trimIndent())
 | 
			
		||||
            """.trimIndent()
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//    @Test
 | 
			
		||||
    fun testMinimumOptimization() = runTest {
 | 
			
		||||
        val x = Scope().eval(
 | 
			
		||||
            """
 | 
			
		||||
                fun naiveCountHappyNumbers() {
 | 
			
		||||
                    var count = 0
 | 
			
		||||
                    for( n1 in 0..9 )
 | 
			
		||||
                        for( n2 in 0..9 )
 | 
			
		||||
                            for( n3 in 0..9 )
 | 
			
		||||
                                for( n4 in 0..9 )
 | 
			
		||||
                                    for( n5 in 0..9 )
 | 
			
		||||
                                        for( n6 in 0..9 )
 | 
			
		||||
                                            if( n1 + n2 + n3 == n4 + n5 + n6 ) count++
 | 
			
		||||
                    count
 | 
			
		||||
                }
 | 
			
		||||
                naiveCountHappyNumbers()
 | 
			
		||||
            """.trimIndent()
 | 
			
		||||
        ).toInt()
 | 
			
		||||
        assertEquals(55252, x)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testRegex1() = runTest {
 | 
			
		||||
        eval(
 | 
			
		||||
            """
 | 
			
		||||
            assert( ! "123".re.matches("abs123def") )
 | 
			
		||||
            assert( ".*123.*".re.matches("abs123def") )
 | 
			
		||||
//            assertEquals( "123", "123".re.find("abs123def")?.value )
 | 
			
		||||
//            assertEquals( "123", "[0-9]{3}".re.find("abs123def")?.value )
 | 
			
		||||
            assertEquals( "123", "\d{3}".re.find("abs123def")?.value )
 | 
			
		||||
            assertEquals( "123", "\\d{3}".re.find("abs123def")?.value )
 | 
			
		||||
            assertEquals( [1,2,3], "\d".re.findAll("abs123def").map { it.value.toInt() } )
 | 
			
		||||
            """
 | 
			
		||||
                .trimIndent()
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user