closure tests and fix

This commit is contained in:
sergeych committed 2025-05-20 22:59:15 +04:00
1 parent ebeed385e9
commit 758f49603f
9 files changed
+193 -43

No files matched your search

@@ -498,11 +498,16 @@ class Compiler {
// Here we should be at open body
val fnStatements = parseBlock(tokens)
val fnBody = statement(t.pos) { context ->
// load params
var closure: Context? = null
val fnBody = statement(t.pos) { callerContext ->
// remember closure where the function was defined:
val context = closure ?: Context()
// load params from caller context
println("calling function $name in context $context <- ${context.parent}")
for ((i, d) in params.withIndex()) {
if (i < context.args.size)
context.addItem(d.name, false, context.args.list[i].value)
if (i < callerContext.args.size)
context.addItem(d.name, false, callerContext.args.list[i].value)
else
context.addItem(
d.name,
@@ -514,10 +519,12 @@ class Compiler {
)
)
}
// save closure
fnStatements.execute(context)
}
return statement(start) { context ->
println("adding function $name to context $context")
closure = context
context.addItem(name, false, fnBody)
fnBody
}
@@ -5,24 +5,21 @@ class Context(
val args: Arguments = Arguments.EMPTY
) {
data class Item(
val name: String,
var value: Obj?,
val isMutable: Boolean = false
)
private val objects = mutableMapOf<String, StoredObj>()
private val objects = mutableMapOf<String, Item>()
operator fun get(name: String): Item? = objects[name] ?: parent?.get(name)
operator fun get(name: String): StoredObj? =
objects[name]
?: parent?.get(name)
fun copy(args: Arguments = Arguments.EMPTY): Context = Context(this, args)
fun addItem(name: String, isMutable: Boolean, value: Obj?) {
objects.put(name, Item(name, value, isMutable))
println("ading item $name=$value in $this <- ${this.parent}")
objects.put(name, StoredObj(name, value, isMutable))
}
fun getOrCreateNamespace(name: String) =
(objects.getOrPut(name) { Item(name, ObjNamespace(name,copy()), isMutable = false) }.value as ObjNamespace)
(objects.getOrPut(name) { StoredObj(name, ObjNamespace(name,copy()), isMutable = false) }.value as ObjNamespace)
.context
inline fun <reified T> addFn(vararg names: String, crossinline fn: suspend Context.() -> T) {
@@ -1,33 +1,77 @@
package net.sergeych.ling
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlin.math.floor
typealias InstanceMethod = (Context, Obj) -> Obj
data class Item<T>(var value: T, val isMutable: Boolean = false)
@Serializable
sealed class ClassDef(
val className: String
) {
val baseClasses: List<ClassDef> get() = emptyList()
private val instanceMethods: MutableMap<String, Item<InstanceMethod>> get() = mutableMapOf()
private val instanceLock = Mutex()
suspend fun addInstanceMethod(
name: String,
freeze: Boolean = false,
pos: Pos = Pos.builtIn,
body: InstanceMethod
) {
instanceLock.withLock {
instanceMethods[name]?.let {
if( !it.isMutable )
throw ScriptError(pos, "existing method $name is frozen and can't be updated")
it.value = body
} ?: instanceMethods.put(name, Item(body, freeze))
}
}
//suspend fun callInstanceMethod(context: Context, self: Obj,args: Arguments): Obj {
//
// }
}
object ObjClassDef : ClassDef("Obj")
@Serializable
sealed class Obj : Comparable<Obj> {
open val asStr: ObjString by lazy {
if (this is ObjString) this else ObjString(this.toString())
}
open val type: Type = Type.Any
open val definition: ClassDef = ObjClassDef
@Suppress("unused")
enum class Type {
@SerialName("Void")
Void,
@SerialName("Null")
Null,
@SerialName("String")
String,
@SerialName("Int")
Int,
@SerialName("Real")
Real,
@SerialName("Bool")
Bool,
@SerialName("Fn")
Fn,
@SerialName("Any")
Any,
}
@@ -112,7 +156,8 @@ fun Obj.toLong(): Long =
fun Obj.toInt(): Int = toLong().toInt()
fun Obj.toBool(): Boolean = (this as? ObjBool)?.value ?: throw IllegalArgumentException("cannot convert to boolean ${this.type}:$this")
fun Obj.toBool(): Boolean =
(this as? ObjBool)?.value ?: throw IllegalArgumentException("cannot convert to boolean $this")
@Serializable
@@ -125,7 +170,7 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
override val toObjReal: ObjReal by lazy { ObjReal(value) }
override fun compareTo(other: Obj): Int {
if( other !is Numeric) throw IllegalArgumentException("cannot compare $this with $other")
if (other !is Numeric) throw IllegalArgumentException("cannot compare $this with $other")
return value.compareTo(other.doubleValue)
}
@@ -142,7 +187,7 @@ data class ObjInt(val value: Long) : Obj(), Numeric {
override val toObjReal: ObjReal by lazy { ObjReal(doubleValue) }
override fun compareTo(other: Obj): Int {
if( other !is Numeric) throw IllegalArgumentException("cannot compare $this with $other")
if (other !is Numeric) throw IllegalArgumentException("cannot compare $this with $other")
return value.compareTo(other.doubleValue)
}
@@ -155,9 +200,10 @@ data class ObjBool(val value: Boolean) : Obj() {
override val asStr by lazy { ObjString(value.toString()) }
override fun compareTo(other: Obj): Int {
if( other !is ObjBool) throw IllegalArgumentException("cannot compare $this with $other")
if (other !is ObjBool) throw IllegalArgumentException("cannot compare $this with $other")
return value.compareTo(other.value)
}
override fun toString(): String = value.toString()
}
@@ -9,6 +9,7 @@ class Script(
override suspend fun execute(context: Context): Obj {
// todo: run script
println("exec script in $context <- ${context.parent}")
var lastResult: Obj = ObjVoid
for (s in statements) {
lastResult = s.execute(context)
@@ -0,0 +1,10 @@
package net.sergeych.ling
/**
* Whatever [Obj] stored somewhere
*/
data class StoredObj(
val name: String,
var value: Obj?,
val isMutable: Boolean = false
)
+29 -16
View File
@@ -34,22 +34,21 @@ data class DocTest(
val sourceLines by lazy { code.lines() }
override fun toString(): String {
return "DocTest:$fileName:${line+1}..${line + sourceLines.size}"
return "DocTest:$fileName:${line + 1}..${line + sourceLines.size}"
}
val detailedString by lazy {
val codeWithLines = sourceLines.withIndex().map { (i, s) -> "${i + line}: $s" }.joinToString("\n")
"$this\n" +
codeWithLines + "\n" +
"--------expected output--------\n" +
expectedOutput +
"-----expected return value-----\n" +
expectedResult
var result = "$this\n$codeWithLines\n"
if (expectedOutput.isNotBlank())
result += "--------expected output--------\n$expectedOutput\n"
"$result-----expected return value-----\n$expectedResult"
}
}
fun parseDocTests(name: String): Flow<DocTest> = flow {
val book = readAllLines(Paths.get("../docs/tutorial.md"))
fun parseDocTests(fileName: String): Flow<DocTest> = flow {
val book = readAllLines(Paths.get(fileName))
var startOffset = 0
val block = mutableListOf<String>()
var startIndex = 0
@@ -94,10 +93,10 @@ fun parseDocTests(name: String): Flow<DocTest> = flow {
if (isValid) {
emit(
DocTest(
name, startIndex,
fileName, startIndex,
block.joinToString("\n"),
if (result.size > 1)
result.dropLast(1).joinToString { it + "\n" }
result.dropLast(1).joinToString("") { it + "\n" }
else "",
result.last()
)
@@ -149,8 +148,7 @@ suspend fun DocTest.test() {
var error: Throwable? = null
val result = try {
context.eval(code)
}
catch (e: Throwable) {
} catch (e: Throwable) {
error = e
null
}?.toString()?.replace(Regex("@\\d+"), "@...")
@@ -166,12 +164,27 @@ suspend fun DocTest.test() {
// println("OK: $this")
}
suspend fun runDocTests(fileName: String) {
parseDocTests(fileName).collect { dt ->
dt.test()
}
}
class BookTest {
@Test
fun testsFromTutorial() = runTest {
parseDocTests("../docs/tutorial.md").collect { dt ->
dt.test()
}
runDocTests("../docs/tutorial.md")
}
@Test
fun testsFromMath() = runTest {
runDocTests("../docs/math.md")
}
@Test
fun testsFromAdvanced() = runTest {
runDocTests("../docs/advanced_topics.md")
}
}