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
 | 
			
		||||
 | 
			
		||||
@ -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")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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