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,9 +326,11 @@ 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)
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
@ -360,7 +362,11 @@ class Compiler(
}
}
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(
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
@ -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
@ -70,7 +76,8 @@ 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>()
@ -87,7 +94,8 @@ val ObjIterable by lazy { ObjClass("Iterable").apply {
ObjList(result)
}
} }
}
}
/**
* Collection is an iterator with `size`]
@ -145,3 +153,7 @@ val ObjArray by lazy {
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)