Skip to content

Commit

Permalink
Implement concurrent evaluator
Browse files Browse the repository at this point in the history
  • Loading branch information
predatorray committed Feb 28, 2016
1 parent ecdbdd2 commit b6a2b38
Show file tree
Hide file tree
Showing 9 changed files with 237 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package me.predatorray.bud.lisp.evaluator;

import me.predatorray.bud.lisp.lang.BudObject;
import me.predatorray.bud.lisp.lang.Environment;
import me.predatorray.bud.lisp.parser.Expression;
import me.predatorray.bud.lisp.parser.ExpressionVisitorAdapter;
import me.predatorray.bud.lisp.parser.ProcedureCall;
import me.predatorray.bud.lisp.util.ConcurrentUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

public class ConcurrentEvaluator implements Evaluator {

@Override
public BudObject evaluate(Expression expression, Environment environment) {
ForkJoinPool pool = new ForkJoinPool();
TaskConstructor taskConstructor = new TaskConstructor(environment);
expression.accept(taskConstructor);
ForkJoinTask<BudObject> submitted = pool.submit(taskConstructor.task);

try {
return ConcurrentUtils.getUninterruptibly(submitted);
} catch (ExecutionException e) {
throw toRuntimeException(e);
}
}

private static RuntimeException toRuntimeException(ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw ((RuntimeException) cause);
} else {
throw new EvaluatingException("unknown", cause);
}
}

private class SimpleEvaluatingTask extends RecursiveTask<BudObject> {

private final Expression expression;
private final Environment environment;

SimpleEvaluatingTask(Expression expression, Environment environment) {
this.expression = expression;
this.environment = environment;
}

@Override
protected BudObject compute() {
return expression.evaluate(environment, ConcurrentEvaluator.this);
}
}

private class ProcedureCallTask extends RecursiveTask<BudObject> {

private final ProcedureCall procedureCall;
private final Environment environment;

ProcedureCallTask(ProcedureCall procedureCall, Environment environment) {
this.procedureCall = procedureCall;
this.environment = environment;
}

@Override
protected BudObject compute() {
Expression operator = procedureCall.getOperator();
List<? extends Expression> operands = procedureCall.getOperands();
int operandSize = operands.size();

List<RecursiveTask<BudObject>> tasks = new ArrayList<>(operandSize + 1);
TaskConstructor taskConstructor = new TaskConstructor(environment);
operator.accept(taskConstructor);
tasks.add(taskConstructor.task);
for (Expression operand : operands) {
operand.accept(taskConstructor);
tasks.add(taskConstructor.task);
}

invokeAll(tasks);
try {
BudObject applicative = ConcurrentUtils.getUninterruptibly(tasks.get(0));
List<BudObject> arguments = new ArrayList<>(operandSize);
for (int i = 0; i < operandSize; i++) {
arguments.add(ConcurrentUtils.getUninterruptibly(tasks.get(i + 1)));
}
return ProcedureCall.apply(applicative, arguments);
} catch (ExecutionException e) {
throw toRuntimeException(e);
}
}
}

