Implement call strategy controls and a functional if statement. (#328)

This commit is contained in:
Marcin Kostrzewa 2019-11-13 12:27:52 +01:00 committed by GitHub
parent 43f51e9dac
commit 3929b3f72c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 393 additions and 250 deletions

View File

@ -8,7 +8,7 @@ class AtomFixtures extends InterpreterRunner {
val generateListCode =
"""
|{ |length|
| generator = { |acc, i| ifZero: [i, acc, @generator [@Cons [i, acc], i - 1]] };
| generator = { |acc, i| @ifZero [i, acc, @generator [@Cons [i, acc], i - 1]] };
| res = @generator [@Nil, length];
| res
|}

View File

@ -9,7 +9,7 @@ class NamedDefaultedArgumentFixtures extends InterpreterRunner {
"""
|{ |sumTo|
| summator = { |acc, current|
| ifZero: [current, acc, @summator [current = current - 1, acc = acc + current]]
| @ifZero [current, acc, @summator [current = current - 1, acc = acc + current]]
| };
| res = @summator [current = sumTo, acc = 0];
| res
@ -22,7 +22,7 @@ class NamedDefaultedArgumentFixtures extends InterpreterRunner {
"""
|{ |sumTo|
| summator = { |acc = 0, current|
| ifZero: [current, acc, @summator [current = current - 1, acc = acc + current]]
| @ifZero [current, acc, @summator [current = current - 1, acc = acc + current]]
| };
| res = @summator [current = sumTo];
| res

View File

@ -13,7 +13,7 @@ class RecursionFixtures extends InterpreterRunner {
val mutRecursiveCode =
"""
|summator = { |acc, current|
| ifZero: [current, acc, @summator [acc + current, current - 1]]
| @ifZero [current, acc, @summator [acc + current, current - 1]]
|}
|
|{ |sumTo|
@ -26,7 +26,7 @@ class RecursionFixtures extends InterpreterRunner {
"""
|{ |sumTo|
| summator = { |acc, current|
| ifZero: [current, acc, @summator [acc + current, current - 1]]
| @ifZero [current, acc, @summator [acc + current, current - 1]]
| };
| res = @summator [0, sumTo];
| res
@ -38,7 +38,7 @@ class RecursionFixtures extends InterpreterRunner {
val sumTCOFoldLikeCode =
"""
|{ |sumTo|
| summator = { |acc, i, f| ifZero: [i, acc, @summator [@f [acc, i], i - 1, f]] };
| summator = { |acc, i, f| @ifZero [i, acc, @summator [@f [acc, i], i - 1, f]] };
| res = @summator [0, sumTo, {|x, y| x + y }];
| res
|}
@ -49,7 +49,7 @@ class RecursionFixtures extends InterpreterRunner {
val sumRecursiveCode =
"""
|{ |sumTo|
| summator = { |i| ifZero: [i, 0, i + (@summator [i - 1])] };
| summator = { |i| @ifZero [i, 0, i + (@summator [i - 1])] };
| res = @summator [sumTo];
| res
|}
@ -60,7 +60,7 @@ class RecursionFixtures extends InterpreterRunner {
val oversaturatedRecursiveCallTCOCode =
"""
|{ |sumTo|
| summator = { |acc, i, f| ifZero: [i, acc, @summator [@f [acc, i], i - 1, f]] };
| summator = { |acc, i, f| @ifZero [i, acc, @summator [@f [acc, i], i - 1, f]] };
| res = @summator [0, sumTo, {|x| { |y| x + y }}];
| res
|}
@ -75,7 +75,7 @@ class RecursionFixtures extends InterpreterRunner {
| stateSum = { |n|
| acc = @get [@State];
| @put [@State, acc + n];
| ifZero: [n, 0, @stateSum [n-1]]
| @ifZero [n, @get [@State], @stateSum [n-1]]
| };
| @put [@State, 0];
| @stateSum [sumTo]

View File

@ -330,19 +330,6 @@ public class ExpressionFactory implements AstExpressionVisitor<ExpressionNode> {
function.visit(this), callArgs.toArray(new CallArgument[0]), defaultsExecutionMode);
}
/**
* 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.
*

View File

@ -6,7 +6,7 @@ import org.enso.interpreter.node.callable.function.CreateFunctionNode;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.callable.function.ArgumentSchema;
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.error.VariableDoesNotExistException;
import org.enso.interpreter.runtime.scope.ModuleScope;
@ -97,7 +97,10 @@ public class ModuleScopeExpressionFactory implements AstModuleScopeVisitor<Expre
method.fun().getArguments(), method.fun().getStatements(), method.fun().ret());
funNode.markTail();
Function function =
new Function(funNode.getCallTarget(), null, new ArgumentSchema(funNode.getArgs()));
new Function(
funNode.getCallTarget(),
null,
new FunctionSchema(FunctionSchema.CallStrategy.CALL_LOOP, funNode.getArgs()));
if (method.typeName().equals(Constants.ANY_TYPE_NAME)) {
moduleScope.registerMethodForAny(method.methodName(), function);

View File

@ -71,4 +71,8 @@ public abstract class ExecuteCallNode extends Node {
* @return the result of executing {@code function} on {@code arguments}
*/
public abstract Stateful executeCall(Object function, Object state, Object[] arguments);
public static ExecuteCallNode build() {
return ExecuteCallNodeGen.create();
}
}

View File

@ -66,9 +66,8 @@ public abstract class ArgumentSorterNode extends BaseNode {
Object[] arguments,
@Cached(
"build(function, getSchema(), getDefaultsExecutionMode(), getArgumentsExecutionMode(), isTail())")
CachedArgumentSorterNode mappingNode,
@Cached CallOptimiserNode optimiser) {
return mappingNode.execute(function, state, arguments, optimiser);
CachedArgumentSorterNode mappingNode) {
return mappingNode.execute(function, state, arguments);
}
/**
@ -91,8 +90,7 @@ public abstract class ArgumentSorterNode extends BaseNode {
getSchema(),
getDefaultsExecutionMode(),
getArgumentsExecutionMode(),
isTail()),
CallOptimiserNode.create());
isTail()));
}
/**

View File

@ -4,6 +4,7 @@ import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.node.callable.ExecuteCallNode;
import org.enso.interpreter.node.callable.InvokeCallableNode;
import org.enso.interpreter.node.callable.InvokeCallableNodeGen;
import org.enso.interpreter.node.callable.argument.ThunkExecutorNode;
@ -12,7 +13,7 @@ import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo.ArgumentMapping;
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo.ArgumentMappingBuilder;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.callable.function.ArgumentSchema;
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.control.TailCallException;
import org.enso.interpreter.runtime.state.Stateful;
@ -26,12 +27,14 @@ public class CachedArgumentSorterNode extends BaseNode {
private final Function originalFunction;
private final ArgumentMapping mapping;
private final ArgumentSchema postApplicationSchema;
private final FunctionSchema postApplicationSchema;
private @CompilerDirectives.CompilationFinal(dimensions = 1) boolean[] argumentShouldExecute;
@Children private ThunkExecutorNode[] executors;
private @Children ThunkExecutorNode[] executors;
private final boolean appliesFully;
@Child private InvokeCallableNode oversaturatedCallableNode = null;
private @Child InvokeCallableNode oversaturatedCallableNode;
private final InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode;
private @Child ExecuteCallNode directCall;
private @Child CallOptimiserNode loopingCall;
/**
* Creates a node that generates and then caches the argument mapping.
@ -56,6 +59,39 @@ public class CachedArgumentSorterNode extends BaseNode {
this.mapping = mapping.getAppliedMapping();
this.postApplicationSchema = mapping.getPostApplicationSchema();
appliesFully = isFunctionFullyApplied(defaultsExecutionMode);
initializeOversaturatedCallNode(defaultsExecutionMode, argumentsExecutionMode);
argumentShouldExecute = this.mapping.getArgumentShouldExecute();
initializeCallNodes();
}
private void initializeCallNodes() {
if (postApplicationSchema.hasOversaturatedArgs()
|| !originalFunction.getCallStrategy().shouldCallDirect(isTail())) {
this.loopingCall = CallOptimiserNode.build();
} else {
this.directCall = ExecuteCallNode.build();
}
}
private void initializeOversaturatedCallNode(
InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode,
InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode) {
if (postApplicationSchema.hasOversaturatedArgs()) {
oversaturatedCallableNode =
InvokeCallableNodeGen.create(
postApplicationSchema.getOversaturatedArguments(),
defaultsExecutionMode,
argumentsExecutionMode);
oversaturatedCallableNode.setTail(isTail());
}
}
private boolean isFunctionFullyApplied(
InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode) {
boolean functionIsFullyApplied = true;
for (int i = 0; i < postApplicationSchema.getArgumentsCount(); i++) {
boolean hasValidDefault =
@ -67,18 +103,7 @@ public class CachedArgumentSorterNode extends BaseNode {
break;
}
}
appliesFully = functionIsFullyApplied;
if (postApplicationSchema.hasOversaturatedArgs()) {
oversaturatedCallableNode =
InvokeCallableNodeGen.create(
postApplicationSchema.getOversaturatedArguments(),
defaultsExecutionMode,
argumentsExecutionMode);
oversaturatedCallableNode.setTail(isTail);
}
argumentShouldExecute = this.mapping.getArgumentShouldExecute();
return functionIsFullyApplied;
}
/**
@ -133,33 +158,21 @@ public class CachedArgumentSorterNode extends BaseNode {
* @param function the function this node is reordering arguments for
* @param state the state to pass to the function
* @param arguments the arguments to reorder
* @param optimiser a call optimiser node, capable of performing the actual function call
* @return the provided {@code arguments} in the order expected by the cached {@link Function}
*/
public Stateful execute(
Function function, Object state, Object[] arguments, CallOptimiserNode optimiser) {
Object[] mappedAppliedArguments;
public Stateful execute(Function function, Object state, Object[] arguments) {
if (argumentsExecutionMode.shouldExecute()) {
state = executeArguments(arguments, state);
}
if (originalFunction.getSchema().hasAnyPreApplied()) {
mappedAppliedArguments = function.clonePreAppliedArguments();
} else {
mappedAppliedArguments = new Object[this.postApplicationSchema.getArgumentsCount()];
}
mapping.reorderAppliedArguments(arguments, mappedAppliedArguments);
Object[] mappedAppliedArguments = prepareArguments(function, arguments);
if (this.appliesFully()) {
if (!postApplicationSchema.hasOversaturatedArgs()) {
if (this.isTail()) {
throw new TailCallException(function, state, mappedAppliedArguments);
} else {
return optimiser.executeDispatch(function, state, mappedAppliedArguments);
}
return doCall(function, state, mappedAppliedArguments);
} else {
Stateful evaluatedVal = optimiser.executeDispatch(function, state, mappedAppliedArguments);
Stateful evaluatedVal =
loopingCall.executeDispatch(function, state, mappedAppliedArguments);
return this.oversaturatedCallableNode.execute(
evaluatedVal.getValue(),
@ -178,6 +191,17 @@ public class CachedArgumentSorterNode extends BaseNode {
}
}
private Object[] prepareArguments(Function function, Object[] arguments) {
Object[] mappedAppliedArguments;
if (originalFunction.getSchema().hasAnyPreApplied()) {
mappedAppliedArguments = function.clonePreAppliedArguments();
} else {
mappedAppliedArguments = new Object[this.postApplicationSchema.getArgumentsCount()];
}
mapping.reorderAppliedArguments(arguments, mappedAppliedArguments);
return mappedAppliedArguments;
}
/**
* Generates an array containing the oversaturated arguments for the function being executed (if
* any).
@ -208,6 +232,16 @@ public class CachedArgumentSorterNode extends BaseNode {
return oversaturatedArguments;
}
private Stateful doCall(Function function, Object state, Object[] arguments) {
if (getOriginalFunction().getCallStrategy().shouldCallDirect(isTail())) {
return directCall.executeCall(function, state, arguments);
} else if (isTail()) {
throw new TailCallException(function, state, arguments);
} else {
return loopingCall.executeDispatch(function, state, arguments);
}
}
/**
* Determines if the provided function is the same as the cached one.
*
@ -228,11 +262,11 @@ public class CachedArgumentSorterNode extends BaseNode {
}
/**
* Returns the {@link ArgumentSchema} to use in case the function call is not fully saturated.
* Returns the {@link FunctionSchema} to use in case the function call is not fully saturated.
*
* @return the call result {@link ArgumentSchema}.
* @return the call result {@link FunctionSchema}.
*/
public ArgumentSchema getPostApplicationSchema() {
public FunctionSchema getPostApplicationSchema() {
return postApplicationSchema;
}

View File

@ -23,7 +23,7 @@ public abstract class CallOptimiserNode extends Node {
*
* @return a fresh instance of {@link CallOptimiserNode}
*/
public static CallOptimiserNode create() {
public static CallOptimiserNode build() {
return new SimpleCallOptimiserNode();
}
}

View File

@ -6,7 +6,7 @@ import com.oracle.truffle.api.frame.VirtualFrame;
import org.enso.interpreter.node.ClosureRootNode;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.function.ArgumentSchema;
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
import org.enso.interpreter.runtime.callable.function.Function;
/**
@ -15,7 +15,7 @@ import org.enso.interpreter.runtime.callable.function.Function;
*/
public class CreateFunctionNode extends ExpressionNode {
private final RootCallTarget callTarget;
private final ArgumentSchema schema;
private final FunctionSchema schema;
/**
* Creates a new node to represent a function definition.
@ -25,7 +25,7 @@ public class CreateFunctionNode extends ExpressionNode {
*/
public CreateFunctionNode(RootCallTarget callTarget, ArgumentDefinition[] args) {
this.callTarget = callTarget;
this.schema = new ArgumentSchema(args);
this.schema = new FunctionSchema(FunctionSchema.CallStrategy.CALL_LOOP, args);
}
/**

View File

@ -1,74 +0,0 @@
package org.enso.interpreter.node.controlflow;
import com.oracle.truffle.api.frame.VirtualFrame;
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.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();
@Child private ExpressionNode condition;
@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)) {
return ifTrue.executeGeneric(frame);
} else {
return ifFalse.executeGeneric(frame);
}
}
/**
* 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);
} catch (UnexpectedResultException ex) {
throw new TypeError("Type error, expected long", this);
}
}
/**
* Sets whether the conditional is tail-recursive or not.
*
* @param isTail whether or not the conditional is tail recursive
*/
@Override
public void setTail(boolean isTail) {
ifTrue.setTail(isTail);
ifFalse.setTail(isTail);
}
}

View File

@ -0,0 +1,25 @@
package org.enso.interpreter.node.expression.builtin;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.RootNode;
import org.enso.interpreter.Language;
import org.enso.interpreter.runtime.state.Stateful;
/** Root node for use by all the builtin functions. */
@NodeInfo(description = "Root node for builtin functions.")
public abstract class BuiltinRootNode extends RootNode {
protected BuiltinRootNode(Language language) {
super(language);
}
/**
* Executes this node's logic, returning a pair of return value and the new state.
*
* @param frame current execution frame
* @return the {@link Stateful} instance containing new state and the result value of executing
* the logic.
*/
@Override
public abstract Stateful execute(VirtualFrame frame);
}

View File

@ -0,0 +1,66 @@
package org.enso.interpreter.node.expression.builtin;
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.Language;
import org.enso.interpreter.node.callable.argument.ThunkExecutorNode;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
import org.enso.interpreter.runtime.state.Stateful;
import org.enso.interpreter.runtime.type.TypesGen;
/** The root node for the basic control flow structure of the language. */
@NodeInfo(shortName = "Number.ifZero", description = "Root node of the Number.ifZero method.")
public class IfZeroNode extends BuiltinRootNode {
private @Child ThunkExecutorNode leftThunkExecutorNode = ThunkExecutorNode.build(true);
private @Child ThunkExecutorNode rightThunkExecutorNode = ThunkExecutorNode.build(true);
private final ConditionProfile condProfile = ConditionProfile.createCountingProfile();
private IfZeroNode(Language language) {
super(language);
}
/**
* Takes a number and two thunks and executes the first thunk if the number was 0 and second
* otherwise.
*
* <p>Assumes the number is the first argument in the current execution frame, while the first and
* second thunks are respectively the second and third arguments.
*
* @param frame current execution frame
* @return the result of executing the proper thunk
*/
@Override
public Stateful execute(VirtualFrame frame) {
long self =
TypesGen.asLong(Function.ArgumentsHelper.getPositionalArguments(frame.getArguments())[0]);
Thunk ifT =
TypesGen.asThunk(Function.ArgumentsHelper.getPositionalArguments(frame.getArguments())[1]);
Thunk ifF =
TypesGen.asThunk(Function.ArgumentsHelper.getPositionalArguments(frame.getArguments())[2]);
Object state = Function.ArgumentsHelper.getState(frame.getArguments());
if (condProfile.profile(self == 0)) {
return leftThunkExecutorNode.executeThunk(ifT, state);
} else {
return rightThunkExecutorNode.executeThunk(ifF, state);
}
}
/**
* Creates a three-argument function wrapping this node.
*
* @param language current language instance
* @return a function wrapping this node
*/
public static Function makeFunction(Language language) {
return Function.fromBuiltinRootNode(
new IfZeroNode(language),
FunctionSchema.CallStrategy.DIRECT_WHEN_TAIL,
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "ifTrue", ArgumentDefinition.ExecutionMode.PASS_THUNK),
new ArgumentDefinition(2, "ifFalse", ArgumentDefinition.ExecutionMode.PASS_THUNK));
}
}

View File

@ -2,19 +2,20 @@ package org.enso.interpreter.node.expression.builtin.error;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.profiles.ConditionProfile;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.callable.InvokeCallableNode;
import org.enso.interpreter.node.expression.builtin.BuiltinRootNode;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
import org.enso.interpreter.runtime.state.Stateful;
import org.enso.interpreter.runtime.type.TypesGen;
/** Root node for the {@code catch} function. */
@NodeInfo(shortName = "Error.catch", description = "Root node for the catch function.")
public class CatchErrorNode extends RootNode {
public class CatchErrorNode extends BuiltinRootNode {
private @Child InvokeCallableNode invokeCallableNode;
private final ConditionProfile executionProfile = ConditionProfile.createCountingProfile();
@ -25,6 +26,7 @@ public class CatchErrorNode extends RootNode {
new CallArgumentInfo[] {new CallArgumentInfo()},
InvokeCallableNode.DefaultsExecutionMode.EXECUTE,
InvokeCallableNode.ArgumentsExecutionMode.PRE_EXECUTED);
this.invokeCallableNode.markTail();
}
/**
@ -56,8 +58,9 @@ public class CatchErrorNode extends RootNode {
* @return a function wrapping this node
*/
public static Function makeFunction(Language language) {
return Function.fromRootNode(
return Function.fromBuiltinRootNode(
new CatchErrorNode(language),
FunctionSchema.CallStrategy.DIRECT_WHEN_TAIL,
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "handler", ArgumentDefinition.ExecutionMode.EXECUTE));
}

View File

@ -2,11 +2,12 @@ package org.enso.interpreter.node.expression.builtin.error;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.RootNode;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.callable.argument.ThunkExecutorNode;
import org.enso.interpreter.node.expression.builtin.BuiltinRootNode;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
import org.enso.interpreter.runtime.error.PanicException;
import org.enso.interpreter.runtime.error.RuntimeError;
import org.enso.interpreter.runtime.state.Stateful;
@ -16,7 +17,7 @@ import org.enso.interpreter.runtime.type.TypesGen;
@NodeInfo(
shortName = "Panic.catch",
description = "Root node for the builtin catch panic function.")
public class CatchPanicNode extends RootNode {
public class CatchPanicNode extends BuiltinRootNode {
private ThunkExecutorNode thunkExecutorNode = ThunkExecutorNode.build(false);
private CatchPanicNode(Language language) {
@ -54,8 +55,9 @@ public class CatchPanicNode extends RootNode {
* @return a function wrapping this node
*/
public static Function makeFunction(Language language) {
return Function.fromRootNode(
return Function.fromBuiltinRootNode(
new CatchPanicNode(language),
FunctionSchema.CallStrategy.ALWAYS_DIRECT,
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "value", ArgumentDefinition.ExecutionMode.PASS_THUNK));
}

View File

@ -2,16 +2,17 @@ package org.enso.interpreter.node.expression.builtin.error;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.RootNode;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.expression.builtin.BuiltinRootNode;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
import org.enso.interpreter.runtime.error.PanicException;
import org.enso.interpreter.runtime.state.Stateful;
/** Root node for the builtin panic function. */
@NodeInfo(shortName = "Panic.throw", description = "Root node for the builtin panic function.")
public class PanicNode extends RootNode {
public class PanicNode extends BuiltinRootNode {
private PanicNode(Language language) {
super(language);
}
@ -37,8 +38,9 @@ public class PanicNode extends RootNode {
* @return a function wrapping this node
*/
public static Function makeFunction(Language language) {
return Function.fromRootNode(
return Function.fromBuiltinRootNode(
new PanicNode(language),
FunctionSchema.CallStrategy.ALWAYS_DIRECT,
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "value", ArgumentDefinition.ExecutionMode.EXECUTE));
}

View File

@ -2,10 +2,11 @@ package org.enso.interpreter.node.expression.builtin.error;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.RootNode;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.expression.builtin.BuiltinRootNode;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
import org.enso.interpreter.runtime.error.RuntimeError;
import org.enso.interpreter.runtime.state.Stateful;
@ -13,7 +14,7 @@ import org.enso.interpreter.runtime.state.Stateful;
@NodeInfo(
shortName = "Error.throw",
description = "Root node for the builtin throw error function.")
public class ThrowErrorNode extends RootNode {
public class ThrowErrorNode extends BuiltinRootNode {
private ThrowErrorNode(Language language) {
super(language);
@ -29,7 +30,7 @@ public class ThrowErrorNode extends RootNode {
* @return a runtime error wrapped argument
*/
@Override
public Object execute(VirtualFrame frame) {
public Stateful execute(VirtualFrame frame) {
Object errorPayload = Function.ArgumentsHelper.getPositionalArguments(frame.getArguments())[1];
Object state = Function.ArgumentsHelper.getState(frame.getArguments());
return new Stateful(state, new RuntimeError(errorPayload));
@ -42,8 +43,9 @@ public class ThrowErrorNode extends RootNode {
* @return a function wrapping this node
*/
public static Function makeFunction(Language language) {
return Function.fromRootNode(
return Function.fromBuiltinRootNode(
new ThrowErrorNode(language),
FunctionSchema.CallStrategy.ALWAYS_DIRECT,
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "payload", ArgumentDefinition.ExecutionMode.EXECUTE));
}

View File

@ -5,18 +5,19 @@ import com.oracle.truffle.api.dsl.CachedContext;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.RootNode;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.expression.builtin.BuiltinRootNode;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
import org.enso.interpreter.runtime.state.Stateful;
import java.io.PrintStream;
/** Allows for printing arbitrary values to the standard output. */
@NodeInfo(shortName = "IO.println", description = "Root of the IO.println method.")
public abstract class PrintNode extends RootNode {
public abstract class PrintNode extends BuiltinRootNode {
PrintNode(Language language) {
super(language);
}
@ -42,8 +43,9 @@ public abstract class PrintNode extends RootNode {
* @return a {@link Function} object wrapping the behavior of this node
*/
public static Function makeFunction(Language language) {
return Function.fromRootNode(
return Function.fromBuiltinRootNode(
PrintNodeGen.create(language),
FunctionSchema.CallStrategy.ALWAYS_DIRECT,
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "value", ArgumentDefinition.ExecutionMode.EXECUTE));
}

View File

@ -2,15 +2,16 @@ package org.enso.interpreter.node.expression.builtin.state;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.RootNode;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.expression.builtin.BuiltinRootNode;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
import org.enso.interpreter.runtime.state.Stateful;
/** Root node for the builtin State.get function. */
@NodeInfo(shortName = "State.get", description = "Root node for the builtin State.get function")
public class GetStateNode extends RootNode {
public class GetStateNode extends BuiltinRootNode {
private GetStateNode(Language language) {
super(language);
}
@ -34,8 +35,9 @@ public class GetStateNode extends RootNode {
* @return a function wrapping this node
*/
public static Function makeFunction(Language language) {
return Function.fromRootNode(
return Function.fromBuiltinRootNode(
new GetStateNode(language),
FunctionSchema.CallStrategy.ALWAYS_DIRECT,
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE));
}
}

View File

@ -2,15 +2,16 @@ package org.enso.interpreter.node.expression.builtin.state;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.RootNode;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.expression.builtin.BuiltinRootNode;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
import org.enso.interpreter.runtime.state.Stateful;
/** Root node for the builtin State.put function. */
@NodeInfo(shortName = "State.put", description = "Root node for the builtin State.put function")
public class PutStateNode extends RootNode {
public class PutStateNode extends BuiltinRootNode {
private PutStateNode(Language language) {
super(language);
}
@ -35,8 +36,9 @@ public class PutStateNode extends RootNode {
* @return a function wrapping this node
*/
public static Function makeFunction(Language language) {
return Function.fromRootNode(
return Function.fromBuiltinRootNode(
new PutStateNode(language),
FunctionSchema.CallStrategy.ALWAYS_DIRECT,
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "newState", ArgumentDefinition.ExecutionMode.EXECUTE));
}

View File

@ -2,20 +2,19 @@ package org.enso.interpreter.node.expression.builtin.state;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.profiles.ConditionProfile;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.callable.argument.ThunkExecutorNode;
import org.enso.interpreter.node.expression.builtin.BuiltinRootNode;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
import org.enso.interpreter.runtime.state.Stateful;
import org.enso.interpreter.runtime.type.TypesGen;
import javax.swing.plaf.nimbus.State;
/** Root for the builtin State.run function. */
@NodeInfo(shortName = "State.run", description = "Root for the builtin State.run function")
public class RunStateNode extends RootNode {
public class RunStateNode extends BuiltinRootNode {
private RunStateNode(Language language) {
super(language);
}
@ -54,8 +53,9 @@ public class RunStateNode extends RootNode {
* @return a function wrapping this node
*/
public static Function makeFunction(Language language) {
return Function.fromRootNode(
return Function.fromBuiltinRootNode(
new RunStateNode(language),
FunctionSchema.CallStrategy.ALWAYS_DIRECT,
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "state", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(

View File

@ -1,6 +1,7 @@
package org.enso.interpreter.runtime;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.expression.builtin.IfZeroNode;
import org.enso.interpreter.node.expression.builtin.error.CatchErrorNode;
import org.enso.interpreter.node.expression.builtin.error.CatchPanicNode;
import org.enso.interpreter.node.expression.builtin.error.PanicNode;
@ -53,10 +54,11 @@ public class Builtins {
scope.registerMethod(error, "throw", ThrowErrorNode.makeFunction(language));
scope.registerMethodForAny("catch", CatchErrorNode.makeFunction(language));
scope.registerMethodForNumber("ifZero", IfZeroNode.makeFunction(language));
scope.registerMethod(state, "get", GetStateNode.makeFunction(language));
scope.registerMethod(state, "put", PutStateNode.makeFunction(language));
scope.registerMethod(state, "run", RunStateNode.makeFunction(language));
}
/**

View File

@ -51,7 +51,7 @@ public class UnresolvedSymbol implements TruffleObject {
*/
@CompilerDirectives.TruffleBoundary
public Function resolveForNumber() {
return scope.lookupMethodDefinitionForAny(name).orElse(null);
return scope.lookupMethodDefinitionForNumber(name).orElse(null);
}
/**

View File

@ -2,7 +2,7 @@ package org.enso.interpreter.runtime.callable.argument;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import org.enso.interpreter.runtime.callable.function.ArgumentSchema;
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
import java.util.OptionalInt;
import java.util.function.Predicate;
@ -33,9 +33,7 @@ public class CallArgumentInfo {
this.name = name;
}
/**
* Creates an unnamed call argument.
*/
/** Creates an unnamed call argument. */
public CallArgumentInfo() {
this.name = null;
}
@ -80,6 +78,7 @@ public class CallArgumentInfo {
private boolean[] argumentUsed;
private boolean[] callSiteArgApplied;
private int oversaturatedWritePosition = 0;
private FunctionSchema.CallStrategy callStrategy;
/**
* Creates an unitialised object of this class. This instance is not safe for external use and
@ -88,7 +87,7 @@ public class CallArgumentInfo {
* @param schema the definition site arguments schema
* @param callArgs the call site arguments schema
*/
private ArgumentMappingBuilder(ArgumentSchema schema, CallArgumentInfo[] callArgs) {
private ArgumentMappingBuilder(FunctionSchema schema, CallArgumentInfo[] callArgs) {
this.appliedMapping = new int[callArgs.length];
this.oversaturatedArgumentMapping = new int[callArgs.length];
this.callSiteArgApplied = new boolean[callArgs.length];
@ -98,6 +97,7 @@ public class CallArgumentInfo {
this.definitions = schema.getArgumentInfos();
this.argumentUsed = schema.cloneHasPreApplied();
this.existingOversaturatedArgs = schema.cloneOversaturatedArgs();
this.callStrategy = schema.getCallStrategy();
}
/**
@ -108,7 +108,7 @@ public class CallArgumentInfo {
* @return the generated argument mapping
*/
public static ArgumentMappingBuilder generate(
ArgumentSchema schema, CallArgumentInfo[] callArgs) {
FunctionSchema schema, CallArgumentInfo[] callArgs) {
ArgumentMappingBuilder mapping = new ArgumentMappingBuilder(schema, callArgs);
mapping.processArguments();
return mapping;
@ -185,12 +185,12 @@ public class CallArgumentInfo {
}
/**
* Returns an {@link ArgumentSchema} object resulting from filling in all the call site
* Returns an {@link FunctionSchema} object resulting from filling in all the call site
* arguments in the original definition site schema.
*
* @return the post-application arguments schema
*/
public ArgumentSchema getPostApplicationSchema() {
public FunctionSchema getPostApplicationSchema() {
CallArgumentInfo[] newOversaturatedArgInfo =
IntStream.range(0, this.callArgs.length)
.filter(i -> !this.callSiteArgApplied[i])
@ -214,7 +214,7 @@ public class CallArgumentInfo {
this.existingOversaturatedArgs.length,
newOversaturatedArgInfo.length);
return new ArgumentSchema(definitions, argumentUsed, oversaturatedArgInfo);
return new FunctionSchema(callStrategy, definitions, argumentUsed, oversaturatedArgInfo);
}
}

View File

@ -10,7 +10,7 @@ import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.node.callable.argument.ReadArgumentNode;
import org.enso.interpreter.node.expression.atom.InstantiateNode;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.function.ArgumentSchema;
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.scope.ModuleScope;
@ -68,7 +68,8 @@ public class AtomConstructor implements TruffleObject {
new ClosureRootNode(
null, new FrameDescriptor(), instantiateNode, null, "<constructor>:" + name);
RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(rootNode);
return new Function(callTarget, null, new ArgumentSchema(args));
return new Function(
callTarget, null, new FunctionSchema(FunctionSchema.CallStrategy.ALWAYS_DIRECT, args));
}
/**

View File

@ -19,6 +19,7 @@ import org.enso.interpreter.Language;
import org.enso.interpreter.node.callable.InvokeCallableNode;
import org.enso.interpreter.node.callable.argument.sorter.ArgumentSorterNode;
import org.enso.interpreter.node.callable.argument.sorter.ArgumentSorterNodeGen;
import org.enso.interpreter.node.expression.builtin.BuiltinRootNode;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
@ -29,7 +30,7 @@ import org.enso.interpreter.runtime.callable.argument.Thunk;
public final class Function implements TruffleObject {
private final RootCallTarget callTarget;
private final MaterializedFrame scope;
private final ArgumentSchema schema;
private final FunctionSchema schema;
private final @CompilerDirectives.CompilationFinal(dimensions = 1) Object[] preAppliedArguments;
private final @CompilationFinal(dimensions = 1) Object[] oversaturatedArguments;
@ -38,7 +39,7 @@ public final class Function implements TruffleObject {
*
* @param callTarget the target containing the function's code
* @param scope a frame representing the function's scope
* @param schema the {@link ArgumentSchema} with which the function was defined
* @param schema the {@link FunctionSchema} with which the function was defined
* @param preappliedArguments the preapplied arguments for this function. The layout of this array
* must be conforming to the {@code schema}. {@code null} is allowed if the function does not
* have any partially applied arguments.
@ -46,7 +47,7 @@ public final class Function implements TruffleObject {
public Function(
RootCallTarget callTarget,
MaterializedFrame scope,
ArgumentSchema schema,
FunctionSchema schema,
Object[] preappliedArguments,
Object[] oversaturatedArguments) {
this.callTarget = callTarget;
@ -61,12 +62,27 @@ public final class Function implements TruffleObject {
*
* @param callTarget the target containing the function's code
* @param scope a frame representing the function's scope
* @param schema the {@link ArgumentSchema} with which the function was defined
* @param schema the {@link FunctionSchema} with which the function was defined
*/
public Function(RootCallTarget callTarget, MaterializedFrame scope, ArgumentSchema schema) {
public Function(RootCallTarget callTarget, MaterializedFrame scope, FunctionSchema schema) {
this(callTarget, scope, schema, null, null);
}
/**
* Creates a Function object from a {@link RootNode} and argument definitions.
*
* @param node the {@link RootNode} for the function logic
* @param callStrategy the {@link FunctionSchema.CallStrategy} to use for this function
* @param args argument definitons
* @return a Function object with specified behavior and arguments
*/
public static Function fromBuiltinRootNode(
BuiltinRootNode node, FunctionSchema.CallStrategy callStrategy, ArgumentDefinition... args) {
RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(node);
FunctionSchema schema = new FunctionSchema(callStrategy, args);
return new Function(callTarget, null, schema);
}
/**
* Gets the target containing the function's code.
*
@ -76,6 +92,15 @@ public final class Function implements TruffleObject {
return callTarget;
}
/**
* Gets the call strategy that should be used for this function.
*
* @return this function's call strategy
*/
public FunctionSchema.CallStrategy getCallStrategy() {
return getSchema().getCallStrategy();
}
/**
* Gets the function's scope.
*
@ -90,7 +115,7 @@ public final class Function implements TruffleObject {
*
* @return the function's argument schema
*/
public ArgumentSchema getSchema() {
public FunctionSchema getSchema() {
return schema;
}
@ -257,17 +282,4 @@ public final class Function implements TruffleObject {
return (MaterializedFrame) arguments[0];
}
}
/**
* Creates a Function object from a {@link RootNode} and argument definitions.
*
* @param node the {@link RootNode} for the function logic
* @param args argument definitons
* @return a Function object with specified behavior and arguments
*/
public static Function fromRootNode(RootNode node, ArgumentDefinition... args) {
RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(node);
ArgumentSchema schema = new ArgumentSchema(args);
return new Function(callTarget, null, schema);
}
}

View File

@ -8,24 +8,60 @@ import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
* Holds the definition site argument information together with information on the partially applied
* arguments positions.
*/
public class ArgumentSchema {
public class FunctionSchema {
/**
* Denotes the call strategy that should be used whenever a function with this schema is called.
*
* <p>For builtin functions, the algorithm for choosing the proper {@link CallStrategy} is as
* follows: if the node executes user-provided code ({@link
* org.enso.interpreter.runtime.callable.argument.Thunk} or a {@link Function}) using the Tail
* Call Optimization machinery (i.e. marking the execution node as tail position), the right
* choice is {@code DIRECT_WHEN_TAIL}. Otherwise {@code ALWAYS_DIRECT} should be chosen.
*/
public enum CallStrategy {
/** Always call the function directly. */
ALWAYS_DIRECT,
/** Call the function directly when said function is in tail call position. */
DIRECT_WHEN_TAIL,
/** Always call the function using standard tail call machinery. */
CALL_LOOP;
/**
* Should this function be called directly in the given position?
*
* @param isTail is this a tail position?
* @return {@code true} if the function should be called directly, {@code false} otherwise
*/
public boolean shouldCallDirect(boolean isTail) {
return this == ALWAYS_DIRECT || (this == DIRECT_WHEN_TAIL && isTail);
}
}
private final @CompilationFinal(dimensions = 1) ArgumentDefinition[] argumentInfos;
private final @CompilationFinal(dimensions = 1) boolean[] hasPreApplied;
private final @CompilationFinal(dimensions = 1) CallArgumentInfo[] oversaturatedArguments;
private final CallStrategy callStrategy;
private final boolean hasAnyPreApplied;
private final boolean hasOversaturatedArguments;
/**
* Creates an {@link ArgumentSchema} instance.
* Creates an {@link FunctionSchema} instance.
*
* @param callStrategy the call strategy to use for functions having this schema
* @param argumentInfos Definition site arguments information
* @param hasPreApplied A flags collection such that {@code hasPreApplied[i]} is true iff a
* function has a partially applied argument at position {@code i}
* @param oversaturatedArguments information about any unused, oversaturated arguments passed to
* this function so far
*/
public ArgumentSchema(
public FunctionSchema(
CallStrategy callStrategy,
ArgumentDefinition[] argumentInfos,
boolean[] hasPreApplied,
CallArgumentInfo[] oversaturatedArguments) {
this.callStrategy = callStrategy;
this.argumentInfos = argumentInfos;
this.oversaturatedArguments = oversaturatedArguments;
this.hasPreApplied = hasPreApplied;
@ -43,13 +79,13 @@ public class ArgumentSchema {
}
/**
* Creates an {@link ArgumentSchema} instance assuming the function has no partially applied
* Creates an {@link FunctionSchema} instance assuming the function has no partially applied
* arguments.
*
* @param argumentInfos Definition site arguments information
*/
public ArgumentSchema(ArgumentDefinition... argumentInfos) {
this(argumentInfos, new boolean[argumentInfos.length], new CallArgumentInfo[0]);
public FunctionSchema(CallStrategy callStrategy, ArgumentDefinition... argumentInfos) {
this(callStrategy, argumentInfos, new boolean[argumentInfos.length], new CallArgumentInfo[0]);
}
/**
@ -138,4 +174,13 @@ public class ArgumentSchema {
public CallArgumentInfo[] getOversaturatedArguments() {
return oversaturatedArguments;
}
/**
* Returns the call strategy to use for functions with this schema.
*
* @return the call strategy to use
*/
public CallStrategy getCallStrategy() {
return callStrategy;
}
}

View File

@ -12,6 +12,7 @@ public class ModuleScope {
private final Map<String, AtomConstructor> constructors = new HashMap<>();
private final Map<AtomConstructor, Map<String, Function>> methods = new HashMap<>();
private final Map<String, Function> anyMethods = new HashMap<>();
private final Map<String, Function> numberMethods = new HashMap<>();
private final Set<ModuleScope> imports = new HashSet<>();
private final Set<ModuleScope> transitiveImports = new HashSet<>();
@ -70,6 +71,16 @@ public class ModuleScope {
anyMethods.put(methodName, function);
}
/**
* Registers a method for the {@code Number} type.
*
* @param methodName the name of the method to register
* @param function the {@link Function} associated with this definition
*/
public void registerMethodForNumber(String methodName, Function function) {
numberMethods.put(methodName, function);
}
/**
* Looks up the definition for a given type and method name.
*
@ -102,14 +113,7 @@ public class ModuleScope {
*/
@CompilerDirectives.TruffleBoundary
public Optional<Function> lookupMethodDefinitionForAny(String name) {
Function definedHere = anyMethods.get(name);
if (definedHere != null) {
return Optional.of(definedHere);
}
return transitiveImports.stream()
.map(scope -> scope.getMethodsOfAny().get(name))
.filter(Objects::nonNull)
.findFirst();
return searchAuxiliaryMethodsMap(ModuleScope::getMethodsOfAny, name);
}
private Optional<Function> lookupSpecificMethodDefinitionForAtom(
@ -128,6 +132,43 @@ public class ModuleScope {
.findFirst();
}
private Optional<Function> lookupSpecificMethodDefinitionForNumber(String name) {
return searchAuxiliaryMethodsMap(ModuleScope::getMethodsOfNumber, name);
}
/**
* Looks up a method definition by-name, for methods defined on the type Number.
*
* <p>The resolution algorithm prefers methods defined locally over any other method. The
* definitions are imported into scope transitively.
*
* <p>If the specific search fails, methods defined for any type are searched, first looking at *
* locally defined methods and then all the transitive imports.
*
* @param name the name of the method to look up
* @return {@code Optional.of(resultMethod)} if the method existed, {@code Optional.empty()}
* otherwise
*/
@CompilerDirectives.TruffleBoundary
public Optional<Function> lookupMethodDefinitionForNumber(String name) {
return Optional.ofNullable(
lookupSpecificMethodDefinitionForNumber(name)
.orElseGet(() -> lookupMethodDefinitionForAny(name).orElse(null)));
}
private Optional<Function> searchAuxiliaryMethodsMap(
java.util.function.Function<ModuleScope, Map<String, Function>> mapGetter,
String methodName) {
Function definedHere = mapGetter.apply(this).get(methodName);
if (definedHere != null) {
return Optional.of(definedHere);
}
return transitiveImports.stream()
.map(scope -> mapGetter.apply(scope).get(methodName))
.filter(Objects::nonNull)
.findFirst();
}
/**
* Returns all the transitive dependencies of this module.
*
@ -141,6 +182,10 @@ public class ModuleScope {
return anyMethods;
}
private Map<String, Function> getMethodsOfNumber() {
return numberMethods;
}
/**
* Adds a dependency for this module.
*

View File

@ -33,12 +33,6 @@ trait AstExpressionVisitor[+T] {
defaultsSuspended: Boolean
): T
def visitIf(
cond: AstExpression,
ifTrue: AstExpression,
ifFalse: AstExpression
): T
def visitAssignment(varName: String, expr: AstExpression): T
def visitMatch(
@ -203,15 +197,6 @@ case class AstAssignment(name: String, body: AstExpression)
visitor.visitAssignment(name, body)
}
case class AstIfZero(
cond: AstExpression,
ifTrue: AstExpression,
ifFalse: AstExpression
) extends AstExpression {
override def visit[T](visitor: AstExpressionVisitor[T]): T =
visitor.visitIf(cond, ifTrue, ifFalse)
}
case class AstCase(cons: AstExpression, function: AstCaseFunction)
case class AstMatch(
@ -288,7 +273,7 @@ class EnsoParserInternal extends JavaTokenParsers {
}
def expression: Parser[AstExpression] =
desuspend | ifZero | matchClause | arith | function
desuspend | matchClause | arith | function
def functionCall: Parser[AstApply] =
"@" ~> expression ~ (argList ?) ~ defaultSuspend ^^ {
@ -308,11 +293,6 @@ class EnsoParserInternal extends JavaTokenParsers {
case v ~ exp => AstAssignment(v, exp)
}
def ifZero: Parser[AstIfZero] =
"ifZero:" ~> "[" ~> (expression ~ ("," ~> expression ~ ("," ~> expression))) <~ "]" ^^ {
case cond ~ (ift ~ iff) => AstIfZero(cond, ift, iff)
}
def function: Parser[AstFunction] =
("{" ~> (inArgList ?) ~ ((statement <~ ";") *) ~ expression <~ "}") ^^ {
case args ~ stmts ~ expr => AstFunction(args.getOrElse(Nil), stmts, expr)

View File

@ -22,7 +22,7 @@ class ConstructorsTest extends InterpreterTest {
val testCode =
"""
|@{
| genList = { |i| ifZero: [i, @Nil, @Cons [i, @genList [i-1]]] };
| genList = { |i| @ifZero [i, @Nil, @Cons [i, @genList [i-1]]] };
| sumList = { |list| match list <
| Cons ~ { |h, t| h + (@sumList [t]) };
| Nil ~ { 0 };
@ -82,7 +82,7 @@ class ConstructorsTest extends InterpreterTest {
"""
|type Cons2 a b;
|
|Unit.genList = { |i| ifZero: [i, @Nil2, @Cons2 [i, @genList [@Unit, i-1]]] }
|Unit.genList = { |i| @ifZero [i, @Nil2, @Cons2 [i, @genList [@Unit, i-1]]] }
|
|type Nil2;
|

View File

@ -32,7 +32,7 @@ class FunctionArgumentsTest extends InterpreterTest {
val code =
"""
|@{
| sumTo = { |x| ifZero: [x, 0, x + (@sumTo [x - 1])] };
| sumTo = { |x| @ifZero [x, 0, x + (@sumTo [x - 1])] };
| @sumTo [10]
|}
""".stripMargin
@ -103,7 +103,7 @@ class FunctionArgumentsTest extends InterpreterTest {
val code =
"""
|@{
| summator = { |current| ifZero: [current, 0, @{@summator [current - 1]} ] };
| summator = { |current| @ifZero [current, 0, @{@summator [current - 1]} ] };
| res = @summator [0];
| res
|}

View File

@ -68,7 +68,7 @@ class GlobalScopeTest extends InterpreterTest {
|}
|
|Unit.fn1 = { |number|
| ifZero: [number % 3, number, @decrementCall [@Unit, number]]
| @ifZero [number % 3, number, @decrementCall [@Unit, number]]
|}
|
|@fn1 [@Unit, 5]

View File

@ -7,7 +7,7 @@ class InteropTest extends InterpreterTest {
val code =
"""
|@{
| recurFun = { |i| ifZero: [i, 0, @recurFun [i - 1]] };
| recurFun = { |i| @ifZero [i, 0, @recurFun [i - 1]] };
| recurFun
|}
|""".stripMargin

View File

@ -9,7 +9,7 @@ class LazyArgumentsTest extends InterpreterTest {
val code =
"""
|@{
| foo = { |i, $x, $y| ifZero: [i, $x, $y] };
| foo = { |i, $x, $y| @ifZero [i, $x, $y] };
| @foo [1, @println [@IO, 1], @println [@IO, 2]]
|}
|""".stripMargin
@ -22,7 +22,7 @@ class LazyArgumentsTest extends InterpreterTest {
val code =
"""
|@{
| if = { |c, $ifT, $ifF| ifZero: [c, $ifT, $ifF] };
| if = { |c, $ifT, $ifF| @ifZero [c, $ifT, $ifF] };
| sum = { |c, acc| @if [c, acc, @sum [c-1, acc + c]] };
| res = @sum [10000, 0];
| res
@ -68,7 +68,7 @@ class LazyArgumentsTest extends InterpreterTest {
val code =
"""
|@{
| if = { |c, $ifT, $ifF| ifZero: [c, $ifT, $ifF] };
| if = { |c, $ifT, $ifF| @ifZero [c, $ifT, $ifF] };
| foo = { |c| @if [c] };
| @foo [0, @println [@IO, 1], @println [@IO, 2]];
| @foo [1, @println [@IO, 3], @println [@IO, 4]]

View File

@ -119,7 +119,7 @@ class NamedArgumentsTest extends InterpreterTest {
"""
|Unit.summer = { |sumTo|
| summator = { |acc = 0, current|
| ifZero: [current, acc, @summator [current = current - 1, acc = acc + current]]
| @ifZero [current, acc, @summator [current = current - 1, acc = acc + current]]
| };
| res = @summator [current = sumTo];
| res
@ -189,7 +189,7 @@ class NamedArgumentsTest extends InterpreterTest {
|type Nil2;
|
|@{
| genList = { |i| ifZero: [i, @Nil2, @Cons2 [rest = @genList [i-1], head = i]] };
| genList = { |i| @ifZero [i, @Nil2, @Cons2 [rest = @genList [i-1], head = i]] };
|
| sumList = { |list| match list <
| Cons2 ~ { |head, rest| head + @sumList [rest] };
@ -210,7 +210,7 @@ class NamedArgumentsTest extends InterpreterTest {
|type Nil2;
|type Cons2 head (rest = Nil2);
|@{
| genList = { |i| ifZero: [i, @Nil2, @Cons2 [rest = @genList [i-1], head = i]] };
| genList = { |i| @ifZero [i, @Nil2, @Cons2 [rest = @genList [i-1], head = i]] };
|
| sumList = { |list| match list <
| Cons2 ~ { |head, rest| head + @sumList [rest] };

View File

@ -65,7 +65,7 @@ class StateTest extends InterpreterTest {
| acc = @get [@State];
| @println[@IO, acc];
| @put [@State, acc + n];
| ifZero: [n, @get [@State], @stateSum [n-1]]
| @ifZero [n, @get [@State], @stateSum [n-1]]
| };
| @run [@State, 0, @stateSum [10]]
|}