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] typesafe bit-effective serialization
|
||||||
- [x] compression/decompression (integrated in serialization)
|
- [x] compression/decompression (integrated in serialization)
|
||||||
- [x] dynamic fields
|
- [x] dynamic fields
|
||||||
|
- [x] function annotations
|
||||||
|
|
||||||
### Under way:
|
### Under way:
|
||||||
|
|
||||||
|
@ -112,6 +112,30 @@ arguments list in almost arbitrary ways. For example:
|
|||||||
)
|
)
|
||||||
>>> void
|
>>> 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
|
[parallelism]: parallelism.md
|
||||||
|
@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
|||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
group = "net.sergeych"
|
group = "net.sergeych"
|
||||||
version = "0.8.6-SNAPSHOT"
|
version = "0.8.7-SNAPSHOT"
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
|
@ -96,7 +96,10 @@ class Compiler(
|
|||||||
return result.toString()
|
return result.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var lastAnnotation: (suspend (Scope, ObjString,Statement) -> Statement)? = null
|
||||||
|
|
||||||
private suspend fun parseStatement(braceMeansLambda: Boolean = false): Statement? {
|
private suspend fun parseStatement(braceMeansLambda: Boolean = false): Statement? {
|
||||||
|
lastAnnotation = null
|
||||||
while (true) {
|
while (true) {
|
||||||
val t = cc.next()
|
val t = cc.next()
|
||||||
return when (t.type) {
|
return when (t.type) {
|
||||||
@ -113,6 +116,10 @@ class Compiler(
|
|||||||
parseExpression()
|
parseExpression()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Token.Type.ATLABEL -> {
|
||||||
|
lastAnnotation = parseAnnotation(t) ?: throw ScriptError(t.pos, "can't parse annotation")
|
||||||
|
continue
|
||||||
|
}
|
||||||
Token.Type.LABEL -> continue
|
Token.Type.LABEL -> continue
|
||||||
Token.Type.SINLGE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT -> 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")
|
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.
|
* Parse keyword-starting statement.
|
||||||
* @return parsed statement or null if, for example. [id] is not among keywords
|
* @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'")
|
throw ScriptError(t.pos, "Expected identifier after 'fun'")
|
||||||
else t.value
|
else t.value
|
||||||
|
|
||||||
|
val annotation = lastAnnotation
|
||||||
|
|
||||||
|
|
||||||
t = cc.next()
|
t = cc.next()
|
||||||
// Is extension?
|
// Is extension?
|
||||||
if (t.type == Token.Type.DOT) {
|
if (t.type == Token.Type.DOT) {
|
||||||
@ -1622,6 +1652,10 @@ class Compiler(
|
|||||||
// we added fn in the context. now we must save closure
|
// we added fn in the context. now we must save closure
|
||||||
// for the function
|
// for the function
|
||||||
closure = context
|
closure = context
|
||||||
|
|
||||||
|
val annotatedFnBody = annotation?.invoke(context, ObjString(name), fnBody)
|
||||||
|
?: fnBody
|
||||||
|
|
||||||
extTypeName?.let { typeName ->
|
extTypeName?.let { typeName ->
|
||||||
// class extension method
|
// class extension method
|
||||||
val type = context[typeName]?.value ?: context.raiseSymbolNotFound("class $typeName not found")
|
val type = context[typeName]?.value ?: context.raiseSymbolNotFound("class $typeName not found")
|
||||||
@ -1629,17 +1663,17 @@ class Compiler(
|
|||||||
type.addFn(name, isOpen = true) {
|
type.addFn(name, isOpen = true) {
|
||||||
// ObjInstance has a fixed instance scope, so we need to build a closure
|
// ObjInstance has a fixed instance scope, so we need to build a closure
|
||||||
(thisObj as? ObjInstance)?.let { i ->
|
(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:
|
// other classes can create one-time scope for this rare case:
|
||||||
?: fnBody.execute(thisObj.autoInstanceScope(this))
|
?: annotatedFnBody.execute(thisObj.autoInstanceScope(this))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// regular function/method
|
// 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
|
// as the function can be called from anywhere, we have
|
||||||
// saved the proper context in the closure
|
// saved the proper context in the closure
|
||||||
fnBody
|
annotatedFnBody
|
||||||
}
|
}
|
||||||
return if (isStatic) {
|
return if (isStatic) {
|
||||||
currentInitScope += fnCreateStatement
|
currentInitScope += fnCreateStatement
|
||||||
|
@ -20,7 +20,7 @@ class CompilerContext(val tokens: List<Token>) {
|
|||||||
fun hasNext() = currentIndex < tokens.size
|
fun hasNext() = currentIndex < tokens.size
|
||||||
fun hasPrevious() = currentIndex > 0
|
fun hasPrevious() = currentIndex > 0
|
||||||
fun next() =
|
fun next() =
|
||||||
if( currentIndex < tokens.size ) tokens[currentIndex++]
|
if (currentIndex < tokens.size) tokens[currentIndex++]
|
||||||
else Token("", tokens.last().pos, Token.Type.EOF)
|
else Token("", tokens.last().pos, Token.Type.EOF)
|
||||||
// throw IllegalStateException("No more tokens")
|
// throw IllegalStateException("No more tokens")
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ class CompilerContext(val tokens: List<Token>) {
|
|||||||
*/
|
*/
|
||||||
fun skipId(name: String): Boolean {
|
fun skipId(name: String): Boolean {
|
||||||
current().let { t ->
|
current().let { t ->
|
||||||
if( t.type == Token.Type.ID && t.value == name ) {
|
if (t.type == Token.Type.ID && t.value == name) {
|
||||||
next()
|
next()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -93,6 +93,20 @@ class CompilerContext(val tokens: List<Token>) {
|
|||||||
} else true
|
} 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")
|
@Suppress("unused")
|
||||||
fun skipTokens(vararg tokenTypes: Token.Type) {
|
fun skipTokens(vararg tokenTypes: Token.Type) {
|
||||||
while (next().type in tokenTypes) { /**/
|
while (next().type in tokenTypes) { /**/
|
||||||
@ -143,19 +157,24 @@ class CompilerContext(val tokens: List<Token>) {
|
|||||||
fun matchQualifiers(keyword: String, vararg qualifiers: String): Boolean {
|
fun matchQualifiers(keyword: String, vararg qualifiers: String): Boolean {
|
||||||
val pos = savePos()
|
val pos = savePos()
|
||||||
var count = 0
|
var count = 0
|
||||||
while( count < qualifiers.size) {
|
while (count < qualifiers.size) {
|
||||||
val t = next()
|
val t = next()
|
||||||
when(t.type) {
|
when (t.type) {
|
||||||
Token.Type.ID -> {
|
Token.Type.ID -> {
|
||||||
if( t.value in qualifiers ) count++
|
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 -> {}
|
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()
|
val t = next()
|
||||||
if( t.type == Token.Type.ID && t.value == keyword ) {
|
if (t.type == Token.Type.ID && t.value == keyword) {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
restorePos(pos)
|
restorePos(pos)
|
||||||
@ -168,7 +187,7 @@ class CompilerContext(val tokens: List<Token>) {
|
|||||||
* Note that [Token.Type.EOF] is not considered a whitespace token.
|
* Note that [Token.Type.EOF] is not considered a whitespace token.
|
||||||
*/
|
*/
|
||||||
fun skipWsTokens(): Token {
|
fun skipWsTokens(): Token {
|
||||||
while( current().type in wstokens ) {
|
while (current().type in wstokens) {
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
return next()
|
return next()
|
||||||
|
@ -2,6 +2,7 @@ package net.sergeych.lyng
|
|||||||
|
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
import net.sergeych.lyng.obj.ObjClass
|
import net.sergeych.lyng.obj.ObjClass
|
||||||
|
import net.sergeych.lyng.obj.ObjNull
|
||||||
import net.sergeych.lyng.obj.ObjVoid
|
import net.sergeych.lyng.obj.ObjVoid
|
||||||
|
|
||||||
fun String.toSource(name: String = "eval"): Source = Source(name, this)
|
fun String.toSource(name: String = "eval"): Source = Source(name, this)
|
||||||
@ -28,6 +29,7 @@ abstract class Statement(
|
|||||||
abstract suspend fun execute(scope: Scope): Obj
|
abstract suspend fun execute(scope: Scope): Obj
|
||||||
|
|
||||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
|
if( other == ObjNull || other == ObjVoid ) return 1
|
||||||
throw UnsupportedOperationException("not comparable")
|
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