diff --git a/.gitignore b/.gitignore index 7ff8b61abd8..290628baa94 100644 --- a/.gitignore +++ b/.gitignore @@ -68,8 +68,14 @@ cabal.sandbox.config *.swp .projections.json +############################ +## Rendered Documentation ## +############################ + +javadoc/ ####################### ## Benchmark Reports ## ####################### bench-report.xml + diff --git a/Interpreter/src/bench/java/org/enso/interpreter/benchmarks/AtomBenchmarks.java b/Interpreter/src/bench/java/org/enso/interpreter/benchmarks/AtomBenchmarks.java index e3460d0b953..229ab8c9634 100644 --- a/Interpreter/src/bench/java/org/enso/interpreter/benchmarks/AtomBenchmarks.java +++ b/Interpreter/src/bench/java/org/enso/interpreter/benchmarks/AtomBenchmarks.java @@ -1,7 +1,6 @@ package org.enso.interpreter.benchmarks; -import org.enso.interpreter.AtomFixtures; -import org.enso.interpreter.RecursionFixtures; +import org.enso.interpreter.fixtures.AtomFixtures; import org.openjdk.jmh.annotations.*; import java.util.concurrent.TimeUnit; diff --git a/Interpreter/src/bench/java/org/enso/interpreter/benchmarks/NamedDefaultedArgumentBenchmarks.java b/Interpreter/src/bench/java/org/enso/interpreter/benchmarks/NamedDefaultedArgumentBenchmarks.java new file mode 100644 index 00000000000..5be93e8d705 --- /dev/null +++ b/Interpreter/src/bench/java/org/enso/interpreter/benchmarks/NamedDefaultedArgumentBenchmarks.java @@ -0,0 +1,31 @@ +package org.enso.interpreter.benchmarks; + +import java.util.concurrent.TimeUnit; +import org.enso.interpreter.fixtures.NamedDefaultedArgumentFixtures; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Warmup; + +@BenchmarkMode(Mode.AverageTime) +@Fork(2) +@Warmup(iterations = 5) +@Measurement(iterations = 5) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +public class NamedDefaultedArgumentBenchmarks { + private static NamedDefaultedArgumentFixtures argumentFixtures = + new NamedDefaultedArgumentFixtures(); + + @Benchmark + public void benchSumTCOWithNamedArgs() { + argumentFixtures.sumTCOWithNamedArguments().execute(argumentFixtures.hundredMillion()); + } + + @Benchmark + public void benchSumTCOWithDefaultArgs() { + argumentFixtures.sumTCOWithDefaultedArguments().execute(argumentFixtures.hundredMillion()); + } +} diff --git a/Interpreter/src/bench/java/org/enso/interpreter/benchmarks/RecursionBenchmarks.java b/Interpreter/src/bench/java/org/enso/interpreter/benchmarks/RecursionBenchmarks.java index 55588c8ee68..2410b37a4dc 100644 --- a/Interpreter/src/bench/java/org/enso/interpreter/benchmarks/RecursionBenchmarks.java +++ b/Interpreter/src/bench/java/org/enso/interpreter/benchmarks/RecursionBenchmarks.java @@ -1,6 +1,6 @@ package org.enso.interpreter.benchmarks; -import org.enso.interpreter.RecursionFixtures; +import org.enso.interpreter.fixtures.RecursionFixtures; import org.openjdk.jmh.annotations.*; import java.util.concurrent.TimeUnit; @@ -22,4 +22,9 @@ public class RecursionBenchmarks { public void benchSumTCOFoldLike() { recursionFixtures.sumTCOFoldLike().execute(recursionFixtures.hundredMillion()); } + + @Benchmark + public void benchSumRecursive() { + recursionFixtures.sumTCO().execute(recursionFixtures.hundred()); + } } diff --git a/Interpreter/src/bench/scala/org/enso/interpreter/RegressionTest.scala b/Interpreter/src/bench/scala/org/enso/interpreter/RegressionTest.scala index 73218ce8190..34626863f85 100644 --- a/Interpreter/src/bench/scala/org/enso/interpreter/RegressionTest.scala +++ b/Interpreter/src/bench/scala/org/enso/interpreter/RegressionTest.scala @@ -3,9 +3,10 @@ package org.enso.interpreter import org.scalatest.FlatSpec import org.scalatest.Matchers -import collection.JavaConverters._ +import scala.collection.JavaConverters._ class RegressionTest extends FlatSpec with Matchers { + // This tolerance may be adjusted depending on the stability of CI final val TOLERANCE = 0.2 val runner = new BenchmarksRunner diff --git a/Interpreter/src/bench/scala/org/enso/interpreter/AtomFixtures.scala b/Interpreter/src/bench/scala/org/enso/interpreter/fixtures/AtomFixtures.scala similarity index 94% rename from Interpreter/src/bench/scala/org/enso/interpreter/AtomFixtures.scala rename to Interpreter/src/bench/scala/org/enso/interpreter/fixtures/AtomFixtures.scala index 7894f7e5f20..18ce4227864 100644 --- a/Interpreter/src/bench/scala/org/enso/interpreter/AtomFixtures.scala +++ b/Interpreter/src/bench/scala/org/enso/interpreter/fixtures/AtomFixtures.scala @@ -1,4 +1,6 @@ -package org.enso.interpreter +package org.enso.interpreter.fixtures + +import org.enso.interpreter.LanguageRunner class AtomFixtures extends LanguageRunner { val million: Long = 1000000 diff --git a/Interpreter/src/bench/scala/org/enso/interpreter/fixtures/NamedDefaultedArgumentFixtures.scala b/Interpreter/src/bench/scala/org/enso/interpreter/fixtures/NamedDefaultedArgumentFixtures.scala new file mode 100644 index 00000000000..2099bd67698 --- /dev/null +++ b/Interpreter/src/bench/scala/org/enso/interpreter/fixtures/NamedDefaultedArgumentFixtures.scala @@ -0,0 +1,34 @@ +package org.enso.interpreter.fixtures + +import org.enso.interpreter.LanguageRunner + +class NamedDefaultedArgumentFixtures extends LanguageRunner { + val hundredMillion: Long = 100000000 + + val sumTCOWithNamedArgumentsCode = + """ + |{ |sumTo| + | summator = { |acc, current| + | ifZero: [current, acc, @summator [current = current - 1, acc = acc + current]] + | }; + | res = @summator [current = sumTo, acc = 0]; + | res + |} + """.stripMargin + + val sumTCOWithNamedArguments = eval(sumTCOWithNamedArgumentsCode) + + val sumTCOWithDefaultedArgumentsCode = + """ + |{ |sumTo| + | summator = { |acc = 0, current| + | ifZero: [current, acc, @summator [current = current - 1, acc = acc + current]] + | }; + | res = @summator [current = sumTo]; + | res + |} + """.stripMargin + + val sumTCOWithDefaultedArguments = eval(sumTCOWithDefaultedArgumentsCode) + +} diff --git a/Interpreter/src/bench/scala/org/enso/interpreter/RecursionFixtures.scala b/Interpreter/src/bench/scala/org/enso/interpreter/fixtures/RecursionFixtures.scala similarity index 91% rename from Interpreter/src/bench/scala/org/enso/interpreter/RecursionFixtures.scala rename to Interpreter/src/bench/scala/org/enso/interpreter/fixtures/RecursionFixtures.scala index df395473de3..e40b89735d3 100644 --- a/Interpreter/src/bench/scala/org/enso/interpreter/RecursionFixtures.scala +++ b/Interpreter/src/bench/scala/org/enso/interpreter/fixtures/RecursionFixtures.scala @@ -1,7 +1,10 @@ -package org.enso.interpreter +package org.enso.interpreter.fixtures + +import org.enso.interpreter.{Constants, LanguageRunner} class RecursionFixtures extends LanguageRunner { val hundredMillion: Long = 100000000 + val hundred: Long = 100 // Currently unused as we know this is very slow. val mutRecursiveCode = diff --git a/Interpreter/src/main/java/org/enso/interpreter/Constants.java b/Interpreter/src/main/java/org/enso/interpreter/Constants.java index 7f21edc98e0..66fa1f57efc 100644 --- a/Interpreter/src/main/java/org/enso/interpreter/Constants.java +++ b/Interpreter/src/main/java/org/enso/interpreter/Constants.java @@ -1,5 +1,8 @@ package org.enso.interpreter; +/** + * Language-level constants for use throughout the program. + */ public class Constants { public static final String LANGUAGE_ID = "enso"; public static final String LANGUAGE_NAME = "Enso"; diff --git a/Interpreter/src/main/java/org/enso/interpreter/Language.java b/Interpreter/src/main/java/org/enso/interpreter/Language.java index 46d01ae3824..692c5e13c99 100644 --- a/Interpreter/src/main/java/org/enso/interpreter/Language.java +++ b/Interpreter/src/main/java/org/enso/interpreter/Language.java @@ -12,6 +12,15 @@ import org.enso.interpreter.node.EnsoRootNode; import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.runtime.Context; +/** + * The root of the Enso implementation. + * + *
This class contains all of the services needed by a Truffle language to enable interoperation
+ * with other guest languages on the same VM. This ensures that Enso is usable via the polyglot API,
+ * and hence that it can both call other languages seamlessly, and be called from other languages.
+ *
+ * See {@link TruffleLanguage} for more information on the lifecycle of a language.
+ */
@TruffleLanguage.Registration(
id = Constants.LANGUAGE_ID,
name = Constants.LANGUAGE_NAME,
@@ -29,21 +38,47 @@ import org.enso.interpreter.runtime.Context;
})
public final class Language extends TruffleLanguage Such arguments are used to disable the function's usage of a default with which it was
+ * defined, and become useful in the presence of partial function application and currying.
+ *
+ * @param name the name of the argument whose default is ignored
+ * @param position the position of this argument in the calling arguments list
+ * @return a runtime representation of the argument
+ */
+ @Override
+ public CallArgument visitIgnore(String name, int position) {
+ return new CallArgument(name);
+ }
+
+ /**
+ * Processes a named argument application.
+ *
+ * Arguments can be applied by name, and can occur at any point in the parameter list.
+ *
+ * @param name the name of the argument being applied
+ * @param value the value of the argument being applied
+ * @param position the position of this argument in the calling arguments list
+ * @return a runtime representation of the argument
+ */
+ @Override
+ public CallArgument visitNamedCallArg(String name, AstExpression value, int position) {
+ ExpressionFactory factory = new ExpressionFactory(language, scope, scopeName, globalScope);
+ return new CallArgument(name, value.visit(factory));
+ }
+
+ /**
+ * Processes a positional argument application.
+ *
+ * Though all arguments have positions at the call site, an argument without a name is applied
+ * purely based on its position.
+ *
+ * @param value the value of the argument being applied
+ * @param position the position of this argument in the calling arguments list
+ * @return a runtime representation of the argument
+ */
+ @Override
+ public CallArgument visitUnnamedCallArg(AstExpression value, int position) {
+ ExpressionFactory factory = new ExpressionFactory(language, scope, scopeName, globalScope);
+ return new CallArgument(value.visit(factory));
+ }
+}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/builder/ExpressionFactory.java b/Interpreter/src/main/java/org/enso/interpreter/builder/ExpressionFactory.java
index 60009e532f8..04a76223056 100644
--- a/Interpreter/src/main/java/org/enso/interpreter/builder/ExpressionFactory.java
+++ b/Interpreter/src/main/java/org/enso/interpreter/builder/ExpressionFactory.java
@@ -4,70 +4,144 @@ import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.frame.FrameSlot;
import com.oracle.truffle.api.nodes.RootNode;
-import org.enso.interpreter.*;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.enso.interpreter.AstArgDefinition;
+import org.enso.interpreter.AstCallArg;
+import org.enso.interpreter.AstCase;
+import org.enso.interpreter.AstCaseFunction;
+import org.enso.interpreter.AstExpression;
+import org.enso.interpreter.AstExpressionVisitor;
+import org.enso.interpreter.Language;
import org.enso.interpreter.node.EnsoRootNode;
import org.enso.interpreter.node.ExpressionNode;
-import org.enso.interpreter.node.controlflow.*;
+import org.enso.interpreter.node.callable.InvokeCallableNodeGen;
+import org.enso.interpreter.node.callable.argument.ReadArgumentNode;
+import org.enso.interpreter.node.callable.function.CreateFunctionNode;
+import org.enso.interpreter.node.callable.function.FunctionBodyNode;
+import org.enso.interpreter.node.controlflow.CaseNode;
+import org.enso.interpreter.node.controlflow.ConstructorCaseNode;
+import org.enso.interpreter.node.controlflow.DefaultFallbackNode;
+import org.enso.interpreter.node.controlflow.FallbackNode;
+import org.enso.interpreter.node.controlflow.IfZeroNode;
+import org.enso.interpreter.node.controlflow.MatchNode;
import org.enso.interpreter.node.expression.builtin.PrintNode;
import org.enso.interpreter.node.expression.constant.ConstructorNode;
import org.enso.interpreter.node.expression.literal.IntegerLiteralNode;
-import org.enso.interpreter.node.expression.operator.*;
-import org.enso.interpreter.node.function.CreateFunctionNode;
-import org.enso.interpreter.node.function.FunctionBodyNode;
-import org.enso.interpreter.node.function.InvokeNodeGen;
-import org.enso.interpreter.node.function.ReadArgumentNode;
+import org.enso.interpreter.node.expression.operator.AddOperatorNodeGen;
+import org.enso.interpreter.node.expression.operator.DivideOperatorNodeGen;
+import org.enso.interpreter.node.expression.operator.ModOperatorNodeGen;
+import org.enso.interpreter.node.expression.operator.MultiplyOperatorNodeGen;
+import org.enso.interpreter.node.expression.operator.SubtractOperatorNodeGen;
import org.enso.interpreter.node.scope.AssignmentNode;
import org.enso.interpreter.node.scope.AssignmentNodeGen;
import org.enso.interpreter.node.scope.ReadGlobalTargetNode;
import org.enso.interpreter.node.scope.ReadLocalTargetNodeGen;
-import org.enso.interpreter.runtime.errors.VariableDoesNotExistException;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
+import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
+import org.enso.interpreter.runtime.callable.argument.CallArgument;
+import org.enso.interpreter.runtime.error.DuplicateArgumentNameException;
+import org.enso.interpreter.runtime.error.VariableDoesNotExistException;
+import org.enso.interpreter.runtime.scope.GlobalScope;
+import org.enso.interpreter.runtime.scope.LocalScope;
+import sun.reflect.generics.reflectiveObjects.NotImplementedException;
+/**
+ * An {@code ExpressionFactory} is responsible for converting the majority of Enso's parsed AST into
+ * nodes evaluated by the interpreter at runtime.
+ */
public class ExpressionFactory implements AstExpressionVisitor This child will be initialized with a {@code LocalScope} that is a child of the local scope
+ * contained within {@code this}.
+ *
+ * @param name the name of the new scope
+ * @return a child of this current expression factory
+ */
public ExpressionFactory createChild(String name) {
return new ExpressionFactory(language, scope.createChild(), name, this.globalScope);
}
+ /**
+ * Creates an executable expression from an AST expression.
+ *
+ * @param expr the expression to make executable
+ * @return a node representing the provided computation
+ */
public ExpressionNode run(AstExpression expr) {
ExpressionNode result = expr.visit(this);
result.markNotTail();
return result;
}
+ /**
+ * Creates a runtime {@code long} value from an AST node.
+ *
+ * @param l the value to represent
+ * @return a runtime node representing that value
+ */
public ExpressionNode visitLong(long l) {
return new IntegerLiteralNode(l);
}
+ /**
+ * Creates runtime nodes representing arithmetic expressions.
+ *
+ * @param operator the operator to represent
+ * @param leftAst the expressions to the left of the operator
+ * @param rightAst the expressions to the right of the operator
+ * @return a runtime node representing the arithmetic expression
+ */
@Override
public ExpressionNode visitArithOp(
String operator, AstExpression leftAst, AstExpression rightAst) {
@@ -81,21 +155,37 @@ public class ExpressionFactory implements AstExpressionVisitor This method is solely responsible for creating a variable in the parent scope with the
+ * provided name and does not handle associating that variable with a value.
+ *
+ * @param name the name of the variable
+ * @return a runtime node representing the variable
+ */
@Override
public ExpressionNode visitVariable(String name) {
- Supplier In addition to the creation of the node, this method is also responsible for rewriting
+ * function arguments into a state where they can actually be read.
+ *
+ * @param arguments the arguments the function is defined for
+ * @param expressions the body of the function
+ * @param retValue the return value of the function
+ * @return a runtime node representing the function body
+ */
public ExpressionNode processFunctionBody(
- List Given that most of the work takes place in {@link #processFunctionBody(List, List,
+ * AstExpression) processFunctionBody}, this node is solely responsible for handling the creation
+ * of a new scope for the function, and marking it as tail recursive.
+ *
+ * @param arguments the arguments to the function
+ * @param expressions the expressions that make up the function body
+ * @param retValue the return value of the function
+ * @return a runtime node representing the function
+ */
@Override
public ExpressionNode visitFunction(
- List Given that most of the work takes place in {@link #processFunctionBody(List, List,
+ * AstExpression) processFunctionBody}, this node is solely responsible for handling the creation
+ * of a new scope for the function.
+ *
+ * @param arguments the arguments to the function
+ * @param expressions the expressions that make up the function body
+ * @param retValue the return value of the function
+ * @return a runtime node representing the function
+ */
@Override
- public ExpressionNode visitApplication(AstExpression function, List All new computations in Enso must be executed from within an {@link EnsoRootNode}, as
+ * determined by the API provided by Truffle.
+ */
public class EnsoRootNode extends RootNode {
private final String name;
private final SourceSection sourceSection;
@Child private ExpressionNode body;
+ /**
+ * Creates a new root node.
+ *
+ * @param language the language identifier
+ * @param frameDescriptor a description of the stack frame
+ * @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 EnsoRootNode(
Language language,
FrameDescriptor frameDescriptor,
@@ -23,17 +38,43 @@ public class EnsoRootNode extends RootNode {
this.name = name;
}
+ /**
+ * Executes the node.
+ *
+ * @param frame the stack frame to execute in
+ * @return the result of executing this node
+ */
@Override
public Object execute(VirtualFrame frame) {
return body.executeGeneric(frame);
}
+ /**
+ * Converts this node to a textual representation good for debugging.
+ *
+ * @return a {@link String} representation of this node
+ */
@Override
public String toString() {
return this.name;
}
+ /** Marks the node as tail-recursive. */
public void markTail() {
body.markTail();
}
+
+ /** Marks the node as not tail-recursive. */
+ public void markNotTail() {
+ body.markNotTail();
+ }
+
+ /**
+ * Sets whether the node is tail-recursive.
+ *
+ * @param isTail whether or not the node is tail-recursive.
+ */
+ public void setTail(boolean isTail) {
+ body.setTail(isTail);
+ }
}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/ExpressionNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/ExpressionNode.java
index eeea0b3f3b6..aceb99904af 100644
--- a/Interpreter/src/main/java/org/enso/interpreter/node/ExpressionNode.java
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/ExpressionNode.java
@@ -1,53 +1,88 @@
package org.enso.interpreter.node;
-import com.oracle.truffle.api.CompilerDirectives;
-import com.oracle.truffle.api.dsl.ReportPolymorphism;
import com.oracle.truffle.api.frame.VirtualFrame;
-import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
-import org.enso.interpreter.runtime.Atom;
-import org.enso.interpreter.runtime.AtomConstructor;
-import org.enso.interpreter.runtime.Function;
-import org.enso.interpreter.runtime.TypesGen;
+import org.enso.interpreter.runtime.callable.atom.Atom;
+import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
+import org.enso.interpreter.runtime.callable.function.Function;
+import org.enso.interpreter.runtime.type.TypesGen;
+/**
+ * A base class for all Enso expressions.
+ *
+ * Enso is an expression-oriented language, and hence doesn't have any statements. This means
+ * that all expression execution will return a value, even if that is just the {@link
+ * AtomConstructor#UNIT} type.
+ *
+ * This class contains specialisations of the {@link #executeGeneric(VirtualFrame)
+ * executeGeneric} method for various scenarios in order to improve performance.
+ */
@NodeInfo(shortName = "EnsoExpression", description = "The base node for all enso expressions.")
-@ReportPolymorphism
-public abstract class ExpressionNode extends Node {
-
- @CompilerDirectives.CompilationFinal private boolean isTail = false;
-
- public void markTail() {
- isTail = true;
- }
-
- public void markNotTail() {
- isTail = false;
- }
-
- public boolean isTail() {
- return isTail;
- }
+public abstract class ExpressionNode extends BaseNode {
+ /**
+ * Executes the current node, returning the result as a {@code long}.
+ *
+ * @param frame the stack frame for execution
+ * @return the {@code long} value obtained by executing the node
+ * @throws UnexpectedResultException if the result cannot be represented as a value of the return
+ * type
+ */
public long executeLong(VirtualFrame frame) throws UnexpectedResultException {
return TypesGen.expectLong(executeGeneric(frame));
}
+ /**
+ * Executes the current node, returning the result as an {@link AtomConstructor}.
+ *
+ * @param frame the stack frame for execution
+ * @return the Atom constructor obtained by executing the node
+ * @throws UnexpectedResultException if the result cannot be represented as a value of the return
+ * type
+ */
public AtomConstructor executeAtomConstructor(VirtualFrame frame)
throws UnexpectedResultException {
return TypesGen.expectAtomConstructor(executeGeneric(frame));
}
+ /**
+ * Executes the current node, returning the result as an {@link Atom}.
+ *
+ * @param frame the stack frame for execution
+ * @return the Atom obtained by executing the node
+ * @throws UnexpectedResultException if the result cannot be represented as a value of the return
+ * type
+ */
public Atom executeAtom(VirtualFrame frame) throws UnexpectedResultException {
return TypesGen.expectAtom(executeGeneric(frame));
}
+ /**
+ * Executes the current node, returning the result as a {@link Function}.
+ *
+ * @param frame the stack frame for execution
+ * @return the function obtained by executing the node
+ * @throws UnexpectedResultException if the result cannot be represented as a value of the return
+ * type
+ */
public Function executeFunction(VirtualFrame frame) throws UnexpectedResultException {
return TypesGen.expectFunction(executeGeneric(frame));
}
+ /**
+ * Executes the current node and returns a result.
+ *
+ * @param frame the stack frame for execution
+ * @return the result of executing the node
+ */
public abstract Object executeGeneric(VirtualFrame frame);
+ /**
+ * Executes the current node without returning a result.
+ *
+ * @param frame the stack frame for execution
+ */
public void executeVoid(VirtualFrame frame) {
executeGeneric(frame);
}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/callable/ExecuteCallNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/callable/ExecuteCallNode.java
new file mode 100644
index 00000000000..ca1651ecc40
--- /dev/null
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/callable/ExecuteCallNode.java
@@ -0,0 +1,66 @@
+package org.enso.interpreter.node.callable;
+
+import com.oracle.truffle.api.RootCallTarget;
+import com.oracle.truffle.api.dsl.Cached;
+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.function.Function;
+
+/**
+ * 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.
+ */
+public abstract class ExecuteCallNode extends Node {
+
+ /**
+ * Calls the function directly.
+ *
+ * This specialisation comes into play where the call target for the provided function is
+ * already cached. THis means that the call can be made quickly.
+ *
+ * @param function the function to execute
+ * @param arguments the arguments passed to {@code function} in the expected positional order
+ * @param cachedTarget the cached call target for {@code function}
+ * @param callNode the cached call node for {@code cachedTarget}
+ * @return the result of executing {@code function} on {@code arguments}
+ */
+ @Specialization(guards = "function.getCallTarget() == cachedTarget")
+ protected Object callDirect(
+ Function function,
+ Object[] arguments,
+ @Cached("function.getCallTarget()") RootCallTarget cachedTarget,
+ @Cached("create(cachedTarget)") DirectCallNode callNode) {
+ return callNode.call(Function.ArgumentsHelper.buildArguments(function, arguments));
+ }
+
+ /**
+ * Calls the function with a lookup.
+ *
+ * This specialisation is used in the case where there is no cached call target for the
+ * provided function. This is much slower and should, in general, be avoided.
+ *
+ * @param function the function to execute
+ * @param arguments the arguments passed to {@code function} in the expected positional order
+ * @param callNode the cached call node for making indirect calls
+ * @return the result of executing {@code function} on {@code arguments}
+ */
+ @Specialization(replaces = "callDirect")
+ protected Object callIndirect(
+ Function function, Object[] arguments, @Cached IndirectCallNode callNode) {
+ return callNode.call(
+ function.getCallTarget(), Function.ArgumentsHelper.buildArguments(function, arguments));
+ }
+
+ /**
+ * Executes the function call.
+ *
+ * @param function the function to execute
+ * @param arguments the arguments to be passed to {@code function}
+ * @return the result of executing {@code function} on {@code arguments}
+ */
+ public abstract Object executeCall(Object function, Object[] arguments);
+}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java
new file mode 100644
index 00000000000..932b3e1808e
--- /dev/null
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java
@@ -0,0 +1,122 @@
+package org.enso.interpreter.node.callable;
+
+import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
+import com.oracle.truffle.api.dsl.Fallback;
+import com.oracle.truffle.api.dsl.NodeChild;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.nodes.ExplodeLoop;
+import com.oracle.truffle.api.nodes.NodeInfo;
+import java.util.Arrays;
+import org.enso.interpreter.node.ExpressionNode;
+import org.enso.interpreter.node.callable.argument.sorter.ArgumentSorterNode;
+import org.enso.interpreter.node.callable.argument.sorter.ArgumentSorterNodeGen;
+import org.enso.interpreter.node.callable.dispatch.CallOptimiserNode;
+import org.enso.interpreter.node.callable.dispatch.SimpleCallOptimiserNode;
+import org.enso.interpreter.optimiser.tco.TailCallException;
+import org.enso.interpreter.runtime.callable.argument.CallArgument;
+import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
+import org.enso.interpreter.runtime.callable.atom.Atom;
+import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
+import org.enso.interpreter.runtime.callable.function.Function;
+import org.enso.interpreter.runtime.error.NotInvokableException;
+
+/**
+ * This node is responsible for organising callable calls so that they are ready to be made.
+ *
+ * It handles computing the values of the arguments to the callable, and also the sorting of
+ * those arguments into the correct positional order for the callable being called.
+ */
+@NodeInfo(shortName = "@", description = "Executes function")
+@NodeChild(value = "callable", type = ExpressionNode.class)
+public abstract class InvokeCallableNode extends ExpressionNode {
+ @Children
+ private @CompilationFinal(dimensions = 1) ExpressionNode[] argExpressions;
+
+ @Child private ArgumentSorterNode argumentSorter;
+ @Child private CallOptimiserNode callOptimiserNode;
+
+ /**
+ * Creates a new node for performing callable invocation.
+ *
+ * @param callArguments information on the arguments being passed to the {@link
+ * org.enso.interpreter.runtime.callable.Callable}
+ */
+ public InvokeCallableNode(CallArgument[] callArguments) {
+ this.argExpressions =
+ Arrays.stream(callArguments)
+ .map(CallArgument::getExpression)
+ .toArray(ExpressionNode[]::new);
+
+ CallArgumentInfo[] argSchema =
+ Arrays.stream(callArguments).map(CallArgumentInfo::new).toArray(CallArgumentInfo[]::new);
+
+ this.callOptimiserNode = new SimpleCallOptimiserNode();
+ this.argumentSorter = ArgumentSorterNodeGen.create(argSchema);
+ }
+
+ /**
+ * Evaluates the arguments being passed to the callable.
+ *
+ * @param frame the stack frame in which to execute
+ * @return the results of evaluating the function arguments
+ */
+ @ExplodeLoop
+ public Object[] evaluateArguments(VirtualFrame frame) {
+ Object[] computedArguments = new Object[this.argExpressions.length];
+
+ for (int i = 0; i < this.argExpressions.length; ++i) {
+ computedArguments[i] = this.argExpressions[i].executeGeneric(frame);
+ }
+
+ return computedArguments;
+ }
+
+ /**
+ * Invokes a function directly on the arguments contained in this node.
+ *
+ * @param frame the stack frame in which to execute
+ * @param callable the function to be executed
+ * @return the result of executing {@code callable} on the known arguments
+ */
+ @Specialization
+ public Object invokeFunction(VirtualFrame frame, Function callable) {
+ Object[] evaluatedArguments = evaluateArguments(frame);
+ Object[] sortedArguments = this.argumentSorter.execute(callable, evaluatedArguments);
+
+ if (this.isTail()) {
+ throw new TailCallException(callable, sortedArguments);
+ } else {
+ return this.callOptimiserNode.executeDispatch(callable, sortedArguments);
+ }
+ }
+
+ /**
+ * Invokes a constructor directly on the arguments contained in this node.
+ *
+ * @param frame the stack frame in which to execute
+ * @param callable the constructor to be executed
+ * @return the result of executing {@code callable} on the known arguments
+ */
+ @Specialization
+ public Atom invokeConstructor(VirtualFrame frame, AtomConstructor callable) {
+ Object[] evaluatedArguments = evaluateArguments(frame);
+ Object[] sortedArguments = this.argumentSorter.execute(callable, evaluatedArguments);
+ return callable.newInstance(sortedArguments);
+ }
+
+ /**
+ * A fallback that should never be called.
+ *
+ * If this is called, something has gone horribly wrong. It throws a {@link
+ * NotInvokableException} to signal this.
+ *
+ * @param frame the stack frame in which to execute
+ * @param callable the callable to be executed
+ * @return error
+ */
+ @Fallback
+ public Object invokeGeneric(VirtualFrame frame, Object callable) {
+ throw new NotInvokableException(callable, this);
+ }
+}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/callable/argument/ReadArgumentNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/callable/argument/ReadArgumentNode.java
new file mode 100644
index 00000000000..3c1b25428f1
--- /dev/null
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/callable/argument/ReadArgumentNode.java
@@ -0,0 +1,75 @@
+package org.enso.interpreter.node.callable.argument;
+
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.nodes.NodeInfo;
+import com.oracle.truffle.api.profiles.ConditionProfile;
+import org.enso.interpreter.node.ExpressionNode;
+import org.enso.interpreter.runtime.callable.argument.sentinel.DefaultedArgumentSentinel;
+import org.enso.interpreter.runtime.callable.function.Function;
+import org.enso.interpreter.runtime.callable.function.Function.ArgumentsHelper;
+
+/**
+ * Reads and evaluates the expression provided as a function argument. It handles the case where
+ * none is given and the default should be used instead.
+ */
+@NodeInfo(description = "Read function argument.")
+public class ReadArgumentNode extends ExpressionNode {
+ private final int index;
+ @Child ExpressionNode defaultValue;
+ private final ConditionProfile defaultingProfile = ConditionProfile.createCountingProfile();
+ private final ConditionProfile argAcquisitionProfile = ConditionProfile.createCountingProfile();
+
+ /**
+ * Creates a node to compute a function argument.
+ *
+ * @param position the argument's position at the definition site
+ * @param defaultValue the default value provided for that argument
+ */
+ public ReadArgumentNode(int position, ExpressionNode defaultValue) {
+ this.index = position;
+ this.defaultValue = defaultValue;
+ }
+
+ /**
+ * Computes the value of an argument in a function.
+ *
+ * This function also handles the defaulted case by checking for a {@code null} value at the
+ * argument's position. This works in conjunction with {@link
+ * org.enso.interpreter.runtime.callable.argument.CallArgumentInfo#reorderArguments(int[],
+ * Object[], int)}, which will place nulls in any position where an argument has not been applied.
+ *
+ * @param frame the stack frame to execute in
+ * @return the computed value of the argument at this position
+ */
+ @Override
+ public Object executeGeneric(VirtualFrame frame) {
+ if (defaultValue == null) {
+ return Function.ArgumentsHelper.getPositionalArguments(frame.getArguments())[index];
+ }
+
+ Object argument = null;
+
+ if (argAcquisitionProfile.profile(
+ index < ArgumentsHelper.getPositionalArguments(frame.getArguments()).length)) {
+ argument = Function.ArgumentsHelper.getPositionalArguments(frame.getArguments())[index];
+ }
+
+ // Note [Handling Argument Defaults]
+ if (defaultingProfile.profile(argument instanceof DefaultedArgumentSentinel || argument == null)) {
+ return defaultValue.executeGeneric(frame);
+ } else {
+ return argument;
+ }
+ }
+
+ /* Note [Handling Argument Defaults]
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * While it is tempting to handle defaulted arguments as a special case, we instead treat them as
+ * the absence of an argument for that position in the function definition. If none is provided,
+ * we can detect this using a sentinel, and hence evaluate the default value in its place.
+ *
+ * Any `null` value is treated as a usage of the default argument, so when this execution
+ * encounters a null value at the argument position, then it will instead execute the default
+ * value.
+ */
+}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/callable/argument/sorter/ArgumentSorterNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/callable/argument/sorter/ArgumentSorterNode.java
new file mode 100644
index 00000000000..c05fa39354b
--- /dev/null
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/callable/argument/sorter/ArgumentSorterNode.java
@@ -0,0 +1,137 @@
+package org.enso.interpreter.node.callable.argument.sorter;
+
+import com.oracle.truffle.api.CompilerDirectives;
+import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
+import com.oracle.truffle.api.dsl.Cached;
+import com.oracle.truffle.api.dsl.Fallback;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.nodes.ExplodeLoop;
+import com.oracle.truffle.api.nodes.NodeInfo;
+import java.util.Arrays;
+import org.enso.interpreter.node.BaseNode;
+import org.enso.interpreter.runtime.callable.Callable;
+import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
+import org.enso.interpreter.runtime.error.NotInvokableException;
+
+/**
+ * This class represents the protocol for remapping the arguments provided at a call site into the
+ * positional order expected by the definition of the {@link Callable}.
+ */
+@NodeInfo(shortName = "ArgumentSorter")
+public abstract class ArgumentSorterNode extends BaseNode {
+ private @CompilationFinal(dimensions = 1) CallArgumentInfo[] schema;
+ private @CompilationFinal boolean isFullyPositional;
+
+ /**
+ * Creates a node that performs the argument organisation for the provided schema.
+ *
+ * @param schema information about the call arguments in positional order
+ */
+ public ArgumentSorterNode(CallArgumentInfo[] schema) {
+ this.schema = schema;
+ this.isFullyPositional = Arrays.stream(schema).allMatch(CallArgumentInfo::isPositional);
+ }
+
+ /**
+ * Generates the argument mapping in the fully positional case.
+ *
+ * This specialisation is executed when all of the arguments provided at the call-site are
+ * specified in a purely positional fashion (no ignores and no named argument applications). This
+ * is intended to be a very quick path.
+ *
+ * @param callable the callable to sort arguments for
+ * @param arguments the arguments being passed to {@code callable}
+ * @return the provided {@code} arguments in the order expected by {@code callable}
+ */
+ @Specialization(guards = "isFullyPositional()")
+ public Object[] invokePositional(Object callable, Object[] arguments) {
+ CompilerDirectives.ensureVirtualizedHere(arguments);
+ return arguments;
+ }
+
+ /**
+ * Generates the argument mapping where it has already been computed.
+ *
+ * This specialisation is executed in the cases where the interpreter has already computed the
+ * mapping necessary to reorder call-stite arguments into the order expected by the definition
+ * site. It is also a fast path.
+ *
+ * This specialisation can only execute when the {@link Callable} provided to the method
+ * matches with the one stored in the cached argument sorter object.
+ *
+ * @param callable the callable to sort arguments for
+ * @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
+ * @return the provided {@code arguments} in the order expected by {@code callable}
+ */
+ @Specialization(guards = "mappingNode.hasSameCallable(callable)")
+ @ExplodeLoop
+ public Object[] invokeCached(
+ Callable callable,
+ Object[] arguments,
+ @Cached("create(callable, getSchema())") CachedArgumentSorterNode mappingNode) {
+ return mappingNode.execute(arguments, callable.getArgs().length);
+ }
+
+ /**
+ * Generates the argument mapping freshly.
+ *
+ * This specialisation is executed only when we cache miss, and will compute the argument
+ * mapping freshly each time.
+ *
+ * @param callable the callable to sort arguments for
+ * @param arguments the arguments being passed to {@code callable}
+ * @param mappingNode a cached node that computes argument mappings freshly each time
+ * @return the provided {@code arguments} in the order expected by {@code callable}
+ */
+ @Specialization
+ public Object[] invokeUncached(
+ Callable callable,
+ Object[] arguments,
+ @Cached("create(getSchema())") UncachedArgumentSorterNode mappingNode) {
+ return mappingNode.execute(callable, arguments, callable.getArgs().length);
+ }
+
+ /**
+ * A fallback that should never be called.
+ *
+ * The only cases in which this specialisation can be called are system-wide error conditions,
+ * and so we stop with a {@link NotInvokableException}.
+ *
+ * @param callable the callable to sort arguments for
+ * @param arguments the arguments being passed to {@code callable}
+ * @return error
+ */
+ @Fallback
+ public Object[] invokeGeneric(Object callable, Object[] arguments) {
+ throw new NotInvokableException(callable, this);
+ }
+
+ /**
+ * Executes the {@link ArgumentSorterNode} to reorder the arguments.
+ *
+ * @param callable the callable to sort arguments for
+ * @param arguments the arguments being passed to {@code callable}
+ * @return the provided {@code arguments} in the order expected by {@code callable}
+ */
+ public abstract Object[] execute(Object callable, Object[] arguments);
+
+ /**
+ * Gets the schema for use in Truffle DSL guards.
+ *
+ * @return the argument schema
+ */
+ CallArgumentInfo[] getSchema() {
+ return schema;
+ }
+
+ /**
+ * Checks if the argument schema is fully positional for use in Truffle DLS guards.
+ *
+ * @return {@code true} if the arguments are all positional, otherwise {@false}
+ */
+ boolean isFullyPositional() {
+ return isFullyPositional;
+ }
+}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/callable/argument/sorter/CachedArgumentSorterNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/callable/argument/sorter/CachedArgumentSorterNode.java
new file mode 100644
index 00000000000..1a645f7060c
--- /dev/null
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/callable/argument/sorter/CachedArgumentSorterNode.java
@@ -0,0 +1,80 @@
+package org.enso.interpreter.node.callable.argument.sorter;
+
+import com.oracle.truffle.api.CompilerAsserts;
+import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
+import com.oracle.truffle.api.nodes.ExplodeLoop;
+import com.oracle.truffle.api.nodes.NodeInfo;
+import com.oracle.truffle.api.profiles.ConditionProfile;
+import org.enso.interpreter.node.BaseNode;
+import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
+import org.enso.interpreter.runtime.callable.function.Function;
+import org.enso.interpreter.runtime.type.TypesGen;
+
+/**
+ * This class handles the case where a mapping for reordering arguments to a given callable has
+ * already been computed.
+ */
+@NodeInfo(shortName = "CachedArgumentSorter")
+public class CachedArgumentSorterNode extends BaseNode {
+ private @CompilationFinal Object callable;
+ private @CompilationFinal(dimensions = 1) int[] mapping;
+ private final ConditionProfile otherIsAtomCons = ConditionProfile.createCountingProfile();
+ private final ConditionProfile otherIsFunction = ConditionProfile.createCountingProfile();
+ private @CompilationFinal boolean callableIsFunction;
+
+ /**
+ * Creates a node that generates and then caches the argument mapping.
+ *
+ * @param callable the callable to sort arguments for
+ * @param schema information on the calling arguments
+ */
+ public CachedArgumentSorterNode(Object callable, CallArgumentInfo[] schema) {
+ this.callable = callable;
+ this.mapping = CallArgumentInfo.generateArgMapping(callable, schema);
+
+ this.callableIsFunction = TypesGen.isFunction(callable);
+ CompilerAsserts.compilationConstant(callableIsFunction);
+ }
+
+ /**
+ * Creates a node that generates and then caches the argument mapping.
+ *
+ * @param callable the callable to sort arguments for
+ * @param schema information on the calling arguments
+ * @return a sorter node for the arguments in {@code schema} being passed to {@code callable}
+ */
+ public static CachedArgumentSorterNode create(Object callable, CallArgumentInfo[] schema) {
+ return new CachedArgumentSorterNode(callable, schema);
+ }
+
+ /**
+ * Reorders the provided arguments into the necessary order for the cached callable.
+ *
+ * @param arguments the arguments to reorder
+ * @param numArgsDefinedForCallable the number of arguments that the cached callable was defined
+ * for
+ * @return the provided {@code arguments} in the order expected by the cached {@link
+ * org.enso.interpreter.runtime.callable.Callable}
+ */
+ @ExplodeLoop
+ public Object[] execute(Object[] arguments, int numArgsDefinedForCallable) {
+ return CallArgumentInfo.reorderArguments(this.mapping, arguments, numArgsDefinedForCallable);
+ }
+
+ /**
+ * Determines if the provided callable is the same as the cached one.
+ *
+ * @param other the callable to check for equality
+ * @return {@code true} if {@code other} matches the cached callable, otherwise {@code false}
+ */
+ public boolean hasSameCallable(Object other) {
+ if (otherIsAtomCons.profile(TypesGen.isAtomConstructor(other))) {
+ return this.callable == other;
+ } else if (this.callableIsFunction) {
+ if (otherIsFunction.profile(TypesGen.isFunction(other))) {
+ return ((Function) this.callable).getCallTarget() == ((Function) other).getCallTarget();
+ }
+ }
+ return false;
+ }
+}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/callable/argument/sorter/UncachedArgumentSorterNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/callable/argument/sorter/UncachedArgumentSorterNode.java
new file mode 100644
index 00000000000..6cb3d80861b
--- /dev/null
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/callable/argument/sorter/UncachedArgumentSorterNode.java
@@ -0,0 +1,59 @@
+package org.enso.interpreter.node.callable.argument.sorter;
+
+import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
+import com.oracle.truffle.api.nodes.NodeInfo;
+import com.oracle.truffle.api.profiles.ConditionProfile;
+import org.enso.interpreter.node.BaseNode;
+import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
+import org.enso.interpreter.runtime.callable.function.Function;
+import org.enso.interpreter.runtime.error.NotInvokableException;
+import org.enso.interpreter.runtime.type.TypesGen;
+
+/**
+ * This class handles the case where we have no cached mapping for a given callable's arguments, and
+ * will compute it on the fly.
+ */
+@NodeInfo(shortName = "UncachedArgumentSorter")
+public class UncachedArgumentSorterNode extends BaseNode {
+ private @CompilationFinal(dimensions = 1) CallArgumentInfo[] schema;
+ private final ConditionProfile isCallableProfile = ConditionProfile.createCountingProfile();
+
+ /**
+ * Creates a node to sort arguments on the fly.
+ *
+ * @param schema information on the calling arguments
+ */
+ public UncachedArgumentSorterNode(CallArgumentInfo[] schema) {
+ this.schema = schema;
+ }
+
+ /**
+ * Creates a node to sort arguments on the fly.
+ *
+ * @param schema information on the calling arguments
+ * @return a sorter node for the arguments in {@code schema} for an unknown callable
+ */
+ public static UncachedArgumentSorterNode create(CallArgumentInfo[] schema) {
+ return new UncachedArgumentSorterNode(schema);
+ }
+
+ /**
+ * Reorders the provided {@code arguments} for the given {@link
+ * org.enso.interpreter.runtime.callable.Callable}.
+ *
+ * @param callable the callable to reorder arguments for
+ * @param arguments the arguments passed to {@code callable}
+ * @param numArgsDefinedForCallable the number of arguments defined on {@code callable}
+ * @return the provided {@code arguments} in the order expected by the cached {@link
+ * org.enso.interpreter.runtime.callable.Callable}
+ */
+ public Object[] execute(Object callable, Object[] arguments, int numArgsDefinedForCallable) {
+ if (isCallableProfile.profile(TypesGen.isCallable(callable))) {
+ Function actualCallable = (Function) callable;
+ int[] order = CallArgumentInfo.generateArgMapping(actualCallable, this.schema);
+ return CallArgumentInfo.reorderArguments(order, arguments, numArgsDefinedForCallable);
+ } else {
+ throw new NotInvokableException(callable, this);
+ }
+ }
+}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/callable/dispatch/CallOptimiserNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/callable/dispatch/CallOptimiserNode.java
new file mode 100644
index 00000000000..f7165b53809
--- /dev/null
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/callable/dispatch/CallOptimiserNode.java
@@ -0,0 +1,19 @@
+package org.enso.interpreter.node.callable.dispatch;
+
+import com.oracle.truffle.api.nodes.Node;
+
+/**
+ * This node handles optimising calls. It performs detection based on the kind of call being made,
+ * and then executes the call in the most efficient manner possible for it.
+ */
+public abstract class CallOptimiserNode extends Node {
+
+ /**
+ * Calls the provided {@code callable} using the provided {@code arguments}.
+ *
+ * @param callable the callable to execute
+ * @param arguments the arguments to {@code callable}
+ * @return the result of executing {@code callable} using {@code arguments}
+ */
+ public abstract Object executeDispatch(Object callable, Object[] arguments);
+}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/callable/dispatch/LoopingCallOptimiserNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/callable/dispatch/LoopingCallOptimiserNode.java
new file mode 100644
index 00000000000..32426788f58
--- /dev/null
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/callable/dispatch/LoopingCallOptimiserNode.java
@@ -0,0 +1,139 @@
+package org.enso.interpreter.node.callable.dispatch;
+
+import com.oracle.truffle.api.Truffle;
+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.frame.FrameUtil;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.nodes.LoopNode;
+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.optimiser.tco.TailCallException;
+
+/**
+ * A version of {@link CallOptimiserNode} that is fully prepared to handle tail calls. Tail calls
+ * are handled through exceptions – whenever a tail-recursive call would be executed, an exception
+ * containing the next unevaluated call and arguments is thrown instead.
+ *
+ * This node executes the function in a loop, following all the continuations, until obtaining
+ * the actual return value.
+ *
+ * @see TailCallException
+ */
+public class LoopingCallOptimiserNode extends CallOptimiserNode {
+ private final FrameDescriptor loopFrameDescriptor = new FrameDescriptor();
+ @Child private LoopNode loopNode;
+
+ /** Creates a new node for computing tail-call-optimised functions. */
+ public LoopingCallOptimiserNode() {
+ loopNode = Truffle.getRuntime().createLoopNode(new RepeatedCallNode(loopFrameDescriptor));
+ }
+
+ /**
+ * Calls the provided {@code callable} using the provided {@code arguments}.
+ *
+ * @param callable the callable to execute
+ * @param arguments the arguments to {@code callable}
+ * @return the result of executing {@code callable} using {@code arguments}
+ */
+ @Override
+ public Object executeDispatch(Object callable, Object[] arguments) {
+ VirtualFrame frame = Truffle.getRuntime().createVirtualFrame(null, loopFrameDescriptor);
+ ((RepeatedCallNode) loopNode.getRepeatingNode()).setNextCall(frame, callable, arguments);
+ loopNode.executeLoop(frame);
+
+ return ((RepeatedCallNode) loopNode.getRepeatingNode()).getResult(frame);
+ }
+
+ /**
+ * This node handles the actually looping computation computed in a tail-recursive function. It
+ * will execute the computation repeatedly and is intended to be used in the context of a Truffle
+ * {@link RepeatingNode}.
+ */
+ public static final class RepeatedCallNode extends Node implements RepeatingNode {
+ private final FrameSlot resultSlot;
+ private final FrameSlot functionSlot;
+ private final FrameSlot argsSlot;
+ @Child private ExecuteCallNode dispatchNode;
+
+ /**
+ * Creates a new node used for repeating a call.
+ *
+ * @param descriptor a handle to the slots of the current stack frame
+ */
+ public RepeatedCallNode(FrameDescriptor descriptor) {
+ functionSlot = descriptor.findOrAddFrameSlot(" It has no direct return value and instead uses a {@link BranchSelectedException} to signal
+ * the correct result back to the parent of the case expression.
+ *
+ * @param frame the stack frame in which to execute
+ * @param target the constructor to destructure
+ * @throws UnexpectedResultException when the result of desctructuring {@code target} can't be
+ * represented as a value of the expected return type
+ */
public void execute(VirtualFrame frame, Atom target) throws UnexpectedResultException {
AtomConstructor matcherVal = matcher.executeAtomConstructor(frame);
if (profile.profile(matcherVal == target.getConstructor())) {
Function function = branch.executeFunction(frame);
- throw new BranchSelectedException(callNode.executeCall(function, target.getFields()));
+ throw new BranchSelectedException(executeCallNode.executeCall(function, target.getFields()));
}
}
}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/DefaultFallbackNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/DefaultFallbackNode.java
index 9c16731300f..2a2dc5be692 100644
--- a/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/DefaultFallbackNode.java
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/DefaultFallbackNode.java
@@ -1,10 +1,21 @@
package org.enso.interpreter.node.controlflow;
import com.oracle.truffle.api.frame.VirtualFrame;
-import org.enso.interpreter.runtime.Atom;
-import org.enso.interpreter.runtime.errors.InexhaustivePatternMatchException;
+import org.enso.interpreter.runtime.callable.atom.Atom;
+import org.enso.interpreter.runtime.error.InexhaustivePatternMatchException;
+/**
+ * This node is used when there is no explicit default case provided by the user for a pattern
+ * match. It is used to signal inexhaustivity.
+ */
public class DefaultFallbackNode extends CaseNode {
+
+ /**
+ * Executes the case expression's error case.
+ *
+ * @param frame the stack frame in which to execute
+ * @param target the constructor to destructure
+ */
@Override
public void execute(VirtualFrame frame, Atom target) {
throw new InexhaustivePatternMatchException(this.getParent());
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/FallbackNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/FallbackNode.java
index 77bd20adc8f..feb349bf8b3 100644
--- a/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/FallbackNode.java
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/FallbackNode.java
@@ -3,22 +3,42 @@ package org.enso.interpreter.node.controlflow;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import org.enso.interpreter.node.ExpressionNode;
-import org.enso.interpreter.node.function.CallNode;
-import org.enso.interpreter.node.function.CallNodeGen;
-import org.enso.interpreter.runtime.Atom;
-import org.enso.interpreter.runtime.Function;
+import org.enso.interpreter.node.callable.ExecuteCallNode;
+import org.enso.interpreter.node.callable.ExecuteCallNodeGen;
+import org.enso.interpreter.runtime.callable.atom.Atom;
+import org.enso.interpreter.runtime.callable.function.Function;
+/**
+ * This node represents an explicit catch-call case in a pattern match, as provided by the user. It
+ * executes the catch-all case code.
+ */
public class FallbackNode extends CaseNode {
@Child private ExpressionNode functionNode;
- @Child private CallNode callNode = CallNodeGen.create();
+ @Child private ExecuteCallNode executeCallNode = ExecuteCallNodeGen.create();
+ /**
+ * Creates a node to handle the case catch-call.
+ *
+ * @param functionNode the function to execute in this case
+ */
public FallbackNode(ExpressionNode functionNode) {
this.functionNode = functionNode;
}
+ /**
+ * Executes the case expression catch-all case.
+ *
+ * It has no direct return value and instead uses a {@link BranchSelectedException} to signal
+ * the correct result back to the parent of the case expression.
+ *
+ * @param frame the stack frame in which to execute
+ * @param target the constructor to destructure
+ * @throws UnexpectedResultException when the result of desctructuring {@code target} can't be
+ * represented as a value of the expected return type
+ */
@Override
public void execute(VirtualFrame frame, Atom target) throws UnexpectedResultException {
Function function = functionNode.executeFunction(frame);
- throw new BranchSelectedException(callNode.executeCall(function, new Object[0]));
+ throw new BranchSelectedException(executeCallNode.executeCall(function, new Object[0]));
}
}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/IfZeroNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/IfZeroNode.java
index 943f24ea00d..b95f9558c2e 100644
--- a/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/IfZeroNode.java
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/IfZeroNode.java
@@ -5,8 +5,12 @@ import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import com.oracle.truffle.api.profiles.ConditionProfile;
import org.enso.interpreter.node.ExpressionNode;
-import org.enso.interpreter.runtime.errors.TypeError;
+import org.enso.interpreter.runtime.error.TypeError;
+/**
+ * This node represents a very simple form of the if-then-else expression, specialised to the case
+ * of checking whether its input is equal to zero.
+ */
@NodeInfo(shortName = "if_then_else", description = "if arg0 = 0 then arg1 else arg2")
public class IfZeroNode extends ExpressionNode {
private final ConditionProfile conditionProf = ConditionProfile.createCountingProfile();
@@ -14,12 +18,26 @@ public class IfZeroNode extends ExpressionNode {
@Child private ExpressionNode ifTrue;
@Child private ExpressionNode ifFalse;
+ /**
+ * Creates a new conditional expression node.
+ *
+ * @param condition the condition to execute and check for equality with zero
+ * @param ifTrue the code to execute if {@code condition} is true
+ * @param ifFalse the code to execute if {@code condition} is false
+ */
public IfZeroNode(ExpressionNode condition, ExpressionNode ifTrue, ExpressionNode ifFalse) {
this.condition = condition;
this.ifTrue = ifTrue;
this.ifFalse = ifFalse;
}
+ /**
+ * Executes the conditional expression.
+ *
+ * @param frame the stack frame for execution
+ * @return the result of the {@code isTrue} branch if the condition is {@code true}, otherwise the
+ * result of the {@code isFalse} branch
+ */
@Override
public Object executeGeneric(VirtualFrame frame) {
if (conditionProf.profile(executeCondition(frame) == 0)) {
@@ -29,6 +47,12 @@ public class IfZeroNode extends ExpressionNode {
}
}
+ /**
+ * Executes the condition to be checked.
+ *
+ * @param frame the stack frame for execution
+ * @return the result of executing the condition
+ */
private long executeCondition(VirtualFrame frame) {
try {
return condition.executeLong(frame);
@@ -37,9 +61,14 @@ public class IfZeroNode extends ExpressionNode {
}
}
+ /**
+ * Sets whether the conditional is tail-recursive or not.
+ *
+ * @param isTail whether or not the conditional is tail recursive
+ */
@Override
- public void markTail() {
- ifTrue.markTail();
- ifFalse.markTail();
+ public void setTail(boolean isTail) {
+ ifTrue.setTail(isTail);
+ ifFalse.setTail(isTail);
}
}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/MatchNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/MatchNode.java
index b5465db6e0a..ec3d21b2838 100644
--- a/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/MatchNode.java
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/MatchNode.java
@@ -6,28 +6,49 @@ import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import com.oracle.truffle.api.profiles.BranchProfile;
import org.enso.interpreter.node.ExpressionNode;
-import org.enso.interpreter.runtime.Atom;
-import org.enso.interpreter.runtime.errors.TypeError;
+import org.enso.interpreter.runtime.callable.atom.Atom;
+import org.enso.interpreter.runtime.error.TypeError;
+/** A node representing a pattern match on an Atom. */
public class MatchNode extends ExpressionNode {
@Child private ExpressionNode target;
@Children private final CaseNode[] cases;
@Child private CaseNode fallback;
private final BranchProfile typeErrorProfile = BranchProfile.create();
+ /**
+ * Creates a node that pattern matches on an Atom.
+ *
+ * @param target the atom to pattern match on
+ * @param cases the branches of the pattern match
+ * @param fallback the fallback case of the pattern match
+ */
public MatchNode(ExpressionNode target, CaseNode[] cases, CaseNode fallback) {
this.target = target;
this.cases = cases;
this.fallback = fallback;
}
- @Override public void markTail() {
- for (CaseNode caseNode: cases) {
- caseNode.markTail();
+ /**
+ * Sets whether or not the pattern match is tail-recursive.
+ *
+ * @param isTail whether or not the expression is tail-recursive
+ */
+ @Override
+ @ExplodeLoop
+ public void setTail(boolean isTail) {
+ for (CaseNode caseNode : cases) {
+ caseNode.setTail(isTail);
}
- fallback.markTail();
+ fallback.setTail(isTail);
}
+ /**
+ * Executes the pattern match.
+ *
+ * @param frame the stack frame for execution
+ * @return the result of the pattern match
+ */
@ExplodeLoop
@Override
public Object executeGeneric(VirtualFrame frame) {
@@ -39,11 +60,27 @@ public class MatchNode extends ExpressionNode {
fallback.execute(frame, atom);
CompilerDirectives.transferToInterpreter();
throw new RuntimeException("Impossible behavior.");
+
} catch (BranchSelectedException e) {
+ // Note [Branch Selection Control Flow]
return e.getResult();
+
} catch (UnexpectedResultException e) {
typeErrorProfile.enter();
throw new TypeError("Expected an Atom.", this);
}
}
+
+ /* Note [Branch Selection Control Flow]
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * Truffle provides no easy way to return control flow from multiple paths. This is entirely due
+ * to Java's (current) lack of support for case-expressions.
+ *
+ * As a result, this implementation resorts to using an exception to short-circuit evaluation on
+ * a successful match. This short-circuiting also allows us to hand the result of evaluation back
+ * out to the caller (here) wrapped in the exception.
+ *
+ * The main alternative to this was desugaring to a nested-if, which would've been significantly
+ * harder to maintain, and also resulted in significantly higher code complexity.
+ */
}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/expression/builtin/PrintNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/expression/builtin/PrintNode.java
index 441f5e7e95d..e2afcb33a2f 100644
--- a/Interpreter/src/main/java/org/enso/interpreter/node/expression/builtin/PrintNode.java
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/expression/builtin/PrintNode.java
@@ -4,17 +4,28 @@ import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.node.ExpressionNode;
-import org.enso.interpreter.runtime.AtomConstructor;
+import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
+/** This node allows for printing the result of an arbitrary expression to standard output. */
@NodeInfo(shortName = "print", description = "Prints the value of child expression.")
public final class PrintNode extends ExpressionNode {
-
@Child private ExpressionNode expression;
+ /**
+ * Creates a node that prints the result of its expression.
+ *
+ * @param expression the expression to print the result of
+ */
public PrintNode(ExpressionNode expression) {
this.expression = expression;
}
+ /**
+ * Executes the print node.
+ *
+ * @param frame the stack frame for execution
+ * @return unit {@link AtomConstructor#UNIT unit} type
+ */
@Override
public Object executeGeneric(VirtualFrame frame) {
doPrint(expression.executeGeneric(frame));
@@ -22,6 +33,11 @@ public final class PrintNode extends ExpressionNode {
return AtomConstructor.UNIT.newInstance();
}
+ /**
+ * Prints the provided value to standard output.
+ *
+ * @param object the value to print
+ */
@CompilerDirectives.TruffleBoundary
private void doPrint(Object object) {
System.out.println(object);
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/expression/constant/ConstructorNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/expression/constant/ConstructorNode.java
index b220c50cfe7..45a782f1e5e 100644
--- a/Interpreter/src/main/java/org/enso/interpreter/node/expression/constant/ConstructorNode.java
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/expression/constant/ConstructorNode.java
@@ -2,20 +2,38 @@ package org.enso.interpreter.node.expression.constant;
import com.oracle.truffle.api.frame.VirtualFrame;
import org.enso.interpreter.node.ExpressionNode;
-import org.enso.interpreter.runtime.AtomConstructor;
+import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
+/** Represents a type constructor definition. */
public class ConstructorNode extends ExpressionNode {
private final AtomConstructor constructor;
+ /**
+ * Creates a new type constructor definition.
+ *
+ * @param constructor the constructor to define
+ */
public ConstructorNode(AtomConstructor constructor) {
this.constructor = constructor;
}
+ /**
+ * Executes the type constructor definition.
+ *
+ * @param frame the frame to execute in
+ * @return the constructor of the type defined
+ */
@Override
public AtomConstructor executeAtomConstructor(VirtualFrame frame) {
return constructor;
}
+ /**
+ * Executes the type constructor definition.
+ *
+ * @param frame the frame to execute in
+ * @return the constructor of the type defined
+ */
@Override
public Object executeGeneric(VirtualFrame frame) {
return constructor;
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/expression/literal/IntegerLiteralNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/expression/literal/IntegerLiteralNode.java
index 3b8d59bbb2d..2c77f373590 100644
--- a/Interpreter/src/main/java/org/enso/interpreter/node/expression/literal/IntegerLiteralNode.java
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/expression/literal/IntegerLiteralNode.java
@@ -3,14 +3,26 @@ package org.enso.interpreter.node.expression.literal;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
+/** A representation of integer literals in Enso. */
@NodeInfo(shortName = "IntegerLiteral")
public final class IntegerLiteralNode extends LiteralNode {
private final long value;
+ /**
+ * Creates a new integer literal.
+ *
+ * @param value the integer value of the literal
+ */
public IntegerLiteralNode(long value) {
this.value = value;
}
+ /**
+ * Gets the value of the literal.
+ *
+ * @param frame the stack frame for execution
+ * @return the value of the integer literal
+ */
@Override
public Object executeGeneric(VirtualFrame frame) {
return this.value;
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/expression/literal/LiteralNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/expression/literal/LiteralNode.java
index 6e8edd36320..1de87ea38f6 100644
--- a/Interpreter/src/main/java/org/enso/interpreter/node/expression/literal/LiteralNode.java
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/expression/literal/LiteralNode.java
@@ -3,5 +3,10 @@ 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/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/AddOperatorNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/AddOperatorNode.java
index e747ede50f9..48f39da784e 100644
--- a/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/AddOperatorNode.java
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/AddOperatorNode.java
@@ -3,8 +3,19 @@ package org.enso.interpreter.node.expression.operator;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.NodeInfo;
+/**
+ * The addition operator for Enso.
+ */
@NodeInfo(shortName = "+")
public abstract class AddOperatorNode extends BinaryOperatorNode {
+
+ /**
+ * Adds two numbers together.
+ *
+ * @param left the first summand
+ * @param right the second summand
+ * @return the result of adding {@code left} and {@code right}
+ */
@Specialization
protected long add(long left, long right) {
return left + right;
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/BinaryOperatorNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/BinaryOperatorNode.java
index e9bf503e5e3..a493f7fe142 100644
--- a/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/BinaryOperatorNode.java
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/BinaryOperatorNode.java
@@ -4,6 +4,9 @@ import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.node.ExpressionNode;
+/**
+ * A base class for all binary operators in Enso.
+ */
@NodeInfo(
shortName = "BinaryOperator",
description = "A representation of generic binary operators.")
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/DivideOperatorNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/DivideOperatorNode.java
index 48e410ea2ae..a2983e7895f 100644
--- a/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/DivideOperatorNode.java
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/DivideOperatorNode.java
@@ -3,9 +3,17 @@ package org.enso.interpreter.node.expression.operator;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.NodeInfo;
+/** The division operator for Enso. */
@NodeInfo(shortName = "/")
public abstract class DivideOperatorNode extends BinaryOperatorNode {
+ /**
+ * Performs integer division of two numbers.
+ *
+ * @param left the dividend
+ * @param right the divisor
+ * @return the result of dividing {@code left} by {@code right}
+ */
@Specialization
protected long divide(long left, long right) {
return left / right;
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/ModOperatorNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/ModOperatorNode.java
index 4dcf992e89f..205c11fb72e 100644
--- a/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/ModOperatorNode.java
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/ModOperatorNode.java
@@ -3,9 +3,19 @@ package org.enso.interpreter.node.expression.operator;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.NodeInfo;
+/**
+ * The modulo operator for Enso.
+ */
@NodeInfo(shortName = "%")
public abstract class ModOperatorNode extends BinaryOperatorNode {
+ /**
+ * Calculates the modulus of two numbers.
+ *
+ * @param left the dividend
+ * @param right the divisor
+ * @return the result of calculating {@code left} modulo {@code right}
+ */
@Specialization
protected long mod(long left, long right) {
return left % right;
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/MultiplyOperatorNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/MultiplyOperatorNode.java
index 64c2bfc26d8..28545e9bf75 100644
--- a/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/MultiplyOperatorNode.java
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/MultiplyOperatorNode.java
@@ -3,9 +3,19 @@ package org.enso.interpreter.node.expression.operator;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.NodeInfo;
+/**
+ * The multiplication operator for Enso.
+ */
@NodeInfo(shortName = "*")
public abstract class MultiplyOperatorNode extends BinaryOperatorNode {
+ /**
+ * Multiplies two numbers together.
+ *
+ * @param left the first factor
+ * @param right the second factor
+ * @return the result of {@code left} multiplied by {@code right}
+ */
@Specialization
protected long multiply(long left, long right) {
return left * right;
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/SubtractOperatorNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/SubtractOperatorNode.java
index e038a2f3b1d..f953830fac1 100644
--- a/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/SubtractOperatorNode.java
+++ b/Interpreter/src/main/java/org/enso/interpreter/node/expression/operator/SubtractOperatorNode.java
@@ -3,9 +3,19 @@ package org.enso.interpreter.node.expression.operator;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.NodeInfo;
+/**
+ * The subtraction operator for Enso.
+ */
@NodeInfo(shortName = "-")
public abstract class SubtractOperatorNode extends BinaryOperatorNode {
+ /**
+ * Subtracts one number from another.
+ *
+ * @param left the minuend
+ * @param right the subtrahend
+ * @return the result of subtracting {@code right} from {@code left}
+ */
@Specialization
protected long subtract(long left, long right) {
return left - right;
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/function/CallNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/function/CallNode.java
deleted file mode 100644
index f71becfe05e..00000000000
--- a/Interpreter/src/main/java/org/enso/interpreter/node/function/CallNode.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.enso.interpreter.node.function;
-
-import com.oracle.truffle.api.RootCallTarget;
-import com.oracle.truffle.api.dsl.Cached;
-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.Function;
-
-public abstract class CallNode extends Node {
- @Specialization(guards = "function.getCallTarget() == cachedTarget")
- protected Object callDirect(
- Function function,
- Object[] arguments,
- @Cached("function.getCallTarget()") RootCallTarget cachedTarget,
- @Cached("create(cachedTarget)") DirectCallNode callNode) {
- return callNode.call(Function.ArgumentsHelper.buildArguments(function, arguments));
- }
-
- @Specialization(replaces = "callDirect")
- protected Object callIndirect(
- Function function, Object[] arguments, @Cached IndirectCallNode callNode) {
- return callNode.call(
- function.getCallTarget(), Function.ArgumentsHelper.buildArguments(function, arguments));
- }
-
- public abstract Object executeCall(Object receiver, Object[] arguments);
-}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/function/CreateFunctionNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/function/CreateFunctionNode.java
deleted file mode 100644
index 0d320c7ed71..00000000000
--- a/Interpreter/src/main/java/org/enso/interpreter/node/function/CreateFunctionNode.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.enso.interpreter.node.function;
-
-import com.oracle.truffle.api.RootCallTarget;
-import com.oracle.truffle.api.frame.MaterializedFrame;
-import com.oracle.truffle.api.frame.VirtualFrame;
-import org.enso.interpreter.node.EnsoRootNode;
-import org.enso.interpreter.node.ExpressionNode;
-import org.enso.interpreter.runtime.Function;
-
-public class CreateFunctionNode extends ExpressionNode {
- private final RootCallTarget callTarget;
-
- public CreateFunctionNode(RootCallTarget callTarget) {
- this.callTarget = callTarget;
- }
-
- @Override
- public void markTail() {
- ((EnsoRootNode) callTarget.getRootNode()).markTail();
- }
-
- @Override
- public Function executeFunction(VirtualFrame frame) {
- MaterializedFrame scope = frame.materialize();
- return new Function(callTarget, scope);
- }
-
- @Override
- public Object executeGeneric(VirtualFrame frame) {
- return executeFunction(frame);
- }
-}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/function/DispatchNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/function/DispatchNode.java
deleted file mode 100644
index c32743f75a7..00000000000
--- a/Interpreter/src/main/java/org/enso/interpreter/node/function/DispatchNode.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.enso.interpreter.node.function;
-
-import com.oracle.truffle.api.nodes.Node;
-
-public abstract class DispatchNode extends Node {
- public abstract Object executeDispatch(Object receiver, Object[] arguments);
-}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/function/FunctionBodyNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/function/FunctionBodyNode.java
deleted file mode 100644
index 3b2bcc80336..00000000000
--- a/Interpreter/src/main/java/org/enso/interpreter/node/function/FunctionBodyNode.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.enso.interpreter.node.function;
-
-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.ExpressionNode;
-
-@NodeInfo(shortName = "{}")
-public class FunctionBodyNode extends ExpressionNode {
-
- @Children private final ExpressionNode[] statements;
- @Child private ExpressionNode returnExpr;
-
- public FunctionBodyNode(ExpressionNode[] statements, ExpressionNode returnExpr) {
- this.statements = statements;
- this.returnExpr = returnExpr;
- }
-
- @Override
- public void markTail() {
- returnExpr.markTail();
- }
-
- @Override
- @ExplodeLoop
- public Object executeGeneric(VirtualFrame frame) {
- for (ExpressionNode statement : statements) {
- statement.executeVoid(frame);
- }
- return returnExpr.executeGeneric(frame);
- }
-}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/function/InvokeNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/function/InvokeNode.java
deleted file mode 100644
index 3197adb296c..00000000000
--- a/Interpreter/src/main/java/org/enso/interpreter/node/function/InvokeNode.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.enso.interpreter.node.function;
-
-import com.oracle.truffle.api.CompilerAsserts;
-import com.oracle.truffle.api.dsl.Fallback;
-import com.oracle.truffle.api.dsl.NodeChild;
-import com.oracle.truffle.api.dsl.Specialization;
-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.ExpressionNode;
-import org.enso.interpreter.optimiser.TailCallException;
-import org.enso.interpreter.runtime.*;
-import org.enso.interpreter.runtime.errors.NotInvokableException;
-import org.enso.interpreter.runtime.errors.TypeError;
-
-@NodeInfo(shortName = "@", description = "Executes function")
-@NodeChild("target")
-public abstract class InvokeNode extends ExpressionNode {
- @Children private final ExpressionNode[] arguments;
- @Child private DispatchNode dispatchNode;
-
- public InvokeNode(ExpressionNode[] arguments) {
- this.arguments = arguments;
- this.dispatchNode = new SimpleDispatchNode();
- }
-
- @ExplodeLoop
- public Object[] computeArguments(VirtualFrame frame) {
- Object[] positionalArguments = new Object[arguments.length];
- for (int i = 0; i < arguments.length; i++) {
- positionalArguments[i] = arguments[i].executeGeneric(frame);
- }
- return positionalArguments;
- }
-
- @Specialization
- public Object invokeFunction(VirtualFrame frame, Function target) {
- Object[] positionalArguments = computeArguments(frame);
-
- CompilerAsserts.compilationConstant(this.isTail());
- if (this.isTail()) {
- throw new TailCallException(target, positionalArguments);
- } else {
- return dispatchNode.executeDispatch(target, positionalArguments);
- }
- }
-
- @Specialization
- public Atom invokeConstructor(VirtualFrame frame, AtomConstructor constructor) {
- Object[] positionalArguments = computeArguments(frame);
- return constructor.newInstance(positionalArguments);
- }
-
- @Fallback
- public Object invokeGeneric(VirtualFrame frame, Object target) {
- if (TypesGen.isFunction(target))
- return invokeFunction(frame, (Function) target);
- if (TypesGen.isAtomConstructor(target))
- return invokeConstructor(frame, (AtomConstructor) target);
- throw new NotInvokableException(target, this);
- }
-}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/function/LoopingDispatchNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/function/LoopingDispatchNode.java
deleted file mode 100644
index 01bffc9f69e..00000000000
--- a/Interpreter/src/main/java/org/enso/interpreter/node/function/LoopingDispatchNode.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package org.enso.interpreter.node.function;
-
-import com.oracle.truffle.api.Truffle;
-import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.api.nodes.LoopNode;
-import com.oracle.truffle.api.nodes.Node;
-import com.oracle.truffle.api.nodes.RepeatingNode;
-import org.enso.interpreter.optimiser.TailCallException;
-
-/**
- * A version of {@link DispatchNode} that is fully prepared to handle tail calls. Tail calls are
- * handled through exceptions – whenever a tail-recursive call would be executed, an exception
- * containing the next unevaluated call and arguments is thrown instead (see: {@link
- * TailCallException}).
- *
- * This node executes the function in a loop, following all the continuations, until obtaining
- * the actual return value.
- */
-public class LoopingDispatchNode extends DispatchNode {
-
- private final FrameDescriptor loopFrameDescriptor = new FrameDescriptor();
- @Child private LoopNode loopNode;
-
- public LoopingDispatchNode() {
- loopNode = Truffle.getRuntime().createLoopNode(new RepeatedCallNode(loopFrameDescriptor));
- }
-
- @Override
- public Object executeDispatch(Object receiver, Object[] arguments) {
- VirtualFrame frame = Truffle.getRuntime().createVirtualFrame(null, loopFrameDescriptor);
- ((RepeatedCallNode) loopNode.getRepeatingNode()).setNextCall(frame, receiver, arguments);
- loopNode.executeLoop(frame);
- return ((RepeatedCallNode) loopNode.getRepeatingNode()).getResult(frame);
- }
-
- public static final class RepeatedCallNode extends Node implements RepeatingNode {
- private final FrameSlot resultSlot;
- private final FrameSlot functionSlot;
- private final FrameSlot argsSlot;
- @Child private CallNode dispatchNode;
-
- public RepeatedCallNode(FrameDescriptor descriptor) {
- functionSlot = descriptor.findOrAddFrameSlot(" This method is responsible for getting the guest language parent frame for the current frame
+ * by walking up the stack based on the scope in which the function was defined.
+ *
+ * @param frame the frame to find the Enso parent frame for
+ * @return the guest language parent frame of {@code frame}
+ */
+ @ExplodeLoop
+ public MaterializedFrame getProperFrame(Frame frame) {
+ MaterializedFrame currentFrame = getParentFrame(frame);
+ for (int i = 1; i < getFramePointer().getParentLevel(); i++) {
+ currentFrame = getParentFrame(currentFrame);
+ }
+ return currentFrame;
+ }
}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/optimiser/TailCallException.java b/Interpreter/src/main/java/org/enso/interpreter/optimiser/TailCallException.java
deleted file mode 100644
index d2e783c55c4..00000000000
--- a/Interpreter/src/main/java/org/enso/interpreter/optimiser/TailCallException.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.enso.interpreter.optimiser;
-
-import com.oracle.truffle.api.nodes.ControlFlowException;
-import org.enso.interpreter.runtime.Function;
-
-public class TailCallException extends ControlFlowException {
- private final Function function;
- private final Object[] arguments;
-
- public TailCallException(Function function, Object[] arguments) {
- this.function = function;
- this.arguments = arguments;
- }
-
- public Function getFunction() {
- return function;
- }
-
- public Object[] getArguments() {
- return arguments;
- }
-}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/data/.gitkeep b/Interpreter/src/main/java/org/enso/interpreter/optimiser/constant/.gitkeep
similarity index 100%
rename from Interpreter/src/main/java/org/enso/interpreter/node/data/.gitkeep
rename to Interpreter/src/main/java/org/enso/interpreter/optimiser/constant/.gitkeep
diff --git a/Interpreter/src/main/java/org/enso/interpreter/optimiser/tco/TailCallException.java b/Interpreter/src/main/java/org/enso/interpreter/optimiser/tco/TailCallException.java
new file mode 100644
index 00000000000..6f2675fb019
--- /dev/null
+++ b/Interpreter/src/main/java/org/enso/interpreter/optimiser/tco/TailCallException.java
@@ -0,0 +1,43 @@
+package org.enso.interpreter.optimiser.tco;
+
+import com.oracle.truffle.api.nodes.ControlFlowException;
+import org.enso.interpreter.runtime.callable.function.Function;
+
+/**
+ * Used to model the switch of control-flow from standard stack-based execution to looping.
+ *
+ * This is used as part of the tail-call optimisation functionality in the interpreter.
+ */
+public class TailCallException extends ControlFlowException {
+ private final Function function;
+ private final Object[] arguments;
+
+ /**
+ * Creates a new exception containing the necessary data to continue computation.
+ *
+ * @param function the function to execute in a loop
+ * @param arguments the arguments to {@code function}
+ */
+ public TailCallException(Function function, Object[] arguments) {
+ this.function = function;
+ this.arguments = arguments;
+ }
+
+ /**
+ * Gets the function to execute.
+ *
+ * @return the {@link Function} awaiting execution
+ */
+ public Function getFunction() {
+ return function;
+ }
+
+ /**
+ * Gets the arguments for the function.
+ *
+ * @return the arguments for the associated {@link Function}
+ */
+ public Object[] getArguments() {
+ return arguments;
+ }
+}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/runtime/AtomConstructor.java b/Interpreter/src/main/java/org/enso/interpreter/runtime/AtomConstructor.java
deleted file mode 100644
index 3d8ff53e571..00000000000
--- a/Interpreter/src/main/java/org/enso/interpreter/runtime/AtomConstructor.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package org.enso.interpreter.runtime;
-
-import com.oracle.truffle.api.interop.TruffleObject;
-import org.enso.interpreter.runtime.errors.ArityException;
-
-import java.util.List;
-
-public class AtomConstructor implements TruffleObject {
- public static final AtomConstructor CONS = new AtomConstructor("Cons", 2);
- public static final AtomConstructor NIL = new AtomConstructor("Nil", 0);
- public static final AtomConstructor UNIT = new AtomConstructor("Unit", 0);
-
- private final String name;
- private final int arity;
- private final Atom cachedInstance;
-
- public AtomConstructor(String name, List If an argument is not applied in {@code args}, the resultant array will contain {@code null}
+ * in any places where an argument was not applied. This is then handled later at the point of
+ * reading the arguments, where {@link
+ * org.enso.interpreter.node.callable.argument.ReadArgumentNode} will use the default value for
+ * that argument.
+ *
+ * If an argument is explicitly left unapplied (for the purposes of currying), the result array
+ * will contain a sentinel value of type {@link
+ * UnappliedArgumentSentinel} in that position.
+ * This ensures that execution knows not to use a possible default value for this position.
+ *
+ * @param order a mapping where position {@code i} in the array contains the destination position
+ * in the target array for the calling argument in position {@code i}
+ * @param args the function arguments to reorder, ordered as at the call site
+ * @param numDefinedArgs the number of arguments the function was defined for
+ * @return {@code args} sorted according to the provided {@code order} in an array with slots for
+ * the number of arguments the function was defined for
+ */
+ @ExplodeLoop
+ public static Object[] reorderArguments(int[] order, Object[] args, int numDefinedArgs) {
+ Object[] result = new Object[numDefinedArgs]; // Note [Defined Arguments]
+
+ for (int i = 0; i < args.length; i++) {
+ result[order[i]] = args[i];
+ }
+ return result;
+ }
+
+ /* Note [Defined Arguments]
+ * ~~~~~~~~~~~~~~~~~~~~~~~~
+ * In a language where it is possible to both partially-apply functions and to have default
+ * function arguments, it is important that we track information about where arguments have and
+ * have not been applied.
+ *
+ * To do this, we take advantage of the fact that Java will `null` initialise an array. As a
+ * result, we know that any nulls in the result array must correspond to arguments that haven't
+ * been applied.
+ *
+ * However, this doesn't handle the case where arguments are intentionally ignored, so we use a
+ * special sentinel value to handle this.
+ */
+
+ /**
+ * Generates a mapping between the call-site argument positions and the definition-site argument
+ * positions.
+ *
+ * @param callable the construct being called
+ * @param callArgs information on the arguments at the call site, ordered as at the call site
+ * @return a mapping where position {@code i} in the array contains the destination position in
+ * the target array for the calling argument in position {@code i}
+ */
+ public static int[] generateArgMapping(Object callable, CallArgumentInfo[] callArgs) {
+ if (TypesGen.isCallable(callable)) {
+ Callable realCallable = (Callable) callable;
+
+ ArgumentDefinition[] definedArgs = realCallable.getArgs();
+ int numberOfDefinedArgs = definedArgs.length;
+
+ boolean[] definedArgumentIsUsed = new boolean[numberOfDefinedArgs];
+ int[] argumentSortOrder = new int[callArgs.length];
+
+ for (int i = 0; i < callArgs.length; ++i) {
+ boolean argumentProcessed = false;
+ CallArgumentInfo currentArgument = callArgs[i];
+
+ boolean argumentIsPositional = currentArgument.isPositional();
+
+ if (argumentIsPositional) {
+ for (int j = 0; j < numberOfDefinedArgs; j++) {
+ boolean argumentIsUnused = !definedArgumentIsUsed[j];
+
+ if (argumentIsUnused) {
+ argumentSortOrder[i] = j;
+ definedArgumentIsUsed[j] = true;
+ argumentProcessed = true;
+ break;
+ }
+ }
+
+ if (!argumentProcessed) {
+ throw new ArgumentMappingException(realCallable, currentArgument, i);
+ }
+
+ } else {
+ for (int j = 0; j < numberOfDefinedArgs; j++) {
+ boolean argumentIsValidAndNamed =
+ currentArgument.getName().equals(definedArgs[j].getName())
+ && !definedArgumentIsUsed[j];
+
+ if (argumentIsValidAndNamed) {
+ argumentSortOrder[i] = j;
+ definedArgumentIsUsed[j] = true;
+ argumentProcessed = true;
+ break;
+ }
+ }
+
+ if (!argumentProcessed) {
+ throw new ArgumentMappingException(realCallable, currentArgument, i);
+ }
+ }
+ }
+
+ return argumentSortOrder;
+ } else {
+ throw new NotInvokableException(callable, null);
+ }
+ }
+}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/runtime/callable/argument/sentinel/DefaultedArgumentSentinel.java b/Interpreter/src/main/java/org/enso/interpreter/runtime/callable/argument/sentinel/DefaultedArgumentSentinel.java
new file mode 100644
index 00000000000..71745e76056
--- /dev/null
+++ b/Interpreter/src/main/java/org/enso/interpreter/runtime/callable/argument/sentinel/DefaultedArgumentSentinel.java
@@ -0,0 +1,17 @@
+package org.enso.interpreter.runtime.callable.argument.sentinel;
+
+import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
+
+/** A representation of an argument that has been explicitly defaulted in the program source. */
+public class DefaultedArgumentSentinel {
+ private ArgumentDefinition argument;
+
+ /**
+ * Creates a defaulted argument sentinel value.
+ *
+ * @param argument information about the defaulted argument
+ */
+ public DefaultedArgumentSentinel(ArgumentDefinition argument) {
+ this.argument = argument;
+ }
+}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/runtime/callable/argument/sentinel/UnappliedArgumentSentinel.java b/Interpreter/src/main/java/org/enso/interpreter/runtime/callable/argument/sentinel/UnappliedArgumentSentinel.java
new file mode 100644
index 00000000000..597a01244f7
--- /dev/null
+++ b/Interpreter/src/main/java/org/enso/interpreter/runtime/callable/argument/sentinel/UnappliedArgumentSentinel.java
@@ -0,0 +1,19 @@
+package org.enso.interpreter.runtime.callable.argument.sentinel;
+
+import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
+
+/**
+ * A value used to sentinel an argument that has been defined for a function but not yet applied.
+ */
+public class UnappliedArgumentSentinel {
+ private ArgumentDefinition argument;
+
+ /**
+ * Creates a sentinel value for the given argument.
+ *
+ * @param argument information about the argument being signalled
+ */
+ public UnappliedArgumentSentinel(ArgumentDefinition argument) {
+ this.argument = argument;
+ }
+}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/runtime/Atom.java b/Interpreter/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java
similarity index 57%
rename from Interpreter/src/main/java/org/enso/interpreter/runtime/Atom.java
rename to Interpreter/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java
index fbc84cafc39..deed56cd837 100644
--- a/Interpreter/src/main/java/org/enso/interpreter/runtime/Atom.java
+++ b/Interpreter/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java
@@ -1,29 +1,51 @@
-package org.enso.interpreter.runtime;
+package org.enso.interpreter.runtime.callable.atom;
import com.oracle.truffle.api.CompilerDirectives;
+import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.interop.TruffleObject;
-
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
+/** A runtime representation of an Atom in Enso. */
public class Atom implements TruffleObject {
private final AtomConstructor constructor;
- private final Object[] fields;
+ private @CompilationFinal(dimensions = 1) Object[] fields;
+ /**
+ * Creates a new Atom for a given constructor.
+ *
+ * @param constructor the Atom's constructor
+ * @param fields the Atom's fields
+ */
public Atom(AtomConstructor constructor, Object... fields) {
this.constructor = constructor;
this.fields = fields;
}
+ /**
+ * Gets the Atom's constructor.
+ *
+ * @return the constructor for this Atom
+ */
public AtomConstructor getConstructor() {
return constructor;
}
+ /**
+ * Gets the fields from the Atom.
+ *
+ * @return this Atom's fields
+ */
public Object[] getFields() {
return fields;
}
+ /**
+ * Creates a textual representation of this Atom, useful for debugging.
+ *
+ * @return a textual representation of this Atom.
+ */
@Override
@CompilerDirectives.TruffleBoundary
public String toString() {
@@ -34,6 +56,7 @@ public class Atom implements TruffleObject {
Arrays.stream(fields).map(Object::toString).collect(Collectors.toList());
builder.append(String.join(", ", fieldStrings));
builder.append(">");
+
return builder.toString();
}
}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java b/Interpreter/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java
new file mode 100644
index 00000000000..bbf8cfe97be
--- /dev/null
+++ b/Interpreter/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java
@@ -0,0 +1,79 @@
+package org.enso.interpreter.runtime.callable.atom;
+
+import com.oracle.truffle.api.interop.TruffleObject;
+import org.enso.interpreter.runtime.callable.Callable;
+import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
+import org.enso.interpreter.runtime.error.ArityException;
+
+/** A representation of an Atom constructor. */
+public class AtomConstructor extends Callable implements TruffleObject {
+ public static final AtomConstructor CONS =
+ new AtomConstructor(
+ "Cons",
+ new ArgumentDefinition[] {
+ new ArgumentDefinition(0, "head"), new ArgumentDefinition(1, "rest")
+ });
+ public static final AtomConstructor NIL = new AtomConstructor("Nil", new ArgumentDefinition[0]);
+ public static final AtomConstructor UNIT = new AtomConstructor("Unit", new ArgumentDefinition[0]);
+
+ private final String name;
+ private final Atom cachedInstance;
+
+ /**
+ * Creates a new Atom constructor for a given name and with the provided arguments.
+ *
+ * @param name the name of the Atom constructor
+ * @param args the fields associated with the constructor
+ */
+ public AtomConstructor(String name, ArgumentDefinition[] args) {
+ super(args);
+ this.name = name;
+ if (args.length == 0) {
+ cachedInstance = new Atom(this);
+ } else {
+ cachedInstance = null;
+ }
+ }
+
+ /**
+ * Gets the name of the constructor.
+ *
+ * @return the name of the Atom constructor
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Gets the number of arguments expected by the constructor.
+ *
+ * @return the number of args expected by the constructor.
+ */
+ public int getArity() {
+ return getArgs().length;
+ }
+
+ /**
+ * Creates a new runtime instance of the Atom represented by this constructor.
+ *
+ * @param arguments the runtime arguments to the constructor
+ * @return a new instance of the atom represented by this constructor
+ */
+ public Atom newInstance(Object... arguments) {
+ if (arguments.length > getArity()) {
+ throw new ArityException(getArity(), arguments.length);
+ }
+ if (cachedInstance != null) return cachedInstance;
+ return new Atom(this, arguments);
+ }
+
+ /**
+ * Creates a textual representation of this Atom constructor, useful for debugging.
+ *
+ * @return a textual representation of this Atom constructor
+ */
+ @Override
+ public String toString() {
+ return super.toString() + "<" + name + "/" + getArity() + ">";
+ }
+}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java b/Interpreter/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java
new file mode 100644
index 00000000000..b09a4bf1768
--- /dev/null
+++ b/Interpreter/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java
@@ -0,0 +1,157 @@
+package org.enso.interpreter.runtime.callable.function;
+
+import com.oracle.truffle.api.RootCallTarget;
+import com.oracle.truffle.api.dsl.Cached;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.frame.MaterializedFrame;
+import com.oracle.truffle.api.interop.InteropLibrary;
+import com.oracle.truffle.api.interop.TruffleObject;
+import com.oracle.truffle.api.library.ExportLibrary;
+import com.oracle.truffle.api.library.ExportMessage;
+import com.oracle.truffle.api.nodes.DirectCallNode;
+import com.oracle.truffle.api.nodes.IndirectCallNode;
+import org.enso.interpreter.runtime.callable.Callable;
+import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
+
+/** A runtime representation of a function object in Enso. */
+@ExportLibrary(InteropLibrary.class)
+public final class Function extends Callable implements TruffleObject {
+ private final RootCallTarget callTarget;
+ private final MaterializedFrame scope;
+
+ /**
+ * Creates a new function.
+ *
+ * @param callTarget the target containing the function's code
+ * @param scope a frame representing the function's scope
+ * @param arguments the arguments with which the function was defined
+ */
+ public Function(
+ RootCallTarget callTarget, MaterializedFrame scope, ArgumentDefinition[] arguments) {
+ super(arguments);
+ this.callTarget = callTarget;
+ this.scope = scope;
+ }
+
+ /**
+ * Gets the target containing the function's code.
+ *
+ * @return the target containing the function's code
+ */
+ public RootCallTarget getCallTarget() {
+ return callTarget;
+ }
+
+ /**
+ * Gets the function's scope.
+ *
+ * @return the function's scope
+ */
+ public MaterializedFrame getScope() {
+ return scope;
+ }
+
+ /**
+ * Checks if this runtime object is executable.
+ *
+ * @return {@code true}
+ */
+ @ExportMessage
+ public boolean isExecutable() {
+ return true;
+ }
+
+ /**
+ * A class representing the executable behaviour of the function.
+ *
+ * This class gets exposed via the Truffle interop library to allow Enso functions to be called
+ * from other guest languages running on GraalVM.
+ */
+ @ExportMessage
+ public abstract static class Execute {
+
+ /**
+ * Calls the function directly.
+ *
+ * This specialisation comes into play where the call target for the provided function is
+ * already cached. THis means that the call can be made quickly.
+ *
+ * @param function the function to execute
+ * @param arguments the arguments passed to {@code function} in the expected positional order
+ * @param cachedTarget the cached call target for {@code function}
+ * @param callNode the cached call node for {@code cachedTarget}
+ * @return the result of executing {@code function} on {@code arguments}
+ */
+ @Specialization(guards = "function.getCallTarget() == cachedTarget")
+ protected static Object callDirect(
+ Function function,
+ Object[] arguments,
+ @Cached("function.getCallTarget()") RootCallTarget cachedTarget,
+ @Cached("create(cachedTarget)") DirectCallNode callNode) {
+ return callNode.call(function.getScope(), arguments);
+ }
+
+ /**
+ * Calls the function with a lookup.
+ *
+ * This specialisation is used in the case where there is no cached call target for the
+ * provided function. This is much slower and should, in general, be avoided.
+ *
+ * @param function the function to execute
+ * @param arguments the arguments passed to {@code function} in the expected positional order
+ * @param callNode the cached call node for making indirect calls
+ * @return the result of executing {@code function} on {@code arguments}
+ */
+ @Specialization(replaces = "callDirect")
+ protected static Object callIndirect(
+ Function function, Object[] arguments, @Cached IndirectCallNode callNode) {
+ return callNode.call(function.getCallTarget(), function.getScope(), arguments);
+ }
+ }
+
+ /**
+ * Defines a simple schema for accessing arguments from call targets.
+ *
+ * As Truffle call targets can only take a simple {@code Object[]}, this class provides a way
+ * to get the various necessary pieces of information out of that array.
+ */
+ public static class ArgumentsHelper {
+
+ /**
+ * Generates an array of arguments using the schema to be passed to a call target.
+ *
+ * The arguments passed to this function must be in positional order. For more information on
+ * how to do this, see {@link
+ * org.enso.interpreter.node.callable.argument.sorter.ArgumentSorterNode}.
+ *
+ * @param function the function to be called
+ * @param positionalArguments the arguments to that function, sorted into positional order
+ * @return an array containing the necessary information to call an Enso function
+ */
+ public static Object[] buildArguments(Function function, Object[] positionalArguments) {
+ return new Object[] {function.getScope(), positionalArguments};
+ }
+
+ /**
+ * Gets the positional arguments out of the array.
+ *
+ * @param arguments an array produced by {@link ArgumentsHelper#buildArguments(Function,
+ * Object[])}
+ * @return the positional arguments to the function
+ */
+ public static Object[] getPositionalArguments(Object[] arguments) {
+ return (Object[]) arguments[1];
+ }
+
+ /**
+ * Gets the function's local scope out of the array.
+ *
+ * @param arguments an array produced by {@link ArgumentsHelper#buildArguments(Function,
+ * Object[])}
+ * @return the local scope for the associated function
+ */
+ public static MaterializedFrame getLocalScope(Object[] arguments) {
+ return (MaterializedFrame) arguments[0];
+ }
+ }
+}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/runtime/error/ArgumentMappingException.java b/Interpreter/src/main/java/org/enso/interpreter/runtime/error/ArgumentMappingException.java
new file mode 100644
index 00000000000..b82094582ab
--- /dev/null
+++ b/Interpreter/src/main/java/org/enso/interpreter/runtime/error/ArgumentMappingException.java
@@ -0,0 +1,32 @@
+package org.enso.interpreter.runtime.error;
+
+import java.util.Arrays;
+import java.util.stream.Collectors;
+import org.enso.interpreter.runtime.callable.Callable;
+import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
+
+/**
+ * An error that is thrown when the interpreter is unable to match the arguments provided at the
+ * call site to the arguments defined for the callable.
+ */
+public class ArgumentMappingException extends RuntimeException {
+
+ /**
+ * Creates a new error.
+ *
+ * @param callable the callable for which argument matching is taking place
+ * @param argument information on the call-site argument that failed to match
+ * @param callPosition the position at the call site of {@code argument}
+ */
+ public ArgumentMappingException(Callable callable, CallArgumentInfo argument, int callPosition) {
+ super(
+ String.format(
+ "Cannot match argument %s to callable with arguments %s",
+ argument.isPositional()
+ ? "at position " + callPosition
+ : "with name " + argument.getName(),
+ Arrays.stream(callable.getArgs())
+ .map(arg -> arg.getName() + ", ")
+ .collect(Collectors.joining())));
+ }
+}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/runtime/error/ArityException.java b/Interpreter/src/main/java/org/enso/interpreter/runtime/error/ArityException.java
new file mode 100644
index 00000000000..6abf70c46cd
--- /dev/null
+++ b/Interpreter/src/main/java/org/enso/interpreter/runtime/error/ArityException.java
@@ -0,0 +1,18 @@
+package org.enso.interpreter.runtime.error;
+
+/**
+ * An exception that is thrown whenever a call is made to a {@link
+ * org.enso.interpreter.runtime.callable.Callable} with the wrong number of arguments.
+ */
+public class ArityException extends RuntimeException {
+
+ /**
+ * Creates a new error.
+ *
+ * @param expected the expected number of arguments
+ * @param actual the provided number of arguments
+ */
+ public ArityException(int expected, int actual) {
+ super("Wrong number of arguments. Expected: " + expected + " but got: " + actual + ".");
+ }
+}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/runtime/error/DuplicateArgumentNameException.java b/Interpreter/src/main/java/org/enso/interpreter/runtime/error/DuplicateArgumentNameException.java
new file mode 100644
index 00000000000..8eadba1698f
--- /dev/null
+++ b/Interpreter/src/main/java/org/enso/interpreter/runtime/error/DuplicateArgumentNameException.java
@@ -0,0 +1,16 @@
+package org.enso.interpreter.runtime.error;
+
+/**
+ * An exception thrown when a function is defined with duplicate argument names.
+ */
+public class DuplicateArgumentNameException extends RuntimeException {
+
+ /**
+ * Creates a new error.
+ *
+ * @param name the name defined multiple times
+ */
+ public DuplicateArgumentNameException(String name) {
+ super("A function cannot have multiple arguments called " + name);
+ }
+}
diff --git a/Interpreter/src/main/java/org/enso/interpreter/runtime/errors/InexhaustivePatternMatchException.java b/Interpreter/src/main/java/org/enso/interpreter/runtime/error/InexhaustivePatternMatchException.java
similarity index 51%
rename from Interpreter/src/main/java/org/enso/interpreter/runtime/errors/InexhaustivePatternMatchException.java
rename to Interpreter/src/main/java/org/enso/interpreter/runtime/error/InexhaustivePatternMatchException.java
index 7d4d0f0a5f3..78c4a5aee24 100644
--- a/Interpreter/src/main/java/org/enso/interpreter/runtime/errors/InexhaustivePatternMatchException.java
+++ b/Interpreter/src/main/java/org/enso/interpreter/runtime/error/InexhaustivePatternMatchException.java
@@ -1,18 +1,31 @@
-package org.enso.interpreter.runtime.errors;
+package org.enso.interpreter.runtime.error;
import com.oracle.truffle.api.TruffleException;
import com.oracle.truffle.api.nodes.Node;
+/**
+ * An error thrown when a pattern match is is inexhaustive and execution cannot find a branch that
+ * is able to be executed.
+ */
public class InexhaustivePatternMatchException extends RuntimeException
implements TruffleException {
-
private final Node node;
+ /**
+ * Creates a new error.
+ *
+ * @param node the node where the fallthrough occurred
+ */
public InexhaustivePatternMatchException(Node node) {
super("Inexhaustive pattern match.");
this.node = node;
}
+ /**
+ * Gets the location where the error occurred.
+ *
+ * @return the node where the error occurred
+ */
@Override
public Node getLocation() {
return node;
diff --git a/Interpreter/src/main/java/org/enso/interpreter/runtime/error/InvalidArgumentNameException.java b/Interpreter/src/main/java/org/enso/interpreter/runtime/error/InvalidArgumentNameException.java
new file mode 100644
index 00000000000..314d3a5b3cb
--- /dev/null
+++ b/Interpreter/src/main/java/org/enso/interpreter/runtime/error/InvalidArgumentNameException.java
@@ -0,0 +1,23 @@
+package org.enso.interpreter.runtime.error;
+
+import java.util.Set;
+
+/**
+ * An error thrown when an argument is provided by name, but doesn't match any of the arguments
+ * defined on the callable.
+ */
+public class InvalidArgumentNameException extends RuntimeException {
+
+ /**
+ * Creates a new error.
+ *
+ * @param name the erroneous argument name at the call site
+ * @param availableNames the argument names defined on the callable
+ */
+ public InvalidArgumentNameException(String name, Set A {@link FrameDescriptor} is a handle to an interpreter frame. This provides the means to
+ * map between Enso's concept of frames, and the interpreter's concept of frames.
+ *
+ * @return the frame descriptor for this scope
+ */
+ public FrameDescriptor getFrameDescriptor() {
+ return frameDescriptor;
+ }
+
+ /**
+ * Gets the Enso-semantics parent of this scope.
+ *
+ * @return the parent scope
+ */
+ public LocalScope getParent() {
+ return parent;
+ }
+
+ /**
+ * Creates a scope that is the Enso-semantics child of this.
+ *
+ * @return a new scope with {@code this} as its parent
+ */
+ public LocalScope createChild() {
+ return new LocalScope(this);
+ }
+
+ /**
+ * Creates a new variable in the Enso frame.
+ *
+ * @param name the name of the variable
+ * @return a handle to the defined variable
+ */
+ public FrameSlot createVarSlot(String name) {
+ if (items.containsKey(name)) throw new VariableRedefinitionException(name);
+ // The FrameSlot is created for a given identifier.
+ FrameSlot slot = frameDescriptor.addFrameSlot(name);
+ items.put(name, slot);
+ return slot;
+ }
+
+ /**
+ * Reads a variable from the Enso frame.
+ *
+ * @param name the name of the variable
+ * @return a handle to the variable, otherwise {@link Optional#empty()}
+ */
+ public Optional While the language has support for rich types, the interpreter only cares about a small set of
+ * primitive-level types in order to make execution fast. All higher-level types can be desugared in
+ * terms of the more limited set of types expressed here.
+ *
+ * By declaring the primitive types here, the interpreter obtains automatically generated utilities
+ * for working with them.
+ */
+@TypeSystem({long.class, Function.class, Atom.class, AtomConstructor.class, Callable.class})
+public class Types {
+
+ /**
+ * An implicit conversion between {@code int} and {@code long} for Enso programs.
+ *
+ * @param value the value to convert
+ * @return {@code value} as the appropriate type
+ */
+ @ImplicitCast
+ @CompilerDirectives.TruffleBoundary
+ public static long castLong(int value) {
+ return value;
+ }
+}
diff --git a/Interpreter/src/main/scala/org/enso/interpreter/Parser.scala b/Interpreter/src/main/scala/org/enso/interpreter/Parser.scala
index dca9a4e5300..64d02117ee8 100644
--- a/Interpreter/src/main/scala/org/enso/interpreter/Parser.scala
+++ b/Interpreter/src/main/scala/org/enso/interpreter/Parser.scala
@@ -13,20 +13,20 @@ trait AstExpressionVisitor[+T] {
def visitVariable(name: String): T
def visitFunction(
- arguments: java.util.List[String],
+ arguments: java.util.List[AstArgDefinition],
statements: java.util.List[AstExpression],
retValue: AstExpression
): T
def visitCaseFunction(
- arguments: java.util.List[String],
+ arguments: java.util.List[AstArgDefinition],
statements: java.util.List[AstExpression],
retValue: AstExpression
): T
- def visitApplication(
+ def visitFunctionApplication(
function: AstExpression,
- arguments: java.util.List[AstExpression]
+ arguments: java.util.List[AstCallArg]
): T
def visitIf(
@@ -57,15 +57,14 @@ trait AstGlobalScopeVisitor[+T] {
sealed trait AstGlobalSymbol
-case class AstTypeDef(name: String, arguments: List[String])
+case class AstTypeDef(name: String, arguments: List[AstArgDefinition])
extends AstGlobalSymbol {
- def getArguments: java.util.List[String] = arguments.asJava
+ def getArguments: java.util.List[AstArgDefinition] = arguments.asJava
}
case class AstGlobalScope(
bindings: List[AstGlobalSymbol],
- expression: AstExpression)
- extends {
+ expression: AstExpression) {
def visit[T](visitor: AstGlobalScopeVisitor[T]): T = {
val types = new util.ArrayList[AstTypeDef]()
@@ -84,6 +83,52 @@ sealed trait AstExpression {
def visit[T](visitor: AstExpressionVisitor[T]): T
}
+sealed trait AstArgDefinition {
+ def visit[T](visitor: AstArgDefinitionVisitor[T], position: Int): T
+}
+
+trait AstArgDefinitionVisitor[+T] {
+ def visitDefaultedArg(name: String, value: AstExpression, position: Int): T
+ def visitBareArg(name: String, position: Int): T
+}
+
+case class AstDefaultedArgDefinition(name: String, value: AstExpression)
+ extends AstArgDefinition {
+ override def visit[T](visitor: AstArgDefinitionVisitor[T], position: Int): T =
+ visitor.visitDefaultedArg(name, value, position)
+}
+
+case class AstBareArgDefinition(name: String) extends AstArgDefinition {
+ override def visit[T](visitor: AstArgDefinitionVisitor[T], position: Int): T =
+ visitor.visitBareArg(name, position)
+}
+
+sealed trait AstCallArg {
+ def visit[T](visitor: AstCallArgVisitor[T], position: Int): T
+}
+
+trait AstCallArgVisitor[+T] {
+ def visitNamedCallArg(name: String, value: AstExpression, position: Int): T
+ def visitUnnamedCallArg(value: AstExpression, position: Int): T
+ def visitIgnore(name: String, position: Int): T
+}
+
+case class AstNamedCallArg(name: String, value: AstExpression)
+ extends AstCallArg {
+ override def visit[T](visitor: AstCallArgVisitor[T], position: Int): T =
+ visitor.visitNamedCallArg(name, value, position)
+}
+
+case class AstUnnamedCallArg(value: AstExpression) extends AstCallArg {
+ override def visit[T](visitor: AstCallArgVisitor[T], position: Int): T =
+ visitor.visitUnnamedCallArg(value, position)
+}
+
+case class AstIgnore(name: String) extends AstCallArg {
+ override def visit[T](visitor: AstCallArgVisitor[T], position: Int): T =
+ visitor.visitIgnore(name, position)
+}
+
case class AstLong(l: Long) extends AstExpression {
override def visit[T](visitor: AstExpressionVisitor[T]): T =
visitor.visitLong(l)
@@ -105,14 +150,14 @@ case class AstVariable(name: String) extends AstExpression {
visitor.visitVariable(name)
}
-case class AstApply(fun: AstExpression, args: List[AstExpression])
+case class AstApply(fun: AstExpression, args: List[AstCallArg])
extends AstExpression {
override def visit[T](visitor: AstExpressionVisitor[T]): T =
- visitor.visitApplication(fun, args.asJava)
+ visitor.visitFunctionApplication(fun, args.asJava)
}
case class AstFunction(
- arguments: List[String],
+ arguments: List[AstArgDefinition],
statements: List[AstExpression],
ret: AstExpression)
extends AstExpression {
@@ -121,7 +166,7 @@ case class AstFunction(
}
case class AstCaseFunction(
- arguments: List[String],
+ arguments: List[AstArgDefinition],
statements: List[AstExpression],
ret: AstExpression)
extends AstExpression {
@@ -185,10 +230,29 @@ class EnsoParserInternal extends JavaTokenParsers {
case lang ~ code => AstForeign(lang, code)
}
- def argList: Parser[List[AstExpression]] =
- delimited("[", "]", nonEmptyList(expression))
+ def argList: Parser[List[AstCallArg]] =
+ delimited("[", "]", nonEmptyList(ignore | namedCallArg | unnamedCallArg))
- def inArgList: Parser[List[String]] = delimited("|", "|", nonEmptyList(ident))
+ def namedCallArg: Parser[AstNamedCallArg] = ident ~ ("=" ~> expression) ^^ {
+ case name ~ expr => AstNamedCallArg(name, expr)
+ }
+
+ def unnamedCallArg: Parser[AstCallArg] = expression ^^ AstUnnamedCallArg
+
+ def defaultedArgDefinition: Parser[AstDefaultedArgDefinition] =
+ ident ~ ("=" ~> expression) ^^ {
+ case name ~ value => AstDefaultedArgDefinition(name, value)
+ }
+
+ def bareArgDefinition: Parser[AstBareArgDefinition] =
+ ident ^^ AstBareArgDefinition
+
+ def inArgList: Parser[List[AstArgDefinition]] =
+ delimited(
+ "|",
+ "|",
+ nonEmptyList(defaultedArgDefinition | bareArgDefinition)
+ )
def foreignLiteral: Parser[String] = "**" ~> "[^\\*]*".r <~ "**"
@@ -206,6 +270,8 @@ class EnsoParserInternal extends JavaTokenParsers {
def expression: Parser[AstExpression] =
ifZero | matchClause | arith | function
+ def ignore: Parser[AstIgnore] = ident ~> "=" ~> "!!" ^^ AstIgnore
+
def call: Parser[AstApply] = "@" ~> expression ~ (argList ?) ^^ {
case expr ~ args => AstApply(expr, args.getOrElse(Nil))
}
@@ -216,9 +282,10 @@ class EnsoParserInternal extends JavaTokenParsers {
def print: Parser[AstPrint] = "print:" ~> expression ^^ AstPrint
- def ifZero: Parser[AstIfZero] = "ifZero:" ~> argList ^^ {
- case List(cond, ift, iff) => AstIfZero(cond, ift, iff)
- }
+ def ifZero: Parser[AstIfZero] =
+ "ifZero:" ~> "[" ~> (expression ~ ("," ~> expression ~ ("," ~> expression))) <~ "]" ^^ {
+ case cond ~ (ift ~ iff) => AstIfZero(cond, ift, iff)
+ }
def function: Parser[AstFunction] =
("{" ~> (inArgList ?) ~ ((statement <~ ";") *) ~ expression <~ "}") ^^ {
@@ -242,9 +309,10 @@ class EnsoParserInternal extends JavaTokenParsers {
def statement: Parser[AstExpression] = assignment | print | expression
- def typeDef: Parser[AstGlobalSymbol] = "type" ~> ident ~ (ident *) <~ ";" ^^ {
- case name ~ args => AstTypeDef(name, args)
- }
+ def typeDef: Parser[AstGlobalSymbol] =
+ "type" ~> ident ~ ((bareArgDefinition | ("(" ~> defaultedArgDefinition <~ ")")) *) <~ ";" ^^ {
+ case name ~ args => AstTypeDef(name, args)
+ }
def globalScope: Parser[AstGlobalScope] =
((typeDef | assignment) *) ~ expression ^^ {
diff --git a/Interpreter/src/test/scala/org/enso/interpreter/CurryingTest.scala b/Interpreter/src/test/scala/org/enso/interpreter/CurryingTest.scala
new file mode 100644
index 00000000000..17ee0ab6319
--- /dev/null
+++ b/Interpreter/src/test/scala/org/enso/interpreter/CurryingTest.scala
@@ -0,0 +1,20 @@
+package org.enso.interpreter;
+
+class CurryingTest extends LanguageTest {
+
+ "Functions" should "allow defaulted values to be removed" in {
+ pending
+ val code =
+ """
+ |addNum = { |a, num = 10| a + num }
+ |
+ |add = @addNum [num = !!]
+ |
+ |0
+ """.stripMargin
+
+ noException should be thrownBy parse(code)
+ eval(code) shouldEqual 0
+ }
+
+}
diff --git a/Interpreter/src/test/scala/org/enso/interpreter/FunctionArgumentsTest.scala b/Interpreter/src/test/scala/org/enso/interpreter/FunctionArgumentsTest.scala
index 7771cfee793..5dfab253180 100644
--- a/Interpreter/src/test/scala/org/enso/interpreter/FunctionArgumentsTest.scala
+++ b/Interpreter/src/test/scala/org/enso/interpreter/FunctionArgumentsTest.scala
@@ -1,5 +1,7 @@
package org.enso.interpreter
+import org.graalvm.polyglot.PolyglotException
+
class FunctionArgumentsTest extends LanguageTest {
"Functions" should "take arguments and use them in their bodies" in {
val code = "{ |x| x * x }"
@@ -24,7 +26,7 @@ class FunctionArgumentsTest extends LanguageTest {
"Recursion" should "work" in {
val code =
"""
- |@{
+ |@{
| sumTo = { |x| ifZero: [x, 0, x + (@sumTo [x - 1])] };
| @sumTo [10]
|}
@@ -32,4 +34,35 @@ class FunctionArgumentsTest extends LanguageTest {
eval(code) shouldEqual 55
}
+
+ "Functions" should "not take more arguments than they are defined for" in {
+ pending
+ val code =
+ """
+ |@{
+ | sumTo = { |x| ifZero: [x, 0, x + (@sumTo [x - 1])] };
+ | @sumTo [10, 11]
+ |}
+ """.stripMargin
+
+ val errMsg =
+ """
+ |org.enso.interpreter.runtime.errors.ArityException: Wrong number of
+ | arguments. Expected: 1 but got: 2.
+ |""".stripMargin.replaceAll("\n", "")
+
+ the[PolyglotException] thrownBy eval(code) should have message errMsg
+ }
+
+ "Function calls" should "accept more arguments than needed and pass them to the result upon execution" in {
+ pending
+ val code =
+ """
+ |f = { |x| { |z| x + z } }
+ |@f [1, 2]
+ |""".stripMargin
+
+ eval(code) shouldEqual 3
+ }
+
}
diff --git a/Interpreter/src/test/scala/org/enso/interpreter/GlobalScopeTest.scala b/Interpreter/src/test/scala/org/enso/interpreter/GlobalScopeTest.scala
index 0d6f872944f..ad355bab80d 100644
--- a/Interpreter/src/test/scala/org/enso/interpreter/GlobalScopeTest.scala
+++ b/Interpreter/src/test/scala/org/enso/interpreter/GlobalScopeTest.scala
@@ -3,7 +3,6 @@ package org.enso.interpreter
import org.graalvm.polyglot.PolyglotException
class GlobalScopeTest extends LanguageTest {
- // TODO [AA] Should work with bare vars.
"Variables" should "be able to be read from the global scope" in {
val code =
"""
diff --git a/Interpreter/src/test/scala/org/enso/interpreter/LanguageTest.scala b/Interpreter/src/test/scala/org/enso/interpreter/LanguageTest.scala
index c60983b85e3..f153757fbb3 100644
--- a/Interpreter/src/test/scala/org/enso/interpreter/LanguageTest.scala
+++ b/Interpreter/src/test/scala/org/enso/interpreter/LanguageTest.scala
@@ -1,8 +1,10 @@
package org.enso.interpreter
-import org.graalvm.polyglot.{Context, Value}
+import org.graalvm.polyglot.Context
+import org.graalvm.polyglot.Value
import org.scalactic.Equality
-import org.scalatest.{FlatSpec, Matchers}
+import org.scalatest.FlatSpec
+import org.scalatest.Matchers
trait LanguageRunner {
implicit class RichValue(value: Value) {
@@ -10,6 +12,9 @@ trait LanguageRunner {
}
val ctx = Context.newBuilder(Constants.LANGUAGE_ID).build()
def eval(code: String): Value = ctx.eval(Constants.LANGUAGE_ID, code)
+
+ def parse(code: String): AstGlobalScope =
+ new EnsoParser().parseEnso(code)
}
abstract class LanguageTest extends FlatSpec with Matchers with LanguageRunner {
diff --git a/Interpreter/src/test/scala/org/enso/interpreter/NamedArgumentsTest.scala b/Interpreter/src/test/scala/org/enso/interpreter/NamedArgumentsTest.scala
new file mode 100644
index 00000000000..f9f3e9f05d8
--- /dev/null
+++ b/Interpreter/src/test/scala/org/enso/interpreter/NamedArgumentsTest.scala
@@ -0,0 +1,268 @@
+package org.enso.interpreter
+
+import org.graalvm.polyglot.PolyglotException
+
+class NamedArgumentsTest extends LanguageTest {
+ "Functions" should "take arguments by name and use them in their bodies" in {
+
+ val code =
+ """
+ |a = 10
+ |addTen = { |b| a + b }
+ |
+ |@addTen [b = 10]
+ """.stripMargin
+
+ eval(code) shouldEqual 20
+ }
+
+ "Functions" should "be able to have named arguments given out of order" in {
+ val code =
+ """
+ |subtract = { |a, b| a - b }
+ |
+ |@subtract [b = 10, a = 5]
+ """.stripMargin
+
+ eval(code) shouldEqual -5
+ }
+
+ "Functions" should "be able to have scope values as named arguments" in {
+ val code =
+ """
+ |a = 10
+ |addTen = { |num| num + a }
+ |
+ |@addTen [num = a]
+ """.stripMargin
+
+ eval(code) shouldEqual 20
+ }
+
+ "Functions" should "be able to be defined with default argument values" in {
+ val code =
+ """
+ |addNum = { |a, num = 10| a + num }
+ |
+ |@addNum [5]
+ """.stripMargin
+
+ eval(code) shouldEqual 15
+ }
+
+ "Default arguments" should "be able to default to complex expressions" in {
+ val code =
+ """
+ |add = { |a, b| a + b }
+ |
+ |doThing = { |a, b = @add [1, 2]| a + b }
+ |
+ |@doThing [10]
+ |""".stripMargin
+
+ eval(code) shouldEqual 13
+ }
+
+ "Default arguments" should "be able to close over their outer scope" in {
+ val code =
+ """
+ |id = { |x| x }
+ |
+ |apply = { |val, fn = id| @id [val] }
+ |
+ |@apply [val = 1]
+ |""".stripMargin
+
+ eval(code) shouldEqual 1
+ }
+
+ "Functions" should "use their default values when none is supplied" in {
+ val code =
+ """
+ |addTogether = { |a = 5, b = 6| a + b }
+ |
+ |@addTogether
+ """.stripMargin
+
+ eval(code) shouldEqual 11
+ }
+
+ "Functions" should "override defaults by name" in {
+ val code =
+ """
+ |addNum = { |a, num = 10| a + num }
+ |
+ |@addNum [1, num = 1]
+ """.stripMargin
+
+ eval(code) shouldEqual 2
+ }
+
+ "Functions" should "override defaults by position" in {
+ val code =
+ """
+ |addNum = { |a, num = 10| a + num }
+ |
+ |@addNum [1, 2]
+ |""".stripMargin
+
+ eval(code) shouldEqual 3
+ }
+
+ "Defaulted arguments" should "work in a recursive context" in {
+ val code =
+ """
+ |summer = { |sumTo|
+ | summator = { |acc = 0, current|
+ | ifZero: [current, acc, @summator [current = current - 1, acc = acc + current]]
+ | };
+ | res = @summator [current = sumTo];
+ | res
+ |}
+ |
+ |@summer [100]
+ """.stripMargin
+
+ eval(code) shouldEqual 5050
+ }
+
+ "Named Arguments" should "only be scoped to their definitions" in {
+ val code =
+ """
+ |foo = { |x, y| x - y }
+ |bar = { |y, x| x - y }
+ |
+ |baz = { |f| @f [x = 10, y = 11] }
+ |
+ |a = @baz [foo]
+ |b = @baz [bar]
+ |
+ |a - b
+ |""".stripMargin
+
+ eval(code) shouldEqual 0
+ }
+
+ "Named arguments" should "be applied in a sequence compatible with Eta-expansions" in {
+ pending
+ val code =
+ """
+ |foo = { |a, b, c| a + b }
+ |@foo [20, a = 10]
+ |""".stripMargin
+ }
+
+ "Default arguments" should "be able to depend on prior arguments" in {
+ val code =
+ """
+ |doubleOrAdd = { |a, b = a| a + b }
+ |
+ |@doubleOrAdd [5]
+ |""".stripMargin
+
+ eval(code) shouldEqual 10
+ }
+
+ "Default arguments" should "not be able to depend on later arguments" in {
+ val code =
+ """
+ |badArgFn = { | a, b = c, c = a | a + b + c }
+ |
+ |@badArgFn [3]
+ |""".stripMargin
+
+ val errMsg = "java.lang.RuntimeException: No result when parsing failed"
+
+ the[PolyglotException] thrownBy eval(code) should have message errMsg
+ }
+
+ "Constructors" should "be able to use named arguments" in {
+ val code =
+ """
+ |type Cons2 head rest;
+ |type Nil2;
+ |
+ |genList = { |i| ifZero: [i, @Nil2, @Cons2 [rest = @genList [i-1], head = i]] }
+ |
+ |sumList = { |list| match list <
+ | Cons2 ~ { |head, rest| head + @sumList [rest] };
+ | Nil2 ~ { 0 };
+ |>}
+ |
+ |@sumList [@genList [10]]
+ """.stripMargin
+
+ eval(code) shouldEqual 55
+ }
+
+ "Constructors" should "be able to take default arguments that are overridden" in {
+ val code =
+ """
+ |type Nil2;
+ |type Cons2 head (rest = Nil2);
+ |
+ |genList = { |i| ifZero: [i, @Nil2, @Cons2 [rest = @genList [i-1], head = i]] }
+ |
+ |sumList = { |list| match list <
+ | Cons2 ~ { |head, rest| head + @sumList [rest] };
+ | Nil2 ~ { 0 };
+ |>}
+ |
+ |@sumList [@genList [5]]
+ """.stripMargin
+
+ eval(code) shouldEqual 15
+ }
+
+ "Default arguments to constructors" should "be resolved dynamically" in {
+ val code =
+ """
+ |type Cons2 head (rest = Nil2);
+ |type Nil2;
+ |
+ |5
+ |""".stripMargin
+
+ pending
+ eval(code) shouldEqual 5
+ }
+
+ "Constructors" should "be able to take and use default arguments" in {
+ pending
+ val code =
+ """
+ |type Nil2;
+ |type Cons2 head (rest = Nil2);
+ |
+ |sumList = { |list| match list <
+ | Cons2 ~ { |head, rest| head + @sumList [rest] };
+ | Nil2 ~ { 0 };
+ |>}
+ |
+ |@sumList [@Cons2 [10]]
+ """.stripMargin
+
+ eval(code) shouldEqual 10
+ }
+
+ "Constructor arguments" should "be matchable in arbitrary order by name" in {
+ val code =
+ """
+ |type Nil2;
+ |type Cons2 head (rest = Nil2);
+ |
+ |genList = { |i| ifZero: [i, @Nil2, @Cons2 [rest = @genList [i-1], head = i]] }
+ |
+ |sumList = { |list| match list <
+ | Cons2 ~ { |rest, head| head + @sumList [rest] };
+ | Nil2 ~ { 0 };
+ |>}
+ |
+ |@sumList [@genList [5]]
+ """.stripMargin
+
+ pending
+ eval(code) shouldEqual 15
+ }
+
+}
diff --git a/build.sbt b/build.sbt
index 356900766d0..2f7a4bcdf04 100644
--- a/build.sbt
+++ b/build.sbt
@@ -102,6 +102,8 @@ lazy val interpreter = (project in file("Interpreter"))
.settings(
libraryDependencies ++= Seq(
"com.chuusai" %% "shapeless" % "2.3.3",
+ "org.apache.commons" % "commons-lang3" % "3.9",
+ "org.apache.tika" % "tika-core" % "1.21",
"org.graalvm.sdk" % "graal-sdk" % "19.0.0",
"org.graalvm.sdk" % "polyglot-tck" % "19.0.0",
"org.graalvm.truffle" % "truffle-api" % "19.0.0",
@@ -109,12 +111,11 @@ lazy val interpreter = (project in file("Interpreter"))
"org.graalvm.truffle" % "truffle-nfi" % "19.0.0",
"org.graalvm.truffle" % "truffle-tck" % "19.0.0",
"org.graalvm.truffle" % "truffle-tck-common" % "19.0.0",
- "org.scalacheck" %% "scalacheck" % "1.14.0" % Test,
- "org.scalatest" %% "scalatest" % "3.2.0-SNAP10" % Test,
- "org.scalactic" %% "scalactic" % "3.0.8" % Test,
- "org.typelevel" %% "cats-core" % "2.0.0-M4",
"org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.4",
- "org.apache.commons" % "commons-lang3" % "3.9"
+ "org.scalacheck" %% "scalacheck" % "1.14.0" % Test,
+ "org.scalactic" %% "scalactic" % "3.0.8" % Test,
+ "org.scalatest" %% "scalatest" % "3.2.0-SNAP10" % Test,
+ "org.typelevel" %% "cats-core" % "2.0.0-M4",
),
libraryDependencies ++= jmh
)