Compare commits
12 Commits
fix/scope-
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d8fdce637 | |||
| 5a8881bfd5 | |||
| d487886c8f | |||
| 180471e4cd | |||
| 71a37a2906 | |||
| ab05f83e77 | |||
| 9e11519608 | |||
| a2d26fc777 | |||
| dd1a1544c6 | |||
| fba44622e5 | |||
| 2737aaa14e | |||
| bce88ced43 |
28
.run/Tests in 'lyng.lynglib.jvmTest'.run.xml
Normal file
28
.run/Tests in 'lyng.lynglib.jvmTest'.run.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<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>
|
||||
<ExternalSystemDebugDisabled>false</ExternalSystemDebugDisabled>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>true</RunAsTest>
|
||||
<GradleProfilingDisabled>false</GradleProfilingDisabled>
|
||||
<GradleCoverageDisabled>false</GradleCoverageDisabled>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@ -68,6 +68,20 @@ These, again, does the thing:
|
||||
|
||||
>>> void
|
||||
|
||||
## map and mapNotNull
|
||||
|
||||
Used to transform either the whole iterable stream or also skipping som elements from it:
|
||||
|
||||
val source = [1,2,3,4]
|
||||
// transform every element to string or null:
|
||||
assertEquals(["n1", "n2", null, "n4"], source.map { if( it == 3 ) null else "n"+it } )
|
||||
|
||||
// transform every element to stirng, skipping 3:
|
||||
assertEquals(["n1", "n2", "n4"], source.mapNotNull { if( it == 3 ) null else "n"+it } )
|
||||
|
||||
>>> void
|
||||
|
||||
|
||||
## Instance methods:
|
||||
|
||||
| fun/method | description |
|
||||
|
||||
@ -516,6 +516,23 @@ open class Scope(
|
||||
|
||||
open fun applyClosure(closure: Scope): Scope = ClosureScope(this, closure)
|
||||
|
||||
/**
|
||||
* Resolve and evaluate a qualified identifier exactly as compiled code would.
|
||||
* For input like `A.B.C`, it builds the same ObjRef chain the compiler emits:
|
||||
* `LocalVarRef("A", Pos.builtIn)` followed by `FieldRef` for each segment, then evaluates it.
|
||||
* This mirrors `eval("A.B.C")` resolution semantics without invoking the compiler.
|
||||
*/
|
||||
suspend fun resolveQualifiedIdentifier(qualifiedName: String): Obj {
|
||||
val trimmed = qualifiedName.trim()
|
||||
if (trimmed.isEmpty()) raiseSymbolNotFound("empty identifier")
|
||||
val parts = trimmed.split('.')
|
||||
var ref: ObjRef = LocalVarRef(parts[0], Pos.builtIn)
|
||||
for (i in 1 until parts.size) {
|
||||
ref = FieldRef(ref, parts[i], false)
|
||||
}
|
||||
return ref.evalValue(this)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun new(): Scope =
|
||||
|
||||
@ -155,52 +155,65 @@ class Script(
|
||||
sqrt(args.firstAndOnly().toDouble())
|
||||
)
|
||||
}
|
||||
addFn( "abs" ) {
|
||||
addFn("abs") {
|
||||
val x = args.firstAndOnly()
|
||||
if( x is ObjInt) ObjInt( x.value.absoluteValue ) else ObjReal( x.toDouble().absoluteValue )
|
||||
if (x is ObjInt) ObjInt(x.value.absoluteValue) else ObjReal(x.toDouble().absoluteValue)
|
||||
}
|
||||
|
||||
addVoidFn("assert") {
|
||||
val cond = requiredArg<ObjBool>(0)
|
||||
val message = if( args.size > 1 )
|
||||
val message = if (args.size > 1)
|
||||
": " + (args[1] as Statement).execute(this).toString(this).value
|
||||
else ""
|
||||
if( !cond.value == true )
|
||||
if (!cond.value == true)
|
||||
raiseError(ObjAssertionFailedException(this, "Assertion failed$message"))
|
||||
}
|
||||
|
||||
addVoidFn("assertEquals") {
|
||||
val a = requiredArg<Obj>(0)
|
||||
val b = requiredArg<Obj>(1)
|
||||
if( a.compareTo(this, b) != 0 )
|
||||
raiseError(ObjAssertionFailedException(this,"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}"))
|
||||
if (a.compareTo(this, b) != 0)
|
||||
raiseError(
|
||||
ObjAssertionFailedException(
|
||||
this,
|
||||
"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}"
|
||||
)
|
||||
)
|
||||
}
|
||||
// alias used in tests
|
||||
addVoidFn("assertEqual") {
|
||||
val a = requiredArg<Obj>(0)
|
||||
val b = requiredArg<Obj>(1)
|
||||
if( a.compareTo(this, b) != 0 )
|
||||
raiseError(ObjAssertionFailedException(this,"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}"))
|
||||
if (a.compareTo(this, b) != 0)
|
||||
raiseError(
|
||||
ObjAssertionFailedException(
|
||||
this,
|
||||
"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}"
|
||||
)
|
||||
)
|
||||
}
|
||||
addVoidFn("assertNotEquals") {
|
||||
val a = requiredArg<Obj>(0)
|
||||
val b = requiredArg<Obj>(1)
|
||||
if( a.compareTo(this, b) == 0 )
|
||||
raiseError(ObjAssertionFailedException(this,"Assertion failed: ${a.inspect(this)} != ${b.inspect(this)}"))
|
||||
if (a.compareTo(this, b) == 0)
|
||||
raiseError(
|
||||
ObjAssertionFailedException(
|
||||
this,
|
||||
"Assertion failed: ${a.inspect(this)} != ${b.inspect(this)}"
|
||||
)
|
||||
)
|
||||
}
|
||||
addFn("assertThrows") {
|
||||
val code = requireOnlyArg<Statement>()
|
||||
val result =try {
|
||||
val result = try {
|
||||
code.execute(this)
|
||||
null
|
||||
}
|
||||
catch( e: ExecutionError ) {
|
||||
} catch (e: ExecutionError) {
|
||||
e.errorObject
|
||||
}
|
||||
catch (_: ScriptError) {
|
||||
} catch (_: ScriptError) {
|
||||
ObjNull
|
||||
}
|
||||
result ?: raiseError(ObjAssertionFailedException(this,"Expected exception but nothing was thrown"))
|
||||
result ?: raiseError(ObjAssertionFailedException(this, "Expected exception but nothing was thrown"))
|
||||
}
|
||||
|
||||
addFn("dynamic") {
|
||||
@ -209,7 +222,7 @@ class Script(
|
||||
|
||||
addFn("require") {
|
||||
val condition = requiredArg<ObjBool>(0)
|
||||
if( !condition.value ) {
|
||||
if (!condition.value) {
|
||||
val message = args.list.getOrNull(1)?.toString() ?: "requirement not met"
|
||||
raiseIllegalArgument(message)
|
||||
}
|
||||
@ -217,7 +230,7 @@ class Script(
|
||||
}
|
||||
addFn("check") {
|
||||
val condition = requiredArg<ObjBool>(0)
|
||||
if( !condition.value ) {
|
||||
if (!condition.value) {
|
||||
val message = args.list.getOrNull(1)?.toString() ?: "check failed"
|
||||
raiseIllegalState(message)
|
||||
}
|
||||
@ -340,7 +353,7 @@ class Script(
|
||||
doc = "Suspend for the given time. Accepts Duration, Int seconds, or Real seconds."
|
||||
) {
|
||||
val a = args.firstAndOnly()
|
||||
when(a) {
|
||||
when (a) {
|
||||
is ObjInt -> delay(a.value * 1000)
|
||||
is ObjReal -> delay((a.value * 1000).roundToLong())
|
||||
is ObjDuration -> delay(a.duration)
|
||||
|
||||
@ -131,7 +131,7 @@ object CompletionEngineLight {
|
||||
}
|
||||
is MiniClassDecl -> add(CompletionItem(d.name, Kind.Class_))
|
||||
is MiniValDecl -> add(CompletionItem(d.name, Kind.Value, typeText = typeOf(d.type)))
|
||||
else -> add(CompletionItem(d.name, Kind.Value))
|
||||
// else -> add(CompletionItem(d.name, Kind.Value))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -197,9 +197,20 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
!it.key.contains("::") && it.value.visibility.isPublic && it.value.type.serializable
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val fields = publicFields.map { "${it.key}=${it.value.value}" }.joinToString(",")
|
||||
return "${objClass.className}($fields)"
|
||||
override suspend fun toString(scope: Scope, calledFromLyng: Boolean): ObjString {
|
||||
return ObjString(buildString {
|
||||
append("${objClass.className}(")
|
||||
var first = true
|
||||
for ((name, value) in publicFields) {
|
||||
if (first) first = false else append(",")
|
||||
append("$name=${value.value.toString(scope)}")
|
||||
}
|
||||
append(")")
|
||||
})
|
||||
}
|
||||
|
||||
override suspend fun inspect(scope: Scope): String {
|
||||
return toString(scope).value
|
||||
}
|
||||
|
||||
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
|
||||
|
||||
@ -158,6 +158,23 @@ val ObjIterable by lazy {
|
||||
ObjList(result)
|
||||
}
|
||||
|
||||
addFnDoc(
|
||||
name = "mapNotNull",
|
||||
doc = "Transform elements by applying the given lambda unless it returns null.",
|
||||
params = listOf(ParamDoc("transform")),
|
||||
returns = type("lyng.List"),
|
||||
isOpen = true,
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val fn = requiredArg<Statement>(0)
|
||||
val result = mutableListOf<Obj>()
|
||||
thisObj.toFlow(this).collect {
|
||||
val transformed = fn.call(this, it)
|
||||
if( transformed != ObjNull) result += transformed
|
||||
}
|
||||
ObjList(result)
|
||||
}
|
||||
|
||||
addFnDoc(
|
||||
name = "take",
|
||||
doc = "Take the first N elements and return them as a list.",
|
||||
|
||||
@ -198,6 +198,18 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
return JsonArray(list.map { it.toJson(scope) })
|
||||
}
|
||||
|
||||
override suspend fun toString(scope: Scope, calledFromLyng: Boolean): ObjString {
|
||||
return ObjString(buildString {
|
||||
append("[")
|
||||
var first = true
|
||||
for (v in list) {
|
||||
if (first) first = false else append(",")
|
||||
append(v.toString(scope).value)
|
||||
}
|
||||
append("]")
|
||||
})
|
||||
}
|
||||
|
||||
companion object {
|
||||
val type = object : ObjClass("List", ObjArray) {
|
||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
|
||||
|
||||
@ -52,8 +52,8 @@ class ObjMapEntry(val key: Obj, val value: Obj) : Obj() {
|
||||
else -> scope.raiseIndexOutOfBounds()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "$key=>$value"
|
||||
override suspend fun toString(scope: Scope, calledFromLyng: Boolean): ObjString {
|
||||
return ObjString("(${key.toString(scope).value} => ${value.toString(scope).value})")
|
||||
}
|
||||
|
||||
override val objClass = type
|
||||
|
||||
@ -1625,7 +1625,7 @@ class ListLiteralRef(private val entries: List<ListEntry>) : ObjRef {
|
||||
when (elements) {
|
||||
is ObjList -> {
|
||||
// Grow underlying array once when possible
|
||||
if (list is ArrayList) list.ensureCapacity(list.size + elements.list.size)
|
||||
list.ensureCapacity(list.size + elements.list.size)
|
||||
list.addAll(elements.list)
|
||||
}
|
||||
else -> scope.raiseError("Spread element must be list")
|
||||
|
||||
@ -82,9 +82,17 @@ open class LynonDecoder(val bin: BitInput, val settings: LynonSettings = LynonSe
|
||||
if (it !is ObjClass)
|
||||
scope.raiseClassCastError("Expected obj class but got ${it::class.simpleName}")
|
||||
it
|
||||
} ?: scope.raiseSymbolNotFound("can't deserialize: not found type $className")
|
||||
} ?: run {
|
||||
// Use Scope API that mirrors compiler-emitted ObjRef chain for qualified identifiers
|
||||
val evaluated = scope.resolveQualifiedIdentifier(className.value)
|
||||
if (evaluated !is ObjClass)
|
||||
scope.raiseClassCastError("Expected obj class but got ${evaluated::class.simpleName}")
|
||||
evaluated
|
||||
}
|
||||
}
|
||||
|
||||
// helper moved to Scope as resolveQualifiedIdentifier
|
||||
|
||||
suspend fun decodeAnyList(scope: Scope, fixedSize: Int? = null): MutableList<Obj> {
|
||||
return if (bin.getBit() == 1) {
|
||||
// homogenous
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user