diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintErrNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintErrNode.java new file mode 100644 index 00000000000..0bfae29b73d --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintErrNode.java @@ -0,0 +1,61 @@ +package org.enso.interpreter.node.expression.builtin.io; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.dsl.CachedContext; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.NodeInfo; +import java.io.PrintStream; +import org.enso.interpreter.Language; +import org.enso.interpreter.node.expression.builtin.BuiltinRootNode; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; +import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition.ExecutionMode; +import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.callable.function.FunctionSchema.CallStrategy; +import org.enso.interpreter.runtime.state.Stateful; + +@NodeInfo(shortName = "IO.print_err", description = "Prints its argument to standard error.") +public abstract class PrintErrNode extends BuiltinRootNode { + PrintErrNode(Language language) { + super(language); + } + + @Specialization + Stateful doPrint(VirtualFrame frame, @CachedContext(Language.class) Context ctx) { + print(ctx.getErr(), Function.ArgumentsHelper.getPositionalArguments(frame.getArguments())[1]); + Object state = Function.ArgumentsHelper.getState(frame.getArguments()); + + return new Stateful(state, ctx.getUnit().newInstance()); + } + + @TruffleBoundary + private void print(PrintStream err, Object object) { + err.println(object); + } + + /** + * Creates a {@link Function} object ignoring its first argument and printing the second to the + * standard error stream. + * + * @param language the current {@link Language} instance + * @return a {@link Function} object wrapping the behavior of this node + */ + public static Function makeFunction(Language language) { + return Function.fromBuiltinRootNode( + PrintErrNodeGen.create(language), + CallStrategy.ALWAYS_DIRECT, + new ArgumentDefinition(0, "this", ExecutionMode.EXECUTE), + new ArgumentDefinition(1, "value", ExecutionMode.EXECUTE)); + } + + /** + * Gets the source-level name of this node. + * + * @return the source-level name of this node + */ + @Override + public String getName() { + return "IO.print_err"; + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintlnNode.java similarity index 83% rename from engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintNode.java rename to engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintlnNode.java index cc653c10e1a..7e5c99e8dd7 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintlnNode.java @@ -15,22 +15,22 @@ import org.enso.interpreter.runtime.callable.function.FunctionSchema; import org.enso.interpreter.runtime.state.Stateful; /** Allows for printing arbitrary values to the standard output. */ -@NodeInfo(shortName = "IO.println", description = "Root of the IO.println method.") -public abstract class PrintNode extends BuiltinRootNode { - PrintNode(Language language) { +@NodeInfo(shortName = "IO.println", description = "Prints its argument to standard out.") +public abstract class PrintlnNode extends BuiltinRootNode { + PrintlnNode(Language language) { super(language); } @Specialization Stateful doPrint(VirtualFrame frame, @CachedContext(Language.class) Context ctx) { - doPrint(ctx.getOut(), Function.ArgumentsHelper.getPositionalArguments(frame.getArguments())[1]); + print(ctx.getOut(), Function.ArgumentsHelper.getPositionalArguments(frame.getArguments())[1]); Object state = Function.ArgumentsHelper.getState(frame.getArguments()); return new Stateful(state, ctx.getUnit().newInstance()); } @CompilerDirectives.TruffleBoundary - private void doPrint(PrintStream out, Object object) { + private void print(PrintStream out, Object object) { out.println(object); } @@ -43,7 +43,7 @@ public abstract class PrintNode extends BuiltinRootNode { */ public static Function makeFunction(Language language) { return Function.fromBuiltinRootNode( - PrintNodeGen.create(language), + PrintlnNodeGen.create(language), FunctionSchema.CallStrategy.ALWAYS_DIRECT, new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE), new ArgumentDefinition(1, "value", ArgumentDefinition.ExecutionMode.EXECUTE)); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/ReadlnNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/ReadlnNode.java new file mode 100644 index 00000000000..84c11fc6b17 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/ReadlnNode.java @@ -0,0 +1,65 @@ +package org.enso.interpreter.node.expression.builtin.io; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.dsl.CachedContext; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.NodeInfo; +import java.io.BufferedReader; +import java.io.IOException; +import org.enso.interpreter.Language; +import org.enso.interpreter.node.expression.builtin.BuiltinRootNode; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; +import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition.ExecutionMode; +import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.callable.function.FunctionSchema.CallStrategy; +import org.enso.interpreter.runtime.error.RuntimeError; +import org.enso.interpreter.runtime.state.Stateful; + +@NodeInfo(shortName = "IO.readln", description = "Reads a line from standard in.") +public abstract class ReadlnNode extends BuiltinRootNode { + public ReadlnNode(Language language) { + super(language); + } + + @Specialization + Stateful doRead(VirtualFrame frame, @CachedContext(Language.class) Context ctx) { + return read(ctx.getIn(), Function.ArgumentsHelper.getState(frame.getArguments())); + } + + @TruffleBoundary + private Stateful read(BufferedReader in, Object state) { + try { + String str = in.readLine(); + + return new Stateful(state, str); + } catch (IOException e) { + return new Stateful(state, new RuntimeError("Empty input stream.")); + } + } + + /** + * Creates a {@link Function} object ignoring its first argument and reading from the standard + * input stream. + * + * @param language the current {@link Language} instance + * @return a {@link Function} object wrapping the behavior of this node + */ + public static Function makeFunction(Language language) { + return Function.fromBuiltinRootNode( + ReadlnNodeGen.create(language), + CallStrategy.ALWAYS_DIRECT, + new ArgumentDefinition(0, "this", ExecutionMode.EXECUTE)); + } + + /** + * Gets the source-level name of this node. + * + * @return the source-level name of the node + */ + @Override + public String getName() { + return "IO.readln"; + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/NanoTimeNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/system/NanoTimeNode.java similarity index 89% rename from engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/NanoTimeNode.java rename to engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/system/NanoTimeNode.java index fc248b358df..b0fe1f286af 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/NanoTimeNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/system/NanoTimeNode.java @@ -1,4 +1,4 @@ -package org.enso.interpreter.node.expression.builtin.io; +package org.enso.interpreter.node.expression.builtin.system; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.frame.VirtualFrame; @@ -10,7 +10,7 @@ import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.callable.function.FunctionSchema; import org.enso.interpreter.runtime.state.Stateful; -@NodeInfo(shortName = "IO.nano_time", description = "Gets the nanosecond resolution system time.") +@NodeInfo(shortName = "System.nano_time", description = "Gets the nanosecond resolution system time.") public final class NanoTimeNode extends BuiltinRootNode { private NanoTimeNode(Language language) { super(language); @@ -47,6 +47,6 @@ public final class NanoTimeNode extends BuiltinRootNode { */ @Override public String getName() { - return "IO.nano_time"; + return "System.nano_time"; } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/Builtins.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/Builtins.java index c08b7cb96a8..19e2a8f1384 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/Builtins.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/Builtins.java @@ -14,8 +14,10 @@ import org.enso.interpreter.node.expression.builtin.function.ExplicitCallFunctio import org.enso.interpreter.node.expression.builtin.interop.generic.*; import org.enso.interpreter.node.expression.builtin.interop.syntax.MethodDispatchNode; import org.enso.interpreter.node.expression.builtin.interop.syntax.ConstructorDispatchNode; -import org.enso.interpreter.node.expression.builtin.io.NanoTimeNode; -import org.enso.interpreter.node.expression.builtin.io.PrintNode; +import org.enso.interpreter.node.expression.builtin.io.PrintErrNode; +import org.enso.interpreter.node.expression.builtin.io.PrintlnNode; +import org.enso.interpreter.node.expression.builtin.io.ReadlnNode; +import org.enso.interpreter.node.expression.builtin.system.NanoTimeNode; import org.enso.interpreter.node.expression.builtin.interop.java.*; import org.enso.interpreter.node.expression.builtin.number.AddNode; import org.enso.interpreter.node.expression.builtin.number.DivideNode; @@ -94,6 +96,7 @@ public class Builtins { new ArgumentDefinition(0, "head", ArgumentDefinition.ExecutionMode.EXECUTE), new ArgumentDefinition(1, "rest", ArgumentDefinition.ExecutionMode.EXECUTE)); AtomConstructor io = new AtomConstructor("IO", scope).initializeFields(); + AtomConstructor system = new AtomConstructor("System", scope).initializeFields(); AtomConstructor panic = new AtomConstructor("Panic", scope).initializeFields(); AtomConstructor error = new AtomConstructor("Error", scope).initializeFields(); AtomConstructor state = new AtomConstructor("State", scope).initializeFields(); @@ -120,8 +123,11 @@ public class Builtins { scope.registerConstructor(java); scope.registerConstructor(createPolyglot(language)); - scope.registerMethod(io, "println", PrintNode.makeFunction(language)); - scope.registerMethod(io, "nano_time", NanoTimeNode.makeFunction(language)); + scope.registerMethod(io, "println", PrintlnNode.makeFunction(language)); + scope.registerMethod(io, "print_err", PrintErrNode.makeFunction(language)); + scope.registerMethod(io, "readln", ReadlnNode.makeFunction(language)); + + scope.registerMethod(system, "nano_time", NanoTimeNode.makeFunction(language)); scope.registerMethod(panic, "throw", PanicNode.makeFunction(language)); scope.registerMethod(panic, "recover", CatchPanicNode.makeFunction(language)); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/Context.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/Context.java index 1645ce1c0a3..733013441d7 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/Context.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/Context.java @@ -3,7 +3,11 @@ package org.enso.interpreter.runtime; import com.oracle.truffle.api.TruffleFile; import com.oracle.truffle.api.TruffleLanguage; import com.oracle.truffle.api.TruffleLanguage.Env; +import java.io.BufferedReader; import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.io.PrintStream; import java.util.List; import java.util.Map; @@ -30,6 +34,8 @@ public class Context { private final Env environment; private final Compiler compiler; private final PrintStream out; + private final PrintStream err; + private final BufferedReader in; private final List packages; /** @@ -42,6 +48,8 @@ public class Context { this.language = language; this.environment = environment; this.out = new PrintStream(environment.out()); + this.err = new PrintStream(environment.err()); + this.in = new BufferedReader(new InputStreamReader(environment.in())); List packagePaths = OptionsHelper.getPackagesPaths(environment); @@ -125,6 +133,24 @@ public class Context { return out; } + /** + * Returns the standard error stream for this context. + * + * @return the standard error stream for this context + */ + public PrintStream getErr() { + return err; + } + + /** + * Returns the standard input stream for this context. + * + * @return the standard input stream for this context + */ + public BufferedReader getIn() { + return in; + } + /** * Creates a new module scope that automatically imports all the builtin types and methods. * diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/InterpreterTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/InterpreterTest.scala index 0ee433e0247..2b223e3d10a 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/InterpreterTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/InterpreterTest.scala @@ -1,6 +1,11 @@ package org.enso.interpreter.test -import java.io.ByteArrayOutputStream +import java.io.{ + ByteArrayOutputStream, + PipedInputStream, + PipedOutputStream, + PrintStream +} import java.util.UUID import com.oracle.truffle.api.instrumentation.EventBinding @@ -72,12 +77,19 @@ trait InterpreterRunner { value.execute(l.map(_.asInstanceOf[AnyRef]): _*) ) } - val output = new ByteArrayOutputStream() + val output = new ByteArrayOutputStream() + val err = new ByteArrayOutputStream() + val inOut = new PipedOutputStream() + val inOutPrinter = new PrintStream(inOut, true) + val in = new PipedInputStream(inOut) + val ctx = Context .newBuilder(LanguageInfo.ID) .allowExperimentalOptions(true) .allowAllAccess(true) .out(output) + .err(err) + .in(in) .build() lazy val executionContext = new PolyglotContext(ctx) @@ -127,12 +139,22 @@ trait InterpreterRunner { } } + def consumeErr: List[String] = { + val result = err.toString + err.reset() + result.linesIterator.toList + } + def consumeOut: List[String] = { val result = output.toString output.reset() result.linesIterator.toList } + def feedInput(string: String): Unit = { + inOutPrinter.println(string) + } + def getReplInstrument: ReplDebuggerInstrument = { ctx.getEngine.getInstruments .get(ReplDebuggerInstrument.INSTRUMENT_ID) diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/TextTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/TextTest.scala index a3a0f64786f..1061b5f6442 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/TextTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/TextTest.scala @@ -64,4 +64,31 @@ class TextTest extends InterpreterTest { eval(code) consumeOut shouldEqual List("\"Grzegorz Brzeczyszczykiewicz\"") } + + "Text literals" should "be able to be printed to standard error" in { + val errString = "\"My error string\"" + val resultStr = errString.drop(1).dropRight(1) + + val code = + s""" + |main = IO.print_err $errString + |""".stripMargin + + eval(code) + consumeErr shouldEqual List(resultStr) + } + + "Text literals" should "be able to be read from standard in" in { + val inputString = "foobarbaz" + + val code = + """ + |main = + | IO.readln + " yay!" + |""".stripMargin + + feedInput(inputString) + + eval(code) shouldEqual "foobarbaz yay!" + } } diff --git a/lib/syntax/specialization/shared/src/test/scala/org/enso/syntax/text/ParserTest.scala b/lib/syntax/specialization/shared/src/test/scala/org/enso/syntax/text/ParserTest.scala index d777e9cc3fe..6e98317c3a8 100644 --- a/lib/syntax/specialization/shared/src/test/scala/org/enso/syntax/text/ParserTest.scala +++ b/lib/syntax/specialization/shared/src/test/scala/org/enso/syntax/text/ParserTest.scala @@ -50,7 +50,6 @@ class ParserTest extends AnyFlatSpec with Matchers { val module = Parser().run(input) val idmap1 = module.idMap val idmap2 = Parser().run(new Reader(input), idmap1).idMap - println(module.zipWithOffset) assertSpan(input, module) assert(module.show() == new Reader(input).toString()) assert(idmap1 == idmap2)