function annotation and some docs for it
This commit is contained in:
parent
804087f16d
commit
9704f18284
@ -157,6 +157,7 @@ Ready features:
|
||||
- [x] typesafe bit-effective serialization
|
||||
- [x] compression/decompression (integrated in serialization)
|
||||
- [x] dynamic fields
|
||||
- [x] function annotations
|
||||
|
||||
### Under way:
|
||||
|
||||
|
@ -112,6 +112,30 @@ arguments list in almost arbitrary ways. For example:
|
||||
)
|
||||
>>> void
|
||||
|
||||
,
|
||||
# Annotations
|
||||
|
||||
Annotation in Lyng resembles these proposed for Javascript. Annotation is just regular functions that, if used as annotation, are called when defining a function, var, val or class.
|
||||
|
||||
## Function annotation
|
||||
|
||||
When used without params, annotation calls a function with two arguments: actual function name and callable function body. Function annotation __must return callable for the function__, either what it received as a second argument (most often), or something else. Annotation name convention is upper scaled:
|
||||
|
||||
var annotated = false
|
||||
|
||||
// this is annotation function:
|
||||
fun Special(name, body) {
|
||||
assertEquals("foo", name)
|
||||
annotated = true
|
||||
{ body(it) + 100 }
|
||||
}
|
||||
|
||||
@Special
|
||||
fun foo(value) { value + 1 }
|
||||
|
||||
assert(annotated)
|
||||
assertEquals(111, foo( 10 ))
|
||||
>>> void
|
||||
|
||||
Function annotation can have more args specified at call time.
|
||||
|
||||
[parallelism]: parallelism.md
|
||||
|
@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
group = "net.sergeych"
|
||||
version = "0.8.6-SNAPSHOT"
|
||||
version = "0.8.7-SNAPSHOT"
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
|
@ -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
|
||||
|
@ -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) { /**/
|
||||
@ -148,10 +162,15 @@ class CompilerContext(val tokens: List<Token>) {
|
||||
when (t.type) {
|
||||
Token.Type.ID -> {
|
||||
if (t.value in qualifiers) count++
|
||||
else { restorePos(pos); return false }
|
||||
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()
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -2831,4 +2831,28 @@ class ScriptTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tesFunAnnotation() = runTest {
|
||||
eval("""
|
||||
|
||||
val exportedSymbols = Map()
|
||||
|
||||
fun Exported(name, f, overrideName = null) {
|
||||
assertEquals(name, "getBalance")
|
||||
assertEquals(null, overrideName)
|
||||
exportedSymbols[ overrideName ?: name ] = f
|
||||
f
|
||||
}
|
||||
|
||||
@Exported
|
||||
fun getBalance(x = 0) {
|
||||
121 + x
|
||||
}
|
||||
|
||||
assert( exportedSymbols["getBalance"] != null )
|
||||
assertEquals(122, getBalance(1))
|
||||
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user