mirror of https://github.com/apache/lucene.git
LUCENE-5207: lucene expressions module
git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1523462 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
commit
eecd78f37a
|
@ -23,6 +23,7 @@
|
|||
<module filepath="$PROJECT_DIR$/lucene/codecs/src/test/codecs-tests.iml" />
|
||||
<module filepath="$PROJECT_DIR$/lucene/codecs/src/resources/codecs-resources.iml" />
|
||||
<module filepath="$PROJECT_DIR$/lucene/demo/demo.iml" />
|
||||
<module filepath="$PROJECT_DIR$/lucene/expressions/expressions.iml" />
|
||||
<module filepath="$PROJECT_DIR$/lucene/facet/facet.iml" />
|
||||
<module filepath="$PROJECT_DIR$/lucene/grouping/grouping.iml" />
|
||||
<module filepath="$PROJECT_DIR$/lucene/highlighter/highlighter.iml" />
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="false">
|
||||
<output url="file://$MODULE_DIR$/../../idea-build/lucene/expressions/classes/java" />
|
||||
<output-test url="file://$MODULE_DIR$/../../idea-build/lucene/expressions/classes/test" />
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/resources" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test" isTestSource="true" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="file://$MODULE_DIR$/lib" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
<jarDirectory url="file://$MODULE_DIR$/lib" recursive="false" />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="library" scope="TEST" name="JUnit" level="project" />
|
||||
<orderEntry type="module" scope="TEST" module-name="lucene-test-framework" />
|
||||
<orderEntry type="module" module-name="lucene-core" />
|
||||
<orderEntry type="module" module-name="queries" />
|
||||
</component>
|
||||
</module>
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-parent</artifactId>
|
||||
<version>@version@</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-expressions</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Lucene Expressions</name>
|
||||
<description>
|
||||
Dynamically computed values to sort/facet/search on based on a pluggable grammar.
|
||||
</description>
|
||||
<properties>
|
||||
<module-directory>lucene/expressions</module-directory>
|
||||
<relative-top-level>../../..</relative-top-level>
|
||||
<module-path>${relative-top-level}/${module-directory}</module-path>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<!-- lucene-test-framework dependency must be declared before lucene-core -->
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>lucene-test-framework</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.antlr</groupId>
|
||||
<artifactId>antlr-runtime</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm-commons</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>lucene-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>lucene-queries</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<sourceDirectory>${module-path}/src/java</sourceDirectory>
|
||||
<testSourceDirectory>${module-path}/src/test</testSourceDirectory>
|
||||
</build>
|
||||
</project>
|
|
@ -47,6 +47,7 @@
|
|||
<module>benchmark</module>
|
||||
<module>classification</module>
|
||||
<module>demo</module>
|
||||
<module>expressions</module>
|
||||
<module>facet</module>
|
||||
<module>grouping</module>
|
||||
<module>highlighter</module>
|
||||
|
|
|
@ -265,6 +265,32 @@
|
|||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.antlr</groupId>
|
||||
<artifactId>antlr-runtime</artifactId>
|
||||
<version>3.5</version>
|
||||
<!-- these are dependencies to compile antlr-runtime itself -->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>antlr</groupId>
|
||||
<artifactId>antlr</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.antlr</groupId>
|
||||
<artifactId>stringtemplate</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm</artifactId>
|
||||
<version>4.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm-commons</artifactId>
|
||||
<version>4.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.ant</groupId>
|
||||
<artifactId>ant</artifactId>
|
||||
|
|
|
@ -651,7 +651,7 @@ def verifyUnpacked(project, artifact, unpackPath, svnRevision, version, testArgs
|
|||
|
||||
if project == 'lucene':
|
||||
# TODO: clean this up to not be a list of modules that we must maintain
|
||||
extras = ('analysis', 'benchmark', 'classification', 'codecs', 'core', 'demo', 'docs', 'facet', 'grouping', 'highlighter', 'join', 'memory', 'misc', 'queries', 'queryparser', 'replicator', 'sandbox', 'spatial', 'suggest', 'test-framework', 'licenses')
|
||||
extras = ('analysis', 'benchmark', 'classification', 'codecs', 'core', 'demo', 'docs', 'expressions', 'facet', 'grouping', 'highlighter', 'join', 'memory', 'misc', 'queries', 'queryparser', 'replicator', 'sandbox', 'spatial', 'suggest', 'test-framework', 'licenses')
|
||||
if isSrc:
|
||||
extras += ('build.xml', 'common-build.xml', 'module-build.xml', 'ivy-settings.xml', 'backwards', 'tools', 'site')
|
||||
else:
|
||||
|
|
|
@ -58,6 +58,10 @@ New Features
|
|||
String is too restrictive (Robert Muir, Shai Erera, Mike
|
||||
McCandless)
|
||||
|
||||
* LUCENE-5207: Added expressions module for customizing ranking
|
||||
with script-like syntax.
|
||||
(Jack Conradson, Ryan Ernst, Uwe Schindler via Robert Muir)
|
||||
|
||||
Bug Fixes
|
||||
|
||||
* LUCENE-4998: Fixed a few places to pass IOContext.READONCE instead
|
||||
|
|
|
@ -283,6 +283,7 @@
|
|||
<!-- codecs: problems -->
|
||||
<!-- core: problems -->
|
||||
<check-missing-javadocs dir="build/docs/demo" level="method"/>
|
||||
<check-missing-javadocs dir="build/docs/expressions" level="method"/>
|
||||
<!-- facet: problems -->
|
||||
<!-- grouping: problems -->
|
||||
<!-- highlighter: problems -->
|
||||
|
|
|
@ -1567,6 +1567,8 @@ ${tests-output}/junit4-*.suites - per-JVM executed suites
|
|||
<pattern substring="This file was generated automatically by the Snowball to Java compiler"/>
|
||||
<!-- uima tests generated by JCasGen -->
|
||||
<pattern substring="First created by JCasGen"/>
|
||||
<!-- parsers generated by antlr -->
|
||||
<pattern substring="ANTLR GENERATED CODE"/>
|
||||
</rat:substringMatcher>
|
||||
|
||||
<!-- built in approved licenses -->
|
||||
|
|
|
@ -44,6 +44,13 @@ public final class MathUtil {
|
|||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates logarithm in a given base with doubles.
|
||||
*/
|
||||
public static double log(double base, double x) {
|
||||
return Math.log(x) / Math.log(base);
|
||||
}
|
||||
|
||||
/** Return the greatest common divisor of <code>a</code> and <code>b</code>,
|
||||
* consistently with {@link BigInteger#gcd(BigInteger)}.
|
||||
* <p><b>NOTE</b>: A greatest common divisor must be positive, but
|
||||
|
@ -78,4 +85,69 @@ public final class MathUtil {
|
|||
}
|
||||
return a << commonTrailingZeros;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates inverse hyperbolic sine of a {@code double} value.
|
||||
* <p>
|
||||
* Special cases:
|
||||
* <ul>
|
||||
* <li>If the argument is NaN, then the result is NaN.
|
||||
* <li>If the argument is zero, then the result is a zero with the same sign as the argument.
|
||||
* <li>If the argument is infinite, then the result is infinity with the same sign as the argument.
|
||||
* </ul>
|
||||
*/
|
||||
public static double asinh(double a) {
|
||||
final double sign;
|
||||
// check the sign bit of the raw representation to handle -0
|
||||
if (Double.doubleToRawLongBits(a) < 0) {
|
||||
a = Math.abs(a);
|
||||
sign = -1.0d;
|
||||
} else {
|
||||
sign = 1.0d;
|
||||
}
|
||||
|
||||
return sign * Math.log(Math.sqrt(a * a + 1.0d) + a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates inverse hyperbolic cosine of a {@code double} value.
|
||||
* <p>
|
||||
* Special cases:
|
||||
* <ul>
|
||||
* <li>If the argument is NaN, then the result is NaN.
|
||||
* <li>If the argument is +1, then the result is a zero.
|
||||
* <li>If the argument is positive infinity, then the result is positive infinity.
|
||||
* <li>If the argument is less than 1, then the result is NaN.
|
||||
* </ul>
|
||||
*/
|
||||
public static double acosh(double a) {
|
||||
return Math.log(Math.sqrt(a * a - 1.0d) + a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates inverse hyperbolic tangent of a {@code double} value.
|
||||
* <p>
|
||||
* Special cases:
|
||||
* <ul>
|
||||
* <li>If the argument is NaN, then the result is NaN.
|
||||
* <li>If the argument is zero, then the result is a zero with the same sign as the argument.
|
||||
* <li>If the argument is +1, then the result is positive infinity.
|
||||
* <li>If the argument is -1, then the result is negative infinity.
|
||||
* <li>If the argument's absolute value is greater than 1, then the result is NaN.
|
||||
* </ul>
|
||||
*/
|
||||
public static double atanh(double a) {
|
||||
final double mult;
|
||||
// check the sign bit of the raw representation to handle -0
|
||||
if (Double.doubleToRawLongBits(a) < 0) {
|
||||
a = Math.abs(a);
|
||||
mult = -0.5d;
|
||||
} else {
|
||||
mult = 0.5d;
|
||||
}
|
||||
return mult * Math.log((1.0d + a) / (1.0d - a));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -102,4 +102,80 @@ public class TestMathUtil extends LuceneTestCase {
|
|||
assertEquals(Long.MIN_VALUE, MathUtil.gcd(Long.MIN_VALUE, Long.MIN_VALUE));
|
||||
}
|
||||
|
||||
public void testAcoshMethod() {
|
||||
// acosh(NaN) == NaN
|
||||
assertTrue(Double.isNaN(MathUtil.acosh(Double.NaN)));
|
||||
// acosh(1) == +0
|
||||
assertEquals(0, Double.doubleToLongBits(MathUtil.acosh(1D)));
|
||||
// acosh(POSITIVE_INFINITY) == POSITIVE_INFINITY
|
||||
assertEquals(Double.doubleToLongBits(Double.POSITIVE_INFINITY),
|
||||
Double.doubleToLongBits(MathUtil.acosh(Double.POSITIVE_INFINITY)));
|
||||
// acosh(x) : x < 1 == NaN
|
||||
assertTrue(Double.isNaN(MathUtil.acosh(0.9D))); // x < 1
|
||||
assertTrue(Double.isNaN(MathUtil.acosh(0D))); // x == 0
|
||||
assertTrue(Double.isNaN(MathUtil.acosh(-0D))); // x == -0
|
||||
assertTrue(Double.isNaN(MathUtil.acosh(-0.9D))); // x < 0
|
||||
assertTrue(Double.isNaN(MathUtil.acosh(-1D))); // x == -1
|
||||
assertTrue(Double.isNaN(MathUtil.acosh(-10D))); // x < -1
|
||||
assertTrue(Double.isNaN(MathUtil.acosh(Double.NEGATIVE_INFINITY))); // x == -Inf
|
||||
|
||||
double epsilon = 0.000001;
|
||||
assertEquals(0, MathUtil.acosh(1), epsilon);
|
||||
assertEquals(1.5667992369724109, MathUtil.acosh(2.5), epsilon);
|
||||
assertEquals(14.719378760739708, MathUtil.acosh(1234567.89), epsilon);
|
||||
}
|
||||
|
||||
public void testAsinhMethod() {
|
||||
|
||||
// asinh(NaN) == NaN
|
||||
assertTrue(Double.isNaN(MathUtil.asinh(Double.NaN)));
|
||||
// asinh(+0) == +0
|
||||
assertEquals(0, Double.doubleToLongBits(MathUtil.asinh(0D)));
|
||||
// asinh(-0) == -0
|
||||
assertEquals(Double.doubleToLongBits(-0D), Double.doubleToLongBits(MathUtil.asinh(-0D)));
|
||||
// asinh(POSITIVE_INFINITY) == POSITIVE_INFINITY
|
||||
assertEquals(Double.doubleToLongBits(Double.POSITIVE_INFINITY),
|
||||
Double.doubleToLongBits(MathUtil.asinh(Double.POSITIVE_INFINITY)));
|
||||
// asinh(NEGATIVE_INFINITY) == NEGATIVE_INFINITY
|
||||
assertEquals(Double.doubleToLongBits(Double.NEGATIVE_INFINITY),
|
||||
Double.doubleToLongBits(MathUtil.asinh(Double.NEGATIVE_INFINITY)));
|
||||
|
||||
double epsilon = 0.000001;
|
||||
assertEquals(-14.719378760740035, MathUtil.asinh(-1234567.89), epsilon);
|
||||
assertEquals(-1.6472311463710958, MathUtil.asinh(-2.5), epsilon);
|
||||
assertEquals(-0.8813735870195429, MathUtil.asinh(-1), epsilon);
|
||||
assertEquals(0, MathUtil.asinh(0), 0);
|
||||
assertEquals(0.8813735870195429, MathUtil.asinh(1), epsilon);
|
||||
assertEquals(1.6472311463710958, MathUtil.asinh(2.5), epsilon);
|
||||
assertEquals(14.719378760740035, MathUtil.asinh(1234567.89), epsilon );
|
||||
}
|
||||
|
||||
public void testAtanhMethod() {
|
||||
// atanh(NaN) == NaN
|
||||
assertTrue(Double.isNaN(MathUtil.atanh(Double.NaN)));
|
||||
// atanh(+0) == +0
|
||||
assertEquals(0, Double.doubleToLongBits(MathUtil.atanh(0D)));
|
||||
// atanh(-0) == -0
|
||||
assertEquals(Double.doubleToLongBits(-0D),
|
||||
Double.doubleToLongBits(MathUtil.atanh(-0D)));
|
||||
// atanh(1) == POSITIVE_INFINITY
|
||||
assertEquals(Double.doubleToLongBits(Double.POSITIVE_INFINITY),
|
||||
Double.doubleToLongBits(MathUtil.atanh(1D)));
|
||||
// atanh(-1) == NEGATIVE_INFINITY
|
||||
assertEquals(Double.doubleToLongBits(Double.NEGATIVE_INFINITY),
|
||||
Double.doubleToLongBits(MathUtil.atanh(-1D)));
|
||||
// atanh(x) : Math.abs(x) > 1 == NaN
|
||||
assertTrue(Double.isNaN(MathUtil.atanh(1.1D))); // x > 1
|
||||
assertTrue(Double.isNaN(MathUtil.atanh(Double.POSITIVE_INFINITY))); // x == Inf
|
||||
assertTrue(Double.isNaN(MathUtil.atanh(-1.1D))); // x < -1
|
||||
assertTrue(Double.isNaN(MathUtil.atanh(Double.NEGATIVE_INFINITY))); // x == -Inf
|
||||
|
||||
double epsilon = 0.000001;
|
||||
assertEquals(Double.NEGATIVE_INFINITY, MathUtil.atanh(-1), 0);
|
||||
assertEquals(-0.5493061443340549, MathUtil.atanh(-0.5), epsilon);
|
||||
assertEquals(0, MathUtil.atanh(0), 0);
|
||||
assertEquals(0.5493061443340549, MathUtil.atanh(0.5), epsilon);
|
||||
assertEquals(Double.POSITIVE_INFINITY, MathUtil.atanh(1), 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<project name="expressions" default="default">
|
||||
|
||||
<description>
|
||||
Dynamically computed values to sort/facet/search on based on a pluggable grammar.
|
||||
</description>
|
||||
|
||||
<!-- some files for testing that do not have license headers -->
|
||||
<property name="rat.excludes" value="**/*.tokens"/>
|
||||
|
||||
<import file="../module-build.xml"/>
|
||||
|
||||
<path id="classpath">
|
||||
<path refid="base.classpath"/>
|
||||
<fileset dir="lib"/>
|
||||
<pathelement path="${queries.jar}"/>
|
||||
</path>
|
||||
|
||||
<path id="test.classpath">
|
||||
<path refid="test.base.classpath"/>
|
||||
<fileset dir="lib"/>
|
||||
<pathelement path="src/test-files"/>
|
||||
</path>
|
||||
|
||||
<target name="compile-core" depends="jar-queries,common.compile-core" />
|
||||
|
||||
<target name="javadocs" depends="javadocs-queries,compile-core">
|
||||
<invoke-module-javadoc>
|
||||
<links>
|
||||
<link href="../queries"/>
|
||||
</links>
|
||||
</invoke-module-javadoc>
|
||||
</target>
|
||||
|
||||
<target name="regenerate" depends="run-antlr"/>
|
||||
|
||||
<target name="resolve-antlr" xmlns:ivy="antlib:org.apache.ivy.ant">
|
||||
<ivy:cachepath organisation="org.antlr" module="antlr" revision="3.5"
|
||||
inline="true" conf="default" type="jar" pathid="antlr.classpath"/>
|
||||
</target>
|
||||
|
||||
<target name="run-antlr" depends="resolve-antlr">
|
||||
<regen-grammar package="js" grammar="Javascript"/>
|
||||
</target>
|
||||
|
||||
<macrodef name="replace-value">
|
||||
<attribute name="value" />
|
||||
<attribute name="property" />
|
||||
<attribute name="from" />
|
||||
<attribute name="to" />
|
||||
<sequential>
|
||||
<loadresource property="@{property}">
|
||||
<string value="@{value}"/>
|
||||
<filterchain>
|
||||
<tokenfilter>
|
||||
<filetokenizer/>
|
||||
<replacestring from="@{from}" to="@{to}"/>
|
||||
</tokenfilter>
|
||||
</filterchain>
|
||||
</loadresource>
|
||||
</sequential>
|
||||
</macrodef>
|
||||
|
||||
<macrodef name="regen-grammar">
|
||||
<attribute name="package" />
|
||||
<attribute name="grammar" />
|
||||
<sequential>
|
||||
<!-- delete parser and lexer so files will be generated -->
|
||||
<delete>
|
||||
<fileset dir="src/java/org/apache/lucene/expressions/@{package}">
|
||||
<include name="@{grammar}Lexer.java" />
|
||||
<include name="@{grammar}Parser.java" />
|
||||
</fileset>
|
||||
</delete>
|
||||
<local name="grammar.path"/>
|
||||
<local name="grammar.matchpath"/>
|
||||
<local name="-grammar.relative.path"/>
|
||||
<local name="grammar.relative.path"/>
|
||||
<property name="grammar.path" location="src/java/org/apache/lucene/expressions/@{package}"/>
|
||||
<!-- this is a hack for windows, because antlr writes absolute paths with double backslashes (no idea why...): -->
|
||||
<replace-value property="grammar.matchpath" value="${grammar.path}${file.separator}" from="\" to="\\"/>
|
||||
<property name="-grammar.relative.path" location="${grammar.path}" relative="true"/>
|
||||
<replace-value property="grammar.relative.path" value="${-grammar.relative.path}${file.separator}" from="${file.separator}" to="/"/>
|
||||
<java classname="org.antlr.Tool" fork="true" failonerror="true" classpathref="antlr.classpath" taskname="antlr">
|
||||
<!-- this is a hack because antlr generates code comments in nondeterministic order
|
||||
(using HashMap somewhere it should use LinkedHashMap). This hack only works for Java 7,
|
||||
Java 8 always uses murmurhash for strings and uses time-of-day as seed. -->
|
||||
<sysproperty key="jdk.map.althashing.threshold" value="-1"/>
|
||||
<sysproperty key="file.encoding" value="UTF-8"/>
|
||||
<sysproperty key="user.language" value="en"/>
|
||||
<sysproperty key="user.country" value="US"/>
|
||||
<sysproperty key="user.variant" value=""/>
|
||||
<arg value="-verbose"/>
|
||||
<arg value="-make"/>
|
||||
<arg value="-o"/>
|
||||
<arg path="${grammar.path}"/>
|
||||
<arg path="${grammar.path}/@{grammar}.g"/>
|
||||
</java>
|
||||
<!-- replace absolute paths by relative ones -->
|
||||
<replace file="${grammar.path}/@{grammar}Parser.java" token="${grammar.matchpath}" value="${grammar.relative.path}" encoding="UTF-8"/>
|
||||
<replace file="${grammar.path}/@{grammar}Lexer.java" token="${grammar.matchpath}" value="${grammar.relative.path}" encoding="UTF-8"/>
|
||||
<!-- make the generated classes package private (it's an antlr option with 4.0) -->
|
||||
<replace file="${grammar.path}/@{grammar}Parser.java" token="public class @{grammar}Parser" value="class @{grammar}Parser" encoding="UTF-8"/>
|
||||
<replace file="${grammar.path}/@{grammar}Lexer.java" token="public class @{grammar}Lexer" value="class @{grammar}Lexer" encoding="UTF-8"/>
|
||||
<!-- nuke timestamps in generated files -->
|
||||
<replaceregexp file="${grammar.path}/@{grammar}Parser.java" match=".*" replace="\/\/ ANTLR GENERATED CODE: DO NOT EDIT" encoding="UTF-8"/>
|
||||
<replaceregexp file="${grammar.path}/@{grammar}Lexer.java" match=".*" replace="\/\/ ANTLR GENERATED CODE: DO NOT EDIT" encoding="UTF-8"/>
|
||||
<!-- remove tabs in antlr generated files -->
|
||||
<replaceregexp file="${grammar.path}/@{grammar}Parser.java" match="\t" flags="g" replace=" " encoding="UTF-8"/>
|
||||
<replaceregexp file="${grammar.path}/@{grammar}Lexer.java" match="\t" flags="g" replace=" " encoding="UTF-8"/>
|
||||
<!-- fix line endings -->
|
||||
<fixcrlf file="${grammar.path}/@{grammar}Parser.java"/>
|
||||
<fixcrlf file="${grammar.path}/@{grammar}Lexer.java"/>
|
||||
</sequential>
|
||||
</macrodef>
|
||||
</project>
|
|
@ -0,0 +1,27 @@
|
|||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<ivy-module version="2.0">
|
||||
<info organisation="org.apache.lucene" module="expressions"/>
|
||||
<dependencies>
|
||||
<dependency org="org.antlr" name="antlr-runtime" rev="3.5" transitive="false"/>
|
||||
<dependency org="org.ow2.asm" name="asm" rev="4.1" transitive="false"/>
|
||||
<dependency org="org.ow2.asm" name="asm-commons" rev="4.1" transitive="false"/>
|
||||
<exclude org="*" ext="*" matcher="regexp" type="${ivy.exclude.types}"/>
|
||||
</dependencies>
|
||||
</ivy-module>
|
|
@ -0,0 +1,87 @@
|
|||
package org.apache.lucene.expressions;
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.lucene.queries.function.ValueSource;
|
||||
|
||||
/**
|
||||
* Binds variable names in expressions to actual data.
|
||||
* <p>
|
||||
* These are typically DocValues fields/FieldCache, the document's
|
||||
* relevance score, or other ValueSources.
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
public abstract class Bindings implements Iterable<String> {
|
||||
|
||||
/** Sole constructor. (For invocation by subclass
|
||||
* constructors, typically implicit.) */
|
||||
protected Bindings() {}
|
||||
|
||||
/**
|
||||
* Returns a ValueSource bound to the variable name.
|
||||
*/
|
||||
public abstract ValueSource getValueSource(String name);
|
||||
|
||||
/** Returns an <code>Iterator</code> over the variable names in this binding */
|
||||
@Override
|
||||
public abstract Iterator<String> iterator();
|
||||
|
||||
/**
|
||||
* Traverses the graph of bindings, checking there are no cycles or missing references
|
||||
* @throws IllegalArgumentException if the bindings is inconsistent
|
||||
*/
|
||||
public final void validate() {
|
||||
Set<String> marked = new HashSet<String>();
|
||||
Set<String> chain = new HashSet<String>();
|
||||
|
||||
for (String name : this) {
|
||||
if (!marked.contains(name)) {
|
||||
chain.add(name);
|
||||
validate(name, marked, chain);
|
||||
chain.remove(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validate(String name, Set<String> marked, Set<String> chain) {
|
||||
ValueSource vs = getValueSource(name);
|
||||
if (vs == null) {
|
||||
throw new IllegalArgumentException("Invalid reference '" + name + "'");
|
||||
}
|
||||
|
||||
if (vs instanceof ExpressionValueSource) {
|
||||
Expression expr = ((ExpressionValueSource)vs).expression;
|
||||
for (String external : expr.variables) {
|
||||
if (chain.contains(external)) {
|
||||
throw new IllegalArgumentException("Recursion Error: Cycle detected originating in (" + external + ")");
|
||||
}
|
||||
if (!marked.contains(external)) {
|
||||
chain.add(external);
|
||||
validate(external, marked, chain);
|
||||
chain.remove(external);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
marked.add(name);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package org.apache.lucene.expressions;
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import org.apache.lucene.expressions.js.JavascriptCompiler; // javadocs
|
||||
import org.apache.lucene.queries.function.FunctionValues;
|
||||
import org.apache.lucene.queries.function.ValueSource;
|
||||
import org.apache.lucene.search.SortField;
|
||||
|
||||
/**
|
||||
* Base class that computes the value of an expression for a document.
|
||||
* <p>
|
||||
* Example usage:
|
||||
* <pre class="prettyprint">
|
||||
* // compile an expression:
|
||||
* Expression expr = JavascriptCompiler.compile("sqrt(_score) + ln(popularity)");
|
||||
*
|
||||
* // SimpleBindings just maps variables to SortField instances
|
||||
* SimpleBindings bindings = new SimpleBindings();
|
||||
* bindings.add(new SortField("_score", SortField.Type.SCORE));
|
||||
* bindings.add(new SortField("popularity", SortField.Type.INT));
|
||||
*
|
||||
* // create a sort field and sort by it (reverse order)
|
||||
* Sort sort = new Sort(expr.getSortField(bindings, true));
|
||||
* Query query = new TermQuery(new Term("body", "contents"));
|
||||
* searcher.search(query, null, 10, sort);
|
||||
* </pre>
|
||||
* @see JavascriptCompiler#compile
|
||||
* @lucene.experimental
|
||||
*/
|
||||
public abstract class Expression {
|
||||
|
||||
/** The original source text */
|
||||
public final String sourceText;
|
||||
|
||||
/** Named variables referred to by this expression */
|
||||
public final String[] variables;
|
||||
|
||||
/**
|
||||
* Creates a new {@code Expression}.
|
||||
*
|
||||
* @param sourceText Source text for the expression: e.g. {@code ln(popularity)}
|
||||
* @param variables Names of external variables referred to by the expression
|
||||
*/
|
||||
protected Expression(String sourceText, String[] variables) {
|
||||
this.sourceText = sourceText;
|
||||
this.variables = variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the expression for the given document.
|
||||
*
|
||||
* @param document <code>docId</code> of the document to compute a value for
|
||||
* @param functionValues {@link FunctionValues} for each element of {@link #variables}.
|
||||
* @return The computed value of the expression for the given document.
|
||||
*/
|
||||
public abstract double evaluate(int document, FunctionValues[] functionValues);
|
||||
|
||||
/**
|
||||
* Get a value source which can compute the value of this expression in the context of the given bindings.
|
||||
* @param bindings Bindings to use for external values in this expression
|
||||
* @return A value source which will evaluate this expression when used
|
||||
*/
|
||||
public ValueSource getValueSource(Bindings bindings) {
|
||||
return new ExpressionValueSource(bindings, this);
|
||||
}
|
||||
|
||||
/** Get a sort field which can be used to rank documents by this expression. */
|
||||
public SortField getSortField(Bindings bindings, boolean reverse) {
|
||||
return getValueSource(bindings).getSortField(reverse);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package org.apache.lucene.expressions;
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.lucene.index.AtomicReaderContext;
|
||||
import org.apache.lucene.queries.function.FunctionValues;
|
||||
import org.apache.lucene.queries.function.ValueSource;
|
||||
import org.apache.lucene.search.FieldComparator;
|
||||
import org.apache.lucene.search.Scorer;
|
||||
|
||||
/** A custom comparator for sorting documents by an expression */
|
||||
class ExpressionComparator extends FieldComparator<Double> {
|
||||
private final double[] values;
|
||||
private double bottom;
|
||||
|
||||
private ValueSource source;
|
||||
private FunctionValues scores;
|
||||
private AtomicReaderContext readerContext;
|
||||
|
||||
public ExpressionComparator(ValueSource source, int numHits) {
|
||||
values = new double[numHits];
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
// TODO: change FieldComparator.setScorer to throw IOException and remove this try-catch
|
||||
@Override
|
||||
public void setScorer(Scorer scorer) {
|
||||
super.setScorer(scorer);
|
||||
// TODO: might be cleaner to lazy-init 'source' and set scorer after?
|
||||
assert readerContext != null;
|
||||
try {
|
||||
Map<String,Object> context = new HashMap<String,Object>();
|
||||
assert scorer != null;
|
||||
context.put("scorer", new ScoreFunctionValues(scorer));
|
||||
context.put("valuesCache", new HashMap<String, FunctionValues>());
|
||||
scores = source.getValues(context, readerContext);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(int slot1, int slot2) {
|
||||
return Double.compare(values[slot1], values[slot2]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBottom(int slot) {
|
||||
bottom = values[slot];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareBottom(int doc) throws IOException {
|
||||
return Double.compare(bottom, scores.doubleVal(doc));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copy(int slot, int doc) throws IOException {
|
||||
values[slot] = scores.doubleVal(doc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldComparator<Double> setNextReader(AtomicReaderContext context) throws IOException {
|
||||
this.readerContext = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double value(int slot) {
|
||||
return Double.valueOf(values[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, Double valueObj) throws IOException {
|
||||
return Double.compare(scores.doubleVal(doc), valueObj.doubleValue());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package org.apache.lucene.expressions;
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import org.apache.lucene.queries.function.FunctionValues;
|
||||
|
||||
/** A {@link FunctionValues} which evaluates an expression */
|
||||
class ExpressionFunctionValues extends FunctionValues {
|
||||
final Expression expression;
|
||||
final FunctionValues[] functionValues;
|
||||
|
||||
int currentDocument = -1;
|
||||
double currentValue;
|
||||
|
||||
public ExpressionFunctionValues(Expression expression, FunctionValues[] functionValues) {
|
||||
if (expression == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
if (functionValues == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
this.expression = expression;
|
||||
this.functionValues = functionValues;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleVal(int document) {
|
||||
if (currentDocument != document) {
|
||||
currentDocument = document;
|
||||
currentValue = expression.evaluate(document, functionValues);
|
||||
}
|
||||
|
||||
return currentValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object objectVal(int doc) {
|
||||
return doubleVal(doc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(int document) {
|
||||
return "ExpressionFunctionValues(" + document + ": " + objectVal(document) + ")";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package org.apache.lucene.expressions;
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.lucene.queries.function.ValueSource;
|
||||
import org.apache.lucene.search.FieldComparator;
|
||||
import org.apache.lucene.search.SortField;
|
||||
|
||||
/** A {@link SortField} which sorts documents by the evaluated value of an expression for each document */
|
||||
class ExpressionSortField extends SortField {
|
||||
private final ValueSource source;
|
||||
|
||||
ExpressionSortField(String name, ValueSource source, boolean reverse) {
|
||||
super(name, Type.CUSTOM, reverse);
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldComparator<?> getComparator(final int numHits, final int sortPos) throws IOException {
|
||||
return new ExpressionComparator(source, numHits);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package org.apache.lucene.expressions;
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.lucene.index.AtomicReaderContext;
|
||||
import org.apache.lucene.queries.function.FunctionValues;
|
||||
import org.apache.lucene.queries.function.ValueSource;
|
||||
import org.apache.lucene.search.SortField;
|
||||
|
||||
/**
|
||||
* A {@link ValueSource} which evaluates a {@link Expression} given the context of an {@link Bindings}.
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
final class ExpressionValueSource extends ValueSource {
|
||||
private final Bindings bindings;
|
||||
final Expression expression;
|
||||
|
||||
public ExpressionValueSource(Bindings bindings, Expression expression) {
|
||||
if (bindings == null) throw new NullPointerException();
|
||||
if (expression == null) throw new NullPointerException();
|
||||
this.bindings = bindings;
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
/** <code>context</code> must contain a key <code>"valuesCache"</code> which is a <code>Map<String,FunctionValues></code>. */
|
||||
@Override
|
||||
public FunctionValues getValues(Map context, AtomicReaderContext readerContext) throws IOException {
|
||||
ValueSource source;
|
||||
Map<String, FunctionValues> valuesCache = (Map<String, FunctionValues>)context.get("valuesCache");
|
||||
if (valuesCache == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
FunctionValues[] externalValues = new FunctionValues[expression.variables.length];
|
||||
|
||||
for (int i = 0; i < expression.variables.length; ++i) {
|
||||
String externalName = expression.variables[i];
|
||||
FunctionValues values = valuesCache.get(externalName);
|
||||
if (values == null) {
|
||||
source = bindings.getValueSource(externalName);
|
||||
values = source.getValues(context, readerContext);
|
||||
if (values == null) {
|
||||
throw new RuntimeException("Internal error. External (" + externalName + ") does not exist.");
|
||||
}
|
||||
valuesCache.put(externalName, values);
|
||||
}
|
||||
externalValues[i] = values;
|
||||
}
|
||||
|
||||
return new ExpressionFunctionValues(expression, externalValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortField getSortField(boolean reverse) {
|
||||
return new ExpressionSortField(expression.sourceText, this, reverse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "ExpressionValueSource(" + expression.sourceText + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return System.identityHashCode(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj == this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package org.apache.lucene.expressions;
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.lucene.queries.function.FunctionValues;
|
||||
import org.apache.lucene.search.Scorer;
|
||||
|
||||
/**
|
||||
* A utility class to allow expressions to access the score as a {@link FunctionValues}.
|
||||
*/
|
||||
class ScoreFunctionValues extends FunctionValues {
|
||||
final Scorer scorer;
|
||||
|
||||
ScoreFunctionValues(Scorer scorer) {
|
||||
this.scorer = scorer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleVal(int document) {
|
||||
try {
|
||||
assert document == scorer.docID();
|
||||
return scorer.score();
|
||||
} catch (IOException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(int document) {
|
||||
return "ScoreFunctionValues(" + document + ": " + doubleVal(document) + ")";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package org.apache.lucene.expressions;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import org.apache.lucene.index.AtomicReaderContext;
|
||||
import org.apache.lucene.queries.function.FunctionValues;
|
||||
import org.apache.lucene.queries.function.ValueSource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A {@link ValueSource} which uses the {@link ScoreFunctionValues} passed through
|
||||
* the context map by {@link ExpressionComparator}.
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes"})
|
||||
class ScoreValueSource extends ValueSource {
|
||||
|
||||
/**
|
||||
* <code>context</code> must contain a key "scorer" which is a {@link FunctionValues}.
|
||||
*/
|
||||
@Override
|
||||
public FunctionValues getValues(Map context, AtomicReaderContext readerContext) throws IOException {
|
||||
FunctionValues v = (FunctionValues) context.get("scorer");
|
||||
if (v == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o == this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return System.identityHashCode(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "ValueSource to expose scorer passed by ExpressionComparator";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
package org.apache.lucene.expressions;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.lucene.queries.function.ValueSource;
|
||||
import org.apache.lucene.queries.function.valuesource.DoubleFieldSource;
|
||||
import org.apache.lucene.queries.function.valuesource.FloatFieldSource;
|
||||
import org.apache.lucene.queries.function.valuesource.IntFieldSource;
|
||||
import org.apache.lucene.queries.function.valuesource.LongFieldSource;
|
||||
import org.apache.lucene.search.FieldCache.DoubleParser;
|
||||
import org.apache.lucene.search.FieldCache.FloatParser;
|
||||
import org.apache.lucene.search.FieldCache.IntParser;
|
||||
import org.apache.lucene.search.FieldCache.LongParser;
|
||||
import org.apache.lucene.search.SortField;
|
||||
|
||||
/**
|
||||
* Simple class that binds expression variable names to {@link SortField}s
|
||||
* or other {@link Expression}s.
|
||||
* <p>
|
||||
* Example usage:
|
||||
* <pre class="prettyprint">
|
||||
* SimpleBindings bindings = new SimpleBindings();
|
||||
* // document's text relevance score
|
||||
* bindings.add(new SortField("_score", SortField.Type.SCORE));
|
||||
* // integer NumericDocValues field (or from FieldCache)
|
||||
* bindings.add(new SortField("popularity", SortField.Type.INT));
|
||||
* // another expression
|
||||
* bindings.add("recency", myRecencyExpression);
|
||||
*
|
||||
* // create a sort field in reverse order
|
||||
* Sort sort = new Sort(expr.getSortField(bindings, true));
|
||||
* </pre>
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
public final class SimpleBindings extends Bindings {
|
||||
final Map<String,Object> map = new HashMap<String,Object>();
|
||||
|
||||
/** Creates a new empty Bindings */
|
||||
public SimpleBindings() {}
|
||||
|
||||
/**
|
||||
* Adds a SortField to the bindings.
|
||||
* <p>
|
||||
* This can be used to reference a DocValuesField, a field from
|
||||
* FieldCache, the document's score, etc.
|
||||
*/
|
||||
public void add(SortField sortField) {
|
||||
map.put(sortField.getField(), sortField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an Expression to the bindings.
|
||||
* <p>
|
||||
* This can be used to reference expressions from other expressions.
|
||||
*/
|
||||
public void add(String name, Expression expression) {
|
||||
map.put(name, expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueSource getValueSource(String name) {
|
||||
Object o = map.get(name);
|
||||
if (o == null) {
|
||||
throw new IllegalArgumentException("Invalid reference '" + name + "'");
|
||||
} else if (o instanceof Expression) {
|
||||
return ((Expression)o).getValueSource(this);
|
||||
}
|
||||
SortField field = (SortField) o;
|
||||
switch(field.getType()) {
|
||||
case INT:
|
||||
return new IntFieldSource(field.getField(), (IntParser) field.getParser());
|
||||
case LONG:
|
||||
return new LongFieldSource(field.getField(), (LongParser) field.getParser());
|
||||
case FLOAT:
|
||||
return new FloatFieldSource(field.getField(), (FloatParser) field.getParser());
|
||||
case DOUBLE:
|
||||
return new DoubleFieldSource(field.getField(), (DoubleParser) field.getParser());
|
||||
case SCORE:
|
||||
return new ScoreValueSource();
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return map.keySet().iterator();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,381 @@
|
|||
/*
|
||||
Javascript.g
|
||||
An expression syntax based on ECMAScript/Javascript.
|
||||
|
||||
This file was adapted from a general ECMAScript language definition at http://research.xebic.com/es3.
|
||||
The major changes are the following:
|
||||
* Stripped grammar of all parts not relevant for expression syntax.
|
||||
* Stripped grammar of unicode character support.
|
||||
* Added override function for customized error handling.
|
||||
* Renaming of many grammar rules.
|
||||
* Removal of annotations no longer relevant for stripped pieces.
|
||||
|
||||
The Original Copyright Notice is the following:
|
||||
|
||||
Copyrights 2008-2009 Xebic Reasearch BV. All rights reserved..
|
||||
Original work by Patrick Hulsmeijer.
|
||||
|
||||
This ANTLR 3 LL(*) grammar is based on Ecma-262 3rd edition (JavaScript 1.5, JScript 5.5).
|
||||
The annotations refer to the "A Grammar Summary" section (e.g. A.1 Lexical Grammar)
|
||||
and the numbers in parenthesis to the paragraph numbers (e.g. (7.8) ).
|
||||
This document is best viewed with ANTLRWorks (www.antlr.org).
|
||||
|
||||
Software License Agreement (BSD License)
|
||||
|
||||
Copyright (c) 2008-2010, Xebic Research B.V.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms, with or without modification, are
|
||||
permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* Neither the name of Xebic Research B.V. nor the names of its
|
||||
contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior
|
||||
written permission of Xebic Research B.V.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
// ***********************************************************************
|
||||
// * ANTLRv3 grammar for A9 rank expression language.
|
||||
// ***********************************************************************
|
||||
|
||||
grammar Javascript;
|
||||
|
||||
options {
|
||||
language = Java;
|
||||
output = AST;
|
||||
ASTLabelType=CommonTree;
|
||||
}
|
||||
|
||||
tokens
|
||||
{
|
||||
AT_LPAREN = '(' ;
|
||||
AT_RPAREN = ')' ;
|
||||
AT_DOT = '.' ;
|
||||
AT_COMMA = ',' ;
|
||||
AT_COLON = ':' ;
|
||||
|
||||
AT_COMP_LT = '<' ;
|
||||
AT_COMP_LTE = '<=' ;
|
||||
AT_COMP_EQ = '==' ;
|
||||
AT_COMP_NEQ = '!=' ;
|
||||
AT_COMP_GTE = '>=' ;
|
||||
AT_COMP_GT = '>' ;
|
||||
|
||||
AT_BOOL_NOT = '!' ;
|
||||
AT_BOOL_AND = '&&' ;
|
||||
AT_BOOL_OR = '||' ;
|
||||
AT_COND_QUE = '?' ;
|
||||
|
||||
AT_NEGATE ;
|
||||
AT_ADD = '+' ;
|
||||
AT_SUBTRACT = '-' ;
|
||||
AT_MULTIPLY = '*' ;
|
||||
AT_DIVIDE = '/' ;
|
||||
AT_MODULO = '%' ;
|
||||
|
||||
AT_BIT_SHL = '<<' ;
|
||||
AT_BIT_SHR = '>>' ;
|
||||
AT_BIT_SHU = '>>>';
|
||||
AT_BIT_AND = '&' ;
|
||||
AT_BIT_OR = '|' ;
|
||||
AT_BIT_XOR = '^' ;
|
||||
AT_BIT_NOT = '~' ;
|
||||
|
||||
AT_CALL ;
|
||||
}
|
||||
|
||||
// ***********************************************************************
|
||||
// * Java Package
|
||||
// ***********************************************************************
|
||||
|
||||
@lexer::header {
|
||||
package org.apache.lucene.expressions.js;
|
||||
|
||||
import java.text.ParseException;
|
||||
}
|
||||
|
||||
@parser::header {
|
||||
package org.apache.lucene.expressions.js;
|
||||
|
||||
import java.text.ParseException;
|
||||
}
|
||||
|
||||
// ***********************************************************************
|
||||
// * Error Handling
|
||||
// ***********************************************************************
|
||||
|
||||
@lexer::members {
|
||||
|
||||
@Override
|
||||
public void displayRecognitionError(String[] tokenNames, RecognitionException re) {
|
||||
String message = " unexpected character '" + (char)re.c
|
||||
+ "' at position (" + re.charPositionInLine + ").";
|
||||
ParseException parseException = new ParseException(message, re.charPositionInLine);
|
||||
parseException.initCause(re);
|
||||
throw new RuntimeException(parseException);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@parser::members {
|
||||
|
||||
@Override
|
||||
public void displayRecognitionError(String[] tokenNames, RecognitionException re) {
|
||||
String message;
|
||||
|
||||
if (re.token == null) {
|
||||
message = " unknown error (missing token).";
|
||||
}
|
||||
else if (re instanceof UnwantedTokenException) {
|
||||
message = " extraneous " + getReadableTokenString(re.token)
|
||||
+ " at position (" + re.charPositionInLine + ").";
|
||||
}
|
||||
else if (re instanceof MissingTokenException) {
|
||||
message = " missing " + getReadableTokenString(re.token)
|
||||
+ " at position (" + re.charPositionInLine + ").";
|
||||
}
|
||||
else if (re instanceof NoViableAltException) {
|
||||
switch (re.token.getType()) {
|
||||
case EOF:
|
||||
message = " unexpected end of expression.";
|
||||
break;
|
||||
default:
|
||||
message = " invalid sequence of tokens near " + getReadableTokenString(re.token)
|
||||
+ " at position (" + re.charPositionInLine + ").";
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
message = " unexpected token " + getReadableTokenString(re.token)
|
||||
+ " at position (" + re.charPositionInLine + ").";
|
||||
}
|
||||
ParseException parseException = new ParseException(message, re.charPositionInLine);
|
||||
parseException.initCause(re);
|
||||
throw new RuntimeException(parseException);
|
||||
}
|
||||
|
||||
public static String getReadableTokenString(Token token) {
|
||||
if (token == null) {
|
||||
return "unknown token";
|
||||
}
|
||||
|
||||
switch (token.getType()) {
|
||||
case AT_LPAREN:
|
||||
return "open parenthesis '('";
|
||||
case AT_RPAREN:
|
||||
return "close parenthesis ')'";
|
||||
case AT_COMP_LT:
|
||||
return "less than '<'";
|
||||
case AT_COMP_LTE:
|
||||
return "less than or equal '<='";
|
||||
case AT_COMP_GT:
|
||||
return "greater than '>'";
|
||||
case AT_COMP_GTE:
|
||||
return "greater than or equal '>='";
|
||||
case AT_COMP_EQ:
|
||||
return "equal '=='";
|
||||
case AT_NEGATE:
|
||||
return "negate '!='";
|
||||
case AT_BOOL_NOT:
|
||||
return "boolean not '!'";
|
||||
case AT_BOOL_AND:
|
||||
return "boolean and '&&'";
|
||||
case AT_BOOL_OR:
|
||||
return "boolean or '||'";
|
||||
case AT_COND_QUE:
|
||||
return "conditional '?'";
|
||||
case AT_ADD:
|
||||
return "addition '+'";
|
||||
case AT_SUBTRACT:
|
||||
return "subtraction '-'";
|
||||
case AT_MULTIPLY:
|
||||
return "multiplication '*'";
|
||||
case AT_DIVIDE:
|
||||
return "division '/'";
|
||||
case AT_MODULO:
|
||||
return "modulo '\%'";
|
||||
case AT_BIT_SHL:
|
||||
return "bit shift left '<<'";
|
||||
case AT_BIT_SHR:
|
||||
return "bit shift right '>>'";
|
||||
case AT_BIT_SHU:
|
||||
return "unsigned bit shift right '>>>'";
|
||||
case AT_BIT_AND:
|
||||
return "bitwise and '&'";
|
||||
case AT_BIT_OR:
|
||||
return "bitwise or '|'";
|
||||
case AT_BIT_XOR:
|
||||
return "bitwise xor '^'";
|
||||
case AT_BIT_NOT:
|
||||
return "bitwise not '~'";
|
||||
case ID:
|
||||
return "identifier '" + token.getText() + "'";
|
||||
case DECIMAL:
|
||||
return "decimal '" + token.getText() + "'";
|
||||
case OCTAL:
|
||||
return "octal '" + token.getText() + "'";
|
||||
case HEX:
|
||||
return "hex '" + token.getText() + "'";
|
||||
case EOF:
|
||||
return "end of expression";
|
||||
default:
|
||||
return "'" + token.getText() + "'";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ***********************************************************************
|
||||
// * Parser Rules
|
||||
// ***********************************************************************
|
||||
|
||||
expression
|
||||
: conditional EOF!
|
||||
;
|
||||
|
||||
conditional
|
||||
: logical_or (AT_COND_QUE^ conditional AT_COLON! conditional)?
|
||||
;
|
||||
|
||||
logical_or
|
||||
: logical_and (AT_BOOL_OR^ logical_and)*
|
||||
;
|
||||
|
||||
logical_and
|
||||
: bitwise_or (AT_BOOL_AND^ bitwise_or)*
|
||||
;
|
||||
|
||||
bitwise_or
|
||||
: bitwise_xor (AT_BIT_OR^ bitwise_xor)*
|
||||
;
|
||||
|
||||
bitwise_xor
|
||||
: bitwise_and (AT_BIT_XOR^ bitwise_and)*
|
||||
;
|
||||
|
||||
bitwise_and
|
||||
: equality (AT_BIT_AND^ equality)*
|
||||
;
|
||||
|
||||
equality
|
||||
: relational ((AT_COMP_EQ | AT_COMP_NEQ)^ relational)*
|
||||
;
|
||||
|
||||
relational
|
||||
: shift ((AT_COMP_LT | AT_COMP_GT | AT_COMP_LTE | AT_COMP_GTE)^ shift)*
|
||||
;
|
||||
|
||||
shift
|
||||
: additive ((AT_BIT_SHL | AT_BIT_SHR | AT_BIT_SHU)^ additive)*
|
||||
;
|
||||
|
||||
additive
|
||||
: multiplicative ((AT_ADD | AT_SUBTRACT)^ multiplicative)*
|
||||
;
|
||||
|
||||
multiplicative
|
||||
: unary ((AT_MULTIPLY | AT_DIVIDE | AT_MODULO)^ unary)*
|
||||
;
|
||||
|
||||
unary
|
||||
: postfix
|
||||
| AT_ADD! unary
|
||||
| unary_operator^ unary
|
||||
;
|
||||
|
||||
unary_operator
|
||||
: AT_SUBTRACT -> AT_NEGATE
|
||||
| AT_BIT_NOT
|
||||
| AT_BOOL_NOT
|
||||
;
|
||||
|
||||
postfix
|
||||
: primary
|
||||
| ID arguments -> ^(AT_CALL ID arguments?)
|
||||
;
|
||||
|
||||
primary
|
||||
: ID
|
||||
| numeric
|
||||
| AT_LPAREN! conditional AT_RPAREN!
|
||||
;
|
||||
|
||||
arguments
|
||||
: AT_LPAREN! (conditional (AT_COMMA! conditional)*)? AT_RPAREN!
|
||||
;
|
||||
|
||||
numeric
|
||||
: HEX | OCTAL | DECIMAL
|
||||
;
|
||||
|
||||
// ***********************************************************************
|
||||
// * Lexer Rules
|
||||
// ***********************************************************************
|
||||
|
||||
ID
|
||||
: ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*
|
||||
;
|
||||
|
||||
WS
|
||||
: (' '|'\t'|'\n'|'\r')+ {skip();}
|
||||
;
|
||||
|
||||
DECIMAL
|
||||
: DECIMALINTEGER AT_DOT DECIMALDIGIT* EXPONENT?
|
||||
| AT_DOT DECIMALDIGIT+ EXPONENT?
|
||||
| DECIMALINTEGER EXPONENT?
|
||||
;
|
||||
|
||||
OCTAL
|
||||
: '0' OCTALDIGIT+
|
||||
;
|
||||
|
||||
HEX
|
||||
: ('0x'|'0X') HEXDIGIT+
|
||||
;
|
||||
|
||||
fragment
|
||||
DECIMALINTEGER
|
||||
: '0'
|
||||
| '1'..'9' DECIMALDIGIT*
|
||||
;
|
||||
|
||||
fragment
|
||||
EXPONENT
|
||||
: ('e'|'E') ('+'|'-')? DECIMALDIGIT+
|
||||
;
|
||||
|
||||
fragment
|
||||
DECIMALDIGIT
|
||||
: '0'..'9'
|
||||
;
|
||||
|
||||
fragment
|
||||
HEXDIGIT
|
||||
: DECIMALDIGIT
|
||||
| 'a'..'f'
|
||||
| 'A'..'F'
|
||||
;
|
||||
|
||||
fragment
|
||||
OCTALDIGIT
|
||||
: '0'..'7'
|
||||
;
|
|
@ -0,0 +1,66 @@
|
|||
AT_ADD=4
|
||||
AT_BIT_AND=5
|
||||
AT_BIT_NOT=6
|
||||
AT_BIT_OR=7
|
||||
AT_BIT_SHL=8
|
||||
AT_BIT_SHR=9
|
||||
AT_BIT_SHU=10
|
||||
AT_BIT_XOR=11
|
||||
AT_BOOL_AND=12
|
||||
AT_BOOL_NOT=13
|
||||
AT_BOOL_OR=14
|
||||
AT_CALL=15
|
||||
AT_COLON=16
|
||||
AT_COMMA=17
|
||||
AT_COMP_EQ=18
|
||||
AT_COMP_GT=19
|
||||
AT_COMP_GTE=20
|
||||
AT_COMP_LT=21
|
||||
AT_COMP_LTE=22
|
||||
AT_COMP_NEQ=23
|
||||
AT_COND_QUE=24
|
||||
AT_DIVIDE=25
|
||||
AT_DOT=26
|
||||
AT_LPAREN=27
|
||||
AT_MODULO=28
|
||||
AT_MULTIPLY=29
|
||||
AT_NEGATE=30
|
||||
AT_RPAREN=31
|
||||
AT_SUBTRACT=32
|
||||
DECIMAL=33
|
||||
DECIMALDIGIT=34
|
||||
DECIMALINTEGER=35
|
||||
EXPONENT=36
|
||||
HEX=37
|
||||
HEXDIGIT=38
|
||||
ID=39
|
||||
OCTAL=40
|
||||
OCTALDIGIT=41
|
||||
WS=42
|
||||
'!'=13
|
||||
'!='=23
|
||||
'%'=28
|
||||
'&&'=12
|
||||
'&'=5
|
||||
'('=27
|
||||
')'=31
|
||||
'*'=29
|
||||
'+'=4
|
||||
','=17
|
||||
'-'=32
|
||||
'.'=26
|
||||
'/'=25
|
||||
':'=16
|
||||
'<'=21
|
||||
'<<'=8
|
||||
'<='=22
|
||||
'=='=18
|
||||
'>'=19
|
||||
'>='=20
|
||||
'>>'=9
|
||||
'>>>'=10
|
||||
'?'=24
|
||||
'^'=11
|
||||
'|'=7
|
||||
'||'=14
|
||||
'~'=6
|
|
@ -0,0 +1,564 @@
|
|||
package org.apache.lucene.expressions.js;
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.text.ParseException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.antlr.runtime.ANTLRStringStream;
|
||||
import org.antlr.runtime.CharStream;
|
||||
import org.antlr.runtime.CommonTokenStream;
|
||||
import org.antlr.runtime.RecognitionException;
|
||||
import org.antlr.runtime.tree.Tree;
|
||||
import org.apache.lucene.expressions.Expression;
|
||||
import org.apache.lucene.queries.function.FunctionValues;
|
||||
import org.apache.lucene.util.IOUtils;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.commons.GeneratorAdapter;
|
||||
|
||||
/**
|
||||
* An expression compiler for javascript expressions.
|
||||
* <p>
|
||||
* Example:
|
||||
* <pre class="prettyprint">
|
||||
* Expression foo = JavascriptCompiler.compile("((0.3*popularity)/10.0)+(0.7*score)");
|
||||
* </pre>
|
||||
* <p>
|
||||
* See the {@link org.apache.lucene.expressions.js package documentation} for
|
||||
* the supported syntax and default functions.
|
||||
* <p>
|
||||
* You can compile with an alternate set of functions via {@link #compile(String, Map, ClassLoader)}.
|
||||
* For example:
|
||||
* <pre class="prettyprint">
|
||||
* Map<String,Method> functions = new HashMap<>();
|
||||
* // add all the default functions
|
||||
* functions.putAll(JavascriptCompiler.DEFAULT_FUNCTIONS);
|
||||
* // add cbrt()
|
||||
* functions.put("cbrt", Math.class.getMethod("cbrt", double.class));
|
||||
* // call compile with customized function map
|
||||
* Expression foo = JavascriptCompiler.compile("cbrt(score)+ln(popularity)",
|
||||
* functions,
|
||||
* getClass().getClassLoader());
|
||||
* </pre>
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
public class JavascriptCompiler {
|
||||
|
||||
static class Loader extends ClassLoader {
|
||||
Loader(ClassLoader parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
public Class<? extends Expression> define(String className, byte[] bytecode) {
|
||||
return defineClass(className, bytecode, 0, bytecode.length).asSubclass(Expression.class);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int CLASSFILE_VERSION = Opcodes.V1_7;
|
||||
|
||||
// We use the same class name for all generated classes as they all have their own class loader.
|
||||
// The source code is displayed as "source file name" in stack trace.
|
||||
private static final String COMPILED_EXPRESSION_CLASS = JavascriptCompiler.class.getName() + "$CompiledExpression";
|
||||
private static final String COMPILED_EXPRESSION_INTERNAL = COMPILED_EXPRESSION_CLASS.replace('.', '/');
|
||||
|
||||
private static final Type EXPRESSION_TYPE = Type.getType(Expression.class);
|
||||
private static final Type FUNCTION_VALUES_TYPE = Type.getType(FunctionValues.class);
|
||||
|
||||
private static final org.objectweb.asm.commons.Method
|
||||
EXPRESSION_CTOR = getMethod("void <init>(String, String[])"),
|
||||
EVALUATE_METHOD = getMethod("double evaluate(int, " + FunctionValues.class.getName() + "[])"),
|
||||
DOUBLE_VAL_METHOD = getMethod("double doubleVal(int)");
|
||||
|
||||
// to work around import clash:
|
||||
private static org.objectweb.asm.commons.Method getMethod(String method) {
|
||||
return org.objectweb.asm.commons.Method.getMethod(method);
|
||||
}
|
||||
|
||||
// This maximum length is theoretically 65535 bytes, but as its CESU-8 encoded we dont know how large it is in bytes, so be safe
|
||||
// rcmuir: "If your ranking function is that large you need to check yourself into a mental institution!"
|
||||
private static final int MAX_SOURCE_LENGTH = 16384;
|
||||
|
||||
private final String sourceText;
|
||||
private final Map<String, Integer> externalsMap = new LinkedHashMap<String, Integer>();
|
||||
private final ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
|
||||
private GeneratorAdapter gen;
|
||||
|
||||
private final Map<String,Method> functions;
|
||||
|
||||
/**
|
||||
* Compiles the given expression.
|
||||
*
|
||||
* @param sourceText The expression to compile
|
||||
* @return A new compiled expression
|
||||
* @throws ParseException on failure to compile
|
||||
*/
|
||||
public static Expression compile(String sourceText) throws ParseException {
|
||||
return new JavascriptCompiler(sourceText).compileExpression(JavascriptCompiler.class.getClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the given expression with the supplied custom functions.
|
||||
* <p>
|
||||
* Functions must be {@code public static}, return {@code double} and
|
||||
* can take from zero to 256 {@code double} parameters.
|
||||
*
|
||||
* @param sourceText The expression to compile
|
||||
* @param functions map of String names to functions
|
||||
* @param parent a {@code ClassLoader} that should be used as the parent of the loaded class.
|
||||
* It must contain all classes referred to by the given {@code functions}.
|
||||
* @return A new compiled expression
|
||||
* @throws ParseException on failure to compile
|
||||
*/
|
||||
public static Expression compile(String sourceText, Map<String,Method> functions, ClassLoader parent) throws ParseException {
|
||||
if (parent == null) {
|
||||
throw new NullPointerException("A parent ClassLoader must be given.");
|
||||
}
|
||||
for (Method m : functions.values()) {
|
||||
checkFunction(m, parent);
|
||||
}
|
||||
return new JavascriptCompiler(sourceText, functions).compileExpression(parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is unused, it is just here to make sure that the function signatures don't change.
|
||||
* If this method fails to compile, you also have to change the byte code generator to correctly
|
||||
* use the FunctionValues class.
|
||||
*/
|
||||
@SuppressWarnings({"unused", "null"})
|
||||
private static void unusedTestCompile() {
|
||||
FunctionValues f = null;
|
||||
double ret = f.doubleVal(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a compiler for expressions.
|
||||
* @param sourceText The expression to compile
|
||||
*/
|
||||
private JavascriptCompiler(String sourceText) {
|
||||
this(sourceText, DEFAULT_FUNCTIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a compiler for expressions with specific set of functions
|
||||
* @param sourceText The expression to compile
|
||||
*/
|
||||
private JavascriptCompiler(String sourceText, Map<String,Method> functions) {
|
||||
if (sourceText == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
this.sourceText = sourceText;
|
||||
this.functions = functions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the given expression with the specified parent classloader
|
||||
*
|
||||
* @return A new compiled expression
|
||||
* @throws ParseException on failure to compile
|
||||
*/
|
||||
private Expression compileExpression(ClassLoader parent) throws ParseException {
|
||||
try {
|
||||
Tree antlrTree = getAntlrComputedExpressionTree();
|
||||
|
||||
beginCompile();
|
||||
recursiveCompile(antlrTree, Type.DOUBLE_TYPE);
|
||||
endCompile();
|
||||
|
||||
Class<? extends Expression> evaluatorClass = new Loader(parent)
|
||||
.define(COMPILED_EXPRESSION_CLASS, classWriter.toByteArray());
|
||||
Constructor<? extends Expression> constructor = evaluatorClass.getConstructor(String.class, String[].class);
|
||||
return constructor.newInstance(sourceText, externalsMap.keySet().toArray(new String[externalsMap.size()]));
|
||||
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException exception) {
|
||||
throw new IllegalStateException("An internal error occurred attempting to compile the expression (" + sourceText + ").", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void beginCompile() {
|
||||
classWriter.visit(CLASSFILE_VERSION,
|
||||
Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC,
|
||||
COMPILED_EXPRESSION_INTERNAL,
|
||||
null, EXPRESSION_TYPE.getInternalName(), null);
|
||||
String clippedSourceText = (sourceText.length() <= MAX_SOURCE_LENGTH) ?
|
||||
sourceText : (sourceText.substring(0, MAX_SOURCE_LENGTH - 3) + "...");
|
||||
classWriter.visitSource(clippedSourceText, null);
|
||||
|
||||
GeneratorAdapter constructor = new GeneratorAdapter(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC,
|
||||
EXPRESSION_CTOR, null, null, classWriter);
|
||||
constructor.loadThis();
|
||||
constructor.loadArgs();
|
||||
constructor.invokeConstructor(EXPRESSION_TYPE, EXPRESSION_CTOR);
|
||||
constructor.returnValue();
|
||||
constructor.endMethod();
|
||||
|
||||
gen = new GeneratorAdapter(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC,
|
||||
EVALUATE_METHOD, null, null, classWriter);
|
||||
}
|
||||
|
||||
private void recursiveCompile(Tree current, Type expected) {
|
||||
int type = current.getType();
|
||||
String text = current.getText();
|
||||
|
||||
switch (type) {
|
||||
case JavascriptParser.AT_CALL:
|
||||
Tree identifier = current.getChild(0);
|
||||
String call = identifier.getText();
|
||||
int arguments = current.getChildCount() - 1;
|
||||
|
||||
Method method = functions.get(call);
|
||||
if (method == null) {
|
||||
throw new IllegalArgumentException("Unrecognized method call (" + call + ").");
|
||||
}
|
||||
|
||||
int arity = method.getParameterTypes().length;
|
||||
if (arguments != arity) {
|
||||
throw new IllegalArgumentException("Expected (" + arity + ") arguments for method call (" +
|
||||
call + "), but found (" + arguments + ").");
|
||||
}
|
||||
|
||||
for (int argument = 1; argument <= arguments; ++argument) {
|
||||
recursiveCompile(current.getChild(argument), Type.DOUBLE_TYPE);
|
||||
}
|
||||
|
||||
gen.invokeStatic(Type.getType(method.getDeclaringClass()),
|
||||
org.objectweb.asm.commons.Method.getMethod(method));
|
||||
|
||||
gen.cast(Type.DOUBLE_TYPE, expected);
|
||||
break;
|
||||
case JavascriptParser.ID:
|
||||
int index;
|
||||
|
||||
if (externalsMap.containsKey(text)) {
|
||||
index = externalsMap.get(text);
|
||||
} else {
|
||||
index = externalsMap.size();
|
||||
externalsMap.put(text, index);
|
||||
}
|
||||
|
||||
gen.loadArg(1);
|
||||
gen.push(index);
|
||||
gen.arrayLoad(FUNCTION_VALUES_TYPE);
|
||||
gen.loadArg(0);
|
||||
gen.invokeVirtual(FUNCTION_VALUES_TYPE, DOUBLE_VAL_METHOD);
|
||||
gen.cast(Type.DOUBLE_TYPE, expected);
|
||||
break;
|
||||
case JavascriptParser.HEX:
|
||||
pushLong(expected, Long.parseLong(text.substring(2), 16));
|
||||
break;
|
||||
case JavascriptParser.OCTAL:
|
||||
pushLong(expected, Long.parseLong(text.substring(1), 8));
|
||||
break;
|
||||
case JavascriptParser.DECIMAL:
|
||||
gen.push(Double.parseDouble(text));
|
||||
gen.cast(Type.DOUBLE_TYPE, expected);
|
||||
break;
|
||||
case JavascriptParser.AT_NEGATE:
|
||||
recursiveCompile(current.getChild(0), Type.DOUBLE_TYPE);
|
||||
gen.visitInsn(Opcodes.DNEG);
|
||||
gen.cast(Type.DOUBLE_TYPE, expected);
|
||||
break;
|
||||
case JavascriptParser.AT_ADD:
|
||||
pushArith(Opcodes.DADD, current, expected);
|
||||
break;
|
||||
case JavascriptParser.AT_SUBTRACT:
|
||||
pushArith(Opcodes.DSUB, current, expected);
|
||||
break;
|
||||
case JavascriptParser.AT_MULTIPLY:
|
||||
pushArith(Opcodes.DMUL, current, expected);
|
||||
break;
|
||||
case JavascriptParser.AT_DIVIDE:
|
||||
pushArith(Opcodes.DDIV, current, expected);
|
||||
break;
|
||||
case JavascriptParser.AT_MODULO:
|
||||
pushArith(Opcodes.DREM, current, expected);
|
||||
break;
|
||||
case JavascriptParser.AT_BIT_SHL:
|
||||
pushShift(Opcodes.LSHL, current, expected);
|
||||
break;
|
||||
case JavascriptParser.AT_BIT_SHR:
|
||||
pushShift(Opcodes.LSHR, current, expected);
|
||||
break;
|
||||
case JavascriptParser.AT_BIT_SHU:
|
||||
pushShift(Opcodes.LUSHR, current, expected);
|
||||
break;
|
||||
case JavascriptParser.AT_BIT_AND:
|
||||
pushBitwise(Opcodes.LAND, current, expected);
|
||||
break;
|
||||
case JavascriptParser.AT_BIT_OR:
|
||||
pushBitwise(Opcodes.LOR, current, expected);
|
||||
break;
|
||||
case JavascriptParser.AT_BIT_XOR:
|
||||
pushBitwise(Opcodes.LXOR, current, expected);
|
||||
break;
|
||||
case JavascriptParser.AT_BIT_NOT:
|
||||
recursiveCompile(current.getChild(0), Type.LONG_TYPE);
|
||||
gen.push(-1L);
|
||||
gen.visitInsn(Opcodes.LXOR);
|
||||
gen.cast(Type.LONG_TYPE, expected);
|
||||
break;
|
||||
case JavascriptParser.AT_COMP_EQ:
|
||||
pushCond(GeneratorAdapter.EQ, current, expected);
|
||||
break;
|
||||
case JavascriptParser.AT_COMP_NEQ:
|
||||
pushCond(GeneratorAdapter.NE, current, expected);
|
||||
break;
|
||||
case JavascriptParser.AT_COMP_LT:
|
||||
pushCond(GeneratorAdapter.LT, current, expected);
|
||||
break;
|
||||
case JavascriptParser.AT_COMP_GT:
|
||||
pushCond(GeneratorAdapter.GT, current, expected);
|
||||
break;
|
||||
case JavascriptParser.AT_COMP_LTE:
|
||||
pushCond(GeneratorAdapter.LE, current, expected);
|
||||
break;
|
||||
case JavascriptParser.AT_COMP_GTE:
|
||||
pushCond(GeneratorAdapter.GE, current, expected);
|
||||
break;
|
||||
case JavascriptParser.AT_BOOL_NOT:
|
||||
Label labelNotTrue = new Label();
|
||||
Label labelNotReturn = new Label();
|
||||
|
||||
recursiveCompile(current.getChild(0), Type.INT_TYPE);
|
||||
gen.visitJumpInsn(Opcodes.IFEQ, labelNotTrue);
|
||||
pushBoolean(expected, false);
|
||||
gen.goTo(labelNotReturn);
|
||||
gen.visitLabel(labelNotTrue);
|
||||
pushBoolean(expected, true);
|
||||
gen.visitLabel(labelNotReturn);
|
||||
break;
|
||||
case JavascriptParser.AT_BOOL_AND:
|
||||
Label andFalse = new Label();
|
||||
Label andEnd = new Label();
|
||||
|
||||
recursiveCompile(current.getChild(0), Type.INT_TYPE);
|
||||
gen.visitJumpInsn(Opcodes.IFEQ, andFalse);
|
||||
recursiveCompile(current.getChild(1), Type.INT_TYPE);
|
||||
gen.visitJumpInsn(Opcodes.IFEQ, andFalse);
|
||||
pushBoolean(expected, true);
|
||||
gen.goTo(andEnd);
|
||||
gen.visitLabel(andFalse);
|
||||
pushBoolean(expected, false);
|
||||
gen.visitLabel(andEnd);
|
||||
break;
|
||||
case JavascriptParser.AT_BOOL_OR:
|
||||
Label orTrue = new Label();
|
||||
Label orEnd = new Label();
|
||||
|
||||
recursiveCompile(current.getChild(0), Type.INT_TYPE);
|
||||
gen.visitJumpInsn(Opcodes.IFNE, orTrue);
|
||||
recursiveCompile(current.getChild(1), Type.INT_TYPE);
|
||||
gen.visitJumpInsn(Opcodes.IFNE, orTrue);
|
||||
pushBoolean(expected, false);
|
||||
gen.goTo(orEnd);
|
||||
gen.visitLabel(orTrue);
|
||||
pushBoolean(expected, true);
|
||||
gen.visitLabel(orEnd);
|
||||
break;
|
||||
case JavascriptParser.AT_COND_QUE:
|
||||
Label condFalse = new Label();
|
||||
Label condEnd = new Label();
|
||||
|
||||
recursiveCompile(current.getChild(0), Type.INT_TYPE);
|
||||
gen.visitJumpInsn(Opcodes.IFEQ, condFalse);
|
||||
recursiveCompile(current.getChild(1), expected);
|
||||
gen.goTo(condEnd);
|
||||
gen.visitLabel(condFalse);
|
||||
recursiveCompile(current.getChild(2), expected);
|
||||
gen.visitLabel(condEnd);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown operation specified: (" + current.getText() + ").");
|
||||
}
|
||||
}
|
||||
|
||||
private void pushArith(int operator, Tree current, Type expected) {
|
||||
pushBinaryOp(operator, current, expected, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE);
|
||||
}
|
||||
|
||||
private void pushShift(int operator, Tree current, Type expected) {
|
||||
pushBinaryOp(operator, current, expected, Type.LONG_TYPE, Type.INT_TYPE, Type.LONG_TYPE);
|
||||
}
|
||||
|
||||
private void pushBitwise(int operator, Tree current, Type expected) {
|
||||
pushBinaryOp(operator, current, expected, Type.LONG_TYPE, Type.LONG_TYPE, Type.LONG_TYPE);
|
||||
}
|
||||
|
||||
private void pushBinaryOp(int operator, Tree current, Type expected, Type arg1, Type arg2, Type returnType) {
|
||||
recursiveCompile(current.getChild(0), arg1);
|
||||
recursiveCompile(current.getChild(1), arg2);
|
||||
gen.visitInsn(operator);
|
||||
gen.cast(returnType, expected);
|
||||
}
|
||||
|
||||
private void pushCond(int operator, Tree current, Type expected) {
|
||||
Label labelTrue = new Label();
|
||||
Label labelReturn = new Label();
|
||||
|
||||
recursiveCompile(current.getChild(0), Type.DOUBLE_TYPE);
|
||||
recursiveCompile(current.getChild(1), Type.DOUBLE_TYPE);
|
||||
|
||||
gen.ifCmp(Type.DOUBLE_TYPE, operator, labelTrue);
|
||||
pushBoolean(expected, false);
|
||||
gen.goTo(labelReturn);
|
||||
gen.visitLabel(labelTrue);
|
||||
pushBoolean(expected, true);
|
||||
gen.visitLabel(labelReturn);
|
||||
}
|
||||
|
||||
private void pushBoolean(Type expected, boolean truth) {
|
||||
switch (expected.getSort()) {
|
||||
case Type.INT:
|
||||
gen.push(truth);
|
||||
break;
|
||||
case Type.LONG:
|
||||
gen.push(truth ? 1L : 0L);
|
||||
break;
|
||||
case Type.DOUBLE:
|
||||
gen.push(truth ? 1. : 0.);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Invalid expected type: " + expected);
|
||||
}
|
||||
}
|
||||
|
||||
private void pushLong(Type expected, long i) {
|
||||
switch (expected.getSort()) {
|
||||
case Type.INT:
|
||||
gen.push((int) i);
|
||||
break;
|
||||
case Type.LONG:
|
||||
gen.push(i);
|
||||
break;
|
||||
case Type.DOUBLE:
|
||||
gen.push((double) i);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Invalid expected type: " + expected);
|
||||
}
|
||||
}
|
||||
|
||||
private void endCompile() {
|
||||
gen.returnValue();
|
||||
gen.endMethod();
|
||||
|
||||
classWriter.visitEnd();
|
||||
}
|
||||
|
||||
private Tree getAntlrComputedExpressionTree() throws ParseException {
|
||||
CharStream input = new ANTLRStringStream(sourceText);
|
||||
JavascriptLexer lexer = new JavascriptLexer(input);
|
||||
CommonTokenStream tokens = new CommonTokenStream(lexer);
|
||||
JavascriptParser parser = new JavascriptParser(tokens);
|
||||
|
||||
try {
|
||||
return parser.expression().tree;
|
||||
|
||||
} catch (RecognitionException exception) {
|
||||
throw new IllegalArgumentException(exception);
|
||||
} catch (RuntimeException exception) {
|
||||
if (exception.getCause() instanceof ParseException) {
|
||||
throw (ParseException)exception.getCause();
|
||||
}
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The default set of functions available to expressions.
|
||||
* <p>
|
||||
* See the {@link org.apache.lucene.expressions.js package documentation}
|
||||
* for a list.
|
||||
*/
|
||||
public static final Map<String,Method> DEFAULT_FUNCTIONS;
|
||||
static {
|
||||
Map<String,Method> map = new HashMap<String,Method>();
|
||||
try {
|
||||
final Properties props = new Properties();
|
||||
try (Reader in = IOUtils.getDecodingReader(JavascriptCompiler.class,
|
||||
JavascriptCompiler.class.getSimpleName() + ".properties", IOUtils.CHARSET_UTF_8)) {
|
||||
props.load(in);
|
||||
}
|
||||
for (final String call : props.stringPropertyNames()) {
|
||||
final String[] vals = props.getProperty(call).split(",");
|
||||
if (vals.length != 3) {
|
||||
throw new Error("Syntax error while reading Javascript functions from resource");
|
||||
}
|
||||
final Class<?> clazz = Class.forName(vals[0].trim());
|
||||
final String methodName = vals[1].trim();
|
||||
final int arity = Integer.parseInt(vals[2].trim());
|
||||
@SuppressWarnings({"rawtypes", "unchecked"}) Class[] args = new Class[arity];
|
||||
Arrays.fill(args, double.class);
|
||||
Method method = clazz.getMethod(methodName, args);
|
||||
checkFunction(method, JavascriptCompiler.class.getClassLoader());
|
||||
map.put(call, method);
|
||||
}
|
||||
} catch (NoSuchMethodException | ClassNotFoundException | IOException e) {
|
||||
throw new Error("Cannot resolve function", e);
|
||||
}
|
||||
DEFAULT_FUNCTIONS = Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
private static void checkFunction(Method method, ClassLoader parent) {
|
||||
// We can only call the function if the given parent class loader of our compiled class has access to the method:
|
||||
final ClassLoader functionClassloader = method.getDeclaringClass().getClassLoader();
|
||||
if (functionClassloader != null) { // it is a system class iff null!
|
||||
boolean found = false;
|
||||
while (parent != null) {
|
||||
if (parent == functionClassloader) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
parent = parent.getParent();
|
||||
}
|
||||
if (!found) {
|
||||
throw new IllegalArgumentException(method + " is not declared by a class which is accessible by the given parent ClassLoader.");
|
||||
}
|
||||
}
|
||||
// do some checks if the signature is "compatible":
|
||||
if (!Modifier.isStatic(method.getModifiers())) {
|
||||
throw new IllegalArgumentException(method + " is not static.");
|
||||
}
|
||||
if (!Modifier.isPublic(method.getModifiers())) {
|
||||
throw new IllegalArgumentException(method + " is not public.");
|
||||
}
|
||||
if (!Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
|
||||
throw new IllegalArgumentException(method.getDeclaringClass().getName() + " is not public.");
|
||||
}
|
||||
for (Class<?> clazz : method.getParameterTypes()) {
|
||||
if (!clazz.equals(double.class)) {
|
||||
throw new IllegalArgumentException(method + " must take only double parameters");
|
||||
}
|
||||
}
|
||||
if (method.getReturnType() != double.class) {
|
||||
throw new IllegalArgumentException(method + " does not return a double.");
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,44 @@
|
|||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>Javascript expressions</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Javascript expressions</h1>
|
||||
<p>A Javascript expression is a numeric expression specified using an expression syntax that's based on JavaScript expressions. You can construct expressions using:</p>
|
||||
<ul>
|
||||
<li>Integer, floating point, hex and octal literals</li>
|
||||
<li>Arithmetic operators: <code>+ - * / %</code></li>
|
||||
<li>Bitwise operators: <code>| & ^ ~ << >> >>></code></li>
|
||||
<li>Boolean operators (including the ternary operator): <code>&& || ! ?:</code></li>
|
||||
<li>Comparison operators: <code>< <= == >= ></code></li>
|
||||
<li>Common mathematic functions: <code>abs ceil exp floor ln log2 log10 logn max min sqrt pow</code></li>
|
||||
<li>Trigonometric library functions: <code>acosh acos asinh asin atanh atan atan2 cosh cos sinh sin tanh tan</code></li>
|
||||
<li>Miscellaneous functions: <code>min, max</code></li>
|
||||
<li>Arbitrary external variables - see {@link org.apache.lucene.expressions.Bindings}</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
JavaScript order of precedence rules apply for operators. Shortcut evaluation is used for logical operators—the second argument is only evaluated if the value of the expression cannot be determined after evaluating the first argument. For example, in the expression <code>a || b</code>, <code>b</code> is only evaluated if a is not true.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To compile an expression, use {@link org.apache.lucene.expressions.js.JavascriptCompiler}.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,39 @@
|
|||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>expressions</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>expressions</h1>
|
||||
<p>
|
||||
{@link org.apache.lucene.expressions.Expression} - result of compiling an expression, which can
|
||||
evaluate it for a given document. Each expression can have external variables are resolved by
|
||||
{@code Bindings}.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{@link org.apache.lucene.expressions.Bindings} - abstraction for binding external variables
|
||||
to a way to get a value for those variables for a particular document (ValueSource).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{@link org.apache.lucene.expressions.SimpleBindings} - default implementation of bindings which provide easy ways to bind sort fields and other expressions to external variables
|
||||
</p>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,41 @@
|
|||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>Apache Lucene Expressions Module</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>The Expressions Module for Apache Lucene</h1>
|
||||
|
||||
<p>
|
||||
The expressions module is new to Lucene 4.6. It provides an API for dynamically computing per-document values based on string expressions.
|
||||
</p>
|
||||
<p>
|
||||
The module is organized in two sections:
|
||||
<ol>
|
||||
<li>{@link org.apache.lucene.expressions} - The abstractions and simple utilities for common operations like sorting on an expression</li>
|
||||
<li>{@link org.apache.lucene.expressions.js} - A compiler for a subset of JavaScript expressions</li>
|
||||
</ol>
|
||||
</p>
|
||||
<p>
|
||||
For sample code showing how to use the API, see {@link org.apache.lucene.expressions.Expression}.
|
||||
</p>
|
||||
<p>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,44 @@
|
|||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
# This properties file contains all Javascript functions as keys.
|
||||
# The values are the implementing class, the static method name, and
|
||||
# the number of double parameters.
|
||||
|
||||
abs = java.lang.Math, abs, 1
|
||||
acos = java.lang.Math, acos, 1
|
||||
acosh = org.apache.lucene.util.MathUtil, acosh, 1
|
||||
asin = java.lang.Math, asin, 1
|
||||
asinh = org.apache.lucene.util.MathUtil, asinh, 1
|
||||
atan = java.lang.Math, atan, 1
|
||||
atan2 = java.lang.Math, atan2, 2
|
||||
atanh = org.apache.lucene.util.MathUtil, atanh, 1
|
||||
ceil = java.lang.Math, ceil, 1
|
||||
cos = java.lang.Math, cos, 1
|
||||
cosh = java.lang.Math, cosh, 1
|
||||
exp = java.lang.Math, exp, 1
|
||||
floor = java.lang.Math, floor, 1
|
||||
ln = java.lang.Math, log, 1
|
||||
log10 = java.lang.Math, log10, 1
|
||||
logn = org.apache.lucene.util.MathUtil, log, 2
|
||||
max = java.lang.Math, max, 2
|
||||
min = java.lang.Math, min, 2
|
||||
pow = java.lang.Math, pow, 2
|
||||
sin = java.lang.Math, sin, 1
|
||||
sinh = java.lang.Math, sinh, 1
|
||||
sqrt = java.lang.Math, sqrt, 1
|
||||
tan = java.lang.Math, tan, 1
|
||||
tanh = java.lang.Math, tanh, 1
|
|
@ -0,0 +1,183 @@
|
|||
package org.apache.lucene.expressions;
|
||||
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.NumericDocValuesField;
|
||||
import org.apache.lucene.expressions.js.JavascriptCompiler;
|
||||
import org.apache.lucene.index.DirectoryReader;
|
||||
import org.apache.lucene.index.RandomIndexWriter;
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.apache.lucene.search.CheckHits;
|
||||
import org.apache.lucene.search.FieldDoc;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.search.Sort;
|
||||
import org.apache.lucene.search.SortField;
|
||||
import org.apache.lucene.search.TermQuery;
|
||||
import org.apache.lucene.search.TopFieldDocs;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/** simple demo of using expressions */
|
||||
public class TestDemoExpressions extends LuceneTestCase {
|
||||
IndexSearcher searcher;
|
||||
DirectoryReader reader;
|
||||
Directory dir;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
dir = newDirectory();
|
||||
RandomIndexWriter iw = new RandomIndexWriter(random(), dir);
|
||||
|
||||
Document doc = new Document();
|
||||
doc.add(newStringField("id", "1", Field.Store.YES));
|
||||
doc.add(newTextField("body", "some contents and more contents", Field.Store.NO));
|
||||
doc.add(new NumericDocValuesField("popularity", 5));
|
||||
iw.addDocument(doc);
|
||||
|
||||
doc = new Document();
|
||||
doc.add(newStringField("id", "2", Field.Store.YES));
|
||||
doc.add(newTextField("body", "another document with different contents", Field.Store.NO));
|
||||
doc.add(new NumericDocValuesField("popularity", 20));
|
||||
iw.addDocument(doc);
|
||||
|
||||
doc = new Document();
|
||||
doc.add(newStringField("id", "3", Field.Store.YES));
|
||||
doc.add(newTextField("body", "crappy contents", Field.Store.NO));
|
||||
doc.add(new NumericDocValuesField("popularity", 2));
|
||||
iw.addDocument(doc);
|
||||
|
||||
reader = iw.getReader();
|
||||
searcher = new IndexSearcher(reader);
|
||||
iw.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() throws Exception {
|
||||
reader.close();
|
||||
dir.close();
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
/** an example of how to rank by an expression */
|
||||
public void test() throws Exception {
|
||||
// compile an expression:
|
||||
Expression expr = JavascriptCompiler.compile("sqrt(_score) + ln(popularity)");
|
||||
|
||||
// we use SimpleBindings: which just maps variables to SortField instances
|
||||
SimpleBindings bindings = new SimpleBindings();
|
||||
bindings.add(new SortField("_score", SortField.Type.SCORE));
|
||||
bindings.add(new SortField("popularity", SortField.Type.INT));
|
||||
|
||||
// create a sort field and sort by it (reverse order)
|
||||
Sort sort = new Sort(expr.getSortField(bindings, true));
|
||||
Query query = new TermQuery(new Term("body", "contents"));
|
||||
searcher.search(query, null, 3, sort);
|
||||
}
|
||||
|
||||
/** tests the returned sort values are correct */
|
||||
public void testSortValues() throws Exception {
|
||||
Expression expr = JavascriptCompiler.compile("sqrt(_score)");
|
||||
|
||||
SimpleBindings bindings = new SimpleBindings();
|
||||
bindings.add(new SortField("_score", SortField.Type.SCORE));
|
||||
|
||||
Sort sort = new Sort(expr.getSortField(bindings, true));
|
||||
Query query = new TermQuery(new Term("body", "contents"));
|
||||
TopFieldDocs td = searcher.search(query, null, 3, sort, true, true);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
FieldDoc d = (FieldDoc) td.scoreDocs[i];
|
||||
float expected = (float) Math.sqrt(d.score);
|
||||
float actual = ((Double)d.fields[0]).floatValue();
|
||||
assertEquals(expected, actual, CheckHits.explainToleranceDelta(expected, actual));
|
||||
}
|
||||
}
|
||||
|
||||
/** tests same binding used more than once in an expression */
|
||||
public void testTwoOfSameBinding() throws Exception {
|
||||
Expression expr = JavascriptCompiler.compile("_score + _score");
|
||||
|
||||
SimpleBindings bindings = new SimpleBindings();
|
||||
bindings.add(new SortField("_score", SortField.Type.SCORE));
|
||||
|
||||
Sort sort = new Sort(expr.getSortField(bindings, true));
|
||||
Query query = new TermQuery(new Term("body", "contents"));
|
||||
TopFieldDocs td = searcher.search(query, null, 3, sort, true, true);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
FieldDoc d = (FieldDoc) td.scoreDocs[i];
|
||||
float expected = 2*d.score;
|
||||
float actual = ((Double)d.fields[0]).floatValue();
|
||||
assertEquals(expected, actual, CheckHits.explainToleranceDelta(expected, actual));
|
||||
}
|
||||
}
|
||||
|
||||
/** tests expression referring to another expression */
|
||||
public void testExpressionRefersToExpression() throws Exception {
|
||||
Expression expr1 = JavascriptCompiler.compile("_score");
|
||||
Expression expr2 = JavascriptCompiler.compile("2*expr1");
|
||||
|
||||
SimpleBindings bindings = new SimpleBindings();
|
||||
bindings.add(new SortField("_score", SortField.Type.SCORE));
|
||||
bindings.add("expr1", expr1);
|
||||
|
||||
Sort sort = new Sort(expr2.getSortField(bindings, true));
|
||||
Query query = new TermQuery(new Term("body", "contents"));
|
||||
TopFieldDocs td = searcher.search(query, null, 3, sort, true, true);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
FieldDoc d = (FieldDoc) td.scoreDocs[i];
|
||||
float expected = 2*d.score;
|
||||
float actual = ((Double)d.fields[0]).floatValue();
|
||||
assertEquals(expected, actual, CheckHits.explainToleranceDelta(expected, actual));
|
||||
}
|
||||
}
|
||||
|
||||
/** tests huge amounts of variables in the expression */
|
||||
public void testLotsOfBindings() throws Exception {
|
||||
doTestLotsOfBindings(Byte.MAX_VALUE-1);
|
||||
doTestLotsOfBindings(Byte.MAX_VALUE);
|
||||
doTestLotsOfBindings(Byte.MAX_VALUE+1);
|
||||
// TODO: ideally we'd test > Short.MAX_VALUE too, but compilation is currently recursive.
|
||||
// so if we want to test such huge expressions, we need to instead change parser to use an explicit Stack
|
||||
}
|
||||
|
||||
private void doTestLotsOfBindings(int n) throws Exception {
|
||||
SimpleBindings bindings = new SimpleBindings();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (i > 0) {
|
||||
sb.append("+");
|
||||
}
|
||||
sb.append("x" + i);
|
||||
bindings.add(new SortField("x" + i, SortField.Type.SCORE));
|
||||
}
|
||||
|
||||
Expression expr = JavascriptCompiler.compile(sb.toString());
|
||||
Sort sort = new Sort(expr.getSortField(bindings, true));
|
||||
Query query = new TermQuery(new Term("body", "contents"));
|
||||
TopFieldDocs td = searcher.search(query, null, 3, sort, true, true);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
FieldDoc d = (FieldDoc) td.scoreDocs[i];
|
||||
float expected = n*d.score;
|
||||
float actual = ((Double)d.fields[0]).floatValue();
|
||||
assertEquals(expected, actual, CheckHits.explainToleranceDelta(expected, actual));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
package org.apache.lucene.expressions;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.DoubleField;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.FloatDocValuesField;
|
||||
import org.apache.lucene.document.FloatField;
|
||||
import org.apache.lucene.document.IntField;
|
||||
import org.apache.lucene.document.LongField;
|
||||
import org.apache.lucene.document.NumericDocValuesField;
|
||||
import org.apache.lucene.expressions.js.JavascriptCompiler;
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.RandomIndexWriter;
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.apache.lucene.search.BooleanClause;
|
||||
import org.apache.lucene.search.BooleanQuery;
|
||||
import org.apache.lucene.search.CheckHits;
|
||||
import org.apache.lucene.search.Filter;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.MatchAllDocsQuery;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.search.QueryWrapperFilter;
|
||||
import org.apache.lucene.search.Sort;
|
||||
import org.apache.lucene.search.SortField;
|
||||
import org.apache.lucene.search.TermQuery;
|
||||
import org.apache.lucene.search.TopDocs;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.util.English;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.apache.lucene.util._TestUtil;
|
||||
|
||||
/**
|
||||
* Tests some basic expressions against different queries,
|
||||
* and fieldcache/docvalues fields against an equivalent sort.
|
||||
*/
|
||||
public class TestExpressionSorts extends LuceneTestCase {
|
||||
private Directory dir;
|
||||
private IndexReader reader;
|
||||
private IndexSearcher searcher;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
dir = newDirectory();
|
||||
RandomIndexWriter iw = new RandomIndexWriter(random(), dir);
|
||||
int numDocs = _TestUtil.nextInt(random(), 2049, 4000);
|
||||
for (int i = 0; i < numDocs; i++) {
|
||||
Document document = new Document();
|
||||
document.add(newTextField("english", English.intToEnglish(i), Field.Store.NO));
|
||||
document.add(newTextField("oddeven", (i % 2 == 0) ? "even" : "odd", Field.Store.NO));
|
||||
document.add(newStringField("byte", "" + ((byte) random().nextInt()), Field.Store.NO));
|
||||
document.add(newStringField("short", "" + ((short) random().nextInt()), Field.Store.NO));
|
||||
document.add(new IntField("int", random().nextInt(), Field.Store.NO));
|
||||
document.add(new LongField("long", random().nextLong(), Field.Store.NO));
|
||||
|
||||
document.add(new FloatField("float", random().nextFloat(), Field.Store.NO));
|
||||
document.add(new DoubleField("double", random().nextDouble(), Field.Store.NO));
|
||||
|
||||
document.add(new NumericDocValuesField("intdocvalues", random().nextInt()));
|
||||
document.add(new FloatDocValuesField("floatdocvalues", random().nextFloat()));
|
||||
iw.addDocument(document);
|
||||
}
|
||||
reader = iw.getReader();
|
||||
iw.close();
|
||||
searcher = newSearcher(reader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() throws Exception {
|
||||
reader.close();
|
||||
dir.close();
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
public void testQueries() throws Exception {
|
||||
int n = atLeast(4);
|
||||
for (int i = 0; i < n; i++) {
|
||||
Filter odd = new QueryWrapperFilter(new TermQuery(new Term("oddeven", "odd")));
|
||||
assertQuery(new MatchAllDocsQuery(), null);
|
||||
assertQuery(new TermQuery(new Term("english", "one")), null);
|
||||
assertQuery(new MatchAllDocsQuery(), odd);
|
||||
assertQuery(new TermQuery(new Term("english", "four")), odd);
|
||||
BooleanQuery bq = new BooleanQuery();
|
||||
bq.add(new TermQuery(new Term("english", "one")), BooleanClause.Occur.SHOULD);
|
||||
bq.add(new TermQuery(new Term("oddeven", "even")), BooleanClause.Occur.SHOULD);
|
||||
assertQuery(bq, null);
|
||||
// force in order
|
||||
bq.add(new TermQuery(new Term("english", "two")), BooleanClause.Occur.SHOULD);
|
||||
bq.setMinimumNumberShouldMatch(2);
|
||||
assertQuery(bq, null);
|
||||
}
|
||||
}
|
||||
|
||||
void assertQuery(Query query, Filter filter) throws Exception {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
boolean reversed = random().nextBoolean();
|
||||
SortField fields[] = new SortField[] {
|
||||
new SortField("int", SortField.Type.INT, reversed),
|
||||
new SortField("long", SortField.Type.LONG, reversed),
|
||||
new SortField("float", SortField.Type.FLOAT, reversed),
|
||||
new SortField("double", SortField.Type.DOUBLE, reversed),
|
||||
new SortField("intdocvalues", SortField.Type.INT, reversed),
|
||||
new SortField("floatdocvalues", SortField.Type.FLOAT, reversed),
|
||||
new SortField("score", SortField.Type.SCORE)
|
||||
};
|
||||
Collections.shuffle(Arrays.asList(fields), random());
|
||||
int numSorts = _TestUtil.nextInt(random(), 1, fields.length);
|
||||
assertQuery(query, filter, new Sort(Arrays.copyOfRange(fields, 0, numSorts)));
|
||||
}
|
||||
}
|
||||
|
||||
void assertQuery(Query query, Filter filter, Sort sort) throws Exception {
|
||||
int size = _TestUtil.nextInt(random(), 1, searcher.getIndexReader().maxDoc()/5);
|
||||
TopDocs expected = searcher.search(query, filter, size, sort, random().nextBoolean(), random().nextBoolean());
|
||||
|
||||
// make our actual sort, mutating original by replacing some of the
|
||||
// sortfields with equivalent expressions
|
||||
|
||||
SortField original[] = sort.getSort();
|
||||
SortField mutated[] = new SortField[original.length];
|
||||
for (int i = 0; i < mutated.length; i++) {
|
||||
if (random().nextInt(3) > 0) {
|
||||
SortField s = original[i];
|
||||
Expression expr = JavascriptCompiler.compile(s.getField());
|
||||
SimpleBindings simpleBindings = new SimpleBindings();
|
||||
simpleBindings.add(s);
|
||||
boolean reverse = s.getType() == SortField.Type.SCORE || s.getReverse();
|
||||
mutated[i] = expr.getSortField(simpleBindings, reverse);
|
||||
} else {
|
||||
mutated[i] = original[i];
|
||||
}
|
||||
}
|
||||
|
||||
Sort mutatedSort = new Sort(mutated);
|
||||
TopDocs actual = searcher.search(query, filter, size, mutatedSort, random().nextBoolean(), random().nextBoolean());
|
||||
CheckHits.checkEqual(query, expected.scoreDocs, actual.scoreDocs);
|
||||
|
||||
if (size < actual.totalHits) {
|
||||
expected = searcher.searchAfter(expected.scoreDocs[size-1], query, filter, size, sort);
|
||||
actual = searcher.searchAfter(actual.scoreDocs[size-1], query, filter, size, mutatedSort);
|
||||
CheckHits.checkEqual(query, expected.scoreDocs, actual.scoreDocs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
package org.apache.lucene.expressions;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import org.apache.lucene.expressions.js.JavascriptCompiler;
|
||||
import org.apache.lucene.search.SortField;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
|
||||
/** Tests validation of bindings */
|
||||
public class TestExpressionValidation extends LuceneTestCase {
|
||||
|
||||
public void testValidExternals() throws Exception {
|
||||
SimpleBindings bindings = new SimpleBindings();
|
||||
bindings.add(new SortField("valid0", SortField.Type.INT));
|
||||
bindings.add(new SortField("valid1", SortField.Type.INT));
|
||||
bindings.add(new SortField("valid2", SortField.Type.INT));
|
||||
bindings.add(new SortField("_score", SortField.Type.SCORE));
|
||||
bindings.add("valide0", JavascriptCompiler.compile("valid0 - valid1 + valid2 + _score"));
|
||||
bindings.validate();
|
||||
bindings.add("valide1", JavascriptCompiler.compile("valide0 + valid0"));
|
||||
bindings.validate();
|
||||
bindings.add("valide2", JavascriptCompiler.compile("valide0 * valide1"));
|
||||
bindings.validate();
|
||||
}
|
||||
|
||||
public void testInvalidExternal() throws Exception {
|
||||
SimpleBindings bindings = new SimpleBindings();
|
||||
bindings.add(new SortField("valid", SortField.Type.INT));
|
||||
bindings.add("invalid", JavascriptCompiler.compile("badreference"));
|
||||
try {
|
||||
bindings.validate();
|
||||
fail("didn't get expected exception");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
assertTrue(expected.getMessage().contains("Invalid reference"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testInvalidExternal2() throws Exception {
|
||||
SimpleBindings bindings = new SimpleBindings();
|
||||
bindings.add(new SortField("valid", SortField.Type.INT));
|
||||
bindings.add("invalid", JavascriptCompiler.compile("valid + badreference"));
|
||||
try {
|
||||
bindings.validate();
|
||||
fail("didn't get expected exception");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
assertTrue(expected.getMessage().contains("Invalid reference"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testSelfRecursion() throws Exception {
|
||||
SimpleBindings bindings = new SimpleBindings();
|
||||
bindings.add("cycle0", JavascriptCompiler.compile("cycle0"));
|
||||
try {
|
||||
bindings.validate();
|
||||
fail("didn't get expected exception");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
assertTrue(expected.getMessage().contains("Cycle detected"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testCoRecursion() throws Exception {
|
||||
SimpleBindings bindings = new SimpleBindings();
|
||||
bindings.add("cycle0", JavascriptCompiler.compile("cycle1"));
|
||||
bindings.add("cycle1", JavascriptCompiler.compile("cycle0"));
|
||||
try {
|
||||
bindings.validate();
|
||||
fail("didn't get expected exception");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
assertTrue(expected.getMessage().contains("Cycle detected"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testCoRecursion2() throws Exception {
|
||||
SimpleBindings bindings = new SimpleBindings();
|
||||
bindings.add("cycle0", JavascriptCompiler.compile("cycle1"));
|
||||
bindings.add("cycle1", JavascriptCompiler.compile("cycle2"));
|
||||
bindings.add("cycle2", JavascriptCompiler.compile("cycle0"));
|
||||
try {
|
||||
bindings.validate();
|
||||
fail("didn't get expected exception");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
assertTrue(expected.getMessage().contains("Cycle detected"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testCoRecursion3() throws Exception {
|
||||
SimpleBindings bindings = new SimpleBindings();
|
||||
bindings.add("cycle0", JavascriptCompiler.compile("100"));
|
||||
bindings.add("cycle1", JavascriptCompiler.compile("cycle0 + cycle2"));
|
||||
bindings.add("cycle2", JavascriptCompiler.compile("cycle0 + cycle1"));
|
||||
try {
|
||||
bindings.validate();
|
||||
fail("didn't get expected exception");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
assertTrue(expected.getMessage().contains("Cycle detected"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testCoRecursion4() throws Exception {
|
||||
SimpleBindings bindings = new SimpleBindings();
|
||||
bindings.add("cycle0", JavascriptCompiler.compile("100"));
|
||||
bindings.add("cycle1", JavascriptCompiler.compile("100"));
|
||||
bindings.add("cycle2", JavascriptCompiler.compile("cycle1 + cycle0 + cycle3"));
|
||||
bindings.add("cycle3", JavascriptCompiler.compile("cycle0 + cycle1 + cycle2"));
|
||||
try {
|
||||
bindings.validate();
|
||||
fail("didn't get expected exception");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
assertTrue(expected.getMessage().contains("Cycle detected"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
package org.apache.lucene.expressions.js;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.lucene.expressions.Expression;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.commons.GeneratorAdapter;
|
||||
|
||||
/** Tests customing the function map */
|
||||
public class TestCustomFunctions extends LuceneTestCase {
|
||||
private static double DELTA = 0.0000001;
|
||||
|
||||
/** empty list of methods */
|
||||
public void testEmpty() throws Exception {
|
||||
Map<String,Method> functions = Collections.emptyMap();
|
||||
try {
|
||||
JavascriptCompiler.compile("sqrt(20)", functions, getClass().getClassLoader());
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertTrue(e.getMessage().contains("Unrecognized method"));
|
||||
}
|
||||
}
|
||||
|
||||
/** using the default map explicitly */
|
||||
public void testDefaultList() throws Exception {
|
||||
Map<String,Method> functions = JavascriptCompiler.DEFAULT_FUNCTIONS;
|
||||
Expression expr = JavascriptCompiler.compile("sqrt(20)", functions, getClass().getClassLoader());
|
||||
assertEquals(Math.sqrt(20), expr.evaluate(0, null), DELTA);
|
||||
}
|
||||
|
||||
public static double zeroArgMethod() { return 5; }
|
||||
|
||||
/** tests a method with no arguments */
|
||||
public void testNoArgMethod() throws Exception {
|
||||
Map<String,Method> functions = new HashMap<String,Method>();
|
||||
functions.put("foo", getClass().getMethod("zeroArgMethod"));
|
||||
Expression expr = JavascriptCompiler.compile("foo()", functions, getClass().getClassLoader());
|
||||
assertEquals(5, expr.evaluate(0, null), DELTA);
|
||||
}
|
||||
|
||||
public static double oneArgMethod(double arg1) { return 3 + arg1; }
|
||||
|
||||
/** tests a method with one arguments */
|
||||
public void testOneArgMethod() throws Exception {
|
||||
Map<String,Method> functions = new HashMap<String,Method>();
|
||||
functions.put("foo", getClass().getMethod("oneArgMethod", double.class));
|
||||
Expression expr = JavascriptCompiler.compile("foo(3)", functions, getClass().getClassLoader());
|
||||
assertEquals(6, expr.evaluate(0, null), DELTA);
|
||||
}
|
||||
|
||||
public static double threeArgMethod(double arg1, double arg2, double arg3) { return arg1 + arg2 + arg3; }
|
||||
|
||||
/** tests a method with three arguments */
|
||||
public void testThreeArgMethod() throws Exception {
|
||||
Map<String,Method> functions = new HashMap<String,Method>();
|
||||
functions.put("foo", getClass().getMethod("threeArgMethod", double.class, double.class, double.class));
|
||||
Expression expr = JavascriptCompiler.compile("foo(3, 4, 5)", functions, getClass().getClassLoader());
|
||||
assertEquals(12, expr.evaluate(0, null), DELTA);
|
||||
}
|
||||
|
||||
/** tests a map with 2 functions */
|
||||
public void testTwoMethods() throws Exception {
|
||||
Map<String,Method> functions = new HashMap<String,Method>();
|
||||
functions.put("foo", getClass().getMethod("zeroArgMethod"));
|
||||
functions.put("bar", getClass().getMethod("oneArgMethod", double.class));
|
||||
Expression expr = JavascriptCompiler.compile("foo() + bar(3)", functions, getClass().getClassLoader());
|
||||
assertEquals(11, expr.evaluate(0, null), DELTA);
|
||||
}
|
||||
|
||||
public static String bogusReturnType() { return "bogus!"; }
|
||||
|
||||
/** wrong return type: must be double */
|
||||
public void testWrongReturnType() throws Exception {
|
||||
Map<String,Method> functions = new HashMap<String,Method>();
|
||||
functions.put("foo", getClass().getMethod("bogusReturnType"));
|
||||
try {
|
||||
JavascriptCompiler.compile("foo()", functions, getClass().getClassLoader());
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertTrue(e.getMessage().contains("does not return a double"));
|
||||
}
|
||||
}
|
||||
|
||||
public static double bogusParameterType(String s) { return 0; }
|
||||
|
||||
/** wrong param type: must be doubles */
|
||||
public void testWrongParameterType() throws Exception {
|
||||
Map<String,Method> functions = new HashMap<String,Method>();
|
||||
functions.put("foo", getClass().getMethod("bogusParameterType", String.class));
|
||||
try {
|
||||
JavascriptCompiler.compile("foo(2)", functions, getClass().getClassLoader());
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertTrue(e.getMessage().contains("must take only double parameters"));
|
||||
}
|
||||
}
|
||||
|
||||
public double nonStaticMethod() { return 0; }
|
||||
|
||||
/** wrong modifiers: must be static */
|
||||
public void testWrongNotStatic() throws Exception {
|
||||
Map<String,Method> functions = new HashMap<String,Method>();
|
||||
functions.put("foo", getClass().getMethod("nonStaticMethod"));
|
||||
try {
|
||||
JavascriptCompiler.compile("foo()", functions, getClass().getClassLoader());
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertTrue(e.getMessage().contains("is not static"));
|
||||
}
|
||||
}
|
||||
|
||||
static double nonPublicMethod() { return 0; }
|
||||
|
||||
/** wrong modifiers: must be public */
|
||||
public void testWrongNotPublic() throws Exception {
|
||||
Map<String,Method> functions = new HashMap<String,Method>();
|
||||
functions.put("foo", getClass().getDeclaredMethod("nonPublicMethod"));
|
||||
try {
|
||||
JavascriptCompiler.compile("foo()", functions, getClass().getClassLoader());
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertTrue(e.getMessage().contains("is not public"));
|
||||
}
|
||||
}
|
||||
|
||||
static class NestedNotPublic {
|
||||
public static double method() { return 0; }
|
||||
}
|
||||
|
||||
/** wrong class modifiers: class containing method is not public */
|
||||
public void testWrongNestedNotPublic() throws Exception {
|
||||
Map<String,Method> functions = new HashMap<String,Method>();
|
||||
functions.put("foo", NestedNotPublic.class.getMethod("method"));
|
||||
try {
|
||||
JavascriptCompiler.compile("foo()", functions, getClass().getClassLoader());
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertTrue(e.getMessage().contains("is not public"));
|
||||
}
|
||||
}
|
||||
|
||||
/** Classloader that can be used to create a fake static class that has one method returning a static var */
|
||||
static class Loader extends ClassLoader implements Opcodes {
|
||||
Loader(ClassLoader parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
public Class<?> createFakeClass() {
|
||||
String className = TestCustomFunctions.class.getName() + "$Foo";
|
||||
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
|
||||
classWriter.visit(Opcodes.V1_5, ACC_PUBLIC | ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC,
|
||||
className.replace('.', '/'), null, Type.getInternalName(Object.class), null);
|
||||
|
||||
org.objectweb.asm.commons.Method m = org.objectweb.asm.commons.Method.getMethod("void <init>()");
|
||||
GeneratorAdapter constructor = new GeneratorAdapter(ACC_PRIVATE | ACC_SYNTHETIC, m, null, null, classWriter);
|
||||
constructor.loadThis();
|
||||
constructor.loadArgs();
|
||||
constructor.invokeConstructor(Type.getType(Object.class), m);
|
||||
constructor.returnValue();
|
||||
constructor.endMethod();
|
||||
|
||||
GeneratorAdapter gen = new GeneratorAdapter(ACC_STATIC | ACC_PUBLIC | ACC_SYNTHETIC,
|
||||
org.objectweb.asm.commons.Method.getMethod("double bar()"), null, null, classWriter);
|
||||
gen.push(2.0);
|
||||
gen.returnValue();
|
||||
gen.endMethod();
|
||||
|
||||
byte[] bc = classWriter.toByteArray();
|
||||
return defineClass(className, bc, 0, bc.length);
|
||||
}
|
||||
}
|
||||
|
||||
/** uses this test with a different classloader and tries to
|
||||
* register it using the default classloader, which should fail */
|
||||
public void testClassLoader() throws Exception {
|
||||
ClassLoader thisLoader = getClass().getClassLoader();
|
||||
Loader childLoader = new Loader(thisLoader);
|
||||
Class<?> fooClass = childLoader.createFakeClass();
|
||||
|
||||
Method barMethod = fooClass.getMethod("bar");
|
||||
Map<String,Method> functions = Collections.singletonMap("bar", barMethod);
|
||||
assertNotSame(thisLoader, fooClass.getClassLoader());
|
||||
assertNotSame(thisLoader, barMethod.getDeclaringClass().getClassLoader());
|
||||
|
||||
// this should pass:
|
||||
Expression expr = JavascriptCompiler.compile("bar()", functions, childLoader);
|
||||
assertEquals(2.0, expr.evaluate(0, null), DELTA);
|
||||
|
||||
// use our classloader, not the foreign one, which should fail!
|
||||
try {
|
||||
JavascriptCompiler.compile("bar()", functions, thisLoader);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertTrue(e.getMessage().contains("is not declared by a class which is accessible by the given parent ClassLoader"));
|
||||
}
|
||||
|
||||
// mix foreign and default functions
|
||||
Map<String,Method> mixedFunctions = new HashMap<>(JavascriptCompiler.DEFAULT_FUNCTIONS);
|
||||
mixedFunctions.putAll(functions);
|
||||
expr = JavascriptCompiler.compile("bar()", mixedFunctions, childLoader);
|
||||
assertEquals(2.0, expr.evaluate(0, null), DELTA);
|
||||
expr = JavascriptCompiler.compile("sqrt(20)", mixedFunctions, childLoader);
|
||||
assertEquals(Math.sqrt(20), expr.evaluate(0, null), DELTA);
|
||||
|
||||
// use our classloader, not the foreign one, which should fail!
|
||||
try {
|
||||
JavascriptCompiler.compile("bar()", functions, thisLoader);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertTrue(e.getMessage().contains("is not declared by a class which is accessible by the given parent ClassLoader"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
package org.apache.lucene.expressions.js;
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
|
||||
public class TestJavascriptCompiler extends LuceneTestCase {
|
||||
|
||||
public void testValidCompiles() throws Exception {
|
||||
assertNotNull(JavascriptCompiler.compile("100"));
|
||||
assertNotNull(JavascriptCompiler.compile("valid0+100"));
|
||||
assertNotNull(JavascriptCompiler.compile("valid0+\n100"));
|
||||
assertNotNull(JavascriptCompiler.compile("logn(2, 20+10-5.0)"));
|
||||
}
|
||||
|
||||
public void testInvalidCompiles() throws Exception {
|
||||
try {
|
||||
JavascriptCompiler.compile("100 100");
|
||||
fail();
|
||||
} catch (ParseException expected) {
|
||||
// expected exception
|
||||
}
|
||||
|
||||
try {
|
||||
JavascriptCompiler.compile("7*/-8");
|
||||
fail();
|
||||
} catch (ParseException expected) {
|
||||
// expected exception
|
||||
}
|
||||
|
||||
try {
|
||||
JavascriptCompiler.compile("0y1234");
|
||||
fail();
|
||||
} catch (ParseException expected) {
|
||||
// expected exception
|
||||
}
|
||||
|
||||
try {
|
||||
JavascriptCompiler.compile("500EE");
|
||||
fail();
|
||||
} catch (ParseException expected) {
|
||||
// expected exception
|
||||
}
|
||||
|
||||
try {
|
||||
JavascriptCompiler.compile("500.5EE");
|
||||
fail();
|
||||
} catch (ParseException expected) {
|
||||
// expected exception
|
||||
}
|
||||
}
|
||||
|
||||
public void testEmpty() {
|
||||
try {
|
||||
JavascriptCompiler.compile("");
|
||||
fail();
|
||||
} catch (ParseException expected) {
|
||||
// expected exception
|
||||
}
|
||||
|
||||
try {
|
||||
JavascriptCompiler.compile("()");
|
||||
fail();
|
||||
} catch (ParseException expected) {
|
||||
// expected exception
|
||||
}
|
||||
|
||||
try {
|
||||
JavascriptCompiler.compile(" \r\n \n \t");
|
||||
fail();
|
||||
} catch (ParseException expected) {
|
||||
// expected exception
|
||||
}
|
||||
}
|
||||
|
||||
public void testNull() throws Exception {
|
||||
try {
|
||||
JavascriptCompiler.compile(null);
|
||||
fail();
|
||||
} catch (NullPointerException expected) {
|
||||
// expected exception
|
||||
}
|
||||
}
|
||||
|
||||
public void testWrongArity() throws Exception {
|
||||
try {
|
||||
JavascriptCompiler.compile("tan()");
|
||||
fail();
|
||||
} catch (IllegalArgumentException expected) {
|
||||
assertTrue(expected.getMessage().contains("arguments for method call"));
|
||||
}
|
||||
|
||||
try {
|
||||
JavascriptCompiler.compile("tan(1, 1)");
|
||||
fail();
|
||||
} catch (IllegalArgumentException expected) {
|
||||
assertTrue(expected.getMessage().contains("arguments for method call"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,264 @@
|
|||
package org.apache.lucene.expressions.js;
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import org.apache.lucene.expressions.Expression;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
|
||||
public class TestJavascriptFunction extends LuceneTestCase {
|
||||
private static double DELTA = 0.0000001;
|
||||
|
||||
private void assertEvaluatesTo(String expression, double expected) throws Exception {
|
||||
Expression evaluator = JavascriptCompiler.compile(expression);
|
||||
double actual = evaluator.evaluate(0, null);
|
||||
assertEquals(expected, actual, DELTA);
|
||||
}
|
||||
|
||||
public void testAbsMethod() throws Exception {
|
||||
assertEvaluatesTo("abs(0)", 0);
|
||||
assertEvaluatesTo("abs(119)", 119);
|
||||
assertEvaluatesTo("abs(119)", 119);
|
||||
assertEvaluatesTo("abs(1)", 1);
|
||||
assertEvaluatesTo("abs(-1)", 1);
|
||||
}
|
||||
|
||||
public void testAcosMethod() throws Exception {
|
||||
assertEvaluatesTo("acos(-1)", Math.PI);
|
||||
assertEvaluatesTo("acos(-0.8660254)", Math.PI*5/6);
|
||||
assertEvaluatesTo("acos(-0.7071068)", Math.PI*3/4);
|
||||
assertEvaluatesTo("acos(-0.5)", Math.PI*2/3);
|
||||
assertEvaluatesTo("acos(0)", Math.PI/2);
|
||||
assertEvaluatesTo("acos(0.5)", Math.PI/3);
|
||||
assertEvaluatesTo("acos(0.7071068)", Math.PI/4);
|
||||
assertEvaluatesTo("acos(0.8660254)", Math.PI/6);
|
||||
assertEvaluatesTo("acos(1)", 0);
|
||||
}
|
||||
|
||||
public void testAcoshMethod() throws Exception {
|
||||
assertEvaluatesTo("acosh(1)", 0);
|
||||
assertEvaluatesTo("acosh(2.5)", 1.5667992369724109);
|
||||
assertEvaluatesTo("acosh(1234567.89)", 14.719378760739708);
|
||||
}
|
||||
|
||||
public void testAsinMethod() throws Exception {
|
||||
assertEvaluatesTo("asin(-1)", -Math.PI/2);
|
||||
assertEvaluatesTo("asin(-0.8660254)", -Math.PI/3);
|
||||
assertEvaluatesTo("asin(-0.7071068)", -Math.PI/4);
|
||||
assertEvaluatesTo("asin(-0.5)", -Math.PI/6);
|
||||
assertEvaluatesTo("asin(0)", 0);
|
||||
assertEvaluatesTo("asin(0.5)", Math.PI/6);
|
||||
assertEvaluatesTo("asin(0.7071068)", Math.PI/4);
|
||||
assertEvaluatesTo("asin(0.8660254)", Math.PI/3);
|
||||
assertEvaluatesTo("asin(1)", Math.PI/2);
|
||||
}
|
||||
|
||||
public void testAsinhMethod() throws Exception {
|
||||
assertEvaluatesTo("asinh(-1234567.89)", -14.719378760740035);
|
||||
assertEvaluatesTo("asinh(-2.5)", -1.6472311463710958);
|
||||
assertEvaluatesTo("asinh(-1)", -0.8813735870195429);
|
||||
assertEvaluatesTo("asinh(0)", 0);
|
||||
assertEvaluatesTo("asinh(1)", 0.8813735870195429);
|
||||
assertEvaluatesTo("asinh(2.5)", 1.6472311463710958);
|
||||
assertEvaluatesTo("asinh(1234567.89)", 14.719378760740035);
|
||||
}
|
||||
|
||||
public void testAtanMethod() throws Exception {
|
||||
assertEvaluatesTo("atan(-1.732050808)", -Math.PI/3);
|
||||
assertEvaluatesTo("atan(-1)", -Math.PI/4);
|
||||
assertEvaluatesTo("atan(-0.577350269)", -Math.PI/6);
|
||||
assertEvaluatesTo("atan(0)", 0);
|
||||
assertEvaluatesTo("atan(0.577350269)", Math.PI/6);
|
||||
assertEvaluatesTo("atan(1)", Math.PI/4);
|
||||
assertEvaluatesTo("atan(1.732050808)", Math.PI/3);
|
||||
}
|
||||
|
||||
public void testAtan2Method() throws Exception {
|
||||
assertEvaluatesTo("atan2(+0,+0)", +0.0);
|
||||
assertEvaluatesTo("atan2(+0,-0)", +Math.PI);
|
||||
assertEvaluatesTo("atan2(-0,+0)", -0.0);
|
||||
assertEvaluatesTo("atan2(-0,-0)", -Math.PI);
|
||||
assertEvaluatesTo("atan2(2,2)", Math.PI/4);
|
||||
assertEvaluatesTo("atan2(-2,2)", -Math.PI/4);
|
||||
assertEvaluatesTo("atan2(2,-2)", Math.PI*3/4);
|
||||
assertEvaluatesTo("atan2(-2,-2)", -Math.PI*3/4);
|
||||
}
|
||||
|
||||
public void testAtanhMethod() throws Exception {
|
||||
assertEvaluatesTo("atanh(-1)", Double.NEGATIVE_INFINITY);
|
||||
assertEvaluatesTo("atanh(-0.5)", -0.5493061443340549);
|
||||
assertEvaluatesTo("atanh(0)", 0);
|
||||
assertEvaluatesTo("atanh(0.5)", 0.5493061443340549);
|
||||
assertEvaluatesTo("atanh(1)", Double.POSITIVE_INFINITY);
|
||||
}
|
||||
|
||||
public void testCeilMethod() throws Exception {
|
||||
assertEvaluatesTo("ceil(0)", 0);
|
||||
assertEvaluatesTo("ceil(0.1)", 1);
|
||||
assertEvaluatesTo("ceil(0.9)", 1);
|
||||
assertEvaluatesTo("ceil(25.2)", 26);
|
||||
assertEvaluatesTo("ceil(-0.1)", 0);
|
||||
assertEvaluatesTo("ceil(-0.9)", 0);
|
||||
assertEvaluatesTo("ceil(-1.1)", -1);
|
||||
}
|
||||
|
||||
public void testCosMethod() throws Exception {
|
||||
assertEvaluatesTo("cos(0)", 1);
|
||||
assertEvaluatesTo("cos(" + Math.PI/2 + ")", 0);
|
||||
assertEvaluatesTo("cos(" + -Math.PI/2 + ")", 0);
|
||||
assertEvaluatesTo("cos(" + Math.PI/4 + ")", 0.7071068);
|
||||
assertEvaluatesTo("cos(" + -Math.PI/4 + ")", 0.7071068);
|
||||
assertEvaluatesTo("cos(" + Math.PI*2/3 + ")",-0.5);
|
||||
assertEvaluatesTo("cos(" + -Math.PI*2/3 + ")", -0.5);
|
||||
assertEvaluatesTo("cos(" + Math.PI/6 + ")", 0.8660254);
|
||||
assertEvaluatesTo("cos(" + -Math.PI/6 + ")", 0.8660254);
|
||||
}
|
||||
|
||||
public void testCoshMethod() throws Exception {
|
||||
assertEvaluatesTo("cosh(0)", 1);
|
||||
assertEvaluatesTo("cosh(-1)", 1.5430806348152437);
|
||||
assertEvaluatesTo("cosh(1)", 1.5430806348152437);
|
||||
assertEvaluatesTo("cosh(-0.5)", 1.1276259652063807);
|
||||
assertEvaluatesTo("cosh(0.5)", 1.1276259652063807);
|
||||
assertEvaluatesTo("cosh(-12.3456789)", 114982.09728671524);
|
||||
assertEvaluatesTo("cosh(12.3456789)", 114982.09728671524);
|
||||
}
|
||||
|
||||
public void testExpMethod() throws Exception {
|
||||
assertEvaluatesTo("exp(0)", 1);
|
||||
assertEvaluatesTo("exp(-1)", 0.36787944117);
|
||||
assertEvaluatesTo("exp(1)", 2.71828182846);
|
||||
assertEvaluatesTo("exp(-0.5)", 0.60653065971);
|
||||
assertEvaluatesTo("exp(0.5)", 1.6487212707);
|
||||
assertEvaluatesTo("exp(-12.3456789)", 0.0000043485);
|
||||
assertEvaluatesTo("exp(12.3456789)", 229964.194569);
|
||||
}
|
||||
|
||||
public void testFloorMethod() throws Exception {
|
||||
assertEvaluatesTo("floor(0)", 0);
|
||||
assertEvaluatesTo("floor(0.1)", 0);
|
||||
assertEvaluatesTo("floor(0.9)", 0);
|
||||
assertEvaluatesTo("floor(25.2)", 25);
|
||||
assertEvaluatesTo("floor(-0.1)", -1);
|
||||
assertEvaluatesTo("floor(-0.9)", -1);
|
||||
assertEvaluatesTo("floor(-1.1)", -2);
|
||||
}
|
||||
|
||||
public void testLnMethod() throws Exception {
|
||||
assertEvaluatesTo("ln(0)", Double.NEGATIVE_INFINITY);
|
||||
assertEvaluatesTo("ln(" + Math.E + ")", 1);
|
||||
assertEvaluatesTo("ln(-1)", Double.NaN);
|
||||
assertEvaluatesTo("ln(1)", 0);
|
||||
assertEvaluatesTo("ln(0.5)", -0.69314718056);
|
||||
assertEvaluatesTo("ln(12.3456789)", 2.51330611521);
|
||||
}
|
||||
|
||||
public void testLog10Method() throws Exception {
|
||||
assertEvaluatesTo("log10(0)", Double.NEGATIVE_INFINITY);
|
||||
assertEvaluatesTo("log10(1)", 0);
|
||||
assertEvaluatesTo("log10(-1)", Double.NaN);
|
||||
assertEvaluatesTo("log10(0.5)", -0.3010299956639812);
|
||||
assertEvaluatesTo("log10(12.3456789)", 1.0915149771692705);
|
||||
}
|
||||
|
||||
public void testLognMethod() throws Exception {
|
||||
assertEvaluatesTo("logn(2, 0)", Double.NEGATIVE_INFINITY);
|
||||
assertEvaluatesTo("logn(2, 1)", 0);
|
||||
assertEvaluatesTo("logn(2, -1)", Double.NaN);
|
||||
assertEvaluatesTo("logn(2, 0.5)", -1);
|
||||
assertEvaluatesTo("logn(2, 12.3456789)", 3.6259342686489378);
|
||||
assertEvaluatesTo("logn(2.5, 0)", Double.NEGATIVE_INFINITY);
|
||||
assertEvaluatesTo("logn(2.5, 1)", 0);
|
||||
assertEvaluatesTo("logn(2.5, -1)", Double.NaN);
|
||||
assertEvaluatesTo("logn(2.5, 0.5)", -0.75647079736603);
|
||||
assertEvaluatesTo("logn(2.5, 12.3456789)", 2.7429133874016745);
|
||||
}
|
||||
|
||||
public void testMaxMethod() throws Exception {
|
||||
assertEvaluatesTo("max(0, 0)", 0);
|
||||
assertEvaluatesTo("max(1, 0)", 1);
|
||||
assertEvaluatesTo("max(0, -1)", 0);
|
||||
assertEvaluatesTo("max(-1, 0)", 0);
|
||||
assertEvaluatesTo("max(25, 23)", 25);
|
||||
}
|
||||
|
||||
public void testMinMethod() throws Exception {
|
||||
assertEvaluatesTo("min(0, 0)", 0);
|
||||
assertEvaluatesTo("min(1, 0)", 0);
|
||||
assertEvaluatesTo("min(0, -1)", -1);
|
||||
assertEvaluatesTo("min(-1, 0)", -1);
|
||||
assertEvaluatesTo("min(25, 23)", 23);
|
||||
}
|
||||
|
||||
public void testPowMethod() throws Exception {
|
||||
assertEvaluatesTo("pow(0, 0)", 1);
|
||||
assertEvaluatesTo("pow(0.1, 2)", 0.01);
|
||||
assertEvaluatesTo("pow(0.9, -1)", 1.1111111111111112);
|
||||
assertEvaluatesTo("pow(2.2, -2.5)", 0.13929749224447147);
|
||||
assertEvaluatesTo("pow(5, 3)", 125);
|
||||
assertEvaluatesTo("pow(-0.9, 5)", -0.59049);
|
||||
assertEvaluatesTo("pow(-1.1, 2)", 1.21);
|
||||
}
|
||||
|
||||
public void testSinMethod() throws Exception {
|
||||
assertEvaluatesTo("sin(0)", 0);
|
||||
assertEvaluatesTo("sin(" + Math.PI/2 + ")", 1);
|
||||
assertEvaluatesTo("sin(" + -Math.PI/2 + ")", -1);
|
||||
assertEvaluatesTo("sin(" + Math.PI/4 + ")", 0.7071068);
|
||||
assertEvaluatesTo("sin(" + -Math.PI/4 + ")", -0.7071068);
|
||||
assertEvaluatesTo("sin(" + Math.PI*2/3 + ")", 0.8660254);
|
||||
assertEvaluatesTo("sin(" + -Math.PI*2/3 + ")", -0.8660254);
|
||||
assertEvaluatesTo("sin(" + Math.PI/6 + ")", 0.5);
|
||||
assertEvaluatesTo("sin(" + -Math.PI/6 + ")", -0.5);
|
||||
}
|
||||
|
||||
public void testSinhMethod() throws Exception {
|
||||
assertEvaluatesTo("sinh(0)", 0);
|
||||
assertEvaluatesTo("sinh(-1)", -1.1752011936438014);
|
||||
assertEvaluatesTo("sinh(1)", 1.1752011936438014);
|
||||
assertEvaluatesTo("sinh(-0.5)", -0.52109530549);
|
||||
assertEvaluatesTo("sinh(0.5)", 0.52109530549);
|
||||
assertEvaluatesTo("sinh(-12.3456789)", -114982.09728236674);
|
||||
assertEvaluatesTo("sinh(12.3456789)", 114982.09728236674);
|
||||
}
|
||||
|
||||
public void testSqrtMethod() throws Exception {
|
||||
assertEvaluatesTo("sqrt(0)", 0);
|
||||
assertEvaluatesTo("sqrt(-1)", Double.NaN);
|
||||
assertEvaluatesTo("sqrt(0.49)", 0.7);
|
||||
assertEvaluatesTo("sqrt(49)", 7);
|
||||
}
|
||||
|
||||
public void testTanMethod() throws Exception {
|
||||
assertEvaluatesTo("tan(0)", 0);
|
||||
assertEvaluatesTo("tan(-1)", -1.55740772465);
|
||||
assertEvaluatesTo("tan(1)", 1.55740772465);
|
||||
assertEvaluatesTo("tan(-0.5)", -0.54630248984);
|
||||
assertEvaluatesTo("tan(0.5)", 0.54630248984);
|
||||
assertEvaluatesTo("tan(-1.3)", -3.60210244797);
|
||||
assertEvaluatesTo("tan(1.3)", 3.60210244797);
|
||||
}
|
||||
|
||||
public void testTanhMethod() throws Exception {
|
||||
assertEvaluatesTo("tanh(0)", 0);
|
||||
assertEvaluatesTo("tanh(-1)", -0.76159415595);
|
||||
assertEvaluatesTo("tanh(1)", 0.76159415595);
|
||||
assertEvaluatesTo("tanh(-0.5)", -0.46211715726);
|
||||
assertEvaluatesTo("tanh(0.5)", 0.46211715726);
|
||||
assertEvaluatesTo("tanh(-12.3456789)", -0.99999999996);
|
||||
assertEvaluatesTo("tanh(12.3456789)", 0.99999999996);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,331 @@
|
|||
package org.apache.lucene.expressions.js;
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import org.apache.lucene.expressions.Expression;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
|
||||
public class TestJavascriptOperations extends LuceneTestCase {
|
||||
private void assertEvaluatesTo(String expression, long expected) throws Exception {
|
||||
Expression evaluator = JavascriptCompiler.compile(expression);
|
||||
long actual = (long)evaluator.evaluate(0, null);
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
public void testNegationOperation() throws Exception {
|
||||
assertEvaluatesTo("-1", -1);
|
||||
assertEvaluatesTo("--1", 1);
|
||||
assertEvaluatesTo("-(-1)", 1);
|
||||
assertEvaluatesTo("-0", 0);
|
||||
assertEvaluatesTo("--0", 0);
|
||||
}
|
||||
|
||||
public void testAddOperation() throws Exception {
|
||||
assertEvaluatesTo("1+1", 2);
|
||||
assertEvaluatesTo("1+0.5+0.5", 2);
|
||||
assertEvaluatesTo("5+10", 15);
|
||||
assertEvaluatesTo("1+1+2", 4);
|
||||
assertEvaluatesTo("(1+1)+2", 4);
|
||||
assertEvaluatesTo("1+(1+2)", 4);
|
||||
assertEvaluatesTo("0+1", 1);
|
||||
assertEvaluatesTo("1+0", 1);
|
||||
assertEvaluatesTo("0+0", 0);
|
||||
}
|
||||
|
||||
public void testSubtractOperation() throws Exception {
|
||||
assertEvaluatesTo("1-1", 0);
|
||||
assertEvaluatesTo("5-10", -5);
|
||||
assertEvaluatesTo("1-0.5-0.5", 0);
|
||||
assertEvaluatesTo("1-1-2", -2);
|
||||
assertEvaluatesTo("(1-1)-2", -2);
|
||||
assertEvaluatesTo("1-(1-2)", 2);
|
||||
assertEvaluatesTo("0-1", -1);
|
||||
assertEvaluatesTo("1-0", 1);
|
||||
assertEvaluatesTo("0-0", 0);
|
||||
}
|
||||
|
||||
public void testMultiplyOperation() throws Exception {
|
||||
assertEvaluatesTo("1*1", 1);
|
||||
assertEvaluatesTo("5*10", 50);
|
||||
assertEvaluatesTo("50*0.1", 5);
|
||||
assertEvaluatesTo("1*1*2", 2);
|
||||
assertEvaluatesTo("(1*1)*2", 2);
|
||||
assertEvaluatesTo("1*(1*2)", 2);
|
||||
assertEvaluatesTo("10*0", 0);
|
||||
assertEvaluatesTo("0*0", 0);
|
||||
}
|
||||
|
||||
public void testDivisionOperation() throws Exception {
|
||||
assertEvaluatesTo("1*1", 1);
|
||||
assertEvaluatesTo("10/5", 2);
|
||||
assertEvaluatesTo("10/0.5", 20);
|
||||
assertEvaluatesTo("10/5/2", 1);
|
||||
assertEvaluatesTo("(27/9)/3", 1);
|
||||
assertEvaluatesTo("27/(9/3)", 9);
|
||||
assertEvaluatesTo("1/0", 9223372036854775807L);
|
||||
}
|
||||
|
||||
public void testModuloOperation() throws Exception {
|
||||
assertEvaluatesTo("1%1", 0);
|
||||
assertEvaluatesTo("10%3", 1);
|
||||
assertEvaluatesTo("10%3%2", 1);
|
||||
assertEvaluatesTo("(27%10)%4", 3);
|
||||
assertEvaluatesTo("27%(9%5)", 3);
|
||||
}
|
||||
|
||||
public void testLessThanOperation() throws Exception {
|
||||
assertEvaluatesTo("1 < 1", 0);
|
||||
assertEvaluatesTo("2 < 1", 0);
|
||||
assertEvaluatesTo("1 < 2", 1);
|
||||
assertEvaluatesTo("2 < 1 < 3", 1);
|
||||
assertEvaluatesTo("2 < (1 < 3)", 0);
|
||||
assertEvaluatesTo("(2 < 1) < 1", 1);
|
||||
assertEvaluatesTo("-1 < -2", 0);
|
||||
assertEvaluatesTo("-1 < 0", 1);
|
||||
}
|
||||
|
||||
public void testLessThanEqualsOperation() throws Exception {
|
||||
assertEvaluatesTo("1 <= 1", 1);
|
||||
assertEvaluatesTo("2 <= 1", 0);
|
||||
assertEvaluatesTo("1 <= 2", 1);
|
||||
assertEvaluatesTo("1 <= 1 <= 0", 0);
|
||||
assertEvaluatesTo("-1 <= -1", 1);
|
||||
assertEvaluatesTo("-1 <= 0", 1);
|
||||
assertEvaluatesTo("-1 <= -2", 0);
|
||||
assertEvaluatesTo("-1 <= 0", 1);
|
||||
}
|
||||
|
||||
public void testGreaterThanOperation() throws Exception {
|
||||
assertEvaluatesTo("1 > 1", 0);
|
||||
assertEvaluatesTo("2 > 1", 1);
|
||||
assertEvaluatesTo("1 > 2", 0);
|
||||
assertEvaluatesTo("2 > 1 > 3", 0);
|
||||
assertEvaluatesTo("2 > (1 > 3)", 1);
|
||||
assertEvaluatesTo("(2 > 1) > 1", 0);
|
||||
assertEvaluatesTo("-1 > -2", 1);
|
||||
assertEvaluatesTo("-1 > 0", 0);
|
||||
}
|
||||
|
||||
public void testGreaterThanEqualsOperation() throws Exception {
|
||||
assertEvaluatesTo("1 >= 1", 1);
|
||||
assertEvaluatesTo("2 >= 1", 1);
|
||||
assertEvaluatesTo("1 >= 2", 0);
|
||||
assertEvaluatesTo("1 >= 1 >= 0", 1);
|
||||
assertEvaluatesTo("-1 >= -1", 1);
|
||||
assertEvaluatesTo("-1 >= 0", 0);
|
||||
assertEvaluatesTo("-1 >= -2", 1);
|
||||
assertEvaluatesTo("-1 >= 0", 0);
|
||||
}
|
||||
|
||||
public void testEqualsOperation() throws Exception {
|
||||
assertEvaluatesTo("1 == 1", 1);
|
||||
assertEvaluatesTo("0 == 0", 1);
|
||||
assertEvaluatesTo("-1 == -1", 1);
|
||||
assertEvaluatesTo("1.1 == 1.1", 1);
|
||||
assertEvaluatesTo("0.9 == 0.9", 1);
|
||||
assertEvaluatesTo("-0 == 0", 1);
|
||||
assertEvaluatesTo("0 == 1", 0);
|
||||
assertEvaluatesTo("1 == 2", 0);
|
||||
assertEvaluatesTo("-1 == 1", 0);
|
||||
assertEvaluatesTo("-1 == 0", 0);
|
||||
assertEvaluatesTo("-2 == 1", 0);
|
||||
assertEvaluatesTo("-2 == -1", 0);
|
||||
}
|
||||
|
||||
public void testNotEqualsOperation() throws Exception {
|
||||
assertEvaluatesTo("1 != 1", 0);
|
||||
assertEvaluatesTo("0 != 0", 0);
|
||||
assertEvaluatesTo("-1 != -1", 0);
|
||||
assertEvaluatesTo("1.1 != 1.1", 0);
|
||||
assertEvaluatesTo("0.9 != 0.9", 0);
|
||||
assertEvaluatesTo("-0 != 0", 0);
|
||||
assertEvaluatesTo("0 != 1", 1);
|
||||
assertEvaluatesTo("1 != 2", 1);
|
||||
assertEvaluatesTo("-1 != 1", 1);
|
||||
assertEvaluatesTo("-1 != 0", 1);
|
||||
assertEvaluatesTo("-2 != 1", 1);
|
||||
assertEvaluatesTo("-2 != -1", 1);
|
||||
}
|
||||
|
||||
public void testBoolNotOperation() throws Exception {
|
||||
assertEvaluatesTo("!1", 0);
|
||||
assertEvaluatesTo("!!1", 1);
|
||||
assertEvaluatesTo("!0", 1);
|
||||
assertEvaluatesTo("!!0", 0);
|
||||
assertEvaluatesTo("!-1", 0);
|
||||
assertEvaluatesTo("!2", 0);
|
||||
assertEvaluatesTo("!-2", 0);
|
||||
}
|
||||
|
||||
public void testBoolAndOperation() throws Exception {
|
||||
assertEvaluatesTo("1 && 1", 1);
|
||||
assertEvaluatesTo("1 && 0", 0);
|
||||
assertEvaluatesTo("0 && 1", 0);
|
||||
assertEvaluatesTo("0 && 0", 0);
|
||||
assertEvaluatesTo("-1 && -1", 1);
|
||||
assertEvaluatesTo("-1 && 0", 0);
|
||||
assertEvaluatesTo("0 && -1", 0);
|
||||
assertEvaluatesTo("-0 && -0", 0);
|
||||
}
|
||||
|
||||
public void testBoolOrOperation() throws Exception {
|
||||
assertEvaluatesTo("1 || 1", 1);
|
||||
assertEvaluatesTo("1 || 0", 1);
|
||||
assertEvaluatesTo("0 || 1", 1);
|
||||
assertEvaluatesTo("0 || 0", 0);
|
||||
assertEvaluatesTo("-1 || -1", 1);
|
||||
assertEvaluatesTo("-1 || 0", 1);
|
||||
assertEvaluatesTo("0 || -1", 1);
|
||||
assertEvaluatesTo("-0 || -0", 0);
|
||||
}
|
||||
|
||||
public void testConditionalOperation() throws Exception {
|
||||
assertEvaluatesTo("1 ? 2 : 3", 2);
|
||||
assertEvaluatesTo("-1 ? 2 : 3", 2);
|
||||
assertEvaluatesTo("0 ? 2 : 3", 3);
|
||||
assertEvaluatesTo("1 ? 2 ? 3 : 4 : 5", 3);
|
||||
assertEvaluatesTo("0 ? 2 ? 3 : 4 : 5", 5);
|
||||
assertEvaluatesTo("1 ? 0 ? 3 : 4 : 5", 4);
|
||||
assertEvaluatesTo("1 ? 2 : 3 ? 4 : 5", 2);
|
||||
assertEvaluatesTo("0 ? 2 : 3 ? 4 : 5", 4);
|
||||
assertEvaluatesTo("0 ? 2 : 0 ? 4 : 5", 5);
|
||||
assertEvaluatesTo("(1 ? 1 : 0) ? 3 : 4", 3);
|
||||
assertEvaluatesTo("(0 ? 1 : 0) ? 3 : 4", 4);
|
||||
}
|
||||
|
||||
public void testBitShiftLeft() throws Exception {
|
||||
assertEvaluatesTo("1 << 1", 2);
|
||||
assertEvaluatesTo("2 << 1", 4);
|
||||
assertEvaluatesTo("-1 << 31", -2147483648);
|
||||
assertEvaluatesTo("3 << 5", 96);
|
||||
assertEvaluatesTo("-5 << 3", -40);
|
||||
assertEvaluatesTo("4195 << 7", 536960);
|
||||
assertEvaluatesTo("4195 << 66", 16780);
|
||||
assertEvaluatesTo("4195 << 6", 268480);
|
||||
assertEvaluatesTo("4195 << 70", 268480);
|
||||
assertEvaluatesTo("-4195 << 70", -268480);
|
||||
assertEvaluatesTo("-15 << 62", 4611686018427387904L);
|
||||
}
|
||||
|
||||
public void testBitShiftRight() throws Exception {
|
||||
assertEvaluatesTo("1 >> 1", 0);
|
||||
assertEvaluatesTo("2 >> 1", 1);
|
||||
assertEvaluatesTo("-1 >> 5", -1);
|
||||
assertEvaluatesTo("-2 >> 30", -1);
|
||||
assertEvaluatesTo("-5 >> 1", -3);
|
||||
assertEvaluatesTo("536960 >> 7", 4195);
|
||||
assertEvaluatesTo("16780 >> 66", 4195);
|
||||
assertEvaluatesTo("268480 >> 6", 4195);
|
||||
assertEvaluatesTo("268480 >> 70", 4195);
|
||||
assertEvaluatesTo("-268480 >> 70", -4195);
|
||||
assertEvaluatesTo("-2147483646 >> 1", -1073741823);
|
||||
}
|
||||
|
||||
public void testBitShiftRightUnsigned() throws Exception {
|
||||
assertEvaluatesTo("1 >>> 1", 0);
|
||||
assertEvaluatesTo("2 >>> 1", 1);
|
||||
assertEvaluatesTo("-1 >>> 37", 134217727);
|
||||
assertEvaluatesTo("-2 >>> 62", 3);
|
||||
assertEvaluatesTo("-5 >>> 33", 2147483647);
|
||||
assertEvaluatesTo("536960 >>> 7", 4195);
|
||||
assertEvaluatesTo("16780 >>> 66", 4195);
|
||||
assertEvaluatesTo("268480 >>> 6", 4195);
|
||||
assertEvaluatesTo("268480 >>> 70", 4195);
|
||||
assertEvaluatesTo("-268480 >>> 102", 67108863);
|
||||
assertEvaluatesTo("2147483648 >>> 1", 1073741824);
|
||||
}
|
||||
|
||||
public void testBitwiseAnd() throws Exception {
|
||||
assertEvaluatesTo("4 & 4", 4);
|
||||
assertEvaluatesTo("3 & 2", 2);
|
||||
assertEvaluatesTo("7 & 3", 3);
|
||||
assertEvaluatesTo("-1 & -1", -1);
|
||||
assertEvaluatesTo("-1 & 25", 25);
|
||||
assertEvaluatesTo("3 & 7", 3);
|
||||
assertEvaluatesTo("0 & 1", 0);
|
||||
assertEvaluatesTo("1 & 0", 0);
|
||||
}
|
||||
|
||||
public void testBitwiseOr() throws Exception {
|
||||
assertEvaluatesTo("4 | 4", 4);
|
||||
assertEvaluatesTo("5 | 2", 7);
|
||||
assertEvaluatesTo("7 | 3", 7);
|
||||
assertEvaluatesTo("-1 | -5", -1);
|
||||
assertEvaluatesTo("-1 | 25", -1);
|
||||
assertEvaluatesTo("-100 | 15", -97);
|
||||
assertEvaluatesTo("0 | 1", 1);
|
||||
assertEvaluatesTo("1 | 0", 1);
|
||||
}
|
||||
|
||||
public void testBitwiseXor() throws Exception {
|
||||
assertEvaluatesTo("4 ^ 4", 0);
|
||||
assertEvaluatesTo("5 ^ 2", 7);
|
||||
assertEvaluatesTo("15 ^ 3", 12);
|
||||
assertEvaluatesTo("-1 ^ -5", 4);
|
||||
assertEvaluatesTo("-1 ^ 25", -26);
|
||||
assertEvaluatesTo("-100 ^ 15", -109);
|
||||
assertEvaluatesTo("0 ^ 1", 1);
|
||||
assertEvaluatesTo("1 ^ 0", 1);
|
||||
assertEvaluatesTo("0 ^ 0", 0);
|
||||
}
|
||||
|
||||
public void testBitwiseNot() throws Exception {
|
||||
assertEvaluatesTo("~-5", 4);
|
||||
assertEvaluatesTo("~25", -26);
|
||||
assertEvaluatesTo("~0", -1);
|
||||
assertEvaluatesTo("~-1", 0);
|
||||
}
|
||||
|
||||
public void testDecimalConst() throws Exception {
|
||||
assertEvaluatesTo("0", 0);
|
||||
assertEvaluatesTo("1", 1);
|
||||
assertEvaluatesTo("123456789", 123456789);
|
||||
assertEvaluatesTo("5.6E2", 560);
|
||||
assertEvaluatesTo("5.6E+2", 560);
|
||||
assertEvaluatesTo("500E-2", 5);
|
||||
}
|
||||
|
||||
public void testHexConst() throws Exception {
|
||||
assertEvaluatesTo("0x0", 0);
|
||||
assertEvaluatesTo("0x1", 1);
|
||||
assertEvaluatesTo("0xF", 15);
|
||||
assertEvaluatesTo("0x1234ABCDEF", 78193085935L);
|
||||
assertEvaluatesTo("1 << 0x1", 1 << 0x1);
|
||||
assertEvaluatesTo("1 << 0xA", 1 << 0xA);
|
||||
assertEvaluatesTo("0x1 << 2", 0x1 << 2);
|
||||
assertEvaluatesTo("0xA << 2", 0xA << 2);
|
||||
}
|
||||
|
||||
public void testHexConst2() throws Exception {
|
||||
assertEvaluatesTo("0X0", 0);
|
||||
assertEvaluatesTo("0X1", 1);
|
||||
assertEvaluatesTo("0XF", 15);
|
||||
assertEvaluatesTo("0X1234ABCDEF", 78193085935L);
|
||||
}
|
||||
|
||||
public void testOctalConst() throws Exception {
|
||||
assertEvaluatesTo("00", 0);
|
||||
assertEvaluatesTo("01", 1);
|
||||
assertEvaluatesTo("010", 8);
|
||||
assertEvaluatesTo("0123456777", 21913087);
|
||||
assertEvaluatesTo("1 << 01", 1 << 01);
|
||||
assertEvaluatesTo("1 << 010", 1 << 010);
|
||||
assertEvaluatesTo("01 << 2", 01 << 2);
|
||||
assertEvaluatesTo("010 << 2", 010 << 2);
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
0baa82bff19059401e90e1b90020beb9c96305d7
|
|
@ -0,0 +1,7 @@
|
|||
Copyright (c) 2012 Terence Parr and Sam Harwell
|
||||
All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1 @@
|
|||
ad568238ee36a820bd6c6806807e8a14ea34684d
|
|
@ -0,0 +1,26 @@
|
|||
Copyright (c) 2012 France Télécom
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holders nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1 @@
|
|||
f8b86f4ee6e02082f63a658e00eb5506821253c6
|
|
@ -0,0 +1,26 @@
|
|||
Copyright (c) 2012 France Télécom
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holders nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -433,6 +433,28 @@
|
|||
<property name="codecs-javadocs.uptodate" value="true"/>
|
||||
</target>
|
||||
|
||||
<property name="expressions.jar" value="${common.dir}/build/expressions/lucene-expressions-${version}.jar"/>
|
||||
<target name="check-expressions-uptodate" unless="expressions.uptodate">
|
||||
<module-uptodate name="expressions" jarfile="${expressions.jar}" property="expressions.uptodate"/>
|
||||
</target>
|
||||
<target name="jar-expressions" unless="expressions.uptodate" depends="check-expressions-uptodate">
|
||||
<ant dir="${common.dir}/expressions" target="jar-core" inheritAll="false">
|
||||
<propertyset refid="uptodate.and.compiled.properties"/>
|
||||
</ant>
|
||||
<property name="expressions.uptodate" value="true"/>
|
||||
</target>
|
||||
|
||||
<property name="expressions-javadoc.jar" value="${common.dir}/build/expressions/lucene-expressions-${version}-javadoc.jar"/>
|
||||
<target name="check-expressions-javadocs-uptodate" unless="expressions-javadocs.uptodate">
|
||||
<module-uptodate name="expressions" jarfile="${expressions-javadoc.jar}" property="expressions-javadocs.uptodate"/>
|
||||
</target>
|
||||
<target name="javadocs-expressions" unless="expressions-javadocs.uptodate" depends="check-expressions-javadocs-uptodate">
|
||||
<ant dir="${common.dir}/expressions" target="javadocs" inheritAll="false">
|
||||
<propertyset refid="uptodate.and.compiled.properties"/>
|
||||
</ant>
|
||||
<property name="expressions-javadocs.uptodate" value="true"/>
|
||||
</target>
|
||||
|
||||
<property name="grouping.jar" value="${common.dir}/build/grouping/lucene-grouping-${version}.jar"/>
|
||||
<target name="check-grouping-uptodate" unless="grouping.uptodate">
|
||||
<module-uptodate name="grouping" jarfile="${grouping.jar}" property="grouping.uptodate"/>
|
||||
|
|
Loading…
Reference in New Issue