smple enums w/serialization

This commit is contained in:
Sergey Chernov 2025-08-12 14:47:41 +03:00
parent 84e345b04e
commit b5e89c7e78
7 changed files with 170 additions and 6 deletions

View File

@ -475,7 +475,7 @@ Lyng has built-in mutable array class `List` with simple literals:
[1, "two", 3.33].size [1, "two", 3.33].size
>>> 3 >>> 3
[List] is an implementation of the type `Array`, and through it `Collection` and [Iterable]. [List] is an implementation of the type `Array`, and through it `Collection` and [Iterable]. Please read [Iterable], many collection based methods are implemented there.
Lists can contain any type of objects, lists too: Lists can contain any type of objects, lists too:
@ -1120,6 +1120,25 @@ These should be imported from [lyng.time](time.md). For example:
See [more docs on time manipulation](time.md) See [more docs on time manipulation](time.md)
# Enums
For the moment, only simple enums are implemented. Enum is a list of constants, represented also by their _ordinal_ - [Int] value.
enum Color {
RED, GREEN, BLUE
}
assert( Color.RED is Color )
assertEquals( 2, Color.BLUE.ordinal )
assertEquals( "BLUE", Color.BLUE.name )
assertEquals( [Color.RED,Color.GREEN,Color.BLUE], Color.entries)
assertEquals( Color.valueOf("GREEN"), Color.GREEN )
>>> void
Enums are serialized as ordinals. Please note that due to caching, serialized string arrays could be even more compact than enum arrays, until `Lynon.encodeTyped` will be implemented.
# Comments # Comments
// single line comment // single line comment

View File

