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;
2. parents method: no guarantee but we enumerate parents in order of appearance;
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
>>> 1
>>> 2
>>> void
>> void
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
@ -75,3 +75,15 @@ The example above could be rewritten using inner lambda, too:
>>> 1
>>> 2
>>> 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")
val pos = cc.currentPos()
val body = parseBlock(cc, skipLeadingBrace = true)
return Accessor { _ ->
statement {
val context = this.copy(pos)
if (argsDeclaration == null) {
// no args: automatic var 'it'
val l = args.values
val itValue: Obj = when (l.size) {
// no args: it == void
0 -> ObjVoid
// one args: it is this arg
1 -> l[0]
// more args: it is a list of args
else -> ObjList(l.toMutableList())
}
context.addItem("it", false, itValue)
} else {
// assign vars as declared
if (args.size != argsDeclaration.args.size && !argsDeclaration.args.last().isEllipsis)
raiseArgumentError("Too many arguments : called with ${args.size}, lambda accepts only ${argsDeclaration.args.size}")
for ((n, a) in argsDeclaration.args.withIndex()) {
if (n >= args.size) {
if (a.initialValue != null)
context.addItem(a.name, false, a.initialValue.execute(context))
else throw ScriptError(a.pos, "argument $n is out of scope")
} else {
val value = if (a.isEllipsis) {
ObjList(args.values.subList(n, args.values.size).toMutableList())
} else
args[n]
context.addItem(a.name, false, value)
}
var closure: Context? = null
val callStatement = statement {
val context = closure!!.copy(pos, args)
if (argsDeclaration == null) {
// no args: automatic var 'it'
val l = args.values
val itValue: Obj = when (l.size) {
// no args: it == void
0 -> ObjVoid
// one args: it is this arg
1 -> l[0]
// more args: it is a list of args
else -> ObjList(l.toMutableList())
}
context.addItem("it", false, itValue)
} else {
// assign vars as declared
if (args.size != argsDeclaration.args.size && !argsDeclaration.args.last().isEllipsis)
raiseArgumentError("Too many arguments : called with ${args.size}, lambda accepts only ${argsDeclaration.args.size}")
for ((n, a) in argsDeclaration.args.withIndex()) {
if (n >= args.size) {
if (a.initialValue != null)
context.addItem(a.name, false, a.initialValue.execute(context))
else throw ScriptError(a.pos, "argument $n is out of scope")
} else {
val value = if (a.isEllipsis) {
ObjList(args.values.subList(n, args.values.size).toMutableList())
} 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(
val name: String,
val type: TypeDecl = TypeDecl.Obj,
val pos: Pos,
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) {
@ -421,16 +437,73 @@ class Compiler(
* Parse argument declaration, used in lambda (and later in fn too)
* @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>()
var endTokenType: Token.Type? = null
val startPos = cc.savePos()
while (endTokenType == null) {
val t = cc.next()
var t = cc.next()
when (t.type) {
Token.Type.NEWLINE -> {}
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
cc.ifNextIs(Token.Type.ASSIGN) {
defaultValue = parseExpression(cc)
@ -438,7 +511,7 @@ class Compiler(
// type information
val typeInfo = parseTypeDeclaration(cc)
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 ')'
// otherwise it is not an argument list:
@ -630,9 +703,36 @@ class Compiler(
"continue" -> parseContinueStatement(id.pos, cc)
"fn", "fun" -> parseFunctionDeclaration(cc)
"if" -> parseIfStatement(cc)
"class" -> parseClassDeclaration(cc, false)
"struct" -> parseClassDeclaration(cc, true)
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? {
var cnt = 0
var found: String? = null
@ -749,7 +849,7 @@ class Compiler(
var result: Obj = ObjVoid
val iVar = ObjInt(0)
loopVar.value = iVar
if( catchBreak) {
if (catchBreak) {
for (i in start..<end) {
iVar.value = i.toLong()
try {
@ -1185,7 +1285,8 @@ class Compiler(
Operator.simple(Token.Type.NOTIS, lastPrty) { c, a, b -> ObjBool(!a.isInstanceOf(b)) },
// shuttle <=> 6
Operator.simple(Token.Type.SHUTTLE, ++lastPrty) { c, a, b ->
ObjInt(a.compareTo(c, b).toLong()) },
ObjInt(a.compareTo(c, b).toLong())
},
// bit shifts 7
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) },
@ -1213,7 +1314,7 @@ class Compiler(
/**
* 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
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(
val getter: suspend (Context) -> WithAccess<Obj>,

View File

@ -4,8 +4,14 @@ val ObjClassType by lazy { ObjClass("Class") }
class ObjClass(
val className: String,
val constructorArgs: List<Compiler.ArgVar> = emptyList(),
vararg val parents: ObjClass,
) : Obj() {
constructor(
className: String,
vararg parents: ObjClass,
) : this(className, emptyList(), *parents)
val allParentsSet: Set<ObjClass> = parents.flatMap {
listOf(it) + it.allParentsSet
@ -22,7 +28,7 @@ class ObjClass(
// 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())
// ?: context.raiseError("No initInstance handler for $this")
//
@ -70,24 +76,26 @@ class ObjClass(
/**
* 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") {
val result = mutableListOf<Obj>()
val iterator = thisObj.invokeInstanceMethod(this, "iterator")
addFn("toList") {
val result = mutableListOf<Obj>()
val iterator = thisObj.invokeInstanceMethod(this, "iterator")
while( iterator.invokeInstanceMethod(this, "hasNext").toBool() )
result += iterator.invokeInstanceMethod(this, "next")
while (iterator.invokeInstanceMethod(this, "hasNext").toBool())
result += iterator.invokeInstanceMethod(this, "next")
// val next = iterator.getMemberOrNull("next")!!
// val hasNext = iterator.getMemberOrNull("hasNext")!!
// while( hasNext.invoke(this, iterator).toBool() )
// result += next.invoke(this, iterator)
ObjList(result)
}
ObjList(result)
}
} }
}
}
/**
* Collection is an iterator with `size`]
@ -123,7 +131,7 @@ class ObjArrayIterator(val array: Obj) : Obj() {
}
addFn("hasNext") {
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") {
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(
"""
fun mapValues(iterable, transform) {
println("start: ", transform)
var result = []
for( x in iterable ) result += transform(x)
}
@ -1238,6 +1239,17 @@ class ScriptTest {
assert( 5 <=> 7 < 0 )
""".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.readAllLines
import java.nio.file.Paths
import kotlin.io.path.absolutePathString
import kotlin.io.path.extension
import kotlin.test.Test
import kotlin.test.assertEquals
@ -41,7 +42,8 @@ data class DocTest(
}
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 {
@ -190,7 +192,7 @@ suspend fun DocTest.test(context: Context = Context()) {
if (error != null || expectedOutput != collectedOutput.toString() ||
expectedResult != result
) {
println("Test failed: ${this.detailedString}")
System.err.println("\nfailed: ${this.detailedString}")
}
error?.let {
fail("test failed", it)