function annotation and some docs for it

This commit is contained in:
sergeych committed 2025-08-12 04:51:26 +03:00
1 parent 804087f16d
commit 9704f18284
7 files changed
+119 -15

No files matched your search

@@ -96,7 +96,10 @@ class Compiler(
return result.toString()
}
private var lastAnnotation: (suspend (Scope, ObjString,Statement) -> Statement)? = null
private suspend fun parseStatement(braceMeansLambda: Boolean = false): Statement? {
lastAnnotation = null
while (true) {
val t = cc.next()
return when (t.type) {
@@ -113,6 +116,10 @@ class Compiler(
parseExpression()
}
Token.Type.ATLABEL -> {
lastAnnotation = parseAnnotation(t) ?: throw ScriptError(t.pos, "can't parse annotation")
continue
}
Token.Type.LABEL -> continue
Token.Type.SINLGE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT -> continue
@@ -818,6 +825,26 @@ class Compiler(
return parseNumberOrNull(isPlus) ?: throw ScriptError(cc.currentPos(), "Expecting number")
}
suspend fun parseAnnotation(t: Token): (suspend (Scope, ObjString,Statement)->Statement) {
val extraArgs = parseArgsOrNull()
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.copy(Arguments(args))) as? Statement)
?: scope.raiseClassCastError("function annotation must return callable")
}
}
suspend fun parseArgsOrNull(): Pair<List<ParsedArgument>, Boolean>? =
if( cc.skipNextIf(Token.Type.LPAREN))
parseArgs()
else
null
/**
* Parse keyword-starting statement.
* @return parsed statement or null if, for example. [id] is not among keywords
@@ -1572,6 +1599,9 @@ class Compiler(
throw ScriptError(t.pos, "Expected identifier after 'fun'")
else t.value
val annotation = lastAnnotation
t = cc.next()
// Is extension?
if (t.type == Token.Type.DOT) {
@@ -1622,6 +1652,10 @@ class Compiler(
// we added fn in the context. now we must save closure
// for the function
closure = context
val annotatedFnBody = annotation?.invoke(context, ObjString(name), fnBody)
?: fnBody
extTypeName?.let { typeName ->
// class extension method
val type = context[typeName]?.value ?: context.raiseSymbolNotFound("class $typeName not found")
@@ -1629,17 +1663,17 @@ class Compiler(
type.addFn(name, isOpen = true) {
// ObjInstance has a fixed instance scope, so we need to build a closure
(thisObj as? ObjInstance)?.let { i ->
fnBody.execute(ClosureScope(this, i.instanceScope))
annotatedFnBody.execute(ClosureScope(this, i.instanceScope))
}
// other classes can create one-time scope for this rare case:
?: fnBody.execute(thisObj.autoInstanceScope(this))
?: annotatedFnBody.execute(thisObj.autoInstanceScope(this))
}
}
// regular function/method
?: context.addItem(name, false, fnBody, visibility)
?: context.addItem(name, false, annotatedFnBody, visibility)
// as the function can be called from anywhere, we have
// saved the proper context in the closure
fnBody
annotatedFnBody
}
return if (isStatic) {
currentInitScope += fnCreateStatement
@@ -20,7 +20,7 @@ class CompilerContext(val tokens: List<Token>) {
fun hasNext() = currentIndex < tokens.size
fun hasPrevious() = currentIndex > 0
fun next() =
if( currentIndex < tokens.size ) tokens[currentIndex++]
if (currentIndex < tokens.size) tokens[currentIndex++]
else Token("", tokens.last().pos, Token.Type.EOF)
// throw IllegalStateException("No more tokens")
@@ -61,7 +61,7 @@ class CompilerContext(val tokens: List<Token>) {
*/
fun skipId(name: String): Boolean {
current().let { t ->
if( t.type == Token.Type.ID && t.value == name ) {
if (t.type == Token.Type.ID && t.value == name) {
next()
return true
}
@@ -93,6 +93,20 @@ class CompilerContext(val tokens: List<Token>) {
} else true
}
/**
* If next token is one of these types, skip it.
* @return true if token was found and skipped
*/
fun skipNextIf(vararg types: Token.Type): Boolean {
val t = next()
return if (t.type in types)
true
else {
previous()
false
}
}
@Suppress("unused")
fun skipTokens(vararg tokenTypes: Token.Type) {
while (next().type in tokenTypes) { /**/
@@ -143,19 +157,24 @@ class CompilerContext(val tokens: List<Token>) {
fun matchQualifiers(keyword: String, vararg qualifiers: String): Boolean {
val pos = savePos()
var count = 0
while( count < qualifiers.size) {
while (count < qualifiers.size) {
val t = next()
when(t.type) {
when (t.type) {
Token.Type.ID -> {
if( t.value in qualifiers ) count++
else { restorePos(pos); return false }
if (t.value in qualifiers) count++
else {
restorePos(pos); return false
}
}
Token.Type.MULTILINE_COMMENT, Token.Type.SINLGE_LINE_COMMENT, Token.Type.NEWLINE -> {}
else -> { restorePos(pos); return false }
else -> {
restorePos(pos); return false
}
}
}
val t = next()
if( t.type == Token.Type.ID && t.value == keyword ) {
if (t.type == Token.Type.ID && t.value == keyword) {
return true
} else {
restorePos(pos)
@@ -168,7 +187,7 @@ class CompilerContext(val tokens: List<Token>) {
* Note that [Token.Type.EOF] is not considered a whitespace token.
*/
fun skipWsTokens(): Token {
while( current().type in wstokens ) {
while (current().type in wstokens) {
next()
}
return next()
@@ -2,6 +2,7 @@ package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.ObjNull
import net.sergeych.lyng.obj.ObjVoid
fun String.toSource(name: String = "eval"): Source = Source(name, this)
@@ -28,6 +29,7 @@ abstract class Statement(
abstract suspend fun execute(scope: Scope): Obj
override suspend fun compareTo(scope: Scope, other: Obj): Int {
if( other == ObjNull || other == ObjVoid ) return 1
throw UnsupportedOperationException("not comparable")
}