diff --git a/docs/OOP.md b/docs/OOP.md index fb5ff26..2be4d38 100644 --- a/docs/OOP.md +++ b/docs/OOP.md @@ -67,7 +67,7 @@ Functions defined inside a class body are methods, and unless declared // private called from inside public: OK assertEquals( 5, p.length() ) // but us not available directly - assertThrows() { p.d2() } + assertThrows { p.d2() } void >>> void @@ -126,7 +126,7 @@ Private fields are visible only _inside the class instance_: assert( c.isEnough() ) // but the count is not available outside: - assertThrows() { c.count } + assertThrows { c.count } void >>> void @@ -137,7 +137,7 @@ set at construction but not available outside the class: // ... } val c = SecretCounter(10) - assertThrows() { c.count } + assertThrows { c.count } void >>> void diff --git a/library/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/library/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index aa317bc..7138c7a 100644 --- a/library/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/library/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -11,7 +11,8 @@ class Compiler( class Settings fun compile(source: Source): Script { - return parseScript(source.startPos, + return parseScript( + source.startPos, CompilerContext(parseLyng(source)) ) } @@ -38,7 +39,7 @@ class Compiler( } Token.Type.PRIVATE, Token.Type.PROTECTED -> { - if(cc.nextIdValue() in setOf("var", "val", "class", "fun", "fn")) { + if (cc.nextIdValue() in setOf("var", "val", "class", "fun", "fn")) { continue } else throw ScriptError(t.pos, "unexpected keyword ${t.value}") @@ -132,21 +133,49 @@ class Compiler( var isCall = false val next = cc.next() if (next.type == Token.Type.ID) { - cc.ifNextIs(Token.Type.LPAREN) { - // instance method call - val args = parseArgs(cc) - isCall = true - operand = Accessor { context -> - context.pos = next.pos - val v = left.getter(context).value - ObjRecord( - v.invokeInstanceMethod( - context, - next.value, - args.toArguments(context) - ), isMutable = false - ) + // could be () call or obj.method {} call + val nt = cc.current() + when (nt.type) { + Token.Type.LPAREN -> { + cc.next() + // instance method call + val args = parseArgs(cc) + isCall = true + operand = Accessor { context -> + context.pos = next.pos + val v = left.getter(context).value + ObjRecord( + v.invokeInstanceMethod( + context, + next.value, + args.toArguments(context) + ), isMutable = false + ) + } } + + Token.Type.LBRACE -> { + // single lambda arg, like assertTrows { ... } + cc.next() + isCall = true + val lambda = + parseExpression(cc) ?: throw ScriptError(t.pos, "expected valid lambda here") + println(cc.current()) + cc.skipTokenOfType(Token.Type.RBRACE) + operand = Accessor { context -> + context.pos = next.pos + val v = left.getter(context).value + ObjRecord( + v.invokeInstanceMethod( + context, + next.value, + Arguments(listOf(Arguments.Info(lambda, t.pos))) + ), isMutable = false + ) + } + } + + else -> {} } } if (!isCall) { @@ -169,6 +198,7 @@ class Compiler( operand = parseFunctionCall( cc, left, + false, ) } ?: run { // Expression in parentheses @@ -314,9 +344,10 @@ class Compiler( } Token.Type.LBRACE -> { - if (operand != null) { - throw ScriptError(t.pos, "syntax error: lambda expression not allowed here") - } else operand = parseLambdaExpression(cc) + operand = operand?.let { left -> + cc.previous() + parseFunctionCall(cc, left, blockArgument = true) + } ?: parseLambdaExpression(cc) } @@ -410,6 +441,7 @@ class Compiler( enum class AccessType(val isMutable: Boolean) { Val(false), Var(true), + @Suppress("unused") Initialization(false) } @@ -441,9 +473,10 @@ class Compiler( cc.restorePos(startPos); return null } } + Token.Type.ID -> { // visibility - val visibility = if( isClassDeclaration ) + val visibility = if (isClassDeclaration) cc.getVisibility(Visibility.Public) else Visibility.Public // val/var? @@ -530,6 +563,7 @@ class Compiler( } private fun parseArgs(cc: CompilerContext): List { + val args = mutableListOf() do { val t = cc.next() @@ -570,9 +604,17 @@ class Compiler( } - private fun parseFunctionCall(cc: CompilerContext, left: Accessor): Accessor { + private fun parseFunctionCall(cc: CompilerContext, left: Accessor, blockArgument: Boolean): Accessor { // insofar, functions always return lvalue - val args = parseArgs(cc) + val args = if (blockArgument) { + val blockArg = ParsedArgument( + parseExpression(cc) + ?: throw ScriptError(cc.currentPos(), "lambda body expected"), cc.currentPos() + ) + listOf(blockArg) + } else { + parseArgs(cc) + } return Accessor { context -> val v = left.getter(context) @@ -724,12 +766,13 @@ class Compiler( constructorArgsDeclaration?.assignToContext(this) bodyInit?.execute(this) // export public - for( (name,record) in objects ) { - when(record.visibility) { + for ((name, record) in objects) { + when (record.visibility) { Visibility.Public -> { thisObj.publicFields += name thisObj.protectedFields += name } + Visibility.Protected -> thisObj.protectedFields += name @@ -1133,9 +1176,10 @@ class Compiler( private fun parseVarDeclaration(kind: String, mutable: Boolean, tokens: CompilerContext): Statement { // we are just after var/val, visibility if exists is 2 steps behind - val visibility = when( tokens.atOffset(-2)?.type ) { + val visibility = when (tokens.atOffset(-2)?.type) { Token.Type.PRIVATE -> Visibility.Private + Token.Type.PROTECTED -> Visibility.Protected else -> Visibility.Public } diff --git a/library/src/commonTest/kotlin/ScriptTest.kt b/library/src/commonTest/kotlin/ScriptTest.kt index 20fdc15..1053d80 100644 --- a/library/src/commonTest/kotlin/ScriptTest.kt +++ b/library/src/commonTest/kotlin/ScriptTest.kt @@ -1461,7 +1461,30 @@ class ScriptTest { class Point(private var x,y) val p = Point(1,2) p.y = 101 - assertThrows() { p.x = 10 } + assertThrows { p.x = 10 } """) } + + @Test + fun testLBraceMethodCall() = runTest { + eval(""" + class Foo() { + fun cond(block) { + block() + } + } + val f = Foo() + assertEquals( 1, f.cond { 1 } ) + """.trimIndent()) + } + + @Test + fun testLBraceFnCall() = runTest { + eval(""" + fun cond(block) { + block() + } + assertEquals( 1, cond { 1 } ) + """.trimIndent()) + } } \ No newline at end of file