From 0ec41b5bbd057ef3659e89a1ae94f1e9bfc0fe6e Mon Sep 17 00:00:00 2001 From: Marcin Kostrzewa Date: Fri, 15 Nov 2019 15:49:57 +0100 Subject: [PATCH] String literals, caller frame access, eval function (#333) --- .../semantic/RecursionBenchmarks.java | 5 + .../fixtures/semantic/RecursionFixtures.scala | 12 +++ .../java/org/enso/interpreter/Constants.java | 1 + .../java/org/enso/interpreter/Language.java | 6 +- .../interpreter/builder/CallArgFactory.java | 3 +- .../builder/ExpressionFactory.java | 16 +++- .../builder/ModuleScopeExpressionFactory.java | 2 +- .../interpreter/node/ClosureRootNode.java | 17 ++-- .../enso/interpreter/node/EnsoRootNode.java | 41 ++++++-- .../interpreter/node/ProgramRootNode.java | 16 ++-- .../node/callable/ApplicationNode.java | 6 +- .../node/callable/CaptureCallerInfoNode.java | 48 ++++++++++ .../node/callable/ExecuteCallNode.java | 27 ++++-- .../interpreter/node/callable/ForceNode.java | 2 - .../node/callable/InvokeCallableNode.java | 27 ++++-- .../callable/argument/ThunkExecutorNode.java | 25 ++--- .../argument/sorter/ArgumentSorterNode.java | 16 +++- .../sorter/CachedArgumentSorterNode.java | 33 +++++-- .../callable/dispatch/CallOptimiserNode.java | 6 +- .../dispatch/LoopingCallOptimiserNode.java | 31 +++++- .../dispatch/SimpleCallOptimiserNode.java | 10 +- .../callable/function/CreateFunctionNode.java | 2 +- .../node/controlflow/ConstructorCaseNode.java | 11 ++- .../node/controlflow/FallbackNode.java | 4 +- .../builtin/debug/DebugEvalNode.java | 55 +++++++++++ .../builtin/error/CatchErrorNode.java | 5 +- .../builtin/state/RunStateNode.java | 4 +- .../node/expression/debug/EvalNode.java | 95 +++++++++++++++++++ .../literal/IntegerLiteralNode.java | 3 +- .../node/expression/literal/LiteralNode.java | 12 --- .../expression/literal/StringLiteralNode.java | 31 ++++++ .../enso/interpreter/runtime/Builtins.java | 5 + .../runtime/callable/CallerInfo.java | 54 +++++++++++ .../runtime/callable/UnresolvedSymbol.java | 5 + .../callable/argument/CallArgumentInfo.java | 11 ++- .../callable/atom/AtomConstructor.java | 11 ++- .../runtime/callable/function/Function.java | 66 ++++++++++--- .../callable/function/FunctionSchema.java | 58 ++++++++++- .../runtime/control/TailCallException.java | 17 +++- .../interpreter/runtime/scope/LocalScope.java | 17 ++-- .../scala/org/enso/compiler/Compiler.scala | 35 +++++++ .../scala/org/enso/interpreter/Parser.scala | 25 ++++- .../test/InterpreterException.scala | 1 - .../interpreter/test/InterpreterTest.scala | 6 +- .../test/semantic/ErrorsTest.scala | 3 +- .../interpreter/test/semantic/EvalTest.scala | 85 +++++++++++++++++ .../test/semantic/GlobalScopeTest.scala | 3 +- .../test/semantic/NamedArgumentsTest.scala | 1 - .../test/semantic/PackageTest.scala | 10 +- .../test/semantic/StringTest.scala | 15 +++ 50 files changed, 848 insertions(+), 152 deletions(-) create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/callable/CaptureCallerInfoNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/debug/DebugEvalNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/debug/EvalNode.java delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/literal/LiteralNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/literal/StringLiteralNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/CallerInfo.java create mode 100644 engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/EvalTest.scala create mode 100644 engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/StringTest.scala diff --git a/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/RecursionBenchmarks.java b/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/RecursionBenchmarks.java index 2a2b0ed586..e83522722e 100644 --- a/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/RecursionBenchmarks.java +++ b/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/RecursionBenchmarks.java @@ -18,6 +18,11 @@ public class RecursionBenchmarks { recursionFixtures.sumTCO().execute(recursionFixtures.hundredMillion()); } + @Benchmark + public void benchSumTCOWithEval() { + recursionFixtures.sumTCOWithEval().execute(recursionFixtures.hundredMillion()); + } + @Benchmark public void benchSumTCOFoldLike() { recursionFixtures.sumTCOFoldLike().execute(recursionFixtures.hundredMillion()); diff --git a/engine/runtime/src/bench/scala/org/enso/interpreter/bench/fixtures/semantic/RecursionFixtures.scala b/engine/runtime/src/bench/scala/org/enso/interpreter/bench/fixtures/semantic/RecursionFixtures.scala index d96b01b97c..a30c55e8ab 100644 --- a/engine/runtime/src/bench/scala/org/enso/interpreter/bench/fixtures/semantic/RecursionFixtures.scala +++ b/engine/runtime/src/bench/scala/org/enso/interpreter/bench/fixtures/semantic/RecursionFixtures.scala @@ -83,4 +83,16 @@ class RecursionFixtures extends InterpreterRunner { |""".stripMargin val sumStateTCO = eval(sumStateTCOCode) + + val sumTCOWithEvalCode = + """ + |{ |sumTo| + | summator = { |acc, current| + | @ifZero [current, acc, @eval [@Debug, "@summator [acc + current, current - 1]"]] + | }; + | res = @summator [0, sumTo]; + | res + |} + |""".stripMargin + val sumTCOWithEval = eval(sumTCOWithEvalCode) } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/Constants.java b/engine/runtime/src/main/java/org/enso/interpreter/Constants.java index 1320b80538..f68af6356d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/Constants.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/Constants.java @@ -18,5 +18,6 @@ public class Constants { public static final String ARGUMENT_SORTER_NODE = "10"; public static final String FUNCTION_INTEROP_LIBRARY = "10"; public static final String THUNK_EXECUTOR_NODE = "10"; + public static final String EVAL_NODE = "10"; } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/Language.java b/engine/runtime/src/main/java/org/enso/interpreter/Language.java index 72672ee134..1a0db37598 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/Language.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/Language.java @@ -3,7 +3,6 @@ package org.enso.interpreter; import com.oracle.truffle.api.CallTarget; import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.TruffleLanguage; -import com.oracle.truffle.api.frame.FrameDescriptor; import com.oracle.truffle.api.instrumentation.ProvidedTags; import com.oracle.truffle.api.instrumentation.StandardTags; import com.oracle.truffle.api.nodes.RootNode; @@ -11,6 +10,8 @@ import org.enso.interpreter.builder.FileDetector; import org.enso.interpreter.node.ProgramRootNode; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.RuntimeOptions; +import org.enso.interpreter.runtime.scope.LocalScope; +import org.enso.interpreter.runtime.scope.ModuleScope; import org.graalvm.options.OptionDescriptors; /** @@ -82,7 +83,8 @@ public final class Language extends TruffleLanguage { @Override protected CallTarget parse(ParsingRequest request) { RootNode root = - new ProgramRootNode(this, new FrameDescriptor(), "root", null, request.getSource()); + new ProgramRootNode( + this, new LocalScope(), new ModuleScope(), "root", null, request.getSource()); return Truffle.getRuntime().createCallTarget(root); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/builder/CallArgFactory.java b/engine/runtime/src/main/java/org/enso/interpreter/builder/CallArgFactory.java index b0a8d39759..9953e9c793 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/builder/CallArgFactory.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/builder/CallArgFactory.java @@ -61,7 +61,6 @@ public class CallArgFactory implements AstCallArgVisitor { name.orElse(null), Truffle.getRuntime() .createCallTarget( - new ClosureRootNode( - language, childScope.getFrameDescriptor(), expr, null, displayName))); + new ClosureRootNode(language, childScope, moduleScope, expr, null, displayName))); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/builder/ExpressionFactory.java b/engine/runtime/src/main/java/org/enso/interpreter/builder/ExpressionFactory.java index fe48366709..0138c247da 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/builder/ExpressionFactory.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/builder/ExpressionFactory.java @@ -17,6 +17,7 @@ import org.enso.interpreter.node.controlflow.*; import org.enso.interpreter.node.expression.constant.ConstructorNode; import org.enso.interpreter.node.expression.constant.DynamicSymbolNode; import org.enso.interpreter.node.expression.literal.IntegerLiteralNode; +import org.enso.interpreter.node.expression.literal.StringLiteralNode; import org.enso.interpreter.node.expression.operator.*; import org.enso.interpreter.node.scope.AssignmentNode; import org.enso.interpreter.node.scope.AssignmentNodeGen; @@ -113,10 +114,22 @@ public class ExpressionFactory implements AstExpressionVisitor { * @param l the value to represent * @return a runtime node representing that value */ + @Override public ExpressionNode visitLong(long l) { return new IntegerLiteralNode(l); } + /** + * Creates a runtime String literal value from an AST node. + * + * @param string the string value of this literal + * @return a runtime node representing this literal + */ + @Override + public ExpressionNode visitStringLiteral(String string) { + return new StringLiteralNode(string); + } + /** * Creates runtime nodes representing arithmetic expressions. * @@ -234,8 +247,7 @@ public class ExpressionFactory implements AstExpressionVisitor { FunctionBodyNode fnBodyNode = new FunctionBodyNode(allFnExpressions.toArray(new ExpressionNode[0]), returnExpr); RootNode fnRootNode = - new ClosureRootNode( - language, scope.getFrameDescriptor(), fnBodyNode, null, "lambda::" + scopeName); + new ClosureRootNode(language, scope, moduleScope, fnBodyNode, null, "lambda::" + scopeName); RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(fnRootNode); return new CreateFunctionNode(callTarget, argDefinitions); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/builder/ModuleScopeExpressionFactory.java b/engine/runtime/src/main/java/org/enso/interpreter/builder/ModuleScopeExpressionFactory.java index bdae264a96..a1c34d32ef 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/builder/ModuleScopeExpressionFactory.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/builder/ModuleScopeExpressionFactory.java @@ -6,8 +6,8 @@ import org.enso.interpreter.node.callable.function.CreateFunctionNode; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; import org.enso.interpreter.runtime.callable.atom.AtomConstructor; -import org.enso.interpreter.runtime.callable.function.FunctionSchema; import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.callable.function.FunctionSchema; import org.enso.interpreter.runtime.error.VariableDoesNotExistException; import org.enso.interpreter.runtime.scope.ModuleScope; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/ClosureRootNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/ClosureRootNode.java index 969c2a645f..e8eab153da 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/ClosureRootNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/ClosureRootNode.java @@ -2,13 +2,13 @@ package org.enso.interpreter.node; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.dsl.ReportPolymorphism; -import com.oracle.truffle.api.frame.FrameDescriptor; -import com.oracle.truffle.api.frame.FrameSlotKind; import com.oracle.truffle.api.frame.FrameUtil; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.source.SourceSection; import org.enso.interpreter.Language; import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.scope.LocalScope; +import org.enso.interpreter.runtime.scope.ModuleScope; import org.enso.interpreter.runtime.state.Stateful; /** @@ -26,23 +26,20 @@ public class ClosureRootNode extends EnsoRootNode { * Creates a new root node. * * @param language the language identifier - * @param frameDescriptor a description of the stack frame + * @param localScope a description of the local scope + * @param moduleScope a description of the module scope * @param body the program body to be executed * @param section a mapping from {@code body} to the program source * @param name a name for the node */ public ClosureRootNode( Language language, - FrameDescriptor frameDescriptor, + LocalScope localScope, + ModuleScope moduleScope, ExpressionNode body, SourceSection section, String name) { - super( - language, - frameDescriptor, - name, - section, - frameDescriptor.findOrAddFrameSlot("<>", FrameSlotKind.Object)); + super(language, localScope, moduleScope, name, section); this.body = body; } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/EnsoRootNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/EnsoRootNode.java index 31dc5d96c2..4a2ea7e544 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/EnsoRootNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/EnsoRootNode.java @@ -2,18 +2,22 @@ package org.enso.interpreter.node; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.TruffleLanguage; -import com.oracle.truffle.api.frame.FrameDescriptor; import com.oracle.truffle.api.frame.FrameSlot; +import com.oracle.truffle.api.frame.FrameSlotKind; import com.oracle.truffle.api.nodes.RootNode; import com.oracle.truffle.api.source.SourceSection; import org.enso.interpreter.Language; import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.scope.LocalScope; +import org.enso.interpreter.runtime.scope.ModuleScope; /** A common base class for all kinds of root node in Enso. */ public abstract class EnsoRootNode extends RootNode { private final String name; private final SourceSection sourceSection; private final FrameSlot stateFrameSlot; + private final LocalScope localScope; + private final ModuleScope moduleScope; private @CompilerDirectives.CompilationFinal TruffleLanguage.ContextReference contextReference; private @CompilerDirectives.CompilationFinal TruffleLanguage.LanguageReference @@ -23,21 +27,24 @@ public abstract class EnsoRootNode extends RootNode { * Constructs the root node. * * @param language the language instance in which this will execute - * @param frameDescriptor a reference to the construct root frame + * @param localScope a reference to the construct local scope + * @param moduleScope a reference to the construct module scope * @param name the name of the construct * @param sourceSection a reference to the source code being executed - * @param stateFrameSlot the code to compile and execute */ public EnsoRootNode( Language language, - FrameDescriptor frameDescriptor, + LocalScope localScope, + ModuleScope moduleScope, String name, - SourceSection sourceSection, - FrameSlot stateFrameSlot) { - super(language, frameDescriptor); + SourceSection sourceSection) { + super(language, localScope.getFrameDescriptor()); this.name = name; + this.localScope = localScope; + this.moduleScope = moduleScope; this.sourceSection = sourceSection; - this.stateFrameSlot = stateFrameSlot; + this.stateFrameSlot = + localScope.getFrameDescriptor().findOrAddFrameSlot("<>", FrameSlotKind.Object); } /** @@ -89,4 +96,22 @@ public abstract class EnsoRootNode extends RootNode { public SourceSection getSourceSection() { return sourceSection; } + + /** + * Gets the local scope this node expects to work with + * + * @return the local scope for this node + */ + public LocalScope getLocalScope() { + return localScope; + } + + /** + * Gets the module scope this node was defined with + * + * @return the module scope for this node + */ + public ModuleScope getModuleScope() { + return moduleScope; + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/ProgramRootNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/ProgramRootNode.java index 38c375a9ec..ba1cb92897 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/ProgramRootNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/ProgramRootNode.java @@ -1,13 +1,13 @@ package org.enso.interpreter.node; import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.api.frame.FrameDescriptor; -import com.oracle.truffle.api.frame.FrameSlotKind; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.api.source.SourceSection; import org.enso.interpreter.Language; import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.scope.LocalScope; +import org.enso.interpreter.runtime.scope.ModuleScope; /** * This node handles static transformation of the input AST before execution and represents the root @@ -27,23 +27,25 @@ public class ProgramRootNode extends EnsoRootNode { * Constructs the root node. * * @param language the language instance in which this will execute - * @param frameDescriptor a reference to the program root frame + * @param localScope a reference to the program local scope + * @param moduleScope a reference to the program module scope * @param name the name of the program * @param sourceSection a reference to the source code being executed * @param sourceCode the code to compile and execute */ public ProgramRootNode( Language language, - FrameDescriptor frameDescriptor, + LocalScope localScope, + ModuleScope moduleScope, String name, SourceSection sourceSection, Source sourceCode) { super( language, - frameDescriptor, + localScope, + moduleScope, name, - sourceSection, - frameDescriptor.findOrAddFrameSlot("<>", FrameSlotKind.Object)); + sourceSection); this.sourceCode = sourceCode; } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/ApplicationNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/ApplicationNode.java index c1e7934a80..f368eafc91 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/ApplicationNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/ApplicationNode.java @@ -90,8 +90,10 @@ public class ApplicationNode extends ExpressionNode { @Override public Object executeGeneric(VirtualFrame frame) { Object state = FrameUtil.getObjectSafe(frame, getStateFrameSlot()); - Stateful result = this.invokeCallableNode.execute( - this.callable.executeGeneric(frame), state, evaluateArguments(frame)); + + Stateful result = + this.invokeCallableNode.execute( + this.callable.executeGeneric(frame), frame, state, evaluateArguments(frame)); frame.setObject(getStateFrameSlot(), result.getState()); return result.getValue(); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/CaptureCallerInfoNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/CaptureCallerInfoNode.java new file mode 100644 index 0000000000..6338fe8fc0 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/CaptureCallerInfoNode.java @@ -0,0 +1,48 @@ +package org.enso.interpreter.node.callable; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.nodes.NodeInfo; +import org.enso.interpreter.node.EnsoRootNode; +import org.enso.interpreter.runtime.callable.CallerInfo; +import org.enso.interpreter.runtime.scope.LocalScope; +import org.enso.interpreter.runtime.scope.ModuleScope; + +/** + * Captures the current caller info to pass to functions requiring it. + * + *

The information captured includes current execution frame, as well as static context, + * including the local and module scope metadata. + */ +@NodeInfo( + description = "Captures the caller info for use in functions called from this node's scope") +public class CaptureCallerInfoNode extends Node { + private @CompilerDirectives.CompilationFinal LocalScope localScope; + private @CompilerDirectives.CompilationFinal ModuleScope moduleScope; + + /** + * Captures the caller info for use in functions called from the current scope. + * + * @param frame current execution frame + * @return caller information for the current scope + */ + public CallerInfo execute(VirtualFrame frame) { + if (localScope == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + EnsoRootNode rootNode = (EnsoRootNode) getRootNode(); + localScope = rootNode.getLocalScope(); + moduleScope = rootNode.getModuleScope(); + } + return new CallerInfo(frame.materialize(), localScope, moduleScope); + } + + /** + * Creates an instance of this node. + * + * @return an instance of this node + */ + public static CaptureCallerInfoNode build() { + return new CaptureCallerInfoNode(); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/ExecuteCallNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/ExecuteCallNode.java index 0a3169046f..f7fa2a37fc 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/ExecuteCallNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/ExecuteCallNode.java @@ -6,14 +6,14 @@ import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.nodes.DirectCallNode; import com.oracle.truffle.api.nodes.IndirectCallNode; import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.runtime.callable.CallerInfo; import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.state.Stateful; /** * This node is responsible for optimising function calls. * - *

Where possible, it will make the call as a 'direct' call, one with no lookup needed, but will - * fall back to performing a lookup if necessary. + *

Where possible, it will make the call as a direct call, with potential for inlining. */ public abstract class ExecuteCallNode extends Node { @@ -24,6 +24,7 @@ public abstract class ExecuteCallNode extends Node { * already cached. THis means that the call can be made quickly. * * @param function the function to execute + * @param callerInfo the caller info to pass to the function * @param state the current state value * @param arguments the arguments passed to {@code function} in the expected positional order * @param cachedTarget the cached call target for {@code function} @@ -33,12 +34,14 @@ public abstract class ExecuteCallNode extends Node { @Specialization(guards = "function.getCallTarget() == cachedTarget") protected Stateful callDirect( Function function, + CallerInfo callerInfo, Object state, Object[] arguments, @Cached("function.getCallTarget()") RootCallTarget cachedTarget, @Cached("create(cachedTarget)") DirectCallNode callNode) { return (Stateful) - callNode.call(Function.ArgumentsHelper.buildArguments(function, state, arguments)); + callNode.call( + Function.ArgumentsHelper.buildArguments(function, callerInfo, state, arguments)); } /** @@ -48,6 +51,7 @@ public abstract class ExecuteCallNode extends Node { * provided function. This is much slower and should, in general, be avoided. * * @param function the function to execute + * @param callerInfo the caller info to pass to the function * @param state the current state value * @param arguments the arguments passed to {@code function} in the expected positional order * @param callNode the cached call node for making indirect calls @@ -55,23 +59,34 @@ public abstract class ExecuteCallNode extends Node { */ @Specialization(replaces = "callDirect") protected Stateful callIndirect( - Function function, Object state, Object[] arguments, @Cached IndirectCallNode callNode) { + Function function, + CallerInfo callerInfo, + Object state, + Object[] arguments, + @Cached IndirectCallNode callNode) { return (Stateful) callNode.call( function.getCallTarget(), - Function.ArgumentsHelper.buildArguments(function, state, arguments)); + Function.ArgumentsHelper.buildArguments(function, callerInfo, state, arguments)); } /** * Executes the function call. * * @param function the function to execute + * @param callerInfo the caller info to pass to the function * @param state the state value to pass to the function * @param arguments the arguments to be passed to {@code function} * @return the result of executing {@code function} on {@code arguments} */ - public abstract Stateful executeCall(Object function, Object state, Object[] arguments); + public abstract Stateful executeCall( + Object function, CallerInfo callerInfo, Object state, Object[] arguments); + /** + * Creates an instance of this node. + * + * @return an instance of this node + */ public static ExecuteCallNode build() { return ExecuteCallNodeGen.create(); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/ForceNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/ForceNode.java index c16a115346..827d461887 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/ForceNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/ForceNode.java @@ -1,10 +1,8 @@ package org.enso.interpreter.node.callable; -import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.NodeChild; import com.oracle.truffle.api.dsl.Specialization; -import com.oracle.truffle.api.frame.FrameSlot; import com.oracle.truffle.api.frame.FrameUtil; import com.oracle.truffle.api.frame.VirtualFrame; import org.enso.interpreter.node.ExpressionNode; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java index c390768f25..1ea9a98d93 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java @@ -3,6 +3,7 @@ package org.enso.interpreter.node.callable; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; import org.enso.interpreter.Constants; import org.enso.interpreter.node.BaseNode; import org.enso.interpreter.node.callable.argument.ThunkExecutorNode; @@ -114,26 +115,30 @@ public abstract class InvokeCallableNode extends BaseNode { * Invokes a function directly on the arguments contained in this node. * * @param function the function to be executed + * @param callerFrame the caller frame to pass to the function * @param state the state to pass to the function * @param arguments the arguments to the function * @return the result of executing {@code callable} on the known arguments */ @Specialization - public Stateful invokeFunction(Function function, Object state, Object[] arguments) { - return this.argumentSorter.execute(function, state, arguments); + Stateful invokeFunction( + Function function, VirtualFrame callerFrame, Object state, Object[] arguments) { + return this.argumentSorter.execute(function, callerFrame, state, arguments); } /** * Invokes a constructor directly on the arguments contained in this node. * * @param constructor the constructor to be executed + * @param callerFrame the caller frame to pass to the function * @param state the state to pass to the function * @param arguments the arguments to the constructor * @return the result of executing {@code constructor} on the known arguments */ @Specialization - public Stateful invokeConstructor(AtomConstructor constructor, Object state, Object[] arguments) { - return invokeFunction(constructor.getConstructorFunction(), state, arguments); + Stateful invokeConstructor( + AtomConstructor constructor, VirtualFrame callerFrame, Object state, Object[] arguments) { + return invokeFunction(constructor.getConstructorFunction(), callerFrame, state, arguments); } /** @@ -141,12 +146,14 @@ public abstract class InvokeCallableNode extends BaseNode { * argument. * * @param symbol the name of the requested symbol + * @param callerFrame the caller frame to pass to the function * @param state the state to pass to the function * @param arguments the arguments to the dynamic symbol * @return the result of resolving and executing the symbol for the {@code this} argument */ @Specialization - public Stateful invokeDynamicSymbol(UnresolvedSymbol symbol, Object state, Object[] arguments) { + public Stateful invokeDynamicSymbol( + UnresolvedSymbol symbol, VirtualFrame callerFrame, Object state, Object[] arguments) { if (canApplyThis) { Object selfArgument = arguments[thisArgumentPosition]; if (argumentsExecutionMode.shouldExecute()) { @@ -159,7 +166,7 @@ public abstract class InvokeCallableNode extends BaseNode { state = selfResult.getState(); } Function function = methodResolverNode.execute(symbol, selfArgument); - return this.argumentSorter.execute(function, state, arguments); + return this.argumentSorter.execute(function, callerFrame, state, arguments); } else { throw new RuntimeException("Currying without `this` argument is not yet supported."); } @@ -172,12 +179,14 @@ public abstract class InvokeCallableNode extends BaseNode { * NotInvokableException} to signal this. * * @param callable the callable to be executed + * @param callerFrame the caller frame to pass to the function * @param state the state to pass to the function * @param arguments the arguments to the callable * @return error */ @Fallback - public Stateful invokeGeneric(Object callable, Object state, Object[] arguments) { + public Stateful invokeGeneric( + Object callable, VirtualFrame callerFrame, Object state, Object[] arguments) { throw new NotInvokableException(callable, this); } @@ -185,11 +194,13 @@ public abstract class InvokeCallableNode extends BaseNode { * Executes the provided {@code callable} on the supplied {@code arguments}. * * @param callable the callable to evaluate + * @param callerFrame the caller frame to pass to the function * @param state the state to pass to the function * @param arguments the arguments to evaluate {@code callable} on * @return the result of executing {@code callable} on the supplied {@code arguments} */ - public abstract Stateful execute(Object callable, Object state, Object[] arguments); + public abstract Stateful execute( + Object callable, VirtualFrame callerFrame, Object state, Object[] arguments); /** * Sets whether or not the current node is tail-recursive. diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ThunkExecutorNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ThunkExecutorNode.java index 30b09aea31..8f1165f37f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ThunkExecutorNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ThunkExecutorNode.java @@ -38,15 +38,13 @@ public abstract class ThunkExecutorNode extends Node { @Cached("createLoopingOptimizerIfNeeded()") LoopingCallOptimiserNode loopingCallOptimiserNode) { if (getIsTail()) { - return (Stateful) - callNode.call(Function.ArgumentsHelper.buildArguments(thunk, state)); + return (Stateful) callNode.call(Function.ArgumentsHelper.buildArguments(thunk, state)); } else { try { - return (Stateful) - callNode.call(Function.ArgumentsHelper.buildArguments(thunk, state)); + return (Stateful) callNode.call(Function.ArgumentsHelper.buildArguments(thunk, state)); } catch (TailCallException e) { return loopingCallOptimiserNode.executeDispatch( - e.getFunction(), e.getState(), e.getArguments()); + e.getFunction(), e.getCallerInfo(), e.getState(), e.getArguments()); } } } @@ -58,14 +56,19 @@ public abstract class ThunkExecutorNode extends Node { @Cached IndirectCallNode callNode, @Cached("createLoopingOptimizerIfNeeded()") LoopingCallOptimiserNode loopingCallOptimiserNode) { - try { + if (getIsTail()) { return (Stateful) callNode.call( - thunk.getCallTarget(), - Function.ArgumentsHelper.buildArguments(thunk, state)); - } catch (TailCallException e) { - return loopingCallOptimiserNode.executeDispatch( - e.getFunction(), e.getState(), e.getArguments()); + thunk.getCallTarget(), Function.ArgumentsHelper.buildArguments(thunk, state)); + } else { + try { + return (Stateful) + callNode.call( + thunk.getCallTarget(), Function.ArgumentsHelper.buildArguments(thunk, state)); + } catch (TailCallException e) { + return loopingCallOptimiserNode.executeDispatch( + e.getFunction(), e.getCallerInfo(), e.getState(), e.getArguments()); + } } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/sorter/ArgumentSorterNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/sorter/ArgumentSorterNode.java index 16689a4f65..08731ce6b8 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/sorter/ArgumentSorterNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/sorter/ArgumentSorterNode.java @@ -3,11 +3,11 @@ package org.enso.interpreter.node.callable.argument.sorter; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.NodeInfo; import org.enso.interpreter.Constants; import org.enso.interpreter.node.BaseNode; import org.enso.interpreter.node.callable.InvokeCallableNode; -import org.enso.interpreter.node.callable.dispatch.CallOptimiserNode; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.state.Stateful; @@ -50,11 +50,11 @@ public abstract class ArgumentSorterNode extends BaseNode { * matches with the one stored in the cached argument sorter object. * * @param function the function to sort arguments for + * @param callerFrame the caller frame to pass to the function * @param state the state to pass to the function * @param arguments the arguments being passed to {@code callable} * @param mappingNode a cached node that tracks information about the mapping to enable a fast * path - * @param optimiser a cached call optimizer node, capable of performing the actual function call * @return the result of applying the function with remapped arguments */ @Specialization( @@ -62,12 +62,13 @@ public abstract class ArgumentSorterNode extends BaseNode { limit = Constants.CacheSizes.ARGUMENT_SORTER_NODE) public Stateful invokeCached( Function function, + VirtualFrame callerFrame, Object state, Object[] arguments, @Cached( "build(function, getSchema(), getDefaultsExecutionMode(), getArgumentsExecutionMode(), isTail())") CachedArgumentSorterNode mappingNode) { - return mappingNode.execute(function, state, arguments); + return mappingNode.execute(function, callerFrame, state, arguments); } /** @@ -75,14 +76,17 @@ public abstract class ArgumentSorterNode extends BaseNode { * perform any caching and is thus a slow-path operation. * * @param function the function to execute. + * @param callerFrame the caller frame to pass to the function * @param state the state to pass to the function * @param arguments the arguments to reorder and supply to the {@code function}. * @return the result of calling {@code function} with the supplied {@code arguments}. */ @Specialization(replaces = "invokeCached") - public Stateful invokeUncached(Function function, Object state, Object[] arguments) { + public Stateful invokeUncached( + Function function, VirtualFrame callerFrame, Object state, Object[] arguments) { return invokeCached( function, + callerFrame, state, arguments, CachedArgumentSorterNode.build( @@ -97,11 +101,13 @@ public abstract class ArgumentSorterNode extends BaseNode { * Executes the {@link ArgumentSorterNode} to reorder the arguments. * * @param callable the function to sort arguments for + * @param callerFrame the caller frame to pass to the function * @param state the state to pass to the function * @param arguments the arguments being passed to {@code function} * @return the result of executing the {@code function} with reordered {@code arguments} */ - public abstract Stateful execute(Function callable, Object state, Object[] arguments); + public abstract Stateful execute( + Function callable, VirtualFrame callerFrame, Object state, Object[] arguments); CallArgumentInfo[] getSchema() { return schema; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/sorter/CachedArgumentSorterNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/sorter/CachedArgumentSorterNode.java index f2198386a5..023faf1d42 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/sorter/CachedArgumentSorterNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/sorter/CachedArgumentSorterNode.java @@ -1,20 +1,23 @@ package org.enso.interpreter.node.callable.argument.sorter; import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.nodes.NodeInfo; import org.enso.interpreter.node.BaseNode; +import org.enso.interpreter.node.callable.CaptureCallerInfoNode; import org.enso.interpreter.node.callable.ExecuteCallNode; import org.enso.interpreter.node.callable.InvokeCallableNode; import org.enso.interpreter.node.callable.InvokeCallableNodeGen; import org.enso.interpreter.node.callable.argument.ThunkExecutorNode; import org.enso.interpreter.node.callable.dispatch.CallOptimiserNode; +import org.enso.interpreter.runtime.callable.CallerInfo; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo.ArgumentMapping; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo.ArgumentMappingBuilder; import org.enso.interpreter.runtime.callable.argument.Thunk; -import org.enso.interpreter.runtime.callable.function.FunctionSchema; import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.callable.function.FunctionSchema; import org.enso.interpreter.runtime.control.TailCallException; import org.enso.interpreter.runtime.state.Stateful; @@ -35,6 +38,7 @@ public class CachedArgumentSorterNode extends BaseNode { private final InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode; private @Child ExecuteCallNode directCall; private @Child CallOptimiserNode loopingCall; + private @Child CaptureCallerInfoNode captureCallerInfoNode; /** * Creates a node that generates and then caches the argument mapping. @@ -64,8 +68,11 @@ public class CachedArgumentSorterNode extends BaseNode { initializeOversaturatedCallNode(defaultsExecutionMode, argumentsExecutionMode); argumentShouldExecute = this.mapping.getArgumentShouldExecute(); - initializeCallNodes(); + + if (originalFunction.getSchema().getCallerFrameAccess().shouldFrameBePassed()) { + this.captureCallerInfoNode = CaptureCallerInfoNode.build(); + } } private void initializeCallNodes() { @@ -156,11 +163,17 @@ public class CachedArgumentSorterNode extends BaseNode { * Reorders the provided arguments into the necessary order for the cached callable. * * @param function the function this node is reordering arguments for + * @param callerFrame the caller frame to pass to the function * @param state the state to pass to the function * @param arguments the arguments to reorder * @return the provided {@code arguments} in the order expected by the cached {@link Function} */ - public Stateful execute(Function function, Object state, Object[] arguments) { + public Stateful execute( + Function function, VirtualFrame callerFrame, Object state, Object[] arguments) { + CallerInfo callerInfo = null; + if (captureCallerInfoNode != null) { + callerInfo = captureCallerInfoNode.execute(callerFrame); + } if (argumentsExecutionMode.shouldExecute()) { state = executeArguments(arguments, state); } @@ -169,13 +182,14 @@ public class CachedArgumentSorterNode extends BaseNode { if (this.appliesFully()) { if (!postApplicationSchema.hasOversaturatedArgs()) { - return doCall(function, state, mappedAppliedArguments); + return doCall(function, callerInfo, state, mappedAppliedArguments); } else { Stateful evaluatedVal = - loopingCall.executeDispatch(function, state, mappedAppliedArguments); + loopingCall.executeDispatch(function, callerInfo, state, mappedAppliedArguments); return this.oversaturatedCallableNode.execute( evaluatedVal.getValue(), + callerFrame, evaluatedVal.getState(), generateOversaturatedArguments(function, arguments)); } @@ -232,13 +246,14 @@ public class CachedArgumentSorterNode extends BaseNode { return oversaturatedArguments; } - private Stateful doCall(Function function, Object state, Object[] arguments) { + private Stateful doCall( + Function function, CallerInfo callerInfo, Object state, Object[] arguments) { if (getOriginalFunction().getCallStrategy().shouldCallDirect(isTail())) { - return directCall.executeCall(function, state, arguments); + return directCall.executeCall(function, callerInfo, state, arguments); } else if (isTail()) { - throw new TailCallException(function, state, arguments); + throw new TailCallException(function, callerInfo, state, arguments); } else { - return loopingCall.executeDispatch(function, state, arguments); + return loopingCall.executeDispatch(function, callerInfo, state, arguments); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/CallOptimiserNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/CallOptimiserNode.java index adc1d9950f..3e3fbcf380 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/CallOptimiserNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/CallOptimiserNode.java @@ -1,6 +1,7 @@ package org.enso.interpreter.node.callable.dispatch; import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.runtime.callable.CallerInfo; import org.enso.interpreter.runtime.state.Stateful; /** @@ -13,10 +14,13 @@ public abstract class CallOptimiserNode extends Node { * Calls the provided {@code callable} using the provided {@code arguments}. * * @param callable the callable to execute + * @param callerInfo the caller info to pass to the function + * @param state the state to pass to the function * @param arguments the arguments to {@code callable} * @return the result of executing {@code callable} using {@code arguments} */ - public abstract Stateful executeDispatch(Object callable, Object state, Object[] arguments); + public abstract Stateful executeDispatch( + Object callable, CallerInfo callerInfo, Object state, Object[] arguments); /** * Creates an instance of default implementation of {@link CallOptimiserNode}. diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/LoopingCallOptimiserNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/LoopingCallOptimiserNode.java index 9f13aefa4c..00f91697cc 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/LoopingCallOptimiserNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/LoopingCallOptimiserNode.java @@ -7,6 +7,7 @@ import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.RepeatingNode; import org.enso.interpreter.node.callable.ExecuteCallNode; import org.enso.interpreter.node.callable.ExecuteCallNodeGen; +import org.enso.interpreter.runtime.callable.CallerInfo; import org.enso.interpreter.runtime.control.TailCallException; import org.enso.interpreter.runtime.state.Stateful; @@ -33,14 +34,17 @@ public class LoopingCallOptimiserNode extends CallOptimiserNode { * Calls the provided {@code function} using the provided {@code arguments}. * * @param function the function to execute + * @param callerInfo the caller info to pass to the function * @param state the state to pass to the function * @param arguments the arguments to {@code function} * @return the result of executing {@code function} using {@code arguments} */ @Override - public Stateful executeDispatch(Object function, Object state, Object[] arguments) { + public Stateful executeDispatch( + Object function, CallerInfo callerInfo, Object state, Object[] arguments) { VirtualFrame frame = Truffle.getRuntime().createVirtualFrame(null, loopFrameDescriptor); - ((RepeatedCallNode) loopNode.getRepeatingNode()).setNextCall(frame, function, state, arguments); + ((RepeatedCallNode) loopNode.getRepeatingNode()) + .setNextCall(frame, function, callerInfo, state, arguments); loopNode.executeLoop(frame); return ((RepeatedCallNode) loopNode.getRepeatingNode()).getResult(frame); @@ -56,6 +60,7 @@ public class LoopingCallOptimiserNode extends CallOptimiserNode { private final FrameSlot functionSlot; private final FrameSlot argsSlot; private final FrameSlot stateSlot; + private final FrameSlot callerInfoSlot; @Child private ExecuteCallNode dispatchNode; /** @@ -68,6 +73,7 @@ public class LoopingCallOptimiserNode extends CallOptimiserNode { resultSlot = descriptor.findOrAddFrameSlot("", FrameSlotKind.Object); argsSlot = descriptor.findOrAddFrameSlot("", FrameSlotKind.Object); stateSlot = descriptor.findOrAddFrameSlot("", FrameSlotKind.Object); + callerInfoSlot = descriptor.findOrAddFrameSlot("", FrameSlotKind.Object); dispatchNode = ExecuteCallNodeGen.create(); } @@ -76,11 +82,18 @@ public class LoopingCallOptimiserNode extends CallOptimiserNode { * * @param frame the stack frame for execution * @param function the function to execute in {@code frame} + * @param callerInfo the caller info to pass to the function * @param state the state to pass to the function * @param arguments the arguments to execute {@code function} with */ - public void setNextCall(VirtualFrame frame, Object function, Object state, Object[] arguments) { + public void setNextCall( + VirtualFrame frame, + Object function, + CallerInfo callerInfo, + Object state, + Object[] arguments) { frame.setObject(functionSlot, function); + frame.setObject(callerInfoSlot, callerInfo); frame.setObject(stateSlot, state); frame.setObject(argsSlot, arguments); } @@ -95,6 +108,12 @@ public class LoopingCallOptimiserNode extends CallOptimiserNode { return (Stateful) FrameUtil.getObjectSafe(frame, resultSlot); } + private CallerInfo getCallerInfo(VirtualFrame frame) { + CallerInfo result = (CallerInfo) FrameUtil.getObjectSafe(frame, callerInfoSlot); + frame.setObject(callerInfoSlot, null); + return result; + } + /** * Generates the next call to be made during looping execution. * @@ -143,10 +162,12 @@ public class LoopingCallOptimiserNode extends CallOptimiserNode { Object function = getNextFunction(frame); Object state = getNextState(frame); Object[] arguments = getNextArgs(frame); - frame.setObject(resultSlot, dispatchNode.executeCall(function, state, arguments)); + CallerInfo callerInfo = getCallerInfo(frame); + frame.setObject( + resultSlot, dispatchNode.executeCall(function, callerInfo, state, arguments)); return false; } catch (TailCallException e) { - setNextCall(frame, e.getFunction(), e.getState(), e.getArguments()); + setNextCall(frame, e.getFunction(), e.getCallerInfo(), e.getState(), e.getArguments()); return true; } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/SimpleCallOptimiserNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/SimpleCallOptimiserNode.java index 8f338b0525..09988c9d80 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/SimpleCallOptimiserNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/SimpleCallOptimiserNode.java @@ -3,6 +3,7 @@ package org.enso.interpreter.node.callable.dispatch; import com.oracle.truffle.api.CompilerDirectives; import org.enso.interpreter.node.callable.ExecuteCallNode; import org.enso.interpreter.node.callable.ExecuteCallNodeGen; +import org.enso.interpreter.runtime.callable.CallerInfo; import org.enso.interpreter.runtime.control.TailCallException; import org.enso.interpreter.runtime.state.Stateful; @@ -19,19 +20,22 @@ public class SimpleCallOptimiserNode extends CallOptimiserNode { * Calls the provided {@code function} using the provided {@code arguments}. * * @param function the function to execute + * @param callerInfo the caller info to pass to the function * @param state the state to pass to the function * @param arguments the arguments to {@code function} * @return the result of executing {@code function} using {@code arguments} */ @Override - public Stateful executeDispatch(Object function, Object state, Object[] arguments) { + public Stateful executeDispatch( + Object function, CallerInfo callerInfo, Object state, Object[] arguments) { try { - return executeCallNode.executeCall(function, state, arguments); + return executeCallNode.executeCall(function, callerInfo, state, arguments); } catch (TailCallException e) { CompilerDirectives.transferToInterpreterAndInvalidate(); CallOptimiserNode replacement = new LoopingCallOptimiserNode(); this.replace(replacement); - return replacement.executeDispatch(e.getFunction(), e.getState(), e.getArguments()); + return replacement.executeDispatch( + e.getFunction(), e.getCallerInfo(), e.getState(), e.getArguments()); } } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/function/CreateFunctionNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/function/CreateFunctionNode.java index c6e2808857..14c1187282 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/function/CreateFunctionNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/function/CreateFunctionNode.java @@ -6,8 +6,8 @@ import com.oracle.truffle.api.frame.VirtualFrame; import org.enso.interpreter.node.ClosureRootNode; import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; -import org.enso.interpreter.runtime.callable.function.FunctionSchema; import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.callable.function.FunctionSchema; /** * This node is responsible for representing the definition of a function. It contains information diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/ConstructorCaseNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/ConstructorCaseNode.java index 357f46a90d..e6afbaec17 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/ConstructorCaseNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/ConstructorCaseNode.java @@ -56,10 +56,19 @@ public class ConstructorCaseNode extends CaseNode { if (profile.profile(matcherVal == target.getConstructor())) { Function function = branch.executeFunction(frame); throw new BranchSelectedException( - executeCallNode.executeCall(function, state, target.getFields())); + executeCallNode.executeCall( + function, null, state, target.getFields())); // Note [Caller Info For Case Branches] } } + /* Note [Caller Info For Case Branches] + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * It is assumed that functions serving as pattern match logic branches are always function + * literals, not references, curried functions etc. Therefore, as function literals, they + * have no way of accessing the caller frame and can safely be passed null. + */ + /** * Handles the function scrutinee case, by not matching it at all. * diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/FallbackNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/FallbackNode.java index 9ce70149f1..26a249842d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/FallbackNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/FallbackNode.java @@ -29,7 +29,9 @@ public class FallbackNode extends CaseNode { private void execute(VirtualFrame frame, Object target) throws UnexpectedResultException { Function function = functionNode.executeFunction(frame); Object state = FrameUtil.getObjectSafe(frame, getStateFrameSlot()); - throw new BranchSelectedException(executeCallNode.executeCall(function, state, new Object[0])); + throw new BranchSelectedException( + executeCallNode.executeCall( + function, null, state, new Object[0])); // Note [Caller Info For Case Branches] } /** diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/debug/DebugEvalNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/debug/DebugEvalNode.java new file mode 100644 index 0000000000..4540cbe697 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/debug/DebugEvalNode.java @@ -0,0 +1,55 @@ +package org.enso.interpreter.node.expression.builtin.debug; + +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.node.expression.debug.EvalNode; +import org.enso.interpreter.runtime.callable.CallerInfo; +import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; +import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.callable.function.FunctionSchema; +import org.enso.interpreter.runtime.state.Stateful; + +/** Root node for the builtin Debug.eval function. */ +@NodeInfo(shortName = "Debug.eval", description = "Root node for the builtin Debug.eval function") +public class DebugEvalNode extends BuiltinRootNode { + + private @Child EvalNode evalNode = EvalNode.build(); + + private DebugEvalNode(Language language) { + super(language); + evalNode.markTail(); + } + + /** + * Executes the function in the given execution frame. + * + *

Requires a non-null {@link CallerInfo} passed to it. The string argument containing code to + * evaluate is this function's second (non-this) argument. + * + * @param frame current execution frame + * @return + */ + @Override + public Stateful execute(VirtualFrame frame) { + CallerInfo callerInfo = Function.ArgumentsHelper.getCallerInfo(frame.getArguments()); + Object state = Function.ArgumentsHelper.getState(frame.getArguments()); + String code = (String) Function.ArgumentsHelper.getPositionalArguments(frame.getArguments())[1]; + return evalNode.execute(callerInfo, state, code); + } + + /** + * Returns a two argument function wrapping this node. + * + * @param language current language instance + * @return a function wrapping this node's logic + */ + public static Function makeFunction(Language language) { + return Function.fromBuiltinRootNodeWithCallerFrameAccess( + new DebugEvalNode(language), + FunctionSchema.CallStrategy.DIRECT_WHEN_TAIL, + new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE), + new ArgumentDefinition(1, "expression", ArgumentDefinition.ExecutionMode.EXECUTE)); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchErrorNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchErrorNode.java index a5a4c1e1cd..f6468e364f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchErrorNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchErrorNode.java @@ -6,10 +6,13 @@ import com.oracle.truffle.api.profiles.ConditionProfile; import org.enso.interpreter.Language; import org.enso.interpreter.node.callable.InvokeCallableNode; import org.enso.interpreter.node.expression.builtin.BuiltinRootNode; +import org.enso.interpreter.runtime.callable.CallerInfo; import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.callable.function.FunctionSchema; +import org.enso.interpreter.runtime.scope.LocalScope; +import org.enso.interpreter.runtime.scope.ModuleScope; import org.enso.interpreter.runtime.state.Stateful; import org.enso.interpreter.runtime.type.TypesGen; @@ -45,7 +48,7 @@ public class CatchErrorNode extends BuiltinRootNode { Object handler = arguments[1]; if (executionProfile.profile(TypesGen.isRuntimeError(scrutinee))) { return invokeCallableNode.execute( - handler, state, new Object[] {TypesGen.asRuntimeError(scrutinee).getPayload()}); + handler, frame, state, new Object[] {TypesGen.asRuntimeError(scrutinee).getPayload()}); } else { return new Stateful(state, scrutinee); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/state/RunStateNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/state/RunStateNode.java index e0ca22d46d..b0f44ccd31 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/state/RunStateNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/state/RunStateNode.java @@ -40,7 +40,9 @@ public class RunStateNode extends BuiltinRootNode { if (thunksProfile.profile(TypesGen.isThunk(maybeThunk))) { return new Stateful( state, - thunkExecutorNode.executeThunk(TypesGen.asThunk(maybeThunk), localState).getValue()); + thunkExecutorNode + .executeThunk(TypesGen.asThunk(maybeThunk), localState) + .getValue()); } else { return new Stateful(state, maybeThunk); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/debug/EvalNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/debug/EvalNode.java new file mode 100644 index 0000000000..5c6478f6b6 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/debug/EvalNode.java @@ -0,0 +1,95 @@ +package org.enso.interpreter.node.expression.debug; + +import com.oracle.truffle.api.RootCallTarget; +import com.oracle.truffle.api.Truffle; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.nodes.NodeInfo; +import org.enso.interpreter.Constants; +import org.enso.interpreter.Language; +import org.enso.interpreter.node.BaseNode; +import org.enso.interpreter.node.ClosureRootNode; +import org.enso.interpreter.node.ExpressionNode; +import org.enso.interpreter.node.callable.argument.ThunkExecutorNode; +import org.enso.interpreter.runtime.callable.CallerInfo; +import org.enso.interpreter.runtime.callable.argument.Thunk; +import org.enso.interpreter.runtime.scope.LocalScope; +import org.enso.interpreter.runtime.scope.ModuleScope; +import org.enso.interpreter.runtime.state.Stateful; + +/** Node running Enso expressions passed to it as strings. */ +@NodeInfo(description = "Evaluates code passed to it as string") +public abstract class EvalNode extends BaseNode { + + /** + * Creates an instance of this node. + * + * @return an instance of this node + */ + public static EvalNode build() { + return EvalNodeGen.create(); + } + + /** + * Executes a string expression in the given execution context + * + * @param callerInfo captured information about the execution context + * @param state current value of the monadic state + * @param expression the string containing expression to evaluate + * @return the result of evaluating {@code expression} in the {@code callerInfo} context + */ + public abstract Stateful execute(CallerInfo callerInfo, Object state, String expression); + + RootCallTarget parseExpression(LocalScope scope, ModuleScope moduleScope, String expression) { + LocalScope localScope = new LocalScope(scope); + Language language = lookupLanguageReference(Language.class).get(); + ExpressionNode expr = + lookupContextReference(Language.class) + .get() + .compiler() + .runInline(expression, language, localScope, moduleScope); + ClosureRootNode framedNode = + new ClosureRootNode( + lookupLanguageReference(Language.class).get(), + localScope, + moduleScope, + expr, + null, + ""); + framedNode.setTail(isTail()); + return Truffle.getRuntime().createCallTarget(framedNode); + } + + @Specialization( + guards = { + "expression == cachedExpression", + "callerInfo.getLocalScope() == cachedCallerInfo.getLocalScope()", + "callerInfo.getModuleScope() == cachedCallerInfo.getModuleScope()", + }, + limit = Constants.CacheSizes.EVAL_NODE) + Stateful doCached( + CallerInfo callerInfo, + Object state, + String expression, + @Cached("expression") String cachedExpression, + @Cached("callerInfo") CallerInfo cachedCallerInfo, + @Cached( + "parseExpression(callerInfo.getLocalScope(), callerInfo.getModuleScope(), expression)") + RootCallTarget cachedCallTarget, + @Cached("build(isTail())") ThunkExecutorNode thunkExecutorNode) { + Thunk thunk = new Thunk(cachedCallTarget, callerInfo.getFrame()); + return thunkExecutorNode.executeThunk(thunk, state); + } + + @Specialization + Stateful doUncached( + CallerInfo callerInfo, + Object state, + String expression, + @Cached("build(isTail())") ThunkExecutorNode thunkExecutorNode) { + RootCallTarget callTarget = + parseExpression(callerInfo.getLocalScope(), callerInfo.getModuleScope(), expression); + Thunk thunk = new Thunk(callTarget, callerInfo.getFrame()); + return thunkExecutorNode.executeThunk(thunk, state); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/literal/IntegerLiteralNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/literal/IntegerLiteralNode.java index 2c77f37359..809ef5dc01 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/literal/IntegerLiteralNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/literal/IntegerLiteralNode.java @@ -2,10 +2,11 @@ package org.enso.interpreter.node.expression.literal; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.NodeInfo; +import org.enso.interpreter.node.ExpressionNode; /** A representation of integer literals in Enso. */ @NodeInfo(shortName = "IntegerLiteral") -public final class IntegerLiteralNode extends LiteralNode { +public final class IntegerLiteralNode extends ExpressionNode { private final long value; /** diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/literal/LiteralNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/literal/LiteralNode.java deleted file mode 100644 index 1de87ea38f..0000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/literal/LiteralNode.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.enso.interpreter.node.expression.literal; - -import com.oracle.truffle.api.nodes.NodeInfo; -import org.enso.interpreter.node.ExpressionNode; - -/** - * A base class for all Enso literals. - * - *

It exists only to enable working with literal values as an aggregate. - */ -@NodeInfo(shortName = "Literal", description = "A representation of literal values.") -public abstract class LiteralNode extends ExpressionNode {} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/literal/StringLiteralNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/literal/StringLiteralNode.java new file mode 100644 index 0000000000..23ea2a4edc --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/literal/StringLiteralNode.java @@ -0,0 +1,31 @@ +package org.enso.interpreter.node.expression.literal; + +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.NodeInfo; +import org.enso.interpreter.node.ExpressionNode; + +/** Node representing a constant String value. */ +@NodeInfo(shortName = "StringLiteral", description = "Constant string literal expression") +public class StringLiteralNode extends ExpressionNode { + private final String value; + + /** + * Creates a new instance of this node. + * + * @param value the literal value this node represents + */ + public StringLiteralNode(String value) { + this.value = value; + } + + /** + * Returns the constant value of this string literal. + * + * @param frame the stack frame for execution + * @return the string value this node was created with + */ + @Override + public Object executeGeneric(VirtualFrame frame) { + return value; + } +} 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 7509d11468..f853541053 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 @@ -2,6 +2,7 @@ package org.enso.interpreter.runtime; import org.enso.interpreter.Language; import org.enso.interpreter.node.expression.builtin.IfZeroNode; +import org.enso.interpreter.node.expression.builtin.debug.DebugEvalNode; import org.enso.interpreter.node.expression.builtin.error.CatchErrorNode; import org.enso.interpreter.node.expression.builtin.error.CatchPanicNode; import org.enso.interpreter.node.expression.builtin.error.PanicNode; @@ -38,6 +39,7 @@ public class Builtins { AtomConstructor panic = new AtomConstructor("Panic", scope).initializeFields(); AtomConstructor error = new AtomConstructor("Error", scope).initializeFields(); AtomConstructor state = new AtomConstructor("State", scope).initializeFields(); + AtomConstructor debug = new AtomConstructor("Debug", scope).initializeFields(); scope.registerConstructor(cons); scope.registerConstructor(nil); @@ -46,6 +48,7 @@ public class Builtins { scope.registerConstructor(panic); scope.registerConstructor(error); scope.registerConstructor(state); + scope.registerConstructor(debug); scope.registerMethod(io, "println", PrintNode.makeFunction(language)); @@ -59,6 +62,8 @@ public class Builtins { scope.registerMethod(state, "get", GetStateNode.makeFunction(language)); scope.registerMethod(state, "put", PutStateNode.makeFunction(language)); scope.registerMethod(state, "run", RunStateNode.makeFunction(language)); + + scope.registerMethod(debug, "eval", DebugEvalNode.makeFunction(language)); } /** diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/CallerInfo.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/CallerInfo.java new file mode 100644 index 0000000000..f6411bcdf4 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/CallerInfo.java @@ -0,0 +1,54 @@ +package org.enso.interpreter.runtime.callable; + +import com.oracle.truffle.api.frame.MaterializedFrame; +import org.enso.interpreter.runtime.scope.LocalScope; +import org.enso.interpreter.runtime.scope.ModuleScope; + +/** + * Represents the caller execution context, to be passed to functions that declare the need for it. + */ +public class CallerInfo { + private final MaterializedFrame frame; + private final LocalScope localScope; + private final ModuleScope moduleScope; + + /** + * Creates a new instance of caller information + * + * @param frame the caller's execution frame + * @param localScope the local scope caller uses + * @param moduleScope the module scope caller was defined in + */ + public CallerInfo(MaterializedFrame frame, LocalScope localScope, ModuleScope moduleScope) { + this.frame = frame; + this.localScope = localScope; + this.moduleScope = moduleScope; + } + + /** + * Gets the caller's execution frame. + * + * @return the caller's execution frame + */ + public MaterializedFrame getFrame() { + return frame; + } + + /** + * Gets the caller's local scope metadata. + * + * @return the caller's local scope metadata + */ + public LocalScope getLocalScope() { + return localScope; + } + + /** + * Gets the caller's module scope. + * + * @return the caller's module scope. + */ + public ModuleScope getModuleScope() { + return moduleScope; + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java index 0184ac2a6a..b0f60c2c11 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java @@ -73,4 +73,9 @@ public class UnresolvedSymbol implements TruffleObject { public Function resolveForError() { return scope.lookupMethodDefinitionForAny(name).orElse(null); } + + @Override + public String toString() { + return "UnresolvedSymbol<" + this.name + ">"; + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/argument/CallArgumentInfo.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/argument/CallArgumentInfo.java index 7bfa181990..923459ea04 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/argument/CallArgumentInfo.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/argument/CallArgumentInfo.java @@ -77,8 +77,8 @@ public class CallArgumentInfo { private CallArgumentInfo[] existingOversaturatedArgs; private boolean[] argumentUsed; private boolean[] callSiteArgApplied; + private FunctionSchema originalSchema; private int oversaturatedWritePosition = 0; - private FunctionSchema.CallStrategy callStrategy; /** * Creates an unitialised object of this class. This instance is not safe for external use and @@ -97,7 +97,7 @@ public class CallArgumentInfo { this.definitions = schema.getArgumentInfos(); this.argumentUsed = schema.cloneHasPreApplied(); this.existingOversaturatedArgs = schema.cloneOversaturatedArgs(); - this.callStrategy = schema.getCallStrategy(); + this.originalSchema = schema; } /** @@ -214,7 +214,12 @@ public class CallArgumentInfo { this.existingOversaturatedArgs.length, newOversaturatedArgInfo.length); - return new FunctionSchema(callStrategy, definitions, argumentUsed, oversaturatedArgInfo); + return new FunctionSchema( + originalSchema.getCallStrategy(), + originalSchema.getCallerFrameAccess(), + definitions, + argumentUsed, + oversaturatedArgInfo); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java index 82551d22fe..ef74f2b3b9 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java @@ -3,15 +3,15 @@ package org.enso.interpreter.runtime.callable.atom; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.Truffle; -import com.oracle.truffle.api.frame.FrameDescriptor; import com.oracle.truffle.api.interop.TruffleObject; import org.enso.interpreter.node.ClosureRootNode; import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.node.callable.argument.ReadArgumentNode; import org.enso.interpreter.node.expression.atom.InstantiateNode; import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; -import org.enso.interpreter.runtime.callable.function.FunctionSchema; import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.callable.function.FunctionSchema; +import org.enso.interpreter.runtime.scope.LocalScope; import org.enso.interpreter.runtime.scope.ModuleScope; /** A representation of an Atom constructor. */ @@ -66,7 +66,12 @@ public class AtomConstructor implements TruffleObject { ExpressionNode instantiateNode = new InstantiateNode(this, argumentReaders); ClosureRootNode rootNode = new ClosureRootNode( - null, new FrameDescriptor(), instantiateNode, null, ":" + name); + null, + new LocalScope(), + new ModuleScope(), + instantiateNode, + null, + ":" + name); RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(rootNode); return new Function( callTarget, null, new FunctionSchema(FunctionSchema.CallStrategy.ALWAYS_DIRECT, args)); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java index 2e32059f2a..462ebd3b1a 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java @@ -21,6 +21,7 @@ import org.enso.interpreter.node.callable.argument.sorter.ArgumentSorterNode; import org.enso.interpreter.node.callable.argument.sorter.ArgumentSorterNodeGen; import org.enso.interpreter.node.expression.builtin.BuiltinRootNode; import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.callable.CallerInfo; import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; import org.enso.interpreter.runtime.callable.argument.Thunk; @@ -43,6 +44,9 @@ public final class Function implements TruffleObject { * @param preappliedArguments the preapplied arguments for this function. The layout of this array * must be conforming to the {@code schema}. {@code null} is allowed if the function does not * have any partially applied arguments. + * @param oversaturatedArguments the oversaturated arguments this function may have accumulated. + * The layout of this array must be conforming to the {@code schema}. @{code null} is allowed + * if the function does not carry any oversaturated arguments. */ public Function( RootCallTarget callTarget, @@ -69,7 +73,7 @@ public final class Function implements TruffleObject { } /** - * Creates a Function object from a {@link RootNode} and argument definitions. + * Creates a Function object from a {@link BuiltinRootNode} and argument definitions. * * @param node the {@link RootNode} for the function logic * @param callStrategy the {@link FunctionSchema.CallStrategy} to use for this function @@ -83,6 +87,25 @@ public final class Function implements TruffleObject { return new Function(callTarget, null, schema); } + /** + * Creates a Function object from a {@link BuiltinRootNode} and argument definitions. + * + *

The root node wrapped using this method can safely assume the {@link CallerInfo} argument + * will be non-null. + * + * @param node the {@link RootNode} for the function logic + * @param callStrategy the {@link FunctionSchema.CallStrategy} to use for this function + * @param args argument definitons + * @return a Function object with specified behavior and arguments + */ + public static Function fromBuiltinRootNodeWithCallerFrameAccess( + BuiltinRootNode node, FunctionSchema.CallStrategy callStrategy, ArgumentDefinition... args) { + RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(node); + FunctionSchema schema = + new FunctionSchema(callStrategy, FunctionSchema.CallerFrameAccess.FULL, args); + return new Function(callTarget, null, schema); + } + /** * Gets the target containing the function's code. * @@ -194,7 +217,9 @@ public final class Function implements TruffleObject { @CachedContext(Language.class) Context context, @Cached(value = "arguments.length") int cachedArgsLength, @Cached(value = "buildSorter(cachedArgsLength)") ArgumentSorterNode sorterNode) { - return sorterNode.execute(function, context.getUnit().newInstance(), arguments).getValue(); + return sorterNode + .execute(function, null, context.getUnit().newInstance(), arguments) + .getValue(); } /** @@ -234,8 +259,8 @@ public final class Function implements TruffleObject { * @return an array containing the necessary information to call an Enso function */ public static Object[] buildArguments( - Function function, Object state, Object[] positionalArguments) { - return new Object[] {function.getScope(), state, positionalArguments}; + Function function, CallerInfo callerInfo, Object state, Object[] positionalArguments) { + return new Object[] {function.getScope(), callerInfo, state, positionalArguments}; } /** @@ -246,36 +271,51 @@ public final class Function implements TruffleObject { * @return an array containing the necessary information to call an Enso thunk */ public static Object[] buildArguments(Thunk thunk, Object state) { - return new Object[] {thunk.getScope(), state, new Object[0]}; + return new Object[] {thunk.getScope(), null, state, new Object[0]}; } /** * Gets the positional arguments out of the array. * - * @param arguments an array produced by {@link ArgumentsHelper#buildArguments(Function, Object, - * Object[])} + * @param arguments an array produced by {@link ArgumentsHelper#buildArguments(Function, + * CallerInfo, Object, Object[])} * @return the positional arguments to the function */ public static Object[] getPositionalArguments(Object[] arguments) { - return (Object[]) arguments[2]; + return (Object[]) arguments[3]; } /** * Gets the state out of the array. * - * @param arguments an array produced by {@link ArgumentsHelper#buildArguments(Function, Object, - * Object[])} + * @param arguments an array produced by {@link + * ArgumentsHelper#buildArguments(Function,CallerInfo, Object, Object[])} * @return the state for the function */ public static Object getState(Object[] arguments) { - return arguments[1]; + return arguments[2]; + } + + /** + * Gets the caller info out of the array. + * + *

Any function using this method should declare {@link + * FunctionSchema.CallerFrameAccess#FULL} in its schema for the result to be guaranteed + * non-null. + * + * @param arguments an array produced by {@link ArgumentsHelper#buildArguments(Function, + * CallerInfo, Object, Object[])} + * @return the caller info for the function + */ + public static CallerInfo getCallerInfo(Object[] arguments) { + return (CallerInfo) arguments[1]; } /** * Gets the function's local scope out of the array. * - * @param arguments an array produced by {@link ArgumentsHelper#buildArguments(Function, Object, - * Object[])} + * @param arguments an array produced by {@link ArgumentsHelper#buildArguments(Function, + * CallerInfo, Object, Object[])} * @return the local scope for the associated function */ public static MaterializedFrame getLocalScope(Object[] arguments) { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/FunctionSchema.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/FunctionSchema.java index 99fa1b0288..703c0289c1 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/FunctionSchema.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/FunctionSchema.java @@ -39,17 +39,38 @@ public class FunctionSchema { } } + /** Denotes the caller frame access functions with this schema require to run properly. */ + public enum CallerFrameAccess { + /** Requires full access to the (materialized) caller frame. */ + FULL, + /** Does not use the caller frame at all. */ + NONE; + + /** + * Is there any level of caller frame access required by the function? + * + * @return {@code true} if the function must be passed the caller frame, {@code false} + * otherwise. + */ + public boolean shouldFrameBePassed() { + return this != NONE; + } + } + private final @CompilationFinal(dimensions = 1) ArgumentDefinition[] argumentInfos; private final @CompilationFinal(dimensions = 1) boolean[] hasPreApplied; private final @CompilationFinal(dimensions = 1) CallArgumentInfo[] oversaturatedArguments; private final CallStrategy callStrategy; private final boolean hasAnyPreApplied; private final boolean hasOversaturatedArguments; + private final CallerFrameAccess callerFrameAccess; /** * Creates an {@link FunctionSchema} instance. * * @param callStrategy the call strategy to use for functions having this schema + * @param callerFrameAccess the declaration of whether access to caller frame is required for this + * function * @param argumentInfos Definition site arguments information * @param hasPreApplied A flags collection such that {@code hasPreApplied[i]} is true iff a * function has a partially applied argument at position {@code i} @@ -58,6 +79,7 @@ public class FunctionSchema { */ public FunctionSchema( CallStrategy callStrategy, + CallerFrameAccess callerFrameAccess, ArgumentDefinition[] argumentInfos, boolean[] hasPreApplied, CallArgumentInfo[] oversaturatedArguments) { @@ -65,8 +87,8 @@ public class FunctionSchema { this.argumentInfos = argumentInfos; this.oversaturatedArguments = oversaturatedArguments; this.hasPreApplied = hasPreApplied; + this.callerFrameAccess = callerFrameAccess; boolean hasAnyPreApplied = false; - for (boolean b : hasPreApplied) { if (b) { hasAnyPreApplied = true; @@ -82,10 +104,33 @@ public class FunctionSchema { * Creates an {@link FunctionSchema} instance assuming the function has no partially applied * arguments. * + * @param callStrategy the call strategy to use for this function + * @param callerFrameAccess the declaration of need to access the caller frame from the function + * @param argumentInfos Definition site arguments information + */ + public FunctionSchema( + CallStrategy callStrategy, + CallerFrameAccess callerFrameAccess, + ArgumentDefinition... argumentInfos) { + this( + callStrategy, + callerFrameAccess, + argumentInfos, + new boolean[argumentInfos.length], + new CallArgumentInfo[0]); + } + + /** + * Creates an {@link FunctionSchema} instance assuming the function has no partially applied + * arguments. + * + *

Caller frame access is assumed to be {@link CallerFrameAccess#NONE}. + * + * @param callStrategy the call strategy to use for this function * @param argumentInfos Definition site arguments information */ public FunctionSchema(CallStrategy callStrategy, ArgumentDefinition... argumentInfos) { - this(callStrategy, argumentInfos, new boolean[argumentInfos.length], new CallArgumentInfo[0]); + this(callStrategy, CallerFrameAccess.NONE, argumentInfos); } /** @@ -183,4 +228,13 @@ public class FunctionSchema { public CallStrategy getCallStrategy() { return callStrategy; } + + /** + * Returns the caller frame access declaration for this function. + * + * @return the caller frame access declaration + */ + public CallerFrameAccess getCallerFrameAccess() { + return callerFrameAccess; + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/control/TailCallException.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/control/TailCallException.java index 5fd50eb616..78378d66a3 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/control/TailCallException.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/control/TailCallException.java @@ -1,6 +1,7 @@ package org.enso.interpreter.runtime.control; import com.oracle.truffle.api.nodes.ControlFlowException; +import org.enso.interpreter.runtime.callable.CallerInfo; import org.enso.interpreter.runtime.callable.function.Function; /** @@ -10,6 +11,7 @@ import org.enso.interpreter.runtime.callable.function.Function; */ public class TailCallException extends ControlFlowException { private final Function function; + private final CallerInfo callerInfo; private final Object state; private final Object[] arguments; @@ -20,8 +22,10 @@ public class TailCallException extends ControlFlowException { * @param state the state to pass to the function * @param arguments the arguments to {@code function} */ - public TailCallException(Function function, Object state, Object[] arguments) { + public TailCallException( + Function function, CallerInfo callerInfo, Object state, Object[] arguments) { this.function = function; + this.callerInfo = callerInfo; this.arguments = arguments; this.state = state; } @@ -45,11 +49,20 @@ public class TailCallException extends ControlFlowException { } /** - * Gets the state to pass to the function + * Gets the state to pass to the function. * * @return the state to pass for next call */ public Object getState() { return state; } + + /** + * Gets the caller info to pass to the function. + * + * @return the state to pass for next call + */ + public CallerInfo getCallerInfo() { + return callerInfo; + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/LocalScope.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/LocalScope.java index f6c1a50498..8cee5def69 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/LocalScope.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/LocalScope.java @@ -14,24 +14,23 @@ import java.util.Optional; * frames. */ public class LocalScope { - private Map items; - private FrameDescriptor frameDescriptor; - private LocalScope parent; + public final Map items; + private final FrameDescriptor frameDescriptor; + public final LocalScope parent; - /** Creates a new local scope with defaulted arguments. */ + /** Creates a root local scope. */ public LocalScope() { - items = new HashMap<>(); - frameDescriptor = new FrameDescriptor(); - parent = null; + this(null); } /** - * Creates a new local scope with a known parent. + * Creates a child local scope with a given parent. * * @param parent the parent scope */ public LocalScope(LocalScope parent) { - this(); + items = new HashMap<>(); + frameDescriptor = new FrameDescriptor(); this.parent = parent; } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/Compiler.scala b/engine/runtime/src/main/scala/org/enso/compiler/Compiler.scala index 766e418526..365bf7be1d 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/Compiler.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/Compiler.scala @@ -5,14 +5,17 @@ import com.oracle.truffle.api.source.Source import org.enso.compiler.generate.AstToIr import org.enso.compiler.ir.IR import org.enso.flexer.Reader +import org.enso.interpreter.AstExpression import org.enso.interpreter.Constants import org.enso.interpreter.EnsoParser import org.enso.interpreter.Language +import org.enso.interpreter.builder.ExpressionFactory import org.enso.interpreter.builder.ModuleScopeExpressionFactory import org.enso.interpreter.node.ExpressionNode import org.enso.interpreter.runtime.Context import org.enso.interpreter.runtime.Module import org.enso.interpreter.runtime.error.ModuleDoesNotExistException +import org.enso.interpreter.runtime.scope.LocalScope import org.enso.interpreter.runtime.scope.ModuleScope import org.enso.syntax.text.AST import org.enso.syntax.text.Parser @@ -88,6 +91,27 @@ class Compiler( run(Source.newBuilder(Constants.LANGUAGE_ID, file).build) } + /** + * Processes the language source, interpreting it as an expression. + * Processes the source in the context of given local and module scopes. + * + * @param source string representing the expression to process + * @param language current language instance + * @param localScope local scope to process the source in + * @param moduleScope module scope to process the source in + * @return an expression node representing the parsed and analyzed source + */ + def runInline( + source: String, + language: Language, + localScope: LocalScope, + moduleScope: ModuleScope + ): ExpressionNode = { + val parsed = parseInline(source) + new ExpressionFactory(language, localScope, "", moduleScope) + .run(parsed) + } + /** * Finds and processes a language source by its qualified name. * @@ -119,6 +143,17 @@ class Compiler( resolvedAST } + /** + * Parses the provided language source expression in inline mode. + * + * @param source the code to parse + * @return an AST representation of `source` + */ + def parseInline(source: String): AstExpression = { + val parsed = new EnsoParser().parseEnsoInline(source) + parsed + } + /** * Lowers the input AST to the compiler's high-level intermediate * representation. diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/Parser.scala b/engine/runtime/src/main/scala/org/enso/interpreter/Parser.scala index 7de41034bd..ccf07aeca5 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/Parser.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/Parser.scala @@ -2,6 +2,8 @@ package org.enso.interpreter import java.util.Optional +import org.apache.commons.lang3.StringEscapeUtils + import scala.collection.JavaConverters._ import scala.language.postfixOps import scala.util.parsing.combinator._ @@ -42,6 +44,8 @@ trait AstExpressionVisitor[+T] { ): T def visitDesuspend(target: AstExpression): T + + def visitStringLiteral(string: String): T } trait AstModuleScopeVisitor[+T] { @@ -144,6 +148,11 @@ case class AstLong(l: Long) extends AstExpression { visitor.visitLong(l) } +case class AstStringLiteral(string: String) extends AstExpression { + override def visit[T](visitor: AstExpressionVisitor[T]): T = + visitor.visitStringLiteral(string) +} + case class AstArithOp(op: String, left: AstExpression, right: AstExpression) extends AstExpression { override def visit[T](visitor: AstExpressionVisitor[T]): T = @@ -261,6 +270,12 @@ class EnsoParserInternal extends JavaTokenParsers { def foreignLiteral: Parser[String] = "**" ~> "[^\\*]*".r <~ "**" + def string: Parser[AstStringLiteral] = stringLiteral ^^ { lit => + AstStringLiteral( + StringEscapeUtils.unescapeJava(lit.substring(1, lit.length - 1)) + ) + } + def variable: Parser[AstVariable] = ident ^^ AstVariable def operand: Parser[AstExpression] = @@ -273,7 +288,7 @@ class EnsoParserInternal extends JavaTokenParsers { } def expression: Parser[AstExpression] = - desuspend | matchClause | arith | function + desuspend | matchClause | arith | function | string def functionCall: Parser[AstApply] = "@" ~> expression ~ (argList ?) ~ defaultSuspend ^^ { @@ -351,6 +366,10 @@ class EnsoParserInternal extends JavaTokenParsers { def parse(code: String): AstExpression = { parseAll(expression | function, code).get } + + def parseLine(code: String): AstExpression = { + parseAll(statement, code).get + } } class EnsoParser { @@ -358,4 +377,8 @@ class EnsoParser { def parseEnso(code: String): AstModuleScope = { new EnsoParserInternal().parseGlobalScope(code) } + + def parseEnsoInline(code: String): AstExpression = { + new EnsoParserInternal().parseLine(code) + } } diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/InterpreterException.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/InterpreterException.scala index 9c3ef520c5..f43f925a0a 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/InterpreterException.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/InterpreterException.scala @@ -1,7 +1,6 @@ package org.enso.interpreter.test import org.graalvm.polyglot.PolyglotException -import org.graalvm.polyglot.Value case class InterpreterException( @transient polyglotException: PolyglotException 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 c81245fa05..c635f1cc59 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 @@ -3,10 +3,8 @@ package org.enso.interpreter.test import java.io.ByteArrayOutputStream import org.enso.interpreter.Constants -import org.graalvm.polyglot.Context -import org.graalvm.polyglot.Value -import org.scalatest.FlatSpec -import org.scalatest.Matchers +import org.graalvm.polyglot.{Context, Value} +import org.scalatest.{FlatSpec, Matchers} trait InterpreterRunner { implicit class RichValue(value: Value) { diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/ErrorsTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/ErrorsTest.scala index f6dd1187c7..8b248cae1a 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/ErrorsTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/ErrorsTest.scala @@ -1,7 +1,6 @@ package org.enso.interpreter.test.semantic -import org.enso.interpreter.test.InterpreterException -import org.enso.interpreter.test.InterpreterTest +import org.enso.interpreter.test.{InterpreterException, InterpreterTest} class ErrorsTest extends InterpreterTest { "Panics" should "be thrown and stop evaluation" in { diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/EvalTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/EvalTest.scala new file mode 100644 index 0000000000..b6b2752581 --- /dev/null +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/EvalTest.scala @@ -0,0 +1,85 @@ +package org.enso.interpreter.test.semantic + +import org.enso.interpreter.test.InterpreterTest + +class EvalTest extends InterpreterTest { + "Debug.eval" should "evaluate a string expression" in { + val code = + """ + |@{ + | @eval [@Debug, "@println[@IO, \"foo\"]"] + |} + |""".stripMargin + eval(code) + consumeOut shouldEqual List("foo") + } + + "Debug.eval" should "have access to the caller scope" in { + val code = + """ + |@{ + | x = "Hello World!"; + | @eval [@Debug, "@println[@IO, x]"] + |} + |""".stripMargin + eval(code) + consumeOut shouldEqual List("Hello World!") + } + + "Debug.eval" should "have access to the caller module scope" in { + val code = + """ + |type MyType x; + | + |@{ + | x = 10; + | @eval [@Debug, "@println[@IO, @MyType[x]]"] + |} + |""".stripMargin + eval(code) + consumeOut shouldEqual List("MyType<10>") + } + + "Debug.eval" should "return a value usable in the caller scope" in { + val code = + """ + |@{ + | x = 1; + | y = 2; + | res = @eval [@Debug, "x + y"]; + | res + 1 + |} + |""".stripMargin + eval(code) shouldEqual 4 + } + + "Debug.eval" should "work in a recursive setting" in { + val code = + """ + |{ |sumTo| + | summator = { |acc, current| + | @eval [@Debug, "@ifZero [current, acc, @summator [acc + current, current - 1]]"] + | }; + | res = @summator [0, sumTo]; + | res + |} + |""".stripMargin + val fun = eval(code) + fun.call(100) shouldEqual 5050 + } + + "Debug.eval" should "work inside a thunk passed to another function" in { + val code = + """ + |{ |sumTo| + | summator = { |acc, current| + | @ifZero [current, acc, @eval [@Debug, "@summator [acc + current, current - 1]"]] + | }; + | res = @summator [0, sumTo]; + | res + |} + |""".stripMargin + val fun = eval(code) + fun.call(100) shouldEqual 5050 + } +} diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/GlobalScopeTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/GlobalScopeTest.scala index 92508801b9..5b154bafe8 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/GlobalScopeTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/GlobalScopeTest.scala @@ -1,7 +1,6 @@ package org.enso.interpreter.test.semantic -import org.enso.interpreter.test.InterpreterException -import org.enso.interpreter.test.InterpreterTest +import org.enso.interpreter.test.{InterpreterException, InterpreterTest} class GlobalScopeTest extends InterpreterTest { diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/NamedArgumentsTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/NamedArgumentsTest.scala index 6e95e59ff1..0a262d5280 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/NamedArgumentsTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/NamedArgumentsTest.scala @@ -1,7 +1,6 @@ package org.enso.interpreter.test.semantic import org.enso.interpreter.test.{InterpreterException, InterpreterTest} -import org.graalvm.polyglot.PolyglotException class NamedArgumentsTest extends InterpreterTest { "Functions" should "take arguments by name and use them in their bodies" in { diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/PackageTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/PackageTest.scala index 241e9b7929..bdede583f3 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/PackageTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/PackageTest.scala @@ -4,14 +4,10 @@ import java.io.File import org.enso.interpreter.Constants import org.enso.interpreter.runtime.RuntimeOptions -import org.enso.interpreter.test.InterpreterException -import org.enso.interpreter.test.ValueEquality +import org.enso.interpreter.test.{InterpreterException, ValueEquality} import org.enso.pkg.Package -import org.graalvm.polyglot.Context -import org.graalvm.polyglot.Source -import org.graalvm.polyglot.Value -import org.scalatest.FlatSpec -import org.scalatest.Matchers +import org.graalvm.polyglot.{Context, Source, Value} +import org.scalatest.{FlatSpec, Matchers} trait PackageTest extends FlatSpec with Matchers with ValueEquality { diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/StringTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/StringTest.scala new file mode 100644 index 0000000000..b2dcfe2e90 --- /dev/null +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/StringTest.scala @@ -0,0 +1,15 @@ +package org.enso.interpreter.test.semantic + +import org.enso.interpreter.test.InterpreterTest + +class StringTest extends InterpreterTest { + "Strings" should "exist in the language and be printable" in { + val code = + """ + |@println [@IO, "hello world!"] + |""".stripMargin + + noException shouldBe thrownBy(eval(code)) + consumeOut shouldEqual List("hello world!") + } +}