fix: lambda now have correct closure
This commit is contained in:
parent
306a7f26ef
commit
c0cf190452
15
docs/OOP.md
15
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
|
@ -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
24
docs/class_reference.md
Normal 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
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>,
|
||||
|
@ -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() {
|
||||
|
||||
}
|
||||
|
@ -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()
|
||||
)
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user