fixed call arg precedence bug in last arg callable scenario

This commit is contained in:
Sergey Chernov 2025-11-27 08:11:09 +01:00
parent f0fc7ddd84
commit 83825a9272
3 changed files with 224 additions and 58 deletions

2
docs/samples/sum.lyng Normal file → Executable file
View File

@ -1,7 +1,7 @@
#!/bin/env lyng
/* /*
Calculate the limit of Sum( f(n) ) Calculate the limit of Sum( f(n) )
until it reaches asymptotic limit 0.00001% change until it reaches asymptotic limit 0.00001% change
return null or found limit return null or found limit
*/ */
fun findSumLimit(f) { fun findSumLimit(f) {

View File

@ -35,8 +35,8 @@ class Compiler(
// Stack of parameter-to-slot plans for current function being parsed (by declaration index) // Stack of parameter-to-slot plans for current function being parsed (by declaration index)
private val paramSlotPlanStack = mutableListOf<Map<String, Int>>() private val paramSlotPlanStack = mutableListOf<Map<String, Int>>()
private val currentParamSlotPlan: Map<String, Int>? // private val currentParamSlotPlan: Map<String, Int>?
get() = paramSlotPlanStack.lastOrNull() // get() = paramSlotPlanStack.lastOrNull()
// Track identifiers known to be locals/parameters in the current function for fast local emission // Track identifiers known to be locals/parameters in the current function for fast local emission
private val localNamesStack = mutableListOf<MutableSet<String>>() private val localNamesStack = mutableListOf<MutableSet<String>>()
@ -50,7 +50,11 @@ class Compiler(
private inline fun <T> withLocalNames(names: Set<String>, block: () -> T): T { private inline fun <T> withLocalNames(names: Set<String>, block: () -> T): T {
localNamesStack.add(names.toMutableSet()) localNamesStack.add(names.toMutableSet())
return try { block() } finally { localNamesStack.removeLast() } return try {
block()
} finally {
localNamesStack.removeLast()
}
} }
private fun declareLocalName(name: String) { private fun declareLocalName(name: String) {
@ -86,6 +90,7 @@ class Compiler(
if (t.startsWith("*")) t.removePrefix("*").trimStart() else line if (t.startsWith("*")) t.removePrefix("*").trimStart() else line
} }
} }
else -> raw else -> raw
} }
} }
@ -158,6 +163,7 @@ class Compiler(
// A standalone newline not immediately following a comment resets doc buffer // A standalone newline not immediately following a comment resets doc buffer
if (!prevWasComment) clearPendingDoc() else prevWasComment = false if (!prevWasComment) clearPendingDoc() else prevWasComment = false
} }
else -> {} else -> {}
} }
cc.next() cc.next()
@ -191,12 +197,15 @@ class Compiler(
val start = Pos(pos.source, pos.line, col) val start = Pos(pos.source, pos.line, col)
val end = Pos(pos.source, pos.line, col + p.length) val end = Pos(pos.source, pos.line, col + p.length)
col += p.length + 1 // account for following '.' between segments col += p.length + 1 // account for following '.' between segments
net.sergeych.lyng.miniast.MiniImport.Segment(p, net.sergeych.lyng.miniast.MiniRange(start, end)) MiniImport.Segment(
p,
MiniRange(start, end)
)
} }
val lastEnd = segs.last().range.end val lastEnd = segs.last().range.end
miniSink?.onImport( miniSink?.onImport(
net.sergeych.lyng.miniast.MiniImport( MiniImport(
net.sergeych.lyng.miniast.MiniRange(pos, lastEnd), MiniRange(pos, lastEnd),
segs segs
) )
) )
@ -241,7 +250,10 @@ class Compiler(
Script(start, statements) Script(start, statements)
}.also { }.also {
// Best-effort script end notification (use current position) // Best-effort script end notification (use current position)
miniSink?.onScriptEnd(cc.currentPos(), net.sergeych.lyng.miniast.MiniScript(MiniRange(start, cc.currentPos()))) miniSink?.onScriptEnd(
cc.currentPos(),
MiniScript(MiniRange(start, cc.currentPos()))
)
} }
} }
@ -327,9 +339,8 @@ class Compiler(
var lvalue: ObjRef? = parseExpressionLevel(level + 1) ?: return null var lvalue: ObjRef? = parseExpressionLevel(level + 1) ?: return null
while (true) { while (true) {
val opToken = cc.next() val opToken = cc.next()
val op = byLevel[level][opToken.type] val op = byLevel[level][opToken.type]
if (op == null) { if (op == null) {
// handle ternary conditional at the top precedence level only: a ? b : c // handle ternary conditional at the top precedence level only: a ? b : c
if (opToken.type == Token.Type.QUESTION && level == 0) { if (opToken.type == Token.Type.QUESTION && level == 0) {
@ -424,7 +435,7 @@ class Compiler(
// single lambda arg, like assertThrows { ... } // single lambda arg, like assertThrows { ... }
cc.next() cc.next()
isCall = true isCall = true
val lambda = parseLambdaExpression() val lambda = parseLambdaExpression()
val argStmt = statement { lambda.get(this).value } val argStmt = statement { lambda.get(this).value }
val args = listOf(ParsedArgument(argStmt, next.pos)) val args = listOf(ParsedArgument(argStmt, next.pos))
operand = MethodCallRef(left, next.value, args, true, isOptional) operand = MethodCallRef(left, next.value, args, true, isOptional)
@ -552,11 +563,14 @@ class Compiler(
Token.Type.LBRACE, Token.Type.NULL_COALESCE_BLOCKINVOKE -> { Token.Type.LBRACE, Token.Type.NULL_COALESCE_BLOCKINVOKE -> {
operand = operand?.let { left -> operand = operand?.let { left ->
cc.previous() // Trailing block-argument function call: the leading '{' is already consumed,
// and the lambda must be parsed as a single argument BEFORE any following
// selectors like ".foo" are considered. Do NOT rewind here, otherwise
// the expression parser may capture ".foo" as part of the lambda expression.
parseFunctionCall( parseFunctionCall(
left, left,
blockArgument = true, blockArgument = true,
t.type == Token.Type.NULL_COALESCE_BLOCKINVOKE isOptional = t.type == Token.Type.NULL_COALESCE_BLOCKINVOKE
) )
} ?: parseLambdaExpression() } ?: parseLambdaExpression()
} }
@ -778,7 +792,11 @@ class Compiler(
val typeStart = cc.currentPos() val typeStart = cc.currentPos()
var lastEnd = typeStart var lastEnd = typeStart
while (true) { while (true) {
val idTok = if (first) cc.requireToken(Token.Type.ID, "type name or type expression required") else cc.requireToken(Token.Type.ID, "identifier expected after '.' in type") val idTok =
if (first) cc.requireToken(Token.Type.ID, "type name or type expression required") else cc.requireToken(
Token.Type.ID,
"identifier expected after '.' in type"
)
first = false first = false
segments += MiniTypeName.Segment(idTok.value, MiniRange(idTok.pos, idTok.pos)) segments += MiniTypeName.Segment(idTok.value, MiniRange(idTok.pos, idTok.pos))
lastEnd = cc.currentPos() lastEnd = cc.currentPos()
@ -796,8 +814,11 @@ class Compiler(
// Helper to build MiniTypeRef (base or generic) // Helper to build MiniTypeRef (base or generic)
fun buildBaseRef(rangeEnd: Pos, args: List<MiniTypeRef>?, nullable: Boolean): MiniTypeRef { fun buildBaseRef(rangeEnd: Pos, args: List<MiniTypeRef>?, nullable: Boolean): MiniTypeRef {
val base = MiniTypeName(MiniRange(typeStart, rangeEnd), segments.toList(), nullable = false) val base = MiniTypeName(MiniRange(typeStart, rangeEnd), segments.toList(), nullable = false)
return if (args == null || args.isEmpty()) base.copy(range = MiniRange(typeStart, rangeEnd), nullable = nullable) return if (args == null || args.isEmpty()) base.copy(
else net.sergeych.lyng.miniast.MiniGenericType(MiniRange(typeStart, rangeEnd), base, args, nullable) range = MiniRange(typeStart, rangeEnd),
nullable = nullable
)
else MiniGenericType(MiniRange(typeStart, rangeEnd), base, args, nullable)
} }
// Optional generic arguments: '<' Type (',' Type)* '>' — single-level only (no nested generics for now) // Optional generic arguments: '<' Type (',' Type)* '>' — single-level only (no nested generics for now)
@ -811,12 +832,17 @@ class Compiler(
var argFirst = true var argFirst = true
val argStart = cc.currentPos() val argStart = cc.currentPos()
while (true) { while (true) {
val idTok = if (argFirst) cc.requireToken(Token.Type.ID, "type argument name expected") else cc.requireToken(Token.Type.ID, "identifier expected after '.' in type argument") val idTok = if (argFirst) cc.requireToken(
Token.Type.ID,
"type argument name expected"
) else cc.requireToken(Token.Type.ID, "identifier expected after '.' in type argument")
argFirst = false argFirst = false
argSegs += MiniTypeName.Segment(idTok.value, MiniRange(idTok.pos, idTok.pos)) argSegs += MiniTypeName.Segment(idTok.value, MiniRange(idTok.pos, idTok.pos))
val p = cc.savePos() val p = cc.savePos()
val tt = cc.next() val tt = cc.next()
if (tt.type == Token.Type.DOT) continue else { cc.restorePos(p); break } if (tt.type == Token.Type.DOT) continue else {
cc.restorePos(p); break
}
} }
val argNullable = cc.skipTokenOfType(Token.Type.QUESTION, isOptional = true) val argNullable = cc.skipTokenOfType(Token.Type.QUESTION, isOptional = true)
val argEnd = cc.currentPos() val argEnd = cc.currentPos()
@ -825,7 +851,9 @@ class Compiler(
val sep = cc.next() val sep = cc.next()
when (sep.type) { when (sep.type) {
Token.Type.COMMA -> { /* continue */ } Token.Type.COMMA -> { /* continue */
}
Token.Type.GT -> break Token.Type.GT -> break
else -> sep.raiseSyntax("expected ',' or '>' in generic arguments") else -> sep.raiseSyntax("expected ',' or '>' in generic arguments")
} }
@ -934,11 +962,14 @@ class Compiler(
): ObjRef { ): ObjRef {
var detectedBlockArgument = blockArgument var detectedBlockArgument = blockArgument
val args = if (blockArgument) { val args = if (blockArgument) {
val blockArg = ParsedArgument( // Leading '{' has already been consumed by the caller token branch.
parseExpression() // Parse only the lambda expression as the last argument and DO NOT
?: throw ScriptError(cc.currentPos(), "lambda body expected"), cc.currentPos() // allow any subsequent selectors (like ".last()") to be absorbed
) // into the lambda body. This ensures expected order:
listOf(blockArg) // foo { ... }.bar() == (foo { ... }).bar()
val callableAccessor = parseLambdaExpression()
val argStmt = statement { callableAccessor.get(this).value }
listOf(ParsedArgument(argStmt, cc.currentPos()))
} else { } else {
val r = parseArgs() val r = parseArgs()
detectedBlockArgument = r.second detectedBlockArgument = r.second
@ -1058,6 +1089,7 @@ class Compiler(
pendingDeclDoc = consumePendingDoc() pendingDeclDoc = consumePendingDoc()
parseVarDeclaration(false, Visibility.Public) parseVarDeclaration(false, Visibility.Public)
} }
"var" -> { "var" -> {
pendingDeclStart = id.pos pendingDeclStart = id.pos
pendingDeclDoc = consumePendingDoc() pendingDeclDoc = consumePendingDoc()
@ -1069,6 +1101,7 @@ class Compiler(
pendingDeclDoc = consumePendingDoc() pendingDeclDoc = consumePendingDoc()
parseFunctionDeclaration(isOpen = false, isExtern = false, isStatic = false) parseFunctionDeclaration(isOpen = false, isExtern = false, isStatic = false)
} }
"fn" -> { "fn" -> {
pendingDeclStart = id.pos pendingDeclStart = id.pos
pendingDeclDoc = consumePendingDoc() pendingDeclDoc = consumePendingDoc()
@ -1085,11 +1118,24 @@ class Compiler(
when (k.value) { when (k.value) {
"val" -> parseVarDeclaration(false, Visibility.Private, isStatic = isStatic) "val" -> parseVarDeclaration(false, Visibility.Private, isStatic = isStatic)
"var" -> parseVarDeclaration(true, Visibility.Private, isStatic = isStatic) "var" -> parseVarDeclaration(true, Visibility.Private, isStatic = isStatic)
"fun" -> parseFunctionDeclaration(visibility = Visibility.Private, isOpen = false, isExtern = false, isStatic = isStatic) "fun" -> parseFunctionDeclaration(
"fn" -> parseFunctionDeclaration(visibility = Visibility.Private, isOpen = false, isExtern = false, isStatic = isStatic) visibility = Visibility.Private,
isOpen = false,
isExtern = false,
isStatic = isStatic
)
"fn" -> parseFunctionDeclaration(
visibility = Visibility.Private,
isOpen = false,
isExtern = false,
isStatic = isStatic
)
else -> k.raiseSyntax("unsupported private declaration kind: ${k.value}") else -> k.raiseSyntax("unsupported private declaration kind: ${k.value}")
} }
} }
"protected" -> { "protected" -> {
var k = cc.requireToken(Token.Type.ID, "declaration expected after 'protected'") var k = cc.requireToken(Token.Type.ID, "declaration expected after 'protected'")
var isStatic = false var isStatic = false
@ -1100,11 +1146,24 @@ class Compiler(
when (k.value) { when (k.value) {
"val" -> parseVarDeclaration(false, Visibility.Protected, isStatic = isStatic) "val" -> parseVarDeclaration(false, Visibility.Protected, isStatic = isStatic)
"var" -> parseVarDeclaration(true, Visibility.Protected, isStatic = isStatic) "var" -> parseVarDeclaration(true, Visibility.Protected, isStatic = isStatic)
"fun" -> parseFunctionDeclaration(visibility = Visibility.Protected, isOpen = false, isExtern = false, isStatic = isStatic) "fun" -> parseFunctionDeclaration(
"fn" -> parseFunctionDeclaration(visibility = Visibility.Protected, isOpen = false, isExtern = false, isStatic = isStatic) visibility = Visibility.Protected,
isOpen = false,
isExtern = false,
isStatic = isStatic
)
"fn" -> parseFunctionDeclaration(
visibility = Visibility.Protected,
isOpen = false,
isExtern = false,
isStatic = isStatic
)
else -> k.raiseSyntax("unsupported protected declaration kind: ${k.value}") else -> k.raiseSyntax("unsupported protected declaration kind: ${k.value}")
} }
} }
"while" -> parseWhileStatement() "while" -> parseWhileStatement()
"do" -> parseDoWhileStatement() "do" -> parseDoWhileStatement()
"for" -> parseForStatement() "for" -> parseForStatement()
@ -1116,11 +1175,13 @@ class Compiler(
pendingDeclDoc = consumePendingDoc() pendingDeclDoc = consumePendingDoc()
parseClassDeclaration() parseClassDeclaration()
} }
"enum" -> { "enum" -> {
pendingDeclStart = id.pos pendingDeclStart = id.pos
pendingDeclDoc = consumePendingDoc() pendingDeclDoc = consumePendingDoc()
parseEnumDeclaration() parseEnumDeclaration()
} }
"try" -> parseTryStatement() "try" -> parseTryStatement()
"throw" -> parseThrowStatement(id.pos) "throw" -> parseThrowStatement(id.pos)
"when" -> parseWhenStatement() "when" -> parseWhenStatement()
@ -1130,9 +1191,10 @@ class Compiler(
val isExtern = cc.skipId("extern") val isExtern = cc.skipId("extern")
when { when {
cc.matchQualifiers("fun", "private") -> { cc.matchQualifiers("fun", "private") -> {
pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc()
parseFunctionDeclaration(Visibility.Private, isExtern) parseFunctionDeclaration(Visibility.Private, isExtern)
} }
cc.matchQualifiers("fun", "private", "static") -> parseFunctionDeclaration( cc.matchQualifiers("fun", "private", "static") -> parseFunctionDeclaration(
Visibility.Private, Visibility.Private,
isExtern, isExtern,
@ -1149,27 +1211,78 @@ class Compiler(
cc.matchQualifiers("fun", "open") -> parseFunctionDeclaration(isOpen = true, isExtern = isExtern) cc.matchQualifiers("fun", "open") -> parseFunctionDeclaration(isOpen = true, isExtern = isExtern)
cc.matchQualifiers("fn", "open") -> parseFunctionDeclaration(isOpen = true, isExtern = isExtern) cc.matchQualifiers("fn", "open") -> parseFunctionDeclaration(isOpen = true, isExtern = isExtern)
cc.matchQualifiers("fun") -> { pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseFunctionDeclaration(isOpen = false, isExtern = isExtern) } cc.matchQualifiers("fun") -> {
cc.matchQualifiers("fn") -> { pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseFunctionDeclaration(isOpen = false, isExtern = isExtern) } pendingDeclStart = id.pos; pendingDeclDoc =
consumePendingDoc(); parseFunctionDeclaration(isOpen = false, isExtern = isExtern)
}
cc.matchQualifiers("val", "private", "static") -> { pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration( cc.matchQualifiers("fn") -> {
false, pendingDeclStart = id.pos; pendingDeclDoc =
Visibility.Private, consumePendingDoc(); parseFunctionDeclaration(isOpen = false, isExtern = isExtern)
isStatic = true }
) }
cc.matchQualifiers("val", "static") -> { pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(false, Visibility.Public, isStatic = true) } cc.matchQualifiers("val", "private", "static") -> {
cc.matchQualifiers("val", "private") -> { pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(false, Visibility.Private) } pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(
cc.matchQualifiers("var", "static") -> { pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(true, Visibility.Public, isStatic = true) } false,
cc.matchQualifiers("var", "static", "private") -> { pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration( Visibility.Private,
true, isStatic = true
Visibility.Private, )
isStatic = true }
) }
cc.matchQualifiers("val", "static") -> {
pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(
false,
Visibility.Public,
isStatic = true
)
}
cc.matchQualifiers("val", "private") -> {
pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(
false,
Visibility.Private
)
}
cc.matchQualifiers("var", "static") -> {
pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(
true,
Visibility.Public,
isStatic = true
)
}
cc.matchQualifiers("var", "static", "private") -> {
pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(
true,
Visibility.Private,
isStatic = true
)
}
cc.matchQualifiers("var", "private") -> {
pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(
true,
Visibility.Private
)
}
cc.matchQualifiers("val", "open") -> {
pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(
false,
Visibility.Private,
true
)
}
cc.matchQualifiers("var", "open") -> {
pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(
true,
Visibility.Private,
true
)
}
cc.matchQualifiers("var", "private") -> { pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(true, Visibility.Private) }
cc.matchQualifiers("val", "open") -> { pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(false, Visibility.Private, true) }
cc.matchQualifiers("var", "open") -> { pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(true, Visibility.Private, true) }
else -> { else -> {
cc.next() cc.next()
null null
@ -1306,9 +1419,10 @@ class Compiler(
errorObject.extraData, errorObject.extraData,
errorObject.useStackTrace errorObject.useStackTrace
) )
else -> throwScope.raiseError("this is not an exception object: $errorObject") else -> throwScope.raiseError("this is not an exception object: $errorObject")
} }
throwScope.raiseError(errorObject as ObjException) throwScope.raiseError(errorObject)
} }
} }
@ -1473,6 +1587,7 @@ class Compiler(
// Optional base list: ":" Base ("," Base)* where Base := ID ( "(" args? ")" )? // Optional base list: ":" Base ("," Base)* where Base := ID ( "(" args? ")" )?
data class BaseSpec(val name: String, val args: List<ParsedArgument>?) data class BaseSpec(val name: String, val args: List<ParsedArgument>?)
val baseSpecs = mutableListOf<BaseSpec>() val baseSpecs = mutableListOf<BaseSpec>()
if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) { if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) {
do { do {
@ -1516,13 +1631,13 @@ class Compiler(
val declRange = MiniRange(pendingDeclStart ?: nameToken.pos, cc.currentPos()) val declRange = MiniRange(pendingDeclStart ?: nameToken.pos, cc.currentPos())
val bases = baseSpecs.map { it.name } val bases = baseSpecs.map { it.name }
// Collect constructor fields declared as val/var in primary constructor // Collect constructor fields declared as val/var in primary constructor
val ctorFields = mutableListOf<net.sergeych.lyng.miniast.MiniCtorField>() val ctorFields = mutableListOf<MiniCtorField>()
constructorArgsDeclaration?.let { ad -> constructorArgsDeclaration?.let { ad ->
for (p in ad.params) { for (p in ad.params) {
val at = p.accessType val at = p.accessType
if (at != null) { if (at != null) {
val mutable = at == AccessType.Var val mutable = at == AccessType.Var
ctorFields += net.sergeych.lyng.miniast.MiniCtorField( ctorFields += MiniCtorField(
name = p.name, name = p.name,
mutable = mutable, mutable = mutable,
type = p.miniType, type = p.miniType,
@ -1571,7 +1686,8 @@ class Compiler(
// accessors, constructor registration, etc. // accessors, constructor registration, etc.
// Resolve parent classes by name at execution time // Resolve parent classes by name at execution time
val parentClasses = baseSpecs.map { baseSpec -> val parentClasses = baseSpecs.map { baseSpec ->
val rec = this[baseSpec.name] ?: throw ScriptError(nameToken.pos, "unknown base class: ${baseSpec.name}") val rec =
this[baseSpec.name] ?: throw ScriptError(nameToken.pos, "unknown base class: ${baseSpec.name}")
(rec.value as? ObjClass) ?: throw ScriptError(nameToken.pos, "${baseSpec.name} is not a class") (rec.value as? ObjClass) ?: throw ScriptError(nameToken.pos, "${baseSpec.name} is not a class")
} }
@ -2082,7 +2198,7 @@ class Compiler(
val paramNames: Set<String> = argsDeclaration.params.map { it.name }.toSet() val paramNames: Set<String> = argsDeclaration.params.map { it.name }.toSet()
// Parse function body while tracking declared locals to compute precise capacity hints // Parse function body while tracking declared locals to compute precise capacity hints
val fnLocalDeclStart = currentLocalDeclCount currentLocalDeclCount
localDeclCountStack.add(0) localDeclCountStack.add(0)
val fnStatements = if (isExtern) val fnStatements = if (isExtern)
statement { raiseError("extern function not provided: $name") } statement { raiseError("extern function not provided: $name") }
@ -2113,7 +2229,7 @@ class Compiler(
} }
fnStatements.execute(context) fnStatements.execute(context)
} }
val enclosingCtx = parentContext parentContext
val fnCreateStatement = statement(start) { context -> val fnCreateStatement = statement(start) { context ->
// 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, unless we're in the class scope: // for the function, unless we're in the class scope:
@ -2363,7 +2479,7 @@ class Compiler(
) { ) {
// fun isLeftAssociative() = tokenType != Token.Type.OR && tokenType != Token.Type.AND // fun isLeftAssociative() = tokenType != Token.Type.OR && tokenType != Token.Type.AND
companion object {} companion object
} }
@ -2377,15 +2493,24 @@ class Compiler(
* Compile [source] while streaming a Mini-AST into the provided [sink]. * Compile [source] while streaming a Mini-AST into the provided [sink].
* When [sink] is null, behaves like [compile]. * When [sink] is null, behaves like [compile].
*/ */
suspend fun compileWithMini(source: Source, importManager: ImportProvider, sink: net.sergeych.lyng.miniast.MiniAstSink?): Script { suspend fun compileWithMini(
return Compiler(CompilerContext(parseLyng(source)), importManager, Settings(miniAstSink = sink)).parseScript() source: Source,
importManager: ImportProvider,
sink: MiniAstSink?
): Script {
return Compiler(
CompilerContext(parseLyng(source)),
importManager,
Settings(miniAstSink = sink)
).parseScript()
} }
/** Convenience overload to compile raw [code] with a Mini-AST [sink]. */ /** Convenience overload to compile raw [code] with a Mini-AST [sink]. */
suspend fun compileWithMini(code: String, sink: net.sergeych.lyng.miniast.MiniAstSink?): Script = suspend fun compileWithMini(code: String, sink: MiniAstSink?): Script =
compileWithMini(Source("<eval>", code), Script.defaultImportManager, sink) compileWithMini(Source("<eval>", code), Script.defaultImportManager, sink)
private var lastPriority = 0 private var lastPriority = 0
// Helpers for conservative constant folding (literal-only). Only pure, side-effect-free ops. // Helpers for conservative constant folding (literal-only). Only pure, side-effect-free ops.
private fun constOf(r: ObjRef): Obj? = (r as? ConstRef)?.constValue private fun constOf(r: ObjRef): Obj? = (r as? ConstRef)?.constValue
@ -2404,30 +2529,35 @@ class Compiler(
a is ObjChar && b is ObjChar -> if (a.value == b.value) ObjTrue else ObjFalse a is ObjChar && b is ObjChar -> if (a.value == b.value) ObjTrue else ObjFalse
else -> null else -> null
} }
BinOp.NEQ -> when { BinOp.NEQ -> when {
a is ObjInt && b is ObjInt -> if (a.value != b.value) ObjTrue else ObjFalse a is ObjInt && b is ObjInt -> if (a.value != b.value) ObjTrue else ObjFalse
a is ObjString && b is ObjString -> if (a.value != b.value) ObjTrue else ObjFalse a is ObjString && b is ObjString -> if (a.value != b.value) ObjTrue else ObjFalse
a is ObjChar && b is ObjChar -> if (a.value != b.value) ObjTrue else ObjFalse a is ObjChar && b is ObjChar -> if (a.value != b.value) ObjTrue else ObjFalse
else -> null else -> null
} }
BinOp.LT -> when { BinOp.LT -> when {
a is ObjInt && b is ObjInt -> if (a.value < b.value) ObjTrue else ObjFalse a is ObjInt && b is ObjInt -> if (a.value < b.value) ObjTrue else ObjFalse
a is ObjString && b is ObjString -> if (a.value < b.value) ObjTrue else ObjFalse a is ObjString && b is ObjString -> if (a.value < b.value) ObjTrue else ObjFalse
a is ObjChar && b is ObjChar -> if (a.value < b.value) ObjTrue else ObjFalse a is ObjChar && b is ObjChar -> if (a.value < b.value) ObjTrue else ObjFalse
else -> null else -> null
} }
BinOp.LTE -> when { BinOp.LTE -> when {
a is ObjInt && b is ObjInt -> if (a.value <= b.value) ObjTrue else ObjFalse a is ObjInt && b is ObjInt -> if (a.value <= b.value) ObjTrue else ObjFalse
a is ObjString && b is ObjString -> if (a.value <= b.value) ObjTrue else ObjFalse a is ObjString && b is ObjString -> if (a.value <= b.value) ObjTrue else ObjFalse
a is ObjChar && b is ObjChar -> if (a.value <= b.value) ObjTrue else ObjFalse a is ObjChar && b is ObjChar -> if (a.value <= b.value) ObjTrue else ObjFalse
else -> null else -> null
} }
BinOp.GT -> when { BinOp.GT -> when {
a is ObjInt && b is ObjInt -> if (a.value > b.value) ObjTrue else ObjFalse a is ObjInt && b is ObjInt -> if (a.value > b.value) ObjTrue else ObjFalse
a is ObjString && b is ObjString -> if (a.value > b.value) ObjTrue else ObjFalse a is ObjString && b is ObjString -> if (a.value > b.value) ObjTrue else ObjFalse
a is ObjChar && b is ObjChar -> if (a.value > b.value) ObjTrue else ObjFalse a is ObjChar && b is ObjChar -> if (a.value > b.value) ObjTrue else ObjFalse
else -> null else -> null
} }
BinOp.GTE -> when { BinOp.GTE -> when {
a is ObjInt && b is ObjInt -> if (a.value >= b.value) ObjTrue else ObjFalse a is ObjInt && b is ObjInt -> if (a.value >= b.value) ObjTrue else ObjFalse
a is ObjString && b is ObjString -> if (a.value >= b.value) ObjTrue else ObjFalse a is ObjString && b is ObjString -> if (a.value >= b.value) ObjTrue else ObjFalse
@ -2441,6 +2571,7 @@ class Compiler(
a is ObjString && b is ObjString -> ObjString(a.value + b.value) a is ObjString && b is ObjString -> ObjString(a.value + b.value)
else -> null else -> null
} }
BinOp.MINUS -> if (a is ObjInt && b is ObjInt) ObjInt(a.value - b.value) else null BinOp.MINUS -> if (a is ObjInt && b is ObjInt) ObjInt(a.value - b.value) else null
BinOp.STAR -> if (a is ObjInt && b is ObjInt) ObjInt(a.value * b.value) else null BinOp.STAR -> if (a is ObjInt && b is ObjInt) ObjInt(a.value * b.value) else null
BinOp.SLASH -> if (a is ObjInt && b is ObjInt && b.value != 0L) ObjInt(a.value / b.value) else null BinOp.SLASH -> if (a is ObjInt && b is ObjInt && b.value != 0L) ObjInt(a.value / b.value) else null
@ -2468,6 +2599,7 @@ class Compiler(
is ObjReal -> ObjReal(-a.value) is ObjReal -> ObjReal(-a.value)
else -> null else -> null
} }
UnaryOp.BITNOT -> if (a is ObjInt) ObjInt(a.value.inv()) else null UnaryOp.BITNOT -> if (a is ObjInt) ObjInt(a.value.inv()) else null
} }
} }
@ -2638,5 +2770,5 @@ class Compiler(
} }
} }
suspend fun eval(code: String) = Compiler.compile(code).execute() suspend fun eval(code: String) = compile(code).execute()

View File

@ -3544,6 +3544,40 @@ class ScriptTest {
""".trimIndent()) """.trimIndent())
} }
@Test
fun testCallAndResultOrder() = runTest {
eval("""
import lyng.stdlib
fun test(a="a", b="b", c="c") { [a, b, c] }
// the parentheses here are in fact unnecessary:
val ok1 = (test { void }).last()
assert( ok1 is Callable)
// it should work without them, as the call test() {} must be executed
// first, then the result should be used to call methods on it:
// the parentheses here are in fact unnecessary:
val ok2 = test { void }.last()
assert( ok2 is Callable)
""".trimIndent())
}
// @Test
// fun namedArgsProposal() = runTest {
// eval("""
// import lyng.stdlib
//
// fun test(a="a", b="b", c="c") { [a, b, c] }
//
// val l = (test{ void }).last()
// println(l)
//
// """.trimIndent())
// }
// @Ignore // @Ignore
// @Test // @Test