Implement print_err and readln (#754)

This commit is contained in:
Ara Adkins 2020-05-15 16:21:39 +01:00 committed by GitHub
parent 81bde28589
commit ad9eb285fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 222 additions and 16 deletions

View File

@ -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";
}
}

View File

@ -15,22 +15,22 @@ import org.enso.interpreter.runtime.callable.function.FunctionSchema;
import org.enso.interpreter.runtime.state.Stateful; import org.enso.interpreter.runtime.state.Stateful;
/** Allows for printing arbitrary values to the standard output. */ /** Allows for printing arbitrary values to the standard output. */
@NodeInfo(shortName = "IO.println", description = "Root of the IO.println method.") @NodeInfo(shortName = "IO.println", description = "Prints its argument to standard out.")
public abstract class PrintNode extends BuiltinRootNode { public abstract class PrintlnNode extends BuiltinRootNode {
PrintNode(Language language) { PrintlnNode(Language language) {
super(language); super(language);
} }
@Specialization @Specialization
Stateful doPrint(VirtualFrame frame, @CachedContext(Language.class) Context ctx) { 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()); Object state = Function.ArgumentsHelper.getState(frame.getArguments());
return new Stateful(state, ctx.getUnit().newInstance()); return new Stateful(state, ctx.getUnit().newInstance());
} }
@CompilerDirectives.TruffleBoundary @CompilerDirectives.TruffleBoundary
private void doPrint(PrintStream out, Object object) { private void print(PrintStream out, Object object) {
out.println(object); out.println(object);
} }
@ -43,7 +43,7 @@ public abstract class PrintNode extends BuiltinRootNode {
*/ */
public static Function makeFunction(Language language) { public static Function makeFunction(Language language) {
return Function.fromBuiltinRootNode( return Function.fromBuiltinRootNode(
PrintNodeGen.create(language), PrintlnNodeGen.create(language),
FunctionSchema.CallStrategy.ALWAYS_DIRECT, FunctionSchema.CallStrategy.ALWAYS_DIRECT,
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE), new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "value", ArgumentDefinition.ExecutionMode.EXECUTE)); new ArgumentDefinition(1, "value", ArgumentDefinition.ExecutionMode.EXECUTE));

View File

@ -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";
}
}

View File

