Skip to content

Commit

Permalink
Merge pull request allegro#19 from allegro/functions
Browse files Browse the repository at this point in the history
Functions
  • Loading branch information
tfij authored Oct 14, 2016
2 parents 665a5dd + c38281d commit aa698c5
Show file tree
Hide file tree
Showing 10 changed files with 533 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package pl.allegro.tech.opel;

import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

public class AnonymousFunctionExpressionNode implements OpelNode {

private final OpelNode expression;
private final ArgumentsListExpressionNode arguments;

public AnonymousFunctionExpressionNode(OpelNode expression, ArgumentsListExpressionNode arguments) {
this.expression = expression;
this.arguments = arguments;
}

@Override
public CompletableFuture<?> getValue(EvalContext context) {
return expression.getValue(context).thenCompose(function -> {
if (function instanceof OpelAsyncFunction) {
return ((OpelAsyncFunction<?>) function).apply(arguments.getArgs().stream().map(it -> it.getValue(context)).collect(Collectors.toList()));
}
throw new OpelException("Can't use expression of type " + expression.getClass().getSimpleName() + " as a function");
});
}
}
26 changes: 26 additions & 0 deletions src/main/java/pl/allegro/tech/opel/ArgsGroupNode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package pl.allegro.tech.opel;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;

public class ArgsGroupNode implements OpelNode {
private final List<ArgumentsListExpressionNode> argsGroup;

public ArgsGroupNode(List<ArgumentsListExpressionNode> argsGroup) {
this.argsGroup = argsGroup;
}

@Override
public CompletableFuture<?> getValue(EvalContext context) {
throw new UnsupportedOperationException("Can't get value on ArgsGroupNode");
}

public static OpelNode empty() {
return new ArgsGroupNode(Collections.emptyList());
}

public List<ArgumentsListExpressionNode> getGroups() {
return argsGroup;
}
}
40 changes: 23 additions & 17 deletions src/main/java/pl/allegro/tech/opel/FunctionCallExpressionNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;

