diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 562f132f6..3702505c6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,7 +6,7 @@ variables: BEFORE_TASK: "" AFTER_TASK: "" GCMD: "gradle" - BUILD_TASK: "assemble" + BUILD_TASK: "clean assemble" TEST_TASK: "check -x dokka" TEST_JVM_TASK: "jvmTest -x dokka" TEST_JS_TASK: "clean jsTest -x dokka" diff --git a/buildSrc/src/main/kotlin/Libs.kt b/buildSrc/src/main/kotlin/Libs.kt index 553f0497a..311afee67 100644 --- a/buildSrc/src/main/kotlin/Libs.kt +++ b/buildSrc/src/main/kotlin/Libs.kt @@ -7,6 +7,20 @@ import kotlin.String * `$ ./gradlew buildSrcVersions` */ object Libs { + /** + * https://github.com/FasterXML/jackson-dataformat-xml + */ + const val jackson_dataformat_xml: String = + "com.fasterxml.jackson.dataformat:jackson-dataformat-xml:" + + Versions.com_fasterxml_jackson_dataformat + + /** + * https://github.com/FasterXML/jackson-dataformats-text + */ + const val jackson_dataformat_yaml: String = + "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:" + + Versions.com_fasterxml_jackson_dataformat + /** * https://javaeden.github.io/Orchid/latest/core/ */ @@ -123,11 +137,24 @@ object Libs { Versions.com_jfrog_bintray_gradle_plugin /** - * 2.7.1 + * https://github.com/FasterXML/jackson-modules-java8 + */ + const val jackson_datatype_jsr310: String = + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:" + + Versions.jackson_datatype_jsr310 + + /** + * 2.8.0 */ const val clikt_multiplatform: String = "com.github.ajalt:clikt-multiplatform:" + Versions.clikt_multiplatform + /** + * https://github.com/FasterXML/jackson-core + */ + const val jackson_core: String = "com.fasterxml.jackson.core:jackson-core:" + + Versions.jackson_core + /** * http://plantuml.sourceforge.net */ diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 97ef92b2f..5437d3b76 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -12,7 +12,9 @@ import org.gradle.plugin.use.PluginDependencySpec * YOU are responsible for updating manually the dependency version. */ object Versions { - const val io_github_javaeden_orchid: String = "0.21.0" + const val com_fasterxml_jackson_dataformat: String = "2.11.1" + + const val io_github_javaeden_orchid: String = "0.21.1" const val org_jetbrains_kotlin: String = "1.3.72" @@ -24,18 +26,22 @@ object Versions { const val org_jetbrains_kotlin_multiplatform_gradle_plugin: String = "1.3.72" - const val com_github_johnrengelman_shadow_gradle_plugin: String = "5.2.0" + const val com_github_johnrengelman_shadow_gradle_plugin: String = "6.0.0" const val de_fayard_buildsrcversions_gradle_plugin: String = "0.7.0" - const val com_eden_orchidplugin_gradle_plugin: String = "0.20.0" // available: "0.21.0" + const val com_eden_orchidplugin_gradle_plugin: String = "0.21.1" const val org_jetbrains_dokka_gradle_plugin: String = "0.10.1" const val com_jfrog_bintray_gradle_plugin: String = "1.8.5" + const val jackson_datatype_jsr310: String = "2.11.1" + const val clikt_multiplatform: String = "2.7.1" + const val jackson_core: String = "2.11.1" + const val plantuml: String = "1.2020.2" // available: "8059" const val kt_math: String = "0.1.3" diff --git a/core/src/commonMain/kotlin/it/unibo/tuprolog/core/Real.kt b/core/src/commonMain/kotlin/it/unibo/tuprolog/core/Real.kt index c17e939a5..0bd4babca 100644 --- a/core/src/commonMain/kotlin/it/unibo/tuprolog/core/Real.kt +++ b/core/src/commonMain/kotlin/it/unibo/tuprolog/core/Real.kt @@ -32,6 +32,13 @@ interface Real : Numeric { @JvmField val REAL_REGEX_PATTERN = "^[+\\-]?(($INT$DEC$EXP?)|($INT$EXP)|($DEC$EXP?))$".toRegex() + @JvmStatic + @JsName("toStringEnsuringDecimal") + fun toStringEnsuringDecimal(real: BigDecimal): String = + real.toString().let { + if ("." !in it) "$it.0" else it + } + @JvmStatic @JsName("ofBigDecimal") fun of(real: BigDecimal): Real = RealImpl(real) diff --git a/core/src/commonMain/kotlin/it/unibo/tuprolog/core/Scope.kt b/core/src/commonMain/kotlin/it/unibo/tuprolog/core/Scope.kt index d3a6e1ec0..a17c20720 100644 --- a/core/src/commonMain/kotlin/it/unibo/tuprolog/core/Scope.kt +++ b/core/src/commonMain/kotlin/it/unibo/tuprolog/core/Scope.kt @@ -40,6 +40,12 @@ interface Scope { @JsName("structOfSequence") fun structOf(functor: String, args: Sequence): Struct + @JsName("structOfIterable") + fun structOf(functor: String, args: Iterable): Struct + + @JsName("structOfList") + fun structOf(functor: String, args: List): Struct + @JsName("tupleOf") fun tupleOf(vararg terms: Term): Tuple diff --git a/core/src/commonMain/kotlin/it/unibo/tuprolog/core/Struct.kt b/core/src/commonMain/kotlin/it/unibo/tuprolog/core/Struct.kt index e1f6d1ab2..5ea6edd5b 100644 --- a/core/src/commonMain/kotlin/it/unibo/tuprolog/core/Struct.kt +++ b/core/src/commonMain/kotlin/it/unibo/tuprolog/core/Struct.kt @@ -3,7 +3,6 @@ package it.unibo.tuprolog.core import it.unibo.tuprolog.core.impl.StructImpl import kotlin.js.JsName import kotlin.jvm.JvmField -import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmStatic import kotlin.collections.List as KtList @@ -144,6 +143,10 @@ interface Struct : Term { @JsName("ofSequence") fun of(functor: String, args: Sequence): Struct = of(functor, args.toList()) + @JvmStatic + @JsName("ofIterable") + fun of(functor: String, args: Iterable): Struct = of(functor, args.toList()) + @JvmStatic @JsName("foldListNullTerminated") fun fold(operator: String, terms: KtList): Struct = diff --git a/core/src/commonMain/kotlin/it/unibo/tuprolog/core/impl/RealImpl.kt b/core/src/commonMain/kotlin/it/unibo/tuprolog/core/impl/RealImpl.kt index 516f3bbe6..6cd0f851a 100644 --- a/core/src/commonMain/kotlin/it/unibo/tuprolog/core/impl/RealImpl.kt +++ b/core/src/commonMain/kotlin/it/unibo/tuprolog/core/impl/RealImpl.kt @@ -14,7 +14,8 @@ internal class RealImpl(override val value: BigDecimal) : NumericImpl(), Real { value.toBigInteger() } - override fun toString(): String = value.toString() + override fun toString(): String = + Real.toStringEnsuringDecimal(value) override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/core/src/commonMain/kotlin/it/unibo/tuprolog/core/impl/ScopeImpl.kt b/core/src/commonMain/kotlin/it/unibo/tuprolog/core/impl/ScopeImpl.kt index c2491d2ee..48ff61527 100644 --- a/core/src/commonMain/kotlin/it/unibo/tuprolog/core/impl/ScopeImpl.kt +++ b/core/src/commonMain/kotlin/it/unibo/tuprolog/core/impl/ScopeImpl.kt @@ -91,6 +91,12 @@ internal class ScopeImpl(private val _variables: MutableMap) : Scop override fun structOf(functor: String, args: Sequence): Struct = Struct.of(functor, args) + override fun structOf(functor: String, args: Iterable): Struct = + Struct.of(functor, args) + + override fun structOf(functor: String, args: List): Struct = + Struct.of(functor, args) + override fun factOf(head: Struct): Fact = Fact.of(head) diff --git a/core/src/commonTest/kotlin/it/unibo/tuprolog/core/impl/RealImplTest.kt b/core/src/commonTest/kotlin/it/unibo/tuprolog/core/impl/RealImplTest.kt index 1d0d13296..795d4f4b4 100644 --- a/core/src/commonTest/kotlin/it/unibo/tuprolog/core/impl/RealImplTest.kt +++ b/core/src/commonTest/kotlin/it/unibo/tuprolog/core/impl/RealImplTest.kt @@ -47,7 +47,7 @@ internal class RealImplTest { @Test fun correctToString() { - val expectedToString = RealUtils.bigDecimals.map { it.toString() } + val expectedToString = RealUtils.bigDecimals.map { Real.toStringEnsuringDecimal(it) } onCorrespondingItems(expectedToString, realInstances.map { it.toString() }) { expectedString, realToString -> assertEquals(expectedString, realToString) diff --git a/serialize-core/build.gradle.kts b/serialize-core/build.gradle.kts new file mode 100644 index 000000000..0342c9779 --- /dev/null +++ b/serialize-core/build.gradle.kts @@ -0,0 +1,29 @@ +kotlin { + + sourceSets { + val commonMain by getting { + dependencies { + api(project(":core")) + } + } + + jvm { + compilations["main"].defaultSourceSet { + dependencies { + implementation(Libs.jackson_core) + implementation(Libs.jackson_datatype_jsr310) + implementation(Libs.jackson_dataformat_yaml) + implementation(Libs.jackson_dataformat_xml) + } + } + } + + js { + compilations["main"].defaultSourceSet { + dependencies { + api(npm("yaml", "^1.10.0")) + } + } + } + } +} diff --git a/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/DeobjectificationException.kt b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/DeobjectificationException.kt new file mode 100644 index 000000000..639e5dca7 --- /dev/null +++ b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/DeobjectificationException.kt @@ -0,0 +1,5 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.core.exception.TuPrologException + +class DeobjectificationException(`object`: Any) : TuPrologException("Error while deobjectifying $`object`") \ No newline at end of file diff --git a/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/Deobjectifier.kt b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/Deobjectifier.kt new file mode 100644 index 000000000..526f44cfb --- /dev/null +++ b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/Deobjectifier.kt @@ -0,0 +1,11 @@ +package it.unibo.tuprolog.serialize + +import kotlin.js.JsName + +interface Deobjectifier { + @JsName("deobjectify") + fun deobjectify(`object`: Any): T + + @JsName("deobjectifyMany") + fun deobjectifyMany(`object`: Any): Iterable +} \ No newline at end of file diff --git a/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/Deserializer.kt b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/Deserializer.kt new file mode 100644 index 000000000..cc9baea8c --- /dev/null +++ b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/Deserializer.kt @@ -0,0 +1,14 @@ +package it.unibo.tuprolog.serialize + +import kotlin.js.JsName + +interface Deserializer { + @JsName("mimeType") + val mimeType: MimeType + + @JsName("deserialize") + fun deserialize(string: String): T + + @JsName("deserializeMany") + fun deserializeMany(string: String): Iterable +} \ No newline at end of file diff --git a/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/MimeType.kt b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/MimeType.kt new file mode 100644 index 000000000..1b5030b15 --- /dev/null +++ b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/MimeType.kt @@ -0,0 +1,18 @@ +package it.unibo.tuprolog.serialize + +import kotlin.js.JsName + +sealed class MimeType( + @JsName("type") + val type: String, + @JsName("subType") + val subType: String +) { + + object Json : MimeType("application", "json") + + object Yaml : MimeType("application", "yaml") + + object Xml : MimeType("application", "xml") + +} \ No newline at end of file diff --git a/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/Objectifier.kt b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/Objectifier.kt new file mode 100644 index 000000000..1ad0b7726 --- /dev/null +++ b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/Objectifier.kt @@ -0,0 +1,19 @@ +package it.unibo.tuprolog.objectify + +import kotlin.js.JsName + +interface Objectifier { + @JsName("objectify") + fun objectify(value: T): Any + + @JsName("objectifyMany") + fun objectifyMany(vararg values: T): Any = + objectifyMany(listOf(*values)) + + @JsName("objectifyManyIterable") + fun objectifyMany(values: Iterable): Any + + @JsName("objectifyManySequence") + fun objectifyMany(values: Sequence): Any = + objectifyMany(values.asIterable()) +} \ No newline at end of file diff --git a/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/ObjectsUtils.kt b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/ObjectsUtils.kt new file mode 100644 index 000000000..6f234684d --- /dev/null +++ b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/ObjectsUtils.kt @@ -0,0 +1,7 @@ +package it.unibo.tuprolog.serialize + +expect object ObjectsUtils { + fun parseAsObject(string: String, mimeType: MimeType): Any + + fun deeplyEqual(obj1: Any?, obj2: Any?): Boolean +} \ No newline at end of file diff --git a/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/SerializationException.kt b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/SerializationException.kt new file mode 100644 index 000000000..91a084c17 --- /dev/null +++ b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/SerializationException.kt @@ -0,0 +1,6 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.core.Term +import it.unibo.tuprolog.core.exception.TuPrologException + +class SerializationException(term: Term) : TuPrologException("Error while serialising $term") \ No newline at end of file diff --git a/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/Serializer.kt b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/Serializer.kt new file mode 100644 index 000000000..48442f675 --- /dev/null +++ b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/Serializer.kt @@ -0,0 +1,22 @@ +package it.unibo.tuprolog.serialize + +import kotlin.js.JsName + +interface Serializer { + @JsName("mimeType") + val mimeType: MimeType + + @JsName("serialize") + fun serialize(value: T): String + + @JsName("serializeMany") + fun serializeMany(vararg values: T): String = + serializeMany(listOf(*values)) + + @JsName("serializeManyIterable") + fun serializeMany(values: Iterable): String + + @JsName("serializeManySequence") + fun serializeMany(values: Sequence): String = + serializeMany(values.asIterable()) +} \ No newline at end of file diff --git a/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TermDeobjectifier.kt b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TermDeobjectifier.kt new file mode 100644 index 000000000..8736ec752 --- /dev/null +++ b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TermDeobjectifier.kt @@ -0,0 +1,14 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.core.Term +import kotlin.js.JsName +import kotlin.jvm.JvmStatic + +interface TermDeobjectifier : Deobjectifier { + companion object { + @JsName("default") + @JvmStatic + val default: TermDeobjectifier + get() = termDeobjectifier() + } +} \ No newline at end of file diff --git a/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TermDeserializer.kt b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TermDeserializer.kt new file mode 100644 index 000000000..c1e407ced --- /dev/null +++ b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TermDeserializer.kt @@ -0,0 +1,15 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.core.Term +import kotlin.js.JsName +import kotlin.jvm.JvmStatic + +interface TermDeserializer : Deserializer { + companion object { + @JvmStatic + @JsName("of") + fun of(mimeType: MimeType): TermDeserializer { + return termDeserializer(mimeType) + } + } +} \ No newline at end of file diff --git a/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TermObjectifier.kt b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TermObjectifier.kt new file mode 100644 index 000000000..475ff872d --- /dev/null +++ b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TermObjectifier.kt @@ -0,0 +1,18 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.core.Term +import it.unibo.tuprolog.core.TermVisitor +import it.unibo.tuprolog.objectify.Objectifier +import kotlin.js.JsName +import kotlin.jvm.JvmStatic + +interface TermObjectifier : Objectifier, TermVisitor { + override fun objectify(value: Term): Any = visit(value) + + companion object { + @JsName("default") + @JvmStatic + val default: TermObjectifier + get() = termObjectifier() + } +} \ No newline at end of file diff --git a/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TermSerialization.kt b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TermSerialization.kt new file mode 100644 index 000000000..931b6dc21 --- /dev/null +++ b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TermSerialization.kt @@ -0,0 +1,13 @@ +@file:JvmName("TermSerialization") + +package it.unibo.tuprolog.serialize + +import kotlin.jvm.JvmName + +expect fun termSerializer(mimeType: MimeType): TermSerializer + +expect fun termDeserializer(mimeType: MimeType): TermDeserializer + +expect fun termObjectifier(): TermObjectifier + +expect fun termDeobjectifier(): TermDeobjectifier \ No newline at end of file diff --git a/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TermSerializer.kt b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TermSerializer.kt new file mode 100644 index 000000000..8e66c4af1 --- /dev/null +++ b/serialize-core/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TermSerializer.kt @@ -0,0 +1,17 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.core.Term +import kotlin.js.JsName +import kotlin.jvm.JvmStatic + +interface TermSerializer : Serializer { + + companion object { + @JvmStatic + @JsName("of") + fun of(mimeType: MimeType): TermSerializer { + return termSerializer(mimeType) + } + } + +} \ No newline at end of file diff --git a/serialize-core/src/commonTest/kotlin/it/unibo/tuprolog/serialize/TestTermDeserializer.kt b/serialize-core/src/commonTest/kotlin/it/unibo/tuprolog/serialize/TestTermDeserializer.kt new file mode 100644 index 000000000..002c87fc5 --- /dev/null +++ b/serialize-core/src/commonTest/kotlin/it/unibo/tuprolog/serialize/TestTermDeserializer.kt @@ -0,0 +1,230 @@ +package it.unibo.tuprolog.serialize + +import kotlin.test.Test +import kotlin.test.assertEquals + +class TestTermDeserializer { + @Test + fun testTailedListSerializationInJSON() { + val deserializer: TermDeserializer = TermDeserializer.of(MimeType.Json) + assertEquals(MimeType.Json, deserializer.mimeType) + + deserializer.assertTermDeserializationWorks("{\"fun\":\"member\",\"args\":[{\"var\":\"H\"},{\"list\":[{\"var\":\"H\"}],\"tail\":{\"var\":\"_\"}}]}") { + structOf("member", varOf("H"), consOf(varOf("H"), anonymous())) + } + } + + @Test + fun testTailedListSerializationInYAML() { + val deserializer: TermDeserializer = TermDeserializer.of(MimeType.Yaml) + assertEquals(MimeType.Yaml, deserializer.mimeType) + + deserializer.assertTermDeserializationWorks( + """ + |fun: member + |args: + | - var: H + | - list: + | - var: H + | tail: + | var: _ + """.trimMargin() + ) { + structOf("member", varOf("H"), consOf(varOf("H"), anonymous())) + } + } + + @Test + fun testAtomDeserializationInJSON() { + val deserializer: TermDeserializer = TermDeserializer.of(MimeType.Json) + assertEquals(MimeType.Json, deserializer.mimeType) + deserializer.assertTermDeserializationWorks("\"hello\"") { + atomOf("hello") + } + deserializer.assertTermDeserializationWorks("\"other atom\"") { + atomOf("other atom") + } + } + + @Test + fun testAtomDeserializationInYaml() { + val deserializer: TermDeserializer = TermDeserializer.of(MimeType.Yaml) + assertEquals(MimeType.Yaml, deserializer.mimeType) + deserializer.assertTermDeserializationWorks("\"hello\"") { + atomOf("hello") + } + deserializer.assertTermDeserializationWorks("\"other atom\"") { + atomOf("other atom") + } + + } + + @Test + fun testNumericDeserializationInJSON() { + val deserializer: TermDeserializer = TermDeserializer.of(MimeType.Json) + assertEquals(MimeType.Json, deserializer.mimeType) + deserializer.assertTermDeserializationWorks("2") { + numOf(2) + } + deserializer.assertTermDeserializationWorks("3.1") { + numOf(3.1) + } + } + + @Test + fun testNumericDeserializationInYAML() { + val deserializer: TermDeserializer = TermDeserializer.of(MimeType.Yaml) + assertEquals(MimeType.Yaml, deserializer.mimeType) + deserializer.assertTermDeserializationWorks("2") { + numOf(2) + } + deserializer.assertTermDeserializationWorks("3.1") { + numOf(3.1) + } + } + + @Test + fun testListDeserializationInJSON() { + val deserializer: TermDeserializer = TermDeserializer.of(MimeType.Json) + assertEquals(MimeType.Json, deserializer.mimeType) + deserializer.assertTermDeserializationWorks("{\"list\":[\"hello\",1,true]}") { + listOf(atomOf("hello"), numOf(1), truthOf(true)) + } + } + + @Test + fun testListDeserializationInYAML() { + val deserializer: TermDeserializer = TermDeserializer.of(MimeType.Yaml) + assertEquals(MimeType.Yaml, deserializer.mimeType) + val actual = """ + |list: + |- hello + |- 1 + |- true + """.trimMargin() + deserializer.assertTermDeserializationWorks(actual) { + listOf(atomOf("hello"), numOf(1), truthOf(true)) + } + } + + @Test + fun testSetDeserializationInJSON() { + val deserializer: TermDeserializer = TermDeserializer.of(MimeType.Json) + assertEquals(MimeType.Json, deserializer.mimeType) + deserializer.assertTermDeserializationWorks("{\"set\":[\"hello\",1, false]}") { + setOf(atomOf("hello"), numOf(1), truthOf(false)) + } + } + + @Test + fun testSetDeserializationInYAML() { + val deserializer: TermDeserializer = TermDeserializer.of(MimeType.Yaml) + assertEquals(MimeType.Yaml, deserializer.mimeType) + val actual = """ + |set: + |- "hello" + |- 1 + |- true + | + """.trimMargin() + deserializer.assertTermDeserializationWorks(actual) { + setOf(atomOf("hello"), numOf(1), truthOf(true)) + } + } + + @Test + fun testStructDeserializationInJSON() { + val deserializer: TermDeserializer = TermDeserializer.of(MimeType.Json) + assertEquals(MimeType.Json, deserializer.mimeType) + deserializer.assertTermDeserializationWorks("{\"fun\":\"f\",\"args\":[\"hello\",2]}") { + structOf("f", atomOf("hello"), numOf(2)) + } + + deserializer.assertTermDeserializationWorks("{\"fun\":\"f\",\"args\":[\"prova 2\",{\"real\":3.0},{\"list\":[\"qua ci va una lista\",true]}]}") { + structOf( + "f", atomOf("prova 2"), realOf(3.0), + listOf(atomOf("qua ci va una lista"), truthOf(true)) + ) + } + } + + @Test + fun testStructDeserializationInYAML() { + val deserializer: TermDeserializer = TermDeserializer.of(MimeType.Yaml) + assertEquals(MimeType.Yaml, deserializer.mimeType) + var actual = """ + |fun: f + |args: + |- hello + |- 2 + """.trimMargin() + deserializer.assertTermDeserializationWorks(actual) { + structOf("f", atomOf("hello"), numOf(2)) + } + + actual = """ + |fun: "f" + |args: + |- "prova 2" + |- real: 3.0 + |- list: + | - "qua ci va una lista" + | - true + """.trimMargin() + deserializer.assertTermDeserializationWorks(actual) { + structOf( + "f", atomOf("prova 2"), realOf(3.0), + listOf(atomOf("qua ci va una lista"), truthOf(true)) + ) + } + } + + @Test + fun testVariablesDeserializationInJSON() { + val deserializer: TermDeserializer = TermDeserializer.of(MimeType.Json) + + assertEquals(MimeType.Json, deserializer.mimeType) + + deserializer.assertTermDeserializationWorks("{\"var\":\"X\"}") { + varOf("X") + } + } + + @Test + fun testVariablesDeserializationInYAML() { + val deserializer: TermDeserializer = TermDeserializer.of(MimeType.Yaml) + + assertEquals(MimeType.Yaml, deserializer.mimeType) + + val actual = """ + |var: "X" + """.trimMargin() + deserializer.assertTermDeserializationWorks(actual) { + varOf("X") + } + } + + @Test + fun testConstantDeserializationInJSON() { + val deserializer: TermDeserializer = TermDeserializer.of(MimeType.Json) + assertEquals(MimeType.Json, deserializer.mimeType) + deserializer.assertTermDeserializationWorks("true") { + truthOf(true) + } + deserializer.assertTermDeserializationWorks("false") { + truthOf(false) + } + } + + @Test + fun testConstantDeserializationInYAML() { + val deserializer: TermDeserializer = TermDeserializer.of(MimeType.Yaml) + assertEquals(MimeType.Yaml, deserializer.mimeType) + deserializer.assertTermDeserializationWorks("true") { + truthOf(true) + } + deserializer.assertTermDeserializationWorks("false") { + truthOf(false) + } + } +} \ No newline at end of file diff --git a/serialize-core/src/commonTest/kotlin/it/unibo/tuprolog/serialize/TestTermSerializer.kt b/serialize-core/src/commonTest/kotlin/it/unibo/tuprolog/serialize/TestTermSerializer.kt new file mode 100644 index 000000000..950555f7b --- /dev/null +++ b/serialize-core/src/commonTest/kotlin/it/unibo/tuprolog/serialize/TestTermSerializer.kt @@ -0,0 +1,254 @@ +package it.unibo.tuprolog.serialize + +import kotlin.test.Test +import kotlin.test.assertEquals + +class TestTermSerializer { + + @Test + fun testAtomSerializationInJSON() { + val serializer: TermSerializer = TermSerializer.of(MimeType.Json) + assertEquals(MimeType.Json, serializer.mimeType) + + serializer.assertTermSerializationWorks("\"atom\"") { + atomOf("atom") + } + + serializer.assertTermSerializationWorks("\"an atom\"") { + atomOf("an atom") + } + } + + @Test + fun testAtomSerializationInYAML() { + val serializer: TermSerializer = TermSerializer.of(MimeType.Yaml) + + assertEquals(MimeType.Yaml, serializer.mimeType) + + serializer.assertTermSerializationWorks("\"atom\"") { + atomOf("atom") + } + serializer.assertTermSerializationWorks("\"an atom\"") { + atomOf("an atom") + } + } + + @Test + fun testNumericSerializationInJSON() { + val serializer: TermSerializer = TermSerializer.of(MimeType.Json) + assertEquals(MimeType.Json, serializer.mimeType) + + serializer.assertTermSerializationWorks("2") { + numOf(2) + } + + serializer.assertTermSerializationWorks("{\"real\": \"3.0\"}") { + numOf(3.0) + } + } + + @Test + fun testNumericSerializationInYAML() { + val serializer: TermSerializer = TermSerializer.of(MimeType.Yaml) + + assertEquals(MimeType.Yaml, serializer.mimeType) + + serializer.assertTermSerializationWorks("3") { + numOf(3) + } + serializer.assertTermSerializationWorks("real: \"4.2\"") { + numOf(4.2) + } + } + + @Test + fun testListSerializationInJSON() { + val serializer: TermSerializer = TermSerializer.of(MimeType.Json) + assertEquals(MimeType.Json, serializer.mimeType) + + serializer.assertTermSerializationWorks("{\"list\":[\"hello\",false]}") { + listOf(atomOf("hello"), truthOf(false)) + } + + serializer.assertTermSerializationWorks("{\"list\":[\"hello\",1]}") { + listOf(atomOf("hello"), numOf(1)) + } + } + + @Test + fun testTailedListSerializationInJSON() { + val serializer: TermSerializer = TermSerializer.of(MimeType.Json) + assertEquals(MimeType.Json, serializer.mimeType) + + serializer.assertTermSerializationWorks("{\"fun\":\"member\",\"args\":[{\"var\":\"H\"},{\"list\":[{\"var\":\"H\"}],\"tail\":{\"var\":\"_\"}}]}") { + structOf("member", varOf("H"), consOf(varOf("H"), anonymous())) + } + } + + @Test + fun testListSerializationInYAML() { + val serializer: TermSerializer = TermSerializer.of(MimeType.Yaml) + + assertEquals(MimeType.Yaml, serializer.mimeType) + + var expected = """ + |list: + |- hello + |- false + """.trimMargin() + serializer.assertTermSerializationWorks(expected) { + listOf(atomOf("hello"), truthOf(false)) + } + + expected = """ + |list: + |- hello + |- 1 + """.trimMargin() + serializer.assertTermSerializationWorks(expected) { + listOf(atomOf("hello"), numOf(1)) + } + } + + @Test + fun testTailedListSerializationInYAML() { + val serializer: TermSerializer = TermSerializer.of(MimeType.Yaml) + assertEquals(MimeType.Yaml, serializer.mimeType) + + serializer.assertTermSerializationWorks( + """ + |fun: member + |args: + | - var: H + | - list: + | - var: H + | tail: + | var: _ + """.trimMargin() + ) { + structOf("member", varOf("H"), consOf(varOf("H"), anonymous())) + } + } + + @Test + fun testSetSerializationInJSON() { + val serializer: TermSerializer = TermSerializer.of(MimeType.Json) + assertEquals(MimeType.Json, serializer.mimeType) + + serializer.assertTermSerializationWorks("{\"set\":[\"hello\",1]}") { + setOf(atomOf("hello"), numOf(1)) + } + } + + @Test + fun testSetSerializationInYAML() { + val serializer: TermSerializer = TermSerializer.of(MimeType.Yaml) + + assertEquals(MimeType.Yaml, serializer.mimeType) + + var expected = """ + |set: + |- hello + |- false + """.trimMargin() + serializer.assertTermSerializationWorks(expected) { + setOf(atomOf("hello"), truthOf(false)) + } + + expected = """ + |set: + |- hello + |- 1 + """.trimMargin() + serializer.assertTermSerializationWorks(expected) { + setOf(atomOf("hello"), numOf(1)) + } + } + + @Test + fun testStructSerializationInJSON() { + val serializer: TermSerializer = TermSerializer.of(MimeType.Json) + assertEquals(MimeType.Json, serializer.mimeType) + + serializer.assertTermSerializationWorks("{\"fun\":\"f\",\"args\":[\"hello\",2]}") { + structOf("f", atomOf("hello"), numOf(2)) + } + } + + @Test + fun testStructSerializationInYAML() { + val serializer: TermSerializer = TermSerializer.of(MimeType.Yaml) + + assertEquals(MimeType.Yaml, serializer.mimeType) + + val expected = """ + |fun: f + |args: + |- hello + |- 2 + |- list: + | - true + | - ciao + """.trimMargin() + serializer.assertTermSerializationWorks(expected) { + structOf("f", atomOf("hello"), numOf(2), listOf(truthOf(true), atomOf("ciao"))) + } + } + + @Test + fun testConstantSerializationInJSON() { + val serializer: TermSerializer = TermSerializer.of(MimeType.Json) + assertEquals(MimeType.Json, serializer.mimeType) + + serializer.assertTermSerializationWorks("true") { + truthOf(true) + } + + serializer.assertTermSerializationWorks("false") { + truthOf(false) + } + } + + @Test + fun testConstantSerializationInYAML() { + val serializer: TermSerializer = TermSerializer.of(MimeType.Yaml) + + assertEquals(MimeType.Yaml, serializer.mimeType) + + serializer.assertTermSerializationWorks("true") { + truthOf(true) + } + serializer.assertTermSerializationWorks("false") { + truthOf(false) + } + } + + @Test + fun testVariablesSerializationInJSON() { + val serializer: TermSerializer = TermSerializer.of(MimeType.Json) + assertEquals(MimeType.Json, serializer.mimeType) + + + serializer.assertTermSerializationWorks("{\"var\":\"Y\"}") { + varOf("Y") + } + + serializer.assertTermSerializationWorks("{\"var\":\"Two Words\"}") { + varOf("Two Words") + } + } + + @Test + fun testVariablesSerializationInYAML() { + val serializer: TermSerializer = TermSerializer.of(MimeType.Yaml) + assertEquals(MimeType.Yaml, serializer.mimeType) + + val expected = """ + |var: "Y" + """.trimMargin() + + serializer.assertTermSerializationWorks(expected) { + varOf("Y") + } + } +} \ No newline at end of file diff --git a/serialize-core/src/commonTest/kotlin/it/unibo/tuprolog/serialize/TestUtils.kt b/serialize-core/src/commonTest/kotlin/it/unibo/tuprolog/serialize/TestUtils.kt new file mode 100644 index 000000000..da8acf691 --- /dev/null +++ b/serialize-core/src/commonTest/kotlin/it/unibo/tuprolog/serialize/TestUtils.kt @@ -0,0 +1,70 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.core.* +import it.unibo.tuprolog.serialize.ObjectsUtils.deeplyEqual +import it.unibo.tuprolog.serialize.ObjectsUtils.parseAsObject + +/** + * Utility assertion method aimed at checking if a serializer correctly works + */ +fun Serializer.assertSerializationWorks(expected: String, actual: T) { + val expectedObj = parseAsObject(expected, mimeType) + val actualObj = TermObjectifier.default.objectify(actual) + kotlin.test.assertTrue(""" + |Expected: + | $expectedObj + |got instead: + | $actualObj + | + """.trimMargin()) { deeplyEqual(expectedObj, actualObj) } +} + +/** + * Utility assertion method aimed at checking if a serializer for [Term]s correctly works + * + * Usage example: + * ```kotlin + * val s: TermSerializer = // ... + * + * s.assertTermSerializationWorks("expected string") { + * // use utilities of it.unibo.tuprolog.core.Scope to build the to-be-serialized term + * } + * ``` + * + * @see Scope https://pika-lab.gitlab.io/tuprolog/2p-in-kotlin/kotlindoc/it/unibo/tuprolog/core/scope/ + */ +fun Serializer.assertTermSerializationWorks(expected: String, actualGenerator: Scope.() -> Term) { + assertSerializationWorks(expected, Scope.empty().actualGenerator()) +} + +/** + * Utility assertion method aimed at checking if a deserializer correctly works + */ +fun Deserializer.assertDeserializationWorks(expected: T, actual: String) { + val deserialized = deserialize(actual) + kotlin.test.assertTrue(""" + |Expected: + | $expected + |got: + | $deserialized + | + """.trimMargin()) { expected.equals(deserialized, false) } +} + +/** + * Utility assertion method aimed at checking if a deserializer for [Term]s correctly works + * + * Usage example: + * ```kotlin + * val d: TermDeserializer = // ... + * + * d.assertTermDeserializationWorks("to-be-deserialized string") { + * // use utilities of it.unibo.tuprolog.core.Scope to build the expected term + * } + * ``` + * + * @see Scope https://pika-lab.gitlab.io/tuprolog/2p-in-kotlin/kotlindoc/it/unibo/tuprolog/core/scope/ + */ +fun Deserializer.assertTermDeserializationWorks(actual: String, expectedGenerator: Scope.() -> Term) { + assertDeserializationWorks(Scope.empty().expectedGenerator(), actual) +} \ No newline at end of file diff --git a/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTermDeobjectifier.kt b/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTermDeobjectifier.kt new file mode 100644 index 000000000..7c7f3927b --- /dev/null +++ b/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTermDeobjectifier.kt @@ -0,0 +1,129 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.core.Scope +import it.unibo.tuprolog.core.Struct +import it.unibo.tuprolog.core.Term +import it.unibo.tuprolog.core.Var + +@Suppress("USELESS_CAST") +internal class JsTermDeobjectifier : TermDeobjectifier { + + private val scope: Scope = Scope.empty() + + override fun deobjectify(`object`: Any): Term { + return when (`object`) { + is Boolean -> deobjectifyBoolean(`object`) + is Int, Long, Short, Byte, Float, Double -> deobjectifyNumber(`object`) + is String -> deobjectifyString(`object`) + else -> deobjectifyObj(`object`) + } + } + + override fun deobjectifyMany(`object`: Any): Iterable { + return when (`object`) { + is Array<*> -> `object`.map { deobjectify(it ?: throw DeobjectificationException(`object`)) } + else -> throw DeobjectificationException(`object`) + } + } + + private fun deobjectifyObj(value: dynamic): Term { + return when { + hasProperty(value, "var") -> deobjectifyVariable(value) + hasProperty(value, "fun") && hasProperty(value, "args") -> deobjectifyStructure(value) + hasProperty(value, "list") -> deobjectifyList(value) + hasProperty(value, "set") -> deobjectifySet(value) + hasProperty(value, "tuple") -> deobjectifyTuple(value) + hasProperty(value, "integer") -> deobjectifyInteger(value) + hasProperty(value, "real") -> deobjectifyReal(value) + hasProperty(value, "head") || hasProperty(value, "body") -> deobjectifyClause(value) + else -> throw DeobjectificationException(value) + } + } + + private fun deobjectifyReal(value: dynamic): Term { + return when (val actualValue = value["real"]) { + is String -> scope.realOf(actualValue as String) + is Double -> scope.realOf(actualValue as Double) + else -> throw DeobjectificationException(value) + } + } + + private fun deobjectifyInteger(value: dynamic): Term { + return when (val actualValue = value["integer"]) { + is String -> scope.intOf(actualValue as String) + is Int -> scope.intOf(actualValue as Int) + else -> throw DeobjectificationException(value) + } + } + + private fun deobjectifyClause(value: dynamic): Term { + var head = value["head"] + if (head != null) { + head = deobjectify(head) as? Struct ?: throw DeobjectificationException(value) + } + var body = value["body"] + if (body != null) { + body = deobjectify(body) + } + return if (body == null) { + scope.factOf(head) + } else { + scope.clauseOf(head, body) + } + } + + private fun deobjectifyList(value: dynamic): Term { + val items = value["list"] as? Array ?: throw DeobjectificationException(value) + val last = value["tail"] + return scope.listFrom( + items.map { + deobjectify(it ?: throw DeobjectificationException(value)) + }, + last = if (last != null) deobjectify(last) else null + ) + } + + private fun deobjectifyTuple(value: dynamic): Term { + val items = value["tuple"] as? Array<*> ?: throw DeobjectificationException(value) + return scope.tupleOf(items.map { + deobjectify(it ?: throw DeobjectificationException(value)) + }) + } + + private fun deobjectifySet(value: dynamic): Term { + val items = value["set"] as? Array<*> ?: throw DeobjectificationException(value) + return scope.setOf(items.map { + deobjectify(it ?: throw DeobjectificationException(value)) + }) + } + + private fun deobjectifyStructure(value: dynamic): Term { + val name = value["fun"] as? String ?: throw DeobjectificationException(value) + val args = value["args"] as? Array<*> ?: throw DeobjectificationException(value) + return scope.structOf(name, args.map { + deobjectify(it ?: throw DeobjectificationException(value)) + }) + } + + private fun deobjectifyVariable(value: dynamic): Term { + val name = value["var"] as? String ?: throw DeobjectificationException(value) + return if (name == Var.ANONYMOUS_VAR_NAME) { + scope.anonymous() + } else { + scope.varOf(name) + } + } + + private fun deobjectifyString(value: String): Term { + return scope.atomOf(value) + } + + private fun deobjectifyBoolean(value: Boolean): Term { + return scope.truthOf(value) + } + + private fun deobjectifyNumber(value: dynamic): Term { + return scope.numOf(value.toString()) + } + +} \ No newline at end of file diff --git a/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTermDeserializer.kt b/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTermDeserializer.kt new file mode 100644 index 000000000..6ed674ede --- /dev/null +++ b/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTermDeserializer.kt @@ -0,0 +1,18 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.core.Term + +internal class JsTermDeserializer(override val mimeType: MimeType) : TermDeserializer { + override fun deserialize(string: String): Term = + JsTermDeobjectifier().deobjectify(parse(string)) + + override fun deserializeMany(string: String): Iterable = + JsTermDeobjectifier().deobjectifyMany(parse(string)) + + private fun parse(string: String): Any = + when (mimeType) { + is MimeType.Xml -> throw NotImplementedError("XML is currently not supported in JS") + is MimeType.Json -> JSON.parse(string) + is MimeType.Yaml -> YAML.parse(string) + } as Any +} \ No newline at end of file diff --git a/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTermObjectifier.kt b/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTermObjectifier.kt new file mode 100644 index 000000000..ecce00b1b --- /dev/null +++ b/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTermObjectifier.kt @@ -0,0 +1,110 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.core.* + +internal class JsTermObjectifier : TermObjectifier { + override fun defaultValue(term: Term): Any { + throw IllegalStateException() + } + + override fun objectifyMany(values: Iterable): Any { + return values.map { objectify(it) }.toTypedArray() + } + + override fun visitVar(term: Var): dynamic = + jsObject( + "var" to term.name + ) + + override fun visitTruth(term: Truth): Any = + when (term) { + Truth.TRUE -> true + Truth.FALSE -> false + Truth.FAIL -> "fail" + else -> NotImplementedError("Serialization of Truth value: $term") + } + + override fun visitStruct(term: Struct): Any = + jsObject( + "fun" to term.functor, + "args" to term.argsList.map { it.accept(this) } + ) + + override fun visitAtom(term: Atom): String = + term.value + + override fun visitInteger(term: Integer): dynamic = + try { + term.value.toIntExact() + } catch (e: ArithmeticException) { + jsObject( + "integer" to term.value.toString() + ) + } + + override fun visitReal(term: Real): Any = + jsObject( + "real" to term.value.toString().let { + if ("." !in it) { + "$it.0" + } else { + it + } + } + ) + + override fun visitSet(term: Set): Any = + jsObject( + "set" to term.toList().map { it.accept(this) } + ) + + override fun visitEmptySet(term: EmptySet): Any = + visitSet(term) + + override fun visitList(term: List): Any { + val listed = term.toList() + return if (term.isWellFormed) { + jsObject( + "list" to listed.map { it.accept(this) }.toTypedArray() + ) + } else { + jsObject( + "list" to listed.subList(0, listed.lastIndex).map { it.accept(this) }.toTypedArray(), + "tail" to listed[listed.lastIndex].accept(this) + ) + } + } + + override fun visitCons(term: Cons): Any = + visitList(term) + + override fun visitEmptyList(term: EmptyList): Any = + visitList(term) + + override fun visitTuple(term: Tuple): Any = + jsObject( + "tuple" to term.toList().map { it.accept(this) } + ) + + override fun visitIndicator(term: Indicator): Any = + jsObject( + "name" to term.nameTerm.accept(this), + "arity" to term.arityTerm.accept(this) + ) + + override fun visitRule(term: Rule): Any = + jsObject( + "head" to term.head.accept(this), + "body" to term.body.accept(this) + ) + + override fun visitFact(term: Fact): Any = + jsObject( + "head" to term.head.accept(this) + ) + + override fun visitDirective(term: Directive): Any = + jsObject( + "body" to term.body.accept(this) + ) +} \ No newline at end of file diff --git a/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTermSerializer.kt b/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTermSerializer.kt new file mode 100644 index 000000000..821bdbddb --- /dev/null +++ b/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTermSerializer.kt @@ -0,0 +1,21 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.core.Term + +internal class JsTermSerializer(override val mimeType: MimeType) : TermSerializer { + + private val objectifier = JsTermObjectifier() + + override fun serialize(value: Term): String = + stringify(objectifier.objectify(value)) + + override fun serializeMany(values: Iterable): String = + stringify(objectifier.objectifyMany(values)) + + private fun stringify(objectified: Any): String = + when (mimeType) { + is MimeType.Xml -> throw NotImplementedError("XML is currently not supported in JS") + is MimeType.Json -> JSON.stringify(objectified) + is MimeType.Yaml -> YAML.stringify(objectified) + } +} \ No newline at end of file diff --git a/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsUtils.kt b/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsUtils.kt new file mode 100644 index 000000000..58239eb68 --- /dev/null +++ b/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsUtils.kt @@ -0,0 +1,28 @@ +package it.unibo.tuprolog.serialize + +internal fun jsObject(config: dynamic.() -> Unit): Any { + val obj = object {} + config(obj) + return obj +} + +internal fun jsObject(vararg properties: Pair, config: dynamic.() -> Unit = {}): Any { + return jsObject { + for ((k, v) in properties) { + this[k] = v + } + config(this) + } +} + +internal fun jsObject(properties: Iterable>, config: dynamic.() -> Unit = {}): Any { + return jsObject { + for ((k, v) in properties) { + this[k] = v + } + config(this) + } +} + +internal fun hasProperty(obj: dynamic, name: String): Boolean = + obj[name] != undefined \ No newline at end of file diff --git a/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/ObjectsUtils.kt b/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/ObjectsUtils.kt new file mode 100644 index 000000000..5d7260009 --- /dev/null +++ b/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/ObjectsUtils.kt @@ -0,0 +1,16 @@ +package it.unibo.tuprolog.serialize + +actual object ObjectsUtils { + + actual fun parseAsObject(string: String, mimeType: MimeType): Any { + return when (mimeType) { + is MimeType.Xml -> throw NotImplementedError() + is MimeType.Yaml -> YAML.parse(string) + is MimeType.Json -> JSON.parse(string) + } as Any + } + + actual fun deeplyEqual(obj1: Any?, obj2: Any?): Boolean { + return JSON.stringify(obj1) == JSON.stringify(obj2) + } +} \ No newline at end of file diff --git a/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/TermSerialization.kt b/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/TermSerialization.kt new file mode 100644 index 000000000..53ec154ef --- /dev/null +++ b/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/TermSerialization.kt @@ -0,0 +1,17 @@ +package it.unibo.tuprolog.serialize + +actual fun termSerializer(mimeType: MimeType): TermSerializer { + return JsTermSerializer(mimeType) +} + +actual fun termDeserializer(mimeType: MimeType): TermDeserializer { + return JsTermDeserializer(mimeType) +} + +actual fun termObjectifier(): TermObjectifier { + return JsTermObjectifier() +} + +actual fun termDeobjectifier(): TermDeobjectifier { + return JsTermDeobjectifier() +} \ No newline at end of file diff --git a/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/YAML.kt b/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/YAML.kt new file mode 100644 index 000000000..75111fbbe --- /dev/null +++ b/serialize-core/src/jsMain/kotlin/it/unibo/tuprolog/serialize/YAML.kt @@ -0,0 +1,11 @@ +package it.unibo.tuprolog.serialize + +@JsModule("yaml") +@JsNonModule +external object YAML { + fun stringify(value: dynamic, options: dynamic): String + fun stringify(value: dynamic): String + + fun parse(string: String, options: dynamic): dynamic + fun parse(string: String): dynamic +} \ No newline at end of file diff --git a/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTermDeobjectifier.kt b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTermDeobjectifier.kt new file mode 100644 index 000000000..a7a5615a9 --- /dev/null +++ b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTermDeobjectifier.kt @@ -0,0 +1,135 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.core.* +import java.math.BigDecimal +import java.math.BigInteger +import it.unibo.tuprolog.core.Integer as LogicInteger + +internal class JvmTermDeobjectifier : TermDeobjectifier { + + private val scope: Scope = Scope.empty() + + override fun deobjectify(`object`: Any): Term { + return when (`object`) { + is Boolean -> deobjectifyBoolean(`object`) + is Number -> deobjectifyNumber(`object`) + is String -> deobjectifyString(`object`) + is Map<*, *> -> deobjectifyMap(`object`) + else -> throw DeobjectificationException(`object`) + } + } + + override fun deobjectifyMany(`object`: Any): Iterable { + return when (`object`) { + is List<*> -> `object`.map { deobjectify(it ?: throw DeobjectificationException(`object`)) } + else -> throw DeobjectificationException(`object`) + } + } + + private fun deobjectifyMap(value: Map<*, *>): Term { + return when { + value.containsKey("var") -> deobjectifyVariable(value) + value.containsKey("fun") && value.containsKey("args") -> deobjectifyStructure(value) + value.containsKey("list") -> deobjectifyList(value) + value.containsKey("set") -> deobjectifySet(value) + value.containsKey("tuple") -> deobjectifyTuple(value) + value.containsKey("integer") -> deobjectifyInteger(value) + value.containsKey("real") -> deobjectifyReal(value) + value.containsKey("head") || value.containsKey("body") -> deobjectifyClause(value) + else -> throw DeobjectificationException(value) + } + } + + private fun deobjectifyReal(value: Map<*, *>): Term { + return when (val actualValue = value["real"]) { + is String -> scope.realOf(actualValue) + is Number -> deobjectifyNumber(actualValue) as? Real ?: throw DeobjectificationException(value) + else -> throw DeobjectificationException(value) + } + } + + private fun deobjectifyInteger(value: Map<*, *>): Term { + return when (val actualValue = value["integer"]) { + is String -> scope.intOf(actualValue) + is Number -> deobjectifyNumber(actualValue) as? LogicInteger ?: throw DeobjectificationException(value) + else -> throw DeobjectificationException(value) + } + } + + private fun deobjectifyClause(value: Map<*, *>): Term { + val head = value["head"]?.let { + deobjectify(it) as? Struct ?: throw DeobjectificationException(value) + } + val body = value["body"]?.let { deobjectify(it) } + return if (body == null) { + scope.factOf(head!!) + } else { + scope.clauseOf(head, body) + } + } + + private fun deobjectifyList(value: Map<*, *>): Term { + val items = value["list"] as? List<*> ?: throw DeobjectificationException(value) + val last = value["tail"] + return scope.listFrom( + items.map { + deobjectify(it ?: throw DeobjectificationException(value)) + }, + last = last?.let { deobjectify(it) } + ) + } + + private fun deobjectifyTuple(value: Map<*, *>): Term { + val items = value["tuple"] as? List<*> ?: throw DeobjectificationException(value) + return scope.tupleOf(items.map { + deobjectify(it ?: throw DeobjectificationException(value)) + }) + } + + private fun deobjectifySet(value: Map<*, *>): Term { + val items = value["set"] as? List<*> ?: throw DeobjectificationException(value) + return scope.setOf(items.map { + deobjectify(it ?: throw DeobjectificationException(value)) + }) + } + + private fun deobjectifyStructure(value: Map<*, *>): Term { + val name = value["fun"] as? String ?: throw DeobjectificationException(value) + val args = value["args"] as? List<*> ?: throw DeobjectificationException(value) + return scope.structOf(name, args.map { + deobjectify(it ?: throw DeobjectificationException(value)) + }) + } + + private fun deobjectifyVariable(value: Map<*, *>): Term { + val name = value["var"] as? String ?: throw DeobjectificationException(value) + return if (name == Var.ANONYMOUS_VAR_NAME) { + scope.anonymous() + } else { + scope.varOf(name) + } + } + + private fun deobjectifyString(value: String): Term { + return scope.atomOf(value) + } + + private fun deobjectifyNumber(value: Number): Term { + return when (value) { + is Int -> scope.numOf(value) + is Long -> scope.numOf(value) + is Double -> scope.numOf(value) + is Byte -> scope.numOf(value) + is Short -> scope.numOf(value) + is Float -> scope.numOf(value) + is BigInteger -> LogicInteger.of(value.toString()) + is BigDecimal -> Real.of(value.toString()) + else -> throw DeobjectificationException(value) + } + } + + private fun deobjectifyBoolean(value: Boolean): Term { + return scope.truthOf(value) + } + +} \ No newline at end of file diff --git a/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTermDeserializer.kt b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTermDeserializer.kt new file mode 100644 index 000000000..970baa56e --- /dev/null +++ b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTermDeserializer.kt @@ -0,0 +1,20 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.core.Term +import java.io.Reader + +internal class JvmTermDeserializer(override val mimeType: MimeType) : ReadingTermDeserializer { + + private val mapper = mimeType.objectMapper + + override fun deserialize(reader: Reader): Term = + JvmTermDeobjectifier().deobjectify( + mapper.readValue(reader, java.lang.Object::class.java) + ) + + override fun deserializeMany(reader: Reader): Iterable = + JvmTermDeobjectifier().deobjectifyMany( + mapper.readValue(reader, java.lang.Object::class.java) + ) + +} \ No newline at end of file diff --git a/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTermObjectifier.kt b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTermObjectifier.kt new file mode 100644 index 000000000..96ebf8c89 --- /dev/null +++ b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTermObjectifier.kt @@ -0,0 +1,106 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.core.* +import it.unibo.tuprolog.core.List +import it.unibo.tuprolog.core.Set + +internal class JvmTermObjectifier : TermObjectifier { + override fun defaultValue(term: Term): Any { + throw IllegalStateException() + } + + override fun objectifyMany(values: Iterable): Any { + return values.map { objectify(it) } + } + + override fun visitVar(term: Var): Map = + mapOf( + "var" to term.name + ) + + override fun visitTruth(term: Truth): Any = + when (term) { + Truth.TRUE -> true + Truth.FALSE -> false + Truth.FAIL -> "fail" + else -> NotImplementedError("Serialization of Truth value: $term") + } + + override fun visitStruct(term: Struct): Map = + mapOf( + "fun" to term.functor, + "args" to term.argsList.map { it.accept(this) } + ) + + override fun visitAtom(term: Atom): String = + term.value + + override fun visitInteger(term: Integer): Any = + try { + term.value.toLongExact() + } catch (e: ArithmeticException) { + mapOf( + "integer" to term.value.toString() + ) + } + + override fun visitReal(term: Real): Any = + mapOf( + "real" to term.value.toString() + ) + + override fun visitSet(term: Set): Map = + mapOf( + "set" to term.toList().map { it.accept(this) } + ) + + override fun visitEmptySet(term: EmptySet): Map = + visitSet(term) + + override fun visitList(term: List): Map { + val listed = term.toList() + return if (term.isWellFormed) { + mapOf( + "list" to listed.map { it.accept(this) } + ) + } else { + mapOf( + "list" to listed.subList(0, listed.lastIndex).map { it.accept(this) }, + "tail" to listed[listed.lastIndex].accept(this) + ) + } + } + + override fun visitCons(term: Cons): Map = + visitList(term) + + override fun visitEmptyList(term: EmptyList): Map = + visitList(term) + + override fun visitTuple(term: Tuple): Map = + mapOf( + "tuple" to term.toList().map { it.accept(this) } + ) + + override fun visitIndicator(term: Indicator): Map = + mapOf( + "name" to term.nameTerm.accept(this), + "arity" to term.arityTerm.accept(this) + ) + + override fun visitRule(term: Rule): Map = + mapOf( + "head" to term.head.accept(this), + "body" to term.body.accept(this) + ) + + override fun visitFact(term: Fact): Map = + mapOf( + "head" to term.head.accept(this) + ) + + override fun visitDirective(term: Directive): Map = + mapOf( + "body" to term.body.accept(this) + ) +} \ No newline at end of file diff --git a/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTermSerializer.kt b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTermSerializer.kt new file mode 100644 index 000000000..5c5133ce3 --- /dev/null +++ b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTermSerializer.kt @@ -0,0 +1,18 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.core.Term +import java.io.Writer + +internal class JvmTermSerializer(override val mimeType: MimeType) : WritingTermSerializer { + + private val mapper = mimeType.objectMapper + private val objectifier = JvmTermObjectifier() + + override fun serialize(writer: Writer, value: Term) { + mapper.writeValue(writer, objectifier.objectify(value)) + } + + override fun serializeMany(writer: Writer, values: Iterable) { + mapper.writeValue(writer, objectifier.objectifyMany(values)) + } +} \ No newline at end of file diff --git a/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/MimeTypeExtensions.kt b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/MimeTypeExtensions.kt new file mode 100644 index 000000000..073c7d12a --- /dev/null +++ b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/MimeTypeExtensions.kt @@ -0,0 +1,16 @@ +@file:JvmName("MimeTypeExtensions") + +package it.unibo.tuprolog.serialize + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.xml.XmlMapper +import com.fasterxml.jackson.dataformat.yaml.YAMLMapper + +private val objectMappers = mapOf( + MimeType.Json to ObjectMapper(), + MimeType.Yaml to YAMLMapper(), + MimeType.Xml to XmlMapper() +) + +val MimeType.objectMapper: ObjectMapper + get() = objectMappers[this] ?: throw NotImplementedError("MIME type not supported: $this") diff --git a/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/ObjectsUtils.kt b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/ObjectsUtils.kt new file mode 100644 index 000000000..1b78c382b --- /dev/null +++ b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/ObjectsUtils.kt @@ -0,0 +1,24 @@ +package it.unibo.tuprolog.serialize + +actual object ObjectsUtils { + actual fun parseAsObject(string: String, mimeType: MimeType): Any { + return mimeType.objectMapper.readValue(string, java.lang.Object::class.java) + } + + actual fun deeplyEqual(obj1: Any?, obj2: Any?): Boolean { + return when { + obj1 is Number && obj2 is Number -> obj1.toString() == obj2.toString() + obj1 is List<*> && obj2 is List<*> -> obj1.asSequence().zip(obj2.asSequence()).all { + deeplyEqual(it.first, it.second) + } + obj1 is Map<*,*> && obj2 is Map<*,*> -> { + if (obj1.keys != obj2.keys) { + false + } else { + obj1.keys.all { deeplyEqual(obj1[it], obj2[it]) } + } + } + else -> obj1 == obj2 + } + } +} \ No newline at end of file diff --git a/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/ReadingDeserializer.kt b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/ReadingDeserializer.kt new file mode 100644 index 000000000..c5c82eca0 --- /dev/null +++ b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/ReadingDeserializer.kt @@ -0,0 +1,20 @@ +package it.unibo.tuprolog.serialize + +import java.io.Reader +import java.io.StringReader + +interface ReadingDeserializer : Deserializer { + fun deserialize(reader: Reader): T + + override fun deserialize(string: String): T = + StringReader(string).use { + deserialize(it) + } + + fun deserializeMany(reader: Reader): Iterable + + override fun deserializeMany(string: String): Iterable = + StringReader(string).use { + deserializeMany(it) + } +} \ No newline at end of file diff --git a/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/ReadingTermDeserializer.kt b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/ReadingTermDeserializer.kt new file mode 100644 index 000000000..99cb74ee5 --- /dev/null +++ b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/ReadingTermDeserializer.kt @@ -0,0 +1,12 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.core.Term + +interface ReadingTermDeserializer : TermDeserializer, ReadingDeserializer { + companion object { + @JvmStatic + fun of(mimeType: MimeType): ReadingTermDeserializer { + return JvmTermDeserializer(mimeType) + } + } +} \ No newline at end of file diff --git a/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/TermSerialization.kt b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/TermSerialization.kt new file mode 100644 index 000000000..172290440 --- /dev/null +++ b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/TermSerialization.kt @@ -0,0 +1,17 @@ +package it.unibo.tuprolog.serialize + +actual fun termSerializer(mimeType: MimeType): TermSerializer { + return JvmTermSerializer(mimeType) +} + +actual fun termDeserializer(mimeType: MimeType): TermDeserializer { + return JvmTermDeserializer(mimeType) +} + +actual fun termObjectifier(): TermObjectifier { + return JvmTermObjectifier() +} + +actual fun termDeobjectifier(): TermDeobjectifier { + return JvmTermDeobjectifier() +} \ No newline at end of file diff --git a/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/WritingSerializer.kt b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/WritingSerializer.kt new file mode 100644 index 000000000..4da18630c --- /dev/null +++ b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/WritingSerializer.kt @@ -0,0 +1,28 @@ +package it.unibo.tuprolog.serialize + +import java.io.StringWriter +import java.io.Writer + +interface WritingSerializer : Serializer { + fun serialize(writer: Writer, value: T) + + override fun serialize(value: T): String = + StringWriter().use { + serialize(it, value) + it.toString() + } + + fun serializeMany(writer: Writer, vararg values: T) = + serializeMany(writer, listOf(*values)) + + fun serializeMany(writer: Writer, values: Iterable) + + override fun serializeMany(values: Iterable): String = + StringWriter().use { + serializeMany(it, values) + it.toString() + } + + fun serializeMany(writer: Writer, values: Sequence) = + serializeMany(writer, values.asIterable()) +} \ No newline at end of file diff --git a/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/WritingTermSerializer.kt b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/WritingTermSerializer.kt new file mode 100644 index 000000000..12694fde8 --- /dev/null +++ b/serialize-core/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/WritingTermSerializer.kt @@ -0,0 +1,12 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.core.Term + +interface WritingTermSerializer : TermSerializer, WritingSerializer { + companion object { + @JvmStatic + fun of(mimeType: MimeType): WritingTermSerializer { + return JvmTermSerializer(mimeType) + } + } +} \ No newline at end of file diff --git a/serialize-theory/build.gradle.kts b/serialize-theory/build.gradle.kts new file mode 100644 index 000000000..15949f483 --- /dev/null +++ b/serialize-theory/build.gradle.kts @@ -0,0 +1,36 @@ +kotlin { + + sourceSets { + val commonMain by getting { + dependencies { + api(project(":theory")) + api(project(":serialize-core")) + } + } + + val commonTest by getting { + dependencies { + api(project(":solve")) + } + } + + jvm { + compilations["main"].defaultSourceSet { + dependencies { + implementation(Libs.jackson_core) + implementation(Libs.jackson_datatype_jsr310) + implementation(Libs.jackson_dataformat_yaml) + implementation(Libs.jackson_dataformat_xml) + } + } + } + + js { + compilations["main"].defaultSourceSet { + dependencies { +// api(npm("yaml", "^1.10.0")) + } + } + } + } +} diff --git a/serialize-theory/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TheoryDeobjectifier.kt b/serialize-theory/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TheoryDeobjectifier.kt new file mode 100644 index 000000000..f45c2f452 --- /dev/null +++ b/serialize-theory/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TheoryDeobjectifier.kt @@ -0,0 +1,14 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.theory.Theory +import kotlin.js.JsName +import kotlin.jvm.JvmStatic + +interface TheoryDeobjectifier : Deobjectifier { + companion object { + @JsName("default") + @JvmStatic + val default: TheoryDeobjectifier + get() = theoryDeobjectifier() + } +} \ No newline at end of file diff --git a/serialize-theory/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TheoryDeserializer.kt b/serialize-theory/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TheoryDeserializer.kt new file mode 100644 index 000000000..d5f099a87 --- /dev/null +++ b/serialize-theory/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TheoryDeserializer.kt @@ -0,0 +1,15 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.theory.Theory +import kotlin.js.JsName +import kotlin.jvm.JvmStatic + +interface TheoryDeserializer : Deserializer { + companion object { + @JvmStatic + @JsName("of") + fun of(mimeType: MimeType): TheoryDeserializer { + return theoryDeserializer(mimeType) + } + } +} \ No newline at end of file diff --git a/serialize-theory/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TheoryObjectifier.kt b/serialize-theory/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TheoryObjectifier.kt new file mode 100644 index 000000000..7af7b799c --- /dev/null +++ b/serialize-theory/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TheoryObjectifier.kt @@ -0,0 +1,15 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.objectify.Objectifier +import it.unibo.tuprolog.theory.Theory +import kotlin.js.JsName +import kotlin.jvm.JvmStatic + +interface TheoryObjectifier : Objectifier { + companion object { + @JsName("default") + @JvmStatic + val default: TheoryObjectifier + get() = theoryObjectifier() + } +} \ No newline at end of file diff --git a/serialize-theory/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TheorySerialization.kt b/serialize-theory/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TheorySerialization.kt new file mode 100644 index 000000000..e7fdff393 --- /dev/null +++ b/serialize-theory/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TheorySerialization.kt @@ -0,0 +1,13 @@ +@file:JvmName("TheorySerialization") + +package it.unibo.tuprolog.serialize + +import kotlin.jvm.JvmName + +expect fun theorySerializer(mimeType: MimeType): TheorySerializer + +expect fun theoryDeserializer(mimeType: MimeType): TheoryDeserializer + +expect fun theoryObjectifier(): TheoryObjectifier + +expect fun theoryDeobjectifier(): TheoryDeobjectifier \ No newline at end of file diff --git a/serialize-theory/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TheorySerializer.kt b/serialize-theory/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TheorySerializer.kt new file mode 100644 index 000000000..787d32e19 --- /dev/null +++ b/serialize-theory/src/commonMain/kotlin/it/unibo/tuprolog/serialize/TheorySerializer.kt @@ -0,0 +1,15 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.theory.Theory +import kotlin.js.JsName +import kotlin.jvm.JvmStatic + +interface TheorySerializer : Serializer { + companion object { + @JvmStatic + @JsName("of") + fun of(mimeType: MimeType): TheorySerializer { + return theorySerializer(mimeType) + } + } +} \ No newline at end of file diff --git a/serialize-theory/src/commonTest/kotlin/it/unibo/tuprolog/serialize/Instances.kt b/serialize-theory/src/commonTest/kotlin/it/unibo/tuprolog/serialize/Instances.kt new file mode 100644 index 000000000..c95cb928a --- /dev/null +++ b/serialize-theory/src/commonTest/kotlin/it/unibo/tuprolog/serialize/Instances.kt @@ -0,0 +1,115 @@ +package it.unibo.tuprolog.serialize + +object Instances { + val commonRulesInJSON = "[{\"head\":{\"fun\":\"not\",\"args\":[{\"var\":\"G\"}]},\"body\":{\"fun\":\"\\\\+\",\"args\":[{\"var\":\"G\"}]}},{\"head\":{\"fun\":\"->\",\"args\":[{\"var\":\"Cond\"},{\"var\":\"Then\"}]},\"body\":{\"tuple\":[{\"fun\":\"call\",\"args\":[{\"var\":\"Cond\"}]},\"!\",{\"var\":\"Then\"}]}},{\"head\":{\"fun\":\";\",\"args\":[{\"fun\":\"->\",\"args\":[{\"var\":\"Cond\"},{\"var\":\"Then\"}]},{\"var\":\"Else\"}]},\"body\":{\"tuple\":[{\"fun\":\"call\",\"args\":[{\"var\":\"Cond\"}]},\"!\",{\"var\":\"Then\"}]}},{\"head\":{\"fun\":\";\",\"args\":[{\"fun\":\"->\",\"args\":[{\"var\":\"Cond\"},{\"var\":\"Then\"}]},{\"var\":\"Else\"}]},\"body\":{\"tuple\":[\"!\",{\"var\":\"Else\"}]}},{\"head\":{\"fun\":\";\",\"args\":[{\"var\":\"A\"},{\"var\":\"B\"}]},\"body\":{\"var\":\"A\"}},{\"head\":{\"fun\":\";\",\"args\":[{\"var\":\"A\"},{\"var\":\"B\"}]},\"body\":{\"var\":\"B\"}},{\"head\":{\"fun\":\"member\",\"args\":[{\"var\":\"H\"},{\"list\":[{\"var\":\"H\"}],\"tail\":{\"var\":\"_\"}}]}},{\"head\":{\"fun\":\"member\",\"args\":[{\"var\":\"H\"},{\"list\":[{\"var\":\"_\"}],\"tail\":{\"var\":\"T\"}}]},\"body\":{\"fun\":\"member\",\"args\":[{\"var\":\"H\"},{\"var\":\"T\"}]}},{\"head\":{\"fun\":\"append\",\"args\":[{\"list\":[]},{\"var\":\"X\"},{\"var\":\"X\"}]}},{\"head\":{\"fun\":\"append\",\"args\":[{\"list\":[{\"var\":\"X\"}],\"tail\":{\"var\":\"Y\"}},{\"var\":\"Z\"},{\"list\":[{\"var\":\"X\"}],\"tail\":{\"var\":\"W\"}}]},\"body\":{\"fun\":\"append\",\"args\":[{\"var\":\"Y\"},{\"var\":\"Z\"},{\"var\":\"W\"}]}}]" + + val commonRulesInYAML = + """ + |- head: + | fun: "not" + | args: + | - var: "G" + | body: + | fun: "\\+" + | args: + | - var: "G" + |- head: + | fun: "->" + | args: + | - var: "Cond" + | - var: "Then" + | body: + | tuple: + | - fun: "call" + | args: + | - var: "Cond" + | - "!" + | - var: "Then" + |- head: + | fun: ";" + | args: + | - fun: "->" + | args: + | - var: "Cond" + | - var: "Then" + | - var: "Else" + | body: + | tuple: + | - fun: "call" + | args: + | - var: "Cond" + | - "!" + | - var: "Then" + |- head: + | fun: ";" + | args: + | - fun: "->" + | args: + | - var: "Cond" + | - var: "Then" + | - var: "Else" + | body: + | tuple: + | - "!" + | - var: "Else" + |- head: + | fun: ";" + | args: + | - var: "A" + | - var: "B" + | body: + | var: "A" + |- head: + | fun: ";" + | args: + | - var: "A" + | - var: "B" + | body: + | var: "B" + |- head: + | fun: "member" + | args: + | - var: "H" + | - list: + | - var: "H" + | tail: + | var: "_" + |- head: + | fun: "member" + | args: + | - var: "H" + | - list: + | - var: "_" + | tail: + | var: "T" + | body: + | fun: "member" + | args: + | - var: "H" + | - var: "T" + |- head: + | fun: "append" + | args: + | - list: [] + | - var: "X" + | - var: "X" + |- head: + | fun: "append" + | args: + | - list: + | - var: "X" + | tail: + | var: "Y" + | - var: "Z" + | - list: + | - var: "X" + | tail: + | var: "W" + | body: + | fun: "append" + | args: + | - var: "Y" + | - var: "Z" + | - var: "W" + """.trimMargin() +} \ No newline at end of file diff --git a/serialize-theory/src/commonTest/kotlin/it/unibo/tuprolog/serialize/TestTheoryDeserialization.kt b/serialize-theory/src/commonTest/kotlin/it/unibo/tuprolog/serialize/TestTheoryDeserialization.kt new file mode 100644 index 000000000..47c382a8f --- /dev/null +++ b/serialize-theory/src/commonTest/kotlin/it/unibo/tuprolog/serialize/TestTheoryDeserialization.kt @@ -0,0 +1,17 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.solve.stdlib.CommonRules +import kotlin.test.Test + +class TestTheoryDeserialization { + + @Test + fun testTheorySerializationJSON() { + TheoryDeserializer.of(MimeType.Json).assertDeserializationWorks(CommonRules.theory, Instances.commonRulesInJSON) + } + + @Test + fun testTheorySerializationYAML() { + TheoryDeserializer.of(MimeType.Yaml).assertDeserializationWorks(CommonRules.theory, Instances.commonRulesInYAML) + } +} \ No newline at end of file diff --git a/serialize-theory/src/commonTest/kotlin/it/unibo/tuprolog/serialize/TestTheorySerialization.kt b/serialize-theory/src/commonTest/kotlin/it/unibo/tuprolog/serialize/TestTheorySerialization.kt new file mode 100644 index 000000000..14139f896 --- /dev/null +++ b/serialize-theory/src/commonTest/kotlin/it/unibo/tuprolog/serialize/TestTheorySerialization.kt @@ -0,0 +1,17 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.solve.stdlib.CommonRules +import kotlin.test.Test + +class TestTheorySerialization { + + @Test + fun testTheorySerializationJSON() { + TheorySerializer.of(MimeType.Json).assertSerializationWorks(Instances.commonRulesInJSON, CommonRules.theory) + } + + @Test + fun testTheorySerializationYAML() { + TheorySerializer.of(MimeType.Yaml).assertSerializationWorks(Instances.commonRulesInYAML, CommonRules.theory) + } +} \ No newline at end of file diff --git a/serialize-theory/src/commonTest/kotlin/it/unibo/tuprolog/serialize/TestUtils.kt b/serialize-theory/src/commonTest/kotlin/it/unibo/tuprolog/serialize/TestUtils.kt new file mode 100644 index 000000000..bc808e2d3 --- /dev/null +++ b/serialize-theory/src/commonTest/kotlin/it/unibo/tuprolog/serialize/TestUtils.kt @@ -0,0 +1,44 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.core.* +import it.unibo.tuprolog.serialize.ObjectsUtils.deeplyEqual +import it.unibo.tuprolog.serialize.ObjectsUtils.parseAsObject +import it.unibo.tuprolog.theory.Theory +import it.unibo.tuprolog.utils.itemWiseEquals + +fun Serializer.assertSerializationWorks(expected: String, actual: T) { + val expectedObj = parseAsObject(expected, mimeType) + val actualObj = TheoryObjectifier.default.objectify(actual) + kotlin.test.assertTrue(""" + |Expected: + | $expectedObj + |got instead: + | $actualObj + | + """.trimMargin()) { deeplyEqual(expectedObj, actualObj) } +} + +fun Serializer.assertTermSerializationWorks(expected: String, actualGenerator: Scope.() -> Theory) { + assertSerializationWorks(expected, Scope.empty().actualGenerator()) +} + +fun Deserializer.assertDeserializationWorks(expected: T, actual: String) { + val deserialized = deserialize(actual) + kotlin.test.assertTrue(""" + |Expected: + | $expected + |got: + | $deserialized + | + """.trimMargin()) { representationsAreEqual(expected, deserialized) } +} + +fun Deserializer.assertTermDeserializationWorks(actual: String, expectedGenerator: Scope.() -> Theory) { + assertDeserializationWorks(Scope.empty().expectedGenerator(), actual) +} + +private fun representationsAreEqual(t1: Theory, t2: Theory): Boolean { + return itemWiseEquals(t1, t2) { x, y -> + x.equals(y, false) + } +} \ No newline at end of file diff --git a/serialize-theory/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTheoryDeobjectifier.kt b/serialize-theory/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTheoryDeobjectifier.kt new file mode 100644 index 000000000..2b8d94804 --- /dev/null +++ b/serialize-theory/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTheoryDeobjectifier.kt @@ -0,0 +1,22 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.core.Clause +import it.unibo.tuprolog.theory.Theory + +internal class JsTheoryDeobjectifier : TheoryDeobjectifier { + override fun deobjectify(`object`: Any): Theory { + return Theory.of( + TermDeobjectifier.default.deobjectifyMany(`object`) + .asSequence() + .map { it as Clause } + ) + } + + override fun deobjectifyMany(`object`: Any): Iterable { + return when (`object`) { + is Array<*> -> `object`.map { deobjectify(it ?: throw DeobjectificationException(`object`)) } + else -> throw DeobjectificationException(`object`) + } + } + +} \ No newline at end of file diff --git a/serialize-theory/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTheoryDeserializer.kt b/serialize-theory/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTheoryDeserializer.kt new file mode 100644 index 000000000..d3e718668 --- /dev/null +++ b/serialize-theory/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTheoryDeserializer.kt @@ -0,0 +1,18 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.theory.Theory + +internal class JsTheoryDeserializer(override val mimeType: MimeType) : TheoryDeserializer { + override fun deserialize(string: String): Theory = + JsTheoryDeobjectifier().deobjectify(parse(string)) + + override fun deserializeMany(string: String): Iterable = + JsTheoryDeobjectifier().deobjectifyMany(parse(string)) + + private fun parse(string: String): Any = + when (mimeType) { + is MimeType.Xml -> throw NotImplementedError("XML is currently not supported in JS") + is MimeType.Json -> JSON.parse(string) + is MimeType.Yaml -> YAML.parse(string) + } as Any +} \ No newline at end of file diff --git a/serialize-theory/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTheoryObjectifier.kt b/serialize-theory/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTheoryObjectifier.kt new file mode 100644 index 000000000..032951c93 --- /dev/null +++ b/serialize-theory/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTheoryObjectifier.kt @@ -0,0 +1,16 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.theory.Theory + +internal class JsTheoryObjectifier : TheoryObjectifier { + private val objectifier = TermObjectifier.default + + override fun objectify(value: Theory): Any { + return value.map { objectifier.objectify(it) }.toTypedArray() + } + + override fun objectifyMany(values: Iterable): Any { + return values.map { objectify(it) }.toTypedArray() + } + +} \ No newline at end of file diff --git a/serialize-theory/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTheorySerializer.kt b/serialize-theory/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTheorySerializer.kt new file mode 100644 index 000000000..cdae0be82 --- /dev/null +++ b/serialize-theory/src/jsMain/kotlin/it/unibo/tuprolog/serialize/JsTheorySerializer.kt @@ -0,0 +1,21 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.theory.Theory + +internal class JsTheorySerializer(override val mimeType: MimeType) : TheorySerializer { + + private val objectifier = JsTheoryObjectifier() + + override fun serialize(value: Theory): String = + stringify(objectifier.objectify(value)) + + override fun serializeMany(values: Iterable): String = + stringify(objectifier.objectifyMany(values)) + + private fun stringify(objectified: Any): String = + when (mimeType) { + is MimeType.Xml -> throw NotImplementedError("XML is currently not supported in JS") + is MimeType.Json -> JSON.stringify(objectified) + is MimeType.Yaml -> YAML.stringify(objectified) + } +} \ No newline at end of file diff --git a/serialize-theory/src/jsMain/kotlin/it/unibo/tuprolog/serialize/TheorySerialization.kt b/serialize-theory/src/jsMain/kotlin/it/unibo/tuprolog/serialize/TheorySerialization.kt new file mode 100644 index 000000000..123f35254 --- /dev/null +++ b/serialize-theory/src/jsMain/kotlin/it/unibo/tuprolog/serialize/TheorySerialization.kt @@ -0,0 +1,17 @@ +package it.unibo.tuprolog.serialize + +actual fun theorySerializer(mimeType: MimeType): TheorySerializer { + return JsTheorySerializer(mimeType) +} + +actual fun theoryDeserializer(mimeType: MimeType): TheoryDeserializer { + return JsTheoryDeserializer(mimeType) +} + +actual fun theoryObjectifier(): TheoryObjectifier { + return JsTheoryObjectifier() +} + +actual fun theoryDeobjectifier(): TheoryDeobjectifier { + return JsTheoryDeobjectifier() +} \ No newline at end of file diff --git a/serialize-theory/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTheoryDeobjectifier.kt b/serialize-theory/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTheoryDeobjectifier.kt new file mode 100644 index 000000000..a83916e54 --- /dev/null +++ b/serialize-theory/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTheoryDeobjectifier.kt @@ -0,0 +1,21 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.core.Clause +import it.unibo.tuprolog.theory.Theory + +internal class JvmTheoryDeobjectifier : TheoryDeobjectifier { + override fun deobjectify(`object`: Any): Theory { + return Theory.of( + TermDeobjectifier.default.deobjectifyMany(`object`) + .asSequence() + .map { it as Clause } + ) + } + + override fun deobjectifyMany(`object`: Any): Iterable { + return when (`object`) { + is List<*> -> `object`.map { deobjectify(it ?: throw DeobjectificationException(`object`)) } + else -> throw DeobjectificationException(`object`) + } + } +} \ No newline at end of file diff --git a/serialize-theory/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTheoryDeserializer.kt b/serialize-theory/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTheoryDeserializer.kt new file mode 100644 index 000000000..366513419 --- /dev/null +++ b/serialize-theory/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTheoryDeserializer.kt @@ -0,0 +1,20 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.theory.Theory +import java.io.Reader + +internal class JvmTheoryDeserializer(override val mimeType: MimeType) : ReadingTheoryDeserializer { + + private val mapper = mimeType.objectMapper + + override fun deserialize(reader: Reader): Theory = + JvmTheoryDeobjectifier().deobjectify( + mapper.readValue(reader, java.lang.Object::class.java) + ) + + override fun deserializeMany(reader: Reader): Iterable = + JvmTheoryDeobjectifier().deobjectifyMany( + mapper.readValue(reader, java.lang.Object::class.java) + ) + +} \ No newline at end of file diff --git a/serialize-theory/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTheoryObjectifier.kt b/serialize-theory/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTheoryObjectifier.kt new file mode 100644 index 000000000..962e99d66 --- /dev/null +++ b/serialize-theory/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTheoryObjectifier.kt @@ -0,0 +1,16 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.theory.Theory + +internal class JvmTheoryObjectifier : TheoryObjectifier { + + private val objectifier = TermObjectifier.default + + override fun objectify(value: Theory): Any { + return value.map { objectifier.objectify(it) } + } + + override fun objectifyMany(values: Iterable): Any { + return values.map { objectify(it) } + } +} \ No newline at end of file diff --git a/serialize-theory/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTheorySerializer.kt b/serialize-theory/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTheorySerializer.kt new file mode 100644 index 000000000..9e03fc9b1 --- /dev/null +++ b/serialize-theory/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/JvmTheorySerializer.kt @@ -0,0 +1,18 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.theory.Theory +import java.io.Writer + +internal class JvmTheorySerializer(override val mimeType: MimeType) : WritingTheorySerializer { + + private val mapper = mimeType.objectMapper + private val objectifier = JvmTheoryObjectifier() + + override fun serialize(writer: Writer, value: Theory) { + mapper.writeValue(writer, objectifier.objectify(value)) + } + + override fun serializeMany(writer: Writer, values: Iterable) { + mapper.writeValue(writer, objectifier.objectifyMany(values)) + } +} \ No newline at end of file diff --git a/serialize-theory/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/ReadingTheoryDeserializer.kt b/serialize-theory/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/ReadingTheoryDeserializer.kt new file mode 100644 index 000000000..692803e93 --- /dev/null +++ b/serialize-theory/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/ReadingTheoryDeserializer.kt @@ -0,0 +1,12 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.theory.Theory + +interface ReadingTheoryDeserializer : TheoryDeserializer, ReadingDeserializer { + companion object { + @JvmStatic + fun of(mimeType: MimeType): ReadingTheoryDeserializer { + return JvmTheoryDeserializer(mimeType) + } + } +} \ No newline at end of file diff --git a/serialize-theory/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/TheorySerialization.kt b/serialize-theory/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/TheorySerialization.kt new file mode 100644 index 000000000..70c25c6d4 --- /dev/null +++ b/serialize-theory/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/TheorySerialization.kt @@ -0,0 +1,17 @@ +package it.unibo.tuprolog.serialize + +actual fun theorySerializer(mimeType: MimeType): TheorySerializer { + return JvmTheorySerializer(mimeType) +} + +actual fun theoryDeserializer(mimeType: MimeType): TheoryDeserializer { + return JvmTheoryDeserializer(mimeType) +} + +actual fun theoryObjectifier(): TheoryObjectifier { + return JvmTheoryObjectifier() +} + +actual fun theoryDeobjectifier(): TheoryDeobjectifier { + return JvmTheoryDeobjectifier() +} \ No newline at end of file diff --git a/serialize-theory/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/WritingTheorySerializer.kt b/serialize-theory/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/WritingTheorySerializer.kt new file mode 100644 index 000000000..4b1f89483 --- /dev/null +++ b/serialize-theory/src/jvmMain/kotlin/it/unibo/tuprolog/serialize/WritingTheorySerializer.kt @@ -0,0 +1,12 @@ +package it.unibo.tuprolog.serialize + +import it.unibo.tuprolog.theory.Theory + +interface WritingTheorySerializer : TheorySerializer, WritingSerializer { + companion object { + @JvmStatic + fun of(mimeType: MimeType): WritingTheorySerializer { + return JvmTheorySerializer(mimeType) + } + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 955f972b3..0cadfb20a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -16,6 +16,8 @@ include("parser-core") include("parser-jvm") include("parser-js") include("parser-theory") +include("serialize-core") +include("serialize-theory") include("repl") include("examples") \ No newline at end of file