@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
group = "net.sergeych" group = "net.sergeych"
version = "0.8.7-SNAPSHOT" version = "0.8.8-SNAPSHOT"
buildscript { buildscript {
repositories { repositories {

View File

@ -1,5 +1,6 @@
package net.sergeych.lyng package net.sergeych.lyng
import ObjEnumClass
import net.sergeych.lyng.obj.* import net.sergeych.lyng.obj.*
import net.sergeych.lyng.pacman.ImportProvider import net.sergeych.lyng.pacman.ImportProvider
@ -858,7 +859,8 @@ class Compiler(
"break" -> parseBreakStatement(id.pos) "break" -> parseBreakStatement(id.pos)
"continue" -> parseContinueStatement(id.pos) "continue" -> parseContinueStatement(id.pos)
"if" -> parseIfStatement() "if" -> parseIfStatement()
"class" -> parseClassDeclaration(false) "class" -> parseClassDeclaration()
"enum" -> parseEnumDeclaration()
"try" -> parseTryStatement() "try" -> parseTryStatement()
"throw" -> parseThrowStatement() "throw" -> parseThrowStatement()
"when" -> parseWhenStatement() "when" -> parseWhenStatement()
@ -1145,7 +1147,40 @@ class Compiler(
} }
} }
private suspend fun parseClassDeclaration(isStruct: Boolean): Statement { private fun parseEnumDeclaration(): Statement {
val nameToken = cc.requireToken(Token.Type.ID)
// so far only simplest enums:
val names = mutableListOf<String>()
// skip '{'
cc.skipTokenOfType(Token.Type.LBRACE)
do {
val t = cc.skipWsTokens()
when(t.type) {
Token.Type.ID -> {
names += t.value
val t1 = cc.skipWsTokens()
when(t1.type) {
Token.Type.COMMA ->
continue
Token.Type.RBRACE -> break
else -> {
t1.raiseSyntax("unexpected token")
}
}
}
else -> t.raiseSyntax("expected enum entry name")
}
} while(true)
return statement {
ObjEnumClass.createSimpleEnum(nameToken.value, names).also {
addItem(nameToken.value, false, it, recordType = ObjRecord.Type.Enum)
}
}
}
private suspend fun parseClassDeclaration(): Statement {
val nameToken = cc.requireToken(Token.Type.ID) val nameToken = cc.requireToken(Token.Type.ID)
val constructorArgsDeclaration = val constructorArgsDeclaration =
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
@ -1178,8 +1213,8 @@ class Compiler(
// create class // create class
val className = nameToken.value val className = nameToken.value
@Suppress("UNUSED_VARIABLE") val defaultAccess = if (isStruct) AccessType.Var else AccessType.Initialization // @Suppress("UNUSED_VARIABLE") val defaultAccess = if (isStruct) AccessType.Var else AccessType.Initialization
@Suppress("UNUSED_VARIABLE") val defaultVisibility = Visibility.Public // @Suppress("UNUSED_VARIABLE") val defaultVisibility = Visibility.Public
// create instance constructor // create instance constructor
// create custom objClass with all fields and instance constructor // create custom objClass with all fields and instance constructor

View File

@ -1,6 +1,10 @@
package net.sergeych.lyng package net.sergeych.lyng
data class Token(val value: String, val pos: Pos, val type: Type) { data class Token(val value: String, val pos: Pos, val type: Type) {
fun raiseSyntax(text: String): Nothing {
throw ScriptError(pos, text)
}
val isComment: Boolean by lazy { type == Type.SINLGE_LINE_COMMENT || type == Type.MULTILINE_COMMENT } val isComment: Boolean by lazy { type == Type.SINLGE_LINE_COMMENT || type == Type.MULTILINE_COMMENT }
@Suppress("unused") @Suppress("unused")

View File

@ -0,0 +1,62 @@
import net.sergeych.lyng.Scope
import net.sergeych.lyng.obj.*
import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType
open class ObjEnumEntry(enumClass: ObjEnumClass, val name: ObjString, val ordinal: ObjInt) : Obj() {
override val objClass = enumClass
override fun toString(): String {
return "$objClass.$name"
}
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
encoder.encodeUnsigned(ordinal.value.toULong())
}
override suspend fun compareTo(scope: Scope, other: Obj): Int {
if( other !is ObjEnumEntry) return -2
if( other.objClass != objClass ) return -2
return ordinal.compareTo(scope, other.ordinal)
}
}
object EnumBase : ObjClass("Enum") {
}
class ObjEnumClass(val name: String) : ObjClass(name, EnumBase) {
val objEntries = ObjList()
val byName by lazy { objEntries.list.associateBy { (it as ObjEnumEntry).name } }
init {
addClassConst("entries", objEntries )
addClassFn("valueOf") {
val name = requireOnlyArg<ObjString>()
byName[name] ?: raiseSymbolNotFound("does not exists: enum ${className}.$name")
}
addFn("name") { thisAs<ObjEnumEntry>().name }
addFn("ordinal") { thisAs<ObjEnumEntry>().ordinal }
}
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
val index = decoder.unpackUnsigned().toInt()
return objEntries.list[index]
}
companion object {
fun createSimpleEnum(enumName: String, names: List<String>): ObjEnumClass {
val klass = ObjEnumClass(enumName)
names.forEachIndexed { index, name ->
val entry = ObjEnumEntry(klass, ObjString(name), ObjInt(index.toLong(), isConst = true))
klass.objEntries.list += entry
klass.addClassConst(name, entry)
}
return klass
}
}
}

View File

@ -19,6 +19,9 @@ data class ObjRecord(
@Suppress("unused") @Suppress("unused")
Fun, Fun,
ConstructorField(true, true), ConstructorField(true, true),
@Suppress("unused")
Class,
Enum,
Other Other
} }
@Suppress("unused") @Suppress("unused")

View File

@ -2855,4 +2855,45 @@ class ScriptTest {
""".trimIndent()) """.trimIndent())
} }
@Test
fun enumTest() = runTest {
eval(
"""
enum Color {
RED, GREEN, BLUE
}
assert( Color.RED is Color )
assertEquals( 2, Color.BLUE.ordinal )
assertEquals( "BLUE", Color.BLUE.name )
assertEquals( [Color.RED,Color.GREEN,Color.BLUE], Color.entries)
assertEquals( Color.valueOf("GREEN"), Color.GREEN )
""".trimIndent())
}
@Test
fun enumSerializationTest() = runTest {
eval("""
import lyng.serialization
enum Color {
RED, GREEN, BLUE
}
val e = Lynon.encode(Color.BLUE)
assertEquals( Color.BLUE, Lynon.decode(e) )
println(e.toDump())
val e1 = Lynon.encode( (1..100).map { Color.GREEN } )
println(e1.toDump())
println(Lynon.encode( (1..100).map { "RED" } ).toDump() )
""".trimIndent())
}
} }