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
|
>>> 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:
|
## Instance methods:
|
||||||
|
|
||||||
| fun/method | description |
|
| fun/method | description |
|
||||||
|
|||||||
@ -516,6 +516,23 @@ open class Scope(
|
|||||||
|
|
||||||
open fun applyClosure(closure: Scope): Scope = ClosureScope(this, closure)
|
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 {
|
companion object {
|
||||||
|
|
||||||
fun new(): Scope =
|
fun new(): Scope =
|
||||||
|
|||||||
@ -155,52 +155,65 @@ class Script(
|
|||||||
sqrt(args.firstAndOnly().toDouble())
|
sqrt(args.firstAndOnly().toDouble())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
addFn( "abs" ) {
|
addFn("abs") {
|
||||||
val x = args.firstAndOnly()
|
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") {
|
addVoidFn("assert") {
|
||||||
val cond = requiredArg<ObjBool>(0)
|
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
|
": " + (args[1] as Statement).execute(this).toString(this).value
|
||||||
else ""
|
else ""
|
||||||
if( !cond.value == true )
|
if (!cond.value == true)
|
||||||
raiseError(ObjAssertionFailedException(this, "Assertion failed$message"))
|
raiseError(ObjAssertionFailedException(this, "Assertion failed$message"))
|
||||||
}
|
}
|
||||||
|
|
||||||
addVoidFn("assertEquals") {
|
addVoidFn("assertEquals") {
|
||||||
val a = requiredArg<Obj>(0)
|
val a = requiredArg<Obj>(0)
|
||||||
val b = requiredArg<Obj>(1)
|
val b = requiredArg<Obj>(1)
|
||||||
if( a.compareTo(this, b) != 0 )
|
if (a.compareTo(this, b) != 0)
|
||||||
raiseError(ObjAssertionFailedException(this,"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}"))
|
raiseError(
|
||||||
|
ObjAssertionFailedException(
|
||||||
|
this,
|
||||||
|
"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}"
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// alias used in tests
|
// alias used in tests
|
||||||
addVoidFn("assertEqual") {
|
addVoidFn("assertEqual") {
|
||||||
val a = requiredArg<Obj>(0)
|
val a = requiredArg<Obj>(0)
|
||||||
val b = requiredArg<Obj>(1)
|
val b = requiredArg<Obj>(1)
|
||||||
if( a.compareTo(this, b) != 0 )
|
if (a.compareTo(this, b) != 0)
|
||||||
raiseError(ObjAssertionFailedException(this,"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}"))
|
raiseError(
|
||||||
|
ObjAssertionFailedException(
|
||||||
|
this,
|
||||||
|
"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}"
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
addVoidFn("assertNotEquals") {
|
addVoidFn("assertNotEquals") {
|
||||||
val a = requiredArg<Obj>(0)
|
val a = requiredArg<Obj>(0)
|
||||||
val b = requiredArg<Obj>(1)
|
val b = requiredArg<Obj>(1)
|
||||||
if( a.compareTo(this, b) == 0 )
|
if (a.compareTo(this, b) == 0)
|
||||||
raiseError(ObjAssertionFailedException(this,"Assertion failed: ${a.inspect(this)} != ${b.inspect(this)}"))
|
raiseError(
|
||||||
|
ObjAssertionFailedException(
|
||||||
|
this,
|
||||||
|
"Assertion failed: ${a.inspect(this)} != ${b.inspect(this)}"
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
addFn("assertThrows") {
|
addFn("assertThrows") {
|
||||||
val code = requireOnlyArg<Statement>()
|
val code = requireOnlyArg<Statement>()
|
||||||
val result =try {
|
val result = try {
|
||||||
code.execute(this)
|
code.execute(this)
|
||||||
null
|
null
|
||||||
}
|
} catch (e: ExecutionError) {
|
||||||
catch( e: ExecutionError ) {
|
|
||||||
e.errorObject
|
e.errorObject
|
||||||
}
|
} catch (_: 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"))
|
||||||
}
|
}
|
||||||
|
|
||||||
addFn("dynamic") {
|
addFn("dynamic") {
|
||||||
@ -209,7 +222,7 @@ class Script(
|
|||||||
|
|
||||||
addFn("require") {
|
addFn("require") {
|
||||||
val condition = requiredArg<ObjBool>(0)
|
val condition = requiredArg<ObjBool>(0)
|
||||||
if( !condition.value ) {
|
if (!condition.value) {
|
||||||
val message = args.list.getOrNull(1)?.toString() ?: "requirement not met"
|
val message = args.list.getOrNull(1)?.toString() ?: "requirement not met"
|
||||||
raiseIllegalArgument(message)
|
raiseIllegalArgument(message)
|
||||||
}
|
}
|
||||||
@ -217,7 +230,7 @@ class Script(
|
|||||||
}
|
}
|
||||||
addFn("check") {
|
addFn("check") {
|
||||||
val condition = requiredArg<ObjBool>(0)
|
val condition = requiredArg<ObjBool>(0)
|
||||||
if( !condition.value ) {
|
if (!condition.value) {
|
||||||
val message = args.list.getOrNull(1)?.toString() ?: "check failed"
|
val message = args.list.getOrNull(1)?.toString() ?: "check failed"
|
||||||
raiseIllegalState(message)
|
raiseIllegalState(message)
|
||||||
}
|
}
|
||||||
@ -340,7 +353,7 @@ class Script(
|
|||||||
doc = "Suspend for the given time. Accepts Duration, Int seconds, or Real seconds."
|
doc = "Suspend for the given time. Accepts Duration, Int seconds, or Real seconds."
|
||||||
) {
|
) {
|
||||||
val a = args.firstAndOnly()
|
val a = args.firstAndOnly()
|
||||||
when(a) {
|
when (a) {
|
||||||
is ObjInt -> delay(a.value * 1000)
|
is ObjInt -> delay(a.value * 1000)
|
||||||
is ObjReal -> delay((a.value * 1000).roundToLong())
|
is ObjReal -> delay((a.value * 1000).roundToLong())
|
||||||
is ObjDuration -> delay(a.duration)
|
is ObjDuration -> delay(a.duration)
|
||||||
|
|||||||
@ -131,7 +131,7 @@ object CompletionEngineLight {
|
|||||||
}
|
}
|
||||||
is MiniClassDecl -> add(CompletionItem(d.name, Kind.Class_))
|
is MiniClassDecl -> add(CompletionItem(d.name, Kind.Class_))
|
||||||
is MiniValDecl -> add(CompletionItem(d.name, Kind.Value, typeText = typeOf(d.type)))
|
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
|
!it.key.contains("::") && it.value.visibility.isPublic && it.value.type.serializable
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override suspend fun toString(scope: Scope, calledFromLyng: Boolean): ObjString {
|
||||||
val fields = publicFields.map { "${it.key}=${it.value.value}" }.joinToString(",")
|
return ObjString(buildString {
|
||||||
return "${objClass.className}($fields)"
|
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?) {
|
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
|
||||||
|
|||||||
@ -158,6 +158,23 @@ val ObjIterable by lazy {
|
|||||||
ObjList(result)
|
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(
|
addFnDoc(
|
||||||
name = "take",
|
name = "take",
|
||||||
doc = "Take the first N elements and return them as a list.",
|
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) })
|
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 {
|
companion object {
|
||||||
val type = object : ObjClass("List", ObjArray) {
|
val type = object : ObjClass("List", ObjArray) {
|
||||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
|
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()
|
else -> scope.raiseIndexOutOfBounds()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override suspend fun toString(scope: Scope, calledFromLyng: Boolean): ObjString {
|
||||||
return "$key=>$value"
|
return ObjString("(${key.toString(scope).value} => ${value.toString(scope).value})")
|
||||||
}
|
}
|
||||||
|
|
||||||
override val objClass = type
|
override val objClass = type
|
||||||
|
|||||||
@ -1625,7 +1625,7 @@ class ListLiteralRef(private val entries: List<ListEntry>) : ObjRef {
|
|||||||
when (elements) {
|
when (elements) {
|
||||||
is ObjList -> {
|
is ObjList -> {
|
||||||
// Grow underlying array once when possible
|
// 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)
|
list.addAll(elements.list)
|
||||||
}
|
}
|
||||||
else -> scope.raiseError("Spread element must be 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)
|
if (it !is ObjClass)
|
||||||
scope.raiseClassCastError("Expected obj class but got ${it::class.simpleName}")
|
scope.raiseClassCastError("Expected obj class but got ${it::class.simpleName}")
|
||||||
it
|
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> {
|
suspend fun decodeAnyList(scope: Scope, fixedSize: Int? = null): MutableList<Obj> {
|
||||||
return if (bin.getBit() == 1) {
|
return if (bin.getBit() == 1) {
|
||||||
// homogenous
|
// homogenous
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user