From c0cf19045253985b287ee4effcdbbaf98111ec4b Mon Sep 17 00:00:00 2001 From: sergeych Date: Sun, 8 Jun 2025 00:30:13 +0400 Subject: [PATCH] fix: lambda now have correct closure --- docs/OOP.md | 15 ++ docs/advanced_topics.md | 14 +- docs/class_reference.md | 24 +++ .../kotlin/net/sergeych/lyng/Compiler.kt | 181 ++++++++++++++---- .../kotlin/net/sergeych/lyng/Obj.kt | 6 +- .../kotlin/net/sergeych/lyng/ObjClass.kt | 38 ++-- library/src/commonTest/kotlin/ScriptTest.kt | 12 ++ library/src/jvmTest/kotlin/BookTest.kt | 6 +- 8 files changed, 239 insertions(+), 57 deletions(-) create mode 100644 docs/class_reference.md diff --git a/docs/OOP.md b/docs/OOP.md index da320f5..56bce41 100644 --- a/docs/OOP.md +++ b/docs/OOP.md @@ -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 \ No newline at end of file diff --git a/docs/advanced_topics.md b/docs/advanced_topics.md index d7853b0..337627b 100644 --- a/docs/advanced_topics.md +++ b/docs/advanced_topics.md @@ -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 \ No newline at end of file diff --git a/docs/class_reference.md b/docs/class_reference.md new file mode 100644 index 0000000..e1825fb --- /dev/null +++ b/docs/class_reference.md @@ -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 \ No newline at end of file diff --git a/library/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/library/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 347f93f..bc12b00 100644 --- a/library/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/library/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -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, 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() 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.. 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") } } diff --git a/library/src/commonMain/kotlin/net/sergeych/lyng/Obj.kt b/library/src/commonMain/kotlin/net/sergeych/lyng/Obj.kt index fb03789..f9e27d8 100644 --- a/library/src/commonMain/kotlin/net/sergeych/lyng/Obj.kt +++ b/library/src/commonMain/kotlin/net/sergeych/lyng/Obj.kt @@ -8,7 +8,11 @@ import net.sergeych.synctools.ProtectedOp //typealias InstanceMethod = (Context, Obj) -> Obj -data class WithAccess(var value: T, val isMutable: Boolean) +data class WithAccess( + var value: T, + val isMutable: Boolean, + val visibility: Compiler.Visibility = Compiler.Visibility.Public +) data class Accessor( val getter: suspend (Context) -> WithAccess, diff --git a/library/src/commonMain/kotlin/net/sergeych/lyng/ObjClass.kt b/library/src/commonMain/kotlin/net/sergeych/lyng/ObjClass.kt index ecae5c1..05b435f 100644 --- a/library/src/commonMain/kotlin/net/sergeych/lyng/ObjClass.kt +++ b/library/src/commonMain/kotlin/net/sergeych/lyng/ObjClass.kt @@ -4,8 +4,14 @@ val ObjClassType by lazy { ObjClass("Class") } class ObjClass( val className: String, + val constructorArgs: List = emptyList(), vararg val parents: ObjClass, ) : Obj() { + constructor( + className: String, + vararg parents: ObjClass, + ) : this(className, emptyList(), *parents) + val allParentsSet: Set = parents.flatMap { listOf(it) + it.allParentsSet @@ -22,7 +28,7 @@ class ObjClass( // private var initInstanceHandler: (suspend (Context, List) -> 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() - val iterator = thisObj.invokeInstanceMethod(this, "iterator") + addFn("toList") { + val result = mutableListOf() + 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() - 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() } } -} \ No newline at end of file +} + +class ObjInstance(override val objClass: ObjClass): Obj() { + +} diff --git a/library/src/commonTest/kotlin/ScriptTest.kt b/library/src/commonTest/kotlin/ScriptTest.kt index c495862..e214509 100644 --- a/library/src/commonTest/kotlin/ScriptTest.kt +++ b/library/src/commonTest/kotlin/ScriptTest.kt @@ -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() + ) } } \ No newline at end of file diff --git a/library/src/jvmTest/kotlin/BookTest.kt b/library/src/jvmTest/kotlin/BookTest.kt index 275d7be..ab4a8d5 100644 --- a/library/src/jvmTest/kotlin/BookTest.kt +++ b/library/src/jvmTest/kotlin/BookTest.kt @@ -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)