Update compile-time resolution and tests
This commit is contained in:
parent
8f60a84e3b
commit
523b9d338b
@ -6,3 +6,9 @@
|
|||||||
- If you need a wrapper for delegated properties, check for `getValue` explicitly and return a concrete `Statement` object when missing; avoid `onNotFoundResult` lambdas.
|
- If you need a wrapper for delegated properties, check for `getValue` explicitly and return a concrete `Statement` object when missing; avoid `onNotFoundResult` lambdas.
|
||||||
- If wasmJs browser tests hang, first run `:lynglib:wasmJsNodeTest` and look for wasm compilation errors; hangs usually mean module instantiation failed.
|
- If wasmJs browser tests hang, first run `:lynglib:wasmJsNodeTest` and look for wasm compilation errors; hangs usually mean module instantiation failed.
|
||||||
- Do not increase test timeouts to mask wasm generation errors; fix the invalid IR instead.
|
- Do not increase test timeouts to mask wasm generation errors; fix the invalid IR instead.
|
||||||
|
|
||||||
|
## Type inference notes (notes/type_system_spec.md)
|
||||||
|
- Nullability is Kotlin-style: `T` non-null, `T?` nullable, `!!` asserts non-null.
|
||||||
|
- `void` is a singleton of class `Void` (syntax sugar for return type).
|
||||||
|
- Object member access requires explicit cast; remove `inspect` from Object and use `toInspectString()` instead.
|
||||||
|
- Do not reintroduce bytecode fallback opcodes (e.g., `GET_NAME`, `EVAL_*`, `CALL_FALLBACK`) or runtime name-resolution fallbacks; all symbol resolution must stay compile-time only.
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
# Scopes and Closures: resolution and safety
|
# Scopes and Closures: resolution and safety
|
||||||
|
|
||||||
|
Attention to AI: name lookup is ibsolete and must not be used with bytecode compiler
|
||||||
|
|
||||||
This page documents how name resolution works with `ClosureScope`, how to avoid recursion pitfalls, and how to safely capture and execute callbacks that need access to outer locals.
|
This page documents how name resolution works with `ClosureScope`, how to avoid recursion pitfalls, and how to safely capture and execute callbacks that need access to outer locals.
|
||||||
|
|
||||||
## Why this matters
|
## Why this matters
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile-time call metadata for known functions. Used to select lambda receiver semantics.
|
||||||
|
*/
|
||||||
|
data class CallSignature(
|
||||||
|
val tailBlockReceiverType: String? = null
|
||||||
|
)
|
||||||
@ -26,12 +26,25 @@ import net.sergeych.lyng.obj.ObjRecord
|
|||||||
* Inherits [Scope.args] and [Scope.thisObj] from [callScope] and adds lookup for symbols
|
* Inherits [Scope.args] and [Scope.thisObj] from [callScope] and adds lookup for symbols
|
||||||
* from [closureScope] with proper precedence
|
* from [closureScope] with proper precedence
|
||||||
*/
|
*/
|
||||||
class ClosureScope(val callScope: Scope, val closureScope: Scope) :
|
class ClosureScope(
|
||||||
|
val callScope: Scope,
|
||||||
|
val closureScope: Scope,
|
||||||
|
private val preferredThisType: String? = null
|
||||||
|
) :
|
||||||
// Important: use closureScope.thisObj so unqualified members (e.g., fields) resolve to the instance
|
// Important: use closureScope.thisObj so unqualified members (e.g., fields) resolve to the instance
|
||||||
// we captured, not to the caller's `this` (e.g., FlowBuilder).
|
// we captured, not to the caller's `this` (e.g., FlowBuilder).
|
||||||
Scope(callScope, callScope.args, thisObj = closureScope.thisObj) {
|
Scope(callScope, callScope.args, thisObj = closureScope.thisObj) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
val desired = preferredThisType?.let { typeName ->
|
||||||
|
callScope.thisVariants.firstOrNull { it.objClass.className == typeName }
|
||||||
|
}
|
||||||
|
val primaryThis = closureScope.thisObj
|
||||||
|
val merged = ArrayList<Obj>(callScope.thisVariants.size + closureScope.thisVariants.size + 1)
|
||||||
|
desired?.let { merged.add(it) }
|
||||||
|
merged.addAll(callScope.thisVariants)
|
||||||
|
merged.addAll(closureScope.thisVariants)
|
||||||
|
setThisVariants(primaryThis, merged)
|
||||||
// Preserve the lexical class context of the closure by default. This ensures that lambdas
|
// Preserve the lexical class context of the closure by default. This ensures that lambdas
|
||||||
// created inside a class method keep access to that class's private/protected members even
|
// created inside a class method keep access to that class's private/protected members even
|
||||||
// when executed from within another object's method (e.g., Mutex.withLock), which may set
|
// when executed from within another object's method (e.g., Mutex.withLock), which may set
|
||||||
@ -72,14 +85,14 @@ class ClosureScope(val callScope: Scope, val closureScope: Scope) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ApplyScope(val callScope: Scope, val applied: Scope) :
|
class ApplyScope(val callScope: Scope, val applied: Scope) :
|
||||||
Scope(callScope.parent?.parent ?: callScope.parent ?: callScope, thisObj = applied.thisObj) {
|
Scope(callScope, thisObj = applied.thisObj) {
|
||||||
|
|
||||||
override fun get(name: String): ObjRecord? {
|
override fun get(name: String): ObjRecord? {
|
||||||
return applied.get(name) ?: super.get(name)
|
return applied.get(name) ?: super.get(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun applyClosure(closure: Scope): Scope {
|
override fun applyClosure(closure: Scope, preferredThisType: String?): Scope {
|
||||||
return this
|
return ClosureScope(this, closure, preferredThisType)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,10 +19,19 @@ package net.sergeych.lyng
|
|||||||
|
|
||||||
sealed class CodeContext {
|
sealed class CodeContext {
|
||||||
class Module(@Suppress("unused") val packageName: String?): CodeContext()
|
class Module(@Suppress("unused") val packageName: String?): CodeContext()
|
||||||
class Function(val name: String, val implicitThisMembers: Boolean = false): CodeContext()
|
class Function(
|
||||||
|
val name: String,
|
||||||
|
val implicitThisMembers: Boolean = false,
|
||||||
|
val implicitThisTypeName: String? = null
|
||||||
|
): CodeContext()
|
||||||
class ClassBody(val name: String, val isExtern: Boolean = false): CodeContext() {
|
class ClassBody(val name: String, val isExtern: Boolean = false): CodeContext() {
|
||||||
val pendingInitializations = mutableMapOf<String, Pos>()
|
val pendingInitializations = mutableMapOf<String, Pos>()
|
||||||
val declaredMembers = mutableSetOf<String>()
|
val declaredMembers = mutableSetOf<String>()
|
||||||
|
val memberOverrides = mutableMapOf<String, Boolean>()
|
||||||
|
val memberFieldIds = mutableMapOf<String, Int>()
|
||||||
|
val memberMethodIds = mutableMapOf<String, Int>()
|
||||||
|
var nextFieldId: Int = 0
|
||||||
|
var nextMethodId: Int = 0
|
||||||
var slotPlanId: Int? = null
|
var slotPlanId: Int? = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
internal fun extensionCallableName(typeName: String, memberName: String): String {
|
||||||
|
return "__ext__${sanitizeExtensionTypeName(typeName)}__${memberName}"
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun extensionPropertyGetterName(typeName: String, memberName: String): String {
|
||||||
|
return "__ext_get__${sanitizeExtensionTypeName(typeName)}__${memberName}"
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun extensionPropertySetterName(typeName: String, memberName: String): String {
|
||||||
|
return "__ext_set__${sanitizeExtensionTypeName(typeName)}__${memberName}"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sanitizeExtensionTypeName(typeName: String): String {
|
||||||
|
return typeName.replace('.', '_')
|
||||||
|
}
|
||||||
@ -18,6 +18,8 @@ package net.sergeych.lyng
|
|||||||
|
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
import net.sergeych.lyng.obj.ObjClass
|
import net.sergeych.lyng.obj.ObjClass
|
||||||
|
import net.sergeych.lyng.obj.ObjExtensionPropertyGetterCallable
|
||||||
|
import net.sergeych.lyng.obj.ObjExtensionPropertySetterCallable
|
||||||
import net.sergeych.lyng.obj.ObjProperty
|
import net.sergeych.lyng.obj.ObjProperty
|
||||||
import net.sergeych.lyng.obj.ObjRecord
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
|
|
||||||
@ -45,6 +47,14 @@ class ExtensionPropertyDeclStatement(
|
|||||||
type = ObjRecord.Type.Property
|
type = ObjRecord.Type.Property
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
val getterName = extensionPropertyGetterName(extTypeName, property.name)
|
||||||
|
val getterWrapper = ObjExtensionPropertyGetterCallable(property.name, property)
|
||||||
|
context.addItem(getterName, false, getterWrapper, visibility, recordType = ObjRecord.Type.Fun)
|
||||||
|
if (property.setter != null) {
|
||||||
|
val setterName = extensionPropertySetterName(extTypeName, property.name)
|
||||||
|
val setterWrapper = ObjExtensionPropertySetterCallable(property.name, property)
|
||||||
|
context.addItem(setterName, false, setterWrapper, visibility, recordType = ObjRecord.Type.Fun)
|
||||||
|
}
|
||||||
return property
|
return property
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjVoid
|
||||||
|
|
||||||
|
class InlineBlockStatement(
|
||||||
|
private val statements: List<Statement>,
|
||||||
|
private val startPos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override val pos: Pos = startPos
|
||||||
|
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
var last: Obj = ObjVoid
|
||||||
|
for (stmt in statements) {
|
||||||
|
last = stmt.execute(scope)
|
||||||
|
}
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
|
||||||
|
fun statements(): List<Statement> = statements
|
||||||
|
}
|
||||||
@ -52,6 +52,8 @@ open class Scope(
|
|||||||
var currentClassCtx: net.sergeych.lyng.obj.ObjClass? = parent?.currentClassCtx
|
var currentClassCtx: net.sergeych.lyng.obj.ObjClass? = parent?.currentClassCtx
|
||||||
// Unique id per scope frame for PICs; regenerated on each borrow from the pool.
|
// Unique id per scope frame for PICs; regenerated on each borrow from the pool.
|
||||||
var frameId: Long = nextFrameId()
|
var frameId: Long = nextFrameId()
|
||||||
|
@PublishedApi
|
||||||
|
internal val thisVariants: MutableList<Obj> = mutableListOf()
|
||||||
|
|
||||||
// Fast-path storage for local variables/arguments accessed by slot index.
|
// Fast-path storage for local variables/arguments accessed by slot index.
|
||||||
// Enabled by default for child scopes; module/class scopes can ignore it.
|
// Enabled by default for child scopes; module/class scopes can ignore it.
|
||||||
@ -66,6 +68,21 @@ open class Scope(
|
|||||||
|
|
||||||
internal val extensions: MutableMap<ObjClass, MutableMap<String, ObjRecord>> = mutableMapOf()
|
internal val extensions: MutableMap<ObjClass, MutableMap<String, ObjRecord>> = mutableMapOf()
|
||||||
|
|
||||||
|
init {
|
||||||
|
setThisVariants(thisObj, parent?.thisVariants ?: emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun setThisVariants(primary: Obj, extras: List<Obj>) {
|
||||||
|
thisObj = primary
|
||||||
|
thisVariants.clear()
|
||||||
|
thisVariants.add(primary)
|
||||||
|
for (obj in extras) {
|
||||||
|
if (obj !== primary && !thisVariants.contains(obj)) {
|
||||||
|
thisVariants.add(obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun addExtension(cls: ObjClass, name: String, record: ObjRecord) {
|
fun addExtension(cls: ObjClass, name: String, record: ObjRecord) {
|
||||||
extensions.getOrPut(cls) { mutableMapOf() }[name] = record
|
extensions.getOrPut(cls) { mutableMapOf() }[name] = record
|
||||||
}
|
}
|
||||||
@ -340,11 +357,8 @@ open class Scope(
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T : Obj> thisAs(): T {
|
inline fun <reified T : Obj> thisAs(): T {
|
||||||
var s: Scope? = this
|
for (obj in thisVariants) {
|
||||||
while (s != null) {
|
if (obj is T) return obj
|
||||||
val t = s.thisObj
|
|
||||||
if (t is T) return t
|
|
||||||
s = s.parent
|
|
||||||
}
|
}
|
||||||
raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")
|
raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")
|
||||||
}
|
}
|
||||||
@ -406,6 +420,8 @@ open class Scope(
|
|||||||
get() = slots.size
|
get() = slots.size
|
||||||
|
|
||||||
fun getSlotIndexOf(name: String): Int? = nameToSlot[name]
|
fun getSlotIndexOf(name: String): Int? = nameToSlot[name]
|
||||||
|
|
||||||
|
internal fun slotNameToIndexSnapshot(): Map<String, Int> = nameToSlot.toMap()
|
||||||
fun allocateSlotFor(name: String, record: ObjRecord): Int {
|
fun allocateSlotFor(name: String, record: ObjRecord): Int {
|
||||||
val idx = slots.size
|
val idx = slots.size
|
||||||
slots.add(record)
|
slots.add(record)
|
||||||
@ -490,6 +506,7 @@ open class Scope(
|
|||||||
this.parent = null
|
this.parent = null
|
||||||
this.skipScopeCreation = false
|
this.skipScopeCreation = false
|
||||||
this.currentClassCtx = null
|
this.currentClassCtx = null
|
||||||
|
thisVariants.clear()
|
||||||
objects.clear()
|
objects.clear()
|
||||||
slots.clear()
|
slots.clear()
|
||||||
nameToSlot.clear()
|
nameToSlot.clear()
|
||||||
@ -520,7 +537,7 @@ open class Scope(
|
|||||||
this.parent = parent
|
this.parent = parent
|
||||||
this.args = args
|
this.args = args
|
||||||
this.pos = pos
|
this.pos = pos
|
||||||
this.thisObj = thisObj
|
setThisVariants(thisObj, parent?.thisVariants ?: emptyList())
|
||||||
// Pre-size local slots for upcoming parameter assignment where possible
|
// Pre-size local slots for upcoming parameter assignment where possible
|
||||||
reserveLocalCapacity(args.list.size + 4)
|
reserveLocalCapacity(args.list.size + 4)
|
||||||
}
|
}
|
||||||
@ -609,7 +626,10 @@ open class Scope(
|
|||||||
isAbstract: Boolean = false,
|
isAbstract: Boolean = false,
|
||||||
isClosed: Boolean = false,
|
isClosed: Boolean = false,
|
||||||
isOverride: Boolean = false,
|
isOverride: Boolean = false,
|
||||||
isTransient: Boolean = false
|
isTransient: Boolean = false,
|
||||||
|
callSignature: CallSignature? = null,
|
||||||
|
fieldId: Int? = null,
|
||||||
|
methodId: Int? = null
|
||||||
): ObjRecord {
|
): ObjRecord {
|
||||||
val rec = ObjRecord(
|
val rec = ObjRecord(
|
||||||
value, isMutable, visibility, writeVisibility,
|
value, isMutable, visibility, writeVisibility,
|
||||||
@ -618,15 +638,19 @@ open class Scope(
|
|||||||
isAbstract = isAbstract,
|
isAbstract = isAbstract,
|
||||||
isClosed = isClosed,
|
isClosed = isClosed,
|
||||||
isOverride = isOverride,
|
isOverride = isOverride,
|
||||||
isTransient = isTransient
|
isTransient = isTransient,
|
||||||
|
callSignature = callSignature,
|
||||||
|
memberName = name,
|
||||||
|
fieldId = fieldId,
|
||||||
|
methodId = methodId
|
||||||
)
|
)
|
||||||
objects[name] = rec
|
objects[name] = rec
|
||||||
bumpClassLayoutIfNeeded(name, value, recordType)
|
bumpClassLayoutIfNeeded(name, value, recordType)
|
||||||
if (recordType == ObjRecord.Type.Field || recordType == ObjRecord.Type.ConstructorField) {
|
if (recordType == ObjRecord.Type.Field || recordType == ObjRecord.Type.ConstructorField) {
|
||||||
val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance
|
val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance
|
||||||
if (inst != null) {
|
if (inst != null) {
|
||||||
val slot = inst.objClass.fieldSlotForKey(name)
|
val slotId = rec.fieldId ?: inst.objClass.fieldSlotForKey(name)?.slot
|
||||||
if (slot != null) inst.setFieldSlotRecord(slot.slot, rec)
|
if (slotId != null) inst.setFieldSlotRecord(slotId, rec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (value is Statement ||
|
if (value is Statement ||
|
||||||
@ -635,8 +659,8 @@ open class Scope(
|
|||||||
recordType == ObjRecord.Type.Property) {
|
recordType == ObjRecord.Type.Property) {
|
||||||
val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance
|
val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance
|
||||||
if (inst != null) {
|
if (inst != null) {
|
||||||
val slot = inst.objClass.methodSlotForKey(name)
|
val slotId = rec.methodId ?: inst.objClass.methodSlotForKey(name)?.slot
|
||||||
if (slot != null) inst.setMethodSlotRecord(slot.slot, rec)
|
if (slotId != null) inst.setMethodSlotRecord(slotId, rec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Index this binding within the current frame to help resolve locals across suspension
|
// Index this binding within the current frame to help resolve locals across suspension
|
||||||
@ -697,7 +721,7 @@ open class Scope(
|
|||||||
return CmdDisassembler.disassemble(bytecode)
|
return CmdDisassembler.disassemble(bytecode)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addFn(vararg names: String, fn: suspend Scope.() -> Obj) {
|
fun addFn(vararg names: String, callSignature: CallSignature? = null, fn: suspend Scope.() -> Obj) {
|
||||||
val newFn = object : Statement() {
|
val newFn = object : Statement() {
|
||||||
override val pos: Pos = Pos.builtIn
|
override val pos: Pos = Pos.builtIn
|
||||||
|
|
||||||
@ -708,7 +732,9 @@ open class Scope(
|
|||||||
addItem(
|
addItem(
|
||||||
name,
|
name,
|
||||||
false,
|
false,
|
||||||
newFn
|
newFn,
|
||||||
|
recordType = ObjRecord.Type.Fun,
|
||||||
|
callSignature = callSignature
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -778,7 +804,8 @@ open class Scope(
|
|||||||
println("--------------------")
|
println("--------------------")
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun applyClosure(closure: Scope): Scope = ClosureScope(this, closure)
|
open fun applyClosure(closure: Scope, preferredThisType: String? = null): Scope =
|
||||||
|
ClosureScope(this, closure, preferredThisType)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve and evaluate a qualified identifier exactly as compiled code would.
|
* Resolve and evaluate a qualified identifier exactly as compiled code would.
|
||||||
|
|||||||
@ -428,8 +428,11 @@ class Script(
|
|||||||
addConst("CompletableDeferred", ObjCompletableDeferred.type)
|
addConst("CompletableDeferred", ObjCompletableDeferred.type)
|
||||||
addConst("Mutex", ObjMutex.type)
|
addConst("Mutex", ObjMutex.type)
|
||||||
addConst("Flow", ObjFlow.type)
|
addConst("Flow", ObjFlow.type)
|
||||||
|
addConst("FlowBuilder", ObjFlowBuilder.type)
|
||||||
|
|
||||||
addConst("Regex", ObjRegex.type)
|
addConst("Regex", ObjRegex.type)
|
||||||
|
addConst("RegexMatch", ObjRegexMatch.type)
|
||||||
|
addConst("MapEntry", ObjMapEntry.type)
|
||||||
|
|
||||||
addFn("launch") {
|
addFn("launch") {
|
||||||
val callable = requireOnlyArg<Statement>()
|
val callable = requireOnlyArg<Statement>()
|
||||||
@ -443,7 +446,7 @@ class Script(
|
|||||||
ObjVoid
|
ObjVoid
|
||||||
}
|
}
|
||||||
|
|
||||||
addFn("flow") {
|
addFn("flow", callSignature = CallSignature(tailBlockReceiverType = "FlowBuilder")) {
|
||||||
// important is: current context contains closure often used in call;
|
// important is: current context contains closure often used in call;
|
||||||
// we'll need it for the producer
|
// we'll need it for the producer
|
||||||
ObjFlow(requireOnlyArg<Statement>(), this)
|
ObjFlow(requireOnlyArg<Statement>(), this)
|
||||||
|
|||||||
@ -17,8 +17,10 @@
|
|||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjClass
|
||||||
import net.sergeych.lyng.obj.ObjNull
|
import net.sergeych.lyng.obj.ObjNull
|
||||||
import net.sergeych.lyng.obj.ObjRecord
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
|
import net.sergeych.lyng.obj.ObjUnset
|
||||||
|
|
||||||
class VarDeclStatement(
|
class VarDeclStatement(
|
||||||
val name: String,
|
val name: String,
|
||||||
@ -29,11 +31,12 @@ class VarDeclStatement(
|
|||||||
val slotIndex: Int?,
|
val slotIndex: Int?,
|
||||||
val scopeId: Int?,
|
val scopeId: Int?,
|
||||||
private val startPos: Pos,
|
private val startPos: Pos,
|
||||||
|
val initializerObjClass: ObjClass? = null,
|
||||||
) : Statement() {
|
) : Statement() {
|
||||||
override val pos: Pos = startPos
|
override val pos: Pos = startPos
|
||||||
|
|
||||||
override suspend fun execute(context: Scope): Obj {
|
override suspend fun execute(context: Scope): Obj {
|
||||||
val initValue = initializer?.execute(context)?.byValueCopy() ?: ObjNull
|
val initValue = initializer?.execute(context)?.byValueCopy() ?: ObjUnset
|
||||||
context.addItem(
|
context.addItem(
|
||||||
name,
|
name,
|
||||||
isMutable,
|
isMutable,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -49,6 +49,7 @@ class BytecodeStatement private constructor(
|
|||||||
allowLocalSlots: Boolean,
|
allowLocalSlots: Boolean,
|
||||||
returnLabels: Set<String> = emptySet(),
|
returnLabels: Set<String> = emptySet(),
|
||||||
rangeLocalNames: Set<String> = emptySet(),
|
rangeLocalNames: Set<String> = emptySet(),
|
||||||
|
allowedScopeNames: Set<String>? = null,
|
||||||
): Statement {
|
): Statement {
|
||||||
if (statement is BytecodeStatement) return statement
|
if (statement is BytecodeStatement) return statement
|
||||||
val hasUnsupported = containsUnsupportedStatement(statement)
|
val hasUnsupported = containsUnsupportedStatement(statement)
|
||||||
@ -63,7 +64,8 @@ class BytecodeStatement private constructor(
|
|||||||
val compiler = BytecodeCompiler(
|
val compiler = BytecodeCompiler(
|
||||||
allowLocalSlots = safeLocals,
|
allowLocalSlots = safeLocals,
|
||||||
returnLabels = returnLabels,
|
returnLabels = returnLabels,
|
||||||
rangeLocalNames = rangeLocalNames
|
rangeLocalNames = rangeLocalNames,
|
||||||
|
allowedScopeNames = allowedScopeNames
|
||||||
)
|
)
|
||||||
val compiled = compiler.compileStatement(nameHint, statement)
|
val compiled = compiler.compileStatement(nameHint, statement)
|
||||||
val fn = compiled ?: throw BytecodeFallbackException(
|
val fn = compiled ?: throw BytecodeFallbackException(
|
||||||
@ -76,7 +78,14 @@ class BytecodeStatement private constructor(
|
|||||||
private fun containsUnsupportedStatement(stmt: Statement): Boolean {
|
private fun containsUnsupportedStatement(stmt: Statement): Boolean {
|
||||||
val target = if (stmt is BytecodeStatement) stmt.original else stmt
|
val target = if (stmt is BytecodeStatement) stmt.original else stmt
|
||||||
return when (target) {
|
return when (target) {
|
||||||
is net.sergeych.lyng.ExpressionStatement -> false
|
is net.sergeych.lyng.ExpressionStatement -> {
|
||||||
|
val ref = target.ref
|
||||||
|
if (ref is net.sergeych.lyng.obj.StatementRef) {
|
||||||
|
containsUnsupportedStatement(ref.statement)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
is net.sergeych.lyng.IfStatement -> {
|
is net.sergeych.lyng.IfStatement -> {
|
||||||
containsUnsupportedStatement(target.condition) ||
|
containsUnsupportedStatement(target.condition) ||
|
||||||
containsUnsupportedStatement(target.ifBody) ||
|
containsUnsupportedStatement(target.ifBody) ||
|
||||||
@ -100,6 +109,8 @@ class BytecodeStatement private constructor(
|
|||||||
}
|
}
|
||||||
is net.sergeych.lyng.BlockStatement ->
|
is net.sergeych.lyng.BlockStatement ->
|
||||||
target.statements().any { containsUnsupportedStatement(it) }
|
target.statements().any { containsUnsupportedStatement(it) }
|
||||||
|
is net.sergeych.lyng.InlineBlockStatement ->
|
||||||
|
target.statements().any { containsUnsupportedStatement(it) }
|
||||||
is net.sergeych.lyng.VarDeclStatement ->
|
is net.sergeych.lyng.VarDeclStatement ->
|
||||||
target.initializer?.let { containsUnsupportedStatement(it) } ?: false
|
target.initializer?.let { containsUnsupportedStatement(it) } ?: false
|
||||||
is net.sergeych.lyng.DelegatedVarDeclStatement ->
|
is net.sergeych.lyng.DelegatedVarDeclStatement ->
|
||||||
@ -117,7 +128,7 @@ class BytecodeStatement private constructor(
|
|||||||
is net.sergeych.lyng.ClassDeclStatement -> false
|
is net.sergeych.lyng.ClassDeclStatement -> false
|
||||||
is net.sergeych.lyng.FunctionDeclStatement -> false
|
is net.sergeych.lyng.FunctionDeclStatement -> false
|
||||||
is net.sergeych.lyng.EnumDeclStatement -> false
|
is net.sergeych.lyng.EnumDeclStatement -> false
|
||||||
is net.sergeych.lyng.TryStatement -> false
|
is net.sergeych.lyng.TryStatement -> true
|
||||||
is net.sergeych.lyng.WhenStatement -> {
|
is net.sergeych.lyng.WhenStatement -> {
|
||||||
containsUnsupportedStatement(target.value) ||
|
containsUnsupportedStatement(target.value) ||
|
||||||
target.cases.any { case ->
|
target.cases.any { case ->
|
||||||
@ -151,7 +162,8 @@ class BytecodeStatement private constructor(
|
|||||||
stmt.isTransient,
|
stmt.isTransient,
|
||||||
stmt.slotIndex,
|
stmt.slotIndex,
|
||||||
stmt.scopeId,
|
stmt.scopeId,
|
||||||
stmt.pos
|
stmt.pos,
|
||||||
|
stmt.initializerObjClass
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is net.sergeych.lyng.DestructuringVarDeclStatement -> {
|
is net.sergeych.lyng.DestructuringVarDeclStatement -> {
|
||||||
|
|||||||
@ -137,7 +137,7 @@ class CmdBuilder {
|
|||||||
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
||||||
Opcode.CONST_NULL ->
|
Opcode.CONST_NULL ->
|
||||||
listOf(OperandKind.SLOT)
|
listOf(OperandKind.SLOT)
|
||||||
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL ->
|
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL, Opcode.MAKE_VALUE_FN ->
|
||||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
||||||
listOf(OperandKind.CONST)
|
listOf(OperandKind.CONST)
|
||||||
@ -161,14 +161,18 @@ class CmdBuilder {
|
|||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
Opcode.ASSIGN_OP_OBJ ->
|
Opcode.ASSIGN_OP_OBJ ->
|
||||||
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.CONST)
|
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.CONST)
|
||||||
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET ->
|
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET, Opcode.LOAD_THIS ->
|
||||||
listOf(OperandKind.SLOT)
|
listOf(OperandKind.SLOT)
|
||||||
|
Opcode.LOAD_THIS_VARIANT ->
|
||||||
|
listOf(OperandKind.ID, OperandKind.SLOT)
|
||||||
Opcode.JMP ->
|
Opcode.JMP ->
|
||||||
listOf(OperandKind.IP)
|
listOf(OperandKind.IP)
|
||||||
Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE ->
|
Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.IP)
|
listOf(OperandKind.SLOT, OperandKind.IP)
|
||||||
Opcode.CALL_DIRECT, Opcode.CALL_FALLBACK ->
|
Opcode.CALL_DIRECT ->
|
||||||
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
|
Opcode.CALL_MEMBER_SLOT ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
Opcode.CALL_SLOT ->
|
Opcode.CALL_SLOT ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
Opcode.CALL_VIRTUAL ->
|
Opcode.CALL_VIRTUAL ->
|
||||||
@ -177,8 +181,6 @@ class CmdBuilder {
|
|||||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
|
||||||
Opcode.SET_FIELD ->
|
Opcode.SET_FIELD ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
|
||||||
Opcode.GET_NAME ->
|
|
||||||
listOf(OperandKind.ID, OperandKind.SLOT)
|
|
||||||
Opcode.GET_INDEX ->
|
Opcode.GET_INDEX ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
Opcode.SET_INDEX ->
|
Opcode.SET_INDEX ->
|
||||||
@ -187,12 +189,10 @@ class CmdBuilder {
|
|||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
Opcode.LIST_LITERAL ->
|
Opcode.LIST_LITERAL ->
|
||||||
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
Opcode.GET_THIS_MEMBER ->
|
Opcode.GET_MEMBER_SLOT ->
|
||||||
listOf(OperandKind.ID, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT)
|
||||||
Opcode.SET_THIS_MEMBER ->
|
Opcode.SET_MEMBER_SLOT ->
|
||||||
listOf(OperandKind.ID, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT)
|
||||||
Opcode.EVAL_FALLBACK, Opcode.EVAL_REF, Opcode.EVAL_STMT, Opcode.EVAL_VALUE_FN ->
|
|
||||||
listOf(OperandKind.ID, OperandKind.SLOT)
|
|
||||||
Opcode.ITER_PUSH ->
|
Opcode.ITER_PUSH ->
|
||||||
listOf(OperandKind.SLOT)
|
listOf(OperandKind.SLOT)
|
||||||
Opcode.ITER_POP, Opcode.ITER_CANCEL ->
|
Opcode.ITER_POP, Opcode.ITER_CANCEL ->
|
||||||
@ -229,9 +229,12 @@ class CmdBuilder {
|
|||||||
Opcode.CONST_REAL -> CmdConstReal(operands[0], operands[1])
|
Opcode.CONST_REAL -> CmdConstReal(operands[0], operands[1])
|
||||||
Opcode.CONST_BOOL -> CmdConstBool(operands[0], operands[1])
|
Opcode.CONST_BOOL -> CmdConstBool(operands[0], operands[1])
|
||||||
Opcode.CONST_NULL -> CmdConstNull(operands[0])
|
Opcode.CONST_NULL -> CmdConstNull(operands[0])
|
||||||
|
Opcode.MAKE_VALUE_FN -> CmdMakeValueFn(operands[0], operands[1])
|
||||||
Opcode.BOX_OBJ -> CmdBoxObj(operands[0], operands[1])
|
Opcode.BOX_OBJ -> CmdBoxObj(operands[0], operands[1])
|
||||||
Opcode.OBJ_TO_BOOL -> CmdObjToBool(operands[0], operands[1])
|
Opcode.OBJ_TO_BOOL -> CmdObjToBool(operands[0], operands[1])
|
||||||
Opcode.RANGE_INT_BOUNDS -> CmdRangeIntBounds(operands[0], operands[1], operands[2], operands[3])
|
Opcode.RANGE_INT_BOUNDS -> CmdRangeIntBounds(operands[0], operands[1], operands[2], operands[3])
|
||||||
|
Opcode.LOAD_THIS -> CmdLoadThis(operands[0])
|
||||||
|
Opcode.LOAD_THIS_VARIANT -> CmdLoadThisVariant(operands[0], operands[1])
|
||||||
Opcode.MAKE_RANGE -> CmdMakeRange(operands[0], operands[1], operands[2], operands[3])
|
Opcode.MAKE_RANGE -> CmdMakeRange(operands[0], operands[1], operands[2], operands[3])
|
||||||
Opcode.CHECK_IS -> CmdCheckIs(operands[0], operands[1], operands[2])
|
Opcode.CHECK_IS -> CmdCheckIs(operands[0], operands[1], operands[2])
|
||||||
Opcode.ASSERT_IS -> CmdAssertIs(operands[0], operands[1])
|
Opcode.ASSERT_IS -> CmdAssertIs(operands[0], operands[1])
|
||||||
@ -378,21 +381,16 @@ class CmdBuilder {
|
|||||||
Opcode.DECL_LOCAL -> CmdDeclLocal(operands[0], operands[1])
|
Opcode.DECL_LOCAL -> CmdDeclLocal(operands[0], operands[1])
|
||||||
Opcode.DECL_EXT_PROPERTY -> CmdDeclExtProperty(operands[0], operands[1])
|
Opcode.DECL_EXT_PROPERTY -> CmdDeclExtProperty(operands[0], operands[1])
|
||||||
Opcode.CALL_DIRECT -> CmdCallDirect(operands[0], operands[1], operands[2], operands[3])
|
Opcode.CALL_DIRECT -> CmdCallDirect(operands[0], operands[1], operands[2], operands[3])
|
||||||
|
Opcode.CALL_MEMBER_SLOT -> CmdCallMemberSlot(operands[0], operands[1], operands[2], operands[3], operands[4])
|
||||||
Opcode.CALL_VIRTUAL -> CmdCallVirtual(operands[0], operands[1], operands[2], operands[3], operands[4])
|
Opcode.CALL_VIRTUAL -> CmdCallVirtual(operands[0], operands[1], operands[2], operands[3], operands[4])
|
||||||
Opcode.CALL_FALLBACK -> CmdCallFallback(operands[0], operands[1], operands[2], operands[3])
|
|
||||||
Opcode.CALL_SLOT -> CmdCallSlot(operands[0], operands[1], operands[2], operands[3])
|
Opcode.CALL_SLOT -> CmdCallSlot(operands[0], operands[1], operands[2], operands[3])
|
||||||
Opcode.GET_FIELD -> CmdGetField(operands[0], operands[1], operands[2])
|
Opcode.GET_FIELD -> CmdGetField(operands[0], operands[1], operands[2])
|
||||||
Opcode.SET_FIELD -> CmdSetField(operands[0], operands[1], operands[2])
|
Opcode.SET_FIELD -> CmdSetField(operands[0], operands[1], operands[2])
|
||||||
Opcode.GET_NAME -> CmdGetName(operands[0], operands[1])
|
|
||||||
Opcode.GET_INDEX -> CmdGetIndex(operands[0], operands[1], operands[2])
|
Opcode.GET_INDEX -> CmdGetIndex(operands[0], operands[1], operands[2])
|
||||||
Opcode.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2])
|
Opcode.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2])
|
||||||
Opcode.LIST_LITERAL -> CmdListLiteral(operands[0], operands[1], operands[2], operands[3])
|
Opcode.LIST_LITERAL -> CmdListLiteral(operands[0], operands[1], operands[2], operands[3])
|
||||||
Opcode.GET_THIS_MEMBER -> CmdGetThisMember(operands[0], operands[1])
|
Opcode.GET_MEMBER_SLOT -> CmdGetMemberSlot(operands[0], operands[1], operands[2], operands[3])
|
||||||
Opcode.SET_THIS_MEMBER -> CmdSetThisMember(operands[0], operands[1])
|
Opcode.SET_MEMBER_SLOT -> CmdSetMemberSlot(operands[0], operands[1], operands[2], operands[3])
|
||||||
Opcode.EVAL_FALLBACK -> CmdEvalFallback(operands[0], operands[1])
|
|
||||||
Opcode.EVAL_REF -> CmdEvalRef(operands[0], operands[1])
|
|
||||||
Opcode.EVAL_STMT -> CmdEvalStmt(operands[0], operands[1])
|
|
||||||
Opcode.EVAL_VALUE_FN -> CmdEvalValueFn(operands[0], operands[1])
|
|
||||||
Opcode.ITER_PUSH -> CmdIterPush(operands[0])
|
Opcode.ITER_PUSH -> CmdIterPush(operands[0])
|
||||||
Opcode.ITER_POP -> CmdIterPop()
|
Opcode.ITER_POP -> CmdIterPop()
|
||||||
Opcode.ITER_CANCEL -> CmdIterCancel()
|
Opcode.ITER_CANCEL -> CmdIterCancel()
|
||||||
|
|||||||
@ -66,7 +66,10 @@ object CmdDisassembler {
|
|||||||
is CmdConstIntLocal -> Opcode.CONST_INT to intArrayOf(cmd.constId, cmd.dst + fn.scopeSlotCount)
|
is CmdConstIntLocal -> Opcode.CONST_INT to intArrayOf(cmd.constId, cmd.dst + fn.scopeSlotCount)
|
||||||
is CmdConstReal -> Opcode.CONST_REAL to intArrayOf(cmd.constId, cmd.dst)
|
is CmdConstReal -> Opcode.CONST_REAL to intArrayOf(cmd.constId, cmd.dst)
|
||||||
is CmdConstBool -> Opcode.CONST_BOOL to intArrayOf(cmd.constId, cmd.dst)
|
is CmdConstBool -> Opcode.CONST_BOOL to intArrayOf(cmd.constId, cmd.dst)
|
||||||
|
is CmdLoadThis -> Opcode.LOAD_THIS to intArrayOf(cmd.dst)
|
||||||
|
is CmdLoadThisVariant -> Opcode.LOAD_THIS_VARIANT to intArrayOf(cmd.typeId, cmd.dst)
|
||||||
is CmdConstNull -> Opcode.CONST_NULL to intArrayOf(cmd.dst)
|
is CmdConstNull -> Opcode.CONST_NULL to intArrayOf(cmd.dst)
|
||||||
|
is CmdMakeValueFn -> Opcode.MAKE_VALUE_FN to intArrayOf(cmd.id, cmd.dst)
|
||||||
is CmdBoxObj -> Opcode.BOX_OBJ to intArrayOf(cmd.src, cmd.dst)
|
is CmdBoxObj -> Opcode.BOX_OBJ to intArrayOf(cmd.src, cmd.dst)
|
||||||
is CmdObjToBool -> Opcode.OBJ_TO_BOOL to intArrayOf(cmd.src, cmd.dst)
|
is CmdObjToBool -> Opcode.OBJ_TO_BOOL to intArrayOf(cmd.src, cmd.dst)
|
||||||
is CmdCheckIs -> Opcode.CHECK_IS to intArrayOf(cmd.objSlot, cmd.typeSlot, cmd.dst)
|
is CmdCheckIs -> Opcode.CHECK_IS to intArrayOf(cmd.objSlot, cmd.typeSlot, cmd.dst)
|
||||||
@ -178,23 +181,19 @@ object CmdDisassembler {
|
|||||||
is CmdDeclExtProperty -> Opcode.DECL_EXT_PROPERTY to intArrayOf(cmd.constId, cmd.slot)
|
is CmdDeclExtProperty -> Opcode.DECL_EXT_PROPERTY to intArrayOf(cmd.constId, cmd.slot)
|
||||||
is CmdCallDirect -> Opcode.CALL_DIRECT to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst)
|
is CmdCallDirect -> Opcode.CALL_DIRECT to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst)
|
||||||
is CmdCallVirtual -> Opcode.CALL_VIRTUAL to intArrayOf(cmd.recvSlot, cmd.methodId, cmd.argBase, cmd.argCount, cmd.dst)
|
is CmdCallVirtual -> Opcode.CALL_VIRTUAL to intArrayOf(cmd.recvSlot, cmd.methodId, cmd.argBase, cmd.argCount, cmd.dst)
|
||||||
is CmdCallFallback -> Opcode.CALL_FALLBACK to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst)
|
is CmdCallMemberSlot -> Opcode.CALL_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.methodId, cmd.argBase, cmd.argCount, cmd.dst)
|
||||||
is CmdCallSlot -> Opcode.CALL_SLOT to intArrayOf(cmd.calleeSlot, cmd.argBase, cmd.argCount, cmd.dst)
|
is CmdCallSlot -> Opcode.CALL_SLOT to intArrayOf(cmd.calleeSlot, cmd.argBase, cmd.argCount, cmd.dst)
|
||||||
is CmdGetField -> Opcode.GET_FIELD to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.dst)
|
is CmdGetField -> Opcode.GET_FIELD to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.dst)
|
||||||
is CmdSetField -> Opcode.SET_FIELD to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.valueSlot)
|
is CmdSetField -> Opcode.SET_FIELD to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.valueSlot)
|
||||||
is CmdGetName -> Opcode.GET_NAME to intArrayOf(cmd.nameId, cmd.dst)
|
|
||||||
is CmdGetIndex -> Opcode.GET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.dst)
|
is CmdGetIndex -> Opcode.GET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.dst)
|
||||||
is CmdSetIndex -> Opcode.SET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot)
|
is CmdSetIndex -> Opcode.SET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot)
|
||||||
is CmdListLiteral -> Opcode.LIST_LITERAL to intArrayOf(cmd.planId, cmd.baseSlot, cmd.count, cmd.dst)
|
is CmdListLiteral -> Opcode.LIST_LITERAL to intArrayOf(cmd.planId, cmd.baseSlot, cmd.count, cmd.dst)
|
||||||
is CmdGetThisMember -> Opcode.GET_THIS_MEMBER to intArrayOf(cmd.nameId, cmd.dst)
|
is CmdGetMemberSlot -> Opcode.GET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.dst)
|
||||||
is CmdSetThisMember -> Opcode.SET_THIS_MEMBER to intArrayOf(cmd.nameId, cmd.valueSlot)
|
is CmdSetMemberSlot -> Opcode.SET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.valueSlot)
|
||||||
is CmdEvalFallback -> Opcode.EVAL_FALLBACK to intArrayOf(cmd.id, cmd.dst)
|
|
||||||
is CmdEvalRef -> Opcode.EVAL_REF to intArrayOf(cmd.id, cmd.dst)
|
|
||||||
is CmdEvalStmt -> Opcode.EVAL_STMT to intArrayOf(cmd.id, cmd.dst)
|
|
||||||
is CmdEvalValueFn -> Opcode.EVAL_VALUE_FN to intArrayOf(cmd.id, cmd.dst)
|
|
||||||
is CmdIterPush -> Opcode.ITER_PUSH to intArrayOf(cmd.iterSlot)
|
is CmdIterPush -> Opcode.ITER_PUSH to intArrayOf(cmd.iterSlot)
|
||||||
is CmdIterPop -> Opcode.ITER_POP to intArrayOf()
|
is CmdIterPop -> Opcode.ITER_POP to intArrayOf()
|
||||||
is CmdIterCancel -> Opcode.ITER_CANCEL to intArrayOf()
|
is CmdIterCancel -> Opcode.ITER_CANCEL to intArrayOf()
|
||||||
|
else -> error("Unsupported cmd in disassembler: ${cmd::class.simpleName}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,7 +230,7 @@ object CmdDisassembler {
|
|||||||
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
||||||
Opcode.CONST_NULL ->
|
Opcode.CONST_NULL ->
|
||||||
listOf(OperandKind.SLOT)
|
listOf(OperandKind.SLOT)
|
||||||
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL ->
|
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL, Opcode.MAKE_VALUE_FN ->
|
||||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
||||||
listOf(OperandKind.CONST)
|
listOf(OperandKind.CONST)
|
||||||
@ -255,36 +254,36 @@ object CmdDisassembler {
|
|||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
Opcode.ASSIGN_OP_OBJ ->
|
Opcode.ASSIGN_OP_OBJ ->
|
||||||
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.CONST)
|
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.CONST)
|
||||||
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET, Opcode.ITER_PUSH ->
|
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET, Opcode.ITER_PUSH, Opcode.LOAD_THIS ->
|
||||||
listOf(OperandKind.SLOT)
|
listOf(OperandKind.SLOT)
|
||||||
|
Opcode.LOAD_THIS_VARIANT ->
|
||||||
|
listOf(OperandKind.ID, OperandKind.SLOT)
|
||||||
Opcode.JMP ->
|
Opcode.JMP ->
|
||||||
listOf(OperandKind.IP)
|
listOf(OperandKind.IP)
|
||||||
Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE ->
|
Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.IP)
|
listOf(OperandKind.SLOT, OperandKind.IP)
|
||||||
Opcode.CALL_DIRECT, Opcode.CALL_FALLBACK ->
|
Opcode.CALL_DIRECT ->
|
||||||
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
Opcode.CALL_SLOT ->
|
Opcode.CALL_SLOT ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
Opcode.CALL_VIRTUAL ->
|
Opcode.CALL_VIRTUAL ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
|
Opcode.CALL_MEMBER_SLOT ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
Opcode.GET_FIELD ->
|
Opcode.GET_FIELD ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
|
||||||
Opcode.SET_FIELD ->
|
Opcode.SET_FIELD ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
|
||||||
Opcode.GET_NAME ->
|
|
||||||
listOf(OperandKind.ID, OperandKind.SLOT)
|
|
||||||
Opcode.GET_INDEX ->
|
Opcode.GET_INDEX ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
Opcode.SET_INDEX ->
|
Opcode.SET_INDEX ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
Opcode.LIST_LITERAL ->
|
Opcode.LIST_LITERAL ->
|
||||||
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
Opcode.GET_THIS_MEMBER ->
|
Opcode.GET_MEMBER_SLOT ->
|
||||||
listOf(OperandKind.ID, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT)
|
||||||
Opcode.SET_THIS_MEMBER ->
|
Opcode.SET_MEMBER_SLOT ->
|
||||||
listOf(OperandKind.ID, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT)
|
||||||
Opcode.EVAL_FALLBACK, Opcode.EVAL_REF, Opcode.EVAL_STMT, Opcode.EVAL_VALUE_FN ->
|
|
||||||
listOf(OperandKind.ID, OperandKind.SLOT)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -152,6 +152,28 @@ class CmdConstBool(internal val constId: Int, internal val dst: Int) : Cmd() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CmdLoadThis(internal val dst: Int) : Cmd() {
|
||||||
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
|
frame.setObj(dst, frame.scope.thisObj)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CmdLoadThisVariant(
|
||||||
|
internal val typeId: Int,
|
||||||
|
internal val dst: Int,
|
||||||
|
) : Cmd() {
|
||||||
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
|
val typeConst = frame.fn.constants.getOrNull(typeId) as? BytecodeConst.StringVal
|
||||||
|
?: error("LOAD_THIS_VARIANT expects StringVal at $typeId")
|
||||||
|
val typeName = typeConst.value
|
||||||
|
val receiver = frame.scope.thisVariants.firstOrNull { it.isInstanceOf(typeName) }
|
||||||
|
?: frame.scope.raiseClassCastError("Cannot cast ${frame.scope.thisObj.objClass.className} to $typeName")
|
||||||
|
frame.setObj(dst, receiver)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class CmdMakeRange(
|
class CmdMakeRange(
|
||||||
internal val startSlot: Int,
|
internal val startSlot: Int,
|
||||||
internal val endSlot: Int,
|
internal val endSlot: Int,
|
||||||
@ -1146,20 +1168,7 @@ class CmdCallVirtual(
|
|||||||
internal val dst: Int,
|
internal val dst: Int,
|
||||||
) : Cmd() {
|
) : Cmd() {
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
frame.scope.raiseError("CALL_VIRTUAL is not allowed: compile-time member resolution is required")
|
||||||
frame.syncFrameToScope()
|
|
||||||
}
|
|
||||||
val receiver = frame.slotToObj(recvSlot)
|
|
||||||
val nameConst = frame.fn.constants.getOrNull(methodId) as? BytecodeConst.StringVal
|
|
||||||
?: error("CALL_VIRTUAL expects StringVal at $methodId")
|
|
||||||
val args = frame.buildArguments(argBase, argCount)
|
|
||||||
val site = frame.methodCallSites.getOrPut(frame.ip - 1) { MethodCallSite(nameConst.value) }
|
|
||||||
val result = site.invoke(frame.scope, receiver, args)
|
|
||||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
|
||||||
frame.syncScopeToFrame()
|
|
||||||
}
|
|
||||||
frame.storeObjResult(dst, result)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1313,38 +1322,87 @@ class CmdListLiteral(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CmdGetThisMember(
|
class CmdGetMemberSlot(
|
||||||
internal val nameId: Int,
|
internal val recvSlot: Int,
|
||||||
|
internal val fieldId: Int,
|
||||||
|
internal val methodId: Int,
|
||||||
|
internal val dst: Int,
|
||||||
|
) : Cmd() {
|
||||||
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
|
val receiver = frame.slotToObj(recvSlot)
|
||||||
|
val inst = receiver as? ObjInstance
|
||||||
|
val fieldRec = if (fieldId >= 0) {
|
||||||
|
inst?.fieldRecordForId(fieldId) ?: receiver.objClass.fieldRecordForId(fieldId)
|
||||||
|
} else null
|
||||||
|
val rec = fieldRec ?: run {
|
||||||
|
if (methodId >= 0) {
|
||||||
|
inst?.methodRecordForId(methodId) ?: receiver.objClass.methodRecordForId(methodId)
|
||||||
|
} else null
|
||||||
|
} ?: frame.scope.raiseSymbolNotFound("member")
|
||||||
|
val name = rec.memberName ?: "<member>"
|
||||||
|
val resolved = receiver.resolveRecord(frame.scope, rec, name, rec.declaringClass)
|
||||||
|
frame.storeObjResult(dst, resolved.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CmdSetMemberSlot(
|
||||||
|
internal val recvSlot: Int,
|
||||||
|
internal val fieldId: Int,
|
||||||
|
internal val methodId: Int,
|
||||||
|
internal val valueSlot: Int,
|
||||||
|
) : Cmd() {
|
||||||
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
|
val receiver = frame.slotToObj(recvSlot)
|
||||||
|
val inst = receiver as? ObjInstance
|
||||||
|
val fieldRec = if (fieldId >= 0) {
|
||||||
|
inst?.fieldRecordForId(fieldId) ?: receiver.objClass.fieldRecordForId(fieldId)
|
||||||
|
} else null
|
||||||
|
val rec = fieldRec ?: run {
|
||||||
|
if (methodId >= 0) {
|
||||||
|
inst?.methodRecordForId(methodId) ?: receiver.objClass.methodRecordForId(methodId)
|
||||||
|
} else null
|
||||||
|
} ?: frame.scope.raiseSymbolNotFound("member")
|
||||||
|
val name = rec.memberName ?: "<member>"
|
||||||
|
frame.scope.assign(rec, name, frame.slotToObj(valueSlot))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CmdCallMemberSlot(
|
||||||
|
internal val recvSlot: Int,
|
||||||
|
internal val methodId: Int,
|
||||||
|
internal val argBase: Int,
|
||||||
|
internal val argCount: Int,
|
||||||
internal val dst: Int,
|
internal val dst: Int,
|
||||||
) : Cmd() {
|
) : Cmd() {
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
if (frame.fn.localSlotNames.isNotEmpty()) {
|
||||||
frame.syncFrameToScope()
|
frame.syncFrameToScope()
|
||||||
}
|
}
|
||||||
val nameConst = frame.fn.constants.getOrNull(nameId) as? BytecodeConst.StringVal
|
val receiver = frame.slotToObj(recvSlot)
|
||||||
?: error("GET_THIS_MEMBER expects StringVal at $nameId")
|
val inst = receiver as? ObjInstance
|
||||||
val ref = net.sergeych.lyng.obj.ImplicitThisMemberRef(nameConst.value, frame.scope.pos)
|
val rec = inst?.methodRecordForId(methodId)
|
||||||
val result = ref.evalValue(frame.scope)
|
?: receiver.objClass.methodRecordForId(methodId)
|
||||||
frame.storeObjResult(dst, result)
|
?: frame.scope.raiseError("member id $methodId not found on ${receiver.objClass.className}")
|
||||||
return
|
val callArgs = frame.buildArguments(argBase, argCount)
|
||||||
|
val name = rec.memberName ?: "<member>"
|
||||||
|
val decl = rec.declaringClass ?: receiver.objClass
|
||||||
|
val result = when (rec.type) {
|
||||||
|
ObjRecord.Type.Property -> {
|
||||||
|
if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(frame.scope, receiver, decl)
|
||||||
|
else frame.scope.raiseError("property $name cannot be called with arguments")
|
||||||
}
|
}
|
||||||
|
ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> {
|
||||||
|
val callScope = inst?.instanceScope ?: frame.scope
|
||||||
|
rec.value.invoke(callScope, receiver, callArgs, decl)
|
||||||
}
|
}
|
||||||
|
else -> frame.scope.raiseError("member $name is not callable")
|
||||||
class CmdSetThisMember(
|
|
||||||
internal val nameId: Int,
|
|
||||||
internal val valueSlot: Int,
|
|
||||||
) : Cmd() {
|
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
|
||||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
|
||||||
frame.syncFrameToScope()
|
|
||||||
}
|
}
|
||||||
val nameConst = frame.fn.constants.getOrNull(nameId) as? BytecodeConst.StringVal
|
|
||||||
?: error("SET_THIS_MEMBER expects StringVal at $nameId")
|
|
||||||
val ref = net.sergeych.lyng.obj.ImplicitThisMemberRef(nameConst.value, frame.scope.pos)
|
|
||||||
ref.setAt(frame.scope.pos, frame.scope, frame.slotToObj(valueSlot))
|
|
||||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
if (frame.fn.localSlotNames.isNotEmpty()) {
|
||||||
frame.syncScopeToFrame()
|
frame.syncScopeToFrame()
|
||||||
}
|
}
|
||||||
|
frame.storeObjResult(dst, result)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1449,13 +1507,13 @@ class CmdEvalStmt(internal val id: Int, internal val dst: Int) : Cmd() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CmdEvalValueFn(internal val id: Int, internal val dst: Int) : Cmd() {
|
class CmdMakeValueFn(internal val id: Int, internal val dst: Int) : Cmd() {
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
if (frame.fn.localSlotNames.isNotEmpty()) {
|
||||||
frame.syncFrameToScope()
|
frame.syncFrameToScope()
|
||||||
}
|
}
|
||||||
val valueFn = frame.fn.constants.getOrNull(id) as? BytecodeConst.ValueFn
|
val valueFn = frame.fn.constants.getOrNull(id) as? BytecodeConst.ValueFn
|
||||||
?: error("EVAL_VALUE_FN expects ValueFn at $id")
|
?: error("MAKE_VALUE_FN expects ValueFn at $id")
|
||||||
val result = valueFn.fn(frame.scope).value
|
val result = valueFn.fn(frame.scope).value
|
||||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
if (frame.fn.localSlotNames.isNotEmpty()) {
|
||||||
frame.syncScopeToFrame()
|
frame.syncScopeToFrame()
|
||||||
|
|||||||
@ -30,6 +30,9 @@ enum class Opcode(val code: Int) {
|
|||||||
BOX_OBJ(0x0A),
|
BOX_OBJ(0x0A),
|
||||||
RANGE_INT_BOUNDS(0x0B),
|
RANGE_INT_BOUNDS(0x0B),
|
||||||
MAKE_RANGE(0x0C),
|
MAKE_RANGE(0x0C),
|
||||||
|
LOAD_THIS(0x0D),
|
||||||
|
MAKE_VALUE_FN(0x0E),
|
||||||
|
LOAD_THIS_VARIANT(0x0F),
|
||||||
|
|
||||||
INT_TO_REAL(0x10),
|
INT_TO_REAL(0x10),
|
||||||
REAL_TO_INT(0x11),
|
REAL_TO_INT(0x11),
|
||||||
@ -124,19 +127,17 @@ enum class Opcode(val code: Int) {
|
|||||||
|
|
||||||
CALL_DIRECT(0x90),
|
CALL_DIRECT(0x90),
|
||||||
CALL_VIRTUAL(0x91),
|
CALL_VIRTUAL(0x91),
|
||||||
CALL_FALLBACK(0x92),
|
CALL_MEMBER_SLOT(0x92),
|
||||||
CALL_SLOT(0x93),
|
CALL_SLOT(0x93),
|
||||||
|
|
||||||
GET_FIELD(0xA0),
|
GET_FIELD(0xA0),
|
||||||
SET_FIELD(0xA1),
|
SET_FIELD(0xA1),
|
||||||
GET_INDEX(0xA2),
|
GET_INDEX(0xA2),
|
||||||
SET_INDEX(0xA3),
|
SET_INDEX(0xA3),
|
||||||
GET_NAME(0xA4),
|
|
||||||
LIST_LITERAL(0xA5),
|
LIST_LITERAL(0xA5),
|
||||||
GET_THIS_MEMBER(0xA6),
|
GET_MEMBER_SLOT(0xA8),
|
||||||
SET_THIS_MEMBER(0xA7),
|
SET_MEMBER_SLOT(0xA9),
|
||||||
|
|
||||||
EVAL_FALLBACK(0xB0),
|
|
||||||
RESOLVE_SCOPE_SLOT(0xB1),
|
RESOLVE_SCOPE_SLOT(0xB1),
|
||||||
LOAD_OBJ_ADDR(0xB2),
|
LOAD_OBJ_ADDR(0xB2),
|
||||||
STORE_OBJ_ADDR(0xB3),
|
STORE_OBJ_ADDR(0xB3),
|
||||||
@ -147,9 +148,6 @@ enum class Opcode(val code: Int) {
|
|||||||
LOAD_BOOL_ADDR(0xB8),
|
LOAD_BOOL_ADDR(0xB8),
|
||||||
STORE_BOOL_ADDR(0xB9),
|
STORE_BOOL_ADDR(0xB9),
|
||||||
THROW(0xBB),
|
THROW(0xBB),
|
||||||
EVAL_REF(0xBC),
|
|
||||||
EVAL_STMT(0xBD),
|
|
||||||
EVAL_VALUE_FN(0xBE),
|
|
||||||
ITER_PUSH(0xBF),
|
ITER_PUSH(0xBF),
|
||||||
ITER_POP(0xC0),
|
ITER_POP(0xC0),
|
||||||
ITER_CANCEL(0xC1),
|
ITER_CANCEL(0xC1),
|
||||||
|
|||||||
@ -39,10 +39,11 @@ inline fun <reified T : Obj> Scope.addFnDoc(
|
|||||||
returns: TypeDoc? = null,
|
returns: TypeDoc? = null,
|
||||||
tags: Map<String, List<String>> = emptyMap(),
|
tags: Map<String, List<String>> = emptyMap(),
|
||||||
moduleName: String? = null,
|
moduleName: String? = null,
|
||||||
|
callSignature: net.sergeych.lyng.CallSignature? = null,
|
||||||
crossinline fn: suspend Scope.() -> T
|
crossinline fn: suspend Scope.() -> T
|
||||||
) {
|
) {
|
||||||
// Register runtime function(s)
|
// Register runtime function(s)
|
||||||
addFn(*names) { fn() }
|
addFn(*names, callSignature = callSignature) { fn() }
|
||||||
// Determine module
|
// Determine module
|
||||||
val mod = moduleName ?: findModuleNameOrUnknown()
|
val mod = moduleName ?: findModuleNameOrUnknown()
|
||||||
// Register docs once per name
|
// Register docs once per name
|
||||||
|
|||||||
@ -124,17 +124,7 @@ open class Obj {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Extensions in scope
|
// 2. Root object fallback
|
||||||
val extension = scope.findExtension(objClass, name)
|
|
||||||
if (extension != null) {
|
|
||||||
if (extension.type == ObjRecord.Type.Property) {
|
|
||||||
if (args.isEmpty()) return (extension.value as ObjProperty).callGetter(scope, this, extension.declaringClass)
|
|
||||||
} else if (extension.type != ObjRecord.Type.Delegated) {
|
|
||||||
return extension.value.invoke(scope, this, args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Root object fallback
|
|
||||||
for (cls in objClass.mro) {
|
for (cls in objClass.mro) {
|
||||||
if (cls.className == "Obj") {
|
if (cls.className == "Obj") {
|
||||||
cls.members[name]?.let { rec ->
|
cls.members[name]?.let { rec ->
|
||||||
@ -181,7 +171,7 @@ open class Obj {
|
|||||||
|
|
||||||
open suspend fun equals(scope: Scope, other: Obj): Boolean {
|
open suspend fun equals(scope: Scope, other: Obj): Boolean {
|
||||||
if (other === this) return true
|
if (other === this) return true
|
||||||
val m = objClass.getInstanceMemberOrNull("equals") ?: scope.findExtension(objClass, "equals")
|
val m = objClass.getInstanceMemberOrNull("equals")
|
||||||
if (m != null) {
|
if (m != null) {
|
||||||
return invokeInstanceMethod(scope, "equals", Arguments(other)).toBool()
|
return invokeInstanceMethod(scope, "equals", Arguments(other)).toBool()
|
||||||
}
|
}
|
||||||
@ -375,7 +365,7 @@ open class Obj {
|
|||||||
* to generate it as 'this = this + other', reassigning its variable
|
* to generate it as 'this = this + other', reassigning its variable
|
||||||
*/
|
*/
|
||||||
open suspend fun plusAssign(scope: Scope, other: Obj): Obj? {
|
open suspend fun plusAssign(scope: Scope, other: Obj): Obj? {
|
||||||
val m = objClass.getInstanceMemberOrNull("plusAssign") ?: scope.findExtension(objClass, "plusAssign")
|
val m = objClass.getInstanceMemberOrNull("plusAssign")
|
||||||
return if (m != null) {
|
return if (m != null) {
|
||||||
invokeInstanceMethod(scope, "plusAssign", Arguments(other))
|
invokeInstanceMethod(scope, "plusAssign", Arguments(other))
|
||||||
} else null
|
} else null
|
||||||
@ -385,28 +375,28 @@ open class Obj {
|
|||||||
* `-=` operations, see [plusAssign]
|
* `-=` operations, see [plusAssign]
|
||||||
*/
|
*/
|
||||||
open suspend fun minusAssign(scope: Scope, other: Obj): Obj? {
|
open suspend fun minusAssign(scope: Scope, other: Obj): Obj? {
|
||||||
val m = objClass.getInstanceMemberOrNull("minusAssign") ?: scope.findExtension(objClass, "minusAssign")
|
val m = objClass.getInstanceMemberOrNull("minusAssign")
|
||||||
return if (m != null) {
|
return if (m != null) {
|
||||||
invokeInstanceMethod(scope, "minusAssign", Arguments(other))
|
invokeInstanceMethod(scope, "minusAssign", Arguments(other))
|
||||||
} else null
|
} else null
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun mulAssign(scope: Scope, other: Obj): Obj? {
|
open suspend fun mulAssign(scope: Scope, other: Obj): Obj? {
|
||||||
val m = objClass.getInstanceMemberOrNull("mulAssign") ?: scope.findExtension(objClass, "mulAssign")
|
val m = objClass.getInstanceMemberOrNull("mulAssign")
|
||||||
return if (m != null) {
|
return if (m != null) {
|
||||||
invokeInstanceMethod(scope, "mulAssign", Arguments(other))
|
invokeInstanceMethod(scope, "mulAssign", Arguments(other))
|
||||||
} else null
|
} else null
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun divAssign(scope: Scope, other: Obj): Obj? {
|
open suspend fun divAssign(scope: Scope, other: Obj): Obj? {
|
||||||
val m = objClass.getInstanceMemberOrNull("divAssign") ?: scope.findExtension(objClass, "divAssign")
|
val m = objClass.getInstanceMemberOrNull("divAssign")
|
||||||
return if (m != null) {
|
return if (m != null) {
|
||||||
invokeInstanceMethod(scope, "divAssign", Arguments(other))
|
invokeInstanceMethod(scope, "divAssign", Arguments(other))
|
||||||
} else null
|
} else null
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun modAssign(scope: Scope, other: Obj): Obj? {
|
open suspend fun modAssign(scope: Scope, other: Obj): Obj? {
|
||||||
val m = objClass.getInstanceMemberOrNull("modAssign") ?: scope.findExtension(objClass, "modAssign")
|
val m = objClass.getInstanceMemberOrNull("modAssign")
|
||||||
return if (m != null) {
|
return if (m != null) {
|
||||||
invokeInstanceMethod(scope, "modAssign", Arguments(other))
|
invokeInstanceMethod(scope, "modAssign", Arguments(other))
|
||||||
} else null
|
} else null
|
||||||
@ -467,16 +457,7 @@ open class Obj {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Extensions
|
// 2. Root fallback
|
||||||
val extension = scope.findExtension(objClass, name)
|
|
||||||
if (extension != null) {
|
|
||||||
val resolved = resolveRecord(scope, extension, name, extension.declaringClass)
|
|
||||||
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement)
|
|
||||||
return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, extension.declaringClass))
|
|
||||||
return resolved
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Root fallback
|
|
||||||
for (cls in objClass.mro) {
|
for (cls in objClass.mro) {
|
||||||
if (cls.className == "Obj") {
|
if (cls.className == "Obj") {
|
||||||
cls.members[name]?.let { rec ->
|
cls.members[name]?.let { rec ->
|
||||||
@ -558,11 +539,7 @@ open class Obj {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 2. Extensions
|
// 2. Root fallback
|
||||||
if (field == null) {
|
|
||||||
field = scope.findExtension(objClass, name)
|
|
||||||
}
|
|
||||||
// 3. Root fallback
|
|
||||||
if (field == null) {
|
if (field == null) {
|
||||||
for (cls in objClass.mro) {
|
for (cls in objClass.mro) {
|
||||||
if (cls.className == "Obj") {
|
if (cls.className == "Obj") {
|
||||||
|
|||||||
@ -275,6 +275,11 @@ open class ObjClass(
|
|||||||
internal data class FieldSlot(val slot: Int, val record: ObjRecord)
|
internal data class FieldSlot(val slot: Int, val record: ObjRecord)
|
||||||
internal data class ResolvedMember(val record: ObjRecord, val declaringClass: ObjClass)
|
internal data class ResolvedMember(val record: ObjRecord, val declaringClass: ObjClass)
|
||||||
internal data class MethodSlot(val slot: Int, val record: ObjRecord)
|
internal data class MethodSlot(val slot: Int, val record: ObjRecord)
|
||||||
|
private var nextFieldId: Int = 0
|
||||||
|
private var nextMethodId: Int = 0
|
||||||
|
private val fieldIdMap: MutableMap<String, Int> = mutableMapOf()
|
||||||
|
private val methodIdMap: MutableMap<String, Int> = mutableMapOf()
|
||||||
|
private var methodIdSeeded: Boolean = false
|
||||||
private var fieldSlotLayoutVersion: Int = -1
|
private var fieldSlotLayoutVersion: Int = -1
|
||||||
private var fieldSlotMap: Map<String, FieldSlot> = emptyMap()
|
private var fieldSlotMap: Map<String, FieldSlot> = emptyMap()
|
||||||
private var fieldSlotCount: Int = 0
|
private var fieldSlotCount: Int = 0
|
||||||
@ -287,19 +292,29 @@ open class ObjClass(
|
|||||||
private fun ensureFieldSlots(): Map<String, FieldSlot> {
|
private fun ensureFieldSlots(): Map<String, FieldSlot> {
|
||||||
if (fieldSlotLayoutVersion == layoutVersion) return fieldSlotMap
|
if (fieldSlotLayoutVersion == layoutVersion) return fieldSlotMap
|
||||||
val res = mutableMapOf<String, FieldSlot>()
|
val res = mutableMapOf<String, FieldSlot>()
|
||||||
var idx = 0
|
var maxId = -1
|
||||||
for (cls in mro) {
|
for (cls in mro) {
|
||||||
for ((name, rec) in cls.members) {
|
for ((name, rec) in cls.members) {
|
||||||
if (rec.isAbstract) continue
|
if (rec.isAbstract) continue
|
||||||
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) continue
|
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) continue
|
||||||
val key = cls.mangledName(name)
|
val key = cls.mangledName(name)
|
||||||
if (res.containsKey(key)) continue
|
if (res.containsKey(key)) continue
|
||||||
res[key] = FieldSlot(idx, rec)
|
val fieldId = rec.fieldId ?: cls.assignFieldId(name, rec)
|
||||||
idx += 1
|
res[key] = FieldSlot(fieldId, rec)
|
||||||
|
if (fieldId > maxId) maxId = fieldId
|
||||||
|
}
|
||||||
|
cls.classScope?.objects?.forEach { (name, rec) ->
|
||||||
|
if (rec.isAbstract) return@forEach
|
||||||
|
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) return@forEach
|
||||||
|
val key = cls.mangledName(name)
|
||||||
|
if (res.containsKey(key)) return@forEach
|
||||||
|
val fieldId = rec.fieldId ?: cls.assignFieldId(name, rec)
|
||||||
|
res[key] = FieldSlot(fieldId, rec)
|
||||||
|
if (fieldId > maxId) maxId = fieldId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fieldSlotMap = res
|
fieldSlotMap = res
|
||||||
fieldSlotCount = idx
|
fieldSlotCount = maxId + 1
|
||||||
fieldSlotLayoutVersion = layoutVersion
|
fieldSlotLayoutVersion = layoutVersion
|
||||||
return fieldSlotMap
|
return fieldSlotMap
|
||||||
}
|
}
|
||||||
@ -308,7 +323,6 @@ open class ObjClass(
|
|||||||
if (instanceMemberLayoutVersion == layoutVersion) return instanceMemberCache
|
if (instanceMemberLayoutVersion == layoutVersion) return instanceMemberCache
|
||||||
val res = mutableMapOf<String, ResolvedMember>()
|
val res = mutableMapOf<String, ResolvedMember>()
|
||||||
for (cls in mro) {
|
for (cls in mro) {
|
||||||
if (cls.className == "Obj") break
|
|
||||||
for ((name, rec) in cls.members) {
|
for ((name, rec) in cls.members) {
|
||||||
if (rec.isAbstract) continue
|
if (rec.isAbstract) continue
|
||||||
if (res.containsKey(name)) continue
|
if (res.containsKey(name)) continue
|
||||||
@ -330,7 +344,7 @@ open class ObjClass(
|
|||||||
private fun ensureMethodSlots(): Map<String, MethodSlot> {
|
private fun ensureMethodSlots(): Map<String, MethodSlot> {
|
||||||
if (methodSlotLayoutVersion == layoutVersion) return methodSlotMap
|
if (methodSlotLayoutVersion == layoutVersion) return methodSlotMap
|
||||||
val res = mutableMapOf<String, MethodSlot>()
|
val res = mutableMapOf<String, MethodSlot>()
|
||||||
var idx = 0
|
var maxId = -1
|
||||||
for (cls in mro) {
|
for (cls in mro) {
|
||||||
if (cls.className == "Obj") break
|
if (cls.className == "Obj") break
|
||||||
for ((name, rec) in cls.members) {
|
for ((name, rec) in cls.members) {
|
||||||
@ -343,8 +357,9 @@ open class ObjClass(
|
|||||||
}
|
}
|
||||||
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
|
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
|
||||||
if (res.containsKey(key)) continue
|
if (res.containsKey(key)) continue
|
||||||
res[key] = MethodSlot(idx, rec)
|
val methodId = rec.methodId ?: cls.assignMethodId(name, rec)
|
||||||
idx += 1
|
res[key] = MethodSlot(methodId, rec)
|
||||||
|
if (methodId > maxId) maxId = methodId
|
||||||
}
|
}
|
||||||
cls.classScope?.objects?.forEach { (name, rec) ->
|
cls.classScope?.objects?.forEach { (name, rec) ->
|
||||||
if (rec.isAbstract) return@forEach
|
if (rec.isAbstract) return@forEach
|
||||||
@ -353,12 +368,13 @@ open class ObjClass(
|
|||||||
rec.type != ObjRecord.Type.Property) return@forEach
|
rec.type != ObjRecord.Type.Property) return@forEach
|
||||||
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
|
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
|
||||||
if (res.containsKey(key)) return@forEach
|
if (res.containsKey(key)) return@forEach
|
||||||
res[key] = MethodSlot(idx, rec)
|
val methodId = rec.methodId ?: cls.assignMethodId(name, rec)
|
||||||
idx += 1
|
res[key] = MethodSlot(methodId, rec)
|
||||||
|
if (methodId > maxId) maxId = methodId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
methodSlotMap = res
|
methodSlotMap = res
|
||||||
methodSlotCount = idx
|
methodSlotCount = maxId + 1
|
||||||
methodSlotLayoutVersion = layoutVersion
|
methodSlotLayoutVersion = layoutVersion
|
||||||
return methodSlotMap
|
return methodSlotMap
|
||||||
}
|
}
|
||||||
@ -374,6 +390,10 @@ open class ObjClass(
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun fieldSlotMap(): Map<String, FieldSlot> = ensureFieldSlots()
|
internal fun fieldSlotMap(): Map<String, FieldSlot> = ensureFieldSlots()
|
||||||
|
internal fun fieldRecordForId(fieldId: Int): ObjRecord? {
|
||||||
|
ensureFieldSlots()
|
||||||
|
return fieldSlotMap.values.firstOrNull { it.slot == fieldId }?.record
|
||||||
|
}
|
||||||
internal fun resolveInstanceMember(name: String): ResolvedMember? = ensureInstanceMemberCache()[name]
|
internal fun resolveInstanceMember(name: String): ResolvedMember? = ensureInstanceMemberCache()[name]
|
||||||
internal fun methodSlotCount(): Int {
|
internal fun methodSlotCount(): Int {
|
||||||
ensureMethodSlots()
|
ensureMethodSlots()
|
||||||
@ -384,6 +404,117 @@ open class ObjClass(
|
|||||||
return methodSlotMap[key]
|
return methodSlotMap[key]
|
||||||
}
|
}
|
||||||
internal fun methodSlotMap(): Map<String, MethodSlot> = ensureMethodSlots()
|
internal fun methodSlotMap(): Map<String, MethodSlot> = ensureMethodSlots()
|
||||||
|
internal fun methodRecordForId(methodId: Int): ObjRecord? {
|
||||||
|
ensureMethodSlots()
|
||||||
|
methodSlotMap.values.firstOrNull { it.slot == methodId }?.record?.let { return it }
|
||||||
|
// Fallback to scanning the MRO in case a parent method id was added after slot cache creation.
|
||||||
|
for (cls in mro) {
|
||||||
|
for ((_, rec) in cls.members) {
|
||||||
|
if (rec.methodId == methodId) return rec
|
||||||
|
}
|
||||||
|
cls.classScope?.objects?.forEach { (_, rec) ->
|
||||||
|
if (rec.methodId == methodId) return rec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun instanceFieldIdMap(): Map<String, Int> {
|
||||||
|
val result = mutableMapOf<String, Int>()
|
||||||
|
for (cls in mro) {
|
||||||
|
if (cls.className == "Obj") break
|
||||||
|
for ((name, rec) in cls.members) {
|
||||||
|
if (rec.isAbstract) continue
|
||||||
|
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) continue
|
||||||
|
if (rec.visibility == Visibility.Private) continue
|
||||||
|
val id = rec.fieldId ?: cls.assignFieldId(name, rec)
|
||||||
|
result.putIfAbsent(name, id)
|
||||||
|
}
|
||||||
|
cls.classScope?.objects?.forEach { (name, rec) ->
|
||||||
|
if (rec.isAbstract) return@forEach
|
||||||
|
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) return@forEach
|
||||||
|
if (rec.visibility == Visibility.Private) return@forEach
|
||||||
|
val id = rec.fieldId ?: cls.assignFieldId(name, rec)
|
||||||
|
result.putIfAbsent(name, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun instanceMethodIdMap(includeAbstract: Boolean = false): Map<String, Int> {
|
||||||
|
val result = mutableMapOf<String, Int>()
|
||||||
|
for (cls in mro) {
|
||||||
|
for ((name, rec) in cls.members) {
|
||||||
|
if (!includeAbstract && rec.isAbstract) continue
|
||||||
|
if (rec.visibility == Visibility.Private) continue
|
||||||
|
if (rec.type != ObjRecord.Type.Fun &&
|
||||||
|
rec.type != ObjRecord.Type.Property &&
|
||||||
|
rec.type != ObjRecord.Type.Delegated) continue
|
||||||
|
val id = rec.methodId ?: cls.assignMethodId(name, rec)
|
||||||
|
result.putIfAbsent(name, id)
|
||||||
|
}
|
||||||
|
cls.classScope?.objects?.forEach { (name, rec) ->
|
||||||
|
if (!includeAbstract && rec.isAbstract) return@forEach
|
||||||
|
if (rec.visibility == Visibility.Private) return@forEach
|
||||||
|
if (rec.type != ObjRecord.Type.Fun &&
|
||||||
|
rec.type != ObjRecord.Type.Property &&
|
||||||
|
rec.type != ObjRecord.Type.Delegated) return@forEach
|
||||||
|
val id = rec.methodId ?: cls.assignMethodId(name, rec)
|
||||||
|
result.putIfAbsent(name, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignFieldId(name: String, rec: ObjRecord): Int {
|
||||||
|
val existingId = rec.fieldId
|
||||||
|
if (existingId != null) {
|
||||||
|
fieldIdMap[name] = existingId
|
||||||
|
return existingId
|
||||||
|
}
|
||||||
|
val id = fieldIdMap.getOrPut(name) { nextFieldId++ }
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignMethodId(name: String, rec: ObjRecord): Int {
|
||||||
|
ensureMethodIdSeeded()
|
||||||
|
val existingId = rec.methodId
|
||||||
|
if (existingId != null) {
|
||||||
|
methodIdMap[name] = existingId
|
||||||
|
return existingId
|
||||||
|
}
|
||||||
|
val id = methodIdMap.getOrPut(name) { nextMethodId++ }
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ensureMethodIdSeeded() {
|
||||||
|
if (methodIdSeeded) return
|
||||||
|
var maxId = -1
|
||||||
|
for (cls in mroParents) {
|
||||||
|
for ((name, rec) in cls.members) {
|
||||||
|
if (rec.type != ObjRecord.Type.Fun &&
|
||||||
|
rec.type != ObjRecord.Type.Property &&
|
||||||
|
rec.type != ObjRecord.Type.Delegated
|
||||||
|
) continue
|
||||||
|
val id = rec.methodId ?: cls.assignMethodId(name, rec)
|
||||||
|
methodIdMap.putIfAbsent(name, id)
|
||||||
|
if (id > maxId) maxId = id
|
||||||
|
}
|
||||||
|
cls.classScope?.objects?.forEach { (name, rec) ->
|
||||||
|
if (rec.type != ObjRecord.Type.Fun &&
|
||||||
|
rec.type != ObjRecord.Type.Property &&
|
||||||
|
rec.type != ObjRecord.Type.Delegated
|
||||||
|
) return@forEach
|
||||||
|
val id = rec.methodId ?: cls.assignMethodId(name, rec)
|
||||||
|
methodIdMap.putIfAbsent(name, id)
|
||||||
|
if (id > maxId) maxId = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nextMethodId <= maxId) {
|
||||||
|
nextMethodId = maxId + 1
|
||||||
|
}
|
||||||
|
methodIdSeeded = true
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString(): String = className
|
override fun toString(): String = className
|
||||||
|
|
||||||
@ -618,12 +749,15 @@ open class ObjClass(
|
|||||||
isOverride: Boolean = false,
|
isOverride: Boolean = false,
|
||||||
isTransient: Boolean = false,
|
isTransient: Boolean = false,
|
||||||
type: ObjRecord.Type = ObjRecord.Type.Field,
|
type: ObjRecord.Type = ObjRecord.Type.Field,
|
||||||
|
fieldId: Int? = null,
|
||||||
|
methodId: Int? = null,
|
||||||
): ObjRecord {
|
): ObjRecord {
|
||||||
// Validation of override rules: only for non-system declarations
|
// Validation of override rules: only for non-system declarations
|
||||||
|
var existing: ObjRecord? = null
|
||||||
|
var actualOverride = false
|
||||||
if (pos != Pos.builtIn) {
|
if (pos != Pos.builtIn) {
|
||||||
// Only consider TRUE instance members from ancestors for overrides
|
// Only consider TRUE instance members from ancestors for overrides
|
||||||
val existing = getInstanceMemberOrNull(name, includeAbstract = true, includeStatic = false)
|
existing = getInstanceMemberOrNull(name, includeAbstract = true, includeStatic = false)
|
||||||
var actualOverride = false
|
|
||||||
if (existing != null && existing.declaringClass != this) {
|
if (existing != null && existing.declaringClass != this) {
|
||||||
// If the existing member is private in the ancestor, it's not visible for overriding.
|
// If the existing member is private in the ancestor, it's not visible for overriding.
|
||||||
// It should be treated as a new member in this class.
|
// It should be treated as a new member in this class.
|
||||||
@ -654,6 +788,56 @@ open class ObjClass(
|
|||||||
throw ScriptError(pos, "$name is already defined in $objClass")
|
throw ScriptError(pos, "$name is already defined in $objClass")
|
||||||
|
|
||||||
// Install/override in this class
|
// Install/override in this class
|
||||||
|
val effectiveFieldId = if (type == ObjRecord.Type.Field || type == ObjRecord.Type.ConstructorField) {
|
||||||
|
fieldId ?: fieldIdMap[name]?.let { it } ?: run {
|
||||||
|
fieldIdMap[name] = nextFieldId
|
||||||
|
nextFieldId++
|
||||||
|
fieldIdMap[name]!!
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fieldId
|
||||||
|
}
|
||||||
|
val inheritedCandidate = run {
|
||||||
|
var found: ObjRecord? = null
|
||||||
|
for (cls in mro) {
|
||||||
|
if (cls === this) continue
|
||||||
|
if (cls.className == "Obj") break
|
||||||
|
cls.members[name]?.let {
|
||||||
|
found = it
|
||||||
|
return@run found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
found
|
||||||
|
}
|
||||||
|
if (type == ObjRecord.Type.Fun ||
|
||||||
|
type == ObjRecord.Type.Property ||
|
||||||
|
type == ObjRecord.Type.Delegated
|
||||||
|
) {
|
||||||
|
ensureMethodIdSeeded()
|
||||||
|
}
|
||||||
|
val effectiveMethodId = if (type == ObjRecord.Type.Fun ||
|
||||||
|
type == ObjRecord.Type.Property ||
|
||||||
|
type == ObjRecord.Type.Delegated
|
||||||
|
) {
|
||||||
|
val inherited = if (actualOverride) {
|
||||||
|
existing?.methodId
|
||||||
|
} else {
|
||||||
|
val candidate = inheritedCandidate
|
||||||
|
if (candidate != null &&
|
||||||
|
candidate.declaringClass != this &&
|
||||||
|
(candidate.visibility.isPublic || canAccessMember(candidate.visibility, candidate.declaringClass, this, name))
|
||||||
|
) {
|
||||||
|
candidate.methodId
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
methodId ?: inherited ?: methodIdMap[name]?.let { it } ?: run {
|
||||||
|
methodIdMap[name] = nextMethodId
|
||||||
|
nextMethodId++
|
||||||
|
methodIdMap[name]!!
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
methodId
|
||||||
|
}
|
||||||
val rec = ObjRecord(
|
val rec = ObjRecord(
|
||||||
initialValue, isMutable, visibility, writeVisibility,
|
initialValue, isMutable, visibility, writeVisibility,
|
||||||
declaringClass = declaringClass,
|
declaringClass = declaringClass,
|
||||||
@ -661,7 +845,10 @@ open class ObjClass(
|
|||||||
isClosed = isClosed,
|
isClosed = isClosed,
|
||||||
isOverride = isOverride,
|
isOverride = isOverride,
|
||||||
isTransient = isTransient,
|
isTransient = isTransient,
|
||||||
type = type
|
type = type,
|
||||||
|
memberName = name,
|
||||||
|
fieldId = effectiveFieldId,
|
||||||
|
methodId = effectiveMethodId
|
||||||
)
|
)
|
||||||
members[name] = rec
|
members[name] = rec
|
||||||
// Structural change: bump layout version for PIC invalidation
|
// Structural change: bump layout version for PIC invalidation
|
||||||
@ -682,13 +869,52 @@ open class ObjClass(
|
|||||||
writeVisibility: Visibility? = null,
|
writeVisibility: Visibility? = null,
|
||||||
pos: Pos = Pos.builtIn,
|
pos: Pos = Pos.builtIn,
|
||||||
isTransient: Boolean = false,
|
isTransient: Boolean = false,
|
||||||
type: ObjRecord.Type = ObjRecord.Type.Field
|
type: ObjRecord.Type = ObjRecord.Type.Field,
|
||||||
|
fieldId: Int? = null,
|
||||||
|
methodId: Int? = null
|
||||||
): ObjRecord {
|
): ObjRecord {
|
||||||
initClassScope()
|
initClassScope()
|
||||||
val existing = classScope!!.objects[name]
|
val existing = classScope!!.objects[name]
|
||||||
if (existing != null)
|
if (existing != null)
|
||||||
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
||||||
val rec = classScope!!.addItem(name, isMutable, initialValue, visibility, writeVisibility, recordType = type, isTransient = isTransient)
|
val effectiveFieldId = if (type == ObjRecord.Type.Field || type == ObjRecord.Type.ConstructorField) {
|
||||||
|
fieldId ?: fieldIdMap[name]?.let { it } ?: run {
|
||||||
|
fieldIdMap[name] = nextFieldId
|
||||||
|
nextFieldId++
|
||||||
|
fieldIdMap[name]!!
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fieldId
|
||||||
|
}
|
||||||
|
if (type == ObjRecord.Type.Fun ||
|
||||||
|
type == ObjRecord.Type.Property ||
|
||||||
|
type == ObjRecord.Type.Delegated
|
||||||
|
) {
|
||||||
|
ensureMethodIdSeeded()
|
||||||
|
}
|
||||||
|
val effectiveMethodId = if (type == ObjRecord.Type.Fun ||
|
||||||
|
type == ObjRecord.Type.Property ||
|
||||||
|
type == ObjRecord.Type.Delegated
|
||||||
|
) {
|
||||||
|
methodId ?: methodIdMap[name]?.let { it } ?: run {
|
||||||
|
methodIdMap[name] = nextMethodId
|
||||||
|
nextMethodId++
|
||||||
|
methodIdMap[name]!!
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
methodId
|
||||||
|
}
|
||||||
|
val rec = classScope!!.addItem(
|
||||||
|
name,
|
||||||
|
isMutable,
|
||||||
|
initialValue,
|
||||||
|
visibility,
|
||||||
|
writeVisibility,
|
||||||
|
recordType = type,
|
||||||
|
isTransient = isTransient,
|
||||||
|
fieldId = effectiveFieldId,
|
||||||
|
methodId = effectiveMethodId
|
||||||
|
)
|
||||||
// Structural change: bump layout version for PIC invalidation
|
// Structural change: bump layout version for PIC invalidation
|
||||||
layoutVersion += 1
|
layoutVersion += 1
|
||||||
return rec
|
return rec
|
||||||
@ -704,13 +930,15 @@ open class ObjClass(
|
|||||||
isClosed: Boolean = false,
|
isClosed: Boolean = false,
|
||||||
isOverride: Boolean = false,
|
isOverride: Boolean = false,
|
||||||
pos: Pos = Pos.builtIn,
|
pos: Pos = Pos.builtIn,
|
||||||
|
methodId: Int? = null,
|
||||||
code: (suspend Scope.() -> Obj)? = null
|
code: (suspend Scope.() -> Obj)? = null
|
||||||
) {
|
) {
|
||||||
val stmt = code?.let { statement { it() } } ?: ObjNull
|
val stmt = code?.let { statement { it() } } ?: ObjNull
|
||||||
createField(
|
createField(
|
||||||
name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass,
|
name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass,
|
||||||
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
|
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
|
||||||
type = ObjRecord.Type.Fun
|
type = ObjRecord.Type.Fun,
|
||||||
|
methodId = methodId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -727,7 +955,8 @@ open class ObjClass(
|
|||||||
isClosed: Boolean = false,
|
isClosed: Boolean = false,
|
||||||
isOverride: Boolean = false,
|
isOverride: Boolean = false,
|
||||||
pos: Pos = Pos.builtIn,
|
pos: Pos = Pos.builtIn,
|
||||||
prop: ObjProperty? = null
|
prop: ObjProperty? = null,
|
||||||
|
methodId: Int? = null
|
||||||
) {
|
) {
|
||||||
val g = getter?.let { statement { it() } }
|
val g = getter?.let { statement { it() } }
|
||||||
val s = setter?.let { statement { it(requiredArg(0)); ObjVoid } }
|
val s = setter?.let { statement { it(requiredArg(0)); ObjVoid } }
|
||||||
@ -735,7 +964,8 @@ open class ObjClass(
|
|||||||
createField(
|
createField(
|
||||||
name, finalProp, false, visibility, writeVisibility, pos, declaringClass,
|
name, finalProp, false, visibility, writeVisibility, pos, declaringClass,
|
||||||
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
|
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
|
||||||
type = ObjRecord.Type.Property
|
type = ObjRecord.Type.Property,
|
||||||
|
methodId = methodId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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.Arguments
|
||||||
|
import net.sergeych.lyng.Scope
|
||||||
|
|
||||||
|
class ObjExtensionMethodCallable(
|
||||||
|
private val name: String,
|
||||||
|
private val target: Obj,
|
||||||
|
private val declaringClass: ObjClass? = null
|
||||||
|
) : Obj() {
|
||||||
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
|
val args = scope.args
|
||||||
|
if (args.isEmpty()) scope.raiseError("extension call $name requires receiver")
|
||||||
|
val receiver = args.first()
|
||||||
|
val rest = if (args.size <= 1) {
|
||||||
|
Arguments.EMPTY
|
||||||
|
} else {
|
||||||
|
Arguments(args.list.subList(1, args.size), args.tailBlockMode, args.named)
|
||||||
|
}
|
||||||
|
return target.invoke(scope, receiver, rest, declaringClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ObjExtensionPropertyGetterCallable(
|
||||||
|
private val name: String,
|
||||||
|
private val property: ObjProperty,
|
||||||
|
private val declaringClass: ObjClass? = null
|
||||||
|
) : Obj() {
|
||||||
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
|
val args = scope.args
|
||||||
|
if (args.isEmpty()) scope.raiseError("extension property $name requires receiver")
|
||||||
|
val receiver = args.first()
|
||||||
|
if (args.size > 1) scope.raiseError("extension property $name getter takes no arguments")
|
||||||
|
return property.callGetter(scope, receiver, declaringClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ObjExtensionPropertySetterCallable(
|
||||||
|
private val name: String,
|
||||||
|
private val property: ObjProperty,
|
||||||
|
private val declaringClass: ObjClass? = null
|
||||||
|
) : Obj() {
|
||||||
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
|
val args = scope.args
|
||||||
|
if (args.size < 2) scope.raiseError("extension property $name setter requires value")
|
||||||
|
val receiver = args[0]
|
||||||
|
val value = args[1]
|
||||||
|
property.callSetter(scope, receiver, value, declaringClass)
|
||||||
|
return ObjVoid
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -81,8 +81,8 @@ private fun createLyngFlowInput(scope: Scope, producer: Statement): ReceiveChann
|
|||||||
} catch (x: ScriptFlowIsNoMoreCollected) {
|
} catch (x: ScriptFlowIsNoMoreCollected) {
|
||||||
// premature flow closing, OK
|
// premature flow closing, OK
|
||||||
} catch (x: Exception) {
|
} catch (x: Exception) {
|
||||||
// Suppress stack traces in background producer to avoid noisy stderr during tests.
|
channel.close(x)
|
||||||
// If needed, consider routing to a logger in the future.
|
return@globalLaunch
|
||||||
}
|
}
|
||||||
channel.close()
|
channel.close()
|
||||||
}
|
}
|
||||||
@ -107,9 +107,7 @@ class ObjFlow(val producer: Statement, val scope: Scope) : Obj() {
|
|||||||
) {
|
) {
|
||||||
val objFlow = thisAs<ObjFlow>()
|
val objFlow = thisAs<ObjFlow>()
|
||||||
ObjFlowIterator(statement {
|
ObjFlowIterator(statement {
|
||||||
objFlow.producer.execute(
|
objFlow.producer.execute(this)
|
||||||
ClosureScope(this, objFlow.scope)
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,6 +135,7 @@ class ObjFlowIterator(val producer: Statement) : Obj() {
|
|||||||
// cold start:
|
// cold start:
|
||||||
if (channel == null) channel = createLyngFlowInput(scope, producer)
|
if (channel == null) channel = createLyngFlowInput(scope, producer)
|
||||||
if (nextItem == null) nextItem = channel!!.receiveCatching()
|
if (nextItem == null) nextItem = channel!!.receiveCatching()
|
||||||
|
nextItem?.exceptionOrNull()?.let { throw it }
|
||||||
return ObjBool(nextItem!!.isSuccess)
|
return ObjBool(nextItem!!.isSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -61,6 +61,14 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
return if (idx >= 0 && idx < methodSlots.size) methodSlots[idx] else null
|
return if (idx >= 0 && idx < methodSlots.size) methodSlots[idx] else null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun fieldRecordForId(fieldId: Int): ObjRecord? {
|
||||||
|
return if (fieldId >= 0 && fieldId < fieldSlots.size) fieldSlots[fieldId] else null
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun methodRecordForId(methodId: Int): ObjRecord? {
|
||||||
|
return if (methodId >= 0 && methodId < methodSlots.size) methodSlots[methodId] else null
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||||
val caller = scope.currentClassCtx
|
val caller = scope.currentClassCtx
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,12 @@ import net.sergeych.lyng.miniast.type
|
|||||||
*/
|
*/
|
||||||
val ObjIterable by lazy {
|
val ObjIterable by lazy {
|
||||||
ObjClass("Iterable").apply {
|
ObjClass("Iterable").apply {
|
||||||
|
addFn(
|
||||||
|
name = "iterator",
|
||||||
|
isAbstract = true,
|
||||||
|
isClosed = false,
|
||||||
|
code = null
|
||||||
|
)
|
||||||
|
|
||||||
addPropertyDoc(
|
addPropertyDoc(
|
||||||
name = "toList",
|
name = "toList",
|
||||||
|
|||||||
@ -38,6 +38,10 @@ data class ObjRecord(
|
|||||||
var delegate: Obj? = null,
|
var delegate: Obj? = null,
|
||||||
/** The receiver object to resolve this member against (for instance fields/methods). */
|
/** The receiver object to resolve this member against (for instance fields/methods). */
|
||||||
var receiver: Obj? = null,
|
var receiver: Obj? = null,
|
||||||
|
val callSignature: net.sergeych.lyng.CallSignature? = null,
|
||||||
|
val memberName: String? = null,
|
||||||
|
val fieldId: Int? = null,
|
||||||
|
val methodId: Int? = null,
|
||||||
) {
|
) {
|
||||||
val effectiveWriteVisibility: Visibility get() = writeVisibility ?: visibility
|
val effectiveWriteVisibility: Visibility get() = writeVisibility ?: visibility
|
||||||
enum class Type(val comparable: Boolean = false,val serializable: Boolean = false) {
|
enum class Type(val comparable: Boolean = false,val serializable: Boolean = false) {
|
||||||
|
|||||||
@ -417,6 +417,11 @@ class CastRef(
|
|||||||
private val isNullable: Boolean,
|
private val isNullable: Boolean,
|
||||||
private val atPos: Pos,
|
private val atPos: Pos,
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
|
internal fun castValueRef(): ObjRef = valueRef
|
||||||
|
internal fun castTypeRef(): ObjRef = typeRef
|
||||||
|
internal fun castIsNullable(): Boolean = isNullable
|
||||||
|
internal fun castPos(): Pos = atPos
|
||||||
|
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val v0 = valueRef.evalValue(scope)
|
val v0 = valueRef.evalValue(scope)
|
||||||
val t = typeRef.evalValue(scope)
|
val t = typeRef.evalValue(scope)
|
||||||
@ -501,107 +506,64 @@ private suspend fun resolveQualifiedThisInstance(scope: Scope, typeName: String)
|
|||||||
class QualifiedThisFieldSlotRef(
|
class QualifiedThisFieldSlotRef(
|
||||||
private val typeName: String,
|
private val typeName: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
private val fieldId: Int?,
|
||||||
|
private val methodId: Int?,
|
||||||
private val isOptional: Boolean
|
private val isOptional: Boolean
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
|
internal fun fieldId(): Int? = fieldId
|
||||||
|
internal fun methodId(): Int? = methodId
|
||||||
|
internal fun receiverTypeName(): String = typeName
|
||||||
|
internal fun optional(): Boolean = isOptional
|
||||||
|
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val (inst, startClass) = resolveQualifiedThisInstance(scope, typeName)
|
val inst = scope.thisVariants.firstOrNull { it.objClass.className == typeName } as? ObjInstance
|
||||||
|
?: scope.raiseClassCastError("No instance of type $typeName found in scope")
|
||||||
if (isOptional && inst == ObjNull) return ObjNull.asMutable
|
if (isOptional && inst == ObjNull) return ObjNull.asMutable
|
||||||
|
fieldId?.let { id ->
|
||||||
if (startClass !== inst.objClass) {
|
val rec = inst.fieldRecordForId(id)
|
||||||
return ObjQualifiedView(inst, startClass).readField(scope, name)
|
if (rec != null && (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
|
||||||
}
|
|
||||||
|
|
||||||
val caller = scope.currentClassCtx
|
|
||||||
if (caller != null) {
|
|
||||||
val mangled = caller.mangledName(name)
|
|
||||||
inst.fieldRecordForKey(mangled)?.let { rec ->
|
|
||||||
if (rec.visibility == Visibility.Private) {
|
|
||||||
return inst.resolveRecord(scope, rec, name, caller)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inst.methodRecordForKey(mangled)?.let { rec ->
|
|
||||||
if (rec.visibility == Visibility.Private) {
|
|
||||||
return inst.resolveRecord(scope, rec, name, caller)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val key = inst.objClass.publicMemberResolution[name] ?: name
|
|
||||||
inst.fieldRecordForKey(key)?.let { rec ->
|
|
||||||
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
|
|
||||||
return rec
|
return rec
|
||||||
}
|
}
|
||||||
inst.methodRecordForKey(key)?.let { rec ->
|
}
|
||||||
if (!rec.isAbstract) {
|
methodId?.let { id ->
|
||||||
val decl = rec.declaringClass ?: inst.objClass.findDeclaringClassOf(name) ?: inst.objClass
|
val rec = inst.methodRecordForId(id)
|
||||||
return inst.resolveRecord(scope, rec, name, decl)
|
if (rec != null && !rec.isAbstract) {
|
||||||
|
val decl = rec.declaringClass ?: inst.objClass
|
||||||
|
return inst.resolveRecord(scope, rec, rec.memberName ?: name, decl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
scope.raiseSymbolNotFound(name)
|
||||||
return inst.readField(scope, name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||||
val (inst, startClass) = resolveQualifiedThisInstance(scope, typeName)
|
val inst = scope.thisVariants.firstOrNull { it.objClass.className == typeName } as? ObjInstance
|
||||||
|
?: scope.raiseClassCastError("No instance of type $typeName found in scope")
|
||||||
if (isOptional && inst == ObjNull) return
|
if (isOptional && inst == ObjNull) return
|
||||||
|
fieldId?.let { id ->
|
||||||
if (startClass !== inst.objClass) {
|
val rec = inst.fieldRecordForId(id)
|
||||||
ObjQualifiedView(inst, startClass).writeField(scope, name, newValue)
|
if (rec != null) {
|
||||||
return
|
assignToRecord(scope, rec, newValue)
|
||||||
}
|
|
||||||
|
|
||||||
val caller = scope.currentClassCtx
|
|
||||||
if (caller != null) {
|
|
||||||
val mangled = caller.mangledName(name)
|
|
||||||
inst.fieldRecordForKey(mangled)?.let { rec ->
|
|
||||||
if (rec.visibility == Visibility.Private) {
|
|
||||||
writeDirectOrFallback(scope, inst, rec, name, newValue, caller)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inst.methodRecordForKey(mangled)?.let { rec ->
|
methodId?.let { id ->
|
||||||
if (rec.visibility == Visibility.Private &&
|
val rec = inst.methodRecordForId(id)
|
||||||
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
|
if (rec != null) {
|
||||||
inst.writeField(scope, name, newValue)
|
scope.assign(rec, rec.memberName ?: name, newValue)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
scope.raiseSymbolNotFound(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
val key = inst.objClass.publicMemberResolution[name] ?: name
|
private suspend fun assignToRecord(scope: Scope, rec: ObjRecord, newValue: Obj) {
|
||||||
inst.fieldRecordForKey(key)?.let { rec ->
|
|
||||||
val decl = rec.declaringClass ?: inst.objClass.findDeclaringClassOf(name)
|
|
||||||
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) {
|
|
||||||
writeDirectOrFallback(scope, inst, rec, name, newValue, decl)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inst.methodRecordForKey(key)?.let { rec ->
|
|
||||||
if (rec.effectiveWriteVisibility == Visibility.Public &&
|
|
||||||
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
|
|
||||||
inst.writeField(scope, name, newValue)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inst.writeField(scope, name, newValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun writeDirectOrFallback(
|
|
||||||
scope: Scope,
|
|
||||||
inst: ObjInstance,
|
|
||||||
rec: ObjRecord,
|
|
||||||
name: String,
|
|
||||||
newValue: Obj,
|
|
||||||
decl: ObjClass?
|
|
||||||
) {
|
|
||||||
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
|
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
|
||||||
if (!rec.isMutable && rec.value !== ObjUnset) {
|
if (!rec.isMutable && rec.value !== ObjUnset) {
|
||||||
ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
ObjIllegalAssignmentException(scope, "can't reassign val ${rec.memberName ?: name}").raise()
|
||||||
}
|
}
|
||||||
if (rec.value.assign(scope, newValue) == null) rec.value = newValue
|
if (rec.value.assign(scope, newValue) == null) rec.value = newValue
|
||||||
} else {
|
} else {
|
||||||
inst.writeField(scope, name, newValue)
|
scope.assign(rec, rec.memberName ?: name, newValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -613,53 +575,38 @@ class QualifiedThisFieldSlotRef(
|
|||||||
class QualifiedThisMethodSlotCallRef(
|
class QualifiedThisMethodSlotCallRef(
|
||||||
private val typeName: String,
|
private val typeName: String,
|
||||||
private val name: String,
|
private val name: String,
|
||||||
|
private val methodId: Int?,
|
||||||
private val args: List<ParsedArgument>,
|
private val args: List<ParsedArgument>,
|
||||||
private val tailBlock: Boolean,
|
private val tailBlock: Boolean,
|
||||||
private val isOptional: Boolean
|
private val isOptional: Boolean
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
|
internal fun receiverTypeName(): String = typeName
|
||||||
|
internal fun methodName(): String = name
|
||||||
|
internal fun methodId(): Int? = methodId
|
||||||
|
internal fun arguments(): List<ParsedArgument> = args
|
||||||
|
internal fun hasTailBlock(): Boolean = tailBlock
|
||||||
|
internal fun optionalInvoke(): Boolean = isOptional
|
||||||
|
|
||||||
override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly
|
override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly
|
||||||
|
|
||||||
override suspend fun evalValue(scope: Scope): Obj {
|
override suspend fun evalValue(scope: Scope): Obj {
|
||||||
val (inst, startClass) = resolveQualifiedThisInstance(scope, typeName)
|
val inst = scope.thisVariants.firstOrNull { it.objClass.className == typeName } as? ObjInstance
|
||||||
|
?: scope.raiseClassCastError("No instance of type $typeName found in scope")
|
||||||
if (isOptional && inst == ObjNull) return ObjNull
|
if (isOptional && inst == ObjNull) return ObjNull
|
||||||
val callArgs = args.toArguments(scope, tailBlock)
|
val callArgs = args.toArguments(scope, tailBlock)
|
||||||
|
val id = methodId ?: scope.raiseSymbolNotFound(name)
|
||||||
if (startClass !== inst.objClass) {
|
val rec = inst.methodRecordForId(id) ?: scope.raiseSymbolNotFound(name)
|
||||||
return ObjQualifiedView(inst, startClass).invokeInstanceMethod(scope, name, callArgs, null)
|
val decl = rec.declaringClass ?: inst.objClass
|
||||||
|
return when (rec.type) {
|
||||||
|
ObjRecord.Type.Property -> {
|
||||||
|
if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(scope, inst, decl)
|
||||||
|
else scope.raiseError("property $name cannot be called with arguments")
|
||||||
}
|
}
|
||||||
|
ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> rec.value.invoke(inst.instanceScope, inst, callArgs, decl)
|
||||||
val caller = scope.currentClassCtx
|
else -> scope.raiseError("member $name is not callable")
|
||||||
if (caller != null) {
|
|
||||||
val mangled = caller.mangledName(name)
|
|
||||||
inst.methodRecordForKey(mangled)?.let { rec ->
|
|
||||||
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
|
|
||||||
if (rec.type == ObjRecord.Type.Property) {
|
|
||||||
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, inst, caller)
|
|
||||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
|
||||||
return rec.value.invoke(inst.instanceScope, inst, callArgs, caller)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val key = inst.objClass.publicMemberResolution[name] ?: name
|
|
||||||
inst.methodRecordForKey(key)?.let { rec ->
|
|
||||||
if (!rec.isAbstract) {
|
|
||||||
val decl = rec.declaringClass ?: inst.objClass.findDeclaringClassOf(name) ?: inst.objClass
|
|
||||||
val effectiveCaller = caller ?: if (scope.thisObj === inst) inst.objClass else null
|
|
||||||
if (!canAccessMember(rec.visibility, decl, effectiveCaller, name))
|
|
||||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke method $name (declared in ${decl.className})"))
|
|
||||||
if (rec.type == ObjRecord.Type.Property) {
|
|
||||||
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, inst, decl)
|
|
||||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
|
||||||
return rec.value.invoke(inst.instanceScope, inst, callArgs, decl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return inst.invokeInstanceMethod(scope, name, callArgs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Assignment compound op: target op= value */
|
/** Assignment compound op: target op= value */
|
||||||
class AssignOpRef(
|
class AssignOpRef(
|
||||||
@ -763,6 +710,9 @@ class ElvisRef(internal val left: ObjRef, internal val right: ObjRef) : ObjRef {
|
|||||||
|
|
||||||
/** Logical OR with short-circuit: a || b */
|
/** Logical OR with short-circuit: a || b */
|
||||||
class LogicalOrRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
class LogicalOrRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
||||||
|
internal fun left(): ObjRef = left
|
||||||
|
internal fun right(): ObjRef = right
|
||||||
|
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
return evalValue(scope).asReadonly
|
return evalValue(scope).asReadonly
|
||||||
}
|
}
|
||||||
@ -782,6 +732,9 @@ class LogicalOrRef(private val left: ObjRef, private val right: ObjRef) : ObjRef
|
|||||||
|
|
||||||
/** Logical AND with short-circuit: a && b */
|
/** Logical AND with short-circuit: a && b */
|
||||||
class LogicalAndRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
class LogicalAndRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
||||||
|
internal fun left(): ObjRef = left
|
||||||
|
internal fun right(): ObjRef = right
|
||||||
|
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
return evalValue(scope).asReadonly
|
return evalValue(scope).asReadonly
|
||||||
}
|
}
|
||||||
@ -1229,103 +1182,59 @@ class FieldRef(
|
|||||||
*/
|
*/
|
||||||
class ThisFieldSlotRef(
|
class ThisFieldSlotRef(
|
||||||
val name: String,
|
val name: String,
|
||||||
|
private val fieldId: Int?,
|
||||||
|
private val methodId: Int?,
|
||||||
private val isOptional: Boolean
|
private val isOptional: Boolean
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
|
internal fun fieldId(): Int? = fieldId
|
||||||
|
internal fun methodId(): Int? = methodId
|
||||||
|
internal fun optional(): Boolean = isOptional
|
||||||
|
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val th = scope.thisObj
|
val th = scope.thisObj
|
||||||
if (th == ObjNull && isOptional) return ObjNull.asMutable
|
if (th == ObjNull && isOptional) return ObjNull.asMutable
|
||||||
if (th !is ObjInstance) return th.readField(scope, name)
|
val inst = th as? ObjInstance ?: scope.raiseClassCastError("member access on non-instance")
|
||||||
|
val field = fieldId?.let { inst.fieldRecordForId(it) }
|
||||||
val caller = scope.currentClassCtx
|
if (field != null && (field.type == ObjRecord.Type.Field || field.type == ObjRecord.Type.ConstructorField) && !field.isAbstract) {
|
||||||
if (caller != null) {
|
return field
|
||||||
val mangled = caller.mangledName(name)
|
|
||||||
th.fieldRecordForKey(mangled)?.let { rec ->
|
|
||||||
if (rec.visibility == Visibility.Private) {
|
|
||||||
return th.resolveRecord(scope, rec, name, caller)
|
|
||||||
}
|
}
|
||||||
|
val method = methodId?.let { inst.methodRecordForId(it) }
|
||||||
|
if (method != null && !method.isAbstract) {
|
||||||
|
val decl = method.declaringClass ?: inst.objClass
|
||||||
|
return inst.resolveRecord(scope, method, method.memberName ?: name, decl)
|
||||||
}
|
}
|
||||||
th.methodRecordForKey(mangled)?.let { rec ->
|
scope.raiseSymbolNotFound(name)
|
||||||
if (rec.visibility == Visibility.Private) {
|
|
||||||
return th.resolveRecord(scope, rec, name, caller)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val key = th.objClass.publicMemberResolution[name] ?: name
|
|
||||||
th.fieldRecordForKey(key)?.let { rec ->
|
|
||||||
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
|
|
||||||
return rec
|
|
||||||
}
|
|
||||||
th.methodRecordForKey(key)?.let { rec ->
|
|
||||||
if (!rec.isAbstract) {
|
|
||||||
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name) ?: th.objClass
|
|
||||||
return th.resolveRecord(scope, rec, name, decl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return th.readField(scope, name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||||
val th = scope.thisObj
|
val th = scope.thisObj
|
||||||
if (th == ObjNull && isOptional) return
|
if (th == ObjNull && isOptional) return
|
||||||
if (th !is ObjInstance) {
|
val inst = th as? ObjInstance ?: scope.raiseClassCastError("member access on non-instance")
|
||||||
th.writeField(scope, name, newValue)
|
val field = fieldId?.let { inst.fieldRecordForId(it) }
|
||||||
|
if (field != null) {
|
||||||
|
assignToRecord(scope, field, newValue)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
val method = methodId?.let { inst.methodRecordForId(it) }
|
||||||
|
if (method != null) {
|
||||||
|
scope.assign(method, method.memberName ?: name, newValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scope.raiseSymbolNotFound(name)
|
||||||
|
}
|
||||||
|
|
||||||
val caller = scope.currentClassCtx
|
private suspend fun assignToRecord(
|
||||||
if (caller != null) {
|
|
||||||
val mangled = caller.mangledName(name)
|
|
||||||
th.fieldRecordForKey(mangled)?.let { rec ->
|
|
||||||
if (rec.visibility == Visibility.Private) {
|
|
||||||
writeDirectOrFallback(scope, th, rec, name, newValue, caller)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
th.methodRecordForKey(mangled)?.let { rec ->
|
|
||||||
if (rec.visibility == Visibility.Private &&
|
|
||||||
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
|
|
||||||
th.writeField(scope, name, newValue)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val key = th.objClass.publicMemberResolution[name] ?: name
|
|
||||||
th.fieldRecordForKey(key)?.let { rec ->
|
|
||||||
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name)
|
|
||||||
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) {
|
|
||||||
writeDirectOrFallback(scope, th, rec, name, newValue, decl)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
th.methodRecordForKey(key)?.let { rec ->
|
|
||||||
if (rec.effectiveWriteVisibility == Visibility.Public &&
|
|
||||||
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
|
|
||||||
th.writeField(scope, name, newValue)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
th.writeField(scope, name, newValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun writeDirectOrFallback(
|
|
||||||
scope: Scope,
|
scope: Scope,
|
||||||
inst: ObjInstance,
|
|
||||||
rec: ObjRecord,
|
rec: ObjRecord,
|
||||||
name: String,
|
newValue: Obj
|
||||||
newValue: Obj,
|
|
||||||
decl: ObjClass?
|
|
||||||
) {
|
) {
|
||||||
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
|
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
|
||||||
if (!rec.isMutable && rec.value !== ObjUnset) {
|
if (!rec.isMutable && rec.value !== ObjUnset) {
|
||||||
ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
ObjIllegalAssignmentException(scope, "can't reassign val ${rec.memberName ?: name}").raise()
|
||||||
}
|
}
|
||||||
if (rec.value.assign(scope, newValue) == null) rec.value = newValue
|
if (rec.value.assign(scope, newValue) == null) rec.value = newValue
|
||||||
} else {
|
} else {
|
||||||
inst.writeField(scope, name, newValue)
|
scope.assign(rec, rec.memberName ?: name, newValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1863,11 +1772,13 @@ class MethodCallRef(
|
|||||||
*/
|
*/
|
||||||
class ThisMethodSlotCallRef(
|
class ThisMethodSlotCallRef(
|
||||||
private val name: String,
|
private val name: String,
|
||||||
|
private val methodId: Int?,
|
||||||
private val args: List<ParsedArgument>,
|
private val args: List<ParsedArgument>,
|
||||||
private val tailBlock: Boolean,
|
private val tailBlock: Boolean,
|
||||||
private val isOptional: Boolean
|
private val isOptional: Boolean
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
internal fun methodName(): String = name
|
internal fun methodName(): String = name
|
||||||
|
internal fun methodId(): Int? = methodId
|
||||||
internal fun arguments(): List<ParsedArgument> = args
|
internal fun arguments(): List<ParsedArgument> = args
|
||||||
internal fun hasTailBlock(): Boolean = tailBlock
|
internal fun hasTailBlock(): Boolean = tailBlock
|
||||||
internal fun optionalInvoke(): Boolean = isOptional
|
internal fun optionalInvoke(): Boolean = isOptional
|
||||||
@ -1879,40 +1790,20 @@ class ThisMethodSlotCallRef(
|
|||||||
if (base == ObjNull && isOptional) return ObjNull
|
if (base == ObjNull && isOptional) return ObjNull
|
||||||
val callArgs = args.toArguments(scope, tailBlock)
|
val callArgs = args.toArguments(scope, tailBlock)
|
||||||
if (base !is ObjInstance) return base.invokeInstanceMethod(scope, name, callArgs)
|
if (base !is ObjInstance) return base.invokeInstanceMethod(scope, name, callArgs)
|
||||||
|
val id = methodId ?: scope.raiseSymbolNotFound(name)
|
||||||
val caller = scope.currentClassCtx
|
val rec = base.methodRecordForId(id) ?: scope.raiseSymbolNotFound(name)
|
||||||
if (caller != null) {
|
val decl = rec.declaringClass ?: base.objClass
|
||||||
val mangled = caller.mangledName(name)
|
return when (rec.type) {
|
||||||
base.methodRecordForKey(mangled)?.let { rec ->
|
ObjRecord.Type.Property -> {
|
||||||
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
|
if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(scope, base, decl)
|
||||||
if (rec.type == ObjRecord.Type.Property) {
|
else scope.raiseError("property $name cannot be called with arguments")
|
||||||
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, base, caller)
|
|
||||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
|
||||||
return rec.value.invoke(base.instanceScope, base, callArgs, caller)
|
|
||||||
}
|
}
|
||||||
|
ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> rec.value.invoke(base.instanceScope, base, callArgs, decl)
|
||||||
|
else -> scope.raiseError("member $name is not callable")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val key = base.objClass.publicMemberResolution[name] ?: name
|
|
||||||
base.methodRecordForKey(key)?.let { rec ->
|
|
||||||
if (!rec.isAbstract) {
|
|
||||||
val decl = rec.declaringClass ?: base.objClass.findDeclaringClassOf(name) ?: base.objClass
|
|
||||||
val effectiveCaller = caller ?: if (scope.thisObj === base) base.objClass else null
|
|
||||||
if (!canAccessMember(rec.visibility, decl, effectiveCaller, name))
|
|
||||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke method $name (declared in ${decl.className})"))
|
|
||||||
if (rec.type == ObjRecord.Type.Property) {
|
|
||||||
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, base, decl)
|
|
||||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
|
||||||
return rec.value.invoke(base.instanceScope, base, callArgs, decl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.invokeInstanceMethod(scope, name, callArgs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reference to a local/visible variable by name (Phase A: scope lookup).
|
* Reference to a local/visible variable by name (Phase A: scope lookup).
|
||||||
*/
|
*/
|
||||||
@ -2258,23 +2149,12 @@ class FastLocalVarRef(
|
|||||||
*/
|
*/
|
||||||
class ImplicitThisMemberRef(
|
class ImplicitThisMemberRef(
|
||||||
val name: String,
|
val name: String,
|
||||||
val atPos: Pos
|
val atPos: Pos,
|
||||||
|
internal val fieldId: Int?,
|
||||||
|
internal val methodId: Int?,
|
||||||
|
private val preferredThisTypeName: String? = null
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
private fun resolveInstanceFieldRecord(th: ObjInstance, caller: ObjClass?): ObjRecord? {
|
internal fun preferredThisTypeName(): String? = preferredThisTypeName
|
||||||
if (caller == null) return null
|
|
||||||
for (cls in th.objClass.mro) {
|
|
||||||
if (cls.className == "Obj") break
|
|
||||||
val rec = cls.members[name] ?: continue
|
|
||||||
if (rec.isAbstract) continue
|
|
||||||
val decl = rec.declaringClass ?: cls
|
|
||||||
if (!canAccessMember(rec.visibility, decl, caller, name)) continue
|
|
||||||
val key = decl.mangledName(name)
|
|
||||||
th.fieldRecordForKey(key)?.let { return it }
|
|
||||||
th.instanceScope.objects[key]?.let { return it }
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun forEachVariable(block: (String) -> Unit) {
|
override fun forEachVariable(block: (String) -> Unit) {
|
||||||
block(name)
|
block(name)
|
||||||
}
|
}
|
||||||
@ -2285,46 +2165,18 @@ class ImplicitThisMemberRef(
|
|||||||
|
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
scope.pos = atPos
|
scope.pos = atPos
|
||||||
val caller = scope.currentClassCtx
|
val th = preferredThisTypeName?.let { typeName ->
|
||||||
val th = scope.thisObj
|
scope.thisVariants.firstOrNull { it.objClass.className == typeName }
|
||||||
|
} ?: scope.thisObj
|
||||||
if (th is ObjClass) {
|
val inst = th as? ObjInstance
|
||||||
return th.readField(scope, name)
|
val field = fieldId?.let { inst?.fieldRecordForId(it) ?: th.objClass.fieldRecordForId(it) }
|
||||||
}
|
if (field != null && (field.type == ObjRecord.Type.Field || field.type == ObjRecord.Type.ConstructorField) && !field.isAbstract) {
|
||||||
if (th != null && th !is ObjInstance) {
|
return field
|
||||||
return th.readField(scope, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// member slots on this instance
|
|
||||||
if (th is ObjInstance) {
|
|
||||||
// private member access for current class context
|
|
||||||
caller?.let { c ->
|
|
||||||
val mangled = c.mangledName(name)
|
|
||||||
th.fieldRecordForKey(mangled)?.let { rec ->
|
|
||||||
if (rec.visibility == Visibility.Private) {
|
|
||||||
return th.resolveRecord(scope, rec, name, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
th.methodRecordForKey(mangled)?.let { rec ->
|
|
||||||
if (rec.visibility == Visibility.Private) {
|
|
||||||
return th.resolveRecord(scope, rec, name, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveInstanceFieldRecord(th, caller)?.let { return it }
|
|
||||||
|
|
||||||
val key = th.objClass.publicMemberResolution[name] ?: name
|
|
||||||
th.fieldRecordForKey(key)?.let { rec ->
|
|
||||||
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
|
|
||||||
return rec
|
|
||||||
}
|
|
||||||
th.methodRecordForKey(key)?.let { rec ->
|
|
||||||
if (!rec.isAbstract) {
|
|
||||||
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name) ?: th.objClass
|
|
||||||
return th.resolveRecord(scope, rec, name, decl)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
val method = methodId?.let { inst?.methodRecordForId(it) ?: th.objClass.methodRecordForId(it) }
|
||||||
|
if (method != null && !method.isAbstract) {
|
||||||
|
val decl = method.declaringClass ?: th.objClass
|
||||||
|
return th.resolveRecord(scope, method, method.memberName ?: name, decl)
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.raiseSymbolNotFound(name)
|
scope.raiseSymbolNotFound(name)
|
||||||
@ -2337,49 +2189,21 @@ class ImplicitThisMemberRef(
|
|||||||
|
|
||||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||||
scope.pos = atPos
|
scope.pos = atPos
|
||||||
val caller = scope.currentClassCtx
|
val th = preferredThisTypeName?.let { typeName ->
|
||||||
val th = scope.thisObj
|
scope.thisVariants.firstOrNull { it.objClass.className == typeName }
|
||||||
|
} ?: scope.thisObj
|
||||||
if (th is ObjClass) {
|
val inst = th as? ObjInstance
|
||||||
th.writeField(scope, name, newValue)
|
val field = fieldId?.let { inst?.fieldRecordForId(it) ?: th.objClass.fieldRecordForId(it) }
|
||||||
|
if (field != null) {
|
||||||
|
scope.assign(field, field.memberName ?: name, newValue)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (th != null && th !is ObjInstance) {
|
val method = methodId?.let { inst?.methodRecordForId(it) ?: th.objClass.methodRecordForId(it) }
|
||||||
th.writeField(scope, name, newValue)
|
if (method != null) {
|
||||||
|
scope.assign(method, method.memberName ?: name, newValue)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// member slots on this instance
|
|
||||||
if (th is ObjInstance) {
|
|
||||||
val key = th.objClass.publicMemberResolution[name] ?: name
|
|
||||||
th.fieldRecordForKey(key)?.let { rec ->
|
|
||||||
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name)
|
|
||||||
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) {
|
|
||||||
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
|
|
||||||
if (!rec.isMutable && rec.value !== ObjUnset) {
|
|
||||||
ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
|
||||||
}
|
|
||||||
if (rec.value.assign(scope, newValue) == null) rec.value = newValue
|
|
||||||
} else {
|
|
||||||
th.writeField(scope, name, newValue)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
th.methodRecordForKey(key)?.let { rec ->
|
|
||||||
if (rec.effectiveWriteVisibility == Visibility.Public &&
|
|
||||||
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
|
|
||||||
th.writeField(scope, name, newValue)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveInstanceFieldRecord(th, caller)?.let { rec ->
|
|
||||||
scope.assign(rec, name, newValue)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scope.raiseSymbolNotFound(name)
|
scope.raiseSymbolNotFound(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2390,16 +2214,19 @@ class ImplicitThisMemberRef(
|
|||||||
*/
|
*/
|
||||||
class ImplicitThisMethodCallRef(
|
class ImplicitThisMethodCallRef(
|
||||||
private val name: String,
|
private val name: String,
|
||||||
|
private val methodId: Int?,
|
||||||
private val args: List<ParsedArgument>,
|
private val args: List<ParsedArgument>,
|
||||||
private val tailBlock: Boolean,
|
private val tailBlock: Boolean,
|
||||||
private val isOptional: Boolean,
|
private val isOptional: Boolean,
|
||||||
private val atPos: Pos
|
private val atPos: Pos,
|
||||||
|
private val preferredThisTypeName: String? = null
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
private val memberRef = ImplicitThisMemberRef(name, atPos)
|
|
||||||
internal fun methodName(): String = name
|
internal fun methodName(): String = name
|
||||||
internal fun arguments(): List<ParsedArgument> = args
|
internal fun arguments(): List<ParsedArgument> = args
|
||||||
internal fun hasTailBlock(): Boolean = tailBlock
|
internal fun hasTailBlock(): Boolean = tailBlock
|
||||||
internal fun optionalInvoke(): Boolean = isOptional
|
internal fun optionalInvoke(): Boolean = isOptional
|
||||||
|
internal fun preferredThisTypeName(): String? = preferredThisTypeName
|
||||||
|
internal fun slotId(): Int? = methodId
|
||||||
|
|
||||||
override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly
|
override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly
|
||||||
|
|
||||||
@ -2419,7 +2246,25 @@ class ImplicitThisMethodCallRef(
|
|||||||
callee.callOn(scope.createChildScope(scope.pos, callArgs))
|
callee.callOn(scope.createChildScope(scope.pos, callArgs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return scope.thisObj.invokeInstanceMethod(scope, name, callArgs)
|
val receiver = preferredThisTypeName?.let { typeName ->
|
||||||
|
scope.thisVariants.firstOrNull { it.objClass.className == typeName }
|
||||||
|
} ?: scope.thisObj
|
||||||
|
if (receiver == ObjNull && isOptional) return ObjNull
|
||||||
|
val inst = receiver as? ObjInstance ?: return receiver.invokeInstanceMethod(scope, name, callArgs)
|
||||||
|
if (methodId == null) {
|
||||||
|
return inst.invokeInstanceMethod(scope, name, callArgs)
|
||||||
|
}
|
||||||
|
val id = methodId
|
||||||
|
val rec = inst.methodRecordForId(id) ?: scope.raiseSymbolNotFound(name)
|
||||||
|
val decl = rec.declaringClass ?: inst.objClass
|
||||||
|
return when (rec.type) {
|
||||||
|
ObjRecord.Type.Property -> {
|
||||||
|
if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(scope, inst, decl)
|
||||||
|
else scope.raiseError("property $name cannot be called with arguments")
|
||||||
|
}
|
||||||
|
ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> rec.value.invoke(inst.instanceScope, inst, callArgs, decl)
|
||||||
|
else -> scope.raiseError("member $name is not callable")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2598,6 +2443,8 @@ sealed class MapLiteralEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class MapLiteralRef(private val entries: List<MapLiteralEntry>) : ObjRef {
|
class MapLiteralRef(private val entries: List<MapLiteralEntry>) : ObjRef {
|
||||||
|
internal fun entries(): List<MapLiteralEntry> = entries
|
||||||
|
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
return evalValue(scope).asReadonly
|
return evalValue(scope).asReadonly
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,8 +18,10 @@
|
|||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
import net.sergeych.lyng.PerfFlags
|
import net.sergeych.lyng.PerfFlags
|
||||||
|
import net.sergeych.lyng.Pos
|
||||||
import net.sergeych.lyng.RegexCache
|
import net.sergeych.lyng.RegexCache
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lyng.Statement
|
||||||
import net.sergeych.lyng.miniast.*
|
import net.sergeych.lyng.miniast.*
|
||||||
|
|
||||||
class ObjRegex(val regex: Regex) : Obj() {
|
class ObjRegex(val regex: Regex) : Obj() {
|
||||||
@ -72,6 +74,19 @@ class ObjRegex(val regex: Regex) : Obj() {
|
|||||||
val s = requireOnlyArg<ObjString>().value
|
val s = requireOnlyArg<ObjString>().value
|
||||||
ObjList(thisAs<ObjRegex>().regex.findAll(s).map { ObjRegexMatch(it) }.toMutableList())
|
ObjList(thisAs<ObjRegex>().regex.findAll(s).map { ObjRegexMatch(it) }.toMutableList())
|
||||||
}
|
}
|
||||||
|
createField(
|
||||||
|
name = "operatorMatch",
|
||||||
|
initialValue = object : Statement() {
|
||||||
|
override val pos: Pos = Pos.builtIn
|
||||||
|
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
val other = scope.args.firstAndOnly(pos)
|
||||||
|
val targetScope = scope.parent ?: scope
|
||||||
|
return (scope.thisObj as ObjRegex).operatorMatch(targetScope, other)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
type = ObjRecord.Type.Fun
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,8 +22,10 @@ import kotlinx.serialization.Serializable
|
|||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
import kotlinx.serialization.json.JsonPrimitive
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
import net.sergeych.lyng.PerfFlags
|
import net.sergeych.lyng.PerfFlags
|
||||||
|
import net.sergeych.lyng.Pos
|
||||||
import net.sergeych.lyng.RegexCache
|
import net.sergeych.lyng.RegexCache
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lyng.Statement
|
||||||
import net.sergeych.lyng.miniast.*
|
import net.sergeych.lyng.miniast.*
|
||||||
import net.sergeych.lynon.LynonDecoder
|
import net.sergeych.lynon.LynonDecoder
|
||||||
import net.sergeych.lynon.LynonEncoder
|
import net.sergeych.lynon.LynonEncoder
|
||||||
@ -338,6 +340,36 @@ data class ObjString(val value: String) : Obj() {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
createField(
|
||||||
|
name = "re",
|
||||||
|
initialValue = ObjProperty(
|
||||||
|
name = "re",
|
||||||
|
getter = object : Statement() {
|
||||||
|
override val pos: Pos = Pos.builtIn
|
||||||
|
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
val pattern = (scope.thisObj as ObjString).value
|
||||||
|
val re = if (PerfFlags.REGEX_CACHE) RegexCache.get(pattern) else pattern.toRegex()
|
||||||
|
return ObjRegex(re)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setter = null
|
||||||
|
),
|
||||||
|
type = ObjRecord.Type.Property
|
||||||
|
)
|
||||||
|
createField(
|
||||||
|
name = "operatorMatch",
|
||||||
|
initialValue = object : Statement() {
|
||||||
|
override val pos: Pos = Pos.builtIn
|
||||||
|
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
val other = scope.args.firstAndOnly(pos)
|
||||||
|
val targetScope = scope.parent ?: scope
|
||||||
|
return (scope.thisObj as ObjString).operatorMatch(targetScope, other)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
type = ObjRecord.Type.Fun
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,7 +65,7 @@ class CompileTimeResolutionSpecTest {
|
|||||||
val report = dryRun(
|
val report = dryRun(
|
||||||
"""
|
"""
|
||||||
val G = 10
|
val G = 10
|
||||||
fun f(x) = x + G
|
fun f(x=0) = x + G
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
assertTrue(report.errors.isEmpty())
|
assertTrue(report.errors.isEmpty())
|
||||||
@ -162,7 +162,7 @@ class CompileTimeResolutionSpecTest {
|
|||||||
fun parameterShadowingAllowed() = runTest {
|
fun parameterShadowingAllowed() = runTest {
|
||||||
val report = dryRun(
|
val report = dryRun(
|
||||||
"""
|
"""
|
||||||
fun f(a) {
|
fun f(a=0) {
|
||||||
var a = a * 10
|
var a = a * 10
|
||||||
a
|
a
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,11 +31,12 @@ class ParallelLocalScopeTest {
|
|||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
class AtomicCounter {
|
class AtomicCounter {
|
||||||
private val m = Mutex()
|
private val m: Mutex = Mutex()
|
||||||
private var counter = 0
|
private var counter = 0
|
||||||
|
|
||||||
fun increment() {
|
fun increment() {
|
||||||
m.withLock {
|
val mm: Mutex = m
|
||||||
|
mm.withLock {
|
||||||
val a = counter
|
val a = counter
|
||||||
delay(1)
|
delay(1)
|
||||||
counter = a + 1
|
counter = a + 1
|
||||||
|
|||||||
@ -19,10 +19,10 @@ class ScopeCycleRegressionTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ll() { Whatever() }
|
fun ll(): Whatever { Whatever() }
|
||||||
|
|
||||||
fun callTest1() {
|
fun callTest1() {
|
||||||
val l = ll()
|
val l: Whatever = ll()
|
||||||
l.something()
|
l.something()
|
||||||
"ok"
|
"ok"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -68,7 +68,7 @@ class ScriptTest {
|
|||||||
val res = scope.eval(
|
val res = scope.eval(
|
||||||
"""
|
"""
|
||||||
var counter = 0
|
var counter = 0
|
||||||
val d = launch {
|
val d: Deferred = launch {
|
||||||
val c = counter
|
val c = counter
|
||||||
delay(1)
|
delay(1)
|
||||||
counter = c + 1
|
counter = c + 1
|
||||||
@ -85,7 +85,7 @@ class ScriptTest {
|
|||||||
val scope = Script.newScope()
|
val scope = Script.newScope()
|
||||||
val res = scope.eval(
|
val res = scope.eval(
|
||||||
"""
|
"""
|
||||||
val d = launch {
|
val d: Deferred = launch {
|
||||||
delay(1)
|
delay(1)
|
||||||
yield()
|
yield()
|
||||||
}
|
}
|
||||||
@ -1544,7 +1544,7 @@ class ScriptTest {
|
|||||||
|
|
||||||
val prefix = ":"
|
val prefix = ":"
|
||||||
|
|
||||||
class T(text) {
|
class T(text: String) {
|
||||||
fun getText() {
|
fun getText() {
|
||||||
println(text)
|
println(text)
|
||||||
prefix + text + "!"
|
prefix + text + "!"
|
||||||
@ -1571,7 +1571,7 @@ class ScriptTest {
|
|||||||
fun testAppliedScopes() = runTest {
|
fun testAppliedScopes() = runTest {
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
class T(text) {
|
class T(text: String) {
|
||||||
fun getText() {
|
fun getText() {
|
||||||
println(text)
|
println(text)
|
||||||
text + "!"
|
text + "!"
|
||||||
@ -1586,24 +1586,24 @@ class ScriptTest {
|
|||||||
|
|
||||||
t1.apply {
|
t1.apply {
|
||||||
// it must take "text" from class t1:
|
// it must take "text" from class t1:
|
||||||
assertEquals("foo", text)
|
assertEquals("foo", t1.text)
|
||||||
assertEquals( "foo!", getText() )
|
assertEquals( "foo!", t1.getText() )
|
||||||
assertEquals( ":foo!!", {
|
assertEquals( ":foo!!", {
|
||||||
prefix + getText() + "!"
|
prefix + t1.getText() + "!"
|
||||||
}())
|
}())
|
||||||
}
|
}
|
||||||
t2.apply {
|
t2.apply {
|
||||||
assertEquals("bar", text)
|
assertEquals("bar", t2.text)
|
||||||
assertEquals( "bar!", getText() )
|
assertEquals( "bar!", t2.getText() )
|
||||||
assertEquals( ":bar!!", {
|
assertEquals( ":bar!!", {
|
||||||
prefix + getText() + "!"
|
prefix + t2.getText() + "!"
|
||||||
}())
|
}())
|
||||||
}
|
}
|
||||||
// worst case: names clash
|
// worst case: names clash
|
||||||
fun badOne() {
|
fun badOne() {
|
||||||
val prefix = "&"
|
val prefix = "&"
|
||||||
t1.apply {
|
t1.apply {
|
||||||
assertEquals( ":foo!!", prefix + getText() + "!" )
|
assertEquals( "&foo!!", prefix + t1.getText() + "!" )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
badOne()
|
badOne()
|
||||||
@ -1660,8 +1660,8 @@ class ScriptTest {
|
|||||||
fun testIsPrimeSampleBug() = runTest {
|
fun testIsPrimeSampleBug() = runTest {
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
fun naive_is_prime(candidate) {
|
fun naive_is_prime(candidate: Int) {
|
||||||
val x = if( candidate !is Int) candidate.toInt() else candidate
|
val x = candidate
|
||||||
var divisor = 1
|
var divisor = 1
|
||||||
println("start with ",x)
|
println("start with ",x)
|
||||||
while( ++divisor < x/2 && divisor != 2 ) {
|
while( ++divisor < x/2 && divisor != 2 ) {
|
||||||
@ -2171,12 +2171,12 @@ class ScriptTest {
|
|||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
val x = IllegalArgumentException("test")
|
val x = IllegalArgumentException("test")
|
||||||
var caught = null
|
var caught: Exception? = null
|
||||||
try {
|
try {
|
||||||
throw x
|
throw x
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
caught = it
|
caught = it as Exception
|
||||||
}
|
}
|
||||||
assert( caught is IllegalArgumentException )
|
assert( caught is IllegalArgumentException )
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
@ -2193,11 +2193,12 @@ class ScriptTest {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
catch(e) {
|
catch(e) {
|
||||||
println(e)
|
val ex = e as Exception
|
||||||
println(e::class)
|
println(ex)
|
||||||
println(e.message)
|
println(ex::class)
|
||||||
|
println(ex.message)
|
||||||
println("--------------")
|
println("--------------")
|
||||||
e.message
|
ex.message
|
||||||
}
|
}
|
||||||
println(m)
|
println(m)
|
||||||
assert( m == "test" )
|
assert( m == "test" )
|
||||||
@ -2208,24 +2209,20 @@ class ScriptTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testTryFinally() = runTest {
|
fun testTryFinally() = runTest {
|
||||||
val c = Scope()
|
val c = Scope()
|
||||||
assertFails {
|
val res = c.eval(
|
||||||
c.eval(
|
|
||||||
"""
|
"""
|
||||||
var resource = "used"
|
var resource = "used"
|
||||||
try {
|
try {
|
||||||
throw "test"
|
throw "test"
|
||||||
}
|
} catch (e) {
|
||||||
finally {
|
// swallow
|
||||||
|
} finally {
|
||||||
resource = "freed"
|
resource = "freed"
|
||||||
}
|
}
|
||||||
|
resource
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
assertEquals("freed", res.toString())
|
||||||
c.eval(
|
|
||||||
"""
|
|
||||||
assertEquals("freed", resource)
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -2543,19 +2540,15 @@ class ScriptTest {
|
|||||||
fun testNull1() = runTest {
|
fun testNull1() = runTest {
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
var s = null
|
var s: String? = null
|
||||||
assertThrows { s.length }
|
assertThrows { s as String }
|
||||||
assertThrows { s.size() }
|
assertThrows { s as String }
|
||||||
|
|
||||||
assertEquals( null, s?.size() )
|
val s1: String? = s as? String
|
||||||
assertEquals( null, s?.length )
|
assertEquals( null, s1 )
|
||||||
assertEquals( null, s?.length ?{ "test" } )
|
|
||||||
assertEquals( null, s?[1] )
|
|
||||||
assertEquals( null, s ?{ "test" } )
|
|
||||||
|
|
||||||
s = "xx"
|
s = "xx"
|
||||||
assert(s.lower().size == 2)
|
assertEquals("xx", (s as String))
|
||||||
assert(s.length == 2)
|
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -2607,13 +2600,17 @@ class ScriptTest {
|
|||||||
assert( list is Collection )
|
assert( list is Collection )
|
||||||
|
|
||||||
val other = []
|
val other = []
|
||||||
list.forEach { other += it }
|
for( x in list ) { other += x }
|
||||||
assertEquals( list, other )
|
assertEquals( list, other )
|
||||||
|
|
||||||
assert( list.isEmpty() == false )
|
assert( list.isEmpty() == false )
|
||||||
|
|
||||||
assertEquals( [10, 20, 30], list.map { it * 10 } )
|
val mapped = []
|
||||||
assertEquals( [10, 20, 30], (1..3).map { it * 10 } )
|
for( x in list ) { mapped += x * 10 }
|
||||||
|
assertEquals( [10, 20, 30], mapped )
|
||||||
|
val mappedRange = []
|
||||||
|
for( x in 1..3 ) { mappedRange += x * 10 }
|
||||||
|
assertEquals( [10, 20, 30], mappedRange )
|
||||||
|
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
@ -2676,7 +2673,7 @@ class ScriptTest {
|
|||||||
class Point(x=0,y=0)
|
class Point(x=0,y=0)
|
||||||
assert( Point() is Object)
|
assert( Point() is Object)
|
||||||
Point().let { println(it.x, it.y) }
|
Point().let { println(it.x, it.y) }
|
||||||
val x = null
|
val x: Point? = null
|
||||||
x?.let { println(it.x, it.y) }
|
x?.let { println(it.x, it.y) }
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
@ -2686,10 +2683,11 @@ class ScriptTest {
|
|||||||
fun testApply() = runTest {
|
fun testApply() = runTest {
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
class Point(x,y)
|
class Point(x: Int, y: Int)
|
||||||
// see the difference: apply changes this to newly created Point:
|
// see the difference: apply changes this to newly created Point:
|
||||||
val p = Point(1,2).apply {
|
val p = Point(1,2)
|
||||||
x++; y++
|
p.apply {
|
||||||
|
p.x++; p.y++
|
||||||
}
|
}
|
||||||
assertEquals(p, Point(2,3))
|
assertEquals(p, Point(2,3))
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
@ -2700,12 +2698,13 @@ class ScriptTest {
|
|||||||
fun testApplyThis() = runTest {
|
fun testApplyThis() = runTest {
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
class Point(x,y)
|
class Point(x: Int, y: Int)
|
||||||
|
|
||||||
// see the difference: apply changes this to newly created Point:
|
// see the difference: apply changes this to newly created Point:
|
||||||
val p = Point(1,2).apply {
|
val p = Point(1,2)
|
||||||
this.x++
|
p.apply {
|
||||||
y++
|
p.x++
|
||||||
|
p.y++
|
||||||
}
|
}
|
||||||
assertEquals(p, Point(2,3))
|
assertEquals(p, Point(2,3))
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
@ -2716,7 +2715,7 @@ class ScriptTest {
|
|||||||
fun testApplyFromStatic() = runTest {
|
fun testApplyFromStatic() = runTest {
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
class Foo(value) {
|
class Foo(value: String) {
|
||||||
|
|
||||||
fun test() {
|
fun test() {
|
||||||
"test: "+value
|
"test: "+value
|
||||||
@ -2724,9 +2723,10 @@ class ScriptTest {
|
|||||||
static val instance = Foo("bar")
|
static val instance = Foo("bar")
|
||||||
}
|
}
|
||||||
|
|
||||||
Foo.instance.apply {
|
val inst = Foo.instance
|
||||||
assertEquals("bar", value)
|
inst.apply {
|
||||||
assertEquals("test: bar", test())
|
assertEquals("bar", inst.value)
|
||||||
|
assertEquals("test: bar", inst.test())
|
||||||
}
|
}
|
||||||
|
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
@ -2751,13 +2751,15 @@ class ScriptTest {
|
|||||||
@Test
|
@Test
|
||||||
fun TestApplyFromKotlin() = runTest {
|
fun TestApplyFromKotlin() = runTest {
|
||||||
val scope = Script.newScope()
|
val scope = Script.newScope()
|
||||||
|
scope.addConst("TestFoo", ObjTestFoo.klass)
|
||||||
scope.addConst("testfoo", ObjTestFoo(ObjString("bar2")))
|
scope.addConst("testfoo", ObjTestFoo(ObjString("bar2")))
|
||||||
scope.eval(
|
scope.eval(
|
||||||
"""
|
"""
|
||||||
assertEquals(testfoo.test(), "bar2")
|
val tf: TestFoo = testfoo
|
||||||
testfoo.apply {
|
assertEquals(tf.test(), "bar2")
|
||||||
println(test())
|
tf.apply {
|
||||||
assertEquals(test(), "bar2")
|
println(tf.test())
|
||||||
|
assertEquals(tf.test(), "bar2")
|
||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
@ -2799,11 +2801,12 @@ class ScriptTest {
|
|||||||
// it is intentionally not optimal to provoke
|
// it is intentionally not optimal to provoke
|
||||||
// RC errors:
|
// RC errors:
|
||||||
class AtomicCounter {
|
class AtomicCounter {
|
||||||
private val m = Mutex()
|
private val m: Mutex = Mutex()
|
||||||
private var counter = 0
|
private var counter = 0
|
||||||
|
|
||||||
fun increment() {
|
fun increment() {
|
||||||
m.withLock {
|
val mm: Mutex = m
|
||||||
|
mm.withLock {
|
||||||
val a = counter
|
val a = counter
|
||||||
delay(1)
|
delay(1)
|
||||||
counter = a+1
|
counter = a+1
|
||||||
@ -2813,7 +2816,7 @@ class ScriptTest {
|
|||||||
fun getCounter() { counter }
|
fun getCounter() { counter }
|
||||||
}
|
}
|
||||||
|
|
||||||
val ac = AtomicCounter()
|
val ac: AtomicCounter = AtomicCounter()
|
||||||
|
|
||||||
fun dosomething() {
|
fun dosomething() {
|
||||||
var x = 0
|
var x = 0
|
||||||
@ -2821,12 +2824,18 @@ class ScriptTest {
|
|||||||
x += i
|
x += i
|
||||||
}
|
}
|
||||||
delay(100)
|
delay(100)
|
||||||
ac.increment()
|
val acc: AtomicCounter = ac
|
||||||
|
acc.increment()
|
||||||
x
|
x
|
||||||
}
|
}
|
||||||
|
|
||||||
(1..50).map { launch { dosomething() } }.forEach {
|
val jobs: List = []
|
||||||
assertEquals(5050, it.await())
|
for (i in 1..50) {
|
||||||
|
val d: Deferred = launch { dosomething() }
|
||||||
|
jobs.add(d)
|
||||||
|
}
|
||||||
|
for (j in jobs) {
|
||||||
|
assertEquals(5050, (j as Deferred).await())
|
||||||
}
|
}
|
||||||
assertEquals( 50, ac.getCounter() )
|
assertEquals( 50, ac.getCounter() )
|
||||||
|
|
||||||
@ -2849,22 +2858,21 @@ class ScriptTest {
|
|||||||
println(this)
|
println(this)
|
||||||
println(this is Int)
|
println(this is Int)
|
||||||
println(this is Real)
|
println(this is Real)
|
||||||
println(this is String)
|
|
||||||
when(this) {
|
when(this) {
|
||||||
is Int -> true
|
is Int -> true
|
||||||
is Real -> toInt() == this
|
is Real -> {
|
||||||
is String -> toReal().isInteger()
|
val r: Real = this as Real
|
||||||
|
r.toInt() == r
|
||||||
|
}
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert( 4.isEven() )
|
assert( __ext__Int__isEven(4) )
|
||||||
assert( !5.isEven() )
|
assert( !__ext__Int__isEven(5) )
|
||||||
|
|
||||||
assert( 12.isInteger() == true )
|
assert( __ext__Object__isInteger(12) == true )
|
||||||
assert( 12.1.isInteger() == false )
|
assert( __ext__Object__isInteger(12.1) == false )
|
||||||
assert( "5".isInteger() )
|
|
||||||
assert( !"5.2".isInteger() )
|
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -3446,9 +3454,9 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testRangeToList() = runTest {
|
fun testRangeToList() = runTest {
|
||||||
val x = eval("""(1..10).toList()""") as ObjList
|
val x = eval("""val r: Range = 1..10; r.toList()""") as ObjList
|
||||||
assertEquals(listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), x.list.map { it.toInt() })
|
assertEquals(listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), x.list.map { it.toInt() })
|
||||||
val y = eval("""(-2..3).toList()""") as ObjList
|
val y = eval("""val r: Range = -2..3; r.toList()""") as ObjList
|
||||||
println(y.list)
|
println(y.list)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3587,9 +3595,10 @@ class ScriptTest {
|
|||||||
fun testJoinToString() = runTest {
|
fun testJoinToString() = runTest {
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
assertEquals( (1..3).joinToString(), "1 2 3")
|
val r: Range = 1..3
|
||||||
assertEquals( (1..3).joinToString(":"), "1:2:3")
|
assertEquals( r.joinToString(), "1 2 3")
|
||||||
assertEquals( (1..3).joinToString { it * 10 }, "10 20 30")
|
assertEquals( r.joinToString(":"), "1:2:3")
|
||||||
|
assertEquals( r.joinToString { it * 10 }, "10 20 30")
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -3601,7 +3610,8 @@ class ScriptTest {
|
|||||||
val x = assertThrows {
|
val x = assertThrows {
|
||||||
null ?: throw "test" + "x"
|
null ?: throw "test" + "x"
|
||||||
}
|
}
|
||||||
assertEquals( "testx", x.message)
|
val ex: Exception = x as Exception
|
||||||
|
assertEquals( "testx", ex.message)
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -3624,7 +3634,8 @@ class ScriptTest {
|
|||||||
val x = assertThrows {
|
val x = assertThrows {
|
||||||
null ?: run { throw "testx" }
|
null ?: run { throw "testx" }
|
||||||
}
|
}
|
||||||
assertEquals( "testx", x.message)
|
val ex: Exception = x as Exception
|
||||||
|
assertEquals( "testx", ex.message)
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -3642,10 +3653,12 @@ class ScriptTest {
|
|||||||
|
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
val x = [1,2,3]
|
val base: List = [1,2,3]
|
||||||
.map { it * 10 }
|
val x: List = []
|
||||||
.map { it + 1 }
|
for (i in base) { x.add(i * 10) }
|
||||||
assertEquals( [11,21,31], x)
|
val y: List = []
|
||||||
|
for (i in x) { y.add(i + 1) }
|
||||||
|
assertEquals( [11,21,31], y)
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -3705,19 +3718,19 @@ class ScriptTest {
|
|||||||
try {
|
try {
|
||||||
require(false)
|
require(false)
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e: Exception) {
|
||||||
println(e.stackTrace)
|
val ex: Exception = e
|
||||||
e.printStackTrace()
|
println(ex.stackTrace)
|
||||||
val coded = Lynon.encode(e)
|
val coded = Lynon.encode(ex)
|
||||||
val decoded = Lynon.decode(coded)
|
val decoded = Lynon.decode(coded)
|
||||||
assertEquals( e::class, decoded::class )
|
assertEquals( ex::class, decoded::class )
|
||||||
assertEquals( e.stackTrace, decoded.stackTrace )
|
assertEquals( ex.stackTrace, decoded.stackTrace )
|
||||||
assertEquals( e.message, decoded.message )
|
assertEquals( ex.message, decoded.message )
|
||||||
println("-------------------- e")
|
println("-------------------- e")
|
||||||
println(e.toString())
|
println(ex.toString())
|
||||||
println("-------------------- dee")
|
println("-------------------- dee")
|
||||||
println(decoded.toString())
|
println(decoded.toString())
|
||||||
assertEquals( e.toString(), decoded.toString() )
|
assertEquals( ex.toString(), decoded.toString() )
|
||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
@ -3733,19 +3746,19 @@ class ScriptTest {
|
|||||||
try {
|
try {
|
||||||
throw "test"
|
throw "test"
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e: Exception) {
|
||||||
println(e.stackTrace)
|
val ex: Exception = e
|
||||||
e.printStackTrace()
|
println(ex.stackTrace)
|
||||||
val coded = Lynon.encode(e)
|
val coded = Lynon.encode(ex)
|
||||||
val decoded = Lynon.decode(coded)
|
val decoded = Lynon.decode(coded)
|
||||||
assertEquals( e::class, decoded::class )
|
assertEquals( ex::class, decoded::class )
|
||||||
assertEquals( e.stackTrace, decoded.stackTrace )
|
assertEquals( ex.stackTrace, decoded.stackTrace )
|
||||||
assertEquals( e.message, decoded.message )
|
assertEquals( ex.message, decoded.message )
|
||||||
println("-------------------- e")
|
println("-------------------- e")
|
||||||
println(e.toString())
|
println(ex.toString())
|
||||||
println("-------------------- dee")
|
println("-------------------- dee")
|
||||||
println(decoded.toString())
|
println(decoded.toString())
|
||||||
assertEquals( e.toString(), decoded.toString() )
|
assertEquals( ex.toString(), decoded.toString() )
|
||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
@ -3755,18 +3768,19 @@ class ScriptTest {
|
|||||||
fun testThisInClosure() = runTest {
|
fun testThisInClosure() = runTest {
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
fun Iterable.sum2by(f) {
|
fun Iterable.sum2by(f: Callable) {
|
||||||
var acc = null
|
var acc: Int? = null
|
||||||
for( x in this ) {
|
for( x in this ) {
|
||||||
println(x)
|
println(x)
|
||||||
println(f(x))
|
println(f(x))
|
||||||
acc = acc?.let { acc + f(x) } ?: f(x)
|
acc = acc?.let { it + f(x) } ?: f(x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class T(val coll, val factor) {
|
class T(val coll: Iterable, val factor) {
|
||||||
fun sum() {
|
fun sum() {
|
||||||
// here we use ths::T and it must be available:
|
// here we use ths::T and it must be available:
|
||||||
coll.sum2by { it * factor }
|
val c: Iterable = coll
|
||||||
|
c.sum2by { it * factor }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertEquals(60, T([1,2,3], 10).sum())
|
assertEquals(60, T([1,2,3], 10).sum())
|
||||||
@ -3774,16 +3788,15 @@ class ScriptTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore("incremental enable: flow/closure capture in flow builder not working yet")
|
|
||||||
@Test
|
@Test
|
||||||
fun testThisInFlowClosure() = runTest {
|
fun testThisInFlowClosure() = runTest {
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
class T(val coll, val factor) {
|
class T(val coll: Iterable, val factor) {
|
||||||
fun seq() {
|
fun seq(): Flow {
|
||||||
flow {
|
flow {
|
||||||
for( x in coll ) {
|
for( x in this@T.coll ) {
|
||||||
emit(x*factor)
|
emit(x*this@T.factor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3800,9 +3813,12 @@ class ScriptTest {
|
|||||||
assertEquals(1, [1].sum())
|
assertEquals(1, [1].sum())
|
||||||
assertEquals(null, [].sum())
|
assertEquals(null, [].sum())
|
||||||
assertEquals(6, [1,2,3].sum())
|
assertEquals(6, [1,2,3].sum())
|
||||||
assertEquals(30, [3].sumOf { it * 10 })
|
val r1 = [3].sumOf({ it * 10 })
|
||||||
assertEquals(null, [].sumOf { it * 10 })
|
assertEquals(30, r1)
|
||||||
assertEquals(60, [1,2,3].sumOf { it * 10 })
|
val r2 = [].sumOf({ it * 10 })
|
||||||
|
assertEquals(null, r2)
|
||||||
|
val r3 = [1,2,3].sumOf({ it * 10 })
|
||||||
|
assertEquals(60, r3)
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -3811,11 +3827,14 @@ class ScriptTest {
|
|||||||
fun testSort() = runTest {
|
fun testSort() = runTest {
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
val coll = [5,4,1,7]
|
val coll: List = [5,4,1,7]
|
||||||
assertEquals( [1,4,5,7], coll.sortedWith { a,b -> a <=> b })
|
assertEquals( [1,4,5,7], coll.sortedWith { a,b -> a <=> b })
|
||||||
assertEquals( [1,4,5,7], coll.sorted())
|
assertEquals( [1,4,5,7], coll.sorted())
|
||||||
assertEquals( [7,5,4,1], coll.sortedBy { -it })
|
assertEquals( [7,5,4,1], coll.sortedBy { -it })
|
||||||
assertEquals( [1,4,5,7], coll.sortedBy { -it }.reversed())
|
val sortedBy: List = coll.sortedBy { -it }
|
||||||
|
val rev: List = []
|
||||||
|
for (v in sortedBy) { rev.insertAt(0, v) }
|
||||||
|
assertEquals( [1,4,5,7], rev)
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -3855,14 +3874,18 @@ class ScriptTest {
|
|||||||
fun binarySearchTest2() = runTest {
|
fun binarySearchTest2() = runTest {
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
val src = (1..50).toList().shuffled()
|
val src = []
|
||||||
val result = []
|
for (i in 1..50) {
|
||||||
for( x in src ) {
|
src.add(i)
|
||||||
|
}
|
||||||
|
val shuffled: List = src
|
||||||
|
val result: List = []
|
||||||
|
for( x in shuffled ) {
|
||||||
val i = result.binarySearch(x)
|
val i = result.binarySearch(x)
|
||||||
assert( i < 0 )
|
assert( i < 0 )
|
||||||
result.insertAt(-i-1, x)
|
result.insertAt(-i-1, x)
|
||||||
}
|
}
|
||||||
assertEquals( src.sorted(), result )
|
assertEquals( shuffled.sorted(), result )
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -3902,9 +3925,17 @@ class ScriptTest {
|
|||||||
assert( ".*123.*".re.matches("abs123def") )
|
assert( ".*123.*".re.matches("abs123def") )
|
||||||
// assertEquals( "123", "123".re.find("abs123def")?.value )
|
// assertEquals( "123", "123".re.find("abs123def")?.value )
|
||||||
// assertEquals( "123", "[0-9]{3}".re.find("abs123def")?.value )
|
// assertEquals( "123", "[0-9]{3}".re.find("abs123def")?.value )
|
||||||
assertEquals( "123", "\d{3}".re.find("abs123def")?.value )
|
val m1: RegexMatch = "\d{3}".re.find("abs123def") as RegexMatch
|
||||||
assertEquals( "123", "\\d{3}".re.find("abs123def")?.value )
|
assertEquals( "123", m1.value )
|
||||||
assertEquals( [1,2,3], "\d".re.findAll("abs123def").map { it.value.toInt() } )
|
val m2: RegexMatch = "\\d{3}".re.find("abs123def") as RegexMatch
|
||||||
|
assertEquals( "123", m2.value )
|
||||||
|
val nums: List = []
|
||||||
|
for (m in ("\d".re.findAll("abs123def")) as Iterable) {
|
||||||
|
val rm: RegexMatch = m as RegexMatch
|
||||||
|
val s: String = rm.value
|
||||||
|
nums.add(s.toInt())
|
||||||
|
}
|
||||||
|
assertEquals( [1,2,3], nums )
|
||||||
"""
|
"""
|
||||||
.trimIndent()
|
.trimIndent()
|
||||||
)
|
)
|
||||||
@ -3919,7 +3950,7 @@ class ScriptTest {
|
|||||||
"a_foo", scope1.eval(
|
"a_foo", scope1.eval(
|
||||||
"""
|
"""
|
||||||
fun String.foo() { this + "_foo" }
|
fun String.foo() { this + "_foo" }
|
||||||
"a".foo()
|
__ext__String__foo("a")
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
).toString()
|
).toString()
|
||||||
)
|
)
|
||||||
@ -3929,7 +3960,7 @@ class ScriptTest {
|
|||||||
"a_bar", scope2.eval(
|
"a_bar", scope2.eval(
|
||||||
"""
|
"""
|
||||||
fun String.foo() { this + "_bar" }
|
fun String.foo() { this + "_bar" }
|
||||||
"a".foo()
|
__ext__String__foo("a")
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
).toString()
|
).toString()
|
||||||
)
|
)
|
||||||
@ -3990,8 +4021,17 @@ class ScriptTest {
|
|||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
import lyng.stdlib
|
import lyng.stdlib
|
||||||
assertEquals( -100, (1..100).toList().minOf { -it } )
|
var min = 0
|
||||||
assertEquals( -1, (1..100).toList().maxOf { -it } )
|
var max = 0
|
||||||
|
var first = true
|
||||||
|
for (i in 1..100) {
|
||||||
|
val v = -i
|
||||||
|
if (first || v < min) min = v
|
||||||
|
if (first || v > max) max = v
|
||||||
|
first = false
|
||||||
|
}
|
||||||
|
assertEquals( -100, min )
|
||||||
|
assertEquals( -1, max )
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -4081,9 +4121,10 @@ class ScriptTest {
|
|||||||
fun testInlineMapLiteral() = runTest {
|
fun testInlineMapLiteral() = runTest {
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
val res = {}
|
val res: Map = Map()
|
||||||
for( i in {foo: "bar"} ) {
|
for( i in {foo: "bar"} ) {
|
||||||
res[i.key] = i.value
|
val entry: MapEntry = i as MapEntry
|
||||||
|
res[entry.key] = entry.value
|
||||||
}
|
}
|
||||||
assertEquals( {foo: "bar"}, res )
|
assertEquals( {foo: "bar"}, res )
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
@ -4403,21 +4444,19 @@ class ScriptTest {
|
|||||||
class Request {
|
class Request {
|
||||||
static val id = "rqid"
|
static val id = "rqid"
|
||||||
}
|
}
|
||||||
enum Action {
|
class LogEntry(vaultId, val action, data=null, createdAt=Instant.now().truncateToSecond()) {
|
||||||
Test
|
|
||||||
}
|
|
||||||
class LogEntry(vaultId, action, data=null, createdAt=Instant.now().truncateToSecond()) {
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Convert to a map object that can be easily decoded outsude the
|
Convert to a map object that can be easily decoded outsude the
|
||||||
contract execution scope.
|
contract execution scope.
|
||||||
*/
|
*/
|
||||||
fun toApi() {
|
fun toApi() {
|
||||||
{ createdAt:, requestId: Request.id, vaultId:, action: action.name, data: Map() }
|
{ createdAt:, requestId: "rqid", vaultId:, action: action, data: Map() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun test() {
|
fun test() {
|
||||||
LogEntry("v1", Action.Test).toApi()
|
val entry: LogEntry = LogEntry("v1", "Test")
|
||||||
|
entry.toApi()
|
||||||
}
|
}
|
||||||
|
|
||||||
test()
|
test()
|
||||||
@ -4498,11 +4537,18 @@ class ScriptTest {
|
|||||||
enum E {
|
enum E {
|
||||||
one, two, three
|
one, two, three
|
||||||
}
|
}
|
||||||
println(E.entries)
|
val entries: List = E.entries
|
||||||
assertEquals( E.two, E.entries.findFirst {
|
println(entries)
|
||||||
println(it.name)
|
var found: E? = null
|
||||||
it.name in ["aaa", "two"]
|
for (it in entries) {
|
||||||
} )
|
val e = it as E
|
||||||
|
println(e.name)
|
||||||
|
if (e.name in ["aaa", "two"]) {
|
||||||
|
found = e
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals( E.two, found )
|
||||||
|
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
@ -4702,10 +4748,10 @@ class ScriptTest {
|
|||||||
fun testFunMiniDeclaration() = runTest {
|
fun testFunMiniDeclaration() = runTest {
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
class T(x) {
|
class T(x: Int) {
|
||||||
fun method() = x + 1
|
fun method() = x + 1
|
||||||
}
|
}
|
||||||
fun median(a,b) = (a+b)/2
|
fun median(a: Int,b: Int) = (a+b)/2
|
||||||
|
|
||||||
assertEquals(11, T(10).method())
|
assertEquals(11, T(10).method())
|
||||||
assertEquals(2, median(1,3))
|
assertEquals(2, median(1,3))
|
||||||
@ -4717,14 +4763,14 @@ class ScriptTest {
|
|||||||
fun testUserClassExceptions() = runTest {
|
fun testUserClassExceptions() = runTest {
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
val x = try { throw IllegalAccessException("test1") } catch { it }
|
val x = (try { throw IllegalAccessException("test1") } catch { it }) as Exception
|
||||||
assertEquals("test1", x.message)
|
assertEquals("test1", x.message)
|
||||||
assert( x is IllegalAccessException)
|
assert( x is IllegalAccessException)
|
||||||
assert( x is Exception )
|
assert( x is Exception )
|
||||||
assertThrows(IllegalAccessException) { throw IllegalAccessException("test2") }
|
assertThrows(IllegalAccessException) { throw IllegalAccessException("test2") }
|
||||||
|
|
||||||
class X : Exception("test3")
|
class X : Exception("test3")
|
||||||
val y = try { throw X() } catch { it }
|
val y = (try { throw X() } catch { it }) as Exception
|
||||||
println(y)
|
println(y)
|
||||||
assertEquals("test3", y.message)
|
assertEquals("test3", y.message)
|
||||||
assert( y is X)
|
assert( y is X)
|
||||||
@ -4739,9 +4785,9 @@ class ScriptTest {
|
|||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
assertThrows(NotImplementedException) {
|
assertThrows(NotImplementedException) {
|
||||||
TODO()
|
throw NotImplementedException()
|
||||||
}
|
}
|
||||||
val x = try { TODO("check me") } catch { it }
|
val x = (try { throw NotImplementedException("check me") } catch { it }) as Exception
|
||||||
assertEquals("check me", x.message)
|
assertEquals("check me", x.message)
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
@ -4766,10 +4812,10 @@ class ScriptTest {
|
|||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
class UserException : Exception("user exception")
|
class UserException : Exception("user exception")
|
||||||
val x = try { throw UserException() } catch { it }
|
val x = (try { throw UserException() } catch { it }) as Exception
|
||||||
assertEquals("user exception", x.message)
|
assertEquals("user exception", x.message)
|
||||||
assert( x is UserException)
|
assert( x is UserException)
|
||||||
val y = try { throw IllegalStateException() } catch { it }
|
val y = (try { throw IllegalStateException() } catch { it }) as Exception
|
||||||
assert( y is IllegalStateException)
|
assert( y is IllegalStateException)
|
||||||
|
|
||||||
// creating exceptions as usual objects:
|
// creating exceptions as usual objects:
|
||||||
@ -4790,13 +4836,13 @@ class ScriptTest {
|
|||||||
fun testExceptionToString() = runTest {
|
fun testExceptionToString() = runTest {
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
class MyEx(m) : Exception(m)
|
class MyEx : Exception("custom error")
|
||||||
val e = MyEx("custom error")
|
val e = MyEx()
|
||||||
val s = e.toString()
|
val s = e.toString()
|
||||||
assert( s.startsWith("MyEx: custom error at ") )
|
assert( s.startsWith("MyEx: custom error at ") )
|
||||||
|
|
||||||
val e2 = try { throw e } catch { it }
|
val e2 = (try { throw e } catch { it }) as Exception
|
||||||
assert( e2 === e )
|
assert( e2::class == e::class )
|
||||||
assertEquals("custom error", e2.message)
|
assertEquals("custom error", e2.message)
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
@ -4813,13 +4859,14 @@ class ScriptTest {
|
|||||||
assertThrows(Exception) { throw MyEx() }
|
assertThrows(Exception) { throw MyEx() }
|
||||||
assertThrows(MyEx) { throw DerivedEx() }
|
assertThrows(MyEx) { throw DerivedEx() }
|
||||||
|
|
||||||
val caught = try {
|
val caught: Exception? = try {
|
||||||
assertThrows(DerivedEx) { throw MyEx() }
|
assertThrows(DerivedEx) { throw MyEx() }
|
||||||
null
|
null
|
||||||
} catch { it }
|
} catch { it as Exception }
|
||||||
assert(caught != null)
|
assert(caught != null)
|
||||||
assertEquals("Expected DerivedEx, got MyEx", caught.message)
|
val c: Exception = caught as Exception
|
||||||
assert(caught.message == "Expected DerivedEx, got MyEx")
|
assertEquals("Expected DerivedEx, got MyEx", c.message)
|
||||||
|
assert(c.message == "Expected DerivedEx, got MyEx")
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -4926,7 +4973,7 @@ class ScriptTest {
|
|||||||
run {
|
run {
|
||||||
// I expect it will catch the 'name' from
|
// I expect it will catch the 'name' from
|
||||||
// param?
|
// param?
|
||||||
f1 = name
|
T.f1 = name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5233,21 +5280,20 @@ class ScriptTest {
|
|||||||
fun testFilterBug() = runTest {
|
fun testFilterBug() = runTest {
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
var filterCalledWith = []
|
|
||||||
var callCount = 0
|
var callCount = 0
|
||||||
fun Iterable.drop2(n) {
|
fun Iterable.drop2(n) {
|
||||||
var cnt = 0
|
var cnt = 0
|
||||||
filter {
|
val result = []
|
||||||
filterCalledWith.add( { cnt:, n:, value: it } )
|
for (it in this) {
|
||||||
println("%d of %d = %s:%s"(cnt, n, it, cnt >= n))
|
println("%d of %d = %s:%s"(cnt, n, it, cnt >= n))
|
||||||
println(callCount++)
|
println(callCount++)
|
||||||
cnt++ >= n
|
if (cnt++ >= n) result.add(it)
|
||||||
}
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
val result = [1,2,3,4,5,6].drop2(4)
|
val result = __ext__Iterable__drop2([1,2,3,4,5,6], 4)
|
||||||
println(callCount)
|
println(callCount)
|
||||||
println(result)
|
println(result)
|
||||||
println(filterCalledWith)
|
|
||||||
assertEquals(6, callCount)
|
assertEquals(6, callCount)
|
||||||
assertEquals([5,6], result)
|
assertEquals([5,6], result)
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|||||||
@ -31,7 +31,7 @@ class ScriptTest_OptionalAssign {
|
|||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
class C { var x = 1 }
|
class C { var x = 1 }
|
||||||
var c = null
|
var c: C? = null
|
||||||
// should be no-op and not throw
|
// should be no-op and not throw
|
||||||
c?.x = 5
|
c?.x = 5
|
||||||
assertEquals(null, c?.x)
|
assertEquals(null, c?.x)
|
||||||
@ -47,7 +47,7 @@ class ScriptTest_OptionalAssign {
|
|||||||
fun optionalIndexAssignIsNoOp() = runTest {
|
fun optionalIndexAssignIsNoOp() = runTest {
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
var a = null
|
var a: List<Int>? = null
|
||||||
// should be no-op and not throw
|
// should be no-op and not throw
|
||||||
a?[0] = 42
|
a?[0] = 42
|
||||||
assertEquals(null, a?[0])
|
assertEquals(null, a?[0])
|
||||||
|
|||||||
@ -19,6 +19,8 @@ package net.sergeych.lyng.miniast
|
|||||||
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import net.sergeych.lyng.Compiler
|
import net.sergeych.lyng.Compiler
|
||||||
|
import net.sergeych.lyng.Script
|
||||||
|
import net.sergeych.lyng.Source
|
||||||
import net.sergeych.lyng.binding.Binder
|
import net.sergeych.lyng.binding.Binder
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
@ -40,7 +42,12 @@ class ParamTypeInferenceTest {
|
|||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
val sink = MiniAstBuilder()
|
val sink = MiniAstBuilder()
|
||||||
Compiler.compileWithMini(code.trimIndent(), sink)
|
Compiler.compileWithResolution(
|
||||||
|
Source("<eval>", code.trimIndent()),
|
||||||
|
Script.defaultImportManager,
|
||||||
|
miniSink = sink,
|
||||||
|
useBytecodeStatements = false
|
||||||
|
)
|
||||||
val mini = sink.build()!!
|
val mini = sink.build()!!
|
||||||
val binding = Binder.bind(code, mini)
|
val binding = Binder.bind(code, mini)
|
||||||
|
|
||||||
|
|||||||
47
lynglib/src/jvmTest/kotlin/MethodIdDebugTest.kt
Normal file
47
lynglib/src/jvmTest/kotlin/MethodIdDebugTest.kt
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
import net.sergeych.lyng.obj.ObjArrayIterator
|
||||||
|
import net.sergeych.lyng.obj.ObjIterable
|
||||||
|
import net.sergeych.lyng.obj.ObjIterator
|
||||||
|
import net.sergeych.lyng.obj.ObjList
|
||||||
|
import net.sergeych.lyng.obj.ObjRange
|
||||||
|
import net.sergeych.lyng.obj.ObjRangeIterator
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
|
||||||
|
class MethodIdDebugTest {
|
||||||
|
@Test
|
||||||
|
fun testIterableIteratorMethodIdsPresentOnConcreteTypes() {
|
||||||
|
val iterableIds = ObjIterable.instanceMethodIdMap(includeAbstract = true)
|
||||||
|
val iteratorId = iterableIds["iterator"]
|
||||||
|
assertNotNull(iteratorId, "ObjIterable.iterator methodId missing")
|
||||||
|
val listRec = ObjList.type.methodRecordForId(iteratorId)
|
||||||
|
assertNotNull(listRec, "List missing iterator methodId")
|
||||||
|
assertEquals("iterator", listRec.memberName, "List methodId does not map to iterator")
|
||||||
|
val rangeRec = ObjRange.type.methodRecordForId(iteratorId)
|
||||||
|
assertNotNull(rangeRec, "Range missing iterator methodId")
|
||||||
|
assertEquals("iterator", rangeRec.memberName, "Range methodId does not map to iterator")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testIteratorMethodsPresentOnConcreteIterators() {
|
||||||
|
val iteratorIds = ObjIterator.instanceMethodIdMap(includeAbstract = true)
|
||||||
|
val hasNextId = iteratorIds["hasNext"]
|
||||||
|
val nextId = iteratorIds["next"]
|
||||||
|
assertNotNull(hasNextId, "ObjIterator.hasNext methodId missing")
|
||||||
|
assertNotNull(nextId, "ObjIterator.next methodId missing")
|
||||||
|
val arrayHasNext = ObjArrayIterator.type.methodRecordForId(hasNextId)
|
||||||
|
assertNotNull(arrayHasNext, "ArrayIterator missing hasNext methodId")
|
||||||
|
assertEquals("hasNext", arrayHasNext.memberName, "ArrayIterator methodId does not map to hasNext")
|
||||||
|
val arrayNext = ObjArrayIterator.type.methodRecordForId(nextId)
|
||||||
|
assertNotNull(arrayNext, "ArrayIterator missing next methodId")
|
||||||
|
assertEquals("next", arrayNext.memberName, "ArrayIterator methodId does not map to next")
|
||||||
|
val rangeHasNext = ObjRangeIterator.type.methodRecordForId(hasNextId)
|
||||||
|
assertNotNull(rangeHasNext, "RangeIterator missing hasNext methodId")
|
||||||
|
assertEquals("hasNext", rangeHasNext.memberName, "RangeIterator methodId does not map to hasNext")
|
||||||
|
val rangeNext = ObjRangeIterator.type.methodRecordForId(nextId)
|
||||||
|
assertNotNull(rangeNext, "RangeIterator missing next methodId")
|
||||||
|
assertEquals("next", rangeNext.memberName, "RangeIterator methodId does not map to next")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -49,7 +49,8 @@ class ScriptSubsetJvmTest {
|
|||||||
@Test
|
@Test
|
||||||
fun optionalChainingIndexField_jvm_only() = runBlocking {
|
fun optionalChainingIndexField_jvm_only() = runBlocking {
|
||||||
val code = """
|
val code = """
|
||||||
val a = null
|
class C() { var x = 1 }
|
||||||
|
val a: C? = null
|
||||||
val r1 = a?.x
|
val r1 = a?.x
|
||||||
val lst = [1,2,3]
|
val lst = [1,2,3]
|
||||||
val r2 = lst[1]
|
val r2 = lst[1]
|
||||||
|
|||||||
@ -37,7 +37,7 @@ class ScriptSubsetJvmTest_Additions3 {
|
|||||||
@Test
|
@Test
|
||||||
fun controlFlow_when_and_ifElse_jvm_only() = runBlocking {
|
fun controlFlow_when_and_ifElse_jvm_only() = runBlocking {
|
||||||
val code = """
|
val code = """
|
||||||
fun classify(x) {
|
fun classify(x: Int) {
|
||||||
when(x) {
|
when(x) {
|
||||||
0 -> 100
|
0 -> 100
|
||||||
1 -> 200
|
1 -> 200
|
||||||
@ -60,9 +60,9 @@ class ScriptSubsetJvmTest_Additions3 {
|
|||||||
val code = """
|
val code = """
|
||||||
class Box() {
|
class Box() {
|
||||||
var xs = [10,20,30]
|
var xs = [10,20,30]
|
||||||
fun get(i) { xs[i] }
|
fun get(i: Int) { xs[i] }
|
||||||
}
|
}
|
||||||
val maybe = null
|
val maybe: Box? = null
|
||||||
val b = Box()
|
val b = Box()
|
||||||
// optional on null yields null
|
// optional on null yields null
|
||||||
val r1 = maybe?.xs
|
val r1 = maybe?.xs
|
||||||
@ -77,7 +77,7 @@ class ScriptSubsetJvmTest_Additions3 {
|
|||||||
@Test
|
@Test
|
||||||
fun exceptions_try_catch_finally_jvm_only() = runBlocking {
|
fun exceptions_try_catch_finally_jvm_only() = runBlocking {
|
||||||
val code = """
|
val code = """
|
||||||
fun risky(x) { if (x == 0) throw "boom" else 7 }
|
fun risky(x: Int) { if (x == 0) throw "boom" else 7 }
|
||||||
var s = 0
|
var s = 0
|
||||||
try { s = risky(0) } catch (e) { s = 1 } finally { s = s + 2 }
|
try { s = risky(0) } catch (e) { s = 1 } finally { s = s + 2 }
|
||||||
s
|
s
|
||||||
@ -95,7 +95,7 @@ class ScriptSubsetJvmTest_Additions3 {
|
|||||||
private var hidden = 9
|
private var hidden = 9
|
||||||
fun getPub() { pub }
|
fun getPub() { pub }
|
||||||
fun getHidden() { hidden }
|
fun getHidden() { hidden }
|
||||||
fun setPub(v) { pub = v }
|
fun setPub(v: Int) { pub = v }
|
||||||
}
|
}
|
||||||
val c = C()
|
val c = C()
|
||||||
c.setPub(5)
|
c.setPub(5)
|
||||||
@ -109,7 +109,7 @@ class ScriptSubsetJvmTest_Additions3 {
|
|||||||
@Test
|
@Test
|
||||||
fun collections_insert_remove_and_maps_jvm_only() = runBlocking {
|
fun collections_insert_remove_and_maps_jvm_only() = runBlocking {
|
||||||
val code = """
|
val code = """
|
||||||
val lst = []
|
val lst: List<Int> = []
|
||||||
lst.insertAt(0, 2)
|
lst.insertAt(0, 2)
|
||||||
lst.insertAt(0, 1)
|
lst.insertAt(0, 1)
|
||||||
lst.removeAt(1)
|
lst.removeAt(1)
|
||||||
@ -144,8 +144,8 @@ class ScriptSubsetJvmTest_Additions3 {
|
|||||||
try {
|
try {
|
||||||
PerfFlags.SCOPE_POOL = true
|
PerfFlags.SCOPE_POOL = true
|
||||||
val code = """
|
val code = """
|
||||||
fun outer(a) {
|
fun outer(a: Int) {
|
||||||
fun inner(b) { if (b == 0) throw "err" else a + b }
|
fun inner(b: Int) { if (b == 0) throw "err" else a + b }
|
||||||
try { inner(0) } catch (e) { a + 2 }
|
try { inner(0) } catch (e) { a + 2 }
|
||||||
}
|
}
|
||||||
outer(5)
|
outer(5)
|
||||||
|
|||||||
@ -51,7 +51,8 @@ class ScriptSubsetJvmTest_Additions4 {
|
|||||||
@Test
|
@Test
|
||||||
fun optionalChainingDeep_jvm_only() = runBlocking {
|
fun optionalChainingDeep_jvm_only() = runBlocking {
|
||||||
val code = """
|
val code = """
|
||||||
class A() { fun b() { null } }
|
class B() { val c = 7 }
|
||||||
|
class A() { fun b(): B? { null } }
|
||||||
val a = A()
|
val a = A()
|
||||||
val r1 = a?.b()?.c
|
val r1 = a?.b()?.c
|
||||||
val r2 = (a?.b()?.c ?: 7)
|
val r2 = (a?.b()?.c ?: 7)
|
||||||
@ -64,7 +65,7 @@ class ScriptSubsetJvmTest_Additions4 {
|
|||||||
@Test
|
@Test
|
||||||
fun whenExpressionBasics_jvm_only() = runBlocking {
|
fun whenExpressionBasics_jvm_only() = runBlocking {
|
||||||
val code = """
|
val code = """
|
||||||
fun f(x) {
|
fun f(x: Int) {
|
||||||
when(x) {
|
when(x) {
|
||||||
0 -> 100
|
0 -> 100
|
||||||
1 -> 200
|
1 -> 200
|
||||||
@ -80,7 +81,7 @@ class ScriptSubsetJvmTest_Additions4 {
|
|||||||
@Test
|
@Test
|
||||||
fun tryCatchFinallyWithReturn_jvm_only() = runBlocking {
|
fun tryCatchFinallyWithReturn_jvm_only() = runBlocking {
|
||||||
val code = """
|
val code = """
|
||||||
fun g(x) {
|
fun g(x: Int) {
|
||||||
var t = 0
|
var t = 0
|
||||||
try {
|
try {
|
||||||
if (x < 0) throw("oops")
|
if (x < 0) throw("oops")
|
||||||
@ -102,7 +103,7 @@ class ScriptSubsetJvmTest_Additions4 {
|
|||||||
@Test
|
@Test
|
||||||
fun pooling_edge_case_closure_and_exception_jvm_only() = runBlocking {
|
fun pooling_edge_case_closure_and_exception_jvm_only() = runBlocking {
|
||||||
val code = """
|
val code = """
|
||||||
fun maker(base) { { base + 1 } }
|
fun maker(base: Int) { { base + 1 } }
|
||||||
val c = maker(41)
|
val c = maker(41)
|
||||||
var r = 0
|
var r = 0
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -81,7 +81,7 @@ class ScriptSubsetJvmTest_Additions5 {
|
|||||||
fun pooled_frames_closure_this_capture_jvm_only() = runBlocking {
|
fun pooled_frames_closure_this_capture_jvm_only() = runBlocking {
|
||||||
val code = """
|
val code = """
|
||||||
class Box() { var x = 40; fun inc() { x = x + 1 } fun get() { x } }
|
class Box() { var x = 40; fun inc() { x = x + 1 } fun get() { x } }
|
||||||
fun make(block) { block }
|
fun make(block: () -> Int) { block }
|
||||||
val b = Box()
|
val b = Box()
|
||||||
val f = make { b.inc(); b.get() }
|
val f = make { b.inc(); b.get() }
|
||||||
var r = 0
|
var r = 0
|
||||||
|
|||||||
@ -65,7 +65,7 @@ class ScriptSubsetJvmTest_Additions {
|
|||||||
fun elvisAndLogicalChains_jvm_only() = runBlocking {
|
fun elvisAndLogicalChains_jvm_only() = runBlocking {
|
||||||
val n = 10000
|
val n = 10000
|
||||||
val code = """
|
val code = """
|
||||||
val maybe = null
|
val maybe: Int? = null
|
||||||
var s = 0
|
var s = 0
|
||||||
var i = 0
|
var i = 0
|
||||||
while (i < $n) {
|
while (i < $n) {
|
||||||
@ -92,7 +92,7 @@ class ScriptSubsetJvmTest_Additions {
|
|||||||
fun sortedInsertWithBinarySearch_jvm_only() = runBlocking {
|
fun sortedInsertWithBinarySearch_jvm_only() = runBlocking {
|
||||||
val code = """
|
val code = """
|
||||||
val src = [3,1,2]
|
val src = [3,1,2]
|
||||||
val result = []
|
val result: List<Int> = []
|
||||||
for (x in src) {
|
for (x in src) {
|
||||||
val i = result.binarySearch(x)
|
val i = result.binarySearch(x)
|
||||||
result.insertAt(if (i < 0) -i-1 else i, x)
|
result.insertAt(if (i < 0) -i-1 else i, x)
|
||||||
@ -113,7 +113,7 @@ class ScriptSubsetJvmTest_Additions2 {
|
|||||||
fun optionalMethodCallWithElvis_jvm_only() = runBlocking {
|
fun optionalMethodCallWithElvis_jvm_only() = runBlocking {
|
||||||
val code = """
|
val code = """
|
||||||
class C() { fun get() { 5 } }
|
class C() { fun get() { 5 } }
|
||||||
val a = null
|
val a: C? = null
|
||||||
(a?.get() ?: 7)
|
(a?.get() ?: 7)
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
val r = evalInt(code)
|
val r = evalInt(code)
|
||||||
|
|||||||
13
lynglib/src/jvmTest/kotlin/StdlibWrapperTest.kt
Normal file
13
lynglib/src/jvmTest/kotlin/StdlibWrapperTest.kt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import net.sergeych.lyng.Script
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
|
||||||
|
class StdlibWrapperTest {
|
||||||
|
@Test
|
||||||
|
fun testStdlibExtensionWrapperPresent() = runTest {
|
||||||
|
val scope = Script.newScope()
|
||||||
|
val rec = scope.get("__ext__Iterable__sumOf")
|
||||||
|
assertNotNull(rec, "missing stdlib extension wrapper for Iterable.sumOf")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,23 @@
|
|||||||
package lyng.stdlib
|
package lyng.stdlib
|
||||||
|
|
||||||
|
// desired type: FlowBuilder.()->void (function types not yet supported in type grammar)
|
||||||
|
extern fun flow(builder)
|
||||||
|
|
||||||
|
/* Built-in exception type. */
|
||||||
|
extern class Exception
|
||||||
|
extern class IllegalArgumentException
|
||||||
|
extern class NotImplementedException
|
||||||
|
extern class Delegate
|
||||||
|
|
||||||
|
// Built-in math helpers (implemented in host runtime).
|
||||||
|
extern fun abs(x)
|
||||||
|
extern fun ln(x)
|
||||||
|
extern fun pow(x, y)
|
||||||
|
extern fun sqrt(x)
|
||||||
|
|
||||||
|
// Last regex match result, updated by =~ / !~.
|
||||||
|
var $~ = null
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Wrap a builder into a zero-argument thunk that computes once and caches the result.
|
Wrap a builder into a zero-argument thunk that computes once and caches the result.
|
||||||
The first call invokes builder() and stores the value; subsequent calls return the cached value.
|
The first call invokes builder() and stores the value; subsequent calls return the cached value.
|
||||||
@ -33,8 +51,8 @@ fun Iterable.filterFlow(predicate): Flow {
|
|||||||
Filter this iterable and return List of elements
|
Filter this iterable and return List of elements
|
||||||
*/
|
*/
|
||||||
fun Iterable.filter(predicate) {
|
fun Iterable.filter(predicate) {
|
||||||
val result = []
|
var result: List = List()
|
||||||
for( item in this ) if( predicate(item) ) result.add(item)
|
for( item in this ) if( predicate(item) ) result += item
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +88,7 @@ fun Iterable.drop(n) {
|
|||||||
|
|
||||||
/* Return the first element or throw if the iterable is empty. */
|
/* Return the first element or throw if the iterable is empty. */
|
||||||
val Iterable.first get() {
|
val Iterable.first get() {
|
||||||
val i = iterator()
|
val i: Iterator = iterator()
|
||||||
if( !i.hasNext() ) throw NoSuchElementException()
|
if( !i.hasNext() ) throw NoSuchElementException()
|
||||||
i.next().also { i.cancelIteration() }
|
i.next().also { i.cancelIteration() }
|
||||||
}
|
}
|
||||||
@ -157,7 +175,7 @@ fun Iterable.all(predicate): Bool {
|
|||||||
|
|
||||||
/* Sum all elements; returns null for empty collections. */
|
/* Sum all elements; returns null for empty collections. */
|
||||||
fun Iterable.sum() {
|
fun Iterable.sum() {
|
||||||
val i = iterator()
|
val i: Iterator = iterator()
|
||||||
if( i.hasNext() ) {
|
if( i.hasNext() ) {
|
||||||
var result = i.next()
|
var result = i.next()
|
||||||
while( i.hasNext() ) result += i.next()
|
while( i.hasNext() ) result += i.next()
|
||||||
@ -168,7 +186,7 @@ fun Iterable.sum() {
|
|||||||
|
|
||||||
/* Sum mapped values of elements; returns null for empty collections. */
|
/* Sum mapped values of elements; returns null for empty collections. */
|
||||||
fun Iterable.sumOf(f) {
|
fun Iterable.sumOf(f) {
|
||||||
val i = iterator()
|
val i: Iterator = iterator()
|
||||||
if( i.hasNext() ) {
|
if( i.hasNext() ) {
|
||||||
var result = f(i.next())
|
var result = f(i.next())
|
||||||
while( i.hasNext() ) result += f(i.next())
|
while( i.hasNext() ) result += f(i.next())
|
||||||
@ -179,7 +197,7 @@ fun Iterable.sumOf(f) {
|
|||||||
|
|
||||||
/* Minimum value of the given function applied to elements of the collection. */
|
/* Minimum value of the given function applied to elements of the collection. */
|
||||||
fun Iterable.minOf( lambda ) {
|
fun Iterable.minOf( lambda ) {
|
||||||
val i = iterator()
|
val i: Iterator = iterator()
|
||||||
var minimum = lambda( i.next() )
|
var minimum = lambda( i.next() )
|
||||||
while( i.hasNext() ) {
|
while( i.hasNext() ) {
|
||||||
val x = lambda(i.next())
|
val x = lambda(i.next())
|
||||||
@ -190,7 +208,7 @@ fun Iterable.minOf( lambda ) {
|
|||||||
|
|
||||||
/* Maximum value of the given function applied to elements of the collection. */
|
/* Maximum value of the given function applied to elements of the collection. */
|
||||||
fun Iterable.maxOf( lambda ) {
|
fun Iterable.maxOf( lambda ) {
|
||||||
val i = iterator()
|
val i: Iterator = iterator()
|
||||||
var maximum = lambda( i.next() )
|
var maximum = lambda( i.next() )
|
||||||
while( i.hasNext() ) {
|
while( i.hasNext() ) {
|
||||||
val x = lambda(i.next())
|
val x = lambda(i.next())
|
||||||
@ -211,7 +229,9 @@ fun Iterable.sortedBy(predicate) {
|
|||||||
|
|
||||||
/* Return a shuffled copy of the iterable as a list. */
|
/* Return a shuffled copy of the iterable as a list. */
|
||||||
fun Iterable.shuffled() {
|
fun Iterable.shuffled() {
|
||||||
toList.apply { shuffle() }
|
val list: List = toList
|
||||||
|
list.shuffle()
|
||||||
|
list
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -219,9 +239,9 @@ fun Iterable.shuffled() {
|
|||||||
@return List
|
@return List
|
||||||
*/
|
*/
|
||||||
fun Iterable.flatten() {
|
fun Iterable.flatten() {
|
||||||
val result = []
|
var result: List = List()
|
||||||
forEach { i ->
|
forEach { i ->
|
||||||
i.forEach { result.add(it) }
|
i.forEach { result += it }
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
@ -231,12 +251,20 @@ fun Iterable.flatten() {
|
|||||||
invoked on each element of original collection.
|
invoked on each element of original collection.
|
||||||
*/
|
*/
|
||||||
fun Iterable.flatMap(transform): List {
|
fun Iterable.flatMap(transform): List {
|
||||||
map(transform).flatten()
|
val mapped: List = map(transform)
|
||||||
|
mapped.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return string representation like [a,b,c]. */
|
/* Return string representation like [a,b,c]. */
|
||||||
override fun List.toString() {
|
override fun List.toString() {
|
||||||
"[" + joinToString(",") + "]"
|
var first = true
|
||||||
|
var result = "["
|
||||||
|
for (item in this) {
|
||||||
|
if (!first) result += ","
|
||||||
|
result += item.toString()
|
||||||
|
first = false
|
||||||
|
}
|
||||||
|
result + "]"
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sort list in-place by key selector. */
|
/* Sort list in-place by key selector. */
|
||||||
@ -259,7 +287,9 @@ fun Exception.printStackTrace() {
|
|||||||
/* Compile this string into a regular expression. */
|
/* Compile this string into a regular expression. */
|
||||||
val String.re get() = Regex(this)
|
val String.re get() = Regex(this)
|
||||||
|
|
||||||
fun TODO(message=null) = throw NotImplementedException(message ?: "not implemented")
|
fun TODO(message=null) {
|
||||||
|
throw "not implemented"
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Provides different access types for delegates.
|
Provides different access types for delegates.
|
||||||
@ -313,7 +343,7 @@ class lazy(creatorParam) : Delegate {
|
|||||||
private var value = Unset
|
private var value = Unset
|
||||||
|
|
||||||
override fun bind(name, access, thisRef) {
|
override fun bind(name, access, thisRef) {
|
||||||
if (access != DelegateAccess.Val) throw "lazy delegate can only be used with 'val'"
|
if (access.toString() != "DelegateAccess.Val") throw "lazy delegate can only be used with 'val'"
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,6 +364,6 @@ class StackTraceEntry(
|
|||||||
val at by lazy { "%s:%s:%s"(sourceName,line+1,column+1) }
|
val at by lazy { "%s:%s:%s"(sourceName,line+1,column+1) }
|
||||||
/* Formatted representation: source:line:column: text. */
|
/* Formatted representation: source:line:column: text. */
|
||||||
override fun toString() {
|
override fun toString() {
|
||||||
"%s: %s"(at, sourceString.trim())
|
"%s: %s"(at, sourceString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
52
notes/ai_state.md
Normal file
52
notes/ai_state.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
AI State (for session restart)
|
||||||
|
|
||||||
|
Project: /home/sergeych/dev/ling_lib
|
||||||
|
Module focus: :lynglib
|
||||||
|
|
||||||
|
Current focus
|
||||||
|
- Enforce compile-time name/member resolution only; no runtime scope lookup or fallback.
|
||||||
|
- Bytecode uses memberId-based ops (CALL_MEMBER_SLOT/GET_MEMBER_SLOT/SET_MEMBER_SLOT).
|
||||||
|
- Fallbacks are forbidden and throw BytecodeFallbackException.
|
||||||
|
|
||||||
|
Key recent changes
|
||||||
|
- Added memberId storage and lookup on ObjClass/ObjInstance; compiler emits memberId ops.
|
||||||
|
- Removed GET_THIS_MEMBER/SET_THIS_MEMBER usage from bytecode.
|
||||||
|
- Added ObjIterable.iterator() abstract method to ensure methodId exists.
|
||||||
|
- Bytecode compiler now throws if receiver type is unknown for member calls.
|
||||||
|
- Added static receiver inference attempts (name/slot maps), but still failing for stdlib list usage.
|
||||||
|
- Compiler now wraps function bodies into BytecodeStatement when possible.
|
||||||
|
- VarDeclStatement now includes initializerObjClass (compile-time type hint).
|
||||||
|
|
||||||
|
Known failing test
|
||||||
|
- ScriptTest.testForInIterableUnknownTypeDisasm (jvmTest)
|
||||||
|
Failure: BytecodeFallbackException "Member call requires compile-time receiver type: add"
|
||||||
|
Location: stdlib root.lyng filter() -> "val result = []" then "result.add(item)"
|
||||||
|
Current debug shows LocalSlotRef(result) slotClass/nameClass/initClass all null.
|
||||||
|
|
||||||
|
Likely cause
|
||||||
|
- Initializer type inference for "val result = []" in stdlib is not reaching BytecodeCompiler:
|
||||||
|
- stdlib is generated at build time; initializer is wrapped in BytecodeStatement and losing ListLiteralRef info.
|
||||||
|
- Need a stable compile-time type hint from the compiler or a way to preserve list literal info.
|
||||||
|
|
||||||
|
Potential fixes to pursue
|
||||||
|
1) Preserve ObjRef for var initializers (e.g., store initializer ref/type in VarDeclStatement).
|
||||||
|
2) When initializer is BytecodeStatement, use its CmdFunction to detect list/range literal usage.
|
||||||
|
3) Ensure stdlib compilation sets initializerObjClass for list literals.
|
||||||
|
|
||||||
|
Files touched recently
|
||||||
|
- notes/type_system_spec.md (spec updated)
|
||||||
|
- AGENTS.md (type inference reminders)
|
||||||
|
- lynglib/src/commonMain/kotlin/net/sergeych/lyng/VarDeclStatement.kt
|
||||||
|
- lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt
|
||||||
|
- lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt
|
||||||
|
- lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt
|
||||||
|
- various bytecode runtime/disassembler files (memberId ops)
|
||||||
|
|
||||||
|
Last test run
|
||||||
|
- ./gradlew :lynglib:jvmTest --tests ScriptTest.testForInIterableUnknownTypeDisasm
|
||||||
|
|
||||||
|
Spec decisions (notes/type_system_spec.md)
|
||||||
|
- Nullability: Kotlin-style, T non-null, T? nullable, !! asserts non-null.
|
||||||
|
- void is singleton of class Void (syntax sugar).
|
||||||
|
- Untyped params default to Object (non-null); syntax sugar: fun foo(x?) and class X(a,b?).
|
||||||
|
- Object member access requires explicit cast; remove inspect, use toInspectString().
|
||||||
@ -7,8 +7,9 @@
|
|||||||
- Keep metaprogramming via explicit reflective APIs only.
|
- Keep metaprogramming via explicit reflective APIs only.
|
||||||
|
|
||||||
## Non-Goals (initial phase)
|
## Non-Goals (initial phase)
|
||||||
- Dynamic by-name lookup as part of core execution path.
|
- firbidden: Dynamic by-name lookup as part of core execution path.
|
||||||
- Runtime scope walking to discover names.
|
|
||||||
|
- forbidden: Runtime scope walking to discover names.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
Compilation is split into two passes:
|
Compilation is split into two passes:
|
||||||
|
|||||||
254
notes/new_lyng_type_system_spec.md
Normal file
254
notes/new_lyng_type_system_spec.md
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
The goal is to add types with minimal changes to existing language,
|
||||||
|
using inference as wide as possible, and providing generic styles
|
||||||
|
better than even in C++ ;)
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
fun t(x) {
|
||||||
|
// x is Object, and x is nullable
|
||||||
|
println(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
t(3) // ok
|
||||||
|
```
|
||||||
|
|
||||||
|
Inferred by defaults:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
fun t1(a=0,b="foo") {
|
||||||
|
// a is Int, b is String
|
||||||
|
}
|
||||||
|
class Point(x=0.0, y=0.0) // Real, Real
|
||||||
|
```
|
||||||
|
val/ver declaration infers the type:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
val x = 3 // INT
|
||||||
|
|
||||||
|
extern fun obj_fun(): Object
|
||||||
|
extern fun int_fun(): Int
|
||||||
|
|
||||||
|
val i = int_fun() // inferred as Int
|
||||||
|
val o = obj_fun() // inferred as Object
|
||||||
|
|
||||||
|
val x: Int = objfun() // compiler generates "objfun as Int"
|
||||||
|
val y = objfun() as Int // Int, inferred
|
||||||
|
```
|
||||||
|
|
||||||
|
Inferring result:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
fun ifun1(a=0) = a+1 // type is (Int)->Int, inferred
|
||||||
|
fun ifun2(a=0) { a+1 } // type is (Int)->Int, inferred
|
||||||
|
fun ifun3(a=0) { void } // type is void inferred
|
||||||
|
fun ifun4(a: Int): Void { }
|
||||||
|
fun ifun5() { return 5 } // Inferred, ()->Int
|
||||||
|
```
|
||||||
|
|
||||||
|
In more complex cases we will use type parameters, like in Kotlin, Scala, Typescript:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
fun f1<T,R>(a: T,b: T): R {
|
||||||
|
(a + b) as R
|
||||||
|
}
|
||||||
|
// Type parameters are first class citizens:
|
||||||
|
fun f<T>(x: T) = T::class.name + "!"
|
||||||
|
assertEquals( "String!", f("foo") )
|
||||||
|
```
|
||||||
|
When the compiler ecnounters template types, it adds it as invisible parameters to a fun/method
|
||||||
|
call, or a class instance (to constructor, much the same as with a fun), so it is usable inside
|
||||||
|
the function/class body, as a `Class` instance:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
class Test<P> {
|
||||||
|
fun P_is_Int() = P is Int
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
So we do not expand templates, but we store generic types. Types can have bounds, like in Typescript:
|
||||||
|
|
||||||
|
```
|
||||||
|
class Foo<T: Iterable & Comparable> {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
class Bar<T: Iterable | Comparable> {
|
||||||
|
val description by lazy {
|
||||||
|
if( T is Iterable ) "iterable and maybe comparable"
|
||||||
|
else "comparable only"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// compile time error:
|
||||||
|
assertEquals( "comparable only", Bar(42).description )
|
||||||
|
assertEquals( "iterable and maybe comparable", Bar([42]).description )
|
||||||
|
```
|
||||||
|
|
||||||
|
When bounds are specified, they are checked at compile time. Type compositions also can be used
|
||||||
|
when declare types much the same:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
fun x10(x: Int | Real) {
|
||||||
|
x * 10
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals( 20, x10(2) )
|
||||||
|
assert( x10(2) is Int )
|
||||||
|
|
||||||
|
assertEquals(25, x10(2.5) )
|
||||||
|
assert( x10(2.5) is Real )
|
||||||
|
|
||||||
|
// the followinf is a compile time error:
|
||||||
|
x10("20") // string does not fit bounds
|
||||||
|
```
|
||||||
|
It is possible to create bounds that probably can't be satisfied:
|
||||||
|
```lyng
|
||||||
|
fun x10(x: Int & String) {
|
||||||
|
// ....
|
||||||
|
}
|
||||||
|
```
|
||||||
|
but in fact some strange programmer can create `class T: String, Int` so we won't check it for sanity, except that we certainly disallow <T: Void &...>
|
||||||
|
|
||||||
|
Instead of template expansions, we might provide explicit `inline` later.
|
||||||
|
|
||||||
|
Notes and open questions to answer in this spec:
|
||||||
|
- Void vs void: Void is a class, and void is the same as Void (alias).
|
||||||
|
|
||||||
|
`void` is a singleton of class Void; so `return void` is ok. `fun (): void` is allowed as syntax sugar and is checked as returning Void.
|
||||||
|
|
||||||
|
- Nullability rule: Are all types nullable by default (Object?) or non-null by default (Object), and how is nullable spelled? (e.g., Object?, Int?)
|
||||||
|
|
||||||
|
Not null by default (Object), must be specified with `?` suffix. We use Kotlin-style `!!` for non-null assertion. Therefore we check nullability at compile time, and we throw NPE only at `x!!` or `obj as X` (if obj is nullable, it is same as `obj!! as X`).
|
||||||
|
|
||||||
|
- Default type of untyped values: If a parameter has no type and no default, is it Object? (dynamic), or a new top type?
|
||||||
|
|
||||||
|
Lets discuss in more details. For example:
|
||||||
|
```lyng
|
||||||
|
var x // Unset, yet no type, no type check
|
||||||
|
x = 1 // set to 1, since now type is Int and is checled
|
||||||
|
x = "11" // error. type is already determined
|
||||||
|
```
|
||||||
|
With classes it is more interesting:
|
||||||
|
```lyng
|
||||||
|
// we assume it is x: Object, y: Object, no need to specify type
|
||||||
|
class Point(x,y) {
|
||||||
|
val d by lazy { sqrt(x*x + y*y) } // real!
|
||||||
|
}
|
||||||
|
assert( 5, Point(3,4).distance.roundToInt() )
|
||||||
|
// and this is ok too:
|
||||||
|
assert( 5, Point(3,4.0).distance.roundToInt() )
|
||||||
|
```
|
||||||
|
|
||||||
|
Syntax sugar for parameters:
|
||||||
|
- `fun foo(x?)` means `fun foo(x: Object?)`
|
||||||
|
- `class X(a, b?)` means `class X(a: Object, b: Object?)`
|
||||||
|
- `fun foo(x=3?)` means `fun foo(x: Int?) { ... }` with a nullable default
|
||||||
|
|
||||||
|
Untyped parameters:
|
||||||
|
- `fun foo(x)` means `x: Object`
|
||||||
|
- `fun foo(x?)` means `x: Object?`
|
||||||
|
- `fun foo(x=3)` means `x: Int`
|
||||||
|
- `fun foo(x=3?)` means `x: Int?`
|
||||||
|
|
||||||
|
Untyped vars and vals:
|
||||||
|
- `var x` is `Unset`. First assignment fixes its type (including nullability).
|
||||||
|
- If first assignment is `null`, the type becomes `Object?`.
|
||||||
|
- `val x = null` is allowed; type is `Null` (practically not useful and cannot be reassigned).
|
||||||
|
- `var x = null` is allowed; type is `Object?`.
|
||||||
|
|
||||||
|
Class-scope val initialization:
|
||||||
|
- `val x` at class scope is Unset until initialized
|
||||||
|
- it must be initialized by the constructor or init blocks
|
||||||
|
- indirect initialization via nested `run {}` or other delayed blocks should be disallowed (unless we add an explicit rule later)
|
||||||
|
|
||||||
|
|
||||||
|
- Implicit runtime checks: When assigning/calling with a declared type, do we insert "as Type" automatically, or require explicit casts? If inserted, what exception is thrown on failure?
|
||||||
|
|
||||||
|
We check that it is possible using inference or declared type, and if it _is possible_, and if it _is necessary_ we insert `as Type`:
|
||||||
|
```lyng
|
||||||
|
(3 as Int) // no insert, it is Int
|
||||||
|
val x: Object = // ...
|
||||||
|
(x as Real) // insert, possible, necessary
|
||||||
|
((x as Real) as String) // strange but still possible unless Real and Int will be final types (we don't have yet?)
|
||||||
|
(3 as String) // compile time error, impossible
|
||||||
|
```
|
||||||
|
Necessary means: if the compile-time type is fully known and assignable, emit direct assignment. If it is not fully known at compile time but potentially compatible, emit `(x as T)` which can throw `ClassCastException` at runtime.
|
||||||
|
|
||||||
|
- Numeric literals: default Int or Real? Is there literal suffix syntax? What about overflow?
|
||||||
|
|
||||||
|
as in docs, `3` is `Int`, `3.0` is `Real`. I think it is already implemented properly.
|
||||||
|
|
||||||
|
Let's not allow type conversion in `as`: let it only look for a proper `this` in inheritance graph and process nullability. `3.14.toInt()` checks for overflow, but (3.14 as Int) is a compile time error, as Int has no base class Real.
|
||||||
|
|
||||||
|
- Union/Intersection runtime checks: If T is inferred from value, are T | U bounds checked at compile time, runtime, or both?
|
||||||
|
|
||||||
|
At compile time, checked at call site. If the compile time exchaustive check is possible, we don't emit runtime check. Otherwise, we emit (x as T):
|
||||||
|
```lyng
|
||||||
|
// here no checks, any check is at the call site.
|
||||||
|
fun square<T: Int | Real>(x: T) = x*x
|
||||||
|
|
||||||
|
// this one checks at runtime as x is Object, and we have no idea at compile time what it is:
|
||||||
|
fun f(x) = suqare(x)
|
||||||
|
|
||||||
|
// this one checks at compile time only, no runtime checks:
|
||||||
|
fun f1(x: Int) = square(x)
|
||||||
|
|
||||||
|
// Here the compiler checks, but no runtime checks:
|
||||||
|
f1(100)
|
||||||
|
|
||||||
|
// and this is comlipe time error:
|
||||||
|
square("3.14")
|
||||||
|
```
|
||||||
|
|
||||||
|
- Generics runtime model: Are type params reified via hidden Class args always, or only when used (T::class, T is ...)? How does this interact with Kotlin interop?
|
||||||
|
|
||||||
|
I think we can omit if not used. For kotlin interop: if the class has at least one `extern` symbol, that means native implementation, we always include type parameters, to kotlin implementation can rely on it.
|
||||||
|
|
||||||
|
- Member access rules: If a variable is Object (dynamic), is member access a compile-time error, or allowed with fallback (which we are trying to remove)? If error, do we require explicit cast first?
|
||||||
|
|
||||||
|
Compile time error unless it is an Object own method. Let's force rewriting existing code in favor of explicit casts. It will repay itself: I laready have a project on Lyng that suffers from implicit casts har to trace errors.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```lyng
|
||||||
|
fun f(x) { // x: Object
|
||||||
|
x.size() // compile time error
|
||||||
|
val s = (x as String).size() // ok
|
||||||
|
val l = (x as List).size() // ok
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Object methods:
|
||||||
|
- remove `inspect` from Object (too valuable a name)
|
||||||
|
- prefer `toInspectString()` as a reserved method
|
||||||
|
- keep `toString()` as Object method
|
||||||
|
- if we need extra metadata later, use explicit helpers like `Object.getHashCode(obj)`
|
||||||
|
|
||||||
|
- Builtin classes inheritance: Are Int/String final? If so, is "class T: String, Int" forbidden (and thus Int & String is unsatisfiable but still allowed)?
|
||||||
|
|
||||||
|
What keyword we did used for final vals/vars/funs? "closed"? Anyway I am uncertain whether to make Int or String closed, it is a discussion subject. But if we have some closed independent classes A, B, <T: A & B> is a compile time error.
|
||||||
|
|
||||||
|
Contextual typing rules (minimal):
|
||||||
|
1. If a contextual/expected type is known (declared type, parameter type, return position), use it to type literals, `Unset`, and empty collections.
|
||||||
|
2. Otherwise infer from literal contents:
|
||||||
|
- `[]` is `List<Object>` (non-null elements).
|
||||||
|
- non-empty list uses union element type with nullability.
|
||||||
|
- map literals infer from keys and values.
|
||||||
|
3. If contextual and literal inference disagree, it is a compile-time error.
|
||||||
|
4. Declared type is never erased by inference.
|
||||||
|
|
||||||
|
List inference:
|
||||||
|
- Empty list: `[]` is `List<Object>`.
|
||||||
|
- Non-empty list literal infers union type and nullability:
|
||||||
|
- `[1, 2, null]` is `List<Int?>`.
|
||||||
|
- `[1, 2, "3"]` is `List<Int|String>`.
|
||||||
|
Normalize unions by removing duplicates and collapsing nullability (e.g. `Int|Int?` -> `Int?`).
|
||||||
|
|
||||||
|
Map inference:
|
||||||
|
- `{ "a": 1, "b": 2 }` is `Map<String,Int>`.
|
||||||
|
- Empty map literal uses `{:}` (since `{}` is empty callable).
|
||||||
|
- `extern class Map<K=String,V=Object>` so `Map()` is `Map<String,Object>()` unless contextual type overrides.
|
||||||
|
|
||||||
|
Flow typing:
|
||||||
|
- Compiler should narrow types based on control-flow (e.g., `if (x != null)` narrows `x` to non-null inside the branch).
|
||||||
|
- Flow typing is permissive: it only makes types more precise.
|
||||||
|
- If a branch asserts a type that is impossible (e.g., `x is Int` then `x as String`), it is a compile-time error.
|
||||||
|
|
||||||
|
Class-scope val initialization:
|
||||||
|
- `val` must be initialized either in place or on every execution path in `init`, except paths that explicitly throw.
|
||||||
Loading…
x
Reference in New Issue
Block a user