diff --git a/docs/tutorial.md b/docs/tutorial.md index 9c517ac..03c61f8 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -475,7 +475,7 @@ Lyng has built-in mutable array class `List` with simple literals: [1, "two", 3.33].size >>> 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: @@ -1120,6 +1120,25 @@ These should be imported from [lyng.time](time.md). For example: 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 // single line comment diff --git a/lynglib/build.gradle.kts b/lynglib/build.gradle.kts index a527aa8..ea52f94 100644 --- a/lynglib/build.gradle.kts +++ b/lynglib/build.gradle.kts @@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget group = "net.sergeych" -version = "0.8.7-SNAPSHOT" +version = "0.8.8-SNAPSHOT" buildscript { repositories { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index de64f1a..8fca756 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -1,5 +1,6 @@ package net.sergeych.lyng +import ObjEnumClass import net.sergeych.lyng.obj.* import net.sergeych.lyng.pacman.ImportProvider @@ -858,7 +859,8 @@ class Compiler( "break" -> parseBreakStatement(id.pos) "continue" -> parseContinueStatement(id.pos) "if" -> parseIfStatement() - "class" -> parseClassDeclaration(false) + "class" -> parseClassDeclaration() + "enum" -> parseEnumDeclaration() "try" -> parseTryStatement() "throw" -> parseThrowStatement() "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() + // 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 constructorArgsDeclaration = if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) @@ -1178,8 +1213,8 @@ class Compiler( // create class val className = nameToken.value - @Suppress("UNUSED_VARIABLE") val defaultAccess = if (isStruct) AccessType.Var else AccessType.Initialization - @Suppress("UNUSED_VARIABLE") val defaultVisibility = Visibility.Public +// @Suppress("UNUSED_VARIABLE") val defaultAccess = if (isStruct) AccessType.Var else AccessType.Initialization +// @Suppress("UNUSED_VARIABLE") val defaultVisibility = Visibility.Public // create instance constructor // create custom objClass with all fields and instance constructor diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Token.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Token.kt index b1252c6..a795511 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Token.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Token.kt @@ -1,6 +1,10 @@ package net.sergeych.lyng 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 } @Suppress("unused") diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjEnum.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjEnum.kt new file mode 100644 index 0000000..be42048 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjEnum.kt @@ -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() + byName[name] ?: raiseSymbolNotFound("does not exists: enum ${className}.$name") + } + addFn("name") { thisAs().name } + addFn("ordinal") { thisAs().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): 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 + } + } + +} \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRecord.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRecord.kt index 0b049b1..a71b272 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRecord.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRecord.kt @@ -19,6 +19,9 @@ data class ObjRecord( @Suppress("unused") Fun, ConstructorField(true, true), + @Suppress("unused") + Class, + Enum, Other } @Suppress("unused") diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index 19a5717..edb7e70 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -2855,4 +2855,45 @@ class ScriptTest { """.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()) + } + + } \ No newline at end of file