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
|
||||
/compile_jvm_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
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -17,6 +17,8 @@
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
import net.sergeych.lyng.obj.ObjClass
|
||||
import net.sergeych.lyng.obj.ObjRecord
|
||||
|
||||
/**
|
||||
@ -38,138 +40,34 @@ class ClosureScope(val callScope: Scope, val closureScope: Scope) :
|
||||
}
|
||||
|
||||
override fun get(name: String): ObjRecord? {
|
||||
// Fast-path built-ins
|
||||
if (name == "this") return thisObj.asReadonly
|
||||
|
||||
// Priority:
|
||||
// 1) Locals and arguments declared in this lambda frame (including values defined before suspension)
|
||||
// 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. Current frame locals (parameters, local variables)
|
||||
tryGetLocalRecord(this, name, currentClassCtx)?.let { return it }
|
||||
|
||||
// 1) Locals/arguments in this closure frame
|
||||
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.
|
||||
// 2. Lexical environment (captured locals from entire ancestry)
|
||||
closureScope.chainLookupIgnoreClosure(name, followClosure = true)?.let { return it }
|
||||
|
||||
// 2) Members on the captured receiver instance
|
||||
(closureScope.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
|
||||
// 3. Lexical this members (captured receiver)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
findExtension(closureScope.thisObj.objClass, name)?.let { return it }
|
||||
closureScope.thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec ->
|
||||
// Finally, root object fallback
|
||||
Obj.rootObjectType.members[name]?.let { rec ->
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) {
|
||||
// Return only non-field/non-property members (methods) from class-level records.
|
||||
// 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
|
||||
return rec.copy(receiver = receiver)
|
||||
}
|
||||
}
|
||||
|
||||
// 3) Closure scope chain (locals/parents + members), ignore ClosureScope overrides to prevent recursion
|
||||
closureScope.chainLookupWithMembers(name, currentClassCtx, followClosure = true)?.let { return it }
|
||||
|
||||
// 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)
|
||||
// 4. Call environment (caller locals, caller this, and global fallback)
|
||||
return callScope.get(name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -667,19 +667,25 @@ class Compiler(
|
||||
"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) }
|
||||
val body = parseBlock(skipLeadingBrace = true)
|
||||
val body = inCodeContext(CodeContext.Function("<lambda>")) {
|
||||
withLocalNames(paramNames.toSet()) {
|
||||
parseBlock(skipLeadingBrace = true)
|
||||
}
|
||||
}
|
||||
label?.let { cc.labels.remove(it) }
|
||||
|
||||
return ValueFnRef { closureScope ->
|
||||
statement {
|
||||
statement(body.pos) { scope ->
|
||||
// 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
|
||||
// will create child scopes as usual, so re-declarations inside loops work.
|
||||
if (argsDeclaration == null) {
|
||||
// no args: automatic var 'it'
|
||||
val l = args.list
|
||||
val l = scope.args.list
|
||||
val itValue: Obj = when (l.size) {
|
||||
// no args: it == void
|
||||
0 -> ObjVoid
|
||||
@ -2192,20 +2198,6 @@ class Compiler(
|
||||
for (s in initScope)
|
||||
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)
|
||||
// Debug summary: list registered instance methods and class-scope functions for this class
|
||||
newClass
|
||||
@ -2292,7 +2284,7 @@ class Compiler(
|
||||
} else if (sourceObj.isInstanceOf(ObjIterable)) {
|
||||
loopIterable(forContext, sourceObj, loopSO, body, elseStatement, label, canBreak)
|
||||
} else {
|
||||
val size = runCatching { sourceObj.invokeInstanceMethod(forContext, "size").toInt() }
|
||||
val size = runCatching { sourceObj.readField(forContext, "size").value.toInt() }
|
||||
.getOrElse {
|
||||
throw ScriptError(
|
||||
tOp.pos,
|
||||
@ -3079,31 +3071,88 @@ class Compiler(
|
||||
val mark = cc.savePos()
|
||||
cc.restorePos(markBeforeEq)
|
||||
cc.skipWsTokens()
|
||||
|
||||
// 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.isId("get") || next.isId("set") || next.isId("private") || next.isId("protected")) {
|
||||
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
|
||||
cc.restorePos(markBeforeEq)
|
||||
cc.skipWsTokens()
|
||||
// Do not consume eqToken if it's an accessor keyword
|
||||
} else {
|
||||
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
|
||||
if (!isStatic) declareLocalName(name)
|
||||
|
||||
val isDelegate = if (isAbstract || actualExtern) {
|
||||
if (!isProperty && (eqToken.type == Token.Type.ASSIGN || eqToken.type == Token.Type.BY))
|
||||
throw ScriptError(eqToken.pos, "${if (isAbstract) "abstract" else "extern"} variable $name cannot have an initializer or delegate")
|
||||
if (!isProperty && (effectiveEqToken?.type == Token.Type.ASSIGN || effectiveEqToken?.type == Token.Type.BY))
|
||||
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
|
||||
cc.restorePos(markBeforeEq)
|
||||
cc.skipWsTokens()
|
||||
setNull = true
|
||||
false
|
||||
} else if (!isProperty && eqToken.type == Token.Type.BY) {
|
||||
} else if (!isProperty && effectiveEqToken?.type == Token.Type.BY) {
|
||||
true
|
||||
} else {
|
||||
if (!isProperty && eqToken.type != Token.Type.ASSIGN) {
|
||||
if (!isProperty && effectiveEqToken?.type != Token.Type.ASSIGN) {
|
||||
if (!isMutable && (declaringClassNameCaptured == null) && (extTypeName == null))
|
||||
throw ScriptError(start, "val must be initialized")
|
||||
else if (!isMutable && declaringClassNameCaptured != null && extTypeName == null) {
|
||||
@ -3123,12 +3172,12 @@ class Compiler(
|
||||
|
||||
val initialExpression = if (setNull || isProperty) null
|
||||
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
|
||||
run {
|
||||
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(
|
||||
range = declRange,
|
||||
name = name,
|
||||
@ -3193,17 +3242,24 @@ class Compiler(
|
||||
if (t.isId("get")) {
|
||||
val getStart = cc.currentPos()
|
||||
cc.next() // consume 'get'
|
||||
cc.requireToken(Token.Type.LPAREN)
|
||||
if (cc.peekNextNonWhitespace().type == Token.Type.LPAREN) {
|
||||
cc.next() // consume (
|
||||
cc.requireToken(Token.Type.RPAREN)
|
||||
}
|
||||
miniSink?.onEnterFunction(null)
|
||||
getter = if (cc.peekNextNonWhitespace().type == Token.Type.LBRACE) {
|
||||
cc.skipWsTokens()
|
||||
inCodeContext(CodeContext.Function("<getter>")) {
|
||||
parseBlock()
|
||||
}
|
||||
} else if (cc.peekNextNonWhitespace().type == Token.Type.ASSIGN) {
|
||||
cc.skipWsTokens()
|
||||
cc.next() // consume '='
|
||||
val expr = parseExpression() ?: throw ScriptError(cc.current().pos, "Expected getter expression")
|
||||
inCodeContext(CodeContext.Function("<getter>")) {
|
||||
val expr = parseExpression()
|
||||
?: throw ScriptError(cc.current().pos, "Expected getter expression")
|
||||
expr
|
||||
}
|
||||
} else {
|
||||
throw ScriptError(cc.current().pos, "Expected { or = after get()")
|
||||
}
|
||||
@ -3211,26 +3267,34 @@ class Compiler(
|
||||
} else if (t.isId("set")) {
|
||||
val setStart = cc.currentPos()
|
||||
cc.next() // consume 'set'
|
||||
cc.requireToken(Token.Type.LPAREN)
|
||||
val setArg = cc.requireToken(Token.Type.ID, "Expected setter argument name")
|
||||
var setArgName = "it"
|
||||
if (cc.peekNextNonWhitespace().type == Token.Type.LPAREN) {
|
||||
cc.next() // consume (
|
||||
setArgName = cc.requireToken(Token.Type.ID, "Expected setter argument name").value
|
||||
cc.requireToken(Token.Type.RPAREN)
|
||||
}
|
||||
miniSink?.onEnterFunction(null)
|
||||
setter = if (cc.peekNextNonWhitespace().type == Token.Type.LBRACE) {
|
||||
cc.skipWsTokens()
|
||||
val body = parseBlock()
|
||||
val body = inCodeContext(CodeContext.Function("<setter>")) {
|
||||
parseBlock()
|
||||
}
|
||||
statement(body.pos) { scope ->
|
||||
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)
|
||||
}
|
||||
} else if (cc.peekNextNonWhitespace().type == Token.Type.ASSIGN) {
|
||||
cc.skipWsTokens()
|
||||
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
|
||||
statement(st.pos) { scope ->
|
||||
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)
|
||||
}
|
||||
} else {
|
||||
@ -3274,6 +3338,8 @@ class Compiler(
|
||||
throw ScriptError(cc.current().pos, "Expected { or = after set(...)")
|
||||
}
|
||||
miniSink?.onExitFunction(cc.currentPos())
|
||||
} else {
|
||||
// private set without body: setter remains null, visibility is restricted
|
||||
}
|
||||
} else {
|
||||
cc.restorePos(mark)
|
||||
@ -3525,7 +3591,7 @@ class Compiler(
|
||||
} else {
|
||||
// Not in class body: regular local/var declaration
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,7 +106,7 @@ open class Scope(
|
||||
* 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.
|
||||
*/
|
||||
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 ->
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller)) return rec
|
||||
}
|
||||
@ -330,27 +330,37 @@ open class Scope(
|
||||
|
||||
internal val objects = mutableMapOf<String, ObjRecord>()
|
||||
|
||||
open operator fun get(name: String): ObjRecord? =
|
||||
if (name == "this") thisObj.asReadonly
|
||||
else {
|
||||
// Prefer direct locals/bindings declared in this frame
|
||||
(objects[name]?.let { rec ->
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) rec else null
|
||||
open operator fun get(name: String): ObjRecord? {
|
||||
if (name == "this") return thisObj.asReadonly
|
||||
|
||||
// 1. Prefer direct locals/bindings declared in this frame
|
||||
objects[name]?.let { rec ->
|
||||
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
|
||||
localBindings[name]?.let { rec ->
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) return rec
|
||||
}
|
||||
// Walk up ancestry
|
||||
?: parent?.get(name)
|
||||
// Finally, fallback to class members on thisObj
|
||||
?: thisObj.objClass.getInstanceMemberOrNull(name)?.let { 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)) {
|
||||
if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.Property || rec.isAbstract) null
|
||||
else rec
|
||||
} else null
|
||||
return rec.copy(receiver = receiver)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// 3. Finally, walk up ancestry
|
||||
return parent?.get(name)
|
||||
}
|
||||
|
||||
// Slot fast-path API
|
||||
@ -380,6 +390,7 @@ open class Scope(
|
||||
// that could interact badly with the new parent and produce a cycle.
|
||||
this.parent = null
|
||||
this.skipScopeCreation = false
|
||||
this.currentClassCtx = parent?.currentClassCtx
|
||||
// fresh identity for PIC caches
|
||||
this.frameId = nextFrameId()
|
||||
// clear locals and slot maps
|
||||
@ -388,6 +399,7 @@ open class Scope(
|
||||
nameToSlot.clear()
|
||||
localBindings.clear()
|
||||
extensions.clear()
|
||||
this.currentClassCtx = parent?.currentClassCtx
|
||||
// Now safe to validate and re-parent
|
||||
ensureNoCycle(parent)
|
||||
this.parent = parent
|
||||
@ -628,46 +640,34 @@ open class Scope(
|
||||
}
|
||||
|
||||
suspend fun resolve(rec: ObjRecord, name: String): Obj {
|
||||
if (rec.type == ObjRecord.Type.Delegated) {
|
||||
val del = rec.delegate ?: run {
|
||||
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
|
||||
val receiver = rec.receiver ?: thisObj
|
||||
return receiver.resolveRecord(this, rec, name, rec.declaringClass).value
|
||||
}
|
||||
|
||||
suspend fun assign(rec: ObjRecord, name: String, newValue: Obj) {
|
||||
if (rec.type == ObjRecord.Type.Delegated) {
|
||||
val receiver = rec.receiver ?: thisObj
|
||||
val del = rec.delegate ?: run {
|
||||
if (thisObj is ObjInstance) {
|
||||
(thisObj as ObjInstance).writeField(this, name, newValue)
|
||||
if (receiver is ObjInstance) {
|
||||
(receiver as ObjInstance).writeField(this, name, newValue)
|
||||
return
|
||||
}
|
||||
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))
|
||||
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")
|
||||
rec.value = newValue
|
||||
}
|
||||
|
||||
@ -293,10 +293,14 @@ class Script(
|
||||
ObjVoid
|
||||
}
|
||||
|
||||
// Delay in milliseconds (plain numeric). For time-aware variants use lyng.time.Duration API.
|
||||
addVoidFn("delay") {
|
||||
val ms = (this.args.firstAndOnly().toDouble()).roundToLong()
|
||||
delay(ms)
|
||||
val a = args.firstAndOnly()
|
||||
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)
|
||||
|
||||
@ -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");
|
||||
* 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(
|
||||
name: String,
|
||||
value: Obj,
|
||||
|
||||
@ -93,24 +93,46 @@ open class Obj {
|
||||
args: Arguments = Arguments.EMPTY,
|
||||
onNotFoundResult: (suspend () -> Obj?)? = null
|
||||
): 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)
|
||||
for (cls in objClass.mro) {
|
||||
if (cls.className == "Obj") break
|
||||
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 caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.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.Delegated) {
|
||||
return rec.value.invoke(scope, this, args, decl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Extensions in scope
|
||||
val extension = scope.findExtension(objClass, name)
|
||||
if (extension != null) {
|
||||
if (extension.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (extension.value as ObjProperty).callGetter(scope, this, extension.declaringClass)
|
||||
} else if (extension.type != ObjRecord.Type.Delegated) {
|
||||
return extension.value.invoke(scope, this, args)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Root object fallback
|
||||
for (cls in objClass.mro) {
|
||||
@ -120,10 +142,15 @@ open class Obj {
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.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.Delegated) {
|
||||
return rec.value.invoke(scope, this, args, decl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return onNotFoundResult?.invoke()
|
||||
?: scope.raiseError(
|
||||
@ -415,29 +442,52 @@ open class Obj {
|
||||
// suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
|
||||
|
||||
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)
|
||||
for (cls in objClass.mro) {
|
||||
if (cls.className == "Obj") break
|
||||
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
||||
if (rec != null) {
|
||||
if (!rec.isAbstract) {
|
||||
return resolveRecord(scope, rec, name, rec.declaringClass)
|
||||
}
|
||||
if (rec != null && !rec.isAbstract) {
|
||||
val decl = rec.declaringClass ?: cls
|
||||
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
|
||||
val extension = scope.findExtension(objClass, name)
|
||||
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
|
||||
for (cls in objClass.mro) {
|
||||
if (cls.className == "Obj") {
|
||||
cls.members[name]?.let {
|
||||
val decl = it.declaringClass ?: cls
|
||||
return resolveRecord(scope, it, name, decl)
|
||||
cls.members[name]?.let { rec ->
|
||||
val decl = rec.declaringClass ?: cls
|
||||
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 {
|
||||
if (obj.type == ObjRecord.Type.Delegated) {
|
||||
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(
|
||||
value = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name))),
|
||||
value = res,
|
||||
type = ObjRecord.Type.Other
|
||||
)
|
||||
}
|
||||
val value = obj.value
|
||||
if (value is ObjProperty) {
|
||||
return ObjRecord(value.callGetter(scope, this, decl), obj.isMutable)
|
||||
}
|
||||
if (value is Statement && decl != null) {
|
||||
return ObjRecord(value.execute(scope.createChildScope(scope.pos, newThisObj = this)), obj.isMutable)
|
||||
if (value is ObjProperty || obj.type == ObjRecord.Type.Property) {
|
||||
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}")
|
||||
val res = prop.callGetter(scope, this, decl)
|
||||
return ObjRecord(res, obj.isMutable)
|
||||
}
|
||||
val caller = scope.currentClassCtx
|
||||
// Check visibility for non-property members here if they weren't checked before
|
||||
@ -472,7 +534,17 @@ open class Obj {
|
||||
open suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
|
||||
willMutate(scope)
|
||||
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)
|
||||
if (field == null) {
|
||||
for (cls in objClass.mro) {
|
||||
if (cls.className == "Obj") break
|
||||
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
||||
@ -481,6 +553,7 @@ open class Obj {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2. Extensions
|
||||
if (field == null) {
|
||||
field = scope.findExtension(objClass, name)
|
||||
@ -512,12 +585,19 @@ open class Obj {
|
||||
}
|
||||
|
||||
open suspend fun getAt(scope: Scope, index: Obj): Obj {
|
||||
if (index is ObjString) {
|
||||
return readField(scope, index.value).value
|
||||
}
|
||||
scope.raiseNotImplemented("indexing")
|
||||
}
|
||||
|
||||
suspend fun getAt(scope: Scope, index: Int): Obj = getAt(scope, ObjInt(index.toLong()))
|
||||
|
||||
open suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
|
||||
if (index is ObjString) {
|
||||
writeField(scope, index.value, newValue)
|
||||
return
|
||||
}
|
||||
scope.raiseNotImplemented("indexing")
|
||||
}
|
||||
|
||||
@ -683,9 +763,7 @@ open class Obj {
|
||||
}
|
||||
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun from(obj: Any?): Obj {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun from(obj: Any?): Obj {
|
||||
return when (obj) {
|
||||
is Obj -> obj
|
||||
is Double -> ObjReal(obj)
|
||||
@ -694,16 +772,16 @@ open class Obj {
|
||||
is Long -> ObjInt.of(obj)
|
||||
is String -> ObjString(obj)
|
||||
is CharSequence -> ObjString(obj.toString())
|
||||
is Char -> ObjChar(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
|
||||
null -> ObjNull
|
||||
is Iterator<*> -> ObjKotlinIterator(obj)
|
||||
is Map.Entry<*, *> -> {
|
||||
obj as MutableMap.MutableEntry<Obj, Obj>
|
||||
ObjMapEntry(obj.key, obj.value)
|
||||
}
|
||||
|
||||
is Iterator<*> -> ObjKotlinIterator(obj as Iterator<Any?>)
|
||||
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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -17,10 +17,7 @@
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.miniast.ParamDoc
|
||||
import net.sergeych.lyng.miniast.TypeGenericDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import net.sergeych.lyng.miniast.*
|
||||
|
||||
val ObjArray by lazy {
|
||||
|
||||
@ -52,32 +49,35 @@ val ObjArray by lazy {
|
||||
ObjFalse
|
||||
}
|
||||
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "last",
|
||||
doc = "The last element of this array.",
|
||||
returns = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisObj.invokeInstanceMethod(
|
||||
type = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
this.thisObj.invokeInstanceMethod(
|
||||
this,
|
||||
"getAt",
|
||||
(thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj()
|
||||
(this.thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj()
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "lastIndex",
|
||||
doc = "Index of the last element (size - 1).",
|
||||
returns = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { (thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj() }
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { (this.thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj() }
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "indices",
|
||||
doc = "Range of valid indices for this array.",
|
||||
returns = type("lyng.Range"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { ObjRange(0.toObj(), thisObj.invokeInstanceMethod(this, "size"), false) }
|
||||
type = type("lyng.Range"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { ObjRange(0.toObj(), this.thisObj.invokeInstanceMethod(this, "size"), false) }
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
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");
|
||||
* 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.lyng.Scope
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import net.sergeych.lynon.BitArray
|
||||
|
||||
class ObjBitBuffer(val bitArray: BitArray) : Obj() {
|
||||
@ -43,12 +45,20 @@ class ObjBitBuffer(val bitArray: BitArray) : Obj() {
|
||||
thisAs<ObjBitBuffer>().bitArray.asUByteArray().toDump()
|
||||
)
|
||||
}
|
||||
addFn("size") {
|
||||
thisAs<ObjBitBuffer>().bitArray.size.toObj()
|
||||
}
|
||||
addFn("sizeInBytes") {
|
||||
ObjInt((thisAs<ObjBitBuffer>().bitArray.size + 7) shr 3)
|
||||
}
|
||||
addPropertyDoc(
|
||||
name = "size",
|
||||
doc = "Size of the bit buffer in bits.",
|
||||
type = type("lyng.Int"),
|
||||
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");
|
||||
* 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.toDump
|
||||
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.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
@ -174,20 +175,26 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() {
|
||||
addClassFn("decodeHex") {
|
||||
ObjBuffer(requireOnlyArg<Obj>().toString().decodeHex().asUByteArray())
|
||||
}
|
||||
createField("size",
|
||||
statement {
|
||||
(thisObj as ObjBuffer).byteArray.size.toObj()
|
||||
}
|
||||
addPropertyDoc(
|
||||
name = "size",
|
||||
doc = "Number of bytes in this buffer.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { (this.thisObj as ObjBuffer).byteArray.size.toObj() }
|
||||
)
|
||||
createField("hex",
|
||||
statement {
|
||||
thisAs<ObjBuffer>().hex.toObj()
|
||||
}
|
||||
addPropertyDoc(
|
||||
name = "hex",
|
||||
doc = "Hexadecimal string representation of the buffer.",
|
||||
type = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjBuffer>().hex.toObj() }
|
||||
)
|
||||
createField("base64",
|
||||
statement {
|
||||
thisAs<ObjBuffer>().base64.toObj()
|
||||
}
|
||||
addPropertyDoc(
|
||||
name = "base64",
|
||||
doc = "Base64 (URL-safe) string representation of the buffer.",
|
||||
type = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjBuffer>().base64.toObj() }
|
||||
)
|
||||
addFn("decodeUtf8") {
|
||||
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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -18,7 +18,7 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
|
||||
class ObjChar(val value: Char): Obj() {
|
||||
@ -47,12 +47,13 @@ class ObjChar(val value: Char): Obj() {
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("Char").apply {
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "code",
|
||||
doc = "Unicode code point (UTF-16 code unit) of this character.",
|
||||
returns = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { ObjInt(thisAs<ObjChar>().value.code.toLong()) }
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { ObjInt((this.thisObj as ObjChar).value.code.toLong()) }
|
||||
)
|
||||
addFn("isDigit") {
|
||||
thisAs<ObjChar>().value.isDigit().toObj()
|
||||
}
|
||||
|
||||
@ -18,10 +18,7 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.*
|
||||
import net.sergeych.lyng.miniast.ParamDoc
|
||||
import net.sergeych.lyng.miniast.TypeGenericDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonType
|
||||
|
||||
@ -30,21 +27,28 @@ private object ClassIdGen { var c: Long = 1L; fun nextId(): Long = c++ }
|
||||
|
||||
val ObjClassType by lazy {
|
||||
ObjClass("Class").apply {
|
||||
addProperty("className", getter = { thisAs<ObjClass>().classNameObj })
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
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",
|
||||
doc = "Simple name of this class (without package).",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { thisAs<ObjClass>().classNameObj }
|
||||
type = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { (this.thisObj as ObjClass).classNameObj }
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "fields",
|
||||
doc = "Declared instance fields of this class and its ancestors (C3 order), without duplicates.",
|
||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val cls = thisAs<ObjClass>()
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val cls = this.thisObj as ObjClass
|
||||
val seen = hashSetOf<String>()
|
||||
val names = mutableListOf<Obj>()
|
||||
for (c in cls.mro) {
|
||||
@ -54,14 +58,15 @@ val ObjClassType by lazy {
|
||||
}
|
||||
ObjList(names.toMutableList())
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "methods",
|
||||
doc = "Declared instance methods of this class and its ancestors (C3 order), without duplicates.",
|
||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val cls = thisAs<ObjClass>()
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val cls = this.thisObj as ObjClass
|
||||
val seen = hashSetOf<String>()
|
||||
val names = mutableListOf<Obj>()
|
||||
for (c in cls.mro) {
|
||||
@ -71,6 +76,7 @@ val ObjClassType by lazy {
|
||||
}
|
||||
ObjList(names.toMutableList())
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "get",
|
||||
@ -247,21 +253,29 @@ open class ObjClass(
|
||||
// remains stable even when call frames are pooled and reused.
|
||||
val stableParent = classScope ?: scope.parent
|
||||
instance.instanceScope = Scope(stableParent, scope.args, scope.pos, instance)
|
||||
// println("[DEBUG_LOG] createInstance: created $instance scope@${instance.instanceScope.hashCode()}")
|
||||
instance.instanceScope.currentClassCtx = null
|
||||
// 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
|
||||
|
||||
for (cls in mro) {
|
||||
// 1) members-defined methods
|
||||
for ((k, v) in members) {
|
||||
for ((k, v) in cls.members) {
|
||||
if (v.value is Statement || v.type == ObjRecord.Type.Delegated) {
|
||||
instance.instanceScope.objects[k] = if (v.type == ObjRecord.Type.Delegated) v.copy() else v
|
||||
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
|
||||
classScope?.objects?.forEach { (k, rec) ->
|
||||
cls.classScope?.objects?.forEach { (k, rec) ->
|
||||
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 (!instance.instanceScope.objects.containsKey(k)) {
|
||||
instance.instanceScope.objects[k] = if (rec.type == ObjRecord.Type.Delegated) rec.copy() else rec
|
||||
if (!instance.instanceScope.objects.containsKey(key)) {
|
||||
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 ->
|
||||
val argsHere = argsForThis ?: Arguments.EMPTY
|
||||
// Assign constructor params into instance scope (unmangled)
|
||||
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
|
||||
// and so that base-class casts like `(obj as Base).field` work.
|
||||
for (p in meta.params) {
|
||||
@ -329,7 +349,13 @@ open class ObjClass(
|
||||
// parameters even if they were shadowed/overwritten by parent class initialization.
|
||||
c.constructorMeta?.let { meta ->
|
||||
val argsHere = argsForThis ?: Arguments.EMPTY
|
||||
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
|
||||
for (p in meta.params) {
|
||||
val rec = instance.instanceScope.objects[p.name]
|
||||
@ -382,7 +408,8 @@ open class ObjClass(
|
||||
): ObjRecord {
|
||||
// Validation of override rules: only for non-system declarations
|
||||
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
|
||||
if (existing != null && existing.declaringClass != this) {
|
||||
// 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.
|
||||
*/
|
||||
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
|
||||
for (cls in mro) {
|
||||
cls.members[name]?.let { return it }
|
||||
cls.classScope?.objects?.get(name)?.let { return it }
|
||||
cls.members[name]?.let {
|
||||
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)
|
||||
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. */
|
||||
|
||||
@ -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");
|
||||
* 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`
|
||||
*/
|
||||
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");
|
||||
* 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 net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
|
||||
open class ObjDeferred(val deferred: Deferred<Obj>): Obj() {
|
||||
@ -38,28 +39,30 @@ open class ObjDeferred(val deferred: Deferred<Obj>): Obj() {
|
||||
returns = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { thisAs<ObjDeferred>().deferred.await() }
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "isCompleted",
|
||||
doc = "Whether this deferred has completed (successfully or with an error).",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { thisAs<ObjDeferred>().deferred.isCompleted.toObj() }
|
||||
addFnDoc(
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjDeferred>().deferred.isCompleted.toObj() }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "isActive",
|
||||
doc = "Whether this deferred is currently active (not completed and not cancelled).",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val d = thisAs<ObjDeferred>().deferred
|
||||
(d.isActive || (!d.isCompleted && !d.isCancelled)).toObj()
|
||||
}
|
||||
addFnDoc(
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "isCancelled",
|
||||
doc = "Whether this deferred was cancelled.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { thisAs<ObjDeferred>().deferred.isCancelled.toObj() }
|
||||
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -18,7 +18,7 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.days
|
||||
@ -74,169 +74,195 @@ class ObjDuration(val duration: Duration) : Obj() {
|
||||
)
|
||||
}
|
||||
}.apply {
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "days",
|
||||
doc = "Return this duration as a real number of days.",
|
||||
returns = type("lyng.Real"),
|
||||
moduleName = "lyng.time"
|
||||
) { thisAs<ObjDuration>().duration.toDouble(DurationUnit.DAYS).toObj() }
|
||||
addFnDoc(
|
||||
type = type("lyng.Real"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.DAYS).toObj() }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "hours",
|
||||
doc = "Return this duration as a real number of hours.",
|
||||
returns = type("lyng.Real"),
|
||||
moduleName = "lyng.time"
|
||||
) { thisAs<ObjDuration>().duration.toDouble(DurationUnit.HOURS).toObj() }
|
||||
addFnDoc(
|
||||
type = type("lyng.Real"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.HOURS).toObj() }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "minutes",
|
||||
doc = "Return this duration as a real number of minutes.",
|
||||
returns = type("lyng.Real"),
|
||||
moduleName = "lyng.time"
|
||||
) { thisAs<ObjDuration>().duration.toDouble(DurationUnit.MINUTES).toObj() }
|
||||
addFnDoc(
|
||||
type = type("lyng.Real"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.MINUTES).toObj() }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "seconds",
|
||||
doc = "Return this duration as a real number of seconds.",
|
||||
returns = type("lyng.Real"),
|
||||
moduleName = "lyng.time"
|
||||
) { thisAs<ObjDuration>().duration.toDouble(DurationUnit.SECONDS).toObj() }
|
||||
addFnDoc(
|
||||
type = type("lyng.Real"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.SECONDS).toObj() }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "milliseconds",
|
||||
doc = "Return this duration as a real number of milliseconds.",
|
||||
returns = type("lyng.Real"),
|
||||
moduleName = "lyng.time"
|
||||
) { thisAs<ObjDuration>().duration.toDouble(DurationUnit.MILLISECONDS).toObj() }
|
||||
addFnDoc(
|
||||
type = type("lyng.Real"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.MILLISECONDS).toObj() }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "microseconds",
|
||||
doc = "Return this duration as a real number of microseconds.",
|
||||
returns = type("lyng.Real"),
|
||||
moduleName = "lyng.time"
|
||||
) { thisAs<ObjDuration>().duration.toDouble(DurationUnit.MICROSECONDS).toObj() }
|
||||
type = type("lyng.Real"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.MICROSECONDS).toObj() }
|
||||
)
|
||||
// extensions
|
||||
|
||||
ObjInt.type.addFnDoc(
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "seconds",
|
||||
doc = "Construct a `Duration` equal to this integer number of seconds.",
|
||||
returns = type("lyng.Duration"),
|
||||
moduleName = "lyng.time"
|
||||
) { ObjDuration(thisAs<ObjInt>().value.seconds) }
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.seconds) }
|
||||
)
|
||||
|
||||
ObjInt.type.addFnDoc(
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "second",
|
||||
doc = "Construct a `Duration` equal to this integer number of seconds.",
|
||||
returns = type("lyng.Duration"),
|
||||
moduleName = "lyng.time"
|
||||
) { ObjDuration(thisAs<ObjInt>().value.seconds) }
|
||||
ObjInt.type.addFnDoc(
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.seconds) }
|
||||
)
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "milliseconds",
|
||||
doc = "Construct a `Duration` equal to this integer number of milliseconds.",
|
||||
returns = type("lyng.Duration"),
|
||||
moduleName = "lyng.time"
|
||||
) { ObjDuration(thisAs<ObjInt>().value.milliseconds) }
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.milliseconds) }
|
||||
)
|
||||
|
||||
ObjInt.type.addFnDoc(
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "millisecond",
|
||||
doc = "Construct a `Duration` equal to this integer number of milliseconds.",
|
||||
returns = type("lyng.Duration"),
|
||||
moduleName = "lyng.time"
|
||||
) { ObjDuration(thisAs<ObjInt>().value.milliseconds) }
|
||||
ObjReal.type.addFnDoc(
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.milliseconds) }
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "seconds",
|
||||
doc = "Construct a `Duration` equal to this real number of seconds.",
|
||||
returns = type("lyng.Duration"),
|
||||
moduleName = "lyng.time"
|
||||
) { ObjDuration(thisAs<ObjReal>().value.seconds) }
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.seconds) }
|
||||
)
|
||||
|
||||
ObjReal.type.addFnDoc(
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "second",
|
||||
doc = "Construct a `Duration` equal to this real number of seconds.",
|
||||
returns = type("lyng.Duration"),
|
||||
moduleName = "lyng.time"
|
||||
) { ObjDuration(thisAs<ObjReal>().value.seconds) }
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.seconds) }
|
||||
)
|
||||
|
||||
ObjReal.type.addFnDoc(
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "milliseconds",
|
||||
doc = "Construct a `Duration` equal to this real number of milliseconds.",
|
||||
returns = type("lyng.Duration"),
|
||||
moduleName = "lyng.time"
|
||||
) { ObjDuration(thisAs<ObjReal>().value.milliseconds) }
|
||||
ObjReal.type.addFnDoc(
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.milliseconds) }
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "millisecond",
|
||||
doc = "Construct a `Duration` equal to this real number of milliseconds.",
|
||||
returns = type("lyng.Duration"),
|
||||
moduleName = "lyng.time"
|
||||
) { ObjDuration(thisAs<ObjReal>().value.milliseconds) }
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.milliseconds) }
|
||||
)
|
||||
|
||||
ObjInt.type.addFnDoc(
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "minutes",
|
||||
doc = "Construct a `Duration` equal to this integer number of minutes.",
|
||||
returns = type("lyng.Duration"),
|
||||
moduleName = "lyng.time"
|
||||
) { ObjDuration(thisAs<ObjInt>().value.minutes) }
|
||||
ObjReal.type.addFnDoc(
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.minutes) }
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "minutes",
|
||||
doc = "Construct a `Duration` equal to this real number of minutes.",
|
||||
returns = type("lyng.Duration"),
|
||||
moduleName = "lyng.time"
|
||||
) { ObjDuration(thisAs<ObjReal>().value.minutes) }
|
||||
ObjInt.type.addFnDoc(
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.minutes) }
|
||||
)
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "minute",
|
||||
doc = "Construct a `Duration` equal to this integer number of minutes.",
|
||||
returns = type("lyng.Duration"),
|
||||
moduleName = "lyng.time"
|
||||
) { ObjDuration(thisAs<ObjInt>().value.minutes) }
|
||||
ObjReal.type.addFnDoc(
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.minutes) }
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "minute",
|
||||
doc = "Construct a `Duration` equal to this real number of minutes.",
|
||||
returns = type("lyng.Duration"),
|
||||
moduleName = "lyng.time"
|
||||
) { ObjDuration(thisAs<ObjReal>().value.minutes) }
|
||||
ObjInt.type.addFnDoc(
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.minutes) }
|
||||
)
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "hours",
|
||||
doc = "Construct a `Duration` equal to this integer number of hours.",
|
||||
returns = type("lyng.Duration"),
|
||||
moduleName = "lyng.time"
|
||||
) { ObjDuration(thisAs<ObjInt>().value.hours) }
|
||||
ObjReal.type.addFnDoc(
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.hours) }
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "hours",
|
||||
doc = "Construct a `Duration` equal to this real number of hours.",
|
||||
returns = type("lyng.Duration"),
|
||||
moduleName = "lyng.time"
|
||||
) { ObjDuration(thisAs<ObjReal>().value.hours) }
|
||||
ObjInt.type.addFnDoc(
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.hours) }
|
||||
)
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "hour",
|
||||
doc = "Construct a `Duration` equal to this integer number of hours.",
|
||||
returns = type("lyng.Duration"),
|
||||
moduleName = "lyng.time"
|
||||
) { ObjDuration(thisAs<ObjInt>().value.hours) }
|
||||
ObjReal.type.addFnDoc(
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.hours) }
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "hour",
|
||||
doc = "Construct a `Duration` equal to this real number of hours.",
|
||||
returns = type("lyng.Duration"),
|
||||
moduleName = "lyng.time"
|
||||
) { ObjDuration(thisAs<ObjReal>().value.hours) }
|
||||
ObjInt.type.addFnDoc(
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.hours) }
|
||||
)
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "days",
|
||||
doc = "Construct a `Duration` equal to this integer number of days.",
|
||||
returns = type("lyng.Duration"),
|
||||
moduleName = "lyng.time"
|
||||
) { ObjDuration(thisAs<ObjInt>().value.days) }
|
||||
ObjReal.type.addFnDoc(
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.days) }
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "days",
|
||||
doc = "Construct a `Duration` equal to this real number of days.",
|
||||
returns = type("lyng.Duration"),
|
||||
moduleName = "lyng.time"
|
||||
) { ObjDuration(thisAs<ObjReal>().value.days) }
|
||||
ObjInt.type.addFnDoc(
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.days) }
|
||||
)
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "day",
|
||||
doc = "Construct a `Duration` equal to this integer number of days.",
|
||||
returns = type("lyng.Duration"),
|
||||
moduleName = "lyng.time"
|
||||
) { ObjDuration(thisAs<ObjInt>().value.days) }
|
||||
ObjReal.type.addFnDoc(
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.days) }
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "day",
|
||||
doc = "Construct a `Duration` equal to this real number of days.",
|
||||
returns = type("lyng.Duration"),
|
||||
moduleName = "lyng.time"
|
||||
) { ObjDuration(thisAs<ObjReal>().value.days) }
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.days) }
|
||||
)
|
||||
|
||||
|
||||
// addFn("epochSeconds") {
|
||||
|
||||
@ -35,6 +35,8 @@ package net.sergeych.lyng.obj/*
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
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.LynonEncoder
|
||||
import net.sergeych.lynon.LynonType
|
||||
@ -76,8 +78,8 @@ class ObjEnumClass(val name: String) : ObjClass(name, EnumBase) {
|
||||
val name = requireOnlyArg<ObjString>()
|
||||
byName[name] ?: raiseSymbolNotFound("does not exists: enum ${className}.$name")
|
||||
}
|
||||
addFn("name") { thisAs<ObjEnumEntry>().name }
|
||||
addFn("ordinal") { thisAs<ObjEnumEntry>().ordinal }
|
||||
addPropertyDoc("name", doc = "Entry name as string", type = type("lyng.String"), getter = { thisAs<ObjEnumEntry>().name })
|
||||
addPropertyDoc("ordinal", doc = "Entry ordinal position", type = type("lyng.Int"), getter = { thisAs<ObjEnumEntry>().ordinal })
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -17,11 +17,10 @@
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.bintools.encodeToHex
|
||||
import net.sergeych.lyng.*
|
||||
import net.sergeych.lyng.miniast.TypeGenericDoc
|
||||
import net.sergeych.lyng.miniast.addConstDoc
|
||||
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.LynonEncoder
|
||||
@ -133,7 +132,7 @@ open class ObjException(
|
||||
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 {
|
||||
return try {
|
||||
@ -170,43 +169,44 @@ open class ObjException(
|
||||
ObjVoid
|
||||
})
|
||||
instanceConstructor = statement { ObjVoid }
|
||||
addConstDoc(
|
||||
addPropertyDoc(
|
||||
name = "message",
|
||||
value = statement {
|
||||
when (val t = thisObj) {
|
||||
doc = "Human‑readable error message.",
|
||||
type = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
when (val t = this.thisObj) {
|
||||
is ObjException -> t.message
|
||||
is ObjInstance -> t.instanceScope.get("Exception::message")?.value ?: ObjNull
|
||||
else -> ObjNull
|
||||
}
|
||||
},
|
||||
doc = "Human‑readable error message.",
|
||||
type = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
}
|
||||
)
|
||||
addConstDoc(
|
||||
addPropertyDoc(
|
||||
name = "extraData",
|
||||
value = statement {
|
||||
when (val t = thisObj) {
|
||||
doc = "Extra data associated with the exception.",
|
||||
type = type("lyng.Any", nullable = true),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
when (val t = this.thisObj) {
|
||||
is ObjException -> t.extraData
|
||||
else -> ObjNull
|
||||
}
|
||||
},
|
||||
doc = "Extra data associated with the exception.",
|
||||
type = type("lyng.Any", nullable = true),
|
||||
moduleName = "lyng.stdlib"
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "stackTrace",
|
||||
doc = "Stack trace captured at throw site as a list of `StackTraceEntry`.",
|
||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.StackTraceEntry"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
when (val t = thisObj) {
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.StackTraceEntry"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
when (val t = this.thisObj) {
|
||||
is ObjException -> t.getStackTrace()
|
||||
is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList()
|
||||
else -> ObjList()
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "toString",
|
||||
doc = "Human‑readable string representation of the error.",
|
||||
|
||||
@ -21,6 +21,7 @@ import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import net.sergeych.lyng.Arguments
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.Visibility
|
||||
import net.sergeych.lyng.canAccessMember
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
@ -31,140 +32,128 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
internal lateinit var instanceScope: Scope
|
||||
|
||||
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||
// 1. Direct (unmangled) lookup first
|
||||
instanceScope[name]?.let { rec ->
|
||||
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) {
|
||||
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
|
||||
// Allow unconditional access when accessing through `this` of the same instance
|
||||
// BUT only if we are in the class context (not extension)
|
||||
if (scope.thisObj !== this || scope.currentClassCtx == null) {
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
scope.raiseError(
|
||||
ObjIllegalAccessException(
|
||||
scope,
|
||||
"can't access field $name (declared in ${decl?.className ?: "?"})"
|
||||
)
|
||||
)
|
||||
}
|
||||
// Unmangled access is only allowed if it's public OR we are inside the same instance's method
|
||||
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.visibility, decl, caller))
|
||||
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)
|
||||
return super.readField(scope, name)
|
||||
}
|
||||
|
||||
override suspend fun resolveRecord(scope: Scope, obj: ObjRecord, name: String, decl: ObjClass?): ObjRecord {
|
||||
if (obj.type == ObjRecord.Type.Delegated) {
|
||||
val storageName = "${decl?.className}::$name"
|
||||
var del = instanceScope[storageName]?.delegate
|
||||
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 ?: obj.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate (tried $storageName)")
|
||||
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
|
||||
}
|
||||
return super.resolveRecord(scope, obj, name, decl)
|
||||
|
||||
// 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) {
|
||||
// Direct (unmangled) first
|
||||
instanceScope[name]?.let { f ->
|
||||
val decl = f.declaringClass
|
||||
if (scope.thisObj !== this || scope.currentClassCtx == null) {
|
||||
willMutate(scope)
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(f.effectiveWriteVisibility, decl, caller))
|
||||
ObjIllegalAccessException(
|
||||
scope,
|
||||
"can't assign to field $name (declared in ${decl?.className ?: "?"})"
|
||||
).raise()
|
||||
|
||||
// 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
|
||||
}
|
||||
if (f.type == ObjRecord.Type.Property) {
|
||||
val prop = f.value as ObjProperty
|
||||
}
|
||||
}
|
||||
|
||||
// 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 (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
|
||||
}
|
||||
// Try MI-mangled resolution along linearization (C3 MRO)
|
||||
val cls = objClass
|
||||
fun findMangled(): ObjRecord? {
|
||||
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"
|
||||
val storageName = "${decl?.className}::$name"
|
||||
var del = instanceScope[storageName]?.delegate
|
||||
if (del == null) {
|
||||
for (c in objClass.mro) {
|
||||
@ -179,32 +168,53 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
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(
|
||||
scope: Scope, name: String, args: Arguments,
|
||||
onNotFoundResult: (suspend () -> 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
|
||||
for (cls in objClass.mro) {
|
||||
if (cls.className == "Obj") break
|
||||
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
||||
if (rec != null) {
|
||||
if (rec != null && !rec.isAbstract) {
|
||||
if (rec.type == ObjRecord.Type.Delegated) {
|
||||
val storageName = "${cls.className}::$name"
|
||||
val del = instanceScope[storageName]?.delegate ?: rec.delegate
|
||||
?: 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()
|
||||
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)))
|
||||
propVal.invoke(scope, this, args, rec.declaringClass ?: cls)
|
||||
})
|
||||
}
|
||||
if (rec.type == ObjRecord.Type.Fun && !rec.isAbstract) {
|
||||
val decl = rec.declaringClass ?: cls
|
||||
val caller = scope.currentClassCtx ?: if (scope.thisObj === this) objClass else null
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
@ -214,13 +224,17 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
"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(
|
||||
instanceScope,
|
||||
this,
|
||||
args,
|
||||
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)
|
||||
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");
|
||||
* 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.JsonPrimitive
|
||||
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.LynonEncoder
|
||||
import net.sergeych.lynon.LynonSettings
|
||||
@ -148,34 +151,71 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru
|
||||
}
|
||||
|
||||
}.apply {
|
||||
addFn("epochSeconds") {
|
||||
addPropertyDoc(
|
||||
name = "epochSeconds",
|
||||
doc = "Return the number of seconds since the Unix epoch as a real number (including fractions).",
|
||||
type = type("lyng.Real"),
|
||||
moduleName = "lyng.time",
|
||||
getter = {
|
||||
val instant = thisAs<ObjInstant>().instant
|
||||
ObjReal(instant.epochSeconds + instant.nanosecondsOfSecond * 1e-9)
|
||||
}
|
||||
addFn("isDistantFuture") {
|
||||
thisAs<ObjInstant>().instant.isDistantFuture.toObj()
|
||||
}
|
||||
addFn("isDistantPast") {
|
||||
thisAs<ObjInstant>().instant.isDistantPast.toObj()
|
||||
}
|
||||
addFn("epochWholeSeconds") {
|
||||
ObjInt(thisAs<ObjInstant>().instant.epochSeconds)
|
||||
}
|
||||
addFn("nanosecondsOfSecond") {
|
||||
ObjInt(thisAs<ObjInstant>().instant.nanosecondsOfSecond.toLong())
|
||||
}
|
||||
addFn("truncateToSecond") {
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "isDistantFuture",
|
||||
doc = "Whether this instant represents the distant future.",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { thisAs<ObjInstant>().instant.isDistantFuture.toObj() }
|
||||
)
|
||||
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
|
||||
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
|
||||
ObjInstant(
|
||||
Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond / 1_000_000 * 1_000_000),
|
||||
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
|
||||
ObjInstant(
|
||||
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");
|
||||
* 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.miniast.ParamDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
|
||||
/**
|
||||
@ -29,19 +30,20 @@ import net.sergeych.lyng.miniast.type
|
||||
val ObjIterable by lazy {
|
||||
ObjClass("Iterable").apply {
|
||||
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "toList",
|
||||
doc = "Collect elements of this iterable into a new list.",
|
||||
returns = type("lyng.List"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
type = type("lyng.List"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val result = mutableListOf<Obj>()
|
||||
val iterator = thisObj.invokeInstanceMethod(this, "iterator")
|
||||
|
||||
while (iterator.invokeInstanceMethod(this, "hasNext").toBool())
|
||||
result += iterator.invokeInstanceMethod(this, "next")
|
||||
val it = this.thisObj.invokeInstanceMethod(this, "iterator")
|
||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||
result.add(it.invokeInstanceMethod(this, "next"))
|
||||
}
|
||||
ObjList(result)
|
||||
}
|
||||
)
|
||||
|
||||
// it is not effective, but it is open:
|
||||
addFnDoc(
|
||||
@ -55,7 +57,7 @@ val ObjIterable by lazy {
|
||||
val obj = args.firstAndOnly()
|
||||
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
||||
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
|
||||
}
|
||||
ObjFalse
|
||||
@ -73,46 +75,49 @@ val ObjIterable by lazy {
|
||||
var index = 0
|
||||
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
||||
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())
|
||||
index++
|
||||
}
|
||||
ObjInt(-1L)
|
||||
}
|
||||
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "toSet",
|
||||
doc = "Collect elements of this iterable into a new set.",
|
||||
returns = type("lyng.Set"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
if( thisObj.isInstanceOf(ObjSet.type) )
|
||||
thisObj
|
||||
type = type("lyng.Set"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
if( this.thisObj.isInstanceOf(ObjSet.type) )
|
||||
this.thisObj
|
||||
else {
|
||||
val result = mutableSetOf<Obj>()
|
||||
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
||||
val it = this.thisObj.invokeInstanceMethod(this, "iterator")
|
||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||
result += it.invokeInstanceMethod(this, "next")
|
||||
result.add(it.invokeInstanceMethod(this, "next"))
|
||||
}
|
||||
ObjSet(result)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "toMap",
|
||||
doc = "Collect pairs into a map using [0] as key and [1] as value for each element.",
|
||||
returns = type("lyng.Map"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
type = type("lyng.Map"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val result = mutableMapOf<Obj, Obj>()
|
||||
thisObj.toFlow(this).collect { pair ->
|
||||
this.thisObj.enumerate(this) { pair ->
|
||||
when (pair) {
|
||||
is ObjMapEntry -> result[pair.key] = pair.value
|
||||
else -> result[pair.getAt(this, 0)] = pair.getAt(this, 1)
|
||||
}
|
||||
true
|
||||
}
|
||||
ObjMap(result)
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "associateBy",
|
||||
@ -156,7 +161,7 @@ val ObjIterable by lazy {
|
||||
val fn = requiredArg<Statement>(0)
|
||||
val result = mutableListOf<Obj>()
|
||||
thisObj.toFlow(this).collect {
|
||||
result += fn.call(this, it)
|
||||
result.add(fn.call(this, it))
|
||||
}
|
||||
ObjList(result)
|
||||
}
|
||||
@ -173,7 +178,7 @@ val ObjIterable by lazy {
|
||||
val result = mutableListOf<Obj>()
|
||||
thisObj.toFlow(this).collect {
|
||||
val transformed = fn.call(this, it)
|
||||
if( transformed != ObjNull) result += transformed
|
||||
if( transformed != ObjNull) result.add(transformed)
|
||||
}
|
||||
ObjList(result)
|
||||
}
|
||||
@ -189,25 +194,26 @@ val ObjIterable by lazy {
|
||||
val result = mutableListOf<Obj>()
|
||||
if (n > 0) {
|
||||
thisObj.enumerate(this) {
|
||||
result += it
|
||||
result.add(it)
|
||||
--n > 0
|
||||
}
|
||||
}
|
||||
ObjList(result)
|
||||
}
|
||||
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "isEmpty",
|
||||
doc = "Whether the iterable has no elements.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
ObjBool(
|
||||
thisObj.invokeInstanceMethod(this, "iterator")
|
||||
this.thisObj.invokeInstanceMethod(this, "iterator")
|
||||
.invokeInstanceMethod(this, "hasNext").toBool()
|
||||
.not()
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
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");
|
||||
* 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.Statement
|
||||
import net.sergeych.lyng.miniast.ParamDoc
|
||||
import net.sergeych.lyng.miniast.addConstDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import net.sergeych.lyng.statement
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
import net.sergeych.lynon.LynonType
|
||||
|
||||
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 {
|
||||
return when (index) {
|
||||
is ObjInt -> {
|
||||
@ -77,21 +91,35 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
}
|
||||
|
||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||
if (other !is ObjList) return -2
|
||||
if (other is ObjList) {
|
||||
val mySize = list.size
|
||||
val otherSize = other.list.size
|
||||
val commonSize = minOf(mySize, otherSize)
|
||||
for (i in 0..<commonSize) {
|
||||
if (list[i].compareTo(scope, other.list[i]) != 0) {
|
||||
return list[i].compareTo(scope, other.list[i])
|
||||
val d = list[i].compareTo(scope, other.list[i])
|
||||
if (d != 0) {
|
||||
return d
|
||||
}
|
||||
}
|
||||
// equal so far, longer is greater:
|
||||
return when {
|
||||
mySize < otherSize -> -1
|
||||
mySize > otherSize -> 1
|
||||
else -> 0
|
||||
val res = mySize.compareTo(otherSize)
|
||||
return res
|
||||
}
|
||||
if (other.isInstanceOf(ObjIterable)) {
|
||||
val it1 = this.list.iterator()
|
||||
val it2 = other.invokeInstanceMethod(scope, "iterator")
|
||||
val hasNext2 = it2.getInstanceMethod(scope, "hasNext")
|
||||
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 =
|
||||
@ -99,27 +127,28 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
other is ObjList ->
|
||||
ObjList((list + other.list).toMutableList())
|
||||
|
||||
other.isInstanceOf(ObjIterable) -> {
|
||||
other.isInstanceOf(ObjIterable) && other !is ObjString && other !is ObjBuffer -> {
|
||||
val l = other.callMethod<ObjList>(scope, "toList")
|
||||
ObjList((list + l.list).toMutableList())
|
||||
}
|
||||
|
||||
else ->
|
||||
scope.raiseError("'+': can't concatenate ${this.toString(scope)} with ${other.toString(scope)}")
|
||||
else -> {
|
||||
val newList = list.toMutableList()
|
||||
newList.add(other)
|
||||
ObjList(newList)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override suspend fun plusAssign(scope: Scope, other: Obj): Obj {
|
||||
// optimization
|
||||
if (other is ObjList) {
|
||||
list += other.list
|
||||
return this
|
||||
list.addAll(other.list)
|
||||
} 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
|
||||
}
|
||||
|
||||
@ -222,26 +251,25 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
return ObjList(decoder.decodeAnyList(scope))
|
||||
}
|
||||
}.apply {
|
||||
addConstDoc(
|
||||
addPropertyDoc(
|
||||
name = "size",
|
||||
value = statement {
|
||||
(thisObj as ObjList).list.size.toObj()
|
||||
},
|
||||
doc = "Number of elements in this list.",
|
||||
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",
|
||||
value = statement {
|
||||
doc = "Append one or more elements to the end of this list.",
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
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.",
|
||||
type = type("lyng.Callable"),
|
||||
moduleName = "lyng.stdlib"
|
||||
)
|
||||
}
|
||||
addFnDoc(
|
||||
name = "insertAt",
|
||||
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");
|
||||
* 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 net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.Statement
|
||||
import net.sergeych.lyng.miniast.ParamDoc
|
||||
import net.sergeych.lyng.miniast.TypeGenericDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
import net.sergeych.lynon.LynonType
|
||||
@ -76,24 +73,27 @@ class ObjMapEntry(val key: Obj, val value: Obj) : Obj() {
|
||||
)
|
||||
}
|
||||
}.apply {
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "key",
|
||||
doc = "Key component of this map entry.",
|
||||
returns = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { thisAs<ObjMapEntry>().key }
|
||||
addFnDoc(
|
||||
type = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjMapEntry>().key }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "value",
|
||||
doc = "Value component of this map entry.",
|
||||
returns = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { thisAs<ObjMapEntry>().value }
|
||||
addFnDoc(
|
||||
type = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjMapEntry>().value }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "size",
|
||||
doc = "Number of components in this entry (always 2).",
|
||||
returns = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { 2.toObj() }
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
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() {
|
||||
|
||||
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 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 {
|
||||
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
|
||||
}
|
||||
|
||||
@ -247,14 +265,13 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
||||
lambda.execute(this)
|
||||
}
|
||||
}
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "size",
|
||||
doc = "Number of entries in the map.",
|
||||
returns = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjMap>().map.size.toObj()
|
||||
}
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { (this.thisObj as ObjMap).map.size.toObj() }
|
||||
)
|
||||
addFnDoc(
|
||||
name = "remove",
|
||||
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()
|
||||
thisObj
|
||||
}
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "keys",
|
||||
doc = "List of keys in this map.",
|
||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjMap>().map.keys.toObj()
|
||||
}
|
||||
addFnDoc(
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjMap>().map.keys.toObj() }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "values",
|
||||
doc = "List of values in this map.",
|
||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjList(thisAs<ObjMap>().map.values.toMutableList())
|
||||
}
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { ObjList(thisAs<ObjMap>().map.values.toMutableList()) }
|
||||
)
|
||||
addFnDoc(
|
||||
name = "iterator",
|
||||
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) {
|
||||
val entry = when (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
|
||||
}
|
||||
|
||||
@ -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");
|
||||
* 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.miniast.TypeGenericDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
|
||||
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 {
|
||||
val type = ObjClass("Range", ObjIterable).apply {
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "start",
|
||||
doc = "Start bound of the range or null if open.",
|
||||
returns = type("lyng.Any", nullable = true),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjRange>().start ?: ObjNull
|
||||
}
|
||||
addFnDoc(
|
||||
type = type("lyng.Any", nullable = true),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRange>().start ?: ObjNull }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "end",
|
||||
doc = "End bound of the range or null if open.",
|
||||
returns = type("lyng.Any", nullable = true),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjRange>().end ?: ObjNull
|
||||
}
|
||||
addFnDoc(
|
||||
type = type("lyng.Any", nullable = true),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRange>().end ?: ObjNull }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "isOpen",
|
||||
doc = "Whether the range is open on either side (no start or no end).",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjRange>().let { it.start == null || it.end == null }.toObj()
|
||||
}
|
||||
addFnDoc(
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRange>().let { it.start == null || it.end == null }.toObj() }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "isIntRange",
|
||||
doc = "True if both bounds are Int values.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjRange>().isIntRange.toObj()
|
||||
}
|
||||
addFnDoc(
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRange>().isIntRange.toObj() }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "isCharRange",
|
||||
doc = "True if both bounds are Char values.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjRange>().isCharRange.toObj()
|
||||
}
|
||||
addFnDoc(
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRange>().isCharRange.toObj() }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "isEndInclusive",
|
||||
doc = "Whether the end bound is inclusive.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjRange>().isEndInclusive.toObj()
|
||||
}
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRange>().isEndInclusive.toObj() }
|
||||
)
|
||||
addFnDoc(
|
||||
name = "iterator",
|
||||
doc = "Iterator over elements in this range (optimized for Int ranges).",
|
||||
|
||||
@ -36,6 +36,8 @@ data class ObjRecord(
|
||||
val isClosed: Boolean = false,
|
||||
val isOverride: Boolean = false,
|
||||
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
|
||||
enum class Type(val comparable: Boolean = false,val serializable: Boolean = false) {
|
||||
|
||||
@ -34,7 +34,15 @@ sealed interface ObjRef {
|
||||
*/
|
||||
suspend fun evalValue(scope: Scope): Obj {
|
||||
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
|
||||
}
|
||||
suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||
@ -383,7 +391,7 @@ class IncDecRef(
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val rec = target.get(scope)
|
||||
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
|
||||
// We now treat numbers as immutable and always perform write-back via setAt.
|
||||
// This avoids issues where literals are shared and mutated in-place.
|
||||
@ -947,14 +955,13 @@ class IndexRef(
|
||||
}
|
||||
|
||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||
val fastRval = PerfFlags.RVAL_FASTPATH
|
||||
val base = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
||||
val base = target.evalValue(scope)
|
||||
if (base == ObjNull && isOptional) {
|
||||
// no-op on null receiver for optional chaining assignment
|
||||
return
|
||||
}
|
||||
val idx = if (fastRval) index.evalValue(scope) else index.get(scope).value
|
||||
if (fastRval) {
|
||||
val idx = index.evalValue(scope)
|
||||
|
||||
// Mirror read fast-path with direct write for ObjList + ObjInt index
|
||||
if (base is ObjList && idx is ObjInt) {
|
||||
val i = idx.toInt()
|
||||
@ -966,7 +973,7 @@ class IndexRef(
|
||||
base.map[idx] = newValue
|
||||
return
|
||||
}
|
||||
if (PerfFlags.INDEX_PIC) {
|
||||
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
|
||||
@ -1007,7 +1014,6 @@ class IndexRef(
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
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++
|
||||
// 2) Fallback to current-scope object or field on `this`
|
||||
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 {
|
||||
return scope.thisObj.readField(scope, name)
|
||||
} catch (e: ExecutionError) {
|
||||
@ -1305,18 +1297,6 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.localVarPicMiss++
|
||||
// 2) Fallback name in scope or field on `this`
|
||||
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 {
|
||||
return scope.thisObj.readField(scope, name)
|
||||
} catch (e: ExecutionError) {
|
||||
@ -1327,22 +1307,9 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
||||
|
||||
override suspend fun evalValue(scope: Scope): Obj {
|
||||
scope.pos = atPos
|
||||
if (!PerfFlags.LOCAL_SLOT_PIC) {
|
||||
scope.getSlotIndexOf(name)?.let { return scope.resolve(scope.getSlotRecord(it), name) }
|
||||
// fallback to current-scope object 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 {
|
||||
scope.thisObj.readField(scope, name).value
|
||||
} catch (e: ExecutionError) {
|
||||
@ -1350,38 +1317,6 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
||||
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 {
|
||||
val res = scope.thisObj.readField(scope, name).value
|
||||
res
|
||||
} catch (e: ExecutionError) {
|
||||
if ((e.message ?: "").contains("no such field: $name")) scope.raiseSymbolNotFound(name)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||
scope.pos = atPos
|
||||
@ -1395,24 +1330,6 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
||||
scope.assign(stored, name, newValue)
|
||||
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`
|
||||
scope.thisObj.writeField(scope, name, newValue)
|
||||
return
|
||||
@ -1429,24 +1346,6 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
||||
scope.assign(stored, name, newValue)
|
||||
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)
|
||||
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");
|
||||
* 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.RegexCache
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.miniast.ParamDoc
|
||||
import net.sergeych.lyng.miniast.TypeGenericDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import net.sergeych.lyng.miniast.*
|
||||
|
||||
class ObjRegex(val regex: Regex) : Obj() {
|
||||
override val objClass get() = type
|
||||
@ -123,30 +120,27 @@ class ObjRegexMatch(val match: MatchResult) : Obj() {
|
||||
scope.raiseError("RegexMatch can't be constructed directly")
|
||||
}
|
||||
}.apply {
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "groups",
|
||||
doc = "List of captured groups with index 0 as the whole match.",
|
||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjRegexMatch>().objGroups
|
||||
}
|
||||
addFnDoc(
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRegexMatch>().objGroups }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "value",
|
||||
doc = "The matched substring.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjRegexMatch>().objValue
|
||||
}
|
||||
addFnDoc(
|
||||
type = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRegexMatch>().objValue }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "range",
|
||||
doc = "Range of the match in the input (end-exclusive).",
|
||||
returns = type("lyng.Range"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjRegexMatch>().objRange
|
||||
}
|
||||
type = type("lyng.Range"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { 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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -18,10 +18,7 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.miniast.ParamDoc
|
||||
import net.sergeych.lyng.miniast.TypeGenericDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import net.sergeych.lyng.miniast.*
|
||||
|
||||
class RingBuffer<T>(val maxSize: Int) : Iterable<T> {
|
||||
private val data = arrayOfNulls<Any>(maxSize)
|
||||
@ -94,18 +91,20 @@ class ObjRingBuffer(val capacity: Int) : Obj() {
|
||||
return ObjRingBuffer(scope.requireOnlyArg<ObjInt>().toInt())
|
||||
}
|
||||
}.apply {
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "capacity",
|
||||
doc = "Maximum number of elements the buffer can hold.",
|
||||
returns = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { thisAs<ObjRingBuffer>().capacity.toObj() }
|
||||
addFnDoc(
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRingBuffer>().capacity.toObj() }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "size",
|
||||
doc = "Current number of elements in the buffer.",
|
||||
returns = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { thisAs<ObjRingBuffer>().buffer.size.toObj() }
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRingBuffer>().buffer.size.toObj() }
|
||||
)
|
||||
addFnDoc(
|
||||
name = "iterator",
|
||||
doc = "Iterator over elements in insertion order (oldest to newest).",
|
||||
@ -122,12 +121,16 @@ class ObjRingBuffer(val capacity: Int) : Obj() {
|
||||
returns = type("lyng.Void"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { thisAs<ObjRingBuffer>().apply { buffer.add(requireOnlyArg<Obj>()) } }
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "first",
|
||||
doc = "Return the oldest element in the buffer.",
|
||||
returns = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { thisAs<ObjRingBuffer>().buffer.first() }
|
||||
type = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib",
|
||||
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");
|
||||
* 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() {
|
||||
|
||||
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 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 {
|
||||
return if (other !is ObjSet) -1
|
||||
else {
|
||||
if (set == other.set) 0
|
||||
else -1
|
||||
if (other is ObjSet) {
|
||||
if (set == other.set) return 0
|
||||
if (set.size != other.set.size) return set.size.compareTo(other.set.size)
|
||||
return set.toString().compareTo(other.set.toString())
|
||||
}
|
||||
return -2
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
@ -126,10 +139,7 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other == null || this::class != other::class) return false
|
||||
|
||||
other as ObjSet
|
||||
|
||||
if (other !is ObjSet) return false
|
||||
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");
|
||||
* 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.Scope
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lyng.statement
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
import net.sergeych.lynon.LynonType
|
||||
@ -124,10 +123,16 @@ data class ObjString(val value: String) : Obj() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
val type = object : ObjClass("String") {
|
||||
val type = object : ObjClass("String", ObjCollection) {
|
||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
|
||||
ObjString(decoder.unpackBinaryData().decodeToString())
|
||||
}.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(
|
||||
name = "toInt",
|
||||
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))
|
||||
}
|
||||
addConstDoc(
|
||||
addPropertyDoc(
|
||||
name = "length",
|
||||
value = statement { ObjInt.of(thisAs<ObjString>().value.length.toLong()) },
|
||||
doc = "Number of UTF-16 code units in this string.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { ObjInt.of((this.thisObj as ObjString).value.length.toLong()) }
|
||||
)
|
||||
addFnDoc(
|
||||
name = "takeLast",
|
||||
@ -240,16 +245,17 @@ data class ObjString(val value: String) : Obj() {
|
||||
) {
|
||||
thisAs<ObjString>().value.uppercase().let(::ObjString)
|
||||
}
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "characters",
|
||||
doc = "List of characters of this string.",
|
||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Char"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Char"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
ObjList(
|
||||
thisAs<ObjString>().value.map { ObjChar(it) }.toMutableList()
|
||||
(this.thisObj as ObjString).value.map { ObjChar(it) }.toMutableList()
|
||||
)
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "last",
|
||||
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"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { ObjBuffer(thisAs<ObjString>().value.encodeToByteArray().asUByteArray()) }
|
||||
addFnDoc(
|
||||
addPropertyDoc(
|
||||
name = "size",
|
||||
doc = "Alias for length: the number of characters (code units) in this string.",
|
||||
returns = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { ObjInt.of(thisAs<ObjString>().value.length.toLong()) }
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { ObjInt.of((this.thisObj as ObjString).value.length.toLong()) }
|
||||
)
|
||||
addFnDoc(
|
||||
name = "toReal",
|
||||
doc = "Parse this string as a real number (floating point).",
|
||||
|
||||
@ -1994,12 +1994,12 @@ class ScriptTest {
|
||||
"""
|
||||
class Foo {
|
||||
// this means last is lambda:
|
||||
fun f(e=1, f) {
|
||||
"e="+e+"f="+f()
|
||||
fun test_f(e=1, f_param) {
|
||||
"e="+e+"f="+f_param()
|
||||
}
|
||||
}
|
||||
val f = Foo()
|
||||
assertEquals("e=1f=xx", f.f { "xx" })
|
||||
val f_obj = Foo()
|
||||
assertEquals("e=1f=xx", f_obj.test_f { "xx" })
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
@ -2011,13 +2011,13 @@ class ScriptTest {
|
||||
"""
|
||||
class Foo {
|
||||
// this means last is lambda:
|
||||
fun f(e..., f) {
|
||||
"e="+e+"f="+f()
|
||||
fun test_f_ellipsis(e..., f_param) {
|
||||
"e="+e+"f="+f_param()
|
||||
}
|
||||
}
|
||||
val f = Foo()
|
||||
assertEquals("e=[]f=xx", f.f { "xx" })
|
||||
assertEquals("e=[1,2]f=xx", f.f(1,2) { "xx" })
|
||||
val f_obj = Foo()
|
||||
assertEquals("e=[]f=xx", f_obj.test_f_ellipsis { "xx" })
|
||||
assertEquals("e=[1,2]f=xx", f_obj.test_f_ellipsis(1,2) { "xx" })
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
@ -4645,7 +4645,8 @@ class ScriptTest {
|
||||
null
|
||||
} catch { it }
|
||||
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())
|
||||
}
|
||||
|
||||
@ -4703,7 +4704,7 @@ class ScriptTest {
|
||||
// 61755f07-630c-4181-8d50-1b044d96e1f4
|
||||
class T {
|
||||
static var f1 = null
|
||||
static fun t(name=null) {
|
||||
static fun testCapture(name=null) {
|
||||
run {
|
||||
// I expect it will catch the 'name' from
|
||||
// param?
|
||||
@ -4714,11 +4715,86 @@ class ScriptTest {
|
||||
assert(T.f1 == null)
|
||||
println("-- "+T.f1::class)
|
||||
println("-- "+T.f1)
|
||||
T.t("foo")
|
||||
T.testCapture("foo")
|
||||
println("2- "+T.f1::class)
|
||||
println("2- "+T.f1)
|
||||
assert(T.f1 == "foo")
|
||||
""".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");
|
||||
* 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. */
|
||||
fun Iterable.shuffled() {
|
||||
toList().apply { shuffle() }
|
||||
toList.apply { shuffle() }
|
||||
}
|
||||
|
||||
/*
|
||||
@ -267,7 +267,7 @@ class StackTraceEntry(
|
||||
fun Exception.printStackTrace() {
|
||||
println(this)
|
||||
var lastEntry = null
|
||||
for( entry in stackTrace() ) {
|
||||
for( entry in stackTrace ) {
|
||||
if( lastEntry == null || lastEntry !is StackTraceEntry || lastEntry.line != entry.line )
|
||||
println("\tat "+entry.toString())
|
||||
lastEntry = entry
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user