Stopgap JSON serialization for Enso objects (#698)

This commit is contained in:
Marcin Kostrzewa 2020-04-29 11:23:46 +02:00 committed by GitHub
parent ff096cdb44
commit d03a5a9dde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 155 additions and 2 deletions

View File

@ -542,7 +542,7 @@ lazy val runtime = (project in file("engine/runtime"))
logBuffered in Test := false,
scalacOptions += "-Ymacro-annotations",
scalacOptions ++= Seq("-Ypatmat-exhaust-depth", "off"),
libraryDependencies ++= jmh ++ Seq(
libraryDependencies ++= circe ++ jmh ++ Seq(
"com.chuusai" %% "shapeless" % "2.3.3",
"org.apache.commons" % "commons-lang3" % "3.9",
"org.apache.tika" % "tika-core" % "1.23",

View File

@ -0,0 +1,61 @@
package org.enso.interpreter.node.expression.builtin.text;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.expression.builtin.BuiltinRootNode;
import org.enso.interpreter.runtime.builtin.LanguageEntitySerializer;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.callable.function.FunctionSchema.CallStrategy;
import org.enso.interpreter.runtime.state.Stateful;
/** An implementation of generic JSON serialization. */
@NodeInfo(shortName = "Any.json_serialize", description = "Generic JSON serialization.")
public class JsonSerializeNode extends BuiltinRootNode {
private JsonSerializeNode(Language language) {
super(language);
}
/**
* Creates a function wrapping this node.
*
* @param language the current language instance
* @return a function wrapping this node
*/
public static Function makeFunction(Language language) {
return Function.fromBuiltinRootNode(
new JsonSerializeNode(language),
CallStrategy.ALWAYS_DIRECT,
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE));
}
/**
* Executes the node.
*
* @param frame current execution frame.
* @return the result of converting input into a string.
*/
@Override
public Stateful execute(VirtualFrame frame) {
Object thisArg = Function.ArgumentsHelper.getPositionalArguments(frame.getArguments())[0];
Object state = Function.ArgumentsHelper.getState(frame.getArguments());
return new Stateful(state, serialize(thisArg));
}
@CompilerDirectives.TruffleBoundary
private String serialize(Object obj) {
return LanguageEntitySerializer.serialize(obj);
}
/**
* Returns a language-specific name for this node.
*
* @return the name of this node
*/
@Override
public String getName() {
return "Any.json_serialize";
}
}

View File

@ -20,6 +20,7 @@ import org.enso.interpreter.node.expression.builtin.state.PutStateNode;
import org.enso.interpreter.node.expression.builtin.state.RunStateNode;
import org.enso.interpreter.node.expression.builtin.text.AnyToTextNode;
import org.enso.interpreter.node.expression.builtin.text.ConcatNode;
import org.enso.interpreter.node.expression.builtin.text.JsonSerializeNode;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.scope.ModuleScope;
@ -123,6 +124,7 @@ public class Builtins {
scope.registerMethod(text, "+", ConcatNode.makeFunction(language));
scope.registerMethod(any, "to_text", AnyToTextNode.makeFunction(language));
scope.registerMethod(any, "json_serialize", JsonSerializeNode.makeFunction(language));
}
/**

View File

@ -0,0 +1,33 @@
package org.enso.interpreter.runtime.builtin
import io.circe.Json
import org.enso.interpreter.runtime.callable.atom.{Atom, AtomConstructor}
/**
* Helper for JSON-serializing runtime entities of the language.
*/
object LanguageEntitySerializer {
/**
* Serializes a language entity into a JSON string. Returns null JSON for
* unexpected entities.
*
* @param obj any object representing an Enso language entity.
* @return the JSON string representing `obj` or `"null"` if the object
* is not a serializable language entity.
*/
final def serialize(obj: Object): String = toJson(obj).noSpaces
private def toJson(obj: Any): Json = obj match {
case l: Long => Json.fromLong(l)
case s: String => Json.fromString(s)
case cons: AtomConstructor =>
Json.obj("type" -> Json.fromString(cons.getName), "fields" -> Json.arr())
case atom: Atom =>
Json.obj(
"type" -> Json.fromString(atom.getConstructor.getName),
"fields" -> Json.arr(atom.getFields.map(toJson).toIndexedSeq: _*)
)
case _ => Json.Null
}
}

View File

@ -21,7 +21,7 @@ class StrictCompileErrorsTest extends InterpreterTest {
| x = ()
| x = 5
| y = @
|""".stripMargin.lines.mkString("\n")
|""".stripMargin.linesIterator.mkString("\n")
the[InterpreterException] thrownBy eval(code) should have message "Compilation aborted due to errors."
val _ :: errors = consumeOut

View File

@ -0,0 +1,57 @@
package org.enso.std.test
import org.enso.interpreter.test.InterpreterTest
class JsonSerializationTest extends InterpreterTest {
"strings" should "be serializable" in {
val code =
"""
|main = "it's a \"string\"" . json_serialize
|""".stripMargin
eval(code) shouldEqual "\"it's a \\\"string\\\"\""
}
"nubmers" should "be serializable" in {
val code =
"""
|main = 1234 . json_serialize
|""".stripMargin
eval(code) shouldEqual "1234"
}
"atoms" should "be serializable" in {
val code =
"""
|type X a b c
|
|main = X 123 "foo" Unit . json_serialize
|""".stripMargin
eval(code) shouldEqual """{"type":"X","fields":[123,"foo",{"type":"Unit","fields":[]}]}"""
}
"functions" should "serialize as a null" in {
val code =
"""
|main = (x -> x).json_serialize
|""".stripMargin
eval(code) shouldEqual "null"
}
"nested types" should "serialize recursively" in {
val code =
"""
|main =
| test_val = Cons 1 (Cons "\"foo\"" (Cons Unit (Cons (x -> x) Nil)))
| test_val.json_serialize
|""".stripMargin
val expectedResult =
"""{"type":"Cons","fields":[1,{"type":"Cons","fields":["\"foo\"",{"type":
|"Cons","fields":[{"type":"Unit","fields":[]},{"type":"Cons","fields":
|[null,{"type":"Nil","fields":[]}]}]}]}]}""".stripMargin.linesIterator
.mkString("")
eval(code) shouldEqual expectedResult
}
}