fix: lambda now have correct closure

This commit is contained in:
Sergey Chernov 2025-06-08 00:30:13 +04:00
parent 306a7f26ef
commit c0cf190452
8 changed files with 239 additions and 57 deletions

View File

@ -70,3 +70,18 @@ Regular methods are called on instances as usual `instance.method()`. The method
1. this instance methods; 1. this instance methods;
2. parents method: no guarantee but we enumerate parents in order of appearance; 2. parents method: no guarantee but we enumerate parents in order of appearance;
3. possible extension methods (scoped) 3. possible extension methods (scoped)
# Defining a new class
The class is a some data record with named fields and fixed order, in fact. To define a class,
just Provide a name and a record like this:
class Vec2(x,y)
This way, you have created a _constructor_, so calling `Vec2( 10, 20 )` would create an _instane_ of `Vec2` class:
class Vec2(x,y)
Vec2(10,20)
>> eee
TBD

View File

@ -47,7 +47,7 @@ One interesting way of using closure isolation is to keep state of the functions
>>> 0 >>> 0
>>> 1 >>> 1
>>> 2 >>> 2
>>> void >> void
Inner `counter` is not accessible from outside, no way; still it is kept Inner `counter` is not accessible from outside, no way; still it is kept
between calls in the closure, as inner function `doit`, returned from the between calls in the closure, as inner function `doit`, returned from the
@ -75,3 +75,15 @@ The example above could be rewritten using inner lambda, too:
>>> 1 >>> 1
>>> 2 >>> 2
>>> void >>> void
Lambda functions remember their scopes, so it will work the same as previous:
var counter = 200
fun createLambda() {
var counter = 0
{ counter += 1 }
}
val c = createLambda()
println(c)
>> 1
>> void

24
docs/class_reference.md Normal file
View File

@ -0,0 +1,24 @@
# Classes
## Declaring
class Foo1
class Foo2() // same, empty constructor
class Foo3() { // full
}
class Foo4 { // Only body
}
```
class_declaration = ["abstract",] "class" [, constructor] [, body]
constructor = "(", [field [, field]] ")
field = [visibility ,] [access ,] name [, typedecl]
body = [visibility] ("var", vardecl) | ("val", vardecl) | ("fun", fundecl)
visibility = "private" | "protected" | "internal"
```
### Abstract classes
Contain one pr more abstract methods which must be implemented; though they
can have constructors, the instances of the abstract classes could not be
created independently

View File

