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
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

View File

@ -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,7 +133,11 @@ class Compiler(
var isCall = false
val next = cc.next()
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
val args = parseArgs(cc)
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) {
operand = Accessor({ context ->
@ -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<ParsedArgument> {
val args = mutableListOf<ParsedArgument>()
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
}

View File

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