Implement named and defaulted arguments (#80)

This commit is contained in:
Ara Adkins 2019-08-09 16:25:30 +01:00 committed by GitHub
parent caf8808ff7
commit 5ee1c2d194
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
99 changed files with 3587 additions and 758 deletions

6
.gitignore vendored
View File

@ -68,8 +68,14 @@ cabal.sandbox.config
*.swp
.projections.json
############################
## Rendered Documentation ##
############################
javadoc/
#######################
## Benchmark Reports ##
#######################
bench-report.xml

View File

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

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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 =

View File

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

View File

@ -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.
*
* <p>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<Context> {
/**
* Creates a new Enso context.
*
* @param env the language execution environment
* @return a new Enso context
*/
@Override
protected Context createContext(Env env) {
return new Context(this, env);
}
/**
* Checks if a given object is native to Enso.
*
* @param object the object to check
* @return {@code true} if {@code object} belongs to Enso, {@code false} otherwise
*/
@Override
protected boolean isObjectOfLanguage(Object object) {
return false;
}
/**
* Checks if this Enso execution environment is accessible in a multithreaded context.
*
* @param thread the thread to check access for
* @param singleThreaded whether or not execution is single threaded
* @return whether or not thread access is allowed
*/
@Override
protected boolean isThreadAccessAllowed(Thread thread, boolean singleThreaded) {
return super.isThreadAccessAllowed(thread, singleThreaded);
}
/**
* Parses Enso source code ready for execution.
*
* @param request the source to parse, plus contextual information
* @return a ready-to-execute node representing the code provided in {@code request}
* @throws Exception when parsing or AST construction fail
*/
@Override
protected CallTarget parse(ParsingRequest request) throws Exception {
AstGlobalScope parsed =
@ -53,6 +88,11 @@ public final class Language extends TruffleLanguage<Context> {
return Truffle.getRuntime().createCallTarget(root);
}
/**
* Gets the current Enso execution context.
*
* @return the current execution context
*/
public Context getCurrentContext() {
return getCurrentContext(Language.class);
}

View File

@ -0,0 +1,82 @@
package org.enso.interpreter.builder;
import org.enso.interpreter.AstArgDefinitionVisitor;
import org.enso.interpreter.AstExpression;
import org.enso.interpreter.Language;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.scope.GlobalScope;
import org.enso.interpreter.runtime.scope.LocalScope;
/**
* An {@code ArgDefinitionFactory} is responsible for converting argument definitions in the Enso
* AST into runtime nodes for evaluation in the interpreter.
*/
public class ArgDefinitionFactory implements AstArgDefinitionVisitor<ArgumentDefinition> {
private final LocalScope scope;
private final Language language;
private final String scopeName;
private final GlobalScope globalScope;
/**
* Explicitly specifies all constructor arguments.
*
* @param scope the language scope into which the arguments are defined
* @param language the name of the language for which the arguments are defined
* @param scopeName the name of the scope in which the arguments are defined
* @param globalScope the current language global scope
*/
public ArgDefinitionFactory(
LocalScope scope, Language language, String scopeName, GlobalScope globalScope) {
this.scope = scope;
this.language = language;
this.scopeName = scopeName;
this.globalScope = globalScope;
}
/**
* Defaults the local scope to a newly created one.
*
* @param language the name of the language for which the arguments are defined
* @param scopeName the name of the scope in which the arguments are defined
* @param globalScope the current language global scope
*/
public ArgDefinitionFactory(Language language, String scopeName, GlobalScope globalScope) {
this(new LocalScope(), language, scopeName, globalScope);
}
/**
* Default constructs the {@code LocalScope} and defaults the scope name to {@code <root>}.
*
* @param language the name of the language for which the arguments are defined
* @param globalScope the current language global scope
*/
public ArgDefinitionFactory(Language language, GlobalScope globalScope) {
this(language, "<root>", globalScope);
}
/**
* Processes a function argument that is provided without a default.
*
* @param name the name of the argument
* @param position the position of the argument in the definition list
* @return a runtime type representing the argument input
*/
@Override
public ArgumentDefinition visitBareArg(String name, int position) {
return new ArgumentDefinition(position, name);
}
/**
* Processes a function argument that is provided with a default value.
*
* @param name the name of the argument
* @param value the default value of the argument
* @param position the position of the argument in the definition list
* @return a runtime type representing the argument input
*/
@Override
public ArgumentDefinition visitDefaultedArg(String name, AstExpression value, int position) {
ExpressionFactory exprFactory = new ExpressionFactory(language, scope, scopeName, globalScope);
return new ArgumentDefinition(position, name, value.visit(exprFactory));
}
}

View File

@ -0,0 +1,82 @@
package org.enso.interpreter.builder;
import org.enso.interpreter.AstCallArgVisitor;
import org.enso.interpreter.AstExpression;
import org.enso.interpreter.Language;
import org.enso.interpreter.runtime.callable.argument.CallArgument;
import org.enso.interpreter.runtime.scope.GlobalScope;
import org.enso.interpreter.runtime.scope.LocalScope;
/**
* A {@code CallArgFactory} is responsible for converting arguments passed to a function call into
* runtime nodes used by the interpreter to guide function evaluation.
*/
public class CallArgFactory implements AstCallArgVisitor<CallArgument> {
private final LocalScope scope;
private final Language language;
private final String scopeName;
private final GlobalScope globalScope;
/**
* Explicitly specifies all constructor parameters.
*
* @param scope the language scope in which the arguments are called
* @param language the name of the language for which the arguments are defined
* @param scopeName the name of the scope in which the arguments are called
* @param globalScope the current language global scope
*/
public CallArgFactory(
LocalScope scope, Language language, String scopeName, GlobalScope globalScope) {
this.scope = scope;
this.language = language;
this.scopeName = scopeName;
this.globalScope = globalScope;
}
/**
* Processes an ignore argument.
*
* <p>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.
*
* <p>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.
*
* <p>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));
}
}

View File

@ -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<ExpressionNode> {
private final LocalScope scope;
private final Language language;
private final String scopeName;
private final GlobalScope globalScope;
private String currentVarName = "annonymous";
/**
* Explicitly specifies all contructor parameters.
*
* @param language the name of the language for which the nodes are defined
* @param scope the language scope in which definitions are processed
* @param scopeName the name of the scope in which definitions are processed
* @param globalScope the current language global scope
*/
public ExpressionFactory(
Language language, LocalScope scope, String name, GlobalScope globalScope) {
Language language, LocalScope scope, String scopeName, GlobalScope globalScope) {
this.language = language;
this.scope = scope;
this.scopeName = name;
this.scopeName = scopeName;
this.globalScope = globalScope;
}
public ExpressionFactory(Language lang, String scopeName, GlobalScope globalScope) {
this(lang, new LocalScope(), scopeName, globalScope);
/**
* Defaults the local scope to a freshly constructed scope.
*
* @param language the name of the language for which the nodes are defined
* @param scopeName the name of the scope in which definitions are processed
* @param globalScope the current language global scope
*/
public ExpressionFactory(Language language, String scopeName, GlobalScope globalScope) {
this(language, new LocalScope(), scopeName, globalScope);
}
/**
* Defaults the local scope, and defaults the name of said scope to {@code "<root>"}
*
* @param language the name of the language for which the nodes are defined
* @param globalScope the current language global scope
*/
public ExpressionFactory(Language language, GlobalScope globalScope) {
this(language, "<root>", globalScope);
}
/**
* Creates a child of this {@code ExpressionFactory}.
*
* <p>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<ExpressionNode> {
return null;
}
/**
* Creates runtime nodes representing foreign code blocks.
*
* @param lang the name of the foreign language
* @param code the code in the foreign language
* @return a runtime node representing the foreign code
*/
@Override
public ExpressionNode visitForeign(String lang, String code) {
return null;
throw new NotImplementedException();
}
/**
* Creates a runtime node representing a variable definition.
*
* <p>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<Optional<ExpressionNode>> localNode =
Supplier<Optional<ExpressionNode>> localVariableNode =
() -> scope.getSlot(name).map(ReadLocalTargetNodeGen::create);
Supplier<Optional<ExpressionNode>> constructor =
Supplier<Optional<ExpressionNode>> constructorNode =
() -> globalScope.getConstructor(name).map(ConstructorNode::new);
Supplier<Optional<ExpressionNode>> globalFun =
Supplier<Optional<ExpressionNode>> globalDefinitionNode =
() -> globalScope.getGlobalCallTarget(name).map(ReadGlobalTargetNode::new);
return Stream.of(localNode, constructor, globalFun)
return Stream.of(localVariableNode, constructorNode, globalDefinitionNode)
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
@ -103,51 +193,167 @@ public class ExpressionFactory implements AstExpressionVisitor<ExpressionNode> {
.orElseThrow(() -> new VariableDoesNotExistException(name));
}
/**
* Creates a runtime node representing the body of a function.
*
* <p>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<String> arguments, List<AstExpression> statements, AstExpression retValue) {
List<ExpressionNode> argRewrites = new ArrayList<>();
List<AstArgDefinition> arguments, List<AstExpression> expressions, AstExpression retValue) {
ArgDefinitionFactory argFactory =
new ArgDefinitionFactory(scope, language, scopeName, globalScope);
ArgumentDefinition[] argDefinitions = new ArgumentDefinition[arguments.size()];
List<ExpressionNode> argExpressions = new ArrayList<>();
Set<String> seenArgNames = new HashSet<>();
// Note [Rewriting Arguments]
for (int i = 0; i < arguments.size(); i++) {
FrameSlot slot = scope.createVarSlot(arguments.get(i));
ReadArgumentNode readArg = new ReadArgumentNode(i);
ArgumentDefinition arg = arguments.get(i).visit(argFactory, i);
argDefinitions[i] = arg;
FrameSlot slot = scope.createVarSlot(arg.getName());
ReadArgumentNode readArg = new ReadArgumentNode(i, arg.getDefaultValue().orElse(null));
AssignmentNode assignArg = AssignmentNodeGen.create(readArg, slot);
argRewrites.add(assignArg);
argExpressions.add(assignArg);
String argName = arg.getName();
if (seenArgNames.contains(argName)) {
throw new DuplicateArgumentNameException(argName);
} else {
seenArgNames.add(argName);
}
}
List<ExpressionNode> statementNodes =
statements.stream().map(stmt -> stmt.visit(this)).collect(Collectors.toList());
List<ExpressionNode> allStatements = new ArrayList<>();
allStatements.addAll(argRewrites);
allStatements.addAll(statementNodes);
ExpressionNode expr = retValue.visit(this);
FunctionBodyNode functionBodyNode =
new FunctionBodyNode(allStatements.toArray(new ExpressionNode[0]), expr);
RootNode rootNode =
List<ExpressionNode> fnBodyExpressionNodes =
expressions.stream().map(stmt -> stmt.visit(this)).collect(Collectors.toList());
List<ExpressionNode> allFnExpressions = new ArrayList<>();
allFnExpressions.addAll(argExpressions);
allFnExpressions.addAll(fnBodyExpressionNodes);
ExpressionNode returnExpr = retValue.visit(this);
FunctionBodyNode fnBodyNode =
new FunctionBodyNode(allFnExpressions.toArray(new ExpressionNode[0]), returnExpr);
RootNode fnRootNode =
new EnsoRootNode(
language, scope.getFrameDescriptor(), functionBodyNode, null, "lambda::" + scopeName);
RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(rootNode);
return new CreateFunctionNode(callTarget);
language, scope.getFrameDescriptor(), fnBodyNode, null, "lambda::" + scopeName);
RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(fnRootNode);
return new CreateFunctionNode(callTarget, argDefinitions);
}
/* Note [Rewriting Arguments]
* ~~~~~~~~~~~~~~~~~~~~~~~~~~
* While it would be tempting to handle function arguments as a special case of a lookup, it is
* instead far simpler to rewrite them such that they just become bindings in the function local
* scope. This occurs for both explicitly passed argument values, and those that have been
* defaulted.
*
* For each argument, the following algorithm is executed:
*
* 1. Argument Conversion: Arguments are converted into their definitions so as to provide a
* compact representation of all known information about that argument.
* 2. Frame Conversion: A variable slot is created in the function's local frame to contain the
* value of the function argument.
* 3. Read Provision: A `ReadArgumentNode` is generated to allow that function argument to be
* treated purely as a local variable access. See Note [Handling Argument Defaults] for more
* information on how this works.
* 4. Value Assignment: A `AssignmentNode` is created to connect the argument value to the frame
* slot created in Step 2.
* 5. Body Rewriting: The expression representing the argument is written into the function body,
* thus allowing it to be read simply.
*/
/**
* Creates a runtime node representing a function.
*
* <p>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<String> arguments, List<AstExpression> statements, AstExpression retValue) {
List<AstArgDefinition> arguments, List<AstExpression> expressions, AstExpression retValue) {
ExpressionFactory child = createChild(currentVarName);
ExpressionNode fun = child.processFunctionBody(arguments, statements, retValue);
ExpressionNode fun = child.processFunctionBody(arguments, expressions, retValue);
fun.markTail();
return fun;
}
/**
* Creates a runtime node representing a case function.
*
* <p>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<AstExpression> arguments) {
return InvokeNodeGen.create(
arguments.stream().map(arg -> arg.visit(this)).toArray(ExpressionNode[]::new),
function.visit(this));
public ExpressionNode visitCaseFunction(
List<AstArgDefinition> arguments, List<AstExpression> expressions, AstExpression retValue) {
ExpressionFactory child = createChild(currentVarName);
return child.processFunctionBody(arguments, expressions, retValue);
}
/**
* Creates a runtime node representing function application.
*
* @param function the function being called
* @param arguments the arguments being applied to the function
* @return a runtime node representing the function call
*/
@Override
public ExpressionNode visitFunctionApplication(
AstExpression function, List<AstCallArg> arguments) {
CallArgFactory argFactory = new CallArgFactory(scope, language, scopeName, globalScope);
List<CallArgument> callArgs = new ArrayList<>();
for (int position = 0; position < arguments.size(); ++position) {
CallArgument arg = arguments.get(position).visit(argFactory, position);
callArgs.add(arg);
}
return InvokeCallableNodeGen.create(
callArgs.stream().toArray(CallArgument[]::new), function.visit(this));
}
/**
* Creates a runtime node representing a conditional expression.
*
* @param cond the condition
* @param ifTrue the code to execute if {@code cond} is true
* @param ifFalse the code to execute if {@code cond} is false
* @return a runtime node representing the conditional
*/
@Override
public ExpressionNode visitIf(AstExpression cond, AstExpression ifTrue, AstExpression ifFalse) {
return new IfZeroNode(cond.visit(this), ifTrue.visit(this), ifFalse.visit(this));
}
/**
* Creates a runtime node representing an assignment expression.
*
* @param varName the name of the variable
* @param expr the expression whose result is assigned to {@code varName}
* @return a runtime node representing the assignment
*/
@Override
public ExpressionNode visitAssignment(String varName, AstExpression expr) {
currentVarName = varName;
@ -155,19 +361,25 @@ public class ExpressionFactory implements AstExpressionVisitor<ExpressionNode> {
return AssignmentNodeGen.create(expr.visit(this), slot);
}
/**
* Creates a runtime node representing a print expression.
*
* @param body an expression that computes the value to print
* @return a runtime node representing the print
*/
@Override
public ExpressionNode visitPrint(AstExpression body) {
return new PrintNode(body.visit(this));
}
@Override
public ExpressionNode visitCaseFunction(
List<String> arguments, List<AstExpression> statements, AstExpression retValue) {
ExpressionFactory child = createChild(currentVarName);
ExpressionNode fun = child.processFunctionBody(arguments, statements, retValue);
return fun;
}
/**
* Creates a runtime node representing a pattern match.
*
* @param target the value to destructure in the pattern match
* @param branches the cases of the pattern match
* @param fallback any fallback case for the pattern match
* @return a runtime node representing a pattern match expression
*/
@Override
public ExpressionNode visitMatch(
AstExpression target, List<AstCase> branches, Optional<AstCaseFunction> fallback) {
@ -179,6 +391,8 @@ public class ExpressionFactory implements AstExpressionVisitor<ExpressionNode> {
new ConstructorCaseNode(
branch.cons().visit(this), branch.function().visit(this)))
.toArray(CaseNode[]::new);
// Note [Pattern Match Fallbacks]
CaseNode fallbackNode =
fallback
.map(fb -> (CaseNode) new FallbackNode(fb.visit(this)))
@ -186,4 +400,12 @@ public class ExpressionFactory implements AstExpressionVisitor<ExpressionNode> {
return new MatchNode(targetNode, cases, fallbackNode);
}
/* Note [Pattern Match Fallbacks]
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Enso in its current state has no coverage checking for constructors on pattern matches as it
* has no sense of what constructors contribute to make a 'type'. This means that, in absence of a
* user-provided fallback or catch-all case in a pattern match, the interpreter has to ensure that
* it has one to catch that error.
*/
}

View File

@ -1,13 +1,26 @@
package org.enso.interpreter.builder;
import com.oracle.truffle.api.TruffleFile;
import com.oracle.truffle.api.TruffleFile.FileTypeDetector;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import org.apache.tika.detect.DefaultEncodingDetector;
import org.apache.tika.detect.EncodingDetector;
import org.enso.interpreter.Constants;
import java.io.IOException;
import java.nio.charset.Charset;
/**
* A detector for finding a {@link TruffleFile file's} MIME type and encoding.
*/
public final class FileDetector implements TruffleFile.FileTypeDetector {
/**
* Finds the MINE type for a given {@link TruffleFile}.
*
* @param file the {@link TruffleFile file} to find a MIME type for
* @return the MIME type or {@code null} if the MIME type is not recognized
* @throws IOException if an I/O error occurs
*/
@Override
public String findMimeType(TruffleFile file) throws IOException {
String name = file.getName();
@ -17,9 +30,22 @@ public final class FileDetector implements TruffleFile.FileTypeDetector {
return null;
}
/**
* For a file containing an encoding information returns the encoding.
*
* Enso uses Apache Tika for determining the encoding for a provided file, but this is an inexact
* science at best.
*
* @param file the {@link TruffleFile file} to find an encoding for. It's guaranteed that
* the {@code file} has a MIME type supported by the language registering this
* {@link FileTypeDetector}.
* @return the file encoding or {@code null} if the file does not provide encoding
* @throws IOException if an I/O error occurs
*/
@Override
public Charset findEncoding(TruffleFile file) throws IOException {
// TODO [AA] Give this a proper implementation.
return null;
InputStream fileReader = file.newInputStream();
EncodingDetector detector = new DefaultEncodingDetector();
return detector.detect(fileReader, null);
}
}

View File

@ -1,21 +0,0 @@
package org.enso.interpreter.builder;
import com.oracle.truffle.api.frame.FrameSlot;
public class FramePointer {
private final int parentLevel;
private final FrameSlot frameSlot;
public FramePointer(int parentLevel, FrameSlot frameSlot) {
this.parentLevel = parentLevel;
this.frameSlot = frameSlot;
}
public int getParentLevel() {
return parentLevel;
}
public FrameSlot getFrameSlot() {
return frameSlot;
}
}

View File

@ -3,34 +3,69 @@ package org.enso.interpreter.builder;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.frame.FrameDescriptor;
import org.enso.interpreter.*;
import java.util.List;
import org.enso.interpreter.AstAssignment;
import org.enso.interpreter.AstExpression;
import org.enso.interpreter.AstGlobalScope;
import org.enso.interpreter.AstGlobalScopeVisitor;
import org.enso.interpreter.AstTypeDef;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.EnsoRootNode;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.runtime.AtomConstructor;
import java.util.List;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.scope.GlobalScope;
/**
* A {@code GlobalScopeExpressionFactory} is responsible for converting the top-level definitions of
* an Enso program into AST nodes for the interpreter to evaluate.
*/
public class GlobalScopeExpressionFactory implements AstGlobalScopeVisitor<ExpressionNode> {
private final Language language;
/**
* Creates a factory for the given language.
*
* @param language the name of the language for which this factory is creating nodes
*/
public GlobalScopeExpressionFactory(Language language) {
this.language = language;
}
/**
* Executes the factory on a global expression.
*
* @param expr the expression to execute on
* @return a runtime node representing the top-level expression
*/
public ExpressionNode run(AstGlobalScope expr) {
return expr.visit(this);
}
/**
* Processes definitions in the language global scope.
*
* @param typeDefs any type definitions defined in the global scope
* @param bindings any bindings made in the global scope
* @param executableExpression the executable expression for the program
* @return a runtime node representing the whole top-level program scope
*/
@Override
public ExpressionNode visitGlobalScope(
List<AstTypeDef> typeDefs, List<AstAssignment> bindings, AstExpression expression) {
List<AstTypeDef> typeDefs, List<AstAssignment> bindings, AstExpression executableExpression) {
GlobalScope globalScope = new GlobalScope();
bindings.forEach(binding -> globalScope.registerName(binding.name()));
for (AstTypeDef type : typeDefs) {
globalScope.registerConstructor(new AtomConstructor(type.name(), type.getArguments()));
ArgDefinitionFactory argFactory = new ArgDefinitionFactory(language, globalScope);
ArgumentDefinition[] argDefs = new ArgumentDefinition[type.getArguments().size()];
for (int i = 0; i < type.getArguments().size(); ++i) {
argDefs[i] = type.getArguments().get(i).visit(argFactory, i);
}
globalScope.registerConstructor(new AtomConstructor(type.name(), argDefs));
}
for (AstAssignment binding : bindings) {
@ -47,6 +82,6 @@ public class GlobalScopeExpressionFactory implements AstGlobalScopeVisitor<Expre
}
ExpressionFactory factory = new ExpressionFactory(this.language, globalScope);
return factory.run(expression);
return factory.run(executableExpression);
}
}

View File

@ -1,59 +0,0 @@
package org.enso.interpreter.builder;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.FrameSlot;
import org.enso.interpreter.runtime.errors.VariableRedefinitionException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class LocalScope {
private Map<String, FrameSlot> items;
private FrameDescriptor frameDescriptor;
private LocalScope parent;
public LocalScope() {
items = new HashMap<>();
frameDescriptor = new FrameDescriptor();
parent = null;
}
public LocalScope(LocalScope parent) {
this();
this.parent = parent;
}
public FrameDescriptor getFrameDescriptor() {
return frameDescriptor;
}
public LocalScope getParent() {
return parent;
}
public LocalScope createChild() {
return new LocalScope(this);
}
public FrameSlot createVarSlot(String name) {
if (items.containsKey(name)) throw new VariableRedefinitionException(name);
FrameSlot slot = frameDescriptor.addFrameSlot(name);
items.put(name, slot);
return slot;
}
public Optional<FramePointer> getSlot(String name) {
LocalScope scope = this;
int parentCounter = 0;
while (scope != null) {
FrameSlot slot = scope.items.get(name);
if (slot != null) {
return Optional.of(new FramePointer(parentCounter, slot));
}
scope = scope.parent;
parentCounter++;
}
return Optional.empty();
}
}

View File

@ -0,0 +1,45 @@
package org.enso.interpreter.node;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.dsl.ReportPolymorphism;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeInfo;
/** A base type for all Enso language nodes. */
@NodeInfo(shortName = "Base", description = "A base node for the Enso AST")
@ReportPolymorphism
public class BaseNode extends Node {
private @CompilationFinal boolean isTail = false;
/**
* Sets whether the node is tail-recursive.
*
* @param isTail whether or not the node is tail-recursive.
*/
public void setTail(boolean isTail) {
this.isTail = isTail;
}
/**
* Marks the node as tail-recursive.
*/
final public void markTail() {
setTail(true);
}
/**
* Marks the node as not tail-recursive.
*/
final public void markNotTail() {
setTail(false);
}
/**
* Checks if the node is tail-recursive.
*
* @return {@code true} if the node is tail-recursive, otherwise {@code false}
*/
public boolean isTail() {
return isTail;
}
}

View File

@ -6,11 +6,26 @@ import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.SourceSection;
import org.enso.interpreter.Language;
/**
* This node represents the root of all Enso computations.
*
* <p>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);
}
}

View File

@ -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.
*
* <p>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.
*
* <p>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);
}

View File

@ -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.
*
* <p>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.
*
* <p>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.
*
* <p>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);
}

View File

@ -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.
*
* <p>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.
*
* <p>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);
}
}

View File

@ -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.
*
* <p>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.
*/
}

View File

@ -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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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;
}
}

View File

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

View File

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

View File

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

View File

@ -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.
*
* <p>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("<TCO Function>", FrameSlotKind.Object);
resultSlot = descriptor.findOrAddFrameSlot("<TCO Result>", FrameSlotKind.Object);
argsSlot = descriptor.findOrAddFrameSlot("<TCO Arguments>", FrameSlotKind.Object);
dispatchNode = ExecuteCallNodeGen.create();
}
/**
* Sets the call target for the next call made using {@code frame}.
*
* @param frame the stack frame for execution
* @param function the function to execute in {@code frame}
* @param arguments the arguments to execute {@code function} with
*/
public void setNextCall(VirtualFrame frame, Object function, Object[] arguments) {
frame.setObject(functionSlot, function);
frame.setObject(argsSlot, arguments);
}
/**
* Obtains the result of looping execution.
*
* @param frame the stack frame for execution
* @return the result of execution in {@code frame}
*/
public Object getResult(VirtualFrame frame) {
return FrameUtil.getObjectSafe(frame, resultSlot);
}
/**
* Generates the next call to be made during looping execution.
*
* @param frame the stack frame for execution
* @return the function to be executed next in the loop
*/
public Object getNextFunction(VirtualFrame frame) {
Object result = FrameUtil.getObjectSafe(frame, functionSlot);
frame.setObject(functionSlot, null);
return result;
}
/**
* Generates the next set of arguments to the looping function.
*
* @param frame the stack frame for execution
* @return the arguments to be applied to the next function
*/
public Object[] getNextArgs(VirtualFrame frame) {
Object[] result = (Object[]) FrameUtil.getObjectSafe(frame, argsSlot);
frame.setObject(argsSlot, null);
return result;
}
/**
* Executes the node in a repeating fashion until the call is complete.
*
* @param frame the stack frame for execution
* @return {@code true} if execution is continuing, {@code false} otherwise
*/
@Override
public boolean executeRepeating(VirtualFrame frame) {
try {
Object function = getNextFunction(frame);
Object[] arguments = getNextArgs(frame);
frame.setObject(resultSlot, dispatchNode.executeCall(function, arguments));
return false;
} catch (TailCallException e) {
setNextCall(frame, e.getFunction(), e.getArguments());
return true;
}
}
}
}

View File

@ -0,0 +1,35 @@
package org.enso.interpreter.node.callable.dispatch;
import com.oracle.truffle.api.CompilerDirectives;
import org.enso.interpreter.node.callable.ExecuteCallNode;
import org.enso.interpreter.node.callable.ExecuteCallNodeGen;
import org.enso.interpreter.optimiser.tco.TailCallException;
/**
* Optimistic version of {@link CallOptimiserNode} for the non tail call recursive case. Tries to
* just call the function. If that turns out to be a tail call, it replaces itself with a {@link
* LoopingCallOptimiserNode}. Thanks to this design, the (much more common) case of calling a
* function in a non-tail position does not force the overhead of loop.
*/
public class SimpleCallOptimiserNode extends CallOptimiserNode {
@Child private ExecuteCallNode executeCallNode = ExecuteCallNodeGen.create();
/**
* 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) {
try {
return executeCallNode.executeCall(callable, arguments);
} catch (TailCallException e) {
CompilerDirectives.transferToInterpreterAndInvalidate();
CallOptimiserNode replacement = new LoopingCallOptimiserNode();
this.replace(replacement);
return replacement.executeDispatch(e.getFunction(), e.getArguments());
}
}
}

View File

@ -0,0 +1,62 @@
package org.enso.interpreter.node.callable.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.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.function.Function;
/**
* This node is responsible for representing the definition of a function. It contains information
* about the function's arguments, as well as the target for calling said function.
*/
public class CreateFunctionNode extends ExpressionNode {
private final RootCallTarget callTarget;
private final ArgumentDefinition[] args;
/**
* Creates a new node to represent a function definition.
*
* @param callTarget the target for calling the function represented by this node
* @param args information on the arguments to the function
*/
public CreateFunctionNode(RootCallTarget callTarget, ArgumentDefinition[] args) {
this.callTarget = callTarget;
this.args = args;
}
/**
* Sets the tail-recursiveness of the function.
*
* @param isTail whether or not the function is tail-recursive
*/
@Override
public void setTail(boolean isTail) {
((EnsoRootNode) callTarget.getRootNode()).setTail(isTail);
}
/**
* Generates the provided function definition in the given stack {@code frame}.
*
* @param frame the stack frame for execution
* @return the function defined by this node
*/
@Override
public Function executeFunction(VirtualFrame frame) {
MaterializedFrame scope = frame.materialize();
return new Function(callTarget, scope, this.args);
}
/**
* Executes the current node.
*
* @param frame the stack frame for execution
* @return the result of executing the node
*/
@Override
public Object executeGeneric(VirtualFrame frame) {
return executeFunction(frame);
}
}

View File

@ -0,0 +1,52 @@
package org.enso.interpreter.node.callable.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;
/**
* This node defines the body of a function for execution, as well as the protocol for executing the
* function body.
*/
@NodeInfo(shortName = "{}")
public class FunctionBodyNode extends ExpressionNode {
@Children private final ExpressionNode[] statements;
@Child private ExpressionNode returnExpr;
/**
* Creates a new node to represent the body of a function.
*
* @param expressions the function body
* @param returnExpr the return expression from the function
*/
public FunctionBodyNode(ExpressionNode[] expressions, ExpressionNode returnExpr) {
this.statements = expressions;
this.returnExpr = returnExpr;
}
/**
* Sets whether or not the function is tail-recursive.
*
* @param isTail whether or not the function is tail-recursive.
*/
@Override
public void setTail(boolean isTail) {
returnExpr.setTail(isTail);
}
/**
* Executes the body of the function.
*
* @param frame the stack frame for execution
* @return the result of executing this function
*/
@Override
@ExplodeLoop
public Object executeGeneric(VirtualFrame frame) {
for (ExpressionNode statement : statements) {
statement.executeVoid(frame);
}
return returnExpr.executeGeneric(frame);
}
}

View File

@ -2,13 +2,24 @@ package org.enso.interpreter.node.controlflow;
import com.oracle.truffle.api.nodes.ControlFlowException;
/** This exception is used to signal when a certain branch in a case expression has been taken. */
public class BranchSelectedException extends ControlFlowException {
private final Object result;
/**
* Creates a new exception instance.
*
* @param result the result of executing the branch this is thrown from
*/
public BranchSelectedException(Object result) {
this.result = result;
}
/**
* Gets the result of executing the case branch in question.
*
* @return the result of executing the case branch from which this is thrown
*/
public Object getResult() {
return result;
}

View File

@ -1,11 +1,19 @@
package org.enso.interpreter.node.controlflow;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import org.enso.interpreter.runtime.Atom;
import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.runtime.callable.atom.Atom;
public abstract class CaseNode extends Node {
public void markTail() {}
/** An abstract representation of a case expression. */
public abstract class CaseNode extends BaseNode {
/**
* Executes 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 abstract void execute(VirtualFrame frame, Atom target) throws UnexpectedResultException;
}

View File

@ -4,33 +4,56 @@ import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import com.oracle.truffle.api.profiles.ConditionProfile;
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.AtomConstructor;
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.atom.AtomConstructor;
import org.enso.interpreter.runtime.callable.function.Function;
/** An implementation of the case expression specialised to working on explicit constructors. */
public class ConstructorCaseNode extends CaseNode {
@Child private ExpressionNode matcher;
@Child private ExpressionNode branch;
@Child private CallNode callNode = CallNodeGen.create();
@Child private ExecuteCallNode executeCallNode = ExecuteCallNodeGen.create();
private final ConditionProfile profile = ConditionProfile.createCountingProfile();
/**
* Creates a new node for handling matching on a case expression.
*
* @param matcher the expression to use for matching
* @param branch the expression to be executed if (@code matcher} matches
*/
public ConstructorCaseNode(ExpressionNode matcher, ExpressionNode branch) {
this.matcher = matcher;
this.branch = branch;
}
/**
* Sets whether or not the case expression is tail recursive.
*
* @param isTail whether or not the case expression is tail-recursive
*/
@Override
public void markTail() {
branch.markTail();
public void setTail(boolean isTail) {
branch.setTail(isTail);
}
/**
* Executes the case expression.
*
* <p>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()));
}
}
}

View File

@ -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());

View File

@ -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.
*
* <p>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]));
}
}

View File

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

View File

@ -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.
*/
}

View File

@ -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);

View File

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

View File

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

View File

@ -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.
*
* <p>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 {}

View File

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

View File

@ -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.")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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}).
*
* <p>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("<TCO Function>", FrameSlotKind.Object);
resultSlot = descriptor.findOrAddFrameSlot("<TCO Result>", FrameSlotKind.Object);
argsSlot = descriptor.findOrAddFrameSlot("<TCO Arguments>", FrameSlotKind.Object);
dispatchNode = CallNodeGen.create();
}
public void setNextCall(VirtualFrame frame, Object function, Object[] arguments) {
frame.setObject(functionSlot, function);
frame.setObject(argsSlot, arguments);
}
public Object getResult(VirtualFrame frame) {
return FrameUtil.getObjectSafe(frame, resultSlot);
}
public Object getNextFunction(VirtualFrame frame) {
Object result = FrameUtil.getObjectSafe(frame, functionSlot);
frame.setObject(functionSlot, null);
return result;
}
public Object[] getNextArgs(VirtualFrame frame) {
Object[] result = (Object[]) FrameUtil.getObjectSafe(frame, argsSlot);
frame.setObject(argsSlot, null);
return result;
}
@Override
public boolean executeRepeating(VirtualFrame frame) {
try {
Object function = getNextFunction(frame);
Object[] arguments = getNextArgs(frame);
frame.setObject(resultSlot, dispatchNode.executeCall(function, arguments));
return false;
} catch (TailCallException e) {
setNextCall(frame, e.getFunction(), e.getArguments());
return true;
}
}
}
}

View File

@ -1,20 +0,0 @@
package org.enso.interpreter.node.function;
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.Function;
@NodeInfo(description = "Read function argument.")
public class ReadArgumentNode extends ExpressionNode {
private final int index;
public ReadArgumentNode(int index) {
this.index = index;
}
@Override
public Object executeGeneric(VirtualFrame frame) {
return Function.ArgumentsHelper.getPositionalArguments(frame.getArguments())[index];
}
}

View File

@ -1,26 +0,0 @@
package org.enso.interpreter.node.function;
import com.oracle.truffle.api.CompilerDirectives;
import org.enso.interpreter.optimiser.TailCallException;
/**
* Optimistic version of {@link DispatchNode} for the non tail call recursive case. Tries to just
* call the function. If that turns out to be a tail call, it replaces itself with a {@link
* LoopingDispatchNode}. Thanks to this design, the (much more common) case of calling a function in
* a non-tail position does not force the overhead of loop.
*/
public class SimpleDispatchNode extends DispatchNode {
@Child private CallNode callNode = CallNodeGen.create();
@Override
public Object executeDispatch(Object receiver, Object[] arguments) {
try {
return callNode.executeCall(receiver, arguments);
} catch (TailCallException e) {
CompilerDirectives.transferToInterpreterAndInvalidate();
DispatchNode replacement = new LoopingDispatchNode();
this.replace(replacement);
return replacement.executeDispatch(e.getFunction(), e.getArguments());
}
}
}

View File

@ -8,15 +8,20 @@ import com.oracle.truffle.api.frame.FrameSlotKind;
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 represents an assignment to a variable in a given scope. */
@NodeInfo(shortName = "=", description = "Assigns expression result to a variable.")
@NodeChild(value = "rhsNode", type = ExpressionNode.class)
@NodeField(name = "frameSlot", type = FrameSlot.class)
public abstract class AssignmentNode extends ExpressionNode {
public abstract FrameSlot getFrameSlot();
/**
* Writes a long value into the provided frame.
*
* @param frame the frame to write to
* @param value the value to write
* @return the {@link AtomConstructor#UNIT unit} type
*/
@Specialization
protected Object writeLong(VirtualFrame frame, long value) {
frame.getFrameDescriptor().setFrameSlotKind(getFrameSlot(), FrameSlotKind.Long);
@ -25,6 +30,13 @@ public abstract class AssignmentNode extends ExpressionNode {
return AtomConstructor.UNIT.newInstance();
}
/**
* Writes an object value into the provided frame.
*
* @param frame the frame to write to
* @param value the value to write
* @return the {@link AtomConstructor#UNIT unit} type
*/
@Specialization
protected Object writeObject(VirtualFrame frame, Object value) {
frame.getFrameDescriptor().setFrameSlotKind(getFrameSlot(), FrameSlotKind.Object);
@ -32,4 +44,11 @@ public abstract class AssignmentNode extends ExpressionNode {
return AtomConstructor.UNIT.newInstance();
}
/**
* Gets the current frame slot
*
* @return the frame slot being written to
*/
public abstract FrameSlot getFrameSlot();
}

View File

@ -8,16 +8,27 @@ import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.runtime.GlobalCallTarget;
/** A representation of a read from a global call target. */
@NodeInfo(shortName = "Global Call Target")
public class ReadGlobalTargetNode extends ExpressionNode {
private final GlobalCallTarget globalCallTarget;
@Child private DirectCallNode directCallNode = null;
/**
* Creates a new node to read from a global call target.
*
* @param globalCallTarget the call target to read from
*/
public ReadGlobalTargetNode(GlobalCallTarget globalCallTarget) {
this.globalCallTarget = globalCallTarget;
}
/**
* Reads from the global call target.
*
* @param frame the stack frame for execution
* @return the result of executing the call target
*/
@Override
public Object executeGeneric(VirtualFrame frame) {
if (directCallNode == null) {

View File

@ -2,32 +2,31 @@ package org.enso.interpreter.node.scope;
import com.oracle.truffle.api.dsl.NodeField;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.*;
import com.oracle.truffle.api.frame.Frame;
import com.oracle.truffle.api.frame.FrameSlotTypeException;
import com.oracle.truffle.api.frame.FrameUtil;
import com.oracle.truffle.api.frame.MaterializedFrame;
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.builder.FramePointer;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.runtime.Function;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.scope.FramePointer;
/** Reads from a local target (variable or call target). */
@NodeInfo(shortName = "readVar", description = "Access local variable value.")
@NodeField(name = "framePointer", type = FramePointer.class)
public abstract class ReadLocalTargetNode extends ExpressionNode {
public abstract FramePointer getFramePointer();
public MaterializedFrame getParentFrame(Frame frame) {
return Function.ArgumentsHelper.getLocalScope(frame.getArguments());
}
@ExplodeLoop
public MaterializedFrame getProperFrame(Frame frame) {
MaterializedFrame currentFrame = getParentFrame(frame);
for (int i = 1; i < getFramePointer().getParentLevel(); i++) {
currentFrame = getParentFrame(currentFrame);
}
return currentFrame;
}
/**
* Reads a {@code long} value from the frame.
*
* @param frame the stack frame to read from
* @return the value read from the appropriate slot in {@code frame}
* @throws FrameSlotTypeException when the specified frame slot does not contain a value with the
* expected type
*/
@Specialization(rewriteOn = FrameSlotTypeException.class)
protected long readLong(VirtualFrame frame) throws FrameSlotTypeException {
if (getFramePointer().getParentLevel() == 0)
@ -36,6 +35,14 @@ public abstract class ReadLocalTargetNode extends ExpressionNode {
return currentFrame.getLong(getFramePointer().getFrameSlot());
}
/**
* Reads an generic value from the frame.
*
* @param frame the stack frame to read from
* @return the value read from the appropriate slot in {@code frame}
* @throws FrameSlotTypeException when the specified frame slot does not contain a value with the
* expected type
*/
@Specialization
protected Object readGeneric(VirtualFrame frame) {
if (getFramePointer().getParentLevel() == 0)
@ -43,4 +50,32 @@ public abstract class ReadLocalTargetNode extends ExpressionNode {
MaterializedFrame currentFrame = getProperFrame(frame);
return FrameUtil.getObjectSafe(currentFrame, getFramePointer().getFrameSlot());
}
/**
* Obtains the direct parent frame for a given frame.
*
* @param frame the frame whose parent needs to be found
* @return the parent frame of {@code frame}
*/
public MaterializedFrame getParentFrame(Frame frame) {
return Function.ArgumentsHelper.getLocalScope(frame.getArguments());
}
/**
* Gets the Enso parent frame for a given frame.
*
* <p>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;
}
}

View File

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

View File

@ -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.
*
* <p>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;
}
}

View File

@ -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<String> argNames) {
this(name, argNames.size());
}
public AtomConstructor(String name, int arity) {
this.name = name;
this.arity = arity;
if (arity == 0) {
cachedInstance = new Atom(this);
} else {
cachedInstance = null;
}
}
public String getName() {
return name;
}
public int getArity() {
return arity;
}
public Atom newInstance(Object... arguments) {
if (arguments.length != arity)
throw new ArityException(arity, arguments.length);
if (cachedInstance != null) return cachedInstance;
return new Atom(this, arguments);
}
@Override
public String toString() {
return super.toString() + "<" + name + "/" + arity + ">";
}
}

View File

@ -1,18 +1,28 @@
package org.enso.interpreter.runtime;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleLanguage.Env;
import org.enso.interpreter.Language;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import org.enso.interpreter.Language;
/**
* The language context is the internal state of the language that is associated with each thread in
* a running Enso program.
*/
public class Context {
private final Language language;
private final Env environment;
private final BufferedReader input;
private final PrintWriter output;
/**
* Creates a new Enso context.
*
* @param language the language identifier
* @param environment the execution environment of the {@link TruffleLanguage}
*/
public Context(Language language, Env environment) {
this.language = language;
this.environment = environment;

View File

@ -1,69 +0,0 @@
package org.enso.interpreter.runtime;
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;
@ExportLibrary(InteropLibrary.class)
public final class Function implements TruffleObject {
private final RootCallTarget callTarget;
private final MaterializedFrame scope;
public Function(RootCallTarget callTarget, MaterializedFrame scope) {
this.callTarget = callTarget;
this.scope = scope;
}
public RootCallTarget getCallTarget() {
return callTarget;
}
public MaterializedFrame getScope() {
return scope;
}
@ExportMessage
public boolean isExecutable() {
return true;
}
@ExportMessage
abstract static class Execute {
@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);
}
@Specialization(replaces = "callDirect")
protected static Object callIndirect(
Function function, Object[] arguments, @Cached IndirectCallNode callNode) {
return callNode.call(function.getCallTarget(), function.getScope(), arguments);
}
}
public static class ArgumentsHelper {
public static Object[] buildArguments(Function function, Object[] positionalArguments) {
return new Object[] {function.getScope(), positionalArguments};
}
public static Object[] getPositionalArguments(Object[] arguments) {
return (Object[]) arguments[1];
}
public static MaterializedFrame getLocalScope(Object[] arguments) {
return (MaterializedFrame) arguments[0];
}
}
}

View File

@ -4,18 +4,35 @@ import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.interop.TruffleObject;
/**
* A representation of the top-level callable pieces of code in Enso.
*/
public class GlobalCallTarget implements TruffleObject {
@CompilationFinal private RootCallTarget target;
/**
* Creates a new call target.
*
* @param target a handle to the code to associate with this target
*/
public GlobalCallTarget(RootCallTarget target) {
this.target = target;
}
/**
* Gets the handle to the code associated with this target.
*
* @return a handle to the associated code
*/
public RootCallTarget getTarget() {
return target;
}
/**
* Sets the internal code handle.
*
* @param target a handle to the code to be associated with this target
*/
public void setTarget(RootCallTarget target) {
this.target = target;
}

View File

@ -1,16 +0,0 @@
package org.enso.interpreter.runtime;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.ImplicitCast;
import com.oracle.truffle.api.dsl.TypeSystem;
@TypeSystem({long.class, Function.class, Atom.class, AtomConstructor.class})
public class Types {
@ImplicitCast
@CompilerDirectives.TruffleBoundary
public static long castLong(int value) {
return value;
}
}

View File

@ -0,0 +1,28 @@
package org.enso.interpreter.runtime.callable;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
/** A class that represents all runtime types in Enso that can be called. */
public abstract class Callable {
private @CompilationFinal(dimensions = 1) ArgumentDefinition[] args;
/**
* Creates a new callable with the specified arguments.
*
* @param args information on the arguments defined for the callable, in the order they are
* defined
*/
public Callable(ArgumentDefinition[] args) {
this.args = args;
}
/**
* Gets the arguments defined on this callable.
*
* @return information on the arguments defined for the callable
*/
public ArgumentDefinition[] getArgs() {
return this.args;
}
}

View File

@ -0,0 +1,70 @@
package org.enso.interpreter.runtime.callable.argument;
import java.util.Optional;
import org.enso.interpreter.node.ExpressionNode;
/** Tracks the specifics about how arguments are defined at the callable definition site. */
public class ArgumentDefinition {
private final int position;
private final String name;
private final Optional<ExpressionNode> defaultValue;
/**
* Creates a new argument definition without a default value.
*
* @param position the position of the argument at the definition site
* @param name the name of the argument
*/
public ArgumentDefinition(int position, String name) {
this(position, name, null);
}
/**
* Creates a new argument definition with a default value.
*
* @param position the position of the argument at the definition site
* @param name the name of the argument
* @param defaultValue the default value of the argument
*/
public ArgumentDefinition(int position, String name, ExpressionNode defaultValue) {
this.position = position;
this.name = name;
this.defaultValue = Optional.ofNullable(defaultValue);
}
/**
* Gets the argument position at the definition site.
*
* @return the argument's position at the definition side
*/
public int getPosition() {
return this.position;
}
/**
* Gets the argument's name.
*
* @return the name of the argument
*/
public String getName() {
return this.name;
}
/**
* Gets the argument's default value.
*
* @return the default value, if present, otherwise {@link Optional#empty()}
*/
public Optional<ExpressionNode> getDefaultValue() {
return this.defaultValue;
}
/**
* Checks if the argument has a default value.
*
* @return {@code true} if a default value is present, otherwise {@code false}
*/
public boolean hasDefaultValue() {
return this.defaultValue.isPresent();
}
}

View File

@ -0,0 +1,85 @@
package org.enso.interpreter.runtime.callable.argument;
import org.enso.interpreter.node.ExpressionNode;
/**
* Tracks the specifics about how arguments are specified at a call site.
*/
public class CallArgument {
private final String name;
private ExpressionNode expression;
/**
* Creates an argument passed positionally.
*
* @param expression the value of the argument
*/
public CallArgument(ExpressionNode expression) {
this(null, expression);
}
/**
* Creates an argument that ignores the default (for currying purposes).
*
* @param name the name of the argument whose default should be ignored
*/
public CallArgument(String name) {
this(name, null);
}
/**
* Creates an argument passed by name.
*
* @param name the name of the argument being applied
* @param expression the value of the argument
*/
public CallArgument(String name, ExpressionNode expression) {
this.name = name;
this.expression = expression;
}
/**
* Checks if the argument is an ignore.
*
* @return {@code true} if it is an ignore, otherwise {@code false}
*/
public boolean isIgnored() {
return (this.name != null) && (this.expression == null);
}
/**
* Checks if the argument is passed by name.
*
* @return {@code true} if it is passed by name, otherwise {@code false}
*/
public boolean isNamed() {
return (this.name != null) && (this.expression != null);
}
/**
* Checks if the argument is passed by position.
*
* @return {@code true} if it is passed by position, otherwise {@code false}
*/
public boolean isPositional() {
return !isNamed() && !isIgnored();
}
/**
* Gets the argument name.
*
* @return the name of the argument
*/
public String getName() {
return this.name;
}
/**
* Gets the expression representing the argument's value.
*
* @return the expression representing the value of the argument
*/
public ExpressionNode getExpression() {
return expression;
}
}

View File

@ -0,0 +1,195 @@
package org.enso.interpreter.runtime.callable.argument;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import org.enso.interpreter.runtime.callable.argument.sentinel.UnappliedArgumentSentinel;
import org.enso.interpreter.runtime.callable.Callable;
import org.enso.interpreter.runtime.error.ArgumentMappingException;
import org.enso.interpreter.runtime.error.NotInvokableException;
import org.enso.interpreter.runtime.type.TypesGen;
/**
* Tracks simple information about call-site arguments, used to make processing of caller argument
* lists much mpre simple.
*/
public class CallArgumentInfo {
private final String name;
private final boolean isNamed;
private final boolean isPositional;
private final boolean isIgnored;
/**
* Creates the information from a {@link CallArgument}.
*
* @param callArgNode the structure to take information from
*/
public CallArgumentInfo(CallArgument callArgNode) {
this(
callArgNode.getName(),
callArgNode.isNamed(),
callArgNode.isPositional(),
callArgNode.isIgnored());
}
/**
* Creates the information explicitly.
*
* @param name the name of the argument, if present
* @param isNamed whether or not the argument is passed by name
* @param isPositional whether or not the argument is passed by position
* @param isIgnored whether or not the argument is an ignore
*/
public CallArgumentInfo(String name, boolean isNamed, boolean isPositional, boolean isIgnored) {
this.name = name;
this.isNamed = isNamed;
this.isPositional = isPositional;
this.isIgnored = isIgnored;
}
/**
* Gets the name of the argument.
*
* @return the name of the argument at the call site, if specified
*/
public String getName() {
return name;
}
/**
* Checks whether the argument was applied by name or not.
*
* @return {@code true} if the argument was applied by name, otherwise {@code false}
*/
public boolean isNamed() {
return isNamed;
}
/**
* Checks whether the argument was applied by position or not.
*
* @return {@code true} if the argument was applied positionally, otherwise {@code false}
*/
public boolean isPositional() {
return isPositional;
}
/**
* Checks whether the argument is ignoring a default value.
*
* @return {@code true} if the argument ignores a default, otherwise {@code false}
*/
public boolean isIgnored() {
return isIgnored;
}
/**
* Reorders the arguments from the call-site to match the order at the definition site.
*
* <p>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.
*
* <p>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);
}
}
}

View File

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

View File

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

View File

@ -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();
}
}

View File

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

View File

@ -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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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];
}
}
}

View File

@ -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())));
}
}

View File

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

View File

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

View File

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

View File

@ -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<String> availableNames) {
super(
name
+ " is not a valid argument name for a function with arguments: "
+ availableNames.stream().reduce("", (l, r) -> l + ", " + r));
}
}

View File

@ -0,0 +1,30 @@
package org.enso.interpreter.runtime.error;
import com.oracle.truffle.api.TruffleException;
import com.oracle.truffle.api.nodes.Node;
/** An exception thrown when execution attempts to invoke something that cannot be invoked. */
public class NotInvokableException extends RuntimeException implements TruffleException {
private final Node node;
/**
* Creates the error.
*
* @param target the construct that was attempted to be invoked
* @param node the node where the erroneous invocation took place
*/
public NotInvokableException(Object target, Node node) {
super("Object " + target + " is not invokable.");
this.node = node;
}
/**
* The location where the erroneous invocation took place.
*
* @return the node where the error occurred
*/
@Override
public Node getLocation() {
return node;
}
}

View File

@ -1,17 +1,30 @@
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 exception thrown on a type mismatch.
*/
public class TypeError extends RuntimeException implements TruffleException {
private final Node node;
/**
* Creates the error.
*
* @param message an informative error message
* @param node the node where the error occurred
*/
public TypeError(String message, Node node) {
super(message);
this.node = node;
}
/**
* Gets the location of the error.
*
* @return the node where the error occurred.
*/
@Override
public Node getLocation() {
return node;

View File

@ -0,0 +1,19 @@
package org.enso.interpreter.runtime.error;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
/**
* An error thrown when an attempt is made to execute a callable that has not had all of its
* arguments provided.
*/
public class UnsaturatedCallException extends RuntimeException {
/**
* Creates the error.
*
* @param arg the unapplied argument
*/
public UnsaturatedCallException(ArgumentDefinition arg) {
super("The argument named " + arg.getName() + " has not been applied.");
}
}

View File

@ -1,13 +1,27 @@
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 the program attempts to read from a variable that has not been defined.
*/
public class VariableDoesNotExistException extends RuntimeException implements TruffleException {
/**
* Creates the error.
*
* @param name the name of the undefined variable
*/
public VariableDoesNotExistException(String name) {
super("Variable " + name + " is not defined.");
}
/**
* Gets the location of the error.
*
* @return the node where the error occurred.
*/
@Override
public Node getLocation() {
return null;

View File

@ -1,13 +1,27 @@
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 exception thrown when a program attempts to redefine a variable that has already been defined.
*/
public class VariableRedefinitionException extends RuntimeException implements TruffleException {
/**
* Creates the error.
*
* @param name the name of the already-defined variable
*/
public VariableRedefinitionException(String name) {
super("Variable " + name + " was already defined in this scope.");
}
/**
* Gets the location of the error.
*
* @return the node where the error occurred.
*/
@Override
public Node getLocation() {
return null;

View File

@ -1,10 +0,0 @@
package org.enso.interpreter.runtime.errors;
import com.oracle.truffle.api.TruffleException;
import com.oracle.truffle.api.nodes.Node;
public class ArityException extends RuntimeException {
public ArityException(int expected, int actual) {
super("Wrong number of arguments. Expected: " + expected + " but got: " + actual + ".");
}
}

View File

@ -1,18 +0,0 @@
package org.enso.interpreter.runtime.errors;
import com.oracle.truffle.api.TruffleException;
import com.oracle.truffle.api.nodes.Node;
public class NotInvokableException extends RuntimeException implements TruffleException {
private final Node node;
public NotInvokableException(Object target, Node node) {
super("Object " + target + " is not invokable.");
this.node = node;
}
@Override
public Node getLocation() {
return node;
}
}

View File

@ -0,0 +1,40 @@
package org.enso.interpreter.runtime.scope;
import com.oracle.truffle.api.frame.FrameSlot;
/**
* A representation of a pointer into a stack frame at a given number of levels above the current.
*/
public class FramePointer {
private final int parentLevel;
private final FrameSlot frameSlot;
/**
* A representation of a frame slot at a given level above the current frame.
*
* @param parentLevel the number of parents to move from the current frame to get here
* @param frameSlot the slot in the n-th parent frame
*/
public FramePointer(int parentLevel, FrameSlot frameSlot) {
this.parentLevel = parentLevel;
this.frameSlot = frameSlot;
}
/**
* Gets the parent level.
*
* @return the parent level represented by this {@code FramePointer}
*/
public int getParentLevel() {
return parentLevel;
}
/**
* Gets the frame slot.
*
* @return the frame slot represented by this {@code FramePointer}
*/
public FrameSlot getFrameSlot() {
return frameSlot;
}
}

View File

@ -1,36 +1,60 @@
package org.enso.interpreter.builder;
package org.enso.interpreter.runtime.scope;
import com.oracle.truffle.api.RootCallTarget;
import org.enso.interpreter.runtime.AtomConstructor;
import org.enso.interpreter.runtime.GlobalCallTarget;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.enso.interpreter.runtime.GlobalCallTarget;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
/** A representation of Enso's top-level scope. */
public class GlobalScope {
private final Map<String, GlobalCallTarget> globalNames = new HashMap<>();
private final Map<String, AtomConstructor> constructors = new HashMap<>();
/**
* Creates a new instance of the global scope.
*
* This constructor will take on the duty of registering any built-in constructors for use by the
* program.
*/
public GlobalScope() {
registerBuiltinConstructors();
}
/**
* Registers any built-in Atom constructors under name in the global scope.
*/
private void registerBuiltinConstructors() {
registerConstructor(AtomConstructor.UNIT);
registerConstructor(AtomConstructor.NIL);
registerConstructor(AtomConstructor.CONS);
}
/**
* Adds an Atom constructor definition to the global scope.
*
* @param constructor the constructor to register
*/
public void registerConstructor(AtomConstructor constructor) {
constructors.put(constructor.getName(), constructor);
}
/**
* Registers a new name for a call target in the global scope.
*
* @param name the name of the global variable
*/
public void registerName(String name) {
this.globalNames.put(name, new GlobalCallTarget(null));
}
/**
* Assigns a value to an existing global variable.
*
* @param name the name to assign to
* @param rootBinding the code to bind to that name
*/
public void updateCallTarget(String name, RootCallTarget rootBinding) {
GlobalCallTarget globalTarget = this.globalNames.get(name);
@ -41,10 +65,22 @@ public class GlobalScope {
}
}
/**
* Looks up a call target in the global scope.
*
* @param name the name of the global binding
* @return the call target associated with {@code name}, or {@link Optional#empty()}
*/
public Optional<GlobalCallTarget> getGlobalCallTarget(String name) {
return Optional.ofNullable(this.globalNames.get(name));
}
/**
* Looks up a constructor in the global scope.
*
* @param name the name of the global binding
* @return the Atom constructor associated with {@code name}, or {@link Optional#empty()}
*/
public Optional<AtomConstructor> getConstructor(String name) {
return Optional.ofNullable(this.constructors.get(name));
}

View File

@ -0,0 +1,100 @@
package org.enso.interpreter.runtime.scope;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.FrameSlot;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.enso.interpreter.runtime.error.VariableRedefinitionException;
/**
* A representation of an Enso local scope. These can be arbitrarily nested and are used to map
* between the interpreter's concept of stack frames and the guest language's concept of stack
* frames.
*/
public class LocalScope {
private Map<String, FrameSlot> items;
private FrameDescriptor frameDescriptor;
private LocalScope parent;
/** Creates a new local scope with defaulted arguments. */
public LocalScope() {
items = new HashMap<>();
frameDescriptor = new FrameDescriptor();
parent = null;
}
/**
* Creates a new local scope with a known parent.
*
* @param parent the parent scope
*/
public LocalScope(LocalScope parent) {
this();
this.parent = parent;
}
/**
* Gets the frame descriptor for this scope.
*
* <p>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<FramePointer> getSlot(String name) {
LocalScope scope = this;
int parentCounter = 0;
while (scope != null) {
FrameSlot slot = scope.items.get(name);
if (slot != null) {
return Optional.of(new FramePointer(parentCounter, slot));
}
scope = scope.parent;
parentCounter++;
}
return Optional.empty();
}
}

View File

@ -0,0 +1,35 @@
package org.enso.interpreter.runtime.type;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.ImplicitCast;
import com.oracle.truffle.api.dsl.TypeSystem;
import org.enso.interpreter.runtime.callable.Callable;
import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.callable.function.Function;
/**
* This class defines the interpreter-level type system for Enso.
*
* <p>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;
}
}

View File

@ -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 ^^ {

View File

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

View File

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

View File

@ -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 =
"""

View File

@ -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 {

View File

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

View File

@ -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
)