Skip to content

Commit

Permalink
add logical negation operation (allegro#27)
Browse files Browse the repository at this point in the history
* add logical negation operation
  • Loading branch information
tfij authored and bgalek committed Feb 13, 2018
1 parent 7be7754 commit 8ccabb7
Show file tree
Hide file tree
Showing 13 changed files with 177 additions and 29 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@ After checking some expression languages like:

opel aims at very simple expressions. Certainly it won't be enough to make complicated scripts - but we want opel to stay that way.

We discourage using *anonymous functions*, *code blocks*, *loops*, etc.

## What can opel do for you?

opel supports:
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ test {
}

task wrapper(type: Wrapper) {
gradleVersion = '2.14.1'
gradleVersion = '4.4.1'
}

nexusStaging {
Expand Down
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
3 changes: 1 addition & 2 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#Thu Jun 09 09:02:39 CEST 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-bin.zip
26 changes: 17 additions & 9 deletions gradlew
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/usr/bin/env sh

##############################################################################
##
Expand Down Expand Up @@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn ( ) {
warn () {
echo "$*"
}

die ( ) {
die () {
echo
echo "$*"
echo
Expand Down Expand Up @@ -154,11 +154,19 @@ if $cygwin ; then
esac
fi

# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
APP_ARGS=$(save "$@")

exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"

# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi

exec "$JAVACMD" "$@"
6 changes: 0 additions & 6 deletions gradlew.bat
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ goto fail
@rem Get command-line arguments, handling Windows variants

if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args

:win9xME_args
@rem Slurp the command line arguments.
Expand All @@ -60,11 +59,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute

set CMD_LINE_ARGS=%*
goto execute

:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$

:execute
@rem Setup the command line
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package pl.allegro.tech.opel;

import java.util.concurrent.CompletableFuture;

class LogicalNegationOperatorExpressionNode implements OpelNode {

private final OpelNode opelNode;
private final ImplicitConversion conversion;

LogicalNegationOperatorExpressionNode(OpelNode opelNode, ImplicitConversion conversion) {
this.opelNode = opelNode;
this.conversion = conversion;
}

@Override
public CompletableFuture<?> getValue(EvalContext context) {
return opelNode.getValue(context)
.thenApply(this::getValue);
}

private Boolean getValue(Object value) {
if (value == null) {
throw new OpelException("Can't negate null value");
} if (value instanceof Boolean) {
return !((boolean)value);
} else if (conversion.hasConverter(value, Boolean.class)) {
return !conversion.convert(value, Boolean.class);
}
throw new OpelException("Can't negate " + value.getClass().getSimpleName() + " type");
}
}
3 changes: 1 addition & 2 deletions src/main/java/pl/allegro/tech/opel/OpelEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
public class OpelEngine {
private final ThreadLocal<OpelParser> parser;
private final ImplicitConversion implicitConversion;

private EvalContext embeddedEvalContext = EvalContext.empty();
private final EvalContext embeddedEvalContext;

OpelEngine(MethodExecutionFilter methodExecutionFilter, ImplicitConversion implicitConversion, EvalContext embeddedEvalContext) {
this.embeddedEvalContext = embeddedEvalContext;
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/pl/allegro/tech/opel/OpelNodeFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,8 @@ public PairsListNode emptyPairsListNode() {
public OpelNode mapInstantiationExpressionNode(OpelNode pairs) {
return new MapInstantiationExpressionNode((PairsListNode)pairs);
}

public OpelNode logicalNegationOperatorExpressionNode(OpelNode value) {
return new LogicalNegationOperatorExpressionNode(value, implicitConversion);
}
}
13 changes: 10 additions & 3 deletions src/main/java/pl/allegro/tech/opel/OpelParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,16 +147,23 @@ Rule AdditiveExpression() {

Rule MultiplyExpression() {
return Sequence(
Factor(),
LogicalNegation(),
ZeroOrMore(
FirstOf(
Sequence("* ", Factor(), push(binaryOperation(Operator.MULTIPLY))),
Sequence("/ ", Factor(), push(binaryOperation(Operator.DIV)))
Sequence("* ", LogicalNegation(), push(binaryOperation(Operator.MULTIPLY))),
Sequence("/ ", LogicalNegation(), push(binaryOperation(Operator.DIV)))
)
)
);
}

Rule LogicalNegation() {
return FirstOf(
Sequence("! ", LogicalNegation(), push(nodeFactory.logicalNegationOperatorExpressionNode(pop()))),
Factor()
);
}

Rule Declarations() {
return Sequence(push(nodeFactory.emptyDeclarationsList()), ZeroOrMore(Declaration()));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package pl.allegro.tech.opel

import spock.lang.Ignore
import spock.lang.Specification
import spock.lang.Unroll

import java.util.concurrent.CompletionException

class LogicalNegationOperatorExpressionNodeSpec extends Specification {

@Unroll
def 'should return negation of boolean'() {
given:
def conversion = new ImplicitConversion()
def node = new LogicalNegationOperatorExpressionNode(new LiteralExpressionNode(booleanValue), conversion)

expect:
node.getValue(EvalContext.empty()).get() == !booleanValue

where:
booleanValue << [true, false]
}

@Unroll
def 'should return negation of converted value'() {
given:
def conversion = new ImplicitConversion()
conversion.register(convertion)
def node = new LogicalNegationOperatorExpressionNode(new LiteralExpressionNode(new Foo()), conversion)

expect:
node.getValue(EvalContext.empty()).get() == expectedValue

where:
convertion || expectedValue
new ImplicitConversionUnit<>(Foo, Boolean, { true }) || false
new ImplicitConversionUnit<>(Foo, Boolean, { false }) || true
}

def 'should thrown exception on negation of nulls'() {
given:
def conversion = new ImplicitConversion()
def node = new LogicalNegationOperatorExpressionNode(new LiteralExpressionNode(null), conversion)

when:
node.getValue(EvalContext.empty()).join()

then:
def ex = thrown CompletionException
ex.cause.class == OpelException
}

def 'should throw exception on negation of not boolean type'() {
given:
def conversion = new ImplicitConversion()
def node = new LogicalNegationOperatorExpressionNode(new LiteralExpressionNode("some string"), conversion)

when:
node.getValue(EvalContext.empty()).join()

then:
def ex = thrown CompletionException
ex.cause.class == OpelException
}

static class Foo {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import java.util.concurrent.ExecutionException
import static pl.allegro.tech.opel.OpelEngineBuilder.create
import static pl.allegro.tech.opel.TestUtil.constFunctionReturning
import static pl.allegro.tech.opel.TestUtil.functions
import static pl.allegro.tech.opel.TestUtil.identityFunction

class OpelEngineMathIntegrationSpec extends Specification {
@Unroll
Expand Down Expand Up @@ -139,6 +140,43 @@ class OpelEngineMathIntegrationSpec extends Specification {
"identity(if (false || (true && true)) true else false)" || true
}

@Unroll
def '! should negate boolean expression #input'() {
given:
def engine = create()
.withImplicitConversion(String, BigDecimal, { string -> new BigDecimal(string) })
.withImplicitConversion(BigDecimal, String, { decimal -> decimal.toPlainString() })
.build()
def evalContext = EvalContextBuilder.create()
.withCompletedValue('value', true)
.withCompletedValue('fun', constFunctionReturning(false))
.withCompletedValue('identity', identityFunction())
.build()

expect:
engine.eval(input, evalContext).get() == expResult

where:
input || expResult
"!true" || false
"! true" || false
"! !true" || true
"!!true" || true
"!false" || true
"!(true)" || false
"!value" || false
"!(value)" || false
"!fun('')" || true
"!(fun(''))" || true
"!true || true" || true
"!(true || true)" || false
"!(true || true)" || false
"!(false || !false)" || false
"identity(!true)" || false
"!identity(!true)" || true
"true && !false" || true
}

def 'AND operator should have higher priority than OR'() {
given:
def engine = create().build()
Expand Down
9 changes: 5 additions & 4 deletions src/test/groovy/pl/allegro/tech/opel/TestUtil.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,13 @@ class TestUtil {
def functionWith4Args = function({ args ->
args.join("")
})
def identityFunction = function({ args ->
args[0]
})
return [
'zero' : CompletableFuture.completedFuture((OpelAsyncFunction<?>) constFunctionReturning('zero')),
'one' : CompletableFuture.completedFuture((OpelAsyncFunction<?>) constFunctionReturning('one')),
'twoArgsFunc' : CompletableFuture.completedFuture(functionWith2Args),
'oneTwoThree' : CompletableFuture.completedFuture(constFunctionReturning('one two three')),
'fourArgsFunc': CompletableFuture.completedFuture(functionWith4Args),
'identity' : CompletableFuture.completedFuture(identityFunction)
'identity' : CompletableFuture.completedFuture(identityFunction())
];
}

Expand All @@ -28,6 +25,10 @@ class TestUtil {
function({ args -> result })
}

static def identityFunction() {
function({ args -> args[0] })
}

static function(def body) {
return { args ->
FutureUtil.sequence(args).thenApply { completedArgs ->
Expand Down

0 comments on commit 8ccabb7

Please sign in to comment.