1.2.0-SNAPSHOT started bug core refactor to support all new future and more strict rules (namely, fundamental difference between properties, functions and delegates)
This commit is contained in:
parent
10b7cb2db2
commit
b9831a422a
1
.gitignore
vendored
1
.gitignore
vendored
@ -26,3 +26,4 @@ debug.log
|
|||||||
/check_output.txt
|
/check_output.txt
|
||||||
/compile_jvm_output.txt
|
/compile_jvm_output.txt
|
||||||
/compile_metadata_output.txt
|
/compile_metadata_output.txt
|
||||||
|
test_output*.txt
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
|||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
group = "net.sergeych"
|
group = "net.sergeych"
|
||||||
version = "1.1.1-SNAPSHOT"
|
version = "1.2.-SNAPSHOT"
|
||||||
|
|
||||||
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below
|
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjClass
|
||||||
import net.sergeych.lyng.obj.ObjRecord
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,138 +40,34 @@ class ClosureScope(val callScope: Scope, val closureScope: Scope) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun get(name: String): ObjRecord? {
|
override fun get(name: String): ObjRecord? {
|
||||||
// Fast-path built-ins
|
|
||||||
if (name == "this") return thisObj.asReadonly
|
if (name == "this") return thisObj.asReadonly
|
||||||
|
|
||||||
// Priority:
|
// 1. Current frame locals (parameters, local variables)
|
||||||
// 1) Locals and arguments declared in this lambda frame (including values defined before suspension)
|
tryGetLocalRecord(this, name, currentClassCtx)?.let { return it }
|
||||||
// 2) Instance/class members of the captured receiver (`closureScope.thisObj`)
|
|
||||||
// 3) Symbols from the captured closure scope chain (locals/parents), ignoring nested ClosureScope overrides
|
|
||||||
// 4) Instance members of the caller's `this` (e.g., FlowBuilder.emit)
|
|
||||||
// 5) Symbols from the caller chain (locals/parents), ignoring nested ClosureScope overrides
|
|
||||||
// 6) Special fallback for module pseudo-symbols (e.g., __PACKAGE__)
|
|
||||||
|
|
||||||
// 1) Locals/arguments in this closure frame
|
// 2. Lexical environment (captured locals from entire ancestry)
|
||||||
super.objects[name]?.let { return it }
|
|
||||||
super.localBindings[name]?.let { return it }
|
|
||||||
|
|
||||||
// 1a) Priority: if we are in a class context, prefer our own private members to support
|
|
||||||
// non-virtual private dispatch. This prevents a subclass from accidentally
|
|
||||||
// capturing a private member call from a base class.
|
|
||||||
// We only return non-field/non-property members (methods) here; fields must
|
|
||||||
// be resolved via instance storage in priority 2.
|
|
||||||
currentClassCtx?.let { ctx ->
|
|
||||||
ctx.members[name]?.let { rec ->
|
|
||||||
if (rec.visibility == Visibility.Private &&
|
|
||||||
rec.type != net.sergeych.lyng.obj.ObjRecord.Type.Field &&
|
|
||||||
rec.type != net.sergeych.lyng.obj.ObjRecord.Type.Property) return rec
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1b) Captured locals from the entire closure ancestry. This ensures that parameters
|
|
||||||
// and local variables shadow members of captured receivers, matching standard
|
|
||||||
// lexical scoping rules.
|
|
||||||
closureScope.chainLookupIgnoreClosure(name, followClosure = true)?.let { return it }
|
closureScope.chainLookupIgnoreClosure(name, followClosure = true)?.let { return it }
|
||||||
|
|
||||||
// 2) Members on the captured receiver instance
|
// 3. Lexical this members (captured receiver)
|
||||||
(closureScope.thisObj as? net.sergeych.lyng.obj.ObjInstance)?.let { inst ->
|
val receiver = thisObj
|
||||||
// Check direct locals in instance scope (unmangled)
|
val effectiveClass = receiver as? ObjClass ?: receiver.objClass
|
||||||
inst.instanceScope.objects[name]?.let { rec ->
|
for (cls in effectiveClass.mro) {
|
||||||
if (rec.type != net.sergeych.lyng.obj.ObjRecord.Type.Property &&
|
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
||||||
canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) return rec
|
if (rec != null && !rec.isAbstract) {
|
||||||
}
|
if (canAccessMember(rec.visibility, rec.declaringClass ?: cls, currentClassCtx)) {
|
||||||
// Check mangled names for fields along MRO
|
return rec.copy(receiver = receiver)
|
||||||
for (cls in inst.objClass.mro) {
|
|
||||||
inst.instanceScope.objects["${cls.className}::$name"]?.let { rec ->
|
|
||||||
if (rec.type != net.sergeych.lyng.obj.ObjRecord.Type.Property &&
|
|
||||||
canAccessMember(rec.visibility, rec.declaringClass ?: cls, currentClassCtx)) return rec
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Finally, root object fallback
|
||||||
findExtension(closureScope.thisObj.objClass, name)?.let { return it }
|
Obj.rootObjectType.members[name]?.let { rec ->
|
||||||
closureScope.thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec ->
|
|
||||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) {
|
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) {
|
||||||
// Return only non-field/non-property members (methods) from class-level records.
|
return rec.copy(receiver = receiver)
|
||||||
// Fields and properties must be resolved via instance storage (mangled) or readField.
|
|
||||||
if (rec.type != net.sergeych.lyng.obj.ObjRecord.Type.Field &&
|
|
||||||
rec.type != net.sergeych.lyng.obj.ObjRecord.Type.Property &&
|
|
||||||
!rec.isAbstract) return rec
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Closure scope chain (locals/parents + members), ignore ClosureScope overrides to prevent recursion
|
// 4. Call environment (caller locals, caller this, and global fallback)
|
||||||
closureScope.chainLookupWithMembers(name, currentClassCtx, followClosure = true)?.let { return it }
|
return callScope.get(name)
|
||||||
|
|
||||||
// 4) Caller `this` members
|
|
||||||
(callScope.thisObj as? net.sergeych.lyng.obj.ObjInstance)?.let { inst ->
|
|
||||||
// Check direct locals in instance scope (unmangled)
|
|
||||||
inst.instanceScope.objects[name]?.let { rec ->
|
|
||||||
if (rec.type != net.sergeych.lyng.obj.ObjRecord.Type.Property &&
|
|
||||||
canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) return rec
|
|
||||||
}
|
|
||||||
// Check mangled names for fields along MRO
|
|
||||||
for (cls in inst.objClass.mro) {
|
|
||||||
inst.instanceScope.objects["${cls.className}::$name"]?.let { rec ->
|
|
||||||
if (rec.type != net.sergeych.lyng.obj.ObjRecord.Type.Property &&
|
|
||||||
canAccessMember(rec.visibility, rec.declaringClass ?: cls, currentClassCtx)) return rec
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
findExtension(callScope.thisObj.objClass, name)?.let { return it }
|
|
||||||
callScope.thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec ->
|
|
||||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) {
|
|
||||||
if (rec.type != net.sergeych.lyng.obj.ObjRecord.Type.Field &&
|
|
||||||
rec.type != net.sergeych.lyng.obj.ObjRecord.Type.Property &&
|
|
||||||
!rec.isAbstract) return rec
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5) Caller chain (locals/parents + members)
|
|
||||||
callScope.chainLookupWithMembers(name, currentClassCtx)?.let { return it }
|
|
||||||
|
|
||||||
// 6) Module pseudo-symbols (e.g., __PACKAGE__) — walk caller ancestry and query ModuleScope directly
|
|
||||||
if (name.startsWith("__")) {
|
|
||||||
var s: Scope? = callScope
|
|
||||||
val visited = HashSet<Long>(4)
|
|
||||||
while (s != null) {
|
|
||||||
if (!visited.add(s.frameId)) break
|
|
||||||
if (s is ModuleScope) return s.get(name)
|
|
||||||
s = s.parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7) Direct module/global fallback: try to locate nearest ModuleScope and check its own locals
|
|
||||||
fun lookupInModuleAncestry(from: Scope): ObjRecord? {
|
|
||||||
var s: Scope? = from
|
|
||||||
val visited = HashSet<Long>(4)
|
|
||||||
while (s != null) {
|
|
||||||
if (!visited.add(s.frameId)) break
|
|
||||||
if (s is ModuleScope) {
|
|
||||||
s.objects[name]?.let { return it }
|
|
||||||
s.localBindings[name]?.let { return it }
|
|
||||||
// check immediate parent (root scope) locals/constants for globals like `delay`
|
|
||||||
val p = s.parent
|
|
||||||
if (p != null) {
|
|
||||||
p.objects[name]?.let { return it }
|
|
||||||
p.localBindings[name]?.let { return it }
|
|
||||||
p.thisObj.objClass.getInstanceMemberOrNull(name)?.let { return it }
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
s = s.parent
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
lookupInModuleAncestry(closureScope)?.let { return it }
|
|
||||||
lookupInModuleAncestry(callScope)?.let { return it }
|
|
||||||
|
|
||||||
// 8) Global root scope constants/functions (e.g., delay, yield) via current import provider
|
|
||||||
runCatching { this.currentImportProvider.rootScope.objects[name] }.getOrNull()?.let { return it }
|
|
||||||
|
|
||||||
// Final safe fallback: base scope lookup from this frame walking raw parents
|
|
||||||
return baseGetIgnoreClosure(name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -667,19 +667,25 @@ class Compiler(
|
|||||||
"lambda must have either valid arguments declaration with '->' or no arguments"
|
"lambda must have either valid arguments declaration with '->' or no arguments"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val paramNames = argsDeclaration?.params?.map { it.name } ?: emptyList()
|
||||||
|
|
||||||
label?.let { cc.labels.add(it) }
|
label?.let { cc.labels.add(it) }
|
||||||
val body = parseBlock(skipLeadingBrace = true)
|
val body = inCodeContext(CodeContext.Function("<lambda>")) {
|
||||||
|
withLocalNames(paramNames.toSet()) {
|
||||||
|
parseBlock(skipLeadingBrace = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
label?.let { cc.labels.remove(it) }
|
label?.let { cc.labels.remove(it) }
|
||||||
|
|
||||||
return ValueFnRef { closureScope ->
|
return ValueFnRef { closureScope ->
|
||||||
statement {
|
statement(body.pos) { scope ->
|
||||||
// and the source closure of the lambda which might have other thisObj.
|
// and the source closure of the lambda which might have other thisObj.
|
||||||
val context = this.applyClosure(closureScope)
|
val context = scope.applyClosure(closureScope)
|
||||||
// Execute lambda body in a closure-aware context. Blocks inside the lambda
|
// Execute lambda body in a closure-aware context. Blocks inside the lambda
|
||||||
// will create child scopes as usual, so re-declarations inside loops work.
|
// will create child scopes as usual, so re-declarations inside loops work.
|
||||||
if (argsDeclaration == null) {
|
if (argsDeclaration == null) {
|
||||||
// no args: automatic var 'it'
|
// no args: automatic var 'it'
|
||||||
val l = args.list
|
val l = scope.args.list
|
||||||
val itValue: Obj = when (l.size) {
|
val itValue: Obj = when (l.size) {
|
||||||
// no args: it == void
|
// no args: it == void
|
||||||
0 -> ObjVoid
|
0 -> ObjVoid
|
||||||
@ -2192,20 +2198,6 @@ class Compiler(
|
|||||||
for (s in initScope)
|
for (s in initScope)
|
||||||
s.execute(classScope)
|
s.execute(classScope)
|
||||||
}
|
}
|
||||||
// Fallback: ensure any functions declared in class scope are also present as instance methods
|
|
||||||
// (defensive in case some paths skipped cls.addFn during parsing/execution ordering)
|
|
||||||
for ((k, rec) in classScope.objects) {
|
|
||||||
val v = rec.value
|
|
||||||
if (v is Statement) {
|
|
||||||
if (newClass.members[k] == null) {
|
|
||||||
newClass.addFn(k, isMutable = true, pos = rec.importedFrom?.pos ?: nameToken.pos) {
|
|
||||||
(thisObj as? ObjInstance)?.let { i ->
|
|
||||||
v.execute(ClosureScope(this, i.instanceScope))
|
|
||||||
} ?: v.execute(thisObj.autoInstanceScope(this))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newClass.checkAbstractSatisfaction(nameToken.pos)
|
newClass.checkAbstractSatisfaction(nameToken.pos)
|
||||||
// Debug summary: list registered instance methods and class-scope functions for this class
|
// Debug summary: list registered instance methods and class-scope functions for this class
|
||||||
newClass
|
newClass
|
||||||
@ -2292,7 +2284,7 @@ class Compiler(
|
|||||||
} else if (sourceObj.isInstanceOf(ObjIterable)) {
|
} else if (sourceObj.isInstanceOf(ObjIterable)) {
|
||||||
loopIterable(forContext, sourceObj, loopSO, body, elseStatement, label, canBreak)
|
loopIterable(forContext, sourceObj, loopSO, body, elseStatement, label, canBreak)
|
||||||
} else {
|
} else {
|
||||||
val size = runCatching { sourceObj.invokeInstanceMethod(forContext, "size").toInt() }
|
val size = runCatching { sourceObj.readField(forContext, "size").value.toInt() }
|
||||||
.getOrElse {
|
.getOrElse {
|
||||||
throw ScriptError(
|
throw ScriptError(
|
||||||
tOp.pos,
|
tOp.pos,
|
||||||
@ -3079,31 +3071,88 @@ class Compiler(
|
|||||||
val mark = cc.savePos()
|
val mark = cc.savePos()
|
||||||
cc.restorePos(markBeforeEq)
|
cc.restorePos(markBeforeEq)
|
||||||
cc.skipWsTokens()
|
cc.skipWsTokens()
|
||||||
val next = cc.peekNextNonWhitespace()
|
|
||||||
if (next.isId("get") || next.isId("set") || next.isId("private") || next.isId("protected")) {
|
// Heuristic: if we see 'get(' or 'set(' or 'private set(' or 'protected set(',
|
||||||
|
// look ahead for a body.
|
||||||
|
fun hasAccessorWithBody(): Boolean {
|
||||||
|
val t = cc.peekNextNonWhitespace()
|
||||||
|
if (t.isId("get") || t.isId("set")) {
|
||||||
|
val saved = cc.savePos()
|
||||||
|
cc.next() // consume get/set
|
||||||
|
val nextToken = cc.peekNextNonWhitespace()
|
||||||
|
if (nextToken.type == Token.Type.LPAREN) {
|
||||||
|
cc.next() // consume (
|
||||||
|
var depth = 1
|
||||||
|
while (cc.hasNext() && depth > 0) {
|
||||||
|
val tt = cc.next()
|
||||||
|
if (tt.type == Token.Type.LPAREN) depth++
|
||||||
|
else if (tt.type == Token.Type.RPAREN) depth--
|
||||||
|
}
|
||||||
|
val next = cc.peekNextNonWhitespace()
|
||||||
|
if (next.type == Token.Type.LBRACE || next.type == Token.Type.ASSIGN) {
|
||||||
|
cc.restorePos(saved)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if (nextToken.type == Token.Type.LBRACE || nextToken.type == Token.Type.ASSIGN) {
|
||||||
|
cc.restorePos(saved)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
cc.restorePos(saved)
|
||||||
|
} else if (t.isId("private") || t.isId("protected")) {
|
||||||
|
val saved = cc.savePos()
|
||||||
|
cc.next() // consume modifier
|
||||||
|
if (cc.skipWsTokens().isId("set")) {
|
||||||
|
cc.next() // consume set
|
||||||
|
val nextToken = cc.peekNextNonWhitespace()
|
||||||
|
if (nextToken.type == Token.Type.LPAREN) {
|
||||||
|
cc.next() // consume (
|
||||||
|
var depth = 1
|
||||||
|
while (cc.hasNext() && depth > 0) {
|
||||||
|
val tt = cc.next()
|
||||||
|
if (tt.type == Token.Type.LPAREN) depth++
|
||||||
|
else if (tt.type == Token.Type.RPAREN) depth--
|
||||||
|
}
|
||||||
|
val next = cc.peekNextNonWhitespace()
|
||||||
|
if (next.type == Token.Type.LBRACE || next.type == Token.Type.ASSIGN) {
|
||||||
|
cc.restorePos(saved)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if (nextToken.type == Token.Type.LBRACE || nextToken.type == Token.Type.ASSIGN) {
|
||||||
|
cc.restorePos(saved)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cc.restorePos(saved)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAccessorWithBody()) {
|
||||||
isProperty = true
|
isProperty = true
|
||||||
cc.restorePos(markBeforeEq)
|
cc.restorePos(markBeforeEq)
|
||||||
cc.skipWsTokens()
|
// Do not consume eqToken if it's an accessor keyword
|
||||||
} else {
|
} else {
|
||||||
cc.restorePos(mark)
|
cc.restorePos(mark)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val effectiveEqToken = if (isProperty) null else eqToken
|
||||||
|
|
||||||
// Register the local name at compile time so that subsequent identifiers can be emitted as fast locals
|
// Register the local name at compile time so that subsequent identifiers can be emitted as fast locals
|
||||||
if (!isStatic) declareLocalName(name)
|
if (!isStatic) declareLocalName(name)
|
||||||
|
|
||||||
val isDelegate = if (isAbstract || actualExtern) {
|
val isDelegate = if (isAbstract || actualExtern) {
|
||||||
if (!isProperty && (eqToken.type == Token.Type.ASSIGN || eqToken.type == Token.Type.BY))
|
if (!isProperty && (effectiveEqToken?.type == Token.Type.ASSIGN || effectiveEqToken?.type == Token.Type.BY))
|
||||||
throw ScriptError(eqToken.pos, "${if (isAbstract) "abstract" else "extern"} variable $name cannot have an initializer or delegate")
|
throw ScriptError(effectiveEqToken.pos, "${if (isAbstract) "abstract" else "extern"} variable $name cannot have an initializer or delegate")
|
||||||
// Abstract or extern variables don't have initializers
|
// Abstract or extern variables don't have initializers
|
||||||
cc.restorePos(markBeforeEq)
|
cc.restorePos(markBeforeEq)
|
||||||
cc.skipWsTokens()
|
cc.skipWsTokens()
|
||||||
setNull = true
|
setNull = true
|
||||||
false
|
false
|
||||||
} else if (!isProperty && eqToken.type == Token.Type.BY) {
|
} else if (!isProperty && effectiveEqToken?.type == Token.Type.BY) {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
if (!isProperty && eqToken.type != Token.Type.ASSIGN) {
|
if (!isProperty && effectiveEqToken?.type != Token.Type.ASSIGN) {
|
||||||
if (!isMutable && (declaringClassNameCaptured == null) && (extTypeName == null))
|
if (!isMutable && (declaringClassNameCaptured == null) && (extTypeName == null))
|
||||||
throw ScriptError(start, "val must be initialized")
|
throw ScriptError(start, "val must be initialized")
|
||||||
else if (!isMutable && declaringClassNameCaptured != null && extTypeName == null) {
|
else if (!isMutable && declaringClassNameCaptured != null && extTypeName == null) {
|
||||||
@ -3123,12 +3172,12 @@ class Compiler(
|
|||||||
|
|
||||||
val initialExpression = if (setNull || isProperty) null
|
val initialExpression = if (setNull || isProperty) null
|
||||||
else parseStatement(true)
|
else parseStatement(true)
|
||||||
?: throw ScriptError(eqToken.pos, "Expected initializer expression")
|
?: throw ScriptError(effectiveEqToken!!.pos, "Expected initializer expression")
|
||||||
|
|
||||||
// Emit MiniValDecl for this declaration (before execution wiring), attach doc if any
|
// Emit MiniValDecl for this declaration (before execution wiring), attach doc if any
|
||||||
run {
|
run {
|
||||||
val declRange = MiniRange(pendingDeclStart ?: start, cc.currentPos())
|
val declRange = MiniRange(pendingDeclStart ?: start, cc.currentPos())
|
||||||
val initR = if (setNull || isProperty) null else MiniRange(eqToken.pos, cc.currentPos())
|
val initR = if (setNull || isProperty) null else MiniRange(effectiveEqToken!!.pos, cc.currentPos())
|
||||||
val node = MiniValDecl(
|
val node = MiniValDecl(
|
||||||
range = declRange,
|
range = declRange,
|
||||||
name = name,
|
name = name,
|
||||||
@ -3193,17 +3242,24 @@ class Compiler(
|
|||||||
if (t.isId("get")) {
|
if (t.isId("get")) {
|
||||||
val getStart = cc.currentPos()
|
val getStart = cc.currentPos()
|
||||||
cc.next() // consume 'get'
|
cc.next() // consume 'get'
|
||||||
cc.requireToken(Token.Type.LPAREN)
|
if (cc.peekNextNonWhitespace().type == Token.Type.LPAREN) {
|
||||||
cc.requireToken(Token.Type.RPAREN)
|
cc.next() // consume (
|
||||||
|
cc.requireToken(Token.Type.RPAREN)
|
||||||
|
}
|
||||||
miniSink?.onEnterFunction(null)
|
miniSink?.onEnterFunction(null)
|
||||||
getter = if (cc.peekNextNonWhitespace().type == Token.Type.LBRACE) {
|
getter = if (cc.peekNextNonWhitespace().type == Token.Type.LBRACE) {
|
||||||
cc.skipWsTokens()
|
cc.skipWsTokens()
|
||||||
parseBlock()
|
inCodeContext(CodeContext.Function("<getter>")) {
|
||||||
|
parseBlock()
|
||||||
|
}
|
||||||
} else if (cc.peekNextNonWhitespace().type == Token.Type.ASSIGN) {
|
} else if (cc.peekNextNonWhitespace().type == Token.Type.ASSIGN) {
|
||||||
cc.skipWsTokens()
|
cc.skipWsTokens()
|
||||||
cc.next() // consume '='
|
cc.next() // consume '='
|
||||||
val expr = parseExpression() ?: throw ScriptError(cc.current().pos, "Expected getter expression")
|
inCodeContext(CodeContext.Function("<getter>")) {
|
||||||
expr
|
val expr = parseExpression()
|
||||||
|
?: throw ScriptError(cc.current().pos, "Expected getter expression")
|
||||||
|
expr
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw ScriptError(cc.current().pos, "Expected { or = after get()")
|
throw ScriptError(cc.current().pos, "Expected { or = after get()")
|
||||||
}
|
}
|
||||||
@ -3211,26 +3267,34 @@ class Compiler(
|
|||||||
} else if (t.isId("set")) {
|
} else if (t.isId("set")) {
|
||||||
val setStart = cc.currentPos()
|
val setStart = cc.currentPos()
|
||||||
cc.next() // consume 'set'
|
cc.next() // consume 'set'
|
||||||
cc.requireToken(Token.Type.LPAREN)
|
var setArgName = "it"
|
||||||
val setArg = cc.requireToken(Token.Type.ID, "Expected setter argument name")
|
if (cc.peekNextNonWhitespace().type == Token.Type.LPAREN) {
|
||||||
cc.requireToken(Token.Type.RPAREN)
|
cc.next() // consume (
|
||||||
|
setArgName = cc.requireToken(Token.Type.ID, "Expected setter argument name").value
|
||||||
|
cc.requireToken(Token.Type.RPAREN)
|
||||||
|
}
|
||||||
miniSink?.onEnterFunction(null)
|
miniSink?.onEnterFunction(null)
|
||||||
setter = if (cc.peekNextNonWhitespace().type == Token.Type.LBRACE) {
|
setter = if (cc.peekNextNonWhitespace().type == Token.Type.LBRACE) {
|
||||||
cc.skipWsTokens()
|
cc.skipWsTokens()
|
||||||
val body = parseBlock()
|
val body = inCodeContext(CodeContext.Function("<setter>")) {
|
||||||
|
parseBlock()
|
||||||
|
}
|
||||||
statement(body.pos) { scope ->
|
statement(body.pos) { scope ->
|
||||||
val value = scope.args.list.firstOrNull() ?: ObjNull
|
val value = scope.args.list.firstOrNull() ?: ObjNull
|
||||||
scope.addItem(setArg.value, true, value, recordType = ObjRecord.Type.Argument)
|
scope.addItem(setArgName, true, value, recordType = ObjRecord.Type.Argument)
|
||||||
body.execute(scope)
|
body.execute(scope)
|
||||||
}
|
}
|
||||||
} else if (cc.peekNextNonWhitespace().type == Token.Type.ASSIGN) {
|
} else if (cc.peekNextNonWhitespace().type == Token.Type.ASSIGN) {
|
||||||
cc.skipWsTokens()
|
cc.skipWsTokens()
|
||||||
cc.next() // consume '='
|
cc.next() // consume '='
|
||||||
val expr = parseExpression() ?: throw ScriptError(cc.current().pos, "Expected setter expression")
|
val expr = inCodeContext(CodeContext.Function("<setter>")) {
|
||||||
|
parseExpression()
|
||||||
|
?: throw ScriptError(cc.current().pos, "Expected setter expression")
|
||||||
|
}
|
||||||
val st = expr
|
val st = expr
|
||||||
statement(st.pos) { scope ->
|
statement(st.pos) { scope ->
|
||||||
val value = scope.args.list.firstOrNull() ?: ObjNull
|
val value = scope.args.list.firstOrNull() ?: ObjNull
|
||||||
scope.addItem(setArg.value, true, value, recordType = ObjRecord.Type.Argument)
|
scope.addItem(setArgName, true, value, recordType = ObjRecord.Type.Argument)
|
||||||
st.execute(scope)
|
st.execute(scope)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -3274,6 +3338,8 @@ class Compiler(
|
|||||||
throw ScriptError(cc.current().pos, "Expected { or = after set(...)")
|
throw ScriptError(cc.current().pos, "Expected { or = after set(...)")
|
||||||
}
|
}
|
||||||
miniSink?.onExitFunction(cc.currentPos())
|
miniSink?.onExitFunction(cc.currentPos())
|
||||||
|
} else {
|
||||||
|
// private set without body: setter remains null, visibility is restricted
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cc.restorePos(mark)
|
cc.restorePos(mark)
|
||||||
@ -3525,7 +3591,7 @@ class Compiler(
|
|||||||
} else {
|
} else {
|
||||||
// Not in class body: regular local/var declaration
|
// Not in class body: regular local/var declaration
|
||||||
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
|
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
|
||||||
context.addItem(name, isMutable, initValue, visibility, recordType = ObjRecord.Type.Field)
|
context.addItem(name, isMutable, initValue, visibility, recordType = ObjRecord.Type.Other)
|
||||||
initValue
|
initValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -106,7 +106,7 @@ open class Scope(
|
|||||||
* intertwined closure frames. They traverse the plain parent chain and consult only locals
|
* intertwined closure frames. They traverse the plain parent chain and consult only locals
|
||||||
* and bindings of each frame. Instance/class member fallback must be decided by the caller.
|
* and bindings of each frame. Instance/class member fallback must be decided by the caller.
|
||||||
*/
|
*/
|
||||||
private fun tryGetLocalRecord(s: Scope, name: String, caller: net.sergeych.lyng.obj.ObjClass?): ObjRecord? {
|
internal fun tryGetLocalRecord(s: Scope, name: String, caller: net.sergeych.lyng.obj.ObjClass?): ObjRecord? {
|
||||||
s.objects[name]?.let { rec ->
|
s.objects[name]?.let { rec ->
|
||||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller)) return rec
|
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller)) return rec
|
||||||
}
|
}
|
||||||
@ -330,28 +330,38 @@ open class Scope(
|
|||||||
|
|
||||||
internal val objects = mutableMapOf<String, ObjRecord>()
|
internal val objects = mutableMapOf<String, ObjRecord>()
|
||||||
|
|
||||||
open operator fun get(name: String): ObjRecord? =
|
open operator fun get(name: String): ObjRecord? {
|
||||||
if (name == "this") thisObj.asReadonly
|
if (name == "this") return thisObj.asReadonly
|
||||||
else {
|
|
||||||
// Prefer direct locals/bindings declared in this frame
|
// 1. Prefer direct locals/bindings declared in this frame
|
||||||
(objects[name]?.let { rec ->
|
objects[name]?.let { rec ->
|
||||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) rec else null
|
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) return rec
|
||||||
}
|
|
||||||
// Then, check known local bindings in this frame (helps after suspension)
|
|
||||||
?: localBindings[name]?.let { rec ->
|
|
||||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) rec else null
|
|
||||||
}
|
|
||||||
// Walk up ancestry
|
|
||||||
?: parent?.get(name)
|
|
||||||
// Finally, fallback to class members on thisObj
|
|
||||||
?: thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec ->
|
|
||||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) {
|
|
||||||
if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.Property || rec.isAbstract) null
|
|
||||||
else rec
|
|
||||||
} else null
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
localBindings[name]?.let { rec ->
|
||||||
|
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) return rec
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Then, check members of thisObj
|
||||||
|
val receiver = thisObj
|
||||||
|
val effectiveClass = receiver as? ObjClass ?: receiver.objClass
|
||||||
|
for (cls in effectiveClass.mro) {
|
||||||
|
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
||||||
|
if (rec != null && !rec.isAbstract) {
|
||||||
|
if (canAccessMember(rec.visibility, rec.declaringClass ?: cls, currentClassCtx)) {
|
||||||
|
return rec.copy(receiver = receiver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Finally, root object fallback
|
||||||
|
Obj.rootObjectType.members[name]?.let { rec ->
|
||||||
|
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) {
|
||||||
|
return rec.copy(receiver = receiver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Finally, walk up ancestry
|
||||||
|
return parent?.get(name)
|
||||||
|
}
|
||||||
|
|
||||||
// Slot fast-path API
|
// Slot fast-path API
|
||||||
fun getSlotRecord(index: Int): ObjRecord = slots[index]
|
fun getSlotRecord(index: Int): ObjRecord = slots[index]
|
||||||
@ -380,6 +390,7 @@ open class Scope(
|
|||||||
// that could interact badly with the new parent and produce a cycle.
|
// that could interact badly with the new parent and produce a cycle.
|
||||||
this.parent = null
|
this.parent = null
|
||||||
this.skipScopeCreation = false
|
this.skipScopeCreation = false
|
||||||
|
this.currentClassCtx = parent?.currentClassCtx
|
||||||
// fresh identity for PIC caches
|
// fresh identity for PIC caches
|
||||||
this.frameId = nextFrameId()
|
this.frameId = nextFrameId()
|
||||||
// clear locals and slot maps
|
// clear locals and slot maps
|
||||||
@ -388,6 +399,7 @@ open class Scope(
|
|||||||
nameToSlot.clear()
|
nameToSlot.clear()
|
||||||
localBindings.clear()
|
localBindings.clear()
|
||||||
extensions.clear()
|
extensions.clear()
|
||||||
|
this.currentClassCtx = parent?.currentClassCtx
|
||||||
// Now safe to validate and re-parent
|
// Now safe to validate and re-parent
|
||||||
ensureNoCycle(parent)
|
ensureNoCycle(parent)
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
@ -628,46 +640,34 @@ open class Scope(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun resolve(rec: ObjRecord, name: String): Obj {
|
suspend fun resolve(rec: ObjRecord, name: String): Obj {
|
||||||
if (rec.type == ObjRecord.Type.Delegated) {
|
val receiver = rec.receiver ?: thisObj
|
||||||
val del = rec.delegate ?: run {
|
return receiver.resolveRecord(this, rec, name, rec.declaringClass).value
|
||||||
if (thisObj is ObjInstance) {
|
|
||||||
val res = (thisObj as ObjInstance).resolveRecord(this, rec, name, rec.declaringClass).value
|
|
||||||
rec.value = res
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
raiseError("Internal error: delegated property $name has no delegate")
|
|
||||||
}
|
|
||||||
val th = if (thisObj === ObjVoid) ObjNull else thisObj
|
|
||||||
val res = del.invokeInstanceMethod(this, "getValue", Arguments(th, ObjString(name)), onNotFoundResult = {
|
|
||||||
// If getValue not found, return a wrapper that calls invoke
|
|
||||||
object : Statement() {
|
|
||||||
override val pos: Pos = Pos.builtIn
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
|
||||||
val th2 = if (scope.thisObj === ObjVoid) ObjNull else scope.thisObj
|
|
||||||
val allArgs = (listOf(th2, ObjString(name)) + scope.args.list).toTypedArray()
|
|
||||||
return del.invokeInstanceMethod(scope, "invoke", Arguments(*allArgs))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
rec.value = res
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
return rec.value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun assign(rec: ObjRecord, name: String, newValue: Obj) {
|
suspend fun assign(rec: ObjRecord, name: String, newValue: Obj) {
|
||||||
if (rec.type == ObjRecord.Type.Delegated) {
|
if (rec.type == ObjRecord.Type.Delegated) {
|
||||||
|
val receiver = rec.receiver ?: thisObj
|
||||||
val del = rec.delegate ?: run {
|
val del = rec.delegate ?: run {
|
||||||
if (thisObj is ObjInstance) {
|
if (receiver is ObjInstance) {
|
||||||
(thisObj as ObjInstance).writeField(this, name, newValue)
|
(receiver as ObjInstance).writeField(this, name, newValue)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
raiseError("Internal error: delegated property $name has no delegate")
|
raiseError("Internal error: delegated property $name has no delegate")
|
||||||
}
|
}
|
||||||
val th = if (thisObj === ObjVoid) ObjNull else thisObj
|
val th = if (receiver === ObjVoid) ObjNull else receiver
|
||||||
del.invokeInstanceMethod(this, "setValue", Arguments(th, ObjString(name), newValue))
|
del.invokeInstanceMethod(this, "setValue", Arguments(th, ObjString(name), newValue))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (rec.value is ObjProperty) {
|
||||||
|
(rec.value as ObjProperty).callSetter(this, rec.receiver ?: thisObj, newValue, rec.declaringClass)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If it's a member (explicitly tracked by receiver or declaringClass), use writeField.
|
||||||
|
// Important: locals have receiver == null and declaringClass == null (enforced in addItem).
|
||||||
|
if (rec.receiver != null || (rec.declaringClass != null && (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.Property))) {
|
||||||
|
(rec.receiver ?: thisObj).writeField(this, name, newValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!rec.isMutable && rec.value !== ObjUnset) raiseIllegalAssignment("can't reassign val $name")
|
if (!rec.isMutable && rec.value !== ObjUnset) raiseIllegalAssignment("can't reassign val $name")
|
||||||
rec.value = newValue
|
rec.value = newValue
|
||||||
}
|
}
|
||||||
|
|||||||
@ -293,10 +293,14 @@ class Script(
|
|||||||
ObjVoid
|
ObjVoid
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delay in milliseconds (plain numeric). For time-aware variants use lyng.time.Duration API.
|
|
||||||
addVoidFn("delay") {
|
addVoidFn("delay") {
|
||||||
val ms = (this.args.firstAndOnly().toDouble()).roundToLong()
|
val a = args.firstAndOnly()
|
||||||
delay(ms)
|
when (a) {
|
||||||
|
is ObjInt -> delay(a.value)
|
||||||
|
is ObjReal -> delay((a.value * 1000).roundToLong())
|
||||||
|
is ObjDuration -> delay(a.duration)
|
||||||
|
else -> raiseIllegalArgument("Expected Int, Real or Duration, got ${a.inspect(this)}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addConst("Object", rootObjectType)
|
addConst("Object", rootObjectType)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -145,6 +145,23 @@ fun ObjClass.addClassFnDoc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ObjClass.addPropertyDoc(
|
||||||
|
name: String,
|
||||||
|
doc: String,
|
||||||
|
type: TypeDoc? = null,
|
||||||
|
visibility: Visibility = Visibility.Public,
|
||||||
|
moduleName: String? = null,
|
||||||
|
getter: (suspend Scope.() -> Obj)? = null,
|
||||||
|
setter: (suspend Scope.(Obj) -> Unit)? = null
|
||||||
|
) {
|
||||||
|
addProperty(name, getter, setter, visibility)
|
||||||
|
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {
|
||||||
|
classDoc(this@addPropertyDoc.className, doc = "") {
|
||||||
|
field(name = name, doc = doc, type = type, mutable = setter != null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun ObjClass.addClassConstDoc(
|
fun ObjClass.addClassConstDoc(
|
||||||
name: String,
|
name: String,
|
||||||
value: Obj,
|
value: Obj,
|
||||||
|
|||||||
@ -93,23 +93,45 @@ open class Obj {
|
|||||||
args: Arguments = Arguments.EMPTY,
|
args: Arguments = Arguments.EMPTY,
|
||||||
onNotFoundResult: (suspend () -> Obj?)? = null
|
onNotFoundResult: (suspend () -> Obj?)? = null
|
||||||
): Obj {
|
): Obj {
|
||||||
|
// 0. Prefer private member of current class context
|
||||||
|
scope.currentClassCtx?.let { caller ->
|
||||||
|
caller.members[name]?.let { rec ->
|
||||||
|
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
|
||||||
|
if (rec.type == ObjRecord.Type.Property) {
|
||||||
|
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, caller)
|
||||||
|
} else if (rec.type != ObjRecord.Type.Delegated) {
|
||||||
|
return rec.value.invoke(scope, this, args, caller)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Hierarchy members (excluding root fallback)
|
// 1. Hierarchy members (excluding root fallback)
|
||||||
for (cls in objClass.mro) {
|
for (cls in objClass.mro) {
|
||||||
if (cls.className == "Obj") break
|
if (cls.className == "Obj") break
|
||||||
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
||||||
if (rec != null && !rec.isAbstract && rec.type != ObjRecord.Type.Property) {
|
if (rec != null && !rec.isAbstract) {
|
||||||
val decl = rec.declaringClass ?: cls
|
val decl = rec.declaringClass ?: cls
|
||||||
val caller = scope.currentClassCtx
|
val caller = scope.currentClassCtx
|
||||||
if (!canAccessMember(rec.visibility, decl, caller))
|
if (!canAccessMember(rec.visibility, decl, caller))
|
||||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
|
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
|
||||||
return rec.value.invoke(scope, this, args, decl)
|
|
||||||
|
if (rec.type == ObjRecord.Type.Property) {
|
||||||
|
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
|
||||||
|
} else if (rec.type != ObjRecord.Type.Delegated) {
|
||||||
|
return rec.value.invoke(scope, this, args, decl)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Extensions in scope
|
// 2. Extensions in scope
|
||||||
val extension = scope.findExtension(objClass, name)
|
val extension = scope.findExtension(objClass, name)
|
||||||
if (extension != null) {
|
if (extension != null) {
|
||||||
return extension.value.invoke(scope, this, args)
|
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
|
// 3. Root object fallback
|
||||||
@ -120,7 +142,12 @@ open class Obj {
|
|||||||
val caller = scope.currentClassCtx
|
val caller = scope.currentClassCtx
|
||||||
if (!canAccessMember(rec.visibility, decl, caller))
|
if (!canAccessMember(rec.visibility, decl, caller))
|
||||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
|
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
|
||||||
return rec.value.invoke(scope, this, args, decl)
|
|
||||||
|
if (rec.type == ObjRecord.Type.Property) {
|
||||||
|
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
|
||||||
|
} else if (rec.type != ObjRecord.Type.Delegated) {
|
||||||
|
return rec.value.invoke(scope, this, args, decl)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -415,29 +442,52 @@ open class Obj {
|
|||||||
// suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
|
// suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
|
||||||
|
|
||||||
open suspend fun readField(scope: Scope, name: String): ObjRecord {
|
open suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||||
|
// 0. Prefer private member of current class context
|
||||||
|
scope.currentClassCtx?.let { caller ->
|
||||||
|
caller.members[name]?.let { rec ->
|
||||||
|
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
|
||||||
|
val resolved = resolveRecord(scope, rec, name, caller)
|
||||||
|
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement)
|
||||||
|
return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, caller))
|
||||||
|
return resolved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Hierarchy members (excluding root fallback)
|
// 1. Hierarchy members (excluding root fallback)
|
||||||
for (cls in objClass.mro) {
|
for (cls in objClass.mro) {
|
||||||
if (cls.className == "Obj") break
|
if (cls.className == "Obj") break
|
||||||
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
||||||
if (rec != null) {
|
if (rec != null && !rec.isAbstract) {
|
||||||
if (!rec.isAbstract) {
|
val decl = rec.declaringClass ?: cls
|
||||||
return resolveRecord(scope, rec, name, rec.declaringClass)
|
val resolved = resolveRecord(scope, rec, name, decl)
|
||||||
}
|
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement)
|
||||||
|
return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, decl))
|
||||||
|
return resolved
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Extensions
|
// 2. Extensions
|
||||||
val extension = scope.findExtension(objClass, name)
|
val extension = scope.findExtension(objClass, name)
|
||||||
if (extension != null) {
|
if (extension != null) {
|
||||||
return resolveRecord(scope, extension, name, extension.declaringClass)
|
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
|
// 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 {
|
cls.members[name]?.let { rec ->
|
||||||
val decl = it.declaringClass ?: cls
|
val decl = rec.declaringClass ?: cls
|
||||||
return resolveRecord(scope, it, name, decl)
|
val caller = scope.currentClassCtx
|
||||||
|
if (!canAccessMember(rec.visibility, decl, caller))
|
||||||
|
scope.raiseError(ObjIllegalAccessException(scope, "can't access field ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
|
||||||
|
val resolved = resolveRecord(scope, rec, name, decl)
|
||||||
|
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement)
|
||||||
|
return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, decl))
|
||||||
|
return resolved
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -450,17 +500,29 @@ open class Obj {
|
|||||||
open suspend fun resolveRecord(scope: Scope, obj: ObjRecord, name: String, decl: ObjClass?): ObjRecord {
|
open suspend fun resolveRecord(scope: Scope, obj: ObjRecord, name: String, decl: ObjClass?): ObjRecord {
|
||||||
if (obj.type == ObjRecord.Type.Delegated) {
|
if (obj.type == ObjRecord.Type.Delegated) {
|
||||||
val del = obj.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate")
|
val del = obj.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate")
|
||||||
|
val th = if (this === ObjVoid) ObjNull else this
|
||||||
|
val res = del.invokeInstanceMethod(scope, "getValue", Arguments(th, ObjString(name)), onNotFoundResult = {
|
||||||
|
// If getValue not found, return a wrapper that calls invoke
|
||||||
|
object : Statement() {
|
||||||
|
override val pos: Pos = Pos.builtIn
|
||||||
|
override suspend fun execute(s: Scope): Obj {
|
||||||
|
val th2 = if (s.thisObj === ObjVoid) ObjNull else s.thisObj
|
||||||
|
val allArgs = (listOf(th2, ObjString(name)) + s.args.list).toTypedArray()
|
||||||
|
return del.invokeInstanceMethod(s, "invoke", Arguments(*allArgs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
return obj.copy(
|
return obj.copy(
|
||||||
value = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name))),
|
value = res,
|
||||||
type = ObjRecord.Type.Other
|
type = ObjRecord.Type.Other
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val value = obj.value
|
val value = obj.value
|
||||||
if (value is ObjProperty) {
|
if (value is ObjProperty || obj.type == ObjRecord.Type.Property) {
|
||||||
return ObjRecord(value.callGetter(scope, this, decl), obj.isMutable)
|
val prop = if (value is ObjProperty) value else (value as? Statement)?.execute(scope.createChildScope(scope.pos, newThisObj = this)) as? ObjProperty
|
||||||
}
|
?: scope.raiseError("Expected ObjProperty for property member $name, got ${value::class}")
|
||||||
if (value is Statement && decl != null) {
|
val res = prop.callGetter(scope, this, decl)
|
||||||
return ObjRecord(value.execute(scope.createChildScope(scope.pos, newThisObj = this)), obj.isMutable)
|
return ObjRecord(res, obj.isMutable)
|
||||||
}
|
}
|
||||||
val caller = scope.currentClassCtx
|
val caller = scope.currentClassCtx
|
||||||
// Check visibility for non-property members here if they weren't checked before
|
// Check visibility for non-property members here if they weren't checked before
|
||||||
@ -472,13 +534,24 @@ open class Obj {
|
|||||||
open suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
|
open suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
|
||||||
willMutate(scope)
|
willMutate(scope)
|
||||||
var field: ObjRecord? = null
|
var field: ObjRecord? = null
|
||||||
|
// 0. Prefer private member of current class context
|
||||||
|
scope.currentClassCtx?.let { caller ->
|
||||||
|
caller.members[name]?.let { rec ->
|
||||||
|
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
|
||||||
|
field = rec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Hierarchy members (excluding root fallback)
|
// 1. Hierarchy members (excluding root fallback)
|
||||||
for (cls in objClass.mro) {
|
if (field == null) {
|
||||||
if (cls.className == "Obj") break
|
for (cls in objClass.mro) {
|
||||||
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
if (cls.className == "Obj") break
|
||||||
if (rec != null && !rec.isAbstract) {
|
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
||||||
field = rec
|
if (rec != null && !rec.isAbstract) {
|
||||||
break
|
field = rec
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 2. Extensions
|
// 2. Extensions
|
||||||
@ -512,12 +585,19 @@ open class Obj {
|
|||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun getAt(scope: Scope, index: Obj): Obj {
|
open suspend fun getAt(scope: Scope, index: Obj): Obj {
|
||||||
|
if (index is ObjString) {
|
||||||
|
return readField(scope, index.value).value
|
||||||
|
}
|
||||||
scope.raiseNotImplemented("indexing")
|
scope.raiseNotImplemented("indexing")
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getAt(scope: Scope, index: Int): Obj = getAt(scope, ObjInt(index.toLong()))
|
suspend fun getAt(scope: Scope, index: Int): Obj = getAt(scope, ObjInt(index.toLong()))
|
||||||
|
|
||||||
open suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
|
open suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
|
||||||
|
if (index is ObjString) {
|
||||||
|
writeField(scope, index.value, newValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
scope.raiseNotImplemented("indexing")
|
scope.raiseNotImplemented("indexing")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -683,9 +763,7 @@ open class Obj {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
fun from(obj: Any?): Obj {
|
||||||
inline fun from(obj: Any?): Obj {
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
return when (obj) {
|
return when (obj) {
|
||||||
is Obj -> obj
|
is Obj -> obj
|
||||||
is Double -> ObjReal(obj)
|
is Double -> ObjReal(obj)
|
||||||
@ -694,16 +772,16 @@ open class Obj {
|
|||||||
is Long -> ObjInt.of(obj)
|
is Long -> ObjInt.of(obj)
|
||||||
is String -> ObjString(obj)
|
is String -> ObjString(obj)
|
||||||
is CharSequence -> ObjString(obj.toString())
|
is CharSequence -> ObjString(obj.toString())
|
||||||
|
is Char -> ObjChar(obj)
|
||||||
is Boolean -> ObjBool(obj)
|
is Boolean -> ObjBool(obj)
|
||||||
is Set<*> -> ObjSet((obj as Set<Obj>).toMutableSet())
|
is Set<*> -> ObjSet(obj.map { from(it) }.toMutableSet())
|
||||||
|
is List<*> -> ObjList(obj.map { from(it) }.toMutableList())
|
||||||
|
is Map<*, *> -> ObjMap(obj.entries.associate { from(it.key) to from(it.value) }.toMutableMap())
|
||||||
|
is Map.Entry<*, *> -> ObjMapEntry(from(obj.key), from(obj.value))
|
||||||
|
is Enum<*> -> ObjString(obj.name)
|
||||||
Unit -> ObjVoid
|
Unit -> ObjVoid
|
||||||
null -> ObjNull
|
null -> ObjNull
|
||||||
is Iterator<*> -> ObjKotlinIterator(obj)
|
is Iterator<*> -> ObjKotlinIterator(obj as Iterator<Any?>)
|
||||||
is Map.Entry<*, *> -> {
|
|
||||||
obj as MutableMap.MutableEntry<Obj, Obj>
|
|
||||||
ObjMapEntry(obj.key, obj.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> throw IllegalArgumentException("cannot convert to Obj: $obj")
|
else -> throw IllegalArgumentException("cannot convert to Obj: $obj")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -17,10 +17,7 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
import net.sergeych.lyng.miniast.ParamDoc
|
import net.sergeych.lyng.miniast.*
|
||||||
import net.sergeych.lyng.miniast.TypeGenericDoc
|
|
||||||
import net.sergeych.lyng.miniast.addFnDoc
|
|
||||||
import net.sergeych.lyng.miniast.type
|
|
||||||
|
|
||||||
val ObjArray by lazy {
|
val ObjArray by lazy {
|
||||||
|
|
||||||
@ -52,32 +49,35 @@ val ObjArray by lazy {
|
|||||||
ObjFalse
|
ObjFalse
|
||||||
}
|
}
|
||||||
|
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "last",
|
name = "last",
|
||||||
doc = "The last element of this array.",
|
doc = "The last element of this array.",
|
||||||
returns = type("lyng.Any"),
|
type = type("lyng.Any"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = {
|
||||||
thisObj.invokeInstanceMethod(
|
this.thisObj.invokeInstanceMethod(
|
||||||
this,
|
this,
|
||||||
"getAt",
|
"getAt",
|
||||||
(thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj()
|
(this.thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "lastIndex",
|
name = "lastIndex",
|
||||||
doc = "Index of the last element (size - 1).",
|
doc = "Index of the last element (size - 1).",
|
||||||
returns = type("lyng.Int"),
|
type = type("lyng.Int"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) { (thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj() }
|
getter = { (this.thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj() }
|
||||||
|
)
|
||||||
|
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "indices",
|
name = "indices",
|
||||||
doc = "Range of valid indices for this array.",
|
doc = "Range of valid indices for this array.",
|
||||||
returns = type("lyng.Range"),
|
type = type("lyng.Range"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) { ObjRange(0.toObj(), thisObj.invokeInstanceMethod(this, "size"), false) }
|
getter = { ObjRange(0.toObj(), this.thisObj.invokeInstanceMethod(this, "size"), false) }
|
||||||
|
)
|
||||||
|
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "binarySearch",
|
name = "binarySearch",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -19,6 +19,8 @@ package net.sergeych.lyng.obj
|
|||||||
|
|
||||||
import net.sergeych.bintools.toDump
|
import net.sergeych.bintools.toDump
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||||
|
import net.sergeych.lyng.miniast.type
|
||||||
import net.sergeych.lynon.BitArray
|
import net.sergeych.lynon.BitArray
|
||||||
|
|
||||||
class ObjBitBuffer(val bitArray: BitArray) : Obj() {
|
class ObjBitBuffer(val bitArray: BitArray) : Obj() {
|
||||||
@ -43,12 +45,20 @@ class ObjBitBuffer(val bitArray: BitArray) : Obj() {
|
|||||||
thisAs<ObjBitBuffer>().bitArray.asUByteArray().toDump()
|
thisAs<ObjBitBuffer>().bitArray.asUByteArray().toDump()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
addFn("size") {
|
addPropertyDoc(
|
||||||
thisAs<ObjBitBuffer>().bitArray.size.toObj()
|
name = "size",
|
||||||
}
|
doc = "Size of the bit buffer in bits.",
|
||||||
addFn("sizeInBytes") {
|
type = type("lyng.Int"),
|
||||||
ObjInt((thisAs<ObjBitBuffer>().bitArray.size + 7) shr 3)
|
moduleName = "lyng.stdlib",
|
||||||
}
|
getter = { thisAs<ObjBitBuffer>().bitArray.size.toObj() }
|
||||||
|
)
|
||||||
|
addPropertyDoc(
|
||||||
|
name = "sizeInBytes",
|
||||||
|
doc = "Size of the bit buffer in full bytes (rounded up).",
|
||||||
|
type = type("lyng.Int"),
|
||||||
|
moduleName = "lyng.stdlib",
|
||||||
|
getter = { ObjInt((thisAs<ObjBitBuffer>().bitArray.size + 7) shr 3) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -23,7 +23,8 @@ import net.sergeych.bintools.decodeHex
|
|||||||
import net.sergeych.bintools.encodeToHex
|
import net.sergeych.bintools.encodeToHex
|
||||||
import net.sergeych.bintools.toDump
|
import net.sergeych.bintools.toDump
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.statement
|
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||||
|
import net.sergeych.lyng.miniast.type
|
||||||
import net.sergeych.lynon.BitArray
|
import net.sergeych.lynon.BitArray
|
||||||
import net.sergeych.lynon.LynonDecoder
|
import net.sergeych.lynon.LynonDecoder
|
||||||
import net.sergeych.lynon.LynonEncoder
|
import net.sergeych.lynon.LynonEncoder
|
||||||
@ -174,20 +175,26 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() {
|
|||||||
addClassFn("decodeHex") {
|
addClassFn("decodeHex") {
|
||||||
ObjBuffer(requireOnlyArg<Obj>().toString().decodeHex().asUByteArray())
|
ObjBuffer(requireOnlyArg<Obj>().toString().decodeHex().asUByteArray())
|
||||||
}
|
}
|
||||||
createField("size",
|
addPropertyDoc(
|
||||||
statement {
|
name = "size",
|
||||||
(thisObj as ObjBuffer).byteArray.size.toObj()
|
doc = "Number of bytes in this buffer.",
|
||||||
}
|
type = type("lyng.Int"),
|
||||||
|
moduleName = "lyng.stdlib",
|
||||||
|
getter = { (this.thisObj as ObjBuffer).byteArray.size.toObj() }
|
||||||
)
|
)
|
||||||
createField("hex",
|
addPropertyDoc(
|
||||||
statement {
|
name = "hex",
|
||||||
thisAs<ObjBuffer>().hex.toObj()
|
doc = "Hexadecimal string representation of the buffer.",
|
||||||
}
|
type = type("lyng.String"),
|
||||||
|
moduleName = "lyng.stdlib",
|
||||||
|
getter = { thisAs<ObjBuffer>().hex.toObj() }
|
||||||
)
|
)
|
||||||
createField("base64",
|
addPropertyDoc(
|
||||||
statement {
|
name = "base64",
|
||||||
thisAs<ObjBuffer>().base64.toObj()
|
doc = "Base64 (URL-safe) string representation of the buffer.",
|
||||||
}
|
type = type("lyng.String"),
|
||||||
|
moduleName = "lyng.stdlib",
|
||||||
|
getter = { thisAs<ObjBuffer>().base64.toObj() }
|
||||||
)
|
)
|
||||||
addFn("decodeUtf8") {
|
addFn("decodeUtf8") {
|
||||||
ObjString(
|
ObjString(
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -18,7 +18,7 @@
|
|||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.miniast.addFnDoc
|
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||||
import net.sergeych.lyng.miniast.type
|
import net.sergeych.lyng.miniast.type
|
||||||
|
|
||||||
class ObjChar(val value: Char): Obj() {
|
class ObjChar(val value: Char): Obj() {
|
||||||
@ -47,12 +47,13 @@ class ObjChar(val value: Char): Obj() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val type = ObjClass("Char").apply {
|
val type = ObjClass("Char").apply {
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "code",
|
name = "code",
|
||||||
doc = "Unicode code point (UTF-16 code unit) of this character.",
|
doc = "Unicode code point (UTF-16 code unit) of this character.",
|
||||||
returns = type("lyng.Int"),
|
type = type("lyng.Int"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) { ObjInt(thisAs<ObjChar>().value.code.toLong()) }
|
getter = { ObjInt((this.thisObj as ObjChar).value.code.toLong()) }
|
||||||
|
)
|
||||||
addFn("isDigit") {
|
addFn("isDigit") {
|
||||||
thisAs<ObjChar>().value.isDigit().toObj()
|
thisAs<ObjChar>().value.isDigit().toObj()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,10 +18,7 @@
|
|||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
import net.sergeych.lyng.*
|
import net.sergeych.lyng.*
|
||||||
import net.sergeych.lyng.miniast.ParamDoc
|
import net.sergeych.lyng.miniast.*
|
||||||
import net.sergeych.lyng.miniast.TypeGenericDoc
|
|
||||||
import net.sergeych.lyng.miniast.addFnDoc
|
|
||||||
import net.sergeych.lyng.miniast.type
|
|
||||||
import net.sergeych.lynon.LynonDecoder
|
import net.sergeych.lynon.LynonDecoder
|
||||||
import net.sergeych.lynon.LynonType
|
import net.sergeych.lynon.LynonType
|
||||||
|
|
||||||
@ -30,47 +27,56 @@ private object ClassIdGen { var c: Long = 1L; fun nextId(): Long = c++ }
|
|||||||
|
|
||||||
val ObjClassType by lazy {
|
val ObjClassType by lazy {
|
||||||
ObjClass("Class").apply {
|
ObjClass("Class").apply {
|
||||||
addProperty("className", getter = { thisAs<ObjClass>().classNameObj })
|
addPropertyDoc(
|
||||||
addFnDoc(
|
name = "className",
|
||||||
|
doc = "Full name of this class including package if available.",
|
||||||
|
type = type("lyng.String"),
|
||||||
|
moduleName = "lyng.stdlib",
|
||||||
|
getter = { (this.thisObj as ObjClass).classNameObj }
|
||||||
|
)
|
||||||
|
addPropertyDoc(
|
||||||
name = "name",
|
name = "name",
|
||||||
doc = "Simple name of this class (without package).",
|
doc = "Simple name of this class (without package).",
|
||||||
returns = type("lyng.String"),
|
type = type("lyng.String"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) { thisAs<ObjClass>().classNameObj }
|
getter = { (this.thisObj as ObjClass).classNameObj }
|
||||||
|
)
|
||||||
|
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "fields",
|
name = "fields",
|
||||||
doc = "Declared instance fields of this class and its ancestors (C3 order), without duplicates.",
|
doc = "Declared instance fields of this class and its ancestors (C3 order), without duplicates.",
|
||||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
|
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = {
|
||||||
val cls = thisAs<ObjClass>()
|
val cls = this.thisObj as ObjClass
|
||||||
val seen = hashSetOf<String>()
|
val seen = hashSetOf<String>()
|
||||||
val names = mutableListOf<Obj>()
|
val names = mutableListOf<Obj>()
|
||||||
for (c in cls.mro) {
|
for (c in cls.mro) {
|
||||||
for ((n, rec) in c.members) {
|
for ((n, rec) in c.members) {
|
||||||
if (rec.value !is Statement && seen.add(n)) names += ObjString(n)
|
if (rec.value !is Statement && seen.add(n)) names += ObjString(n)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
ObjList(names.toMutableList())
|
||||||
}
|
}
|
||||||
ObjList(names.toMutableList())
|
)
|
||||||
}
|
|
||||||
|
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "methods",
|
name = "methods",
|
||||||
doc = "Declared instance methods of this class and its ancestors (C3 order), without duplicates.",
|
doc = "Declared instance methods of this class and its ancestors (C3 order), without duplicates.",
|
||||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
|
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = {
|
||||||
val cls = thisAs<ObjClass>()
|
val cls = this.thisObj as ObjClass
|
||||||
val seen = hashSetOf<String>()
|
val seen = hashSetOf<String>()
|
||||||
val names = mutableListOf<Obj>()
|
val names = mutableListOf<Obj>()
|
||||||
for (c in cls.mro) {
|
for (c in cls.mro) {
|
||||||
for ((n, rec) in c.members) {
|
for ((n, rec) in c.members) {
|
||||||
if (rec.value is Statement && seen.add(n)) names += ObjString(n)
|
if (rec.value is Statement && seen.add(n)) names += ObjString(n)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
ObjList(names.toMutableList())
|
||||||
}
|
}
|
||||||
ObjList(names.toMutableList())
|
)
|
||||||
}
|
|
||||||
|
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "get",
|
name = "get",
|
||||||
@ -247,21 +253,29 @@ open class ObjClass(
|
|||||||
// remains stable even when call frames are pooled and reused.
|
// remains stable even when call frames are pooled and reused.
|
||||||
val stableParent = classScope ?: scope.parent
|
val stableParent = classScope ?: scope.parent
|
||||||
instance.instanceScope = Scope(stableParent, scope.args, scope.pos, instance)
|
instance.instanceScope = Scope(stableParent, scope.args, scope.pos, instance)
|
||||||
|
// println("[DEBUG_LOG] createInstance: created $instance scope@${instance.instanceScope.hashCode()}")
|
||||||
instance.instanceScope.currentClassCtx = null
|
instance.instanceScope.currentClassCtx = null
|
||||||
// Expose instance methods (and other callable members) directly in the instance scope for fast lookup
|
// Expose instance methods (and other callable members) directly in the instance scope for fast lookup
|
||||||
// This mirrors Obj.autoInstanceScope behavior for ad-hoc scopes and makes fb.method() resolution robust
|
// This mirrors Obj.autoInstanceScope behavior for ad-hoc scopes and makes fb.method() resolution robust
|
||||||
// 1) members-defined methods
|
|
||||||
for ((k, v) in members) {
|
for (cls in mro) {
|
||||||
if (v.value is Statement || v.type == ObjRecord.Type.Delegated) {
|
// 1) members-defined methods
|
||||||
instance.instanceScope.objects[k] = if (v.type == ObjRecord.Type.Delegated) v.copy() else v
|
for ((k, v) in cls.members) {
|
||||||
|
if (v.value is Statement || v.type == ObjRecord.Type.Delegated) {
|
||||||
|
val key = if (v.visibility == Visibility.Private) "${cls.className}::$k" else k
|
||||||
|
if (!instance.instanceScope.objects.containsKey(key)) {
|
||||||
|
instance.instanceScope.objects[key] = if (v.type == ObjRecord.Type.Delegated) v.copy() else v
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
// 2) class-scope methods registered during class-body execution
|
||||||
// 2) class-scope methods registered during class-body execution
|
cls.classScope?.objects?.forEach { (k, rec) ->
|
||||||
classScope?.objects?.forEach { (k, rec) ->
|
if (rec.value is Statement || rec.type == ObjRecord.Type.Delegated) {
|
||||||
if (rec.value is Statement || rec.type == ObjRecord.Type.Delegated) {
|
val key = if (rec.visibility == Visibility.Private) "${cls.className}::$k" else k
|
||||||
// if not already present, copy reference for dispatch
|
// if not already present, copy reference for dispatch
|
||||||
if (!instance.instanceScope.objects.containsKey(k)) {
|
if (!instance.instanceScope.objects.containsKey(key)) {
|
||||||
instance.instanceScope.objects[k] = if (rec.type == ObjRecord.Type.Delegated) rec.copy() else rec
|
instance.instanceScope.objects[key] = if (rec.type == ObjRecord.Type.Delegated) rec.copy() else rec
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -300,7 +314,13 @@ open class ObjClass(
|
|||||||
c.constructorMeta?.let { meta ->
|
c.constructorMeta?.let { meta ->
|
||||||
val argsHere = argsForThis ?: Arguments.EMPTY
|
val argsHere = argsForThis ?: Arguments.EMPTY
|
||||||
// Assign constructor params into instance scope (unmangled)
|
// Assign constructor params into instance scope (unmangled)
|
||||||
meta.assignToContext(instance.instanceScope, argsHere, declaringClass = c)
|
val savedCtx = instance.instanceScope.currentClassCtx
|
||||||
|
instance.instanceScope.currentClassCtx = c
|
||||||
|
try {
|
||||||
|
meta.assignToContext(instance.instanceScope, argsHere, declaringClass = c)
|
||||||
|
} finally {
|
||||||
|
instance.instanceScope.currentClassCtx = savedCtx
|
||||||
|
}
|
||||||
// Also expose them under MI-mangled storage keys `${Class}::name` so qualified views can access them
|
// Also expose them under MI-mangled storage keys `${Class}::name` so qualified views can access them
|
||||||
// and so that base-class casts like `(obj as Base).field` work.
|
// and so that base-class casts like `(obj as Base).field` work.
|
||||||
for (p in meta.params) {
|
for (p in meta.params) {
|
||||||
@ -329,7 +349,13 @@ open class ObjClass(
|
|||||||
// parameters even if they were shadowed/overwritten by parent class initialization.
|
// parameters even if they were shadowed/overwritten by parent class initialization.
|
||||||
c.constructorMeta?.let { meta ->
|
c.constructorMeta?.let { meta ->
|
||||||
val argsHere = argsForThis ?: Arguments.EMPTY
|
val argsHere = argsForThis ?: Arguments.EMPTY
|
||||||
meta.assignToContext(instance.instanceScope, argsHere, declaringClass = c)
|
val savedCtx = instance.instanceScope.currentClassCtx
|
||||||
|
instance.instanceScope.currentClassCtx = c
|
||||||
|
try {
|
||||||
|
meta.assignToContext(instance.instanceScope, argsHere, declaringClass = c)
|
||||||
|
} finally {
|
||||||
|
instance.instanceScope.currentClassCtx = savedCtx
|
||||||
|
}
|
||||||
// Re-sync mangled names to point to the fresh records to keep them consistent
|
// Re-sync mangled names to point to the fresh records to keep them consistent
|
||||||
for (p in meta.params) {
|
for (p in meta.params) {
|
||||||
val rec = instance.instanceScope.objects[p.name]
|
val rec = instance.instanceScope.objects[p.name]
|
||||||
@ -382,7 +408,8 @@ open class ObjClass(
|
|||||||
): ObjRecord {
|
): ObjRecord {
|
||||||
// Validation of override rules: only for non-system declarations
|
// Validation of override rules: only for non-system declarations
|
||||||
if (pos != Pos.builtIn) {
|
if (pos != Pos.builtIn) {
|
||||||
val existing = getInstanceMemberOrNull(name)
|
// Only consider TRUE instance members from ancestors for overrides
|
||||||
|
val existing = getInstanceMemberOrNull(name, includeAbstract = true, includeStatic = false)
|
||||||
var actualOverride = 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.
|
||||||
@ -505,14 +532,21 @@ open class ObjClass(
|
|||||||
/**
|
/**
|
||||||
* Get instance member traversing the hierarchy if needed. Its meaning is different for different objects.
|
* Get instance member traversing the hierarchy if needed. Its meaning is different for different objects.
|
||||||
*/
|
*/
|
||||||
fun getInstanceMemberOrNull(name: String): ObjRecord? {
|
fun getInstanceMemberOrNull(name: String, includeAbstract: Boolean = false, includeStatic: Boolean = true): ObjRecord? {
|
||||||
// Unified traversal in strict C3 order: self, then each ancestor, checking members before classScope
|
// Unified traversal in strict C3 order: self, then each ancestor, checking members before classScope
|
||||||
for (cls in mro) {
|
for (cls in mro) {
|
||||||
cls.members[name]?.let { return it }
|
cls.members[name]?.let {
|
||||||
cls.classScope?.objects?.get(name)?.let { return it }
|
if (includeAbstract || !it.isAbstract) return it
|
||||||
|
}
|
||||||
|
if (includeStatic) {
|
||||||
|
cls.classScope?.objects?.get(name)?.let {
|
||||||
|
if (includeAbstract || !it.isAbstract) return it
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Finally, allow root object fallback (rare; mostly built-ins like toString)
|
// Finally, allow root object fallback (rare; mostly built-ins like toString)
|
||||||
return rootObjectType.members[name]
|
val rootRec = rootObjectType.members[name]
|
||||||
|
return if (rootRec != null && (includeAbstract || !rootRec.isAbstract)) rootRec else null
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find the declaring class where a member with [name] is defined, starting from this class along MRO. */
|
/** Find the declaring class where a member with [name] is defined, starting from this class along MRO. */
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -21,5 +21,6 @@ package net.sergeych.lyng.obj
|
|||||||
* Collection is an iterator with `size`
|
* Collection is an iterator with `size`
|
||||||
*/
|
*/
|
||||||
val ObjCollection = ObjClass("Collection", ObjIterable).apply {
|
val ObjCollection = ObjClass("Collection", ObjIterable).apply {
|
||||||
|
addProperty("size", isAbstract = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -20,6 +20,7 @@ package net.sergeych.lyng.obj
|
|||||||
import kotlinx.coroutines.Deferred
|
import kotlinx.coroutines.Deferred
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.miniast.addFnDoc
|
import net.sergeych.lyng.miniast.addFnDoc
|
||||||
|
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||||
import net.sergeych.lyng.miniast.type
|
import net.sergeych.lyng.miniast.type
|
||||||
|
|
||||||
open class ObjDeferred(val deferred: Deferred<Obj>): Obj() {
|
open class ObjDeferred(val deferred: Deferred<Obj>): Obj() {
|
||||||
@ -38,28 +39,30 @@ open class ObjDeferred(val deferred: Deferred<Obj>): Obj() {
|
|||||||
returns = type("lyng.Any"),
|
returns = type("lyng.Any"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib"
|
||||||
) { thisAs<ObjDeferred>().deferred.await() }
|
) { thisAs<ObjDeferred>().deferred.await() }
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "isCompleted",
|
name = "isCompleted",
|
||||||
doc = "Whether this deferred has completed (successfully or with an error).",
|
doc = "Whether this deferred has completed (successfully or with an error).",
|
||||||
returns = type("lyng.Bool"),
|
type = type("lyng.Bool"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) { thisAs<ObjDeferred>().deferred.isCompleted.toObj() }
|
getter = { thisAs<ObjDeferred>().deferred.isCompleted.toObj() }
|
||||||
addFnDoc(
|
)
|
||||||
|
addPropertyDoc(
|
||||||
name = "isActive",
|
name = "isActive",
|
||||||
doc = "Whether this deferred is currently active (not completed and not cancelled).",
|
doc = "Whether this deferred is currently active (not completed and not cancelled).",
|
||||||
returns = type("lyng.Bool"),
|
type = type("lyng.Bool"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = {
|
||||||
val d = thisAs<ObjDeferred>().deferred
|
val d = thisAs<ObjDeferred>().deferred
|
||||||
(d.isActive || (!d.isCompleted && !d.isCancelled)).toObj()
|
(d.isActive || (!d.isCompleted && !d.isCancelled)).toObj()
|
||||||
}
|
}
|
||||||
addFnDoc(
|
)
|
||||||
|
addPropertyDoc(
|
||||||
name = "isCancelled",
|
name = "isCancelled",
|
||||||
doc = "Whether this deferred was cancelled.",
|
doc = "Whether this deferred was cancelled.",
|
||||||
returns = type("lyng.Bool"),
|
type = type("lyng.Bool"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) { thisAs<ObjDeferred>().deferred.isCancelled.toObj() }
|
getter = { thisAs<ObjDeferred>().deferred.isCancelled.toObj() }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -18,7 +18,7 @@
|
|||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.miniast.addFnDoc
|
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||||
import net.sergeych.lyng.miniast.type
|
import net.sergeych.lyng.miniast.type
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
import kotlin.time.Duration.Companion.days
|
import kotlin.time.Duration.Companion.days
|
||||||
@ -74,169 +74,195 @@ class ObjDuration(val duration: Duration) : Obj() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}.apply {
|
}.apply {
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "days",
|
name = "days",
|
||||||
doc = "Return this duration as a real number of days.",
|
doc = "Return this duration as a real number of days.",
|
||||||
returns = type("lyng.Real"),
|
type = type("lyng.Real"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { thisAs<ObjDuration>().duration.toDouble(DurationUnit.DAYS).toObj() }
|
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.DAYS).toObj() }
|
||||||
addFnDoc(
|
)
|
||||||
|
addPropertyDoc(
|
||||||
name = "hours",
|
name = "hours",
|
||||||
doc = "Return this duration as a real number of hours.",
|
doc = "Return this duration as a real number of hours.",
|
||||||
returns = type("lyng.Real"),
|
type = type("lyng.Real"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { thisAs<ObjDuration>().duration.toDouble(DurationUnit.HOURS).toObj() }
|
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.HOURS).toObj() }
|
||||||
addFnDoc(
|
)
|
||||||
|
addPropertyDoc(
|
||||||
name = "minutes",
|
name = "minutes",
|
||||||
doc = "Return this duration as a real number of minutes.",
|
doc = "Return this duration as a real number of minutes.",
|
||||||
returns = type("lyng.Real"),
|
type = type("lyng.Real"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { thisAs<ObjDuration>().duration.toDouble(DurationUnit.MINUTES).toObj() }
|
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.MINUTES).toObj() }
|
||||||
addFnDoc(
|
)
|
||||||
|
addPropertyDoc(
|
||||||
name = "seconds",
|
name = "seconds",
|
||||||
doc = "Return this duration as a real number of seconds.",
|
doc = "Return this duration as a real number of seconds.",
|
||||||
returns = type("lyng.Real"),
|
type = type("lyng.Real"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { thisAs<ObjDuration>().duration.toDouble(DurationUnit.SECONDS).toObj() }
|
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.SECONDS).toObj() }
|
||||||
addFnDoc(
|
)
|
||||||
|
addPropertyDoc(
|
||||||
name = "milliseconds",
|
name = "milliseconds",
|
||||||
doc = "Return this duration as a real number of milliseconds.",
|
doc = "Return this duration as a real number of milliseconds.",
|
||||||
returns = type("lyng.Real"),
|
type = type("lyng.Real"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { thisAs<ObjDuration>().duration.toDouble(DurationUnit.MILLISECONDS).toObj() }
|
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.MILLISECONDS).toObj() }
|
||||||
addFnDoc(
|
)
|
||||||
|
addPropertyDoc(
|
||||||
name = "microseconds",
|
name = "microseconds",
|
||||||
doc = "Return this duration as a real number of microseconds.",
|
doc = "Return this duration as a real number of microseconds.",
|
||||||
returns = type("lyng.Real"),
|
type = type("lyng.Real"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { thisAs<ObjDuration>().duration.toDouble(DurationUnit.MICROSECONDS).toObj() }
|
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.MICROSECONDS).toObj() }
|
||||||
|
)
|
||||||
// extensions
|
// extensions
|
||||||
|
|
||||||
ObjInt.type.addFnDoc(
|
ObjInt.type.addPropertyDoc(
|
||||||
name = "seconds",
|
name = "seconds",
|
||||||
doc = "Construct a `Duration` equal to this integer number of seconds.",
|
doc = "Construct a `Duration` equal to this integer number of seconds.",
|
||||||
returns = type("lyng.Duration"),
|
type = type("lyng.Duration"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { ObjDuration(thisAs<ObjInt>().value.seconds) }
|
getter = { ObjDuration(thisAs<ObjInt>().value.seconds) }
|
||||||
|
)
|
||||||
|
|
||||||
ObjInt.type.addFnDoc(
|
ObjInt.type.addPropertyDoc(
|
||||||
name = "second",
|
name = "second",
|
||||||
doc = "Construct a `Duration` equal to this integer number of seconds.",
|
doc = "Construct a `Duration` equal to this integer number of seconds.",
|
||||||
returns = type("lyng.Duration"),
|
type = type("lyng.Duration"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { ObjDuration(thisAs<ObjInt>().value.seconds) }
|
getter = { ObjDuration(thisAs<ObjInt>().value.seconds) }
|
||||||
ObjInt.type.addFnDoc(
|
)
|
||||||
|
ObjInt.type.addPropertyDoc(
|
||||||
name = "milliseconds",
|
name = "milliseconds",
|
||||||
doc = "Construct a `Duration` equal to this integer number of milliseconds.",
|
doc = "Construct a `Duration` equal to this integer number of milliseconds.",
|
||||||
returns = type("lyng.Duration"),
|
type = type("lyng.Duration"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { ObjDuration(thisAs<ObjInt>().value.milliseconds) }
|
getter = { ObjDuration(thisAs<ObjInt>().value.milliseconds) }
|
||||||
|
)
|
||||||
|
|
||||||
ObjInt.type.addFnDoc(
|
ObjInt.type.addPropertyDoc(
|
||||||
name = "millisecond",
|
name = "millisecond",
|
||||||
doc = "Construct a `Duration` equal to this integer number of milliseconds.",
|
doc = "Construct a `Duration` equal to this integer number of milliseconds.",
|
||||||
returns = type("lyng.Duration"),
|
type = type("lyng.Duration"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { ObjDuration(thisAs<ObjInt>().value.milliseconds) }
|
getter = { ObjDuration(thisAs<ObjInt>().value.milliseconds) }
|
||||||
ObjReal.type.addFnDoc(
|
)
|
||||||
|
ObjReal.type.addPropertyDoc(
|
||||||
name = "seconds",
|
name = "seconds",
|
||||||
doc = "Construct a `Duration` equal to this real number of seconds.",
|
doc = "Construct a `Duration` equal to this real number of seconds.",
|
||||||
returns = type("lyng.Duration"),
|
type = type("lyng.Duration"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { ObjDuration(thisAs<ObjReal>().value.seconds) }
|
getter = { ObjDuration(thisAs<ObjReal>().value.seconds) }
|
||||||
|
)
|
||||||
|
|
||||||
ObjReal.type.addFnDoc(
|
ObjReal.type.addPropertyDoc(
|
||||||
name = "second",
|
name = "second",
|
||||||
doc = "Construct a `Duration` equal to this real number of seconds.",
|
doc = "Construct a `Duration` equal to this real number of seconds.",
|
||||||
returns = type("lyng.Duration"),
|
type = type("lyng.Duration"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { ObjDuration(thisAs<ObjReal>().value.seconds) }
|
getter = { ObjDuration(thisAs<ObjReal>().value.seconds) }
|
||||||
|
)
|
||||||
|
|
||||||
ObjReal.type.addFnDoc(
|
ObjReal.type.addPropertyDoc(
|
||||||
name = "milliseconds",
|
name = "milliseconds",
|
||||||
doc = "Construct a `Duration` equal to this real number of milliseconds.",
|
doc = "Construct a `Duration` equal to this real number of milliseconds.",
|
||||||
returns = type("lyng.Duration"),
|
type = type("lyng.Duration"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { ObjDuration(thisAs<ObjReal>().value.milliseconds) }
|
getter = { ObjDuration(thisAs<ObjReal>().value.milliseconds) }
|
||||||
ObjReal.type.addFnDoc(
|
)
|
||||||
|
ObjReal.type.addPropertyDoc(
|
||||||
name = "millisecond",
|
name = "millisecond",
|
||||||
doc = "Construct a `Duration` equal to this real number of milliseconds.",
|
doc = "Construct a `Duration` equal to this real number of milliseconds.",
|
||||||
returns = type("lyng.Duration"),
|
type = type("lyng.Duration"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { ObjDuration(thisAs<ObjReal>().value.milliseconds) }
|
getter = { ObjDuration(thisAs<ObjReal>().value.milliseconds) }
|
||||||
|
)
|
||||||
|
|
||||||
ObjInt.type.addFnDoc(
|
ObjInt.type.addPropertyDoc(
|
||||||
name = "minutes",
|
name = "minutes",
|
||||||
doc = "Construct a `Duration` equal to this integer number of minutes.",
|
doc = "Construct a `Duration` equal to this integer number of minutes.",
|
||||||
returns = type("lyng.Duration"),
|
type = type("lyng.Duration"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { ObjDuration(thisAs<ObjInt>().value.minutes) }
|
getter = { ObjDuration(thisAs<ObjInt>().value.minutes) }
|
||||||
ObjReal.type.addFnDoc(
|
)
|
||||||
|
ObjReal.type.addPropertyDoc(
|
||||||
name = "minutes",
|
name = "minutes",
|
||||||
doc = "Construct a `Duration` equal to this real number of minutes.",
|
doc = "Construct a `Duration` equal to this real number of minutes.",
|
||||||
returns = type("lyng.Duration"),
|
type = type("lyng.Duration"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { ObjDuration(thisAs<ObjReal>().value.minutes) }
|
getter = { ObjDuration(thisAs<ObjReal>().value.minutes) }
|
||||||
ObjInt.type.addFnDoc(
|
)
|
||||||
|
ObjInt.type.addPropertyDoc(
|
||||||
name = "minute",
|
name = "minute",
|
||||||
doc = "Construct a `Duration` equal to this integer number of minutes.",
|
doc = "Construct a `Duration` equal to this integer number of minutes.",
|
||||||
returns = type("lyng.Duration"),
|
type = type("lyng.Duration"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { ObjDuration(thisAs<ObjInt>().value.minutes) }
|
getter = { ObjDuration(thisAs<ObjInt>().value.minutes) }
|
||||||
ObjReal.type.addFnDoc(
|
)
|
||||||
|
ObjReal.type.addPropertyDoc(
|
||||||
name = "minute",
|
name = "minute",
|
||||||
doc = "Construct a `Duration` equal to this real number of minutes.",
|
doc = "Construct a `Duration` equal to this real number of minutes.",
|
||||||
returns = type("lyng.Duration"),
|
type = type("lyng.Duration"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { ObjDuration(thisAs<ObjReal>().value.minutes) }
|
getter = { ObjDuration(thisAs<ObjReal>().value.minutes) }
|
||||||
ObjInt.type.addFnDoc(
|
)
|
||||||
|
ObjInt.type.addPropertyDoc(
|
||||||
name = "hours",
|
name = "hours",
|
||||||
doc = "Construct a `Duration` equal to this integer number of hours.",
|
doc = "Construct a `Duration` equal to this integer number of hours.",
|
||||||
returns = type("lyng.Duration"),
|
type = type("lyng.Duration"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { ObjDuration(thisAs<ObjInt>().value.hours) }
|
getter = { ObjDuration(thisAs<ObjInt>().value.hours) }
|
||||||
ObjReal.type.addFnDoc(
|
)
|
||||||
|
ObjReal.type.addPropertyDoc(
|
||||||
name = "hours",
|
name = "hours",
|
||||||
doc = "Construct a `Duration` equal to this real number of hours.",
|
doc = "Construct a `Duration` equal to this real number of hours.",
|
||||||
returns = type("lyng.Duration"),
|
type = type("lyng.Duration"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { ObjDuration(thisAs<ObjReal>().value.hours) }
|
getter = { ObjDuration(thisAs<ObjReal>().value.hours) }
|
||||||
ObjInt.type.addFnDoc(
|
)
|
||||||
|
ObjInt.type.addPropertyDoc(
|
||||||
name = "hour",
|
name = "hour",
|
||||||
doc = "Construct a `Duration` equal to this integer number of hours.",
|
doc = "Construct a `Duration` equal to this integer number of hours.",
|
||||||
returns = type("lyng.Duration"),
|
type = type("lyng.Duration"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { ObjDuration(thisAs<ObjInt>().value.hours) }
|
getter = { ObjDuration(thisAs<ObjInt>().value.hours) }
|
||||||
ObjReal.type.addFnDoc(
|
)
|
||||||
|
ObjReal.type.addPropertyDoc(
|
||||||
name = "hour",
|
name = "hour",
|
||||||
doc = "Construct a `Duration` equal to this real number of hours.",
|
doc = "Construct a `Duration` equal to this real number of hours.",
|
||||||
returns = type("lyng.Duration"),
|
type = type("lyng.Duration"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { ObjDuration(thisAs<ObjReal>().value.hours) }
|
getter = { ObjDuration(thisAs<ObjReal>().value.hours) }
|
||||||
ObjInt.type.addFnDoc(
|
)
|
||||||
|
ObjInt.type.addPropertyDoc(
|
||||||
name = "days",
|
name = "days",
|
||||||
doc = "Construct a `Duration` equal to this integer number of days.",
|
doc = "Construct a `Duration` equal to this integer number of days.",
|
||||||
returns = type("lyng.Duration"),
|
type = type("lyng.Duration"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { ObjDuration(thisAs<ObjInt>().value.days) }
|
getter = { ObjDuration(thisAs<ObjInt>().value.days) }
|
||||||
ObjReal.type.addFnDoc(
|
)
|
||||||
|
ObjReal.type.addPropertyDoc(
|
||||||
name = "days",
|
name = "days",
|
||||||
doc = "Construct a `Duration` equal to this real number of days.",
|
doc = "Construct a `Duration` equal to this real number of days.",
|
||||||
returns = type("lyng.Duration"),
|
type = type("lyng.Duration"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { ObjDuration(thisAs<ObjReal>().value.days) }
|
getter = { ObjDuration(thisAs<ObjReal>().value.days) }
|
||||||
ObjInt.type.addFnDoc(
|
)
|
||||||
|
ObjInt.type.addPropertyDoc(
|
||||||
name = "day",
|
name = "day",
|
||||||
doc = "Construct a `Duration` equal to this integer number of days.",
|
doc = "Construct a `Duration` equal to this integer number of days.",
|
||||||
returns = type("lyng.Duration"),
|
type = type("lyng.Duration"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { ObjDuration(thisAs<ObjInt>().value.days) }
|
getter = { ObjDuration(thisAs<ObjInt>().value.days) }
|
||||||
ObjReal.type.addFnDoc(
|
)
|
||||||
|
ObjReal.type.addPropertyDoc(
|
||||||
name = "day",
|
name = "day",
|
||||||
doc = "Construct a `Duration` equal to this real number of days.",
|
doc = "Construct a `Duration` equal to this real number of days.",
|
||||||
returns = type("lyng.Duration"),
|
type = type("lyng.Duration"),
|
||||||
moduleName = "lyng.time"
|
moduleName = "lyng.time",
|
||||||
) { ObjDuration(thisAs<ObjReal>().value.days) }
|
getter = { ObjDuration(thisAs<ObjReal>().value.days) }
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
// addFn("epochSeconds") {
|
// addFn("epochSeconds") {
|
||||||
|
|||||||
@ -35,6 +35,8 @@ package net.sergeych.lyng.obj/*
|
|||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
import kotlinx.serialization.json.JsonPrimitive
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||||
|
import net.sergeych.lyng.miniast.type
|
||||||
import net.sergeych.lynon.LynonDecoder
|
import net.sergeych.lynon.LynonDecoder
|
||||||
import net.sergeych.lynon.LynonEncoder
|
import net.sergeych.lynon.LynonEncoder
|
||||||
import net.sergeych.lynon.LynonType
|
import net.sergeych.lynon.LynonType
|
||||||
@ -76,8 +78,8 @@ class ObjEnumClass(val name: String) : ObjClass(name, EnumBase) {
|
|||||||
val name = requireOnlyArg<ObjString>()
|
val name = requireOnlyArg<ObjString>()
|
||||||
byName[name] ?: raiseSymbolNotFound("does not exists: enum ${className}.$name")
|
byName[name] ?: raiseSymbolNotFound("does not exists: enum ${className}.$name")
|
||||||
}
|
}
|
||||||
addFn("name") { thisAs<ObjEnumEntry>().name }
|
addPropertyDoc("name", doc = "Entry name as string", type = type("lyng.String"), getter = { thisAs<ObjEnumEntry>().name })
|
||||||
addFn("ordinal") { thisAs<ObjEnumEntry>().ordinal }
|
addPropertyDoc("ordinal", doc = "Entry ordinal position", type = type("lyng.Int"), getter = { thisAs<ObjEnumEntry>().ordinal })
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,11 +17,10 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
import net.sergeych.bintools.encodeToHex
|
|
||||||
import net.sergeych.lyng.*
|
import net.sergeych.lyng.*
|
||||||
import net.sergeych.lyng.miniast.TypeGenericDoc
|
import net.sergeych.lyng.miniast.TypeGenericDoc
|
||||||
import net.sergeych.lyng.miniast.addConstDoc
|
|
||||||
import net.sergeych.lyng.miniast.addFnDoc
|
import net.sergeych.lyng.miniast.addFnDoc
|
||||||
|
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||||
import net.sergeych.lyng.miniast.type
|
import net.sergeych.lyng.miniast.type
|
||||||
import net.sergeych.lynon.LynonDecoder
|
import net.sergeych.lynon.LynonDecoder
|
||||||
import net.sergeych.lynon.LynonEncoder
|
import net.sergeych.lynon.LynonEncoder
|
||||||
@ -133,7 +132,7 @@ open class ObjException(
|
|||||||
return ObjException(this, scope, message)
|
return ObjException(this, scope, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String = "ExceptionClass[$name]@${hashCode().encodeToHex()}"
|
override fun toString(): String = name
|
||||||
|
|
||||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
|
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
|
||||||
return try {
|
return try {
|
||||||
@ -170,43 +169,44 @@ open class ObjException(
|
|||||||
ObjVoid
|
ObjVoid
|
||||||
})
|
})
|
||||||
instanceConstructor = statement { ObjVoid }
|
instanceConstructor = statement { ObjVoid }
|
||||||
addConstDoc(
|
addPropertyDoc(
|
||||||
name = "message",
|
name = "message",
|
||||||
value = statement {
|
doc = "Human‑readable error message.",
|
||||||
when (val t = thisObj) {
|
type = type("lyng.String"),
|
||||||
|
moduleName = "lyng.stdlib",
|
||||||
|
getter = {
|
||||||
|
when (val t = this.thisObj) {
|
||||||
is ObjException -> t.message
|
is ObjException -> t.message
|
||||||
is ObjInstance -> t.instanceScope.get("Exception::message")?.value ?: ObjNull
|
is ObjInstance -> t.instanceScope.get("Exception::message")?.value ?: ObjNull
|
||||||
else -> ObjNull
|
else -> ObjNull
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
doc = "Human‑readable error message.",
|
|
||||||
type = type("lyng.String"),
|
|
||||||
moduleName = "lyng.stdlib"
|
|
||||||
)
|
)
|
||||||
addConstDoc(
|
addPropertyDoc(
|
||||||
name = "extraData",
|
name = "extraData",
|
||||||
value = statement {
|
doc = "Extra data associated with the exception.",
|
||||||
when (val t = thisObj) {
|
type = type("lyng.Any", nullable = true),
|
||||||
|
moduleName = "lyng.stdlib",
|
||||||
|
getter = {
|
||||||
|
when (val t = this.thisObj) {
|
||||||
is ObjException -> t.extraData
|
is ObjException -> t.extraData
|
||||||
else -> ObjNull
|
else -> ObjNull
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
doc = "Extra data associated with the exception.",
|
|
||||||
type = type("lyng.Any", nullable = true),
|
|
||||||
moduleName = "lyng.stdlib"
|
|
||||||
)
|
)
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "stackTrace",
|
name = "stackTrace",
|
||||||
doc = "Stack trace captured at throw site as a list of `StackTraceEntry`.",
|
doc = "Stack trace captured at throw site as a list of `StackTraceEntry`.",
|
||||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.StackTraceEntry"))),
|
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.StackTraceEntry"))),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = {
|
||||||
when (val t = thisObj) {
|
when (val t = this.thisObj) {
|
||||||
is ObjException -> t.getStackTrace()
|
is ObjException -> t.getStackTrace()
|
||||||
is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList()
|
is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList()
|
||||||
else -> ObjList()
|
else -> ObjList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "toString",
|
name = "toString",
|
||||||
doc = "Human‑readable string representation of the error.",
|
doc = "Human‑readable string representation of the error.",
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import kotlinx.serialization.json.JsonElement
|
|||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import net.sergeych.lyng.Arguments
|
import net.sergeych.lyng.Arguments
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lyng.Visibility
|
||||||
import net.sergeych.lyng.canAccessMember
|
import net.sergeych.lyng.canAccessMember
|
||||||
import net.sergeych.lynon.LynonDecoder
|
import net.sergeych.lynon.LynonDecoder
|
||||||
import net.sergeych.lynon.LynonEncoder
|
import net.sergeych.lynon.LynonEncoder
|
||||||
@ -31,50 +32,35 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
internal lateinit var instanceScope: Scope
|
internal lateinit var instanceScope: Scope
|
||||||
|
|
||||||
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||||
// 1. Direct (unmangled) lookup first
|
val caller = scope.currentClassCtx
|
||||||
instanceScope[name]?.let { rec ->
|
|
||||||
|
// 0. Private mangled of current class context
|
||||||
|
caller?.let { c ->
|
||||||
|
val mangled = "${c.className}::$name"
|
||||||
|
instanceScope.objects[mangled]?.let { rec ->
|
||||||
|
if (rec.visibility == Visibility.Private) {
|
||||||
|
return resolveRecord(scope, rec, name, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. MRO mangled storage
|
||||||
|
for (cls in objClass.mro) {
|
||||||
|
if (cls.className == "Obj") break
|
||||||
|
val mangled = "${cls.className}::$name"
|
||||||
|
instanceScope.objects[mangled]?.let { rec ->
|
||||||
|
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.visibility, cls, caller)) {
|
||||||
|
return resolveRecord(scope, rec, name, cls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Unmangled storage
|
||||||
|
instanceScope.objects[name]?.let { rec ->
|
||||||
val decl = rec.declaringClass
|
val decl = rec.declaringClass
|
||||||
// Allow unconditional access when accessing through `this` of the same instance
|
// Unmangled access is only allowed if it's public OR we are inside the same instance's method
|
||||||
// BUT only if we are in the class context (not extension)
|
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.visibility, decl, caller))
|
||||||
if (scope.thisObj !== this || scope.currentClassCtx == null) {
|
return resolveRecord(scope, rec, name, decl)
|
||||||
val caller = scope.currentClassCtx
|
|
||||||
if (!canAccessMember(rec.visibility, decl, caller))
|
|
||||||
scope.raiseError(
|
|
||||||
ObjIllegalAccessException(
|
|
||||||
scope,
|
|
||||||
"can't access field $name (declared in ${decl?.className ?: "?"})"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return resolveRecord(scope, rec, name, decl)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. MI-mangled instance scope lookup
|
|
||||||
val cls = objClass
|
|
||||||
fun findMangledInRead(): ObjRecord? {
|
|
||||||
instanceScope.objects["${cls.className}::$name"]?.let { return it }
|
|
||||||
for (p in cls.mroParents) {
|
|
||||||
instanceScope.objects["${p.className}::$name"]?.let { return it }
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
findMangledInRead()?.let { rec ->
|
|
||||||
val declaring = when {
|
|
||||||
instanceScope.objects.containsKey("${cls.className}::$name") -> cls
|
|
||||||
else -> cls.mroParents.firstOrNull { instanceScope.objects.containsKey("${it.className}::$name") }
|
|
||||||
}
|
|
||||||
if (scope.thisObj !== this || scope.currentClassCtx == null) {
|
|
||||||
val caller = scope.currentClassCtx
|
|
||||||
if (!canAccessMember(rec.visibility, declaring, caller))
|
|
||||||
scope.raiseError(
|
|
||||||
ObjIllegalAccessException(
|
|
||||||
scope,
|
|
||||||
"can't access field $name (declared in ${declaring?.className ?: "?"})"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return resolveRecord(scope, rec, name, declaring)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Fall back to super (handles class members and extensions)
|
// 3. Fall back to super (handles class members and extensions)
|
||||||
@ -83,6 +69,90 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
|
|
||||||
override suspend fun resolveRecord(scope: Scope, obj: ObjRecord, name: String, decl: ObjClass?): ObjRecord {
|
override suspend fun resolveRecord(scope: Scope, obj: ObjRecord, name: String, decl: ObjClass?): ObjRecord {
|
||||||
if (obj.type == ObjRecord.Type.Delegated) {
|
if (obj.type == ObjRecord.Type.Delegated) {
|
||||||
|
val d = decl ?: obj.declaringClass
|
||||||
|
val storageName = "${d?.className}::$name"
|
||||||
|
var del = instanceScope[storageName]?.delegate ?: obj.delegate
|
||||||
|
if (del == null) {
|
||||||
|
for (c in objClass.mro) {
|
||||||
|
del = instanceScope["${c.className}::$name"]?.delegate
|
||||||
|
if (del != null) break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
del = del ?: scope.raiseError("Internal error: delegated property $name has no delegate")
|
||||||
|
val res = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name)))
|
||||||
|
obj.value = res
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map member template to instance storage if applicable
|
||||||
|
var targetRec = obj
|
||||||
|
val d = decl ?: obj.declaringClass
|
||||||
|
if (d != null) {
|
||||||
|
val mangled = "${d.className}::$name"
|
||||||
|
instanceScope.objects[mangled]?.let {
|
||||||
|
targetRec = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (targetRec === obj) {
|
||||||
|
instanceScope.objects[name]?.let { rec ->
|
||||||
|
// Check if this record in instanceScope is the one we want.
|
||||||
|
// For members, it must match the declaring class.
|
||||||
|
// Arguments are also preferred.
|
||||||
|
if (rec.type == ObjRecord.Type.Argument || rec.declaringClass == d || rec.declaringClass == null) {
|
||||||
|
targetRec = rec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.resolveRecord(scope, targetRec, name, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
|
||||||
|
willMutate(scope)
|
||||||
|
val caller = scope.currentClassCtx
|
||||||
|
|
||||||
|
// 0. Private mangled of current class context
|
||||||
|
caller?.let { c ->
|
||||||
|
val mangled = "${c.className}::$name"
|
||||||
|
instanceScope.objects[mangled]?.let { rec ->
|
||||||
|
if (rec.visibility == Visibility.Private) {
|
||||||
|
updateRecord(scope, rec, name, newValue, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. MRO mangled storage
|
||||||
|
for (cls in objClass.mro) {
|
||||||
|
if (cls.className == "Obj") break
|
||||||
|
val mangled = "${cls.className}::$name"
|
||||||
|
instanceScope.objects[mangled]?.let { rec ->
|
||||||
|
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.effectiveWriteVisibility, cls, caller)) {
|
||||||
|
updateRecord(scope, rec, name, newValue, cls)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Unmangled storage
|
||||||
|
instanceScope.objects[name]?.let { rec ->
|
||||||
|
val decl = rec.declaringClass
|
||||||
|
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.effectiveWriteVisibility, decl, caller)) {
|
||||||
|
updateRecord(scope, rec, name, newValue, decl)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.writeField(scope, name, newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun updateRecord(scope: Scope, rec: ObjRecord, name: String, newValue: Obj, decl: ObjClass?) {
|
||||||
|
if (rec.type == ObjRecord.Type.Property) {
|
||||||
|
val prop = rec.value as ObjProperty
|
||||||
|
prop.callSetter(scope, this, newValue, decl)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (rec.type == ObjRecord.Type.Delegated) {
|
||||||
val storageName = "${decl?.className}::$name"
|
val storageName = "${decl?.className}::$name"
|
||||||
var del = instanceScope[storageName]?.delegate
|
var del = instanceScope[storageName]?.delegate
|
||||||
if (del == null) {
|
if (del == null) {
|
||||||
@ -91,136 +161,80 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
if (del != null) break
|
if (del != null) break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
del = del ?: obj.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate (tried $storageName)")
|
del = del ?: rec.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate (tried $storageName)")
|
||||||
val res = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name)))
|
del.invokeInstanceMethod(scope, "setValue", Arguments(this, ObjString(name), newValue))
|
||||||
obj.value = res
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
return super.resolveRecord(scope, obj, name, decl)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
|
|
||||||
// Direct (unmangled) first
|
|
||||||
instanceScope[name]?.let { f ->
|
|
||||||
val decl = f.declaringClass
|
|
||||||
if (scope.thisObj !== this || scope.currentClassCtx == null) {
|
|
||||||
val caller = scope.currentClassCtx
|
|
||||||
if (!canAccessMember(f.effectiveWriteVisibility, decl, caller))
|
|
||||||
ObjIllegalAccessException(
|
|
||||||
scope,
|
|
||||||
"can't assign to field $name (declared in ${decl?.className ?: "?"})"
|
|
||||||
).raise()
|
|
||||||
}
|
|
||||||
if (f.type == ObjRecord.Type.Property) {
|
|
||||||
val prop = f.value as ObjProperty
|
|
||||||
prop.callSetter(scope, this, newValue, decl)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (f.type == ObjRecord.Type.Delegated) {
|
|
||||||
val storageName = "${decl?.className}::$name"
|
|
||||||
var del = instanceScope[storageName]?.delegate
|
|
||||||
if (del == null) {
|
|
||||||
for (c in objClass.mro) {
|
|
||||||
del = instanceScope["${c.className}::$name"]?.delegate
|
|
||||||
if (del != null) break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
del = del ?: f.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate (tried $storageName)")
|
|
||||||
del.invokeInstanceMethod(scope, "setValue", Arguments(this, ObjString(name), newValue))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!f.isMutable && f.value !== ObjUnset) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
|
||||||
if (f.value.assign(scope, newValue) == null)
|
|
||||||
f.value = newValue
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Try MI-mangled resolution along linearization (C3 MRO)
|
if (!rec.isMutable && rec.value !== ObjUnset) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
||||||
val cls = objClass
|
if (rec.value.assign(scope, newValue) == null)
|
||||||
fun findMangled(): ObjRecord? {
|
rec.value = newValue
|
||||||
instanceScope.objects["${cls.className}::$name"]?.let { return it }
|
|
||||||
for (p in cls.mroParents) {
|
|
||||||
instanceScope.objects["${p.className}::$name"]?.let { return it }
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
val rec = findMangled()
|
|
||||||
if (rec != null) {
|
|
||||||
val declaring = when {
|
|
||||||
instanceScope.objects.containsKey("${cls.className}::$name") -> cls
|
|
||||||
else -> cls.mroParents.firstOrNull { instanceScope.objects.containsKey("${it.className}::$name") }
|
|
||||||
}
|
|
||||||
if (scope.thisObj !== this || scope.currentClassCtx == null) {
|
|
||||||
val caller = scope.currentClassCtx
|
|
||||||
if (!canAccessMember(rec.effectiveWriteVisibility, declaring, caller))
|
|
||||||
ObjIllegalAccessException(
|
|
||||||
scope,
|
|
||||||
"can't assign to field $name (declared in ${declaring?.className ?: "?"})"
|
|
||||||
).raise()
|
|
||||||
}
|
|
||||||
if (rec.type == ObjRecord.Type.Property) {
|
|
||||||
val prop = rec.value as ObjProperty
|
|
||||||
prop.callSetter(scope, this, newValue, declaring)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (rec.type == ObjRecord.Type.Delegated) {
|
|
||||||
val storageName = "${declaring?.className}::$name"
|
|
||||||
var del = instanceScope[storageName]?.delegate
|
|
||||||
if (del == null) {
|
|
||||||
for (c in objClass.mro) {
|
|
||||||
del = instanceScope["${c.className}::$name"]?.delegate
|
|
||||||
if (del != null) break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
del = del ?: rec.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate (tried $storageName)")
|
|
||||||
del.invokeInstanceMethod(scope, "setValue", Arguments(this, ObjString(name), newValue))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!rec.isMutable && rec.value !== ObjUnset) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
|
||||||
if (rec.value.assign(scope, newValue) == null)
|
|
||||||
rec.value = newValue
|
|
||||||
return
|
|
||||||
}
|
|
||||||
super.writeField(scope, name, newValue)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun invokeInstanceMethod(
|
override suspend fun invokeInstanceMethod(
|
||||||
scope: Scope, name: String, args: Arguments,
|
scope: Scope, name: String, args: Arguments,
|
||||||
onNotFoundResult: (suspend () -> Obj?)?
|
onNotFoundResult: (suspend () -> Obj?)?
|
||||||
): Obj {
|
): Obj {
|
||||||
|
// 0. Prefer private member of current class context
|
||||||
|
scope.currentClassCtx?.let { caller ->
|
||||||
|
val mangled = "${caller.className}::$name"
|
||||||
|
instanceScope.objects[mangled]?.let { rec ->
|
||||||
|
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
|
||||||
|
if (rec.type == ObjRecord.Type.Property) {
|
||||||
|
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, caller)
|
||||||
|
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||||
|
return rec.value.invoke(instanceScope, this, args, caller)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
caller.members[name]?.let { rec ->
|
||||||
|
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
|
||||||
|
if (rec.type == ObjRecord.Type.Property) {
|
||||||
|
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, caller)
|
||||||
|
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||||
|
return rec.value.invoke(instanceScope, this, args, caller)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Walk MRO to find member, handling delegation
|
// 1. Walk MRO to find member, handling delegation
|
||||||
for (cls in objClass.mro) {
|
for (cls in objClass.mro) {
|
||||||
if (cls.className == "Obj") break
|
if (cls.className == "Obj") break
|
||||||
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
||||||
if (rec != null) {
|
if (rec != null && !rec.isAbstract) {
|
||||||
if (rec.type == ObjRecord.Type.Delegated) {
|
if (rec.type == ObjRecord.Type.Delegated) {
|
||||||
val storageName = "${cls.className}::$name"
|
val storageName = "${cls.className}::$name"
|
||||||
val del = instanceScope[storageName]?.delegate ?: rec.delegate
|
val del = instanceScope[storageName]?.delegate ?: rec.delegate
|
||||||
?: scope.raiseError("Internal error: delegated member $name has no delegate (tried $storageName)")
|
?: scope.raiseError("Internal error: delegated member $name has no delegate (tried $storageName)")
|
||||||
|
|
||||||
|
// For delegated member, try 'invoke' first if it's a function-like call
|
||||||
val allArgs = (listOf(this, ObjString(name)) + args.list).toTypedArray()
|
val allArgs = (listOf(this, ObjString(name)) + args.list).toTypedArray()
|
||||||
return del.invokeInstanceMethod(scope, "invoke", Arguments(*allArgs), onNotFoundResult = {
|
return del.invokeInstanceMethod(scope, "invoke", Arguments(*allArgs), onNotFoundResult = {
|
||||||
// Fallback: property delegation
|
// Fallback: property delegation (getValue then call result)
|
||||||
val propVal = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name)))
|
val propVal = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name)))
|
||||||
propVal.invoke(scope, this, args, rec.declaringClass ?: cls)
|
propVal.invoke(scope, this, args, rec.declaringClass ?: cls)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (rec.type == ObjRecord.Type.Fun && !rec.isAbstract) {
|
val decl = rec.declaringClass ?: cls
|
||||||
val decl = rec.declaringClass ?: cls
|
val caller = scope.currentClassCtx ?: if (scope.thisObj === this) objClass else null
|
||||||
val caller = scope.currentClassCtx ?: if (scope.thisObj === this) objClass else null
|
if (!canAccessMember(rec.visibility, decl, caller))
|
||||||
if (!canAccessMember(rec.visibility, decl, caller))
|
scope.raiseError(
|
||||||
scope.raiseError(
|
ObjIllegalAccessException(
|
||||||
ObjIllegalAccessException(
|
scope,
|
||||||
scope,
|
"can't invoke method $name (declared in ${decl.className})"
|
||||||
"can't invoke method $name (declared in ${decl.className})"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (rec.type == ObjRecord.Type.Property) {
|
||||||
|
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
|
||||||
|
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||||
return rec.value.invoke(
|
return rec.value.invoke(
|
||||||
instanceScope,
|
instanceScope,
|
||||||
this,
|
this,
|
||||||
args,
|
args,
|
||||||
decl
|
decl
|
||||||
)
|
)
|
||||||
} else if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.Property) && !rec.isAbstract) {
|
} else if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField || rec.type == ObjRecord.Type.Argument) {
|
||||||
val resolved = readField(scope, name)
|
val resolved = readField(scope, name)
|
||||||
return resolved.value.invoke(scope, this, args, resolved.declaringClass)
|
return resolved.value.invoke(scope, this, args, resolved.declaringClass)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -20,6 +20,9 @@ package net.sergeych.lyng.obj
|
|||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
import kotlinx.serialization.json.JsonPrimitive
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lyng.miniast.addFnDoc
|
||||||
|
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||||
|
import net.sergeych.lyng.miniast.type
|
||||||
import net.sergeych.lynon.LynonDecoder
|
import net.sergeych.lynon.LynonDecoder
|
||||||
import net.sergeych.lynon.LynonEncoder
|
import net.sergeych.lynon.LynonEncoder
|
||||||
import net.sergeych.lynon.LynonSettings
|
import net.sergeych.lynon.LynonSettings
|
||||||
@ -148,34 +151,71 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru
|
|||||||
}
|
}
|
||||||
|
|
||||||
}.apply {
|
}.apply {
|
||||||
addFn("epochSeconds") {
|
addPropertyDoc(
|
||||||
val instant = thisAs<ObjInstant>().instant
|
name = "epochSeconds",
|
||||||
ObjReal(instant.epochSeconds + instant.nanosecondsOfSecond * 1e-9)
|
doc = "Return the number of seconds since the Unix epoch as a real number (including fractions).",
|
||||||
}
|
type = type("lyng.Real"),
|
||||||
addFn("isDistantFuture") {
|
moduleName = "lyng.time",
|
||||||
thisAs<ObjInstant>().instant.isDistantFuture.toObj()
|
getter = {
|
||||||
}
|
val instant = thisAs<ObjInstant>().instant
|
||||||
addFn("isDistantPast") {
|
ObjReal(instant.epochSeconds + instant.nanosecondsOfSecond * 1e-9)
|
||||||
thisAs<ObjInstant>().instant.isDistantPast.toObj()
|
}
|
||||||
}
|
)
|
||||||
addFn("epochWholeSeconds") {
|
addPropertyDoc(
|
||||||
ObjInt(thisAs<ObjInstant>().instant.epochSeconds)
|
name = "isDistantFuture",
|
||||||
}
|
doc = "Whether this instant represents the distant future.",
|
||||||
addFn("nanosecondsOfSecond") {
|
type = type("lyng.Bool"),
|
||||||
ObjInt(thisAs<ObjInstant>().instant.nanosecondsOfSecond.toLong())
|
moduleName = "lyng.time",
|
||||||
}
|
getter = { thisAs<ObjInstant>().instant.isDistantFuture.toObj() }
|
||||||
addFn("truncateToSecond") {
|
)
|
||||||
|
addPropertyDoc(
|
||||||
|
name = "isDistantPast",
|
||||||
|
doc = "Whether this instant represents the distant past.",
|
||||||
|
type = type("lyng.Bool"),
|
||||||
|
moduleName = "lyng.time",
|
||||||
|
getter = { thisAs<ObjInstant>().instant.isDistantPast.toObj() }
|
||||||
|
)
|
||||||
|
addPropertyDoc(
|
||||||
|
name = "epochWholeSeconds",
|
||||||
|
doc = "Return the number of full seconds since the Unix epoch.",
|
||||||
|
type = type("lyng.Int"),
|
||||||
|
moduleName = "lyng.time",
|
||||||
|
getter = { ObjInt(thisAs<ObjInstant>().instant.epochSeconds) }
|
||||||
|
)
|
||||||
|
addPropertyDoc(
|
||||||
|
name = "nanosecondsOfSecond",
|
||||||
|
doc = "The number of nanoseconds within the current second.",
|
||||||
|
type = type("lyng.Int"),
|
||||||
|
moduleName = "lyng.time",
|
||||||
|
getter = { ObjInt(thisAs<ObjInstant>().instant.nanosecondsOfSecond.toLong()) }
|
||||||
|
)
|
||||||
|
addFnDoc(
|
||||||
|
name = "truncateToSecond",
|
||||||
|
doc = "Truncate this instant to the nearest second.",
|
||||||
|
returns = type("lyng.Instant"),
|
||||||
|
moduleName = "lyng.time"
|
||||||
|
) {
|
||||||
val t = thisAs<ObjInstant>().instant
|
val t = thisAs<ObjInstant>().instant
|
||||||
ObjInstant(Instant.fromEpochSeconds(t.epochSeconds), LynonSettings.InstantTruncateMode.Second)
|
ObjInstant(Instant.fromEpochSeconds(t.epochSeconds), LynonSettings.InstantTruncateMode.Second)
|
||||||
}
|
}
|
||||||
addFn("truncateToMillisecond") {
|
addFnDoc(
|
||||||
|
name = "truncateToMillisecond",
|
||||||
|
doc = "Truncate this instant to the nearest millisecond.",
|
||||||
|
returns = type("lyng.Instant"),
|
||||||
|
moduleName = "lyng.time"
|
||||||
|
) {
|
||||||
val t = thisAs<ObjInstant>().instant
|
val t = thisAs<ObjInstant>().instant
|
||||||
ObjInstant(
|
ObjInstant(
|
||||||
Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond / 1_000_000 * 1_000_000),
|
Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond / 1_000_000 * 1_000_000),
|
||||||
LynonSettings.InstantTruncateMode.Millisecond
|
LynonSettings.InstantTruncateMode.Millisecond
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
addFn("truncateToMicrosecond") {
|
addFnDoc(
|
||||||
|
name = "truncateToMicrosecond",
|
||||||
|
doc = "Truncate this instant to the nearest microsecond.",
|
||||||
|
returns = type("lyng.Instant"),
|
||||||
|
moduleName = "lyng.time"
|
||||||
|
) {
|
||||||
val t = thisAs<ObjInstant>().instant
|
val t = thisAs<ObjInstant>().instant
|
||||||
ObjInstant(
|
ObjInstant(
|
||||||
Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond / 1_000 * 1_000),
|
Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond / 1_000 * 1_000),
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -21,6 +21,7 @@ import net.sergeych.lyng.Arguments
|
|||||||
import net.sergeych.lyng.Statement
|
import net.sergeych.lyng.Statement
|
||||||
import net.sergeych.lyng.miniast.ParamDoc
|
import net.sergeych.lyng.miniast.ParamDoc
|
||||||
import net.sergeych.lyng.miniast.addFnDoc
|
import net.sergeych.lyng.miniast.addFnDoc
|
||||||
|
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||||
import net.sergeych.lyng.miniast.type
|
import net.sergeych.lyng.miniast.type
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,19 +30,20 @@ import net.sergeych.lyng.miniast.type
|
|||||||
val ObjIterable by lazy {
|
val ObjIterable by lazy {
|
||||||
ObjClass("Iterable").apply {
|
ObjClass("Iterable").apply {
|
||||||
|
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "toList",
|
name = "toList",
|
||||||
doc = "Collect elements of this iterable into a new list.",
|
doc = "Collect elements of this iterable into a new list.",
|
||||||
returns = type("lyng.List"),
|
type = type("lyng.List"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = {
|
||||||
val result = mutableListOf<Obj>()
|
val result = mutableListOf<Obj>()
|
||||||
val iterator = thisObj.invokeInstanceMethod(this, "iterator")
|
val it = this.thisObj.invokeInstanceMethod(this, "iterator")
|
||||||
|
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||||
while (iterator.invokeInstanceMethod(this, "hasNext").toBool())
|
result.add(it.invokeInstanceMethod(this, "next"))
|
||||||
result += iterator.invokeInstanceMethod(this, "next")
|
}
|
||||||
ObjList(result)
|
ObjList(result)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// it is not effective, but it is open:
|
// it is not effective, but it is open:
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
@ -55,7 +57,7 @@ val ObjIterable by lazy {
|
|||||||
val obj = args.firstAndOnly()
|
val obj = args.firstAndOnly()
|
||||||
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
||||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||||
if (obj.compareTo(this, it.invokeInstanceMethod(this, "next")) == 0)
|
if (obj.equals(this, it.invokeInstanceMethod(this, "next")))
|
||||||
return@addFnDoc ObjTrue
|
return@addFnDoc ObjTrue
|
||||||
}
|
}
|
||||||
ObjFalse
|
ObjFalse
|
||||||
@ -73,46 +75,49 @@ val ObjIterable by lazy {
|
|||||||
var index = 0
|
var index = 0
|
||||||
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
||||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||||
if (obj.compareTo(this, it.invokeInstanceMethod(this, "next")) == 0)
|
if (obj.equals(this, it.invokeInstanceMethod(this, "next")))
|
||||||
return@addFnDoc ObjInt(index.toLong())
|
return@addFnDoc ObjInt(index.toLong())
|
||||||
index++
|
index++
|
||||||
}
|
}
|
||||||
ObjInt(-1L)
|
ObjInt(-1L)
|
||||||
}
|
}
|
||||||
|
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "toSet",
|
name = "toSet",
|
||||||
doc = "Collect elements of this iterable into a new set.",
|
doc = "Collect elements of this iterable into a new set.",
|
||||||
returns = type("lyng.Set"),
|
type = type("lyng.Set"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = {
|
||||||
if( thisObj.isInstanceOf(ObjSet.type) )
|
if( this.thisObj.isInstanceOf(ObjSet.type) )
|
||||||
thisObj
|
this.thisObj
|
||||||
else {
|
else {
|
||||||
val result = mutableSetOf<Obj>()
|
val result = mutableSetOf<Obj>()
|
||||||
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
val it = this.thisObj.invokeInstanceMethod(this, "iterator")
|
||||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||||
result += it.invokeInstanceMethod(this, "next")
|
result.add(it.invokeInstanceMethod(this, "next"))
|
||||||
|
}
|
||||||
|
ObjSet(result)
|
||||||
}
|
}
|
||||||
ObjSet(result)
|
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "toMap",
|
name = "toMap",
|
||||||
doc = "Collect pairs into a map using [0] as key and [1] as value for each element.",
|
doc = "Collect pairs into a map using [0] as key and [1] as value for each element.",
|
||||||
returns = type("lyng.Map"),
|
type = type("lyng.Map"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = {
|
||||||
val result = mutableMapOf<Obj, Obj>()
|
val result = mutableMapOf<Obj, Obj>()
|
||||||
thisObj.toFlow(this).collect { pair ->
|
this.thisObj.enumerate(this) { pair ->
|
||||||
when (pair) {
|
when (pair) {
|
||||||
is ObjMapEntry -> result[pair.key] = pair.value
|
is ObjMapEntry -> result[pair.key] = pair.value
|
||||||
else -> result[pair.getAt(this, 0)] = pair.getAt(this, 1)
|
else -> result[pair.getAt(this, 0)] = pair.getAt(this, 1)
|
||||||
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
ObjMap(result)
|
||||||
}
|
}
|
||||||
ObjMap(result)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "associateBy",
|
name = "associateBy",
|
||||||
@ -156,7 +161,7 @@ val ObjIterable by lazy {
|
|||||||
val fn = requiredArg<Statement>(0)
|
val fn = requiredArg<Statement>(0)
|
||||||
val result = mutableListOf<Obj>()
|
val result = mutableListOf<Obj>()
|
||||||
thisObj.toFlow(this).collect {
|
thisObj.toFlow(this).collect {
|
||||||
result += fn.call(this, it)
|
result.add(fn.call(this, it))
|
||||||
}
|
}
|
||||||
ObjList(result)
|
ObjList(result)
|
||||||
}
|
}
|
||||||
@ -173,7 +178,7 @@ val ObjIterable by lazy {
|
|||||||
val result = mutableListOf<Obj>()
|
val result = mutableListOf<Obj>()
|
||||||
thisObj.toFlow(this).collect {
|
thisObj.toFlow(this).collect {
|
||||||
val transformed = fn.call(this, it)
|
val transformed = fn.call(this, it)
|
||||||
if( transformed != ObjNull) result += transformed
|
if( transformed != ObjNull) result.add(transformed)
|
||||||
}
|
}
|
||||||
ObjList(result)
|
ObjList(result)
|
||||||
}
|
}
|
||||||
@ -189,25 +194,26 @@ val ObjIterable by lazy {
|
|||||||
val result = mutableListOf<Obj>()
|
val result = mutableListOf<Obj>()
|
||||||
if (n > 0) {
|
if (n > 0) {
|
||||||
thisObj.enumerate(this) {
|
thisObj.enumerate(this) {
|
||||||
result += it
|
result.add(it)
|
||||||
--n > 0
|
--n > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ObjList(result)
|
ObjList(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "isEmpty",
|
name = "isEmpty",
|
||||||
doc = "Whether the iterable has no elements.",
|
doc = "Whether the iterable has no elements.",
|
||||||
returns = type("lyng.Bool"),
|
type = type("lyng.Bool"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = {
|
||||||
ObjBool(
|
ObjBool(
|
||||||
thisObj.invokeInstanceMethod(this, "iterator")
|
this.thisObj.invokeInstanceMethod(this, "iterator")
|
||||||
.invokeInstanceMethod(this, "hasNext").toBool()
|
.invokeInstanceMethod(this, "hasNext").toBool()
|
||||||
.not()
|
.not()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "sortedWith",
|
name = "sortedWith",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -22,16 +22,30 @@ import kotlinx.serialization.json.JsonElement
|
|||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.Statement
|
import net.sergeych.lyng.Statement
|
||||||
import net.sergeych.lyng.miniast.ParamDoc
|
import net.sergeych.lyng.miniast.ParamDoc
|
||||||
import net.sergeych.lyng.miniast.addConstDoc
|
|
||||||
import net.sergeych.lyng.miniast.addFnDoc
|
import net.sergeych.lyng.miniast.addFnDoc
|
||||||
|
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||||
import net.sergeych.lyng.miniast.type
|
import net.sergeych.lyng.miniast.type
|
||||||
import net.sergeych.lyng.statement
|
|
||||||
import net.sergeych.lynon.LynonDecoder
|
import net.sergeych.lynon.LynonDecoder
|
||||||
import net.sergeych.lynon.LynonEncoder
|
import net.sergeych.lynon.LynonEncoder
|
||||||
import net.sergeych.lynon.LynonType
|
import net.sergeych.lynon.LynonType
|
||||||
|
|
||||||
class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||||
|
|
||||||
|
override suspend fun equals(scope: Scope, other: Obj): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is ObjList) {
|
||||||
|
if (other.isInstanceOf(ObjIterable)) {
|
||||||
|
return compareTo(scope, other) == 0
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (list.size != other.list.size) return false
|
||||||
|
for (i in 0..<list.size) {
|
||||||
|
if (!list[i].equals(scope, other.list[i])) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getAt(scope: Scope, index: Obj): Obj {
|
override suspend fun getAt(scope: Scope, index: Obj): Obj {
|
||||||
return when (index) {
|
return when (index) {
|
||||||
is ObjInt -> {
|
is ObjInt -> {
|
||||||
@ -77,21 +91,35 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
if (other !is ObjList) return -2
|
if (other is ObjList) {
|
||||||
val mySize = list.size
|
val mySize = list.size
|
||||||
val otherSize = other.list.size
|
val otherSize = other.list.size
|
||||||
val commonSize = minOf(mySize, otherSize)
|
val commonSize = minOf(mySize, otherSize)
|
||||||
for (i in 0..<commonSize) {
|
for (i in 0..<commonSize) {
|
||||||
if (list[i].compareTo(scope, other.list[i]) != 0) {
|
val d = list[i].compareTo(scope, other.list[i])
|
||||||
return list[i].compareTo(scope, other.list[i])
|
if (d != 0) {
|
||||||
|
return d
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
val res = mySize.compareTo(otherSize)
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
// equal so far, longer is greater:
|
if (other.isInstanceOf(ObjIterable)) {
|
||||||
return when {
|
val it1 = this.list.iterator()
|
||||||
mySize < otherSize -> -1
|
val it2 = other.invokeInstanceMethod(scope, "iterator")
|
||||||
mySize > otherSize -> 1
|
val hasNext2 = it2.getInstanceMethod(scope, "hasNext")
|
||||||
else -> 0
|
val next2 = it2.getInstanceMethod(scope, "next")
|
||||||
|
|
||||||
|
while (it1.hasNext()) {
|
||||||
|
if (!hasNext2.invoke(scope, it2).toBool()) return 1 // I'm longer
|
||||||
|
val v1 = it1.next()
|
||||||
|
val v2 = next2.invoke(scope, it2)
|
||||||
|
val d = v1.compareTo(scope, v2)
|
||||||
|
if (d != 0) return d
|
||||||
|
}
|
||||||
|
return if (hasNext2.invoke(scope, it2).toBool()) -1 else 0
|
||||||
}
|
}
|
||||||
|
return -2
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun plus(scope: Scope, other: Obj): Obj =
|
override suspend fun plus(scope: Scope, other: Obj): Obj =
|
||||||
@ -99,27 +127,28 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
other is ObjList ->
|
other is ObjList ->
|
||||||
ObjList((list + other.list).toMutableList())
|
ObjList((list + other.list).toMutableList())
|
||||||
|
|
||||||
other.isInstanceOf(ObjIterable) -> {
|
other.isInstanceOf(ObjIterable) && other !is ObjString && other !is ObjBuffer -> {
|
||||||
val l = other.callMethod<ObjList>(scope, "toList")
|
val l = other.callMethod<ObjList>(scope, "toList")
|
||||||
ObjList((list + l.list).toMutableList())
|
ObjList((list + l.list).toMutableList())
|
||||||
}
|
}
|
||||||
|
|
||||||
else ->
|
else -> {
|
||||||
scope.raiseError("'+': can't concatenate ${this.toString(scope)} with ${other.toString(scope)}")
|
val newList = list.toMutableList()
|
||||||
|
newList.add(other)
|
||||||
|
ObjList(newList)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override suspend fun plusAssign(scope: Scope, other: Obj): Obj {
|
override suspend fun plusAssign(scope: Scope, other: Obj): Obj {
|
||||||
// optimization
|
|
||||||
if (other is ObjList) {
|
if (other is ObjList) {
|
||||||
list += other.list
|
list.addAll(other.list)
|
||||||
return this
|
} else if (other.isInstanceOf(ObjIterable) && other !is ObjString && other !is ObjBuffer) {
|
||||||
|
val otherList = (other.invokeInstanceMethod(scope, "toList") as ObjList).list
|
||||||
|
list.addAll(otherList)
|
||||||
|
} else {
|
||||||
|
list.add(other)
|
||||||
}
|
}
|
||||||
if (other.isInstanceOf(ObjIterable)) {
|
|
||||||
val otherList = other.invokeInstanceMethod(scope, "toList") as ObjList
|
|
||||||
list += otherList.list
|
|
||||||
} else
|
|
||||||
list += other
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,26 +251,25 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
return ObjList(decoder.decodeAnyList(scope))
|
return ObjList(decoder.decodeAnyList(scope))
|
||||||
}
|
}
|
||||||
}.apply {
|
}.apply {
|
||||||
addConstDoc(
|
addPropertyDoc(
|
||||||
name = "size",
|
name = "size",
|
||||||
value = statement {
|
|
||||||
(thisObj as ObjList).list.size.toObj()
|
|
||||||
},
|
|
||||||
doc = "Number of elements in this list.",
|
doc = "Number of elements in this list.",
|
||||||
type = type("lyng.Int"),
|
type = type("lyng.Int"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
|
getter = {
|
||||||
|
val s = (this.thisObj as ObjList).list.size
|
||||||
|
s.toObj()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
addConstDoc(
|
addFnDoc(
|
||||||
name = "add",
|
name = "add",
|
||||||
value = statement {
|
|
||||||
val l = thisAs<ObjList>().list
|
|
||||||
for (a in args) l.add(a)
|
|
||||||
ObjVoid
|
|
||||||
},
|
|
||||||
doc = "Append one or more elements to the end of this list.",
|
doc = "Append one or more elements to the end of this list.",
|
||||||
type = type("lyng.Callable"),
|
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib"
|
||||||
)
|
) {
|
||||||
|
val l = thisAs<ObjList>().list
|
||||||
|
for (a in args) l.add(a)
|
||||||
|
ObjVoid
|
||||||
|
}
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "insertAt",
|
name = "insertAt",
|
||||||
doc = "Insert elements starting at the given index.",
|
doc = "Insert elements starting at the given index.",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -21,10 +21,7 @@ import kotlinx.serialization.json.JsonElement
|
|||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.Statement
|
import net.sergeych.lyng.Statement
|
||||||
import net.sergeych.lyng.miniast.ParamDoc
|
import net.sergeych.lyng.miniast.*
|
||||||
import net.sergeych.lyng.miniast.TypeGenericDoc
|
|
||||||
import net.sergeych.lyng.miniast.addFnDoc
|
|
||||||
import net.sergeych.lyng.miniast.type
|
|
||||||
import net.sergeych.lynon.LynonDecoder
|
import net.sergeych.lynon.LynonDecoder
|
||||||
import net.sergeych.lynon.LynonEncoder
|
import net.sergeych.lynon.LynonEncoder
|
||||||
import net.sergeych.lynon.LynonType
|
import net.sergeych.lynon.LynonType
|
||||||
@ -76,24 +73,27 @@ class ObjMapEntry(val key: Obj, val value: Obj) : Obj() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}.apply {
|
}.apply {
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "key",
|
name = "key",
|
||||||
doc = "Key component of this map entry.",
|
doc = "Key component of this map entry.",
|
||||||
returns = type("lyng.Any"),
|
type = type("lyng.Any"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) { thisAs<ObjMapEntry>().key }
|
getter = { thisAs<ObjMapEntry>().key }
|
||||||
addFnDoc(
|
)
|
||||||
|
addPropertyDoc(
|
||||||
name = "value",
|
name = "value",
|
||||||
doc = "Value component of this map entry.",
|
doc = "Value component of this map entry.",
|
||||||
returns = type("lyng.Any"),
|
type = type("lyng.Any"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) { thisAs<ObjMapEntry>().value }
|
getter = { thisAs<ObjMapEntry>().value }
|
||||||
addFnDoc(
|
)
|
||||||
|
addPropertyDoc(
|
||||||
name = "size",
|
name = "size",
|
||||||
doc = "Number of components in this entry (always 2).",
|
doc = "Number of components in this entry (always 2).",
|
||||||
returns = type("lyng.Int"),
|
type = type("lyng.Int"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) { 2.toObj() }
|
getter = { 2.toObj() }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,6 +106,18 @@ class ObjMapEntry(val key: Obj, val value: Obj) : Obj() {
|
|||||||
|
|
||||||
class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
||||||
|
|
||||||
|
override suspend fun equals(scope: Scope, other: Obj): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is ObjMap) return false
|
||||||
|
if (map.size != other.map.size) return false
|
||||||
|
for ((k, v) in map) {
|
||||||
|
val otherV = other.getAt(scope, k)
|
||||||
|
if (otherV === ObjNull && !other.contains(scope, k)) return false
|
||||||
|
if (!v.equals(scope, otherV)) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
override val objClass get() = type
|
override val objClass get() = type
|
||||||
|
|
||||||
override suspend fun getAt(scope: Scope, index: Obj): Obj =
|
override suspend fun getAt(scope: Scope, index: Obj): Obj =
|
||||||
@ -120,7 +132,13 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
if( other is ObjMap && other.map == map) return 0
|
if (other is ObjMap) {
|
||||||
|
if (map == other.map) return 0
|
||||||
|
if (map.size != other.map.size) return map.size.compareTo(other.map.size)
|
||||||
|
// for same size, if they are not equal, we don't have a stable order
|
||||||
|
// but let's try to be consistent
|
||||||
|
return map.toString().compareTo(other.map.toString())
|
||||||
|
}
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,14 +265,13 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
|||||||
lambda.execute(this)
|
lambda.execute(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "size",
|
name = "size",
|
||||||
doc = "Number of entries in the map.",
|
doc = "Number of entries in the map.",
|
||||||
returns = type("lyng.Int"),
|
type = type("lyng.Int"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = { (this.thisObj as ObjMap).map.size.toObj() }
|
||||||
thisAs<ObjMap>().map.size.toObj()
|
)
|
||||||
}
|
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "remove",
|
name = "remove",
|
||||||
doc = "Remove the entry by key and return the previous value or null if absent.",
|
doc = "Remove the entry by key and return the previous value or null if absent.",
|
||||||
@ -273,22 +290,20 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
|||||||
thisAs<ObjMap>().map.clear()
|
thisAs<ObjMap>().map.clear()
|
||||||
thisObj
|
thisObj
|
||||||
}
|
}
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "keys",
|
name = "keys",
|
||||||
doc = "List of keys in this map.",
|
doc = "List of keys in this map.",
|
||||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
|
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = { thisAs<ObjMap>().map.keys.toObj() }
|
||||||
thisAs<ObjMap>().map.keys.toObj()
|
)
|
||||||
}
|
addPropertyDoc(
|
||||||
addFnDoc(
|
|
||||||
name = "values",
|
name = "values",
|
||||||
doc = "List of values in this map.",
|
doc = "List of values in this map.",
|
||||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
|
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = { ObjList(thisAs<ObjMap>().map.values.toMutableList()) }
|
||||||
ObjList(thisAs<ObjMap>().map.values.toMutableList())
|
)
|
||||||
}
|
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "iterator",
|
name = "iterator",
|
||||||
doc = "Iterator over map entries as MapEntry objects.",
|
doc = "Iterator over map entries as MapEntry objects.",
|
||||||
@ -328,7 +343,14 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
|||||||
for (e in other.list) {
|
for (e in other.list) {
|
||||||
val entry = when (e) {
|
val entry = when (e) {
|
||||||
is ObjMapEntry -> e
|
is ObjMapEntry -> e
|
||||||
else -> scope.raiseIllegalArgument("map can only be merged with MapEntry elements; got $e")
|
else -> {
|
||||||
|
if (e.isInstanceOf(ObjArray)) {
|
||||||
|
if (e.invokeInstanceMethod(scope, "size").toInt() != 2)
|
||||||
|
scope.raiseIllegalArgument("Array element to merge into map must have 2 elements, got $e")
|
||||||
|
ObjMapEntry(e.getAt(scope, 0), e.getAt(scope, 1))
|
||||||
|
} else
|
||||||
|
scope.raiseIllegalArgument("map can only be merged with MapEntry elements; got $e")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
map[entry.key] = entry.value
|
map[entry.key] = entry.value
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -20,6 +20,7 @@ package net.sergeych.lyng.obj
|
|||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.miniast.TypeGenericDoc
|
import net.sergeych.lyng.miniast.TypeGenericDoc
|
||||||
import net.sergeych.lyng.miniast.addFnDoc
|
import net.sergeych.lyng.miniast.addFnDoc
|
||||||
|
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||||
import net.sergeych.lyng.miniast.type
|
import net.sergeych.lyng.miniast.type
|
||||||
|
|
||||||
class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Obj() {
|
class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Obj() {
|
||||||
@ -174,54 +175,48 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val type = ObjClass("Range", ObjIterable).apply {
|
val type = ObjClass("Range", ObjIterable).apply {
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "start",
|
name = "start",
|
||||||
doc = "Start bound of the range or null if open.",
|
doc = "Start bound of the range or null if open.",
|
||||||
returns = type("lyng.Any", nullable = true),
|
type = type("lyng.Any", nullable = true),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = { thisAs<ObjRange>().start ?: ObjNull }
|
||||||
thisAs<ObjRange>().start ?: ObjNull
|
)
|
||||||
}
|
addPropertyDoc(
|
||||||
addFnDoc(
|
|
||||||
name = "end",
|
name = "end",
|
||||||
doc = "End bound of the range or null if open.",
|
doc = "End bound of the range or null if open.",
|
||||||
returns = type("lyng.Any", nullable = true),
|
type = type("lyng.Any", nullable = true),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = { thisAs<ObjRange>().end ?: ObjNull }
|
||||||
thisAs<ObjRange>().end ?: ObjNull
|
)
|
||||||
}
|
addPropertyDoc(
|
||||||
addFnDoc(
|
|
||||||
name = "isOpen",
|
name = "isOpen",
|
||||||
doc = "Whether the range is open on either side (no start or no end).",
|
doc = "Whether the range is open on either side (no start or no end).",
|
||||||
returns = type("lyng.Bool"),
|
type = type("lyng.Bool"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = { thisAs<ObjRange>().let { it.start == null || it.end == null }.toObj() }
|
||||||
thisAs<ObjRange>().let { it.start == null || it.end == null }.toObj()
|
)
|
||||||
}
|
addPropertyDoc(
|
||||||
addFnDoc(
|
|
||||||
name = "isIntRange",
|
name = "isIntRange",
|
||||||
doc = "True if both bounds are Int values.",
|
doc = "True if both bounds are Int values.",
|
||||||
returns = type("lyng.Bool"),
|
type = type("lyng.Bool"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = { thisAs<ObjRange>().isIntRange.toObj() }
|
||||||
thisAs<ObjRange>().isIntRange.toObj()
|
)
|
||||||
}
|
addPropertyDoc(
|
||||||
addFnDoc(
|
|
||||||
name = "isCharRange",
|
name = "isCharRange",
|
||||||
doc = "True if both bounds are Char values.",
|
doc = "True if both bounds are Char values.",
|
||||||
returns = type("lyng.Bool"),
|
type = type("lyng.Bool"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = { thisAs<ObjRange>().isCharRange.toObj() }
|
||||||
thisAs<ObjRange>().isCharRange.toObj()
|
)
|
||||||
}
|
addPropertyDoc(
|
||||||
addFnDoc(
|
|
||||||
name = "isEndInclusive",
|
name = "isEndInclusive",
|
||||||
doc = "Whether the end bound is inclusive.",
|
doc = "Whether the end bound is inclusive.",
|
||||||
returns = type("lyng.Bool"),
|
type = type("lyng.Bool"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = { thisAs<ObjRange>().isEndInclusive.toObj() }
|
||||||
thisAs<ObjRange>().isEndInclusive.toObj()
|
)
|
||||||
}
|
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "iterator",
|
name = "iterator",
|
||||||
doc = "Iterator over elements in this range (optimized for Int ranges).",
|
doc = "Iterator over elements in this range (optimized for Int ranges).",
|
||||||
|
|||||||
@ -36,6 +36,8 @@ data class ObjRecord(
|
|||||||
val isClosed: Boolean = false,
|
val isClosed: Boolean = false,
|
||||||
val isOverride: Boolean = false,
|
val isOverride: Boolean = false,
|
||||||
var delegate: Obj? = null,
|
var delegate: Obj? = null,
|
||||||
|
/** The receiver object to resolve this member against (for instance fields/methods). */
|
||||||
|
var receiver: Obj? = 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) {
|
||||||
|
|||||||
@ -34,7 +34,15 @@ sealed interface ObjRef {
|
|||||||
*/
|
*/
|
||||||
suspend fun evalValue(scope: Scope): Obj {
|
suspend fun evalValue(scope: Scope): Obj {
|
||||||
val rec = get(scope)
|
val rec = get(scope)
|
||||||
if (rec.type == ObjRecord.Type.Delegated) return scope.resolve(rec, "unknown")
|
if (rec.type == ObjRecord.Type.Delegated) {
|
||||||
|
val receiver = rec.receiver ?: scope.thisObj
|
||||||
|
// Use resolve to handle delegated property logic
|
||||||
|
return scope.resolve(rec, "unknown")
|
||||||
|
}
|
||||||
|
// Template record: must map to instance storage
|
||||||
|
if (rec.receiver != null && rec.declaringClass != null) {
|
||||||
|
return rec.receiver!!.resolveRecord(scope, rec, "unknown", rec.declaringClass).value
|
||||||
|
}
|
||||||
return rec.value
|
return rec.value
|
||||||
}
|
}
|
||||||
suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||||
@ -383,7 +391,7 @@ class IncDecRef(
|
|||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val rec = target.get(scope)
|
val rec = target.get(scope)
|
||||||
if (!rec.isMutable) scope.raiseError("Cannot ${if (isIncrement) "increment" else "decrement"} immutable value")
|
if (!rec.isMutable) scope.raiseError("Cannot ${if (isIncrement) "increment" else "decrement"} immutable value")
|
||||||
val v = scope.resolve(rec, "unknown")
|
val v = target.evalValue(scope)
|
||||||
val one = ObjInt.One
|
val one = ObjInt.One
|
||||||
// We now treat numbers as immutable and always perform write-back via setAt.
|
// We now treat numbers as immutable and always perform write-back via setAt.
|
||||||
// This avoids issues where literals are shared and mutated in-place.
|
// This avoids issues where literals are shared and mutated in-place.
|
||||||
@ -947,65 +955,63 @@ class IndexRef(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||||
val fastRval = PerfFlags.RVAL_FASTPATH
|
val base = target.evalValue(scope)
|
||||||
val base = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
|
||||||
if (base == ObjNull && isOptional) {
|
if (base == ObjNull && isOptional) {
|
||||||
// no-op on null receiver for optional chaining assignment
|
// no-op on null receiver for optional chaining assignment
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val idx = if (fastRval) index.evalValue(scope) else index.get(scope).value
|
val idx = index.evalValue(scope)
|
||||||
if (fastRval) {
|
|
||||||
// Mirror read fast-path with direct write for ObjList + ObjInt index
|
// Mirror read fast-path with direct write for ObjList + ObjInt index
|
||||||
if (base is ObjList && idx is ObjInt) {
|
if (base is ObjList && idx is ObjInt) {
|
||||||
val i = idx.toInt()
|
val i = idx.toInt()
|
||||||
base.list[i] = newValue
|
base.list[i] = newValue
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
// Direct write fast path for ObjMap + ObjString
|
||||||
|
if (base is ObjMap && idx is ObjString) {
|
||||||
|
base.map[idx] = newValue
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (PerfFlags.RVAL_FASTPATH && PerfFlags.INDEX_PIC) {
|
||||||
|
// Polymorphic inline cache for index write
|
||||||
|
val (key, ver) = when (base) {
|
||||||
|
is ObjInstance -> base.objClass.classId to base.objClass.layoutVersion
|
||||||
|
is ObjClass -> base.classId to base.layoutVersion
|
||||||
|
else -> 0L to -1
|
||||||
}
|
}
|
||||||
// Direct write fast path for ObjMap + ObjString
|
if (key != 0L) {
|
||||||
if (base is ObjMap && idx is ObjString) {
|
wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) { s(base, scope, idx, newValue); return } }
|
||||||
base.map[idx] = newValue
|
wSetter2?.let { s -> if (key == wKey2 && ver == wVer2) {
|
||||||
return
|
val tk = wKey2; val tv = wVer2; val ts = wSetter2
|
||||||
}
|
|
||||||
if (PerfFlags.INDEX_PIC) {
|
|
||||||
// Polymorphic inline cache for index write
|
|
||||||
val (key, ver) = when (base) {
|
|
||||||
is ObjInstance -> base.objClass.classId to base.objClass.layoutVersion
|
|
||||||
is ObjClass -> base.classId to base.layoutVersion
|
|
||||||
else -> 0L to -1
|
|
||||||
}
|
|
||||||
if (key != 0L) {
|
|
||||||
wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) { s(base, scope, idx, newValue); return } }
|
|
||||||
wSetter2?.let { s -> if (key == wKey2 && ver == wVer2) {
|
|
||||||
val tk = wKey2; val tv = wVer2; val ts = wSetter2
|
|
||||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
|
||||||
wKey1 = tk; wVer1 = tv; wSetter1 = ts
|
|
||||||
s(base, scope, idx, newValue); return
|
|
||||||
} }
|
|
||||||
if (PerfFlags.INDEX_PIC_SIZE_4) wSetter3?.let { s -> if (key == wKey3 && ver == wVer3) {
|
|
||||||
val tk = wKey3; val tv = wVer3; val ts = wSetter3
|
|
||||||
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
|
||||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
|
||||||
wKey1 = tk; wVer1 = tv; wSetter1 = ts
|
|
||||||
s(base, scope, idx, newValue); return
|
|
||||||
} }
|
|
||||||
if (PerfFlags.INDEX_PIC_SIZE_4) wSetter4?.let { s -> if (key == wKey4 && ver == wVer4) {
|
|
||||||
val tk = wKey4; val tv = wVer4; val ts = wSetter4
|
|
||||||
wKey4 = wKey3; wVer4 = wVer3; wSetter4 = wSetter3
|
|
||||||
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
|
||||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
|
||||||
wKey1 = tk; wVer1 = tv; wSetter1 = ts
|
|
||||||
s(base, scope, idx, newValue); return
|
|
||||||
} }
|
|
||||||
// Miss: perform write and install generic handler
|
|
||||||
base.putAt(scope, idx, newValue)
|
|
||||||
if (PerfFlags.INDEX_PIC_SIZE_4) {
|
|
||||||
wKey4 = wKey3; wVer4 = wVer3; wSetter4 = wSetter3
|
|
||||||
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
|
||||||
}
|
|
||||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||||
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, ix, v -> obj.putAt(sc, ix, v) }
|
wKey1 = tk; wVer1 = tv; wSetter1 = ts
|
||||||
return
|
s(base, scope, idx, newValue); return
|
||||||
|
} }
|
||||||
|
if (PerfFlags.INDEX_PIC_SIZE_4) wSetter3?.let { s -> if (key == wKey3 && ver == wVer3) {
|
||||||
|
val tk = wKey3; val tv = wVer3; val ts = wSetter3
|
||||||
|
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
||||||
|
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||||
|
wKey1 = tk; wVer1 = tv; wSetter1 = ts
|
||||||
|
s(base, scope, idx, newValue); return
|
||||||
|
} }
|
||||||
|
if (PerfFlags.INDEX_PIC_SIZE_4) wSetter4?.let { s -> if (key == wKey4 && ver == wVer4) {
|
||||||
|
val tk = wKey4; val tv = wVer4; val ts = wSetter4
|
||||||
|
wKey4 = wKey3; wVer4 = wVer3; wSetter4 = wSetter3
|
||||||
|
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
||||||
|
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||||
|
wKey1 = tk; wVer1 = tv; wSetter1 = ts
|
||||||
|
s(base, scope, idx, newValue); return
|
||||||
|
} }
|
||||||
|
// Miss: perform write and install generic handler
|
||||||
|
base.putAt(scope, idx, newValue)
|
||||||
|
if (PerfFlags.INDEX_PIC_SIZE_4) {
|
||||||
|
wKey4 = wKey3; wVer4 = wVer3; wSetter4 = wSetter3
|
||||||
|
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
||||||
}
|
}
|
||||||
|
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||||
|
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, ix, v -> obj.putAt(sc, ix, v) }
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
base.putAt(scope, idx, newValue)
|
base.putAt(scope, idx, newValue)
|
||||||
@ -1266,20 +1272,6 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
|||||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.localVarPicMiss++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.localVarPicMiss++
|
||||||
// 2) Fallback to current-scope object or field on `this`
|
// 2) Fallback to current-scope object or field on `this`
|
||||||
scope[name]?.let { return it }
|
scope[name]?.let { return it }
|
||||||
// 2a) Try nearest ClosureScope's closure ancestry explicitly
|
|
||||||
run {
|
|
||||||
var s: Scope? = scope
|
|
||||||
val visited = HashSet<Long>(4)
|
|
||||||
while (s != null) {
|
|
||||||
if (!visited.add(s.frameId)) break
|
|
||||||
if (s is ClosureScope) {
|
|
||||||
s.closureScope.chainLookupWithMembers(name)?.let { return it }
|
|
||||||
}
|
|
||||||
s = s.parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 2b) Try raw ancestry local/binding lookup (cycle-safe), including slots in parents
|
|
||||||
scope.chainLookupIgnoreClosure(name)?.let { return it }
|
|
||||||
try {
|
try {
|
||||||
return scope.thisObj.readField(scope, name)
|
return scope.thisObj.readField(scope, name)
|
||||||
} catch (e: ExecutionError) {
|
} catch (e: ExecutionError) {
|
||||||
@ -1305,18 +1297,6 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
|||||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.localVarPicMiss++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.localVarPicMiss++
|
||||||
// 2) Fallback name in scope or field on `this`
|
// 2) Fallback name in scope or field on `this`
|
||||||
scope[name]?.let { return it }
|
scope[name]?.let { return it }
|
||||||
run {
|
|
||||||
var s: Scope? = scope
|
|
||||||
val visited = HashSet<Long>(4)
|
|
||||||
while (s != null) {
|
|
||||||
if (!visited.add(s.frameId)) break
|
|
||||||
if (s is ClosureScope) {
|
|
||||||
s.closureScope.chainLookupWithMembers(name)?.let { return it }
|
|
||||||
}
|
|
||||||
s = s.parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scope.chainLookupIgnoreClosure(name)?.let { return it }
|
|
||||||
try {
|
try {
|
||||||
return scope.thisObj.readField(scope, name)
|
return scope.thisObj.readField(scope, name)
|
||||||
} catch (e: ExecutionError) {
|
} catch (e: ExecutionError) {
|
||||||
@ -1327,56 +1307,11 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
|||||||
|
|
||||||
override suspend fun evalValue(scope: Scope): Obj {
|
override suspend fun evalValue(scope: Scope): Obj {
|
||||||
scope.pos = atPos
|
scope.pos = atPos
|
||||||
if (!PerfFlags.LOCAL_SLOT_PIC) {
|
scope.getSlotIndexOf(name)?.let { return scope.resolve(scope.getSlotRecord(it), name) }
|
||||||
scope.getSlotIndexOf(name)?.let { return scope.resolve(scope.getSlotRecord(it), name) }
|
// fallback to current-scope object or field on `this`
|
||||||
// fallback to current-scope object or field on `this`
|
scope[name]?.let { return scope.resolve(it, name) }
|
||||||
scope[name]?.let { return scope.resolve(it, name) }
|
|
||||||
run {
|
|
||||||
var s: Scope? = scope
|
|
||||||
val visited = HashSet<Long>(4)
|
|
||||||
while (s != null) {
|
|
||||||
if (!visited.add(s.frameId)) break
|
|
||||||
if (s is ClosureScope) {
|
|
||||||
s.closureScope.chainLookupWithMembers(name)?.let { return s.resolve(it, name) }
|
|
||||||
}
|
|
||||||
s = s.parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scope.chainLookupIgnoreClosure(name)?.let { return scope.resolve(it, name) }
|
|
||||||
return try {
|
|
||||||
scope.thisObj.readField(scope, name).value
|
|
||||||
} catch (e: ExecutionError) {
|
|
||||||
if ((e.message ?: "").contains("no such field: $name")) scope.raiseSymbolNotFound(name)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val hit = (cachedFrameId == scope.frameId && cachedSlot >= 0 && cachedSlot < scope.slotCount())
|
|
||||||
val slot = if (hit) cachedSlot else resolveSlot(scope)
|
|
||||||
if (slot >= 0) {
|
|
||||||
val rec = scope.getSlotRecord(slot)
|
|
||||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) {
|
|
||||||
return scope.resolve(rec, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Fallback name in scope or field on `this`
|
|
||||||
scope[name]?.let {
|
|
||||||
return scope.resolve(it, name)
|
|
||||||
}
|
|
||||||
run {
|
|
||||||
var s: Scope? = scope
|
|
||||||
val visited = HashSet<Long>(4)
|
|
||||||
while (s != null) {
|
|
||||||
if (!visited.add(s.frameId)) break
|
|
||||||
if (s is ClosureScope) {
|
|
||||||
s.closureScope.chainLookupWithMembers(name)?.let { return s.resolve(it, name) }
|
|
||||||
}
|
|
||||||
s = s.parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scope.chainLookupIgnoreClosure(name)?.let { return scope.resolve(it, name) }
|
|
||||||
return try {
|
return try {
|
||||||
val res = scope.thisObj.readField(scope, name).value
|
scope.thisObj.readField(scope, name).value
|
||||||
res
|
|
||||||
} catch (e: ExecutionError) {
|
} catch (e: ExecutionError) {
|
||||||
if ((e.message ?: "").contains("no such field: $name")) scope.raiseSymbolNotFound(name)
|
if ((e.message ?: "").contains("no such field: $name")) scope.raiseSymbolNotFound(name)
|
||||||
throw e
|
throw e
|
||||||
@ -1395,24 +1330,6 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
|||||||
scope.assign(stored, name, newValue)
|
scope.assign(stored, name, newValue)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
run {
|
|
||||||
var s: Scope? = scope
|
|
||||||
val visited = HashSet<Long>(4)
|
|
||||||
while (s != null) {
|
|
||||||
if (!visited.add(s.frameId)) break
|
|
||||||
if (s is ClosureScope) {
|
|
||||||
s.closureScope.chainLookupWithMembers(name)?.let { stored ->
|
|
||||||
s.assign(stored, name, newValue)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s = s.parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scope.chainLookupIgnoreClosure(name)?.let { stored ->
|
|
||||||
scope.assign(stored, name, newValue)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Fallback: write to field on `this`
|
// Fallback: write to field on `this`
|
||||||
scope.thisObj.writeField(scope, name, newValue)
|
scope.thisObj.writeField(scope, name, newValue)
|
||||||
return
|
return
|
||||||
@ -1429,24 +1346,6 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
|||||||
scope.assign(stored, name, newValue)
|
scope.assign(stored, name, newValue)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
run {
|
|
||||||
var s: Scope? = scope
|
|
||||||
val visited = HashSet<Long>(4)
|
|
||||||
while (s != null) {
|
|
||||||
if (!visited.add(s.frameId)) break
|
|
||||||
if (s is ClosureScope) {
|
|
||||||
s.closureScope.chainLookupWithMembers(name)?.let { stored ->
|
|
||||||
s.assign(stored, name, newValue)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s = s.parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scope.chainLookupIgnoreClosure(name)?.let { stored ->
|
|
||||||
scope.assign(stored, name, newValue)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
scope.thisObj.writeField(scope, name, newValue)
|
scope.thisObj.writeField(scope, name, newValue)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -20,10 +20,7 @@ package net.sergeych.lyng.obj
|
|||||||
import net.sergeych.lyng.PerfFlags
|
import net.sergeych.lyng.PerfFlags
|
||||||
import net.sergeych.lyng.RegexCache
|
import net.sergeych.lyng.RegexCache
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.miniast.ParamDoc
|
import net.sergeych.lyng.miniast.*
|
||||||
import net.sergeych.lyng.miniast.TypeGenericDoc
|
|
||||||
import net.sergeych.lyng.miniast.addFnDoc
|
|
||||||
import net.sergeych.lyng.miniast.type
|
|
||||||
|
|
||||||
class ObjRegex(val regex: Regex) : Obj() {
|
class ObjRegex(val regex: Regex) : Obj() {
|
||||||
override val objClass get() = type
|
override val objClass get() = type
|
||||||
@ -123,30 +120,27 @@ class ObjRegexMatch(val match: MatchResult) : Obj() {
|
|||||||
scope.raiseError("RegexMatch can't be constructed directly")
|
scope.raiseError("RegexMatch can't be constructed directly")
|
||||||
}
|
}
|
||||||
}.apply {
|
}.apply {
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "groups",
|
name = "groups",
|
||||||
doc = "List of captured groups with index 0 as the whole match.",
|
doc = "List of captured groups with index 0 as the whole match.",
|
||||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
|
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = { thisAs<ObjRegexMatch>().objGroups }
|
||||||
thisAs<ObjRegexMatch>().objGroups
|
)
|
||||||
}
|
addPropertyDoc(
|
||||||
addFnDoc(
|
|
||||||
name = "value",
|
name = "value",
|
||||||
doc = "The matched substring.",
|
doc = "The matched substring.",
|
||||||
returns = type("lyng.String"),
|
type = type("lyng.String"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = { thisAs<ObjRegexMatch>().objValue }
|
||||||
thisAs<ObjRegexMatch>().objValue
|
)
|
||||||
}
|
addPropertyDoc(
|
||||||
addFnDoc(
|
|
||||||
name = "range",
|
name = "range",
|
||||||
doc = "Range of the match in the input (end-exclusive).",
|
doc = "Range of the match in the input (end-exclusive).",
|
||||||
returns = type("lyng.Range"),
|
type = type("lyng.Range"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = { thisAs<ObjRegexMatch>().objRange }
|
||||||
thisAs<ObjRegexMatch>().objRange
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -18,10 +18,7 @@
|
|||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.miniast.ParamDoc
|
import net.sergeych.lyng.miniast.*
|
||||||
import net.sergeych.lyng.miniast.TypeGenericDoc
|
|
||||||
import net.sergeych.lyng.miniast.addFnDoc
|
|
||||||
import net.sergeych.lyng.miniast.type
|
|
||||||
|
|
||||||
class RingBuffer<T>(val maxSize: Int) : Iterable<T> {
|
class RingBuffer<T>(val maxSize: Int) : Iterable<T> {
|
||||||
private val data = arrayOfNulls<Any>(maxSize)
|
private val data = arrayOfNulls<Any>(maxSize)
|
||||||
@ -94,18 +91,20 @@ class ObjRingBuffer(val capacity: Int) : Obj() {
|
|||||||
return ObjRingBuffer(scope.requireOnlyArg<ObjInt>().toInt())
|
return ObjRingBuffer(scope.requireOnlyArg<ObjInt>().toInt())
|
||||||
}
|
}
|
||||||
}.apply {
|
}.apply {
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "capacity",
|
name = "capacity",
|
||||||
doc = "Maximum number of elements the buffer can hold.",
|
doc = "Maximum number of elements the buffer can hold.",
|
||||||
returns = type("lyng.Int"),
|
type = type("lyng.Int"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) { thisAs<ObjRingBuffer>().capacity.toObj() }
|
getter = { thisAs<ObjRingBuffer>().capacity.toObj() }
|
||||||
addFnDoc(
|
)
|
||||||
|
addPropertyDoc(
|
||||||
name = "size",
|
name = "size",
|
||||||
doc = "Current number of elements in the buffer.",
|
doc = "Current number of elements in the buffer.",
|
||||||
returns = type("lyng.Int"),
|
type = type("lyng.Int"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) { thisAs<ObjRingBuffer>().buffer.size.toObj() }
|
getter = { thisAs<ObjRingBuffer>().buffer.size.toObj() }
|
||||||
|
)
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "iterator",
|
name = "iterator",
|
||||||
doc = "Iterator over elements in insertion order (oldest to newest).",
|
doc = "Iterator over elements in insertion order (oldest to newest).",
|
||||||
@ -122,12 +121,16 @@ class ObjRingBuffer(val capacity: Int) : Obj() {
|
|||||||
returns = type("lyng.Void"),
|
returns = type("lyng.Void"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib"
|
||||||
) { thisAs<ObjRingBuffer>().apply { buffer.add(requireOnlyArg<Obj>()) } }
|
) { thisAs<ObjRingBuffer>().apply { buffer.add(requireOnlyArg<Obj>()) } }
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "first",
|
name = "first",
|
||||||
doc = "Return the oldest element in the buffer.",
|
doc = "Return the oldest element in the buffer.",
|
||||||
returns = type("lyng.Any"),
|
type = type("lyng.Any"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) { thisAs<ObjRingBuffer>().buffer.first() }
|
getter = {
|
||||||
|
val buffer = (this.thisObj as ObjRingBuffer).buffer
|
||||||
|
if (buffer.size == 0) ObjNull else buffer.first()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -28,6 +28,18 @@ import net.sergeych.lynon.LynonType
|
|||||||
|
|
||||||
class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
||||||
|
|
||||||
|
override suspend fun equals(scope: Scope, other: Obj): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is ObjSet) return false
|
||||||
|
if (set.size != other.set.size) return false
|
||||||
|
// Sets are equal if all my elements are in other and vice versa
|
||||||
|
// contains() in ObjSet uses equals(scope, ...), so we need to be careful
|
||||||
|
for (e in set) {
|
||||||
|
if (!other.contains(scope, e)) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
override val objClass get() = type
|
override val objClass get() = type
|
||||||
|
|
||||||
override suspend fun contains(scope: Scope, other: Obj): Boolean {
|
override suspend fun contains(scope: Scope, other: Obj): Boolean {
|
||||||
@ -113,11 +125,12 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
return if (other !is ObjSet) -1
|
if (other is ObjSet) {
|
||||||
else {
|
if (set == other.set) return 0
|
||||||
if (set == other.set) 0
|
if (set.size != other.set.size) return set.size.compareTo(other.set.size)
|
||||||
else -1
|
return set.toString().compareTo(other.set.toString())
|
||||||
}
|
}
|
||||||
|
return -2
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
@ -126,10 +139,7 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
|||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other == null || this::class != other::class) return false
|
if (other !is ObjSet) return false
|
||||||
|
|
||||||
other as ObjSet
|
|
||||||
|
|
||||||
return set == other.set
|
return set == other.set
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -25,7 +25,6 @@ import net.sergeych.lyng.PerfFlags
|
|||||||
import net.sergeych.lyng.RegexCache
|
import net.sergeych.lyng.RegexCache
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.miniast.*
|
import net.sergeych.lyng.miniast.*
|
||||||
import net.sergeych.lyng.statement
|
|
||||||
import net.sergeych.lynon.LynonDecoder
|
import net.sergeych.lynon.LynonDecoder
|
||||||
import net.sergeych.lynon.LynonEncoder
|
import net.sergeych.lynon.LynonEncoder
|
||||||
import net.sergeych.lynon.LynonType
|
import net.sergeych.lynon.LynonType
|
||||||
@ -124,10 +123,16 @@ data class ObjString(val value: String) : Obj() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val type = object : ObjClass("String") {
|
val type = object : ObjClass("String", ObjCollection) {
|
||||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
|
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
|
||||||
ObjString(decoder.unpackBinaryData().decodeToString())
|
ObjString(decoder.unpackBinaryData().decodeToString())
|
||||||
}.apply {
|
}.apply {
|
||||||
|
addFnDoc(
|
||||||
|
name = "iterator",
|
||||||
|
doc = "Iterator over characters of this string.",
|
||||||
|
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Char"))),
|
||||||
|
moduleName = "lyng.stdlib"
|
||||||
|
) { ObjKotlinIterator(thisAs<ObjString>().value.iterator()) }
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "toInt",
|
name = "toInt",
|
||||||
doc = "Parse this string as an integer or throw if it is not a valid integer.",
|
doc = "Parse this string as an integer or throw if it is not a valid integer.",
|
||||||
@ -157,12 +162,12 @@ data class ObjString(val value: String) : Obj() {
|
|||||||
) {
|
) {
|
||||||
ObjBool(thisAs<ObjString>().value.endsWith(requiredArg<ObjString>(0).value))
|
ObjBool(thisAs<ObjString>().value.endsWith(requiredArg<ObjString>(0).value))
|
||||||
}
|
}
|
||||||
addConstDoc(
|
addPropertyDoc(
|
||||||
name = "length",
|
name = "length",
|
||||||
value = statement { ObjInt.of(thisAs<ObjString>().value.length.toLong()) },
|
|
||||||
doc = "Number of UTF-16 code units in this string.",
|
doc = "Number of UTF-16 code units in this string.",
|
||||||
type = type("lyng.Int"),
|
type = type("lyng.Int"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
|
getter = { ObjInt.of((this.thisObj as ObjString).value.length.toLong()) }
|
||||||
)
|
)
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "takeLast",
|
name = "takeLast",
|
||||||
@ -240,16 +245,17 @@ data class ObjString(val value: String) : Obj() {
|
|||||||
) {
|
) {
|
||||||
thisAs<ObjString>().value.uppercase().let(::ObjString)
|
thisAs<ObjString>().value.uppercase().let(::ObjString)
|
||||||
}
|
}
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "characters",
|
name = "characters",
|
||||||
doc = "List of characters of this string.",
|
doc = "List of characters of this string.",
|
||||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Char"))),
|
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Char"))),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) {
|
getter = {
|
||||||
ObjList(
|
ObjList(
|
||||||
thisAs<ObjString>().value.map { ObjChar(it) }.toMutableList()
|
(this.thisObj as ObjString).value.map { ObjChar(it) }.toMutableList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "last",
|
name = "last",
|
||||||
doc = "The last character of this string or throw if the string is empty.",
|
doc = "The last character of this string or throw if the string is empty.",
|
||||||
@ -264,12 +270,13 @@ data class ObjString(val value: String) : Obj() {
|
|||||||
returns = type("lyng.Buffer"),
|
returns = type("lyng.Buffer"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib"
|
||||||
) { ObjBuffer(thisAs<ObjString>().value.encodeToByteArray().asUByteArray()) }
|
) { ObjBuffer(thisAs<ObjString>().value.encodeToByteArray().asUByteArray()) }
|
||||||
addFnDoc(
|
addPropertyDoc(
|
||||||
name = "size",
|
name = "size",
|
||||||
doc = "Alias for length: the number of characters (code units) in this string.",
|
doc = "Alias for length: the number of characters (code units) in this string.",
|
||||||
returns = type("lyng.Int"),
|
type = type("lyng.Int"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib",
|
||||||
) { ObjInt.of(thisAs<ObjString>().value.length.toLong()) }
|
getter = { ObjInt.of((this.thisObj as ObjString).value.length.toLong()) }
|
||||||
|
)
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "toReal",
|
name = "toReal",
|
||||||
doc = "Parse this string as a real number (floating point).",
|
doc = "Parse this string as a real number (floating point).",
|
||||||
|
|||||||
@ -1994,12 +1994,12 @@ class ScriptTest {
|
|||||||
"""
|
"""
|
||||||
class Foo {
|
class Foo {
|
||||||
// this means last is lambda:
|
// this means last is lambda:
|
||||||
fun f(e=1, f) {
|
fun test_f(e=1, f_param) {
|
||||||
"e="+e+"f="+f()
|
"e="+e+"f="+f_param()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val f = Foo()
|
val f_obj = Foo()
|
||||||
assertEquals("e=1f=xx", f.f { "xx" })
|
assertEquals("e=1f=xx", f_obj.test_f { "xx" })
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2011,13 +2011,13 @@ class ScriptTest {
|
|||||||
"""
|
"""
|
||||||
class Foo {
|
class Foo {
|
||||||
// this means last is lambda:
|
// this means last is lambda:
|
||||||
fun f(e..., f) {
|
fun test_f_ellipsis(e..., f_param) {
|
||||||
"e="+e+"f="+f()
|
"e="+e+"f="+f_param()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val f = Foo()
|
val f_obj = Foo()
|
||||||
assertEquals("e=[]f=xx", f.f { "xx" })
|
assertEquals("e=[]f=xx", f_obj.test_f_ellipsis { "xx" })
|
||||||
assertEquals("e=[1,2]f=xx", f.f(1,2) { "xx" })
|
assertEquals("e=[1,2]f=xx", f_obj.test_f_ellipsis(1,2) { "xx" })
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -4645,7 +4645,8 @@ class ScriptTest {
|
|||||||
null
|
null
|
||||||
} catch { it }
|
} catch { it }
|
||||||
assert(caught != null)
|
assert(caught != null)
|
||||||
assert(caught.message.contains("Expected DerivedEx, got MyEx"))
|
assertEquals("Expected DerivedEx, got MyEx", caught.message)
|
||||||
|
assert(caught.message == "Expected DerivedEx, got MyEx")
|
||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4703,7 +4704,7 @@ class ScriptTest {
|
|||||||
// 61755f07-630c-4181-8d50-1b044d96e1f4
|
// 61755f07-630c-4181-8d50-1b044d96e1f4
|
||||||
class T {
|
class T {
|
||||||
static var f1 = null
|
static var f1 = null
|
||||||
static fun t(name=null) {
|
static fun testCapture(name=null) {
|
||||||
run {
|
run {
|
||||||
// I expect it will catch the 'name' from
|
// I expect it will catch the 'name' from
|
||||||
// param?
|
// param?
|
||||||
@ -4714,11 +4715,86 @@ class ScriptTest {
|
|||||||
assert(T.f1 == null)
|
assert(T.f1 == null)
|
||||||
println("-- "+T.f1::class)
|
println("-- "+T.f1::class)
|
||||||
println("-- "+T.f1)
|
println("-- "+T.f1)
|
||||||
T.t("foo")
|
T.testCapture("foo")
|
||||||
println("2- "+T.f1::class)
|
println("2- "+T.f1::class)
|
||||||
println("2- "+T.f1)
|
println("2- "+T.f1)
|
||||||
assert(T.f1 == "foo")
|
assert(T.f1 == "foo")
|
||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLazyLocals() = runTest() {
|
||||||
|
eval("""
|
||||||
|
class T {
|
||||||
|
val x by lazy {
|
||||||
|
val c = "c"
|
||||||
|
c + "!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val t = T()
|
||||||
|
assertEquals("c!", t.x)
|
||||||
|
assertEquals("c!", t.x)
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun testGetterLocals() = runTest() {
|
||||||
|
eval("""
|
||||||
|
class T {
|
||||||
|
val x get() {
|
||||||
|
val c = "c"
|
||||||
|
c + "!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val t = T()
|
||||||
|
assertEquals("c!", t.x)
|
||||||
|
assertEquals("c!", t.x)
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMethodLocals() = runTest() {
|
||||||
|
eval("""
|
||||||
|
class T {
|
||||||
|
fun x() {
|
||||||
|
val c = "c"
|
||||||
|
c + "!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val t = T()
|
||||||
|
assertEquals("c!", t.x())
|
||||||
|
assertEquals("c!", t.x())
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testContrcuctorMagicIdBug() = runTest() {
|
||||||
|
eval("""
|
||||||
|
interface SomeI {
|
||||||
|
abstract fun x()
|
||||||
|
}
|
||||||
|
class T(id): SomeI {
|
||||||
|
override fun x() {
|
||||||
|
val c = id
|
||||||
|
c + "!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val t = T("c")
|
||||||
|
assertEquals("c!", t.x())
|
||||||
|
assertEquals("c!", t.x())
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLambdaLocals() = runTest() {
|
||||||
|
eval("""
|
||||||
|
class T {
|
||||||
|
val l = { x ->
|
||||||
|
val c = x + ":"
|
||||||
|
c + x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals("r:r", T().l("r"))
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@ -211,7 +211,7 @@ 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() }
|
toList.apply { shuffle() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -267,7 +267,7 @@ class StackTraceEntry(
|
|||||||
fun Exception.printStackTrace() {
|
fun Exception.printStackTrace() {
|
||||||
println(this)
|
println(this)
|
||||||
var lastEntry = null
|
var lastEntry = null
|
||||||
for( entry in stackTrace() ) {
|
for( entry in stackTrace ) {
|
||||||
if( lastEntry == null || lastEntry !is StackTraceEntry || lastEntry.line != entry.line )
|
if( lastEntry == null || lastEntry !is StackTraceEntry || lastEntry.line != entry.line )
|
||||||
println("\tat "+entry.toString())
|
println("\tat "+entry.toString())
|
||||||
lastEntry = entry
|
lastEntry = entry
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user