@ -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.CompilerDirectives;
import com.oracle.truffle.api.frame.VirtualFrame; 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.callable.function.FunctionSchema;
import org.enso.interpreter.runtime.state.Stateful; 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 { public final class NanoTimeNode extends BuiltinRootNode {
private NanoTimeNode(Language language) { private NanoTimeNode(Language language) {
super(language); super(language);
@ -47,6 +47,6 @@ public final class NanoTimeNode extends BuiltinRootNode {
*/ */
@Override @Override
public String getName() { public String getName() {
return "IO.nano_time"; return "System.nano_time";
} }
} }

View File

@ -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.generic.*;
import org.enso.interpreter.node.expression.builtin.interop.syntax.MethodDispatchNode; 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.interop.syntax.ConstructorDispatchNode;
import org.enso.interpreter.node.expression.builtin.io.NanoTimeNode; import org.enso.interpreter.node.expression.builtin.io.PrintErrNode;
import org.enso.interpreter.node.expression.builtin.io.PrintNode; 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.interop.java.*;
import org.enso.interpreter.node.expression.builtin.number.AddNode; import org.enso.interpreter.node.expression.builtin.number.AddNode;
import org.enso.interpreter.node.expression.builtin.number.DivideNode; 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(0, "head", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "rest", ArgumentDefinition.ExecutionMode.EXECUTE)); new ArgumentDefinition(1, "rest", ArgumentDefinition.ExecutionMode.EXECUTE));
AtomConstructor io = new AtomConstructor("IO", scope).initializeFields(); AtomConstructor io = new AtomConstructor("IO", scope).initializeFields();
AtomConstructor system = new AtomConstructor("System", scope).initializeFields();
AtomConstructor panic = new AtomConstructor("Panic", scope).initializeFields(); AtomConstructor panic = new AtomConstructor("Panic", scope).initializeFields();
AtomConstructor error = new AtomConstructor("Error", scope).initializeFields(); AtomConstructor error = new AtomConstructor("Error", scope).initializeFields();
AtomConstructor state = new AtomConstructor("State", scope).initializeFields(); AtomConstructor state = new AtomConstructor("State", scope).initializeFields();
@ -120,8 +123,11 @@ public class Builtins {
scope.registerConstructor(java); scope.registerConstructor(java);
scope.registerConstructor(createPolyglot(language)); scope.registerConstructor(createPolyglot(language));
scope.registerMethod(io, "println", PrintNode.makeFunction(language)); scope.registerMethod(io, "println", PrintlnNode.makeFunction(language));
scope.registerMethod(io, "nano_time", NanoTimeNode.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, "throw", PanicNode.makeFunction(language));
scope.registerMethod(panic, "recover", CatchPanicNode.makeFunction(language)); scope.registerMethod(panic, "recover", CatchPanicNode.makeFunction(language));

View File

@ -3,7 +3,11 @@ package org.enso.interpreter.runtime;
import com.oracle.truffle.api.TruffleFile; import com.oracle.truffle.api.TruffleFile;
import com.oracle.truffle.api.TruffleLanguage; import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleLanguage.Env; import com.oracle.truffle.api.TruffleLanguage.Env;
import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream; import java.io.PrintStream;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -30,6 +34,8 @@ public class Context {
private final Env environment; private final Env environment;
private final Compiler compiler; private final Compiler compiler;
private final PrintStream out; private final PrintStream out;
private final PrintStream err;
private final BufferedReader in;
private final List<Package> packages; private final List<Package> packages;
/** /**
@ -42,6 +48,8 @@ public class Context {
this.language = language; this.language = language;
this.environment = environment; this.environment = environment;
this.out = new PrintStream(environment.out()); this.out = new PrintStream(environment.out());
this.err = new PrintStream(environment.err());
this.in = new BufferedReader(new InputStreamReader(environment.in()));
List<File> packagePaths = OptionsHelper.getPackagesPaths(environment); List<File> packagePaths = OptionsHelper.getPackagesPaths(environment);
@ -125,6 +133,24 @@ public class Context {
return out; 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. * Creates a new module scope that automatically imports all the builtin types and methods.
* *

View File

@ -1,6 +1,11 @@
package org.enso.interpreter.test package org.enso.interpreter.test
import java.io.ByteArrayOutputStream import java.io.{
ByteArrayOutputStream,
PipedInputStream,
PipedOutputStream,
PrintStream
}
import java.util.UUID import java.util.UUID
import com.oracle.truffle.api.instrumentation.EventBinding import com.oracle.truffle.api.instrumentation.EventBinding
@ -72,12 +77,19 @@ trait InterpreterRunner {
value.execute(l.map(_.asInstanceOf[AnyRef]): _*) 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 val ctx = Context
.newBuilder(LanguageInfo.ID) .newBuilder(LanguageInfo.ID)
.allowExperimentalOptions(true) .allowExperimentalOptions(true)
.allowAllAccess(true) .allowAllAccess(true)
.out(output) .out(output)
.err(err)
.in(in)
.build() .build()
lazy val executionContext = new PolyglotContext(ctx) 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] = { def consumeOut: List[String] = {
val result = output.toString val result = output.toString
output.reset() output.reset()
result.linesIterator.toList result.linesIterator.toList
} }
def feedInput(string: String): Unit = {
inOutPrinter.println(string)
}
def getReplInstrument: ReplDebuggerInstrument = { def getReplInstrument: ReplDebuggerInstrument = {
ctx.getEngine.getInstruments ctx.getEngine.getInstruments
.get(ReplDebuggerInstrument.INSTRUMENT_ID) .get(ReplDebuggerInstrument.INSTRUMENT_ID)

View File

@ -64,4 +64,31 @@ class TextTest extends InterpreterTest {
eval(code) eval(code)
consumeOut shouldEqual List("\"Grzegorz Brzeczyszczykiewicz\"") 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!"
}
} }

View File

@ -50,7 +50,6 @@ class ParserTest extends AnyFlatSpec with Matchers {
val module = Parser().run(input) val module = Parser().run(input)
val idmap1 = module.idMap val idmap1 = module.idMap
val idmap2 = Parser().run(new Reader(input), idmap1).idMap val idmap2 = Parser().run(new Reader(input), idmap1).idMap
println(module.zipWithOffset)
assertSpan(input, module) assertSpan(input, module)
assert(module.show() == new Reader(input).toString()) assert(module.show() == new Reader(input).toString())
assert(idmap1 == idmap2) assert(idmap1 == idmap2)