mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 13:02:07 +03:00
Stopgap JSON serialization for Enso objects (#698)
This commit is contained in:
parent
ff096cdb44
commit
d03a5a9dde
@ -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",
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user