class FunctionCallExpressionNode implements OpelNode {
private final String functionName;
Expand All @@ -14,11 +15,6 @@ public FunctionCallExpressionNode(String functionName, ArgumentsListExpressionNo
this.arguments = Optional.of(arguments);
}

private FunctionCallExpressionNode(String functionName) {
this.functionName = functionName;
this.arguments = Optional.empty();
}

static FunctionCallExpressionNode create(OpelNode identifier, OpelNode args) {
if (identifier instanceof IdentifierExpressionNode && args instanceof ArgumentsListExpressionNode) {
String identifierValue = ((IdentifierExpressionNode) identifier).getIdentifier();
Expand All @@ -27,20 +23,30 @@ static FunctionCallExpressionNode create(OpelNode identifier, OpelNode args) {
throw new IllegalArgumentException("Cannot create FunctionCallExpressionNode from " + identifier.getClass().getSimpleName() + " and " + args.getClass().getSimpleName());
}

static FunctionCallExpressionNode create(OpelNode identifier) {
if (identifier instanceof IdentifierExpressionNode) {
String identifierValue = ((IdentifierExpressionNode) identifier).getIdentifier();
return new FunctionCallExpressionNode(identifierValue);
}
throw new IllegalArgumentException("Cannot create FunctionCallExpressionNode from " + identifier.getClass().getSimpleName());
}

@Override
public CompletableFuture<?> getValue(EvalContext context) {
OpelAsyncFunction<?> function = context
.getFunction(functionName)
.orElseThrow(() -> new RuntimeException("Function " + functionName + " not found."));
CompletableFuture<OpelAsyncFunction<?>> function = getFunction(context);
List<CompletableFuture<?>> args = arguments.map(ags -> ags.getListOfValues(context)).orElse(Collections.emptyList());
return function.apply(args);
return function.thenCompose(fun -> fun.apply(args));
}

private CompletableFuture<OpelAsyncFunction<?>> getFunction(EvalContext context) {
Optional<CompletableFuture<OpelAsyncFunction<?>>> functionFromRegisteredFunctions = context.getFunction(functionName).map(CompletableFuture::completedFuture);
return functionFromRegisteredFunctions.orElseGet(() -> getFunctionFromValue(context));
}

private CompletableFuture<OpelAsyncFunction<?>> getFunctionFromValue(EvalContext context) {
CompletableFuture<? extends OpelAsyncFunction<?>> opelAsyncFunctionCompletableFuture = context.getValue(functionName)
.map(val -> val.thenApply(it -> getValueAsFunction(it)))
.orElseThrow(() -> new OpelException("Function '" + functionName + "' not found"));
return opelAsyncFunctionCompletableFuture.thenApply(Function.identity());
}

private OpelAsyncFunction<?> getValueAsFunction(Object it) {
if (it instanceof OpelAsyncFunction) {
return (OpelAsyncFunction<?>) it;
} else {
throw new OpelException("Value '" + functionName + "' is not a function");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package pl.allegro.tech.opel;

import java.util.concurrent.CompletableFuture;

public class FunctionChainExpressionNode implements OpelNode {
private final OpelNode expression;
private final ArgsGroupNode argsGroups;

public FunctionChainExpressionNode(OpelNode expression, ArgsGroupNode argsGroups) {
this.expression = expression;
this.argsGroups = argsGroups;
}

@Override
public CompletableFuture<?> getValue(EvalContext context) {
CompletableFuture<?> result = expression.getValue(context);
for (ArgumentsListExpressionNode argsGroup : argsGroups.getGroups()) {
result = callFunction(result, argsGroup, context);
}
return result;
}

private CompletableFuture<Object> callFunction(CompletableFuture<?> function, ArgumentsListExpressionNode argsGroup, EvalContext context) {
return function.thenCompose(fun -> {
if (fun instanceof OpelAsyncFunction) {
return ((OpelAsyncFunction) fun).apply(argsGroup.getListOfValues(context));
}
throw new OpelException("Can't use '" + fun.getClass().getSimpleName() + "' as a function");
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package pl.allegro.tech.opel;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Collectors;

public class FunctionInstantiationExpressionNode implements OpelNode {
private final IdentifiersListNode arguments;
private final OpelNode body;

public FunctionInstantiationExpressionNode(IdentifiersListNode arguments, OpelNode body) {
this.arguments = arguments;
this.body = body;
}

@Override
public CompletableFuture<?> getValue(EvalContext context) {
return CompletableFuture.completedFuture(new OpelAsyncFunction<Object>() {
@Override
public CompletableFuture<Object> apply(List<CompletableFuture<?>> args) {

List<CompletableFuture<Object>> collect = arguments.getIdentifiers().stream()
.map(it -> it.getValue(context))
.map(it -> javaGenericWorkaround(it))
.collect(Collectors.toList());
CompletableFuture<List<Object>> argsNames = FutureUtil.sequence(collect);

return argsNames.thenCompose(names -> {
EvalContextBuilder contextBuilder = EvalContextBuilder.create().withExternalEvalContext(context);
for (int i = 0; i < names.size(); i++) {
String name = (String) names.get(i);
if (args.size() == i) {
throw new OpelException("Missing argument '" + name + "' in function call");
}
CompletableFuture<Object> value = args.get(i).thenApply(Function.identity());
contextBuilder.withValue(name, value);
}
EvalContext localContext = contextBuilder.build();
return body.getValue(localContext).thenApply(Function.identity());
});

}
});
}

private CompletableFuture<Object> javaGenericWorkaround(CompletableFuture<?> future) {
return future.thenApply(Function.identity());
}
}
26 changes: 26 additions & 0 deletions src/main/java/pl/allegro/tech/opel/IdentifiersListNode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package pl.allegro.tech.opel;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;

public class IdentifiersListNode implements OpelNode {
private final List<OpelNode> identifiers;

public IdentifiersListNode(List<OpelNode> identifiers) {
this.identifiers = identifiers;
}

@Override
public CompletableFuture<?> getValue(EvalContext context) {
throw new UnsupportedOperationException("Can't get value on ArgumentsListExpressionNode");
}

public static OpelNode empty() {
return new IdentifiersListNode(Collections.emptyList());
}

public List<OpelNode> getIdentifiers() {
return identifiers;
}
}
41 changes: 39 additions & 2 deletions src/main/java/pl/allegro/tech/opel/OpelNodeFactory.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package pl.allegro.tech.opel;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

Expand Down Expand Up @@ -37,8 +38,12 @@ public OpelNode mapAccess(OpelNode subject, OpelNode fieldName) {
return new MapAccessExpressionNode(subject, fieldName);
}

public OpelNode functionCallNode(OpelNode pop, OpelNode functionArguments) {
return FunctionCallExpressionNode.create(pop, functionArguments);
public OpelNode functionCallNode(OpelNode identifier, OpelNode functionArguments) {
return FunctionCallExpressionNode.create(identifier, functionArguments);
}

public OpelNode anonymousFunctionCallNode(OpelNode expression, OpelNode functionArguments) {
return new AnonymousFunctionExpressionNode(expression, (ArgumentsListExpressionNode) functionArguments);
}

public OpelNode methodCall(OpelNode subject, OpelNode methodName, OpelNode functionArguments) {
Expand Down Expand Up @@ -79,4 +84,36 @@ public OpelNode program(OpelNode declarationsList, OpelNode expression) {
public OpelNode listInstantiation(OpelNode listElements) {
return new ListInstantiationExpressionNode((ArgumentsListExpressionNode) listElements);
}

public OpelNode functionInstantiation(OpelNode arguments, OpelNode body) {
return new FunctionInstantiationExpressionNode((IdentifiersListNode) arguments, body);
}

public OpelNode emptyIdentifiersList() {
return IdentifiersListNode.empty();
}

public OpelNode identifiersList(OpelNode identifiers, OpelNode identifier) {
ArrayList<OpelNode> allArgs = new ArrayList<>(((IdentifiersListNode) identifiers).getIdentifiers());
allArgs.add(identifier);
return new IdentifiersListNode(allArgs);
}

public OpelNode emptyArgsGroup() {
return ArgsGroupNode.empty();
}

public OpelNode argsGroup(OpelNode argsGroups, OpelNode argsGroup) {
List<ArgumentsListExpressionNode> allGroups = new ArrayList<>(((ArgsGroupNode) argsGroups).getGroups());
allGroups.add((ArgumentsListExpressionNode) argsGroup);
return new ArgsGroupNode(allGroups);
}

public OpelNode argsGroup(OpelNode argsGroup) {
return new ArgsGroupNode(Arrays.asList((ArgumentsListExpressionNode) argsGroup));
}

public OpelNode functionChain(OpelNode expression, OpelNode argsGroups) {
return new FunctionChainExpressionNode(expression, (ArgsGroupNode) argsGroups);
}
}
63 changes: 59 additions & 4 deletions src/main/java/pl/allegro/tech/opel/OpelParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ Rule ParsingUnit() {
}

Rule Program() {
return Body();
}

Rule Body() {
return Sequence(Declarations(), Expression(), Optional("; "), push(nodeFactory.program(pop(1), pop())));
}

Expand All @@ -36,7 +40,14 @@ Rule Factor() {
}

Rule Object() {
return FirstOf(FunctionCall(),StringLiteral(), NamedValue(), ListInstantiation(), Sequence("( ", Expression(), ") "));
return FirstOf(
FunctionCallChain(),
StringLiteral(),
FunctionInstantiation(),
NamedValue(),
ListInstantiation(),
Sequence("( ", Expression(), ") ")
);
}

Rule Train() {
Expand Down Expand Up @@ -76,9 +87,27 @@ Rule escapedChar() {
return Sequence("\\", ANY);
}

Rule FunctionCallChain() {
return Sequence(FunctionCall(), ArgsGroups(), push(nodeFactory.functionChain(pop(1), pop())));
}

Rule ArgsGroups() {
return Sequence(
push(nodeFactory.emptyArgsGroup()),
ZeroOrMore(ArgsGroup(), EMPTY)
);
}

Rule ArgsGroup() {
return Sequence("( ", Args(), ") ", push(nodeFactory.argsGroup(pop(1), pop())));
}

Rule FunctionCall() {
return Sequence(Identifier(), "( ", Args(), ") ",
push(nodeFactory.functionCallNode(pop(1), pop())));
return FirstOf(
Sequence(Identifier(), "( ", Args(), ") ", push(nodeFactory.functionCallNode(pop(1), pop()))),
Sequence(FunctionInstantiation(), "( ", Args(), ") ", push(nodeFactory.anonymousFunctionCallNode(pop(1), pop()))),
Sequence("( ", Expression(), ") ", "( ", Args(), ") ", push(nodeFactory.functionChain(pop(1), nodeFactory.argsGroup(pop()))))
);
}

Rule Args() {
Expand Down Expand Up @@ -219,13 +248,39 @@ Rule IntNumber() {
);
}

Rule FunctionInstantiation() {
return Sequence(FunctionArgs(), "-> ", FunctionBody(), push(nodeFactory.functionInstantiation(pop(1), pop())));
}

Rule FunctionArgs() {
return FirstOf(
Sequence("( ", IdentifiersList(), ") "),
Sequence(push(nodeFactory.emptyIdentifiersList()), IdentifiersListItem())
);
}

Rule IdentifiersList() {
return Sequence(
push(nodeFactory.emptyIdentifiersList()),
FirstOf(Sequence(IdentifiersListItem(), ZeroOrMore(", ", IdentifiersListItem())), EMPTY));
}

Rule IdentifiersListItem() {
return Sequence(Identifier(), push(nodeFactory.identifiersList(pop(1), pop())));
}

Rule FunctionBody() {
return FirstOf(
Expression(),
Sequence("{ ", Body(), "} "));
}

Rule ListInstantiation() {
return Sequence(
"[ ",
Args(),
"] ",
push(nodeFactory.listInstantiation(pop())));

}

@SuppressSubnodes
Expand Down
Loading

0 comments on commit aa698c5

Please sign in to comment.