mirror of
https://github.com/enso-org/enso.git
synced 2024-12-22 21:41:34 +03:00
Implement named and defaulted arguments (#80)
This commit is contained in:
parent
caf8808ff7
commit
5ee1c2d194
6
.gitignore
vendored
6
.gitignore
vendored
@ -68,8 +68,14 @@ cabal.sandbox.config
|
||||
*.swp
|
||||
.projections.json
|
||||
|
||||
############################
|
||||
## Rendered Documentation ##
|
||||
############################
|
||||
|
||||
javadoc/
|
||||
|
||||
#######################
|
||||
## Benchmark Reports ##
|
||||
#######################
|
||||
bench-report.xml
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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)
|
||||
|
||||
}
|
@ -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 =
|
@ -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";
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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.
|
||||
*/
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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.
|
||||
*/
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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]));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 {}
|
||||
|
@ -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;
|
||||
|
@ -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.")
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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];
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 + ">";
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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() + ">";
|
||||
}
|
||||
}
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
@ -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())));
|
||||
}
|
||||
}
|
@ -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 + ".");
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
@ -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));
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
@ -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.");
|
||||
}
|
||||
}
|
@ -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;
|
@ -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;
|
@ -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 + ".");
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 ^^ {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 =
|
||||
"""
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
11
build.sbt
11
build.sbt
@ -102,6 +102,8 @@ lazy val interpreter = (project in file("Interpreter"))
|
||||
.settings(
|
||||
libraryDependencies ++= Seq(
|
||||
"com.chuusai" %% "shapeless" % "2.3.3",
|
||||
"org.apache.commons" % "commons-lang3" % "3.9",
|
||||
"org.apache.tika" % "tika-core" % "1.21",
|
||||
"org.graalvm.sdk" % "graal-sdk" % "19.0.0",
|
||||
"org.graalvm.sdk" % "polyglot-tck" % "19.0.0",
|
||||
"org.graalvm.truffle" % "truffle-api" % "19.0.0",
|
||||
@ -109,12 +111,11 @@ lazy val interpreter = (project in file("Interpreter"))
|
||||
"org.graalvm.truffle" % "truffle-nfi" % "19.0.0",
|
||||
"org.graalvm.truffle" % "truffle-tck" % "19.0.0",
|
||||
"org.graalvm.truffle" % "truffle-tck-common" % "19.0.0",
|
||||
"org.scalacheck" %% "scalacheck" % "1.14.0" % Test,
|
||||
"org.scalatest" %% "scalatest" % "3.2.0-SNAP10" % Test,
|
||||
"org.scalactic" %% "scalactic" % "3.0.8" % Test,
|
||||
"org.typelevel" %% "cats-core" % "2.0.0-M4",
|
||||
"org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.4",
|
||||
"org.apache.commons" % "commons-lang3" % "3.9"
|
||||
"org.scalacheck" %% "scalacheck" % "1.14.0" % Test,
|
||||
"org.scalactic" %% "scalactic" % "3.0.8" % Test,
|
||||
"org.scalatest" %% "scalatest" % "3.2.0-SNAP10" % Test,
|
||||
"org.typelevel" %% "cats-core" % "2.0.0-M4",
|
||||
),
|
||||
libraryDependencies ++= jmh
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user