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