@ -326,41 +326,47 @@ class Compiler(
throw ScriptError(startPos, "lambda must have either valid arguments declaration with '->' or no arguments") throw ScriptError(startPos, "lambda must have either valid arguments declaration with '->' or no arguments")
val pos = cc.currentPos() val pos = cc.currentPos()
val body = parseBlock(cc, skipLeadingBrace = true) val body = parseBlock(cc, skipLeadingBrace = true)
return Accessor { _ ->
statement { var closure: Context? = null
val context = this.copy(pos)
if (argsDeclaration == null) { val callStatement = statement {
// no args: automatic var 'it' val context = closure!!.copy(pos, args)
val l = args.values if (argsDeclaration == null) {
val itValue: Obj = when (l.size) { // no args: automatic var 'it'
// no args: it == void val l = args.values
0 -> ObjVoid val itValue: Obj = when (l.size) {
// one args: it is this arg // no args: it == void
1 -> l[0] 0 -> ObjVoid
// more args: it is a list of args // one args: it is this arg
else -> ObjList(l.toMutableList()) 1 -> l[0]
} // more args: it is a list of args
context.addItem("it", false, itValue) else -> ObjList(l.toMutableList())
} else { }
// assign vars as declared context.addItem("it", false, itValue)
if (args.size != argsDeclaration.args.size && !argsDeclaration.args.last().isEllipsis) } else {
raiseArgumentError("Too many arguments : called with ${args.size}, lambda accepts only ${argsDeclaration.args.size}") // assign vars as declared
for ((n, a) in argsDeclaration.args.withIndex()) { if (args.size != argsDeclaration.args.size && !argsDeclaration.args.last().isEllipsis)
if (n >= args.size) { raiseArgumentError("Too many arguments : called with ${args.size}, lambda accepts only ${argsDeclaration.args.size}")
if (a.initialValue != null) for ((n, a) in argsDeclaration.args.withIndex()) {
context.addItem(a.name, false, a.initialValue.execute(context)) if (n >= args.size) {
else throw ScriptError(a.pos, "argument $n is out of scope") if (a.initialValue != null)
} else { context.addItem(a.name, false, a.initialValue.execute(context))
val value = if (a.isEllipsis) { else throw ScriptError(a.pos, "argument $n is out of scope")
ObjList(args.values.subList(n, args.values.size).toMutableList()) } else {
} else val value = if (a.isEllipsis) {
args[n] ObjList(args.values.subList(n, args.values.size).toMutableList())
context.addItem(a.name, false, value) } else
} args[n]
context.addItem(a.name, false, value)
} }
} }
body.execute(context) }
}.asReadonly body.execute(context)
}
return Accessor { x ->
if( closure == null ) closure = x
callStatement.asReadonly
} }
} }
@ -402,12 +408,22 @@ class Compiler(
} }
} }
enum class AccessType {
Val, Var, Default
}
enum class Visibility {
Default, Public, Private, Protected, Internal
}
data class ArgVar( data class ArgVar(
val name: String, val name: String,
val type: TypeDecl = TypeDecl.Obj, val type: TypeDecl = TypeDecl.Obj,
val pos: Pos, val pos: Pos,
val isEllipsis: Boolean, val isEllipsis: Boolean,
val initialValue: Statement? = null val initialValue: Statement? = null,
val accessType: AccessType = AccessType.Default,
val visibility: Visibility = Visibility.Default
) )
data class ArgsDeclaration(val args: List<ArgVar>, val endTokenType: Token.Type) { data class ArgsDeclaration(val args: List<ArgVar>, val endTokenType: Token.Type) {
@ -421,16 +437,73 @@ class Compiler(
* Parse argument declaration, used in lambda (and later in fn too) * Parse argument declaration, used in lambda (and later in fn too)
* @return declaration or null if there is no valid list of arguments * @return declaration or null if there is no valid list of arguments
*/ */
private fun parseArgsDeclaration(cc: CompilerContext): ArgsDeclaration? { private fun parseArgsDeclaration(cc: CompilerContext, isClassDeclaration: Boolean = false): ArgsDeclaration? {
val result = mutableListOf<ArgVar>() val result = mutableListOf<ArgVar>()
var endTokenType: Token.Type? = null var endTokenType: Token.Type? = null
val startPos = cc.savePos() val startPos = cc.savePos()
while (endTokenType == null) { while (endTokenType == null) {
val t = cc.next() var t = cc.next()
when (t.type) { when (t.type) {
Token.Type.NEWLINE -> {} Token.Type.NEWLINE -> {}
Token.Type.ID -> { Token.Type.ID -> {
// visibility
val visibility = when (t.value) {
"private" -> {
if (!isClassDeclaration) {
cc.restorePos(startPos); return null
}
t = cc.next()
Visibility.Private
}
"protected" -> {
if (!isClassDeclaration) {
cc.restorePos(startPos); return null
}
t = cc.next()
Visibility.Protected
}
"internal" -> {
if (!isClassDeclaration) {
cc.restorePos(startPos); return null
}
t = cc.next()
Visibility.Internal
}
"public" -> {
if (!isClassDeclaration) {
cc.restorePos(startPos); return null
}
t = cc.next()
Visibility.Public
}
else -> Visibility.Default
}
// val/var?
val access = when (t.value) {
"val" -> {
if (!isClassDeclaration) {
cc.restorePos(startPos); return null
}
t = cc.next()
AccessType.Val
}
"var" -> {
if (!isClassDeclaration) {
cc.restorePos(startPos); return null
}
t = cc.next()
AccessType.Var
}
else -> AccessType.Default
}
var defaultValue: Statement? = null var defaultValue: Statement? = null
cc.ifNextIs(Token.Type.ASSIGN) { cc.ifNextIs(Token.Type.ASSIGN) {
defaultValue = parseExpression(cc) defaultValue = parseExpression(cc)
@ -438,7 +511,7 @@ class Compiler(
// type information // type information
val typeInfo = parseTypeDeclaration(cc) val typeInfo = parseTypeDeclaration(cc)
val isEllipsis = cc.skipTokenOfType(Token.Type.ELLIPSIS, isOptional = true) val isEllipsis = cc.skipTokenOfType(Token.Type.ELLIPSIS, isOptional = true)
result += ArgVar(t.value, typeInfo, t.pos, isEllipsis, defaultValue) result += ArgVar(t.value, typeInfo, t.pos, isEllipsis, defaultValue, access, visibility)
// important: valid argument list continues with ',' and ends with '->' or ')' // important: valid argument list continues with ',' and ends with '->' or ')'
// otherwise it is not an argument list: // otherwise it is not an argument list:
@ -630,9 +703,36 @@ class Compiler(
"continue" -> parseContinueStatement(id.pos, cc) "continue" -> parseContinueStatement(id.pos, cc)
"fn", "fun" -> parseFunctionDeclaration(cc) "fn", "fun" -> parseFunctionDeclaration(cc)
"if" -> parseIfStatement(cc) "if" -> parseIfStatement(cc)
"class" -> parseClassDeclaration(cc, false)
"struct" -> parseClassDeclaration(cc, true)
else -> null else -> null
} }
private fun parseClassDeclaration(cc: CompilerContext, isStruct: Boolean): Statement {
val nameToken = cc.requireToken(Token.Type.ID)
val parsedArgs = parseArgsDeclaration(cc)
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
val t = cc.next()
if (t.type == Token.Type.LBRACE) {
// parse body
}
// create class
val className = nameToken.value
// val constructorCode = statement {
// val classContext = copy()
// }
val newClass = ObjClass(className, parsedArgs?.args ?: emptyList())
// statement {
// addConst(nameToken.value, )
// }
// }
TODO()
}
private fun getLabel(cc: CompilerContext, maxDepth: Int = 2): String? { private fun getLabel(cc: CompilerContext, maxDepth: Int = 2): String? {
var cnt = 0 var cnt = 0
var found: String? = null var found: String? = null
@ -749,7 +849,7 @@ class Compiler(
var result: Obj = ObjVoid var result: Obj = ObjVoid
val iVar = ObjInt(0) val iVar = ObjInt(0)
loopVar.value = iVar loopVar.value = iVar
if( catchBreak) { if (catchBreak) {
for (i in start..<end) { for (i in start..<end) {
iVar.value = i.toLong() iVar.value = i.toLong()
try { try {
@ -1185,7 +1285,8 @@ class Compiler(
Operator.simple(Token.Type.NOTIS, lastPrty) { c, a, b -> ObjBool(!a.isInstanceOf(b)) }, Operator.simple(Token.Type.NOTIS, lastPrty) { c, a, b -> ObjBool(!a.isInstanceOf(b)) },
// shuttle <=> 6 // shuttle <=> 6
Operator.simple(Token.Type.SHUTTLE, ++lastPrty) { c, a, b -> Operator.simple(Token.Type.SHUTTLE, ++lastPrty) { c, a, b ->
ObjInt(a.compareTo(c, b).toLong()) }, ObjInt(a.compareTo(c, b).toLong())
},
// bit shifts 7 // bit shifts 7
Operator.simple(Token.Type.PLUS, ++lastPrty) { ctx, a, b -> a.plus(ctx, b) }, Operator.simple(Token.Type.PLUS, ++lastPrty) { ctx, a, b -> a.plus(ctx, b) },
Operator.simple(Token.Type.MINUS, lastPrty) { ctx, a, b -> a.minus(ctx, b) }, Operator.simple(Token.Type.MINUS, lastPrty) { ctx, a, b -> a.minus(ctx, b) },
@ -1213,7 +1314,7 @@ class Compiler(
/** /**
* The keywords that stop processing of expression term * The keywords that stop processing of expression term
*/ */
val stopKeywords = setOf("break", "continue", "return", "if", "when", "do", "while", "for") val stopKeywords = setOf("break", "continue", "return", "if", "when", "do", "while", "for", "class", "struct")
} }
} }

View File

@ -8,7 +8,11 @@ import net.sergeych.synctools.ProtectedOp
//typealias InstanceMethod = (Context, Obj) -> Obj //typealias InstanceMethod = (Context, Obj) -> Obj
data class WithAccess<T>(var value: T, val isMutable: Boolean) data class WithAccess<T>(
var value: T,
val isMutable: Boolean,
val visibility: Compiler.Visibility = Compiler.Visibility.Public
)
data class Accessor( data class Accessor(
val getter: suspend (Context) -> WithAccess<Obj>, val getter: suspend (Context) -> WithAccess<Obj>,

View File

@ -4,8 +4,14 @@ val ObjClassType by lazy { ObjClass("Class") }
class ObjClass( class ObjClass(
val className: String, val className: String,
val constructorArgs: List<Compiler.ArgVar> = emptyList(),
vararg val parents: ObjClass, vararg val parents: ObjClass,
) : Obj() { ) : Obj() {
constructor(
className: String,
vararg parents: ObjClass,
) : this(className, emptyList(), *parents)
val allParentsSet: Set<ObjClass> = parents.flatMap { val allParentsSet: Set<ObjClass> = parents.flatMap {
listOf(it) + it.allParentsSet listOf(it) + it.allParentsSet
@ -22,7 +28,7 @@ class ObjClass(
// private var initInstanceHandler: (suspend (Context, List<Obj>) -> Obj)? = null // private var initInstanceHandler: (suspend (Context, List<Obj>) -> Obj)? = null
// suspend fun newInstance(context: Context, vararg args: Obj): Obj = // suspend fun newInstance(context: Context, vararg args: Obj): Obj =
// initInstanceHandler?.invoke(context, args.toList()) // initInstanceHandler?.invoke(context, args.toList())
// ?: context.raiseError("No initInstance handler for $this") // ?: context.raiseError("No initInstance handler for $this")
// //
@ -70,24 +76,26 @@ class ObjClass(
/** /**
* Abstract class that must provide `iterator` method that returns [ObjIterator] instance. * Abstract class that must provide `iterator` method that returns [ObjIterator] instance.
*/ */
val ObjIterable by lazy { ObjClass("Iterable").apply { val ObjIterable by lazy {
ObjClass("Iterable").apply {
addFn("toList") { addFn("toList") {
val result = mutableListOf<Obj>() val result = mutableListOf<Obj>()
val iterator = thisObj.invokeInstanceMethod(this, "iterator") val iterator = thisObj.invokeInstanceMethod(this, "iterator")
while( iterator.invokeInstanceMethod(this, "hasNext").toBool() ) while (iterator.invokeInstanceMethod(this, "hasNext").toBool())
result += iterator.invokeInstanceMethod(this, "next") result += iterator.invokeInstanceMethod(this, "next")
// val next = iterator.getMemberOrNull("next")!! // val next = iterator.getMemberOrNull("next")!!
// val hasNext = iterator.getMemberOrNull("hasNext")!! // val hasNext = iterator.getMemberOrNull("hasNext")!!
// while( hasNext.invoke(this, iterator).toBool() ) // while( hasNext.invoke(this, iterator).toBool() )
// result += next.invoke(this, iterator) // result += next.invoke(this, iterator)
ObjList(result) ObjList(result)
} }
} } }
}
/** /**
* Collection is an iterator with `size`] * Collection is an iterator with `size`]
@ -123,7 +131,7 @@ class ObjArrayIterator(val array: Obj) : Obj() {
} }
addFn("hasNext") { addFn("hasNext") {
val self = thisAs<ObjArrayIterator>() val self = thisAs<ObjArrayIterator>()
if (self.nextIndex < self.lastIndex) ObjTrue else ObjFalse if (self.nextIndex < self.lastIndex) ObjTrue else ObjFalse
} }
} }
} }
@ -142,6 +150,10 @@ val ObjArray by lazy {
addFn("iterator") { addFn("iterator") {
ObjArrayIterator(thisObj).also { it.init(this) } ObjArrayIterator(thisObj).also { it.init(this) }
} }
addFn("isample") { "ok".toObj()} addFn("isample") { "ok".toObj() }
} }
} }
class ObjInstance(override val objClass: ObjClass): Obj() {
}

View File

@ -1167,6 +1167,7 @@ class ScriptTest {
eval( eval(
""" """
fun mapValues(iterable, transform) { fun mapValues(iterable, transform) {
println("start: ", transform)
var result = [] var result = []
for( x in iterable ) result += transform(x) for( x in iterable ) result += transform(x)
} }
@ -1238,6 +1239,17 @@ class ScriptTest {
assert( 5 <=> 7 < 0 ) assert( 5 <=> 7 < 0 )
""".trimIndent() """.trimIndent()
) )
}
@Test
fun simpleClassDelaration() = runTest {
eval( """
// class Vec2(x,y)
// println(Vec2(1,2)::class)
println("---------------------")
println(Int::class)
""".trimIndent()
)
} }
} }

View File

@ -8,6 +8,7 @@ import net.sergeych.lyng.ObjVoid
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Files.readAllLines import java.nio.file.Files.readAllLines
import java.nio.file.Paths import java.nio.file.Paths
import kotlin.io.path.absolutePathString
import kotlin.io.path.extension import kotlin.io.path.extension
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -41,7 +42,8 @@ data class DocTest(
} }
override fun toString(): String { override fun toString(): String {
return "DocTest:$fileName:${line + 1}..${line + sourceLines.size}" val absPath = Paths.get(fileName).absolutePathString()
return "DocTest: $absPath:${line + 1}"
} }
val detailedString by lazy { val detailedString by lazy {
@ -190,7 +192,7 @@ suspend fun DocTest.test(context: Context = Context()) {
if (error != null || expectedOutput != collectedOutput.toString() || if (error != null || expectedOutput != collectedOutput.toString() ||
expectedResult != result expectedResult != result
) { ) {
println("Test failed: ${this.detailedString}") System.err.println("\nfailed: ${this.detailedString}")
} }
error?.let { error?.let {
fail("test failed", it) fail("test failed", it)