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,9 +326,11 @@ 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)
val callStatement = statement {
val context = closure!!.copy(pos, args)
if (argsDeclaration == null) { if (argsDeclaration == null) {
// no args: automatic var 'it' // no args: automatic var 'it'
val l = args.values val l = args.values
@ -360,7 +362,11 @@ class Compiler(
} }
} }
body.execute(context) body.execute(context)
}.asReadonly }
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
@ -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
@ -70,7 +76,8 @@ 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>()
@ -87,7 +94,8 @@ val ObjIterable by lazy { ObjClass("Iterable").apply {
ObjList(result) ObjList(result)
} }
} } }
}
/** /**
* Collection is an iterator with `size`] * Collection is an iterator with `size`]
@ -145,3 +153,7 @@ val ObjArray by lazy {
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)