private class TaskConstructor extends ExpressionVisitorAdapter {

private final Environment environment;

RecursiveTask<BudObject> task = null;

TaskConstructor(Environment environment) {
this.environment = environment;
}

@Override
public void visit(ProcedureCall procedureCall) {
task = new ProcedureCallTask(procedureCall, environment);
}

@Override
protected void visitByDefault(final Expression expression) {
task = new SimpleEvaluatingTask(expression, environment);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ public EvaluatingException(String message) {
public EvaluatingException(String message, Expression expression) {
super(message + ": " + expression + " at " + expression.getLocation());
}

public EvaluatingException(String message, Throwable cause) {
super(message, cause);
}
}
25 changes: 18 additions & 7 deletions src/main/java/me/predatorray/bud/lisp/parser/ProcedureCall.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import me.predatorray.bud.lisp.lang.Environment;
import me.predatorray.bud.lisp.lang.Function;
import me.predatorray.bud.lisp.lexer.LeftParenthesis;
import me.predatorray.bud.lisp.util.Validation;

import java.util.ArrayList;
import java.util.List;
Expand All @@ -30,18 +31,28 @@ public void accept(ExpressionVisitor expressionVisitor) {
@Override
public BudObject evaluate(Environment environment, Evaluator evaluator) {
BudObject applicable = evaluator.evaluate(operator, environment);
if (!BudType.Category.FUNCTION.equals(applicable.getType().getCategory())) {
throw new EvaluatingException("not applicable", operator);
}
Function function = (Function) applicable;

List<BudType> argTypes = new ArrayList<>(operands.size());
List<BudObject> arguments = new ArrayList<>(operands.size());
for (Expression operand : operands) {
BudObject arg = evaluator.evaluate(operand, environment);
argTypes.add(arg.getType());
arguments.add(arg);
}

return apply(applicable, arguments);
}

public static BudObject apply(BudObject applicable, List<BudObject> arguments) {
Validation.notNull(applicable);
Validation.notNull(arguments);

if (!BudType.Category.FUNCTION.equals(applicable.getType().getCategory())) {
throw new EvaluatingException(applicable + " is not applicable");
}
Function function = (Function) applicable;

List<BudType> argTypes = new ArrayList<>(arguments.size());
for (BudObject argument : arguments) {
argTypes.add(argument.getType());
}
function.inspect(argTypes);
return function.apply(arguments);
}
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/me/predatorray/bud/lisp/util/ConcurrentUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package me.predatorray.bud.lisp.util;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class ConcurrentUtils {

public static <V> V getUninterruptibly(Future<V> future) throws ExecutionException {
boolean interrupted = false;
try {
while (true) {
try {
return future.get();
} catch (InterruptedException e) {
interrupted = true;
}
}
} finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
}
2 changes: 1 addition & 1 deletion src/main/java/me/predatorray/bud/lisp/util/Sets.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static <T> Set<T> union(Collection<? extends T> collection1, Collection<?
Validation.notNull(collection2);

int capacity = collection1.size() + collection2.size();
Set<T> union = new HashSet<T>(capacity);
Set<T> union = new HashSet<>(capacity);
union.addAll(collection1);
union.addAll(collection2);
return union;
Expand Down
5 changes: 5 additions & 0 deletions src/test/java/me/predatorray/bud/lisp/TailRecursionTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package me.predatorray.bud.lisp;

import me.predatorray.bud.lisp.evaluator.ConcurrentEvaluator;
import me.predatorray.bud.lisp.lang.BudNumber;
import me.predatorray.bud.lisp.test.AbstractInterpreterTest;
import org.junit.Ignore;
Expand All @@ -9,6 +10,10 @@

public class TailRecursionTest extends AbstractInterpreterTest {

public TailRecursionTest() {
super(new BudInterpreter(new ConcurrentEvaluator()));
}

@Ignore("tail recursion is not implemented")
@Test
public void testTailRecursion1() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@
import me.predatorray.bud.lisp.parser.Variable;
import me.predatorray.bud.lisp.test.AbstractBudLispTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;

import static org.junit.Assert.assertEquals;
Expand All @@ -33,11 +36,22 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;

public class NaiveEvaluatorTest extends AbstractBudLispTest {
@RunWith(Parameterized.class)
public class EvaluatorsTest extends AbstractBudLispTest {

private final NaiveEvaluator sut = new NaiveEvaluator();
private final Environment EMPTY_ENV = new Environment();

@Parameterized.Parameter(0)
public Evaluator sut;

@Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(
new Object[] {new NaiveEvaluator()},
new Object[] {new ConcurrentEvaluator()}
);
}

@Test
public void testEvaluateString() throws Exception {
// "hello" ==> "hello"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@

public abstract class AbstractInterpreterTest extends AbstractBudLispTest {

private final BudInterpreter interpreter = new BudInterpreter();
private final BudInterpreter interpreter;

public AbstractInterpreterTest() {
this(new BudInterpreter());
}

public AbstractInterpreterTest(BudInterpreter interpreter) {
this.interpreter = interpreter;
}

protected final void assertInterpretCorrectly(BudObject expected, String sourceClasspath)
throws URISyntaxException, IOException {
Expand Down
43 changes: 43 additions & 0 deletions src/test/java/me/predatorray/bud/lisp/util/SetsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package me.predatorray.bud.lisp.util;

import org.junit.Test;

import java.util.Arrays;
import java.util.Collection;
import java.util.Set;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class SetsTest {

@Test
public void testAsSet1() {
Object[] objects = new Object [] {new Object(), new Object()};
Set<Object> set = Sets.asSet(objects);
assertEquals(2, set.size());
for (Object object : objects) {
assertTrue(set.contains(object));
}
}

@Test
public void testAsSet2() {
Set<Object> set = Sets.asSet();
assertEquals(0, set.size());
}

@Test
public void testUnion1() {
Collection<Object> collection1 = Arrays.asList(new Object(), new Object());
Collection<Object> collection2 = Arrays.asList(new Object(), new Object(), new Object());
Set<Object> union = Sets.union(collection1, collection2);
assertEquals(5, union.size());
for (Object o1 : collection1) {
union.contains(o1);
}
for (Object o2 : collection2) {
union.contains(o2);
}
}
}

0 comments on commit b6a2b38

Please sign in to comment.