Add DB decode annotations and preserved declaration metadata
This commit is contained in:
1 parent
2abe7e2f96
commit
50e34e520e
23 files changed
+1690
-55
No files matched your search
@@ -88,7 +88,8 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
||||
a.visibility ?: defaultVisibility,
|
||||
recordType = recordType,
|
||||
declaringClass = declaringClass,
|
||||
isTransient = a.isTransient
|
||||
isTransient = a.isTransient,
|
||||
annotations = a.annotations
|
||||
)
|
||||
}
|
||||
return
|
||||
@@ -108,7 +109,8 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
||||
a.visibility ?: defaultVisibility,
|
||||
recordType = recordType,
|
||||
declaringClass = declaringClass,
|
||||
isTransient = a.isTransient
|
||||
isTransient = a.isTransient,
|
||||
annotations = a.annotations
|
||||
)
|
||||
}
|
||||
|
||||
@@ -505,5 +507,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
||||
val accessType: AccessType? = null,
|
||||
val visibility: Visibility? = null,
|
||||
val isTransient: Boolean = false,
|
||||
val annotationSpecs: List<ParsedDeclAnnotation> = emptyList(),
|
||||
val annotations: List<DeclAnnotation> = emptyList(),
|
||||
)
|
||||
}
|
||||
@@ -41,6 +41,17 @@ data class ClassDeclSpec(
|
||||
val initScope: List<Statement>,
|
||||
)
|
||||
|
||||
private suspend fun evaluateConstructorAnnotations(scope: Scope, args: ArgsDeclaration?): ArgsDeclaration? {
|
||||
if (args == null) return null
|
||||
if (args.params.none { it.annotationSpecs.isNotEmpty() }) return args
|
||||
return args.copy(
|
||||
params = args.params.map { item ->
|
||||
if (item.annotationSpecs.isEmpty()) item
|
||||
else item.copy(annotations = item.annotationSpecs.evaluateDeclAnnotations(scope))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
internal suspend fun executeClassDecl(
|
||||
scope: Scope,
|
||||
spec: ClassDeclSpec,
|
||||
@@ -61,7 +72,7 @@ internal suspend fun executeClassDecl(
|
||||
val newClass = ObjInstanceClass(spec.className, *parentClasses.toTypedArray())
|
||||
newClass.isAnonymous = spec.isAnonymous
|
||||
newClass.isSingletonObject = true
|
||||
newClass.constructorMeta = ArgsDeclaration(emptyList(), Token.Type.RPAREN)
|
||||
newClass.constructorMeta = evaluateConstructorAnnotations(scope, ArgsDeclaration(emptyList(), Token.Type.RPAREN))
|
||||
for (i in parentClasses.indices) {
|
||||
val argsList = spec.baseSpecs[i].args
|
||||
if (argsList != null) newClass.directParentArgs[parentClasses[i]] = argsList
|
||||
@@ -86,6 +97,7 @@ internal suspend fun executeClassDecl(
|
||||
}
|
||||
|
||||
if (spec.isExtern) {
|
||||
val evaluatedConstructorArgs = evaluateConstructorAnnotations(scope, spec.constructorArgs)
|
||||
val parentClasses = spec.baseSpecs.mapNotNull { baseSpec ->
|
||||
val rec = scope[baseSpec.name]
|
||||
val cls = rec?.value as? ObjClass
|
||||
@@ -106,8 +118,8 @@ internal suspend fun executeClassDecl(
|
||||
}
|
||||
val stub = resolved ?: ObjInstanceClass(spec.className, *parentClasses.toTypedArray()).apply {
|
||||
this.isAbstract = true
|
||||
constructorMeta = spec.constructorArgs
|
||||
spec.constructorArgs?.params?.forEach { p ->
|
||||
constructorMeta = evaluatedConstructorArgs
|
||||
evaluatedConstructorArgs?.params?.forEach { p ->
|
||||
if (p.accessType != null) {
|
||||
createField(
|
||||
p.name,
|
||||
@@ -118,6 +130,7 @@ internal suspend fun executeClassDecl(
|
||||
pos = Pos.builtIn,
|
||||
isTransient = p.isTransient,
|
||||
type = ObjRecord.Type.ConstructorField,
|
||||
annotations = p.annotations,
|
||||
fieldId = spec.constructorFieldIds?.get(p.name)
|
||||
)
|
||||
}
|
||||
@@ -161,16 +174,17 @@ internal suspend fun executeClassDecl(
|
||||
}
|
||||
}
|
||||
|
||||
val evaluatedConstructorArgs = evaluateConstructorAnnotations(scope, spec.constructorArgs)
|
||||
val newClass = ObjInstanceClass(spec.className, *parentClasses.toTypedArray()).also {
|
||||
it.isAbstract = spec.isAbstract
|
||||
it.isClosed = spec.isClosed
|
||||
it.instanceConstructor = constructorCode
|
||||
it.constructorMeta = spec.constructorArgs
|
||||
it.constructorMeta = evaluatedConstructorArgs
|
||||
for (i in parentClasses.indices) {
|
||||
val argsList = spec.baseSpecs[i].args
|
||||
if (argsList != null) it.directParentArgs[parentClasses[i]] = argsList
|
||||
}
|
||||
spec.constructorArgs?.params?.forEach { p ->
|
||||
evaluatedConstructorArgs?.params?.forEach { p ->
|
||||
if (p.accessType != null) {
|
||||
it.createField(
|
||||
p.name,
|
||||
@@ -181,6 +195,7 @@ internal suspend fun executeClassDecl(
|
||||
pos = Pos.builtIn,
|
||||
isTransient = p.isTransient,
|
||||
type = ObjRecord.Type.ConstructorField,
|
||||
annotations = p.annotations,
|
||||
fieldId = spec.constructorFieldIds?.get(p.name)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ class ClassInstanceFieldDeclStatement(
|
||||
val isClosed: Boolean,
|
||||
val isOverride: Boolean,
|
||||
val isTransient: Boolean,
|
||||
val annotationSpecs: List<ParsedDeclAnnotation> = emptyList(),
|
||||
val fieldId: Int?,
|
||||
val initStatement: Statement?,
|
||||
override val pos: Pos,
|
||||
@@ -56,6 +57,7 @@ class ClassInstancePropertyDeclStatement(
|
||||
val isClosed: Boolean,
|
||||
val isOverride: Boolean,
|
||||
val isTransient: Boolean,
|
||||
val annotationSpecs: List<ParsedDeclAnnotation> = emptyList(),
|
||||
val prop: ObjProperty,
|
||||
val methodId: Int?,
|
||||
val initStatement: Statement?,
|
||||
@@ -75,6 +77,7 @@ class ClassInstanceDelegatedDeclStatement(
|
||||
val isClosed: Boolean,
|
||||
val isOverride: Boolean,
|
||||
val isTransient: Boolean,
|
||||
val annotationSpecs: List<ParsedDeclAnnotation> = emptyList(),
|
||||
val methodId: Int?,
|
||||
val initStatement: Statement?,
|
||||
override val pos: Pos,
|
||||
|
||||
@@ -32,6 +32,7 @@ class ClassStaticFieldInitStatement(
|
||||
val initializer: Statement?,
|
||||
val isDelegated: Boolean,
|
||||
val isTransient: Boolean,
|
||||
val annotationSpecs: List<ParsedDeclAnnotation> = emptyList(),
|
||||
private val startPos: Pos,
|
||||
) : Statement() {
|
||||
override val pos: Pos = startPos
|
||||
@@ -39,6 +40,7 @@ class ClassStaticFieldInitStatement(
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
val initValue = initializer?.let { execBytecodeOnly(scope, it, "class static field init") }?.byValueCopy()
|
||||
?: ObjNull
|
||||
val annotations = annotationSpecs.evaluateDeclAnnotations(scope)
|
||||
val cls = scope.thisObj as? ObjClass
|
||||
?: scope.raiseIllegalState("static field init requires class scope")
|
||||
return if (isDelegated) {
|
||||
@@ -61,7 +63,8 @@ class ClassStaticFieldInitStatement(
|
||||
writeVisibility,
|
||||
startPos,
|
||||
isTransient = isTransient,
|
||||
type = ObjRecord.Type.Delegated
|
||||
type = ObjRecord.Type.Delegated,
|
||||
annotations = annotations
|
||||
).apply {
|
||||
delegate = finalDelegate
|
||||
}
|
||||
@@ -72,7 +75,8 @@ class ClassStaticFieldInitStatement(
|
||||
visibility,
|
||||
writeVisibility,
|
||||
recordType = ObjRecord.Type.Delegated,
|
||||
isTransient = isTransient
|
||||
isTransient = isTransient,
|
||||
annotations = annotations
|
||||
).apply {
|
||||
delegate = finalDelegate
|
||||
}
|
||||
@@ -85,7 +89,8 @@ class ClassStaticFieldInitStatement(
|
||||
visibility,
|
||||
writeVisibility,
|
||||
startPos,
|
||||
isTransient = isTransient
|
||||
isTransient = isTransient,
|
||||
annotations = annotations
|
||||
)
|
||||
scope.addItem(
|
||||
name,
|
||||
@@ -94,7 +99,8 @@ class ClassStaticFieldInitStatement(
|
||||
visibility,
|
||||
writeVisibility,
|
||||
recordType = ObjRecord.Type.Field,
|
||||
isTransient = isTransient
|
||||
isTransient = isTransient,
|
||||
annotations = annotations
|
||||
)
|
||||
initValue
|
||||
}
|
||||
|
||||
@@ -2014,6 +2014,7 @@ class Compiler(
|
||||
|
||||
private var lastAnnotation: (suspend (Scope, ObjString, Statement) -> Statement)? = null
|
||||
private var isTransientFlag: Boolean = false
|
||||
private val pendingDeclAnnotations: MutableList<ParsedDeclAnnotation> = mutableListOf()
|
||||
private var lastLabel: String? = null
|
||||
private val strictSlotRefs: Boolean = settings.strictSlotRefs
|
||||
private val allowUnresolvedRefs: Boolean = settings.allowUnresolvedRefs
|
||||
@@ -2689,6 +2690,7 @@ class Compiler(
|
||||
lastAnnotation = null
|
||||
lastLabel = null
|
||||
isTransientFlag = false
|
||||
pendingDeclAnnotations.clear()
|
||||
while (true) {
|
||||
val t = cc.next()
|
||||
return when (t.type) {
|
||||
@@ -2706,15 +2708,17 @@ class Compiler(
|
||||
}
|
||||
|
||||
Token.Type.ATLABEL -> {
|
||||
val label = t.value
|
||||
if (label == "Transient") {
|
||||
val parsedAnnotation = parseDeclAnnotation(t)
|
||||
if (parsedAnnotation.name == "Transient") {
|
||||
isTransientFlag = true
|
||||
continue
|
||||
}
|
||||
if (cc.peekNextNonWhitespace().type == Token.Type.LBRACE) {
|
||||
lastLabel = label
|
||||
lastLabel = parsedAnnotation.name
|
||||
}
|
||||
pendingDeclAnnotations += parsedAnnotation
|
||||
if (parsedAnnotation.name != "Transient") {
|
||||
lastAnnotation = parsedAnnotation.toStatementAnnotation()
|
||||
}
|
||||
lastAnnotation = parseAnnotation(t)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -4033,11 +4037,17 @@ class Compiler(
|
||||
|
||||
Token.Type.ID, Token.Type.ATLABEL -> {
|
||||
var isTransient = false
|
||||
if (t.type == Token.Type.ATLABEL) {
|
||||
if (t.value == "Transient") {
|
||||
val annotationSpecs = mutableListOf<ParsedDeclAnnotation>()
|
||||
while (t.type == Token.Type.ATLABEL) {
|
||||
val spec = parseDeclAnnotation(t)
|
||||
annotationSpecs += spec
|
||||
if (spec.name == "Transient") {
|
||||
isTransient = true
|
||||
t = cc.next()
|
||||
} else throw ScriptError(t.pos, "Unexpected label in argument list")
|
||||
}
|
||||
t = cc.next()
|
||||
}
|
||||
if (annotationSpecs.isNotEmpty() && !isClassDeclaration) {
|
||||
throw ScriptError(t.pos, "parameter annotations are currently supported only on class constructor parameters")
|
||||
}
|
||||
|
||||
// visibility
|
||||
@@ -4114,7 +4124,8 @@ class Compiler(
|
||||
defaultValue,
|
||||
effectiveAccess,
|
||||
visibility,
|
||||
isTransient
|
||||
isTransient,
|
||||
annotationSpecs = annotationSpecs
|
||||
)
|
||||
|
||||
// important: valid argument list continues with ',' and ends with '->' or ')'
|
||||
@@ -7120,19 +7131,25 @@ class Compiler(
|
||||
return parseNumberOrNull(isPlus) ?: throw ScriptError(cc.currentPos(), "Expecting number")
|
||||
}
|
||||
|
||||
suspend fun parseAnnotation(t: Token): (suspend (Scope, ObjString, Statement) -> Statement) {
|
||||
private suspend fun parseDeclAnnotation(t: Token): ParsedDeclAnnotation {
|
||||
val extraArgs = parseArgsOrNull()
|
||||
resolutionSink?.reference(t.value, t.pos)
|
||||
// println("annotation ${t.value}: args: $extraArgs")
|
||||
return { scope, name, body ->
|
||||
val extras = extraArgs?.first?.toArguments(scope, extraArgs.second)?.list
|
||||
val required = listOf(name, body)
|
||||
val args = extras?.let { required + it } ?: required
|
||||
val fn = scope.get(t.value)?.value ?: scope.raiseSymbolNotFound("annotation not found: ${t.value}")
|
||||
if (fn !is Statement) scope.raiseIllegalArgument("annotation must be callable, got ${fn.objClass}")
|
||||
(fn.execute(scope.createChildScope(Arguments(args))) as? Statement)
|
||||
?: scope.raiseClassCastError("function annotation must return callable")
|
||||
}
|
||||
val compiledArgs = extraArgs?.first?.map { arg ->
|
||||
val value = arg.value
|
||||
arg.copy(
|
||||
value = if (value is Statement) wrapBytecode(value) else value
|
||||
)
|
||||
} ?: emptyList()
|
||||
return ParsedDeclAnnotation(
|
||||
name = t.value,
|
||||
args = compiledArgs,
|
||||
tailBlockMode = extraArgs?.second ?: false,
|
||||
pos = t.pos
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun parseAnnotation(t: Token): (suspend (Scope, ObjString, Statement) -> Statement) {
|
||||
return parseDeclAnnotation(t).toStatementAnnotation()
|
||||
}
|
||||
|
||||
suspend fun parseArgsOrNull(): Pair<List<ParsedArgument>, Boolean>? =
|
||||
@@ -9232,6 +9249,8 @@ class Compiler(
|
||||
isTransient: Boolean = isTransientFlag
|
||||
): Statement {
|
||||
isTransientFlag = false
|
||||
val declarationAnnotationSpecs = pendingDeclAnnotations.toList()
|
||||
pendingDeclAnnotations.clear()
|
||||
val actualExtern = isExtern || (codeContexts.lastOrNull() as? CodeContext.ClassBody)?.isExtern == true
|
||||
var start = cc.currentPos()
|
||||
var extTypeName: String? = null
|
||||
@@ -9700,6 +9719,7 @@ class Compiler(
|
||||
isClosed = isClosed,
|
||||
isOverride = isOverride,
|
||||
isTransient = isTransient,
|
||||
annotations = emptyList(),
|
||||
accessTypeLabel = "Callable",
|
||||
initializer = initExpr,
|
||||
pos = start
|
||||
@@ -10251,6 +10271,8 @@ class Compiler(
|
||||
isTransient: Boolean = isTransientFlag
|
||||
): Statement {
|
||||
isTransientFlag = false
|
||||
val declarationAnnotationSpecs = pendingDeclAnnotations.toList()
|
||||
pendingDeclAnnotations.clear()
|
||||
val actualExtern = isExtern || (codeContexts.lastOrNull() as? CodeContext.ClassBody)?.isExtern == true
|
||||
val markStart = cc.savePos()
|
||||
val nextToken = cc.next()
|
||||
@@ -10643,6 +10665,9 @@ class Compiler(
|
||||
!isStatic &&
|
||||
!isProperty
|
||||
) {
|
||||
if (declarationAnnotationSpecs.isNotEmpty()) {
|
||||
throw ScriptError(start, "declaration annotations are currently supported only on class members")
|
||||
}
|
||||
if (isDelegate) {
|
||||
val initExpr = initialExpression ?: throw ScriptError(start, "Delegate must be initialized")
|
||||
val slotPlan = slotPlanStack.lastOrNull()
|
||||
@@ -10663,6 +10688,9 @@ class Compiler(
|
||||
}
|
||||
|
||||
if (isStatic) {
|
||||
if (extTypeName != null && declarationAnnotationSpecs.isNotEmpty()) {
|
||||
throw ScriptError(start, "declaration annotations are not supported on extension properties")
|
||||
}
|
||||
if (declaringClassNameCaptured != null) {
|
||||
val directRef = unwrapDirectRef(initialExpression)
|
||||
val declClass = resolveTypeDeclObjClass(varTypeDecl)
|
||||
@@ -10685,6 +10713,7 @@ class Compiler(
|
||||
initializer = initialExpression,
|
||||
isDelegated = isDelegate,
|
||||
isTransient = isTransient,
|
||||
annotationSpecs = declarationAnnotationSpecs,
|
||||
startPos = start
|
||||
)
|
||||
return NopStatement
|
||||
@@ -10847,6 +10876,9 @@ class Compiler(
|
||||
}
|
||||
|
||||
if (extTypeName != null) {
|
||||
if (declarationAnnotationSpecs.isNotEmpty()) {
|
||||
throw ScriptError(start, "declaration annotations are not supported on extension properties")
|
||||
}
|
||||
declareLocalName(extensionPropertyGetterName(extTypeName, name), isMutable = false)
|
||||
if (setter != null) {
|
||||
declareLocalName(extensionPropertySetterName(extTypeName, name), isMutable = false)
|
||||
@@ -10883,6 +10915,7 @@ class Compiler(
|
||||
isClosed = isClosed,
|
||||
isOverride = isOverride,
|
||||
isTransient = isTransient,
|
||||
annotations = emptyList(),
|
||||
accessTypeLabel = accessType,
|
||||
initializer = initExpr,
|
||||
pos = start
|
||||
@@ -10898,6 +10931,7 @@ class Compiler(
|
||||
isClosed = isClosed,
|
||||
isOverride = isOverride,
|
||||
isTransient = isTransient,
|
||||
annotationSpecs = declarationAnnotationSpecs,
|
||||
methodId = memberMethodId,
|
||||
initStatement = initStmt,
|
||||
pos = start
|
||||
@@ -10919,6 +10953,7 @@ class Compiler(
|
||||
isClosed = isClosed,
|
||||
isOverride = isOverride,
|
||||
isTransient = isTransient,
|
||||
annotations = emptyList(),
|
||||
prop = prop,
|
||||
pos = start
|
||||
)
|
||||
@@ -10933,6 +10968,7 @@ class Compiler(
|
||||
isClosed = isClosed,
|
||||
isOverride = isOverride,
|
||||
isTransient = isTransient,
|
||||
annotationSpecs = declarationAnnotationSpecs,
|
||||
prop = prop,
|
||||
methodId = memberMethodId,
|
||||
initStatement = initStmt,
|
||||
@@ -10950,6 +10986,7 @@ class Compiler(
|
||||
isClosed = isClosed,
|
||||
isOverride = isOverride,
|
||||
isTransient = isTransient,
|
||||
annotations = emptyList(),
|
||||
isLateInitVal = isLateInitVal,
|
||||
initializer = initialExpression,
|
||||
pos = start
|
||||
@@ -10966,6 +11003,7 @@ class Compiler(
|
||||
isClosed = isClosed,
|
||||
isOverride = isOverride,
|
||||
isTransient = isTransient,
|
||||
annotationSpecs = declarationAnnotationSpecs,
|
||||
fieldId = memberFieldId,
|
||||
initStatement = initStmt,
|
||||
pos = start
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
import net.sergeych.lyng.bytecode.BytecodeStatement
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
import net.sergeych.lyng.obj.ObjIterable
|
||||
import net.sergeych.lyng.obj.ObjList
|
||||
import net.sergeych.lyng.obj.ObjMap
|
||||
import net.sergeych.lyng.obj.ObjString
|
||||
|
||||
/**
|
||||
* Preserved declaration annotation evaluated at declaration-creation time.
|
||||
*/
|
||||
data class DeclAnnotation(
|
||||
val name: String,
|
||||
val positional: List<Obj> = emptyList(),
|
||||
val named: Map<String, Obj> = emptyMap(),
|
||||
)
|
||||
|
||||
/**
|
||||
* Parsed declaration annotation awaiting declaration-time evaluation.
|
||||
*/
|
||||
data class ParsedDeclAnnotation(
|
||||
val name: String,
|
||||
val args: List<ParsedArgument> = emptyList(),
|
||||
val tailBlockMode: Boolean = false,
|
||||
val pos: Pos = Pos.builtIn,
|
||||
) {
|
||||
suspend fun evaluate(scope: Scope): DeclAnnotation {
|
||||
val resolved = evaluateDeclAnnotationArguments(scope, args, tailBlockMode)
|
||||
return DeclAnnotation(name, resolved.list, resolved.named)
|
||||
}
|
||||
|
||||
fun toStatementAnnotation(): suspend (Scope, ObjString, Statement) -> Statement = { scope, declName, body ->
|
||||
val extras = args.toArguments(scope, tailBlockMode).list
|
||||
val required = listOf(declName, body)
|
||||
val callArgs = if (extras.isEmpty()) required else required + extras
|
||||
val fn = scope.get(name)?.value ?: scope.raiseSymbolNotFound("annotation not found: $name")
|
||||
if (fn !is Statement) scope.raiseIllegalArgument("annotation must be callable, got ${fn.objClass}")
|
||||
(fn.execute(scope.createChildScope(Arguments(callArgs))) as? Statement)
|
||||
?: scope.raiseClassCastError("function annotation must return callable")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun Iterable<ParsedDeclAnnotation>.evaluateDeclAnnotations(scope: Scope): List<DeclAnnotation> {
|
||||
val result = mutableListOf<DeclAnnotation>()
|
||||
for (spec in this) {
|
||||
result += spec.evaluate(scope)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private suspend fun evaluateDeclAnnotationArguments(
|
||||
scope: Scope,
|
||||
args: List<ParsedArgument>,
|
||||
tailBlockMode: Boolean,
|
||||
): Arguments {
|
||||
suspend fun eval(value: Obj): Obj = when (value) {
|
||||
is BytecodeBodyProvider -> (value.bytecodeBody() ?: scope.raiseIllegalState("annotation argument requires bytecode body")).execute(scope)
|
||||
is Statement -> BytecodeStatement.wrap(value, "@annotation", allowLocalSlots = true).execute(scope)
|
||||
else -> value.callOn(scope)
|
||||
}
|
||||
|
||||
val resolved = ArrayList<ParsedArgument>(args.size)
|
||||
for (arg in args) {
|
||||
resolved += arg.copy(value = eval(arg.value))
|
||||
}
|
||||
|
||||
val positional: MutableList<Obj> = mutableListOf()
|
||||
var named: MutableMap<String, Obj>? = null
|
||||
var namedSeen = false
|
||||
for ((idx, x) in resolved.withIndex()) {
|
||||
if (x.name != null) {
|
||||
if (named == null) named = linkedMapOf()
|
||||
if (named.containsKey(x.name)) scope.raiseIllegalArgument("argument '${x.name}' is already set")
|
||||
named[x.name] = x.value
|
||||
namedSeen = true
|
||||
continue
|
||||
}
|
||||
val value = x.value
|
||||
if (x.isSplat) {
|
||||
when {
|
||||
value is ObjMap -> {
|
||||
if (named == null) named = linkedMapOf()
|
||||
for ((k, v) in value.map) {
|
||||
if (k !is ObjString) scope.raiseIllegalArgument("named splat expects a Map with string keys")
|
||||
val key = k.value
|
||||
if (named.containsKey(key)) scope.raiseIllegalArgument("argument '$key' is already set")
|
||||
named[key] = v
|
||||
}
|
||||
namedSeen = true
|
||||
}
|
||||
value is ObjList -> {
|
||||
if (namedSeen) scope.raiseIllegalArgument("positional splat cannot follow named arguments")
|
||||
positional.addAll(value.list)
|
||||
}
|
||||
value.isInstanceOf(ObjIterable) -> {
|
||||
if (namedSeen) scope.raiseIllegalArgument("positional splat cannot follow named arguments")
|
||||
val iterable = value.invokeInstanceMethod(scope, "toList") as ObjList
|
||||
positional.addAll(iterable.list)
|
||||
}
|
||||
else -> scope.raiseClassCastError("expected list of objects for splat argument")
|
||||
}
|
||||
} else {
|
||||
val isLast = idx == resolved.size - 1
|
||||
if (namedSeen && !(isLast && tailBlockMode)) {
|
||||
scope.raiseIllegalArgument("positional argument cannot follow named arguments")
|
||||
}
|
||||
positional.add(value)
|
||||
}
|
||||
}
|
||||
return Arguments(positional, tailBlockMode, named ?: emptyMap())
|
||||
}
|
||||
@@ -33,6 +33,7 @@ class InstanceFieldInitStatement(
|
||||
val isClosed: Boolean,
|
||||
val isOverride: Boolean,
|
||||
val isTransient: Boolean,
|
||||
val annotations: List<DeclAnnotation> = emptyList(),
|
||||
val isLateInitVal: Boolean,
|
||||
val initializer: Statement?,
|
||||
override val pos: Pos,
|
||||
@@ -50,7 +51,8 @@ class InstanceFieldInitStatement(
|
||||
isAbstract = isAbstract,
|
||||
isClosed = isClosed,
|
||||
isOverride = isOverride,
|
||||
isTransient = isTransient
|
||||
isTransient = isTransient,
|
||||
annotations = annotations
|
||||
)
|
||||
return ObjVoid
|
||||
}
|
||||
@@ -74,6 +76,7 @@ class InstancePropertyInitStatement(
|
||||
val isClosed: Boolean,
|
||||
val isOverride: Boolean,
|
||||
val isTransient: Boolean,
|
||||
val annotations: List<DeclAnnotation> = emptyList(),
|
||||
val prop: ObjProperty,
|
||||
override val pos: Pos,
|
||||
) : Statement() {
|
||||
@@ -88,7 +91,8 @@ class InstancePropertyInitStatement(
|
||||
isAbstract = isAbstract,
|
||||
isClosed = isClosed,
|
||||
isOverride = isOverride,
|
||||
isTransient = isTransient
|
||||
isTransient = isTransient,
|
||||
annotations = annotations
|
||||
)
|
||||
return ObjVoid
|
||||
}
|
||||
@@ -104,6 +108,7 @@ class InstanceDelegatedInitStatement(
|
||||
val isClosed: Boolean,
|
||||
val isOverride: Boolean,
|
||||
val isTransient: Boolean,
|
||||
val annotations: List<DeclAnnotation> = emptyList(),
|
||||
val accessTypeLabel: String,
|
||||
val initializer: Statement,
|
||||
override val pos: Pos,
|
||||
@@ -130,7 +135,8 @@ class InstanceDelegatedInitStatement(
|
||||
isAbstract = isAbstract,
|
||||
isClosed = isClosed,
|
||||
isOverride = isOverride,
|
||||
isTransient = isTransient
|
||||
isTransient = isTransient,
|
||||
annotations = annotations
|
||||
).apply {
|
||||
delegate = finalDelegate
|
||||
}
|
||||
|
||||
@@ -718,6 +718,7 @@ open class Scope(
|
||||
isTransient: Boolean = false,
|
||||
callSignature: CallSignature? = null,
|
||||
typeDecl: TypeDecl? = null,
|
||||
annotations: List<DeclAnnotation> = emptyList(),
|
||||
fieldId: Int? = null,
|
||||
methodId: Int? = null
|
||||
): ObjRecord {
|
||||
@@ -731,6 +732,7 @@ open class Scope(
|
||||
isTransient = isTransient,
|
||||
callSignature = callSignature,
|
||||
typeDecl = typeDecl,
|
||||
annotations = annotations,
|
||||
memberName = name,
|
||||
fieldId = fieldId,
|
||||
methodId = methodId
|
||||
|
||||
@@ -6360,7 +6360,8 @@ class BytecodeCompiler(
|
||||
isMutable = stmt.isMutable,
|
||||
visibility = stmt.visibility,
|
||||
writeVisibility = stmt.writeVisibility,
|
||||
isTransient = stmt.isTransient
|
||||
isTransient = stmt.isTransient,
|
||||
annotationSpecs = stmt.annotationSpecs
|
||||
)
|
||||
)
|
||||
} else {
|
||||
@@ -6370,7 +6371,8 @@ class BytecodeCompiler(
|
||||
isMutable = stmt.isMutable,
|
||||
visibility = stmt.visibility,
|
||||
writeVisibility = stmt.writeVisibility,
|
||||
isTransient = stmt.isTransient
|
||||
isTransient = stmt.isTransient,
|
||||
annotationSpecs = stmt.annotationSpecs
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -6397,6 +6399,7 @@ class BytecodeCompiler(
|
||||
writeVisibility = stmt.writeVisibility,
|
||||
typeDecl = stmt.typeDecl,
|
||||
isTransient = stmt.isTransient,
|
||||
annotationSpecs = stmt.annotationSpecs,
|
||||
isAbstract = stmt.isAbstract,
|
||||
isClosed = stmt.isClosed,
|
||||
isOverride = stmt.isOverride,
|
||||
@@ -6419,6 +6422,7 @@ class BytecodeCompiler(
|
||||
visibility = stmt.visibility,
|
||||
writeVisibility = stmt.writeVisibility,
|
||||
isTransient = stmt.isTransient,
|
||||
annotationSpecs = stmt.annotationSpecs,
|
||||
isAbstract = stmt.isAbstract,
|
||||
isClosed = stmt.isClosed,
|
||||
isOverride = stmt.isOverride,
|
||||
@@ -6442,6 +6446,7 @@ class BytecodeCompiler(
|
||||
visibility = stmt.visibility,
|
||||
writeVisibility = stmt.writeVisibility,
|
||||
isTransient = stmt.isTransient,
|
||||
annotationSpecs = stmt.annotationSpecs,
|
||||
isAbstract = stmt.isAbstract,
|
||||
isClosed = stmt.isClosed,
|
||||
isOverride = stmt.isOverride,
|
||||
@@ -6475,6 +6480,7 @@ class BytecodeCompiler(
|
||||
visibility = stmt.visibility,
|
||||
writeVisibility = stmt.writeVisibility,
|
||||
isTransient = stmt.isTransient,
|
||||
annotations = stmt.annotations,
|
||||
isAbstract = stmt.isAbstract,
|
||||
isClosed = stmt.isClosed,
|
||||
isOverride = stmt.isOverride
|
||||
@@ -6497,6 +6503,7 @@ class BytecodeCompiler(
|
||||
visibility = stmt.visibility,
|
||||
writeVisibility = stmt.writeVisibility,
|
||||
isTransient = stmt.isTransient,
|
||||
annotations = stmt.annotations,
|
||||
isAbstract = stmt.isAbstract,
|
||||
isClosed = stmt.isClosed,
|
||||
isOverride = stmt.isOverride
|
||||
@@ -6517,6 +6524,7 @@ class BytecodeCompiler(
|
||||
visibility = stmt.visibility,
|
||||
writeVisibility = stmt.writeVisibility,
|
||||
isTransient = stmt.isTransient,
|
||||
annotations = stmt.annotations,
|
||||
isAbstract = stmt.isAbstract,
|
||||
isClosed = stmt.isClosed,
|
||||
isOverride = stmt.isOverride,
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package net.sergeych.lyng.bytecode
|
||||
|
||||
import net.sergeych.lyng.ArgsDeclaration
|
||||
import net.sergeych.lyng.ParsedDeclAnnotation
|
||||
import net.sergeych.lyng.Pos
|
||||
import net.sergeych.lyng.TypeDecl
|
||||
import net.sergeych.lyng.Visibility
|
||||
@@ -85,6 +86,7 @@ sealed class BytecodeConst {
|
||||
val visibility: Visibility,
|
||||
val writeVisibility: Visibility?,
|
||||
val isTransient: Boolean,
|
||||
val annotationSpecs: List<ParsedDeclAnnotation>,
|
||||
) : BytecodeConst()
|
||||
data class ClassDelegatedDecl(
|
||||
val name: String,
|
||||
@@ -92,6 +94,7 @@ sealed class BytecodeConst {
|
||||
val visibility: Visibility,
|
||||
val writeVisibility: Visibility?,
|
||||
val isTransient: Boolean,
|
||||
val annotationSpecs: List<ParsedDeclAnnotation>,
|
||||
) : BytecodeConst()
|
||||
data class ClassInstanceInitDecl(
|
||||
val initStatement: Obj,
|
||||
@@ -103,6 +106,7 @@ sealed class BytecodeConst {
|
||||
val writeVisibility: Visibility?,
|
||||
val typeDecl: TypeDecl?,
|
||||
val isTransient: Boolean,
|
||||
val annotationSpecs: List<ParsedDeclAnnotation>,
|
||||
val isAbstract: Boolean,
|
||||
val isClosed: Boolean,
|
||||
val isOverride: Boolean,
|
||||
@@ -116,6 +120,7 @@ sealed class BytecodeConst {
|
||||
val visibility: Visibility,
|
||||
val writeVisibility: Visibility?,
|
||||
val isTransient: Boolean,
|
||||
val annotationSpecs: List<ParsedDeclAnnotation>,
|
||||
val isAbstract: Boolean,
|
||||
val isClosed: Boolean,
|
||||
val isOverride: Boolean,
|
||||
@@ -130,6 +135,7 @@ sealed class BytecodeConst {
|
||||
val visibility: Visibility,
|
||||
val writeVisibility: Visibility?,
|
||||
val isTransient: Boolean,
|
||||
val annotationSpecs: List<ParsedDeclAnnotation>,
|
||||
val isAbstract: Boolean,
|
||||
val isClosed: Boolean,
|
||||
val isOverride: Boolean,
|
||||
@@ -143,6 +149,7 @@ sealed class BytecodeConst {
|
||||
val visibility: Visibility,
|
||||
val writeVisibility: Visibility?,
|
||||
val isTransient: Boolean,
|
||||
val annotations: List<net.sergeych.lyng.DeclAnnotation>,
|
||||
val isAbstract: Boolean,
|
||||
val isClosed: Boolean,
|
||||
val isOverride: Boolean,
|
||||
@@ -153,6 +160,7 @@ sealed class BytecodeConst {
|
||||
val visibility: Visibility,
|
||||
val writeVisibility: Visibility?,
|
||||
val isTransient: Boolean,
|
||||
val annotations: List<net.sergeych.lyng.DeclAnnotation>,
|
||||
val isAbstract: Boolean,
|
||||
val isClosed: Boolean,
|
||||
val isOverride: Boolean,
|
||||
@@ -164,6 +172,7 @@ sealed class BytecodeConst {
|
||||
val visibility: Visibility,
|
||||
val writeVisibility: Visibility?,
|
||||
val isTransient: Boolean,
|
||||
val annotations: List<net.sergeych.lyng.DeclAnnotation>,
|
||||
val isAbstract: Boolean,
|
||||
val isClosed: Boolean,
|
||||
val isOverride: Boolean,
|
||||
|
||||
@@ -365,6 +365,7 @@ class BytecodeStatement private constructor(
|
||||
stmt.initializer?.let { unwrapDeep(it) },
|
||||
stmt.isDelegated,
|
||||
stmt.isTransient,
|
||||
stmt.annotationSpecs,
|
||||
stmt.pos
|
||||
)
|
||||
}
|
||||
@@ -385,6 +386,7 @@ class BytecodeStatement private constructor(
|
||||
stmt.isClosed,
|
||||
stmt.isOverride,
|
||||
stmt.isTransient,
|
||||
stmt.annotationSpecs,
|
||||
stmt.fieldId,
|
||||
stmt.initStatement?.let { unwrapDeep(it) },
|
||||
stmt.pos
|
||||
@@ -400,6 +402,7 @@ class BytecodeStatement private constructor(
|
||||
stmt.isClosed,
|
||||
stmt.isOverride,
|
||||
stmt.isTransient,
|
||||
stmt.annotationSpecs,
|
||||
stmt.prop,
|
||||
stmt.methodId,
|
||||
stmt.initStatement?.let { unwrapDeep(it) },
|
||||
@@ -416,6 +419,7 @@ class BytecodeStatement private constructor(
|
||||
stmt.isClosed,
|
||||
stmt.isOverride,
|
||||
stmt.isTransient,
|
||||
stmt.annotationSpecs,
|
||||
stmt.methodId,
|
||||
stmt.initStatement?.let { unwrapDeep(it) },
|
||||
stmt.pos
|
||||
@@ -431,6 +435,7 @@ class BytecodeStatement private constructor(
|
||||
stmt.isClosed,
|
||||
stmt.isOverride,
|
||||
stmt.isTransient,
|
||||
stmt.annotations,
|
||||
stmt.isLateInitVal,
|
||||
stmt.initializer?.let { unwrapDeep(it) },
|
||||
stmt.pos
|
||||
@@ -446,6 +451,7 @@ class BytecodeStatement private constructor(
|
||||
stmt.isClosed,
|
||||
stmt.isOverride,
|
||||
stmt.isTransient,
|
||||
stmt.annotations,
|
||||
stmt.prop,
|
||||
stmt.pos
|
||||
)
|
||||
@@ -461,6 +467,7 @@ class BytecodeStatement private constructor(
|
||||
stmt.isClosed,
|
||||
stmt.isOverride,
|
||||
stmt.isTransient,
|
||||
stmt.annotations,
|
||||
stmt.accessTypeLabel,
|
||||
unwrapDeep(stmt.initializer),
|
||||
stmt.pos
|
||||
|
||||
@@ -2805,6 +2805,7 @@ class CmdDeclClassField(internal val constId: Int, internal val slot: Int) : Cmd
|
||||
val decl = frame.fn.constants[constId] as? BytecodeConst.ClassFieldDecl
|
||||
?: error("DECL_CLASS_FIELD expects ClassFieldDecl at $constId")
|
||||
val scope = frame.ensureScope()
|
||||
val annotations = decl.annotationSpecs.evaluateDeclAnnotations(scope)
|
||||
val cls = scope.thisObj as? ObjClass
|
||||
?: scope.raiseIllegalState("class field init requires class scope")
|
||||
val value = frame.slotToObj(slot).byValueCopy()
|
||||
@@ -2815,7 +2816,8 @@ class CmdDeclClassField(internal val constId: Int, internal val slot: Int) : Cmd
|
||||
decl.visibility,
|
||||
decl.writeVisibility,
|
||||
Pos.builtIn,
|
||||
isTransient = decl.isTransient
|
||||
isTransient = decl.isTransient,
|
||||
annotations = annotations
|
||||
)
|
||||
scope.addItem(
|
||||
decl.name,
|
||||
@@ -2824,7 +2826,8 @@ class CmdDeclClassField(internal val constId: Int, internal val slot: Int) : Cmd
|
||||
decl.visibility,
|
||||
decl.writeVisibility,
|
||||
recordType = ObjRecord.Type.Field,
|
||||
isTransient = decl.isTransient
|
||||
isTransient = decl.isTransient,
|
||||
annotations = annotations
|
||||
)
|
||||
return
|
||||
}
|
||||
@@ -2835,6 +2838,7 @@ class CmdDeclClassDelegated(internal val constId: Int, internal val slot: Int) :
|
||||
val decl = frame.fn.constants[constId] as? BytecodeConst.ClassDelegatedDecl
|
||||
?: error("DECL_CLASS_DELEGATED expects ClassDelegatedDecl at $constId")
|
||||
val scope = frame.ensureScope()
|
||||
val annotations = decl.annotationSpecs.evaluateDeclAnnotations(scope)
|
||||
val cls = scope.thisObj as? ObjClass
|
||||
?: scope.raiseIllegalState("class delegated init requires class scope")
|
||||
val initValue = frame.slotToObj(slot)
|
||||
@@ -2857,7 +2861,8 @@ class CmdDeclClassDelegated(internal val constId: Int, internal val slot: Int) :
|
||||
decl.writeVisibility,
|
||||
Pos.builtIn,
|
||||
isTransient = decl.isTransient,
|
||||
type = ObjRecord.Type.Delegated
|
||||
type = ObjRecord.Type.Delegated,
|
||||
annotations = annotations
|
||||
).apply {
|
||||
delegate = finalDelegate
|
||||
}
|
||||
@@ -2868,7 +2873,8 @@ class CmdDeclClassDelegated(internal val constId: Int, internal val slot: Int) :
|
||||
decl.visibility,
|
||||
decl.writeVisibility,
|
||||
recordType = ObjRecord.Type.Delegated,
|
||||
isTransient = decl.isTransient
|
||||
isTransient = decl.isTransient,
|
||||
annotations = annotations
|
||||
).apply {
|
||||
delegate = finalDelegate
|
||||
}
|
||||
@@ -2895,6 +2901,7 @@ class CmdDeclClassInstanceField(internal val constId: Int, internal val slot: In
|
||||
val decl = frame.fn.constants[constId] as? BytecodeConst.ClassInstanceFieldDecl
|
||||
?: error("DECL_CLASS_INSTANCE_FIELD expects ClassInstanceFieldDecl at $constId")
|
||||
val scope = frame.ensureScope()
|
||||
val annotations = decl.annotationSpecs.evaluateDeclAnnotations(scope)
|
||||
val cls = scope.thisObj as? ObjClass
|
||||
?: scope.raiseIllegalState("class instance field requires class scope")
|
||||
cls.createField(
|
||||
@@ -2911,7 +2918,8 @@ class CmdDeclClassInstanceField(internal val constId: Int, internal val slot: In
|
||||
isTransient = decl.isTransient,
|
||||
typeDecl = decl.typeDecl,
|
||||
type = ObjRecord.Type.Field,
|
||||
fieldId = decl.fieldId
|
||||
fieldId = decl.fieldId,
|
||||
annotations = annotations
|
||||
)
|
||||
if (!decl.isAbstract) {
|
||||
decl.initStatement?.let { cls.instanceInitializers += it }
|
||||
@@ -2926,6 +2934,7 @@ class CmdDeclClassInstanceProperty(internal val constId: Int, internal val slot:
|
||||
val decl = frame.fn.constants[constId] as? BytecodeConst.ClassInstancePropertyDecl
|
||||
?: error("DECL_CLASS_INSTANCE_PROPERTY expects ClassInstancePropertyDecl at $constId")
|
||||
val scope = frame.ensureScope()
|
||||
val annotations = decl.annotationSpecs.evaluateDeclAnnotations(scope)
|
||||
val cls = scope.thisObj as? ObjClass
|
||||
?: scope.raiseIllegalState("class instance property requires class scope")
|
||||
cls.addProperty(
|
||||
@@ -2938,7 +2947,8 @@ class CmdDeclClassInstanceProperty(internal val constId: Int, internal val slot:
|
||||
isOverride = decl.isOverride,
|
||||
pos = decl.pos,
|
||||
prop = decl.prop,
|
||||
methodId = decl.methodId
|
||||
methodId = decl.methodId,
|
||||
annotations = annotations
|
||||
)
|
||||
if (!decl.isAbstract) {
|
||||
decl.initStatement?.let { cls.instanceInitializers += it }
|
||||
@@ -2953,6 +2963,7 @@ class CmdDeclClassInstanceDelegated(internal val constId: Int, internal val slot
|
||||
val decl = frame.fn.constants[constId] as? BytecodeConst.ClassInstanceDelegatedDecl
|
||||
?: error("DECL_CLASS_INSTANCE_DELEGATED expects ClassInstanceDelegatedDecl at $constId")
|
||||
val scope = frame.ensureScope()
|
||||
val annotations = decl.annotationSpecs.evaluateDeclAnnotations(scope)
|
||||
val cls = scope.thisObj as? ObjClass
|
||||
?: scope.raiseIllegalState("class instance delegated requires class scope")
|
||||
cls.createField(
|
||||
@@ -2968,7 +2979,8 @@ class CmdDeclClassInstanceDelegated(internal val constId: Int, internal val slot
|
||||
isOverride = decl.isOverride,
|
||||
isTransient = decl.isTransient,
|
||||
type = ObjRecord.Type.Delegated,
|
||||
methodId = decl.methodId
|
||||
methodId = decl.methodId,
|
||||
annotations = annotations
|
||||
)
|
||||
if (!decl.isAbstract) {
|
||||
decl.initStatement?.let { cls.instanceInitializers += it }
|
||||
@@ -2994,7 +3006,8 @@ class CmdDeclInstanceField(internal val constId: Int, internal val slot: Int) :
|
||||
isAbstract = decl.isAbstract,
|
||||
isClosed = decl.isClosed,
|
||||
isOverride = decl.isOverride,
|
||||
isTransient = decl.isTransient
|
||||
isTransient = decl.isTransient,
|
||||
annotations = decl.annotations
|
||||
)
|
||||
if (slot >= frame.fn.scopeSlotCount) {
|
||||
val localIndex = slot - frame.fn.scopeSlotCount
|
||||
@@ -3023,7 +3036,8 @@ class CmdDeclInstanceProperty(internal val constId: Int, internal val slot: Int)
|
||||
isAbstract = decl.isAbstract,
|
||||
isClosed = decl.isClosed,
|
||||
isOverride = decl.isOverride,
|
||||
isTransient = decl.isTransient
|
||||
isTransient = decl.isTransient,
|
||||
annotations = decl.annotations
|
||||
)
|
||||
if (slot >= frame.fn.scopeSlotCount) {
|
||||
val localIndex = slot - frame.fn.scopeSlotCount
|
||||
@@ -3076,7 +3090,8 @@ class CmdDeclInstanceDelegated(internal val constId: Int, internal val slot: Int
|
||||
isAbstract = decl.isAbstract,
|
||||
isClosed = decl.isClosed,
|
||||
isOverride = decl.isOverride,
|
||||
isTransient = decl.isTransient
|
||||
isTransient = decl.isTransient,
|
||||
annotations = decl.annotations
|
||||
).apply {
|
||||
delegate = finalDelegate
|
||||
}
|
||||
@@ -3705,9 +3720,26 @@ class CmdGetClassScope(
|
||||
decl = declared
|
||||
break
|
||||
}
|
||||
val resolved = rec ?: scope.raiseSymbolNotFound(name)
|
||||
val declClass = decl ?: cls
|
||||
val resolvedRec = cls.resolveRecord(scope, resolved, name, declClass)
|
||||
val resolvedRec = if (rec != null) {
|
||||
val declClass = decl ?: cls
|
||||
cls.resolveRecord(scope, rec, name, declClass)
|
||||
} else {
|
||||
val metaRec = cls.objClass.getInstanceMemberOrNull(name)
|
||||
if (metaRec == null || metaRec.isAbstract) {
|
||||
scope.raiseSymbolNotFound(name)
|
||||
}
|
||||
val declClass = metaRec.declaringClass ?: cls.objClass
|
||||
val resolved = cls.resolveRecord(scope, metaRec, name, declClass)
|
||||
if (resolved.type == ObjRecord.Type.Fun) {
|
||||
resolved.copy(
|
||||
value = ObjExternCallable.fromBridge {
|
||||
resolved.value.invoke(scope, cls, args, declClass)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
resolved
|
||||
}
|
||||
}
|
||||
val value = resolvedRec.value
|
||||
frame.storeObjResult(dst, value)
|
||||
return
|
||||
|
||||
@@ -28,6 +28,23 @@ import net.sergeych.lynon.LynonType
|
||||
// Simple id generator for class identities (not thread-safe; fine for scripts)
|
||||
private object ClassIdGen { var c: Long = 1L; fun nextId(): Long = c++ }
|
||||
|
||||
private fun DeclAnnotation.toObj(): Obj {
|
||||
val namedArgs = linkedMapOf<Obj, Obj>()
|
||||
for ((k, v) in named) {
|
||||
namedArgs[ObjString(k)] = v
|
||||
}
|
||||
return ObjMap(
|
||||
linkedMapOf(
|
||||
ObjString("name") to ObjString(name),
|
||||
ObjString("positional") to ObjImmutableList(positional),
|
||||
ObjString("named") to ObjMap(namedArgs)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun annotationListObj(annotations: List<DeclAnnotation>): Obj =
|
||||
ObjImmutableList(annotations.map { it.toObj() })
|
||||
|
||||
val ObjClassType by lazy {
|
||||
object : ObjClass("Class") {
|
||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
|
||||
@@ -98,6 +115,30 @@ val ObjClassType by lazy {
|
||||
val rec = cls.getInstanceMemberOrNull(name)
|
||||
rec?.value ?: ObjNull
|
||||
}
|
||||
addFnDoc(
|
||||
name = "getConstructorAnnotations",
|
||||
doc = "Return preserved annotations for a constructor parameter by name as descriptor maps with fields `name`, `positional`, and `named`.",
|
||||
params = listOf(ParamDoc("name", type("lyng.String"))),
|
||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Map"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val cls = thisAs<ObjClass>()
|
||||
val name = requiredArg<ObjString>(0).value
|
||||
val param = cls.constructorMeta?.params?.firstOrNull { it.name == name }
|
||||
annotationListObj(param?.annotations ?: emptyList())
|
||||
}
|
||||
addFnDoc(
|
||||
name = "getMemberAnnotations",
|
||||
doc = "Return preserved annotations for a member by name as descriptor maps with fields `name`, `positional`, and `named`.",
|
||||
params = listOf(ParamDoc("name", type("lyng.String"))),
|
||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Map"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val cls = thisAs<ObjClass>()
|
||||
val name = requiredArg<ObjString>(0).value
|
||||
val rec = cls.getInstanceMemberOrNull(name) ?: cls.classScope?.objects?.get(name)
|
||||
annotationListObj(rec?.annotations ?: emptyList())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -851,6 +892,7 @@ open class ObjClass(
|
||||
methodId: Int? = null,
|
||||
typeDecl: net.sergeych.lyng.TypeDecl? = null,
|
||||
callSignature: net.sergeych.lyng.CallSignature? = null,
|
||||
annotations: List<DeclAnnotation> = emptyList(),
|
||||
): ObjRecord {
|
||||
// Validation of override rules: only for non-system declarations
|
||||
var existing: ObjRecord? = null
|
||||
@@ -953,6 +995,7 @@ open class ObjClass(
|
||||
type = type,
|
||||
callSignature = callSignature,
|
||||
typeDecl = typeDecl,
|
||||
annotations = annotations,
|
||||
memberName = name,
|
||||
fieldId = effectiveFieldId,
|
||||
methodId = effectiveMethodId
|
||||
@@ -979,7 +1022,8 @@ open class ObjClass(
|
||||
type: ObjRecord.Type = ObjRecord.Type.Field,
|
||||
fieldId: Int? = null,
|
||||
methodId: Int? = null,
|
||||
callSignature: net.sergeych.lyng.CallSignature? = null
|
||||
callSignature: net.sergeych.lyng.CallSignature? = null,
|
||||
annotations: List<DeclAnnotation> = emptyList()
|
||||
): ObjRecord {
|
||||
initClassScope()
|
||||
val existing = classScope!!.objects[name]
|
||||
@@ -1021,6 +1065,7 @@ open class ObjClass(
|
||||
recordType = type,
|
||||
isTransient = isTransient,
|
||||
callSignature = callSignature,
|
||||
annotations = annotations,
|
||||
fieldId = effectiveFieldId,
|
||||
methodId = effectiveMethodId
|
||||
)
|
||||
@@ -1067,7 +1112,8 @@ open class ObjClass(
|
||||
isOverride: Boolean = false,
|
||||
pos: Pos = Pos.builtIn,
|
||||
prop: ObjProperty? = null,
|
||||
methodId: Int? = null
|
||||
methodId: Int? = null,
|
||||
annotations: List<DeclAnnotation> = emptyList()
|
||||
) {
|
||||
val g = getter?.let { ObjExternCallable.fromBridge { it() } }
|
||||
val s = setter?.let { ObjExternCallable.fromBridge { it(requiredArg(0)); ObjVoid } }
|
||||
@@ -1076,7 +1122,8 @@ open class ObjClass(
|
||||
name, finalProp, false, visibility, writeVisibility, pos, declaringClass,
|
||||
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
|
||||
type = ObjRecord.Type.Property,
|
||||
methodId = methodId
|
||||
methodId = methodId,
|
||||
annotations = annotations
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
import net.sergeych.lyng.DeclAnnotation
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.Visibility
|
||||
|
||||
@@ -40,6 +41,7 @@ data class ObjRecord(
|
||||
var receiver: Obj? = null,
|
||||
val callSignature: net.sergeych.lyng.CallSignature? = null,
|
||||
val typeDecl: net.sergeych.lyng.TypeDecl? = null,
|
||||
val annotations: List<DeclAnnotation> = emptyList(),
|
||||
val memberName: String? = null,
|
||||
val fieldId: Int? = null,
|
||||
val methodId: Int? = null,
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.sergeych.lyng.obj.ObjInt
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class DeclAnnotationIntrospectionTest {
|
||||
|
||||
@Test
|
||||
fun classAnnotationQueriesExposeConstructorAndMemberAnnotations() = runTest {
|
||||
val scope = Scope()
|
||||
val result = scope.eval(
|
||||
"""
|
||||
val suffix = "!"
|
||||
|
||||
object Marker
|
||||
|
||||
class Sample(
|
||||
@Transient @Tag(1, label: "ctor", extra: suffix) val x: Int
|
||||
) {
|
||||
@Transient @DbDecodeWith(Marker)
|
||||
var y: Int = 10
|
||||
}
|
||||
|
||||
val ctorAnnotations: ImmutableList<Map<String, Object>> = Sample.getConstructorAnnotations("x")
|
||||
val ctorTag: Map<String, Object> = ctorAnnotations[1]
|
||||
val ctorPositional: ImmutableList<Object> = ctorTag["positional"] as ImmutableList<Object>
|
||||
val ctorNamed: Map<String, Object> = ctorTag["named"] as Map<String, Object>
|
||||
assertEquals(2, ctorAnnotations.size)
|
||||
assertEquals("Transient", ctorAnnotations[0]["name"])
|
||||
assertEquals("Tag", ctorTag["name"])
|
||||
assertEquals(1, ctorPositional[0])
|
||||
assertEquals("ctor", ctorNamed["label"])
|
||||
assertEquals("!", ctorNamed["extra"])
|
||||
|
||||
val memberAnnotations: ImmutableList<Map<String, Object>> = Sample.getMemberAnnotations("y")
|
||||
val memberDecodeWith: Map<String, Object> = memberAnnotations[1]
|
||||
val memberPositional: ImmutableList<Object> = memberDecodeWith["positional"] as ImmutableList<Object>
|
||||
assertEquals(2, memberAnnotations.size)
|
||||
assertEquals("Transient", memberAnnotations[0]["name"])
|
||||
assertEquals("DbDecodeWith", memberDecodeWith["name"])
|
||||
assertEquals(Marker, memberPositional[0])
|
||||
|
||||
memberAnnotations.size + ctorAnnotations.size
|
||||
""".trimIndent()
|
||||
) as ObjInt
|
||||
|
||||
assertEquals(4L, result.value)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user