fix #9 block argument {} call without ()

This commit is contained in:
Sergey Chernov 2025-06-10 18:32:38 +04:00
parent 20c81dbf2e
commit 2a93e6f7da
3 changed files with 96 additions and 29 deletions

View File

@ -67,7 +67,7 @@ Functions defined inside a class body are methods, and unless declared
// private called from inside public: OK // private called from inside public: OK
assertEquals( 5, p.length() ) assertEquals( 5, p.length() )
// but us not available directly // but us not available directly
assertThrows() { p.d2() } assertThrows { p.d2() }
void void
>>> void >>> void
@ -126,7 +126,7 @@ Private fields are visible only _inside the class instance_:
assert( c.isEnough() ) assert( c.isEnough() )
// but the count is not available outside: // but the count is not available outside:
assertThrows() { c.count } assertThrows { c.count }
void void
>>> void >>> void
@ -137,7 +137,7 @@ set at construction but not available outside the class:
// ... // ...
} }
val c = SecretCounter(10) val c = SecretCounter(10)
assertThrows() { c.count } assertThrows { c.count }
void void
>>> void >>> void

View File

@ -11,7 +11,8 @@ class Compiler(
class Settings class Settings
fun compile(source: Source): Script { fun compile(source: Source): Script {
return parseScript(source.startPos, return parseScript(
source.startPos,
CompilerContext(parseLyng(source)) CompilerContext(parseLyng(source))
) )
} }
@ -38,7 +39,7 @@ class Compiler(
} }
Token.Type.PRIVATE, Token.Type.PROTECTED -> { 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 continue
} else } else
throw ScriptError(t.pos, "unexpected keyword ${t.value}") throw ScriptError(t.pos, "unexpected keyword ${t.value}")
@ -132,7 +133,11 @@ class Compiler(
var isCall = false var isCall = false
val next = cc.next() val next = cc.next()
if (next.type == Token.Type.ID) { if (next.type == Token.Type.ID) {
cc.ifNextIs(Token.Type.LPAREN) { // could be () call or obj.method {} call
val nt = cc.current()
when (nt.type) {
Token.Type.LPAREN -> {
cc.next()
// instance method call // instance method call
val args = parseArgs(cc) val args = parseArgs(cc)
isCall = true isCall = true
@ -148,6 +153,30 @@ class Compiler(
) )
} }
} }
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) { if (!isCall) {
operand = Accessor({ context -> operand = Accessor({ context ->
@ -169,6 +198,7 @@ class Compiler(
operand = parseFunctionCall( operand = parseFunctionCall(
cc, cc,
left, left,
false,
) )
} ?: run { } ?: run {
// Expression in parentheses // Expression in parentheses
@ -314,9 +344,10 @@ class Compiler(
} }
Token.Type.LBRACE -> { Token.Type.LBRACE -> {
if (operand != null) { operand = operand?.let { left ->
throw ScriptError(t.pos, "syntax error: lambda expression not allowed here") cc.previous()
} else operand = parseLambdaExpression(cc) parseFunctionCall(cc, left, blockArgument = true)
} ?: parseLambdaExpression(cc)
} }
@ -410,6 +441,7 @@ class Compiler(
enum class AccessType(val isMutable: Boolean) { enum class AccessType(val isMutable: Boolean) {
Val(false), Var(true), Val(false), Var(true),
@Suppress("unused") @Suppress("unused")
Initialization(false) Initialization(false)
} }
@ -441,9 +473,10 @@ class Compiler(
cc.restorePos(startPos); return null cc.restorePos(startPos); return null
} }
} }
Token.Type.ID -> { Token.Type.ID -> {
// visibility // visibility
val visibility = if( isClassDeclaration ) val visibility = if (isClassDeclaration)
cc.getVisibility(Visibility.Public) cc.getVisibility(Visibility.Public)
else Visibility.Public else Visibility.Public
// val/var? // val/var?
@ -530,6 +563,7 @@ class Compiler(
} }
private fun parseArgs(cc: CompilerContext): List<ParsedArgument> { private fun parseArgs(cc: CompilerContext): List<ParsedArgument> {
val args = mutableListOf<ParsedArgument>() val args = mutableListOf<ParsedArgument>()
do { do {
val t = cc.next() 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 // 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 -> return Accessor { context ->
val v = left.getter(context) val v = left.getter(context)
@ -724,12 +766,13 @@ class Compiler(
constructorArgsDeclaration?.assignToContext(this) constructorArgsDeclaration?.assignToContext(this)
bodyInit?.execute(this) bodyInit?.execute(this)
// export public // export public
for( (name,record) in objects ) { for ((name, record) in objects) {
when(record.visibility) { when (record.visibility) {
Visibility.Public -> { Visibility.Public -> {
thisObj.publicFields += name thisObj.publicFields += name
thisObj.protectedFields += name thisObj.protectedFields += name
} }
Visibility.Protected -> Visibility.Protected ->
thisObj.protectedFields += name thisObj.protectedFields += name
@ -1133,9 +1176,10 @@ class Compiler(
private fun parseVarDeclaration(kind: String, mutable: Boolean, tokens: CompilerContext): Statement { private fun parseVarDeclaration(kind: String, mutable: Boolean, tokens: CompilerContext): Statement {
// we are just after var/val, visibility if exists is 2 steps behind // 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 -> Token.Type.PRIVATE ->
Visibility.Private Visibility.Private
Token.Type.PROTECTED -> Visibility.Protected Token.Type.PROTECTED -> Visibility.Protected
else -> Visibility.Public else -> Visibility.Public
} }

View File

@ -1461,7 +1461,30 @@ class ScriptTest {
class Point(private var x,y) class Point(private var x,y)
val p = Point(1,2) val p = Point(1,2)
p.y = 101 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())
}
} }